1use scirs2_core::ndarray::{Array1, Array2, Array3, ArrayView1, ArrayView2, ArrayView3};
40use scirs2_core::numeric::Complex;
41use scirs2_core::numeric::{Float, FloatConst};
42use std::f64::consts::PI;
43
44use crate::error::{LinalgError, LinalgResult};
45
46pub type Complex64 = Complex<f64>;
48pub type Complex32 = Complex<f32>;
49
50#[derive(Debug, Clone, Copy)]
52pub enum FFTAlgorithm {
53 CooleyTukey,
55 MixedRadix,
57 PrimeFactor,
59 Auto,
61}
62
63#[derive(Debug, Clone, Copy)]
65pub enum WindowFunction {
66 Rectangular,
68 Hann,
70 Hamming,
72 Blackman,
74 Kaiser(f64),
76 Tukey(f64),
78 Gaussian(f64),
80}
81
82#[derive(Debug)]
84pub struct FFTPlan<F> {
85 pub size: usize,
87 pub algorithm: FFTAlgorithm,
89 pub twiddle_factors: Vec<Complex<F>>,
91 pub bit_reversal: Vec<usize>,
93 pub real_input: bool,
95}
96
97impl<F> FFTPlan<F>
98where
99 F: Float + FloatConst,
100{
101 pub fn new(size: usize, algorithm: FFTAlgorithm, realinput: bool) -> LinalgResult<Self> {
121 if size == 0 {
122 return Err(LinalgError::ShapeError(
123 "FFT size must be positive".to_string(),
124 ));
125 }
126
127 let selected_algorithm = match algorithm {
128 FFTAlgorithm::Auto => Self::select_algorithm(size),
129 _ => algorithm,
130 };
131
132 if let FFTAlgorithm::CooleyTukey = selected_algorithm {
134 if !size.is_power_of_two() {
135 return Err(LinalgError::ShapeError(
136 "Cooley-Tukey algorithm requires power-of-2 size".to_string(),
137 ));
138 }
139 }
140
141 let twiddle_factors = Self::compute_twiddle_factors(size);
142 let bit_reversal = Self::compute_bit_reversal(size);
143
144 Ok(FFTPlan {
145 size,
146 algorithm: selected_algorithm,
147 twiddle_factors,
148 bit_reversal,
149 real_input: realinput,
150 })
151 }
152
153 fn select_algorithm(size: usize) -> FFTAlgorithm {
155 if size.is_power_of_two() {
156 FFTAlgorithm::CooleyTukey
157 } else {
158 FFTAlgorithm::MixedRadix
159 }
160 }
161
162 fn compute_twiddle_factors(size: usize) -> Vec<Complex<F>> {
164 let mut twiddles = Vec::with_capacity(size);
165 let two_pi = F::from(2.0).expect("Failed to convert constant to float") * F::PI();
166
167 for k in 0..size {
168 let angle = -two_pi * F::from(k).expect("Failed to convert to float")
169 / F::from(size).expect("Failed to convert to float");
170 twiddles.push(Complex::new(angle.cos(), angle.sin()));
171 }
172
173 twiddles
174 }
175
176 fn compute_bit_reversal(size: usize) -> Vec<usize> {
178 let mut reversal = vec![0; size];
179 let logsize = (size as f64).log2() as usize;
180
181 for (i, item) in reversal.iter_mut().enumerate().take(size) {
182 *item = Self::reverse_bits(i, logsize);
183 }
184
185 reversal
186 }
187
188 fn reverse_bits(mut num: usize, bits: usize) -> usize {
190 let mut result = 0;
191 for _ in 0..bits {
192 result = (result << 1) | (num & 1);
193 num >>= 1;
194 }
195 result
196 }
197}
198
199#[allow(dead_code)]
227pub fn fft_1d(input: &ArrayView1<Complex64>, inverse: bool) -> LinalgResult<Array1<Complex64>> {
228 let size = input.len();
229
230 if !size.is_power_of_two() {
231 return Err(LinalgError::ShapeError(
232 "Input size must be power of 2 for Cooley-Tukey FFT".to_string(),
233 ));
234 }
235
236 let plan = FFTPlan::new(size, FFTAlgorithm::CooleyTukey, false)?;
237 fft_1d_with_plan(input, &plan, inverse)
238}
239
240#[allow(dead_code)]
242pub fn fft_1d_with_plan(
243 input: &ArrayView1<Complex64>,
244 plan: &FFTPlan<f64>,
245 inverse: bool,
246) -> LinalgResult<Array1<Complex64>> {
247 if input.len() != plan.size {
248 return Err(LinalgError::ShapeError(format!(
249 "Input size {} doesn't match plan size {}",
250 input.len(),
251 plan.size
252 )));
253 }
254
255 match plan.algorithm {
256 FFTAlgorithm::CooleyTukey => cooley_tukey_fft(input, plan, inverse),
257 FFTAlgorithm::MixedRadix => mixed_radix_fft(input, plan, inverse),
258 _ => Err(LinalgError::ComputationError(
259 "Unsupported FFT algorithm".to_string(),
260 )),
261 }
262}
263
264#[allow(dead_code)]
266fn cooley_tukey_fft(
267 input: &ArrayView1<Complex64>,
268 plan: &FFTPlan<f64>,
269 inverse: bool,
270) -> LinalgResult<Array1<Complex64>> {
271 let size = input.len();
272 let mut data = input.to_owned();
273
274 for i in 0..size {
276 let j = plan.bit_reversal[i];
277 if i < j {
278 data.swap(i, j);
279 }
280 }
281
282 let mut length = 2;
284 while length <= size {
285 let half_length = length / 2;
286 let step = size / length;
287
288 for start in (0..size).step_by(length) {
289 for i in 0..half_length {
290 let u = data[start + i];
291 let twiddle_index = i * step;
292 let mut twiddle = plan.twiddle_factors[twiddle_index];
293
294 if inverse {
295 twiddle = twiddle.conj();
296 }
297
298 let v = data[start + i + half_length] * twiddle;
299
300 data[start + i] = u + v;
301 data[start + i + half_length] = u - v;
302 }
303 }
304
305 length *= 2;
306 }
307
308 if inverse {
310 let scale = Complex64::new(1.0 / size as f64, 0.0);
311 for elem in data.iter_mut() {
312 *elem *= scale;
313 }
314 }
315
316 Ok(data)
317}
318
319#[allow(dead_code)]
321fn mixed_radix_fft(
322 input: &ArrayView1<Complex64>,
323 _plan: &FFTPlan<f64>,
324 inverse: bool,
325) -> LinalgResult<Array1<Complex64>> {
326 let size = input.len();
327
328 bluestein_fft(input, inverse)
330}
331
332#[allow(dead_code)]
334pub fn bluestein_fft(
335 input: &ArrayView1<Complex64>,
336 inverse: bool,
337) -> LinalgResult<Array1<Complex64>> {
338 let n = input.len();
339 if n <= 1 {
340 return Ok(input.to_owned());
341 }
342
343 let m = (2 * n - 1).next_power_of_two();
345
346 let mut chirp = Array1::zeros(m);
348 let pi_over_n = if inverse {
349 PI / n as f64
350 } else {
351 -PI / n as f64
352 };
353
354 for k in 0..n {
355 let arg = pi_over_n * (k * k) as f64;
356 chirp[k] = Complex64::new(arg.cos(), arg.sin());
357 if k > 0 && k < n {
358 chirp[m - k] = chirp[k];
359 }
360 }
361
362 let mut a = Array1::zeros(m);
364 for k in 0..n {
365 a[k] = input[k] * chirp[k];
366 }
367
368 let a_fft = fft_power_of_2(&a.view())?;
370 let chirp_fft = fft_power_of_2(&chirp.view())?;
371
372 let mut product = Array1::zeros(m);
374 for k in 0..m {
375 product[k] = a_fft[k] * chirp_fft[k];
376 }
377
378 let ifft_result = ifft_power_of_2(&product.view())?;
380
381 let mut result = Array1::zeros(n);
383 for k in 0..n {
384 result[k] = ifft_result[k] * chirp[k];
385 }
386
387 if inverse {
389 let scale = Complex64::new(1.0 / n as f64, 0.0);
390 for elem in result.iter_mut() {
391 *elem *= scale;
392 }
393 }
394
395 Ok(result)
396}
397
398#[allow(dead_code)]
400fn fft_power_of_2(input: &ArrayView1<Complex64>) -> LinalgResult<Array1<Complex64>> {
401 if input.len().is_power_of_two() {
402 fft_1d(input, false)
403 } else {
404 Err(LinalgError::ShapeError(
405 "Input size must be power of 2".to_string(),
406 ))
407 }
408}
409
410#[allow(dead_code)]
412fn ifft_power_of_2(input: &ArrayView1<Complex64>) -> LinalgResult<Array1<Complex64>> {
413 if input.len().is_power_of_two() {
414 fft_1d(input, true)
415 } else {
416 Err(LinalgError::ShapeError(
417 "Input size must be power of 2".to_string(),
418 ))
419 }
420}
421
422#[allow(dead_code)]
446pub fn rfft_1d(input: &ArrayView1<f64>) -> LinalgResult<Array1<Complex64>> {
447 let n = input.len();
448
449 let mut complex_input = Array1::zeros(n);
451 for i in 0..n {
452 complex_input[i] = Complex64::new(input[i], 0.0);
453 }
454
455 let full_fft = if n.is_power_of_two() {
457 fft_1d(&complex_input.view(), false)?
458 } else {
459 bluestein_fft(&complex_input.view(), false)?
460 };
461
462 let outputsize = n / 2 + 1;
464 let mut result = Array1::zeros(outputsize);
465
466 for i in 0..outputsize {
467 result[i] = full_fft[i];
468 }
469
470 Ok(result)
471}
472
473#[allow(dead_code)]
486pub fn irfft_1d(input: &ArrayView1<Complex64>, outputsize: usize) -> LinalgResult<Array1<f64>> {
487 if !outputsize.is_multiple_of(2) {
488 return Err(LinalgError::ShapeError(
489 "Output size must be even for IRFFT".to_string(),
490 ));
491 }
492
493 let expected_inputsize = outputsize / 2 + 1;
494 if input.len() != expected_inputsize {
495 return Err(LinalgError::ShapeError(format!(
496 "Input size {} doesn't match expected size {} for output size {}",
497 input.len(),
498 expected_inputsize,
499 outputsize
500 )));
501 }
502
503 let mut full_spectrum = Array1::zeros(outputsize);
505
506 for i in 0..input.len() {
508 full_spectrum[i] = input[i];
509 }
510
511 for i in 1..outputsize / 2 {
513 full_spectrum[outputsize - i] = input[i].conj();
514 }
515
516 let ifft_result = if outputsize.is_power_of_two() {
518 fft_1d(&full_spectrum.view(), true)?
519 } else {
520 bluestein_fft(&full_spectrum.view(), true)?
521 };
522
523 let mut result = Array1::zeros(outputsize);
525 for i in 0..outputsize {
526 result[i] = ifft_result[i].re;
527 }
528
529 Ok(result)
530}
531
532#[allow(dead_code)]
557pub fn fft_2d(input: &ArrayView2<Complex64>, inverse: bool) -> LinalgResult<Array2<Complex64>> {
558 let (rows, cols) = input.dim();
559 let mut result = input.to_owned();
560
561 for i in 0..rows {
563 let row = result.row(i).to_owned();
564 let row_fft = if cols.is_power_of_two() {
565 fft_1d(&row.view(), inverse)?
566 } else {
567 bluestein_fft(&row.view(), inverse)?
568 };
569
570 for j in 0..cols {
571 result[[i, j]] = row_fft[j];
572 }
573 }
574
575 for j in 0..cols {
577 let col = result.column(j).to_owned();
578 let col_fft = if rows.is_power_of_two() {
579 fft_1d(&col.view(), inverse)?
580 } else {
581 bluestein_fft(&col.view(), inverse)?
582 };
583
584 for i in 0..rows {
585 result[[i, j]] = col_fft[i];
586 }
587 }
588
589 Ok(result)
590}
591
592#[allow(dead_code)]
594pub fn fft_3d(input: &ArrayView3<Complex64>, inverse: bool) -> LinalgResult<Array3<Complex64>> {
595 let (depth, rows, cols) = input.dim();
596 let mut result = input.to_owned();
597
598 for i in 0..rows {
602 for j in 0..cols {
603 let mut line = Array1::zeros(depth);
604 for k in 0..depth {
605 line[k] = result[[k, i, j]];
606 }
607
608 let line_fft = if depth.is_power_of_two() {
609 fft_1d(&line.view(), inverse)?
610 } else {
611 bluestein_fft(&line.view(), inverse)?
612 };
613
614 for k in 0..depth {
615 result[[k, i, j]] = line_fft[k];
616 }
617 }
618 }
619
620 for k in 0..depth {
622 for j in 0..cols {
623 let mut line = Array1::zeros(rows);
624 for i in 0..rows {
625 line[i] = result[[k, i, j]];
626 }
627
628 let line_fft = if rows.is_power_of_two() {
629 fft_1d(&line.view(), inverse)?
630 } else {
631 bluestein_fft(&line.view(), inverse)?
632 };
633
634 for i in 0..rows {
635 result[[k, i, j]] = line_fft[i];
636 }
637 }
638 }
639
640 for k in 0..depth {
642 for i in 0..rows {
643 let mut line = Array1::zeros(cols);
644 for j in 0..cols {
645 line[j] = result[[k, i, j]];
646 }
647
648 let line_fft = if cols.is_power_of_two() {
649 fft_1d(&line.view(), inverse)?
650 } else {
651 bluestein_fft(&line.view(), inverse)?
652 };
653
654 for j in 0..cols {
655 result[[k, i, j]] = line_fft[j];
656 }
657 }
658 }
659
660 Ok(result)
661}
662
663#[allow(dead_code)]
674pub fn apply_window(signal: &ArrayView1<f64>, window: WindowFunction) -> LinalgResult<Array1<f64>> {
675 let n = signal.len();
676 let mut windowed = signal.to_owned();
677
678 match window {
679 WindowFunction::Rectangular => {
680 }
682 WindowFunction::Hann => {
683 for i in 0..n {
684 let factor = 0.5 * (1.0 - (2.0 * PI * i as f64 / (n - 1) as f64).cos());
685 windowed[i] *= factor;
686 }
687 }
688 WindowFunction::Hamming => {
689 for i in 0..n {
690 let factor = 0.54 - 0.46 * (2.0 * PI * i as f64 / (n - 1) as f64).cos();
691 windowed[i] *= factor;
692 }
693 }
694 WindowFunction::Blackman => {
695 for i in 0..n {
696 let x = 2.0 * PI * i as f64 / (n - 1) as f64;
697 let factor = 0.42 - 0.5 * x.cos() + 0.08 * (2.0 * x).cos();
698 windowed[i] *= factor;
699 }
700 }
701 WindowFunction::Kaiser(beta) => {
702 let i0_beta = modified_bessel_i0(beta);
704 for i in 0..n {
705 let x = 2.0 * i as f64 / (n - 1) as f64 - 1.0;
706 let factor = modified_bessel_i0(beta * (1.0 - x * x).sqrt()) / i0_beta;
707 windowed[i] *= factor;
708 }
709 }
710 WindowFunction::Tukey(alpha) => {
711 let taper_len = ((alpha * n as f64) / 2.0) as usize;
712 for i in 0..n {
713 let factor = if i < taper_len {
714 0.5 * (1.0 + (PI * i as f64 / taper_len as f64 - PI).cos())
715 } else if i >= n - taper_len {
716 0.5 * (1.0 + (PI * (n - 1 - i) as f64 / taper_len as f64 - PI).cos())
717 } else {
718 1.0
719 };
720 windowed[i] *= factor;
721 }
722 }
723 WindowFunction::Gaussian(sigma) => {
724 let center = (n - 1) as f64 / 2.0;
725 for i in 0..n {
726 let x = (i as f64 - center) / sigma;
727 let factor = (-0.5 * x * x).exp();
728 windowed[i] *= factor;
729 }
730 }
731 }
732
733 Ok(windowed)
734}
735
736#[allow(dead_code)]
738fn modified_bessel_i0(x: f64) -> f64 {
739 let mut result = 1.0;
740 let mut term = 1.0;
741 let mut k = 1.0;
742
743 while term.abs() > 1e-12 * result.abs() {
744 term *= (x / 2.0) * (x / 2.0) / (k * k);
745 result += term;
746 k += 1.0;
747 }
748
749 result
750}
751
752#[allow(dead_code)]
764pub fn dct_1d(input: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
765 let n = input.len();
766 let mut result = Array1::zeros(n);
767
768 for k in 0..n {
769 let mut sum = 0.0;
770 for i in 0..n {
771 let angle = PI * k as f64 * (2.0 * i as f64 + 1.0) / (2.0 * n as f64);
772 sum += input[i] * angle.cos();
773 }
774
775 let normalization = if k == 0 {
776 (1.0 / n as f64).sqrt()
777 } else {
778 (2.0 / n as f64).sqrt()
779 };
780
781 result[k] = sum * normalization;
782 }
783
784 Ok(result)
785}
786
787#[allow(dead_code)]
789pub fn idct_1d(input: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
790 let n = input.len();
791 let mut result = Array1::zeros(n);
792
793 for i in 0..n {
794 let mut sum = 0.0;
795
796 sum += input[0] * (1.0 / n as f64).sqrt();
798
799 for k in 1..n {
801 let angle = PI * k as f64 * (2.0 * i as f64 + 1.0) / (2.0 * n as f64);
802 sum += input[k] * (2.0 / n as f64).sqrt() * angle.cos();
803 }
804
805 result[i] = sum;
806 }
807
808 Ok(result)
809}
810
811#[allow(dead_code)]
813pub fn dst_1d(input: &ArrayView1<f64>) -> LinalgResult<Array1<f64>> {
814 let n = input.len();
815 let mut result = Array1::zeros(n);
816
817 for k in 0..n {
818 let mut sum = 0.0;
819 for i in 0..n {
820 let angle = PI * (k + 1) as f64 * (i + 1) as f64 / (n + 1) as f64;
821 sum += input[i] * angle.sin();
822 }
823
824 result[k] = sum * (2.0 / (n + 1) as f64).sqrt();
825 }
826
827 Ok(result)
828}
829
830#[allow(dead_code)]
844pub fn fft_convolve(
845 signal1: &ArrayView1<f64>,
846 signal2: &ArrayView1<f64>,
847) -> LinalgResult<Array1<f64>> {
848 let n1 = signal1.len();
849 let n2 = signal2.len();
850 let outputsize = n1 + n2 - 1;
851
852 let fftsize = outputsize.next_power_of_two();
854
855 let mut padded1 = Array1::zeros(fftsize);
857 let mut padded2 = Array1::zeros(fftsize);
858
859 for i in 0..n1 {
860 padded1[i] = signal1[i];
861 }
862 for i in 0..n2 {
863 padded2[i] = signal2[i];
864 }
865
866 let complex1 = rfft_1d(&padded1.view())?;
868 let complex2 = rfft_1d(&padded2.view())?;
869
870 let mut product = Array1::zeros(complex1.len());
872 for i in 0..complex1.len() {
873 product[i] = complex1[i] * complex2[i];
874 }
875
876 let result_full = irfft_1d(&product.view(), fftsize)?;
878
879 let mut result = Array1::zeros(outputsize);
881 for i in 0..outputsize {
882 result[i] = result_full[i];
883 }
884
885 Ok(result)
886}
887
888#[allow(dead_code)]
900pub fn periodogram_psd(
901 signal: &ArrayView1<f64>,
902 window: WindowFunction,
903 nfft: Option<usize>,
904) -> LinalgResult<Array1<f64>> {
905 let n = signal.len();
906 let fftsize = nfft.unwrap_or(n);
907
908 let windowed = apply_window(signal, window)?;
910
911 let mut padded = Array1::zeros(fftsize);
913 for i in 0..n.min(fftsize) {
914 padded[i] = windowed[i];
915 }
916
917 let fft_result = rfft_1d(&padded.view())?;
919
920 let mut psd = Array1::zeros(fft_result.len());
922 let normalization = 1.0 / (fftsize as f64);
923
924 for i in 0..fft_result.len() {
925 psd[i] = fft_result[i].norm_sqr() * normalization;
926 }
927
928 if fftsize.is_multiple_of(2) && fft_result.len() > 1 {
930 for i in 1..fft_result.len() - 1 {
932 psd[i] *= 2.0;
933 }
934 } else {
935 for i in 1..fft_result.len() {
937 psd[i] *= 2.0;
938 }
939 }
940
941 Ok(psd)
942}
943
944#[allow(dead_code)]
960pub fn welch_psd(
961 signal: &ArrayView1<f64>,
962 nperseg: usize,
963 overlap: f64,
964 window: WindowFunction,
965) -> LinalgResult<Array1<f64>> {
966 if !(0.0..1.0).contains(&overlap) {
967 return Err(LinalgError::ShapeError(
968 "Overlap must be between 0.0 and 1.0".to_string(),
969 ));
970 }
971
972 let n = signal.len();
973 let step = ((1.0 - overlap) * nperseg as f64) as usize;
974 let num_segments = if n >= nperseg {
975 (n - nperseg) / step + 1
976 } else {
977 0
978 };
979
980 if num_segments == 0 {
981 return Err(LinalgError::ShapeError(
982 "Signal too short for given segment length".to_string(),
983 ));
984 }
985
986 let fftsize = nperseg.next_power_of_two();
987 let outputsize = fftsize / 2 + 1;
988 let mut psd_sum = Array1::zeros(outputsize);
989
990 for seg in 0..num_segments {
991 let start = seg * step;
992 let end = (start + nperseg).min(n);
993
994 let mut segment = Array1::zeros(nperseg);
996 for i in 0..(end - start) {
997 segment[i] = signal[start + i];
998 }
999
1000 let segment_psd = periodogram_psd(&segment.view(), window, Some(fftsize))?;
1002
1003 for i in 0..outputsize {
1005 psd_sum[i] += segment_psd[i];
1006 }
1007 }
1008
1009 for i in 0..outputsize {
1011 psd_sum[i] /= num_segments as f64;
1012 }
1013
1014 Ok(psd_sum)
1015}
1016
1017#[allow(dead_code)]
1042pub fn hadamard_transform(input: &ArrayView1<f64>, inverse: bool) -> LinalgResult<Array1<f64>> {
1043 let n = input.len();
1044
1045 if !n.is_power_of_two() {
1046 return Err(LinalgError::ShapeError(
1047 "Input length must be a power of 2 for Hadamard transform".to_string(),
1048 ));
1049 }
1050
1051 if n == 0 {
1052 return Ok(Array1::zeros(0));
1053 }
1054
1055 let mut data = input.to_owned();
1056 let mut size = 1;
1057
1058 while size < n {
1060 for start in (0..n).step_by(size * 2) {
1061 for i in 0..size {
1062 let left = start + i;
1063 let right = start + i + size;
1064
1065 if right < n {
1066 let a = data[left];
1067 let b = data[right];
1068 data[left] = a + b;
1069 data[right] = a - b;
1070 }
1071 }
1072 }
1073 size *= 2;
1074 }
1075
1076 if inverse {
1078 let scale = 1.0 / n as f64;
1079 data.mapv_inplace(|x| x * scale);
1080 }
1081
1082 Ok(data)
1083}
1084
1085#[allow(dead_code)]
1099pub fn walsh_hadamard_transform(
1100 input: &ArrayView1<f64>,
1101 inverse: bool,
1102) -> LinalgResult<Array1<f64>> {
1103 let n = input.len();
1104
1105 if !n.is_power_of_two() {
1106 return Err(LinalgError::ShapeError(
1107 "Input length must be a power of 2 for Walsh-Hadamard transform".to_string(),
1108 ));
1109 }
1110
1111 let mut data = input.to_owned();
1112 let log_n = (n as f64).log2() as usize;
1113
1114 for i in 0..n {
1116 let j = bit_reverse(i, log_n);
1117 if i < j {
1118 data.swap(i, j);
1119 }
1120 }
1121
1122 hadamard_transform(&data.view(), inverse)
1124}
1125
1126#[allow(dead_code)]
1128fn bit_reverse(mut n: usize, bits: usize) -> usize {
1129 let mut result = 0;
1130 for _ in 0..bits {
1131 result = (result << 1) | (n & 1);
1132 n >>= 1;
1133 }
1134 result
1135}
1136
1137#[allow(dead_code)]
1151pub fn fast_walsh_transform(input: &ArrayView1<f64>, inverse: bool) -> LinalgResult<Array1<f64>> {
1152 let n = input.len();
1153
1154 if !n.is_power_of_two() {
1155 return Err(LinalgError::ShapeError(
1156 "Input length must be a power of 2 for Fast Walsh Transform".to_string(),
1157 ));
1158 }
1159
1160 let mut data = input.to_owned();
1161 let mut h = 1;
1162
1163 while h < n {
1164 for i in (0..n).step_by(h * 2) {
1165 for j in i..i + h {
1166 let u = data[j];
1167 let v = data[j + h];
1168 data[j] = u + v;
1169 data[j + h] = u - v;
1170 }
1171 }
1172 h *= 2;
1173 }
1174
1175 if inverse {
1177 let scale = 1.0 / n as f64;
1178 data.mapv_inplace(|x| x * scale);
1179 }
1180
1181 Ok(data)
1182}
1183
1184#[allow(dead_code)]
1196pub fn fft_frequencies(n: usize, sample_rate: f64, realfft: bool) -> Array1<f64> {
1197 let outputsize = if realfft { n / 2 + 1 } else { n };
1198 let mut freqs = Array1::zeros(outputsize);
1199
1200 let df = sample_rate / n as f64;
1201
1202 if realfft {
1203 for i in 0..outputsize {
1204 freqs[i] = i as f64 * df;
1205 }
1206 } else {
1207 for i in 0..outputsize {
1208 freqs[i] = if i <= n / 2 {
1209 i as f64 * df
1210 } else {
1211 (i as i64 - n as i64) as f64 * df
1212 };
1213 }
1214 }
1215
1216 freqs
1217}
1218
1219#[cfg(test)]
1220mod tests {
1221 use super::*;
1222 use approx::assert_relative_eq;
1223 use scirs2_core::ndarray::array;
1224
1225 #[test]
1226 fn test_fft_plan_creation() {
1227 let plan =
1228 FFTPlan::<f64>::new(8, FFTAlgorithm::CooleyTukey, false).expect("Operation failed");
1229 assert_eq!(plan.size, 8);
1230 assert_eq!(plan.twiddle_factors.len(), 8);
1231 assert_eq!(plan.bit_reversal.len(), 8);
1232 }
1233
1234 #[test]
1235 fn test_fft_1d_basic() {
1236 let input = array![
1237 Complex64::new(1.0, 0.0),
1238 Complex64::new(0.0, 0.0),
1239 Complex64::new(0.0, 0.0),
1240 Complex64::new(0.0, 0.0),
1241 ];
1242
1243 let result = fft_1d(&input.view(), false).expect("Operation failed");
1244 assert_eq!(result.len(), 4);
1245
1246 assert_relative_eq!(result[0].re, 1.0, epsilon = 1e-10);
1248 assert_relative_eq!(result[0].im, 0.0, epsilon = 1e-10);
1249 }
1250
1251 #[test]
1252 fn test_fft_inverse_property() {
1253 let input = array![
1254 Complex64::new(1.0, 0.5),
1255 Complex64::new(0.2, -0.3),
1256 Complex64::new(-0.1, 0.8),
1257 Complex64::new(0.7, -0.2),
1258 ];
1259
1260 let fft_result = fft_1d(&input.view(), false).expect("Operation failed");
1261 let ifft_result = fft_1d(&fft_result.view(), true).expect("Operation failed");
1262
1263 for i in 0..input.len() {
1264 assert_relative_eq!(input[i].re, ifft_result[i].re, epsilon = 1e-12);
1265 assert_relative_eq!(input[i].im, ifft_result[i].im, epsilon = 1e-12);
1266 }
1267 }
1268
1269 #[test]
1270 fn test_rfft_1d() {
1271 let input = array![1.0, 0.0, 0.0, 0.0];
1272 let result = rfft_1d(&input.view()).expect("Operation failed");
1273
1274 assert_eq!(result.len(), 3); assert_relative_eq!(result[0].re, 1.0, epsilon = 1e-10);
1276 assert_relative_eq!(result[0].im, 0.0, epsilon = 1e-10);
1277 }
1278
1279 #[test]
1280 fn test_irfft_1d() {
1281 let input = array![1.0, 0.0, 0.0, 0.0];
1282 let fft_result = rfft_1d(&input.view()).expect("Operation failed");
1283 let reconstructed = irfft_1d(&fft_result.view(), 4).expect("Operation failed");
1284
1285 for i in 0..input.len() {
1286 assert_relative_eq!(input[i], reconstructed[i], epsilon = 1e-12);
1287 }
1288 }
1289
1290 #[test]
1291 fn test_fft_2d() {
1292 let input = Array2::from_shape_fn((4, 4), |(i, j)| Complex64::new((i + j) as f64, 0.0));
1293
1294 let result = fft_2d(&input.view(), false).expect("Operation failed");
1295 assert_eq!(result.shape(), &[4, 4]);
1296
1297 let reconstructed = fft_2d(&result.view(), true).expect("Operation failed");
1298
1299 for i in 0..4 {
1300 for j in 0..4 {
1301 assert_relative_eq!(input[[i, j]].re, reconstructed[[i, j]].re, epsilon = 1e-12);
1302 assert_relative_eq!(input[[i, j]].im, reconstructed[[i, j]].im, epsilon = 1e-12);
1303 }
1304 }
1305 }
1306
1307 #[test]
1308 fn test_window_functions() {
1309 let signal = array![1.0, 1.0, 1.0, 1.0];
1310
1311 let rect =
1313 apply_window(&signal.view(), WindowFunction::Rectangular).expect("Operation failed");
1314 for i in 0..signal.len() {
1315 assert_relative_eq!(signal[i], rect[i], epsilon = 1e-10);
1316 }
1317
1318 let hann = apply_window(&signal.view(), WindowFunction::Hann).expect("Operation failed");
1320 assert_relative_eq!(hann[0], 0.0, epsilon = 1e-10);
1321 assert_relative_eq!(hann[3], 0.0, epsilon = 1e-10);
1322 assert!(hann[1] > 0.0);
1323 assert!(hann[2] > 0.0);
1324 }
1325
1326 #[test]
1327 fn test_dct_1d() {
1328 let input = array![1.0, 0.0, 0.0, 0.0];
1329 let dct_result = dct_1d(&input.view()).expect("Operation failed");
1330 let idct_result = idct_1d(&dct_result.view()).expect("Operation failed");
1331
1332 for i in 0..input.len() {
1333 assert_relative_eq!(input[i], idct_result[i], epsilon = 1e-12);
1334 }
1335 }
1336
1337 #[test]
1338 fn test_dst_1d() {
1339 let input = array![1.0, 2.0, 3.0, 4.0];
1340 let dst_result = dst_1d(&input.view()).expect("Operation failed");
1341 assert_eq!(dst_result.len(), 4);
1342 assert!(!dst_result.iter().all(|&x| x == 0.0));
1343 }
1344
1345 #[test]
1346 fn test_fft_convolve() {
1347 let signal1 = array![1.0, 2.0, 3.0];
1348 let signal2 = array![0.5, 1.5];
1349
1350 let result = fft_convolve(&signal1.view(), &signal2.view()).expect("Operation failed");
1351 assert_eq!(result.len(), 4); assert_relative_eq!(result[0], 1.0 * 0.5, epsilon = 1e-10);
1355 }
1356
1357 #[test]
1358 fn test_periodogram_psd() {
1359 let signal = Array1::from_shape_fn(16, |i| (2.0 * PI * i as f64 / 16.0).sin());
1360 let psd = periodogram_psd(&signal.view(), WindowFunction::Rectangular, None)
1361 .expect("Operation failed");
1362
1363 assert_eq!(psd.len(), 9); assert!(psd.iter().all(|&x| x >= 0.0));
1365 }
1366
1367 #[test]
1368 fn test_welch_psd() {
1369 let signal = Array1::from_shape_fn(64, |i| (2.0 * PI * i as f64 / 8.0).sin());
1370 let psd =
1371 welch_psd(&signal.view(), 16, 0.5, WindowFunction::Hann).expect("Operation failed");
1372
1373 assert!(!psd.is_empty());
1374 assert!(psd.iter().all(|&x| x >= 0.0));
1375 }
1376
1377 #[test]
1378 fn test_fft_frequencies() {
1379 let freqs = fft_frequencies(8, 1000.0, true);
1380 assert_eq!(freqs.len(), 5); assert_relative_eq!(freqs[0], 0.0, epsilon = 1e-10);
1383 assert_relative_eq!(freqs[1], 125.0, epsilon = 1e-10); assert_relative_eq!(freqs[4], 500.0, epsilon = 1e-10); }
1386
1387 #[test]
1388 fn test_bluestein_arbitrarysize() {
1389 let input = array![
1390 Complex64::new(1.0, 0.0),
1391 Complex64::new(0.0, 0.0),
1392 Complex64::new(0.0, 0.0),
1393 Complex64::new(0.0, 0.0),
1394 Complex64::new(0.0, 0.0),
1395 ]; let result = bluestein_fft(&input.view(), false).expect("Operation failed");
1398 let reconstructed = bluestein_fft(&result.view(), true).expect("Operation failed");
1399
1400 for i in 0..input.len() {
1401 assert_relative_eq!(input[i].re, reconstructed[i].re, epsilon = 1e-10);
1402 assert_relative_eq!(input[i].im, reconstructed[i].im, epsilon = 1e-10);
1403 }
1404 }
1405
1406 #[test]
1407 fn test_fft_3d() {
1408 let input = Array3::from_shape_fn((2, 2, 2), |(i, j, k)| {
1409 Complex64::new((i + j + k) as f64, 0.0)
1410 });
1411
1412 let result = fft_3d(&input.view(), false).expect("Operation failed");
1413 assert_eq!(result.shape(), &[2, 2, 2]);
1414
1415 let reconstructed = fft_3d(&result.view(), true).expect("Operation failed");
1416
1417 for i in 0..2 {
1418 for j in 0..2 {
1419 for k in 0..2 {
1420 assert_relative_eq!(
1421 input[[i, j, k]].re,
1422 reconstructed[[i, j, k]].re,
1423 epsilon = 1e-12
1424 );
1425 assert_relative_eq!(
1426 input[[i, j, k]].im,
1427 reconstructed[[i, j, k]].im,
1428 epsilon = 1e-12
1429 );
1430 }
1431 }
1432 }
1433 }
1434
1435 #[test]
1436 fn test_kaiser_window() {
1437 let signal = array![1.0, 1.0, 1.0, 1.0, 1.0];
1438 let windowed =
1439 apply_window(&signal.view(), WindowFunction::Kaiser(2.0)).expect("Operation failed");
1440
1441 assert!(windowed[0] < windowed[2]); assert!(windowed[4] < windowed[2]); assert!(windowed[2] > 0.0); }
1446
1447 #[test]
1448 fn test_tukey_window() {
1449 let signal = Array1::ones(10);
1450 let windowed =
1451 apply_window(&signal.view(), WindowFunction::Tukey(0.5)).expect("Operation failed");
1452
1453 assert!(windowed[0] < windowed[5]); assert!(windowed[9] < windowed[5]); }
1457
1458 #[test]
1459 fn test_hadamard_transform() {
1460 let input = array![1.0, 1.0, 1.0, 1.0];
1461 let result = hadamard_transform(&input.view(), false).expect("Operation failed");
1462
1463 assert_relative_eq!(result[0], 4.0, epsilon = 1e-12);
1465 assert_relative_eq!(result[1], 0.0, epsilon = 1e-12);
1466 assert_relative_eq!(result[2], 0.0, epsilon = 1e-12);
1467 assert_relative_eq!(result[3], 0.0, epsilon = 1e-12);
1468
1469 let reconstructed = hadamard_transform(&result.view(), true).expect("Operation failed");
1471 for i in 0..4 {
1472 assert_relative_eq!(input[i], reconstructed[i], epsilon = 1e-12);
1473 }
1474 }
1475
1476 #[test]
1477 fn test_walsh_hadamard_transform() {
1478 let input = array![1.0, 0.0, 1.0, 0.0];
1479 let result = walsh_hadamard_transform(&input.view(), false).expect("Operation failed");
1480
1481 assert_eq!(result.len(), 4);
1483
1484 let reconstructed =
1486 walsh_hadamard_transform(&result.view(), true).expect("Operation failed");
1487 for i in 0..4 {
1488 assert_relative_eq!(input[i], reconstructed[i], epsilon = 1e-12);
1489 }
1490 }
1491
1492 #[test]
1493 fn test_fast_walsh_transform() {
1494 let input = array![1.0, -1.0, 1.0, -1.0];
1495 let result = fast_walsh_transform(&input.view(), false).expect("Operation failed");
1496
1497 assert_eq!(result.len(), 4);
1499
1500 let reconstructed = fast_walsh_transform(&result.view(), true).expect("Operation failed");
1502 for i in 0..4 {
1503 assert_relative_eq!(input[i], reconstructed[i], epsilon = 1e-12);
1504 }
1505 }
1506
1507 #[test]
1508 fn test_hadamard_transform_properties() {
1509 let input = array![1.0, 2.0, 3.0, 4.0];
1511 let transformed = hadamard_transform(&input.view(), false).expect("Operation failed");
1512 let twice_transformed =
1513 hadamard_transform(&transformed.view(), false).expect("Operation failed");
1514
1515 let n = input.len() as f64;
1517 for i in 0..4 {
1518 assert_relative_eq!(twice_transformed[i], n * input[i], epsilon = 1e-12);
1519 }
1520 }
1521
1522 #[test]
1523 fn test_bit_reverse() {
1524 assert_eq!(bit_reverse(0, 3), 0); assert_eq!(bit_reverse(1, 3), 4); assert_eq!(bit_reverse(2, 3), 2); assert_eq!(bit_reverse(3, 3), 6); assert_eq!(bit_reverse(4, 3), 1); }
1530}