use crate::ball::ArbBall;
use crate::kernel::{ExprId, ExprPool};
use crate::primitive::Primitive;
use rug::Float;
pub const ASYMPTOTIC_FROM: f64 = 6.0;
const F64_PREC: u32 = 96;
const MAX_TERMS: usize = 400;
pub fn fresnel_s(x: f64) -> Option<f64> {
fresnel_pair_f64(x).map(|(s, _)| s)
}
pub fn fresnel_c(x: f64) -> Option<f64> {
fresnel_pair_f64(x).map(|(_, c)| c)
}
fn fresnel_pair_f64(x: f64) -> Option<(f64, f64)> {
if !x.is_finite() {
return None;
}
let (s, c) = fresnel_pair_ball(&Float::with_val(F64_PREC, x), F64_PREC)?;
Some((s.mid.to_f64(), c.mid.to_f64()))
}
pub fn fresnel_pair_ball(x: &Float, prec: u32) -> Option<(ArbBall, ArbBall)> {
if !x.is_finite() {
return None;
}
let work = x.prec().max(prec);
let neg = x.is_sign_negative();
let ax = Float::with_val(work, x.abs_ref());
let (mut s, mut c) = if ax.to_f64() < ASYMPTOTIC_FROM {
fresnel_series_ball(&ax, prec)?
} else {
fresnel_asymptotic_ball(&ax, prec)?
};
if neg {
s.mid = -s.mid;
c.mid = -c.mid;
}
Some((s, c))
}
fn ball_from(value: &Float, abs_err: &Float, prec: u32) -> ArbBall {
let work = value.prec().max(prec) + 32;
let mid = Float::with_val(prec, value);
let trunc = Float::with_val(prec, Float::with_val(work, value - &mid).abs());
let mut rad = Float::with_val(prec, abs_err.abs_ref()) + trunc;
let mut bump = Float::with_val(prec, mid.abs_ref());
bump >>= prec.saturating_sub(2);
rad += bump;
ArbBall { mid, rad, prec }
}
fn fresnel_series_ball(x: &Float, prec: u32) -> Option<(ArbBall, ArbBall)> {
let xf = x.to_f64();
let cancel = (2.3 * xf * xf).ceil().max(0.0) as u32;
let w = prec + 64 + cancel;
let pi = Float::with_val(w, rug::float::Constant::Pi);
let x2 = Float::with_val(w, x * x);
let a = Float::with_val(w, Float::with_val(w, &pi * &x2) / 2u32);
let a2 = Float::with_val(w, &a * &a);
let mut u = Float::with_val(w, 1); let mut v = Float::with_val(w, &a); let mut sum_c = Float::new(w);
let mut sum_s = Float::new(w);
let mut biggest = Float::with_val(w, 1);
let mut used = 0usize;
let mut trunc = Float::with_val(w, f64::INFINITY);
let tiny_threshold = Float::with_val(w, 1.0) >> (w - 8);
for n in 0..MAX_TERMS {
used = n + 1;
let nn = n as u32;
let tc = Float::with_val(w, &u / (4 * nn + 1));
let ts = Float::with_val(w, &v / (4 * nn + 3));
if n % 2 == 0 {
sum_c += &tc;
sum_s += &ts;
} else {
sum_c -= &tc;
sum_s -= &ts;
}
let mag = Float::with_val(w, u.abs_ref()).max(&Float::with_val(w, v.abs_ref()));
if mag > biggest {
biggest = mag;
}
let du = Float::with_val(w, f64::from(2 * nn + 1) * f64::from(2 * nn + 2));
let dv = Float::with_val(w, f64::from(2 * nn + 2) * f64::from(2 * nn + 3));
u *= &a2;
u /= &du;
v *= &a2;
v /= &dv;
let next = Float::with_val(w, u.abs_ref()).max(&Float::with_val(w, v.abs_ref()));
if a2 < du && next < tiny_threshold {
trunc = next;
break;
}
}
if !trunc.is_finite() {
return None;
}
let mut round = Float::with_val(w, &biggest * Float::with_val(w, (4 * used * used) as f64));
round >>= w;
let scale = Float::with_val(w, x.abs_ref()).max(&Float::with_val(w, 1.0));
let err = Float::with_val(w, Float::with_val(w, &trunc + &round) * &scale);
let s_val = Float::with_val(w, x * &sum_s);
let c_val = Float::with_val(w, x * &sum_c);
Some((ball_from(&s_val, &err, prec), ball_from(&c_val, &err, prec)))
}
fn fresnel_asymptotic_ball(x: &Float, prec: u32) -> Option<(ArbBall, ArbBall)> {
let xf = x.to_f64();
let grow = if xf > 1.0 {
xf.log2().ceil().max(0.0) as u32
} else {
0
};
let w = prec + 96 + grow;
let pi = Float::with_val(w, rug::float::Constant::Pi);
let x2 = Float::with_val(w, x * x);
let pix2 = Float::with_val(w, &pi * &x2);
let theta = Float::with_val(w, &pix2 / 2u32);
let (sin_t, cos_t) = Float::with_val(w, &theta).sin_cos(Float::new(w));
let mut t = Float::with_val(w, Float::with_val(w, 1) / x); let mut sum_a = Float::new(w); let mut sum_b = Float::new(w); let mut err_a = Float::with_val(w, f64::INFINITY);
let mut err_b = Float::with_val(w, f64::INFINITY);
let mut used = 0usize;
for n in 0..MAX_TERMS {
used = n + 1;
let m = n / 2;
let plus = m % 2 == 0;
if n % 2 == 0 {
if plus {
sum_a += &t;
} else {
sum_a -= &t;
}
} else if plus {
sum_b += &t;
} else {
sum_b -= &t;
}
let step = Float::with_val(w, f64::from(2 * (n as u32) + 1));
let next = Float::with_val(w, Float::with_val(w, &t * &step) / &pix2);
if next >= t || n + 1 == MAX_TERMS {
let step2 = Float::with_val(w, f64::from(2 * (n as u32) + 3));
let after = Float::with_val(w, Float::with_val(w, &next * &step2) / &pix2);
if n % 2 == 0 {
err_b = next;
err_a = after;
} else {
err_a = next;
err_b = after;
}
break;
}
t = next;
}
if !err_a.is_finite() || !err_b.is_finite() {
return None;
}
let f = Float::with_val(w, &sum_a / &pi);
let g = Float::with_val(w, &sum_b / &pi);
let ef = Float::with_val(w, &err_a / &pi);
let eg = Float::with_val(w, &err_b / &pi);
let half = Float::with_val(w, 0.5);
let c_val = Float::with_val(
w,
&half
+ Float::with_val(
w,
Float::with_val(w, &f * &sin_t) - Float::with_val(w, &g * &cos_t),
),
);
let s_val = Float::with_val(
w,
&half
- Float::with_val(
w,
Float::with_val(w, &f * &cos_t) + Float::with_val(w, &g * &sin_t),
),
);
let mut dtheta = Float::with_val(w, &theta);
dtheta >>= w;
let amp = Float::with_val(
w,
Float::with_val(w, f.abs_ref()) + Float::with_val(w, g.abs_ref()),
);
let mut round = Float::with_val(w, (4 * used * used) as f64);
round >>= w;
let err = Float::with_val(
w,
Float::with_val(w, &ef + &eg)
+ Float::with_val(w, Float::with_val(w, & * &dtheta) + round),
);
Some((ball_from(&s_val, &err, prec), ball_from(&c_val, &err, prec)))
}
fn half_pi(pool: &ExprPool) -> ExprId {
pool.float(std::f64::consts::FRAC_PI_2, 53)
}
fn phase(trig: &'static str, x: ExprId, pool: &ExprPool) -> ExprId {
let x2 = pool.pow(x, pool.integer(2_i32));
let arg = pool.mul(vec![half_pi(pool), x2]);
pool.func(trig, vec![arg])
}
fn unary_ball(args: &[ArbBall]) -> Option<&ArbBall> {
match args {
[only] => Some(only),
_ => None,
}
}
pub struct FresnelSPrimitive;
impl Primitive for FresnelSPrimitive {
fn name(&self) -> &'static str {
"fresnels"
}
fn pretty(&self, args: &[ExprId], pool: &ExprPool) -> String {
format!("S({})", pool.display(args[0]))
}
fn diff_forward(&self, args: &[ExprId], wrt: ExprId, pool: &ExprPool) -> Option<ExprId> {
if args.len() != 1 {
return None;
}
let x = args[0];
let dx = crate::diff::diff(x, wrt, pool).ok()?.value;
Some(pool.mul(vec![phase("sin", x, pool), dx]))
}
fn diff_reverse(&self, args: &[ExprId], cotan: ExprId, pool: &ExprPool) -> Option<Vec<ExprId>> {
if args.len() != 1 {
return None;
}
Some(vec![pool.mul(vec![cotan, phase("sin", args[0], pool)])])
}
fn numeric_f64(&self, args: &[f64]) -> Option<f64> {
match args {
[x] => fresnel_s(*x),
_ => None,
}
}
fn numeric_ball(&self, args: &[ArbBall]) -> Option<ArbBall> {
unary_ball(args)?.fresnel_s()
}
}
pub struct FresnelCPrimitive;
impl Primitive for FresnelCPrimitive {
fn name(&self) -> &'static str {
"fresnelc"
}
fn pretty(&self, args: &[ExprId], pool: &ExprPool) -> String {
format!("C({})", pool.display(args[0]))
}
fn diff_forward(&self, args: &[ExprId], wrt: ExprId, pool: &ExprPool) -> Option<ExprId> {
if args.len() != 1 {
return None;
}
let x = args[0];
let dx = crate::diff::diff(x, wrt, pool).ok()?.value;
Some(pool.mul(vec![phase("cos", x, pool), dx]))
}
fn diff_reverse(&self, args: &[ExprId], cotan: ExprId, pool: &ExprPool) -> Option<Vec<ExprId>> {
if args.len() != 1 {
return None;
}
Some(vec![pool.mul(vec![cotan, phase("cos", args[0], pool)])])
}
fn numeric_f64(&self, args: &[f64]) -> Option<f64> {
match args {
[x] => fresnel_c(*x),
_ => None,
}
}
fn numeric_ball(&self, args: &[ArbBall]) -> Option<ArbBall> {
unary_ball(args)?.fresnel_c()
}
}
#[cfg(test)]
mod tests {
use super::*;
const REFERENCE: &[(f64, f64, f64)] = &[
(0.0, 0.0, 0.0),
(0.25, 0.008_175_600_235_777_757, 0.249_759_150_356_543_2),
(0.5, 0.064_732_432_859_999_29, 0.492_344_225_871_446_4),
(1.0, 0.438_259_147_390_354_7, 0.779_893_400_376_823),
(1.5, 0.697_504_960_082_093, 0.445_261_176_039_821_57),
(2.0, 0.343_415_678_363_698_24, 0.488_253_406_075_340_73),
(2.5, 0.619_181_755_819_592_9, 0.457_413_009_641_777_06),
(3.0, 0.496_312_998_967_375, 0.605_720_789_297_685_7),
(4.0, 0.420_515_754_246_928_44, 0.498_426_033_038_177_6),
(5.0, 0.499_191_381_917_116_87, 0.563_631_188_704_012_2),
(8.0, 0.460_214_214_393_014_46, 0.499_802_180_377_197_15),
(20.0, 0.484_084_535_925_953_9, 0.499_987_334_972_344_4),
(100.0, 0.496_816_901_147_837_55, 0.499_999_898_678_817_9),
];
fn rel(got: f64, want: f64) -> f64 {
if want == 0.0 {
got.abs()
} else {
(got - want).abs() / want.abs()
}
}
#[test]
fn matches_published_reference_values() {
for &(x, s, c) in REFERENCE {
let gs = fresnel_s(x).unwrap();
let gc = fresnel_c(x).unwrap();
assert!(rel(gs, s) < 1e-14, "S({x}): got {gs}, want {s}");
assert!(rel(gc, c) < 1e-14, "C({x}): got {gc}, want {c}");
}
}
#[test]
fn half_at_infinity_pins_the_convention() {
for x in [1.0e3_f64, 1.0e6, 1.0e9] {
assert!((fresnel_s(x).unwrap() - 0.5).abs() < 1.0 / x);
assert!((fresnel_c(x).unwrap() - 0.5).abs() < 1.0 / x);
}
assert!((fresnel_s(1.0e9).unwrap() - 0.5).abs() < 1e-9);
assert!((fresnel_c(1.0e9).unwrap() - 0.5).abs() < 1e-9);
}
#[test]
fn both_functions_are_odd() {
for x in [0.3_f64, 1.7, 4.5, 6.5, 30.0] {
assert_eq!(fresnel_s(-x).unwrap(), -fresnel_s(x).unwrap());
assert_eq!(fresnel_c(-x).unwrap(), -fresnel_c(x).unwrap());
}
}
#[test]
fn the_two_regimes_agree_across_the_switchover() {
let e = ASYMPTOTIC_FROM;
for d in [1e-9_f64, 1e-7, 1e-5, 1e-3] {
let (s_lo, c_lo) = (fresnel_s(e - d).unwrap(), fresnel_c(e - d).unwrap());
let (s_hi, c_hi) = (fresnel_s(e + d).unwrap(), fresnel_c(e + d).unwrap());
assert!((s_hi - s_lo).abs() <= 2.0 * d + 1e-14, "S seam at ±{d}");
assert!((c_hi - c_lo).abs() <= 2.0 * d + 1e-14, "C seam at ±{d}");
}
let series = fresnel_series_ball(&Float::with_val(F64_PREC, ASYMPTOTIC_FROM), F64_PREC)
.expect("series at the seam");
let asym = fresnel_asymptotic_ball(&Float::with_val(F64_PREC, ASYMPTOTIC_FROM), F64_PREC)
.expect("asymptotic at the seam");
assert!(
(series.0.mid.to_f64() - asym.0.mid.to_f64()).abs() < 1e-15,
"S: series {} vs asymptotic {}",
series.0.mid.to_f64(),
asym.0.mid.to_f64()
);
assert!(
(series.1.mid.to_f64() - asym.1.mid.to_f64()).abs() < 1e-15,
"C: series {} vs asymptotic {}",
series.1.mid.to_f64(),
asym.1.mid.to_f64()
);
}
#[test]
fn the_enclosures_enclose() {
for &(x, s, c) in REFERENCE {
let (hs, hc) = fresnel_pair_ball(&Float::with_val(200, x), 200).unwrap();
assert!((hs.mid.to_f64() - s).abs() < 1e-15, "S({x}) vs table");
assert!((hc.mid.to_f64() - c).abs() < 1e-15, "C({x}) vs table");
assert!(hs.rad.to_f64() < 1e-30, "S({x}) radius {}", hs.rad);
assert!(hc.rad.to_f64() < 1e-30, "C({x}) radius {}", hc.rad);
for prec in [32_u32, 53, 96] {
let (ls, lc) = fresnel_pair_ball(&Float::with_val(prec, x), prec).unwrap();
assert!(
ls.lo() <= hs.mid && hs.mid <= ls.hi(),
"S({x}) at {prec} bits: {} outside {ls}",
hs.mid
);
assert!(
lc.lo() <= hc.mid && hc.mid <= lc.hi(),
"C({x}) at {prec} bits: {} outside {lc}",
hc.mid
);
}
}
}
#[test]
fn the_derivatives_are_the_integrands() {
let h = 1e-5_f64;
for x in [0.3_f64, 1.0, 2.4, 5.0] {
let ds = (fresnel_s(x + h).unwrap() - fresnel_s(x - h).unwrap()) / (2.0 * h);
let dc = (fresnel_c(x + h).unwrap() - fresnel_c(x - h).unwrap()) / (2.0 * h);
let t = std::f64::consts::FRAC_PI_2 * x * x;
assert!((ds - t.sin()).abs() < 1e-6, "S′({x}): {ds} vs {}", t.sin());
assert!((dc - t.cos()).abs() < 1e-6, "C′({x}): {dc} vs {}", t.cos());
}
}
#[test]
fn non_finite_arguments_are_declined() {
for x in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(fresnel_s(x).is_none());
assert!(fresnel_c(x).is_none());
}
}
}