Skip to content

IBM: ib_flux_wall for fixed no-slip inert bodies, and trace species no longer starve the reacting flux wall - #2213

Merged
sbryngelson merged 4 commits into
MFlowCode:masterfrom
sbryngelson:feat/ib-flux-wall-inert
Nov 14, 2026
Merged

sbryngelson merged 4 commits into
MFlowCode:masterfrom
sbryngelson:feat/ib-flux-wall-inert

Conversation

@sbryngelson

@sbryngelson sbryngelson commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Two commits on origin/master 80997da, each with its own tests and goldens. The option stays opt-in (ib_flux_wall default F); with it off nothing changes (no existing golden moved on either vendor, see Tests).

Summary

  1. Trace species and the positivity factor (m_ibm.fpp, s_ib_flux_wall_prepare). The positivity factor theta of a fluid cell caps all of its wall faces so that they take at most 0.9 of any consumed species in one forward-Euler step. A species with a negligible share of the wall consumption that the cell holds almost none of set theta ~ 0 for every species. Species carrying less than 1e-6 of the cell's wall consumption no longer set theta (they are not limited; rhoY of such a species can in principle go slightly negative, which no run here showed).
  2. Inert flux wall (m_ibm.fpp, s_ib_flux_wall_setup, validator, docs). ib_flux_wall also covers fixed, no-slip, non-blowing bodies with surface_reaction = 0. Their wall faces keep a zero source: no mass or species, only conduction, pressure and viscous stresses. This removes the ghost-cell gas exchange across the staircase faces that makes the gas mass of a closed periodic box with fixed isothermal inert bodies drift. An inert body that slips or blows keeps the ghost-state wall. The validator accepts the option with such bodies (it still needs every body fixed and at least one reacting or no-slip, non-blowing body). In a case that mixes reacting and inert bodies with ib_flux_wall = T, the inert bodies now get the zero-flux wall.

No case sets ib_flux_wall by default, and no toolchain default is touched.

Evidence

Machines: A100: NVIDIA A100 80GB PCIe, nvfortran 25.11, OpenACC (--gpu acc), 1 rank unless stated. MI300A: AMD MI300A (gfx942), amdflang (AFAR 24.3.0), OpenMP offload (--gpu mp), 1 rank. "master" is a build of origin/master 80997da; "branch" is this PR. The MI300A runs below used the branch with the option off, which is bit-identical to master (see Tests); they reproduce the A100 master numbers to all printed digits.

1. Trace species (commit 1)

Mass-budget example (examples/2D_ibm_wall_mass_budget, reacting carbon cylinder in a closed box, 16 cells/D, --flux_wall, A100). Gasified mass is the time integral of the surface records, as in mass_budget.py; the fix is the only difference.

t [us] gasified, master [kg/m] gasified, branch [kg/m] branch / master defect, master defect, branch
15 6.55e-11 7.75e-11 1.18 1.4e-2 1.6e-2
30 1.98e-10 2.32e-10 1.17 5.8e-3 3.4e-3
60 4.70e-10 5.42e-10 1.15 -1.8e-4 -1.2e-3
105 8.73e-10 9.99e-10 1.14 -5.4e-4 1.5e-4
150 1.27e-9 1.45e-9 1.14 -2.1e-3 -3.7e-4
300 2.69e-9 2.97e-9 1.11 -3.1e-3 -1.8e-3

The gas budget closes to 0.3% or better after 60 us in both. That the shortfall on master is trace-species starvation is inferred from the next table; I did not print theta for this case.

The 48 x 48 reacting-cylinder case (the new test; the 24 x 24 Flux Wall test never has theta < 1). Gasification rate of the body (ib_heat_wrt column 3), A100:

step (t [us]) master [kg/s/m] branch [kg/s/m] master / branch
25 (13.0) 9.261e-6 9.261e-6 1.000
30 (15.5) 8.670e-6 9.259e-6 0.936
35 (18.0) 8.238e-6 9.259e-6 0.890
40 (20.0) 8.105e-6 9.260e-6 0.875

A CPU build with a print in s_ib_flux_wall_prepare shows species 9 (OH) as the limiting species in those cells (consumption ~ 7e-46 against rhoY ~ 2e-46 kg/m^3 per step). Fields at 2e-5 s differ between the two builds by up to 1e-4 relative in the mixture density and 1e-3 in the energy and x-momentum, far above the 1e-6 test tolerance, so the new test fails on master.

