use core::f64::consts::{FRAC_PI_2, PI};
pub fn complete_elliptic(m: f64) -> (f64, f64) {
let (mut a, mut b, mut c) = (1.0, (1.0 - m).sqrt(), m.sqrt());
let (mut sum, mut weight) = (0.5 * m, 0.5);
for _ in 0..64 {
if c <= f64::EPSILON * a {
break;
}
(a, b, c) = (0.5 * (a + b), (a * b).sqrt(), 0.5 * (a - b));
weight *= 2.0;
sum += weight * c * c;
}
let k = FRAC_PI_2 / a;
(k, k * (1.0 - sum))
}
pub fn cel(kc: f64, p: f64, a: f64, b: f64) -> f64 {
assert!(p > 0.0, "cel is implemented for p > 0");
let (mut kc, mut e, mut m) = (kc.abs(), kc.abs(), 1.0);
let mut p = p.sqrt();
let (mut a, mut b) = (a, b / p);
for _ in 0..64 {
let f = a;
a += b / p;
let g = e / p;
b = 2.0 * (b + f * g);
p += g;
let previous = m;
m += kc;
if (previous - kc).abs() <= previous * 1e-15 {
break;
}
kc = 2.0 * e.sqrt();
e = kc * m;
}
FRAC_PI_2 * (a * m + b) / (m * (m + p))
}
const CARLSON_R: f64 = f64::EPSILON;
pub fn carlson_rf(x: f64, y: f64, z: f64) -> f64 {
let a0 = (x + y + z) / 3.0;
let q = (3.0 * CARLSON_R).powf(-1.0 / 6.0) * (a0 - x).abs().max((a0 - y).abs()).max((a0 - z).abs());
let (mut v, mut a, mut f) = ([x, y, z], a0, 1.0);
while f * q >= a.abs() {
let [sx, sy, sz] = v.map(f64::sqrt);
let l = sx * sy + sx * sz + sy * sz;
(v, a, f) = (v.map(|w| (w + l) / 4.0), (a + l) / 4.0, f / 4.0);
}
let (dx, dy) = ((a0 - x) * f / a, (a0 - y) * f / a);
let dz = -dx - dy;
let (e2, e3) = (dx * dy - dz * dz, dx * dy * dz);
(1.0 - e2 / 10.0 + e3 / 14.0 + e2 * e2 / 24.0 - 3.0 * e2 * e3 / 44.0) / a.sqrt()
}
pub fn carlson_rd(x: f64, y: f64, z: f64) -> f64 {
let a0 = (x + y + 3.0 * z) / 5.0;
let q = (CARLSON_R / 4.0).powf(-1.0 / 6.0) * (a0 - x).abs().max((a0 - y).abs()).max((a0 - z).abs());
let (mut v, mut a, mut f, mut sum) = ([x, y, z], a0, 1.0, 0.0);
while f * q >= a.abs() {
let [sx, sy, sz] = v.map(f64::sqrt);
let l = sx * sy + sx * sz + sy * sz;
sum += f / (sz * (v[2] + l));
(v, a, f) = (v.map(|w| (w + l) / 4.0), (a + l) / 4.0, f / 4.0);
}
let (dx, dy) = ((a0 - x) * f / a, (a0 - y) * f / a);
let dz = -(dx + dy) / 3.0;
let (e2, e3) = (dx * dy - 6.0 * dz * dz, (3.0 * dx * dy - 8.0 * dz * dz) * dz);
let (e4, e5) = (3.0 * (dx * dy - dz * dz) * dz * dz, dx * dy * dz.powi(3));
f / (a * a.sqrt()) * series(e2, e3, e4, e5) + 3.0 * sum
}
pub fn carlson_rj(x: f64, y: f64, z: f64, p: f64) -> f64 {
if p == 0.0 {
return f64::INFINITY;
}
if p < 0.0 {
let mut v = [x, y, z];
v.sort_by(f64::total_cmp);
let ([x, y, z], q) = (v, -p);
let pp = y + (z - y) * (y - x) / (y + q); let tail = 3.0 * (x * y * z / (x * z + pp * q)).sqrt() * carlson_rc(x * z + pp * q, pp * q);
return ((pp - y) * carlson_rj(x, y, z, pp) - 3.0 * carlson_rf(x, y, z) + tail) / (y + q);
}
let a0 = (x + y + z + 2.0 * p) / 5.0;
let delta = (p - x) * (p - y) * (p - z);
let q = (CARLSON_R / 4.0).powf(-1.0 / 6.0) * [x, y, z, p].iter().fold(0f64, |m, w| m.max((a0 - w).abs()));
let (mut v, mut a, mut f, mut sum) = ([x, y, z, p], a0, 1.0, 0.0);
while f * q >= a.abs() {
let [sx, sy, sz, sp] = v.map(f64::sqrt);
let l = sx * sy + sx * sz + sy * sz;
let d = (sp + sx) * (sp + sy) * (sp + sz);
sum += f / d * carlson_rc(1.0, 1.0 + f * f * f * delta / (d * d));
(v, a, f) = (v.map(|w| (w + l) / 4.0), (a + l) / 4.0, f / 4.0);
}
let [dx, dy, dz] = [x, y, z].map(|w| (a0 - w) * f / a);
let dp = -(dx + dy + dz) / 2.0;
let e2 = dx * dy + dx * dz + dy * dz - 3.0 * dp * dp;
let e3 = dx * dy * dz + 2.0 * e2 * dp + 4.0 * dp.powi(3);
let e4 = (2.0 * dx * dy * dz + e2 * dp + 3.0 * dp.powi(3)) * dp;
let e5 = dx * dy * dz * dp * dp;
f / (a * a.sqrt()) * series(e2, e3, e4, e5) + 6.0 * sum
}
pub fn carlson_rc(x: f64, y: f64) -> f64 {
if y == 0.0 {
return f64::INFINITY;
}
if y < 0.0 {
return (x / (x - y)).sqrt() * carlson_rc(x - y, -y);
}
let a0 = (x + 2.0 * y) / 3.0;
let q = (3.0 * CARLSON_R).powf(-1.0 / 8.0) * (a0 - x).abs();
let (mut v, mut a, mut f) = ([x, y], a0, 1.0);
while f * q >= a.abs() {
let l = 2.0 * v[0].sqrt() * v[1].sqrt() + v[1];
(v, a, f) = (v.map(|w| (w + l) / 4.0), (a + l) / 4.0, f / 4.0);
}
let s = (y - a0) * f / a;
(1.0 + s * s * (3.0 / 10.0 + s * (1.0 / 7.0 + s * (3.0 / 8.0 + s * (9.0 / 22.0 + s * (159.0 / 208.0 + s * 9.0 / 8.0)))))) / a.sqrt()
}
fn reduce(phi: f64) -> (f64, f64, f64) {
let q = phi / PI;
let j = if (q - q.round()).abs() == 0.5 { q.trunc() } else { q.round() };
let (s, c) = (phi - j * PI).sin_cos();
(j, s, c)
}
fn delta2(s: f64, c: f64, m: f64) -> f64 {
if m > 0.0 && m <= 1.0 { c * c + (1.0 - m) * s * s } else { 1.0 - m * s * s }
}
pub fn elliptic_f(phi: f64, m: f64) -> f64 {
let (j, s, c) = reduce(phi);
let d2 = delta2(s, c, m);
if d2 < 0.0 || (j != 0.0 && m >= 1.0) {
return f64::NAN;
}
s * carlson_rf(c * c, d2, 1.0) + if j == 0.0 { 0.0 } else { 2.0 * j * carlson_rf(0.0, 1.0 - m, 1.0) }
}
pub fn elliptic_e(phi: f64, m: f64) -> f64 {
let (j, s, c) = reduce(phi);
let (x, y) = (c * c, delta2(s, c, m));
if y < 0.0 || (j != 0.0 && m > 1.0) {
return f64::NAN;
}
let s3 = s * s * s;
let value = if s == 0.0 {
0.0
} else if m <= 0.0 {
s * carlson_rf(x, y, 1.0) - m / 3.0 * s3 * carlson_rd(x, y, 1.0)
} else if m == 1.0 {
s
} else if m < 1.0 {
(1.0 - m) * s * carlson_rf(x, y, 1.0) + m * (1.0 - m) / 3.0 * s3 * carlson_rd(x, 1.0, y) + m * s * c / y.sqrt()
} else {
-(1.0 - m) / 3.0 * s3 * carlson_rd(y, 1.0, x) + s * y.sqrt() / c
};
value + if j == 0.0 { 0.0 } else { 2.0 * j * (carlson_rf(0.0, 1.0 - m, 1.0) - m / 3.0 * carlson_rd(0.0, 1.0 - m, 1.0)) }
}
pub fn elliptic_pi(n: f64, phi: f64, m: f64) -> f64 {
let (j, s, c) = reduce(phi);
let y = delta2(s, c, m);
if y < 0.0 || (j != 0.0 && m >= 1.0) {
return f64::NAN;
}
let value = if s == 0.0 { 0.0 } else { s * carlson_rf(c * c, y, 1.0) + n / 3.0 * s * s * s * carlson_rj(c * c, y, 1.0, 1.0 - n * s * s) };
value + if j == 0.0 { 0.0 } else { 2.0 * j * (carlson_rf(0.0, 1.0 - m, 1.0) + n / 3.0 * carlson_rj(0.0, 1.0 - m, 1.0, 1.0 - n)) }
}
fn series(e2: f64, e3: f64, e4: f64, e5: f64) -> f64 {
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
}
pub fn ring(radius: f64, rho: f64, z: f64) -> (f64, [f64; 2]) {
let r = radius;
let s = (r + rho).powi(2) + z * z;
let d = (r - rho).powi(2) + z * z;
let (k, e) = complete_elliptic(4.0 * r * rho / s);
let v = 2.0 / PI * k / s.sqrt();
let dv_dz = -2.0 / PI * z * e / (s.sqrt() * d);
let u = r * r + z * z;
let dv_drho = if rho == 0.0 {
0.0
} else if rho * rho < 9e-8 * u {
let (z2, r2) = (z * z, r * r);
0.5 * rho * (r2 - 2.0 * z2) / u.powf(2.5) - rho.powi(3) / 16.0 * (-24.0 * z2 * z2 + 72.0 * z2 * r2 - 9.0 * r2 * r2) / u.powf(4.5)
} else {
let kc = (d / s).sqrt(); -2.0 / (PI * s.powf(1.5)) * cel(kc, kc * kc, rho + r, rho - r)
};
(v, [dv_drho, dv_dz])
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts::PI;
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
#[test]
fn complete_elliptic_integrals() {
let (k, e) = complete_elliptic(0.0);
assert!(close(k, FRAC_PI_2, 1e-15) && close(e, FRAC_PI_2, 1e-15));
let (k, e) = complete_elliptic(0.5);
assert!(close(k, 1.854_074_677_301_372, 1e-14) && close(e, 1.350_643_881_047_675, 1e-14));
let ((k, e), (k1, e1)) = (complete_elliptic(0.3), complete_elliptic(0.7));
assert!(close(e * k1 + e1 * k - k * k1, FRAC_PI_2, 1e-14));
}
#[test]
fn cel_reduces_to_k_and_e() {
for m in [1e-9, 0.1, 0.5, 0.9, 0.999_999] {
let (k, e) = complete_elliptic(m);
let kc = (1.0 - m).sqrt();
assert!(close(cel(kc, 1.0, 1.0, 1.0), k, 1e-14), "K at m = {m}");
assert!(close(cel(kc, 1.0, 1.0, kc * kc), e, 1e-14), "E at m = {m}");
assert!(close(cel(kc, kc * kc, 1.0, 1.0), e / (1.0 - m), 1e-13), "E/(1−m) at m = {m}");
}
}
#[test]
fn cel_keeps_a_cancelling_numerator() {
for k in [10, 30, 50] {
let delta = 2f64.powi(-k);
assert!(close(cel(1.0, 1.0, 1.0 + delta, -1.0 + delta), delta * FRAC_PI_2, 1e-15), "δ = 2^-{k}");
}
}
#[test]
fn carlsons_table() {
assert!(close(carlson_rf(1.0, 2.0, 0.0), 1.311_028_777_146_1, 1e-13));
assert!(close(carlson_rf(0.5, 1.0, 0.0), 1.854_074_677_301_4, 1e-13));
assert!(close(carlson_rf(2.0, 3.0, 4.0), 0.584_082_841_677_15, 1e-13));
assert!(close(carlson_rc(0.0, 0.25), PI, 1e-15));
assert!(close(carlson_rc(2.25, 2.0), 2f64.ln(), 1e-15));
assert!(close(carlson_rj(0.0, 1.0, 2.0, 3.0), 0.776_886_237_785_82, 1e-13));
assert!(close(carlson_rj(2.0, 3.0, 4.0, 5.0), 0.142_975_796_671_57, 1e-13));
assert!(close(carlson_rd(0.0, 2.0, 1.0), 1.797_210_352_103_4, 1e-13));
assert!(close(carlson_rd(2.0, 3.0, 4.0), 0.165_105_272_942_61, 1e-13));
assert!(close(carlson_rf(4.0, 4.0, 4.0), 0.5, 1e-15) && close(carlson_rd(4.0, 4.0, 4.0), 0.125, 1e-15));
assert!(close(carlson_rj(4.0, 4.0, 4.0, 4.0), 0.125, 1e-15) && close(carlson_rj(2.0, 3.0, 4.0, 4.0), carlson_rd(2.0, 3.0, 4.0), 1e-14));
assert!(close(carlson_rf(8.0, 12.0, 16.0), carlson_rf(2.0, 3.0, 4.0) / 2.0, 1e-15));
}
#[test]
fn carlson_principal_values() {
assert!(close(carlson_rc(0.25, -2.0), 2f64.ln() / 3.0, 1e-14));
assert_eq!(carlson_rc(0.0, -2.0), 0.0);
assert!(close(carlson_rj(2.0, 3.0, 4.0, -0.5), 0.247_238_197_030_52, 1e-13));
assert!(close(carlson_rj(2.0, 3.0, 4.0, -5.0), -0.127_112_300_429_64, 1e-13));
assert!(close(carlson_rj(4.0, 2.0, 3.0, -0.5), carlson_rj(2.0, 3.0, 4.0, -0.5), 1e-14));
assert!(carlson_rj(2.0, 3.0, 4.0, -1.252_195_934_7).abs() < 1e-10);
assert!(carlson_rj(2.0, 3.0, 4.0, -1.24) > 0.0 && carlson_rj(2.0, 3.0, 4.0, -1.26) < 0.0);
assert!(carlson_rj(2.0, 3.0, 4.0, 0.0).is_infinite() && carlson_rc(1.0, 0.0).is_infinite());
}
#[test]
fn carlson_against_the_agm_and_cel() {
for m in [1e-6, 0.1, 0.5, 0.9, 0.999] {
let (k, e) = complete_elliptic(m);
let (rf, rd) = (carlson_rf(0.0, 1.0 - m, 1.0), carlson_rd(0.0, 1.0 - m, 1.0));
assert!(close(rf, k, 2e-15) && close(rf - m / 3.0 * rd, e, 2e-15), "m = {m}");
let kc = (1.0 - m).sqrt();
for n in [-2.0, 0.3, 0.8] {
let pi3 = rf + n / 3.0 * carlson_rj(0.0, 1.0 - m, 1.0, 1.0 - n);
assert!(close(pi3, cel(kc, 1.0 - n, 1.0, 1.0), 1e-14), "Π({n}, {m})");
}
}
assert!(close(carlson_rc(1.0, 4.0), (3f64.sqrt()).atan() / 3f64.sqrt(), 1e-15));
assert!(close(carlson_rc(4.0, 1.0), (2.0 + 3f64.sqrt()).ln() / 3f64.sqrt(), 1e-15));
}
#[test]
fn legendres_incomplete_integrals_against_abramowitz_and_stegun() {
let d = f64::to_radians;
for (phi, alpha, want) in [(30.0, 45.0, 0.535_622_73), (30.0, 60.0, 0.542_229_11), (30.0, 90.0, 0.549_306_14), (20.0, 40.0, 0.351_988_69)] {
assert!((elliptic_f(d(phi), d(alpha).sin().powi(2)) - want).abs() < 5e-9, "F({phi}\\{alpha})");
}
for (phi, alpha, want) in [(30.0, 45.0, 0.512_049_32), (30.0, 60.0, 0.506_092_07), (90.0, 45.0, 1.350_643_88), (70.0, 60.0, 1.026_636_89), (75.0, 85.0, 0.969_922_12), (80.0, 88.0, 0.985_689_15), (85.0, 88.0, 0.997_483_92)] {
assert!((elliptic_e(d(phi), d(alpha).sin().powi(2)) - want).abs() < 5e-9, "E({phi}\\{alpha})");
}
for (n, phi, alpha, want) in [(0.5, 60.0, 45.0, 1.38218), (0.3, 75.0, 60.0, 1.93879), (0.6, 45.0, 30.0, 0.91527), (0.5, 90.0, 45.0, 2.70129), (0.1, 90.0, 0.0, 1.65576), (0.0, 75.0, 90.0, 2.02759)] {
assert!((elliptic_pi(n, d(phi), d(alpha).sin().powi(2)) - want).abs() < 5e-6, "Π({n};{phi}\\{alpha})");
}
}
#[test]
#[allow(clippy::approx_constant)] fn legendres_integrals_special_values_and_periods() {
assert!(close(elliptic_f(0.7, 1.0), 0.7f64.sin().atanh(), 1e-15) && close(elliptic_e(0.7, 1.0), 0.7f64.sin(), 1e-15));
assert!(close(elliptic_f(1.3, 0.0), 1.3, 1e-15) && close(elliptic_e(1.3, 0.0), 1.3, 1e-15));
assert!(close(elliptic_pi(1.0, 1.1, 0.0), 1.1f64.tan(), 1e-14));
let csc2 = 1.0 / 1.1f64.sin().powi(2);
assert!(close(elliptic_pi(3.0, 1.1, 0.0), carlson_rc(csc2 - 1.0, csc2 - 3.0), 1e-14));
let m = 0.7;
let (k, e) = complete_elliptic(m);
assert!(close(elliptic_f(FRAC_PI_2, m), k, 1e-15) && close(elliptic_e(FRAC_PI_2, m), e, 1e-15));
for (j, r) in [(1.0, 0.4), (2.0, -0.3), (-1.0, 1.0)] {
assert!(close(elliptic_f(j * PI + r, m), 2.0 * j * k + elliptic_f(r, m), 1e-14), "F, j = {j}");
assert!(close(elliptic_e(j * PI + r, m), 2.0 * j * e + elliptic_e(r, m), 1e-14), "E, j = {j}");
}
assert!((elliptic_f(-0.8, 0.6) + elliptic_f(0.8, 0.6)).abs() < 1e-16 && (elliptic_e(-0.8, 0.6) + elliptic_e(0.8, 0.6)).abs() < 1e-16);
assert!(close(elliptic_pi(2.0, PI + 0.3, 0.5), 2.0 * elliptic_pi(2.0, FRAC_PI_2, 0.5) + elliptic_pi(2.0, 0.3, 0.5), 1e-14));
let (phi, m): (f64, f64) = (0.5, 2.5);
let beta = (m.sqrt() * phi.sin()).asin();
assert!(close(elliptic_f(phi, m), elliptic_f(beta, 1.0 / m) / m.sqrt(), 1e-14) && close(elliptic_f(phi, m), 0.570_176_528_700_536, 1e-14));
assert!(close(elliptic_e(phi, m), m.sqrt() * (elliptic_e(beta, 1.0 / m) - (1.0 - 1.0 / m) * elliptic_f(beta, 1.0 / m)), 1e-14));
assert!(close(elliptic_e(phi, m), 0.444_842_124_053_142, 1e-14) && elliptic_f(1.0, m).is_nan() && elliptic_e(1.0, m).is_nan());
let (phi, m): (f64, f64) = (1.5707, 1.0 - 1e-10);
let (s, c) = phi.sin_cos();
let (x, plain) = (c * c, 1.0 - m * s * s);
let lossy = (1.0 - m) * s * carlson_rf(x, plain, 1.0) + m * (1.0 - m) / 3.0 * s * s * s * carlson_rd(x, 1.0, plain) + m * s * c / plain.sqrt();
let other = s * carlson_rf(x, plain, 1.0) - m / 3.0 * s * s * s * carlson_rd(x, plain, 1.0);
assert!(close(elliptic_e(phi, m), other, 1e-14) && (lossy / other - 1.0).abs() > 1e-10, "{} {lossy} {other}", elliptic_e(phi, m));
}
#[test]
fn pi_beyond_its_pole_is_the_principal_value() {
for (n, phi, m, want) in [(2.0, 1.2, 0.5, 0.253_604_270_701_507), (4.0, 0.9, 0.3, 0.259_293_626_045_840), (1.5, 1.4, 0.9, -0.973_823_998_688_881), (10.0, 0.5, 0.2, 0.233_655_250_908_165)] {
let c = 1.0 / f64::sin(phi).powi(2);
let w2: f64 = m / n;
let dlmf = -w2 / 3.0 * carlson_rj(c - 1.0, c - m, c, c - w2) + ((c - 1.0) * (c - m) / ((n - 1.0) * (1.0 - w2))).sqrt() * carlson_rc(c * (n - 1.0) * (1.0 - w2), (n - c) * (c - w2));
let ours = elliptic_pi(n, phi, m);
assert!(close(ours, dlmf, 1e-14) && close(ours, want, 1e-14), "Π({n};{phi}|{m}) = {ours}");
}
for (n, m) in [(2.0, 0.5), (5.0, 0.9)] {
assert!(close(elliptic_pi(n, FRAC_PI_2, m), complete_elliptic(m).0 - elliptic_pi(m / n, FRAC_PI_2, m), 1e-13));
}
assert!(close(elliptic_pi(2.0, FRAC_PI_2, 0.5), -0.313_544_683_465_184, 1e-13));
assert!(elliptic_pi(1.0, FRAC_PI_2, 0.3).is_infinite() && close(elliptic_pi(0.0, 0.9, 0.4), elliptic_f(0.9, 0.4), 1e-15));
}
fn summed_ring(radius: f64, rho: f64, z: f64, n: usize) -> [f64; 3] {
let mut sums = [(0.0f64, 0.0f64); 3];
for i in 0..n {
let t = 2.0 * PI * (i as f64 + 0.5) / n as f64;
let d2 = rho * rho + radius * radius - 2.0 * rho * radius * t.cos() + z * z;
let terms = [1.0 / d2.sqrt(), -(rho - radius * t.cos()) / d2.powf(1.5), -z / d2.powf(1.5)];
for (k, &x) in terms.iter().enumerate() {
let x = x / n as f64;
let (sum, carry) = &mut sums[k];
let next = *sum + x;
*carry += if sum.abs() >= x.abs() { (*sum - next) + x } else { (x - next) + *sum };
*sum = next;
}
}
sums.map(|(s, c)| s + c)
}
#[test]
fn a_ring_against_direct_summation() {
let r = 0.04;
for (rho, z) in [(0.023, 0.027), (r * 3.5e-4, 0.01), (r * 2.5e-4, 0.01), (r * 1e-3, 0.01), (1e-3, 4.0), (0.3, 4.0), (0.02, 0.0), (0.0399, 1e-4)] {
let (v, [dr, dz]) = ring(r, rho, z);
let [sv, sr, sz] = summed_ring(r, rho, z, 20_000);
assert!(close(v, sv, 1e-12) && close(dr, sr, 1e-10), "ρ = {rho}, z = {z}: {dr:e} vs {sr:e}");
assert!(if z == 0.0 { dz == 0.0 } else { close(dz, sz, 1e-12) }, "∂z at ρ = {rho}, z = {z}");
}
for f in [1e-7, 1e-10] {
let (rho, z) = (f * r, 0.01);
let u = r * r + z * z;
let near = 0.5 * rho * (r * r - 2.0 * z * z) / u.powf(2.5);
assert!(close(ring(r, rho, z).1[0], near, 1e-12), "ρ = {f}R");
}
let (v, [dr, dz]) = ring(2.0, 0.0, 1.5);
assert!(close(v, 0.4, 1e-15) && dr == 0.0 && close(dz, -1.5 / 15.625, 1e-15));
}
}