use core::mem::swap;
use num_traits::Float;
use crate::{
carlson::{elliprc_unchecked, elliprd_unchecked, elliprf_unchecked},
crate_util::{check, let_mut},
StrErr,
};
pub fn elliprg<T: Float>(x: T, y: T, z: T) -> Result<T, StrErr> {
check!(@neg, elliprg, [x, y, z]);
let ans = elliprg_unchecked(x, y, z);
if ans.is_finite() {
return Ok(ans);
}
check!(@nan, elliprg, [x, y, z]);
Err("elliprg: Arguments must be finite.")
}
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
#[inline]
pub fn elliprg_unchecked<T: Float>(x: T, y: T, z: T) -> T {
let_mut!(x, y, z);
if x < y {
swap(&mut x, &mut y);
}
if x < z {
swap(&mut x, &mut z);
}
if y > z {
swap(&mut y, &mut z);
}
if x == z {
if y == z {
return x.sqrt();
}
if y == 0.0 {
return pi!() * x.sqrt() / 4.0;
}
return (x * elliprc_unchecked(y, x) + y.sqrt()) / 2.0;
}
if y == z {
if y == 0.0 {
return x.sqrt() / 2.0;
}
return (y * elliprc_unchecked(x, y) + x.sqrt()) / 2.0;
}
if y == 0.0 {
let mut xn = x.sqrt();
let mut yn = z.sqrt();
let x0 = xn;
let y0 = yn;
let mut sum = 0.0;
let mut sum_pow = 0.25;
while (xn - yn).abs() >= 2.7 * epsilon!() * xn.abs() {
let t = (xn * yn).sqrt();
xn = (xn + yn) / 2.0;
yn = t;
sum_pow = sum_pow * 2.0;
sum = sum + sum_pow * (xn - yn) * (xn - yn);
}
let rf = pi!() / (xn + yn);
return ((x0 + y0) * (x0 + y0) / 4.0 - sum) * rf / 2.0;
}
(z * elliprf_unchecked(x, y, z) - (x - z) * (y - z) * elliprd_unchecked(x, y, z) / 3.0
+ (x * y / z).sqrt())
/ 2.0
}
#[cfg(not(feature = "test_force_fail"))]
#[cfg(test)]
mod tests {
use itertools::Itertools;
use super::*;
use crate::{assert_close, compare_test_data_boost};
fn __elliprg(inp: &[&f64]) -> f64 {
elliprg(*inp[0], *inp[1], *inp[2]).unwrap()
}
fn _elliprg(inp: &[f64]) -> f64 {
let res = elliprg(inp[0], inp[1], inp[2]).unwrap();
inp.iter().permutations(inp.len()).skip(1).for_each(|perm| {
assert_close!(res, __elliprg(&perm), 6.5e-16);
});
res
}
#[test]
fn test_elliprg() {
compare_test_data_boost!("elliprg_data.txt", _elliprg, 8.1e-16);
}
#[test]
fn test_elliprg_xxx() {
compare_test_data_boost!("elliprg_xxx.txt", _elliprg, 2.4e-16);
}
#[test]
fn test_elliprg_xy0() {
compare_test_data_boost!("elliprg_xy0.txt", _elliprg, 4.4e-16);
}
#[test]
fn test_elliprg_xyy() {
compare_test_data_boost!("elliprg_xyy.txt", _elliprg, 5.4e-16);
}
#[test]
fn test_elliprg_00x() {
compare_test_data_boost!("elliprg_00x.txt", _elliprg, f64::EPSILON);
}
#[test]
fn test_elliprg_special_cases() {
use std::f64::{INFINITY, NAN};
assert_eq!(
elliprg(-1.0, 1.0, 1.0),
Err("elliprg: Arguments must be non-negative.")
);
assert_eq!(
elliprg(1.0, -1.0, 1.0),
Err("elliprg: Arguments must be non-negative.")
);
assert_eq!(
elliprg(1.0, 1.0, -1.0),
Err("elliprg: Arguments must be non-negative.")
);
assert_eq!(
elliprg(NAN, 1.0, 1.0),
Err("elliprg: Arguments cannot be NAN.")
);
assert_eq!(
elliprg(1.0, NAN, 1.0),
Err("elliprg: Arguments cannot be NAN.")
);
assert_eq!(
elliprg(1.0, 1.0, NAN),
Err("elliprg: Arguments cannot be NAN.")
);
assert_eq!(
elliprg(INFINITY, 1.0, 1.0),
Err("elliprg: Arguments must be finite.")
);
assert_eq!(
elliprg(1.0, INFINITY, 1.0),
Err("elliprg: Arguments must be finite.")
);
assert_eq!(
elliprg(1.0, 1.0, INFINITY),
Err("elliprg: Arguments must be finite.")
);
}
}