#![allow(clippy::extra_unused_type_parameters)]
use thermite::{
math::{TranscendentalMathWithPolicy as _, policy::Policy},
prelude::*,
register::FloatElement,
};
use crate::specialized::SpecializedSpecialMath;
pub const KIND_F: u8 = 1; pub const KIND_E: u8 = 2; pub const KIND_PI: u8 = 3; pub const KIND_D: u8 = 4;
macro_rules! sc {
($n:literal / $d:literal) => {
<E as FloatElement>::ConstRatio::<$n, $d>::VALUE
};
}
#[inline(always)]
pub fn agm_complete_ke<E, V>(k: V) -> (V, V)
where
E: FloatElement,
V: FloatVector<Element = E>,
{
let mut a = V::ONE;
let mut b = k.one_minus_sq().sqrt();
let mut c = k;
let mut sum = (c * c).scale(sc!(1 / 2));
let mut pow2 = V::ONE;
let thresh = V::SQRT_EPSILON; let mut iter = 0;
loop {
let an = (a + b).scale(sc!(1 / 2));
let bn = (a * b).sqrt();
c = (a - b).scale(sc!(1 / 2));
a = an;
b = bn;
sum = pow2.mul_adde(c * c, sum); pow2 = pow2 + pow2;
iter += 1;
if iter >= 24 || c.abs().cmp_le(a * thresh).all() {
break;
}
}
let k_int = V::FRAC_PI_2 / a;
let e_int = k_int.nmul_adde(sum, k_int); (k_int, e_int)
}
pub trait EllipticConsts {
const CARLSON_THRESH: Self;
}
impl EllipticConsts for f32 {
const CARLSON_THRESH: f32 = 0.15637917816638947;
}
impl EllipticConsts for f64 {
const CARLSON_THRESH: f64 = 0.012674918778210762;
}
#[inline(always)]
fn carlson_thresh<V: FloatVector<Element: EllipticConsts>>() -> V {
V::splat(<V::Element as EllipticConsts>::CARLSON_THRESH)
}
#[inline(always)]
pub fn carlson_rf<P, E, V>(x: V, y: V, z: V) -> V
where
P: Policy,
E: FloatElement + EllipticConsts,
V: FloatVector<Element = E>,
{
macro_rules! c {
($n:literal / $d:literal) => {
V::splat(<E as FloatElement>::ConstRatio::<$n, $d>::VALUE)
};
}
let quarter = c!(1 / 4);
let thresh = carlson_thresh::<V>();
let mut xn = x;
let mut yn = y;
let mut zn = z;
let mut an = (x + y + z).scale(sc!(1 / 3));
let a0 = an;
let mut fmn = V::ONE; let q = (a0 - x).abs().max((a0 - y).abs()).max((a0 - z).abs()) / thresh;
let mut iter = 0;
loop {
let rx = xn.sqrt();
let ry = yn.sqrt();
let rz = zn.sqrt();
let lambda = rx.mul_adde(ry + rz, ry * rz); if const { V::HAS_TRUE_FMA } {
let lq = lambda * quarter;
an = an.mul_add(quarter, lq);
xn = xn.mul_add(quarter, lq);
yn = yn.mul_add(quarter, lq);
zn = zn.mul_add(quarter, lq);
} else {
an = (an + lambda) * quarter;
xn = (xn + lambda) * quarter;
yn = (yn + lambda) * quarter;
zn = (zn + lambda) * quarter;
}
fmn *= quarter;
iter += 1;
if iter >= 30 || (fmn * q).cmp_le(an).all() {
break;
}
}
let scale = fmn / an; let xd = (a0 - x) * scale;
let yd = (a0 - y) * scale;
let zd = -xd - yd;
let e2 = xd.mul_sube(yd, zd * zd); let e3 = xd * yd * zd;
let e2_2 = e2 * e2;
let lin = e2.mul_adde(c!(-1 / 10), V::ONE);
let lin = e3.mul_adde(c!(1 / 14), lin);
let quad = (e2 * e3).mul_adde(c!(-3 / 44), e2_2.scale(sc!(1 / 24)));
let quad = (e3 * e3).mul_adde(c!(3 / 104), quad);
let cub = (e2_2 * e3).mul_adde(c!(1 / 16), (e2_2 * e2).scale(sc!(-5 / 208)));
let poly = lin + (quad + cub);
poly / an.sqrt()
}
#[inline(always)]
pub fn carlson_rd<P, E, V>(x: V, y: V, z: V) -> V
where
P: Policy,
E: FloatElement + EllipticConsts,
V: FloatVector<Element = E>,
{
macro_rules! c {
($n:literal / $d:literal) => {
V::splat(<E as FloatElement>::ConstRatio::<$n, $d>::VALUE)
};
}
let quarter = c!(1 / 4);
let thresh = carlson_thresh::<V>();
let mut xn = x;
let mut yn = y;
let mut zn = z;
let mut an = ((x + y) + (z + z + z)).scale(sc!(1 / 5)); let a0 = an;
let mut sum = V::ZERO;
let mut fac = V::ONE; let q = (a0 - x).abs().max((a0 - y).abs()).max((a0 - z).abs()) / thresh;
let mut iter = 0;
loop {
let rx = xn.sqrt();
let ry = yn.sqrt();
let rz = zn.sqrt();
let lambda = rx.mul_adde(ry + rz, ry * rz); sum += fac / (rz * (zn + lambda));
if const { V::HAS_TRUE_FMA } {
let lq = lambda * quarter;
an = an.mul_add(quarter, lq);
xn = xn.mul_add(quarter, lq);
yn = yn.mul_add(quarter, lq);
zn = zn.mul_add(quarter, lq);
} else {
an = (an + lambda) * quarter;
xn = (xn + lambda) * quarter;
yn = (yn + lambda) * quarter;
zn = (zn + lambda) * quarter;
}
fac *= quarter;
iter += 1;
if iter >= 30 || (fac * q).cmp_le(an).all() {
break;
}
}
let scale = fac / an; let xd = (a0 - x) * scale;
let yd = (a0 - y) * scale;
let zd = (xd + yd).scale(sc!(-1 / 3));
let xy = xd * yd;
let zz = zd * zd;
let xy3 = xy.scale(sc!(3 / 1)); let e2 = zz.mul_adde(c!(-6 / 1), xy); let e3 = zz.mul_adde(c!(-8 / 1), xy3) * zd; let e4 = zz.mul_adde(c!(-3 / 1), xy3) * zz; let e5 = xy * (zz * zd);
let taylor = fac * rdj_poly::<E, V>(e2, e3, e4, e5) / (an * an.sqrt()); c!(3 / 1).mul_adde(sum, taylor) }
#[inline(always)]
fn rdj_poly<E, V>(e2: V, e3: V, e4: V, e5: V) -> V
where
E: FloatElement,
V: FloatVector<Element = E>,
{
macro_rules! c {
($n:literal / $d:literal) => {
V::splat(<E as FloatElement>::ConstRatio::<$n, $d>::VALUE)
};
}
let e2_2 = e2 * e2;
let lin = e2.mul_adde(c!(-3 / 14), V::ONE);
let lin = e3.mul_adde(c!(1 / 6), lin);
let lin = e4.mul_adde(c!(-3 / 22), lin);
let lin = e5.mul_adde(c!(3 / 26), lin);
let quad = (e2 * e3).mul_adde(c!(-9 / 52), e2_2.scale(sc!(9 / 88)));
let quad = (e3 * e3).mul_adde(c!(3 / 40), quad);
let quad = (e2 * e4).mul_adde(c!(3 / 20), quad);
let cub = (e2_2 * e3).mul_adde(c!(45 / 272), (e2_2 * e2).scale(sc!(-1 / 16)));
let cub = (e3 * e4 + e2 * e5).mul_adde(c!(-9 / 68), cub);
lin + (quad + cub)
}
#[inline(always)]
pub fn carlson_rg<P, E, V>(x: V, y: V, z: V) -> V
where
P: Policy,
E: FloatElement + EllipticConsts,
V: FloatVector<Element = E>,
{
macro_rules! c {
($n:literal / $d:literal) => {
V::splat(<E as FloatElement>::ConstRatio::<$n, $d>::VALUE)
};
}
let lo = x.min(y).min(z);
let hi = x.max(y).max(z);
let mid = (x + y + z) - (lo + hi); let rf = carlson_rf::<P, E, V>(hi, lo, mid);
let rd = carlson_rd::<P, E, V>(hi, lo, mid);
let root = (hi * lo / mid).sqrt();
let prod = (hi - mid) * (lo - mid) * rd;
prod.mul_adde(c!(-1 / 3), mid.mul_adde(rf, root)).scale(sc!(1 / 2))
}
#[inline(always)]
pub fn carlson_rc<P, E, V>(x: V, y: V) -> V
where
P: Policy,
E: FloatElement,
V: SpecializedSpecialMath<E>,
{
macro_rules! c {
($n:literal / $d:literal) => {
V::splat(<E as FloatElement>::ConstRatio::<$n, $d>::VALUE)
};
}
let d = y - x;
let absd = d.abs();
let (s, neg_arg, irx, scale) = if const { V::HAS_APPROX_RSQRT } {
let isad = absd.inverse_sqrt_p::<P>(); let irx = x.inverse_sqrt_p::<P>(); let iry = y.inverse_sqrt_p::<P>(); let sad = absd * isad; let rx = x * irx; (sad * irx, (rx + sad) * iry, irx, isad)
} else {
let sad = absd.sqrt(); let rx = x.sqrt();
let irx = rx.reciprocal_p::<P>(); (sad * irx, (rx + sad) / y.sqrt(), irx, sad)
};
let num = d.cmp_gt(V::ZERO).select(s.atan_p::<P>(), neg_arg.ln_p::<P>());
let closed = if const { V::HAS_APPROX_RSQRT } {
num * scale
} else {
num / scale
};
let t = d / x;
let mut res = closed;
let small = t.abs().cmp_lt(c!(1 / 128));
if const { P::POLICY.avoid_branching } || small.any() {
let series = t.poly_rev_p::<P, _>(&[
<E as FloatElement>::ConstRatio::<-1, 15>::VALUE,
<E as FloatElement>::ConstRatio::<1, 13>::VALUE,
<E as FloatElement>::ConstRatio::<-1, 11>::VALUE,
<E as FloatElement>::ConstRatio::<1, 9>::VALUE,
<E as FloatElement>::ConstRatio::<-1, 7>::VALUE,
<E as FloatElement>::ConstRatio::<1, 5>::VALUE,
<E as FloatElement>::ConstRatio::<-1, 3>::VALUE,
<E as FloatElement>::ConstRatio::<1, 1>::VALUE,
]) * irx;
res = small.select(series, res);
}
res
}
#[inline(always)]
pub fn carlson_rj<P, E, V>(x: V, y: V, z: V, p: V) -> V
where
P: Policy,
E: FloatElement + EllipticConsts,
V: SpecializedSpecialMath<E>,
{
macro_rules! c {
($n:literal / $d:literal) => {
V::splat(<E as FloatElement>::ConstRatio::<$n, $d>::VALUE)
};
}
let quarter = c!(1 / 4);
let thresh = carlson_thresh::<V>();
let lo = x.min(y).min(z);
let hi = x.max(y).max(z);
let mid = (x + y + z) - (lo + hi);
let neg = p.cmp_lt(V::ZERO);
let q = -p; let p_new = hi.mul_sube(lo + mid + q, lo * mid) / (hi + q);
let p_eff = neg.select(p_new, p);
let mut xn = lo;
let mut yn = mid;
let mut zn = hi;
let mut pn = p_eff;
let mut an = ((lo + mid + hi) + (p_eff + p_eff)).scale(sc!(1 / 5)); let a0 = an;
let mut rc_sum = V::ZERO;
let mut fmn = V::ONE; let qb = (a0 - lo)
.abs()
.max((a0 - mid).abs())
.max((a0 - hi).abs().max((a0 - p_eff).abs()))
/ thresh;
let mut iter = 0;
loop {
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 inner = ry.mul_adde(rz, rx.mul_adde(ry + rz, pn)); let b = (rp * inner) / dn.scale(sc!(1 / 2));
rc_sum = (fmn / dn).mul_adde(carlson_rc::<P, E, V>(V::ONE, b), rc_sum);
let lambda = rx.mul_adde(ry + rz, ry * rz); if const { V::HAS_TRUE_FMA } {
let lq = lambda * quarter;
an = an.mul_add(quarter, lq);
xn = xn.mul_add(quarter, lq);
yn = yn.mul_add(quarter, lq);
zn = zn.mul_add(quarter, lq);
pn = pn.mul_add(quarter, lq);
} else {
an = (an + lambda) * quarter;
xn = (xn + lambda) * quarter;
yn = (yn + lambda) * quarter;
zn = (zn + lambda) * quarter;
pn = (pn + lambda) * quarter;
}
fmn *= quarter;
iter += 1;
if iter >= 30 || (fmn * qb).cmp_le(an).all() {
break;
}
}
let scale = fmn / an; let xd = (a0 - lo) * scale;
let yd = (a0 - mid) * scale;
let zd = (a0 - hi) * scale;
let pd = (xd + yd + zd).scale(sc!(-1 / 2));
let xyz = xd * yd * zd;
let pp = pd * pd;
let ppd = pp * pd; let sym = yd.mul_adde(zd, xd * (yd + zd)); let e2 = pp.mul_adde(c!(-3 / 1), sym); let pre3 = ppd.mul_adde(c!(4 / 1), xyz); let e3 = e2.mul_adde(pd + pd, pre3); let pre4 = ppd.mul_adde(c!(3 / 1), xyz + xyz) * pd; let e4 = e2.mul_adde(pp, pre4); let e5 = xyz * pp;
let taylor = fmn * rdj_poly::<E, V>(e2, e3, e4, e5) / (an * an.sqrt());
let rj = c!(6 / 1).mul_adde(rc_sum, taylor);
if const { P::POLICY.avoid_branching } || neg.any() {
let rf = carlson_rf::<P, E, V>(lo, mid, hi);
let xy = lo * mid;
let xyz = xy * hi;
let pq = p_new * q;
let rc = carlson_rc::<P, E, V>(xy + pq, pq);
let root = (xyz / (xy + pq)).sqrt();
let val_neg = (p_new - hi).mul_adde(rj, root.mul_sube(rc, rf).scale(sc!(3 / 1))) / (hi + q);
neg.select(val_neg, rj)
} else {
rj
}
}
#[inline(always)]
pub fn ellint_impl<P, E, V, const KIND: u8, const COMPLETE: bool>(phi: V, k: V, n: V) -> V
where
P: Policy,
E: FloatElement + EllipticConsts,
V: SpecializedSpecialMath<E>, {
const {
assert!(
KIND == KIND_F || KIND == KIND_E || KIND == KIND_D || KIND == KIND_PI,
"ellint_impl: KIND must be KIND_F, KIND_E, KIND_D, or KIND_PI"
);
}
if const { COMPLETE } {
if const { KIND == KIND_PI } {
let w = k.one_minus_sq(); let rf = carlson_rf::<P, E, V>(V::ZERO, w, V::ONE);
let rj = carlson_rj::<P, E, V>(V::ZERO, w, V::ONE, V::ONE - n);
n.scale(sc!(1 / 3)).mul_adde(rj, rf) } else {
let (kk, ee) = agm_complete_ke::<E, V>(k);
if const { KIND == KIND_F } {
kk
} else if const { KIND == KIND_E } {
ee
} else {
(kk - ee) / (k * k)
}
}
} else {
let m = (phi * V::FRAC_1_PI).round();
let phi_red = m.nmul_adde(V::PI, phi);
let (s, cphi) = phi_red.sin_cos_p::<P>();
let c2 = cphi * cphi;
let ks = k * s;
let w = ks.one_minus_sq(); let rf = carlson_rf::<P, E, V>(c2, w, V::ONE);
let core = if const { KIND == KIND_F } {
s * rf
} else if const { KIND == KIND_PI } {
let pp = n.nmul_adde(s * s, V::ONE);
let rj = carlson_rj::<P, E, V>(c2, w, V::ONE, pp);
let s3 = s * s * s;
(n.scale(sc!(1 / 3)) * s3).mul_adde(rj, s * rf) } else {
let rd = carlson_rd::<P, E, V>(c2, w, V::ONE);
let s3 = s * s * s;
if const { KIND == KIND_E } {
((k * k).scale(sc!(1 / 3)) * s3).nmul_adde(rd, s * rf)
} else {
s3.scale(sc!(1 / 3)) * rd
}
};
if const { P::POLICY.avoid_branching } || !m.is_zero().all() {
let complete = ellint_impl::<P, E, V, KIND, true>(phi, k, n);
m.is_zero().select(core, (m + m).mul_adde(complete, core))
} else {
core
}
}
}
pub trait CarlsonKind {
type Output;
fn eval<P: Policy>(self) -> Self::Output;
}
pub trait EllipticKind {
type Output;
fn eval<P: Policy>(self) -> Self::Output;
}
use thermite::math::scalar::Unwrap;
pub trait WrapTo {
type Wrapped: Unwrap<Unwrapped = Self>;
}
macro_rules! request_struct {
($(#[$meta:meta])* $name:ident { $($field:ident),* }) => {
#[derive(Debug, Clone, Copy)]
$(#[$meta])* pub struct $name<V> {
$(pub $field: V,)*
}
impl<V: Unwrap> Unwrap for $name<V> {
type Unwrapped = $name<<V as Unwrap>::Unwrapped>;
#[inline(always)]
fn wrap(value: Self::Unwrapped) -> Self {
$name { $($field: Unwrap::wrap(value.$field),)* }
}
#[inline(always)]
fn unwrap(self) -> Self::Unwrapped {
$name { $($field: self.$field.unwrap(),)* }
}
}
impl<E> WrapTo for $name<E>
where
E: FloatElement + thermite::register::FloatRegister<Storage = E>,
thermite::Vector<E>: Unwrap<Unwrapped = E>,
{
type Wrapped = $name<thermite::Vector<E>>;
}
};
}
macro_rules! decl_carlson {
($( $(#[$meta:meta])* struct $name:ident { $($field:ident),* } => $func:ident; )*) => {$(
request_struct! { $(#[$meta])* $name { $($field),* } }
impl<E, V> CarlsonKind for $name<V>
where
E: FloatElement + EllipticConsts,
V: FloatVector<Element = E> + SpecializedSpecialMath<E>,
{
type Output = V;
#[inline(always)]
fn eval<P: Policy>(self) -> V {
$func::<P, E, V>($(self.$field),*)
}
}
)*};
}
decl_carlson! {
struct CarlsonRf { x, y, z } => carlson_rf;
struct CarlsonRc { x, y } => carlson_rc;
struct CarlsonRd { x, y, z } => carlson_rd;
struct CarlsonRj { x, y, z, p } => carlson_rj;
struct CarlsonRg { x, y, z } => carlson_rg;
}
macro_rules! decl_ellint {
($( $(#[$meta:meta])* struct $name:ident { $($field:ident),* } = [$kind:expr, $complete:expr]($phi:ident, $k:ident, $n:ident); )*) => {$(
request_struct! { $(#[$meta])* $name { $($field),* } }
impl<E, V> EllipticKind for $name<V>
where
E: FloatElement + EllipticConsts,
V: FloatVector<Element = E> + SpecializedSpecialMath<E>,
{
type Output = V;
#[inline(always)]
fn eval<P: Policy>(self) -> V {
ellint_impl::<P, E, V, { $kind }, { $complete }>(self.$phi, self.$k, self.$n)
}
}
)*};
}
decl_ellint! {
struct EllintK { k } = [KIND_F, true](k, k, k);
struct EllintF { phi, k } = [KIND_F, false](phi, k, k);
struct EllintE { k } = [KIND_E, true](k, k, k);
struct EllintEInc { phi, k } = [KIND_E, false](phi, k, k);
struct EllintD { k } = [KIND_D, true](k, k, k);
struct EllintDInc { phi, k } = [KIND_D, false](phi, k, k);
struct EllintPi { n, k } = [KIND_PI, true](k, k, n);
struct EllintPiInc { n, phi, k } = [KIND_PI, false](phi, k, n);
}
#[cfg(test)]
mod tests {
#![allow(clippy::approx_constant)]
use super::*;
use thermite::backend::x86_v3::prelude::*;
use thermite::math::policy::policies::Precision;
fn close(got: f64, want: f64, tol: f64) -> bool {
(got - want).abs() <= tol * want.abs().max(1.0)
}
const KE: &[(f64, f64, f64)] = &[
(0.0, 1.570796326794897, 1.570796326794897),
(0.5, 1.685750354812596, 1.467462209339427),
(0.7071067811865476, 1.854074677301372, 1.350643881047676),
(0.8660254037844386, 2.156515647499643, 1.211056027568459),
(0.9486832980505138, 2.578092113348173, 1.104774732704073),
];
#[test]
fn agm_complete_ke_matches_reference() {
for &(k, want_k, want_e) in KE {
let (kk, ee) = agm_complete_ke::<f64, _>(f64x4::splat(k));
let gk = kk.extract::<0>();
let ge = ee.extract::<0>();
assert!(close(gk, want_k, 1.0e-13), "K({k}): got {gk}, want {want_k}");
assert!(close(ge, want_e, 1.0e-13), "E({k}): got {ge}, want {want_e}");
}
}
#[test]
fn carlson_thresh_const_matches_runtime() {
let e64 = f64::EPSILON;
assert_eq!(
<f64 as EllipticConsts>::CARLSON_THRESH,
(e64 + e64 + e64).sqrt().sqrt().sqrt()
);
let e32 = f32::EPSILON;
assert_eq!(
<f32 as EllipticConsts>::CARLSON_THRESH,
(e32 + e32 + e32).sqrt().sqrt().sqrt()
);
}
#[test]
fn carlson_exact_spot_checks() {
let rf = carlson_rf::<Precision, f64, _>(f64x4::splat(4.0), f64x4::splat(4.0), f64x4::splat(4.0));
assert!(close(rf.extract::<0>(), 0.5, 1.0e-14), "R_F(4,4,4)");
let rd = carlson_rd::<Precision, f64, _>(f64x4::splat(4.0), f64x4::splat(4.0), f64x4::splat(4.0));
assert!(close(rd.extract::<0>(), 0.125, 1.0e-14), "R_D(4,4,4)");
let rg = carlson_rg::<Precision, f64, _>(f64x4::splat(4.0), f64x4::splat(4.0), f64x4::splat(4.0));
assert!(close(rg.extract::<0>(), 2.0, 1.0e-14), "R_G(4,4,4)");
}
#[test]
fn carlson_rc_f32_rsqrt_path() {
let rc =
|x: f32, y: f32| carlson_rc::<Precision, f32, _>(f32x4::splat(x), f32x4::splat(y)).extract::<0>() as f64;
assert!(
close(rc(1.0, 2.0), core::f64::consts::FRAC_PI_4, 1.0e-6),
"R_C(1,2) = pi/4"
);
assert!(
close(rc(2.0, 1.0), (1.0 + 2.0_f64.sqrt()).ln(), 1.0e-6),
"R_C(2,1) = ln(1+sqrt2)"
);
assert!(close(rc(4.0, 4.0), 0.5, 1.0e-6), "R_C(4,4) = 1/2 (series)");
}
#[test]
fn carlson_kind_dispatch() {
let q = f64x4::splat(4.0);
let rf = CarlsonRf { x: q, y: q, z: q }.eval::<Precision>();
let rd = CarlsonRd { x: q, y: q, z: q }.eval::<Precision>();
let rg = CarlsonRg { x: q, y: q, z: q }.eval::<Precision>();
let rc = CarlsonRc { x: q, y: q }.eval::<Precision>();
let rj = CarlsonRj { x: q, y: q, z: q, p: q }.eval::<Precision>();
assert!(close(rf.extract::<0>(), 0.5, 1.0e-14), "R_F");
assert!(close(rd.extract::<0>(), 0.125, 1.0e-14), "R_D");
assert!(close(rg.extract::<0>(), 2.0, 1.0e-14), "R_G");
assert!(close(rc.extract::<0>(), 0.5, 1.0e-14), "R_C");
assert!(close(rj.extract::<0>(), 0.125, 1.0e-13), "R_J");
}
#[test]
fn carlson_rg_matches_complete_e() {
for &(k, _, want_e) in KE {
let omk2 = f64x4::splat((1.0 - k) * (1.0 + k)); let rg = carlson_rg::<Precision, f64, _>(f64x4::splat(0.0), omk2, f64x4::splat(1.0));
let got = 2.0 * rg.extract::<0>();
assert!(
close(got, want_e, 1.0e-13),
"2 R_G(0,1-k^2,1) vs E({k}): got {got}, want {want_e}"
);
}
}
#[test]
fn carlson_matches_agm_complete() {
for &(k, _, _) in KE {
let (want_k, want_e) = agm_complete_ke::<f64, _>(f64x4::splat(k));
let omk2 = f64x4::splat((1.0 - k) * (1.0 + k)); let zero = f64x4::splat(0.0);
let one = f64x4::splat(1.0);
let rf = carlson_rf::<Precision, f64, _>(zero, omk2, one);
let rd = carlson_rd::<Precision, f64, _>(zero, omk2, one);
let e = rf - f64x4::splat(k * k / 3.0) * rd;
assert!(
close(rf.extract::<0>(), want_k.extract::<0>(), 1.0e-13),
"R_F vs K({k})"
);
assert!(
close(e.extract::<0>(), want_e.extract::<0>(), 1.0e-13),
"R_F/R_D vs E({k})"
);
}
}
const INC: &[(f64, f64, f64, f64)] = &[
(
0.3430906586047127,
2.712952582080266,
0.4340870330108736,
0.2852345328295404,
),
(
1.302990057703935,
0.1279518954120547,
1.307312511398114,
1.298690225567921,
),
(
0.6523628380743488,
-1.429437513650137,
0.8005154258533936,
0.5508100202571943,
),
(
0.4046022501376546,
-1.981659235625333,
0.4656721451084328,
0.3575401358115371,
),
(
0.630370432896175,
0.8641142168759754,
0.6632598061016007,
0.6003112504412838,
),
];
#[test]
fn ellint_incomplete_matches_reference() {
use thermite::math::policy::policies::Precision;
for &(phi, k, want_f, want_e) in INC {
let p = f64x4::splat(phi);
let kk = f64x4::splat(k);
let zero = f64x4::splat(0.0);
let f = ellint_impl::<Precision, f64, _, KIND_F, false>(p, kk, zero);
let e = ellint_impl::<Precision, f64, _, KIND_E, false>(p, kk, zero);
assert!(
close(f.extract::<0>(), want_f, 1.0e-12),
"F({phi},{k}): got {}",
f.extract::<0>()
);
assert!(
close(e.extract::<0>(), want_e, 1.0e-12),
"E({phi},{k}): got {}",
e.extract::<0>()
);
}
}
#[test]
fn ellint_phi_range_reduction() {
use thermite::math::policy::policies::Precision;
for &(phi, k, want_f, want_e) in INC {
if k.abs() > 1.0 {
continue;
}
let kk = f64x4::splat(k);
let zero = f64x4::splat(0.0);
let comp_k = ellint_impl::<Precision, f64, _, KIND_F, true>(zero, kk, zero).extract::<0>();
let comp_e = ellint_impl::<Precision, f64, _, KIND_E, true>(zero, kk, zero).extract::<0>();
for m in [-2i32, -1, 1, 3] {
let shifted = f64x4::splat(phi + m as f64 * core::f64::consts::PI);
let f = ellint_impl::<Precision, f64, _, KIND_F, false>(shifted, kk, zero).extract::<0>();
let e = ellint_impl::<Precision, f64, _, KIND_E, false>(shifted, kk, zero).extract::<0>();
let ef = want_f + 2.0 * m as f64 * comp_k;
let ee = want_e + 2.0 * m as f64 * comp_e;
assert!(close(f, ef, 1.0e-11), "F({phi}+{m}pi,{k}): got {f}, want {ef}");
assert!(close(e, ee, 1.0e-11), "E({phi}+{m}pi,{k}): got {e}, want {ee}");
}
}
}
const PIC: &[(f64, f64, f64)] = &[
(-10.0, 0.25, 0.4892245275965397),
(-3.0, 0.50, 0.8760028274011437),
(-1.0, 0.75, 1.440034318657551),
(0.0, 0.25, 1.685750354812596), (0.5, 0.50, 2.701287762095351), (0.5, 0.95, 4.633308147279891),
];
#[test]
fn ellint_pi_complete_matches_reference() {
use thermite::math::policy::policies::Precision;
for &(n, m, want) in PIC {
let k = m.sqrt();
let got =
ellint_impl::<Precision, f64, _, KIND_PI, true>(f64x4::splat(0.0), f64x4::splat(k), f64x4::splat(n));
assert!(
close(got.extract::<0>(), want, 1.0e-13),
"Pi_complete({n},k={k}): got {}, want {want}",
got.extract::<0>()
);
}
}
#[test]
fn ellint_pi_phi_range_reduction() {
use thermite::math::policy::policies::Precision;
for &(phi, n, k, want) in PI3 {
if k.abs() > 1.0 {
continue;
}
let kk = f64x4::splat(k);
let nn = f64x4::splat(n);
let zero = f64x4::splat(0.0);
let comp = ellint_impl::<Precision, f64, _, KIND_PI, true>(zero, kk, nn).extract::<0>();
for m in [-1i32, 1, 2] {
let shifted = f64x4::splat(phi + m as f64 * core::f64::consts::PI);
let got = ellint_impl::<Precision, f64, _, KIND_PI, false>(shifted, kk, nn).extract::<0>();
let exp = want + 2.0 * m as f64 * comp;
assert!(
close(got, exp, 1.0e-11),
"Pi({n},{phi}+{m}pi,{k}): got {got}, want {exp}"
);
}
}
}
#[test]
fn carlson_rj_spot_check() {
use thermite::math::policy::policies::Precision;
let rj = carlson_rj::<Precision, f64, _>(
f64x4::splat(4.0),
f64x4::splat(4.0),
f64x4::splat(4.0),
f64x4::splat(4.0),
);
assert!(
close(rj.extract::<0>(), 0.125, 1.0e-13),
"R_J(4,4,4,4) = {}",
rj.extract::<0>()
);
}
fn rj_pv_reference(x: f64, y: f64, z: f64, p: f64) -> f64 {
assert!(p < 0.0);
let t0 = -p;
let g = |t: f64| ((t + x) * (t + y) * (t + z)).powf(-0.5);
let gp = |t: f64| g(t) * (-0.5) * (1.0 / (t + x) + 1.0 / (t + y) + 1.0 / (t + z));
let n = 200_000usize;
let b = 2.0 * t0;
let h = b / n as f64;
let f1 = |t: f64| {
let d = t - t0;
if d.abs() < 1e-12 { gp(t0) } else { (g(t) - g(t0)) / d }
};
let mut s1 = f1(0.0) + f1(b);
for i in 1..n {
let w = if i % 2 == 1 { 4.0 } else { 2.0 };
s1 += w * f1(i as f64 * h);
}
s1 *= h / 3.0;
let hu = 1.0 / n as f64;
let f2 = |u: f64| {
let om = 1.0 - u;
let t = 2.0 * t0 + u / om;
g(t) / (t - t0) / (om * om)
};
let mut s2 = f2(0.0); for i in 1..n {
let w = if i % 2 == 1 { 4.0 } else { 2.0 };
s2 += w * f2(i as f64 * hu);
}
s2 *= hu / 3.0;
1.5 * (s1 + s2)
}
#[test]
fn carlson_rj_negative_p() {
use thermite::math::policy::policies::Precision;
for &(x, y, z, p) in &[(1.0, 2.0, 4.0, -0.5), (0.5, 1.0, 2.0, -0.25), (1.0, 3.0, 5.0, -2.0)] {
let want = rj_pv_reference(x, y, z, p);
let got =
carlson_rj::<Precision, f64, _>(f64x4::splat(x), f64x4::splat(y), f64x4::splat(z), f64x4::splat(p));
assert!(
close(got.extract::<0>(), want, 1.0e-6),
"R_J({x},{y},{z},{p}): got {}, PV oracle {want}",
got.extract::<0>()
);
}
}
const PI3: &[(f64, f64, f64, f64)] = &[
(
1.087095515757691,
0.157358332363011,
0.8160487832898813,
1.31594514075427,
),
(
0.7128175949111615,
1.926593468907062,
0.2994546721661018,
1.25394623148424,
),
(
0.630370432896175,
1.465981775919188,
1.008702896970963,
0.8737159913132074,
),
(
0.9695030752034163,
-0.4072847419780592,
-0.6962608926846425,
0.9442477901112342,
),
];
#[test]
fn ellint_pi_matches_reference() {
use thermite::math::policy::policies::Precision;
for &(phi, n, k, want) in PI3 {
let got =
ellint_impl::<Precision, f64, _, KIND_PI, false>(f64x4::splat(phi), f64x4::splat(k), f64x4::splat(n));
assert!(
close(got.extract::<0>(), want, 1.0e-12),
"Pi({phi},{n},{k}): got {}",
got.extract::<0>()
);
}
}
#[test]
fn ellint_phi_range_reduction_large() {
use thermite::math::policy::policies::Precision;
let (phi, k) = (0.7, 0.5);
let kk = f64x4::splat(k);
let zero = f64x4::splat(0.0);
let comp_k = ellint_impl::<Precision, f64, _, KIND_F, true>(zero, kk, zero).extract::<0>();
let base = ellint_impl::<Precision, f64, _, KIND_F, false>(f64x4::splat(phi), kk, zero).extract::<0>();
for m in [1_000i64, 100_000, 10_000_000, 1_000_000_000] {
let shifted = f64x4::splat(phi + m as f64 * core::f64::consts::PI);
let f = ellint_impl::<Precision, f64, _, KIND_F, false>(shifted, kk, zero).extract::<0>();
let want = base + 2.0 * m as f64 * comp_k;
let err = (f - want).abs() / want.abs();
assert!(err < 1e-12, "F(phi + {m}*pi): got {f}, want {want} (rel {err})");
}
}
}