Skip to content

Operator-split α-QSS sub-stepped chemistry integrator with per-rank adaptive substepping#1647

Open
sbryngelson wants to merge 13 commits into
MFlowCode:masterfrom
sbryngelson:aqss-adaptive-substepping
Open

Operator-split α-QSS sub-stepped chemistry integrator with per-rank adaptive substepping#1647
sbryngelson wants to merge 13 commits into
MFlowCode:masterfrom
sbryngelson:aqss-adaptive-substepping

Conversation

@sbryngelson

@sbryngelson sbryngelson commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

Adds an operator-split integrator for the chemistry reaction source, run as a fixed number of sub-steps per flow step (chem_params%reaction_substeps). The sub-step uses an α-QSS (Mott/CHEMEQ2 predictor–corrector) update on a constant-(ρ,e) reactor — matrix-free, Jacobian-free, and GPU-friendly (deterministic per-cell work, no thread divergence). This keeps stiff mechanisms stable where an explicit source in the flow RHS diverges (e.g. GRI-Mech methane).

On top of that, an optional per-rank adaptive sub-step count (chem_params%adap_substeps): each rank sizes its nsub from a local max-reduction of a stiffness metric over its own cells (floor reaction_substeps, ceiling reaction_substeps_max). Zero MPI collectives — bottleneck-equivalent to a global reduction since the slowest rank dominates anyway, but safe at scale. Default off, so existing behavior is unchanged.

Dependency

The integrator calls get_creation_destruction_rates (a single-pass routine filling creation + destruction in one concentration/rate evaluation, halving the rate-of-progress work per sub-step), added in pyrometheus/pyrometheus#116 and released in pyrometheus 1.1.1. toolchain/pyproject.toml pins pyrometheus == 1.1.1 — verified end-to-end that MFC installs it from PyPI and builds against it.

Validation

  • Chemistry regression suite: only the sub-stepped reactor golden (5D59DDF0) shifts (forward-Euler → α-QSS), max ~0.5% and confined to trace radicals; all reactions=F/substeps=0 goldens unchanged. Golden regenerated for α-QSS.
  • Adaptive nsub verified to track stiffness across [floor, ceiling] (floor in quiescent gas, ceiling only at the reaction front).
  • 1D reactive shocktube + 2D cellular/expanding/colliding detonations run stably at CFL ~0.5 (≈8× the substeps=0 stability limit); substeps=0 at that dt NaNs.
  • Builds + runs CPU and GPU (nvfortran/OpenACC); adaptive-off is bit-identical to the fixed-sub-step path by construction.

Files

  • src/common/m_chemistry.fpp — α-QSS integrator + adaptive probe
  • params/checker plumbing: m_derived_types.fpp, toolchain/mfc/params/definitions.py, m_global_parameters.fpp (×3), src/simulation/m_checker.fpp
  • src/simulation/m_rhs.fpp, m_time_steppers.fpp — operator-split call site
  • examples: 2D_reactive_shock_bubble, 1D_reactive_shocktube_adaptive, 2D_detonation_cell (the latter two demonstrate the automated cfl_const_dt + adaptive pipeline)
  • sub-stepped reactor test + docs

@sbryngelson
sbryngelson marked this pull request as ready for review July 16, 2026 20:04
Copilot AI review requested due to automatic review settings July 16, 2026 20:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds an operator-split α‑QSS chemistry integrator with fixed sub-stepping and optional per-rank adaptive sub-step selection, wiring it into the simulation timestep and exposing new configuration parameters and examples.

Changes:

  • Introduces operator-split reaction integration (s_chemistry_reaction_substep) and disables reaction RHS coupling when sub-stepping is enabled.
  • Adds new chemistry parameters (reaction_substeps, adap_substeps, reaction_substeps_max) across definitions, defaults, and input checking.
  • Expands tests/examples coverage and updates dependency pinning and goldens.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
