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}