use num_complex::Complex;
use crate::algo::constants::HPI;
use crate::besi::zbesi;
use crate::besk::zbesk;
use crate::machine::BesselFloat;
use crate::types::{Accuracy, Error, Scaling};
use crate::utils::{Workspace, mul_i, mul_neg_i, with_workspace, zabs};
#[inline]
fn far_k_term<T: BesselFloat>(zv: Complex<T>, tay: T) -> Complex<T> {
let arm = T::from_f64(1.0e3) * T::MACH_TINY;
let m = zv.re.abs().max(zv.im.abs());
if m == T::zero() || m.ln() - tay <= arm.ln() {
return Complex::new(T::zero(), T::zero());
}
let half = (-tay * T::from_f64(0.5)).exp();
zv * half * half
}
#[inline]
pub(crate) fn zbesy<T: BesselFloat>(
z: Complex<T>,
fnu: T,
scaling: Scaling,
y: &mut [Complex<T>],
) -> Result<(usize, Accuracy), Error> {
zbesy_with(z, fnu, scaling, y, Workspace::Stack)
}
#[inline]
pub(crate) fn zbesy_with<T: BesselFloat>(
z: Complex<T>,
fnu: T,
scaling: Scaling,
y: &mut [Complex<T>],
ws: Workspace<'_, T>,
) -> Result<(usize, Accuracy), Error> {
let n = y.len();
let zero = T::zero();
let one = T::one();
let czero = Complex::new(zero, zero);
let hpi_t = T::from_f64(HPI);
y.fill(czero);
if n < 1 {
return Err(Error::InvalidInput);
}
if fnu < zero {
return Err(Error::InvalidInput);
}
if z == czero {
return Err(Error::InvalidInput);
}
if !z.re.is_finite() || !z.im.is_finite() || !fnu.is_finite() {
return Err(Error::InvalidInput);
}
let az = zabs(z);
let fn_val = fnu + T::from_f64((n - 1) as f64);
let aa_tol = T::from_f64(0.5) / T::tol();
let bb = T::from_f64(2147483647.0 * 0.5);
let aa = aa_tol.min(bb);
if az > aa || fn_val > aa {
return Err(Error::TotalPrecisionLoss);
}
let aa_sqrt = aa.sqrt();
let status = if az > aa_sqrt || fn_val > aa_sqrt {
Accuracy::Reduced
} else {
Accuracy::Normal
};
let zn = Complex::new(z.im.abs(), -z.re);
let ifnu = fnu.to_i32().unwrap();
let ffnu = fnu - T::from_f64(ifnu as f64);
let arg = hpi_t * ffnu;
let mut csgn = Complex::new(arg.cos(), arg.sin());
csgn = match (ifnu % 4) as usize {
0 => csgn,
1 => mul_i(csgn),
2 => -csgn,
_ => mul_neg_i(csgn),
};
let rhpi = one / hpi_t;
let mut cspn = csgn.conj() * rhpi;
csgn = mul_i(csgn);
if n == 1 {
let mut i_buf = [czero];
let mut k_buf = [czero];
let (nz1, _) = zbesi(zn, fnu, scaling, &mut i_buf)?;
let (nz2, _) = zbesk(zn, fnu, scaling, &mut k_buf)?;
let nz = nz1.min(nz2);
if scaling == Scaling::Unscaled {
let cy_val = csgn * i_buf[0] - cspn * k_buf[0];
y[0] = if z.im < zero { cy_val.conj() } else { cy_val };
return Ok((nz, status));
}
let tol = T::tol();
let elim = T::elim();
let exr = z.re.cos();
let exi = z.re.sin();
let tay = (z.im + z.im).abs();
let k_term_far = tay >= elim;
let ey = if k_term_far { one } else { (-tay).exp() };
cspn = Complex::new(exr, exi) * cspn * ey;
let rtol = one / tol;
let ascle = T::MACH_TINY * rtol * T::from_f64(1.0e3);
let (zv_s, zv_atol) = if k_buf[0].re.abs().max(k_buf[0].im.abs()) <= ascle {
(k_buf[0] * rtol, tol)
} else {
(k_buf[0], one)
};
let mut zv = zv_s * cspn * zv_atol;
if k_term_far {
zv = far_k_term(zv, tay);
}
let (zu_s, zu_atol) = if i_buf[0].re.abs().max(i_buf[0].im.abs()) <= ascle {
(i_buf[0] * rtol, tol)
} else {
(i_buf[0], one)
};
let zu = zu_s * csgn * zu_atol;
let cy_val = zu - zv;
y[0] = if z.im < zero { cy_val.conj() } else { cy_val };
let nz_out = if cy_val == czero && k_term_far { 1 } else { 0 };
return Ok((nz_out, status));
}
zbesy_seq(z, zn, fnu, scaling, y, csgn, cspn, status, ws)
}
#[inline(never)]
#[allow(clippy::too_many_arguments)]
fn zbesy_seq<T: BesselFloat>(
z: Complex<T>,
zn: Complex<T>,
fnu: T,
scaling: Scaling,
y: &mut [Complex<T>],
csgn: Complex<T>,
cspn: Complex<T>,
status: Accuracy,
ws: Workspace<'_, T>,
) -> Result<(usize, Accuracy), Error> {
let n = y.len();
with_workspace(ws, n, |k_buf| {
zbesy_combine(z, zn, fnu, scaling, y, csgn, cspn, status, k_buf)
})
}
#[inline]
#[allow(clippy::too_many_arguments)]
fn zbesy_combine<T: BesselFloat>(
z: Complex<T>,
zn: Complex<T>,
fnu: T,
scaling: Scaling,
y: &mut [Complex<T>],
mut csgn: Complex<T>,
mut cspn: Complex<T>,
status: Accuracy,
k_buf: &mut [Complex<T>],
) -> Result<(usize, Accuracy), Error> {
let chunk = k_buf.len();
assert!(chunk > 0, "empty K workspace");
let zero = T::zero();
let one = T::one();
let czero = Complex::new(zero, zero);
let (nz1, _) = zbesi(zn, fnu, scaling, y)?;
let tol = T::tol();
let rtol = one / tol;
let ascle = T::MACH_TINY * rtol * T::from_f64(1.0e3);
let tay = (z.im + z.im).abs();
let k_term_far = tay >= T::elim();
if scaling == Scaling::Exponential {
let ey = if k_term_far { one } else { (-tay).exp() };
cspn = Complex::new(z.re.cos(), z.re.sin()) * cspn * ey;
}
let mut nz2: usize = 0;
let mut nz_out: usize = 0;
for (c, y_chunk) in y.chunks_mut(chunk).enumerate() {
let k_chunk = &mut k_buf[..y_chunk.len()];
let start = if c == 0 {
fnu
} else {
fnu + T::from_f64((c * chunk) as f64)
};
let (nz_k, _) = zbesk(zn, start, scaling, k_chunk)?;
nz2 += nz_k;
for (yi, &k) in y_chunk.iter_mut().zip(k_chunk.iter()) {
let i = *yi;
if scaling == Scaling::Unscaled {
*yi = csgn * i - cspn * k;
} else {
let (zv_s, zv_atol) = if k.re.abs().max(k.im.abs()) <= ascle {
(k * rtol, tol)
} else {
(k, one)
};
let mut zv = zv_s * cspn * zv_atol;
if k_term_far {
zv = far_k_term(zv, tay);
}
let (zu_s, zu_atol) = if i.re.abs().max(i.im.abs()) <= ascle {
(i * rtol, tol)
} else {
(i, one)
};
let zu = zu_s * csgn * zu_atol;
let cy_val = zu - zv;
*yi = if z.im < zero { cy_val.conj() } else { cy_val };
if cy_val == czero && k_term_far {
nz_out += 1;
}
}
csgn = mul_i(csgn);
cspn = mul_neg_i(cspn);
}
}
if scaling == Scaling::Exponential {
return Ok((nz_out, status));
}
if z.im < zero {
for yi in y.iter_mut() {
*yi = yi.conj();
}
}
Ok((nz1.min(nz2), status))
}
#[cfg(test)]
mod tests {
use super::*;
use num_complex::Complex64;
#[test]
fn besy_z_zero_returns_error() {
let z = Complex64::new(0.0, 0.0);
let mut y = [Complex64::new(0.0, 0.0)];
assert!(zbesy(z, 0.0, Scaling::Unscaled, &mut y).is_err());
}
#[test]
fn besy_non_finite_inputs_return_invalid_input() {
let nan = f64::NAN;
let inf = f64::INFINITY;
let mut y = [Complex64::new(0.0, 0.0)];
for z in [
Complex64::new(nan, 0.0),
Complex64::new(0.0, nan),
Complex64::new(nan, nan),
Complex64::new(inf, 0.0),
] {
assert!(matches!(
zbesy(z, 1.0, Scaling::Unscaled, &mut y),
Err(Error::InvalidInput)
));
}
let z = Complex64::new(1.0, 1.0);
for fnu in [nan, inf] {
assert!(matches!(
zbesy(z, fnu, Scaling::Unscaled, &mut y),
Err(Error::InvalidInput)
));
}
}
#[test]
fn besy_reports_reduced_accuracy_for_large_argument() {
let mut y = [Complex64::new(0.0, 0.0)];
let (_, status) =
zbesy(Complex64::new(40000.0, 0.0), 0.0, Scaling::Unscaled, &mut y).unwrap();
assert_eq!(status, Accuracy::Reduced);
let (_, status) = zbesy(Complex64::new(1.0, 1.0), 0.0, Scaling::Unscaled, &mut y).unwrap();
assert_eq!(status, Accuracy::Normal);
}
#[test]
fn besy_large_finite_order_returns_total_precision_loss() {
let z = Complex64::new(1.0, 1.0);
let mut y = [Complex64::new(0.0, 0.0)];
assert!(matches!(
zbesy(z, 3.0e9, Scaling::Unscaled, &mut y),
Err(Error::TotalPrecisionLoss)
));
}
}