use num_complex::Complex32;
const W3_RE: f32 = -0.5;
const W3_IM: f32 = -0.866_025_4;
#[inline]
pub fn fft_3(x: &mut [Complex32; 3]) {
let x0 = x[0];
let x1 = x[1];
let x2 = x[2];
x[0] = Complex32::new(x0.re + x1.re + x2.re, x0.im + x1.im + x2.im);
let sum_re = x1.re + x2.re;
let sum_im = x1.im + x2.im;
let diff_re = x1.re - x2.re;
let diff_im = x1.im - x2.im;
let t_re = W3_RE * sum_re;
let t_im = W3_RE * sum_im;
let r_re = W3_IM * diff_im; let r_im = -W3_IM * diff_re;
let _ = (r_re, r_im, t_re, t_im); let x1_x2_sum_re = W3_RE * sum_re + W3_IM * (-diff_im);
let x1_x2_sum_im = W3_RE * sum_im + W3_IM * diff_re;
x[1] = Complex32::new(x0.re + x1_x2_sum_re, x0.im + x1_x2_sum_im);
let x1_x2_sum_re_2 = W3_RE * sum_re + W3_IM * diff_im;
let x1_x2_sum_im_2 = W3_RE * sum_im - W3_IM * diff_re;
x[2] = Complex32::new(x0.re + x1_x2_sum_re_2, x0.im + x1_x2_sum_im_2);
}
const INPUT_MAP_15: [usize; 15] = {
let mut m = [0usize; 15];
let mut n1 = 0;
while n1 < 3 {
let mut n2 = 0;
while n2 < 5 {
m[n1 * 5 + n2] = (5 * n1 + 3 * n2) % 15;
n2 += 1;
}
n1 += 1;
}
m
};
const OUTPUT_MAP_15: [usize; 15] = {
let mut m = [0usize; 15];
let mut k1 = 0;
while k1 < 3 {
let mut k2 = 0;
while k2 < 5 {
m[k1 * 5 + k2] = (10 * k1 + 6 * k2) % 15;
k2 += 1;
}
k1 += 1;
}
m
};
pub fn fft_15(x: &mut [Complex32; 15]) {
let mut m = [[Complex32::new(0.0, 0.0); 5]; 3];
for n1 in 0..3 {
for n2 in 0..5 {
m[n1][n2] = x[INPUT_MAP_15[n1 * 5 + n2]];
}
}
for row in m.iter_mut() {
fft_5(row);
}
for k2 in 0..5 {
let mut col = [m[0][k2], m[1][k2], m[2][k2]];
fft_3(&mut col);
m[0][k2] = col[0];
m[1][k2] = col[1];
m[2][k2] = col[2];
}
for k1 in 0..3 {
for k2 in 0..5 {
x[OUTPUT_MAP_15[k1 * 5 + k2]] = m[k1][k2];
}
}
}
#[cfg(test)]
mod tests_15 {
use super::*;
fn rustfft_15(input: &[Complex32; 15]) -> [Complex32; 15] {
use rustfft::FftPlanner;
let mut planner = FftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(15);
let mut buf: Vec<Complex32> = input.to_vec();
fft.process(&mut buf);
let mut out = [Complex32::new(0.0, 0.0); 15];
out.copy_from_slice(&buf);
out
}
fn close(a: Complex32, b: Complex32, eps: f32) -> bool {
(a.re - b.re).abs() < eps && (a.im - b.im).abs() < eps
}
#[test]
fn fft15_impulse() {
let mut x = [Complex32::new(0.0, 0.0); 15];
x[0] = Complex32::new(1.0, 0.0);
let expected = rustfft_15(&x);
fft_15(&mut x);
for k in 0..15 {
assert!(close(x[k], expected[k], 1e-5), "k={k}");
}
}
#[test]
fn fft15_dc() {
let mut x = [Complex32::new(2.0, -1.0); 15];
let expected = rustfft_15(&x);
fft_15(&mut x);
for k in 0..15 {
assert!(close(x[k], expected[k], 1e-5));
}
}
#[test]
fn fft15_random() {
let mut xs = [Complex32::new(0.0, 0.0); 15];
let seed: u64 = 0xcafef00d_dead_beef;
let mut s = seed;
for c in xs.iter_mut() {
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let r = (s >> 33) as f32 / (1u32 << 31) as f32 - 1.0;
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let i = (s >> 33) as f32 / (1u32 << 31) as f32 - 1.0;
*c = Complex32::new(r, i);
}
let expected = rustfft_15(&xs);
let mut x = xs;
fft_15(&mut x);
for k in 0..15 {
assert!(
close(x[k], expected[k], 1e-4),
"k={k}: got {:?}, want {:?}",
x[k],
expected[k]
);
}
}
#[test]
fn fft15_pure_bin() {
use core::f32::consts::TAU;
let mut x = [Complex32::new(0.0, 0.0); 15];
for n in 0..15 {
let phi = -TAU * 4.0 * (n as f32) / 15.0;
x[n] = Complex32::new(phi.cos(), phi.sin());
}
let expected = rustfft_15(&x);
fft_15(&mut x);
for k in 0..15 {
assert!(
close(x[k], expected[k], 1e-4),
"k={k}: got {:?}, want {:?}",
x[k],
expected[k]
);
}
}
}
const C1: f32 = 0.309_017; const S1: f32 = 0.951_056_5; const C2: f32 = -0.809_017; const S2: f32 = 0.587_785_3;
#[inline]
pub fn fft_5(x: &mut [Complex32; 5]) {
let x0 = x[0];
let sum14_re = x[1].re + x[4].re;
let sum14_im = x[1].im + x[4].im;
let diff14_re = x[1].re - x[4].re;
let diff14_im = x[1].im - x[4].im;
let sum23_re = x[2].re + x[3].re;
let sum23_im = x[2].im + x[3].im;
let diff23_re = x[2].re - x[3].re;
let diff23_im = x[2].im - x[3].im;
x[0] = Complex32::new(x0.re + sum14_re + sum23_re, x0.im + sum14_im + sum23_im);
let t1_re = C1 * sum14_re + C2 * sum23_re;
let t1_im = C1 * sum14_im + C2 * sum23_im;
let t2_re = C2 * sum14_re + C1 * sum23_re;
let t2_im = C2 * sum14_im + C1 * sum23_im;
let u1_im = S1 * diff14_im + S2 * diff23_im;
let u1_re = S1 * diff14_re + S2 * diff23_re;
let u2_im = S2 * diff14_im - S1 * diff23_im;
let u2_re = S2 * diff14_re - S1 * diff23_re;
x[1] = Complex32::new(x0.re + t1_re + u1_im, x0.im + t1_im - u1_re);
x[4] = Complex32::new(x0.re + t1_re - u1_im, x0.im + t1_im + u1_re);
x[2] = Complex32::new(x0.re + t2_re + u2_im, x0.im + t2_im - u2_re);
x[3] = Complex32::new(x0.re + t2_re - u2_im, x0.im + t2_im + u2_re);
}
#[cfg(test)]
mod tests_5 {
use super::*;
fn rustfft_5(input: &[Complex32; 5]) -> [Complex32; 5] {
use rustfft::FftPlanner;
let mut planner = FftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(5);
let mut buf: Vec<Complex32> = input.to_vec();
fft.process(&mut buf);
let mut out = [Complex32::new(0.0, 0.0); 5];
out.copy_from_slice(&buf);
out
}
fn close(a: Complex32, b: Complex32, eps: f32) -> bool {
(a.re - b.re).abs() < eps && (a.im - b.im).abs() < eps
}
#[test]
fn fft5_impulse() {
let mut x = [
Complex32::new(1.0, 0.0),
Complex32::new(0.0, 0.0),
Complex32::new(0.0, 0.0),
Complex32::new(0.0, 0.0),
Complex32::new(0.0, 0.0),
];
let expected = rustfft_5(&x);
fft_5(&mut x);
for k in 0..5 {
assert!(
close(x[k], expected[k], 1e-5),
"k={k}: got {:?}, want {:?}",
x[k],
expected[k]
);
}
}
#[test]
fn fft5_dc() {
let mut x = [
Complex32::new(1.0, 0.0),
Complex32::new(1.0, 0.0),
Complex32::new(1.0, 0.0),
Complex32::new(1.0, 0.0),
Complex32::new(1.0, 0.0),
];
let expected = rustfft_5(&x);
fft_5(&mut x);
for k in 0..5 {
assert!(close(x[k], expected[k], 1e-5));
}
}
#[test]
fn fft5_random() {
let xs = [
Complex32::new(0.7, -0.3),
Complex32::new(-1.4, 1.1),
Complex32::new(0.2, 0.6),
Complex32::new(1.5, -0.8),
Complex32::new(-0.9, 0.4),
];
let expected = rustfft_5(&xs);
let mut x = xs;
fft_5(&mut x);
for k in 0..5 {
assert!(
close(x[k], expected[k], 1e-5),
"k={k}: got {:?}, want {:?}",
x[k],
expected[k]
);
}
}
#[test]
fn fft5_complex_sinusoid() {
use core::f32::consts::TAU;
let mut x = [Complex32::new(0.0, 0.0); 5];
for n in 0..5 {
let phi = -TAU * (n as f32) / 5.0;
x[n] = Complex32::new(phi.cos(), phi.sin());
}
let expected = rustfft_5(&x);
fft_5(&mut x);
for k in 0..5 {
assert!(
close(x[k], expected[k], 1e-5),
"k={k}: got {:?}, want {:?}",
x[k],
expected[k]
);
}
}
}
#[cfg(test)]
mod tests_3 {
use super::*;
fn rustfft_3(input: &[Complex32; 3]) -> [Complex32; 3] {
use rustfft::FftPlanner;
let mut planner = FftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(3);
let mut buf: Vec<Complex32> = input.to_vec();
fft.process(&mut buf);
[buf[0], buf[1], buf[2]]
}
fn close(a: Complex32, b: Complex32, eps: f32) -> bool {
(a.re - b.re).abs() < eps && (a.im - b.im).abs() < eps
}
#[test]
fn fft3_impulse() {
let mut x = [
Complex32::new(1.0, 0.0),
Complex32::new(0.0, 0.0),
Complex32::new(0.0, 0.0),
];
let expected = rustfft_3(&x);
fft_3(&mut x);
for k in 0..3 {
assert!(
close(x[k], expected[k], 1e-5),
"k={k}: got {:?}, want {:?}",
x[k],
expected[k]
);
}
}
#[test]
fn fft3_dc() {
let mut x = [
Complex32::new(2.5, 0.0),
Complex32::new(2.5, 0.0),
Complex32::new(2.5, 0.0),
];
let expected = rustfft_3(&x);
fft_3(&mut x);
for k in 0..3 {
assert!(close(x[k], expected[k], 1e-5));
}
}
#[test]
fn fft3_random() {
let xs = [
Complex32::new(0.7, -0.3),
Complex32::new(-1.4, 1.1),
Complex32::new(0.2, 0.6),
];
let expected = rustfft_3(&xs);
let mut x = xs;
fft_3(&mut x);
for k in 0..3 {
assert!(
close(x[k], expected[k], 1e-5),
"k={k}: got {:?}, want {:?}",
x[k],
expected[k]
);
}
}
}