use std::f64::consts::PI;
const MAX_SERIES_TERMS: usize = 100;
const EPSILON: f64 = 1e-15;
const AUXILIARY_THRESHOLD: f64 = 4.0;
pub fn fresnel_s_scalar(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let ax = x.abs();
if ax < AUXILIARY_THRESHOLD {
sign * fresnel_s_series(ax)
} else {
sign * fresnel_s_auxiliary(ax)
}
}
pub fn fresnel_c_scalar(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
if x == 0.0 {
return 0.0;
}
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let ax = x.abs();
if ax < AUXILIARY_THRESHOLD {
sign * fresnel_c_series(ax)
} else {
sign * fresnel_c_auxiliary(ax)
}
}
fn fresnel_s_series(x: f64) -> f64 {
let pi_2 = PI / 2.0;
let x2 = x * x;
let x4 = x2 * x2;
let mut sum = x * x2 * pi_2 / 3.0; let mut term = sum;
let mut sign = -1.0;
for n in 1..MAX_SERIES_TERMS {
let n2 = 2 * n;
let n4 = 4 * n;
term *= pi_2 * pi_2 * x4
/ (((n2 * (n2 + 1)) as f64) * ((n4 * (n4 + 1) * (n4 + 2) * (n4 + 3)) as f64));
term *= (((n4 - 1) * (n4 - 2) * (n4 - 3)) as f64) / (((n2 - 1) * (n2)) as f64);
sum += sign * term;
sign = -sign;
if term.abs() < EPSILON * sum.abs() {
break;
}
}
fresnel_s_series_direct(x)
}
fn fresnel_s_series_direct(x: f64) -> f64 {
let pi_2 = PI / 2.0;
let t = pi_2 * x * x;
let mut sum = 0.0;
let mut term = x * t / 3.0; sum += term;
for n in 1..MAX_SERIES_TERMS {
let n2 = 2 * n;
let n4 = 4 * n;
term *= -t * t / ((n2 * (n2 + 1)) as f64);
term *= ((n4 - 1) as f64) / ((n4 + 3) as f64);
sum += term;
if term.abs() < EPSILON * sum.abs() {
break;
}
}
sum
}
fn fresnel_c_series(x: f64) -> f64 {
let pi_2 = PI / 2.0;
let t = pi_2 * x * x;
let mut sum = 0.0;
let mut term = x; sum += term;
for n in 1..MAX_SERIES_TERMS {
let n2 = 2 * n;
let n4 = 4 * n;
term *= -t * t / (((n2 - 1) * n2) as f64);
term *= ((n4 - 3) as f64) / ((n4 + 1) as f64);
sum += term;
if term.abs() < EPSILON * sum.abs() {
break;
}
}
sum
}
fn fresnel_s_auxiliary(x: f64) -> f64 {
let (f, g) = auxiliary_fg(x);
let t = PI * x * x / 2.0;
0.5 - f * t.cos() - g * t.sin()
}
fn fresnel_c_auxiliary(x: f64) -> f64 {
let (f, g) = auxiliary_fg(x);
let t = PI * x * x / 2.0;
0.5 + f * t.sin() - g * t.cos()
}
fn auxiliary_fg(x: f64) -> (f64, f64) {
let pix = PI * x;
let pix2 = pix * pix;
let z = 1.0 / pix2;
let f_num = 1.0 + z * (-1.0 / 4.0 + z * 3.0 / 64.0);
let f_den = 1.0 + z * (3.0 / 4.0 + z * 15.0 / 64.0);
let f = f_num / (pix * f_den);
let g_num = 1.0 + z * (-5.0 / 4.0 + z * 35.0 / 64.0);
let g_den = 1.0 + z * (7.0 / 4.0 + z * 63.0 / 64.0);
let g = g_num / (pix2 * x * g_den);
(f, g)
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 1e-7;
fn assert_close(a: f64, b: f64, tol: f64, msg: &str) {
let diff = (a - b).abs();
assert!(
diff < tol || (a.is_nan() && b.is_nan()),
"{}: expected {}, got {}, diff {}",
msg,
b,
a,
diff
);
}
#[test]
fn test_fresnel_s_special_values() {
assert_close(fresnel_s_scalar(0.0), 0.0, TOL, "S(0)");
assert_close(fresnel_s_scalar(1.0), 0.4382591473903548, TOL, "S(1)");
assert_close(fresnel_s_scalar(2.0), 0.3434156783636982, TOL, "S(2)");
assert_close(fresnel_s_scalar(10.0), 0.5, 0.05, "S(10)");
assert_close(fresnel_s_scalar(100.0), 0.5, 0.01, "S(100)");
}
#[test]
fn test_fresnel_c_special_values() {
assert_close(fresnel_c_scalar(0.0), 0.0, TOL, "C(0)");
assert_close(fresnel_c_scalar(1.0), 0.7798934003768228, TOL, "C(1)");
assert_close(fresnel_c_scalar(2.0), 0.4882534060753408, TOL, "C(2)");
assert_close(fresnel_c_scalar(10.0), 0.5, 0.05, "C(10)");
assert_close(fresnel_c_scalar(100.0), 0.5, 0.01, "C(100)");
}
#[test]
fn test_fresnel_odd_symmetry() {
for &x in &[0.5, 1.0, 2.0, 5.0] {
assert_close(
fresnel_s_scalar(-x),
-fresnel_s_scalar(x),
TOL,
&format!("S(-{}) = -S({})", x, x),
);
assert_close(
fresnel_c_scalar(-x),
-fresnel_c_scalar(x),
TOL,
&format!("C(-{}) = -C({})", x, x),
);
}
}
#[test]
fn test_fresnel_nan() {
assert!(fresnel_s_scalar(f64::NAN).is_nan());
assert!(fresnel_c_scalar(f64::NAN).is_nan());
}
#[test]
fn test_fresnel_cornu_spiral() {
let (c, s) = (fresnel_c_scalar(100.0), fresnel_s_scalar(100.0));
let dist = ((c - 0.5).powi(2) + (s - 0.5).powi(2)).sqrt();
assert!(dist < 0.02, "Should converge to (0.5, 0.5)");
}
#[test]
fn test_fresnel_series_auxiliary_continuity() {
let x = AUXILIARY_THRESHOLD;
let s_series = fresnel_s_series_direct(x);
let s_aux = fresnel_s_auxiliary(x);
assert!(
(s_series - s_aux).abs() < 0.05,
"S series-auxiliary continuity: series={}, aux={}",
s_series,
s_aux
);
let c_at = fresnel_c_scalar(x);
assert!(c_at > 0.0 && c_at < 1.0, "C at threshold in valid range");
}
}