// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #include "div_free_solver.hpp" using namespace std; using namespace mfem; using namespace blocksolvers; void SetOptions(IterativeSolver& solver, const IterSolveParameters& param) { solver.SetPrintLevel(param.print_level); solver.SetMaxIter(param.max_iter); solver.SetAbsTol(param.abs_tol); solver.SetRelTol(param.rel_tol); } HypreParMatrix* TwoStepsRAP(const HypreParMatrix& Rt, const HypreParMatrix& A, const HypreParMatrix& P) { OperatorPtr R(Rt.Transpose()); OperatorPtr RA(ParMult(R.As(), &A)); return ParMult(RA.As(), &P, true); } void GetRowColumnsRef(const SparseMatrix& A, int row, Array& cols) { cols.MakeRef(const_cast(A.GetRowColumns(row)), A.RowSize(row)); } SparseMatrix ElemToDof(const ParFiniteElementSpace& fes) { int* I = new int[fes.GetNE()+1]; copy_n(fes.GetElementToDofTable().GetI(), fes.GetNE()+1, I); Array J(new int[I[fes.GetNE()]], I[fes.GetNE()]); copy_n(fes.GetElementToDofTable().GetJ(), J.Size(), J.begin()); fes.AdjustVDofs(J); double* D = new double[J.Size()]; fill_n(D, J.Size(), 1.0); return SparseMatrix(I, J, D, fes.GetNE(), fes.GetVSize()); } DFSSpaces::DFSSpaces(int order, int num_refine, ParMesh *mesh, const Array& ess_attr, const DFSParameters& param) : hdiv_fec_(order, mesh->Dimension()), l2_fec_(order, mesh->Dimension()), l2_0_fec_(0, mesh->Dimension()), ess_bdr_attr_(ess_attr), level_(0) { if (mesh->GetElement(0)->GetType() == Element::TETRAHEDRON && order) { mfem_error("DFSDataCollector: High order spaces on tetrahedra are not supported"); } data_.param = param; if (mesh->Dimension() == 3) { hcurl_fec_.reset(new ND_FECollection(order+1, mesh->Dimension())); } else { hcurl_fec_.reset(new H1_FECollection(order+1, mesh->Dimension())); } all_bdr_attr_.SetSize(ess_attr.Size(), 1); hdiv_fes_.reset(new ParFiniteElementSpace(mesh, &hdiv_fec_)); l2_fes_.reset(new ParFiniteElementSpace(mesh, &l2_fec_)); coarse_hdiv_fes_.reset(new ParFiniteElementSpace(*hdiv_fes_)); coarse_l2_fes_.reset(new ParFiniteElementSpace(*l2_fes_)); l2_0_fes_.reset(new ParFiniteElementSpace(mesh, &l2_0_fec_)); l2_0_fes_->SetUpdateOperatorType(Operator::MFEM_SPARSEMAT); el_l2dof_.reserve(num_refine+1); el_l2dof_.push_back(ElemToDof(*coarse_l2_fes_)); data_.agg_hdivdof.resize(num_refine); data_.agg_l2dof.resize(num_refine); data_.P_hdiv.resize(num_refine, OperatorPtr(Operator::Hypre_ParCSR)); data_.P_l2.resize(num_refine, OperatorPtr(Operator::Hypre_ParCSR)); data_.Q_l2.resize(num_refine); hdiv_fes_->GetEssentialTrueDofs(ess_attr, data_.coarsest_ess_hdivdofs); data_.C.resize(num_refine+1); hcurl_fes_.reset(new ParFiniteElementSpace(mesh, hcurl_fec_.get())); coarse_hcurl_fes_.reset(new ParFiniteElementSpace(*hcurl_fes_)); data_.P_hcurl.resize(num_refine, OperatorPtr(Operator::Hypre_ParCSR)); } SparseMatrix* AggToInteriorDof(const Array& bdr_truedofs, const SparseMatrix& agg_elem, const SparseMatrix& elem_dof, const HypreParMatrix& dof_truedof, Array& agg_starts) { OperatorPtr agg_dof(Mult(agg_elem, elem_dof)); SparseMatrix& agg_dof_ref = *agg_dof.As(); OperatorPtr agg_tdof(dof_truedof.LeftDiagMult(agg_dof_ref, agg_starts)); OperatorPtr agg_tdof_T(agg_tdof.As()->Transpose()); SparseMatrix tdof_agg, is_shared; HYPRE_BigInt* trash; agg_tdof_T.As()->GetDiag(tdof_agg); agg_tdof_T.As()->GetOffd(is_shared, trash); int * I = new int [tdof_agg.NumRows()+1](); int * J = new int[tdof_agg.NumNonZeroElems()]; Array is_bdr; FiniteElementSpace::ListToMarker(bdr_truedofs, tdof_agg.NumRows(), is_bdr); int counter = 0; for (int i = 0; i < tdof_agg.NumRows(); ++i) { bool agg_bdr = is_bdr[i] || is_shared.RowSize(i) || tdof_agg.RowSize(i)>1; if (agg_bdr) { I[i+1] = I[i]; continue; } I[i+1] = I[i] + 1; J[counter++] = tdof_agg.GetRowColumns(i)[0]; } double * D = new double[I[tdof_agg.NumRows()]]; std::fill_n(D, I[tdof_agg.NumRows()], 1.0); SparseMatrix intdof_agg(I, J, D, tdof_agg.NumRows(), tdof_agg.NumCols()); return Transpose(intdof_agg); } void DFSSpaces::MakeDofRelationTables(int level) { Array agg_starts(Array(l2_0_fes_->GetDofOffsets(), 2)); auto& elem_agg = (const SparseMatrix&)*l2_0_fes_->GetUpdateOperator(); OperatorPtr agg_elem(Transpose(elem_agg)); SparseMatrix& agg_el = *agg_elem.As(); el_l2dof_.push_back(ElemToDof(*l2_fes_)); data_.agg_l2dof[level].Reset(Mult(agg_el, el_l2dof_[level+1])); Array bdr_tdofs; hdiv_fes_->GetEssentialTrueDofs(all_bdr_attr_, bdr_tdofs); auto tmp = AggToInteriorDof(bdr_tdofs, agg_el, ElemToDof(*hdiv_fes_), *hdiv_fes_->Dof_TrueDof_Matrix(), agg_starts); data_.agg_hdivdof[level].Reset(tmp); } void DFSSpaces::CollectDFSData() { auto GetP = [this](OperatorPtr& P, unique_ptr& cfes, ParFiniteElementSpace& fes, bool remove_zero) { fes.Update(); fes.GetTrueTransferOperator(*cfes, P); if (remove_zero) { P.As()->DropSmallEntries(1e-16); } (level_ < (int)data_.P_l2.size()-1) ? cfes->Update() : cfes.reset(); }; GetP(data_.P_hdiv[level_], coarse_hdiv_fes_, *hdiv_fes_, true); GetP(data_.P_l2[level_], coarse_l2_fes_, *l2_fes_, false); MakeDofRelationTables(level_); GetP(data_.P_hcurl[level_], coarse_hcurl_fes_, *hcurl_fes_, true); Vector trash1(hcurl_fes_->GetVSize()), trash2(hdiv_fes_->GetVSize()); ParDiscreteLinearOperator curl(hcurl_fes_.get(), hdiv_fes_.get()); curl.AddDomainInterpolator(new CurlInterpolator); curl.Assemble(); curl.EliminateTrialDofs(ess_bdr_attr_, trash1, trash2); curl.Finalize(); data_.C[level_+1].Reset(curl.ParallelAssemble()); ++level_; if (level_ == (int)data_.P_l2.size()) { DataFinalize(); } } void DFSSpaces::DataFinalize() { ParBilinearForm mass(l2_fes_.get()); mass.AddDomainIntegrator(new MassIntegrator()); mass.Assemble(); mass.Finalize(); OperatorPtr W(mass.LoseMat()); SparseMatrix P_l2; for (int l = (int)data_.P_l2.size()-1; l >= 0; --l) { data_.P_l2[l].As()->GetDiag(P_l2); OperatorPtr PT_l2(Transpose(P_l2)); auto PTW = Mult(*PT_l2.As(), *W.As()); auto cW = Mult(*PTW, P_l2); auto cW_inv = new SymDirectSubBlockSolver(*cW, el_l2dof_[l]); data_.Q_l2[l].Reset(new ProductOperator(cW_inv, PTW, true, true)); W.Reset(cW); } l2_0_fes_.reset(); } BBTSolver::BBTSolver(const HypreParMatrix& B, IterSolveParameters param) : Solver(B.NumRows()), BBT_solver_(B.GetComm()) { OperatorPtr BT(B.Transpose()); BBT_.Reset(ParMult(&B, BT.As())); BBT_.As()->CopyColStarts(); BBT_prec_.Reset(new HypreBoomerAMG(*BBT_.As())); BBT_prec_.As()->SetPrintLevel(0); SetOptions(BBT_solver_, param); BBT_solver_.SetOperator(*BBT_); BBT_solver_.SetPreconditioner(*BBT_prec_.As()); } LocalSolver::LocalSolver(const DenseMatrix& M, const DenseMatrix& B) : Solver(M.NumRows()+B.NumRows()), local_system_(height), offset_(M.NumRows()) { local_system_.CopyMN(M, 0, 0); local_system_.CopyMN(B, offset_, 0); local_system_.CopyMNt(B, 0, offset_); local_system_.SetRow(offset_, 0.0); local_system_.SetCol(offset_, 0.0); local_system_(offset_, offset_) = -1.0; local_solver_.SetOperator(local_system_); } void LocalSolver::Mult(const Vector &x, Vector &y) const { const double x0 = x[offset_]; const_cast(x)[offset_] = 0.0; y.SetSize(local_system_.NumRows()); local_solver_.Mult(x, y); const_cast(x)[offset_] = x0; } SaddleSchwarzSmoother::SaddleSchwarzSmoother(const HypreParMatrix& M, const HypreParMatrix& B, const SparseMatrix& agg_hdivdof, const SparseMatrix& agg_l2dof, const HypreParMatrix& P_l2, const HypreParMatrix& Q_l2) : Solver(M.NumRows() + B.NumRows()), agg_hdivdof_(agg_hdivdof), agg_l2dof_(agg_l2dof), solvers_loc_(agg_l2dof.NumRows()) { coarse_l2_projector_.Reset(new ProductOperator(&P_l2, &Q_l2, false, false)); offsets_loc_.SetSize(3, 0); offsets_.SetSize(3, 0); offsets_[1] = M.NumRows(); offsets_[2] = M.NumRows() + B.NumRows(); SparseMatrix M_diag, B_diag; M.GetDiag(M_diag); B.GetDiag(B_diag); DenseMatrix B_loc, M_loc; for (int agg = 0; agg < (int)solvers_loc_.size(); agg++) { GetRowColumnsRef(agg_hdivdof_, agg, hdivdofs_loc_); GetRowColumnsRef(agg_l2dof_, agg, l2dofs_loc_); M_loc.SetSize(hdivdofs_loc_.Size(), hdivdofs_loc_.Size()); B_loc.SetSize(l2dofs_loc_.Size(), hdivdofs_loc_.Size()); M_diag.GetSubMatrix(hdivdofs_loc_, hdivdofs_loc_, M_loc); B_diag.GetSubMatrix(l2dofs_loc_, hdivdofs_loc_, B_loc); solvers_loc_[agg].Reset(new LocalSolver(M_loc, B_loc)); } } void SaddleSchwarzSmoother::Mult(const Vector & x, Vector & y) const { y.SetSize(offsets_[2]); y = 0.0; BlockVector blk_y(y.GetData(), offsets_); BlockVector Pi_x(offsets_); // aggregate-wise average free projection of x static_cast(Pi_x) = x; // Right hand side: F_l = F - W_l P_l2[l] (W_{l+1})^{-1} P_l2[l]^T F // This ensures the existence of solutions to the local problems Vector coarse_l2_projection(Pi_x.BlockSize(1)); coarse_l2_projector_->MultTranspose(Pi_x.GetBlock(1), coarse_l2_projection); Pi_x.GetBlock(1) -= coarse_l2_projection; for (int agg = 0; agg < (int)solvers_loc_.size(); agg++) { GetRowColumnsRef(agg_hdivdof_, agg, hdivdofs_loc_); GetRowColumnsRef(agg_l2dof_, agg, l2dofs_loc_); offsets_loc_[1] = hdivdofs_loc_.Size(); offsets_loc_[2] = offsets_loc_[1]+l2dofs_loc_.Size(); BlockVector rhs_loc(offsets_loc_), sol_loc(offsets_loc_); Pi_x.GetBlock(0).GetSubVector(hdivdofs_loc_, rhs_loc.GetBlock(0)); Pi_x.GetBlock(1).GetSubVector(l2dofs_loc_, rhs_loc.GetBlock(1)); solvers_loc_[agg]->Mult(rhs_loc, sol_loc); blk_y.GetBlock(0).AddElementVector(hdivdofs_loc_, sol_loc.GetBlock(0)); blk_y.GetBlock(1).AddElementVector(l2dofs_loc_, sol_loc.GetBlock(1)); } coarse_l2_projector_->Mult(blk_y.GetBlock(1), coarse_l2_projection); blk_y.GetBlock(1) -= coarse_l2_projection; } BDPMinresSolver::BDPMinresSolver(HypreParMatrix& M, HypreParMatrix& B, IterSolveParameters param) : DarcySolver(M.NumRows(), B.NumRows()), op_(offsets_), prec_(offsets_), BT_(B.Transpose()), solver_(M.GetComm()) { op_.SetBlock(0,0, &M); op_.SetBlock(0,1, BT_.As()); op_.SetBlock(1,0, &B); Vector Md; M.GetDiag(Md); BT_.As()->InvScaleRows(Md); S_.Reset(ParMult(&B, BT_.As())); BT_.As()->ScaleRows(Md); prec_.SetDiagonalBlock(0, new HypreDiagScale(M)); prec_.SetDiagonalBlock(1, new HypreBoomerAMG(*S_.As())); static_cast(prec_.GetDiagonalBlock(1)).SetPrintLevel(0); prec_.owns_blocks = true; SetOptions(solver_, param); solver_.SetOperator(op_); solver_.SetPreconditioner(prec_); } void BDPMinresSolver::Mult(const Vector & x, Vector & y) const { solver_.Mult(x, y); for (int dof : ess_zero_dofs_) { y[dof] = 0.0; } } DivFreeSolver::DivFreeSolver(const HypreParMatrix &M, const HypreParMatrix& B, const DFSData& data) : DarcySolver(M.NumRows(), B.NumRows()), data_(data), param_(data.param), BT_(B.Transpose()), BBT_solver_(B, param_.BBT_solve_param), ops_offsets_(data.P_l2.size()+1), ops_(ops_offsets_.size()), blk_Ps_(ops_.Size()-1), smoothers_(ops_.Size()) { ops_offsets_.back().MakeRef(DarcySolver::offsets_); ops_.Last() = new BlockOperator(ops_offsets_.back()); ops_.Last()->SetBlock(0, 0, const_cast(&M)); ops_.Last()->SetBlock(1, 0, const_cast(&B)); ops_.Last()->SetBlock(0, 1, BT_.Ptr()); for (int l = data.P_l2.size(); l >= 0; --l) { auto& M_f = static_cast(ops_[l]->GetBlock(0, 0)); auto& B_f = static_cast(ops_[l]->GetBlock(1, 0)); if (l == 0) { SparseMatrix M_f_diag, B_f_diag; M_f.GetDiag(M_f_diag); B_f.GetDiag(B_f_diag); for (int dof : data.coarsest_ess_hdivdofs) { M_f_diag.EliminateRowCol(dof); B_f_diag.EliminateCol(dof); } const IterSolveParameters& param = param_.coarse_solve_param; auto coarse_solver = new BDPMinresSolver(M_f, B_f, param); if (ops_.Size() > 1) { coarse_solver->SetEssZeroDofs(data.coarsest_ess_hdivdofs); } smoothers_[l] = coarse_solver; continue; } HypreParMatrix& P_hdiv_l = *data.P_hdiv[l-1].As(); HypreParMatrix& P_l2_l = *data.P_l2[l-1].As(); SparseMatrix& agg_hdivdof_l = *data.agg_hdivdof[l-1].As(); SparseMatrix& agg_l2dof_l = *data.agg_l2dof[l-1].As(); HypreParMatrix& Q_l2_l = *data.Q_l2[l-1].As(); HypreParMatrix* C_l = data.C[l].As(); auto S0 = new SaddleSchwarzSmoother(M_f, B_f, agg_hdivdof_l, agg_l2dof_l, P_l2_l, Q_l2_l); if (param_.coupled_solve) { auto S1 = new BlockDiagonalPreconditioner(ops_offsets_[l]); S1->SetDiagonalBlock(0, new AuxSpaceSmoother(M_f, C_l)); S1->owns_blocks = true; smoothers_[l] = new ProductSolver(ops_[l], S0, S1, false, true, true); } else { smoothers_[l] = S0; } HypreParMatrix* M_c = TwoStepsRAP(P_hdiv_l, M_f, P_hdiv_l); HypreParMatrix* B_c = TwoStepsRAP(P_l2_l, B_f, P_hdiv_l); ops_offsets_[l-1].SetSize(3, 0); ops_offsets_[l-1][1] = M_c->NumRows(); ops_offsets_[l-1][2] = M_c->NumRows() + B_c->NumRows(); blk_Ps_[l-1] = new BlockOperator(ops_offsets_[l], ops_offsets_[l-1]); blk_Ps_[l-1]->SetBlock(0, 0, &P_hdiv_l); blk_Ps_[l-1]->SetBlock(1, 1, &P_l2_l); ops_[l-1] = new BlockOperator(ops_offsets_[l-1]); ops_[l-1]->SetBlock(0, 0, M_c); ops_[l-1]->SetBlock(1, 0, B_c); ops_[l-1]->SetBlock(0, 1, B_c->Transpose()); ops_[l-1]->owns_blocks = true; } Array own_ops(ops_.Size()); Array own_smoothers(smoothers_.Size()); Array own_Ps(blk_Ps_.Size()); own_ops = true; own_smoothers = true; own_Ps = true; if (data_.P_l2.size() == 0) { return; } if (param_.coupled_solve) { solver_.Reset(new GMRESSolver(B.GetComm())); solver_.As()->SetOperator(*(ops_.Last())); prec_.Reset(new Multigrid(ops_, smoothers_, blk_Ps_, own_ops, own_smoothers, own_Ps)); } else { Array ops(data_.P_hcurl.size()+1); Array smoothers(ops.Size()); Array Ps(data_.P_hcurl.size()); own_Ps = false; HypreParMatrix& C_finest = *data.C.back().As(); ops.Last() = TwoStepsRAP(C_finest, M, C_finest); ops.Last()->EliminateZeroRows(); ops.Last()->DropSmallEntries(1e-14); solver_.Reset(new CGSolver(B.GetComm())); solver_.As()->SetOperator(*ops.Last()); smoothers.Last() = new HypreSmoother(*ops.Last()); static_cast(smoothers.Last())->SetOperatorSymmetry(true); for (int l = Ps.Size()-1; l >= 0; --l) { Ps[l] = data_.P_hcurl[l].As(); ops[l] = TwoStepsRAP(*Ps[l], *ops[l+1], *Ps[l]); ops[l]->DropSmallEntries(1e-14); smoothers[l] = new HypreSmoother(*ops[l]); static_cast(smoothers[l])->SetOperatorSymmetry(true); } prec_.Reset(new Multigrid(ops, smoothers, Ps, own_ops, own_smoothers, own_Ps)); } solver_.As()->SetPreconditioner(*prec_.As()); SetOptions(*solver_.As(), param_); } DivFreeSolver::~DivFreeSolver() { if (param_.coupled_solve) { return; } for (int i = 0; i < ops_.Size(); ++i) { delete ops_[i]; delete smoothers_[i]; if (i == ops_.Size() - 1) { break; } delete blk_Ps_[i]; } } void DivFreeSolver::SolveParticular(const Vector& rhs, Vector& sol) const { std::vector rhss(smoothers_.Size()); std::vector sols(smoothers_.Size()); rhss.back().SetDataAndSize(const_cast(rhs.HostRead()), rhs.Size()); sols.back().SetDataAndSize(sol.HostWrite(), sol.Size()); for (int l = blk_Ps_.Size()-1; l >= 0; --l) { rhss[l].SetSize(blk_Ps_[l]->NumCols()); sols[l].SetSize(blk_Ps_[l]->NumCols()); sols[l] = 0.0; rhss[l] = 0.0; blk_Ps_[l]->MultTranspose(rhss[l+1], rhss[l]); } for (int l = 0; l < smoothers_.Size(); ++l) { smoothers_[l]->Mult(rhss[l], sols[l]); } for (int l = 0; l < blk_Ps_.Size(); ++l) { Vector P_sol(blk_Ps_[l]->NumRows()); blk_Ps_[l]->Mult(sols[l], P_sol); sols[l+1] += P_sol; } } void DivFreeSolver::SolveDivFree(const Vector &rhs, Vector& sol) const { Vector rhs_divfree(data_.C.back()->NumCols()); data_.C.back()->MultTranspose(rhs, rhs_divfree); Vector potential_divfree(rhs_divfree.Size()); potential_divfree = 0.0; solver_->Mult(rhs_divfree, potential_divfree); data_.C.back()->Mult(potential_divfree, sol); } void DivFreeSolver::SolvePotential(const Vector& rhs, Vector& sol) const { Vector rhs_p(BT_->NumCols()); BT_->MultTranspose(rhs, rhs_p); BBT_solver_.Mult(rhs_p, sol); } void DivFreeSolver::Mult(const Vector & x, Vector & y) const { MFEM_VERIFY(x.Size() == offsets_[2], "MLDivFreeSolver: x size is invalid"); MFEM_VERIFY(y.Size() == offsets_[2], "MLDivFreeSolver: y size is invalid"); if (ops_.Size() == 1) { smoothers_[0]->Mult(x, y); return; } BlockVector blk_y(y, offsets_); BlockVector resid(offsets_); ops_.Last()->Mult(y, resid); add(1.0, x, -1.0, resid, resid); BlockVector correction(offsets_); correction = 0.0; if (param_.coupled_solve) { solver_->Mult(resid, correction); y += correction; } else { StopWatch ch; ch.Start(); SolveParticular(resid, correction); blk_y += correction; if (param_.verbose) { cout << "Particular solution found in " << ch.RealTime() << "s.\n"; } ch.Clear(); ch.Start(); ops_.Last()->Mult(y, resid); add(1.0, x, -1.0, resid, resid); SolveDivFree(resid.GetBlock(0), correction.GetBlock(0)); blk_y.GetBlock(0) += correction.GetBlock(0); if (param_.verbose) { cout << "Divergence free solution found in " << ch.RealTime() << "s.\n"; } ch.Clear(); ch.Start(); auto M = dynamic_cast(ops_.Last()->GetBlock(0, 0)); M.Mult(-1.0, correction.GetBlock(0), 1.0, resid.GetBlock(0)); SolvePotential(resid.GetBlock(0), correction.GetBlock(1)); blk_y.GetBlock(1) += correction.GetBlock(1); if (param_.verbose) { cout << "Scalar potential found in " << ch.RealTime() << "s.\n"; } } } int DivFreeSolver::GetNumIterations() const { if (ops_.Size() == 1) { return static_cast(smoothers_[0])->GetNumIterations(); } return solver_.As()->GetNumIterations(); }