pub(crate) const SERIES_TERM_FLOOR: f64 = 1e-18;
pub(crate) const FAST_REGIME_MAX_TERMS: usize = 200;
pub(crate) const SLOW_TRILOG_MAX_TERMS: usize = 5000;
#[inline]
pub(crate) fn dilog_unit(z: f64) -> f64 {
if !z.is_finite() {
return f64::NAN;
}
let z = z.clamp(0.0, 1.0);
if z == 0.0 {
return 0.0;
}
if z >= 1.0 {
return std::f64::consts::PI * std::f64::consts::PI / 6.0;
}
if z <= 0.5 {
let mut sum = 0.0_f64;
let mut zk = z;
for k in 1..=FAST_REGIME_MAX_TERMS {
let kf = k as f64;
let term = zk / (kf * kf);
sum += term;
if term < SERIES_TERM_FLOOR {
break;
}
zk *= z;
}
sum
} else {
let one_minus_z = 1.0 - z;
let pi2_6 = std::f64::consts::PI * std::f64::consts::PI / 6.0;
pi2_6 - z.ln() * one_minus_z.ln() - dilog_unit(one_minus_z)
}
}
#[inline]
pub(crate) fn trilog_unit(z: f64) -> f64 {
const ZETA3: f64 = 1.2020569031595942853997381615114499907649862923404988817922;
if !z.is_finite() {
return f64::NAN;
}
let z = z.clamp(0.0, 1.0);
if z == 0.0 {
return 0.0;
}
if z >= 1.0 {
return ZETA3;
}
let max_terms: usize = if z <= 0.5 {
FAST_REGIME_MAX_TERMS
} else {
SLOW_TRILOG_MAX_TERMS
};
let mut sum = 0.0_f64;
let mut zk = z;
for k in 1..=max_terms {
let kf = k as f64;
let term = zk / (kf * kf * kf);
sum += term;
if term < SERIES_TERM_FLOOR {
break;
}
zk *= z;
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dilog_unit_zero_is_zero() {
assert_eq!(dilog_unit(0.0), 0.0);
}
#[test]
fn dilog_unit_at_one_is_pi_squared_over_six() {
let expected = std::f64::consts::PI * std::f64::consts::PI / 6.0;
assert!((dilog_unit(1.0) - expected).abs() < 1e-15);
}
#[test]
fn dilog_unit_nan_input_returns_nan() {
assert!(dilog_unit(f64::NAN).is_nan());
}
#[test]
fn dilog_unit_at_half_matches_reflection_identity() {
let expected = std::f64::consts::PI.powi(2) / 12.0
- (2.0_f64).ln().powi(2) / 2.0;
assert!((dilog_unit(0.5) - expected).abs() < 1e-14);
}
#[test]
fn dilog_unit_is_positive_on_open_unit_interval() {
for z in [0.1, 0.25, 0.5, 0.75, 0.9] {
assert!(dilog_unit(z) > 0.0, "dilog_unit({z}) should be positive");
}
}
#[test]
fn dilog_unit_clamps_below_zero() {
assert_eq!(dilog_unit(-1.0), dilog_unit(0.0));
}
#[test]
fn dilog_unit_clamps_above_one() {
assert!((dilog_unit(2.0) - dilog_unit(1.0)).abs() < 1e-15);
}
#[test]
fn trilog_unit_zero_is_zero() {
assert_eq!(trilog_unit(0.0), 0.0);
}
#[test]
fn trilog_unit_at_one_is_zeta3() {
const ZETA3: f64 = 1.2020569031595942853997381615114499907649862923404988817922;
assert!((trilog_unit(1.0) - ZETA3).abs() < 1e-14);
}
#[test]
fn trilog_unit_nan_input_returns_nan() {
assert!(trilog_unit(f64::NAN).is_nan());
}
#[test]
fn trilog_unit_at_half_matches_known_value() {
let expected = 0.5372131936080403_f64;
assert!((trilog_unit(0.5) - expected).abs() < 1e-13);
}
#[test]
fn trilog_unit_is_positive_on_open_unit_interval() {
for z in [0.1, 0.25, 0.5, 0.75, 0.9] {
assert!(trilog_unit(z) > 0.0, "trilog_unit({z}) should be positive");
}
}
#[test]
fn trilog_unit_clamps_below_zero() {
assert_eq!(trilog_unit(-0.5), trilog_unit(0.0));
}
#[test]
fn trilog_unit_clamps_above_one() {
assert!((trilog_unit(2.0) - trilog_unit(1.0)).abs() < 1e-14);
}
}