version 3.10-dev
Python bindings

The DuMux Python bindings have two objectives

  • making it possible to use DuMux from Python
  • use Python code in DuMux

The current Python bindings are far from complete and only cover a a small subset of DuMux functionality (see test/python for some example Python scripts). The binding are experimental until further notice which means the API might undergo unannounced changes breaking backwards compatibility. Track changes by regularly checking for current merge requests and newly merged Dumux commits on GitLab. Python bindings require Python 3. Feedback over the mailing list or the issue tracker is highly welcome.

Installation of Python bindings

Using Python bindings require at least Dune core modules version 2.9 and at least DuMux version 3.7. Python bindings are configured automatically with dunecontrol if Python is found on your system and DuMux and upstream modules are built using the default options in cmake.opts. Nothing special needs to be done if you follow the installation instructions.

Running a test

After configuring and installing DuMux with dunecontrol (or the installscript) you are ready to run the Python tests.

From the top-level dumux folder, run your first DuMux Python test using the helper script

./build-cmake/run-in-dune-env dumux/test/python/test_gridgeometry.py

See below what this script does and how you can get better control over what is exactly happening.

The Python bindings are based on just-in-time compilation of C++ code, so the first execution might take considerably longer than subsequent executions. In particular, compiling the Dune grid interface may take a few minutes.

Running all tests

To check that everything works correctly, you can also run all currently existing DuMux Python tests with

cd build-cmake
ctest -L python

Setup with a Python virtual environment

When configuring dune-common, by default, an internal Python virtual environment setup is created in the build folder of dune-common for the Python bindings. Hence, in the description above, you were already running the script in a virtual environment. In this virtual environment, all Dune module Python bindings are automatically installed in editable mode (symlinked) when running dunecontrol. After running dunecontrol the internal virtual environment can be activated with

source ./dune-common/build-cmake/dune-env/bin/activate

Then you can run Python tests without the helper script like this

python3 dumux/test/python/test_gridgeometry.py

To have better control of the virtual environment, you can create and activate a new virtual environment yourself before running dunecontrol. The CMake build system of dune-common detects the activated virtual environment and installs Python bindings into that virtual environment (again in editable mode (symlinked)). This looks like this:

python3 -m venv venv
source ./venv/bin/activate
./dune-common/bin/bexec rm -r CMakeFiles CMakeCache.txt
./dune-common/bin/dunecontrol --opts=dumux/cmake.opts all

This installs everything necessary into your virtual environment venv. (You may choose any other name of course.) With activated virtual environment, you can run Python script using Python bindings like this

python3 dumux/test/python/test_gridgeometry.py

DuMux Python example

As an example of how to use the Python bindings have a look at this Python script solving a problem with one-phase flow in porous media.

