Functor system
Functors are an abstraction, existing as a base class, that is available to several systems in MOOSE:
All functors can be called using the same interfaces. This enables considerable code re-use. For example, instead of having a kernel for each type of coupled forcing term, like CoupledForce / ADCoupledForce, BodyForce, MatCoupledForce, ADMatCoupledForce, we could just have a single object FunctorForce
and have the force term be a functor.
Functions
provide a good analogy to Functors
. Both Functions
and Functors
are evaluated on-the-fly at a location in space and time, but for Functors
, the space arguments can be an element, a point in an element, a face of an element, and so on. The time arguments represent the state of the functor : current, previous value (whether in time or iteration), or value before that previous one.
Note that when using post-processors, instead of being evaluated on-the-fly at a point in space and time, the most recently computed value is used, in accordance with the execute_on
for the post-processor.
If a Functor is reported as missing by the simulation, you may use the Debug/
"show_functors" parameter to get more information about which functors were created and requested.
Developing with functors
Functors are stored on the SubProblem
, a derived class of Problems that is used for solving nonlinear systems. As such, classes do not need to have memory ownership of functors; they may simply store a reference or a pointer.
In the header of a class using a Functor
_functor
you will have:
const Moose::Functor<T> & _functor
to store a reference to _functor
, where T
is the return type of the functor. For a variable, it should be ADReal
, while for a vector variable, it should be ADRealVectorValue
. If the object using the functor is not leveraging automatic differentiation (AD), it may be Real
or RealVectorValue
. Note that functors are automatically converted between AD
and non-AD
types when retrieved, so when you retrieve a functor, you must only consider whether the object using the functor (i.e., the "consumer") needs AD
types. When you create a functor (for example, a functor material property) it's best practice to always use the AD
return type so as to never discard some derivatives.
The following table summarizes which functors are AD or non-AD (before possible conversion):
Functor | AD Type |
---|---|
Variables | AD |
Auxiliary variables | AD |
Functor material properties | AD or non-AD (user-defined) |
Functions | non-AD |
Post-processors | non-AD |
Functor consumers that derive from ADFunctorInterface
will report an error if you attempt to retrieve a functor with a non-AD type when that functor is an AD type. Functor consumers that instead derive from NonADFunctorInterface
such as aux kernels and post-processors, may freely convert an AD functor to its corresponding non-AD type.
In the constructor of the same class, you will have:
CLASSNAME::CLASSNAME(const InputParameters & parameters) :
...
_functor(getFunctor<T>("<functor_parameter_name>")),
...
where CLASSNAME
is the name of the class, T
is still the required return type of the functor, and <functor_parameter_name>
is the name of the parameter used for providing the functor in the input.
Evaluating functors
Functors are evaluated on-the-fly. E.g. they can be viewed as functions of the current location in space (and time). Functors provide several overloads of the operator()
method for different "geometric quantities". One example of a "geometric quantity" is based around an element, e.g. for an FVElementalKernel
, the value of a functor material property in a cell-averaged sense can be obtained by the syntax
_foo(makeElemArg(_current_elem), determineState())
where here _foo
is a functor data member of the kernel, makeElemArg
is a helper routine for creating a functor element-based spatial argument, and determineState()
is a helper routine for determining the correct time state to evaluate at, e.g. the current time for an implicit kernel and the old time for an explicit kernel.
Spatial arguments to functors
In the following subsections, we describe the various spatial arguments that functors can be evaluated at. Almost no functor developers should have to concern themselves with these details as most functor definitions should just appear as functions of space and time, e.g. the same lambda defining the property evaluation should apply across all spatial and temporal arguments. However, in the case that a functor developer wishes to create specific implementations for specific arguments (as illustrated in IMakeMyOwnFunctorProps
test class) or simply wishes to know more about the system, we give the details below.
Any call to a functor looks like the following _foo(const SpatialArg & r, const TemporalArg & t)
. Below are the possible type overloads of SpatialArg
.
FaceArg
A struct
defining a "face" evaluation calling argument. This is composed of
a face information object which defines our location in space
a limiter which defines how the functor evaluated on either side of the face should be interpolated to the face
a boolean which states whether the face information element is upwind of the face
a boolean to indicate whether to correct for element skewness
a pointer to an element indicating if there is sidedness to the face, and if so, the side of the face. If null, the evaluation should use information from both sides of the face, if the functor is defined on both sides. If not null, the evaluation should ignore information from the other element.
ElemArg
Argument for requesting functor evaluation at an element. This is often used to evaluate constant monomial or finite volume variables. Data in the argument:
The element of interest
Whether to correct for element skewness
ElemQpArg
Argument for requesting functor evaluation at a quadrature point location in an element. Data in the argument:
The element containing the quadrature point
The quadrature point index, e.g. if there are
n
quadrature points, we are requesting the evaluation of the ith pointThe quadrature rule that can be used to initialize the functor on the given element
If functors are functions of nonlinear degrees of freedom, evaluation with this argument will likely result in calls to libMesh FE::reinit
.
ElemSideQpArg
Argument for requesting functor evaluation at quadrature point locations on an element side. Data in the argument:
The element
The element side on which the quadrature points are located
The quadrature point index, e.g. if there are
n
quadrature points, we are requesting the evaluation of the ith pointThe quadrature rule that can be used to initialize the functor on the given element and side
If functors are functions of nonlinear degrees of freedom, evaluation with this argument will likely result in calls to libMesh FE::reinit
.
ElemPointArg
Argument for requesting functor evaluation at a point located inside an element. Data in the argument:
The element containing the point
The point to evaluate the functor at
Whether to correct for element skewness
Nodes
There is currently no nodal argument to functors. Please contact a MOOSE developer if you need this.
Functor caching
By default, functors are always (re-)evaluated every time they are called with operator()
. However, the base class Moose::Functor
has a setCacheClearanceSchedule(const std::set<ExecFlagType> & clearance_schedule)
API that allows control of evaluations (in addition to the clearance_schedule
argument to addFunctorProperty
introduced above). Supported values for the clearance_schedule
are any combination of EXEC_ALWAYS
, EXEC_TIMESTEP_BEGIN
, EXEC_LINEAR
, and EXEC_NONLINEAR
. These will cause cached evaluations of functors to be cleared always (in fact not surprisingly in this case we never fill the cache), on timestepSetup
, on residualSetup
, and on jacobianSetup
respectively. If a functor is expected to depend on nonlinear degrees of freedom, then the cache should be cleared on EXEC_LINEAR
and EXEC_NONLINEAR
(the default EXEC_ALWAYS
would obviously also work) in order to achieve a perfect Jacobian. Not surprisingly, if a functor evaluation is cached, then memory usage will increase.
Functor caching is only currently implemented for ElemQpArg
and ElemSideQpArg
spatial overloads. This is with the idea that calls to FE::reinit
can be fairly expensive whereas for the other spatial argument types, evaluation of the functors may be relatively inexpensive compared to the memory expense incurred from caching. We may definitely implement caching for other overloads, however, if use cases call for it.