scirs2_spatial/neuromorphic/algorithms/
processing.rs1use crate::error::{SpatialError, SpatialResult};
8use scirs2_core::ndarray::{Array2, ArrayView2};
9use scirs2_core::random::Rng;
10use std::collections::{HashMap, VecDeque};
11
12use super::super::core::SpikeEvent;
14
15#[derive(Debug, Clone)]
50pub struct NeuromorphicProcessor {
51 memristive_crossbar: bool,
53 temporal_coding: bool,
55 crossbar_size: (usize, usize),
57 conductances: Array2<f64>,
59 event_pipeline: VecDeque<SpikeEvent>,
61 max_pipeline_length: usize,
63 crossbar_threshold: f64,
65 memristive_learning_rate: f64,
67}
68
69impl Default for NeuromorphicProcessor {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl NeuromorphicProcessor {
76 pub fn new() -> Self {
81 Self {
82 memristive_crossbar: false,
83 temporal_coding: false,
84 crossbar_size: (64, 64),
85 conductances: Array2::zeros((64, 64)),
86 event_pipeline: VecDeque::new(),
87 max_pipeline_length: 1000,
88 crossbar_threshold: 0.5,
89 memristive_learning_rate: 0.001,
90 }
91 }
92
93 pub fn with_memristive_crossbar(mut self, enabled: bool) -> Self {
101 self.memristive_crossbar = enabled;
102 if enabled {
103 self.initialize_crossbar();
104 }
105 self
106 }
107
108 pub fn with_temporal_coding(mut self, enabled: bool) -> Self {
116 self.temporal_coding = enabled;
117 self
118 }
119
120 pub fn with_crossbar_size(mut self, rows: usize, cols: usize) -> Self {
129 self.crossbar_size = (rows, cols);
130 self.conductances = Array2::zeros((rows, cols));
131 if self.memristive_crossbar {
132 self.initialize_crossbar();
133 }
134 self
135 }
136
137 pub fn with_processing_params(
144 mut self,
145 max_pipeline_length: usize,
146 crossbar_threshold: f64,
147 learning_rate: f64,
148 ) -> Self {
149 self.max_pipeline_length = max_pipeline_length;
150 self.crossbar_threshold = crossbar_threshold;
151 self.memristive_learning_rate = learning_rate;
152 self
153 }
154
155 pub fn encode_spatial_events(
167 &self,
168 points: &ArrayView2<'_, f64>,
169 ) -> SpatialResult<Vec<SpikeEvent>> {
170 let (n_points, n_dims) = points.dim();
171 let mut events = Vec::new();
172
173 if n_points == 0 || n_dims == 0 {
174 return Ok(events);
175 }
176
177 for (point_idx, point) in points.outer_iter().enumerate() {
178 for (dim, &coord) in point.iter().enumerate() {
179 let normalized_coord = (coord + 10.0) / 20.0; let normalized_coord = normalized_coord.clamp(0.0, 1.0);
182
183 if self.temporal_coding {
184 let spike_time = normalized_coord * 100.0; let event =
187 SpikeEvent::new(point_idx * n_dims + dim, spike_time, 1.0, point.to_vec());
188 events.push(event);
189 } else {
190 let spike_rate = normalized_coord * 50.0; let num_spikes = spike_rate as usize;
193
194 for spike_idx in 0..num_spikes {
195 let spike_time = if spike_rate > 0.0 {
196 (spike_idx as f64) * (1.0 / spike_rate)
197 } else {
198 0.0
199 };
200 let event = SpikeEvent::new(
201 point_idx * n_dims + dim,
202 spike_time,
203 1.0,
204 point.to_vec(),
205 );
206 events.push(event);
207 }
208 }
209 }
210 }
211
212 events.sort_by(|a, b| a.timestamp().partial_cmp(&b.timestamp()).unwrap());
214
215 Ok(events)
216 }
217
218 pub fn process_events(&mut self, events: &[SpikeEvent]) -> SpatialResult<Vec<SpikeEvent>> {
230 let mut processed_events = Vec::new();
231
232 for event in events {
233 self.event_pipeline.push_back(event.clone());
234
235 if self.memristive_crossbar {
237 let crossbar_output = self.process_through_crossbar(event)?;
238 processed_events.extend(crossbar_output);
239 } else {
240 processed_events.push(event.clone());
241 }
242
243 if self.temporal_coding {
245 Self::apply_temporal_dynamics(&mut processed_events)?;
246 }
247
248 if self.event_pipeline.len() > self.max_pipeline_length {
250 self.event_pipeline.pop_front();
251 }
252 }
253
254 Ok(processed_events)
255 }
256
257 fn initialize_crossbar(&mut self) {
262 let (rows, cols) = self.crossbar_size;
263 let mut rng = scirs2_core::random::rng();
264
265 for i in 0..rows {
267 for j in 0..cols {
268 self.conductances[[i, j]] = 0.1 + rng.gen_range(0.0..0.9);
270 }
271 }
272 }
273
274 fn process_through_crossbar(&mut self, event: &SpikeEvent) -> SpatialResult<Vec<SpikeEvent>> {
280 let (rows, cols) = self.crossbar_size;
281 let mut output_events = Vec::new();
282
283 let input_row = event.neuron_id() % rows;
285
286 for col in 0..cols {
288 let conductance = self.conductances[[input_row, col]];
289 let output_current = event.amplitude() * conductance;
290
291 if output_current > self.crossbar_threshold {
293 let output_event = SpikeEvent::new(
294 rows + col, event.timestamp() + 0.1, output_current,
297 event.spatial_coords().to_vec(),
298 );
299 output_events.push(output_event);
300
301 self.update_memristive_device(input_row, col, event.amplitude())?;
303 }
304 }
305
306 Ok(output_events)
307 }
308
309 fn update_memristive_device(
314 &mut self,
315 row: usize,
316 col: usize,
317 spike_amplitude: f64,
318 ) -> SpatialResult<()> {
319 let current_conductance = self.conductances[[row, col]];
320
321 let conductance_change =
323 self.memristive_learning_rate * spike_amplitude * (1.0 - current_conductance);
324
325 self.conductances[[row, col]] += conductance_change;
326 self.conductances[[row, col]] = self.conductances[[row, col]].clamp(0.0, 1.0);
327
328 Ok(())
329 }
330
331 fn apply_temporal_dynamics(events: &mut Vec<SpikeEvent>) -> SpatialResult<()> {
336 let mut filtered_events = Vec::new();
338
339 for (i, event) in events.iter().enumerate() {
340 let mut should_include = true;
341 let mut modified_event = event.clone();
342
343 for other_event in events.iter().skip(i + 1) {
345 let time_diff = (other_event.timestamp() - event.timestamp()).abs();
346
347 if time_diff < 5.0 {
348 let new_amplitude = modified_event.amplitude() * 1.1;
351 modified_event = SpikeEvent::new(
352 modified_event.neuron_id(),
353 modified_event.timestamp(),
354 new_amplitude,
355 modified_event.spatial_coords().to_vec(),
356 );
357
358 if time_diff < 1.0 {
360 let enhanced_amplitude = modified_event.amplitude() * 1.5;
361 modified_event = SpikeEvent::new(
362 modified_event.neuron_id(),
363 modified_event.timestamp(),
364 enhanced_amplitude,
365 modified_event.spatial_coords().to_vec(),
366 );
367 }
368 }
369
370 if time_diff < 0.5 && event.neuron_id() == other_event.neuron_id() {
372 should_include = false; break;
374 }
375 }
376
377 if should_include {
378 filtered_events.push(modified_event);
379 }
380 }
381
382 *events = filtered_events;
383 Ok(())
384 }
385
386 pub fn get_crossbar_statistics(&self) -> HashMap<String, f64> {
394 let mut stats = HashMap::new();
395
396 if self.memristive_crossbar {
397 let total_conductance: f64 = self.conductances.sum();
398 let avg_conductance =
399 total_conductance / (self.crossbar_size.0 * self.crossbar_size.1) as f64;
400 let max_conductance = self.conductances.fold(0.0f64, |acc, &x| acc.max(x));
401 let min_conductance = self.conductances.fold(1.0f64, |acc, &x| acc.min(x));
402
403 stats.insert("total_conductance".to_string(), total_conductance);
404 stats.insert("avg_conductance".to_string(), avg_conductance);
405 stats.insert("max_conductance".to_string(), max_conductance);
406 stats.insert("min_conductance".to_string(), min_conductance);
407 }
408
409 stats.insert(
410 "event_pipeline_length".to_string(),
411 self.event_pipeline.len() as f64,
412 );
413 stats
414 }
415
416 pub fn crossbar_size(&self) -> (usize, usize) {
418 self.crossbar_size
419 }
420
421 pub fn is_memristive_enabled(&self) -> bool {
423 self.memristive_crossbar
424 }
425
426 pub fn is_temporal_coding_enabled(&self) -> bool {
428 self.temporal_coding
429 }
430
431 pub fn crossbar_threshold(&self) -> f64 {
433 self.crossbar_threshold
434 }
435
436 pub fn learning_rate(&self) -> f64 {
438 self.memristive_learning_rate
439 }
440
441 pub fn pipeline_length(&self) -> usize {
443 self.event_pipeline.len()
444 }
445
446 pub fn clear_pipeline(&mut self) {
448 self.event_pipeline.clear();
449 }
450
451 pub fn reset_crossbar(&mut self) {
453 if self.memristive_crossbar {
454 self.initialize_crossbar();
455 }
456 }
457
458 pub fn conductance_matrix(&self) -> &Array2<f64> {
460 &self.conductances
461 }
462
463 pub fn set_conductance(&mut self, row: usize, col: usize, value: f64) -> SpatialResult<()> {
465 let (rows, cols) = self.crossbar_size;
466 if row >= rows || col >= cols {
467 return Err(SpatialError::InvalidInput(
468 "Crossbar indices out of bounds".to_string(),
469 ));
470 }
471
472 self.conductances[[row, col]] = value.clamp(0.0, 1.0);
473 Ok(())
474 }
475
476 pub fn get_conductance(&self, row: usize, col: usize) -> Option<f64> {
478 let (rows, cols) = self.crossbar_size;
479 if row >= rows || col >= cols {
480 None
481 } else {
482 Some(self.conductances[[row, col]])
483 }
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use scirs2_core::ndarray::Array2;
491
492 #[test]
493 fn test_processor_creation() {
494 let processor = NeuromorphicProcessor::new();
495 assert!(!processor.is_memristive_enabled());
496 assert!(!processor.is_temporal_coding_enabled());
497 assert_eq!(processor.crossbar_size(), (64, 64));
498 assert_eq!(processor.pipeline_length(), 0);
499 }
500
501 #[test]
502 fn test_processor_configuration() {
503 let processor = NeuromorphicProcessor::new()
504 .with_memristive_crossbar(true)
505 .with_temporal_coding(true)
506 .with_crossbar_size(32, 32)
507 .with_processing_params(500, 0.7, 0.01);
508
509 assert!(processor.is_memristive_enabled());
510 assert!(processor.is_temporal_coding_enabled());
511 assert_eq!(processor.crossbar_size(), (32, 32));
512 assert_eq!(processor.crossbar_threshold(), 0.7);
513 assert_eq!(processor.learning_rate(), 0.01);
514 assert_eq!(processor.max_pipeline_length, 500);
515 }
516
517 #[test]
518 fn test_spatial_event_encoding() {
519 let points = Array2::from_shape_vec((2, 2), vec![0.0, 0.0, 1.0, 1.0]).unwrap();
520 let processor = NeuromorphicProcessor::new();
521
522 let events = processor.encode_spatial_events(&points.view()).unwrap();
523
524 assert!(!events.is_empty());
526
527 for i in 1..events.len() {
529 assert!(events[i - 1].timestamp() <= events[i].timestamp());
530 }
531 }
532
533 #[test]
534 fn test_temporal_vs_rate_coding() {
535 let points = Array2::from_shape_vec((1, 2), vec![1.0, 2.0]).unwrap();
536
537 let processor_rate = NeuromorphicProcessor::new().with_temporal_coding(false);
539 let events_rate = processor_rate
540 .encode_spatial_events(&points.view())
541 .unwrap();
542
543 let processor_temporal = NeuromorphicProcessor::new().with_temporal_coding(true);
545 let events_temporal = processor_temporal
546 .encode_spatial_events(&points.view())
547 .unwrap();
548
549 assert!(events_temporal.len() <= events_rate.len());
552 }
553
554 #[test]
555 fn test_event_processing() {
556 let points = Array2::from_shape_vec((2, 2), vec![0.0, 0.0, 1.0, 1.0]).unwrap();
557 let mut processor = NeuromorphicProcessor::new()
558 .with_memristive_crossbar(false)
559 .with_temporal_coding(false);
560
561 let events = processor.encode_spatial_events(&points.view()).unwrap();
562 let processed_events = processor.process_events(&events).unwrap();
563
564 assert_eq!(events.len(), processed_events.len());
566 assert!(processor.pipeline_length() > 0);
567 }
568
569 #[test]
570 #[ignore]
571 fn test_memristive_crossbar() {
572 let points = Array2::from_shape_vec((1, 1), vec![1.0]).unwrap();
573 let mut processor = NeuromorphicProcessor::new()
574 .with_memristive_crossbar(true)
575 .with_crossbar_size(4, 4);
576
577 let events = processor.encode_spatial_events(&points.view()).unwrap();
578 let processed_events = processor.process_events(&events).unwrap();
579
580 assert!(!processed_events.is_empty());
582
583 let stats = processor.get_crossbar_statistics();
584 assert!(stats.contains_key("avg_conductance"));
585 assert!(stats.contains_key("max_conductance"));
586 }
587
588 #[test]
589 fn test_conductance_operations() {
590 let mut processor = NeuromorphicProcessor::new()
591 .with_memristive_crossbar(true)
592 .with_crossbar_size(4, 4);
593
594 processor.set_conductance(0, 0, 0.8).unwrap();
596 assert_eq!(processor.get_conductance(0, 0), Some(0.8));
597
598 assert!(processor.set_conductance(10, 10, 0.5).is_err());
600 assert_eq!(processor.get_conductance(10, 10), None);
601
602 processor.set_conductance(1, 1, 2.0).unwrap(); assert_eq!(processor.get_conductance(1, 1), Some(1.0));
605 }
606
607 #[test]
608 fn test_processor_reset() {
609 let mut processor = NeuromorphicProcessor::new().with_memristive_crossbar(true);
610
611 let points = Array2::from_shape_vec((1, 1), vec![1.0]).unwrap();
613 let events = processor.encode_spatial_events(&points.view()).unwrap();
614 processor.process_events(&events).unwrap();
615
616 assert!(processor.pipeline_length() > 0);
617
618 processor.clear_pipeline();
620 assert_eq!(processor.pipeline_length(), 0);
621
622 let initial_stats = processor.get_crossbar_statistics();
624 processor.reset_crossbar();
625 let reset_stats = processor.get_crossbar_statistics();
626
627 assert!(initial_stats.contains_key("avg_conductance"));
629 assert!(reset_stats.contains_key("avg_conductance"));
630 }
631
632 #[test]
633 fn test_empty_input() {
634 let points = Array2::zeros((0, 2));
635 let processor = NeuromorphicProcessor::new();
636
637 let events = processor.encode_spatial_events(&points.view()).unwrap();
638 assert!(events.is_empty());
639 }
640}