mirror of
https://github.com/OPM/opm-simulators.git
synced 2024-12-23 07:53:29 -06:00
5fdd83c9be
handbook: some improvements
301 lines
10 KiB
TeX
301 lines
10 KiB
TeX
\chapter{The \Dumux Property System}
|
|
\label{sec:propertysytem}
|
|
|
|
This chapter tries to give high-level understanding of the motivation
|
|
for the \Dumux property system and how to use it. First, an
|
|
introduction to polymorphism is given. After this, the fundamental
|
|
motivation and the ideas behind the \Dumux property system are
|
|
highlighted and the implementation is outlined from a high-level
|
|
perspective. The chapter concludes with a simple example of how to use
|
|
the \Dumux property system.
|
|
|
|
\section{Polymorphism}
|
|
|
|
In object oriented programming, it often happens that some
|
|
functionality make sense for all classes in a hierarchy, but what
|
|
actually need to be \textit{done} can be quite different. This
|
|
observation gives rise to \textit{polymorphism}. In polymorphism, a
|
|
call to an object's method \textit{means} the same thing, but how this
|
|
method is \textit{implemented} is case specific\footnote{This
|
|
\textit{poly} of polymorphism: There are multiple ways to achieve
|
|
the same goal.}.
|
|
|
|
In C++, there are two common approaches to polymorphism: The
|
|
traditional -- i.e. non-template programming -- dynamic polymorphism,
|
|
and static polymorphism which is made possible by template
|
|
programming.
|
|
|
|
\subsection*{Dynamic Polymorphism}
|
|
|
|
To utilize \textit{dynamic polymorphism} in C++, the polymorphic
|
|
methods are marked with the \texttt{virtual} keyword in the base
|
|
class. Internally, the compiler realizes dynamic polymorphism by
|
|
storing a pointer to a so-called \texttt{vtable} within each object of
|
|
polymorphic classes. The \texttt{vtable} itself stores the entry point
|
|
of each method which is declared virtual. If such a method is called
|
|
on an object, the compiler generates code which retrieves the method's
|
|
address from the object's \texttt{vtable} and then calls it. This
|
|
explains why this mechanism is called \textbf{dynamic} polymorphism:
|
|
the methods which are actually called are dynamically determined at
|
|
run time.
|
|
|
|
\begin{example}
|
|
\label{example:DynPoly}
|
|
A class called \texttt{Car} could feature the methods
|
|
\texttt{gasUsage} which by default corrosponds to the current $CO_2$
|
|
emission goal of the European Union but can be overwritten by the
|
|
classes representing actual cars. Also, a method called
|
|
\texttt{fuelTankSize} makes sense for all cars, but since there is
|
|
no useful default, its \texttt{vtable} entry is set to $0$ in the
|
|
base class which tells the compiler that this method must
|
|
mandatorily be specified by all derived classes. Finally the method
|
|
\texttt{range} may calculate the expected remaining kilometers the
|
|
car can drive given a fill level of the fuel tank. Since the
|
|
\texttt{range} method can retrieve information it needs, it does not
|
|
need to be polymorphic.
|
|
\begin{verbatim}
|
|
// The base class
|
|
class Car
|
|
{public:
|
|
virtual double gasUsage()
|
|
{ return 4.5; };
|
|
virtual double fuelTankSize() = 0;
|
|
|
|
double range(double fuelTankFillLevel)
|
|
{ return 100*fuelTankFillLevel*fuelTankSize()/gasUsage(); }
|
|
}
|
|
\end{verbatim}
|
|
|
|
Actual car models can now derived from the base class:
|
|
\begin{verbatim}
|
|
// A Mercedes S-class car
|
|
class S : public Car
|
|
{public:
|
|
virtual double gasUsage() { return 9.0; };
|
|
virtual double fuelTankSize() { return 65.0; };
|
|
}
|
|
|
|
// A VW Lupo
|
|
class Lupo : public Car
|
|
{public:
|
|
virtual double gasUsage() { return 2.99; };
|
|
virtual double fuelTankSize() { return 30.0; };
|
|
}
|
|
\end{verbatim}
|
|
|
|
The \text{range} method called on the base class yields correct result
|
|
for any car type:
|
|
\begin{verbatim}
|
|
void printMaxRange(Car &car)
|
|
{ std::cout << "Maximum Range: " << car.range(1.00) << "\n"; }
|
|
|
|
int main()
|
|
{
|
|
Lupo lupo;
|
|
S s;
|
|
std::cout << "VW Lupo:"
|
|
std::cout << "Median range: " << lupo.range(0.50) << "\n";
|
|
printMaxRange(lupo);
|
|
std::cout << "Mercedes S-Class:"
|
|
std::cout << "Median range: " << s.range(0.50) << "\n";
|
|
printMaxRange(s);
|
|
return 0;
|
|
}
|
|
\end{verbatim}
|
|
|
|
For both types of cars, \texttt{Lupo} and \texttt{S} the
|
|
\texttt{printMaxRange} function works as expected, yielding
|
|
$1003.3\;\mathrm{km}$ for the Lupo and $722.2\;\mathrm{km}$ for the
|
|
S-Class.
|
|
\end{example}
|
|
|
|
\begin{exc}
|
|
What happens if \dots
|
|
\begin{itemize}
|
|
\item \dots the \texttt{gasUsage} method is removed from the \texttt{Lupo} class?
|
|
\item \dots the \texttt{virtual} qualifier is removed in front of the
|
|
\texttt{gasUsage} method in the base class?
|
|
\item \dots the \texttt{fuelTankSize} method is removed from the \texttt{Lupo} class?
|
|
\item \dots the \texttt{range} method in the \texttt{S} class is
|
|
overwritten?
|
|
\end{itemize}
|
|
\end{exc}
|
|
|
|
\subsection*{Static Polymorphism}
|
|
|
|
Static polymorphism has a few disadvantages, probably the most
|
|
relevant in the context of \Dumux is that the compiler can not see
|
|
``inside'' the called methods and thus cannot properly optimize
|
|
them. For example modern C++ compilers 'inline' short methods, that is
|
|
they copy the body the function body to where it is called which saves
|
|
a few instructions as well as allows further optimizations across the
|
|
function call. Inlining and other cross call optimizations are next to
|
|
impossible in conjunction with dynamic polymorphism, since these
|
|
techniques need to be done by the compiler (i.e. at compile time)
|
|
while the actual code which gets called is only determined at run time
|
|
for virtual methods. To overcome this issue, template programming
|
|
allows a compile time polymorphism. This scheme works by supplying an
|
|
additional template parameter to the base class which specifies the
|
|
type of the derived class. Whenever the base class needs to call back
|
|
at the derived class, the memory of the current object is
|
|
reinterpreted as a derived object and the method is then called. This
|
|
scheme gives the C++ compiler complete transparency of the code
|
|
executed and thus opens for much better optimization
|
|
oportunities. Also, since this mechanism completely happens at compile
|
|
time, it is called ``static polymorphism'' because the called method
|
|
cannot be changed dynamically at runtime.
|
|
\begin{example}
|
|
Using static polymorphism, the base class of example \ref{example:DynPoly}
|
|
can be written as
|
|
\begin{verbatim}
|
|
// The base class. The 'Imp' template parameter is the
|
|
// type of the implementation, i.e. the derived class
|
|
template <class Imp>
|
|
class Car
|
|
{public:
|
|
double gasUsage()
|
|
{ return 4.5; };
|
|
double fuelTankSize()
|
|
{ throw "The derived class needs to implement the fuelTankSize() method" };
|
|
|
|
double range(double fuelTankFillLevel)
|
|
{ return 100*fuelTankFillLevel*asImp_().fuelTankSize()/asImp_().gasUsage(); }
|
|
|
|
protected:
|
|
// reinterpret the 'this' object as an object of type 'Imp'
|
|
Imp &asImp_() { return *static_cast<Imp*>(this); }
|
|
// version for constant 'this' objects
|
|
const Imp &asImp_() const { return *static_cast<const Impl*>(this); }
|
|
}
|
|
\end{verbatim}
|
|
(Note the \texttt{asImp\_()} calls in the \texttt{range} method.)
|
|
|
|
The derived classes can now be defined like this
|
|
\begin{verbatim}
|
|
// A Mercedes S-class car
|
|
class S : public Car<S>
|
|
{public:
|
|
double gasUsage() { return 9.0; };
|
|
double fuelTankSize() { return 65.0; };
|
|
}
|
|
|
|
// A VW Lupo
|
|
class Lupo : public Car<Lupo>
|
|
{public:
|
|
double gasUsage() { return 2.99; };
|
|
double fuelTankSize() { return 30.0; };
|
|
}
|
|
\end{verbatim}
|
|
\end{example}
|
|
|
|
Analogously to example \ref{example:DynPoly}, the two kinds of cars
|
|
can be used generically within (template) functions:
|
|
\begin{verbatim}
|
|
template <class CarType>
|
|
void printMaxRange(CarType &car)
|
|
{ std::cout << "Maximum Range: " << car.range(1.00) << "\n"; }
|
|
|
|
int main()
|
|
{
|
|
Lupo lupo;
|
|
S s;
|
|
std::cout << "VW Lupo:"
|
|
std::cout << "Median range: " << lupo.range(0.50) << "\n";
|
|
printMaxRange(lupo);
|
|
std::cout << "Mercedes S-Class:"
|
|
std::cout << "Median range: " << s.range(0.50) << "\n";
|
|
printMaxRange(s);
|
|
return 0;
|
|
}
|
|
\end{verbatim}
|
|
|
|
\textbf{TODO: Exercise}
|
|
|
|
\subsection*{Explosion of Template Arguments}
|
|
|
|
A major drawback with template programming in general and with static
|
|
polymorphism in particular is that the template arguments required for
|
|
the base class tend to explode quickly. This is due to the fact that
|
|
often template arguments are often used as arguments for other
|
|
template classes. To demonstrate this point consider the local
|
|
jacobian class of the two-phase, two-component box model before the
|
|
\Dumux property system was introduced:
|
|
\begin{verbatim}
|
|
template<class ProblemT, class BoxTraitsT, class TwoPTwoCTraitsT>
|
|
class TwoPTwoCBoxJacobian
|
|
: public TwoPTwoCBoxJacobianBase<
|
|
ProblemT,
|
|
BoxTraitsT,
|
|
TwoPTwoCTraitsT,
|
|
TwoPTwoCElementData<TwoPTwoCTraitsT,ProblemT>,
|
|
TwoPTwoCVertexData<TwoPTwoCTraitsT,ProblemT>,
|
|
TwoPTwoCFluxData<TwoPTwoCTraitsT,
|
|
ProblemT,
|
|
TwoPTwoCVertexData<TwoPTwoCTraitsT,
|
|
ProblemT> >,
|
|
TwoPTwoCBoxJacobian<ProblemT,
|
|
BoxTraitsT,
|
|
TwoPTwoCTraitsT> >
|
|
{
|
|
// ...
|
|
}
|
|
\end{verbatim}
|
|
|
|
|
|
% \begin{frame}[fragile]
|
|
% \frametitle{Nested template arguments}
|
|
|
|
% One problem of static Polymorphism:
|
|
% \begin{itemize}
|
|
% \item Template arguments often also require template parameters
|
|
% \item ``Implementation'' arguments are especially nasty:
|
|
% \begin{itemize}
|
|
% \item Assume a base class Base<A, B, Imp>
|
|
% \item Assume a class Derived<B> and that A is C< Derived<B> >
|
|
% \item To specify Derived:
|
|
% \end{itemize}
|
|
% \end{itemize}
|
|
% {\scriptsize
|
|
% \begin{verbatim}
|
|
% template <class B>
|
|
% class Derived : public Base< C<Derived<B> >, B, Derived<B> >
|
|
% \end{verbatim}
|
|
% }
|
|
% \end{frame}
|
|
|
|
% \begin{frame}[fragile]
|
|
% \frametitle{A real-world example}
|
|
|
|
% {\scriptsize
|
|
% }
|
|
% \end{frame}
|
|
|
|
% \begin{frame}[fragile]
|
|
% \frametitle{Traits}
|
|
|
|
% A possible solution is to specify a class which specifies the
|
|
% template parameters as type definitions (often called a Traits
|
|
% class): {\scriptsize
|
|
% \begin{verbatim}
|
|
% class Traits
|
|
% { public:
|
|
% typedef int A;
|
|
% typedef C<A> B;
|
|
% };
|
|
|
|
% template <class Traits, class Implementation>
|
|
% class Base {};
|
|
|
|
% template <class Traits>
|
|
% class Derived : public class Base<Traits, Derived<Traits>
|
|
% {};
|
|
% \end{verbatim}
|
|
% }
|
|
|
|
|
|
|
|
%%% Local Variables:
|
|
%%% mode: latex
|
|
%%% TeX-master: "dumux-handbook"
|
|
%%% End:
|