1use 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#[allow(dead_code)]
20fn downcast_to_complex<T: 'static>(value: &T) -> Option<Complex64> {
21 if let Some(complex) = (value as &dyn Any).downcast_ref::<Complex64>() {
23 return Some(*complex);
24 }
25
26 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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum FftMode {
50 Forward,
52 Inverse,
54}
55
56#[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 let use_simd = n >= 32 && crate::simd_fft::simd_support_available();
120
121 if use_simd {
122 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 for (i, &val) in result.iter().enumerate() {
136 input[i] = val;
137 output[i] = val;
138 }
139
140 return Ok(n);
141 }
142
143 #[cfg(feature = "oxifft")]
145 {
146 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 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 let scale = if normalize { 1.0 / (n as f64) } else { 1.0 };
160
161 if scale != 1.0 && use_simd {
162 for (i, c) in output_oxi.iter().enumerate() {
164 input[i] = Complex64::new(c.re, c.im);
165 }
166
167 crate::simd_fft::apply_simd_normalization(input, scale);
169
170 output.copy_from_slice(input);
172 } else {
173 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 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 let mut buffer: Vec<RustComplex<f64>> = input
195 .iter()
196 .map(|&c| RustComplex::new(c.re, c.im))
197 .collect();
198
199 fft.process(&mut buffer);
201
202 let scale = if normalize { 1.0 / (n as f64) } else { 1.0 };
204
205 if scale != 1.0 && use_simd {
206 for (i, &c) in buffer.iter().enumerate() {
208 input[i] = Complex64::new(c.re, c.im);
209 }
210
211 crate::simd_fft::apply_simd_normalization(input, scale);
213
214 output.copy_from_slice(input);
216 } else {
217 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#[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 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#[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 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 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 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 let mut buffer = complex_input
350 .as_slice_mut()
351 .expect("Operation failed")
352 .to_vec();
353
354 let direction = match mode {
356 FftMode::Forward => {
357 #[cfg(feature = "oxifft")]
358 {
359 Direction::Forward
360 }
361 #[cfg(not(feature = "oxifft"))]
363 {
364 0 }
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 #[cfg(not(feature = "oxifft"))]
380 {
381 1 }
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 let _row_buffer = vec![Complex64::new(0.0, 0.0); n_cols_out];
394
395 #[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 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 oxifft_plan_cache::execute_c2c(&input_oxi, &mut output_oxi, direction)?;
412
413 for (i, c) in output_oxi.iter().enumerate() {
415 row_slice[i] = Complex64::new(c.re, c.im);
416 }
417 }
418 }
419
420 #[cfg(not(feature = "oxifft"))]
422 {
423 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 let mut row_data: Vec<RustComplex<f64>> = row_slice
438 .iter()
439 .map(|&c| RustComplex::new(c.re, c.im))
440 .collect();
441
442 row_fft.process(&mut row_data);
444
445 for (i, &c) in row_data.iter().enumerate() {
447 row_slice[i] = Complex64::new(c.re, c.im);
448 }
449 }
450 }
451
452 let mut transposed = vec![Complex64::new(0.0, 0.0); n_rows_out * n_cols_out];
454
455 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 let _col_buffer = vec![Complex64::new(0.0, 0.0); n_rows_out];
466
467 #[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 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 oxifft_plan_cache::execute_c2c(&input_oxi, &mut output_oxi, direction)?;
484
485 for (i, c) in output_oxi.iter().enumerate() {
487 col_slice[i] = Complex64::new(c.re, c.im);
488 }
489 }
490 }
491
492 #[cfg(not(feature = "oxifft"))]
494 {
495 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 let mut col_data: Vec<RustComplex<f64>> = col_slice
510 .iter()
511 .map(|&c| RustComplex::new(c.re, c.im))
512 .collect();
513
514 col_fft.process(&mut col_data);
516
517 for (i, &c) in col_data.iter().enumerate() {
519 col_slice[i] = Complex64::new(c.re, c.im);
520 }
521 }
522 }
523
524 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 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#[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 if input_length > 1_000_000 {
579 1_048_576
581 } else if input_length > 100_000 {
582 65_536
584 } else {
585 input_length
587 },
588 );
589
590 if input_length <= chunk_size_val || n_val <= chunk_size_val {
592 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 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 match n_val.cmp(&complex_input.len()) {
615 std::cmp::Ordering::Less => {
616 complex_input.truncate(n_val);
618 }
619 std::cmp::Ordering::Greater => {
620 complex_input.resize(n_val, Complex64::new(0.0, 0.0));
622 }
623 std::cmp::Ordering::Equal => {
624 }
626 }
627
628 #[cfg(feature = "oxifft")]
630 {
631 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 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 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 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 let mut buffer: Vec<RustComplex<f64>> = complex_input
672 .iter()
673 .map(|&c| RustComplex::new(c.re, c.im))
674 .collect();
675
676 fft.process(&mut buffer);
678
679 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 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 let mut chunk_input = Vec::with_capacity(chunk_size);
715
716 if start < input_length {
717 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 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 if input_end < end {
739 chunk_input.resize(chunk_size, Complex64::new(0.0, 0.0));
740 }
741 } else {
742 chunk_input.resize(chunk_size, Complex64::new(0.0, 0.0));
744 }
745
746 #[cfg(feature = "oxifft")]
748 {
749 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 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 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 result.extend(chunk_result);
777 }
778
779 #[cfg(not(feature = "oxifft"))]
781 {
782 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 let mut buffer: Vec<RustComplex<f64>> = chunk_input
791 .iter()
792 .map(|&c| RustComplex::new(c.re, c.im))
793 .collect();
794
795 fft.process(&mut buffer);
797
798 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 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 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 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 fft_inplace(&mut input, &mut output, FftMode::Forward, false).expect("Operation failed");
858
859 assert_relative_eq!(input[0].re, 10.0, epsilon = 1e-10);
861
862 fft_inplace(&mut input, &mut output, FftMode::Inverse, true).expect("Operation failed");
864
865 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 let arr = array![[1.0, 2.0], [3.0, 4.0]];
876
877 let spectrum_2d =
879 fft2_efficient(&arr.view(), None, FftMode::Forward, false).expect("Operation failed");
880
881 assert_relative_eq!(spectrum_2d[[0, 0]].re, 10.0, epsilon = 1e-10);
883
884 let recovered = fft2_efficient(&spectrum_2d.view(), None, FftMode::Inverse, true)
886 .expect("Operation failed");
887
888 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 let signal = vec![1.0, 2.0, 3.0, 4.0];
899
900 let result =
902 fft_streaming(&signal, None, FftMode::Forward, None).expect("Operation failed");
903
904 assert_relative_eq!(result[0].re, 10.0, epsilon = 1e-10);
906
907 let inverse =
909 fft_streaming(&result, None, FftMode::Inverse, None).expect("Operation failed");
910
911 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 let result_chunked = fft_streaming(&signal, None, FftMode::Forward, Some(signal.len()))
919 .expect("Operation failed");
920
921 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}