Skip to content

Convection discretization rewrite#23

Open
speth wants to merge 37 commits into
mainfrom
convection-scheme
Open

Convection discretization rewrite#23
speth wants to merge 37 commits into
mainfrom
convection-scheme

Conversation

@speth

@speth speth commented Jul 6, 2026

Copy link
Copy Markdown
Owner

No description provided.

speth and others added 30 commits July 4, 2026 11:27
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add test/convergence/run_baselines.py and compare_baselines.py, plus
captured baseline JSON for the six curated example cases (single,
diffusion, twin, cylindrical outward/inward, laminarFlameSpeed) from
commit 5552b7d, the pre-implementation tip of this branch. These will
be diffed against the modified convection solver in later phases.
Add the shared advective-derivative kernel for the convection operator, with
a bit-identical FirstOrderUpwind path and a van Albada limited, upwind-biased
SecondOrderLimited path (spec 3.1-3.2). Includes gtest unit tests covering
constant/linear exactness, bit-identical legacy parity, ~2nd-order convergence
on smoothly nonuniform grids, no-new-extrema on monotone data, and upwind-branch
selection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013DZnCpPG1q1crycu8GPukv
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Add General.convectionScheme StringOption (default "secondOrderLimited",
allowed value "firstOrderUpwind") and plumb it through to
ConfigOptions::convectionScheme, mirroring the splittingMethod pattern.
Parsing the string to the ConvectionDifferencer::Scheme enum is Task 1.3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
…ontinuity

Replace the inline dTdx/dUdx/dWdx upwind loop in ConvectionSystemUTW::f()
with three ConvectionDifferencer::computeDerivatives() calls (branch
selection on rV, unchanged sign convention), and add the spec 3.3
trapezoidal continuity march gated on SecondOrderLimited in all
continuity-BC branches (Left forward; Zero forward+backward; Temp/Right/
Qdot forward+backward). FirstOrderUpwind keeps the legacy rectangle rule
verbatim. The differencer is a per-instance member of ConvectionSystemUTW,
resized alongside the system. ConvectionSystemSplit::setTolerances() parses
convectionScheme to the enum (throwing DebugException on unknown values),
stores it, and propagates it to utwSystem.

Verification: firstOrderUpwind is bit-identical to pre-change behavior.
Spot-check ran the test_flame_configs TestPremixedStrained config
(nThreads=1, convectionScheme='firstOrderUpwind') on the working tree vs a
clean build of pre-change commit f2eafc1; final T and x profiles matched
exactly (max|dT|=0.0, arrays bit-identical). gtest 27/27 and pytest 15/15
pass (the flame-config integration tests run under the default
secondOrderLimited scheme).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
…-2D velocity fix

Route ConvectionSystemY::f() through the per-instance ConvectionDifferencer
kernel (one call per f() evaluation) instead of the inline upwind loop. Each
species system owns its differencer and scratch vectors for TBB safety. The
standard path selects the upwind branch from the interpolated normal velocity
v as before; the quasi-2D path builds a node-wise velocity array from vrInterp
and passes it to the kernel, fixing the legacy uninitialized-v upwind-direction
bug (spec 3.4). Boundary rows (j=0 inflow balance, j=jj outflow) and the
quasi-2D ydot assembly form are unchanged. The scheme is propagated to each
species system in ConvectionSystemSplit::configureSolver().

FirstOrderUpwind remains bit-identical (verified: full final state parity
against 30a4c3c, max abs diff 0.0 for x, T, and all species Y).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Confirms firstOrderUpwind parity against the Phase 0 baselines (all six
curated cases pass compare_baselines.py's 0.02 threshold, five of six at
or below the ~1e-6 reproducibility floor) and characterizes
secondOrderLimited's deltas (small, physically consistent shifts; no
sign changes, negative Y, NaNs, or CVODE step-count blowup). Extends
run_baselines.py with a --scheme override flag and a
total_convection_steps field (summed from the per-timestep [C: N]
debug log lines) to support both checks.

