ProblemComposers System
The ProblemComposers system allows the user to construct arbitrary problem operators with custom inputs. This class is specifically intended for composing user-defined custom operators which may be raw mfem::Operator's optimized for specific purposes, e.g. a physics application, a bespoke solver or preconditioner implementation, usage of an optimized third-party library, or cutting-edge mfem functionality. Users' operators may still access the wider MOOSE multi-physics system. As of yet, the user may only provide a single, but arbitrarily convoluted, problem composer (and thus operator) object per MFEMProblem. The problem composer classes are built within the MFEMProblem class, however the ProblemOperators are built by MFEM executioners.
Using a custom problem composer to plug in a custom problem operator
This custom problem operator example will follow the custom_composer_and_operator.i test which is based on MFEM's ex0p. Firstly, a custom ProblemOperator class must be built. If the problem is steady, i.e exercised by the MFEMSteady executioner, then the problem operator class inherits from Moose::MFEM::ProblemOperator. If the problem is transient, i.e. exercised by the MFEMTransient executioner, then the class must inherit from Moose::MFEM::TimeDependentProblemOperator. For this case, the problem is steady and an example class may look as follows:
class CustomProblemOperator : public Moose::MFEM::ProblemOperator
{
private:
// The linear and bilinear forms
mfem::ParBilinearForm * _a;
mfem::ParLinearForm * _b;
// The coefficient
mfem::Coefficient & _coef;
// The boundary conditions arrays
mfem::Array<int> _boundary_dofs;
// The operator and solution vectors (could
// potentially use the ones in the base class)
mfem::OperatorHandle _problem_operator; // The actual mfem problem operator
mfem::Vector _B, _X;
public:
// The constructor
CustomProblemOperator(MFEMProblem & mfem_problem, mfem::Coefficient & coef);
// The destructor
~CustomProblemOperator() override;
// The initialisation function
void Init(mfem::BlockVector &) override;
// Solve the equation
void Solve() override;
};
(test/include/mfem/problem_operators/CustomProblemOperator.h)As any typical mfem example, the class needs Forms, Coefficients, BC Arrays, Operators and solution/forcing Vectors, though an EquationSystem can also be used instead of or along side the previous objects. The case in this example is linear and the operator is only built once in the Init() function:
CustomProblemOperator::Init(mfem::BlockVector &)
{
// Get the FE-space and Variable that were just built
auto fes = _problem.getProblemData().fespaces.Get("H1");
auto gridfunction = _problem.getGridFunction("u");
// Boundary conditions
fes->GetBoundaryTrueDofs(_boundary_dofs);
// Build the linear form
_b = new mfem::ParLinearForm(fes);
_b->AddDomainIntegrator(new mfem::DomainLFIntegrator(_coef));
_b->Assemble();
// Build the bilinear form
_a = new mfem::ParBilinearForm(fes);
_a->AddDomainIntegrator(new mfem::DiffusionIntegrator);
_a->Assemble();
// Form the linear system
_a->FormLinearSystem(_boundary_dofs, *gridfunction, *_b, _problem_operator, _X, _B);
}
(test/src/mfem/problem_operators/CustomProblemOperator.C)To set-up the problem, the Forms need access to the FESpaces and the Postprocessors may need access to the GridFunctions. Both the FESpaces and GridFunctions are owned by the MFEMProblem and need to be retrieved for usage in the ProblemOperator. The constructor should only be used to retrieve custom input parameters, retrieving FESpaces and GridFunctions has to be done no earlier than Init().
The rest of the Init() function mirrors the MFEM ex0p example, i.e. build the forms, add the integrators, assemble the forms and form the linear system.
The Solve() method solves the linear/non-linear system that has been setup and passes the data to the mfem GridFunctions so that the Postprocessors can view the results. The class inherits a reference to the MFEMProblem and MFEMProblemData from Moose::MFEM::ProblemOperator meaning the they can be used to access the gridfunctions and solvers.
CustomProblemOperator::Solve()
{
// Set the operator and solve the equation
_problem_data.jacobian_solver->SetOperator(*_problem_operator);
_problem_data.jacobian_solver->GetSolver().Mult(_B, _X);
// Set the data in the grid function
auto grid_function = _problem.getGridFunction("u");
grid_function->SetFromTrueDofs(_X);
}
(test/src/mfem/problem_operators/CustomProblemOperator.C)Once the ProblemOperator has been written, an MFEMProblemComposer class is needed. The composer class must inherit from MFEMProblemComposer making it an MFEMObject and by proxy a MooseObject, thus it has a fixed signature constructor and destructor, plus a createProblemOperator() method it must override. An example minimal class looks like:
class CustomProblemComposer : public MFEMProblemComposer
{
public:
static InputParameters validParams();
CustomProblemComposer(const InputParameters & parameters);
/// Returns a pointer to a freshly minted operator.
std::shared_ptr<Moose::MFEM::ProblemOperatorBase>
createProblemOperator(MFEMProblem & mfem_problem) override;
};
(test/include/mfem/problem_composers/CustomProblemComposer.h)The validParams() method can be used to generate custom inputs for the problem composer, the inputs can be then put in the ProblemComposers block of the input file. The custom inputs in problem composer can then be passed to the problem operator in the createProblemOperator() method.
InputParameters
CustomProblemComposer::validParams()
{
InputParameters params = MFEMProblemComposer::validParams();
params.addParam<MFEMScalarCoefficientName>("coefficient", "1.", "Diffusion coefficient");
return params;
}
(test/src/mfem/problem_composers/CustomProblemComposer.C)The constructor can be left more or less empty if the operator being built has no custom options associated with it directly (e.g. solver customizations may occur in the MFEMProblem class), but in the case there are custom inputs it can be used to retrieve the input params and store them.
CustomProblemComposer::CustomProblemComposer(const InputParameters & parameters)
: MFEMProblemComposer(parameters)
{
}
(test/src/mfem/problem_composers/CustomProblemComposer.C)The last method to be built is createProblemOperator(): it simply returns a shared pointer to the ProblemOperator that was defined earlier.
std::shared_ptr<Moose::MFEM::ProblemOperatorBase>
CustomProblemComposer::createProblemOperator(MFEMProblem & mfem_problem)
{
return std::make_shared<CustomProblemOperator>(mfem_problem, getScalarCoefficient("coefficient"));
}
(test/src/mfem/problem_composers/CustomProblemComposer.C)Once the object has been defined, the new MFEMProblemComposer object must be registered to the MooseApp system:
registerMooseObject("MooseApp", CustomProblemComposer);
(test/src/mfem/problem_composers/CustomProblemComposer.C)