use petir::mathf::RealMath;
use petir::real::{erf as c_erf, erfc as c_erfc, tgamma as c_gamma};
fn factorial(n: i32) -> f64 {
(1..=n).fold(1.0_f64, |acc, i| acc * i as f64)
}
fn calc_qe11(a: f64, x: f64, e: i32) -> f64 {
let mut a_2n = 0.0_f64;
let mut b_2n = 1.0_f64;
let mut a_2np1 = 1.0_f64;
let mut b_2np1 = x;
let mut n = 1;
while 2 * n <= e {
let a_2nm1 = a_2np1;
let b_2nm1 = b_2np1;
let n_minus_a = n as f64 - a;
a_2n = a_2nm1 + n_minus_a * a_2n;
b_2n = b_2nm1 + n_minus_a * b_2n;
a_2np1 = x * a_2n + n as f64 * a_2nm1;
b_2np1 = x * b_2n + n as f64 * b_2nm1;
n += 1;
}
if 2 * (n - 1) < e {
a_2np1 / b_2np1
} else {
a_2n / b_2n
}
}
fn calc_pe15(a: f64, x: f64, nmax: i32) -> f64 {
let mut prod = 1.0_f64;
let mut sum = 0.0_f64;
for n in 1..=nmax {
prod *= a + n as f64;
sum += x.r_powi(n) / prod;
}
let r = (-x).r_exp() * x.r_powf(a) / c_gamma(a);
r / a * (1.0 + sum)
}
fn calc_qe16(a: f64, x: f64, n_terms: i32) -> f64 {
let mut an = 1.0_f64;
let mut sum = 0.0_f64;
for n in 1..=(n_terms - 1) {
an *= a - n as f64;
sum += an / x.r_powi(n);
}
let r = (-x).r_exp() * x.r_powf(a) / c_gamma(a);
r / x * (1.0 + sum)
}
fn calc_te18(a: f64, e0: f64, _x: f64, lambda: f64, sigma: f64, phi: f64) -> f64 {
const D0_0: f64 = -0.333_333_333_333_333e+00;
const D0_1: f64 = 0.833_333_333_333_333e-01;
const D0_2: f64 = -0.148_148_148_148_148e-01;
const D0_3: f64 = 0.115_740_740_740_741e-02;
const D0_4: f64 = 0.352_733_686_067_019e-03;
const D0_5: f64 = -0.178_755_144_032_922e-03;
const D0_6: f64 = 0.391_926_317_852_244e-04;
const D1_0: f64 = -0.185_185_185_185_185e-02;
const D1_1: f64 = -0.347_222_222_222_222e-02;
const D1_2: f64 = 0.264_550_264_550_265e-02;
const D1_3: f64 = -0.990_226_337_448_560e-03;
const D1_4: f64 = 0.205_761_316_872_428e-03;
const D2_0: f64 = 0.413_359_788_359_788e-02;
const D2_1: f64 = -0.268_132_716_049_383e-02;
let u = 1.0 / a;
let mut z = (2.0 * phi).sqrt();
if lambda < 1.0 {
z = -z;
}
let c2 = D2_1 * z + D2_0;
if sigma > e0 / a.sqrt() {
let z2 = z * z;
let z3 = z2 * z;
let z4 = z2 * z2;
let z5 = z4 * z;
let z6 = z3 * z3;
let c0 = D0_6 * z6 + D0_5 * z5 + D0_4 * z4 + D0_3 * z3 + D0_2 * z2 + D0_1 * z + D0_0;
let c1 = D1_4 * z4 + D1_3 * z3 + D1_2 * z2 + D1_1 * z + D1_0;
c2 * u * u + c1 * u + c0
} else {
let z2 = z * z;
let c0 = D0_2 * z2 + D0_1 * z + D0_0;
let c1 = D1_1 * z + D1_0;
c2 * u * u + c1 * u + c0
}
}
pub fn inc_gamma_ratio_q(a: f64, x: f64) -> f64 {
use std::f64::consts::PI;
const BIG: f64 = 14.0;
const X0: f64 = 17.0;
const E0: f64 = 0.025;
if a < 1.0 {
if (a - 0.5).abs() < f64::EPSILON {
if x < 0.25 {
return 1.0 - c_erf(x.sqrt());
} else {
return c_erfc(x.sqrt());
}
} else if x < 1.1 {
let alpha = if x < 0.5 {
(0.765_f64.sqrt().r_ln()) / x.r_ln()
} else {
x / 2.59
};
let mut sum = 0.0;
for n in 1i32..=10 {
sum += (-x).r_powi(n) / ((a + n as f64) * factorial(n));
}
let j = -a * sum;
if a >= alpha {
return 1.0 - (x.r_powf(a) * (1.0 - j)) / c_gamma(a + 1.0);
} else {
let l = (a * x.r_ln()).r_exp() - 1.0;
let h = 1.0 / c_gamma(a + 1.0) - 1.0;
return (x.r_powf(a) * j - l) / c_gamma(a + 1.0) - h;
}
} else {
let r = (-x).r_exp() * x.r_powf(a) / c_gamma(a);
return r * calc_qe11(a, x, 30);
}
} else if a >= BIG {
let sigma = (1.0 - x / a).abs();
let lambda = x / a;
let phi = lambda - 1.0 - lambda.r_ln();
let y = a * phi;
if sigma <= E0 / a.sqrt() {
let e_val = 0.5 - (1.0 - y / 3.0) * (y / PI).sqrt();
let te = calc_te18(a, E0, x, lambda, sigma, phi);
return if lambda <= 1.0 {
1.0 - (e_val - (1.0 - y) / (2.0 * PI * a).sqrt() * te)
} else {
e_val + (1.0 - y) / (2.0 * PI * a).sqrt() * te
};
} else if sigma <= 0.4 {
let te = calc_te18(a, E0, x, lambda, sigma, phi);
return if lambda <= 1.0 {
1.0 - (0.5 * c_erfc(y.sqrt()) - (-y).r_exp() / (2.0 * PI * a).sqrt() * te)
} else {
0.5 * c_erfc(y.sqrt()) + (-y).r_exp() / (2.0 * PI * a).sqrt() * te
};
} else if x <= a.max(10.0_f64.r_ln()) {
return 1.0 - calc_pe15(a, x, 20);
} else if x < X0 {
let r = (-x).r_exp() * x.r_powf(a) / c_gamma(a);
return r * calc_qe11(a, x, 30);
} else {
return calc_qe16(a, x, 20);
}
} else {
if a > x || x >= X0 {
if x <= a.max(10.0_f64.r_ln()) {
return 1.0 - calc_pe15(a, x, 20);
} else if x < X0 {
let r = (-x).r_exp() * x.r_powf(a) / c_gamma(a);
return r * calc_qe11(a, x, 30);
} else {
return calc_qe16(a, x, 20);
}
} else {
let a_floor = a.floor();
let half_a = (a * 2.0).floor();
if half_a == a * 2.0 {
if a_floor == a {
let mut sum = 0.0;
for n in 0..=(a as i64 - 1) {
sum += x.r_powi(n as i32) / factorial(n as i32);
}
return (-x).r_exp() * sum;
} else {
let i = (a - 0.5) as i32;
let mut prod = 1.0_f64;
let mut sum = 0.0_f64;
for n in 1..=i {
prod *= n as f64 - 0.5;
sum += x.r_powi(n) / prod;
}
return c_erfc(x.sqrt()) + (-x).r_exp() / (PI * x).sqrt() * sum;
}
} else if x <= a.max(10.0_f64.r_ln()) {
return 1.0 - calc_pe15(a, x, 20);
} else if x < X0 {
let r = (-x).r_exp() * x.r_powf(a) / c_gamma(a);
return r * calc_qe11(a, x, 30);
} else {
return calc_qe16(a, x, 20);
}
}
}
}
#[inline]
pub fn inc_gamma_ratio_p(a: f64, x: f64) -> f64 {
1.0 - inc_gamma_ratio_q(a, x)
}
#[inline]
pub fn inc_gamma_q(a: f64, x: f64) -> f64 {
inc_gamma_ratio_q(a, x) * c_gamma(a)
}
#[inline]
pub fn inc_gamma_p(a: f64, x: f64) -> f64 {
inc_gamma_ratio_p(a, x) * c_gamma(a)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn p_1_1() {
let p = inc_gamma_ratio_p(1.0, 1.0);
assert!(approx_eq(p, 1.0 - (-1.0_f64).r_exp(), 1e-6), "P(1,1)={p}");
}
#[test]
fn pq_sum_to_one() {
for &(a, x) in &[(0.5_f64, 1.0), (1.0, 2.0), (5.0, 3.0), (20.0, 15.0)] {
let p = inc_gamma_ratio_p(a, x);
let q = inc_gamma_ratio_q(a, x);
assert!(
approx_eq(p + q, 1.0, 1e-10),
"P+Q≠1 for a={a} x={x}: {}",
p + q
);
}
}
#[test]
fn q_at_zero() {
for &a in &[0.5_f64, 1.0, 5.0, 15.0] {
let q = inc_gamma_ratio_q(a, 1e-300);
assert!(approx_eq(q, 1.0, 1e-4), "Q({a},0)={q}");
}
}
#[test]
fn unnormalised_consistency() {
let a = 3.0;
let x = 2.0;
let gq = inc_gamma_q(a, x);
let gp = inc_gamma_p(a, x);
assert!(approx_eq(gq + gp, c_gamma(a), 1e-10), "γ+Γ≠Γ(a)");
}
}