toolchain/pyproject.toml Switches pyrometheus dependency to a pinned release version.
toolchain/mfc/test/cases.py Adds new example cases and a chemistry sub-stepping test case.
toolchain/mfc/params/definitions.py Registers new chemistry parameters for the toolchain.
tests/5D59DDF0/golden.txt Adds/updates golden outputs for a test case.
tests/5D59DDF0/golden-metadata.txt Adds/updates metadata for the golden generation run.
src/simulation/m_time_steppers.fpp Calls operator-split chemistry sub-stepper after the flow update.
src/simulation/m_rhs.fpp Skips reaction RHS contribution when operator-split sub-stepping is enabled.
src/simulation/m_global_parameters.fpp Adds defaults for new chemistry parameters (simulation).
src/simulation/m_checker.fpp Validates new chemistry parameters and combinations.
src/pre_process/m_global_parameters.fpp Adds defaults for new chemistry parameters (pre-process).
src/post_process/m_global_parameters.fpp Adds defaults for new chemistry parameters (post-process).
src/common/m_derived_types.fpp Extends chemistry_parameters with new fields and docstrings.
src/common/m_chemistry.fpp Implements α‑QSS operator-split sub-stepped reactor with adaptive per-rank sub-stepping.
examples/2D_reactive_shock_bubble/h2o2_xe.yaml Adds a new Cantera mechanism YAML for an example case.
examples/2D_reactive_shock_bubble/case.py Adds a new reactive shock-bubble example using sub-stepped chemistry.
examples/2D_detonation_cell/case.py Adds a cellular detonation example using adaptive sub-stepping.
examples/1D_reactive_shocktube_adaptive/case.py Adds a 1D adaptive sub-stepping shocktube example.
docs/documentation/case.md Documents reaction_substeps (but currently describes the wrong algorithm).

Comment on lines +171 to +175
#:if not MFC_CASE_OPTIMIZATION and USING_AMD
real(wp), dimension(10) :: Ys, cdot, ddot, y0, prod0, Lloss, alp
#:else
real(wp), dimension(num_species) :: Ys, cdot, ddot, y0, prod0, Lloss, alp
#:endif
Comment thread src/common/m_chemistry.fpp Outdated
Comment on lines +251 to +266
Ysum = 0._wp
$:GPU_LOOP(parallelism='[seq]')
do eqn = 1, num_species
wr = molecular_weights(eqn)/rho
prod_p = wr*cdot(eqn)
loss_p = wr*ddot(eqn)
Lbar = 0.5_wp*(Lloss(eqn) + loss_p/max(Ys(eqn), y_floor))
pbar = alp(eqn)*prod_p + (1._wp - alp(eqn))*prod0(eqn)
Ys(eqn) = y0(eqn) + dt_sub*(pbar - Lbar*y0(eqn))/(1._wp + alp(eqn)*dt_sub*Lbar)
if (Ys(eqn) < 0._wp) Ys(eqn) = 0._wp
Ysum = Ysum + Ys(eqn)
end do
$:GPU_LOOP(parallelism='[seq]')
do eqn = 1, num_species
Ys(eqn) = Ys(eqn)/Ysum
end do
| `chem_params%%reactions` | Logical | Enable chemical reactions |
| `chem_params%%gamma_method` | Integer | Methodology for calculating the heat capacity ratio |
| `chem_params%%transport_model` | Integer | Methodology for calculating the diffusion coefficients |
| `chem_params%%reaction_substeps` | Integer | Sub-steps for operator-split reaction integration (0 = off) |
Comment thread docs/documentation/case.md Outdated
| `cantera_file` | String | Cantera-format mechanism file (e.g., .yaml) |

- `chem_params%%transport_model` specifies the methodology for calculating diffusion coefficients and other transport properties, `1` for mixture-average, `2` for Unity-Lewis
- `chem_params%%reaction_substeps` controls how the reaction source is integrated. With `0` (default) the net production rates are added to the flow right-hand side and advanced by the flow time stepper (fine for hydrogen). With a value `> 0`, the reaction is instead integrated by operator splitting after each flow update: every cell's constant-density, constant-internal-energy reactor is advanced over the timestep with that many forward-Euler sub-steps. This decouples the (often much faster) chemical timescale from the flow timestep and is required for stiff mechanisms — e.g. hydrocarbons such as GRI-Mech methane, which otherwise diverge on the first step
Comment thread toolchain/pyproject.toml
"cantera>=3.1.0",
#"pyrometheus == 1.0.5",
"pyrometheus @ git+https://github.com/pyrometheus/pyrometheus.git",
"pyrometheus == 1.1.1",
Comment thread tests/5D59DDF0/golden-metadata.txt Outdated

