thermite_complex/math/mod.rs
1// The `fn name[..][..](self: Self, ..)` shape in the `decl_complex_math!` invocation
2// below is the macro DSL's, as in `thermite::math`.
3#![allow(clippy::needless_arbitrary_self_type)]
4
5//! Math kernels for [`Complex`].
6//!
7//! Implements the `Specialized*Math` traits. Complex vectors thereby get
8//! [`CoreMath`](thermite::math::CoreMath),
9//! [`TranscendentalMath`](thermite::math::TranscendentalMath) and
10//! [`SpatialMath`](thermite::math::SpatialMath) (with their `_p` policy forms)
11//! from the blanket impls in `thermite::math`, plus
12//! [`SpecializedComplexMath`] for the operations
13//! over the real part.
14//!
15//! Each kernel evaluates its complex function through the *inner* vector's policy
16//! math (`sin_cos_p`, `exp_p`, `atan2_p`, ...), carrying the caller's policy all the
17//! way down. Anything expressible by composition (`sin`, `cos`, `tan_pi`,
18//! `powi`, `sqrt1pm1`, `compound`, ...) comes from the trait defaults, which are
19//! already correct over C.
20//!
21//! [`RealMath`](thermite::math::RealMath) is not implemented; see the note at the
22//! bottom of this file.
23
24use thermite::math::policy::{DefaultPolicy, Policy, PrecisionPolicy};
25use thermite::math::specialized::{SpecializedCoreMath, SpecializedSpatialMath, SpecializedTranscendentalMath};
26use thermite::prelude::*;
27
28use crate::Complex;
29use self::specialized::{ComplexVector, SpecializedComplexMath};
30use crate::vector::RealFloatVector;
31
32// A copy of thermite::math's (private) decl_math!, dropping the ScalarMath
33// aggregate, which only makes sense for bare f32/f64. The rest is unchanged, so
34// ComplexMath is generated as TranscendentalMath is, #[dispatch] trampolines and
35// all.
36macro_rules! decl_complex_math {
37 ($(
38 $(#[$trait_meta:meta])*
39 trait $trait:ident<$element:ident> $(: $($bound:ident)&+ )? { $(
40 $(#[$meta:meta])*
41 fn $name:ident [ $($generics:tt)* ][$($generic_names:ident),*]( $($arg_name:ident : $arg_ty:ty),* $(,)?) -> $ret:ty;
42 )*}
43 )*) => {paste::paste! {$(
44 #[doc = "" $trait " math functions with customizable policies."]
45 $(#[$trait_meta])*
46 #[doc = ""]
47 #[doc = "Each function takes a [`Policy`] as its first generic argument. For the"]
48 #[doc = "default-policy versions (same names, no `_p` suffix), see [`" $trait "Math`]."]
49 #[doc = ""]
50 #[doc = "Implemented automatically for every type implementing [`Specialized" $trait "Math`]."]
51 #[thermite::dispatch(Self)]
52 pub trait [<$trait MathWithPolicy>] $(: $($bound +)+)? {$(
53 $(#[$meta])* fn [<$name _p>]<P: Policy, $($generics)*>($($arg_name: $arg_ty),*) -> $ret;
54 )*}
55
56 #[doc = "" $trait " math functions using the default policy."]
57 $(#[$trait_meta])*
58 #[doc = ""]
59 #[doc = "Every method here has a counterpart in [`" $trait "MathWithPolicy`] with a `_p`"]
60 #[doc = "suffix that takes an explicit [`Policy`]."]
61 #[doc = ""]
62 #[doc = "Implementors of [`" $trait "MathWithPolicy`] implement this automatically."]
63 #[thermite::dispatch(Self)]
64 pub trait [<$trait Math>]: [<$trait MathWithPolicy>] {$(
65 $(#[$meta])* #[inline(always)] fn $name<$($generics)*>($($arg_name: $arg_ty),*) -> $ret
66 { [<$trait MathWithPolicy>]::[<$name _p>]::<DefaultPolicy, $($generic_names),*>($($arg_name),*) }
67 )*}
68
69 impl<M> [<$trait Math>] for M where M: [<$trait MathWithPolicy>] {}
70
71 // The FloatVector<Element = E> bound is what ties E down, as in core.
72 #[thermite::dispatch(Self)]
73 impl<E: $element, V: FloatVector<Element = E> + $($($bound +)+)?> [<$trait MathWithPolicy>] for V
74 where V: [<Specialized $trait Math>]<E>
75 {$(
76 $(#[$meta])* #[inline(always)] fn [<$name _p>]<P: Policy, $($generics)*>($($arg_name: $arg_ty),*) -> $ret
77 { <V as [<Specialized $trait Math>]<E>>::$name::<P, $($generic_names),*>($($arg_name),*) }
78 )*})*
79 }};
80}
81
82decl_complex_math! {
83 /// Operations whose result is real (modulus, argument, polar form) or whose
84 /// argument is (a real power, base, or logarithm base), which the `Self -> Self`
85 /// core families cannot express.
86 ///
87 /// The purely complex operations (`exp`, `ln`, `sin`, `sqrt`, `powf`, ...) fit
88 /// the core families and come from
89 /// [`TranscendentalMath`](thermite::math::TranscendentalMath) as they do for
90 /// any other vector.
91 trait Complex<FloatElement>: ComplexVector {
92 /// The modulus `$|z|$`, as a real value.
93 ///
94 /// Uses `hypot`, so it does not overflow for large components the way
95 /// `sqrt(norm_sqr())` would.
96 fn norm[][](self: Self) -> Self::Real;
97
98 /// The principal argument `arg(z)`, in `(-pi, pi]`, as a real value.
99 fn arg[][](self: Self) -> Self::Real;
100
101 /// Converts to polar form `(r, theta)`, such that `self == r * exp(i*theta)`.
102 fn to_polar[][](self: Self) -> (Self::Real, Self::Real);
103
104 /// Builds a complex number from a polar representation `r * exp(i*theta)`.
105 fn from_polar[][](r: Self::Real, theta: Self::Real) -> Self;
106
107 /// Raises `self` to a real power.
108 ///
109 /// The complex-exponent form is [`powf`](thermite::math::TranscendentalMath::powf).
110 fn powfr[][](self: Self, e: Self::Real) -> Self;
111
112 /// Raises a real base to the complex power `self`.
113 fn expf[][](self: Self, base: Self::Real) -> Self;
114
115 /// The logarithm of `self` in an arbitrary real base.
116 ///
117 /// The complex-base form is [`log`](thermite::math::TranscendentalMath::log).
118 fn logr[][](self: Self, base: Self::Real) -> Self;
119
120 /// `1/self`, scaling by the modulus and not its square.
121 ///
122 /// Survives the magnitudes where [`inv`](ComplexVector::inv) would have
123 /// `norm_sqr()` overflow to infinity or underflow to zero.
124 fn finv[][](self: Self) -> Self;
125
126 /// `self/rhs`, scaling by the modulus and not its square.
127 ///
128 /// Survives the magnitudes where `/` would have `rhs.norm_sqr()` overflow
129 /// to infinity or underflow to zero.
130 fn fdiv[][](self: Self, rhs: Self) -> Self;
131 }
132}
133
134pub mod specialized;
135
136#[cfg(feature = "special")]
137pub mod special;
138
139// --- SpecializedComplexMath: the real-valued and real-argument operations ---
140
141impl<V: RealFloatVector> SpecializedComplexMath<Complex<V::Element>> for Complex<V> {
142 #[inline(always)]
143 fn norm<P: Policy>(self) -> V {
144 self.re.hypot_p::<P>(self.im)
145 }
146
147 #[inline(always)]
148 fn arg<P: Policy>(self) -> V {
149 self.im.atan2_p::<P>(self.re)
150 }
151
152 #[inline(always)]
153 fn from_polar<P: Policy>(r: V, theta: V) -> Self {
154 let (s, c) = theta.sin_cos_p::<P>();
155
156 Self::new(r * c, r * s)
157 }
158
159 #[inline(always)]
160 fn powfr<P: Policy>(self, e: V) -> Self {
161 // z^y = (r e^(i t))^y = r^y e^(i t y)
162 let (r, theta) = self.to_polar_p::<P>();
163
164 Self::from_polar_p::<P>(r.powf_p::<P>(e), theta * e)
165 }
166
167 #[inline(always)]
168 fn expf<P: Policy>(self, base: V) -> Self {
169 // b^(a + ci) = b^a * e^(i c ln b)
170 Self::from_polar_p::<P>(base.powf_p::<P>(self.re), self.im * base.ln_p::<P>())
171 }
172
173 #[inline(always)]
174 fn logr<P: Policy>(self, base: V) -> Self {
175 // log_b(z) = ln(z) / ln(b); one real reciprocal, then scale both components.
176 let (r, theta) = self.to_polar_p::<P>();
177 let d = base.ln_p::<P>().reciprocal_p::<P>();
178
179 Self::new(r.ln_p::<P>() * d, theta * d)
180 }
181
182 #[inline(always)]
183 fn finv<P: Policy>(self) -> Self {
184 // Scaling twice by 1/|z| keeps every intermediate in range, where
185 // conj(z) / |z|^2 would overflow the square.
186 //
187 // Taking the reciprocal explicitly, rather than writing `(conj/n)/n`:
188 // `Div<V>` is already a reciprocal-and-scale, so the quotient form spent
189 // two divisions computing the same 1/|z| twice. `digamma` calls this in a
190 // loop.
191 let inv = self.norm_p::<P>().reciprocal_p::<P>();
192
193 self.conj() * inv * inv
194 }
195}
196
197// --- SpecializedCoreMath ---
198
199impl<V: RealFloatVector> SpecializedCoreMath<Complex<V::Element>> for Complex<V> {
200 /// `P(z)/Q(z)`, evaluated directly or through `1/z` depending on which is better
201 /// conditioned.
202 ///
203 /// Overridden only to change *which quantity* that decision is made on. The
204 /// generic default tests `x.cmp_gt(ONE)`, which over C is the lexicographic order
205 /// on `(re, im)` - so it keys off the real part alone and will happily evaluate
206 /// the direct form at `z = 10^150 i`, overflowing, while reporting that `z` is
207 /// "not greater than one". The condition that actually matters is `|z| > 1`.
208 ///
209 /// Both forms are the same rational function (`P_rev(1/z)/Q_rev(1/z)` differs from
210 /// `P(z)/Q(z)` only by `z^(D-N)`, corrected below), so this is a conditioning fix,
211 /// not a correctness one - except where the wrong choice overflows outright.
212 #[inline(always)]
213 fn poly_rational<P: Policy, const N: usize, const D: usize>(
214 self,
215 numerator: &[Complex<V::Element>; N],
216 denominator: &[Complex<V::Element>; D],
217 ) -> Self {
218 let x = self;
219
220 if const { P::POLICY.precision.le(thermite::math::policy::PrecisionPolicy::Average) } {
221 let n = SpecializedCoreMath::poly::<P, N>(x, numerator);
222 let d = SpecializedCoreMath::poly::<P, D>(x, denominator);
223
224 return n.approx_div_p::<P>(d);
225 }
226
227 let invert = x.norm_sqr().cmp_gt(V::ONE);
228
229 let mut n0 = Self::EMPTY;
230 let mut d0 = Self::EMPTY;
231
232 if const { P::POLICY.avoid_branching } || !invert.all() {
233 n0 = SpecializedCoreMath::poly::<P, N>(x, numerator);
234 d0 = SpecializedCoreMath::poly::<P, D>(x, denominator);
235 }
236
237 let mut z = Self::EMPTY;
238 let mut n1 = Self::EMPTY;
239 let mut d1 = Self::EMPTY;
240
241 if const { P::POLICY.avoid_branching } || invert.any() {
242 z = SpecializedCoreMath::reciprocal::<P>(x);
243 n1 = SpecializedCoreMath::poly_rev::<P, N>(z, numerator);
244 d1 = SpecializedCoreMath::poly_rev::<P, D>(z, denominator);
245 }
246
247 let n = invert.select(n1, n0);
248 let d = invert.select(d1, d0);
249
250 let res = n.approx_div_p::<P>(d);
251
252 // Same degree: the reversed form is the same value, nothing to undo.
253 if const { N == D } {
254 return res;
255 }
256
257 if const { P::POLICY.avoid_branching } || invert.any() {
258 let (u, e) = if const { N < D } { (z, D - N) } else { (x, N - D) };
259
260 return invert.select(res * SpecializedCoreMath::powi::<P>(u, e as i32), res);
261 }
262
263 res
264 }
265
266 // 1/sqrt(z) = conj(sqrt(z)) / |z|, since |sqrt(z)|^2 == |z|.
267 #[inline(always)]
268 fn inverse_sqrt<P: Policy>(self) -> Self {
269 let s = self.sqrt();
270 let inv = self.norm_p::<P>().reciprocal_p::<P>();
271
272 Complex::new(s.re * inv, -(s.im * inv))
273 }
274}
275
276// --- SpecializedTranscendentalMath ---
277
278/// `$iz = -\operatorname{Im} z + i\operatorname{Re} z$`: a component swap and a sign flip.
279///
280/// Worth a helper because the natural spelling is not free. `z.mul_add(Self::I, w)`
281/// is a complex FMA - four inner FMAs - over a constant of zeros and ones, and IEEE
282/// forbids folding `a*0.0 + b` to `b` (`a` may be infinite, and the zero has a sign),
283/// so all four survive into the assembly.
284#[inline(always)]
285fn mul_i<V: RealFloatVector>(z: Complex<V>) -> Complex<V> {
286 Complex::new(-z.im, z.re)
287}
288
289/// `$-iz = \operatorname{Im} z - i\operatorname{Re} z$`. See [`mul_i`].
290#[inline(always)]
291fn mul_neg_i<V: RealFloatVector>(z: Complex<V>) -> Complex<V> {
292 Complex::new(z.im, -z.re)
293}
294
295/// Replaces the lanes where a hyperbolic quotient has overflowed with its limit.
296///
297/// `tan`/`tanh` divide by `cosh` plus a bounded term. Past `$|2x| \approx 710$` in
298/// binary64 that denominator is infinite while the numerator's `sinh` is too, so the
299/// quotient is `inf/inf` - `NaN` in the component that should have saturated, and a
300/// signed zero in the other. The function itself is perfectly well behaved there and
301/// tends to a unit along one axis.
302///
303/// Keyed on the denominator rather than a magnitude threshold, so it fires exactly on
304/// the lanes that lost the value and needs no per-element constant. `is_infinite`, not
305/// `!is_finite`: a `NaN` argument must still produce `NaN`.
306///
307/// A no-op unless the policy sets
308/// [`check_overflow`](thermite::math::policy::PolicyParameters::check_overflow).
309#[inline(always)]
310fn saturate<P: Policy, V: RealFloatVector>(res: Complex<V>, denom: V, limit: Complex<V>) -> Complex<V> {
311 if const { !P::POLICY.check_overflow } {
312 return res;
313 }
314
315 let lost = denom.is_infinite();
316
317 if thermite::unlikely(lost.any()) {
318 return lost.select(limit, res);
319 }
320
321 res
322}
323
324/// `$\operatorname{asinh}(p) = \ln(p + \sqrt{1 + p^2})$`, conditioned at both ends.
325///
326/// Serves `asin` too: `$(iz)^2 = -z^2$`, so `$\sqrt{1 - z^2}$` is this function's
327/// `$\sqrt{1 + p^2}$` at `$p = iz$`, and `$\operatorname{asin}(z) = -i\operatorname{asinh}(iz)$`
328/// exactly. One kernel, two functions.
329///
330/// The naive `$\ln(p + s)$` loses everything at both extremes:
331///
332/// - **Large `$|p|$`**: `$s \to \mp p$` and the sum cancels. At `$z = 10^8 i$` the
333/// `asin` sum is `$-10^8 + 10^8$` - *exactly* zero - so `$\ln 0$` returned an
334/// infinity where the true value is `19.11i`. The companion `$s - p$` is the other
335/// root, and `$(p + s)(s - p) = s^2 - p^2 = 1$`, so it is both exact and the
336/// well-conditioned one. `$|w| < 1$` tests which cancelled, and `$\ln w = -\ln w'$`
337/// holds outright rather than up to `$2\pi i$`, the companion lying in the right
338/// half-plane exactly when it is selected.
339/// - **Small `$|p|$`**: `$w = 1 + O(p)$`, and forming that sum rounds away the very
340/// `$p$` the answer consists of - `asinh(1e-8)` kept 8 of its 16 digits. Feeding
341/// `$w - 1$` to `ln_1p` instead fixes it, provided `$w - 1$` is *not* formed by
342/// subtracting: `$s - 1 = p^2/(s + 1)$` has no cancellation of its own.
343///
344/// # Policy
345///
346/// Both corrections are gated at [`Best`](PrecisionPolicy::Best) and above, together
347/// costing one complex division and, only where a lane actually cancels, a second
348/// logarithm. Below that the plain `$\ln(p + s)$` stands, as before.
349#[inline(always)]
350fn log_asinh<P: Policy, V: RealFloatVector>(p: Complex<V>) -> Complex<V> {
351 let s = p.mul_add(p, Complex::ONE).sqrt();
352 let w = p + s;
353
354 if const { !P::POLICY.precision.ge(PrecisionPolicy::Best) } {
355 return w.ln_p::<P>();
356 }
357
358 // w - 1, without ever forming the difference.
359 let mut res = (p + p.square() / (s + Complex::ONE)).ln_1p_p::<P>();
360
361 // `|w| < 1` alone is too eager. For tiny `p` with a negative real part `w = 1 + p`
362 // sits just under one without anything having cancelled, and the log branch would
363 // undo the `ln_1p` correction above. Genuine cancellation needs the two terms to be
364 // large and nearly opposite, so `|p| > 1` as well - which is also where `u` itself
365 // stops being trustworthy, `u = w - 1 ~ -1` then being a difference of two terms of
366 // size `|p|`.
367 let flip = w.norm_sqr().cmp_lt(V::ONE) & p.norm_sqr().cmp_gt(V::ONE);
368
369 if thermite::unlikely(flip.any()) {
370 let l = (s - p).ln_p::<P>();
371
372 res = flip.select(Complex::new(-l.re, -l.im), res);
373 }
374
375 res
376}
377
378/// `$\ln w$`, taking the reciprocal companion where `w` has cancelled.
379///
380/// The inverse trigonometric and hyperbolic functions are each `$\ln$` of a sum of two
381/// terms whose *difference* is the algebraically conjugate root, and in every case the
382/// product of the two is exactly one:
383///
384/// ```text
385/// asin : (iz + s)(s - iz) = s^2 + z^2 = 1 s = sqrt(1 - z^2)
386/// acos : (z + is)(z - is) = z^2 + s^2 = 1 s = sqrt(1 - z^2)
387/// asinh: (z + s)(s - z) = s^2 - z^2 = 1 s = sqrt(1 + z^2)
388/// ```
389///
390/// So exactly one of the pair is well conditioned: for large `$|z|$` the two terms are
391/// nearly equal in magnitude, one sum cancels to nothing and the other doubles. At
392/// `$z = 10^8 i$` the `asin` sum is `$-10^8 + 10^8$`, *exactly zero*, and `$\ln 0$`
393/// returned an infinity for a true value of `19.11i`.
394///
395/// Since `$ww' = 1$`, `$|w| < 1$` is an exact test for which one cancelled, and
396/// `$\ln w = -\ln w'$` - unambiguously, not merely up to `$2\pi i$`, because the
397/// companion is in the right half-plane exactly when it is the one being selected.
398///
399/// One `ln` either way: the argument is blended *before* the logarithm, so the cost
400/// over the naive form is a complex add, a `norm_sqr` and two selects.
401///
402/// # Policy
403///
404/// Gated at [`Best`](PrecisionPolicy::Best) and above. Below it the cancelling form is
405/// used unconditionally, as it was before - the failure needs `$|z| \gg 1$`, and the
406/// lower tiers do not promise the digits that are lost.
407#[inline(always)]
408fn ln_reciprocal_pair<P: Policy, V: RealFloatVector>(w: Complex<V>, companion: Complex<V>) -> Complex<V> {
409 if const { !P::POLICY.precision.ge(PrecisionPolicy::Best) } {
410 return w.ln_p::<P>();
411 }
412
413 let flip = w.norm_sqr().cmp_lt(V::ONE);
414
415 let l = flip.select(companion, w).ln_p::<P>();
416
417 Complex::new(l.re.neg_c(flip), l.im.neg_c(flip))
418}
419
420/// `$b^{a+ci} - 1$` from the real `$b^a - 1$` and the angle `$\phi = c\ln b$`.
421///
422/// ```text
423/// b^(a+ci) - 1 = (b^a cos(phi) - 1) + i b^a sin(phi)
424/// = (bm1 cos(phi) + (cos(phi) - 1)) + i (bm1 + 1) sin(phi)
425/// ```
426///
427/// `exp_m1` and `cos_m1` are the cancellation-free primitives, keeping the relative
428/// accuracy near `z = 0` that the naive form loses entirely. Shared by the `e`, `2`
429/// and `10` bases, which differ only in which real `*_m1` supplies `bm1` and in the
430/// scale of `phi`.
431#[inline(always)]
432fn expm1_from<P: Policy, V: RealFloatVector>(bm1: V, phi: V) -> Complex<V> {
433 let (s, c) = phi.sin_cos_p::<P>();
434 let cm1 = phi.cos_m1_p::<P>();
435
436 Complex::new(bm1.mul_adde(c, cm1), s.mul_adde(bm1, s))
437}
438
439impl<V: RealFloatVector> SpecializedTranscendentalMath<Complex<V::Element>> for Complex<V> {
440 /// `sin(a + bi) = sin(a)cosh(b) + i*cos(a)sinh(b)`,
441 /// `cos(a + bi) = cos(a)cosh(b) - i*sin(a)sinh(b)`.
442 #[inline(always)]
443 fn sin_cos<P: Policy>(self) -> (Self, Self) {
444 let (s, c) = self.re.sin_cos_p::<P>();
445 let (sh, ch) = self.im.sinh_cosh_p::<P>();
446
447 (Complex::new(s * ch, c * sh), Complex::new(c * ch, -(s * sh)))
448 }
449
450 /// `$\sin(\pi z)$` and `$\cos(\pi z)$`, from the *real* `sincos_pi`.
451 ///
452 /// Must be overridden rather than left to the default. That default is
453 /// `sin_cos(z * pi)`, which rounds `pi * Re z` before doing any reduction and so
454 /// throws away the exact argument reduction real `sincos_pi` performs near the
455 /// integers - precisely where the Gamma reflection formulas put their poles, and
456 /// where `sin(pi z)` passes through zero. It is also no more work: one real
457 /// `sincos_pi` and one real `sinh_cosh`, the same two calls the default makes.
458 #[inline(always)]
459 fn sincos_pi<P: Policy>(self) -> (Self, Self) {
460 let (s, c) = self.re.sincos_pi_p::<P>();
461 let (sh, ch) = (self.im * <V as thermite::math::FloatConsts>::PI).sinh_cosh_p::<P>();
462
463 (Complex::new(s * ch, c * sh), Complex::new(c * ch, -(s * sh)))
464 }
465
466 /// `$\tan(a + bi) = \frac{\sin 2a + i\sinh 2b}{\cos 2a + \cosh 2b}$`
467 ///
468 /// The doubled-angle form takes one real division, where the default
469 /// (`sin_cos` then a complex divide) takes a complex one.
470 ///
471 /// Under [`check_overflow`](thermite::math::policy::PolicyParameters::check_overflow)
472 /// the saturation is handled: `$\tan(z) \to i\,\mathrm{sign}(b)$` as `$|b|$` grows,
473 /// but `$\sinh$` and `$\cosh$` both overflow past `$|2b| \approx 710$` and the
474 /// quotient becomes `inf/inf`. `tan(1 + 400i)` was `NaN`.
475 #[inline(always)]
476 fn tan<P: Policy>(self) -> Self {
477 let (two_re, two_im) = (self.re + self.re, self.im + self.im);
478
479 let (s, c) = two_re.sin_cos_p::<P>();
480 let (sh, ch) = two_im.sinh_cosh_p::<P>();
481
482 let denom = c + ch;
483 let res = Complex::new(s, sh) / denom;
484
485 saturate::<P, V>(res, denom, Complex::new(V::ZERO, V::ONE.mul_sign(self.im)))
486 }
487
488 /// `sinh(a + bi) = sinh(a)cos(b) + i*cosh(a)sin(b)`,
489 /// `cosh(a + bi) = cosh(a)cos(b) + i*sinh(a)sin(b)`.
490 #[inline(always)]
491 fn sinh_cosh<P: Policy>(self) -> (Self, Self) {
492 let (s, c) = self.im.sin_cos_p::<P>();
493 let (sh, ch) = self.re.sinh_cosh_p::<P>();
494
495 (Complex::new(sh * c, ch * s), Complex::new(ch * c, sh * s))
496 }
497
498 /// `tanh(a + bi) = (sinh(2a) + i*sin(2b)) / (cosh(2a) + cos(2b))`.
499 ///
500 /// Saturates to `$\mathrm{sign}(a)$` for large `$|a|$` under `check_overflow`; see
501 /// [`tan`](Self::tan), of which this is the transpose.
502 #[inline(always)]
503 fn tanh<P: Policy>(self) -> Self {
504 let (two_re, two_im) = (self.re + self.re, self.im + self.im);
505
506 let (s, c) = two_im.sin_cos_p::<P>();
507 let (sh, ch) = two_re.sinh_cosh_p::<P>();
508
509 let denom = ch + c;
510 let res = Complex::new(sh, s) / denom;
511
512 saturate::<P, V>(res, denom, Complex::new(V::ONE.mul_sign(self.re), V::ZERO))
513 }
514
515 /// `$\mathrm{sinc}(z) = \sin(z)/z$`, with the removable singularity filled in.
516 #[inline(always)]
517 fn sinc<P: Policy>(self) -> Self {
518 let is_zero = self.is_zero();
519
520 // 0/0 = NaN at the origin, so the guard has to be a select; the quotient
521 // cannot be patched up after the fact.
522 let q = self.sin_p::<P>() / self;
523
524 is_zero.select(Self::ONE, q)
525 }
526
527 /// `$\mathrm{sinc}_\pi(z) = \frac{\sin(\pi z)}{\pi z}$`, singularity filled in.
528 ///
529 /// Overridden so the zeros are *exact*. The default is `sinc(z * pi)`, which
530 /// rounds `pi * Re z` before reducing; the subsequent division by `pi z` cancels
531 /// most of that error, so the default is accurate to about an ulp - but at a
532 /// non-zero integer it returns ~1e-16 rather than zero. Going through the real
533 /// `sin_pi`, which is exactly zero there, makes this exactly zero too.
534 ///
535 /// That is the property that makes `sinc_pi` an *interpolating* kernel: Lanczos
536 /// and sinc resampling reproduce their samples only if the kernel vanishes at
537 /// every non-zero integer.
538 #[inline(always)]
539 fn sinc_pi<P: Policy>(self) -> Self {
540 let is_zero = self.is_zero();
541
542 // As in `sinc`: 0/0 is NaN at the origin, so the guard must be a select.
543 // Trait-qualified: the blanket `TranscendentalMath::sin_pi` is equally in scope.
544 let q = SpecializedTranscendentalMath::sin_pi::<P>(self) / (self * <V as thermite::math::FloatConsts>::PI);
545
546 is_zero.select(Self::ONE, q)
547 }
548
549 /// `e^(a + bi) = e^a * (cos(b) + i*sin(b))`.
550 #[inline(always)]
551 fn exp<P: Policy>(self) -> Self {
552 Self::from_polar_p::<P>(self.re.exp_p::<P>(), self.im)
553 }
554
555 /// `exph(z) = e^z / 2`.
556 #[inline(always)]
557 fn exph<P: Policy>(self) -> Self {
558 Self::from_polar_p::<P>(self.re.exph_p::<P>(), self.im)
559 }
560
561 /// `$2^z = 2^a e^{ib\ln 2}$`.
562 ///
563 /// # Policy
564 ///
565 /// Above [`Average`](PrecisionPolicy::Average) the real part goes through the real
566 /// `exp2`. Rescaling it as `exp(a ln 2)` instead rounds `a ln 2` first, and `exp`
567 /// then amplifies that rounding by the argument - `exp2(1000)` is wrong in its
568 /// tenth digit (~300 ulp) that way. The imaginary part can afford the multiply
569 /// either way, feeding a `sincos` that reduces its own argument.
570 ///
571 /// At or below `Average` the rescaled form is used, `exp2` being the more
572 /// expensive kernel and 300 ulp being well inside that tier's budget.
573 #[inline(always)]
574 fn exp2<P: Policy>(self) -> Self {
575 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } {
576 return (self * V::LN_2).exp_p::<P>();
577 }
578
579 Self::from_polar_p::<P>(self.re.exp2_p::<P>(), self.im * V::LN_2)
580 }
581
582 /// `$10^z = 10^a e^{ib\ln 10}$`. See [`exp2`](Self::exp2), including the policy split.
583 #[inline(always)]
584 fn exp10<P: Policy>(self) -> Self {
585 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } {
586 return (self * V::LN_10).exp_p::<P>();
587 }
588
589 Self::from_polar_p::<P>(self.re.exp10_p::<P>(), self.im * V::LN_10)
590 }
591
592 /// `$e^z - 1$`, without the cancellation of forming `$e^z$` and subtracting one.
593 #[inline(always)]
594 fn exp_m1<P: Policy>(self) -> Self {
595 expm1_from::<P, V>(self.re.exp_m1_p::<P>(), self.im)
596 }
597
598 /// `$2^z - 1$`. Through the real `exp2_m1` above `Average`, as [`exp2`](Self::exp2) is.
599 #[inline(always)]
600 fn exp2_m1<P: Policy>(self) -> Self {
601 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } {
602 return (self * V::LN_2).exp_m1_p::<P>();
603 }
604
605 expm1_from::<P, V>(self.re.exp2_m1_p::<P>(), self.im * V::LN_2)
606 }
607
608 /// `$10^z - 1$`. Through the real `exp10_m1` above `Average`, as [`exp10`](Self::exp10) is.
609 #[inline(always)]
610 fn exp10_m1<P: Policy>(self) -> Self {
611 if const { P::POLICY.precision.le(PrecisionPolicy::Average) } {
612 return (self * V::LN_10).exp_m1_p::<P>();
613 }
614
615 expm1_from::<P, V>(self.re.exp10_m1_p::<P>(), self.im * V::LN_10)
616 }
617
618 /// `z^w`, the principal value.
619 #[inline(always)]
620 fn powf<P: Policy>(self, e: Self) -> Self {
621 // z^w = (r e^(i t))^(c + di)
622 // = r^c e^(-d t) * (cos(c t + d ln r) + i sin(c t + d ln r))
623 // = from_polar(e^(c ln r - d t), c t + d ln r)
624 //
625 // The angle needs `ln r` at every policy, so it is computed once up front.
626 let (r, theta) = self.to_polar_p::<P>();
627 let ln_r = r.ln_p::<P>();
628
629 let angle = e.im.mul_adde(ln_r, e.re * theta);
630
631 let mut modulus = if const { P::POLICY.precision.le(PrecisionPolicy::Average) } {
632 // Fused exponent: one `exp` for the whole modulus. `ln r` is already in
633 // hand and `powf` is `exp(c ln r)` underneath, so the split form costs a
634 // second `exp` and a second `ln` for an extended-precision `c ln r` that
635 // this tier is not paying for.
636 e.im.nmul_adde(theta, e.re * ln_r).exp_p::<P>()
637 } else {
638 // `powf` carries more precision through `c ln r` than the fused exponent
639 // can, which is what keeps `|z^w|` near an ulp once `|c ln r|` is large.
640 r.powf_p::<P>(e.re) * (-e.im * theta).exp_p::<P>()
641 };
642
643 if const { P::POLICY.check_overflow && !P::POLICY.precision.le(PrecisionPolicy::Average) } {
644 // `r^c` leaves the range on its own where `e^{-dt}` would have brought the
645 // product back - `(-1e200)^(2 + 300i)` is about 1e-9 and the split form
646 // gives `inf`, or `NaN` from the mirror-image `0 * inf`. The fused exponent
647 // has no such intermediate, so it covers those lanes.
648 let lost = !modulus.is_finite();
649
650 if thermite::unlikely(lost.any()) {
651 modulus = lost.select(e.im.nmul_adde(theta, e.re * ln_r).exp_p::<P>(), modulus);
652 }
653 }
654
655 Self::from_polar_p::<P>(modulus, angle)
656 }
657
658 /// The principal cube root.
659 ///
660 /// This does not agree with the real cube root of a negative real: the real
661 /// cube root of -8 is -2, the principal complex one `$1 + i\sqrt{3}$`.
662 #[inline(always)]
663 fn cbrt<P: Policy>(self) -> Self {
664 let (r, theta) = self.to_polar_p::<P>();
665
666 // 1/3 is not representable, so divide; multiplying by a rounded reciprocal
667 // loses a bit at the higher policies.
668 let three: V = thermite::const_splat!(int <V::Element>: 3);
669
670 Self::from_polar_p::<P>(r.cbrt_p::<P>(), theta / three)
671 }
672
673 /// The principal `N`th root, `$z^{1/N} = |z|^{1/N} e^{i\arg(z)/N}$`.
674 ///
675 /// The trait default is real-only: for odd `N` it takes `abs()` and restores the
676 /// sign afterwards, which over C collapses `z` to its modulus and returns a real
677 /// root.
678 #[inline(always)]
679 fn nth_root<P: Policy, const N: usize>(self) -> Self {
680 let (r, theta) = self.to_polar_p::<P>();
681
682 // Not `const_splat!`: `N` is a generic parameter, which cannot appear in the
683 // const operation that arm expands to.
684 let n = V::splat(<V::Element as FloatElement>::from_int(N as thermite::LargeInt));
685
686 Self::from_polar_p::<P>(r.powf_p::<P>(n.reciprocal_p::<P>()), theta / n)
687 }
688
689 /// The principal natural logarithm: `ln(z) = ln|z| + i*arg(z)`.
690 ///
691 /// Branch cut on `(-inf, 0]`, continuous from above; `-pi <= Im(ln z) <= pi`.
692 #[inline(always)]
693 fn ln<P: Policy>(self) -> Self {
694 let (r, theta) = self.to_polar_p::<P>();
695
696 Complex::new(r.ln_p::<P>(), theta)
697 }
698
699 /// `$\ln(1 + z)$`, without the cancellation of forming `1 + z` first.
700 #[inline(always)]
701 fn ln_1p<P: Policy>(self) -> Self {
702 // |1 + z|^2 - 1 = 2a + a^2 + b^2 = a(a + 2) + b^2, so
703 // Re = 0.5 * ln_1p(a(a + 2) + b^2), Im = atan2(b, 1 + a)
704 // The real part goes through ln_1p to keep the accuracy near z = 0 that
705 // ln(1 + z) would lose.
706 let t = self.re.mul_adde(self.re + V::TWO, self.im * self.im);
707
708 Complex::new(
709 t.ln_1p_p::<P>() * <V as FloatVector>::HALF,
710 self.im.atan2_p::<P>(self.re + V::ONE),
711 )
712 }
713
714 /// `$\log_2 z = \log_2|z| + i\arg(z)\log_2 e$`.
715 ///
716 /// Through the real `log2` rather than `ln(z) * log2(e)`, which is the same work
717 /// (one multiply fewer, in fact - the argument is scaled but `ln|z|` is not) and
718 /// picks up whatever the element's own `log2` does. Measured identical to the
719 /// rescaled form on f64, where thermite's `log2` *is* `ln * LOG2_E`; f32 has a
720 /// dedicated kernel, so no policy gate is warranted either way.
721 #[inline(always)]
722 fn log2<P: Policy>(self) -> Self {
723 let (r, theta) = self.to_polar_p::<P>();
724
725 Complex::new(r.log2_p::<P>(), theta * V::LOG2_E)
726 }
727
728 /// `$\log_{10} z = \log_{10}|z| + i\arg(z)\log_{10} e$`. See [`log2`](Self::log2).
729 #[inline(always)]
730 fn log10<P: Policy>(self) -> Self {
731 let (r, theta) = self.to_polar_p::<P>();
732
733 Complex::new(r.log10_p::<P>(), theta * V::LOG10_E)
734 }
735
736 /// `log_N(z) = ln(z) / ln(N)` for a compile-time integer base.
737 #[inline(always)]
738 fn log_n<P: Policy, const N: usize>(self) -> Self {
739 // See `nth_root`: a generic `N` rules `const_splat!` out here.
740 let ln_n = V::splat(<V::Element as FloatElement>::from_int(N as thermite::LargeInt)).ln_p::<P>();
741
742 self.ln_p::<P>() / ln_n
743 }
744
745 /// `$\ln(1 - e^{-z})$`.
746 ///
747 /// The `_ext` form lets a real vector reuse an already-computed `ln(x)`. There
748 /// is no such shortcut over C, so it forwards to the plain form.
749 #[inline(always)]
750 fn ln1m_expnx_ext<P: Policy>(self, _lnx: Self) -> Self {
751 self.ln1m_expnx_p::<P>()
752 }
753
754 // --- inverse trigonometric / hyperbolic functions ---
755
756 /// `asin(z) = -i ln(iz + sqrt(1 - z^2))`.
757 ///
758 /// Branch cuts on `(-inf, -1)` (continuous from above) and `(1, inf)`
759 /// (continuous from below); `-pi/2 <= Re(asin z) <= pi/2`.
760 #[inline(always)]
761 fn asin<P: Policy>(self) -> Self {
762 // asin(z) = -i*asinh(iz); see [`log_asinh`] for the conditioning.
763 mul_neg_i(log_asinh::<P, V>(mul_i(self)))
764 }
765
766 /// `acos(z) = -i ln(z + i sqrt(1 - z^2))`.
767 ///
768 /// Branch cuts on `(-inf, -1)` and `(1, inf)`; `0 <= Re(acos z) <= pi`.
769 #[inline(always)]
770 fn acos<P: Policy>(self) -> Self {
771 let is = mul_i(self.nmul_add(self, Self::ONE).sqrt());
772
773 mul_neg_i(ln_reciprocal_pair::<P, V>(self + is, self - is))
774 }
775
776 /// `atan(z) = (ln(1 + iz) - ln(1 - iz)) / (2i)`.
777 ///
778 /// Branch cuts on `(-inf*i, -i]` and `[i, inf*i)`; `-pi/2 <= Re(atan z) <= pi/2`.
779 #[inline(always)]
780 fn atan<P: Policy>(self) -> Self {
781 // 1 +- iz = (1 -+ Im z) +- i*Re z: two adds, no multiply by the unit.
782 let a = Complex::new(V::ONE - self.im, self.re);
783 let b = Complex::new(V::ONE + self.im, -self.re);
784
785 // z / (2i) == -0.5i * z, and the -i is the free swap-and-negate.
786 mul_neg_i(a.ln_p::<P>() - b.ln_p::<P>()) * <V as FloatVector>::HALF
787 }
788
789 /// `asinh(z) = ln(z + sqrt(1 + z^2))`.
790 #[inline(always)]
791 fn asinh<P: Policy>(self) -> Self {
792 log_asinh::<P, V>(self)
793 }
794
795 /// `acosh(z) = 2 ln(sqrt((z+1)/2) + sqrt((z-1)/2))`.
796 ///
797 /// Branch cut on `(-inf, 1)`, continuous from above.
798 #[inline(always)]
799 fn acosh<P: Policy>(self) -> Self {
800 // (z +- 1)/2 scales by a *real* half, so the complex FMA (four inner FMAs,
801 // half of them against a zero imaginary part) is one FMA and one multiply.
802 let h = <V as FloatVector>::HALF;
803 let half_im = self.im * h;
804
805 let a = Complex::new(self.re.mul_adde(h, h), half_im).sqrt();
806 let b = Complex::new(self.re.mul_sube(h, h), half_im).sqrt();
807
808 let half_res = (a + b).ln_p::<P>();
809
810 half_res + half_res
811 }
812
813 /// `atanh(z) = (ln(1 + z) - ln(1 - z)) / 2`.
814 ///
815 /// Branch cuts on `(-inf, -1]` and `[1, inf)`.
816 #[inline(always)]
817 fn atanh<P: Policy>(self) -> Self {
818 ((Self::ONE + self).ln_p::<P>() - (Self::ONE - self).ln_p::<P>()) * <V as FloatVector>::HALF
819 }
820}
821
822// --- SpecializedSpatialMath: the norms, as real-valued complex numbers ---
823
824/// The modulus of each term, for the `hypot` family.
825#[inline(always)]
826fn moduli<V: RealFloatVector, P: Policy, const N: usize>(values: [Complex<V>; N]) -> [V; N] {
827 let mut out = [V::ZERO; N];
828
829 let mut i = 0;
830 while i < N {
831 out[i] = values[i].norm_p::<P>();
832 i += 1;
833 }
834
835 out
836}
837
838impl<V: RealFloatVector> SpecializedSpatialMath<Complex<V::Element>> for Complex<V> {
839 /// `$\sqrt{\sum_i |z_i|^2}$`, as a real complex number.
840 ///
841 /// Must be overridden, and not only for tuning. The generic `hypot_n` changes
842 /// *meaning* over C depending on the precision policy: its high-precision path
843 /// opens with `abs()`, which here is the modulus, so everything after it is real
844 /// and the result is the norm - but the `PrecisionPolicy::Worst` path skips that
845 /// and squares directly, giving the analytic continuation `sqrt(sum z_i^2)`
846 /// instead. Two different functions behind one name, chosen by a policy.
847 ///
848 /// This pins the norm, matching [`l2_norm`](Self::l2_norm) and the crate docs.
849 /// Taking the modulus of each term first costs `N` extra square roots and buys
850 /// the same overflow safety the real `hypot_n` has.
851 #[inline(always)]
852 fn hypot_n<P: Policy, const N: usize>(values: [Self; N]) -> Self {
853 Self::real(<V as thermite::math::SpatialMathWithPolicy>::hypot_n_p::<P, N>(
854 moduli::<V, P, N>(values),
855 ))
856 }
857
858 /// `$1/\sqrt{\sum_i |z_i|^2}$`, as a real complex number. See [`hypot_n`](Self::hypot_n).
859 #[inline(always)]
860 fn inv_hypot_n<P: Policy, const N: usize>(values: [Self; N]) -> Self {
861 Self::real(<V as thermite::math::SpatialMathWithPolicy>::inv_hypot_n_p::<P, N>(
862 moduli::<V, P, N>(values),
863 ))
864 }
865
866 /// `|re| + |im|`, as a real complex number.
867 #[inline(always)]
868 fn l1_norm<P: Policy>(self) -> Self {
869 Self::real(self.norm_l1())
870 }
871
872 /// `$|z|^2 = z\bar{z}$`, as a real complex number.
873 #[inline(always)]
874 fn l2_norm_squared<P: Policy>(self) -> Self {
875 Self::real(self.norm_sqr())
876 }
877
878 /// The modulus `$|z|$`, as a real complex number.
879 ///
880 /// The default `sqrt(l2_norm_squared())` squares the range and overflows for
881 /// large components; `hypot` does not.
882 #[inline(always)]
883 fn l2_norm<P: Policy>(self) -> Self {
884 Self::real(self.norm_p::<P>())
885 }
886}
887
888// --- No SpecializedRealMath ---
889//
890// RealMath is the part of the math library that assumes an ordered field: atan2 (a
891// quadrant of the real plane), wrap_angle, to_degrees/to_radians, step, smoothstep
892// and its inverse, logaddexp, rescale. Their defaults are written over max, abs and
893// clamp as orderings. Over C they would return a plausible-looking result with no
894// meaning.
895//
896// Each public math trait blankets off its own specialized trait, and leaving this
897// one unimplemented costs nothing: CoreMath, TranscendentalMath and SpatialMath are
898// unaffected. The argument of a complex number is ComplexMath::arg, returning a real
899// value where atan2 would have to return a complex one.