use crate::distributions::{chi2_sf, ln_gamma};
pub(crate) fn chi_square_sf(x: f64, k: usize) -> f64 {
chi2_sf(x, k as f64)
}
pub(crate) fn chi_square_sf_df(x: f64, df: f64) -> f64 {
chi2_sf(x, df)
}
fn betacf(a: f64, b: f64, x: f64) -> f64 {
let tiny = 1e-300;
let qab = a + b;
let qap = a + 1.0;
let qam = a - 1.0;
let mut c = 1.0;
let mut d = 1.0 - qab * x / qap;
if d.abs() < tiny {
d = tiny;
}
d = 1.0 / d;
let mut h = d;
for m in 1..300 {
let m = m as f64;
let m2 = 2.0 * m;
let aa = m * (b - m) * x / ((qam + m2) * (a + m2));
d = 1.0 + aa * d;
if d.abs() < tiny {
d = tiny;
}
c = 1.0 + aa / c;
if c.abs() < tiny {
c = tiny;
}
d = 1.0 / d;
h *= d * c;
let aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
d = 1.0 + aa * d;
if d.abs() < tiny {
d = tiny;
}
c = 1.0 + aa / c;
if c.abs() < tiny {
c = tiny;
}
d = 1.0 / d;
let del = d * c;
h *= del;
if (del - 1.0).abs() < 1e-15 {
break;
}
}
h
}
fn betai(a: f64, b: f64, x: f64) -> f64 {
if x <= 0.0 {
return 0.0;
}
if x >= 1.0 {
return 1.0;
}
let ln_beta = ln_gamma(a) + ln_gamma(b) - ln_gamma(a + b);
let bt = (a * x.ln() + b * (1.0 - x).ln() - ln_beta).exp();
if x < (a + 1.0) / (a + b + 2.0) {
bt * betacf(a, b, x) / a
} else {
1.0 - bt * betacf(b, a, 1.0 - x) / b
}
}
pub(crate) fn f_sf(f: f64, d1: f64, d2: f64) -> f64 {
if f <= 0.0 {
return 1.0;
}
if d1 <= 0.0 || d2 <= 0.0 {
return 1.0;
}
let x = d2 / (d2 + d1 * f);
betai(d2 / 2.0, d1 / 2.0, x)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chi_square_sf_sane() {
assert!((chi_square_sf(3.8415, 1) - 0.05).abs() < 1e-3);
assert!((chi_square_sf(0.0, 3) - 1.0).abs() < 1e-12);
assert!((chi_square_sf(5.9915, 2) - 0.05).abs() < 1e-3);
assert!(chi_square_sf(1.0, 3) > chi_square_sf(5.0, 3));
assert!((chi_square_sf_df(5.9915, 2.0) - chi_square_sf(5.9915, 2)).abs() < 1e-12);
}
#[test]
fn f_sf_matches_tabulated_quantiles() {
assert!((f_sf(0.0, 3.0, 10.0) - 1.0).abs() < 1e-12);
assert!((f_sf(-1.0, 3.0, 10.0) - 1.0).abs() < 1e-12);
assert!(
(f_sf(4.9646, 1.0, 10.0) - 0.05).abs() < 1e-2,
"F(1,10) SF at 4.9646 = {}",
f_sf(4.9646, 1.0, 10.0)
);
assert!(
(f_sf(2.7109, 5.0, 20.0) - 0.05).abs() < 1e-2,
"F(5,20) SF at 2.7109 = {}",
f_sf(2.7109, 5.0, 20.0)
);
assert!(
(f_sf(4.5097, 3.0, 30.0) - 0.01).abs() < 1e-2,
"F(3,30) SF at 4.5097 = {}",
f_sf(4.5097, 3.0, 30.0)
);
assert!(f_sf(3.4, 5.0, 20.0) < 0.05);
assert!(f_sf(1.0, 3.0, 20.0) > f_sf(5.0, 3.0, 20.0));
let p = f_sf(2.5, 4.0, 15.0);
assert!((0.0..=1.0).contains(&p));
}
}