Skip to main content

numr/ops/traits/
complex.rs

1//! Complex number operations trait.
2
3use crate::error::{Error, Result};
4use crate::runtime::Runtime;
5use crate::tensor::Tensor;
6
7/// Complex number operations
8pub trait ComplexOps<R: Runtime> {
9    /// Complex conjugate: conj(a + bi) = a - bi
10    ///
11    /// Returns the complex conjugate of the input tensor.
12    /// For real tensors, returns the input unchanged.
13    ///
14    /// # Arguments
15    ///
16    /// * `a` - Input tensor (Complex64, Complex128, or real types)
17    ///
18    /// # Returns
19    ///
20    /// * Complex types: Tensor with same shape and dtype, imaginary part negated
21    /// * Real types: Returns input tensor unchanged (real numbers equal their conjugate)
22    ///
23    /// # Supported Types
24    ///
25    /// * Complex64: All backends (CPU, CUDA, WebGPU)
26    /// * Complex128: CPU and CUDA only (WebGPU does not support F64)
27    /// * Real types: All backends (identity operation)
28    ///
29    /// # Examples
30    ///
31    /// ```
32    /// # use numr::prelude::*;
33    /// # let device = CpuDevice::new();
34    /// # let client = CpuRuntime::default_client(&device);
35    /// use numr::ops::ComplexOps;
36    /// use numr::dtype::Complex64;
37    ///
38    /// let z = Tensor::<CpuRuntime>::from_slice(
39    ///     &[Complex64::new(1.0, 2.0), Complex64::new(3.0, -4.0)],
40    ///     &[2],
41    ///     &device
42    /// );
43    /// let conj_z = client.conj(&z)?;
44    /// // Result: [1.0 - 2.0i, 3.0 + 4.0i]
45    /// # Ok::<(), numr::error::Error>(())
46    /// ```
47    fn conj(&self, a: &Tensor<R>) -> Result<Tensor<R>> {
48        let _ = a;
49        Err(Error::NotImplemented {
50            feature: "ComplexOps::conj",
51        })
52    }
53
54    /// Extract real part of complex tensor: real(a + bi) = a
55    ///
56    /// Extracts the real component from a complex tensor.
57    /// For real tensors, returns a copy of the input.
58    ///
59    /// # Arguments
60    ///
61    /// * `a` - Input tensor
62    ///
63    /// # Returns
64    ///
65    /// * Complex64 input → F32 tensor with same shape
66    /// * Complex128 input → F64 tensor with same shape
67    /// * Real input → Copy of input tensor
68    ///
69    /// # Supported Types
70    ///
71    /// * Complex64: All backends (CPU, CUDA, WebGPU)
72    /// * Complex128: CPU and CUDA only (WebGPU does not support F64)
73    /// * Real types: All backends
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// # use numr::prelude::*;
79    /// # let device = CpuDevice::new();
80    /// # let client = CpuRuntime::default_client(&device);
81    /// use numr::ops::ComplexOps;
82    /// use numr::dtype::Complex64;
83    ///
84    /// let z = Tensor::<CpuRuntime>::from_slice(
85    ///     &[Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)],
86    ///     &[2],
87    ///     &device
88    /// );
89    /// let re = client.real(&z)?;  // F32 tensor: [1.0, 3.0]
90    /// # Ok::<(), numr::error::Error>(())
91    /// ```
92    fn real(&self, a: &Tensor<R>) -> Result<Tensor<R>> {
93        let _ = a;
94        Err(Error::NotImplemented {
95            feature: "ComplexOps::real",
96        })
97    }
98
99    /// Extract imaginary part of complex tensor: imag(a + bi) = b
100    ///
101    /// Extracts the imaginary component from a complex tensor.
102    /// For real tensors, returns a zero tensor with the same shape.
103    ///
104    /// # Arguments
105    ///
106    /// * `a` - Input tensor
107    ///
108    /// # Returns
109    ///
110    /// * Complex64 input → F32 tensor with same shape
111    /// * Complex128 input → F64 tensor with same shape
112    /// * Real input → Zero tensor with same shape and dtype
113    ///
114    /// # Supported Types
115    ///
116    /// * Complex64: All backends (CPU, CUDA, WebGPU)
117    /// * Complex128: CPU and CUDA only (WebGPU does not support F64)
118    /// * Real types: All backends
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// # use numr::prelude::*;
124    /// # let device = CpuDevice::new();
125    /// # let client = CpuRuntime::default_client(&device);
126    /// use numr::ops::ComplexOps;
127    /// use numr::dtype::Complex64;
128    ///
129    /// let z = Tensor::<CpuRuntime>::from_slice(
130    ///     &[Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)],
131    ///     &[2],
132    ///     &device
133    /// );
134    /// let im = client.imag(&z)?;  // F32 tensor: [2.0, 4.0]
135    /// # Ok::<(), numr::error::Error>(())
136    /// ```
137    fn imag(&self, a: &Tensor<R>) -> Result<Tensor<R>> {
138        let _ = a;
139        Err(Error::NotImplemented {
140            feature: "ComplexOps::imag",
141        })
142    }
143
144    /// Compute phase angle of complex tensor: angle(a + bi) = atan2(b, a)
145    ///
146    /// Returns the phase angle (argument) of complex numbers in radians.
147    /// The result is in the range [-π, π].
148    ///
149    /// # Arguments
150    ///
151    /// * `a` - Input tensor
152    ///
153    /// # Returns
154    ///
155    /// * Complex64 input → F32 tensor with angles in radians
156    /// * Complex128 input → F64 tensor with angles in radians
157    /// * Real input → Zero tensor (real numbers have phase angle 0 for positive, π for negative)
158    ///
159    /// # Supported Types
160    ///
161    /// * Complex64: All backends (CPU, CUDA, WebGPU)
162    /// * Complex128: CPU and CUDA only (WebGPU does not support F64)
163    /// * Real types: All backends
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// # use numr::prelude::*;
169    /// # let device = CpuDevice::new();
170    /// # let client = CpuRuntime::default_client(&device);
171    /// use numr::ops::ComplexOps;
172    /// use numr::dtype::Complex64;
173    ///
174    /// let z = Tensor::<CpuRuntime>::from_slice(
175    ///     &[Complex64::new(1.0, 1.0), Complex64::new(-1.0, 0.0)],
176    ///     &[2],
177    ///     &device
178    /// );
179    /// let angles = client.angle(&z)?;  // F32 tensor: [π/4, π]
180    /// # Ok::<(), numr::error::Error>(())
181    /// ```
182    ///
183    /// # Mathematical Notes
184    ///
185    /// For complex z = a + bi, returns atan2(b, a) in radians [-π, π].
186    /// For real x, returns 0 if x ≥ 0, π if x < 0.
187    /// To compute magnitude, use abs(z) = sqrt(re² + im²) separately.
188    fn angle(&self, a: &Tensor<R>) -> Result<Tensor<R>> {
189        let _ = a;
190        Err(Error::NotImplemented {
191            feature: "ComplexOps::angle",
192        })
193    }
194
195    /// Construct complex tensor from separate real and imaginary part tensors.
196    ///
197    /// Creates a complex tensor where each element is `real[i] + imag[i]*i`.
198    ///
199    /// # Arguments
200    ///
201    /// * `real` - Tensor containing real parts (F32 or F64)
202    /// * `imag` - Tensor containing imaginary parts (must match `real` dtype and shape)
203    ///
204    /// # Returns
205    ///
206    /// * F32 inputs → Complex64 tensor with same shape
207    /// * F64 inputs → Complex128 tensor with same shape
208    ///
209    /// # Errors
210    ///
211    /// * `ShapeMismatch` - if `real` and `imag` have different shapes
212    /// * `DTypeMismatch` - if `real` and `imag` have different dtypes
213    /// * `UnsupportedDType` - if input dtype is not F32 or F64
214    ///
215    /// # Supported Types
216    ///
217    /// * F32 → Complex64: All backends (CPU, CUDA, WebGPU)
218    /// * F64 → Complex128: CPU and CUDA only (WebGPU does not support F64)
219    ///
220    /// # Examples
221    ///
222    /// ```
223    /// # use numr::prelude::*;
224    /// # let device = CpuDevice::new();
225    /// # let client = CpuRuntime::default_client(&device);
226    /// use numr::ops::ComplexOps;
227    ///
228    /// let real = Tensor::<CpuRuntime>::from_slice(&[1.0f32, 2.0, 3.0], &[3], &device);
229    /// let imag = Tensor::<CpuRuntime>::from_slice(&[4.0f32, 5.0, 6.0], &[3], &device);
230    /// let complex = client.make_complex(&real, &imag)?;
231    /// // Result: [1.0+4.0i, 2.0+5.0i, 3.0+6.0i]
232    /// # Ok::<(), numr::error::Error>(())
233    /// ```
234    fn make_complex(&self, real: &Tensor<R>, imag: &Tensor<R>) -> Result<Tensor<R>> {
235        let _ = (real, imag);
236        Err(Error::NotImplemented {
237            feature: "ComplexOps::make_complex",
238        })
239    }
240
241    /// Multiply complex tensor by real tensor element-wise.
242    ///
243    /// Computes (a + bi) * r = ar + br*i for each element.
244    ///
245    /// # Arguments
246    ///
247    /// * `complex` - Complex tensor (Complex64 or Complex128)
248    /// * `real` - Real tensor (F32 for Complex64, F64 for Complex128)
249    ///
250    /// # Returns
251    ///
252    /// Complex tensor with same dtype and shape as input complex tensor.
253    ///
254    /// # Errors
255    ///
256    /// * `ShapeMismatch` - if shapes don't match (no broadcasting)
257    /// * `DTypeMismatch` - if real dtype doesn't match complex component dtype
258    /// * `UnsupportedDType` - if complex is not Complex64/Complex128
259    ///
260    /// # Supported Types
261    ///
262    /// * Complex64 × F32: All backends (CPU, CUDA, WebGPU)
263    /// * Complex128 × F64: CPU and CUDA only (WebGPU does not support F64)
264    ///
265    /// # Examples
266    ///
267    /// ```
268    /// # use numr::prelude::*;
269    /// # let device = CpuDevice::new();
270    /// # let client = CpuRuntime::default_client(&device);
271    /// use numr::ops::ComplexOps;
272    /// use numr::dtype::Complex64;
273    ///
274    /// let complex = Tensor::<CpuRuntime>::from_slice(
275    ///     &[Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)],
276    ///     &[2],
277    ///     &device
278    /// );
279    /// let scale = Tensor::<CpuRuntime>::from_slice(&[2.0f32, 0.5], &[2], &device);
280    /// let result = client.complex_mul_real(&complex, &scale)?;
281    /// // Result: [2.0+4.0i, 1.5+2.0i]
282    /// # Ok::<(), numr::error::Error>(())
283    /// ```
284    fn complex_mul_real(&self, complex: &Tensor<R>, real: &Tensor<R>) -> Result<Tensor<R>> {
285        let _ = (complex, real);
286        Err(Error::NotImplemented {
287            feature: "ComplexOps::complex_mul_real",
288        })
289    }
290
291    /// Divide complex tensor by real tensor element-wise.
292    ///
293    /// Computes (a + bi) / r = (a/r) + (b/r)*i for each element.
294    ///
295    /// # Arguments
296    ///
297    /// * `complex` - Complex tensor (Complex64 or Complex128)
298    /// * `real` - Real tensor (F32 for Complex64, F64 for Complex128)
299    ///
300    /// # Returns
301    ///
302    /// Complex tensor with same dtype and shape as input complex tensor.
303    ///
304    /// # Errors
305    ///
306    /// * `ShapeMismatch` - if shapes don't match (no broadcasting)
307    /// * `DTypeMismatch` - if real dtype doesn't match complex component dtype
308    /// * `UnsupportedDType` - if complex is not Complex64/Complex128
309    ///
310    /// # Supported Types
311    ///
312    /// * Complex64 / F32: All backends (CPU, CUDA, WebGPU)
313    /// * Complex128 / F64: CPU and CUDA only (WebGPU does not support F64)
314    ///
315    /// # Note
316    ///
317    /// Division by zero will result in NaN/Inf values, following IEEE 754 semantics.
318    ///
319    /// # Examples
320    ///
321    /// ```
322    /// # use numr::prelude::*;
323    /// # let device = CpuDevice::new();
324    /// # let client = CpuRuntime::default_client(&device);
325    /// use numr::ops::ComplexOps;
326    /// use numr::dtype::Complex64;
327    ///
328    /// let complex = Tensor::<CpuRuntime>::from_slice(
329    ///     &[Complex64::new(4.0, 6.0), Complex64::new(2.0, 4.0)],
330    ///     &[2],
331    ///     &device
332    /// );
333    /// let divisor = Tensor::<CpuRuntime>::from_slice(&[2.0f32, 2.0], &[2], &device);
334    /// let result = client.complex_div_real(&complex, &divisor)?;
335    /// // Result: [2.0+3.0i, 1.0+2.0i]
336    /// # Ok::<(), numr::error::Error>(())
337    /// ```
338    fn complex_div_real(&self, complex: &Tensor<R>, real: &Tensor<R>) -> Result<Tensor<R>> {
339        let _ = (complex, real);
340        Err(Error::NotImplemented {
341            feature: "ComplexOps::complex_div_real",
342        })
343    }
344
345    /// Multiply real tensor by complex tensor element-wise.
346    ///
347    /// Computes r * (a + bi) = ra + rb*i for each element.
348    /// This is equivalent to `complex_mul_real` (multiplication is commutative).
349    ///
350    /// # Arguments
351    ///
352    /// * `real` - Real tensor (F32 for Complex64, F64 for Complex128)
353    /// * `complex` - Complex tensor (Complex64 or Complex128)
354    ///
355    /// # Returns
356    ///
357    /// Complex tensor with same dtype and shape as input complex tensor.
358    ///
359    /// # Examples
360    ///
361    /// ```
362    /// # use numr::prelude::*;
363    /// # let device = CpuDevice::new();
364    /// # let client = CpuRuntime::default_client(&device);
365    /// use numr::ops::ComplexOps;
366    /// use numr::dtype::Complex64;
367    ///
368    /// let scale = Tensor::<CpuRuntime>::from_slice(&[2.0f32, 0.5], &[2], &device);
369    /// let complex = Tensor::<CpuRuntime>::from_slice(
370    ///     &[Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)],
371    ///     &[2],
372    ///     &device
373    /// );
374    /// let result = client.real_mul_complex(&scale, &complex)?;
375    /// // Result: [2.0+4.0i, 1.5+2.0i]
376    /// # Ok::<(), numr::error::Error>(())
377    /// ```
378    fn real_mul_complex(&self, real: &Tensor<R>, complex: &Tensor<R>) -> Result<Tensor<R>> {
379        // Multiplication is commutative
380        self.complex_mul_real(complex, real)
381    }
382}