The DuMuX property system

Flexible compile-time parameters

Parameters vs. properties

Parameters vs. properties

  • Parameters are set at run-time

    A default value may be used if the user does not provide one at run-time

  • Properties are known and set at compile-time

    Can be used e.g. as template parameters (types or values with constexpr specifier); No run-time penalty, enable compiler to optimize

Template parameters

  • C++ supports generic programming via templates
    • e.g. classes defined in terms of other types
    • concrete versions of templates are stamped out upon compilation
  • 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 parameters

An example - std::vector

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`
};

Too many template 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
>;

DGGO2 dggo2(gfs, cd, gfs, cf, lop, mbe);

Traits classes

A usual way to group template parameters

struct MyGridOperatorTraits
{
    using FromGFS = ...;
    // ...
};

using GOF0 = Dune::PDELab::GridOperator<MyGridOperatorTraits>;

Inheriting from traits classes

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};

Type traits

Based on template specialization

// 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; };

Type traits

A usage example

// expecting Container to export value_type
template<typename Container>
void someFunction(const Container& c) {
    using V = typename Container::value_type;
    // do something with V
    // ...
}
// using type traits
template<typename Container>
void someFunction(const Container& c) {
    using V = typename ValueType<Container>::type;
    // do something with V
    // ...
}

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
{
    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> {
    private: using Scalar = GetPropType<TypeTag, Properties::Scalar>;
    public: 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

  • Extension \(\leftrightarrow\) tree of so called TypeTag nodes
  • Each TypeTag is associated with Properties

The DuMux property system

  • Hierarchy / Inheritance
    • TypeTags can inherit properties from other TypeTags
    • Properties with the same name are overwritten

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) \(\leftrightarrow\) Property names are unique!

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