use core::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpikeEvent {
neuron_id: usize,
timestamp_us: u64,
}
impl SpikeEvent {
pub fn new(neuron_id: usize, timestamp_us: u64) -> Self {
Self {
neuron_id,
timestamp_us,
}
}
pub fn neuron_id(&self) -> usize {
self.neuron_id
}
pub fn timestamp_us(&self) -> u64 {
self.timestamp_us
}
pub fn timestamp_ms(&self) -> f64 {
self.timestamp_us as f64 / 1000.0
}
pub fn timestamp_s(&self) -> f64 {
self.timestamp_us as f64 / 1_000_000.0
}
}
impl PartialOrd for SpikeEvent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SpikeEvent {
fn cmp(&self, other: &Self) -> Ordering {
self.timestamp_us.cmp(&other.timestamp_us)
}
}
#[derive(Debug, Clone)]
pub struct SpikeTrain {
neuron_id: usize,
spikes: Vec<u64>, }
impl SpikeTrain {
pub fn new(neuron_id: usize) -> Self {
Self {
neuron_id,
spikes: Vec::new(),
}
}
pub fn add_spike(&mut self, timestamp_us: u64) {
self.spikes.push(timestamp_us);
}
pub fn neuron_id(&self) -> usize {
self.neuron_id
}
pub fn spike_count(&self) -> usize {
self.spikes.len()
}
pub fn spikes(&self) -> &[u64] {
&self.spikes
}
pub fn firing_rate(&self, duration_us: u64) -> f64 {
if duration_us == 0 {
return 0.0;
}
let duration_s = duration_us as f64 / 1_000_000.0;
self.spikes.len() as f64 / duration_s
}
pub fn inter_spike_intervals(&self) -> Vec<u64> {
if self.spikes.len() < 2 {
return Vec::new();
}
self.spikes
.windows(2)
.map(|w| w[1].saturating_sub(w[0]))
.collect()
}
}
#[derive(Debug, Clone)]
pub struct LIFNeuron {
id: usize,
tau: f64,
resistance: f64,
v_rest: f64,
v_thresh: f64,
v_mem: f64,
refrac_period_us: u64,
last_spike_us: Option<u64>,
}
impl LIFNeuron {
pub fn new(id: usize, tau: f64, resistance: f64, v_rest: f64, v_thresh: f64) -> Self {
Self {
id,
tau,
resistance,
v_rest,
v_thresh,
v_mem: v_rest,
refrac_period_us: 2000, last_spike_us: None,
}
}
pub fn id(&self) -> usize {
self.id
}
pub fn membrane_potential(&self) -> f64 {
self.v_mem
}
pub fn update(&mut self, current: f64, dt_us: u64, current_time_us: u64) -> Option<u64> {
if let Some(last_spike) = self.last_spike_us {
if current_time_us < last_spike + self.refrac_period_us {
return None;
}
}
let dt = dt_us as f64 / 1_000_000.0;
let dv = ((-(self.v_mem - self.v_rest) + self.resistance * current) / self.tau) * dt;
self.v_mem += dv;
if self.v_mem >= self.v_thresh {
self.v_mem = self.v_rest; self.last_spike_us = Some(current_time_us);
Some(current_time_us)
} else {
None
}
}
pub fn reset(&mut self) {
self.v_mem = self.v_rest;
self.last_spike_us = None;
}
pub fn set_refractory_period(&mut self, period_us: u64) {
self.refrac_period_us = period_us;
}
}
#[derive(Debug, Clone)]
pub struct IzhikevichNeuron {
id: usize,
a: f64,
b: f64,
c: f64,
d: f64,
v: f64,
u: f64,
}
impl IzhikevichNeuron {
pub fn new(id: usize, a: f64, b: f64, c: f64, d: f64) -> Self {
Self {
id,
a,
b,
c,
d,
v: c, u: b * c,
}
}
pub fn regular_spiking(id: usize) -> Self {
Self::new(id, 0.02, 0.2, -65.0, 8.0)
}
pub fn fast_spiking(id: usize) -> Self {
Self::new(id, 0.1, 0.2, -65.0, 2.0)
}
pub fn bursting(id: usize) -> Self {
Self::new(id, 0.02, 0.2, -50.0, 2.0)
}
pub fn id(&self) -> usize {
self.id
}
pub fn membrane_potential(&self) -> f64 {
self.v
}
pub fn update(&mut self, current: f64, dt_us: u64, current_time_us: u64) -> Option<u64> {
let dt = dt_us as f64 / 1000.0;
let v_squared = self.v * self.v;
let dv = (0.04 * v_squared + 5.0 * self.v + 140.0 - self.u + current) * dt;
let du = (self.a * (self.b * self.v - self.u)) * dt;
self.v += dv;
self.u += du;
if self.v >= 30.0 {
self.v = self.c;
self.u += self.d;
Some(current_time_us)
} else {
None
}
}
pub fn reset(&mut self) {
self.v = self.c;
self.u = self.b * self.c;
}
}
#[derive(Debug, Clone)]
pub struct STDPSynapse {
pre_id: usize,
post_id: usize,
weight: f64,
a_plus: f64,
a_minus: f64,
tau_plus: f64,
tau_minus: f64,
last_pre_spike_us: Option<u64>,
last_post_spike_us: Option<u64>,
}
impl STDPSynapse {
pub fn new(
pre_id: usize,
post_id: usize,
initial_weight: f64,
a_plus: f64,
a_minus: f64,
) -> Self {
Self {
pre_id,
post_id,
weight: initial_weight,
a_plus,
a_minus,
tau_plus: 20.0, tau_minus: 20.0, last_pre_spike_us: None,
last_post_spike_us: None,
}
}
pub fn weight(&self) -> f64 {
self.weight
}
pub fn pre_id(&self) -> usize {
self.pre_id
}
pub fn post_id(&self) -> usize {
self.post_id
}
pub fn on_pre_spike(&mut self, spike_time_us: u64) {
self.last_pre_spike_us = Some(spike_time_us);
if let Some(last_post) = self.last_post_spike_us {
if last_post < spike_time_us {
let dt = ((spike_time_us - last_post) as f64) / 1000.0; let delta_w = -self.a_minus * (-dt / self.tau_minus).exp();
self.weight += delta_w;
self.weight = self.weight.max(0.0); }
}
}
pub fn on_post_spike(&mut self, spike_time_us: u64) {
self.last_post_spike_us = Some(spike_time_us);
if let Some(last_pre) = self.last_pre_spike_us {
if last_pre < spike_time_us {
let dt = ((spike_time_us - last_pre) as f64) / 1000.0; let delta_w = self.a_plus * (-dt / self.tau_plus).exp();
self.weight += delta_w;
}
}
}
pub fn set_time_constants(&mut self, tau_plus: f64, tau_minus: f64) {
self.tau_plus = tau_plus;
self.tau_minus = tau_minus;
}
pub fn reset(&mut self) {
self.last_pre_spike_us = None;
self.last_post_spike_us = None;
}
}
#[derive(Debug, Clone)]
pub struct NeuromorphicCore {
id: usize,
max_neurons: usize,
max_synapses: usize,
neuron_count: usize,
synapse_count: usize,
}
impl NeuromorphicCore {
pub fn new(id: usize, max_neurons: usize, max_synapses: usize) -> Self {
Self {
id,
max_neurons,
max_synapses,
neuron_count: 0,
synapse_count: 0,
}
}
pub fn loihi_core(id: usize) -> Self {
Self::new(id, 128, 128 * 1024)
}
pub fn truenorth_core(id: usize) -> Self {
Self::new(id, 256, 64 * 1024)
}
pub fn id(&self) -> usize {
self.id
}
pub fn can_add_neuron(&self) -> bool {
self.neuron_count < self.max_neurons
}
pub fn can_add_synapse(&self) -> bool {
self.synapse_count < self.max_synapses
}
pub fn add_neuron(&mut self) -> Result<usize, &'static str> {
if !self.can_add_neuron() {
return Err("Core neuron capacity exceeded");
}
let neuron_id = self.neuron_count;
self.neuron_count += 1;
Ok(neuron_id)
}
pub fn add_synapse(&mut self) -> Result<usize, &'static str> {
if !self.can_add_synapse() {
return Err("Core synapse capacity exceeded");
}
let synapse_id = self.synapse_count;
self.synapse_count += 1;
Ok(synapse_id)
}
pub fn utilization(&self) -> CoreUtilization {
CoreUtilization {
neuron_util: self.neuron_count as f64 / self.max_neurons as f64,
synapse_util: self.synapse_count as f64 / self.max_synapses as f64,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct CoreUtilization {
pub neuron_util: f64,
pub synapse_util: f64,
}
#[derive(Debug, Clone)]
pub struct EventDrivenSimulation {
current_time_us: u64,
event_queue: Vec<SpikeEvent>,
}
impl EventDrivenSimulation {
pub fn new() -> Self {
Self {
current_time_us: 0,
event_queue: Vec::new(),
}
}
pub fn current_time_us(&self) -> u64 {
self.current_time_us
}
pub fn schedule_event(&mut self, event: SpikeEvent) {
self.event_queue.push(event);
self.event_queue.sort(); }
pub fn next_event(&mut self) -> Option<SpikeEvent> {
if self.event_queue.is_empty() {
return None;
}
let event = self.event_queue.remove(0);
self.current_time_us = event.timestamp_us();
Some(event)
}
pub fn has_events(&self) -> bool {
!self.event_queue.is_empty()
}
pub fn event_count(&self) -> usize {
self.event_queue.len()
}
pub fn advance_to(&mut self, time_us: u64) {
self.current_time_us = time_us;
}
pub fn reset(&mut self) {
self.current_time_us = 0;
self.event_queue.clear();
}
}
impl Default for EventDrivenSimulation {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub enum SpikeEncoding {
Rate,
Temporal,
Population,
Phase,
}
#[derive(Debug, Clone)]
pub struct RateEncoder {
max_rate: f64,
window_us: u64,
}
impl RateEncoder {
pub fn new(max_rate: f64, window_us: u64) -> Self {
Self {
max_rate,
window_us,
}
}
pub fn encode(&self, value: f64) -> usize {
let rate = value.max(0.0).min(1.0) * self.max_rate;
let window_s = self.window_us as f64 / 1_000_000.0;
(rate * window_s).round() as usize
}
pub fn max_rate(&self) -> f64 {
self.max_rate
}
pub fn window_us(&self) -> u64 {
self.window_us
}
}
#[derive(Debug, Clone)]
pub struct RateDecoder {
window_us: u64,
}
impl RateDecoder {
pub fn new(window_us: u64) -> Self {
Self { window_us }
}
pub fn decode(&self, spike_train: &SpikeTrain) -> f64 {
spike_train.firing_rate(self.window_us)
}
pub fn window_us(&self) -> u64 {
self.window_us
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spike_event_creation() {
let event = SpikeEvent::new(0, 1000);
assert_eq!(event.neuron_id(), 0);
assert_eq!(event.timestamp_us(), 1000);
assert_eq!(event.timestamp_ms(), 1.0);
assert_eq!(event.timestamp_s(), 0.001);
}
#[test]
fn test_spike_event_ordering() {
let event1 = SpikeEvent::new(0, 1000);
let event2 = SpikeEvent::new(1, 2000);
assert!(event1 < event2);
}
#[test]
fn test_spike_train() {
let mut train = SpikeTrain::new(0);
train.add_spike(1000);
train.add_spike(2000);
train.add_spike(3000);
assert_eq!(train.spike_count(), 3);
assert_eq!(train.neuron_id(), 0);
let rate = train.firing_rate(10_000); assert!((rate - 300.0).abs() < 1.0);
let isis = train.inter_spike_intervals();
assert_eq!(isis.len(), 2);
assert_eq!(isis[0], 1000);
assert_eq!(isis[1], 1000);
}
#[test]
fn test_lif_neuron() {
let mut neuron = LIFNeuron::new(0, 0.02, 0.2, -65.0, -50.0);
assert_eq!(neuron.id(), 0);
assert_eq!(neuron.membrane_potential(), -65.0);
let spike = neuron.update(100.0, 1000, 1000);
assert!(spike.is_some() || spike.is_none());
neuron.reset();
assert_eq!(neuron.membrane_potential(), -65.0);
}
#[test]
fn test_izhikevich_neuron() {
let mut neuron = IzhikevichNeuron::regular_spiking(0);
assert_eq!(neuron.id(), 0);
let spike = neuron.update(10.0, 1000, 1000);
assert!(spike.is_some() || spike.is_none());
neuron.reset();
}
#[test]
fn test_izhikevich_neuron_types() {
let _rs = IzhikevichNeuron::regular_spiking(0);
let _fs = IzhikevichNeuron::fast_spiking(1);
let _burst = IzhikevichNeuron::bursting(2);
}
#[test]
fn test_stdp_synapse() {
let mut synapse = STDPSynapse::new(0, 1, 0.5, 0.02, 0.02);
assert_eq!(synapse.pre_id(), 0);
assert_eq!(synapse.post_id(), 1);
assert_eq!(synapse.weight(), 0.5);
synapse.on_pre_spike(1000);
synapse.on_post_spike(2000);
assert!(synapse.weight() > 0.5);
synapse.reset();
}
#[test]
fn test_neuromorphic_core() {
let mut core = NeuromorphicCore::loihi_core(0);
assert_eq!(core.id(), 0);
assert!(core.can_add_neuron());
assert!(core.can_add_synapse());
let neuron_id = core.add_neuron().expect("add_neuron should succeed");
assert_eq!(neuron_id, 0);
let synapse_id = core.add_synapse().expect("add_synapse should succeed");
assert_eq!(synapse_id, 0);
let util = core.utilization();
assert!(util.neuron_util > 0.0);
assert!(util.synapse_util > 0.0);
}
#[test]
fn test_truenorth_core() {
let core = NeuromorphicCore::truenorth_core(0);
assert_eq!(core.id(), 0);
}
#[test]
fn test_event_driven_simulation() {
let mut sim = EventDrivenSimulation::new();
assert_eq!(sim.current_time_us(), 0);
assert!(!sim.has_events());
sim.schedule_event(SpikeEvent::new(0, 1000));
sim.schedule_event(SpikeEvent::new(1, 500));
assert!(sim.has_events());
assert_eq!(sim.event_count(), 2);
let event1 = sim.next_event().expect("next_event should return event");
assert_eq!(event1.timestamp_us(), 500);
let event2 = sim.next_event().expect("next_event should return event");
assert_eq!(event2.timestamp_us(), 1000);
assert!(!sim.has_events());
sim.reset();
assert_eq!(sim.current_time_us(), 0);
}
#[test]
fn test_rate_encoder() {
let encoder = RateEncoder::new(100.0, 10_000); let spike_count = encoder.encode(0.5); assert!(spike_count > 0);
assert_eq!(encoder.max_rate(), 100.0);
assert_eq!(encoder.window_us(), 10_000);
}
#[test]
fn test_rate_decoder() {
let decoder = RateDecoder::new(10_000);
let mut train = SpikeTrain::new(0);
train.add_spike(1000);
train.add_spike(2000);
train.add_spike(3000);
let rate = decoder.decode(&train);
assert!(rate > 0.0);
assert_eq!(decoder.window_us(), 10_000);
}
#[test]
fn test_spike_encoding_variants() {
let _rate = SpikeEncoding::Rate;
let _temporal = SpikeEncoding::Temporal;
let _population = SpikeEncoding::Population;
let _phase = SpikeEncoding::Phase;
}
#[test]
fn test_default_simulation() {
let sim = EventDrivenSimulation::default();
assert_eq!(sim.current_time_us(), 0);
}
#[test]
fn test_simulation_advance() {
let mut sim = EventDrivenSimulation::new();
sim.advance_to(5000);
assert_eq!(sim.current_time_us(), 5000);
}
#[test]
fn test_lif_refractory_period() {
let mut neuron = LIFNeuron::new(0, 0.02, 0.2, -65.0, -50.0);
neuron.set_refractory_period(3000); }
#[test]
fn test_stdp_time_constants() {
let mut synapse = STDPSynapse::new(0, 1, 0.5, 0.02, 0.02);
synapse.set_time_constants(15.0, 25.0);
}
#[test]
fn test_core_capacity() {
let mut core = NeuromorphicCore::new(0, 2, 2);
core.add_neuron().expect("first add_neuron should succeed");
core.add_neuron().expect("second add_neuron should succeed");
assert!(core.add_neuron().is_err());
core.add_synapse()
.expect("first add_synapse should succeed");
core.add_synapse()
.expect("second add_synapse should succeed");
assert!(core.add_synapse().is_err()); }
}