2. Inert flux wall (commit 2)

Periodic square array of fixed isothermal inert cylinders (area fraction 0.2), carbon-gas mechanism of examples/2D_ibm_reacting_surface with reactions off, thermal_bc = 1, no-slip, body-force driven (bf_x), Re_m = 100 and Mach 0.3 at the start (u_mean 110-190 m/s as the gas heats; the Mach number drifts between about 0.2 and 0.4), 10 nominal flow-throughs (L/U0), WENO5 + HLLC + RK3, A100, 16/24/32/48 cells per diameter. Drag is the x-force on the body and Q the heat rate into the body (ib_force_wrt, ib_heat_wrt), both averaged over the last 5 flow-throughs (the flow is unsteady: the drag standard deviation is 3-4% of its mean, Q about 15%). Errors are relative to the 48 cells/D run with the option; master at 48 cells/D is within 0.3% (drag) and 1.8% (Q) of it, so that reference is not an artifact of the option.

Cold wall (gas 600 K, wall 300 K):

cells/D gas mass drift per flow-through, master branch drag err., master branch Q err., master branch
16 +0.96% -2e-14 +2.4% -1.7% +13.5% +2.3%
24 +0.78% -3e-14 +1.9% -0.8% +10.1% +0.5%
32 +0.53% -4e-14 +1.8% -0.2% +6.7% -0.1%
48 +0.09% -6e-14 +0.3% (ref.) +1.2% (ref.)

The drift is the mean over the 10 flow-throughs; on master at 16 cells/D it is +2.1% in the first and +0.9% in the last. With the option the gas mass is constant to round-off. Absolute values at 48 cells/D with the option: drag 0.1821 N/m, Q 54.98 W/m.

Neutral wall (gas 300 K, wall 300 K, heated only by dissipation):

cells/D drift per flow-through, master branch drag err., master branch Q err., master branch
16 +0.37% -2e-14 -0.35% -1.7% +30.6% +5.4%
24 +0.28% -3e-14 -0.18% -1.1% +22.9% +3.6%
32 +0.15% -4e-14 +0.23% -0.3% +12.4% +1.4%
48 -0.03% -6e-14 -0.13% (ref.) -1.8% (ref.)

3D (periodic cubic array of spheres, volume fraction 0.2, cold wall, 8 flow-throughs, A100):

cells/D drift per flow-through, master branch drag, master branch Q, master branch
24 +1.19% -2e-14 7.34e-6 7.18e-6 2.38e-3 2.12e-3
32 +0.86% -3e-14 7.30e-6 7.18e-6 2.29e-3 2.09e-3

(Two resolutions only, so no convergence statement for 3D; WENO5 needs 26 or more cells per direction, so 16 cells/D does not run.)

What this shows, and what it does not:

  • The mass drift is real on master, has the sign and order of magnitude expected, and decays with resolution. With the option it is round-off.
  • The heat rate is much closer to the converged value with the option at every resolution (cold wall 2.3% against 13.5% at 16 cells/D; neutral wall 5.4% against 30.6%).
  • The drag gain is smaller than hoped, and for the neutral wall it is worse. For the cold wall the option moves the drag error from +1.8-2.4% to -0.2 to -1.7%, so it is smaller from 24 cells/D on and converges from below. For the neutral wall master's drag is already within 0.4% at every resolution and the option is further from the reference at the coarse grids (-1.7% at 16, -1.1% at 24, -0.3% at 32 cells/D). I did not find the 4-6% default drag error that an earlier 2D replica suggested.
  • Part of the master-vs-branch difference in Q and drag comes through the gas mass: on master the box gas density rises 5-10% over the run, which changes the heat and drag independently of the wall treatment. I did not separate that from the direct effect of the wall.
  • Cost: with the option the step is more expensive (extra face kernels and buffer exchange). The positivity-factor kernel (s_ib_flux_wall_prepare) uses no per-thread arrays, (removing the arrays is what changed it; I assume the amdflang OpenMP-offload build put them in scratch memory, I did not inspect the generated code): on the MI300A at 96 cells/D it takes 0.030 ms per call (9.0 ms of 100 steps, 1.0% of kernel time) against 3.69 ms per call (1107 ms, 57%) with the arrays, where the option cost +80% per step (9.6 -> 17.4 ns/gp/eq/rhs, two repeats). With the kernel as committed the option is within run-to-run noise on the MI300A (10.66 and 10.70 on; 9.66 and 11.59 off ns/gp/eq/rhs). On the A100 (nvfortran 25.11, GPU 0 otherwise idle, 96 cells/D, 100 fixed steps, three repeats each, interleaved) it is +10.5%: 5.30 (5.29-5.31) off, 5.85 (5.81-5.89) on ns/gp/eq/rhs. The earlier A100 sweep at 16-48 cells/D, with other jobs on the same GPUs (indicative only), gave 173/141, 72.8/60.9, 38.3/36.9, 18.5/16.1 ns/gp/eq/rhs (branch/master), i.e. +4% to +23%.
  • MI300A, 16 and 24 cells/D, branch with option off and on: drift, drag and Q equal the A100 master and branch values to all printed digits (e.g. drift +9.637e-3 per ft at 16 cells/D off; drag 0.1865 N/m off, 0.1789 on). 2 ranks (a seam through the domain) at 32 cells/D with the option give the same drift, drag and Q as 1 rank to the digits printed.

