1use crate::{Float, SklResult, SklearsError};
11use scirs2_core::ndarray::{s, Array1, ArrayView1};
12
13#[derive(Debug, Clone, Copy)]
15pub enum TimeSeriesWaveletType {
16 Haar,
18 Daubechies4,
20 Daubechies8,
22 Biorthogonal,
24 Coiflets,
26}
27
28#[derive(Debug, Clone, Copy)]
30pub enum WaveletFeatureType {
31 Mean,
33 Variance,
35 Energy,
37 Entropy,
39 Skewness,
41 Kurtosis,
43 MaxCoeff,
45 MinCoeff,
47}
48
49struct WaveletDecomposition {
51 approximation: Array1<Float>,
52 details: Vec<Array1<Float>>,
53}
54
55pub struct TimeSeriesWaveletExtractor {
61 wavelet_type: TimeSeriesWaveletType,
62 num_levels: usize,
63 feature_types: Vec<WaveletFeatureType>,
64 include_scaleogram: bool,
65 include_energy_ratios: bool,
66}
67
68impl TimeSeriesWaveletExtractor {
69 pub fn new() -> Self {
71 Self {
72 wavelet_type: TimeSeriesWaveletType::Haar,
73 num_levels: 4,
74 feature_types: vec![
75 WaveletFeatureType::Mean,
76 WaveletFeatureType::Variance,
77 WaveletFeatureType::Energy,
78 WaveletFeatureType::Entropy,
79 ],
80 include_scaleogram: false,
81 include_energy_ratios: true,
82 }
83 }
84
85 pub fn wavelet_type(mut self, wavelet_type: TimeSeriesWaveletType) -> Self {
87 self.wavelet_type = wavelet_type;
88 self
89 }
90
91 pub fn num_levels(mut self, levels: usize) -> Self {
93 self.num_levels = levels;
94 self
95 }
96
97 pub fn feature_types(mut self, feature_types: Vec<WaveletFeatureType>) -> Self {
99 self.feature_types = feature_types;
100 self
101 }
102
103 pub fn include_scaleogram(mut self, include: bool) -> Self {
105 self.include_scaleogram = include;
106 self
107 }
108
109 pub fn include_energy_ratios(mut self, include: bool) -> Self {
111 self.include_energy_ratios = include;
112 self
113 }
114
115 pub fn extract_features(&self, ts: &ArrayView1<Float>) -> SklResult<Array1<Float>> {
117 if ts.len() < 4 {
118 return Err(SklearsError::InvalidInput(
119 "Time series too short for wavelet analysis".to_string(),
120 ));
121 }
122
123 let padded_ts = self.pad_to_power_of_2(ts)?;
125
126 let decomposition = self.wavelet_decompose(&padded_ts)?;
128
129 let mut features = Vec::new();
130
131 for level in 0..self.num_levels {
133 if level < decomposition.details.len() {
134 let detail_features = self.extract_level_features(&decomposition.details[level])?;
135 features.extend(detail_features);
136 }
137 }
138
139 let approx_features = self.extract_level_features(&decomposition.approximation)?;
141 features.extend(approx_features);
142
143 if self.include_energy_ratios {
145 let energy_ratios = self.calculate_energy_ratios(&decomposition)?;
146 features.extend(energy_ratios);
147 }
148
149 if self.include_scaleogram {
151 let scaleogram_features = self.extract_scaleogram_features(&padded_ts)?;
152 features.extend(scaleogram_features);
153 }
154
155 Ok(Array1::from_vec(features))
156 }
157
158 fn pad_to_power_of_2(&self, ts: &ArrayView1<Float>) -> SklResult<Array1<Float>> {
160 let len = ts.len();
161 let next_power = (len as Float).log2().ceil() as usize;
162 let padded_len = 2_usize.pow(next_power as u32);
163
164 let mut padded = Array1::zeros(padded_len);
165 padded.slice_mut(s![..len]).assign(ts);
166
167 for i in len..padded_len {
169 padded[i] = ts[len - 1]; }
171
172 Ok(padded)
173 }
174
175 fn wavelet_decompose(&self, signal: &Array1<Float>) -> SklResult<WaveletDecomposition> {
177 let (low_pass, high_pass) = self.get_wavelet_filters()?;
178
179 let mut approximation = signal.clone();
180 let mut details = Vec::new();
181
182 for _ in 0..self.num_levels {
183 if approximation.len() < 2 {
184 break;
185 }
186
187 let (approx, detail) = self.single_level_dwt(&approximation, &low_pass, &high_pass)?;
188 details.push(detail);
189 approximation = approx;
190 }
191
192 Ok(WaveletDecomposition {
193 approximation,
194 details,
195 })
196 }
197
198 fn get_wavelet_filters(&self) -> SklResult<(Vec<Float>, Vec<Float>)> {
200 match self.wavelet_type {
201 TimeSeriesWaveletType::Haar => {
202 let low_pass = vec![
203 std::f64::consts::FRAC_1_SQRT_2,
204 std::f64::consts::FRAC_1_SQRT_2,
205 ];
206 let high_pass = vec![
207 -std::f64::consts::FRAC_1_SQRT_2,
208 std::f64::consts::FRAC_1_SQRT_2,
209 ];
210 Ok((low_pass, high_pass))
211 }
212 TimeSeriesWaveletType::Daubechies4 => {
213 let low_pass = vec![
214 0.48296291314469025,
215 0.8365163037378079,
216 0.2241438680420134,
217 -0.12940952255092145,
218 ];
219 let high_pass = vec![
220 -0.12940952255092145,
221 -0.2241438680420134,
222 0.8365163037378079,
223 -0.48296291314469025,
224 ];
225 Ok((low_pass, high_pass))
226 }
227 TimeSeriesWaveletType::Daubechies8 => {
228 let low_pass = vec![
229 0.23037781330885523,
230 0.7148465705525415,
231 0.6308807679295904,
232 -0.02798376941698385,
233 -0.18703481171888114,
234 0.030841381835986965,
235 0.032883011666982945,
236 -0.010597401784997278,
237 ];
238 let high_pass = vec![
239 -0.010597401784997278,
240 -0.032883011666982945,
241 0.030841381835986965,
242 0.18703481171888114,
243 -0.02798376941698385,
244 -0.6308807679295904,
245 0.7148465705525415,
246 -0.23037781330885523,
247 ];
248 Ok((low_pass, high_pass))
249 }
250 TimeSeriesWaveletType::Biorthogonal => {
251 let low_pass = vec![
253 -0.1767766952966369,
254 0.3535533905932738,
255 1.0606601717798214,
256 0.3535533905932738,
257 -0.1767766952966369,
258 ];
259 let high_pass = vec![0.0, -0.5, 1.0, -0.5, 0.0];
260 Ok((low_pass, high_pass))
261 }
262 TimeSeriesWaveletType::Coiflets => {
263 let low_pass = vec![
265 -0.051429864679368207,
266 0.23389032745436126,
267 0.6054328720162073,
268 0.6054328720162073,
269 0.23389032745436126,
270 -0.051429864679368207,
271 ];
272 let high_pass = vec![
273 -0.051429864679368207,
274 -0.23389032745436126,
275 0.6054328720162073,
276 -0.6054328720162073,
277 0.23389032745436126,
278 0.051429864679368207,
279 ];
280 Ok((low_pass, high_pass))
281 }
282 }
283 }
284
285 fn single_level_dwt(
287 &self,
288 signal: &Array1<Float>,
289 low_pass: &[Float],
290 high_pass: &[Float],
291 ) -> SklResult<(Array1<Float>, Array1<Float>)> {
292 let n = signal.len();
293 let filter_len = low_pass.len();
294
295 let mut approx = Vec::new();
297 let mut detail = Vec::new();
298
299 for i in (0..n).step_by(2) {
300 let mut low_sum = 0.0;
301 let mut high_sum = 0.0;
302
303 for j in 0..filter_len {
304 let idx = (i + j) % n; low_sum += signal[idx] * low_pass[j];
306 high_sum += signal[idx] * high_pass[j];
307 }
308
309 approx.push(low_sum);
310 detail.push(high_sum);
311 }
312
313 Ok((Array1::from_vec(approx), Array1::from_vec(detail)))
314 }
315
316 fn extract_level_features(&self, coeffs: &Array1<Float>) -> SklResult<Vec<Float>> {
318 let mut features = Vec::new();
319
320 for &feature_type in &self.feature_types {
321 let feature_value = match feature_type {
322 WaveletFeatureType::Mean => coeffs.mean().unwrap_or(0.0),
323 WaveletFeatureType::Variance => coeffs.var(0.0),
324 WaveletFeatureType::Energy => coeffs.iter().map(|x| x * x).sum::<Float>(),
325 WaveletFeatureType::Entropy => self.calculate_wavelet_entropy(coeffs)?,
326 WaveletFeatureType::Skewness => self.calculate_skewness(coeffs),
327 WaveletFeatureType::Kurtosis => self.calculate_kurtosis(coeffs),
328 WaveletFeatureType::MaxCoeff => {
329 coeffs.iter().cloned().fold(Float::NEG_INFINITY, Float::max)
330 }
331 WaveletFeatureType::MinCoeff => {
332 coeffs.iter().cloned().fold(Float::INFINITY, Float::min)
333 }
334 };
335
336 features.push(feature_value);
337 }
338
339 Ok(features)
340 }
341
342 fn calculate_wavelet_entropy(&self, coeffs: &Array1<Float>) -> SklResult<Float> {
344 let energy: Float = coeffs.iter().map(|x| x * x).sum();
345
346 if energy < 1e-10 {
347 return Ok(0.0);
348 }
349
350 let mut entropy = 0.0;
351 for &coeff in coeffs.iter() {
352 let prob = (coeff * coeff) / energy;
353 if prob > 1e-10 {
354 entropy -= prob * prob.log2();
355 }
356 }
357
358 Ok(entropy)
359 }
360
361 fn calculate_skewness(&self, coeffs: &Array1<Float>) -> Float {
363 let n = coeffs.len() as Float;
364 if n < 3.0 {
365 return 0.0;
366 }
367
368 let mean = coeffs.mean().unwrap_or(0.0);
369 let variance = coeffs.var(0.0);
370
371 if variance < 1e-10 {
372 return 0.0;
373 }
374
375 let std_dev = variance.sqrt();
376 let third_moment: Float = coeffs
377 .iter()
378 .map(|x| ((x - mean) / std_dev).powi(3))
379 .sum::<Float>()
380 / n;
381
382 third_moment
383 }
384
385 fn calculate_kurtosis(&self, coeffs: &Array1<Float>) -> Float {
387 let n = coeffs.len() as Float;
388 if n < 4.0 {
389 return 0.0;
390 }
391
392 let mean = coeffs.mean().unwrap_or(0.0);
393 let variance = coeffs.var(0.0);
394
395 if variance < 1e-10 {
396 return 0.0;
397 }
398
399 let std_dev = variance.sqrt();
400 let fourth_moment: Float = coeffs
401 .iter()
402 .map(|x| ((x - mean) / std_dev).powi(4))
403 .sum::<Float>()
404 / n;
405
406 fourth_moment - 3.0 }
408
409 fn calculate_energy_ratios(
411 &self,
412 decomposition: &WaveletDecomposition,
413 ) -> SklResult<Vec<Float>> {
414 let mut energies = Vec::new();
415
416 let approx_energy: Float = decomposition.approximation.iter().map(|x| x * x).sum();
418 energies.push(approx_energy);
419
420 for detail in &decomposition.details {
422 let detail_energy: Float = detail.iter().map(|x| x * x).sum();
423 energies.push(detail_energy);
424 }
425
426 let total_energy: Float = energies.iter().sum();
427
428 let mut ratios = Vec::new();
429 for energy in energies {
430 let ratio = if total_energy > 1e-10 {
431 energy / total_energy
432 } else {
433 0.0
434 };
435 ratios.push(ratio);
436 }
437
438 Ok(ratios)
439 }
440
441 fn extract_scaleogram_features(&self, signal: &Array1<Float>) -> SklResult<Vec<Float>> {
443 let mut features = Vec::new();
444
445 let scales = vec![1.0, 2.0, 4.0, 8.0, 16.0];
447
448 for scale in scales {
449 let scaled_coeffs = self.continuous_wavelet_transform(signal, scale)?;
450
451 let mean = scaled_coeffs.mean().unwrap_or(0.0);
453 let variance = scaled_coeffs.var(0.0);
454 let max_val = scaled_coeffs
455 .iter()
456 .cloned()
457 .fold(Float::NEG_INFINITY, Float::max);
458
459 features.extend_from_slice(&[mean, variance, max_val]);
460 }
461
462 Ok(features)
463 }
464
465 fn continuous_wavelet_transform(
467 &self,
468 signal: &Array1<Float>,
469 scale: Float,
470 ) -> SklResult<Array1<Float>> {
471 let n = signal.len();
472 let mut coeffs = Array1::zeros(n);
473
474 for i in 0..n {
476 let mut sum = 0.0;
477 for j in 0..n {
478 let t = (j as Float - i as Float) / scale;
479 let wavelet_val = (t * t / 2.0).exp() * (5.0 * t).cos(); sum += signal[j] * wavelet_val;
481 }
482 coeffs[i] = sum / scale.sqrt();
483 }
484
485 Ok(coeffs)
486 }
487}
488
489impl Default for TimeSeriesWaveletExtractor {
490 fn default() -> Self {
491 Self::new()
492 }
493}
494
495pub struct TimeFrequencyExtractor {
500 window_size: usize,
501 hop_size: usize,
502 num_frequency_bins: usize,
503 include_spectral_features: bool,
504 include_wavelet_features: bool,
505 include_instantaneous_features: bool,
506}
507
508impl TimeFrequencyExtractor {
509 pub fn new() -> Self {
511 Self {
512 window_size: 256,
513 hop_size: 128,
514 num_frequency_bins: 64,
515 include_spectral_features: true,
516 include_wavelet_features: true,
517 include_instantaneous_features: false,
518 }
519 }
520
521 pub fn window_size(mut self, size: usize) -> Self {
523 self.window_size = size;
524 self
525 }
526
527 pub fn hop_size(mut self, size: usize) -> Self {
529 self.hop_size = size;
530 self
531 }
532
533 pub fn num_frequency_bins(mut self, bins: usize) -> Self {
535 self.num_frequency_bins = bins;
536 self
537 }
538
539 pub fn include_spectral_features(mut self, include: bool) -> Self {
541 self.include_spectral_features = include;
542 self
543 }
544
545 pub fn include_wavelet_features(mut self, include: bool) -> Self {
547 self.include_wavelet_features = include;
548 self
549 }
550
551 pub fn include_instantaneous_features(mut self, include: bool) -> Self {
553 self.include_instantaneous_features = include;
554 self
555 }
556
557 pub fn extract_features(&self, ts: &ArrayView1<Float>) -> SklResult<Array1<Float>> {
559 if ts.len() < self.window_size {
560 return Err(SklearsError::InvalidInput(
561 "Time series too short for time-frequency analysis".to_string(),
562 ));
563 }
564
565 let mut features = Vec::new();
566
567 if self.include_spectral_features {
569 let spectral_features = self.extract_spectral_features(ts)?;
570 features.extend(spectral_features);
571 }
572
573 if self.include_wavelet_features {
575 let wavelet_extractor = TimeSeriesWaveletExtractor::new()
576 .wavelet_type(TimeSeriesWaveletType::Daubechies4)
577 .num_levels(3)
578 .include_scaleogram(true);
579
580 let wavelet_features = wavelet_extractor.extract_features(ts)?;
581 features.extend(wavelet_features.to_vec());
582 }
583
584 if self.include_instantaneous_features {
586 let instantaneous_features = self.extract_instantaneous_features(ts)?;
587 features.extend(instantaneous_features);
588 }
589
590 Ok(Array1::from_vec(features))
591 }
592
593 fn extract_spectral_features(&self, ts: &ArrayView1<Float>) -> SklResult<Vec<Float>> {
595 let num_windows = (ts.len() - self.window_size) / self.hop_size + 1;
596 let mut spectral_centroids = Vec::new();
597 let mut spectral_bandwidths = Vec::new();
598 let mut spectral_rolloffs = Vec::new();
599
600 for i in 0..num_windows {
601 let start = i * self.hop_size;
602 let end = start + self.window_size;
603
604 if end <= ts.len() {
605 let window = ts.slice(s![start..end]);
606 let windowed = self.apply_hanning_window(&window.to_owned())?;
607
608 let fft_result = self.compute_fft(&windowed)?;
610 let max_freq = fft_result.len() / 2; let mut weighted_sum = 0.0;
614 let mut total_power = 0.0;
615
616 for (i, &(real, imag)) in fft_result.iter().take(max_freq).enumerate() {
617 let power = real * real + imag * imag;
618 weighted_sum += i as Float * power;
619 total_power += power;
620 }
621
622 let centroid = if total_power > 1e-10 {
623 weighted_sum / total_power
624 } else {
625 0.0
626 };
627 spectral_centroids.push(centroid);
628
629 let mut bandwidth_sum = 0.0;
631 for (i, &(real, imag)) in fft_result.iter().take(max_freq).enumerate() {
632 let power = real * real + imag * imag;
633 bandwidth_sum += (i as Float - centroid).powi(2) * power;
634 }
635
636 let bandwidth = if total_power > 1e-10 {
637 (bandwidth_sum / total_power).sqrt()
638 } else {
639 0.0
640 };
641 spectral_bandwidths.push(bandwidth);
642
643 let target_energy = 0.85 * total_power;
645 let mut cumulative_energy = 0.0;
646 let mut rolloff = 0.0;
647
648 for (i, &(real, imag)) in fft_result.iter().take(max_freq).enumerate() {
649 let power = real * real + imag * imag;
650 cumulative_energy += power;
651 if cumulative_energy >= target_energy {
652 rolloff = i as Float;
653 break;
654 }
655 }
656 spectral_rolloffs.push(rolloff);
657 }
658 }
659
660 let mut features = Vec::new();
661
662 features.push(spectral_centroids.iter().sum::<Float>() / spectral_centroids.len() as Float);
664 features
665 .push(spectral_bandwidths.iter().sum::<Float>() / spectral_bandwidths.len() as Float);
666 features.push(spectral_rolloffs.iter().sum::<Float>() / spectral_rolloffs.len() as Float);
667
668 let centroid_mean = features[features.len() - 3];
670 let bandwidth_mean = features[features.len() - 2];
671 let rolloff_mean = features[features.len() - 1];
672
673 let centroid_var = spectral_centroids
674 .iter()
675 .map(|x| (x - centroid_mean).powi(2))
676 .sum::<Float>()
677 / spectral_centroids.len() as Float;
678 let bandwidth_var = spectral_bandwidths
679 .iter()
680 .map(|x| (x - bandwidth_mean).powi(2))
681 .sum::<Float>()
682 / spectral_bandwidths.len() as Float;
683 let rolloff_var = spectral_rolloffs
684 .iter()
685 .map(|x| (x - rolloff_mean).powi(2))
686 .sum::<Float>()
687 / spectral_rolloffs.len() as Float;
688
689 features.extend_from_slice(&[centroid_var, bandwidth_var, rolloff_var]);
690
691 Ok(features)
692 }
693
694 fn extract_instantaneous_features(&self, ts: &ArrayView1<Float>) -> SklResult<Vec<Float>> {
696 let analytic_signal = self.hilbert_transform(ts)?;
698
699 let mut instantaneous_freqs = Vec::new();
700 let mut instantaneous_phases = Vec::new();
701
702 for i in 1..analytic_signal.len() {
703 let (real_prev, imag_prev) = analytic_signal[i - 1];
704 let (real_curr, imag_curr) = analytic_signal[i];
705
706 let phase_prev = imag_prev.atan2(real_prev);
708 let phase_curr = imag_curr.atan2(real_curr);
709
710 let mut freq = phase_curr - phase_prev;
712
713 if freq > std::f64::consts::PI {
715 freq -= 2.0 * std::f64::consts::PI;
716 } else if freq < -std::f64::consts::PI {
717 freq += 2.0 * std::f64::consts::PI;
718 }
719
720 instantaneous_freqs.push(freq);
721 instantaneous_phases.push(phase_curr);
722 }
723
724 let mut features = Vec::new();
725
726 if !instantaneous_freqs.is_empty() {
728 let mean_freq =
729 instantaneous_freqs.iter().sum::<Float>() / instantaneous_freqs.len() as Float;
730 let var_freq = instantaneous_freqs
731 .iter()
732 .map(|x| (x - mean_freq).powi(2))
733 .sum::<Float>()
734 / instantaneous_freqs.len() as Float;
735
736 features.extend_from_slice(&[mean_freq, var_freq]);
737 }
738
739 if !instantaneous_phases.is_empty() {
741 let phase_variance = instantaneous_phases
742 .iter()
743 .map(|x| x.powi(2))
744 .sum::<Float>()
745 / instantaneous_phases.len() as Float;
746
747 features.push(phase_variance);
748 }
749
750 Ok(features)
751 }
752
753 fn apply_hanning_window(&self, signal: &Array1<Float>) -> SklResult<Array1<Float>> {
755 let n = signal.len();
756 let mut windowed = Array1::zeros(n);
757
758 for i in 0..n {
759 let window_val =
760 0.5 * (1.0 - (2.0 * std::f64::consts::PI * i as Float / (n - 1) as Float).cos());
761 windowed[i] = signal[i] * window_val;
762 }
763
764 Ok(windowed)
765 }
766
767 fn compute_fft(&self, signal: &Array1<Float>) -> SklResult<Vec<(Float, Float)>> {
769 let n = signal.len();
770 let mut result = vec![(0.0, 0.0); n];
771
772 #[allow(clippy::needless_range_loop)]
775 for k in 0..n {
776 let mut real_sum = 0.0;
777 let mut imag_sum = 0.0;
778
779 for j in 0..n {
780 let angle = -2.0 * std::f64::consts::PI * (k * j) as Float / n as Float;
781 real_sum += signal[j] * angle.cos();
782 imag_sum += signal[j] * angle.sin();
783 }
784
785 result[k] = (real_sum, imag_sum);
786 }
787
788 Ok(result)
789 }
790
791 fn hilbert_transform(&self, signal: &ArrayView1<Float>) -> SklResult<Vec<(Float, Float)>> {
793 let n = signal.len();
794 let mut result = Vec::with_capacity(n);
795
796 for i in 0..n {
798 let mut hilbert_val = 0.0;
799
800 for j in 0..n {
801 if i != j {
802 hilbert_val += signal[j] / (std::f64::consts::PI * (i as Float - j as Float));
803 }
804 }
805
806 result.push((signal[i], hilbert_val));
807 }
808
809 Ok(result)
810 }
811}
812
813impl Default for TimeFrequencyExtractor {
814 fn default() -> Self {
815 Self::new()
816 }
817}