use std::f64::consts::PI;
const LANCZOS_G: f64 = 7.0;
const LANCZOS_C: [f64; 9] = [
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7,
];
pub fn gamma(z: f64) -> f64 {
if z < 0.5 {
let sin_pi_z = (PI * z).sin();
if sin_pi_z.abs() < 1e-15 {
return f64::INFINITY; }
PI / (sin_pi_z * gamma(1.0 - z))
} else {
let z = z - 1.0;
let mut x = LANCZOS_C[0];
for i in 1..LANCZOS_C.len() {
x += LANCZOS_C[i] / (z + i as f64);
}
let t = z + LANCZOS_G + 0.5;
(2.0 * PI).sqrt() * t.powf(z + 0.5) * (-t).exp() * x
}
}
pub fn log_gamma(z: f64) -> f64 {
if z <= 0.0 {
return f64::NAN;
}
let g = gamma(z);
if g.is_infinite() || g <= 0.0 {
return f64::INFINITY;
}
g.ln()
}
pub fn beta(a: f64, b: f64) -> f64 {
gamma(a) * gamma(b) / gamma(a + b)
}
pub fn erf(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
if x.is_infinite() {
return if x > 0.0 { 1.0 } else { -1.0 };
}
let sign = if x < 0.0 { -1.0 } else { 1.0 };
sign * incomplete_gamma_p(0.5, x * x)
}
pub fn erfc(x: f64) -> f64 {
1.0 - erf(x)
}
pub fn sinc(x: f64) -> f64 {
if x.abs() < 1e-15 {
1.0
} else {
(PI * x).sin() / (PI * x)
}
}
pub fn sinc_unnorm(x: f64) -> f64 {
if x.abs() < 1e-15 {
1.0
} else {
x.sin() / x
}
}
pub fn incomplete_gamma_p(a: f64, x: f64) -> f64 {
if x < 0.0 || a <= 0.0 {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
let gln = log_gamma(a);
if x < a + 1.0 {
let mut term = 1.0 / a;
let mut sum = term;
for n in 1..200 {
term *= x / (a + n as f64);
sum += term;
if term.abs() < 1e-18 * sum.abs() {
break;
}
}
sum * x.powf(a) * (-x).exp() / gln.exp()
} else {
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 f = d;
for n in 1..300 {
let an = -(n as f64) * (n as f64 - a);
b = 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 = c * d;
f *= delta;
if (delta - 1.0).abs() < 1e-16 {
break;
}
}
let q = f * (-x + a * x.ln() - gln).exp();
1.0 - q
}
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
fn rel_close(a: f64, b: f64, tol: f64) -> bool {
if b.abs() < 1e-15 {
(a - b).abs() < tol
} else {
((a - b) / b).abs() < tol
}
}
#[test]
fn gamma_half() {
assert!(close(gamma(0.5), PI.sqrt(), 1e-10));
}
#[test]
fn gamma_integers() {
assert!(close(gamma(1.0), 1.0, 1e-10));
assert!(close(gamma(2.0), 1.0, 1e-10));
assert!(close(gamma(3.0), 2.0, 1e-10));
assert!(close(gamma(4.0), 6.0, 1e-10));
assert!(close(gamma(5.0), 24.0, 1e-10));
assert!(close(gamma(6.0), 120.0, 1e-10));
}
#[test]
fn gamma_reflection() {
let z = 0.3;
let product = gamma(z) * gamma(1.0 - z);
assert!(close(product, PI / (PI * z).sin(), 1e-9));
}
#[test]
fn beta_function() {
assert!(close(beta(1.0, 1.0), 1.0, 1e-10));
assert!(close(beta(2.0, 2.0), 1.0 / 6.0, 1e-10));
assert!(close(beta(0.5, 0.5), PI, 1e-9));
}
#[test]
fn erf_basic() {
assert!(close(erf(0.0), 0.0, 1e-15));
assert!(close(erf(0.5), 0.5204998778, 1e-8));
assert!(close(erf(1.0), 0.8427007929, 1e-8));
assert!(close(erf(2.0), 0.9953222650, 1e-8));
}
#[test]
fn erf_negative() {
assert!(close(erf(-1.0), -erf(1.0), 1e-14));
assert!(close(erf(-0.5), -erf(0.5), 1e-14));
}
#[test]
fn erfc_basic() {
assert!(close(erfc(0.0), 1.0, 1e-15));
assert!(close(erfc(1.0), 0.1572992071, 1e-8));
for &x in &[0.1, 0.5, 1.0, 2.0, 3.0] {
assert!(close(erfc(x), 1.0 - erf(x), 1e-12));
}
}
#[test]
fn erf_large() {
assert!(close(erf(f64::INFINITY), 1.0, 1e-15));
assert!(close(erf(f64::NEG_INFINITY), -1.0, 1e-15));
}
#[test]
fn sinc_basic() {
assert!(close(sinc(0.0), 1.0, 1e-15));
assert!(close(sinc(1.0), 0.0, 1e-15));
assert!(close(sinc(0.5), 2.0 / PI, 1e-14));
}
#[test]
fn sinc_unnorm_basic() {
assert!(close(sinc_unnorm(0.0), 1.0, 1e-15));
assert!(close(sinc_unnorm(PI), 0.0, 1e-14));
}
#[test]
fn log_gamma_positive() {
assert!(close(log_gamma(5.0), 24.0f64.ln(), 1e-10));
}
#[test]
fn incomplete_gamma_chi2() {
assert!(close(incomplete_gamma_p(1.0, 0.0), 0.0, 1e-15));
let p = incomplete_gamma_p(1.0, 2.0);
assert!(close(p, 1.0 - (-2.0f64).exp(), 1e-8));
}
}