//! # Temporal-Compare //! //! Advanced temporal sequence comparison and pattern matching. //! //! ## Features //! - Dynamic Time Warping (DTW) //! - Longest Common Subsequence (LCS) //! - Edit Distance (Levenshtein) //! - Pattern matching and detection //! - Efficient caching use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt; use std::hash::Hash; use thiserror::Error; use dashmap::DashMap; use lru::LruCache; use std::sync::{Arc, Mutex}; use std::num::NonZeroUsize; /// Errors that can occur during temporal comparison #[derive(Debug, Error)] pub enum TemporalError { #[error("Sequence too long: {0}")] SequenceTooLong(usize), #[error("Invalid algorithm: {0}")] InvalidAlgorithm(String), #[error("Cache error: {0}")] CacheError(String), #[error("Invalid pattern length: min={0}, max={1}")] InvalidPatternLength(usize, usize), #[error("Pattern not found")] PatternNotFound, } /// A temporal sequence element #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TemporalElement { pub value: T, pub timestamp: u64, } /// A temporal sequence #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Sequence { pub elements: Vec>, } impl Sequence { pub fn new() -> Self { Self { elements: Vec::new() } } pub fn push(&mut self, value: T, timestamp: u64) { self.elements.push(TemporalElement { value, timestamp }); } pub fn len(&self) -> usize { self.elements.len() } pub fn is_empty(&self) -> bool { self.elements.is_empty() } } impl Default for Sequence { fn default() -> Self { Self::new() } } /// Comparison algorithm types #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum ComparisonAlgorithm { /// Dynamic Time Warping DTW, /// Longest Common Subsequence LCS, /// Edit Distance (Levenshtein) EditDistance, /// Euclidean distance Euclidean, } /// Result of a temporal comparison #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ComparisonResult { pub distance: f64, pub algorithm: ComparisonAlgorithm, pub alignment: Option>, } /// Statistics about cache performance #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CacheStats { pub hits: u64, pub misses: u64, pub size: usize, pub capacity: usize, } impl CacheStats { pub fn hit_rate(&self) -> f64 { if self.hits + self.misses == 0 { 0.0 } else { self.hits as f64 / (self.hits + self.misses) as f64 } } } /// A detected pattern in a sequence #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Pattern { /// The pattern sequence pub sequence: Vec, /// Starting indices of all occurrences pub occurrences: Vec, /// Confidence score (0.0 to 1.0) pub confidence: f64, } impl Pattern { /// Create a new pattern pub fn new(sequence: Vec, occurrences: Vec, confidence: f64) -> Self { Self { sequence, occurrences, confidence, } } /// Get the number of times this pattern occurs pub fn frequency(&self) -> usize { self.occurrences.len() } /// Get the length of the pattern pub fn length(&self) -> usize { self.sequence.len() } } /// Match result for similarity search #[derive(Debug, Clone, PartialEq)] pub struct SimilarityMatch { /// Starting index in the haystack pub start_index: usize, /// Similarity score (0.0 to 1.0, higher is more similar) pub similarity: f64, /// DTW distance (lower is better) pub distance: f64, } impl SimilarityMatch { pub fn new(start_index: usize, distance: f64) -> Self { // Convert distance to similarity score (inverse exponential decay) let similarity = (-distance / 10.0).exp(); Self { start_index, similarity, distance, } } } /// Temporal comparator with caching pub struct TemporalComparator { cache: Arc>>, pattern_cache: Arc>>>>, similarity_cache: Arc>>>, cache_hits: Arc>, cache_misses: Arc>, max_sequence_length: usize, } impl TemporalComparator where T: Clone + PartialEq + fmt::Debug + Serialize + Hash + Eq, { /// Create a new temporal comparator pub fn new(cache_size: usize, max_sequence_length: usize) -> Self { Self { cache: Arc::new(Mutex::new(LruCache::new( NonZeroUsize::new(cache_size).unwrap() ))), pattern_cache: Arc::new(Mutex::new(LruCache::new( NonZeroUsize::new(cache_size).unwrap() ))), similarity_cache: Arc::new(Mutex::new(LruCache::new( NonZeroUsize::new(cache_size).unwrap() ))), cache_hits: Arc::new(DashMap::new()), cache_misses: Arc::new(DashMap::new()), max_sequence_length, } } /// Compare two sequences using the specified algorithm pub fn compare( &self, seq1: &Sequence, seq2: &Sequence, algorithm: ComparisonAlgorithm, ) -> Result { // Check sequence length if seq1.len() > self.max_sequence_length || seq2.len() > self.max_sequence_length { return Err(TemporalError::SequenceTooLong( seq1.len().max(seq2.len()) )); } // Generate cache key let cache_key = self.cache_key(seq1, seq2, algorithm); // Check cache if let Ok(mut cache) = self.cache.lock() { if let Some(result) = cache.get(&cache_key) { self.record_cache_hit(&cache_key); return Ok(result.clone()); } } self.record_cache_miss(&cache_key); // Compute comparison let result = match algorithm { ComparisonAlgorithm::DTW => self.dtw(seq1, seq2), ComparisonAlgorithm::LCS => self.lcs(seq1, seq2), ComparisonAlgorithm::EditDistance => self.edit_distance(seq1, seq2), ComparisonAlgorithm::Euclidean => self.euclidean(seq1, seq2), }?; // Store in cache if let Ok(mut cache) = self.cache.lock() { cache.put(cache_key, result.clone()); } Ok(result) } /// Dynamic Time Warping implementation fn dtw(&self, seq1: &Sequence, seq2: &Sequence) -> Result { let n = seq1.len(); let m = seq2.len(); if n == 0 || m == 0 { return Ok(ComparisonResult { distance: (n + m) as f64, algorithm: ComparisonAlgorithm::DTW, alignment: None, }); } // Initialize DTW matrix let mut dtw = vec![vec![f64::INFINITY; m + 1]; n + 1]; dtw[0][0] = 0.0; // Fill DTW matrix for i in 1..=n { for j in 1..=m { let cost = if seq1.elements[i-1].value == seq2.elements[j-1].value { 0.0 } else { 1.0 }; dtw[i][j] = cost + dtw[i-1][j-1].min(dtw[i-1][j]).min(dtw[i][j-1]); } } // Backtrack for alignment let mut alignment = Vec::new(); let (mut i, mut j) = (n, m); while i > 0 && j > 0 { alignment.push((i - 1, j - 1)); let min_val = dtw[i-1][j-1].min(dtw[i-1][j]).min(dtw[i][j-1]); if dtw[i-1][j-1] == min_val { i -= 1; j -= 1; } else if dtw[i-1][j] == min_val { i -= 1; } else { j -= 1; } } alignment.reverse(); Ok(ComparisonResult { distance: dtw[n][m], algorithm: ComparisonAlgorithm::DTW, alignment: Some(alignment), }) } /// Longest Common Subsequence implementation fn lcs(&self, seq1: &Sequence, seq2: &Sequence) -> Result { let n = seq1.len(); let m = seq2.len(); let mut dp = vec![vec![0; m + 1]; n + 1]; for i in 1..=n { for j in 1..=m { if seq1.elements[i-1].value == seq2.elements[j-1].value { dp[i][j] = dp[i-1][j-1] + 1; } else { dp[i][j] = dp[i-1][j].max(dp[i][j-1]); } } } let lcs_length = dp[n][m]; let distance = (n + m - 2 * lcs_length) as f64; Ok(ComparisonResult { distance, algorithm: ComparisonAlgorithm::LCS, alignment: None, }) } /// Edit Distance (Levenshtein) implementation fn edit_distance(&self, seq1: &Sequence, seq2: &Sequence) -> Result { let n = seq1.len(); let m = seq2.len(); let mut dp = vec![vec![0; m + 1]; n + 1]; for i in 0..=n { dp[i][0] = i; } for j in 0..=m { dp[0][j] = j; } for i in 1..=n { for j in 1..=m { let cost = if seq1.elements[i-1].value == seq2.elements[j-1].value { 0 } else { 1 }; dp[i][j] = (dp[i-1][j] + 1) .min(dp[i][j-1] + 1) .min(dp[i-1][j-1] + cost); } } Ok(ComparisonResult { distance: dp[n][m] as f64, algorithm: ComparisonAlgorithm::EditDistance, alignment: None, }) } /// Euclidean distance (for numeric sequences) fn euclidean(&self, seq1: &Sequence, seq2: &Sequence) -> Result { let n = seq1.len().min(seq2.len()); let mut sum: f64 = 0.0; for i in 0..n { // Simplified: just count mismatches if seq1.elements[i].value != seq2.elements[i].value { sum += 1.0; } } Ok(ComparisonResult { distance: sum.sqrt(), // f64 type is now explicit from declaration algorithm: ComparisonAlgorithm::Euclidean, alignment: None, }) } /// Generate cache key for a comparison fn cache_key(&self, seq1: &Sequence, seq2: &Sequence, algorithm: ComparisonAlgorithm) -> String { format!( "{:?}:{:?}:{:?}", seq1.elements.len(), seq2.elements.len(), algorithm ) } fn record_cache_hit(&self, key: &str) { self.cache_hits.entry(key.to_string()) .and_modify(|v| *v += 1) .or_insert(1); } fn record_cache_miss(&self, key: &str) { self.cache_misses.entry(key.to_string()) .and_modify(|v| *v += 1) .or_insert(1); } /// Get cache statistics pub fn cache_stats(&self) -> CacheStats { let hits: u64 = self.cache_hits.iter().map(|r| *r.value()).sum(); let misses: u64 = self.cache_misses.iter().map(|r| *r.value()).sum(); let (size, capacity) = if let Ok(cache) = self.cache.lock() { (cache.len(), cache.cap().get()) } else { (0, 0) }; CacheStats { hits, misses, size, capacity, } } /// Clear the cache pub fn clear_cache(&self) { if let Ok(mut cache) = self.cache.lock() { cache.clear(); } if let Ok(mut cache) = self.pattern_cache.lock() { cache.clear(); } if let Ok(mut cache) = self.similarity_cache.lock() { cache.clear(); } self.cache_hits.clear(); self.cache_misses.clear(); } /// Find similar sequences within a haystack using generic types pub fn find_similar_generic( &self, haystack: &[T], needle: &[T], threshold: f64, ) -> Result, TemporalError> { if needle.is_empty() || haystack.len() < needle.len() { return Ok(Vec::new()); } // Generate cache key let cache_key = format!( "similar:{:?}:{:?}:{}", haystack.len(), needle.len(), threshold ); // Check cache if let Ok(mut cache) = self.similarity_cache.lock() { if let Some(results) = cache.get(&cache_key) { self.record_cache_hit(&cache_key); return Ok(results.clone()); } } self.record_cache_miss(&cache_key); let needle_len = needle.len(); let mut matches = Vec::new(); // Sliding window approach for start_idx in 0..=(haystack.len() - needle_len) { let window = &haystack[start_idx..start_idx + needle_len]; // Convert to Sequence for comparison let mut seq1 = Sequence::new(); for (i, item) in window.iter().enumerate() { seq1.push(item.clone(), i as u64); } let mut seq2 = Sequence::new(); for (i, item) in needle.iter().enumerate() { seq2.push(item.clone(), i as u64); } // Compute DTW distance if let Ok(result) = self.dtw(&seq1, &seq2) { // Normalize distance by pattern length let normalized_distance = result.distance / needle_len as f64; if normalized_distance <= threshold { matches.push(SimilarityMatch::new(start_idx, result.distance)); } } } // Sort by distance (best matches first) matches.sort_by(|a, b| { a.distance .partial_cmp(&b.distance) .unwrap_or(std::cmp::Ordering::Equal) }); // Store in cache if let Ok(mut cache) = self.similarity_cache.lock() { cache.put(cache_key, matches.clone()); } Ok(matches) } /// Detect recurring patterns in a sequence pub fn detect_recurring_patterns( &self, sequence: &[T], min_length: usize, max_length: usize, ) -> Result>, TemporalError> { if min_length > max_length { return Err(TemporalError::InvalidPatternLength(min_length, max_length)); } if sequence.len() < min_length { return Ok(Vec::new()); } // Generate cache key let cache_key = format!( "patterns:{:?}:{}:{}", sequence.len(), min_length, max_length ); // Check cache if let Ok(mut cache) = self.pattern_cache.lock() { if let Some(patterns) = cache.get(&cache_key) { self.record_cache_hit(&cache_key); return Ok(patterns.clone()); } } self.record_cache_miss(&cache_key); let mut pattern_map: HashMap, Vec> = HashMap::new(); // Search for patterns of each length for pattern_len in min_length..=max_length.min(sequence.len()) { for start_idx in 0..=(sequence.len() - pattern_len) { let pattern_seq = sequence[start_idx..start_idx + pattern_len].to_vec(); pattern_map .entry(pattern_seq) .or_insert_with(Vec::new) .push(start_idx); } } // Filter patterns that occur at least twice let mut patterns: Vec> = pattern_map .into_iter() .filter(|(_, occurrences)| occurrences.len() >= 2) .map(|(seq, occurrences)| { // Calculate confidence based on frequency and pattern length let frequency = occurrences.len() as f64; let pattern_len = seq.len() as f64; let total_possible = (sequence.len() - seq.len() + 1) as f64; // Confidence is weighted by frequency and pattern length let confidence = ((frequency / total_possible) * (pattern_len / max_length as f64)) .min(1.0); Pattern::new(seq, occurrences, confidence) }) .collect(); // Sort by frequency (most common first), then by confidence patterns.sort_by(|a, b| { b.frequency() .cmp(&a.frequency()) .then_with(|| { b.confidence .partial_cmp(&a.confidence) .unwrap_or(std::cmp::Ordering::Equal) }) }); // Store in cache if let Ok(mut cache) = self.pattern_cache.lock() { cache.put(cache_key, patterns.clone()); } Ok(patterns) } } impl Default for TemporalComparator where T: Clone + PartialEq + fmt::Debug + Serialize + Hash + Eq, { fn default() -> Self { Self::new(1000, 10000) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_sequence_creation() { let mut seq: Sequence = Sequence::new(); seq.push(1, 100); seq.push(2, 200); assert_eq!(seq.len(), 2); assert!(!seq.is_empty()); } #[test] fn test_dtw() { let comparator = TemporalComparator::new(100, 1000); let mut seq1: Sequence = Sequence::new(); seq1.push(1, 100); seq1.push(2, 200); seq1.push(3, 300); let mut seq2: Sequence = Sequence::new(); seq2.push(1, 100); seq2.push(2, 200); seq2.push(3, 300); let result = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW).unwrap(); assert_eq!(result.distance, 0.0); } #[test] fn test_cache() { let comparator = TemporalComparator::new(100, 1000); let mut seq1: Sequence = Sequence::new(); seq1.push(1, 1); seq1.push(2, 2); let mut seq2: Sequence = Sequence::new(); seq2.push(1, 1); seq2.push(2, 2); // First comparison - cache miss comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW).unwrap(); // Second comparison - cache hit comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW).unwrap(); let stats = comparator.cache_stats(); assert_eq!(stats.hits, 1); assert_eq!(stats.misses, 1); } #[test] fn test_find_similar_generic_integers() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); let haystack = vec![1, 2, 3, 4, 5, 3, 4, 5]; let needle = vec![3, 4, 5]; let matches = comparator.find_similar_generic(&haystack, &needle, 0.1).unwrap(); assert_eq!(matches.len(), 2); assert_eq!(matches[0].start_index, 2); assert_eq!(matches[1].start_index, 5); assert!(matches[0].similarity > 0.9); // High similarity for exact match } #[test] fn test_detect_recurring_patterns_simple() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); let sequence = vec!['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']; let patterns = comparator.detect_recurring_patterns(&sequence, 2, 4).unwrap(); assert!(!patterns.is_empty()); // Should find 'abc' pattern recurring let abc_pattern = patterns.iter().find(|p| p.sequence == vec!['a', 'b', 'c']); assert!(abc_pattern.is_some()); let pattern = abc_pattern.unwrap(); assert_eq!(pattern.frequency(), 3); assert!(pattern.confidence > 0.0); } }