Skip to main content

scirs2_metrics/domains/neuromorphic/
pattern_recognition.rs

1//! Spike pattern recognition for neuromorphic systems
2//!
3//! This module provides pattern recognition capabilities for detecting
4//! and analyzing spike patterns in neuromorphic networks.
5
6#![allow(clippy::too_many_arguments)]
7#![allow(dead_code)]
8
9use scirs2_core::numeric::Float;
10use std::collections::{HashMap, VecDeque};
11use std::time::{Duration, Instant};
12
13/// Spike pattern recognizer
14#[derive(Debug)]
15pub struct SpikePatternRecognizer<F: Float> {
16    /// Pattern templates
17    pub pattern_templates: Vec<SpikePattern<F>>,
18    /// Recognition thresholds
19    pub thresholds: HashMap<String, F>,
20    /// Pattern matching algorithms
21    pub matching_algorithms: Vec<PatternMatchingAlgorithm>,
22    /// Recognition history
23    pub recognition_history: VecDeque<PatternRecognition<F>>,
24}
25
26/// Spike pattern template
27#[derive(Debug, Clone)]
28pub struct SpikePattern<F: Float> {
29    /// Pattern name
30    pub name: String,
31    /// Spatial pattern (which neurons)
32    pub spatial_pattern: Vec<usize>,
33    /// Temporal pattern (spike timings)
34    pub temporal_pattern: Vec<Duration>,
35    /// Pattern strength
36    pub strength: F,
37    /// Variability tolerance
38    pub tolerance: F,
39}
40
41/// Pattern matching algorithms
42#[derive(Debug, Clone)]
43pub enum PatternMatchingAlgorithm {
44    /// Cross-correlation based
45    CrossCorrelation,
46    /// Dynamic time warping
47    DynamicTimeWarping,
48    /// Hidden Markov models
49    HiddenMarkov,
50    /// Neural network classifier
51    NeuralClassifier,
52    /// Template matching
53    TemplateMatching,
54}
55
56/// Pattern recognition result
57#[derive(Debug, Clone)]
58pub struct PatternRecognition<F: Float> {
59    pub timestamp: Instant,
60    pub pattern_name: String,
61    pub confidence: F,
62    pub matching_neurons: Vec<usize>,
63    pub temporal_offset: Duration,
64}
65
66impl<F: Float + std::iter::Sum> SpikePatternRecognizer<F> {
67    /// Create new pattern recognizer
68    pub fn new() -> Self {
69        Self {
70            pattern_templates: Vec::new(),
71            thresholds: HashMap::new(),
72            matching_algorithms: vec![PatternMatchingAlgorithm::CrossCorrelation],
73            recognition_history: VecDeque::new(),
74        }
75    }
76
77    /// Add pattern template
78    pub fn add_pattern(&mut self, pattern: SpikePattern<F>) {
79        self.thresholds.insert(
80            pattern.name.clone(),
81            F::from(0.8).expect("Failed to convert constant to float"),
82        );
83        self.pattern_templates.push(pattern);
84    }
85
86    /// Recognize patterns in spike data
87    pub fn recognize_patterns(
88        &mut self,
89        spike_data: &HashMap<usize, Vec<Instant>>,
90    ) -> crate::error::Result<Vec<PatternRecognition<F>>> {
91        let mut recognitions = Vec::new();
92
93        for pattern in &self.pattern_templates {
94            if let Some(recognition) = self.match_pattern(pattern, spike_data)? {
95                recognitions.push(recognition.clone());
96                self.recognition_history.push_back(recognition);
97            }
98        }
99
100        // Keep bounded
101        if self.recognition_history.len() > 1000 {
102            self.recognition_history.pop_front();
103        }
104
105        Ok(recognitions)
106    }
107
108    /// Match a specific pattern
109    fn match_pattern(
110        &self,
111        pattern: &SpikePattern<F>,
112        spike_data: &HashMap<usize, Vec<Instant>>,
113    ) -> crate::error::Result<Option<PatternRecognition<F>>> {
114        let threshold = self
115            .thresholds
116            .get(&pattern.name)
117            .copied()
118            .unwrap_or(F::from(0.8).expect("Failed to convert constant to float"));
119
120        for algorithm in &self.matching_algorithms {
121            let confidence = match algorithm {
122                PatternMatchingAlgorithm::CrossCorrelation => {
123                    self.cross_correlation_match(pattern, spike_data)?
124                }
125                PatternMatchingAlgorithm::TemplateMatching => {
126                    self.template_match(pattern, spike_data)?
127                }
128                _ => F::from(0.5).expect("Failed to convert constant to float"), // Default confidence
129            };
130
131            if confidence >= threshold {
132                return Ok(Some(PatternRecognition {
133                    timestamp: Instant::now(),
134                    pattern_name: pattern.name.clone(),
135                    confidence,
136                    matching_neurons: pattern.spatial_pattern.clone(),
137                    temporal_offset: Duration::from_millis(0),
138                }));
139            }
140        }
141
142        Ok(None)
143    }
144
145    /// Cross-correlation based pattern matching
146    fn cross_correlation_match(
147        &self,
148        pattern: &SpikePattern<F>,
149        spike_data: &HashMap<usize, Vec<Instant>>,
150    ) -> crate::error::Result<F> {
151        let mut correlation_sum = F::zero();
152        let mut count = 0;
153
154        for &neuron_id in &pattern.spatial_pattern {
155            if let Some(spikes) = spike_data.get(&neuron_id) {
156                if !spikes.is_empty() {
157                    correlation_sum = correlation_sum + F::one();
158                }
159                count += 1;
160            }
161        }
162
163        if count > 0 {
164            Ok(correlation_sum / F::from(count).expect("Failed to convert to float"))
165        } else {
166            Ok(F::zero())
167        }
168    }
169
170    /// Template-based pattern matching
171    fn template_match(
172        &self,
173        pattern: &SpikePattern<F>,
174        spike_data: &HashMap<usize, Vec<Instant>>,
175    ) -> crate::error::Result<F> {
176        let mut matched_neurons = 0;
177        let total_neurons = pattern.spatial_pattern.len();
178
179        for &neuron_id in &pattern.spatial_pattern {
180            if let Some(spikes) = spike_data.get(&neuron_id) {
181                if !spikes.is_empty() {
182                    matched_neurons += 1;
183                }
184            }
185        }
186
187        if total_neurons > 0 {
188            Ok(
189                F::from(matched_neurons).expect("Failed to convert to float")
190                    / F::from(total_neurons).expect("Failed to convert to float"),
191            )
192        } else {
193            Ok(F::zero())
194        }
195    }
196
197    /// Get recognition statistics
198    pub fn get_recognition_stats(&self) -> RecognitionStats<F> {
199        let total_recognitions = self.recognition_history.len();
200        let recent_recognitions = self.recognition_history.iter().rev().take(10).count();
201
202        let avg_confidence = if !self.recognition_history.is_empty() {
203            self.recognition_history
204                .iter()
205                .map(|r| r.confidence)
206                .sum::<F>()
207                / F::from(self.recognition_history.len()).expect("Operation failed")
208        } else {
209            F::zero()
210        };
211
212        RecognitionStats {
213            total_recognitions,
214            recent_recognitions,
215            average_confidence: avg_confidence,
216            pattern_counts: self.get_pattern_counts(),
217        }
218    }
219
220    /// Get pattern recognition counts
221    fn get_pattern_counts(&self) -> HashMap<String, usize> {
222        let mut counts = HashMap::new();
223        for recognition in &self.recognition_history {
224            *counts.entry(recognition.pattern_name.clone()).or_insert(0) += 1;
225        }
226        counts
227    }
228}
229
230impl<F: Float> SpikePattern<F> {
231    /// Create new spike pattern
232    pub fn new(
233        name: String,
234        spatial_pattern: Vec<usize>,
235        temporal_pattern: Vec<Duration>,
236        strength: F,
237        tolerance: F,
238    ) -> Self {
239        Self {
240            name,
241            spatial_pattern,
242            temporal_pattern,
243            strength,
244            tolerance,
245        }
246    }
247
248    /// Check if pattern matches given spike data
249    pub fn matches(&self, spike_data: &HashMap<usize, Vec<Instant>>) -> bool {
250        let mut matched_count = 0;
251
252        for &neuron_id in &self.spatial_pattern {
253            if let Some(spikes) = spike_data.get(&neuron_id) {
254                if !spikes.is_empty() {
255                    matched_count += 1;
256                }
257            }
258        }
259
260        let match_ratio = matched_count as f64 / self.spatial_pattern.len() as f64;
261        match_ratio >= self.tolerance.to_f64().unwrap_or(0.8)
262    }
263}
264
265/// Recognition statistics
266#[derive(Debug)]
267pub struct RecognitionStats<F: Float> {
268    pub total_recognitions: usize,
269    pub recent_recognitions: usize,
270    pub average_confidence: F,
271    pub pattern_counts: HashMap<String, usize>,
272}