#![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> {
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> {
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> {
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 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) = besj::zbesj(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];
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> {
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 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) = besy::zbesy(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];
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> {
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 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) = besi::zbesi(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,
});
}
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(all(test, feature = "alloc"))]
mod neg_order_seq_tests {
use super::*;
use num_complex::Complex64;
const TOL: f64 = 2e-14;
fn rel_err(a: Complex64, b: Complex64) -> f64 {
let diff = (a - b).norm();
let mag = a.norm().max(b.norm());
if mag == 0.0 { diff } else { diff / mag }
}
fn check_seq_vs_single<F, G>(
seq_fn: F,
single_fn: G,
nu: f64,
z: Complex64,
n: usize,
scaling: Scaling,
tol: f64,
label: &str,
) where
F: FnOnce(f64, Complex64, usize, Scaling) -> Result<BesselResult<f64>, Error>,
G: Fn(f64, Complex64) -> Result<Complex64, Error>,
{
let result = seq_fn(nu, z, n, scaling).unwrap();
assert_eq!(result.values.len(), n, "{label}: wrong length");
for j in 0..n {
let order = nu + j as f64;
let expected = single_fn(order, z).unwrap();
let err = rel_err(result.values[j], expected);
assert!(
err < tol,
"{label}: order={order}, seq={:?}, single={expected:?}, rel_err={err}",
result.values[j]
);
}
}
#[test]
fn besselk_seq_all_negative_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besselk_seq,
|nu, z| besselk(nu, z),
-3.7,
z,
3,
Scaling::Unscaled,
TOL,
"K all neg",
);
}
#[test]
fn besselk_seq_crossing_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besselk_seq,
|nu, z| besselk(nu, z),
-1.5,
z,
5,
Scaling::Unscaled,
TOL,
"K crossing nonint",
);
}
#[test]
fn besselk_seq_int_negative() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besselk_seq,
|nu, z| besselk(nu, z),
-3.0,
z,
4,
Scaling::Unscaled,
TOL,
"K int neg",
);
}
#[test]
fn besselk_seq_int_crossing() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besselk_seq,
|nu, z| besselk(nu, z),
-2.0,
z,
5,
Scaling::Unscaled,
TOL,
"K int cross",
);
}
#[test]
fn besselk_seq_neg_zero() {
let z = Complex64::new(1.0, 0.0);
check_seq_vs_single(
besselk_seq,
|nu, z| besselk(nu, z),
-0.0,
z,
1,
Scaling::Unscaled,
TOL,
"K neg zero",
);
}
#[test]
fn hankel1_seq_all_negative_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
hankel1_seq,
|nu, z| hankel1(nu, z),
-3.7,
z,
3,
Scaling::Unscaled,
TOL,
"H1 all neg",
);
}
#[test]
fn hankel1_seq_crossing_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
hankel1_seq,
|nu, z| hankel1(nu, z),
-1.5,
z,
5,
Scaling::Unscaled,
TOL,
"H1 crossing",
);
}
#[test]
fn hankel1_seq_int_crossing() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
hankel1_seq,
|nu, z| hankel1(nu, z),
-2.0,
z,
5,
Scaling::Unscaled,
TOL,
"H1 int cross",
);
}
#[test]
fn hankel2_seq_all_negative_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
hankel2_seq,
|nu, z| hankel2(nu, z),
-3.7,
z,
3,
Scaling::Unscaled,
TOL,
"H2 all neg",
);
}
#[test]
fn hankel2_seq_crossing_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
hankel2_seq,
|nu, z| hankel2(nu, z),
-1.5,
z,
5,
Scaling::Unscaled,
TOL,
"H2 crossing",
);
}
#[test]
fn hankel2_seq_int_crossing() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
hankel2_seq,
|nu, z| hankel2(nu, z),
-2.0,
z,
5,
Scaling::Unscaled,
TOL,
"H2 int cross",
);
}
#[test]
fn besselj_seq_all_negative_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besselj_seq,
|nu, z| besselj(nu, z),
-3.7,
z,
3,
Scaling::Unscaled,
TOL,
"J all neg",
);
}
#[test]
fn besselj_seq_crossing_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besselj_seq,
|nu, z| besselj(nu, z),
-1.5,
z,
5,
Scaling::Unscaled,
TOL,
"J crossing",
);
}
#[test]
fn besselj_seq_int_negative() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besselj_seq,
|nu, z| besselj(nu, z),
-3.0,
z,
4,
Scaling::Unscaled,
TOL,
"J int neg",
);
}
#[test]
fn besselj_seq_int_crossing() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besselj_seq,
|nu, z| besselj(nu, z),
-2.0,
z,
5,
Scaling::Unscaled,
TOL,
"J int cross",
);
}
#[test]
fn bessely_seq_all_negative_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
bessely_seq,
|nu, z| bessely(nu, z),
-3.7,
z,
3,
Scaling::Unscaled,
TOL,
"Y all neg",
);
}
#[test]
fn bessely_seq_crossing_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
bessely_seq,
|nu, z| bessely(nu, z),
-1.5,
z,
5,
Scaling::Unscaled,
TOL,
"Y crossing",
);
}
#[test]
fn bessely_seq_int_negative() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
bessely_seq,
|nu, z| bessely(nu, z),
-3.0,
z,
4,
Scaling::Unscaled,
TOL,
"Y int neg",
);
}
#[test]
fn bessely_seq_int_crossing() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
bessely_seq,
|nu, z| bessely(nu, z),
-2.0,
z,
5,
Scaling::Unscaled,
TOL,
"Y int cross",
);
}
#[test]
fn besseli_seq_all_negative_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besseli_seq,
|nu, z| besseli(nu, z),
-3.7,
z,
3,
Scaling::Unscaled,
TOL,
"I all neg",
);
}
#[test]
fn besseli_seq_crossing_nonint() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besseli_seq,
|nu, z| besseli(nu, z),
-1.5,
z,
5,
Scaling::Unscaled,
TOL,
"I crossing",
);
}
#[test]
fn besseli_seq_int_negative() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besseli_seq,
|nu, z| besseli(nu, z),
-3.0,
z,
4,
Scaling::Unscaled,
TOL,
"I int neg",
);
}
#[test]
fn besseli_seq_int_crossing() {
let z = Complex64::new(1.5, 0.5);
check_seq_vs_single(
besseli_seq,
|nu, z| besseli(nu, z),
-2.0,
z,
5,
Scaling::Unscaled,
TOL,
"I int cross",
);
}
#[test]
fn besseli_seq_scaled_negative_nonint() {
let z = Complex64::new(2.0, 1.0);
check_seq_vs_single(
besseli_seq,
|nu, z| besseli_scaled(nu, z),
-2.3,
z,
5,
Scaling::Exponential,
TOL,
"I scaled neg",
);
}
#[test]
fn besselj_seq_scaled_crossing() {
let z = Complex64::new(2.0, 1.0);
check_seq_vs_single(
besselj_seq,
|nu, z| besselj_scaled(nu, z),
-1.5,
z,
4,
Scaling::Exponential,
TOL,
"J scaled cross",
);
}
#[test]
fn besselk_seq_scaled_negative() {
let z = Complex64::new(2.0, 1.0);
check_seq_vs_single(
besselk_seq,
|nu, z| besselk_scaled(nu, z),
-2.5,
z,
4,
Scaling::Exponential,
TOL,
"K scaled neg",
);
}
#[test]
fn hankel1_seq_scaled_negative() {
let z = Complex64::new(2.0, 1.0);
check_seq_vs_single(
hankel1_seq,
|nu, z| hankel1_scaled(nu, z),
-2.5,
z,
4,
Scaling::Exponential,
TOL,
"H1 scaled neg",
);
}
#[test]
fn bessely_seq_scaled_crossing() {
let z = Complex64::new(2.0, 1.0);
check_seq_vs_single(
bessely_seq,
|nu, z| bessely_scaled(nu, z),
-1.5,
z,
4,
Scaling::Exponential,
TOL,
"Y scaled cross",
);
}
#[test]
fn besselj_seq_neg_zero() {
let z = Complex64::new(1.0, 0.0);
check_seq_vs_single(
besselj_seq,
|nu, z| besselj(nu, z),
-0.0,
z,
3,
Scaling::Unscaled,
TOL,
"J neg zero",
);
}
}