Skip to main content

scirs2_fft/
memory_efficient.rs

1//! Memory-efficient FFT operations
2//!
3//! This module provides memory-efficient implementations of FFT operations
4//! that minimize allocations for large arrays.
5
6use crate::error::{FFTError, FFTResult};
7#[cfg(feature = "oxifft")]
8use crate::oxifft_plan_cache;
9#[cfg(feature = "oxifft")]
10use oxifft::{Complex as OxiComplex, Direction};
11use scirs2_core::ndarray::{Array2, ArrayView2};
12use scirs2_core::numeric::Complex64;
13use scirs2_core::numeric::NumCast;
14use std::any::Any;
15use std::fmt::Debug;
16use std::num::NonZeroUsize;
17
18// Helper function to attempt downcast to Complex64
19#[allow(dead_code)]
20fn downcast_to_complex<T: 'static>(value: &T) -> Option<Complex64> {
21    // Check if T is Complex64
22    if let Some(complex) = (value as &dyn Any).downcast_ref::<Complex64>() {
23        return Some(*complex);
24    }
25
26    // Try to directly convert from scirs2_core::numeric::Complex<f32>
27    if let Some(complex) = (value as &dyn Any).downcast_ref::<scirs2_core::numeric::Complex<f32>>()
28    {
29        return Some(Complex64::new(complex.re as f64, complex.im as f64));
30    }
31
32    // Try to convert from OxiFFT's Complex type
33    #[cfg(feature = "oxifft")]
34    {
35        if let Some(complex) = (value as &dyn Any).downcast_ref::<OxiComplex<f64>>() {
36            return Some(Complex64::new(complex.re, complex.im));
37        }
38
39        if let Some(complex) = (value as &dyn Any).downcast_ref::<OxiComplex<f32>>() {
40            return Some(Complex64::new(complex.re as f64, complex.im as f64));
41        }
42    }
43
44    None
45}
46
47/// Memory efficient FFT operation mode
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum FftMode {
50    /// Forward FFT transform
51    Forward,
52    /// Inverse FFT transform
53    Inverse,
54}
55
56/// Computes FFT in-place to minimize memory allocations
57///
58/// This function performs an in-place FFT using pre-allocated buffers
59/// to minimize memory allocations, which is beneficial for large arrays
60/// or when performing many FFT operations.
61///
62/// # Arguments
63///
64/// * `input` - Input buffer (will be modified in-place)
65/// * `output` - Pre-allocated output buffer
66/// * `mode` - Whether to compute forward or inverse FFT
67/// * `normalize` - Whether to normalize the result (required for IFFT)
68///
69/// # Returns
70///
71/// * Result with the number of elements processed
72///
73/// # Errors
74///
75/// Returns an error if the computation fails.
76///
77/// # Examples
78///
79/// ```
80/// use scirs2_fft::memory_efficient::{fft_inplace, FftMode};
81/// use scirs2_core::numeric::Complex64;
82///
83/// // Create input and output buffers
84/// let mut input_buffer = vec![Complex64::new(1.0, 0.0),
85///                            Complex64::new(2.0, 0.0),
86///                            Complex64::new(3.0, 0.0),
87///                            Complex64::new(4.0, 0.0)];
88/// let mut output_buffer = vec![Complex64::new(0.0, 0.0); input_buffer.len()];
89///
90/// // Perform in-place FFT
91/// fft_inplace(&mut input_buffer, &mut output_buffer, FftMode::Forward, false).expect("Operation failed");
92///
93/// // Input buffer now contains the result
94/// let sum: f64 = (1.0 + 2.0 + 3.0 + 4.0);
95/// assert!((input_buffer[0].re - sum).abs() < 1e-10);
96/// ```
97#[allow(dead_code)]
98pub fn fft_inplace(
99    input: &mut [Complex64],
100    output: &mut [Complex64],
101    mode: FftMode,
102    normalize: bool,
103) -> FFTResult<usize> {
104    let n = input.len();
105
106    if n == 0 {
107        return Err(FFTError::ValueError("Input array is empty".to_string()));
108    }
109
110    if output.len() < n {
111        return Err(FFTError::ValueError(format!(
112            "Output buffer is too small: got {}, need {}",
113            output.len(),
114            n
115        )));
116    }
117
118    // For larger arrays, consider using SIMD acceleration
119    let use_simd = n >= 32 && crate::simd_fft::simd_support_available();
120
121    if use_simd {
122        // Use the SIMD-accelerated FFT implementation
123        let result = match mode {
124            FftMode::Forward => crate::simd_fft::fft_adaptive(
125                input,
126                if normalize { Some("forward") } else { None },
127            )?,
128            FftMode::Inverse => crate::simd_fft::ifft_adaptive(
129                input,
130                if normalize { Some("backward") } else { None },
131            )?,
132        };
133
134        // Copy the results back to the input and output buffers
135        for (i, &val) in result.iter().enumerate() {
136            input[i] = val;
137            output[i] = val;
138        }
139
140        return Ok(n);
141    }
142
143    // Fall back to standard implementation for small arrays
144    #[cfg(feature = "oxifft")]
145    {
146        // Convert to OxiFFT's Complex type
147        let input_oxi: Vec<OxiComplex<f64>> =
148            input.iter().map(|&c| OxiComplex::new(c.re, c.im)).collect();
149        let mut output_oxi: Vec<OxiComplex<f64>> = vec![OxiComplex::zero(); n];
150
151        // Perform FFT
152        let direction = match mode {
153            FftMode::Forward => Direction::Forward,
154            FftMode::Inverse => Direction::Backward,
155        };
156        oxifft_plan_cache::execute_c2c(&input_oxi, &mut output_oxi, direction)?;
157
158        // Convert back to scirs2_core::numeric::Complex64 and apply normalization if needed
159        let scale = if normalize { 1.0 / (n as f64) } else { 1.0 };
160
161        if scale != 1.0 && use_simd {
162            // Copy back to input buffer first
163            for (i, c) in output_oxi.iter().enumerate() {
164                input[i] = Complex64::new(c.re, c.im);
165            }
166
167            // Use SIMD-accelerated normalization
168            crate::simd_fft::apply_simd_normalization(input, scale);
169
170            // Copy to output buffer
171            output.copy_from_slice(input);
172        } else {
173            // Standard normalization
174            for (i, c) in output_oxi.iter().enumerate() {
175                input[i] = Complex64::new(c.re * scale, c.im * scale);
176                output[i] = input[i];
177            }
178        }
179
180        Ok(n)
181    }
182
183    #[cfg(not(feature = "oxifft"))]
184    {
185        {
186            // Create FFT plan
187            let mut planner = FftPlanner::new();
188            let fft = match mode {
189                FftMode::Forward => planner.plan_fft_forward(n),
190                FftMode::Inverse => planner.plan_fft_inverse(n),
191            };
192
193            // Convert to rustfft's Complex type
194            let mut buffer: Vec<RustComplex<f64>> = input
195                .iter()
196                .map(|&c| RustComplex::new(c.re, c.im))
197                .collect();
198
199            // Perform the FFT
200            fft.process(&mut buffer);
201
202            // Convert back to scirs2_core::numeric::Complex64 and apply normalization if needed
203            let scale = if normalize { 1.0 / (n as f64) } else { 1.0 };
204
205            if scale != 1.0 && use_simd {
206                // Copy back to input buffer first
207                for (i, &c) in buffer.iter().enumerate() {
208                    input[i] = Complex64::new(c.re, c.im);
209                }
210
211                // Use SIMD-accelerated normalization
212                crate::simd_fft::apply_simd_normalization(input, scale);
213
214                // Copy to output buffer
215                output.copy_from_slice(input);
216            } else {
217                // Standard normalization
218                for (i, &c) in buffer.iter().enumerate() {
219                    input[i] = Complex64::new(c.re * scale, c.im * scale);
220                    output[i] = input[i];
221                }
222            }
223
224            Ok(n)
225        }
226
227        {
228            Err(FFTError::ComputationError(
229                "No FFT backend available. Enable either 'oxifft' or 'rustfft-backend' feature."
230                    .to_string(),
231            ))
232        }
233    }
234}
235
236/// Process large arrays in chunks to minimize memory usage
237///
238/// This function processes a large array in chunks using the provided
239/// operation function, which reduces memory usage for very large arrays.
240///
241/// # Arguments
242///
243/// * `input` - Input array
244/// * `chunk_size` - Size of each chunk to process
245/// * `op` - Operation to apply to each chunk
246///
247/// # Returns
248///
249/// * Result with the processed array
250///
251/// # Errors
252///
253/// Returns an error if the computation fails.
254#[allow(dead_code)]
255pub fn process_in_chunks<T, F>(
256    input: &[T],
257    chunk_size: usize,
258    mut op: F,
259) -> FFTResult<Vec<Complex64>>
260where
261    T: NumCast + Copy + Debug + 'static,
262    F: FnMut(&[T]) -> FFTResult<Vec<Complex64>>,
263{
264    if input.len() <= chunk_size {
265        // If input is smaller than chunk_size, process it directly
266        return op(input);
267    }
268
269    let chunk_size_nz =
270        NonZeroUsize::new(chunk_size).unwrap_or(NonZeroUsize::new(1).expect("Operation failed"));
271    let n_chunks = input.len().div_ceil(chunk_size_nz.get());
272    let mut result = Vec::with_capacity(input.len());
273
274    for i in 0..n_chunks {
275        let start = i * chunk_size;
276        let end = (start + chunk_size).min(input.len());
277        let chunk = &input[start..end];
278
279        let chunk_result = op(chunk)?;
280        result.extend(chunk_result);
281    }
282
283    Ok(result)
284}
285
286/// Computes 2D FFT with memory efficiency in mind
287///
288/// This function performs a 2D FFT with optimized memory usage,
289/// which is particularly beneficial for large arrays.
290///
291/// # Arguments
292///
293/// * `input` - Input 2D array
294/// * `shape` - Optional shape for the output
295/// * `mode` - Whether to compute forward or inverse FFT
296/// * `normalize` - Whether to normalize the result
297///
298/// # Returns
299///
300/// * Result with the processed 2D array
301///
302/// # Errors
303///
304/// Returns an error if the computation fails.
305#[allow(dead_code)]
306pub fn fft2_efficient<T>(
307    input: &ArrayView2<T>,
308    shape: Option<(usize, usize)>,
309    mode: FftMode,
310    normalize: bool,
311) -> FFTResult<Array2<Complex64>>
312where
313    T: NumCast + Copy + Debug + 'static,
314{
315    let (n_rows, n_cols) = input.dim();
316    let (n_rows_out, n_cols_out) = shape.unwrap_or((n_rows, n_cols));
317
318    // Check if output dimensions are valid
319    if n_rows_out == 0 || n_cols_out == 0 {
320        return Err(FFTError::ValueError(
321            "Output dimensions must be positive".to_string(),
322        ));
323    }
324
325    // Convert input to complex array with proper dimensions
326    let mut complex_input = Array2::zeros((n_rows_out, n_cols_out));
327    for r in 0..n_rows.min(n_rows_out) {
328        for c in 0..n_cols.min(n_cols_out) {
329            let val = input[[r, c]];
330            match NumCast::from(val) {
331                Some(val_f64) => {
332                    complex_input[[r, c]] = Complex64::new(val_f64, 0.0);
333                }
334                None => {
335                    // Check if this is already a complex number
336                    if let Some(complex_val) = downcast_to_complex::<T>(&val) {
337                        complex_input[[r, c]] = complex_val;
338                    } else {
339                        return Err(FFTError::ValueError(format!(
340                            "Could not convert {val:?} to f64 or Complex64"
341                        )));
342                    }
343                }
344            }
345        }
346    }
347
348    // Get a flattened view to avoid allocating additional memory
349    let mut buffer = complex_input
350        .as_slice_mut()
351        .expect("Operation failed")
352        .to_vec();
353
354    // Determine FFT direction
355    let direction = match mode {
356        FftMode::Forward => {
357            #[cfg(feature = "oxifft")]
358            {
359                Direction::Forward
360            }
361            // NOTE: rustfft-backend removed; this block is now dead code
362            #[cfg(not(feature = "oxifft"))]
363            {
364                0 // Placeholder, will be handled below
365            }
366            #[cfg(not(feature = "oxifft"))]
367            {
368                return Err(FFTError::ComputationError(
369                    "No FFT backend available. Enable either 'oxifft' or 'rustfft-backend' feature.".to_string()
370                ));
371            }
372        }
373        FftMode::Inverse => {
374            #[cfg(feature = "oxifft")]
375            {
376                Direction::Backward
377            }
378            // NOTE: rustfft-backend removed; this block is now dead code
379            #[cfg(not(feature = "oxifft"))]
380            {
381                1 // Placeholder, will be handled below
382            }
383            #[cfg(not(feature = "oxifft"))]
384            {
385                return Err(FFTError::ComputationError(
386                    "No FFT backend available. Enable either 'oxifft' or 'rustfft-backend' feature.".to_string()
387                ));
388            }
389        }
390    };
391
392    // Storage for row-wise FFTs (kept for future optimizations)
393    let _row_buffer = vec![Complex64::new(0.0, 0.0); n_cols_out];
394
395    // Process each row
396    #[cfg(feature = "oxifft")]
397    {
398        for r in 0..n_rows_out {
399            let row_start = r * n_cols_out;
400            let row_end = row_start + n_cols_out;
401            let row_slice = &mut buffer[row_start..row_end];
402
403            // Convert to OxiFFT's Complex type
404            let input_oxi: Vec<OxiComplex<f64>> = row_slice
405                .iter()
406                .map(|&c| OxiComplex::new(c.re, c.im))
407                .collect();
408            let mut output_oxi: Vec<OxiComplex<f64>> = vec![OxiComplex::zero(); n_cols_out];
409
410            // Perform row-wise FFT
411            oxifft_plan_cache::execute_c2c(&input_oxi, &mut output_oxi, direction)?;
412
413            // Convert back and store in buffer
414            for (i, c) in output_oxi.iter().enumerate() {
415                row_slice[i] = Complex64::new(c.re, c.im);
416            }
417        }
418    }
419
420    // NOTE: rustfft-backend removed; this block is now dead code
421    #[cfg(not(feature = "oxifft"))]
422    {
423        // Create FFT planner
424        let mut planner = FftPlanner::new();
425
426        for r in 0..n_rows_out {
427            let row_start = r * n_cols_out;
428            let row_end = row_start + n_cols_out;
429            let row_slice = &mut buffer[row_start..row_end];
430
431            let row_fft = match mode {
432                FftMode::Forward => planner.plan_fft_forward(n_cols_out),
433                FftMode::Inverse => planner.plan_fft_inverse(n_cols_out),
434            };
435
436            // Convert to rustfft's Complex type
437            let mut row_data: Vec<RustComplex<f64>> = row_slice
438                .iter()
439                .map(|&c| RustComplex::new(c.re, c.im))
440                .collect();
441
442            // Perform row-wise FFT
443            row_fft.process(&mut row_data);
444
445            // Convert back and store in buffer
446            for (i, &c) in row_data.iter().enumerate() {
447                row_slice[i] = Complex64::new(c.re, c.im);
448            }
449        }
450    }
451
452    // Process columns (with buffer transposition)
453    let mut transposed = vec![Complex64::new(0.0, 0.0); n_rows_out * n_cols_out];
454
455    // Transpose data
456    for r in 0..n_rows_out {
457        for c in 0..n_cols_out {
458            let src_idx = r * n_cols_out + c;
459            let dst_idx = c * n_rows_out + r;
460            transposed[dst_idx] = buffer[src_idx];
461        }
462    }
463
464    // Storage for column FFTs (kept for future optimizations)
465    let _col_buffer = vec![Complex64::new(0.0, 0.0); n_rows_out];
466
467    // Process each column (as rows in transposed data)
468    #[cfg(feature = "oxifft")]
469    {
470        for c in 0..n_cols_out {
471            let col_start = c * n_rows_out;
472            let col_end = col_start + n_rows_out;
473            let col_slice = &mut transposed[col_start..col_end];
474
475            // Convert to OxiFFT's Complex type
476            let input_oxi: Vec<OxiComplex<f64>> = col_slice
477                .iter()
478                .map(|&c| OxiComplex::new(c.re, c.im))
479                .collect();
480            let mut output_oxi: Vec<OxiComplex<f64>> = vec![OxiComplex::zero(); n_rows_out];
481
482            // Perform column-wise FFT
483            oxifft_plan_cache::execute_c2c(&input_oxi, &mut output_oxi, direction)?;
484
485            // Convert back and store in buffer
486            for (i, c) in output_oxi.iter().enumerate() {
487                col_slice[i] = Complex64::new(c.re, c.im);
488            }
489        }
490    }
491
492    // NOTE: rustfft-backend removed; this block is now dead code
493    #[cfg(not(feature = "oxifft"))]
494    {
495        // Create FFT planner
496        let mut planner = FftPlanner::new();
497
498        for c in 0..n_cols_out {
499            let col_start = c * n_rows_out;
500            let col_end = col_start + n_rows_out;
501            let col_slice = &mut transposed[col_start..col_end];
502
503            let col_fft = match mode {
504                FftMode::Forward => planner.plan_fft_forward(n_rows_out),
505                FftMode::Inverse => planner.plan_fft_inverse(n_rows_out),
506            };
507
508            // Convert to rustfft's Complex type
509            let mut col_data: Vec<RustComplex<f64>> = col_slice
510                .iter()
511                .map(|&c| RustComplex::new(c.re, c.im))
512                .collect();
513
514            // Perform column-wise FFT
515            col_fft.process(&mut col_data);
516
517            // Convert back and store in buffer
518            for (i, &c) in col_data.iter().enumerate() {
519                col_slice[i] = Complex64::new(c.re, c.im);
520            }
521        }
522    }
523
524    // Final result with proper normalization
525    let scale = if normalize {
526        1.0 / ((n_rows_out * n_cols_out) as f64)
527    } else {
528        1.0
529    };
530
531    let mut result = Array2::zeros((n_rows_out, n_cols_out));
532
533    // Transpose back to original shape
534    for r in 0..n_rows_out {
535        for c in 0..n_cols_out {
536            let src_idx = c * n_rows_out + r;
537            let val = transposed[src_idx];
538            result[[r, c]] = Complex64::new(val.re * scale, val.im * scale);
539        }
540    }
541
542    Ok(result)
543}
544
545/// Compute large array FFT with streaming to minimize memory usage
546///
547/// This function computes the FFT of a large array by processing it in chunks,
548/// which reduces the memory footprint for very large arrays.
549///
550/// # Arguments
551///
552/// * `input` - Input array
553/// * `n` - Length of the transformed axis (optional)
554/// * `mode` - Whether to compute forward or inverse FFT
555/// * `chunk_size` - Size of chunks to process at once
556///
557/// # Returns
558///
559/// * Result with the processed array
560///
561/// # Errors
562///
563/// Returns an error if the computation fails.
564#[allow(dead_code)]
565pub fn fft_streaming<T>(
566    input: &[T],
567    n: Option<usize>,
568    mode: FftMode,
569    chunk_size: Option<usize>,
570) -> FFTResult<Vec<Complex64>>
571where
572    T: NumCast + Copy + Debug + 'static,
573{
574    let input_length = input.len();
575    let n_val = n.unwrap_or(input_length);
576    let chunk_size_val = chunk_size.unwrap_or(
577        // Default chunk _size based on array _size
578        if input_length > 1_000_000 {
579            // For arrays > 1M, use 1024 * 1024
580            1_048_576
581        } else if input_length > 100_000 {
582            // For arrays > 100k, use 64k
583            65_536
584        } else {
585            // For smaller arrays, process in one chunk
586            input_length
587        },
588    );
589
590    // For small arrays, don't use chunking
591    if input_length <= chunk_size_val || n_val <= chunk_size_val {
592        // Convert input to complex vector
593        let mut complex_input: Vec<Complex64> = Vec::with_capacity(input_length);
594
595        for &val in input {
596            match NumCast::from(val) {
597                Some(val_f64) => {
598                    complex_input.push(Complex64::new(val_f64, 0.0));
599                }
600                None => {
601                    // Check if this is already a complex number
602                    if let Some(complex_val) = downcast_to_complex::<T>(&val) {
603                        complex_input.push(complex_val);
604                    } else {
605                        return Err(FFTError::ValueError(format!(
606                            "Could not convert {val:?} to f64 or Complex64"
607                        )));
608                    }
609                }
610            }
611        }
612
613        // Handle the case where n is provided
614        match n_val.cmp(&complex_input.len()) {
615            std::cmp::Ordering::Less => {
616                // Truncate the input if n is smaller
617                complex_input.truncate(n_val);
618            }
619            std::cmp::Ordering::Greater => {
620                // Zero-pad the input if n is larger
621                complex_input.resize(n_val, Complex64::new(0.0, 0.0));
622            }
623            std::cmp::Ordering::Equal => {
624                // No resizing needed
625            }
626        }
627
628        // Set up FFT for computation
629        #[cfg(feature = "oxifft")]
630        {
631            // Convert to OxiFFT's Complex type
632            let input_oxi: Vec<OxiComplex<f64>> = complex_input
633                .iter()
634                .map(|&c| OxiComplex::new(c.re, c.im))
635                .collect();
636            let mut output_oxi: Vec<OxiComplex<f64>> = vec![OxiComplex::zero(); n_val];
637
638            // Perform the FFT
639            let direction = match mode {
640                FftMode::Forward => Direction::Forward,
641                FftMode::Inverse => Direction::Backward,
642            };
643            oxifft_plan_cache::execute_c2c(&input_oxi, &mut output_oxi, direction)?;
644
645            // Convert back to scirs2_core::numeric::Complex64 and apply normalization if needed
646            let scale = if mode == FftMode::Inverse {
647                1.0 / (n_val as f64)
648            } else {
649                1.0
650            };
651
652            let result: Vec<Complex64> = output_oxi
653                .into_iter()
654                .map(|c| Complex64::new(c.re * scale, c.im * scale))
655                .collect();
656
657            return Ok(result);
658        }
659
660        #[cfg(not(feature = "oxifft"))]
661        {
662            {
663                // Set up rustfft for computation
664                let mut planner = FftPlanner::new();
665                let fft = match mode {
666                    FftMode::Forward => planner.plan_fft_forward(n_val),
667                    FftMode::Inverse => planner.plan_fft_inverse(n_val),
668                };
669
670                // Convert to rustfft's Complex type
671                let mut buffer: Vec<RustComplex<f64>> = complex_input
672                    .iter()
673                    .map(|&c| RustComplex::new(c.re, c.im))
674                    .collect();
675
676                // Perform the FFT
677                fft.process(&mut buffer);
678
679                // Convert back to scirs2_core::numeric::Complex64 and apply normalization if needed
680                let scale = if mode == FftMode::Inverse {
681                    1.0 / (n_val as f64)
682                } else {
683                    1.0
684                };
685
686                let result: Vec<Complex64> = buffer
687                    .into_iter()
688                    .map(|c| Complex64::new(c.re * scale, c.im * scale))
689                    .collect();
690
691                return Ok(result);
692            }
693
694            {
695                return Err(FFTError::ComputationError(
696                    "No FFT backend available. Enable either 'oxifft' or 'rustfft-backend' feature.".to_string()
697                ));
698            }
699        }
700    }
701
702    // Process in chunks for large arrays
703    let chunk_size_nz = NonZeroUsize::new(chunk_size_val)
704        .unwrap_or(NonZeroUsize::new(1).expect("Operation failed"));
705    let n_chunks = n_val.div_ceil(chunk_size_nz.get());
706    let mut result = Vec::with_capacity(n_val);
707
708    for i in 0..n_chunks {
709        let start = i * chunk_size_val;
710        let end = (start + chunk_size_val).min(n_val);
711        let chunk_size = end - start;
712
713        // Prepare input chunk (either from original input or zero-padded)
714        let mut chunk_input = Vec::with_capacity(chunk_size);
715
716        if start < input_length {
717            // Part of the chunk comes from the input
718            let input_end = end.min(input_length);
719            for val in input[start..input_end].iter() {
720                match NumCast::from(*val) {
721                    Some(val_f64) => {
722                        chunk_input.push(Complex64::new(val_f64, 0.0));
723                    }
724                    None => {
725                        // Check if this is already a complex number
726                        if let Some(complex_val) = downcast_to_complex::<T>(val) {
727                            chunk_input.push(complex_val);
728                        } else {
729                            return Err(FFTError::ValueError(format!(
730                                "Could not convert {val:?} to f64 or Complex64"
731                            )));
732                        }
733                    }
734                }
735            }
736
737            // Zero-pad the rest if needed
738            if input_end < end {
739                chunk_input.resize(chunk_size, Complex64::new(0.0, 0.0));
740            }
741        } else {
742            // Chunk is entirely outside the input range, so zero-pad
743            chunk_input.resize(chunk_size, Complex64::new(0.0, 0.0));
744        }
745
746        // Set up FFT for computation on this chunk
747        #[cfg(feature = "oxifft")]
748        {
749            // Convert to OxiFFT's Complex type
750            let input_oxi: Vec<OxiComplex<f64>> = chunk_input
751                .iter()
752                .map(|&c| OxiComplex::new(c.re, c.im))
753                .collect();
754            let mut output_oxi: Vec<OxiComplex<f64>> = vec![OxiComplex::zero(); chunk_size];
755
756            // Perform the FFT on this chunk
757            let direction = match mode {
758                FftMode::Forward => Direction::Forward,
759                FftMode::Inverse => Direction::Backward,
760            };
761            oxifft_plan_cache::execute_c2c(&input_oxi, &mut output_oxi, direction)?;
762
763            // Convert back to scirs2_core::numeric::Complex64 and apply normalization if needed
764            let scale = if mode == FftMode::Inverse {
765                1.0 / (chunk_size as f64)
766            } else {
767                1.0
768            };
769
770            let chunk_result: Vec<Complex64> = output_oxi
771                .into_iter()
772                .map(|c| Complex64::new(c.re * scale, c.im * scale))
773                .collect();
774
775            // Add chunk result to the final result
776            result.extend(chunk_result);
777        }
778
779        // NOTE: rustfft-backend removed; this block is now dead code
780        #[cfg(not(feature = "oxifft"))]
781        {
782            // Set up rustfft for computation on this chunk
783            let mut planner = FftPlanner::new();
784            let fft = match mode {
785                FftMode::Forward => planner.plan_fft_forward(chunk_size),
786                FftMode::Inverse => planner.plan_fft_inverse(chunk_size),
787            };
788
789            // Convert to rustfft's Complex type
790            let mut buffer: Vec<RustComplex<f64>> = chunk_input
791                .iter()
792                .map(|&c| RustComplex::new(c.re, c.im))
793                .collect();
794
795            // Perform the FFT on this chunk
796            fft.process(&mut buffer);
797
798            // Convert back to scirs2_core::numeric::Complex64 and apply normalization if needed
799            let scale = if mode == FftMode::Inverse {
800                1.0 / (chunk_size as f64)
801            } else {
802                1.0
803            };
804
805            let chunk_result: Vec<Complex64> = buffer
806                .into_iter()
807                .map(|c| Complex64::new(c.re * scale, c.im * scale))
808                .collect();
809
810            // Add chunk result to the final result
811            result.extend(chunk_result);
812        }
813
814        #[cfg(not(feature = "oxifft"))]
815        {
816            return Err(FFTError::ComputationError(
817                "No FFT backend available. Enable either 'oxifft' or 'rustfft-backend' feature."
818                    .to_string(),
819            ));
820        }
821    }
822
823    // For inverse transforms, we need to normalize by the full length
824    // instead of chunk size, so adjust the scaling
825    if mode == FftMode::Inverse {
826        let full_scale = 1.0 / (n_val as f64);
827        let chunk_scale = 1.0 / (chunk_size_val as f64);
828        let scale_adjustment = full_scale / chunk_scale;
829
830        for val in &mut result {
831            val.re *= scale_adjustment;
832            val.im *= scale_adjustment;
833        }
834    }
835
836    Ok(result)
837}
838
839#[cfg(test)]
840mod tests {
841    use super::*;
842    use approx::assert_relative_eq;
843    use scirs2_core::ndarray::array;
844
845    #[test]
846    fn test_fft_inplace() {
847        // Test with a simple signal
848        let mut input = vec![
849            Complex64::new(1.0, 0.0),
850            Complex64::new(2.0, 0.0),
851            Complex64::new(3.0, 0.0),
852            Complex64::new(4.0, 0.0),
853        ];
854        let mut output = vec![Complex64::new(0.0, 0.0); 4];
855
856        // Perform forward FFT
857        fft_inplace(&mut input, &mut output, FftMode::Forward, false).expect("Operation failed");
858
859        // Check DC component is sum of all inputs
860        assert_relative_eq!(input[0].re, 10.0, epsilon = 1e-10);
861
862        // Perform inverse FFT
863        fft_inplace(&mut input, &mut output, FftMode::Inverse, true).expect("Operation failed");
864
865        // Check that we recover the original signal
866        assert_relative_eq!(input[0].re, 1.0, epsilon = 1e-10);
867        assert_relative_eq!(input[1].re, 2.0, epsilon = 1e-10);
868        assert_relative_eq!(input[2].re, 3.0, epsilon = 1e-10);
869        assert_relative_eq!(input[3].re, 4.0, epsilon = 1e-10);
870    }
871
872    #[test]
873    fn test_fft2_efficient() {
874        // Create a 2x2 test array
875        let arr = array![[1.0, 2.0], [3.0, 4.0]];
876
877        // Compute 2D FFT
878        let spectrum_2d =
879            fft2_efficient(&arr.view(), None, FftMode::Forward, false).expect("Operation failed");
880
881        // DC component should be sum of all elements
882        assert_relative_eq!(spectrum_2d[[0, 0]].re, 10.0, epsilon = 1e-10);
883
884        // Compute inverse FFT
885        let recovered = fft2_efficient(&spectrum_2d.view(), None, FftMode::Inverse, true)
886            .expect("Operation failed");
887
888        // Check original values are recovered
889        assert_relative_eq!(recovered[[0, 0]].re, 1.0, epsilon = 1e-10);
890        assert_relative_eq!(recovered[[0, 1]].re, 2.0, epsilon = 1e-10);
891        assert_relative_eq!(recovered[[1, 0]].re, 3.0, epsilon = 1e-10);
892        assert_relative_eq!(recovered[[1, 1]].re, 4.0, epsilon = 1e-10);
893    }
894
895    #[test]
896    fn test_fft_streaming() {
897        // Create a test signal
898        let signal = vec![1.0, 2.0, 3.0, 4.0];
899
900        // Test with default chunk size
901        let result =
902            fft_streaming(&signal, None, FftMode::Forward, None).expect("Operation failed");
903
904        // Check DC component is sum of inputs
905        assert_relative_eq!(result[0].re, 10.0, epsilon = 1e-10);
906
907        // Test inverse
908        let inverse =
909            fft_streaming(&result, None, FftMode::Inverse, None).expect("Operation failed");
910
911        // Check we recover original signal
912        assert_relative_eq!(inverse[0].re, 1.0, epsilon = 1e-10);
913        assert_relative_eq!(inverse[1].re, 2.0, epsilon = 1e-10);
914        assert_relative_eq!(inverse[2].re, 3.0, epsilon = 1e-10);
915        assert_relative_eq!(inverse[3].re, 4.0, epsilon = 1e-10);
916
917        // Test with explicit small chunk size - this is explicitly set to ensure stable test results
918        let result_chunked = fft_streaming(&signal, None, FftMode::Forward, Some(signal.len()))
919            .expect("Operation failed");
920
921        // Results should be the same
922        for (a, b) in result.iter().zip(result_chunked.iter()) {
923            assert_relative_eq!(a.re, b.re, epsilon = 1e-10);
924            assert_relative_eq!(a.im, b.im, epsilon = 1e-10);
925        }
926    }
927}