Skip to main content

torsh_core/
neuromorphic.rs

1// Copyright (c) 2025 ToRSh Contributors
2//
3// Neuromorphic Computing Data Structures
4//
5// This module provides data structures and abstractions for neuromorphic computing,
6// enabling efficient representation and simulation of spiking neural networks (SNNs)
7// and neuromorphic hardware architectures.
8//
9// # Key Features
10//
11// - **Spiking Neural Networks**: Efficient representation of spike-based computation
12// - **Spike Timing**: Precise temporal coding with microsecond resolution
13// - **Neuron Models**: LIF, Izhikevich, and other spiking neuron models
14// - **Synaptic Plasticity**: STDP and other learning rules
15// - **Event-Driven Simulation**: Efficient spike-driven computation
16//
17// # Design Principles
18//
19// 1. **Temporal Precision**: Support microsecond-level spike timing
20// 2. **Memory Efficiency**: Sparse event representation
21// 3. **Hardware Mapping**: Compatible with neuromorphic chips (Loihi, TrueNorth, etc.)
22// 4. **Biological Realism**: Support for realistic neuron and synapse models
23//
24// # Examples
25//
26// ```rust
27// use torsh_core::neuromorphic::{SpikeEvent, LIFNeuron, STDPSynapse};
28//
29// // Create a spiking neuron
30// let neuron = LIFNeuron::new(0, 0.02, 0.2, -65.0, 8.0);
31//
32// // Create a spike event
33// let spike = SpikeEvent::new(0, 1000); // Neuron 0 spikes at t=1ms
34//
35// // STDP learning rule
36// let synapse = STDPSynapse::new(0, 1, 0.5, 0.02, 0.02);
37// ```
38
39use core::cmp::Ordering;
40
41/// Spike event representation
42///
43/// Represents a single spike event in a spiking neural network.
44/// Contains the neuron ID and the precise timing of the spike.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct SpikeEvent {
47    /// ID of the neuron that fired
48    neuron_id: usize,
49    /// Timestamp in microseconds
50    timestamp_us: u64,
51}
52
53impl SpikeEvent {
54    /// Create a new spike event
55    pub fn new(neuron_id: usize, timestamp_us: u64) -> Self {
56        Self {
57            neuron_id,
58            timestamp_us,
59        }
60    }
61
62    /// Get the neuron ID
63    pub fn neuron_id(&self) -> usize {
64        self.neuron_id
65    }
66
67    /// Get the timestamp
68    pub fn timestamp_us(&self) -> u64 {
69        self.timestamp_us
70    }
71
72    /// Get the timestamp in milliseconds
73    pub fn timestamp_ms(&self) -> f64 {
74        self.timestamp_us as f64 / 1000.0
75    }
76
77    /// Get the timestamp in seconds
78    pub fn timestamp_s(&self) -> f64 {
79        self.timestamp_us as f64 / 1_000_000.0
80    }
81}
82
83impl PartialOrd for SpikeEvent {
84    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
85        Some(self.cmp(other))
86    }
87}
88
89impl Ord for SpikeEvent {
90    fn cmp(&self, other: &Self) -> Ordering {
91        self.timestamp_us.cmp(&other.timestamp_us)
92    }
93}
94
95/// Spike train representation
96///
97/// A sequence of spike events for a single neuron or population.
98#[derive(Debug, Clone)]
99pub struct SpikeTrain {
100    neuron_id: usize,
101    spikes: Vec<u64>, // Timestamps in microseconds
102}
103
104impl SpikeTrain {
105    /// Create a new spike train
106    pub fn new(neuron_id: usize) -> Self {
107        Self {
108            neuron_id,
109            spikes: Vec::new(),
110        }
111    }
112
113    /// Add a spike to the train
114    pub fn add_spike(&mut self, timestamp_us: u64) {
115        self.spikes.push(timestamp_us);
116    }
117
118    /// Get the neuron ID
119    pub fn neuron_id(&self) -> usize {
120        self.neuron_id
121    }
122
123    /// Get the number of spikes
124    pub fn spike_count(&self) -> usize {
125        self.spikes.len()
126    }
127
128    /// Get the spikes
129    pub fn spikes(&self) -> &[u64] {
130        &self.spikes
131    }
132
133    /// Calculate firing rate (Hz)
134    pub fn firing_rate(&self, duration_us: u64) -> f64 {
135        if duration_us == 0 {
136            return 0.0;
137        }
138        let duration_s = duration_us as f64 / 1_000_000.0;
139        self.spikes.len() as f64 / duration_s
140    }
141
142    /// Calculate inter-spike intervals (ISI)
143    pub fn inter_spike_intervals(&self) -> Vec<u64> {
144        if self.spikes.len() < 2 {
145            return Vec::new();
146        }
147        self.spikes
148            .windows(2)
149            .map(|w| w[1].saturating_sub(w[0]))
150            .collect()
151    }
152}
153
154/// Leaky Integrate-and-Fire (LIF) neuron model
155///
156/// Classic spiking neuron model with exponential decay and threshold-based firing.
157#[derive(Debug, Clone)]
158pub struct LIFNeuron {
159    id: usize,
160    /// Time constant (seconds)
161    tau: f64,
162    /// Membrane resistance (MOhm)
163    resistance: f64,
164    /// Resting potential (mV)
165    v_rest: f64,
166    /// Threshold potential (mV)
167    v_thresh: f64,
168    /// Current membrane potential (mV)
169    v_mem: f64,
170    /// Refractory period (microseconds)
171    refrac_period_us: u64,
172    /// Time of last spike (microseconds)
173    last_spike_us: Option<u64>,
174}
175
176impl LIFNeuron {
177    /// Create a new LIF neuron
178    pub fn new(id: usize, tau: f64, resistance: f64, v_rest: f64, v_thresh: f64) -> Self {
179        Self {
180            id,
181            tau,
182            resistance,
183            v_rest,
184            v_thresh,
185            v_mem: v_rest,
186            refrac_period_us: 2000, // 2ms default
187            last_spike_us: None,
188        }
189    }
190
191    /// Get the neuron ID
192    pub fn id(&self) -> usize {
193        self.id
194    }
195
196    /// Get current membrane potential
197    pub fn membrane_potential(&self) -> f64 {
198        self.v_mem
199    }
200
201    /// Update membrane potential with input current
202    ///
203    /// Returns Some(spike_time) if neuron fires, None otherwise
204    pub fn update(&mut self, current: f64, dt_us: u64, current_time_us: u64) -> Option<u64> {
205        // Check if in refractory period
206        if let Some(last_spike) = self.last_spike_us {
207            if current_time_us < last_spike + self.refrac_period_us {
208                return None;
209            }
210        }
211
212        let dt = dt_us as f64 / 1_000_000.0; // Convert to seconds
213
214        // Integrate membrane potential: dV/dt = (-(V - V_rest) + R*I) / tau
215        let dv = ((-(self.v_mem - self.v_rest) + self.resistance * current) / self.tau) * dt;
216        self.v_mem += dv;
217
218        // Check for spike
219        if self.v_mem >= self.v_thresh {
220            self.v_mem = self.v_rest; // Reset
221            self.last_spike_us = Some(current_time_us);
222            Some(current_time_us)
223        } else {
224            None
225        }
226    }
227
228    /// Reset the neuron to resting state
229    pub fn reset(&mut self) {
230        self.v_mem = self.v_rest;
231        self.last_spike_us = None;
232    }
233
234    /// Set refractory period
235    pub fn set_refractory_period(&mut self, period_us: u64) {
236        self.refrac_period_us = period_us;
237    }
238}
239
240/// Izhikevich neuron model
241///
242/// More biologically realistic neuron model capable of reproducing various
243/// spiking patterns (regular spiking, fast spiking, bursting, etc.)
244#[derive(Debug, Clone)]
245pub struct IzhikevichNeuron {
246    id: usize,
247    /// Time scale of recovery variable u
248    a: f64,
249    /// Sensitivity of u to v
250    b: f64,
251    /// After-spike reset value of v
252    c: f64,
253    /// After-spike reset increment of u
254    d: f64,
255    /// Membrane potential
256    v: f64,
257    /// Recovery variable
258    u: f64,
259}
260
261impl IzhikevichNeuron {
262    /// Create a new Izhikevich neuron
263    pub fn new(id: usize, a: f64, b: f64, c: f64, d: f64) -> Self {
264        Self {
265            id,
266            a,
267            b,
268            c,
269            d,
270            v: c, // Initialize to reset value
271            u: b * c,
272        }
273    }
274
275    /// Create a regular spiking neuron
276    pub fn regular_spiking(id: usize) -> Self {
277        Self::new(id, 0.02, 0.2, -65.0, 8.0)
278    }
279
280    /// Create a fast spiking neuron
281    pub fn fast_spiking(id: usize) -> Self {
282        Self::new(id, 0.1, 0.2, -65.0, 2.0)
283    }
284
285    /// Create a bursting neuron
286    pub fn bursting(id: usize) -> Self {
287        Self::new(id, 0.02, 0.2, -50.0, 2.0)
288    }
289
290    /// Get the neuron ID
291    pub fn id(&self) -> usize {
292        self.id
293    }
294
295    /// Get membrane potential
296    pub fn membrane_potential(&self) -> f64 {
297        self.v
298    }
299
300    /// Update neuron state
301    ///
302    /// Returns Some(spike_time) if neuron fires, None otherwise
303    pub fn update(&mut self, current: f64, dt_us: u64, current_time_us: u64) -> Option<u64> {
304        let dt = dt_us as f64 / 1000.0; // Convert to ms
305
306        // Izhikevich equations:
307        // v' = 0.04v^2 + 5v + 140 - u + I
308        // u' = a(bv - u)
309
310        let v_squared = self.v * self.v;
311        let dv = (0.04 * v_squared + 5.0 * self.v + 140.0 - self.u + current) * dt;
312        let du = (self.a * (self.b * self.v - self.u)) * dt;
313
314        self.v += dv;
315        self.u += du;
316
317        // Check for spike
318        if self.v >= 30.0 {
319            // Spike threshold
320            self.v = self.c;
321            self.u += self.d;
322            Some(current_time_us)
323        } else {
324            None
325        }
326    }
327
328    /// Reset the neuron
329    pub fn reset(&mut self) {
330        self.v = self.c;
331        self.u = self.b * self.c;
332    }
333}
334
335/// Synapse with Spike-Timing-Dependent Plasticity (STDP)
336///
337/// Implements STDP learning rule where synapse strength is modified
338/// based on the relative timing of pre- and post-synaptic spikes.
339#[derive(Debug, Clone)]
340pub struct STDPSynapse {
341    /// Pre-synaptic neuron ID
342    pre_id: usize,
343    /// Post-synaptic neuron ID
344    post_id: usize,
345    /// Synaptic weight
346    weight: f64,
347    /// Learning rate for potentiation (pre before post)
348    a_plus: f64,
349    /// Learning rate for depression (post before pre)
350    a_minus: f64,
351    /// Time constant for potentiation (ms)
352    tau_plus: f64,
353    /// Time constant for depression (ms)
354    tau_minus: f64,
355    /// Time of last pre-synaptic spike
356    last_pre_spike_us: Option<u64>,
357    /// Time of last post-synaptic spike
358    last_post_spike_us: Option<u64>,
359}
360
361impl STDPSynapse {
362    /// Create a new STDP synapse
363    pub fn new(
364        pre_id: usize,
365        post_id: usize,
366        initial_weight: f64,
367        a_plus: f64,
368        a_minus: f64,
369    ) -> Self {
370        Self {
371            pre_id,
372            post_id,
373            weight: initial_weight,
374            a_plus,
375            a_minus,
376            tau_plus: 20.0,  // 20ms default
377            tau_minus: 20.0, // 20ms default
378            last_pre_spike_us: None,
379            last_post_spike_us: None,
380        }
381    }
382
383    /// Get the synaptic weight
384    pub fn weight(&self) -> f64 {
385        self.weight
386    }
387
388    /// Get pre-synaptic neuron ID
389    pub fn pre_id(&self) -> usize {
390        self.pre_id
391    }
392
393    /// Get post-synaptic neuron ID
394    pub fn post_id(&self) -> usize {
395        self.post_id
396    }
397
398    /// Update synapse on pre-synaptic spike
399    pub fn on_pre_spike(&mut self, spike_time_us: u64) {
400        self.last_pre_spike_us = Some(spike_time_us);
401
402        // Depression: post spike came before pre spike
403        if let Some(last_post) = self.last_post_spike_us {
404            if last_post < spike_time_us {
405                let dt = ((spike_time_us - last_post) as f64) / 1000.0; // Convert to ms
406                let delta_w = -self.a_minus * (-dt / self.tau_minus).exp();
407                self.weight += delta_w;
408                self.weight = self.weight.max(0.0); // Ensure non-negative
409            }
410        }
411    }
412
413    /// Update synapse on post-synaptic spike
414    pub fn on_post_spike(&mut self, spike_time_us: u64) {
415        self.last_post_spike_us = Some(spike_time_us);
416
417        // Potentiation: pre spike came before post spike
418        if let Some(last_pre) = self.last_pre_spike_us {
419            if last_pre < spike_time_us {
420                let dt = ((spike_time_us - last_pre) as f64) / 1000.0; // Convert to ms
421                let delta_w = self.a_plus * (-dt / self.tau_plus).exp();
422                self.weight += delta_w;
423            }
424        }
425    }
426
427    /// Set time constants
428    pub fn set_time_constants(&mut self, tau_plus: f64, tau_minus: f64) {
429        self.tau_plus = tau_plus;
430        self.tau_minus = tau_minus;
431    }
432
433    /// Reset synapse state
434    pub fn reset(&mut self) {
435        self.last_pre_spike_us = None;
436        self.last_post_spike_us = None;
437    }
438}
439
440/// Neuromorphic hardware core abstraction
441///
442/// Represents a computation core in neuromorphic hardware (e.g., Loihi core)
443#[derive(Debug, Clone)]
444pub struct NeuromorphicCore {
445    /// Core ID
446    id: usize,
447    /// Maximum number of neurons
448    max_neurons: usize,
449    /// Maximum number of synapses
450    max_synapses: usize,
451    /// Current neuron count
452    neuron_count: usize,
453    /// Current synapse count
454    synapse_count: usize,
455}
456
457impl NeuromorphicCore {
458    /// Create a new neuromorphic core
459    pub fn new(id: usize, max_neurons: usize, max_synapses: usize) -> Self {
460        Self {
461            id,
462            max_neurons,
463            max_synapses,
464            neuron_count: 0,
465            synapse_count: 0,
466        }
467    }
468
469    /// Create a Loihi-like core (128 neurons, 128K synapses per core)
470    pub fn loihi_core(id: usize) -> Self {
471        Self::new(id, 128, 128 * 1024)
472    }
473
474    /// Create a TrueNorth-like core (256 neurons, 64K synapses per core)
475    pub fn truenorth_core(id: usize) -> Self {
476        Self::new(id, 256, 64 * 1024)
477    }
478
479    /// Get core ID
480    pub fn id(&self) -> usize {
481        self.id
482    }
483
484    /// Check if can add a neuron
485    pub fn can_add_neuron(&self) -> bool {
486        self.neuron_count < self.max_neurons
487    }
488
489    /// Check if can add a synapse
490    pub fn can_add_synapse(&self) -> bool {
491        self.synapse_count < self.max_synapses
492    }
493
494    /// Add a neuron
495    pub fn add_neuron(&mut self) -> Result<usize, &'static str> {
496        if !self.can_add_neuron() {
497            return Err("Core neuron capacity exceeded");
498        }
499        let neuron_id = self.neuron_count;
500        self.neuron_count += 1;
501        Ok(neuron_id)
502    }
503
504    /// Add a synapse
505    pub fn add_synapse(&mut self) -> Result<usize, &'static str> {
506        if !self.can_add_synapse() {
507            return Err("Core synapse capacity exceeded");
508        }
509        let synapse_id = self.synapse_count;
510        self.synapse_count += 1;
511        Ok(synapse_id)
512    }
513
514    /// Get utilization statistics
515    pub fn utilization(&self) -> CoreUtilization {
516        CoreUtilization {
517            neuron_util: self.neuron_count as f64 / self.max_neurons as f64,
518            synapse_util: self.synapse_count as f64 / self.max_synapses as f64,
519        }
520    }
521}
522
523/// Core utilization statistics
524#[derive(Debug, Clone, Copy)]
525pub struct CoreUtilization {
526    /// Neuron utilization (0.0 to 1.0)
527    pub neuron_util: f64,
528    /// Synapse utilization (0.0 to 1.0)
529    pub synapse_util: f64,
530}
531
532/// Event-driven simulation state
533///
534/// Manages the simulation of spiking neural networks using event-driven approach
535#[derive(Debug, Clone)]
536pub struct EventDrivenSimulation {
537    /// Current simulation time (microseconds)
538    current_time_us: u64,
539    /// Event queue (sorted by timestamp)
540    event_queue: Vec<SpikeEvent>,
541}
542
543impl EventDrivenSimulation {
544    /// Create a new event-driven simulation
545    pub fn new() -> Self {
546        Self {
547            current_time_us: 0,
548            event_queue: Vec::new(),
549        }
550    }
551
552    /// Get current simulation time
553    pub fn current_time_us(&self) -> u64 {
554        self.current_time_us
555    }
556
557    /// Add a spike event to the queue
558    pub fn schedule_event(&mut self, event: SpikeEvent) {
559        self.event_queue.push(event);
560        self.event_queue.sort(); // Keep queue sorted
561    }
562
563    /// Get next event
564    pub fn next_event(&mut self) -> Option<SpikeEvent> {
565        if self.event_queue.is_empty() {
566            return None;
567        }
568        let event = self.event_queue.remove(0);
569        self.current_time_us = event.timestamp_us();
570        Some(event)
571    }
572
573    /// Check if events remain
574    pub fn has_events(&self) -> bool {
575        !self.event_queue.is_empty()
576    }
577
578    /// Get number of pending events
579    pub fn event_count(&self) -> usize {
580        self.event_queue.len()
581    }
582
583    /// Advance simulation to a specific time
584    pub fn advance_to(&mut self, time_us: u64) {
585        self.current_time_us = time_us;
586    }
587
588    /// Reset simulation
589    pub fn reset(&mut self) {
590        self.current_time_us = 0;
591        self.event_queue.clear();
592    }
593}
594
595impl Default for EventDrivenSimulation {
596    fn default() -> Self {
597        Self::new()
598    }
599}
600
601/// Spike encoding schemes for converting continuous signals to spikes
602#[derive(Debug, Clone, Copy)]
603pub enum SpikeEncoding {
604    /// Rate coding: spike frequency represents value
605    Rate,
606    /// Temporal coding: spike timing represents value
607    Temporal,
608    /// Population coding: population activity represents value
609    Population,
610    /// Phase coding: spike phase relative to oscillation
611    Phase,
612}
613
614/// Rate encoder for converting continuous values to spike trains
615#[derive(Debug, Clone)]
616pub struct RateEncoder {
617    /// Maximum firing rate (Hz)
618    max_rate: f64,
619    /// Encoding window (microseconds)
620    window_us: u64,
621}
622
623impl RateEncoder {
624    /// Create a new rate encoder
625    pub fn new(max_rate: f64, window_us: u64) -> Self {
626        Self {
627            max_rate,
628            window_us,
629        }
630    }
631
632    /// Encode a value (0.0 to 1.0) as spike count
633    pub fn encode(&self, value: f64) -> usize {
634        let rate = value.max(0.0).min(1.0) * self.max_rate;
635        let window_s = self.window_us as f64 / 1_000_000.0;
636        (rate * window_s).round() as usize
637    }
638
639    /// Get maximum rate
640    pub fn max_rate(&self) -> f64 {
641        self.max_rate
642    }
643
644    /// Get encoding window
645    pub fn window_us(&self) -> u64 {
646        self.window_us
647    }
648}
649
650/// Spike decoder for converting spike trains to continuous values
651#[derive(Debug, Clone)]
652pub struct RateDecoder {
653    /// Decoding window (microseconds)
654    window_us: u64,
655}
656
657impl RateDecoder {
658    /// Create a new rate decoder
659    pub fn new(window_us: u64) -> Self {
660        Self { window_us }
661    }
662
663    /// Decode spike train to continuous value
664    pub fn decode(&self, spike_train: &SpikeTrain) -> f64 {
665        spike_train.firing_rate(self.window_us)
666    }
667
668    /// Get decoding window
669    pub fn window_us(&self) -> u64 {
670        self.window_us
671    }
672}
673
674#[cfg(test)]
675mod tests {
676    use super::*;
677
678    #[test]
679    fn test_spike_event_creation() {
680        let event = SpikeEvent::new(0, 1000);
681        assert_eq!(event.neuron_id(), 0);
682        assert_eq!(event.timestamp_us(), 1000);
683        assert_eq!(event.timestamp_ms(), 1.0);
684        assert_eq!(event.timestamp_s(), 0.001);
685    }
686
687    #[test]
688    fn test_spike_event_ordering() {
689        let event1 = SpikeEvent::new(0, 1000);
690        let event2 = SpikeEvent::new(1, 2000);
691        assert!(event1 < event2);
692    }
693
694    #[test]
695    fn test_spike_train() {
696        let mut train = SpikeTrain::new(0);
697        train.add_spike(1000);
698        train.add_spike(2000);
699        train.add_spike(3000);
700
701        assert_eq!(train.spike_count(), 3);
702        assert_eq!(train.neuron_id(), 0);
703
704        let rate = train.firing_rate(10_000); // 10ms window
705        assert!((rate - 300.0).abs() < 1.0); // ~300 Hz
706
707        let isis = train.inter_spike_intervals();
708        assert_eq!(isis.len(), 2);
709        assert_eq!(isis[0], 1000);
710        assert_eq!(isis[1], 1000);
711    }
712
713    #[test]
714    fn test_lif_neuron() {
715        let mut neuron = LIFNeuron::new(0, 0.02, 0.2, -65.0, -50.0);
716        assert_eq!(neuron.id(), 0);
717        assert_eq!(neuron.membrane_potential(), -65.0);
718
719        // Apply input current
720        let spike = neuron.update(100.0, 1000, 1000);
721        assert!(spike.is_some() || spike.is_none()); // May or may not spike depending on parameters
722
723        neuron.reset();
724        assert_eq!(neuron.membrane_potential(), -65.0);
725    }
726
727    #[test]
728    fn test_izhikevich_neuron() {
729        let mut neuron = IzhikevichNeuron::regular_spiking(0);
730        assert_eq!(neuron.id(), 0);
731
732        let spike = neuron.update(10.0, 1000, 1000);
733        assert!(spike.is_some() || spike.is_none());
734
735        neuron.reset();
736    }
737
738    #[test]
739    fn test_izhikevich_neuron_types() {
740        let _rs = IzhikevichNeuron::regular_spiking(0);
741        let _fs = IzhikevichNeuron::fast_spiking(1);
742        let _burst = IzhikevichNeuron::bursting(2);
743    }
744
745    #[test]
746    fn test_stdp_synapse() {
747        let mut synapse = STDPSynapse::new(0, 1, 0.5, 0.02, 0.02);
748        assert_eq!(synapse.pre_id(), 0);
749        assert_eq!(synapse.post_id(), 1);
750        assert_eq!(synapse.weight(), 0.5);
751
752        synapse.on_pre_spike(1000);
753        synapse.on_post_spike(2000); // Post after pre -> potentiation
754
755        assert!(synapse.weight() > 0.5); // Weight should increase
756
757        synapse.reset();
758    }
759
760    #[test]
761    fn test_neuromorphic_core() {
762        let mut core = NeuromorphicCore::loihi_core(0);
763        assert_eq!(core.id(), 0);
764        assert!(core.can_add_neuron());
765        assert!(core.can_add_synapse());
766
767        let neuron_id = core.add_neuron().expect("add_neuron should succeed");
768        assert_eq!(neuron_id, 0);
769
770        let synapse_id = core.add_synapse().expect("add_synapse should succeed");
771        assert_eq!(synapse_id, 0);
772
773        let util = core.utilization();
774        assert!(util.neuron_util > 0.0);
775        assert!(util.synapse_util > 0.0);
776    }
777
778    #[test]
779    fn test_truenorth_core() {
780        let core = NeuromorphicCore::truenorth_core(0);
781        assert_eq!(core.id(), 0);
782    }
783
784    #[test]
785    fn test_event_driven_simulation() {
786        let mut sim = EventDrivenSimulation::new();
787        assert_eq!(sim.current_time_us(), 0);
788        assert!(!sim.has_events());
789
790        sim.schedule_event(SpikeEvent::new(0, 1000));
791        sim.schedule_event(SpikeEvent::new(1, 500));
792
793        assert!(sim.has_events());
794        assert_eq!(sim.event_count(), 2);
795
796        let event1 = sim.next_event().expect("next_event should return event");
797        assert_eq!(event1.timestamp_us(), 500); // Earlier event comes first
798
799        let event2 = sim.next_event().expect("next_event should return event");
800        assert_eq!(event2.timestamp_us(), 1000);
801
802        assert!(!sim.has_events());
803
804        sim.reset();
805        assert_eq!(sim.current_time_us(), 0);
806    }
807
808    #[test]
809    fn test_rate_encoder() {
810        let encoder = RateEncoder::new(100.0, 10_000); // 100 Hz max, 10ms window
811        let spike_count = encoder.encode(0.5); // 50% of max rate
812        assert!(spike_count > 0);
813        assert_eq!(encoder.max_rate(), 100.0);
814        assert_eq!(encoder.window_us(), 10_000);
815    }
816
817    #[test]
818    fn test_rate_decoder() {
819        let decoder = RateDecoder::new(10_000);
820        let mut train = SpikeTrain::new(0);
821        train.add_spike(1000);
822        train.add_spike(2000);
823        train.add_spike(3000);
824
825        let rate = decoder.decode(&train);
826        assert!(rate > 0.0);
827        assert_eq!(decoder.window_us(), 10_000);
828    }
829
830    #[test]
831    fn test_spike_encoding_variants() {
832        let _rate = SpikeEncoding::Rate;
833        let _temporal = SpikeEncoding::Temporal;
834        let _population = SpikeEncoding::Population;
835        let _phase = SpikeEncoding::Phase;
836    }
837
838    #[test]
839    fn test_default_simulation() {
840        let sim = EventDrivenSimulation::default();
841        assert_eq!(sim.current_time_us(), 0);
842    }
843
844    #[test]
845    fn test_simulation_advance() {
846        let mut sim = EventDrivenSimulation::new();
847        sim.advance_to(5000);
848        assert_eq!(sim.current_time_us(), 5000);
849    }
850
851    #[test]
852    fn test_lif_refractory_period() {
853        let mut neuron = LIFNeuron::new(0, 0.02, 0.2, -65.0, -50.0);
854        neuron.set_refractory_period(3000); // 3ms
855    }
856
857    #[test]
858    fn test_stdp_time_constants() {
859        let mut synapse = STDPSynapse::new(0, 1, 0.5, 0.02, 0.02);
860        synapse.set_time_constants(15.0, 25.0);
861    }
862
863    #[test]
864    fn test_core_capacity() {
865        let mut core = NeuromorphicCore::new(0, 2, 2);
866
867        core.add_neuron().expect("first add_neuron should succeed");
868        core.add_neuron().expect("second add_neuron should succeed");
869        assert!(core.add_neuron().is_err()); // Should fail - capacity exceeded
870
871        core.add_synapse()
872            .expect("first add_synapse should succeed");
873        core.add_synapse()
874            .expect("second add_synapse should succeed");
875        assert!(core.add_synapse().is_err()); // Should fail - capacity exceeded
876    }
877}