use crate::error::{SpecialError, SpecialResult};
use std::f64::consts::PI;
pub fn debye1(x: f64) -> SpecialResult<f64> {
debye_n(1, x)
}
pub fn debye2(x: f64) -> SpecialResult<f64> {
debye_n(2, x)
}
pub fn debye3(x: f64) -> SpecialResult<f64> {
debye_n(3, x)
}
pub fn debye4(x: f64) -> SpecialResult<f64> {
debye_n(4, x)
}
pub fn debye5(x: f64) -> SpecialResult<f64> {
debye_n(5, x)
}
fn debye_n(n: i32, x: f64) -> SpecialResult<f64> {
if n < 1 {
return Err(SpecialError::DomainError(
"Debye function order n must be >= 1".to_string(),
));
}
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to Debye function".to_string(),
));
}
if x < 0.0 {
return Err(SpecialError::DomainError(
"Debye function requires non-negative x".to_string(),
));
}
if x == 0.0 {
return Ok(1.0);
}
if x < 0.1 {
return debye_small_x(n, x);
}
if x < 2.0 * PI {
return debye_series(n, x);
}
debye_large_x(n, x)
}
fn debye_small_x(n: i32, x: f64) -> SpecialResult<f64> {
let n_f = n as f64;
let bernoulli_2k = [
1.0 / 6.0, -1.0 / 30.0, 1.0 / 42.0, -1.0 / 30.0, 5.0 / 66.0, -691.0 / 2730.0, ];
let mut result = 1.0 - n_f * x / (2.0 * (n_f + 1.0));
let mut x_power = 1.0;
for (k_idx, &b2k) in bernoulli_2k.iter().enumerate() {
let k = (k_idx + 1) as f64; let two_k = 2.0 * k;
x_power *= x * x;
let mut factorial_2k = 1.0;
for j in 1..=(2 * (k_idx + 1)) {
factorial_2k *= j as f64;
}
let term = n_f * b2k / (factorial_2k * (n_f + two_k)) * x_power;
result += term;
if term.abs() < 1e-16 * result.abs() {
break;
}
}
Ok(result)
}
fn debye_series(n: i32, x: f64) -> SpecialResult<f64> {
let integral = gauss_legendre_debye(n, x)?;
let n_f = n as f64;
let x_pow_n = x.powi(n);
if x_pow_n == 0.0 {
return Ok(0.0);
}
Ok(n_f / x_pow_n * integral)
}
fn debye_large_x(n: i32, x: f64) -> SpecialResult<f64> {
let n_f = n as f64;
let zeta_values = [
0.0, PI * PI / 6.0, 1.202056903159594, PI.powi(4) / 90.0, 1.036927755143370, PI.powi(6) / 945.0, ];
let zeta_n_plus_1 = if (n as usize) < zeta_values.len() {
zeta_values[n as usize]
} else {
1.0 + 1.0 / 2.0_f64.powi(n + 1) + 1.0 / 3.0_f64.powi(n + 1)
};
let mut n_factorial = 1.0;
for k in 1..=n {
n_factorial *= k as f64;
}
let full_integral = n_factorial * zeta_n_plus_1;
let mut tail = 0.0;
for k in 1..=20 {
let k_f = k as f64;
let ekx = (-k_f * x).exp();
if ekx < 1e-300 {
break; }
let mut poly = 0.0;
let mut coeff = 1.0; let mut x_power = x.powi(n); let mut k_power = k_f;
for j in 0..=n {
poly += coeff * x_power / k_power;
if j < n {
coeff *= (n - j) as f64;
x_power /= x;
k_power *= k_f;
}
}
tail += ekx * poly;
if ekx * poly < 1e-16 * tail.abs() {
break;
}
}
let integral = full_integral - tail;
let x_pow_n = x.powi(n);
if x_pow_n == 0.0 {
return Ok(0.0);
}
Ok(n_f / x_pow_n * integral)
}
fn gauss_legendre_debye(n: i32, x: f64) -> SpecialResult<f64> {
let nodes = [
-0.993128599185094925,
-0.963971927277913791,
-0.912234428251325906,
-0.839116971822218823,
-0.746331906460150793,
-0.636053680726515025,
-0.510867001950827098,
-0.373706088715419561,
-0.227785851141645078,
-0.076526521133497334,
0.076526521133497334,
0.227785851141645078,
0.373706088715419561,
0.510867001950827098,
0.636053680726515025,
0.746331906460150793,
0.839116971822218823,
0.912234428251325906,
0.963971927277913791,
0.993128599185094925,
];
let weights = [
0.017614007139152118,
0.040601429800386941,
0.062672048334109064,
0.083276741576704749,
0.101930119817240435,
0.118194531961518417,
0.131688638449176627,
0.142096109318382051,
0.149172986472603747,
0.152753387130725851,
0.152753387130725851,
0.149172986472603747,
0.142096109318382051,
0.131688638449176627,
0.118194531961518417,
0.101930119817240435,
0.083276741576704749,
0.062672048334109064,
0.040601429800386941,
0.017614007139152118,
];
let half_x = x / 2.0;
let mid_x = x / 2.0;
let mut integral = 0.0;
let n_f = n as f64;
for (&node, &weight) in nodes.iter().zip(weights.iter()) {
let t = mid_x + half_x * node;
if t <= 0.0 {
continue;
}
let integrand = if t < 1e-10 {
t.powf(n_f - 1.0)
} else if t > 500.0 {
t.powf(n_f) * (-t).exp()
} else {
let exp_t = t.exp();
t.powf(n_f) / (exp_t - 1.0)
};
integral += weight * integrand;
}
integral *= half_x;
Ok(integral)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_debye_at_zero() {
for n in 1..=5 {
let val = debye_n(n, 0.0).expect("debye_n failed at x=0");
assert_relative_eq!(val, 1.0, epsilon = 1e-10);
}
}
#[test]
fn test_debye1_known_values() {
let val = debye1(1.0).expect("debye1 failed");
assert_relative_eq!(val, 0.7775046341122, epsilon = 1e-4);
}
#[test]
fn test_debye2_known_values() {
let val = debye2(1.0).expect("debye2 failed");
assert_relative_eq!(val, 0.7075883, epsilon = 1e-3);
}
#[test]
fn test_debye3_known_values() {
let val = debye3(1.0).expect("debye3 failed");
assert_relative_eq!(val, 0.6744, epsilon = 5e-3);
}
#[test]
fn test_debye4_known_values() {
let val = debye4(1.0).expect("debye4 failed");
assert_relative_eq!(val, 0.6544, epsilon = 5e-3);
}
#[test]
fn test_debye_monotone_decreasing() {
for n in 1..=4 {
let d1 = debye_n(n, 0.5).expect("failed");
let d2 = debye_n(n, 1.0).expect("failed");
let d3 = debye_n(n, 2.0).expect("failed");
let d4 = debye_n(n, 5.0).expect("failed");
assert!(d1 > d2, "D_{} should be decreasing", n);
assert!(d2 > d3, "D_{} should be decreasing", n);
assert!(d3 > d4, "D_{} should be decreasing", n);
}
}
#[test]
fn test_debye_range_zero_to_one() {
for n in 1..=5 {
for &x in &[0.01, 0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 50.0] {
let val = debye_n(n, x).expect("failed");
assert!(
val > 0.0 && val <= 1.0,
"D_{}({}) = {} should be in (0, 1]",
n,
x,
val
);
}
}
}
#[test]
fn test_debye_large_x() {
let x = 100.0;
let val = debye1(x).expect("failed");
let expected = PI * PI / 6.0 / x;
assert_relative_eq!(val, expected, epsilon = 1e-3);
}
#[test]
fn test_debye_small_x_series() {
let x = 0.01;
for n in 1..=4 {
let val = debye_n(n, x).expect("failed");
let approx = 1.0 - n as f64 * x / (2.0 * (n as f64 + 1.0));
assert_relative_eq!(val, approx, epsilon = 1e-4);
}
}
#[test]
fn test_debye_negative_x() {
assert!(debye1(-1.0).is_err());
}
#[test]
fn test_debye_nan() {
assert!(debye1(f64::NAN).is_err());
}
#[test]
fn test_debye5_positive() {
let val = debye5(3.0).expect("failed");
assert!(val > 0.0);
assert!(val < 1.0);
}
}