From fdbedac43761daa303e5c155c697650e75036840 Mon Sep 17 00:00:00 2001 From: kaghi Date: Wed, 20 May 2026 09:37:18 -0700 Subject: [PATCH 1/3] Fixed bug where under certain conditions tau_decay would diverge uncontrollably after grid search and during golden refinement. --- crates/solver/src/biexp_fit.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/solver/src/biexp_fit.rs b/crates/solver/src/biexp_fit.rs index 1da14b19..581a8838 100644 --- a/crates/solver/src/biexp_fit.rs +++ b/crates/solver/src/biexp_fit.rs @@ -561,9 +561,14 @@ fn golden_section_refine( } } 1 => { - // Refine tau_d + // Refine tau_d — cap to match grid-search upper bound (tau_d_hi = 5.0) + // Without this cap, values exceeding 5s will cause runaway behavior. + const TAU_D_CAP: f64 = 5.0; + if tau_d > TAU_D_CAP { + tau_d = TAU_D_CAP; // clamp runaway warm-start back to cap + } let lo = (tau_d * 0.5).max(tau_r * 1.01); - let hi = tau_d * 2.0; + let hi = (tau_d * 2.0).min(TAU_D_CAP); if lo < hi { tau_d = golden_bracket(lo, hi, |x| { eval_two_component(h_free, tau_r, x, tau_r_fast, tau_d_fast, dt, skip).2 From 768c04aedeca9687f292956a0a7d81ba973742f6 Mon Sep 17 00:00:00 2001 From: kaghi Date: Fri, 22 May 2026 18:03:25 -0700 Subject: [PATCH 2/3] testing linting fix for bi_exp.rs --- crates/solver/src/biexp_fit.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/solver/src/biexp_fit.rs b/crates/solver/src/biexp_fit.rs index 581a8838..008fdb65 100644 --- a/crates/solver/src/biexp_fit.rs +++ b/crates/solver/src/biexp_fit.rs @@ -562,10 +562,10 @@ fn golden_section_refine( } 1 => { // Refine tau_d — cap to match grid-search upper bound (tau_d_hi = 5.0) - // Without this cap, values exceeding 5s will cause runaway behavior. + // Without this cap, values exceeding 5s will cause runaway behavior. const TAU_D_CAP: f64 = 5.0; if tau_d > TAU_D_CAP { - tau_d = TAU_D_CAP; // clamp runaway warm-start back to cap + tau_d = TAU_D_CAP; // clamp runaway warm-start back to cap } let lo = (tau_d * 0.5).max(tau_r * 1.01); let hi = (tau_d * 2.0).min(TAU_D_CAP); From 5031fcef7a3baec3c7b3ab2d552732e0c15ef6f3 Mon Sep 17 00:00:00 2001 From: daharoni Date: Sat, 23 May 2026 07:53:00 -0700 Subject: [PATCH 3/3] refactor(solver): share tau_d upper bound between grid and refinement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract `TAU_D_HI` so cold_grid_search and golden_section_refine reference a single source of truth — prevents the two stages from drifting if the slow-decay upper bound is ever changed. --- crates/solver/src/biexp_fit.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/solver/src/biexp_fit.rs b/crates/solver/src/biexp_fit.rs index 008fdb65..b2aefe64 100644 --- a/crates/solver/src/biexp_fit.rs +++ b/crates/solver/src/biexp_fit.rs @@ -244,13 +244,17 @@ fn refine_candidate( } } +/// Slow-component decay upper bound (seconds). Shared by the cold-start grid +/// search and the golden-section refinement so the two stages cannot drift. +const TAU_D_HI: f64 = 5.0; + /// Cold-start grid search. Returns (best_slow_only, best_two_component). fn cold_grid_search(h_free: &[f32], fs: f64, dt: f64, skip: usize) -> (BiexpResult, BiexpResult) { // Slow component grid ranges (in seconds). let tau_r_lo = (1.0 / fs).max(0.005_f64); let tau_r_hi = 0.5_f64; let tau_d_lo = 0.05_f64; - let tau_d_hi = 5.0_f64; + let tau_d_hi = TAU_D_HI; let grid_n = 20; let log_tr_lo = tau_r_lo.ln(); @@ -561,14 +565,13 @@ fn golden_section_refine( } } 1 => { - // Refine tau_d — cap to match grid-search upper bound (tau_d_hi = 5.0) - // Without this cap, values exceeding 5s will cause runaway behavior. - const TAU_D_CAP: f64 = 5.0; - if tau_d > TAU_D_CAP { - tau_d = TAU_D_CAP; // clamp runaway warm-start back to cap + // Refine tau_d — cap to the grid-search upper bound. Without this + // cap, a warm-started tau_d > TAU_D_HI causes runaway behavior. + if tau_d > TAU_D_HI { + tau_d = TAU_D_HI; } let lo = (tau_d * 0.5).max(tau_r * 1.01); - let hi = (tau_d * 2.0).min(TAU_D_CAP); + let hi = (tau_d * 2.0).min(TAU_D_HI); if lo < hi { tau_d = golden_bracket(lo, hi, |x| { eval_two_component(h_free, tau_r, x, tau_r_fast, tau_d_fast, dt, skip).2