use crate::core::{Expression, Number};
use crate::functions::special::gamma::lanczos_gamma;
use std::f64::consts::PI;
pub fn zeta(s: &Expression) -> Expression {
match s {
Expression::Number(Number::Integer(n)) => zeta_integer(*n),
Expression::Number(Number::Float(val)) => {
if (*val - val.round()).abs() < 1e-10 {
zeta_integer(val.round() as i64)
} else {
let result = zeta_numerical(*val);
Expression::Number(Number::Float(result))
}
}
_ => Expression::function("zeta", vec![s.clone()]),
}
}
fn zeta_integer(n: i64) -> Expression {
match n {
0 => Expression::rational(-1, 2),
1 => Expression::function("zeta", vec![Expression::integer(1)]),
2 => Expression::div(
Expression::pow(Expression::pi(), Expression::integer(2)),
Expression::integer(6),
),
4 => Expression::div(
Expression::pow(Expression::pi(), Expression::integer(4)),
Expression::integer(90),
),
6 => Expression::div(
Expression::pow(Expression::pi(), Expression::integer(6)),
Expression::integer(945),
),
8 => Expression::div(
Expression::pow(Expression::pi(), Expression::integer(8)),
Expression::integer(9450),
),
10 => Expression::div(
Expression::pow(Expression::pi(), Expression::integer(10)),
Expression::integer(93555),
),
-1 => Expression::rational(-1, 12),
n if n < 0 && n % 2 == 0 => Expression::integer(0),
-3 => Expression::rational(1, 120),
-5 => Expression::rational(-1, 252),
-7 => Expression::rational(1, 240),
n if n > 0 && n % 2 == 0 => {
let result = zeta_numerical(n as f64);
Expression::Number(Number::Float(result))
}
n if n < 0 && n % 2 != 0 => {
let result = zeta_numerical(n as f64);
Expression::Number(Number::Float(result))
}
n => {
let result = zeta_numerical(n as f64);
Expression::Number(Number::Float(result))
}
}
}
pub fn zeta_numerical(s: f64) -> f64 {
if s.is_nan() || s.is_infinite() {
return f64::NAN;
}
if (s - 1.0).abs() < 1e-10 {
return f64::INFINITY;
}
if s.abs() < 1e-10 {
return -0.5;
}
if s > 1.5 {
zeta_euler_maclaurin(s)
} else if s < 0.0 {
zeta_functional_equation(s)
} else {
zeta_series_eta(s)
}
}
fn zeta_euler_maclaurin(s: f64) -> f64 {
const N: usize = 50;
let mut sum = 0.0;
for n in 1..=N {
sum += 1.0 / (n as f64).powf(s);
}
let n = N as f64;
let integral = n.powf(1.0 - s) / (s - 1.0);
let correction1 = 1.0 / (2.0 * n.powf(s));
let correction2 = s / (12.0 * n.powf(s + 1.0));
let correction3 = -s * (s + 1.0) * (s + 2.0) / (720.0 * n.powf(s + 3.0));
sum + integral + correction1 + correction2 + correction3
}
fn zeta_series_eta(s: f64) -> f64 {
const N_TERMS: usize = 10000; const EPSILON: f64 = 1e-14;
let mut eta = 0.0;
let mut prev_eta = 0.0;
for n in 1..=N_TERMS {
let sign = if n % 2 == 1 { 1.0 } else { -1.0 };
eta += sign / (n as f64).powf(s);
if n > 100 && (eta - prev_eta).abs() < EPSILON * eta.abs() {
break;
}
prev_eta = eta;
}
let factor = 1.0 - 2.0_f64.powf(1.0 - s);
eta / factor
}
fn zeta_functional_equation(s: f64) -> f64 {
let one_minus_s = 1.0 - s;
let zeta_reflected = zeta_numerical(one_minus_s);
let factor1 = 2.0_f64.powf(s);
let factor2 = PI.powf(s - 1.0);
let factor3 = (PI * s / 2.0).sin();
let factor4 = lanczos_gamma(one_minus_s);
factor1 * factor2 * factor3 * factor4 * zeta_reflected
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zeta_2_basel_problem() {
let result = zeta(&Expression::integer(2));
match result {
Expression::Number(_) => panic!("Expected symbolic expression for ζ(2)"),
_ => {
}
}
let numerical = zeta_numerical(2.0);
let pi_squared_over_6 = PI * PI / 6.0;
assert!(
(numerical - pi_squared_over_6).abs() < 1e-3,
"ζ(2) numerically = {}, expected π²/6 ≈ {}",
numerical,
pi_squared_over_6
);
}
#[test]
fn test_zeta_0_exact() {
let result = zeta(&Expression::integer(0));
let expected = Expression::rational(-1, 2);
assert_eq!(result, expected, "ζ(0) should equal -1/2");
}
#[test]
fn test_zeta_negative_1() {
let result = zeta(&Expression::integer(-1));
let expected = Expression::rational(-1, 12);
assert_eq!(result, expected, "ζ(-1) should equal -1/12");
}
#[test]
fn test_zeta_4_exact() {
let result = zeta(&Expression::integer(4));
match result {
Expression::Number(_) => panic!("Expected symbolic expression for ζ(4)"),
_ => {
}
}
let numerical = zeta_numerical(4.0);
let pi_fourth_over_90 = PI.powi(4) / 90.0;
assert!(
(numerical - pi_fourth_over_90).abs() < 1e-6,
"ζ(4) numerically = {}, expected π⁴/90 ≈ {}",
numerical,
pi_fourth_over_90
);
}
#[test]
fn test_zeta_numerical_convergence() {
let val = zeta_numerical(3.0);
let expected = 1.202064903159592;
assert!(
(val - expected).abs() < 1e-6,
"ζ(3) = {}, expected ≈ {}",
val,
expected
);
}
#[test]
fn test_zeta_pole_at_1() {
let result = zeta(&Expression::integer(1));
match result {
Expression::Function { name, args } => {
assert_eq!(name.as_ref(), "zeta");
assert_eq!(args.len(), 1);
}
_ => panic!("ζ(1) should remain symbolic (pole)"),
}
}
#[test]
fn test_zeta_negative_odd_integers() {
let result = zeta(&Expression::integer(-3));
let expected = Expression::rational(1, 120);
assert_eq!(result, expected, "ζ(-3) should equal 1/120");
}
#[test]
fn test_zeta_trivial_zeros() {
for n in 1..=10 {
let result = zeta(&Expression::integer(-2 * n));
assert_eq!(
result,
Expression::integer(0),
"ζ({}) should be 0 (trivial zero)",
-2 * n
);
}
}
#[test]
fn test_zeta_symbolic_fallback() {
let s = Expression::symbol(crate::core::Symbol::scalar("s"));
let result = zeta(&s);
match result {
Expression::Function { name, args } => {
assert_eq!(name.as_ref(), "zeta");
assert_eq!(args.len(), 1);
}
_ => panic!("Expected symbolic function for variable input"),
}
}
#[test]
fn test_zeta_float_rounding() {
let result = zeta(&Expression::Number(Number::Float(2.0)));
match result {
Expression::Number(_) => {
panic!("ζ(2.0) should be treated as ζ(2) and return symbolic")
}
_ => {
}
}
}
#[test]
fn test_zeta_large_argument() {
let val = zeta_numerical(10.0);
let expected = 1.000994575127818;
assert!(
(val - expected).abs() < 1e-9,
"ζ(10) = {}, expected ≈ {}",
val,
expected
);
}
#[test]
fn test_zeta_8_exact() {
let result = zeta(&Expression::integer(8));
match result {
Expression::Number(_) => panic!("Expected symbolic expression for ζ(8)"),
_ => {
}
}
}
#[test]
fn test_zeta_10_exact() {
let result = zeta(&Expression::integer(10));
match result {
Expression::Number(_) => panic!("Expected symbolic expression for ζ(10)"),
_ => {
}
}
}
#[test]
fn test_zeta_negative_5() {
let result = zeta(&Expression::integer(-5));
let expected = Expression::rational(-1, 252);
assert_eq!(result, expected, "ζ(-5) should equal -1/252");
}
#[test]
fn test_zeta_negative_7() {
let result = zeta(&Expression::integer(-7));
let expected = Expression::rational(1, 240);
assert_eq!(result, expected, "ζ(-7) should equal 1/240");
}
}