use crate::error::DigiFiError;
use crate::consts::{GAMMA_DK, GAMMA_R};
pub fn ln_gamma(z: f64) -> f64 {
let ln_2_sqrt_e_over_pi: f64 = (2.0 * (std::f64::consts::E / std::f64::consts::PI).sqrt()).ln();
if z < 0.5 {
let s = GAMMA_DK.iter().enumerate().skip(1).fold(GAMMA_DK[0], |s, t| s + t.1 / (t.0 as f64 - z));
std::f64::consts::PI.ln() - (std::f64::consts::PI * z).sin().ln() - s.ln() - ln_2_sqrt_e_over_pi
- (0.5 - z) * ((0.5 - z + GAMMA_R) / std::f64::consts::E).ln()
} else {
let s = GAMMA_DK.iter().enumerate().skip(1).fold(GAMMA_DK[0], |s, t| s + t.1 / (z + t.0 as f64 - 1.0));
s.ln() + ln_2_sqrt_e_over_pi + (z - 0.5) * ((z - 0.5 + GAMMA_R) / std::f64::consts::E).ln()
}
}
pub fn gamma(z: f64) -> f64 {
ln_gamma(z).exp()
}
pub fn lower_incomplete_gamma(z: f64, x: f64, n_terms: Option<usize>) -> Result<f64, DigiFiError> {
if x < 0.0 {
return Err(DigiFiError::ParameterConstraint {
title: "Lower Incomplete Gamma Function".to_owned(),
constraint: "The value of `x` must be non-negative.".to_owned(),
});
}
let n_terms: usize = n_terms.unwrap_or(20);
let result: f64 = (0..n_terms).into_iter().fold(0.0, |result, k| {
let denominator: f64 = match k {
0 => z,
_ => {
let denom: f64 = (0..(k+1)).into_iter().fold(1.0, |denom, i| denom * (z + (i as f64)) );
denom
},
};
result + x.powi(k as i32) / denominator
} );
Ok(x.powf(z) * (-x).exp() * result)
}
pub fn upper_incomplete_gamma(z: f64, x: f64, n_terms: Option<usize>) -> Result<f64, DigiFiError> {
if x < 0.0 {
return Err(DigiFiError::ParameterConstraint {
title: "Upper Incomplete Gamma Function".to_owned(),
constraint: "The value of `x` must be non-negative.".to_owned(),
});
}
Ok(gamma(z) - lower_incomplete_gamma(z, x, n_terms)?)
}
pub fn digamma(z: f64) -> Result<f64, DigiFiError> {
if z <= 0.0 {
return Err(DigiFiError::ParameterConstraint { title: "Digamma Function".to_owned(), constraint: "The value of `z` must be positive.".to_owned(), });
}
let mut shifted_z: f64 = z;
let mut result: f64 = 0.0;
while shifted_z < 6.0 {
result -= 1.0 / shifted_z;
shifted_z += 1.0;
}
let z: f64 = shifted_z;
let proxy_result: f64 = z.ln() - 1.0/(2.0 * z) - 1.0/(12.0 * z.powi(2) + 1.0/(120.0 * z.powi(4)) - 1.0/(252.0 * z.powi(6)) + 1.0/(240.0 * z.powi(8)) - 1.0/(132.0 * z.powi(10)) + 691.0/(32760.0 * z.powi(12)) - 1.0/(12.0 * z.powi(14)));
Ok(proxy_result + result)
}
#[cfg(test)]
mod tests {
use crate::utilities::TEST_ACCURACY;
#[test]
fn unit_test_gamma() -> () {
use crate::utilities::maths_utils::factorial;
use crate::statistics::gamma::gamma;
assert!((gamma(1.0) - 1.0).abs() < TEST_ACCURACY);
let theoretical_result: f64 = std::f64::consts::PI.sqrt() * (factorial(factorial(3 - 2)) as f64) / 2.0_f64.powf((3.0 - 1.0) / 2.0);
assert!((gamma(3.0/2.0) - theoretical_result).abs() < TEST_ACCURACY);
}
#[test]
fn unit_test_lower_incomplete_gamma() -> () {
use crate::statistics::gamma::lower_incomplete_gamma;
assert!((lower_incomplete_gamma(1.0, 3.0, None).unwrap() - (1.0 - (-3.0_f64).exp())).abs() < TEST_ACCURACY);
}
#[test]
fn unit_test_upper_incomplete_gamma() -> () {
use crate::statistics::gamma::{gamma, upper_incomplete_gamma};
assert!((upper_incomplete_gamma(4.0, 0.0, Some(30)).unwrap() - gamma(4.0)).abs() < TEST_ACCURACY);
assert!((upper_incomplete_gamma(1.0, 3.0, Some(30)).unwrap() - (-3.0_f64).exp()).abs() < TEST_ACCURACY);
}
#[test]
fn unit_test_digamma() -> () {
use crate::consts::EULER_MASCHERONI_CONSTANT;
use crate::statistics::gamma::digamma;
assert!((digamma(1.0).unwrap() + EULER_MASCHERONI_CONSTANT).abs() < 1_000.0 * TEST_ACCURACY);
assert!((digamma(0.5).unwrap() + (2.0 * 2.0_f64.ln() + EULER_MASCHERONI_CONSTANT)).abs() < 1_000.0 * TEST_ACCURACY);
assert!((digamma(1.0/3.0).unwrap() + (std::f64::consts::PI/(2.0 * 3.0_f64.sqrt()) + 3.0 * 3.0_f64.ln() / 2.0 + EULER_MASCHERONI_CONSTANT)).abs() < 1_000.0 * TEST_ACCURACY);
}
}