LCOV - code coverage report
Current view: top level - src/mfem/solvers - MFEMGeometricMultigridSolver.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #33416 (b10b36) with base 9fbd27 Lines: 106 130 81.5 %
Date: 2026-07-23 16:15:30 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        2144 : MFEMGeometricMultigridSolver::validParams()
      50             : {
      51        2144 :   InputParameters params = Moose::MFEM::LinearSolverBase::validParams();
      52        4288 :   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        8576 :   params.addRequiredParam<std::string>("variable",
      58             :                                        "Name of the trial variable this preconditioner acts on.");
      59        8576 :   params.addRequiredParam<std::string>(
      60             :       "fespace_hierarchy", "Name of the MFEMFESpaceHierarchy that defines the level structure.");
      61        8576 :   params.addRequiredParam<std::vector<MFEMSolverName>>(
      62             :       "smoothers",
      63             :       "Names of LinearSolverBase objects used as smoothers on the interior levels "
      64             :       "(levels 1 to N-1). May have length 1 (used on all interior levels) or "
      65             :       "N-1 (one per interior level, ordered coarse-to-fine).");
      66        8576 :   params.addRequiredParam<MFEMSolverName>(
      67             :       "coarse_solver", "Name of the LinearSolverBase used on the coarsest level.");
      68        6432 :   params.addRequiredParam<std::vector<std::string>>(
      69             :       "assembly_levels",
      70             :       "Assembly level for each level in the hierarchy. Valid values: 'legacy', 'full', "
      71             :       "'element', 'partial', 'none'. May have length 1 (used on all N levels) or N.");
      72        2144 :   return params;
      73           0 : }
      74             : 
      75           9 : MFEMGeometricMultigridSolver::MFEMGeometricMultigridSolver(const InputParameters & parameters)
      76             :   : Moose::MFEM::LinearSolverBase(parameters),
      77           9 :     _var_name(getParam<std::string>("variable")),
      78          18 :     _smoother_names(getParam<std::vector<MFEMSolverName>>("smoothers")),
      79          45 :     _coarse_solver_name(getParam<MFEMSolverName>("coarse_solver"))
      80             : {
      81             :   // Co-own the hierarchy so it outlives this solver.
      82          18 :   const auto & hierarchy_name = getParam<std::string>("fespace_hierarchy");
      83           9 :   _hierarchy = getMFEMProblem().getProblemData().fespace_hierarchies.GetShared(hierarchy_name);
      84             : 
      85             :   // Parse assembly levels, optionally expanding a single input value to all levels.
      86           9 :   const int N = _hierarchy->GetNumLevels();
      87          18 :   const auto & asm_strs = getParam<std::vector<std::string>>("assembly_levels");
      88           9 :   const int n_asm = asm_strs.size();
      89           9 :   if (n_asm != 1 && n_asm != N)
      90           0 :     paramError(
      91             :         "assembly_levels", "must have length 1 or N = ", N, " (total levels), got ", n_asm, ".");
      92             : 
      93           9 :   _assembly_levels.resize(N);
      94          27 :   for (const auto i : make_range(N))
      95          18 :     _assembly_levels[i] = ParseAssemblyLevel(n_asm == 1 ? asm_strs[0] : asm_strs[i]);
      96             : 
      97           9 :   ConstructSolver();
      98           9 : }
      99             : 
     100             : void
     101           9 : MFEMGeometricMultigridSolver::ConstructSolver()
     102             : {
     103           9 :   _mg.reset();
     104           9 :   _level_ops.clear();
     105           9 :   _level_blfs.clear();
     106             : 
     107           9 :   auto proxy = std::make_unique<MGProxy>(*this);
     108           9 :   _mg_proxy = proxy.get();
     109           9 :   _solver = std::move(proxy);
     110           9 : }
     111             : 
     112             : mfem::AssemblyLevel
     113          18 : MFEMGeometricMultigridSolver::ParseAssemblyLevel(const std::string & s) const
     114             : {
     115          18 :   if (s == "legacy")
     116          18 :     return mfem::AssemblyLevel::LEGACY;
     117           0 :   if (s == "full")
     118           0 :     return mfem::AssemblyLevel::FULL;
     119           0 :   if (s == "element")
     120           0 :     return mfem::AssemblyLevel::ELEMENT;
     121           0 :   if (s == "partial")
     122           0 :     return mfem::AssemblyLevel::PARTIAL;
     123           0 :   if (s == "none")
     124           0 :     return mfem::AssemblyLevel::NONE;
     125           0 :   paramError("assembly_levels",
     126             :              "unknown assembly level '",
     127             :              s,
     128             :              "'. Valid values: legacy, full, element, partial, none.");
     129             :   return mfem::AssemblyLevel::LEGACY;
     130             : }
     131             : 
     132             : void
     133           0 : MFEMGeometricMultigridSolver::SetOperator(mfem::Operator & op)
     134             : {
     135           0 :   BuildMultigrid(op);
     136           0 : }
     137             : 
     138             : void
     139           9 : MFEMGeometricMultigridSolver::BuildMultigrid(const mfem::Operator & op)
     140             : {
     141           9 :   auto & problem = getMFEMProblem();
     142           9 :   auto & pd = problem.getProblemData();
     143             : 
     144           9 :   auto * eq_sys = dynamic_cast<Moose::MFEM::EquationSystem *>(pd.eqn_system.get());
     145           9 :   if (!eq_sys)
     146           0 :     mooseError("GeometricMultigridSolver '",
     147           0 :                name(),
     148             :                "': requires a standard (non-complex, non-time-dependent) EquationSystem.");
     149             : 
     150           9 :   if (eq_sys->Nonlinear())
     151           2 :     mooseError("GeometricMultigridSolver '",
     152           2 :                name(),
     153             :                "': nonlinear equation systems are not currently supported.");
     154             : 
     155           7 :   if (eq_sys->HasMixedBilinearForms(_var_name))
     156           0 :     paramError("variable",
     157             :                "mixed bilinear form contributions are not supported for variable '",
     158           0 :                _var_name,
     159             :                "'. Block multigrid is required for saddle-point / mixed-field problems.");
     160             : 
     161           7 :   const int N = _hierarchy->GetNumLevels();
     162           7 :   if (N < 1)
     163           0 :     paramError("fespace_hierarchy", "hierarchy must contain at least one level.");
     164           7 :   const int finest_level = N - 1;
     165             : 
     166             :   // Validate smoother vector length (levels 1 to N-1 each need a smoother).
     167           7 :   const int n_smooth = _smoother_names.size();
     168           7 :   if (n_smooth != 1 && n_smooth != N - 1)
     169           0 :     paramError("smoothers", "must have length 1 or N-1 = ", N - 1, ", got ", n_smooth, ".");
     170             : 
     171          14 :   auto get_smoother = [&](int level) -> Moose::MFEM::LinearSolverBase &
     172             :   {
     173          14 :     if (level == 0)
     174          28 :       return problem.getMFEMObject<Moose::MFEM::LinearSolverBase>("Moose::MFEM::SolverBase",
     175           7 :                                                                   _coarse_solver_name);
     176           7 :     const std::string & sname = (n_smooth == 1) ? _smoother_names[0] : _smoother_names[level - 1];
     177          21 :     return problem.getMFEMObject<Moose::MFEM::LinearSolverBase>("Moose::MFEM::SolverBase", sname);
     178           7 :   };
     179             : 
     180             :   // Obtain essential boundary attribute markers from the equation system.
     181           7 :   mfem::Array<int> ess_bdr = eq_sys->BuildEssentialBoundaryMarkers(_var_name);
     182             : 
     183             :   auto & finest_fespace =
     184           7 :       static_cast<mfem::ParFiniteElementSpace &>(_hierarchy->GetFESpaceAtLevel(finest_level));
     185           7 :   const int finest_size = finest_fespace.GetTrueVSize();
     186           7 :   if (op.Height() != finest_size || op.Width() != finest_size)
     187           0 :     mooseError("GeometricMultigridSolver '",
     188           0 :                name(),
     189             :                "': incoming fine operator has size ",
     190           0 :                op.Height(),
     191             :                " x ",
     192           0 :                op.Width(),
     193             :                ", but the finest hierarchy space has true size ",
     194             :                finest_size,
     195             :                ".");
     196             : 
     197             :   // Build new levels' forms; accumulate before touching _mg / _level_*.
     198           7 :   std::vector<std::shared_ptr<mfem::ParBilinearForm>> new_blfs;
     199           7 :   std::vector<std::unique_ptr<mfem::OperatorHandle>> new_level_ops;
     200           7 :   new_level_ops.reserve(N - 1);
     201             : 
     202           7 :   auto mg = std::make_unique<mfem::GeometricMultigrid>(*_hierarchy, ess_bdr);
     203           7 :   auto * mg_ptr = mg.get();
     204             : 
     205          21 :   for (const auto level : make_range(N))
     206             :   {
     207             :     auto & level_fespace =
     208          14 :         static_cast<mfem::ParFiniteElementSpace &>(_hierarchy->GetFESpaceAtLevel(level));
     209             : 
     210             :     // Compute essential true DoFs for this level.
     211          14 :     mfem::Array<int> level_tdofs;
     212          14 :     level_fespace.GetEssentialTrueDofs(ess_bdr, level_tdofs);
     213             : 
     214             :     // Build level operator.
     215          14 :     mfem::Operator * level_op = nullptr;
     216          14 :     bool own_op = false;
     217             : 
     218          14 :     if (level == finest_level)
     219             :     {
     220           7 :       level_op = const_cast<mfem::Operator *>(&op);
     221           7 :       own_op = false;
     222             :     }
     223             :     else
     224             :     {
     225             :       auto blf =
     226           7 :           eq_sys->BuildBilinearFormForFESpace(_var_name, level_fespace, _assembly_levels[level]);
     227             : 
     228           7 :       auto level_op_handle = std::make_unique<mfem::OperatorHandle>();
     229           7 :       blf->FormSystemMatrix(level_tdofs, *level_op_handle);
     230           7 :       level_op = level_op_handle->Ptr();
     231           7 :       own_op = false; // owned by level_op_handle or blf
     232           7 :       new_level_ops.push_back(std::move(level_op_handle));
     233           7 :       new_blfs.push_back(std::move(blf));
     234           7 :     }
     235             : 
     236             :     // Configure the smoother / coarse solver with this level's operator.
     237             :     // Each smoother's SetOperator() owns full initialization.
     238          14 :     auto & level_smoother = get_smoother(level);
     239          14 :     level_smoother.SetOperator(*level_op);
     240             : 
     241          14 :     mg_ptr->AddLevel(level_op, &level_smoother.GetSolver(), own_op, /*ownSmoother=*/false);
     242          14 :   }
     243             : 
     244             :   // Atomically replace:
     245             :   //  1. Old MG freed, dropping raw pointers into level operators.
     246             :   //  2. Old operator handles freed before old forms they may wrap.
     247             :   //  3. Proxy updated to point at the new MG and level data.
     248           7 :   _mg = std::move(mg);
     249           7 :   _level_ops = std::move(new_level_ops);
     250           7 :   _level_blfs = std::move(new_blfs);
     251           7 :   _mg_proxy->setMG(*_mg);
     252           7 : }
     253             : #endif

Generated by: LCOV version 1.14