#![allow(clippy::excessive_precision)]
use num_complex::Complex;
use crate::algo::acai::zacai;
use crate::algo::binu::zbinu;
use crate::algo::bknu::zbknu;
use crate::machine::BesselFloat;
use crate::types::{Accuracy, AiryDerivative, Error, Scaling};
use crate::utils::{mul_add_scalar, zabs, zdiv};
use crate::algo::constants::{PI, TTH};
const AI_C1: f64 = 3.55028053887817240e-01;
const AI_C2: f64 = 2.58819403792806799e-01;
const AI_COEF: f64 = 1.83776298473930683e-01;
const BI_C1: f64 = 6.14926627446000736e-01; const BI_C2: f64 = 4.48288357353826359e-01; const BI_COEF: f64 = 5.77350269189625765e-01;
fn airy_power_series<T: BesselFloat>(
z: Complex<T>,
az: T,
fid: T,
tol: T,
) -> (Complex<T>, Complex<T>) {
let one = T::one();
let mut s1 = Complex::from(one);
let mut s2 = Complex::from(one);
let aa = az * az;
if aa < tol / az {
return (s1, s2);
}
let mut trm1 = Complex::from(one);
let mut trm2 = Complex::from(one);
let mut atrm = one;
let z2 = z * z;
let z3 = z2 * z;
let az3 = az * aa;
let two = T::from_f64(2.0);
let three = T::from_f64(3.0);
let four = T::from_f64(4.0);
let nine = T::from_f64(9.0);
let eighteen = T::from_f64(18.0);
let twenty_four = T::from_f64(24.0);
let thirty = T::from_f64(30.0);
let mut ak = two + fid;
let mut bk = three - fid - fid;
let ck = four - fid;
let dk = three + fid + fid;
let mut d1 = ak * dk;
let mut d2 = bk * ck;
let mut ad = d1.min(d2);
ak = twenty_four + nine * fid;
bk = thirty - nine * fid;
for _ in 0..25 {
trm1 = trm1 * z3 / d1;
s1 = s1 + trm1;
trm2 = trm2 * z3 / d2;
s2 = s2 + trm2;
atrm = atrm * az3 / ad;
d1 = d1 + ak;
d2 = d2 + bk;
ad = d1.min(d2);
if atrm < tol * ad {
break;
}
ak = ak + eighteen;
bk = bk + eighteen;
}
(s1, s2)
}
#[inline]
pub(crate) fn zairy<T: BesselFloat>(
z: Complex<T>,
id: AiryDerivative,
kode: Scaling,
) -> Result<(Complex<T>, i32, Accuracy), Error> {
let zero = T::zero();
let one = T::one();
let tth = T::from_f64(TTH);
let c1 = T::from_f64(AI_C1);
let c2 = T::from_f64(AI_C2);
let coef = T::from_f64(AI_COEF);
let czero = Complex::new(zero, zero);
let az = zabs(z);
let tol = T::tol();
let fid = if id == AiryDerivative::Derivative {
one
} else {
zero
};
if az > one {
return zairy_large_z(z, az, id, kode, fid, tol, tth, coef);
}
if az < tol {
let aa = T::from_f64(1.0e3) * T::MACH_TINY;
if id == AiryDerivative::Value {
let mut s1 = czero;
if az > aa {
s1 = z * c2;
}
let ai = Complex::new(c1, zero) - s1;
return if kode == Scaling::Exponential {
Ok((zairy_scale_exp_zta(z, ai, tth), 0, Accuracy::Normal))
} else {
Ok((ai, 0, Accuracy::Normal))
};
}
let mut ai = Complex::new(-c2, zero);
let aa_sqrt = aa.sqrt();
if az > aa_sqrt {
let s1 = z * z * T::from_f64(0.5);
ai = ai + s1 * c1;
}
return if kode == Scaling::Exponential {
Ok((zairy_scale_exp_zta(z, ai, tth), 0, Accuracy::Normal))
} else {
Ok((ai, 0, Accuracy::Normal))
};
}
let (s1, s2) = airy_power_series(z, az, fid, tol);
if id == AiryDerivative::Value {
let ai = s1 * c1 - z * s2 * c2;
if kode == Scaling::Unscaled {
return Ok((ai, 0, Accuracy::Normal));
}
Ok((zairy_scale_exp_zta(z, ai, tth), 0, Accuracy::Normal))
} else {
let mut ai = -(s2 * c2);
if az > tol {
let cc = c1 / (one + fid);
ai = ai + z * s1 * z * cc;
}
if kode == Scaling::Unscaled {
return Ok((ai, 0, Accuracy::Normal));
}
Ok((zairy_scale_exp_zta(z, ai, tth), 0, Accuracy::Normal))
}
}
#[inline]
fn zairy_scale_exp_zta<T: BesselFloat>(z: Complex<T>, val: Complex<T>, tth: T) -> Complex<T> {
let zta = z * z.sqrt() * tth;
val * zta.exp()
}
#[inline]
fn airy_zta<T: BesselFloat>(z: Complex<T>, tth: T) -> (Complex<T>, Complex<T>) {
let zero = T::zero();
let csq = z.sqrt();
let mut zta = z * csq * tth;
let ak = zta.im;
if z.re < zero {
zta = Complex::new(-zta.re.abs(), ak);
}
if z.im == zero && z.re <= zero {
zta = Complex::new(zero, ak);
}
(csq, zta)
}
#[allow(clippy::too_many_arguments)]
fn zairy_large_z<T: BesselFloat>(
z: Complex<T>,
az: T,
id: AiryDerivative,
kode: Scaling,
fid: T,
tol: T,
tth: T,
coef: T,
) -> Result<(Complex<T>, i32, Accuracy), Error> {
let zero = T::zero();
let one = T::one();
let czero = Complex::new(zero, zero);
let fnu = (one + fid) / T::from_f64(3.0);
let elim = T::elim();
let alim = T::alim();
let rl = T::rl();
let alaz = az.ln();
let mut nz: i32 = 0;
let half = T::from_f64(0.5);
let mut aa = (half / tol).min(T::from_f64(1073741823.5));
aa = aa.powf(tth);
if az > aa {
return Err(Error::TotalPrecisionLoss);
}
let aa_sqrt = aa.sqrt();
let status = if az > aa_sqrt {
Accuracy::Reduced
} else {
Accuracy::Normal
};
let (csq, zta) = airy_zta(z, tth);
let mut iflag: i32 = 0;
let mut sfac = one;
let aa_zta = zta.re;
if aa_zta >= zero && z.re > zero {
if kode != Scaling::Exponential && aa_zta >= alim {
let test_val = -aa_zta - T::from_f64(0.25) * alaz;
iflag = 2;
sfac = one / tol;
if test_val < -elim {
return Ok((czero, 1, status));
}
}
let mut cy_buf = [czero];
let nz_k = zbknu(zta, fnu, kode, &mut cy_buf, tol, elim, alim)?;
nz += nz_k as i32;
let s1 = cy_buf[0] * coef;
return zairy_form_result(z, csq, s1, id, iflag, sfac, nz, status);
}
if kode != Scaling::Exponential {
if -aa_zta > alim {
let test_val = -aa_zta + T::from_f64(0.25) * alaz;
iflag = 1;
sfac = tol;
if test_val > elim {
return Err(Error::Overflow);
}
}
}
let mr: i32 = if z.im < zero { -1 } else { 1 };
let mut cy_buf = [czero];
let nn = zacai(zta, fnu, kode, mr, &mut cy_buf, rl, tol, elim, alim)?;
if nn < 0 {
return if nn == -1 {
Err(Error::Overflow)
} else {
Err(Error::ConvergenceFailure)
};
}
nz += nn;
let s1 = cy_buf[0] * coef;
zairy_form_result(z, csq, s1, id, iflag, sfac, nz, status)
}
#[allow(clippy::too_many_arguments)]
fn zairy_form_result<T: BesselFloat>(
z: Complex<T>,
csq: Complex<T>,
s1: Complex<T>,
id: AiryDerivative,
iflag: i32,
sfac: T,
nz: i32,
status: Accuracy,
) -> Result<(Complex<T>, i32, Accuracy), Error> {
if iflag == 0 {
if id == AiryDerivative::Value {
Ok((csq * s1, nz, status))
} else {
Ok((-(z * s1), nz, status))
}
} else {
let s1s = s1 * sfac;
if id == AiryDerivative::Value {
Ok((s1s * csq / sfac, nz, status))
} else {
Ok((-(s1s * z) / sfac, nz, status))
}
}
}
#[inline]
pub(crate) fn zbiry<T: BesselFloat>(
z: Complex<T>,
id: AiryDerivative,
kode: Scaling,
) -> Result<(Complex<T>, Accuracy), Error> {
let zero = T::zero();
let one = T::one();
let tth = T::from_f64(TTH);
let c1 = T::from_f64(BI_C1);
let c2 = T::from_f64(BI_C2);
let coef = T::from_f64(BI_COEF);
let pi_t = T::from_f64(PI);
let az = zabs(z);
let tol = T::tol();
let fid = if id == AiryDerivative::Derivative {
one
} else {
zero
};
if az > one {
return zbiry_large_z(z, az, id, kode, fid, tol, tth, coef, pi_t);
}
if az < tol {
let bi = Complex::new(c1.fma(one - fid, fid * c2), zero);
return Ok((bi, Accuracy::Normal));
}
let (s1, s2) = airy_power_series(z, az, fid, tol);
if id == AiryDerivative::Value {
let bi = s1 * c1 + z * s2 * c2;
if kode == Scaling::Unscaled {
return Ok((bi, Accuracy::Normal));
}
Ok((zbiry_scale_exp(z, bi, tth), Accuracy::Normal))
} else {
let mut bi = s2 * c2;
if az > tol {
let cc = c1 / (one + fid);
bi = bi + z * s1 * z * cc;
}
if kode == Scaling::Unscaled {
return Ok((bi, Accuracy::Normal));
}
Ok((zbiry_scale_exp(z, bi, tth), Accuracy::Normal))
}
}
#[inline]
fn zbiry_scale_exp<T: BesselFloat>(z: Complex<T>, val: Complex<T>, tth: T) -> Complex<T> {
let zta_re = (z * z.sqrt() * tth).re;
val * (-zta_re.abs()).exp()
}
#[allow(clippy::too_many_arguments)]
fn zbiry_large_z<T: BesselFloat>(
z: Complex<T>,
az: T,
id: AiryDerivative,
kode: Scaling,
fid: T,
tol: T,
tth: T,
coef: T,
pi_t: T,
) -> Result<(Complex<T>, Accuracy), Error> {
let zero = T::zero();
let one = T::one();
let two = T::from_f64(2.0);
let three = T::from_f64(3.0);
let half = T::from_f64(0.5);
let fnu = (one + fid) / three;
let elim = T::elim();
let alim = T::alim();
let rl = T::rl();
let fnul = T::fnul();
let mut aa = (half / tol).min(T::from_f64(1073741823.5));
aa = aa.powf(tth);
if az > aa {
return Err(Error::TotalPrecisionLoss);
}
let aa_sqrt = aa.sqrt();
let status = if az > aa_sqrt {
Accuracy::Reduced
} else {
Accuracy::Normal
};
let (csq, mut zta) = airy_zta(z, tth);
let mut sfac = one;
let aa_zta = zta.re;
if kode != Scaling::Exponential {
let bb_abs = aa_zta.abs();
if bb_abs >= alim {
let bb_test = bb_abs + T::from_f64(0.25) * az.ln();
sfac = tol;
if bb_test > elim {
return Err(Error::Overflow);
}
}
}
let mut fmr = zero;
if !(aa_zta >= zero && z.re > zero) {
fmr = pi_t;
if z.im < zero {
fmr = -pi_t;
}
zta = -zta;
}
let czero = Complex::new(zero, zero);
let mut cy1_buf = [czero];
zbinu(zta, fnu, kode, &mut cy1_buf, rl, fnul, tol, elim, alim)?;
let aa_fmr = fmr * fnu;
let phase = Complex::new(aa_fmr.cos(), aa_fmr.sin());
let mut s1 = phase * cy1_buf[0] * sfac;
let fnu2 = (two - fid) / three;
let mut cy2_buf = [czero; 2];
zbinu(zta, fnu2, kode, &mut cy2_buf, rl, fnul, tol, elim, alim)?;
let cy2_0 = cy2_buf[0] * sfac;
let cy2_1 = cy2_buf[1] * sfac;
let s2 = mul_add_scalar(zdiv(cy2_0, zta), fnu2 + fnu2, cy2_1);
let aa_rot = fmr * (fnu2 - one);
let phase2 = Complex::new(aa_rot.cos(), aa_rot.sin());
s1 = (s1 + phase2 * s2) * coef;
if id == AiryDerivative::Value {
Ok((csq * s1 / sfac, status))
} else {
Ok((z * s1 / sfac, status))
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_complex::Complex64;
#[test]
fn zairy_zero() {
let (ai, nz, _status) = zairy(
Complex64::new(0.0, 0.0),
AiryDerivative::Value,
Scaling::Unscaled,
)
.unwrap();
assert_eq!(nz, 0);
assert!((ai.re - AI_C1).abs() < 1e-15);
assert!(ai.im.abs() < 1e-15);
}
#[test]
fn zairy_zero_deriv() {
let (ai, nz, _status) = zairy(
Complex64::new(0.0, 0.0),
AiryDerivative::Derivative,
Scaling::Unscaled,
)
.unwrap();
assert_eq!(nz, 0);
assert!((ai.re - (-AI_C2)).abs() < 1e-15);
assert!(ai.im.abs() < 1e-15);
}
#[test]
fn zairy_small_real() {
let (ai, nz, _status) = zairy(
Complex64::new(0.5, 0.0),
AiryDerivative::Value,
Scaling::Unscaled,
)
.unwrap();
assert_eq!(nz, 0);
assert!((ai.re - 0.23169360648083349).abs() < 1e-13);
assert!(ai.im.abs() < 1e-15);
}
#[test]
fn zairy_negative_real() {
let (ai, nz, _status) = zairy(
Complex64::new(-1.0, 0.0),
AiryDerivative::Value,
Scaling::Unscaled,
)
.unwrap();
assert_eq!(nz, 0);
assert!((ai.re - 0.53556088329235176).abs() < 1e-13);
assert!(ai.im.abs() < 1e-15);
}
#[test]
fn zairy_complex() {
let (ai, nz, _status) = zairy(
Complex64::new(1.0, 1.0),
AiryDerivative::Value,
Scaling::Unscaled,
)
.unwrap();
assert_eq!(nz, 0);
assert!((ai.re - 0.060458308371838149).abs() < 1e-13);
assert!((ai.im - (-0.15188956587718140)).abs() < 1e-13);
}
#[test]
fn zbiry_zero() {
let (bi, _status) = zbiry(
Complex64::new(0.0, 0.0),
AiryDerivative::Value,
Scaling::Unscaled,
)
.unwrap();
assert!((bi.re - BI_C1).abs() < 1e-15);
assert!(bi.im.abs() < 1e-15);
}
#[test]
fn zbiry_zero_deriv() {
let (bi, _status) = zbiry(
Complex64::new(0.0, 0.0),
AiryDerivative::Derivative,
Scaling::Unscaled,
)
.unwrap();
assert!((bi.re - BI_C2).abs() < 1e-15);
assert!(bi.im.abs() < 1e-15);
}
#[test]
fn zbiry_small_real() {
let (bi, _status) = zbiry(
Complex64::new(0.5, 0.0),
AiryDerivative::Value,
Scaling::Unscaled,
)
.unwrap();
assert!((bi.re - 0.85427704310315549).abs() < 1e-13);
assert!(bi.im.abs() < 1e-15);
}
#[test]
fn airy_wronskian() {
let z = Complex64::new(0.5, 0.3);
let (ai, _, _) = zairy(z, AiryDerivative::Value, Scaling::Unscaled).unwrap();
let (ai_p, _, _) = zairy(z, AiryDerivative::Derivative, Scaling::Unscaled).unwrap();
let (bi, _) = zbiry(z, AiryDerivative::Value, Scaling::Unscaled).unwrap();
let (bi_p, _) = zbiry(z, AiryDerivative::Derivative, Scaling::Unscaled).unwrap();
let w = ai * bi_p - ai_p * bi;
let inv_pi = 1.0 / core::f64::consts::PI;
assert!(
(w.re - inv_pi).abs() < 1e-13,
"Wronskian real part: got {}, expected {}",
w.re,
inv_pi
);
assert!(w.im.abs() < 1e-13, "Wronskian imaginary part: got {}", w.im);
}
}