use std::sync::Arc;
use std::cmp::max;
use num_complex::Complex;
use transpose;
use crate::common::FFTnum;
use crate::{Length, IsInverse, Fft};
use crate::array_utils;
pub struct MixedRadix<T> {
twiddles: Box<[Complex<T>]>,
width_size_fft: Arc<dyn Fft<T>>,
width: usize,
height_size_fft: Arc<dyn Fft<T>>,
height: usize,
inplace_scratch_len: usize,
outofplace_scratch_len: usize,
inverse: bool,
}
impl<T: FFTnum> MixedRadix<T> {
pub fn new(width_fft: Arc<dyn Fft<T>>, height_fft: Arc<dyn Fft<T>>) -> Self {
assert_eq!(
width_fft.is_inverse(), height_fft.is_inverse(),
"width_fft and height_fft must both be inverse, or neither. got width inverse={}, height inverse={}",
width_fft.is_inverse(), height_fft.is_inverse());
let inverse = width_fft.is_inverse();
let width = width_fft.len();
let height = height_fft.len();
let len = width * height;
let mut twiddles = Vec::with_capacity(len);
for x in 0..width {
for y in 0..height {
twiddles.push(T::generate_twiddle_factor(x * y, len, inverse));
}
}
let height_inplace_scratch = height_fft.get_inplace_scratch_len();
let width_inplace_scratch = width_fft.get_inplace_scratch_len();
let width_outofplace_scratch = width_fft.get_out_of_place_scratch_len();
let outofplace_scratch = max(height_inplace_scratch, width_inplace_scratch);
let inplace_extra = max(if height_inplace_scratch > len { height_inplace_scratch } else { 0 }, width_outofplace_scratch);
Self {
twiddles: twiddles.into_boxed_slice(),
width_size_fft: width_fft,
width: width,
height_size_fft: height_fft,
height: height,
inplace_scratch_len: len + inplace_extra,
outofplace_scratch_len: if outofplace_scratch > len { outofplace_scratch } else { 0 },
inverse: inverse,
}
}
fn perform_fft_inplace(&self, buffer: &mut [Complex<T>], scratch: &mut [Complex<T>]) {
let (scratch, inner_scratch) = scratch.split_at_mut(self.len());
transpose::transpose(buffer, scratch, self.width, self.height);
let height_scratch = if inner_scratch.len() > buffer.len() { &mut inner_scratch[..] } else { &mut buffer[..] };
self.height_size_fft.process_inplace_multi(scratch, height_scratch);
for (element, twiddle) in scratch.iter_mut().zip(self.twiddles.iter()) {
*element = *element * twiddle;
}
transpose::transpose(scratch, buffer, self.height, self.width);
self.width_size_fft.process_multi(buffer, scratch, inner_scratch);
transpose::transpose(scratch, buffer, self.width, self.height);
}
fn perform_fft_out_of_place(&self, input: &mut [Complex<T>], output: &mut [Complex<T>], scratch: &mut [Complex<T>]) {
transpose::transpose(input, output, self.width, self.height);
let height_scratch = if scratch.len() > input.len() { &mut scratch[..] } else { &mut input[..] };
self.height_size_fft.process_inplace_multi(output, height_scratch);
for (element, twiddle) in output.iter_mut().zip(self.twiddles.iter()) {
*element = *element * twiddle;
}
transpose::transpose(output, input, self.height, self.width);
let width_scratch = if scratch.len() > output.len() { &mut scratch[..] } else { &mut output[..] };
self.width_size_fft.process_inplace_multi(input, width_scratch);
transpose::transpose(input, output, self.width, self.height);
}
}
boilerplate_fft!(MixedRadix,
|this: &MixedRadix<_>| this.twiddles.len(),
|this: &MixedRadix<_>| this.inplace_scratch_len,
|this: &MixedRadix<_>| this.outofplace_scratch_len
);
pub struct MixedRadixSmall<T> {
twiddles: Box<[Complex<T>]>,
width_size_fft: Arc<dyn Fft<T>>,
width: usize,
height_size_fft: Arc<dyn Fft<T>>,
height: usize,
inverse: bool,
}
impl<T: FFTnum> MixedRadixSmall<T> {
pub fn new(width_fft: Arc<dyn Fft<T>>, height_fft: Arc<dyn Fft<T>>) -> Self {
assert_eq!(
width_fft.is_inverse(), height_fft.is_inverse(),
"width_fft and height_fft must both be inverse, or neither. got width inverse={}, height inverse={}",
width_fft.is_inverse(), height_fft.is_inverse());
let inverse = width_fft.is_inverse();
let width = width_fft.len();
let height = height_fft.len();
let len = width * height;
let mut twiddles = Vec::with_capacity(len);
for x in 0..width {
for y in 0..height {
twiddles.push(T::generate_twiddle_factor(x * y, len, inverse));
}
}
Self {
twiddles: twiddles.into_boxed_slice(),
width_size_fft: width_fft,
width: width,
height_size_fft: height_fft,
height: height,
inverse: inverse,
}
}
fn perform_fft_inplace(&self, buffer: &mut [Complex<T>], scratch: &mut [Complex<T>]) {
unsafe { array_utils::transpose_small(self.width, self.height, buffer, scratch) };
self.height_size_fft.process_inplace_multi(scratch, buffer);
for (element, twiddle) in scratch.iter_mut().zip(self.twiddles.iter()) {
*element = *element * twiddle;
}
unsafe { array_utils::transpose_small(self.height, self.width, scratch, buffer) };
self.width_size_fft.process_multi(buffer, scratch, &mut []);
unsafe { array_utils::transpose_small(self.width, self.height, scratch, buffer) };
}
fn perform_fft_out_of_place(&self, input: &mut [Complex<T>], output: &mut [Complex<T>], _scratch: &mut [Complex<T>]) {
unsafe { array_utils::transpose_small(self.width, self.height, input, output) };
self.height_size_fft.process_inplace_multi(output, input);
for (element, twiddle) in output.iter_mut().zip(self.twiddles.iter()) {
*element = *element * twiddle;
}
unsafe { array_utils::transpose_small(self.height, self.width, output, input) };
self.width_size_fft.process_inplace_multi(input, output);
unsafe { array_utils::transpose_small(self.width, self.height, input, output) };
}
}
boilerplate_fft!(MixedRadixSmall,
|this: &MixedRadixSmall<_>| this.twiddles.len(),
|this: &MixedRadixSmall<_>| this.len(),
|_| 0
);
#[cfg(test)]
mod unit_tests {
use super::*;
use std::sync::Arc;
use crate::test_utils::check_fft_algorithm;
use crate::algorithm::DFT;
#[test]
fn test_mixed_radix() {
for width in 1..7 {
for height in 1..7 {
test_mixed_radix_with_lengths(width, height, false);
test_mixed_radix_with_lengths(width, height, true);
}
}
}
#[test]
fn test_mixed_radix_small() {
for width in 2..7 {
for height in 2..7 {
test_mixed_radix_small_with_lengths(width, height, false);
test_mixed_radix_small_with_lengths(width, height, true);
}
}
}
fn test_mixed_radix_with_lengths(width: usize, height: usize, inverse: bool) {
let width_fft = Arc::new(DFT::new(width, inverse)) as Arc<dyn Fft<f32>>;
let height_fft = Arc::new(DFT::new(height, inverse)) as Arc<dyn Fft<f32>>;
let fft = MixedRadix::new(width_fft, height_fft);
check_fft_algorithm(&fft, width * height, inverse);
}
fn test_mixed_radix_small_with_lengths(width: usize, height: usize, inverse: bool) {
let width_fft = Arc::new(DFT::new(width, inverse)) as Arc<dyn Fft<f32>>;
let height_fft = Arc::new(DFT::new(height, inverse)) as Arc<dyn Fft<f32>>;
let fft = MixedRadixSmall::new(width_fft, height_fft);
check_fft_algorithm(&fft, width * height, inverse);
}
}