use num_traits::Float;
use crate::{
bulirsch::{cel2::cel2_with_const, constants::BulirschConst, N_MAX_ITERATIONS},
crate_util::{case, check, declare, let_mut},
StrErr,
};
pub fn el2<T: Float>(x: T, kc: T, a: T, b: T) -> Result<T, StrErr> {
if core::mem::size_of::<T>() <= 4 {
el2_with_const::<T, f32>(x, kc, a, b)
} else {
el2_with_const::<T, f64>(x, kc, a, b)
}
}
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
#[inline]
pub fn el2_with_const<T: Float, C: BulirschConst<T>>(x: T, kc: T, a: T, b: T) -> Result<T, StrErr> {
let ans = el2_unchecked::<T, C>(x, kc, a, b);
if ans.is_finite() {
return Ok(ans);
}
check!(@nan, el2, [x, kc, a, b]);
check!(@zero, el2, [kc]);
case!(x == T::zero(), T::zero());
if x == inf!() {
return cel2_with_const::<T, C>(kc, a, b);
}
Err("el2: Failed to converge.")
}
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
#[inline]
pub fn el2_unchecked<T: Float, C: BulirschConst<T>>(x: T, kc: T, a: T, b: T) -> T {
let_mut!(b);
declare!(mut [c = x * x, d = T::one() + c, p = ((T::one() + kc * kc * c) / d).sqrt()]);
d = x / d;
c = d / (p * 2.0);
let z = a - b;
let mut i = a;
let mut a = (b + a) / 2.0;
declare!(mut [y = x.recip().abs(), f = T::zero(), l = 0, m = T::one(), kc = kc.abs(), e, g]);
for _ in 0..N_MAX_ITERATIONS {
b = i * kc + b;
e = m * kc;
g = e / p;
d = f * g + d;
f = c;
i = a;
p = g + p;
c = (d / p + c) / 2.0;
g = m;
m = kc + m;
a = (b / m + a) / 2.0;
y = -e / y + y;
if y == 0.0 {
y = e.sqrt() * C::cb();
}
if (g - kc).abs() > C::ca() * g {
kc = e.sqrt() * 2.0;
l *= 2;
if y < 0.0 {
l += 1;
}
continue;
}
if y < 0.0 {
l += 1;
}
let e = x.signum() * ((m / y).atan() + pi!() * T::from(l).unwrap()) * a / m;
return e + c * z;
}
nan!()
}
#[cfg(not(feature = "test_force_fail"))]
#[cfg(test)]
mod tests {
use super::*;
use crate::compare_test_data_wolfram;
#[test]
fn test_el2() {
compare_test_data_wolfram!("el2_data.csv", el2, 4, 100.0 * f64::EPSILON);
}
#[test]
fn test_el2_special_cases() {
use crate::bulirsch::cel2;
use std::f64::{INFINITY, NAN};
assert_eq!(el2(0.0, 0.5, 1.0, 1.0).unwrap(), 0.0);
assert_eq!(el2(0.5, 0.0, 1.0, 1.0), Err("el2: kc cannot be zero."));
assert_eq!(el2(0.5, 0.5, 0.0, 0.0).unwrap(), 0.0);
assert_eq!(
el2(INFINITY, 0.5, 1.0, 1.0).unwrap(),
cel2(0.5, 1.0, 1.0).unwrap()
);
assert_eq!(
el2(NAN, 0.5, 1.0, 1.0),
Err("el2: Arguments cannot be NAN.")
);
assert_eq!(
el2(0.5, NAN, 1.0, 1.0),
Err("el2: Arguments cannot be NAN.")
);
}
}
#[cfg(feature = "test_force_fail")]
crate::test_force_unreachable! {
use crate::bulirsch::constants::DefaultPrecision;
assert_eq!(el2_with_const::<f64, DefaultPrecision>(0.5, 0.5, 0.5, 0.5), Err("el2: Failed to converge."));
}