use std::sync::Arc;
use num_complex::Complex;
use common::{FFTnum, verify_length, verify_length_divisible};
use math_utils;
use array_utils;
use ::{Length, IsInverse, FFT};
pub struct GoodThomasAlgorithm<T> {
width: usize,
width_size_fft: Arc<FFT<T>>,
height: usize,
height_size_fft: Arc<FFT<T>>,
input_map: Box<[usize]>,
output_map: Box<[usize]>,
inverse: bool,
}
impl<T: FFTnum> GoodThomasAlgorithm<T> {
pub fn new(n1_fft: Arc<FFT<T>>, n2_fft: Arc<FFT<T>>) -> Self {
assert_eq!(
n1_fft.is_inverse(), n2_fft.is_inverse(),
"n1_fft and n2_fft must both be inverse, or neither. got n1 inverse={}, n2 inverse={}",
n1_fft.is_inverse(), n2_fft.is_inverse());
let n1 = n1_fft.len();
let n2 = n2_fft.len();
let (gcd, mut n1_inverse, mut n2_inverse) =
math_utils::extended_euclidean_algorithm(n1 as i64, n2 as i64);
assert!(gcd == 1,
"Invalid input n1 and n2 to Good-Thomas Algorithm: ({},{}): Inputs must be coprime",
n1,
n2);
if n1_inverse < 0 {
n1_inverse += n2 as i64;
}
if n2_inverse < 0 {
n2_inverse += n1 as i64;
}
let input_map: Vec<usize> =
(0..n1 * n2)
.map(|i| (i % n1, i / n1))
.map(|(x, y)| (x * n2 + y * n1) % (n1 * n2))
.collect();
let output_map: Vec<usize> =
(0..n1 * n2)
.map(|i| (i % n2, i / n2))
.map(|(y, x)| {
(x * n2 * n2_inverse as usize + y * n1 * n1_inverse as usize) % (n1 * n2)
})
.collect();
GoodThomasAlgorithm {
inverse: n1_fft.is_inverse(),
width: n1,
width_size_fft: n1_fft,
height: n2,
height_size_fft: n2_fft,
input_map: input_map.into_boxed_slice(),
output_map: output_map.into_boxed_slice(),
}
}
fn perform_fft(&self, input: &mut [Complex<T>], output: &mut [Complex<T>]) {
for (output_element, &input_index) in output.iter_mut().zip(self.input_map.iter()) {
*output_element = input[input_index];
}
self.width_size_fft.process_multi(output, input);
array_utils::transpose(self.width, self.height, input, output);
self.height_size_fft.process_multi(output, input);
for (input_element, &output_index) in input.iter().zip(self.output_map.iter()) {
output[output_index] = *input_element;
}
}
}
impl<T: FFTnum> FFT<T> for GoodThomasAlgorithm<T> {
fn process(&self, input: &mut [Complex<T>], output: &mut [Complex<T>]) {
verify_length(input, output, self.len());
self.perform_fft(input, output);
}
fn process_multi(&self, input: &mut [Complex<T>], output: &mut [Complex<T>]) {
verify_length_divisible(input, output, self.len());
for (in_chunk, out_chunk) in input.chunks_mut(self.len()).zip(output.chunks_mut(self.len())) {
self.perform_fft(in_chunk, out_chunk);
}
}
}
impl<T> Length for GoodThomasAlgorithm<T> {
#[inline(always)]
fn len(&self) -> usize {
self.input_map.len()
}
}
impl<T> IsInverse for GoodThomasAlgorithm<T> {
#[inline(always)]
fn is_inverse(&self) -> bool {
self.inverse
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
use std::sync::Arc;
use test_utils::check_fft_algorithm;
use algorithm::DFT;
#[test]
fn test_good_thomas() {
for width in 2..20 {
test_good_thomas_with_lengths(width, width - 1);
test_good_thomas_with_lengths(width, width + 1);
}
test_good_thomas_with_lengths(1, 10);
test_good_thomas_with_lengths(10, 1);
test_good_thomas_with_lengths(1, 1);
}
fn test_good_thomas_with_lengths(width: usize, height: usize) {
let width_fft = Arc::new(DFT::new(width, false)) as Arc<FFT<f32>>;
let height_fft = Arc::new(DFT::new(height, false)) as Arc<FFT<f32>>;
let fft = GoodThomasAlgorithm::new(width_fft, height_fft);
check_fft_algorithm(&fft, width * height, false);
}
}