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| {
214 a.timestamp()
215 .partial_cmp(&b.timestamp())
216 .expect("Operation failed")
217 });
218
219 Ok(events)
220 }
221
222 pub fn process_events(&mut self, events: &[SpikeEvent]) -> SpatialResult<Vec<SpikeEvent>> {
234 let mut processed_events = Vec::new();
235
236 for event in events {
237 self.event_pipeline.push_back(event.clone());
238
239 if self.memristive_crossbar {
241 let crossbar_output = self.process_through_crossbar(event)?;
242 processed_events.extend(crossbar_output);
243 } else {
244 processed_events.push(event.clone());
245 }
246
247 if self.temporal_coding {
249 Self::apply_temporal_dynamics(&mut processed_events)?;
250 }
251
252 if self.event_pipeline.len() > self.max_pipeline_length {
254 self.event_pipeline.pop_front();
255 }
256 }
257
258 Ok(processed_events)
259 }
260
261 fn initialize_crossbar(&mut self) {
266 let (rows, cols) = self.crossbar_size;
267 let mut rng = scirs2_core::random::rng();
268
269 for i in 0..rows {
271 for j in 0..cols {
272 self.conductances[[i, j]] = 0.1 + rng.gen_range(0.0..0.9);
274 }
275 }
276 }
277
278 fn process_through_crossbar(&mut self, event: &SpikeEvent) -> SpatialResult<Vec<SpikeEvent>> {
284 let (rows, cols) = self.crossbar_size;
285 let mut output_events = Vec::new();
286
287 let input_row = event.neuron_id() % rows;
289
290 for col in 0..cols {
292 let conductance = self.conductances[[input_row, col]];
293 let output_current = event.amplitude() * conductance;
294
295 if output_current > self.crossbar_threshold {
297 let output_event = SpikeEvent::new(
298 rows + col, event.timestamp() + 0.1, output_current,
301 event.spatial_coords().to_vec(),
302 );
303 output_events.push(output_event);
304
305 self.update_memristive_device(input_row, col, event.amplitude())?;
307 }
308 }
309
310 Ok(output_events)
311 }
312
313 fn update_memristive_device(
318 &mut self,
319 row: usize,
320 col: usize,
321 spike_amplitude: f64,
322 ) -> SpatialResult<()> {
323 let current_conductance = self.conductances[[row, col]];
324
325 let conductance_change =
327 self.memristive_learning_rate * spike_amplitude * (1.0 - current_conductance);
328
329 self.conductances[[row, col]] += conductance_change;
330 self.conductances[[row, col]] = self.conductances[[row, col]].clamp(0.0, 1.0);
331
332 Ok(())
333 }
334
335 fn apply_temporal_dynamics(events: &mut Vec<SpikeEvent>) -> SpatialResult<()> {
340 let mut filtered_events = Vec::new();
342
343 for (i, event) in events.iter().enumerate() {
344 let mut should_include = true;
345 let mut modified_event = event.clone();
346
347 for other_event in events.iter().skip(i + 1) {
349 let time_diff = (other_event.timestamp() - event.timestamp()).abs();
350
351 if time_diff < 5.0 {
352 let new_amplitude = modified_event.amplitude() * 1.1;
355 modified_event = SpikeEvent::new(
356 modified_event.neuron_id(),
357 modified_event.timestamp(),
358 new_amplitude,
359 modified_event.spatial_coords().to_vec(),
360 );
361
362 if time_diff < 1.0 {
364 let enhanced_amplitude = modified_event.amplitude() * 1.5;
365 modified_event = SpikeEvent::new(
366 modified_event.neuron_id(),
367 modified_event.timestamp(),
368 enhanced_amplitude,
369 modified_event.spatial_coords().to_vec(),
370 );
371 }
372 }
373
374 if time_diff < 0.5 && event.neuron_id() == other_event.neuron_id() {
376 should_include = false; break;
378 }
379 }
380
381 if should_include {
382 filtered_events.push(modified_event);
383 }
384 }
385
386 *events = filtered_events;
387 Ok(())
388 }
389
390 pub fn get_crossbar_statistics(&self) -> HashMap<String, f64> {
398 let mut stats = HashMap::new();
399
400 if self.memristive_crossbar {
401 let total_conductance: f64 = self.conductances.sum();
402 let avg_conductance =
403 total_conductance / (self.crossbar_size.0 * self.crossbar_size.1) as f64;
404 let max_conductance = self.conductances.fold(0.0f64, |acc, &x| acc.max(x));
405 let min_conductance = self.conductances.fold(1.0f64, |acc, &x| acc.min(x));
406
407 stats.insert("total_conductance".to_string(), total_conductance);
408 stats.insert("avg_conductance".to_string(), avg_conductance);
409 stats.insert("max_conductance".to_string(), max_conductance);
410 stats.insert("min_conductance".to_string(), min_conductance);
411 }
412
413 stats.insert(
414 "event_pipeline_length".to_string(),
415 self.event_pipeline.len() as f64,
416 );
417 stats
418 }
419
420 pub fn crossbar_size(&self) -> (usize, usize) {
422 self.crossbar_size
423 }
424
425 pub fn is_memristive_enabled(&self) -> bool {
427 self.memristive_crossbar
428 }
429
430 pub fn is_temporal_coding_enabled(&self) -> bool {
432 self.temporal_coding
433 }
434
435 pub fn crossbar_threshold(&self) -> f64 {
437 self.crossbar_threshold
438 }
439
440 pub fn learning_rate(&self) -> f64 {
442 self.memristive_learning_rate
443 }
444
445 pub fn pipeline_length(&self) -> usize {
447 self.event_pipeline.len()
448 }
449
450 pub fn clear_pipeline(&mut self) {
452 self.event_pipeline.clear();
453 }
454
455 pub fn reset_crossbar(&mut self) {
457 if self.memristive_crossbar {
458 self.initialize_crossbar();
459 }
460 }
461
462 pub fn conductance_matrix(&self) -> &Array2<f64> {
464 &self.conductances
465 }
466
467 pub fn set_conductance(&mut self, row: usize, col: usize, value: f64) -> SpatialResult<()> {
469 let (rows, cols) = self.crossbar_size;
470 if row >= rows || col >= cols {
471 return Err(SpatialError::InvalidInput(
472 "Crossbar indices out of bounds".to_string(),
473 ));
474 }
475
476 self.conductances[[row, col]] = value.clamp(0.0, 1.0);
477 Ok(())
478 }
479
480 pub fn get_conductance(&self, row: usize, col: usize) -> Option<f64> {
482 let (rows, cols) = self.crossbar_size;
483 if row >= rows || col >= cols {
484 None
485 } else {
486 Some(self.conductances[[row, col]])
487 }
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use scirs2_core::ndarray::Array2;
495
496 #[test]
497 fn test_processor_creation() {
498 let processor = NeuromorphicProcessor::new();
499 assert!(!processor.is_memristive_enabled());
500 assert!(!processor.is_temporal_coding_enabled());
501 assert_eq!(processor.crossbar_size(), (64, 64));
502 assert_eq!(processor.pipeline_length(), 0);
503 }
504
505 #[test]
506 fn test_processor_configuration() {
507 let processor = NeuromorphicProcessor::new()
508 .with_memristive_crossbar(true)
509 .with_temporal_coding(true)
510 .with_crossbar_size(32, 32)
511 .with_processing_params(500, 0.7, 0.01);
512
513 assert!(processor.is_memristive_enabled());
514 assert!(processor.is_temporal_coding_enabled());
515 assert_eq!(processor.crossbar_size(), (32, 32));
516 assert_eq!(processor.crossbar_threshold(), 0.7);
517 assert_eq!(processor.learning_rate(), 0.01);
518 assert_eq!(processor.max_pipeline_length, 500);
519 }
520
521 #[test]
522 fn test_spatial_event_encoding() {
523 let points =
524 Array2::from_shape_vec((2, 2), vec![0.0, 0.0, 1.0, 1.0]).expect("Operation failed");
525 let processor = NeuromorphicProcessor::new();
526
527 let events = processor
528 .encode_spatial_events(&points.view())
529 .expect("Operation failed");
530
531 assert!(!events.is_empty());
533
534 for i in 1..events.len() {
536 assert!(events[i - 1].timestamp() <= events[i].timestamp());
537 }
538 }
539
540 #[test]
541 fn test_temporal_vs_rate_coding() {
542 let points = Array2::from_shape_vec((1, 2), vec![1.0, 2.0]).expect("Operation failed");
543
544 let processor_rate = NeuromorphicProcessor::new().with_temporal_coding(false);
546 let events_rate = processor_rate
547 .encode_spatial_events(&points.view())
548 .expect("Operation failed");
549
550 let processor_temporal = NeuromorphicProcessor::new().with_temporal_coding(true);
552 let events_temporal = processor_temporal
553 .encode_spatial_events(&points.view())
554 .expect("Operation failed");
555
556 assert!(events_temporal.len() <= events_rate.len());
559 }
560
561 #[test]
562 fn test_event_processing() {
563 let points =
564 Array2::from_shape_vec((2, 2), vec![0.0, 0.0, 1.0, 1.0]).expect("Operation failed");
565 let mut processor = NeuromorphicProcessor::new()
566 .with_memristive_crossbar(false)
567 .with_temporal_coding(false);
568
569 let events = processor
570 .encode_spatial_events(&points.view())
571 .expect("Operation failed");
572 let processed_events = processor.process_events(&events).expect("Operation failed");
573
574 assert_eq!(events.len(), processed_events.len());
576 assert!(processor.pipeline_length() > 0);
577 }
578
579 #[test]
580 fn test_memristive_crossbar() {
581 let points = Array2::from_shape_vec((1, 1), vec![1.0]).expect("Operation failed");
582 let mut processor = NeuromorphicProcessor::new()
583 .with_memristive_crossbar(true)
584 .with_crossbar_size(4, 4)
585 .with_processing_params(1000, 0.2, 0.001); let events = processor
588 .encode_spatial_events(&points.view())
589 .expect("Operation failed");
590 let processed_events = processor.process_events(&events).expect("Operation failed");
591
592 assert!(!processed_events.is_empty());
595
596 let stats = processor.get_crossbar_statistics();
597 assert!(stats.contains_key("avg_conductance"));
598 assert!(stats.contains_key("max_conductance"));
599 }
600
601 #[test]
602 fn test_conductance_operations() {
603 let mut processor = NeuromorphicProcessor::new()
604 .with_memristive_crossbar(true)
605 .with_crossbar_size(4, 4);
606
607 processor
609 .set_conductance(0, 0, 0.8)
610 .expect("Operation failed");
611 assert_eq!(processor.get_conductance(0, 0), Some(0.8));
612
613 assert!(processor.set_conductance(10, 10, 0.5).is_err());
615 assert_eq!(processor.get_conductance(10, 10), None);
616
617 processor
619 .set_conductance(1, 1, 2.0)
620 .expect("Operation failed"); assert_eq!(processor.get_conductance(1, 1), Some(1.0));
622 }
623
624 #[test]
625 fn test_processor_reset() {
626 let mut processor = NeuromorphicProcessor::new().with_memristive_crossbar(true);
627
628 let points = Array2::from_shape_vec((1, 1), vec![1.0]).expect("Operation failed");
630 let events = processor
631 .encode_spatial_events(&points.view())
632 .expect("Operation failed");
633 processor.process_events(&events).expect("Operation failed");
634
635 assert!(processor.pipeline_length() > 0);
636
637 processor.clear_pipeline();
639 assert_eq!(processor.pipeline_length(), 0);
640
641 let initial_stats = processor.get_crossbar_statistics();
643 processor.reset_crossbar();
644 let reset_stats = processor.get_crossbar_statistics();
645
646 assert!(initial_stats.contains_key("avg_conductance"));
648 assert!(reset_stats.contains_key("avg_conductance"));
649 }
650
651 #[test]
652 fn test_empty_input() {
653 let points = Array2::zeros((0, 2));
654 let processor = NeuromorphicProcessor::new();
655
656 let events = processor
657 .encode_spatial_events(&points.view())
658 .expect("Operation failed");
659 assert!(events.is_empty());
660 }
661}