use std::sync::Arc;
use num_complex::Complex;
use num_traits::Zero;
use crate::algorithm::butterflies::{Butterfly1, Butterfly16, Butterfly2, Butterfly4, Butterfly8};
use crate::array_utils;
use crate::common::{fft_error_inplace, fft_error_outofplace};
use crate::{
array_utils::{RawSlice, RawSliceMut},
common::FftNum,
twiddles, FftDirection,
};
use crate::{Direction, Fft, Length};
pub struct Radix4<T> {
twiddles: Box<[Complex<T>]>,
base_fft: Arc<dyn Fft<T>>,
base_len: usize,
len: usize,
direction: FftDirection,
}
impl<T: FftNum> Radix4<T> {
pub fn new(len: usize, direction: FftDirection) -> Self {
assert!(
len.is_power_of_two(),
"Radix4 algorithm requires a power-of-two input size. Got {}",
len
);
let num_bits = len.trailing_zeros();
let (base_len, base_fft) = match num_bits {
0 => (len, Arc::new(Butterfly1::new(direction)) as Arc<dyn Fft<T>>),
1 => (len, Arc::new(Butterfly2::new(direction)) as Arc<dyn Fft<T>>),
2 => (len, Arc::new(Butterfly4::new(direction)) as Arc<dyn Fft<T>>),
_ => {
if num_bits % 2 == 1 {
(8, Arc::new(Butterfly8::new(direction)) as Arc<dyn Fft<T>>)
} else {
(16, Arc::new(Butterfly16::new(direction)) as Arc<dyn Fft<T>>)
}
}
};
let mut twiddle_stride = len / (base_len * 4);
let mut twiddle_factors = Vec::with_capacity(len * 2);
while twiddle_stride > 0 {
let num_rows = len / (twiddle_stride * 4);
for i in 0..num_rows {
for k in 1..4 {
let twiddle = twiddles::compute_twiddle(i * k * twiddle_stride, len, direction);
twiddle_factors.push(twiddle);
}
}
twiddle_stride >>= 2;
}
Self {
twiddles: twiddle_factors.into_boxed_slice(),
base_fft,
base_len,
len,
direction,
}
}
fn perform_fft_out_of_place(
&self,
signal: &[Complex<T>],
spectrum: &mut [Complex<T>],
_scratch: &mut [Complex<T>],
) {
prepare_radix4(signal.len(), self.base_len, signal, spectrum, 1);
self.base_fft.process_with_scratch(spectrum, &mut []);
let mut current_size = self.base_len * 4;
let mut layer_twiddles: &[Complex<T>] = &self.twiddles;
while current_size <= signal.len() {
let num_rows = signal.len() / current_size;
for i in 0..num_rows {
unsafe {
butterfly_4(
&mut spectrum[i * current_size..],
layer_twiddles,
current_size / 4,
self.direction,
)
}
}
let twiddle_offset = (current_size * 3) / 4;
layer_twiddles = &layer_twiddles[twiddle_offset..];
current_size *= 4;
}
}
}
boilerplate_fft_oop!(Radix4, |this: &Radix4<_>| this.len);
fn prepare_radix4<T: FftNum>(
size: usize,
base_len: usize,
signal: &[Complex<T>],
spectrum: &mut [Complex<T>],
stride: usize,
) {
if size == base_len {
unsafe {
for i in 0..size {
*spectrum.get_unchecked_mut(i) = *signal.get_unchecked(i * stride);
}
}
} else {
for i in 0..4 {
prepare_radix4(
size / 4,
base_len,
&signal[i * stride..],
&mut spectrum[i * (size / 4)..],
stride * 4,
);
}
}
}
unsafe fn butterfly_4<T: FftNum>(
data: &mut [Complex<T>],
twiddles: &[Complex<T>],
num_ffts: usize,
direction: FftDirection,
) {
let butterfly4 = Butterfly4::new(direction);
let mut idx = 0usize;
let mut tw_idx = 0usize;
let mut scratch = [Zero::zero(); 4];
for _ in 0..num_ffts {
scratch[0] = *data.get_unchecked(idx);
scratch[1] = *data.get_unchecked(idx + 1 * num_ffts) * twiddles[tw_idx];
scratch[2] = *data.get_unchecked(idx + 2 * num_ffts) * twiddles[tw_idx + 1];
scratch[3] = *data.get_unchecked(idx + 3 * num_ffts) * twiddles[tw_idx + 2];
butterfly4.perform_fft_contiguous(RawSlice::new(&scratch), RawSliceMut::new(&mut scratch));
*data.get_unchecked_mut(idx) = scratch[0];
*data.get_unchecked_mut(idx + 1 * num_ffts) = scratch[1];
*data.get_unchecked_mut(idx + 2 * num_ffts) = scratch[2];
*data.get_unchecked_mut(idx + 3 * num_ffts) = scratch[3];
tw_idx += 3;
idx += 1;
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
use crate::test_utils::check_fft_algorithm;
#[test]
fn test_radix4() {
for pow in 0..8 {
let len = 1 << pow;
test_radix4_with_length(len, FftDirection::Forward);
test_radix4_with_length(len, FftDirection::Inverse);
}
}
fn test_radix4_with_length(len: usize, direction: FftDirection) {
let fft = Radix4::new(len, direction);
check_fft_algorithm::<f32>(&fft, len, direction);
}
}