1use scirs2_core::ndarray::{Array1, Array2, ArrayBase, Data, Ix1, Ix2};
7use scirs2_core::numeric::Complex;
8use scirs2_core::numeric::{Float, NumCast};
9use scirs2_fft::fft;
10
11use crate::error::{Result, TransformError};
12
13#[derive(Debug, Clone)]
18pub struct FourierFeatures {
19 n_components: usize,
21 include_phase: bool,
23 normalize: bool,
25 sampling_freq: Option<f64>,
27}
28
29impl FourierFeatures {
30 pub fn new(ncomponents: usize) -> Self {
35 FourierFeatures {
36 n_components: ncomponents,
37 include_phase: false,
38 normalize: true,
39 sampling_freq: None,
40 }
41 }
42
43 pub fn with_phase(mut self) -> Self {
45 self.include_phase = true;
46 self
47 }
48
49 pub fn with_sampling_freq(mut self, freq: f64) -> Self {
51 self.sampling_freq = Some(freq);
52 self
53 }
54
55 fn extract_features_1d<S>(&self, x: &ArrayBase<S, Ix1>) -> Result<Array1<f64>>
57 where
58 S: Data,
59 S::Elem: Float + NumCast,
60 {
61 let n = x.len();
62 if n == 0 {
63 return Err(TransformError::InvalidInput(
64 "Empty time series".to_string(),
65 ));
66 }
67
68 let real_data: Vec<f64> = x
70 .iter()
71 .map(|&val| NumCast::from(val).unwrap_or(0.0))
72 .collect();
73
74 let fft_result = fft(&real_data, None)?;
76
77 let n_freq = (n / 2).min(self.n_components);
79 let mut features = if self.include_phase {
80 Array1::zeros(n_freq * 2)
81 } else {
82 Array1::zeros(n_freq)
83 };
84
85 let norm_factor = if self.normalize { 1.0 / n as f64 } else { 1.0 };
86
87 for i in 0..n_freq {
88 let magnitude =
89 (fft_result[i].re * fft_result[i].re + fft_result[i].im * fft_result[i].im).sqrt()
90 * norm_factor;
91
92 features[i] = magnitude;
93
94 if self.include_phase && magnitude > 1e-10 {
95 let phase = fft_result[i].im.atan2(fft_result[i].re);
96 features[n_freq + i] = phase;
97 }
98 }
99
100 Ok(features)
101 }
102
103 pub fn transform<S>(&self, x: &ArrayBase<S, Ix2>) -> Result<Array2<f64>>
111 where
112 S: Data,
113 S::Elem: Float + NumCast,
114 {
115 let n_samples = x.shape()[0];
116 let n_features = if self.include_phase {
117 self.n_components * 2
118 } else {
119 self.n_components
120 };
121
122 let mut result = Array2::zeros((n_samples, n_features));
123
124 for i in 0..n_samples {
125 let features = self.extract_features_1d(&x.row(i))?;
126 let feat_len = features.len().min(n_features);
127 result
128 .slice_mut(scirs2_core::ndarray::s![i, ..feat_len])
129 .assign(&features.slice(scirs2_core::ndarray::s![..feat_len]));
130 }
131
132 Ok(result)
133 }
134}
135
136#[derive(Debug, Clone)]
141pub struct LagFeatures {
142 lags: Vec<usize>,
144 drop_na: bool,
146}
147
148impl LagFeatures {
149 pub fn new(lags: Vec<usize>) -> Self {
154 LagFeatures {
155 lags,
156 drop_na: true,
157 }
158 }
159
160 pub fn with_range(start: usize, end: usize) -> Self {
162 let lags = (start..=end).collect();
163 LagFeatures {
164 lags,
165 drop_na: true,
166 }
167 }
168
169 pub fn with_drop_na(mut self, dropna: bool) -> Self {
171 self.drop_na = dropna;
172 self
173 }
174
175 pub fn transform_1d<S>(&self, x: &ArrayBase<S, Ix1>) -> Result<Array2<f64>>
183 where
184 S: Data,
185 S::Elem: Float + NumCast,
186 {
187 let n = x.len();
188 let max_lag = *self.lags.iter().max().unwrap_or(&0);
189
190 if max_lag >= n {
191 return Err(TransformError::InvalidInput(format!(
192 "Maximum lag {max_lag} must be less than series length {n}"
193 )));
194 }
195
196 let start_idx = if self.drop_na { max_lag } else { 0 };
197 let n_samples = n - start_idx;
198 let n_features = self.lags.len() + 1; let mut result = Array2::zeros((n_samples, n_features));
201
202 for i in 0..n_samples {
204 result[[i, 0]] = NumCast::from(x[start_idx + i]).unwrap_or(0.0);
205 }
206
207 for (lag_idx, &lag) in self.lags.iter().enumerate() {
209 for i in 0..n_samples {
210 let idx = start_idx + i;
211 if idx >= lag {
212 result[[i, lag_idx + 1]] = NumCast::from(x[idx - lag]).unwrap_or(0.0);
213 } else if !self.drop_na {
214 result[[i, lag_idx + 1]] = f64::NAN;
215 }
216 }
217 }
218
219 Ok(result)
220 }
221
222 pub fn transform<S>(&self, x: &ArrayBase<S, Ix2>) -> Result<Vec<Array2<f64>>>
224 where
225 S: Data,
226 S::Elem: Float + NumCast,
227 {
228 let n_series = x.shape()[0];
229 let mut results = Vec::new();
230
231 for i in 0..n_series {
232 let series = x.row(i);
233 let lag_features = self.transform_1d(&series)?;
234 results.push(lag_features);
235 }
236
237 Ok(results)
238 }
239}
240
241#[derive(Debug, Clone)]
246pub struct WaveletFeatures {
247 wavelet: String,
249 level: usize,
251 include_approx: bool,
253}
254
255impl WaveletFeatures {
256 pub fn new(wavelet: &str, level: usize) -> Self {
262 WaveletFeatures {
263 wavelet: wavelet.to_string(),
264 level,
265 include_approx: true,
266 }
267 }
268
269 pub fn with_include_approx(mut self, include: bool) -> Self {
271 self.include_approx = include;
272 self
273 }
274
275 #[allow(dead_code)]
277 fn haar_transform(&self, x: &[f64]) -> (Vec<f64>, Vec<f64>) {
278 let n = x.len();
279 let mut approx = Vec::with_capacity(n / 2);
280 let mut detail = Vec::with_capacity(n / 2);
281
282 for i in (0..n).step_by(2) {
283 if i + 1 < n {
284 approx.push((x[i] + x[i + 1]) / 2.0_f64.sqrt());
285 detail.push((x[i] - x[i + 1]) / 2.0_f64.sqrt());
286 } else {
287 approx.push(x[i]);
289 }
290 }
291
292 (approx, detail)
293 }
294
295 fn get_wavelet_coeffs(&self) -> Result<(Vec<f64>, Vec<f64>)> {
297 match self.wavelet.as_str() {
298 "db1" | "haar" => {
299 let h = vec![1.0 / 2.0_f64.sqrt(), 1.0 / 2.0_f64.sqrt()];
301 let g = vec![1.0 / 2.0_f64.sqrt(), -1.0 / 2.0_f64.sqrt()];
302 Ok((h, g))
303 }
304 "db2" => {
305 let sqrt3 = 3.0_f64.sqrt();
307 let denom = 4.0 * 2.0_f64.sqrt();
308 let h = vec![
309 (1.0 + sqrt3) / denom,
310 (3.0 + sqrt3) / denom,
311 (3.0 - sqrt3) / denom,
312 (1.0 - sqrt3) / denom,
313 ];
314 let g = vec![h[3], -h[2], h[1], -h[0]];
315 Ok((h, g))
316 }
317 "db4" => {
318 let h = vec![
320 -0.010597401784997,
321 0.032883011666983,
322 0.030841381835987,
323 -0.187034811718881,
324 -0.027983769416984,
325 0.630880767929590,
326 0.714846570552542,
327 0.230377813308855,
328 ];
329 let mut g = Vec::with_capacity(h.len());
330 for (i, &coeff) in h.iter().enumerate() {
331 g.push(if i % 2 == 0 { coeff } else { -coeff });
332 }
333 g.reverse();
334 Ok((h, g))
335 }
336 "db8" => {
337 let h = vec![
339 -0.00011747678400228,
340 0.0006754494059985,
341 -0.0003917403729959,
342 -0.00487035299301066,
343 0.008746094047015655,
344 0.013981027917015516,
345 -0.04408825393106472,
346 -0.01736930100202211,
347 0.128747426620186,
348 0.00047248457399797254,
349 -0.2840155429624281,
350 -0.015829105256023893,
351 0.5853546836548691,
352 0.6756307362980128,
353 0.3182301045617746,
354 0.05441584224308161,
355 ];
356 let mut g = Vec::with_capacity(h.len());
357 for (i, &coeff) in h.iter().enumerate() {
358 g.push(if i % 2 == 0 { coeff } else { -coeff });
359 }
360 g.reverse();
361 Ok((h, g))
362 }
363 "bior2.2" => {
364 let h = vec![
366 0.0,
367 -0.17677669529663687,
368 0.35355339059327373,
369 1.0606601717798214,
370 0.35355339059327373,
371 -0.17677669529663687,
372 0.0,
373 ];
374 let g = vec![
375 0.0,
376 0.35355339059327373,
377 -std::f64::consts::FRAC_1_SQRT_2,
378 0.35355339059327373,
379 0.0,
380 ];
381 Ok((h, g))
382 }
383 "bior4.4" => {
384 let h = vec![
386 0.0,
387 0.03314563036811941,
388 -0.06629126073623884,
389 -0.17677669529663687,
390 0.4198446513295126,
391 0.9943689110435825,
392 0.4198446513295126,
393 -0.17677669529663687,
394 -0.06629126073623884,
395 0.03314563036811941,
396 0.0,
397 ];
398 let g = vec![
399 0.0,
400 -0.06453888262893856,
401 0.04068941760955867,
402 0.41809227322161724,
403 -0.7884856164056651,
404 0.41809227322161724,
405 0.04068941760955867,
406 -0.06453888262893856,
407 0.0,
408 ];
409 Ok((h, g))
410 }
411 _ => Err(TransformError::InvalidInput(format!(
412 "Unsupported wavelet type: {}",
413 self.wavelet
414 ))),
415 }
416 }
417
418 fn wavelet_transform(&self, x: &[f64]) -> Result<(Vec<f64>, Vec<f64>)> {
420 let (h, g) = self.get_wavelet_coeffs()?;
421
422 if x.len() < h.len() {
423 return Err(TransformError::InvalidInput(
424 "Input signal too short for selected wavelet".to_string(),
425 ));
426 }
427
428 let n = x.len();
429 let mut approx = Vec::with_capacity(n / 2);
430 let mut detail = Vec::with_capacity(n / 2);
431
432 for i in (0..n).step_by(2) {
434 let mut h_sum = 0.0;
435 let mut g_sum = 0.0;
436
437 for (j, (&h_coeff, &g_coeff)) in h.iter().zip(g.iter()).enumerate() {
438 let idx = (i + j) % n; h_sum += h_coeff * x[idx];
440 g_sum += g_coeff * x[idx];
441 }
442
443 approx.push(h_sum);
444 detail.push(g_sum);
445 }
446
447 Ok((approx, detail))
448 }
449
450 fn wavelet_decompose(&self, x: &[f64]) -> Result<Vec<Vec<f64>>> {
452 let mut coefficients = Vec::new();
453 let mut current = x.to_vec();
454
455 for _ in 0..self.level {
456 let (approx, detail) = self.wavelet_transform(¤t)?;
457 coefficients.push(detail);
458 current = approx;
459
460 if current.len() < 2 {
461 break;
462 }
463 }
464
465 if self.include_approx {
466 coefficients.push(current);
467 }
468
469 Ok(coefficients)
470 }
471
472 fn extract_features_1d<S>(&self, x: &ArrayBase<S, Ix1>) -> Result<Array1<f64>>
474 where
475 S: Data,
476 S::Elem: Float + NumCast,
477 {
478 let x_vec: Vec<f64> = x.iter().map(|&v| NumCast::from(v).unwrap_or(0.0)).collect();
479
480 let coefficients = self.wavelet_decompose(&x_vec)?;
481
482 let n_features: usize = coefficients.iter().map(|c| c.len()).sum();
484 let mut features = Array1::zeros(n_features);
485
486 let mut idx = 0;
487 for coeff_level in coefficients {
488 for &coeff in &coeff_level {
489 features[idx] = coeff;
490 idx += 1;
491 }
492 }
493
494 Ok(features)
495 }
496
497 pub fn transform<S>(&self, x: &ArrayBase<S, Ix2>) -> Result<Vec<Array1<f64>>>
505 where
506 S: Data,
507 S::Elem: Float + NumCast,
508 {
509 let n_samples = x.shape()[0];
510 let mut results = Vec::new();
511
512 for i in 0..n_samples {
513 let features = self.extract_features_1d(&x.row(i))?;
514 results.push(features);
515 }
516
517 Ok(results)
518 }
519}
520
521#[derive(Debug, Clone)]
526pub struct TimeSeriesFeatures {
527 use_fourier: bool,
529 use_lags: bool,
531 use_wavelets: bool,
533 fourier_config: Option<FourierFeatures>,
535 lag_config: Option<LagFeatures>,
537 wavelet_config: Option<WaveletFeatures>,
539}
540
541impl TimeSeriesFeatures {
542 pub fn new() -> Self {
544 TimeSeriesFeatures {
545 use_fourier: true,
546 use_lags: true,
547 use_wavelets: false,
548 fourier_config: Some(FourierFeatures::new(10)),
549 lag_config: Some(LagFeatures::with_range(1, 5)),
550 wavelet_config: None,
551 }
552 }
553
554 pub fn with_fourier(mut self, n_components: usize, includephase: bool) -> Self {
556 self.use_fourier = true;
557 let mut fourier = FourierFeatures::new(n_components);
558 if includephase {
559 fourier = fourier.with_phase();
560 }
561 self.fourier_config = Some(fourier);
562 self
563 }
564
565 pub fn with_lags(mut self, lags: Vec<usize>) -> Self {
567 self.use_lags = true;
568 self.lag_config = Some(LagFeatures::new(lags));
569 self
570 }
571
572 pub fn with_wavelets(mut self, wavelet: &str, level: usize) -> Self {
574 self.use_wavelets = true;
575 self.wavelet_config = Some(WaveletFeatures::new(wavelet, level));
576 self
577 }
578}
579
580impl Default for TimeSeriesFeatures {
581 fn default() -> Self {
582 Self::new()
583 }
584}
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589 use scirs2_core::ndarray::Array;
590
591 #[test]
592 fn test_fourier_features() {
593 let n = 100;
595 let mut signal = Vec::new();
596 for i in 0..n {
597 let t = i as f64 / n as f64 * 4.0 * std::f64::consts::PI;
598 signal.push((t).sin() + 0.5 * (2.0 * t).sin());
599 }
600 let x = Array::from_shape_vec((1, n), signal).unwrap();
601
602 let fourier = FourierFeatures::new(10);
603 let features = fourier.transform(&x).unwrap();
604
605 assert_eq!(features.shape(), &[1, 10]);
606
607 assert!(features[[0, 0]].abs() < 1e-10);
609
610 assert!(features[[0, 1]] > 0.1); assert!(features[[0, 2]] > 0.04); }
616
617 #[test]
618 fn test_lag_features() {
619 let x = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
620
621 let lag_extractor = LagFeatures::new(vec![1, 2]);
622 let features = lag_extractor.transform_1d(&x.view()).unwrap();
623
624 assert_eq!(features.shape(), &[4, 3]);
626
627 assert_eq!(features[[0, 0]], 3.0);
629 assert_eq!(features[[0, 1]], 2.0);
630 assert_eq!(features[[0, 2]], 1.0);
631 }
632
633 #[test]
634 fn test_wavelet_features() {
635 let x = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
636 let x_2d = x.clone().into_shape_with_order((1, 8)).unwrap();
637
638 let wavelet = WaveletFeatures::new("db1", 2);
639 let features = wavelet.transform(&x_2d).unwrap();
640
641 assert!(!features.is_empty());
642 assert!(features[0].len() > 0);
643 }
644}