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.
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
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")
gridView = structuredGrid([0, 0], [1, 1], [10, 10])
gridGeometry = GridGeometry(gridView=gridView, discMethod=discMethod)
model = Model(inheritsFrom=["OneP"], gridGeometry=gridGeometry)
model["LocalResidual"] = Property.fromCppType(
"OnePIncompressibleLocalResidual<TypeTag>",
cppIncludes=["dumux/porousmediumflow/1p/incompressiblelocalresidual.hh"],
)
h20 = Component("SimpleH2O")
onePLiquid = FluidSystem("OnePLiquid", component=h20, scalar=model["Scalar"])
model["FluidSystem"] = Property.fromInstance(onePLiquid)
@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
globalPos = scv.dofPosition
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)
@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
problem = Problem()
model["Problem"] = Property.fromInstance(problem)
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)
assembler.assembleJacobianAndResidual(sol)
res = assembler.residual
jac = assembler.jacobian
S = CGSolver(jac.asLinearOperator(), SeqSSOR(jac), 1e-10)
res *= -1
_, _, converged, _, _ = S(sol, res)
if not converged:
raise Exception("CGSolver has not converged")
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)
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
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
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.