Also documents an example_single-specific finding: same-code repeats
show termination-time sensitivity (thread-scheduling noise pushes the
steady-heat-release termination check to noticeably different sim
times), producing run-to-run profile differences larger than the
previously-recorded floor for that one case. Not a scheme-induced
regression, but flagged for follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Close the final whole-branch review findings: smoke-test the previously
never-executed stagnationPoint/fixedTemperature continuity branches under
both convection schemes (4/4 pass, results in test/convergence/README.md),
fix the stale species-propagation comment, document the Temp-BC anchor's
first-order inversion under secondOrderLimited (accepted per spec 3.2), and
reconcile the ConvectionDifferencer::resize() doc with the defensive
self-sizing fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Add run_convergence.py (case/scheme/rung-selectable harness for the spec
§6.4 grid-convergence study: strained/twin/cylindrical cases, 6-rung
vtol/dvtol/gridMax ladder, --damp-const trial support) and
plot_convergence.py (error-vs-N plots referenced to the finest
secondOrderLimited run). Smoke-tested the coarsest rung of all three cases;
full study matrix is Task 2.2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Full (case x scheme x rung) matrix for the grid-convergence study (spec
§6.4): strained/twin/cylindrical x firstOrderUpwind/secondOrderLimited x
6-rung ladder, plus a dampConst=15 relaxation trial on
strained/secondOrderLimited. 33/36 main-matrix runs and 5/6 dampConst-trial
runs completed; strained/secondOrderLimited rung5 and
cylindrical/secondOrderLimited rungs 4-5 fail deterministically
(CVODE Integrator had too many errors, reproducible under nThreads=1) while
firstOrderUpwind completes cleanly at the same tolerances for both cases --
flagged for the analysis stage, not worked around. No RUNGS retune was
needed/performed (twin and firstOrderUpwind both hit the full ~4-8x N span
per the existing ladder). All 6 case/metric plots generated via
plot_convergence.py and verified non-empty. Full run table, dampConst
comparison, and sanity-gate self-review in
.superpowers/sdd/task-2.2-runs-report.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Append "Phase 2 findings" (§P2.1-P2.7) to the convection-discretization
design spec: observed convergence orders, error-vs-N grid-reduction tables,
CVODE step-count/limiter-smoothness comparison (spec §5), diagnosis of the
deterministic CVODE failures at the finest rungs, the dampConst null result,
and the Phase 3 gate assessment.

Headline: secondOrderLimited buys a large, consistent per-grid-point accuracy
gain (peak_T grid-converged at the coarsest grid in all cases; ~7x grid
reduction at matched consumption_speed for strained/twin). The spec §5
step-count red flag has triggered: a deterministic grid-refinement/limiter-
stiffness feedback runs CVODE away to failure at the finest rungs of the two
high-strain cases. Analysis-only diagnostic probes (grid-frozen and
intermediate-rung, no src edits) locate the mechanism and a stable fixed
point just below the failing rung; JSONs under
test/convergence/results/diagnostics/.

Recommendation (formulated, not decided): Phase 3 gate OPEN on the diffusion-
dominance criterion, conditioned on first fixing the tight-tolerance CVODE
robustness bug. Decision: PENDING user review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
…ostic

