-
Anna Mareike Kostelecky authoredAnna Mareike Kostelecky authored
- Property System Design
- Goals
- Customization points
- Challenges
- A C++ solution
- Template parameters
- Template parameters
- Template specializations
- Templates in algorithms
- Challenge: Too many parameters
- Technique 1: Traits classes
- Technique 2: Type traits
- Technique 2: Type traits
- Challenge: Inheritance
- The DuMuX Property System
- Property System Design
- Property System Design
- Property System Design
- Property System Design
- Actual Design
- Actual Design
- Actual Design
- Using type tags as traits classes
- The DuMux property system
- The DuMux property system
- How to use I
- How to use II
- How to use III
- How to use III (alternative)
- How to use IV
- How to use IV (alternative)
- How to use V
- Summary
- Alternatives to properties
- Should I create new properties
- Alternative 1:
- Alternative 2:
- Alternative 2:
- Alternative 2:
- Exercise
- Exercise
title: The DuMuX property system
subtitle: Flexible compile-time customization
Property System Design
Goals
- Change parts of the simulation
- e.g. add a energy equation
- Reuse specializations efficiently
- e.g. test various discretization schemes for same application
- Group properties of the simulation
- Improves readability
- Efficiency (runtime and implementation time)
Customization points
- Simulations have many possible customization points!
- fluid system, constitutive relations
- the type of the grid (incl. dimension)
- the terms in the equation
- discretization scheme
- details of the discretization scheme
- ...
Challenges
- Simulations have many possible customization points
- This means many properties
- Composing and reusing properties while keeping flexibility is technically challenging
A C++ solution
Template parameters
- C++ supports generic programming via templates
- e.g. classes parameterized in terms of other types
- concrete versions of templates are defined by specifying concrete types
- the actual type has to be known at compilation time
- Flexible: implementation not restricted to concrete types
- Efficient: decisions made at compile-time
Template parameters
An example - std::vector
// Declaration of the class template, usable with any
// `T` that fulfills the requirements that `vector` poses on it.
template<typename T, typename A = std::allocator<T>>
class vector;
// Instantiation of a concrete vector - a vector of ints.
// The compiler will define this concrete type for us,
// using the definition of the class template.
std::vector<int> v;
Template specializations
Template implementations can be specialized for concrete types
template<typename T>
class MyVector
{
// Generic implementation for any T
};
template<>
class MyVector<int>
{
// specialized implementation for `int`
};
Templates in algorithms
Using class templates, we can write generic algorithms
template<typename T>
double two_norm(const MyVector<T>& v)
{
double norm = 0.0;
for (int i = 0; i < v.size(); ++i)
norm += v[i]*v[i];
return std::sqrt(norm);
}
Challenge: Too many parameters
For some classes, providing all template parameters can be very cumbersome and error-prone.
// Example from dune-pdelab. 9 template parameters!
using GOF0 = Dune::GridOperator<
GFS, GFS, LOP, MBE,
RF, RF, RF,
CF, CF
>;
Technique 1: Traits classes
A usual way to group template parameters
template<class PV, class FSY, class FST, class SSY,...>
struct TwoPVolumeVariablesTraits
{
using PrimaryVariables = PV;
using FluidSystem = FSY;
using FluidState = FST;
using SolidSystem = SSY;
using SolidState = SST;
using PermeabilityType = PT;
using ModelTraits = MT;
using SaturationReconstruction = SR;
};
Technique 2: Type traits
Why do we need the type?
// Type trait template declaration
template<typename T> struct ValueType;
// Specialization for vectors of T
template<typename T, typename Allocator>
struct ValueType<std::vector<T, Allocator>> { using type = T; };
// Specialization for Dune::FieldVector
template<typename T, int size>
struct ValueType<Dune::FieldVector<T, size>> { using type = T; };
Technique 2: Type traits
template <class Traits>
class TwoPVolumeVariables
{
...
using FluidSystem = typename Traits::FluidSystem
...
Usage: these VolumeVariables will work for various FluidSystems:
Scalar mu = FluidSystem::viscosity(fluidState, paramCache, phaseIdx);
Challenge: Inheritance
Inheritance may lead to unexpected results
struct MyBaseTraits
{
using Scalar = int;
using Vector = std::vector<Scalar>;
};
struct MyDoubleTraits : public MyBaseTraits
{
using Scalar = double;
};
// this is a vector of ints!
typename MyDoubleTraits::Vector v{1.14142, 1.73205};
The DuMuX Property System
Property System Design
- Based on C++ template specialization (type traits)
- From a type tag, one can extract properties defined for it
- A property tag is a type trait declaration (or default implementation)
- A property is exported from a property tag specialization for a type tag
- (The property system also supports definitions of traits classes - see later)
Property System Design
A simplified example to illustrate the idea
// myproperties.hh
namespace Dumux::Properties {
namespace TTag { struct MyTypeTag {}; }
// some property tag
template<typename TypeTag> struct PropTagA;
// property definition for MyTypeTag
template<>
struct PropTagA<MyTypeTag>
{ using type = /*the actual property*/; };
} // namespace Dumux::Properties
Property System Design
A simplified example to illustrate the idea
template<class TypeTag>
class GenericClass // e.g. LocalResidual
{
using PropA = typename Properties::PropTagA<TypeTag>::type;
using PropB = typename Properties::PropTagB<TypeTag>::type;
// ...
// A property could be, for instance, a fluid system
using FS = typename Properties::FluidSystem<TypeTag>::type;
};
Property System Design
Issue: Inheritance not (easily) possible. All type traits need to be specialized for MyTypeTag
.
Goal: We would like type tags to be composable via inheritance, while providing a mechanism for customizing any property defined in the hierarchy.
Actual Design
- A hierarchy of nodes -- called type tags -- is defined (via inheritance)
- Properties are defined for the appropriate nodes in this hierarchy.
- The definition of properties may depend on arbitrary other properties, which may be overwritten at any higher node of the hierarchy
- The only requirement for properties is that they may not exhibit cyclic dependencies
Actual Design
Let's implement the Vector
example using the property system
namespace Properties {
namespace TTag { struct BaseTag {}; }
// specialization of the Scalar property for BaseTag
template<class TypeTag>
struct Scalar<TypeTag, TTag::BaseTag> { using type = int; };
// specialization of the Vector property for BaseTag
template<class TypeTag>
struct Vector<TypeTag, TTag::BaseTag> {
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
using type = std::vector<Scalar>;
};
Actual Design
Let's implement the Vector
example using the property system
namespace Properties {
namespace TTag {
struct DoubleTag { using InheritsFrom = std::tuple<BaseTag>; };
} // namespace TTag
// Specialization of the Scalar property for DoubleTag
template<class TypeTag>
struct Scalar<TypeTag, TTag::DoubleTag>
{ using type = double; };
// using the property
using Vector = GetPropType<DoubleTag, Properties::Vector>;
Vector v{1.41421, 1.73205}; // v is a std::vector<double>!
Using type tags as traits classes
struct BaseTag
{
using Scalar = double;
// Important: do not directly use Scalar here as it would
// break the possibility to overwrite it in a child node
template<typename TypeTag>
using Vector = std::vector<
GetPropType<TypeTag, Properties::Scalar>
>;
};
The DuMux property system
The DuMux property system
- Hierarchy / Inheritance
How to use I
Creating new TypeTag nodes
namespace Dumux::Properties::TTag {
struct MyTypeTag {};
struct MyOtherTypeTag
{ using InheritsFrom = std::tuple<MyTypeTag>; }
} // end namespace Dumux::Properties::TTag
How to use II
Creating new property tags (empty, unset properties)
namespace Dumux::Properties {
DUMUX_DEFINE_PROPERTY(Problem)
} // end namespace Dumux::Properties
Usually not needed in user code because all necessary properties are already defined in dumux/common/properties.hh.
How to use III
Setting type properties for a specific type tag MyTypeTag
namespace Properties {
template<class TypeTag>
struct Problem<TypeTag, TTag::MyTypeTag>
{ using type = Dumux::MyProblem<TypeTag>; };
} // end namespace Properties
How to use III (alternative)
Alternatively, using type alias properties for a specific type tag MyTypeTag
namespace Properties::TTag {
struct MyTypeTag {
...
template<class TypeTag>
using Problem = Dumux::MyProblem<TypeTag>;
...
};
} // end namespace Properties
How to use IV
Setting value properties for a specific type tag MyTypeTag
namespace Properties{
template<class TypeTag>
struct EnableBoxInterfaceSolver<TypeTag, TTag::MyTypeTag>
{ static constexpr bool value = true; }
} // end namespace Properties
How to use IV (alternative)
Setting value for a alias property for a specific type tag MyTypeTag
namespace Properties {
struct MyTypeTag {
...
using EnableBoxInterfaceSolver = std::true_type;
...
};
} // end namespace Properties
How to use V
Getting the property type set for a TypeTag
namespace Dumux{
template <class TypeTag>
class Problem
{
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
constexpr auto useIFS = getPropValue<
TypeTag, Properties::EnableBoxInterfaceSolver
>();
};
} // end namespace Dumux
Summary
- "Top-level" classes in DuMux depend on a type tag and use the property system to obtain other types
- Setting a property for your type tag will affect all classes using the same type tag
- Each model defines a set of properties grouped in a type tag
- e.g. TwoP, TwoPTwoC, TwoPNI
- By deriving your type tag from those, your problem inherits all type information needed to set up the model at compile time!
- Example: see Exercise
Alternatives to properties
Should I create new properties
- Not necessary for most problems
- Only when designing a complex class of models (e.g. porous medium flow models)
- When there is many customization points
Alternative 1:
- Use a simple template argument
template <bool useNeummanBoundaryConditions>
class Problem
{
...
if constexpr (useNeummanBoundaryConditions)
...
else
...
};
Alternative 2:
C++ type traits
template<class CouplingManager>
struct SupportsMultithreadedAssembly
: public std::false_type {}
Alternative 2:
C++ type traits
class MyCouplingManager { ... };
template<>
struct SupportsMultithreadedAssembly<MyCouplingManager>
: public std::true_type {}
Alternative 2:
Generic algorithm
template<class CouplingManager>
class Assembler {
void assemble() {
if (SupportsMultithreadedAssembly<CouplingManager>::value)
// assembly multi-threaded
else
// assembly single-threaded
}
};
Exercise
Exercise
- Go to Properties exercise