#![allow(clippy::needless_arbitrary_self_type)]
use thermite::math::policy::{DefaultPolicy, Policy};
use thermite::math::{CoreMathWithPolicy as _, FloatConsts, TranscendentalMathWithPolicy as _};
use thermite::prelude::*;
use thermite_special::specialized::{ExpIntDetails, SpecializedSpecialMath};
use crate::Complex;
use crate::math::ComplexMathWithPolicy as _;
use crate::math::specialized::ComplexVector;
use crate::vector::RealFloatVector;
use thermite_special::tables::Lanczos;
pub mod faddeeva;
const SERIES_TERMS: usize = 160;
const SERIES_RADIUS_SQ: i64 = 64;
const SERIES_ALTERNATION_LIMIT: i64 = 8;
const DIRECT_ERFC_LIMIT: i64 = 6;
#[inline(always)]
fn erf_erfc_positive<P: Policy, E, V>(z: Complex<V>) -> (Complex<V>, Complex<V>)
where
V: RealFloatVector<Element = E>,
Complex<V>: SpecializedComplexSpecialMath<Complex<E>> + GenericVector<Mask = V::Mask>,
{
let one = Complex::<V>::ONE;
let z2 = z.square();
let exp_nz2 = (-z2).exp_p::<P>();
let cancels = z2
.re
.cmp_ge(thermite::const_splat!(int <V::Element>: DIRECT_ERFC_LIMIT));
let alternates = z2
.re
.cmp_le(thermite::const_splat!(int <V::Element>: -SERIES_ALTERNATION_LIMIT));
let beyond_series = z
.norm_sqr()
.cmp_gt(thermite::const_splat!(int <V::Element>: SERIES_RADIUS_SQ));
let use_w = cancels | alternates | beyond_series;
let mut series_erf = Complex::<V>::EMPTY;
let mut w_erfc = Complex::<V>::EMPTY;
if const { P::POLICY.avoid_branching } || !use_w.all() {
let two_z2 = z2 + z2;
let mut term = z;
let mut sum = z;
let eps_sqr: V = <V as FloatVector>::EPSILON * <V as FloatVector>::EPSILON;
let mut n = 1usize;
while n < SERIES_TERMS {
let denom = V::splat(<V::Element as FloatElement>::from_int(2 * n as thermite::LargeInt + 1));
let ratio = two_z2 / denom;
term *= ratio;
sum += term;
let converged = term.norm_sqr().cmp_le(sum.norm_sqr() * eps_sqr);
if (converged | use_w).all() {
break;
}
n += 1;
}
series_erf = sum * exp_nz2 * <V as thermite::math::FloatConsts>::FRAC_2_SQRT_PI;
}
if const { P::POLICY.avoid_branching } || use_w.any() {
w_erfc = exp_nz2 * SpecializedComplexSpecialMath::faddeeva_w::<P>(Complex::new(-z.im, z.re));
}
let erf = use_w.select(one - w_erfc, series_erf);
let erfc = use_w.select(w_erfc, one - series_erf);
(erf, erfc)
}
impl<E, V: RealFloatVector<Element = E>> SpecializedSpecialMath<Complex<E>> for Complex<V>
where
Complex<V>: SpecializedComplexSpecialMath<Complex<E>> + GenericVector<Mask = V::Mask>,
{
type ExpIntDetails = Self;
#[inline(always)]
fn erf<P: Policy>(self) -> Self {
let neg = self.re.is_negative();
let z = Complex::new(self.re.neg_c(neg), self.im.neg_c(neg));
let (erf, _) = erf_erfc_positive::<P, E, V>(z);
Complex::new(erf.re.neg_c(neg), erf.im.neg_c(neg))
}
#[inline(always)]
fn erfc<P: Policy>(self) -> Self {
let neg = self.re.is_negative();
let z = Complex::new(self.re.neg_c(neg), self.im.neg_c(neg));
let (_, erfc) = erf_erfc_positive::<P, E, V>(z);
neg.select(Self::TWO - erfc, erfc)
}
#[inline(always)]
fn logistic_sigmoid<P: Policy>(self) -> Self {
(Self::ONE + (-self).exp_p::<P>()).reciprocal_p::<P>()
}
#[inline(always)]
fn softplus<P: Policy>(self, k: Self, rcp_k: Self) -> Self {
(Self::ONE + (self * k).exp_p::<P>()).ln_p::<P>() * rcp_k
}
#[inline(always)]
fn tgamma<P: Policy>(self) -> Self {
self.complex_tgamma::<P>()
}
#[inline(always)]
fn lgamma<P: Policy>(self) -> Self {
self.complex_lgamma::<P>()
}
#[inline(always)]
fn digamma<P: Policy>(self) -> Self {
self.complex_digamma::<P>()
}
#[inline(always)]
fn trigamma<P: Policy>(self) -> Self {
self.complex_trigamma::<P>()
}
#[inline(always)]
fn beta<P: Policy>(a: Self, b: Self) -> Self {
a.complex_beta::<P>(b)
}
#[inline(always)]
fn lambert_w<P: Policy>(self) -> (Self, Self) {
self.complex_lambert_w::<P>()
}
}
impl<E, V: RealFloatVector<Element = E>> ExpIntDetails<Complex<E>, Complex<V>> for Complex<V>
where
Complex<V>: FloatVector<Element = Complex<E>, Mask = V::Mask>,
{
#[inline(always)]
fn use_series(z: Complex<V>) -> V::Mask {
z.norm_sqr().cmp_lt(V::ONE) | z.re.is_negative()
}
#[inline(always)]
fn invalid(z: Complex<V>) -> V::Mask {
z.is_nan()
}
#[inline(always)]
fn cf_tiny() -> Complex<V> {
Complex::real(V::MIN_POSITIVE.sqrt() / <V as FloatVector>::EPSILON)
}
}
pub trait SpecializedComplexSpecialMath<E>: ComplexVector<Element = E> {
fn complex_tgamma<P: Policy>(self) -> Self;
fn complex_lgamma<P: Policy>(self) -> Self;
fn complex_digamma<P: Policy>(self) -> Self;
fn complex_trigamma<P: Policy>(self) -> Self;
fn complex_lambert_w<P: Policy>(self) -> (Self, Self);
fn faddeeva_w<P: Policy>(self) -> Self;
#[inline(always)]
fn erfcx<P: Policy>(self) -> Self {
Self::from_parts(-self.im(), self.re()).faddeeva_w::<P>()
}
#[inline(always)]
fn voigt<P: Policy>(self) -> Self::Real {
self.faddeeva_w::<P>().re()
}
#[inline(always)]
fn complex_beta<P: Policy>(self, b: Self) -> Self {
self.complex_tgamma::<P>() * b.complex_tgamma::<P>() / (self + b).complex_tgamma::<P>()
}
}
#[inline(always)]
fn complex_consts<V: RealFloatVector, const N: usize>(c: &[V::Element; N]) -> [Complex<V::Element>; N] {
let zero = <V::Element as crate::RealValue>::VAL_ZERO;
let mut out = [Complex::new(c[0], zero); N];
let mut i = 1;
while i < N {
out[i] = Complex::new(c[i], zero);
i += 1;
}
out
}
#[inline(always)]
fn tgamma_impl<P: Policy, V: RealFloatVector, const N: usize>(z: Complex<V>, l: &Lanczos<V::Element, N>) -> Complex<V> {
let reflect = z.re.cmp_lt(V::HALF);
let w = reflect.select(Complex::ONE - z, z);
let gh = V::splat(l.g) - V::HALF;
let zgh = Complex::new(w.re + gh, w.im);
let lanczos = w.poly_rev_p::<P, N>(&complex_consts::<V, N>(&l.p_rev))
/ w.poly_rev_p::<P, N>(&complex_consts::<V, N>(&l.q_rev));
let e = Complex::new(w.re - V::HALF, w.im);
let res = (e * zgh.ln_p::<P>() - zgh).exp_p::<P>() * lanczos;
let refl = Complex::real(<V as FloatConsts>::PI) / (z.sin_pi_p::<P>() * res);
reflect.select(refl, res)
}
#[inline(always)]
fn lgamma_impl<P: Policy, V: RealFloatVector, const N: usize>(z: Complex<V>, l: &Lanczos<V::Element, N>) -> Complex<V> {
let reflect = z.re.cmp_lt(V::HALF);
let w = reflect.select(Complex::ONE - z, z);
let b = Complex::new(w.re - V::HALF, w.im);
let a = Complex::new(b.re + V::splat(l.g), b.im).ln_p::<P>() - Complex::ONE;
let s =
w.poly_p::<P, N>(&complex_consts::<V, N>(&l.p_expg_scaled)) / w.poly_p::<P, N>(&complex_consts::<V, N>(&l.q));
let res = a * b + s.ln_p::<P>();
let refl = Complex::real(<V as FloatConsts>::LN_PI) - z.sin_pi_p::<P>().ln_p::<P>() - res;
reflect.select(refl, res)
}
#[inline(always)]
fn digamma_impl<P: Policy, V: RealFloatVector, const NL: usize>(
z: Complex<V>,
p_large: &[V::Element; NL],
shift: V::Element,
) -> Complex<V> {
let reflect = z.re.cmp_lt(V::HALF);
let mut w = reflect.select(Complex::ONE - z, z);
let mut refl = Complex::<V>::ZERO;
if const { P::POLICY.avoid_branching } || reflect.any() {
let (s, c) = z.sincos_pi_p::<P>();
refl = -(c / s * Complex::real(<V as FloatConsts>::PI));
}
let shift = V::splat(shift);
let mut acc = Complex::<V>::ZERO;
let mut active = w.re.cmp_lt(shift);
while active.any() {
acc = active.select(acc - w.finv_p::<P>(), acc);
w = active.select(w + Complex::ONE, w);
active = w.re.cmp_lt(shift);
}
let xm1 = w - Complex::ONE;
let u = (xm1 * xm1).finv_p::<P>();
let psi = xm1.ln_p::<P>() + (xm1 + xm1).finv_p::<P>() - u * u.poly_p::<P, NL>(&complex_consts::<V, NL>(p_large));
let total = acc + psi;
reflect.select(refl + total, total)
}
#[inline(always)]
fn trigamma_impl<P: Policy, V: RealFloatVector, const NB: usize>(
z: Complex<V>,
bernoulli: &[V::Element; NB],
shift: V::Element,
) -> Complex<V> {
let reflect = z.re.cmp_lt(V::HALF);
let mut w = reflect.select(Complex::ONE - z, z);
let mut refl = Complex::<V>::ZERO;
if const { P::POLICY.avoid_branching } || reflect.any() {
let s = z.sin_pi_p::<P>();
refl = Complex::real(<V as FloatConsts>::PI_SQUARED) / (s * s);
}
let shift = V::splat(shift);
let mut acc = Complex::<V>::ZERO;
let mut active = w.re.cmp_lt(shift);
while active.any() {
let t = (w * w).finv_p::<P>();
acc = active.select(acc + t, acc);
w = active.select(w + Complex::ONE, w);
active = w.re.cmp_lt(shift);
}
let u = w.finv_p::<P>();
let u2 = u * u;
let mut tail = Complex::real(V::splat(bernoulli[NB - 1]));
let mut i = NB - 1;
while i > 0 {
i -= 1;
let c = V::splat(bernoulli[i]);
tail = Complex::new(
tail.im.nmul_adde(u2.im, tail.re.mul_adde(u2.re, c)),
tail.re.mul_adde(u2.im, tail.im * u2.re),
);
}
let psi = acc + u + u2 * V::HALF + (u2 * u) * tail;
reflect.select(refl - psi, psi)
}
#[inline(always)]
fn lambert_w_impl<P: Policy, V: RealFloatVector>(
z: Complex<V>,
c: &[V::Element; 2],
iters: usize,
) -> (Complex<V>, Complex<V>) {
let e = <V as FloatConsts>::E;
let ez1 = z.mul_adde(e, Complex::ONE);
let p = (ez1 + ez1).sqrt();
let p2 = p.square();
let p3 = p2 * p;
let odd = p3.mul_adde(V::splat(c[0]), p);
let even = p2.nmul_adde(<V as FloatConsts>::FRAC_1_3, Complex::NEG_ONE);
let w0_branch = even + odd;
let wm1_branch = even - odd;
let lnz = z.ln_p::<P>();
let l1_0 = lnz;
let l1_m1 = Complex::new(lnz.re, lnz.im - <V as FloatConsts>::TAU);
let l2_0 = l1_0.ln_p::<P>();
let l2_m1 = l1_m1.ln_p::<P>();
let w0_log = l1_0 - l2_0 + l2_0 / l1_0;
let wm1_log = l1_m1 - l2_m1 + l2_m1 / l1_m1;
let ez = z * e;
let w0_mid = ez / (ez + Complex::real(V::TWO));
let d = Complex::new(z.re + <V as FloatConsts>::FRAC_NEG_1_E.abs(), z.im);
let near_branch = d.norm_sqr().cmp_lt(V::splat(c[1]));
let far = z.norm_sqr().cmp_gt(e * e);
let mut w0 = near_branch.select(w0_branch, far.select(w0_log, w0_mid));
let mut wm1 = near_branch.select(wm1_branch, wm1_log);
let mut n = 0;
while n < iters {
n += 1;
w0 = halley::<P, V>(w0, z);
wm1 = halley::<P, V>(wm1, z);
}
if const { P::POLICY.check_overflow } {
let zero = z.re.is_zero() & z.im.is_zero();
let nonzero = !zero;
w0 = nonzero.select(w0, Complex::ZERO);
wm1 = nonzero.select(wm1, Complex::new(V::NEG_INFINITY, V::ZERO));
}
(w0, wm1)
}
#[inline(always)]
fn halley<P: Policy, V: RealFloatVector>(w: Complex<V>, z: Complex<V>) -> Complex<V> {
let enw = (-w).exp_p::<P>();
let wp1 = w + Complex::ONE;
let zenw = z * enw;
let q = wp1.mul_adde(wp1, Complex::ONE);
let g = w - zenw;
let d = (wp1 + Complex::ONE).mul_adde(zenw, q);
(wp1 + wp1).nmul_adde(g / d, w)
}
macro_rules! bernoulli_b2n {
($t:ty) => {
[
1.0 / 6.0,
-1.0 / 30.0,
1.0 / 42.0,
-1.0 / 30.0,
5.0 / 66.0,
-691.0 / 2730.0,
7.0 / 6.0,
]
};
}
impl<V: RealFloatVector<Element = f32>> SpecializedComplexSpecialMath<Complex<f32>> for Complex<V> {
#[inline(always)]
fn complex_tgamma<P: Policy>(self) -> Self {
tgamma_impl::<P, V, _>(self, &thermite_special::tables::LANCZOS_F32)
}
#[inline(always)]
fn complex_lgamma<P: Policy>(self) -> Self {
lgamma_impl::<P, V, _>(self, &thermite_special::tables::LANCZOS_F32)
}
#[inline(always)]
fn complex_digamma<P: Policy>(self) -> Self {
digamma_impl::<P, V, _>(self, &thermite_special::tables::DIGAMMA_F32.p_large, 10.0)
}
#[inline(always)]
fn complex_trigamma<P: Policy>(self) -> Self {
const B: [f32; 4] = [1.0 / 6.0, -1.0 / 30.0, 1.0 / 42.0, -1.0 / 30.0];
trigamma_impl::<P, V, 4>(self, &B, 8.0)
}
#[inline(always)]
fn complex_lambert_w<P: Policy>(self) -> (Self, Self) {
const C: [f32; 2] = [11.0 / 72.0, 0.09];
lambert_w_impl::<P, V>(self, &C, 3)
}
#[inline(always)]
fn faddeeva_w<P: Policy>(self) -> Self {
self::faddeeva::faddeeva_w::<P, f32, V>(self)
}
}
impl<V: RealFloatVector<Element = f64>> SpecializedComplexSpecialMath<Complex<f64>> for Complex<V> {
#[inline(always)]
fn complex_tgamma<P: Policy>(self) -> Self {
tgamma_impl::<P, V, _>(self, &thermite_special::tables::LANCZOS_F64)
}
#[inline(always)]
fn complex_lgamma<P: Policy>(self) -> Self {
lgamma_impl::<P, V, _>(self, &thermite_special::tables::LANCZOS_F64)
}
#[inline(always)]
fn complex_digamma<P: Policy>(self) -> Self {
digamma_impl::<P, V, _>(self, &thermite_special::tables::DIGAMMA_F64.p_large, 10.0)
}
#[inline(always)]
fn complex_trigamma<P: Policy>(self) -> Self {
const B: [f64; 7] = bernoulli_b2n!(f64);
trigamma_impl::<P, V, 7>(self, &B, 16.0)
}
#[inline(always)]
fn complex_lambert_w<P: Policy>(self) -> (Self, Self) {
const C: [f64; 2] = [11.0 / 72.0, 0.09];
lambert_w_impl::<P, V>(self, &C, 4)
}
#[inline(always)]
fn faddeeva_w<P: Policy>(self) -> Self {
self::faddeeva::faddeeva_w::<P, f64, V>(self)
}
}
#[cfg(feature = "dual")]
#[inline(always)]
fn dual_consts<E, const N: usize, const M: usize>(src: &[f64; N]) -> [thermite_dual::Dual<E, M>; N]
where
E: thermite::element::FloatElementWithBits + thermite_dual::DualValue,
{
let mut out = [thermite_dual::Dual::<E, M>::constant(E::from_f64(src[0])); N];
let mut i = 1;
while i < N {
out[i] = thermite_dual::Dual::constant(E::from_f64(src[i]));
i += 1;
}
out
}
#[cfg(feature = "dual")]
#[inline(always)]
fn dual_lanczos<E, const M: usize>() -> Lanczos<thermite_dual::Dual<E, M>, 13>
where
E: thermite::element::FloatElementWithBits + thermite_dual::DualValue,
{
let l = &thermite_special::tables::LANCZOS_F64;
Lanczos {
g: thermite_dual::Dual::constant(E::from_f64(l.g)),
p_rev: dual_consts(&l.p_rev),
q_rev: dual_consts(&l.q_rev),
p_expg_scaled: dual_consts(&l.p_expg_scaled),
q: dual_consts(&l.q),
}
}
#[cfg(feature = "dual")]
impl<E, V: FloatVector<Element = E>, const N: usize> SpecializedComplexSpecialMath<Complex<thermite_dual::Dual<E, N>>>
for Complex<thermite_dual::Dual<V, N>>
where
E: thermite::element::FloatElementWithBits + thermite_dual::DualValue,
thermite_dual::Dual<V, N>: RealFloatVector<Element = thermite_dual::Dual<E, N>>,
{
#[inline(always)]
fn complex_tgamma<P: Policy>(self) -> Self {
tgamma_impl::<P, thermite_dual::Dual<V, N>, 13>(self, &dual_lanczos::<E, N>())
}
#[inline(always)]
fn complex_lgamma<P: Policy>(self) -> Self {
lgamma_impl::<P, thermite_dual::Dual<V, N>, 13>(self, &dual_lanczos::<E, N>())
}
#[inline(always)]
fn complex_digamma<P: Policy>(self) -> Self {
digamma_impl::<P, thermite_dual::Dual<V, N>, 8>(
self,
&dual_consts(&thermite_special::tables::DIGAMMA_F64.p_large),
thermite_dual::Dual::constant(E::from_f64(10.0)),
)
}
#[inline(always)]
fn complex_trigamma<P: Policy>(self) -> Self {
let b = [
thermite_dual::Dual::<E, N>::constant(E::from_f64(1.0 / 6.0)),
thermite_dual::Dual::<E, N>::constant(E::from_f64(-1.0 / 30.0)),
thermite_dual::Dual::<E, N>::constant(E::from_f64(1.0 / 42.0)),
thermite_dual::Dual::<E, N>::constant(E::from_f64(-1.0 / 30.0)),
thermite_dual::Dual::<E, N>::constant(E::from_f64(5.0 / 66.0)),
thermite_dual::Dual::<E, N>::constant(E::from_f64(-691.0 / 2730.0)),
thermite_dual::Dual::<E, N>::constant(E::from_f64(7.0 / 6.0)),
];
trigamma_impl::<P, thermite_dual::Dual<V, N>, 7>(
self,
&b,
thermite_dual::Dual::<E, N>::constant(E::from_f64(16.0)),
)
}
#[inline(always)]
fn complex_lambert_w<P: Policy>(self) -> (Self, Self) {
let c = [
thermite_dual::Dual::<E, N>::constant(E::from_f64(11.0 / 72.0)),
thermite_dual::Dual::<E, N>::constant(E::from_f64(0.09)),
];
lambert_w_impl::<P, thermite_dual::Dual<V, N>>(self, &c, 4)
}
#[inline(always)]
fn faddeeva_w<P: Policy>(self) -> Self {
use self::faddeeva::{Weideman, WeidemanTables, faddeeva_w_with, weideman_n};
macro_rules! tier {
($n:literal) => {
faddeeva_w_with::<P, thermite_dual::Dual<E, N>, thermite_dual::Dual<V, N>, $n>(
self,
thermite_dual::Dual::constant(E::from_f64(<f64 as Weideman<$n>>::L)),
&dual_consts::<E, $n, N>(&<f64 as Weideman<$n>>::A),
thermite_dual::Dual::constant(E::from_f64(<f64 as WeidemanTables>::HUGE)),
thermite_dual::Dual::constant(E::from_f64(<f64 as WeidemanTables>::REAL_AXIS_Y)),
thermite_dual::Dual::constant(E::from_f64(<f64 as WeidemanTables>::REAL_AXIS_X)),
)
};
}
macro_rules! is {
($n:literal) => {
const { weideman_n(P::POLICY.precision, <f64 as WeidemanTables>::MAX_N) <= $n }
};
}
if is!(8) {
tier!(8)
} else if is!(16) {
tier!(16)
} else if is!(24) {
tier!(24)
} else if is!(32) {
tier!(32)
} else {
tier!(40)
}
}
}
decl_complex_math! {
trait ComplexSpecial<FloatElement>: ComplexVector {
fn faddeeva_w[][](self: Self) -> Self;
fn erfcx[][](self: Self) -> Self;
fn voigt[][](self: Self) -> Self::Real;
}
}