mathhook_core/functions/special/
polygamma.rs1use super::{digamma, digamma_numerical};
2use crate::{Expression, Number};
3
4pub fn polygamma(n: i32, z: &Expression) -> Expression {
35 if n == 0 {
36 return digamma(z);
37 }
38
39 if n == 1 {
40 match z {
41 Expression::Number(Number::Integer(1)) => Expression::div(
42 Expression::pow(Expression::pi(), Expression::integer(2)),
43 Expression::integer(6),
44 ),
45 Expression::Number(Number::Float(x)) => {
46 let result = polygamma_numerical(1, *x);
47 Expression::Number(Number::Float(result))
48 }
49 _ => Expression::function(
50 "polygamma",
51 vec![Expression::Number(Number::Integer(n as i64)), z.clone()],
52 ),
53 }
54 } else {
55 match z {
56 Expression::Number(Number::Float(x)) => {
57 let result = polygamma_numerical(n, *x);
58 Expression::Number(Number::Float(result))
59 }
60 _ => Expression::function(
61 "polygamma",
62 vec![Expression::Number(Number::Integer(n as i64)), z.clone()],
63 ),
64 }
65 }
66}
67
68fn polygamma_numerical(n: i32, z: f64) -> f64 {
81 if n < 0 {
82 return f64::NAN;
83 }
84
85 if n == 0 {
86 return digamma_numerical(z);
87 }
88
89 if z.is_nan() || z.is_infinite() {
90 return f64::NAN;
91 }
92
93 if z <= 0.0 && (z - z.round()).abs() < 1e-10 {
94 return f64::NAN;
95 }
96
97 let sign = if (n + 1) % 2 == 0 { 1.0 } else { -1.0 };
98
99 let mut sum = 0.0;
100 for k in 0..1000 {
101 let term = 1.0 / f64::powi(z + k as f64, n + 1);
102 sum += term;
103 if term.abs() < 1e-15 * sum.abs() {
104 break;
105 }
106 }
107
108 let mut factorial = 1.0;
109 for i in 1..=n {
110 factorial *= i as f64;
111 }
112
113 sign * factorial * sum
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::{Expression, Number};
120
121 #[test]
122 fn test_polygamma_zero_is_digamma() {
123 let z = Expression::Number(Number::Integer(2));
124 let poly_0 = polygamma(0, &z);
125 let dig = digamma(&z);
126 assert_eq!(poly_0, dig);
127 }
128
129 #[test]
130 fn test_polygamma_trigamma_special_value() {
131 let result = polygamma(1, &Expression::Number(Number::Integer(1)));
132 let result_str = result.to_string();
133 assert!(
134 result_str.contains("Pi") && (result_str.contains('6') || result_str.contains("Pow")),
135 "ψ^(1)(1) = π²/6"
136 );
137 }
138}