// 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 "../config/config.hpp" #ifdef MFEM_USE_MPI #include "linalg.hpp" #include "../fem/pfespace.hpp" #include "../fem/pbilinearform.hpp" namespace mfem { GeneralAMS::GeneralAMS(const Operator& curlcurl_op_, const Operator& pi_, const Operator& gradient_, const Operator& pispacesolver_, const Operator& gspacesolver_, const Operator& smoother_, const Array& ess_tdof_list_) : Solver(curlcurl_op_.Height()), curlcurl_op(curlcurl_op_), pi(pi_), gradient(gradient_), pispacesolver(pispacesolver_), gspacesolver(gspacesolver_), smoother(smoother_), ess_tdof_list(ess_tdof_list_) { } GeneralAMS::~GeneralAMS() { } void GeneralAMS::FormResidual(const Vector& rhs, const Vector& x, Vector& residual) const { curlcurl_op.Mult(x, residual); residual *= -1.0; residual += rhs; } /* This implementation follows that in hypre, see hypre_ParCSRSubspacePrec() in hypre/src/parcsr_ls/ams.c and also hypre_AMSSolve() in the same file. hypre's default cycle (cycle 1) is "01210", ie, smooth, correct in space 1, correct in space 2, correct in space 1, smooth. Their space 1 is G and space 2 is Pi by default. The MFEM interface in mfem::HypreAMS though picks cycle 13, or 034515430, which separates the Pi-space solve into three separate (scalar) AMG solves instead of a single vector solve. We choose below the hypre default, but we have experimented with some other cycles; the short version is that they often work but the differences are generally not large. */ void GeneralAMS::Mult(const Vector& x, Vector& y) const { MFEM_ASSERT(x.Size() == y.Size(), "Sizes don't match!"); MFEM_ASSERT(curlcurl_op.Height() == x.Size(), "Sizes don't match!"); Vector residual(x.Size()); residual = 0.0; y = 0.0; // smooth smoother.Mult(x, y); // g-space correction FormResidual(x, y, residual); Vector gspacetemp(gradient.Width()); gradient.MultTranspose(residual, gspacetemp); Vector gspacecorrection(gradient.Width()); gspacecorrection = 0.0; gspacesolver.Mult(gspacetemp, gspacecorrection); gradient.Mult(gspacecorrection, residual); y += residual; // pi-space correction FormResidual(x, y, residual); Vector pispacetemp(pi.Width()); pi.MultTranspose(residual, pispacetemp); Vector pispacecorrection(pi.Width()); pispacecorrection = 0.0; pispacesolver.Mult(pispacetemp, pispacecorrection); pi.Mult(pispacecorrection, residual); y += residual; // g-space correction FormResidual(x, y, residual); gradient.MultTranspose(residual, gspacetemp); gspacecorrection = 0.0; gspacesolver.Mult(gspacetemp, gspacecorrection); gradient.Mult(gspacecorrection, residual); y += residual; // smooth FormResidual(x, y, residual); Vector temp(x.Size()); smoother.Mult(residual, temp); y += temp; } // Pi-space constructor MatrixFreeAuxiliarySpace::MatrixFreeAuxiliarySpace( ParMesh& mesh_lor, Coefficient* alpha_coeff, Coefficient* beta_coeff, MatrixCoefficient* beta_mcoeff, Array& ess_bdr, Operator& curlcurl_oper, Operator& pi, #ifdef MFEM_USE_AMGX bool useAmgX_, #endif int cg_iterations) : Solver(pi.Width()), comm(mesh_lor.GetComm()), matfree(NULL), cg(NULL), #ifdef MFEM_USE_AMGX useAmgX(useAmgX_), #endif inner_aux_iterations(0) { H1_FECollection * fec_lor = new H1_FECollection(1, mesh_lor.Dimension()); ParFiniteElementSpace fespace_lor_d(&mesh_lor, fec_lor, mesh_lor.Dimension(), Ordering::byVDIM); // build LOR AMG v-cycle if (ess_bdr.Size()) { fespace_lor_d.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); } ParBilinearForm a_lor(&fespace_lor_d); // this choice of policy is important for the G-space solver, but // also can make some difference here const Matrix::DiagonalPolicy policy = Matrix::DIAG_KEEP; a_lor.SetDiagonalPolicy(policy); if (alpha_coeff == NULL) { a_lor.AddDomainIntegrator(new VectorDiffusionIntegrator); } else { a_lor.AddDomainIntegrator(new VectorDiffusionIntegrator(*alpha_coeff)); } if (beta_mcoeff != NULL) { MFEM_VERIFY(beta_coeff == NULL, "Only one beta coefficient should be defined."); a_lor.AddDomainIntegrator(new VectorMassIntegrator(*beta_mcoeff)); } else if (beta_coeff != NULL) { a_lor.AddDomainIntegrator(new VectorMassIntegrator(*beta_coeff)); } else { a_lor.AddDomainIntegrator(new VectorMassIntegrator); } a_lor.UsePrecomputedSparsity(); a_lor.Assemble(); a_lor.EliminateEssentialBC(ess_bdr, policy); a_lor.Finalize(); lor_matrix = a_lor.ParallelAssemble(); lor_matrix->CopyRowStarts(); lor_matrix->CopyColStarts(); SetupAMG(fespace_lor_d.GetMesh()->Dimension()); if (cg_iterations > 0) { SetupCG(curlcurl_oper, pi, cg_iterations); } else { SetupVCycle(); } delete fec_lor; } /* G-space constructor The auxiliary space solves in general, and this one in particular, seem to be quite sensitive to handling of boundary conditions. Note some careful choices for Matrix::DiagonalPolicy and the ZeroWrapAMG object, as well as the use of a single CG iteration (instead of just an AMG V-cycle). Just a V-cycle may be more efficient in some cases, but we recommend the CG wrapper for robustness here. */ MatrixFreeAuxiliarySpace::MatrixFreeAuxiliarySpace( ParMesh& mesh_lor, Coefficient* beta_coeff, MatrixCoefficient* beta_mcoeff, Array& ess_bdr, Operator& curlcurl_oper, Operator& g, #ifdef MFEM_USE_AMGX bool useAmgX_, #endif int cg_iterations) : Solver(curlcurl_oper.Height()), comm(mesh_lor.GetComm()), matfree(NULL), cg(NULL), #ifdef MFEM_USE_AMGX useAmgX(useAmgX_), #endif inner_aux_iterations(0) { H1_FECollection * fec_lor = new H1_FECollection(1, mesh_lor.Dimension()); ParFiniteElementSpace fespace_lor(&mesh_lor, fec_lor); // build LOR AMG v-cycle ParBilinearForm a_lor(&fespace_lor); // we need something like DIAG_ZERO in the solver, but explicitly doing // that makes BoomerAMG setup complain, so instead we constrain the boundary // in the CG solver const Matrix::DiagonalPolicy policy = Matrix::DIAG_ONE; a_lor.SetDiagonalPolicy(policy); if (beta_mcoeff != NULL) { MFEM_VERIFY(beta_coeff == NULL, "Only one beta coefficient should be defined."); a_lor.AddDomainIntegrator(new DiffusionIntegrator(*beta_mcoeff)); } else if (beta_coeff != NULL) { a_lor.AddDomainIntegrator(new DiffusionIntegrator(*beta_coeff)); } else { a_lor.AddDomainIntegrator(new DiffusionIntegrator); } a_lor.UsePrecomputedSparsity(); a_lor.Assemble(); if (ess_bdr.Size()) { fespace_lor.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); } // you have to use (serial) BilinearForm eliminate routines to get // diag policy DIAG_ZERO all the ParallelEliminateTDofs etc. routines // implicitly have a Matrix::DIAG_KEEP policy a_lor.EliminateEssentialBC(ess_bdr, policy); a_lor.Finalize(); lor_matrix = a_lor.ParallelAssemble(); lor_matrix->CopyRowStarts(); lor_matrix->CopyColStarts(); SetupAMG(0); if (cg_iterations > 0) { SetupCG(curlcurl_oper, g, cg_iterations); } else { SetupVCycle(); } delete fec_lor; } void MatrixFreeAuxiliarySpace::SetupCG( Operator& curlcurl_oper, Operator& conn, int inner_cg_iterations) { MFEM_ASSERT(conn.Height() == curlcurl_oper.Width(), "Operators don't match!"); matfree = new RAPOperator(conn, curlcurl_oper, conn); MFEM_ASSERT(matfree->Height() == lor_pc->Height(), "Operators don't match!"); cg = new CGSolver(comm); cg->SetOperator(*matfree); cg->SetPreconditioner(*lor_pc); if (inner_cg_iterations > 99) { cg->SetRelTol(1.e-14); cg->SetMaxIter(100); } else { cg->SetRelTol(0.0); cg->SetMaxIter(inner_cg_iterations); } cg->SetPrintLevel(-1); aspacewrapper = cg; } void MatrixFreeAuxiliarySpace::SetupVCycle() { aspacewrapper = lor_pc; } class ZeroWrapAMG : public Solver { public: #ifdef MFEM_USE_AMGX ZeroWrapAMG(HypreParMatrix& mat, Array& ess_tdof_list_, const bool useAmgX) : #else ZeroWrapAMG(HypreParMatrix& mat, Array& ess_tdof_list_) : #endif Solver(mat.Height()), ess_tdof_list(ess_tdof_list_) { #ifdef MFEM_USE_AMGX if (useAmgX) { const bool amgx_verbose = false; AmgXSolver *amgx = new AmgXSolver(mat.GetComm(), AmgXSolver::PRECONDITIONER, amgx_verbose); amgx->SetOperator(mat); amg_ = amgx; } else #endif { HypreBoomerAMG *amg = new HypreBoomerAMG(mat); amg->SetPrintLevel(0); amg_ = amg; } } void Mult(const Vector& x, Vector& y) const { amg_->Mult(x, y); auto Y = y.HostReadWrite(); for (int k : ess_tdof_list) { Y[k] = 0.0; } } void SetOperator(const Operator&) { } ~ZeroWrapAMG() { delete amg_; } private: Solver *amg_ = NULL; Array& ess_tdof_list; }; void MatrixFreeAuxiliarySpace::SetupAMG(int system_dimension) { if (system_dimension == 0) { // boundary condition tweak for G-space solver #ifdef MFEM_USE_AMGX lor_pc = new ZeroWrapAMG(*lor_matrix, ess_tdof_list, useAmgX); #else lor_pc = new ZeroWrapAMG(*lor_matrix, ess_tdof_list); #endif } else { // systems options for Pi-space solver #ifdef MFEM_USE_AMGX if (useAmgX) { const bool amgx_verbose = false; AmgXSolver *amgx = new AmgXSolver(lor_matrix->GetComm(), AmgXSolver::PRECONDITIONER, amgx_verbose); amgx->SetOperator(*lor_matrix); lor_pc = amgx; } else #endif { HypreBoomerAMG* hpc = new HypreBoomerAMG(*lor_matrix); hpc->SetSystemsOptions(system_dimension); hpc->SetPrintLevel(0); lor_pc = hpc; } } } void MatrixFreeAuxiliarySpace::Mult(const Vector& x, Vector& y) const { int rank; MPI_Comm_rank(comm, &rank); y = 0.0; aspacewrapper->Mult(x, y); if (cg && rank == 0) { int q = cg->GetNumIterations(); inner_aux_iterations += q; } } MatrixFreeAuxiliarySpace::~MatrixFreeAuxiliarySpace() { delete lor_matrix; delete lor_pc; delete matfree; if (lor_pc != aspacewrapper) { delete aspacewrapper; } if (cg != aspacewrapper) { delete cg; } } /* As an implementation note, a lot depends on the quality of the auxiliary space solves. For high-contrast coefficients, and other difficult problems, inner iteration counts may need to be increased. Boundary conditions can matter as well (see DIAG_ZERO policy). */ MatrixFreeAMS::MatrixFreeAMS( ParBilinearForm& aform, Operator& oper, ParFiniteElementSpace& nd_fespace, Coefficient* alpha_coeff, Coefficient* beta_coeff, MatrixCoefficient* beta_mcoeff, Array& ess_bdr, #ifdef MFEM_USE_AMGX bool useAmgX, #endif int inner_pi_iterations, int inner_g_iterations, Solver * nd_smoother) : Solver(oper.Height()) { int order = nd_fespace.GetFE(0)->GetOrder(); ParMesh *mesh = nd_fespace.GetParMesh(); int dim = mesh->Dimension(); // smoother Array ess_tdof_list; nd_fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); if (nd_smoother) { smoother = nd_smoother; } else { const double scale = 0.25; smoother = new OperatorJacobiSmoother(aform, ess_tdof_list, scale); } // get H1 space FiniteElementCollection *h1_fec = new H1_FECollection(order, dim); h1_fespace = new ParFiniteElementSpace(mesh, h1_fec); h1_fespace_d = new ParFiniteElementSpace(mesh, h1_fec, dim, Ordering::byVDIM); // build G operator pa_grad = new ParDiscreteLinearOperator(h1_fespace, &nd_fespace); pa_grad->SetAssemblyLevel(AssemblyLevel::PARTIAL); pa_grad->AddDomainInterpolator(new GradientInterpolator); pa_grad->Assemble(); pa_grad->FormRectangularSystemMatrix(Gradient); // build Pi operator pa_interp = new ParDiscreteLinearOperator(h1_fespace_d, &nd_fespace); pa_interp->SetAssemblyLevel(AssemblyLevel::PARTIAL); pa_interp->AddDomainInterpolator(new IdentityInterpolator); pa_interp->Assemble(); pa_interp->FormRectangularSystemMatrix(Pi); // build LOR space ParMesh mesh_lor = ParMesh::MakeRefined(*mesh, order, BasisType::GaussLobatto); // build G space solver Gspacesolver = new MatrixFreeAuxiliarySpace(mesh_lor, beta_coeff, beta_mcoeff, ess_bdr, oper, *Gradient, #ifdef MFEM_USE_AMGX useAmgX, #endif inner_g_iterations); // build Pi space solver Pispacesolver = new MatrixFreeAuxiliarySpace(mesh_lor, alpha_coeff, beta_coeff, beta_mcoeff, ess_bdr, oper, *Pi, #ifdef MFEM_USE_AMGX useAmgX, #endif inner_pi_iterations); general_ams = new GeneralAMS(oper, *Pi, *Gradient, *Pispacesolver, *Gspacesolver, *smoother, ess_tdof_list); delete h1_fec; } MatrixFreeAMS::~MatrixFreeAMS() { delete smoother; delete pa_grad; delete pa_interp; delete Gspacesolver; delete Pispacesolver; delete general_ams; delete h1_fespace; delete h1_fespace_d; } } // namespace mfem #endif