use crate::utils::traits::BixverseFloat;
const DIGAMMA_ASYMPTOTIC_MIN: f64 = 10.0;
const DIGAMMA_SERIES: [f64; 6] = [
1.0 / 12.0,
-1.0 / 120.0,
1.0 / 252.0,
-1.0 / 240.0,
1.0 / 132.0,
-691.0 / 32760.0,
];
#[inline]
pub fn digamma<F: BixverseFloat>(x: F) -> F {
if x <= F::zero() {
return F::nan();
}
let min_shift = F::from_f64(DIGAMMA_ASYMPTOTIC_MIN).unwrap();
let mut acc = F::zero();
let mut z = x;
while z < min_shift {
acc -= z.recip();
z += F::one();
}
let inv = z.recip();
let inv_sq = inv * inv;
let mut series = F::zero();
let mut power = inv_sq;
for coeff in DIGAMMA_SERIES {
series += F::from_f64(coeff).unwrap() * power;
power *= inv_sq;
}
acc + z.ln() - F::from_f64(0.5).unwrap() * inv - series
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use statrs::function::gamma::digamma as statrs_digamma;
#[test]
fn test_digamma_matches_statrs_f64() {
let xs = [
1e-6, 1e-3, 0.1, 0.5, 1.0, 1.5, 2.0, 3.7, 9.999, 10.0, 10.001, 50.0, 1e3, 1e6,
];
for x in xs {
assert_relative_eq!(digamma(x), statrs_digamma(x), max_relative = 1e-13);
}
}
#[test]
fn test_digamma_matches_statrs_f32() {
for x in [0.25_f32, 1.0, 2.5, 7.0, 100.0] {
assert_relative_eq!(
digamma(x),
statrs_digamma(x as f64) as f32,
max_relative = 1e-6
);
}
}
#[test]
fn test_digamma_known_values() {
const EULER_MASCHERONI: f64 = 0.577_215_664_901_532_9;
assert_relative_eq!(digamma(1.0), -EULER_MASCHERONI, max_relative = 1e-14);
assert_relative_eq!(
digamma(0.5),
-EULER_MASCHERONI - 2.0 * 2.0_f64.ln(),
max_relative = 1e-14
);
}
#[test]
fn test_digamma_recurrence() {
for x in [0.3_f64, 2.0, 5.5, 9.999, 15.0] {
assert_relative_eq!(digamma(x + 1.0) - digamma(x), 1.0 / x, max_relative = 1e-12);
}
}
#[test]
fn test_digamma_non_positive_is_nan() {
assert!(digamma(0.0_f64).is_nan());
assert!(digamma(-1.5_f64).is_nan());
}
}