use core::mem::swap;
use crate::{
carlson::{elliprc_unchecked, elliprd_unchecked, elliprf_unchecked},
crate_util::{case, check, declare, let_mut},
StrErr,
};
use num_traits::Float;
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub fn elliprj<T: Float>(x: T, y: T, z: T, p: T) -> Result<T, StrErr> {
let ans = elliprj_unchecked(x, y, z, p);
if ans.is_finite() {
return Ok(ans);
}
check!(@nan, elliprj, [x, y, z, p]);
check!(@zero, elliprj, [p]);
check!(@neg, elliprj, "x, y, and z must be non-negative.", [x, y, z]);
check!(@multi_zero, elliprj, [x, y, z]);
case!(@any [x, y, z, p] == inf!(), T::zero());
Err("elliprj: Failed to converge.")
}
#[inline]
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
fn elliprc1p<T: Float>(y: T) -> T {
if y > 0.0 {
y.sqrt().atan() / y.sqrt()
} else if y == 0.0 {
1.0
} else if y > -0.5 {
let arg = (-y).sqrt();
(arg.ln_1p() - (-arg).ln_1p()) / (2.0 * (-y).sqrt())
} else if y < -1.0 {
(1.0 / -y).sqrt() * elliprc_unchecked(-y, -1.0 - y)
} else {
((1.0 + (-y).sqrt()) / (1.0 + y).sqrt()).ln() / (-y).sqrt()
}
}
#[inline]
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub fn elliprj_unchecked<T: Float>(x: T, y: T, z: T, p: T) -> T {
let_mut!(x, y, z);
if p <= 0.0 {
if x > y {
swap(&mut x, &mut y);
}
if y > z {
swap(&mut y, &mut z);
}
if x > y {
swap(&mut x, &mut y);
}
let q = -p;
let z_plus_q = z + q;
let xy = x * y;
let p = (z * (x + y + q) - xy) / z_plus_q;
let pq = p * q;
let xy_plus_pq = xy + pq;
let xyz = xy * z;
let rj = elliprj_unchecked(x, y, z, p);
let rf = elliprf_unchecked(x, y, z);
let rc = elliprc_unchecked(xy_plus_pq, pq);
let mut value = (p - z) * rj;
value = value - 3.0 * rf;
value = value + 3.0 * (xyz / xy_plus_pq).sqrt() * rc;
return value / z_plus_q;
}
if x == y {
if x == z {
if x == p {
return 1.0 / (x * x.sqrt());
} else {
return (3.0 / (x - p)) * (elliprc_unchecked(x, p) - 1.0 / x.sqrt());
}
} else {
swap(&mut x, &mut z);
}
}
if y == z {
if y == p {
return elliprd_unchecked(x, y, y);
}
if p.max(y) / p.min(y) > 1.2 {
return 3.0 * (elliprc_unchecked(x, y) - elliprc_unchecked(x, p)) / (p - y);
}
}
if z == p {
return elliprd_unchecked(x, y, z);
}
declare!(mut [xn = x, yn = y, zn = z, pn = p]);
let mut an = (x + y + z + 2.0 * p) / 5.0;
let a0 = an;
let mut delta = (p - x) * (p - y) * (p - z);
let q = (epsilon!() / 5.0).powf(-1.0 / 8.0)
* (an - x)
.abs()
.max((an - y).abs())
.max((an - z).abs())
.max((an - p).abs());
let mut fmn = 1.0;
let mut rc_sum = 0.0;
let mut ans = nan!();
for _ in 0..N_MAX_ITERATION {
let rx = xn.sqrt();
let ry = yn.sqrt();
let rz = zn.sqrt();
let rp = pn.sqrt();
let dn = (rp + rx) * (rp + ry) * (rp + rz);
let en = delta / (dn * dn);
if en < -0.5 && en > -1.5 {
let b = 2.0 * rp * (pn + rx * (ry + rz) + ry * rz) / dn;
rc_sum = rc_sum + fmn / dn * elliprc_unchecked(1.0, b);
} else {
rc_sum = rc_sum + fmn / dn * elliprc1p(en);
}
let lambda = rx * ry + rx * rz + ry * rz;
an = (an + lambda) / 4.0;
fmn = fmn / 4.0;
if fmn * q < an {
let x = fmn * (a0 - x) / an;
let y = fmn * (a0 - y) / an;
let z = fmn * (a0 - z) / an;
let p = (-x - y - z) / 2.0;
let xyz = x * y * z;
let p2 = p * p;
let p3 = p2 * p;
let e2 = x * y + x * z + y * z - 3.0 * p2;
let e3 = xyz + 2.0 * e2 * p + 4.0 * p3;
let e4 = (2.0 * xyz + e2 * p + 3.0 * p3) * p;
let e5 = xyz * p2;
let result = fmn
* an.powf(-1.5)
* (1.0 - 3.0 * e2 / 14.0 + e3 / 6.0 + 9.0 * e2 * e2 / 88.0
- 3.0 * e4 / 22.0
- 9.0 * e2 * e3 / 52.0
+ 3.0 * e5 / 26.0
- e2 * e2 * e2 / 16.0
+ 3.0 * e3 * e3 / 40.0
+ 3.0 * e2 * e4 / 20.0
+ 45.0 * e2 * e2 * e3 / 272.0
- 9.0 * (e3 * e4 + e2 * e5) / 68.0);
ans = result + 6.0 * rc_sum;
break;
}
xn = (xn + lambda) / 4.0;
yn = (yn + lambda) / 4.0;
zn = (zn + lambda) / 4.0;
pn = (pn + lambda) / 4.0;
delta = delta / 64.0;
}
ans
}
#[cfg(not(feature = "test_force_fail"))]
const N_MAX_ITERATION: usize = 100;
#[cfg(feature = "test_force_fail")]
const N_MAX_ITERATION: usize = 1;
#[cfg(not(feature = "test_force_fail"))]
#[cfg(test)]
mod tests {
use itertools::Itertools;
use super::*;
use crate::{assert_close, compare_test_data_boost, compare_test_data_wolfram};
fn __elliprj(inp: &[&f64]) -> f64 {
elliprj(*inp[0], *inp[1], *inp[2], *inp[3]).unwrap()
}
fn _elliprj(inp: &[f64]) -> f64 {
let res = elliprj(inp[0], inp[1], inp[2], inp[3]).unwrap();
let (p, sym_params) = inp.split_last().unwrap();
sym_params
.iter()
.permutations(sym_params.len())
.skip(1)
.for_each(|mut perm| {
perm.push(p);
assert_close!(res, __elliprj(&perm), 3e-14);
});
res
}
#[test]
fn test_elliprj() {
compare_test_data_boost!("elliprj_data.txt", _elliprj, 2.7e-14, atol: 5e-25);
compare_test_data_boost!("elliprj_e2.txt", _elliprj, 4.8e-14, atol: 5e-25);
compare_test_data_boost!("elliprj_e3.txt", _elliprj, 3.1e-15, atol: 5e-25);
compare_test_data_boost!("elliprj_e4.txt", _elliprj, 2.2e-16, atol: 5e-25);
compare_test_data_boost!("elliprj_zp.txt", _elliprj, 3.5e-15, atol: 5e-25);
}
#[test]
fn test_elliprj_wolfram() {
compare_test_data_wolfram!("elliprj_data.csv", elliprj, 4, 3e-15);
compare_test_data_wolfram!("elliprj_pv.csv", elliprj, 4, 5e-14, atol: f64::EPSILON);
}
#[test]
fn test_elliprj_special_cases() {
use std::f64::{INFINITY, NAN};
assert_eq!(
elliprj(-1.0, 1.0, 1.0, 1.0),
Err("elliprj: x, y, and z must be non-negative.")
);
assert_eq!(
elliprj(1.0, -1.0, 1.0, 1.0),
Err("elliprj: x, y, and z must be non-negative.")
);
assert_eq!(
elliprj(1.0, 1.0, -1.0, 1.0),
Err("elliprj: x, y, and z must be non-negative.")
);
assert_eq!(
elliprj(0.0, 0.0, 1.0, 1.0),
Err("elliprj: At most one argument can be zero.")
);
assert_eq!(
elliprj(0.0, 1.0, 0.0, 1.0),
Err("elliprj: At most one argument can be zero.")
);
assert_eq!(
elliprj(1.0, 0.0, 0.0, 1.0),
Err("elliprj: At most one argument can be zero.")
);
assert_eq!(
elliprj(1.0, 1.0, 1.0, 0.0),
Err("elliprj: p cannot be zero.")
);
assert_eq!(
elliprj(NAN, 1.0, 1.0, 1.0),
Err("elliprj: Arguments cannot be NAN.")
);
assert_eq!(
elliprj(1.0, NAN, 1.0, 1.0),
Err("elliprj: Arguments cannot be NAN.")
);
assert_eq!(
elliprj(1.0, 1.0, NAN, 1.0),
Err("elliprj: Arguments cannot be NAN.")
);
assert_eq!(
elliprj(1.0, 1.0, 1.0, NAN),
Err("elliprj: Arguments cannot be NAN.")
);
assert_eq!(elliprj(INFINITY, 1.0, 1.0, 1.0).unwrap(), 0.0);
assert_eq!(elliprj(1.0, INFINITY, 1.0, 1.0).unwrap(), 0.0);
assert_eq!(elliprj(1.0, 1.0, INFINITY, 1.0).unwrap(), 0.0);
assert_eq!(elliprj(1.0, 1.0, 1.0, INFINITY).unwrap(), 0.0);
}
#[test]
fn cover_deadcode() {
assert!(elliprc1p(-0.6).is_finite());
assert!(elliprc1p(-1.1).is_finite());
}
}
#[cfg(feature = "test_force_fail")]
crate::test_force_unreachable! {
assert_eq!(elliprj(0.2, 0.5, 1e300, 1.0), Err("elliprj: Failed to converge."));
}