#![allow(clippy::excessive_precision)]
use num_complex::Complex;
use crate::machine::BesselFloat;
use crate::types::{IkFlag, SumOption};
use crate::utils::{mul_add, zdiv};
const CON: [f64; 2] = [3.98942280401432678e-01, 1.25331413731550025e+00];
#[rustfmt::skip]
const C_COEFFS: [f64; 120] = [
1.00000000000000000e+00, -2.08333333333333333e-01,
1.25000000000000000e-01, 3.34201388888888889e-01,
-4.01041666666666667e-01, 7.03125000000000000e-02,
-1.02581259645061728e+00, 1.84646267361111111e+00,
-8.91210937500000000e-01, 7.32421875000000000e-02,
4.66958442342624743e+00, -1.12070026162229938e+01,
8.78912353515625000e+00, -2.36408691406250000e+00,
1.12152099609375000e-01, -2.82120725582002449e+01,
8.46362176746007346e+01, -9.18182415432400174e+01,
4.25349987453884549e+01, -7.36879435947963170e+00,
2.27108001708984375e-01, 2.12570130039217123e+02,
-7.65252468141181642e+02, 1.05999045252799988e+03,
-6.99579627376132541e+02, 2.18190511744211590e+02,
-2.64914304869515555e+01, 5.72501420974731445e-01,
-1.91945766231840700e+03, 8.06172218173730938e+03,
-1.35865500064341374e+04, 1.16553933368645332e+04,
-5.30564697861340311e+03, 1.20090291321635246e+03,
-1.08090919788394656e+02, 1.72772750258445740e+00,
2.02042913309661486e+04, -9.69805983886375135e+04,
1.92547001232531532e+05, -2.03400177280415534e+05,
1.22200464983017460e+05, -4.11926549688975513e+04,
7.10951430248936372e+03, -4.93915304773088012e+02,
6.07404200127348304e+00, -2.42919187900551333e+05,
1.31176361466297720e+06, -2.99801591853810675e+06,
3.76327129765640400e+06, -2.81356322658653411e+06,
1.26836527332162478e+06, -3.31645172484563578e+05,
4.52187689813627263e+04, -2.49983048181120962e+03,
2.43805296995560639e+01, 3.28446985307203782e+06,
-1.97068191184322269e+07, 5.09526024926646422e+07,
-7.41051482115326577e+07, 6.63445122747290267e+07,
-3.75671766607633513e+07, 1.32887671664218183e+07,
-2.78561812808645469e+06, 3.08186404612662398e+05,
-1.38860897537170405e+04, 1.10017140269246738e+02,
-4.93292536645099620e+07, 3.25573074185765749e+08,
-9.39462359681578403e+08, 1.55359689957058006e+09,
-1.62108055210833708e+09, 1.10684281682301447e+09,
-4.95889784275030309e+08, 1.42062907797533095e+08,
-2.44740627257387285e+07, 2.24376817792244943e+06,
-8.40054336030240853e+04, 5.51335896122020586e+02,
8.14789096118312115e+08, -5.86648149205184723e+09,
1.86882075092958249e+10, -3.46320433881587779e+10,
4.12801855797539740e+10, -3.30265997498007231e+10,
1.79542137311556001e+10, -6.56329379261928433e+09,
1.55927986487925751e+09, -2.25105661889415278e+08,
1.73951075539781645e+07, -5.49842327572288687e+05,
3.03809051092238427e+03, -1.46792612476956167e+10,
1.14498237732025810e+11, -3.99096175224466498e+11,
8.19218669548577329e+11, -1.09837515608122331e+12,
1.00815810686538209e+12, -6.45364869245376503e+11,
2.87900649906150589e+11, -8.78670721780232657e+10,
1.76347306068349694e+10, -2.16716498322379509e+09,
1.43157876718888981e+08, -3.87183344257261262e+06,
1.82577554742931747e+04, 2.86464035717679043e+11,
-2.40629790002850396e+12, 9.10934118523989896e+12,
-2.05168994109344374e+13, 3.05651255199353206e+13,
-3.16670885847851584e+13, 2.33483640445818409e+13,
-1.23204913055982872e+13, 4.61272578084913197e+12,
-1.19655288019618160e+12, 2.05914503232410016e+11,
-2.18229277575292237e+10, 1.24700929351271032e+09,
-2.91883881222208134e+07, 1.18838426256783253e+05,
];
#[derive(Clone, Copy, Debug)]
pub(crate) struct UnikCache<T: BesselFloat> {
pub init: usize,
pub cwrk: [Complex<T>; 16],
pub zeta1: Complex<T>,
pub zeta2: Complex<T>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct UnikOutput<T: BesselFloat> {
pub phi: Complex<T>,
pub zeta1: Complex<T>,
pub zeta2: Complex<T>,
pub sum: Complex<T>,
pub cache: UnikCache<T>,
}
#[inline]
fn compute_sum<T: BesselFloat>(ikflg: IkFlag, cache: &UnikCache<T>) -> UnikOutput<T> {
let zero = T::zero();
let one = T::one();
let czero = Complex::new(zero, zero);
let (sum, con_idx) = match ikflg {
IkFlag::I => {
let mut sr = czero;
for i in 0..cache.init {
sr = sr + cache.cwrk[i];
}
(sr, 0usize)
}
IkFlag::K => {
let mut sr = czero;
let mut tr = one;
for i in 0..cache.init {
sr = sr + cache.cwrk[i] * tr;
tr = -tr;
}
(sr, 1usize)
}
};
let con_val = T::from_f64(CON[con_idx]);
let phi = cache.cwrk[15] * con_val;
UnikOutput {
phi,
zeta1: cache.zeta1,
zeta2: cache.zeta2,
sum,
cache: *cache,
}
}
pub(crate) fn zunik<T: BesselFloat>(
zr: Complex<T>,
fnu: T,
ikflg: IkFlag,
ipmtr: SumOption,
tol: T,
cache: Option<UnikCache<T>>,
) -> UnikOutput<T> {
let zero = T::zero();
let one = T::one();
let czero = Complex::new(zero, zero);
let cone = Complex::from(one);
if let Some(c) = cache {
if c.init > 0 {
return compute_sum(ikflg, &c);
}
}
let rfn = one / fnu;
let test = T::MACH_TINY * T::from_f64(1.0e3);
let ac = fnu * test;
if zr.re.abs() <= ac && zr.im.abs() <= ac {
let zeta1 = Complex::new(T::from_f64(2.0) * test.ln().abs() + fnu, zero);
let zeta2 = Complex::new(fnu, zero);
return UnikOutput {
phi: cone,
zeta1,
zeta2,
sum: czero,
cache: UnikCache {
init: 0,
cwrk: [czero; 16],
zeta1,
zeta2,
},
};
}
let t = zr * rfn;
let s = cone + t * t;
let sr = s.sqrt();
let zn = zdiv(cone + sr, t);
let ln_zn = zn.ln();
let zeta1 = ln_zn * fnu;
let zeta2 = sr * fnu;
let t_inv = zdiv(cone, sr);
let sr_new = t_inv * rfn;
let mut cwrk = [czero; 16];
cwrk[15] = sr_new.sqrt();
let con_val = T::from_f64(match ikflg {
IkFlag::I => CON[0],
IkFlag::K => CON[1],
});
let phi = cwrk[15] * con_val;
if ipmtr == SumOption::SkipSum {
return UnikOutput {
phi,
zeta1,
zeta2,
sum: czero,
cache: UnikCache {
init: 0,
cwrk,
zeta1,
zeta2,
},
};
}
let t2 = zdiv(cone, s);
cwrk[0] = cone;
let mut crfn = cone;
let mut ac_val = one;
let mut l: usize = 0; let mut init: usize = 15;
#[allow(clippy::needless_range_loop)]
for k in 1..=14 {
let mut s_sum = czero;
for _j in 0..=k {
l += 1;
let c_val = T::from_f64(C_COEFFS[l]);
s_sum = mul_add(s_sum, t2, Complex::new(c_val, zero));
}
crfn = crfn * sr_new;
cwrk[k] = crfn * s_sum;
ac_val = ac_val * rfn;
let test_val = cwrk[k].re.abs() + cwrk[k].im.abs();
if ac_val < tol && test_val < tol {
init = k + 1; break;
}
}
let cache = UnikCache {
init,
cwrk,
zeta1,
zeta2,
};
compute_sum(ikflg, &cache)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{IkFlag, SumOption};
use num_complex::Complex64;
const TOL: f64 = 2.220446049250313e-16;
#[test]
fn zunik_cache_reuse() {
let zr = Complex64::new(3.0, 1.0);
let result_k = zunik(zr, 15.0, IkFlag::K, SumOption::Full, TOL, None);
let result_i = zunik(
zr,
15.0,
IkFlag::I,
SumOption::Full,
TOL,
Some(result_k.cache),
);
assert_eq!(result_i.zeta1.re, result_k.zeta1.re);
assert_eq!(result_i.zeta1.im, result_k.zeta1.im);
assert_eq!(result_i.zeta2.re, result_k.zeta2.re);
assert_eq!(result_i.zeta2.im, result_k.zeta2.im);
let con1 = CON[0];
let con2 = CON[1];
let phi_i_mag =
(result_i.phi.re * result_i.phi.re + result_i.phi.im * result_i.phi.im).sqrt();
let phi_k_mag =
(result_k.phi.re * result_k.phi.re + result_k.phi.im * result_k.phi.im).sqrt();
assert!(
(phi_i_mag / phi_k_mag - con1 / con2).abs() < 1e-12,
"phi magnitude ratio: {}",
phi_i_mag / phi_k_mag
);
let result_i_fresh = zunik(zr, 15.0, IkFlag::I, SumOption::Full, TOL, None);
assert!((result_i.sum.re - result_i_fresh.sum.re).abs() < 1e-14);
}
#[test]
fn zunik_ipmtr_1() {
let zr = Complex64::new(4.0, 2.0);
let result = zunik(zr, 12.0, IkFlag::I, SumOption::SkipSum, TOL, None);
assert!(result.phi.re.abs() > 1e-10 || result.phi.im.abs() > 1e-10);
assert_eq!(result.cache.init, 0);
}
#[test]
fn zunik_overflow_precheck() {
let zr = Complex64::new(1e-310, 1e-310);
let result = zunik(zr, 1.0, IkFlag::I, SumOption::Full, TOL, None);
assert_eq!(result.phi.re, 1.0);
assert_eq!(result.phi.im, 0.0);
assert_eq!(result.zeta2.re, 1.0);
assert!(result.zeta1.re > result.zeta2.re);
assert_eq!(result.cache.init, 0);
}
}