use std::f64::consts::PI;
const LANCZOS_G: f64 = 7.0;
const LANCZOS_C: [f64; 9] = [
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7,
];
pub fn gamma(z: f64) -> f64 {
if z < 0.5 {
let sin_pi_z = (PI * z).sin();
if sin_pi_z.abs() < 1e-15 {
return f64::INFINITY; }
PI / (sin_pi_z * gamma(1.0 - z))
} else {
let z = z - 1.0;
let mut x = LANCZOS_C[0];
for i in 1..LANCZOS_C.len() {
x += LANCZOS_C[i] / (z + i as f64);
}
let t = z + LANCZOS_G + 0.5;
(2.0 * PI).sqrt() * t.powf(z + 0.5) * (-t).exp() * x
}
}
pub fn log_gamma(z: f64) -> f64 {
if z <= 0.0 {
return f64::NAN;
}
let g = gamma(z);
if g.is_infinite() || g <= 0.0 {
return f64::INFINITY;
}
g.ln()
}
pub fn beta(a: f64, b: f64) -> f64 {
gamma(a) * gamma(b) / gamma(a + b)
}
pub fn erf(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
if x.is_infinite() {
return if x > 0.0 { 1.0 } else { -1.0 };
}
let sign = if x < 0.0 { -1.0 } else { 1.0 };
sign * incomplete_gamma_p(0.5, x * x)
}
pub fn erfc(x: f64) -> f64 {
1.0 - erf(x)
}
pub fn sinc(x: f64) -> f64 {
if x.abs() < 1e-15 {
1.0
} else {
(PI * x).sin() / (PI * x)
}
}
pub fn sinc_unnorm(x: f64) -> f64 {
if x.abs() < 1e-15 {
1.0
} else {
x.sin() / x
}
}
pub fn bessel_j0(x: f64) -> f64 {
let ax = x.abs();
if ax < 8.0 {
let xsq = x * x / 4.0;
let mut term = 1.0_f64;
let mut sum = term;
for k in 1..60 {
let kf = k as f64;
term *= -xsq / (kf * kf);
sum += term;
if term.abs() < 1e-17 * sum.abs().max(1e-300) {
break;
}
}
sum
} else {
let z = 8.0 / ax;
let y = z * z;
let p = 1.0
+ y * (-0.1098628627e-2
+ y * (0.7464519654e-3
+ y * (-0.4724987825e-4
+ y * (0.2181196076e-5
+ y * (-0.6397653302e-7 + y * 0.9538904063e-9)))));
let q = -0.1562499995e-1
+ y * (0.1430484407e-3
+ y * (-0.4253339102e-4
+ y * (0.2493458662e-5
+ y * (-0.1248279047e-6 + y * 0.2860702546e-8))));
let xx = ax - PI / 4.0;
let result = (p * xx.cos() - z * q * xx.sin()) / ax.sqrt();
result.abs() * result.signum()
}
}
pub fn bessel_j1(x: f64) -> f64 {
let ax = x.abs();
let sign = if x < 0.0 { -1.0 } else { 1.0 };
if ax < 8.0 {
let xsq = x * x / 4.0;
let mut term = 0.5 * x; let mut sum = term;
for k in 1..60 {
let kf = k as f64;
term *= -xsq / (kf * (kf + 1.0));
sum += term;
if term.abs() < 1e-17 * sum.abs().max(1e-300) {
break;
}
}
sum
} else {
let z = 8.0 / ax;
let y = z * z;
let p = 1.0
+ y * (0.183105e-2
+ y * (-0.3516396496e-3
+ y * (0.2457529642e-4
+ y * (-0.2403370194e-5
+ y * 0.1058465960e-7))));
let q = 0.4687499995e-1
+ y * (-0.2002690873e-3
+ y * (0.4717512717e-4
+ y * (-0.9414049147e-6
+ y * (0.1344888788e-7 + y * -0.2199534093e-9))));
let xx = ax - 3.0 * PI / 4.0;
let result = (p * xx.cos() - z * q * xx.sin()) / ax.sqrt();
sign * result
}
}
pub fn bessel_jn(n: i32, x: f64) -> f64 {
if n == 0 {
return bessel_j0(x);
}
if n == 1 {
return bessel_j1(x);
}
if n < 0 {
let jn = bessel_jn(-n, x);
return if (-n) % 2 == 1 { -jn } else { jn };
}
if x.abs() < 1e-15 {
return 0.0;
}
let n_u = n as u32;
let half_x = x.abs() / 2.0;
if (n as f64) > x.abs() {
let mut term = 1.0 / factorial_u64(n_u);
let mut sum = term;
let xx = half_x * half_x;
for k in 1..200u32 {
term *= -xx / (k as f64 * (n_u + k) as f64);
let next = sum + term;
if (next - sum).abs() < 1e-18 * sum.abs().max(1e-300) {
break;
}
sum = next;
}
let mag = sum * half_x.powi(n);
if x < 0.0 && n_u % 2 == 1 { -mag } else { mag }
} else {
let tox = 2.0 / x;
let mut prev = bessel_j0(x);
let mut curr = bessel_j1(x);
for k in 1..n_u {
let next = (k as f64) * tox * curr - prev;
prev = curr;
curr = next;
if curr.abs() > 1e150 {
return 0.0;
}
}
if x < 0.0 && n_u % 2 == 1 { -curr } else { curr }
}
}
fn factorial_u64(n: u32) -> f64 {
let mut f = 1.0_f64;
for i in 2..=n {
f *= i as f64;
}
f
}
pub fn incomplete_gamma_p(a: f64, x: f64) -> f64 {
if x < 0.0 || a <= 0.0 {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
let gln = log_gamma(a);
if x < a + 1.0 {
let mut term = 1.0 / a;
let mut sum = term;
for n in 1..200 {
term *= x / (a + n as f64);
sum += term;
if term.abs() < 1e-18 * sum.abs() {
break;
}
}
sum * x.powf(a) * (-x).exp() / gln.exp()
} else {
let tiny = 1e-30;
let mut b = x + 1.0 - a;
let mut c = 1.0 / tiny;
let mut d = 1.0 / b;
let mut f = d;
for n in 1..300 {
let an = -(n as f64) * (n as f64 - a);
b = b + 2.0;
d = an * d + b;
if d.abs() < tiny {
d = tiny;
}
c = b + an / c;
if c.abs() < tiny {
c = tiny;
}
d = 1.0 / d;
let delta = c * d;
f *= delta;
if (delta - 1.0).abs() < 1e-16 {
break;
}
}
let q = f * (-x + a * x.ln() - gln).exp();
1.0 - q
}
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
fn rel_close(a: f64, b: f64, tol: f64) -> bool {
if b.abs() < 1e-15 {
(a - b).abs() < tol
} else {
((a - b) / b).abs() < tol
}
}
#[test]
fn gamma_half() {
assert!(close(gamma(0.5), PI.sqrt(), 1e-10));
}
#[test]
fn gamma_integers() {
assert!(close(gamma(1.0), 1.0, 1e-10));
assert!(close(gamma(2.0), 1.0, 1e-10));
assert!(close(gamma(3.0), 2.0, 1e-10));
assert!(close(gamma(4.0), 6.0, 1e-10));
assert!(close(gamma(5.0), 24.0, 1e-10));
assert!(close(gamma(6.0), 120.0, 1e-10));
}
#[test]
fn gamma_reflection() {
let z = 0.3;
let product = gamma(z) * gamma(1.0 - z);
assert!(close(product, PI / (PI * z).sin(), 1e-9));
}
#[test]
fn beta_function() {
assert!(close(beta(1.0, 1.0), 1.0, 1e-10));
assert!(close(beta(2.0, 2.0), 1.0 / 6.0, 1e-10));
assert!(close(beta(0.5, 0.5), PI, 1e-9));
}
#[test]
fn erf_basic() {
assert!(close(erf(0.0), 0.0, 1e-15));
assert!(close(erf(0.5), 0.5204998778, 1e-8));
assert!(close(erf(1.0), 0.8427007929, 1e-8));
assert!(close(erf(2.0), 0.9953222650, 1e-8));
}
#[test]
fn erf_negative() {
assert!(close(erf(-1.0), -erf(1.0), 1e-14));
assert!(close(erf(-0.5), -erf(0.5), 1e-14));
}
#[test]
fn erfc_basic() {
assert!(close(erfc(0.0), 1.0, 1e-15));
assert!(close(erfc(1.0), 0.1572992071, 1e-8));
for &x in &[0.1, 0.5, 1.0, 2.0, 3.0] {
assert!(close(erfc(x), 1.0 - erf(x), 1e-12));
}
}
#[test]
fn erf_large() {
assert!(close(erf(f64::INFINITY), 1.0, 1e-15));
assert!(close(erf(f64::NEG_INFINITY), -1.0, 1e-15));
}
#[test]
fn sinc_basic() {
assert!(close(sinc(0.0), 1.0, 1e-15));
assert!(close(sinc(1.0), 0.0, 1e-15));
assert!(close(sinc(0.5), 2.0 / PI, 1e-14));
}
#[test]
fn sinc_unnorm_basic() {
assert!(close(sinc_unnorm(0.0), 1.0, 1e-15));
assert!(close(sinc_unnorm(PI), 0.0, 1e-14));
}
#[test]
fn log_gamma_positive() {
assert!(close(log_gamma(5.0), 24.0f64.ln(), 1e-10));
}
#[test]
fn incomplete_gamma_chi2() {
assert!(close(incomplete_gamma_p(1.0, 0.0), 0.0, 1e-15));
let p = incomplete_gamma_p(1.0, 2.0);
assert!(close(p, 1.0 - (-2.0f64).exp(), 1e-8));
}
#[test]
fn bessel_j0_basic() {
assert!(close(bessel_j0(0.0), 1.0, 1e-12));
assert!(close(bessel_j0(2.4048255576957727), 0.0, 1e-6));
assert!(close(bessel_j0(5.0), -0.17759677131434, 1e-8));
}
#[test]
fn bessel_j1_basic() {
assert!(close(bessel_j1(0.0), 0.0, 1e-12));
assert!(close(bessel_j1(2.0), 0.5767248077568736, 1e-8));
assert!(close(bessel_j1(3.8317059702075125), 0.0, 1e-6));
}
#[test]
fn bessel_jn_positive() {
for &x in &[0.5, 1.0, 2.0, 5.0, 10.0] {
assert!(close(bessel_jn(0, x), bessel_j0(x), 1e-10));
assert!(close(bessel_jn(1, x), bessel_j1(x), 1e-10));
}
assert!(close(bessel_jn(2, 5.0), 0.0465651, 1e-6));
assert!(close(bessel_jn(3, 2.0), 0.128943249, 1e-6));
let v = bessel_jn(10, 5.0);
assert!(v.is_finite());
}
#[test]
fn bessel_jn_negative() {
for &x in &[1.0, 3.0, 5.0] {
for n in [1, 2, 3, 4] {
let jn = bessel_jn(n, x);
let jneg = bessel_jn(-n, x);
let expected = if n % 2 == 1 { -jn } else { jn };
assert!(close(jneg, expected, 1e-10), "n={} x={}: {} vs {}", n, x, jneg, expected);
}
}
}
#[test]
fn bessel_jn_at_zero() {
assert!(close(bessel_jn(5, 0.0), 0.0, 1e-12));
}
#[test]
fn bessel_recurrence() {
for x in [1.0, 3.0, 5.0, 8.0] {
let jnm1 = bessel_jn(1, x);
let jn = bessel_jn(2, x);
let jnp1 = bessel_jn(3, x);
let lhs = jnp1;
let rhs = 2.0 * 2.0 / x * jn - jnm1;
assert!(close(lhs, rhs, 1e-8), "x={}: {} vs {}", x, lhs, rhs);
}
}
}
pub fn digamma(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
if x <= 0.0 && x == x.floor() {
return f64::NAN;
}
let mut x = x;
let mut result = 0.0f64;
if x < 0.0 {
result -= PI / (PI * x).tan();
x = 1.0 - x;
}
while x < 12.0 {
result -= 1.0 / x;
x += 1.0;
}
let inv = 1.0 / x;
let inv2 = inv * inv;
result += x.ln() - 0.5 * inv
- inv2 * (1.0 / 12.0
- inv2 * (1.0 / 120.0
- inv2 * (1.0 / 252.0
- inv2 * (1.0 / 240.0 - inv2 * (1.0 / 132.0)))));
result
}
pub fn trigamma(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
if x <= 0.0 && x == x.floor() {
return f64::NAN;
}
if x < 0.0 {
let sin_term = (PI * x).sin();
return PI * PI / (sin_term * sin_term) - trigamma(1.0 - x);
}
let mut x = x;
let mut result = 0.0f64;
while x < 14.0 {
result += 1.0 / (x * x);
x += 1.0;
}
let inv = 1.0 / x;
let inv2 = inv * inv;
result += inv + 0.5 * inv2
+ inv2 * inv * (1.0 / 6.0
- inv2 * (1.0 / 30.0 - inv2 * (1.0 / 42.0 - inv2 * (1.0 / 30.0))));
result
}
pub fn polygamma(m: u32, x: f64) -> f64 {
match m {
0 => digamma(x),
1 => trigamma(x),
_ => {
let sign: f64 = if (m + 1) % 2 == 0 { 1.0 } else { -1.0 };
let mfact: f64 = (1..=m as u64).product::<u64>() as f64;
sign * mfact * hurwitz_zeta(m as f64 + 1.0, x)
}
}
}
pub fn harmonic(n: u64) -> f64 {
if n == 0 {
return 0.0;
}
if n <= 100 {
return (1..=n).map(|k| 1.0 / k as f64).sum();
}
digamma(n as f64 + 1.0) + std::f64::consts::EULER_GAMMA
}
pub fn hurwitz_zeta(s: f64, a: f64) -> f64 {
if s.is_nan() || a.is_nan() || a <= 0.0 {
return f64::NAN;
}
if s == 1.0 {
return f64::INFINITY;
}
const N: u64 = 12;
let mut sum: f64 = (0..N).map(|k| (a + k as f64).powf(-s)).sum();
let t = a + N as f64;
sum += t.powf(1.0 - s) / (s - 1.0);
sum += 0.5 * t.powf(-s);
const BERN: [f64; 6] = [
1.0 / 12.0,
-1.0 / 720.0,
1.0 / 30240.0,
-1.0 / 1209600.0,
1.0 / 47900160.0,
-691.0 / 1307674368000.0,
];
let mut rising = s;
let mut power = t.powf(-s - 1.0);
for j in 1..=6u64 {
sum += BERN[(j - 1) as usize] * rising * power;
rising *= (s + (2 * j - 1) as f64) * (s + 2.0 * j as f64);
power /= t * t;
}
sum
}
pub fn zeta(s: f64) -> f64 {
if s.is_nan() {
return f64::NAN;
}
if s == 1.0 {
return f64::INFINITY;
}
if s == 0.0 {
return -0.5;
}
if s < 0.0 && s == s.floor() && ((s as i64) % 2 == 0) {
return 0.0;
}
hurwitz_zeta(s, 1.0)
}
pub fn carlson_rf(mut x: f64, mut y: f64, mut z: f64) -> f64 {
if x.min(y).min(z) < 0.0 {
return f64::NAN;
}
const THIRD: f64 = 1.0 / 3.0;
const C1: f64 = 1.0 / 24.0;
const C2: f64 = 0.1;
const C3: f64 = 3.0 / 44.0;
const C4: f64 = 1.0 / 14.0;
for _ in 0..100 {
let sx = x.sqrt();
let sy = y.sqrt();
let sz = z.sqrt();
let alamb = sx * (sy + sz) + sy * sz;
x = 0.25 * (x + alamb);
y = 0.25 * (y + alamb);
z = 0.25 * (z + alamb);
let ave = (x + y + z) * THIRD;
let delx = (ave - x) / ave;
let dely = (ave - y) / ave;
let delz = (ave - z) / ave;
if delx.abs().max(dely.abs()).max(delz.abs()) < 1e-9 {
let e2 = delx * dely - delx * delz - dely * delz;
let e3 = delx * dely * delz;
return (1.0 + (C1 * e2 - C2 - C3 * e3) * e2 + C4 * e3) / ave.sqrt();
}
}
f64::NAN
}
pub fn elliptic_k(k: f64) -> f64 {
let m = k * k;
if m >= 1.0 {
return f64::INFINITY;
}
carlson_rf(0.0, 1.0 - m, 1.0)
}
fn agm_ke(m: f64) -> (f64, f64) {
let kp = (1.0 - m).sqrt();
let mut a = 1.0f64;
let mut b = kp;
let mut sum = 0.5 * m;
let mut pow2 = 1.0f64;
for _ in 0..64 {
let an = 0.5 * (a + b);
let bn = (a * b).sqrt();
let d2 = (an - bn) * (an + bn);
a = an;
b = bn;
if d2 <= 1e-15 * a * a {
break;
}
sum += pow2 * d2;
pow2 *= 2.0;
}
let kk = std::f64::consts::FRAC_PI_2 / a;
(kk, kk * (1.0 - sum))
}
pub fn elliptic_e(k: f64) -> f64 {
let m = k * k;
if m > 1.0 {
return f64::NAN;
}
if m >= 1.0 {
return 1.0;
}
agm_ke(m).1
}
pub fn elliptic_f(phi: f64, k: f64) -> f64 {
let s = phi.sin();
let c = phi.cos();
carlson_rf(c * c, 1.0 - k * k * s * s, 1.0) * s
}
pub fn elliptic_e_inc(phi: f64, k: f64) -> f64 {
let m = k * k;
crate::calculus::integrate_simpson(
|th| (1.0 - m * th.sin().powi(2)).sqrt(),
0.0,
phi,
2048,
)
.unwrap_or(f64::NAN)
}
#[cfg(test)]
mod zeta_gamma_tests {
use super::*;
fn close(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
#[test]
fn digamma_known_values() {
assert!(close(digamma(1.0), -0.5772156649015329, 1e-13));
assert!(close(digamma(0.5), -1.9635100260214235, 1e-13));
assert!(close(digamma(2.0), 0.4227843350984671, 1e-13));
assert!(close(digamma(10.0), 2.2517525890667211, 1e-11));
assert!(close(digamma(-0.5), 0.03648997397857652, 1e-13));
assert!(digamma(0.0).is_nan());
assert!(digamma(-3.0).is_nan());
}
#[test]
fn trigamma_known_values() {
assert!(close(trigamma(1.0), std::f64::consts::PI.powi(2) / 6.0, 1e-12));
assert!(close(trigamma(0.5), std::f64::consts::PI.powi(2) / 2.0, 1e-12));
assert!(close(trigamma(2.0), std::f64::consts::PI.powi(2) / 6.0 - 1.0, 1e-12));
assert!(trigamma(0.0).is_nan());
}
#[test]
fn polygamma_consistency() {
assert!(close(polygamma(0, 1.0), digamma(1.0), 1e-14));
assert!(close(polygamma(1, 1.0), trigamma(1.0), 1e-14));
assert!(close(polygamma(2, 1.0), -2.0 * zeta(3.0), 1e-12));
assert!(close(polygamma(3, 1.0), std::f64::consts::PI.powi(4) / 15.0, 1e-11));
let x = 1.3;
let h = 1e-6;
let d = (digamma(x + h) - digamma(x - h)) / (2.0 * h);
assert!(close(d, trigamma(x), 1e-6));
}
#[test]
fn harmonic_known_values() {
assert_eq!(harmonic(0), 0.0);
assert_eq!(harmonic(1), 1.0);
assert!(close(harmonic(2), 1.5, 1e-15));
assert!(close(harmonic(10), 2.9289682539682538, 1e-14));
assert!(close(harmonic(100), 5.187377517639621, 1e-12));
let n = 50_000u64;
let approx = (n as f64).ln() + std::f64::consts::EULER_GAMMA + 1.0 / (2.0 * n as f64);
assert!(close(harmonic(n), approx, 1e-9));
}
#[test]
fn hurwitz_zeta_reduces_to_riemann() {
assert!(close(hurwitz_zeta(2.0, 1.0), zeta(2.0), 1e-13));
assert!(close(hurwitz_zeta(3.0, 1.0), zeta(3.0), 1e-13));
assert!(close(hurwitz_zeta(2.0, 2.0), zeta(2.0) - 1.0, 1e-12));
assert!(close(hurwitz_zeta(2.0, 0.5), zeta(2.0) * 3.0, 1e-12)); }
#[test]
fn zeta_known_values() {
assert!(close(zeta(2.0), std::f64::consts::PI.powi(2) / 6.0, 1e-13));
assert!(close(zeta(4.0), std::f64::consts::PI.powi(4) / 90.0, 1e-13));
assert!(close(zeta(3.0), 1.2020569031595943, 1e-12));
assert!(close(zeta(0.0), -0.5, 1e-15));
assert!(close(zeta(-1.0), -1.0 / 12.0, 1e-13));
assert!(close(zeta(-3.0), 1.0 / 120.0, 1e-13));
assert_eq!(zeta(-2.0), 0.0);
assert_eq!(zeta(-4.0), 0.0);
assert!(close(zeta(0.5), -1.4603545088095868, 1e-12));
assert_eq!(zeta(1.0), f64::INFINITY);
}
#[test]
fn carlson_rf_known_values() {
assert!(close(carlson_rf(1.0, 1.0, 1.0), 1.0, 1e-15));
assert!(close(carlson_rf(0.0, 1.0, 1.0), std::f64::consts::FRAC_PI_2, 1e-14));
assert!(close(carlson_rf(0.0, 0.5, 1.0), 1.8540746773013719, 1e-13));
}
#[test]
fn elliptic_integrals_known_values() {
use std::f64::consts::FRAC_PI_2;
assert!(close(elliptic_k(0.0), FRAC_PI_2, 1e-14));
assert!(close(elliptic_e(0.0), FRAC_PI_2, 1e-14));
assert!(close(elliptic_k(0.5), 1.6857503548125960, 1e-13));
assert!(close(elliptic_e(0.5), 1.4674622093394272, 1e-13));
assert!(close(elliptic_k(0.9), 2.2805491384227702, 1e-12));
assert!(close(elliptic_e(1.0), 1.0, 1e-15));
assert_eq!(elliptic_k(1.0), f64::INFINITY);
assert!(elliptic_k(0.7) * elliptic_e(0.7) > FRAC_PI_2);
}
#[test]
fn incomplete_elliptic_reduces_to_complete() {
let k = 0.6;
let phi = std::f64::consts::FRAC_PI_2;
assert!(close(elliptic_f(phi, k), elliptic_k(k), 1e-12));
assert!(close(elliptic_e_inc(phi, k), elliptic_e(k), 1e-12));
assert!(close(elliptic_f(0.0, k), 0.0, 1e-15));
assert!(close(elliptic_e_inc(0.0, k), 0.0, 1e-15));
let phi = 0.7;
assert!(close(elliptic_f(phi, 0.0), phi, 1e-14));
assert!(close(elliptic_e_inc(phi, 0.0), phi, 1e-14));
}
#[test]
fn special_functions_via_eval() {
let ctx = crate::eval::Context::standard();
let eval_str = |src: &str, ctx: &crate::eval::Context| -> f64 {
crate::eval::eval(&crate::parser::Parser::parse(src).unwrap(), ctx).unwrap()
};
assert!(close(eval_str("zeta(2)", &ctx), std::f64::consts::PI.powi(2) / 6.0, 1e-12));
assert!(close(eval_str("harmonic(10)", &ctx), 2.9289682539682538, 1e-14));
assert!(close(eval_str("digamma(1) + 0.5772156649015329", &ctx), 0.0, 1e-13));
assert!(close(eval_str("elliptic_k(0.5)", &ctx), 1.6857503548125960, 1e-12));
assert!(close(eval_str("polygamma(2, 1)", &ctx), -2.0 * zeta(3.0), 1e-12));
}
#[test]
fn digamma_large_value() {
let v = digamma(100.0);
let expected = 100.0_f64.ln() - 1.0 / 200.0;
assert!(close(v, expected, 1e-4), "digamma(100) ≈ {}: got {}", expected, v);
}
#[test]
fn digamma_fractional_negative() {
let v = digamma(-1.5);
assert!(v.is_finite(), "digamma(-1.5) should be finite: {}", v);
}
#[test]
fn trigamma_negative_reflection() {
let v = trigamma(-0.5);
assert!(v.is_finite(), "trigamma(-0.5) should be finite: {}", v);
let expected = std::f64::consts::PI.powi(2) / 2.0 + 4.0;
assert!(close(v, expected, 1e-6), "trigamma(-0.5) ≈ {}: got {}", expected, v);
}
#[test]
fn trigamma_large_value() {
let v = trigamma(100.0);
let expected = 1.0 / 100.0 + 1.0 / (2.0 * 100.0 * 100.0);
assert!(close(v, expected, 1e-4), "trigamma(100) ≈ {}: got {}", expected, v);
}
#[test]
fn polygamma_recurrence_identity() {
let m: u32 = 1;
let x = 2.0;
let lhs = polygamma(m, x + 1.0);
let mi = m as i32;
let rhs = polygamma(m, x) + (-1.0_f64).powi(mi) * (1.0_f64).powi(mi) / x.powi(mi + 1);
assert!(close(lhs, rhs, 1e-10), "polygamma recurrence: {} vs {}", lhs, rhs);
}
#[test]
fn harmonic_consistency_with_digamma() {
for n in [1, 5, 10, 50, 100, 500] {
let h = harmonic(n);
let psi = digamma((n + 1) as f64) + 0.5772156649015329;
assert!(close(h, psi, 1e-6), "H_{} = {} but ψ({})+γ = {}", n, h, n + 1, psi);
}
}
#[test]
fn zeta_negative_odd() {
let v = zeta(-5.0);
assert!(close(v, -1.0 / 252.0, 1e-6), "zeta(-5) = {}: got {}", -1.0 / 252.0, v);
let v2 = zeta(-7.0);
assert!(close(v2, 1.0 / 240.0, 1e-6), "zeta(-7) = {}: got {}", 1.0 / 240.0, v2);
}
#[test]
fn zeta_even_positive() {
let v = zeta(6.0);
let expected = std::f64::consts::PI.powi(6) / 945.0;
assert!(close(v, expected, 1e-10), "zeta(6) = {}: got {}", expected, v);
let v2 = zeta(8.0);
let expected2 = std::f64::consts::PI.powi(8) / 9450.0;
assert!(close(v2, expected2, 1e-10), "zeta(8) = {}: got {}", expected2, v2);
}
#[test]
fn hurwitz_zeta_non_integer_a() {
let v = hurwitz_zeta(2.0, 0.5);
let expected = std::f64::consts::PI.powi(2) / 2.0;
assert!(close(v, expected, 1e-6), "hurwitz_zeta(2, 0.5) = {}: got {}", expected, v);
}
#[test]
fn hurwitz_zeta_s_equals_one_errors() {
let v = hurwitz_zeta(1.0, 1.0);
assert!(v.is_nan() || v.is_infinite(), "hurwitz_zeta(1, 1) should be NaN/inf: {}", v);
}
#[test]
fn carlson_rf_symmetry() {
let a = carlson_rf(1.0, 2.0, 3.0);
let b = carlson_rf(3.0, 2.0, 1.0);
let c = carlson_rf(2.0, 3.0, 1.0);
assert!(close(a, b, 1e-12), "RF symmetry: {} vs {}", a, b);
assert!(close(a, c, 1e-12), "RF symmetry: {} vs {}", a, c);
}
#[test]
fn carlson_rf_with_one_zero() {
let v = carlson_rf(0.0, 1.0, 1.0);
let expected = std::f64::consts::FRAC_PI_2;
assert!(close(v, expected, 1e-10), "RF(0,1,1) = {}: got {}", expected, v);
}
#[test]
fn elliptic_k_at_zero() {
let v = elliptic_k(0.0);
assert!(close(v, std::f64::consts::FRAC_PI_2, 1e-12), "K(0) = π/2: got {}", v);
}
#[test]
fn elliptic_e_at_zero() {
let v = elliptic_e(0.0);
assert!(close(v, std::f64::consts::FRAC_PI_2, 1e-12), "E(0) = π/2: got {}", v);
}
#[test]
fn elliptic_e_at_one() {
let v = elliptic_e(1.0);
assert!(close(v, 1.0, 1e-6), "E(1) = 1: got {}", v);
}
#[test]
fn elliptic_f_at_zero_modulus() {
let v = elliptic_f(std::f64::consts::FRAC_PI_4, 0.0);
assert!(close(v, std::f64::consts::FRAC_PI_4, 1e-12), "F(π/4, 0) = π/4: got {}", v);
}
#[test]
fn elliptic_e_inc_at_zero_modulus() {
let v = elliptic_e_inc(std::f64::consts::FRAC_PI_4, 0.0);
assert!(close(v, std::f64::consts::FRAC_PI_4, 1e-6), "E_inc(π/4, 0) = π/4: got {}", v);
}
#[test]
fn elliptic_e_inc_at_quarter_pi() {
let v = elliptic_e_inc(std::f64::consts::FRAC_PI_2, 0.5);
let expected = elliptic_e(0.5);
assert!(close(v, expected, 1e-4), "E_inc(π/2, 0.5) = E(0.5) = {}: got {}", expected, v);
}
#[test]
fn special_functions_via_eval_extended() {
let ctx = crate::eval::Context::standard();
let eval_str = |src: &str, ctx: &crate::eval::Context| -> f64 {
crate::eval::eval(&crate::parser::Parser::parse(src).unwrap(), ctx).unwrap()
};
assert!(close(eval_str("trigamma(1)", &ctx), std::f64::consts::PI.powi(2) / 6.0, 1e-10));
assert!(close(eval_str("elliptic_e(0)", &ctx), std::f64::consts::FRAC_PI_2, 1e-10));
assert!(close(eval_str("elliptic_f(0, 0.5)", &ctx), 0.0, 1e-10));
}
#[test]
fn bessel_jn_recurrence_large_n() {
assert!(close(bessel_jn(10, 0.0), 0.0, 1e-15));
assert!(close(bessel_jn(0, 0.0), 1.0, 1e-15));
}
#[test]
fn gamma_reflection_negative() {
let g = gamma(0.5);
assert!(close(g * g, std::f64::consts::PI, 1e-10), "Γ(0.5)² = π: got {}", g * g);
}
#[test]
fn beta_symmetry() {
let a = beta(2.0, 3.0);
let b = beta(3.0, 2.0);
assert!(close(a, b, 1e-12), "B(2,3) = B(3,2): {} vs {}", a, b);
}
#[test]
fn erf_at_inf() {
assert!(close(erf(50.0), 1.0, 1e-15), "erf(50) ≈ 1: got {}", erf(50.0));
assert!(close(erf(-50.0), -1.0, 1e-15), "erf(-50) ≈ -1: got {}", erf(-50.0));
}
#[test]
fn erfc_at_inf() {
assert!(close(erfc(50.0), 0.0, 1e-15), "erfc(50) ≈ 0: got {}", erfc(50.0));
assert!(close(erfc(-50.0), 2.0, 1e-15), "erfc(-50) ≈ 2: got {}", erfc(-50.0));
}
#[test]
fn incomplete_gamma_chi2_round_trip() {
for &x in &[0.5, 1.0, 2.0, 5.0] {
let p = incomplete_gamma_p(1.0, x);
let expected = 1.0 - (-x).exp();
assert!(close(p, expected, 1e-10), "P(1, {}) = {}: got {}", x, expected, p);
}
}
}