use num_traits::Float;
use super::coeffs::{self, *};
use crate::{
crate_util::{check, declare},
polyeval, StrErr,
};
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub fn ellipk<T: Float>(m: T) -> Result<T, StrErr> {
if m < 0.0 && m.is_finite() {
return ellipk_precise(m);
}
_ellipk(m)
}
#[inline]
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub(crate) fn _ellipk<T: Float>(m: T) -> Result<T, StrErr> {
match (m * 20.0).to_i64() {
Some(0) | Some(1) => Ok(polyeval(m - 0.05, &coeffs::to_t(K_0_1))),
Some(2) | Some(3) => Ok(polyeval(m - 0.15, &coeffs::to_t(K_2_3))),
Some(4) | Some(5) => Ok(polyeval(m - 0.25, &coeffs::to_t(K_4_5))),
Some(6) | Some(7) => Ok(polyeval(m - 0.35, &coeffs::to_t(K_6_7))),
Some(8) | Some(9) => Ok(polyeval(m - 0.45, &coeffs::to_t(K_8_9))),
Some(10) | Some(11) => Ok(polyeval(m - 0.55, &coeffs::to_t(K_10_11))),
Some(12) | Some(13) => Ok(polyeval(m - 0.65, &coeffs::to_t(K_12_13))),
Some(14) | Some(15) => Ok(polyeval(m - 0.75, &coeffs::to_t(K_14_15))),
Some(16) => Ok(polyeval(m - 0.825, &coeffs::to_t(K_16))),
Some(17) => Ok(polyeval(m - 0.875, &coeffs::to_t(K_17))),
Some(_) => ellipk_precise(m),
None => {
check!(@nan, ellipk, [m]);
if m == neg_inf!() {
return Ok(0.0);
}
#[cfg(not(feature = "test_force_fail"))]
if m > 1.0 {
return Err("ellipk: m must not be greater than 1.");
}
Err("ellipk: Unexpected error.")
}
}
}
#[inline]
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub(crate) fn ellipk_precise<T: Float>(m: T) -> Result<T, StrErr> {
if m >= 1.0 {
if m == 1.0 {
return Ok(inf!());
}
return Err("ellipk: m must not be greater than 1.");
}
Ok(ellipk_precise_unchecked(m))
}
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
#[inline]
pub fn ellipk_precise_unchecked<T: Float>(m: T) -> T {
declare!(mut [xn = T::one(), yn = (T::one() - m).sqrt(), t]);
let tol = if core::mem::size_of::<T>() <= 4 {
1e-3
} else {
1e-7
};
for _ in 0..MAX_ITERATION {
let diff = (xn - yn).abs();
if diff >= tol * xn.abs() {
t = (xn * yn).sqrt();
xn = (xn + yn) / 2.0;
yn = t;
continue;
}
break;
}
let diff = xn - yn;
let sum = xn + yn;
let corr = 1.0 - (diff * diff) / (4.0 * sum * sum);
pi!() / (sum * corr)
}
const MAX_ITERATION: usize = 32;
#[cfg(not(feature = "test_force_fail"))]
#[cfg(test)]
mod tests {
use super::*;
use crate::compare_test_data_boost;
use crate::compare_test_data_wolfram;
#[test]
fn test_ellipk_boost() {
compare_test_data_boost!("ellipk_data.txt", ellipk, 1, 2.8e-16);
}
#[test]
fn test_ellipk_wolfram() {
compare_test_data_wolfram!("./tests/data/coverage", "ellipk_cov.csv", ellipk, 1, 2e-12);
compare_test_data_wolfram!("ellipk_data.csv", ellipk, 1, 5e-15);
}
#[test]
fn test_ellipk_special_cases() {
use std::f64::{consts::FRAC_PI_2, INFINITY, NAN, NEG_INFINITY};
assert_eq!(ellipk(0.0).unwrap(), FRAC_PI_2);
assert_eq!(ellipk(1.0).unwrap(), INFINITY);
assert!(ellipk(-1.0).unwrap().is_finite());
assert_eq!(ellipk(1.1), Err("ellipk: m must not be greater than 1."));
assert_eq!(ellipk(NAN), Err("ellipk: Arguments cannot be NAN."));
assert_eq!(
ellipk(INFINITY),
Err("ellipk: m must not be greater than 1.")
);
assert_eq!(ellipk(NEG_INFINITY).unwrap(), 0.0);
}
#[test]
fn test_large_negative_parameters() {
crate::assert_close!(ellipk(-1e18).unwrap(), 2.2109560198066302e-8, 2e-15);
crate::assert_close!(ellipk(-1e100).unwrap(), 1.1651554901082218e-48, 3e-15);
crate::assert_close!(ellipk(-f64::MAX).unwrap(), 2.6572401146362276e-152, 3e-15);
assert_eq!(ellipk(f64::NEG_INFINITY).unwrap(), 0.0);
assert!(ellipk(f64::NAN).is_err());
}
}
#[cfg(feature = "test_force_fail")]
crate::test_force_unreachable! {
assert_eq!(ellipk(f64::INFINITY), Err("ellipk: Unexpected error."));
}