From c44e4d171936c95a4dce3a365a6ed0c2ea99bef1 Mon Sep 17 00:00:00 2001 From: David Schneider Date: Thu, 22 Jan 2026 12:39:46 +0100 Subject: [PATCH 1/3] Add initial setup --- channel-transport-particles/clean-tutorial.sh | 1 + .../fluid-nutils/clean.sh | 6 + .../fluid-nutils/fluid.py | 111 +++++++++++++ .../fluid-nutils/requirements.txt | 5 + .../fluid-nutils/run.sh | 16 ++ .../fluid-openfoam/0/U | 39 +++++ .../fluid-openfoam/0/p | 45 ++++++ .../fluid-openfoam/clean.sh | 6 + .../constant/transportProperties | 11 ++ .../constant/turbulenceProperties | 9 ++ .../fluid-openfoam/run.sh | 12 ++ .../fluid-openfoam/system/blockMeshDict | 146 ++++++++++++++++++ .../fluid-openfoam/system/controlDict | 45 ++++++ .../fluid-openfoam/system/decomposeParDict | 16 ++ .../fluid-openfoam/system/fvSchemes | 39 +++++ .../fluid-openfoam/system/fvSolution | 74 +++++++++ .../fluid-openfoam/system/preciceDict | 32 ++++ .../particles-mercurydpm/.keepme | 0 .../precice-config.xml | 44 ++++++ 19 files changed, 657 insertions(+) create mode 120000 channel-transport-particles/clean-tutorial.sh create mode 100755 channel-transport-particles/fluid-nutils/clean.sh create mode 100644 channel-transport-particles/fluid-nutils/fluid.py create mode 100644 channel-transport-particles/fluid-nutils/requirements.txt create mode 100755 channel-transport-particles/fluid-nutils/run.sh create mode 100644 channel-transport-particles/fluid-openfoam/0/U create mode 100644 channel-transport-particles/fluid-openfoam/0/p create mode 100755 channel-transport-particles/fluid-openfoam/clean.sh create mode 100644 channel-transport-particles/fluid-openfoam/constant/transportProperties create mode 100644 channel-transport-particles/fluid-openfoam/constant/turbulenceProperties create mode 100755 channel-transport-particles/fluid-openfoam/run.sh create mode 100644 channel-transport-particles/fluid-openfoam/system/blockMeshDict create mode 100644 channel-transport-particles/fluid-openfoam/system/controlDict create mode 100644 channel-transport-particles/fluid-openfoam/system/decomposeParDict create mode 100644 channel-transport-particles/fluid-openfoam/system/fvSchemes create mode 100644 channel-transport-particles/fluid-openfoam/system/fvSolution create mode 100644 channel-transport-particles/fluid-openfoam/system/preciceDict create mode 100644 channel-transport-particles/particles-mercurydpm/.keepme create mode 100644 channel-transport-particles/precice-config.xml diff --git a/channel-transport-particles/clean-tutorial.sh b/channel-transport-particles/clean-tutorial.sh new file mode 120000 index 000000000..4713f5092 --- /dev/null +++ b/channel-transport-particles/clean-tutorial.sh @@ -0,0 +1 @@ +../tools/clean-tutorial-base.sh \ No newline at end of file diff --git a/channel-transport-particles/fluid-nutils/clean.sh b/channel-transport-particles/fluid-nutils/clean.sh new file mode 100755 index 000000000..0d1c2d23d --- /dev/null +++ b/channel-transport-particles/fluid-nutils/clean.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +set -e -u + +. ../../tools/cleaning-tools.sh + +clean_nutils . diff --git a/channel-transport-particles/fluid-nutils/fluid.py b/channel-transport-particles/fluid-nutils/fluid.py new file mode 100644 index 000000000..aa91605df --- /dev/null +++ b/channel-transport-particles/fluid-nutils/fluid.py @@ -0,0 +1,111 @@ +#! /usr/bin/env python3 + +# +# Incompressible NSE solved within a channel geometry with parabolic inflow profile and an obstacle attached to the bottom towards the middle of the domain. The fluid field is initialized with a Stokes solution. The resulting velocity field is written to preCICE on the complete volume. +# + +from nutils import function, mesh, cli, solver, export +import treelog as log +import numpy as np +import precice +from mpi4py import MPI + + +def main(): + + print("Running Nutils") + + # define the Nutils mesh + nx = 48 + ny = 16 + step_start = nx // 3 + step_end = nx // 2 + step_hight = ny // 2 + + grid = np.linspace(0, 6, nx + 1), np.linspace(0, 2, ny + 1) + domain, geom = mesh.rectilinear(grid) + domain = domain.withboundary(inflow="left", outflow="right", wall="top,bottom") - domain[ + step_start:step_end, :step_hight + ].withboundary(wall="left,top,right") + + # cloud of Gauss points + gauss = domain.sample("gauss", degree=4) + + # Nutils namespace + ns = function.Namespace() + ns.x = geom + + ns.ubasis = domain.basis("std", degree=2).vector(2) + ns.pbasis = domain.basis("std", degree=1) + ns.u_i = "ubasis_ni ?u_n" # solution + ns.p = "pbasis_n ?p_n" # solution + ns.dudt_i = "ubasis_ni (?u_n - ?u0_n) / ?dt" # time derivative + ns.μ = 0.5 # viscosity + ns.σ_ij = "μ (u_i,j + u_j,i) - p δ_ij" + ns.uin = "10 x_1 (2 - x_1)" # inflow profile + + # define the weak form, Stokes problem + ures = gauss.integral("ubasis_ni,j σ_ij d:x" @ ns) + pres = gauss.integral("pbasis_n u_k,k d:x" @ ns) + + # define Dirichlet boundary condition + sqr = domain.boundary["inflow"].integral("(u_0 - uin)^2 d:x" @ ns, degree=2) + sqr += domain.boundary["inflow,outflow"].integral("u_1^2 d:x" @ ns, degree=2) + sqr += domain.boundary["wall"].integral("u_k u_k d:x" @ ns, degree=2) + cons = solver.optimize(["u"], sqr, droptol=1e-15) + + # preCICE setup + participant = precice.Participant("Fluid", "../precice-config.xml", 0, 1) + + # define coupling mesh + mesh_name = "Fluid-Mesh" + vertices = gauss.eval(ns.x) + vertex_ids = participant.set_mesh_vertices(mesh_name, vertices) + + # coupling data + data_name = "Velocity" + + participant.initialize() + + timestep = 0 + solver_dt = 0.005 + precice_dt = participant.get_max_time_step_size() + dt = min(precice_dt, solver_dt) + + state = solver.solve_linear(("u", "p"), (ures, pres), constrain=cons) # initial condition + + # add convective term and time derivative for Navier-Stokes + ures += gauss.integral("ubasis_ni (dudt_i + μ (u_i u_j)_,j) d:x" @ ns) + + while participant.is_coupling_ongoing(): + + if timestep % 1 == 0: # visualize + bezier = domain.sample("bezier", 2) + x, u, p = bezier.eval(["x_i", "u_i", "p"] @ ns, **state) + with log.add(log.DataLog()): + export.vtk("Fluid_" + str(timestep), bezier.tri, x, u=u, p=p) + + precice_dt = participant.get_max_time_step_size() + + # potentially adjust non-matching timestep sizes + dt = min(solver_dt, precice_dt) + + # solve Nutils timestep + state["u0"] = state["u"] + state["dt"] = dt + state = solver.newton(("u", "p"), (ures, pres), constrain=cons, arguments=state).solve(1e-10) + + velocity_values = gauss.eval(ns.u, **state) + participant.write_data(mesh_name, data_name, vertex_ids, velocity_values) + + # do the coupling + participant.advance(dt) + + # advance variables + timestep += 1 + + participant.finalize() + + +if __name__ == "__main__": + cli.run(main) diff --git a/channel-transport-particles/fluid-nutils/requirements.txt b/channel-transport-particles/fluid-nutils/requirements.txt new file mode 100644 index 000000000..56c3d4a92 --- /dev/null +++ b/channel-transport-particles/fluid-nutils/requirements.txt @@ -0,0 +1,5 @@ +setuptools # required by nutils +nutils==7 +numpy >1, <2 +pyprecice~=3.0 +setuptools diff --git a/channel-transport-particles/fluid-nutils/run.sh b/channel-transport-particles/fluid-nutils/run.sh new file mode 100755 index 000000000..641f4f465 --- /dev/null +++ b/channel-transport-particles/fluid-nutils/run.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -e -u + +. ../../tools/log.sh +exec > >(tee --append "$LOGFILE") 2>&1 + +if [ ! -v PRECICE_TUTORIALS_NO_VENV ] +then + python3 -m venv .venv + . .venv/bin/activate + pip install -r requirements.txt && pip freeze > pip-installed-packages.log +fi + +python3 fluid.py + +close_log diff --git a/channel-transport-particles/fluid-openfoam/0/U b/channel-transport-particles/fluid-openfoam/0/U new file mode 100644 index 000000000..2cf022003 --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/0/U @@ -0,0 +1,39 @@ +FoamFile +{ + version 2.0; + format ascii; + class volVectorField; + object U; +} + +dimensions [0 1 -1 0 0 0 0]; +internalField uniform (10 0 0); + +boundaryField +{ + inlet + { + type fixedValue; + value $internalField; + } + outlet + { + type zeroGradient; + } + obstacle + { + type noSlip; + } + upperWall + { + type noSlip; + } + lowerWall + { + type noSlip; + } + frontAndBack + { + type empty; + } +} diff --git a/channel-transport-particles/fluid-openfoam/0/p b/channel-transport-particles/fluid-openfoam/0/p new file mode 100644 index 000000000..5252ec67a --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/0/p @@ -0,0 +1,45 @@ +FoamFile +{ + version 2.0; + format ascii; + class volScalarField; + object p; +} + +dimensions [0 2 -2 0 0 0 0]; + +internalField uniform 0; + +boundaryField +{ + inlet + { + type zeroGradient; + } + + outlet + { + type fixedValue; + value uniform 0; + } + + obstacle + { + type zeroGradient; + } + + upperWall + { + type zeroGradient; + } + + lowerWall + { + type zeroGradient; + } + + frontAndBack + { + type empty; + } +} \ No newline at end of file diff --git a/channel-transport-particles/fluid-openfoam/clean.sh b/channel-transport-particles/fluid-openfoam/clean.sh new file mode 100755 index 000000000..b64fc5101 --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/clean.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +set -e -u + +. ../../tools/cleaning-tools.sh + +clean_openfoam . diff --git a/channel-transport-particles/fluid-openfoam/constant/transportProperties b/channel-transport-particles/fluid-openfoam/constant/transportProperties new file mode 100644 index 000000000..5383adaad --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/constant/transportProperties @@ -0,0 +1,11 @@ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object transportProperties; +} + +transportModel Newtonian; + +nu nu [ 0 2 -1 0 0 0 0 ] 1; diff --git a/channel-transport-particles/fluid-openfoam/constant/turbulenceProperties b/channel-transport-particles/fluid-openfoam/constant/turbulenceProperties new file mode 100644 index 000000000..592f6d52d --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/constant/turbulenceProperties @@ -0,0 +1,9 @@ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object turbulenceProperties; +} + +simulationType laminar; diff --git a/channel-transport-particles/fluid-openfoam/run.sh b/channel-transport-particles/fluid-openfoam/run.sh new file mode 100755 index 000000000..8f55fbfa5 --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/run.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e -u + +. ../../tools/log.sh +exec > >(tee --append "$LOGFILE") 2>&1 + +blockMesh + +../../tools/run-openfoam.sh "$@" +. ../../tools/openfoam-remove-empty-dirs.sh && openfoam_remove_empty_dirs + +close_log diff --git a/channel-transport-particles/fluid-openfoam/system/blockMeshDict b/channel-transport-particles/fluid-openfoam/system/blockMeshDict new file mode 100644 index 000000000..bdb4fe67b --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/system/blockMeshDict @@ -0,0 +1,146 @@ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object blockMeshDict; +} + +x0 0.; +x1 2.; +x2 3.; +x3 6.; + +y0 0.; +y1 1.; +y2 2.; + +z0 0; +z1 1; + +vertices +( + ($x0 $y0 $z0 ) // 0 + ($x1 $y0 $z0 ) // 1 + ($x2 $y0 $z0 ) // 2 + ($x3 $y0 $z0 ) // 3 + ($x0 $y1 $z0 ) // 4 + ($x1 $y1 $z0 ) // 5 + ($x2 $y1 $z0 ) // 6 + ($x3 $y1 $z0 ) // 7 + ($x0 $y2 $z0 ) // 8 + ($x1 $y2 $z0 ) // 9 + ($x2 $y2 $z0 ) // 10 + ($x3 $y2 $z0 ) // 11 + + ($x0 $y0 $z1 ) // 12 + ($x1 $y0 $z1 ) // 13 + ($x2 $y0 $z1 ) // 14 + ($x3 $y0 $z1 ) // 15 + ($x0 $y1 $z1 ) // 16 + ($x1 $y1 $z1 ) // 17 + ($x2 $y1 $z1 ) // 18 + ($x3 $y1 $z1 ) // 19 + ($x0 $y2 $z1 ) // 20 + ($x1 $y2 $z1 ) // 21 + ($x2 $y2 $z1 ) // 22 + ($x3 $y2 $z1 ) // 23 +); + + +// Grading +h1 16; +h2 8; +h3 24; +v1 8; + +blocks +( + hex ( 0 1 5 4 12 13 17 16 ) + ($h1 $v1 1 ) + simpleGrading (1 1 1) + + hex ( 2 3 7 6 14 15 19 18 ) + ($h3 $v1 1) + simpleGrading (1 1 1) + + hex ( 4 5 9 8 16 17 21 20 ) + ($h1 $v1 1) + simpleGrading (1 1 1) + + hex ( 5 6 10 9 17 18 22 21 ) + ($h2 $v1 1) + simpleGrading (1 1 1) + + hex ( 6 7 11 10 18 19 23 22 ) + ($h3 $v1 1 ) + simpleGrading (1 1 1) +); + +boundary +( + inlet + { + type patch; + faces + ( + ( 0 4 16 12 ) + ( 4 8 20 16 ) + ); + } + outlet + { + type patch; + faces + ( + ( 3 7 19 15 ) + ( 7 11 23 19 ) + ); + } + obstacle + { + type wall; + faces + ( + ( 1 5 17 13 ) + ( 5 6 18 17 ) + ( 6 2 14 18 ) + ); + } + upperWall + { + type wall; + faces + ( + ( 8 9 21 20 ) + ( 9 10 22 21 ) + ( 10 11 23 22 ) + ); + } + lowerWall + { + type wall; + faces + ( + ( 0 1 13 12 ) + ( 2 3 15 14 ) + ); + } + frontAndBack + { + type empty; + faces + ( + ( 0 1 5 4 ) + ( 2 3 7 6 ) + ( 4 5 9 8 ) + ( 5 6 10 9 ) + ( 6 7 11 10 ) + ( 12 13 17 16 ) + ( 14 15 19 18 ) + ( 16 17 21 20 ) + ( 17 18 22 21 ) + ( 18 19 23 22 ) + ); + } +); \ No newline at end of file diff --git a/channel-transport-particles/fluid-openfoam/system/controlDict b/channel-transport-particles/fluid-openfoam/system/controlDict new file mode 100644 index 000000000..55c798b99 --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/system/controlDict @@ -0,0 +1,45 @@ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object controlDict; +} + +application pimpleFoam; + +startFrom startTime; + +startTime 0; + +stopAt endTime; + +endTime 1.0; + +deltaT 0.005; + +writeControl adjustableRunTime; + +writeInterval 0.005; + +purgeWrite 0; + +writeFormat ascii; + +writePrecision 6; + +writeCompression off; + +timeFormat general; + +timePrecision 6; + +libs ("libpreciceAdapterFunctionObject.so"); +functions +{ + preCICE_Adapter + { + type preciceAdapterFunctionObject; + errors strict; // Available since OpenFOAM v2012 + } +} diff --git a/channel-transport-particles/fluid-openfoam/system/decomposeParDict b/channel-transport-particles/fluid-openfoam/system/decomposeParDict new file mode 100644 index 000000000..f9eae003a --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/system/decomposeParDict @@ -0,0 +1,16 @@ +FoamFile { + version 2.0; + class dictionary; + object decomposeParDict; + format ascii; +} + +numberOfSubdomains 4; + +method simple; + +simpleCoeffs +{ + n (2 2 1); + delta 0.001; +} \ No newline at end of file diff --git a/channel-transport-particles/fluid-openfoam/system/fvSchemes b/channel-transport-particles/fluid-openfoam/system/fvSchemes new file mode 100644 index 000000000..80c096192 --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/system/fvSchemes @@ -0,0 +1,39 @@ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object fvSchemes; +} + +ddtSchemes +{ + default Euler; +} + +gradSchemes +{ + default Gauss linear; +} + +divSchemes +{ + default none; + div(phi,U) bounded Gauss upwind; + div((nuEff*dev2(T(grad(U))))) Gauss linear; +} + +laplacianSchemes +{ + default Gauss linear corrected; +} + +interpolationSchemes +{ + default linear; +} + +snGradSchemes +{ + default corrected; +} diff --git a/channel-transport-particles/fluid-openfoam/system/fvSolution b/channel-transport-particles/fluid-openfoam/system/fvSolution new file mode 100644 index 000000000..d8045408b --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/system/fvSolution @@ -0,0 +1,74 @@ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + location "system"; + object fvSolution; +} + +solvers +{ + + p + { + + solver PCG; + preconditioner DIC; + tolerance 1e-8; + relTol 1e-3; + } + + pFinal + { + $p; + relTol 0; + } + + pcorr + { + $p; + } + + pcorrFinal + { + $pcorr; + relTol 0; + } + + Phi + { + $p; + } + + "(U|cellDisplacement)" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-6; + relTol 1e-4; + minIter 2; + } + + "(U|cellDisplacement)Final" + { + $U; + relTol 0; + } +} + +PIMPLE +{ + nCorrectors 4; + nNonOrthogonalCorrectors 1; + consistent true; + correctPhi true; + momentumPredictor true; + nOuterCorrectors 1; +} + + +potentialFlow +{ + nNonOrthogonalCorrectors 10; +} diff --git a/channel-transport-particles/fluid-openfoam/system/preciceDict b/channel-transport-particles/fluid-openfoam/system/preciceDict new file mode 100644 index 000000000..7368a7a56 --- /dev/null +++ b/channel-transport-particles/fluid-openfoam/system/preciceDict @@ -0,0 +1,32 @@ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object preciceDict; +} + +preciceConfig "../precice-config.xml"; + +participant Fluid; + +modules (FF); + +interfaces +{ + Interface1 + { + mesh Fluid-Mesh; + patches (); + locations volumeCenters; + + readData + ( + ); + + writeData + ( + Velocity + ); + }; +}; \ No newline at end of file diff --git a/channel-transport-particles/particles-mercurydpm/.keepme b/channel-transport-particles/particles-mercurydpm/.keepme new file mode 100644 index 000000000..e69de29bb diff --git a/channel-transport-particles/precice-config.xml b/channel-transport-particles/precice-config.xml new file mode 100644 index 000000000..0d0d77cb8 --- /dev/null +++ b/channel-transport-particles/precice-config.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a0bbd04b24ec1ff55bd8e2bb2097db381fe89d8a Mon Sep 17 00:00:00 2001 From: David Schneider Date: Thu, 22 Jan 2026 18:43:50 +0100 Subject: [PATCH 2/3] Configure the case properly --- .../precice-config.xml | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/channel-transport-particles/precice-config.xml b/channel-transport-particles/precice-config.xml index 0d0d77cb8..01af32b7a 100644 --- a/channel-transport-particles/precice-config.xml +++ b/channel-transport-particles/precice-config.xml @@ -1,5 +1,5 @@ - + - - - - - - - - - - - + + + + + + - + - - - + + + From f4ae5cef75eca027c64bc1f10351cfd14dbba1b3 Mon Sep 17 00:00:00 2001 From: David Schneider Date: Mon, 26 Jan 2026 18:23:32 +0100 Subject: [PATCH 3/3] Add preliminary README --- channel-transport-particles/README.md | 55 +++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 channel-transport-particles/README.md diff --git a/channel-transport-particles/README.md b/channel-transport-particles/README.md new file mode 100644 index 000000000..1de7d35cc --- /dev/null +++ b/channel-transport-particles/README.md @@ -0,0 +1,55 @@ +--- +title: Channel transport with particles +permalink: tutorials-channel-transport-particles.html +keywords: volume coupling, particles, OpenFOAM, MercuryDPM, transport, just-in-time mapping +summary: A CFD problem is coupled to a particles in a uni-directional way for particles tracing. +--- + +{% note %} +Get the [case files of this tutorial](https://github.com/precice/tutorials/tree/master/channel-transport-particles). Read how in the [tutorials introduction](https://precice.org/tutorials.html). +{% endnote %} + +## Setup + +We model a two-dimensional incompressible fluid flowing through a channel with an obstacle. The fluid problem is coupled to a particle participant for particle tracing. Similar to the transport problem (see the [channel-transport tutorial](tutorials-channel-transport.html)), particles are arranged in a circular blob close to the inflow. + +## Configuration + +preCICE configuration (image generated using the [precice-config-visualizer](https://precice.org/tooling-config-visualization.html)): + +![preCICE configuration visualization](images/tutorials-channel-transport-precice-config.png +) + +## Available solvers + +Fluid participant: + +* Nutils. For more information, have a look at the [Nutils adapter documentation](https://precice.org/adapter-nutils.html). This Nutils solver requires at least Nutils v7.0. + +* OpenFOAM (pimpleFoam). For more information, have a look at the [OpenFOAM adapter documentation](https://precice.org/adapter-openfoam-overview.html). + +Particle participant: + +* MercuryDPM + +## Running the simulation + +For the fluid solver, use Nutils for ease of installation and OpenFOAM for speed. + +Open two separate terminals and start one fluid and one transport participant by calling the respective run scripts `run.sh` located in each of the participants' directory. For example: + +```bash +cd fluid-nutils +./run.sh +``` + +and either the non-adaptive mesh transport solver + +```bash +cd particles-mercurydpm +./run.sh +``` + +## Post-processing + +All solvers generate `vtk` files which can be visualized using, e.g., ParaView.