use crate::kernel::{Domain, ExprData, ExprId, ExprPool};
use rug::{Integer, Rational};
const MAX_RECOGNIZED_POWER: i64 = 60;
fn pascal_rows(n: usize) -> Vec<Vec<Integer>> {
let mut rows: Vec<Vec<Integer>> = Vec::with_capacity(n + 1);
rows.push(vec![Integer::from(1)]);
for i in 1..=n {
let prev = &rows[i - 1];
let mut row = Vec::with_capacity(i + 1);
row.push(Integer::from(1));
for j in 1..i {
row.push(prev[j - 1].clone() + prev[j].clone());
}
row.push(Integer::from(1));
rows.push(row);
}
rows
}
fn bernoulli_numbers(up_to: usize) -> Vec<Rational> {
let rows = pascal_rows(up_to + 1);
let mut b: Vec<Rational> = Vec::with_capacity(up_to + 1);
b.push(Rational::from(1));
for m in 1..=up_to {
let row = &rows[m + 1];
let mut sum = Rational::from(0);
for (j, bj) in b.iter().enumerate().take(m) {
sum += Rational::from(row[j].clone()) * bj.clone();
}
b.push(-sum / Rational::from(m as i64 + 1));
}
b
}
fn factorial(n: u64) -> Integer {
let mut r = Integer::from(1);
for i in 2..=n {
r *= i;
}
r
}
fn zeta_even_coefficient(m: u32) -> Rational {
let b = bernoulli_numbers(2 * m as usize);
let b_2m = b[2 * m as usize].clone();
let sign: i64 = if m % 2 == 1 { 1 } else { -1 }; let two_pow_2m = Integer::from(1) << (2 * m); let denom = Integer::from(2) * factorial(2 * m as u64);
Rational::from(sign) * b_2m * Rational::from((two_pow_2m, denom))
}
fn depends_on(expr: ExprId, k: ExprId, pool: &ExprPool) -> bool {
if expr == k {
return true;
}
match pool.get(expr) {
ExprData::Add(xs) | ExprData::Mul(xs) => xs.iter().any(|&a| depends_on(a, k, pool)),
ExprData::Pow { base, exp } => depends_on(base, k, pool) || depends_on(exp, k, pool),
ExprData::Func { args, .. } => args.iter().any(|&a| depends_on(a, k, pool)),
_ => false,
}
}
fn negative_integer_exponent(exp: ExprId, pool: &ExprPool) -> Option<i64> {
if let ExprData::Integer(n) = pool.get(exp) {
let v = n.0.to_i64()?;
if v < 0 {
return Some(-v);
}
}
None
}
fn match_p_series_term(term: ExprId, k: ExprId, pool: &ExprPool) -> Option<(ExprId, i64)> {
match pool.get(term) {
ExprData::Pow { base, exp } if base == k => {
let p = negative_integer_exponent(exp, pool)?;
Some((pool.integer(1_i32), p))
}
ExprData::Mul(args) => {
let mut coeff_factors = Vec::new();
let mut p_found: Option<i64> = None;
for a in args {
if p_found.is_none() {
if let ExprData::Pow { base, exp } = pool.get(a) {
if base == k {
if let Some(p) = negative_integer_exponent(exp, pool) {
p_found = Some(p);
continue;
}
}
}
}
if depends_on(a, k, pool) {
return None;
}
coeff_factors.push(a);
}
let p = p_found?;
let coeff = match coeff_factors.len() {
0 => pool.integer(1_i32),
1 => coeff_factors[0],
_ => pool.mul(coeff_factors),
};
Some((coeff, p))
}
_ => None,
}
}
pub(super) fn basel_family_closed_form(
term: ExprId,
k: ExprId,
lo: ExprId,
hi: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
if hi != pool.pos_infinity() {
return None;
}
if !matches!(pool.get(lo), ExprData::Integer(n) if n.0 == 1) {
return None;
}
let (term_coeff, p) = match_p_series_term(term, k, pool)?;
if p <= 0 || p % 2 != 0 || p > MAX_RECOGNIZED_POWER {
return None;
}
let m = (p / 2) as u32;
let zeta_coeff = zeta_even_coefficient(m);
let pi = pool.symbol("pi", Domain::Real);
let pi_pow = pool.pow(pi, pool.integer(p));
let (num, den) = (zeta_coeff.numer().clone(), zeta_coeff.denom().clone());
let zeta_expr = if den == 1 {
pool.mul(vec![pool.integer(num), pi_pow])
} else {
pool.mul(vec![pool.rational(num, den), pi_pow])
};
Some(pool.mul(vec![term_coeff, zeta_expr]))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jit::eval_interp;
use crate::simplify::engine::simplify;
use std::collections::HashMap;
fn simp(pool: &ExprPool, e: ExprId) -> ExprId {
simplify(e, pool).value
}
#[test]
fn zeta_two_is_pi_squared_over_six() {
let c = zeta_even_coefficient(1);
assert_eq!(c, Rational::from((1, 6)));
}
#[test]
fn zeta_four_is_pi_fourth_over_ninety() {
let c = zeta_even_coefficient(2);
assert_eq!(c, Rational::from((1, 90)));
}
#[test]
fn zeta_six_is_pi_sixth_over_945() {
let c = zeta_even_coefficient(3);
assert_eq!(c, Rational::from((1, 945)));
}
#[test]
fn basel_sum_matches_pi_squared_over_six_numerically() {
let pool = ExprPool::new();
let k = pool.symbol("k", Domain::Positive);
let term = simp(&pool, pool.pow(k, pool.integer(-2_i32)));
let lo = pool.integer(1_i32);
let hi = pool.pos_infinity();
let value = basel_family_closed_form(term, k, lo, hi, &pool).expect("Basel sum");
let pi = pool.symbol("pi", Domain::Real);
let mut env = HashMap::new();
env.insert(pi, std::f64::consts::PI);
let got = eval_interp(value, &env, &pool).expect("eval");
let want = std::f64::consts::PI.powi(2) / 6.0;
assert!((got - want).abs() < 1e-9, "got {got} want {want}");
}
#[test]
fn sum_one_over_n_fourth_matches_pi_fourth_over_ninety() {
let pool = ExprPool::new();
let k = pool.symbol("k", Domain::Positive);
let term = simp(&pool, pool.pow(k, pool.integer(-4_i32)));
let lo = pool.integer(1_i32);
let hi = pool.pos_infinity();
let value = basel_family_closed_form(term, k, lo, hi, &pool).expect("zeta(4)");
let pi = pool.symbol("pi", Domain::Real);
let mut env = HashMap::new();
env.insert(pi, std::f64::consts::PI);
let got = eval_interp(value, &env, &pool).expect("eval");
let want = std::f64::consts::PI.powi(4) / 90.0;
assert!((got - want).abs() < 1e-9, "got {got} want {want}");
}
#[test]
fn scaled_basel_sum_carries_the_coefficient() {
let pool = ExprPool::new();
let k = pool.symbol("k", Domain::Positive);
let term = simp(
&pool,
pool.mul(vec![pool.integer(3_i32), pool.pow(k, pool.integer(-2_i32))]),
);
let lo = pool.integer(1_i32);
let hi = pool.pos_infinity();
let value = basel_family_closed_form(term, k, lo, hi, &pool).expect("3·zeta(2)");
let pi = pool.symbol("pi", Domain::Real);
let mut env = HashMap::new();
env.insert(pi, std::f64::consts::PI);
let got = eval_interp(value, &env, &pool).expect("eval");
let want = 3.0 * std::f64::consts::PI.powi(2) / 6.0;
assert!((got - want).abs() < 1e-9, "got {got} want {want}");
}
#[test]
fn odd_power_is_not_recognized() {
let pool = ExprPool::new();
let k = pool.symbol("k", Domain::Positive);
let term = simp(&pool, pool.pow(k, pool.integer(-3_i32)));
let lo = pool.integer(1_i32);
let hi = pool.pos_infinity();
assert!(basel_family_closed_form(term, k, lo, hi, &pool).is_none());
}
#[test]
fn finite_upper_bound_is_not_recognized() {
let pool = ExprPool::new();
let k = pool.symbol("k", Domain::Positive);
let term = simp(&pool, pool.pow(k, pool.integer(-2_i32)));
let lo = pool.integer(1_i32);
let hi = pool.integer(100_i32);
assert!(basel_family_closed_form(term, k, lo, hi, &pool).is_none());
}
#[test]
fn non_unit_lower_bound_is_not_recognized() {
let pool = ExprPool::new();
let k = pool.symbol("k", Domain::Positive);
let term = simp(&pool, pool.pow(k, pool.integer(-2_i32)));
let lo = pool.integer(2_i32);
let hi = pool.pos_infinity();
assert!(basel_family_closed_form(term, k, lo, hi, &pool).is_none());
}
#[test]
fn divergent_positive_power_is_not_recognized() {
let pool = ExprPool::new();
let k = pool.symbol("k", Domain::Positive);
let term = simp(&pool, pool.pow(k, pool.integer(2_i32)));
let lo = pool.integer(1_i32);
let hi = pool.pos_infinity();
assert!(basel_family_closed_form(term, k, lo, hi, &pool).is_none());
}
}