use num_complex::Complex;
use crate::algo::acon::zacon;
use crate::algo::bknu::zbknu;
use crate::algo::bunk::zbunk;
use crate::algo::constants::HPI;
use crate::algo::uoik::zuoik;
use crate::machine::BesselFloat;
use crate::types::{Accuracy, Error, HankelKind, IkFlag, Scaling};
use crate::utils::{mul_i, mul_neg_i, zabs};
#[inline]
pub(crate) fn zbesh<T: BesselFloat>(
z: Complex<T>,
fnu: T,
kind: HankelKind,
scaling: Scaling,
y: &mut [Complex<T>],
) -> Result<(usize, Accuracy), Error> {
let zero = T::zero();
let one = T::one();
let two = T::from_f64(2.0);
let czero = Complex::new(zero, zero);
let n = y.len();
if n < 1 {
return Err(Error::InvalidInput);
}
if fnu < zero {
return Err(Error::InvalidInput);
}
if z == czero {
return Err(Error::InvalidInput);
}
let nn = n;
let tol = T::tol();
let elim = T::elim();
let alim = T::alim();
let fnul = T::fnul();
let fn_val = fnu + T::from_f64((nn - 1) as f64);
let m: i32 = match kind {
HankelKind::First => 1,
HankelKind::Second => 2,
};
let mm = 3 - 2 * m; let fmm = T::from_f64(mm as f64);
let zn = Complex::new(fmm * z.im, -fmm * z.re);
let az = zabs(z);
let aa_tol = T::from_f64(0.5) / 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 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 nz: usize = 0;
let mut nn_eff = nn;
if fnu > fnul {
let mut mr = 0i32;
let mut zn_call = zn;
if !((zn.re >= zero) && (zn.re != zero || zn.im >= zero || m != 2)) {
mr = -mm;
if zn.re == zero && zn.im < zero {
zn_call = -zn;
}
}
let nw = zbunk(zn_call, fnu, scaling, mr, &mut y[..nn_eff], tol, elim, alim);
if nw < 0 {
return if nw == -1 {
Err(Error::Overflow)
} else {
Err(Error::ConvergenceFailure)
};
}
nz += nw as usize;
} else {
if fn_val > one {
if fn_val > two {
let nuf = zuoik(
zn,
fnu,
scaling,
IkFlag::K,
&mut y[..nn_eff],
tol,
elim,
alim,
);
if nuf < 0 {
return Err(Error::Overflow);
}
nz += nuf as usize;
nn_eff -= nuf as usize;
if nn_eff == 0 {
if zn.re < zero {
return Err(Error::Overflow);
}
let status = if precision_warning {
Accuracy::Reduced
} else {
Accuracy::Normal
};
return Ok((nz, status));
}
}
if fn_val <= two && az <= tol {
let half = T::from_f64(0.5);
let arg = half * az;
let aln = -fn_val * arg.ln();
if aln > elim {
return Err(Error::Overflow);
}
}
}
if zn.re < zero || (zn.re == zero && zn.im < zero && m == 2) {
let mr = -mm; let rl = T::rl();
let nw = zacon(
zn,
fnu,
scaling,
mr,
&mut y[..nn_eff],
rl,
fnul,
tol,
elim,
alim,
)?;
nz = nw; } else {
let nw = zbknu(zn, fnu, scaling, &mut y[..nn_eff], tol, elim, alim)?;
nz += nw;
}
};
let hpi_t = T::from_f64(HPI);
let sgn = hpi_t.copysign(-fmm);
let inu = fnu.to_i32().unwrap();
let inuh = inu / 2;
let ir = inu - 2 * inuh; let arg = (fnu - T::from_f64((inu - ir) as f64)) * sgn;
let rhpi = one / sgn;
let mut csgn = Complex::new(-rhpi * arg.sin(), rhpi * arg.cos());
if inuh % 2 != 0 {
csgn = -csgn;
}
let zti = -fmm;
let rtol = one / tol;
let ascle = ufl * rtol;
for item in y.iter_mut().take(nn_eff) {
let (scaled, atol) = if item.re.abs().max(item.im.abs()) <= ascle {
(*item * rtol, tol)
} else {
(*item, one)
};
*item = scaled * csgn * atol;
csgn = if zti > zero {
mul_i(csgn)
} else {
mul_neg_i(csgn)
};
}
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 besh_z_zero_returns_error() {
let z = Complex64::new(0.0, 0.0);
let mut y = [Complex64::new(0.0, 0.0)];
assert!(matches!(
zbesh(z, 0.0, HankelKind::First, Scaling::Unscaled, &mut y),
Err(Error::InvalidInput)
));
}
#[test]
fn besh_negative_order_returns_error() {
let z = Complex64::new(1.0, 0.0);
let mut y = [Complex64::new(0.0, 0.0)];
assert!(matches!(
zbesh(z, -1.0, HankelKind::First, Scaling::Unscaled, &mut y),
Err(Error::InvalidInput)
));
}
#[test]
fn besh_n_zero_returns_error() {
let z = Complex64::new(1.0, 0.0);
let mut y: [Complex64; 0] = [];
assert!(matches!(
zbesh(z, 0.0, HankelKind::First, Scaling::Unscaled, &mut y),
Err(Error::InvalidInput)
));
}
#[test]
fn besh_h1_conjugate_h2_real_axis() {
let z = Complex64::new(2.0, 0.0);
let fnu = 0.5;
let mut y1 = [Complex64::new(0.0, 0.0)];
let mut y2 = [Complex64::new(0.0, 0.0)];
zbesh(z, fnu, HankelKind::First, Scaling::Unscaled, &mut y1).unwrap();
zbesh(z, fnu, HankelKind::Second, Scaling::Unscaled, &mut y2).unwrap();
let diff = (y1[0] - y2[0].conj()).norm();
let scale = y1[0].norm();
assert!(
diff / scale < 1e-14,
"H^(1) != conj(H^(2)) on real axis, err = {:.2e}",
diff / scale
);
}
}