Skip to main content

thermite_special/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![allow(clippy::needless_arbitrary_self_type, clippy::needless_range_loop)]
5#![recursion_limit = "256"]
6
7use thermite::{
8    element::{Element, ElementExt, FloatElementWithBits},
9    math::{
10        PrimalMathWithPolicy, PrimalProjection, TranscendentalMathWithPolicy,
11        policy::{DefaultPolicy, Policy},
12        scalar::Unwrap,
13    },
14    vector::{FloatVector, FloatVectorWithBits},
15};
16
17pub mod specialized;
18
19// Raw approximation coefficients, shared with the sibling crates. Documented on the
20// module itself rather than here: an outer doc at this declaration site is merged with
21// the module's own and then resolved in THIS scope, which breaks every link it makes
22// to its own submodules.
23#[doc(hidden)]
24pub mod tables;
25
26pub use tables::bernoulli::BernoulliNumbers;
27pub use tables::cot_pi::CotPiDerivatives;
28pub use tables::factorial::Factorials;
29
30pub mod bernoulli;
31pub mod bessel;
32pub mod polylog;
33pub mod zernike;
34
35// `BesselOrder` appears in the signature of every runtime-order Bessel entry point below,
36// so a caller has to be able to name it without reaching into the module.
37pub use crate::bessel::BesselOrder;
38
39// The marker-selected Bessel entry points (`bessel_n`, `bessel`, `sph_bessel_n`,
40// `sph_bessel`, `airy`) are bounded on these. The markers themselves stay in
41// `bessel::{J, Y, I, K, Scaled, Ai, ..}`.
42use crate::bessel::{AiryFn, BesselFamily, BesselRatioFamily};
43
44// Likewise `PolylogOrder`, the order argument of `polylog`.
45pub use crate::polylog::PolylogOrder;
46
47// The two normalization flags appear in the `zernike` signature below as a const
48// generic, so a caller has to be able to name them without reaching into the module.
49pub use crate::zernike::{ZERNIKE_ORTHONORMAL, ZERNIKE_UNIT_PEAK};
50
51use crate::specialized::{CarlsonKind, EllipticKind, WrapTo};
52
53// Spherical-harmonic support: `ShTable` appears in the public signatures below, and
54// `MAX_SH_DEGREE` is the documented degree at which they leave the unrolled path.
55pub use crate::specialized::{MAX_SH_DEGREE, ShTable};
56
57/// Elliptic integral request structs and the traits they implement:
58///
59/// - Carlson symmetric integrals (for [`SpecialMath::carlson`]): [`CarlsonRf`](elliptic::CarlsonRf),
60///   [`CarlsonRc`](elliptic::CarlsonRc), [`CarlsonRd`](elliptic::CarlsonRd), [`CarlsonRj`](elliptic::CarlsonRj),
61///   [`CarlsonRg`](elliptic::CarlsonRg), implementing [`CarlsonKind`].
62/// - Legendre integrals (for [`SpecialMath::ellint`]): [`EllintK`](elliptic::EllintK)/[`EllintF`](elliptic::EllintF),
63///   [`EllintE`](elliptic::EllintE)/[`EllintEInc`](elliptic::EllintEInc),
64///   [`EllintD`](elliptic::EllintD)/[`EllintDInc`](elliptic::EllintDInc),
65///   [`EllintPi`](elliptic::EllintPi)/[`EllintPiInc`](elliptic::EllintPiInc), implementing
66///   [`EllipticKind`]. Completeness is encoded by the struct: a complete integral has no `phi` field.
67///
68/// The request structs are implemented for every float vector whose element carries
69/// [`EllipticConsts`](elliptic::EllipticConsts): real `f32`/`f64` vectors, `Dual` (the
70/// derivative is the chain rule through the Carlson duplication and the AGM, contractive
71/// algebraic iterations) and `Compensated` (which supplies its own, tighter, convergence
72/// thresholds and holds full double-double). `Complex` does not implement the constants, so
73/// an elliptic integral of a complex vector is a compile error rather than a wrong answer:
74/// the kernels' region decisions are real-line comparisons.
75pub mod elliptic {
76    pub use crate::specialized::EllipticConsts;
77
78    pub use crate::specialized::{CarlsonKind, CarlsonRc, CarlsonRd, CarlsonRf, CarlsonRg, CarlsonRj};
79
80    pub use crate::specialized::{
81        EllintD, EllintDInc, EllintE, EllintEInc, EllintF, EllintK, EllintPi, EllintPiInc, EllipticKind,
82    };
83
84    /// The two members of the family that are not Legendre integrals, dispatched through the
85    /// same [`EllipticKind`] entry point as the rest.
86    pub use crate::specialized::{HeumanLambda, JacobiZeta};
87}
88
89thermite::math_traits! {
90    #![thermite(thermite)]
91    #![scalar(ScalarSpecialMath)]
92    #![surface]
93
94    /// Special math functions that are valid for both real and complex floating-point vectors.
95    #[diagnostic::on_unimplemented(
96        message = "`{Self}` does not provide special math (`erf`, `gamma`, activations, ...)",
97        note = "The special-math traits are auto-implemented for every float vector (any `FloatVector` whose element is `f32`/`f64`) and for composite float types. A bare `f32`/`f64` does not qualify. Wrap it in `Vector::<f32>::splat(x)`, or use `ScalarSpecialMath`'s `scalar_`-prefixed methods.",
98        note = "If `{Self}` already is a `FloatVector` and only the method call fails to resolve, bring the trait into scope: `use thermite_special::SpecialMath;` (or the relevant `RealSpecialMath` / `RealPrimalMath`)."
99    )]
100    pub trait SpecialMath: TranscendentalMathWithPolicy {
101        /// Computes the error function.
102        ///
103        /// For f32 vectors, this is still decently accurate even with the `Medium` and `Worst` precision policies,
104        /// thanks to good approximations that don't rely on the precision of `exp`. Subsequently, performance
105        /// of the lower precision policies is excellent. Furthermore, if using on a GPU with native `exp` support,
106        /// all precision policies will have good performance and accuracy.
107        ///
108        /// Below `Best`, the f64 kernel forms `erf` as `$1 - m\,e^{-x^2}$`, whose error is a fixed
109        /// absolute ulp of 1: `erf(0)` comes out `2.2e-16` and `erf(1e-8)` is only 2e-8 relative.
110        /// From `Best` up, `|x| < 0.84375` takes a direct `$x + x\,R(x^2)/S(x^2)$` arm that is
111        /// exact at zero and relatively accurate down to the denormals. The f32 kernel carries
112        /// that arm from `Average`.
113        fn erf(self) -> Self;
114
115        /// Computes the complementary error function.
116        ///
117        /// The f64 kernel is one product of six rationals times `$e^{-x^2}$` over the whole
118        /// line, within about 3 ulp everywhere on hardware with a fused multiply-add: the one
119        /// error that grows, the rounding of `$x^2$` under the exponential amplified by `$x^2$`,
120        /// is removed with the exact residual of the product at every tier. Without a native
121        /// FMA that residual is unavailable, so `Best` removes the growth with a bit-split of `x`
122        /// instead, and the lower tiers keep it (47 ulp at `x = 14`, 237 at `x = 24`).
123        fn erfc(self) -> Self;
124
125        /// Computes the scaled complementary error function,
126        /// `$\operatorname{erfcx}(x) = e^{x^2}\operatorname{erfc}(x)$`.
127        ///
128        /// `erfc` underflows to zero at `x ~ 27` in `f64` and `x ~ 9` in `f32`,
129        /// where the true value is `$e^{-x^2}/(x\sqrt{\pi})$`, nonzero and merely too small to
130        /// represent. Anything reading a Gaussian tail past that point silently gets zero:
131        /// importance weights, log-likelihoods, censored-data models, the Voigt profile.
132        /// `erfcx` removes the exponential and decays only as `$1/(x\sqrt{\pi})$`, so it is
133        /// representable for every finite argument and keeps full relative accuracy.
134        ///
135        /// Computed on the real backends as the Faddeeva function restricted to the imaginary
136        /// axis, `$w(ix) = \operatorname{erfcx}(x)$`, where Weideman's rational approximation
137        /// degenerates to real arithmetic: one reciprocal and one Horner, no transcendental at
138        /// all for `x >= 0`. That makes it cheaper than the `erfc` it complements, and
139        /// measures 1.22 ulp worst over `$x \in [0, 10^{15}]$` at the `Best` tier and above.
140        ///
141        /// Negative arguments use `$\operatorname{erfcx}(-x) = 2e^{x^2} - \operatorname{erfcx}(x)$`
142        /// and legitimately overflow below about `-26.6` (`f64`), the function itself growing
143        /// like `$e^{x^2}$` in that direction.
144        ///
145        /// The two are related by `$\operatorname{erfc}(x) = e^{-x^2}\operatorname{erfcx}(x)$`,
146        /// which is the numerically sound way to recover a tail value that `erfc` alone cannot
147        /// hold. Keep the `$-x^2$` in the log domain rather than exponentiating it.
148        fn erfcx(self) -> Self;
149
150        /// Computes the Logistic sigmoid function, defined as `$\sigma(x) = \frac{1}{1 + e^{-x}}$`.
151        ///
152        /// It's worth mentioning that the derivative of the logistic sigmoid can be computed very cheaply
153        /// from the output of the logistic sigmoid itself, in the form of:
154        ///
155        /// ```rust,ignore
156        /// let s = x.logistic_sigmoid();
157        /// let derivative = s * (1.0 - s); // or s.nmul_adde(s, s), which may be slightly faster
158        /// ```
159        ///
160        /// Notably, for `f32` and `f64` this implementation still has good precision for the `Worst`
161        /// precision policy, and for the `Best` precision policies handles very large positive and negative
162        /// inputs without overflow or underflow issues.
163        #[doc(alias = "expit")]
164        fn logistic_sigmoid(self) -> Self;
165
166        /// Computes the logit `$\ln\!\frac{p}{1-p}$`, the inverse of
167        /// [`logistic_sigmoid`](SpecialMath::logistic_sigmoid).
168        ///
169        /// Evaluated as `$\ln(p) - \ln_{1p}(-p)$`, which is accurate for small `p` where the direct
170        /// quotient is not. For `p` approaching 1 no evaluation order helps. `$1 - p$` has already
171        /// lost its low digits inside the input itself, and the information is not recoverable from
172        /// `p`. A caller who knows `$q = 1 - p$` should pass it to
173        /// [`logit_1m`](SpecialMath::logit_1m) instead, which is exact at the far end of the range.
174        ///
175        /// `p = 0` gives `-∞`, `p = 1` gives `+∞`, and `p` outside `[0, 1]` is out of domain.
176        fn logit(self) -> Self;
177
178        /// Computes `$\mathrm{logit}(1 - q) = \ln\!\frac{1-q}{q}$` from the complement `q` directly.
179        ///
180        /// The companion entry point to [`logit`](SpecialMath::logit), in the same relationship as
181        /// [`langevin_1m`](RealSpecialMath::langevin_1m) has to
182        /// [`langevin`](RealSpecialMath::langevin). The logit diverges as its argument approaches 1,
183        /// and near that end `$1 - p$` cannot be formed from `p` without losing every digit that
184        /// matters. Working in `q` throughout sidesteps that: evaluated as
185        /// `$\ln_{1p}(-q) - \ln(q)$`, accurate to a few ulp however small `q` is.
186        ///
187        /// Note the sign convention follows the substitution, so `logit_1m(q) == -logit(q)` as
188        /// functions of the same number. The two differ in _which_ probability the argument names.
189        fn logit_1m(self) -> Self;
190
191        /// Computes the softplus function, defined as `$\frac{1}{k}\ln(1 + e^{kx})$`.
192        ///
193        /// This is a smooth approximation to the ReLU function
194        /// that is more numerically stable for large inputs.
195        ///
196        /// The parameter `k` controls the steepness of the curve, with larger values approaching ReLU more closely.
197        /// Pass `k = 1` and `rcp_k = 1` for the standard softplus with no steepness scaling.
198        ///
199        /// `rcp_k` must equal `1/k`. It is passed explicitly so callers that invoke softplus repeatedly
200        /// with the same `k` can pre-compute the reciprocal once rather than recomputing it per call.
201        ///
202        /// To also obtain the derivative with respect to `x`, use
203        /// [`softplus_d`](crate::RealPrimalMath::softplus_d).
204        fn softplus(self, k: Self, rcp_k: Self) -> Self;
205
206        /// Computes the Gamma function (`$\Gamma(z)$`) for any real input, for each value in a vector.
207        ///
208        /// This implementation uses a few different behaviors to ensure the greatest precision where possible.
209        ///
210        /// * For non-integer positive inputs, it uses the Lanczos approximation.
211        /// * For small non-integer negative inputs, it uses the recursive identity `$\Gamma(z) = \Gamma(z+1)/z$` until `z` is positive.
212        /// * For large non-integer negative inputs, it uses the reflection formula `$-\pi / (\Gamma(z)\sin(\pi z)\,z)$`.
213        /// * For positive integers, it simply computes the factorial in a tight loop to ensure precision. Lookup tables could not be used with SIMD.
214        /// * At zero, the result will be positive or negative infinity based on the input sign (signed zero is a thing).
215        ///
216        /// **NOTE**: The Gamma function is not defined for negative integers.
217        #[doc(alias = "gamma")]
218        fn tgamma(self) -> Self;
219
220        /// Computes the natural log of the Gamma function (`$\ln|\Gamma(x)|$`) for any real input, for each value in a vector.
221        #[doc(alias = "gammaln")]
222        #[doc(alias = "lngamma")]
223        fn lgamma(self) -> Self;
224
225        /// The Poisson probability mass `$P(k; \lambda) = e^{-\lambda}\lambda^k / k!$` at `k = self`,
226        /// for real `$k \ge 0$` and mean `$\lambda \ge 0$`.
227        ///
228        /// Not `exp(k ln lambda - lambda - lgamma(k+1))`: that forms an `$O(1)$` answer as the
229        /// exponential of a difference of large numbers, and half an ulp of
230        /// `$\ln\Gamma(k+1) = O(k \ln k)$` becomes that many ulp of the mass. For `$k \ge 9$` this
231        /// uses Loader's saddle-point form (the one R's `dpois` uses),
232        ///
233        /// ```math
234        /// P(k; \lambda) = \frac{e^{-\mathrm{stirlerr}(k) - \mathrm{bd0}(k, \lambda)}}{\sqrt{2\pi k}}
235        /// ```
236        ///
237        /// with `stirlerr` the Stirling remainder (a short `$1/k^2$` series) and `bd0` the
238        /// deviance `$k \ln(k/\lambda) + \lambda - k$` (a series in `$(k-\lambda)/(k+\lambda)$` near
239        /// the peak, where the direct form cancels): both are small where the mass is not
240        /// negligible, so the exponential amplifies nothing, and there is no `lgamma` and no
241        /// `ln` at all near the peak. Below `$k = 9$` the same machinery is used after shifting
242        /// `k` up by an integer, with the exact product `$(k+1)\cdots(k+m)$` taken back out, so
243        /// there is no `lgamma` anywhere, and mixed vectors share one `ln`, one `stirlerr` and
244        /// one `exp`. Real `k` is allowed because
245        /// the Gamma density is the same function: `$f(x; a) = P(a-1; x)$` for shape `$a \ge 1$`
246        /// (unit scale).
247        ///
248        /// Edges: `$\lambda = 0$` gives `1` at `$k = 0$` and `0` above; `$k = 0$` is `$e^{-\lambda}$`.
249        fn poisson_pmf(self, lambda: Self) -> Self;
250
251        /// `$\ln P(k; \lambda)$`, the log of [`poisson_pmf`](SpecialMath::poisson_pmf), formed
252        /// directly (no `exp` then `ln`) so it stays finite far in the tails where the mass
253        /// itself underflows.
254        fn poisson_log_pmf(self, lambda: Self) -> Self;
255
256        /// Computes the digamma function `$\psi(x) = \frac{\mathrm{d}}{\mathrm{d}x}\ln\Gamma(x) = \frac{\Gamma'(x)}{\Gamma(x)}$`
257        /// for any real input, for each value in a vector.
258        ///
259        /// The argument is handled in three regimes:
260        ///
261        /// * For `x >= 10`, an asymptotic expansion in `$1/x^2$` is used.
262        /// * For smaller `x`, the recurrence `$\psi(x) = \psi(x+1) - 1/x$` shifts the argument into
263        ///   `[1, 2]`, where a rational minimax approximation `$\psi(x) = (x - x_0)(Y + R(x-1))$` is used
264        ///   (`$x_0$` is the positive root of `$\psi$`).
265        /// * For `x <= -1`, the reflection formula `$\psi(1-x) = \psi(x) + \pi\cot(\pi x)$` is applied.
266        ///
267        /// **NOTE**: The digamma function is not defined at zero or the negative integers. Those inputs
268        /// yield NaN when overflow checking is enabled.
269        #[doc(alias = "psi")]
270        fn digamma(self) -> Self;
271
272        /// Computes the trigamma function `$\psi_1(x) = \frac{\mathrm{d}}{\mathrm{d}x}\psi(x)$`,
273        /// the second derivative of `$\ln\Gamma$`.
274        ///
275        /// Real vectors run a dedicated kernel (three minimax rational regions with a single
276        /// recurrence step and the `$\pi^2/\sin^2(\pi x)$` reflection) that is a little tighter
277        /// than the general [`polygamma`](crate::SpecialMath::polygamma) machinery at
278        /// order 1. `polygamma(1)` routes here, so the two spellings agree exactly. Complex
279        /// vectors have their own implementation, which is the reason this lives on
280        /// `SpecialMath` while `polygamma` is real-only.
281        ///
282        /// The poles at zero and the negative integers evaluate to `+inf`: `$\psi_1$` has
283        /// double poles, so unlike [`digamma`](SpecialMath::digamma) the two one-sided limits
284        /// agree.
285        fn trigamma(self) -> Self;
286
287        /// Computes the polygamma function `$\psi_n(x) = \frac{\mathrm{d}^n}{\mathrm{d}x^n}\psi(x)$`,
288        /// the n-th derivative of [`digamma`](SpecialMath::digamma) (`n = 0` **is** digamma,
289        /// `n = 1` is [`trigamma`](SpecialMath::trigamma)).
290        ///
291        /// The order `n` is a runtime scalar shared by every lane. That is a deliberate design
292        /// choice: it closes the Gamma family under differentiation, since
293        /// `$\psi_n'(x) = \psi_{n+1}(x)$` is reachable by passing `n + 1`, which is what lets
294        /// forward-mode AD (`Dual`) differentiate through any member of the family to any depth.
295        /// All order-dependent coefficients are scalar work splatted once, so uniform `n`
296        /// costs a vector nothing.
297        ///
298        /// For `n >= 2`, real vectors run a masked recurrence up to the transition point
299        /// `$N = 0.4\,d_{10} + 4n$` and then the Bernoulli asymptotic series on the positive
300        /// axis. Negative arguments reflect through the n-th derivative of `$\cot(\pi x)$`
301        /// (tabulated to `n = 20`, above which negative arguments return NaN). At zero
302        /// and the negative integers, odd `n` returns `+inf` (the correct two-sided limit)
303        /// and even `n` has one-sided limits of opposite sign, so it returns NaN when
304        /// overflow checking is enabled.
305        ///
306        /// Complex vectors run the same recurrence-plus-series in complex arithmetic, gated
307        /// on `$\operatorname{Re} z$`, reflecting the half-plane `$\operatorname{Re} z < 1/2$`
308        /// through the same tabulated `$\cot$` derivative (so the `n <= 20` reflection reach
309        /// applies there too). Only `psi_n` of a _real_ variable is real, so this is the
310        /// family member that makes `polygamma` complex-capable at all orders.
311        ///
312        /// Orders where `$n!$` overflows the element type (`n >= 171` for f64, `n >= 35` for
313        /// f32) return the signed infinity carried by the leading term on the real positive
314        /// axis, and NaN over C.
315        fn polygamma(self, n: u32) -> Self;
316
317        /// Computes the Riemann zeta function `$\zeta(s) = \sum_{n\ge1} n^{-s}$`.
318        ///
319        /// Evaluated as `1 + `[`zetac`](SpecialMath::zetac), which is where the accuracy
320        /// argument lives (see there). Worst relative error measured against mpmath at 40
321        /// digits: 4.4e-16 for `s` in `[1.5, 5]`, 4.3e-16 for `[5, 40]`, 2.3e-15 through the
322        /// critical strip `[0.1, 0.9]`, and 4.6e-16 approaching the pole at `s = 1`, which
323        /// returns infinity.
324        ///
325        /// Negative `s` goes through the functional equation
326        /// `$\zeta(s) = 2^s\pi^{s-1}\sin(\pi s/2)\,\Gamma(1-s)\,\zeta(1-s)$`, landing back at
327        /// `$1-s > 1$` where the series is at its most accurate. That arm costs a `tgamma` and
328        /// a `sin_pi` beyond the main path, so it is gated on a lane needing it.
329        ///
330        /// This is the Riemann zeta of one real argument. The two-argument Hurwitz form
331        /// `$\zeta(s, q)$` is **not** provided: it generalizes the same expansion but loses the
332        /// prime factorization that makes this one cheap, so it is a separate and materially
333        /// more expensive function rather than a special case of this one.
334        #[doc(alias = "riemann_zeta")]
335        fn zeta(self) -> Self;
336
337        /// Computes `$\zeta(s) - 1$`, accurately where `$\zeta(s)$` is within rounding of 1.
338        ///
339        /// `$\zeta$` approaches 1 quickly: `$\zeta(40) - 1$` is about `9.1e-13`, already below
340        /// the mantissa of `$\zeta$` itself, and `$\zeta(80) - 1$` is `8.3e-25`. Forming
341        /// [`zeta`](SpecialMath::zeta) and subtracting 1 therefore destroys the answer: at
342        /// `s = 40` it is off by `9e-8` relative, at `s = 80` by **100%**, and past `s = 200` it
343        /// returns a flat zero.
344        ///
345        /// This is not a wrapper around that subtraction. The Euler-Maclaurin sum underneath
346        /// opens with the `$n = 1$` term, which _is_ the 1, so the complement is obtained by
347        /// **omitting** it, with no cancellation anywhere and still full relative accuracy
348        /// at `s = 700`, where the value is around `1e-211`. `$\zeta$` is the derived form here,
349        /// the same way `exp` relates to [`exp_m1`](thermite::math::TranscendentalMath::exp_m1).
350        ///
351        /// Same accuracy and the same negative-`s` handling as `zeta`.
352        #[doc(alias = "zeta_minus_one")]
353        fn zetac(self) -> Self;
354
355        /// Computes the polylogarithm `$\mathrm{Li}_s(z) = \sum_{k \ge 1} z^k / k^s$`, continued
356        /// to the whole plane, at a scalar real order given as a [`PolylogOrder`].
357        ///
358        /// The order is uniform across the packet and tagged by class, because whole-number
359        /// order is a different, far cheaper algorithm than arbitrary real order and every
360        /// order-dependent coefficient is a per-call scalar precompute. See the
361        /// [order module](crate::polylog) for why it is not a vector. [`Integer`](PolylogOrder::Integer)
362        /// covers both signs: `$n \le 0$` is the closed rational form (a polynomial in
363        /// `$z/(1-z)$`), `$n = 1$` is `$-\ln(1-z)$`, and `$n \ge 2$` runs entirely on tabulated
364        /// `$\zeta$` values. [`Real`](PolylogOrder::Real) is the general algorithm (Wood 1992,
365        /// Roughan 2026): the defining series, the unity series about `$z = 1$` with its two
366        /// cancelling poles fused algebraically so orders arbitrarily close to an integer cost
367        /// nothing extra, and Wood's m-th-root identity in the far field.
368        ///
369        /// On a real vector the argument is real and the result is the **real part** of the
370        /// principal value, which for `$z > 1$` (the cut) is the same from either side. Complex
371        /// vectors return the full value. On the cut it follows the sign of `$\mathrm{Im}\,z$`'s
372        /// zero, C99 style, with `-0` giving mpmath's and Wood's convention for a bare real.
373        ///
374        /// ```rust,ignore
375        /// let li2 = z.polylog(PolylogOrder::Integer(2));   // the dilogarithm
376        /// let fd  = (-x.exp()).polylog(PolylogOrder::Real(1.5)); // -F_{1/2}(x)/Gamma(3/2)
377        /// ```
378        ///
379        /// The order is spelled in the vector's own element types: `Real` carries
380        /// `Self::Element` (a complex element on a complex vector, of which only a real value
381        /// is implemented and anything else answers NaN, or a dual element on a dual vector, whose
382        /// derivative part must be zero) and `Integer` carries the signed lane element
383        /// (`i64` on an `f64` vector, `i32` on an `f32` one). Every order-dependent coefficient
384        /// is computed once per call in that element type through the scalar math surface.
385        ///
386        /// Special values: `$\mathrm{Li}_s(1) = \zeta(s)$` for `$s > 1$` and `$+\infty$` below,
387        /// `$\mathrm{Li}_s(-1) = -\eta(s)$`, `$\mathrm{Li}_s(0) = 0$`. Every arm is a fixed-length
388        /// series whose length follows the policy's precision tier. Whole-number orders past
389        /// `$n = 79$` (binary64) or `$n = 34$` (binary32, where `$n!$` overflows) return NaN in
390        /// the far field (`$|\ln z| > 3.2$`). The series and unity arms have no such limit. Cost
391        /// grows with `$\ln|z|$` in the far field at real order (one unity series per root,
392        /// `$m \approx \ln|z| / 2.08$` roots).
393        ///
394        /// Measured against mpmath on 4952 points (real and complex `$z$`, orders from `-6` to
395        /// `30` and a dozen real ones including `$2 + 10^{-9}$`), binary64 at `Precision`:
396        /// whole-number orders `$n \ge 0$` within 1.3e-14 relative on the real line. Negative
397        /// whole orders within 1.5e-13 (the alternating defining series on the negative axis
398        /// peaks at ~2500x its sum). Real orders within 3.1e-13, with the far field's m-th-root
399        /// sum cancelling by `$m^{s-1}$`, which is what makes binary32 real order 1.1e-4 there
400        /// and 2e-5 elsewhere. On the cut the real part is accurate normwise (the imaginary part
401        /// can be a millionth of it near `$z = 1$` at `$s = 1 + 10^{-6}$`).
402        ///
403        /// Autodiff closes by `$\mathrm{Li}_s'(z) = \mathrm{Li}_{s-1}(z)/z$` with the order
404        /// lowered by one, which is why the runtime order is what the trait carries.
405        #[scalar_form((self, order: PolylogOrder<Self, Self::Signed>) -> Self)]
406        fn polylog(self, order: PolylogOrder<Self::Element, <Self::Signed as thermite::vector::GenericVector>::Element>) -> Self;
407
408        /// A cylindrical Bessel function at compile-time order, selected by family marker:
409        /// [`J`](bessel::J), [`Y`](bessel::Y), [`I`](bessel::I), [`K`](bessel::K), or any of
410        /// them under [`Scaled`](bessel::Scaled). `N` is signed and the families reflect at
411        /// negative order (`$J_{-n} = (-1)^n J_n$`, `$I_{-n} = I_n$`).
412        ///
413        /// ```rust,ignore
414        /// let j2 = x.bessel_n::<J, 2>();                 // J_2(x)
415        /// let ke = x.bessel_n::<Scaled<K>, 0>();         // e^x K_0(x)
416        /// ```
417        ///
418        /// The marker only selects: each spelling is a one-line route into the kernel for that
419        /// family, scaling and order form, with nothing evaluated that was not asked for. `Scaled<J>` and
420        /// `Scaled<Y>` are the SciPy `jve`/`yve` scalings by `$e^{-|\mathrm{Im}\,z|}$`, which
421        /// is 1 on the real axis, so on a real vector they are `J` and `Y` unchanged. On a
422        /// complex vector they are the scaled values.
423        fn bessel_n<F: BesselFamily, const N: i32>(self) -> Self;
424
425        /// [`bessel_n`](SpecialMath::bessel_n) with the order taken **per lane**, at runtime,
426        /// as a [`BesselOrder`] of any class.
427        ///
428        /// ```rust,ignore
429        /// let iv = x.bessel::<Scaled<I>>(BesselOrder::Real(nu));   // e^{-|x|} I_nu(x)
430        /// let jh = x.bessel::<J>(BesselOrder::HalfInteger(k));     // J_{k/2}(x), elementary
431        /// ```
432        fn bessel<F: BesselFamily>(self, order: BesselOrder<Self, Self::Signed>) -> Self;
433
434        /// A spherical Bessel function at compile-time order, the twin of
435        /// [`bessel_n`](SpecialMath::bessel_n) for `$j_n$`, `$y_n$`, `$i_n$`, `$k_n$`.
436        ///
437        /// ```rust,ignore
438        /// let j3 = x.sph_bessel_n::<J, 3>();             // j_3(x)
439        /// let ke = x.sph_bessel_n::<Scaled<K>, 1>();     // e^x k_1(x)
440        /// ```
441        fn sph_bessel_n<F: BesselFamily, const N: usize>(self) -> Self;
442
443        /// [`sph_bessel_n`](SpecialMath::sph_bessel_n) for an order known only at runtime.
444        fn sph_bessel<F: BesselFamily>(self, n: u32) -> Self;
445
446        /// One Airy function selected by marker: [`Ai`](bessel::Ai), [`AiPrime`](bessel::AiPrime),
447        /// [`Bi`](bessel::Bi), [`BiPrime`](bessel::BiPrime), or any of them under
448        /// [`Scaled`](bessel::Scaled).
449        ///
450        /// Not a slice of [`airy_all`](SpecialMath::airy_all): the four outputs come from two
451        /// Bessel passes (order 1/3 for the values, 2/3 for the derivatives), and asking for
452        /// one runs one pass (`Ai` skips the `I` half of it too, so it is roughly a quarter
453        /// of the tuple). Take the tuple when you want more than one of them.
454        ///
455        /// ```rust,ignore
456        /// let ai = x.airy::<Ai>();
457        /// let bp = x.airy::<Scaled<BiPrime>>();      // e^{-zeta} Bi'(x) on the positive axis
458        /// ```
459        fn airy<W: AiryFn>(self) -> Self;
460
461        /// `$(\mathrm{Ai}, \mathrm{Ai}', \mathrm{Bi}, \mathrm{Bi}')$`, all four, with the
462        /// exponential factored out on the positive axis when `SCALED` (SciPy `airy` / `airye`).
463        ///
464        /// Prefer the scaled form on **accuracy** grounds, not only range: on the positive
465        /// axis the kernel produces `$e^{\zeta}K$` natively, so it evaluates no exponential
466        /// anywhere and holds 1-3 eps where the unscaled one reaches 684 at `x = 100`
467        /// (`$\zeta = \tfrac{2}{3}x^{3/2}$`). Unscaled, `Ai` underflows past `x ~ 104` and
468        /// `Bi` overflows past `x ~ 104.5`. For `x < 0` the functions oscillate, nothing is
469        /// factored out, and the phase error grows like `$|x|^{3/2}$` in every library.
470        fn airy_all<const SCALED: bool>(self) -> (Self, Self, Self, Self);
471
472        /// Computes the Beta function `$\mathrm{B}(x, y)$`
473        fn beta(self, y: Self) -> Self;
474
475        /// Computes `$\ln\left|\mathrm{B}(x, y)\right|$`, the log of the absolute Beta function.
476        ///
477        /// [`beta`](SpecialMath::beta) itself underflows to zero for quite ordinary arguments
478        /// (`$\mathrm{B}(200, 200)$` is about `1e-121`, already gone in f32) and overflows for
479        /// arguments straddling the poles. The log form has range to spare in both directions and is
480        /// what the surrounding computation usually wants anyway, since Beta almost always appears
481        /// inside a product of Gammas that is about to be logged.
482        ///
483        /// Evaluated as `$\ln\Gamma(x) + \ln\Gamma(y) - \ln\Gamma(x+y)$`. The absolute value follows
484        /// [`lgamma`](SpecialMath::lgamma), so recover the sign from
485        /// [`lgamma_r`](RealSpecialMath::lgamma_r) if the arguments can be negative.
486        ///
487        /// This buys range at some cost in relative accuracy. The three `lgamma` terms cancel
488        /// against each other, shedding roughly `$\log_{10}\frac{\ln\Gamma(x+y)}{|\ln \mathrm{B}|}$`
489        /// digits. That is under one digit at `$x = y = 200$`, and a little over two at
490        /// `$x = 200,\ y = 1$` where the terms are near 860 and the answer is near -5.3. It remains
491        /// far better conditioned than [`beta`](SpecialMath::beta), which simply has no value to
492        /// return across most of that domain.
493        fn lbeta(self, y: Self) -> Self;
494
495        /// Computes the m-th derivative of the n-th degree Jacobi polynomial
496        ///
497        /// A the special case where α and β are both zero, the Jacobi polynomial reduces to a
498        /// Legendre polynomial.
499        ///
500        /// **NOTE**: Given constant α, β or `n`, LLVM will happily optimize those away and unroll loops.
501        fn jacobi(self, alpha: Self, beta: Self, n: u32, m: u32) -> Self;
502
503        /// Computes the N-th degree physicists' [Hermite polynomial](https://en.wikipedia.org/wiki/Hermite_polynomials)
504        /// `$H_N(x)$` where `x` is `self` and `N` is the polynomial degree.
505        ///
506        /// Evaluated by the three-term recurrence
507        ///
508        /// ```math
509        /// H_{n+1}(x) = 2x\,H_n(x) - 2n\,H_{n-1}(x)
510        /// ```
511        ///
512        /// seeded with `$H_0 = 1$` and `$H_1(x) = 2x$`. The trip count is `N`, with no data
513        /// dependence, so LLVM unrolls the whole thing into straight-line FMA.
514        ///
515        /// The derivative is another member of the same family, `$H_n'(x) = 2n\,H_{n-1}(x)$`, so a
516        /// value-and-slope pair costs one extra call rather than a separate kernel. The
517        /// probabilists' polynomials are a rescaling, `$He_n(x) = 2^{-n/2} H_n(x/\sqrt{2})$`.
518        ///
519        /// **NOTE**: this is the raw polynomial, which grows fast: `$H_n(0) = (-2)^{n/2} (n-1)!!$` for
520        /// even `n`, and `$H_n(x) \sim (2x)^n$` in the tails. It leaves binary32 range at the origin
521        /// around degree 48 and binary64 around 300, and much earlier for `|x|` of a few units. If
522        /// what you actually want is the *normalized* Hermite function (the quantum harmonic
523        /// oscillator eigenstate, a Hermite-Gauss beam mode, or the basis of a Hermite spectral
524        /// method), use [`hermite_function`](SpecialMath::hermite_function), which folds the
525        /// Gaussian weight and the normalization into the recurrence and stays `$O(1)$` at every
526        /// degree. The raw polynomial is the right primitive for Gauss-Hermite quadrature
527        /// node-finding at modest `n` and for anything that genuinely wants `$H_n$` itself.
528        fn hermite_n<const N: usize>(self) -> Self;
529
530        /// Computes the n-th degree physicists' [Hermite polynomial](https://en.wikipedia.org/wiki/Hermite_polynomials)
531        /// `H_n(x)` where `x` is `self` and `n` is a vector of unsigned integers representing the polynomial degree.
532        ///
533        /// The polynomial is calculated independently per-lane with the given degree in `n`.
534        ///
535        /// This uses the recurrence relation to compute the polynomial iteratively.
536        fn hermitev(self, n: Self::Unsigned) -> Self;
537
538        /// `$H_n(x)$` for a degree known only at runtime: [`hermitev`](SpecialMath::hermitev)
539        /// with the degree splatted, which is the cheapest correct spelling of a uniform degree.
540        /// The runtime twin of [`hermite_n`](SpecialMath::hermite_n).
541        fn hermite(self, n: u32) -> Self;
542
543        /// Computes the orthonormal [Hermite function](https://en.wikipedia.org/wiki/Hermite_polynomials#Hermite_functions)
544        ///
545        /// ```math
546        /// \psi_N(x) = \frac{1}{\sqrt{2^N N! \sqrt{\pi}}}\, e^{-x^2/2}\, H_N(x)
547        /// ```
548        ///
549        /// where `x` is `self`. These are the eigenfunctions of the quantum harmonic oscillator
550        /// and of the Fourier transform, the Hermite-Gauss modes of a paraxial beam, and the
551        /// basis of Hermite spectral methods. They are orthonormal on the whole line,
552        /// `$\int \psi_m \psi_n\, dx = \delta_{mn}$`.
553        ///
554        /// Evaluated by the recurrence on the functions themselves,
555        ///
556        /// ```math
557        /// \psi_{n+1}(x) = \sqrt{\tfrac{2}{n+1}}\, x\, \psi_n(x) - \sqrt{\tfrac{n}{n+1}}\, \psi_{n-1}(x)
558        /// ```
559        ///
560        /// which keeps every intermediate `$O(1)$` (the polynomial's growth and the Gaussian's
561        /// decay cancel inside each step), so unlike [`hermite`](SpecialMath::hermite) it does not
562        /// overflow at high degree. Both square roots are literals under the unrolled loop. The
563        /// per-step cost is one FMA on the critical path.
564        ///
565        /// # Range
566        ///
567        /// The only quantity that can leave the exponent range is the Gaussian seed, which is
568        /// carried as `$e^{-x^2/4}$` in two halves to double the reach. Full accuracy at every
569        /// degree holds for `$|x|$` under about 18.7 (binary32) or 53 (binary64), which covers
570        /// every degree up to about 175 / 1400 everywhere on the line, since past the turning
571        /// point `$\sqrt{2n+1}$` the true value decays faster than the seed. Beyond that the result
572        /// is still correct wherever `$e^{-x^2/4}$` is representable, and zero past it.
573        ///
574        /// Under a `Best`-or-better precision policy on true-FMA hardware, the rounding of `$x^2$`
575        /// (which is the entire error budget of a Gaussian at large `x`) is recovered exactly and
576        /// corrected to first order.
577        fn hermite_function_n<const N: usize>(self) -> Self;
578
579        /// `$\psi_n(x)$` for a degree known only at runtime. The runtime twin of
580        /// [`hermite_function_n`](SpecialMath::hermite_function_n): the same seed and recurrence,
581        /// with the per-step constants computed rather than folded.
582        fn hermite_function(self, n: u32) -> Self;
583
584        /// Evaluates a finite series of Hermite functions at `x = self`:
585        ///
586        /// ```math
587        /// \sum_{k=0}^{N-1} \mathrm{coeffs}[k] \cdot \psi_k(x)
588        /// ```
589        ///
590        /// with `$\psi_k$` as in [`hermite_function`](SpecialMath::hermite_function). Evaluated by
591        /// Clenshaw's backward recurrence, which is more stable than summing the functions one at
592        /// a time and never forms them individually. `N` is the *length* of the coefficient array,
593        /// so the highest function is `$\psi_{N-1}$`; `N = 0` is rejected.
594        ///
595        /// Same range as [`hermite_function`](SpecialMath::hermite_function): the coefficients are
596        /// pre-scaled by half of the Gaussian and the outer factor carries the other half, so the
597        /// running Clenshaw values grow no faster than `$e^{x^2/4}$`.
598        #[skip_dispatch] #[compose] fn hermite_function_series_n<const N: usize>(self, coeffs: &[Self::Element; N]) -> Self;
599
600        /// [`hermite_function_series_n`](SpecialMath::hermite_function_series_n) over a
601        /// runtime-length coefficient slice.
602        ///
603        /// Same recurrence, same pre-scaling, same range. The length is the only difference,
604        /// and it costs real work rather than only unrolling: the recurrence coefficients
605        /// `$\sqrt{2/(k+1)}$` and `$\sqrt{k/(k+1)}$` fold to literals when `N` is a constant
606        /// and become per-step square roots when it is not. Prefer the const form when the
607        /// degree is known.
608        ///
609        /// An empty coefficient slice is `0`, where the const form rejects `N = 0` at compile
610        /// time.
611        #[skip_dispatch] #[compose] fn hermite_function_series(self, coeffs: &[Self::Element]) -> Self;
612
613        /// Computes the generalized (associated) [Laguerre polynomial](https://en.wikipedia.org/wiki/Laguerre_polynomials)
614        /// `$L_N^{(\alpha)}(x)$`, where `x` is `self` and `N` is the polynomial degree.
615        ///
616        /// Passing `alpha = Self::ZERO` gives the ordinary Laguerre polynomial `$L_N(x)$`; because
617        /// `alpha` is an ordinary argument rather than a const generic, that case folds away
618        /// completely when the zero is visible at the call site.
619        ///
620        /// Evaluated by the three-term recurrence
621        ///
622        /// ```math
623        /// (n+1)\,L_{n+1}^{(\alpha)}(x) = (2n + \alpha + 1 - x)\,L_n^{(\alpha)}(x) - (n + \alpha)\,L_{n-1}^{(\alpha)}(x)
624        /// ```
625        ///
626        /// seeded with `$L_0^{(\alpha)} = 1$` and `$L_1^{(\alpha)}(x) = 1 + \alpha - x$`. The trip count
627        /// is `N`, with no data dependence, so LLVM unrolls the whole thing into straight-line FMA.
628        ///
629        /// The derivative is another member of the same family,
630        /// `$\frac{\mathrm{d}}{\mathrm{d}x} L_n^{(\alpha)}(x) = -L_{n-1}^{(\alpha+1)}(x)$`, so a
631        /// value-and-slope pair costs one extra call rather than a separate kernel.
632        ///
633        /// **NOTE**: the forward recurrence is the standard evaluation route (Boost and GSL both use
634        /// it) and is well behaved across the oscillatory region `$0 \le x \lesssim 4n$`. Past that
635        /// `$L_n^{(\alpha)}$` itself grows like `$(-x)^n/n!$` and will overflow for large `N` and `x`
636        /// on its own account.
637        ///
638        /// Laguerre-Gaussian beam modes, the radial part of the hydrogen wavefunction, the quantum
639        /// harmonic oscillator and coherent-state expansions, and Gauss-Laguerre quadrature.
640        fn laguerre_n<const N: usize>(self, alpha: Self) -> Self;
641
642        /// Computes the generalized (associated) [Laguerre polynomial](https://en.wikipedia.org/wiki/Laguerre_polynomials)
643        /// `$L_n^{(\alpha)}(x)$` where `n` is a vector of unsigned integers giving the degree per lane.
644        ///
645        /// The per-lane counterpart of [`laguerre`](SpecialMath::laguerre), in the same relation to it
646        /// as [`hermitev`](SpecialMath::hermitev) is to [`hermite`](SpecialMath::hermite). The
647        /// recurrence runs to the largest `n` in the vector and lanes freeze at their own degree, so
648        /// the cost is set by `max(n)` rather than by any one lane.
649        fn laguerrev(self, alpha: Self, n: Self::Unsigned) -> Self;
650
651        /// `$L_n^{(\alpha)}(x)$` for a degree known only at runtime:
652        /// [`laguerrev`](SpecialMath::laguerrev) with the degree splatted. The runtime twin of
653        /// [`laguerre_n`](SpecialMath::laguerre_n).
654        fn laguerre(self, alpha: Self, n: u32) -> Self;
655
656        /// Computes the orthonormal generalized [Laguerre function](https://en.wikipedia.org/wiki/Laguerre_polynomials#Generalized_Laguerre_polynomials)
657        ///
658        /// ```math
659        /// l_N^{(\alpha)}(x) = \sqrt{\frac{N!}{\Gamma(N+\alpha+1)}}\; x^{\alpha/2} e^{-x/2}\, L_N^{(\alpha)}(x)
660        /// ```
661        ///
662        /// where `x` is `self`. Orthonormal on the half-line, `$\int_0^\infty l_m l_n\, dx = \delta_{mn}$`.
663        /// This is the radial factor of Laguerre-Gauss beam modes and (up to a power of `x` from the
664        /// spherical measure) of the hydrogen wavefunctions. Defined for `$x \ge 0$` and
665        /// `$\alpha > -1$`, and nothing is checked outside that.
666        ///
667        /// Evaluated by the recurrence on the functions themselves, with
668        /// `$s_k = \sqrt{(k+1)(k+\alpha+1)}$`:
669        ///
670        /// ```math
671        /// l_{k+1} = \frac{(2k + \alpha + 1 - x)\, l_k - s_{k-1}\, l_{k-1}}{s_k}
672        /// ```
673        ///
674        /// which keeps every intermediate `$O(1)$`, so unlike [`laguerre`](SpecialMath::laguerre)
675        /// it does not overflow at high degree or large `x`. `alpha` is a runtime vector, so each
676        /// step also carries a `sqrt` and a reciprocal, beside the recurrence rather than on its
677        /// critical path, and folded to literals when `alpha` is a visible constant. The seed
678        /// is skipped outright by a uniform branch when every lane has `alpha = 0`, which is the
679        /// ordinary Laguerre function and by far the common case.
680        ///
681        /// # Range
682        ///
683        /// The Gaussian-like seed `$x^{\alpha/2} e^{-x/2}$` is carried as `$e^{-x/4}$` in two
684        /// halves, as in [`hermite_function`](SpecialMath::hermite_function). Full accuracy at
685        /// every degree for `x` under about 350 (binary32) or 2800 (binary64), covering every
686        /// degree up to roughly 87 / 700 everywhere on the half-line (the turning point of
687        /// `$l_n^{(\alpha)}$` is near `4n`).
688        ///
689        /// `alpha` is unrestricted over the same `x` range. The seed's whole parameter
690        /// dependence, `$x^{\alpha/2}/\sqrt{\Gamma(\alpha+1)}$`, is the square root of the Poisson
691        /// mass `$P(\alpha; x)$` and is evaluated as [`poisson_pmf`](SpecialMath::poisson_pmf)
692        /// is (Loader's saddle-point form, one exponential of a small exponent), so neither
693        /// factor materializes (separately `$x^{\alpha/2}$` overflows binary64 near
694        /// `$\alpha = 250$` and `$1/\sqrt{\Gamma(\alpha+1)}$` underflows near `$\alpha = 320$`,
695        /// and their overlap would be `inf * 0`) and nothing large is exponentiated: 0-3 ulp
696        /// at the peak `x ~ alpha` out to `$\alpha = 1400$`, against a 50-digit oracle.
697        fn laguerre_function_n<const N: usize>(self, alpha: Self) -> Self;
698
699        /// `$\ell_n^{(\alpha)}(x)$` for a degree known only at runtime. The runtime twin of
700        /// [`laguerre_function_n`](SpecialMath::laguerre_function_n): the same seed and
701        /// recurrence, with the per-step scales computed rather than folded.
702        fn laguerre_function(self, alpha: Self, n: u32) -> Self;
703
704        /// [`laguerre_function`](SpecialMath::laguerre_function) at an integer weight, taken as a
705        /// **scalar** `i32` rather than a vector.
706        ///
707        /// Same function and same range. What changes is what the compiler can see. Every
708        /// quantity the recurrence derives from the weight (the `$s_k = \sqrt{(k+1)(k+\alpha+1)}$`
709        /// and their reciprocals, and the `$2k+\alpha+1$` offsets) becomes a scalar constant
710        /// instead of a vector `sqrt` and reciprocal per step, and folds to a literal outright
711        /// when `alpha` is compile-time known.
712        ///
713        /// The seed changes too. Up to `$\alpha = 170$` (binary64) / `29` (binary32) the
714        /// normalization `$x^{\alpha/2}/\sqrt{\alpha!}$` is a scalar factorial, a `powi` and at
715        /// most one `sqrt`, with no `ln`, `lgamma` or second `exp` at all, and a few ulp *more*
716        /// accurate than the log form, whose `lgamma` error is amplified by the exponential.
717        /// `$\alpha = 0$` is a scalar test that skips even that. Beyond the cap it takes
718        /// the vector form's saddle-point seed. Measured on AVX2 f64x4 at degree 4:
719        /// about 5x faster than the vector form at a literal small weight, 2x at a runtime one.
720        ///
721        /// Prefer this whenever the weight is a non-negative integer, which every classical
722        /// application has: the hydrogen radial functions use `$\alpha = 2\ell+1$` and the
723        /// Laguerre-Gauss beam modes use `$\alpha = |\ell|$`. Negative values are out of domain,
724        /// as `$\alpha \le -1$` is for the general form.
725        ///
726        /// Like the series forms this is inlined into the caller rather than given its own
727        /// dispatch trampoline: the weight is a plain `i32` argument, and a shared
728        /// out-of-line copy would take it at runtime, which both defeats the folding above
729        /// and (measured) stops LLVM overlapping consecutive evaluations, at 7x the cost.
730        /// Call it from inside a `#[thermite::dispatch]` body.
731        #[skip_dispatch] #[compose] fn laguerre_function_i_n<const N: usize>(self, alpha: i32) -> Self;
732
733        /// [`laguerre_function_i_n`](SpecialMath::laguerre_function_i_n) for a degree known only
734        /// at runtime.
735        #[skip_dispatch] #[compose] fn laguerre_function_i(self, alpha: i32, n: u32) -> Self;
736
737        /// Evaluates a finite series of generalized Laguerre functions at `x = self`:
738        ///
739        /// ```math
740        /// \sum_{k=0}^{N-1} \mathrm{coeffs}[k] \cdot l_k^{(\alpha)}(x)
741        /// ```
742        ///
743        /// with `$l_k^{(\alpha)}$` as in [`laguerre_function`](SpecialMath::laguerre_function).
744        /// Clenshaw's backward recurrence, same range as the single function; `N` is the
745        /// coefficient count and `N = 0` is rejected.
746        #[skip_dispatch] #[compose] fn laguerre_function_series_n<const N: usize>(self, alpha: Self, coeffs: &[Self::Element; N]) -> Self;
747
748        /// [`laguerre_function_series_n`](SpecialMath::laguerre_function_series_n) over a
749        /// runtime-length coefficient slice.
750        ///
751        /// Same recurrence, same pre-scaling, same range. The per-step weights are computed
752        /// rather than folded, as in
753        /// [`hermite_function_series`](SpecialMath::hermite_function_series). An empty
754        /// coefficient slice is `0`.
755        #[skip_dispatch] #[compose] fn laguerre_function_series(self, alpha: Self, coeffs: &[Self::Element]) -> Self;
756
757        /// [`laguerre_function_series`](SpecialMath::laguerre_function_series) at a scalar integer
758        /// weight, in the same relation to it as
759        /// [`laguerre_function_i`](SpecialMath::laguerre_function_i) is to
760        /// [`laguerre_function`](SpecialMath::laguerre_function). See there for what the integer
761        /// form buys.
762        #[skip_dispatch] #[compose] fn laguerre_function_series_i_n<const N: usize>(self, alpha: i32, coeffs: &[Self::Element; N]) -> Self;
763
764        /// [`laguerre_function_series_i_n`](SpecialMath::laguerre_function_series_i_n) over a
765        /// runtime-length coefficient slice.
766        ///
767        /// The `_n` is the coefficient count and the `_i` is the integer weight, in that
768        /// order because the length is the newer axis, and both mean what they do everywhere else.
769        /// An empty coefficient slice is `0`.
770        #[skip_dispatch] #[compose] fn laguerre_function_series_i(self, alpha: i32, coeffs: &[Self::Element]) -> Self;
771
772        /// Evaluates a finite series of [Chebyshev polynomials](https://en.wikipedia.org/wiki/Chebyshev_polynomials)
773        /// of the `K`-th kind at `x = self`:
774        ///
775        /// ```math
776        /// \sum_{k=0}^{N-1} \mathrm{coeffs}[k] \cdot P_k(x)
777        /// ```
778        ///
779        /// where `P_k` is `T_k`, `U_k`, `V_k`, or `W_k` depending on `K`. All four kinds share the
780        /// recurrence `$P_{k+1}(x) = 2x \cdot P_k(x) - P_{k-1}(x)$` with `P_0(x) = 1`, and differ only in
781        /// `P_1(x)`:
782        ///
783        /// | `K` | Kind   | `P_1(x)`   | Notes |
784        /// |-----|--------|------------|-------|
785        /// | `1` | First  (`T_k`) | `x`        | Most common, the minimax/approximation basis on `[-1, 1]`. |
786        /// | `2` | Second (`U_k`) | `2x`       | Related to `$\sin((k+1)\theta)/\sin(\theta)$` under `$x = \cos\theta$`. |
787        /// | `3` | Third  (`V_k`) | `2x - 1`   | "Airfoil" polynomials; `$\cos((k+\tfrac12)\theta)/\cos(\theta/2)$`. |
788        /// | `4` | Fourth (`W_k`) | `2x + 1`   | `$\sin((k+\tfrac12)\theta)/\sin(\theta/2)$`. |
789        ///
790        /// Any other value of `K` is a compile-time error.
791        ///
792        /// There is deliberately no single-polynomial `T_n(x)` entry point beside this, unlike
793        /// [`legendre`](SpecialMath::legendre) or [`hermite`](SpecialMath::hermite). Chebyshev
794        /// polynomials are used almost exclusively as an approximation basis, i.e. as a series;
795        /// their quadrature nodes and weights are closed-form, so nothing needs to iterate on a
796        /// lone `$T_n$`; and the one genuine single-`$T_n$` application (Chebyshev filter response,
797        /// Dolph-Chebyshev windows) needs `$|x| > 1$`, where the right evaluation is
798        /// `$\cosh(n \cosh^{-1} x)$` and not this recurrence at all. A unit coefficient array
799        /// recovers `$T_n$` if it is ever wanted.
800        ///
801        /// Evaluation is done via Clenshaw's backward recurrence with FMA, which is
802        /// more numerically stable than a forward sum when the partial sums of
803        /// `$\sum c_k P_k$` are much smaller than `$\max_k |c_k P_k|$` (e.g. fitted minimax series
804        /// with alternating-sign coefficients). `N` is the *length* of the coefficient
805        /// slice, so the highest polynomial term is `P_{N-1}`; `N = 0` is rejected,
806        /// `N = 1` evaluates to `coeffs[0]`.
807        ///
808        /// `coeffs[0]` multiplies `P_0 = 1`, `coeffs[1]` multiplies `P_1(x)` (which depends on `K`),
809        /// and so on. Because LLVM sees both `K` and `N` as constants, the recurrence loop and the
810        /// `P_1` selection are fully unrolled and specialized at monomorphization time.
811        ///
812        /// # Accuracy near `$x = \pm 1$`
813        ///
814        /// The plain recurrence forms `$2x b_{k+1} - b_{k+2}$` with consecutive `$b_k$` of nearly
815        /// equal magnitude as `x` approaches either endpoint, and cancels. This is a property of
816        /// the *recurrence*, not of the series: measured against a 60-digit oracle at `N = 24`,
817        /// it costs up to 37 ulp on sums whose own condition number is about 1, and up to 230 ulp
818        /// on unstructured coefficients.
819        ///
820        /// Under a `Best`-or-better precision policy, real vectors instead take Reinsch's
821        /// modification, which recurs on the differences (near `+1`) or sums (near `-1`) so the
822        /// small quantity is never formed by subtraction. On the same grid that bounds the error
823        /// envelope 2.5x to 17x tighter across all four kinds. It is an envelope improvement
824        /// rather than a pointwise one (individual arguments can land worse), and costs
825        /// roughly 2x on the recurrence's dependency chain, which is why it is gated.
826        ///
827        /// binary32 gains the same way, 2.6x to 13.5x on its own grid. Measuring it needs an
828        /// f32-native one: `1 - 2^-j` rounds to exactly `1.0` for every `j >= 24`, so an f64
829        /// grid piles two thirds of its points onto the endpoint itself, where the endpoint
830        /// form degenerates into a plain running sum and the two policies agree, and never
831        /// samples the f32 neighbourhood where the cancellation actually bites.
832        ///
833        /// Coefficients from a minimax or least-squares *fit* decay geometrically and barely
834        /// notice either way (about 3 ulp to 1). The gap opens on slowly-decaying or
835        /// non-decaying spectra: truncated expansions, near-singular functions, or coefficients
836        /// that came from somewhere other than a fit.
837        ///
838        /// `Complex` and the composite arithmetics keep the plain recurrence at every policy,
839        /// since Reinsch needs a real `copysign` and a meaningful nearest endpoint.
840        #[skip_dispatch] #[compose] fn chebyshev_n<const K: usize, const N: usize>(self, coeffs: &[Self::Element; N]) -> Self;
841
842        /// [`chebyshev_n`](SpecialMath::chebyshev_n) over a runtime-length coefficient slice.
843        ///
844        /// `K` stays a const generic, since it selects *which* Chebyshev kind, not how many
845        /// coefficients, and there are exactly four. Only the length becomes dynamic.
846        ///
847        /// Same recurrence and the same `Best`-precision Reinsch form near `$x = \pm 1$`; what
848        /// the runtime length costs is the unrolling and the folded `coeffs` indices. An empty
849        /// coefficient slice is `0`.
850        #[skip_dispatch] #[compose] fn chebyshev<const K: usize>(self, coeffs: &[Self::Element]) -> Self;
851
852        /// Computes the Gaussian function with amplitude `a` and standard deviation `c`, defined as `$a\, e^{-\frac{1}{2}(x/c)^2}$`.
853        ///
854        /// The position `b` is assumed to be zero. For a non-zero position, use `self - b` as the input.
855        fn gaussian(self, a: Self, c: Self) -> Self;
856
857        /// Computes the Planck shape factor `$\frac{x^3}{e^x - 1}$`, finite at `x = 0` where it
858        /// vanishes like `$x^2$`.
859        ///
860        /// The dimensionless kernel of Planck's law: substituting `$x = h\nu/kT$` recovers the
861        /// spectral radiance up to a scale factor, so this is the part worth computing carefully and
862        /// the constants are left to the caller. Radiative transfer, climate radiation budgets, and
863        /// stellar atmospheres.
864        ///
865        /// The denominator cancels for small `x` and the quotient is `$0/0$` at the origin.
866        /// Evaluated here as `$x^2/\varphi_1(x)$` using `phi_n::<1>`, which is finite and equal
867        /// to 1 there, so the singularity never forms rather than being patched after the fact.
868        fn planck(self) -> Self;
869
870        /// Computes the m-th associated n-th degree Legendre polynomial,
871        /// where m=0 signifies the regular n-th degree Legendre polynomial.
872        ///
873        /// If `m` is odd, the input is only valid between -1 and 1
874        ///
875        /// **NOTE**: Given constant `n` and/or `m`, LLVM will happily unroll and optimize inner loops.
876        ///
877        /// Internally, this is computed with [`jacobi`](SpecialMath::jacobi) when m > 0.
878        fn legendre(self, n: u32, m: u32) -> Self;
879
880        /// Evaluates a finite [Legendre series](https://en.wikipedia.org/wiki/Legendre_polynomials)
881        /// at `x = self`:
882        ///
883        /// ```math
884        /// \sum_{k=0}^{N-1} \mathrm{coeffs}[k] \cdot P_k(x)
885        /// ```
886        ///
887        /// The form a Legendre-moment expansion takes: Mie and Henyey-Greenstein scattering
888        /// phase functions tabulated by their moments, multipole expansions in `$\cos\theta$`, and
889        /// the polar factor of a spherical-harmonic expansion at fixed order.
890        ///
891        /// Evaluated by Clenshaw's backward recurrence on the Legendre three-term relation, which
892        /// is more stable than building each `$P_k$` with [`legendre`](SpecialMath::legendre) and
893        /// summing, and does `$O(N)$` work rather than `$O(N^2)$`. The recurrence ratios
894        /// `$(2k+1)/(k+1)$` and `$k/(k+1)$` are literals under the unrolled loop, so the per-step
895        /// cost matches [`chebyshev`](SpecialMath::chebyshev): one FMA on the critical path. `N`
896        /// is the coefficient count; `N = 0` is rejected, `N = 1` evaluates to `coeffs[0]`.
897        ///
898        /// Plain Clenshaw at every policy: the endpoint cancellation that `chebyshev` treats
899        /// under `Best` precision exists here too (`$P_n(1) = 1$` for every `n`), but its
900        /// Reinsch-style rewrite for the Legendre ratios has not been derived or measured.
901        #[skip_dispatch] #[compose] fn legendre_series_n<const N: usize>(self, coeffs: &[Self::Element; N]) -> Self;
902
903        /// [`legendre_series_n`](SpecialMath::legendre_series_n) over a runtime-length
904        /// coefficient slice.
905        ///
906        /// Plain Clenshaw here too. The recurrence ratios `$(2k+1)/(k+1)$` and `$k/(k+1)$` are
907        /// literals only when `N` is a constant, so this pays a division per step where the
908        /// const form pays none, the widest const-versus-slice gap of the series family.
909        /// An empty coefficient slice is `0`.
910        #[skip_dispatch] #[compose] fn legendre_series(self, coeffs: &[Self::Element]) -> Self;
911
912        /// Computes the [Zernike](https://en.wikipedia.org/wiki/Zernike_polynomials) radial
913        /// polynomial `$R_n^m(\rho)$`, where `rho` is `self`.
914        ///
915        /// Returns zero unless `$m \le n$` with `$n - m$` even, the condition for the mode to
916        /// exist. `m` is the *absolute* azimuthal frequency here. The sign only affects the
917        /// angular factor, which lives in [`zernike`](SpecialMath::zernike).
918        ///
919        /// Evaluated through the shifted Jacobi identity
920        ///
921        /// ```math
922        /// R_n^m(\rho) = \rho^m\, P_{(n-m)/2}^{(0,\,m)}\!\left(2\rho^2 - 1\right)
923        /// ```
924        ///
925        /// rather than the textbook sum
926        /// `$\sum_k (-1)^k \frac{(n-k)!}{k!\,((n+m)/2 - k)!\,((n-m)/2 - k)!} \rho^{n-2k}$`, which
927        /// alternates factorials of size `$(n-k)!$` against an answer bounded by 1 and loses all
928        /// precision somewhere around `n = 10-15`. That is well inside the range adaptive optics,
929        /// ophthalmology and surface metrology actually use.
930        ///
931        /// The `$(-1)^{(n-m)/2}$` prefactor usually seen with this identity is absent because the
932        /// argument is written `$2\rho^2 - 1$` rather than `$1 - 2\rho^2$`: reflecting a Jacobi
933        /// polynomial swaps its two parameters and absorbs exactly that sign.
934        ///
935        /// The polynomial is only orthogonal on `$\rho \in [0, 1]$` and grows quickly outside it.
936        /// Nothing clamps the argument, so an unnormalized pupil coordinate stays the caller's
937        /// problem.
938        fn zernike_r(self, n: u32, m: u32) -> Self;
939
940        /// Computes the Zernike polynomial `$Z_n^m(\rho, \theta)$` on the unit disc, with `rho`
941        /// as `self`:
942        ///
943        /// ```math
944        /// Z_n^m(\rho, \theta) = N_n^m\, R_n^{|m|}(\rho) \times
945        ///   \begin{cases} \cos(m\theta) & m \ge 0 \\ \sin(|m|\theta) & m < 0 \end{cases}
946        /// ```
947        ///
948        /// Returns zero unless `$|m| \le n$` with `$n - |m|$` even.
949        ///
950        /// `NORM` selects the normalization `$N_n^m$` and must be either
951        /// [`ZERNIKE_UNIT_PEAK`] (`$N = 1$`, so `$R_n^m(1) = 1$` and coefficients read as peak
952        /// amplitude) or [`ZERNIKE_ORTHONORMAL`]
953        /// (`$N_n^m = \sqrt{2(n+1)/(1 + \delta_{m,0})}$`, the ANSI Z80.28 and Noll convention,
954        /// under which coefficients read as RMS contributions). Any other value is a compile-time
955        /// error. There is deliberately no default: the two differ by a factor of up to
956        /// `$\sqrt{2(n+1)}$` per mode, and picking one silently is how coefficient sets get
957        /// misinterpreted.
958        ///
959        /// `(n, m)` is a runtime pair rather than a const generic on purpose. The workload is a
960        /// basis, not a function. A wavefront fit evaluates tens to hundreds of modes over
961        /// thousands of pupil samples, with the mode list coming from a config or a sensor
962        /// geometry, so the degree is loop-invariant across the vector axis and const-generic
963        /// specialization would buy a jump table rather than an unrolled loop.
964        ///
965        /// The single-index conventions (ANSI Z80.28 / OSA, Noll, Fringe) and the conversions
966        /// between them are in [`crate::zernike`]. They disagree from the second term
967        /// onward, so convert at the boundary rather than assuming.
968        fn zernike<const NORM: u8>(self, theta: Self, n: u32, m: i32) -> Self;
969
970        /// Evaluates **all** Zernike modes through degree `L` at the Cartesian pupil point
971        /// `(x, y)`, into `out[j]` for the ANSI Z80.28 / OSA index `$j = (n(n+2) + m)/2$`.
972        ///
973        /// `N` must equal `(L+1)(L+2)/2` (compile-time checked), and `NORM` is
974        /// [`ZERNIKE_UNIT_PEAK`] or [`ZERNIKE_ORTHONORMAL`] as on
975        /// [`zernike`](SpecialMath::zernike).
976        ///
977        /// This is the entry point a wavefront fit or reconstruction wants. It is not merely
978        /// a loop over [`zernike`](SpecialMath::zernike). Substituting `$s = x^2+y^2$`
979        /// splits every mode into a polynomial in `s` times `$\operatorname{Re}$` or
980        /// `$\operatorname{Im}$` of `$(x+iy)^{|m|}$`, which is where the `$\rho^{|m|}$` and the
981        /// `$\cos m\theta$` both come from at once. Evaluation is then **pure polynomial
982        /// arithmetic**: no `atan2`, no `sqrt`, no trigonometry, no division, `$O(L^2)$` FMAs
983        /// for the entire basis, and no singularity at the pupil centre. Calling the
984        /// single-mode form per mode instead costs a `sin_cos` and a `powi` each and restarts
985        /// the radial recurrence every time, for `$O(L^3)$` work.
986        ///
987        /// Cartesian input is part of that, not a convenience: pupil samples arrive as
988        /// `(x, y)`, and a polar entry point would charge an `atan2` per sample for an angle
989        /// this kernel immediately dissolves.
990        ///
991        /// Fully unrolled at compile time for each `L` up to
992        /// [`MAX_ZERNIKE_DEGREE`](specialized::MAX_ZERNIKE_DEGREE); above that it takes a
993        /// rolled path that is correct at any degree and substantially slower.
994        ///
995        /// Nothing normalizes `(x, y)` onto the unit disc. Outside it the polynomials still
996        /// evaluate correctly and simply are not orthogonal.
997        ///
998        /// The layout is ANSI because it is the scheme whose index has a closed form *and*
999        /// whose degree truncation is contiguous. Noll and Fringe callers gather through
1000        /// [`noll_to_ansi`](crate::zernike::noll_to_ansi) /
1001        /// [`fringe_to_ansi`](crate::zernike::fringe_to_ansi).
1002        ///
1003        /// ```
1004        /// use thermite::prelude::*;
1005        /// use thermite_special::{SpecialMath, ZERNIKE_ORTHONORMAL};
1006        /// use thermite_special::zernike::noll_to_ansi;
1007        ///
1008        /// type V = Vector<f64>;
1009        /// const L: usize = 4;
1010        /// const N: usize = 15; // (L+1)(L+2)/2
1011        ///
1012        /// let mut basis = [V::ZERO; N];
1013        /// V::zernike_basis::<L, ZERNIKE_ORTHONORMAL, N>(V::splat(0.3), V::splat(0.4), &mut basis);
1014        ///
1015        /// // Noll 4 is defocus, Z_2^0 = sqrt(3) (2 rho^2 - 1) orthonormal.
1016        /// let defocus = basis[noll_to_ansi(4) as usize].extract::<0>();
1017        /// assert!((defocus - 3f64.sqrt() * (2.0 * 0.25 - 1.0)).abs() < 1e-14);
1018        /// ```
1019        #[skip_dispatch] #[compose] fn zernike_basis<const L: usize, const NORM: u8, const N: usize>(x: Self, y: Self, out: &mut [Self; N]) -> ();
1020
1021        /// Computes both branches of the Lambert W function simultaneously: (`$W_0(x)$`, `$W_{-1}(x)$`).
1022        ///
1023        /// The `$W_0$` result is valid for `x >= -1/e`; the `$W_{-1}$` result is valid for `-1/e <= x < 0`.
1024        /// Outside these domains, the respective result is NaN (when overflow checking is enabled).
1025        fn lambert_w(self) -> (Self, Self);
1026
1027        // TEMP(bessel_j): disabled until orders beyond J_0 exist. Only f32 `J_0` was
1028        // ever implemented, so every composite type (Dual, Complex, Compensated) could
1029        // do nothing but `todo!()`. Re-enable this line and the ones marked
1030        // TEMP(bessel_j) elsewhere together.
1031        //fn bessel_j<const N: i32>(self) -> Self;
1032
1033        /// Computes the generalized exponential integral `E_n(x)` for integer order `n`.
1034        #[doc(alias = "expn")]
1035        #[doc(alias = "exp1")]
1036        fn expint_n<const N: usize>(self) -> Self;
1037
1038        /// `E_n(x)` for an order known only at runtime. The runtime twin of
1039        /// [`expint_n`](SpecialMath::expint_n): the same `E_1` kernel, the same recurrence and the
1040        /// same continued-fraction handover, so the two agree to the bit.
1041        fn expint(self, n: u32) -> Self;
1042
1043        /// Returns `$\varphi_N(x)$`, the `N`-th phi-function of exponential integrators.
1044        ///
1045        /// ```math
1046        /// \varphi_0(x) = e^x, \qquad
1047        /// \varphi_{k+1}(x) = \frac{\varphi_k(x) - 1/k!}{x}, \qquad
1048        /// \varphi_k(x) = \sum_{n \ge 0} \frac{x^n}{(n + k)!}, \qquad
1049        /// \varphi_k(0) = \frac{1}{k!}
1050        /// ```
1051        ///
1052        /// `phi_n::<0>` is `exp`. `phi_n::<1>` is `$(e^x - 1)/x$`, which written out
1053        /// directly is `$0/0$` at the origin and loses most of the mantissa near it, so it is
1054        /// evaluated as `$\mathrm{expm1}(x)/x$` with the removable singularity filled in (the
1055        /// value is 1), which is accurate across the whole line. Outside the
1056        /// exponential-integrator literature `phi_n::<1>` goes by **`exprel`**, which is the name
1057        /// SciPy, Boost and the statistics literature use for it. There is no separate
1058        /// `exprel` here because this is it. Beyond that the recurrence is
1059        /// the wrong way to compute them: each step subtracts `1/k!` from a value that is barely
1060        /// larger while `|x|` is small, so `$\varphi_2 = (\mathrm{expm1}(x) - x)/x^2$` loses twice the bits
1061        /// `phi_n::<1>` would have, and gets worse with `N`. Below `|x| = N` this sums the series
1062        /// instead (its terms are monotone there, so nothing cancels), and above it runs the
1063        /// recurrence upward from `expm1`, where the amplification per step is bounded. Measured
1064        /// against mpmath, both arms sit within a few ulp for `N <= 8`.
1065        ///
1066        /// The series arm's length is bounded by the policy's `max_iterations`. The primitive
1067        /// float types know their precision statically and use a fixed count instead. Nothing
1068        /// caps `N`, though nothing needs it large: ETDRK4 wants `phi_1..phi_3`, and exponential
1069        /// Rosenbrock methods rarely go past `phi_4`.
1070        ///
1071        /// `phi_n::<1>` alone is the coefficient that keeps appearing wherever an exponential is
1072        /// integrated over a finite step:
1073        ///
1074        /// * The in-scattering integral through a homogeneous medium,
1075        ///   `$\int_0^t e^{-\sigma s}\,ds = t\,\varphi_1(-\sigma t)$`. The singular case is the empty
1076        ///   medium, which is not an edge case in practice.
1077        /// * Exact stepping of an Ornstein-Uhlenbeck process, and the Langevin thermostat's
1078        ///   mean-reversion factor.
1079        /// * Frame-rate-independent exponential smoothing, usually written `1 - exp(-k * dt)` and then
1080        ///   divided by `k`.
1081        ///
1082        /// The higher orders are the coefficients of exponential time differencing: integrating
1083        /// `y' = Ly + N(y)` exactly over a step gives `$y(h) = e^{hL} y_0 + h\,\varphi_1(hL)\,N$`, and
1084        /// expanding `N` in time along the step brings in `$\varphi_2, \varphi_3, \ldots$` as the
1085        /// weights of the higher-order terms.
1086        #[doc(alias = "exprel")]
1087        fn phi_n<const N: usize>(self) -> Self;
1088
1089        /// `$\varphi_n(x)$` for an order known only at runtime. The runtime twin of
1090        /// [`phi_n`](SpecialMath::phi_n): the same series and recurrence arms, with the series
1091        /// length worked out from `n` per call rather than at compile time.
1092        fn phi(self, n: u32) -> Self;
1093
1094        /// Carlson symmetric elliptic integral, selected by a [`CarlsonKind`] request struct
1095        /// with named fields. The arity (and which argument is the parameter / repeated one)
1096        /// is fixed per kind, so the wrong shape is a compile error.
1097        ///
1098        /// ```rust,ignore
1099        /// let rf = V::carlson(CarlsonRf { x, y, z });
1100        /// let rj = V::carlson_p::<Precision, _>(CarlsonRj { x, y, z, p });
1101        /// ```
1102        #[kind]
1103        fn carlson<K: CarlsonKind<Output = Self>>(kind: K) -> Self;
1104
1105        /// Legendre elliptic integral, selected by an [`EllipticKind`] request struct. Each
1106        /// form ([`EllintK`](elliptic::EllintK)/[`EllintF`](elliptic::EllintF)/[`EllintE`](elliptic::EllintE)/
1107        /// [`EllintEInc`](elliptic::EllintEInc)/[`EllintD`](elliptic::EllintD)/[`EllintDInc`](elliptic::EllintDInc)/
1108        /// [`EllintPi`](elliptic::EllintPi)/[`EllintPiInc`](elliptic::EllintPiInc)) carries exactly
1109        /// its own arguments, and completeness is encoded by whether the struct has a `phi` field.
1110        ///
1111        /// Two family members that are _not_ Legendre integrals dispatch through here as well,
1112        /// because they are built from the same Carlson forms and belong beside their siblings:
1113        /// [`JacobiZeta`](elliptic::JacobiZeta), the oscillating part of `$E(\varphi, k)$`, and
1114        /// [`HeumanLambda`](elliptic::HeumanLambda), its complementary-modulus companion.
1115        ///
1116        /// ```rust,ignore
1117        /// let k_int = V::ellint(EllintK { k });                       // K(k)
1118        /// let e_inc = V::ellint_p::<Precision, _>(EllintEInc { phi, k }); // E(phi, k)
1119        /// let z     = V::ellint(JacobiZeta { phi, k });               // Z(phi, k)
1120        /// ```
1121        #[kind]
1122        fn ellint<K: EllipticKind<Output = Self>>(kind: K) -> Self;
1123    }
1124
1125    /// Special math functions that are only defined for real-valued floating-point vectors.
1126    ///
1127    /// These functions either rely on ordering/sign information that has no complex analogue
1128    /// (e.g. `erfinv`, `probit`, `lgamma_r`), or use the real absolute value in a way that
1129    /// makes them non-holomorphic (e.g. `algebraic_sigmoid`).
1130    #[diagnostic::on_unimplemented(
1131        message = "`{Self}` does not provide real-valued special math (`erfinv`, `probit`, `lgamma_r`, ...)",
1132        note = "`RealSpecialMath` is only meaningful for real-valued float vectors. Complex number types deliberately do not implement it. A bare `f32`/`f64` does not qualify either. Wrap it in `Vector::<f32>::splat(x)`, or use `ScalarSpecialMath`."
1133    )]
1134    pub trait RealSpecialMath: SpecialMathWithPolicy {
1135        /// Computes the inverse error function.
1136        fn erfinv(self) -> Self;
1137
1138        /// Computes the Probit function, the inverse of the cumulative distribution function
1139        /// of the standard normal distribution.
1140        #[doc(alias = "ndtri")]
1141        fn probit(self) -> Self;
1142
1143        /// Computes the cumulative distribution function of the standard normal
1144        /// distribution, the inverse of [`probit`](RealSpecialMath::probit):
1145        ///
1146        /// ```math
1147        /// \Phi(x) = \frac{1}{\sqrt{2\pi}} \int_{-\infty}^{x} e^{-t^2/2}\,dt
1148        ///         = \tfrac12 \operatorname{erfc}\!\left(-\frac{x}{\sqrt 2}\right)
1149        /// ```
1150        ///
1151        /// The probability that a standard normal variable falls below `x`: z-scores to
1152        /// p-values, the `N(d_1)`/`N(d_2)` terms of Black-Scholes, the probit link, and
1153        /// `x * ndtr(x)` is GELU. The name is Cephes/SciPy's.
1154        ///
1155        /// Underflows to zero below about `x = -38.6` (`f64`) and `-14.4` (`f32`). When
1156        /// the tail probability itself is the quantity of interest, use
1157        /// [`log_ndtr`](RealSpecialMath::log_ndtr), which is finite there.
1158        #[doc(alias = "norm_cdf")]
1159        #[doc(alias = "Phi")]
1160        fn ndtr(self) -> Self;
1161
1162        /// Computes `$\ln \Phi(x)$`, the logarithm of the standard normal CDF, finite
1163        /// for every finite `x`.
1164        ///
1165        /// `ln(ndtr(x))` is `-inf` below `x ~ -38.6` in `f64` (`-14.4` in `f32`), exactly
1166        /// where a probit or censored-regression likelihood, a truncated-normal density,
1167        /// or an expected-improvement acquisition needs the tail: `log_ndtr(-100)` is an
1168        /// ordinary `-5004.6`. The kernel keeps `$-x^2/2$` in the log domain and takes
1169        /// the rest from [`erfcx`](SpecialMath::erfcx), which has no underflow, so the
1170        /// left tail carries full relative accuracy to the largest `x` whose square is
1171        /// representable. On the right it is `ln_1p` of the complement, so
1172        /// `log_ndtr(10) = -7.6e-24` rather than a rounded zero.
1173        ///
1174        /// Costs one `erfcx`, one `ln_1p`, and an `exp` for the lanes with `x > 0`.
1175        #[doc(alias = "log_norm_cdf")]
1176        fn log_ndtr(self) -> Self;
1177
1178        /// Computes `$\ln \operatorname{erfc}(x)$`, finite for every finite `x`.
1179        ///
1180        /// `erfc` underflows at `x ~ 27` (`f64`) / `9.3` (`f32`) and its logarithm does
1181        /// not: `logerfc(100) = -10004.8`. This is the log-domain form of a Gaussian
1182        /// tail wherever `erfc` rather than the normal CDF is the natural quantity
1183        /// (Ewald sums, Gaussian-smeared edges, the Mills ratio in the log domain), and
1184        /// it is `log_ndtr` with `x = -\sqrt 2 x'`. Built on
1185        /// [`erfcx`](SpecialMath::erfcx) with `$-x^2$` kept in the log domain. On the
1186        /// left, where `erfc(x)` is between 1 and 2, it is `ln_1p(erf(|x|))`, so the
1187        /// result stays accurate down to `logerfc(-1e-20) = 1.13e-20`.
1188        #[doc(alias = "log_erfc")]
1189        fn logerfc(self) -> Self;
1190
1191        /// The Fresnel integrals `$S(x) = \int_0^x \sin(\pi t^2/2)\,dt$` and
1192        /// `$C(x) = \int_0^x \cos(\pi t^2/2)\,dt$`, together.
1193        ///
1194        /// **Returns `(S, C)`**, the same order as SciPy's `fresnel` and this crate's own
1195        /// [`sici`](RealSpecialMath::sici).
1196        ///
1197        /// Both are odd, both tend to `1/2`, and both stay in `[0.32, 0.72]` past the
1198        /// first oscillation. Measured against a 45-digit oracle over `x` from `1e-4` to
1199        /// `1e15`: 2.80 ulp (`C`) and 2.64 (`S`) in `f64`, 2.14 and 3.40 in `f32` out to
1200        /// `1e7`.
1201        ///
1202        /// The phase `$\pi x^2/2$` is carried in two words and reduced exactly, which is
1203        /// not a refinement but the whole of the large-argument accuracy: computed the
1204        /// obvious way as `x*x*0.5`, the phase is already 5.3e-6 wrong at `x = 98765` and
1205        /// returns the wrong _sign_ by `$x \approx 10^9$`, and since `C` and `S` are
1206        /// `1/2` plus a term of size `$1/(\pi x)$` that error lands straight on the
1207        /// result. Below `Average` the residual is dropped and that behaviour returns.
1208        ///
1209        /// Above `x = 1.147e16` (`f64`) / `2.136e7` (`f32`) the oscillating correction is
1210        /// under half an ulp of `1/2`, and both are exactly `1/2`.
1211        #[doc(alias = "fresnels")]
1212        #[doc(alias = "fresnelc")]
1213        fn fresnel(self) -> (Self, Self);
1214
1215        /// `$C(x)$` alone. See [`fresnel`](RealSpecialMath::fresnel).
1216        ///
1217        /// Unlike `airy::<Ai>` this is not a cheaper evaluation by
1218        /// much: `C` and `S` share the argument reduction, the phase and both
1219        /// auxiliaries, so asking for one drops a single Chebyshev series and one
1220        /// reconstruction: roughly a third, not three quarters.
1221        fn fresnel_c(self) -> Self;
1222
1223        /// `$S(x)$` alone. See [`fresnel_c`](RealSpecialMath::fresnel_c).
1224        fn fresnel_s(self) -> Self;
1225
1226        /// The trigonometric integrals `$\mathrm{Si}(x) = \int_0^x \frac{\sin t}{t}\,dt$`
1227        /// and `$\mathrm{Ci}(x) = \gamma + \ln x + \int_0^x \frac{\cos t - 1}{t}\,dt$`,
1228        /// together. Returns `(Si, Ci)`.
1229        ///
1230        /// `Si` is odd. `Ci` is real only on the positive axis (`$\mathrm{Ci}(-x) =
1231        /// \mathrm{Ci}(x) + i\pi$`), so this returns `Ci(|x|)`, dropping the imaginary
1232        /// part, which is what SciPy's `sici` does. `Ci(0)` is `$-\infty$`.
1233        ///
1234        /// Measured 2.03 ulp (`Si`) and 1.42 (`Ci`, against its envelope) in `f64` over
1235        /// `x` from `1e-4` to `1e15`. In `f32`, 1.34 and 1.99.
1236        ///
1237        /// Two things worth knowing before relying on `Ci`:
1238        ///
1239        /// - **It has zeros**, the first near `x = 0.6165`, and no algorithm is
1240        ///   relatively accurate at one. The accuracy above is relative to
1241        ///   `$\lvert\gamma + \ln x\rvert + \lvert\mathrm{Cin}\rvert$` below the
1242        ///   crossover and to the `$1/x$` envelope above it.
1243        /// - **Its large-argument accuracy is
1244        ///   [`sin_cos`](thermite::math::TranscendentalMath::sin_cos)'s**: for `Ci` the
1245        ///   oscillation _is_ the value, so a phase error is a relative error, and full
1246        ///   argument reduction is a `Best`-tier property. `Si` is insulated, tending to
1247        ///   `$\pi/2$` with the oscillation only a `$1/x$` correction, and is `$\pi/2$`
1248        ///   exactly above `x = 1.147e16` (`f64`) / `2.136e7` (`f32`). `Ci` has no such
1249        ///   cutoff: it decays like `$1/x$` and stays representable for every finite `x`.
1250        #[doc(alias = "si")]
1251        #[doc(alias = "ci")]
1252        fn sici(self) -> (Self, Self);
1253
1254        /// `$\mathrm{Si}(x)$` alone. See [`sici`](RealSpecialMath::sici), and
1255        /// [`fresnel_c`](RealSpecialMath::fresnel_c) for what a single accessor saves.
1256        #[doc(alias = "Si")]
1257        fn sinint(self) -> Self;
1258
1259        /// `$\mathrm{Ci}(x)$` alone. See [`sici`](RealSpecialMath::sici).
1260        #[doc(alias = "Ci")]
1261        fn cosint(self) -> Self;
1262
1263        /// Computes the inverse of [`log_ndtr`](RealSpecialMath::log_ndtr): the `x` with
1264        /// `$\ln \Phi(x) = y$`, for `y <= 0`. The quantile of a log-probability.
1265        ///
1266        /// [`probit`](RealSpecialMath::probit) of `$e^y$` stops working once `$e^y$`
1267        /// underflows (`y < -745` in `f64`), which is exactly where a log-likelihood, a
1268        /// truncated-normal EM step or an extreme-value fit needs the quantile. This
1269        /// inverts `log_ndtr` directly, by Newton with the inverse Mills ratio as the
1270        /// derivative, from a `probit(e^y)` seed one precision tier down where that
1271        /// exists and from the tail asymptotic below. Within a few ulp of the true inverse
1272        /// of the given `y` over the whole domain. `y = 0` gives `+inf`, `y = -inf` gives
1273        /// `-inf`, and `y > 0` is NaN.
1274        #[doc(alias = "ndtri_exp")]
1275        fn inv_log_ndtr(self) -> Self;
1276
1277        /// Computes the inverse of the digamma function on `$(0, \infty)$`: the `x` with
1278        /// `$\psi(x) = y$`.
1279        ///
1280        /// The maximum-likelihood estimate of a gamma shape or a Dirichlet concentration is
1281        /// this function of a mean log. Newton on `digamma` with `trigamma` from Minka's
1282        /// seed (`$e^y + 1/2$` above `y = -2.22`, `$-1/(y + \gamma)$` below). Above `y = 6`
1283        /// the Stirling series is solved for `x` directly, since there Newton on `digamma`
1284        /// cannot see past `digamma`'s own rounding. `+inf` maps to `+inf` and `-inf` to `0`.
1285        #[doc(alias = "digammainv")]
1286        fn inv_digamma(self) -> Self;
1287
1288        /// Computes the Wright omega function, the `$\omega > 0$` with
1289        /// `$\omega + \ln \omega = x$`.
1290        ///
1291        /// This is `$W_0(e^x)$`, the principal Lambert W of an exponential, evaluated without
1292        /// forming `$e^x$`: `$W_0(e^x)$` overflows past `x = 709` where `$\omega(x) \approx x - \ln x$`
1293        /// is ordinary. Newton on `$\omega + \ln \omega - x$` from a cheap seed per region.
1294        /// Below `x = -7` the Lagrange series in `$e^x$` is the answer outright.
1295        #[doc(alias = "wrightomega")]
1296        fn wright_omega(self) -> Self;
1297
1298        /// Computes the modified Bessel ratio `$A_\nu(x) = I_\nu(x) / I_{\nu-1}(x)$` for
1299        /// `nu >= 1`, odd in `x`.
1300        ///
1301        /// With `$p = 2\nu$` this is the mean resultant length of a von Mises-Fisher
1302        /// distribution on `$S^{p-1}$` at concentration `x`. `nu = 1` is the von Mises circle
1303        /// `$I_1/I_0$`, and `nu = 3/2` is the [`langevin`](RealSpecialMath::langevin) function.
1304        /// Never forms the two Bessel functions where they would underflow: a series pair for
1305        /// small `x`, the continued fraction for the ratio in the middle, and the scaled
1306        /// quotient only where `x` dominates the order. The order is a plain vector, but whole
1307        /// and half-integer orders reach their fast Bessel kernels through the order simplifier.
1308        #[doc(alias = "vmf_a")]
1309        fn bessel_ratio<F: BesselRatioFamily>(self, nu: Self) -> Self;
1310
1311        /// Computes the inverse of [`bessel_ratio`](RealSpecialMath::bessel_ratio): the
1312        /// concentration `$\kappa$` with `$I_\nu(\kappa)/I_{\nu-1}(\kappa) = r$`, for
1313        /// `0 <= r < 1`, odd in `r`.
1314        ///
1315        /// The maximum-likelihood concentration of a von Mises-Fisher distribution from its
1316        /// observed mean resultant length, in any dimension `$p = 2\nu$`. Banerjee's
1317        /// `$r(p - r^2)/(1 - r^2)$` seeds a Newton whose derivative is the closed form
1318        /// `$1 - A^2 - (2\nu - 1)A/\kappa$`, so each step is one ratio evaluation. `r = 1`
1319        /// gives `+inf`, `r > 1` NaN.
1320        ///
1321        /// As `r -> 1` the problem itself is ill-conditioned: `$\kappa \sim (p-1)/(2(1-r))$`,
1322        /// and an ulp of `r` is a relative `$2\kappa\epsilon/(p-1)$` of `$\kappa$`. The result is
1323        /// the exact inverse of the given `r` to that extent.
1324        #[doc(alias = "vmf_kappa")]
1325        fn inv_bessel_ratio<F: BesselRatioFamily>(self, nu: Self) -> Self;
1326
1327        /// Computes `$1 - A_\nu(x)$`, the complement of
1328        /// [`bessel_ratio`](RealSpecialMath::bessel_ratio), to full relative accuracy
1329        /// where the ratio itself is within an ulp of 1.
1330        ///
1331        /// `1 - bessel::ratio::<I>(x)` is gone once `$A$` rounds to 1 (`x` past `1e16 (p-1)/2`),
1332        /// and is only accurate to `$\epsilon/(1 - A)$` before that. This evaluates the
1333        /// complement directly for `x >= 8 nu`, from the Hankel expansions at a reduced order
1334        /// and the ratio recurrence walked upward in complement form. `$A$` is odd, so
1335        /// `$1 - A(-x) = 2 - (1 - A(x))$`.
1336        #[doc(alias = "vmf_a_1m")]
1337        fn bessel_ratio_1m<F: BesselRatioFamily>(self, nu: Self) -> Self;
1338
1339        /// Computes the inverse of [`bessel_ratio_1m`](RealSpecialMath::bessel_ratio_1m):
1340        /// the concentration `$\kappa$` with `$1 - I_\nu(\kappa)/I_{\nu-1}(\kappa) = t$`, for
1341        /// `0 < t <= 2` (`t = 1 - r`).
1342        ///
1343        /// The complement form of [`inv_bessel_ratio`](RealSpecialMath::inv_bessel_ratio)
1344        /// for nearly concentrated data: `$\kappa \sim (p-1)/(2t)$` as `t -> 0`. This form
1345        /// keeps full relative accuracy there instead of losing `$2\kappa\epsilon/(p-1)$`
1346        /// to the rounding of `r`. It is the [`inv_langevin_1m`](RealSpecialMath::inv_langevin_1m)
1347        /// move in every dimension. `t = 0` gives `+inf`. `t` in `(1, 2]` is a negative `r`
1348        /// and returns the mirrored `$\kappa$`.
1349        #[doc(alias = "vmf_kappa_1m")]
1350        fn inv_bessel_ratio_1m<F: BesselRatioFamily>(self, nu: Self) -> Self;
1351
1352        /// Computes the `k`-th node and weight of the `n`-point Gauss-Legendre quadrature
1353        /// rule on `$[-1, 1]$`, with the root index `k` taken **per lane**.
1354        ///
1355        /// The rule integrates every polynomial through degree `$2n - 1$` exactly:
1356        /// `$\int_{-1}^{1} f \approx \sum_k w_k f(x_k)$`, `$x_k$` the roots of `$P_n$` in
1357        /// descending order (`k = 0` is the largest, `$x_{n-1-k} = -x_k$`) and
1358        /// `$w_k = 2 / ((1 - x_k^2) P_n'(x_k)^2)$`. The packet _is_ the rule: sweep `k` over
1359        /// `0..n` in packets of consecutive indices and store the two vectors. Every lane
1360        /// runs the same `O(n)` recurrence, so a packet of roots costs one root.
1361        ///
1362        /// Tricomi's `$\cos(\pi(k + 3/4)/(n + 1/2))$` seeds a Newton on `$P_n$` from the
1363        /// recurrence, and nodes land within a few `$\epsilon$` absolute. A non-integer or
1364        /// out-of-range `k` gives NaN in both.
1365        ///
1366        /// ```rust,ignore
1367        /// let n = 16;
1368        /// for base in (0..n).step_by(V::LANES) {
1369        ///     let k = V::from_array(core::array::from_fn(|i| (base + i) as f64));
1370        ///     let (x, w) = k.gauss_legendre(n as u32); // lanes past n - 1 are NaN
1371        /// }
1372        /// ```
1373        fn gauss_legendre(self, n: u32) -> (Self, Self);
1374
1375        /// Computes the `k`-th node and weight of the `n`-point Gauss-Hermite rule, for
1376        /// `$\int_{-\infty}^{\infty} f(x) e^{-x^2}\,dx \approx \sum_k w_k f(x_k)$`, the root
1377        /// index `k` per lane (`k = 0` the largest root, `$x_{n-1-k} = -x_k$`).
1378        ///
1379        /// Same shape as [`gauss_legendre`](RealSpecialMath::gauss_legendre): a packet of
1380        /// consecutive indices is the rule. Seeded from the WKB phase of the Hermite equation
1381        /// and finished by Newton on `$H_n/n!$`, whose recurrence stays in range where the raw
1382        /// `$H_n$` overflows at degree 48. The weights are the unscaled ones, which reach
1383        /// `$e^{-x_k^2}$` at the outer nodes. The scalar factor in them underflows past
1384        /// `n = 170` in `f64` and `n = 40` in `f32`, which bounds the rule.
1385        fn gauss_hermite(self, n: u32) -> (Self, Self);
1386
1387        /// Computes the `k`-th node and weight of the `n`-point Gauss-Laguerre rule, for
1388        /// `$\int_0^{\infty} f(x)\, x^\alpha e^{-x}\,dx \approx \sum_k w_k f(x_k)$`, the root
1389        /// index `k` and `alpha > -1` per lane (`k = 0` the largest root).
1390        ///
1391        /// Same shape as [`gauss_legendre`](RealSpecialMath::gauss_legendre). Seeded from the
1392        /// WKB phase of the Laguerre equation, whose phase count between the turning points
1393        /// carries the Bessel-zero offset on the left and the Airy offset on the right, and
1394        /// finished by Newton on the raw `$L_n^\alpha$` with Hildebrand's weight
1395        /// `$\Gamma(n+\alpha+1)/(n!\,x_k\,L_n^{\alpha\prime}(x_k)^2)$`. Unscaled weights, which
1396        /// reach `$e^{-x_k}$` at the outer nodes. `$L_{n-1}$` at the largest root grows like
1397        /// `$e^{x/2}$`, which bounds the rule near `n = 170` in `f64` and `n = 20` in `f32`.
1398        fn gauss_laguerre(self, alpha: Self, n: u32) -> (Self, Self);
1399
1400        /// Computes the Pochhammer symbol `$(z)_m = \dfrac{\Gamma(z+m)}{\Gamma(z)}$`.
1401        ///
1402        /// Combinatorics calls this the **rising factorial**, and for a non-negative integer
1403        /// `m` it is exactly the ascending product `$z(z+1)\cdots(z+m-1)$`. The name here is
1404        /// the special-function one because the function is not restricted to integers: `m`
1405        /// is any real, which is what the hypergeometric series need and what "factorial"
1406        /// would misdescribe.
1407        ///
1408        /// Note that the notation `$(z)_m$` is **ambiguous in the literature**: it means the
1409        /// rising factorial in special functions and the _falling_ factorial through much of
1410        /// combinatorics and statistics. This function is the rising one. The falling
1411        /// factorial is `pochhammer(z - n + 1, n)`, and the two are related by
1412        /// `$z^{(\bar n)} = (-1)^n (-z)^{(\underline n)}$`. Neither is shipped separately,
1413        /// being an argument transform away.
1414        ///
1415        /// # Accuracy
1416        ///
1417        /// The obvious spelling `exp(lgamma(z+m) - lgamma(z))` cancels catastrophically
1418        /// whenever `m` is small beside `z`: at `z = 1e8, m = 1e-4` it has **no correct
1419        /// digits**. This does not use it.
1420        ///
1421        /// At `Average` precision and above (which includes the default policy), integer `m`
1422        /// up to 20 in absolute value takes an exact product, 0.00 ulp median and 4.2 worst.
1423        /// That path also covers negative `z` and returns exact zeros at the poles: `$(-2)_3$`
1424        /// is 0.
1425        ///
1426        /// Below `Average` it is compiled out and integer `m` goes through the Stirling
1427        /// difference like anything else, which measures 4.2 ulp median and 172 worst. The
1428        /// difference that shows is the exactness rather than the ulp count: `$(3)_1$` comes
1429        /// back as `3.0000000000000018` there, and `$(200)_2$` as `40200.00000000002`.
1430        ///
1431        /// Any other `m` with `z` and `z+m` both positive takes a Stirling difference
1432        /// arranged so nothing large is ever subtracted from anything large. Its error is the
1433        /// floor for anything exponentiating a logarithm, tracking
1434        /// `$|\ln (z)_m|\cdot\epsilon$`. Over 6924 measured points with `z` in `[0.1, 8.9]`
1435        /// that is a median of 2.6 ulp and a 99th percentile of 25. Individual points scale
1436        /// with the result's own logarithm, reaching 259 ulp where the value is near `1e163`,
1437        /// and falling to nothing as the result approaches 1.
1438        ///
1439        /// A non-integer `m` with `z` or `z+m` non-positive (a ratio taken across Gamma's
1440        /// poles) has no cheap rearrangement and does fall back to the logarithmic form,
1441        /// inheriting its cancellation.
1442        #[doc(alias = "poch")]
1443        #[doc(alias = "rising_factorial")]
1444        fn pochhammer(self, m: Self) -> Self;
1445
1446        /// Computes the Jacobi elliptic functions `$(\mathrm{sn}, \mathrm{cn}, \mathrm{dn})$`
1447        /// at argument `self` and modulus `k`, all three from one evaluation.
1448        ///
1449        /// All three are made from a single angle, the **amplitude**
1450        /// `$\varphi = \mathrm{am}(u, k)$`, defined by `$F(\varphi, k) = u$`, so this function
1451        /// inverts the incomplete integral of the first kind that
1452        /// [`ellint`](SpecialMath::ellint) evaluates:
1453        ///
1454        /// ```math
1455        /// \mathrm{sn}(u, k) = \sin\varphi, \qquad
1456        /// \mathrm{cn}(u, k) = \cos\varphi, \qquad
1457        /// \mathrm{dn}(u, k) = \sqrt{1 - k^2 \sin^2\varphi}
1458        /// ```
1459        ///
1460        /// Hence their names: sine amplitude, cosine amplitude and delta amplitude. At
1461        /// `k = 0` the amplitude is `u` and they collapse to `$(\sin u, \cos u, 1)$`. At
1462        /// `k = 1` they stop being periodic and become
1463        /// `$(\tanh u, \operatorname{sech} u, \operatorname{sech} u)$`.
1464        ///
1465        /// # Why one function and not three
1466        ///
1467        /// The triple is closed under differentiation in `u`, each derivative a product
1468        /// of the other two:
1469        ///
1470        /// ```math
1471        /// \frac{d\,\mathrm{sn}}{du} = \mathrm{cn}\,\mathrm{dn}, \qquad
1472        /// \frac{d\,\mathrm{cn}}{du} = -\mathrm{sn}\,\mathrm{dn}, \qquad
1473        /// \frac{d\,\mathrm{dn}}{du} = -k^2\,\mathrm{sn}\,\mathrm{cn}
1474        /// ```
1475        ///
1476        /// so they are one object the way `$(\sin, \cos)$` are, and
1477        /// [`Dual`](https://docs.rs/thermite-dual) differentiates them without touching the
1478        /// iteration underneath. It also costs nothing to return all three: they share the
1479        /// entire computation, and only the last few operations differ.
1480        ///
1481        /// The other nine Jacobi functions in Glaisher's notation (`ns`, `nc`, `nd`, `sc`,
1482        /// `sd`, `cs`, `cd`, `ds`, `dc`) are reciprocals and ratios of these three, so this
1483        /// gives all twelve.
1484        ///
1485        /// # Domain and accuracy
1486        ///
1487        /// Only `$k^2$` enters, so the sign of `k` does not matter. `|k| > 1` is out of
1488        /// domain and gives NaN. Worst absolute error measured against mpmath at 40 digits
1489        /// over `|u| <= 8` and `k` in `[0, 1)` is 8.3 eps for `sn`, 4.1 for `cn` and 3.8 for
1490        /// `dn`. Absolute is the meaningful metric: all three are bounded by 1 and all three
1491        /// have zeros, so relative accuracy at a zero depends on how well that zero's
1492        /// location is known, exactly as for `sin`. For the same reason accuracy falls off
1493        /// slowly with `|u|`, that being the argument of the single trigonometric call
1494        /// inside.
1495        #[doc(alias = "sn")]
1496        #[doc(alias = "cn")]
1497        #[doc(alias = "dn")]
1498        #[doc(alias = "ellipj")]
1499        #[doc(alias = "sncndn")]
1500        fn jacobi_elliptic(self, k: Self) -> (Self, Self, Self);
1501
1502        /// Computes the arithmetic-geometric mean `$\mathrm{AGM}(a, b)$` of two non-negative
1503        /// arguments.
1504        ///
1505        /// Iterating `$a \mapsto (a + b)/2$` against `$b \mapsto \sqrt{ab}$` drives the two
1506        /// sequences to a common limit, quadratically: the pair closes to within a factor of
1507        /// a few in a handful of passes from any starting ratio, and the correct digits then
1508        /// double per pass. The loop is branchless and costs one `sqrt` per iteration, with no
1509        /// transcendentals anywhere, which is why it is also the engine behind the complete
1510        /// elliptic integrals, `$K(k) = \pi / (2\,\mathrm{AGM}(1, k'))$`, reached through
1511        /// [`ellint`](SpecialMath::ellint) rather than by calling this directly.
1512        ///
1513        /// Symmetric in its arguments and homogeneous, `$\mathrm{AGM}(ca, cb) =
1514        /// c\,\mathrm{AGM}(a, b)$`. `AGM(a, 0)` is `0` and `AGM(inf, b)` is `inf`. A negative
1515        /// argument is outside the domain (the geometric mean's sign becomes ambiguous after
1516        /// the first pass) and returns NaN under overflow checking, as does a zero paired with
1517        /// an infinity.
1518        ///
1519        /// The geometric mean is formed as one product, so two arguments both above
1520        /// `$\sqrt{\text{MAX}}$` (about 1.3e154 in f64, 1.8e19 in f32) overflow to infinity
1521        /// even where the mean is representable. Scale both by a common power of two first if
1522        /// that range matters. Homogeneity makes it exact.
1523        fn agm(self, other: Self) -> Self;
1524
1525        /// Computes the Langevin function `$L(x) = \coth x - \frac{1}{x}$`.
1526        ///
1527        /// Odd, strictly increasing, `L(0) = 0`, `L'(0) = 1/3`, `L(x) -> 1` as `x -> ∞`.
1528        /// This is the mean resultant length `$A_3(\kappa)$` of a von Mises-Fisher
1529        /// distribution on the sphere, and the freely-jointed-chain force-extension law
1530        /// in polymer physics.
1531        ///
1532        /// Evaluated as an odd minimax polynomial for `|x| <= 2` (the direct form
1533        /// `coth x - 1/x` cancels catastrophically there, losing `3u/x^2`), and as
1534        /// `1 - 1/x + 2/(e^{2x} - 1)` beyond. Both branches are accurate to a few ulp
1535        /// at every precision policy. The policy mainly selects the `exp`.
1536        ///
1537        /// To also obtain the derivative `L'(x)`, use
1538        /// [`langevin_d`](crate::RealPrimalMath::langevin_d).
1539        fn langevin(self) -> Self;
1540
1541        /// Computes the inverse Langevin function `$L^{-1}(y)$` for `|y| < 1`.
1542        ///
1543        /// Odd, with a simple pole at `y = 1`: `L^-1(y) ~ 1/(1-y)`. `|y| = 1` returns
1544        /// `±∞`, and `|y| > 1` returns NaN under overflow checking (an unspecified
1545        /// value otherwise). Its condition number is `1/(1-y)`, so near the pole the
1546        /// result cannot be more accurate than that, however exact the arithmetic. A
1547        /// consumer that knows `1 - y` should form it before rounding.
1548        ///
1549        /// A rational seed (the same family as Cohen's Pade approximant, which the vMF
1550        /// literature knows as the Banerjee et al. concentration estimator) is refined by
1551        /// Newton (f32) or Halley (f64) steps whose count follows the precision policy:
1552        ///
1553        /// | precision | steps | relative error |
1554        /// |---|---|---|
1555        /// | `Worst` | 0 | ~2e-5 |
1556        /// | `Medium`, `Average`, `Best` | 1 | full (a few ulp) |
1557        /// | `Reference` | 2 | full |
1558        fn inv_langevin(self) -> Self;
1559
1560        /// Computes `1 - L(x)`, the complement of the [Langevin function](RealSpecialMath::langevin),
1561        /// accurately where `L(x)` is within rounding of 1.
1562        ///
1563        /// `1 - L(x) ~ 1/x`, so once `x > 1/u` (sharpness ~1e7 in f32, ~1e16 in f64)
1564        /// `langevin(x)` rounds to exactly 1 and its complement is gone. This returns it
1565        /// to full relative precision at any `x`, from the same intermediates. Same cost
1566        /// as `langevin`. Negative `x` gives `1 + L(|x|)`.
1567        ///
1568        /// Pairs with [`inv_langevin_1m`](RealSpecialMath::inv_langevin_1m): the vMF
1569        /// convolution `kappa' = L^-1(L(k1) L(k2))` should be formed as
1570        /// `inv_langevin_1m(a + b - a*b)` with `a = langevin_1m(k1)`, `b = langevin_1m(k2)`,
1571        /// which is cancellation-free at every sharpness.
1572        fn langevin_1m(self) -> Self;
1573
1574        /// Computes `L^-1(1 - t)` from the complement `t` directly.
1575        ///
1576        /// The [inverse Langevin function](RealSpecialMath::inv_langevin) has a pole at
1577        /// `y = 1` and a condition number of `1/(1-y)`, so a caller that knows `1 - y`
1578        /// (see [`langevin_1m`](RealSpecialMath::langevin_1m)) should pass it here rather
1579        /// than form `y` and lose its low digits: this entry point works in `t` throughout
1580        /// and is accurate to a few ulp at any sharpness. `t = 0` returns `+∞`, `t > 1`
1581        /// gives the negative branch, and `t < 0` is out of the domain (NaN under
1582        /// overflow checking). Same cost as `inv_langevin`.
1583        fn inv_langevin_1m(self) -> Self;
1584
1585        /// GELU activation function, defined as `$\tfrac{1}{2} x \left(1 + \operatorname{erf}\!\left(\frac{\alpha x}{\sqrt{2}}\right)\right)$`,
1586        /// where `alpha` helps control the shape of the curve. The standard GELU function
1587        /// is recovered when `alpha` is 1.
1588        ///
1589        /// For f32 vectors, this remains decently accurate even with the `Medium` and `Worst` precision policies,
1590        /// thanks to good `erf` implementations at the various precision levels. See `erf` for more details.
1591        ///
1592        /// To also obtain the derivative with respect to `x` (which shares most of the computation), use
1593        /// [`gelu_d`](crate::RealPrimalMath::gelu_d).
1594        fn gelu(self, alpha: Self) -> Self;
1595
1596        /// Swish activation function, defined as `$x\,\sigma(\beta x) = \frac{x}{1 + e^{-\beta x}}$`,
1597        /// where `beta` controls the sharpness of the gate. The standard Swish/SiLU function
1598        /// is recovered when `beta` is 1. As `beta -> 0`, the output approaches `x/2` (half-identity);
1599        /// as `beta -> inf`, Swish approaches ReLU.
1600        ///
1601        /// To also obtain the derivative with respect to `x`, use
1602        /// [`swish_d`](crate::RealPrimalMath::swish_d).
1603        fn swish(self, beta: Self) -> Self;
1604
1605        /// Computes the algebraic sigmoid function, defined as `$\frac{x}{(1 + |x|^N)^{1/N}}$`, where
1606        /// `N` is a positive integer parameter that controls the steepness of the curve.
1607        ///
1608        /// This also has the unique behavior where for `N=0`, the function is just the identity function,
1609        /// and for `N=1` it is the [softsign function](https://en.wikipedia.org/wiki/Activation_function#Softsign).
1610        ///
1611        /// **Note**: This function uses `$|x|^N$` (the real absolute value), so it is non-holomorphic
1612        /// and only meaningful for real-valued inputs.
1613        ///
1614        /// To also obtain the derivative with respect to `x`, use
1615        /// [`algebraic_sigmoid_d`](crate::RealPrimalMath::algebraic_sigmoid_d).
1616        fn algebraic_sigmoid_n<const N: usize>(self) -> Self;
1617
1618        /// The algebraic sigmoid for a degree known only at runtime. The runtime twin of
1619        /// [`algebraic_sigmoid_n`](RealSpecialMath::algebraic_sigmoid_n), same arithmetic.
1620        fn algebraic_sigmoid(self, n: u32) -> Self;
1621
1622        /// Algebraic analogue of the [Swish](https://en.wikipedia.org/wiki/Swish_function) activation,
1623        /// defined as `$x\left(\frac{1}{2} + \frac{x}{2\sqrt{1 + x^2}}\right)$`. Equivalent to gating `x` by
1624        /// `(1 + algebraic_sigmoid_n::<2>(x)) / 2`, the `[0, 1]`-rescaled `N=2` algebraic sigmoid.
1625        ///
1626        /// Like standard Swish/SiLU, this is smooth and non-monotonic (it dips slightly below zero
1627        /// for moderately negative `x` before rising) and shares the same asymptotes (`f(x) -> x` as
1628        /// `x -> ∞`, `f(x) -> 0` as `x -> -∞`). Unlike Swish, it requires no `exp` or `log`, which
1629        /// is substantially cheaper on hardware without fast transcendentals.
1630        ///
1631        /// To also obtain the derivative with respect to `x` (which shares most of the underlying
1632        /// computation, notably `$1/\sqrt{1 + x^2}$`), use
1633        /// [`algebraic_swish_d`](crate::RealPrimalMath::algebraic_swish_d).
1634        ///
1635        /// # Historical note
1636        ///
1637        /// Algebraic gating functions of this form are effectively unknown in modern deep learning,
1638        /// which standardized on `exp`-based activations (sigmoid, Swish/SiLU, GELU) once GPUs made
1639        /// `exp` essentially free, a single-cycle special-function-unit op on most modern hardware.
1640        /// On CPUs the calculus is different: a vectorized `exp` still costs ~20+ cycles even with
1641        /// good polynomial approximations, while `sqrt`/`rsqrt` are cheap hardware ops (often
1642        /// approximated in 4-7 cycles). For CPU-side inference, training on CPU, or embedded targets
1643        /// without a transcendental SFU, this remains a competitive Swish-shaped activation at a
1644        /// fraction of the cost.
1645        fn algebraic_swish(self) -> Self;
1646
1647        /// Computes the natural log of the Gamma function (`$\ln|\Gamma(x)|$`) for any real input, for each value in a vector,
1648        /// and returns the sign of the Gamma function from before the absolute value was taken.
1649        fn lgamma_r(self) -> (Self, Self);
1650
1651        /// Computes the definite integral of the Gaussian function from `x0` to `x1`, with amplitude `a` and standard deviation `c`.
1652        /// This is more efficient than evaluating the indefinite integral at both limits and subtracting.
1653        ///
1654        /// The position `b` is assumed to be zero, so offset the limits accordingly for a non-zero position.
1655        fn gaussian_integral(x0: Self, x1: Self, a: Self, c: Self) -> Self;
1656
1657        /// The [Box-Cox transform](https://en.wikipedia.org/wiki/Power_transform) of `x = self`
1658        /// with parameter `lambda`.
1659        ///
1660        /// ```math
1661        /// \mathrm{boxcox}(x, \lambda) = \begin{cases} \dfrac{x^\lambda - 1}{\lambda} & \lambda \ne 0 \\[6pt] \ln x & \lambda = 0\end{cases}
1662        /// ```
1663        ///
1664        /// The variance-stabilizing power transform of applied statistics: `$\lambda$` is fitted
1665        /// to make skewed data as close to normal as possible before a model sees it, and the
1666        /// family interpolates the transforms people otherwise pick by hand: `$\lambda = 1$`
1667        /// leaves the data alone up to a shift, `$1/2$` is a square root, `$0$` a logarithm,
1668        /// `$-1$` a reciprocal. A fixture of statistical software since Box and Cox introduced
1669        /// it in 1964.
1670        ///
1671        /// The two cases are one function: `$\ln x$` is the limit as `$\lambda \to 0$`, not a
1672        /// separate rule. Written out, `$(x^\lambda - 1)/\lambda$` is `$0/0$` there, and the
1673        /// trouble is not confined to the point. Computing `$x^\lambda$` and subtracting one
1674        /// cancels, so the naive form is already wrong in the fifth digit at
1675        /// `$\lambda = 10^{-12}$` and returns a flat zero by `$10^{-300}$`. That matters because
1676        /// a fitting routine searches `$\lambda$` near zero, which is the usual answer for
1677        /// right-skewed data.
1678        ///
1679        /// Evaluated as [`powf_m1`](thermite::math::TranscendentalMath::powf_m1)`(x, lambda)/lambda`,
1680        /// which forms `$x^\lambda - 1$` without ever forming `$x^\lambda$`, so there is nothing to
1681        /// cancel and **no series or crossover is needed**. Measured against a 60-digit oracle,
1682        /// it holds a few ulp from `$\lambda = 10^{-300}$` to `$\lambda = \pm 8$`. Only the exact
1683        /// `$\lambda = 0$` is selected apart.
1684        ///
1685        /// Domain is `$x > 0$`, and a negative `x` gives NaN. At `$x = 0$` the limits are taken:
1686        /// `$-1/\lambda$` for `$\lambda > 0$` and `$-\infty$` otherwise, which is the
1687        /// conventional choice. That needs no special case: `powf_m1(0, lambda)` is `$-1$`
1688        /// above zero and `$+\infty$` below, and the division does the rest.
1689        fn boxcox(self, lambda: Self) -> Self;
1690
1691        /// The Box-Cox transform of `$1 + x$`, where `x = self`.
1692        ///
1693        /// ```math
1694        /// \mathrm{boxcox1p}(x, \lambda) = \begin{cases} \dfrac{(1 + x)^\lambda - 1}{\lambda} & \lambda \ne 0 \\[6pt] \ln (1 + x) & \lambda = 0\end{cases}
1695        /// ```
1696        ///
1697        /// The shifted form exists for the same reason [`ln_1p`](thermite::math::TranscendentalMath::ln_1p)
1698        /// does: when `x` is small, `$1 + x$` rounds it away, and every digit of the answer
1699        /// with it. Calling [`boxcox`](crate::RealSpecialMath::boxcox)`(1 + x, lambda)` loses `x` entirely once
1700        /// `$|x| < \varepsilon$`, where this returns `$\lambda x$` to full precision. Built on
1701        /// [`compound_m1`](thermite::math::TranscendentalMath::compound_m1), which forms
1702        /// `$(1 + x)^\lambda - 1$` without forming either `$1 + x$` or `$(1+x)^\lambda$`.
1703        ///
1704        /// This is also the kernel underneath [`yeo_johnson`](crate::RealSpecialMath::yeo_johnson), whose
1705        /// argument is data centered near zero by construction.
1706        ///
1707        /// Domain is `$x > -1$`; below that the result is NaN. At `$x = -1$` the limits are
1708        /// `$-1/\lambda$` for `$\lambda > 0$` and `$-\infty$` otherwise.
1709        fn boxcox_1p(self, lambda: Self) -> Self;
1710
1711        /// The inverse [Box-Cox transform](https://en.wikipedia.org/wiki/Power_transform) of
1712        /// `y = self` with parameter `lambda`, undoing [`boxcox`](crate::RealSpecialMath::boxcox).
1713        ///
1714        /// ```math
1715        /// \mathrm{boxcox}^{-1}(y, \lambda) = \begin{cases} (\lambda y + 1)^{1/\lambda} & \lambda \ne 0 \\[6pt] e^y & \lambda = 0\end{cases}
1716        /// ```
1717        ///
1718        /// Wanted by anyone who uses the forward transform: a model fitted on transformed
1719        /// data predicts in transformed units, and the prediction has to come back.
1720        ///
1721        /// Evaluated as `$\exp\!\left(\ln(1 + \lambda y)/\lambda\right)$` rather than as a
1722        /// literal power, which is not merely a rearrangement. The whole
1723        /// point of [`boxcox`](crate::RealSpecialMath::boxcox) is that it stays accurate as `$\lambda \to 0$`,
1724        /// and `$\lambda$` fitted near zero is the common case. There `$\lambda y$` is tiny,
1725        /// so forming `$\lambda y + 1$` and raising it to the power `$1/\lambda$` throws away
1726        /// exactly the digits the forward transform took care to keep. Through `ln_1p` the
1727        /// exponent tends smoothly to `y`, so the `$\lambda = 0$` case is the limit rather
1728        /// than a discontinuity, and only the exact zero is selected apart.
1729        ///
1730        /// The range of the forward transform is `$\lambda y + 1 > 0$`. Outside it the result
1731        /// is NaN, and on the boundary it is `$0$` for `$\lambda > 0$` and `$+\infty$` below.
1732        fn inv_boxcox(self, lambda: Self) -> Self;
1733
1734        /// The inverse of [`boxcox_1p`](crate::RealSpecialMath::boxcox_1p).
1735        ///
1736        /// ```math
1737        /// \mathrm{boxcox1p}^{-1}(y, \lambda) = \begin{cases} (\lambda y + 1)^{1/\lambda} - 1 & \lambda \ne 0 \\[6pt] e^y - 1 & \lambda = 0\end{cases}
1738        /// ```
1739        ///
1740        /// The same exponent as [`inv_boxcox`](crate::RealSpecialMath::inv_boxcox) with `expm1` outside it
1741        /// instead of `exp`, so a result near zero keeps its relative accuracy, which, this
1742        /// being the inverse of a transform applied to data centered near zero, is the
1743        /// ordinary case rather than an edge one. Also the kernel underneath
1744        /// [`inv_yeo_johnson`](crate::RealSpecialMath::inv_yeo_johnson).
1745        fn inv_boxcox_1p(self, lambda: Self) -> Self;
1746
1747        /// The [Yeo-Johnson transform](https://en.wikipedia.org/wiki/Power_transform) of
1748        /// `y = self` with parameter `lambda`.
1749        ///
1750        /// ```math
1751        /// \psi(y, \lambda) = \begin{cases}
1752        ///   \dfrac{(y + 1)^\lambda - 1}{\lambda} & y \ge 0,\ \lambda \ne 0 \\[6pt]
1753        ///   \ln(y + 1) & y \ge 0,\ \lambda = 0 \\[6pt]
1754        ///   -\dfrac{(1 - y)^{2 - \lambda} - 1}{2 - \lambda} & y < 0,\ \lambda \ne 2 \\[6pt]
1755        ///   -\ln(1 - y) & y < 0,\ \lambda = 2
1756        /// \end{cases}
1757        /// ```
1758        ///
1759        /// Box-Cox's sibling, and the one that gets used more, since it is defined on the whole
1760        /// real line rather than on `$x > 0$`. Same job (fit `$\lambda$` by maximum likelihood
1761        /// to make skewed data as close to normal as a power transform can) without the "add a
1762        /// constant to make everything positive first" step, which is an arbitrary choice that
1763        /// changes the fitted `$\lambda$`. Introduced by Yeo and Johnson in 2000.
1764        ///
1765        /// # One kernel, not four
1766        ///
1767        /// The four cases are one function seen twice. The `$y < 0$` branch is the `$y \ge 0$`
1768        /// branch applied to `$|y|$` with `$\lambda$` reflected to `$2 - \lambda$` and the
1769        /// result negated, which is what makes `$\psi$` smooth in `$\lambda$` across `$y = 0$`
1770        /// in the first place. Folding the sign out first therefore collapses the two
1771        /// logarithmic special cases (`$\lambda = 0$` above zero, `$\lambda = 2$` below) into
1772        /// the single seam that [`boxcox_1p`](crate::RealSpecialMath::boxcox_1p) already handles, and the whole
1773        /// transform is `$\pm\,\mathrm{boxcox1p}(|y|, \lambda\ \mathrm{or}\ 2 - \lambda)$`.
1774        ///
1775        /// That the kernel is the `1p` form and not [`boxcox`](crate::RealSpecialMath::boxcox) applied to
1776        /// `$1 + |y|$` matters here more than anywhere else. `$\psi(y, \lambda) \approx y$`
1777        /// near the origin for every `$\lambda$`, and the origin is where the data is: the
1778        /// transform's reason for existing is samples that straddle zero. Forming `$1 + |y|$`
1779        /// would round away everything below `$\varepsilon$` and return a flat zero there.
1780        ///
1781        /// The value is finite for every finite `y`, so there is nothing to guard: the two
1782        /// domain edges of the kernel are at `$|y| = -1$`, which the fold never reaches.
1783        fn yeo_johnson(self, lambda: Self) -> Self;
1784
1785        /// The inverse [Yeo-Johnson transform](https://en.wikipedia.org/wiki/Power_transform),
1786        /// undoing [`yeo_johnson`](crate::RealSpecialMath::yeo_johnson).
1787        ///
1788        /// ```math
1789        /// \psi^{-1}(z, \lambda) = \begin{cases}
1790        ///   (\lambda z + 1)^{1/\lambda} - 1 & z \ge 0,\ \lambda \ne 0 \\[6pt]
1791        ///   e^z - 1 & z \ge 0,\ \lambda = 0 \\[6pt]
1792        ///   1 - \left((\lambda - 2) z + 1\right)^{1/(2 - \lambda)} & z < 0,\ \lambda \ne 2 \\[6pt]
1793        ///   1 - e^{-z} & z < 0,\ \lambda = 2
1794        /// \end{cases}
1795        /// ```
1796        ///
1797        /// The same sign fold as the forward transform, over
1798        /// [`inv_boxcox_1p`](crate::RealSpecialMath::inv_boxcox_1p). `$\psi$` is increasing and fixes the origin,
1799        /// so the branch on the way back is the sign of the transformed value, which is the
1800        /// sign of `y`.
1801        ///
1802        /// Unlike the forward direction this one has a range to respect: for `$\lambda > 0$`
1803        /// the transform's image is bounded below by `$-1/\lambda$`, and a `z` past that came
1804        /// from no `y`. Such an input gives NaN rather than a plausible-looking number.
1805        fn inv_yeo_johnson(self, lambda: Self) -> Self;
1806
1807        /// Evaluates **all** real spherical harmonics through degree `L` at the unit
1808        /// direction `(x, y, z)`, into `out[l * (l + 1) + m]` for `m` in `-l..=l`.
1809        ///
1810        /// Orthonormal real harmonics. Evaluation is pure polynomial arithmetic:
1811        /// no trigonometry, no division, `O(L^2)` FMAs total, exact zeros for every
1812        /// `m != 0` harmonic at the poles, fully unrolled at compile time for each
1813        /// `L` up to [`MAX_SH_DEGREE`] (above that it takes the rolled general path,
1814        /// which is correct at any degree but roughly 10x slower).
1815        ///
1816        /// `CS` picks the phase convention. `false` gives the standard real-SH
1817        /// tables (`$Y_{11} = \sqrt{3/4\pi}\,x$`); `true` applies the Condon-Shortley
1818        /// `$(-1)^{|m|}$` phase, negating every odd-`|m|` harmonic to match Sloan's
1819        /// `SHEval` and the physics convention (`$Y_{11} = -\sqrt{3/4\pi}\,x$`). The
1820        /// choice is baked into a constant table, so neither costs an instruction,
1821        /// but mixing the two silently corrupts any projection/reconstruction
1822        /// round-trip, which is why it must be named.
1823        ///
1824        /// `N` must equal `(L + 1)^2` (compile-time checked). The direction is
1825        /// assumed unit-length, and nothing renormalizes. See
1826        /// [`sh_impl`](specialized::sh_impl) for the full convention, algorithm,
1827        /// and domain notes.
1828        ///
1829        /// ```
1830        /// use thermite::prelude::*;
1831        /// use thermite_special::RealSpecialMath;
1832        ///
1833        /// type V = Vector<f64>;
1834        /// let (x, y, z) = (V::splat(0.6), V::splat(0.0), V::splat(0.8));
1835        ///
1836        /// let mut sh = [V::ZERO; 9];
1837        /// V::spherical_harmonics::<2, 9, false>(x, y, z, &mut sh);
1838        /// // Y(1,1) = sqrt(3/4pi) * x
1839        /// assert!((sh[3].extract::<0>() - 0.48860251190292 * 0.6).abs() < 1e-14);
1840        ///
1841        /// // Condon-Shortley negates odd |m|, and agrees on even |m|.
1842        /// let mut cs = [V::ZERO; 9];
1843        /// V::spherical_harmonics::<2, 9, true>(x, y, z, &mut cs);
1844        /// assert_eq!(cs[3].extract::<0>(), -sh[3].extract::<0>());
1845        /// assert_eq!(cs[8].extract::<0>(), sh[8].extract::<0>());
1846        /// ```
1847        #[skip_dispatch] #[compose] fn spherical_harmonics<const L: usize, const N: usize, const CS: bool>(x: Self, y: Self, z: Self, out: &mut [Self; N]) -> ();
1848
1849        /// Builds the runtime coefficient table that [`spherical_harmonics_with`](RealSpecialMath::spherical_harmonics_with)
1850        /// and [`spherical_harmonics_d_with`](RealPrimalMath::spherical_harmonics_d_with) evaluate.
1851        ///
1852        /// The table depends only on `L` and `CS`, never on the direction, so a caller
1853        /// sweeping many directions should build it once rather than calling the
1854        /// one-shot [`spherical_harmonics`](RealSpecialMath::spherical_harmonics)
1855        /// per direction. The phase is baked in here, which is why the evaluators take
1856        /// no `CS`.
1857        ///
1858        /// The table is typed by `Self::Primal`, the unaugmented value type: the
1859        /// recurrence coefficients are constants, so a `Dual`'s derivative parts and a
1860        /// `Complex`'s imaginary part would only store zeros. For plain vectors and
1861        /// `Compensated` the primal is `Self` and nothing changes. For `Dual` the table
1862        /// is a fraction of the size and its entries multiply as reals.
1863        ///
1864        /// ```
1865        /// use thermite::prelude::*;
1866        /// use thermite_special::{RealSpecialMath, ShTable};
1867        ///
1868        /// type V = Vector<f64>;
1869        /// const L: usize = 3;
1870        /// const N: usize = (L + 1) * (L + 1);
1871        ///
1872        /// let mut table = ShTable::<V, N>::zeroed();
1873        /// V::spherical_harmonics_table::<L, N, false>(&mut table);
1874        ///
1875        /// let mut sh = [V::ZERO; N];
1876        /// for &(x, y, z) in &[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0)] {
1877        ///     V::spherical_harmonics_with::<L, N>(
1878        ///         &table, V::splat(x), V::splat(y), V::splat(z), &mut sh,
1879        ///     );
1880        /// }
1881        /// assert!((sh[1].extract::<0>() - 0.48860251190292).abs() < 1e-14);
1882        /// ```
1883        #[skip_dispatch] #[scalar_form((table: &mut ShTable<Self, N>) -> ())]
1884        fn spherical_harmonics_table<const L: usize, const N: usize, const CS: bool>(table: &mut ShTable<<Self as PrimalProjection>::Primal, N>) -> ();
1885
1886        /// Evaluates all harmonics through degree `L` from a prebuilt table.
1887        ///
1888        /// The table holds `Self::Primal` coefficients. See
1889        /// [`spherical_harmonics_table`](RealSpecialMath::spherical_harmonics_table)
1890        /// for how to build it and why, and
1891        /// [`spherical_harmonics`](RealSpecialMath::spherical_harmonics) for the
1892        /// conventions and layout.
1893        #[skip_dispatch] #[scalar_form((table: &ShTable<Self, N>, x: Self, y: Self, z: Self, out: &mut [Self; N]) -> ())]
1894        fn spherical_harmonics_with<const L: usize, const N: usize>(table: &ShTable<<Self as PrimalProjection>::Primal, N>, x: Self, y: Self, z: Self, out: &mut [Self; N]) -> ();
1895
1896    }
1897
1898    /// "Primal" special functions: the value-and-derivative (`_d`) forms of the activation
1899    /// functions, returning `(value, derivative)` together.
1900    ///
1901    /// These exist for *single-value* real numbers (`f32`, `f64`, `Compensated`, ...) where the
1902    /// analytic derivative is a useful, cheaply-shared byproduct of the value. They are **not**
1903    /// implemented for derivative-carrying numbers such as `Dual`: an automatic-differentiation
1904    /// type already produces the derivative from the plain value form (e.g. [`gelu`](RealSpecialMath::gelu)),
1905    /// so the bundled `_d` derivative would be redundant work at the wrong level of abstraction.
1906    ///
1907    /// Each `*_d` method mirrors the like-named value-only function in [`SpecialMath`] /
1908    /// [`RealSpecialMath`], returning that same value as the first tuple element.
1909    #[diagnostic::on_unimplemented(
1910        message = "`{Self}` does not provide value-and-derivative special math (`softplus_d`, `gelu_d`, `spherical_harmonics_d`, ...)",
1911        note = "`RealPrimalMath` builds on `RealSpecialMath` and is implemented only for primal real vectors (plain float vectors and `Compensated`), never for `Dual` or `Complex`, which get their derivatives from the value form instead. A bare `f32`/`f64` does not qualify either. Wrap it in `Vector::<f32>::splat(x)`, or use `ScalarSpecialMath`."
1912    )]
1913    pub trait RealPrimalMath: RealSpecialMathWithPolicy + PrimalMathWithPolicy {
1914        /// [`spherical_harmonics`](RealSpecialMath::spherical_harmonics) plus the
1915        /// ambient Cartesian gradient of every harmonic, into `ddx`/`ddy`/`ddz`.
1916        ///
1917        /// Lives on [`RealPrimalMath`] rather than [`RealSpecialMath`], so `Dual` does
1918        /// not get it, and should not want it. If you need `$\partial/\partial(x,y,z)$`,
1919        /// call this directly rather than evaluating
1920        /// [`spherical_harmonics`](RealSpecialMath::spherical_harmonics) on a
1921        /// `Dual<V, 3>` seeded with an identity Jacobian: this shares the recurrence
1922        /// between the value and all three gradients, whereas dual arithmetic carries a
1923        /// derivative through every operation and costs roughly twice as much.
1924        ///
1925        /// `Dual` earns its keep on the _value_ form instead, where `(x, y, z)` are
1926        /// themselves functions of upstream parameters and the chain rule has real work
1927        /// to do. Even there, going the other way (contracting these three gradients
1928        /// against an upstream Jacobian) loses: spherical harmonics cost about two
1929        /// operations per harmonic to evaluate but three per harmonic per parameter to
1930        /// contract, because one recurrence produces the whole basis.
1931        ///
1932        /// The derivatives are those of the polynomial form at the given (unit)
1933        /// input. Project out the radial component (`g - (g . n) n`) for the
1934        /// tangential gradient. Shares all recurrence work with the value pass, since
1935        /// the gradients come from tabulated norm ratios, not new recurrences.
1936        #[skip_dispatch] #[compose] fn spherical_harmonics_d<const L: usize, const N: usize, const CS: bool>(x: Self, y: Self, z: Self, out: &mut [Self; N], ddx: &mut [Self; N], ddy: &mut [Self; N], ddz: &mut [Self; N]) -> ();
1937
1938        /// [`zernike_basis`](SpecialMath::zernike_basis) plus `$\partial Z_n^m/\partial x$`
1939        /// and `$\partial Z_n^m/\partial y$` for every mode, in the same ANSI layout.
1940        ///
1941        /// This is what a Shack-Hartmann wavefront reconstruction integrates against. The
1942        /// sensor measures local wavefront *slopes*, not the wavefront itself, so the fit
1943        /// matrix is built from the gradient basis and the value basis never appears in it.
1944        ///
1945        /// Lives on [`RealPrimalMath`] rather than [`SpecialMath`] for the same reason
1946        /// [`spherical_harmonics_d`](RealPrimalMath::spherical_harmonics_d) does: `Dual`
1947        /// should not get it and should not want it. Seeding a `Dual<V, 2>` and calling the
1948        /// value form carries two derivative components through every operation of the whole
1949        /// ladder, where this differentiates only the two factors that depend on the point
1950        /// and shares the radial recurrence between the value and both gradients.
1951        ///
1952        /// The gradient is finite everywhere, including the pupil centre. That is the
1953        /// practical dividend of the Cartesian formulation: the polar
1954        /// `$\partial_\theta Z/\rho$` is singular there, and hand-rolled polar
1955        /// implementations guard the origin with a special case.
1956        ///
1957        /// `N` must equal `(L+1)(L+2)/2`, and `NORM` is as on
1958        /// [`zernike_basis`](SpecialMath::zernike_basis). All three output buffers are
1959        /// written in full.
1960        #[skip_dispatch] #[compose] fn zernike_basis_d<const L: usize, const NORM: u8, const N: usize>(x: Self, y: Self, out: &mut [Self; N], ddx: &mut [Self; N], ddy: &mut [Self; N]) -> ();
1961
1962        /// [`spherical_harmonics_with`](RealSpecialMath::spherical_harmonics_with) plus
1963        /// the ambient Cartesian gradients, from a prebuilt table.
1964        #[skip_dispatch] #[compose] fn spherical_harmonics_d_with<const L: usize, const N: usize>(table: &ShTable<Self, N>, x: Self, y: Self, z: Self, out: &mut [Self; N], ddx: &mut [Self; N], ddy: &mut [Self; N], ddz: &mut [Self; N]) -> ();
1965
1966        /// [`softplus`](SpecialMath::softplus) together with its derivative w.r.t. `x`
1967        /// (the logistic sigmoid `$\sigma(kx)$`).
1968        fn softplus_d(self, k: Self, rcp_k: Self) -> (Self, Self);
1969
1970        /// [`gelu`](RealSpecialMath::gelu) together with its derivative w.r.t. `x`.
1971        fn gelu_d(self, alpha: Self) -> (Self, Self);
1972
1973        /// [`swish`](RealSpecialMath::swish) together with its derivative w.r.t. `x`.
1974        fn swish_d(self, beta: Self) -> (Self, Self);
1975
1976        /// [`algebraic_sigmoid`](RealSpecialMath::algebraic_sigmoid) together with its derivative w.r.t. `x`.
1977        fn algebraic_sigmoid_d_n<const N: usize>(self) -> (Self, Self);
1978
1979        /// [`algebraic_sigmoid_d_n`](RealPrimalMath::algebraic_sigmoid_d_n) for a degree known
1980        /// only at runtime.
1981        fn algebraic_sigmoid_d(self, n: u32) -> (Self, Self);
1982
1983        /// [`algebraic_swish`](RealSpecialMath::algebraic_swish) together with its derivative w.r.t. `x`.
1984        fn algebraic_swish_d(self) -> (Self, Self);
1985
1986        /// [`langevin`](RealSpecialMath::langevin) together with its derivative
1987        /// `$L'(x) = \frac{1}{x^2} - \operatorname{csch}^2 x$`.
1988        ///
1989        /// The derivative shares every intermediate with the value, so this costs a
1990        /// handful of arithmetic ops over `langevin` alone.
1991        fn langevin_d(self) -> (Self, Self);
1992    }
1993}
Last built: 2026-09-08 21:35:55 UTC