#![allow(dead_code)]
use core::f64::consts::{FRAC_PI_2, PI};
const AGM_EPS: f64 = 4.0 * f64::EPSILON;
const AGM_MAX: usize = 16;
pub fn k_elliptic(m: f64) -> f64 {
if m.is_nan() || !(0.0..=1.0).contains(&m) {
return f64::NAN;
}
if m == 0.0 {
return FRAC_PI_2;
}
if m == 1.0 {
return f64::INFINITY;
}
let mut a = 1.0_f64;
let mut b = (1.0 - m).sqrt();
for _ in 0..AGM_MAX {
let a_new = (a + b) * 0.5;
let b_new = (a * b).sqrt();
a = a_new;
b = b_new;
if (a - b).abs() <= a * AGM_EPS {
break;
}
}
FRAC_PI_2 / a
}
pub fn k_prime(m: f64) -> f64 {
k_elliptic(1.0 - m)
}
pub fn sncndn(u: f64, m: f64) -> (f64, f64, f64) {
if u.is_nan() || m.is_nan() || !(0.0..=1.0).contains(&m) {
return (f64::NAN, f64::NAN, f64::NAN);
}
if m == 0.0 {
return (u.sin(), u.cos(), 1.0);
}
if m == 1.0 {
let sech = 1.0 / u.cosh();
return (u.tanh(), sech, sech);
}
const CA: f64 = 1.0e-8_f64;
let mut em = [0.0_f64; AGM_MAX + 1];
let mut en = [0.0_f64; AGM_MAX + 1];
let mut a = 1.0_f64;
let mut b = (1.0 - m).sqrt(); let mut l = 0_usize;
for i in 0..AGM_MAX {
em[i] = a;
en[i] = b;
if (a - b).abs() <= CA * a {
l = i;
break;
}
let c = (a + b) * 0.5;
b = (a * b).sqrt();
a = c;
l = i + 1;
}
let u2 = u * a;
let sn0 = u2.sin();
let cn0 = u2.cos();
if sn0 == 0.0 {
return (0.0, 1.0, 1.0);
}
let mut a_var = cn0 / sn0; let mut c_var = a * a_var; let mut dn_var = 1.0_f64;
for ii in (0..=l).rev() {
let tmp = a_var * c_var;
c_var *= dn_var;
dn_var = (en[ii] + tmp) / (em[ii] + tmp);
a_var = c_var / em[ii];
}
let inv = 1.0_f64 / (c_var * c_var + 1.0).sqrt();
let sn = if sn0 >= 0.0 { inv } else { -inv };
let cn = c_var * sn;
(sn, cn, dn_var)
}
pub fn am(u: f64, m: f64) -> f64 {
let (sn, cn, _) = sncndn(u, m);
sn.atan2(cn)
}
pub fn jacobi_elliptic(u: f64, m: f64) -> (f64, f64, f64) {
sncndn(u, m)
}
pub fn elliptic_k(m: f64) -> f64 {
k_elliptic(m)
}
pub fn elliptic_am(u: f64, m: f64) -> f64 {
am(u, m)
}
fn k_and_e(m: f64) -> (f64, f64) {
if m == 0.0 {
return (FRAC_PI_2, FRAC_PI_2);
}
if m == 1.0 {
return (f64::INFINITY, 1.0);
}
let mut a = 1.0_f64;
let mut b = (1.0 - m).sqrt();
let mut s = m / 2.0;
let mut power = 1.0_f64;
for _ in 0..AGM_MAX {
if (a - b).abs() < AGM_EPS * a {
break;
}
let c = (a - b) / 2.0;
s += c * c * power;
power *= 2.0;
let a_new = (a + b) / 2.0;
b = (a * b).sqrt();
a = a_new;
}
let k = FRAC_PI_2 / a;
let e = k * (1.0 - s);
(k, e)
}
fn carlson_rc(x: f64, y: f64) -> f64 {
if (x - y).abs() < f64::EPSILON * y.abs() {
1.0 / x.sqrt()
} else if x < y {
let diff = y - x;
(diff / y).sqrt().atan() / diff.sqrt()
} else {
let diff = x - y;
(diff / x).sqrt().atanh() / diff.sqrt()
}
}
fn carlson_rj(x: f64, y: f64, z: f64, p: f64) -> f64 {
const ERRTOL: f64 = 1e-10;
let mut sum = 0.0_f64;
let mut fac = 1.0_f64;
let (mut xt, mut yt, mut zt, mut pt) = (x, y, z, p);
for _ in 0..50 {
let sqx = xt.sqrt();
let sqy = yt.sqrt();
let sqz = zt.sqrt();
let alamb = sqx * (sqy + sqz) + sqy * sqz;
let alpha = (pt * (sqx + sqy + sqz) + sqx * sqy * sqz).powi(2);
let beta = pt * (pt + alamb).powi(2);
sum += fac * carlson_rc(alpha, beta);
fac /= 4.0;
xt = (xt + alamb) / 4.0;
yt = (yt + alamb) / 4.0;
zt = (zt + alamb) / 4.0;
pt = (pt + alamb) / 4.0;
let ave = (xt + yt + zt + 2.0 * pt) / 5.0;
let delx = (ave - xt) / ave;
let dely = (ave - yt) / ave;
let delz = (ave - zt) / ave;
let delp = (ave - pt) / ave;
let max_del = [delx, dely, delz, delp]
.iter()
.map(|d| d.abs())
.fold(0.0_f64, f64::max);
if max_del < ERRTOL {
break;
}
}
let ave = (xt + yt + zt + 2.0 * pt) / 5.0;
let delx = (ave - xt) / ave;
let dely = (ave - yt) / ave;
let delz = (ave - zt) / ave;
let delp = (ave - pt) / ave;
let ea = delx * dely + dely * delz + delz * delx - 3.0 * delp * delp;
let eb = delx * dely * delz
+ 3.0 * delp * (delx * dely + dely * delz + delz * delx)
+ 5.0 * delp * delp * (delx + dely + delz);
let ec = delp
* delp
* (3.0 * (delx * dely + dely * delz + delz * delx)
+ 7.0 * delp * (delx + dely + delz)
+ 13.0 * delp * delp);
let ed = delx * dely * delz * delp;
3.0 * sum
+ fac
* (1.0
+ ea * (-3.0 / 14.0)
+ eb * (1.0 / 6.0)
+ ec * (-9.0 / 88.0)
+ ed * (3.0 / 22.0)
+ ea * ea * (-9.0 / 52.0)
- ea * eb / 26.0)
/ (ave * ave.sqrt() * pt)
}
pub fn cel(kc: f64, p: f64, a: f64, b: f64) -> f64 {
if kc == 0.0 {
return f64::NAN;
}
let m = 1.0 - kc * kc;
if !(0.0..=1.0).contains(&m) {
return f64::NAN;
}
let (k_val, e_val) = k_and_e(m);
if (p - 1.0).abs() < 1e-12 {
if m.abs() < 1e-15 {
return a * FRAC_PI_2;
}
((a - b) * e_val + (b - a * kc * kc) * k_val) / m
} else {
let n = 1.0 - p;
if n.abs() < 1e-12 {
if m.abs() < 1e-15 {
return a * FRAC_PI_2;
}
return ((a - b) * e_val + (b - a * kc * kc) * k_val) / m;
}
if p <= 0.0 {
return f64::NAN;
}
let pi_n_m = k_val + (n / 3.0) * carlson_rj(0.0, 1.0 - m, 1.0, p);
a * pi_n_m + (b - a * p) * (pi_n_m - k_val) / n
}
}
fn carlson_rf(x: f64, y: f64, z: f64) -> f64 {
const ERRTOL: f64 = 3.0e-5;
let (mut xt, mut yt, mut zt) = (x, y, z);
loop {
let lam = xt.sqrt() * yt.sqrt() + yt.sqrt() * zt.sqrt() + zt.sqrt() * xt.sqrt();
xt = (xt + lam) / 4.0;
yt = (yt + lam) / 4.0;
zt = (zt + lam) / 4.0;
let ave = (xt + yt + zt) / 3.0;
let delx = 1.0 - xt / ave;
let dely = 1.0 - yt / ave;
let delz = 1.0 - zt / ave;
if delx.abs().max(dely.abs()).max(delz.abs()) < ERRTOL {
let e2 = delx * dely - delz * delz;
let e3 = delx * dely * delz;
return (1.0 + e2 * (-1.0 / 10.0 + 3.0 / 44.0 * e2 - 3.0 / 14.0 * e3) + e3 / 6.0)
/ ave.sqrt();
}
}
}
pub fn elliptic_f(phi: f64, m: f64) -> f64 {
if !(0.0..=1.0).contains(&m) {
return f64::NAN;
}
if phi == 0.0 || m == 0.0 {
return phi;
}
let n = (phi / PI + 0.5).floor();
let phi_r = phi - n * PI;
let kn = if n == 0.0 {
0.0
} else {
2.0 * n * k_elliptic(m)
};
let s = phi_r.sin();
let c = phi_r.cos();
let x = c * c;
let y = 1.0 - m * s * s;
let rf = carlson_rf(x, y, 1.0);
s * rf + kn
}
pub fn conformal_square(re: f64, im: f64, ks: f64, m: f64) -> (f64, f64) {
let m1 = 1.0 - m;
let (sn_u, cn_u, dn_u) = sncndn(re * ks, m);
let (sn_v, cn_v, dn_v) = sncndn(im * ks, m1);
let denom = cn_v * cn_v + m * sn_u * sn_u * sn_v * sn_v;
if denom == 0.0 {
return (f64::NAN, f64::NAN);
}
let re_out = sn_u * dn_v / denom;
let im_out = sn_v * cn_v * cn_u * dn_u / denom;
(re_out, im_out)
}
#[cfg(test)]
#[allow(clippy::excessive_precision)]
mod tests {
use super::*;
const TOL: f64 = 1e-10;
#[test]
fn k_zero_is_pi_over_2() {
assert!((k_elliptic(0.0) - core::f64::consts::FRAC_PI_2).abs() < 1e-15);
}
#[test]
fn k_half() {
let expected = 1.854_074_677_301_371_9_f64;
assert!(
(k_elliptic(0.5) - expected).abs() < 1e-13,
"got {}",
k_elliptic(0.5)
);
}
#[test]
fn k_out_of_domain() {
assert!(k_elliptic(-0.1).is_nan());
assert!(k_elliptic(1.1).is_nan());
}
#[test]
fn k_one_is_infinity() {
assert!(k_elliptic(1.0).is_infinite());
}
#[test]
fn k_prime_sanity() {
assert!(k_prime(0.0).is_infinite());
assert!((k_prime(1.0) - core::f64::consts::FRAC_PI_2).abs() < 1e-15);
}
#[test]
fn sncndn_at_zero_u() {
for &m in &[0.0_f64, 0.25, 0.5, 0.75, 1.0] {
let (sn, cn, dn) = sncndn(0.0, m);
assert!(sn.abs() < 1e-15, "sn at u=0, m={m}: {sn}");
assert!((cn - 1.0).abs() < 1e-15, "cn at u=0, m={m}: {cn}");
assert!((dn - 1.0).abs() < 1e-15, "dn at u=0, m={m}: {dn}");
}
}
#[test]
fn sncndn_m_zero_is_trig() {
let u = 1.234_5_f64;
let (sn, cn, dn) = sncndn(u, 0.0);
assert!((sn - u.sin()).abs() < TOL);
assert!((cn - u.cos()).abs() < TOL);
assert!((dn - 1.0).abs() < TOL);
}
#[test]
fn sncndn_m_one_is_hyperbolic() {
let u = 1.0_f64;
let (sn, cn, dn) = sncndn(u, 1.0);
assert!((sn - u.tanh()).abs() < TOL);
assert!((cn - 1.0 / u.cosh()).abs() < TOL);
assert!((dn - 1.0 / u.cosh()).abs() < TOL);
}
#[test]
fn sncndn_at_k_m() {
let m = 0.5_f64;
let k = k_elliptic(m);
let (sn, cn, dn) = sncndn(k, m);
assert!((sn - 1.0).abs() < 1e-9, "sn(K,m)={sn}");
assert!(cn.abs() < 1e-9, "cn(K,m)={cn}");
assert!((dn - (1.0 - m).sqrt()).abs() < 1e-9, "dn(K,m)={dn}");
}
#[test]
fn sncndn_known_value() {
let (sn, cn, dn) = sncndn(1.0, 0.5);
assert!((sn - 0.803_001_824_895_644_f64).abs() < 1e-9, "sn={sn}");
assert!((cn - 0.595_976_567_672_141_f64).abs() < 1e-9, "cn={cn}");
assert!((dn - 0.823_161_001_631_596_f64).abs() < 1e-9, "dn={dn}");
}
#[test]
fn pythagoras_identity_sncndn() {
let (u, m) = (0.7_f64, 0.3_f64);
let (sn, cn, dn) = sncndn(u, m);
assert!((sn * sn + cn * cn - 1.0).abs() < 1e-14);
assert!((dn * dn + m * sn * sn - 1.0).abs() < 1e-14);
}
#[test]
fn cel_matches_k() {
let kc = 0.5_f64.sqrt();
let m = 0.5_f64;
let cel_val = cel(kc, 1.0, 1.0, 1.0);
let k_val = k_elliptic(m);
if !cel_val.is_nan() {
assert!(
(cel_val - k_val).abs() / k_val < 1e-6,
"cel={cel_val} K={k_val}"
);
}
}
#[test]
fn am_at_zero() {
assert!(am(0.0, 0.5).abs() < 1e-15);
}
#[test]
fn am_at_k_m() {
let m = 0.5_f64;
let k = k_elliptic(m);
let result = am(k, m);
assert!(
(result - core::f64::consts::FRAC_PI_2).abs() < 1e-9,
"am(K,m)={result}"
);
}
#[test]
fn elliptic_f_at_zero_phi() {
for &m in &[0.0_f64, 0.25, 0.5, 0.75] {
let f = elliptic_f(0.0, m);
assert!(f.abs() < 1e-15, "F(0,{m})={f}");
}
}
#[test]
fn elliptic_f_m_zero_is_phi() {
let phi = 1.234_5_f64;
let f = elliptic_f(phi, 0.0);
assert!((f - phi).abs() < 1e-14, "F(phi,0)={f} phi={phi}");
}
#[test]
fn elliptic_f_complete_equals_k() {
let m = 0.5_f64;
let f = elliptic_f(core::f64::consts::FRAC_PI_2, m);
let k = k_elliptic(m);
assert!((f - k).abs() < 1e-10, "F(pi/2,0.5)={f} K(0.5)={k}");
}
#[test]
fn elliptic_f_known_value() {
let f = elliptic_f(core::f64::consts::FRAC_PI_4, 0.5);
assert!(
(f - 0.826_017_876_249_245_f64).abs() < 1e-10,
"F(pi/4,0.5)={f}"
);
}
#[test]
fn elliptic_am_round_trip() {
for &(phi, m) in &[
(0.5_f64, 0.3_f64),
(1.0_f64, 0.5_f64),
(0.7_f64, 0.9_f64),
(1.2_f64, 0.1_f64),
] {
let f = elliptic_f(phi, m);
let back = elliptic_am(f, m);
assert!(
(back - phi).abs() < 1e-9,
"round-trip fail phi={phi} m={m}: got {back}"
);
}
}
#[test]
fn elliptic_k_alias() {
assert_eq!(elliptic_k(0.5), k_elliptic(0.5));
assert_eq!(elliptic_k(0.0), k_elliptic(0.0));
}
#[test]
fn jacobi_elliptic_alias() {
let (a, b, c) = jacobi_elliptic(1.0, 0.5);
let (d, e, f) = sncndn(1.0, 0.5);
assert_eq!((a, b, c), (d, e, f));
}
#[test]
fn jacobi_elliptic_at_zero() {
let (sn, cn, dn) = jacobi_elliptic(0.0, 0.5);
assert!(sn.abs() < f64::EPSILON);
assert!((cn - 1.0).abs() < f64::EPSILON);
assert!((dn - 1.0).abs() < f64::EPSILON);
}
#[test]
fn conformal_square_at_zero() {
let ks = k_elliptic(0.5);
let (re, im) = conformal_square(0.0, 0.0, ks, 0.5);
assert!(re.abs() < 1e-14, "re={re}");
assert!(im.abs() < 1e-14, "im={im}");
}
#[test]
fn conformal_square_real_axis_matches_sn() {
let m = 0.5_f64;
let ks = k_elliptic(m);
let re = 0.5_f64;
let (cs_re, cs_im) = conformal_square(re, 0.0, ks, m);
let (sn, _cn, _dn) = sncndn(re * ks, m);
assert!((cs_re - sn).abs() < 1e-13, "re_out={cs_re} sn={sn}");
assert!(cs_im.abs() < 1e-14, "im_out should be 0: {cs_im}");
}
}