Skip to content

feat(vof): NeoN building blocks for the incompressibleVoFNeon solver - #562

Open
HenningScheufler wants to merge 8 commits into
developfrom
feat/incompressibleVoFNeoN
Open

feat(vof): NeoN building blocks for the incompressibleVoFNeon solver#562
HenningScheufler wants to merge 8 commits into
developfrom
feat/incompressibleVoFNeoN

Conversation

@HenningScheufler

Copy link
Copy Markdown
Collaborator

NeoN building blocks for the incompressibleVoFNeon solver

Adds the generic NeoN primitives the NeoFOAM-side VoF solver composes from, keeping NeoN building-blocks-only (no VoF/MULES algorithm lives here).

What's here

  • FixedFluxPressure<scalar> volume boundarycorrectBoundaryCondition re-derives the boundary value from the existing per-face refGrad (it does not overwrite it); an external caller (constrainPressure, NeoFOAM side) sets refGrad each PISO corrector so the pressure projection cancels the wall face flux. Used by the buoyant p_rgh correction.
  • Density-weighted ddt + reconstruct for the VoF momentum/pressure path.
  • Bindings hardening — force-instantiate the Gauss div/laplacian operators in _neon (+ regression test), build _neon with nanobind NB_SHARED, pin nanobind to 2.13.0 to match pybFoam's ABI.

Not here (by design)

The MULES FCT limiter was briefly added on this branch and then removed (refactor(vof): remove MULES from NeoN): MULES is a VoF-specific algorithm, so it now lives in NeoFOAM's vofOperators.cpp alongside mules_correct. The net diff adds no MULES to NeoN.

Paired with the NeoFOAM PR (feat/incompressibleVoF) that consumes these primitives; the NeoFOAM side pins this commit via its src/NeoN submodule.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BdqJykWhD6VvPjWR3tvAez

The div/laplacian operators are declared `extern template`, so including their
headers in the _neon binding TU does not instantiate them — their self-registration
only fires in libNeoN. Compiled `-fvisibility=hidden`, _neon gets a private, empty
copy of the runtime-selection factory table, so scheme resolution at assembly time
aborts with "Could not find constructor for Gauss". Force explicit instantiation
here so the self-registration runs inside _neon and populates its table.

Marked with a TODO to drop once the factory table is shared across shared objects.
Drives Expression.read from Python — the call that performs the runtime-selection
factory lookup (create("Gauss")) — to guard the explicit-instantiation fix. Without
that fix the lookup aborts with "Could not find constructor for Gauss". A scalar
equation covers the <scalar> instantiation; a vector equation additionally covers
<Vec3> and <Vec3, scalar> (vector div/laplacian build both same-type and
scalar-matrix strategies).

Adds the minimal bindings the test needs: Expression.read, and
Dictionary.insert_dict / insert_token_list (so a nested divSchemes/laplacianSchemes
dictionary can be built from Python).
Static nanobind gives each extension module its own private nanobind runtime, so types
cannot be exchanged across modules reliably (it happens to work when everything is built
against one nanobind, but breaks in isolated CI builds). Build _neon against a shared
nanobind runtime so it can exchange types with neofoam_bindings and pybFoam (which also
uses NB_SHARED) — e.g. so create_adapter_run_time accepts pybFoam's Foam::Time.
NeoN CPM-pinned nanobind 2.9.2 while pybFoam (and pyOFTools) build against pip's 2.13.0.
The _neon binding shares pybFoam's libnanobind.so at runtime, but nanobind's
ndarray_create signature changed after 2.9.2 (gained a size_t arg), so _neon built
against 2.9.2 failed to resolve the symbol from pybFoam's 2.13.0 lib (undefined symbol
in CI). Bump to 2.13.0 so the ABIs match; keep it overridable so a downstream can track
its own pybFoam. CPM_USE_LOCAL_PACKAGES still reuses an installed nanobind when present.
Add the NeoN-core discretisation primitives the NeoFOAM incompressibleVoF /
neoInterFoam solver needs, on top of develop:

- ddtOperator: density-weighted overload (rho on the diagonal, oldTime(rho) on
  the rhs) for the interFoam ddt(rho, U) momentum term; imp::ddt(rho, phi) dsl.
- reconstruct: fvc::reconstruct(surfaceScalar) -> volVector least-squares
  surface-flux -> cell-vector op (inv(sum Sf(x)Sf/|Sf|) & sum (Sf/|Sf|) ssf),
  with a trace-regularisation so it is correct on 2D (empty-direction) meshes.
- mules: bounded FCT flux limiter (MULES::explicitSolve/limit/limiter,
  simplified rho=1 / Sp=Su=0 / no-compression path) — conservatively bounds
  alpha in [0,1] without clamping; matches OpenFOAM's lambdam/lambdap limiter.
- surfaceField: value assignment helper used by the VoF flux updates.
- bindings: expose nn.reconstruct, nn.mules_explicit_solve, imp.ddt(rho,phi).

Verified against OpenFOAM/pybFoam from the NeoFOAM side (reconstruct/MULES/
surface-tension parity to machine precision; MULES matches pybFoam.vof).
Add the FixedFluxPressure<scalar> volume boundary condition and register it in
boundary.hpp. correctBoundaryCondition re-derives the boundary value from the
existing per-face refGrad (it does NOT overwrite refGrad from a stored uniform);
an external caller (constrainPressure) sets refGrad each PISO corrector so the
pressure projection cancels the wall face flux. Used by the incompressibleVoFNeon
solver's buoyant p_rgh correction.
MULES::explicitSolve is a VoF-specific FCT algorithm, not a NeoN building block;
move it to NeoFOAM (src/bindings/vofOperators.cpp) beside MULES::correct so NeoN
stays algorithm-free and only provides the generic primitives it composes from
(parallelFor / surfaceIntegrate / mesh views / Vector). Removes the mules
operator, header, and Python binding + their CMake/registration entries.
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Thank you for your PR, here are some useful tips:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant