scirs2_spatial/neuromorphic/algorithms/
processing.rs

1//! Neuromorphic Event Processing
2//!
3//! This module implements general-purpose neuromorphic processing capabilities
4//! including event-driven computation, memristive crossbar arrays, and temporal
5//! dynamics for spatial data processing.
6
7use crate::error::{SpatialError, SpatialResult};
8use scirs2_core::ndarray::{Array2, ArrayView2};
9use scirs2_core::random::Rng;
10use std::collections::{HashMap, VecDeque};
11
12// Import core neuromorphic components
13use super::super::core::SpikeEvent;
14
15/// Neuromorphic processor for general spatial computations
16///
17/// This processor implements event-driven neuromorphic computing paradigms
18/// for spatial data processing. It supports memristive crossbar arrays for
19/// in-memory computation and temporal coding schemes for efficient information
20/// processing.
21///
22/// # Features
23/// - Event-driven processing pipeline
24/// - Memristive crossbar arrays for in-memory computation
25/// - Temporal and rate-based coding schemes
26/// - Spike timing dynamics and correlation detection
27/// - Configurable processing parameters
28///
29/// # Example
30/// ```rust
31/// use scirs2_core::ndarray::Array2;
32/// use scirs2_spatial::neuromorphic::algorithms::NeuromorphicProcessor;
33///
34/// let points = Array2::from_shape_vec((3, 2), vec![
35///     0.0, 0.0, 1.0, 1.0, 2.0, 2.0
36/// ]).unwrap();
37///
38/// let mut processor = NeuromorphicProcessor::new()
39///     .with_memristive_crossbar(true)
40///     .with_temporal_coding(true)
41///     .with_crossbar_size(64, 64);
42///
43/// // Encode spatial data as neuromorphic events
44/// let events = processor.encode_spatial_events(&points.view()).unwrap();
45///
46/// // Process events through neuromorphic pipeline
47/// let processed_events = processor.process_events(&events).unwrap();
48/// ```
49#[derive(Debug, Clone)]
50pub struct NeuromorphicProcessor {
51    /// Enable memristive crossbar arrays
52    memristive_crossbar: bool,
53    /// Enable temporal coding
54    temporal_coding: bool,
55    /// Crossbar array dimensions
56    crossbar_size: (usize, usize),
57    /// Memristive device conductances
58    conductances: Array2<f64>,
59    /// Event processing pipeline
60    event_pipeline: VecDeque<SpikeEvent>,
61    /// Maximum pipeline length
62    max_pipeline_length: usize,
63    /// Crossbar threshold for spike generation
64    crossbar_threshold: f64,
65    /// Memristive learning rate
66    memristive_learning_rate: f64,
67}
68
69impl Default for NeuromorphicProcessor {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl NeuromorphicProcessor {
76    /// Create new neuromorphic processor
77    ///
78    /// # Returns
79    /// A new `NeuromorphicProcessor` with default configuration
80    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    /// Enable memristive crossbar arrays
94    ///
95    /// When enabled, events are processed through a memristive crossbar array
96    /// that provides in-memory computation capabilities.
97    ///
98    /// # Arguments
99    /// * `enabled` - Whether to enable memristive crossbar processing
100    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    /// Enable temporal coding
109    ///
110    /// When enabled, spatial coordinates are encoded using spike timing
111    /// rather than spike rates.
112    ///
113    /// # Arguments
114    /// * `enabled` - Whether to enable temporal coding
115    pub fn with_temporal_coding(mut self, enabled: bool) -> Self {
116        self.temporal_coding = enabled;
117        self
118    }
119
120    /// Configure crossbar size
121    ///
122    /// Sets the dimensions of the memristive crossbar array used for
123    /// in-memory computation.
124    ///
125    /// # Arguments
126    /// * `rows` - Number of rows in crossbar array
127    /// * `cols` - Number of columns in crossbar array
128    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    /// Configure processing parameters
138    ///
139    /// # Arguments
140    /// * `max_pipeline_length` - Maximum length of event pipeline
141    /// * `crossbar_threshold` - Threshold for crossbar spike generation
142    /// * `learning_rate` - Learning rate for memristive adaptation
143    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    /// Encode spatial data as neuromorphic events
156    ///
157    /// Converts spatial data points into spike events using either temporal
158    /// coding (spike timing) or rate coding (spike frequency) depending on
159    /// the processor configuration.
160    ///
161    /// # Arguments
162    /// * `points` - Input spatial points (n_points × n_dims)
163    ///
164    /// # Returns
165    /// Vector of spike events representing the spatial data
166    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                // Temporal coding: encode coordinate as spike timing
180                let normalized_coord = (coord + 10.0) / 20.0; // Normalize to [0, 1]
181                let normalized_coord = normalized_coord.clamp(0.0, 1.0);
182
183                if self.temporal_coding {
184                    // Timing-based encoding
185                    let spike_time = normalized_coord * 100.0; // Map to [0, 100] time units
186                    let event =
187                        SpikeEvent::new(point_idx * n_dims + dim, spike_time, 1.0, point.to_vec());
188                    events.push(event);
189                } else {
190                    // Rate-based encoding
191                    let spike_rate = normalized_coord * 50.0; // Max 50 Hz
192                    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        // Sort events by timestamp
213        events.sort_by(|a, b| a.timestamp().partial_cmp(&b.timestamp()).unwrap());
214
215        Ok(events)
216    }
217
218    /// Process events through neuromorphic pipeline
219    ///
220    /// Processes spike events through the neuromorphic computing pipeline,
221    /// applying memristive crossbar processing and temporal dynamics as
222    /// configured.
223    ///
224    /// # Arguments
225    /// * `events` - Input spike events to process
226    ///
227    /// # Returns
228    /// Vector of processed spike events
229    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            // Process through memristive crossbar if enabled
236            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            // Apply temporal dynamics
244            if self.temporal_coding {
245                Self::apply_temporal_dynamics(&mut processed_events)?;
246            }
247
248            // Maintain event pipeline size
249            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    /// Initialize memristive crossbar array
258    ///
259    /// Initializes the conductance values of the memristive crossbar array
260    /// with random values representing the initial device states.
261    fn initialize_crossbar(&mut self) {
262        let (rows, cols) = self.crossbar_size;
263        let mut rng = scirs2_core::random::rng();
264
265        // Initialize conductances with random values
266        for i in 0..rows {
267            for j in 0..cols {
268                // Random conductance between 0.1 and 1.0 (normalized)
269                self.conductances[[i, j]] = 0.1 + rng.gen_range(0.0..0.9);
270            }
271        }
272    }
273
274    /// Process event through memristive crossbar
275    ///
276    /// Processes a spike event through the memristive crossbar array,
277    /// generating output spikes based on the crossbar conductances and
278    /// updating the memristive devices.
279    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        // Map input neuron to crossbar row
284        let input_row = event.neuron_id() % rows;
285
286        // Compute crossbar outputs
287        for col in 0..cols {
288            let conductance = self.conductances[[input_row, col]];
289            let output_current = event.amplitude() * conductance;
290
291            // Generate output spike if current exceeds threshold
292            if output_current > self.crossbar_threshold {
293                let output_event = SpikeEvent::new(
294                    rows + col,              // Offset for output neurons
295                    event.timestamp() + 0.1, // Small delay
296                    output_current,
297                    event.spatial_coords().to_vec(),
298                );
299                output_events.push(output_event);
300
301                // Update memristive device (Hebbian-like plasticity)
302                self.update_memristive_device(input_row, col, event.amplitude())?;
303            }
304        }
305
306        Ok(output_events)
307    }
308
309    /// Update memristive device conductance
310    ///
311    /// Updates the conductance of a memristive device based on the input
312    /// spike amplitude using a simple Hebbian-like learning rule.
313    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        // Simple memristive update rule
322        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    /// Apply temporal dynamics to event processing
332    ///
333    /// Applies temporal filtering and spike-timing dependent processing
334    /// to enhance temporal correlations and implement refractory periods.
335    fn apply_temporal_dynamics(events: &mut Vec<SpikeEvent>) -> SpatialResult<()> {
336        // Apply temporal filtering and spike-timing dependent processing
337        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            // Check for temporal correlations with recent events
344            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                    // Within temporal window
349                    // Apply temporal correlation enhancement
350                    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                    // Coincidence detection
359                    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                // Refractory period simulation
371                if time_diff < 0.5 && event.neuron_id() == other_event.neuron_id() {
372                    should_include = false; // Suppress due to refractory period
373                    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    /// Get crossbar statistics
387    ///
388    /// Returns statistics about the current state of the memristive crossbar
389    /// array and event processing pipeline.
390    ///
391    /// # Returns
392    /// HashMap containing various statistics about the processor state
393    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    /// Get crossbar size
417    pub fn crossbar_size(&self) -> (usize, usize) {
418        self.crossbar_size
419    }
420
421    /// Check if memristive crossbar is enabled
422    pub fn is_memristive_enabled(&self) -> bool {
423        self.memristive_crossbar
424    }
425
426    /// Check if temporal coding is enabled
427    pub fn is_temporal_coding_enabled(&self) -> bool {
428        self.temporal_coding
429    }
430
431    /// Get current crossbar threshold
432    pub fn crossbar_threshold(&self) -> f64 {
433        self.crossbar_threshold
434    }
435
436    /// Get memristive learning rate
437    pub fn learning_rate(&self) -> f64 {
438        self.memristive_learning_rate
439    }
440
441    /// Get number of events in pipeline
442    pub fn pipeline_length(&self) -> usize {
443        self.event_pipeline.len()
444    }
445
446    /// Clear event pipeline
447    pub fn clear_pipeline(&mut self) {
448        self.event_pipeline.clear();
449    }
450
451    /// Reset crossbar to initial state
452    pub fn reset_crossbar(&mut self) {
453        if self.memristive_crossbar {
454            self.initialize_crossbar();
455        }
456    }
457
458    /// Get conductance matrix (for analysis)
459    pub fn conductance_matrix(&self) -> &Array2<f64> {
460        &self.conductances
461    }
462
463    /// Set specific conductance value
464    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    /// Get specific conductance value
477    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        // Should generate events for each coordinate
525        assert!(!events.is_empty());
526
527        // Events should be sorted by timestamp
528        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        // Rate coding
538        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        // Temporal coding
544        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        // Different coding schemes should produce different numbers of events
550        // (temporal coding typically produces fewer events)
551        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        // Without crossbar, should have same number of events
565        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        // Memristive crossbar might generate additional output events
581        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        // Test setting and getting conductance
595        processor.set_conductance(0, 0, 0.8).unwrap();
596        assert_eq!(processor.get_conductance(0, 0), Some(0.8));
597
598        // Test bounds checking
599        assert!(processor.set_conductance(10, 10, 0.5).is_err());
600        assert_eq!(processor.get_conductance(10, 10), None);
601
602        // Test clamping
603        processor.set_conductance(1, 1, 2.0).unwrap(); // Should be clamped to 1.0
604        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        // Process some events to change state
612        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        // Reset should clear pipeline
619        processor.clear_pipeline();
620        assert_eq!(processor.pipeline_length(), 0);
621
622        // Reset crossbar should reinitialize conductances
623        let initial_stats = processor.get_crossbar_statistics();
624        processor.reset_crossbar();
625        let reset_stats = processor.get_crossbar_statistics();
626
627        // Conductances might be different after reset
628        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}