use core::mem::swap;
use num_traits::Float;
use crate::{
carlson::elliprc_unchecked,
crate_util::{case, check, declare},
StrErr,
};
pub fn elliprf<T: Float>(x: T, y: T, z: T) -> Result<T, StrErr> {
let ans = elliprf_unchecked(x, y, z);
if ans.is_finite() {
return Ok(ans);
}
check!(@nan, elliprf, [x, y, z]);
check!(@neg, elliprf, [x, y, z]);
check!(@multi_zero, elliprf, [x, y, z]);
case!(@any [x, y, z] == inf!(), T::zero());
Err("elliprf: Failed to converge.")
}
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
#[inline]
pub fn elliprf_unchecked<T: Float>(x: T, y: T, z: T) -> T {
if x == y {
if x == z {
return 1.0 / x.sqrt();
}
if z == 0.0 {
return pi!() / (2.0 * x.sqrt());
}
return elliprc_unchecked(z, x);
}
if x == z {
if y == 0.0 {
return pi!() / (2.0 * x.sqrt());
}
return elliprc_unchecked(y, x);
}
if y == z {
if x == 0.0 {
return pi!() / (2.0 * y.sqrt());
}
return elliprc_unchecked(x, y);
}
declare!(mut [xn = x, yn = y, zn = z]);
if xn == 0.0 {
swap(&mut xn, &mut zn);
} else if yn == 0.0 {
swap(&mut yn, &mut zn);
}
if zn == 0.0 {
declare!(mut [xn = xn.sqrt(), yn = yn.sqrt(), t]);
for _ in 0..N_MAX_ITERATIONS {
if (xn - yn).abs() >= 2.7 * epsilon!() * xn.abs() {
t = (xn * yn).sqrt();
xn = (xn + yn) / 2.0;
yn = t;
continue;
}
break;
}
return pi!() / (xn + yn);
}
let mut an = (xn + yn + zn) / 3.0;
let a0 = an;
let mut q = (3.0 * epsilon!()).powf(-1.0 / 8.0)
* an.abs()
.max((an - xn).abs())
.max((an - yn).abs())
.max((an - zn).abs());
declare!(mut [fn_val = T::one(), ans = nan!()]);
for _ in 0..N_MAX_ITERATIONS {
let root_x = xn.sqrt();
let root_y = yn.sqrt();
let root_z = zn.sqrt();
let lambda = root_x * root_y + root_x * root_z + root_y * root_z;
an = (an + lambda) / 4.0;
xn = (xn + lambda) / 4.0;
yn = (yn + lambda) / 4.0;
zn = (zn + lambda) / 4.0;
q = q / 4.0;
fn_val = fn_val * 4.0;
if q < an.abs() {
let x = (a0 - x) / (an * fn_val);
let y = (a0 - y) / (an * fn_val);
let z = -x - y;
let e2 = x * y - z * z;
let e3 = x * y * z;
ans = (1.0
+ e3 * (1.0 / 14.0 + 3.0 * e3 / 104.0)
+ e2 * (-0.1 + e2 / 24.0 - (3.0 * e3) / 44.0 - 5.0 * e2 * e2 / 208.0
+ e2 * e3 / 16.0))
/ an.sqrt();
break;
}
}
ans
}
#[cfg(not(feature = "test_force_fail"))]
const N_MAX_ITERATIONS: usize = 11;
#[cfg(feature = "test_force_fail")]
const N_MAX_ITERATIONS: usize = 1;
#[cfg(not(feature = "test_force_fail"))]
#[cfg(test)]
mod tests {
use core::f64;
use itertools::Itertools;
use super::*;
use crate::{assert_close, compare_test_data_boost};
fn __elliprf(inp: &[&f64]) -> f64 {
elliprf(*inp[0], *inp[1], *inp[2]).unwrap()
}
fn _elliprf(inp: &[f64]) -> f64 {
let res = elliprf(inp[0], inp[1], inp[2]).unwrap();
inp.iter().permutations(inp.len()).skip(1).for_each(|perm| {
assert_close!(res, __elliprf(&perm), 6.5e-16);
});
res
}
#[test]
fn test_elliprf() {
compare_test_data_boost!("elliprf_data.txt", _elliprf, 4.5e-16);
}
#[test]
fn test_elliprf_xxx() {
compare_test_data_boost!("elliprf_xxx.txt", _elliprf, 2.3e-16);
}
#[test]
fn test_elliprf_xy0() {
compare_test_data_boost!("elliprf_xy0.txt", _elliprf, 4.2e-16);
}
#[test]
fn test_elliprf_xyy() {
compare_test_data_boost!("elliprf_xyy.txt", _elliprf, 5.3e-16);
}
#[test]
fn test_elliprf_0yy() {
compare_test_data_boost!("elliprf_0yy.txt", _elliprf, f64::EPSILON);
}
#[test]
fn test_elliprf_special_cases() {
use std::f64::{INFINITY, NAN};
assert_eq!(
elliprf(-1.0, 1.0, 2.0),
Err("elliprf: Arguments must be non-negative.")
);
assert_eq!(
elliprf(1.0, -1.0, 1.0),
Err("elliprf: Arguments must be non-negative.")
);
assert_eq!(
elliprf(1.0, 1.0, -1.0),
Err("elliprf: Arguments must be non-negative.")
);
assert_eq!(
elliprf(0.0, 0.0, 1.0),
Err("elliprf: At most one argument can be zero.")
);
assert_eq!(
elliprf(0.0, 1.0, 0.0),
Err("elliprf: At most one argument can be zero.")
);
assert_eq!(
elliprf(1.0, 0.0, 0.0),
Err("elliprf: At most one argument can be zero.")
);
assert_eq!(
elliprf(NAN, 1.0, 1.0),
Err("elliprf: Arguments cannot be NAN.")
);
assert_eq!(
elliprf(1.0, NAN, 1.0),
Err("elliprf: Arguments cannot be NAN.")
);
assert_eq!(
elliprf(1.0, 1.0, NAN),
Err("elliprf: Arguments cannot be NAN.")
);
assert_eq!(elliprf(INFINITY, 1.0, 1.0).unwrap(), 0.0);
assert_eq!(elliprf(1.0, INFINITY, 1.0).unwrap(), 0.0);
assert_eq!(elliprf(1.0, 1.0, INFINITY).unwrap(), 0.0);
}
}
#[cfg(feature = "test_force_fail")]
crate::test_force_unreachable! {
assert_eq!(elliprf(0.2, 0.5, 1e300), Err("elliprf: Failed to converge."));
}