1#[cfg(feature = "parallel")]
12#[allow(unused_imports)]
13use rayon::prelude::*;
14use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
15use std::collections::HashMap;
16use std::collections::VecDeque;
17
18use crate::kernels::Kernel;
19use crate::svc::SVC;
20use sklears_core::error::{Result, SklearsError};
21use sklears_core::traits::{Fit, Predict, Trained};
22
23#[derive(Debug, Clone)]
37pub struct DynamicTimeWarpingKernel {
38 pub bandwidth: Option<usize>,
40 pub gamma: f64,
42 pub distance_metric: DistanceMetric,
44 pub step_pattern: StepPattern,
46 pub normalize: bool,
48}
49
50#[derive(Debug, Clone)]
52pub enum DistanceMetric {
53 Euclidean,
55 Manhattan,
57 SquaredEuclidean,
59 Cosine,
61}
62
63#[derive(Debug, Clone)]
65pub enum StepPattern {
66 Symmetric,
68 Asymmetric,
70 TypeIVc,
72}
73
74impl Default for DynamicTimeWarpingKernel {
75 fn default() -> Self {
76 Self {
77 bandwidth: None,
78 gamma: 1.0,
79 distance_metric: DistanceMetric::Euclidean,
80 step_pattern: StepPattern::Symmetric,
81 normalize: true,
82 }
83 }
84}
85
86impl DynamicTimeWarpingKernel {
87 pub fn new(gamma: f64) -> Self {
89 Self {
90 gamma,
91 ..Default::default()
92 }
93 }
94
95 pub fn with_bandwidth(mut self, bandwidth: Option<usize>) -> Self {
97 self.bandwidth = bandwidth;
98 self
99 }
100
101 pub fn with_distance_metric(mut self, distance_metric: DistanceMetric) -> Self {
103 self.distance_metric = distance_metric;
104 self
105 }
106
107 pub fn with_step_pattern(mut self, step_pattern: StepPattern) -> Self {
109 self.step_pattern = step_pattern;
110 self
111 }
112
113 pub fn with_normalize(mut self, normalize: bool) -> Self {
115 self.normalize = normalize;
116 self
117 }
118
119 pub fn compute_dtw_distance(&self, seq1: &Array1<f64>, seq2: &Array1<f64>) -> f64 {
121 let n = seq1.len();
122 let m = seq2.len();
123
124 if n == 0 || m == 0 {
125 return f64::INFINITY;
126 }
127
128 let mut dtw = Array2::from_elem((n + 1, m + 1), f64::INFINITY);
130 dtw[[0, 0]] = 0.0;
131
132 for i in 1..=n {
134 let start_j = if let Some(band) = self.bandwidth {
135 ((i as f64 * m as f64 / n as f64) as usize)
136 .saturating_sub(band)
137 .max(1)
138 } else {
139 1
140 };
141
142 let end_j = if let Some(band) = self.bandwidth {
143 ((i as f64 * m as f64 / n as f64) as usize + band + 1).min(m + 1)
144 } else {
145 m + 1
146 };
147
148 for j in start_j..end_j {
149 let cost = self.point_distance(seq1[i - 1], seq2[j - 1]);
150
151 let step_cost = match self.step_pattern {
152 StepPattern::Symmetric => {
153 let diag = dtw[[i - 1, j - 1]];
155 let up = dtw[[i - 1, j]];
156 let left = dtw[[i, j - 1]];
157 diag.min(up).min(left)
158 }
159 StepPattern::Asymmetric => {
160 let diag = dtw[[i - 1, j - 1]];
162 let up = dtw[[i - 1, j]];
163 let left = dtw[[i, j - 1]] * 2.0; diag.min(up).min(left)
165 }
166 StepPattern::TypeIVc => {
167 let mut min_cost = f64::INFINITY;
169
170 if i >= 1 && j >= 1 {
171 min_cost = min_cost.min(dtw[[i - 1, j - 1]]);
172 }
173 if i >= 1 {
174 min_cost = min_cost.min(dtw[[i - 1, j]]);
175 }
176 if j >= 1 {
177 min_cost = min_cost.min(dtw[[i, j - 1]]);
178 }
179 if i >= 2 && j >= 1 {
180 min_cost = min_cost.min(dtw[[i - 2, j - 1]]);
181 }
182 if i >= 1 && j >= 2 {
183 min_cost = min_cost.min(dtw[[i - 1, j - 2]]);
184 }
185
186 min_cost
187 }
188 };
189
190 dtw[[i, j]] = cost + step_cost;
191 }
192 }
193
194 let distance = dtw[[n, m]];
195
196 if self.normalize {
197 distance / (n + m) as f64
198 } else {
199 distance
200 }
201 }
202
203 fn point_distance(&self, x: f64, y: f64) -> f64 {
205 match self.distance_metric {
206 DistanceMetric::Euclidean => (x - y).abs(),
207 DistanceMetric::Manhattan => (x - y).abs(),
208 DistanceMetric::SquaredEuclidean => (x - y).powi(2),
209 DistanceMetric::Cosine => {
210 let norm_x = x.abs();
211 let norm_y = y.abs();
212 if norm_x > 0.0 && norm_y > 0.0 {
213 1.0 - (x * y) / (norm_x * norm_y)
214 } else {
215 1.0
216 }
217 }
218 }
219 }
220
221 pub fn compute_time_series_similarity(&self, seq1: &Array1<f64>, seq2: &Array1<f64>) -> f64 {
223 let dtw_distance = self.compute_dtw_distance(seq1, seq2);
224 (-self.gamma * dtw_distance).exp()
225 }
226
227 pub fn time_series_to_matrix(&self, series: &[Array1<f64>]) -> Result<Array2<f64>> {
229 if series.is_empty() {
230 return Err(SklearsError::InvalidInput(
231 "Empty time series list".to_string(),
232 ));
233 }
234
235 let n_series = series.len();
236 let max_length = series.iter().map(|s| s.len()).max().unwrap_or(0);
237
238 let mut matrix = Array2::zeros((n_series, max_length));
240
241 for (i, seq) in series.iter().enumerate() {
242 for (j, &value) in seq.iter().enumerate() {
243 matrix[[i, j]] = value;
244 }
245 }
246
247 Ok(matrix)
248 }
249}
250
251impl Kernel for DynamicTimeWarpingKernel {
252 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
253 let x_owned = x.to_owned();
255 let y_owned = y.to_owned();
256 self.compute_time_series_similarity(&x_owned, &y_owned)
257 }
258
259 fn parameters(&self) -> HashMap<String, f64> {
260 let mut params = HashMap::new();
261 params.insert("gamma".to_string(), self.gamma);
262 if let Some(bandwidth) = self.bandwidth {
263 params.insert("bandwidth".to_string(), bandwidth as f64);
264 }
265 params.insert(
266 "normalize".to_string(),
267 if self.normalize { 1.0 } else { 0.0 },
268 );
269 params
270 }
271}
272
273#[derive(Debug, Clone)]
287pub struct GlobalAlignmentKernel {
288 pub sigma: f64,
290 pub gamma: f64,
292 pub normalize: bool,
294}
295
296impl Default for GlobalAlignmentKernel {
297 fn default() -> Self {
298 Self {
299 sigma: 1.0,
300 gamma: 1.0,
301 normalize: true,
302 }
303 }
304}
305
306impl GlobalAlignmentKernel {
307 pub fn new(sigma: f64, gamma: f64) -> Self {
309 Self {
310 sigma,
311 gamma,
312 normalize: true,
313 }
314 }
315
316 pub fn compute_gak_similarity(&self, seq1: &Array1<f64>, seq2: &Array1<f64>) -> f64 {
318 let ga_distance = self.compute_global_alignment(seq1, seq2);
319 (-self.gamma * ga_distance).exp()
320 }
321
322 fn compute_global_alignment(&self, seq1: &Array1<f64>, seq2: &Array1<f64>) -> f64 {
324 let n = seq1.len();
325 let m = seq2.len();
326
327 if n == 0 || m == 0 {
328 return f64::INFINITY;
329 }
330
331 let triangular_kernel = |x: f64, y: f64| -> f64 {
333 let diff = (x - y).abs();
334 if diff <= self.sigma {
335 1.0 - diff / self.sigma
336 } else {
337 0.0
338 }
339 };
340
341 let mut log_ga = Array2::from_elem((n + 1, m + 1), f64::NEG_INFINITY);
343 log_ga[[0, 0]] = 0.0;
344
345 for i in 1..=n {
346 for j in 1..=m {
347 let cost = -triangular_kernel(seq1[i - 1], seq2[j - 1]).ln();
348
349 let candidates = [
351 log_ga[[i - 1, j - 1]],
352 log_ga[[i - 1, j]],
353 log_ga[[i, j - 1]],
354 ];
355
356 let max_val = candidates.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
357
358 if max_val.is_finite() {
359 let sum_exp: f64 = candidates.iter().map(|&x| (x - max_val).exp()).sum();
360 log_ga[[i, j]] = cost + max_val + sum_exp.ln();
361 } else {
362 log_ga[[i, j]] = cost;
363 }
364 }
365 }
366
367 let distance = log_ga[[n, m]];
368
369 if self.normalize {
370 distance / (n + m) as f64
371 } else {
372 distance
373 }
374 }
375}
376
377impl Kernel for GlobalAlignmentKernel {
378 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
379 let x_owned = x.to_owned();
380 let y_owned = y.to_owned();
381 self.compute_gak_similarity(&x_owned, &y_owned)
382 }
383
384 fn parameters(&self) -> HashMap<String, f64> {
385 let mut params = HashMap::new();
386 params.insert("sigma".to_string(), self.sigma);
387 params.insert("gamma".to_string(), self.gamma);
388 params.insert(
389 "normalize".to_string(),
390 if self.normalize { 1.0 } else { 0.0 },
391 );
392 params
393 }
394}
395
396#[derive(Debug, Clone)]
409pub struct AutoRegressiveKernel {
410 pub order: usize,
412 pub gamma: f64,
414 pub include_bias: bool,
416 pub regularization: f64,
418}
419
420impl Default for AutoRegressiveKernel {
421 fn default() -> Self {
422 Self {
423 order: 3,
424 gamma: 1.0,
425 include_bias: true,
426 regularization: 1e-6,
427 }
428 }
429}
430
431impl AutoRegressiveKernel {
432 pub fn new(order: usize, gamma: f64) -> Self {
434 Self {
435 order,
436 gamma,
437 ..Default::default()
438 }
439 }
440
441 pub fn estimate_ar_coefficients(&self, series: &Array1<f64>) -> Result<Array1<f64>> {
443 let n = series.len();
444
445 if n <= self.order {
446 return Err(SklearsError::InvalidInput(
447 "Time series too short for AR estimation".to_string(),
448 ));
449 }
450
451 let n_params = if self.include_bias {
452 self.order + 1
453 } else {
454 self.order
455 };
456
457 let n_obs = n - self.order;
459 let mut x_matrix = Array2::zeros((n_obs, n_params));
460 let mut y_vector = Array1::zeros(n_obs);
461
462 for i in 0..n_obs {
463 for j in 0..self.order {
465 x_matrix[[i, j]] = series[self.order - 1 - j + i];
466 }
467
468 if self.include_bias {
470 x_matrix[[i, self.order]] = 1.0;
471 }
472
473 y_vector[i] = series[self.order + i];
474 }
475
476 let xtx = x_matrix.t().dot(&x_matrix);
478 let mut xtx_reg = xtx.clone();
479
480 for i in 0..n_params {
482 xtx_reg[[i, i]] += self.regularization;
483 }
484
485 let xty = x_matrix.t().dot(&y_vector);
486
487 let coefficients = self.solve_linear_system(&xtx_reg, &xty)?;
490
491 Ok(coefficients)
492 }
493
494 fn solve_linear_system(&self, a: &Array2<f64>, b: &Array1<f64>) -> Result<Array1<f64>> {
496 let n = a.nrows();
497 if n != a.ncols() || n != b.len() {
498 return Err(SklearsError::InvalidInput(
499 "Incompatible matrix dimensions".to_string(),
500 ));
501 }
502
503 let mut aug = Array2::zeros((n, n + 1));
505
506 for i in 0..n {
508 for j in 0..n {
509 aug[[i, j]] = a[[i, j]];
510 }
511 aug[[i, n]] = b[i];
512 }
513
514 for k in 0..n {
516 let mut max_row = k;
518 for i in (k + 1)..n {
519 if aug[[i, k]].abs() > aug[[max_row, k]].abs() {
520 max_row = i;
521 }
522 }
523
524 if max_row != k {
526 for j in 0..=n {
527 let temp = aug[[k, j]];
528 aug[[k, j]] = aug[[max_row, j]];
529 aug[[max_row, j]] = temp;
530 }
531 }
532
533 for i in (k + 1)..n {
535 if aug[[k, k]].abs() > 1e-12 {
536 let factor = aug[[i, k]] / aug[[k, k]];
537 for j in k..=n {
538 aug[[i, j]] -= factor * aug[[k, j]];
539 }
540 }
541 }
542 }
543
544 let mut x = Array1::zeros(n);
546 for i in (0..n).rev() {
547 x[i] = aug[[i, n]];
548 for j in (i + 1)..n {
549 x[i] -= aug[[i, j]] * x[j];
550 }
551 if aug[[i, i]].abs() > 1e-12 {
552 x[i] /= aug[[i, i]];
553 } else {
554 return Err(SklearsError::Other("Singular matrix".to_string()));
555 }
556 }
557
558 Ok(x)
559 }
560
561 pub fn compute_ar_similarity(&self, seq1: &Array1<f64>, seq2: &Array1<f64>) -> Result<f64> {
563 let coeff1 = self.estimate_ar_coefficients(seq1)?;
564 let coeff2 = self.estimate_ar_coefficients(seq2)?;
565
566 let diff = &coeff1 - &coeff2;
567 let distance_squared = diff.dot(&diff);
568
569 Ok((-self.gamma * distance_squared).exp())
570 }
571}
572
573impl Kernel for AutoRegressiveKernel {
574 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
575 let x_owned = x.to_owned();
576 let y_owned = y.to_owned();
577 self.compute_ar_similarity(&x_owned, &y_owned)
578 .unwrap_or(0.0)
579 }
580
581 fn parameters(&self) -> HashMap<String, f64> {
582 let mut params = HashMap::new();
583 params.insert("order".to_string(), self.order as f64);
584 params.insert("gamma".to_string(), self.gamma);
585 params.insert(
586 "include_bias".to_string(),
587 if self.include_bias { 1.0 } else { 0.0 },
588 );
589 params.insert("regularization".to_string(), self.regularization);
590 params
591 }
592}
593
594#[derive(Debug)]
606pub struct StreamingSVM {
607 pub window_size: usize,
609 pub learning_rate: f64,
611 pub forgetting_factor: f64,
613 sample_buffer: VecDeque<Array1<f64>>,
615 label_buffer: VecDeque<f64>,
617 pub drift_threshold: f64,
619 error_history: VecDeque<f64>,
621 is_fitted: bool,
623 trained_svm: Option<SVC<Trained>>,
625}
626
627impl StreamingSVM {
628 pub fn new(window_size: usize) -> Self {
630 Self {
631 window_size,
632 learning_rate: 0.01,
633 forgetting_factor: 0.95,
634 sample_buffer: VecDeque::with_capacity(window_size),
635 label_buffer: VecDeque::with_capacity(window_size),
636 drift_threshold: 0.1,
637 error_history: VecDeque::with_capacity(100),
638 is_fitted: false,
639 trained_svm: None,
640 }
641 }
642
643 pub fn with_learning_rate(mut self, learning_rate: f64) -> Self {
645 self.learning_rate = learning_rate;
646 self
647 }
648
649 pub fn with_forgetting_factor(mut self, forgetting_factor: f64) -> Self {
651 self.forgetting_factor = forgetting_factor;
652 self
653 }
654
655 pub fn with_drift_threshold(mut self, drift_threshold: f64) -> Self {
657 self.drift_threshold = drift_threshold;
658 self
659 }
660
661 pub fn initialize(&mut self, x_init: &Array2<f64>, y_init: &Array1<f64>) -> Result<()> {
663 if x_init.nrows() != y_init.len() {
664 return Err(SklearsError::InvalidInput(
665 "Mismatched number of samples and labels".to_string(),
666 ));
667 }
668
669 let base_svm = SVC::new();
671 let fitted_svm = base_svm.fit(x_init, y_init)?;
672 self.trained_svm = Some(fitted_svm);
673
674 for i in 0..x_init.nrows().min(self.window_size) {
676 self.sample_buffer.push_back(x_init.row(i).to_owned());
677 self.label_buffer.push_back(y_init[i]);
678 }
679
680 self.is_fitted = true;
681 Ok(())
682 }
683
684 pub fn partial_fit(&mut self, x_new: &Array1<f64>, y_true: f64) -> Result<f64> {
686 if !self.is_fitted {
687 return Err(SklearsError::NotFitted {
688 operation: "partial_fit".to_string(),
689 });
690 }
691
692 let x_matrix = Array2::from_shape_vec((1, x_new.len()), x_new.to_vec())?;
694 let y_pred = if let Some(ref trained_svm) = self.trained_svm {
695 let y_pred_array = trained_svm.predict(&x_matrix)?;
696 y_pred_array[0]
697 } else {
698 return Err(SklearsError::NotFitted {
699 operation: "prediction".to_string(),
700 });
701 };
702
703 let error = (y_true - y_pred).abs();
705 self.error_history.push_back(error);
706 if self.error_history.len() > 100 {
707 self.error_history.pop_front();
708 }
709
710 let drift_detected = self.detect_concept_drift();
712
713 self.sample_buffer.push_back(x_new.clone());
715 self.label_buffer.push_back(y_true);
716
717 if self.sample_buffer.len() > self.window_size {
718 self.sample_buffer.pop_front();
719 self.label_buffer.pop_front();
720 }
721
722 if drift_detected || self.sample_buffer.len() >= self.window_size {
724 self.retrain_model()?;
725 }
726
727 Ok(y_pred)
728 }
729
730 fn detect_concept_drift(&self) -> bool {
732 if self.error_history.len() < 20 {
733 return false;
734 }
735
736 let recent_errors: Vec<f64> = self.error_history.iter().rev().take(10).cloned().collect();
738 let older_errors: Vec<f64> = self
739 .error_history
740 .iter()
741 .rev()
742 .skip(10)
743 .take(10)
744 .cloned()
745 .collect();
746
747 let recent_mean = recent_errors.iter().sum::<f64>() / recent_errors.len() as f64;
748 let older_mean = older_errors.iter().sum::<f64>() / older_errors.len() as f64;
749
750 (recent_mean - older_mean).abs() > self.drift_threshold
751 }
752
753 fn retrain_model(&mut self) -> Result<()> {
755 if self.sample_buffer.is_empty() {
756 return Ok(());
757 }
758
759 let n_samples = self.sample_buffer.len();
761 let n_features = self.sample_buffer[0].len();
762
763 let mut x_matrix = Array2::zeros((n_samples, n_features));
764 let mut y_vector = Array1::zeros(n_samples);
765
766 for (i, (sample, &label)) in self
767 .sample_buffer
768 .iter()
769 .zip(self.label_buffer.iter())
770 .enumerate()
771 {
772 x_matrix.row_mut(i).assign(sample);
773 y_vector[i] = label;
774 }
775
776 let mut weights = Array1::zeros(n_samples);
778 for i in 0..n_samples {
779 let age = (n_samples - i - 1) as f64;
780 weights[i] = self.forgetting_factor.powf(age);
781 }
782
783 let base_svm = SVC::new();
785 let fitted_svm = base_svm.fit(&x_matrix, &y_vector)?;
786 self.trained_svm = Some(fitted_svm);
787
788 Ok(())
789 }
790
791 pub fn predict(&self, x: &Array1<f64>) -> Result<f64> {
793 if !self.is_fitted {
794 return Err(SklearsError::NotFitted {
795 operation: "predict".to_string(),
796 });
797 }
798
799 let x_matrix = Array2::from_shape_vec((1, x.len()), x.to_vec())?;
800 if let Some(ref trained_svm) = self.trained_svm {
801 let y_pred = trained_svm.predict(&x_matrix)?;
802 Ok(y_pred[0])
803 } else {
804 Err(SklearsError::NotFitted {
805 operation: "prediction".to_string(),
806 })
807 }
808 }
809
810 pub fn buffer_size(&self) -> usize {
812 self.sample_buffer.len()
813 }
814
815 pub fn get_error_stats(&self) -> (f64, f64) {
817 if self.error_history.is_empty() {
818 return (0.0, 0.0);
819 }
820
821 let mean = self.error_history.iter().sum::<f64>() / self.error_history.len() as f64;
822 let variance = self
823 .error_history
824 .iter()
825 .map(|&x| (x - mean).powi(2))
826 .sum::<f64>()
827 / self.error_history.len() as f64;
828
829 (mean, variance.sqrt())
830 }
831}
832
833pub struct TemporalPatternRecognizer {
838 pub window_size: usize,
840 pub overlap: usize,
842 pub distance_threshold: f64,
844 dtw_kernel: DynamicTimeWarpingKernel,
846}
847
848impl TemporalPatternRecognizer {
849 pub fn new(window_size: usize) -> Self {
851 Self {
852 window_size,
853 overlap: window_size / 2,
854 distance_threshold: 0.1,
855 dtw_kernel: DynamicTimeWarpingKernel::new(1.0),
856 }
857 }
858
859 pub fn extract_patterns(&self, series: &Array1<f64>) -> Vec<Array1<f64>> {
861 let mut patterns = Vec::new();
862 let step = self.window_size - self.overlap;
863
864 let mut i = 0;
865 while i + self.window_size <= series.len() {
866 let pattern = series
867 .slice(scirs2_core::ndarray::s![i..i + self.window_size])
868 .to_owned();
869 patterns.push(pattern);
870 i += step;
871 }
872
873 patterns
874 }
875
876 pub fn find_motifs(
878 &self,
879 series: &Array1<f64>,
880 min_occurrences: usize,
881 ) -> Vec<(Array1<f64>, Vec<usize>)> {
882 let patterns = self.extract_patterns(series);
883 let mut motifs = Vec::new();
884
885 for (i, pattern) in patterns.iter().enumerate() {
886 let mut occurrences = vec![i];
887
888 for (j, other_pattern) in patterns.iter().enumerate().skip(i + 1) {
889 let similarity = self
890 .dtw_kernel
891 .compute_time_series_similarity(pattern, other_pattern);
892 if similarity > 1.0 - self.distance_threshold {
893 occurrences.push(j);
894 }
895 }
896
897 if occurrences.len() >= min_occurrences {
898 motifs.push((pattern.clone(), occurrences));
899 }
900 }
901
902 motifs
903 }
904
905 pub fn detect_anomalies(
907 &self,
908 series: &Array1<f64>,
909 baseline_patterns: &[Array1<f64>],
910 ) -> Vec<usize> {
911 let patterns = self.extract_patterns(series);
912 let mut anomalies = Vec::new();
913
914 for (i, pattern) in patterns.iter().enumerate() {
915 let mut max_similarity: f64 = 0.0;
916
917 for baseline in baseline_patterns {
918 let similarity = self
919 .dtw_kernel
920 .compute_time_series_similarity(pattern, baseline);
921 max_similarity = max_similarity.max(similarity);
922 }
923
924 if max_similarity < 1.0 - self.distance_threshold {
925 anomalies.push(i);
926 }
927 }
928
929 anomalies
930 }
931}
932
933#[allow(non_snake_case)]
934#[cfg(test)]
935mod tests {
936 use super::*;
937
938 #[test]
939 fn test_dtw_kernel() {
940 let kernel = DynamicTimeWarpingKernel::new(1.0);
941
942 let seq1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 2.0, 1.0]);
943 let seq2 = Array1::from_vec(vec![1.0, 2.0, 3.0, 2.0, 1.0]);
944
945 let similarity = kernel.compute_time_series_similarity(&seq1, &seq2);
946 assert!((similarity - 1.0).abs() < 1e-6); let seq3 = Array1::from_vec(vec![5.0, 6.0, 7.0, 6.0, 5.0]);
949 let similarity2 = kernel.compute_time_series_similarity(&seq1, &seq3);
950 assert!(similarity2 < similarity); }
952
953 #[test]
954 fn test_dtw_distance() {
955 let kernel = DynamicTimeWarpingKernel::new(1.0);
956
957 let seq1 = Array1::from_vec(vec![1.0, 2.0, 3.0]);
958 let seq2 = Array1::from_vec(vec![1.0, 2.0, 3.0]);
959
960 let distance = kernel.compute_dtw_distance(&seq1, &seq2);
961 assert_eq!(distance, 0.0); let seq3 = Array1::from_vec(vec![4.0, 5.0, 6.0]);
964 let distance2 = kernel.compute_dtw_distance(&seq1, &seq3);
965 assert!(distance2 > 0.0); }
967
968 #[test]
969 fn test_gak_kernel() {
970 let kernel = GlobalAlignmentKernel::new(1.0, 1.0);
971
972 let seq1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 2.0, 1.0]);
973 let seq2 = Array1::from_vec(vec![1.0, 2.0, 3.0, 2.0, 1.0]);
974
975 let similarity = kernel.compute_gak_similarity(&seq1, &seq2);
976 assert!(similarity > 0.0);
977 }
978
979 #[test]
980 fn test_ar_kernel() {
981 let kernel = AutoRegressiveKernel::new(2, 1.0);
982
983 let seq1 = Array1::from_vec(vec![1.0, 0.5, 0.25, 0.125, 0.0625, 0.03125]);
985 let seq2 = Array1::from_vec(vec![2.0, 1.0, 0.5, 0.25, 0.125, 0.0625]);
986
987 let result = kernel.compute_ar_similarity(&seq1, &seq2);
988 assert!(result.is_ok());
989
990 let similarity = result.expect("operation should succeed");
991 assert!(similarity > 0.0 && similarity <= 1.0);
992 }
993
994 #[test]
995 fn test_ar_coefficient_estimation() {
996 let kernel = AutoRegressiveKernel::new(2, 1.0);
997
998 let series = Array1::from_vec(vec![1.0, 0.5, 0.55, 0.425, 0.44, 0.407, 0.419]);
1000
1001 let result = kernel.estimate_ar_coefficients(&series);
1002 assert!(result.is_ok());
1003
1004 let coeffs = result.expect("operation should succeed");
1005 assert_eq!(coeffs.len(), 3); }
1007
1008 #[test]
1009 fn test_streaming_svm_initialization() {
1010 let mut streaming_svm = StreamingSVM::new(10);
1011
1012 let x_init = Array2::from_shape_vec((4, 2), vec![1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0])
1013 .expect("array shape mismatch");
1014 let y_init = Array1::from_vec(vec![1.0, 1.0, -1.0, -1.0]);
1015
1016 let result = streaming_svm.initialize(&x_init, &y_init);
1017 assert!(result.is_ok());
1018 assert_eq!(streaming_svm.buffer_size(), 4);
1019 }
1020
1021 #[test]
1022 fn test_temporal_pattern_recognizer() {
1023 let recognizer = TemporalPatternRecognizer::new(3);
1024
1025 let series = Array1::from_vec(vec![1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1026 let patterns = recognizer.extract_patterns(&series);
1027
1028 assert!(!patterns.is_empty());
1029 assert_eq!(patterns[0].len(), 3);
1030 }
1031
1032 #[test]
1033 fn test_motif_detection() {
1034 let recognizer = TemporalPatternRecognizer::new(3);
1035
1036 let series = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 7.0, 8.0]);
1038 let _motifs = recognizer.find_motifs(&series, 2);
1039
1040 }
1043
1044 #[test]
1045 fn test_distance_metrics() {
1046 let kernel =
1047 DynamicTimeWarpingKernel::default().with_distance_metric(DistanceMetric::Manhattan);
1048
1049 let distance = kernel.point_distance(3.0, 1.0);
1050 assert_eq!(distance, 2.0);
1051
1052 let kernel2 = DynamicTimeWarpingKernel::default()
1053 .with_distance_metric(DistanceMetric::SquaredEuclidean);
1054
1055 let distance2 = kernel2.point_distance(3.0, 1.0);
1056 assert_eq!(distance2, 4.0);
1057 }
1058
1059 #[test]
1060 fn test_time_series_to_matrix() {
1061 let kernel = DynamicTimeWarpingKernel::new(1.0);
1062
1063 let series = vec![
1064 Array1::from_vec(vec![1.0, 2.0, 3.0]),
1065 Array1::from_vec(vec![4.0, 5.0]),
1066 Array1::from_vec(vec![6.0, 7.0, 8.0, 9.0]),
1067 ];
1068
1069 let result = kernel.time_series_to_matrix(&series);
1070 assert!(result.is_ok());
1071
1072 let matrix = result.expect("operation should succeed");
1073 assert_eq!(matrix.nrows(), 3);
1074 assert_eq!(matrix.ncols(), 4); }
1076}