#[cfg(any(test, feature = "long_bench"))]
use super::fft::FftFloat;
#[cfg(any(test, feature = "long_bench"))]
pub struct FftPlannerRadix4<T: FftFloat> {
n: usize,
bit_reverse: Vec<usize>,
stage_twiddle_re1: Vec<T>,
stage_twiddle_im1: Vec<T>,
stage_twiddle_re2: Vec<T>,
stage_twiddle_im2: Vec<T>,
stage_twiddle_re3: Vec<T>,
stage_twiddle_im3: Vec<T>,
stage_l: Vec<usize>,
}
#[cfg(any(test, feature = "long_bench"))]
impl<T: FftFloat> FftPlannerRadix4<T> {
pub fn new(n: usize) -> Self {
assert!(n > 0, "FFT size must be positive");
assert!(
n.is_power_of_two(),
"FFT size must be a power of two (Radix-4 requires power of four), got {n}"
);
assert!(n >= 4, "Radix-4 FFT requires N ≥ 4, got {n}");
let n_half = n / 2;
let num_stages_radix4 = (n.ilog2() / 2) as usize;
let base4_digits = num_stages_radix4;
let mut bit_reverse = vec![0usize; n];
#[expect(
clippy::needless_range_loop,
reason = "Range loop required for explicit SIMD lane indexing not expressible via iterator"
)]
for i in 0..n {
let mut rev = 0usize;
let mut x = i;
for _ in 0..base4_digits {
rev = (rev << 2) | (x & 0x3);
x >>= 2;
}
bit_reverse[i] = rev;
}
let tau = T::tau();
let n_t = T::from_usize(n);
let twiddle_re: Vec<T> = (0..n_half)
.map(|k| {
let angle = tau * T::from_usize(k) * n_t.recip();
angle.cos()
})
.collect();
let twiddle_im: Vec<T> = (0..n_half)
.map(|k| {
let angle = tau * T::from_usize(k) * n_t.recip();
-angle.sin()
})
.collect();
let total = n.saturating_sub(1);
let mut stage_twiddle_re1 = Vec::with_capacity(total);
let mut stage_twiddle_im1 = Vec::with_capacity(total);
let mut stage_twiddle_re2 = Vec::with_capacity(total);
let mut stage_twiddle_im2 = Vec::with_capacity(total);
let mut stage_twiddle_re3 = Vec::with_capacity(total);
let mut stage_twiddle_im3 = Vec::with_capacity(total);
let mut stage_l = Vec::with_capacity(num_stages_radix4);
let mut len = 4;
while len <= n {
let l = len / 4;
let step = n / len;
stage_l.push(l);
for j in 0..l {
let w1 = j * step;
let w2 = (2 * j) * step;
let w3 = (3 * j) * step;
stage_twiddle_re1.push(twiddle_re[w1]);
stage_twiddle_im1.push(twiddle_im[w1]);
stage_twiddle_re2.push(twiddle_re[w2]);
stage_twiddle_im2.push(twiddle_im[w2]);
if w3 < n_half {
stage_twiddle_re3.push(twiddle_re[w3]);
stage_twiddle_im3.push(twiddle_im[w3]);
} else {
stage_twiddle_re3.push(-twiddle_re[w3 - n_half]);
stage_twiddle_im3.push(-twiddle_im[w3 - n_half]);
}
}
len <<= 2;
}
Self {
n,
bit_reverse,
stage_twiddle_re1,
stage_twiddle_im1,
stage_twiddle_re2,
stage_twiddle_im2,
stage_twiddle_re3,
stage_twiddle_im3,
stage_l,
}
}
#[inline]
pub fn len(&self) -> usize {
self.n
}
#[inline]
pub fn is_empty(&self) -> bool {
self.n == 0
}
pub fn process(&self, re: &mut [T], im: &mut [T]) {
debug_assert_eq!(re.len(), self.n);
debug_assert_eq!(im.len(), self.n);
for (i, &j) in self.bit_reverse.iter().enumerate() {
if i < j {
unsafe {
std::ptr::swap(re.get_unchecked_mut(i), re.get_unchecked_mut(j));
std::ptr::swap(im.get_unchecked_mut(i), im.get_unchecked_mut(j));
}
}
}
let mut tw_offset = 0usize;
for &l in &self.stage_l {
self.radix4_stage(re, im, l, tw_offset, false);
tw_offset += l;
}
}
pub fn process_inverse(&self, re: &mut [T], im: &mut [T]) {
debug_assert_eq!(re.len(), self.n);
debug_assert_eq!(im.len(), self.n);
for (i, &j) in self.bit_reverse.iter().enumerate() {
if i < j {
unsafe {
std::ptr::swap(re.get_unchecked_mut(i), re.get_unchecked_mut(j));
std::ptr::swap(im.get_unchecked_mut(i), im.get_unchecked_mut(j));
}
}
}
let mut tw_offset = 0usize;
for &l in &self.stage_l {
self.radix4_stage(re, im, l, tw_offset, true);
tw_offset += l;
}
let scale = T::from_usize(self.n).recip();
for s in re.iter_mut() {
*s = *s * scale;
}
for s in im.iter_mut() {
*s = *s * scale;
}
}
fn radix4_stage(&self, re: &mut [T], im: &mut [T], l: usize, tw_offset: usize, inverse: bool) {
let len = 4 * l;
for k in (0..self.n).step_by(len) {
for j in 0..l {
let idx0 = k + j;
let idx1 = k + j + l;
let idx2 = k + j + 2 * l;
let idx3 = k + j + 3 * l;
let tw_idx = tw_offset + j;
let w1_re = unsafe { *self.stage_twiddle_re1.get_unchecked(tw_idx) };
let w1_im = if inverse {
-unsafe { *self.stage_twiddle_im1.get_unchecked(tw_idx) }
} else {
unsafe { *self.stage_twiddle_im1.get_unchecked(tw_idx) }
};
let w2_re = unsafe { *self.stage_twiddle_re2.get_unchecked(tw_idx) };
let w2_im = if inverse {
-unsafe { *self.stage_twiddle_im2.get_unchecked(tw_idx) }
} else {
unsafe { *self.stage_twiddle_im2.get_unchecked(tw_idx) }
};
let w3_re = unsafe { *self.stage_twiddle_re3.get_unchecked(tw_idx) };
let w3_im = if inverse {
-unsafe { *self.stage_twiddle_im3.get_unchecked(tw_idx) }
} else {
unsafe { *self.stage_twiddle_im3.get_unchecked(tw_idx) }
};
let (r0, i0, r1, i1, r2, i2, r3, i3) = unsafe {
(
*re.get_unchecked(idx0),
*im.get_unchecked(idx0),
*re.get_unchecked(idx1),
*im.get_unchecked(idx1),
*re.get_unchecked(idx2),
*im.get_unchecked(idx2),
*re.get_unchecked(idx3),
*im.get_unchecked(idx3),
)
};
let y1_re = w1_re.mul_add(r1, -w1_im * i1);
let y1_im = w1_re.mul_add(i1, w1_im * r1);
let y2_re = w2_re.mul_add(r2, -w2_im * i2);
let y2_im = w2_re.mul_add(i2, w2_im * r2);
let y3_re = w3_re.mul_add(r3, -w3_im * i3);
let y3_im = w3_re.mul_add(i3, w3_im * r3);
unsafe {
*re.get_unchecked_mut(idx0) = (r0 + y1_re) + (y2_re + y3_re);
*im.get_unchecked_mut(idx0) = (i0 + y1_im) + (y2_im + y3_im);
if inverse {
*re.get_unchecked_mut(idx3) = (r0 + y1_im) - (y2_re + y3_im);
*im.get_unchecked_mut(idx3) = (i0 - y1_re) - (y2_im - y3_re);
*re.get_unchecked_mut(idx2) = (r0 - y1_re) + (y2_re - y3_re);
*im.get_unchecked_mut(idx2) = (i0 - y1_im) + (y2_im - y3_im);
*re.get_unchecked_mut(idx1) = (r0 - y1_im) - (y2_re - y3_im);
*im.get_unchecked_mut(idx1) = (i0 + y1_re) - (y2_im + y3_re);
} else {
*re.get_unchecked_mut(idx1) = (r0 + y1_im) - (y2_re + y3_im);
*im.get_unchecked_mut(idx1) = (i0 - y1_re) - (y2_im - y3_re);
*re.get_unchecked_mut(idx2) = (r0 - y1_re) + (y2_re - y3_re);
*im.get_unchecked_mut(idx2) = (i0 - y1_im) + (y2_im - y3_im);
*re.get_unchecked_mut(idx3) = (r0 - y1_im) - (y2_re - y3_im);
*im.get_unchecked_mut(idx3) = (i0 + y1_re) - (y2_im + y3_re);
}
}
}
}
}
}
#[cfg(test)]
#[path = "fft_radix4_test.rs"]
mod tests;