opm-simulators/doc/handbook/propertysystem.tex
2012-07-12 21:22:47 +02:00

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: