use std::f64::consts::PI;
const SQRT_PI_OVER_2: f64 = 0.886_226_925_452_758;
const SIMPSON_N: usize = 2000;
pub fn f_half(eta: f64) -> f64 {
if eta <= -2.0 {
f_half_boltzmann(eta)
} else if eta <= 5.0 {
f_half_midrange(eta)
} else {
f_half_sommerfeld(eta)
}
}
fn f_half_boltzmann(eta: f64) -> f64 {
let e = eta.exp();
let e2 = e * e;
let e3 = e2 * e;
let e4 = e3 * e;
let e5 = e4 * e;
SQRT_PI_OVER_2
* e
* (1.0 - e / (2.0 * 2.0_f64.sqrt()) + e2 / (3.0 * 3.0_f64.sqrt())
- e3 / (4.0 * 4.0_f64.sqrt())
+ e4 / (5.0 * 5.0_f64.sqrt())
- e5 / (6.0 * 6.0_f64.sqrt()))
}
fn f_half_midrange(eta: f64) -> f64 {
let u_max = (eta + 50.0).max(50.0).sqrt();
let n = SIMPSON_N;
let h = u_max / n as f64;
let mut sum = 0.0_f64;
for k in 0..=n {
let u = k as f64 * h;
let u2 = u * u;
let exponent = u2 - eta;
let val = if exponent > 700.0 {
0.0
} else {
2.0 * u2 / (1.0 + exponent.exp())
};
let w = if k == 0 || k == n {
1.0
} else if k % 2 == 1 {
4.0
} else {
2.0
};
sum += w * val;
}
sum * h / 3.0
}
fn f_half_sommerfeld(eta: f64) -> f64 {
let eta2 = eta * eta;
let eta32 = eta.powf(1.5);
let pi2 = PI * PI;
let pi4 = pi2 * pi2;
(2.0 / 3.0) * eta32 * (1.0 + pi2 / (8.0 * eta2) + 7.0 * pi4 / (640.0 * eta2 * eta2))
}
pub fn f_minus_half(eta: f64) -> f64 {
if eta <= -2.0 {
f_minus_half_boltzmann(eta)
} else if eta <= 5.0 {
f_minus_half_midrange(eta)
} else {
f_minus_half_sommerfeld(eta)
}
}
fn f_minus_half_boltzmann(eta: f64) -> f64 {
let e = eta.exp();
let e2 = e * e;
let e3 = e2 * e;
let e4 = e3 * e;
let e5 = e4 * e;
PI.sqrt()
* e
* (1.0 - e / 2.0_f64.sqrt() + e2 / 3.0_f64.sqrt() - e3 / 4.0_f64.sqrt()
+ e4 / 5.0_f64.sqrt()
- e5 / 6.0_f64.sqrt())
}
fn f_minus_half_midrange(eta: f64) -> f64 {
let u_max = (eta + 50.0).max(50.0).sqrt();
let n = SIMPSON_N;
let h = u_max / n as f64;
let mut sum = 0.0_f64;
for k in 0..=n {
let u = k as f64 * h;
let u2 = u * u;
let exponent = u2 - eta;
let val = if exponent > 700.0 {
0.0
} else {
2.0 / (1.0 + exponent.exp())
};
let w = if k == 0 || k == n {
1.0
} else if k % 2 == 1 {
4.0
} else {
2.0
};
sum += w * val;
}
sum * h / 3.0
}
fn f_minus_half_sommerfeld(eta: f64) -> f64 {
let eta2 = eta * eta;
let pi2 = PI * PI;
let pi4 = pi2 * pi2;
2.0 * eta.sqrt() * (1.0 - pi2 / (24.0 * eta2) - 7.0 * pi4 / (384.0 * eta2 * eta2))
}
pub fn joyce_dixon_eta(u: f64) -> f64 {
let target = u * SQRT_PI_OVER_2;
let mut eta = if u <= 8.0 {
let a1 = 1.0_f64 / 8.0_f64.sqrt(); let a2 = -4.9587e-3_f64;
let a3 = 1.483e-4_f64;
let a4 = -2.13e-6_f64;
let ln_u = u.ln();
ln_u + a1 * u + a2 * u * u + a3 * u.powi(3) + a4 * u.powi(4)
} else {
(1.5 * target).powf(2.0 / 3.0)
};
for _ in 0..20 {
let f = f_half(eta) - target;
let df = 0.5 * f_minus_half(eta);
if df.abs() < 1e-300 {
break;
}
let step = f / df;
eta -= step;
if step.abs() < 1e-12 * (eta.abs() + 1.0) {
break;
}
}
eta
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn f_half_zero_eta_matches_known_value() {
let val = f_half(0.0);
let expected = 0.678_093_895_2_f64;
assert!(
(val - expected).abs() < 1e-6,
"F_1/2(0) = {val}, expected {expected}"
);
}
#[test]
fn f_minus_half_zero_eta_matches_known_value() {
let val = f_minus_half(0.0);
let expected = 1.072_154_930_0_f64;
assert!(
(val - expected).abs() < 1e-6,
"F_{{-1/2}}(0) = {val}, expected {expected}"
);
}
#[test]
fn derivative_identity_holds() {
let h = 1e-4_f64;
for eta in [-5.0_f64, -1.0, 0.0, 2.0, 4.0] {
let d_num = (f_half(eta + h) - f_half(eta - h)) / (2.0 * h);
let expected = 0.5 * f_minus_half(eta);
let relerr = (d_num - expected).abs() / expected.abs().max(1e-10);
assert!(
relerr < 1e-5,
"Derivative identity failed at η={eta}: numerical={d_num}, (1/2)·F_{{-1/2}}={expected}, relerr={relerr}"
);
}
}
}