Supporting both AD and non-AD variables through templating
There are a large number of classes that were written before the automatic differentiation (AD) system was available that cannot leverage the AD system, and cannot therefore be used with the finite volume (FV) system which is AD-only.
To avoid duplicating classes that a user may wish to use with the FV system, classes can be generalized to allow use with both AD and non-AD variables through templating.
These classes are templated on a bool is_ad
, which is true
for AD variables, and false
for non-AD variables. Several templated methods for coupling variables and declaring/getting material properties of any type are available, such as
coupledGenericValue<is_ad>(var_name)
declareGenericMaterialProperty<T, is_ad>(mat_prop)
getGenericMaterialProperty<T, is_ad>(mat_prop)
The following examples in the framework demonstrate how to template a class to use both AD and non-AD variables and material properties.
Materials
Consider the GenericConstantMaterial.md material. The header file defines a templated class GenericConstantMaterialTempl<is_ad>
with a material property of GenericMaterialProperty<Real, is_ad
which evaluates to the correct type depending on the value of is_ad
.
#pragma once
#include "Material.h"
/**
* This material automatically declares as material properties whatever is passed to it
* through the parameters 'prop_names' and uses the values from 'prop_values' as the values
* for those properties.
*
* This is not meant to be used in a production capacity... and instead is meant to be used
* during development phases for ultimate flexibility.
*/
template <bool is_ad>
class GenericConstantMaterialTempl : public Material
{
public:
static InputParameters validParams();
GenericConstantMaterialTempl(const InputParameters & parameters);
protected:
virtual void initQpStatefulProperties() override;
virtual void computeQpProperties() override;
const std::vector<std::string> & _prop_names;
const std::vector<Real> & _prop_values;
unsigned int _num_props;
std::vector<GenericMaterialProperty<Real, is_ad> *> _properties;
};
typedef GenericConstantMaterialTempl<false> GenericConstantMaterial;
typedef GenericConstantMaterialTempl<true> ADGenericConstantMaterial;
(moose/framework/include/materials/GenericConstantMaterial.h)Note the typedef
's at the end of the header: when GenericConstantMaterial
is used in an input file, this class is instantiated with is_ad = false
, while when ADGenericConstantMaterial
is used, is_ad = true
.
The corresponding source file with templated methods is
#include "GenericConstantMaterial.h"
registerMooseObject("MooseApp", GenericConstantMaterial);
registerMooseObject("MooseApp", ADGenericConstantMaterial);
template <bool is_ad>
InputParameters
GenericConstantMaterialTempl<is_ad>::validParams()
{
InputParameters params = Material::validParams();
params.addClassDescription(
"Declares material properties based on names and values prescribed by input parameters.");
params.addRequiredParam<std::vector<std::string>>(
"prop_names", "The names of the properties this material will have");
params.addRequiredParam<std::vector<Real>>("prop_values",
"The values associated with the named properties");
params.set<MooseEnum>("constant_on") = "SUBDOMAIN";
params.declareControllable("prop_values");
return params;
}
template <bool is_ad>
GenericConstantMaterialTempl<is_ad>::GenericConstantMaterialTempl(
const InputParameters & parameters)
: Material(parameters),
_prop_names(getParam<std::vector<std::string>>("prop_names")),
_prop_values(getParam<std::vector<Real>>("prop_values"))
{
unsigned int num_names = _prop_names.size();
unsigned int num_values = _prop_values.size();
if (num_names != num_values)
mooseError(
"Number of prop_names must match the number of prop_values for a GenericConstantMaterial!");
_num_props = num_names;
_properties.resize(num_names);
for (unsigned int i = 0; i < _num_props; i++)
_properties[i] = &declareGenericProperty<Real, is_ad>(_prop_names[i]);
}
template <bool is_ad>
void
GenericConstantMaterialTempl<is_ad>::initQpStatefulProperties()
{
computeQpProperties();
}
template <bool is_ad>
void
GenericConstantMaterialTempl<is_ad>::computeQpProperties()
{
for (unsigned int i = 0; i < _num_props; i++)
(*_properties[i])[_qp] = _prop_values[i];
}
template class GenericConstantMaterialTempl<false>;
template class GenericConstantMaterialTempl<true>;
(moose/framework/src/materials/GenericConstantMaterial.C)Note that both GenericConstantMaterial
and ADGenericConstantMaterial
are registered to the app, and the material property is declared using the templated declareGenericMaterialProperty<T, is_ad>(mat_prop)
.
Kernels
Other classes can be templated in a similar fashion. Consider a Kernel example. In this case, the class is derived from the GenericKernel
base class, to ensure that the computeQpResidual()
method returns the correct type for both AD and non-AD variables (note that it is of type GenericReal<is_ad>
). Otherwise, the basic concept is the same as before.
#pragma once
#include "GenericKernel.h"
class Function;
/**
* This kernel implements a generic functional
* body force term:
* $ - c \cdof f \cdot \phi_i $
*
* The coefficient and function both have defaults
* equal to 1.0.
*/
template <bool is_ad>
class BodyForceTempl : public GenericKernel<is_ad>
{
public:
static InputParameters validParams();
BodyForceTempl(const InputParameters & parameters);
protected:
virtual GenericReal<is_ad> computeQpResidual() override;
/// Scale factor
const Real & _scale;
/// Optional function value
const Function & _function;
/// Optional Postprocessor value
const PostprocessorValue & _postprocessor;
// AD/non-AD version of the quadrature point coordinates
const MooseArray<Moose::GenericType<Point, is_ad>> * _generic_q_point;
usingGenericKernelMembers;
};
typedef BodyForceTempl<false> BodyForce;
typedef BodyForceTempl<true> ADBodyForce;
(moose/framework/include/kernels/BodyForce.h)The corresponding source file is
#include "BodyForce.h"
// MOOSE
#include "Function.h"
#include "Assembly.h"
registerMooseObject("MooseApp", BodyForce);
registerMooseObject("MooseApp", ADBodyForce);
template <bool is_ad>
InputParameters
BodyForceTempl<is_ad>::validParams()
{
InputParameters params = GenericKernel<is_ad>::validParams();
params.addClassDescription("Demonstrates the multiple ways that scalar values can be introduced "
"into kernels, e.g. (controllable) constants, functions, and "
"postprocessors. Implements the weak form $(\\psi_i, -f)$.");
params.addParam<Real>("value", 1.0, "Coefficient to multiply by the body force term");
params.addParam<FunctionName>("function", "1", "A function that describes the body force");
params.addParam<PostprocessorName>(
"postprocessor", 1, "A postprocessor whose value is multiplied by the body force");
params.declareControllable("value");
return params;
}
template <bool is_ad>
BodyForceTempl<is_ad>::BodyForceTempl(const InputParameters & parameters)
: GenericKernel<is_ad>(parameters),
_scale(this->template getParam<Real>("value")),
_function(getFunction("function")),
_postprocessor(getPostprocessorValue("postprocessor")),
_generic_q_point(this->_use_displaced_mesh ? &this->_assembly.template genericQPoints<is_ad>()
: nullptr)
{
}
template <bool is_ad>
GenericReal<is_ad>
BodyForceTempl<is_ad>::computeQpResidual()
{
if (_generic_q_point)
return -_test[_i][_qp] * _scale * _postprocessor *
_function.value(_t, (*_generic_q_point)[_qp]);
else
return -_test[_i][_qp] * _scale * _postprocessor * _function.value(_t, _q_point[_qp]);
}
template class BodyForceTempl<false>;
template class BodyForceTempl<true>;
(moose/framework/src/kernels/BodyForce.C)One important observation in this case is that this class (BodyForceTempl
) derives from a templated base class (GenericKernel<is_ad
). This is slightly more complicated than the material example above. Members of the base class are not available in this derived class without including them with the using
declaration in the header file (usingGenericKernelMembers
), which is defined in the GenericKernel
header
#pragma once
#include "Kernel.h"
#include "ADKernel.h"
template <bool is_ad>
class GenericKernel : public Kernel
{
public:
static InputParameters validParams() { return Kernel::validParams(); };
GenericKernel(const InputParameters & parameters) : Kernel(parameters) {}
};
template <>
class GenericKernel<true> : public ADKernel
{
public:
static InputParameters validParams() { return ADKernel::validParams(); };
GenericKernel(const InputParameters & parameters) : ADKernel(parameters) {}
};
#define usingGenericKernelMembers \
usingFunctionInterfaceMembers; \
usingPostprocessorInterfaceMembers; \
usingMooseObjectMembers; \
usingTransientInterfaceMembers; \
usingTaggingInterfaceMembers; \
usingBlockRestrictableMembers; \
using GenericKernel<is_ad>::_qp; \
using GenericKernel<is_ad>::_i; \
using GenericKernel<is_ad>::_j; \
using GenericKernel<is_ad>::_u; \
using GenericKernel<is_ad>::_phi; \
using GenericKernel<is_ad>::_test; \
using GenericKernel<is_ad>::_q_point; \
using GenericKernel<is_ad>::_var; \
using GenericKernel<is_ad>::getVar; \
using Coupleable::coupled; \
using Coupleable::coupledComponents
(moose/framework/include/kernels/GenericKernel.h)In addition, templated methods used in a class derived from a templated base class (like in example above) must be prefixed with this->template
to avoid compiler ambiguity, for example, the use of getParam<T>
in the BodyForce
kernel above
_scale(this->template getParam<Real>("value")),