SOTA: integrating large, stiff soot/PAH mechanisms efficiently#
Reference notes from the CRECK “cold-band” CVODE investigation
(PR #435, closed unmerged;
its standalone Cantera bench scripts/creck_cold_band_bench.py lives on that
branch). Captures what we found mechanistically, what’s actionable now, and
what the literature offers for going further. Not a plan — a map of options,
so the next person doesn’t have to re-derive or re-search this.
Status (2026-09-20)#
The opt-in
rtol/atolTubeFurnace YAML properties described below shipped with PR #437.The first real model to need them is CH5RVP PH1 (PR #423): 300 K unheated legs on both sides of the heated zone. At the 1e-12 default the nominal case was not reproducible (crash locally; two CI runs of the same commit differing by 2x in outlet C2H4).
atol: 1.0e-14fixed that and cut the CI solve from 243 s to 42 s; soot yield and H2 then converge to <0.3 % across rtol 1e-6..1e-8 and a dense-Newton check, while outlet C2H2/C2H4 still scatter ~15 % between settings (decided in the exit-ramp quench).The full explanation of the cold-leg cost, and the options to skip the frozen chemistry there, are in issue #442. Even at atol 1e-14 the two legs are 55 % of the CI march and 85 % of a local one. The team is leaning towards dynamic adaptive chemistry (below).
What we actually found#
Marching a closed parcel from torch-outlet conditions (~1777 K) through a wall-cooled tube toward 300 K on the full CRECK 452-species soot mechanism, CVODE’s cost is dominated by a Newton-failure storm that gets worse, not better, as the gas keeps cooling — not confined to a narrow band as originally hypothesized. In the severe case it doesn’t just get slow, it crashes: CVODE eventually accepts a state with negative moles for a few steps before a fatal, unrecoverable error.
The mechanism, traced to the solver’s own math: CVODE weighs convergence per state component as
w_i = 1 / (rtol·|y_i| + atol_i)
(SUNDIALS CVODE Mathematical Considerations). When |y_i| is small, atol
alone sets the weight — rtol does nothing for trace species. That’s
already the textbook reason atol matters here. What makes it worse for
IdealGasConstPressureMoleReactor specifically: its species state is
absolute moles (kmol), an extensive quantity that scales with however
much total gas happens to be in the reactor — confirmed directly:
r.component_index('CH4') # -> 14
r.get_state()[14] # -> 2.317e-06 (matches X(CH4) * n_total, not X(CH4) itself)
IdealGasConstPressureReactor’s species state is mass fraction Y_k — an
intensive, self-normalized [0,1] quantity, independent of reactor size.
So the same numerical atol=1e-12 means two very different things for the
two reactor families: “resolve to within 1e-12 kmol absolute” (which,
for a trace species whose entire absolute amount in a small reactor is
already ~1e-12 to 1e-13 kmol, is essentially no resolution) versus “resolve
to within 1e-12 of total mass” (a scale that never drifts). This is very
likely why IdealGasConstPressureReactor showed 48x fewer Newton failures
and ran 6.5x faster on the same severe case in the bench’s test 7, with
zero tuning.
SUNDIALS’ C API supports a per-component atol vector (SVtolerances);
Cantera’s Python ReactorNet.atol does not — it rejects a list
(TypeError: must be real number, not list, checked directly against
Cantera 3.2.0). So true per-species scaling isn’t reachable from Python
today without a Cantera-side change.
Near-term lever: scale atol to the reactor’s own total moles#
Actionable now, cheap to test, no new dependencies. Since the mole
reactor’s atol is absolute-extensive, a fixed constant is only
well-scaled for whatever total mole count it happened to be tuned against.
At reactor-build time (build_closed_carrier_net already has density,
volume, mean_molecular_weight), compute
n_total = density * volume / mean_molecular_weight
atol = n_total * relative_floor # e.g. relative_floor ~ 1e-10
instead of hardcoding 1e-12. This makes the tolerance self-scaling across
geometries and mass flows instead of requiring per-model manual tuning
(which is what the opt-in atol/rtol TubeFurnace YAML properties, now in
PR #437, require the user to do by hand — PH1 sets atol: 1.0e-14). Not
yet built or tested — needs the same evidence bar as everything else in this
investigation: checked against both the bench’s severe case and a real,
non-pathological case (tube_furnace/BASELINE) before it’s believed.
SOTA landscape for large-scale stiff mechanisms#
None of these validate literally switching between a small, hand-picked set of mechanisms at fixed temperature/composition thresholds — that risks discontinuities and mass-conservation artifacts right at the switch boundary, the same class of problem this investigation has been chasing, just relocated. The literature’s answers are smoother and more automatic versions of the same idea.
Dynamic Adaptive Chemistry (DAC / TDAC)#
The closest real match to “one mechanism per regime” — except continuous and automatic. At every computational state, a graph-based reduction (DRG / DRGEP) builds a local skeletal mechanism sized to what’s actually active there. Reported 15-70x speedups on 578-1099 species mechanisms (larger than CRECK’s 452). TDAC adds ISAT tabulation on top for more. Substantial build: nothing in Cantera does this automatically; it needs a DRGEP reduction layer wrapping every chemistry call.
Sketch for the Lagrangian parcel march (issue #442). The natural granularity is the checkpoint: the march already stops 200 times per tube to record the state and refresh the wall terms. A “light DAC” would, at each checkpoint:
Evaluate the net rates of progress of all reactions at the current state (
Kinetics.net_rates_of_progress) and build the DRGEP graph from them: for each species pair, the fraction of A’s production/consumption that runs through reactions involving B. Path-flux (PFA) is the usual refinement if two-step couplings matter.Starting from a target set (H2, C2H2, the BIN soot classes, CO — the KPI species), keep every species whose DRGEP coefficient exceeds a threshold (typically 1e-3 to 1e-2) and every reaction whose participants are all kept.
Zero the multipliers of the discarded reactions (
Kinetics.set_multiplier(0, i)), restore the kept ones to 1, andReactorNet.reinitialize()before the nextadvance. The species state vector keeps its full length; discarded species simply stop changing, so nothing is lost at a switch and elements are conserved exactly.
Expected effect: in a 300 K leg the local skeleton collapses to a few dozen radical-recombination reactions and the Newton iteration has nothing left to fail on; in the 1673 K plateau most of the mechanism stays active. The threshold, the target set and the checkpoint count are per-model, opt-in YAML properties, never a runtime heuristic in the shared engine (see the reverted attempts in PR #435 for why).
Acceptance bar: tube_furnace/BASELINE and every model that does not opt
in byte-identical; for a model that opts in, KPI species within the
tolerance scatter documented for it (PH1: soot yield and H2 within 0.3 %,
C2H2/C2H4 within ~15 %); the leg vs heated-zone time split reported before
and after. Cantera provides the multipliers and the rates; the DRGEP graph
(a few hundred lines) has to be written, or borrowed from an offline
skeletal-reduction tool such as pyMARS and applied per checkpoint.
Quasi-Steady-State Approximation (QSSA)#
Instead of switching mechanisms, algebraically collapse the fastest species (typically short-lived radicals) into zero-derivative constraints, removing exactly the stiffest eigenvalues from the Jacobian rather than working around them with tolerances. Well-precedented; tooling exists (Cantera- compatible QSS reduction scripts, CHEMKIN’s QSS preprocessor). A mechanism-file-level transformation of CRECK, not a solver setting.
In Situ Adaptive Tabulation (ISAT)#
Cache/interpolate the chemistry mapping in composition space instead of re-integrating every time; up to ~1000x reported in turbulent CFD. Less obviously useful for a single 1D Lagrangian march that doesn’t revisit similar states the way a 3D CFD grid with millions of cells does — the benefit comes from reuse, which one march has less of.
Flamelet Generated Manifolds (FGM) with soot progress variables#
Built specifically for a “fast gas chemistry + slow soot” structure: dual-scale FGM uses two progress variables, one for major species and a separate one for PAH/soot evolution, precomputed offline into a lookup table. Strongest conceptual match to “mixture of mechanisms per regime” for this exact combustion+soot case, but it’s a paradigm shift — table lookup replacing finite-rate integration entirely, not a tweak to the current approach.
Operator splitting: soot population balance vs. gas-phase chemistry#
The most incremental-feeling option: decouple the (slow) soot population balance from the (fast) deterministic gas-phase chemistry solve via Strang-type splitting, each on its own appropriate timescale/solver. Directly operationalizes “leverage the fast/slow split” without replacing the modeling paradigm. Specific literature exists on exactly this pairing.
Domain-specific prior art: OpenSMOKE++#
Built by the same group that publishes CRECK (Cuoci, Frassoldati, Faravelli, Ranzi) — a purpose-built, object-oriented C++ framework specifically for large detailed mechanisms with PAH/soot chemistry. The mechanism’s own authors evidently found generic tools insufficient for exactly this problem class, which is a strong signal this is a known, real limitation and not specific to how Bloc uses Cantera.
What’s actually in it:
Kinetic preprocessor — CHEMKIN-format compatible, so CRECK mechanisms load natively.
Ideal reactors — batch, plug-flow, CSTR/jet-stirred, shock-tube, rapid compression machine.
1D laminar flames — freely-propagating, burner-stabilized, counterflow diffusion; plus a flamelet/lookup-table generator (their own FGM-style tool, see above).
Soot module — PAHs with ≥20 carbon atoms plus an aerosol/particle population tracked via a sectional method, i.e. soot is not just more finite-rate kinetic species piled onto the same stiff ODE the way CRECK-in-Cantera currently treats it — it’s a separate representation designed for the slow end of the timescale split this whole investigation has been circling.
Own stiff ODE/DAE numerics — a dedicated library (
BzzMath, developed and maintained by the same authors), plus, per the suite’s own docs, “coupling to a wide range of external ODE, DAE, and NLS solvers” and dense/sparse direct and iterative linear solvers — not simply “Cantera’s CVODE by another name.”OpenSMOKE++4OpenFOAM/laminarSMOKE— couples the same chemistry core into OpenFOAM for full 2D/3D reacting-flow CFD, not just 0D/1D.GPP (Graphical Post-Processor) — sensitivity analysis, rate-of- production analysis, reaction-path diagrams via GraphViz.
Practical caveat before treating this as a real option: the project’s own
site states it is “completely free for Academic use” with registration
required. That phrasing does not extend to commercial use, and nothing
found states commercial terms — this would need to be checked directly
with the CRECK Modeling Lab / Politecnico di Milano before assuming Spark
could adopt it. Beyond licensing, this is an architecture decision, not a
small change: a different language ecosystem (C++, not the Cantera/Python
stack bloc is built on), different reactor/flame APIs, and a soot
representation (sectional method) that isn’t a drop-in replacement for
CRECK’s finite-rate PAH species — it would mean re-deriving soot output
against the sectional model’s own moments, not just swapping a solver call.
Complementary: analytical/sparse Jacobians (pyJac)#
A performance angle rather than a stiffness-avoidance strategy: generate
fully analytical, sparse Jacobians instead of finite-differencing them.
Cantera’s own sparse AdaptivePreconditioner already captures much of this
benefit for us — the bench’s test 4 confirmed removing it is 47x worse, not
better — so this is lower incremental value for Bloc specifically than for
a from-scratch solver.
Effort vs. payoff#
Option |
Effort |
Notes |
|---|---|---|
Auto-scaled |
Cheap, same-day testable |
No new dependencies; extends PR #435’s opt-in knob |
QSSA-reduced CRECK variant |
Medium — mechanism-level project |
Removes stiffness at the source; tooling exists |
Operator-split soot vs. gas-phase |
Medium-large — numerical-methods project |
Directly matches the fast/slow structure asked about |
DAC / TDAC |
Large — new infrastructure |
Best-documented speedups on comparable mechanism sizes |
FGM / tabulated soot chemistry |
Large — paradigm shift |
Closest conceptual match to “regime-based mechanisms” |
Switch to OpenSMOKE++ |
Large — architecture decision |
Prior art from CRECK’s own authors |
None of these should be started speculatively. Each needs the same evidence
bar this investigation has held throughout: checked against both a severe
reproduction case and a real, currently-working case (e.g.
tube_furnace/BASELINE) before anything lands. Earlier in this same
investigation, a plausible-sounding runtime fix that wasn’t checked against
a working case cost real regressions (non-deterministic outputs, and a
3.7x-11x slowdown on BASELINE) before it was reverted — see the PR #435
discussion for the full account.
Full reference list#
SUNDIALS CVODE Mathematical Considerations (WRMS norm,
atol/rtolweights): https://sundials.readthedocs.io/en/latest/cvode/Mathematics_link.htmlDynamic adaptive chemistry, gasoline surrogates: https://www.sciencedirect.com/science/article/abs/pii/S0010218009000819
Dynamic adaptive chemistry scheme for reactive flow computations: https://sciencedirect.com/science/article/pii/S1540748908000941
Tabulated dynamic adaptive chemistry (TDAC) for MILD combustion: https://pubs.acs.org/doi/10.1021/acs.energyfuels.8b01001
In situ adaptive tabulation (ISAT) — Wikipedia: https://en.wikipedia.org/wiki/In_situ_adaptive_tabulation
Pope (1997), ISAT original paper: https://tcg.mae.cornell.edu/pubs/Pope_CTM_97.pdf
QSSA overview: https://www.emergentmind.com/topics/quasi-steady-state-assumptions-qssa
QSSA combined with DRG for autoignition: https://doi.org/10.3390/modelling4040027
Progress variables for Flamelet Generated Manifolds: https://www.sciencedirect.com/science/article/abs/pii/S0010218011003087
Dual-scale FGM for soot in turbulent jet flames: https://pubs.aip.org/aip/pof/article-abstract/38/7/075122/3397552/Soot-formation-in-turbulent-non-premixed-jet
Operator splitting for soot population balance + gas-phase chemistry: https://www.researchgate.net/publication/222526279_Coupling_a_stochastic_soot_population_balance_to_gas-phase_chemistry_using_operator_splitting
OpenSMOKE++ framework: https://www.sciencedirect.com/science/article/abs/pii/S0010465515000715
Kinetic modeling of soot formation in turbulent nonpremixed flames (CRECK group): https://journals.sagepub.com/doi/abs/10.1089/ees.2007.0193
pyJac analytical Jacobian generator: https://arxiv.org/abs/1605.03262