The DuMux property system is based on the concept of type traits with added inheritance. It is implemented using template metaprogramming.
In the context of the DuMux property system, a property is an arbitrary class which may contain type definitions, values and methods. Just like normal classes, properties can be arranged in hierarchies. In the context of the DuMux property system, nodes of the inheritance hierarchy are called type tags.
It also supports property nesting. Property nesting means that the definition of a property can depend on the value of other properties which may be defined for arbitrary levels of the inheritance hierarchy.
This section gives a high level overview over the property system's design and principle ideas illustrated by self-contained examples.
How to use the property system
All source files which use the property system should include the header file dumux/common/properties.hh. Declaration of type tags and property tags as well as defining properties must be done inside the namespace Dumux::Properties.
Defining type tags
New nodes in the type tag hierarchy can be defined in the Dumux::Properties::TTag namespace using
// Create new type tags
namespace TTag {
struct NewTypeTagName { using InheritsFrom = std::tuple<BaseTagName1, BaseTagName2, ...>; };
} // end namespace TTag
where the InheritsFrom alias is optional. To avoid inconsistencies in the hierarchy, each type tag may be defined only once for a program. If you call Dumux::GetProp the property system will first look for the properties defined in BaseTagName1 in the InheritsFrom list. If a defined property is found this property is returned. If no defined property is found the search will continue in the ancestors of BaseTagName1. If again no defined property is found the search will continue in the second BaseTagName2 in the list, and so on. If no defined property is found at all, a compiler error is triggered.
where here the property PropertyTagName is specialized for the tag TTag::TypeTagName. The body typically contains either the type aliastype, or a static constexpr data member value. However, you can of course write in the body whatever you like.
template<class TypeTag>
struct PropertyTagName<TypeTag, TTag::TypeTagName> { using type = <type>; };
template<class TypeTag>
struct PropertyTagName<TypeTag, TTag::TypeTagName> { staticconstexprbool value = <booleanValue>; };
template<class TypeTag>
struct PropertyTagName<TypeTag, TTag::TypeTagName> { staticconstexprint value = <integerValue>; };
Here is an example including a type tag, property definitions and specializations:
// retrieve the ::type attribute of the 'Scalar' property
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
using Scalar2 = GetProp<TypeTag, Properties::Scalar>::type; // equivalent
};
Nesting property definitions
Inside property definitions there is access to all other properties which are defined somewhere on the type tag hierarchy. The node for which the current property is requested is available via the template argument TypeTag. Inside property class bodies GetPropType can be used to retrieve other properties and create aliases.
Example:
template<class TypeTag>
struct Vector<TypeTag, TTag::MyModelTypeTag>
{
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
using type = std::vector<Scalar>;
};
A self-contained property example
As a concrete example, let us consider some kinds of cars: Compact cars, sedans, trucks, pickups, military tanks and the Hummer-H1 sports utility vehicle. Since all these cars share some characteristics, it makes sense to inherit those from the closest matching car type and only specify the properties which are different. Thus, an inheritance diagram for the car types above might look like outlined in the figure.
Defining type tags, properties, and specializations
Using the DuMux property system, this type hierarchy with inheritance is defined by:
struct Sedan { using InheritsFrom = std::tuple<CompactCar>; };
struct Pickup { using InheritsFrom = std::tuple<Truck, Sedan>; };
struct HummerH1 { using InheritsFrom = std::tuple<Tank, Pickup>; };
} // end namespace Dumux::Properties::TTag
The Figure lists a few property names which make sense for at least one of the nodes. These property names can be defined as follows:
template<class TypeTag, class MyTypeTag>
struct GasUsage { using type = UndefinedProperty; }; // [l/100km]
template<class TypeTag, class MyTypeTag>
struct TopSpeed { using type = UndefinedProperty; }; // [km/h]
template<class TypeTag, class MyTypeTag>
struct NumSeats { using type = UndefinedProperty; }; // []
template<class TypeTag, class MyTypeTag>
struct AutomaticTransmission { using type = UndefinedProperty; }; // true/false
template<class TypeTag, class MyTypeTag>
struct CannonCaliber { using type = UndefinedProperty; }; // [mm]
template<class TypeTag, class MyTypeTag>
struct Payload { using type = UndefinedProperty; }; // [t]
So far, the inheritance hierarchy and the property names are completely separate. What is missing is setting some values for the property names on specific nodes of the inheritance hierarchy. Let us assume the following:
For a compact car, the top speed is the gas usage per 100 km times 30, the number of seats is 5 and the gas usage is 4 l/100km.
A truck is by law limited to 100 km/h top speed, the number of seats is 2, it uses 18 l/100km and has a cargo payload of 35 tons.
A tank exhibits a top speed of 60 km/h, uses 65 l/100km and features a 120 mm diameter cannon
A sedan has a gas usage of 7 l/100km, as well as an automatic transmission. In every other aspect it is like a compact car.
A pick-up truck has a top speed of 120 km/h and a payload of 5 tons. In every other aspect it is like a sedan or a truck but if in doubt, it is more like a truck.
The Hummer-H1 SUV exhibits the same top speed as a pick-up truck. In all other aspects it is similar to a pickup and a tank, but, if in doubt, more like a tank.
Using the DuMux property system, these assumptions are formulated using
The above hierarchy can also be written in a more terse notation using property type aliases. For this to work, the properties must be defined with the DUMUX_DEFINE_PROPERTY macro.
The macro defines two components for each property. The first is the definition of the property. For example for a property Scalar we get
template<class TypeTag, class MyTypeTag> \
struct Scalar { using type = UndefinedProperty; };
The second is the specialization of the PropertyAlias template for the newly defined property. For a property Scalar, we get
template<class ...Args> // specialization for property Scalar
struct PropertyAlias<Scalar<Args...>> {
template <class MyTypeTag> using Alias = typename MyTypeTag::Scalar;
template <class MyTypeTag, class TypeTag> using TemplateAlias = typename MyTypeTag::template Scalar<TypeTag>;
};
The specialization contains the template alias "Alias" that can be used to check if a given type tag "MyTypeTag" has an alias member "Scalar", and a template alias "TemplateAlias" that can be used to check if a given type tag "MyTypeTag" has a template alias Scalar and can be instantiated with a given type tag "TypeTag" (this will be the user- end type tag).