use crate::error::{FFTError, FFTResult};
use crate::fft::{fft, ifft};
use crate::frft_ozaktas;
use scirs2_core::numeric::Complex64;
use scirs2_core::numeric::{NumCast, Zero};
use std::f64::consts::PI;
use scirs2_core::simd_ops::{
simd_add_f32_ultra_vec, simd_cos_f32_ultra_vec, simd_div_f32_ultra_vec, simd_exp_f32_ultra_vec,
simd_fma_f32_ultra_vec, simd_mul_f32_ultra_vec, simd_pow_f32_ultra_vec, simd_sin_f32_ultra_vec,
simd_sub_f32_ultra_vec, PlatformCapabilities, SimdUnifiedOps,
};
#[allow(dead_code)]
pub fn frft<T>(x: &[T], alpha: f64, d: Option<f64>) -> FFTResult<Vec<Complex64>>
where
T: NumCast + Copy + std::fmt::Debug + 'static,
{
if x.is_empty() {
return Err(FFTError::ValueError("Input signal is empty".to_string()));
}
let x_complex: Vec<Complex64> = x
.iter()
.map(|&val| {
if let Some(val_f64) = NumCast::from(val) {
return Ok(Complex64::new(val_f64, 0.0));
}
if let Some(complex) = try_as_complex(val) {
return Ok(complex);
}
Err(FFTError::ValueError(format!(
"Could not convert {val:?} to numeric type"
)))
})
.collect::<Result<Vec<_>, _>>()?;
fn try_as_complex<U: 'static + Copy>(val: U) -> Option<Complex64> {
use std::any::Any;
if let Some(complex) = (&val as &dyn Any).downcast_ref::<Complex64>() {
return Some(*complex);
}
if let Some(complex32) =
(&val as &dyn Any).downcast_ref::<scirs2_core::numeric::Complex<f32>>()
{
return Some(Complex64::new(complex32.re as f64, complex32.im as f64));
}
None
}
frft_complex(&x_complex, alpha, d)
}
#[allow(dead_code)]
fn frft_decomposition(x: &[Complex64], alpha: f64, d: f64) -> FFTResult<Vec<Complex64>> {
let n = x.len();
let n_padded = 2 * n;
let cot_alpha = 1.0 / alpha.tan();
let scale = (1.0 - Complex64::i() * cot_alpha).sqrt() / (2.0 * PI).sqrt();
let mut padded = vec![Complex64::zero(); n_padded];
for i in 0..n {
padded[i + n / 2] = x[i];
}
let mut result = vec![Complex64::zero(); n_padded];
for i in 0..n_padded {
let t = (i as f64 - n_padded as f64 / 2.0) * d;
let chirp = Complex64::new(0.0, PI * t * t * cot_alpha).exp();
result[i] = padded[i] * chirp;
}
let fft_result = fft(&result, None)?;
let mut final_result = vec![Complex64::zero(); n];
for (i, result_val) in final_result.iter_mut().enumerate().take(n) {
let u = (i as f64 - n as f64 / 2.0) * 2.0 * PI / (n_padded as f64 * d);
let chirp = Complex64::new(0.0, PI * u * u * cot_alpha).exp();
let idx = (i + n_padded / 4) % n_padded;
*result_val = fft_result[idx] * chirp * scale * d;
}
Ok(final_result)
}
#[allow(dead_code)]
fn frft_near_special_case(x: &[Complex64], alpha: f64, _d: f64) -> FFTResult<Vec<Complex64>> {
let n = x.len();
let (alpha1, alpha2, t) = if alpha.abs() < 0.1 {
(0.0, 0.5 * PI, alpha / (0.5 * PI))
} else if (PI - alpha).abs() < 0.1 {
(0.5 * PI, PI, (alpha - 0.5 * PI) / (0.5 * PI))
} else {
let base = (alpha / PI).floor() * PI;
(base, base + 0.5 * PI, (alpha - base) / (0.5 * PI))
};
let f1 = if alpha1 == 0.0 {
x.to_vec() } else if alpha1 == PI {
let mut result = x.to_vec();
result.reverse();
result
} else if alpha1 == PI * 0.5 {
fft(x, None)? } else if alpha1 == PI * 1.5 {
ifft(x, None)? } else {
unreachable!()
};
let f2 = if alpha2 == PI * 0.5 {
fft(x, None)? } else if alpha2 == PI {
let mut result = x.to_vec();
result.reverse();
result
} else if alpha2 == PI * 1.5 {
ifft(x, None)? } else if alpha2 == PI * 2.0 {
x.to_vec() } else {
unreachable!()
};
let mut result = vec![Complex64::zero(); n];
for (i, result_val) in result.iter_mut().enumerate().take(n) {
*result_val = f1[i] * (1.0 - t) + f2[i] * t;
}
Ok(result)
}
#[allow(dead_code)]
pub fn frft_complex(x: &[Complex64], alpha: f64, d: Option<f64>) -> FFTResult<Vec<Complex64>> {
if x.is_empty() {
return Err(FFTError::ValueError("Input signal is empty".to_string()));
}
let alpha = alpha.rem_euclid(4.0);
let d = d.unwrap_or(1.0);
if d <= 0.0 {
return Err(FFTError::ValueError(
"Sampling interval must be positive".to_string(),
));
}
if (alpha - 0.0).abs() < 1e-10 || (alpha - 4.0).abs() < 1e-10 {
return Ok(x.to_vec());
} else if (alpha - 1.0).abs() < 1e-10 {
return fft(x, None);
} else if (alpha - 2.0).abs() < 1e-10 {
let mut result = x.to_vec();
result.reverse();
return Ok(result);
} else if (alpha - 3.0).abs() < 1e-10 {
return ifft(x, None);
}
let alpha = alpha * PI / 2.0;
if alpha.abs() < 0.1 || (PI - alpha).abs() < 0.1 || (2.0 * PI - alpha).abs() < 0.1 {
return frft_near_special_case(x, alpha, d);
}
frft_decomposition(x, alpha, d)
}
#[allow(dead_code)]
pub fn frft_stable<T>(x: &[T], alpha: f64) -> FFTResult<Vec<Complex64>>
where
T: Copy + Into<f64>,
{
frft_ozaktas::frft_ozaktas(x, alpha)
}
#[allow(dead_code)]
pub fn frft_dft<T>(x: &[T], alpha: f64) -> FFTResult<Vec<Complex64>>
where
T: Copy + Into<f64>,
{
crate::frft_dft::frft_dft(x, alpha)
}
#[allow(dead_code)]
pub fn frft_bandwidth_saturated_simd(
x: &[Complex64],
alpha: f64,
d: Option<f64>,
) -> FFTResult<Vec<Complex64>> {
use scirs2_core::simd_ops::{PlatformCapabilities, SimdUnifiedOps};
if x.is_empty() {
return Err(FFTError::ValueError("Input signal is empty".to_string()));
}
let n = x.len();
let d = d.unwrap_or(1.0);
let alpha = alpha.rem_euclid(4.0);
if (alpha - 0.0).abs() < 1e-10 || (alpha - 4.0).abs() < 1e-10 {
return Ok(x.to_vec());
} else if (alpha - 1.0).abs() < 1e-10 {
return fft(x, None);
} else if (alpha - 2.0).abs() < 1e-10 {
let mut result = x.to_vec();
result.reverse();
return Ok(result);
} else if (alpha - 3.0).abs() < 1e-10 {
return ifft(x, None);
}
let caps = PlatformCapabilities::detect();
if n >= 1024 && caps.has_avx512() {
frft_bandwidth_saturated_avx512(x, alpha, d)
} else if n >= 256 && caps.has_avx2() {
frft_bandwidth_saturated_avx2(x, alpha, d)
} else {
frft_complex(x, alpha * 2.0 / PI, Some(d))
}
}
#[allow(dead_code)]
fn frft_bandwidth_saturated_avx512(
x: &[Complex64],
alpha: f64,
d: f64,
) -> FFTResult<Vec<Complex64>> {
let n = x.len();
let n_padded = 2 * n;
let alpha_rad = alpha * PI / 2.0;
let cot_alpha = 1.0 / alpha_rad.tan();
let scale = (1.0 - Complex64::i() * cot_alpha).sqrt() / (2.0 * PI).sqrt();
let mut padded = vec![Complex64::zero(); n_padded];
let mut chirp_values = vec![Complex64::zero(); n_padded];
let mut result = vec![Complex64::zero(); n_padded];
for i in 0..n {
padded[i + n / 2] = x[i];
}
let chunk_size = 16; for chunk_start in (0..n_padded).step_by(chunk_size) {
let chunk_end = (chunk_start + chunk_size).min(n_padded);
let chunk_len = chunk_end - chunk_start;
let mut t_values = vec![0.0f32; chunk_len];
let mut t_squared = vec![0.0f32; chunk_len];
for (i, t_val) in t_values.iter_mut().enumerate() {
let idx = chunk_start + i;
*t_val = ((idx as f64 - n_padded as f64 / 2.0) * d) as f32;
}
simd_mul_f32_ultra_vec(&t_values, &t_values, &mut t_squared);
let mut phase_values = vec![0.0f32; chunk_len];
let cot_pi = (cot_alpha * PI) as f32;
let cot_pi_vec = vec![cot_pi; chunk_len];
simd_fma_f32_ultra_vec(
&t_squared,
&cot_pi_vec,
&vec![0.0f32; chunk_len],
&mut phase_values,
);
for (i, &phase) in phase_values.iter().enumerate() {
let idx = chunk_start + i;
chirp_values[idx] = Complex64::new(0.0, phase as f64).exp();
}
}
for chunk_start in (0..n_padded).step_by(chunk_size) {
let chunk_end = (chunk_start + chunk_size).min(n_padded);
for i in chunk_start..chunk_end {
result[i] = padded[i] * chirp_values[i];
}
}
let fft_result = fft(&result, None)?;
let mut final_result = vec![Complex64::zero(); n];
for chunk_start in (0..n).step_by(chunk_size) {
let chunk_end = (chunk_start + chunk_size).min(n);
let chunk_len = chunk_end - chunk_start;
let mut u_values = vec![0.0f32; chunk_len];
let mut u_squared = vec![0.0f32; chunk_len];
for (i, u_val) in u_values.iter_mut().enumerate() {
let idx = chunk_start + i;
*u_val = ((idx as f64 - n as f64 / 2.0) * 2.0 * PI / (n_padded as f64 * d)) as f32;
}
simd_mul_f32_ultra_vec(&u_values, &u_values, &mut u_squared);
let mut phase_values = vec![0.0f32; chunk_len];
let cot_pi = (cot_alpha * PI) as f32;
let cot_pi_vec = vec![cot_pi; chunk_len];
simd_fma_f32_ultra_vec(
&u_squared,
&cot_pi_vec,
&vec![0.0f32; chunk_len],
&mut phase_values,
);
for (i, &phase) in phase_values.iter().enumerate() {
let idx = chunk_start + i;
let fft_idx = (idx + n_padded / 4) % n_padded;
let chirp = Complex64::new(0.0, phase as f64).exp();
final_result[idx] = fft_result[fft_idx] * chirp * scale * d;
}
}
Ok(final_result)
}
#[allow(dead_code)]
fn frft_bandwidth_saturated_avx2(x: &[Complex64], alpha: f64, d: f64) -> FFTResult<Vec<Complex64>> {
let n = x.len();
let n_padded = 2 * n;
let alpha_rad = alpha * PI / 2.0;
let cot_alpha = 1.0 / alpha_rad.tan();
let scale = (1.0 - Complex64::i() * cot_alpha).sqrt() / (2.0 * PI).sqrt();
let mut padded = vec![Complex64::zero(); n_padded];
let mut result = vec![Complex64::zero(); n_padded];
for i in 0..n {
padded[i + n / 2] = x[i];
}
let chunk_size = 8;
for chunk_start in (0..n_padded).step_by(chunk_size) {
let chunk_end = (chunk_start + chunk_size).min(n_padded);
let chunk_len = chunk_end - chunk_start;
let mut t_values = vec![0.0f32; chunk_len];
let mut chirp_real = vec![0.0f32; chunk_len];
let mut chirp_imag = vec![0.0f32; chunk_len];
for (i, t_val) in t_values.iter_mut().enumerate() {
let idx = chunk_start + i;
*t_val = ((idx as f64 - n_padded as f64 / 2.0) * d) as f32;
}
let mut t_squared = vec![0.0f32; chunk_len];
simd_mul_f32_ultra_vec(&t_values, &t_values, &mut t_squared);
let mut phases = vec![0.0f32; chunk_len];
let cot_pi = (cot_alpha * PI) as f32;
let cot_pi_vec = vec![cot_pi; chunk_len];
simd_fma_f32_ultra_vec(
&t_squared,
&cot_pi_vec,
&vec![0.0f32; chunk_len],
&mut phases,
);
for (i, &phase) in phases.iter().enumerate() {
let idx = chunk_start + i;
let chirp = Complex64::new(0.0, phase as f64).exp();
result[idx] = padded[idx] * chirp;
}
}
let fft_result = fft(&result, None)?;
let mut final_result = vec![Complex64::zero(); n];
for chunk_start in (0..n).step_by(chunk_size) {
let chunk_end = (chunk_start + chunk_size).min(n);
let chunk_len = chunk_end - chunk_start;
let mut u_values = vec![0.0f32; chunk_len];
for (i, u_val) in u_values.iter_mut().enumerate() {
let idx = chunk_start + i;
*u_val = ((idx as f64 - n as f64 / 2.0) * 2.0 * PI / (n_padded as f64 * d)) as f32;
}
let mut u_squared = vec![0.0f32; chunk_len];
simd_mul_f32_ultra_vec(&u_values, &u_values, &mut u_squared);
let mut phases = vec![0.0f32; chunk_len];
let cot_pi = (cot_alpha * PI) as f32;
let cot_pi_vec = vec![cot_pi; chunk_len];
simd_fma_f32_ultra_vec(
&u_squared,
&cot_pi_vec,
&vec![0.0f32; chunk_len],
&mut phases,
);
for (i, &phase) in phases.iter().enumerate() {
let idx = chunk_start + i;
let fft_idx = (idx + n_padded / 4) % n_padded;
let chirp = Complex64::new(0.0, phase as f64).exp();
final_result[idx] = fft_result[fft_idx] * chirp * scale * d;
}
}
Ok(final_result)
}
#[allow(dead_code)]
pub fn frft_near_special_case_bandwidth_saturated_simd(
x: &[Complex64],
alpha: f64,
) -> FFTResult<Vec<Complex64>> {
use scirs2_core::simd_ops::SimdUnifiedOps;
let n = x.len();
let (alpha1, alpha2, t) = if alpha.abs() < 0.1 {
(0.0, 0.5 * PI, alpha / (0.5 * PI))
} else if (PI - alpha).abs() < 0.1 {
(0.5 * PI, PI, (alpha - 0.5 * PI) / (0.5 * PI))
} else {
let base = (alpha / PI).floor() * PI;
(base, base + 0.5 * PI, (alpha - base) / (0.5 * PI))
};
let f1 = if alpha1 == 0.0 {
x.to_vec()
} else if alpha1 == PI {
let mut result = x.to_vec();
result.reverse();
result
} else if alpha1 == PI * 0.5 {
fft(x, None)?
} else if alpha1 == PI * 1.5 {
ifft(x, None)?
} else {
unreachable!()
};
let f2 = if alpha2 == PI * 0.5 {
fft(x, None)?
} else if alpha2 == PI {
let mut result = x.to_vec();
result.reverse();
result
} else if alpha2 == PI * 1.5 {
ifft(x, None)?
} else if alpha2 == PI * 2.0 {
x.to_vec()
} else {
unreachable!()
};
let mut result = vec![Complex64::zero(); n];
let chunk_size = 8;
for chunk_start in (0..n).step_by(chunk_size) {
let chunk_end = (chunk_start + chunk_size).min(n);
let chunk_len = chunk_end - chunk_start;
let mut f1_real = vec![0.0f32; chunk_len];
let mut f1_imag = vec![0.0f32; chunk_len];
let mut f2_real = vec![0.0f32; chunk_len];
let mut f2_imag = vec![0.0f32; chunk_len];
for (i, idx) in (chunk_start..chunk_end).enumerate() {
f1_real[i] = f1[idx].re as f32;
f1_imag[i] = f1[idx].im as f32;
f2_real[i] = f2[idx].re as f32;
f2_imag[i] = f2[idx].im as f32;
}
let t_f32 = t as f32;
let one_minus_t = 1.0 - t_f32;
let t_vec = vec![t_f32; chunk_len];
let one_minus_t_vec = vec![one_minus_t; chunk_len];
let mut interp_real = vec![0.0f32; chunk_len];
let mut interp_imag = vec![0.0f32; chunk_len];
let mut temp_real = vec![0.0f32; chunk_len];
let mut temp_imag = vec![0.0f32; chunk_len];
simd_mul_f32_ultra_vec(&f1_real, &one_minus_t_vec, &mut temp_real);
simd_fma_f32_ultra_vec(&f2_real, &t_vec, &temp_real, &mut interp_real);
simd_mul_f32_ultra_vec(&f1_imag, &one_minus_t_vec, &mut temp_imag);
simd_fma_f32_ultra_vec(&f2_imag, &t_vec, &temp_imag, &mut interp_imag);
for (i, idx) in (chunk_start..chunk_end).enumerate() {
result[idx] = Complex64::new(interp_real[i] as f64, interp_imag[i] as f64);
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn frft_stable_bandwidth_saturated_simd<T>(x: &[T], alpha: f64) -> FFTResult<Vec<Complex64>>
where
T: Copy + Into<f64>,
{
let n = x.len();
let mut x_complex = vec![Complex64::zero(); n];
let chunk_size = 16;
for chunk_start in (0..n).step_by(chunk_size) {
let chunk_end = (chunk_start + chunk_size).min(n);
for i in chunk_start..chunk_end {
x_complex[i] = Complex64::new(x[i].into(), 0.0);
}
}
use scirs2_core::simd_ops::PlatformCapabilities;
let caps = PlatformCapabilities::detect();
if n >= 512 && (caps.has_avx2() || caps.has_avx512()) {
frft_bandwidth_saturated_simd(&x_complex, alpha, None)
} else {
frft_ozaktas::frft_ozaktas(x, alpha)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_frft_identity() {
let signal = vec![1.0, 2.0, 3.0, 4.0];
let result = frft(&signal, 0.0, None).expect("Operation failed");
for (i, val) in signal.iter().enumerate() {
assert_relative_eq!(result[i].re, *val, epsilon = 1e-10);
assert_relative_eq!(result[i].im, 0.0, epsilon = 1e-10);
}
}
#[test]
fn test_frft_fourier() {
let signal = vec![1.0, 2.0, 3.0, 4.0];
let frft_result = frft(&signal, 1.0, None).expect("Operation failed");
let fft_result = fft(&signal, None).expect("Operation failed");
for i in 0..signal.len() {
assert_relative_eq!(frft_result[i].re, fft_result[i].re, epsilon = 1e-10);
assert_relative_eq!(frft_result[i].im, fft_result[i].im, epsilon = 1e-10);
}
}
#[test]
fn test_frft_time_reversal() {
let signal = vec![1.0, 2.0, 3.0, 4.0];
let result = frft(&signal, 2.0, None).expect("Operation failed");
for i in 0..signal.len() {
assert_relative_eq!(result[i].re, signal[signal.len() - 1 - i], epsilon = 1e-10);
assert_relative_eq!(result[i].im, 0.0, epsilon = 1e-10);
}
}
#[test]
fn test_frft_inverse_fourier() {
let signal_vec = vec![
Complex64::new(1.0, 1.0),
Complex64::new(2.0, -1.0),
Complex64::new(3.0, 1.0),
Complex64::new(4.0, -1.0),
];
let frft_result = frft_complex(&signal_vec, 3.0, None).expect("Operation failed");
let ifft_result = ifft(&signal_vec, None).expect("Operation failed");
for i in 0..signal_vec.len() {
assert_relative_eq!(frft_result[i].re, ifft_result[i].re, epsilon = 1e-10);
assert_relative_eq!(frft_result[i].im, ifft_result[i].im, epsilon = 1e-10);
}
}
#[test]
fn test_frft_additivity() {
let n = 64;
let signal: Vec<f64> = (0..n)
.map(|i| (2.0 * PI * 5.0 * i as f64 / n as f64).sin())
.collect();
let alpha1 = 0.25;
let alpha2 = 0.35;
let signal_complex: Vec<Complex64> =
signal.iter().map(|&x| Complex64::new(x, 0.0)).collect();
let orig_result1 =
frft_complex(&signal_complex, alpha1 + alpha2, None).expect("Operation failed");
let orig_temp = frft_complex(&signal_complex, alpha2, None).expect("Operation failed");
let orig_result2 = frft_complex(&orig_temp, alpha1, None).expect("Operation failed");
let orig_energy1: f64 = orig_result1.iter().map(|c| c.norm_sqr()).sum();
let orig_energy2: f64 = orig_result2.iter().map(|c| c.norm_sqr()).sum();
let orig_energy_ratio = orig_energy1 / orig_energy2;
println!("Original implementation energy ratio: {orig_energy_ratio:.6}");
let ozaktas_result1 = frft_stable(&signal, alpha1 + alpha2).expect("Operation failed");
let ozaktas_temp = frft_stable(&signal, alpha2).expect("Operation failed");
let real_temp: Vec<f64> = ozaktas_temp.iter().map(|c| c.re).collect();
let ozaktas_result2 = frft_stable(&real_temp, alpha1).expect("Operation failed");
let ozaktas_energy1: f64 = ozaktas_result1.iter().map(|c| c.norm_sqr()).sum();
let ozaktas_energy2: f64 = ozaktas_result2.iter().map(|c| c.norm_sqr()).sum();
let ozaktas_energy_ratio = ozaktas_energy1 / ozaktas_energy2;
println!("Ozaktas-Kutay implementation energy ratio: {ozaktas_energy_ratio:.6}");
assert!(
ozaktas_energy_ratio > 0.05 && ozaktas_energy_ratio < 20.0,
"Ozaktas-Kutay energy ratio too far from 1: {ozaktas_energy_ratio}"
);
let dft_result1 = frft_dft(&signal, alpha1 + alpha2).expect("Operation failed");
let dft_temp = frft_dft(&signal, alpha2).expect("Operation failed");
let dft_real_temp: Vec<f64> = dft_temp.iter().map(|c| c.re).collect();
let dft_result2 = frft_dft(&dft_real_temp, alpha1).expect("Operation failed");
let dft_energy1: f64 = dft_result1.iter().map(|c| c.norm_sqr()).sum();
let dft_energy2: f64 = dft_result2.iter().map(|c| c.norm_sqr()).sum();
let dft_energy_ratio = dft_energy1 / dft_energy2;
println!("DFT-based implementation energy ratio: {dft_energy_ratio:.6}");
assert!(
dft_energy_ratio > 0.01 && dft_energy_ratio < 100.0,
"DFT-based energy ratio is completely unreasonable: {dft_energy_ratio}"
);
}
#[test]
fn test_frft_linearity() {
let n = 64;
let signal1: Vec<f64> = (0..n)
.map(|i| (2.0 * PI * 5.0 * i as f64 / n as f64).sin())
.collect();
let signal2: Vec<f64> = (0..n)
.map(|i| (2.0 * PI * 10.0 * i as f64 / n as f64).sin())
.collect();
let alpha = 0.5;
let a = 2.0;
let b = 3.0;
let signal1_complex: Vec<Complex64> =
signal1.iter().map(|&x| Complex64::new(x, 0.0)).collect();
let signal2_complex: Vec<Complex64> =
signal2.iter().map(|&x| Complex64::new(x, 0.0)).collect();
let frft1 = frft_complex(&signal1_complex, alpha, None).expect("Operation failed");
let frft2 = frft_complex(&signal2_complex, alpha, None).expect("Operation failed");
let mut combined1 = vec![Complex64::zero(); n];
for i in 0..n {
combined1[i] = a * frft1[i] + b * frft2[i];
}
let mut combined_signal = vec![Complex64::zero(); n];
for i in 0..n {
combined_signal[i] = Complex64::new(a * signal1[i] + b * signal2[i], 0.0);
}
let combined2 = frft_complex(&combined_signal, alpha, None).expect("Operation failed");
let mut max_relative_error: f64 = 0.0;
for i in n / 4..3 * n / 4 {
let norm1 = combined1[i].norm();
let norm2 = combined2[i].norm();
if norm1 > 1e-10 {
let relative_error = ((norm1 - norm2) / norm1).abs();
max_relative_error = max_relative_error.max(relative_error);
}
}
assert!(
max_relative_error < 0.2,
"Max relative error: {max_relative_error}"
);
}
#[test]
fn test_frft_complex_input() {
let n = 64;
let signal_complex: Vec<Complex64> = (0..n)
.map(|i| {
let t = i as f64 / n as f64;
Complex64::new((2.0 * PI * 5.0 * t).cos(), (2.0 * PI * 5.0 * t).sin())
})
.collect();
let result = frft_complex(&signal_complex, 0.5, None).expect("Operation failed");
assert_eq!(result.len(), n);
let result2 = frft_complex(&result, 0.5, None).expect("Operation failed");
assert_eq!(result2.len(), n);
let result4 = frft_complex(&signal_complex, 4.0, None).expect("Operation failed");
for i in 0..n {
assert_relative_eq!(result4[i].re, signal_complex[i].re, epsilon = 1e-10);
assert_relative_eq!(result4[i].im, signal_complex[i].im, epsilon = 1e-10);
}
}
}