rtx-fsi: IqnIls::reset_history — stale secant columns overshoot in rapid transients
Documentation / Build API Documentation (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 / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (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 User Guide (push) Canceled after 0s
Documentation / Build API Documentation (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 / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (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 User Guide (push) Canceled after 0s
The s = 2 ladder march (resonant growth finally open: 2.3 Hz, amplitude through ±46 mm — 2.7x the wake attractor the loose coupling locked) died at t = 11.12 s with CouplingDiverged at iteration 2: the predictor left a 1e-4 first residual, and the first quasi-Newton update drove it to 1.1e-3. Cross-step secant history assumes the interface Jacobian drifts slowly; columns recorded at much smaller amplitude steered the least-squares direction wrong at 2.7x that amplitude, and the divergence guard read the overshoot as added mass. The map itself converges deeply from a clean start (the noise probe's stall measurement), so the recovery is: reset the history, retry the step once from the predictor. - IqnIls::reset_history(), with a cold-start-equivalence test. - turek_hron_fsi2: on an unaccepted coupling verdict under the IQN coupler, reset + one retry from the predictor (Aitken carries no history — a retry would repeat the identical iteration, so the path is IQN-only); retried steps counted and reported like stalls. Verified end-to-end: the re-run crossed the killing step and marched on (t = 11.18: uy +56 mm, window amp ±50 mm, 1.4 subit/step). 45 lib tests green, clippy clean. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Lnyrw33Lu6rUhW42E9KHwq
This commit is contained in:
co-authored by
Claude Fable 5
parent
140310b223
commit
d5f19ea497
@@ -253,6 +253,7 @@ fn fsi2_flapping_flag() {
|
||||
let mut max_subiterations = 0usize;
|
||||
let mut total_skipped = 0usize;
|
||||
let mut stalled_steps = 0usize;
|
||||
let mut retried_steps = 0usize;
|
||||
let mut worst_stall = 0.0f64;
|
||||
let mut force_times: Vec<f64> = Vec::new();
|
||||
let mut drag_series: Vec<f64> = Vec::new();
|
||||
@@ -313,7 +314,7 @@ fn fsi2_flapping_flag() {
|
||||
.sum::<f64>()
|
||||
.sqrt();
|
||||
let tol_step = tol_floor.max(rtol * increment);
|
||||
let outcome = if let Some(iqn) = iqn.as_mut() {
|
||||
let mut outcome = if let Some(iqn) = iqn.as_mut() {
|
||||
iqn.set_tolerance(tol_step).unwrap();
|
||||
iqn.solve(&d_predicted, pass)
|
||||
} else {
|
||||
@@ -321,6 +322,30 @@ fn fsi2_flapping_flag() {
|
||||
.unwrap()
|
||||
.solve(&d_predicted, pass)
|
||||
};
|
||||
// Stale-history recovery: cross-step secant columns assume the
|
||||
// interface Jacobian drifts slowly, and during a rapid resonant
|
||||
// growth they can steer the quasi-Newton update into an
|
||||
// overshoot the divergence guard reads as added mass (measured:
|
||||
// a first residual of 1e-4 driven to 1e-3 by the first update at
|
||||
// 2.7x the previously seen amplitude, killing a t = 11.1 s
|
||||
// march). The map itself converges deeply from a clean start
|
||||
// (the noise probe's stall measurement), so: reset the history
|
||||
// and retry the step ONCE from the predictor. Aitken carries no
|
||||
// history — a retry would repeat the identical iteration, so
|
||||
// this path is IQN-only.
|
||||
if let (Err(e), Some(iqn_ref)) = (&outcome, iqn.as_mut()) {
|
||||
let recoverable = matches!(
|
||||
e,
|
||||
rtx_fsi::FsiError::CouplingNotConverged { residual, .. }
|
||||
| rtx_fsi::FsiError::CouplingDiverged { residual, .. }
|
||||
if *residual >= 5.0 * tol_step
|
||||
);
|
||||
if recoverable {
|
||||
iqn_ref.reset_history();
|
||||
retried_steps += 1;
|
||||
outcome = iqn_ref.solve(&d_predicted, pass);
|
||||
}
|
||||
}
|
||||
match outcome {
|
||||
Ok(converged) => {
|
||||
total_subiterations += converged.iterations;
|
||||
@@ -422,7 +447,8 @@ fn fsi2_flapping_flag() {
|
||||
println!(
|
||||
" FSI2 (fluid ny = {ny}, flag {flag_nx}x2 Quad8, dt = {dt:.2e}): coupled {coupled_steps} \
|
||||
steps in {:.0} s wall total; {mean_subiterations:.1} subit/step (max \
|
||||
{max_subiterations}); {stalled_steps} stalled steps (worst residual \
|
||||
{max_subiterations}); {stalled_steps} stalled steps, {retried_steps} \
|
||||
history-reset retries (worst residual \
|
||||
{worst_stall:.2e}); worst conservation {worst_conservation:.2e}; skipped samples \
|
||||
{total_skipped} (of which {} spike-rejected)\n measured over [{:.1}, {t_end:.1}] s: uy(A) = {:.4} ± {:.4} mm \
|
||||
(ref {:.2} ± {:.1}), ux(A) = {:.4} ± {:.4} mm (ref {:.2} ± {:.2}), f = {} Hz \
|
||||
|
||||
Reference in New Issue
Block a user