1use std::cmp::max;
2use std::sync::Arc;
3
4use num_complex::Complex;
5use num_traits::Zero;
6use transpose;
7
8use crate::array_utils;
9use crate::{common::FftNum, twiddles, FftDirection};
10use crate::{Direction, Fft, Length};
11
12pub struct MixedRadix<T> {
36 twiddles: Box<[Complex<T>]>,
37
38 width_size_fft: Arc<dyn Fft<T>>,
39 width: usize,
40
41 height_size_fft: Arc<dyn Fft<T>>,
42 height: usize,
43
44 inplace_scratch_len: usize,
45 outofplace_scratch_len: usize,
46 immut_scratch_len: usize,
47
48 direction: FftDirection,
49}
50
51impl<T: FftNum> MixedRadix<T> {
52 pub fn new(width_fft: Arc<dyn Fft<T>>, height_fft: Arc<dyn Fft<T>>) -> Self {
54 assert_eq!(
55 width_fft.fft_direction(), height_fft.fft_direction(),
56 "width_fft and height_fft must have the same direction. got width direction={}, height direction={}",
57 width_fft.fft_direction(), height_fft.fft_direction());
58
59 let direction = width_fft.fft_direction();
60
61 let width = width_fft.len();
62 let height = height_fft.len();
63
64 let len = width * height;
65
66 let mut twiddles = vec![Complex::zero(); len];
67 for (x, twiddle_chunk) in twiddles.chunks_exact_mut(height).enumerate() {
68 for (y, twiddle_element) in twiddle_chunk.iter_mut().enumerate() {
69 *twiddle_element = twiddles::compute_twiddle(x * y, len, direction);
70 }
71 }
72
73 let height_inplace_scratch = height_fft.get_inplace_scratch_len();
75 let width_inplace_scratch = width_fft.get_inplace_scratch_len();
76 let width_outofplace_scratch = width_fft.get_outofplace_scratch_len();
77
78 let max_inner_inplace_scratch = max(height_inplace_scratch, width_inplace_scratch);
86 let outofplace_scratch_len = if max_inner_inplace_scratch > len {
87 max_inner_inplace_scratch
88 } else {
89 0
90 };
91
92 let inplace_scratch_len = len
97 + max(
98 if height_inplace_scratch > len {
99 height_inplace_scratch
100 } else {
101 0
102 },
103 width_outofplace_scratch,
104 );
105
106 let immut_scratch_len = max(
107 len + width_fft.get_inplace_scratch_len(),
108 height_fft.get_inplace_scratch_len(),
109 );
110
111 Self {
112 twiddles: twiddles.into_boxed_slice(),
113
114 width_size_fft: width_fft,
115 width: width,
116
117 height_size_fft: height_fft,
118 height: height,
119
120 inplace_scratch_len,
121 outofplace_scratch_len,
122 immut_scratch_len,
123
124 direction,
125 }
126 }
127
128 fn perform_fft_inplace(&self, buffer: &mut [Complex<T>], scratch: &mut [Complex<T>]) {
129 let (scratch, inner_scratch) = scratch.split_at_mut(self.len());
131
132 transpose::transpose(buffer, scratch, self.width, self.height);
134
135 let height_scratch = if inner_scratch.len() > buffer.len() {
137 &mut inner_scratch[..]
138 } else {
139 &mut buffer[..]
140 };
141 self.height_size_fft
142 .process_with_scratch(scratch, height_scratch);
143
144 for (element, twiddle) in scratch.iter_mut().zip(self.twiddles.iter()) {
146 *element = *element * twiddle;
147 }
148
149 transpose::transpose(scratch, buffer, self.height, self.width);
151
152 self.width_size_fft
154 .process_outofplace_with_scratch(buffer, scratch, inner_scratch);
155
156 transpose::transpose(scratch, buffer, self.width, self.height);
158 }
159
160 fn perform_fft_immut(
161 &self,
162 input: &[Complex<T>],
163 output: &mut [Complex<T>],
164 scratch_raw: &mut [Complex<T>],
165 ) {
166 transpose::transpose(input, output, self.width, self.height);
168
169 self.height_size_fft
171 .process_with_scratch(output, scratch_raw);
172
173 for (element, twiddle) in output.iter_mut().zip(self.twiddles.iter()) {
175 *element = *element * twiddle;
176 }
177
178 let (scratch, inner_scratch) = scratch_raw.split_at_mut(self.len());
179
180 transpose::transpose(output, scratch, self.height, self.width);
182
183 self.width_size_fft
185 .process_with_scratch(scratch, inner_scratch);
186
187 transpose::transpose(scratch, output, self.width, self.height);
189 }
190
191 fn perform_fft_out_of_place(
192 &self,
193 input: &mut [Complex<T>],
194 output: &mut [Complex<T>],
195 scratch: &mut [Complex<T>],
196 ) {
197 transpose::transpose(input, output, self.width, self.height);
201
202 let height_scratch = if scratch.len() > input.len() {
204 &mut scratch[..]
205 } else {
206 &mut input[..]
207 };
208 self.height_size_fft
209 .process_with_scratch(output, height_scratch);
210
211 for (element, twiddle) in output.iter_mut().zip(self.twiddles.iter()) {
213 *element = *element * twiddle;
214 }
215
216 transpose::transpose(output, input, self.height, self.width);
218
219 let width_scratch = if scratch.len() > output.len() {
221 &mut scratch[..]
222 } else {
223 &mut output[..]
224 };
225 self.width_size_fft
226 .process_with_scratch(input, width_scratch);
227
228 transpose::transpose(input, output, self.width, self.height);
230 }
231}
232boilerplate_fft!(
233 MixedRadix,
234 |this: &MixedRadix<_>| this.twiddles.len(),
235 |this: &MixedRadix<_>| this.inplace_scratch_len,
236 |this: &MixedRadix<_>| this.outofplace_scratch_len,
237 |this: &MixedRadix<_>| this.immut_scratch_len
238);
239
240pub struct MixedRadixSmall<T> {
267 twiddles: Box<[Complex<T>]>,
268
269 width_size_fft: Arc<dyn Fft<T>>,
270 width: usize,
271
272 height_size_fft: Arc<dyn Fft<T>>,
273 height: usize,
274
275 direction: FftDirection,
276}
277
278impl<T: FftNum> MixedRadixSmall<T> {
279 pub fn new(width_fft: Arc<dyn Fft<T>>, height_fft: Arc<dyn Fft<T>>) -> Self {
281 assert_eq!(
282 width_fft.fft_direction(), height_fft.fft_direction(),
283 "width_fft and height_fft must have the same direction. got width direction={}, height direction={}",
284 width_fft.fft_direction(), height_fft.fft_direction());
285
286 let width = width_fft.len();
288 let height = height_fft.len();
289 let len = width * height;
290
291 assert_eq!(width_fft.get_outofplace_scratch_len(), 0, "MixedRadixSmall should only be used with algorithms that require 0 out-of-place scratch. Width FFT (len={}) requires {}, should require 0", width, width_fft.get_outofplace_scratch_len());
292 assert_eq!(height_fft.get_outofplace_scratch_len(), 0, "MixedRadixSmall should only be used with algorithms that require 0 out-of-place scratch. Height FFT (len={}) requires {}, should require 0", height, height_fft.get_outofplace_scratch_len());
293
294 assert!(width_fft.get_inplace_scratch_len() <= width, "MixedRadixSmall should only be used with algorithms that require little inplace scratch. Width FFT (len={}) requires {}, should require {} or less", width, width_fft.get_inplace_scratch_len(), width);
295 assert!(height_fft.get_inplace_scratch_len() <= height, "MixedRadixSmall should only be used with algorithms that require little inplace scratch. Height FFT (len={}) requires {}, should require {} or less", height, height_fft.get_inplace_scratch_len(), height);
296
297 let direction = width_fft.fft_direction();
298
299 let mut twiddles = vec![Complex::zero(); len];
300 for (x, twiddle_chunk) in twiddles.chunks_exact_mut(height).enumerate() {
301 for (y, twiddle_element) in twiddle_chunk.iter_mut().enumerate() {
302 *twiddle_element = twiddles::compute_twiddle(x * y, len, direction);
303 }
304 }
305
306 Self {
307 twiddles: twiddles.into_boxed_slice(),
308
309 width_size_fft: width_fft,
310 width: width,
311
312 height_size_fft: height_fft,
313 height: height,
314
315 direction,
316 }
317 }
318
319 fn perform_fft_inplace(&self, buffer: &mut [Complex<T>], scratch: &mut [Complex<T>]) {
320 unsafe { array_utils::transpose_small(self.width, self.height, buffer, scratch) };
323
324 self.height_size_fft.process_with_scratch(scratch, buffer);
326
327 for (element, twiddle) in scratch.iter_mut().zip(self.twiddles.iter()) {
329 *element = *element * twiddle;
330 }
331
332 unsafe { array_utils::transpose_small(self.height, self.width, scratch, buffer) };
334
335 self.width_size_fft
337 .process_outofplace_with_scratch(buffer, scratch, &mut []);
338
339 unsafe { array_utils::transpose_small(self.width, self.height, scratch, buffer) };
341 }
342
343 fn perform_fft_immut(
344 &self,
345 input: &[Complex<T>],
346 output: &mut [Complex<T>],
347 scratch: &mut [Complex<T>],
348 ) {
349 unsafe { array_utils::transpose_small(self.width, self.height, input, output) };
352
353 self.height_size_fft.process_with_scratch(output, scratch);
355
356 for (element, twiddle) in output.iter_mut().zip(self.twiddles.iter()) {
358 *element = *element * twiddle;
359 }
360
361 unsafe { array_utils::transpose_small(self.height, self.width, output, scratch) };
363
364 self.width_size_fft.process_with_scratch(scratch, output);
366
367 unsafe { array_utils::transpose_small(self.width, self.height, scratch, output) };
369 }
370
371 fn perform_fft_out_of_place(
372 &self,
373 input: &mut [Complex<T>],
374 output: &mut [Complex<T>],
375 _scratch: &mut [Complex<T>],
376 ) {
377 unsafe { array_utils::transpose_small(self.width, self.height, input, output) };
380
381 self.height_size_fft.process_with_scratch(output, input);
383
384 for (element, twiddle) in output.iter_mut().zip(self.twiddles.iter()) {
386 *element = *element * twiddle;
387 }
388
389 unsafe { array_utils::transpose_small(self.height, self.width, output, input) };
391
392 self.width_size_fft.process_with_scratch(input, output);
394
395 unsafe { array_utils::transpose_small(self.width, self.height, input, output) };
397 }
398}
399boilerplate_fft!(
400 MixedRadixSmall,
401 |this: &MixedRadixSmall<_>| this.twiddles.len(),
402 |this: &MixedRadixSmall<_>| this.len(),
403 |_| 0,
404 |this: &MixedRadixSmall<_>| this.len()
405);
406
407#[cfg(test)]
408mod unit_tests {
409 use super::*;
410 use crate::test_utils::check_fft_algorithm;
411 use crate::{algorithm::Dft, test_utils::BigScratchAlgorithm};
412 use num_traits::Zero;
413 use std::sync::Arc;
414
415 #[test]
416 fn test_mixed_radix() {
417 for width in 1..7 {
418 for height in 1..7 {
419 test_mixed_radix_with_lengths(width, height, FftDirection::Forward);
420 test_mixed_radix_with_lengths(width, height, FftDirection::Inverse);
421 }
422 }
423 }
424
425 #[test]
426 fn test_mixed_radix_small() {
427 for width in 2..7 {
428 for height in 2..7 {
429 test_mixed_radix_small_with_lengths(width, height, FftDirection::Forward);
430 test_mixed_radix_small_with_lengths(width, height, FftDirection::Inverse);
431 }
432 }
433 }
434
435 fn test_mixed_radix_with_lengths(width: usize, height: usize, direction: FftDirection) {
436 let width_fft = Arc::new(Dft::new(width, direction)) as Arc<dyn Fft<f32>>;
437 let height_fft = Arc::new(Dft::new(height, direction)) as Arc<dyn Fft<f32>>;
438
439 let fft = MixedRadix::new(width_fft, height_fft);
440
441 check_fft_algorithm(&fft, width * height, direction);
442 }
443
444 fn test_mixed_radix_small_with_lengths(width: usize, height: usize, direction: FftDirection) {
445 let width_fft = Arc::new(Dft::new(width, direction)) as Arc<dyn Fft<f32>>;
446 let height_fft = Arc::new(Dft::new(height, direction)) as Arc<dyn Fft<f32>>;
447
448 let fft = MixedRadixSmall::new(width_fft, height_fft);
449
450 check_fft_algorithm(&fft, width * height, direction);
451 }
452
453 #[test]
455 fn test_mixed_radix_inner_scratch() {
456 let scratch_lengths = [1, 5, 25];
457
458 let mut inner_ffts = Vec::new();
459
460 for &len in &scratch_lengths {
461 for &inplace_scratch in &scratch_lengths {
462 for &outofplace_scratch in &scratch_lengths {
463 for &immut_scratch in &scratch_lengths {
464 inner_ffts.push(Arc::new(BigScratchAlgorithm {
465 len,
466 inplace_scratch,
467 outofplace_scratch,
468 immut_scratch,
469 direction: FftDirection::Forward,
470 }) as Arc<dyn Fft<f32>>);
471 }
472 }
473 }
474 }
475
476 for width_fft in inner_ffts.iter() {
477 for height_fft in inner_ffts.iter() {
478 let fft = MixedRadix::new(Arc::clone(width_fft), Arc::clone(height_fft));
479
480 let mut inplace_buffer = vec![Complex::zero(); fft.len()];
481 let mut inplace_scratch = vec![Complex::zero(); fft.get_inplace_scratch_len()];
482
483 fft.process_with_scratch(&mut inplace_buffer, &mut inplace_scratch);
484
485 let mut outofplace_input = vec![Complex::zero(); fft.len()];
486 let mut outofplace_output = vec![Complex::zero(); fft.len()];
487 let mut outofplace_scratch =
488 vec![Complex::zero(); fft.get_outofplace_scratch_len()];
489 fft.process_outofplace_with_scratch(
490 &mut outofplace_input,
491 &mut outofplace_output,
492 &mut outofplace_scratch,
493 );
494
495 let immut_input = vec![Complex::zero(); fft.len()];
496 let mut immut_output = vec![Complex::zero(); fft.len()];
497 let mut immut_scratch = vec![Complex::zero(); fft.get_immutable_scratch_len()];
498
499 fft.process_immutable_with_scratch(
500 &immut_input,
501 &mut immut_output,
502 &mut immut_scratch,
503 );
504 }
505 }
506 }
507}