use num_complex::Complex;
use crate::algo::acon::zacon;
use crate::algo::bknu::zbknu;
use crate::algo::bunk::zbunk;
use crate::algo::uoik::zuoik;
use crate::machine::BesselFloat;
use crate::types::{Accuracy, Error, IkFlag, Scaling};
use crate::utils::zabs;
#[inline]
pub(crate) fn zbesk<T: BesselFloat>(
z: Complex<T>,
fnu: T,
scaling: Scaling,
y: &mut [Complex<T>],
) -> Result<(usize, Accuracy), Error> {
let n = y.len();
let zero = T::zero();
let czero = Complex::new(zero, zero);
if n < 1 {
return Err(Error::InvalidInput);
}
if fnu < zero {
return Err(Error::InvalidInput);
}
if z == czero {
return Err(Error::InvalidInput);
}
let tol = T::tol();
let elim = T::elim();
let alim = T::alim();
let fnul = T::fnul();
let aa_tol = T::from_f64(0.5) / tol;
let bb = T::from_f64(2147483647.0 * 0.5);
let aa = aa_tol.min(bb);
let az = zabs(z);
let fn_val = fnu + T::from_f64(n as f64 - 1.0);
if az > aa || fn_val > aa {
return Err(Error::TotalPrecisionLoss);
}
let aa_sqrt = aa.sqrt();
let precision_warning = az > aa_sqrt || fn_val > aa_sqrt;
let ufl = T::MACH_TINY * T::from_f64(1.0e3);
if az < ufl {
return Err(Error::Overflow);
}
let mut nn = n;
let mut nz: usize = 0;
if fn_val > fnul {
let mr = if z.re >= zero {
0i32
} else if z.im < zero {
-1i32
} else {
1i32
};
let nw = zbunk(z, fnu, scaling, mr, &mut y[..nn], tol, elim, alim);
if nw < 0 {
return if nw == -1 {
Err(Error::Overflow)
} else {
Err(Error::ConvergenceFailure)
};
}
nz += nw as usize;
let status = if precision_warning {
Accuracy::Reduced
} else {
Accuracy::Normal
};
return Ok((nz, status));
}
let rl = T::rl();
if fn_val > T::one() {
if fn_val > T::from_f64(2.0) {
let nuf = zuoik(z, fnu, scaling, IkFlag::K, &mut y[..nn], tol, elim, alim);
if nuf < 0 {
return Err(Error::Overflow);
}
nz += nuf as usize;
nn -= nuf as usize;
if nn == 0 {
let status = if precision_warning {
Accuracy::Reduced
} else {
Accuracy::Normal
};
return Ok((nz, status));
}
}
if az <= tol {
let half = T::from_f64(0.5);
let aln = -(fn_val * (half * az).ln());
if aln > elim {
return Err(Error::Overflow);
}
}
}
if z.re >= zero {
let nw = zbknu(z, fnu, scaling, &mut y[..nn], tol, elim, alim)?;
nz += nw;
} else {
let mr = if z.im < T::zero() { -1i32 } else { 1i32 };
let nw = zacon(z, fnu, scaling, mr, &mut y[..nn], rl, fnul, tol, elim, alim)?;
nz += nw;
}
let status = if precision_warning {
Accuracy::Reduced
} else {
Accuracy::Normal
};
Ok((nz, status))
}
#[cfg(test)]
mod tests {
use super::*;
use num_complex::Complex64;
#[test]
fn besk_z_zero_returns_error() {
let z = Complex64::new(0.0, 0.0);
let mut buf = [Complex64::new(0.0, 0.0)];
assert!(matches!(
zbesk(z, 0.0, Scaling::Unscaled, &mut buf),
Err(Error::InvalidInput)
));
}
#[test]
fn besk_negative_order_returns_error() {
let z = Complex64::new(1.0, 0.0);
let mut buf = [Complex64::new(0.0, 0.0)];
assert!(matches!(
zbesk(z, -1.0, Scaling::Unscaled, &mut buf),
Err(Error::InvalidInput)
));
}
#[test]
fn besk_n_zero_returns_error() {
let z = Complex64::new(1.0, 0.0);
let mut buf: [Complex64; 0] = [];
assert!(matches!(
zbesk(z, 0.0, Scaling::Unscaled, &mut buf),
Err(Error::InvalidInput)
));
}
}