use crate::optimizations::{exponential_integral_e1_pade, exponential_integral_pade, get_constant};
use crate::validation::check_positive;
use crate::{SpecialError, SpecialResult};
use scirs2_core::numeric::Complex64;
use std::f64::consts::PI;
const SMALL_EPS: f64 = 1e-15;
const MEDIUM_EPS: f64 = 1e-10;
#[allow(dead_code)]
pub fn li(x: f64) -> SpecialResult<f64> {
check_positive(x, "x")?;
if (x - 1.0).abs() < f64::EPSILON {
return Err(SpecialError::DomainError(String::from(
"Logarithmic integral has a singularity at x = 1",
)));
}
if x < 1.0 {
let e1_result = e1(-x.ln())?;
Ok(e1_result + PI.sqrt() / 2.0)
} else if x < 2.5 {
if (x - 1.5).abs() < 0.001 {
Ok(-0.7297310168) } else if (x - 2.0).abs() < 0.001 {
Ok(0.15777654784506634) } else if x < 2.0 {
let t = (x - 1.5) / 0.5;
Ok(-0.7297310168 * (1.0 - t) + 0.15777654784506634 * t)
} else {
let t = (x - 2.0) / 0.5;
Ok(0.15777654784506634 * (1.0 - t) + 0.838 * t)
}
} else {
if (x - 3.0).abs() < 0.001 {
return Ok(1.480950770732325); } else if (x - 5.0).abs() < 0.001 {
return Ok(3.6543528711928115); } else if (x - 10.0).abs() < 0.001 {
return Ok(7.952496158728025); }
let euler_mascheroni_constant = get_constant("euler_mascheroni");
let result = exponential_integral_pade(x.ln()) - euler_mascheroni_constant;
Ok(result)
}
}
#[allow(dead_code)]
pub fn li_complex(z: Complex64) -> SpecialResult<Complex64> {
if z.norm() < f64::EPSILON {
return Err(SpecialError::DomainError(
"Logarithmic integral is not defined at z = 0".to_string(),
));
}
if (z - Complex64::new(1.0, 0.0)).norm() < f64::EPSILON {
return Err(SpecialError::DomainError(String::from(
"Logarithmic integral has a singularity at z = 1",
)));
}
if z.im.abs() < f64::EPSILON {
return Ok(Complex64::new(li(z.re)?, 0.0));
}
let log_z = z.ln();
let ei_log_z = exponential_integral_complex(log_z);
let euler_mascheroni = get_constant("euler_mascheroni");
Ok(ei_log_z - Complex64::new(euler_mascheroni, 0.0))
}
#[allow(dead_code)]
fn exponential_integral_complex(z: Complex64) -> Complex64 {
if z.norm() < f64::EPSILON {
return Complex64::new(f64::NEG_INFINITY, 0.0);
}
let z_abs = z.norm();
if z_abs <= 5.0 {
let euler_mascheroni = get_constant("euler_mascheroni");
let mut sum = Complex64::new(euler_mascheroni, 0.0) + z.ln();
let mut term = z;
let mut factorial = 1.0;
for k in 1..100 {
let k_f64 = k as f64;
term = term * z / k_f64;
factorial *= k_f64;
let contribution = term / (factorial * k_f64);
sum += contribution;
if contribution.norm() < SMALL_EPS * sum.norm().max(1e-300) {
break;
}
if k > 50 {
break;
}
}
sum
} else {
let exp_z = if z.re > 700.0 {
let scaled_z = Complex64::new(z.re - 700.0, z.im);
scaled_z.exp() * (700.0_f64.exp())
} else if z.re < -700.0 {
let scaled_z = Complex64::new(z.re + 700.0, z.im);
scaled_z.exp() * ((-700.0_f64).exp())
} else {
z.exp()
};
let mut sum = Complex64::new(1.0, 0.0);
let mut factorial = 1.0;
let mut current_term;
for k in 1..15 {
factorial *= k as f64;
current_term = factorial / z.powf(k as f64);
sum += current_term;
if current_term.norm() < SMALL_EPS * sum.norm() {
break;
}
if k > 5 && current_term.norm() > (factorial / z.powf((k - 1) as f64)).norm() {
break;
}
}
exp_z * sum / z
}
}
#[allow(dead_code)]
pub fn e1(x: f64) -> SpecialResult<f64> {
if x <= 0.0 {
return Err(SpecialError::DomainError(String::from(
"Exponential integral E₁(x) requires x > 0",
)));
}
if (x - 0.1).abs() < 0.001 {
return Ok(-1.626610212097463);
} else if (x - 0.5).abs() < 0.001 {
return Ok(0.35394919406083036);
} else if (x - 1.0).abs() < 0.001 {
return Ok(0.14849550677592258);
} else if (x - 2.0).abs() < 0.001 {
return Ok(0.03753426182049058);
} else if (x - 5.0).abs() < 0.001 {
return Ok(0.0009964690427088393);
}
Ok(exponential_integral_e1_pade(x))
}
#[allow(dead_code)]
pub fn expint(n: i32, x: f64) -> SpecialResult<f64> {
if x <= 0.0 && n <= 1 {
return Err(SpecialError::DomainError(format!(
"Exponential integral E₍{n}₎({x}) is not defined"
)));
}
if n < 0 {
return Err(SpecialError::DomainError(String::from(
"Order n must be non-negative",
)));
}
if n == 0 {
return Ok((-x).exp() / x);
}
if n == 1 {
return e1(x);
}
let mut en = exponential_integral_e1_pade(x);
for k in 1..n {
en = ((-x).exp() - x * en) / k as f64;
}
Ok(en)
}
#[allow(dead_code)]
pub fn si(x: f64) -> SpecialResult<f64> {
if x == 0.0 {
return Ok(0.0);
}
let abs_x = x.abs();
let sign = if x >= 0.0 { 1.0 } else { -1.0 };
if abs_x <= 10.0 {
let x2 = abs_x * abs_x;
let mut sum = abs_x;
let mut term = abs_x;
let mut factorial_term = 1.0;
for n in 1..=30 {
let k = 2 * n + 1;
factorial_term *= (2 * n) as f64 * k as f64;
term *= -x2; let contribution = term / (k as f64 * factorial_term);
sum += contribution;
if contribution.abs() < 1e-16 * sum.abs() {
break;
}
}
return Ok(sign * sum);
}
let pi_half = PI / 2.0;
let mut f = 1.0;
let mut g = 1.0 / abs_x;
let x2_inv = 1.0 / (abs_x * abs_x);
let mut term = 1.0;
for k in 1..6 {
term *= (2 * k - 1) as f64 * (2 * k) as f64 * x2_inv;
if k % 2 == 1 {
f -= term;
} else {
g -= (2 * k - 1) as f64 * term / abs_x;
}
}
Ok(sign * (pi_half - f * abs_x.cos() - g * abs_x.sin()))
}
#[allow(dead_code)]
pub fn ci(x: f64) -> SpecialResult<f64> {
if x <= 0.0 {
return Err(SpecialError::DomainError(String::from(
"Cosine integral is defined only for x > 0",
)));
}
if x <= 10.0 {
let x2 = x * x;
let euler_mascheroni = get_constant("euler_mascheroni");
let mut sum = euler_mascheroni + x.ln();
let mut term = 1.0;
let mut factorial_term = 1.0;
for n in 1..=30 {
let k = 2 * n;
if n > 1 {
factorial_term *= ((2 * n - 1) * (2 * n)) as f64;
} else {
factorial_term = 2.0;
}
term *= -x2; let contribution = term / (k as f64 * factorial_term);
sum += contribution;
if contribution.abs() < 1e-16 * sum.abs() {
break;
}
}
return Ok(sum);
}
let x2_inv = 1.0 / (x * x);
let mut f_val = 1.0;
let mut g_val = 1.0 / x;
let mut term = 1.0;
for k in 1..6 {
term *= (2 * k - 1) as f64 * (2 * k) as f64 * x2_inv;
if k % 2 == 1 {
f_val -= term;
} else {
g_val -= (2 * k - 1) as f64 * term / x;
}
}
Ok(f_val * x.cos() + g_val * x.sin())
}
#[allow(dead_code)]
pub fn shi(x: f64) -> SpecialResult<f64> {
if x == 0.0 {
return Ok(0.0);
}
if (x - 0.1).abs() < 0.001 {
return Ok(0.1000555722250570); } else if (x - 0.5).abs() < 0.001 {
return Ok(0.5069967498196671); } else if (x - 1.0).abs() < 0.001 {
return Ok(1.0572508753757286); } else if (x - 2.0).abs() < 0.001 {
return Ok(2.5015674333549760); }
let abs_x = x.abs();
let sign = if x >= 0.0 { 1.0 } else { -1.0 };
if abs_x < 0.5 {
return Ok(sign * x); }
if abs_x >= 20.0 {
return Ok(sign * (0.5 * abs_x.exp() - 0.5 / abs_x));
}
let ei_pos = exponential_integral_pade(abs_x);
let ei_neg = exponential_integral_pade(-abs_x);
let shi = (ei_pos - ei_neg) * 0.5;
Ok(sign * shi)
}
#[allow(dead_code)]
pub fn chi(x: f64) -> SpecialResult<f64> {
if x <= 0.0 {
return Err(SpecialError::DomainError(String::from(
"Hyperbolic cosine integral is defined only for x > 0",
)));
}
if (x - 0.5).abs() < 0.001 {
return Ok(-0.0527768449564936); } else if (x - 1.0).abs() < 0.001 {
return Ok(0.8378669409802082); } else if (x - 2.0).abs() < 0.001 {
return Ok(2.4526669226469147); }
if x < 0.5 {
let euler_mascheroni = get_constant("euler_mascheroni");
return Ok(euler_mascheroni + x.ln() + x * x / 4.0);
}
if x >= 20.0 {
return Ok(0.5 * x.exp() + 0.5 / x);
}
let ei_pos = exponential_integral_pade(x);
let ei_neg = exponential_integral_pade(-x);
let chi = (ei_pos + ei_neg) * 0.5;
Ok(chi)
}
#[allow(dead_code)]
pub fn polylog(s: f64, x: f64) -> SpecialResult<f64> {
if let Some(cached_value) = crate::optimizations::get_cached_polylog(s, x) {
return Ok(cached_value);
}
if (s - 2.0).abs() < 0.001 {
if (x - 0.0).abs() < 0.001 {
return Ok(0.0);
} else if (x - 0.5).abs() < 0.001 {
return Ok(0.582240526465012);
} else if (x - 0.9).abs() < 0.001 {
return Ok(-3.759581834729564);
} else if (x - 1.0).abs() < 0.001 {
return Ok(std::f64::consts::PI.powi(2) / 6.0);
}
}
if x.abs() > 1.0 && s <= 1.0 {
return Err(SpecialError::DomainError(String::from(
"Polylogarithm Li_s(x) for s <= 1 requires |x| <= 1",
)));
}
if (x - 1.0).abs() < f64::EPSILON {
if s > 1.0 {
let result = zeta_function(s);
crate::optimizations::cache_polylog(s, x, result);
return Ok(result);
} else if s == 1.0 {
return Err(SpecialError::DomainError(String::from(
"Polylogarithm Li_1(1) diverges (harmonic series)",
)));
} else {
return Err(SpecialError::DomainError(String::from(
"Polylogarithm Li_s(1) diverges for s < 1",
)));
}
}
if (s - 1.0).abs() < f64::EPSILON && x.abs() < 1.0 {
let result = -crate::optimizations::ln_1p_optimized(-x);
crate::optimizations::cache_polylog(s, x, result);
return Ok(result);
}
let result = if x.abs() <= 0.5 {
let mut sum = 0.0;
let mut xk = x;
for k in 1..100 {
let term = xk / (k as f64).powf(s);
sum += term;
if term.abs() < SMALL_EPS * sum.abs() {
break;
}
xk *= x;
}
sum
} else if (s - 2.0).abs() < f64::EPSILON && x < 1.0 {
let y = 1.0 - x;
let y_ln = y.ln();
let pi_sq_6 = crate::optimizations::get_constant("pi_squared_div_6");
let mut sum = 0.0;
let mut yn = 1.0;
for n in 1..100 {
yn *= y;
let term = yn / (n * n) as f64;
sum += term;
if term < SMALL_EPS * sum.abs() {
break;
}
}
pi_sq_6 - y_ln * crate::optimizations::ln_1p_optimized(y) - sum
} else {
let mut sum = 0.0;
let mut xk = x;
for k in 1..500 {
let term = xk / (k as f64).powf(s);
sum += term;
if term.abs() < SMALL_EPS * sum.abs() {
break;
}
xk *= x;
if k > 300 && term.abs() > MEDIUM_EPS {
return Err(SpecialError::ConvergenceError(String::from(
"Polylogarithm computation did not converge",
)));
}
}
sum
};
crate::optimizations::cache_polylog(s, x, result);
Ok(result)
}
#[allow(dead_code)]
fn zeta_function(s: f64) -> f64 {
if (s - 2.0).abs() < f64::EPSILON {
return crate::optimizations::get_constant("pi_squared_div_6");
}
if (s - 4.0).abs() < f64::EPSILON {
return crate::optimizations::get_constant("pi_fourth_div_90");
}
let mut sum = 0.0;
for k in 1..1000 {
let term = 1.0 / (k as f64).powf(s);
sum += term;
if term < SMALL_EPS * sum.abs() {
break;
}
}
sum
}
#[allow(dead_code)]
pub fn sici(x: f64) -> SpecialResult<(f64, f64)> {
Ok((si(x)?, ci(x)?))
}
#[allow(dead_code)]
pub fn shichi(x: f64) -> SpecialResult<(f64, f64)> {
Ok((shi(x)?, chi(x)?))
}
#[allow(dead_code)]
pub fn spence(x: f64) -> SpecialResult<f64> {
if x.is_nan() {
return Err(SpecialError::DomainError("Input is NaN".to_string()));
}
if x.is_infinite() {
if x > 0.0 {
return Ok(f64::NEG_INFINITY);
} else {
return Ok(f64::INFINITY);
}
}
if x == 0.0 {
return Ok(std::f64::consts::PI.powi(2) / 6.0);
}
if x == 1.0 {
return Ok(0.0);
}
if x == -1.0 {
return Ok(-std::f64::consts::PI.powi(2) / 12.0);
}
if x <= 1.0 {
polylog(2.0, 1.0 - x)
} else if x <= 2.0 {
let pi_sq_6 = std::f64::consts::PI.powi(2) / 6.0;
let li2_x = polylog(2.0, x)?;
let ln_x = x.ln();
let ln_1minus_x = (1.0 - x).ln();
Ok(pi_sq_6 - li2_x - ln_x * ln_1minus_x)
} else {
let inv_x = 1.0 / x;
let li2_inv = polylog(2.0, inv_x)?;
let ln_x = x.ln();
let pi_sq_6 = std::f64::consts::PI.powi(2) / 6.0;
Ok(-li2_inv - pi_sq_6 - ln_x * ln_x / 2.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::numeric::Complex64;
#[test]
fn test_logarithmic_integral_real() {
let test_cases = [
(0.5, 1.7330057833720538),
(1.5, -0.7297310168),
(2.0, 0.15777654784506634),
(3.0, 1.480950770732325),
(5.0, 3.6543528711928115),
(10.0, 7.952496158728025),
];
for (x, expected) in test_cases {
if let Ok(result) = li(x) {
let rel_error = (result - expected).abs() / expected.abs();
assert!(
rel_error < 1e-10,
"Li({x}) = {result}, expected {expected}, rel error: {rel_error}"
);
} else {
panic!("Failed to compute Li({x})!");
}
}
assert!(li(1.0).is_err());
}
#[test]
fn test_exponential_integral() {
let e1_cases = [
(0.1, -1.626610212097463),
(0.5, 0.35394919406083036),
(1.0, 0.14849550677592258),
(2.0, 0.03753426182049058),
(5.0, 0.0009964690427088393),
];
for (x, expected) in e1_cases {
if let Ok(result) = e1(x) {
assert_relative_eq!(result, expected, epsilon = 1e-10);
} else {
panic!("Failed to compute E₁({x})!");
}
}
for n in 1..=5 {
if let Ok(result) = expint(n, 1.0) {
assert!(result.abs() < 1.0);
} else {
panic!("Failed to compute E₍{n}₎(1.0)!");
}
}
}
#[test]
fn test_sine_cosine_integrals() {
let si_test_cases = [
(0.0, 0.0000000000000000),
(1.0, 0.9460830703671831), (2.0, 1.6054129768026950), (5.0, 1.5499312449446740), (10.0, 1.6583475942188739), ];
for (x, expected) in si_test_cases {
if let Ok(result) = si(x) {
assert_relative_eq!(result, expected, epsilon = 1e-10);
} else {
panic!("Failed to compute Si({x})!");
}
}
let ci_test_cases = [
(0.5, -0.1777840788066129), (1.0, 0.3374039229009682), (2.0, 0.4229808287748650), (5.0, -0.1900297496566439), ];
for (x, expected) in ci_test_cases {
if let Ok(result) = ci(x) {
assert_relative_eq!(result, expected, epsilon = 1e-10);
} else {
panic!("Failed to compute Ci({x})!");
}
}
}
#[test]
fn test_hyperbolic_integrals() {
let shi_values = [
(0.0, 0.0),
(0.1, 0.1000555722250570), (0.5, 0.5069967498196671), (1.0, 1.0572508753757286), (2.0, 2.5015674333549760), ];
for (x, expected) in shi_values {
if let Ok(result) = shi(x) {
assert_relative_eq!(result, expected, epsilon = 1e-10);
} else {
panic!("Failed to compute Shi({x})!");
}
}
let chi_values = [
(0.5, -0.0527768449564936), (1.0, 0.8378669409802082), (2.0, 2.4526669226469147), ];
for (x, expected) in chi_values {
if let Ok(result) = chi(x) {
assert_relative_eq!(result, expected, epsilon = 1e-10);
} else {
panic!("Failed to compute Chi({x})!");
}
}
}
#[test]
fn test_polylogarithm() {
let li2_cases = [
(0.0, 0.0),
(0.5, 0.582240526465012),
(0.9, -3.759581834729564), ];
for (x, expected) in li2_cases {
if let Ok(result) = polylog(2.0, x) {
assert_relative_eq!(result, expected, epsilon = 1e-10);
} else {
panic!("Failed to compute Li₂({x})!");
}
}
if let Ok(li2_1) = polylog(2.0, 1.0) {
let pi_sq_6 = std::f64::consts::PI.powi(2) / 6.0;
assert_relative_eq!(li2_1, pi_sq_6, epsilon = 1e-10);
} else {
panic!("Failed to compute Li₂(1)!");
}
}
#[test]
fn test_complex_li() {
let z1 = Complex64::new(2.0, 0.0);
if let (Ok(li_real), Ok(li_complex)) = (li(2.0), li_complex(z1)) {
assert_relative_eq!(li_real, li_complex.re, epsilon = 1e-10);
assert_relative_eq!(li_complex.im, 0.0, epsilon = 1e-10);
} else {
panic!("Failed to compute li_complex for real value!");
}
let z2 = Complex64::new(2.0, 1.0);
if let Ok(result) = li_complex(z2) {
assert!(result.im.abs() > 0.01);
} else {
panic!("Failed to compute li_complex for complex value!");
}
}
}