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> {
check!(@nan, ellippi, [n, m]);
if m > 1.0 {
return Err("ellippi: m must not be greater than 1.");
}
if n == 1.0 {
return Err("ellippi: n cannot be 1.");
}
if m == 1.0 {
return Ok((1.0 - n).signum() * inf!());
}
if n > 1.0 {
return Ok(-3.0.recip() * m / n * elliprj_unchecked(0.0, 1.0 - m, 1.0, 1.0 - m / n));
}
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 {
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 m == 1.0 {
return -inf!();
}
if n == 0.0 {
if m == 0.0 {
return pi_2!();
}
return ellipk(m).unwrap_or(nan!());
}
if m == n {
return ellipe(m).unwrap_or(nan!()) / (1.0 - m);
}
if (m - n).abs() < 0.05 * n.abs() {
return ellippi_vc(n, m, 1.0 - n);
}
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, 7e-15);
}
#[test]
fn test_ellippi_wolfram_pv() {
compare_test_data_wolfram!("ellippi_pv.csv", ellippi, 2, 1e-14);
}
#[test]
fn test_ellippi_negative_diagonal() {
use crate::util::assert_close;
assert_close(ellippi(-0.5, -0.5).unwrap(), 1.1678475171298786, 1e-14);
assert_close(ellippi(-2.0, -2.0).unwrap(), 0.72814604758206706, 1e-14);
assert_close(ellippi(-10.0, -10.0).unwrap(), 0.33083073076525165, 1e-14);
assert_close(ellippi(-100.0, -100.0).unwrap(), 0.10108179128529279, 1e-14);
assert_close(
ellippi(-1000.0, -1000.0).unwrap(),
0.031675528525186074,
1e-14,
);
assert_close(
ellippi(-0.5, -0.5).unwrap(),
ellipe(-0.5).unwrap() / 1.5,
1e-14,
);
assert_close(
ellippi(0.5, 0.5).unwrap(),
ellipe(0.5).unwrap() / 0.5,
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);
crate::util::assert_close(
ellippi(1.0 - 0.5 * EPSILON, 0.5).unwrap(),
210828713.28594348,
2e-15,
);
crate::util::assert_close(
ellippi(1.0 + EPSILON, 0.5).unwrap(),
-0.8472130847939789,
2e-15,
);
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.")
);
}
#[test]
fn test_ellippi_invalid_input_does_not_abort() {
assert!(ellippi(f64::NAN, 0.5).is_err());
assert!(ellippi(2.0, f64::NAN).is_err());
if std::env::var("ELLIP_A05_PI_CHILD").is_ok() {
assert!(ellippi(2.0, 2.0).is_err());
return;
}
let status = std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"legendre::ellippi::tests::test_ellippi_invalid_input_does_not_abort",
])
.env("ELLIP_A05_PI_CHILD", "1")
.status()
.unwrap();
assert!(
status.success(),
"invalid Pi input aborted or failed: {status}"
);
}
#[test]
fn test_finite_below_singular_boundary() {
let u = f64::from_bits(1.0f64.to_bits() - 1);
crate::assert_close!(ellippi(u, 0.0).unwrap(), 149078413.4323951, 2e-15);
crate::assert_close!(ellippi(0.0, u).unwrap(), 19.75469464595844, 2e-15);
assert!(ellippi(1.0, 0.0).is_err());
assert_eq!(ellippi(0.0, 1.0).unwrap(), f64::INFINITY);
let u = f32::from_bits(1.0f32.to_bits() - 1);
crate::assert_close!(
ellippi(u, 0.0).unwrap() as f64,
(std::f32::consts::FRAC_PI_2 / (1.0 - u).sqrt()) as f64,
4e-7
);
crate::assert_close!(
ellippi(0.0, u).unwrap() as f64,
ellipk(u).unwrap() as f64,
4e-7
);
}
#[test]
fn test_principal_value_at_simultaneous_branch_limits() {
let n = f64::from_bits(1.0f64.to_bits() + 1);
let m = f64::from_bits(1.0f64.to_bits() - 1);
crate::assert_close!(ellippi(n, m).unwrap(), -4_214_834_719_445_440.5, 3e-15);
let n = f32::from_bits(1.0f32.to_bits() + 1);
let m = f32::from_bits(1.0f32.to_bits() - 1);
crate::assert_close!(ellippi(n, m).unwrap() as f64, -7_850_737.0, 4e-7);
}
}
#[cfg(feature = "test_force_fail")]
crate::test_force_unreachable! {
assert_eq!(ellippi(0.5, 0.5), Err("ellippi: Unexpected error."));
}