use num_dual::DualNum;
pub(crate) fn att_a_cnst(omega: f64) -> f64 {
(4.0 / (9.0 * std::f64::consts::PI)).cbrt() * omega / 2.0
}
pub(crate) fn erf_dual<N: DualNum<f64> + Copy>(x: N) -> N {
let x0 = x.re();
let d = x - N::from(x0); let e0 = libm::erf(x0);
let d1 = 2.0 / std::f64::consts::PI.sqrt() * (-x0 * x0).exp();
let d2 = -2.0 * x0 * d1;
N::from(e0) + d * d1 + d * d * (0.5 * d2)
}
const A_CUTOFF: f64 = 1.35;
const LARGE_A_COEFFS: [f64; 8] = [
1.0 / 36.0,
-1.0 / 960.0,
1.0 / 26880.0,
-1.0 / 829440.0,
1.0 / 28385280.0,
-1.0 / 1073479680.0,
1.0 / 44590694400.0,
-1.0 / 2.0214448128e13,
];
pub(crate) fn attenuation_erf<N: DualNum<f64> + Copy>(a: N) -> N {
if a.re() >= A_CUTOFF {
let b = if a.re() > A_CUTOFF {
a
} else {
N::from(A_CUTOFF)
};
let inv2 = (b * b).recip();
let mut p = N::from(LARGE_A_COEFFS[7]);
for &c in LARGE_A_COEFFS[..7].iter().rev() {
p = p * inv2 + N::from(c);
}
p * inv2
} else {
let a2 = a * a;
let m = (-(a2 * 4.0).recip()).exp_m1(); let inner = m - a2 * m * 2.0 - 0.5; let bracket = erf_dual((a * 2.0).recip()) * std::f64::consts::PI.sqrt() + a * inner * 2.0;
N::from(1.0) - a * bracket * (8.0 / 3.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_dual::{first_derivative, second_derivative};
#[test]
fn branches_agree_at_cutoff_and_are_finite() {
let small = {
let a = A_CUTOFF - 1e-12;
attenuation_erf(a)
};
let large = attenuation_erf(A_CUTOFF + 1e-12);
assert!(
(small - large).abs() < 1e-11,
"seam mismatch: {small} vs {large}"
);
for &a in &[
1e-6, 1e-3, 0.1, 0.5, 1.0, 1.349, 1.35, 1.351, 2.0, 10.0, 1e3,
] {
let (v, d1, d2) = second_derivative(attenuation_erf, a);
assert!(v.is_finite() && d1.is_finite() && d2.is_finite(), "a={a}");
assert!(
(0.0..=1.0 + 1e-12).contains(&v),
"F({a}) = {v} out of [0,1]"
);
}
}
#[test]
fn limits() {
let a = 1e-8;
let want = 1.0 - 8.0 / 3.0 * a * std::f64::consts::PI.sqrt();
assert!((attenuation_erf(a) - want).abs() < 1e-13);
let a = 1e4_f64;
let want = 1.0 / (36.0 * a * a) - 1.0 / (960.0 * a.powi(4));
let got = attenuation_erf(a);
assert!((got - want).abs() <= 1e-12 * want.abs(), "{got} vs {want}");
}
#[test]
fn erf_dual_matches_value_and_derivative() {
for &x in &[-2.0, -0.5, 0.0, 0.3, 1.0, 4.0] {
let (v, d) = first_derivative(erf_dual, x);
assert_eq!(v, libm::erf(x));
let want = 2.0 / std::f64::consts::PI.sqrt() * (-x * x).exp();
assert!((d - want).abs() <= 1e-15, "erf'({x}): {d} vs {want}");
}
}
#[test]
fn derivative_matches_finite_difference() {
for &a in &[0.05, 0.3, 0.9, 1.2, 1.5, 3.0, 20.0] {
let (_, d1) = first_derivative(attenuation_erf, a);
let h = 1e-6 * a;
let fd = (attenuation_erf(a + h) - attenuation_erf(a - h)) / (2.0 * h);
assert!(
(d1 - fd).abs() <= 1e-6 * d1.abs().max(1e-10),
"a={a}: {d1} vs {fd}"
);
}
}
}