use super::entry::fit_from_formula;
use super::request::{FitConfig, FitResult, StandardFitResult};
use csv::StringRecord;
use gam_data::encode_recordswith_inferred_schema;
use rand::SeedableRng;
use rand::rngs::StdRng;
use rand_distr::{Distribution, Uniform};
fn near_separated_binomial(n: usize, slope: f64, seed: u64) -> gam_data::EncodedDataset {
let mut rng = StdRng::seed_from_u64(seed);
let unif = Uniform::new(-1.0_f64, 1.0).unwrap();
let ubern = Uniform::new(0.0_f64, 1.0).unwrap();
let headers: Vec<String> = ["x", "y"].iter().map(|s| s.to_string()).collect();
let mut rows = Vec::with_capacity(n);
for _ in 0..n {
let x = unif.sample(&mut rng);
let p = 1.0 / (1.0 + (-slope * x).exp());
let y = if ubern.sample(&mut rng) < p { 1.0 } else { 0.0 };
rows.push(StringRecord::from(vec![x.to_string(), y.to_string()]));
}
encode_recordswith_inferred_schema(headers, rows).expect("encode")
}
fn assert_near_separation_converges(n: usize, slope: f64, seed: u64) {
let ds = near_separated_binomial(n, slope, seed);
let cfg = FitConfig {
family: Some("binomial".to_string()),
..FitConfig::default()
};
let t0 = std::time::Instant::now();
let result = fit_from_formula("y ~ smooth(x)", &ds, &cfg)
.expect("near-separated binomial fit must not error");
let elapsed = t0.elapsed();
let StandardFitResult { fit, .. } = match result {
FitResult::Standard(s) => s,
_ => panic!("expected Standard fit"),
};
let edf = fit.edf_total().unwrap_or(f64::NAN);
let gnorm = fit.outer_gradient_norm.unwrap_or(f64::NAN);
eprintln!(
"#1762 convergence: n={n} slope={slope} elapsed={:.2}s edf={edf:.2} \
converged={} |g|={gnorm:.3e}",
elapsed.as_secs_f64(),
fit.outer_converged
);
assert!(
fit.outer_converged,
"near-separated binomial-logit fit (n={n}, slope={slope}) reported \
NON-CONVERGED (FLAT-VALLEY STALL); the outer ARC optimizer must reach a \
certified minimum under PIRLS weight collapse (#1762)"
);
assert!(
gnorm.is_finite() && gnorm < 5.0,
"near-separated binomial-logit fit (n={n}, slope={slope}) shipped with \
projected outer gradient |g|={gnorm:.3e} ≥ 5.0 ceiling — the FLAT-VALLEY \
STALL signature from #1762 (reported |g|≈54)"
);
assert!(
elapsed.as_secs_f64() < 20.0,
"near-separated binomial-logit fit (n={n}, slope={slope}) took {:.1}s \
(should be ≪1s); the ARC cost-stall escape loop is spinning (#1762)",
elapsed.as_secs_f64()
);
}
#[test]
fn binomial_near_separation_converges_1762() {
assert_near_separation_converges(800, 12.0, 7);
}
#[test]
fn binomial_stronger_separation_converges_1762() {
assert_near_separation_converges(800, 16.0, 7);
}