use std::f64::consts::PI;
#[allow(clippy::excessive_precision)]
pub(crate) fn ln_gamma(x: f64) -> f64 {
const COEFFS: [f64; 9] = [
0.999_999_999_999_809_9,
676.520_368_121_885_1,
-1_259.139_216_722_402_8,
771.323_428_777_653_1,
-176.615_029_162_140_6,
12.507_343_278_686_905,
-0.138_571_095_265_720_12,
9.984_369_578_019_572e-6,
1.505_632_735_149_311_6e-7,
];
const G: f64 = 7.0;
if x < 0.5 {
let ln_pi = PI.ln();
let sin_val = (PI * x).sin();
if sin_val.abs() < 1e-30 {
return f64::INFINITY;
}
return ln_pi - sin_val.abs().ln() - ln_gamma(1.0 - x);
}
let x = x - 1.0;
let mut sum = COEFFS[0];
for i in 1..9 {
sum += COEFFS[i] / (x + i as f64);
}
let t = x + G + 0.5;
0.5 * (2.0 * PI).ln() + (x + 0.5) * t.ln() - t + sum.ln()
}
pub(crate) fn reg_gamma_p(a: f64, x: f64) -> f64 {
if x < 0.0 {
return 0.0;
}
if x == 0.0 {
return 0.0;
}
if a <= 0.0 {
return 1.0;
}
if x < a + 1.0 {
gamma_series(a, x)
} else {
1.0 - gamma_cf(a, x)
}
}
pub(crate) fn reg_gamma_q(a: f64, x: f64) -> f64 {
1.0 - reg_gamma_p(a, x)
}
fn gamma_series(a: f64, x: f64) -> f64 {
let ln_gamma_a = ln_gamma(a);
let max_iter = 200;
let eps = 1e-14;
let mut ap = a;
let mut sum = 1.0 / a;
let mut del = sum;
for _ in 0..max_iter {
ap += 1.0;
del *= x / ap;
sum += del;
if del.abs() < sum.abs() * eps {
break;
}
}
let log_prefix = a * x.ln() - x - ln_gamma_a;
if log_prefix < -700.0 {
return 0.0;
}
sum * log_prefix.exp()
}
fn gamma_cf(a: f64, x: f64) -> f64 {
let ln_gamma_a = ln_gamma(a);
let max_iter = 200;
let eps = 1e-14;
let tiny = 1e-30;
let mut b = x + 1.0 - a;
let mut c = 1.0 / tiny;
let mut d = 1.0 / b;
let mut h = d;
for i in 1..=max_iter {
let an = -(i as f64) * (i as f64 - a);
b += 2.0;
d = an * d + b;
if d.abs() < tiny {
d = tiny;
}
c = b + an / c;
if c.abs() < tiny {
c = tiny;
}
d = 1.0 / d;
let del = d * c;
h *= del;
if (del - 1.0).abs() < eps {
break;
}
}
let log_prefix = a * x.ln() - x - ln_gamma_a;
if log_prefix < -700.0 {
return 0.0;
}
log_prefix.exp() * h
}
pub(crate) fn chi2_cdf(x: f64, k: usize) -> f64 {
if x <= 0.0 {
return 0.0;
}
if k == 0 {
return 1.0;
}
reg_gamma_p(k as f64 / 2.0, x / 2.0)
}
pub(crate) fn chi2_quantile(p: f64, k: usize) -> f64 {
if p <= 0.0 {
return 0.0;
}
if p >= 1.0 {
return f64::INFINITY;
}
if k == 0 {
return 0.0;
}
let df = k as f64;
let z = normal_quantile_approx(p);
let ratio = 2.0 / (9.0 * df);
let cube = 1.0 - ratio + z * ratio.sqrt();
let mut x = df * cube * cube * cube;
if x <= 0.0 {
x = 0.01;
}
let max_iter = 50;
let tol = 1e-12;
for _ in 0..max_iter {
let cdf_val = chi2_cdf(x, k);
let error = cdf_val - p;
if error.abs() < tol {
break;
}
let log_pdf =
(df / 2.0 - 1.0) * x.ln() - x / 2.0 - (df / 2.0) * 2.0_f64.ln() - ln_gamma(df / 2.0);
let pdf = log_pdf.exp();
if pdf < 1e-30 {
break;
}
let delta = error / pdf;
x -= delta;
if x <= 0.0 {
x = tol;
}
}
x
}
fn normal_quantile_approx(p: f64) -> f64 {
if p <= 0.0 {
return f64::NEG_INFINITY;
}
if p >= 1.0 {
return f64::INFINITY;
}
if (p - 0.5).abs() < 1e-15 {
return 0.0;
}
let sign = if p < 0.5 { -1.0 } else { 1.0 };
let p = if p < 0.5 { p } else { 1.0 - p };
let t = (-2.0 * p.ln()).sqrt();
let c0 = 2.515_517;
let c1 = 0.802_853;
let c2 = 0.010_328;
let d1 = 1.432_788;
let d2 = 0.189_269;
let d3 = 0.001_308;
let num = c0 + c1 * t + c2 * t * t;
let den = 1.0 + d1 * t + d2 * t * t + d3 * t * t * t;
sign * (t - num / den)
}
fn gamma_p_series_sf(a: f64, x: f64) -> f64 {
let mut ap = a;
let mut sum = 1.0 / a;
let mut del = sum;
for _ in 0..200 {
ap += 1.0;
del *= x / ap;
sum += del;
if del.abs() < sum.abs() * 1e-15 {
break;
}
}
sum * (-x + a * x.ln() - ln_gamma(a)).exp()
}
fn gamma_q_cf_sf(a: f64, x: f64) -> f64 {
let tiny = 1e-300;
let mut b = x + 1.0 - a;
let mut c = 1.0 / tiny;
let mut d = 1.0 / b;
let mut h = d;
for i in 1..200 {
let an = -(i as f64) * (i as f64 - a);
b += 2.0;
d = an * d + b;
if d.abs() < tiny {
d = tiny;
}
c = b + an / 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;
}
}
(-x + a * x.ln() - ln_gamma(a)).exp() * h
}
pub(crate) fn chi2_sf(x: f64, df: f64) -> f64 {
if x <= 0.0 || df <= 0.0 {
return 1.0;
}
let a = df / 2.0;
let xx = x / 2.0;
if xx < a + 1.0 {
1.0 - gamma_p_series_sf(a, xx)
} else {
gamma_q_cf_sf(a, xx)
}
}