1use dashmap::DashMap;
13use lru::LruCache;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::fmt;
17use std::hash::Hash;
18use std::num::NonZeroUsize;
19use std::sync::{Arc, Mutex};
20use thiserror::Error;
21
22#[derive(Debug, Error)]
24pub enum TemporalError {
25 #[error("Sequence too long: {0}")]
26 SequenceTooLong(usize),
27
28 #[error("Invalid algorithm: {0}")]
29 InvalidAlgorithm(String),
30
31 #[error("Cache error: {0}")]
32 CacheError(String),
33
34 #[error("Invalid pattern length: min={0}, max={1}")]
35 InvalidPatternLength(usize, usize),
36
37 #[error("Pattern not found")]
38 PatternNotFound,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
43pub struct TemporalElement<T> {
44 pub value: T,
45 pub timestamp: u64,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct Sequence<T> {
51 pub elements: Vec<TemporalElement<T>>,
52}
53
54impl<T> Sequence<T> {
55 pub fn new() -> Self {
56 Self {
57 elements: Vec::new(),
58 }
59 }
60
61 pub fn push(&mut self, value: T, timestamp: u64) {
62 self.elements.push(TemporalElement { value, timestamp });
63 }
64
65 pub fn len(&self) -> usize {
66 self.elements.len()
67 }
68
69 pub fn is_empty(&self) -> bool {
70 self.elements.is_empty()
71 }
72}
73
74impl<T> Default for Sequence<T> {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
82pub enum ComparisonAlgorithm {
83 DTW,
85 LCS,
87 EditDistance,
89 Euclidean,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ComparisonResult {
96 pub distance: f64,
97 pub algorithm: ComparisonAlgorithm,
98 pub alignment: Option<Vec<(usize, usize)>>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct CacheStats {
104 pub hits: u64,
105 pub misses: u64,
106 pub size: usize,
107 pub capacity: usize,
108}
109
110impl CacheStats {
111 pub fn hit_rate(&self) -> f64 {
112 if self.hits + self.misses == 0 {
113 0.0
114 } else {
115 self.hits as f64 / (self.hits + self.misses) as f64
116 }
117 }
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct Pattern<T> {
123 pub sequence: Vec<T>,
125 pub occurrences: Vec<usize>,
127 pub confidence: f64,
129}
130
131impl<T> Pattern<T> {
132 pub fn new(sequence: Vec<T>, occurrences: Vec<usize>, confidence: f64) -> Self {
134 Self {
135 sequence,
136 occurrences,
137 confidence,
138 }
139 }
140
141 pub fn frequency(&self) -> usize {
143 self.occurrences.len()
144 }
145
146 pub fn length(&self) -> usize {
148 self.sequence.len()
149 }
150}
151
152#[derive(Debug, Clone, PartialEq)]
154pub struct SimilarityMatch {
155 pub start_index: usize,
157 pub similarity: f64,
159 pub distance: f64,
161}
162
163impl SimilarityMatch {
164 pub fn new(start_index: usize, distance: f64) -> Self {
165 let similarity = (-distance / 10.0).exp();
167 Self {
168 start_index,
169 similarity,
170 distance,
171 }
172 }
173}
174
175pub struct TemporalComparator<T> {
177 cache: Arc<Mutex<LruCache<String, ComparisonResult>>>,
178 pattern_cache: Arc<Mutex<LruCache<String, Vec<Pattern<T>>>>>,
179 similarity_cache: Arc<Mutex<LruCache<String, Vec<SimilarityMatch>>>>,
180 cache_hits: Arc<DashMap<String, u64>>,
181 cache_misses: Arc<DashMap<String, u64>>,
182 max_sequence_length: usize,
183}
184
185impl<T> TemporalComparator<T>
186where
187 T: Clone + PartialEq + fmt::Debug + Serialize + Hash + Eq,
188{
189 pub fn new(cache_size: usize, max_sequence_length: usize) -> Self {
191 Self {
192 cache: Arc::new(Mutex::new(LruCache::new(
193 NonZeroUsize::new(cache_size).unwrap(),
194 ))),
195 pattern_cache: Arc::new(Mutex::new(LruCache::new(
196 NonZeroUsize::new(cache_size).unwrap(),
197 ))),
198 similarity_cache: Arc::new(Mutex::new(LruCache::new(
199 NonZeroUsize::new(cache_size).unwrap(),
200 ))),
201 cache_hits: Arc::new(DashMap::new()),
202 cache_misses: Arc::new(DashMap::new()),
203 max_sequence_length,
204 }
205 }
206
207 pub fn compare(
209 &self,
210 seq1: &Sequence<T>,
211 seq2: &Sequence<T>,
212 algorithm: ComparisonAlgorithm,
213 ) -> Result<ComparisonResult, TemporalError> {
214 if seq1.len() > self.max_sequence_length || seq2.len() > self.max_sequence_length {
216 return Err(TemporalError::SequenceTooLong(seq1.len().max(seq2.len())));
217 }
218
219 let cache_key = self.cache_key(seq1, seq2, algorithm);
221
222 if let Ok(mut cache) = self.cache.lock() {
224 if let Some(result) = cache.get(&cache_key) {
225 self.record_cache_hit(&cache_key);
226 return Ok(result.clone());
227 }
228 }
229
230 self.record_cache_miss(&cache_key);
231
232 let result = match algorithm {
234 ComparisonAlgorithm::DTW => self.dtw(seq1, seq2),
235 ComparisonAlgorithm::LCS => self.lcs(seq1, seq2),
236 ComparisonAlgorithm::EditDistance => self.edit_distance(seq1, seq2),
237 ComparisonAlgorithm::Euclidean => self.euclidean(seq1, seq2),
238 }?;
239
240 if let Ok(mut cache) = self.cache.lock() {
242 cache.put(cache_key, result.clone());
243 }
244
245 Ok(result)
246 }
247
248 fn dtw(
250 &self,
251 seq1: &Sequence<T>,
252 seq2: &Sequence<T>,
253 ) -> Result<ComparisonResult, TemporalError> {
254 let n = seq1.len();
255 let m = seq2.len();
256
257 if n == 0 || m == 0 {
258 return Ok(ComparisonResult {
259 distance: (n + m) as f64,
260 algorithm: ComparisonAlgorithm::DTW,
261 alignment: None,
262 });
263 }
264
265 let mut dtw = vec![vec![f64::INFINITY; m + 1]; n + 1];
267 dtw[0][0] = 0.0;
268
269 for i in 1..=n {
271 for j in 1..=m {
272 let cost = if seq1.elements[i - 1].value == seq2.elements[j - 1].value {
273 0.0
274 } else {
275 1.0
276 };
277
278 dtw[i][j] = cost + dtw[i - 1][j - 1].min(dtw[i - 1][j]).min(dtw[i][j - 1]);
279 }
280 }
281
282 let mut alignment = Vec::new();
284 let (mut i, mut j) = (n, m);
285
286 while i > 0 && j > 0 {
287 alignment.push((i - 1, j - 1));
288
289 let min_val = dtw[i - 1][j - 1].min(dtw[i - 1][j]).min(dtw[i][j - 1]);
290
291 if dtw[i - 1][j - 1] == min_val {
292 i -= 1;
293 j -= 1;
294 } else if dtw[i - 1][j] == min_val {
295 i -= 1;
296 } else {
297 j -= 1;
298 }
299 }
300
301 alignment.reverse();
302
303 Ok(ComparisonResult {
304 distance: dtw[n][m],
305 algorithm: ComparisonAlgorithm::DTW,
306 alignment: Some(alignment),
307 })
308 }
309
310 fn lcs(
312 &self,
313 seq1: &Sequence<T>,
314 seq2: &Sequence<T>,
315 ) -> Result<ComparisonResult, TemporalError> {
316 let n = seq1.len();
317 let m = seq2.len();
318
319 let mut dp = vec![vec![0; m + 1]; n + 1];
320
321 for i in 1..=n {
322 for j in 1..=m {
323 if seq1.elements[i - 1].value == seq2.elements[j - 1].value {
324 dp[i][j] = dp[i - 1][j - 1] + 1;
325 } else {
326 dp[i][j] = dp[i - 1][j].max(dp[i][j - 1]);
327 }
328 }
329 }
330
331 let lcs_length = dp[n][m];
332 let distance = (n + m - 2 * lcs_length) as f64;
333
334 Ok(ComparisonResult {
335 distance,
336 algorithm: ComparisonAlgorithm::LCS,
337 alignment: None,
338 })
339 }
340
341 fn edit_distance(
343 &self,
344 seq1: &Sequence<T>,
345 seq2: &Sequence<T>,
346 ) -> Result<ComparisonResult, TemporalError> {
347 let n = seq1.len();
348 let m = seq2.len();
349
350 let mut dp = vec![vec![0; m + 1]; n + 1];
351
352 #[allow(clippy::needless_range_loop)]
356 for i in 0..=n {
357 dp[i][0] = i;
358 }
359 #[allow(clippy::needless_range_loop)]
360 for j in 0..=m {
361 dp[0][j] = j;
362 }
363
364 for i in 1..=n {
365 for j in 1..=m {
366 let cost = if seq1.elements[i - 1].value == seq2.elements[j - 1].value {
367 0
368 } else {
369 1
370 };
371
372 dp[i][j] = (dp[i - 1][j] + 1)
373 .min(dp[i][j - 1] + 1)
374 .min(dp[i - 1][j - 1] + cost);
375 }
376 }
377
378 Ok(ComparisonResult {
379 distance: dp[n][m] as f64,
380 algorithm: ComparisonAlgorithm::EditDistance,
381 alignment: None,
382 })
383 }
384
385 fn euclidean(
387 &self,
388 seq1: &Sequence<T>,
389 seq2: &Sequence<T>,
390 ) -> Result<ComparisonResult, TemporalError> {
391 let n = seq1.len().min(seq2.len());
392 let mut sum: f64 = 0.0;
393
394 for i in 0..n {
395 if seq1.elements[i].value != seq2.elements[i].value {
397 sum += 1.0;
398 }
399 }
400
401 Ok(ComparisonResult {
402 distance: sum.sqrt(), algorithm: ComparisonAlgorithm::Euclidean,
404 alignment: None,
405 })
406 }
407
408 fn cache_key(
410 &self,
411 seq1: &Sequence<T>,
412 seq2: &Sequence<T>,
413 algorithm: ComparisonAlgorithm,
414 ) -> String {
415 format!(
416 "{:?}:{:?}:{:?}",
417 seq1.elements.len(),
418 seq2.elements.len(),
419 algorithm
420 )
421 }
422
423 fn record_cache_hit(&self, key: &str) {
424 self.cache_hits
425 .entry(key.to_string())
426 .and_modify(|v| *v += 1)
427 .or_insert(1);
428 }
429
430 fn record_cache_miss(&self, key: &str) {
431 self.cache_misses
432 .entry(key.to_string())
433 .and_modify(|v| *v += 1)
434 .or_insert(1);
435 }
436
437 pub fn cache_stats(&self) -> CacheStats {
439 let hits: u64 = self.cache_hits.iter().map(|r| *r.value()).sum();
440 let misses: u64 = self.cache_misses.iter().map(|r| *r.value()).sum();
441
442 let (size, capacity) = if let Ok(cache) = self.cache.lock() {
443 (cache.len(), cache.cap().get())
444 } else {
445 (0, 0)
446 };
447
448 CacheStats {
449 hits,
450 misses,
451 size,
452 capacity,
453 }
454 }
455
456 pub fn clear_cache(&self) {
458 if let Ok(mut cache) = self.cache.lock() {
459 cache.clear();
460 }
461 if let Ok(mut cache) = self.pattern_cache.lock() {
462 cache.clear();
463 }
464 if let Ok(mut cache) = self.similarity_cache.lock() {
465 cache.clear();
466 }
467 self.cache_hits.clear();
468 self.cache_misses.clear();
469 }
470
471 pub fn find_similar_generic(
473 &self,
474 haystack: &[T],
475 needle: &[T],
476 threshold: f64,
477 ) -> Result<Vec<SimilarityMatch>, TemporalError> {
478 if needle.is_empty() || haystack.len() < needle.len() {
479 return Ok(Vec::new());
480 }
481
482 let cache_key = format!(
484 "similar:{:?}:{:?}:{}",
485 haystack.len(),
486 needle.len(),
487 threshold
488 );
489
490 if let Ok(mut cache) = self.similarity_cache.lock() {
492 if let Some(results) = cache.get(&cache_key) {
493 self.record_cache_hit(&cache_key);
494 return Ok(results.clone());
495 }
496 }
497
498 self.record_cache_miss(&cache_key);
499
500 let needle_len = needle.len();
501 let mut matches = Vec::new();
502
503 for start_idx in 0..=(haystack.len() - needle_len) {
505 let window = &haystack[start_idx..start_idx + needle_len];
506
507 let mut seq1 = Sequence::new();
509 for (i, item) in window.iter().enumerate() {
510 seq1.push(item.clone(), i as u64);
511 }
512
513 let mut seq2 = Sequence::new();
514 for (i, item) in needle.iter().enumerate() {
515 seq2.push(item.clone(), i as u64);
516 }
517
518 if let Ok(result) = self.dtw(&seq1, &seq2) {
520 let normalized_distance = result.distance / needle_len as f64;
522
523 if normalized_distance <= threshold {
524 matches.push(SimilarityMatch::new(start_idx, result.distance));
525 }
526 }
527 }
528
529 matches.sort_by(|a, b| {
531 a.distance
532 .partial_cmp(&b.distance)
533 .unwrap_or(std::cmp::Ordering::Equal)
534 });
535
536 if let Ok(mut cache) = self.similarity_cache.lock() {
538 cache.put(cache_key, matches.clone());
539 }
540
541 Ok(matches)
542 }
543
544 pub fn detect_recurring_patterns(
546 &self,
547 sequence: &[T],
548 min_length: usize,
549 max_length: usize,
550 ) -> Result<Vec<Pattern<T>>, TemporalError> {
551 if min_length > max_length {
552 return Err(TemporalError::InvalidPatternLength(min_length, max_length));
553 }
554
555 if sequence.len() < min_length {
556 return Ok(Vec::new());
557 }
558
559 let cache_key = format!(
561 "patterns:{:?}:{}:{}",
562 sequence.len(),
563 min_length,
564 max_length
565 );
566
567 if let Ok(mut cache) = self.pattern_cache.lock() {
569 if let Some(patterns) = cache.get(&cache_key) {
570 self.record_cache_hit(&cache_key);
571 return Ok(patterns.clone());
572 }
573 }
574
575 self.record_cache_miss(&cache_key);
576
577 let mut pattern_map: HashMap<Vec<T>, Vec<usize>> = HashMap::new();
578
579 for pattern_len in min_length..=max_length.min(sequence.len()) {
581 for start_idx in 0..=(sequence.len() - pattern_len) {
582 let pattern_seq = sequence[start_idx..start_idx + pattern_len].to_vec();
583
584 pattern_map.entry(pattern_seq).or_default().push(start_idx);
585 }
586 }
587
588 let mut patterns: Vec<Pattern<T>> = pattern_map
590 .into_iter()
591 .filter(|(_, occurrences)| occurrences.len() >= 2)
592 .map(|(seq, occurrences)| {
593 let frequency = occurrences.len() as f64;
595 let pattern_len = seq.len() as f64;
596 let total_possible = (sequence.len() - seq.len() + 1) as f64;
597
598 let confidence =
600 ((frequency / total_possible) * (pattern_len / max_length as f64)).min(1.0);
601
602 Pattern::new(seq, occurrences, confidence)
603 })
604 .collect();
605
606 patterns.sort_by(|a, b| {
608 b.frequency().cmp(&a.frequency()).then_with(|| {
609 b.confidence
610 .partial_cmp(&a.confidence)
611 .unwrap_or(std::cmp::Ordering::Equal)
612 })
613 });
614
615 if let Ok(mut cache) = self.pattern_cache.lock() {
617 cache.put(cache_key, patterns.clone());
618 }
619
620 Ok(patterns)
621 }
622}
623
624impl<T> Default for TemporalComparator<T>
625where
626 T: Clone + PartialEq + fmt::Debug + Serialize + Hash + Eq,
627{
628 fn default() -> Self {
629 Self::new(1000, 10000)
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn test_sequence_creation() {
639 let mut seq: Sequence<i32> = Sequence::new();
640 seq.push(1, 100);
641 seq.push(2, 200);
642
643 assert_eq!(seq.len(), 2);
644 assert!(!seq.is_empty());
645 }
646
647 #[test]
648 fn test_dtw() {
649 let comparator = TemporalComparator::new(100, 1000);
650
651 let mut seq1: Sequence<i32> = Sequence::new();
652 seq1.push(1, 100);
653 seq1.push(2, 200);
654 seq1.push(3, 300);
655
656 let mut seq2: Sequence<i32> = Sequence::new();
657 seq2.push(1, 100);
658 seq2.push(2, 200);
659 seq2.push(3, 300);
660
661 let result = comparator
662 .compare(&seq1, &seq2, ComparisonAlgorithm::DTW)
663 .unwrap();
664 assert_eq!(result.distance, 0.0);
665 }
666
667 #[test]
668 fn test_cache() {
669 let comparator = TemporalComparator::new(100, 1000);
670
671 let mut seq1: Sequence<i32> = Sequence::new();
672 seq1.push(1, 1);
673 seq1.push(2, 2);
674
675 let mut seq2: Sequence<i32> = Sequence::new();
676 seq2.push(1, 1);
677 seq2.push(2, 2);
678
679 comparator
681 .compare(&seq1, &seq2, ComparisonAlgorithm::DTW)
682 .unwrap();
683
684 comparator
686 .compare(&seq1, &seq2, ComparisonAlgorithm::DTW)
687 .unwrap();
688
689 let stats = comparator.cache_stats();
690 assert_eq!(stats.hits, 1);
691 assert_eq!(stats.misses, 1);
692 }
693
694 #[test]
695 fn test_find_similar_generic_integers() {
696 let comparator: TemporalComparator<i32> = TemporalComparator::new(100, 1000);
697
698 let haystack = vec![1, 2, 3, 4, 5, 3, 4, 5];
699 let needle = vec![3, 4, 5];
700
701 let matches = comparator
702 .find_similar_generic(&haystack, &needle, 0.1)
703 .unwrap();
704
705 assert_eq!(matches.len(), 2);
706 assert_eq!(matches[0].start_index, 2);
707 assert_eq!(matches[1].start_index, 5);
708 assert!(matches[0].similarity > 0.9); }
710
711 #[test]
712 fn test_detect_recurring_patterns_simple() {
713 let comparator: TemporalComparator<char> = TemporalComparator::new(100, 1000);
714
715 let sequence = vec!['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'];
716
717 let patterns = comparator
718 .detect_recurring_patterns(&sequence, 2, 4)
719 .unwrap();
720
721 assert!(!patterns.is_empty());
722 let abc_pattern = patterns.iter().find(|p| p.sequence == vec!['a', 'b', 'c']);
724 assert!(abc_pattern.is_some());
725
726 let pattern = abc_pattern.unwrap();
727 assert_eq!(pattern.frequency(), 3);
728 assert!(pattern.confidence > 0.0);
729 }
730}