Skip to main content

zaft/
lib.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
3 * //
4 * // Redistribution and use in source and binary forms, with or without modification,
5 * // are permitted provided that the following conditions are met:
6 * //
7 * // 1.  Redistributions of source code must retain the above copyright notice, this
8 * // list of conditions and the following disclaimer.
9 * //
10 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * // this list of conditions and the following disclaimer in the documentation
12 * // and/or other materials provided with the distribution.
13 * //
14 * // 3.  Neither the name of the copyright holder nor the names of its
15 * // contributors may be used to endorse or promote products derived from
16 * // this software without specific prior written permission.
17 * //
18 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29#![allow(
30    clippy::assign_op_pattern,
31    clippy::only_used_in_recursion,
32    clippy::too_many_arguments,
33    clippy::type_complexity,
34    clippy::modulo_one
35)]
36#![cfg_attr(docsrs, feature(doc_cfg))]
37#![cfg_attr(
38    all(feature = "fcma", target_arch = "aarch64"),
39    feature(stdarch_neon_fcma)
40)]
41#[cfg(all(target_arch = "x86_64", feature = "avx"))]
42mod avx;
43mod bluestein;
44mod butterflies;
45mod complex_fma;
46mod dft;
47mod err;
48mod factory;
49mod factory64;
50mod fast_divider;
51mod good_thomas;
52mod good_thomas_small;
53mod mixed_radix;
54mod mla;
55#[cfg(all(target_arch = "aarch64", feature = "neon"))]
56mod neon;
57mod prime_factors;
58mod r2c;
59mod raders;
60mod radix10;
61mod radix11;
62mod radix13;
63mod radix3;
64mod radix4;
65mod radix5;
66mod radix6;
67mod radix7;
68mod spectrum_arithmetic;
69mod store;
70mod td;
71mod traits;
72mod transpose;
73mod transpose_arbitrary;
74mod util;
75
76#[allow(unused_imports)]
77use radix3::Radix3;
78#[allow(unused_imports)]
79use radix4::Radix4;
80#[allow(unused_imports)]
81use radix5::Radix5;
82#[allow(unused_imports)]
83use radix6::Radix6;
84#[allow(unused_imports)]
85use radix7::Radix7;
86#[allow(unused_imports)]
87use radix10::Radix10;
88#[allow(unused_imports)]
89use radix11::Radix11;
90#[allow(unused_imports)]
91use radix13::Radix13;
92use std::collections::HashMap;
93
94use crate::factory::AlgorithmFactory;
95use crate::prime_factors::{
96    PrimeFactors, can_be_two_factors, split_factors_closest, try_greedy_pure_power_split,
97};
98use crate::r2c::{
99    C2RFftEvenInterceptor, C2RFftOddInterceptor, OneSizedRealFft, R2CTwiddlesFactory,
100    R2cAlgorithmFactory, strategy_r2c,
101};
102use crate::spectrum_arithmetic::ComplexArithFactory;
103use crate::td::{TwoDimensionalC2C, TwoDimensionalC2R, TwoDimensionalR2C};
104use crate::traits::FftTrigonometry;
105use crate::transpose::TransposeFactory;
106use crate::util::{
107    ALWAYS_BLUESTEIN_1000, ALWAYS_BLUESTEIN_2000, ALWAYS_BLUESTEIN_3000, ALWAYS_BLUESTEIN_4000,
108    ALWAYS_BLUESTEIN_5000, ALWAYS_BLUESTEIN_6000,
109};
110pub use err::ZaftError;
111use num_complex::Complex;
112use num_traits::{AsPrimitive, Float, MulAdd};
113pub use r2c::{C2RFftExecutor, R2CFftExecutor};
114use std::fmt::{Debug, Display, Formatter};
115use std::sync::{Arc, OnceLock, RwLock};
116pub use td::{TwoDimensionalExecutorC2R, TwoDimensionalExecutorR2C, TwoDimensionalFftExecutor};
117
118pub(crate) trait FftSample:
119    AlgorithmFactory<Self>
120    + FftTrigonometry
121    + Float
122    + 'static
123    + Send
124    + Sync
125    + MulAdd<Self, Output = Self>
126    + ComplexArithFactory<Self>
127    + TransposeFactory<Self>
128    + Copy
129    + Display
130    + FftPrimeCache<Self>
131    + Default
132    + Debug
133    + R2cAlgorithmFactory<Self>
134    + R2CTwiddlesFactory<Self>
135{
136    const HALF: Self;
137    const SQRT_3_OVER_2: Self;
138}
139
140impl FftSample for f64 {
141    const HALF: Self = 0.5;
142    // from sage.all import *
143    // import struct
144    //
145    // R = RealField(90)
146    //
147    // def float_to_hex(f):
148    //     packed = struct.pack('>f', float(f))
149    //     return '0x' + packed.hex()
150    //
151    // value = R(3).sqrt() / R(2)
152    //
153    // print(float_to_hex(value))
154    //
155    // def double_to_hex(f):
156    //         packed = struct.pack('>d', float(f))
157    //         return '0x' + packed.hex()
158    //
159    // print(double_to_hex(value))
160    const SQRT_3_OVER_2: Self = f64::from_bits(0x3febb67ae8584caa);
161}
162impl FftSample for f32 {
163    const HALF: Self = 0.5;
164    // from sage.all import *
165    // import struct
166    //
167    // R = RealField(90)
168    //
169    // def float_to_hex(f):
170    //     packed = struct.pack('>f', float(f))
171    //     return '0x' + packed.hex()
172    //
173    // value = R(3).sqrt() / R(2)
174    //
175    // print(float_to_hex(value))
176    //
177    // def double_to_hex(f):
178    //         packed = struct.pack('>d', float(f))
179    //         return '0x' + packed.hex()
180    //
181    // print(double_to_hex(value))
182    const SQRT_3_OVER_2: Self = f32::from_bits(0x3f5db3d7);
183}
184
185pub trait FftExecutor<T> {
186    /// Executes the Complex-to-Complex FFT operation **in-place**.
187    ///
188    /// The input/output slice `in_place` must have a length equal to `self.length()`.
189    /// The direction of the transform (Forward or Inverse) is determined by the executor's
190    /// pre-configured state, accessible via `self.direction()`.
191    ///
192    /// # Parameters
193    /// * `in_place`: The mutable slice containing the complex-valued input data. Upon completion,
194    ///   it will contain the complex-valued frequency-domain result (for a Forward transform)
195    ///   or the time-domain result (for an Inverse transform).
196    ///
197    /// # Errors
198    /// Returns a `ZaftError` if the execution fails (e.g., due to an incorrect slice length).
199    fn execute(&self, in_place: &mut [Complex<T>]) -> Result<(), ZaftError>;
200    /// Executes the FFT operation **in-place**, using caller-provided scratch memory.
201    ///
202    /// This variant avoids internal allocations and may improve performance
203    /// in repeated executions.
204    fn execute_with_scratch(
205        &self,
206        in_place: &mut [Complex<T>],
207        scratch: &mut [Complex<T>],
208    ) -> Result<(), ZaftError>;
209    /// Executes the FFT operation **out-of-place**.
210    ///
211    /// The input slice `src` is left unmodified. The result is written
212    /// into `dst`.
213    fn execute_out_of_place(
214        &self,
215        src: &[Complex<T>],
216        dst: &mut [Complex<T>],
217    ) -> Result<(), ZaftError>;
218    /// Executes the FFT operation **out-of-place**, using caller-provided scratch memory.
219    ///
220    /// This allows reuse of scratch memory across multiple FFT calls
221    /// to avoid repeated allocations.
222    fn execute_out_of_place_with_scratch(
223        &self,
224        src: &[Complex<T>],
225        dst: &mut [Complex<T>],
226        scratch: &mut [Complex<T>],
227    ) -> Result<(), ZaftError>;
228    /// Executes the FFT operation using a **destructive input strategy**.
229    ///
230    /// The `src` buffer may be overwritten during computation and should
231    /// not be assumed to retain its original contents after the call.
232    /// The final transform result is written to `dst`.
233    ///
234    /// This variant may enable more memory-efficient algorithms.
235    fn execute_destructive_with_scratch(
236        &self,
237        src: &mut [Complex<T>],
238        dst: &mut [Complex<T>],
239        scratch: &mut [Complex<T>],
240    ) -> Result<(), ZaftError>;
241    /// Returns the **direction** of the transform this executor is configured to perform.
242    ///
243    /// The direction is typically either `FftDirection::Forward` (Time to Frequency) or
244    /// `FftDirection::Inverse` (Frequency to Time).
245    fn direction(&self) -> FftDirection;
246    /// Returns the **length** (size N) of the input and output complex vectors.
247    ///
248    /// This is the number of complex elements that the executor is designed to process.
249    fn length(&self) -> usize;
250    /// Returns the required scratch buffer length for
251    /// [`execute_with_scratch`].
252    /// The returned size is **not a stable constant** and may change
253    /// between crate versions or algorithm implementations.
254    /// Always query this value dynamically.
255    fn scratch_length(&self) -> usize;
256    /// Returns the required scratch buffer length for
257    /// [`execute_out_of_place_with_scratch`].
258    /// The returned size is **not a stable constant** and may change
259    /// between crate versions or algorithm implementations.
260    /// Always query this value dynamically.
261    fn out_of_place_scratch_length(&self) -> usize;
262    /// Returns the required scratch buffer length for
263    /// [`execute_destructive_with_scratch`].
264    /// The returned size is **not a stable constant** and may change
265    /// between crate versions or algorithm implementations.
266    /// Always query this value dynamically.
267    fn destructive_scratch_length(&self) -> usize;
268}
269
270static PRIME_CACHE_F: OnceLock<RwLock<HashMap<usize, Arc<dyn FftExecutor<f32> + Send + Sync>>>> =
271    OnceLock::new();
272
273static PRIME_CACHE_B: OnceLock<RwLock<HashMap<usize, Arc<dyn FftExecutor<f32> + Send + Sync>>>> =
274    OnceLock::new();
275
276static PRIME_CACHE_DF: OnceLock<RwLock<HashMap<usize, Arc<dyn FftExecutor<f64> + Send + Sync>>>> =
277    OnceLock::new();
278
279static PRIME_CACHE_DB: OnceLock<RwLock<HashMap<usize, Arc<dyn FftExecutor<f64> + Send + Sync>>>> =
280    OnceLock::new();
281
282pub(crate) trait FftPrimeCache<T> {
283    fn has_cached_prime(
284        n: usize,
285        fft_direction: FftDirection,
286    ) -> Option<Arc<dyn FftExecutor<T> + Send + Sync>>;
287    fn put_prime_to_cache(fft_direction: FftDirection, fft: Arc<dyn FftExecutor<T> + Send + Sync>);
288}
289
290impl FftPrimeCache<f32> for f32 {
291    fn has_cached_prime(
292        n: usize,
293        fft_direction: FftDirection,
294    ) -> Option<Arc<dyn FftExecutor<f32> + Send + Sync>> {
295        if n >= 4000 {
296            return None;
297        }
298        let cache = (match fft_direction {
299            FftDirection::Forward => &PRIME_CACHE_F,
300            FftDirection::Inverse => &PRIME_CACHE_B,
301        })
302        .get_or_init(|| RwLock::new(HashMap::new()));
303        cache.read().ok()?.get(&n).cloned()
304    }
305
306    fn put_prime_to_cache(
307        fft_direction: FftDirection,
308        fft: Arc<dyn FftExecutor<f32> + Send + Sync>,
309    ) {
310        let length = fft.length();
311        if length > 4000 {
312            return;
313        }
314        let cache = (match fft_direction {
315            FftDirection::Forward => &PRIME_CACHE_F,
316            FftDirection::Inverse => &PRIME_CACHE_B,
317        })
318        .get_or_init(|| RwLock::new(HashMap::new()));
319        _ = cache.write().ok().and_then(|mut x| x.insert(length, fft));
320    }
321}
322
323impl FftPrimeCache<f64> for f64 {
324    fn has_cached_prime(
325        n: usize,
326        fft_direction: FftDirection,
327    ) -> Option<Arc<dyn FftExecutor<f64> + Send + Sync>> {
328        if n >= 4000 {
329            return None;
330        }
331        let cache = (match fft_direction {
332            FftDirection::Forward => &PRIME_CACHE_DF,
333            FftDirection::Inverse => &PRIME_CACHE_DB,
334        })
335        .get_or_init(|| RwLock::new(HashMap::new()));
336        cache.read().ok()?.get(&n).cloned()
337    }
338
339    fn put_prime_to_cache(
340        fft_direction: FftDirection,
341        fft: Arc<dyn FftExecutor<f64> + Send + Sync>,
342    ) {
343        let length = fft.length();
344        if length > 4000 {
345            return;
346        }
347        let cache = (match fft_direction {
348            FftDirection::Forward => &PRIME_CACHE_DF,
349            FftDirection::Inverse => &PRIME_CACHE_DB,
350        })
351        .get_or_init(|| RwLock::new(HashMap::new()));
352        _ = cache.write().ok().and_then(|mut x| x.insert(length, fft));
353    }
354}
355
356pub struct Zaft {}
357
358impl Zaft {
359    fn could_do_split_mixed_radix() -> bool {
360        #[cfg(all(target_arch = "x86_64", feature = "avx"))]
361        if std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma")
362        {
363            return true;
364        }
365        #[cfg(all(target_arch = "aarch64", feature = "neon"))]
366        {
367            true
368        }
369        #[cfg(not(all(target_arch = "aarch64", feature = "neon")))]
370        {
371            false
372        }
373    }
374
375    fn try_split_mixed_radix_butterflies<T: FftSample>(
376        _n_length: u64,
377        _q_length: u64,
378        _direction: FftDirection,
379    ) -> Result<Option<Arc<dyn FftExecutor<T> + Send + Sync>>, ZaftError>
380    where
381        f64: AsPrimitive<T>,
382    {
383        #[cfg(not(any(
384            all(target_arch = "aarch64", feature = "neon"),
385            all(target_arch = "x86_64", feature = "avx")
386        )))]
387        {
388            Ok(None)
389        }
390        #[cfg(all(target_arch = "x86_64", feature = "avx"))]
391        if !std::arch::is_x86_feature_detected!("avx2")
392            || !std::arch::is_x86_feature_detected!("fma")
393        {
394            return Ok(None);
395        }
396        #[cfg(any(
397            all(target_arch = "aarch64", feature = "neon"),
398            all(target_arch = "x86_64", feature = "avx")
399        ))]
400        {
401            let min_length = _n_length.min(_q_length);
402            let max_length = _n_length.max(_q_length);
403
404            if !(2..=16).contains(&min_length) {
405                // If no butterfly exists, return None
406                return Ok(None);
407            }
408
409            // 1. Get the initial FFT strategy regardless of the butterfly size.
410            let q_fft = Zaft::strategy(max_length as usize, _direction)?;
411
412            let q_fft_opt = match min_length {
413                2 => T::mixed_radix_butterfly2(q_fft),
414                3 => T::mixed_radix_butterfly3(q_fft),
415                4 => T::mixed_radix_butterfly4(q_fft),
416                5 => T::mixed_radix_butterfly5(q_fft),
417                6 => T::mixed_radix_butterfly6(q_fft),
418                7 => T::mixed_radix_butterfly7(q_fft),
419                8 => T::mixed_radix_butterfly8(q_fft),
420                9 => T::mixed_radix_butterfly9(q_fft),
421                10 => T::mixed_radix_butterfly10(q_fft),
422                11 => T::mixed_radix_butterfly11(q_fft),
423                12 => T::mixed_radix_butterfly12(q_fft),
424                13 => T::mixed_radix_butterfly13(q_fft),
425                14 => T::mixed_radix_butterfly14(q_fft),
426                15 => T::mixed_radix_butterfly15(q_fft),
427                16 => T::mixed_radix_butterfly16(q_fft),
428                // This arm is covered by the early exit, but is required if the early exit is removed.
429                _ => unreachable!("min_length is outside the supported range [2, 16]."),
430            };
431
432            // 3. Handle the result once.
433            if let Some(q_fft_opt) = q_fft_opt? {
434                Ok(Some(q_fft_opt))
435            } else {
436                Ok(None)
437            }
438        }
439    }
440
441    fn make_mixed_radix<T: FftSample>(
442        direction: FftDirection,
443        prime_factors: PrimeFactors,
444    ) -> Result<Arc<dyn FftExecutor<T> + Send + Sync>, ZaftError>
445    where
446        f64: AsPrimitive<T>,
447    {
448        let factorization = &prime_factors.factorization;
449        let product = factorization.iter().map(|&x| x.0.pow(x.1)).product::<u64>();
450
451        let (n_length, q_length) = if product <= 529 {
452            match can_be_two_factors(factorization) {
453                None => match try_greedy_pure_power_split(factorization) {
454                    None => split_factors_closest(factorization),
455                    Some(values) => values,
456                },
457                Some(factors) => factors,
458            }
459        } else {
460            match try_greedy_pure_power_split(factorization) {
461                None => split_factors_closest(factorization),
462                Some(values) => values,
463            }
464        };
465
466        macro_rules! try_mixed_radix {
467            ($q: expr, $p: expr) => {{
468                return if let Some(executor) =
469                    Zaft::try_split_mixed_radix_butterflies($q as u64, $p as u64, direction)?
470                {
471                    Ok(executor)
472                } else {
473                    let p_fft = Zaft::strategy($q as usize, direction)?;
474                    let q_fft = Zaft::strategy($p as usize, direction)?;
475                    if $q < $p {
476                        T::mixed_radix(p_fft, q_fft)
477                    } else {
478                        T::mixed_radix(q_fft, p_fft)
479                    }
480                };
481            }};
482        }
483
484        if prime_factors.is_power_of_two_and_three() {
485            let product2 = prime_factors
486                .factorization
487                .iter()
488                .find(|x| x.0 == 2)
489                .map(|x| x.0.pow(x.1))
490                .expect("Factor of 2 must present in 2^n*3^m branch");
491            let product3 = prime_factors
492                .factorization
493                .iter()
494                .find(|x| x.0 == 3)
495                .map(|x| x.0.pow(x.1))
496                .expect("Factor of 3 must present in 2^n*3^m branch");
497
498            let factor2 = prime_factors.factor_of_2();
499            let factor3 = prime_factors.factor_of_3();
500
501            if factor2 == 1 && factor3 > 3 && T::butterfly54(direction).is_some() {
502                try_mixed_radix!(54, product / 54)
503            }
504
505            if product == 1536 {
506                try_mixed_radix!(8, 192)
507            }
508
509            if factor3 >= 1 && factor2 >= 4 {
510                if product.is_multiple_of(36)
511                    && product / 36 > 1
512                    && product / 36 <= 16
513                    && T::butterfly36(direction).is_some()
514                {
515                    try_mixed_radix!(36, product / 36)
516                }
517                if factor2 > factor3 {
518                    let mut factors_diff = 2u64.pow(factor2 - factor3);
519                    let mut remainder_factor = product / factors_diff;
520                    if remainder_factor <= 8 {
521                        remainder_factor *= 2;
522                        factors_diff /= 2;
523                    }
524                    try_mixed_radix!(factors_diff, remainder_factor)
525                }
526                if product.is_multiple_of(48)
527                    && product / 48 > 1
528                    && product / 48 <= 16
529                    && T::butterfly48(direction).is_some()
530                {
531                    try_mixed_radix!(48, product / 48)
532                }
533            }
534
535            return if let Some(executor) =
536                Zaft::try_split_mixed_radix_butterflies(product2, product3, direction)?
537            {
538                Ok(executor)
539            } else {
540                let p_fft = Zaft::strategy(product2 as usize, direction)?;
541                let q_fft = Zaft::strategy(product3 as usize, direction)?;
542                T::mixed_radix(p_fft, q_fft)
543            };
544        } else if prime_factors.is_power_of_two_and_five() {
545            let factor_of_5 = prime_factors.factor_of_5();
546            let factor_of_2 = prime_factors.factor_of_2();
547            if factor_of_5 == 1 {
548                if factor_of_2 >= 8 && factor_of_2 != 9 {
549                    try_mixed_radix!(5, product / 5)
550                }
551                if (2..=6).contains(&factor_of_2) {
552                    try_mixed_radix!(20, product / 20)
553                }
554            } else if factor_of_5 == 2 {
555                if factor_of_2 >= 3 && T::butterfly100(direction).is_some() {
556                    #[cfg(all(target_arch = "aarch64", feature = "neon"))]
557                    {
558                        try_mixed_radix!(product / 100, 100)
559                    }
560                    #[cfg(all(target_arch = "x86_64", feature = "avx"))]
561                    {
562                        use crate::util::has_valid_avx;
563                        if has_valid_avx() {
564                            try_mixed_radix!(product / 100, 100)
565                        }
566                    }
567                }
568            } else if factor_of_5 == 3 {
569                #[cfg(any(
570                    all(target_arch = "aarch64", feature = "neon"),
571                    all(target_arch = "x86_64", feature = "avx")
572                ))]
573                if product == 500 {
574                    try_mixed_radix!(5, 100)
575                }
576            }
577        } else if prime_factors.is_power_of_three_and_five() {
578            let factor_of_5 = prime_factors.factor_of_5();
579            let factor_of_3 = prime_factors.factor_of_3();
580            if factor_of_5 == 1 && factor_of_3 > 1 {
581                try_mixed_radix!(5, product / 5)
582            } else if factor_of_5 == 2 && factor_of_5 > 1 && factor_of_3 > 2 && factor_of_3 < 10 {
583                // 225 is more effective with mixed radix [9,25]
584                try_mixed_radix!(25, product / 25)
585            }
586        } else if prime_factors.is_power_of_two_and_seven() {
587            let factor_of_7 = prime_factors.factor_of_7();
588            let factor_of_2 = prime_factors.factor_of_2();
589            if factor_of_2 > 1 && factor_of_7 == 1 {
590                try_mixed_radix!(14, product / 14)
591            }
592        } else if prime_factors.has_power_of_five_and_seven() {
593            let factor_of_7 = prime_factors.factor_of_7();
594            let factor_of_5 = prime_factors.factor_of_5();
595            #[allow(clippy::collapsible_if)]
596            if factor_of_7 == 1 || factor_of_5 == 1 {
597                if product == 560 || product == 2240 {
598                    if T::butterfly35(direction).is_some() {
599                        try_mixed_radix!(35, product / 35)
600                    }
601                }
602            }
603            if (product == 210
604                || product == 280
605                || product == 315
606                || product == 350
607                || product == 420)
608                && T::butterfly35(direction).is_some()
609            {
610                try_mixed_radix!(35, product / 35)
611            }
612            if prime_factors.is_power_of_five_and_seven() && (factor_of_7 > 2 && factor_of_5 > 2) {
613                let product7 = prime_factors
614                    .factorization
615                    .iter()
616                    .find(|x| x.0 == 7)
617                    .map(|x| x.0.pow(x.1))
618                    .expect("Power of 7 should exist if factor of 5^n*7^m branch");
619                let product5 = prime_factors
620                    .factorization
621                    .iter()
622                    .find(|x| x.0 == 5)
623                    .map(|x| x.0.pow(x.1))
624                    .expect("Power of 5 should exist if factor of 5^n*7^m branch");
625                let p_fft = Zaft::strategy(product5 as usize, direction)?;
626                let q_fft = Zaft::strategy(product7 as usize, direction)?;
627                return if product5 < product7 {
628                    T::mixed_radix(p_fft, q_fft)
629                } else {
630                    T::mixed_radix(q_fft, p_fft)
631                };
632            }
633        } else if prime_factors.has_power_of_two_and_three() {
634            #[allow(clippy::collapsible_if)]
635            if (product == 84
636                || product == 294
637                || product == 252
638                || product == 378
639                || product == 504
640                || product == 672
641                || product == 756)
642                && T::butterfly42(direction).is_some()
643            {
644                try_mixed_radix!(42, product / 42)
645            }
646            let factor_of_2 = prime_factors.factor_of_2();
647            let factor_of_3 = prime_factors.factor_of_3();
648            let factor_of_5 = prime_factors.factor_of_5();
649
650            if (factor_of_2 == 1 && factor_of_3 == 1 && factor_of_5 > 1)
651                && T::butterfly30(direction).is_some()
652            {
653                // factor out 30
654                try_mixed_radix!(30, product / 30)
655            }
656
657            if ((product.is_multiple_of(144) && (product / 144) <= 16)
658                || product == 5040
659                || product == 4896
660                || product == 8496
661                || product == 8352)
662                && T::butterfly144(direction).is_some()
663            {
664                // factor out 144
665                try_mixed_radix!(product / 144, 144)
666            }
667
668            if ((product.is_multiple_of(72) && (product / 72) <= 16)
669                || product == 2088
670                || product == 3240
671                || product == 3816
672                || product == 4248)
673                && T::butterfly72(direction).is_some()
674            {
675                // factor out 72
676                try_mixed_radix!(product / 72, 72)
677            }
678
679            if product == 858 && T::butterfly66(direction).is_some() {
680                // factor out 66
681                try_mixed_radix!(66, product / 66)
682            }
683        }
684
685        if product.is_multiple_of(63)
686            && product / 63 <= 16
687            && product != 126
688            && T::butterfly64(direction).is_some()
689        {
690            // factor out 63
691            try_mixed_radix!(63, product / 63)
692        }
693
694        if (product == 147 || product == 315 || product == 378 || product == 399)
695            && T::butterfly21(direction).is_some()
696        {
697            try_mixed_radix!(21, product / 21)
698        }
699
700        #[cfg(any(
701            all(target_arch = "aarch64", feature = "neon"),
702            all(target_arch = "x86_64", feature = "avx")
703        ))]
704        {
705            macro_rules! get_mixed_butterflies {
706                ($q: expr, $p: expr) => {{
707                    if let Some(executor) =
708                        Zaft::try_split_mixed_radix_butterflies($q as u64, $p as u64, direction)?
709                    {
710                        return Ok(executor);
711                    }
712                }};
713            }
714            let factor_2 = prime_factors.factor_of_2();
715            let rem2_8 = factor_2 % 3;
716            if product.is_multiple_of(10) {
717                get_mixed_butterflies!(10, product / 10)
718            }
719            if factor_2 > 0 {
720                if rem2_8 == 1 {
721                    get_mixed_butterflies!(2, product / 2)
722                }
723                if rem2_8 == 2 {
724                    get_mixed_butterflies!(4, product / 4)
725                }
726                get_mixed_butterflies!(8, product / 8)
727            }
728            if product.is_multiple_of(14) {
729                get_mixed_butterflies!(14, product / 14)
730            }
731            if product.is_multiple_of(15) {
732                get_mixed_butterflies!(15, product / 15)
733            }
734            if product.is_multiple_of(12) {
735                get_mixed_butterflies!(12, product / 12)
736            }
737            if product.is_multiple_of(9) {
738                get_mixed_butterflies!(9, product / 9)
739            }
740            if product.is_multiple_of(7) {
741                get_mixed_butterflies!(7, product / 7)
742            }
743            if product.is_multiple_of(13) {
744                get_mixed_butterflies!(13, product / 13)
745            }
746            if product.is_multiple_of(11) {
747                get_mixed_butterflies!(11, product / 11)
748            }
749            if product.is_multiple_of(5) {
750                get_mixed_butterflies!(5, product / 5)
751            }
752            if product.is_multiple_of(3) {
753                get_mixed_butterflies!(3, product / 3)
754            }
755            match Zaft::try_split_mixed_radix_butterflies(n_length, q_length, direction) {
756                Ok(value) => match value {
757                    None => {}
758                    Some(executor) => return Ok(executor),
759                },
760                Err(err) => return Err(err),
761            }
762        }
763
764        let p_fft = Zaft::strategy(n_length as usize, direction)?;
765        let q_fft = Zaft::strategy(q_length as usize, direction)?;
766        if num_integer::gcd(q_length, n_length) == 1 && q_length < 33 && n_length <= 33 {
767            T::good_thomas(p_fft, q_fft)
768        } else {
769            T::mixed_radix(p_fft, q_fft)
770        }
771    }
772
773    fn make_prime<T: FftSample>(
774        n: usize,
775        direction: FftDirection,
776    ) -> Result<Arc<dyn FftExecutor<T> + Send + Sync>, ZaftError>
777    where
778        f64: AsPrimitive<T>,
779    {
780        if let Some(cached) = T::has_cached_prime(n, direction) {
781            return Ok(cached);
782        }
783        let convolve_prime = PrimeFactors::from_number(n as u64 - 1);
784        if n <= 6000 {
785            let bluesteins = [
786                ALWAYS_BLUESTEIN_1000.as_slice(),
787                ALWAYS_BLUESTEIN_2000.as_slice(),
788                ALWAYS_BLUESTEIN_3000.as_slice(),
789                ALWAYS_BLUESTEIN_4000.as_slice(),
790                ALWAYS_BLUESTEIN_5000.as_slice(),
791                ALWAYS_BLUESTEIN_6000.as_slice(),
792            ];
793            let subset = bluesteins[n / 1000];
794            if subset.contains(&n) {
795                return Zaft::make_bluestein(n, direction);
796            }
797            return Zaft::make_raders(n, direction);
798        }
799        // n-1 may result in Cunningham chain, and we want to avoid compute multiple prime numbers FFT at once
800        let big_factor = convolve_prime.factorization.iter().any(|x| x.0 > 31);
801        let new_prime = if !big_factor {
802            Zaft::make_raders(n, direction)
803        } else {
804            Zaft::make_bluestein(n, direction)
805        };
806        let fft_executor = new_prime?;
807        T::put_prime_to_cache(direction, fft_executor.clone());
808        Ok(fft_executor)
809    }
810
811    fn make_raders<T: FftSample>(
812        n: usize,
813        direction: FftDirection,
814    ) -> Result<Arc<dyn FftExecutor<T> + Send + Sync>, ZaftError>
815    where
816        f64: AsPrimitive<T>,
817    {
818        let convolve_fft = Zaft::strategy(n - 1, direction);
819        T::raders(convolve_fft?, n, direction)
820    }
821
822    fn make_bluestein<T: FftSample>(
823        n: usize,
824        direction: FftDirection,
825    ) -> Result<Arc<dyn FftExecutor<T> + Send + Sync>, ZaftError>
826    where
827        f64: AsPrimitive<T>,
828    {
829        // we want to use bluestein's algorithm. we have a free choice of which inner FFT length to use
830        // the only restriction is that it has to be (2 * len - 1) or larger. So we want the fastest FFT we can compute at or above that size.
831
832        // the most obvious choice is the next-highest power of two, but there's one trick we can pull to get a smaller fft that we can be 100% certain will be faster
833        let min_inner_len = 2 * n - 1;
834        let inner_len_pow2 = min_inner_len.checked_next_power_of_two().unwrap();
835        let inner_len_factor3 = inner_len_pow2 / 4 * 3;
836
837        let inner_len = if inner_len_factor3 >= min_inner_len {
838            inner_len_factor3
839        } else {
840            inner_len_pow2
841        };
842        let convolve_fft = Zaft::strategy(inner_len, direction)?;
843        T::bluestein(convolve_fft, n, direction)
844    }
845
846    fn plan_butterfly<T: FftSample>(
847        n: usize,
848        fft_direction: FftDirection,
849    ) -> Option<Result<Arc<dyn FftExecutor<T> + Send + Sync>, ZaftError>> {
850        match n {
851            1 => return Some(T::butterfly1(fft_direction)),
852            2 => return Some(T::butterfly2(fft_direction)),
853            3 => return Some(T::butterfly3(fft_direction)),
854            4 => return Some(T::butterfly4(fft_direction)),
855            5 => return Some(T::butterfly5(fft_direction)),
856            6 => return Some(T::butterfly6(fft_direction)),
857            7 => return Some(T::butterfly7(fft_direction)),
858            8 => return Some(T::butterfly8(fft_direction)),
859            9 => return Some(T::butterfly9(fft_direction)),
860            10 => return Some(T::butterfly10(fft_direction)),
861            11 => return Some(T::butterfly11(fft_direction)),
862            12 => return Some(T::butterfly12(fft_direction)),
863            13 => return Some(T::butterfly13(fft_direction)),
864            14 => return Some(T::butterfly14(fft_direction)),
865            15 => return Some(T::butterfly15(fft_direction)),
866            16 => return Some(T::butterfly16(fft_direction)),
867            17 => return Some(T::butterfly17(fft_direction)),
868            18 => return Some(T::butterfly18(fft_direction)),
869            19 => return Some(T::butterfly19(fft_direction)),
870            20 => return Some(T::butterfly20(fft_direction)),
871            21 => {
872                return T::butterfly21(fft_direction).map(Ok);
873            }
874            23 => return Some(T::butterfly23(fft_direction)),
875            24 => {
876                return T::butterfly24(fft_direction).map(Ok);
877            }
878            25 => return Some(T::butterfly25(fft_direction)),
879            27 => return Some(T::butterfly27(fft_direction)),
880            28 => return T::butterfly28(fft_direction).map(Ok),
881            29 => return Some(T::butterfly29(fft_direction)),
882            30 => {
883                return T::butterfly30(fft_direction).map(Ok);
884            }
885            31 => return Some(T::butterfly31(fft_direction)),
886            32 => return Some(T::butterfly32(fft_direction)),
887            35 => {
888                return T::butterfly35(fft_direction).map(Ok);
889            }
890            36 => {
891                return T::butterfly36(fft_direction).map(Ok);
892            }
893            37 => return Some(T::butterfly37(fft_direction)),
894            40 => {
895                return T::butterfly40(fft_direction).map(Ok);
896            }
897            41 => return Some(T::butterfly41(fft_direction)),
898            42 => {
899                return T::butterfly42(fft_direction).map(Ok);
900            }
901            48 => {
902                return T::butterfly48(fft_direction).map(Ok);
903            }
904            49 => {
905                return T::butterfly49(fft_direction).map(Ok);
906            }
907            54 => {
908                return T::butterfly54(fft_direction).map(Ok);
909            }
910            63 => {
911                return T::butterfly63(fft_direction).map(Ok);
912            }
913            64 => {
914                return T::butterfly64(fft_direction).map(Ok);
915            }
916            66 => {
917                return T::butterfly66(fft_direction).map(Ok);
918            }
919            70 => {
920                return T::butterfly70(fft_direction).map(Ok);
921            }
922            72 => {
923                return T::butterfly72(fft_direction).map(Ok);
924            }
925            78 => {
926                return T::butterfly78(fft_direction).map(Ok);
927            }
928            81 => {
929                return T::butterfly81(fft_direction).map(Ok);
930            }
931            88 => {
932                return T::butterfly88(fft_direction).map(Ok);
933            }
934            96 => {
935                return T::butterfly96(fft_direction).map(Ok);
936            }
937            100 => {
938                return T::butterfly100(fft_direction).map(Ok);
939            }
940            108 => {
941                return T::butterfly108(fft_direction).map(Ok);
942            }
943            121 => {
944                return T::butterfly121(fft_direction).map(Ok);
945            }
946            125 => {
947                return T::butterfly125(fft_direction).map(Ok);
948            }
949            128 => {
950                return T::butterfly128(fft_direction).map(Ok);
951            }
952            144 => {
953                return T::butterfly144(fft_direction).map(Ok);
954            }
955            169 => {
956                return T::butterfly169(fft_direction).map(Ok);
957            }
958            192 => {
959                return T::butterfly192(fft_direction).map(Ok);
960            }
961            216 => {
962                return T::butterfly216(fft_direction).map(Ok);
963            }
964            243 => {
965                return T::butterfly243(fft_direction).map(Ok);
966            }
967            256 => {
968                return T::butterfly256(fft_direction).map(Ok);
969            }
970            512 => {
971                return T::butterfly512(fft_direction).map(Ok);
972            }
973            1024 => {
974                return T::butterfly1024(fft_direction).map(Ok);
975            }
976            _ => {}
977        }
978        None
979    }
980
981    pub(crate) fn strategy<T: FftSample>(
982        n: usize,
983        fft_direction: FftDirection,
984    ) -> Result<Arc<dyn FftExecutor<T> + Send + Sync>, ZaftError>
985    where
986        f64: AsPrimitive<T>,
987    {
988        if n == 0 {
989            return Err(ZaftError::ZeroSizedFft);
990        }
991        if n <= 512 || n == 1024 {
992            if let Some(bf) = Zaft::plan_butterfly(n, fft_direction) {
993                return bf;
994            }
995        }
996        let prime_factors = PrimeFactors::from_number(n as u64);
997        if prime_factors.is_power_of_three {
998            // Use Radix-3 if divisible by 3
999            T::radix3(n, fft_direction)
1000        } else if prime_factors.is_power_of_five {
1001            // Use Radix-5 if power of 5
1002            T::radix5(n, fft_direction)
1003        } else if prime_factors.is_power_of_two {
1004            // Use Radix-4 if a power of 2
1005            if Zaft::could_do_split_mixed_radix() {
1006                if n == 2048 {
1007                    if let Some(bf) =
1008                        T::mixed_radix_butterfly8(Zaft::strategy(n / 8, fft_direction)?)?
1009                    {
1010                        return Ok(bf);
1011                    }
1012                }
1013                let rem3 = prime_factors.factor_of_2() % 3;
1014                if rem3 == 2 {
1015                    if let Some(bf) =
1016                        T::mixed_radix_butterfly4(Zaft::strategy(n / 4, fft_direction)?)?
1017                    {
1018                        return Ok(bf);
1019                    }
1020                } else if rem3 == 1 {
1021                    let has1024 = T::butterfly1024(fft_direction).is_some();
1022                    if has1024 {
1023                        if let Some(bf) =
1024                            T::mixed_radix_butterfly8(Zaft::strategy(n / 8, fft_direction)?)?
1025                        {
1026                            return Ok(bf);
1027                        }
1028                    }
1029                    if let Some(bf) =
1030                        T::mixed_radix_butterfly2(Zaft::strategy(n / 2, fft_direction)?)?
1031                    {
1032                        return Ok(bf);
1033                    }
1034                }
1035                if let Some(bf) = T::mixed_radix_butterfly8(Zaft::strategy(n / 8, fft_direction)?)?
1036                {
1037                    return Ok(bf);
1038                }
1039            }
1040            T::radix4(n, fft_direction)
1041        } else if prime_factors.is_power_of_six {
1042            T::radix6(n, fft_direction)
1043        } else if prime_factors.is_power_of_seven {
1044            T::radix7(n, fft_direction)
1045        } else if prime_factors.is_power_of_ten {
1046            T::radix10(n, fft_direction)
1047        } else if prime_factors.is_power_of_eleven {
1048            T::radix11(n, fft_direction)
1049        } else if prime_factors.is_power_of_thirteen {
1050            #[cfg(all(target_arch = "x86_64", feature = "avx"))]
1051            {
1052                if Zaft::could_do_split_mixed_radix() {
1053                    let r = n / 13;
1054                    if r == 13 {
1055                        let right_fft = T::butterfly13(fft_direction)?;
1056                        if let Ok(Some(v)) = T::mixed_radix_butterfly13(right_fft) {
1057                            return Ok(v);
1058                        }
1059                    }
1060                    let right_fft = T::radix13(r, fft_direction)?;
1061                    if let Ok(Some(v)) = T::mixed_radix_butterfly13(right_fft) {
1062                        return Ok(v);
1063                    }
1064                }
1065            }
1066            T::radix13(n, fft_direction)
1067        } else if prime_factors.may_be_represented_in_mixed_radix() {
1068            Zaft::make_mixed_radix(fft_direction, prime_factors)
1069        } else if prime_factors.is_prime() {
1070            Zaft::make_prime(n, fft_direction)
1071        } else {
1072            T::dft(n, fft_direction)
1073        }
1074    }
1075
1076    /// Creates a Real-to-Complex (R2C) FFT plan executor for single-precision floating-point numbers (`f32`).
1077    ///
1078    /// This plan transforms a real-valued input array of length `n` into a complex
1079    /// output array of length `n/2 + 1` (or `n` if odd, with a special handling for the last complex element).
1080    ///
1081    /// # Parameters
1082    /// * `n`: The **length** of the real-valued input vector.
1083    ///
1084    /// # Returns
1085    /// A `Result` containing an `Arc` to a dynamically dispatched `R2CFftExecutor<f32>` plan,
1086    /// or a `ZaftError` if the plan cannot be generated.
1087    pub fn make_r2c_fft_f32(
1088        n: usize,
1089    ) -> Result<Arc<dyn R2CFftExecutor<f32> + Send + Sync>, ZaftError> {
1090        strategy_r2c(n)
1091    }
1092
1093    /// Creates a Complex-to-Real (C2R) Inverse FFT plan executor for single-precision floating-point numbers (`f32`).
1094    ///
1095    /// This plan transforms a complex input array (the result of an R2C FFT)
1096    /// back into a real-valued output array of length `n`.
1097    ///
1098    /// # Parameters
1099    /// * `n`: The **length** of the final real-valued output vector.
1100    ///
1101    /// # Returns
1102    /// A `Result` containing an `Arc` to a dynamically dispatched `C2RFftExecutor<f32>` plan,
1103    /// or a `ZaftError` if the plan cannot be generated.
1104    pub fn make_c2r_fft_f32(
1105        n: usize,
1106    ) -> Result<Arc<dyn C2RFftExecutor<f32> + Send + Sync>, ZaftError> {
1107        if n == 1 {
1108            return Ok(Arc::new(OneSizedRealFft {
1109                phantom_data: Default::default(),
1110            }));
1111        }
1112        if n.is_multiple_of(2) {
1113            C2RFftEvenInterceptor::install(n, Zaft::strategy(n / 2, FftDirection::Inverse)?)
1114                .map(|x| Arc::new(x) as Arc<dyn C2RFftExecutor<f32> + Send + Sync>)
1115        } else {
1116            C2RFftOddInterceptor::install(n, Zaft::strategy(n, FftDirection::Inverse)?)
1117                .map(|x| Arc::new(x) as Arc<dyn C2RFftExecutor<f32> + Send + Sync>)
1118        }
1119    }
1120
1121    /// Creates a standard Complex-to-Complex Forward FFT plan executor for single-precision floating-point numbers (`f32`).
1122    ///
1123    /// This is used for a standard Discrete Fourier Transform (DFT) where both input and output are complex.
1124    ///
1125    /// # Parameters
1126    /// * `n`: The **length** of the input/output complex vectors.
1127    ///
1128    /// # Returns
1129    /// A `Result` containing an `Arc` to a dynamically dispatched `FftExecutor<f32>` plan,
1130    /// or a `ZaftError` if the plan cannot be generated.
1131    pub fn make_forward_fft_f32(
1132        n: usize,
1133    ) -> Result<Arc<dyn FftExecutor<f32> + Send + Sync>, ZaftError> {
1134        Zaft::strategy(n, FftDirection::Forward)
1135    }
1136
1137    /// Creates a standard Complex-to-Complex Forward FFT plan executor for double-precision floating-point numbers (`f64`).
1138    ///
1139    /// # Parameters
1140    /// * `n`: The **length** of the input/output complex vectors.
1141    ///
1142    /// # Returns
1143    /// A `Result` containing an `Arc` to a dynamically dispatched `FftExecutor<f64>` plan,
1144    /// or a `ZaftError` if the plan cannot be generated.
1145    pub fn make_forward_fft_f64(
1146        n: usize,
1147    ) -> Result<Arc<dyn FftExecutor<f64> + Send + Sync>, ZaftError> {
1148        Zaft::strategy(n, FftDirection::Forward)
1149    }
1150
1151    /// Creates a Complex-to-Real (C2R) Inverse FFT plan executor for double-precision floating-point numbers (`f64`).
1152    ///
1153    /// This is the double-precision version of `make_c2r_fft_f32`.
1154    ///
1155    /// # Parameters
1156    /// * `n`: The **length** of the final real-valued output vector.
1157    ///
1158    /// # Returns
1159    /// A `Result` containing an `Arc` to a dynamically dispatched `C2RFftExecutor<f64>` plan,
1160    /// or a `ZaftError` if the plan cannot be generated.
1161    pub fn make_c2r_fft_f64(
1162        n: usize,
1163    ) -> Result<Arc<dyn C2RFftExecutor<f64> + Send + Sync>, ZaftError> {
1164        if n == 1 {
1165            return Ok(Arc::new(OneSizedRealFft {
1166                phantom_data: Default::default(),
1167            }));
1168        }
1169        if n.is_multiple_of(2) {
1170            C2RFftEvenInterceptor::install(n, Zaft::strategy(n / 2, FftDirection::Inverse)?)
1171                .map(|x| Arc::new(x) as Arc<dyn C2RFftExecutor<f64> + Send + Sync>)
1172        } else {
1173            C2RFftOddInterceptor::install(n, Zaft::strategy(n, FftDirection::Inverse)?)
1174                .map(|x| Arc::new(x) as Arc<dyn C2RFftExecutor<f64> + Send + Sync>)
1175        }
1176    }
1177
1178    /// Creates a Real-to-Complex (R2C) FFT plan executor for double-precision floating-point numbers (`f64`).
1179    ///
1180    /// This is the double-precision version of `make_r2c_fft_f32`.
1181    ///
1182    /// # Parameters
1183    /// * `n`: The **length** of the real-valued input vector.
1184    ///
1185    /// # Returns
1186    /// A `Result` containing an `Arc` to a dynamically dispatched `R2CFftExecutor<f64>` plan,
1187    /// or a `ZaftError` if the plan cannot be generated.
1188    pub fn make_r2c_fft_f64(
1189        n: usize,
1190    ) -> Result<Arc<dyn R2CFftExecutor<f64> + Send + Sync>, ZaftError> {
1191        strategy_r2c(n)
1192    }
1193
1194    /// Creates a standard Complex-to-Complex Inverse FFT plan executor for single-precision floating-point numbers (`f32`).
1195    ///
1196    /// This is the inverse transformation for the standard DFT, used to convert frequency-domain data
1197    /// back into the time domain.
1198    ///
1199    /// # Parameters
1200    /// * `n`: The **length** of the input/output complex vectors.
1201    ///
1202    /// # Returns
1203    /// A `Result` containing an `Arc` to a dynamically dispatched `FftExecutor<f32>` plan,
1204    /// or a `ZaftError` if the plan cannot be generated.
1205    pub fn make_inverse_fft_f32(
1206        n: usize,
1207    ) -> Result<Arc<dyn FftExecutor<f32> + Send + Sync>, ZaftError> {
1208        Zaft::strategy(n, FftDirection::Inverse)
1209    }
1210
1211    /// Creates a standard Complex-to-Complex Inverse FFT plan executor for double-precision floating-point numbers (`f64`).
1212    ///
1213    /// # Parameters
1214    /// * `n`: The **length** of the input/output complex vectors.
1215    ///
1216    /// # Returns
1217    /// A `Result` containing an `Arc` to a dynamically dispatched `FftExecutor<f64>` plan,
1218    /// or a `ZaftError` if the plan cannot be generated.
1219    pub fn make_inverse_fft_f64(
1220        n: usize,
1221    ) -> Result<Arc<dyn FftExecutor<f64> + Send + Sync>, ZaftError> {
1222        Zaft::strategy(n, FftDirection::Inverse)
1223    }
1224
1225    /// Creates a high-performance, two-dimensional Real-to-Complex (R2C) FFT plan executor for single-precision floating-point numbers (`f32`).
1226    ///
1227    /// This function constructs a plan for transforming a **real-valued** 2D input array (with dimensions `height x width`)
1228    /// into its frequency-domain representation. The executor is optimized for parallel
1229    /// execution across the specified number of threads.
1230    ///
1231    /// **The R2C 2D FFT is typically performed in two steps:**
1232    /// 1. A 1D R2C FFT is performed across the **rows** (or columns) of the input.
1233    /// 2. A 1D Complex-to-Complex (C2C) FFT is performed across the **columns** (or rows) of the intermediate complex data.
1234    ///
1235    /// # Parameters
1236    /// * `width`: The number of elements in the X-dimension (columns) of the 2D input array.
1237    /// * `height`: The number of elements in the Y-dimension (rows) of the 2D input array.
1238    /// * `thread_count`: The maximum number of threads the executor is allowed to use during execution.
1239    ///
1240    /// # Returns
1241    /// A `Result` containing an `Arc` to a dynamically dispatched `TwoDimensionalExecutorR2C<f32>` plan,
1242    /// or a `ZaftError` if the plan cannot be generated (e.g., due to invalid dimensions).
1243    ///
1244    /// # Note on Output Size
1245    /// The resulting frequency-domain complex data will typically have dimensions of `height x (width/2 + 1)`
1246    /// complex elements, leveraging the Hermitian symmetry property of real-input FFTs.
1247    pub fn make_2d_r2c_fft_f32(
1248        width: usize,
1249        height: usize,
1250        thread_count: usize,
1251    ) -> Result<Arc<dyn TwoDimensionalExecutorR2C<f32> + Send + Sync>, ZaftError> {
1252        if width * height == 0 {
1253            return Err(ZaftError::ZeroSizedFft);
1254        }
1255        let width_fft = Zaft::make_r2c_fft_f32(width)?;
1256        let height_fft = Zaft::make_forward_fft_f32(height)?;
1257        let width_scratch_length = width_fft.complex_scratch_length();
1258        let height_scratch_length = height_fft.scratch_length();
1259        Ok(Arc::new(TwoDimensionalR2C {
1260            width_r2c_executor: width_fft,
1261            height_c2c_executor: height_fft,
1262            thread_count: thread_count.max(1),
1263            width,
1264            height,
1265            transpose_width_to_height: f32::transpose_strategy((width / 2) + 1, height),
1266            width_scratch_length,
1267            height_scratch_length,
1268        }))
1269    }
1270
1271    /// Creates a high-performance, two-dimensional Real-to-Complex (R2C) FFT plan executor for **double-precision floating-point numbers** (`f64`).
1272    ///
1273    /// This function constructs a plan for transforming a **real-valued** 2D input array (with dimensions `height x width`)
1274    /// into its frequency-domain representation. The executor is highly optimized for parallel
1275    /// execution across the specified number of threads.
1276    ///
1277    /// **The R2C 2D FFT typically follows a row-column or column-row decomposition:**
1278    /// 1. A 1D R2C FFT is performed across the elements of one axis.
1279    /// 2. A 1D Complex-to-Complex (C2C) FFT is performed across the elements of the other axis on the intermediate complex data.
1280    ///
1281    /// This process efficiently computes the 2D transform while leveraging the computational savings offered
1282    /// by the real-valued nature of the input data.
1283    ///
1284    ///
1285    /// # Parameters
1286    /// * `width`: The number of elements in the X-dimension (**columns**) of the 2D input array.
1287    /// * `height`: The number of elements in the Y-dimension (**rows**) of the 2D input array.
1288    /// * `thread_count`: The maximum number of threads the executor is allowed to use during the parallel computation.
1289    ///
1290    /// # Returns
1291    /// A `Result` containing an `Arc` to a dynamically dispatched `TwoDimensionalExecutorR2C<f64>` plan,
1292    /// which is safe to use across threads (`Send + Sync`), or a `ZaftError` if the plan cannot be generated.
1293    ///
1294    /// # Note on Output Size
1295    /// Due to the resulting frequency-domain complex data will only store approximately half
1296    /// the data required for a full C2C transform. Specifically, the output complex array will typically have dimensions
1297    /// of `height x (width/2 + 1)` complex elements.
1298    pub fn make_2d_r2c_fft_f64(
1299        width: usize,
1300        height: usize,
1301        thread_count: usize,
1302    ) -> Result<Arc<dyn TwoDimensionalExecutorR2C<f64> + Send + Sync>, ZaftError> {
1303        if width * height == 0 {
1304            return Err(ZaftError::ZeroSizedFft);
1305        }
1306        let width_fft = Zaft::make_r2c_fft_f64(width)?;
1307        let height_fft = Zaft::make_forward_fft_f64(height)?;
1308        let width_scratch_length = width_fft.complex_scratch_length();
1309        let height_scratch_length = height_fft.scratch_length();
1310        Ok(Arc::new(TwoDimensionalR2C {
1311            width_r2c_executor: width_fft,
1312            height_c2c_executor: height_fft,
1313            thread_count: thread_count.max(1),
1314            width,
1315            height,
1316            transpose_width_to_height: f64::transpose_strategy((width / 2) + 1, height),
1317            width_scratch_length,
1318            height_scratch_length,
1319        }))
1320    }
1321
1322    /// Creates a 2D complex-to-complex FFT executor for f32 inputs.
1323    ///
1324    /// This function constructs a two-dimensional FFT executor that can perform
1325    /// forward or inverse FFTs on a width × height grid of complex f32 values.
1326    /// The executor internally manages separate FFTs along the width and height dimensions,
1327    /// and uses a transpose strategy for efficient computation.
1328    ///
1329    /// # Parameters
1330    ///
1331    /// * width - The number of columns in the input data grid.
1332    /// * height - The number of rows in the input data grid.
1333    /// * fft_direction - The direction of the FFT (Forward or Inverse).
1334    /// * thread_count - Number of threads to use for parallel computation (minimum 1).
1335    ///
1336    /// # Returns
1337    ///
1338    /// Returns a Result containing an Arc to a type implementing
1339    /// [TwoDimensionalFftExecutor<f32>], or a [ZaftError] if FFT creation fails.
1340    ///
1341    /// # Notes
1342    ///
1343    /// The internal width and height of the FFT executors are swapped for inverse FFTs
1344    /// to correctly handle the transformation.
1345    pub fn make_2d_c2c_fft_f32(
1346        width: usize,
1347        height: usize,
1348        fft_direction: FftDirection,
1349        thread_count: usize,
1350    ) -> Result<Arc<dyn TwoDimensionalFftExecutor<f32> + Send + Sync>, ZaftError> {
1351        if width * height == 0 {
1352            return Err(ZaftError::ZeroSizedFft);
1353        }
1354        let fft_width = match fft_direction {
1355            FftDirection::Forward => width,
1356            FftDirection::Inverse => height,
1357        };
1358        let fft_height = match fft_direction {
1359            FftDirection::Forward => height,
1360            FftDirection::Inverse => width,
1361        };
1362        let width_fft = match fft_direction {
1363            FftDirection::Forward => Zaft::make_forward_fft_f32(fft_width)?,
1364            FftDirection::Inverse => Zaft::make_inverse_fft_f32(fft_width)?,
1365        };
1366        let height_fft = match fft_direction {
1367            FftDirection::Forward => Zaft::make_forward_fft_f32(fft_height)?,
1368            FftDirection::Inverse => Zaft::make_inverse_fft_f32(fft_height)?,
1369        };
1370        let oof_scratch_width = width_fft.out_of_place_scratch_length();
1371        let inplace_scratch_height = height_fft.scratch_length();
1372        Ok(Arc::new(TwoDimensionalC2C {
1373            width_c2c_executor: width_fft,
1374            height_c2c_executor: height_fft,
1375            thread_count: thread_count.max(1),
1376            width: fft_width,
1377            height: fft_height,
1378            transpose_width_to_height: f32::transpose_strategy(fft_width, fft_height),
1379            oof_width_scratch_size: oof_scratch_width,
1380            height_scratch_size: inplace_scratch_height,
1381        }))
1382    }
1383
1384    /// Creates a 2D complex-to-complex FFT executor for f64 inputs.
1385    ///
1386    /// This function constructs a two-dimensional FFT executor that can perform
1387    /// forward or inverse FFTs on a width × height grid of complex f64 values.
1388    /// The executor internally manages separate FFTs along the width and height dimensions,
1389    /// and uses a transpose strategy for efficient computation.
1390    ///
1391    /// # Parameters
1392    ///
1393    /// * width - The number of columns in the input data grid.
1394    /// * height - The number of rows in the input data grid.
1395    /// * fft_direction - The direction of the FFT (Forward or Inverse).
1396    /// * thread_count - Number of threads to use for parallel computation (minimum 1).
1397    ///
1398    /// # Returns
1399    ///
1400    /// Returns a Result containing an Arc to a type implementing
1401    /// [TwoDimensionalFftExecutor<f64>], or a [ZaftError] if FFT creation fails.
1402    ///
1403    /// # Notes
1404    ///
1405    /// The internal width and height of the FFT executors are swapped for inverse FFTs
1406    /// to correctly handle the transformation.
1407    pub fn make_2d_c2c_fft_f64(
1408        width: usize,
1409        height: usize,
1410        fft_direction: FftDirection,
1411        thread_count: usize,
1412    ) -> Result<Arc<dyn TwoDimensionalFftExecutor<f64> + Send + Sync>, ZaftError> {
1413        if width * height == 0 {
1414            return Err(ZaftError::ZeroSizedFft);
1415        }
1416        let fft_width = match fft_direction {
1417            FftDirection::Forward => width,
1418            FftDirection::Inverse => height,
1419        };
1420        let fft_height = match fft_direction {
1421            FftDirection::Forward => height,
1422            FftDirection::Inverse => width,
1423        };
1424        let width_fft = match fft_direction {
1425            FftDirection::Forward => Zaft::make_forward_fft_f64(fft_width)?,
1426            FftDirection::Inverse => Zaft::make_inverse_fft_f64(fft_width)?,
1427        };
1428        let height_fft = match fft_direction {
1429            FftDirection::Forward => Zaft::make_forward_fft_f64(fft_height)?,
1430            FftDirection::Inverse => Zaft::make_inverse_fft_f64(fft_height)?,
1431        };
1432        let oof_scratch_width = width_fft.out_of_place_scratch_length();
1433        let inplace_scratch_height = height_fft.scratch_length();
1434        Ok(Arc::new(TwoDimensionalC2C {
1435            width_c2c_executor: width_fft,
1436            height_c2c_executor: height_fft,
1437            thread_count: thread_count.max(1),
1438            width: fft_width,
1439            height: fft_height,
1440            transpose_width_to_height: f64::transpose_strategy(fft_width, fft_height),
1441            oof_width_scratch_size: oof_scratch_width,
1442            height_scratch_size: inplace_scratch_height,
1443        }))
1444    }
1445
1446    /// Creates two-dimensional Complex-to-Real (C2R) Inverse FFT plan executor
1447    /// for **single-precision floating-point numbers** (`f32`).
1448    ///
1449    /// This plan transforms a **Hermitian symmetric complex** frequency-domain input array (the output of an R2C FFT)
1450    /// back into a **real-valued** time-domain output array of size `height x width`. The executor is configured
1451    /// for parallel execution across the specified number of threads.
1452    ///
1453    /// # Parameters
1454    /// * `width`: The number of elements in the X-dimension (**columns**) of the final real output.
1455    /// * `height`: The number of elements in the Y-dimension (**rows**) of the final real output.
1456    /// * `thread_count`: The maximum number of threads the executor is allowed to use during the parallel computation.
1457    ///
1458    /// # Returns
1459    /// A `Result` containing an `Arc` to a dynamically dispatched `TwoDimensionalExecutorC2R<f32>` plan,
1460    /// or a `ZaftError` if the plan cannot be generated (e.g., `ZeroSizedFft`).
1461    ///
1462    /// # Errors
1463    /// Returns `ZaftError::ZeroSizedFft` if `width * height` is zero.
1464    pub fn make_2d_c2r_fft_f32(
1465        width: usize,
1466        height: usize,
1467        thread_count: usize,
1468    ) -> Result<Arc<dyn TwoDimensionalExecutorC2R<f32> + Send + Sync>, ZaftError> {
1469        if width * height == 0 {
1470            return Err(ZaftError::ZeroSizedFft);
1471        }
1472        let width_fft = Zaft::make_c2r_fft_f32(width)?;
1473        let height_fft = Zaft::make_inverse_fft_f32(height)?;
1474        let width_scratch_length = width_fft.complex_scratch_length();
1475        let height_scratch_length = height_fft.scratch_length();
1476        Ok(Arc::new(TwoDimensionalC2R {
1477            width_c2r_executor: width_fft,
1478            height_c2c_executor: height_fft,
1479            thread_count: thread_count.max(1),
1480            width,
1481            height,
1482            transpose_height_to_width: f32::transpose_strategy(height, (width / 2) + 1),
1483            width_scratch_length,
1484            height_scratch_length,
1485        }))
1486    }
1487
1488    /// Creates two-dimensional Complex-to-Real (C2R) Inverse FFT plan executor
1489    /// for **double-precision floating-point numbers** (`f64`).
1490    ///
1491    /// This is the double-precision equivalent of `make_2d_c2r_fft_f32`. It transforms the complex,
1492    /// frequency-domain input back into a real-valued array of size `height x width`.
1493    ///
1494    /// # Parameters
1495    /// * `width`: The number of elements in the X-dimension (**columns**) of the final real output.
1496    /// * `height`: The number of elements in the Y-dimension (**rows**) of the final real output.
1497    /// * `thread_count`: The maximum number of threads the executor is allowed to use during the parallel computation.
1498    ///
1499    /// # Returns
1500    /// A `Result` containing an `Arc` to a dynamically dispatched `TwoDimensionalExecutorC2R<f64>` plan,
1501    /// or a `ZaftError` if the plan cannot be generated.
1502    ///
1503    /// # Errors
1504    /// Returns `ZaftError::ZeroSizedFft` if `width * height` is zero.
1505    pub fn make_2d_c2r_fft_f64(
1506        width: usize,
1507        height: usize,
1508        thread_count: usize,
1509    ) -> Result<Arc<dyn TwoDimensionalExecutorC2R<f64> + Send + Sync>, ZaftError> {
1510        if width * height == 0 {
1511            return Err(ZaftError::ZeroSizedFft);
1512        }
1513        let width_fft = Zaft::make_c2r_fft_f64(width)?;
1514        let height_fft = Zaft::make_inverse_fft_f64(height)?;
1515        let width_scratch_length = width_fft.complex_scratch_length();
1516        let height_scratch_length = height_fft.scratch_length();
1517        Ok(Arc::new(TwoDimensionalC2R {
1518            width_c2r_executor: width_fft,
1519            height_c2c_executor: height_fft,
1520            thread_count: thread_count.max(1),
1521            width,
1522            height,
1523            transpose_height_to_width: f64::transpose_strategy(height, (width / 2) + 1),
1524            width_scratch_length,
1525            height_scratch_length,
1526        }))
1527    }
1528}
1529
1530/// Specifies the direction of a Fast Fourier Transform (FFT) operation.
1531///
1532/// This enum is used to configure FFT executors, indicating whether the transform should
1533/// map from the time domain to the frequency domain, or vice versa.
1534#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
1535pub enum FftDirection {
1536    /// Represents the **Forward** transform, which typically maps data from the **time or spatial domain**
1537    /// into the **frequency domain**.
1538    Forward,
1539    /// Represents the **Inverse** transform, which maps data back from the **frequency domain**
1540    /// into the **time or spatial domain**.
1541    Inverse,
1542}
1543
1544impl FftDirection {
1545    pub fn inverse(self) -> FftDirection {
1546        match self {
1547            FftDirection::Forward => FftDirection::Inverse,
1548            FftDirection::Inverse => FftDirection::Forward,
1549        }
1550    }
1551}
1552
1553impl Display for FftDirection {
1554    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1555        match self {
1556            FftDirection::Forward => f.write_str("FftDirection::Forward"),
1557            FftDirection::Inverse => f.write_str("FftDirection::Inverse"),
1558        }
1559    }
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564    use crate::Zaft;
1565    use num_complex::Complex;
1566    use num_traits::Zero;
1567
1568    #[test]
1569    fn power_of_four() {
1570        fn is_power_of_four(n: u64) -> bool {
1571            n != 0 && (n & (n - 1)) == 0 && (n & 0x5555_5555_5555_5555) != 0
1572        }
1573        assert_eq!(is_power_of_four(4), true);
1574        assert_eq!(is_power_of_four(8), false);
1575        assert_eq!(is_power_of_four(16), true);
1576        assert_eq!(is_power_of_four(20), false);
1577    }
1578
1579    #[test]
1580    fn test_everything_f32() {
1581        for i in 1..2010 {
1582            let mut data = vec![Complex::new(0.0019528865, 0.); i];
1583            for (i, chunk) in data.iter_mut().enumerate() {
1584                *chunk = Complex::new(
1585                    -0.19528865 + i as f32 * 0.001,
1586                    0.0019528865 - i as f32 * 0.001,
1587                );
1588            }
1589            let zaft_exec = Zaft::make_forward_fft_f32(data.len()).expect("Failed to make FFT!");
1590            let zaft_inverse = Zaft::make_inverse_fft_f32(data.len()).expect("Failed to make FFT!");
1591            let reference_clone = data.clone();
1592            zaft_exec
1593                .execute(&mut data)
1594                .expect(&format!("Failed to execute forward FFT for size {i}!"));
1595            zaft_inverse
1596                .execute(&mut data)
1597                .expect(&format!("Failed to execute inverse FFT for size {i}!"));
1598            let data_len = 1. / data.len() as f32;
1599            for i in data.iter_mut() {
1600                *i *= data_len;
1601            }
1602            data.iter()
1603                .zip(reference_clone)
1604                .enumerate()
1605                .for_each(|(idx, (a, b))| {
1606                    assert!(
1607                        (a.re - b.re).abs() < 1e-2,
1608                        "a_re {}, b_re {} at {idx}, for size {i}",
1609                        a.re,
1610                        b.re
1611                    );
1612                    assert!(
1613                        (a.im - b.im).abs() < 1e-2,
1614                        "a_re {}, b_re {} at {idx}, for size {i}",
1615                        a.im,
1616                        b.im
1617                    );
1618                });
1619        }
1620    }
1621
1622    #[test]
1623    fn test_everything_oof_f32() {
1624        for i in 1..2010 {
1625            let mut data = vec![Complex::new(0.0019528865, 0.); i];
1626            let mut scratch = data.to_vec();
1627            for (i, chunk) in data.iter_mut().enumerate() {
1628                *chunk = Complex::new(
1629                    -0.19528865 + i as f32 * 0.001,
1630                    0.0019528865 - i as f32 * 0.001,
1631                );
1632            }
1633            let zaft_exec = Zaft::make_forward_fft_f32(data.len()).expect("Failed to make FFT!");
1634            let zaft_inverse = Zaft::make_inverse_fft_f32(data.len()).expect("Failed to make FFT!");
1635            let reference_clone = data.clone();
1636            zaft_exec
1637                .execute_out_of_place(&data, &mut scratch)
1638                .expect(&format!("Failed to execute forward FFT for size {i}!"));
1639            zaft_inverse
1640                .execute_out_of_place(&scratch, &mut data)
1641                .expect(&format!("Failed to execute inverse FFT for size {i}!"));
1642            let data_len = 1. / data.len() as f32;
1643            for i in data.iter_mut() {
1644                *i *= data_len;
1645            }
1646            data.iter()
1647                .zip(reference_clone)
1648                .enumerate()
1649                .for_each(|(idx, (a, b))| {
1650                    assert!(
1651                        (a.re - b.re).abs() < 1e-2,
1652                        "a_re {}, b_re {} at {idx}, for size {i}",
1653                        a.re,
1654                        b.re
1655                    );
1656                    assert!(
1657                        (a.im - b.im).abs() < 1e-2,
1658                        "a_re {}, b_re {} at {idx}, for size {i}",
1659                        a.im,
1660                        b.im
1661                    );
1662                });
1663        }
1664    }
1665
1666    #[test]
1667    fn test_everything_f64() {
1668        for i in 1..1900 {
1669            let mut data = vec![Complex::new(0.0019528865, 0.); i];
1670            for (i, chunk) in data.iter_mut().enumerate() {
1671                *chunk = Complex::new(
1672                    -0.19528865 + i as f64 * 0.001,
1673                    0.0019528865 - i as f64 * 0.001,
1674                );
1675            }
1676            let zaft_exec = Zaft::make_forward_fft_f64(data.len()).expect("Failed to make FFT!");
1677            let zaft_inverse = Zaft::make_inverse_fft_f64(data.len()).expect("Failed to make FFT!");
1678            let rust_fft_clone = data.clone();
1679            zaft_exec
1680                .execute(&mut data)
1681                .expect(&format!("Failed to execute forward FFT for size {i}!"));
1682            zaft_inverse
1683                .execute(&mut data)
1684                .expect(&format!("Failed to execute inverse FFT for size {i}!"));
1685            let data_len = 1. / data.len() as f64;
1686            for i in data.iter_mut() {
1687                *i *= data_len;
1688            }
1689            data.iter()
1690                .zip(rust_fft_clone)
1691                .enumerate()
1692                .for_each(|(idx, (a, b))| {
1693                    assert!(
1694                        (a.re - b.re).abs() < 1e-6,
1695                        "a_re {}, b_re {} at {idx}, for size {i}",
1696                        a.re,
1697                        b.re
1698                    );
1699                    assert!(
1700                        (a.im - b.im).abs() < 1e-6,
1701                        "a_im {}, b_im {} at {idx}, for size {i}",
1702                        a.im,
1703                        b.im
1704                    );
1705                });
1706        }
1707    }
1708
1709    #[test]
1710    fn test_everything_oof_f64() {
1711        for i in 1..1900 {
1712            let mut data = vec![Complex::new(0.0019528865, 0.); i];
1713            let mut scratch = data.clone();
1714            for (i, chunk) in data.iter_mut().enumerate() {
1715                *chunk = Complex::new(
1716                    -0.19528865 + i as f64 * 0.001,
1717                    0.0019528865 - i as f64 * 0.001,
1718                );
1719            }
1720            let zaft_exec = Zaft::make_forward_fft_f64(data.len()).expect("Failed to make FFT!");
1721            let zaft_inverse = Zaft::make_inverse_fft_f64(data.len()).expect("Failed to make FFT!");
1722            let rust_fft_clone = data.clone();
1723            zaft_exec
1724                .execute_out_of_place(&data, &mut scratch)
1725                .expect(&format!("Failed to execute forward FFT for size {i}!"));
1726            zaft_inverse
1727                .execute_out_of_place(&scratch, &mut data)
1728                .expect(&format!("Failed to execute inverse FFT for size {i}!"));
1729            let data_len = 1. / data.len() as f64;
1730            for i in data.iter_mut() {
1731                *i *= data_len;
1732            }
1733            data.iter()
1734                .zip(rust_fft_clone)
1735                .enumerate()
1736                .for_each(|(idx, (a, b))| {
1737                    assert!(
1738                        (a.re - b.re).abs() < 1e-6,
1739                        "a_re {}, b_re {} at {idx}, for size {i}",
1740                        a.re,
1741                        b.re
1742                    );
1743                    assert!(
1744                        (a.im - b.im).abs() < 1e-6,
1745                        "a_im {}, b_im {} at {idx}, for size {i}",
1746                        a.im,
1747                        b.im
1748                    );
1749                });
1750        }
1751    }
1752
1753    #[test]
1754    fn test_destructive_everything_f64() {
1755        for i in 1..1900 {
1756            let mut data = vec![Complex::new(0.0019528865, 0.); i];
1757            for (i, chunk) in data.iter_mut().enumerate() {
1758                *chunk = Complex::new(
1759                    -0.19528865 + i as f64 * 0.001,
1760                    0.0019528865 - i as f64 * 0.001,
1761                );
1762            }
1763            let zaft_exec = Zaft::make_forward_fft_f64(data.len()).expect("Failed to make FFT!");
1764            let zaft_inverse = Zaft::make_inverse_fft_f64(data.len()).expect("Failed to make FFT!");
1765            let rust_fft_clone = data.clone();
1766            let mut fwd = vec![Complex::zero(); data.len()];
1767            let mut scratch = vec![Complex::zero(); zaft_exec.destructive_scratch_length()];
1768            zaft_exec
1769                .execute_destructive_with_scratch(&mut data, &mut fwd, &mut scratch)
1770                .expect(&format!("Failed to execute forward FFT for size {i}!"));
1771            zaft_inverse
1772                .execute_destructive_with_scratch(&mut fwd, &mut data, &mut scratch)
1773                .expect(&format!("Failed to execute inverse FFT for size {i}!"));
1774            let data_len = 1. / data.len() as f64;
1775            for i in data.iter_mut() {
1776                *i *= data_len;
1777            }
1778            data.iter()
1779                .zip(rust_fft_clone)
1780                .enumerate()
1781                .for_each(|(idx, (a, b))| {
1782                    assert!(
1783                        (a.re - b.re).abs() < 1e-6,
1784                        "a_re {}, b_re {} at {idx}, for size {i}",
1785                        a.re,
1786                        b.re
1787                    );
1788                    assert!(
1789                        (a.im - b.im).abs() < 1e-6,
1790                        "a_im {}, b_im {} at {idx}, for size {i}",
1791                        a.im,
1792                        b.im
1793                    );
1794                });
1795        }
1796    }
1797
1798    #[test]
1799    fn test_destructive_everything_oof_f32() {
1800        for i in 1..2010 {
1801            let mut data = vec![Complex::new(0.0019528865, 0.); i];
1802            for (i, chunk) in data.iter_mut().enumerate() {
1803                *chunk = Complex::new(
1804                    -0.19528865 + i as f32 * 0.001,
1805                    0.0019528865 - i as f32 * 0.001,
1806                );
1807            }
1808            let zaft_exec = Zaft::make_forward_fft_f32(data.len()).expect("Failed to make FFT!");
1809            let zaft_inverse = Zaft::make_inverse_fft_f32(data.len()).expect("Failed to make FFT!");
1810            let reference_clone = data.clone();
1811            let mut scratch = vec![Complex::zero(); zaft_exec.destructive_scratch_length()];
1812            let mut target = vec![Complex::zero(); data.len()];
1813            zaft_exec
1814                .execute_destructive_with_scratch(&mut data, &mut target, &mut scratch)
1815                .expect(&format!("Failed to execute forward FFT for size {i}!"));
1816            zaft_inverse
1817                .execute_destructive_with_scratch(&mut target, &mut data, &mut scratch)
1818                .expect(&format!("Failed to execute inverse FFT for size {i}!"));
1819            let data_len = 1. / data.len() as f32;
1820            for i in data.iter_mut() {
1821                *i *= data_len;
1822            }
1823            data.iter()
1824                .zip(reference_clone)
1825                .enumerate()
1826                .for_each(|(idx, (a, b))| {
1827                    assert!(
1828                        (a.re - b.re).abs() < 1e-2,
1829                        "a_re {}, b_re {} at {idx}, for size {i}",
1830                        a.re,
1831                        b.re
1832                    );
1833                    assert!(
1834                        (a.im - b.im).abs() < 1e-2,
1835                        "a_re {}, b_re {} at {idx}, for size {i}",
1836                        a.im,
1837                        b.im
1838                    );
1839                });
1840        }
1841    }
1842}