use super::Tensor;
use crate::errors::{Result, TrustformersError};
use scirs2_core::ndarray::{ArrayD, IxDyn};
use scirs2_core::{Complex, Complex32, Complex64};
const MAX_SAFE_MAGNITUDE_F32: f32 = 1e30;
const MAX_SAFE_MAGNITUDE_F64: f64 = 1e300;
fn is_stable_c32(z: Complex32) -> bool {
z.re.is_finite() && z.im.is_finite() && z.norm() < MAX_SAFE_MAGNITUDE_F32
}
fn is_stable_c64(z: Complex64) -> bool {
z.re.is_finite() && z.im.is_finite() && z.norm() < MAX_SAFE_MAGNITUDE_F64
}
fn stabilize_c32(z: Complex32) -> Complex32 {
if !z.re.is_finite() || !z.im.is_finite() {
return Complex32::new(0.0, 0.0);
}
let magnitude = z.norm();
if magnitude > MAX_SAFE_MAGNITUDE_F32 {
let scale = MAX_SAFE_MAGNITUDE_F32 / magnitude;
Complex32::new(z.re * scale, z.im * scale)
} else {
z
}
}
fn stabilize_c64(z: Complex64) -> Complex64 {
if !z.re.is_finite() || !z.im.is_finite() {
return Complex64::new(0.0, 0.0);
}
let magnitude = z.norm();
if magnitude > MAX_SAFE_MAGNITUDE_F64 {
let scale = MAX_SAFE_MAGNITUDE_F64 / magnitude;
Complex64::new(z.re * scale, z.im * scale)
} else {
z
}
}
impl Tensor {
pub fn real(&self) -> Result<Tensor> {
match self {
Tensor::C32(a) => {
let result = a.mapv(|x| x.re);
Ok(Tensor::F32(result))
},
Tensor::C64(a) => {
let result = a.mapv(|x| x.re);
Ok(Tensor::F64(result))
},
Tensor::CF16(a) => {
let result = a.mapv(|x| x.re);
Ok(Tensor::F16(result))
},
Tensor::CBF16(a) => {
let result = a.mapv(|x| x.re);
Ok(Tensor::BF16(result))
},
Tensor::F32(_) | Tensor::F64(_) | Tensor::F16(_) | Tensor::BF16(_) | Tensor::I64(_) => {
Ok(self.clone())
},
_ => Err(TrustformersError::tensor_op_error(
"Real part extraction not supported for this tensor type",
"complex real part extraction",
)),
}
}
pub fn imag(&self) -> Result<Tensor> {
match self {
Tensor::C32(a) => {
let result = a.mapv(|x| x.im);
Ok(Tensor::F32(result))
},
Tensor::C64(a) => {
let result = a.mapv(|x| x.im);
Ok(Tensor::F64(result))
},
Tensor::CF16(a) => {
let result = a.mapv(|x| x.im);
Ok(Tensor::F16(result))
},
Tensor::CBF16(a) => {
let result = a.mapv(|x| x.im);
Ok(Tensor::BF16(result))
},
Tensor::F32(a) => {
let result = ArrayD::zeros(a.raw_dim());
Ok(Tensor::F32(result))
},
Tensor::F64(a) => {
let result = ArrayD::zeros(a.raw_dim());
Ok(Tensor::F64(result))
},
Tensor::F16(a) => {
let size = a.len();
let data = vec![half::f16::ZERO; size];
let result = ArrayD::from_shape_vec(a.raw_dim(), data)
.map_err(|e| TrustformersError::shape_error(e.to_string()))?;
Ok(Tensor::F16(result))
},
Tensor::BF16(a) => {
let size = a.len();
let data = vec![half::bf16::ZERO; size];
let result = ArrayD::from_shape_vec(a.raw_dim(), data)
.map_err(|e| TrustformersError::shape_error(e.to_string()))?;
Ok(Tensor::BF16(result))
},
Tensor::I64(a) => {
let result = ArrayD::zeros(a.raw_dim());
Ok(Tensor::F32(result))
},
_ => Err(TrustformersError::tensor_op_error(
"Imaginary part extraction not supported for this tensor type",
"complex imaginary part extraction",
)),
}
}
pub fn magnitude(&self) -> Result<Tensor> {
match self {
Tensor::C32(a) => {
let result = a.mapv(|x| {
if !is_stable_c32(x) {
let stabilized = stabilize_c32(x);
stabilized.norm()
} else {
let abs_re = x.re.abs();
let abs_im = x.im.abs();
if abs_re == 0.0 {
abs_im
} else if abs_im == 0.0 {
abs_re
} else if abs_re > abs_im {
let ratio = abs_im / abs_re;
abs_re * (1.0 + ratio * ratio).sqrt()
} else {
let ratio = abs_re / abs_im;
abs_im * (1.0 + ratio * ratio).sqrt()
}
}
});
Ok(Tensor::F32(result))
},
Tensor::C64(a) => {
let result = a.mapv(|x| {
if !is_stable_c64(x) {
let stabilized = stabilize_c64(x);
stabilized.norm()
} else {
let abs_re = x.re.abs();
let abs_im = x.im.abs();
if abs_re == 0.0 {
abs_im
} else if abs_im == 0.0 {
abs_re
} else if abs_re > abs_im {
let ratio = abs_im / abs_re;
abs_re * (1.0 + ratio * ratio).sqrt()
} else {
let ratio = abs_re / abs_im;
abs_im * (1.0 + ratio * ratio).sqrt()
}
}
});
Ok(Tensor::F64(result))
},
Tensor::CF16(a) => {
let result = a.mapv(|x| {
let re_f32 = x.re.to_f32();
let im_f32 = x.im.to_f32();
if !re_f32.is_finite() || !im_f32.is_finite() {
return half::f16::from_f32(0.0);
}
let abs_re = re_f32.abs();
let abs_im = im_f32.abs();
let norm = if abs_re == 0.0 {
abs_im
} else if abs_im == 0.0 {
abs_re
} else if abs_re > abs_im {
let ratio = abs_im / abs_re;
abs_re * (1.0 + ratio * ratio).sqrt()
} else {
let ratio = abs_re / abs_im;
abs_im * (1.0 + ratio * ratio).sqrt()
};
half::f16::from_f32(norm.min(half::f16::MAX.to_f32()))
});
Ok(Tensor::F16(result))
},
Tensor::CBF16(a) => {
let result = a.mapv(|x| {
let re_f32 = x.re.to_f32();
let im_f32 = x.im.to_f32();
if !re_f32.is_finite() || !im_f32.is_finite() {
return half::bf16::from_f32(0.0);
}
let abs_re = re_f32.abs();
let abs_im = im_f32.abs();
let norm = if abs_re == 0.0 {
abs_im
} else if abs_im == 0.0 {
abs_re
} else if abs_re > abs_im {
let ratio = abs_im / abs_re;
abs_re * (1.0 + ratio * ratio).sqrt()
} else {
let ratio = abs_re / abs_im;
abs_im * (1.0 + ratio * ratio).sqrt()
};
half::bf16::from_f32(norm.min(half::bf16::MAX.to_f32()))
});
Ok(Tensor::BF16(result))
},
Tensor::F32(a) => {
let result = a.mapv(|x| x.abs());
Ok(Tensor::F32(result))
},
Tensor::F64(a) => {
let result = a.mapv(|x| x.abs());
Ok(Tensor::F64(result))
},
Tensor::F16(a) => {
let result = a.mapv(|x| {
let val = x.to_f32();
half::f16::from_f32(val.abs())
});
Ok(Tensor::F16(result))
},
Tensor::BF16(a) => {
let result = a.mapv(|x| {
let val = x.to_f32();
half::bf16::from_f32(val.abs())
});
Ok(Tensor::BF16(result))
},
Tensor::I64(a) => {
let result = a.mapv(|x| x.abs() as f32);
Ok(Tensor::F32(result))
},
_ => Err(TrustformersError::tensor_op_error(
"Magnitude not supported for this tensor type",
"complex magnitude calculation",
)),
}
}
pub fn phase(&self) -> Result<Tensor> {
match self {
Tensor::C32(a) => {
let result = a.mapv(|x| x.arg());
Ok(Tensor::F32(result))
},
Tensor::C64(a) => {
let result = a.mapv(|x| x.arg());
Ok(Tensor::F64(result))
},
Tensor::CF16(a) => {
let result = a.mapv(|x| {
let re_f32 = x.re.to_f32();
let im_f32 = x.im.to_f32();
let phase = im_f32.atan2(re_f32);
half::f16::from_f32(phase)
});
Ok(Tensor::F16(result))
},
Tensor::CBF16(a) => {
let result = a.mapv(|x| {
let re_f32 = x.re.to_f32();
let im_f32 = x.im.to_f32();
let phase = im_f32.atan2(re_f32);
half::bf16::from_f32(phase)
});
Ok(Tensor::BF16(result))
},
Tensor::F32(a) => {
let result = a.mapv(|x| if x >= 0.0 { 0.0 } else { std::f32::consts::PI });
Ok(Tensor::F32(result))
},
Tensor::F64(a) => {
let result = a.mapv(|x| if x >= 0.0 { 0.0 } else { std::f64::consts::PI });
Ok(Tensor::F64(result))
},
Tensor::F16(a) => {
let result = a.mapv(|x| {
let val = x.to_f32();
if val >= 0.0 {
half::f16::from_f32(0.0)
} else {
half::f16::from_f32(std::f32::consts::PI)
}
});
Ok(Tensor::F16(result))
},
Tensor::BF16(a) => {
let result = a.mapv(|x| {
let val = x.to_f32();
if val >= 0.0 {
half::bf16::from_f32(0.0)
} else {
half::bf16::from_f32(std::f32::consts::PI)
}
});
Ok(Tensor::BF16(result))
},
_ => Err(TrustformersError::tensor_op_error(
"Phase not supported for this tensor type",
"complex phase calculation",
)),
}
}
pub fn conj(&self) -> Result<Tensor> {
match self {
Tensor::C32(a) => {
let result = a.mapv(|x| x.conj());
Ok(Tensor::C32(result))
},
Tensor::C64(a) => {
let result = a.mapv(|x| x.conj());
Ok(Tensor::C64(result))
},
Tensor::CF16(a) => {
let result = a.mapv(|x| Complex::new(x.re, -x.im));
Ok(Tensor::CF16(result))
},
Tensor::CBF16(a) => {
let result = a.mapv(|x| Complex::new(x.re, -x.im));
Ok(Tensor::CBF16(result))
},
Tensor::F32(_) | Tensor::F64(_) | Tensor::F16(_) | Tensor::BF16(_) | Tensor::I64(_) => {
Ok(self.clone())
},
_ => Err(TrustformersError::tensor_op_error(
"Complex conjugate not supported for this tensor type",
"complex conjugate operation",
)),
}
}
pub fn to_complex(&self) -> Result<Tensor> {
match self {
Tensor::F32(a) => {
let result = a.mapv(|x| Complex32::new(x, 0.0));
Ok(Tensor::C32(result))
},
Tensor::F64(a) => {
let result = a.mapv(|x| Complex64::new(x, 0.0));
Ok(Tensor::C64(result))
},
Tensor::F16(a) => {
let result = a.mapv(|x| Complex::new(x, half::f16::from_f32(0.0)));
Ok(Tensor::CF16(result))
},
Tensor::BF16(a) => {
let result = a.mapv(|x| Complex::new(x, half::bf16::from_f32(0.0)));
Ok(Tensor::CBF16(result))
},
Tensor::I64(a) => {
let result = a.mapv(|x| Complex32::new(x as f32, 0.0));
Ok(Tensor::C32(result))
},
Tensor::C32(_) | Tensor::C64(_) | Tensor::CF16(_) | Tensor::CBF16(_) => {
Ok(self.clone())
},
_ => Err(TrustformersError::tensor_op_error(
"Cannot convert this tensor type to complex",
"complex tensor conversion",
)),
}
}
pub fn complex_hadamard(&self, other: &Tensor) -> Result<Tensor> {
match (self, other) {
(Tensor::C32(a), Tensor::C32(b)) => {
let result = a * b;
Ok(Tensor::C32(result))
},
(Tensor::C64(a), Tensor::C64(b)) => {
let result = a * b;
Ok(Tensor::C64(result))
},
(Tensor::CF16(a), Tensor::CF16(b)) => {
let result = a
.iter()
.zip(b.iter())
.map(|(a_val, b_val)| {
Complex::new(
a_val.re * b_val.re - a_val.im * b_val.im,
a_val.re * b_val.im + a_val.im * b_val.re,
)
})
.collect::<Vec<_>>();
Ok(Tensor::CF16(
ArrayD::from_shape_vec(a.raw_dim(), result)
.map_err(|e| TrustformersError::shape_error(e.to_string()))?,
))
},
(Tensor::CBF16(a), Tensor::CBF16(b)) => {
let result = a
.iter()
.zip(b.iter())
.map(|(a_val, b_val)| {
Complex::new(
a_val.re * b_val.re - a_val.im * b_val.im,
a_val.re * b_val.im + a_val.im * b_val.re,
)
})
.collect::<Vec<_>>();
Ok(Tensor::CBF16(
ArrayD::from_shape_vec(a.raw_dim(), result)
.map_err(|e| TrustformersError::shape_error(e.to_string()))?,
))
},
_ => Err(TrustformersError::tensor_op_error(
"Complex Hadamard product requires matching complex tensor types",
"complex Hadamard product",
)),
}
}
pub fn fft(&self) -> Result<Tensor> {
match self {
Tensor::C32(a) => {
if a.shape().len() != 1 {
return Err(TrustformersError::tensor_op_error(
"FFT currently only supports 1D tensors",
"complex FFT operation",
));
}
let n = a.len();
if n == 0 {
return Err(TrustformersError::tensor_op_error(
"FFT requires non-empty tensor",
"complex FFT operation",
));
}
let mut buffer = Vec::with_capacity(n);
for index in 0..n {
let value = a[[index]];
if !is_stable_c32(value) {
return Err(TrustformersError::tensor_op_error(
"FFT input contains non-finite or unsafely large values",
"complex FFT operation",
));
}
buffer.push(Complex64::new(value.re as f64, value.im as f64));
}
fft_1d(&mut buffer)?;
let mut result = ArrayD::zeros(IxDyn(&[n]));
for (index, value) in buffer.iter().enumerate() {
result[[index]] = Complex32::new(value.re as f32, value.im as f32);
}
Ok(Tensor::C32(result))
},
Tensor::C64(a) => {
if a.shape().len() != 1 {
return Err(TrustformersError::tensor_op_error(
"FFT currently only supports 1D tensors",
"complex FFT operation",
));
}
let n = a.len();
if n == 0 {
return Err(TrustformersError::tensor_op_error(
"FFT requires non-empty tensor",
"complex FFT operation",
));
}
let mut buffer = Vec::with_capacity(n);
for index in 0..n {
let value = a[[index]];
if !is_stable_c64(value) {
return Err(TrustformersError::tensor_op_error(
"FFT input contains non-finite or unsafely large values",
"complex FFT operation",
));
}
buffer.push(value);
}
fft_1d(&mut buffer)?;
let mut result = ArrayD::zeros(IxDyn(&[n]));
for (index, value) in buffer.iter().enumerate() {
result[[index]] = *value;
}
Ok(Tensor::C64(result))
},
_ => Err(TrustformersError::tensor_op_error(
"FFT only supports complex tensors",
"complex FFT operation",
)),
}
}
pub fn complex_matmul(&self, other: &Tensor) -> Result<Tensor> {
match (self, other) {
(Tensor::C32(a), Tensor::C32(b)) => {
if a.shape().len() != 2 || b.shape().len() != 2 {
return Err(TrustformersError::tensor_op_error(
"Complex matrix multiplication requires 2D tensors",
"complex matrix multiplication",
));
}
let a_rows = a.shape()[0];
let a_cols = a.shape()[1];
let b_rows = b.shape()[0];
let b_cols = b.shape()[1];
if a_cols != b_rows {
return Err(TrustformersError::tensor_op_error(
"Matrix dimensions incompatible for multiplication",
"complex matrix multiplication",
));
}
if a_rows == 0 || a_cols == 0 || b_cols == 0 {
return Err(TrustformersError::tensor_op_error(
"Matrix multiplication requires non-zero dimensions",
"complex matrix multiplication",
));
}
let mut result = ArrayD::zeros(IxDyn(&[a_rows, b_cols]));
for i in 0..a_rows {
for j in 0..b_cols {
let mut sum = Complex32::new(0.0, 0.0);
let mut compensation = Complex32::new(0.0, 0.0); let mut unstable_count = 0;
for k in 0..a_cols {
let a_val = a[[i, k]];
let b_val = b[[k, j]];
if !is_stable_c32(a_val) || !is_stable_c32(b_val) {
unstable_count += 1;
continue;
}
let product = a_val * b_val;
let y = product - compensation;
let t = sum + y;
compensation = (t - sum) - y;
sum = t;
if !is_stable_c32(sum) {
sum = stabilize_c32(sum);
break;
}
}
if unstable_count > a_cols / 2 {
sum = stabilize_c32(sum * Complex32::new(0.5, 0.0));
}
result[[i, j]] = sum;
}
}
Ok(Tensor::C32(result))
},
(Tensor::C64(a), Tensor::C64(b)) => {
if a.shape().len() != 2 || b.shape().len() != 2 {
return Err(TrustformersError::tensor_op_error(
"Complex matrix multiplication requires 2D tensors",
"complex matrix multiplication",
));
}
let a_rows = a.shape()[0];
let a_cols = a.shape()[1];
let b_rows = b.shape()[0];
let b_cols = b.shape()[1];
if a_cols != b_rows {
return Err(TrustformersError::tensor_op_error(
"Matrix dimensions incompatible for multiplication",
"complex matrix multiplication",
));
}
if a_rows == 0 || a_cols == 0 || b_cols == 0 {
return Err(TrustformersError::tensor_op_error(
"Matrix multiplication requires non-zero dimensions",
"complex matrix multiplication",
));
}
let mut result = ArrayD::zeros(IxDyn(&[a_rows, b_cols]));
for i in 0..a_rows {
for j in 0..b_cols {
let mut sum = Complex64::new(0.0, 0.0);
let mut compensation = Complex64::new(0.0, 0.0); let mut unstable_count = 0;
for k in 0..a_cols {
let a_val = a[[i, k]];
let b_val = b[[k, j]];
if !is_stable_c64(a_val) || !is_stable_c64(b_val) {
unstable_count += 1;
continue;
}
let product = a_val * b_val;
let y = product - compensation;
let t = sum + y;
compensation = (t - sum) - y;
sum = t;
if !is_stable_c64(sum) {
sum = stabilize_c64(sum);
break;
}
}
if unstable_count > a_cols / 2 {
sum = stabilize_c64(sum * Complex64::new(0.5, 0.0));
}
result[[i, j]] = sum;
}
}
Ok(Tensor::C64(result))
},
_ => Err(TrustformersError::tensor_op_error(
"Complex matrix multiplication requires matching complex tensor types",
"complex matrix multiplication",
)),
}
}
pub fn complex_relu(&self) -> Result<Tensor> {
match self {
Tensor::C32(a) => {
let result = a.mapv(|x| Complex32::new(x.re.max(0.0), x.im.max(0.0)));
Ok(Tensor::C32(result))
},
Tensor::C64(a) => {
let result = a.mapv(|x| Complex64::new(x.re.max(0.0), x.im.max(0.0)));
Ok(Tensor::C64(result))
},
Tensor::CF16(a) => {
let result = a.mapv(|x| {
let re_f32 = x.re.to_f32().max(0.0);
let im_f32 = x.im.to_f32().max(0.0);
Complex::new(half::f16::from_f32(re_f32), half::f16::from_f32(im_f32))
});
Ok(Tensor::CF16(result))
},
Tensor::CBF16(a) => {
let result = a.mapv(|x| {
let re_f32 = x.re.to_f32().max(0.0);
let im_f32 = x.im.to_f32().max(0.0);
Complex::new(half::bf16::from_f32(re_f32), half::bf16::from_f32(im_f32))
});
Ok(Tensor::CBF16(result))
},
_ => Err(TrustformersError::tensor_op_error(
"Complex ReLU only supports complex tensors",
"complex ReLU activation",
)),
}
}
}
fn fft_1d(data: &mut Vec<Complex64>) -> Result<()> {
let n = data.len();
if n <= 1 {
return Ok(());
}
if n.is_power_of_two() {
fft_radix2_in_place(data);
Ok(())
} else {
let transformed = fft_bluestein(data)?;
data.clear();
data.extend_from_slice(&transformed);
Ok(())
}
}
fn fft_radix2_in_place(data: &mut [Complex64]) {
let n = data.len();
if n <= 1 {
return;
}
let mut target = 0usize;
for source in 1..n {
let mut bit = n >> 1;
while target & bit != 0 {
target ^= bit;
bit >>= 1;
}
target |= bit;
if source < target {
data.swap(source, target);
}
}
let mut span = 2usize;
while span <= n {
let half = span / 2;
let base_angle = -2.0 * std::f64::consts::PI / span as f64;
let mut offset = 0usize;
while offset < n {
for k in 0..half {
let angle = base_angle * k as f64;
let twiddle = Complex64::new(angle.cos(), angle.sin());
let even = data[offset + k];
let odd = data[offset + k + half] * twiddle;
data[offset + k] = even + odd;
data[offset + k + half] = even - odd;
}
offset += span;
}
span <<= 1;
}
}
fn ifft_radix2_in_place(data: &mut [Complex64]) {
for value in data.iter_mut() {
*value = value.conj();
}
fft_radix2_in_place(data);
let inverse_len = 1.0 / data.len() as f64;
for value in data.iter_mut() {
*value = value.conj() * inverse_len;
}
}
fn fft_bluestein(data: &[Complex64]) -> Result<Vec<Complex64>> {
let n = data.len();
let target = 2 * n - 1;
let m = target.checked_next_power_of_two().ok_or_else(|| {
TrustformersError::tensor_op_error(
"FFT length is too large for the Bluestein convolution buffer",
"complex FFT operation",
)
})?;
let modulus = 2u128 * n as u128;
let chirp = |index: usize| -> Complex64 {
let squared = (index as u128 * index as u128) % modulus;
let angle = -std::f64::consts::PI * squared as f64 / n as f64;
Complex64::new(angle.cos(), angle.sin())
};
let mut a = vec![Complex64::new(0.0, 0.0); m];
let mut b = vec![Complex64::new(0.0, 0.0); m];
for index in 0..n {
let c = chirp(index);
a[index] = data[index] * c;
let conjugate = c.conj();
b[index] = conjugate;
if index > 0 {
b[m - index] = conjugate;
}
}
fft_radix2_in_place(&mut a);
fft_radix2_in_place(&mut b);
for (a_value, b_value) in a.iter_mut().zip(b.iter()) {
*a_value *= *b_value;
}
ifft_radix2_in_place(&mut a);
Ok((0..n).map(|k| a[k] * chirp(k)).collect())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::Result;
use crate::tensor::DType;
#[test]
fn test_is_stable_c32_normal() {
let z = Complex32::new(1.0, 2.0);
assert!(is_stable_c32(z));
}
#[test]
fn test_is_stable_c32_nan() {
let z = Complex32::new(f32::NAN, 0.0);
assert!(!is_stable_c32(z));
}
#[test]
fn test_is_stable_c32_inf() {
let z = Complex32::new(f32::INFINITY, 0.0);
assert!(!is_stable_c32(z));
}
#[test]
fn test_is_stable_c64_normal() {
let z = Complex64::new(3.0, 4.0);
assert!(is_stable_c64(z));
}
#[test]
fn test_is_stable_c64_nan() {
let z = Complex64::new(0.0, f64::NAN);
assert!(!is_stable_c64(z));
}
#[test]
fn test_stabilize_c32_nan_to_zero() {
let z = Complex32::new(f32::NAN, f32::NAN);
let s = stabilize_c32(z);
assert_eq!(s.re, 0.0);
assert_eq!(s.im, 0.0);
}
#[test]
fn test_stabilize_c32_normal_unchanged() {
let z = Complex32::new(1.0, 2.0);
let s = stabilize_c32(z);
assert!((s.re - 1.0).abs() < 1e-6);
assert!((s.im - 2.0).abs() < 1e-6);
}
#[test]
fn test_stabilize_c64_nan_to_zero() {
let z = Complex64::new(f64::INFINITY, 0.0);
let s = stabilize_c64(z);
assert_eq!(s.re, 0.0);
assert_eq!(s.im, 0.0);
}
#[test]
fn test_stabilize_c64_normal_unchanged() {
let z = Complex64::new(5.0, 3.0);
let s = stabilize_c64(z);
assert!((s.re - 5.0).abs() < 1e-10);
assert!((s.im - 3.0).abs() < 1e-10);
}
#[test]
fn test_real_part_c32() -> Result<()> {
let t = Tensor::complex(vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0], &[3])?;
let real = t.real()?;
assert_eq!(real.dtype(), DType::F32);
let data = real.data()?;
assert!((data[0] - 1.0).abs() < 1e-6);
assert!((data[1] - 2.0).abs() < 1e-6);
assert!((data[2] - 3.0).abs() < 1e-6);
Ok(())
}
#[test]
fn test_imag_part_c32() -> Result<()> {
let t = Tensor::complex(vec![1.0, 2.0], vec![3.0, 4.0], &[2])?;
let imag = t.imag()?;
assert_eq!(imag.dtype(), DType::F32);
let data = imag.data()?;
assert!((data[0] - 3.0).abs() < 1e-6);
assert!((data[1] - 4.0).abs() < 1e-6);
Ok(())
}
#[test]
fn test_real_part_of_real_tensor() -> Result<()> {
let t = Tensor::from_data(vec![1.0, 2.0], &[2])?;
let real = t.real()?;
assert_eq!(real.dtype(), DType::F32);
Ok(())
}
#[test]
fn test_magnitude_c32() -> Result<()> {
let t = Tensor::complex(vec![3.0], vec![4.0], &[1])?;
let mag = t.magnitude()?;
let data = mag.data()?;
assert!((data[0] - 5.0).abs() < 1e-5);
Ok(())
}
#[test]
fn test_magnitude_zero() -> Result<()> {
let t = Tensor::complex(vec![0.0], vec![0.0], &[1])?;
let mag = t.magnitude()?;
let data = mag.data()?;
assert!(data[0].abs() < 1e-5);
Ok(())
}
#[test]
fn test_phase_c32() -> Result<()> {
let t = Tensor::complex(vec![1.0], vec![0.0], &[1])?;
let phase = t.phase()?;
let data = phase.data()?;
assert!(data[0].abs() < 1e-5);
Ok(())
}
#[test]
fn test_conj_c32() -> Result<()> {
let t = Tensor::complex(vec![1.0, 2.0], vec![3.0, 4.0], &[2])?;
let conj = t.conj()?;
let imag = conj.imag()?;
let data = imag.data()?;
assert!((data[0] - (-3.0)).abs() < 1e-6);
assert!((data[1] - (-4.0)).abs() < 1e-6);
Ok(())
}
#[test]
fn test_to_complex_from_f32() -> Result<()> {
let t = Tensor::from_data(vec![1.0, 2.0, 3.0], &[3])?;
let c = t.to_complex()?;
assert_eq!(c.dtype(), DType::C32);
assert_eq!(c.shape(), vec![3]);
let imag = c.imag()?;
let data = imag.data()?;
for val in &data {
assert!(val.abs() < 1e-6);
}
Ok(())
}
#[test]
fn test_complex_hadamard() -> Result<()> {
let a = Tensor::complex(vec![1.0, 2.0], vec![0.0, 0.0], &[2])?;
let b = Tensor::complex(vec![3.0, 4.0], vec![0.0, 0.0], &[2])?;
let result = a.complex_hadamard(&b)?;
let real = result.real()?;
let data = real.data()?;
assert!((data[0] - 3.0).abs() < 1e-5);
assert!((data[1] - 8.0).abs() < 1e-5);
Ok(())
}
#[test]
fn test_complex_relu_positive_real() -> Result<()> {
let t = Tensor::complex(vec![1.0, -1.0], vec![2.0, 3.0], &[2])?;
let result = t.complex_relu()?;
let real = result.real()?;
let data = real.data()?;
assert!((data[0] - 1.0).abs() < 1e-5);
assert!(data[1].abs() < 1e-5);
Ok(())
}
#[test]
fn test_complex_c64_real_imag() -> Result<()> {
let t = Tensor::complex_f64(vec![1.0, 2.0], vec![3.0, 4.0], &[2])?;
let real = t.real()?;
assert_eq!(real.dtype(), DType::F64);
let imag = t.imag()?;
assert_eq!(imag.dtype(), DType::F64);
Ok(())
}
#[test]
fn test_conj_of_real_is_itself() -> Result<()> {
let t = Tensor::from_data(vec![1.0, 2.0], &[2])?;
let conj = t.conj()?;
let data = conj.data()?;
assert!((data[0] - 1.0).abs() < 1e-6);
assert!((data[1] - 2.0).abs() < 1e-6);
Ok(())
}
#[test]
fn test_magnitude_c64() -> Result<()> {
let t = Tensor::complex_f64(vec![3.0], vec![4.0], &[1])?;
let mag = t.magnitude()?;
assert_eq!(mag.dtype(), DType::F64);
Ok(())
}
#[test]
fn test_complex_2d() -> Result<()> {
let t = Tensor::complex(vec![1.0, 2.0, 3.0, 4.0], vec![5.0, 6.0, 7.0, 8.0], &[2, 2])?;
assert_eq!(t.shape(), vec![2, 2]);
let real = t.real()?;
assert_eq!(real.shape(), vec![2, 2]);
Ok(())
}
fn reference_dft(values: &[Complex64]) -> Vec<Complex64> {
let n = values.len();
(0..n)
.map(|k| {
let mut sum = Complex64::new(0.0, 0.0);
for (j, value) in values.iter().enumerate() {
let angle = -2.0 * std::f64::consts::PI * (k as f64) * (j as f64) / n as f64;
sum += value * Complex64::new(angle.cos(), angle.sin());
}
sum
})
.collect()
}
fn deterministic_signal(n: usize) -> Vec<Complex64> {
(0..n)
.map(|i| {
Complex64::new(
(i as f64 * 0.37).sin() + 0.25 * i as f64 / n as f64,
(i as f64 * 0.11).cos() - 0.1,
)
})
.collect()
}
#[test]
fn test_fft_matches_naive_dft() {
for &n in &[1usize, 2, 3, 4, 5, 6, 7, 8, 12, 16, 17, 31, 32, 60, 64] {
let signal = deterministic_signal(n);
let expected = reference_dft(&signal);
let mut array = ArrayD::zeros(IxDyn(&[n]));
for (index, value) in signal.iter().enumerate() {
array[[index]] = *value;
}
let transformed = Tensor::C64(array).fft().expect("fft succeeds");
let Tensor::C64(output) = transformed else {
panic!("FFT of a C64 tensor must stay C64");
};
for k in 0..n {
let got = output[[k]];
let want = expected[k];
let tolerance = 1e-9 * (1.0 + want.norm()) * (n as f64);
assert!(
(got.re - want.re).abs() < tolerance && (got.im - want.im).abs() < tolerance,
"n = {n}, k = {k}: got {got:?}, expected {want:?}"
);
}
}
}
#[test]
fn test_fft_known_closed_forms() {
let n = 12usize;
let mut impulse = ArrayD::zeros(IxDyn(&[n]));
impulse[[0]] = Complex64::new(1.0, 0.0);
let Tensor::C64(spectrum) = Tensor::C64(impulse).fft().expect("fft succeeds") else {
panic!("unexpected dtype");
};
for k in 0..n {
assert!((spectrum[[k]].re - 1.0).abs() < 1e-9);
assert!(spectrum[[k]].im.abs() < 1e-9);
}
let mut constant = ArrayD::zeros(IxDyn(&[n]));
for k in 0..n {
constant[[k]] = Complex64::new(2.0, 0.0);
}
let Tensor::C64(spectrum) = Tensor::C64(constant).fft().expect("fft succeeds") else {
panic!("unexpected dtype");
};
assert!((spectrum[[0]].re - 2.0 * n as f64).abs() < 1e-8);
for k in 1..n {
assert!(spectrum[[k]].norm() < 1e-8, "bin {k} = {:?}", spectrum[[k]]);
}
}
#[test]
fn test_fft_rejects_non_finite_input() {
let mut array = ArrayD::zeros(IxDyn(&[4]));
array[[0]] = Complex64::new(1.0, 0.0);
array[[1]] = Complex64::new(f64::NAN, 0.0);
assert!(Tensor::C64(array).fft().is_err());
}
#[test]
fn test_fft_c32_matches_c64() {
let n = 16usize;
let signal = deterministic_signal(n);
let mut array64 = ArrayD::zeros(IxDyn(&[n]));
let mut array32 = ArrayD::zeros(IxDyn(&[n]));
for (index, value) in signal.iter().enumerate() {
array64[[index]] = *value;
array32[[index]] = Complex32::new(value.re as f32, value.im as f32);
}
let Tensor::C64(expected) = Tensor::C64(array64).fft().expect("fft succeeds") else {
panic!("unexpected dtype");
};
let Tensor::C32(got) = Tensor::C32(array32).fft().expect("fft succeeds") else {
panic!("unexpected dtype");
};
for k in 0..n {
assert!((got[[k]].re as f64 - expected[[k]].re).abs() < 1e-4);
assert!((got[[k]].im as f64 - expected[[k]].im).abs() < 1e-4);
}
}
}