LCOV - code coverage report
Current view: top level - src/mfem/solvers - MFEMGeometricMultigridSolver.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #33390 (250e9c) with base 846a5c Lines: 104 114 91.2 %
Date: 2026-07-31 18:15:22 Functions: 10 11 90.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #ifdef MOOSE_MFEM_ENABLED
      11             : 
      12             : #include "MFEMGeometricMultigridSolver.h"
      13             : #include "MFEMProblem.h"
      14             : #include "EquationSystem.h"
      15             : 
      16             : registerMooseObject("MooseApp", MFEMGeometricMultigridSolver);
      17             : 
      18           9 : MFEMGeometricMultigridSolver::MGProxy::MGProxy(MFEMGeometricMultigridSolver & owner) : _owner(owner)
      19             : {
      20             :   // MGProxy is installed as a preconditioner, so Mult() should overwrite its output vector rather
      21             :   // than treating it as an initial iterate. This matches MFEM's
      22             :   // IterativeSolver::SetPreconditioner() convention, which sets the preconditioner's iterative_mode
      23             :   // to false.
      24           9 :   iterative_mode = false;
      25           9 : }
      26             : 
      27             : void
      28           7 : MFEMGeometricMultigridSolver::MGProxy::SetMG(mfem::GeometricMultigrid & mg)
      29             : {
      30           7 :   _mg = &mg;
      31           7 :   height = mg.Height();
      32           7 :   width = mg.Width();
      33           7 : }
      34             : 
      35             : void
      36           9 : MFEMGeometricMultigridSolver::MGProxy::SetOperator(const mfem::Operator & op)
      37             : {
      38           9 :   _owner.BuildMultigrid(op);
      39           7 : }
      40             : 
      41             : void
      42          35 : MFEMGeometricMultigridSolver::MGProxy::Mult(const mfem::Vector & x, mfem::Vector & y) const
      43             : {
      44          35 :   MFEM_VERIFY(_mg, "MGProxy: GeometricMultigrid not yet built");
      45          35 :   _mg->Mult(x, y);
      46          35 : }
      47             : 
      48             : InputParameters
      49        2146 : MFEMGeometricMultigridSolver::validParams()
      50             : {
      51        2146 :   InputParameters params = Moose::MFEM::LinearSolverBase::validParams();
      52        4292 :   params.addClassDescription(
      53             :       "Geometric (p-)multigrid preconditioner backed by mfem::GeometricMultigrid. "
      54             :       "Requires a linear equation system, an MFEMFESpaceHierarchy, and per-level smoother "
      55             :       "objects.");
      56             : 
      57        8584 :   params.addRequiredParam<std::string>("variable",
      58             :                                        "Name of the trial variable this preconditioner acts on.");
      59        8584 :   params.addRequiredParam<std::vector<MFEMSolverName>>(
      60             :       "smoothers",
      61             :       "Names of LinearSolverBase objects used as smoothers on the interior levels "
      62             :       "(levels 1 to N-1). May have length 1 (used on all interior levels) or "
      63             :       "N-1 (one per interior level, ordered coarse-to-fine).");
      64        8584 :   params.addRequiredParam<MFEMSolverName>(
      65             :       "coarse_solver", "Name of the LinearSolverBase used on the coarsest level.");
      66        8584 :   params.addParam<std::vector<std::string>>(
      67             :       "assembly_levels",
      68             :       {"legacy"},
      69             :       "Assembly level for each level in the hierarchy. Valid values: 'legacy', 'full', "
      70             :       "'element', 'partial', 'none'. May have length 1 (used on all N levels) or N.");
      71        2146 :   return params;
      72        6438 : }
      73             : 
      74          11 : MFEMGeometricMultigridSolver::MFEMGeometricMultigridSolver(const InputParameters & parameters)
      75             :   : Moose::MFEM::LinearSolverBase(parameters),
      76          11 :     _var_name(getParam<std::string>("variable")),
      77          22 :     _smoother_names(getParam<std::vector<MFEMSolverName>>("smoothers")),
      78          55 :     _coarse_solver_name(getParam<MFEMSolverName>("coarse_solver"))
      79             : {
      80          11 :   auto & problem = getMFEMProblem();
      81          11 :   auto eq_sys = problem.getProblemData().eqn_system;
      82             : 
      83          11 :   if (eq_sys->IsEigen() || eq_sys->IsComplex())
      84           2 :     mooseError("GeometricMultigridSolver '", name(), "': requires a real, non-eigen eq. system");
      85             : 
      86             :   // Co-own the hierarchy so it outlives this solver.
      87          27 :   if (auto * hierarchy_name = problem.getMFEMObject<MFEMVariable>("MooseVariableBase", _var_name)
      88          36 :                                   .queryParam<std::string>("fespace_hierarchy"))
      89           9 :     _hierarchy = problem.getProblemData().fespace_hierarchies.GetShared(*hierarchy_name);
      90             :   else
      91           0 :     paramError("variable", "must be associated with an MFEMFESpaceHierarchy.");
      92             : 
      93             :   // Parse assembly levels, optionally expanding a single input value to all levels.
      94           9 :   const int N = _hierarchy->GetNumLevels();
      95             :   mooseAssert(N, "Malformed MFEMFESpaceHierarchy w/ no levels");
      96          18 :   const auto & asm_strs = getParam<std::vector<std::string>>("assembly_levels");
      97           9 :   const int n_asm = asm_strs.size();
      98           9 :   if (n_asm != 1 && n_asm != N)
      99           0 :     paramError(
     100             :         "assembly_levels", "must have length 1 or N = ", N, " (total levels), got ", n_asm, ".");
     101             : 
     102           9 :   _assembly_levels.resize(N);
     103          27 :   for (const auto i : make_range(N))
     104          18 :     _assembly_levels[i] = ParseAssemblyLevel(n_asm == 1 ? asm_strs[0] : asm_strs[i]);
     105             : 
     106           9 :   ConstructSolver();
     107           9 : }
     108             : 
     109             : void
     110           9 : MFEMGeometricMultigridSolver::ConstructSolver()
     111             : {
     112           9 :   _mg.reset();
     113           9 :   _level_ops.clear();
     114           9 :   _level_blfs.clear();
     115             : 
     116           9 :   auto proxy = std::make_unique<MGProxy>(*this);
     117           9 :   _mg_proxy = proxy.get();
     118           9 :   _solver = std::move(proxy);
     119           9 : }
     120             : 
     121             : mfem::AssemblyLevel
     122          18 : MFEMGeometricMultigridSolver::ParseAssemblyLevel(const std::string & s) const
     123             : {
     124          54 :   static MooseEnum assembly_levels("legacy full element partial none", "legacy");
     125          18 :   return (assembly_levels = s).getEnum<mfem::AssemblyLevel>();
     126             : }
     127             : 
     128             : void
     129           0 : MFEMGeometricMultigridSolver::SetOperator(mfem::Operator & op)
     130             : {
     131           0 :   BuildMultigrid(op);
     132           0 : }
     133             : 
     134             : void
     135           9 : MFEMGeometricMultigridSolver::BuildMultigrid(const mfem::Operator & op)
     136             : {
     137           9 :   auto & problem = getMFEMProblem();
     138           9 :   auto eq_sys = problem.getProblemData().eqn_system;
     139             : 
     140           9 :   if (eq_sys->IsNonlinear() || eq_sys->IsMultivariate())
     141           2 :     mooseError("GeometricMultigridSolver '", name(), "': requires a univariate, linear eq. system");
     142             : 
     143           7 :   const int N = _hierarchy->GetNumLevels();
     144           7 :   const int finest_level = _hierarchy->GetFinestLevelIndex();
     145             : 
     146             :   // Validate smoother vector length (levels 1 to N-1 each need a smoother).
     147           7 :   const int n_smooth = _smoother_names.size();
     148           7 :   if (n_smooth != 1 && n_smooth != N - 1)
     149           0 :     paramError("smoothers", "must have length 1 or N-1 = ", N - 1, ", got ", n_smooth, ".");
     150             : 
     151          14 :   auto get_smoother = [&](int level) -> Moose::MFEM::LinearSolverBase &
     152             :   {
     153          14 :     if (level == 0)
     154          28 :       return problem.getMFEMObject<Moose::MFEM::LinearSolverBase>("Moose::MFEM::SolverBase",
     155           7 :                                                                   _coarse_solver_name);
     156           7 :     const std::string & sname = (n_smooth == 1) ? _smoother_names[0] : _smoother_names[level - 1];
     157          21 :     return problem.getMFEMObject<Moose::MFEM::LinearSolverBase>("Moose::MFEM::SolverBase", sname);
     158           7 :   };
     159             : 
     160             :   // Obtain essential boundary attribute markers from the equation system.
     161           7 :   mfem::Array<int> & ess_bdr = eq_sys->GetEssentialBoundaryMarkers(_var_name);
     162             : 
     163             :   auto & finest_fespace =
     164           7 :       static_cast<mfem::ParFiniteElementSpace &>(_hierarchy->GetFESpaceAtLevel(finest_level));
     165           7 :   const int finest_size = finest_fespace.GetTrueVSize();
     166           7 :   if (op.Height() != finest_size || op.Width() != finest_size)
     167           0 :     mooseError("GeometricMultigridSolver '",
     168           0 :                name(),
     169             :                "': incoming fine operator has size ",
     170           0 :                op.Height(),
     171             :                " x ",
     172           0 :                op.Width(),
     173             :                ", but the finest hierarchy space has true size ",
     174             :                finest_size,
     175             :                ".");
     176             : 
     177             :   // Build new levels' forms; accumulate before touching _mg / _level_*.
     178           7 :   std::vector<std::shared_ptr<mfem::ParBilinearForm>> new_blfs;
     179           7 :   std::vector<std::unique_ptr<mfem::OperatorHandle>> new_level_ops;
     180           7 :   new_level_ops.reserve(N - 1);
     181             : 
     182           7 :   auto mg = std::make_unique<mfem::GeometricMultigrid>(*_hierarchy, ess_bdr);
     183           7 :   auto * mg_ptr = mg.get();
     184             : 
     185          21 :   for (const auto level : make_range(N))
     186             :   {
     187             :     auto & level_fespace =
     188          14 :         static_cast<mfem::ParFiniteElementSpace &>(_hierarchy->GetFESpaceAtLevel(level));
     189             : 
     190             :     // Compute essential true DoFs for this level.
     191          14 :     mfem::Array<int> level_tdofs;
     192          14 :     level_fespace.GetEssentialTrueDofs(ess_bdr, level_tdofs);
     193             : 
     194             :     // Build level operator.
     195          14 :     mfem::Operator * level_op = nullptr;
     196             : 
     197          14 :     if (level == finest_level)
     198           7 :       level_op = const_cast<mfem::Operator *>(&op);
     199             :     else
     200             :     {
     201             :       auto blf =
     202           7 :           eq_sys->BuildBilinearFormForFESpace(_var_name, level_fespace, _assembly_levels[level]);
     203             : 
     204           7 :       auto level_op_handle = std::make_unique<mfem::OperatorHandle>();
     205           7 :       blf->FormSystemMatrix(level_tdofs, *level_op_handle);
     206           7 :       level_op = level_op_handle->Ptr();
     207           7 :       new_level_ops.push_back(std::move(level_op_handle));
     208           7 :       new_blfs.push_back(std::move(blf));
     209           7 :     }
     210             : 
     211             :     // Configure the smoother / coarse solver with this level's operator.
     212             :     // Each smoother's SetOperator() owns full initialization.
     213          14 :     auto & level_smoother = get_smoother(level);
     214          14 :     level_smoother.SetOperator(*level_op);
     215             : 
     216          28 :     mg_ptr->AddLevel(
     217          14 :         level_op, &level_smoother.GetSolver(), /*ownOperator=*/false, /*ownSmoother=*/false);
     218          14 :   }
     219             : 
     220             :   // Atomically replace:
     221             :   //  1. Old MG freed, dropping raw pointers into level operators.
     222             :   //  2. Old operator handles freed before old forms they may wrap.
     223             :   //  3. Proxy updated to point at the new MG and level data.
     224           7 :   _mg = std::move(mg);
     225           7 :   _level_ops = std::move(new_level_ops);
     226           7 :   _level_blfs = std::move(new_blfs);
     227           7 :   _mg_proxy->SetMG(*_mg);
     228           7 : }
     229             : #endif

Generated by: LCOV version 1.14