Record the project owner's decision in the spec addendum: Phase 3 gate OPEN
(useful accuracy envelope ~1e-4 on scalars, reached at N~100; diffusion
upgrade judged worthwhile), with the tight-tolerance robustness fix routed
through a short design pass. Post-review diagnostic rules out the QSS
chemistry integrator as the CVODE-failure source: strained/sol/rung5 fails
identically with chemistryIntegrator='cvode' (grid 496->537 non-settling,
convection CVODE ~2600-2900 steps/global step, err_convectionIntegration.h5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
…as primary candidate

Owner direction for the robustness/Phase-3 design pass: evaluate
monitor-function/error-equidistribution grid adaptation (dimensional error
density replacing vtol/dvtol relative-change) as the primary candidate,
against the hysteresis+growth-cap patch. Adjoint/QoI-driven refinement is
deliberately out of scope for Ember and earmarked for Cantera.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CxCXLfj7MZ3qNs5eHXeXZ
Replaces vtol/dvtol relative-change criteria with a scheme-aware local
truncation error budget (range-normalized errTol), per owner decisions:
replace-with-deprecation, hard acceptance on the §P2.4 CVODE failures,
limiter smoothing in scope as contingency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…x scope, envelope regression criterion

Pre-plan corrections: the approved h^p error form was dimensionally
inconsistent (v/length vs. the errTol*range(v) threshold); corrected to
the standard degree-p interpolation-error bound h^(p+1)*|d^(p+1)v| with
C_1=1/8, C_2=1/15 starting constants and a documented sub-linear
tol->QoI-error mapping. Scoped in a minimal fix for the pre-existing
dampVal uninitialized-read in adapt(). Regression criterion restated in
accuracy-envelope terms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l; dampVal kept consistent in adapt()

Replace the relative-change vtol/dvtol insertion/removal criteria in
OneDimGrid::adapt() with the local-error-budget criterion from G1
(computeErrorWeights/errTol/errorOrder/errCoeff): insert where the
estimated local representation error exceeds errTol*range(v); remove
only where the merged-interval error estimate stays below
rmTol*errTol*range(v), giving removal built-in hysteresis since error
scales as h^(p+1).

Also fixes a real bug: updateValues() resizes the dampVal Eigen array
without guaranteeing content preservation once points have been
inserted/removed mid-pass, so the damping criterion could read stale
values. adapt() now maintains a std::vector working copy (dampLocal)
kept in sync via addPoint/removePoint-mirroring insert/erase calls, and
writes it back to dampVal at the end of the pass.

Removes ConfigOptions::vtol/dvtol and OneDimGrid's vtol_in/dvtol_in/
vtol/dvtol members; adds ConfigOptions::errTol and Grid.errTol
(FloatOption(2e-3, min=0)) in input.py, forwarded via _ember.pxd/.pyx.
vtol/dvtol remain as (now-unforwarded) input.py Options pending G3's
deprecation handling.

test/python/test_flame_configs.py: replaced Grid(vtol=0.2, dvtol=0.3)
(3 occurrences) with Grid(errTol=3.3e-3), using the same vtol:errTol
ratio as the provisional default mapping, since vtol/dvtol are no
longer forwarded to the C++ grid and the old call would have silently
fallen back to the default errTol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpMZDeJn1DXH5NshhtCDqq
speth and others added 7 commits July 5, 2026 19:34
…ple tail) + dense profile output

Review fixes: n_trajectory now globs prof[0-9]*.h5 so the continuously
rewritten profNow.h5 no longer inflates counts or injects an off-cadence
final sample; grid_settled floor lowered to 4 samples with a >=3-sample
tail window and a distinct TOO FEW OUTPUTS report; strained/twin builders
now set profileStepInterval=10 (matching cylindrical) so trajectories are
dense enough to judge.

Validated: strained rung-2 smoke runs (both schemes) complete, 23 outputs,
SETTLED, sol N=87 < fou N=122; P2.4 failure dir reports NOT SETTLED;
old passing rung-3 dir reports SETTLED.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cases

Parity calibration of the secondOrderLimited error coefficient against
firstOrderUpwind at matched errTol (rungs 1-4, 3 cases, both schemes):
R = err_fou/err_sol = 2.85 (geometric mean, consumption speed), outside
the [0.5, 2] keep-band, so C_2 = 1/15 -> 0.0139 = (1/15)*R^(-3/2).
Note: the brief's rule stated err_sol ~ C_2^(2/3); the sign is inverted
(larger errCoeff -> finer grid -> smaller error), so the R^(-3/2) scaling
implements the brief's stated intent (multiply err_sol by R). Confirmed
empirically on strained rungs 2-4 (grids coarsen, mean error move x2.5
vs predicted x2.85; fou control bit-identical).

Default Grid.errTol = 1e-4 (was 2e-3), chosen from measured
(errTol, N, err_cs) triplets with default gridMax: N = 115-165,
err_cs = 3.1-6.5e-4 on the study cases. The owner envelope
(err ~ 1e-4 at N ~ 100) is not jointly attainable; 1e-4 is the balanced
one-sig-fig point and sits well inside the observed stability region.
Docstring examples updated to 2e-5 (high accuracy, verified stable on
strained with default gridMax) / 5e-4 (minimal).

Details in test/convergence/results/calibration-notes.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpMZDeJn1DXH5NshhtCDqq
…l->error tables

Ran the full errTol ladder (rungs 0-5, 3 cases x 2 schemes, 36 runs) at
G5's post-calibration constants. Hard criterion PASSES: zero CVODE
failures anywhere, including the two configs that failed deterministically
under old vtol (strained sol rung 5, cylindrical sol rungs 4-5); grid
trajectories all overshoot-then-plateau with bounded, decaying convection
step counts (vs. the old divergence into 10,000-16,000). Parity geo-mean
R=1.70 (within 2x band, improved from pre-cal 2.85), with an anomaly
flagged for strained's persistently >2x per-rung ratio. plot_convergence.py
gains the errTol parity view, the errTol->error table, and the sol/fou
N-ratio table (the vtol->errTol metadata migration itself was already a
no-op per G4). Full verdict, tables, and anomalies in spec addendum §A.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpMZDeJn1DXH5NshhtCDqq
…conciliation

