use num_traits::Float;
use crate::{
bulirsch::BulirschConst,
carlson::{elliprc_unchecked, elliprf_unchecked, elliprj_unchecked},
crate_util::check,
ellipf,
legendre::{ellipeinc::ellipeinc_unchecked, ellippi::ellippi_vc},
StrErr,
};
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub fn ellippiinc<T: Float>(phi: T, n: T, m: T) -> Result<T, StrErr> {
let ans = ellippiinc_vc(phi, n, m, 1.0 - n)?;
if ans.is_finite() {
return Ok(ans);
}
check!(@nan, ellippiinc, [phi, n, m]);
if phi.is_infinite() {
return Ok(phi.signum() * inf!());
}
Err("ellippiinc: Unexpected error.")
}
#[inline]
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
fn ellippiinc_vc<T: Float>(phi: T, n: T, m: T, nc: T) -> Result<T, StrErr> {
let sphi = phi.abs().sin();
let sp2 = sphi * sphi;
let mut result = 0.0;
if m * sp2 > 1.0 {
return Err("ellippiinc: m sin²φ must be smaller or equal to one.");
}
if n * sp2 == 1.0 {
return Err("ellippiinc: n sin²φ must not equal one.");
}
if n == 0.0 {
return if m == 0.0 { Ok(phi) } else { ellipf(phi, m) };
}
if n == 1.0 {
if m == 0.0 {
return Ok(phi.tan());
}
result = (1.0 - m * sp2).sqrt() * phi.tan() - ellipeinc_unchecked(phi, m)?;
result = result / (1.0 - m);
result = result + ellipf(phi, m)?;
return Ok(result);
}
if phi >= pi_2!() || phi <= 0.0 {
if phi == pi_2!() {
return Ok(ellippi_vc(n, m, nc));
}
if phi == 0.0 {
return Ok(0.0);
}
if phi.abs() > 1.0 / epsilon!() {
debug_assert!(n <= 1.0);
result = 2.0 * phi.abs() * ellippi_vc(n, m, nc) / pi!();
} else {
let mut rphi = phi.abs() % pi_2!();
let mut mm = ((phi.abs() - rphi) / pi_2!()).round();
let mut sign = 1.0;
if mm != 0.0 && m >= 1.0 {
return Err("ellippiinc: The result is complex.");
}
if mm % 2.0 > 0.5 {
mm = mm + 1.0;
sign = -1.0;
rphi = pi_2!() - rphi;
}
result = sign * ellippiinc_vc(rphi, n, m, nc)?;
if mm > 0.0 && nc > 0.0 {
result = result + mm * ellippi_vc(n, m, nc);
}
}
return if phi < 0.0 { Ok(-result) } else { Ok(result) };
}
if n * sp2 > 1.0 {
let c = 1.0 / sp2; let w2 = m / n;
return Ok((ellipf(phi, m)?
+ c.sqrt() * elliprc_unchecked((c - 1.0) * (c - m), (c - n) * (c - w2)))
- ellippiinc(phi, w2, m)?);
}
if m == 0.0 {
if n < 1.0 {
let vcr = nc.sqrt();
return Ok((vcr * phi.tan()).atan() / vcr);
} else {
let vcr = (-nc).sqrt();
let arg = vcr * phi.tan();
return Ok((arg.ln_1p() - (-arg).ln_1p()) / (2.0 * vcr));
}
}
if n < 0.0 && m <= 1.0 {
if (n - m).abs() < epsilon!() {
return Ok((ellipeinc_unchecked(phi, m)?
- (m * phi.abs().cos() * sphi) / (1.0 - m * sp2).sqrt())
/ (1.0 - m));
}
if m >= n {
let nn = (m - n) / (1.0 - n);
let nm1 = (1.0 - m) / (1.0 - n);
if nn > m {
result = if nn.abs() < n.abs() {
ellippiinc_vc(phi, nn, m, nm1)?
} else {
let c = 1.0 / sp2;
nn / 3.0 * elliprj_unchecked(c - 1.0, c - m, c, c - nn) + ellipf(phi, m)?
};
result = result * n / (n - 1.0);
result = result * (m - 1.0) / (n - m);
}
if m != 0.0 {
let mut t = ellipf(phi, m)?;
t = t * m / (m - n);
result = result + t;
}
let mut p2 = -n * nn;
if p2 <= min_val!() {
p2 = (-n).sqrt() * nn.sqrt();
} else {
p2 = p2.sqrt();
}
let delta = (1.0 - m * sp2).sqrt();
let t = n / ((m - n) * (n - 1.0));
debug_assert!(t > min_val!());
result = result + ((p2 / 2.0) * (2.0 * phi).sin() / delta).atan() * t.sqrt();
return Ok(result);
}
}
if m == 1.0 {
result = n.sqrt() * (n.sqrt() * phi.sin()).atanh() - (1.0 / phi.cos() + phi.tan()).ln();
result = result / (n - 1.0);
return Ok(result);
}
let cosp = phi.cos();
let x = cosp * cosp;
let t = sp2;
let y = 1.0 - m * t;
let z = 1.0;
let p = if n * t < 0.5 { 1.0 - n * t } else { x + nc * t };
Ok(sphi * (elliprf_unchecked(x, y, z) + n * t * elliprj_unchecked(x, y, z, p) / 3.0))
}
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub fn ellippiinc_bulirsch<T: Float + BulirschConst<T>>(phi: T, n: T, m: T) -> Result<T, StrErr> {
ellippiinc_bulirsch_with_const::<T, T>(phi, n, m)
}
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
#[inline]
pub fn ellippiinc_bulirsch_with_const<T: Float, C: BulirschConst<T>>(
phi: T,
n: T,
m: T,
) -> Result<T, StrErr> {
if phi.is_infinite() {
return Ok(phi);
}
if n == 1.0 && m == 0.0 {
return Ok(phi.tan());
}
let sphi = phi.sin();
if m >= 1.0 || n * sphi * sphi >= 1.0 {
return ellippiinc(phi, n, m);
}
let x = phi.tan();
let kc = (T::one() - m).sqrt();
let p = T::one() - n;
let result = crate::bulirsch::el::el3_with_const::<T, C>(x, kc, p);
if result.is_ok() {
return result;
}
#[cfg(feature = "test_force_fail")]
let result: Result<T, &_> = Err("");
let err_str = match result.err().unwrap() {
"el3: kc must not be zero." => "ellippiinc: m must not be 1.",
"el3: 1 + px² cannot be zero." => "ellippiinc: 1 + (1-n)tan²φ cannot be zero.",
"el3: Failed to converge." => "ellippiinc: Failed to converge.",
"el3: Arguments cannot be NAN." => "ellippiinc: Arguments cannot be NAN.",
_ => "ellippiinc: Unexpected error.",
};
Err(err_str)
}
#[cfg(not(feature = "test_force_fail"))]
#[cfg(test)]
mod tests {
use super::*;
use crate::{compare_test_data_boost, compare_test_data_wolfram, ellipeinc, ellippi};
fn ellippiinc_k(inp: &[f64]) -> f64 {
ellippiinc(inp[1], inp[0], inp[2] * inp[2]).unwrap()
}
#[test]
fn test_ellippiinc() {
compare_test_data_boost!("ellippiinc_data.txt", ellippiinc_k, 2.1e-15);
compare_test_data_boost!("ellippi3_large_data.txt", ellippiinc_k, 6e-15);
}
#[test]
fn test_ellippiinc_wolfram() {
compare_test_data_wolfram!("ellippiinc_data.csv", ellippiinc, 3, 5e-14);
}
#[test]
fn test_ellippiinc_wolfram_neg() {
compare_test_data_wolfram!("ellippiinc_neg.csv", ellippiinc, 3, 5e-14);
}
#[test]
fn test_ellippiinc_wolfram_pv() {
compare_test_data_wolfram!("ellippiinc_pv.csv", ellippiinc, 3, 3e-13);
}
#[test]
fn test_ellippiinc_special_cases() {
use std::f64::{
consts::{FRAC_PI_2, PI},
INFINITY, NAN, NEG_INFINITY,
};
assert_eq!(
ellippiinc(FRAC_PI_2, 0.5, 1.1),
Err("ellippiinc: m sin²φ must be smaller or equal to one.")
);
assert_eq!(
ellippiinc(FRAC_PI_2, 1.0, 0.5),
Err("ellippiinc: n sin²φ must not equal one.")
);
assert_eq!(ellippiinc(0.4, 0.0, 0.0).unwrap(), 0.4);
assert_eq!(ellippiinc(0.0, 0.5, 0.5).unwrap(), 0.0);
assert_eq!(ellippiinc(0.2, 1.0, 0.0).unwrap(), 0.2.tan());
assert_eq!(
ellippiinc(0.2, 0.0, 0.5).unwrap(),
ellipf(0.2, 0.5).unwrap()
);
assert_eq!(
ellippiinc(0.2, 1.0, 0.5).unwrap(),
((1.0 - 0.5 * 0.2.sin() * 0.2.sin()).sqrt() * 0.2.tan() - ellipeinc(0.2, 0.5).unwrap())
/ 0.5
+ ellipf(0.2, 0.5).unwrap()
);
assert_eq!(
ellippiinc(0.3, 1.5, 1.0).unwrap(),
((1.5.sqrt() * (1.5.sqrt() * 0.3.sin()).atanh()
- (0.3.cos().recip() + 0.3.tan()).ln())
/ (1.5 - 1.0))
);
let large_phi_ans = 2.0 * 1e16 * ellippi(0.2, 0.5).unwrap() / PI;
assert_eq!(ellippiinc(1e16, 0.2, 0.5).unwrap(), large_phi_ans);
assert_eq!(ellippiinc(-1e16, 0.2, 0.5).unwrap(), -large_phi_ans);
assert_eq!(ellippiinc(INFINITY, 0.2, 0.5).unwrap(), INFINITY);
assert_eq!(ellippiinc(NEG_INFINITY, 0.2, 0.5).unwrap(), -INFINITY);
assert_eq!(
ellippiinc(4.14159, 0.5, 1.0),
Err("ellippiinc: The result is complex.")
);
assert_eq!(
ellippiinc(NAN, 0.5, 0.5),
Err("ellippiinc: Arguments cannot be NAN.")
);
assert_eq!(
ellippiinc(0.5, NAN, 0.5),
Err("ellippiinc: Arguments cannot be NAN.")
);
assert_eq!(
ellippiinc(0.5, 0.5, NAN),
Err("ellippiinc: Arguments cannot be NAN.")
);
}
#[test]
fn test_ellippiinc_bulirsch_wolfram() {
compare_test_data_wolfram!("ellippiinc_data.csv", ellippiinc_bulirsch, 3, 5e-14);
}
#[test]
fn test_ellippiinc_bulirsch_wolfram_neg() {
compare_test_data_wolfram!("ellippiinc_neg.csv", ellippiinc_bulirsch, 3, 5e-14);
}
#[test]
fn test_ellippiinc_bulirsch_wolfram_pv() {
compare_test_data_wolfram!("ellippiinc_pv.csv", ellippiinc_bulirsch, 3, 3e-13);
}
#[test]
fn test_ellippiinc_bulirsch_special_cases() {
use std::f64::{consts::FRAC_PI_2, INFINITY, NAN, NEG_INFINITY};
assert_eq!(
ellippiinc_bulirsch(FRAC_PI_2, 0.5, 1.1),
Err("ellippiinc: m sin²φ must be smaller or equal to one.")
);
assert_eq!(
ellippiinc_bulirsch(FRAC_PI_2, 1.0, 0.5),
Err("ellippiinc: n sin²φ must not equal one.")
);
assert_eq!(ellippiinc_bulirsch(0.4, 0.0, 0.0).unwrap(), 0.4);
assert_eq!(ellippiinc_bulirsch(0.0, 0.5, 0.5).unwrap(), 0.0);
assert_eq!(ellippiinc_bulirsch(0.2, 1.0, 0.0).unwrap(), 0.2.tan());
assert_eq!(
ellippiinc_bulirsch(0.2, 0.0, 0.5).unwrap(),
ellipf(0.2, 0.5).unwrap()
);
assert_eq!(
ellippiinc_bulirsch(0.2, 1.0, 0.5).unwrap(),
((1.0 - 0.5 * 0.2.sin() * 0.2.sin()).sqrt() * 0.2.tan() - ellipeinc(0.2, 0.5).unwrap())
/ 0.5
+ ellipf(0.2, 0.5).unwrap()
);
assert_eq!(
ellippiinc_bulirsch(0.3, 1.5, 1.0).unwrap(),
((1.5.sqrt() * (1.5.sqrt() * 0.3.sin()).atanh()
- (0.3.cos().recip() + 0.3.tan()).ln())
/ (1.5 - 1.0))
);
assert_eq!(ellippiinc_bulirsch(INFINITY, 0.2, 0.5).unwrap(), INFINITY);
assert_eq!(
ellippiinc_bulirsch(NEG_INFINITY, 0.2, 0.5).unwrap(),
-INFINITY
);
assert_eq!(
ellippiinc_bulirsch(4.14159, 0.5, 1.0),
Err("ellippiinc: The result is complex.")
);
assert_eq!(
ellippiinc_bulirsch(NAN, 0.5, 0.5),
Err("ellippiinc: Arguments cannot be NAN.")
);
assert_eq!(
ellippiinc_bulirsch(0.5, NAN, 0.5),
Err("ellippiinc: Arguments cannot be NAN.")
);
assert_eq!(
ellippiinc_bulirsch(0.5, 0.5, NAN),
Err("ellippiinc: Arguments cannot be NAN.")
);
}
}
#[cfg(feature = "test_force_fail")]
crate::test_force_unreachable! {
assert_eq!(ellippiinc_bulirsch(0.5, 0.5, 0.5), Err("ellippiinc: Unexpected error."));
}