use crate::{Expression, MathConstant, Number};
pub fn digamma(z: &Expression) -> Expression {
match z {
Expression::Number(Number::Integer(1)) => Expression::mul(vec![
Expression::integer(-1),
Expression::constant(MathConstant::EulerGamma),
]),
Expression::Number(Number::Integer(n)) if *n > 1 => {
let mut sum = Expression::mul(vec![
Expression::integer(-1),
Expression::constant(MathConstant::EulerGamma),
]);
for k in 1..*n {
sum = Expression::add(vec![sum, Expression::rational(1, k)]);
}
sum
}
Expression::Number(Number::Float(x)) => {
let result = digamma_numerical(*x);
Expression::Number(Number::Float(result))
}
_ => Expression::function("digamma", vec![z.clone()]),
}
}
pub fn digamma_numerical(mut z: f64) -> f64 {
if z.is_nan() || z.is_infinite() {
return f64::NAN;
}
if z <= 0.0 && (z - z.round()).abs() < 1e-10 {
return f64::NAN;
}
let mut result = 0.0;
while z < 10.0 {
result -= 1.0 / z;
z += 1.0;
}
let z_inv = 1.0 / z;
let z2_inv = z_inv * z_inv;
result += f64::ln(z) - 0.5 * z_inv;
result -= z2_inv
* (1.0 / 12.0 - z2_inv * (1.0 / 120.0 - z2_inv * (1.0 / 252.0 - z2_inv * (1.0 / 240.0))));
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_digamma_special_values() {
let result = digamma(&Expression::Number(Number::Integer(1)));
match result {
Expression::Mul(ref factors) if factors.len() == 2 => {
assert!(matches!(
factors[0],
Expression::Number(Number::Integer(-1))
));
assert!(matches!(
factors[1],
Expression::Constant(MathConstant::EulerGamma)
));
}
_ => panic!("ψ(1) should be -γ"),
}
}
#[test]
fn test_digamma_numerical() {
let result = digamma(&Expression::Number(Number::Float(1.0)));
match result {
Expression::Number(Number::Float(val)) => {
const EULER_GAMMA: f64 = 0.577_215_664_901_532_9;
assert!((val + EULER_GAMMA).abs() < 1e-10, "ψ(1) ≈ -γ, got {}", val);
}
_ => panic!("ψ(1.0) should return numerical float"),
}
}
#[test]
fn test_digamma_recurrence() {
const EULER_GAMMA: f64 = 0.577_215_664_901_532_9;
let psi_2 = digamma_numerical(2.0);
let expected = -EULER_GAMMA + 1.0;
assert!(
(psi_2 - expected).abs() < 1e-10,
"ψ(2) = ψ(1) + 1, got {}",
psi_2
);
}
#[test]
fn test_digamma_integer_formula() {
let result = digamma(&Expression::Number(Number::Integer(3)));
let simplified = result.to_string();
assert!(simplified.contains("EulerGamma"));
}
}