Review fixes to spec addendum §A: (1) parity verdict relabeled PARTIAL --
within ~2x in aggregate (R=1.70) and for twin/cylindrical individually,
but strained exceeds the band at every rung 1-4 (2.67-5.40x), which the
spec's drift-at-extremes language does not license; documents that no
single global C_2 can bring all three cases inside 2x simultaneously
(pre-cal per-case geomeans spanned ~5x) and lists the three options as an
owner decision. Hard-criterion PASS unchanged. (2) §A.4 envelope-range
reconciliation sentence: the wider N/err span here covers all cases/
schemes/QoIs, vs. calibration-notes.md §4's sol-only consumption-speed
span at errTol=1e-4 -- same operating point, different scope.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpMZDeJn1DXH5NshhtCDqq
Reran the Phase-1 curated baseline set at library defaults (errTol=1e-4,
secondOrderLimited) and compared against the Phase-1 secondOrderLimited
baselines. 5/6 cases pass the amended spec Sec4 scalar criterion with wide
margin (peak_T/consumption_speed deltas <=1.24e-3), but N grew 10-23% for
every completing case, and example_single fails to complete outright
(deterministic CVODE Integrator error, reproduced at nThreads=1, cleared
by loosening errTol to 2e-4/5e-4) -- a robustness gap outside the G1-G6
three-case ladder, surfaced here for owner review alongside the other
pending errTol=1e-4 default questions. Not tuned away per instruction.

Also: retune the three test_flame_configs.py Grid(errTol=...) call sites
and example_laminarFlameSpeed.py's default config to the post-G5-calibration
errCoeff (both were still tuned against the pre-calibration C_2, ~2.85x
too tight); fix input.py's stale rmTol docstring (compares against errTol,
not vtol/dvtol); and replace analyze_settling.py's fixed tol_pts=3 with a
tail-size-relative tolerance, since the absolute floor was flagging
plateaued high-N ladder runs as NOT SETTLED.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpMZDeJn1DXH5NshhtCDqq
Ten pre-presentation fixes from the final whole-branch review:

1. run_baselines.py: drop dead vtol/dvtol kwargs from the laminarFlameSpeed
   case (G7 baseline actually ran at default errTol=1e-4); note that intent.
2. smoke_continuity_bc.py: replace inert Grid(vtol=0.2, dvtol=0.3) with
   Grid(errTol=1.2e-3) to match the TestPremixedStrained tolerance it derives
   from.
3. Correct the dampVal narrative: final review confirmed (via
   git show 5d4febe:src/grid.cpp) that base addPoint/removePoint already
   maintained dampVal, so updateValues()'s resize was always a same-size
   no-op and no uninitialized read existed. Rewrote the grid.cpp comment and
   the design doc to retract the "pre-existing bug" diagnosis and describe
   dampLocal as defensive hardening; appended a local progress-ledger note.
4. input.py: qualify the errTol docstring and deprecation-warning text —
   accuracy parity is PARTIAL (strained cases run 2.7-5.4x above the 2x
   band), so both now note that highly strained flames may need a tighter
   errTol under firstOrderUpwind.
5. input.py: errTol option now enforces min=1e-12 instead of min=0, so it
   can no longer degenerate to refine-everywhere.
6. input.py: run('force') and runESR bypass validate(); route
   _warnDeprecated() through evaluate() (the common config->solver-options
   path) with a _deprecationWarned guard so it fires exactly once.
7. test_gridAdaptation.cpp: new test pinning setOptions()'s
   convectionScheme -> (errorOrder, errCoeff) mapping (1/0.125,
   2/0.0139).
8. grid.cpp adapt(): assert dampVal.rows() == nPoints alongside the existing
   positivity assert, making the caller contract explicit.
9. grid.cpp: removal-loop damping debug log now prints the actual threshold
   used in the comparison (rmTol*dampConst*dampLocal[j]).
10. example_laminarFlameSpeed.py: comment noting errTol=5e-4 is the
    docstring's "minimal accuracy" tier, chosen to preserve historical
    resolution/runtime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpMZDeJn1DXH5NshhtCDqq
…ly initialize ConfigOptions in mapping test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant