Mass balance equations for two fluid phases: \[ \begin{aligned} \frac{\partial \left(\phi \varrho_\alpha S_\alpha \right)}{\partial t} + \nabla \cdot \left(\varrho_\alpha \boldsymbol{v}_\alpha \right) - q_\alpha = 0, \quad \alpha \in \lbrace w, n \rbrace. \end{aligned} \]
Momentum balance equations (multiphase-phase Darcy’s law): \[ \begin{aligned} \boldsymbol{v}_\alpha = -\frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} \left(\nabla\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right), \quad \alpha \in \lbrace w, n \rbrace. \end{aligned} \]
\[ \begin{aligned} \frac{\partial \left(\phi \varrho_\alpha S_\alpha \right)}{\partial t} - \nabla \cdot \left( \varrho_\alpha \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} \left(\nabla\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right) \right) - q_\alpha = 0 \end{aligned} \]
\[ \begin{aligned} \frac{\partial \left(\phi \varrho_\alpha S_\alpha \right)}{\partial t} - \nabla \cdot \left( \varrho_\alpha \frac{k_{r\alpha}}{\mu_\alpha} \mathbf{K} \left(\nabla\, p_\alpha - \varrho_{\alpha} \mathbf{g} \right) \right) - q_\alpha = 0 \end{aligned} \]
The source code for a typical DuMux application consists of:
properties.hh
)problem.hh
) and often a
spatial parameter class definition
(e.g. spatialparams.hh
)params.input
)main.cc
)CMakeLists.txt
(CMake build system configuration)“Compile-time configuration”
“Tell DuMux what type of classes to use”
Details on properties in Part II: Property system
*properties*.hh
Create tag, choose model and discretization scheme:
Specialize the Grid
property for tag
TTag::Injection2pCC
to set the grid type:
Specialize the Problem
property to set the problem
type:
namespace Dumux::Properties {
template<class TypeTag>
struct Problem<TypeTag, TTag::Injection2pCC>
{ using type = InjectionProblem2P<TypeTag>; };
} // end namespace Dumux::Properties
The problem class InjectionProblem2P
is discussed in one
of the following sections.
Specialize the SpatialParams
property to set the spatial
parameter type:
namespace Dumux::Properties {
template<class TypeTag>
struct SpatialParams<TypeTag, TTag::Injection2pCC>
{
using GridGeometry
= GetPropType<TypeTag, Properties::GridGeometry>;
using Scalar
= GetPropType<TypeTag, Properties::Scalar>;
using type = InjectionSpatialParams<GridGeometry, Scalar>;
};
} // end namespace Dumux::Properties
Specialize the FluidSystem
property to set the fluid
system type:
namespace Dumux::Properties {
template<class TypeTag>
struct FluidSystem<TypeTag, TTag::Injection2pCC>
{
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
using Policy = FluidSystems::H2ON2DefaultPolicy<
/*fastButSimplifiedRelations=*/true>;
using type = FluidSystems::H2ON2<Scalar, Policy>;
};
} // end namespace Dumux::Properties
A “problem” class in DuMux implements a specific scenario:
*problem*.hh
template<class TypeTag>
class InjectionProblem2P : public PorousMediumFlowProblem<TypeTag>
{
// - boundary conditions
// - initial conditions
// - source terms
// - scenario name (for output)
}
Inherit from base class PorousMediumFlowProblem
, only
override scenario-specific functions (static polymorphism).
The boundary condition types:
BoundaryTypes boundaryTypesAtPos(const GlobalPosition &globalPos) const
{
BoundaryTypes bcTypes;
// Use Dirichlet boundary conditions on the left
if (globalPos[0] < eps_)
bcTypes.setAllDirichlet();
// Use Neumann boundary conditions on the rest of the boundary
else
bcTypes.setAllNeumann();
return bcTypes;
}
Dirichlet boundary condition values (only called on Dirichlet boundaries)
PrimaryVariables dirichletAtPos(const GlobalPosition& globalPos) const
{ return initialAtPos(globalPos); }
PrimaryVariables
is an array of primary variables (here,
the size of the array is 2).
More general Dirichlet boundary functions:
Neumann boundary condition values / boundary fluxes (only called on Neumann boundaries)
NumEqVector neumannAtPos(const GlobalPosition& globalPos) const
{
NumEqVector values(0.0);
if (injectionActive() && onInjectionBoundary(globalPos))
{
values[Indices::conti0EqIdx + FluidSystem::N2Idx] = -1e-4;
values[Indices::conti0EqIdx + FluidSystem::H2OIdx] = 0.0;
}
return values;
}
NumEqVector
is an array of equations (here, the size of
the array is 2).
More general Neumann boundary function:
Initial conditions:
PrimaryVariables initialAtPos(const GlobalPosition& globalPos) const
{
PrimaryVariables values(0.0);
const Scalar densityW
= FluidSystem::H2O::liquidDensity(temperature(), 1.0e5);
const Scalar pw
= 1.0e5 - densityW*this->gravity()[dimWorld-1]
*(aquiferDepth_ - globalPos[dimWorld-1]);
values[Indices::pressureIdx] = pw;
values[Indices::saturationIdx] = 0.0;
return values;
}
Source/sink terms:
More general source/sink function:
*spatialparams*.hh
template<class FVGridGeometry, class Scalar>
class InjectionSpatialParams
: public FVPorousMediumFlowSpatialParamsMP<
FVGridGeometry, Scalar,
InjectionSpatialParams<FVGridGeometry, Scalar>
> {
// parameters that are typically position-dependent
// e.g. porosity, permeability
};
Inherit from FVPorousMediumFlowSpatialParamsMP
where
FV
: finite volumes, MP
: multi-phase flow.
A function returning the instrinsic permeability \(K\):
A function returning the porosity:
A function returning a parameterized constitutive law describing fluid-matrix interactions (\(p_c(S_w)\), \(k_r(S_w)\)):
Set the (constant) temperature field in the domain:
Dune INI syntax ([Group]
and Key = Value
pairs)
params.input
See Part I: Runtime parameters for details.
test_name.cc
or
main.cc
)2pmain.cc
and 2pnimain.cc
main
function (mandatory in C/C++
programs)Calling Dumux::initialize
is mandatory at the beginning
of each DuMux program to make sure the environment is
correctly set up.
Parse runtime parameters:
See Part I: Runtime parameters for details.
Define an alias for the test problem type tag
The tag (tag alias) is used to extract types and values via the property system (details on properties in Part II: Property system).
Create the computational grid:
// create a grid (take parameters from input file)
GridManager<GetPropType<TypeTag, Properties::Grid>> gridManager;
gridManager.init();
// obtain the grid and a grid view
const auto& grid = gridManager.grid();
const auto& leafGridView = grid.leafGridView();
More details on the grid in Part I: Grid.
GridGeometry
and Problem
instantiation:
// create the finite volume grid geometry
// (represents the chosen discretization scheme)
using GridGeometry = GetPropType<TypeTag, Properties::GridGeometry>;
auto gridGeometry = std::make_shared<GridGeometry>(leafGridView);
// the problem (initial and boundary conditions)
using Problem = GetPropType<TypeTag, Properties::Problem>;
auto problem = std::make_shared<Problem>(gridGeometry);
Initial solution and secondary variables:
// the solution vector
using SolutionVector
= GetPropType<TypeTag, Properties::SolutionVector>;
SolutionVector x(gridGeometry->numDofs());
// the grid variables
using GridVariables = GetPropType<TypeTag, Properties::GridVariables>;
auto gridVariables
= std::make_shared<GridVariables>(problem, gridGeometry);
gridVariables->init(x);
Setting up VTK output:
// initialize the vtk output module
VtkOutputModule<GridVariables, SolutionVector>
vtkWriter(*gridVariables, x, problem->name());
// optionally add a velocity post-processor
using VelocityOutput
= GetPropType<TypeTag, Properties::VelocityOutput>;
vtkWriter.addVelocityOutput(
std::make_shared<VelocityOutput>(*gridVariables));
// add input/output defaults for the chosen model
using IOFields = GetPropType<TypeTag, Properties::IOFields>;
IOFields::initOutputModule(vtkWriter);
Some typical main function structures:
Assembling the system for a steady-state linear problem:
// the assembler for stationary problems
using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>;
auto assembler = std::make_shared<Assembler>(
problem, gridGeometry, gridVariables);
// the discretization matrices for stationary linear problems
using JacobianMatrix
= GetPropType<TypeTag, Properties::JacobianMatrix>;
auto A = std::make_shared<JacobianMatrix>();
auto r = std::make_shared<SolutionVector>();
assembler->setLinearSystem(A, r);
assembler->assembleJacobianAndResidual(x);
and solve it using an iterative method:
using LinearSolver = ILUBiCGSTABIstlSolver<
LinearSolverTraits<GridGeometry>,
LinearAlgebraTraitsFromAssembler<Assembler>>;
auto linearSolver = std::make_shared<LinearSolver>(
gridGeometry->gridView(), gridGeometry->dofMapper());
(*r) *= -1.0; // solve Ax = -r to save update and copy
linearSolver->solve(*A, x, *r);
Simplified code for steady-state linear problem using
Dumux::LinearPDESolver
For steady-state non-linear problems, use
Dumux::NewtonSolver
:
For time-dependent problems, pass a TimeLoop
instance
and a solution vector carrying the solution on the previous/initial time
level to the assembler:
SolutionVector xOld = x;
auto timeLoop = std::make_shared<TimeLoop<Scalar>(0.0, dt, tEnd);
timeLoop->setMaxTimeStepSize(maxDt);
using Assembler = FVAssembler<TypeTag, DiffMethod::numeric>;
auto assembler = std::make_shared<Assembler>(
problem, gridGeometry, gridVariables,
timeLoop, xOld // <-- new arguments
);
// assemble linear/non-linear problem as before
// ...
The skeleton of a time loop:
Solving a time step:
Advancing the time loop to the next step:
// advance to the time loop to the next step
timeLoop->advanceTimeStep();
// report statistics of this time step
timeLoop->reportTimeStep();
// set new dt as suggested by the newton solver
timeLoop->setTimeStepSize(
nonLinearSolver.suggestTimeStepSize(timeLoop->timeStepSize())
);
} while (!timeLoop->finished());
// print final report
timeLoop->finalize(leafGridView.comm());
Step 3 is usually skipped by DuMux developers. But you can of course “install” Dune modules and DuMux.
CMakeLists.txt
, which is written in the CMake languageCMakeLists.txt
is
executedadd_subdirectory(...)
functiondunecontrol
scriptconfigure
: configure libraries, variables, paths,
filesmake all
: build librariesall
: includes configure
and
make all
dunecontrol
script--opts=<optionfile.opts>
specify e.g. compiler
flags; DuMux provides dumux/cmake.opts
.--build-dir=<build directory>
specify path for
out-of-source build; Default: every module contains its own build
directory called build-cmake/
--only <module>
only apply command to a specific
module/s (comma-separated list)You may have to reconfigure (rerun dunecontrol
) whenever
a dependency changes or a Dune library is updated. CMake caches. To
reconfigure, you may need to remove the cache first:
removes the CMake
cache files from all Dune modules.
(Use --only <module>
to only remove caches of
specific modules.)
add_subdirectory
for recursively adding
subdirectoriesCMakeLists.txt
(can be
empty)add_executable(<name> source1 [source2 ...])
dumux_add_test
which also add a
test executable to the test suitedune_symlink_to_source_files
CMakeLists.txt
for an example application: create a test
target exercise_basic_2p
by defining a name and source
file:
Another example: add extra compile definitions and commands (typical for DuMux regression tests):
dumux_add_test(
NAME test_2p_incompressible_box
SOURCES test_2p_fv.cc
COMPILE_DEFINITIONS TYPETAG=TwoPIncompressibleBox
COMMAND ${CMAKE_SOURCE_DIR}/bin/testing/runtest.py
CMD_ARGS --script fuzzy
--files ${CMAKE_SOURCE_DIR}/test/references/lensbox-reference.vtu
${CMAKE_CURRENT_BINARY_DIR}/2p_box-00007.vtu
--command "${CMAKE_CURRENT_BINARY_DIR}/test_2p_incompressible_box
test_2p.input -Problem.Name 2p_box"
)
Extra: Create linked parameter file in build-cmake
folder, separately add executable and set compile definitions for an
executable:
Useful resources:
Run with:
Partitioned VTK output files:
*.vtu
/*.vtp
file*.pvtu
/*.pvtp
files for each
time step*.pvd
groups all
*.pvtu
/*.pvtp
files into a time seriesDumux::FVAssembler
by default to assemble
the residual and stiffness matrixCMAKE
during configuration and
stored in DUMUX_MULTITHREADING_BACKEND
OpenMP
, TBB
, C++
parallel algorithms, and Kokkos
Restrict the number of threads
Number of threads can be restricted via the environment variable
DUMUX_NUM_THREADS
Multi-threaded assembly
Control use of multi-threading during assembly in input file:
Defaults to true
.
Basic application setup:
The main function: