Skip to main content

torsh_tensor/
complex_ops.rs

1//! Complex number operations for tensors
2//!
3//! This module provides specialized operations for complex-valued tensors including
4//! complex conjugation, real/imaginary part extraction, and complex-specific
5//! automatic differentiation support.
6//!
7//! # Features
8//!
9//! - **Complex conjugation**: Efficient complex conjugate operations
10//! - **Component extraction**: Real and imaginary part access
11//! - **Complex autograd**: Specialized gradient computation for complex numbers
12//! - **Complex arithmetic**: Element-wise operations preserving complex structure
13//! - **Magnitude and phase**: Polar representation support
14
15use scirs2_core::numeric::Float;
16use std::sync::Arc;
17use torsh_core::sync::RwLockExt;
18use torsh_core::{
19    dtype::{ComplexElement, TensorElement},
20    error::{Result, TorshError},
21};
22
23use crate::core_ops::{Operation, Tensor};
24
25impl<T: ComplexElement + Copy> Tensor<T> {
26    /// Complex conjugate for complex tensors
27    pub fn complex_conj(&self) -> Result<Self>
28    where
29        T: Copy,
30    {
31        let data = self.to_vec()?;
32        let conj_data: Vec<T> = data.iter().map(|&z| z.conj()).collect();
33        let mut result = Self::from_data(conj_data, self.shape().dims().to_vec(), self.device)?;
34        result.requires_grad = crate::should_record_grad(self.requires_grad);
35
36        // Set up operation tracking for autograd
37        if crate::should_record_grad(self.requires_grad) {
38            result.operation = Operation::Custom(
39                "complex_conj".to_string(),
40                vec![Arc::downgrade(&Arc::new(self.clone()))],
41            );
42        }
43
44        Ok(result)
45    }
46
47    /// Get real part of complex tensor
48    pub fn real(&self) -> Result<Tensor<T::Real>>
49    where
50        T::Real: TensorElement + Copy,
51    {
52        let data = self.to_vec()?;
53        let real_data: Vec<T::Real> = data.iter().map(|x| x.real()).collect();
54        Tensor::from_data(real_data, self.shape().dims().to_vec(), self.device)
55    }
56
57    /// Get imaginary part of complex tensor
58    pub fn imag(&self) -> Result<Tensor<T::Real>>
59    where
60        T::Real: TensorElement + Copy,
61    {
62        let data = self.to_vec()?;
63        let imag_data: Vec<T::Real> = data.iter().map(|x| x.imag()).collect();
64        Tensor::from_data(imag_data, self.shape().dims().to_vec(), self.device)
65    }
66
67    /// Get magnitude (absolute value) of complex tensor.
68    ///
69    /// This is also the `abs` that *real* tensors resolve to: `f32` and `f64`
70    /// implement [`ComplexElement`] with `Real = Self`, so `Tensor<f32>::abs`
71    /// is the ordinary `|x|`. Only that real case is differentiable —
72    /// `Tensor::record_abs_if_real` records [`crate::core_ops::UnaryKind::Abs`]
73    /// (sub-gradient `sign(x)`, `0` at the kink) when the input and output
74    /// element types coincide and declines otherwise, leaving genuinely
75    /// complex `abs` detached as before (its derivative is the Wirtinger
76    /// `z/|z|`, which needs a different `Operation`).
77    pub fn abs(&self) -> Result<Tensor<T::Real>>
78    where
79        T::Real: TensorElement + Copy + num_traits::Float,
80    {
81        let data = self.to_vec()?;
82        let abs_data: Vec<T::Real> = data.iter().map(|x| x.abs()).collect();
83        let result = Tensor::from_data(abs_data, self.shape().dims().to_vec(), self.device)?;
84        Ok(self.record_abs_if_real(result))
85    }
86
87    /// Get phase (argument) of complex tensor
88    pub fn angle(&self) -> Result<Tensor<T::Real>>
89    where
90        T::Real: TensorElement + Copy + num_traits::Float,
91    {
92        let data = self.to_vec()?;
93        let angle_data: Vec<T::Real> = data.iter().map(|x| x.arg()).collect();
94        Tensor::from_data(angle_data, self.shape().dims().to_vec(), self.device)
95    }
96
97    /// Create complex tensor from real and imaginary parts
98    pub fn complex(real: &Tensor<T::Real>, imag: &Tensor<T::Real>) -> Result<Self>
99    where
100        T::Real: TensorElement + Copy,
101    {
102        if real.shape() != imag.shape() {
103            return Err(TorshError::ShapeMismatch {
104                expected: real.shape().dims().to_vec(),
105                got: imag.shape().dims().to_vec(),
106            });
107        }
108
109        let real_data = real.to_vec()?;
110        let imag_data = imag.to_vec()?;
111
112        let complex_data: Vec<T> = real_data
113            .iter()
114            .zip(imag_data.iter())
115            .map(|(&r, &i)| T::new(r, i))
116            .collect();
117
118        Self::from_data(complex_data, real.shape().dims().to_vec(), real.device)
119    }
120
121    /// Create complex tensor from polar representation (magnitude and phase)
122    pub fn polar(magnitude: &Tensor<T::Real>, phase: &Tensor<T::Real>) -> Result<Self>
123    where
124        T::Real: TensorElement + Copy + num_traits::Float,
125    {
126        if magnitude.shape() != phase.shape() {
127            return Err(TorshError::ShapeMismatch {
128                expected: magnitude.shape().dims().to_vec(),
129                got: phase.shape().dims().to_vec(),
130            });
131        }
132
133        let mag_data = magnitude.to_vec()?;
134        let phase_data = phase.to_vec()?;
135
136        let complex_data: Vec<T> = mag_data
137            .iter()
138            .zip(phase_data.iter())
139            .map(|(&mag, &phase)| {
140                let real = mag * phase.cos();
141                let imag = mag * phase.sin();
142                T::new(real, imag)
143            })
144            .collect();
145
146        Self::from_data(
147            complex_data,
148            magnitude.shape().dims().to_vec(),
149            magnitude.device,
150        )
151    }
152
153    /// Backward pass for complex tensors (compute gradients)
154    ///
155    /// Complex autograd follows PyTorch's approach where gradients are computed
156    /// treating complex numbers as 2D vectors of real numbers.
157    pub fn backward_complex(&self) -> Result<()>
158    where
159        T: Copy
160            + Default
161            + std::ops::Add<Output = T>
162            + std::ops::Sub<Output = T>
163            + std::ops::Mul<Output = T>
164            + std::ops::Div<Output = T>,
165    {
166        if !self.requires_grad {
167            return Err(TorshError::AutogradError(
168                "Called backward on tensor that doesn't require grad".to_string(),
169            ));
170        }
171
172        if self.shape().numel() != 1 {
173            return Err(TorshError::AutogradError(
174                "Gradient can only be computed for scalar outputs".to_string(),
175            ));
176        }
177
178        // Create initial gradient of 1.0 + 0.0i for the output
179        let output_grad_data = vec![T::new(
180            <T::Real as TensorElement>::one(),
181            <T::Real as TensorElement>::zero(),
182        )];
183        let output_grad = Self::from_data(output_grad_data, vec![], self.device)?;
184
185        // Start backpropagation
186        self.backward_complex_impl(&output_grad)?;
187
188        Ok(())
189    }
190
191    /// Internal backward implementation for complex tensors
192    fn backward_complex_impl(&self, grad_output: &Self) -> Result<()>
193    where
194        T: Copy
195            + Default
196            + std::ops::Add<Output = T>
197            + std::ops::Sub<Output = T>
198            + std::ops::Mul<Output = T>
199            + std::ops::Div<Output = T>,
200    {
201        match &self.operation {
202            Operation::Leaf => {
203                // Accumulate gradient for leaf nodes
204                let mut grad_lock = self.grad.write_or_recover();
205                if let Some(existing_grad) = grad_lock.as_ref() {
206                    // Add gradients if they exist
207                    let new_grad = existing_grad.add_op(grad_output)?;
208                    *grad_lock = Some(new_grad);
209                } else {
210                    // Set gradient if it doesn't exist
211                    *grad_lock = Some(grad_output.clone());
212                }
213            }
214            Operation::Add { lhs, rhs } => {
215                // Gradient flows through both operands unchanged for complex addition
216                if lhs.requires_grad {
217                    lhs.backward_complex_impl(grad_output)?;
218                }
219                if rhs.requires_grad {
220                    rhs.backward_complex_impl(grad_output)?;
221                }
222            }
223            Operation::Mul { lhs, rhs } => {
224                // Complex multiplication rule: d/dz(f*g) = f'*g + f*g'
225                if lhs.requires_grad {
226                    let lhs_grad = (**rhs).mul_op(grad_output)?;
227                    lhs.backward_complex_impl(&lhs_grad)?;
228                }
229                if rhs.requires_grad {
230                    let rhs_grad = (**lhs).mul_op(grad_output)?;
231                    rhs.backward_complex_impl(&rhs_grad)?;
232                }
233            }
234            Operation::Custom(op_name, inputs) => {
235                match op_name.as_str() {
236                    "complex_conj" => {
237                        // Gradient of complex conjugate: d/dz(conj(f)) = conj(df/dz)
238                        if let Some(weak_input) = inputs.first() {
239                            if let Some(input) = weak_input.upgrade() {
240                                if input.requires_grad {
241                                    let conj_grad = grad_output.complex_conj()?;
242                                    input.backward_complex_impl(&conj_grad)?;
243                                }
244                            }
245                        }
246                    }
247                    "complex_abs" => {
248                        // Gradient of abs(z) = z / |z| for z != 0
249                        if let Some(weak_input) = inputs.first() {
250                            if let Some(input) = weak_input.upgrade() {
251                                if input.requires_grad {
252                                    let input_data = input.to_vec()?;
253                                    let grad_data = grad_output.to_vec()?;
254
255                                    let input_grad_data: Vec<T> = input_data
256                                        .iter()
257                                        .zip(grad_data.iter())
258                                        .map(|(&z, &grad)| {
259                                            let abs_z = z.abs();
260                                            if abs_z > T::Real::zero() {
261                                                // Gradient is z / |z| * grad_output
262                                                let z_normalized =
263                                                    T::new(z.real() / abs_z, z.imag() / abs_z);
264                                                T::new(
265                                                    z_normalized.real() * grad.real()
266                                                        - z_normalized.imag() * grad.imag(),
267                                                    z_normalized.real() * grad.imag()
268                                                        + z_normalized.imag() * grad.real(),
269                                                )
270                                            } else {
271                                                T::new(T::Real::zero(), T::Real::zero())
272                                            }
273                                        })
274                                        .collect();
275
276                                    let input_grad = Self::from_data(
277                                        input_grad_data,
278                                        input.shape().dims().to_vec(),
279                                        input.device,
280                                    )?;
281                                    input.backward_complex_impl(&input_grad)?;
282                                }
283                            }
284                        }
285                    }
286                    _ => {
287                        // For other custom operations, propagate gradient to all inputs
288                        for weak_input in inputs {
289                            if let Some(input) = weak_input.upgrade() {
290                                if input.requires_grad {
291                                    input.backward_complex_impl(grad_output)?;
292                                }
293                            }
294                        }
295                    }
296                }
297            }
298            _ => {
299                // For other operations, fall back to regular backward pass
300                // This would call the regular backward_impl method
301                // Note: This is a simplified approach - in practice, each operation
302                // would need its own complex-specific gradient computation
303            }
304        }
305
306        Ok(())
307    }
308
309    /// Element-wise complex multiplication with proper gradient tracking
310    pub fn complex_mul(&self, other: &Self) -> Result<Self>
311    where
312        T: std::ops::Mul<Output = T> + std::ops::Add<Output = T> + std::ops::Sub<Output = T>,
313    {
314        if self.shape() != other.shape() {
315            return Err(TorshError::ShapeMismatch {
316                expected: self.shape().dims().to_vec(),
317                got: other.shape().dims().to_vec(),
318            });
319        }
320
321        let self_data = self.to_vec()?;
322        let other_data = other.to_vec()?;
323
324        let result_data: Vec<T> = self_data
325            .iter()
326            .zip(other_data.iter())
327            .map(|(&a, &b)| {
328                // Complex multiplication: (a + bi)(c + di) = (ac - bd) + (ad + bc)i
329                T::new(
330                    a.real() * b.real() - a.imag() * b.imag(),
331                    a.real() * b.imag() + a.imag() * b.real(),
332                )
333            })
334            .collect();
335
336        let mut result = Self::from_data(result_data, self.shape().dims().to_vec(), self.device)?;
337
338        // Set up gradient tracking
339        if crate::should_record_grad(self.requires_grad || other.requires_grad) {
340            result.requires_grad = true;
341            result.operation = Operation::Mul {
342                lhs: Arc::new(self.clone()),
343                rhs: Arc::new(other.clone()),
344            };
345        }
346
347        Ok(result)
348    }
349
350    /// Element-wise complex addition with proper gradient tracking
351    pub fn complex_add(&self, other: &Self) -> Result<Self>
352    where
353        T: std::ops::Add<Output = T>,
354    {
355        if self.shape() != other.shape() {
356            return Err(TorshError::ShapeMismatch {
357                expected: self.shape().dims().to_vec(),
358                got: other.shape().dims().to_vec(),
359            });
360        }
361
362        let self_data = self.to_vec()?;
363        let other_data = other.to_vec()?;
364
365        let result_data: Vec<T> = self_data
366            .iter()
367            .zip(other_data.iter())
368            .map(|(&a, &b)| T::new(a.real() + b.real(), a.imag() + b.imag()))
369            .collect();
370
371        let mut result = Self::from_data(result_data, self.shape().dims().to_vec(), self.device)?;
372
373        // Set up gradient tracking
374        if crate::should_record_grad(self.requires_grad || other.requires_grad) {
375            result.requires_grad = true;
376            result.operation = Operation::Add {
377                lhs: Arc::new(self.clone()),
378                rhs: Arc::new(other.clone()),
379            };
380        }
381
382        Ok(result)
383    }
384
385    /// Check if all elements in the tensor are real (imaginary part is zero)
386    pub fn is_real(&self) -> Result<bool>
387    where
388        T::Real: PartialEq + num_traits::Zero,
389    {
390        let data = self.to_vec()?;
391        Ok(data.iter().all(|&z| z.imag() == T::Real::zero()))
392    }
393
394    /// Check if any elements in the tensor are complex (imaginary part is non-zero)
395    pub fn is_complex(&self) -> Result<bool>
396    where
397        T::Real: PartialEq + num_traits::Zero,
398    {
399        Ok(!self.is_real()?)
400    }
401}
402
403// Note: add_op and mul_op are provided by math_ops.rs for general use
404
405#[cfg(test)]
406mod tests {
407    use super::*;
408    use num_complex::Complex32;
409    use torsh_core::device::DeviceType;
410
411    type C32 = Complex32;
412
413    #[test]
414    fn test_complex_conjugate() {
415        let data = vec![C32::new(1.0, 2.0), C32::new(3.0, -4.0), C32::new(-1.0, 1.0)];
416        let tensor =
417            Tensor::from_data(data, vec![3], DeviceType::Cpu).expect("operation should succeed");
418
419        let conj_tensor = tensor
420            .complex_conj()
421            .expect("complex conjugate should succeed");
422        let conj_data = conj_tensor.to_vec().expect("to_vec should succeed");
423
424        assert_eq!(conj_data[0], C32::new(1.0, -2.0));
425        assert_eq!(conj_data[1], C32::new(3.0, 4.0));
426        assert_eq!(conj_data[2], C32::new(-1.0, -1.0));
427    }
428
429    #[test]
430    fn test_real_imag_extraction() {
431        let data = vec![C32::new(1.0, 2.0), C32::new(3.0, -4.0)];
432        let tensor =
433            Tensor::from_data(data, vec![2], DeviceType::Cpu).expect("operation should succeed");
434
435        let real_part = tensor.real().expect("real extraction should succeed");
436        let imag_part = tensor.imag().expect("imag extraction should succeed");
437
438        assert_eq!(
439            real_part.to_vec().expect("to_vec should succeed"),
440            vec![1.0, 3.0]
441        );
442        assert_eq!(
443            imag_part.to_vec().expect("to_vec should succeed"),
444            vec![2.0, -4.0]
445        );
446    }
447
448    #[test]
449    fn test_magnitude_and_phase() {
450        let data = vec![
451            C32::new(3.0, 4.0), // |z| = 5, arg = atan(4/3)
452            C32::new(1.0, 0.0), // |z| = 1, arg = 0
453        ];
454        let tensor =
455            Tensor::from_data(data, vec![2], DeviceType::Cpu).expect("operation should succeed");
456
457        let magnitude = tensor.abs().expect("abs computation should succeed");
458        let phase = tensor.angle().expect("angle computation should succeed");
459
460        let mag_data = magnitude.to_vec().expect("to_vec should succeed");
461        let phase_data = phase.to_vec().expect("to_vec should succeed");
462
463        assert!((mag_data[0] - 5.0).abs() < 1e-6);
464        assert!((mag_data[1] - 1.0).abs() < 1e-6);
465        assert!((phase_data[1] - 0.0).abs() < 1e-6);
466    }
467
468    #[test]
469    fn test_complex_from_components() {
470        let real_data = vec![1.0f32, 2.0, 3.0];
471        let imag_data = vec![4.0f32, 5.0, 6.0];
472
473        let real_tensor = Tensor::from_data(real_data, vec![3], DeviceType::Cpu)
474            .expect("operation should succeed");
475        let imag_tensor = Tensor::from_data(imag_data, vec![3], DeviceType::Cpu)
476            .expect("operation should succeed");
477
478        let complex_tensor =
479            Tensor::<C32>::complex(&real_tensor, &imag_tensor).expect("operation should succeed");
480        let result_data = complex_tensor.to_vec().expect("to_vec should succeed");
481
482        assert_eq!(result_data[0], C32::new(1.0, 4.0));
483        assert_eq!(result_data[1], C32::new(2.0, 5.0));
484        assert_eq!(result_data[2], C32::new(3.0, 6.0));
485    }
486
487    #[test]
488    fn test_complex_arithmetic() {
489        let a_data = vec![C32::new(1.0, 2.0), C32::new(3.0, 4.0)];
490        let b_data = vec![C32::new(2.0, 1.0), C32::new(1.0, -1.0)];
491
492        let a =
493            Tensor::from_data(a_data, vec![2], DeviceType::Cpu).expect("operation should succeed");
494        let b =
495            Tensor::from_data(b_data, vec![2], DeviceType::Cpu).expect("operation should succeed");
496
497        // Test complex addition
498        let sum = a.complex_add(&b).expect("operation should succeed");
499        let sum_data = sum.to_vec().expect("to_vec should succeed");
500        assert_eq!(sum_data[0], C32::new(3.0, 3.0));
501        assert_eq!(sum_data[1], C32::new(4.0, 3.0));
502
503        // Test complex multiplication
504        let product = a.complex_mul(&b).expect("operation should succeed");
505        let prod_data = product.to_vec().expect("to_vec should succeed");
506        // (1+2i)(2+1i) = 2 + i + 4i + 2i² = 2 + 5i - 2 = 0 + 5i
507        assert_eq!(prod_data[0], C32::new(0.0, 5.0));
508        // (3+4i)(1-1i) = 3 - 3i + 4i - 4i² = 3 + i + 4 = 7 + i
509        assert_eq!(prod_data[1], C32::new(7.0, 1.0));
510    }
511
512    #[test]
513    fn test_polar_construction() {
514        let mag_data = vec![1.0f32, 2.0];
515        let phase_data = vec![0.0f32, std::f32::consts::PI / 2.0];
516
517        let mag_tensor = Tensor::from_data(mag_data, vec![2], DeviceType::Cpu)
518            .expect("operation should succeed");
519        let phase_tensor = Tensor::from_data(phase_data, vec![2], DeviceType::Cpu)
520            .expect("operation should succeed");
521
522        let complex_tensor =
523            Tensor::<C32>::polar(&mag_tensor, &phase_tensor).expect("operation should succeed");
524        let result_data = complex_tensor.to_vec().expect("to_vec should succeed");
525
526        // First element: 1 * (cos(0) + i*sin(0)) = 1 + 0i
527        assert!((result_data[0].re - 1.0).abs() < 1e-6);
528        assert!((result_data[0].im - 0.0).abs() < 1e-6);
529
530        // Second element: 2 * (cos(π/2) + i*sin(π/2)) = 2 * (0 + i) = 0 + 2i
531        assert!((result_data[1].re - 0.0).abs() < 1e-6);
532        assert!((result_data[1].im - 2.0).abs() < 1e-6);
533    }
534
535    #[test]
536    fn test_is_real_complex() {
537        let real_data = vec![C32::new(1.0, 0.0), C32::new(2.0, 0.0)];
538        let complex_data = vec![C32::new(1.0, 1.0), C32::new(2.0, 0.0)];
539
540        let real_tensor = Tensor::from_data(real_data, vec![2], DeviceType::Cpu)
541            .expect("operation should succeed");
542        let complex_tensor = Tensor::from_data(complex_data, vec![2], DeviceType::Cpu)
543            .expect("operation should succeed");
544
545        assert!(real_tensor.is_real().expect("is_real check should succeed"));
546        assert!(!real_tensor
547            .is_complex()
548            .expect("is_complex check should succeed"));
549
550        assert!(!complex_tensor
551            .is_real()
552            .expect("is_real check should succeed"));
553        assert!(complex_tensor
554            .is_complex()
555            .expect("is_complex check should succeed"));
556    }
557
558    #[test]
559    fn test_shape_mismatch_errors() {
560        let a = Tensor::<C32>::zeros(&[2], DeviceType::Cpu).expect("operation should succeed");
561        let b = Tensor::<C32>::zeros(&[3], DeviceType::Cpu).expect("operation should succeed");
562
563        assert!(a.complex_add(&b).is_err());
564        assert!(a.complex_mul(&b).is_err());
565
566        let real_2 = Tensor::<f32>::zeros(&[2], DeviceType::Cpu).expect("operation should succeed");
567        let imag_3 = Tensor::<f32>::zeros(&[3], DeviceType::Cpu).expect("operation should succeed");
568
569        assert!(Tensor::<C32>::complex(&real_2, &imag_3).is_err());
570    }
571}