OpenFOAM backend — zoomyFoam (via preCICE)#
zoomy_foam runs a Zoomy depth-averaged model as a native OpenFOAM finite-volume
solver, and couples it across an interface to another solver (a second Zoomy
participant, or a 3-D OpenFOAM region) through the preCICE
library. It is the one backend where preCICE is in scope; the coupling is built on
the interpolate_to_3d / project_from_3d maps every Zoomy model already carries
(see authoring/model.md).
How it is built — code generation, then compile#
Unlike NumPy/JAX, zoomyFoam is generated, then compiled. A C++ code printer
emits per-case OpenFOAM headers from a frozen SystemModel + Numerics
(zoomy_core/transformation/to_openfoam.py; see the
printer inventory):
Printer |
Emits |
Contents |
|---|---|---|
|
|
|
|
|
|
|
|
the runtime aux-refresh. |
zoomy_foam/create_model.py drives the emit (build_system_model / emit,
using PositiveNonconservativeRusanov and a Coupled(tag="coupled", mesh_name="interface") boundary). The generated headers compile into the solver
binary via compile.sh / wmake, run inside the project’s apptainer image.
This is the same symbolic operator surface the other backends consume — the
printer emits the same numerical_flux / quasilinear_matrix_x kernels as
C++ instead of lambdifying them, so a coupled run matches the
NumPy / JAX reference. If a coupled run needs different
operators, fix the derivation, not the printer.
The runtime — zoomyFoam.C#
zoomy_foam/zoomyFoam.C is a SystemModel-driven explicit finite-volume solver.
Q and Qaux are List<volScalarField*>; reconstruction W/gradW, the source
field, and the cell-interior non-conservative integral are built once. Time
stepping is explicit forward-Euler or Heun SSP-RK2 — it deliberately bypasses
fvm::ddt so the update matches the NumPy/JAX reference exactly.
preCICE coupling#
The adapter is an in-tree preCICE-3 wrapper, zoomy_foam/precice/PreciceManager.H
(precice::Participant), driven from the zoomyFoam.C time loop. Per window:
read peer data → solve → write this participant’s data → advance, with
checkpoint write/read around the step and dt = min(dt, preciceDt).
What is exchanged. The canonical 6-field profile column
[b, h, u, v, w, p] — i.e. interpolate_to_3d — sampled on a uniform vertical
grid (n_faces × nZ vertices per coupled patch):
write: Q on a coupled patch --interpolate_to_3d(z_k)--> 6 fields
read: 6 fields --project_from_3d (depth-average)--> Q ghost
write lifts the interior cell to a ζ-column; read reduces the peer column
back to a state and imposes a ghost cell selected by preciceGhost:
fullstate(default) — conservative: set the whole peer state.characteristic— only the incoming Riemann invariants, via the eigendecomposition ofA = ∂F/∂Q + B.froude— a Froude-switched primitive Dirichlet–Neumann condition onh, hu.
preciceFrozenMass yes freezes the coupled-face mass-row flux per window for a
closed interface mass ledger.
Participants. The coupling is symmetric: the same model and binary run on
both sides. preCICE forbids reusing a (data, mesh) pair, so each participant
writes one data set and reads the peer’s — e.g. SmeA writes *_1 / reads
*_2, SmeB the reverse. Mapping is nearest-neighbour, mesh dimensions="3",
and the coupling scheme is selectable (parallel-explicit, …).
Setting up a coupled run#
A coupled case is generated, not hand-written. The reference lives at
thesis/notebooks/coupling/cases/sme_self/:
# emits part1/, part2/, mono/ case dirs + precice-config.xml
python generate.py LEVEL SCHEME ZSAMPLES DT TEND
# blockMesh/setFields each case, launch both participants in parallel (apptainer)
./run.sh
generate.py writes the preCICE keys into each case’s system/controlDict, e.g.:
preciceParticipant SmeA;
preciceConfig precice-config.xml;
preciceMeshes ( MeshA );
preciceWriteData ( b_1 h_1 u_1 v_1 w_1 p_1 );
preciceReadData ( b_2 h_2 u_2 v_2 w_2 p_2 );
preciceGhost fullstate;
preciceFrozenMass yes;
preciceZSamples 16;
It produces mono/ (a single uncoupled run) alongside the two participants so
the coupled result can be verified against the monolithic one — the standard
correctness check for a coupling change.
Install#
Requires OpenFOAM 12+, preCICE 3, and the project apptainer image. See
zoomy_foam/install/ and the case run.sh for the apptainer invocation.
Repository: library/zoomy_foam
API reference: see zoomy_foam.