use crate::error::{FFTError, FFTResult};
use crate::fft::{fft, ifft};
use scirs2_core::numeric::{Complex64, Zero};
use std::f64::consts::PI;
fn next_pow2(n: usize) -> usize {
if n <= 1 {
return 1;
}
n.next_power_of_two()
}
fn chirp_sequence(n: usize, sign: f64) -> Vec<Complex64> {
let two_n = 2 * n;
(0..n)
.map(|k| {
let k_sq_mod = (k * k) % two_n;
let phase = sign * PI * k_sq_mod as f64 / n as f64;
Complex64::new(phase.cos(), phase.sin())
})
.collect()
}
pub fn bluestein_fft(signal: &[Complex64]) -> FFTResult<Vec<Complex64>> {
let n = signal.len();
if n == 0 {
return Err(FFTError::ValueError(
"bluestein_fft: input signal is empty".to_string(),
));
}
if n == 1 {
return Ok(signal.to_vec());
}
let m = next_pow2(2 * n - 1);
let chirp = chirp_sequence(n, -1.0);
let mut a = vec![Complex64::zero(); m];
for k in 0..n {
a[k] = signal[k] * chirp[k];
}
let mut b = vec![Complex64::zero(); m];
let chirp_conj: Vec<Complex64> = chirp.iter().map(|c| c.conj()).collect();
b[0] = chirp_conj[0];
for k in 1..n {
b[k] = chirp_conj[k];
b[m - k] = chirp_conj[k];
}
let fa = fft(&a, None)?;
let fb = fft(&b, None)?;
let fc: Vec<Complex64> = fa.iter().zip(fb.iter()).map(|(&ai, &bi)| ai * bi).collect();
let c = ifft(&fc, None)?;
let result: Vec<Complex64> = (0..n).map(|k| chirp[k] * c[k]).collect();
Ok(result)
}
pub fn bluestein_real(signal: &[f64]) -> FFTResult<Vec<Complex64>> {
if signal.is_empty() {
return Err(FFTError::ValueError(
"bluestein_real: input signal is empty".to_string(),
));
}
let complex_signal: Vec<Complex64> = signal.iter().map(|&x| Complex64::new(x, 0.0)).collect();
bluestein_fft(&complex_signal)
}
pub fn bluestein_ifft(spectrum: &[Complex64]) -> FFTResult<Vec<Complex64>> {
let n = spectrum.len();
if n == 0 {
return Err(FFTError::ValueError(
"bluestein_ifft: input is empty".to_string(),
));
}
if n == 1 {
return Ok(spectrum.to_vec());
}
let m = next_pow2(2 * n - 1);
let chirp = chirp_sequence(n, 1.0);
let mut a = vec![Complex64::zero(); m];
for k in 0..n {
a[k] = spectrum[k] * chirp[k];
}
let chirp_conj: Vec<Complex64> = chirp.iter().map(|c| c.conj()).collect();
let mut b = vec![Complex64::zero(); m];
b[0] = chirp_conj[0];
for k in 1..n {
b[k] = chirp_conj[k];
b[m - k] = chirp_conj[k];
}
let fa = fft(&a, None)?;
let fb = fft(&b, None)?;
let fc: Vec<Complex64> = fa.iter().zip(fb.iter()).map(|(&ai, &bi)| ai * bi).collect();
let c = ifft(&fc, None)?;
let inv_n = 1.0 / n as f64;
let result: Vec<Complex64> = (0..n).map(|k| chirp[k] * c[k] * inv_n).collect();
Ok(result)
}
fn is_prime(n: usize) -> bool {
if n < 2 {
return false;
}
if n == 2 {
return true;
}
if n % 2 == 0 {
return false;
}
let mut d = 3_usize;
while d * d <= n {
if n % d == 0 {
return false;
}
d += 2;
}
true
}
pub fn prime_length_fft(signal: &[Complex64]) -> FFTResult<Vec<Complex64>> {
let n = signal.len();
if n == 0 {
return Err(FFTError::ValueError(
"prime_length_fft: input signal is empty".to_string(),
));
}
let _ = is_prime(n); bluestein_fft(signal)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fft::fft as reference_fft;
use approx::assert_relative_eq;
use std::f64::consts::PI;
fn assert_complex_close(a: &[Complex64], b: &[Complex64], tol: f64) {
assert_eq!(a.len(), b.len(), "length mismatch");
for (i, (ai, bi)) in a.iter().zip(b.iter()).enumerate() {
let diff = (ai - bi).norm();
assert!(diff < tol, "index {i}: |{ai} - {bi}| = {diff} ≥ {tol}");
}
}
#[test]
fn test_bluestein_pow2_matches_reference() {
for &n in &[1_usize, 2, 4, 8, 16] {
let signal: Vec<Complex64> = (0..n)
.map(|k| Complex64::new(k as f64, -(k as f64)))
.collect();
let blue = bluestein_fft(&signal).expect("bluestein_fft");
let ref_ = reference_fft(&signal, None).expect("reference fft");
assert_complex_close(&blue, &ref_, 1e-9);
}
}
#[test]
fn test_bluestein_prime_length_5() {
let signal: Vec<Complex64> = std::iter::once(Complex64::new(1.0, 0.0))
.chain(std::iter::repeat_n(Complex64::zero(), 4))
.collect();
let spectrum = bluestein_fft(&signal).expect("bluestein_fft");
for val in &spectrum {
assert_relative_eq!(val.re, 1.0, epsilon = 1e-10);
assert_relative_eq!(val.im, 0.0, epsilon = 1e-10);
}
}
#[test]
fn test_bluestein_prime_length_7() {
let n = 7;
let signal: Vec<Complex64> = (0..n).map(|k| Complex64::new(k as f64, 0.0)).collect();
let blue = bluestein_fft(&signal).expect("bluestein_fft");
let brute: Vec<Complex64> = (0..n)
.map(|j| {
signal
.iter()
.enumerate()
.fold(Complex64::zero(), |acc, (k, &xk)| {
let phase = -2.0 * PI * j as f64 * k as f64 / n as f64;
acc + xk * Complex64::new(phase.cos(), phase.sin())
})
})
.collect();
assert_complex_close(&blue, &brute, 1e-9);
}
#[test]
fn test_bluestein_prime_length_11() {
let n = 11;
let signal: Vec<Complex64> = (0..n)
.map(|k| {
let t = k as f64 / n as f64;
Complex64::new((2.0 * PI * t).cos(), (2.0 * PI * t).sin())
})
.collect();
let blue = bluestein_fft(&signal).expect("bluestein");
let brute: Vec<Complex64> = (0..n)
.map(|j| {
signal
.iter()
.enumerate()
.fold(Complex64::zero(), |acc, (k, &xk)| {
let phase = -2.0 * PI * j as f64 * k as f64 / n as f64;
acc + xk * Complex64::new(phase.cos(), phase.sin())
})
})
.collect();
assert_complex_close(&blue, &brute, 1e-8);
}
#[test]
fn test_bluestein_length_6() {
let n = 6;
let signal: Vec<Complex64> = (0..n)
.map(|k| Complex64::new((k as f64).sin(), 0.0))
.collect();
let blue = bluestein_fft(&signal).expect("bluestein");
let brute: Vec<Complex64> = (0..n)
.map(|j| {
signal
.iter()
.enumerate()
.fold(Complex64::zero(), |acc, (k, &xk)| {
let phase = -2.0 * PI * j as f64 * k as f64 / n as f64;
acc + xk * Complex64::new(phase.cos(), phase.sin())
})
})
.collect();
assert_complex_close(&blue, &brute, 1e-9);
}
#[test]
fn test_bluestein_real_length_5() {
let n = 5;
let signal: Vec<f64> = (0..n).map(|k| k as f64).collect();
let blue_real = bluestein_real(&signal).expect("bluestein_real");
let blue_complex: Vec<Complex64> = signal.iter().map(|&x| Complex64::new(x, 0.0)).collect();
let blue_ref = bluestein_fft(&blue_complex).expect("bluestein_fft");
assert_complex_close(&blue_real, &blue_ref, 1e-12);
}
#[test]
fn test_bluestein_ifft_roundtrip_prime() {
let n = 13;
let original: Vec<Complex64> = (0..n)
.map(|k| Complex64::new(k as f64, -(k as f64) / 2.0))
.collect();
let spectrum = bluestein_fft(&original).expect("fft");
let recovered = bluestein_ifft(&spectrum).expect("ifft");
assert_complex_close(&original, &recovered, 1e-9);
}
#[test]
fn test_bluestein_ifft_roundtrip_pow2() {
let n = 8;
let original: Vec<Complex64> = (0..n)
.map(|k| Complex64::new((k as f64).cos(), (k as f64).sin()))
.collect();
let spectrum = bluestein_fft(&original).expect("fft");
let recovered = bluestein_ifft(&spectrum).expect("ifft");
assert_complex_close(&original, &recovered, 1e-9);
}
#[test]
fn test_bluestein_parseval() {
let n = 17; let signal: Vec<Complex64> = (0..n)
.map(|k| Complex64::new((k as f64 / n as f64).sin(), 0.0))
.collect();
let spectrum = bluestein_fft(&signal).expect("bluestein");
let input_energy: f64 = signal.iter().map(|c| c.norm_sqr()).sum();
let output_energy: f64 = spectrum.iter().map(|c| c.norm_sqr()).sum::<f64>() / n as f64;
assert_relative_eq!(
input_energy,
output_energy,
epsilon = 1e-9 * input_energy.max(1.0)
);
}
#[test]
fn test_bluestein_empty_error() {
assert!(bluestein_fft(&[]).is_err());
assert!(bluestein_real(&[]).is_err());
assert!(bluestein_ifft(&[]).is_err());
}
#[test]
fn test_bluestein_single_element() {
let sig = vec![Complex64::new(3.25, 2.72)];
let out = bluestein_fft(&sig).expect("fft");
assert_eq!(out.len(), 1);
assert_relative_eq!(out[0].re, sig[0].re, epsilon = 1e-12);
assert_relative_eq!(out[0].im, sig[0].im, epsilon = 1e-12);
}
#[test]
fn test_prime_length_fft() {
let n = 19;
let signal: Vec<Complex64> = (0..n).map(|k| Complex64::new(k as f64, 0.0)).collect();
let spec = prime_length_fft(&signal).expect("prime_length_fft");
assert_eq!(spec.len(), n);
}
}