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}