--- 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` ```cpp // 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 ```cpp 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 ```cpp 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. ```cpp // 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 ```cpp 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? ```cpp // 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 ```cpp template <class Traits> class TwoPVolumeVariables { ... using FluidSystem = typename Traits::FluidSystem ... ``` Usage: these VolumeVariables will work for various FluidSystems: ```cpp Scalar mu = FluidSystem::viscosity(fluidState, paramCache, phaseIdx); ``` ## Challenge: Inheritance Inheritance may lead to unexpected results ```cpp 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 ```cpp // 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 ```cpp 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 ```cpp 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 ```cpp 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; }; ``` ```cpp // 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 ```cpp 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 DuMu<sup>x</sup> property system - Extension $\leftrightarrow$ tree of so called TypeTag nodes - Each TypeTag is associated with Properties  ## The DuMu<sup>x</sup> property system - Hierarchy / Inheritance - TypeTags can inherit properties from other TypeTags - Properties with the same name are overwritten  ## How to use I Creating new <span style="color:blue">TypeTag</span> nodes ```cpp namespace Dumux::Properties::TTag { struct MyTypeTag {}; struct MyOtherTypeTag { using InheritsFrom = std::tuple<MyTypeTag>; } } // end namespace Dumux::Properties::TTag ``` ## How to use II Creating new <span style="color:blue">property tags</span> (empty, unset properties) $\leftrightarrow$ Property names are unique! ```cpp 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](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux/-/blob/master/dumux/common/properties.hh). ## How to use III Setting **type** properties for a specific type tag `MyTypeTag` ```cpp 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` ```cpp 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` ```cpp 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` ```cpp namespace Properties { struct MyTypeTag { ... using EnableBoxInterfaceSolver = std::true_type; ... }; } // end namespace Properties ``` ## How to use V Getting the property type set for a `TypeTag` ```cpp 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 DuMu<sup>x</sup> 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. <span style="color:blue">TwoP, TwoPTwoC, TwoPNI</span> - 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 ```cpp template <bool useNeummanBoundaryConditions> class Problem { ... if constexpr (useNeummanBoundaryConditions) ... else ... }; ``` ## Alternative 2: C++ type traits ```cpp template<class CouplingManager> struct SupportsMultithreadedAssembly : public std::false_type {} ``` ## Alternative 2: C++ type traits ```cpp class MyCouplingManager { ... }; template<> struct SupportsMultithreadedAssembly<MyCouplingManager> : public std::true_type {} ``` ## Alternative 2: Generic algorithm ```cpp template<class CouplingManager> class Assembler { void assemble() { if (SupportsMultithreadedAssembly<CouplingManager>::value) // assembly multi-threaded else // assembly single-threaded } }; ``` # Exercise ## Exercise * Go to [Properties exercise](https://git.iws.uni-stuttgart.de/dumux-repositories/dumux-course/-/tree/master/exercises/exercise-properties)