scirs2_spatial/neuromorphic/core/
events.rs

1//! Neuromorphic Spike Events
2//!
3//! This module defines the fundamental spike event structure used throughout
4//! neuromorphic computing systems. Spike events represent the basic unit of
5//! communication in spiking neural networks.
6
7/// Neuromorphic spike event
8///
9/// Represents a discrete spike occurring in a neuromorphic system. Each spike
10/// carries information about its source, timing, amplitude, and associated
11/// spatial coordinates.
12///
13/// # Fields
14/// - `neuron_id`: Unique identifier for the neuron that generated the spike
15/// - `timestamp`: Time of spike occurrence in simulation time units
16/// - `amplitude`: Strength/amplitude of the spike
17/// - `spatial_coords`: Spatial coordinates associated with the spike
18///
19/// # Example
20/// ```rust
21/// use scirs2_spatial::neuromorphic::core::SpikeEvent;
22///
23/// let spike = SpikeEvent {
24///     neuron_id: 42,
25///     timestamp: 1000.5,
26///     amplitude: 1.0,
27///     spatial_coords: vec![0.5, 0.3, 0.8],
28/// };
29///
30/// println!("Neuron {} spiked at time {}", spike.neuron_id, spike.timestamp);
31/// ```
32#[derive(Debug, Clone)]
33pub struct SpikeEvent {
34    /// Source neuron ID
35    pub neuron_id: usize,
36    /// Spike timestamp (in simulation time units)
37    pub timestamp: f64,
38    /// Spike amplitude
39    pub amplitude: f64,
40    /// Spatial coordinates associated with the spike
41    pub spatial_coords: Vec<f64>,
42}
43
44impl SpikeEvent {
45    /// Create a new spike event
46    ///
47    /// # Arguments
48    /// * `neuron_id` - ID of the neuron generating the spike
49    /// * `timestamp` - Time of spike occurrence
50    /// * `amplitude` - Spike amplitude (typically 1.0)
51    /// * `spatial_coords` - Associated spatial coordinates
52    ///
53    /// # Returns
54    /// A new `SpikeEvent` instance
55    pub fn new(neuron_id: usize, timestamp: f64, amplitude: f64, spatial_coords: Vec<f64>) -> Self {
56        Self {
57            neuron_id,
58            timestamp,
59            amplitude,
60            spatial_coords,
61        }
62    }
63
64    /// Get the neuron ID that generated this spike
65    pub fn neuron_id(&self) -> usize {
66        self.neuron_id
67    }
68
69    /// Get the timestamp of this spike
70    pub fn timestamp(&self) -> f64 {
71        self.timestamp
72    }
73
74    /// Get the amplitude of this spike
75    pub fn amplitude(&self) -> f64 {
76        self.amplitude
77    }
78
79    /// Get the spatial coordinates associated with this spike
80    pub fn spatial_coords(&self) -> &[f64] {
81        &self.spatial_coords
82    }
83
84    /// Get the number of spatial dimensions
85    pub fn spatial_dims(&self) -> usize {
86        self.spatial_coords.len()
87    }
88
89    /// Calculate the temporal distance to another spike
90    ///
91    /// # Arguments
92    /// * `other` - Another spike event
93    ///
94    /// # Returns
95    /// Absolute temporal distance between the two spikes
96    pub fn temporal_distance(&self, other: &SpikeEvent) -> f64 {
97        (self.timestamp - other.timestamp).abs()
98    }
99
100    /// Calculate the spatial distance to another spike
101    ///
102    /// # Arguments
103    /// * `other` - Another spike event
104    ///
105    /// # Returns
106    /// Euclidean distance between spatial coordinates, or None if dimensions don't match
107    pub fn spatial_distance(&self, other: &SpikeEvent) -> Option<f64> {
108        if self.spatial_coords.len() != other.spatial_coords.len() {
109            return None;
110        }
111
112        let distance = self
113            .spatial_coords
114            .iter()
115            .zip(other.spatial_coords.iter())
116            .map(|(a, b)| (a - b).powi(2))
117            .sum::<f64>()
118            .sqrt();
119
120        Some(distance)
121    }
122
123    /// Check if this spike occurs before another spike
124    ///
125    /// # Arguments
126    /// * `other` - Another spike event
127    ///
128    /// # Returns
129    /// True if this spike occurs before the other spike
130    pub fn is_before(&self, other: &SpikeEvent) -> bool {
131        self.timestamp < other.timestamp
132    }
133
134    /// Check if this spike occurs after another spike
135    ///
136    /// # Arguments
137    /// * `other` - Another spike event
138    ///
139    /// # Returns
140    /// True if this spike occurs after the other spike
141    pub fn is_after(&self, other: &SpikeEvent) -> bool {
142        self.timestamp > other.timestamp
143    }
144
145    /// Check if two spikes are from the same neuron
146    ///
147    /// # Arguments
148    /// * `other` - Another spike event
149    ///
150    /// # Returns
151    /// True if both spikes are from the same neuron
152    pub fn same_neuron(&self, other: &SpikeEvent) -> bool {
153        self.neuron_id == other.neuron_id
154    }
155}
156
157impl PartialEq for SpikeEvent {
158    fn eq(&self, other: &Self) -> bool {
159        self.neuron_id == other.neuron_id
160            && (self.timestamp - other.timestamp).abs() < 1e-9
161            && (self.amplitude - other.amplitude).abs() < 1e-9
162            && self.spatial_coords == other.spatial_coords
163    }
164}
165
166impl PartialOrd for SpikeEvent {
167    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
168        self.timestamp.partial_cmp(&other.timestamp)
169    }
170}
171
172/// Collection of spike events with utilities for temporal and spatial analysis
173#[derive(Debug, Clone)]
174pub struct SpikeSequence {
175    /// Vector of spike events
176    events: Vec<SpikeEvent>,
177}
178
179impl SpikeSequence {
180    /// Create a new empty spike sequence
181    pub fn new() -> Self {
182        Self { events: Vec::new() }
183    }
184
185    /// Create a spike sequence from a vector of events
186    ///
187    /// # Arguments
188    /// * `events` - Vector of spike events
189    ///
190    /// # Returns
191    /// A new `SpikeSequence` containing the events
192    pub fn from_events(events: Vec<SpikeEvent>) -> Self {
193        Self { events }
194    }
195
196    /// Add a spike event to the sequence
197    ///
198    /// # Arguments
199    /// * `event` - Spike event to add
200    pub fn add_event(&mut self, event: SpikeEvent) {
201        self.events.push(event);
202    }
203
204    /// Get all spike events
205    pub fn events(&self) -> &[SpikeEvent] {
206        &self.events
207    }
208
209    /// Get the number of spike events
210    pub fn len(&self) -> usize {
211        self.events.len()
212    }
213
214    /// Check if the sequence is empty
215    pub fn is_empty(&self) -> bool {
216        self.events.is_empty()
217    }
218
219    /// Sort events by timestamp
220    pub fn sort_by_time(&mut self) {
221        self.events
222            .sort_by(|a, b| a.timestamp.partial_cmp(&b.timestamp).unwrap());
223    }
224
225    /// Get events within a time window
226    ///
227    /// # Arguments
228    /// * `start_time` - Start of time window
229    /// * `end_time` - End of time window
230    ///
231    /// # Returns
232    /// Vector of spike events within the time window
233    pub fn events_in_window(&self, start_time: f64, end_time: f64) -> Vec<&SpikeEvent> {
234        self.events
235            .iter()
236            .filter(|event| event.timestamp >= start_time && event.timestamp <= end_time)
237            .collect()
238    }
239
240    /// Get events from a specific neuron
241    ///
242    /// # Arguments
243    /// * `neuron_id` - ID of the neuron
244    ///
245    /// # Returns
246    /// Vector of spike events from the specified neuron
247    pub fn events_from_neuron(&self, neuron_id: usize) -> Vec<&SpikeEvent> {
248        self.events
249            .iter()
250            .filter(|event| event.neuron_id == neuron_id)
251            .collect()
252    }
253
254    /// Calculate average firing rate for a neuron
255    ///
256    /// # Arguments
257    /// * `neuron_id` - ID of the neuron
258    /// * `time_window` - Duration of the time window
259    ///
260    /// # Returns
261    /// Average firing rate in spikes per time unit
262    pub fn firing_rate(&self, neuron_id: usize, time_window: f64) -> f64 {
263        let neuron_events = self.events_from_neuron(neuron_id);
264        if neuron_events.is_empty() || time_window <= 0.0 {
265            return 0.0;
266        }
267
268        neuron_events.len() as f64 / time_window
269    }
270
271    /// Get the time span of the sequence
272    ///
273    /// # Returns
274    /// Tuple of (earliest_time, latest_time), or None if sequence is empty
275    pub fn time_span(&self) -> Option<(f64, f64)> {
276        if self.events.is_empty() {
277            return None;
278        }
279
280        let times: Vec<f64> = self.events.iter().map(|event| event.timestamp).collect();
281        let min_time = times.iter().fold(f64::INFINITY, |a, &b| a.min(b));
282        let max_time = times.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
283
284        Some((min_time, max_time))
285    }
286}
287
288impl Default for SpikeSequence {
289    fn default() -> Self {
290        Self::new()
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn test_spike_event_creation() {
300        let spike = SpikeEvent::new(42, 1000.5, 1.0, vec![0.5, 0.3, 0.8]);
301
302        assert_eq!(spike.neuron_id(), 42);
303        assert_eq!(spike.timestamp(), 1000.5);
304        assert_eq!(spike.amplitude(), 1.0);
305        assert_eq!(spike.spatial_coords(), &[0.5, 0.3, 0.8]);
306        assert_eq!(spike.spatial_dims(), 3);
307    }
308
309    #[test]
310    fn test_temporal_distance() {
311        let spike1 = SpikeEvent::new(1, 100.0, 1.0, vec![0.0]);
312        let spike2 = SpikeEvent::new(2, 105.0, 1.0, vec![1.0]);
313
314        assert_eq!(spike1.temporal_distance(&spike2), 5.0);
315        assert_eq!(spike2.temporal_distance(&spike1), 5.0);
316    }
317
318    #[test]
319    fn test_spatial_distance() {
320        let spike1 = SpikeEvent::new(1, 100.0, 1.0, vec![0.0, 0.0]);
321        let spike2 = SpikeEvent::new(2, 105.0, 1.0, vec![3.0, 4.0]);
322
323        assert_eq!(spike1.spatial_distance(&spike2), Some(5.0));
324
325        // Test mismatched dimensions
326        let spike3 = SpikeEvent::new(3, 110.0, 1.0, vec![1.0]);
327        assert_eq!(spike1.spatial_distance(&spike3), None);
328    }
329
330    #[test]
331    fn test_spike_ordering() {
332        let spike1 = SpikeEvent::new(1, 100.0, 1.0, vec![0.0]);
333        let spike2 = SpikeEvent::new(2, 105.0, 1.0, vec![1.0]);
334
335        assert!(spike1.is_before(&spike2));
336        assert!(spike2.is_after(&spike1));
337        assert!(!spike1.is_after(&spike2));
338        assert!(!spike2.is_before(&spike1));
339    }
340
341    #[test]
342    fn test_spike_sequence() {
343        let mut sequence = SpikeSequence::new();
344        assert!(sequence.is_empty());
345
346        let spike1 = SpikeEvent::new(1, 100.0, 1.0, vec![0.0]);
347        let spike2 = SpikeEvent::new(2, 105.0, 1.0, vec![1.0]);
348
349        sequence.add_event(spike1);
350        sequence.add_event(spike2);
351
352        assert_eq!(sequence.len(), 2);
353        assert!(!sequence.is_empty());
354
355        let time_span = sequence.time_span().unwrap();
356        assert_eq!(time_span.0, 100.0);
357        assert_eq!(time_span.1, 105.0);
358    }
359
360    #[test]
361    fn test_events_in_window() {
362        let mut sequence = SpikeSequence::new();
363        sequence.add_event(SpikeEvent::new(1, 95.0, 1.0, vec![0.0]));
364        sequence.add_event(SpikeEvent::new(2, 100.0, 1.0, vec![1.0]));
365        sequence.add_event(SpikeEvent::new(3, 105.0, 1.0, vec![2.0]));
366        sequence.add_event(SpikeEvent::new(4, 110.0, 1.0, vec![3.0]));
367
368        let windowed_events = sequence.events_in_window(100.0, 105.0);
369        assert_eq!(windowed_events.len(), 2);
370        assert_eq!(windowed_events[0].neuron_id, 2);
371        assert_eq!(windowed_events[1].neuron_id, 3);
372    }
373
374    #[test]
375    fn test_firing_rate() {
376        let mut sequence = SpikeSequence::new();
377        sequence.add_event(SpikeEvent::new(1, 100.0, 1.0, vec![0.0]));
378        sequence.add_event(SpikeEvent::new(1, 105.0, 1.0, vec![0.0]));
379        sequence.add_event(SpikeEvent::new(1, 110.0, 1.0, vec![0.0]));
380        sequence.add_event(SpikeEvent::new(2, 107.0, 1.0, vec![1.0]));
381
382        // Neuron 1 has 3 spikes in 10 time units = 0.3 spikes per unit
383        assert_eq!(sequence.firing_rate(1, 10.0), 0.3);
384
385        // Neuron 2 has 1 spike in 10 time units = 0.1 spikes per unit
386        assert_eq!(sequence.firing_rate(2, 10.0), 0.1);
387
388        // Non-existent neuron
389        assert_eq!(sequence.firing_rate(99, 10.0), 0.0);
390    }
391}