diff --git a/.gitignore b/.gitignore index d87be6b9c28c..bb99cfc3741f 100644 --- a/.gitignore +++ b/.gitignore @@ -229,6 +229,7 @@ framework/contrib/asio/ *.cpr *.cpa.gz-* *.cpr-* +*_cp/ # Ignore petsc arch petsc/arch-*/* diff --git a/framework/doc/content/source/mfem/auxkernels/MFEMAuxKernel.md b/framework/doc/content/source/mfem/auxkernels/MFEMAuxKernel.md index 3e8b6f9839a2..7ea4cfae30ca 100644 --- a/framework/doc/content/source/mfem/auxkernels/MFEMAuxKernel.md +++ b/framework/doc/content/source/mfem/auxkernels/MFEMAuxKernel.md @@ -8,12 +8,12 @@ Base class for MFEM auxkernels used to evaluate real auxiliary variables to the ## Overview -MFEM auxkernels are responsible for updating real auxiliary variables in the system, during pre- or -post-processing steps. +MFEM auxkernels are responsible for updating real auxiliary variables in the system during MFEM +execution passes. -An `MFEMAuxKernel` is derived from `MFEMGeneralUserObject`, and thus the order of their execution -can be controlled similar to other MOOSE UserObjects using the `execution_order_group` input -parameter. +An `MFEMAuxKernel` is derived from `MFEMExecutedObject`. Its ordering relative to other MFEM +executed objects is determined automatically from detected data dependencies rather than by manual +execution groups. `MFEMAuxKernel` is a base class. Derived classes should override the `execute` method to update the `_result_var` during execution. diff --git a/framework/doc/content/source/mfem/auxkernels/MFEMComplexAuxKernel.md b/framework/doc/content/source/mfem/auxkernels/MFEMComplexAuxKernel.md index a3ca364fa308..609d4d4be0d8 100644 --- a/framework/doc/content/source/mfem/auxkernels/MFEMComplexAuxKernel.md +++ b/framework/doc/content/source/mfem/auxkernels/MFEMComplexAuxKernel.md @@ -8,12 +8,12 @@ Base class for MFEM auxkernels used to evaluate complex auxiliary variables to t ## Overview -Complex MFEM auxkernels are responsible for updating complex auxiliary variables in the system, during pre- or -post-processing steps. +Complex MFEM auxkernels are responsible for updating complex auxiliary variables in the system +during MFEM execution passes. -An `MFEMComplexAuxKernel` is derived from `MFEMGeneralUserObject`, and thus the order of their execution -can be controlled similar to other MOOSE UserObjects using the `execution_order_group` input -parameter. +An `MFEMComplexAuxKernel` is derived from `MFEMExecutedObject`. Its ordering relative to other MFEM +executed objects is determined automatically from detected data dependencies rather than by manual +execution groups. `MFEMComplexAuxKernel` is a base class. Derived classes should override the `execute` method to update the `_result_var` during execution. diff --git a/framework/doc/content/source/mfem/postprocessors/MFEMPostprocessor.md b/framework/doc/content/source/mfem/postprocessors/MFEMPostprocessor.md index 5d2a5b420136..b7cbe21427af 100644 --- a/framework/doc/content/source/mfem/postprocessors/MFEMPostprocessor.md +++ b/framework/doc/content/source/mfem/postprocessors/MFEMPostprocessor.md @@ -8,17 +8,12 @@ Base class for MFEM postprocessors used to evaluate a single scalar. ## Overview -MFEM postprocessors calculate scalar quantities from the -(aux)variables, typically after each timestep. +MFEM postprocessors calculate scalar quantities from the (aux)variables, typically after each +timestep. -An `MFEMPostprocessor` is derived from `MFEMGeneralUserObject`. -Therefore, the order of their execution can be controlled similar to other -MOOSE UserObjects using the `execution_order_group` input parameter, e.g., -to require the execution of a postprocessor computing on an [AuxVariable.md] -strictly after the execution of the [MFEMAuxKernel.md] computing the variable -field itself. For example: - -!listing mfem/kernels/irrotational.i block=AuxKernels Postprocessors/velocity_error +An `MFEMPostprocessor` is derived from `MFEMExecutedObject`. Its ordering relative to MFEM initial +conditions, aux kernels, transfers, and other MFEM postprocessors is determined automatically from +detected data dependencies instead of manual execution groups. `MFEMPostprocessor` is a purely virtual base class. Derived classes should override the `execute` and `getValue` methods. diff --git a/framework/doc/content/source/mfem/userobjects/MFEMGeneralUserObject.md b/framework/doc/content/source/mfem/userobjects/MFEMGeneralUserObject.md deleted file mode 100644 index 6444dcee265f..000000000000 --- a/framework/doc/content/source/mfem/userobjects/MFEMGeneralUserObject.md +++ /dev/null @@ -1,21 +0,0 @@ -# MFEMGeneralUserObject - -!if! function=hasCapability('mfem') - -## Overview - -`MFEMGeneralUserObject` is a base class for general MFEM user objects, derived from -`GeneralUserObject`. `MFEMGeneralUserObject` adds the `getMFEMProblem()` method for fetching the a -reference to the [`MFEMProblem`](problem/MFEMProblem.md) directly, to minimise the number of casts -required. - -!syntax parameters /UserObjects/MFEMGeneralUserObject - -!syntax inputs /UserObjects/MFEMGeneralUserObject - -!syntax children /UserObjects/MFEMGeneralUserObject - -!if-end! - -!else -!include mfem/mfem_warning.md diff --git a/framework/doc/content/source/mfem/vectorpostprocessors/MFEMComplexPointValueSampler.md b/framework/doc/content/source/mfem/vectorpostprocessors/MFEMComplexPointValueSampler.md new file mode 100644 index 000000000000..db97b003404e --- /dev/null +++ b/framework/doc/content/source/mfem/vectorpostprocessors/MFEMComplexPointValueSampler.md @@ -0,0 +1,25 @@ +# MFEMComplexPointValueSampler + +!if! function=hasCapability('mfem') + +## Overview + +This class performs accurate interpolation of complex scalar and vector quantities at a +set of specified points using MFEM's +[`FindPointsGSLIB`](https://mfem.org/howto/findpts/) `gslib` interpolation. +The real and imaginary parts of each sampled component are output as separate columns. + +## Example Input File Syntax + +!listing mfem/kernels/nl_heatconduction_complex_aux.i block=VectorPostprocessors + +!syntax parameters /VectorPostprocessors/MFEMComplexPointValueSampler + +!syntax inputs /VectorPostprocessors/MFEMComplexPointValueSampler + +!syntax children /VectorPostprocessors/MFEMComplexPointValueSampler + +!if-end! + +!else +!include mfem/mfem_warning.md diff --git a/framework/doc/content/source/mfem/vectorpostprocessors/MFEMVectorPostprocessor.md b/framework/doc/content/source/mfem/vectorpostprocessors/MFEMVectorPostprocessor.md index 25e5c121b536..8a394b13b7e4 100644 --- a/framework/doc/content/source/mfem/vectorpostprocessors/MFEMVectorPostprocessor.md +++ b/framework/doc/content/source/mfem/vectorpostprocessors/MFEMVectorPostprocessor.md @@ -8,15 +8,12 @@ Base class for MFEM vectorpostprocessors used to evaluate an array of values. ## Overview -MFEM vectorpostprocessors calculate an array of values from the (aux)variables, -typically after each timestep. - -An `MFEMVectorPostprocessor` is derived from `MFEMGeneralUserObject`. -Therefore, the order of their execution can be controlled similar to other -MOOSE UserObjects using the `execution_order_group` input parameter, e.g., to -require the execution of a vectorpostprocessor computing on an [AuxVariable.md] -strictly after the execution of the [MFEMAuxKernel.md] computing the variable -field itself. +MFEM vectorpostprocessors calculate an array of values from the (aux)variables, typically after +each timestep. + +An `MFEMVectorPostprocessor` is derived from `MFEMExecutedObject`. Its ordering relative to other +MFEM executed objects is determined automatically from detected data dependencies instead of manual +execution groups. `MFEMVectorPostprocessor` is a virtual base class. Derived classes should use the `VectorPostprocessor::declareVector` method to get a reference to a vector diff --git a/framework/include/interfaces/DependencyResolverInterface.h b/framework/include/interfaces/DependencyResolverInterface.h index 07f5a38fbb8f..6cea5c505463 100644 --- a/framework/include/interfaces/DependencyResolverInterface.h +++ b/framework/include/interfaces/DependencyResolverInterface.h @@ -65,6 +65,11 @@ class DependencyResolverInterface /** * A helper method for cyclic errors. */ + template + static void cyclicDependencyError(CyclicDependencyException & e, + const std::string & header, + NameFunc && name_func); + template static void cyclicDependencyError(CyclicDependencyException & e, const std::string & header); }; @@ -124,10 +129,11 @@ DependencyResolverInterface::sortDFS(typename std::vector & vector) vector = sorted; } -template +template void DependencyResolverInterface::cyclicDependencyError(CyclicDependencyException & e, - const std::string & header) + const std::string & header, + NameFunc && name_func) { std::ostringstream oss; @@ -135,7 +141,15 @@ DependencyResolverInterface::cyclicDependencyError(CyclicDependencyException const auto cycle = e.getCyclicDependencies(); std::vector names(cycle.size()); for (const auto i : index_range(cycle)) - names[i] = static_cast(cycle[i])->name(); + names[i] = name_func(cycle[i]); oss << MooseUtils::join(names, " <- "); mooseError(oss.str()); } + +template +void +DependencyResolverInterface::cyclicDependencyError(CyclicDependencyException & e, + const std::string & header) +{ + cyclicDependencyError(e, header, [](const auto & obj) { return static_cast(obj)->name(); }); +} diff --git a/framework/include/mesh/MooseMesh.h b/framework/include/mesh/MooseMesh.h index 944fc50e0d48..6d92bd255d96 100644 --- a/framework/include/mesh/MooseMesh.h +++ b/framework/include/mesh/MooseMesh.h @@ -25,6 +25,7 @@ #include "ElemInfo.h" #include //std::unique_ptr +#include #include #include @@ -180,6 +181,15 @@ class MooseMesh : public MooseObject, public Restartable, public PerfGraphInterf */ virtual void buildMesh() = 0; + /** + * Write the mesh files needed for recovery/checkpointing. + * + * The base implementation writes the libMesh checkpoint mesh. + * Derived classes may extend this to write additional backend-specific files. + */ + virtual std::vector + writeRecoveryFiles(const std::filesystem::path & file_base) const; + /** * Returns MeshBase::mesh_dimension(), (not * MeshBase::spatial_dimension()!) of the underlying libMesh mesh @@ -1557,6 +1567,8 @@ class MooseMesh : public MooseObject, public Restartable, public PerfGraphInterf bool getDisplaceNodeListBySideList() { return _displace_node_list_by_side_list; } protected: + bool recoveryAllowed() const { return _allow_recovery; } + /// Deprecated (DO NOT USE) std::vector> _ghosting_functors; diff --git a/framework/include/mfem/auxkernels/MFEMAuxKernel.h b/framework/include/mfem/auxkernels/MFEMAuxKernel.h index aa5b9f95365b..fffb539c3eee 100644 --- a/framework/include/mfem/auxkernels/MFEMAuxKernel.h +++ b/framework/include/mfem/auxkernels/MFEMAuxKernel.h @@ -11,13 +11,13 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMExecutedObject.h" #include "MFEMContainers.h" /** * Class to construct an auxiliary solver used to update a real auxvariable. */ -class MFEMAuxKernel : public MFEMGeneralUserObject +class MFEMAuxKernel : public MFEMExecutedObject { public: static InputParameters validParams(); @@ -26,7 +26,9 @@ class MFEMAuxKernel : public MFEMGeneralUserObject virtual ~MFEMAuxKernel() = default; /// Method called to update any owned objects upon an FE space update - virtual void update() {}; + virtual void update() {} + + virtual std::set producedVariableNames() const override; protected: /// Name of auxvariable to store the result of the auxkernel in. diff --git a/framework/include/mfem/auxkernels/MFEMComplexAuxKernel.h b/framework/include/mfem/auxkernels/MFEMComplexAuxKernel.h index 5599c8163c61..13185b79a6b8 100644 --- a/framework/include/mfem/auxkernels/MFEMComplexAuxKernel.h +++ b/framework/include/mfem/auxkernels/MFEMComplexAuxKernel.h @@ -11,13 +11,13 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMExecutedObject.h" #include "MFEMContainers.h" /** * Class to construct an auxiliary solver used to update a complex auxvariable. */ -class MFEMComplexAuxKernel : public MFEMGeneralUserObject +class MFEMComplexAuxKernel : public MFEMExecutedObject { public: static InputParameters validParams(); @@ -28,6 +28,8 @@ class MFEMComplexAuxKernel : public MFEMGeneralUserObject /// Method called to update any owned objects upon an FE space update virtual void update() {}; + virtual std::set producedVariableNames() const override; + /// Method to add a scaled complex variable to another complex variable. void complexAdd(mfem::ParComplexGridFunction & a, const mfem::ParComplexGridFunction & b, diff --git a/framework/include/mfem/base/MFEMExecutedObject.h b/framework/include/mfem/base/MFEMExecutedObject.h new file mode 100644 index 000000000000..7e2e0470f464 --- /dev/null +++ b/framework/include/mfem/base/MFEMExecutedObject.h @@ -0,0 +1,203 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#pragma once + +#include "MFEMObject.h" +#include "SetupInterface.h" +#include "DependencyResolverInterface.h" + +#include +#include +#include +#include + +/** + * Base class for MFEM objects that participate in execution ordering but are not UserObjects. + */ +class MFEMExecutedObject : public MFEMObject, + public SetupInterface, + public DependencyResolverInterface +{ +public: + /** + * Dependency resource categories understood by the MFEM executed-object scheduler. + */ + enum class DependencyKind : unsigned char + { + Variable = 0, + Postprocessor = 1, + VectorPostprocessor = 2 + }; + + /** + * Metadata describing one dependency-bearing input parameter on this object. + */ + struct DependencyParam + { + /// The resource category requested through this parameter. + DependencyKind kind; + /// The parameter name storing the dependency value(s). + std::string param_name; + /// Whether the parameter stores a vector of dependency names. + bool is_vector; + }; + + /** + * Declare the common parameters used by MFEM executed objects. + */ + static InputParameters validParams(); + + /** + * Construct an executed MFEM object and materialize its dependency metadata. + */ + MFEMExecutedObject(const InputParameters & parameters); + + /** + * Perform any pre-execution setup for this object. + */ + virtual void initialize() {} + /** + * Perform the main work for this object. + */ + virtual void execute() {} + /** + * Perform any post-execution finalization for this object. + */ + virtual void finalize() {} + + /** + * Return the variable names produced by this object. + */ + virtual std::set producedVariableNames() const; + /** + * Return the postprocessor names produced by this object. + */ + virtual std::set producedPostprocessorNames() const; + /** + * Return the vector postprocessor names produced by this object. + */ + virtual std::set producedVectorPostprocessorNames() const; + + virtual const std::set & getRequestedItems() override; + virtual const std::set & getSuppliedItems() override; + + /** + * Add an optional dependency-bearing parameter and register it with the MFEM scheduler. + */ + template + static void addDependencyParam(InputParameters & params, + const std::string & param_name, + const std::string & doc_string); + + /** + * Add a required dependency-bearing parameter and register it with the MFEM scheduler. + */ + template + static void addRequiredDependencyParam(InputParameters & params, + const std::string & param_name, + const std::string & doc_string); + +protected: + /** + * Build the dependency key used for a produced/requested variable. + */ + static std::string variableDependencyKey(const std::string & name); + /** + * Build the dependency key used for a produced/requested postprocessor. + */ + static std::string postprocessorDependencyKey(const std::string & name); + /** + * Build the dependency key used for a produced/requested vector postprocessor. + */ + static std::string vectorPostprocessorDependencyKey(const std::string & name); + + /** + * Map a dependency parameter type to its scheduler resource category. + */ + template + static constexpr DependencyKind dependencyKind(); + + /** + * Whether a dependency parameter type stores a vector of dependency names. + */ + template + static constexpr bool dependencyIsVector(); + + /** + * Record one dependency-bearing parameter in the private parameter metadata. + */ + static void appendDependencyParam(InputParameters & params, + const std::string & param_name, + const DependencyKind kind, + const bool is_vector); + +private: + /// Lazily constructed requested dependency keys for this object's registered dependencies. + std::optional> _requested_items; + /// Lazily constructed supplied dependency keys for this object's produced resources. + std::optional> _supplied_items; + /// Compact typed metadata describing dependency-bearing parameters on this object. + std::vector _dependency_params; +}; + +template +void +MFEMExecutedObject::addDependencyParam(InputParameters & params, + const std::string & param_name, + const std::string & doc_string) +{ + params.addParam(param_name, doc_string); + appendDependencyParam(params, param_name, dependencyKind(), dependencyIsVector()); +} + +template +void +MFEMExecutedObject::addRequiredDependencyParam(InputParameters & params, + const std::string & param_name, + const std::string & doc_string) +{ + params.addRequiredParam(param_name, doc_string); + appendDependencyParam(params, param_name, dependencyKind(), dependencyIsVector()); +} + +template +constexpr MFEMExecutedObject::DependencyKind +MFEMExecutedObject::dependencyKind() +{ + if constexpr (std::is_same_v || std::is_same_v>) + return DependencyKind::Variable; + else if constexpr (std::is_same_v || + std::is_same_v>) + return DependencyKind::Postprocessor; + else if constexpr (std::is_same_v || + std::is_same_v>) + return DependencyKind::VectorPostprocessor; + else + static_assert(!sizeof(T), "Unsupported MFEM executed-object dependency parameter type"); +} + +template +constexpr bool +MFEMExecutedObject::dependencyIsVector() +{ + if constexpr (std::is_same_v> || + std::is_same_v> || + std::is_same_v>) + return true; + else if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) + return false; + else + static_assert(!sizeof(T), "Unsupported MFEM executed-object dependency parameter type"); +} + +#endif diff --git a/framework/include/mfem/base/MFEMObject.h b/framework/include/mfem/base/MFEMObject.h new file mode 100644 index 000000000000..e3df626de3de --- /dev/null +++ b/framework/include/mfem/base/MFEMObject.h @@ -0,0 +1,81 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#pragma once + +#include "MooseObject.h" +#include "FunctionInterface.h" +#include "PostprocessorInterface.h" +#include "VectorPostprocessorInterface.h" +#include "ReporterInterface.h" + +class MFEMProblem; + +/** + * Thin base for MFEM objects backed directly by MooseObject instead of UserObject. + */ +class MFEMObject : public MooseObject, + protected FunctionInterface, + protected PostprocessorInterface, + protected VectorPostprocessorInterface, + protected ReporterInterface +{ +public: + /** + * Declare the common parameters required by MFEM MooseObject-backed classes. + */ + static InputParameters validParams(); + + /** + * Construct an MFEM object backed directly by MooseObject. + */ + MFEMObject(const InputParameters & parameters); + + /** + * Return the owning MFEM problem. + */ + MFEMProblem & getMFEMProblem() { return _mfem_problem; } + /** + * Return the owning MFEM problem. + */ + const MFEMProblem & getMFEMProblem() const { return _mfem_problem; } + + /** + * Retrieve a scalar MFEM coefficient by its declared name. + */ + mfem::Coefficient & getScalarCoefficientByName(const MFEMScalarCoefficientName & name); + /** + * Retrieve a vector MFEM coefficient by its declared name. + */ + mfem::VectorCoefficient & getVectorCoefficientByName(const MFEMVectorCoefficientName & name); + /** + * Retrieve a matrix MFEM coefficient by its declared name. + */ + mfem::MatrixCoefficient & getMatrixCoefficientByName(const MFEMMatrixCoefficientName & name); + /** + * Retrieve a scalar MFEM coefficient using the value of an input parameter. + */ + mfem::Coefficient & getScalarCoefficient(const std::string & name); + /** + * Retrieve a vector MFEM coefficient using the value of an input parameter. + */ + mfem::VectorCoefficient & getVectorCoefficient(const std::string & name); + /** + * Retrieve a matrix MFEM coefficient using the value of an input parameter. + */ + mfem::MatrixCoefficient & getMatrixCoefficient(const std::string & name); + +private: + /// Owning MFEM problem for this object. + MFEMProblem & _mfem_problem; +}; + +#endif diff --git a/framework/include/mfem/bcs/MFEMBoundaryCondition.h b/framework/include/mfem/bcs/MFEMBoundaryCondition.h index 84b829b2ac6a..4042fde39095 100644 --- a/framework/include/mfem/bcs/MFEMBoundaryCondition.h +++ b/framework/include/mfem/bcs/MFEMBoundaryCondition.h @@ -11,12 +11,12 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" #include "MFEMBoundaryRestrictable.h" #include "MFEMContainers.h" #include "Function.h" -class MFEMBoundaryCondition : public MFEMGeneralUserObject, public MFEMBoundaryRestrictable +class MFEMBoundaryCondition : public MFEMObject, public MFEMBoundaryRestrictable { public: static InputParameters validParams(); diff --git a/framework/include/mfem/fespaces/MFEMFESpace.h b/framework/include/mfem/fespaces/MFEMFESpace.h index 5f8e3e28042d..ea15c0d06468 100644 --- a/framework/include/mfem/fespaces/MFEMFESpace.h +++ b/framework/include/mfem/fespaces/MFEMFESpace.h @@ -11,13 +11,13 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" /** * Constructs and stores an mfem::ParFiniteElementSpace object. Access using the * getFESpace() accessor. */ -class MFEMFESpace : public MFEMGeneralUserObject +class MFEMFESpace : public MFEMObject { public: static InputParameters validParams(); diff --git a/framework/include/mfem/functormaterials/MFEMFunctorMaterial.h b/framework/include/mfem/functormaterials/MFEMFunctorMaterial.h index cbf0f3b26309..44f098a1fc07 100644 --- a/framework/include/mfem/functormaterials/MFEMFunctorMaterial.h +++ b/framework/include/mfem/functormaterials/MFEMFunctorMaterial.h @@ -11,12 +11,12 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" #include "MFEMBlockRestrictable.h" #include "MFEMBoundaryRestrictable.h" #include "CoefficientManager.h" -class MFEMFunctorMaterial : public MFEMGeneralUserObject, +class MFEMFunctorMaterial : public MFEMObject, public MFEMBlockRestrictable, public MFEMBoundaryRestrictable { diff --git a/framework/include/mfem/ics/MFEMInitialCondition.h b/framework/include/mfem/ics/MFEMInitialCondition.h index 96187aadfef2..c42a5088be34 100644 --- a/framework/include/mfem/ics/MFEMInitialCondition.h +++ b/framework/include/mfem/ics/MFEMInitialCondition.h @@ -11,16 +11,18 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMExecutedObject.h" /** * Base class used to set the initial value(s) on an MFEMVariable. */ -class MFEMInitialCondition : public MFEMGeneralUserObject +class MFEMInitialCondition : public MFEMExecutedObject { public: static InputParameters validParams(); MFEMInitialCondition(const InputParameters & params); + + virtual std::set producedVariableNames() const override; }; #endif diff --git a/framework/include/mfem/indicators/MFEMIndicator.h b/framework/include/mfem/indicators/MFEMIndicator.h index a6002b66f108..8457162b5d81 100644 --- a/framework/include/mfem/indicators/MFEMIndicator.h +++ b/framework/include/mfem/indicators/MFEMIndicator.h @@ -11,7 +11,7 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" #include "MFEMContainers.h" #include "MFEMVariable.h" @@ -19,7 +19,7 @@ * Wrapper class for mfem::ErrorEstimator objects. To keep the * naming consistent with MOOSE, we refer to it as an Indicator. */ -class MFEMIndicator : public MFEMGeneralUserObject +class MFEMIndicator : public MFEMObject { public: static InputParameters validParams(); diff --git a/framework/include/mfem/integrators/NLScaleIntegrator.h b/framework/include/mfem/integrators/NLScaleIntegrator.h index 3851721e8edb..5795ef5d7f4f 100644 --- a/framework/include/mfem/integrators/NLScaleIntegrator.h +++ b/framework/include/mfem/integrators/NLScaleIntegrator.h @@ -11,11 +11,12 @@ #pragma once -#include "MFEMGeneralUserObject.h" #include "libmesh/ignore_warnings.h" #include "mfem/miniapps/common/pfem_extras.hpp" #include "libmesh/restore_warnings.h" +#include "MooseError.h" + namespace Moose::MFEM { diff --git a/framework/include/mfem/integrators/ScaleIntegrator.h b/framework/include/mfem/integrators/ScaleIntegrator.h index 6c0515fb8559..41e75f1758a5 100644 --- a/framework/include/mfem/integrators/ScaleIntegrator.h +++ b/framework/include/mfem/integrators/ScaleIntegrator.h @@ -11,11 +11,12 @@ #pragma once -#include "MFEMGeneralUserObject.h" #include "libmesh/ignore_warnings.h" #include "mfem/miniapps/common/pfem_extras.hpp" #include "libmesh/restore_warnings.h" +#include "MooseError.h" + namespace Moose::MFEM { diff --git a/framework/include/mfem/kernels/MFEMComplexKernel.h b/framework/include/mfem/kernels/MFEMComplexKernel.h index 3de7d0adffc2..f94db0bca7b7 100644 --- a/framework/include/mfem/kernels/MFEMComplexKernel.h +++ b/framework/include/mfem/kernels/MFEMComplexKernel.h @@ -11,15 +11,12 @@ #pragma once -#include "MFEMGeneralUserObject.h" -#include "MFEMContainers.h" -#include "MFEMBlockRestrictable.h" #include "MFEMKernel.h" /* Class to construct an MFEM integrator to apply to the equation system. */ -class MFEMComplexKernel : public MFEMGeneralUserObject, public MFEMBlockRestrictable +class MFEMComplexKernel : public MFEMKernel { public: static InputParameters validParams(); @@ -48,17 +45,6 @@ class MFEMComplexKernel : public MFEMGeneralUserObject, public MFEMBlockRestrict virtual void setRealKernel(std::shared_ptr kernel) { _real_kernel = kernel; } virtual void setImagKernel(std::shared_ptr kernel) { _imag_kernel = kernel; } - /// Get name of the test variable labelling the weak form this kernel is added to - const VariableName & getTestVariableName() const { return _test_var_name; } - - /// Get name of the trial variable (gridfunction) the kernel acts on. - /// Defaults to the name of the test variable labelling the weak form. - virtual const VariableName & getTrialVariableName() const { return _test_var_name; } - -protected: - /// Name of (the test variable associated with) the weak form that the kernel is applied to. - const VariableName & _test_var_name; - std::shared_ptr _real_kernel{nullptr}; std::shared_ptr _imag_kernel{nullptr}; }; diff --git a/framework/include/mfem/kernels/MFEMKernel.h b/framework/include/mfem/kernels/MFEMKernel.h index e8843ad8ea16..223bddb9f09f 100644 --- a/framework/include/mfem/kernels/MFEMKernel.h +++ b/framework/include/mfem/kernels/MFEMKernel.h @@ -11,14 +11,14 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" #include "MFEMContainers.h" #include "MFEMBlockRestrictable.h" /** * Class to construct an MFEM integrator to apply to the equation system. */ -class MFEMKernel : public MFEMGeneralUserObject, public MFEMBlockRestrictable +class MFEMKernel : public MFEMObject, public MFEMBlockRestrictable { public: static InputParameters validParams(); diff --git a/framework/include/mfem/markers/MFEMRefinementMarker.h b/framework/include/mfem/markers/MFEMRefinementMarker.h index 48373eeee38a..1a7610b3ae00 100644 --- a/framework/include/mfem/markers/MFEMRefinementMarker.h +++ b/framework/include/mfem/markers/MFEMRefinementMarker.h @@ -10,14 +10,14 @@ #ifdef MOOSE_MFEM_ENABLED #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" #include "MFEMIndicator.h" /** * Class to construct threshold refiner. * The underlying mfem::ThresholdRefiner needs to be initialised with a reference to the estimator. */ -class MFEMRefinementMarker : public MFEMGeneralUserObject +class MFEMRefinementMarker : public MFEMObject { public: static InputParameters validParams(); diff --git a/framework/include/mfem/mesh/MFEMMesh.h b/framework/include/mfem/mesh/MFEMMesh.h index e43d977309dd..48ef283f5101 100644 --- a/framework/include/mfem/mesh/MFEMMesh.h +++ b/framework/include/mfem/mesh/MFEMMesh.h @@ -42,7 +42,10 @@ class MFEMMesh : public FileMesh /** * Build MFEM ParMesh and a placeholder MOOSE mesh. */ + void init() override; void buildMesh() override; + std::vector + writeRecoveryFiles(const std::filesystem::path & file_base) const override; /** * Clones the mesh. diff --git a/framework/include/mfem/postprocessors/MFEML2Error.h b/framework/include/mfem/postprocessors/MFEML2Error.h index 80098d527ecf..d6526e74e6aa 100644 --- a/framework/include/mfem/postprocessors/MFEML2Error.h +++ b/framework/include/mfem/postprocessors/MFEML2Error.h @@ -12,7 +12,6 @@ #pragma once #include "MFEMPostprocessor.h" -#include "MFEMGeneralUserObject.h" /** * Compute the L2 error for a variable. diff --git a/framework/include/mfem/postprocessors/MFEMPostprocessor.h b/framework/include/mfem/postprocessors/MFEMPostprocessor.h index 93dbb54e54df..9f9e93c3b428 100644 --- a/framework/include/mfem/postprocessors/MFEMPostprocessor.h +++ b/framework/include/mfem/postprocessors/MFEMPostprocessor.h @@ -12,19 +12,21 @@ #pragma once #include "Postprocessor.h" -#include "MFEMGeneralUserObject.h" +#include "MFEMExecutedObject.h" /** * Postprocessor for MFEM results. Must inherit from Postprocessor in * order for MOOSE to call it. */ -class MFEMPostprocessor : public MFEMGeneralUserObject, public Postprocessor +class MFEMPostprocessor : public MFEMExecutedObject, public Postprocessor { public: static InputParameters validParams(); MFEMPostprocessor(const InputParameters & parameters); + virtual std::set producedPostprocessorNames() const override; + /** * This is called _after_ execute() and _after_ threadJoin()! This is probably where you want to * do MPI communication! diff --git a/framework/include/mfem/postprocessors/MFEMVectorL2Error.h b/framework/include/mfem/postprocessors/MFEMVectorL2Error.h index 90e176107281..6b308afe1dcf 100644 --- a/framework/include/mfem/postprocessors/MFEMVectorL2Error.h +++ b/framework/include/mfem/postprocessors/MFEMVectorL2Error.h @@ -12,7 +12,6 @@ #pragma once #include "MFEMPostprocessor.h" -#include "MFEMGeneralUserObject.h" /** * Compute the L2 error for a vector variable. diff --git a/framework/include/mfem/problem/MFEMProblem.h b/framework/include/mfem/problem/MFEMProblem.h index 42c1c5e0d950..99b16d7bfde9 100644 --- a/framework/include/mfem/problem/MFEMProblem.h +++ b/framework/include/mfem/problem/MFEMProblem.h @@ -11,21 +11,38 @@ #pragma once +#include "Attributes.h" #include "ExternalProblem.h" #include "MFEMProblemData.h" #include "MFEMMesh.h" #include "MFEMRefinementMarker.h" #include "MFEMComplexVariable.h" +namespace Moose::MFEM +{ +struct SolutionState; +} + class MFEMProblem : public ExternalProblem { public: + /** + * Return the input parameters used to construct an MFEM problem. + */ static InputParameters validParams(); + /** + * Construct an MFEM problem from the supplied parameters. + */ MFEMProblem(const InputParameters & params); + + /** + * Destroy the MFEM problem. + */ virtual ~MFEMProblem() {} virtual void initialSetup() override; + virtual void execute(const ExecFlagType & exec_type) override; virtual void externalSolve() override {} virtual void syncSolutions(Direction) override {} @@ -58,9 +75,7 @@ class MFEMProblem : public ExternalProblem /** * Add an MFEM FESpace to the problem. */ - void addFESpace(const std::string & user_object_name, - const std::string & name, - InputParameters & parameters); + void addFESpace(const std::string & type, const std::string & name, InputParameters & parameters); /** * Set the device to use to solve the FE problem. */ @@ -74,9 +89,7 @@ class MFEMProblem : public ExternalProblem /** * Add an MFEM SubMesh to the problem. */ - void addSubMesh(const std::string & user_object_name, - const std::string & name, - InputParameters & parameters); + void addSubMesh(const std::string & type, const std::string & name, InputParameters & parameters); /** * Add transfers between MultiApps and/or MFEM SubMeshes. @@ -118,9 +131,8 @@ class MFEMProblem : public ExternalProblem } /** - * Override of ExternalProblem::addKernel. Uses ExternalProblem::addKernel to create a - * MFEMGeneralUserObject representing the kernel in MOOSE, and creates corresponding MFEM kernel - * to be used in the MFEM solve. + * Override of ExternalProblem::addKernel. Creates the MOOSE-side MFEM kernel wrapper and the + * corresponding MFEM kernel to be used in the MFEM solve. */ void addKernel(const std::string & kernel_name, const std::string & name, @@ -155,23 +167,23 @@ class MFEMProblem : public ExternalProblem InputParameters & parameters); /** - * Override of ExternalProblem::addAuxKernel. Uses ExternalProblem::addAuxKernel to create a - * MFEMGeneralUserObject representing the kernel in MOOSE, and creates corresponding MFEM kernel - * to be used in the MFEM solve. + * Override of ExternalProblem::addAuxKernel. Creates the MOOSE-side MFEM auxkernel wrapper. */ void addAuxKernel(const std::string & kernel_name, const std::string & name, InputParameters & parameters) override; /** - * Override of ExternalProblem::addFunction. Uses ExternalProblem::addFunction to create a - * MFEMGeneralUserObject representing the function in MOOSE, and creates a corresponding - * MFEM Coefficient or VectorCoefficient object. + * Override of ExternalProblem::addFunction. Creates a corresponding MFEM Coefficient or + * VectorCoefficient object for the added MOOSE function. */ void addFunction(const std::string & type, const std::string & name, InputParameters & parameters) override; + /** + * Add an MFEM initial condition to the problem. + */ void addInitialCondition(const std::string & ic_name, const std::string & name, InputParameters & parameters) override; @@ -185,6 +197,13 @@ class MFEMProblem : public ExternalProblem const std::string & name, InputParameters & parameters) override; + /** + * Add a vector postprocessor and register its vectors with the MFEM execution system. + */ + void addVectorPostprocessor(const std::string & type, + const std::string & name, + InputParameters & parameters) override; + /** * Method called in AddMFEMPreconditionerAction which will create the solver. */ @@ -192,18 +211,16 @@ class MFEMProblem : public ExternalProblem const std::string & name, InputParameters & parameters); /** - * Override of FEProblemBase::addIndicator. Uses FEProblemBase::addIndicator to create an - * MFEMGeneralUserObject representing the error estimator in MOOSE, i.e. an - * MFEMIndicator to be used when setting up adaptive mesh refinement later. + * Override of FEProblemBase::addIndicator. Creates the MFEMIndicator used when setting up + * adaptive mesh refinement later. */ void addIndicator(const std::string & type, const std::string & name, InputParameters & parameters) override; /** - * Override of FEProblemBase::addMarker. Uses FEProblemBase::addMarker to create an - * MFEMGeneralUserObject representing the refinement marker in MOOSE, i.e. an - * MFEMRefinementMarker to be used for adaptive mesh refinement. + * Override of FEProblemBase::addMarker. Creates the MFEMRefinementMarker used for adaptive mesh + * refinement. */ void addMarker(const std::string & type, const std::string & name, @@ -225,6 +242,11 @@ class MFEMProblem : public ExternalProblem mfem::real_t nl_rel_tol, unsigned int print_level); + /** + * Execute MFEM executed objects scheduled on the supplied execute flag. + */ + void executeMFEMObjects(const ExecFlagType & exec_type); + /** * Method used to get an mfem FEC depending on the variable family specified in the input file. * This method is used in addAuxVariable to help create the MFEM grid function that corresponds to @@ -244,6 +266,10 @@ class MFEMProblem : public ExternalProblem * current data specifying the FE problem. */ MFEMProblemData & getProblemData() { return _problem_data; } + + /** + * Return the current MFEM problem data in a const context. + */ const MFEMProblemData & getProblemData() const { return _problem_data; } /** @@ -321,16 +347,58 @@ class MFEMProblem : public ExternalProblem return _problem_data.cmplx_gridfunctions.GetShared(name); } + /** + * Retrieve an MFEM object from the warehouse by system and name. + */ + template + T & getMFEMObject(const std::string & system, + const std::string & name, + const THREAD_ID tid = 0) const; + + /** + * Determine whether an MFEM object with the supplied system and name exists. + */ + bool hasMFEMObject(const std::string & system, const std::string & name) const; + + /** + * Enumerates the supported numeric representations for MFEM variables and operators. + */ enum class NumericType { REAL, COMPLEX }; + /** + * The numeric representation currently active for this problem. + */ NumericType num_type; protected: + /** + * Aggregated MFEM-side state for meshes, spaces, variables, coefficients, and solvers. + */ MFEMProblemData _problem_data; + Moose::MFEM::SolutionState & _solution_state_data; }; +template +T & +MFEMProblem::getMFEMObject(const std::string & system, + const std::string & name, + const THREAD_ID tid) const +{ + std::vector objs; + theWarehouse() + .query() + .condition(system) + .condition(tid) + .condition(name) + .queryInto(objs); + if (objs.empty()) + mooseError("Unable to find MFEM object with system '" + system + "' and name '" + name + "'"); + mooseAssert(objs.size() == 1, "Shouldn't find more than one object with given system and name"); + return *(objs[0]); +} + #endif diff --git a/framework/include/mfem/solvers/MFEMSolverBase.h b/framework/include/mfem/solvers/MFEMSolverBase.h index a662f29ee692..635c44f577e3 100644 --- a/framework/include/mfem/solvers/MFEMSolverBase.h +++ b/framework/include/mfem/solvers/MFEMSolverBase.h @@ -11,13 +11,13 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" #include "MFEMHyprePatch.h" /** * Base class for wrapping mfem::Solver-derived classes. */ -class MFEMSolverBase : public MFEMGeneralUserObject +class MFEMSolverBase : public MFEMObject { public: static InputParameters validParams(); diff --git a/framework/include/mfem/submeshes/MFEMSubMesh.h b/framework/include/mfem/submeshes/MFEMSubMesh.h index 17c5748ae932..0f4ddc8ecd65 100644 --- a/framework/include/mfem/submeshes/MFEMSubMesh.h +++ b/framework/include/mfem/submeshes/MFEMSubMesh.h @@ -11,13 +11,13 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" /** * Base class for construction of a mfem::ParSubMesh object. Access using the * getSubMesh() accessor. */ -class MFEMSubMesh : public MFEMGeneralUserObject +class MFEMSubMesh : public MFEMObject { public: static InputParameters validParams(); diff --git a/framework/include/mfem/transfers/MFEMSubMeshTransfer.h b/framework/include/mfem/transfers/MFEMSubMeshTransfer.h index 93c2ec7080b0..fcf48a7f5aee 100644 --- a/framework/include/mfem/transfers/MFEMSubMeshTransfer.h +++ b/framework/include/mfem/transfers/MFEMSubMeshTransfer.h @@ -11,19 +11,21 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMExecutedObject.h" /** * Class to transfer MFEM variable data to or from a restricted copy of the variable defined on an a * subspace of an MFEMMesh, represented as an MFEMSubMesh. */ -class MFEMSubMeshTransfer : public MFEMGeneralUserObject +class MFEMSubMeshTransfer : public MFEMExecutedObject { public: static InputParameters validParams(); MFEMSubMeshTransfer(const InputParameters & parameters); + virtual std::set producedVariableNames() const override; + /// Executes the transfer. virtual void execute() override; diff --git a/framework/include/mfem/userobjects/MFEMGeneralUserObject.h b/framework/include/mfem/userobjects/MFEMGeneralUserObject.h deleted file mode 100644 index 7f1efcc2e2e0..000000000000 --- a/framework/include/mfem/userobjects/MFEMGeneralUserObject.h +++ /dev/null @@ -1,51 +0,0 @@ -//* This file is part of the MOOSE framework -//* https://mooseframework.inl.gov -//* -//* All rights reserved, see COPYRIGHT for full restrictions -//* https://github.com/idaholab/moose/blob/master/COPYRIGHT -//* -//* Licensed under LGPL 2.1, please see LICENSE for details -//* https://www.gnu.org/licenses/lgpl-2.1.html - -#ifdef MOOSE_MFEM_ENABLED - -#pragma once - -#include "GeneralUserObject.h" - -// Forwards declaration. -class MFEMProblem; - -/** - * This class adds a getMFEMProblem method. - */ -class MFEMGeneralUserObject : public GeneralUserObject -{ -public: - static InputParameters validParams(); - - MFEMGeneralUserObject(const InputParameters & parameters); - - /// Returns a reference to the MFEMProblem instance. - MFEMProblem & getMFEMProblem() { return _mfem_problem; } - const MFEMProblem & getMFEMProblem() const { return _mfem_problem; } - - /// Returns references to coefficients stored in the MFEMProblem PropertiesManager. - mfem::Coefficient & getScalarCoefficientByName(const MFEMScalarCoefficientName & name); - mfem::VectorCoefficient & getVectorCoefficientByName(const MFEMVectorCoefficientName & name); - mfem::MatrixCoefficient & getMatrixCoefficientByName(const MFEMMatrixCoefficientName & name); - mfem::Coefficient & getScalarCoefficient(const std::string & name); - mfem::VectorCoefficient & getVectorCoefficient(const std::string & name); - mfem::MatrixCoefficient & getMatrixCoefficient(const std::string & name); - - void execute() override {} - - void initialize() override {} - - void finalize() override {} - -private: - MFEMProblem & _mfem_problem; -}; - -#endif diff --git a/framework/include/mfem/variables/MFEMComplexVariable.h b/framework/include/mfem/variables/MFEMComplexVariable.h index de39ca326c38..5aefbc1c4db1 100644 --- a/framework/include/mfem/variables/MFEMComplexVariable.h +++ b/framework/include/mfem/variables/MFEMComplexVariable.h @@ -3,12 +3,12 @@ #pragma once #include "MFEMFESpace.h" -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" /** * Constructs and stores an mfem::ParComplexGridFunction object. */ -class MFEMComplexVariable : public MFEMGeneralUserObject +class MFEMComplexVariable : public MFEMObject { public: static InputParameters validParams(); diff --git a/framework/include/mfem/variables/MFEMVariable.h b/framework/include/mfem/variables/MFEMVariable.h index b136ee35ca2a..a3d575acf133 100644 --- a/framework/include/mfem/variables/MFEMVariable.h +++ b/framework/include/mfem/variables/MFEMVariable.h @@ -12,12 +12,12 @@ #pragma once #include "MFEMFESpace.h" -#include "MFEMGeneralUserObject.h" +#include "MFEMObject.h" /** * Constructs and stores an mfem::ParGridFunction object. */ -class MFEMVariable : public MFEMGeneralUserObject +class MFEMVariable : public MFEMObject { public: static InputParameters validParams(); diff --git a/framework/include/mfem/vectorpostprocessors/MFEMComplexPointValueSampler.h b/framework/include/mfem/vectorpostprocessors/MFEMComplexPointValueSampler.h new file mode 100644 index 000000000000..1902de6ba488 --- /dev/null +++ b/framework/include/mfem/vectorpostprocessors/MFEMComplexPointValueSampler.h @@ -0,0 +1,28 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#pragma once + +#include "MFEMComplexValueSamplerBase.h" + +/** + * Samples a complex-valued MFEM variable at specific points. + * Outputs real and imaginary parts as separate VPP columns. + */ +class MFEMComplexPointValueSampler : public MFEMComplexValueSamplerBase +{ +public: + static InputParameters validParams(); + + MFEMComplexPointValueSampler(const InputParameters & parameters); +}; + +#endif // MOOSE_MFEM_ENABLED diff --git a/framework/include/mfem/vectorpostprocessors/MFEMComplexValueSamplerBase.h b/framework/include/mfem/vectorpostprocessors/MFEMComplexValueSamplerBase.h new file mode 100644 index 000000000000..57d670fe9385 --- /dev/null +++ b/framework/include/mfem/vectorpostprocessors/MFEMComplexValueSamplerBase.h @@ -0,0 +1,45 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#pragma once + +#include "MFEMSamplerBase.h" + +/** + * MFEM VectorPostprocessor base class for sampling a complex-valued auxiliary + * variable at a set of points. For each vector component of the variable, two + * output columns are produced: "_real_" and "_imag_". + * + * Subclasses supply the point locations (e.g. MFEMComplexPointValueSampler). + */ +class MFEMComplexValueSamplerBase : public MFEMSamplerBase +{ +public: + static InputParameters validParams(); + + MFEMComplexValueSamplerBase(const InputParameters & parameters, + const std::vector & points); + + /// Interpolate the real and imaginary parts of the complex variable. + void execute() override; + +protected: + void finalizeValues() override; + +private: + const mfem::ParComplexGridFunction & _var; + mfem::Vector _real_interp_vals; + mfem::Vector _imag_interp_vals; + std::vector> _declared_real_vals; + std::vector> _declared_imag_vals; +}; + +#endif // MOOSE_MFEM_ENABLED diff --git a/framework/include/mfem/vectorpostprocessors/MFEMSamplerBase.h b/framework/include/mfem/vectorpostprocessors/MFEMSamplerBase.h new file mode 100644 index 000000000000..253eae7ec91e --- /dev/null +++ b/framework/include/mfem/vectorpostprocessors/MFEMSamplerBase.h @@ -0,0 +1,61 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#pragma once + +#include "MFEMVectorPostprocessor.h" + +/** + * Abstract base class for MFEM point/line value samplers. + * + * Handles all GSLIB point-finding machinery and coordinate output that is + * shared between real-valued (MFEMValueSamplerBase) and complex-valued + * (MFEMComplexValueSamplerBase) samplers. Derived classes only need to + * implement execute() (to interpolate their variable) and finalizeValues() + * (to copy interpolated results into their declared VPP vectors). + */ +class MFEMSamplerBase : public MFEMVectorPostprocessor +{ +public: + static InputParameters validParams(); + + void initialize() override {} + + /// Outputs coordinates then delegates value output to finalizeValues(). + void finalize() override; + + /** + * Returns the flat index into an mfem::Vector that stores field values with a + * given MFEM dimension ordering, for a given component and point index. + */ + static size_t mfemIndex(size_t i_dim, + size_t i_point, + size_t num_dims, + size_t num_points, + mfem::Ordering::Type ordering); + +protected: + MFEMSamplerBase(const InputParameters & parameters, const std::vector & points); + + /// Copies interpolated variable values into the subclass VPP vectors. + virtual void finalizeValues() = 0; + + /// Name of the variable being sampled (used by derived classes). + const VariableName _var_name; + mfem::ParMesh & _mesh; + mfem::FindPointsGSLIB _finder; + mfem::Ordering::Type _points_ordering; + mfem::Vector _points; + /// Declared VPP output vectors for spatial coordinates ("x_0", "x_1", ...). + std::vector> _declared_points; +}; + +#endif // MOOSE_MFEM_ENABLED diff --git a/framework/include/mfem/vectorpostprocessors/MFEMValueSamplerBase.h b/framework/include/mfem/vectorpostprocessors/MFEMValueSamplerBase.h index 3849de3e45e6..9b8a627ed88d 100644 --- a/framework/include/mfem/vectorpostprocessors/MFEMValueSamplerBase.h +++ b/framework/include/mfem/vectorpostprocessors/MFEMValueSamplerBase.h @@ -11,40 +11,29 @@ #pragma once -#include "MFEMVectorPostprocessor.h" +#include "MFEMSamplerBase.h" -/* - * MFEM Postprocessor which samples values at points. - * - * Subclasses should override validParams and provide and use the constructor. +/** + * MFEM VectorPostprocessor base class for sampling a real-valued variable at a + * set of points. Subclasses supply the point locations (e.g. MFEMPointValueSampler, + * MFEMLineValueSampler). */ -class MFEMValueSamplerBase : public MFEMVectorPostprocessor +class MFEMValueSamplerBase : public MFEMSamplerBase { public: static InputParameters validParams(); MFEMValueSamplerBase(const InputParameters & parameters, const std::vector & points); - /** Perform the interpolation in FindPointsGSLIB. - */ - virtual void execute() override; + /// Interpolate the real variable at all query points. + void execute() override; - /** Store the result of the interpolation. - */ - virtual void finalize() override; +protected: + void finalizeValues() override; private: - const VariableName & _var_name; const mfem::GridFunction & _var; - mfem::ParMesh & _mesh; - - mfem::FindPointsGSLIB _finder; - mfem::Ordering::Type _points_ordering; - mfem::Vector _points; mfem::Vector _interp_vals; - - // VectorPostprocessor declared values - the values written to these are output - std::vector> _declared_points; std::vector> _declared_vals; }; diff --git a/framework/include/mfem/vectorpostprocessors/MFEMVectorPostprocessor.h b/framework/include/mfem/vectorpostprocessors/MFEMVectorPostprocessor.h index 6b655dedeb07..b13e560b2158 100644 --- a/framework/include/mfem/vectorpostprocessors/MFEMVectorPostprocessor.h +++ b/framework/include/mfem/vectorpostprocessors/MFEMVectorPostprocessor.h @@ -11,19 +11,21 @@ #pragma once -#include "MFEMGeneralUserObject.h" +#include "MFEMExecutedObject.h" #include "VectorPostprocessor.h" /* * Vector postprocessor for MFEM results. Must inherit from VectorPostprocessor * in order for MOOSE to call it. */ -class MFEMVectorPostprocessor : public MFEMGeneralUserObject, public VectorPostprocessor +class MFEMVectorPostprocessor : public MFEMExecutedObject, public VectorPostprocessor { public: static InputParameters validParams(); MFEMVectorPostprocessor(const InputParameters & parameters); + + virtual std::set producedVectorPostprocessorNames() const override; }; #endif // MOOSE_MFEM_ENABLED diff --git a/framework/include/outputs/AdvancedOutput.h b/framework/include/outputs/AdvancedOutput.h index e727d9706169..684c19c0d170 100644 --- a/framework/include/outputs/AdvancedOutput.h +++ b/framework/include/outputs/AdvancedOutput.h @@ -387,34 +387,26 @@ AdvancedOutput::initPostprocessorOrVectorPostprocessorLists(const std::string & oss << "execute_" << execute_data_name << "_on"; std::string execute_on_name = oss.str(); - std::vector objs; - _problem_ptr->theWarehouse() - .query() - .condition("UserObject") - .condition(0) - .queryIntoUnsorted(objs); - -#ifdef MOOSE_KOKKOS_ENABLED - std::vector kokkos_objs; - _problem_ptr->theWarehouse() - .query() - .condition("KokkosUserObject") - .condition(0) - .queryIntoUnsorted(kokkos_objs); - - objs.insert(objs.end(), kokkos_objs.begin(), kokkos_objs.end()); -#endif + std::vector objs; + if constexpr (std::is_same_v) + _problem_ptr->theWarehouse() + .query() + .condition(Interfaces::Postprocessor) + .condition(0) + .queryIntoUnsorted(objs); + else if constexpr (std::is_same_v) + _problem_ptr->theWarehouse() + .query() + .condition(Interfaces::VectorPostprocessor) + .condition(0) + .queryIntoUnsorted(objs); for (const auto & obj : objs) { - auto pps = dynamic_cast(obj); - if (!pps) - continue; - - execute_data.available.insert(pps->PPName()); + execute_data.available.insert(obj->PPName()); // Extract the list of outputs - const auto & pps_outputs = pps->getOutputs(); + const auto & pps_outputs = obj->getOutputs(); // Check that the outputs lists are valid _app.getOutputWarehouse().checkOutputs(pps_outputs); @@ -433,7 +425,7 @@ AdvancedOutput::initPostprocessorOrVectorPostprocessorLists(const std::string & mooseWarning("The ", pp_type_str, " '", - pps->PPName(), + obj->PPName(), "' has requested to be output by the '", name(), "' output, but ", diff --git a/framework/include/problems/FEProblemBase.h b/framework/include/problems/FEProblemBase.h index e1afa3f1b62f..f2221d7a347d 100644 --- a/framework/include/problems/FEProblemBase.h +++ b/framework/include/problems/FEProblemBase.h @@ -1429,6 +1429,14 @@ class FEProblemBase : public SubProblem, public Restartable */ bool hasPostprocessorValueByName(const PostprocessorName & name) const; + /** + * Return the Postprocessor object registered under the supplied object name. + * @param object_name The name of the Postprocessor object + * @param tid The thread identifier for thread-local object lookup + */ + const Postprocessor & getPostprocessorObjectByName(const PostprocessorName & object_name, + const THREAD_ID tid = 0) const; + /** * Get a read-only reference to the value associated with a Postprocessor that exists. * @param name The name of the post-processor diff --git a/framework/include/restart/DataIO.h b/framework/include/restart/DataIO.h index 9b6f5e2a7432..4b5684f61798 100644 --- a/framework/include/restart/DataIO.h +++ b/framework/include/restart/DataIO.h @@ -55,6 +55,15 @@ class FEType; class Point; } +#ifdef MOOSE_MFEM_ENABLED +namespace Moose::MFEM +{ +struct SolutionState +{ +}; +} +#endif + /** * Scalar helper routine */ @@ -441,8 +450,11 @@ template <> void dataStore(std::ostream & stream, ADReal & dn, void * context); template <> void dataStore(std::ostream & stream, libMesh::Parameters & p, void * context); - +#ifdef MOOSE_MFEM_ENABLED template <> +void dataStore(std::ostream & stream, Moose::MFEM::SolutionState & state, void * context); +#endif + /** * Stores an owned numeric vector. * @@ -453,6 +465,7 @@ template <> * Requirements: the unique_ptr must exist (cannot be null), the vector * cannot be ghosted, and the provided context must be the Communicator. */ +template <> void dataStore(std::ostream & stream, std::unique_ptr> & v, void * context); @@ -811,7 +824,10 @@ template <> void dataLoad(std::istream & stream, ADReal & dn, void * context); template <> void dataLoad(std::istream & stream, libMesh::Parameters & p, void * context); +#ifdef MOOSE_MFEM_ENABLED template <> +void dataLoad(std::istream & stream, Moose::MFEM::SolutionState & state, void * context); +#endif /** * Loads an owned numeric vector. * @@ -829,6 +845,7 @@ template <> * the Communicator, and if \p v is initialized, it must have the same global * and local sizes that the vector was stored with. */ +template <> void dataLoad(std::istream & stream, std::unique_ptr> & v, void * context); diff --git a/framework/include/timesteppers/TimeSequenceStepperBase.h b/framework/include/timesteppers/TimeSequenceStepperBase.h index 6b0429470c3f..ccae7839dbd8 100644 --- a/framework/include/timesteppers/TimeSequenceStepperBase.h +++ b/framework/include/timesteppers/TimeSequenceStepperBase.h @@ -35,6 +35,10 @@ class TimeSequenceStepperBase : public TimeStepper // Get the time of the current step from input time sequence virtual Real getNextTimeInSequence() { return _time_sequence[_current_step]; }; + /// Advance past sequence times that have already been reached and report whether a future + /// sequence time remains. + bool advanceToFutureTime(Real time, Real tolerance); + virtual void init() override {} virtual void acceptStep() override; diff --git a/framework/include/utils/MooseTypes.h b/framework/include/utils/MooseTypes.h index 1f14a5725158..ef7c0c4021ad 100644 --- a/framework/include/utils/MooseTypes.h +++ b/framework/include/utils/MooseTypes.h @@ -1284,6 +1284,8 @@ MooseDerivativeStringClass(CLIArgString); MooseDerivativeStringClass(MFEMScalarCoefficientName); MooseDerivativeStringClass(MFEMVectorCoefficientName); MooseDerivativeStringClass(MFEMMatrixCoefficientName); +MooseDerivativeStringClass(MFEMFESpaceName); +MooseDerivativeStringClass(MFEMSolverName); ///@} #endif /** diff --git a/framework/src/base/TheWarehouse.C b/framework/src/base/TheWarehouse.C index fadd7e777e8e..c947eae93d8f 100644 --- a/framework/src/base/TheWarehouse.C +++ b/framework/src/base/TheWarehouse.C @@ -222,8 +222,15 @@ TheWarehouse::prepare(std::vector> conds) } catch (CyclicDependencyException & e) { - DependencyResolverInterface::cyclicDependencyError( - e, "Cyclic dependency detected in object ordering"); + DependencyResolverInterface::cyclicDependencyError( + e, + "Cyclic dependency detected in object ordering", + [](DependencyResolverInterface * obj) + { + auto * moose_obj = dynamic_cast(obj); + mooseAssert(moose_obj, "Failed to cast dependency object to MooseObject"); + return moose_obj->name(); + }); } mooseAssert(dependers.size() == vec.size(), "Dependency resolution size mismatch"); diff --git a/framework/src/mesh/MooseMesh.C b/framework/src/mesh/MooseMesh.C index 64bc5c0716d7..052e619735cb 100644 --- a/framework/src/mesh/MooseMesh.C +++ b/framework/src/mesh/MooseMesh.C @@ -51,6 +51,7 @@ #include "libmesh/hilbert_sfc_partitioner.h" #include "libmesh/morton_sfc_partitioner.h" #include "libmesh/edge_edge2.h" +#include "libmesh/checkpoint_io.h" #include "libmesh/mesh_refinement.h" #include "libmesh/quadrature.h" #include "libmesh/boundary_info.h" @@ -3020,6 +3021,14 @@ MooseMesh::init() } } +std::vector +MooseMesh::writeRecoveryFiles(const std::filesystem::path & file_base) const +{ + CheckpointIO io(const_cast(getMesh()), false); + io.write(file_base); + return {}; +} + unsigned int MooseMesh::dimension() const { diff --git a/framework/src/mfem/auxkernels/MFEMAuxKernel.C b/framework/src/mfem/auxkernels/MFEMAuxKernel.C index ab57bf10b21d..df1e27a2ca84 100644 --- a/framework/src/mfem/auxkernels/MFEMAuxKernel.C +++ b/framework/src/mfem/auxkernels/MFEMAuxKernel.C @@ -15,20 +15,26 @@ InputParameters MFEMAuxKernel::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMExecutedObject::validParams(); params.registerBase("AuxKernel"); - params.addClassDescription("Base class for MFEMGeneralUserObjects that update auxiliary " - "variables outside of the main solve step."); + params.addClassDescription("Base class for MFEM objects that update auxiliary variables outside " + "of the main solve step."); params.addRequiredParam("variable", "The name of the variable that this object applies to"); return params; } MFEMAuxKernel::MFEMAuxKernel(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), + : MFEMExecutedObject(parameters), _result_var_name(getParam("variable")), _result_var(*getMFEMProblem().getGridFunction(_result_var_name)) { } +std::set +MFEMAuxKernel::producedVariableNames() const +{ + return {_result_var_name}; +} + #endif diff --git a/framework/src/mfem/auxkernels/MFEMComplexAuxKernel.C b/framework/src/mfem/auxkernels/MFEMComplexAuxKernel.C index 58e0f6c03c2f..a70d2124912d 100644 --- a/framework/src/mfem/auxkernels/MFEMComplexAuxKernel.C +++ b/framework/src/mfem/auxkernels/MFEMComplexAuxKernel.C @@ -15,22 +15,28 @@ InputParameters MFEMComplexAuxKernel::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMExecutedObject::validParams(); params.registerBase("AuxKernel"); - params.addClassDescription("Base class for MFEMGeneralUserObjects that update auxiliary " - "variables outside of the main solve step."); + params.addClassDescription("Base class for MFEM objects that update auxiliary variables outside " + "of the main solve step."); params.addRequiredParam("variable", "The name of the variable that this object applies to"); return params; } MFEMComplexAuxKernel::MFEMComplexAuxKernel(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), + : MFEMExecutedObject(parameters), _result_var_name(getParam("variable")), _result_var(*getMFEMProblem().getComplexGridFunction(_result_var_name)) { } +std::set +MFEMComplexAuxKernel::producedVariableNames() const +{ + return {_result_var_name}; +} + void MFEMComplexAuxKernel::complexAdd(mfem::ParComplexGridFunction & a, const mfem::ParComplexGridFunction & b, diff --git a/framework/src/mfem/auxkernels/MFEMComplexCurlAux.C b/framework/src/mfem/auxkernels/MFEMComplexCurlAux.C index eaed9bb6795b..78bb62cd5e98 100644 --- a/framework/src/mfem/auxkernels/MFEMComplexCurlAux.C +++ b/framework/src/mfem/auxkernels/MFEMComplexCurlAux.C @@ -21,8 +21,8 @@ MFEMComplexCurlAux::validParams() params.addClassDescription( "Calculates the curl of a complex H(curl) conforming ND source variable and stores the result" " on an H(div) conforming RT result complex auxvariable"); - params.addRequiredParam("source", - "Vector H(curl) MFEMComplexVariable to take the curl of."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "source", "Vector H(curl) MFEMComplexVariable to take the curl of."); params.addParam( "scale_factor_real", 1.0, "Real part of the factor to scale result auxvariable by."); params.addParam( diff --git a/framework/src/mfem/auxkernels/MFEMComplexDivAux.C b/framework/src/mfem/auxkernels/MFEMComplexDivAux.C index 31e691902132..fa92c1325977 100644 --- a/framework/src/mfem/auxkernels/MFEMComplexDivAux.C +++ b/framework/src/mfem/auxkernels/MFEMComplexDivAux.C @@ -21,8 +21,8 @@ MFEMComplexDivAux::validParams() params.addClassDescription("Calculates the divergence of a complex H(div) conforming RT source " "variable and stores the result" " on an L2 conforming result complex auxvariable"); - params.addRequiredParam( - "source", "Vector H(div) MFEMComplexVariable to take the divergence of."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "source", "Vector H(div) MFEMComplexVariable to take the divergence of."); params.addParam( "scale_factor_real", 1.0, "Real part of the factor to scale result auxvariable by."); params.addParam( diff --git a/framework/src/mfem/auxkernels/MFEMComplexExteriorProductAux.C b/framework/src/mfem/auxkernels/MFEMComplexExteriorProductAux.C index e700a629405b..0af927256606 100644 --- a/framework/src/mfem/auxkernels/MFEMComplexExteriorProductAux.C +++ b/framework/src/mfem/auxkernels/MFEMComplexExteriorProductAux.C @@ -20,8 +20,10 @@ MFEMComplexExteriorProductAux::validParams() InputParameters params = MFEMComplexAuxKernel::validParams(); params.addClassDescription( "Projects $s \\vec u \\wedge \\vec v*$ onto a complex vector MFEM auxvariable"); - params.addRequiredParam("first_source_vec", "Complex vector variable (3D)"); - params.addRequiredParam("second_source_vec", "Complex vector variable (3D)"); + MFEMExecutedObject::addRequiredDependencyParam( + params, "first_source_vec", "Complex vector variable (3D)"); + MFEMExecutedObject::addRequiredDependencyParam( + params, "second_source_vec", "Complex vector variable (3D)"); params.addParam("scale_factor_real", 1.0, "Real part of constant multiplier"); params.addParam("scale_factor_imag", 0.0, "Imaginary part of constant multiplier"); diff --git a/framework/src/mfem/auxkernels/MFEMComplexGradAux.C b/framework/src/mfem/auxkernels/MFEMComplexGradAux.C index c46b4779b868..2209199a1224 100644 --- a/framework/src/mfem/auxkernels/MFEMComplexGradAux.C +++ b/framework/src/mfem/auxkernels/MFEMComplexGradAux.C @@ -21,8 +21,8 @@ MFEMComplexGradAux::validParams() params.addClassDescription( "Calculates the gradient of an H1 conforming source variable and stores the result" " on an H(curl) conforming ND result auxvariable"); - params.addRequiredParam("source", - "Scalar H1 MFEMVariable to take the gradient of."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "source", "Scalar H1 MFEMVariable to take the gradient of."); params.addParam( "scale_factor_real", 1.0, "Real part of the factor to scale result auxvariable by."); params.addParam( diff --git a/framework/src/mfem/auxkernels/MFEMComplexInnerProductAux.C b/framework/src/mfem/auxkernels/MFEMComplexInnerProductAux.C index ea5332d24e0c..149fe7eb790b 100644 --- a/framework/src/mfem/auxkernels/MFEMComplexInnerProductAux.C +++ b/framework/src/mfem/auxkernels/MFEMComplexInnerProductAux.C @@ -20,8 +20,10 @@ MFEMComplexInnerProductAux::validParams() InputParameters params = MFEMComplexAuxKernel::validParams(); params.addClassDescription( "Projects $s \\vec u \\cdot \\vec v*$ onto a complex scalar MFEM auxvariable"); - params.addRequiredParam("first_source_vec", "Complex vector variable"); - params.addRequiredParam("second_source_vec", "Complex vector variable"); + MFEMExecutedObject::addRequiredDependencyParam( + params, "first_source_vec", "Complex vector variable"); + MFEMExecutedObject::addRequiredDependencyParam( + params, "second_source_vec", "Complex vector variable"); params.addParam("scale_factor_real", 1.0, "Real part of constant multiplier"); params.addParam("scale_factor_imag", 0.0, "Imaginary part of constant multiplier"); diff --git a/framework/src/mfem/auxkernels/MFEMComplexSumAux.C b/framework/src/mfem/auxkernels/MFEMComplexSumAux.C index ba7555906008..f9d663b886d5 100644 --- a/framework/src/mfem/auxkernels/MFEMComplexSumAux.C +++ b/framework/src/mfem/auxkernels/MFEMComplexSumAux.C @@ -22,8 +22,8 @@ MFEMComplexSumAux::validParams() "Calculates the sum of an arbitrary number of complex variables sharing an FE space, each " "optionally scaled by a complex constant, and stores the result in an auxiliary complex " "variable."); - params.addRequiredParam>( - "source_variables", "The names of the MFEM complex variables to sum"); + MFEMExecutedObject::addRequiredDependencyParam>( + params, "source_variables", "The names of the MFEM complex variables to sum"); params.addParam>( "scale_factors_real", "The real parts of the factors to scale each MFEM variable by during summation"); diff --git a/framework/src/mfem/auxkernels/MFEMCurlAux.C b/framework/src/mfem/auxkernels/MFEMCurlAux.C index f4bf9f531678..5f2b6a29b161 100644 --- a/framework/src/mfem/auxkernels/MFEMCurlAux.C +++ b/framework/src/mfem/auxkernels/MFEMCurlAux.C @@ -21,8 +21,8 @@ MFEMCurlAux::validParams() params.addClassDescription( "Calculates the curl of an H(curl) conforming ND source variable and stores the result" " on an H(div) conforming RT result auxvariable"); - params.addRequiredParam("source", - "Vector H(curl) MFEMVariable to take the curl of."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "source", "Vector H(curl) MFEMVariable to take the curl of."); params.addParam("scale_factor", 1.0, "Factor to scale result auxvariable by."); return params; } diff --git a/framework/src/mfem/auxkernels/MFEMDivAux.C b/framework/src/mfem/auxkernels/MFEMDivAux.C index bcb242f36db0..e2c9a7c69843 100644 --- a/framework/src/mfem/auxkernels/MFEMDivAux.C +++ b/framework/src/mfem/auxkernels/MFEMDivAux.C @@ -21,8 +21,8 @@ MFEMDivAux::validParams() params.addClassDescription( "Calculates the divergence of an H(div) conforming RT source variable and stores the result" " on an L2 conforming result auxvariable"); - params.addRequiredParam("source", - "Vector H(div) MFEMVariable to take the divergence of."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "source", "Vector H(div) MFEMVariable to take the divergence of."); params.addParam("scale_factor", 1.0, "Factor to scale result auxvariable by."); return params; } diff --git a/framework/src/mfem/auxkernels/MFEMGradAux.C b/framework/src/mfem/auxkernels/MFEMGradAux.C index a1fce9e7c76e..6fa8becf9693 100644 --- a/framework/src/mfem/auxkernels/MFEMGradAux.C +++ b/framework/src/mfem/auxkernels/MFEMGradAux.C @@ -21,8 +21,8 @@ MFEMGradAux::validParams() params.addClassDescription( "Calculates the gradient of an H1 conforming source variable and stores the result" " on an H(curl) conforming ND result auxvariable"); - params.addRequiredParam("source", - "Scalar H1 MFEMVariable to take the gradient of."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "source", "Scalar H1 MFEMVariable to take the gradient of."); params.addParam("scale_factor", 1.0, "Factor to scale result auxvariable by."); return params; } diff --git a/framework/src/mfem/auxkernels/MFEMSumAux.C b/framework/src/mfem/auxkernels/MFEMSumAux.C index 324e23d24a4c..608b9f41533f 100644 --- a/framework/src/mfem/auxkernels/MFEMSumAux.C +++ b/framework/src/mfem/auxkernels/MFEMSumAux.C @@ -21,8 +21,8 @@ MFEMSumAux::validParams() params.addClassDescription( "Calculates the sum of an arbitrary number of variables sharing an FE space, each " "optionally scaled by a real constant, and stores the result in an auxiliary variable."); - params.addRequiredParam>("source_variables", - "The names of the MFEM variables to sum"); + MFEMExecutedObject::addRequiredDependencyParam>( + params, "source_variables", "The names of the MFEM variables to sum"); params.addParam>( "scale_factors", "The factors to scale each MFEM variable by during summation"); return params; diff --git a/framework/src/mfem/base/MFEMExecutedObject.C b/framework/src/mfem/base/MFEMExecutedObject.C new file mode 100644 index 000000000000..114fd992fdc1 --- /dev/null +++ b/framework/src/mfem/base/MFEMExecutedObject.C @@ -0,0 +1,167 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#include "MFEMExecutedObject.h" + +InputParameters +MFEMExecutedObject::validParams() +{ + InputParameters params = MFEMObject::validParams(); + params += SetupInterface::validParams(); + params.registerSystemAttributeName("MFEMExecutedObject"); + params.addPrivateParam>("_mfem_dependency_param_names", {}); + params.addPrivateParam>("_mfem_dependency_param_kinds", {}); + params.addPrivateParam>("_mfem_dependency_param_is_vector", {}); + params.set("execute_on", true) = EXEC_TIMESTEP_END; + params.addClassDescription("Base class for executed MFEM objects."); + return params; +} + +MFEMExecutedObject::MFEMExecutedObject(const InputParameters & parameters) + : MFEMObject(parameters), SetupInterface(this), DependencyResolverInterface() +{ + const auto & param_names = getParam>("_mfem_dependency_param_names"); + const auto & kinds = getParam>("_mfem_dependency_param_kinds"); + const auto & is_vector_flags = getParam>("_mfem_dependency_param_is_vector"); + + mooseAssert(param_names.size() == kinds.size() && kinds.size() == is_vector_flags.size(), + "MFEM dependency parameter metadata size mismatch"); + + _dependency_params.reserve(param_names.size()); + for (const auto i : index_range(param_names)) + _dependency_params.push_back( + {static_cast(kinds[i]), param_names[i], is_vector_flags[i]}); +} + +std::set +MFEMExecutedObject::producedVariableNames() const +{ + return {}; +} + +std::set +MFEMExecutedObject::producedPostprocessorNames() const +{ + return {}; +} + +std::set +MFEMExecutedObject::producedVectorPostprocessorNames() const +{ + return {}; +} + +const std::set & +MFEMExecutedObject::getRequestedItems() +{ + if (_requested_items) + return *_requested_items; + + _requested_items.emplace(); + + for (const auto & dep : _dependency_params) + { + if (!isParamValid(dep.param_name)) + continue; + + switch (dep.kind) + { + case DependencyKind::Variable: + if (dep.is_vector) + for (const auto & name : getParam>(dep.param_name)) + _requested_items->insert(variableDependencyKey(name)); + else + _requested_items->insert(variableDependencyKey(getParam(dep.param_name))); + break; + case DependencyKind::Postprocessor: + if (dep.is_vector) + for (const auto & name : getParam>(dep.param_name)) + _requested_items->insert(postprocessorDependencyKey(name)); + else + _requested_items->insert( + postprocessorDependencyKey(getParam(dep.param_name))); + break; + case DependencyKind::VectorPostprocessor: + if (dep.is_vector) + for (const auto & name : getParam>(dep.param_name)) + _requested_items->insert(vectorPostprocessorDependencyKey(name)); + else + _requested_items->insert( + vectorPostprocessorDependencyKey(getParam(dep.param_name))); + break; + } + } + + return *_requested_items; +} + +const std::set & +MFEMExecutedObject::getSuppliedItems() +{ + if (_supplied_items) + return *_supplied_items; + + _supplied_items.emplace(); + + for (const auto & name : producedVariableNames()) + _supplied_items->insert(variableDependencyKey(name)); + for (const auto & name : producedPostprocessorNames()) + _supplied_items->insert(postprocessorDependencyKey(name)); + for (const auto & name : producedVectorPostprocessorNames()) + _supplied_items->insert(vectorPostprocessorDependencyKey(name)); + + return *_supplied_items; +} + +std::string +MFEMExecutedObject::variableDependencyKey(const std::string & name) +{ + return "variable:" + name; +} + +std::string +MFEMExecutedObject::postprocessorDependencyKey(const std::string & name) +{ + return "postprocessor:" + name; +} + +std::string +MFEMExecutedObject::vectorPostprocessorDependencyKey(const std::string & name) +{ + return "vector_postprocessor:" + name; +} + +void +MFEMExecutedObject::appendDependencyParam(InputParameters & params, + const std::string & param_name, + const DependencyKind kind, + const bool is_vector) +{ + auto & param_names = params.set>("_mfem_dependency_param_names"); + auto & kinds = params.set>("_mfem_dependency_param_kinds"); + auto & is_vector_flags = params.set>("_mfem_dependency_param_is_vector"); + +#ifndef NDEBUG + const auto it = std::find(param_names.begin(), param_names.end(), param_name); + if (it != param_names.end()) + { + const auto idx = std::distance(param_names.begin(), it); + mooseAssert(kinds[idx] == static_cast(kind) && is_vector_flags[idx] == is_vector, + "MFEM dependency parameter metadata mismatch for parameter " + param_name); + } +#endif + + param_names.push_back(param_name); + kinds.push_back(static_cast(kind)); + is_vector_flags.push_back(is_vector); +} + +#endif diff --git a/framework/src/mfem/base/MFEMObject.C b/framework/src/mfem/base/MFEMObject.C new file mode 100644 index 000000000000..28ce042464ef --- /dev/null +++ b/framework/src/mfem/base/MFEMObject.C @@ -0,0 +1,75 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#include "MFEMObject.h" +#include "MFEMProblem.h" +#include "SubProblem.h" + +InputParameters +MFEMObject::validParams() +{ + InputParameters params = MooseObject::validParams(); + params += FunctionInterface::validParams(); + params += PostprocessorInterface::validParams(); + params += VectorPostprocessorInterface::validParams(); + params += ReporterInterface::validParams(); + params.addClassDescription("Base class for MFEM objects backed directly by MooseObject."); + return params; +} + +MFEMObject::MFEMObject(const InputParameters & parameters) + : MooseObject(parameters), + FunctionInterface(this), + PostprocessorInterface(this), + VectorPostprocessorInterface(this), + ReporterInterface(this), + _mfem_problem( + static_cast(*parameters.getCheckedPointerParam("_subproblem"))) +{ +} + +mfem::Coefficient & +MFEMObject::getScalarCoefficientByName(const MFEMScalarCoefficientName & name) +{ + return getMFEMProblem().getCoefficients().getScalarCoefficient(name); +} + +mfem::VectorCoefficient & +MFEMObject::getVectorCoefficientByName(const MFEMVectorCoefficientName & name) +{ + return getMFEMProblem().getCoefficients().getVectorCoefficient(name); +} + +mfem::MatrixCoefficient & +MFEMObject::getMatrixCoefficientByName(const MFEMMatrixCoefficientName & name) +{ + return getMFEMProblem().getCoefficients().getMatrixCoefficient(name); +} + +mfem::Coefficient & +MFEMObject::getScalarCoefficient(const std::string & name) +{ + return getScalarCoefficientByName(getParam(name)); +} + +mfem::VectorCoefficient & +MFEMObject::getVectorCoefficient(const std::string & name) +{ + return getVectorCoefficientByName(getParam(name)); +} + +mfem::MatrixCoefficient & +MFEMObject::getMatrixCoefficient(const std::string & name) +{ + return getMatrixCoefficientByName(getParam(name)); +} + +#endif diff --git a/framework/src/mfem/bcs/MFEMBoundaryCondition.C b/framework/src/mfem/bcs/MFEMBoundaryCondition.C index 33980221b843..0110a667b82a 100644 --- a/framework/src/mfem/bcs/MFEMBoundaryCondition.C +++ b/framework/src/mfem/bcs/MFEMBoundaryCondition.C @@ -18,17 +18,18 @@ InputParameters MFEMBoundaryCondition::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMObject::validParams(); params += MFEMBoundaryRestrictable::validParams(); params.addClassDescription("Base class for applying boundary conditions to MFEM problems."); params.registerBase("BoundaryCondition"); + params.registerSystemAttributeName("BoundaryCondition"); params.addParam("variable", "Variable on which to apply the boundary condition"); return params; } MFEMBoundaryCondition::MFEMBoundaryCondition(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), + : MFEMObject(parameters), MFEMBoundaryRestrictable( parameters, getMFEMProblem().getMFEMVariableMesh(getParam("variable"))), _test_var_name(getParam("variable")) diff --git a/framework/src/mfem/equation_systems/EquationSystem.C b/framework/src/mfem/equation_systems/EquationSystem.C index 0ca658e99ecf..a5c89cebddba 100644 --- a/framework/src/mfem/equation_systems/EquationSystem.C +++ b/framework/src/mfem/equation_systems/EquationSystem.C @@ -149,15 +149,11 @@ EquationSystem::AddEssentialBC(std::shared_ptr bc) void EquationSystem::Init(Moose::MFEM::GridFunctions & gridfunctions, - Moose::MFEM::ComplexGridFunctions & cmplx_gridfunctions, + Moose::MFEM::ComplexGridFunctions & /*cmplx_gridfunctions*/, mfem::AssemblyLevel assembly_level) { _assembly_level = assembly_level; - if (cmplx_gridfunctions.size()) - mooseError("Complex variables have been created but the executioner numeric type has not been " - "set to complex. Please set Executioner/numeric_type = complex."); - // Extract which coupled variables are to be trivially eliminated and which are trial variables SetTrialVariableNames(); diff --git a/framework/src/mfem/fespaces/MFEMFESpace.C b/framework/src/mfem/fespaces/MFEMFESpace.C index 72e99099c0be..e81082aee257 100644 --- a/framework/src/mfem/fespaces/MFEMFESpace.C +++ b/framework/src/mfem/fespaces/MFEMFESpace.C @@ -15,8 +15,9 @@ InputParameters MFEMFESpace::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMObject::validParams(); params.registerBase("MFEMFESpace"); + params.registerSystemAttributeName("MFEMFESpace"); MooseEnum ordering("NODES VDIM", "VDIM", false); params.addParam("ordering", ordering, "Ordering style to use for vector DoFs."); params.addParam("vdim", 1, "The number of degrees of freedom per basis function."); @@ -26,7 +27,7 @@ MFEMFESpace::validParams() } MFEMFESpace::MFEMFESpace(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), + : MFEMObject(parameters), _ordering(parameters.get("ordering")), _pmesh( parameters.isParamValid("submesh") diff --git a/framework/src/mfem/functormaterials/MFEMFunctorMaterial.C b/framework/src/mfem/functormaterials/MFEMFunctorMaterial.C index 0d279383cb96..2fa110458be4 100644 --- a/framework/src/mfem/functormaterials/MFEMFunctorMaterial.C +++ b/framework/src/mfem/functormaterials/MFEMFunctorMaterial.C @@ -15,13 +15,14 @@ InputParameters MFEMFunctorMaterial::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMObject::validParams(); params += MFEMBlockRestrictable::validParams(); params += MFEMBoundaryRestrictable::validParams(); params.addClassDescription( "Base class for declaration of material properties to add to MFEM problems."); params.registerBase("FunctorMaterial"); + params.registerSystemAttributeName("FunctorMaterial"); params.addPrivateParam("_neighbor", false); params.addPrivateParam("_interface", false); return params; @@ -34,7 +35,7 @@ MFEMFunctorMaterial::pointFromMFEMVector(const mfem::Vector & vec) } MFEMFunctorMaterial::MFEMFunctorMaterial(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), + : MFEMObject(parameters), MFEMBlockRestrictable(parameters, getMFEMProblem().mesh().getMFEMParMesh()), MFEMBoundaryRestrictable(parameters, getMFEMProblem().mesh().getMFEMParMesh()), _properties(getMFEMProblem().getCoefficients()) diff --git a/framework/src/mfem/ics/MFEMInitialCondition.C b/framework/src/mfem/ics/MFEMInitialCondition.C index 450b2efc63ca..b4f871f2eac7 100644 --- a/framework/src/mfem/ics/MFEMInitialCondition.C +++ b/framework/src/mfem/ics/MFEMInitialCondition.C @@ -14,23 +14,29 @@ InputParameters MFEMInitialCondition::validParams() { - auto params = MFEMGeneralUserObject::validParams(); + auto params = MFEMExecutedObject::validParams(); params.addRequiredParam("variable", "The variable to apply the initial condition on."); params.registerBase("InitialCondition"); params.addClassDescription( "Base class for objects that set the initial condition on an MFEM variable."); // We cannot generally execute this at construction time since the coefficient may be based on a - // MOOSE function which is not itself setup until its initialSetup is called. UserObject initial - // execution occurs after function initialSetup + // MOOSE function which is not itself setup until its initialSetup is called. MFEM initial + // conditions therefore run in the explicit executed-object pass on EXEC_INITIAL. params.set("execute_on") = {EXEC_INITIAL}; params.suppressParameter("execute_on"); return params; } MFEMInitialCondition::MFEMInitialCondition(const InputParameters & params) - : MFEMGeneralUserObject(params) + : MFEMExecutedObject(params) { } +std::set +MFEMInitialCondition::producedVariableNames() const +{ + return {getParam("variable")}; +} + #endif diff --git a/framework/src/mfem/indicators/MFEMIndicator.C b/framework/src/mfem/indicators/MFEMIndicator.C index 19e61422caf6..0b2751bb0d61 100644 --- a/framework/src/mfem/indicators/MFEMIndicator.C +++ b/framework/src/mfem/indicators/MFEMIndicator.C @@ -15,8 +15,9 @@ InputParameters MFEMIndicator::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMObject::validParams(); params.registerBase("Indicator"); + params.registerSystemAttributeName("Indicator"); params.addRequiredParam("variable", "Variable to perform amr with"); params.addRequiredParam("kernel", "Kernel to perform amr with"); @@ -24,7 +25,7 @@ MFEMIndicator::validParams() } MFEMIndicator::MFEMIndicator(const InputParameters & params) - : MFEMGeneralUserObject(params), + : MFEMObject(params), _var_name(getParam("variable")), _kernel_name(getParam("kernel")), _fespace(*getMFEMProblem().getGridFunction(_var_name)->ParFESpace()) diff --git a/framework/src/mfem/indicators/MFEML2ZienkiewiczZhuIndicator.C b/framework/src/mfem/indicators/MFEML2ZienkiewiczZhuIndicator.C index 6aef176f717f..73bfb5082306 100644 --- a/framework/src/mfem/indicators/MFEML2ZienkiewiczZhuIndicator.C +++ b/framework/src/mfem/indicators/MFEML2ZienkiewiczZhuIndicator.C @@ -19,8 +19,8 @@ InputParameters MFEML2ZienkiewiczZhuIndicator::validParams() { InputParameters params = MFEMIndicator::validParams(); - params.addParam("flux_fespace", "FE space to write the flux into"); - params.addParam("smooth_flux_fespace", "FE space to write the smooth flux into"); + params.addParam("flux_fespace", "FE space to write the flux into"); + params.addParam("smooth_flux_fespace", "FE space to write the smooth flux into"); return params; } @@ -31,7 +31,10 @@ MFEML2ZienkiewiczZhuIndicator::MFEML2ZienkiewiczZhuIndicator(const InputParamete if (isParamSetByUser("flux_fespace")) { // fetch the flux_fespace from the object system - auto object_ptr = getUserObject("flux_fespace").getSharedPtr(); + auto object_ptr = + getMFEMProblem() + .getMFEMObject("MFEMFESpace", getParam("flux_fespace")) + .getSharedPtr(); auto fespace_ptr = std::dynamic_pointer_cast(object_ptr); _flux_fes = fespace_ptr->getFESpace(); } @@ -39,7 +42,10 @@ MFEML2ZienkiewiczZhuIndicator::MFEML2ZienkiewiczZhuIndicator(const InputParamete if (isParamSetByUser("smooth_flux_fespace")) { // fetch the smooth_flux_fespace from the object system - auto object_ptr = getUserObject("smooth_flux_fespace").getSharedPtr(); + auto object_ptr = getMFEMProblem() + .getMFEMObject( + "MFEMFESpace", getParam("smooth_flux_fespace")) + .getSharedPtr(); auto fespace_ptr = std::dynamic_pointer_cast(object_ptr); _smooth_flux_fes = fespace_ptr->getFESpace(); } @@ -49,7 +55,7 @@ void MFEML2ZienkiewiczZhuIndicator::createEstimator() { // fetch the kernel first so we can build an auxiliary blf integrator - MFEMKernel & kernel = getMFEMProblem().getUserObject(_kernel_name); + MFEMKernel & kernel = getMFEMProblem().getMFEMObject("Kernel", _kernel_name); _integ = std::unique_ptr(kernel.createBFIntegrator()); // Next, we need to check that this integrator is supported by mfem::L2ZienkiewiczZhuEstimator diff --git a/framework/src/mfem/kernels/MFEMComplexKernel.C b/framework/src/mfem/kernels/MFEMComplexKernel.C index 5bf46ecb6b46..a4b7c2b35498 100644 --- a/framework/src/mfem/kernels/MFEMComplexKernel.C +++ b/framework/src/mfem/kernels/MFEMComplexKernel.C @@ -18,23 +18,13 @@ registerMooseObject("MooseApp", MFEMComplexKernel); InputParameters MFEMComplexKernel::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); - params += MFEMBlockRestrictable::validParams(); - params.registerBase("Kernel"); - params.addParam("variable", - "Variable labelling the weak form this kernel is added to"); + InputParameters params = MFEMKernel::validParams(); params.addClassDescription( "Holds MFEMKernel objects for the real and imaginary parts of a complex kernel."); return params; } -MFEMComplexKernel::MFEMComplexKernel(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), - MFEMBlockRestrictable(parameters, - getMFEMProblem().getMFEMVariableMesh(getParam("variable"))), - _test_var_name(getParam("variable")) -{ -} +MFEMComplexKernel::MFEMComplexKernel(const InputParameters & parameters) : MFEMKernel(parameters) {} #endif diff --git a/framework/src/mfem/kernels/MFEMKernel.C b/framework/src/mfem/kernels/MFEMKernel.C index e7aaf45be380..8ea15435b571 100644 --- a/framework/src/mfem/kernels/MFEMKernel.C +++ b/framework/src/mfem/kernels/MFEMKernel.C @@ -18,16 +18,17 @@ InputParameters MFEMKernel::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMObject::validParams(); params += MFEMBlockRestrictable::validParams(); params.registerBase("Kernel"); + params.registerSystemAttributeName("Kernel"); params.addParam("variable", "Variable labelling the weak form this kernel is added to"); return params; } MFEMKernel::MFEMKernel(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), + : MFEMObject(parameters), MFEMBlockRestrictable(parameters, getMFEMProblem().getMFEMVariableMesh(getParam("variable"))), _test_var_name(getParam("variable")) diff --git a/framework/src/mfem/markers/MFEMRefinementMarker.C b/framework/src/mfem/markers/MFEMRefinementMarker.C index c83d2ff5ec26..185e09f3fd08 100644 --- a/framework/src/mfem/markers/MFEMRefinementMarker.C +++ b/framework/src/mfem/markers/MFEMRefinementMarker.C @@ -17,8 +17,9 @@ registerMooseObject("MooseApp", MFEMRefinementMarker); InputParameters MFEMRefinementMarker::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMObject::validParams(); params.registerBase("Marker"); + params.registerSystemAttributeName("Marker"); params.addRequiredParam("indicator", "Estimator to use"); params.addRangeCheckedParam("threshold", @@ -35,7 +36,7 @@ MFEMRefinementMarker::validParams() } MFEMRefinementMarker::MFEMRefinementMarker(const InputParameters & params) - : MFEMGeneralUserObject(params), + : MFEMObject(params), _estimator_name(getParam("indicator")), _error_threshold(getParam("threshold")), _rebalance(getParam("rebalance")), @@ -48,7 +49,7 @@ void MFEMRefinementMarker::initialSetup() { // fetch const ref to the estimator - _estimator = &getUserObjectByName(_estimator_name); + _estimator = &getMFEMProblem().getMFEMObject("Indicator", _estimator_name); // Check if p-refinement is supported by the fespace supplied with the variable if (_max_p_level and !_estimator->getFESpace().PRefinementSupported()) diff --git a/framework/src/mfem/mesh/MFEMMesh.C b/framework/src/mfem/mesh/MFEMMesh.C index db0a8d3e21ed..8373b3958d0a 100644 --- a/framework/src/mfem/mesh/MFEMMesh.C +++ b/framework/src/mfem/mesh/MFEMMesh.C @@ -10,8 +10,11 @@ #ifdef MOOSE_MFEM_ENABLED #include "MFEMMesh.h" +#include "MooseApp.h" #include "libmesh/mesh_generation.h" +#include + registerMooseObject("MooseApp", MFEMMesh); InputParameters @@ -47,6 +50,37 @@ MFEMMesh::MFEMMesh(const InputParameters & parameters) : FileMesh(parameters) {} MFEMMesh::~MFEMMesh() {} +void +MFEMMesh::init() +{ + // _mesh must be constructed by the action system before init() is called. + // MFEMMesh is always used within the normal MOOSE action system, so the fringe + // case of a mesh built outside that system does not apply here. + mooseAssert(_mesh, "MooseMesh base mesh must be constructed before MFEMMesh::init()"); + + // MooseMesh::init() handles the libMesh dummy mesh: + // - recovery: reads the libMesh mesh back from its checkpoint file + // - normal run: calls buildMesh(), which for MFEMMesh builds both the + // dummy libMesh mesh and the MFEM ParMesh + MooseMesh::init(); + + if (_app.isRecovering() && recoveryAllowed() && _app.isUltimateMaster()) + { + // MooseMesh::init() already restored the libMesh dummy mesh from its checkpoint. + // Now restore the MFEM parallel mesh from its own checkpoint file. + const auto checkpoint_file = _app.getRestartRecoverFileBase() + _app.checkpointSuffix() + + ".mfem.mesh." + std::to_string(this->processor_id()); + std::ifstream input(checkpoint_file); + if (!input) + mooseError("Unable to open MFEM recovery mesh file '", checkpoint_file, "'."); + + _mfem_par_mesh = std::make_shared(this->comm().get(), input); + + if (isParamSetByUser("displacement")) + _mesh_displacement_variable.emplace(getParam("displacement")); + } +} + void MFEMMesh::buildMesh() { @@ -95,6 +129,23 @@ MFEMMesh::buildMesh() _mesh_displacement_variable.emplace(getParam("displacement")); } +std::vector +MFEMMesh::writeRecoveryFiles(const std::filesystem::path & file_base) const +{ + MooseMesh::writeRecoveryFiles(file_base); + + mooseAssert(_mfem_par_mesh, "MFEM parallel mesh is not initialized"); + + const auto checkpoint_file = + file_base.string() + ".mfem.mesh." + std::to_string(this->processor_id()); + std::ofstream output(checkpoint_file); + if (!output) + mooseError("Unable to open MFEM recovery mesh file '", checkpoint_file, "' for writing."); + + _mfem_par_mesh->ParPrint(output); + return {checkpoint_file}; +} + void MFEMMesh::displace(mfem::GridFunction const & displacement) { diff --git a/framework/src/mfem/postprocessors/MFEMComplexVectorPeriodAveragedPostprocessor.C b/framework/src/mfem/postprocessors/MFEMComplexVectorPeriodAveragedPostprocessor.C index 0084ec077340..1c6ecc5c2c52 100644 --- a/framework/src/mfem/postprocessors/MFEMComplexVectorPeriodAveragedPostprocessor.C +++ b/framework/src/mfem/postprocessors/MFEMComplexVectorPeriodAveragedPostprocessor.C @@ -24,10 +24,10 @@ MFEMComplexVectorPeriodAveragedPostprocessor::validParams() "coefficient."); params.addParam( "coefficient", "1.", "Name of optional scalar coefficient to scale integrand by."); - params.addRequiredParam( - "primal_variable", "Name of the first complex vector variable in the inner product."); - params.addRequiredParam( - "dual_variable", "Name of the second complex vector variable in the inner product."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "primal_variable", "Name of the first complex vector variable in the inner product."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "dual_variable", "Name of the second complex vector variable in the inner product."); return params; } diff --git a/framework/src/mfem/postprocessors/MFEML2Error.C b/framework/src/mfem/postprocessors/MFEML2Error.C index c59d6fd5713c..f76cd4b2212b 100644 --- a/framework/src/mfem/postprocessors/MFEML2Error.C +++ b/framework/src/mfem/postprocessors/MFEML2Error.C @@ -23,8 +23,8 @@ MFEML2Error::validParams() "gridfunctions using H1 or L2 elements."); params.addParam("function", "The analytic solution to compare against."); - params.addParam("variable", - "Name of the variable of which to find the norm of the error."); + MFEMExecutedObject::addDependencyParam( + params, "variable", "Name of the variable of which to find the norm of the error."); return params; } diff --git a/framework/src/mfem/postprocessors/MFEMPostprocessor.C b/framework/src/mfem/postprocessors/MFEMPostprocessor.C index 5cb3989e8690..4f8ad21fdb21 100644 --- a/framework/src/mfem/postprocessors/MFEMPostprocessor.C +++ b/framework/src/mfem/postprocessors/MFEMPostprocessor.C @@ -14,14 +14,21 @@ InputParameters MFEMPostprocessor::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMExecutedObject::validParams(); params += Postprocessor::validParams(); + params.registerSystemAttributeName("MFEMExecutedObject"); return params; } MFEMPostprocessor::MFEMPostprocessor(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), Postprocessor(this) + : MFEMExecutedObject(parameters), Postprocessor(this) { } +std::set +MFEMPostprocessor::producedPostprocessorNames() const +{ + return {name()}; +} + #endif diff --git a/framework/src/mfem/postprocessors/MFEMVectorFEInnerProductIntegralPostprocessor.C b/framework/src/mfem/postprocessors/MFEMVectorFEInnerProductIntegralPostprocessor.C index 9d9064cf84a1..334df614066d 100644 --- a/framework/src/mfem/postprocessors/MFEMVectorFEInnerProductIntegralPostprocessor.C +++ b/framework/src/mfem/postprocessors/MFEMVectorFEInnerProductIntegralPostprocessor.C @@ -23,10 +23,10 @@ MFEMVectorFEInnerProductIntegralPostprocessor::validParams() "Calculates the integral of the inner product of two vector variables within a subdomain."); params.addParam( "coefficient", "1.", "Name of optional scalar coefficient to scale integrand by."); - params.addRequiredParam("primal_variable", - "Name of the first vector variable in the inner product."); - params.addRequiredParam("dual_variable", - "Name of the second vector variable in the inner product."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "primal_variable", "Name of the first vector variable in the inner product."); + MFEMExecutedObject::addRequiredDependencyParam( + params, "dual_variable", "Name of the second vector variable in the inner product."); return params; } diff --git a/framework/src/mfem/postprocessors/MFEMVectorL2Error.C b/framework/src/mfem/postprocessors/MFEMVectorL2Error.C index 8e13c621339a..fd02f92e6d41 100644 --- a/framework/src/mfem/postprocessors/MFEMVectorL2Error.C +++ b/framework/src/mfem/postprocessors/MFEMVectorL2Error.C @@ -23,8 +23,8 @@ MFEMVectorL2Error::validParams() "vector gridfunctions."); params.addParam("function", "The analytic solution to compare against."); - params.addParam( - "variable", "Name of the vector variable of which to find the norm of the error."); + MFEMExecutedObject::addDependencyParam( + params, "variable", "Name of the vector variable of which to find the norm of the error."); return params; } diff --git a/framework/src/mfem/problem/MFEMProblem.C b/framework/src/mfem/problem/MFEMProblem.C index 561ae33db43e..4e81cfd1791e 100644 --- a/framework/src/mfem/problem/MFEMProblem.C +++ b/framework/src/mfem/problem/MFEMProblem.C @@ -15,10 +15,20 @@ #include "MFEMIndicator.h" #include "MFEMSubMesh.h" #include "MFEMFunctorMaterial.h" +#include "MFEMSubMeshTransfer.h" +#include "MFEMExecutedObject.h" +#include "Postprocessor.h" +#include "VectorPostprocessor.h" +#include "DataIO.h" + #include "libmesh/string_to_enum.h" #include #include +#include +#include +#include +#include registerMooseObject("MooseApp", MFEMProblem); @@ -35,7 +45,10 @@ MFEMProblem::validParams() } MFEMProblem::MFEMProblem(const InputParameters & params) - : ExternalProblem(params), num_type{static_cast(getParam("numeric_type"))} + : ExternalProblem(params), + num_type{static_cast(getParam("numeric_type"))}, + _solution_state_data(declareRestartableDataWithContext( + "mfem_solution_state", &_problem_data)) { // Initialise Hypre for all MFEM problems. mfem::Hypre::Init(); @@ -48,6 +61,22 @@ void MFEMProblem::initialSetup() { FEProblemBase::initialSetup(); + + // MFEM indicators create their estimators during addIndicator(); markers still need an explicit + // setup pass because they are no longer initialized through the libMesh/MOOSE user-object path. + std::vector markers; + theWarehouse().query().condition("Marker").queryInto(markers); + for (auto marker : markers) + marker->initialSetup(); +} + +void +MFEMProblem::execute(const ExecFlagType & exec_type) +{ + setCurrentExecuteOnFlag(exec_type); + executeMFEMObjects(exec_type); + + FEProblemBase::execute(exec_type); } void @@ -65,7 +94,7 @@ MFEMProblem::addMFEMPreconditioner(const std::string & user_object_name, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(user_object_name, name, parameters); + addObject(user_object_name, name, parameters); } void @@ -73,9 +102,7 @@ MFEMProblem::addIndicator(const std::string & user_object_name, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(user_object_name, name, parameters); - auto object_ptr = getUserObject(name).getSharedPtr(); - auto estimator = std::dynamic_pointer_cast(object_ptr); + auto estimator = addObject(user_object_name, name, parameters).front(); // construct the estimator itself estimator->createEstimator(); @@ -86,10 +113,8 @@ MFEMProblem::addMarker(const std::string & user_object_name, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(user_object_name, name, parameters); - auto object_ptr = getUserObject(name).getSharedPtr(); - - getProblemData().refiner = std::dynamic_pointer_cast(object_ptr); + getProblemData().refiner = + addObject(user_object_name, name, parameters).front(); } void @@ -97,10 +122,8 @@ MFEMProblem::addMFEMSolver(const std::string & user_object_name, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(user_object_name, name, parameters); - auto object_ptr = getUserObject(name).getSharedPtr(); - - getProblemData().jacobian_solver = std::dynamic_pointer_cast(object_ptr); + getProblemData().jacobian_solver = + addObject(user_object_name, name, parameters).front(); } void @@ -125,53 +148,49 @@ MFEMProblem::addBoundaryCondition(const std::string & bc_name, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(bc_name, name, parameters); - const UserObject * mfem_bc_uo = &(getUserObjectBase(name)); + auto bc = addObject(bc_name, name, parameters).front(); + const auto & mfem_bc = *bc; - if (dynamic_cast(mfem_bc_uo)) + if (dynamic_cast(&mfem_bc)) { - auto object_ptr = getUserObject(name).getSharedPtr(); - auto bc = std::dynamic_pointer_cast(object_ptr); + auto integrated_bc = std::dynamic_pointer_cast(bc); auto eqsys = std::dynamic_pointer_cast(getProblemData().eqn_system); if (eqsys) - eqsys->AddIntegratedBC(std::move(bc)); + eqsys->AddIntegratedBC(std::move(integrated_bc)); else mooseError("Cannot add integrated BC with name '" + name + "' because there is no corresponding equation system."); } - else if (dynamic_cast(mfem_bc_uo)) + else if (dynamic_cast(&mfem_bc)) { - auto object_ptr = getUserObject(name).getSharedPtr(); - auto bc = std::dynamic_pointer_cast(object_ptr); + auto integrated_bc = std::dynamic_pointer_cast(bc); auto eqsys = std::dynamic_pointer_cast(getProblemData().eqn_system); if (eqsys) - eqsys->AddComplexIntegratedBC(std::move(bc)); + eqsys->AddComplexIntegratedBC(std::move(integrated_bc)); else mooseError("Cannot add complex integrated BC with name '" + name + "' because there is no corresponding equation system."); } - else if (dynamic_cast(mfem_bc_uo)) + else if (dynamic_cast(&mfem_bc)) { - auto object_ptr = getUserObject(name).getSharedPtr(); - auto bc = std::dynamic_pointer_cast(object_ptr); + auto essential_bc = std::dynamic_pointer_cast(bc); auto eqsys = std::dynamic_pointer_cast(getProblemData().eqn_system); if (eqsys) - eqsys->AddComplexEssentialBCs(std::move(bc)); + eqsys->AddComplexEssentialBCs(std::move(essential_bc)); else mooseError("Cannot add boundary condition with name '" + name + "' because there is no corresponding equation system."); } - else if (dynamic_cast(mfem_bc_uo)) + else if (dynamic_cast(&mfem_bc)) { - auto object_ptr = getUserObject(name).getSharedPtr(); - auto bc = std::dynamic_pointer_cast(object_ptr); + auto essential_bc = std::dynamic_pointer_cast(bc); auto eqsys = std::dynamic_pointer_cast(getProblemData().eqn_system); if (eqsys) - eqsys->AddEssentialBC(std::move(bc)); + eqsys->AddEssentialBC(std::move(essential_bc)); else mooseError("Cannot add boundary condition with name '" + name + "' because there is no corresponding equation system."); @@ -194,17 +213,15 @@ MFEMProblem::addFunctorMaterial(const std::string & material_name, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(material_name, name, parameters); - getUserObject(name); + addObject(material_name, name, parameters); } void -MFEMProblem::addFESpace(const std::string & user_object_name, +MFEMProblem::addFESpace(const std::string & type, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(user_object_name, name, parameters); - MFEMFESpace & mfem_fespace(getUserObject(name)); + auto & mfem_fespace = *addObject(type, name, parameters).front(); // Register fespace and associated fe collection. getProblemData().fecs.Register(name, mfem_fespace.getFEC()); @@ -223,7 +240,7 @@ MFEMProblem::addVariable(const std::string & var_type, if (isTransient()) { const auto time_derivative_var_name = - getUserObject(var_name).getTimeDerivativeName(); + getMFEMObject("MooseVariableBase", var_name).getTimeDerivativeName(); getProblemData().time_derivative_map.addTimeDerivativeAssociation(var_name, time_derivative_var_name); addGridFunction(var_type, time_derivative_var_name, parameters); @@ -239,7 +256,10 @@ MFEMProblem::addGridFunction(const std::string & var_type, if (var_type == "MFEMVariable" || var_type == "MFEMComplexVariable") { // Add MFEM variable directly. - FEProblemBase::addUserObject(var_type, var_name, parameters); + if (var_type == "MFEMComplexVariable") + addObject(var_type, var_name, parameters); + else + addObject(var_type, var_name, parameters); } else { @@ -248,13 +268,14 @@ MFEMProblem::addGridFunction(const std::string & var_type, // Add MFEM variable indirectly ("gridfunction"). InputParameters mfem_variable_params = addMFEMFESpaceFromMOOSEVariable(parameters); - FEProblemBase::addUserObject("MFEMVariable", var_name, mfem_variable_params); + addObject("MFEMVariable", var_name, mfem_variable_params); } // Register gridfunction. if (var_type == "MFEMComplexVariable") { - MFEMComplexVariable & mfem_variable = getUserObject(var_name); + MFEMComplexVariable & mfem_variable = + getMFEMObject("MooseVariableBase", var_name); getProblemData().cmplx_gridfunctions.Register(var_name, mfem_variable.getComplexGridFunction()); if (mfem_variable.getFESpace().isScalar()) { @@ -273,7 +294,7 @@ MFEMProblem::addGridFunction(const std::string & var_type, } else // must be real, but may have been set up indirectly from a MOOSE variable { - MFEMVariable & mfem_variable = getUserObject(var_name); + MFEMVariable & mfem_variable = getMFEMObject("MooseVariableBase", var_name); getProblemData().gridfunctions.Register(var_name, mfem_variable.getGridFunction()); if (mfem_variable.getFESpace().isScalar()) getCoefficients().declareScalar( @@ -299,7 +320,7 @@ MFEMProblem::addAuxKernel(const std::string & kernel_name, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(kernel_name, name, parameters); + addObject(kernel_name, name, parameters); } void @@ -307,37 +328,30 @@ MFEMProblem::addKernel(const std::string & kernel_name, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(kernel_name, name, parameters); - const UserObject * kernel_uo = &(getUserObjectBase(name)); + auto kernel = addObject(kernel_name, name, parameters).front(); + const auto & kernel_object = *kernel; - if (dynamic_cast(kernel_uo)) + if (dynamic_cast(&kernel_object)) { - auto object_ptr = getUserObject(name).getSharedPtr(); - auto kernel = std::dynamic_pointer_cast(object_ptr); + auto complex_kernel = std::dynamic_pointer_cast(kernel); auto eqsys = - std::dynamic_pointer_cast(getProblemData().eqn_system); + std::dynamic_pointer_cast(getProblemData().eqn_system); if (eqsys) - eqsys->AddKernel(std::move(kernel)); + eqsys->AddComplexKernel(std::move(complex_kernel)); else - mooseError("Cannot add kernel with name '" + name + + mooseError("Cannot add complex kernel with name '" + name + "' because there is no corresponding equation system."); } - else if (dynamic_cast(kernel_uo)) + else { - auto object_ptr = getUserObject(name).getSharedPtr(); - auto kernel = std::dynamic_pointer_cast(object_ptr); auto eqsys = - std::dynamic_pointer_cast(getProblemData().eqn_system); + std::dynamic_pointer_cast(getProblemData().eqn_system); if (eqsys) - eqsys->AddComplexKernel(std::move(kernel)); + eqsys->AddKernel(std::move(kernel)); else - mooseError("Cannot add complex kernel with name '" + name + + mooseError("Cannot add kernel with name '" + name + "' because there is no corresponding equation system."); } - else - { - mooseError("Unsupported kernel of type '", kernel_name, "' and name '", name, "' detected."); - } } void @@ -346,12 +360,10 @@ MFEMProblem::addRealComponentToKernel(const std::string & kernel_name, InputParameters & parameters) { auto parent_ptr = std::dynamic_pointer_cast( - getUserObject(name).getSharedPtr()); + getMFEMObject("Kernel", name).getSharedPtr()); parameters.set("variable") = parent_ptr->getParam("variable"); - FEProblemBase::addUserObject(kernel_name, name + "_real", parameters); - auto kernel_ptr = std::dynamic_pointer_cast( - getUserObject(name + "_real").getSharedPtr()); - parent_ptr->setRealKernel(std::dynamic_pointer_cast(kernel_ptr)); + auto kernel_ptr = addObject(kernel_name, name + "_real", parameters).front(); + parent_ptr->setRealKernel(kernel_ptr); } void @@ -360,12 +372,10 @@ MFEMProblem::addImagComponentToKernel(const std::string & kernel_name, InputParameters & parameters) { auto parent_ptr = std::dynamic_pointer_cast( - getUserObject(name).getSharedPtr()); + getMFEMObject("Kernel", name).getSharedPtr()); parameters.set("variable") = parent_ptr->getParam("variable"); - FEProblemBase::addUserObject(kernel_name, name + "_imag", parameters); - auto kernel_ptr = std::dynamic_pointer_cast( - getUserObject(name + "_imag").getSharedPtr()); - parent_ptr->setImagKernel(std::dynamic_pointer_cast(kernel_ptr)); + auto kernel_ptr = addObject(kernel_name, name + "_imag", parameters).front(); + parent_ptr->setImagKernel(kernel_ptr); } void @@ -374,14 +384,13 @@ MFEMProblem::addRealComponentToBC(const std::string & kernel_name, InputParameters & parameters) { auto parent_ptr = std::dynamic_pointer_cast( - getUserObject(name).getSharedPtr()); + getMFEMObject("BoundaryCondition", name).getSharedPtr()); parameters.set("variable") = parent_ptr->getParam("variable"); parameters.set>("boundary") = parent_ptr->getParam>("boundary"); - FEProblemBase::addUserObject(kernel_name, name + "_real", parameters); auto bc_ptr = std::dynamic_pointer_cast( - getUserObject(name + "_real").getSharedPtr()); - parent_ptr->setRealBC(std::dynamic_pointer_cast(bc_ptr)); + addObject(kernel_name, name + "_real", parameters).front()); + parent_ptr->setRealBC(bc_ptr); } void @@ -390,14 +399,13 @@ MFEMProblem::addImagComponentToBC(const std::string & kernel_name, InputParameters & parameters) { auto parent_ptr = std::dynamic_pointer_cast( - getUserObject(name).getSharedPtr()); + getMFEMObject("BoundaryCondition", name).getSharedPtr()); parameters.set("variable") = parent_ptr->getParam("variable"); parameters.set>("boundary") = parent_ptr->getParam>("boundary"); - FEProblemBase::addUserObject(kernel_name, name + "_imag", parameters); auto bc_ptr = std::dynamic_pointer_cast( - getUserObject(name + "_imag").getSharedPtr()); - parent_ptr->setImagBC(std::dynamic_pointer_cast(bc_ptr)); + addObject(kernel_name, name + "_imag", parameters).front()); + parent_ptr->setImagBC(bc_ptr); } libMesh::Point @@ -518,10 +526,30 @@ MFEMProblem::addPostprocessor(const std::string & type, const std::string & name, InputParameters & parameters) { - ExternalProblem::addPostprocessor(type, name, parameters); - const PostprocessorValue & val = getPostprocessorValueByName(name); - getCoefficients().declareScalar( - name, [&val](const mfem::Vector &) -> mfem::real_t { return val; }); + if (parameters.getSystemAttributeName() == "MFEMExecutedObject") + { + checkUserObjectNameCollision(name, "Postprocessor"); + addObject(type, name, parameters); + const PostprocessorValue & val = getPostprocessorValueByName(name); + getCoefficients().declareScalar( + name, [&val](const mfem::Vector &) -> mfem::real_t { return val; }); + } + else + ExternalProblem::addPostprocessor(type, name, parameters); +} + +void +MFEMProblem::addVectorPostprocessor(const std::string & type, + const std::string & name, + InputParameters & parameters) +{ + if (parameters.getSystemAttributeName() == "MFEMExecutedObject") + { + checkUserObjectNameCollision(name, "VectorPostprocessor"); + addObject(type, name, parameters); + } + else + FEProblemBase::addVectorPostprocessor(type, name, parameters); } InputParameters @@ -571,12 +599,12 @@ MFEMProblem::addMFEMFESpaceFromMOOSEVariable(InputParameters & parameters) fespace_params.set("fec_name") = fespace_name; fespace_params.set("vdim") = mfem_vdim; - if (!hasUserObject(fespace_name)) // Create the fespace (implicit). + if (!hasMFEMObject("MFEMFESpace", fespace_name)) // Create the fespace (implicit). { addFESpace("MFEMGenericFESpace", fespace_name, fespace_params); } - mfem_variable_params.set("fespace") = fespace_name; + mfem_variable_params.set("fespace") = fespace_name; return mfem_variable_params; } @@ -657,10 +685,8 @@ MFEMProblem::addSubMesh(const std::string & var_type, const std::string & var_name, InputParameters & parameters) { - // Add MFEM SubMesh. - FEProblemBase::addUserObject(var_type, var_name, parameters); + auto & mfem_submesh = *addObject(var_type, var_name, parameters).front(); // Register submesh. - MFEMSubMesh & mfem_submesh = getUserObject(var_name); getProblemData().submeshes.Register(var_name, mfem_submesh.getSubMesh()); } @@ -670,7 +696,7 @@ MFEMProblem::addTransfer(const std::string & transfer_name, InputParameters & parameters) { if (parameters.getBase() == "MFEMSubMeshTransfer") - FEProblemBase::addUserObject(transfer_name, name, parameters); + addObject(transfer_name, name, parameters); else FEProblemBase::addTransfer(transfer_name, name, parameters); } @@ -680,8 +706,52 @@ MFEMProblem::addInitialCondition(const std::string & ic_name, const std::string & name, InputParameters & parameters) { - FEProblemBase::addUserObject(ic_name, name, parameters); - getUserObject(name); // error check + addObject(ic_name, name, parameters); +} + +void +MFEMProblem::executeMFEMObjects(const ExecFlagType & exec_type) +{ + std::vector objects; + theWarehouse() + .query() + .condition("MFEMExecutedObject") + .condition(exec_type) + .condition(0) + .queryInto(objects); + + std::map suppliers; + for (auto * const object : objects) + for (const auto & item : object->getSuppliedItems()) + { + const auto [it, inserted] = suppliers.emplace(item, object); + if (!inserted && it->second != object) + mooseError("MFEM executed-object dependency ambiguity on ", + exec_type, + ": both '", + it->second->name(), + "' and '", + object->name(), + "' supply '", + item, + "'."); + } + + for (auto * const object : objects) + { + object->initialize(); + object->execute(); + object->finalize(); + + if (auto * const pp = dynamic_cast(object)) + { + _reporter_data.finalize(pp->PPName()); + setPostprocessorValueByName(pp->PPName(), pp->getValue()); + } + + if (auto * const vpp = dynamic_cast(object)) + _reporter_data.finalize(vpp->PPName()); + } } std::string @@ -691,4 +761,17 @@ MFEMProblem::solverTypeString(const unsigned int libmesh_dbg_var(solver_sys_num) return MooseUtils::prettyCppType(getProblemData().jacobian_solver.get()); } +bool +MFEMProblem::hasMFEMObject(const std::string & system, const std::string & name) const +{ + std::vector objs; + theWarehouse() + .query() + .condition(system) + .condition(0) + .condition(name) + .queryInto(objs); + return !objs.empty(); +} + #endif diff --git a/framework/src/mfem/problem/SolutionStateData.C b/framework/src/mfem/problem/SolutionStateData.C new file mode 100644 index 000000000000..bdf5187d666d --- /dev/null +++ b/framework/src/mfem/problem/SolutionStateData.C @@ -0,0 +1,101 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#include "DataIO.h" +#include "MFEMProblemData.h" + +namespace +{ +void +storeGridFunction(std::ostream & stream, mfem::ParGridFunction & gridfunction, void * context) +{ + const auto size = gridfunction.Size(); + dataStore(stream, size, context); + + const auto * values = gridfunction.HostRead(); + for (int i = 0; i < size; ++i) + { + auto value = values[i]; + dataStore(stream, value, context); + } +} + +void +loadGridFunction(std::istream & stream, mfem::ParGridFunction & gridfunction, void * context) +{ + int size = 0; + dataLoad(stream, size, context); + mooseAssert(size == gridfunction.Size(), + "MFEM restartable GridFunction size mismatch during restore."); + + auto * values = gridfunction.HostWrite(); + for (int i = 0; i < size; ++i) + dataLoad(stream, values[i], context); +} +} + +template <> +void +dataStore(std::ostream & stream, Moose::MFEM::SolutionState & /*state*/, void * context) +{ + auto * const data = static_cast(context); + mooseAssert(data, "Missing MFEMProblemData context for solution restart storage."); + + auto num_gridfunctions = data->gridfunctions.size(); + dataStore(stream, num_gridfunctions, context); + for (const auto & [name, gridfunction] : data->gridfunctions) + { + auto stored_name = name; + dataStore(stream, stored_name, context); + storeGridFunction(stream, *gridfunction, context); + } + + auto num_complex_gridfunctions = data->cmplx_gridfunctions.size(); + dataStore(stream, num_complex_gridfunctions, context); + for (const auto & [name, gridfunction] : data->cmplx_gridfunctions) + { + auto stored_name = name; + dataStore(stream, stored_name, context); + storeGridFunction(stream, gridfunction->real(), context); + storeGridFunction(stream, gridfunction->imag(), context); + } +} + +template <> +void +dataLoad(std::istream & stream, Moose::MFEM::SolutionState & /*state*/, void * context) +{ + auto * const data = static_cast(context); + mooseAssert(data, "Missing MFEMProblemData context for solution restart load."); + + int num_gridfunctions = 0; + dataLoad(stream, num_gridfunctions, context); + for (int i = 0; i < num_gridfunctions; ++i) + { + std::string name; + dataLoad(stream, name, context); + auto & gf = data->gridfunctions.GetRef(name); + loadGridFunction(stream, gf, context); + } + + int num_complex_gridfunctions = 0; + dataLoad(stream, num_complex_gridfunctions, context); + for (int i = 0; i < num_complex_gridfunctions; ++i) + { + std::string name; + dataLoad(stream, name, context); + auto & gf = data->cmplx_gridfunctions.GetRef(name); + loadGridFunction(stream, gf.real(), context); + loadGridFunction(stream, gf.imag(), context); + } +} + +#endif diff --git a/framework/src/mfem/solvers/MFEMCGSolver.C b/framework/src/mfem/solvers/MFEMCGSolver.C index 4f2b72b52a4e..783d0d396e7c 100644 --- a/framework/src/mfem/solvers/MFEMCGSolver.C +++ b/framework/src/mfem/solvers/MFEMCGSolver.C @@ -25,7 +25,7 @@ MFEMCGSolver::validParams() params.addParam("l_abs_tol", 1e-50, "Set the absolute tolerance."); params.addParam("l_max_its", 10000, "Set the maximum number of iterations."); params.addParam("print_level", 2, "Set the solver verbosity."); - params.addParam("preconditioner", "Optional choice of preconditioner to use."); + params.addParam("preconditioner", "Optional choice of preconditioner to use."); return params; } diff --git a/framework/src/mfem/solvers/MFEMGMRESSolver.C b/framework/src/mfem/solvers/MFEMGMRESSolver.C index d064d235e169..cbb7df6cdef9 100644 --- a/framework/src/mfem/solvers/MFEMGMRESSolver.C +++ b/framework/src/mfem/solvers/MFEMGMRESSolver.C @@ -25,7 +25,7 @@ MFEMGMRESSolver::validParams() params.addParam("l_abs_tol", 1e-50, "Set the absolute tolerance."); params.addParam("l_max_its", 10000, "Set the maximum number of iterations."); params.addParam("print_level", 2, "Set the solver verbosity."); - params.addParam("preconditioner", "Optional choice of preconditioner to use."); + params.addParam("preconditioner", "Optional choice of preconditioner to use."); return params; } diff --git a/framework/src/mfem/solvers/MFEMHypreADS.C b/framework/src/mfem/solvers/MFEMHypreADS.C index b89ca0d4e862..b9fa95af5a85 100644 --- a/framework/src/mfem/solvers/MFEMHypreADS.C +++ b/framework/src/mfem/solvers/MFEMHypreADS.C @@ -10,6 +10,7 @@ #ifdef MOOSE_MFEM_ENABLED #include "MFEMHypreADS.h" +#include "MFEMProblem.h" registerMooseObject("MooseApp", MFEMHypreADS); @@ -19,14 +20,16 @@ MFEMHypreADS::validParams() InputParameters params = MFEMSolverBase::validParams(); params.addClassDescription("Hypre auxiliary-space divergence solver and preconditioner for the " "iterative solution of MFEM equation systems."); - params.addParam("fespace", "H(div) FESpace to use in HypreADS setup."); + params.addParam("fespace", "H(div) FESpace to use in HypreADS setup."); params.addParam("print_level", 2, "Set the solver verbosity."); return params; } MFEMHypreADS::MFEMHypreADS(const InputParameters & parameters) - : MFEMSolverBase(parameters), _mfem_fespace(getUserObject("fespace")) + : MFEMSolverBase(parameters), + _mfem_fespace(getMFEMProblem().getMFEMObject("MFEMFESpace", + getParam("fespace"))) { constructSolver(); } diff --git a/framework/src/mfem/solvers/MFEMHypreAMS.C b/framework/src/mfem/solvers/MFEMHypreAMS.C index 0c0d3af6fc5e..1fafb8269e25 100644 --- a/framework/src/mfem/solvers/MFEMHypreAMS.C +++ b/framework/src/mfem/solvers/MFEMHypreAMS.C @@ -10,6 +10,7 @@ #ifdef MOOSE_MFEM_ENABLED #include "MFEMHypreAMS.h" +#include "MFEMProblem.h" registerMooseObject("MooseApp", MFEMHypreAMS); @@ -19,7 +20,7 @@ MFEMHypreAMS::validParams() InputParameters params = MFEMSolverBase::validParams(); params.addClassDescription("Hypre auxiliary-space Maxwell solver and preconditioner for the " "iterative solution of MFEM equation systems."); - params.addParam("fespace", "H(curl) FESpace to use in HypreAMS setup."); + params.addParam("fespace", "H(curl) FESpace to use in HypreAMS setup."); params.addParam("singular", false, "Declare that the system is singular; use when solving curl-curl problem " @@ -30,7 +31,9 @@ MFEMHypreAMS::validParams() } MFEMHypreAMS::MFEMHypreAMS(const InputParameters & parameters) - : MFEMSolverBase(parameters), _mfem_fespace(getUserObject("fespace")) + : MFEMSolverBase(parameters), + _mfem_fespace(getMFEMProblem().getMFEMObject("MFEMFESpace", + getParam("fespace"))) { constructSolver(); } diff --git a/framework/src/mfem/solvers/MFEMHypreBoomerAMG.C b/framework/src/mfem/solvers/MFEMHypreBoomerAMG.C index be6e7aa71451..7259040af254 100644 --- a/framework/src/mfem/solvers/MFEMHypreBoomerAMG.C +++ b/framework/src/mfem/solvers/MFEMHypreBoomerAMG.C @@ -11,6 +11,7 @@ #include "MFEMHypreBoomerAMG.h" #include "MFEMFESpace.h" +#include "MFEMProblem.h" registerMooseObject("MooseApp", MFEMHypreBoomerAMG); @@ -23,7 +24,7 @@ MFEMHypreBoomerAMG::validParams() params.addParam("l_tol", 1e-5, "Set the relative tolerance."); params.addParam("l_max_its", 10000, "Set the maximum number of iterations."); params.addParam("print_level", 2, "Set the solver verbosity."); - params.addParam( + params.addParam( "fespace", "H1 FESpace to use in HypreBoomerAMG setup for elasticity problems."); params.addParam( "strength_threshold", 0.25, "HypreBoomerAMG strong threshold. Defaults to 0.25."); @@ -34,8 +35,12 @@ MFEMHypreBoomerAMG::validParams() MFEMHypreBoomerAMG::MFEMHypreBoomerAMG(const InputParameters & parameters) : MFEMSolverBase(parameters), - _mfem_fespace(isParamSetByUser("fespace") ? getUserObject("fespace").getFESpace() - : nullptr) + _mfem_fespace( + isParamSetByUser("fespace") + ? getMFEMProblem() + .getMFEMObject("MFEMFESpace", getParam("fespace")) + .getFESpace() + : nullptr) { constructSolver(); } diff --git a/framework/src/mfem/solvers/MFEMHypreFGMRES.C b/framework/src/mfem/solvers/MFEMHypreFGMRES.C index c5d1ce0cfaa0..bb308b0c5374 100644 --- a/framework/src/mfem/solvers/MFEMHypreFGMRES.C +++ b/framework/src/mfem/solvers/MFEMHypreFGMRES.C @@ -24,7 +24,7 @@ MFEMHypreFGMRES::validParams() params.addParam("l_max_its", 10000, "Set the maximum number of iterations."); params.addParam("kdim", 10, "Set the k-dimension."); params.addParam("print_level", 2, "Set the solver verbosity."); - params.addParam("preconditioner", "Optional choice of preconditioner to use."); + params.addParam("preconditioner", "Optional choice of preconditioner to use."); return params; } diff --git a/framework/src/mfem/solvers/MFEMHypreGMRES.C b/framework/src/mfem/solvers/MFEMHypreGMRES.C index fb87bc02ba5c..3358e196bd6b 100644 --- a/framework/src/mfem/solvers/MFEMHypreGMRES.C +++ b/framework/src/mfem/solvers/MFEMHypreGMRES.C @@ -26,7 +26,7 @@ MFEMHypreGMRES::validParams() params.addParam("l_max_its", 10000, "Set the maximum number of iterations."); params.addParam("kdim", 10, "Set the k-dimension."); params.addParam("print_level", 2, "Set the solver verbosity."); - params.addParam("preconditioner", "Optional choice of preconditioner to use."); + params.addParam("preconditioner", "Optional choice of preconditioner to use."); return params; } diff --git a/framework/src/mfem/solvers/MFEMHyprePCG.C b/framework/src/mfem/solvers/MFEMHyprePCG.C index eeaa5f73041c..d4deef96f949 100644 --- a/framework/src/mfem/solvers/MFEMHyprePCG.C +++ b/framework/src/mfem/solvers/MFEMHyprePCG.C @@ -25,7 +25,7 @@ MFEMHyprePCG::validParams() params.addParam("l_abs_tol", 1e-50, "Set the absolute tolerance."); params.addParam("l_max_its", 10000, "Set the maximum number of iterations."); params.addParam("print_level", 2, "Set the solver verbosity."); - params.addParam("preconditioner", "Optional choice of preconditioner to use."); + params.addParam("preconditioner", "Optional choice of preconditioner to use."); return params; } diff --git a/framework/src/mfem/solvers/MFEMSolverBase.C b/framework/src/mfem/solvers/MFEMSolverBase.C index 88a44aed9c89..dfb1ed6c7009 100644 --- a/framework/src/mfem/solvers/MFEMSolverBase.C +++ b/framework/src/mfem/solvers/MFEMSolverBase.C @@ -10,20 +10,22 @@ #ifdef MOOSE_MFEM_ENABLED #include "MFEMSolverBase.h" +#include "MFEMProblem.h" InputParameters MFEMSolverBase::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMObject::validParams(); params.addClassDescription("Base class for defining mfem::Solver derived classes for Moose."); params.registerBase("MFEMSolverBase"); + params.registerSystemAttributeName("MFEMSolverBase"); params.addParam("low_order_refined", false, "Set usage of Low-Order Refined solver."); return params; } MFEMSolverBase::MFEMSolverBase(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), + : MFEMObject(parameters), _lor{getParam("low_order_refined")}, _solver{nullptr}, _preconditioner{nullptr} @@ -38,7 +40,8 @@ MFEMSolverBase::setPreconditioner(T & solver) { if (!_preconditioner) _preconditioner = - &const_cast(getUserObject("preconditioner")); + &const_cast(getMFEMProblem().getMFEMObject( + "MFEMSolverBase", getParam("preconditioner"))); auto & mfem_pre = _preconditioner->getSolver(); if constexpr (std::is_base_of_v) diff --git a/framework/src/mfem/submeshes/MFEMSubMesh.C b/framework/src/mfem/submeshes/MFEMSubMesh.C index f1e0b7476222..f050f2c9bb36 100644 --- a/framework/src/mfem/submeshes/MFEMSubMesh.C +++ b/framework/src/mfem/submeshes/MFEMSubMesh.C @@ -15,11 +15,12 @@ InputParameters MFEMSubMesh::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMObject::validParams(); params.registerBase("MFEMSubMesh"); + params.registerSystemAttributeName("MFEMSubMesh"); return params; } -MFEMSubMesh::MFEMSubMesh(const InputParameters & parameters) : MFEMGeneralUserObject(parameters) {} +MFEMSubMesh::MFEMSubMesh(const InputParameters & parameters) : MFEMObject(parameters) {} #endif diff --git a/framework/src/mfem/transfers/MFEMSubMeshTransfer.C b/framework/src/mfem/transfers/MFEMSubMeshTransfer.C index 97a9bfba478c..82fb8aa0b392 100644 --- a/framework/src/mfem/transfers/MFEMSubMeshTransfer.C +++ b/framework/src/mfem/transfers/MFEMSubMeshTransfer.C @@ -17,14 +17,16 @@ registerMooseObject("MooseApp", MFEMSubMeshTransfer); InputParameters MFEMSubMeshTransfer::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMExecutedObject::validParams(); params.registerBase("MFEMSubMeshTransfer"); params.addClassDescription("Class to transfer MFEM variable data to or from a restricted copy of " "the variable defined on " " a subspace of an MFEMMesh, represented as an MFEMSubMesh."); - params.addRequiredParam("from_variable", - "MFEM variable to transfer data from. Can be defined on " - "either the parent mesh or a submesh of it."); + MFEMExecutedObject::addRequiredDependencyParam( + params, + "from_variable", + "MFEM variable to transfer data from. Can be defined on either the parent mesh or a " + "submesh of it."); params.addRequiredParam("to_variable", "MFEM variable to transfer data into. Can be defined on " "either the parent mesh or a submesh of it."); @@ -32,7 +34,7 @@ MFEMSubMeshTransfer::validParams() } MFEMSubMeshTransfer::MFEMSubMeshTransfer(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), + : MFEMExecutedObject(parameters), _source_var_name(getParam("from_variable")), _source_var(*getMFEMProblem().getGridFunction(_source_var_name)), _result_var_name(getParam("to_variable")), @@ -40,6 +42,12 @@ MFEMSubMeshTransfer::MFEMSubMeshTransfer(const InputParameters & parameters) { } +std::set +MFEMSubMeshTransfer::producedVariableNames() const +{ + return {_result_var_name}; +} + void MFEMSubMeshTransfer::execute() { diff --git a/framework/src/mfem/userobjects/MFEMGeneralUserObject.C b/framework/src/mfem/userobjects/MFEMGeneralUserObject.C deleted file mode 100644 index abd126b4bf84..000000000000 --- a/framework/src/mfem/userobjects/MFEMGeneralUserObject.C +++ /dev/null @@ -1,66 +0,0 @@ -//* This file is part of the MOOSE framework -//* https://mooseframework.inl.gov -//* -//* All rights reserved, see COPYRIGHT for full restrictions -//* https://github.com/idaholab/moose/blob/master/COPYRIGHT -//* -//* Licensed under LGPL 2.1, please see LICENSE for details -//* https://www.gnu.org/licenses/lgpl-2.1.html - -#ifdef MOOSE_MFEM_ENABLED - -#include "MFEMGeneralUserObject.h" -#include "MFEMProblem.h" - -registerMooseObject("MooseApp", MFEMGeneralUserObject); - -InputParameters -MFEMGeneralUserObject::validParams() -{ - InputParameters params = GeneralUserObject::validParams(); - params.addClassDescription("Base class for custom GeneralUserObjects to add to MFEM problems."); - return params; -} - -MFEMGeneralUserObject::MFEMGeneralUserObject(const InputParameters & parameters) - : GeneralUserObject(parameters), _mfem_problem(static_cast(_fe_problem)) -{ -} - -mfem::Coefficient & -MFEMGeneralUserObject::getScalarCoefficientByName(const MFEMScalarCoefficientName & name) -{ - return getMFEMProblem().getCoefficients().getScalarCoefficient(name); -} - -mfem::VectorCoefficient & -MFEMGeneralUserObject::getVectorCoefficientByName(const MFEMVectorCoefficientName & name) -{ - return getMFEMProblem().getCoefficients().getVectorCoefficient(name); -} - -mfem::MatrixCoefficient & -MFEMGeneralUserObject::getMatrixCoefficientByName(const MFEMMatrixCoefficientName & name) -{ - return getMFEMProblem().getCoefficients().getMatrixCoefficient(name); -} - -mfem::Coefficient & -MFEMGeneralUserObject::getScalarCoefficient(const std::string & name) -{ - return getScalarCoefficientByName(getParam(name)); -} - -mfem::VectorCoefficient & -MFEMGeneralUserObject::getVectorCoefficient(const std::string & name) -{ - return getVectorCoefficientByName(getParam(name)); -} - -mfem::MatrixCoefficient & -MFEMGeneralUserObject::getMatrixCoefficient(const std::string & name) -{ - return getMatrixCoefficientByName(getParam(name)); -} - -#endif diff --git a/framework/src/mfem/variables/MFEMComplexVariable.C b/framework/src/mfem/variables/MFEMComplexVariable.C index 825c1b5fae48..4fbbc0a0fbc8 100644 --- a/framework/src/mfem/variables/MFEMComplexVariable.C +++ b/framework/src/mfem/variables/MFEMComplexVariable.C @@ -2,26 +2,30 @@ #include "MFEMComplexVariable.h" #include "MooseVariableBase.h" +#include "MFEMProblem.h" +#include "MFEMFESpace.h" registerMooseObject("MooseApp", MFEMComplexVariable); InputParameters MFEMComplexVariable::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); - params.addRequiredParam("fespace", - "The finite element space this variable is defined on."); + InputParameters params = MFEMObject::validParams(); + params.addRequiredParam("fespace", + "The finite element space this variable is defined on."); params += MooseVariableBase::validParams(); params.addClassDescription( "Class for adding complex MFEM variables to the problem (`mfem::ParComplexGridFunction`s)."); params.registerBase("MooseVariableBase"); + params.registerSystemAttributeName("MooseVariableBase"); return params; } MFEMComplexVariable::MFEMComplexVariable(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), - _fespace(getUserObject("fespace")), + : MFEMObject(parameters), + _fespace(getMFEMProblem().getMFEMObject("MFEMFESpace", + getParam("fespace"))), _cmplx_gridfunction(buildComplexGridFunction()) { *_cmplx_gridfunction = 0.0; diff --git a/framework/src/mfem/variables/MFEMVariable.C b/framework/src/mfem/variables/MFEMVariable.C index ab77347ae470..4ae3a5c49308 100644 --- a/framework/src/mfem/variables/MFEMVariable.C +++ b/framework/src/mfem/variables/MFEMVariable.C @@ -18,15 +18,16 @@ registerMooseObject("MooseApp", MFEMVariable); InputParameters MFEMVariable::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMObject::validParams(); // Create user-facing 'boundary' input for restricting inheriting object to boundaries. - params.addRequiredParam("fespace", - "The finite element space this variable is defined on."); + params.addRequiredParam("fespace", + "The finite element space this variable is defined on."); // Require moose variable parameters (not used!) params += MooseVariableBase::validParams(); params.addClassDescription( "Class for adding MFEM variables to the problem (`mfem::ParGridFunction`s)."); params.registerBase("MooseVariableBase"); + params.registerSystemAttributeName("MooseVariableBase"); params.addParam( "time_derivative", "Optional name to assign to the time derivative of the variable in transient problems."); @@ -34,8 +35,9 @@ MFEMVariable::validParams() } MFEMVariable::MFEMVariable(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), - _fespace(getUserObject("fespace")), + : MFEMObject(parameters), + _fespace(getMFEMProblem().getMFEMObject("MFEMFESpace", + getParam("fespace"))), _gridfunction(buildGridFunction()), _time_derivative_name( isParamValid("time_derivative") diff --git a/framework/src/mfem/vectorpostprocessors/MFEMComplexPointValueSampler.C b/framework/src/mfem/vectorpostprocessors/MFEMComplexPointValueSampler.C new file mode 100644 index 000000000000..9e880b22bba0 --- /dev/null +++ b/framework/src/mfem/vectorpostprocessors/MFEMComplexPointValueSampler.C @@ -0,0 +1,32 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#include "MFEMComplexPointValueSampler.h" + +registerMooseObject("MooseApp", MFEMComplexPointValueSampler); + +InputParameters +MFEMComplexPointValueSampler::validParams() +{ + InputParameters params = MFEMComplexValueSamplerBase::validParams(); + params.addClassDescription("Sample a complex MFEM variable at specific points, outputting " + "real and imaginary parts as separate columns."); + params.addRequiredParam>("points", + "The points where you want to evaluate the variable"); + return params; +} + +MFEMComplexPointValueSampler::MFEMComplexPointValueSampler(const InputParameters & parameters) + : MFEMComplexValueSamplerBase(parameters, parameters.get>("points")) +{ +} + +#endif // MOOSE_MFEM_ENABLED diff --git a/framework/src/mfem/vectorpostprocessors/MFEMComplexValueSamplerBase.C b/framework/src/mfem/vectorpostprocessors/MFEMComplexValueSamplerBase.C new file mode 100644 index 000000000000..4bada6a7f7a6 --- /dev/null +++ b/framework/src/mfem/vectorpostprocessors/MFEMComplexValueSamplerBase.C @@ -0,0 +1,66 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#include "MFEMComplexValueSamplerBase.h" +#include "MFEMProblem.h" + +InputParameters +MFEMComplexValueSamplerBase::validParams() +{ + return MFEMSamplerBase::validParams(); +} + +MFEMComplexValueSamplerBase::MFEMComplexValueSamplerBase(const InputParameters & parameters, + const std::vector & points) + : MFEMSamplerBase(parameters, points), + _var(*getMFEMProblem().getComplexGridFunction(_var_name)), + _real_interp_vals(points.size()), + _imag_interp_vals(points.size()) +{ + const auto val_dim = _var.real().VectorDim(); + for (int i = 0; i < val_dim; i++) + { + auto & real_declared = this->declareVector(_var_name + "_real_" + std::to_string(i)); + real_declared.resize(points.size()); + _declared_real_vals.push_back(real_declared); + + auto & imag_declared = this->declareVector(_var_name + "_imag_" + std::to_string(i)); + imag_declared.resize(points.size()); + _declared_imag_vals.push_back(imag_declared); + } +} + +void +MFEMComplexValueSamplerBase::execute() +{ + _finder.Interpolate(_var.real(), _real_interp_vals); + _finder.Interpolate(_var.imag(), _imag_interp_vals); +} + +void +MFEMComplexValueSamplerBase::finalizeValues() +{ + _real_interp_vals.HostReadWrite(); + _imag_interp_vals.HostReadWrite(); + + const auto val_dims = _var.real().VectorDim(); + const auto num_points = _declared_points[0].get().size(); + const auto val_fespace_ordering = _var.real().FESpace()->GetOrdering(); + for (int i_dim = 0; i_dim < val_dims; i_dim++) + for (size_t i_point = 0; i_point < num_points; i_point++) + { + const auto idx = mfemIndex(i_dim, i_point, val_dims, num_points, val_fespace_ordering); + _declared_real_vals[i_dim].get()[i_point] = _real_interp_vals[idx]; + _declared_imag_vals[i_dim].get()[i_point] = _imag_interp_vals[idx]; + } +} + +#endif // MOOSE_MFEM_ENABLED diff --git a/framework/src/mfem/vectorpostprocessors/MFEMSamplerBase.C b/framework/src/mfem/vectorpostprocessors/MFEMSamplerBase.C new file mode 100644 index 000000000000..f5e55f2c6d24 --- /dev/null +++ b/framework/src/mfem/vectorpostprocessors/MFEMSamplerBase.C @@ -0,0 +1,104 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#include "MFEMSamplerBase.h" +#include "MFEMProblem.h" + +#include "mfem/fem/fespace.hpp" + +namespace +{ +mfem::Vector +pointsToMFEMVector(const std::vector & points, + const unsigned int num_dims, + const mfem::Ordering::Type ordering) +{ + const unsigned int num_points = points.size(); + mfem::Vector mfem_points(num_points * num_dims); + for (unsigned int i_point = 0; i_point < num_points; i_point++) + for (unsigned int i_dim = 0; i_dim < num_dims; i_dim++) + { + const size_t idx = MFEMSamplerBase::mfemIndex(i_dim, i_point, num_dims, num_points, ordering); + mfem_points(idx) = points[i_point](i_dim); + } + return mfem_points; +} +} // namespace + +InputParameters +MFEMSamplerBase::validParams() +{ + InputParameters params = MFEMVectorPostprocessor::validParams(); + MFEMExecutedObject::addRequiredDependencyParam( + params, "variable", "The names of the variables that this VectorPostprocessor operates on"); + MooseEnum ordering("NODES VDIM", "VDIM", false); + params.addParam( + "point_ordering", ordering, "Ordering style to use for point vector DoFs."); + return params; +} + +MFEMSamplerBase::MFEMSamplerBase(const InputParameters & parameters, + const std::vector & points) + : MFEMVectorPostprocessor(parameters), + _var_name(getParam("variable")), + _mesh(const_cast(getMFEMProblem().getMFEMVariableMesh(_var_name))), + _finder(this->comm().get()), + _points_ordering(getParam("point_ordering") == "NODES" ? mfem::Ordering::byNODES + : mfem::Ordering::byVDIM), + _points(pointsToMFEMVector(points, _mesh.SpaceDimension(), _points_ordering)) +{ + if (getMFEMProblem().mesh().shouldDisplace()) + mooseError("MFEMSamplerBase does not yet support problems with displacement."); + + _mesh.EnsureNodes(); + _finder.Setup(_mesh); + _finder.FindPoints(_points, _points_ordering); + + mfem::Array point_codes = _finder.GetCode(); + for (size_t i = 0; i < points.size(); i++) + if (point_codes[i] > 1) + mooseError("MFEMSamplerBase could not find point at ", points[i], "."); + + const auto mesh_dim = _mesh.SpaceDimension(); + for (int i = 0; i < mesh_dim; i++) + { + auto & declared = this->declareVector("x_" + std::to_string(i)); + declared.resize(points.size()); + _declared_points.push_back(declared); + } +} + +void +MFEMSamplerBase::finalize() +{ + _points.HostReadWrite(); + + const auto mesh_dim = _mesh.SpaceDimension(); + const auto num_points = _declared_points[0].get().size(); + for (int i_dim = 0; i_dim < mesh_dim; i_dim++) + for (size_t i_point = 0; i_point < num_points; i_point++) + _declared_points[i_dim].get()[i_point] = + _points(mfemIndex(i_dim, i_point, mesh_dim, num_points, _points_ordering)); + + finalizeValues(); +} + +size_t +MFEMSamplerBase::mfemIndex( + size_t i_dim, size_t i_point, size_t num_dims, size_t num_points, mfem::Ordering::Type ordering) +{ + if (ordering == mfem::Ordering::byNODES) + return i_dim * num_points + i_point; + else + return i_point * num_dims + i_dim; +} + +#endif // MOOSE_MFEM_ENABLED diff --git a/framework/src/mfem/vectorpostprocessors/MFEMValueSamplerBase.C b/framework/src/mfem/vectorpostprocessors/MFEMValueSamplerBase.C index 3c0d10e0b8b1..510f22d50048 100644 --- a/framework/src/mfem/vectorpostprocessors/MFEMValueSamplerBase.C +++ b/framework/src/mfem/vectorpostprocessors/MFEMValueSamplerBase.C @@ -12,129 +12,24 @@ #include "MFEMValueSamplerBase.h" #include "MFEMProblem.h" -#include "mfem/fem/fespace.hpp" - -namespace -{ -size_t -MFEMIndex(const size_t i_dim, - const size_t i_point, - const size_t num_dims, - const size_t num_points, - const mfem::Ordering::Type ordering) -{ - if (ordering == mfem::Ordering::byNODES) - { - return i_dim * num_points + i_point; - } - else // ordering == mfem::Ordering::byVDIM - { - return i_point * num_dims + i_dim; - } -} - -mfem::Vector -pointsToMFEMVector(const std::vector & points, - const unsigned int num_dims, - const mfem::Ordering::Type ordering) -{ - const unsigned int num_points = points.size(); - mfem::Vector mfem_points(num_points * num_dims); - for (unsigned int i_point = 0; i_point < num_points; i_point++) - { - for (unsigned int i_dim = 0; i_dim < num_dims; i_dim++) - { - const size_t idx = MFEMIndex(i_dim, i_point, num_dims, num_points, ordering); - - mfem_points(idx) = points[i_point](i_dim); - } - } - - return mfem_points; -} - -void -MFEMVectorToPostprocessorPoints( - const mfem::Vector & mfem_points, - std::vector> & points, - const unsigned int num_dims, - const mfem::Ordering::Type ordering) -{ - const unsigned int num_points = mfem_points.Size() / num_dims; - for (unsigned int i_point = 0; i_point < num_points; i_point++) - { - for (unsigned int i_dim = 0; i_dim < num_dims; i_dim++) - { - const size_t idx = MFEMIndex(i_dim, i_point, num_dims, num_points, ordering); - - points[i_dim].get()[i_point] = mfem_points(idx); - } - } -} -} - InputParameters MFEMValueSamplerBase::validParams() { - InputParameters params = MFEMVectorPostprocessor::validParams(); - - params.addRequiredParam( - "variable", "The names of the variables that this VectorPostprocessor operates on"); - MooseEnum ordering("NODES VDIM", "VDIM", false); - params.addParam( - "point_ordering", ordering, "Ordering style to use for point vector DoFs."); - - return params; + return MFEMSamplerBase::validParams(); } MFEMValueSamplerBase::MFEMValueSamplerBase(const InputParameters & parameters, const std::vector & points) - : MFEMVectorPostprocessor(parameters), - _var_name(getParam("variable")), + : MFEMSamplerBase(parameters, points), _var(*getMFEMProblem().getGridFunction(_var_name)), - _mesh(const_cast(getMFEMProblem().getMFEMVariableMesh(_var_name))), - _finder(this->comm().get()), - _points_ordering(getParam("point_ordering") == "NODES" ? mfem::Ordering::byNODES - : mfem::Ordering::byVDIM), - _points(pointsToMFEMVector(points, _mesh.SpaceDimension(), _points_ordering)), _interp_vals(points.size()) { - if (getMFEMProblem().mesh().shouldDisplace()) - mooseError("MFEMValueSamplerBase does not yet support problems with displacement."); - - // set up points vector - _mesh.EnsureNodes(); - _finder.Setup(_mesh); - _finder.FindPoints(_points, _points_ordering); - - // check all points were found - mfem::Array point_codes = _finder.GetCode(); - for (size_t i = 0; i < points.size(); i++) - { - if (point_codes[i] > 1) - { - mooseError("MFEMValueSamplerBase could not find point at ", points[i], "."); - } - } - - // declare points vectors for outputting - const auto mesh_dim = _mesh.SpaceDimension(); - for (int i = 0; i < mesh_dim; i++) - { - std::reference_wrapper declared_dim = - this->declareVector("x_" + std::to_string(i)); - declared_dim.get().resize(points.size()); - _declared_points.push_back(declared_dim); - } - - // declare value vectors for outputting const auto val_dim = _var.VectorDim(); for (int i = 0; i < val_dim; i++) { - std::reference_wrapper declared_dim = - this->declareVector(_var_name + "_" + std::to_string(i)); - declared_dim.get().resize(points.size()); - _declared_vals.push_back(declared_dim); + auto & declared = this->declareVector(_var_name + "_" + std::to_string(i)); + declared.resize(points.size()); + _declared_vals.push_back(declared); } } @@ -145,24 +40,19 @@ MFEMValueSamplerBase::execute() } void -MFEMValueSamplerBase::finalize() +MFEMValueSamplerBase::finalizeValues() { _interp_vals.HostReadWrite(); - _points.HostReadWrite(); - const auto mesh_dim = _mesh.SpaceDimension(); - MFEMVectorToPostprocessorPoints(_points, _declared_points, mesh_dim, _points_ordering); const auto val_dims = _var.VectorDim(); const auto num_points = _declared_points[0].get().size(); const auto val_fespace_ordering = _var.FESpace()->GetOrdering(); for (int i_dim = 0; i_dim < val_dims; i_dim++) - { for (size_t i_point = 0; i_point < num_points; i_point++) { - const auto mfem_idx = MFEMIndex(i_dim, i_point, val_dims, num_points, val_fespace_ordering); - _declared_vals[i_dim].get()[i_point] = _interp_vals[mfem_idx]; + const auto idx = mfemIndex(i_dim, i_point, val_dims, num_points, val_fespace_ordering); + _declared_vals[i_dim].get()[i_point] = _interp_vals[idx]; } - } } #endif // MOOSE_MFEM_ENABLED diff --git a/framework/src/mfem/vectorpostprocessors/MFEMVectorPostprocessor.C b/framework/src/mfem/vectorpostprocessors/MFEMVectorPostprocessor.C index 7c1c00e295b4..2a5d88bc1f0a 100644 --- a/framework/src/mfem/vectorpostprocessors/MFEMVectorPostprocessor.C +++ b/framework/src/mfem/vectorpostprocessors/MFEMVectorPostprocessor.C @@ -14,14 +14,21 @@ InputParameters MFEMVectorPostprocessor::validParams() { - InputParameters params = MFEMGeneralUserObject::validParams(); + InputParameters params = MFEMExecutedObject::validParams(); params += VectorPostprocessor::validParams(); + params.registerSystemAttributeName("MFEMExecutedObject"); return params; } MFEMVectorPostprocessor::MFEMVectorPostprocessor(const InputParameters & parameters) - : MFEMGeneralUserObject(parameters), VectorPostprocessor(this) + : MFEMExecutedObject(parameters), VectorPostprocessor(this) { } +std::set +MFEMVectorPostprocessor::producedVectorPostprocessorNames() const +{ + return {PPName()}; +} + #endif // MOOSE_MFEM_ENABLED diff --git a/framework/src/outputs/Checkpoint.C b/framework/src/outputs/Checkpoint.C index 48bc79028024..1284950c1726 100644 --- a/framework/src/outputs/Checkpoint.C +++ b/framework/src/outputs/Checkpoint.C @@ -156,17 +156,14 @@ Checkpoint::output() // Create the output filename const auto current_file = filename(); - // Create the libMesh Checkpoint_IO object - MeshBase & mesh = _es_ptr->get_mesh(); - CheckpointIO io(mesh, false); - // Create checkpoint file structure CheckpointFileNames curr_file_struct; curr_file_struct.checkpoint = current_file + _app.checkpointSuffix(); - // Write the checkpoint file - io.write(curr_file_struct.checkpoint); + const auto mesh_paths = _problem_ptr->mesh().writeRecoveryFiles(curr_file_struct.checkpoint); + curr_file_struct.restart.insert( + curr_file_struct.restart.end(), mesh_paths.begin(), mesh_paths.end()); // Write out meta data if there is any (only on processor zero) if (processor_id() == 0) diff --git a/framework/src/parser/ParameterRegistration.C b/framework/src/parser/ParameterRegistration.C index 264de3171324..3a0c9f949f61 100644 --- a/framework/src/parser/ParameterRegistration.C +++ b/framework/src/parser/ParameterRegistration.C @@ -108,6 +108,8 @@ registerParameter(SolverSystemName); registerParameter(MFEMScalarCoefficientName); registerParameter(MFEMVectorCoefficientName); registerParameter(MFEMMatrixCoefficientName); +registerParameter(MFEMFESpaceName); +registerParameter(MFEMSolverName); #endif /*******************************************************************************/ diff --git a/framework/src/problems/FEProblemBase.C b/framework/src/problems/FEProblemBase.C index 7d5515ace316..f42e4513ce77 100644 --- a/framework/src/problems/FEProblemBase.C +++ b/framework/src/problems/FEProblemBase.C @@ -4688,6 +4688,25 @@ FEProblemBase::hasPostprocessorValueByName(const PostprocessorName & name) const return _reporter_data.hasReporterValue(PostprocessorReporterName(name)); } +const Postprocessor & +FEProblemBase::getPostprocessorObjectByName(const PostprocessorName & object_name, + const THREAD_ID tid) const +{ + std::vector objs; + theWarehouse() + .query() + .condition(Interfaces::Postprocessor) + .condition(tid) + .condition(object_name) + .queryInto(objs); + + if (objs.empty()) + mooseError("Unable to find Postprocessor with name '", object_name, "'"); + mooseAssert(objs.size() == 1, + "We shouldn't find more than one postprocessor object for a given name"); + return *(objs[0]); +} + const PostprocessorValue & FEProblemBase::getPostprocessorValueByName(const PostprocessorName & name, std::size_t t_index) const @@ -4736,7 +4755,19 @@ const VectorPostprocessor & FEProblemBase::getVectorPostprocessorObjectByName(const std::string & object_name, const THREAD_ID tid) const { - return getUserObject(object_name, tid); + std::vector objs; + theWarehouse() + .query() + .condition(Interfaces::VectorPostprocessor) + .condition(tid) + .condition(object_name) + .queryInto(objs); + + if (objs.empty()) + mooseError("Unable to find VectorPostprocessor with name '", object_name, "'"); + mooseAssert(objs.size() == 1, + "We shouldn't find more than one vector postprocessor object for a given name"); + return *(objs[0]); } void @@ -5327,7 +5358,7 @@ FEProblemBase::computeUserObjectsInternal(const ExecFlagType & type, TheWarehous joinAndFinalize(q); } - // Execute general user objects + // Execute general user objects joinAndFinalize(query.clone().condition(Interfaces::GeneralUserObject), true); } catch (...) diff --git a/framework/src/timesteppers/CompositionDT.C b/framework/src/timesteppers/CompositionDT.C index df56a7ee1698..aacf73c561b1 100644 --- a/framework/src/timesteppers/CompositionDT.C +++ b/framework/src/timesteppers/CompositionDT.C @@ -181,13 +181,10 @@ CompositionDT::getSequenceSteppersNextTime() Real next_time_to_hit = std::numeric_limits::max(); for (auto & tss : time_sequence_steppers) { - Real ts_time_to_hit = tss->getNextTimeInSequence(); + if (!tss->advanceToFutureTime(_time, _dt_min)) + continue; - if (ts_time_to_hit - _time <= _dt_min) - { - tss->increaseCurrentStep(); - ts_time_to_hit = tss->getNextTimeInSequence(); - } + const Real ts_time_to_hit = tss->getNextTimeInSequence(); if (next_time_to_hit > ts_time_to_hit) { _closest_time_sequence_stepper = tss; diff --git a/framework/src/timesteppers/TimeSequenceStepperBase.C b/framework/src/timesteppers/TimeSequenceStepperBase.C index a219d41e3334..b3346fb6415e 100644 --- a/framework/src/timesteppers/TimeSequenceStepperBase.C +++ b/framework/src/timesteppers/TimeSequenceStepperBase.C @@ -40,78 +40,117 @@ TimeSequenceStepperBase::TimeSequenceStepperBase(const InputParameters & paramet void TimeSequenceStepperBase::setupSequence(const std::vector & times) { - // In case of half transient, transient's end time needs to be reset to - // be able to imprint TimeSequenceStepperBase's end time + // The half-transient test mechanism runs the simulation to the midpoint of a doubled time + // range, saves a checkpoint, then recovers and runs the second half. Double end_time here so + // that the full doubled range is available when building the sequence; the half-point is + // imposed at the bottom of this function. if (_app.testCheckpointHalfTransient()) _executioner.endTime() = _executioner.endTime() * 2.0 - _executioner.getStartTime(); - // only set up _time_sequence if the app is _not_ recovering - if (!_app.isRecovering()) + if ((!_app.isRestarting() && !_app.isRecovering()) || _time_sequence.empty()) { - // also we need to do something different when restarting - if (!_app.isRestarting() || _time_sequence.empty()) - updateSequence(times); - else + // Fresh run: build _time_sequence directly from the input times. + updateSequence(times); + } + else if (_app.isRecovering()) + { + // Recover: the checkpoint has _time_sequence and _current_step, but both may be unreliable: + // - computeFailedDT() may have inserted intermediate retry points that are not in the + // input, shifting what _current_step points to relative to the new sequence. + // - When multiple TimeSequenceSteppers coexist under CompositionDT, each one doubles + // and then resets _executioner.endTime() in sequence. The cascade means later steppers + // build their sequences against a partially-reduced end time, filtering out time points + // that should appear in the full run. Using the saved sequence on recover would + // permanently lose those points. + // Solution: rebuild the sequence fresh from the input (using the correct, un-cascaded + // end_time), then recompute _current_step by scanning for the last sequence entry that + // is at or before the recovered simulation time. + + if (!MooseUtils::absoluteFuzzyEqual(_executioner.getStartTime(), _time_sequence[0])) + mooseError("Timesequencestepper does not allow the start time to be modified."); + + Real end_time = _executioner.endTime(); + + // Make sure time sequence is in ascending order. + for (unsigned int j = 0; j + 1 < times.size(); ++j) + if (times[j + 1] <= times[j]) + mooseError("time_sequence must be in ascending order."); + + _time_sequence.clear(); + _time_sequence.push_back(_executioner.getStartTime()); + for (const auto time : times) + if (time > _executioner.getStartTime() && time <= end_time) + _time_sequence.push_back(time); + // Push end_time as a sentinel so that computeDT() always has a valid [_current_step+1] + // to read, even when _current_step sits on the last user-specified time point. + if (!_set_end_time) + _time_sequence.push_back(end_time); + + // _current_step must index the last entry that has already been reached. Scan forward + // until we find an entry strictly after the recovered time. + // absoluteFuzzyLessThan(a, b) is true when a < b - tol, so its negation covers a >= b - tol, + // i.e. _time_sequence[j] is within tolerance of or behind _time. + _current_step = 0; + for (unsigned int j = 1; j < _time_sequence.size(); ++j) { - // in case of restart it should be allowed to modify _time_sequence if it follows the - // following rule: - // all times up to _current_step are identical - // 1. start time cannot be modified - // 2. the entries in _time_sequence and times must be equal up to entry with index - // _current_step + if (!MooseUtils::absoluteFuzzyLessThan(_time, _time_sequence[j])) + _current_step = j; + else + break; + } + } + else + { + // Restart: the simulation is being continued from a checkpoint written during a deliberate + // restart (not a crash recovery). The user may supply a modified future sequence, but the + // completed portion must be identical to the original run so that the restarted solution is + // consistent with what was computed before the restart point. + if (!MooseUtils::absoluteFuzzyEqual(_executioner.getStartTime(), _time_sequence[0])) + mooseError("Timesequencestepper does not allow the start time to be modified."); + + Real end_time = _executioner.endTime(); + + // Make sure time sequence is in ascending order. + for (unsigned int j = 0; j + 1 < times.size(); ++j) + if (times[j + 1] <= times[j]) + mooseError("time_sequence must be in ascending order."); - if (!MooseUtils::absoluteFuzzyEqual(_executioner.getStartTime(), _time_sequence[0])) - mooseError("Timesequencestepper does not allow the start time to be modified."); + // Build the full sequence that the current input implies for this run. + std::vector current_input_sequence; + current_input_sequence.push_back(_executioner.getStartTime()); + for (const auto time : times) + if (time > _executioner.getStartTime() && time <= end_time) + current_input_sequence.push_back(time); + if (!_set_end_time) + current_input_sequence.push_back(end_time); - // sync _executioner.endTime with _time_sequence - Real end_time = _executioner.endTime(); + if (current_input_sequence.size() < _current_step + 1) + mooseError("The timesequence provided in the restart file must be identical to " + "the one in the old file up to entry number ", + _current_step + 1, + " but there are only ", + current_input_sequence.size(), + " value(s) provided for the timesequence in the restart input."); - // make sure time sequence is in ascending order - for (unsigned int j = 0; j < times.size() - 1; ++j) - if (times[j + 1] <= times[j]) - mooseError("time_sequence must be in ascending order."); + // Verify that the new input agrees with the completed steps from the checkpoint. + const std::vector saved_time_sequence = _time_sequence; - if (times.size() < _current_step + 1) + for (unsigned int j = 0; j <= _current_step; ++j) + { + if (!MooseUtils::absoluteFuzzyEqual(current_input_sequence[j], saved_time_sequence[j])) mooseError("The timesequence provided in the restart file must be identical to " "the one in the old file up to entry number ", _current_step + 1, - " but there are only ", - times.size(), - " value(s) provided for the timesequence in the restart input."); - - // save the restarted time_sequence - std::vector saved_time_sequence = _time_sequence; - - _time_sequence.clear(); - - // step 1: fill in the entries up to _current_step - for (unsigned int j = 0; j <= _current_step; ++j) - { - if (!MooseUtils::absoluteFuzzyEqual(times[j], saved_time_sequence[j])) - mooseError("The timesequence provided in the restart file must be identical to " - "the one in the old file up to entry number ", - _current_step + 1, - " but entry ", - j + 1, - " is ", - times[j], - " in the restart input but ", - saved_time_sequence[j], - " in the restarted input."); - - _time_sequence.push_back(saved_time_sequence[j]); - } - - // step 2: fill in the entries up after _current_step - for (unsigned int j = _current_step + 1; j < times.size(); ++j) - { - if (times[j] < end_time) - _time_sequence.push_back(times[j]); - } - - if (!_set_end_time) - _time_sequence.push_back(end_time); + " but entry ", + j + 1, + " is ", + current_input_sequence[j], + " in the restart input but ", + saved_time_sequence[j], + " in the restarted input."); } + + _time_sequence = std::move(current_input_sequence); } // Set end time to last time in sequence if requested @@ -121,6 +160,8 @@ TimeSequenceStepperBase::setupSequence(const std::vector & times) end_time = _time_sequence.back(); } + // For the half-transient test mechanism, clamp end_time to the sequence midpoint so the + // first half of the run stops there and writes a checkpoint. if (_app.testCheckpointHalfTransient()) { unsigned int half = (_time_sequence.size() - 1) / 2; @@ -145,6 +186,9 @@ TimeSequenceStepperBase::updateSequence(const std::vector & times) _time_sequence.push_back(times[j]); } + // Always push end_time as a sentinel even if it duplicates the last user-specified time. + // computeDT() reads _time_sequence[_current_step + 1], so _current_step must never point + // to the last element; the sentinel guarantees that invariant holds. if (!_set_end_time) _time_sequence.push_back(end_time); } @@ -155,11 +199,30 @@ TimeSequenceStepperBase::resetSequence() _time_sequence.clear(); } +bool +TimeSequenceStepperBase::advanceToFutureTime(Real time, Real tolerance) +{ + // Advance _current_step past any sequence entries that have already been reached (within + // tolerance). Used by CompositionDT to find the nearest upcoming sequence time across all + // active TimeSequenceSteppers. + while (_current_step + 1 < _time_sequence.size() && getNextTimeInSequence() - time <= tolerance) + increaseCurrentStep(); + + return getNextTimeInSequence() - time > tolerance; +} + void TimeSequenceStepperBase::acceptStep() { TimeStepper::acceptStep(); - if (MooseUtils::absoluteFuzzyGreaterEqual(_time, getNextTimeInSequence())) + // Advance _current_step to the last sequence entry that is strictly less than the just-accepted + // time, so that _time_sequence[_current_step] == _time after a normal step. Using + // absoluteFuzzyLessThan (a < b - tol) means we stop AT the current time rather than advancing + // past it, which keeps computeDT() returning the correct next interval. + // The loop (rather than a single increment) handles recover, where the simulation may resume + // several sequence points ahead of the initial _current_step. + while (_current_step + 1 < _time_sequence.size() && + MooseUtils::absoluteFuzzyLessThan(_time_sequence[_current_step], _time)) increaseCurrentStep(); } @@ -172,6 +235,9 @@ TimeSequenceStepperBase::computeInitialDT() Real TimeSequenceStepperBase::computeDT() { + // _current_step always points to the last completed sequence time, so [_current_step + 1] + // is the next target. The sentinel end_time appended by updateSequence/setupSequence + // ensures this read is always in bounds. if (_use_last_dt_after_last_t) { // last *provided* time value index; actual last index corresponds to end time @@ -191,7 +257,9 @@ TimeSequenceStepperBase::computeFailedDT() if (computeDT() <= _dt_min) mooseError("Solve failed and timestep already at or below dtmin, cannot continue!"); - // cut the time step in a half if possible + // Cut the time step by the cutback factor (typically 0.5) and insert a new intermediate + // target into the sequence immediately after _current_step. The next computeDT() call + // will then return the shorter interval to the inserted point. Real dt = _cutback_factor_at_failure * computeDT(); if (dt < _dt_min) dt = _dt_min; diff --git a/framework/src/utils/MooseTypes.C b/framework/src/utils/MooseTypes.C index 5af18b87f23a..6bf4f6a791e0 100644 --- a/framework/src/utils/MooseTypes.C +++ b/framework/src/utils/MooseTypes.C @@ -84,3 +84,10 @@ DerivativeStringToJSON(NonlinearSystemName); DerivativeStringToJSON(LinearSystemName); DerivativeStringToJSON(SolverSystemName); DerivativeStringToJSON(CLIArgString); +#ifdef MOOSE_MFEM_ENABLED +DerivativeStringToJSON(MFEMScalarCoefficientName); +DerivativeStringToJSON(MFEMVectorCoefficientName); +DerivativeStringToJSON(MFEMMatrixCoefficientName); +DerivativeStringToJSON(MFEMFESpaceName); +DerivativeStringToJSON(MFEMSolverName); +#endif diff --git a/framework/src/vectorpostprocessors/VectorPostprocessorInterface.C b/framework/src/vectorpostprocessors/VectorPostprocessorInterface.C index c4b3c1681760..e1ef8674db9a 100644 --- a/framework/src/vectorpostprocessors/VectorPostprocessorInterface.C +++ b/framework/src/vectorpostprocessors/VectorPostprocessorInterface.C @@ -14,6 +14,8 @@ #include "MooseTypes.h" #include "UserObject.h" +#include + InputParameters VectorPostprocessorInterface::validParams() { @@ -164,15 +166,9 @@ VectorPostprocessorInterface::hasVectorPostprocessorByName(const VectorPostproce _vpi_feproblem.mooseError("Cannot call hasVectorPostprocessorByName() until all " "VectorPostprocessors have been constructed."); - const bool has_vpp = _vpi_feproblem.getReporterData().hasReporterValue( - VectorPostprocessorReporterName(name, vector_name)); - - if (has_vpp) - mooseAssert(_vpi_feproblem.hasUserObject(name) && dynamic_cast( - &_vpi_feproblem.getUserObjectBase(name)), - "Has reporter VectorPostprocessor Reporter value but not VectorPostprocessor UO"); - - return has_vpp; + return _vpi_feproblem.getReporterData().hasReporterValue( + VectorPostprocessorReporterName(name, vector_name)) && + hasVectorPostprocessorByName(name); } bool @@ -193,8 +189,14 @@ VectorPostprocessorInterface::hasVectorPostprocessorByName( _vpi_feproblem.mooseError("Cannot call hasVectorPostprocessorByName() until all " "VectorPostprocessors have been constructed."); - return _vpi_feproblem.hasUserObject(name) && - dynamic_cast(&_vpi_feproblem.getUserObjectBase(name)); + std::vector objs; + _vpi_feproblem.theWarehouse() + .query() + .condition(Interfaces::VectorPostprocessor) + .condition(0) + .condition(name) + .queryInto(objs); + return !objs.empty(); } bool diff --git a/test/tests/hdgkernels/ldg-diffusion/tests b/test/tests/hdgkernels/ldg-diffusion/tests index cd3fc0b76ec1..4d8f4bbbef70 100644 --- a/test/tests/hdgkernels/ldg-diffusion/tests +++ b/test/tests/hdgkernels/ldg-diffusion/tests @@ -47,6 +47,7 @@ input = test.py test_case = TestRTQuad detail = 'tensor-product elements.' + min_slots = 2 # memory intensive [] [] [prescribed_nonzero_flux] diff --git a/test/tests/kernels/functor_kernel/functor_kernel_target.i b/test/tests/kernels/functor_kernel/functor_kernel_target.i index 56efc7573f56..63119265bcea 100644 --- a/test/tests/kernels/functor_kernel/functor_kernel_target.i +++ b/test/tests/kernels/functor_kernel/functor_kernel_target.i @@ -27,7 +27,6 @@ [] [] - [Executioner] type = Transient solve_type = 'NEWTON' @@ -37,8 +36,8 @@ l_tol = 1e-15 l_max_its = 50 - nl_abs_tol = 1e-16 - nl_rel_tol = 1e-16 + nl_abs_tol = 1e-12 + nl_rel_tol = 1e-12 nl_max_its = 15 start_time = 0.0 @@ -50,7 +49,6 @@ time_sequence = '1 2 3 4 5 6 7 8 9 10 11' [] [] - [] [Outputs] diff --git a/test/tests/linearfvkernels/advection/tests b/test/tests/linearfvkernels/advection/tests index 20dfa301a9d7..6f0f273d4706 100644 --- a/test/tests/linearfvkernels/advection/tests +++ b/test/tests/linearfvkernels/advection/tests @@ -43,6 +43,7 @@ requirement = 'The system shall display first-order convergence for advection problems with linear interpolation solved using a linear finite volume system on a two-dimensional nonorthogonal mesh.' # This is first order in the original implementation as well. At some point somebody could take a look at the error terms. max_threads = 1 # see libmesh issue #3808 + min_slots = 2 [] [mms-2d-rz] type = MMSTest @@ -50,5 +51,6 @@ test_case = TestAdvection2DRZ requirement = 'The system shall display second-order convergence for advection problems with linear interpolation solved using a linear finite volume system on a two-dimensional orthogonal mesh in cylindrical coordinate system.' max_threads = 1 # see libmesh issue #3808 + min_slots = 2 [] [] diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0000.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0000.csv new file mode 100644 index 000000000000..e99573321216 --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0000.csv @@ -0,0 +1,2 @@ +complex_temperature_imag_0,complex_temperature_real_0,x_0,x_1 +0,0,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0010.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0010.csv new file mode 100644 index 000000000000..276448a6e6d6 --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0010.csv @@ -0,0 +1,2 @@ +complex_temperature_imag_0,complex_temperature_real_0,x_0,x_1 +1.7092102224128,1.7092102224128,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0020.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0020.csv new file mode 100644 index 000000000000..fadb98b85511 --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0020.csv @@ -0,0 +1,2 @@ +complex_temperature_imag_0,complex_temperature_real_0,x_0,x_1 +1.4604882411775,1.4604882411775,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0030.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0030.csv new file mode 100644 index 000000000000..a176384a3278 --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0030.csv @@ -0,0 +1,2 @@ +complex_temperature_imag_0,complex_temperature_real_0,x_0,x_1 +1.3383797678165,1.3383797678165,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0040.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0040.csv new file mode 100644 index 000000000000..33d93a97c24d --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0040.csv @@ -0,0 +1,2 @@ +complex_temperature_imag_0,complex_temperature_real_0,x_0,x_1 +1.2718650483004,1.2718650483004,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0050.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0050.csv new file mode 100644 index 000000000000..c03232fc0b10 --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0050.csv @@ -0,0 +1,2 @@ +complex_temperature_imag_0,complex_temperature_real_0,x_0,x_1 +1.2321741962316,1.2321741962316,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0000.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0000.csv new file mode 100644 index 000000000000..8ac27acaa6ba --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0000.csv @@ -0,0 +1,2 @@ +temperature_0,x_0,x_1 +0,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0010.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0010.csv new file mode 100644 index 000000000000..993efd947a26 --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0010.csv @@ -0,0 +1,2 @@ +temperature_0,x_0,x_1 +1.7092102224128,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0020.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0020.csv new file mode 100644 index 000000000000..b51c08890d89 --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0020.csv @@ -0,0 +1,2 @@ +temperature_0,x_0,x_1 +1.4604882411775,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0030.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0030.csv new file mode 100644 index 000000000000..8e14879ba0fe --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0030.csv @@ -0,0 +1,2 @@ +temperature_0,x_0,x_1 +1.3383797678165,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0040.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0040.csv new file mode 100644 index 000000000000..424d0fb1e3bf --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0040.csv @@ -0,0 +1,2 @@ +temperature_0,x_0,x_1 +1.2718650483004,0,0 diff --git a/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0050.csv b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0050.csv new file mode 100644 index 000000000000..c4b74c906557 --- /dev/null +++ b/test/tests/mfem/kernels/gold/OutputData/NLHeatConductionComplexAux_centre_temperature_0050.csv @@ -0,0 +1,2 @@ +temperature_0,x_0,x_1 +1.2321741962316,0,0 diff --git a/test/tests/mfem/kernels/irrotational.i b/test/tests/mfem/kernels/irrotational.i index 8a2f229317b3..90c8f710c88a 100644 --- a/test/tests/mfem/kernels/irrotational.i +++ b/test/tests/mfem/kernels/irrotational.i @@ -115,7 +115,6 @@ centre_y = 0.1 type = MFEMVectorL2Error variable = velocity function = exact_velocity - execution_order_group = 1 [] [] diff --git a/test/tests/mfem/kernels/nl_heatconduction_complex_aux.i b/test/tests/mfem/kernels/nl_heatconduction_complex_aux.i new file mode 100644 index 000000000000..da483d58dacd --- /dev/null +++ b/test/tests/mfem/kernels/nl_heatconduction_complex_aux.i @@ -0,0 +1,127 @@ +# Transient nonlinear heat conduction with a complex auxiliary variable. +# The complex aux variable exercises the cmplx_gridfunctions checkpoint/recover code path +# in SolutionStateData.C without requiring a complex primary solve. + +kappa = 0.5 +alpha = 1e-2 + +[Mesh] + type = MFEMMesh + file = ../mesh/star.mesh + uniform_refine = 1 +[] + +[Problem] + type = MFEMProblem +[] + +[FESpaces] + [H1FESpace] + type = MFEMScalarFESpace + fec_type = H1 + fec_order = SECOND + [] +[] + +[Variables] + [temperature] + type = MFEMVariable + fespace = H1FESpace + [] +[] + +[AuxVariables] + [complex_temperature] + type = MFEMComplexVariable + fespace = H1FESpace + [] +[] + +[AuxKernels] + [project_complex_temperature] + type = MFEMComplexScalarProjectionAux + variable = complex_temperature + coefficient_real = temperature + coefficient_imag = temperature + execute_on = TIMESTEP_END + [] +[] + +[Functions] + [initial] + type = ParsedFunction + expression = 'if((x*x + y*y > 0.251), 1.0, 2.0)' + [] + [diffusivity_temperature_dependence] + type = MFEMParsedFunction + expression = 'alpha * temperature' + symbol_names = 'alpha temperature' + symbol_values = '${alpha} temperature' + [] +[] + +[ICs] + [diffused_ic] + type = MFEMScalarIC + coefficient = initial + variable = temperature + [] +[] + +[Kernels] + [nl_diffusion] + type = MFEMNLDiffusionKernel + variable = temperature + k_coefficient = diffusivity_temperature_dependence + dk_du_coefficient = ${alpha} + [] + [linear_diffusion] + type = MFEMDiffusionKernel + variable = temperature + coefficient = ${kappa} + [] + [dT_dt] + type = MFEMTimeDerivativeMassKernel + variable = temperature + [] +[] + +[Solver] + type = MFEMMUMPS + print_level = 0 +[] + +[Executioner] + type = MFEMTransient + device = cpu + assembly_level = legacy + dt = 1e-2 + start_time = 0.0 + end_time = 0.5 + + nl_max_its = 30 + nl_abs_tol = 1.0e-5 + nl_rel_tol = 1.0e-5 + print_level = 1 +[] + +[VectorPostprocessors] + [centre_temperature] + type = MFEMPointValueSampler + variable = 'temperature' + points = '0.0 0.0 0.0' + execute_on = TIMESTEP_END + [] + [centre_complex_temperature] + type = MFEMComplexPointValueSampler + variable = 'complex_temperature' + points = '0.0 0.0 0.0' + execute_on = TIMESTEP_END + [] +[] + +[Outputs] + file_base = OutputData/NLHeatConductionComplexAux + csv = true + time_step_interval = 10 +[] diff --git a/test/tests/mfem/kernels/tests b/test/tests/mfem/kernels/tests index f524a7557624..747a591c3bdf 100644 --- a/test/tests/mfem/kernels/tests +++ b/test/tests/mfem/kernels/tests @@ -162,7 +162,7 @@ xmldiff = 'OutputData/MixedHeatTransfer/Run0/Run0.pvd OutputData/MixedHeatTransfer/Run0/Cycle000003/proc000000.vtu' requirement = 'The system shall have the ability to solve a transient heat conduction problem solved using the mixed form of the heat equation using MFEM.' - capabilities = 'mfem' + capabilities = 'mfem & platform=linux' compute_devices = 'cpu cuda' max_parallel = 1 # schemadiff with multiple ranks recover = false @@ -221,7 +221,7 @@ compute_devices = 'cpu' max_parallel = 1 # schemadiff with multiple ranks recover = false - [] + [] [MFEMNonLinearDiffusionNonZeroRHS] type = XMLDiff input = nldiffusion.i @@ -246,10 +246,9 @@ csvdiff = 'OutputData/NLHeatTransferLinearized_line_sample_0003.csv' capabilities = 'mfem' compute_devices = 'cpu' - recover = false restep = false cli_args = 'BCs/active="linearized" ' - 'Outputs/CSV/file_base=OutputData/NLHeatTransferLinearized ' + 'Outputs/CSV/file_base=OutputData/NLHeatTransferLinearized ' [] [non-linear] detail = 'when the non-linear BC is given state-dependent coefficients.' @@ -258,8 +257,8 @@ csvdiff = 'OutputData/NLHeatTransfer_line_sample_0003.csv' capabilities = 'mfem' compute_devices = 'cpu' - recover = false restep = false + prereq = 'MFEMNonLinearHeatTransfer/linearized' [] [] [MFEMNonLinearHeatConduction] @@ -276,7 +275,28 @@ OutputData/NLHeatConduction_centre_temperature_0050.csv' capabilities = 'mfem' compute_devices = 'cpu' - recover = false - restep = false + restep = false + [] + [MFEMNonLinearHeatConductionComplexAux] + design = 'EquationSystem.md' + issues = '#30076' + requirement = 'The system shall correctly checkpoint and recover a transient MFEM problem that includes a complex auxiliary variable, exercising the cmplx_gridfunctions restore path.' + type = CSVDiff + input = nl_heatconduction_complex_aux.i + csvdiff = 'OutputData/NLHeatConductionComplexAux_centre_temperature_0000.csv + OutputData/NLHeatConductionComplexAux_centre_temperature_0010.csv + OutputData/NLHeatConductionComplexAux_centre_temperature_0020.csv + OutputData/NLHeatConductionComplexAux_centre_temperature_0030.csv + OutputData/NLHeatConductionComplexAux_centre_temperature_0040.csv + OutputData/NLHeatConductionComplexAux_centre_temperature_0050.csv + OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0000.csv + OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0010.csv + OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0020.csv + OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0030.csv + OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0040.csv + OutputData/NLHeatConductionComplexAux_centre_complex_temperature_0050.csv' + capabilities = 'mfem' + compute_devices = 'cpu' + restep = false [] [] diff --git a/test/tests/mfem/submeshes/av_magnetostatic.i b/test/tests/mfem/submeshes/av_magnetostatic.i index 26fa9467302f..e0f7caaef8be 100644 --- a/test/tests/mfem/submeshes/av_magnetostatic.i +++ b/test/tests/mfem/submeshes/av_magnetostatic.i @@ -48,7 +48,6 @@ source = a_field scale_factor = 1.0 execute_on = TIMESTEP_END - execution_order_group = 3 [] [] diff --git a/test/tests/mfem/submeshes/cut_closed_coil.i b/test/tests/mfem/submeshes/cut_closed_coil.i index 216ab575ce56..b075fb7efdd4 100644 --- a/test/tests/mfem/submeshes/cut_closed_coil.i +++ b/test/tests/mfem/submeshes/cut_closed_coil.i @@ -44,7 +44,6 @@ coil_conductivity = 1.0 [coil] type = MFEMDomainSubMesh block = coil_dom - execution_order_group = 2 [] [] @@ -143,7 +142,6 @@ coil_conductivity = 1.0 variable = e_field source_variables = 'induced_e_field external_e_field' execute_on = TIMESTEP_END - execution_order_group = 3 [] [] @@ -188,7 +186,6 @@ coil_conductivity = 1.0 from_variable = transition_external_e_field to_variable = external_e_field execute_on = TIMESTEP_END - execution_order_group = 2 [] [] diff --git a/test/tests/mfem/submeshes/hphi_magnetodynamic.i b/test/tests/mfem/submeshes/hphi_magnetodynamic.i index a8d46c68529e..990a7835ed61 100644 --- a/test/tests/mfem/submeshes/hphi_magnetodynamic.i +++ b/test/tests/mfem/submeshes/hphi_magnetodynamic.i @@ -89,7 +89,6 @@ vacuum_permeability = 1.0 source = h_field scale_factor = 1.0 execute_on = TIMESTEP_END - execution_order_group = 4 [] [] @@ -171,7 +170,6 @@ vacuum_permeability = 1.0 coefficient = resistivity dual_variable = j_field primal_variable = j_field - execution_order_group = 5 block = 'TorusCore TorusSheath' [] [] diff --git a/test/tests/mfem/submeshes/hphi_magnetostatic.i b/test/tests/mfem/submeshes/hphi_magnetostatic.i index 7c84d084b9da..4498a18faf30 100644 --- a/test/tests/mfem/submeshes/hphi_magnetostatic.i +++ b/test/tests/mfem/submeshes/hphi_magnetostatic.i @@ -44,7 +44,6 @@ vacuum_permeability = 1.0 [vacuum] type = MFEMDomainSubMesh block = vacuum_dom - execution_order_group = 2 [] [] @@ -138,7 +137,6 @@ vacuum_permeability = 1.0 variable = vacuum_h_field source_variables = 'background_h_field cut_function_field' execute_on = TIMESTEP_END - execution_order_group = 3 [] [] @@ -196,14 +194,12 @@ vacuum_permeability = 1.0 from_variable = transition_cut_function_field to_variable = cut_function_field execute_on = TIMESTEP_END - execution_order_group = 2 [] [submesh_transfer_from_vacuum] type = MFEMSubMeshTransfer from_variable = vacuum_h_field to_variable = h_field execute_on = TIMESTEP_END - execution_order_group = 4 [] [] @@ -213,7 +209,6 @@ vacuum_permeability = 1.0 coefficient = ${fparse 0.5*vacuum_permeability} dual_variable = vacuum_h_field primal_variable = vacuum_h_field - execution_order_group = 4 block = 'Exterior' [] [] diff --git a/test/tests/mfem/submeshes/tests b/test/tests/mfem/submeshes/tests index 587cf2120b9c..59b5e8fd0e4a 100644 --- a/test/tests/mfem/submeshes/tests +++ b/test/tests/mfem/submeshes/tests @@ -45,13 +45,13 @@ compute_devices = 'cpu cuda' max_parallel = 1 recover = false - [] + [] [MFEMGeometryCutTransitionSubMesh] design = 'syntax/MFEM/ClosedCoilMagnetostatic.md' issues = '#31404' type = CSVDiff input = av_magnetostatic.i - csvdiff = 'OutputData/AVMagnetostaticClosedCoilCSV.csv' + csvdiff = 'OutputData/AVMagnetostaticClosedCoilCSV.csv' requirement = 'MOOSE shall have the ability to strongly enforce global topological constraints in MFEM by defining a transition region for variable discontinuities on one side of a cut plane defined in a solid geometry.' capabilities = 'mfem' compute_devices = 'cpu cuda' @@ -63,7 +63,7 @@ issues = '#31404' type = CSVDiff input = hphi_magnetostatic.i - csvdiff = 'OutputData/HPhiMagnetostaticClosedCoilCSV.csv' + csvdiff = 'OutputData/HPhiMagnetostaticClosedCoilCSV.csv' requirement = 'MOOSE shall have the ability to strongly enforce global topological constraints in MFEM by defining a transition region for variable discontinuities on one side of a cut plane defined in an exterior vacuum region.' capabilities = 'mfem' compute_devices = 'cpu cuda' @@ -74,12 +74,12 @@ issues = '#31404' type = CSVDiff input = hphi_magnetodynamic.i - csvdiff = 'OutputData/HPhiMagnetodynamicClosedCoilCSV.csv' + csvdiff = 'OutputData/HPhiMagnetodynamicClosedCoilCSV.csv' requirement = 'MOOSE shall have the ability to apply boundary conditions on boundaries of embedded volumetric subdomains.' capabilities = 'mfem' compute_devices = 'cpu cuda' - recover = false restep = false valgrind = heavy - [] + rel_err = 6e-5 + [] [] diff --git a/test/tests/mfem/timesteppers/tests b/test/tests/mfem/timesteppers/tests index 1da68a95a4a4..1b59deb06688 100644 --- a/test/tests/mfem/timesteppers/tests +++ b/test/tests/mfem/timesteppers/tests @@ -10,7 +10,6 @@ detail = 'using the minimum time step size of input time steppers,' capabilities = 'mfem' compute_devices = 'cpu cuda' - recover = false restep = false [] [multiple_time_sequences] @@ -20,7 +19,6 @@ detail = 'or with specific times to hit.' capabilities = 'mfem' compute_devices = 'cpu cuda' - recover = false restep = false [] [] diff --git a/test/tests/mortar/convergence-studies/fv-gap-conductance/tests b/test/tests/mortar/convergence-studies/fv-gap-conductance/tests index d982103ffaac..acb82941b82a 100644 --- a/test/tests/mortar/convergence-studies/fv-gap-conductance/tests +++ b/test/tests/mortar/convergence-studies/fv-gap-conductance/tests @@ -16,6 +16,7 @@ test_case = 'TestGapConductanceSecondOrder' detail = 'second order accuracy' required_python_packages = 'sympy' + min_slots = 2 [] [] [] diff --git a/unit/src/MFEMExecutedObjectDependencyTest.C b/unit/src/MFEMExecutedObjectDependencyTest.C new file mode 100644 index 000000000000..b29687e252ea --- /dev/null +++ b/unit/src/MFEMExecutedObjectDependencyTest.C @@ -0,0 +1,175 @@ +//* This file is part of the MOOSE framework +//* https://mooseframework.inl.gov +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#ifdef MOOSE_MFEM_ENABLED + +#include "MFEMObjectUnitTest.h" +#include "MooseUnitUtils.h" +#include "MFEMAuxKernel.h" +#include "MFEMPostprocessor.h" +#include "MFEMProblem.h" + +#include +#include + +namespace +{ +std::vector execution_log; +} + +class TestMFEMDependencyAux : public MFEMAuxKernel +{ +public: + static InputParameters validParams(); + + TestMFEMDependencyAux(const InputParameters & parameters); + + virtual void execute() override; + +private: + const bool _use_postprocessor; +}; + +registerMooseObject("MooseUnitApp", TestMFEMDependencyAux); + +InputParameters +TestMFEMDependencyAux::validParams() +{ + auto params = MFEMAuxKernel::validParams(); + MFEMExecutedObject::addDependencyParam( + params, "postprocessor", "Optional postprocessor dependency used to set the output value."); + return params; +} + +TestMFEMDependencyAux::TestMFEMDependencyAux(const InputParameters & parameters) + : MFEMAuxKernel(parameters), _use_postprocessor(parameters.isParamSetByUser("postprocessor")) +{ +} + +void +TestMFEMDependencyAux::execute() +{ + execution_log.push_back(name()); + + const auto value = _use_postprocessor ? getPostprocessorValue("postprocessor") + 1.0 : 1.0; + mfem::ConstantCoefficient coef(value); + _result_var.ProjectCoefficient(coef); +} + +class TestMFEMDependencyPostprocessor : public MFEMPostprocessor +{ +public: + static InputParameters validParams(); + + TestMFEMDependencyPostprocessor(const InputParameters & parameters); + + virtual void execute() override; + virtual PostprocessorValue getValue() const override; + +private: + const VariableName _variable_name; + mfem::ParGridFunction & _variable; + PostprocessorValue _value = 0.0; +}; + +registerMooseObject("MooseUnitApp", TestMFEMDependencyPostprocessor); + +InputParameters +TestMFEMDependencyPostprocessor::validParams() +{ + auto params = MFEMPostprocessor::validParams(); + MFEMExecutedObject::addRequiredDependencyParam( + params, "variable", "The MFEM variable this test postprocessor reads."); + return params; +} + +TestMFEMDependencyPostprocessor::TestMFEMDependencyPostprocessor(const InputParameters & parameters) + : MFEMPostprocessor(parameters), + _variable_name(getParam("variable")), + _variable(*getMFEMProblem().getProblemData().gridfunctions.Get(_variable_name)) +{ +} + +void +TestMFEMDependencyPostprocessor::execute() +{ + execution_log.push_back(name()); + _value = _variable.GetData()[0]; +} + +PostprocessorValue +TestMFEMDependencyPostprocessor::getValue() const +{ + return _value; +} + +class MFEMExecutedObjectDependencyTest : public MFEMObjectUnitTest +{ +public: + MFEMExecutedObjectDependencyTest() : MFEMObjectUnitTest("MooseUnitApp") + { + InputParameters fe_params = _factory.getValidParams("MFEMScalarFESpace"); + fe_params.set("fec_type") = "H1"; + _mfem_problem->addFESpace("MFEMScalarFESpace", "h1", fe_params); + + InputParameters variable_params = _factory.getValidParams("MFEMVariable"); + variable_params.set("fespace") = "h1"; + _mfem_problem->addVariable("MFEMVariable", "aux0_var", variable_params); + _mfem_problem->addVariable("MFEMVariable", "aux1_var", variable_params); + } + + virtual void SetUp() override { execution_log.clear(); } +}; + +TEST_F(MFEMExecutedObjectDependencyTest, PostprocessorAuxKernelDependencyChain) +{ + InputParameters pp1_params = _factory.getValidParams("TestMFEMDependencyPostprocessor"); + pp1_params.set("variable") = "aux1_var"; + auto & pp1 = addObject( + "TestMFEMDependencyPostprocessor", "pp1", pp1_params); + + InputParameters aux1_params = _factory.getValidParams("TestMFEMDependencyAux"); + aux1_params.set("variable") = "aux1_var"; + aux1_params.set("postprocessor") = "pp0"; + addObject("TestMFEMDependencyAux", "aux1", aux1_params); + + InputParameters pp0_params = _factory.getValidParams("TestMFEMDependencyPostprocessor"); + pp0_params.set("variable") = "aux0_var"; + auto & pp0 = addObject( + "TestMFEMDependencyPostprocessor", "pp0", pp0_params); + + InputParameters aux0_params = _factory.getValidParams("TestMFEMDependencyAux"); + aux0_params.set("variable") = "aux0_var"; + addObject("TestMFEMDependencyAux", "aux0", aux0_params); + + _mfem_problem->executeMFEMObjects(EXEC_TIMESTEP_END); + + EXPECT_EQ(execution_log, std::vector({"aux0", "pp0", "aux1", "pp1"})); + EXPECT_EQ(pp0.getValue(), 1.0); + EXPECT_EQ(pp1.getValue(), 2.0); + EXPECT_EQ(pp0.getCurrentValue(), 1.0); + EXPECT_EQ(pp1.getCurrentValue(), 2.0); +} + +TEST_F(MFEMExecutedObjectDependencyTest, DuplicateSupplierError) +{ + InputParameters aux0_params = _factory.getValidParams("TestMFEMDependencyAux"); + aux0_params.set("variable") = "aux0_var"; + addObject("TestMFEMDependencyAux", "aux0", aux0_params); + + InputParameters aux0_dup_params = _factory.getValidParams("TestMFEMDependencyAux"); + aux0_dup_params.set("variable") = "aux0_var"; + addObject("TestMFEMDependencyAux", "aux0_dup", aux0_dup_params); + + EXPECT_THROW_MSG_CONTAINS(_mfem_problem->executeMFEMObjects(EXEC_TIMESTEP_END), + std::runtime_error, + "both 'aux0' and 'aux0_dup' supply 'variable:aux0_var'"); +} + +#endif diff --git a/unit/src/MFEMPostprocessorTest.C b/unit/src/MFEMPostprocessorTest.C index b5a5c5d14d47..1f9434ae30fb 100644 --- a/unit/src/MFEMPostprocessorTest.C +++ b/unit/src/MFEMPostprocessorTest.C @@ -36,11 +36,11 @@ public: NDFE_params.set("fec_type") = "ND"; _mfem_problem->addFESpace("MFEMVectorFESpace", "ND_vector", NDFE_params); InputParameters scalar_params = _factory.getValidParams("MFEMVariable"); - scalar_params.set("fespace") = "H1_scalar"; + scalar_params.set("fespace") = "H1_scalar"; _mfem_problem->addVariable("MFEMVariable", "scalar_var", scalar_params); _scalar_var = _mfem_problem->getProblemData().gridfunctions.Get("scalar_var"); InputParameters vector_params = _factory.getValidParams("MFEMVariable"); - vector_params.set("fespace") = "ND_vector"; + vector_params.set("fespace") = "ND_vector"; _mfem_problem->addVariable("MFEMVariable", "vector_var", vector_params); _vector_var = _mfem_problem->getProblemData().gridfunctions.Get("vector_var"); } @@ -74,12 +74,24 @@ TEST_F(MFEMPostprocessorTest, MFEML2ErrorCoefficient) pp_params.set("function") = "scalar_ones"; pp_params.set("variable") = "scalar_var"; _mfem_problem->addPostprocessor("MFEML2Error", "ppl2", pp_params); - auto & l2_pp = _mfem_problem->getUserObject("ppl2"); - auto & l2_coef = _mfem_problem->getCoefficients().getScalarCoefficient("ppl2"); + auto & l2_pp = _mfem_problem->getPostprocessorObjectByName("ppl2"); + auto & l2_pp_as_coef = _mfem_problem->getCoefficients().getScalarCoefficient("ppl2"); mfem::ConstantCoefficient twos(2.); _scalar_var->ProjectCoefficient(twos); - l2_pp.getValue(); + _mfem_problem->executeMFEMObjects(EXEC_TIMESTEP_END); + const auto l2_pp_value = l2_pp.getCurrentValue(); + + // Compute domain volume + mfem::ConstantCoefficient one(1.0); + mfem::LinearForm volume_form(_scalar_var->FESpace()); + volume_form.AddDomainIntegrator(new mfem::DomainLFIntegrator(one)); + volume_form.Assemble(); + mfem::Vector ones(volume_form.Size()); + ones = 1.0; + const auto volume = volume_form * ones; + using std::sqrt; + EXPECT_NEAR(l2_pp_value, sqrt(volume), TOLERANCE * TOLERANCE); mfem::IsoparametricTransformation fe_transform; mfem::IntegrationPoint point; @@ -87,7 +99,7 @@ TEST_F(MFEMPostprocessorTest, MFEML2ErrorCoefficient) point.Set2(0., 0.); fe_transform.SetIdentityTransformation(mfem::Geometry::SQUARE); - EXPECT_EQ(l2_coef.Eval(fe_transform, point), l2_pp.getCurrentValue()); + EXPECT_EQ(l2_pp_as_coef.Eval(fe_transform, point), l2_pp_value); } /** diff --git a/unit/src/MFEMSolverTest.C b/unit/src/MFEMSolverTest.C index d6612b09b3cc..f73c772cf1fa 100644 --- a/unit/src/MFEMSolverTest.C +++ b/unit/src/MFEMSolverTest.C @@ -231,7 +231,7 @@ TEST_F(MFEMSolverTest, MFEMHypreADS) // Build required solver inputs InputParameters solver_params = _factory.getValidParams("MFEMHypreADS"); - solver_params.set("fespace") = "HDivFESpace"; + solver_params.set("fespace") = "HDivFESpace"; // Construct solver MFEMHypreADS & solver = addObject("MFEMHypreADS", "solver1", solver_params); @@ -257,7 +257,7 @@ TEST_F(MFEMSolverTest, MFEMHypreAMS) // Build required solver inputs InputParameters solver_params = _factory.getValidParams("MFEMHypreAMS"); - solver_params.set("fespace") = "HCurlFESpace"; + solver_params.set("fespace") = "HCurlFESpace"; // Construct solver MFEMHypreAMS & solver = addObject("MFEMHypreAMS", "solver1", solver_params);