use num_traits::Float;
use crate::{
bulirsch::{
cel1::cel1_with_const, cel2::cel2_with_const, constants::BulirschConst, MAX_ITERATION,
},
crate_util::{case, check, declare},
StrErr,
};
pub fn cel<T: Float>(kc: T, p: T, a: T, b: T) -> Result<T, StrErr> {
if core::mem::size_of::<T>() <= 4 {
cel_with_const::<T, f32>(kc, p, a, b)
} else {
cel_with_const::<T, f64>(kc, p, a, b)
}
}
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
#[inline]
pub fn cel_with_const<T: Float, C: BulirschConst<T>>(kc: T, p: T, a: T, b: T) -> Result<T, StrErr> {
check!(@zero, cel, [kc, p]);
if p == 1.0 {
let res = if a == 1.0 && b == 1.0 {
cel1_with_const::<T, C>(kc)
} else {
cel2_with_const::<T, C>(kc, a, b)
};
if let Ok(v) = res {
return Ok(v);
}
}
let mut kc = kc.abs();
declare!(mut [pp = p, aa = a, bb = b, f, q, g]);
let mut e = kc;
let mut m = 1.0;
if pp > 0.0 {
pp = pp.sqrt();
bb = bb / pp;
} else {
f = kc * kc;
q = 1.0 - f;
g = 1.0 - pp;
f = f - pp;
q = (bb - aa * pp) * q;
pp = (f / g).sqrt();
aa = (aa - bb) / g;
bb = -q / (g * g * pp) + aa * pp;
}
let mut ans = T::nan();
for _ in 0..MAX_ITERATION {
f = aa;
let inv_pp = 1.0 / pp;
aa = bb * inv_pp + aa;
g = e * inv_pp;
bb = 2.0 * (f * g + bb);
pp = g + pp;
g = m;
m = kc + m;
if (g - kc).abs() > g * C::ca() {
kc = 2.0 * e.sqrt();
e = kc * m;
continue;
}
ans = pi_2!() * (aa * m + bb) / (m * (m + pp));
break;
}
if ans.is_finite() {
return Ok(ans);
}
check!(@nan, cel, [kc, p, a, b]);
check!(@multi, cel, "infinite", is_infinite, [kc, p, a, b]);
case!(@any [kc.abs(), p.abs()] == inf!(), T::zero());
if a.is_infinite() {
return Ok(a.signum() * inf!());
}
if b.is_infinite() {
return Ok(b.signum() * inf!());
}
Err("cel: Failed to converge.")
}
#[cfg(not(feature = "test_force_fail"))]
#[cfg(test)]
mod tests {
use itertools::iproduct;
use super::*;
use crate::{assert_close, ellipe, ellipk, ellippi, test_util::linspace};
#[test]
fn test_cel() {
fn _test(kc: f64, p: f64) {
let m = 1.0 - kc * kc;
let ellipk = ellipk(m).unwrap();
let ellipe = ellipe(m).unwrap();
assert_close! {ellipk, cel(kc, 1.0, 1.0, 1.0).unwrap(), 2e-12};
assert_close! {ellipe, cel(kc, 1.0, 1.0, kc * kc).unwrap(), 1e-15};
assert_close! {(ellipe - kc * kc * ellipk) / m, cel(kc, 1.0, 1.0, 0.0).unwrap(), 8.5e-14};
let n = 1.0 - p;
let ellippi = ellippi(n, m).unwrap();
assert_close! {(ellipk - ellipe) / m, cel(kc, 1.0, 0.0, 1.0).unwrap(), 6e-12};
assert_close! {ellippi, cel(kc, p, 1.0, 1.0).unwrap(), 3.5e-12};
assert_close! {(ellippi - ellipk) / (1.0 - p), cel(kc, p, 0.0, 1.0).unwrap(), 3.5e-12};
}
let linsp_kc = [
linspace(-1.0 + 1e-3, -1e-3, 100),
linspace(1e-3, 1.0 - 1e-3, 100),
]
.concat();
let linsp_p = linspace(1e-3, 1.0 - 1e-3, 10);
iproduct!(linsp_kc, linsp_p).for_each(|(kc, p)| _test(kc, p));
assert_close! {cel(1e-1, 4.1, 1.2, 1.1).unwrap(), 1.5464442694017956, 5e-16};
assert_close! {cel(1e-1, -4.1, 1.2, 1.1).unwrap(), -6.7687378198360556e-1, 5e-16};
}
#[test]
fn test_cel_special_cases() {
use std::f64::{INFINITY, NAN, NEG_INFINITY};
assert_eq!(cel(0.0, 1.0, 1.0, 1.0), Err("cel: kc cannot be zero."));
assert_eq!(cel(0.5, 0.0, 1.0, 1.0), Err("cel: p cannot be zero."));
assert_eq!(cel(0.5, 1.0, 0.0, 0.0).unwrap(), 0.0);
assert_eq!(cel(INFINITY, 0.5, 1.0, 1.0).unwrap(), 0.0);
assert_eq!(cel(NEG_INFINITY, 0.5, 1.0, 1.0).unwrap(), 0.0);
assert_eq!(cel(0.5, INFINITY, 1.0, 1.0).unwrap(), 0.0);
assert_eq!(cel(0.5, NEG_INFINITY, 1.0, 1.0).unwrap(), 0.0);
assert_eq!(cel(0.5, 1.0, INFINITY, 1.0).unwrap(), INFINITY);
assert_eq!(cel(0.5, 1.0, 1.0, INFINITY).unwrap(), INFINITY);
assert_eq!(cel(0.5, 1.0, NEG_INFINITY, 1.0).unwrap(), NEG_INFINITY);
assert_eq!(cel(0.5, 1.0, 1.0, NEG_INFINITY).unwrap(), NEG_INFINITY);
assert_eq!(
cel(NAN, 1.0, 1.0, 1.0),
Err("cel: Arguments cannot be NAN.")
);
assert_eq!(
cel(0.5, NAN, 1.0, 1.0),
Err("cel: Arguments cannot be NAN.")
);
assert_eq!(
cel(0.5, 1.0, NAN, 1.0),
Err("cel: Arguments cannot be NAN.")
);
assert_eq!(
cel(0.5, 1.0, 1.0, NAN),
Err("cel: Arguments cannot be NAN.")
);
}
}
#[cfg(feature = "test_force_fail")]
crate::test_force_unreachable! {
assert_eq!(cel(1e300, 0.2, 0.5, 0.5), Err("cel: Failed to converge."));
}