use libm::j1f;
const J1_ZERO1: f32 = 3.831_706;
const J1_ZERO2: f32 = 7.015_587;
pub(crate) struct AiryPattern {
lut: Vec<f32>,
}
#[allow(
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_possible_truncation
)]
impl AiryPattern {
pub(crate) const SIZE_FACTOR: f32 = J1_ZERO2 / J1_ZERO1;
const LUT_SIZE: usize = 1024;
const LUT_PADDING_LEN: usize = Self::LUT_SIZE;
const LUT_SIZE_FP: f32 = Self::LUT_SIZE as f32;
const INDEX_SCALE: f32 = Self::LUT_SIZE_FP / Self::SIZE_FACTOR;
#[must_use]
pub(crate) fn new() -> Self {
let lut_fn = |i| {
if i == 0 {
1.0
} else if i >= Self::LUT_SIZE {
0.0
} else {
let x = (i as f32) * J1_ZERO2 / Self::LUT_SIZE_FP;
let j1nc = 2.0 * j1f(x) / x;
j1nc * j1nc
}
};
let len = Self::LUT_SIZE + Self::LUT_PADDING_LEN;
let lut = (0..len).map(lut_fn).collect();
AiryPattern { lut }
}
#[must_use]
pub(crate) fn eval(&self, x: f32) -> f32 {
let i = (x * Self::INDEX_SCALE + 0.5) as usize;
self.lut.get(i).copied().unwrap_or(0.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_lut() {
let airy = AiryPattern::new();
let f0 = airy.eval(0.0);
assert!((f0 - 1.0).abs() < 1e-7, "F(0) = {f0}");
let z1 = 1.0;
let f1 = airy.eval(z1);
assert!(f1.abs() < 2e-7, "F({z1}) = {f1}");
let z1dx = z1 - 2e-3;
let f1dy = airy.eval(z1dx);
assert!(f1dy.abs() < 4e-6, "F({z1dx}) = {f1dy}");
let z2 = J1_ZERO2 / J1_ZERO1;
let f2 = airy.eval(z2);
assert!(f2.abs() < 1e-7, "F({z2}) = {f2}");
let z2dx = z2 - 1e-3;
let f2dy = airy.eval(z2dx);
assert!(f2dy.abs() < 4e-7, "F({z2dx}) = {f2dy}");
let z3 = 1.5 * z2;
let f3 = airy.eval(z3);
assert!(f3.abs() < 1e-7);
let z4 = 3.5 * z2;
let f4 = airy.eval(z4);
assert!(f4.abs() < 1e-7);
}
}