use crate::error::{SpecialError, SpecialResult};
use crate::gamma::{gamma, gammaln};
use crate::validation::{check_finite, check_positive};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::{Debug, Display};
use std::ops::{AddAssign, MulAssign, SubAssign};
#[allow(dead_code)]
pub fn gammainc_lower<T>(a: T, x: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display + AddAssign + MulAssign,
{
check_positive(a, "a")?;
check_finite(x, "x value")?;
if x <= T::zero() {
return Ok(T::zero());
}
if x < a + T::one() {
let mut sum = T::one() / a;
let mut term = T::one() / a;
let mut n = T::one();
let tol = T::from_f64(1e-15).expect("Operation failed");
while term.abs() > tol * sum.abs() {
term *= x / (a + n);
sum += term;
n += T::one();
if n > T::from_f64(1000.0).expect("Operation failed") {
return Err(SpecialError::ConvergenceError(
"gammainc_lower: series did not converge".to_string(),
));
}
}
Ok(x.powf(a) * (-x).exp() * sum)
} else {
let gamma_a = gamma(a);
let gamma_upper = gammainc_upper(a, x)?;
Ok(gamma_a - gamma_upper)
}
}
#[allow(dead_code)]
pub fn gammainc_upper<T>(a: T, x: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display + AddAssign + MulAssign,
{
check_positive(a, "a")?;
check_finite(x, "x value")?;
if x <= T::zero() {
return Ok(gamma(a));
}
if x >= a + T::one() {
let mut b = x + T::one() - a;
let mut c = T::from_f64(1e30).expect("Operation failed");
let mut d = T::one() / b;
let mut h = d;
let tol = T::from_f64(1e-15).expect("Operation failed");
for i in 1..1000 {
let an = -T::from_usize(i).expect("Operation failed")
* (T::from_usize(i).expect("Operation failed") - a);
b += T::from_f64(2.0).expect("Operation failed");
d = an * d + b;
if d.abs() < T::from_f64(1e-30).expect("Operation failed") {
d = T::from_f64(1e-30).expect("Operation failed");
}
c = b + an / c;
if c.abs() < T::from_f64(1e-30).expect("Operation failed") {
c = T::from_f64(1e-30).expect("Operation failed");
}
d = T::one() / d;
let delta = d * c;
h *= delta;
if (delta - T::one()).abs() < tol {
return Ok(x.powf(a) * (-x).exp() * h);
}
}
Err(SpecialError::ConvergenceError(
"gammainc_upper: continued fraction did not converge".to_string(),
))
} else {
let gamma_a = gamma(a);
let gamma_lower = gammainc_lower(a, x)?;
Ok(gamma_a - gamma_lower)
}
}
#[allow(dead_code)]
pub fn gammainc<T>(a: T, x: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display + AddAssign + MulAssign,
{
check_positive(a, "a")?;
check_finite(x, "x value")?;
if x <= T::zero() {
return Ok(T::zero());
}
if a > T::from_f64(100.0).expect("Operation failed") {
let log_gamma_a = gammaln(a);
let log_result = compute_log_gammainc(a, x, log_gamma_a)?;
Ok(log_result.exp())
} else {
let gamma_lower = gammainc_lower(a, x)?;
let gamma_a = gamma(a);
Ok(gamma_lower / gamma_a)
}
}
#[allow(dead_code)]
pub fn gammaincc<T>(a: T, x: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display + AddAssign + MulAssign,
{
check_positive(a, "a")?;
check_finite(x, "x value")?;
if x <= T::zero() {
return Ok(T::one());
}
let p = gammainc(a, x)?;
Ok(T::one() - p)
}
#[allow(dead_code)]
pub fn gammaincinv<T>(a: T, p: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display + AddAssign + MulAssign + SubAssign,
{
check_positive(a, "a")?;
crate::validation::check_probability(p, "p")?;
if p == T::zero() {
return Ok(T::zero());
}
if p == T::one() {
return Ok(T::infinity());
}
let x0 = initial_guess_gammaincinv(a, p);
let mut x = x0;
let tol = T::from_f64(1e-12).expect("Operation failed");
for _ in 0..100 {
let f = gammainc(a, x)? - p;
let df = x.powf(a - T::one()) * (-x).exp() / gamma(a);
let dx = f / df;
x -= dx;
if x <= T::zero() {
x = T::from_f64(1e-10).expect("Operation failed");
}
if dx.abs() < tol * x.abs() {
return Ok(x);
}
}
Err(SpecialError::ConvergenceError(
"gammaincinv: Newton iteration did not converge".to_string(),
))
}
#[allow(dead_code)]
pub fn gammainccinv<T>(a: T, q: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display + AddAssign + MulAssign + SubAssign,
{
check_positive(a, "a")?;
crate::validation::check_probability(q, "q")?;
let p = T::one() - q;
gammaincinv(a, p)
}
#[allow(dead_code)]
fn initial_guess_gammaincinv<T>(a: T, p: T) -> T
where
T: Float + FromPrimitive + Display,
{
let g = T::from_f64(2.0).expect("Operation failed")
/ (T::from_f64(9.0).expect("Operation failed") * a);
let z = crate::distributions::ndtri(p).unwrap_or(T::zero());
let w = T::one() + g * z;
if w > T::zero() {
a * w.powf(T::from_f64(3.0).expect("Operation failed"))
} else {
if p < T::from_f64(0.5).expect("Operation failed") {
a * T::from_f64(0.1).expect("Operation failed")
} else {
a * T::from_f64(2.0).expect("Operation failed")
}
}
}
#[allow(dead_code)]
fn compute_log_gammainc<T>(a: T, x: T, log_gammaa: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + AddAssign + MulAssign,
{
let log_x = x.ln();
let log_result = a * log_x - x - log_gammaa;
let mut sum = T::one();
let mut term = T::one();
for n in 1..50 {
term *= (a - T::from_usize(n).expect("Operation failed")) / x;
sum += term;
if term.abs() < T::from_f64(1e-15).expect("Operation failed") {
break;
}
}
Ok(log_result + sum.ln())
}
#[allow(dead_code)]
pub fn gammastar<T>(x: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Debug + Display + AddAssign + MulAssign,
{
check_positive(x, "x")?;
if x >= T::from_f64(10.0).expect("Operation failed") {
let mut sum = T::one();
let x2 = x * x;
let mut xn = x;
let coeffs = [
T::from_f64(1.0 / 12.0).expect("Operation failed"),
T::from_f64(1.0 / 288.0).expect("Operation failed"),
T::from_f64(-139.0 / 51840.0).expect("Operation failed"),
T::from_f64(-571.0 / 2488320.0).expect("Operation failed"),
];
for &c in &coeffs {
sum += c / xn;
xn *= x2;
}
Ok(sum)
} else {
let sqrt_2pi = T::from_f64((2.0 * std::f64::consts::PI).sqrt()).expect("Operation failed");
let gamma_x = gamma(x);
let x_power = x.powf(x - T::from_f64(0.5).expect("Operation failed"));
let exp_neg_x = (-x).exp();
Ok(gamma_x / (sqrt_2pi * x_power * exp_neg_x))
}
}
#[allow(dead_code)]
pub fn gammasgn<T>(x: T) -> T
where
T: Float + FromPrimitive,
{
if x > T::zero() {
T::one()
} else {
let floor_x = x.floor();
if x == floor_x {
T::nan()
} else {
if floor_x.to_isize().unwrap_or(0) % 2 == 0 {
T::one()
} else {
-T::one()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_gammainc_lower() {
assert_relative_eq!(
gammainc_lower(1.0, 1.0).expect("Operation failed"),
0.6321205588285577, epsilon = 1e-10
);
assert_relative_eq!(
gammainc_lower(2.0, 1.0).expect("Operation failed"),
0.264241117657115, epsilon = 1e-10
);
assert_relative_eq!(
gammainc_lower(3.0, 2.0).expect("Operation failed"),
0.646647167633873, epsilon = 1e-10
);
assert_eq!(gammainc_lower(1.0, 0.0).expect("Operation failed"), 0.0);
}
#[test]
fn test_gammainc() {
assert_relative_eq!(
gammainc(1.0, 1.0).expect("Operation failed"),
0.6321205588285577,
epsilon = 1e-10
);
assert_relative_eq!(
gammainc(2.0, 2.0).expect("Operation failed"),
0.5939941502901619,
epsilon = 1e-10
);
assert_eq!(gammainc(1.0, 0.0).expect("Operation failed"), 0.0);
}
#[test]
fn test_gammaincc() {
let a = 2.0;
let x = 1.5;
let p = gammainc(a, x).expect("Operation failed");
let q = gammaincc(a, x).expect("Operation failed");
assert_relative_eq!(p + q, 1.0, epsilon = 1e-10);
}
#[test]
fn test_gammaincinv() {
let a = 2.5;
let x = 3.0;
let p = gammainc(a, x).expect("Operation failed");
let x_recovered = gammaincinv(a, p).expect("Operation failed");
assert_relative_eq!(x_recovered, x, epsilon = 1e-8);
}
#[test]
fn test_gammasgn() {
assert_eq!(gammasgn(1.0), 1.0);
assert_eq!(gammasgn(2.5), 1.0);
assert_eq!(gammasgn(-0.5), -1.0);
assert_eq!(gammasgn(-1.5), 1.0);
assert_eq!(gammasgn(-2.5), -1.0);
}
}