Skip to main content

rustfft/algorithm/
radix3.rs

1use std::sync::Arc;
2
3use num_complex::Complex;
4
5use crate::algorithm::butterflies::{Butterfly1, Butterfly27, Butterfly3, Butterfly9};
6use crate::algorithm::radixn::butterfly_3;
7use crate::array_utils::{bitreversed_transpose, compute_logarithm};
8use crate::{common::FftNum, twiddles, FftDirection};
9use crate::{Direction, Fft, Length};
10
11/// FFT algorithm optimized for power-of-three sizes
12///
13/// ~~~
14/// // Computes a forward FFT of size 2187
15/// use rustfft::algorithm::Radix3;
16/// use rustfft::{Fft, FftDirection};
17/// use rustfft::num_complex::Complex;
18///
19/// let mut buffer = vec![Complex{ re: 0.0f32, im: 0.0f32 }; 2187];
20///
21/// let fft = Radix3::new(2187, FftDirection::Forward);
22/// fft.process(&mut buffer);
23/// ~~~
24
25pub struct Radix3<T> {
26    twiddles: Box<[Complex<T>]>,
27    butterfly3: Butterfly3<T>,
28
29    base_fft: Arc<dyn Fft<T>>,
30    base_len: usize,
31
32    len: usize,
33    direction: FftDirection,
34
35    inplace_scratch_len: usize,
36    outofplace_scratch_len: usize,
37    immut_scratch_len: usize,
38}
39
40impl<T: FftNum> Radix3<T> {
41    /// Preallocates necessary arrays and precomputes necessary data to efficiently compute the power-of-three FFT
42    pub fn new(len: usize, direction: FftDirection) -> Self {
43        // Compute the total power of 3 for this length. IE, len = 3^exponent
44        let exponent = compute_logarithm::<3>(len).unwrap_or_else(|| {
45            panic!(
46                "Radix3 algorithm requires a power-of-three input size. Got {}",
47                len
48            )
49        });
50
51        // figure out which base length we're going to use
52        let (base_exponent, base_fft) = match exponent {
53            0 => (0, Arc::new(Butterfly1::new(direction)) as Arc<dyn Fft<T>>),
54            1 => (1, Arc::new(Butterfly3::new(direction)) as Arc<dyn Fft<T>>),
55            2 => (2, Arc::new(Butterfly9::new(direction)) as Arc<dyn Fft<T>>),
56            _ => (3, Arc::new(Butterfly27::new(direction)) as Arc<dyn Fft<T>>),
57        };
58
59        Self::new_with_base(exponent - base_exponent, base_fft)
60    }
61
62    /// Constructs a Radix3 instance which computes FFTs of length `3^k * base_fft.len()`
63    pub fn new_with_base(k: u32, base_fft: Arc<dyn Fft<T>>) -> Self {
64        let base_len = base_fft.len();
65        let len = base_len * 3usize.pow(k);
66
67        let direction = base_fft.fft_direction();
68
69        // precompute the twiddle factors this algorithm will use.
70        // we're doing the same precomputation of twiddle factors as the mixed radix algorithm where width=3 and height=len/3
71        // but mixed radix only does one step and then calls itself recusrively, and this algorithm does every layer all the way down
72        // so we're going to pack all the "layers" of twiddle factors into a single array, starting with the bottom layer and going up
73        const ROW_COUNT: usize = 3;
74        let mut cross_fft_len = base_len;
75        let mut twiddle_factors = Vec::with_capacity(len * 2);
76        while cross_fft_len < len {
77            let num_columns = cross_fft_len;
78            cross_fft_len *= ROW_COUNT;
79
80            for i in 0..num_columns {
81                for k in 1..ROW_COUNT {
82                    let twiddle = twiddles::compute_twiddle(i * k, cross_fft_len, direction);
83                    twiddle_factors.push(twiddle);
84                }
85            }
86        }
87
88        let base_inplace_scratch = base_fft.get_inplace_scratch_len();
89        let inplace_scratch_len = if base_inplace_scratch > cross_fft_len {
90            cross_fft_len + base_inplace_scratch
91        } else {
92            cross_fft_len
93        };
94        let outofplace_scratch_len = if base_inplace_scratch > len {
95            base_inplace_scratch
96        } else {
97            0
98        };
99
100        Self {
101            twiddles: twiddle_factors.into_boxed_slice(),
102            butterfly3: Butterfly3::new(direction),
103
104            base_fft,
105            base_len,
106
107            len,
108            direction,
109
110            inplace_scratch_len,
111            outofplace_scratch_len,
112            immut_scratch_len: base_inplace_scratch,
113        }
114    }
115
116    fn inplace_scratch_len(&self) -> usize {
117        self.inplace_scratch_len
118    }
119    fn outofplace_scratch_len(&self) -> usize {
120        self.outofplace_scratch_len
121    }
122    fn immut_scratch_len(&self) -> usize {
123        self.immut_scratch_len
124    }
125
126    fn perform_fft_immut(
127        &self,
128        input: &[Complex<T>],
129        output: &mut [Complex<T>],
130        scratch: &mut [Complex<T>],
131    ) {
132        // copy the data into the output vector
133        if self.len() == self.base_len {
134            output.copy_from_slice(input);
135        } else {
136            bitreversed_transpose::<Complex<T>, 3>(self.base_len, input, output);
137        }
138
139        // Base-level FFTs
140        self.base_fft.process_with_scratch(output, scratch);
141
142        // cross-FFTs
143        const ROW_COUNT: usize = 3;
144        let mut cross_fft_len = self.base_len;
145        let mut layer_twiddles: &[Complex<T>] = &self.twiddles;
146
147        while cross_fft_len < output.len() {
148            let num_columns = cross_fft_len;
149            cross_fft_len *= ROW_COUNT;
150
151            for data in output.chunks_exact_mut(cross_fft_len) {
152                unsafe { butterfly_3(data, layer_twiddles, num_columns, &self.butterfly3) }
153            }
154
155            // skip past all the twiddle factors used in this layer
156            let twiddle_offset = num_columns * (ROW_COUNT - 1);
157            layer_twiddles = &layer_twiddles[twiddle_offset..];
158        }
159    }
160
161    #[inline]
162    fn perform_fft_out_of_place(
163        &self,
164        input: &mut [Complex<T>],
165        output: &mut [Complex<T>],
166        scratch: &mut [Complex<T>],
167    ) {
168        // copy the data into the output vector
169        if self.len() == self.base_len {
170            output.copy_from_slice(input);
171        } else {
172            bitreversed_transpose::<Complex<T>, 3>(self.base_len, input, output);
173        }
174
175        // Base-level FFTs
176        let base_scratch = if scratch.len() > 0 { scratch } else { input };
177        self.base_fft.process_with_scratch(output, base_scratch);
178
179        // cross-FFTs
180        const ROW_COUNT: usize = 3;
181        let mut cross_fft_len = self.base_len;
182        let mut layer_twiddles: &[Complex<T>] = &self.twiddles;
183
184        while cross_fft_len < output.len() {
185            let num_columns = cross_fft_len;
186            cross_fft_len *= ROW_COUNT;
187
188            for data in output.chunks_exact_mut(cross_fft_len) {
189                unsafe { butterfly_3(data, layer_twiddles, num_columns, &self.butterfly3) }
190            }
191
192            // skip past all the twiddle factors used in this layer
193            let twiddle_offset = num_columns * (ROW_COUNT - 1);
194            layer_twiddles = &layer_twiddles[twiddle_offset..];
195        }
196    }
197}
198boilerplate_fft_oop!(Radix3, |this: &Radix3<_>| this.len);
199
200#[cfg(test)]
201mod unit_tests {
202    use super::*;
203    use crate::test_utils::{check_fft_algorithm, construct_base};
204
205    #[test]
206    fn test_radix3_with_length() {
207        for pow in 0..8 {
208            let len = 3usize.pow(pow);
209
210            let forward_fft = Radix3::new(len, FftDirection::Forward);
211            check_fft_algorithm::<f32>(&forward_fft, len, FftDirection::Forward);
212
213            let inverse_fft = Radix3::new(len, FftDirection::Inverse);
214            check_fft_algorithm::<f32>(&inverse_fft, len, FftDirection::Inverse);
215        }
216    }
217
218    #[test]
219    fn test_radix3_with_base() {
220        for base in 1..=9 {
221            let base_forward = construct_base(base, FftDirection::Forward);
222            let base_inverse = construct_base(base, FftDirection::Inverse);
223
224            for k in 0..5 {
225                test_radix3(k, Arc::clone(&base_forward));
226                test_radix3(k, Arc::clone(&base_inverse));
227            }
228        }
229    }
230
231    fn test_radix3(k: u32, base_fft: Arc<dyn Fft<f32>>) {
232        let len = base_fft.len() * 3usize.pow(k as u32);
233        let direction = base_fft.fft_direction();
234        let fft = Radix3::new_with_base(k, base_fft);
235
236        check_fft_algorithm::<f32>(&fft, len, direction);
237    }
238}