Tests

New (all Chemistry label, override_tol = 1e-6 for the cross-vendor single-precision output, goldens generated on the MI300A):

  • 2D -> Chemistry -> IBM Reacting Surface -> Flux Wall -> Trace Species (3F5FE078): the reacting cylinder on 48 x 48.
  • 2D -> Chemistry -> IBM Inert Surface -> Flux Wall (8B2A7F3E): the same cylinder with surface_reaction = 0 in the freestream.
  • 2D -> Chemistry -> IBM Inert Surface -> Flux Wall -> Closed Box (FAF04D7A): inert cylinder in the periodic box of 2D_ibm_wall_mass_budget.
  • test_case_validator.py: inert no-slip bodies accepted; slip and blowing inert bodies alone rejected.

Results at the head of this branch (./mfc.sh test --only ..., one rank):

MI300A (amdflang, --gpu mp) A100 (nvfortran 25.11, --gpu acc)
label Chemistry 40/40 passed (before the tolerance edit, 3 new tests at 1e-10 on the generating vendor) 37/40; the 3 failures are the new tests at the then 1e-10 tolerance (1e-8 to 1e-7 relative, single-precision output)
label IBM 85/85 85/85
Chemistry and IBM together 1/1 not run
3 new tests and Flux Wall (C1F0BCB1), final tolerances 4/4 4/4 (goldens from the MI300A)

No existing golden was regenerated or changed on either vendor. ./mfc.sh format leaves everything unchanged and ./mfc.sh precheck passes.

Limitations

  • Not run: CPU builds of the two labels, Cray CCE, GNU, Intel, NVHPC with OpenMP offload, more than 2 ranks, --single, --mixed. The A100 binaries were built before a comment and whitespace reformat of m_ibm.fpp and m_chemistry.fpp (the code is the same; I did not rebuild). Commit 1 on its own was not built or tested separately; its m_ibm.fpp is the theta hunk of the final file.
  • Moving, slipping or blowing inert bodies keep the ghost-state wall, as do hypoelasticity, igr, cylindrical coordinates and ib_cutvol (rejected as before). Only one mechanism (the carbon/air one) was used; 3D has one sphere configuration; ib_wall_order = 2 and ib_ip_interp = 2 were not combined with the new wall.
  • The threshold 1e-6 of commit 1 was not tuned. The 12% burn-rate loss was measured on one case (one mechanism, 48 x 48); the table for the mass-budget example attributes its 10-18% shortfall to the same cause without printing theta there.
  • The flux-wall columns of the 2D_ibm_wall_mass_budget README table are updated to the numbers measured here (defect). Its N2-leak column is not updated: I measure -4e-9 to -5e-9 of the gasified mass for it on both master and branch, against the 1e-10 listed, and did not find why.
  • Drag and Q are time averages of unsteady flows over 5 flow-throughs with one starting condition; the quoted errors have noise of a few tenths of a percent that I did not quantify. Q is a heat rate, not a Nusselt number (no bulk-temperature normalization).

This PR was made with Claude Code (AI).

The positivity factor theta of a fluid cell caps all of its wall fluxes so that they take at most
0.9 of any consumed species in one forward-Euler step. A species with a negligible share of the
consumption that the cell holds almost none of (OH: rho Y ~ 1e-46 against a wall rate ~ 1e-45 per
step) set theta ~ 0 and starved the whole wall. Species carrying less than 1e-6 of the cell's wall
consumption no longer set theta.

