use crate::error::{SpecialError, SpecialResult};
use scirs2_core::numeric::Complex64;
use std::f64::consts::PI;
const MAX_TERMS: usize = 1000;
const TOL: f64 = 1e-15;
pub fn polylogarithm(s: f64, z: Complex64) -> SpecialResult<Complex64> {
if s.is_nan() || !s.is_finite() {
return Err(SpecialError::DomainError(format!(
"polylogarithm: s must be finite, got s = {s}"
)));
}
if !z.re.is_finite() || !z.im.is_finite() {
return Err(SpecialError::DomainError(
"polylogarithm: z must be finite".to_string(),
));
}
if z.norm() < 1e-300 {
return Ok(Complex64::new(0.0, 0.0));
}
if s.abs() < 1e-14 {
let one_minus_z = Complex64::new(1.0 - z.re, -z.im);
if one_minus_z.norm() < 1e-300 {
return Err(SpecialError::DomainError(
"polylogarithm: z = 1 is a pole for s = 0".to_string(),
));
}
return Ok(z / one_minus_z);
}
if (s - 1.0).abs() < 1e-14 {
let one_minus_z = Complex64::new(1.0 - z.re, -z.im);
if one_minus_z.norm() < 1e-300 {
return Err(SpecialError::DomainError(
"polylogarithm: z = 1 is a pole for s = 1".to_string(),
));
}
return Ok(-complex_ln(one_minus_z));
}
if s <= 0.0 && (s - s.round()).abs() < 1e-12 && s >= -20.0 {
let n = (-s.round()) as usize; return polylog_neg_int(n, z);
}
if (z - Complex64::new(1.0, 0.0)).norm() < 1e-14 {
if s <= 1.0 {
return Err(SpecialError::DomainError(format!(
"polylogarithm: z = 1 is a pole for s = {s} ≤ 1"
)));
}
return Ok(Complex64::new(riemann_zeta_approx(s), 0.0));
}
if (z - Complex64::new(-1.0, 0.0)).norm() < 1e-14 && s > 0.0 {
let zeta_s = riemann_zeta_approx(s);
let eta_s = (1.0 - (2.0_f64).powf(1.0 - s)) * zeta_s;
return Ok(Complex64::new(-eta_s, 0.0));
}
if z.norm() <= 0.7 {
return polylog_series(s, z);
}
if z.norm() >= 1.43 {
return polylog_large_z(s, z);
}
polylog_euler_transform(s, z)
}
#[inline]
pub fn polylog(s: f64, z: Complex64) -> SpecialResult<Complex64> {
polylogarithm(s, z)
}
pub fn dilogarithm(z: Complex64) -> SpecialResult<Complex64> {
polylogarithm(2.0, z)
}
pub fn lerch_phi(z: f64, s: f64, a: f64) -> SpecialResult<f64> {
if a <= 0.0 {
return Err(SpecialError::DomainError(format!(
"lerch_phi: a must be positive, got a = {a}"
)));
}
if !s.is_finite() {
return Err(SpecialError::DomainError(format!(
"lerch_phi: s must be finite, got s = {s}"
)));
}
if z.abs() > 1.0 + 1e-12 {
return Err(SpecialError::DomainError(format!(
"lerch_phi: |z| = {} > 1 gives a divergent series",
z.abs()
)));
}
if (z.abs() - 1.0).abs() < 1e-10 && s <= 1.0 {
return Err(SpecialError::DomainError(format!(
"lerch_phi: |z| ≈ 1 and s = {s} ≤ 1 gives a divergent series"
)));
}
let mut sum = 0.0f64;
let mut z_pow = 1.0f64;
for k in 0..MAX_TERMS {
let term = z_pow / (a + k as f64).powf(s);
sum += term;
if term.abs() < TOL * sum.abs().max(1e-300) && k > 5 {
return Ok(sum);
}
if !sum.is_finite() {
return Err(SpecialError::OverflowError(
"lerch_phi: series overflowed".to_string(),
));
}
z_pow *= z;
}
if z.abs() < 1.0 - 1e-10 {
let n_f = MAX_TERMS as f64;
let tail_approx = z_pow / ((a + n_f).powf(s) * (1.0 - z));
sum += tail_approx;
}
if sum.is_finite() {
Ok(sum)
} else {
Err(SpecialError::ConvergenceError(
"lerch_phi: series failed to converge".to_string(),
))
}
}
pub fn clausen_generalized(theta: f64, n: usize) -> SpecialResult<f64> {
if n == 0 {
return Err(SpecialError::DomainError(
"clausen_generalized: order n must be ≥ 1".to_string(),
));
}
if !theta.is_finite() {
return Err(SpecialError::DomainError(
"clausen_generalized: theta must be finite".to_string(),
));
}
if n == 1 {
let half_sin = (theta / 2.0).sin().abs();
if half_sin < 1e-300 {
return Err(SpecialError::DomainError(
"clausen_generalized: Cl_1 is singular at θ = 2kπ".to_string(),
));
}
return Ok(-(2.0 * half_sin).ln());
}
let z = Complex64::new(theta.cos(), theta.sin()); let li = polylogarithm(n as f64, z)?;
if n.is_multiple_of(2) {
Ok(li.im)
} else {
Ok(li.re)
}
}
pub fn nielsen_polylog(n: usize, p: usize, z: f64) -> SpecialResult<f64> {
if p == 0 {
return Err(SpecialError::DomainError(
"nielsen_polylog: p must be ≥ 1".to_string(),
));
}
if z.abs() > 1.0 + 1e-12 {
return Err(SpecialError::DomainError(format!(
"nielsen_polylog: |z| must be ≤ 1 for convergence, got |z| = {}",
z.abs()
)));
}
let order = (n + p) as f64;
let val = polylogarithm(order, Complex64::new(z, 0.0))?;
Ok(val.re)
}
pub fn dilogarithm_reflection(z: f64) -> SpecialResult<(f64, f64)> {
if z <= 0.0 || z >= 1.0 {
return Err(SpecialError::DomainError(format!(
"dilogarithm_reflection: z must be in (0, 1), got z = {z}"
)));
}
let one_minus_z = 1.0 - z;
let li2_z_complex = polylogarithm(2.0, Complex64::new(z, 0.0))?;
let li2_1mz_complex = polylogarithm(2.0, Complex64::new(one_minus_z, 0.0))?;
Ok((li2_z_complex.re, li2_1mz_complex.re))
}
#[inline]
fn complex_ln(z: Complex64) -> Complex64 {
Complex64::new(z.norm().ln(), z.arg())
}
fn polylog_series(s: f64, z: Complex64) -> SpecialResult<Complex64> {
let mut sum = Complex64::new(0.0, 0.0);
let mut z_pow = z;
for k in 1..MAX_TERMS {
let k_s = (k as f64).powf(s);
let term = z_pow / Complex64::new(k_s, 0.0);
sum += term;
if term.norm() < TOL * sum.norm().max(1e-300) && k > 3 {
return Ok(sum);
}
if !sum.re.is_finite() || !sum.im.is_finite() {
return Err(SpecialError::OverflowError(
"polylog_series: series overflowed".to_string(),
));
}
z_pow *= z;
}
if sum.re.is_finite() && sum.im.is_finite() {
Ok(sum)
} else {
Err(SpecialError::ConvergenceError(
"polylog_series: series did not converge".to_string(),
))
}
}
fn polylog_neg_int(n: usize, z: Complex64) -> SpecialResult<Complex64> {
let one_minus_z = Complex64::new(1.0 - z.re, -z.im);
if one_minus_z.norm() < 1e-300 {
return Err(SpecialError::DomainError(
"polylogarithm: z = 1 is a pole for negative integer s".to_string(),
));
}
if n == 0 {
return Ok(z / one_minus_z);
}
let mut euler_row = vec![0u64; n + 1];
euler_row[0] = 1;
for row in 1..=n {
let mut next_row = vec![0u64; row + 1];
for k in 0..=row {
let left = if k < row {
(k + 1) as u64 * euler_row[k]
} else {
0
};
let right = if k > 0 {
(row - k + 1) as u64 * euler_row[k - 1]
} else {
0
};
next_row[k] = left.saturating_add(right);
}
euler_row = next_row;
}
let inv_1mz_n1 = {
let inv_1mz = Complex64::new(1.0, 0.0) / one_minus_z;
let mut p = Complex64::new(1.0, 0.0);
for _ in 0..=(n as u32) {
p *= inv_1mz;
}
p
};
let mut sum = Complex64::new(0.0, 0.0);
let mut z_pow = z; for k in 0..n {
let coeff = euler_row[k] as f64;
sum += z_pow * Complex64::new(coeff, 0.0);
z_pow *= z;
}
Ok(sum * inv_1mz_n1)
}
fn polylog_large_z(s: f64, z: Complex64) -> SpecialResult<Complex64> {
if (s - s.round()).abs() < 1e-12 && s >= 2.0 {
let si = s.round() as i32;
let z_inv = Complex64::new(1.0, 0.0) / z;
let li_inv = polylog_series(s, z_inv)?;
let sign = if si % 2 == 0 { -1.0_f64 } else { 1.0_f64 };
let log_z = complex_ln(z);
let two_pi = 2.0 * PI;
let two_pi_i_s = Complex64::new(0.0, two_pi).powc(Complex64::new(s, 0.0));
let t = log_z / Complex64::new(0.0, two_pi) + Complex64::new(0.5, 0.0);
let bn = bernoulli_poly_complex(si as usize, t);
let factorial_s = gamma_approx(s + 1.0);
let correction = two_pi_i_s * bn / Complex64::new(factorial_s, 0.0);
return Ok(Complex64::new(sign, 0.0) * li_inv + correction);
}
let z_inv = Complex64::new(1.0, 0.0) / z;
let li_inv = polylog_euler_transform(s, z_inv)?;
let log_z = complex_ln(z);
let correction = Complex64::new(0.0, -2.0 * PI) / Complex64::new(gamma_approx(s), 0.0)
* log_z.powc(Complex64::new(s - 1.0, 0.0));
let sign = if (s as i64) % 2 == 0 {
-1.0_f64
} else {
1.0_f64
};
Ok(Complex64::new(sign, 0.0) * li_inv + correction)
}
fn polylog_euler_transform(s: f64, z: Complex64) -> SpecialResult<Complex64> {
let mut sum = Complex64::new(0.0, 0.0);
let mut comp = Complex64::new(0.0, 0.0); let mut z_pow = z;
for k in 1..MAX_TERMS {
let k_s = (k as f64).powf(s);
let term = z_pow / Complex64::new(k_s, 0.0);
let y = term - comp;
let new_sum = sum + y;
comp = (new_sum - sum) - y;
sum = new_sum;
if term.norm() < TOL * sum.norm().max(1e-300) && k > 5 {
return Ok(sum);
}
if !sum.re.is_finite() || !sum.im.is_finite() {
return Err(SpecialError::OverflowError(
"polylog_euler_transform: series overflowed".to_string(),
));
}
z_pow *= z;
}
if sum.re.is_finite() && sum.im.is_finite() {
Ok(sum)
} else {
Err(SpecialError::ConvergenceError(
"polylog_euler_transform: series did not converge".to_string(),
))
}
}
fn bernoulli_poly_complex(n: usize, t: Complex64) -> Complex64 {
const BERN: [f64; 21] = [
1.0,
-0.5,
1.0 / 6.0,
0.0,
-1.0 / 30.0,
0.0,
1.0 / 42.0,
0.0,
-1.0 / 30.0,
0.0,
5.0 / 66.0,
0.0,
-691.0 / 2730.0,
0.0,
7.0 / 6.0,
0.0,
-3617.0 / 510.0,
0.0,
43867.0 / 798.0,
0.0,
-174611.0 / 330.0,
];
if n > 20 {
return Complex64::new(0.0, 0.0);
}
let mut result = Complex64::new(0.0, 0.0);
let mut binom = 1.0f64;
for k in 0..=n {
let bk = if k <= 20 { BERN[k] } else { 0.0 };
let t_pow = t.powc(Complex64::new((n - k) as f64, 0.0));
result += Complex64::new(binom * bk, 0.0) * t_pow;
if k < n {
binom *= (n - k) as f64 / (k + 1) as f64;
}
}
result
}
fn gamma_approx(x: f64) -> f64 {
if x < 0.5 {
let sin_pi_x = (PI * x).sin();
if sin_pi_x.abs() < 1e-300 {
return f64::INFINITY;
}
return PI / (sin_pi_x * gamma_approx(1.0 - x));
}
const G: f64 = 7.0;
const LANCZOS_P: [f64; 9] = [
0.999_999_999_999_809_9,
676.520_368_121_885_1,
-1_259.139_216_722_402_8,
771.323_428_777_653_1,
-176.615_029_162_140_6,
12.507_343_278_686_905,
-0.138_571_095_265_720_12,
9.984_369_578_019_572e-6,
1.505_632_735_149_311_6e-7,
];
let x_adj = x - 1.0;
let t = x_adj + G + 0.5;
let sqrt_2pi = (2.0 * PI).sqrt();
let mut series = LANCZOS_P[0];
for (i, &p) in LANCZOS_P[1..].iter().enumerate() {
series += p / (x_adj + (i + 1) as f64);
}
sqrt_2pi * t.powf(x_adj + 0.5) * (-t).exp() * series
}
fn riemann_zeta_approx(s: f64) -> f64 {
if s <= 1.0 {
return f64::INFINITY;
}
match s.round() as i64 {
2 => return PI * PI / 6.0, 4 => return PI.powi(4) / 90.0, 6 => return PI.powi(6) / 945.0, 8 => return PI.powi(8) / 9450.0, _ => {}
}
const N: usize = 1000;
let mut sum = 0.0f64;
for k in 1..=N {
sum += (k as f64).powf(-s);
}
let tail = (N as f64).powf(1.0 - s) / (s - 1.0);
let em1 = 0.5 * (N as f64).powf(-s);
let em2 = s * (N as f64).powf(-s - 1.0) / 12.0;
sum + tail + em1 + em2
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_polylog_z_zero() {
let val = polylogarithm(2.0, Complex64::new(0.0, 0.0)).expect("Li_2(0)");
assert!(val.norm() < 1e-14);
}
#[test]
fn test_polylog_li2_at_one() {
let val = polylogarithm(2.0, Complex64::new(1.0, 0.0)).expect("Li_2(1)");
let expected = PI * PI / 6.0;
assert!(
(val.re - expected).abs() < 1e-8,
"Li_2(1) = {}, expected {expected}",
val.re
);
}
#[test]
fn test_polylog_li2_at_minus_one() {
let val = polylogarithm(2.0, Complex64::new(-1.0, 0.0)).expect("Li_2(-1)");
let expected = -PI * PI / 12.0;
assert!(
(val.re - expected).abs() < 1e-8,
"Li_2(-1) = {}, expected {}",
val.re,
expected
);
}
#[test]
fn test_polylog_li2_at_half() {
let val = polylogarithm(2.0, Complex64::new(0.5, 0.0)).expect("Li_2(0.5)");
let expected = PI * PI / 12.0 - (2.0_f64.ln()).powi(2) / 2.0;
assert!(
(val.re - expected).abs() < 1e-8,
"Li_2(0.5) = {}, expected {expected}",
val.re
);
}
#[test]
fn test_polylog_li1() {
let val = polylogarithm(1.0, Complex64::new(0.5, 0.0)).expect("Li_1(0.5)");
assert!(
(val.re - 2.0_f64.ln()).abs() < 1e-10,
"Li_1(0.5) = {}",
val.re
);
}
#[test]
fn test_polylog_li0() {
let z = 0.5_f64;
let val = polylogarithm(0.0, Complex64::new(z, 0.0)).expect("Li_0");
let expected = z / (1.0 - z);
assert!(
(val.re - expected).abs() < 1e-13,
"Li_0(0.5) = {}, expected {expected}",
val.re
);
}
#[test]
fn test_polylog_li_neg1() {
let z = 0.5_f64;
let val = polylogarithm(-1.0, Complex64::new(z, 0.0)).expect("Li_{-1}");
let expected = z / (1.0 - z).powi(2);
assert!(
(val.re - expected).abs() < 1e-10,
"Li_{{-1}}(0.5) = {}, expected {expected}",
val.re
);
}
#[test]
fn test_polylog_li3_at_one() {
let val = polylogarithm(3.0, Complex64::new(1.0, 0.0)).expect("Li_3(1)");
let apery = 1.202_056_903_159_594;
assert!(
(val.re - apery).abs() < 1e-6,
"Li_3(1) = {}, expected {apery}",
val.re
);
}
#[test]
fn test_polylog_s1_pole() {
assert!(polylogarithm(1.0, Complex64::new(1.0, 0.0)).is_err());
}
#[test]
fn test_dilogarithm_alias() {
let v1 = dilogarithm(Complex64::new(0.5, 0.0)).expect("dilogarithm");
let v2 = polylogarithm(2.0, Complex64::new(0.5, 0.0)).expect("polylogarithm");
assert!((v1.re - v2.re).abs() < 1e-14);
}
#[test]
fn test_lerch_phi_relation_to_polylog() {
let z = 0.4_f64;
let s = 3.0_f64;
let phi = lerch_phi(z, s, 1.0).expect("lerch_phi");
let li = polylogarithm(s, Complex64::new(z, 0.0)).expect("polylog");
assert!(
(z * phi - li.re).abs() < 1e-7,
"Lerch vs polylog: {phi} vs {}",
li.re
);
}
#[test]
fn test_lerch_phi_hurwitz_zeta() {
let val = lerch_phi(1.0 - 1e-10, 2.0, 1.0).expect("lerch_phi ≈ zeta");
let expected = PI * PI / 6.0;
assert!(
(val - expected).abs() < 0.001,
"Φ(1,2,1) ≈ {val}, expected {expected}"
);
}
#[test]
fn test_lerch_phi_domain_errors() {
assert!(lerch_phi(0.5, 2.0, 0.0).is_err()); assert!(lerch_phi(0.5, 2.0, -1.0).is_err()); assert!(lerch_phi(2.0, 2.0, 1.0).is_err()); }
#[test]
fn test_clausen_n2_maximum() {
let val = clausen_generalized(PI / 3.0, 2).expect("Cl_2(pi/3)");
assert!((val - 1.01494160640965).abs() < 1e-5, "Cl_2(π/3) = {val}");
}
#[test]
fn test_clausen_n1_half_pi() {
let val = clausen_generalized(PI / 2.0, 1).expect("Cl_1(pi/2)");
let expected = -(0.5_f64 * 2_f64.ln());
assert!(
(val - expected).abs() < 1e-10,
"Cl_1(π/2) = {val}, expected {expected}"
);
}
#[test]
fn test_clausen_order_zero_error() {
assert!(clausen_generalized(1.0, 0).is_err());
}
#[test]
fn test_clausen_n1_singular() {
assert!(clausen_generalized(0.0, 1).is_err());
}
#[test]
fn test_nielsen_n0_equals_polylog() {
let z = 0.5_f64;
let s_val = nielsen_polylog(0, 2, z).expect("S_{0,2}");
let li = polylogarithm(2.0, Complex64::new(z, 0.0)).expect("Li_2");
assert!(
(s_val - li.re).abs() < 1e-8,
"S_{{0,2}} = {s_val}, Li_2 = {}",
li.re
);
}
#[test]
fn test_nielsen_s11_at_one() {
let val = nielsen_polylog(1, 1, 1.0).expect("S_{1,1}(1)");
let expected = PI * PI / 6.0;
assert!(
(val - expected).abs() < 1e-8,
"S_{{1,1}}(1) ≈ {val}, expected {expected}"
);
}
#[test]
fn test_nielsen_p0_error() {
assert!(nielsen_polylog(1, 0, 0.5).is_err());
}
#[test]
fn test_nielsen_z_too_large_error() {
assert!(nielsen_polylog(1, 2, 1.5).is_err());
}
#[test]
fn test_reflection_identity() {
let z = 0.3_f64;
let (li2z, li2_1mz) = dilogarithm_reflection(z).expect("reflection");
let lhs = li2z + li2_1mz;
let rhs = PI * PI / 6.0 - z.ln() * (1.0 - z).ln();
assert!((lhs - rhs).abs() < 1e-9, "reflection: lhs={lhs}, rhs={rhs}");
}
#[test]
fn test_reflection_z_half() {
let (li2z, li2_1mz) = dilogarithm_reflection(0.5).expect("reflection 0.5");
assert_relative_eq!(li2z, li2_1mz, epsilon = 1e-8); let expected = PI * PI / 12.0 - (2.0_f64.ln()).powi(2) / 2.0;
assert!(
(li2z - expected).abs() < 1e-8,
"Li_2(0.5) via reflection: {li2z}"
);
}
#[test]
fn test_reflection_domain_errors() {
assert!(dilogarithm_reflection(0.0).is_err());
assert!(dilogarithm_reflection(1.0).is_err());
assert!(dilogarithm_reflection(-0.5).is_err());
assert!(dilogarithm_reflection(1.5).is_err());
}
#[test]
fn test_gamma_approx_integers() {
let expected = [1.0_f64, 1.0, 2.0, 6.0, 24.0, 120.0];
for (i, &e) in expected.iter().enumerate() {
let val = gamma_approx((i + 1) as f64);
assert!(
(val - e).abs() < 1e-8 * e.max(1.0),
"Γ({}) = {val}, expected {e}",
i + 1
);
}
}
}