Skip to main content

thermite_special/specialized/
mod.rs

1#![allow(clippy::excessive_precision)]
2
3use thermite::{
4    const_splat,
5    mask::GenericMask,
6    math::{
7        CoreMathWithPolicy as _, FloatConsts, PrimalProjection, TranscendentalMathWithPolicy as _,
8        policy::{
9            Policy, PrecisionPolicy,
10            policies::{CheckOverflow, ExtraPrecision, LessPrecision},
11        },
12        specialized::FlushDenormals,
13    },
14    register::{Element, FloatElement},
15    vector::{NumericVector, PartialOrdVector, SplatConst},
16};
17
18use super::SpecialMathWithPolicy as _;
19
20#[macro_use]
21mod bessel;
22pub use bessel::{BesselDetails, kernels};
23pub(crate) use bessel::{bessel_reflect_negates, bessel_reflect_v};
24
25/// The real-vector kernel bodies behind the `ps`/`pd` overrides and the trait defaults.
26///
27/// Public so a composite can call one directly from its own override when it wants a
28/// different lowering than the default (e.g. `thermite-dual` takes the Hermite functions
29/// with `EXACT_FMA = true`).
30pub mod generic;
31mod pd;
32mod ps;
33
34pub use generic::bessel::ratio::{bessel_i_ratio_deriv, bessel_i_ratio_deriv_1m};
35pub use generic::ndtr::LogTailPolicy;
36
37/// The decisions the [`expint`](SpecializedSpecialMath::expint) kernel has to make
38/// differently depending on the arithmetic it is running in.
39///
40/// These are choices *inside* one algorithm, not part of the math surface, so they live
41/// here rather than on [`SpecializedSpecialMath`] itself. They exist because a single
42/// series/continued-fraction body serves both the real line and the complex cut plane,
43/// and "the unit disc", "out of domain" and "negligible but nonzero" are three different
44/// comparisons in those two worlds.
45///
46/// Every method defaults to the real-line answer, so a real vector's implementation is
47/// empty and its [`ExpIntDetails`](SpecializedSpecialMath::ExpIntDetails) is `Self`.
48pub trait ExpIntDetails<E, V: thermite::vector::FloatVector<Element = E>> {
49    /// Lanes that should take the power series rather than the continued fraction.
50    ///
51    /// On the real line this is `x < 1`. Over C it is `|z| < 1`, which is *not* what a
52    /// complex `cmp_lt` means. That is a lexicographic sort order, and reading it as a
53    /// magnitude silently routes far-off-axis points into the wrong regime.
54    #[inline(always)]
55    fn use_series(z: V) -> V::Mask {
56        z.cmp_lt(V::ONE)
57    }
58
59    /// Lanes outside the domain, forced to NaN when the policy checks overflow.
60    ///
61    /// Real `E_N` is defined for `x >= 0` only. The complex principal branch covers the
62    /// whole cut plane `|Arg z| < pi`, so there the negative reals are in-domain and the
63    /// cut is carried entirely by the principal `ln` inside the series.
64    #[inline(always)]
65    fn invalid(z: V) -> V::Mask {
66        z.cmp_lt(V::ZERO) | z.is_nan()
67    }
68
69    /// Lentz sentinel: the stand-in for a denominator that came out exactly zero, small
70    /// enough to be negligible against any real term.
71    ///
72    /// The safe magnitude depends on the arithmetic, not just the format. Real division
73    /// only needs this to be tiny and nonzero, so `MIN_POSITIVE` is ideal. A complex
74    /// reciprocal divides by `|z|^2`, so both the sentinel and its reciprocal have to
75    /// survive being *squared* - `MIN_POSITIVE` underflows to zero there, which takes
76    /// the whole fraction to NaN.
77    #[inline(always)]
78    fn cf_tiny() -> V {
79        V::MIN_POSITIVE
80    }
81}
82
83/// `1/(k+1)`, the Laguerre recurrence's leading coefficient, as one scalar divide.
84///
85/// The divisor is a small loop-invariant integer, so this sits off the recurrence's
86/// critical path (and folds to a literal outright when the degree is a const generic).
87/// Dividing the vector instead would put a full divide latency straight into the
88/// dependency chain (roughly 14 cycles per step against 4 for the multiply) to save
89/// half an ulp on a step that already carries several.
90#[inline(always)]
91fn laguerre_rcp<E: FloatElement>(k: usize) -> E {
92    E::from_ratio(1, (k + 1) as thermite::LargeInt)
93}
94
95pub trait SpecializedSpecialMath<E>: thermite::math::specialized::SpecializedTranscendentalMath<E> {
96    /// Per-arithmetic details of the [`expint`](Self::expint) kernel. Almost always
97    /// `Self`, with an empty [`ExpIntDetails`] impl taking every default.
98    type ExpIntDetails: ExpIntDetails<E, Self>;
99
100    /// TwoSum for exponent assembly: `(a + b, the rounding it discarded)`. Internal to
101    /// this trait; it is a lowering detail of the Poisson exponent.
102    ///
103    /// The default returns a zero residual on purpose. A TwoSum spelled with `+`/`-` is
104    /// only error-free while those are strict, and on the scalar backend under
105    /// `algebraic-scalar` they are not: LLVM folds the residual to zero. `ps.rs`/`pd.rs`
106    /// override this with the strict `FloatVectorWithBits::two_sum`, and `Dual` delegates
107    /// componentwise to its inner type. Do not "optimize" the default by spelling the six
108    /// adds here; it passes every test on every SIMD backend and is silently wrong on
109    /// exactly one configuration.
110    #[inline(always)]
111    fn exp_two_sum(a: Self, b: Self) -> (Self, Self) {
112        (a + b, Self::ZERO)
113    }
114
115    /// Largest integer weight for which `laguerre_function_i` seeds by the direct product
116    /// `x^{alpha/2} / sqrt(alpha!)` (a scalar factorial, `powi`, at most one `sqrt`) instead
117    /// of the general `exp(alpha/2 ln x - lgamma(alpha+1)/2)`. `0` disables it.
118    ///
119    /// The bound is per arithmetic because it is set by the exponent range: `alpha!` must
120    /// stay finite, and `x^{alpha/2}` must stay finite wherever `e^{-x/4}` is still
121    /// non-zero (so `inf * 0` cannot arise). Those give 170 / 29 for binary64 / binary32;
122    /// see `generic::laguerre::product_seed`. The default is the safe "never".
123    ///
124    /// It lives on the trait rather than as a const generic on the kernel so that the
125    /// composites can inherit it: `Dual<V, N>` is one blanket impl with no binary32/64
126    /// split to hang a literal on, and forwarding `V`'s value is the only way it keeps the
127    /// product seed at all.
128    const LAGUERRE_PRODUCT_SEED_CAP: i32 = 0;
129
130    fn erf<P: Policy>(self) -> Self;
131
132    #[inline(always)]
133    fn erfc<P: Policy>(self) -> Self {
134        Self::ONE - self.erf_p::<P>()
135    }
136
137    /// `$e^{x^2}\operatorname{erfc}(x)$`, which does not underflow where `erfc` does.
138    ///
139    /// This default is the direct form, whose failure is what the function exists to
140    /// fix: `$e^{x^2}$` overflows just where `erfc` underflows, so it
141    /// is useful only for `$|x|$` under about 26.6 (binary64) or 9.3 (binary32). The
142    /// real backends override it with the imaginary-axis Weideman evaluation, which has
143    /// no such limit (see `generic::erfcx`). Element types without a Weideman table
144    /// (`Compensated`) take this and inherit its range.
145    #[inline(always)]
146    fn erfcx<P: Policy>(self) -> Self {
147        (self * self).exp_p::<P>() * self.erfc_p::<P>()
148    }
149
150    /// Computes the exponential integral `E_N(x)` for integer order `N`.
151    #[inline(always)]
152    fn expint_n<P: Policy, const N: usize>(self) -> Self {
153        self.expint_primal_n::<P, N>().0
154    }
155
156    /// Computes `$E_N(x)$` together with the adjacent lower order `$E_{N-1}(x)$`.
157    ///
158    /// Differentiating the integral definition under the integral sign gives
159    /// `$E_N'(x) = -E_{N-1}(x)$`, so the second element is the derivative up to sign.
160    /// The order recurrence already walks `E_1 -> E_N`, which makes `E_{N-1}` simply
161    /// the previous iterate: the pair costs no more than the value alone. `thermite-dual`
162    /// uses this to take the (guarded) real path for both parts rather than running
163    /// this entire routine in dual arithmetic.
164    ///
165    /// Uses the power series for x < 1 and the Stieltjes continued fraction for x >= 1,
166    /// computed in parallel across SIMD lanes and blended at the end.
167    /// For N > 1, applies the recurrence `$E_{n+1}(x) = (e^{-x} - x \cdot E_n(x)) / n$`.
168    #[inline(always)]
169    fn expint_primal_n<P: Policy, const N: usize>(self) -> (Self, Self) {
170        let x = self;
171
172        // The series/continued-fraction path below produces E_1, so the two orders
173        // beneath it come from their closed forms instead:
174        //   E_0(x)    = e^-x / x
175        //   E_{-1}(x) = e^-x (1 + 1/x) / x
176        let exp_neg_x = (-x).exp_p::<P>();
177        let inv_x = x.approx_reciprocal_p::<P>();
178        let e0 = exp_neg_x * inv_x;
179
180        if const { N == 0 } {
181            let mut value = e0;
182            let mut prev = e0 * (Self::ONE + inv_x);
183
184            if const { P::POLICY.check_overflow } {
185                // Both orders have a pole at the branch point x = 0.
186                let x_is_zero = x.is_zero();
187                value = x_is_zero.select(Self::INFINITY, value);
188                prev = x_is_zero.select(Self::INFINITY, prev);
189
190                let bad = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::invalid(x);
191                value = bad.select(Self::NAN, value);
192                prev = bad.select(Self::NAN, prev);
193            }
194
195            return (value, prev);
196        }
197
198        let mut e_n = Self::expint_e1_generic::<P>(x, exp_neg_x);
199
200        // Order beneath the current one. Before the recurrence runs, E_N is E_1, so the
201        // order below it is E_0.
202        let mut e_prev = e0;
203
204        // --- Apply recurrence for N > 1 ---
205        // E_{n+1}(x) = (e^{-x} - x * E_n(x)) / n
206        if const { N > 1 } {
207            let mut n = 1u32;
208            while n < N as u32 {
209                let nf = Self::splat(E::from_int(n as thermite::LargeInt));
210                e_prev = e_n;
211                e_n = x.nmul_adde(e_n, exp_neg_x) / nf;
212                n += 1;
213            }
214        }
215
216        // --- Edge cases ---
217        if const { P::POLICY.check_overflow } {
218            // E_1(0) = +inf, E_n(0) = 1/(n-1) for n > 1
219            let x_is_zero = x.is_zero();
220            if const { N == 1 } {
221                e_n = x_is_zero.select(Self::INFINITY, e_n);
222            } else if const { N > 1 } {
223                e_n = x_is_zero.select(Self::splat(E::ONE / E::from_int(N as thermite::LargeInt - 1)), e_n);
224            }
225
226            // Same rule one order down: E_0 and E_1 both diverge at zero, E_n does not.
227            if const { N <= 2 } {
228                e_prev = x_is_zero.select(Self::INFINITY, e_prev);
229            } else {
230                e_prev = x_is_zero.select(Self::splat(E::ONE / E::from_int(N as thermite::LargeInt - 2)), e_prev);
231            }
232
233            let bad = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::invalid(x);
234            e_n = bad.select(Self::NAN, e_n);
235            e_prev = bad.select(Self::NAN, e_prev);
236        }
237
238        (e_n, e_prev)
239    }
240
241    /// The runtime-order twin of [`expint_n`](Self::expint_n).
242    #[inline(always)]
243    fn expint<P: Policy>(self, n: u32) -> Self {
244        self.expint_primal::<P>(n).0
245    }
246
247    /// The runtime-order twin of [`expint_primal_n`](Self::expint_primal_n): the same `E_1`
248    /// core, the same recurrence with the order as a value.
249    #[inline(always)]
250    fn expint_primal<P: Policy>(self, n: u32) -> (Self, Self) {
251        let x = self;
252
253        let exp_neg_x = (-x).exp_p::<P>();
254        let inv_x = x.approx_reciprocal_p::<P>();
255        let e0 = exp_neg_x * inv_x;
256
257        if n == 0 {
258            let mut value = e0;
259            let mut prev = e0 * (Self::ONE + inv_x);
260
261            if const { P::POLICY.check_overflow } {
262                let x_is_zero = x.is_zero();
263                value = x_is_zero.select(Self::INFINITY, value);
264                prev = x_is_zero.select(Self::INFINITY, prev);
265
266                let bad = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::invalid(x);
267                value = bad.select(Self::NAN, value);
268                prev = bad.select(Self::NAN, prev);
269            }
270
271            return (value, prev);
272        }
273
274        let mut e_n = Self::expint_e1_generic::<P>(x, exp_neg_x);
275        let mut e_prev = e0;
276
277        let mut k = 1u32;
278        while k < n {
279            let kf = Self::splat(E::from_int(k as thermite::LargeInt));
280            e_prev = e_n;
281            e_n = x.nmul_adde(e_n, exp_neg_x) / kf;
282            k += 1;
283        }
284
285        if const { P::POLICY.check_overflow } {
286            let x_is_zero = x.is_zero();
287            if n == 1 {
288                e_n = x_is_zero.select(Self::INFINITY, e_n);
289            } else {
290                e_n = x_is_zero.select(Self::splat(E::ONE / E::from_int(n as thermite::LargeInt - 1)), e_n);
291            }
292
293            if n <= 2 {
294                e_prev = x_is_zero.select(Self::INFINITY, e_prev);
295            } else {
296                e_prev = x_is_zero.select(Self::splat(E::ONE / E::from_int(n as thermite::LargeInt - 2)), e_prev);
297            }
298
299            let bad = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::invalid(x);
300            e_n = bad.select(Self::NAN, e_n);
301            e_prev = bad.select(Self::NAN, e_prev);
302        }
303
304        (e_n, e_prev)
305    }
306
307    /// `E_1(x)` by the interleaved series and continued fraction, the core both `expint`
308    /// forms share. Not an entry point: no edge handling, and the caller supplies
309    /// `e^{-x}` because it already has it.
310    #[doc(hidden)]
311    #[inline(always)]
312    fn expint_e1_generic<P: Policy>(x: Self, exp_neg_x: Self) -> Self {
313        // E_n(x) is only defined for x > 0 (and x >= 0 for n > 1).
314        // Compute E_1(x) first, then apply recurrence for higher orders.
315
316        // === Interleaved power series (x < 1) and continued fraction (x >= 1) ===
317        //
318        // Power series: E_1(x) = -γ - ln(x) - Σ_{k=1}^∞ (-x)^k / (k*k!)
319        //   Recurrence on terms: A_{k+1} = A_k * (-x * k) / (k+1)^2
320        //   Starting with A_1 = -x, sum = A_1.
321        //
322        // Continued fraction (Stieltjes): E_1(x)*e^x = 1/(x+1 - 1^2/(x+3 - 2^2/(x+5 - 3^2/(x+7 - ...))))
323        //   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.
324        //   Bootstrap j=1 outside the loop, iterate j≥2 inside.
325        //   Result: E_1(x) = f * e^{-x}
326
327        let use_series = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::use_series(x);
328
329        // --- Power series state ---
330        let neg_x = -x;
331        let mut s_term = neg_x; // A_1 = -x
332        let mut s_sum = s_term; // running sum starts at A_1
333
334        // --- Continued fraction state (modified Lentz's method) ---
335        //
336        // E_1(x)*e^x = 1/(x+1 - 1^2/(x+3 - 2^2/(x+5 - 3^2/(x+7 - ...))))
337        //
338        // In standard Lentz form b_0 + a_1/(b_1 + a_2/(b_2 + ...)):
339        //   b_0 = 0
340        //   j=1: a_1 = 1,       b_1 = x+1
341        //   j≥2: a_j = -(j-1)^2, b_j = x + 2j - 1
342        //
343        let tiny = <Self::ExpIntDetails as ExpIntDetails<E, Self>>::cf_tiny();
344
345        // b_0 = 0, so f_0 = tiny, C_0 = tiny, D_0 = 0
346        let mut cf_f = tiny;
347        let mut cf_c = tiny;
348
349        // Bootstrap j=1 step: a_1 = 1, b_1 = x+1. D_0 is only ever read here, so
350        // it stays a comment rather than an initializer the next line overwrites.
351        let mut cf_d = {
352            let b1 = x + Self::ONE;
353            // D_1 = 1/(b_1 + a_1*D_0) = 1/(x+1), since D_0 = 0
354            let d1 = b1.approx_reciprocal_p::<P>();
355            // C_1 = b_1 + a_1/C_0 = (x+1) + 1/tiny ≈ 1/tiny
356            cf_c = b1 + cf_c.approx_reciprocal_p::<P>();
357            let delta = cf_c * d1;
358            cf_f *= delta; // tiny * (1/tiny)/(x+1) ≈ 1/(x+1)
359            d1
360        };
361
362        // Convergence tolerance
363        let eps = Self::splat(E::EPSILON);
364
365        let mut series_done = !use_series; // lanes not using series are "done" immediately
366        let mut cf_done = use_series; // lanes not using CF are "done" immediately
367
368        let mut k = 1usize;
369        while k < const { P::POLICY.max_iterations } {
370            let kf = Self::splat(E::from_int(k as thermite::LargeInt));
371            let kp1 = Self::splat(E::from_int(k as thermite::LargeInt + 1));
372
373            // --- Power series step ---
374            // A_{k+1} = A_k * (-x * k) / (k+1)^2
375            if !series_done.all() {
376                s_term *= (neg_x * kf) / (kp1 * kp1);
377                s_sum = series_done.select(s_sum, s_sum + s_term);
378
379                let term_small = s_term.abs().cmp_lt(s_sum.abs() * eps);
380
381                // series_done | (use_series & term_small)
382                series_done = GenericMask::ternlog::<{ thermite::ternlog_imm!(A | (B & C)) }>(
383                    series_done,
384                    use_series,
385                    term_small,
386                );
387            }
388
389            // --- Continued fraction step (j = k+1, so j ≥ 2) ---
390            // a_j = -(j-1)^2 = -k^2, b_j = x + 2j - 1 = x + 2k + 1
391            if !cf_done.all() {
392                let neg_a_k = kf * kf; // |a_j| = k^2
393                let b_k = (x + kf) + (kf + Self::ONE); // x + 2k + 1
394
395                // D = 1 / (b - |a|*D_prev)  [note: subtraction because a is negative]
396                let d_denom = neg_a_k.nmul_adde(cf_d, b_k); // b - |a|*D
397                let new_d = d_denom
398                    .cmp_eq(Self::ZERO)
399                    .select(tiny, d_denom)
400                    .approx_reciprocal_p::<P>();
401
402                // C = b - |a|/C_prev  [same sign flip]
403                let new_c = b_k - neg_a_k / cf_c;
404                let new_c = new_c.cmp_eq(Self::ZERO).select(tiny, new_c);
405
406                let delta = new_c * new_d;
407
408                cf_d = new_d;
409                cf_c = new_c;
410                cf_f = cf_done.select(cf_f, cf_f * delta);
411
412                let cf_converged = (delta - Self::ONE).abs().cmp_lt(eps);
413
414                cf_done =
415                    GenericMask::ternlog::<{ thermite::ternlog_imm!(A | (!B & C)) }>(cf_done, use_series, cf_converged);
416            }
417
418            if (series_done & cf_done).all() {
419                break;
420            }
421
422            k += 1;
423        }
424
425        // --- Assemble E_1(x) from both methods ---
426
427        // Series: E_1(x) = -γ - ln(x) - sum
428        let mut series_result = Self::EMPTY;
429
430        // CF: E_1(x) = cf_f * e^{-x}  (cf_f approximates E_1(x)*e^x)
431        let mut cf_result = Self::EMPTY;
432
433        if use_series.any() {
434            series_result = (-Self::EULER_GAMMA - s_sum) - x.ln_p::<P>();
435        }
436
437        if !use_series.all() {
438            cf_result = cf_f * exp_neg_x;
439        }
440
441        use_series.select(series_result, cf_result)
442    }
443
444    #[inline(always)]
445    fn logistic_sigmoid<P: Policy>(self) -> Self {
446        if const { P::POLICY.precision.gt(PrecisionPolicy::Average) } {
447            let is_pos = self.is_positive();
448            let x = self.neg_c(is_pos); // conditionally negate if positive
449            let e = x.exp_p::<P>();
450
451            let n = is_pos.select(Self::ONE, e);
452            let d = Self::ONE + e;
453
454            return n / d;
455        }
456
457        (Self::ONE + (-self).exp_p::<P>()).approx_reciprocal_p::<ExtraPrecision<P>>()
458    }
459
460    #[inline(always)]
461    fn softplus<P: Policy>(self, k: Self, rcp_k: Self) -> Self {
462        // For low precision, we can get better performance by computing in base-2 instead of base-e,
463        // at the cost of some accuracy.
464        if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
465            // adjust to be in base-2
466            let k = k.scale(FloatConsts::LOG2_E);
467            let rcp_k = rcp_k.scale(FloatConsts::LN_2);
468
469            let kx = self * k;
470
471            // e needs overflow checks to outright incorrect results here
472            let e = kx.abs().neg().exp2_p::<CheckOverflow<P, true>>();
473            return (Self::ONE + e).log2_p::<P>().mul_adde(rcp_k, self.max(Self::ZERO));
474        }
475
476        let kx = self * k;
477
478        let e = kx.abs().neg().exp_p::<P>();
479
480        // max(0, x) + lnp1(e^(-|x|)) is more stable than ln(1 + e^x) for large |x|.
481        e.ln_1p_p::<P>().mul_adde(rcp_k, self.max(Self::ZERO))
482    }
483
484    fn tgamma<P: Policy>(self) -> Self;
485    fn lgamma<P: Policy>(self) -> Self;
486    fn digamma<P: Policy>(self) -> Self;
487
488    /// The trigamma function `psi_1(x) = d/dx psi(x)`, the second derivative of `ln Gamma`.
489    ///
490    /// Public on `SpecialMath` since 2026-08-29. It was deliberately absent while the
491    /// Gamma-derivative family was open-ended (a public `trigamma` obliged `Dual` to
492    /// produce `psi_2`, which needed `psi_3`, and so on). `polygamma`'s runtime order
493    /// closed that ladder, and every implementor of this trait already carried a
494    /// working `trigamma`, so publishing became a pure decl move.
495    ///
496    /// Not defined at zero or the negative integers.
497    fn trigamma<P: Policy>(self) -> Self;
498
499    /// The polygamma function `$\psi_n(x)$`, the n-th derivative of
500    /// [`digamma`](SpecializedSpecialMath::digamma).
501    ///
502    /// The order is a **runtime** scalar, uniform across lanes, deliberately: runtime
503    /// `n` is what closes the family under differentiation (`$\psi_n' = \psi_{n+1}$`
504    /// is just `n + 1`), where a const-generic order would recurse without bound in
505    /// `Dual`'s chain rule. It costs SIMD nothing, since every order-dependent
506    /// coefficient is scalar math splatted once.
507    ///
508    /// On this trait (rather than the real-only one) since 2026-08-29 so that complex
509    /// vectors carry it too. The complex implementation reflects at `Re z < 1/2` and
510    /// shares the real kernel's series structure in complex arithmetic.
511    fn polygamma<P: Policy>(self, n: u32) -> Self;
512
513    /// `Compensated` keeps the default: the Euler-Maclaurin coefficients are tabulated to
514    /// `f64`, so a double-double built from them would carry 53 real bits and noise, the same
515    /// reason it has no `GammaPrimalTables` impl. `Dual` overrides it through
516    /// [`zeta_with_deriv`](Self::zeta_with_deriv).
517    #[inline(always)]
518    fn zetac<P: Policy>(self) -> Self {
519        todo!("zetac is not implemented for this composite type; see the trait method's docs")
520    }
521
522    /// `zeta(s)`, as `1 + zetac(s)`. Defaulted for the same reason as
523    /// [`zetac`](Self::zetac).
524    #[inline(always)]
525    fn zeta<P: Policy>(self) -> Self {
526        todo!("zeta is not implemented for this composite type; see `zetac`'s docs")
527    }
528
529    /// `Li_s(z)` at a scalar order. Defaulted for the same reason as [`zetac`](Self::zetac):
530    /// the coefficient precompute is `f64`, so a double-double has nothing to reach for.
531    /// `Dual` overrides it through the order-lowering identity `Li_s' = Li_{s-1}/z`.
532    #[inline(always)]
533    fn polylog<P: Policy>(
534        self,
535        order: crate::PolylogOrder<
536            E,
537            <<Self as thermite::vector::GenericVector>::Signed as thermite::vector::GenericVector>::Element,
538        >,
539    ) -> Self {
540        let _ = order;
541        todo!("polylog is not implemented for this composite type; see the trait method's docs")
542    }
543
544    /// `(zeta(s), zeta'(s))`, or `(zeta(s) - 1, zeta'(s))` when `ZETAC` is set: the two
545    /// functions differ by a constant, so one derivative serves both.
546    ///
547    /// This exists because `zeta'` is a _second kernel_ rather than a chain rule over `zeta`:
548    /// `zeta'(s) = -sum ln(n) n^-s`, which has no expression in terms of `zeta` itself. It
549    /// shares every transcendental with the value, so computing both together is far cheaper
550    /// than computing them apart, which is what lets `Dual` differentiate without running the
551    /// correction ladder in dual arithmetic.
552    #[inline(always)]
553    fn zeta_with_deriv<P: Policy, const ZETAC: bool>(self) -> (Self, Self) {
554        todo!("zeta_with_deriv is not implemented for this composite type; see `zetac`'s docs")
555    }
556
557    /// `I_N(x)`, or `e^{-|x|} I_N(x)` when `SCALED`: the modified Bessel function of the
558    /// first kind at compile-time integer order.
559    ///
560    /// One method serves both the scaled and unscaled public entry points because they are
561    /// not built from each other: each table region is natively one or the other, so the
562    /// `SCALED` flag moves _which_ arm pays for an exponential rather than adding one.
563    ///
564    /// Defaulted rather than required: the coefficient tables are element-specific, so a
565    /// generic composite has nothing to reach for.
566    #[inline(always)]
567    fn bessel_i<P: Policy, const N: i32>(self) -> Self {
568        todo!("bessel_i is not implemented for this composite type")
569    }
570
571    /// `e^{-|x|} I_N(x)`. Not a wrapper over [`bessel_i`](Self::bessel_i): above the series
572    /// threshold the coefficient tables _are_ the scaled value, so this form skips the
573    /// exponential the unscaled one pays for, and stays finite where `I_N` overflows.
574    #[inline(always)]
575    fn bessel_i_scaled<P: Policy, const N: i32>(self) -> Self {
576        todo!("bessel_i_scaled is not implemented for this composite type")
577    }
578
579    /// `K_N(x)`, the modified Bessel function of the second kind at compile-time integer
580    /// order. Defaulted for the same reason as [`bessel_i`](Self::bessel_i).
581    #[inline(always)]
582    fn bessel_k<P: Policy, const N: i32>(self) -> Self {
583        todo!("bessel_k is not implemented for this composite type")
584    }
585
586    /// `e^{x} K_N(x)`. Not a wrapper: above the series threshold the tables are natively the
587    /// scaled quantity, so this form skips the exponential the unscaled one pays for, and
588    /// stays in range where `K_N` has decayed to zero.
589    #[inline(always)]
590    fn bessel_k_scaled<P: Policy, const N: i32>(self) -> Self {
591        todo!("bessel_k_scaled is not implemented for this composite type")
592    }
593
594    /// `J_N(x)`, the oscillatory Bessel function of the first kind. Orders 0 and 1 only for
595    /// now. Higher orders want a recurrence that is not written yet.
596    #[inline(always)]
597    fn bessel_j<P: Policy, const N: i32>(self) -> Self {
598        todo!("bessel_j is not implemented for this composite type")
599    }
600
601    /// `Y_N(x)`, the oscillatory Bessel function of the second kind.
602    #[inline(always)]
603    fn bessel_y<P: Policy, const N: i32>(self) -> Self {
604        todo!("bessel_y is not implemented for this composite type")
605    }
606
607    /// `(I_N(x), d/dx I_N(x))`, or the scaled pair when `SCALED`.
608    ///
609    /// A second kernel rather than a chain rule, for the same reason `zeta_with_deriv` is:
610    /// every derivative identity in this family reaches DOWN one order,
611    /// `I_N' = I_{N-1} - (N/x) I_N`, so the value and the derivative share almost all of their
612    /// work: the ratio recurrence produces `I_{N-1}` alongside `I_N` for free. It is also what
613    /// lets `Dual` differentiate without running the recurrence in dual arithmetic.
614    ///
615    /// That the identity reaches down and not up is the fact that unblocks this whole family:
616    /// the textbook form `J_N' = (J_{N-1} - J_{N+1})/2` needs an order ABOVE `N`, which is why
617    /// `bessel_j` sat disabled for so long.
618    #[inline(always)]
619    fn bessel_i_with_deriv<P: Policy, const N: i32, const SCALED: bool>(self) -> (Self, Self) {
620        todo!("bessel_i_with_deriv is not implemented for this composite type")
621    }
622
623    /// `(K_N(x), d/dx K_N(x))`. See [`bessel_i_with_deriv`](Self::bessel_i_with_deriv).
624    #[inline(always)]
625    fn bessel_k_with_deriv<P: Policy, const N: i32, const SCALED: bool>(self) -> (Self, Self) {
626        todo!("bessel_k_with_deriv is not implemented for this composite type")
627    }
628
629    /// `(J_N(x), d/dx J_N(x))`. See [`bessel_i_with_deriv`](Self::bessel_i_with_deriv).
630    #[inline(always)]
631    fn bessel_j_with_deriv<P: Policy, const N: i32>(self) -> (Self, Self) {
632        todo!("bessel_j_with_deriv is not implemented for this composite type")
633    }
634
635    /// `(Y_N(x), d/dx Y_N(x))`. See [`bessel_i_with_deriv`](Self::bessel_i_with_deriv).
636    #[inline(always)]
637    fn bessel_y_with_deriv<P: Policy, const N: i32>(self) -> (Self, Self) {
638        todo!("bessel_y_with_deriv is not implemented for this composite type")
639    }
640
641    /// `I_n(x)` with a per-lane order. See [`bessel_i`](Self::bessel_i).
642    #[inline(always)]
643    fn bessel_iv<P: Policy, const SCALED: bool>(self, _order: crate::BesselOrder<Self, Self::Signed>) -> Self {
644        todo!("bessel_iv is not implemented for this composite type")
645    }
646
647    /// `K_n(x)` with a per-lane order. See [`bessel_k`](Self::bessel_k).
648    #[inline(always)]
649    fn bessel_kv<P: Policy, const SCALED: bool>(self, _order: crate::BesselOrder<Self, Self::Signed>) -> Self {
650        todo!("bessel_kv is not implemented for this composite type")
651    }
652
653    /// `J_n(x)` with a per-lane order. See [`bessel_j`](Self::bessel_j).
654    #[inline(always)]
655    fn bessel_jv<P: Policy>(self, _order: crate::BesselOrder<Self, Self::Signed>) -> Self {
656        todo!("bessel_jv is not implemented for this composite type")
657    }
658
659    /// `Y_n(x)` with a per-lane order. See [`bessel_y`](Self::bessel_y).
660    #[inline(always)]
661    fn bessel_yv<P: Policy>(self, _order: crate::BesselOrder<Self, Self::Signed>) -> Self {
662        todo!("bessel_yv is not implemented for this composite type")
663    }
664
665    /// `j_n(x)`, the spherical Bessel function of the first kind. See
666    /// [`sph_bessel_j`](Self::sph_bessel_j).
667    #[inline(always)]
668    fn sph_bessel_j_n<P: Policy, const N: usize>(self) -> Self {
669        todo!("sph_bessel_j is not implemented for this composite type")
670    }
671
672    /// `y_n(x)`. See [`sph_bessel_y`](Self::sph_bessel_y).
673    #[inline(always)]
674    fn sph_bessel_y_n<P: Policy, const N: usize>(self) -> Self {
675        todo!("sph_bessel_y is not implemented for this composite type")
676    }
677
678    /// `i_n(x)`. See [`sph_bessel_i`](Self::sph_bessel_i).
679    #[inline(always)]
680    fn sph_bessel_i_n<P: Policy, const N: usize>(self) -> Self {
681        todo!("sph_bessel_i is not implemented for this composite type")
682    }
683
684    /// `e^{-x} i_n(x)`. See [`sph_bessel_i_scaled`](Self::sph_bessel_i_scaled).
685    #[inline(always)]
686    fn sph_bessel_i_scaled_n<P: Policy, const N: usize>(self) -> Self {
687        todo!("sph_bessel_i_scaled is not implemented for this composite type")
688    }
689
690    /// `k_n(x)`. See [`sph_bessel_k`](Self::sph_bessel_k).
691    #[inline(always)]
692    fn sph_bessel_k_n<P: Policy, const N: usize>(self) -> Self {
693        todo!("sph_bessel_k is not implemented for this composite type")
694    }
695
696    /// `e^{x} k_n(x)`. See [`sph_bessel_k_scaled`](Self::sph_bessel_k_scaled).
697    #[inline(always)]
698    fn sph_bessel_k_scaled_n<P: Policy, const N: usize>(self) -> Self {
699        todo!("sph_bessel_k_scaled is not implemented for this composite type")
700    }
701
702    /// `(j_n(x), j_n'(x))`, both from one walk.
703    ///
704    /// The derivative identity reaches **down** one order,
705    /// `f_n' = f_{n-1} - ((n+1)/x) f_n`, and the recurrence passes through `n-1` regardless,
706    /// so the pair costs no more than the value. Exists for `Dual`, on the same footing as
707    /// `bessel_j_with_deriv`.
708    #[inline(always)]
709    fn sph_bessel_j_with_deriv_n<P: Policy, const N: usize>(self) -> (Self, Self) {
710        todo!("sph_bessel_j_with_deriv is not implemented for this composite type")
711    }
712
713    /// `(y_n(x), y_n'(x))`. See [`sph_bessel_j_with_deriv`](Self::sph_bessel_j_with_deriv).
714    #[inline(always)]
715    fn sph_bessel_y_with_deriv_n<P: Policy, const N: usize>(self) -> (Self, Self) {
716        todo!("sph_bessel_y_with_deriv is not implemented for this composite type")
717    }
718
719    /// `(i_n(x), i_n'(x))`, scaled by `e^{-x}` when `SCALED`, in which case the derivative is
720    /// the scaled function's own, `d/dx(e^{-x} i_n) = e^{-x}(i_n' - i_n)`.
721    #[inline(always)]
722    fn sph_bessel_i_with_deriv_n<P: Policy, const N: usize, const SCALED: bool>(self) -> (Self, Self) {
723        todo!("sph_bessel_i_with_deriv is not implemented for this composite type")
724    }
725
726    /// `(k_n(x), k_n'(x))`, scaled by `e^{x}` when `SCALED`.
727    #[inline(always)]
728    fn sph_bessel_k_with_deriv_n<P: Policy, const N: usize, const SCALED: bool>(self) -> (Self, Self) {
729        todo!("sph_bessel_k_with_deriv is not implemented for this composite type")
730    }
731
732    // ---- runtime-order twins of the ten above, same composite caveat -------------------------
733
734    /// `j_n(x)` for a runtime order. See [`sph_bessel_j`](Self::sph_bessel_j).
735    #[inline(always)]
736    fn sph_bessel_j<P: Policy>(self, n: u32) -> Self {
737        let _ = n;
738        todo!("sph_bessel_j is not implemented for this composite type")
739    }
740
741    /// `y_n(x)` for a runtime order.
742    #[inline(always)]
743    fn sph_bessel_y<P: Policy>(self, n: u32) -> Self {
744        let _ = n;
745        todo!("sph_bessel_y is not implemented for this composite type")
746    }
747
748    /// `i_n(x)` for a runtime order.
749    #[inline(always)]
750    fn sph_bessel_i<P: Policy>(self, n: u32) -> Self {
751        let _ = n;
752        todo!("sph_bessel_i is not implemented for this composite type")
753    }
754
755    /// `e^{-x} i_n(x)` for a runtime order.
756    #[inline(always)]
757    fn sph_bessel_i_scaled<P: Policy>(self, n: u32) -> Self {
758        let _ = n;
759        todo!("sph_bessel_i_scaled is not implemented for this composite type")
760    }
761
762    /// `k_n(x)` for a runtime order.
763    #[inline(always)]
764    fn sph_bessel_k<P: Policy>(self, n: u32) -> Self {
765        let _ = n;
766        todo!("sph_bessel_k is not implemented for this composite type")
767    }
768
769    /// `e^{x} k_n(x)` for a runtime order.
770    #[inline(always)]
771    fn sph_bessel_k_scaled<P: Policy>(self, n: u32) -> Self {
772        let _ = n;
773        todo!("sph_bessel_k_scaled is not implemented for this composite type")
774    }
775
776    /// `(j_n(x), j_n'(x))` for a runtime order. See
777    /// [`sph_bessel_j_with_deriv_n`](Self::sph_bessel_j_with_deriv_n).
778    #[inline(always)]
779    fn sph_bessel_j_with_deriv<P: Policy>(self, n: u32) -> (Self, Self) {
780        let _ = n;
781        todo!("sph_bessel_j_with_deriv is not implemented for this composite type")
782    }
783
784    /// `(y_n(x), y_n'(x))` for a runtime order.
785    #[inline(always)]
786    fn sph_bessel_y_with_deriv<P: Policy>(self, n: u32) -> (Self, Self) {
787        let _ = n;
788        todo!("sph_bessel_y_with_deriv is not implemented for this composite type")
789    }
790
791    /// `(i_n(x), i_n'(x))` for a runtime order, scaled by `e^{-x}` when `SCALED`.
792    #[inline(always)]
793    fn sph_bessel_i_with_deriv<P: Policy, const SCALED: bool>(self, n: u32) -> (Self, Self) {
794        let _ = n;
795        todo!("sph_bessel_i_with_deriv is not implemented for this composite type")
796    }
797
798    /// `(k_n(x), k_n'(x))` for a runtime order, scaled by `e^{x}` when `SCALED`.
799    #[inline(always)]
800    fn sph_bessel_k_with_deriv<P: Policy, const SCALED: bool>(self, n: u32) -> (Self, Self) {
801        let _ = n;
802        todo!("sph_bessel_k_with_deriv is not implemented for this composite type")
803    }
804
805    // ---- marker-selected entries ---------------------------------------------------------
806    //
807    // The public `bessel_n` / `bessel` / `sph_bessel_n` / `sph_bessel` / `airy` /
808    // `airy_all` are thin: the family marker picks which of the per-family hooks above it
809    // reaches. Nothing here needs overriding (a composite that overrides the per-family
810    // hooks is reached through them), but they are trait methods because the math-traits
811    // forwarder calls every public entry through this trait.
812
813    /// `bessel_n::<F, N>()`: see [`BesselFamily`](crate::bessel::BesselFamily).
814    #[inline(always)]
815    fn bessel_n<P: Policy, F: crate::bessel::BesselFamily, const N: i32>(self) -> Self {
816        F::cyl_n::<P, E, Self, N>(self)
817    }
818
819    /// `bessel::<F>(order)`: see [`BesselFamily`](crate::bessel::BesselFamily).
820    #[inline(always)]
821    fn bessel<P: Policy, F: crate::bessel::BesselFamily>(self, order: crate::BesselOrder<Self, Self::Signed>) -> Self {
822        F::cyl_v::<P, E, Self>(self, order)
823    }
824
825    /// `sph_bessel_n::<F, N>()`.
826    #[inline(always)]
827    fn sph_bessel_n<P: Policy, F: crate::bessel::BesselFamily, const N: usize>(self) -> Self {
828        F::sph_n::<P, E, Self, N>(self)
829    }
830
831    /// `sph_bessel::<F>(n)`.
832    #[inline(always)]
833    fn sph_bessel<P: Policy, F: crate::bessel::BesselFamily>(self, n: u32) -> Self {
834        F::sph_v::<P, E, Self>(self, n)
835    }
836
837    /// `airy::<W>()`: see [`AiryFn`](crate::bessel::AiryFn).
838    #[inline(always)]
839    fn airy<P: Policy, W: crate::bessel::AiryFn>(self) -> Self {
840        W::eval::<P, E, Self, false>(self)
841    }
842
843    /// The four Airy values, scaled on the positive axis when `SCALED`.
844    #[inline(always)]
845    fn airy_all<P: Policy, const SCALED: bool>(self) -> (Self, Self, Self, Self) {
846        if const { SCALED } {
847            self.airy_tuple_scaled::<P>()
848        } else {
849            self.airy_tuple::<P>()
850        }
851    }
852
853    /// `Scaled(J)` at runtime order: `e^{-|Im z|} J_nu(z)`, SciPy's `jve`. The scale factor
854    /// is 1 on the real axis, so the default is the unscaled value. `Complex` overrides.
855    #[inline(always)]
856    fn bessel_jv_scaled<P: Policy>(self, order: crate::BesselOrder<Self, Self::Signed>) -> Self {
857        self.bessel_jv::<P>(order)
858    }
859
860    /// `Scaled(Y)` at runtime order, the `Y` twin of [`bessel_jv_scaled`](Self::bessel_jv_scaled).
861    #[inline(always)]
862    fn bessel_yv_scaled<P: Policy>(self, order: crate::BesselOrder<Self, Self::Signed>) -> Self {
863        self.bessel_yv::<P>(order)
864    }
865
866    /// `(Ai, Ai', Bi, Bi')`. See [`airy`](Self::airy).
867    ///
868    /// The kernel needs the `LogGamma1p` and `AiryZero` tables keyed to a concrete element,
869    /// which a generic `E` on this trait does not carry, the same bind the Bessel family is
870    /// in. The `ps`/`pd` impls override this. A composite gets this until it supplies its own.
871    ///
872    /// **Worth overriding for a derivative-carrying composite**, and unusually easy to: Airy
873    /// satisfies `$w'' = xw$`, so every derivative past the first is a combination of the
874    /// value and the first derivative, both of which this returns. Nothing needs to
875    /// differentiate the Bessel machinery underneath.
876    #[inline(always)]
877    fn airy_tuple<P: Policy>(self) -> (Self, Self, Self, Self) {
878        todo!("airy is not implemented for this composite type")
879    }
880
881    /// `(Ai, Ai', Bi, Bi')` with the exponential factored out on the positive axis. Not a
882    /// wrapper over [`airy`](Self::airy): it is the form the kernel produces natively, and
883    /// the unscaled one is the wrapper. See [`airy`](Self::airy) with a `Scaled` marker.
884    #[inline(always)]
885    fn airy_tuple_scaled<P: Policy>(self) -> (Self, Self, Self, Self) {
886        todo!("airy_scaled is not implemented for this composite type")
887    }
888
889    /// `Ai(x)` alone: a cheaper evaluation than [`airy`](Self::airy), not a projection of
890    /// it. See [`airy_ai`](Self::airy_ai).
891    #[inline(always)]
892    fn airy_ai<P: Policy>(self) -> Self {
893        todo!("airy_ai is not implemented for this composite type")
894    }
895
896    /// `e^zeta Ai(x)` on the positive axis. See [`airy_ai_scaled`](Self::airy_ai_scaled).
897    #[inline(always)]
898    fn airy_ai_scaled<P: Policy>(self) -> Self {
899        todo!("airy_ai_scaled is not implemented for this composite type")
900    }
901
902    /// `Bi(x)` alone. See [`airy_bi`](Self::airy_bi).
903    #[inline(always)]
904    fn airy_bi<P: Policy>(self) -> Self {
905        todo!("airy_bi is not implemented for this composite type")
906    }
907
908    /// `e^-zeta Bi(x)` on the positive axis. See [`airy_bi_scaled`](Self::airy_bi_scaled).
909    #[inline(always)]
910    fn airy_bi_scaled<P: Policy>(self) -> Self {
911        todo!("airy_bi_scaled is not implemented for this composite type")
912    }
913
914    /// `Ai'(x)` alone. See [`airy_ai_prime`](Self::airy_ai_prime).
915    #[inline(always)]
916    fn airy_ai_prime<P: Policy>(self) -> Self {
917        todo!("airy_ai_prime is not implemented for this composite type")
918    }
919
920    /// `e^zeta Ai'(x)` on the positive axis. See
921    /// [`airy_ai_prime_scaled`](Self::airy_ai_prime_scaled).
922    #[inline(always)]
923    fn airy_ai_prime_scaled<P: Policy>(self) -> Self {
924        todo!("airy_ai_prime_scaled is not implemented for this composite type")
925    }
926
927    /// `Bi'(x)` alone. See [`airy_bi_prime`](Self::airy_bi_prime).
928    #[inline(always)]
929    fn airy_bi_prime<P: Policy>(self) -> Self {
930        todo!("airy_bi_prime is not implemented for this composite type")
931    }
932
933    /// `e^-zeta Bi'(x)` on the positive axis. See
934    /// [`airy_bi_prime_scaled`](Self::airy_bi_prime_scaled).
935    #[inline(always)]
936    fn airy_bi_prime_scaled<P: Policy>(self) -> Self {
937        todo!("airy_bi_prime_scaled is not implemented for this composite type")
938    }
939
940    #[inline(always)]
941    fn hermite_n<P: Policy, const N: usize>(mut x: Self) -> Self {
942        #[cfg(not(target_arch = "spirv"))]
943        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
944            x = new_x[0];
945        }
946
947        let mut p0 = Self::ONE;
948
949        if const { N == 0 } {
950            return p0;
951        }
952
953        let mut p1 = x + x; // 2 * x
954
955        cfg_if::cfg_if! {
956            if #[cfg(all(feature = "spirv", target_arch = "spirv"))] {
957                use crunchy::unroll;
958
959                macro_rules! unroll_poly {
960                    ($($len:tt),*) => {
961                        $( if const { N == $len } {
962                            unroll! { for n in 0..$len {
963                                (p0, p1) = (p1, p0); // swap p0, p1
964
965                                const cf: thermite::LargeInt = (1 + n) as thermite::LargeInt;
966                                let next0 = x.mul_sube(p0, p1.scale(E::ConstInt::<{cf}>::VALUE));
967                                p1 = next0 + next0; // 2 * next0
968                            }}
969                        } else )* {
970                            let mut c = 1;
971                            let mut cf = E::ONE;
972
973                            while c < N {
974                                (p0, p1) = (p1, p0); // swap p0, p1
975
976                                let next0 = x.mul_sube(p0, p1.scale(cf));
977                                p1 = next0 + next0; // 2 * next0
978
979                                c += 1;
980                                cf = cf + E::ONE;
981                            }
982                        }
983                    };
984                }
985
986                unroll_poly!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); // up to N=16
987            } else {
988                let mut c = 1;
989                let mut cf = Self::ONE;
990
991                while c < N {
992                    (p0, p1) = (p1, p0); // swap p0, p1
993
994                    let next0 = x.mul_sube(p0, cf * p1);
995                    p1 = next0 + next0; // 2 * next0
996
997                    c += 1;
998                    cf += Self::ONE;
999                }
1000            }
1001        }
1002
1003        p1
1004    }
1005
1006    #[inline(always)]
1007    fn hermitev<P: Policy>(mut x: Self, n: Self::Unsigned) -> Self {
1008        #[cfg(not(target_arch = "spirv"))]
1009        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
1010            x = new_x[0];
1011        }
1012
1013        let i1 = Self::Unsigned::ONE;
1014        let n_is_zero = n.cmp_eq(Self::Unsigned::ZERO);
1015
1016        let mut c = i1;
1017
1018        // count `n = c.to_float()` separately to avoid expensive converting every iteration
1019        let mut cf = Self::ONE;
1020
1021        let mut p0 = Self::ONE;
1022        let mut p1 = x + x; // 2 * x
1023
1024        loop {
1025            let cont = c.cmp_lt(n);
1026
1027            if cont.none() {
1028                break;
1029            }
1030
1031            // H_{k+1} = 2x H_k - 2k H_{k-1}
1032            let next0 = x.mul_sube(p1, cf * p0);
1033            let next = next0 + next0; // 2 * next0
1034
1035            // Freeze BOTH halves of the pair on lanes that have reached their own
1036            // degree. `hermite`'s unconditional (p0, p1) swap cannot be reused here:
1037            // on a retired lane it moves H_{k-1} into p1, and a select that only
1038            // guards p1 then preserves that instead of the lane's answer.
1039            p0 = cont.select(p1, p0);
1040            p1 = cont.select(next, p1);
1041
1042            c += i1;
1043            cf += Self::ONE;
1044        }
1045
1046        n_is_zero.select(Self::ONE, p1)
1047    }
1048
1049    /// A uniform runtime degree is [`hermitev`](Self::hermitev) with the degree splatted.
1050    /// Nothing cheaper is correct.
1051    #[inline(always)]
1052    fn hermite<P: Policy>(self, n: u32) -> Self {
1053        Self::hermitev::<P>(
1054            self,
1055            Self::splat(E::from_int(n as thermite::LargeInt)).to_unsigned_integer(),
1056        )
1057    }
1058
1059    #[inline(always)]
1060    fn hermite_function_n<P: Policy, const N: usize>(mut x: Self) -> Self {
1061        #[cfg(not(target_arch = "spirv"))]
1062        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
1063            x = new_x[0];
1064        }
1065
1066        generic::hermite::hermite_function_n::<P, _, _, N, false>(x)
1067    }
1068
1069    #[inline(always)]
1070    fn hermite_function<P: Policy>(mut x: Self, n: u32) -> Self {
1071        #[cfg(not(target_arch = "spirv"))]
1072        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
1073            x = new_x[0];
1074        }
1075
1076        generic::hermite::hermite_function::<P, _, _, false>(x, n)
1077    }
1078
1079    #[inline(always)]
1080    fn hermite_function_series_n<P: Policy, const N: usize>(self, coeffs: &[Self::Element; N]) -> Self {
1081        generic::hermite::hermite_function_series::<P, _, _, N, false>(self, coeffs)
1082    }
1083
1084    #[inline(always)]
1085    fn hermite_function_series<P: Policy>(self, coeffs: &[Self::Element]) -> Self {
1086        generic::hermite::hermite_function_series_slice::<P, _, _, false>(self, coeffs)
1087    }
1088
1089    #[inline(always)]
1090    fn laguerre_n<P: Policy, const N: usize>(mut x: Self, mut alpha: Self) -> Self {
1091        #[cfg(not(target_arch = "spirv"))]
1092        if let Some(new) = FlushDenormals::<P>::flush_denormals([x, alpha]) {
1093            x = new[0];
1094            alpha = new[1];
1095        }
1096
1097        if const { N == 0 } {
1098            return Self::ONE;
1099        }
1100
1101        let mut p0 = Self::ONE; // L_0 = 1
1102        let mut p1 = (Self::ONE + alpha) - x; // L_1 = 1 + a - x
1103
1104        let mut k = 1;
1105        let mut kf = Self::ONE; // k as a float, counted alongside to avoid a convert per step
1106
1107        while k < N {
1108            // (k+1) L_{k+1} = (2k + a + 1 - x) L_k - (k + a) L_{k-1}
1109            let b = ((kf + kf) + Self::ONE + alpha) - x;
1110            let c = kf + alpha;
1111
1112            let next = b.mul_sube(p1, c * p0) * Self::splat(laguerre_rcp::<E>(k));
1113
1114            p0 = p1;
1115            p1 = next;
1116
1117            k += 1;
1118            kf += Self::ONE;
1119        }
1120
1121        p1
1122    }
1123
1124    #[inline(always)]
1125    fn laguerrev<P: Policy>(mut x: Self, mut alpha: Self, n: Self::Unsigned) -> Self {
1126        #[cfg(not(target_arch = "spirv"))]
1127        if let Some(new) = FlushDenormals::<P>::flush_denormals([x, alpha]) {
1128            x = new[0];
1129            alpha = new[1];
1130        }
1131
1132        let i1 = Self::Unsigned::ONE;
1133        let n_is_zero = n.cmp_eq(Self::Unsigned::ZERO);
1134
1135        let mut c = i1;
1136
1137        let mut k = 1;
1138        let mut kf = Self::ONE;
1139
1140        let mut p0 = Self::ONE;
1141        let mut p1 = (Self::ONE + alpha) - x;
1142
1143        loop {
1144            let cont = c.cmp_lt(n);
1145
1146            if cont.none() {
1147                break;
1148            }
1149
1150            let b = ((kf + kf) + Self::ONE + alpha) - x;
1151            let ck = kf + alpha;
1152
1153            let next = b.mul_sube(p1, ck * p0) * Self::splat(laguerre_rcp::<E>(k));
1154
1155            // Freeze BOTH halves of the pair on lanes that have reached their own degree.
1156            // Carrying `p0` forward unconditionally would leave a finished lane holding
1157            // `L_{k-1}` in `p1` on the next step instead of its answer.
1158            p0 = cont.select(p1, p0);
1159            p1 = cont.select(next, p1);
1160
1161            c += i1;
1162            k += 1;
1163            kf += Self::ONE;
1164        }
1165
1166        n_is_zero.select(Self::ONE, p1)
1167    }
1168
1169    /// A uniform runtime degree is [`laguerrev`](Self::laguerrev) with the degree splatted.
1170    #[inline(always)]
1171    fn laguerre<P: Policy>(self, alpha: Self, n: u32) -> Self {
1172        Self::laguerrev::<P>(
1173            self,
1174            alpha,
1175            Self::splat(E::from_int(n as thermite::LargeInt)).to_unsigned_integer(),
1176        )
1177    }
1178
1179    #[inline(always)]
1180    fn laguerre_function_n<P: Policy, const N: usize>(mut x: Self, mut alpha: Self) -> Self {
1181        #[cfg(not(target_arch = "spirv"))]
1182        if let Some(new) = FlushDenormals::<P>::flush_denormals([x, alpha]) {
1183            x = new[0];
1184            alpha = new[1];
1185        }
1186
1187        generic::laguerre::laguerre_function_n::<P, _, _, N, false>(x, alpha, 0)
1188    }
1189
1190    #[inline(always)]
1191    fn laguerre_function<P: Policy>(mut x: Self, mut alpha: Self, n: u32) -> Self {
1192        #[cfg(not(target_arch = "spirv"))]
1193        if let Some(new) = FlushDenormals::<P>::flush_denormals([x, alpha]) {
1194            x = new[0];
1195            alpha = new[1];
1196        }
1197
1198        generic::laguerre::laguerre_function::<P, _, _, false>(x, alpha, 0, n)
1199    }
1200
1201    #[inline(always)]
1202    fn laguerre_function_i_n<P: Policy, const N: usize>(mut x: Self, alpha: i32) -> Self {
1203        #[cfg(not(target_arch = "spirv"))]
1204        if let Some(new) = FlushDenormals::<P>::flush_denormals([x]) {
1205            x = new[0];
1206        }
1207
1208        generic::laguerre::laguerre_function_n::<P, _, _, N, true>(x, Self::ZERO, alpha)
1209    }
1210
1211    #[inline(always)]
1212    fn laguerre_function_i<P: Policy>(mut x: Self, alpha: i32, n: u32) -> Self {
1213        #[cfg(not(target_arch = "spirv"))]
1214        if let Some(new) = FlushDenormals::<P>::flush_denormals([x]) {
1215            x = new[0];
1216        }
1217
1218        generic::laguerre::laguerre_function::<P, _, _, true>(x, Self::ZERO, alpha, n)
1219    }
1220
1221    #[inline(always)]
1222    fn poisson_pmf<P: Policy>(self, lambda: Self) -> Self {
1223        generic::poisson::poisson_pmf::<P, _, _, false>(self, lambda)
1224    }
1225
1226    #[inline(always)]
1227    fn poisson_log_pmf<P: Policy>(self, lambda: Self) -> Self {
1228        generic::poisson::poisson_pmf::<P, _, _, true>(self, lambda)
1229    }
1230
1231    #[inline(always)]
1232    fn laguerre_function_series_n<P: Policy, const N: usize>(self, alpha: Self, coeffs: &[Self::Element; N]) -> Self {
1233        generic::laguerre::laguerre_function_series::<P, _, _, N, false>(self, alpha, 0, coeffs)
1234    }
1235
1236    #[inline(always)]
1237    fn laguerre_function_series_i_n<P: Policy, const N: usize>(self, alpha: i32, coeffs: &[Self::Element; N]) -> Self {
1238        generic::laguerre::laguerre_function_series::<P, _, _, N, true>(self, Self::ZERO, alpha, coeffs)
1239    }
1240
1241    #[inline(always)]
1242    fn laguerre_function_series<P: Policy>(self, alpha: Self, coeffs: &[Self::Element]) -> Self {
1243        generic::laguerre::laguerre_function_series_slice::<P, _, _, false>(self, alpha, 0, coeffs)
1244    }
1245
1246    #[inline(always)]
1247    fn laguerre_function_series_i<P: Policy>(self, alpha: i32, coeffs: &[Self::Element]) -> Self {
1248        generic::laguerre::laguerre_function_series_slice::<P, _, _, true>(self, Self::ZERO, alpha, coeffs)
1249    }
1250
1251    #[inline(always)]
1252    fn chebyshev<P: Policy, const K: usize>(self, coeffs: &[Self::Element]) -> Self {
1253        // See `chebyshev_n`. Same default, same reason for `REINSCH = false`. `N = 0` is
1254        // the kernel's "length not known" sentinel, so this is the same body at a runtime
1255        // count rather than a second implementation of it.
1256        generic::chebyshev::chebyshev_series::<P, _, _, K, 0, false>(self, coeffs)
1257    }
1258
1259    #[inline(always)]
1260    fn chebyshev_n<P: Policy, const K: usize, const N: usize>(self, coeffs: &[Self::Element; N]) -> Self {
1261        // The kernel treats `N = 0` as "runtime length" and answers `V::ZERO` for it, so the
1262        // empty-series rejection has to live out here to stay a compile error. It was a
1263        // `const` assert inside the kernel before the const and slice bodies were merged.
1264        const {
1265            assert!(N >= 1, "chebyshev_n: N must be at least 1");
1266        }
1267
1268        // Plain Clenshaw. Real vectors override this in `ps.rs`/`pd.rs` to pass `true` for
1269        // the kernel's `REINSCH` parameter, which buys accuracy near `$x = \pm 1$` under a
1270        // `Best`-or-better policy; `Complex` and the composites take this default, since the
1271        // endpoint form needs a real `copysign` and a meaningful nearest endpoint.
1272        generic::chebyshev::chebyshev_series::<P, _, _, K, N, false>(self, coeffs)
1273    }
1274
1275    #[inline(always)]
1276    fn jacobi<P: Policy>(mut x: Self, mut alpha: Self, mut beta: Self, mut n: u32, m: u32) -> Self {
1277        if thermite::unlikely(m > n) {
1278            return Self::ZERO;
1279        }
1280
1281        #[cfg(not(target_arch = "spirv"))]
1282        if let Some(new) = FlushDenormals::<P>::flush_denormals([x, alpha, beta]) {
1283            x = new[0];
1284            alpha = new[1];
1285            beta = new[2];
1286        }
1287
1288        let mut scale = Self::ONE;
1289
1290        if m > 0 {
1291            let mut jf = Self::ONE;
1292            let nf = Self::splat(E::from_int(n as thermite::LargeInt));
1293
1294            let t0 = Self::HALF * (nf + alpha + beta);
1295
1296            let mut _iter = 0;
1297            while _iter < m {
1298                _iter += 1;
1299                scale *= Self::HALF.mul_adde(jf, t0);
1300                jf += Self::ONE;
1301            }
1302
1303            let mf = Self::splat(E::from_int(m as thermite::LargeInt));
1304
1305            alpha += mf;
1306            beta += mf;
1307            n -= m;
1308        }
1309
1310        if thermite::unlikely(n == 0) {
1311            return scale; // scale * one
1312        }
1313
1314        let mut y0 = Self::ONE;
1315
1316        let alpha_p_beta = alpha + beta;
1317        let alpha_sqr = alpha * alpha;
1318        let beta_sqr = beta * beta;
1319        let alpha1 = alpha - Self::ONE;
1320        let beta1 = beta - Self::ONE;
1321        let alpha2beta2 = alpha_sqr - beta_sqr;
1322
1323        //let mut y1 = alpha + 1 + 0.5 * (alpha_p_beta + 2) * (x - 1);
1324        let mut y1 = Self::HALF * (x.mul_adde(alpha, alpha) + x.mul_sube(beta, beta) + x + x);
1325
1326        let mut yk = y1;
1327        let mut k = E::ConstInt::<2>::VALUE;
1328
1329        let k_max = E::from_int(n as thermite::LargeInt) * (<E as Element>::ONE + E::EPSILON);
1330
1331        while k < k_max {
1332            let kf = Self::splat(k);
1333            let kf2 = Self::TWO * kf;
1334
1335            let k_alpha_p_beta = kf + alpha_p_beta;
1336            let k2_alpha_p_beta = kf2 + alpha_p_beta;
1337
1338            let k2_alpha_p_beta_m2 = k2_alpha_p_beta - Self::TWO;
1339
1340            let denom = kf2 * k_alpha_p_beta * k2_alpha_p_beta_m2;
1341            let t0 = x.mul_adde(k2_alpha_p_beta * k2_alpha_p_beta_m2, alpha2beta2);
1342            let gamma1 = k2_alpha_p_beta.mul_sube(t0, t0);
1343            let gamma0 = Self::TWO * (kf + alpha1) * (kf + beta1) * k2_alpha_p_beta;
1344
1345            yk = gamma1.mul_sube(y1, gamma0 * y0) / denom;
1346
1347            y0 = y1;
1348            y1 = yk;
1349
1350            k = k + <E as Element>::ONE;
1351        }
1352
1353        scale * yk
1354    }
1355
1356    #[inline(always)]
1357    fn gaussian<P: Policy>(mut x: Self, a: Self, c: Self) -> Self {
1358        #[cfg(not(target_arch = "spirv"))]
1359        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
1360            x = new_x[0];
1361        }
1362
1363        let xc = if const { P::POLICY.precision.le(PrecisionPolicy::Worst) } {
1364            x * c.approx_reciprocal_p::<P>()
1365        } else {
1366            x / c
1367        };
1368
1369        a * (-Self::HALF * xc * xc).exp_p::<P>()
1370    }
1371
1372    fn beta<P: Policy>(a: Self, b: Self) -> Self;
1373
1374    #[inline(always)]
1375    fn lbeta<P: Policy>(a: Self, b: Self) -> Self {
1376        // ln|B(a,b)| = ln|G(a)| + ln|G(b)| - ln|G(a+b)|. The log form is the only one with
1377        // the range to cover f32 arguments: the Gamma product overflows f64 past ~171
1378        // while B itself stays perfectly ordinary.
1379        Self::lgamma::<P>(a) + Self::lgamma::<P>(b) - Self::lgamma::<P>(a + b)
1380    }
1381
1382    #[inline(always)]
1383    fn logit<P: Policy>(self) -> Self {
1384        // ln(p) - ln(1 - p), with ln_1p carrying the second term so small p stays accurate.
1385        // Nothing can be done for p near 1 from this argument alone. See `logit_1m`.
1386        Self::ln::<P>(self) - Self::ln_1p::<P>(-self)
1387    }
1388
1389    #[inline(always)]
1390    fn logit_1m<P: Policy>(self) -> Self {
1391        // logit(1 - q) = ln(1 - q) - ln(q), in terms of the complement throughout. Here q is
1392        // the small quantity, so ln_1p is at its most accurate exactly where `logit` is worst.
1393        Self::ln_1p::<P>(-self) - Self::ln::<P>(self)
1394    }
1395
1396    #[inline(always)]
1397    fn planck<P: Policy>(self) -> Self {
1398        // x^3/(e^x - 1) = x^2 / phi_1(x). phi_1 is 1 at the origin, so the 0/0 of the direct
1399        // quotient never forms and the x^2 limit falls out on its own.
1400        (self * self).approx_div_p::<P>(Self::phi_n_p::<P, 1>(self))
1401    }
1402
1403    #[rustfmt::skip]
1404    #[inline(always)]
1405    fn legendre0<P: Policy, const N: u32>(x: Self, n: u32) -> Self {
1406        let x2 = x.square();
1407        let x4 = x2.square();
1408        let x8 = x4.square();
1409
1410        if const { N != 0 } {
1411            unsafe { core::hint::assert_unchecked(N == n); }
1412        }
1413
1414        // hand-tuned Estrin's scheme polynomials
1415        match n {
1416            1 => x,
1417            2 => x2.mul_adde(const_splat!(ratio <E>: 3 / 2), const_splat!(ratio <E>: -1 / 2)),
1418            3 => x * x2.mul_adde(const_splat!(ratio <E>: 5 / 2), const_splat!(ratio <E>: -3 / 2)),
1419            4 => x4.mul_adde(const_splat!(ratio <E>: 35 / 8), x2.mul_adde(const_splat!(ratio <E>: -15 / 4), const_splat!(ratio <E>: 3 / 8))),
1420            5 => x * x4.mul_adde(const_splat!(ratio <E>: 63 / 8), x2.mul_adde(const_splat!(ratio <E>: -35 / 4), const_splat!(ratio <E>: 15 / 8))),
1421            6 => x4.mul_adde(
1422                x2.mul_adde(const_splat!(ratio <E>: 231 / 16), const_splat!(ratio <E>: -315 / 16)),
1423                x2.mul_adde(const_splat!(ratio <E>: 105 / 16), const_splat!(ratio <E>: -5 / 16)),
1424            ),
1425            7 => x * x4.mul_adde(
1426                x2.mul_adde(const_splat!(ratio <E>: 429 / 16), const_splat!(ratio <E>: -693 / 16)),
1427                x2.mul_adde(const_splat!(ratio <E>: 315 / 16), const_splat!(ratio <E>: -35 / 16)),
1428            ),
1429            8 => x8.mul_adde(const_splat!(ratio <E>: 6435 / 128), x4.mul_adde(
1430                x2.mul_adde(const_splat!(ratio <E>: -3003 / 32), const_splat!(ratio <E>: 3465 / 64)),
1431                x2.mul_adde(const_splat!(ratio <E>: -315 / 32), const_splat!(ratio <E>: 35 / 128)),
1432            )),
1433            9 => x * x8.mul_adde(const_splat!(ratio <E>: 12155 / 128), x4.mul_adde(
1434                x2.mul_adde(const_splat!(ratio <E>: -6435 / 32), const_splat!(ratio <E>: 9009 / 64)),
1435                x2.mul_adde(const_splat!(ratio <E>: -1155 / 32), const_splat!(ratio <E>: 315 / 128)),
1436            )),
1437            10 => x8.mul_adde(
1438                x2.mul_adde(const_splat!(ratio <E>: 46189 / 256), const_splat!(ratio <E>: -109395 / 256)),
1439                x4.mul_adde(
1440                    x2.mul_adde(const_splat!(ratio <E>: 45045 / 128), const_splat!(ratio <E>: -15015 / 128)),
1441                    x2.mul_adde(const_splat!(ratio <E>: 3465 / 256), const_splat!(ratio <E>: -63 / 256)),
1442                ),
1443            ),
1444            11 => x * x8.mul_adde(
1445                x2.mul_adde(const_splat!(ratio <E>: 88179 / 256), const_splat!(ratio <E>: -230945 / 256)),
1446                x4.mul_adde(
1447                    x2.mul_adde(const_splat!(ratio <E>: 109395 / 128), const_splat!(ratio <E>: -45045 / 128)),
1448                    x2.mul_adde(const_splat!(ratio <E>: 15015 / 256), const_splat!(ratio <E>: -693 / 256)),
1449                ),
1450            ),
1451            12 => x8.mul_adde(
1452                x4.mul_adde(const_splat!(ratio <E>: 676039 / 1024), x2.mul_adde(const_splat!(ratio <E>: -969969 / 512), const_splat!(ratio <E>: 2078505 / 1024))),
1453                x4.mul_adde(
1454                    x2.mul_adde(const_splat!(ratio <E>: -255255 / 256), const_splat!(ratio <E>: 225225 / 1024)),
1455                    x2.mul_adde(const_splat!(ratio <E>: -9009 / 512), const_splat!(ratio <E>: 231 / 1024)),
1456                ),
1457            ),
1458            13 => x * x8.mul_adde(
1459                x4.mul_adde(const_splat!(ratio <E>: 1300075 / 1024), x2.mul_adde(const_splat!(ratio <E>: -2028117 / 512), const_splat!(ratio <E>: 4849845 / 1024))),
1460                x4.mul_adde(
1461                    x2.mul_adde(const_splat!(ratio <E>: -692835 / 256), const_splat!(ratio <E>: 765765 / 1024)),
1462                    x2.mul_adde(const_splat!(ratio <E>: -45045 / 512), const_splat!(ratio <E>: 3003 / 1024)),
1463                ),
1464            ),
1465            _ => unsafe { core::hint::unreachable_unchecked() },
1466        }
1467    }
1468
1469    #[inline(always)]
1470    fn legendre<P: Policy>(mut x: Self, n: u32, m: u32) -> Self {
1471        #[cfg(not(target_arch = "spirv"))]
1472        if let Some(new_x) = FlushDenormals::<P>::flush_denormals([x]) {
1473            x = new_x[0];
1474        }
1475
1476        match (n, m) {
1477            (0, 0) => return Self::ONE,
1478            (n, 0) if n < 14 => return Self::legendre0::<P, 0>(x, n),
1479            (n, 0) => {
1480                let mut k = 14; // set to max degree hard-coded + 1
1481
1482                // these should inline
1483                let mut p0 = Self::legendre0::<P, 12>(x, 12); // n = k - 2
1484                let mut p1 = Self::legendre0::<P, 13>(x, 13); // n = k - 1
1485
1486                while k <= n {
1487                    let nf = Self::splat(E::from_int(k as thermite::LargeInt));
1488
1489                    let tmp = p1;
1490                    p1 = x.mul_sube((nf + nf).mul_sube(p1, p1), nf.mul_sube(p0, p0)) / nf;
1491                    p0 = tmp;
1492
1493                    k += 1;
1494                }
1495
1496                return p1;
1497            }
1498            _ => {}
1499        }
1500
1501        let jacobi = Self::jacobi::<P>(x, Self::ZERO, Self::ZERO, n, m);
1502
1503        let x12 = x.nmul_adde(x, Self::ONE); // (1 - x^2)
1504
1505        if m & 1 == 0 {
1506            jacobi * Self::powi::<P>(x12, (m >> 1) as i32)
1507        } else {
1508            // negate sign for odd powers (-1)^m
1509            -jacobi * Self::powi::<P>(x12, m as i32).sqrt()
1510        }
1511    }
1512
1513    #[inline(always)]
1514    fn legendre_series_n<P: Policy, const N: usize>(self, coeffs: &[Self::Element; N]) -> Self {
1515        // Plain Clenshaw at every policy, as the kernel has no policy-dependent path.
1516        generic::legendre::legendre_series::<_, _, N>(self, coeffs)
1517    }
1518
1519    #[inline(always)]
1520    fn legendre_series<P: Policy>(self, coeffs: &[Self::Element]) -> Self {
1521        generic::legendre::legendre_series_slice::<_, _>(self, coeffs)
1522    }
1523
1524    #[inline(always)]
1525    fn zernike_r<P: Policy>(mut rho: Self, n: u32, m: u32) -> Self {
1526        // A mode that does not exist contributes nothing, rather than whatever a
1527        // recurrence run outside its range happens to produce.
1528        if thermite::unlikely(m > n || (n - m) & 1 == 1) {
1529            return Self::ZERO;
1530        }
1531
1532        #[cfg(not(target_arch = "spirv"))]
1533        if let Some(new) = FlushDenormals::<P>::flush_denormals([rho]) {
1534            rho = new[0];
1535        }
1536
1537        // R_n^m(rho) = rho^m * Q_{(n-m)/2, m}(rho^2), the shifted Jacobi identity with the
1538        // change of variable folded into the recurrence coefficients. See the trait docs
1539        // for why this rather than the direct factorial sum, and where the usual (-1)^k
1540        // prefactor went; `reduced_radial_impl` for why not a general `jacobi` call.
1541        let radial = generic::zernike::reduced_radial_impl::<E, Self>(rho.square(), (n - m) >> 1, m);
1542
1543        if m == 0 {
1544            radial
1545        } else {
1546            radial * Self::powi::<P>(rho, m as i32)
1547        }
1548    }
1549
1550    #[inline(always)]
1551    fn zernike<P: Policy, const NORM: u8>(rho: Self, theta: Self, n: u32, m: i32) -> Self {
1552        const {
1553            assert!(
1554                NORM == crate::ZERNIKE_UNIT_PEAK || NORM == crate::ZERNIKE_ORTHONORMAL,
1555                "zernike: NORM must be ZERNIKE_UNIT_PEAK or ZERNIKE_ORTHONORMAL"
1556            );
1557        }
1558
1559        let am = m.unsigned_abs();
1560
1561        let radial = Self::zernike_r::<P>(rho, n, am);
1562
1563        let z = if m == 0 {
1564            radial // cos(0) = 1
1565        } else {
1566            let (sin, cos) = Self::sin_cos::<P>(theta * Self::splat(E::from_int(am as thermite::LargeInt)));
1567
1568            radial * if m > 0 { cos } else { sin }
1569        };
1570
1571        if const { NORM == crate::ZERNIKE_UNIT_PEAK } {
1572            return z;
1573        }
1574
1575        // N_n^m = sqrt(2(n+1) / (1 + delta_{m,0})). Both the radicand and the root are
1576        // exact in the element type for any n a pupil fit will reach, and n is
1577        // loop-invariant, so this is a splat of a constant rather than a vector sqrt.
1578        let radicand = if m == 0 { n + 1 } else { 2 * (n + 1) };
1579
1580        z * Self::splat(FloatElement::sqrt(E::from_int(radicand as thermite::LargeInt)))
1581    }
1582
1583    #[inline(always)]
1584    fn zernike_basis<P: Policy, const L: usize, const NORM: u8, const N: usize>(x: Self, y: Self, out: &mut [Self; N]) {
1585        generic::zernike::zernike_basis_impl::<P, E, Self, L, NORM, N>(x, y, out);
1586    }
1587
1588    fn lambert_w<P: Policy>(self) -> (Self, Self);
1589
1590    // TEMP(bessel_j): disabled until orders beyond J_0 exist. See the note in lib.rs.
1591    //fn bessel_j<P: Policy, const N: i32>(self) -> Self;
1592
1593    #[inline(always)]
1594    fn phi_n<P: Policy, const N: usize>(self) -> Self {
1595        // Element-agnostic form: the series arm runs until it converges to
1596        // `Self::EPSILON`, capped by the policy's iteration budget. The f32/f64
1597        // backends override this with a compile-time term count.
1598        generic::phi::phi_internal_n::<Self, E, P, N, true>(self, P::POLICY.max_iterations)
1599    }
1600
1601    /// The runtime-order twin of [`phi_n`](Self::phi_n). The f32/f64 backends override it with
1602    /// a term count worked out from `n` per call.
1603    #[inline(always)]
1604    fn phi<P: Policy>(self, n: u32) -> Self {
1605        generic::phi::phi_internal::<Self, E, P, true>(self, n, P::POLICY.max_iterations)
1606    }
1607}
1608
1609// The Carlson / Legendre entry points are kind-dispatched (`SpecialMath::carlson` / `::ellint`),
1610// generated by decl_math!'s `@kinds` blocks. They call the request struct's `eval` directly, so
1611// they need no method here. The request structs and their traits are re-exported below.
1612// `EllipticConsts` is re-exported because `EllipticKind` is bounded on it: any
1613// generic caller of `ellint`/`carlson` has to name it in a where-clause, so
1614// leaving it unreachable made those two functions uncallable from generic code.
1615pub use generic::elliptic::{
1616    CarlsonKind, CarlsonRc, CarlsonRd, CarlsonRf, CarlsonRg, CarlsonRj, EllintD, EllintDInc, EllintE, EllintEInc,
1617    EllintF, EllintK, EllintPi, EllintPiInc, EllipticConsts, EllipticKind, HeumanLambda, JacobiZeta, WrapTo,
1618};
1619
1620// The spherical-harmonic kernels, ahead of their `RealSpecialMath` wiring. Re-exported
1621// the same way as the elliptic internals: the tables trait must be nameable by generic
1622// callers, and the tests drive the kernels through this path.
1623pub use generic::sh::{
1624    MAX_DEGREE as MAX_SH_DEGREE, ShConsts, ShTable, sh_d_impl, sh_eval_d_impl, sh_eval_impl, sh_eval_lifted_impl,
1625    sh_eval_mixed_impl, sh_impl, sh_table_impl,
1626};
1627
1628// The batch Zernike kernel's unrolled-degree cap, named in `zernike_basis`'s docs as the
1629// point past which it stops being straight-line code.
1630pub use generic::zernike::{MAX_DEGREE as MAX_ZERNIKE_DEGREE, zernike_basis_d_impl, zernike_basis_impl};
1631
1632// The zeta kernel's per-element constants and its tier table. `thermite-complex` runs the
1633// same Euler-Maclaurin expansion in complex arithmetic and needs both: the constants are the
1634// base-2 logarithms of the primes under N, which are properties of the _real_ element even
1635// when the argument is complex.
1636pub use generic::zeta::{ZetaConsts, bernoulli_terms as zeta_bernoulli_terms};
1637
1638/// The polylogarithm's per-call order plan and region constants, for thermite-complex's
1639/// kernel. Not a stable surface.
1640#[doc(hidden)]
1641pub use generic::polylog::{
1642    KMAX as POLYLOG_KMAX, PolylogElement, PolylogPlan, root_count as polylog_root_count, t1 as polylog_t1,
1643};
1644
1645// The Landen ladder itself. Composite types override `jacobi_elliptic` to avoid
1646// differentiating it (see the trait method's docs) but still need to reach it for the case
1647// their shortcut does not cover: a dual-valued _modulus_, whose derivative is not a
1648// product of the triple.
1649pub use generic::jacobi_elliptic::{NMAX as JACOBI_NMAX, jacobi_elliptic as jacobi_elliptic_impl};
1650
1651// `C' = cos(pi x^2/2)` and `S' = sin(pi x^2/2)` are the definition of the pair, so the
1652// autodiff rule needs the same exactly-reduced phase the kernel uses, and needs it for
1653// the same reason, the derivative being a full-amplitude oscillation where the values
1654// have settled to 1/2.
1655pub use generic::fresnel::phase_half_x2 as fresnel_phase;
1656
1657/// Specialized implementation trait for real-only special math functions.
1658///
1659/// Extends [`SpecializedSpecialMath`] with functions that have no meaningful
1660/// complex analogue (e.g. functions using the real absolute value, or functions
1661/// that are inverses of real-domain-only operations).
1662pub trait SpecializedRealSpecialMath<E>: SpecializedSpecialMath<E> {
1663    fn erfinv<P: Policy>(self) -> Self;
1664    fn probit<P: Policy>(self) -> Self;
1665
1666    /// `erfc(-x/sqrt 2)/2`, the standard normal CDF.
1667    #[inline(always)]
1668    fn ndtr<P: Policy>(self) -> Self {
1669        generic::ndtr::ndtr_impl::<P, _, _>(self)
1670    }
1671
1672    /// `ln(ndtr(x))`, finite wherever `x` is: `ln(erfc)` in the moderate region, `erfcx`
1673    /// with `-x^2/2` kept in the log domain in the tail, `ln_1p` of the complement on the
1674    /// right. See `generic::ndtr`. Element types without a Weideman table
1675    /// (`Compensated`) inherit their direct `erfcx`'s range, about `|x| < 37`.
1676    #[inline(always)]
1677    fn log_ndtr<P: Policy>(self) -> Self {
1678        generic::ndtr::log_ndtr_impl::<P, _, _>(self)
1679    }
1680
1681    /// `ln(erfc(x))` on the same construction as [`log_ndtr`](Self::log_ndtr), with the
1682    /// tail on the right and `ln_1p(+-erf(|x|))` on the bounded side.
1683    #[inline(always)]
1684    fn logerfc<P: Policy>(self) -> Self {
1685        generic::ndtr::logerfc_impl::<P, _, _>(self)
1686    }
1687
1688    /// `(ln ndtr(x), phi(x)/ndtr(x))`, the value with the inverse Mills ratio, which is its
1689    /// derivative. What [`inv_log_ndtr`](Self::inv_log_ndtr)'s Newton and `Dual` both need.
1690    #[inline(always)]
1691    fn log_ndtr_with_deriv<P: Policy>(self) -> (Self, Self) {
1692        generic::ndtr::log_ndtr_with_deriv_impl::<P, _, _, true>(self)
1693    }
1694
1695    /// The `x` with `ln ndtr(x) = y`. Newton on [`log_ndtr`](Self::log_ndtr).
1696    #[inline(always)]
1697    fn inv_log_ndtr<P: Policy>(self) -> Self {
1698        generic::ndtr::inv_log_ndtr_impl::<P, _, _>(self)
1699    }
1700
1701    /// The `x > 0` with `digamma(x) = y`. Newton on `digamma` with `trigamma`, and the
1702    /// Stirling fixed point above `y = 6`.
1703    #[inline(always)]
1704    fn inv_digamma<P: Policy>(self) -> Self {
1705        generic::inverses::inv_digamma_impl::<P, _, _>(self)
1706    }
1707
1708    /// The `w > 0` with `w + ln w = x`. Newton, and the Lagrange series below `x = -7`.
1709    #[inline(always)]
1710    fn wright_omega<P: Policy>(self) -> Self {
1711        generic::inverses::wright_omega_impl::<P, _, _>(self)
1712    }
1713
1714    /// `(S(x), C(x))`, the Fresnel integrals. See `generic::fresnel`.
1715    ///
1716    /// The coefficient tables are per-element, so the `ps`/`pd` impls supply them and
1717    /// every other type gets this default. `Dual` overrides it with the closed-form
1718    /// derivatives `C' = cos(pi x^2/2)`, `S' = sin(pi x^2/2)`.
1719    #[inline(always)]
1720    fn fresnel<P: Policy>(self) -> (Self, Self) {
1721        todo!("fresnel is not implemented for this composite type")
1722    }
1723
1724    /// `(Si(x), Ci(x))`, the trigonometric integrals. See `generic::sici`.
1725    ///
1726    /// Same shape as [`fresnel`](Self::fresnel): per-element tables in `ps`/`pd`, and
1727    /// `Dual` differentiates by `Si' = sin(x)/x`, `Ci' = cos(x)/x`.
1728    #[inline(always)]
1729    fn sici<P: Policy>(self) -> (Self, Self) {
1730        todo!("sici is not implemented for this composite type")
1731    }
1732
1733    /// `C(x)` alone. Unlike the Airy singles this is genuinely the pair with one half
1734    /// dead: the two share the argument reduction, the phase and both auxiliaries, so
1735    /// only one Chebyshev series and one reconstruction fall out. They are pure, so
1736    /// they do fall out.
1737    #[inline(always)]
1738    fn fresnel_c<P: Policy>(self) -> Self {
1739        Self::fresnel::<P>(self).1
1740    }
1741
1742    /// `S(x)` alone. See [`fresnel_c`](Self::fresnel_c).
1743    #[inline(always)]
1744    fn fresnel_s<P: Policy>(self) -> Self {
1745        Self::fresnel::<P>(self).0
1746    }
1747
1748    /// `Si(x)` alone. See [`fresnel_c`](Self::fresnel_c) for what is and is not saved.
1749    #[inline(always)]
1750    fn sinint<P: Policy>(self) -> Self {
1751        Self::sici::<P>(self).0
1752    }
1753
1754    /// `Ci(x)` alone. See [`fresnel_c`](Self::fresnel_c).
1755    #[inline(always)]
1756    fn cosint<P: Policy>(self) -> Self {
1757        Self::sici::<P>(self).1
1758    }
1759
1760    /// `I_nu(x) / I_{nu-1}(x)`, the vMF mean resultant length. See `generic::bessel_ratio`.
1761    ///
1762    /// The kernel reaches the Bessel continued fraction, which pins `Primal = Self`, so the
1763    /// `ps`/`pd` impls supply it at the concrete element the way `bessel_iv` is. `Dual`
1764    /// overrides through its inner vector, and any other composite is a `todo!()`.
1765    #[inline(always)]
1766    fn bessel_i_ratio<P: Policy>(self, _nu: Self) -> Self {
1767        todo!("bessel_i_ratio is not implemented for this composite type")
1768    }
1769
1770    /// The `kappa` with `I_nu(kappa) / I_{nu-1}(kappa) = r`. Newton on the ratio. Same
1771    /// arrangement as [`bessel_i_ratio`](Self::bessel_i_ratio).
1772    #[inline(always)]
1773    fn inv_bessel_i_ratio<P: Policy>(self, _nu: Self) -> Self {
1774        todo!("inv_bessel_i_ratio is not implemented for this composite type")
1775    }
1776
1777    /// `1 - I_nu(x) / I_{nu-1}(x)`, accurate where the ratio is within an ulp of 1.
1778    #[inline(always)]
1779    fn bessel_i_ratio_1m<P: Policy>(self, _nu: Self) -> Self {
1780        todo!("bessel_i_ratio_1m is not implemented for this composite type")
1781    }
1782
1783    /// The `kappa` with `1 - I_nu(kappa) / I_{nu-1}(kappa) = t`, the complement form.
1784    #[inline(always)]
1785    fn inv_bessel_i_ratio_1m<P: Policy>(self, _nu: Self) -> Self {
1786        todo!("inv_bessel_i_ratio_1m is not implemented for this composite type")
1787    }
1788
1789    // ---- marker-selected ratio entries ---------------------------------------------------
1790    //
1791    // `bessel::ratio::<F>(nu)` and its three companions route through the family marker to
1792    // the per-family hooks above (`I` today). Trait methods only because the forwarder calls
1793    // every public entry through this trait. Nothing overrides them.
1794
1795    /// `bessel::ratio::<F>(nu)`: see [`BesselRatioFamily`](crate::bessel::BesselRatioFamily).
1796    #[inline(always)]
1797    fn bessel_ratio<P: Policy, F: crate::bessel::BesselRatioFamily>(self, nu: Self) -> Self {
1798        F::ratio::<P, E, Self>(self, nu)
1799    }
1800
1801    /// `inv_bessel::ratio::<F>(r)`.
1802    #[inline(always)]
1803    fn inv_bessel_ratio<P: Policy, F: crate::bessel::BesselRatioFamily>(self, nu: Self) -> Self {
1804        F::inv_ratio::<P, E, Self>(self, nu)
1805    }
1806
1807    /// `bessel_ratio_1m::<F>(nu)`.
1808    #[inline(always)]
1809    fn bessel_ratio_1m<P: Policy, F: crate::bessel::BesselRatioFamily>(self, nu: Self) -> Self {
1810        F::ratio_1m::<P, E, Self>(self, nu)
1811    }
1812
1813    /// `inv_bessel_ratio_1m::<F>(t)`.
1814    #[inline(always)]
1815    fn inv_bessel_ratio_1m<P: Policy, F: crate::bessel::BesselRatioFamily>(self, nu: Self) -> Self {
1816        F::inv_ratio_1m::<P, E, Self>(self, nu)
1817    }
1818
1819    /// `(x_k, w_k)` of the `n`-point Gauss-Legendre rule, the index `k` per lane. See
1820    /// `generic::quadrature`.
1821    #[inline(always)]
1822    fn gauss_legendre<P: Policy>(self, n: u32) -> (Self, Self) {
1823        generic::quadrature::gauss_legendre_impl::<P, _, _>(self, n)
1824    }
1825
1826    /// `(x_k, w_k)` of the `n`-point Gauss-Hermite rule, the index `k` per lane.
1827    #[inline(always)]
1828    fn gauss_hermite<P: Policy>(self, n: u32) -> (Self, Self) {
1829        generic::quadrature::gauss_hermite_impl::<P, _, _>(self, n)
1830    }
1831
1832    /// `(x_k, w_k)` of the `n`-point Gauss-Laguerre rule with weight `x^alpha e^{-x}`, the
1833    /// index `k` and `alpha` per lane.
1834    #[inline(always)]
1835    fn gauss_laguerre<P: Policy>(self, alpha: Self, n: u32) -> (Self, Self) {
1836        generic::quadrature::gauss_laguerre_impl::<P, _, _>(self, alpha, n)
1837    }
1838
1839    /// `AGM(a, b)`, sharing its recurrence with the complete elliptic integrals.
1840    #[inline(always)]
1841    fn agm<P: Policy>(a: Self, b: Self) -> Self {
1842        generic::elliptic::agm::<P, _, _>(a, b)
1843    }
1844
1845    /// `zeta(s) - 1`, the primitive of the pair: the Euler-Maclaurin sum's leading term _is_
1846    /// the 1, so omitting it is exact where subtracting it afterwards is not.
1847    ///
1848    /// The kernel needs `E: BernoulliNumbers` and its own `ZetaConsts`, which a generic `E` on
1849    /// this trait does not carry, the same bind [`polygamma`](SpecializedSpecialMath::polygamma) is in. The
1850    /// `ps`/`pd` impls override this at the concrete element. The default here is what a
1851    /// composite gets until it supplies its own.
1852    ///
1853    /// `(z)_m = Gamma(z+m)/Gamma(z)`, by exact product where `m` is a small integer and by
1854    /// the Stirling difference otherwise. Never forms `lgamma(z+m) - lgamma(z)` except in
1855    /// the residual region where nothing else applies.
1856    #[inline(always)]
1857    fn pochhammer<P: Policy>(z: Self, m: Self) -> Self {
1858        generic::pochhammer::pochhammer::<P, _, Self>(z, m)
1859    }
1860
1861    /// `(sn, cn, dn)` by the arithmetic-only descending Landen transformation.
1862    ///
1863    /// Composite types that carry derivatives override this: the triple is closed under
1864    /// `d/du`, so the derivative components are products of the values and there is no
1865    /// reason to differentiate the ladder itself.
1866    #[inline(always)]
1867    fn jacobi_elliptic<P: Policy>(u: Self, k: Self) -> (Self, Self, Self) {
1868        generic::jacobi_elliptic::jacobi_elliptic::<P, _, _>(u, k)
1869    }
1870
1871    /// `(x^lambda - 1)/lambda`, `ln x` at `lambda = 0`.
1872    ///
1873    /// `powf_m1` builds `x^lambda - 1` without forming `x^lambda`, so the division by lambda
1874    /// is the *whole* algorithm: there is no cancellation left to protect against and hence
1875    /// no near-zero series, which the obvious `(pow(x, l) - 1)/l` spelling would need by
1876    /// `l = 1e-8`. Verified a few ulp from `lambda = 1e-300` outward.
1877    ///
1878    /// `lambda` is a fitted parameter, so it is uniform across a vector in every real use and
1879    /// the two uniform branches are what actually run. The blend is there for correctness on
1880    /// a mixed vector, not for speed.
1881    /// The domain edge `x = 0` needs no guard here. `powf_m1(0, lambda)` is `-1` for
1882    /// `lambda > 0` and `+inf` below, so the division delivers the conventional `-1/lambda` and
1883    /// `-inf` on its own, and more accurately than a `reciprocal` would. The Best-tier Dekker
1884    /// residual in `powf_m1` carries the edge itself, so nothing is patched up here.
1885    #[inline(always)]
1886    fn boxcox<P: Policy>(self, lambda: Self) -> Self {
1887        let at_zero = lambda.is_zero();
1888
1889        if const { !P::POLICY.avoid_branching } && at_zero.all() {
1890            Self::ln::<P>(self)
1891        } else if const { !P::POLICY.avoid_branching } && at_zero.none() {
1892            Self::powf_m1::<P>(self, lambda) / lambda
1893        } else {
1894            at_zero.select(Self::ln::<P>(self), Self::powf_m1::<P>(self, lambda) / lambda)
1895        }
1896    }
1897
1898    /// `((1 + x)^lambda - 1)/lambda`, `ln(1 + x)` at `lambda = 0`.
1899    ///
1900    /// Structurally identical to [`boxcox`](Self::boxcox), over `compound_m1` instead of
1901    /// `powf_m1` so that `x` near zero keeps its low bits, which is the only reason to have
1902    /// it, and the reason Yeo-Johnson is built on it.
1903    #[inline(always)]
1904    fn boxcox_1p<P: Policy>(self, lambda: Self) -> Self {
1905        let at_zero = lambda.is_zero();
1906
1907        if const { !P::POLICY.avoid_branching } && at_zero.all() {
1908            Self::ln_1p::<P>(self)
1909        } else if const { !P::POLICY.avoid_branching } && at_zero.none() {
1910            Self::compound_m1::<P>(self, lambda) / lambda
1911        } else {
1912            at_zero.select(Self::ln_1p::<P>(self), Self::compound_m1::<P>(self, lambda) / lambda)
1913        }
1914    }
1915
1916    /// `(lambda*y + 1)^(1/lambda)`, `e^y` at `lambda = 0`. The inverse of
1917    /// [`boxcox`](Self::boxcox).
1918    ///
1919    /// Evaluated as `exp(ln1p(lambda*y)/lambda)` rather than `powf`:
1920    /// `lambda*y` is small exactly where the forward transform's `lambda` is, so `1 + lambda*y`
1921    /// would round it away and the whole reason `boxcox` is accurate near `lambda = 0` would
1922    /// be undone on the way back.
1923    #[inline(always)]
1924    fn inv_boxcox<P: Policy>(self, lambda: Self) -> Self {
1925        let at_zero = lambda.is_zero();
1926
1927        if const { !P::POLICY.avoid_branching } && at_zero.all() {
1928            Self::exp::<P>(self)
1929        } else if const { !P::POLICY.avoid_branching } && at_zero.none() {
1930            Self::exp::<P>(Self::ln_1p::<P>(lambda * self) / lambda)
1931        } else {
1932            at_zero.select(
1933                Self::exp::<P>(self),
1934                Self::exp::<P>(Self::ln_1p::<P>(lambda * self) / lambda),
1935            )
1936        }
1937    }
1938
1939    /// `(lambda*y + 1)^(1/lambda) - 1`, `e^y - 1` at `lambda = 0`. The inverse of
1940    /// [`boxcox_1p`](Self::boxcox_1p).
1941    ///
1942    /// Same exponent as [`inv_boxcox`](Self::inv_boxcox) with `expm1` outside it, so the
1943    /// result keeps its relative accuracy where it is near zero, which, this being the
1944    /// inverse of a transform of data centered near zero, is the ordinary case.
1945    #[inline(always)]
1946    fn inv_boxcox_1p<P: Policy>(self, lambda: Self) -> Self {
1947        let at_zero = lambda.is_zero();
1948
1949        if const { !P::POLICY.avoid_branching } && at_zero.all() {
1950            Self::exp_m1::<P>(self)
1951        } else if const { !P::POLICY.avoid_branching } && at_zero.none() {
1952            Self::exp_m1::<P>(Self::ln_1p::<P>(lambda * self) / lambda)
1953        } else {
1954            at_zero.select(
1955                Self::exp_m1::<P>(self),
1956                Self::exp_m1::<P>(Self::ln_1p::<P>(lambda * self) / lambda),
1957            )
1958        }
1959    }
1960
1961    /// The Yeo-Johnson transform of `y = self` with parameter `lambda`.
1962    ///
1963    /// Four cases in the literature, one kernel here: the transform is odd about the origin
1964    /// in the sense that the `y < 0` branch is the `y >= 0` branch applied to `|y|` with
1965    /// `lambda` reflected to `2 - lambda` and the result negated. Folding the sign out first
1966    /// collapses both `ln` special cases (`lambda = 0` above zero, `lambda = 2` below) into
1967    /// the single `lambda = 0` seam that [`boxcox_1p`](Self::boxcox_1p) already handles.
1968    #[inline(always)]
1969    fn yeo_johnson<P: Policy>(self, lambda: Self) -> Self {
1970        let neg = self.cmp_lt(Self::ZERO);
1971        let reflected = neg.select(Self::TWO - lambda, lambda);
1972        let r = Self::boxcox_1p::<P>(self.abs(), reflected);
1973
1974        r.neg_c(neg)
1975    }
1976
1977    /// The inverse Yeo-Johnson transform. The same sign fold as
1978    /// [`yeo_johnson`](Self::yeo_johnson), over [`inv_boxcox_1p`](Self::inv_boxcox_1p).
1979    ///
1980    /// The transform is monotone increasing and fixes the origin, so the branch condition on
1981    /// the way back is the sign of the *transformed* value, which is the sign of `y`.
1982    #[inline(always)]
1983    fn inv_yeo_johnson<P: Policy>(self, lambda: Self) -> Self {
1984        let neg = self.cmp_lt(Self::ZERO);
1985        let reflected = neg.select(Self::TWO - lambda, lambda);
1986        let r = Self::inv_boxcox_1p::<P>(self.abs(), reflected);
1987
1988        r.neg_c(neg)
1989    }
1990
1991    fn langevin<P: Policy>(self) -> Self;
1992    fn inv_langevin<P: Policy>(self) -> Self;
1993    fn langevin_1m<P: Policy>(self) -> Self;
1994    fn inv_langevin_1m<P: Policy>(self) -> Self;
1995
1996    #[inline(always)]
1997    fn gelu<P: Policy>(self, alpha: Self) -> Self {
1998        // GELU(x) = 0.5 * x * (1 + erf(ax / sqrt(2))) = 0.5 * x * erfc(-ax / sqrt(2))
1999        //
2000        // The `erfc` spelling is load-bearing, not cosmetic. `1 + erf(u)` cancels
2001        // *completely* in the left tail: erf(-4.24) rounds to exactly -1 in float32, so
2002        // `0.5x + 0.5x*erf` evaluates 3 - 3 and `gelu(-6.0)` came back **+0.0** where the
2003        // answer is -5.92e-9, twelve orders out and the wrong sign of zero besides.
2004        // `erfc(4.24) = 1.97e-9` carries every digit. Float32 at `precision` returned
2005        // exactly zero for every x below about -5, and f64 the same below about -8.3. The
2006        // identity is exact, so nothing is traded for it.
2007        //
2008        // It is also cheaper: `erfc` and `erf` are the same kernel behind a const flag,
2009        // and the FMA-vs-not split this replaced existed only to fold the `1 +`.
2010        let c = (-(alpha * self)).scale(FloatConsts::FRAC_1_SQRT_2).erfc_p::<P>();
2011
2012        self.scale(E::ConstRatio::<1, 2>::VALUE) * c
2013    }
2014
2015    #[inline(always)]
2016    fn swish<P: Policy>(self, beta: Self) -> Self {
2017        let x = self;
2018        let beta_x = beta * x;
2019
2020        // sigmoid(beta * x) = 1 / (1 + exp(-beta * x))
2021        let e = (-beta_x).exp_p::<P>();
2022        let s = (Self::ONE + e).approx_reciprocal_p::<P>();
2023
2024        x * s
2025    }
2026
2027    fn lgamma_r<P: Policy>(self) -> (Self, Self);
2028
2029    #[inline(always)]
2030    fn algebraic_sigmoid_n<P: Policy, const N: usize>(self) -> Self {
2031        if const { N == 0 } {
2032            return self; // identity function
2033        }
2034
2035        let pre_root = Self::ONE + self.abs().powi_p::<P>(N as i32); // = 1 + |x|^N
2036
2037        let denom = match N {
2038            1 => pre_root,
2039            2 => pre_root.sqrt(),
2040            3 => pre_root.cbrt_p::<P>(),
2041            4 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } => pre_root.sqrt().sqrt(),
2042            _ => {
2043                // copied from `nth_root`, but without negative handling since we know the input is always ≥ 1
2044                let x = pre_root;
2045
2046                // initial guess using reduced precision
2047                let mut y = x.powf_p::<CheckOverflow<LessPrecision<P>, false>>(Self::splat(
2048                    E::ONE / E::from_int(N as thermite::LargeInt),
2049                ));
2050
2051                // One iteration of Halley's method for nth root
2052                let y_n = y.powi_p::<P>(N as i32);
2053
2054                let np1 = Self::splat(E::from_int((N + 1) as thermite::LargeInt));
2055                let nm1 = Self::splat(E::from_int((N - 1) as thermite::LargeInt));
2056
2057                let n = y * (x - y_n); // half of numerator
2058                let d = y_n.mul_adde(np1, x * nm1);
2059
2060                y += (n + n) / d;
2061
2062                y
2063            }
2064        };
2065
2066        // denom now equals (1 + |x|^N)^(1/N)
2067
2068        let mut y = if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
2069            // this is the same number of operations as the more precise version, but
2070            // with better accuracy on large pre_root when using approximate rpc.
2071            self * denom.approx_reciprocal_p::<P>()
2072        } else {
2073            self / denom
2074        };
2075
2076        if const { P::POLICY.check_overflow } {
2077            y = pre_root.is_infinite().select(self.signum(), y);
2078        }
2079
2080        y
2081    }
2082
2083    /// The runtime twin of [`algebraic_sigmoid_n`](Self::algebraic_sigmoid_n), same arithmetic.
2084    #[inline(always)]
2085    fn algebraic_sigmoid<P: Policy>(self, n: u32) -> Self {
2086        if n == 0 {
2087            return self;
2088        }
2089
2090        let pre_root = Self::ONE + self.abs().powi_p::<P>(n as i32);
2091
2092        let denom = match n {
2093            1 => pre_root,
2094            2 => pre_root.sqrt(),
2095            3 => pre_root.cbrt_p::<P>(),
2096            4 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } => pre_root.sqrt().sqrt(),
2097            _ => {
2098                let x = pre_root;
2099
2100                let mut y = x.powf_p::<CheckOverflow<LessPrecision<P>, false>>(Self::splat(
2101                    E::ONE / E::from_int(n as thermite::LargeInt),
2102                ));
2103
2104                let y_n = y.powi_p::<P>(n as i32);
2105
2106                let np1 = Self::splat(E::from_int((n + 1) as thermite::LargeInt));
2107                let nm1 = Self::splat(E::from_int((n - 1) as thermite::LargeInt));
2108
2109                let num = y * (x - y_n);
2110                let d = y_n.mul_adde(np1, x * nm1);
2111
2112                y += (num + num) / d;
2113
2114                y
2115            }
2116        };
2117
2118        let mut y = if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
2119            self * denom.approx_reciprocal_p::<P>()
2120        } else {
2121            self / denom
2122        };
2123
2124        if const { P::POLICY.check_overflow } {
2125            y = pre_root.is_infinite().select(self.signum(), y);
2126        }
2127
2128        y
2129    }
2130
2131    // f(x)  = x*(1/2 + x/(2 sqrt(1 + x^2)))
2132    // f'(x) = (x^3 + sqrt(1 + x^2) x^2 + sqrt(1 + x^2) + 2 x) / (2 (1 + x^2)^(3/2))
2133    //
2134    // With a = 1 + x^2, r = sqrt(a), q = x/r:
2135    //   f(x)  = (x/2)*(1 + q)
2136    //   f'(x) = (1 + q + q/a) / 2     (since q' = 1/(a*r), so f' = g + x*g' = (1+q)/2 + q/(2a))
2137    #[inline(always)]
2138    fn algebraic_swish<P: Policy>(self) -> Self {
2139        let x = self;
2140
2141        if const { matches!(Self::HAS_NATIVE_FMA, thermite::tribool::True) } {
2142            // rsqrt is about 30% faster than sqrt+div, even with the extra
2143            // newton iteration merged in.
2144            // Capability only, with no denormal-policy gate, and deliberately: the
2145            // argument is `a = x*x + 1`, which is >= 1 for every finite `x`, so it can
2146            // never be subnormal and the denormal-as-zero behaviour of `rsqrt` cannot
2147            // reach it. Gating this on `Preserve` would cost a sqrt and a divide to
2148            // protect an input that does not exist.
2149            if const { Self::HAS_APPROX_RSQRT } {
2150                let a = x.mul_add(x, Self::ONE);
2151                let y0 = a.rsqrt();
2152                let ay2 = a * y0 * y0;
2153                let ch = ay2.nmul_add(Self::HALF, Self::splat(<E as FloatElement>::ConstRatio::<3, 2>::VALUE));
2154                let r_inv = y0 * ch; // Newton-refined 1/sqrt(a)
2155                let q = x * r_inv;
2156                let xh = Self::HALF * x;
2157                q.mul_add(xh, xh)
2158            } else {
2159                let a = x.mul_add(x, Self::ONE);
2160                let q = x / a.sqrt();
2161                let xh = x * Self::HALF;
2162                q.mul_add(xh, xh)
2163            }
2164        // Bare capability. See the `HAS_APPROX_RSQRT` note above: `a = x*x + 1 >= 1`.
2165        } else if const { Self::HAS_APPROX_RCP } {
2166            let a = x * x + Self::ONE;
2167            let y0 = a.rsqrt();
2168            let ay2 = a * y0 * y0;
2169            let c = Self::splat(<E as FloatElement>::ConstInt::<3>::VALUE) - ay2;
2170            let r_inv_2 = y0 * c; // = 2 * (Newton-refined 1/sqrt(a))
2171            let hxy1 = Self::splat(<E as FloatElement>::ConstRatio::<1, 4>::VALUE) * (x * r_inv_2); // = q/2
2172            let w = Self::HALF + hxy1; // = (1 + q)/2
2173            x * w
2174        } else {
2175            let a = x * x + Self::ONE;
2176            let q = x / a.sqrt();
2177            let q1 = q + Self::ONE;
2178            x * Self::HALF * q1
2179        }
2180    }
2181
2182    #[inline(always)]
2183    fn gaussian_integral<P: Policy>(x0: Self, x1: Self, a: Self, c: Self) -> Self {
2184        // 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
2185        let common = Self::SQRT_FRAC_PI_2 * a * c;
2186        let denom = Self::SQRT_2 * c;
2187
2188        let (a1, a0) = if const { P::POLICY.precision.le(PrecisionPolicy::Medium) } {
2189            let d = denom.approx_reciprocal_p::<P>();
2190            (x1 * d, x0 * d)
2191        } else {
2192            (x1 / denom, x0 / denom)
2193        };
2194
2195        common * (a1.erf_p::<P>() - a0.erf_p::<P>())
2196    }
2197
2198    /// Fills a runtime coefficient table for degree `L` and phase `CS`. See
2199    /// [`sh_impl`] for the conventions, layout, and algorithm.
2200    ///
2201    /// The direction-independent half of the work, split out so a caller sweeping many
2202    /// directions pays it once: pair it with [`spherical_harmonics_with`](Self::spherical_harmonics_with).
2203    /// The table records its own phase, which is why the evaluators take no `CS`.
2204    ///
2205    /// The default computes every coefficient from its closed form in `l` and `m`
2206    /// (two `sqrt` and two divisions apiece) using nothing but `FloatVector`
2207    /// arithmetic, so it works at any degree and on any element type. Real `f32`/`f64`
2208    /// vectors override it to splat the compile-time table instead whenever
2209    /// `L <= MAX_DEGREE`, which removes the arithmetic entirely.
2210    #[inline(always)]
2211    fn spherical_harmonics_table<P: Policy, const L: usize, const N: usize, const CS: bool>(
2212        table: &mut ShTable<Self::Primal, N>,
2213    ) {
2214        generic::sh::sh_table_impl::<Self::Primal, L, N, CS>(table);
2215    }
2216
2217    /// Evaluates all harmonics through degree `L` from a table built by
2218    /// [`spherical_harmonics_table`](Self::spherical_harmonics_table).
2219    ///
2220    /// The default lifts each `Self::Primal` coefficient through `from_primal` as it
2221    /// is read. That is the identity for types that are their own primal, so they
2222    /// keep the fused single-type kernel. Composites with a cheaper mixed multiply
2223    /// (`Dual`) override this.
2224    #[inline(always)]
2225    fn spherical_harmonics_with<P: Policy, const L: usize, const N: usize>(
2226        table: &ShTable<Self::Primal, N>,
2227        x: Self,
2228        y: Self,
2229        z: Self,
2230        out: &mut [Self; N],
2231    ) {
2232        generic::sh::sh_eval_lifted_impl::<Self, L, N>(table, x, y, z, out);
2233    }
2234
2235    /// The one-shot form: build a table and evaluate it.
2236    ///
2237    /// This default composes [`spherical_harmonics_table`](Self::spherical_harmonics_table)
2238    /// with [`spherical_harmonics_with`](Self::spherical_harmonics_with), so it needs no
2239    /// compile-time table and works on every element type and at any degree. Real
2240    /// `f32`/`f64` vectors override it with the fully-unrolled kernel for
2241    /// `L <= MAX_DEGREE`.
2242    ///
2243    /// A caller in a loop over directions should build the table once and call
2244    /// `spherical_harmonics_with` instead. This rebuilds it on every invocation, and
2245    /// the table is the expensive part.
2246    #[inline(always)]
2247    fn spherical_harmonics<P: Policy, const L: usize, const N: usize, const CS: bool>(
2248        x: Self,
2249        y: Self,
2250        z: Self,
2251        out: &mut [Self; N],
2252    ) {
2253        let mut table = ShTable::<Self::Primal, N>::zeroed();
2254        Self::spherical_harmonics_table::<P, L, N, CS>(&mut table);
2255        Self::spherical_harmonics_with::<P, L, N>(&table, x, y, z, out);
2256    }
2257}
2258
2259/// Value-and-derivative (`_d`) forms of the activation functions, for single-value real numbers.
2260///
2261/// Every method is a provided default returning `(value, derivative)`; the `value` matches the
2262/// like-named value-only function in [`SpecializedSpecialMath`] / [`SpecializedRealSpecialMath`].
2263/// Implemented (as an empty impl) only for primal types -- *not* for derivative-carrying numbers
2264/// like `Dual`, which obtain the derivative from the value form via automatic differentiation.
2265pub trait SpecializedRealPrimalMath<E>: SpecializedRealSpecialMath<E> + PrimalProjection<Primal = Self> {
2266    /// [`spherical_harmonics_with`](SpecializedRealSpecialMath::spherical_harmonics_with)
2267    /// plus the ambient Cartesian gradients, from a prebuilt table.
2268    #[inline(always)]
2269    #[allow(clippy::too_many_arguments)]
2270    fn spherical_harmonics_d_with<P: Policy, const L: usize, const N: usize>(
2271        table: &ShTable<Self, N>,
2272        x: Self,
2273        y: Self,
2274        z: Self,
2275        out: &mut [Self; N],
2276        ddx: &mut [Self; N],
2277        ddy: &mut [Self; N],
2278        ddz: &mut [Self; N],
2279    ) {
2280        generic::sh::sh_eval_d_impl::<Self, L, N>(table, x, y, z, out, ddx, ddy, ddz);
2281    }
2282
2283    /// [`spherical_harmonics`](SpecializedRealSpecialMath::spherical_harmonics) plus the ambient Cartesian
2284    /// gradient of every harmonic. See [`sh_d_impl`] for the gradient semantics.
2285    #[inline(always)]
2286    #[allow(clippy::too_many_arguments)]
2287    fn spherical_harmonics_d<P: Policy, const L: usize, const N: usize, const CS: bool>(
2288        x: Self,
2289        y: Self,
2290        z: Self,
2291        out: &mut [Self; N],
2292        ddx: &mut [Self; N],
2293        ddy: &mut [Self; N],
2294        ddz: &mut [Self; N],
2295    ) {
2296        let mut table = ShTable::<Self, N>::zeroed();
2297        Self::spherical_harmonics_table::<P, L, N, CS>(&mut table);
2298        Self::spherical_harmonics_d_with::<P, L, N>(&table, x, y, z, out, ddx, ddy, ddz);
2299    }
2300
2301    /// [`zernike_basis`](SpecializedSpecialMath::zernike_basis) plus the Cartesian
2302    /// gradient of every mode. See [`zernike_basis_d_impl`] for the algorithm.
2303    #[inline(always)]
2304    fn zernike_basis_d<P: Policy, const L: usize, const NORM: u8, const N: usize>(
2305        x: Self,
2306        y: Self,
2307        out: &mut [Self; N],
2308        ddx: &mut [Self; N],
2309        ddy: &mut [Self; N],
2310    ) {
2311        generic::zernike::zernike_basis_d_impl::<P, E, Self, L, NORM, N>(x, y, out, ddx, ddy);
2312    }
2313
2314    #[inline(always)]
2315    fn softplus_d<P: Policy>(self, k: Self, rcp_k: Self) -> (Self, Self) {
2316        if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
2317            let k = k.scale(FloatConsts::LOG2_E);
2318            let rcp_k = rcp_k.scale(FloatConsts::LN_2);
2319
2320            let kx = self * k;
2321
2322            let e = kx.abs().neg().exp2_p::<CheckOverflow<P, true>>();
2323            let y = (Self::ONE + e).log2_p::<P>().mul_adde(rcp_k, self.max(Self::ZERO));
2324
2325            let rcp = (Self::ONE + e).approx_reciprocal_p::<P>();
2326            let dy = kx.select_negative(e * rcp, rcp);
2327
2328            return (y, dy);
2329        }
2330
2331        let kx = self * k;
2332
2333        let e = kx.abs().neg().exp_p::<P>();
2334
2335        // max(0, x) + lnp1(e^(-|x|)) is more stable than ln(1 + e^x) for large |x|.
2336        let y = e.ln_1p_p::<P>().mul_adde(rcp_k, self.max(Self::ZERO));
2337
2338        // sigmoid from already-computed e = exp(-|kx|)
2339        let rcp = (e + Self::ONE).approx_reciprocal_p::<P>();
2340        let dy = kx.select_negative(e * rcp, rcp);
2341
2342        (y, dy)
2343    }
2344
2345    #[inline(always)]
2346    fn gelu_d<P: Policy>(self, alpha: Self) -> (Self, Self) {
2347        let alpha_x = alpha * self;
2348
2349        // 0.5 * x * erfc(-ax / sqrt(2)). See `gelu` for why this is not `1 + erf`.
2350        let c = (-alpha_x).scale(FloatConsts::FRAC_1_SQRT_2).erfc_p::<P>();
2351        let y = self.scale(E::ConstRatio::<1, 2>::VALUE) * c;
2352
2353        let dy = (alpha_x * alpha_x)
2354            .scale(E::ConstRatio::<{ -1 }, 2>::VALUE)
2355            .exp_p::<P>()
2356            .scale(FloatConsts::FRAC_1_SQRT_TAU);
2357
2358        (y, dy.mul_adde(alpha_x, y))
2359    }
2360
2361    #[inline(always)]
2362    fn swish_d<P: Policy>(self, beta: Self) -> (Self, Self) {
2363        let x = self;
2364        let beta_x = beta * x;
2365
2366        let e = (-beta_x).exp_p::<P>();
2367        let s = (Self::ONE + e).approx_reciprocal_p::<P>();
2368
2369        let y = x * s;
2370
2371        // dy/dx = s + beta * y * (1 - s); 1 - s = e * s (stable near s ~ 1)
2372        let dy = (beta * y).mul_adde(e * s, s);
2373
2374        (y, dy)
2375    }
2376
2377    #[inline(always)]
2378    fn algebraic_sigmoid_d_n<P: Policy, const N: usize>(self) -> (Self, Self) {
2379        if const { N == 0 } {
2380            return (self, Self::ONE); // identity function
2381        }
2382
2383        let pre_root = Self::ONE + self.abs().powi_p::<P>(N as i32); // = 1 + |x|^N
2384
2385        let denom = match N {
2386            1 => pre_root,
2387            2 => pre_root.sqrt(),
2388            3 => pre_root.cbrt_p::<P>(),
2389            4 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } => pre_root.sqrt().sqrt(),
2390            _ => {
2391                let x = pre_root;
2392
2393                let mut y = x.powf_p::<CheckOverflow<LessPrecision<P>, false>>(Self::splat(
2394                    E::ONE / E::from_int(N as thermite::LargeInt),
2395                ));
2396
2397                let y_n = y.powi_p::<P>(N as i32);
2398
2399                let np1 = Self::splat(E::from_int((N + 1) as thermite::LargeInt));
2400                let nm1 = Self::splat(E::from_int((N - 1) as thermite::LargeInt));
2401
2402                let n = y * (x - y_n); // half of numerator
2403                let d = y_n.mul_adde(np1, x * nm1);
2404
2405                y += (n + n) / d;
2406
2407                y
2408            }
2409        };
2410
2411        // denom = (1 + |x|^N)^(1/N); f'(x) = 1 / (pre_root * denom)
2412        let mut y;
2413        let mut dy;
2414
2415        if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
2416            let inv_denom = denom.approx_reciprocal_p::<P>();
2417            y = self * inv_denom;
2418            dy = inv_denom / pre_root;
2419        } else {
2420            y = self / denom;
2421            dy = (pre_root * denom).approx_reciprocal_p::<P>();
2422        }
2423
2424        if const { P::POLICY.check_overflow } {
2425            let is_infinite = pre_root.is_infinite();
2426
2427            y = is_infinite.select(self.signum(), y);
2428            dy = dy.nz(is_infinite); // zero if is_infinite
2429        }
2430
2431        (y, dy)
2432    }
2433
2434    /// The runtime twin of [`algebraic_sigmoid_d_n`](Self::algebraic_sigmoid_d_n).
2435    #[inline(always)]
2436    fn algebraic_sigmoid_d<P: Policy>(self, n: u32) -> (Self, Self) {
2437        if n == 0 {
2438            return (self, Self::ONE);
2439        }
2440
2441        let pre_root = Self::ONE + self.abs().powi_p::<P>(n as i32);
2442
2443        let denom = match n {
2444            1 => pre_root,
2445            2 => pre_root.sqrt(),
2446            3 => pre_root.cbrt_p::<P>(),
2447            4 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } => pre_root.sqrt().sqrt(),
2448            _ => {
2449                let x = pre_root;
2450
2451                let mut y = x.powf_p::<CheckOverflow<LessPrecision<P>, false>>(Self::splat(
2452                    E::ONE / E::from_int(n as thermite::LargeInt),
2453                ));
2454
2455                let y_n = y.powi_p::<P>(n as i32);
2456
2457                let np1 = Self::splat(E::from_int((n + 1) as thermite::LargeInt));
2458                let nm1 = Self::splat(E::from_int((n - 1) as thermite::LargeInt));
2459
2460                let num = y * (x - y_n);
2461                let d = y_n.mul_adde(np1, x * nm1);
2462
2463                y += (num + num) / d;
2464
2465                y
2466            }
2467        };
2468
2469        let mut y;
2470        let mut dy;
2471
2472        if const { P::POLICY.precision.lt(PrecisionPolicy::Average) } {
2473            let inv_denom = denom.approx_reciprocal_p::<P>();
2474            y = self * inv_denom;
2475            dy = inv_denom / pre_root;
2476        } else {
2477            y = self / denom;
2478            dy = (pre_root * denom).approx_reciprocal_p::<P>();
2479        }
2480
2481        if const { P::POLICY.check_overflow } {
2482            let is_infinite = pre_root.is_infinite();
2483
2484            y = is_infinite.select(self.signum(), y);
2485            dy = dy.nz(is_infinite);
2486        }
2487
2488        (y, dy)
2489    }
2490
2491    /// `L(x)` and `L'(x)`. The derivative falls out of the value's own intermediates
2492    /// on both branches (see `generic::langevin`), so there is no default here that
2493    /// would recompute it.
2494    fn langevin_d<P: Policy>(self) -> (Self, Self);
2495
2496    #[inline(always)]
2497    fn algebraic_swish_d<P: Policy>(self) -> (Self, Self) {
2498        let x = self;
2499
2500        if const { matches!(Self::HAS_NATIVE_FMA, thermite::tribool::True) } {
2501            // Capability only, with no denormal-policy gate, and deliberately: the
2502            // argument is `a = x*x + 1`, which is >= 1 for every finite `x`, so it can
2503            // never be subnormal and the denormal-as-zero behaviour of `rsqrt` cannot
2504            // reach it. Gating this on `Preserve` would cost a sqrt and a divide to
2505            // protect an input that does not exist.
2506            if const { Self::HAS_APPROX_RSQRT } {
2507                let a = x.mul_add(x, Self::ONE);
2508                let y0 = a.rsqrt();
2509                let ay2 = a * y0 * y0;
2510                let ch = ay2.nmul_add(Self::HALF, Self::splat(<E as FloatElement>::ConstRatio::<3, 2>::VALUE));
2511                let r_inv = y0 * ch; // Newton-refined 1/sqrt(a)
2512                let q = x * r_inv;
2513                let xh = Self::HALF * x;
2514                let y = q.mul_add(xh, xh);
2515
2516                let inv_a = r_inv * r_inv;
2517                let qa = q.mul_add(inv_a, q); // q + q/a
2518                let dy = qa.mul_add(Self::HALF, Self::HALF); // (qa + 1)/2
2519
2520                (y, dy)
2521            } else {
2522                let a = x.mul_add(x, Self::ONE);
2523                let q = x / a.sqrt();
2524                let xh = x * Self::HALF;
2525                let y = q.mul_add(xh, xh);
2526
2527                let inv_a = a.approx_reciprocal_p::<P>();
2528                let qa = q.mul_add(inv_a, q);
2529                let dy = qa.mul_add(Self::HALF, Self::HALF);
2530
2531                (y, dy)
2532            }
2533        // Bare capability. See the `HAS_APPROX_RSQRT` note above: `a = x*x + 1 >= 1`.
2534        } else if const { Self::HAS_APPROX_RCP } {
2535            let a = x * x + Self::ONE;
2536            let y0 = a.rsqrt();
2537            let ay2 = a * y0 * y0;
2538            let c = Self::splat(<E as FloatElement>::ConstInt::<3>::VALUE) - ay2;
2539            let r_inv_2 = y0 * c; // = 2 * (Newton-refined 1/sqrt(a))
2540            let hxy1 = Self::splat(<E as FloatElement>::ConstRatio::<1, 4>::VALUE) * (x * r_inv_2); // = q/2
2541            let w = Self::HALF + hxy1; // = (1 + q)/2
2542            let y = x * w;
2543
2544            let inv_a = Self::splat(<E as FloatElement>::ConstRatio::<1, 4>::VALUE) * (r_inv_2 * r_inv_2);
2545            let dy = w + hxy1 * inv_a;
2546
2547            (y, dy)
2548        } else {
2549            let a = x * x + Self::ONE;
2550            let q = x / a.sqrt();
2551            let q1 = q + Self::ONE;
2552            let y = x * Self::HALF * q1;
2553
2554            let dy = Self::HALF * (q1 + q / a);
2555
2556            (y, dy)
2557        }
2558    }
2559}
Last built: 2026-09-08 21:35:55 UTC