1use num_integer::gcd;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use crate::common::RadixFactor;
6use crate::wasm_simd::wasm_simd_planner::FftPlannerWasmSimd;
7use crate::{common::FftNum, fft_cache::FftCache, FftDirection};
8
9use crate::algorithm::butterflies::*;
10use crate::algorithm::*;
11use crate::Fft;
12
13use crate::FftPlannerAvx;
14use crate::FftPlannerNeon;
15use crate::FftPlannerSse;
16
17use crate::math_utils::PrimeFactors;
18
19enum ChosenFftPlanner<T: FftNum> {
20 Scalar(FftPlannerScalar<T>),
21 Avx(FftPlannerAvx<T>),
22 Sse(FftPlannerSse<T>),
23 Neon(FftPlannerNeon<T>),
24 WasmSimd(FftPlannerWasmSimd<T>),
25 }
27
28pub struct FftPlanner<T: FftNum> {
68 chosen_planner: ChosenFftPlanner<T>,
69}
70impl<T: FftNum> FftPlanner<T> {
71 pub fn new() -> Self {
73 if let Ok(avx_planner) = FftPlannerAvx::new() {
74 Self {
75 chosen_planner: ChosenFftPlanner::Avx(avx_planner),
76 }
77 } else if let Ok(sse_planner) = FftPlannerSse::new() {
78 Self {
79 chosen_planner: ChosenFftPlanner::Sse(sse_planner),
80 }
81 } else if let Ok(neon_planner) = FftPlannerNeon::new() {
82 Self {
83 chosen_planner: ChosenFftPlanner::Neon(neon_planner),
84 }
85 } else if let Ok(wasm_simd_planner) = FftPlannerWasmSimd::new() {
86 Self {
87 chosen_planner: ChosenFftPlanner::WasmSimd(wasm_simd_planner),
88 }
89 } else {
90 Self {
91 chosen_planner: ChosenFftPlanner::Scalar(FftPlannerScalar::new()),
92 }
93 }
94 }
95
96 pub fn plan_fft(&mut self, len: usize, direction: FftDirection) -> Arc<dyn Fft<T>> {
102 match &mut self.chosen_planner {
103 ChosenFftPlanner::Scalar(scalar_planner) => scalar_planner.plan_fft(len, direction),
104 ChosenFftPlanner::Avx(avx_planner) => avx_planner.plan_fft(len, direction),
105 ChosenFftPlanner::Sse(sse_planner) => sse_planner.plan_fft(len, direction),
106 ChosenFftPlanner::Neon(neon_planner) => neon_planner.plan_fft(len, direction),
107 ChosenFftPlanner::WasmSimd(wasm_simd_planner) => {
108 wasm_simd_planner.plan_fft(len, direction)
109 }
110 }
111 }
112
113 pub fn plan_fft_forward(&mut self, len: usize) -> Arc<dyn Fft<T>> {
117 self.plan_fft(len, FftDirection::Forward)
118 }
119
120 pub fn plan_fft_inverse(&mut self, len: usize) -> Arc<dyn Fft<T>> {
124 self.plan_fft(len, FftDirection::Inverse)
125 }
126}
127
128const MAX_RADIXN_FACTOR: usize = 7; const MAX_RADER_PRIME_FACTOR: usize = 23; #[derive(Debug, PartialEq, Clone)]
134pub(crate) enum Recipe {
135 Dft(usize),
136 MixedRadix {
137 left_fft: Arc<Recipe>,
138 right_fft: Arc<Recipe>,
139 },
140 #[allow(dead_code)]
141 GoodThomasAlgorithm {
142 left_fft: Arc<Recipe>,
143 right_fft: Arc<Recipe>,
144 },
145 MixedRadixSmall {
146 left_fft: Arc<Recipe>,
147 right_fft: Arc<Recipe>,
148 },
149 GoodThomasAlgorithmSmall {
150 left_fft: Arc<Recipe>,
151 right_fft: Arc<Recipe>,
152 },
153 RadersAlgorithm {
154 inner_fft: Arc<Recipe>,
155 },
156 BluesteinsAlgorithm {
157 len: usize,
158 inner_fft: Arc<Recipe>,
159 },
160 RadixN {
161 factors: Box<[RadixFactor]>,
162 base_fft: Arc<Recipe>,
163 },
164 Radix4 {
165 k: u32,
166 base_fft: Arc<Recipe>,
167 },
168 Butterfly2,
169 Butterfly3,
170 Butterfly4,
171 Butterfly5,
172 Butterfly6,
173 Butterfly7,
174 Butterfly8,
175 Butterfly9,
176 Butterfly11,
177 Butterfly12,
178 Butterfly13,
179 Butterfly16,
180 Butterfly17,
181 Butterfly19,
182 Butterfly23,
183 Butterfly24,
184 Butterfly27,
185 Butterfly29,
186 Butterfly31,
187 Butterfly32,
188}
189
190impl Recipe {
191 pub fn len(&self) -> usize {
192 match self {
193 Recipe::Dft(length) => *length,
194 Recipe::RadixN { factors, base_fft } => {
195 base_fft.len() * factors.iter().map(|f| f.radix()).product::<usize>()
196 }
197 Recipe::Radix4 { k, base_fft } => base_fft.len() * (1 << (k * 2)),
198 Recipe::Butterfly2 => 2,
199 Recipe::Butterfly3 => 3,
200 Recipe::Butterfly4 => 4,
201 Recipe::Butterfly5 => 5,
202 Recipe::Butterfly6 => 6,
203 Recipe::Butterfly7 => 7,
204 Recipe::Butterfly8 => 8,
205 Recipe::Butterfly9 => 9,
206 Recipe::Butterfly11 => 11,
207 Recipe::Butterfly12 => 12,
208 Recipe::Butterfly13 => 13,
209 Recipe::Butterfly16 => 16,
210 Recipe::Butterfly17 => 17,
211 Recipe::Butterfly19 => 19,
212 Recipe::Butterfly23 => 23,
213 Recipe::Butterfly24 => 24,
214 Recipe::Butterfly27 => 27,
215 Recipe::Butterfly29 => 29,
216 Recipe::Butterfly31 => 31,
217 Recipe::Butterfly32 => 32,
218 Recipe::MixedRadix {
219 left_fft,
220 right_fft,
221 } => left_fft.len() * right_fft.len(),
222 Recipe::GoodThomasAlgorithm {
223 left_fft,
224 right_fft,
225 } => left_fft.len() * right_fft.len(),
226 Recipe::MixedRadixSmall {
227 left_fft,
228 right_fft,
229 } => left_fft.len() * right_fft.len(),
230 Recipe::GoodThomasAlgorithmSmall {
231 left_fft,
232 right_fft,
233 } => left_fft.len() * right_fft.len(),
234 Recipe::RadersAlgorithm { inner_fft } => inner_fft.len() + 1,
235 Recipe::BluesteinsAlgorithm { len, .. } => *len,
236 }
237 }
238}
239
240pub struct FftPlannerScalar<T: FftNum> {
271 algorithm_cache: FftCache<T>,
272 recipe_cache: HashMap<usize, Arc<Recipe>>,
273}
274
275impl<T: FftNum> FftPlannerScalar<T> {
276 pub fn new() -> Self {
278 Self {
279 algorithm_cache: FftCache::new(),
280 recipe_cache: HashMap::new(),
281 }
282 }
283
284 pub fn plan_fft(&mut self, len: usize, direction: FftDirection) -> Arc<dyn Fft<T>> {
290 let recipe = self.design_fft_for_len(len);
292
293 self.build_fft(&recipe, direction)
295 }
296
297 pub fn plan_fft_forward(&mut self, len: usize) -> Arc<dyn Fft<T>> {
301 self.plan_fft(len, FftDirection::Forward)
302 }
303
304 pub fn plan_fft_inverse(&mut self, len: usize) -> Arc<dyn Fft<T>> {
308 self.plan_fft(len, FftDirection::Inverse)
309 }
310
311 fn design_fft_for_len(&mut self, len: usize) -> Arc<Recipe> {
313 if len < 2 {
314 Arc::new(Recipe::Dft(len))
315 } else if let Some(recipe) = self.recipe_cache.get(&len) {
316 Arc::clone(&recipe)
317 } else {
318 let factors = PrimeFactors::compute(len);
319 let recipe = self.design_fft_with_factors(len, factors);
320 self.recipe_cache.insert(len, Arc::clone(&recipe));
321 recipe
322 }
323 }
324
325 fn build_fft(&mut self, recipe: &Recipe, direction: FftDirection) -> Arc<dyn Fft<T>> {
327 let len = recipe.len();
328 if let Some(instance) = self.algorithm_cache.get(len, direction) {
329 instance
330 } else {
331 let fft = self.build_new_fft(recipe, direction);
332 self.algorithm_cache.insert(&fft);
333 fft
334 }
335 }
336
337 fn build_new_fft(&mut self, recipe: &Recipe, direction: FftDirection) -> Arc<dyn Fft<T>> {
339 match recipe {
340 Recipe::Dft(len) => Arc::new(Dft::new(*len, direction)) as Arc<dyn Fft<T>>,
341 Recipe::RadixN { factors, base_fft } => {
342 let base_fft = self.build_fft(base_fft, direction);
343 Arc::new(RadixN::new(factors, base_fft)) as Arc<dyn Fft<T>>
344 }
345 Recipe::Radix4 { k, base_fft } => {
346 let base_fft = self.build_fft(base_fft, direction);
347 Arc::new(Radix4::new_with_base(*k, base_fft)) as Arc<dyn Fft<T>>
348 }
349 Recipe::Butterfly2 => Arc::new(Butterfly2::new(direction)) as Arc<dyn Fft<T>>,
350 Recipe::Butterfly3 => Arc::new(Butterfly3::new(direction)) as Arc<dyn Fft<T>>,
351 Recipe::Butterfly4 => Arc::new(Butterfly4::new(direction)) as Arc<dyn Fft<T>>,
352 Recipe::Butterfly5 => Arc::new(Butterfly5::new(direction)) as Arc<dyn Fft<T>>,
353 Recipe::Butterfly6 => Arc::new(Butterfly6::new(direction)) as Arc<dyn Fft<T>>,
354 Recipe::Butterfly7 => Arc::new(Butterfly7::new(direction)) as Arc<dyn Fft<T>>,
355 Recipe::Butterfly8 => Arc::new(Butterfly8::new(direction)) as Arc<dyn Fft<T>>,
356 Recipe::Butterfly9 => Arc::new(Butterfly9::new(direction)) as Arc<dyn Fft<T>>,
357 Recipe::Butterfly11 => Arc::new(Butterfly11::new(direction)) as Arc<dyn Fft<T>>,
358 Recipe::Butterfly12 => Arc::new(Butterfly12::new(direction)) as Arc<dyn Fft<T>>,
359 Recipe::Butterfly13 => Arc::new(Butterfly13::new(direction)) as Arc<dyn Fft<T>>,
360 Recipe::Butterfly16 => Arc::new(Butterfly16::new(direction)) as Arc<dyn Fft<T>>,
361 Recipe::Butterfly17 => Arc::new(Butterfly17::new(direction)) as Arc<dyn Fft<T>>,
362 Recipe::Butterfly19 => Arc::new(Butterfly19::new(direction)) as Arc<dyn Fft<T>>,
363 Recipe::Butterfly23 => Arc::new(Butterfly23::new(direction)) as Arc<dyn Fft<T>>,
364 Recipe::Butterfly24 => Arc::new(Butterfly24::new(direction)) as Arc<dyn Fft<T>>,
365 Recipe::Butterfly27 => Arc::new(Butterfly27::new(direction)) as Arc<dyn Fft<T>>,
366 Recipe::Butterfly29 => Arc::new(Butterfly29::new(direction)) as Arc<dyn Fft<T>>,
367 Recipe::Butterfly31 => Arc::new(Butterfly31::new(direction)) as Arc<dyn Fft<T>>,
368 Recipe::Butterfly32 => Arc::new(Butterfly32::new(direction)) as Arc<dyn Fft<T>>,
369 Recipe::MixedRadix {
370 left_fft,
371 right_fft,
372 } => {
373 let left_fft = self.build_fft(&left_fft, direction);
374 let right_fft = self.build_fft(&right_fft, direction);
375 Arc::new(MixedRadix::new(left_fft, right_fft)) as Arc<dyn Fft<T>>
376 }
377 Recipe::GoodThomasAlgorithm {
378 left_fft,
379 right_fft,
380 } => {
381 let left_fft = self.build_fft(&left_fft, direction);
382 let right_fft = self.build_fft(&right_fft, direction);
383 Arc::new(GoodThomasAlgorithm::new(left_fft, right_fft)) as Arc<dyn Fft<T>>
384 }
385 Recipe::MixedRadixSmall {
386 left_fft,
387 right_fft,
388 } => {
389 let left_fft = self.build_fft(&left_fft, direction);
390 let right_fft = self.build_fft(&right_fft, direction);
391 Arc::new(MixedRadixSmall::new(left_fft, right_fft)) as Arc<dyn Fft<T>>
392 }
393 Recipe::GoodThomasAlgorithmSmall {
394 left_fft,
395 right_fft,
396 } => {
397 let left_fft = self.build_fft(&left_fft, direction);
398 let right_fft = self.build_fft(&right_fft, direction);
399 Arc::new(GoodThomasAlgorithmSmall::new(left_fft, right_fft)) as Arc<dyn Fft<T>>
400 }
401 Recipe::RadersAlgorithm { inner_fft } => {
402 let inner_fft = self.build_fft(&inner_fft, direction);
403 Arc::new(RadersAlgorithm::new(inner_fft)) as Arc<dyn Fft<T>>
404 }
405 Recipe::BluesteinsAlgorithm { len, inner_fft } => {
406 let inner_fft = self.build_fft(&inner_fft, direction);
407 Arc::new(BluesteinsAlgorithm::new(*len, inner_fft)) as Arc<dyn Fft<T>>
408 }
409 }
410 }
411
412 fn design_fft_with_factors(&mut self, len: usize, factors: PrimeFactors) -> Arc<Recipe> {
413 if let Some(fft_instance) = self.design_butterfly_algorithm(len) {
414 fft_instance
415 } else if factors.is_prime() {
416 self.design_prime(len)
417 } else if let Some(butterfly_product) = self.design_butterfly_product(len) {
418 butterfly_product
419 } else if factors.has_factors_leq(MAX_RADIXN_FACTOR) {
420 self.design_radixn(factors)
421 } else {
422 let (left_factors, right_factors) = factors.partition_factors();
423 self.design_mixed_radix(left_factors, right_factors)
424 }
425 }
426
427 fn design_butterfly_product(&mut self, len: usize) -> Option<Arc<Recipe>> {
428 if len > 992 || len.is_power_of_two() {
429 return None;
430 } let limit = (len as f64).sqrt().ceil() as usize + 1;
433 let butterflies = [
434 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 16, 17, 19, 23, 24, 27, 29, 31, 32,
435 ];
436
437 let mut min_sum = usize::MAX;
443 let mut found_butterflies = None;
444 for left in butterflies.iter().take_while(|n| **n < limit) {
445 let right = len / left;
446 if left * right == len && butterflies.contains(&right) {
447 let sum = left + right;
448 if sum < min_sum {
449 min_sum = sum;
450 found_butterflies = Some((*left, right))
451 }
452 }
453 }
454
455 found_butterflies.map(|(left_len, right_len)| {
457 let left_fft = self.design_fft_for_len(left_len);
458 let right_fft = self.design_fft_for_len(right_len);
459
460 if gcd(left_len, right_len) == 1 {
461 Arc::new(Recipe::GoodThomasAlgorithmSmall {
462 left_fft,
463 right_fft,
464 })
465 } else {
466 Arc::new(Recipe::MixedRadixSmall {
467 left_fft,
468 right_fft,
469 })
470 }
471 })
472 }
473
474 fn design_mixed_radix(
475 &mut self,
476 left_factors: PrimeFactors,
477 right_factors: PrimeFactors,
478 ) -> Arc<Recipe> {
479 let left_len = left_factors.get_product();
480 let right_len = right_factors.get_product();
481
482 let left_fft = self.design_fft_with_factors(left_len, left_factors);
484 let right_fft = self.design_fft_with_factors(right_len, right_factors);
485
486 if left_len < 31 && right_len < 31 {
488 if gcd(left_len, right_len) == 1 {
490 Arc::new(Recipe::GoodThomasAlgorithmSmall {
491 left_fft,
492 right_fft,
493 })
494 } else {
495 Arc::new(Recipe::MixedRadixSmall {
496 left_fft,
497 right_fft,
498 })
499 }
500 } else {
501 Arc::new(Recipe::MixedRadix {
502 left_fft,
503 right_fft,
504 })
505 }
506 }
507
508 fn design_radixn(&mut self, factors: PrimeFactors) -> Arc<Recipe> {
509 let p2 = factors.get_power_of_two();
510 let p3 = factors.get_power_of_three();
511 let p5 = factors
512 .get_other_factors()
513 .iter()
514 .find_map(|f| if f.value == 5 { Some(f.count) } else { None }) .unwrap_or(0);
516 let p7 = factors
517 .get_other_factors()
518 .iter()
519 .find_map(|f| if f.value == 7 { Some(f.count) } else { None })
520 .unwrap_or(0);
521
522 let base_len: usize = if factors.has_factors_gt(MAX_RADIXN_FACTOR) {
523 factors.product_above(MAX_RADIXN_FACTOR)
525 } else if p7 == 0 && p5 == 0 && p3 < 2 {
526 if p3 == 0 {
528 assert!(p2 > 5); if p2 % 2 == 1 {
531 8
532 } else {
533 16
534 }
535 } else {
536 assert!(p2 > 3); if p2 % 2 == 1 {
539 24
540 } else {
541 12
542 }
543 }
544 } else if p2 > 0 && p3 > 0 {
545 let excess_p2 = p2.saturating_sub(p3);
548 match excess_p2 {
549 0 => 6,
550 1 => 12,
551 _ => 24,
552 }
553 } else if p3 > 2 {
554 27
555 } else if p3 > 1 {
556 9
557 } else if p7 > 0 {
558 7
559 } else {
560 assert!(p5 > 0);
561 5
562 };
563
564 let base_fft = self.design_fft_for_len(base_len);
566 let mut cross_len = factors.get_product() / base_len;
567
568 let cross_bits = cross_len.trailing_zeros();
570 if cross_len.is_power_of_two() && cross_bits % 2 == 0 {
571 let k = cross_bits / 2;
572 return Arc::new(Recipe::Radix4 { k, base_fft });
573 }
574
575 let mut factors = Vec::new();
578 while cross_len % 7 == 0 {
579 cross_len /= 7;
580 factors.push(RadixFactor::Factor7);
581 }
582 while cross_len % 6 == 0 {
583 cross_len /= 6;
584 factors.push(RadixFactor::Factor6);
585 }
586 while cross_len % 5 == 0 {
587 cross_len /= 5;
588 factors.push(RadixFactor::Factor5);
589 }
590 while cross_len % 3 == 0 {
591 cross_len /= 3;
592 factors.push(RadixFactor::Factor3);
593 }
594 assert!(cross_len.is_power_of_two());
595
596 let cross_bits = cross_len.trailing_zeros();
598 if cross_bits % 2 == 1 {
599 factors.push(RadixFactor::Factor2);
600 }
601 factors.extend(std::iter::repeat(RadixFactor::Factor4).take(cross_bits as usize / 2));
602
603 Arc::new(Recipe::RadixN {
604 factors: factors.into_boxed_slice(),
605 base_fft,
606 })
607 }
608
609 fn design_butterfly_algorithm(&mut self, len: usize) -> Option<Arc<Recipe>> {
611 match len {
612 2 => Some(Arc::new(Recipe::Butterfly2)),
613 3 => Some(Arc::new(Recipe::Butterfly3)),
614 4 => Some(Arc::new(Recipe::Butterfly4)),
615 5 => Some(Arc::new(Recipe::Butterfly5)),
616 6 => Some(Arc::new(Recipe::Butterfly6)),
617 7 => Some(Arc::new(Recipe::Butterfly7)),
618 8 => Some(Arc::new(Recipe::Butterfly8)),
619 9 => Some(Arc::new(Recipe::Butterfly9)),
620 11 => Some(Arc::new(Recipe::Butterfly11)),
621 12 => Some(Arc::new(Recipe::Butterfly12)),
622 13 => Some(Arc::new(Recipe::Butterfly13)),
623 16 => Some(Arc::new(Recipe::Butterfly16)),
624 17 => Some(Arc::new(Recipe::Butterfly17)),
625 19 => Some(Arc::new(Recipe::Butterfly19)),
626 23 => Some(Arc::new(Recipe::Butterfly23)),
627 24 => Some(Arc::new(Recipe::Butterfly24)),
628 27 => Some(Arc::new(Recipe::Butterfly27)),
629 29 => Some(Arc::new(Recipe::Butterfly29)),
630 31 => Some(Arc::new(Recipe::Butterfly31)),
631 32 => Some(Arc::new(Recipe::Butterfly32)),
632 _ => None,
633 }
634 }
635
636 fn design_prime(&mut self, len: usize) -> Arc<Recipe> {
637 let inner_fft_len_rader = len - 1;
638 let raders_factors = PrimeFactors::compute(inner_fft_len_rader);
639 if raders_factors
641 .get_other_factors()
642 .iter()
643 .any(|val| val.value > MAX_RADER_PRIME_FACTOR)
644 {
645 let min_inner_len = 2 * len - 1;
650 let inner_len_pow2 = min_inner_len.checked_next_power_of_two().unwrap();
651 let inner_len_factor3 = inner_len_pow2 / 4 * 3;
652
653 let inner_len = if inner_len_factor3 >= min_inner_len {
654 inner_len_factor3
655 } else {
656 inner_len_pow2
657 };
658 let inner_fft = self.design_fft_for_len(inner_len);
659
660 Arc::new(Recipe::BluesteinsAlgorithm { len, inner_fft })
661 } else {
662 let inner_fft = self.design_fft_with_factors(inner_fft_len_rader, raders_factors);
663 Arc::new(Recipe::RadersAlgorithm { inner_fft })
664 }
665 }
666}
667
668#[cfg(test)]
669mod unit_tests {
670 use super::*;
671
672 fn is_mixedradixsmall(plan: &Recipe) -> bool {
673 match plan {
674 &Recipe::MixedRadixSmall { .. } => true,
675 _ => false,
676 }
677 }
678
679 fn is_goodthomassmall(plan: &Recipe) -> bool {
680 match plan {
681 &Recipe::GoodThomasAlgorithmSmall { .. } => true,
682 _ => false,
683 }
684 }
685
686 fn is_raders(plan: &Recipe) -> bool {
687 match plan {
688 &Recipe::RadersAlgorithm { .. } => true,
689 _ => false,
690 }
691 }
692
693 fn is_bluesteins(plan: &Recipe) -> bool {
694 match plan {
695 &Recipe::BluesteinsAlgorithm { .. } => true,
696 _ => false,
697 }
698 }
699
700 #[test]
701 fn test_plan_scalar_trivial() {
702 let mut planner = FftPlannerScalar::<f64>::new();
704 for len in 0..2 {
705 let plan = planner.design_fft_for_len(len);
706 assert_eq!(*plan, Recipe::Dft(len));
707 assert_eq!(plan.len(), len, "Recipe reports wrong length");
708 }
709 }
710
711 #[test]
712 fn test_plan_scalar_largepoweroftwo() {
713 let mut planner = FftPlannerScalar::<f64>::new();
715 for pow in 6..32 {
716 let len = 1 << pow;
717 let plan = planner.design_fft_for_len(len);
718 assert!(matches!(*plan, Recipe::Radix4 { k: _, base_fft: _ }));
719 assert_eq!(plan.len(), len, "Recipe reports wrong length");
720 }
721 }
722
723 #[test]
724 fn test_plan_scalar_butterflies() {
725 let mut planner = FftPlannerScalar::<f64>::new();
727 assert_eq!(*planner.design_fft_for_len(2), Recipe::Butterfly2);
728 assert_eq!(*planner.design_fft_for_len(3), Recipe::Butterfly3);
729 assert_eq!(*planner.design_fft_for_len(4), Recipe::Butterfly4);
730 assert_eq!(*planner.design_fft_for_len(5), Recipe::Butterfly5);
731 assert_eq!(*planner.design_fft_for_len(6), Recipe::Butterfly6);
732 assert_eq!(*planner.design_fft_for_len(7), Recipe::Butterfly7);
733 assert_eq!(*planner.design_fft_for_len(8), Recipe::Butterfly8);
734 assert_eq!(*planner.design_fft_for_len(11), Recipe::Butterfly11);
735 assert_eq!(*planner.design_fft_for_len(12), Recipe::Butterfly12);
736 assert_eq!(*planner.design_fft_for_len(13), Recipe::Butterfly13);
737 assert_eq!(*planner.design_fft_for_len(16), Recipe::Butterfly16);
738 assert_eq!(*planner.design_fft_for_len(17), Recipe::Butterfly17);
739 assert_eq!(*planner.design_fft_for_len(19), Recipe::Butterfly19);
740 assert_eq!(*planner.design_fft_for_len(23), Recipe::Butterfly23);
741 assert_eq!(*planner.design_fft_for_len(24), Recipe::Butterfly24);
742 assert_eq!(*planner.design_fft_for_len(29), Recipe::Butterfly29);
743 assert_eq!(*planner.design_fft_for_len(31), Recipe::Butterfly31);
744 assert_eq!(*planner.design_fft_for_len(32), Recipe::Butterfly32);
745 }
746
747 #[test]
748 fn test_plan_scalar_radixn() {
749 let mut planner = FftPlannerScalar::<f64>::new();
751 for pow2 in 2..5 {
752 for pow3 in 2..5 {
753 for pow5 in 2..5 {
754 for pow7 in 2..5 {
755 let len = 2usize.pow(pow2)
756 * 3usize.pow(pow3)
757 * 5usize.pow(pow5)
758 * 7usize.pow(pow7);
759 let plan = planner.design_fft_for_len(len);
760 assert!(
761 matches!(
762 *plan,
763 Recipe::RadixN {
764 factors: _,
765 base_fft: _
766 }
767 ),
768 "Expected MixedRadix, got {:?}",
769 plan
770 );
771 assert_eq!(plan.len(), len, "Recipe reports wrong length");
772 }
773 }
774 }
775 }
776 }
777
778 #[test]
779 fn test_plan_scalar_mixedradixsmall() {
780 let mut planner = FftPlannerScalar::<f64>::new();
782 for len in [12 * 3, 6 * 27].iter() {
783 let plan = planner.design_fft_for_len(*len);
784 assert!(
785 is_mixedradixsmall(&plan),
786 "Expected MixedRadixSmall, got {:?}",
787 plan
788 );
789 assert_eq!(plan.len(), *len, "Recipe reports wrong length");
790 }
791 }
792
793 #[test]
794 fn test_plan_scalar_goodthomasbutterfly() {
795 let mut planner = FftPlannerScalar::<f64>::new();
796 for len in [3 * 5, 3 * 7, 5 * 7, 11 * 13].iter() {
797 let plan = planner.design_fft_for_len(*len);
798 assert!(
799 is_goodthomassmall(&plan),
800 "Expected GoodThomasAlgorithmSmall, got {:?}",
801 plan
802 );
803 assert_eq!(plan.len(), *len, "Recipe reports wrong length");
804 }
805 }
806
807 #[test]
808 fn test_plan_scalar_bluestein_vs_rader() {
809 let difficultprimes: [usize; 11] = [59, 83, 107, 149, 167, 173, 179, 359, 719, 1439, 2879];
810 let easyprimes: [usize; 24] = [
811 53, 61, 67, 71, 73, 79, 89, 97, 101, 103, 109, 113, 127, 131, 137, 139, 151, 157, 163,
812 181, 191, 193, 197, 199,
813 ];
814
815 let mut planner = FftPlannerScalar::<f64>::new();
816 for len in difficultprimes.iter() {
817 let plan = planner.design_fft_for_len(*len);
818 assert!(
819 is_bluesteins(&plan),
820 "Expected BluesteinsAlgorithm, got {:?}",
821 plan
822 );
823 assert_eq!(plan.len(), *len, "Recipe reports wrong length");
824 }
825 for len in easyprimes.iter() {
826 let plan = planner.design_fft_for_len(*len);
827 assert!(is_raders(&plan), "Expected RadersAlgorithm, got {:?}", plan);
828 assert_eq!(plan.len(), *len, "Recipe reports wrong length");
829 }
830 }
831
832 #[test]
833 fn test_scalar_fft_cache() {
834 {
835 let mut planner = FftPlannerScalar::<f64>::new();
837 let fft_a = planner.plan_fft(1234, FftDirection::Forward);
838 let fft_b = planner.plan_fft(1234, FftDirection::Forward);
839 assert!(Arc::ptr_eq(&fft_a, &fft_b), "Existing fft was not reused");
840 }
841 {
842 let mut planner = FftPlannerScalar::<f64>::new();
844 let fft_a = planner.plan_fft(1234, FftDirection::Inverse);
845 let fft_b = planner.plan_fft(1234, FftDirection::Inverse);
846 assert!(Arc::ptr_eq(&fft_a, &fft_b), "Existing fft was not reused");
847 }
848 {
849 let mut planner = FftPlannerScalar::<f64>::new();
851 let fft_a = planner.plan_fft(1234, FftDirection::Forward);
852 let fft_b = planner.plan_fft(1234, FftDirection::Inverse);
853 assert!(
854 !Arc::ptr_eq(&fft_a, &fft_b),
855 "Existing fft was reused, even though directions don't match"
856 );
857 }
858 }
859
860 #[test]
861 fn test_scalar_recipe_cache() {
862 let mut planner = FftPlannerScalar::<f64>::new();
864 let fft_a = planner.design_fft_for_len(1234);
865 let fft_b = planner.design_fft_for_len(1234);
866 assert!(
867 Arc::ptr_eq(&fft_a, &fft_b),
868 "Existing recipe was not reused"
869 );
870 }
871
872 #[test]
874 fn test_plan_zero_scalar() {
875 let mut planner32 = FftPlannerScalar::<f32>::new();
876 let fft_zero32 = planner32.plan_fft_forward(0);
877 fft_zero32.process(&mut []);
878
879 let mut planner64 = FftPlannerScalar::<f64>::new();
880 let fft_zero64 = planner64.plan_fft_forward(0);
881 fft_zero64.process(&mut []);
882 }
883
884 #[allow(dead_code)]
887 fn test_impl_fft_planner_send<T: FftNum>() {
888 fn is_send<T: Send>() {}
889 is_send::<FftPlanner<T>>();
890 is_send::<FftPlannerScalar<T>>();
891 is_send::<FftPlannerSse<T>>();
892 is_send::<FftPlannerAvx<T>>();
893 }
894}