On 22/05/15 15:43, Jørgen Kvalsvik wrote:
On 05/22/2015 01:09 PM, Atgeirr Rasmussen wrote:
Dear OPM community,

We have been considering how to best refactor the (huge) class 
FullyImplicitBlackoilSolver
in such a manner that it can be more easily be extended with new options and 
functionality
without copying the whole class (as is currently done to implement the 
flow_polymer variant
in opm-polymer).

The goal is simple enough: avoid copying of code between the black-oil and 
black-oil-with-polymer
cases, which also will make the code a better base for further extensions. A 
first step has been taken:
separating the NewtonSolver functionality into its own class, leaving the rest 
of the functionality in the
BlackoilModel class.

Further refactoring of this is faced with some nontrivial technical challenges, 
centering on the
State classes. For example, many functions take a State object argument, which 
(in the current approach)
are different between models (black-oil and b.o. with polymer for example). 
Then that function
cannot be virtual (it would need to take the same argument).

There seems to be two main alternatives:


1. Applying the "curiously recurring template pattern" (it is indeed called 
that!). This means
essentially using templates to get what we want, including the ability to 
extend models,
reusing most functions and only modifying the few that we want to be different. 
This will
all be checked at compile time, so illegal combinations of objects (such as 
accidentally
using a State class without a certain member together with a model that 
requires that member)
will not even compile. The drawback is that it involves a little more 
boilerplate code than regular
inheritance: assuming that we want to an extensible black-oil model and a 
polymer model that
extends it we'll have three classes, like this:

template <class Implementation>
class BlackoilModelBase
{
// This is where almost all the functions and data go.
};

class BlackoilModel : public BlackoilModelBase<BlackoilModel>
{
// This contains only type definitions and a constructor.
};

class BlackoilPolymerModel: public BlackoilModelBase<BlackoilPolymerModel>
{
// Contains type definitions and a constructor.
// Contains new functions, as well as new versions of functions from 
BlackoilModelBase.
};

I do think that the boilerplate is manageable, and that people will get by just 
fine by starting their own
extension by copying an existing extension. (The resulting copied code will not 
be that many lines).
This pattern is a fairly common C++ idiom and is used a lot in Dune for example.


2. Using regular inheritance and a generic State class. This means we have a 
single, flexible State
class that can be used for all purposes. It should function a bit like a map<string, 
vector<double>>,
but not be implemented like that. This solution is most similar to how things 
would be done in Matlab,
or other weakly typed languages: any usage errors would not be caught until 
runtime. Each access
to the State will be a little more annoying than before, instead of 
state.saturation() you would need to
call state.get("saturation") and it could possibly fail.

In summary, none of these solutions are perfect, but both are workable. I have 
discussed this with
Andreas, and we agreed to propose alternative 1 as the one we'd like to try out 
first. I would like to
hear your opinions!

Atgeirr
The third option, which is somewhat similar to the first option (which
by the way is much better than the broken common State class idiom) is
to properly split things up into values and transactions and abandon the
idea of a full class that is initialised, then called solve() upon.

This means more flexibility in terms of replacing, plugging in and
extending individual parts, as functions could be either arguments,
static (aka hard coded) or compile-time arguments (hello templates),
with the added benefit that you can replace sub components as containers
with relative ease.

Additionally, this actually opens for concurrency, something we should
strive for in design considering the state we're in now.

And if someone should still prefer having a class manage everything with
init => solve, then this class would now be trivial to write.
this is a good idea, although i dunno what would be involved here in achieving it.

as an example, somewhat on a sidetrack but to show how such an approach can be used in codes. it's a bit hard to make this understandable in email form, but

consider this template definition for a RANS driver:

/*!
  \brief Driver class for RANS simulators.
  \details A RANS simulator is a coupling between a fluid solver
  and a viscosity solver. The coupling may be segregated or semi-implicit.
*/
template<class FluidSolver, class ViscSolver,
         template<class T1, class T2> class Coupling=SIMCoupled>
class SIMRANS : public Coupling<FluidSolver,ViscSolver>

a RANS coupling is a general pattern; you have some fluid (navier-stokes) solver which provides a velocity field to a viscosity solver, which again provides a viscosity field to the fluid solver.

we can then instance this template for various fluid solver (G2, VMS, Chorin etc) and various RANS models (Spalart-Allmaras, K-Epsilon, K-Omega, etc). a loose interface is used, i.e., no interface classes, just expected methods that if missing will inhibit the template instantiation).

likewise we have a FSI template;

/*!
  \brief Driver class for fluid-structure interaction simulations.

  \details This class defines a coupling between a fluid solver,
  a structure solver and a mesh-deformation solver.
*/
template<class FluidSolver, class SolidSolver, class GridMover> class SIMFSI

again, FSI (fluid-structure-interaction) is a coupling between some fluid solver, some solid solver and some mesh update solver. the fluid solver can be a 'bare' navier-stokes solver, or it can be a SIMRANS<..,..> for RANS simulations, or a boussinesq simulator (with or without RANS) for temperature dependent simulations.

using these templates the compiler currently generates some 128 different combinations of solvers.

these would be the components jørgen mentions (though i think he had a more micro-level definition of components in mind).

there is a template for configuring a solver than can be used for all of them

//! \brief Structure for configuring a given simulator
//! \details Your SIM need to specialize this for its type
template<class T>
struct SolverConfigurator {
  //! \brief Configure a simulator
  //! \param sim The simulator to configure
  //! \param props The setup properties for the simulator
  //! \param infile The input file to parse
  int setup(T& sim, const typename T::SetupProps& props, char* infile);
};

which is specialized for each type.

finally, we have a timestepping template

/*!
  \brief Template class for simulator drivers.
  \details This template can be instantiated over any type implementing the
  ISolver interface. It provides a time stepping loop with data output.
*/

template<class T1> class SIMSolver

which drives all our applications, again through a loosely defined interface. this would be the init and solve parts of jørgens suggestion.

such an approach works very nice in our code. the drawback is the "normal" ones with nested templates - patience required when reading error messages.

the use of the loose couplings have one big thing going for it: the couplings are *very* thin and thus it's hard for a programmer to add unwanted hard-deps between components. it surely won't happen by accident cause then the compiler spits in your eye. the thin-ness of the interfaces would help with concurrency issues as well as there is a very clear definition of the shared data between solvers.

the drawback is that it's a bit magic to a new users as there is no clear definition of these interfaces in use. we try to combat this using purely virtual interface classes which are only there for documentation purposes, they are never implemented.

this is possibly just nonsense ramblings, but the intention was to show one way such an approach has been applied.

arnem

_______________________________________________
Opm mailing list
Opm@opm-project.org
http://www.opm-project.org/mailman/listinfo/opm

Reply via email to