test(rtx-fsi): closure schedule by tip speed (CRESCUE_SPEED=<f>), default off
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Test (ubuntu-latest) (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 / Test (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
Documentation / Build User Guide (push) Canceled after 0s

Route 1 of omni-cortex docs/coupling_rescue_campaign.md §12, registered
in docs/closure_scheduling_campaign.md: whenever the last committed tip
jump exceeds f x its trailing-2000 peak, open a coarse episode (the
s=2 interpolated closure, CRESCUE_COARSE=<M> steps, re-evaluated at
each end) BEFORE the step — preventive, because the s=1 closure
incubates the crossing instability inside the healthy envelope and no
signature-triggered rescue acts early enough (rungs A, C, A' refuted).
Speed episodes are expected twice per period: not counted against the
runaway-episode cap; the coarse fraction is printed at the end of the
march.

Verified: FSI2 committed default digit-identical knob-off vs the
same-day baseline; fmt + clippy clean.

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-03 09:57:40 -07:00
co-authored by Claude Fable 5.1
parent b66f73aeef
commit 19d8307ae4
3 changed files with 65 additions and 1 deletions
@@ -143,6 +143,16 @@ pub struct MarchConfig {
/// whole march stays under 1.72×; the (1.27, 2.0) death crosses 3×
/// 145 steps before its panic and never during growth.
pub increment_factor: f64,
/// Route 1 of the closure-scheduling campaign
/// (`RTX_{prefix}_CRESCUE_SPEED=<f>`, default 0 = off; needs
/// `coupling_rescue` and `coarse_episode`): whenever the last
/// committed tip jump exceeds f × its trailing-2000 peak, open a
/// coarse episode BEFORE the step — the s = 2 closure at every
/// crossing, preventively, because the s = 1 closure incubates the
/// crossing instability inside the healthy envelope
/// (coupling_rescue_campaign.md §12). Speed episodes are expected
/// twice per period: not capped, reported as a coarse fraction.
pub speed_fraction: f64,
/// C^1 interface motion (constant acceleration across the step from
/// the previous end velocity) instead of a constant velocity with a
/// jump at the step boundary. See `Fsi2Harness::advance_subcycled`.
@@ -201,6 +211,7 @@ impl MarchConfig {
coarse_episode: num("CRESCUE_COARSE", defaults.coarse_episode as f64) as usize,
inc_trace: std::env::var(key("INCTRACE")).ok().or(defaults.inc_trace),
increment_factor: num("CRESCUE_INC", defaults.increment_factor),
speed_fraction: num("CRESCUE_SPEED", defaults.speed_fraction),
c1_interface: num("C1", f64::from(u8::from(defaults.c1_interface))) != 0.0,
predictor: std::env::var(key("PREDICTOR")).unwrap_or(defaults.predictor),
quiescent_release: num("QUIESCENT", f64::from(u8::from(defaults.quiescent_release)))
@@ -355,6 +366,7 @@ pub fn run_march(case: BenchmarkCase, config: &MarchConfig) -> MarchResult {
coarse_episode,
ref inc_trace,
increment_factor,
speed_fraction,
c1_interface,
ref predictor,
quiescent_release,
@@ -500,6 +512,9 @@ pub fn run_march(case: BenchmarkCase, config: &MarchConfig) -> MarchResult {
// Rung A: the trailing window of predictor increments and its median.
let mut inc_window: std::collections::VecDeque<f64> = std::collections::VecDeque::new();
let mut running_inc_median: Option<f64> = None;
// Route 1 bookkeeping: speed-triggered episodes and coarse steps taken.
let mut speed_episodes = 0usize;
let mut coarse_steps_taken = 0usize;
let mut inc_trace_file = inc_trace.as_ref().map(|p| {
let mut w = std::io::BufWriter::new(std::fs::File::create(p).expect("inctrace path"));
writeln!(
@@ -557,6 +572,7 @@ pub fn run_march(case: BenchmarkCase, config: &MarchConfig) -> MarchResult {
Ok(o) => {
flag_state = o.state;
committed_nodal = o.nodal;
coarse_steps_taken += 2;
worst_conservation = worst_conservation.max(o.worst_conservation);
total_skipped += o.skipped;
total_subiterations += o.passes;
@@ -567,7 +583,11 @@ pub fn run_march(case: BenchmarkCase, config: &MarchConfig) -> MarchResult {
}
let t_end_c = t_release + (step + 2) as f64 * dt;
if $new_episode {
coarse_episodes.push(step);
// Speed episodes are expected every half-period;
// only runaway-triggered ones count against the cap.
if $reason != "tip speed" {
coarse_episodes.push(step);
}
coupling_rescues += 1;
let record = super::rescue::RescueRecord {
step,
@@ -664,6 +684,41 @@ pub fn run_march(case: BenchmarkCase, config: &MarchConfig) -> MarchResult {
step += 2;
continue;
}
// Route 1: the s = 2 closure whenever the tip is fast, before any
// pass — preventive, not rescuing (closure_scheduling_campaign.md).
if coupling_rescue
&& coarse_episode > 0
&& speed_fraction > 0.0
&& step + 1 < coupled_steps
&& uy_series.len() > super::rescue::TIP_JUMP_WINDOW
{
let n = uy_series.len();
let last_jump = (uy_series[n - 1] - uy_series[n - 2]).abs();
let peak = uy_series[n - super::rescue::TIP_JUMP_WINDOW - 1..]
.windows(2)
.map(|w| (w[1] - w[0]).abs())
.fold(0.0f64, f64::max);
if peak > 0.0 && last_jump > speed_fraction * peak {
let fs = solver.borrow().snapshot();
let ff = field.borrow().clone();
let ss = flag_state.clone();
let sn = committed_nodal.clone();
speed_episodes += 1;
coarse_now!(
"tip speed",
last_jump / peak,
f64::NAN,
true,
&fs,
&ff,
&ss,
&sn
);
coarse_remaining = coarse_episode.saturating_sub(2);
step += 2;
continue;
}
}
let d_n = extract(&flag_state);
let v_n: Option<Vec<f64>> = c1_interface.then(|| extract_velocity(&flag_state));
@@ -1167,6 +1222,13 @@ pub fn run_march(case: BenchmarkCase, config: &MarchConfig) -> MarchResult {
step += 1;
}
if speed_fraction > 0.0 {
println!(
" closure schedule (f = {speed_fraction}): {speed_episodes} speed episodes, \
{coarse_steps_taken} of {coupled_steps} coupled steps under the s = 2 closure ({:.1}%)",
100.0 * coarse_steps_taken as f64 / coupled_steps.max(1) as f64
);
}
let coupled_elapsed = phase_start.elapsed().as_secs_f64();
let (f, s, l) = (t_fluid.get(), t_structure.get(), t_sample.get());
let pct = |x: f64| 100.0 * x / coupled_elapsed.max(1e-9);