Skip to main content

thermite_complex/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign};
6
7use thermite::vector::ops::{MulAddAssignExt, MulAddExt, Square};
8
9pub mod math;
10mod vector;
11
12pub use crate::vector::RealFloatVector;
13
14/// Everything needed to work with [`Complex`], in one glob.
15///
16/// ```
17/// use thermite::prelude::*;
18/// use thermite_complex::prelude::*;
19/// ```
20///
21/// [`Complex`] itself lives at the crate root, being the type this crate is about;
22/// the traits are spread across [`math`] and its submodules, and that layout is an
23/// implementation detail. Import from here.
24pub mod prelude {
25    pub use crate::math::specialized::{ComplexVector, SpecializedComplexMath};
26    pub use crate::math::{ComplexMath, ComplexMathWithPolicy};
27    pub use crate::RealFloatVector;
28    pub use crate::{Complex, RealValue};
29
30    #[cfg(feature = "special")]
31    pub use crate::math::special::{ComplexSpecialMath, ComplexSpecialMathWithPolicy, SpecializedComplexSpecialMath};
32}
33
34/// A value usable as the real/imaginary storage of a [`Complex`].
35///
36/// Implemented for `f32`/`f64` and for every Thermite float
37/// [`Vector`](thermite::prelude::Vector). The arithmetic below is written once
38/// against it and serves both the element level (`Complex<f32>`) and the vector
39/// level (`Complex<Vector<R>>`). The math library wants the stronger
40/// [`RealFloatVector`].
41pub trait RealValue:
42    Copy
43    + Add<Output = Self>
44    + Sub<Output = Self>
45    + Mul<Output = Self>
46    + Div<Output = Self>
47    + Neg<Output = Self>
48    + MulAddExt<Self, Self, Output = Self>
49{
50    /// The additive identity in this value type.
51    const VAL_ZERO: Self;
52    /// The multiplicative identity in this value type.
53    const VAL_ONE: Self;
54
55    /// Truncate towards zero. Used to give [`Complex`] a (componentwise) `Rem`.
56    fn val_trunc(self) -> Self;
57}
58
59impl RealValue for f32 {
60    const VAL_ZERO: Self = 0.0;
61    const VAL_ONE: Self = 1.0;
62
63    #[inline(always)]
64    fn val_trunc(self) -> Self {
65        thermite::register::FloatElement::trunc(self)
66    }
67}
68
69impl RealValue for f64 {
70    const VAL_ZERO: Self = 0.0;
71    const VAL_ONE: Self = 1.0;
72
73    #[inline(always)]
74    fn val_trunc(self) -> Self {
75        thermite::register::FloatElement::trunc(self)
76    }
77}
78
79impl<R: thermite::register::FloatRegister> RealValue for thermite::prelude::Vector<R> {
80    const VAL_ZERO: Self = <Self as thermite::prelude::NumericVector>::ZERO;
81    const VAL_ONE: Self = <Self as thermite::prelude::NumericVector>::ONE;
82
83    #[inline(always)]
84    fn val_trunc(self) -> Self {
85        thermite::prelude::FloatVector::trunc(self)
86    }
87}
88
89/// `Complex<Dual<V, N>>`: a complex number whose parts each carry `N` derivative
90/// components, giving forward-mode AD through the complex functions.
91///
92/// Everything here is written against [`RealValue`], which [`Dual`] satisfies,
93/// so this impl is all it takes. Seeded along the real axis (`dz = 1`), the dual
94/// parts of `f(z)` are `f'(z)` for holomorphic `f`.
95///
96/// [`Dual`]: thermite_dual::Dual
97#[cfg(feature = "dual")]
98impl<V: thermite_dual::DualValue, const N: usize> RealValue for thermite_dual::Dual<V, N> {
99    const VAL_ZERO: Self = Self::ZERO;
100    const VAL_ONE: Self = Self::ONE;
101
102    #[inline(always)]
103    fn val_trunc(self) -> Self {
104        thermite_dual::DualValue::val_trunc(self)
105    }
106}
107
108/// `Complex<Compensated<V>>`: a complex number whose parts are each a double-double,
109/// roughly doubling the mantissa of the complex arithmetic and of every kernel built
110/// on it.
111///
112/// [`Compensated`] carries no error term of its own through a complex multiply; the
113/// compensation is per component, and the cross terms of `(a + bi)(c + di)` are
114/// summed in double-double, which is where the precision comes from.
115///
116/// [`Compensated`]: thermite_compensated::Compensated
117#[cfg(feature = "compensated")]
118impl<V: thermite_compensated::ScalarValue> RealValue for thermite_compensated::Compensated<V> {
119    const VAL_ZERO: Self = thermite_compensated::Compensated {
120        value: V::SCALAR_ZERO,
121        error: V::SCALAR_ZERO,
122    };
123
124    const VAL_ONE: Self = thermite_compensated::Compensated {
125        value: V::SCALAR_ONE,
126        error: V::SCALAR_ZERO,
127    };
128
129    // Truncating the folded value+error, as `Compensated`'s own `Rem` and
130    // `FloatElement::trunc` do.
131    #[inline(always)]
132    fn val_trunc(self) -> Self {
133        thermite_compensated::Compensated::new(self.value().scalar_trunc())
134    }
135}
136
137/// A complex number `re + im*i`.
138///
139/// The derived [`PartialOrd`] is lexicographic on `(re, im)`, matching
140/// [`PartialOrdVector`](thermite::prelude::PartialOrdVector). The [crate docs](crate)
141/// cover the rest of the ordering/sign/rounding semantics.
142#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
143#[repr(C)]
144pub struct Complex<V> {
145    /// The real part.
146    pub re: V,
147    /// The imaginary part.
148    pub im: V,
149}
150
151impl<V: RealValue> thermite::const_default::ConstDefault for Complex<V> {
152    const DEFAULT: Self = Self::ZERO;
153}
154
155impl<V: RealValue> Complex<V> {
156    /// Zero: `0 + 0i`.
157    pub const ZERO: Self = Self::new(V::VAL_ZERO, V::VAL_ZERO);
158    /// One: `1 + 0i`.
159    pub const ONE: Self = Self::new(V::VAL_ONE, V::VAL_ZERO);
160    /// The imaginary unit: `0 + 1i`.
161    pub const I: Self = Self::new(V::VAL_ZERO, V::VAL_ONE);
162
163    /// Creates a complex number with the given real and imaginary parts.
164    #[inline(always)]
165    pub const fn new(re: V, im: V) -> Self {
166        Self { re, im }
167    }
168
169    /// Creates a complex number with the given real part and zero imaginary part.
170    #[inline(always)]
171    pub const fn real(re: V) -> Self {
172        Self::new(re, V::VAL_ZERO)
173    }
174
175    /// Creates a complex number with zero real part and the given imaginary part.
176    #[inline(always)]
177    pub const fn imag(im: V) -> Self {
178        Self::new(V::VAL_ZERO, im)
179    }
180
181    /// The complex conjugate: `re - im*i`.
182    #[inline(always)]
183    pub fn conj(self) -> Self {
184        Self::new(self.re, -self.im)
185    }
186
187    /// The squared modulus `$|z|^2 = re^2 + im^2$`.
188    ///
189    /// Cheaper than the modulus (no square root), but it squares the range, so it
190    /// overflows or underflows near the limits of the format.
191    #[inline(always)]
192    pub fn norm_sqr(self) -> V {
193        self.re.mul_adde(self.re, self.im * self.im)
194    }
195
196    /// The multiplicative inverse `$1/z = \bar{z}/|z|^2$`.
197    ///
198    /// Inherits the range limits of [`norm_sqr`](Complex::norm_sqr); the scaled
199    /// form is [`finv`](crate::math::ComplexMath::finv).
200    #[inline(always)]
201    pub fn inv(self) -> Self {
202        self.conj() / self.norm_sqr()
203    }
204}
205
206// --- Arithmetic: Complex op Complex ---
207
208impl<V: RealValue> Neg for Complex<V> {
209    type Output = Self;
210
211    #[inline(always)]
212    fn neg(self) -> Self {
213        Self::new(-self.re, -self.im)
214    }
215}
216
217impl<V: RealValue> Add for Complex<V> {
218    type Output = Self;
219
220    #[inline(always)]
221    fn add(self, rhs: Self) -> Self {
222        Self::new(self.re + rhs.re, self.im + rhs.im)
223    }
224}
225
226impl<V: RealValue> Sub for Complex<V> {
227    type Output = Self;
228
229    #[inline(always)]
230    fn sub(self, rhs: Self) -> Self {
231        Self::new(self.re - rhs.re, self.im - rhs.im)
232    }
233}
234
235impl<V: RealValue> Mul for Complex<V> {
236    type Output = Self;
237
238    // (a + bi)(c + di) = (ac - bd) + (ad + bc)i
239    #[inline(always)]
240    fn mul(self, rhs: Self) -> Self {
241        Self::new(
242            self.re.mul_sube(rhs.re, self.im * rhs.im),
243            self.re.mul_adde(rhs.im, self.im * rhs.re),
244        )
245    }
246}
247
248impl<V: RealValue> Div for Complex<V> {
249    type Output = Self;
250
251    // (a + bi)/(c + di) = ((ac + bd) + (bc - ad)i) / (c^2 + d^2), taking one
252    // reciprocal of the real denominator, so there is only one division.
253    #[allow(clippy::suspicious_arithmetic_impl)]
254    #[inline(always)]
255    fn div(self, rhs: Self) -> Self {
256        let denom = rhs.re.mul_adde(rhs.re, rhs.im * rhs.im);
257        let inv = V::VAL_ONE / denom;
258
259        Self::new(
260            self.re.mul_adde(rhs.re, self.im * rhs.im) * inv,
261            self.im.mul_sube(rhs.re, self.re * rhs.im) * inv,
262        )
263    }
264}
265
266// z % w = z - trunc(z/w)*w, truncating the quotient componentwise. Required by
267// num_traits::NumOps for NumericVector; not a complex-analytic operation.
268#[allow(clippy::suspicious_arithmetic_impl)]
269impl<V: RealValue> Rem for Complex<V> {
270    type Output = Self;
271
272    #[inline(always)]
273    fn rem(self, rhs: Self) -> Self {
274        let q = self / rhs;
275        let k = Complex::new(q.re.val_trunc(), q.im.val_trunc());
276
277        k.nmul_adde(rhs, self) // self - k*rhs
278    }
279}
280
281// --- Arithmetic: Complex op real value ---
282
283impl<V: RealValue> Add<V> for Complex<V> {
284    type Output = Self;
285
286    #[inline(always)]
287    fn add(self, rhs: V) -> Self {
288        Self::new(self.re + rhs, self.im)
289    }
290}
291
292impl<V: RealValue> Sub<V> for Complex<V> {
293    type Output = Self;
294
295    #[inline(always)]
296    fn sub(self, rhs: V) -> Self {
297        Self::new(self.re - rhs, self.im)
298    }
299}
300
301impl<V: RealValue> Mul<V> for Complex<V> {
302    type Output = Self;
303
304    #[inline(always)]
305    fn mul(self, rhs: V) -> Self {
306        Self::new(self.re * rhs, self.im * rhs)
307    }
308}
309
310impl<V: RealValue> Div<V> for Complex<V> {
311    type Output = Self;
312
313    // single reciprocal, then multiply through
314    #[allow(clippy::suspicious_arithmetic_impl)]
315    #[inline(always)]
316    fn div(self, rhs: V) -> Self {
317        // single reciprocal, then multiply through
318        let inv = V::VAL_ONE / rhs;
319
320        Self::new(self.re * inv, self.im * inv)
321    }
322}
323
324#[allow(clippy::suspicious_arithmetic_impl)]
325impl<V: RealValue> Rem<V> for Complex<V> {
326    type Output = Self;
327
328    #[inline(always)]
329    fn rem(self, rhs: V) -> Self {
330        let q = self / rhs;
331        let k = Complex::new(q.re.val_trunc(), q.im.val_trunc());
332
333        k.nmul_adde(Complex::real(rhs), self)
334    }
335}
336
337// --- Fused multiply-add by a real value ---
338//
339// z*r + w with real r is a single fused op per component (re*r + w.re,
340// im*r + w.im). Unlike the complex-by-complex form it is therefore a true
341// single-rounding FMA whenever the inner type has one.
342macro_rules! complex_real_fma {
343    ($($name:ident),* $(,)?) => {
344        $(
345            #[inline(always)]
346            fn $name(self, a: V, b: Self) -> Self {
347                Self::new(self.re.$name(a, b.re), self.im.$name(a, b.im))
348            }
349        )*
350    };
351}
352
353#[rustfmt::skip]
354impl<V: RealValue> MulAddExt<V, Self> for Complex<V> {
355    type Output = Self;
356
357    const HAS_TRUE_FMA: bool = <V as MulAddExt<V, V>>::HAS_TRUE_FMA;
358
359    complex_real_fma!(mul_add, mul_sub, nmul_add, nmul_sub, mul_adde, mul_sube, nmul_adde, nmul_sube);
360}
361
362// --- Assignment variants ---
363
364macro_rules! impl_assign {
365    ($($assign_trait:ident::$assign_method:ident => $op_trait:ident::$op_method:ident),* $(,)?) => {$(
366        impl<V: RealValue, T> $assign_trait<T> for Complex<V>
367        where
368            Self: $op_trait<T, Output = Self>,
369        {
370            #[inline(always)]
371            fn $assign_method(&mut self, rhs: T) {
372                *self = $op_trait::$op_method(*self, rhs);
373            }
374        }
375    )*};
376}
377
378#[rustfmt::skip]
379impl_assign! {
380    AddAssign::add_assign => Add::add,
381    SubAssign::sub_assign => Sub::sub,
382    MulAssign::mul_assign => Mul::mul,
383    DivAssign::div_assign => Div::div,
384    RemAssign::rem_assign => Rem::rem,
385}
386
387// --- Fused multiply-add ---
388//
389// (a + bi)(c + di) + (e + fi) expands to
390//
391//   re = a*c - b*d + e  =  fnma(b, d, fma(a, c, e))
392//   im = a*d + b*c + f  =  fma(a, d, fma(b, c, f))
393//
394// i.e. two nested FMAs of the inner type per component. Composing the complex Mul
395// and Add instead would round the product first. Each component still rounds more
396// than once, so HAS_TRUE_FMA is false.
397
398// The eight methods are the (product sign, addend sign) pairs over the exact or
399// the estimating inner FMA. Both negations fold into the inner FMA's sign bits.
400macro_rules! complex_mul_add {
401    ($($name:ident => $neg_self:expr, $neg_addend:expr, $fma:ident, $nfma:ident);* $(;)?) => {
402        $(
403            #[inline(always)]
404            fn $name(self, a: Self, b: Self) -> Self {
405                let p = if $neg_self { -self } else { self };
406                let c = if $neg_addend { -b } else { b };
407
408                // re = p.re*a.re - p.im*a.im + c.re ; im = p.re*a.im + p.im*a.re + c.im
409                Self::new(
410                    p.im.$nfma(a.im, p.re.$fma(a.re, c.re)),
411                    p.re.$fma(a.im, p.im.$fma(a.re, c.im)),
412                )
413            }
414        )*
415    };
416}
417
418#[rustfmt::skip]
419impl<V: RealValue> MulAddExt<Self, Self> for Complex<V> {
420    type Output = Self;
421
422    // A complex "FMA" rounds each component several times whatever the inner FMA
423    // does. It is never a single-rounding operation.
424    const HAS_TRUE_FMA: bool = false;
425
426    complex_mul_add! {
427        mul_add   => false, false, mul_add,  nmul_add;
428        mul_sub   => false, true,  mul_add,  nmul_add;
429        nmul_add  => true,  false, mul_add,  nmul_add;
430        nmul_sub  => true,  true,  mul_add,  nmul_add;
431        mul_adde  => false, false, mul_adde, nmul_adde;
432        mul_sube  => false, true,  mul_adde, nmul_adde;
433        nmul_adde => true,  false, mul_adde, nmul_adde;
434        nmul_sube => true,  true,  mul_adde, nmul_adde;
435    }
436}
437
438#[rustfmt::skip]
439impl<V: RealValue, A, B> MulAddAssignExt<A, B> for Complex<V>
440where
441    Self: MulAddExt<A, B, Output = Self>,
442{
443    #[inline(always)] fn mul_add_assign(&mut self, a: A, b: B) { *self = self.mul_add(a, b); }
444    #[inline(always)] fn mul_sub_assign(&mut self, a: A, b: B) { *self = self.mul_sub(a, b); }
445    #[inline(always)] fn nmul_add_assign(&mut self, a: A, b: B) { *self = self.nmul_add(a, b); }
446    #[inline(always)] fn nmul_sub_assign(&mut self, a: A, b: B) { *self = self.nmul_sub(a, b); }
447    #[inline(always)] fn mul_adde_assign(&mut self, a: A, b: B) { *self = self.mul_adde(a, b); }
448    #[inline(always)] fn mul_sube_assign(&mut self, a: A, b: B) { *self = self.mul_sube(a, b); }
449    #[inline(always)] fn nmul_adde_assign(&mut self, a: A, b: B) { *self = self.nmul_adde(a, b); }
450    #[inline(always)] fn nmul_sube_assign(&mut self, a: A, b: B) { *self = self.nmul_sube(a, b); }
451}
452
453impl<V: RealValue> Square for Complex<V> {
454    type Output = Self;
455
456    // z^2 = (re^2 - im^2) + 2*re*im*i. The imaginary part is one add and one
457    // multiply, versus the FMA over two products a general self*self would take.
458    #[inline(always)]
459    fn square(self) -> Self {
460        Self::new(
461            self.re.mul_sube(self.re, self.im * self.im),
462            (self.re + self.re) * self.im,
463        )
464    }
465}
466
467// --- Iterator reductions ---
468
469impl<V: RealValue> core::iter::Sum for Complex<V> {
470    #[inline]
471    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
472        iter.fold(Self::ZERO, |a, b| a + b)
473    }
474}
475
476impl<V: RealValue> core::iter::Product for Complex<V> {
477    #[inline]
478    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
479        iter.fold(Self::ONE, |a, b| a * b)
480    }
481}
482
483// --- Float constants (real-valued) ---
484
485macro_rules! impl_float_consts {
486    ($($name:ident),* $(,)?) => {
487        impl<V: RealValue + thermite::math::FloatConsts> thermite::math::FloatConsts for Complex<V> {
488            $(const $name: Self = Self::real(<V as thermite::math::FloatConsts>::$name);)*
489        }
490    };
491}
492
493impl_float_consts!(
494    NEG_ZERO,
495    E,
496    EULER_GAMMA,
497    PI_SQUARED,
498    PI_CUBED,
499    PI_FOURTH,
500    FRAC_1_PI,
501    FRAC_1_SQRT_2,
502    FRAC_1_SQRT_3,
503    FRAC_2_PI,
504    FRAC_1_SQRT_PI,
505    FRAC_2_SQRT_PI,
506    FRAC_SQRT_PI_2,
507    FRAC_1_SQRT_TAU,
508    FRAC_PI_2,
509    FRAC_PI_3,
510    FRAC_PI_4,
511    FRAC_PI_6,
512    FRAC_PI_8,
513    FRAC_PI_180,
514    FRAC_180_PI,
515    LN_2,
516    LN_10,
517    LN_PI,
518    FRAC_LN_PI_2,
519    LOG2_10,
520    LOG2_E,
521    LOG10_2,
522    LOG10_E,
523    PI,
524    SQRT_2,
525    SQRT_3,
526    SQRT_E,
527    EPSILON,
528    SQRT_EPSILON,
529    FOURTH_ROOT_EPSILON,
530    TAU,
531    SQRT_FRAC_PI_2,
532    SQRT_TAU,
533    PHI,
534    FRAC_1_3,
535    FRAC_2_3,
536    FRAC_1_4,
537    FRAC_1_6,
538    FRAC_NEG_1_E
539);