rustfft/algorithm/
radix3.rs1use 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
11pub 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 pub fn new(len: usize, direction: FftDirection) -> Self {
43 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 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 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 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 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 self.base_fft.process_with_scratch(output, scratch);
141
142 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 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 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 let base_scratch = if scratch.len() > 0 { scratch } else { input };
177 self.base_fft.process_with_scratch(output, base_scratch);
178
179 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 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}