Skip to main content

thermite_special/specialized/
mod.rs

1#![allow(clippy::excessive_precision)]
2
3use thermite::{
4    mask::GenericMask,
5    math::{
6        CoreMathWithPolicy as _, FloatConsts, TranscendentalMathWithPolicy as _,
7        policy::{
8            Policy, PrecisionPolicy,
9            policies::{CheckOverflow, ExtraPrecision, LessPrecision},
10        },
11        specialized::FlushDenormals,
12    },
13    register::{Element, FloatElement},
14    vector::{NumericVector, PartialOrdVector, SplatConst},
15};
16
17use super::SpecialMathWithPolicy as _;
18
19pub(crate) mod generic;
20mod pd;
21mod ps;
22
23/// The decisions the [`expint`](SpecializedSpecialMath::expint) kernel has to make
24/// differently depending on the arithmetic it is running in.
25///
26/// These are choices *inside* one algorithm, not part of the math surface, so they live
27/// here rather than on [`SpecializedSpecialMath`] itself. They exist because a single
28/// series/continued-fraction body serves both the real line and the complex cut plane,
29/// and "the unit disc", "out of domain" and "negligible but nonzero" are three different
30/// comparisons in those two worlds.
31///
32/// Every method defaults to the real-line answer, so a real vector's implementation is
33/// empty and its [`ExpIntDetails`](SpecializedSpecialMath::ExpIntDetails) is `Self`.
34pub trait ExpIntDetails<E, V: thermite::vector::FloatVector<Element = E>> {
35    /// Lanes that should take the power series rather than the continued fraction.
36    ///
37    /// On the real line this is `x < 1`. Over C it is `|z| < 1`, which is *not* what a
38    /// complex `cmp_lt` means - that is a lexicographic sort order, and reading it as a
39    /// magnitude silently routes far-off-axis points into the wrong regime.
40    #[inline(always)]
41    fn use_series(z: V) -> V::Mask {
42        z.cmp_lt(V::ONE)
43    }
44
45    /// Lanes outside the domain, forced to NaN when the policy checks overflow.
46    ///
47    /// Real `E_N` is defined for `x >= 0` only. The complex principal branch covers the
48    /// whole cut plane `|Arg z| < pi`, so there the negative reals are in-domain and the
49    /// cut is carried entirely by the principal `ln` inside the series.
50    #[inline(always)]
51    fn invalid(z: V) -> V::Mask {
52        z.cmp_lt(V::ZERO) | z.is_nan()
53    }
54
55    /// Lentz sentinel: the stand-in for a denominator that came out exactly zero, small
56    /// enough to be negligible against any real term.
57    ///
58    /// The safe magnitude depends on the arithmetic, not just the format. Real division
59    /// only needs this to be tiny and nonzero, so `MIN_POSITIVE` is ideal. A complex
60    /// reciprocal divides by `|z|^2`, so both the sentinel and its reciprocal have to
61    /// survive being *squared* - `MIN_POSITIVE` underflows to zero there, which takes
62    /// the whole fraction to NaN.
63    #[inline(always)]
64    fn cf_tiny() -> V {
65        V::MIN_POSITIVE
66    }
67}
68
69pub trait SpecializedSpecialMath<E>: thermite::math::specialized::SpecializedTranscendentalMath<E> {
70    /// Per-arithmetic details of the [`expint`](Self::expint) kernel. Almost always
71    /// `Self`, with an empty [`ExpIntDetails`] impl taking every default.
72    type ExpIntDetails: ExpIntDetails<E, Self>;
73
74    fn erf<P: Policy>(self) -> Self;
75
76    #[inline(always)]
77    fn erfc<P: Policy>(self) -> Self {
78        Self::ONE - self.erf_p::<P>()
79    }
80
81    /// Computes the exponential integral `E_N(x)` for integer order `N`.
82    #[inline(always)]
83    fn expint<P: Policy, const N: usize>(self) -> Self {
84        self.expint_primal::<P, N>().0
85    }
86
87    /// Computes `$E_N(x)$` together with the adjacent lower order `$E_{N-1}(x)$`.
88    ///
89    /// Differentiating the integral definition under the integral sign gives
90    /// `$E_N'(x) = -E_{N-1}(x)$`, so the second element is the derivative up to sign.
91    /// The order recurrence already walks `E_1 -> E_N`, which makes `E_{N-1}` simply
92    /// the previous iterate: the pair costs no more than the value alone. `thermite-dual`
93    /// uses this to take the (guarded) real path for both parts rather than running
94    /// this entire routine in dual arithmetic.
95    ///
96    /// Uses the power series for x < 1 and the Stieltjes continued fraction for x >= 1,
97    /// computed in parallel across SIMD lanes and blended at the end.
98    /// For N > 1, applies the recurrence `$E_{n+1}(x) = (e^{-x} - x \cdot E_n(x)) / n$`.
99    #[inline(always)]
100    fn expint_primal<P: Policy, const N: usize>(self) -> (Self, Self) {
101        let x = self;
102
103        // The series/continued-fraction path below produces E_1, so the two orders
104        // beneath it come from their closed forms instead:
105        //   E_0(x)    = e^-x / x
106        //   E_{-1}(x) = e^-x (1 + 1/x) / x
107        let exp_neg_x = (-x).exp_p::<P>();
108        let inv_x = x.reciprocal_p::<P>();
109        let e0 = exp_neg_x * inv_x;
110
111        if const { N == 0 } {
112            let mut value = e0;
113            let mut prev = e0 * (Self::ONE + inv_x);
114
115            if const { P::POLICY.check_overflow } {
116                // Both orders have a pole at the branch point x = 0.
117                let x_is_zero = x.is_zero();
118                value = x_is_zero.select(Self::INFINITY, value);
119                prev = x_is_zero.select(Self::INFINITY, prev);
120
121                let bad = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::invalid(x);
122                value = bad.select(Self::NAN, value);
123                prev = bad.select(Self::NAN, prev);
124            }
125
126            return (value, prev);
127        }
128
129        // E_n(x) is only defined for x > 0 (and x >= 0 for n > 1).
130        // Compute E_1(x) first, then apply recurrence for higher orders.
131
132        // === Interleaved power series (x < 1) and continued fraction (x >= 1) ===
133        //
134        // Power series: E_1(x) = -γ - ln(x) - Σ_{k=1}^∞ (-x)^k / (k*k!)
135        //   Recurrence on terms: A_{k+1} = A_k * (-x * k) / (k+1)^2
136        //   Starting with A_1 = -x, sum = A_1.
137        //
138        // Continued fraction (Stieltjes): E_1(x)*e^x = 1/(x+1 - 1^2/(x+3 - 2^2/(x+5 - 3^2/(x+7 - ...))))
139        //   In standard Lentz form: b_0=0, a_1=1, b_1=x+1; then a_j=-(j-1)^2, b_j=x+2j-1 for j≥2.
140        //   Bootstrap j=1 outside the loop, iterate j≥2 inside.
141        //   Result: E_1(x) = f * e^{-x}
142
143        let use_series = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::use_series(x);
144
145        // --- Power series state ---
146        let neg_x = -x;
147        let mut s_term = neg_x; // A_1 = -x
148        let mut s_sum = s_term; // running sum starts at A_1
149
150        // --- Continued fraction state (modified Lentz's method) ---
151        //
152        // E_1(x)*e^x = 1/(x+1 - 1^2/(x+3 - 2^2/(x+5 - 3^2/(x+7 - ...))))
153        //
154        // In standard Lentz form b_0 + a_1/(b_1 + a_2/(b_2 + ...)):
155        //   b_0 = 0
156        //   j=1: a_1 = 1,       b_1 = x+1
157        //   j≥2: a_j = -(j-1)^2, b_j = x + 2j - 1
158        //
159        let tiny = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::cf_tiny();
160
161        // b_0 = 0, so f_0 = tiny, C_0 = tiny, D_0 = 0
162        let mut cf_f = tiny;
163        let mut cf_c = tiny;
164
165        // Bootstrap j=1 step: a_1 = 1, b_1 = x+1. D_0 is only ever read here, so
166        // it stays a comment rather than an initializer the next line overwrites.
167        let mut cf_d = {
168            let b1 = x + Self::ONE;
169            // D_1 = 1/(b_1 + a_1*D_0) = 1/(x+1), since D_0 = 0
170            let d1 = b1.reciprocal_p::<P>();
171            // C_1 = b_1 + a_1/C_0 = (x+1) + 1/tiny ≈ 1/tiny
172            cf_c = b1 + cf_c.reciprocal_p::<P>();
173            let delta = cf_c * d1;
174            cf_f *= delta; // tiny * (1/tiny)/(x+1) ≈ 1/(x+1)
175            d1
176        };
177
178        // Convergence tolerance
179        let eps = Self::splat(E::EPSILON);
180
181        let mut series_done = !use_series; // lanes not using series are "done" immediately
182        let mut cf_done = use_series; // lanes not using CF are "done" immediately
183
184        let mut k = 1usize;
185        while k < const { P::POLICY.max_iterations } {
186            let kf = Self::splat(E::from_int(k as thermite::LargeInt));
187            let kp1 = Self::splat(E::from_int(k as thermite::LargeInt + 1));
188
189            // --- Power series step ---
190            // A_{k+1} = A_k * (-x * k) / (k+1)^2
191            if !series_done.all() {
192                s_term *= (neg_x * kf) / (kp1 * kp1);
193                s_sum = series_done.select(s_sum, s_sum + s_term);
194
195                let term_small = s_term.abs().cmp_lt(s_sum.abs() * eps);
196
197                // series_done | (use_series & term_small)
198                series_done = GenericMask::ternlog::<{ thermite::ternlog_imm!(A | (B & C)) }>(
199                    series_done,
200                    use_series,
201                    term_small,
202                );
203            }
204
205            // --- Continued fraction step (j = k+1, so j ≥ 2) ---
206            // a_j = -(j-1)^2 = -k^2, b_j = x + 2j - 1 = x + 2k + 1
207            if !cf_done.all() {
208                let neg_a_k = kf * kf; // |a_j| = k^2
209                let b_k = (x + kf) + (kf + Self::ONE); // x + 2k + 1
210
211                // D = 1 / (b - |a|*D_prev)  [note: subtraction because a is negative]
212                let d_denom = neg_a_k.nmul_adde(cf_d, b_k); // b - |a|*D
213                let new_d = d_denom.cmp_eq(Self::ZERO).select(tiny, d_denom).reciprocal_p::<P>();
214
215                // C = b - |a|/C_prev  [same sign flip]
216                let new_c = b_k - neg_a_k / cf_c;
217                let new_c = new_c.cmp_eq(Self::ZERO).select(tiny, new_c);
218
219                let delta = new_c * new_d;
220
221                cf_d = new_d;
222                cf_c = new_c;
223                cf_f = cf_done.select(cf_f, cf_f * delta);
224
225                let cf_converged = (delta - Self::ONE).abs().cmp_lt(eps);
226
227                cf_done =
228                    GenericMask::ternlog::<{ thermite::ternlog_imm!(A | (!B & C)) }>(cf_done, use_series, cf_converged);
229            }
230
231            if (series_done & cf_done).all() {
232                break;
233            }
234
235            k += 1;
236        }
237
238        // --- Assemble E_1(x) from both methods ---
239
240        // Series: E_1(x) = -γ - ln(x) - sum
241        let mut series_result = Self::EMPTY;
242
243        // CF: E_1(x) = cf_f * e^{-x}  (cf_f approximates E_1(x)*e^x)
244        let mut cf_result = Self::EMPTY;
245
246        if use_series.any() {
247            series_result = (-Self::EULER_GAMMA - s_sum) - x.ln_p::<P>();
248        }
249
250        if !use_series.all() {
251            cf_result = cf_f * exp_neg_x;
252        }
253
254        let mut e_n = use_series.select(series_result, cf_result);
255
256        // Order beneath the current one. Before the recurrence runs, E_N is E_1, so the
257        // order below it is E_0.
258        let mut e_prev = e0;
259
260        // --- Apply recurrence for N > 1 ---
261        // E_{n+1}(x) = (e^{-x} - x * E_n(x)) / n
262        if const { N > 1 } {
263            let mut n = 1u32;
264            while n < N as u32 {
265                let nf = Self::splat(E::from_int(n as thermite::LargeInt));
266                e_prev = e_n;
267                e_n = x.nmul_adde(e_n, exp_neg_x) / nf;
268                n += 1;
269            }
270        }
271
272        // --- Edge cases ---
273        if const { P::POLICY.check_overflow } {
274            // E_1(0) = +inf, E_n(0) = 1/(n-1) for n > 1
275            let x_is_zero = x.is_zero();
276            if const { N == 1 } {
277                e_n = x_is_zero.select(Self::INFINITY, e_n);
278            } else if const { N > 1 } {
279                e_n = x_is_zero.select(Self::splat(E::ONE / E::from_int(N as thermite::LargeInt - 1)), e_n);
280            }
281
282            // Same rule one order down: E_0 and E_1 both diverge at zero, E_n does not.
283            if const { N <= 2 } {
284                e_prev = x_is_zero.select(Self::INFINITY, e_prev);
285            } else {
286                e_prev = x_is_zero.select(Self::splat(E::ONE / E::from_int(N as thermite::LargeInt - 2)), e_prev);
287            }
288
289            // Negative x: NaN, and NaN in, NaN out.
290            let bad = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::invalid(x);
291            e_n = bad.select(Self::NAN, e_n);
292            e_prev = bad.select(Self::NAN, e_prev);
293        }
294
295        (e_n, e_prev)
296    }
297
298    #[inline(always)]
299    fn logistic_sigmoid<P: Policy>(self) -> Self {
300        if const { P::POLICY.precision.gt(PrecisionPolicy::Average) } {
301            let is_pos = self.is_positive();
302            let x = self.neg_c(is_pos); // conditionally negate if positive
303            let e = x.exp_p::<P>();
304
305            let n = is_pos.select(Self::ONE, e);
306            let d = Self::ONE + e;
307
308            return n / d;
309        }
310
311        (Self::ONE + (-self).exp_p::<P>()).reciprocal_p::<ExtraPrecision<P>>()
312    }
313
314    #[inline(always)]
315    fn softplus<P: Policy>(self, k: Self, rcp_k: Self) -> Self {
316        // For low precision, we can get better performance by computing in base-2 instead of base-e,
317        // at the cost of some accuracy.
318        if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
319            // adjust to be in base-2
320            let k = k.scale(FloatConsts::LOG2_E);
321            let rcp_k = rcp_k.scale(FloatConsts::LN_2);
322
323            let kx = self * k;
324
325            // e needs overflow checks to outright incorrect results here
326            let e = kx.abs().neg().exp2_p::<CheckOverflow<P, true>>();
327            return (Self::ONE + e).log2_p::<P>().mul_adde(rcp_k, self.max(Self::ZERO));
328        }
329
330        let kx = self * k;
331
332        let e = kx.abs().neg().exp_p::<P>();
333
334        // max(0, x) + lnp1(e^(-|x|)) is more stable than ln(1 + e^x) for large |x|.
335        e.ln_1p_p::<P>().mul_adde(rcp_k, self.max(Self::ZERO))
336    }
337
338    fn tgamma<P: Policy>(self) -> Self;
339    fn lgamma<P: Policy>(self) -> Self;
340    fn digamma<P: Policy>(self) -> Self;
341
342    /// The trigamma function `psi_1(x) = d/dx psi(x)`, the second derivative of `ln Gamma`.
343    ///
344    /// Deliberately absent from the public `SpecialMath` trait, unlike every sibling
345    /// here. It exists only so that `digamma` is differentiable - forward-mode AD over
346    /// the Gamma family needs `psi_1` the way `ln Gamma` needs `psi` - and keeping it
347    /// off the public trait is what stops that need from cascading: a public
348    /// `trigamma` would oblige `Dual` to implement it, which requires `psi_2`, which
349    /// requires `psi_3`, and so on, because the Gamma-derivative family is not closed
350    /// under differentiation. Closing it for real means a general `polygamma(n)`,
351    /// whose derivative is simply `polygamma(n + 1)`.
352    ///
353    /// Not defined at zero or the negative integers.
354    fn trigamma<P: Policy>(self) -> Self;
355
356    #[inline(always)]
357    fn hermite<P: Policy, const N: usize>(mut x: Self) -> Self {
358        #[cfg(not(target_arch = "spirv"))]
359        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
360            x = new_x[0];
361        }
362
363        let mut p0 = Self::ONE;
364
365        if const { N == 0 } {
366            return p0;
367        }
368
369        let mut p1 = x + x; // 2 * x
370
371        cfg_if::cfg_if! {
372            if #[cfg(all(feature = "spirv", target_arch = "spirv"))] {
373                use crunchy::unroll;
374
375                macro_rules! unroll_poly {
376                    ($($len:tt),*) => {
377                        $( if const { N == $len } {
378                            unroll! { for n in 0..$len {
379                                (p0, p1) = (p1, p0); // swap p0, p1
380
381                                const cf: thermite::LargeInt = (1 + n) as thermite::LargeInt;
382                                let next0 = x.mul_sube(p0, p1.scale(E::ConstInt::<{cf}>::VALUE));
383                                p1 = next0 + next0; // 2 * next0
384                            }}
385                        } else )* {
386                            let mut c = 1;
387                            let mut cf = E::ONE;
388
389                            while c < N {
390                                (p0, p1) = (p1, p0); // swap p0, p1
391
392                                let next0 = x.mul_sube(p0, p1.scale(cf));
393                                p1 = next0 + next0; // 2 * next0
394
395                                c += 1;
396                                cf = cf + E::ONE;
397                            }
398                        }
399                    };
400                }
401
402                unroll_poly!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); // up to N=16
403            } else {
404                let mut c = 1;
405                let mut cf = Self::ONE;
406
407                while c < N {
408                    (p0, p1) = (p1, p0); // swap p0, p1
409
410                    let next0 = x.mul_sube(p0, cf * p1);
411                    p1 = next0 + next0; // 2 * next0
412
413                    c += 1;
414                    cf += Self::ONE;
415                }
416            }
417        }
418
419        p1
420    }
421
422    #[inline(always)]
423    fn hermitev<P: Policy>(mut x: Self, n: Self::Unsigned) -> Self {
424        #[cfg(not(target_arch = "spirv"))]
425        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
426            x = new_x[0];
427        }
428
429        let i1 = Self::Unsigned::ONE;
430        let n_is_zero = n.cmp_eq(Self::Unsigned::ZERO);
431
432        let mut c = i1;
433
434        // count `n = c.to_float()` separately to avoid expensive converting every iteration
435        let mut cf = Self::ONE;
436
437        let mut p0 = Self::ONE;
438        let mut p1 = x + x; // 2 * x
439
440        loop {
441            let cont = c.cmp_lt(n);
442
443            if cont.none() {
444                break;
445            }
446
447            (p0, p1) = (p1, p0); // swap p0, p1
448
449            let next0 = x.mul_sube(p0, cf * p1);
450            let next = next0 + next0; // 2 * next0
451
452            p1 = cont.select(next, p1);
453
454            c += i1;
455            cf += Self::ONE;
456        }
457
458        n_is_zero.select(Self::ONE, p1)
459    }
460
461    #[inline(always)]
462    fn chebyshev<P: Policy, const K: usize, const N: usize>(self, coeffs: &[Self::Element; N]) -> Self {
463        const {
464            assert!(K >= 1 && K <= 4, "chebyshev: K must be 1, 2, 3, or 4");
465            assert!(N >= 1, "chebyshev: N must be at least 1");
466        }
467
468        // S = Σ c_k P_0 = c_0 when N = 1; skip the whole recurrence.
469        if const { N == 1 } {
470            return Self::splat(coeffs[0]);
471        }
472
473        let x = self;
474        let x2 = x + x;
475
476        // P_1: T_1 = x, U_1 = 2x, V_1 = 2x - 1, W_1 = 2x + 1.
477        let p1 = if const { K == 1 } {
478            x
479        } else if const { K == 2 } {
480            x2
481        } else if const { K == 3 } {
482            x2 - Self::ONE
483        } else if const { K == 4 } {
484            x2 + Self::ONE
485        } else {
486            unsafe { core::hint::unreachable_unchecked() }
487        };
488
489        let cn1 = Self::splat(coeffs[N - 1]);
490        let cn2 = Self::splat(coeffs[N - 2]);
491
492        // S = c_0 + c_1*P_1(x) when N = 2.
493        if const { N == 2 } {
494            return p1.mul_adde(cn1, cn2);
495        }
496
497        // Clenshaw's backward recurrence. All four kinds share the recurrence
498        // P_{k+1} = 2x*P_k - P_{k-1} with P_0 = 1, so the b_k loop is identical for all of them
499        // and only the final-step P_1(x) differs:
500        //
501        //     b_{N+1} = b_N = 0
502        //     for k = N-1 down to 1:  b_k = 2x*b_{k+1} - b_{k+2} + c_k
503        //     S = (c_0 - b_2) + b_1 * P_1(x)
504        //
505        // This is more numerically stable than the forward sum (especially when the
506        // partial sums of Σ c_k P_k are much smaller than max|c_k P_k|) and uses only two
507        // running scalars instead of three.
508        //
509        // Hoist the first two iterations to eliminate the b_2 = 0 subtraction in the loop:
510        //     k = N-1:  b_{N-1} = 2x*0 + c_{N-1} - 0          = c_{N-1}
511        //     k = N-2:  b_{N-2} = 2x*c_{N-1} + c_{N-2} - 0    = 2x*c_{N-1} + c_{N-2}
512        let mut b1 = x2.mul_adde(cn1, cn2); // b_{k+1} = b_{N-2}
513        let mut b2 = cn1; // b_{k+2} = b_{N-1}
514
515        // Iterate k = N-3, N-4, ..., 1.
516        let mut k = N - 2;
517        while k > 1 {
518            k -= 1;
519            // b_k = (2x*b_{k+1} + c_k) - b_{k+2}
520            let bk = x2.mul_adde(b1, Self::splat(coeffs[k]) - b2);
521            b2 = b1;
522            b1 = bk;
523        }
524
525        // S = b_1 * P_1(x) + (c_0 - b_2)
526        b1.mul_adde(p1, Self::splat(coeffs[0]) - b2)
527    }
528
529    #[inline(always)]
530    fn jacobi<P: Policy>(mut x: Self, mut alpha: Self, mut beta: Self, mut n: u32, m: u32) -> Self {
531        if thermite::unlikely(m > n) {
532            return Self::ZERO;
533        }
534
535        #[cfg(not(target_arch = "spirv"))]
536        if let Some(new) = FlushDenormals::<P>::flush_denormals([x, alpha, beta]) {
537            x = new[0];
538            alpha = new[1];
539            beta = new[2];
540        }
541
542        let mut scale = Self::ONE;
543
544        if m > 0 {
545            let mut jf = Self::ONE;
546            let nf = Self::splat(E::from_int(n as thermite::LargeInt));
547
548            let t0 = Self::HALF * (nf + alpha + beta);
549
550            let mut _iter = 0;
551            while _iter < m {
552                _iter += 1;
553                scale *= Self::HALF.mul_adde(jf, t0);
554                jf += Self::ONE;
555            }
556
557            let mf = Self::splat(E::from_int(m as thermite::LargeInt));
558
559            alpha += mf;
560            beta += mf;
561            n -= m;
562        }
563
564        if thermite::unlikely(n == 0) {
565            return scale; // scale * one
566        }
567
568        let mut y0 = Self::ONE;
569
570        let alpha_p_beta = alpha + beta;
571        let alpha_sqr = alpha * alpha;
572        let beta_sqr = beta * beta;
573        let alpha1 = alpha - Self::ONE;
574        let beta1 = beta - Self::ONE;
575        let alpha2beta2 = alpha_sqr - beta_sqr;
576
577        //let mut y1 = alpha + 1 + 0.5 * (alpha_p_beta + 2) * (x - 1);
578        let mut y1 = Self::HALF * (x.mul_adde(alpha, alpha) + x.mul_sube(beta, beta) + x + x);
579
580        let mut yk = y1;
581        let mut k = E::ConstInt::<2>::VALUE;
582
583        let k_max = E::from_int(n as thermite::LargeInt) * (<E as Element>::ONE + E::EPSILON);
584
585        while k < k_max {
586            let kf = Self::splat(k);
587            let kf2 = Self::TWO * kf;
588
589            let k_alpha_p_beta = kf + alpha_p_beta;
590            let k2_alpha_p_beta = kf2 + alpha_p_beta;
591
592            let k2_alpha_p_beta_m2 = k2_alpha_p_beta - Self::TWO;
593
594            let denom = kf2 * k_alpha_p_beta * k2_alpha_p_beta_m2;
595            let t0 = x.mul_adde(k2_alpha_p_beta * k2_alpha_p_beta_m2, alpha2beta2);
596            let gamma1 = k2_alpha_p_beta.mul_sube(t0, t0);
597            let gamma0 = Self::TWO * (kf + alpha1) * (kf + beta1) * k2_alpha_p_beta;
598
599            yk = gamma1.mul_sube(y1, gamma0 * y0) / denom;
600
601            y0 = y1;
602            y1 = yk;
603
604            k = k + <E as Element>::ONE;
605        }
606
607        scale * yk
608    }
609
610    #[inline(always)]
611    fn gaussian<P: Policy>(mut x: Self, a: Self, c: Self) -> Self {
612        #[cfg(not(target_arch = "spirv"))]
613        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
614            x = new_x[0];
615        }
616
617        let xc = if const { P::POLICY.precision.le(PrecisionPolicy::Worst) } {
618            x * c.reciprocal_p::<P>()
619        } else {
620            x / c
621        };
622
623        a * (-Self::HALF * xc * xc).exp_p::<P>()
624    }
625
626    fn beta<P: Policy>(a: Self, b: Self) -> Self;
627
628    #[rustfmt::skip]
629    #[inline(always)]
630    fn legendre0<P: Policy, const N: u32>(x: Self, n: u32) -> Self {
631        macro_rules! c { ($n:literal / $d:literal) => { Self::splat(E::from_int($n) / E::from_int($d)) }; }
632
633        let x2 = x.square();
634        let x4 = x2.square();
635        let x8 = x4.square();
636
637        if const { N != 0 } {
638            unsafe { core::hint::assert_unchecked(N == n); }
639        }
640
641        // hand-tuned Estrin's scheme polynomials
642        match n {
643            1 => x,
644            2 => x2.mul_adde(c!(3 / 2), c!(-1 / 2)),
645            3 => x * x2.mul_adde(c!(5 / 2), c!(-3 / 2)),
646            4 => x4.mul_adde(c!(35 / 8), x2.mul_adde(c!(-15 / 4), c!(3 / 8))),
647            5 => x * x4.mul_adde(c!(63 / 8), x2.mul_adde(c!(-35 / 4), c!(15 / 8))),
648            6 => x4.mul_adde(
649                x2.mul_adde(c!(231 / 16), c!(-315 / 16)),
650                x2.mul_adde(c!(105 / 16), c!(-5 / 16)),
651            ),
652            7 => x * x4.mul_adde(
653                x2.mul_adde(c!(429 / 16), c!(-693 / 16)),
654                x2.mul_adde(c!(315 / 16), c!(-35 / 16)),
655            ),
656            8 => x8.mul_adde(c!(6435 / 128), x4.mul_adde(
657                x2.mul_adde(c!(-3003 / 32), c!(3465 / 64)),
658                x2.mul_adde(c!(-315 / 32), c!(35 / 128)),
659            )),
660            9 => x * x8.mul_adde(c!(12155 / 128), x4.mul_adde(
661                x2.mul_adde(c!(-6435 / 32), c!(9009 / 64)),
662                x2.mul_adde(c!(-1155 / 32), c!(315 / 128)),
663            )),
664            10 => x8.mul_adde(
665                x2.mul_adde(c!(46189 / 256), c!(-109395 / 256)),
666                x4.mul_adde(
667                    x2.mul_adde(c!(45045 / 128), c!(-15015 / 128)),
668                    x2.mul_adde(c!(3465 / 256), c!(-63 / 256)),
669                ),
670            ),
671            11 => x * x8.mul_adde(
672                x2.mul_adde(c!(88179 / 256), c!(-230945 / 256)),
673                x4.mul_adde(
674                    x2.mul_adde(c!(109395 / 128), c!(-45045 / 128)),
675                    x2.mul_adde(c!(15015 / 256), c!(-693 / 256)),
676                ),
677            ),
678            12 => x8.mul_adde(
679                x4.mul_adde(c!(676039 / 1024), x2.mul_adde(c!(-969969 / 512), c!(2078505 / 1024))),
680                x4.mul_adde(
681                    x2.mul_adde(c!(-255255 / 256), c!(225225 / 1024)),
682                    x2.mul_adde(c!(-9009 / 512), c!(231 / 1024)),
683                ),
684            ),
685            13 => x * x8.mul_adde(
686                x4.mul_adde(c!(1300075 / 1024), x2.mul_adde(c!(-2028117 / 512), c!(4849845 / 1024))),
687                x4.mul_adde(
688                    x2.mul_adde(c!(-692835 / 256), c!(765765 / 1024)),
689                    x2.mul_adde(c!(-45045 / 512), c!(3003 / 1024)),
690                ),
691            ),
692            _ => unsafe { core::hint::unreachable_unchecked() },
693        }
694    }
695
696    #[inline(always)]
697    fn legendre<P: Policy>(mut x: Self, n: u32, m: u32) -> Self {
698        #[cfg(not(target_arch = "spirv"))]
699        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
700            x = new_x[0];
701        }
702
703        match (n, m) {
704            (0, 0) => return Self::ONE,
705            (n, 0) if n < 14 => return Self::legendre0::<P, 0>(x, n),
706            (n, 0) => {
707                let mut k = 14; // set to max degree hard-coded + 1
708
709                // these should inline
710                let mut p0 = Self::legendre0::<P, 12>(x, 12); // n = k - 2
711                let mut p1 = Self::legendre0::<P, 13>(x, 13); // n = k - 1
712
713                while k <= n {
714                    let nf = Self::splat(E::from_int(k as thermite::LargeInt));
715
716                    let tmp = p1;
717                    p1 = x.mul_sube((nf + nf).mul_sube(p1, p1), nf.mul_sube(p0, p0)) / nf;
718                    p0 = tmp;
719
720                    k += 1;
721                }
722
723                return p1;
724            }
725            _ => {}
726        }
727
728        let jacobi = Self::jacobi::<P>(x, Self::ZERO, Self::ZERO, n, m);
729
730        let x12 = x.nmul_adde(x, Self::ONE); // (1 - x^2)
731
732        if m & 1 == 0 {
733            jacobi * Self::powi::<P>(x12, (m >> 1) as i32)
734        } else {
735            // negate sign for odd powers (-1)^m
736            -jacobi * Self::powi::<P>(x12, m as i32).sqrt()
737        }
738    }
739
740    fn lambert_w<P: Policy>(self) -> (Self, Self);
741
742    // TEMP(bessel_j): disabled until orders beyond J_0 exist - see the note in lib.rs.
743    //fn bessel_j<P: Policy, const N: usize>(self) -> Self;
744}
745
746// The Carlson / Legendre entry points are kind-dispatched (`SpecialMath::carlson` / `::ellint`),
747// generated by decl_math!'s `@kinds` blocks - they call the request struct's `eval` directly, so
748// they need no method here. The request structs and their traits are re-exported below.
749// `EllipticConsts` is re-exported because `EllipticKind` is bounded on it: any
750// generic caller of `ellint`/`carlson` has to name it in a where-clause, so
751// leaving it unreachable made those two functions uncallable from generic code.
752pub use generic::elliptic::{
753    CarlsonKind, CarlsonRc, CarlsonRd, CarlsonRf, CarlsonRg, CarlsonRj, EllintD, EllintDInc, EllintE, EllintEInc,
754    EllintF, EllintK, EllintPi, EllintPiInc, EllipticConsts, EllipticKind, WrapTo,
755};
756
757/// Specialized implementation trait for real-only special math functions.
758///
759/// Extends [`SpecializedSpecialMath`] with functions that have no meaningful
760/// complex analogue (e.g. functions using the real absolute value, or functions
761/// that are inverses of real-domain-only operations).
762pub trait SpecializedRealSpecialMath<E>: SpecializedSpecialMath<E> {
763    fn erfinv<P: Policy>(self) -> Self;
764    fn probit<P: Policy>(self) -> Self;
765
766    #[inline(always)]
767    fn gelu<P: Policy>(self, alpha: Self) -> Self {
768        let alpha_x = alpha * self;
769
770        // GELU(x) = 0.5 * x * (1 + erf(ax / sqrt(2)))
771        let erf = alpha_x.scale(FloatConsts::FRAC_1_SQRT_2).erf_p::<P>();
772
773        if Self::HAS_TRUE_FMA {
774            // if we have true FMA, we can maintain precision while avoiding extra work.
775            let half_x = self.scale(E::ConstRatio::<1, 2>::VALUE);
776            half_x.mul_add(erf, half_x) // 0.5 * x + 0.5 * x * erf
777        } else {
778            self.scale(E::ConstRatio::<1, 2>::VALUE) * (Self::ONE + erf)
779        }
780    }
781
782    #[inline(always)]
783    fn swish<P: Policy>(self, beta: Self) -> Self {
784        let x = self;
785        let beta_x = beta * x;
786
787        // sigmoid(beta * x) = 1 / (1 + exp(-beta * x))
788        let e = (-beta_x).exp_p::<P>();
789        let s = (Self::ONE + e).reciprocal_p::<P>();
790
791        x * s
792    }
793
794    fn lgamma_r<P: Policy>(self) -> (Self, Self);
795
796    #[inline(always)]
797    fn algebraic_sigmoid<P: Policy, const N: usize>(self) -> Self {
798        if const { N == 0 } {
799            return self; // identity function
800        }
801
802        let pre_root = Self::ONE + self.abs().powi_p::<P>(N as i32); // = 1 + |x|^N
803
804        let denom = match N {
805            1 => pre_root,
806            2 => pre_root.sqrt(),
807            3 => pre_root.cbrt_p::<P>(),
808            4 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } => pre_root.sqrt().sqrt(),
809            _ => {
810                // copied from `nth_root`, but without negative handling since we know the input is always ≥ 1
811                let x = pre_root;
812
813                // initial guess using reduced precision
814                let mut y = x.powf_p::<CheckOverflow<LessPrecision<P>, false>>(Self::splat(
815                    E::ONE / E::from_int(N as thermite::LargeInt),
816                ));
817
818                // One iteration of Halley's method for nth root
819                let y_n = y.powi_p::<P>(N as i32);
820
821                let np1 = Self::splat(E::from_int((N + 1) as thermite::LargeInt));
822                let nm1 = Self::splat(E::from_int((N - 1) as thermite::LargeInt));
823
824                let n = y * (x - y_n); // half of numerator
825                let d = y_n.mul_adde(np1, x * nm1);
826
827                y += (n + n) / d;
828
829                y
830            }
831        };
832
833        // denom now equals (1 + |x|^N)^(1/N)
834
835        let mut y = if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
836            // this is the same number of operations as the more precise version, but
837            // with better accuracy on large pre_root when using approximate rpc.
838            self * denom.reciprocal_p::<P>()
839        } else {
840            self / denom
841        };
842
843        if const { P::POLICY.check_overflow } {
844            y = pre_root.is_infinite().select(self.signum(), y);
845        }
846
847        y
848    }
849
850    // f(x)  = x*(1/2 + x/(2 sqrt(1 + x^2)))
851    // f'(x) = (x^3 + sqrt(1 + x^2) x^2 + sqrt(1 + x^2) + 2 x) / (2 (1 + x^2)^(3/2))
852    //
853    // With a = 1 + x^2, r = sqrt(a), q = x/r:
854    //   f(x)  = (x/2)*(1 + q)
855    //   f'(x) = (1 + q + q/a) / 2     (since q' = 1/(a*r), so f' = g + x*g' = (1+q)/2 + q/(2a))
856    #[inline(always)]
857    fn algebraic_swish<P: Policy>(self) -> Self {
858        let x = self;
859
860        if const { Self::HAS_TRUE_FMA } {
861            // rsqrt is about 30% faster than sqrt+div, even with the extra
862            // newton iteration merged in.
863            if const { Self::HAS_APPROX_RSQRT } {
864                let a = x.mul_add(x, Self::ONE);
865                let y0 = a.rsqrt();
866                let ay2 = a * y0 * y0;
867                let ch = ay2.nmul_add(Self::HALF, Self::splat(<E as FloatElement>::ConstRatio::<3, 2>::VALUE));
868                let r_inv = y0 * ch; // Newton-refined 1/sqrt(a)
869                let q = x * r_inv;
870                let xh = Self::HALF * x;
871                q.mul_add(xh, xh)
872            } else {
873                let a = x.mul_add(x, Self::ONE);
874                let q = x / a.sqrt();
875                let xh = x * Self::HALF;
876                q.mul_add(xh, xh)
877            }
878        } else if const { Self::HAS_APPROX_RCP } {
879            let a = x * x + Self::ONE;
880            let y0 = a.rsqrt();
881            let ay2 = a * y0 * y0;
882            let c = Self::splat(<E as FloatElement>::ConstInt::<3>::VALUE) - ay2;
883            let r_inv_2 = y0 * c; // = 2 * (Newton-refined 1/sqrt(a))
884            let hxy1 = Self::splat(<E as FloatElement>::ConstRatio::<1, 4>::VALUE) * (x * r_inv_2); // = q/2
885            let w = Self::HALF + hxy1; // = (1 + q)/2
886            x * w
887        } else {
888            let a = x * x + Self::ONE;
889            let q = x / a.sqrt();
890            let q1 = q + Self::ONE;
891            x * Self::HALF * q1
892        }
893    }
894
895    #[inline(always)]
896    fn gaussian_integral<P: Policy>(x0: Self, x1: Self, a: Self, c: Self) -> Self {
897        // https://www.wolframalpha.com/input?i=integrate%20a*e%5E(-1%2F2%20*%20x%5E2%2Fc%5E2)%20from%20x%3Dx_0%20to%20x%3Dx_1
898        let common = Self::SQRT_FRAC_PI_2 * a * c;
899        let denom = Self::SQRT_2 * c;
900
901        let (a1, a0) = if const { P::POLICY.precision.le(PrecisionPolicy::Medium) } {
902            let d = denom.reciprocal_p::<P>();
903            (x1 * d, x0 * d)
904        } else {
905            (x1 / denom, x0 / denom)
906        };
907
908        common * (a1.erf_p::<P>() - a0.erf_p::<P>())
909    }
910}
911
912/// Value-and-derivative (`_d`) forms of the activation functions, for single-value real numbers.
913///
914/// Every method is a provided default returning `(value, derivative)`; the `value` matches the
915/// like-named value-only function in [`SpecializedSpecialMath`] / [`SpecializedRealSpecialMath`].
916/// Implemented (as an empty impl) only for primal types -- *not* for derivative-carrying numbers
917/// like `Dual`, which obtain the derivative from the value form via automatic differentiation.
918pub trait SpecializedRealPrimalMath<E>: SpecializedRealSpecialMath<E> {
919    #[inline(always)]
920    fn softplus_d<P: Policy>(self, k: Self, rcp_k: Self) -> (Self, Self) {
921        if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
922            let k = k.scale(FloatConsts::LOG2_E);
923            let rcp_k = rcp_k.scale(FloatConsts::LN_2);
924
925            let kx = self * k;
926
927            let e = kx.abs().neg().exp2_p::<CheckOverflow<P, true>>();
928            let y = (Self::ONE + e).log2_p::<P>().mul_adde(rcp_k, self.max(Self::ZERO));
929
930            let rcp = (Self::ONE + e).reciprocal_p::<P>();
931            let dy = kx.select_negative(e * rcp, rcp);
932
933            return (y, dy);
934        }
935
936        let kx = self * k;
937
938        let e = kx.abs().neg().exp_p::<P>();
939
940        // max(0, x) + lnp1(e^(-|x|)) is more stable than ln(1 + e^x) for large |x|.
941        let y = e.ln_1p_p::<P>().mul_adde(rcp_k, self.max(Self::ZERO));
942
943        // sigmoid from already-computed e = exp(-|kx|)
944        let rcp = (e + Self::ONE).reciprocal_p::<P>();
945        let dy = kx.select_negative(e * rcp, rcp);
946
947        (y, dy)
948    }
949
950    #[inline(always)]
951    fn gelu_d<P: Policy>(self, alpha: Self) -> (Self, Self) {
952        let alpha_x = alpha * self;
953
954        // GELU(x) = 0.5 * x * (1 + erf(ax / sqrt(2)))
955        let erf = alpha_x.scale(FloatConsts::FRAC_1_SQRT_2).erf_p::<P>();
956
957        let y = if Self::HAS_TRUE_FMA {
958            let half_x = self.scale(E::ConstRatio::<1, 2>::VALUE);
959            half_x.mul_add(erf, half_x) // 0.5 * x + 0.5 * x * erf
960        } else {
961            self.scale(E::ConstRatio::<1, 2>::VALUE) * (Self::ONE + erf)
962        };
963
964        let dy = (alpha_x * alpha_x)
965            .scale(E::ConstRatio::<{ -1 }, 2>::VALUE)
966            .exp_p::<P>()
967            .scale(FloatConsts::FRAC_1_SQRT_TAU);
968
969        (y, dy.mul_adde(alpha_x, y))
970    }
971
972    #[inline(always)]
973    fn swish_d<P: Policy>(self, beta: Self) -> (Self, Self) {
974        let x = self;
975        let beta_x = beta * x;
976
977        let e = (-beta_x).exp_p::<P>();
978        let s = (Self::ONE + e).reciprocal_p::<P>();
979
980        let y = x * s;
981
982        // dy/dx = s + beta * y * (1 - s); 1 - s = e * s (stable near s ~ 1)
983        let dy = (beta * y).mul_adde(e * s, s);
984
985        (y, dy)
986    }
987
988    #[inline(always)]
989    fn algebraic_sigmoid_d<P: Policy, const N: usize>(self) -> (Self, Self) {
990        if const { N == 0 } {
991            return (self, Self::ONE); // identity function
992        }
993
994        let pre_root = Self::ONE + self.abs().powi_p::<P>(N as i32); // = 1 + |x|^N
995
996        let denom = match N {
997            1 => pre_root,
998            2 => pre_root.sqrt(),
999            3 => pre_root.cbrt_p::<P>(),
1000            4 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } => pre_root.sqrt().sqrt(),
1001            _ => {
1002                let x = pre_root;
1003
1004                let mut y = x.powf_p::<CheckOverflow<LessPrecision<P>, false>>(Self::splat(
1005                    E::ONE / E::from_int(N as thermite::LargeInt),
1006                ));
1007
1008                let y_n = y.powi_p::<P>(N as i32);
1009
1010                let np1 = Self::splat(E::from_int((N + 1) as thermite::LargeInt));
1011                let nm1 = Self::splat(E::from_int((N - 1) as thermite::LargeInt));
1012
1013                let n = y * (x - y_n); // half of numerator
1014                let d = y_n.mul_adde(np1, x * nm1);
1015
1016                y += (n + n) / d;
1017
1018                y
1019            }
1020        };
1021
1022        // denom = (1 + |x|^N)^(1/N); f'(x) = 1 / (pre_root * denom)
1023        let mut y;
1024        let mut dy;
1025
1026        if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
1027            let inv_denom = denom.reciprocal_p::<P>();
1028            y = self * inv_denom;
1029            dy = inv_denom / pre_root;
1030        } else {
1031            y = self / denom;
1032            dy = (pre_root * denom).reciprocal_p::<P>();
1033        }
1034
1035        if const { P::POLICY.check_overflow } {
1036            let is_infinite = pre_root.is_infinite();
1037
1038            y = is_infinite.select(self.signum(), y);
1039            dy = dy.nz(is_infinite); // zero if is_infinite
1040        }
1041
1042        (y, dy)
1043    }
1044
1045    #[inline(always)]
1046    fn algebraic_swish_d<P: Policy>(self) -> (Self, Self) {
1047        let x = self;
1048
1049        if const { Self::HAS_TRUE_FMA } {
1050            if const { Self::HAS_APPROX_RSQRT } {
1051                let a = x.mul_add(x, Self::ONE);
1052                let y0 = a.rsqrt();
1053                let ay2 = a * y0 * y0;
1054                let ch = ay2.nmul_add(Self::HALF, Self::splat(<E as FloatElement>::ConstRatio::<3, 2>::VALUE));
1055                let r_inv = y0 * ch; // Newton-refined 1/sqrt(a)
1056                let q = x * r_inv;
1057                let xh = Self::HALF * x;
1058                let y = q.mul_add(xh, xh);
1059
1060                let inv_a = r_inv * r_inv;
1061                let qa = q.mul_add(inv_a, q); // q + q/a
1062                let dy = qa.mul_add(Self::HALF, Self::HALF); // (qa + 1)/2
1063
1064                (y, dy)
1065            } else {
1066                let a = x.mul_add(x, Self::ONE);
1067                let q = x / a.sqrt();
1068                let xh = x * Self::HALF;
1069                let y = q.mul_add(xh, xh);
1070
1071                let inv_a = a.reciprocal_p::<P>();
1072                let qa = q.mul_add(inv_a, q);
1073                let dy = qa.mul_add(Self::HALF, Self::HALF);
1074
1075                (y, dy)
1076            }
1077        } else if const { Self::HAS_APPROX_RCP } {
1078            let a = x * x + Self::ONE;
1079            let y0 = a.rsqrt();
1080            let ay2 = a * y0 * y0;
1081            let c = Self::splat(<E as FloatElement>::ConstInt::<3>::VALUE) - ay2;
1082            let r_inv_2 = y0 * c; // = 2 * (Newton-refined 1/sqrt(a))
1083            let hxy1 = Self::splat(<E as FloatElement>::ConstRatio::<1, 4>::VALUE) * (x * r_inv_2); // = q/2
1084            let w = Self::HALF + hxy1; // = (1 + q)/2
1085            let y = x * w;
1086
1087            let inv_a = Self::splat(<E as FloatElement>::ConstRatio::<1, 4>::VALUE) * (r_inv_2 * r_inv_2);
1088            let dy = w + hxy1 * inv_a;
1089
1090            (y, dy)
1091        } else {
1092            let a = x * x + Self::ONE;
1093            let q = x / a.sqrt();
1094            let q1 = q + Self::ONE;
1095            let y = x * Self::HALF * q1;
1096
1097            let dy = Self::HALF * (q1 + q / a);
1098
1099            (y, dy)
1100        }
1101    }
1102}