Invocation: test --generate --only 5D59DDF0 -j 8
Lock: mpi=Yes & gpu=Acc & debug=Yes & reldebug=No & gcov=No & unified=No & single=No & mixed=No & fastmath=No
Git: 30a411e98a697d4b7c12a342d9e26f3b46d852ca on chemistry (dirty)
Comment on lines +389 to +391
chem_params%reaction_substeps = 0
chem_params%adap_substeps = .false.
chem_params%reaction_substeps_max = 0
@sbryngelson

Copy link
Copy Markdown
Member Author

Thanks for the review — addressed in 40420e1 and d4d0af9.

Fixed

  • Ysum division-by-zero in the corrector — good catch. Guarded: when every species clips to zero, fall back to the sub-step's starting composition y0 (already normalized and mass-consistent) instead of dividing by a vanishing sum.
  • Docs said "forward-Euler" — corrected in case.md to describe the α-QSS (Mott/CHEMEQ2) predictor–corrector with creation/destruction splitting and the Padé α-weighting, and documented adap_substeps / reaction_substeps_max.
  • Stale PR description / dependency pin — the description was written when this was a draft blocked on pyrometheus#116. That PR is now merged and released as pyrometheus 1.1.1; toolchain/pyproject.toml pins == 1.1.1 (kept exact, matching the repo's convention, for reproducibility). Verified end-to-end that MFC installs 1.1.1 from PyPI and builds against it. Description updated.
  • Golden generated from a dirty tree — regenerated 5D59DDF0 from a clean commit in a release (--no-debug) build; the values are byte-identical, only the provenance metadata is now deterministic.

Respectfully keeping as-is (with rationale)

  • dimension(10) in the not MFC_CASE_OPTIMIZATION and USING_AMD branch — this follows a pervasive existing MFC idiom (the same fixed-size-10 fallback appears in ~7 places across m_chemistry.fpp, m_variables_conversion.fpp, m_cbc.fpp, m_hyperelastic.fpp); it's an AMD-flang automatic-array workaround. Case-optimized builds (the norm, and required for any >10-species mechanism like GRI-Mech anyway) take the #:else branch and size to num_species correctly. Changing the constant is a codebase-wide decision that risks the AMD build, so it's out of scope for this PR — happy to file a separate issue to unify these.
  • Defaulting reaction_substeps_max to the floor — this would make adap_substeps = T a silent no-op (nsub could never rise above reaction_substeps, i.e. adaptivity that never adapts), which is a subtler footgun than the current behavior. Enabling adaptivity is a deliberate choice, so the checker fails loud with an actionable message (reaction_substeps_max must be >= reaction_substeps when adap_substeps = T). I'd rather keep that explicit.

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 67.04545% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.61%. Comparing base (e090d56) to head (019df56).
⚠️ Report is 14 commits behind head on master.

Files with missing lines Patch % Lines
src/common/m_chemistry.fpp 61.97% 25 Missing and 2 partials ⚠️
src/simulation/m_time_steppers.fpp 50.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1647      +/-   ##
==========================================
- Coverage   60.95%   59.61%   -1.35%     
==========================================
  Files          83       83              
  Lines       20003    21194    +1191     
  Branches     2983     3137     +154     
==========================================
+ Hits        12193    12634     +441     
- Misses       5782     6452     +670     
- Partials     2028     2108      +80     

☔ 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.

…d reactor seed

q_T_sf holds the pre-update RK-stage temperature, so a just-shock-heated cell was
probed at its stale (cool) temperature, under-sizing the adaptive nsub exactly at an
ignition front. Recompute T from the fresh post-advection internal energy before the
stiffness probe and the first predictor sub-step. Reactor golden 5D59DDF0 unchanged
(at-rest reactor: same energy -> same T).
Address review nits: the reactor header now notes the adaptive nsub range; the
example comments say one nsub per rank (sized by the rank's stiffest cell) instead
of implying spatially-local sub-cycling; stiff_target=0.5 is documented as an
uncalibrated accuracy/cost knob (never stability); the corrector's alpha reuse is
noted as an intentional CHEMEQ2 simplification; and the header records the
first-order Lie-Trotter split and the sum-to-one (not element-conserving) renorm.
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.

2 participants