#!/usr/bin/env python3
# SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
# SPDX-License-Identifier: GPL-3.0-or-later
import sys
from dune.grid import structuredGrid
from dune.istl import blockVector, CGSolver, SeqSSOR
from dumux.common import initParameters, printParameters, getParam
from dumux.common import BoundaryTypes, Model, Property
from dumux.discretization import GridGeometry, GridVariables
from dumux.assembly import FVAssembler
from dumux.porousmediumflow import (
PorousMediumFlowProblem,
PorousMediumFlowVelocityOutput,
FVSpatialParamsOneP,
)
from dumux.material import FluidSystem, Component
from dumux.io import VtkOutputModule
# Initialize parameters
initParameters(
argv=sys.argv,
params={
"Problem.EnableGravity": True,
"Vtk.AddVelocity": False,
"Assembly.NumericDifference.PriVarMagnitude": 1e5,
},
)
discMethod = getParam("DiscMethod", default="box")
if discMethod not in ["box", "cctpfa"]:
raise NotImplementedError(discMethod + " not supported yet. Use cctpfa or box")
diffMethod = getParam("DiffMethod", default="numeric")
if diffMethod not in ["analytic", "numeric"]:
raise NotImplementedError(diffMethod + " must be analytic or numeric")
# Set up the grid and the grid geometry
gridView = structuredGrid([0, 0], [1, 1], [10, 10])
gridGeometry = GridGeometry(gridView=gridView, discMethod=discMethod)
# Set up the model
model = Model(inheritsFrom=["OneP"], gridGeometry=gridGeometry)
# Tell model to use a particular local residual type
model["LocalResidual"] = Property.fromCppType(
"OnePIncompressibleLocalResidual<TypeTag>",
cppIncludes=["dumux/porousmediumflow/1p/incompressiblelocalresidual.hh"],
)
# Setup the fluid system
h20 = Component("SimpleH2O")
onePLiquid = FluidSystem("OnePLiquid", component=h20, scalar=model["Scalar"])
model["FluidSystem"] = Property.fromInstance(onePLiquid)
# Define the spatial parameters
@FVSpatialParamsOneP(gridGeometry=gridGeometry)
class SpatialParams:
dimWorld = gridGeometry.gridView.dimWorld
lensLowerLeft = [0.2, 0.2]
lensUpperRight = [0.8, 0.8]
def isLens(self, globalPos):
eps = 1.5e-7
for i in range(self.dimWorld):
if (globalPos[i] < self.lensLowerLeft[i] + eps) or (
globalPos[i] > self.lensUpperRight[i] - eps
):
return False
return True
def permeability(self, element, scv, elemSol):
globalPos = scv.dofPosition
# permeability can be either given
# as scalar or tensor
if self.isLens(globalPos):
return [[1e-12, 0], [0, 1e-12]]
return 1e-10
def porosityAtPos(self, globalPos):
return 0.4
spatialParams = SpatialParams()
model["SpatialParams"] = Property.fromInstance(spatialParams)
# Define the problem
@PorousMediumFlowProblem(gridGeometry=gridGeometry, spatialParams=spatialParams)
class Problem:
numEq = 1
def boundaryTypesAtPos(self, globalPos):
bTypes = BoundaryTypes(self.numEq)
eps = 1e-6
if globalPos[1] < eps or globalPos[1] > gridGeometry.bBoxMax[1] - eps:
bTypes.setDirichlet()
else:
bTypes.setNeumann()
return bTypes
def dirichletAtPos(self, globalPos):
dp_dy_ = -1.0e5
return 1.0e5 + dp_dy_ * (globalPos[1] - gridGeometry.bBoxMax[1])
def sourceAtPos(self, globalPos):
return 0.0
def name(self):
return "python_problem"
def paramGroup(self):
return ""
def neumann(self, element, fvGeometry, scvf):
return 0
# and set it as a model property
problem = Problem()
model["Problem"] = Property.fromInstance(problem)
# Initialize the GridVariables and the Assembler
sol = blockVector(gridGeometry.numDofs)
gridVars = GridVariables(problem=problem, model=model)
gridVars.init(sol)
assembler = FVAssembler(problem=problem, gridVariables=gridVars, model=model, diffMethod=diffMethod)
print("num dofs: ", assembler.numDofs)
# Assemble the Jacobian and the residual
assembler.assembleJacobianAndResidual(sol)
print("Assembly done")
res = assembler.residual
jac = assembler.jacobian
# Solve the linear system
S = CGSolver(jac.asLinearOperator(), SeqSSOR(jac), 1e-10)
res *= -1
_, _, converged, _, _ = S(sol, res)
if not converged:
raise Exception("CGSolver has not converged")
# Write to vtk
testName = "test_1p_" + discMethod + "_" + diffMethod
output = VtkOutputModule(gridVariables=gridVars, solutionVector=sol, name=testName)
velocityoutput = PorousMediumFlowVelocityOutput(gridVariables=gridVars)
output.addVelocityOutput(velocityoutput)
output.addVolumeVariable(lambda vv: vv.pressure(), "p")
output.write(1.0)
# Print used and unused parameters
printParameters()
T getParam(Args &&... args)
A free function to get a parameter from the parameter tree singleton.
Definition: parameters.hh:139
std::string permeability() noexcept
I/O name of permeability.
Definition: name.hh:131
void print(const Collector &collector)
prints json tree
Definition: metadata.hh:237

Development of Python bindings

You can install all development requirements with

python3 -m pip install -r requirements.txt

The requirements.txt can be found in the DuMux repository in the top level. We recommend to use a virtual environment for development as described above.

All Python files are linted by the tool black. You can install black with pip install black and run it from the dumux top-directory

black ./python

You can also run it on a specific file (replace ./python by file name) This will automatically format the Python files. Run black before every commit changing Python files. The CI pipeline prevents merging code that would reformat when running black.

The dumux Python module should be get a score of 10 from the tool pylint. You can install pylint with pip install pylint and run it from the dumux top-directory

pylint build-cmake/python/dumux

The pylint configuration file dumux/.pylintrc can be used to configure pylint. Some exceptions or other parameters than the default might be sensible in the future but generally advice given by pylint leads to better code. Different from black, pylint does no itself fix the code, you need to do this yourself. Always run black before checking pylint. We also run flake8 on Python code. The CI pipeline automatically checks that pylint and flake8 return without warning.