use std::f64::consts::{FRAC_1_SQRT_2, PI};
pub fn norm_pdf(x: f64) -> f64 {
(-0.5 * x * x).exp() / (2.0 * PI).sqrt()
}
pub fn norm_cdf(x: f64) -> f64 {
0.5 * libm::erfc(-x * FRAC_1_SQRT_2)
}
pub fn ln_gamma(x: f64) -> f64 {
libm::lgamma(x)
}
pub fn digamma(x: f64) -> f64 {
if x.is_nan() || x <= 0.0 {
return f64::NAN;
}
let mut x = x;
let mut acc = 0.0;
while x < 10.0 {
acc -= 1.0 / x;
x += 1.0;
}
let f = 1.0 / (x * x);
let series = f
* (-1.0 / 12.0
+ f * (1.0 / 120.0
+ f * (-1.0 / 252.0
+ f * (1.0 / 240.0 + f * (-1.0 / 132.0 + f * 691.0 / 32760.0)))));
acc + x.ln() - 0.5 / x + series
}
pub fn norm_quantile(p: f64) -> f64 {
if p.is_nan() || !(0.0..=1.0).contains(&p) {
return f64::NAN;
}
if p == 0.0 {
return f64::NEG_INFINITY;
}
if p == 1.0 {
return f64::INFINITY;
}
if p > 0.5 {
return -lower_quantile(1.0 - p);
}
lower_quantile(p)
}
fn lower_quantile(p: f64) -> f64 {
let t = (-2.0 * p.ln()).sqrt();
let mut x = -(t
- (2.515517 + 0.802853 * t + 0.010328 * t * t)
/ (1.0 + 1.432788 * t + 0.189269 * t * t + 0.001308 * t * t * t));
let target = p.ln();
for _ in 0..8 {
let cdf = norm_cdf(x);
let step = (cdf.ln() - target) * cdf / norm_pdf(x);
x -= step;
if step.abs() <= 1e-15 * x.abs().max(1.0) {
break;
}
}
x
}
pub fn beta_inc(a: f64, b: f64, x: f64) -> f64 {
if !(a > 0.0 && b > 0.0) || x.is_nan() || !(0.0..=1.0).contains(&x) {
return f64::NAN;
}
if x == 0.0 || x == 1.0 {
return x;
}
let ln_front = a * x.ln() + b * (-x).ln_1p() + ln_gamma(a + b) - ln_gamma(a) - ln_gamma(b);
if x < (a + 1.0) / (a + b + 2.0) {
(ln_front.exp() / a) * beta_cf(a, b, x)
} else {
1.0 - (ln_front.exp() / b) * beta_cf(b, a, 1.0 - x)
}
}
fn beta_cf(a: f64, b: f64, x: f64) -> f64 {
const TINY: f64 = 1e-300;
let (qab, qap, qam) = (a + b, a + 1.0, 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..=10_000 {
let m = f64::from(m);
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 delta = d * c;
h *= delta;
if (delta - 1.0).abs() < 1e-16 {
break;
}
}
h
}
pub fn student_t_cdf(t: f64, nu: f64) -> f64 {
if t.is_nan() || nu.is_nan() || nu <= 0.0 {
return f64::NAN;
}
if t.is_infinite() {
return if t > 0.0 { 1.0 } else { 0.0 };
}
let x = nu / (nu + t * t);
let tail = 0.5 * beta_inc(0.5 * nu, 0.5, x);
if t > 0.0 { 1.0 - tail } else { tail }
}
pub fn gamma_inc(a: f64, x: f64) -> (f64, f64) {
if a.is_nan() || x.is_nan() || a <= 0.0 || x < 0.0 || a == f64::INFINITY {
return (f64::NAN, f64::NAN);
}
if x == 0.0 {
return (0.0, 1.0);
}
if x == f64::INFINITY {
return (1.0, 0.0);
}
let factor = gamma_inc_factor(a, x).exp();
if x < a + 1.0 {
let mut term = 1.0 / a;
let mut sum = term;
for n in 1..100_000 {
term *= x / (a + f64::from(n));
sum += term;
if term <= sum * 1e-17 {
break;
}
}
let p = (factor * sum).min(1.0);
(p, 1.0 - p)
} else {
const TINY: f64 = 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..100_000 {
let an = -f64::from(i) * (f64::from(i) - 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 delta = d * c;
h *= delta;
if (delta - 1.0).abs() <= 1e-16 {
break;
}
}
let q = (factor * h).min(1.0);
(1.0 - q, q)
}
}
fn gamma_inc_factor(a: f64, x: f64) -> f64 {
if a < 10.0 {
return a * x.ln() - x - ln_gamma(a);
}
let u = (x - a) / a;
let log1pmx = if u.abs() < 0.5 {
let mut sum = 0.0;
let mut power = u * u;
for k in 2..200 {
let term = power / f64::from(k);
sum += if k % 2 == 0 { -term } else { term };
if term.abs() <= 1e-17 * sum.abs() {
break;
}
power *= u;
}
sum
} else {
(x / a).ln() - u
};
let inv = 1.0 / a;
let inv2 = inv * inv;
let stirling_remainder = inv
* (1.0 / 12.0
- inv2
* (1.0 / 360.0
- inv2
* (1.0 / 1260.0
- inv2
* (1.0 / 1680.0
- inv2
* (1.0 / 1188.0
- inv2 * (691.0 / 360_360.0 - inv2 / 156.0))))));
a * log1pmx + 0.5 * (a / (2.0 * PI)).ln() - stirling_remainder
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn digamma_matches_the_derivative_of_ln_gamma() {
let gamma = 0.5772156649015329;
assert!((digamma(0.5) + gamma + 2.0 * 2f64.ln()).abs() < 1e-14);
assert!((digamma(10.0) - (7129.0 / 2520.0 - gamma)).abs() < 1e-14);
for x in [0.01, 0.3, 2.7, 45.0, 1e4] {
let h = 1e-5 * x;
let numeric = (ln_gamma(x + h) - ln_gamma(x - h)) / (2.0 * h);
assert!(
(digamma(x) - numeric).abs() < 1e-7 * numeric.abs().max(1.0),
"{x}"
);
}
assert!(digamma(0.0).is_nan());
}
#[test]
fn quantile_inverts_cdf() {
for &p in &[
1e-300,
1e-100,
1e-20,
1e-8,
0.001,
0.1,
0.3,
0.5,
0.7,
0.99,
1.0 - 1e-12,
] {
let x = norm_quantile(p);
let back = norm_cdf(x);
assert!(
((back - p) / p).abs() < 1e-13,
"p = {p}, x = {x}, back = {back}"
);
}
}
#[test]
fn quantile_known_values() {
let cases = [
(0.5, 0.0),
(0.995, 2.5758293035489004),
(0.001, -3.090232306167813),
(1e-10, -6.361340902404056),
];
for (p, want) in cases {
let got = norm_quantile(p);
assert!(
(got - want).abs() < 1e-13,
"p = {p}: got {got}, want {want}"
);
}
}
#[test]
fn quantile_edges() {
assert_eq!(norm_quantile(0.0), f64::NEG_INFINITY);
assert_eq!(norm_quantile(1.0), f64::INFINITY);
assert!(norm_quantile(1.5).is_nan());
assert!(norm_quantile(f64::NAN).is_nan());
}
#[test]
fn beta_inc_closed_forms_and_symmetry() {
for &x in &[1e-10, 0.01, 0.2, 0.5, 0.8, 0.999] {
for &(a, b) in &[(0.5, 0.5), (2.0, 3.0), (10.0, 0.7), (50.0, 40.0)] {
if x < 1e-3 {
continue;
}
let s = beta_inc(a, b, x) + beta_inc(b, a, 1.0 - x);
assert!((s - 1.0).abs() < 1e-13, "a = {a}, b = {b}, x = {x}");
}
let arcsine = 2.0 / PI * x.sqrt().asin();
assert!((beta_inc(0.5, 0.5, x) - arcsine).abs() < 1e-14 * arcsine.max(1e-300));
}
assert!(beta_inc(0.0, 1.0, 0.5).is_nan());
assert!(beta_inc(1.0, 1.0, 1.5).is_nan());
assert_eq!(beta_inc(2.0, 3.0, 0.0), 0.0);
assert_eq!(beta_inc(2.0, 3.0, 1.0), 1.0);
}
#[test]
fn student_t_limits() {
for &t in &[-30.0, -2.0, -0.1, 0.0, 0.7, 5.0] {
let want = 0.5 + t / (2.0 * (2.0f64 + t * t).sqrt());
assert!((student_t_cdf(t, 2.0) - want).abs() < 1e-15, "t = {t}");
}
assert!((student_t_cdf(1.3, 1e7) - norm_cdf(1.3)).abs() < 1e-7);
assert_eq!(student_t_cdf(f64::INFINITY, 3.0), 1.0);
assert_eq!(student_t_cdf(f64::NEG_INFINITY, 3.0), 0.0);
assert!(student_t_cdf(1.0, 0.0).is_nan());
}
}