use num_complex::Complex;
use crate::machine::BesselFloat;
use crate::types::{HankelKind, Scaling};
use crate::utils::{self, Workspace, cospi, sinpi};
use crate::{besh, besi, besj, besk};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum AxisFn {
Hankel(HankelKind),
K,
Y,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Row {
H1Pos,
H2Pos,
H1Neg,
H2Neg,
YNeg,
KPosI,
KNegI,
H2PosI,
H1NegI,
}
impl Row {
fn ik(self) -> bool {
matches!(self, Row::H2PosI | Row::H1NegI)
}
}
const RATIO_JY: f64 = 0.1;
const RATIO_IK: f64 = 1e-2;
const COEF_Y_NEG: f64 = 0.1;
const RATIO_J_NEG: f64 = 1e-3;
const ORDER_NEG: f64 = 18.0;
fn row<T: BesselFloat>(f: AxisFn, z: Complex<T>, scaling: Scaling) -> Option<(Row, T)> {
let zero = T::zero();
let unscaled = scaling == Scaling::Unscaled;
let first = AxisFn::Hankel(HankelKind::First);
let second = AxisFn::Hankel(HankelKind::Second);
let row = if z.im == zero && z.re > zero {
match f {
_ if !unscaled => return None,
_ if f == first => Row::H1Pos,
_ if f == second => Row::H2Pos,
_ => return None,
}
} else if z.im == zero && z.re < zero {
match f {
AxisFn::Y => Row::YNeg,
_ if !unscaled => return None,
_ if f == first => Row::H1Neg,
_ if f == second => Row::H2Neg,
_ => return None,
}
} else if z.re == zero && z.im > zero {
match f {
_ if f == second => Row::H2PosI,
AxisFn::K if unscaled => Row::KPosI,
_ => return None,
}
} else if z.re == zero && z.im < zero {
match f {
_ if f == first => Row::H1NegI,
AxisFn::K if unscaled => Row::KNegI,
_ => return None,
}
} else {
return None;
};
let x = if z.im == zero { z.re.abs() } else { z.im.abs() };
Some((row, x))
}
#[inline]
fn two_sum<T: BesselFloat>(a: T, b: T) -> (T, T) {
let s = a + b;
let bb = s - a;
(s, (a - (s - bb)) + (b - bb))
}
#[derive(Debug, Clone, Copy)]
struct Order<T> {
m: T,
e: T,
}
impl<T: BesselFloat> Order<T> {
fn exact(m: T) -> Self {
Order { m, e: T::zero() }
}
fn sum(base: T, shift: usize) -> Self {
let (m, e) = two_sum(base, T::from_f64(shift as f64));
Order { m, e }
}
fn sincospi(self, q2: u8) -> (T, T) {
let m = self.m;
let two = T::from_f64(2.0);
let (s, c) = match q2 {
1 => (sinpi(m / two), cospi(m / two)),
2 => (sinpi(m), cospi(m)),
3 => sincospi_three_halves(m),
_ => (sinpi(two * m), cospi(two * m)),
};
if self.e == T::zero() {
return (s, c);
}
let pe = T::from_f64(core::f64::consts::PI * f64::from(q2) / 2.0) * self.e;
(s + pe * c, c - pe * s)
}
}
fn small_component<T: BesselFloat>(
row: Row,
neg: bool,
x: T,
ord: Order<T>,
v: Complex<T>,
) -> Option<usize> {
let m = ord.m;
let (a, b) = (v.re.abs(), v.im.abs());
if !(a.is_finite() && b.is_finite()) {
return None;
}
let (k, small, large) = if a < b { (0, a, b) } else { (1, b, a) };
if large == T::zero() {
return None;
}
let ratio = if row.ik() { RATIO_IK } else { RATIO_JY };
if small >= T::from_f64(ratio) * large {
return None;
}
if !row.ik() && x >= m {
if !matches!(row, Row::YNeg | Row::H2Neg) {
return None;
}
let (ca, cb) = coefficients(row, neg, ord, k);
let y_alone = m < T::from_f64(ORDER_NEG) && ca.abs() < T::from_f64(RATIO_J_NEG) * cb.abs();
let small_coef =
row == Row::YNeg && k == 0 && ca.abs() + cb.abs() < T::from_f64(COEF_Y_NEG);
if !(y_alone || small_coef) {
return None;
}
}
Some(k)
}
fn sincospi_three_halves<T: BesselFloat>(m: T) -> (T, T) {
let r = m % T::from_f64(4.0);
let (s, e) = two_sum(r, r * T::from_f64(0.5));
let pe = T::from_f64(core::f64::consts::PI) * e;
let (sp, cp) = (sinpi(s), cospi(s));
(sp + pe * cp, cp - pe * sp)
}
fn coefficients<T: BesselFloat>(row: Row, neg: bool, ord: Order<T>, k: usize) -> (T, T) {
let one = T::one();
let two = T::from_f64(2.0);
let pi = T::from_f64(core::f64::consts::PI);
let hpi = pi / two;
let tpi = two / pi;
let (ar, ai, br, bi) = match row {
Row::H1Pos | Row::H2Pos => {
let sign = if row == Row::H1Pos { one } else { -one };
if neg {
let (s, c) = ord.sincospi(2);
(c, sign * s, -s, sign * c)
} else {
(one, T::zero(), T::zero(), sign)
}
}
Row::H1Neg => {
if neg {
(-one, T::zero(), T::zero(), one)
} else {
let (s, c) = ord.sincospi(2);
(-c, s, s, c)
}
}
Row::H2Neg => {
if neg {
let (s2, c2) = ord.sincospi(4);
(c2 + two, -s2, -s2, -c2)
} else {
let (s, c) = ord.sincospi(2);
(T::from_f64(3.0) * c, s, -s, -c)
}
}
Row::YNeg => {
let (s, c) = ord.sincospi(2);
if neg {
let half_s2 = ord.sincospi(4).0 / two;
(half_s2, c * c + one, c * c, -half_s2)
} else {
(T::zero(), two * c, c, -s)
}
}
Row::KPosI | Row::KNegI => {
let (sh, ch) = ord.sincospi(1);
let conj = if row == Row::KPosI { one } else { -one };
(-hpi * sh, -conj * hpi * ch, -hpi * ch, conj * hpi * sh)
}
Row::H2PosI | Row::H1NegI => {
let conj = if row == Row::H2PosI { one } else { -one };
let (sh, ch) = ord.sincospi(1);
if neg {
let (s3, c3) = ord.sincospi(3);
(two * ch, -conj * two * sh, tpi * s3, conj * tpi * c3)
} else {
(two * ch, conj * two * sh, tpi * sh, conj * tpi * ch)
}
}
};
if k == 0 { (ar, br) } else { (ai, bi) }
}
fn basis<T: BesselFloat>(row: Row, x: T, m: T, scaling: Scaling) -> Option<(T, T)> {
let zero = T::zero();
let czero = Complex::new(zero, zero);
let z = Complex::new(x, zero);
let mut a = [czero];
let mut b = [czero];
if row.ik() {
besi::zbesi(z, m, scaling, &mut a).ok()?;
besk::zbesk(z, m, scaling, &mut b).ok()?;
Some((a[0].re, scale_k(b[0].re, x, scaling)))
} else {
besj::zbesj(z, m, Scaling::Unscaled, &mut a).ok()?;
besh::zbesh(z, m, HankelKind::First, Scaling::Unscaled, &mut b).ok()?;
Some((a[0].re, b[0].im))
}
}
#[inline]
fn scale_k<T: BesselFloat>(ks: T, x: T, scaling: Scaling) -> T {
if scaling == Scaling::Exponential {
let e = (-x).exp();
ks * e * e
} else {
ks
}
}
#[inline]
fn apply<T: BesselFloat>(
row: Row,
neg: bool,
ord: Order<T>,
k: usize,
a: T,
b: T,
v: &mut Complex<T>,
) {
let (ca, cb) = coefficients(row, neg, ord, k);
let s = ca * a + cb * b;
if k == 0 {
v.re = s;
} else {
v.im = s;
}
}
pub(crate) fn fix_single<T: BesselFloat>(
f: AxisFn,
nu: T,
z: Complex<T>,
scaling: Scaling,
v: Complex<T>,
) -> Complex<T> {
let Some((row, x)) = row(f, z, scaling) else {
return v;
};
fix_value(row, x, nu < T::zero(), Order::exact(nu.abs()), scaling, v)
}
fn fix_value<T: BesselFloat>(
row: Row,
x: T,
neg: bool,
ord: Order<T>,
scaling: Scaling,
v: Complex<T>,
) -> Complex<T> {
let m = ord.m;
let Some(k) = small_component(row, neg, x, ord, v) else {
return v;
};
let ab = if !neg && matches!(row, Row::H1Pos | Row::H2Pos) {
let mut a = [Complex::new(T::zero(), T::zero())];
besj::zbesj(Complex::new(x, T::zero()), m, Scaling::Unscaled, &mut a)
.ok()
.map(|_| (a[0].re, T::zero()))
} else {
basis(row, x, m, scaling)
};
let mut out = v;
if let Some((a, b)) = ab {
apply(row, neg, ord, k, a, b, &mut out);
}
out
}
pub(crate) fn fix_seq<T: BesselFloat>(
f: AxisFn,
nu: T,
z: Complex<T>,
scaling: Scaling,
out: &mut [Complex<T>],
) {
let Some((row, x)) = row(f, z, scaling) else {
return;
};
let n = out.len();
let zero = T::zero();
let nc = if nu < zero {
nu.abs().ceil().to_usize().unwrap_or(n).min(n)
} else {
0
};
let abs_nu = nu.abs();
let pos_base = nu + T::from_f64(nc as f64);
let order = |j: usize| -> Order<T> {
if j < nc {
Order::exact(abs_nu - T::from_f64(j as f64))
} else {
Order::sum(pos_base, j - nc)
}
};
let needs = |j: usize, v: Complex<T>| small_component(row, j < nc, x, order(j), v);
for (lo, hi, neg) in [(0, nc, true), (nc, n, false)] {
let Some(first) = (lo..hi).find(|&j| needs(j, out[j]).is_some()) else {
continue;
};
let last = (first..hi)
.rev()
.find(|&j| needs(j, out[j]).is_some())
.unwrap_or(first);
let len = last - first + 1;
let j_of = if neg { last } else { first };
let j_at = |i: usize| if neg { j_of - i } else { j_of + i };
let j_only = !neg && matches!(row, Row::H1Pos | Row::H2Pos);
utils::with_workspace(Workspace::Stack, 0, |buf: &mut [Complex<T>]| {
let (abuf, bbuf) = buf.split_at_mut(buf.len() / 2);
let chunk = abuf.len();
let mut c0 = 0;
while c0 < len {
let c1 = (c0 + chunk).min(len);
let cl = c1 - c0;
let start = order(j_at(c0)).m;
let zx = Complex::new(x, zero);
let ok = if row.ik() {
besi::zbesi(zx, start, scaling, &mut abuf[..cl]).is_ok()
&& besk::zbesk(zx, start, scaling, &mut bbuf[..cl]).is_ok()
} else {
besj::zbesj(zx, start, Scaling::Unscaled, &mut abuf[..cl]).is_ok()
&& (j_only
|| besh::zbesh(
zx,
start,
HankelKind::First,
Scaling::Unscaled,
&mut bbuf[..cl],
)
.is_ok())
};
for i in 0..cl {
let j = j_at(c0 + i);
let Some(k) = needs(j, out[j]) else {
continue;
};
let ord = order(j);
if ok {
let a = abuf[i].re;
let b = if row.ik() {
scale_k(bbuf[i].re, x, scaling)
} else if j_only {
zero
} else {
bbuf[i].im
};
apply(row, neg, ord, k, a, b, &mut out[j]);
} else {
out[j] = fix_value(row, x, neg, ord, scaling, out[j]);
}
}
c0 = c1;
}
});
}
}
#[inline]
pub(crate) fn real_on_positive_axis<T: BesselFloat>(z: Complex<T>, v: Complex<T>) -> Complex<T> {
if z.im == T::zero() && z.re > T::zero() {
Complex::new(v.re, T::zero())
} else {
v
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sincospi_three_halves_keeps_the_rounding() {
let cases = [
(2.6666666666666665f64, -6.975736996017264e-16, 1.0),
(3.3333333333333335, -6.975736996017264e-16, -1.0),
(67.33333333333333, 2.2322358387255244e-14, -1.0),
];
for (m, s, c) in cases {
let (gs, gc) = sincospi_three_halves(m);
assert!(
((gs - s) / s).abs() < 1e-12,
"sin(1.5π·{m}) = {gs:e}, expected {s:e}"
);
assert!(
(gc - c).abs() < 1e-15,
"cos(1.5π·{m}) = {gc:e}, expected {c:e}"
);
}
for m in [0.3f64, 2.0, 5.5, 1.999999] {
let (gs, gc) = sincospi_three_halves(m);
assert!((gs - sinpi(1.5 * m)).abs() <= 2.0 * f64::EPSILON, "m={m}");
assert!((gc - cospi(1.5 * m)).abs() <= 2.0 * f64::EPSILON, "m={m}");
}
}
}