Skip to main content

rustfft/algorithm/
radixn.rs

1use std::sync::Arc;
2
3use num_complex::Complex;
4
5use crate::array_utils::{factor_transpose, Load, LoadStore, TransposeFactor};
6use crate::common::RadixFactor;
7use crate::{common::FftNum, twiddles, FftDirection};
8use crate::{Direction, Fft, Length};
9
10use super::butterflies::{Butterfly2, Butterfly3, Butterfly4, Butterfly5, Butterfly6, Butterfly7};
11
12#[repr(u8)]
13enum InternalRadixFactor<T> {
14    Factor2(Butterfly2<T>),
15    Factor3(Butterfly3<T>),
16    Factor4(Butterfly4<T>),
17    Factor5(Butterfly5<T>),
18    Factor6(Butterfly6<T>),
19    Factor7(Butterfly7<T>),
20}
21impl<T> InternalRadixFactor<T> {
22    pub const fn radix(&self) -> usize {
23        // note: if we had rustc 1.66, we could just turn these values explicit discriminators on the enum
24        match self {
25            InternalRadixFactor::Factor2(_) => 2,
26            InternalRadixFactor::Factor3(_) => 3,
27            InternalRadixFactor::Factor4(_) => 4,
28            InternalRadixFactor::Factor5(_) => 5,
29            InternalRadixFactor::Factor6(_) => 6,
30            InternalRadixFactor::Factor7(_) => 7,
31        }
32    }
33}
34
35pub(crate) struct RadixN<T> {
36    twiddles: Box<[Complex<T>]>,
37
38    base_fft: Arc<dyn Fft<T>>,
39    base_len: usize,
40
41    factors: Box<[TransposeFactor]>,
42    butterflies: Box<[InternalRadixFactor<T>]>,
43
44    len: usize,
45    direction: FftDirection,
46
47    inplace_scratch_len: usize,
48    outofplace_scratch_len: usize,
49    immut_scratch_len: usize,
50}
51
52impl<T: FftNum> RadixN<T> {
53    /// Constructs a RadixN instance which computes FFTs of length `factor_product * base_fft.len()`
54    pub fn new(factors: &[RadixFactor], base_fft: Arc<dyn Fft<T>>) -> Self {
55        let base_len = base_fft.len();
56        let direction = base_fft.fft_direction();
57
58        // set up our cross FFT butterfly instances. simultaneously, compute the number of twiddle factors
59        let mut butterflies = Vec::with_capacity(factors.len());
60        let mut cross_fft_len = base_len;
61        let mut twiddle_count = 0;
62
63        for factor in factors {
64            // compute how many twiddles this cross-FFT needs
65            let cross_fft_rows = factor.radix();
66            let cross_fft_columns = cross_fft_len;
67
68            twiddle_count += cross_fft_columns * (cross_fft_rows - 1);
69
70            // set up the butterfly for this cross-FFT
71            let butterfly = match factor {
72                RadixFactor::Factor2 => InternalRadixFactor::Factor2(Butterfly2::new(direction)),
73                RadixFactor::Factor3 => InternalRadixFactor::Factor3(Butterfly3::new(direction)),
74                RadixFactor::Factor4 => InternalRadixFactor::Factor4(Butterfly4::new(direction)),
75                RadixFactor::Factor5 => InternalRadixFactor::Factor5(Butterfly5::new(direction)),
76                RadixFactor::Factor6 => InternalRadixFactor::Factor6(Butterfly6::new(direction)),
77                RadixFactor::Factor7 => InternalRadixFactor::Factor7(Butterfly7::new(direction)),
78            };
79            butterflies.push(butterfly);
80
81            cross_fft_len *= cross_fft_rows;
82        }
83        let len = cross_fft_len;
84
85        // set up our list of transpose factors - it's the same list but reversed, and we want to collapse duplicates
86        // Note that we are only de-duplicating adjacent factors. If we're passed 7 * 2 * 7, we can't collapse the sevens
87        // because the exact order of factors is is important for the transpose
88        let mut transpose_factors: Vec<TransposeFactor> = Vec::with_capacity(factors.len());
89        for f in factors.iter().rev() {
90            // I really want let chains for this!
91            let mut push_new = true;
92            if let Some(last) = transpose_factors.last_mut() {
93                if last.factor == *f {
94                    last.count += 1;
95                    push_new = false;
96                }
97            }
98            if push_new {
99                transpose_factors.push(TransposeFactor {
100                    factor: *f,
101                    count: 1,
102                });
103            }
104        }
105
106        // precompute the twiddle factors this algorithm will use.
107        // we're doing the same precomputation of twiddle factors as the mixed radix algorithm where width=factor.radix() and height=len/factor.radix()
108        // but mixed radix only does one step and then calls itself recusrively, and this algorithm does every layer all the way down
109        // so we're going to pack all the "layers" of twiddle factors into a single array, starting with the bottom layer and going up
110        let mut cross_fft_len = base_len;
111        let mut twiddle_factors = Vec::with_capacity(twiddle_count);
112
113        for factor in factors {
114            // Compute the twiddle factors for the cross FFT
115            let cross_fft_columns = cross_fft_len;
116            cross_fft_len *= factor.radix();
117
118            for i in 0..cross_fft_columns {
119                for k in 1..factor.radix() {
120                    let twiddle = twiddles::compute_twiddle(i * k, cross_fft_len, direction);
121                    twiddle_factors.push(twiddle);
122                }
123            }
124        }
125
126        // figure out how much scratch space we need to request from callers
127        let base_inplace_scratch = base_fft.get_inplace_scratch_len();
128        let inplace_scratch_len = if base_inplace_scratch > len {
129            len + base_inplace_scratch
130        } else {
131            len
132        };
133        let outofplace_scratch_len = if base_inplace_scratch > len {
134            base_inplace_scratch
135        } else {
136            0
137        };
138
139        Self {
140            twiddles: twiddle_factors.into_boxed_slice(),
141
142            base_fft,
143            base_len,
144
145            factors: transpose_factors.into_boxed_slice(),
146            butterflies: butterflies.into_boxed_slice(),
147
148            len,
149            direction,
150
151            inplace_scratch_len,
152            outofplace_scratch_len,
153            immut_scratch_len: base_inplace_scratch,
154        }
155    }
156
157    fn inplace_scratch_len(&self) -> usize {
158        self.inplace_scratch_len
159    }
160    fn outofplace_scratch_len(&self) -> usize {
161        self.outofplace_scratch_len
162    }
163    fn immut_scratch_len(&self) -> usize {
164        self.immut_scratch_len
165    }
166
167    fn perform_fft_immut(
168        &self,
169        input: &[Complex<T>],
170        output: &mut [Complex<T>],
171        scratch: &mut [Complex<T>],
172    ) {
173        if let Some(unroll_factor) = self.factors.first() {
174            // for performance, we really, really want to unroll the transpose, but we need to make sure the output length is divisible by the unroll amount
175            // choosing the first factor seems to reliably perform well
176            match unroll_factor.factor {
177                RadixFactor::Factor2 => {
178                    factor_transpose::<Complex<T>, 2>(self.base_len, input, output, &self.factors)
179                }
180                RadixFactor::Factor3 => {
181                    factor_transpose::<Complex<T>, 3>(self.base_len, input, output, &self.factors)
182                }
183                RadixFactor::Factor4 => {
184                    factor_transpose::<Complex<T>, 4>(self.base_len, input, output, &self.factors)
185                }
186                RadixFactor::Factor5 => {
187                    factor_transpose::<Complex<T>, 5>(self.base_len, input, output, &self.factors)
188                }
189                RadixFactor::Factor6 => {
190                    factor_transpose::<Complex<T>, 6>(self.base_len, input, output, &self.factors)
191                }
192                RadixFactor::Factor7 => {
193                    factor_transpose::<Complex<T>, 7>(self.base_len, input, output, &self.factors)
194                }
195            }
196        } else {
197            // no factors, so just pass data straight to our base
198            output.copy_from_slice(input);
199        }
200
201        // Base-level FFTs
202        self.base_fft.process_with_scratch(output, scratch);
203
204        let mut cross_fft_len = self.base_len;
205        let mut layer_twiddles: &[Complex<T>] = &self.twiddles;
206
207        for factor in self.butterflies.iter() {
208            let cross_fft_columns = cross_fft_len;
209            cross_fft_len *= factor.radix();
210
211            match factor {
212                InternalRadixFactor::Factor2(butterfly2) => {
213                    for data in output.chunks_exact_mut(cross_fft_len) {
214                        unsafe { butterfly_2(data, layer_twiddles, cross_fft_columns, butterfly2) }
215                    }
216                }
217                InternalRadixFactor::Factor3(butterfly3) => {
218                    for data in output.chunks_exact_mut(cross_fft_len) {
219                        unsafe { butterfly_3(data, layer_twiddles, cross_fft_columns, butterfly3) }
220                    }
221                }
222                InternalRadixFactor::Factor4(butterfly4) => {
223                    for data in output.chunks_exact_mut(cross_fft_len) {
224                        unsafe { butterfly_4(data, layer_twiddles, cross_fft_columns, butterfly4) }
225                    }
226                }
227                InternalRadixFactor::Factor5(butterfly5) => {
228                    for data in output.chunks_exact_mut(cross_fft_len) {
229                        unsafe { butterfly_5(data, layer_twiddles, cross_fft_columns, butterfly5) }
230                    }
231                }
232                InternalRadixFactor::Factor6(butterfly6) => {
233                    for data in output.chunks_exact_mut(cross_fft_len) {
234                        unsafe { butterfly_6(data, layer_twiddles, cross_fft_columns, butterfly6) }
235                    }
236                }
237                InternalRadixFactor::Factor7(butterfly7) => {
238                    for data in output.chunks_exact_mut(cross_fft_len) {
239                        unsafe { butterfly_7(data, layer_twiddles, cross_fft_columns, butterfly7) }
240                    }
241                }
242            }
243
244            // skip past all the twiddle factors used in this layer
245            let twiddle_offset = cross_fft_columns * (factor.radix() - 1);
246            layer_twiddles = &layer_twiddles[twiddle_offset..];
247        }
248    }
249
250    fn perform_fft_out_of_place(
251        &self,
252        input: &mut [Complex<T>],
253        output: &mut [Complex<T>],
254        scratch: &mut [Complex<T>],
255    ) {
256        if let Some(unroll_factor) = self.factors.first() {
257            // for performance, we really, really want to unroll the transpose, but we need to make sure the output length is divisible by the unroll amount
258            // choosing the first factor seems to reliably perform well
259            match unroll_factor.factor {
260                RadixFactor::Factor2 => {
261                    factor_transpose::<Complex<T>, 2>(self.base_len, input, output, &self.factors)
262                }
263                RadixFactor::Factor3 => {
264                    factor_transpose::<Complex<T>, 3>(self.base_len, input, output, &self.factors)
265                }
266                RadixFactor::Factor4 => {
267                    factor_transpose::<Complex<T>, 4>(self.base_len, input, output, &self.factors)
268                }
269                RadixFactor::Factor5 => {
270                    factor_transpose::<Complex<T>, 5>(self.base_len, input, output, &self.factors)
271                }
272                RadixFactor::Factor6 => {
273                    factor_transpose::<Complex<T>, 6>(self.base_len, input, output, &self.factors)
274                }
275                RadixFactor::Factor7 => {
276                    factor_transpose::<Complex<T>, 7>(self.base_len, input, output, &self.factors)
277                }
278            }
279        } else {
280            // no factors, so just pass data straight to our base
281            output.copy_from_slice(input);
282        }
283
284        // Base-level FFTs
285        let base_scratch = if scratch.len() > 0 { scratch } else { input };
286        self.base_fft.process_with_scratch(output, base_scratch);
287
288        // cross-FFTs
289        let mut cross_fft_len = self.base_len;
290        let mut layer_twiddles: &[Complex<T>] = &self.twiddles;
291
292        for factor in self.butterflies.iter() {
293            let cross_fft_columns = cross_fft_len;
294            cross_fft_len *= factor.radix();
295
296            match factor {
297                InternalRadixFactor::Factor2(butterfly2) => {
298                    for data in output.chunks_exact_mut(cross_fft_len) {
299                        unsafe { butterfly_2(data, layer_twiddles, cross_fft_columns, butterfly2) }
300                    }
301                }
302                InternalRadixFactor::Factor3(butterfly3) => {
303                    for data in output.chunks_exact_mut(cross_fft_len) {
304                        unsafe { butterfly_3(data, layer_twiddles, cross_fft_columns, butterfly3) }
305                    }
306                }
307                InternalRadixFactor::Factor4(butterfly4) => {
308                    for data in output.chunks_exact_mut(cross_fft_len) {
309                        unsafe { butterfly_4(data, layer_twiddles, cross_fft_columns, butterfly4) }
310                    }
311                }
312                InternalRadixFactor::Factor5(butterfly5) => {
313                    for data in output.chunks_exact_mut(cross_fft_len) {
314                        unsafe { butterfly_5(data, layer_twiddles, cross_fft_columns, butterfly5) }
315                    }
316                }
317                InternalRadixFactor::Factor6(butterfly6) => {
318                    for data in output.chunks_exact_mut(cross_fft_len) {
319                        unsafe { butterfly_6(data, layer_twiddles, cross_fft_columns, butterfly6) }
320                    }
321                }
322                InternalRadixFactor::Factor7(butterfly7) => {
323                    for data in output.chunks_exact_mut(cross_fft_len) {
324                        unsafe { butterfly_7(data, layer_twiddles, cross_fft_columns, butterfly7) }
325                    }
326                }
327            }
328
329            // skip past all the twiddle factors used in this layer
330            let twiddle_offset = cross_fft_columns * (factor.radix() - 1);
331            layer_twiddles = &layer_twiddles[twiddle_offset..];
332        }
333    }
334}
335boilerplate_fft_oop!(RadixN, |this: &RadixN<_>| this.len);
336
337#[inline(never)]
338pub(crate) unsafe fn butterfly_2<T: FftNum>(
339    mut data: impl LoadStore<T>,
340    twiddles: impl Load<T>,
341    num_columns: usize,
342    butterfly2: &Butterfly2<T>,
343) {
344    for idx in 0..num_columns {
345        let mut scratch = [
346            data.load(idx + 0 * num_columns),
347            data.load(idx + 1 * num_columns) * twiddles.load(idx),
348        ];
349
350        butterfly2.perform_fft_butterfly(&mut scratch);
351
352        data.store(scratch[0], idx + num_columns * 0);
353        data.store(scratch[1], idx + num_columns * 1);
354    }
355}
356
357#[inline(never)]
358pub(crate) unsafe fn butterfly_3<T: FftNum>(
359    mut data: impl LoadStore<T>,
360    twiddles: impl Load<T>,
361    num_columns: usize,
362    butterfly3: &Butterfly3<T>,
363) {
364    for idx in 0..num_columns {
365        let tw_idx = idx * 2;
366        let mut scratch = [
367            data.load(idx + 0 * num_columns),
368            data.load(idx + 1 * num_columns) * twiddles.load(tw_idx + 0),
369            data.load(idx + 2 * num_columns) * twiddles.load(tw_idx + 1),
370        ];
371
372        butterfly3.perform_fft_butterfly(&mut scratch);
373
374        data.store(scratch[0], idx + 0 * num_columns);
375        data.store(scratch[1], idx + 1 * num_columns);
376        data.store(scratch[2], idx + 2 * num_columns);
377    }
378}
379
380#[inline(never)]
381pub(crate) unsafe fn butterfly_4<T: FftNum>(
382    mut data: impl LoadStore<T>,
383    twiddles: impl Load<T>,
384    num_columns: usize,
385    butterfly4: &Butterfly4<T>,
386) {
387    for idx in 0..num_columns {
388        let tw_idx = idx * 3;
389        let mut scratch = [
390            data.load(idx + 0 * num_columns),
391            data.load(idx + 1 * num_columns) * twiddles.load(tw_idx + 0),
392            data.load(idx + 2 * num_columns) * twiddles.load(tw_idx + 1),
393            data.load(idx + 3 * num_columns) * twiddles.load(tw_idx + 2),
394        ];
395
396        butterfly4.perform_fft_butterfly(&mut scratch);
397
398        data.store(scratch[0], idx + 0 * num_columns);
399        data.store(scratch[1], idx + 1 * num_columns);
400        data.store(scratch[2], idx + 2 * num_columns);
401        data.store(scratch[3], idx + 3 * num_columns);
402    }
403}
404
405#[inline(never)]
406pub(crate) unsafe fn butterfly_5<T: FftNum>(
407    mut data: impl LoadStore<T>,
408    twiddles: impl Load<T>,
409    num_columns: usize,
410    butterfly5: &Butterfly5<T>,
411) {
412    for idx in 0..num_columns {
413        let tw_idx = idx * 4;
414        let mut scratch = [
415            data.load(idx + 0 * num_columns),
416            data.load(idx + 1 * num_columns) * twiddles.load(tw_idx + 0),
417            data.load(idx + 2 * num_columns) * twiddles.load(tw_idx + 1),
418            data.load(idx + 3 * num_columns) * twiddles.load(tw_idx + 2),
419            data.load(idx + 4 * num_columns) * twiddles.load(tw_idx + 3),
420        ];
421
422        butterfly5.perform_fft_butterfly(&mut scratch);
423
424        data.store(scratch[0], idx + 0 * num_columns);
425        data.store(scratch[1], idx + 1 * num_columns);
426        data.store(scratch[2], idx + 2 * num_columns);
427        data.store(scratch[3], idx + 3 * num_columns);
428        data.store(scratch[4], idx + 4 * num_columns);
429    }
430}
431
432#[inline(never)]
433pub(crate) unsafe fn butterfly_6<T: FftNum>(
434    mut data: impl LoadStore<T>,
435    twiddles: impl Load<T>,
436    num_columns: usize,
437    butterfly6: &Butterfly6<T>,
438) {
439    for idx in 0..num_columns {
440        let tw_idx = idx * 5;
441        let mut scratch = [
442            data.load(idx + 0 * num_columns),
443            data.load(idx + 1 * num_columns) * twiddles.load(tw_idx + 0),
444            data.load(idx + 2 * num_columns) * twiddles.load(tw_idx + 1),
445            data.load(idx + 3 * num_columns) * twiddles.load(tw_idx + 2),
446            data.load(idx + 4 * num_columns) * twiddles.load(tw_idx + 3),
447            data.load(idx + 5 * num_columns) * twiddles.load(tw_idx + 4),
448        ];
449
450        butterfly6.perform_fft_butterfly(&mut scratch);
451
452        data.store(scratch[0], idx + 0 * num_columns);
453        data.store(scratch[1], idx + 1 * num_columns);
454        data.store(scratch[2], idx + 2 * num_columns);
455        data.store(scratch[3], idx + 3 * num_columns);
456        data.store(scratch[4], idx + 4 * num_columns);
457        data.store(scratch[5], idx + 5 * num_columns);
458    }
459}
460
461#[inline(never)]
462pub(crate) unsafe fn butterfly_7<T: FftNum>(
463    mut data: impl LoadStore<T>,
464    twiddles: impl Load<T>,
465    num_columns: usize,
466    butterfly7: &Butterfly7<T>,
467) {
468    for idx in 0..num_columns {
469        let tw_idx = idx * 6;
470        let mut scratch = [
471            data.load(idx + 0 * num_columns),
472            data.load(idx + 1 * num_columns) * twiddles.load(tw_idx + 0),
473            data.load(idx + 2 * num_columns) * twiddles.load(tw_idx + 1),
474            data.load(idx + 3 * num_columns) * twiddles.load(tw_idx + 2),
475            data.load(idx + 4 * num_columns) * twiddles.load(tw_idx + 3),
476            data.load(idx + 5 * num_columns) * twiddles.load(tw_idx + 4),
477            data.load(idx + 6 * num_columns) * twiddles.load(tw_idx + 5),
478        ];
479
480        butterfly7.perform_fft_butterfly(&mut scratch);
481
482        data.store(scratch[0], idx + 0 * num_columns);
483        data.store(scratch[1], idx + 1 * num_columns);
484        data.store(scratch[2], idx + 2 * num_columns);
485        data.store(scratch[3], idx + 3 * num_columns);
486        data.store(scratch[4], idx + 4 * num_columns);
487        data.store(scratch[5], idx + 5 * num_columns);
488        data.store(scratch[6], idx + 6 * num_columns);
489    }
490}
491
492#[cfg(test)]
493mod unit_tests {
494    use super::*;
495    use crate::test_utils::{check_fft_algorithm, construct_base};
496
497    #[test]
498    fn test_scalar_radixn() {
499        let factor_list = &[
500            RadixFactor::Factor2,
501            RadixFactor::Factor3,
502            RadixFactor::Factor4,
503            RadixFactor::Factor5,
504            RadixFactor::Factor6,
505            RadixFactor::Factor7,
506        ];
507
508        for base in 1..7 {
509            let base_forward = construct_base(base, FftDirection::Forward);
510            let base_inverse = construct_base(base, FftDirection::Inverse);
511
512            // test just the base with no factors
513            test_radixn(&[], Arc::clone(&base_forward));
514            test_radixn(&[], Arc::clone(&base_inverse));
515
516            // test one factor
517            for factor_a in factor_list {
518                let factors = &[*factor_a];
519                test_radixn(factors, Arc::clone(&base_forward));
520                test_radixn(factors, Arc::clone(&base_inverse));
521            }
522
523            // test two factors
524            for factor_a in factor_list {
525                for factor_b in factor_list {
526                    let factors = &[*factor_a, *factor_b];
527                    test_radixn(factors, Arc::clone(&base_forward));
528                    test_radixn(factors, Arc::clone(&base_inverse));
529                }
530            }
531        }
532    }
533
534    fn test_radixn(factors: &[RadixFactor], base_fft: Arc<dyn Fft<f64>>) {
535        let len = base_fft.len() * factors.iter().map(|f| f.radix()).product::<usize>();
536        let direction = base_fft.fft_direction();
537        let fft = RadixN::new(factors, base_fft);
538
539        check_fft_algorithm::<f64>(&fft, len, direction);
540    }
541}