use num_traits::Float;
use crate::{
carlson::{elliprf_unchecked, elliprj_unchecked},
crate_util::check,
ellipe, ellipk, StrErr,
};
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub fn ellippi<T: Float>(n: T, m: T) -> Result<T, StrErr> {
if n > 1.0 - epsilon!() {
if n > 1.0 {
if n <= 1.0 + epsilon!() {
return Ok(ellipk(m)? - ellipe(m)? / (1.0 - m));
}
return Ok(-3.0.recip() * m / n * elliprj_unchecked(0.0, 1.0 - m, 1.0, 1.0 - m / n));
}
if n == 1.0 {
return Err("ellippi: n cannot be 1.");
}
return Ok(inf!());
}
let ans = ellippi_unchecked(n, m);
#[cfg(not(feature = "test_force_fail"))]
if ans.is_finite() {
return Ok(ans);
}
check!(@nan, ellippi, [n, m]);
if m > 1.0 - epsilon!() {
if m > 1.0 {
return Err("ellippi: m must not be greater than 1.");
}
let sign = (1.0 - n).signum();
return Ok(sign * inf!());
}
let lim_min = 1e-2 * min_val!();
if n < lim_min || m < lim_min {
return Ok(0.0);
}
Err("ellippi: Unexpected error.")
}
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
#[inline]
pub fn ellippi_unchecked<T: Float>(n: T, m: T) -> T {
if n <= 0.0 {
if 1.0 - m < epsilon!() {
return -inf!();
}
if n == 0.0 {
if m == 0.0 {
return pi_2!();
}
return ellipk(m).unwrap_or(nan!());
}
let nn = (m - n) / (1.0 - n);
let nm1 = (1.0 - m) / (1.0 - n);
let mut result = ellippi_vc(nn, m, nm1);
result = result * -n / (1.0 - n);
result = result * (1.0 - m) / (m - n);
result = result + ellipk(m).unwrap_or(nan!()) * m / (m - n);
return result;
}
if m == n {
let mc = 1.0 - m;
return 1.0 / mc * ellipe(m).unwrap_or(nan!());
}
let vc = 1.0 - n;
ellippi_vc(n, m, vc)
}
#[inline]
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub fn ellippi_vc<T: Float>(n: T, m: T, vc: T) -> T {
let x = 0.0;
let y = 1.0 - m;
let z = 1.0;
let p = vc;
elliprf_unchecked(x, y, z) + n * elliprj_unchecked(x, y, z, p) / 3.0
}
#[cfg(not(feature = "test_force_fail"))]
#[cfg(test)]
mod tests {
use super::*;
use crate::{compare_test_data_boost, compare_test_data_wolfram};
#[test]
fn test_ellippi() {
compare_test_data_boost!("ellippi2_data_f64.txt", ellippi, 2, 4.4e-16);
}
#[test]
fn test_ellippi_wolfram() {
compare_test_data_wolfram!("ellippi_data.csv", ellippi, 2, 5e-15);
}
#[test]
fn test_ellippi_wolfram_neg() {
compare_test_data_wolfram!("ellippi_neg.csv", ellippi, 2, 5e-15);
}
#[test]
fn test_ellippi_wolfram_pv() {
compare_test_data_wolfram!("ellippi_pv.csv", ellippi, 2, 1e-14);
}
#[test]
fn test_ellippi_special_cases() {
use std::f64::{
consts::{FRAC_PI_2, PI},
EPSILON, INFINITY, NAN, NEG_INFINITY,
};
assert_eq!(
ellippi(0.5, 1.1),
Err("ellippi: m must not be greater than 1.")
);
assert_eq!(ellippi(1.0, 0.5), Err("ellippi: n cannot be 1."));
assert_eq!(ellippi(0.0, 0.5).unwrap(), ellipk(0.5).unwrap());
assert_eq!(ellippi(0.5, 0.0).unwrap(), PI / (2.0 * 0.5.sqrt()));
assert_eq!(ellippi(2.0, 0.0).unwrap(), 0.0);
assert_eq!(ellippi(2.0, 1.0).unwrap(), NEG_INFINITY);
assert_eq!(ellippi(0.5, 1.0).unwrap(), INFINITY);
assert_eq!(ellippi(-2.0, 1.0).unwrap(), INFINITY);
assert_eq!(ellippi(1.0 - 0.5 * EPSILON, 0.5).unwrap(), INFINITY);
assert_eq!(
ellippi(1.0 + EPSILON, 0.5).unwrap(),
ellipk(0.5).unwrap() - ellipe(0.5).unwrap() / 0.5
);
assert_eq!(ellippi(0.0, 0.0).unwrap(), FRAC_PI_2);
assert_eq!(ellippi(0.5, 0.5).unwrap(), ellipe(0.5).unwrap() / 0.5);
assert_eq!(ellippi(INFINITY, 0.5).unwrap(), 0.0);
assert_eq!(ellippi(NEG_INFINITY, 0.5).unwrap(), 0.0);
assert_eq!(ellippi(0.5, NEG_INFINITY).unwrap(), 0.0);
assert_eq!(ellippi(NAN, 0.5), Err("ellippi: Arguments cannot be NAN."));
assert_eq!(ellippi(0.5, NAN), Err("ellippi: Arguments cannot be NAN."));
assert_eq!(
ellippi(0.5, INFINITY),
Err("ellippi: m must not be greater than 1.")
);
}
}
#[cfg(feature = "test_force_fail")]
crate::test_force_unreachable! {
assert_eq!(ellippi(0.5, 0.5), Err("ellippi: Unexpected error."));
}