mathhook_core/functions/special/
zeta.rs1use crate::core::{Expression, Number};
24use crate::functions::special::gamma::lanczos_gamma;
25use std::f64::consts::PI;
26
27pub fn zeta(s: &Expression) -> Expression {
61 match s {
62 Expression::Number(Number::Integer(n)) => zeta_integer(*n),
63 Expression::Number(Number::Float(val)) => {
64 if (*val - val.round()).abs() < 1e-10 {
65 zeta_integer(val.round() as i64)
66 } else {
67 let result = zeta_numerical(*val);
68 Expression::Number(Number::Float(result))
69 }
70 }
71 _ => Expression::function("zeta", vec![s.clone()]),
72 }
73}
74
75fn zeta_integer(n: i64) -> Expression {
80 match n {
81 0 => Expression::rational(-1, 2),
83
84 1 => Expression::function("zeta", vec![Expression::integer(1)]),
86
87 2 => Expression::div(
89 Expression::pow(Expression::pi(), Expression::integer(2)),
90 Expression::integer(6),
91 ),
92
93 4 => Expression::div(
95 Expression::pow(Expression::pi(), Expression::integer(4)),
96 Expression::integer(90),
97 ),
98
99 6 => Expression::div(
101 Expression::pow(Expression::pi(), Expression::integer(6)),
102 Expression::integer(945),
103 ),
104
105 8 => Expression::div(
107 Expression::pow(Expression::pi(), Expression::integer(8)),
108 Expression::integer(9450),
109 ),
110
111 10 => Expression::div(
113 Expression::pow(Expression::pi(), Expression::integer(10)),
114 Expression::integer(93555),
115 ),
116
117 -1 => Expression::rational(-1, 12),
119
120 n if n < 0 && n % 2 == 0 => Expression::integer(0),
122
123 -3 => Expression::rational(1, 120),
125
126 -5 => Expression::rational(-1, 252),
128
129 -7 => Expression::rational(1, 240),
131
132 n if n > 0 && n % 2 == 0 => {
134 let result = zeta_numerical(n as f64);
135 Expression::Number(Number::Float(result))
136 }
137
138 n if n < 0 && n % 2 != 0 => {
140 let result = zeta_numerical(n as f64);
141 Expression::Number(Number::Float(result))
142 }
143
144 n => {
146 let result = zeta_numerical(n as f64);
147 Expression::Number(Number::Float(result))
148 }
149 }
150}
151
152pub fn zeta_numerical(s: f64) -> f64 {
167 if s.is_nan() || s.is_infinite() {
169 return f64::NAN;
170 }
171
172 if (s - 1.0).abs() < 1e-10 {
174 return f64::INFINITY;
175 }
176
177 if s.abs() < 1e-10 {
178 return -0.5;
179 }
180
181 if s > 1.5 {
183 zeta_euler_maclaurin(s)
184 } else if s < 0.0 {
185 zeta_functional_equation(s)
186 } else {
187 zeta_series_eta(s)
188 }
189}
190
191fn zeta_euler_maclaurin(s: f64) -> f64 {
202 const N: usize = 50; let mut sum = 0.0;
206 for n in 1..=N {
207 sum += 1.0 / (n as f64).powf(s);
208 }
209
210 let n = N as f64;
211
212 let integral = n.powf(1.0 - s) / (s - 1.0);
214
215 let correction1 = 1.0 / (2.0 * n.powf(s));
217 let correction2 = s / (12.0 * n.powf(s + 1.0));
218 let correction3 = -s * (s + 1.0) * (s + 2.0) / (720.0 * n.powf(s + 3.0));
219
220 sum + integral + correction1 + correction2 + correction3
221}
222
223fn zeta_series_eta(s: f64) -> f64 {
231 const N_TERMS: usize = 10000; const EPSILON: f64 = 1e-14; let mut eta = 0.0;
235 let mut prev_eta = 0.0;
236
237 for n in 1..=N_TERMS {
238 let sign = if n % 2 == 1 { 1.0 } else { -1.0 };
239 eta += sign / (n as f64).powf(s);
240
241 if n > 100 && (eta - prev_eta).abs() < EPSILON * eta.abs() {
243 break;
244 }
245
246 prev_eta = eta;
247 }
248
249 let factor = 1.0 - 2.0_f64.powf(1.0 - s);
250 eta / factor
251}
252
253fn zeta_functional_equation(s: f64) -> f64 {
261 let one_minus_s = 1.0 - s;
262 let zeta_reflected = zeta_numerical(one_minus_s);
263
264 let factor1 = 2.0_f64.powf(s);
265 let factor2 = PI.powf(s - 1.0);
266 let factor3 = (PI * s / 2.0).sin();
267 let factor4 = lanczos_gamma(one_minus_s);
268
269 factor1 * factor2 * factor3 * factor4 * zeta_reflected
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn test_zeta_2_basel_problem() {
278 let result = zeta(&Expression::integer(2));
279
280 match result {
283 Expression::Number(_) => panic!("Expected symbolic expression for ζ(2)"),
284 _ => {
285 }
287 }
288
289 let numerical = zeta_numerical(2.0);
291 let pi_squared_over_6 = PI * PI / 6.0;
292 assert!(
293 (numerical - pi_squared_over_6).abs() < 1e-3,
294 "ζ(2) numerically = {}, expected π²/6 ≈ {}",
295 numerical,
296 pi_squared_over_6
297 );
298 }
299
300 #[test]
301 fn test_zeta_0_exact() {
302 let result = zeta(&Expression::integer(0));
303 let expected = Expression::rational(-1, 2);
304 assert_eq!(result, expected, "ζ(0) should equal -1/2");
305 }
306
307 #[test]
308 fn test_zeta_negative_1() {
309 let result = zeta(&Expression::integer(-1));
310 let expected = Expression::rational(-1, 12);
311 assert_eq!(result, expected, "ζ(-1) should equal -1/12");
312 }
313
314 #[test]
315 fn test_zeta_4_exact() {
316 let result = zeta(&Expression::integer(4));
317
318 match result {
320 Expression::Number(_) => panic!("Expected symbolic expression for ζ(4)"),
321 _ => {
322 }
324 }
325
326 let numerical = zeta_numerical(4.0);
328 let pi_fourth_over_90 = PI.powi(4) / 90.0;
329 assert!(
330 (numerical - pi_fourth_over_90).abs() < 1e-6,
331 "ζ(4) numerically = {}, expected π⁴/90 ≈ {}",
332 numerical,
333 pi_fourth_over_90
334 );
335 }
336
337 #[test]
338 fn test_zeta_numerical_convergence() {
339 let val = zeta_numerical(3.0);
340 let expected = 1.202064903159592;
341 assert!(
342 (val - expected).abs() < 1e-6,
343 "ζ(3) = {}, expected ≈ {}",
344 val,
345 expected
346 );
347 }
348
349 #[test]
350 fn test_zeta_pole_at_1() {
351 let result = zeta(&Expression::integer(1));
352
353 match result {
354 Expression::Function { name, args } => {
355 assert_eq!(name, "zeta");
356 assert_eq!(args.len(), 1);
357 }
358 _ => panic!("ζ(1) should remain symbolic (pole)"),
359 }
360 }
361
362 #[test]
363 fn test_zeta_negative_odd_integers() {
364 let result = zeta(&Expression::integer(-3));
365 let expected = Expression::rational(1, 120);
366 assert_eq!(result, expected, "ζ(-3) should equal 1/120");
367 }
368
369 #[test]
370 fn test_zeta_trivial_zeros() {
371 for n in 1..=10 {
373 let result = zeta(&Expression::integer(-2 * n));
374 assert_eq!(
375 result,
376 Expression::integer(0),
377 "ζ({}) should be 0 (trivial zero)",
378 -2 * n
379 );
380 }
381 }
382
383 #[test]
384 fn test_zeta_symbolic_fallback() {
385 let s = Expression::symbol(crate::core::Symbol::scalar("s"));
386 let result = zeta(&s);
387
388 match result {
389 Expression::Function { name, args } => {
390 assert_eq!(name, "zeta");
391 assert_eq!(args.len(), 1);
392 }
393 _ => panic!("Expected symbolic function for variable input"),
394 }
395 }
396
397 #[test]
398 fn test_zeta_float_rounding() {
399 let result = zeta(&Expression::Number(Number::Float(2.0)));
400
401 match result {
403 Expression::Number(_) => {
404 panic!("ζ(2.0) should be treated as ζ(2) and return symbolic")
405 }
406 _ => {
407 }
409 }
410 }
411
412 #[test]
413 fn test_zeta_large_argument() {
414 let val = zeta_numerical(10.0);
415 let expected = 1.000994575127818;
416 assert!(
417 (val - expected).abs() < 1e-9,
418 "ζ(10) = {}, expected ≈ {}",
419 val,
420 expected
421 );
422 }
423
424 #[test]
425 fn test_zeta_8_exact() {
426 let result = zeta(&Expression::integer(8));
427
428 match result {
430 Expression::Number(_) => panic!("Expected symbolic expression for ζ(8)"),
431 _ => {
432 }
434 }
435 }
436
437 #[test]
438 fn test_zeta_10_exact() {
439 let result = zeta(&Expression::integer(10));
440
441 match result {
443 Expression::Number(_) => panic!("Expected symbolic expression for ζ(10)"),
444 _ => {
445 }
447 }
448 }
449
450 #[test]
451 fn test_zeta_negative_5() {
452 let result = zeta(&Expression::integer(-5));
453 let expected = Expression::rational(-1, 252);
454 assert_eq!(result, expected, "ζ(-5) should equal -1/252");
455 }
456
457 #[test]
458 fn test_zeta_negative_7() {
459 let result = zeta(&Expression::integer(-7));
460 let expected = Expression::rational(1, 240);
461 assert_eq!(result, expected, "ζ(-7) should equal 1/240");
462 }
463}