test(rtx-fsi): coupling rescue rung C (CRESCUE_COARSE, burst-local s=2 coarsening) + INCTRACE per-step increment dump — both default off
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s

Coupling-rescue campaign, continued (omni-cortex
docs/coupling_rescue_campaign.md §11). Rung A was refuted 4/4 by
mechanism (the substeps reproduce the rejected motion — the runaway is
in the converged coupled load at the crossing). Diagnostics on the same
death: SUBCYCLE=2 marches GREEN to t=16 (zero bursts), HYST=0.25 dies
EARLIER. So:

- RTX_{prefix}_CRESCUE_COARSE=<M> (with CRESCUE=1): on a trigger,
  reject the step and take 2dt coupled steps with the fluid subcycled
  at 2x (fluid dt unchanged = the s=2 interpolated closure) for M
  coupled steps, then resume; episodes counted, cap 5 per second of
  march (loud). rescue.rs: coarse_step / attempt_with generalisation;
  march loop is now a while loop (a coarse step consumes two indices,
  the series carries a linear midpoint). VERDICT: refuted 2/2 — the
  coarse steps themselves cannot close once the state is 10x wild;
  both rungs act too late (the kinematic trigger is the limitation).
- RTX_{prefix}_INCTRACE=<csv>: reporting-only per-step dump (step, t,
  predictor increment, tol_step, passes, residual, stalled, tip jump)
  — rung A''s calibration data (healthy anchor vs death).

Verified: the FSI2 committed default digit-identical knob-off after
each change (same-day baseline); fmt + clippy clean on the touched
files.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
This commit is contained in:
Omar Sobh
2026-09-02 22:09:49 -07:00
co-authored by Claude Fable 5.1
parent e76271ac67
commit 7e0f159097
4 changed files with 270 additions and 17 deletions
@@ -149,11 +149,39 @@ pub fn substep_interval(iv: &Interval<'_, '_>) -> Result<RescueOutcome, FsiError
Err(last_err.expect("ladder is non-empty"))
}
/// More coarse episodes than this within one second of march is a
/// runaway the coarsening only delays — the march dies loudly.
pub const COARSE_EPISODE_CAP_PER_SECOND: usize = 5;
/// Rung C: ONE coupled step over `factor × dt` with the fluid
/// subcycled at `factor ×` the configured subcycle — the fluid dt is
/// unchanged, so this is exactly the s = 2 interpolated closure
/// (factor 2) that marches through the crossing where s = 1 dies
/// (campaign doc §11, P0.3). From the saved start, like a rung.
///
/// # Errors
/// The coupling error when the coarse step ends above its acceptance
/// window; the fluid and field are then mid-failure — the caller
/// restores or dies.
pub fn coarse_step(iv: &Interval<'_, '_>, factor: usize) -> Result<RescueOutcome, FsiError> {
attempt_with(iv, 1, iv.dt * factor as f64, iv.config.subcycle * factor).map_err(|(e, _)| e)
}
/// One rung: `n` coupled substeps of `dt/n` from the saved start.
fn attempt(iv: &Interval<'_, '_>, n: usize) -> Result<RescueOutcome, (FsiError, usize)> {
attempt_with(iv, n, iv.dt / n as f64, iv.config.subcycle)
}
/// `n` coupled steps of `dt_sub`, each with `subcycle_sub` fluid
/// substeps, from the saved start.
fn attempt_with(
iv: &Interval<'_, '_>,
n: usize,
dt_sub: f64,
subcycle_sub: usize,
) -> Result<RescueOutcome, (FsiError, usize)> {
let cfg = iv.config;
let dt_sub = iv.dt / n as f64;
let dt_fluid_sub = dt_sub / cfg.subcycle as f64;
let dt_fluid_sub = dt_sub / subcycle_sub as f64;
iv.solver.borrow_mut().restore(iv.fluid_saved);
*iv.field.borrow_mut() = iv.field_saved.clone();
@@ -205,7 +233,7 @@ fn attempt(iv: &Interval<'_, '_>, n: usize) -> Result<RescueOutcome, (FsiError,
&mut trial,
&d_n,
d_candidate,
cfg.subcycle,
subcycle_sub,
v_n.as_deref(),
dt_fluid_sub,
);