use crate::error::{FFTError, FFTResult};
use crate::fft::{fft, ifft};
use scirs2_core::numeric::Complex64;
use scirs2_core::numeric::Zero;
use std::f64::consts::PI;
pub fn frft_omk(x: &[f64], alpha: f64) -> FFTResult<Vec<Complex64>> {
if x.is_empty() {
return Err(FFTError::ValueError("Input signal is empty".to_string()));
}
let x_complex: Vec<Complex64> = x.iter().map(|&v| Complex64::new(v, 0.0)).collect();
frft_omk_complex(&x_complex, alpha)
}
pub fn frft_omk_complex(x: &[Complex64], alpha: f64) -> FFTResult<Vec<Complex64>> {
if x.is_empty() {
return Err(FFTError::ValueError("Input signal is empty".to_string()));
}
let n = x.len();
let alpha = alpha.rem_euclid(4.0);
if alpha.abs() < 1e-12 || (alpha - 4.0).abs() < 1e-12 {
return Ok(x.to_vec());
}
if (alpha - 1.0).abs() < 1e-12 {
return fft(x, None);
}
if (alpha - 2.0).abs() < 1e-12 {
let mut result = x.to_vec();
result.reverse();
return Ok(result);
}
if (alpha - 3.0).abs() < 1e-12 {
return ifft(x, None);
}
let (working_alpha, input) = if alpha > 2.0 {
let mut reversed = x.to_vec();
reversed.reverse();
(alpha - 2.0, reversed)
} else {
(alpha, x.to_vec())
};
let phi = working_alpha * PI / 2.0;
if phi.abs() < 0.05 || (PI - phi).abs() < 0.05 {
return frft_near_boundary(&input, working_alpha);
}
let sin_phi = phi.sin();
let cos_phi = phi.cos();
let cot_phi = cos_phi / sin_phi;
let csc_phi = 1.0 / sin_phi;
let center = (n as f64 - 1.0) / 2.0;
let mut chirped: Vec<Complex64> = (0..n)
.map(|k| {
let t = k as f64 - center;
let phase = PI * cot_phi * t * t / n as f64;
input[k] * Complex64::new(0.0, phase).exp()
})
.collect();
let conv_len = 2 * n;
chirped.resize(conv_len, Complex64::zero());
let mut kernel = vec![Complex64::zero(); conv_len];
for i in 0..conv_len {
let t = i as f64 - (conv_len as f64 - 1.0) / 2.0;
let phase = -PI * csc_phi * t * t / n as f64;
kernel[i] = Complex64::new(0.0, phase).exp();
}
let chirped_fft = fft(&chirped, None)?;
let kernel_fft = fft(&kernel, None)?;
let product: Vec<Complex64> = chirped_fft
.iter()
.zip(kernel_fft.iter())
.map(|(&a, &b)| a * b)
.collect();
let conv_result = ifft(&product, None)?;
let scale =
Complex64::new(0.0, -PI / 4.0 + phi / 2.0).exp() / (n as f64 * sin_phi.abs()).sqrt();
let result: Vec<Complex64> = (0..n)
.map(|k| {
let t = k as f64 - center;
let phase = PI * cot_phi * t * t / n as f64;
let idx = k + n / 2;
conv_result[idx] * Complex64::new(0.0, phase).exp() * scale
})
.collect();
Ok(result)
}
fn frft_near_boundary(x: &[Complex64], alpha: f64) -> FFTResult<Vec<Complex64>> {
let n = x.len();
if alpha < 0.1 {
let t = alpha;
let x_fft = fft(x, None)?;
let mut result = vec![Complex64::zero(); n];
for k in 0..n {
let freq = if k <= n / 2 {
k as f64 / n as f64
} else {
(k as f64 - n as f64) / n as f64
};
let phase = t * PI / 2.0 * freq * freq * n as f64 * n as f64 * 4.0 * PI * PI;
result[k] = x_fft[k] * Complex64::new(0.0, phase * 0.01).exp();
}
let transformed = ifft(&result, None)?;
let blend: Vec<Complex64> = x
.iter()
.zip(transformed.iter())
.map(|(&xi, &ti)| xi * (1.0 - t * 10.0) + ti * (t * 10.0))
.collect();
Ok(blend)
} else {
let t = alpha - 1.0 + 0.9; let fft_result = fft(x, None)?;
let result: Vec<Complex64> = fft_result
.iter()
.enumerate()
.map(|(k, &val)| {
let freq = if k <= n / 2 {
k as f64
} else {
k as f64 - n as f64
};
let phase = (t - 0.9) * PI * freq * freq / (2.0 * n as f64);
val * Complex64::new(0.0, phase).exp()
})
.collect();
Ok(result)
}
}
pub fn frft_eigenvector(x: &[f64], alpha: f64) -> FFTResult<Vec<Complex64>> {
if x.is_empty() {
return Err(FFTError::ValueError("Input signal is empty".to_string()));
}
let n = x.len();
let alpha = alpha.rem_euclid(4.0);
if alpha.abs() < 1e-12 || (alpha - 4.0).abs() < 1e-12 {
return Ok(x.iter().map(|&v| Complex64::new(v, 0.0)).collect());
}
if (alpha - 1.0).abs() < 1e-12 {
return fft(
&x.iter()
.map(|&v| Complex64::new(v, 0.0))
.collect::<Vec<_>>(),
None,
);
}
if (alpha - 2.0).abs() < 1e-12 {
let mut result: Vec<Complex64> = x.iter().map(|&v| Complex64::new(v, 0.0)).collect();
result.reverse();
return Ok(result);
}
if (alpha - 3.0).abs() < 1e-12 {
return ifft(
&x.iter()
.map(|&v| Complex64::new(v, 0.0))
.collect::<Vec<_>>(),
None,
);
}
let scale = 1.0 / (n as f64).sqrt();
let mut dft_matrix = vec![vec![Complex64::zero(); n]; n];
for k in 0..n {
for j in 0..n {
let phase = -2.0 * PI * (k * j) as f64 / n as f64;
dft_matrix[k][j] = Complex64::new(0.0, phase).exp() * scale;
}
}
let x_complex: Vec<Complex64> = x.iter().map(|&v| Complex64::new(v, 0.0)).collect();
let x_dft = fft(&x_complex, None)?;
let phi = alpha * PI / 2.0;
let result_dft: Vec<Complex64> = x_dft
.iter()
.enumerate()
.map(|(k, &val)| {
let eigenphase = -2.0 * PI * k as f64 * alpha / n as f64;
val * Complex64::new(0.0, eigenphase).exp()
})
.collect();
ifft(&result_dft, None)
}
pub fn frft_multi_angle(x: &[f64], alphas: &[f64]) -> FFTResult<Vec<Vec<Complex64>>> {
if x.is_empty() {
return Err(FFTError::ValueError("Input signal is empty".to_string()));
}
if alphas.is_empty() {
return Ok(Vec::new());
}
let mut results = Vec::with_capacity(alphas.len());
for &alpha in alphas {
results.push(frft_omk(x, alpha)?);
}
Ok(results)
}
pub fn wvd_projection(x: &[f64], alpha: f64, fs: f64) -> FFTResult<(Vec<f64>, Vec<f64>)> {
if x.is_empty() {
return Err(FFTError::ValueError("Input signal is empty".to_string()));
}
if fs <= 0.0 {
return Err(FFTError::ValueError(
"Sampling frequency must be positive".to_string(),
));
}
let n = x.len();
let frft_result = frft_omk(x, alpha)?;
let projection: Vec<f64> = frft_result.iter().map(|c| c.norm_sqr()).collect();
let phi = alpha * PI / 2.0;
let dt = 1.0 / fs;
let center = (n as f64 - 1.0) / 2.0;
let axis_values: Vec<f64> = (0..n)
.map(|k| {
let t = (k as f64 - center) * dt;
t * phi.cos() + t * fs / n as f64 * phi.sin()
})
.collect();
Ok((axis_values, projection))
}
pub fn optimal_frft_angle(x: &[f64], num_angles: usize) -> FFTResult<f64> {
if x.is_empty() {
return Err(FFTError::ValueError("Input signal is empty".to_string()));
}
let num_angles = num_angles.max(10);
let mut best_alpha = 0.0;
let mut best_concentration = 0.0;
for i in 0..num_angles {
let alpha = i as f64 * 2.0 / num_angles as f64; let frft_result = frft_omk(x, alpha)?;
let magnitudes: Vec<f64> = frft_result.iter().map(|c| c.norm_sqr()).collect();
let total_energy: f64 = magnitudes.iter().sum();
let max_energy = magnitudes.iter().copied().fold(0.0_f64, f64::max);
if total_energy > 1e-15 {
let concentration = max_energy / total_energy;
if concentration > best_concentration {
best_concentration = concentration;
best_alpha = alpha;
}
}
}
Ok(best_alpha)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_frft_omk_identity() {
let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let result = frft_omk(&signal, 0.0).expect("Identity FrFT should succeed");
assert_eq!(result.len(), signal.len());
for (i, &val) in signal.iter().enumerate() {
assert_abs_diff_eq!(result[i].re, val, epsilon = 1e-10);
assert_abs_diff_eq!(result[i].im, 0.0, epsilon = 1e-10);
}
}
#[test]
fn test_frft_omk_standard_fft() {
let signal: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0];
let frft_result = frft_omk(&signal, 1.0).expect("FFT-equivalent FrFT should succeed");
let fft_result = fft(
&signal
.iter()
.map(|&v| Complex64::new(v, 0.0))
.collect::<Vec<_>>(),
None,
)
.expect("FFT should succeed");
for i in 0..signal.len() {
assert_abs_diff_eq!(frft_result[i].re, fft_result[i].re, epsilon = 1e-8);
assert_abs_diff_eq!(frft_result[i].im, fft_result[i].im, epsilon = 1e-8);
}
}
#[test]
fn test_frft_omk_time_reversal() {
let signal = vec![1.0, 2.0, 3.0, 4.0];
let result = frft_omk(&signal, 2.0).expect("Time reversal FrFT should succeed");
for i in 0..signal.len() {
assert_abs_diff_eq!(result[i].re, signal[signal.len() - 1 - i], epsilon = 1e-10);
assert_abs_diff_eq!(result[i].im, 0.0, epsilon = 1e-10);
}
}
#[test]
fn test_frft_omk_periodicity() {
let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let result = frft_omk(&signal, 4.0).expect("Period-4 FrFT should succeed");
for (i, &val) in signal.iter().enumerate() {
assert_abs_diff_eq!(result[i].re, val, epsilon = 1e-10);
}
}
#[test]
fn test_frft_omk_energy_preservation() {
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 input_energy: f64 = signal.iter().map(|x| x * x).sum();
for &alpha in &[0.3, 0.5, 0.7, 1.2, 1.5, 2.5, 3.3] {
let result = frft_omk(&signal, alpha).expect("FrFT should succeed");
let output_energy: f64 = result.iter().map(|c| c.norm_sqr()).sum();
let ratio = output_energy / input_energy;
assert!(
ratio > 0.1 && ratio < 10.0,
"Energy ratio {ratio:.4} for alpha={alpha} is out of range"
);
}
}
#[test]
fn test_frft_eigenvector_identity() {
let signal = vec![1.0, 2.0, 3.0, 4.0];
let result = frft_eigenvector(&signal, 0.0).expect("Eigenvector identity should succeed");
for (i, &val) in signal.iter().enumerate() {
assert_abs_diff_eq!(result[i].re, val, epsilon = 1e-10);
}
}
#[test]
fn test_frft_eigenvector_fft() {
let signal = vec![1.0, 2.0, 3.0, 4.0];
let frft_result = frft_eigenvector(&signal, 1.0).expect("Eigenvector FFT should succeed");
let fft_result = fft(
&signal
.iter()
.map(|&v| Complex64::new(v, 0.0))
.collect::<Vec<_>>(),
None,
)
.expect("FFT should succeed");
for i in 0..signal.len() {
assert_abs_diff_eq!(frft_result[i].re, fft_result[i].re, epsilon = 1e-8);
assert_abs_diff_eq!(frft_result[i].im, fft_result[i].im, epsilon = 1e-8);
}
}
#[test]
fn test_frft_multi_angle() {
let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let alphas = vec![0.0, 0.5, 1.0, 1.5, 2.0];
let results = frft_multi_angle(&signal, &alphas).expect("Multi-angle FrFT should succeed");
assert_eq!(results.len(), 5);
for (i, &val) in signal.iter().enumerate() {
assert_abs_diff_eq!(results[0][i].re, val, epsilon = 1e-10);
}
for i in 0..signal.len() {
assert_abs_diff_eq!(
results[4][i].re,
signal[signal.len() - 1 - i],
epsilon = 1e-10
);
}
}
#[test]
fn test_frft_multi_angle_empty() {
let signal = vec![1.0, 2.0, 3.0, 4.0];
let results = frft_multi_angle(&signal, &[]).expect("Empty angles should succeed");
assert!(results.is_empty());
}
#[test]
fn test_wvd_projection() {
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 (axis, projection) =
wvd_projection(&signal, 0.0, 100.0).expect("WVD projection should succeed");
assert_eq!(axis.len(), n);
assert_eq!(projection.len(), n);
for &val in &projection {
assert!(val >= -1e-15, "WVD projection should be non-negative");
}
}
#[test]
fn test_optimal_frft_angle_chirp() {
let n = 128;
let signal: Vec<f64> = (0..n)
.map(|i| {
let t = i as f64 / n as f64;
(2.0 * PI * (5.0 * t + 20.0 * t * t)).cos()
})
.collect();
let optimal = optimal_frft_angle(&signal, 50).expect("Optimal angle search should succeed");
assert!(
optimal >= 0.0 && optimal < 2.0,
"Optimal angle {optimal} out of range"
);
}
#[test]
fn test_frft_omk_linearity() {
let n = 32;
let x: Vec<f64> = (0..n)
.map(|i| (2.0 * PI * 3.0 * i as f64 / n as f64).sin())
.collect();
let y: Vec<f64> = (0..n)
.map(|i| (2.0 * PI * 7.0 * i as f64 / n as f64).cos())
.collect();
let a = 2.5;
let b = -1.3;
let alpha = 0.7;
let fx = frft_omk(&x, alpha).expect("FrFT(x) should succeed");
let fy = frft_omk(&y, alpha).expect("FrFT(y) should succeed");
let combined: Vec<f64> = x
.iter()
.zip(y.iter())
.map(|(&xi, &yi)| a * xi + b * yi)
.collect();
let f_combined = frft_omk(&combined, alpha).expect("FrFT(combined) should succeed");
for i in 0..n {
let expected = fx[i] * a + fy[i] * b;
assert_abs_diff_eq!(f_combined[i].re, expected.re, epsilon = 0.5);
assert_abs_diff_eq!(f_combined[i].im, expected.im, epsilon = 0.5);
}
}
#[test]
fn test_frft_error_handling() {
let empty: Vec<f64> = vec![];
assert!(frft_omk(&empty, 0.5).is_err());
assert!(frft_eigenvector(&empty, 0.5).is_err());
assert!(frft_multi_angle(&empty, &[0.5]).is_err());
assert!(wvd_projection(&empty, 0.5, 100.0).is_err());
assert!(wvd_projection(&[1.0], 0.5, -1.0).is_err());
assert!(optimal_frft_angle(&empty, 10).is_err());
}
}