On a 48 x 48 grid the reacting-cylinder test starved from step ~27 and the burn rate fell 12 % by
2e-5 s; the 24 x 24 Flux Wall test is unaffected (theta stays 1), so its golden does not change.
Adds the 48 x 48 case as a test and updates the flux-wall columns of the mass-budget example table.
ib_flux_wall now also covers fixed, no-slip, non-blowing inert bodies (surface_reaction = 0): their
wall faces keep a zero source, so they carry no mass or species, only conduction, pressure and
viscous stresses. This removes the ghost-cell gas exchange across the staircase faces that makes the
gas mass of a closed periodic box with fixed isothermal inert bodies drift. An inert body that slips
or blows keeps the ghost-state wall. The option stays opt-in (default F); results with it off do not
change.

The case validator accepts the option with such inert bodies (it still needs at least one reacting
or no-slip, non-blowing body, and every body fixed). Adds two inert flux-wall tests: the reacting
cylinder example with surface_reaction = 0, and a closed periodic box.
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Claude Code Review

Head SHA: 1b0d007

Files changed:

  • 9
  • docs/documentation/case.md
  • examples/2D_ibm_wall_mass_budget/README.md
  • src/common/m_chemistry.fpp
  • src/simulation/m_ibm.fpp
  • src/simulation/m_riemann_solver_hllc.fpp
  • toolchain/mfc/case_validator.py
  • toolchain/mfc/params/descriptions.py
  • toolchain/mfc/test/cases.py
  • toolchain/mfc/test_case_validator.py

Findings:
No issues found. Checked the changed hunks against the available context files (src/common/m_chemistry.fpp, toolchain/mfc/test_case_validator.py, toolchain/mfc/params/descriptions.py, the example README) and the diff. The m_ibm.fpp, m_riemann_solver_hllc.fpp, case_validator.py and cases.py hunks were checked from the diff only, because they have no context file. The validator's inert-body test (no-slip, v_blow and inj_species not positive) matches the Fortran wall-setup condition in s_ib_flux_wall_setup. I did not verify that the wall rates of inert bodies are zero in s_ibm_correct_state, because that code is outside the diff.

f_wall_face_sum built per-thread arrays h(3) and c(3), which the amdflang
OpenMP-offload build placed in scratch memory. It now sums the neighbour terms
through f_wall_face_term, so only the order of the floating-point additions
changes.

MI300A (amdflang AFAR, 96 cells/D, 100 steps, rocprofv3): s_ib_flux_wall_prepare
took 3.69 ms per call (1107 ms of the 1950 ms of kernel time) before and
0.030 ms per call (9.0 ms in total) after. ib_flux_wall step cost went from
+80% (9.6 -> 17.4 ns/gp/eq/rhs) to within run-to-run noise (10.7 on vs 9.7 and
11.6 off).
@sbryngelson
sbryngelson marked this pull request as ready for review November 13, 2026 21:27
@sbryngelson
sbryngelson requested a review from a team as a code owner November 13, 2026 21:27
@sbryngelson
sbryngelson requested review from wilfonba and removed request for a team November 13, 2026 21:27
@github-actions

Copy link
Copy Markdown

Lines of Code

File Lines Diff
src/simulation/m_ibm.fpp 2865 +7
Directory Lines Diff
simulation 39358 +7
total 59323 +7

@codecov

codecov Bot commented Nov 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.16%. Comparing base (2224b0c) to head (54df912).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
src/simulation/m_ibm.fpp 66.66% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2213      +/-   ##
==========================================
+ Coverage   65.15%   65.16%   +0.01%     
==========================================
  Files         110      110              
  Lines       29555    29559       +4     
  Branches     4476     4476              
==========================================
+ Hits        19256    19263       +7     
+ Misses       6743     6742       -1     
+ Partials     3556     3554       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sbryngelson
sbryngelson merged commit 2c942ef into MFlowCode:master Nov 14, 2026
52 of 54 checks passed
@sbryngelson
sbryngelson deleted the feat/ib-flux-wall-inert branch November 14, 2026 05:14
sbryngelson added a commit to sbryngelson/MFC that referenced this pull request Nov 14, 2026
…s ghost temperature also moves

8B2A7F3E, FAF04D7A and 3F5FE078 (MFlowCode#2213 inert and trace-species flux walls) and
F8D466BD and C02C399E (MFlowCode#2200 thermostat, goldens regenerated for MFlowCode#2205)
were generated on master without the isothermal ghost change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant