Skip to content

fix emit/surf: mass-flow inlet (mflow) and time-averaged subsonic BC - #27

Open
stanmoore1 wants to merge 6 commits into
masterfrom
claude/issue-429-pr-430-review-aq2kfe
Open

fix emit/surf: mass-flow inlet (mflow) and time-averaged subsonic BC#27
stanmoore1 wants to merge 6 commits into
masterfrom
claude/issue-429-pr-430-review-aq2kfe

Conversation

@stanmoore1

Copy link
Copy Markdown
Owner

Summary

Adds two boundary-condition capabilities to fix emit/surf, as keywords on the
existing fix (no new fix classes):

  • mflow Mdot Tin [window N] — a mass-flow-rate inlet that injects to hit a target
    mass flow rate Mdot through the surface group.
  • subsonic Psub Tsub [window N] — the existing subsonic pressure BC gains an
    optional moving-average window that damps the statistical noise of the
    instantaneous cell velocity (Sun & Boyd 2005).

This is a corrected, minimal-footprint alternative to the upstream proposal in
sparta#430 (issue sparta#429).

Background — issue sparta#429

sparta#429 (and PR sparta#430) added a mass-flow inlet and a time-averaged subsonic inlet, but
reported two unexplained errors: a steady ~2% deviation from the target mass flow on
a simple channel, and ~2x too much flow on complex geometries.

Root cause (PR sparta#430's subsonic_grid):
nsubsonic = mflow/(species[0].mass * v_n * inlet_area) back-solves a number density
from a convective-only flux model, applied per cell with a user-typed inlet_area,
the local normal velocity, and a single species mass — while particles are actually
inserted with SPARTA's full Maxwellian flux (mol_inflow, convective + thermal) over
the true clipped cell/surf area. The mismatch causes:

  1. ~2x geometry error: realized flow scales by (true emit area)/(typed inlet_area);
  2. ~2% bias: convective-only target vs convective+thermal insertion (grows as Mach drops);
  3. mixture error: species[0].mass is wrong for any real mixture.

What this PR does

mflow chooses one global number density each step so the expected total inserted
mass rate equals the target:

nrho = Mdot / S,
S = sum over tasks,species of  mol_inflow(indot, vscale, fraction) * area * mass / cell_weight

S is built from the true clipped emit area, the actual mol_inflow flux, and
per-species masses, and is MPI_Allreduce'd across ranks. So the realized rate is
correct for any geometry/mesh/mixture/decomposition, with no user inlet-area input.
The optional window applies an EMA to the cell streaming velocity.

Implemented in src/fix_emit_surf.{h,cpp} (parser + mflow_grid() + the subsonic
window), reusing the existing mol_inflow(), subsonic sort/grid machinery, and the
emission tally. Docs in doc/fix_emit_surf.txt; examples in.emit.surf.mflow,
in.emit.surf.mflow.single, in.emit.surf.subsonic with mpi_1/mpi_4 reference logs.

How this differs from PR sparta#430

How this fixes sparta#429

  • Mass-flow ~2% deviation: gone — realized rate uses the same mol_inflow flux as the
    insertion, so target and realization are consistent.
  • Mass-flow ~2x on complex geometry: gone — true emit area is used (no hand-typed area).
  • Subsonic pressure noise: the window EMA provides the requested smoothing.

Verification (serial + MPI via OpenMPI 4.1.6)

  • Development of improved subsonic boundary condition and mass flow rate boundary condition sparta/sparta#429 microchannel (the tests/surf_mflow_test deck), target 4e-10 kg/s:
    realized inlet rate ratio 0.998–1.000 on 1 and 4 ranks (was ~2% off / 2x in Dev subsonic mflow boundary conditions sparta/sparta#430).
  • Linearity: single-species, ratios 1.007/1.007/0.999/0.992 across an 8x range.
  • Geometry/mesh independence: ratios 0.99–1.03 across grid 5→40; on the curved
    circle the true emit area is 3.14x a naive guess yet the rate is on target.
  • Rank independence: ratios 1.007/1.016/0.995/0.986 on 1/2/4/8 ranks.
  • Subsonic window: per-step insertion std 14.9 → 9.9 → 6.6 for window 0/20/200;
    window 0 is byte-identical to stock subsonic.
  • Regression: all stock emit/surf examples reproduce reference logs bit-for-bit
    (serial and 4-rank MPI). valgrind clean.

Notes / limitations

  • The target rate is met in an expected (statistical) sense; measure as a time-average.
  • CPU-only here; under -k on these keywords safely error (the KOKKOS port is separate).

🤖 Generated with Claude Code


Generated by Claude Code

claude and others added 4 commits June 29, 2026 19:42
Adds a mflow keyword to fix emit/surf that imposes a target mass flow
rate through the emitting surface group, rather than a prescribed
pressure. This is an alternative, corrected implementation of the
mass-flow boundary condition proposed in sparta/sparta issue sparta#429 and
PR sparta#430.

The approach in PR sparta#430 back-solved a per-cell number density from a
convective-only flux model, n = mdot/(m * v_n * A), using a user-typed
inlet area, the local normal velocity, and a single species mass, while
particles were actually inserted with the full Maxwellian inflow flux
(mol_inflow) over the true clipped cell/surf overlap area. The mismatch
produced a systematic bias (~2x over-insertion at low Mach) and a flow
rate that scaled with (true emit area)/(typed area), giving ~2x too much
flow on complex geometries. It also re-introduced the double-emission
bug fixed upstream in PR sparta#431 by forking an older copy of the file.

Instead, mflow_grid() chooses a single global number density each step
so the expected total inserted mass rate equals the target:
  nrho = mdot / S,
  S = sum over tasks,species of mol_inflow(indot,vscale,fraction)
                                * area * mass / cell-weight
reduced across procs with MPI_Allreduce. Because S is built from the
true overlap area, the actual mol_inflow flux, and per-species masses,
the realized rate is correct for arbitrary geometries and mixtures,
independent of mesh resolution, with no user inlet-area input. Built as
a keyword on the existing fix (reusing the subsonic PONLY per-task
vscale machinery) it also inherits the PR sparta#431 volume==0 guard.

An optional window N sub-keyword applies a moving average to the cell
streaming velocity to damp statistical fluctuations.

Verified (serial): realized mass rate measured from the fix insertion
tally converges to the requested value within statistical noise on the
issue sparta#429 microchannel (single-species) and on a new multi-species
circle example (examples/emit/in.emit.surf.mflow). Stock emit/surf
normal and flow examples reproduce their reference logs bit-for-bit
(new path gated on mflowflag).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLYXroGvf89s6K4tRYwHar
Co-authored-by: Stan Moore <stanmoore1@gmail.com>
Companion to in.emit.surf.mflow that uses one species so the mean
inserted-particle mass is exact. The fix's cumulative insertion tally is
converted to a realized mass flow rate (v_mrate) which converges to the
requested target (5e-26) within statistical noise, so the reference log
itself documents that the mass flow rate is met.

The realized rate is independent of grid resolution because the inlet
area is the true clipped surface/cell overlap area summed over all
emitting cells and procs, not a user input; this was verified by
re-running at grid resolutions from 5x5 to 40x40 (rate unchanged to
within noise), which is the property PR sparta#430's per-cell formula lacked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLYXroGvf89s6K4tRYwHar
Co-authored-by: Stan Moore <stanmoore1@gmail.com>
Completes the standard mpi_1/mpi_4 reference-log pair for both mflow
examples. Built with a real MPI (OpenMPI 4.1.6) and run on 4 ranks.

The realized mass flow rate is independent of the number of MPI ranks:
the single-species example converges to the 5e-26 target on 1, 2, and 4
ranks (ratios 1.007, 1.016, 0.995), since the inflow density is set from
an MPI_Allreduce of the per-rank emit-flux sum S, so the global target
is met regardless of how inlet cells are distributed across ranks. Runs
are deterministic for a fixed rank count. Stock emit/surf examples still
reproduce their reference logs on the MPI build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLYXroGvf89s6K4tRYwHar
Co-authored-by: Stan Moore <stanmoore1@gmail.com>
Adds an optional "window Nwin" pair to the subsonic keyword of fix
emit/surf. It applies an exponential moving average to the cell
streaming velocity used by the subsonic boundary condition,
vstream = a*vnew + (1-a)*vold with a = 1/(Nwin+1), to damp the
statistical fluctuations of the instantaneous cell velocity (Sun & Boyd
2005). This is the corrected, non-forked equivalent of the
fix_emit_surf_timeavg fix proposed in sparta/sparta issue sparta#429 / PR
sparta#430, whose only substantive change over stock fix emit/surf was this
smoothing.

The default Nwin = 0 disables smoothing and takes the exact original
code path, so existing subsonic behavior and all stock emit/surf example
logs are unchanged (verified bit-for-bit). With Nwin > 0 the per-step
fluctuation of the inserted-particle count drops monotonically with
window size (std 14.9 -> 9.9 -> 6.6 for window 0 -> 20 -> 200 on a test
case), and the smoothing also reduces the noise-induced over-insertion
that arises because mol_inflow is convex in the streaming velocity.

The per-task EMA state persists between steps and is reset when tasks
are rebuilt (grid adapt / load balance), so the new example avoids
dynamic load balancing; with that, results agree across 1 and 4 MPI
ranks to within statistical noise and are deterministic for a fixed rank
count.

The redundant compute_momentum from PR sparta#430 is intentionally not added:
SPARTA already measures mass flux through (transparent) surfaces via
compute surf with the mflux/nflux keywords.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLYXroGvf89s6K4tRYwHar
Co-authored-by: Stan Moore <stanmoore1@gmail.com>
@stanmoore1
stanmoore1 force-pushed the claude/issue-429-pr-430-review-aq2kfe branch from d7ba192 to 5d86ba4 Compare June 29, 2026 19:43
claude and others added 2 commits July 24, 2026 18:59
Code review of the mflow/subsonic work surfaced several real defects,
all fixed here with new tests.

Normalization of the mass-flow target (S):

* Remove the 1/cinfo[weight] factor. ntargetsp already carries 1/weight
  while each inserted particle represents fnum*weight molecules, so the
  weight cancels from the physical mass rate; dividing S by it made the
  realized rate wrong whenever cell weighting was enabled. Verified with
  uniform cell weight 0.25: realized/target is now 1.0007 (was 4x off).

* Weight species mass according to how perform_task actually picks the
  species. With perspecies yes each species is inserted per its own flux
  (flux-weighted mass, as before); with perspecies no a single species is
  drawn from the mixture fractions, so the mean inserted mass is the
  fraction-weighted mean. Previously the flux weighting was used for both,
  biasing the rate for multi-species mixtures: ~0.2% for N/O but ~3.9x for
  a 50/50 H2/Xe mixture. Verified: H2/Xe perspecies no is now 1.006, and an
  equal-mass two-species case gives 1.006/1.008 for perspecies no/yes.

Time-averaging (window) semantics:

* Keep the moving average in a new per-task vcom field instead of vstream.
  The subsonic PONLY pressure correction is added to vstream after the
  average, so with the previous code it was fed back into the next step's
  average and integrated, amplifying the correction gain by ~(window+1).
  Now the correction is applied once per step, as intended.

* An empty cell (np == 0) no longer zeroes the average outright; a zero
  sample is blended in at the averaging rate, matching mflow_grid and
  keeping a single transient from discarding the whole history.

Guards and diagnostics:

* mflow is now a subsonic_style (MFLOW) rather than a separate flag that
  impersonated PONLY, so an inconsistent state is unrepresentable, and
  giving both subsonic and mflow is an explicit error instead of either a
  NULL vscale dereference or a silently ignored pressure setting.
* Error out on mflow with a region: region-rejected insertions are not
  resampled, which silently reduced the realized rate.
* Parse the window value with input->inumeric instead of atoi, so "1e3"
  or a typo is an error rather than a silent 1 or 0.
* mflow with custom options now reports mflow, not subsonic.

Also fold the duplicated window parsing into option_window(), hoist the
constant per-species vscale out of the per-task loop, and merge the two
mflow_grid passes. The examples no longer combine window with fix balance,
which rebuilt tasks and restarted the average every 10 steps, and the doc
records the grid-change caveat plus the weighting and perspecies behavior.

Regression: all stock emit/surf examples reproduce their reference logs
bit-for-bit, serial and on 4 MPI ranks (window 0 takes the original code
path). Linearity, mesh independence (grid 5-40) and rank independence
(1/2/4/8 ranks) all hold to within statistical noise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Stan Moore <stanmoore1@gmail.com>
Claude-Session: https://claude.ai/code/session_01HLYXroGvf89s6K4tRYwHar
FixEmitSurfKokkos does not override option(), so the subsonic and mflow
keywords parse fine under the kk suffix and the run only aborted at the
first emission step, with a message naming subsonic even when the user
had written mflow. Check both in the Kokkos constructor instead, after
the base class has parsed the keywords, so the error is reported at the
fix command itself and names the keyword actually given. Verified: no
timesteps execute, and the mflow decks now report mflow.

Also add MFLOW to this file's copy of the subsonic_style enum so it stays
in sync with fix_emit_surf.cpp, and note the restriction in the docs.

Verified with a KOKKOS Serial build (cmake kokkos_mpi_only preset,
Kokkos_ENABLE_OPENMP=OFF) compiled with -DSPARTA_KOKKOS_EXACT: builds
clean, and the stock emit/surf examples (flow, normal, boundary, and both
region variants) reproduce their CPU reference logs bit-for-bit under
-k on -sf kk. A wider sweep over the emit, circle, free, collide,
surf_collide and adapt examples gives 7 bit-for-bit matches, 0
regressions, and 3 decks correctly rejected. The non-Kokkos serial and
MPI builds are unaffected: 16/16 emit/surf reference logs still match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Stan Moore <stanmoore1@gmail.com>
Claude-Session: https://claude.ai/code/session_01HLYXroGvf89s6K4tRYwHar
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.

Development of improved subsonic boundary condition and mass flow rate boundary condition

2 participants