thermite_complex/math/specialized.rs
1// The `fn name[..][..](self: Self, ..)` shape is the macro DSL's, as in
2// `thermite::math`, which allows this lint at its module root for the same reason.
3#![allow(clippy::needless_arbitrary_self_type)]
4
5//! The complex math trait family.
6//!
7//! Thermite's math families ([`CoreMath`](thermite::math::CoreMath),
8//! [`TranscendentalMath`](thermite::math::TranscendentalMath),
9//! [`SpatialMath`](thermite::math::SpatialMath),
10//! [`RealMath`](thermite::math::RealMath)) are all `Self -> Self`, a real vector
11//! having nothing else to return. A complex number does: its modulus and argument
12//! are real, and its polar form is a pair of reals. Those operations get their own
13//! family here, built the way the core ones are:
14//!
15//! - [`ComplexVector`] carries the structure: the associated real type
16//! [`Real`](ComplexVector::Real), the component accessors, and the operations
17//! that take no [`Policy`] (`conj`, `norm_sqr`, `inv`, `norm_l1`).
18//! - [`SpecializedComplexMath`] carries the algorithms, mirroring
19//! [`thermite::math::specialized`].
20//! - [`ComplexMathWithPolicy`](crate::math::ComplexMathWithPolicy) and [`ComplexMath`](crate::math::ComplexMath) are generated from it by
21//! `decl_complex_math!` (a copy of core's `decl_math!`), giving each operation a
22//! `foo_p::<P>()` and a default-policy `foo()` form.
23//!
24//! So `z.norm_p::<Precision>()` behaves as `x.sin_p::<Precision>()` does, and
25//! generic code bounds on `V: ComplexMath` as it would on `V: TranscendentalMath`.
26
27use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign};
28
29use thermite::math::policy::Policy;
30use thermite::prelude::*;
31use thermite::vector::ops::{self, MulAddAssignExt, MulAddExt};
32
33use crate::vector::RealFloatVector;
34
35#[cfg(feature = "special")]
36pub use crate::math::special::SpecializedComplexSpecialMath;
37
38/// A vector of complex numbers over a real vector type.
39///
40/// The structural half of the complex math family: it names the underlying real
41/// vector ([`Real`](ComplexVector::Real)) and the operations that take no
42/// [`Policy`]. The policy-dependent ones (modulus, argument, polar form, ...) are
43/// in [`ComplexMath`](crate::math::ComplexMath).
44///
45/// # Mixed complex/real arithmetic
46///
47/// The supertraits promise the binary operators against [`Real`](Self::Real), so
48/// generic code can scale, offset and fuse by a real vector without widening it
49/// into a complex one:
50///
51/// ```
52/// use thermite::prelude::*;
53/// use thermite_complex::prelude::*;
54///
55/// // Horner evaluation of a real-coefficient polynomial at a complex point.
56/// fn horner<T: ComplexVector>(z: T, coeffs: &[T::Real]) -> T {
57/// let mut acc = T::real(coeffs[0]);
58///
59/// for &c in &coeffs[1..] {
60/// acc = acc * z + c; // Complex * Complex, then Complex + Real
61/// }
62///
63/// acc
64/// }
65///
66/// type V = Vector<f64>;
67///
68/// // z^2 + 3 at z = 1 + 2i is -3 + 4i + 3 = 4i
69/// let z = Complex::new(V::splat(1.0), V::splat(2.0));
70/// let r = horner(z, &[V::ONE, V::ZERO, V::splat(3.0)]);
71///
72/// assert_eq!((r.re.extract::<0>(), r.im.extract::<0>()), (0.0, 4.0));
73/// ```
74///
75/// `Mul`/`Div` by a real cost two real multiplies, versus the four multiplies and
76/// two adds of a complex multiply by `Complex::real(r)`, which the compiler cannot
77/// recover from the widened form.
78/// [`MulAddExt<Self::Real, Self>`](thermite::vector::ops::MulAddExt) is promised
79/// for the same reason, and is a true single-rounding FMA (one fused op per
80/// component) where the complex-by-complex FMA cannot be.
81pub trait ComplexVector:
82 FloatVector
83 + Add<Self::Real, Output = Self>
84 + Sub<Self::Real, Output = Self>
85 + Mul<Self::Real, Output = Self>
86 + Div<Self::Real, Output = Self>
87 + Rem<Self::Real, Output = Self>
88 + AddAssign<Self::Real>
89 + SubAssign<Self::Real>
90 + MulAssign<Self::Real>
91 + DivAssign<Self::Real>
92 + RemAssign<Self::Real>
93 + MulAddExt<Self::Real, Self, Output = Self>
94 + MulAddAssignExt<Self::Real, Self>
95 + ops::AddMasked<Self::Mask, Self::Real, Output = Self>
96 + ops::SubMasked<Self::Mask, Self::Real, Output = Self>
97 + ops::MulMasked<Self::Mask, Self::Real, Output = Self>
98 + ops::DivMasked<Self::Mask, Self::Real, Output = Self>
99 + ops::MulAddExtMasked<Self::Mask, Self::Real, Self, Output = Self>
100{
101 /// The real vector type of each component, in which a modulus or an argument
102 /// is measured.
103 ///
104 /// The core [`SpatialMath`] family has no such associated type, so its norms
105 /// must return `Self` and come back as real-valued *complex* numbers.
106 /// [`ComplexMath::norm`](crate::math::ComplexMath::norm) returns this instead.
107 ///
108 /// [`SpatialMath`]: thermite::math::SpatialMath
109 type Real: RealFloatVector;
110
111 /// The real part.
112 fn re(self) -> Self::Real;
113
114 /// The imaginary part.
115 fn im(self) -> Self::Real;
116
117 /// Builds a complex vector from its real and imaginary parts.
118 ///
119 /// Not spelled `new`, tempting as it is to match the inherent
120 /// [`Complex::new`](crate::Complex::new): [`GenericVector::new`] is already in
121 /// scope on every implementor and takes a lane array, so a second `new` is
122 /// ambiguous (E0034) in precisely the generic code this trait exists for.
123 /// [`real`](Self::real) has no such clash and does match its inherent twin.
124 fn from_parts(re: Self::Real, im: Self::Real) -> Self;
125
126 /// Non-temporal store of the whole block to `ptr`, in `Self`'s own memory layout (the
127 /// planar/SoA `[re | im]` layout for `Complex<V>`), bypassing the cache. This is for
128 /// **relocating blocks within a `[Self]` buffer** - e.g. an FFT transpose whose output is
129 /// too large to cache - NOT the AoS boundary (that is [`store`](thermite::prelude::GenericVector::store)
130 /// / [`store_streaming`](thermite::prelude::GenericVector::store_streaming), which interleave re/im).
131 ///
132 /// Weakly ordered: a non-temporal store is not guaranteed visible to a later load until an
133 /// `sfence`, so the caller **must fence before reading the result**. Use it only when the
134 /// destination clearly exceeds last-level cache (NT forfeits cache reuse, so it loses below
135 /// a few MB).
136 ///
137 /// The default is a plain store (correct everywhere, no NT benefit). `Complex<V>` overrides
138 /// it to stream each half with the real [`store_streaming`](thermite::prelude::GenericVector::store_streaming)
139 /// of its component vector (`_mm256_stream_ps` on AVX2; a plain store on backends without NT).
140 ///
141 /// # Safety
142 /// `ptr` must be valid for writes and aligned to `Self` (a `[Self]` slot satisfies this).
143 #[inline(always)]
144 unsafe fn store_streaming_block(self, ptr: *mut Self) {
145 unsafe { ptr.write(self) }
146 }
147
148 /// Builds a complex vector from a real part, with zero imaginary part.
149 #[inline(always)]
150 fn real(re: Self::Real) -> Self {
151 Self::from_parts(re, <Self::Real as NumericVector>::ZERO)
152 }
153
154 /// The complex conjugate `re - im*i`.
155 fn conj(self) -> Self;
156
157 /// The squared modulus `$|z|^2 = re^2 + im^2$`.
158 ///
159 /// Cheaper than [`norm`](crate::math::ComplexMath::norm) (no square root), but it squares
160 /// the range, so it overflows or underflows near the limits of the format.
161 fn norm_sqr(self) -> Self::Real;
162
163 /// The L1 ("Manhattan") norm `|re| + |im|`, a real value.
164 fn norm_l1(self) -> Self::Real;
165
166 /// The multiplicative inverse `$1/z = \bar{z}/|z|^2$`.
167 ///
168 /// Inherits the range limits of [`norm_sqr`](ComplexVector::norm_sqr); the
169 /// scaled form is [`ComplexMath::finv`](crate::math::ComplexMath::finv).
170 fn inv(self) -> Self;
171}
172
173/// Element-parameterized implementations behind [`ComplexMath`](crate::math::ComplexMath).
174///
175/// The complex counterpart of [`thermite::math::specialized`]: implementing this
176/// for a complex vector type gives it [`ComplexMath`](crate::math::ComplexMath) and
177/// [`ComplexMathWithPolicy`](crate::math::ComplexMathWithPolicy), as implementing `SpecializedTranscendentalMath`
178/// gives it `TranscendentalMath`.
179///
180/// Bound on [`ComplexMath`](crate::math::ComplexMath); this trait is for implementors.
181pub trait SpecializedComplexMath<E>: ComplexVector<Element = E> {
182 /// The modulus (magnitude) `|z|`.
183 fn norm<P: Policy>(self) -> Self::Real;
184
185 /// The principal argument `arg(z)`, in `(-pi, pi]`.
186 fn arg<P: Policy>(self) -> Self::Real;
187
188 /// Polar form `(r, theta)`, such that `self == r * exp(i*theta)`.
189 #[inline(always)]
190 fn to_polar<P: Policy>(self) -> (Self::Real, Self::Real) {
191 (self.norm::<P>(), self.arg::<P>())
192 }
193
194 /// Builds a complex number from a polar representation `r * exp(i*theta)`.
195 fn from_polar<P: Policy>(r: Self::Real, theta: Self::Real) -> Self;
196
197 /// Raises `self` to a *real* power.
198 fn powfr<P: Policy>(self, e: Self::Real) -> Self;
199
200 /// Raises a *real* base to the complex power `self`.
201 fn expf<P: Policy>(self, base: Self::Real) -> Self;
202
203 /// The logarithm of `self` in an arbitrary *real* base.
204 fn logr<P: Policy>(self, base: Self::Real) -> Self;
205
206 /// `1/self`, scaling by the modulus and not its square.
207 ///
208 /// Survives the magnitudes where [`inv`](ComplexVector::inv) would have
209 /// `norm_sqr()` overflow to infinity or underflow to zero.
210 fn finv<P: Policy>(self) -> Self;
211
212 /// `self/rhs`, scaling by the modulus and not its square.
213 ///
214 /// Survives the magnitudes where `/` would have `rhs.norm_sqr()` overflow to
215 /// infinity or underflow to zero.
216 #[inline(always)]
217 fn fdiv<P: Policy>(self, rhs: Self) -> Self {
218 self * rhs.finv::<P>()
219 }
220}
221