Files
mfem/linalg/auxiliary.cpp
T

514 lines
14 KiB
C++

// Copyright (c) 2010-2025, 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<int>& 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<int>& 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<int>& 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<int>& ess_tdof_list_,
const bool useAmgX) :
#else
ZeroWrapAMG(HypreParMatrix& mat, Array<int>& 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 override
{
amg_->Mult(x, y);
auto Y = y.HostReadWrite();
for (int k : ess_tdof_list)
{
Y[k] = 0.0;
}
}
void SetOperator(const Operator&) override { }
~ZeroWrapAMG() override
{
delete amg_;
}
private:
Solver *amg_ = NULL;
Array<int>& 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<int>& 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.GetTypicalFE()->GetOrder();
ParMesh *mesh = nd_fespace.GetParMesh();
int dim = mesh->Dimension();
// smoother
Array<int> 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