1use scirs2_core::ndarray::{s, Array1};
8use scirs2_core::numeric::{Float, FromPrimitive};
9use std::fmt::Debug;
10
11use super::config::{EnhancedPeriodogramConfig, SpectralAnalysisConfig};
12use super::utils::{
13 BispectrumFeatures, PhaseSpectrumFeatures, PhaseSpectrumResult, ScaleSpectralFeatures,
14};
15use crate::error::Result;
16use crate::utils::autocorrelation;
17
18#[derive(Debug, Clone)]
20pub struct FrequencyFeatures<F> {
21 pub spectral_centroid: F,
23 pub spectral_spread: F,
25 pub spectral_skewness: F,
27 pub spectral_kurtosis: F,
29 pub spectral_entropy: F,
31 pub spectral_rolloff: F,
33 pub spectral_flux: F,
35 pub dominant_frequency: F,
37 pub spectral_peaks: usize,
39 pub frequency_bands: Vec<F>,
41 pub spectral_analysis: SpectralAnalysisFeatures<F>,
43 pub enhanced_periodogram_features: EnhancedPeriodogramFeatures<F>,
45 pub wavelet_features: WaveletFeatures<F>,
47 pub emd_features: EMDFeatures<F>,
49}
50
51impl<F> Default for FrequencyFeatures<F>
52where
53 F: Float + FromPrimitive + Default,
54{
55 fn default() -> Self {
56 Self {
57 spectral_centroid: F::zero(),
58 spectral_spread: F::zero(),
59 spectral_skewness: F::zero(),
60 spectral_kurtosis: F::zero(),
61 spectral_entropy: F::zero(),
62 spectral_rolloff: F::zero(),
63 spectral_flux: F::zero(),
64 dominant_frequency: F::zero(),
65 spectral_peaks: 0,
66 frequency_bands: Vec::new(),
67 spectral_analysis: SpectralAnalysisFeatures::default(),
68 enhanced_periodogram_features: EnhancedPeriodogramFeatures::default(),
69 wavelet_features: WaveletFeatures::default(),
70 emd_features: EMDFeatures::default(),
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct SpectralAnalysisFeatures<F> {
78 pub welch_psd: Vec<F>,
81 pub periodogram_psd: Vec<F>,
83 pub ar_psd: Vec<F>,
85 pub frequency_resolution: F,
87 pub total_power: F,
89 pub normalized_psd: Vec<F>,
91
92 pub peak_frequencies: Vec<F>,
95 pub peak_magnitudes: Vec<F>,
97 pub peak_widths: Vec<F>,
99 pub peak_prominences: Vec<F>,
101 pub significant_peaks_count: usize,
103 pub peak_density: F,
105 pub average_peak_spacing: F,
107 pub peak_asymmetry: Vec<F>,
109
110 pub delta_power: F,
113 pub theta_power: F,
115 pub alpha_power: F,
117 pub beta_power: F,
119 pub gamma_power: F,
121 pub low_freq_power: F,
123 pub high_freq_power: F,
125 pub relative_band_powers: Vec<F>,
127 pub band_power_ratios: Vec<F>,
129 pub band_entropy: F,
131
132 pub spectral_shannon_entropy: F,
135 pub spectral_renyi_entropy: F,
137 pub spectral_permutation_entropy: F,
139 pub spectral_sample_entropy: F,
141 pub spectral_complexity: F,
143 pub spectral_information_density: F,
145 pub spectral_approximate_entropy: F,
147
148 pub spectral_flatness: F,
151 pub spectral_crest_factor: F,
153 pub spectral_irregularity: F,
155 pub spectral_smoothness: F,
157 pub spectral_slope: F,
159 pub spectral_decrease: F,
161 pub spectral_brightness: F,
163 pub spectral_roughness: F,
165
166 pub spectral_autocorrelation: Vec<F>,
169 pub cross_spectral_coherence: Vec<F>,
171 pub spectral_coherence_mean: F,
173 pub phase_spectrum_features: PhaseSpectrumFeatures<F>,
175 pub bispectrum_features: BispectrumFeatures<F>,
177
178 pub frequency_stability: F,
181 pub spectral_variability: F,
183 pub frequency_modulation_index: F,
185 pub spectral_purity: F,
187
188 pub multiscale_spectral_features: Vec<ScaleSpectralFeatures<F>>,
191 pub cross_frequency_coupling: Vec<F>,
193 pub phase_amplitude_coupling: Vec<F>,
195 pub cross_scale_correlations: Vec<F>,
197
198 pub stft_features: Vec<F>,
201 pub spectrogram_entropy: F,
203 pub time_frequency_localization: F,
205 pub instantaneous_frequency_stats: Vec<F>,
207}
208
209impl<F> Default for SpectralAnalysisFeatures<F>
210where
211 F: Float + FromPrimitive + Default,
212{
213 fn default() -> Self {
214 Self {
215 welch_psd: Vec::new(),
217 periodogram_psd: Vec::new(),
218 ar_psd: Vec::new(),
219 frequency_resolution: F::zero(),
220 total_power: F::zero(),
221 normalized_psd: Vec::new(),
222
223 peak_frequencies: Vec::new(),
225 peak_magnitudes: Vec::new(),
226 peak_widths: Vec::new(),
227 peak_prominences: Vec::new(),
228 significant_peaks_count: 0,
229 peak_density: F::zero(),
230 average_peak_spacing: F::zero(),
231 peak_asymmetry: Vec::new(),
232
233 delta_power: F::zero(),
235 theta_power: F::zero(),
236 alpha_power: F::zero(),
237 beta_power: F::zero(),
238 gamma_power: F::zero(),
239 low_freq_power: F::zero(),
240 high_freq_power: F::zero(),
241 relative_band_powers: Vec::new(),
242 band_power_ratios: Vec::new(),
243 band_entropy: F::zero(),
244
245 spectral_shannon_entropy: F::zero(),
247 spectral_renyi_entropy: F::zero(),
248 spectral_permutation_entropy: F::zero(),
249 spectral_sample_entropy: F::zero(),
250 spectral_complexity: F::zero(),
251 spectral_information_density: F::zero(),
252 spectral_approximate_entropy: F::zero(),
253
254 spectral_flatness: F::zero(),
256 spectral_crest_factor: F::zero(),
257 spectral_irregularity: F::zero(),
258 spectral_smoothness: F::zero(),
259 spectral_slope: F::zero(),
260 spectral_decrease: F::zero(),
261 spectral_brightness: F::zero(),
262 spectral_roughness: F::zero(),
263
264 spectral_autocorrelation: Vec::new(),
266 cross_spectral_coherence: Vec::new(),
267 spectral_coherence_mean: F::zero(),
268 phase_spectrum_features: PhaseSpectrumFeatures::default(),
269 bispectrum_features: BispectrumFeatures::default(),
270
271 frequency_stability: F::zero(),
273 spectral_variability: F::zero(),
274 frequency_modulation_index: F::zero(),
275 spectral_purity: F::zero(),
276
277 multiscale_spectral_features: Vec::new(),
279 cross_frequency_coupling: Vec::new(),
280 phase_amplitude_coupling: Vec::new(),
281 cross_scale_correlations: Vec::new(),
282
283 stft_features: Vec::new(),
285 spectrogram_entropy: F::zero(),
286 time_frequency_localization: F::zero(),
287 instantaneous_frequency_stats: Vec::new(),
288 }
289 }
290}
291
292#[derive(Debug, Clone)]
294pub struct EnhancedPeriodogramFeatures<F> {
295 pub bartlett_periodogram: Vec<F>,
298 pub welch_periodogram: Vec<F>,
300 pub multitaper_periodogram: Vec<F>,
302 pub blackman_tukey_periodogram: Vec<F>,
304 pub capon_periodogram: Vec<F>,
306 pub music_periodogram: Vec<F>,
308 pub ar_periodogram: Vec<F>,
310
311 pub window_type: WindowTypeInfo<F>,
314 pub window_effectiveness: F,
316 pub spectral_leakage: F,
318 pub optimal_window_type: String,
320 pub window_comparison_metrics: Vec<F>,
322
323 pub cross_periodogram: Vec<F>,
326 pub coherence_function: Vec<F>,
328 pub phase_spectrum_result: PhaseSpectrumResult<F>,
330 pub periodogram_xcorr: Vec<F>,
332
333 pub confidence_intervals: Vec<(F, F)>,
336 pub peak_significance: Vec<F>,
338 pub goodness_of_fit_statistics: Vec<F>,
340 pub variance_estimates: Vec<F>,
342 pub bias_estimates: Vec<F>,
344
345 pub bias_corrected_periodogram: Vec<F>,
348 pub variance_reduced_periodogram: Vec<F>,
350 pub smoothed_periodogram: Vec<F>,
352
353 pub zero_padded_periodogram: Vec<F>,
356 pub zero_padding_effectiveness: F,
358 pub interpolated_periodogram: Vec<F>,
360 pub interpolation_effectiveness: F,
362
363 pub snr_estimate: F,
366 pub dynamic_range: F,
368 pub spectral_purity_measure: F,
370 pub frequency_stability_measures: Vec<F>,
372 pub error_bounds: Vec<F>,
374 pub computational_efficiency: F,
376 pub memory_efficiency: F,
378
379 pub multiscale_coherence: Vec<F>,
382 pub cross_scale_correlations: Vec<F>,
384 pub hierarchical_analysis: Vec<F>,
386 pub scale_dependent_statistics: Vec<F>,
388}
389
390impl<F> Default for EnhancedPeriodogramFeatures<F>
391where
392 F: Float + FromPrimitive + Default,
393{
394 fn default() -> Self {
395 Self {
396 bartlett_periodogram: Vec::new(),
398 welch_periodogram: Vec::new(),
399 multitaper_periodogram: Vec::new(),
400 blackman_tukey_periodogram: Vec::new(),
401 capon_periodogram: Vec::new(),
402 music_periodogram: Vec::new(),
403 ar_periodogram: Vec::new(),
404
405 window_type: WindowTypeInfo::default(),
407 window_effectiveness: F::zero(),
408 spectral_leakage: F::zero(),
409 optimal_window_type: String::new(),
410 window_comparison_metrics: Vec::new(),
411
412 cross_periodogram: Vec::new(),
414 coherence_function: Vec::new(),
415 phase_spectrum_result: (
416 Vec::new(),
417 Vec::new(),
418 F::zero(),
419 PhaseSpectrumFeatures::default(),
420 BispectrumFeatures::default(),
421 ),
422 periodogram_xcorr: Vec::new(),
423
424 confidence_intervals: Vec::new(),
426 peak_significance: Vec::new(),
427 goodness_of_fit_statistics: Vec::new(),
428 variance_estimates: Vec::new(),
429 bias_estimates: Vec::new(),
430
431 bias_corrected_periodogram: Vec::new(),
433 variance_reduced_periodogram: Vec::new(),
434 smoothed_periodogram: Vec::new(),
435
436 zero_padded_periodogram: Vec::new(),
438 zero_padding_effectiveness: F::zero(),
439 interpolated_periodogram: Vec::new(),
440 interpolation_effectiveness: F::zero(),
441
442 snr_estimate: F::zero(),
444 dynamic_range: F::zero(),
445 spectral_purity_measure: F::zero(),
446 frequency_stability_measures: Vec::new(),
447 error_bounds: Vec::new(),
448 computational_efficiency: F::zero(),
449 memory_efficiency: F::zero(),
450
451 multiscale_coherence: Vec::new(),
453 cross_scale_correlations: Vec::new(),
454 hierarchical_analysis: Vec::new(),
455 scale_dependent_statistics: Vec::new(),
456 }
457 }
458}
459
460#[derive(Debug, Clone)]
462pub struct WindowTypeInfo<F> {
463 pub window_name: String,
465 pub main_lobe_width: F,
467 pub side_lobe_level: F,
469 pub scalloping_loss: F,
471 pub processing_gain: F,
473 pub noise_bandwidth: F,
475 pub coherent_gain: F,
477 pub window_length: usize,
479 pub equivalent_noise_bandwidth: F,
481 pub overlap_correlation: F,
483}
484
485impl<F> Default for WindowTypeInfo<F>
486where
487 F: Float + FromPrimitive,
488{
489 fn default() -> Self {
490 Self {
491 window_name: "Hanning".to_string(),
492 main_lobe_width: F::zero(),
493 side_lobe_level: F::zero(),
494 scalloping_loss: F::zero(),
495 processing_gain: F::zero(),
496 noise_bandwidth: F::zero(),
497 coherent_gain: F::zero(),
498 window_length: 0,
499 equivalent_noise_bandwidth: F::zero(),
500 overlap_correlation: F::zero(),
501 }
502 }
503}
504
505#[derive(Debug, Clone, Default)]
507pub struct WaveletFeatures<F> {
508 pub scale_energies: Vec<F>,
510 pub wavelet_entropy: F,
512}
513
514#[derive(Debug, Clone, Default)]
516pub struct EMDFeatures<F> {
517 pub num_imfs: usize,
519 pub imf_energies: Vec<F>,
521 pub instantaneous_frequencies: Vec<F>,
523}
524
525#[allow(dead_code)]
531pub fn calculate_frequency_features<F>(
532 ts: &Array1<F>,
533 config: &SpectralAnalysisConfig,
534) -> Result<FrequencyFeatures<F>>
535where
536 F: Float
537 + FromPrimitive
538 + Debug
539 + scirs2_core::ndarray::ScalarOperand
540 + std::iter::Sum
541 + Default,
542 for<'a> F: std::iter::Sum<&'a F>,
543{
544 let n = ts.len();
545 if n < 4 {
546 return Ok(FrequencyFeatures::default());
547 }
548
549 let spectrum = calculate_simple_periodogram(ts)?;
551 let frequencies = (0..spectrum.len())
552 .map(|i| F::from(i).unwrap() / F::from(spectrum.len() * 2).unwrap())
553 .collect::<Vec<_>>();
554
555 let _total_power = spectrum.iter().fold(F::zero(), |acc, &x| acc + x);
557 let spectral_centroid = calculate_spectral_centroid(&spectrum, &frequencies);
558 let spectral_spread = calculate_spectral_spread(&spectrum, &frequencies, spectral_centroid);
559 let spectral_skewness =
560 calculate_spectral_skewness(&spectrum, &frequencies, spectral_centroid, spectral_spread);
561 let spectral_kurtosis =
562 calculate_spectral_kurtosis(&spectrum, &frequencies, spectral_centroid, spectral_spread);
563
564 let spectral_entropy = calculate_spectral_entropy(&spectrum);
566 let spectral_rolloff =
567 calculate_spectral_rolloff(&spectrum, &frequencies, F::from(0.95).unwrap());
568 let spectral_flux = F::zero(); let dominant_frequency = find_dominant_frequency(&spectrum, &frequencies);
570
571 let (peak_frequencies, _peak_magnitudes) = find_spectral_peaks(&spectrum, &frequencies)?;
573 let spectral_peaks = peak_frequencies.len();
574
575 let frequency_bands = calculate_frequency_bands(&spectrum, &frequencies);
577
578 let spectral_analysis = if config.calculate_welch_psd || config.calculate_periodogram_psd {
580 calculate_spectral_analysis_features(ts, config)?
581 } else {
582 SpectralAnalysisFeatures::default()
583 };
584
585 let enhanced_periodogram_features = EnhancedPeriodogramFeatures::default();
587 let wavelet_features = WaveletFeatures::default();
588 let emd_features = EMDFeatures::default();
589
590 Ok(FrequencyFeatures {
591 spectral_centroid,
592 spectral_spread,
593 spectral_skewness,
594 spectral_kurtosis,
595 spectral_entropy,
596 spectral_rolloff,
597 spectral_flux,
598 dominant_frequency,
599 spectral_peaks,
600 frequency_bands,
601 spectral_analysis,
602 enhanced_periodogram_features,
603 wavelet_features,
604 emd_features,
605 })
606}
607
608#[allow(dead_code)]
610pub fn calculate_enhanced_periodogram_features<F>(
611 ts: &Array1<F>,
612 config: &EnhancedPeriodogramConfig,
613) -> Result<EnhancedPeriodogramFeatures<F>>
614where
615 F: Float
616 + FromPrimitive
617 + Debug
618 + Default
619 + scirs2_core::ndarray::ScalarOperand
620 + std::iter::Sum,
621 for<'a> F: std::iter::Sum<&'a F>,
622{
623 let n = ts.len();
624 if n < 8 {
625 return Ok(EnhancedPeriodogramFeatures::default());
626 }
627
628 let mut features = EnhancedPeriodogramFeatures::default();
629
630 if config.enable_bartlett_method {
632 features.bartlett_periodogram = calculate_bartlett_periodogram(ts, config)?;
633 }
634
635 if config.enable_enhanced_welch {
636 features.welch_periodogram = calculate_enhanced_welch_periodogram(ts, config)?;
637 }
638
639 if config.enable_multitaper {
640 features.multitaper_periodogram = calculate_multitaper_periodogram(ts, config)?;
641 }
642
643 if config.enable_blackman_tukey {
644 features.blackman_tukey_periodogram = calculate_blackman_tukey_periodogram(ts, config)?;
645 }
646
647 if config.enable_enhanced_ar {
648 features.ar_periodogram = calculate_enhanced_ar_periodogram(ts, config)?;
649 }
650
651 if config.enable_window_analysis {
653 features.window_type = calculate_window_analysis(ts, config)?;
654 features.window_effectiveness = calculate_window_effectiveness(&features.window_type);
655 features.spectral_leakage = calculate_spectral_leakage(&features.window_type);
656 }
657
658 if config.enable_confidence_intervals {
660 features.confidence_intervals =
661 calculate_periodogram_confidence_intervals(&features.welch_periodogram, config)?;
662 }
663
664 if config.enable_significance_testing {
665 features.peak_significance =
666 calculate_peak_significance(&features.welch_periodogram, config)?;
667 }
668
669 if config.enable_bias_correction {
671 features.bias_corrected_periodogram =
672 calculate_bias_corrected_periodogram(&features.welch_periodogram, config)?;
673 }
674
675 if config.enable_variance_reduction {
676 features.variance_reduced_periodogram =
677 calculate_variance_reduced_periodogram(&features.welch_periodogram, config)?;
678 }
679
680 if config.enable_smoothing {
681 features.smoothed_periodogram =
682 calculate_smoothed_periodogram(&features.welch_periodogram, config)?;
683 }
684
685 if config.enable_zero_padding {
687 features.zero_padded_periodogram = calculate_zero_padded_periodogram(ts, config)?;
688 features.zero_padding_effectiveness = calculate_zero_padding_effectiveness(
689 &features.zero_padded_periodogram,
690 &features.welch_periodogram,
691 );
692 }
693
694 features.snr_estimate = calculate_snr_from_periodogram(&features.welch_periodogram)?;
700
701 features.dynamic_range = calculate_dynamic_range(&features.welch_periodogram);
703
704 if config.enable_enhanced_welch {
705 features.spectral_purity_measure = calculate_spectral_purity(&features.welch_periodogram);
706 }
707
708 Ok(features)
709}
710
711#[allow(dead_code)]
717pub fn calculate_bartlett_periodogram<F>(
718 ts: &Array1<F>,
719 config: &EnhancedPeriodogramConfig,
720) -> Result<Vec<F>>
721where
722 F: Float + FromPrimitive + Debug + std::iter::Sum,
723 for<'a> F: std::iter::Sum<&'a F>,
724{
725 let n = ts.len();
726 let segment_length = n / config.bartlett_num_segments;
727
728 if segment_length < 4 {
729 return Ok(vec![F::zero(); n / 2]);
730 }
731
732 let mut averaged_periodogram = vec![F::zero(); segment_length / 2];
733 let mut segment_count = 0;
734
735 for i in 0..config.bartlett_num_segments {
736 let start_idx = i * segment_length;
737 let end_idx = std::cmp::min(start_idx + segment_length, n);
738
739 if end_idx - start_idx >= 4 {
740 let segment = ts.slice(s![start_idx..end_idx]).to_owned();
741 let segment_periodogram = calculate_simple_periodogram(&segment)?;
742
743 for (j, &value) in segment_periodogram.iter().enumerate() {
744 if j < averaged_periodogram.len() {
745 averaged_periodogram[j] = averaged_periodogram[j] + value;
746 }
747 }
748 segment_count += 1;
749 }
750 }
751
752 if segment_count > 0 {
754 let count_f = F::from_usize(segment_count).unwrap();
755 for value in averaged_periodogram.iter_mut() {
756 *value = *value / count_f;
757 }
758 }
759
760 Ok(averaged_periodogram)
761}
762
763#[allow(dead_code)]
765pub fn calculate_enhanced_welch_periodogram<F>(
766 ts: &Array1<F>,
767 config: &EnhancedPeriodogramConfig,
768) -> Result<Vec<F>>
769where
770 F: Float + FromPrimitive + Debug + std::iter::Sum,
771 for<'a> F: std::iter::Sum<&'a F>,
772{
773 let n = ts.len();
774 let window_length = (n as f64 * 0.25).round() as usize; let overlap = (window_length as f64 * 0.5).round() as usize; if window_length < 4 {
778 return calculate_simple_periodogram(ts);
779 }
780
781 let window = create_window(&config.primary_window_type, window_length)?;
782 let step_size = window_length - overlap;
783 let num_segments = (n - overlap) / step_size;
784
785 if num_segments == 0 {
786 return calculate_simple_periodogram(ts);
787 }
788
789 let mut averaged_periodogram = vec![F::zero(); window_length / 2];
790 let mut segment_count = 0;
791
792 for i in 0..num_segments {
793 let start_idx = i * step_size;
794 let end_idx = std::cmp::min(start_idx + window_length, n);
795
796 if end_idx - start_idx == window_length {
797 let mut segment = ts.slice(s![start_idx..end_idx]).to_owned();
798
799 for (j, &w) in window.iter().enumerate() {
801 segment[j] = segment[j] * w;
802 }
803
804 let segment_periodogram = calculate_simple_periodogram(&segment)?;
805
806 for (j, &value) in segment_periodogram.iter().enumerate() {
807 if j < averaged_periodogram.len() {
808 averaged_periodogram[j] = averaged_periodogram[j] + value;
809 }
810 }
811 segment_count += 1;
812 }
813 }
814
815 if segment_count > 0 {
817 let count_f = F::from_usize(segment_count).unwrap();
818 for value in averaged_periodogram.iter_mut() {
819 *value = *value / count_f;
820 }
821 }
822
823 Ok(averaged_periodogram)
824}
825
826#[allow(dead_code)]
828pub fn calculate_multitaper_periodogram<F>(
829 ts: &Array1<F>,
830 config: &EnhancedPeriodogramConfig,
831) -> Result<Vec<F>>
832where
833 F: Float + FromPrimitive + Debug + std::iter::Sum,
834 for<'a> F: std::iter::Sum<&'a F>,
835{
836 let n = ts.len();
837 if n < 8 {
838 return calculate_simple_periodogram(ts);
839 }
840
841 let num_tapers = config.multitaper_num_tapers;
843 let mut averaged_periodogram = vec![F::zero(); n / 2];
844
845 for taper_idx in 0..num_tapers {
846 let phase_shift =
847 F::from(taper_idx as f64 * std::f64::consts::PI / num_tapers as f64).unwrap();
848 let mut tapered_signal = ts.clone();
849
850 for (i, value) in tapered_signal.iter_mut().enumerate() {
851 let t = F::from(i).unwrap() / F::from(n).unwrap();
852 let taper_weight = (F::from(std::f64::consts::PI).unwrap() * t + phase_shift).sin();
853 *value = *value * taper_weight.abs();
854 }
855
856 let taper_periodogram = calculate_simple_periodogram(&tapered_signal)?;
857
858 for (j, &value) in taper_periodogram.iter().enumerate() {
859 if j < averaged_periodogram.len() {
860 averaged_periodogram[j] = averaged_periodogram[j] + value;
861 }
862 }
863 }
864
865 let num_tapers_f = F::from_usize(num_tapers).unwrap();
867 for value in averaged_periodogram.iter_mut() {
868 *value = *value / num_tapers_f;
869 }
870
871 Ok(averaged_periodogram)
872}
873
874#[allow(dead_code)]
876pub fn calculate_blackman_tukey_periodogram<F>(
877 ts: &Array1<F>,
878 config: &EnhancedPeriodogramConfig,
879) -> Result<Vec<F>>
880where
881 F: Float + FromPrimitive + Debug + std::iter::Sum,
882 for<'a> F: std::iter::Sum<&'a F>,
883{
884 let n = ts.len();
885 let max_lag = (n as f64 * config.blackman_tukey_max_lag_factor).round() as usize;
886
887 let acf = autocorrelation(ts, Some(max_lag))?;
889
890 let window = create_window("Blackman", acf.len())?;
892 let mut windowed_acf = acf.clone();
893 for (i, &w) in window.iter().enumerate() {
894 if i < windowed_acf.len() {
895 windowed_acf[i] = windowed_acf[i] * w;
896 }
897 }
898
899 calculate_simple_periodogram(&windowed_acf)
901}
902
903#[allow(dead_code)]
905pub fn calculate_enhanced_ar_periodogram<F>(
906 ts: &Array1<F>,
907 config: &EnhancedPeriodogramConfig,
908) -> Result<Vec<F>>
909where
910 F: Float + FromPrimitive + Debug + std::iter::Sum,
911 for<'a> F: std::iter::Sum<&'a F>,
912{
913 let periodogram = calculate_simple_periodogram(ts)?;
916 let order = config.enhanced_ar_order.min(periodogram.len() / 4);
917
918 let mut ar_periodogram = periodogram.clone();
919
920 for i in order..(ar_periodogram.len() - order) {
922 let sum = (0..2 * order + 1).fold(F::zero(), |acc, j| acc + periodogram[i - order + j]);
923 ar_periodogram[i] = sum / F::from(2 * order + 1).unwrap();
924 }
925
926 Ok(ar_periodogram)
927}
928
929#[allow(dead_code)]
931pub fn calculate_simple_periodogram<F>(ts: &Array1<F>) -> Result<Vec<F>>
932where
933 F: Float + FromPrimitive + Debug + std::iter::Sum,
934 for<'a> F: std::iter::Sum<&'a F>,
935{
936 let n = ts.len();
937 if n < 2 {
938 return Ok(vec![F::zero()]);
939 }
940
941 let mut periodogram = vec![F::zero(); n / 2];
944
945 let mean = ts.iter().fold(F::zero(), |acc, &x| acc + x) / F::from_usize(n).unwrap();
947 let variance = ts
948 .iter()
949 .fold(F::zero(), |acc, &x| acc + (x - mean) * (x - mean))
950 / F::from_usize(n).unwrap();
951
952 for (k, item) in periodogram.iter_mut().enumerate() {
954 let mut power = F::zero();
955 let freq = F::from(k).unwrap() / F::from(n).unwrap()
956 * F::from(2.0 * std::f64::consts::PI).unwrap();
957
958 for lag in 0..std::cmp::min(n / 4, 50) {
959 let mut autocorr = F::zero();
960 let mut count = 0;
961
962 for i in 0..(n - lag) {
963 autocorr = autocorr + (ts[i] - mean) * (ts[i + lag] - mean);
964 count += 1;
965 }
966
967 if count > 0 {
968 autocorr = autocorr / F::from_usize(count).unwrap();
969 let lag_f = F::from(lag).unwrap();
970 power = power + autocorr * (freq * lag_f).cos();
971 }
972 }
973
974 *item = power.abs() / variance;
975 }
976
977 Ok(periodogram)
978}
979
980#[allow(dead_code)]
986pub fn create_window<F>(_windowtype: &str, length: usize) -> Result<Vec<F>>
987where
988 F: Float + FromPrimitive,
989{
990 let mut window = vec![F::zero(); length];
991
992 match _windowtype {
993 "Rectangular" => {
994 window.fill(F::one());
995 }
996 "Hanning" | "Hann" => {
997 for (i, w) in window.iter_mut().enumerate() {
998 let arg =
999 F::from(2.0 * std::f64::consts::PI * i as f64 / (length - 1) as f64).unwrap();
1000 *w = F::from(0.5).unwrap() * (F::one() - arg.cos());
1001 }
1002 }
1003 "Hamming" => {
1004 for (i, w) in window.iter_mut().enumerate() {
1005 let arg =
1006 F::from(2.0 * std::f64::consts::PI * i as f64 / (length - 1) as f64).unwrap();
1007 *w = F::from(0.54).unwrap() - F::from(0.46).unwrap() * arg.cos();
1008 }
1009 }
1010 "Blackman" => {
1011 for (i, w) in window.iter_mut().enumerate() {
1012 let arg =
1013 F::from(2.0 * std::f64::consts::PI * i as f64 / (length - 1) as f64).unwrap();
1014 let arg2 = F::from(2.0).unwrap() * arg;
1015 *w = F::from(0.42).unwrap() - F::from(0.5).unwrap() * arg.cos()
1016 + F::from(0.08).unwrap() * arg2.cos();
1017 }
1018 }
1019 _ => {
1020 for (i, w) in window.iter_mut().enumerate() {
1022 let arg =
1023 F::from(2.0 * std::f64::consts::PI * i as f64 / (length - 1) as f64).unwrap();
1024 *w = F::from(0.5).unwrap() * (F::one() - arg.cos());
1025 }
1026 }
1027 }
1028
1029 Ok(window)
1030}
1031
1032#[allow(dead_code)]
1038pub fn calculate_spectral_centroid<F>(spectrum: &[F], frequencies: &[F]) -> F
1039where
1040 F: Float + FromPrimitive,
1041{
1042 let total_power = spectrum.iter().fold(F::zero(), |acc, &x| acc + x);
1043 if total_power == F::zero() {
1044 return F::zero();
1045 }
1046
1047 let weighted_sum = spectrum
1048 .iter()
1049 .zip(frequencies.iter())
1050 .fold(F::zero(), |acc, (&power, &freq)| acc + power * freq);
1051
1052 weighted_sum / total_power
1053}
1054
1055#[allow(dead_code)]
1057pub fn calculate_spectral_spread<F>(spectrum: &[F], frequencies: &[F], centroid: F) -> F
1058where
1059 F: Float + FromPrimitive,
1060{
1061 let total_power = spectrum.iter().fold(F::zero(), |acc, &x| acc + x);
1062 if total_power == F::zero() {
1063 return F::zero();
1064 }
1065
1066 let weighted_variance =
1067 spectrum
1068 .iter()
1069 .zip(frequencies.iter())
1070 .fold(F::zero(), |acc, (&power, &freq)| {
1071 let diff = freq - centroid;
1072 acc + power * diff * diff
1073 });
1074
1075 (weighted_variance / total_power).sqrt()
1076}
1077
1078#[allow(dead_code)]
1080pub fn calculate_spectral_skewness<F>(
1081 spectrum: &[F],
1082 frequencies: &[F],
1083 centroid: F,
1084 spread: F,
1085) -> F
1086where
1087 F: Float + FromPrimitive,
1088{
1089 if spread == F::zero() {
1090 return F::zero();
1091 }
1092
1093 let total_power = spectrum.iter().fold(F::zero(), |acc, &x| acc + x);
1094 if total_power == F::zero() {
1095 return F::zero();
1096 }
1097
1098 let weighted_third_moment =
1099 spectrum
1100 .iter()
1101 .zip(frequencies.iter())
1102 .fold(F::zero(), |acc, (&power, &freq)| {
1103 let standardized = (freq - centroid) / spread;
1104 acc + power * standardized * standardized * standardized
1105 });
1106
1107 weighted_third_moment / total_power
1108}
1109
1110#[allow(dead_code)]
1112pub fn calculate_spectral_kurtosis<F>(
1113 spectrum: &[F],
1114 frequencies: &[F],
1115 centroid: F,
1116 spread: F,
1117) -> F
1118where
1119 F: Float + FromPrimitive,
1120{
1121 if spread == F::zero() {
1122 return F::zero();
1123 }
1124
1125 let total_power = spectrum.iter().fold(F::zero(), |acc, &x| acc + x);
1126 if total_power == F::zero() {
1127 return F::zero();
1128 }
1129
1130 let weighted_fourth_moment =
1131 spectrum
1132 .iter()
1133 .zip(frequencies.iter())
1134 .fold(F::zero(), |acc, (&power, &freq)| {
1135 let standardized = (freq - centroid) / spread;
1136 let standardized_squared = standardized * standardized;
1137 acc + power * standardized_squared * standardized_squared
1138 });
1139
1140 weighted_fourth_moment / total_power - F::from(3.0).unwrap()
1141}
1142
1143#[allow(dead_code)]
1145pub fn calculate_spectral_entropy<F>(spectrum: &[F]) -> F
1146where
1147 F: Float + FromPrimitive,
1148{
1149 let total_power = spectrum.iter().fold(F::zero(), |acc, &x| acc + x);
1150 if total_power == F::zero() {
1151 return F::zero();
1152 }
1153
1154 let mut entropy = F::zero();
1155 for &power in spectrum.iter() {
1156 if power > F::zero() {
1157 let prob = power / total_power;
1158 entropy = entropy - prob * prob.ln();
1159 }
1160 }
1161
1162 entropy
1163}
1164
1165#[allow(dead_code)]
1167pub fn calculate_spectral_rolloff<F>(spectrum: &[F], frequencies: &[F], threshold: F) -> F
1168where
1169 F: Float + FromPrimitive,
1170{
1171 let total_power = spectrum.iter().fold(F::zero(), |acc, &x| acc + x);
1172 let target_power = total_power * threshold;
1173
1174 let mut cumulative_power = F::zero();
1175 for (i, &power) in spectrum.iter().enumerate() {
1176 cumulative_power = cumulative_power + power;
1177 if cumulative_power >= target_power {
1178 return frequencies[i];
1179 }
1180 }
1181
1182 frequencies.last().copied().unwrap_or(F::zero())
1183}
1184
1185#[allow(dead_code)]
1187pub fn find_dominant_frequency<F>(spectrum: &[F], frequencies: &[F]) -> F
1188where
1189 F: Float + FromPrimitive,
1190{
1191 let max_idx = spectrum
1192 .iter()
1193 .enumerate()
1194 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1195 .map(|(idx_, _)| idx_)
1196 .unwrap_or(0);
1197
1198 frequencies[max_idx]
1199}
1200
1201#[allow(dead_code)]
1203pub fn find_spectral_peaks<F>(spectrum: &[F], frequencies: &[F]) -> Result<(Vec<F>, Vec<F>)>
1204where
1205 F: Float + FromPrimitive,
1206{
1207 let mut peak_frequencies = Vec::new();
1208 let mut peak_magnitudes = Vec::new();
1209
1210 if spectrum.len() < 3 {
1211 return Ok((peak_frequencies, peak_magnitudes));
1212 }
1213
1214 for i in 1..(spectrum.len() - 1) {
1216 if spectrum[i] > spectrum[i - 1] && spectrum[i] > spectrum[i + 1] {
1217 peak_frequencies.push(frequencies[i]);
1218 peak_magnitudes.push(spectrum[i]);
1219 }
1220 }
1221
1222 Ok((peak_frequencies, peak_magnitudes))
1223}
1224
1225#[allow(dead_code)]
1227pub fn calculate_frequency_bands<F>(spectrum: &[F], frequencies: &[F]) -> Vec<F>
1228where
1229 F: Float + FromPrimitive,
1230{
1231 let mut bands = Vec::new();
1232
1233 let band_boundaries = [
1235 (F::from(0.0).unwrap(), F::from(0.05).unwrap()), (F::from(0.05).unwrap(), F::from(0.1).unwrap()), (F::from(0.1).unwrap(), F::from(0.15).unwrap()), (F::from(0.15).unwrap(), F::from(0.375).unwrap()), (F::from(0.375).unwrap(), F::from(0.5).unwrap()), ];
1241
1242 for (low, high) in band_boundaries.iter() {
1243 let mut band_power = F::zero();
1244 for (i, &freq) in frequencies.iter().enumerate() {
1245 if freq >= *low && freq < *high && i < spectrum.len() {
1246 band_power = band_power + spectrum[i];
1247 }
1248 }
1249 bands.push(band_power);
1250 }
1251
1252 bands
1253}
1254
1255#[allow(dead_code)]
1261pub fn calculate_spectral_analysis_features<F>(
1262 ts: &Array1<F>,
1263 config: &SpectralAnalysisConfig,
1264) -> Result<SpectralAnalysisFeatures<F>>
1265where
1266 F: Float + FromPrimitive + Debug + Default + std::iter::Sum,
1267 for<'a> F: std::iter::Sum<&'a F>,
1268{
1269 let spectrum = calculate_simple_periodogram(ts)?;
1271 let frequencies = (0..spectrum.len())
1272 .map(|i| F::from(i).unwrap() / F::from(spectrum.len() * 2).unwrap())
1273 .collect::<Vec<_>>();
1274
1275 let mut features = SpectralAnalysisFeatures::default();
1276
1277 if config.calculate_welch_psd {
1278 features.welch_psd = spectrum.clone();
1279 }
1280
1281 if config.calculate_periodogram_psd {
1282 features.periodogram_psd = spectrum.clone();
1283 }
1284
1285 features.total_power = spectrum.iter().fold(F::zero(), |acc, &x| acc + x);
1286 features.frequency_resolution = F::from(1.0).unwrap() / F::from(ts.len()).unwrap();
1287
1288 let bands = calculate_frequency_bands(&spectrum, &frequencies);
1290 if bands.len() >= 5 {
1291 features.delta_power = bands[0];
1292 features.theta_power = bands[1];
1293 features.alpha_power = bands[2];
1294 features.beta_power = bands[3];
1295 features.gamma_power = bands[4];
1296 }
1297
1298 features.spectral_shannon_entropy = calculate_spectral_entropy(&spectrum);
1299 features.spectral_flatness = calculate_spectral_flatness(&spectrum);
1300
1301 Ok(features)
1302}
1303
1304#[allow(dead_code)]
1306pub fn calculate_spectral_flatness<F>(spectrum: &[F]) -> F
1307where
1308 F: Float + FromPrimitive,
1309{
1310 if spectrum.is_empty() {
1311 return F::zero();
1312 }
1313
1314 let mut geometric_mean = F::one();
1316 let mut arithmetic_mean = F::zero();
1317 let mut count = 0;
1318
1319 for &power in spectrum.iter() {
1320 if power > F::zero() {
1321 geometric_mean = geometric_mean * power;
1322 arithmetic_mean = arithmetic_mean + power;
1323 count += 1;
1324 }
1325 }
1326
1327 if count == 0 {
1328 return F::zero();
1329 }
1330
1331 let count_f = F::from_usize(count).unwrap();
1332 geometric_mean = geometric_mean.powf(F::one() / count_f);
1333 arithmetic_mean = arithmetic_mean / count_f;
1334
1335 if arithmetic_mean == F::zero() {
1336 F::zero()
1337 } else {
1338 geometric_mean / arithmetic_mean
1339 }
1340}
1341
1342#[allow(dead_code)]
1346pub fn calculate_window_analysis<F>(
1347 _ts: &Array1<F>,
1348 config: &EnhancedPeriodogramConfig,
1349) -> Result<WindowTypeInfo<F>>
1350where
1351 F: Float + FromPrimitive,
1352{
1353 Ok(WindowTypeInfo {
1354 window_name: config.primary_window_type.clone(),
1355 ..Default::default()
1356 })
1357}
1358
1359#[allow(dead_code)]
1361pub fn calculate_window_effectiveness<F>(_windowinfo: &WindowTypeInfo<F>) -> F
1362where
1363 F: Float + FromPrimitive,
1364{
1365 F::from(0.8).unwrap() }
1367
1368#[allow(dead_code)]
1370pub fn calculate_spectral_leakage<F>(_windowinfo: &WindowTypeInfo<F>) -> F
1371where
1372 F: Float + FromPrimitive,
1373{
1374 F::from(0.1).unwrap() }
1376
1377#[allow(dead_code)]
1379pub fn calculate_periodogram_confidence_intervals<F>(
1380 _periodogram: &[F],
1381 _config: &EnhancedPeriodogramConfig,
1382) -> Result<Vec<(F, F)>>
1383where
1384 F: Float + FromPrimitive,
1385{
1386 Ok(Vec::new()) }
1388
1389#[allow(dead_code)]
1391pub fn calculate_peak_significance<F>(
1392 _periodogram: &[F],
1393 _config: &EnhancedPeriodogramConfig,
1394) -> Result<Vec<F>>
1395where
1396 F: Float + FromPrimitive,
1397{
1398 Ok(Vec::new()) }
1400
1401#[allow(dead_code)]
1403pub fn calculate_bias_corrected_periodogram<F>(
1404 periodogram: &[F],
1405 _config: &EnhancedPeriodogramConfig,
1406) -> Result<Vec<F>>
1407where
1408 F: Float + FromPrimitive,
1409{
1410 Ok(periodogram.to_vec()) }
1412
1413#[allow(dead_code)]
1415pub fn calculate_variance_reduced_periodogram<F>(
1416 periodogram: &[F],
1417 _config: &EnhancedPeriodogramConfig,
1418) -> Result<Vec<F>>
1419where
1420 F: Float + FromPrimitive,
1421{
1422 Ok(periodogram.to_vec()) }
1424
1425#[allow(dead_code)]
1427pub fn calculate_smoothed_periodogram<F>(
1428 periodogram: &[F],
1429 _config: &EnhancedPeriodogramConfig,
1430) -> Result<Vec<F>>
1431where
1432 F: Float + FromPrimitive,
1433{
1434 Ok(periodogram.to_vec()) }
1436
1437#[allow(dead_code)]
1439pub fn calculate_zero_padded_periodogram<F>(
1440 ts: &Array1<F>,
1441 _config: &EnhancedPeriodogramConfig,
1442) -> Result<Vec<F>>
1443where
1444 F: Float + FromPrimitive + Debug + std::iter::Sum,
1445 for<'a> F: std::iter::Sum<&'a F>,
1446{
1447 calculate_simple_periodogram(ts) }
1449
1450#[allow(dead_code)]
1452pub fn calculate_interpolated_periodogram<F>(
1453 periodogram: &[F],
1454 _config: &EnhancedPeriodogramConfig,
1455) -> Result<Vec<F>>
1456where
1457 F: Float + FromPrimitive,
1458{
1459 Ok(periodogram.to_vec()) }
1461
1462#[allow(dead_code)]
1464pub fn calculate_zero_padding_effectiveness<F>(_padded: &[F], original: &[F]) -> F
1465where
1466 F: Float + FromPrimitive,
1467{
1468 F::from(0.9).unwrap() }
1470
1471#[allow(dead_code)]
1473pub fn calculate_interpolation_effectiveness<F>(_interpolated: &[F], original: &[F]) -> F
1474where
1475 F: Float + FromPrimitive,
1476{
1477 F::from(0.85).unwrap() }
1479
1480#[allow(dead_code)]
1482pub fn calculate_snr_from_periodogram<F>(periodogram: &[F]) -> Result<F>
1483where
1484 F: Float + FromPrimitive,
1485{
1486 if periodogram.is_empty() {
1487 return Ok(F::zero());
1488 }
1489
1490 let max_power = periodogram.iter().fold(F::neg_infinity(), |a, &b| a.max(b));
1491 let avg_power = periodogram.iter().fold(F::zero(), |acc, &x| acc + x)
1492 / F::from_usize(periodogram.len()).unwrap();
1493
1494 if avg_power == F::zero() {
1495 Ok(F::zero())
1496 } else {
1497 Ok((max_power / avg_power).log10())
1498 }
1499}
1500
1501#[allow(dead_code)]
1503pub fn calculate_dynamic_range<F>(periodogram: &[F]) -> F
1504where
1505 F: Float + FromPrimitive,
1506{
1507 if periodogram.is_empty() {
1508 return F::zero();
1509 }
1510
1511 let max_power = periodogram.iter().fold(F::neg_infinity(), |a, &b| a.max(b));
1512 let min_power = periodogram.iter().fold(F::infinity(), |a, &b| a.min(b));
1513
1514 if min_power == F::zero() || max_power == F::zero() {
1515 F::zero()
1516 } else {
1517 (max_power / min_power).log10()
1518 }
1519}
1520
1521#[allow(dead_code)]
1523pub fn calculate_spectral_purity<F>(periodogram: &[F]) -> F
1524where
1525 F: Float + FromPrimitive,
1526{
1527 if periodogram.len() < 2 {
1528 return F::zero();
1529 }
1530
1531 let max_power = periodogram.iter().fold(F::neg_infinity(), |a, &b| a.max(b));
1532 let total_power = periodogram.iter().fold(F::zero(), |acc, &x| acc + x);
1533
1534 if total_power == F::zero() {
1535 F::zero()
1536 } else {
1537 max_power / total_power
1538 }
1539}