fix emit/surf: mass-flow inlet (mflow) and time-averaged subsonic BC - #27
Open
stanmoore1 wants to merge 6 commits into
Open
fix emit/surf: mass-flow inlet (mflow) and time-averaged subsonic BC#27stanmoore1 wants to merge 6 commits into
stanmoore1 wants to merge 6 commits into
Conversation
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
force-pushed
the
claude/issue-429-pr-430-review-aq2kfe
branch
from
June 29, 2026 19:43
d7ba192 to
5d86ba4
Compare
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two boundary-condition capabilities to
fix emit/surf, as keywords on theexisting fix (no new fix classes):
mflow Mdot Tin [window N]— a mass-flow-rate inlet that injects to hit a targetmass flow rate
Mdotthrough the surface group.subsonic Psub Tsub [window N]— the existing subsonic pressure BC gains anoptional moving-average
windowthat damps the statistical noise of theinstantaneous 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 densityfrom 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) overthe true clipped cell/surf area. The mismatch causes:
species[0].massis wrong for any real mixture.What this PR does
mflowchooses one global number density each step so the expected total insertedmass rate equals the target:
S is built from the true clipped emit area, the actual
mol_inflowflux, andper-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
windowapplies an EMA to the cell streaming velocity.Implemented in
src/fix_emit_surf.{h,cpp}(parser +mflow_grid()+ the subsonicwindow), reusing the existingmol_inflow(), subsonic sort/grid machinery, and theemission tally. Docs in
doc/fix_emit_surf.txt; examplesin.emit.surf.mflow,in.emit.surf.mflow.single,in.emit.surf.subsonicwith mpi_1/mpi_4 reference logs.How this differs from PR sparta#430
fix emit/surf, not new forked fix classes. Dev subsonic mflow boundary conditions sparta/sparta#430 copied ~1600 linesinto
fix_emit_surf_mflow.*/fix_emit_surf_timeavg.*; this is a ~75-line diff onthe live fix. The fork was stale and re-introduced the double-emission bug fixed
upstream in Do not emit particles in cells that are outside of the flow volume sparta/sparta#431 (the
cinfo[icell].volume == 0.0guard); the keyword inherits thatguard (and
twopass,custom, region, etc.) for free.Mdot/Srescale (true area + real flux + per-species mass, MPI-reduced) insteadof
Mdot/(species[0].mass*v_n*inlet_area).inlet_areaargument — computed internally (this is the 2x-geometry fix).vmin— obsolete (it only existed to avoid div-by-zero in Dev subsonic mflow boundary conditions sparta/sparta#430's formula).compute momentumdropped — SPARTA already measures mass flux viacompute surf ... mfluxon a transparent surface.normal_vel_dotin the vmin clamp; aper-task
std::coutdebug line).How this fixes sparta#429
mol_inflowflux as theinsertion, so target and realization are consistent.
windowEMA provides the requested smoothing.Verification (serial + MPI via OpenMPI 4.1.6)
tests/surf_mflow_testdeck), 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).
circle the true emit area is 3.14x a naive guess yet the rate is on target.
window 0 is byte-identical to stock subsonic.
emit/surfexamples reproduce reference logs bit-for-bit(serial and 4-rank MPI). valgrind clean.
Notes / limitations
-k onthese keywords safely error (the KOKKOS port is separate).🤖 Generated with Claude Code
Generated by Claude Code