Skip to main content

oxiblas_core/scalar/
complex.rs

1//! Complex number constructors, utilities, and extension traits.
2
3use num_complex::{Complex32, Complex64};
4
5// =============================================================================
6// Type aliases for convenience
7// =============================================================================
8
9/// 32-bit complex type alias (same as `Complex32`).
10pub type C32 = Complex32;
11
12/// 64-bit complex type alias (same as `Complex64`).
13pub type C64 = Complex64;
14
15// =============================================================================
16// Complex number constructors and utilities
17// =============================================================================
18
19/// Creates a complex number from real and imaginary parts.
20///
21/// # Examples
22///
23/// ```
24/// use oxiblas_core::scalar::c64;
25/// let z = c64(3.0, 4.0);
26/// assert_eq!(z.re, 3.0);
27/// assert_eq!(z.im, 4.0);
28/// ```
29#[inline]
30pub const fn c64(re: f64, im: f64) -> C64 {
31    Complex64::new(re, im)
32}
33
34/// Creates a complex number from real and imaginary parts (f32).
35///
36/// # Examples
37///
38/// ```
39/// use oxiblas_core::scalar::c32;
40/// let z = c32(3.0, 4.0);
41/// assert_eq!(z.re, 3.0);
42/// assert_eq!(z.im, 4.0);
43/// ```
44#[inline]
45pub const fn c32(re: f32, im: f32) -> C32 {
46    Complex32::new(re, im)
47}
48
49/// The imaginary unit i (64-bit).
50pub const I64: C64 = Complex64::new(0.0, 1.0);
51
52/// The imaginary unit i (32-bit).
53pub const I32: C32 = Complex32::new(0.0, 1.0);
54
55/// Returns the imaginary unit as a 64-bit complex number.
56#[inline]
57pub const fn imag_unit() -> C64 {
58    I64
59}
60
61/// Returns the imaginary unit as a 32-bit complex number.
62#[inline]
63pub const fn imag_unit32() -> C32 {
64    I32
65}
66
67/// Creates a purely imaginary number (64-bit).
68///
69/// # Examples
70///
71/// ```
72/// use oxiblas_core::scalar::imag;
73/// let z = imag(2.0);
74/// assert_eq!(z.re, 0.0);
75/// assert_eq!(z.im, 2.0);
76/// ```
77#[inline]
78pub const fn imag(im: f64) -> C64 {
79    Complex64::new(0.0, im)
80}
81
82/// Creates a purely imaginary number (32-bit).
83#[inline]
84pub const fn imag32(im: f32) -> C32 {
85    Complex32::new(0.0, im)
86}
87
88/// Creates a real number as complex (64-bit).
89///
90/// # Examples
91///
92/// ```
93/// use oxiblas_core::scalar::real;
94/// let z = real(3.0);
95/// assert_eq!(z.re, 3.0);
96/// assert_eq!(z.im, 0.0);
97/// ```
98#[inline]
99pub const fn real(re: f64) -> C64 {
100    Complex64::new(re, 0.0)
101}
102
103/// Creates a real number as complex (32-bit).
104#[inline]
105pub const fn real32(re: f32) -> C32 {
106    Complex32::new(re, 0.0)
107}
108
109/// Creates a complex number from polar coordinates (64-bit).
110///
111/// # Arguments
112/// * `r` - The magnitude (radius)
113/// * `theta` - The angle in radians
114///
115/// # Examples
116///
117/// ```
118/// use oxiblas_core::scalar::from_polar;
119/// use std::f64::consts::PI;
120/// let z = from_polar(1.0, PI / 2.0);
121/// assert!((z.re - 0.0).abs() < 1e-10);
122/// assert!((z.im - 1.0).abs() < 1e-10);
123/// ```
124#[inline]
125pub fn from_polar(r: f64, theta: f64) -> C64 {
126    Complex64::from_polar(r, theta)
127}
128
129/// Creates a complex number from polar coordinates (32-bit).
130#[inline]
131pub fn from_polar32(r: f32, theta: f32) -> C32 {
132    Complex32::from_polar(r, theta)
133}
134
135/// Extension trait for more ergonomic complex number operations.
136pub trait ComplexExt: Sized {
137    /// The real component type.
138    type Real;
139
140    /// Returns true if this complex number is purely real (imaginary part is approximately 0).
141    #[allow(clippy::wrong_self_convention)] // Complex numbers are Copy, self by value is efficient
142    fn is_purely_real(self, tolerance: Self::Real) -> bool;
143
144    /// Returns true if this complex number is purely imaginary (real part is approximately 0).
145    #[allow(clippy::wrong_self_convention)] // Complex numbers are Copy, self by value is efficient
146    fn is_purely_imaginary(self, tolerance: Self::Real) -> bool;
147
148    /// Rotates the complex number by the given angle (in radians).
149    fn rotate(self, angle: Self::Real) -> Self;
150
151    /// Scales the magnitude while keeping the phase.
152    fn scale_magnitude(self, factor: Self::Real) -> Self;
153
154    /// Returns the complex number normalized to unit magnitude.
155    fn normalize(self) -> Self;
156
157    /// Reflects across the real axis (same as conjugate).
158    fn reflect_real(self) -> Self;
159
160    /// Reflects across the imaginary axis.
161    fn reflect_imag(self) -> Self;
162
163    /// Returns the distance to another complex number.
164    fn distance(self, other: Self) -> Self::Real;
165}
166
167impl ComplexExt for C64 {
168    type Real = f64;
169
170    #[inline]
171    fn is_purely_real(self, tolerance: f64) -> bool {
172        self.im.abs() <= tolerance
173    }
174
175    #[inline]
176    fn is_purely_imaginary(self, tolerance: f64) -> bool {
177        self.re.abs() <= tolerance
178    }
179
180    #[inline]
181    fn rotate(self, angle: f64) -> Self {
182        self * Complex64::from_polar(1.0, angle)
183    }
184
185    #[inline]
186    fn scale_magnitude(self, factor: f64) -> Self {
187        let (r, theta) = self.to_polar();
188        Complex64::from_polar(r * factor, theta)
189    }
190
191    #[inline]
192    fn normalize(self) -> Self {
193        let norm = self.norm();
194        if norm == 0.0 {
195            Complex64::new(0.0, 0.0)
196        } else {
197            self / norm
198        }
199    }
200
201    #[inline]
202    fn reflect_real(self) -> Self {
203        self.conj()
204    }
205
206    #[inline]
207    fn reflect_imag(self) -> Self {
208        Complex64::new(-self.re, self.im)
209    }
210
211    #[inline]
212    fn distance(self, other: Self) -> f64 {
213        (self - other).norm()
214    }
215}
216
217impl ComplexExt for C32 {
218    type Real = f32;
219
220    #[inline]
221    fn is_purely_real(self, tolerance: f32) -> bool {
222        self.im.abs() <= tolerance
223    }
224
225    #[inline]
226    fn is_purely_imaginary(self, tolerance: f32) -> bool {
227        self.re.abs() <= tolerance
228    }
229
230    #[inline]
231    fn rotate(self, angle: f32) -> Self {
232        self * Complex32::from_polar(1.0, angle)
233    }
234
235    #[inline]
236    fn scale_magnitude(self, factor: f32) -> Self {
237        let (r, theta) = self.to_polar();
238        Complex32::from_polar(r * factor, theta)
239    }
240
241    #[inline]
242    fn normalize(self) -> Self {
243        let norm = self.norm();
244        if norm == 0.0 {
245            Complex32::new(0.0, 0.0)
246        } else {
247            self / norm
248        }
249    }
250
251    #[inline]
252    fn reflect_real(self) -> Self {
253        self.conj()
254    }
255
256    #[inline]
257    fn reflect_imag(self) -> Self {
258        Complex32::new(-self.re, self.im)
259    }
260
261    #[inline]
262    fn distance(self, other: Self) -> f32 {
263        (self - other).norm()
264    }
265}
266
267/// Trait for converting real numbers to complex.
268pub trait ToComplex<C> {
269    /// Converts to complex with zero imaginary part.
270    fn to_complex(self) -> C;
271
272    /// Converts to complex with given imaginary part.
273    fn with_imag(self, im: Self) -> C;
274}
275
276impl ToComplex<C64> for f64 {
277    #[inline]
278    fn to_complex(self) -> C64 {
279        Complex64::new(self, 0.0)
280    }
281
282    #[inline]
283    fn with_imag(self, im: f64) -> C64 {
284        Complex64::new(self, im)
285    }
286}
287
288impl ToComplex<C32> for f32 {
289    #[inline]
290    fn to_complex(self) -> C32 {
291        Complex32::new(self, 0.0)
292    }
293
294    #[inline]
295    fn with_imag(self, im: f32) -> C32 {
296        Complex32::new(self, im)
297    }
298}