#![warn(missing_docs)]
#![no_std]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub(crate) mod airy;
pub(crate) mod algo;
pub(crate) mod besh;
pub(crate) mod besi;
pub(crate) mod besj;
pub(crate) mod besk;
pub(crate) mod besy;
pub mod machine;
pub mod types;
pub(crate) mod utils;
pub use machine::BesselFloat;
#[cfg(feature = "alloc")]
pub use types::BesselResult;
pub use types::{Accuracy, AiryResult, Error, Scaling};
use num_complex::Complex;
use types::{AiryDerivative, HankelKind};
#[inline]
fn as_integer<T: BesselFloat>(nu: T) -> Option<i64> {
if nu == nu.floor() {
nu.to_i64()
} else {
None
}
}
#[inline]
fn reflect_j_element<T: BesselFloat>(order: T, j: Complex<T>, y: Complex<T>) -> Complex<T> {
j * utils::cospi(order) - y * utils::sinpi(order)
}
#[inline]
fn reflect_y_element<T: BesselFloat>(order: T, j: Complex<T>, y: Complex<T>) -> Complex<T> {
j * utils::sinpi(order) + y * utils::cospi(order)
}
#[inline]
fn reflect_i_element<T: BesselFloat>(order: T, i_val: Complex<T>, k_val: Complex<T>) -> Complex<T> {
let pi = T::from_f64(core::f64::consts::PI);
let two = T::from_f64(2.0);
utils::mul_add_scalar(k_val, two / pi * utils::sinpi(order), i_val)
}
#[inline]
fn reflect_h_element<T: BesselFloat>(order: T, kind: HankelKind, h: Complex<T>) -> Complex<T> {
let cos_nu_pi = utils::cospi(order);
let sin_nu_pi = utils::sinpi(order);
let rotation = match kind {
HankelKind::First => Complex::new(cos_nu_pi, sin_nu_pi),
HankelKind::Second => Complex::new(cos_nu_pi, -sin_nu_pi),
};
h * rotation
}
#[inline]
fn k_to_i_scaling_correction<T: BesselFloat>(z: Complex<T>, k_val: Complex<T>) -> Complex<T> {
let two = T::from_f64(2.0);
let (sin_a, cos_a) = (-z.im).sin_cos();
let mut result = k_val * Complex::new(cos_a, sin_a);
if z.re > T::zero() {
result = result * (-two * z.re).exp();
}
result
}
#[inline]
fn integer_sign<T: BesselFloat>(n: i64) -> T {
if n % 2 == 0 { T::one() } else { -T::one() }
}
#[inline]
fn besselj_internal<T: BesselFloat>(
nu: T,
z: Complex<T>,
scaling: Scaling,
) -> Result<Complex<T>, Error> {
let zero = T::zero();
let czero = Complex::new(zero, zero);
if nu >= zero {
let mut y = [czero];
besj::zbesj(z, nu, scaling, &mut y)?;
return Ok(y[0]);
}
let abs_nu = nu.abs();
if let Some(n) = as_integer(abs_nu) {
let mut y = [czero];
besj::zbesj(z, abs_nu, scaling, &mut y)?;
return Ok(y[0] * integer_sign::<T>(n));
}
let mut j_buf = [czero];
let mut y_buf = [czero];
besj::zbesj(z, abs_nu, scaling, &mut j_buf)?;
besy::zbesy(z, abs_nu, scaling, &mut y_buf)?;
Ok(reflect_j_element(abs_nu, j_buf[0], y_buf[0]))
}
#[inline]
fn bessely_internal<T: BesselFloat>(
nu: T,
z: Complex<T>,
scaling: Scaling,
) -> Result<Complex<T>, Error> {
let zero = T::zero();
let czero = Complex::new(zero, zero);
if nu >= zero {
let mut y = [czero];
besy::zbesy(z, nu, scaling, &mut y)?;
return Ok(y[0]);
}
let abs_nu = nu.abs();
if let Some(n) = as_integer(abs_nu) {
let mut y = [czero];
besy::zbesy(z, abs_nu, scaling, &mut y)?;
return Ok(y[0] * integer_sign::<T>(n));
}
let mut j_buf = [czero];
let mut y_buf = [czero];
besj::zbesj(z, abs_nu, scaling, &mut j_buf)?;
besy::zbesy(z, abs_nu, scaling, &mut y_buf)?;
Ok(reflect_y_element(abs_nu, j_buf[0], y_buf[0]))
}
#[inline]
fn besseli_internal<T: BesselFloat>(
nu: T,
z: Complex<T>,
scaling: Scaling,
) -> Result<Complex<T>, Error> {
let zero = T::zero();
let czero = Complex::new(zero, zero);
if nu >= zero {
let mut y = [czero];
besi::zbesi(z, nu, scaling, &mut y)?;
return Ok(y[0]);
}
let abs_nu = nu.abs();
if as_integer(abs_nu).is_some() {
let mut y = [czero];
besi::zbesi(z, abs_nu, scaling, &mut y)?;
return Ok(y[0]);
}
let mut i_buf = [czero];
let mut k_buf = [czero];
besi::zbesi(z, abs_nu, scaling, &mut i_buf)?;
besk::zbesk(z, abs_nu, scaling, &mut k_buf)?;
let mut k_val = k_buf[0];
if scaling == Scaling::Exponential {
k_val = k_to_i_scaling_correction(z, k_val);
}
Ok(reflect_i_element(abs_nu, i_buf[0], k_val))
}
#[inline]
fn besselk_internal<T: BesselFloat>(
nu: T,
z: Complex<T>,
scaling: Scaling,
) -> Result<Complex<T>, Error> {
let abs_nu = nu.abs();
let zero = T::zero();
let mut y = [Complex::new(zero, zero)];
besk::zbesk(z, abs_nu, scaling, &mut y)?;
Ok(y[0])
}
#[inline]
fn hankel_internal<T: BesselFloat>(
kind: HankelKind,
nu: T,
z: Complex<T>,
scaling: Scaling,
) -> Result<Complex<T>, Error> {
let zero = T::zero();
if nu >= zero {
let mut y = [Complex::new(zero, zero)];
besh::zbesh(z, nu, kind, scaling, &mut y)?;
return Ok(y[0]);
}
let abs_nu = nu.abs();
let mut y = [Complex::new(zero, zero)];
besh::zbesh(z, abs_nu, kind, scaling, &mut y)?;
Ok(reflect_h_element(abs_nu, kind, y[0]))
}
#[inline]
pub fn besselj<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
besselj_internal(nu, z, Scaling::Unscaled)
}
#[inline]
pub fn bessely<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
bessely_internal(nu, z, Scaling::Unscaled)
}
#[inline]
pub fn besseli<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
besseli_internal(nu, z, Scaling::Unscaled)
}
#[inline]
pub fn besselk<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
besselk_internal(nu, z, Scaling::Unscaled)
}
#[inline]
pub fn hankel1<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
hankel_internal(HankelKind::First, nu, z, Scaling::Unscaled)
}
#[inline]
pub fn hankel2<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
hankel_internal(HankelKind::Second, nu, z, Scaling::Unscaled)
}
#[inline]
pub fn airy<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Value, Scaling::Unscaled)?;
Ok(result)
}
#[inline]
pub fn airyprime<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Derivative, Scaling::Unscaled)?;
Ok(result)
}
#[inline]
pub fn biry<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
let (result, _status) = airy::zbiry(z, AiryDerivative::Value, Scaling::Unscaled)?;
Ok(result)
}
#[inline]
pub fn biryprime<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
let (result, _status) = airy::zbiry(z, AiryDerivative::Derivative, Scaling::Unscaled)?;
Ok(result)
}
#[inline]
pub fn besselj_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
besselj_internal(nu, z, Scaling::Exponential)
}
#[inline]
pub fn bessely_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
bessely_internal(nu, z, Scaling::Exponential)
}
#[inline]
pub fn besseli_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
besseli_internal(nu, z, Scaling::Exponential)
}
#[inline]
pub fn besselk_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
besselk_internal(nu, z, Scaling::Exponential)
}
#[inline]
pub fn hankel1_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
hankel_internal(HankelKind::First, nu, z, Scaling::Exponential)
}
#[inline]
pub fn hankel2_scaled<T: BesselFloat>(nu: T, z: Complex<T>) -> Result<Complex<T>, Error> {
hankel_internal(HankelKind::Second, nu, z, Scaling::Exponential)
}
#[inline]
pub fn airy_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Value, Scaling::Exponential)?;
Ok(result)
}
#[inline]
pub fn airyprime_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
let (result, _nz, _status) = airy::zairy(z, AiryDerivative::Derivative, Scaling::Exponential)?;
Ok(result)
}
#[inline]
pub fn biry_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
let (result, _status) = airy::zbiry(z, AiryDerivative::Value, Scaling::Exponential)?;
Ok(result)
}
#[inline]
pub fn biryprime_scaled<T: BesselFloat>(z: Complex<T>) -> Result<Complex<T>, Error> {
let (result, _status) = airy::zbiry(z, AiryDerivative::Derivative, Scaling::Exponential)?;
Ok(result)
}
#[inline]
pub fn airy_raw<T: BesselFloat>(z: Complex<T>, scaling: Scaling) -> Result<AiryResult<T>, Error> {
let (value, _nz, status) = airy::zairy(z, AiryDerivative::Value, scaling)?;
Ok(AiryResult { value, status })
}
#[inline]
pub fn airyprime_raw<T: BesselFloat>(
z: Complex<T>,
scaling: Scaling,
) -> Result<AiryResult<T>, Error> {
let (value, _nz, status) = airy::zairy(z, AiryDerivative::Derivative, scaling)?;
Ok(AiryResult { value, status })
}
#[inline]
pub fn biry_raw<T: BesselFloat>(z: Complex<T>, scaling: Scaling) -> Result<AiryResult<T>, Error> {
let (value, status) = airy::zbiry(z, AiryDerivative::Value, scaling)?;
Ok(AiryResult { value, status })
}
#[inline]
pub fn biryprime_raw<T: BesselFloat>(
z: Complex<T>,
scaling: Scaling,
) -> Result<AiryResult<T>, Error> {
let (value, status) = airy::zbiry(z, AiryDerivative::Derivative, scaling)?;
Ok(AiryResult { value, status })
}
#[cfg(feature = "alloc")]
extern crate alloc as alloc_crate;
#[cfg(feature = "alloc")]
fn seq_helper<T: BesselFloat>(
n: usize,
f: impl FnOnce(&mut [Complex<T>]) -> Result<(usize, Accuracy), Error>,
) -> Result<BesselResult<T>, Error> {
let zero = T::zero();
let mut values = alloc_crate::vec![Complex::new(zero, zero); n];
let (underflow_count, status) = f(&mut values)?;
Ok(BesselResult {
values,
underflow_count,
status,
})
}
#[cfg(feature = "alloc")]
#[inline]
fn worse_status(a: Accuracy, b: Accuracy) -> Accuracy {
match (a, b) {
(Accuracy::Reduced, _) | (_, Accuracy::Reduced) => Accuracy::Reduced,
_ => Accuracy::Normal,
}
}
#[cfg(feature = "alloc")]
#[inline]
fn recount_underflow<T: BesselFloat>(values: &[Complex<T>]) -> usize {
let zero = Complex::new(T::zero(), T::zero());
values.iter().take_while(|v| **v == zero).count()
}
#[cfg(feature = "alloc")]
#[inline]
fn neg_count<T: BesselFloat>(nu: T, n: usize) -> usize {
if nu >= T::zero() {
return 0;
}
let abs_nu = nu.abs();
let c = abs_nu.ceil();
let cn = if let Some(v) = c.to_usize() { v } else { n };
cn.min(n)
}
#[cfg(feature = "alloc")]
#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
fn besselk_seq_neg<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if n == 0 {
return Err(Error::InvalidInput);
}
let zero = T::zero();
let czero = Complex::new(zero, zero);
let abs_nu = nu.abs();
let nc = neg_count(nu, n);
if nc == n {
let start = abs_nu - T::from_f64((n - 1) as f64);
let mut buf = alloc_crate::vec![czero; n];
let (_, status) = besk::zbesk(z, start, scaling, &mut buf)?;
buf.reverse();
let uc = recount_underflow(&buf);
return Ok(BesselResult {
values: buf,
underflow_count: uc,
status,
});
}
let is_int = as_integer(abs_nu).is_some();
if is_int {
let last_order = nu + T::from_f64((n - 1) as f64);
let max_abs = if abs_nu > last_order {
abs_nu
} else {
last_order
};
let buf_n = max_abs.to_usize().unwrap_or(0) + 1; let mut buf = alloc_crate::vec![czero; buf_n];
let (_, status) = besk::zbesk(z, zero, scaling, &mut buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..n {
let order = nu + T::from_f64(j as f64);
let idx = order.abs().to_usize().unwrap_or(0);
values[j] = buf[idx];
}
let uc = recount_underflow(&values);
return Ok(BesselResult {
values,
underflow_count: uc,
status,
});
}
let frac_neg = abs_nu - abs_nu.floor();
let neg_start = frac_neg; let mut neg_buf = alloc_crate::vec![czero; nc];
let (_, status1) = besk::zbesk(z, neg_start, scaling, &mut neg_buf)?;
let pos_start = nu + T::from_f64(nc as f64);
let pos_n = n - nc;
let mut pos_buf = alloc_crate::vec![czero; pos_n];
let (_, status2) = besk::zbesk(z, pos_start, scaling, &mut pos_buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..nc {
let k = (abs_nu - T::from_f64(j as f64))
.floor()
.to_usize()
.unwrap_or(0);
values[j] = neg_buf[k];
}
for j in 0..pos_n {
values[nc + j] = pos_buf[j];
}
let status = worse_status(status1, status2);
let uc = recount_underflow(&values);
Ok(BesselResult {
values,
underflow_count: uc,
status,
})
}
#[cfg(feature = "alloc")]
#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
fn hankel_seq_neg<T: BesselFloat>(
kind: HankelKind,
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if n == 0 {
return Err(Error::InvalidInput);
}
let zero = T::zero();
let czero = Complex::new(zero, zero);
let abs_nu = nu.abs();
let nc = neg_count(nu, n);
let is_int = as_integer(abs_nu).is_some();
if nc == n {
let start = abs_nu - T::from_f64((n - 1) as f64);
let mut buf = alloc_crate::vec![czero; n];
let (_, status) = besh::zbesh(z, start, kind, scaling, &mut buf)?;
buf.reverse();
for j in 0..n {
let order = abs_nu - T::from_f64(j as f64);
buf[j] = reflect_h_element(order, kind, buf[j]);
}
let uc = recount_underflow(&buf);
return Ok(BesselResult {
values: buf,
underflow_count: uc,
status,
});
}
if is_int {
let last_order = nu + T::from_f64((n - 1) as f64);
let max_abs = if abs_nu > last_order {
abs_nu
} else {
last_order
};
let buf_n = max_abs.to_usize().unwrap_or(0) + 1;
let mut buf = alloc_crate::vec![czero; buf_n];
let (_, status) = besh::zbesh(z, zero, kind, scaling, &mut buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..n {
let order = nu + T::from_f64(j as f64);
let idx = order.abs().to_usize().unwrap_or(0);
values[j] = buf[idx];
if order < zero {
values[j] = reflect_h_element(order.abs(), kind, values[j]);
}
}
let uc = recount_underflow(&values);
return Ok(BesselResult {
values,
underflow_count: uc,
status,
});
}
let frac_neg = abs_nu - abs_nu.floor();
let mut neg_buf = alloc_crate::vec![czero; nc];
let (_, status1) = besh::zbesh(z, frac_neg, kind, scaling, &mut neg_buf)?;
let pos_start = nu + T::from_f64(nc as f64);
let pos_n = n - nc;
let mut pos_buf = alloc_crate::vec![czero; pos_n];
let (_, status2) = besh::zbesh(z, pos_start, kind, scaling, &mut pos_buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..nc {
let abs_order = abs_nu - T::from_f64(j as f64);
let k = abs_order.floor().to_usize().unwrap_or(0);
values[j] = reflect_h_element(abs_order, kind, neg_buf[k]);
}
for j in 0..pos_n {
values[nc + j] = pos_buf[j];
}
let status = worse_status(status1, status2);
let uc = recount_underflow(&values);
Ok(BesselResult {
values,
underflow_count: uc,
status,
})
}
#[cfg(feature = "alloc")]
#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
fn besselj_seq_neg<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if n == 0 {
return Err(Error::InvalidInput);
}
let zero = T::zero();
let czero = Complex::new(zero, zero);
let abs_nu = nu.abs();
let nc = neg_count(nu, n);
let is_int = as_integer(abs_nu).is_some();
if is_int {
let (min_abs, max_abs) = if nc == n {
(abs_nu - T::from_f64((n - 1) as f64), abs_nu)
} else {
let last_order = nu + T::from_f64((n - 1) as f64);
(zero, abs_nu.max(last_order))
};
let buf_n = (max_abs - min_abs).to_usize().unwrap_or(0) + 1;
let mut buf = alloc_crate::vec![czero; buf_n];
let (_, status) = besj::zbesj(z, min_abs, scaling, &mut buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..n {
let order = nu + T::from_f64(j as f64);
let idx = (order.abs() - min_abs).to_usize().unwrap_or(0);
values[j] = buf[idx];
if order < zero {
let int_order = order.abs().to_i64().unwrap_or(0);
values[j] = values[j] * integer_sign::<T>(int_order);
}
}
let uc = recount_underflow(&values);
return Ok(BesselResult {
values,
underflow_count: uc,
status,
});
}
if nc == n {
let start = abs_nu - T::from_f64((n - 1) as f64);
let mut j_buf = alloc_crate::vec![czero; n];
let mut y_buf = alloc_crate::vec![czero; n];
let (_, s1) = besj::zbesj(z, start, scaling, &mut j_buf)?;
let (_, s2) = besy::zbesy(z, start, scaling, &mut y_buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..n {
let k = n - 1 - j;
let order = abs_nu - T::from_f64(j as f64);
values[j] = reflect_j_element(order, j_buf[k], y_buf[k]);
}
let uc = recount_underflow(&values);
return Ok(BesselResult {
values,
underflow_count: uc,
status: worse_status(s1, s2),
});
}
let frac_neg = abs_nu - abs_nu.floor();
let mut neg_j = alloc_crate::vec![czero; nc];
let mut neg_y = alloc_crate::vec![czero; nc];
let (_, s1) = besj::zbesj(z, frac_neg, scaling, &mut neg_j)?;
let (_, s2) = besy::zbesy(z, frac_neg, scaling, &mut neg_y)?;
let pos_start = nu + T::from_f64(nc as f64);
let pos_n = n - nc;
let mut pos_buf = alloc_crate::vec![czero; pos_n];
let (_, s3) = besj::zbesj(z, pos_start, scaling, &mut pos_buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..nc {
let abs_order = abs_nu - T::from_f64(j as f64);
let k = abs_order.floor().to_usize().unwrap_or(0);
values[j] = reflect_j_element(abs_order, neg_j[k], neg_y[k]);
}
for j in 0..pos_n {
values[nc + j] = pos_buf[j];
}
let status = worse_status(worse_status(s1, s2), s3);
let uc = recount_underflow(&values);
Ok(BesselResult {
values,
underflow_count: uc,
status,
})
}
#[cfg(feature = "alloc")]
#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
fn bessely_seq_neg<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if n == 0 {
return Err(Error::InvalidInput);
}
let zero = T::zero();
let czero = Complex::new(zero, zero);
let abs_nu = nu.abs();
let nc = neg_count(nu, n);
let is_int = as_integer(abs_nu).is_some();
if is_int {
let (min_abs, max_abs) = if nc == n {
(abs_nu - T::from_f64((n - 1) as f64), abs_nu)
} else {
let last_order = nu + T::from_f64((n - 1) as f64);
(zero, abs_nu.max(last_order))
};
let buf_n = (max_abs - min_abs).to_usize().unwrap_or(0) + 1;
let mut buf = alloc_crate::vec![czero; buf_n];
let (_, status) = besy::zbesy(z, min_abs, scaling, &mut buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..n {
let order = nu + T::from_f64(j as f64);
let idx = (order.abs() - min_abs).to_usize().unwrap_or(0);
values[j] = buf[idx];
if order < zero {
let int_order = order.abs().to_i64().unwrap_or(0);
values[j] = values[j] * integer_sign::<T>(int_order);
}
}
let uc = recount_underflow(&values);
return Ok(BesselResult {
values,
underflow_count: uc,
status,
});
}
if nc == n {
let start = abs_nu - T::from_f64((n - 1) as f64);
let mut j_buf = alloc_crate::vec![czero; n];
let mut y_buf = alloc_crate::vec![czero; n];
let (_, s1) = besj::zbesj(z, start, scaling, &mut j_buf)?;
let (_, s2) = besy::zbesy(z, start, scaling, &mut y_buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..n {
let k = n - 1 - j;
let order = abs_nu - T::from_f64(j as f64);
values[j] = reflect_y_element(order, j_buf[k], y_buf[k]);
}
let uc = recount_underflow(&values);
return Ok(BesselResult {
values,
underflow_count: uc,
status: worse_status(s1, s2),
});
}
let frac_neg = abs_nu - abs_nu.floor();
let mut neg_j = alloc_crate::vec![czero; nc];
let mut neg_y = alloc_crate::vec![czero; nc];
let (_, s1) = besj::zbesj(z, frac_neg, scaling, &mut neg_j)?;
let (_, s2) = besy::zbesy(z, frac_neg, scaling, &mut neg_y)?;
let pos_start = nu + T::from_f64(nc as f64);
let pos_n = n - nc;
let mut pos_buf = alloc_crate::vec![czero; pos_n];
let (_, s3) = besy::zbesy(z, pos_start, scaling, &mut pos_buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..nc {
let abs_order = abs_nu - T::from_f64(j as f64);
let k = abs_order.floor().to_usize().unwrap_or(0);
values[j] = reflect_y_element(abs_order, neg_j[k], neg_y[k]);
}
for j in 0..pos_n {
values[nc + j] = pos_buf[j];
}
let status = worse_status(worse_status(s1, s2), s3);
let uc = recount_underflow(&values);
Ok(BesselResult {
values,
underflow_count: uc,
status,
})
}
#[cfg(feature = "alloc")]
#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
fn besseli_seq_neg<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if n == 0 {
return Err(Error::InvalidInput);
}
let zero = T::zero();
let czero = Complex::new(zero, zero);
let abs_nu = nu.abs();
let nc = neg_count(nu, n);
let is_int = as_integer(abs_nu).is_some();
if is_int {
let (min_abs, max_abs) = if nc == n {
(abs_nu - T::from_f64((n - 1) as f64), abs_nu)
} else {
let last_order = nu + T::from_f64((n - 1) as f64);
(zero, abs_nu.max(last_order))
};
let buf_n = (max_abs - min_abs).to_usize().unwrap_or(0) + 1;
let mut buf = alloc_crate::vec![czero; buf_n];
let (_, status) = besi::zbesi(z, min_abs, scaling, &mut buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..n {
let order = nu + T::from_f64(j as f64);
let idx = (order.abs() - min_abs).to_usize().unwrap_or(0);
values[j] = buf[idx];
}
let uc = recount_underflow(&values);
return Ok(BesselResult {
values,
underflow_count: uc,
status,
});
}
if nc == n {
let start = abs_nu - T::from_f64((n - 1) as f64);
let mut i_buf = alloc_crate::vec![czero; n];
let mut k_buf = alloc_crate::vec![czero; n];
let (_, s1) = besi::zbesi(z, start, scaling, &mut i_buf)?;
let (_, s2) = besk::zbesk(z, start, scaling, &mut k_buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..n {
let k_idx = n - 1 - j;
let order = abs_nu - T::from_f64(j as f64);
let mut k_val = k_buf[k_idx];
if scaling == Scaling::Exponential {
k_val = k_to_i_scaling_correction(z, k_val);
}
values[j] = reflect_i_element(order, i_buf[k_idx], k_val);
}
let uc = recount_underflow(&values);
return Ok(BesselResult {
values,
underflow_count: uc,
status: worse_status(s1, s2),
});
}
let frac_neg = abs_nu - abs_nu.floor();
let mut neg_i = alloc_crate::vec![czero; nc];
let mut neg_k = alloc_crate::vec![czero; nc];
let (_, s1) = besi::zbesi(z, frac_neg, scaling, &mut neg_i)?;
let (_, s2) = besk::zbesk(z, frac_neg, scaling, &mut neg_k)?;
let pos_start = nu + T::from_f64(nc as f64);
let pos_n = n - nc;
let mut pos_buf = alloc_crate::vec![czero; pos_n];
let (_, s3) = besi::zbesi(z, pos_start, scaling, &mut pos_buf)?;
let mut values = alloc_crate::vec![czero; n];
for j in 0..nc {
let abs_order = abs_nu - T::from_f64(j as f64);
let k_idx = abs_order.floor().to_usize().unwrap_or(0);
let mut k_val = neg_k[k_idx];
if scaling == Scaling::Exponential {
k_val = k_to_i_scaling_correction(z, k_val);
}
values[j] = reflect_i_element(abs_order, neg_i[k_idx], k_val);
}
for j in 0..pos_n {
values[nc + j] = pos_buf[j];
}
let status = worse_status(worse_status(s1, s2), s3);
let uc = recount_underflow(&values);
Ok(BesselResult {
values,
underflow_count: uc,
status,
})
}
#[cfg(feature = "alloc")]
pub fn besselj_seq<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if nu < T::zero() {
return besselj_seq_neg(nu, z, n, scaling);
}
seq_helper(n, |y| besj::zbesj(z, nu, scaling, y))
}
#[cfg(feature = "alloc")]
pub fn bessely_seq<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if nu < T::zero() {
return bessely_seq_neg(nu, z, n, scaling);
}
seq_helper(n, |y| besy::zbesy(z, nu, scaling, y))
}
#[cfg(feature = "alloc")]
pub fn besseli_seq<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if nu < T::zero() {
return besseli_seq_neg(nu, z, n, scaling);
}
seq_helper(n, |y| besi::zbesi(z, nu, scaling, y))
}
#[cfg(feature = "alloc")]
pub fn besselk_seq<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if nu < T::zero() {
return besselk_seq_neg(nu, z, n, scaling);
}
seq_helper(n, |y| besk::zbesk(z, nu, scaling, y))
}
#[cfg(feature = "alloc")]
pub fn hankel1_seq<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if nu < T::zero() {
return hankel_seq_neg(HankelKind::First, nu, z, n, scaling);
}
seq_helper(n, |y| besh::zbesh(z, nu, HankelKind::First, scaling, y))
}
#[cfg(feature = "alloc")]
pub fn hankel2_seq<T: BesselFloat>(
nu: T,
z: Complex<T>,
n: usize,
scaling: Scaling,
) -> Result<BesselResult<T>, Error> {
if nu < T::zero() {
return hankel_seq_neg(HankelKind::Second, nu, z, n, scaling);
}
seq_helper(n, |y| besh::zbesh(z, nu, HankelKind::Second, scaling, y))
}
#[cfg(test)]
mod non_finite_tests {
use super::*;
use num_complex::{Complex32, Complex64};
#[test]
fn bessel_non_finite_z_returns_invalid_input() {
let nan = f64::NAN;
for z in [
Complex64::new(nan, 0.0),
Complex64::new(0.0, nan),
Complex64::new(nan, nan),
Complex64::new(f64::INFINITY, 0.0),
] {
assert!(matches!(besselj(0.0, z), Err(Error::InvalidInput)));
assert!(matches!(besselj_scaled(1.0, z), Err(Error::InvalidInput)));
assert!(matches!(bessely(0.0, z), Err(Error::InvalidInput)));
assert!(matches!(besseli(0.0, z), Err(Error::InvalidInput)));
assert!(matches!(besselk(0.0, z), Err(Error::InvalidInput)));
assert!(matches!(hankel1(0.0, z), Err(Error::InvalidInput)));
assert!(matches!(hankel2(0.0, z), Err(Error::InvalidInput)));
assert!(matches!(besselj(-2.5, z), Err(Error::InvalidInput)));
assert!(matches!(besselk(-2.5, z), Err(Error::InvalidInput)));
}
}
#[test]
fn bessel_non_finite_nu_returns_invalid_input() {
let z = Complex64::new(1.0, 1.0);
for nu in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(matches!(besselj(nu, z), Err(Error::InvalidInput)));
assert!(matches!(bessely(nu, z), Err(Error::InvalidInput)));
assert!(matches!(besseli(nu, z), Err(Error::InvalidInput)));
assert!(matches!(besselk(nu, z), Err(Error::InvalidInput)));
assert!(matches!(hankel1(nu, z), Err(Error::InvalidInput)));
assert!(matches!(hankel2(nu, z), Err(Error::InvalidInput)));
}
}
#[test]
fn bessel_non_finite_f32_returns_invalid_input() {
let z = Complex32::new(1.0, 1.0);
assert!(matches!(
besselj(0.0f32, Complex32::new(f32::NAN, 0.0)),
Err(Error::InvalidInput)
));
assert!(matches!(besselj(f32::NAN, z), Err(Error::InvalidInput)));
}
#[test]
fn airy_non_finite_z_returns_invalid_input() {
let nan = f64::NAN;
for z in [
Complex64::new(nan, 0.0),
Complex64::new(0.0, nan),
Complex64::new(f64::INFINITY, 0.0),
] {
assert!(matches!(airy(z), Err(Error::InvalidInput)));
assert!(matches!(airyprime(z), Err(Error::InvalidInput)));
assert!(matches!(biry(z), Err(Error::InvalidInput)));
assert!(matches!(biryprime(z), Err(Error::InvalidInput)));
assert!(matches!(
airy_raw(z, Scaling::Unscaled),
Err(Error::InvalidInput)
));
assert!(matches!(
biry_raw(z, Scaling::Unscaled),
Err(Error::InvalidInput)
));
}
}
#[test]
fn finite_overflowing_modulus_keeps_total_precision_loss() {
let z = Complex64::new(1.7e308, 1.7e308);
assert!(matches!(besselj(0.0, z), Err(Error::TotalPrecisionLoss)));
}
#[cfg(feature = "alloc")]
#[test]
fn seq_non_finite_inputs_return_invalid_input() {
let z_nan = Complex64::new(f64::NAN, 0.0);
let z = Complex64::new(1.0, 1.0);
assert!(matches!(
besselj_seq(f64::NAN, z, 2, Scaling::Unscaled),
Err(Error::InvalidInput)
));
assert!(matches!(
besselj_seq(0.0, z_nan, 2, Scaling::Unscaled),
Err(Error::InvalidInput)
));
assert!(matches!(
besselk_seq(-2.5, z_nan, 3, Scaling::Unscaled),
Err(Error::InvalidInput)
));
}
}
#[cfg(test)]
mod scaling_tests {
use super::*;
use num_complex::Complex64;
type F<T> = fn(T, Complex<T>) -> Result<Complex<T>, Error>;
fn fns<T: BesselFloat>() -> [(&'static str, F<T>, F<T>); 6] {
[
("J", besselj, besselj_scaled),
("Y", bessely, bessely_scaled),
("I", besseli, besseli_scaled),
("K", besselk, besselk_scaled),
("H1", hankel1, hankel1_scaled),
("H2", hankel2, hankel2_scaled),
]
}
fn log_factor(name: &str, z: Complex64) -> Complex64 {
let i = Complex64::new(0.0, 1.0);
match name {
"J" | "Y" => Complex64::new(-z.im.abs(), 0.0),
"I" => Complex64::new(-z.re.abs(), 0.0),
"K" => z,
"H1" => -i * z,
_ => i * z,
}
}
fn check<T: BesselFloat>(nus: &[f64], radii: &[f64], floor: f64, tol: f64) {
let to64 = |v: Complex<T>| Complex64::new(v.re.to_f64().unwrap(), v.im.to_f64().unwrap());
let mut compared = 0;
for (name, unscaled, scaled) in fns::<T>() {
for &nu in nus {
for &r in radii {
for k in 0..12 {
let th =
-core::f64::consts::PI + core::f64::consts::FRAC_PI_6 * f64::from(k);
let z = Complex::from_polar(T::from_f64(r), T::from_f64(th));
let nu_t = T::from_f64(nu);
let (Ok(u), Ok(s)) = (unscaled(nu_t, z), scaled(nu_t, z)) else {
continue;
};
let (u, s) = (to64(u), to64(s));
let ok = |v: Complex64| v.norm().is_finite() && v.norm() > floor;
if !ok(u) || !ok(s) {
continue;
}
let expected = (u.ln() + log_factor(name, to64(z))).exp();
let err = (s - expected).norm() / expected.norm();
assert!(
err < tol,
"{name}: nu={nu}, z={z:?}: scaled={s:?}, exp(c)*unscaled={expected:?}, \
rel_err={err:e}"
);
compared += 1;
}
}
}
}
assert!(compared > 0);
}
#[test]
fn scaled_matches_unscaled_f64() {
check::<f64>(
&[0.0, 0.3, 2.5, 45.7, 250.0, 575.0, 800.0, 1200.0],
&[0.5, 5.0, 40.0, 200.0, 360.0, 450.0, 600.0],
1e-290,
1e-10,
);
}
#[test]
fn scaled_matches_unscaled_f32() {
check::<f32>(
&[0.0, 0.3, 2.5, 25.7, 65.0, 90.0, 130.0],
&[0.5, 5.0, 20.0, 41.0, 50.0, 60.0, 75.0],
1e-30,
1e-3,
);
}
}
#[cfg(all(test, feature = "alloc"))]
mod seq_tests {
use super::*;
use num_complex::Complex64;
#[test]
fn seq_power_series_underflow_retry_keeps_scaling() {
fn check<T: BesselFloat>(
tops: core::ops::Range<i32>,
ns: &[usize],
z: Complex<T>,
tol: f64,
) {
for &n in ns {
let mut hits = 0;
for top in tops.clone() {
let nu = T::from_f64(f64::from(top) - (n - 1) as f64);
let r = besselj_seq(nu, z, n, Scaling::Unscaled).unwrap();
if r.underflow_count == 0 || r.values[0].norm() == T::zero() {
continue;
}
hits += 1;
for (j, &got) in r.values.iter().enumerate() {
if got.norm() == T::zero() {
continue;
}
let order = nu + T::from_f64(j as f64);
let single = besselj(order, z).unwrap();
let err = ((got - single).norm() / single.norm()).to_f64().unwrap();
assert!(
err < tol,
"n={n} order={:?}: {got:?} vs {single:?}",
order.to_f64()
);
}
}
assert!(hits > 0, "n={n}: no case with a partial underflow");
}
}
check(202..219, &[3, 40], Complex64::new(6.5, 0.0), 1e-10);
check(36..48, &[3, 20], Complex::new(6.5f32, 0.0), 1e-4);
}
#[test]
fn seq_neg_n_zero_is_invalid_input() {
let z = Complex64::new(1.5, 0.5);
let s = Scaling::Unscaled;
for nu in [-1.5, -2.0] {
assert_eq!(besselj_seq(nu, z, 0, s).unwrap_err(), Error::InvalidInput);
assert_eq!(bessely_seq(nu, z, 0, s).unwrap_err(), Error::InvalidInput);
assert_eq!(besseli_seq(nu, z, 0, s).unwrap_err(), Error::InvalidInput);
assert_eq!(besselk_seq(nu, z, 0, s).unwrap_err(), Error::InvalidInput);
assert_eq!(hankel1_seq(nu, z, 0, s).unwrap_err(), Error::InvalidInput);
assert_eq!(hankel2_seq(nu, z, 0, s).unwrap_err(), Error::InvalidInput);
}
}
type SeqFn<T> = fn(T, Complex<T>, usize, Scaling) -> Result<BesselResult<T>, Error>;
type SingleFn<T> = fn(T, Complex<T>) -> Result<Complex<T>, Error>;
type SeqCase<T> = (&'static str, SeqFn<T>, SingleFn<T>, SingleFn<T>);
fn seq_fns<T: BesselFloat>() -> [SeqCase<T>; 6] {
[
("J", besselj_seq, besselj, besselj_scaled),
("Y", bessely_seq, bessely, bessely_scaled),
("I", besseli_seq, besseli, besseli_scaled),
("K", besselk_seq, besselk, besselk_scaled),
("H1", hankel1_seq, hankel1, hankel1_scaled),
("H2", hankel2_seq, hankel2, hankel2_scaled),
]
}
fn check_seq_grid<T: BesselFloat>(
nus: &[f64],
radii: &[f64],
args: &[f64],
n: usize,
tol: f64,
) {
let floor = T::MACH_TINY * T::from_f64(1e3);
for (name, seq, single, single_scaled) in seq_fns::<T>() {
for scaling in [Scaling::Unscaled, Scaling::Exponential] {
let single = if scaling == Scaling::Unscaled {
single
} else {
single_scaled
};
for &nu in nus {
for &r in radii {
for &th in args {
let nu_t = T::from_f64(nu);
let z = Complex::from_polar(T::from_f64(r), T::from_f64(th));
let res = match seq(nu_t, z, n, scaling) {
Ok(res) => res,
Err(e) => {
let some_order_fails = (0..n)
.any(|j| single(nu_t + T::from_f64(j as f64), z).is_err());
assert!(
some_order_fails,
"{name} {scaling:?}: nu={nu}, z={z:?}, n={n}: seq \
failed with {e:?} but every single value succeeds"
);
continue;
}
};
assert_eq!(res.values.len(), n);
for (j, &w) in res.values.iter().enumerate() {
let order = nu_t + T::from_f64(j as f64);
let Ok(v) = single(order, z) else {
continue;
};
if !(v.norm() > floor && v.norm().is_finite()) {
continue;
}
let err = ((w - v).norm() / v.norm()).to_f64().unwrap();
assert!(
err < tol,
"{name} {scaling:?}: nu={nu}, z={z:?}, n={n}, j={j}: \
seq={w:?}, single={v:?}, rel_err={err:e}"
);
}
}
}
}
}
}
}
const ARGS: [f64; 8] = [
0.0,
0.5,
1.2,
core::f64::consts::FRAC_PI_2,
2.2,
3.0,
-1.0,
-2.6,
];
const TOL_GRID_F64: f64 = 1e-10;
#[test]
fn seq_matches_single_grid_f64() {
for n in [1, 9] {
check_seq_grid::<f64>(
&[
-200.0, -20.0, -12.3, -5.3, -3.0, -2.5, -0.0, 0.0, 0.4, 13.5, 46.2, 81.7,
121.3, 575.0,
],
&[
0.3, 3.0, 17.0, 45.0, 120.0, 330.0, 360.0, 666.0, 698.0, 705.0, 709.0,
],
&ARGS,
n,
TOL_GRID_F64,
);
}
}
#[test]
fn seq_matches_single_grid_f64_long() {
check_seq_grid::<f64>(&[0.5, 3.3], &[2.2, 25.0, 90.0], &ARGS, 100, TOL_GRID_F64);
}
const TOL_GRID_F32: f64 = 1e-3;
#[test]
fn seq_matches_single_grid_f32() {
check_seq_grid::<f32>(
&[
-60.0, -9.0, -4.6, -0.0, 0.0, 0.4, 7.5, 21.2, 29.9, 46.3, 70.0,
],
&[0.3, 3.0, 17.0, 45.0, 70.0, 90.0],
&ARGS,
9,
TOL_GRID_F32,
);
}
fn check_recurrence_grid<T: BesselFloat>(
nus: &[f64],
radii: &[f64],
args: &[f64],
n: usize,
tol: f64,
) {
for (name, seq, _, _) in seq_fns::<T>() {
let (s, t) = match name {
"I" => (-1.0, 1.0),
"K" => (-1.0, -1.0),
_ => (1.0, 1.0),
};
let (s, t) = (T::from_f64(s), T::from_f64(t));
for scaling in [Scaling::Unscaled, Scaling::Exponential] {
for &nu in nus {
for &r in radii {
for &th in args {
let nu_t = T::from_f64(nu);
let z = Complex::from_polar(T::from_f64(r), T::from_f64(th));
let Ok(res) = seq(nu_t, z, n, scaling) else {
continue;
};
for (j, w) in res.values.windows(3).enumerate() {
if w.iter()
.any(|v| v.norm() == T::zero() || !v.norm().is_finite())
{
continue;
}
let order = nu_t + T::from_f64((j + 1) as f64);
let lhs = w[0] + w[2] * s;
let rhs = w[1] * (z.inv() * (T::from_f64(2.0) * order * t));
let mag = w[0].norm().max(w[2].norm()).max(rhs.norm());
let err = ((lhs - rhs).norm() / mag).to_f64().unwrap();
assert!(
err < tol,
"{name} {scaling:?}: nu={nu}, z={z:?}, order={:?}: \
recurrence residual {err:e}",
order.to_f64()
);
}
}
}
}
}
}
}
#[test]
fn seq_recurrence_across_zero_f64() {
check_recurrence_grid::<f64>(
&[-12.3, -5.3, -4.0, -3.7, -2.5, -0.3],
&[0.3, 3.0, 17.0, 45.0],
&ARGS,
9,
1e-11, );
}
#[test]
fn seq_recurrence_across_zero_f32() {
check_recurrence_grid::<f32>(
&[-12.3, -5.3, -4.0, -3.7, -2.5, -0.3],
&[0.3, 3.0, 17.0, 45.0],
&ARGS,
9,
1e-4, );
}
}