scirs2_metrics/domains/neuromorphic/
pattern_recognition.rs1#![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#[derive(Debug)]
15pub struct SpikePatternRecognizer<F: Float> {
16 pub pattern_templates: Vec<SpikePattern<F>>,
18 pub thresholds: HashMap<String, F>,
20 pub matching_algorithms: Vec<PatternMatchingAlgorithm>,
22 pub recognition_history: VecDeque<PatternRecognition<F>>,
24}
25
26#[derive(Debug, Clone)]
28pub struct SpikePattern<F: Float> {
29 pub name: String,
31 pub spatial_pattern: Vec<usize>,
33 pub temporal_pattern: Vec<Duration>,
35 pub strength: F,
37 pub tolerance: F,
39}
40
41#[derive(Debug, Clone)]
43pub enum PatternMatchingAlgorithm {
44 CrossCorrelation,
46 DynamicTimeWarping,
48 HiddenMarkov,
50 NeuralClassifier,
52 TemplateMatching,
54}
55
56#[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 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 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 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 if self.recognition_history.len() > 1000 {
102 self.recognition_history.pop_front();
103 }
104
105 Ok(recognitions)
106 }
107
108 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"), };
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 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 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 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 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 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 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#[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}