Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
413
vendor/ruvector/examples/spiking-network/src/neuron/izhikevich.rs
vendored
Normal file
413
vendor/ruvector/examples/spiking-network/src/neuron/izhikevich.rs
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
//! Izhikevich neuron model.
|
||||
//!
|
||||
//! The Izhikevich model captures rich spiking dynamics with just two variables:
|
||||
//! - Membrane potential (fast)
|
||||
//! - Recovery variable (slow)
|
||||
//!
|
||||
//! This allows simulation of 20+ different firing patterns observed in cortical neurons,
|
||||
//! while remaining computationally efficient.
|
||||
//!
|
||||
//! ## Firing Patterns
|
||||
//!
|
||||
//! - Regular spiking (RS) - most common excitatory
|
||||
//! - Intrinsically bursting (IB) - burst then regular
|
||||
//! - Chattering (CH) - fast rhythmic bursting
|
||||
//! - Fast spiking (FS) - inhibitory interneurons
|
||||
//! - Low-threshold spiking (LTS) - inhibitory
|
||||
//!
|
||||
//! ## ASIC Considerations
|
||||
//!
|
||||
//! - 2 multiply-accumulates per timestep
|
||||
//! - 1 multiplication for recovery
|
||||
//! - ~150-200 gates in digital implementation
|
||||
|
||||
use super::{NeuronParams, NeuronState, SpikingNeuron, EnergyModel};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Pre-defined Izhikevich neuron types with biological parameters.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum IzhikevichType {
|
||||
/// Regular spiking - most common excitatory cortical neuron
|
||||
RegularSpiking,
|
||||
/// Intrinsically bursting - initial burst then regular spikes
|
||||
IntrinsicallyBursting,
|
||||
/// Chattering - fast rhythmic bursting
|
||||
Chattering,
|
||||
/// Fast spiking - typical inhibitory interneuron
|
||||
FastSpiking,
|
||||
/// Low-threshold spiking - inhibitory with rebound
|
||||
LowThresholdSpiking,
|
||||
/// Thalamo-cortical - two firing modes
|
||||
ThalamoCortical,
|
||||
/// Resonator - subthreshold oscillations
|
||||
Resonator,
|
||||
}
|
||||
|
||||
impl IzhikevichType {
|
||||
/// Get parameters for this neuron type.
|
||||
pub fn params(self) -> IzhikevichParams {
|
||||
match self {
|
||||
Self::RegularSpiking => IzhikevichParams {
|
||||
a: 0.02,
|
||||
b: 0.2,
|
||||
c: -65.0,
|
||||
d: 8.0,
|
||||
threshold: 30.0,
|
||||
refractory: 0.0, // Implicit in dynamics
|
||||
},
|
||||
Self::IntrinsicallyBursting => IzhikevichParams {
|
||||
a: 0.02,
|
||||
b: 0.2,
|
||||
c: -55.0,
|
||||
d: 4.0,
|
||||
threshold: 30.0,
|
||||
refractory: 0.0,
|
||||
},
|
||||
Self::Chattering => IzhikevichParams {
|
||||
a: 0.02,
|
||||
b: 0.2,
|
||||
c: -50.0,
|
||||
d: 2.0,
|
||||
threshold: 30.0,
|
||||
refractory: 0.0,
|
||||
},
|
||||
Self::FastSpiking => IzhikevichParams {
|
||||
a: 0.1,
|
||||
b: 0.2,
|
||||
c: -65.0,
|
||||
d: 2.0,
|
||||
threshold: 30.0,
|
||||
refractory: 0.0,
|
||||
},
|
||||
Self::LowThresholdSpiking => IzhikevichParams {
|
||||
a: 0.02,
|
||||
b: 0.25,
|
||||
c: -65.0,
|
||||
d: 2.0,
|
||||
threshold: 30.0,
|
||||
refractory: 0.0,
|
||||
},
|
||||
Self::ThalamoCortical => IzhikevichParams {
|
||||
a: 0.02,
|
||||
b: 0.25,
|
||||
c: -65.0,
|
||||
d: 0.05,
|
||||
threshold: 30.0,
|
||||
refractory: 0.0,
|
||||
},
|
||||
Self::Resonator => IzhikevichParams {
|
||||
a: 0.1,
|
||||
b: 0.26,
|
||||
c: -65.0,
|
||||
d: 2.0,
|
||||
threshold: 30.0,
|
||||
refractory: 0.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for Izhikevich neuron model.
|
||||
///
|
||||
/// The model equations are:
|
||||
/// ```text
|
||||
/// dv/dt = 0.04*v² + 5*v + 140 - u + I
|
||||
/// du/dt = a*(b*v - u)
|
||||
/// if v >= 30 mV: v = c, u = u + d
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct IzhikevichParams {
|
||||
/// Time scale of recovery variable (smaller = slower recovery)
|
||||
pub a: f32,
|
||||
/// Sensitivity of recovery to subthreshold membrane potential
|
||||
pub b: f32,
|
||||
/// After-spike reset value of membrane potential (mV)
|
||||
pub c: f32,
|
||||
/// After-spike reset increment of recovery variable
|
||||
pub d: f32,
|
||||
/// Spike threshold (mV) - typically 30
|
||||
pub threshold: f32,
|
||||
/// Explicit refractory period (ms) - usually 0 for Izhikevich
|
||||
pub refractory: f32,
|
||||
}
|
||||
|
||||
impl Default for IzhikevichParams {
|
||||
fn default() -> Self {
|
||||
IzhikevichType::RegularSpiking.params()
|
||||
}
|
||||
}
|
||||
|
||||
impl NeuronParams for IzhikevichParams {
|
||||
fn threshold(&self) -> f32 {
|
||||
self.threshold
|
||||
}
|
||||
|
||||
fn reset_potential(&self) -> f32 {
|
||||
self.c
|
||||
}
|
||||
|
||||
fn resting_potential(&self) -> f32 {
|
||||
// Resting potential is approximately -65 to -70 mV
|
||||
-65.0
|
||||
}
|
||||
|
||||
fn refractory_period(&self) -> f32 {
|
||||
self.refractory
|
||||
}
|
||||
|
||||
fn validate(&self) -> Option<String> {
|
||||
if self.a <= 0.0 || self.a > 1.0 {
|
||||
return Some("a should be in (0, 1]".into());
|
||||
}
|
||||
if self.threshold < 0.0 {
|
||||
return Some("threshold should be positive".into());
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Izhikevich neuron model.
|
||||
///
|
||||
/// Provides rich spiking dynamics while remaining computationally efficient.
|
||||
/// The two-variable model captures most qualitative behaviors of biological neurons.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IzhikevichNeuron {
|
||||
/// Model parameters
|
||||
params: IzhikevichParams,
|
||||
/// Membrane potential (mV)
|
||||
v: f32,
|
||||
/// Recovery variable (dimensionless)
|
||||
u: f32,
|
||||
/// Accumulated input current
|
||||
input_current: f32,
|
||||
/// Time since last spike
|
||||
time_since_spike: Option<f32>,
|
||||
/// Refractory countdown (if explicit refractory used)
|
||||
refractory_remaining: f32,
|
||||
}
|
||||
|
||||
impl IzhikevichNeuron {
|
||||
/// Create neuron from predefined type.
|
||||
pub fn from_type(neuron_type: IzhikevichType) -> Self {
|
||||
Self::new(neuron_type.params())
|
||||
}
|
||||
|
||||
/// Create regular spiking neuron (most common).
|
||||
pub fn regular_spiking() -> Self {
|
||||
Self::from_type(IzhikevichType::RegularSpiking)
|
||||
}
|
||||
|
||||
/// Create fast spiking neuron (inhibitory).
|
||||
pub fn fast_spiking() -> Self {
|
||||
Self::from_type(IzhikevichType::FastSpiking)
|
||||
}
|
||||
|
||||
/// Get recovery variable.
|
||||
pub fn recovery(&self) -> f32 {
|
||||
self.u
|
||||
}
|
||||
}
|
||||
|
||||
impl SpikingNeuron for IzhikevichNeuron {
|
||||
type Params = IzhikevichParams;
|
||||
|
||||
fn new(params: IzhikevichParams) -> Self {
|
||||
// Initialize at resting state
|
||||
let v = params.c;
|
||||
let u = params.b * v;
|
||||
Self {
|
||||
params,
|
||||
v,
|
||||
u,
|
||||
input_current: 0.0,
|
||||
time_since_spike: None,
|
||||
refractory_remaining: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> NeuronState {
|
||||
NeuronState {
|
||||
membrane_potential: self.v,
|
||||
time_since_spike: self.time_since_spike,
|
||||
is_refractory: self.refractory_remaining > 0.0,
|
||||
input_current: self.input_current,
|
||||
}
|
||||
}
|
||||
|
||||
fn params(&self) -> &Self::Params {
|
||||
&self.params
|
||||
}
|
||||
|
||||
fn receive_input(&mut self, current: f32) {
|
||||
self.input_current += current;
|
||||
}
|
||||
|
||||
fn update(&mut self, dt: f32) -> bool {
|
||||
// Update time since spike
|
||||
if let Some(ref mut t) = self.time_since_spike {
|
||||
*t += dt;
|
||||
}
|
||||
|
||||
// Handle explicit refractory if set
|
||||
if self.refractory_remaining > 0.0 {
|
||||
self.refractory_remaining -= dt;
|
||||
self.input_current = 0.0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Izhikevich dynamics with Euler integration
|
||||
// For numerical stability, use two half-steps for v
|
||||
let i = self.input_current;
|
||||
|
||||
// Half step 1
|
||||
let dv1 = 0.04 * self.v * self.v + 5.0 * self.v + 140.0 - self.u + i;
|
||||
self.v += dv1 * dt * 0.5;
|
||||
|
||||
// Half step 2
|
||||
let dv2 = 0.04 * self.v * self.v + 5.0 * self.v + 140.0 - self.u + i;
|
||||
self.v += dv2 * dt * 0.5;
|
||||
|
||||
// Recovery variable
|
||||
let du = self.params.a * (self.params.b * self.v - self.u);
|
||||
self.u += du * dt;
|
||||
|
||||
// Clear input
|
||||
self.input_current = 0.0;
|
||||
|
||||
// Spike check
|
||||
if self.v >= self.params.threshold {
|
||||
// Spike!
|
||||
self.v = self.params.c;
|
||||
self.u += self.params.d;
|
||||
self.time_since_spike = Some(0.0);
|
||||
self.refractory_remaining = self.params.refractory;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.v = self.params.c;
|
||||
self.u = self.params.b * self.v;
|
||||
self.input_current = 0.0;
|
||||
self.time_since_spike = None;
|
||||
self.refractory_remaining = 0.0;
|
||||
}
|
||||
|
||||
fn is_refractory(&self) -> bool {
|
||||
self.refractory_remaining > 0.0
|
||||
}
|
||||
|
||||
fn membrane_potential(&self) -> f32 {
|
||||
self.v
|
||||
}
|
||||
|
||||
fn time_since_spike(&self) -> Option<f32> {
|
||||
self.time_since_spike
|
||||
}
|
||||
}
|
||||
|
||||
impl EnergyModel for IzhikevichNeuron {
|
||||
fn update_energy(&self) -> f32 {
|
||||
// Estimate: 3 multiplies, 6 adds, 1 comparison
|
||||
// More complex than LIF
|
||||
5.0 // picojoules
|
||||
}
|
||||
|
||||
fn spike_energy(&self) -> f32 {
|
||||
10.0 // picojoules
|
||||
}
|
||||
|
||||
fn silicon_area(&self) -> f32 {
|
||||
// ~150-200 gates at 28nm
|
||||
17.5 // square micrometers
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_izhikevich_types() {
|
||||
// Test all predefined types can be created
|
||||
let types = [
|
||||
IzhikevichType::RegularSpiking,
|
||||
IzhikevichType::IntrinsicallyBursting,
|
||||
IzhikevichType::Chattering,
|
||||
IzhikevichType::FastSpiking,
|
||||
IzhikevichType::LowThresholdSpiking,
|
||||
IzhikevichType::ThalamoCortical,
|
||||
IzhikevichType::Resonator,
|
||||
];
|
||||
|
||||
for neuron_type in types {
|
||||
let neuron = IzhikevichNeuron::from_type(neuron_type);
|
||||
assert!(neuron.params().validate().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regular_spiking_behavior() {
|
||||
let mut neuron = IzhikevichNeuron::regular_spiking();
|
||||
let mut spike_count = 0;
|
||||
|
||||
// Inject constant current and count spikes
|
||||
for _ in 0..1000 {
|
||||
neuron.receive_input(10.0);
|
||||
if neuron.update(1.0) {
|
||||
spike_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Should spike regularly
|
||||
assert!(spike_count > 10, "Regular spiking neuron should fire regularly");
|
||||
assert!(spike_count < 200, "Should not fire too fast");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fast_spiking_behavior() {
|
||||
let mut fs = IzhikevichNeuron::fast_spiking();
|
||||
let mut rs = IzhikevichNeuron::regular_spiking();
|
||||
|
||||
let mut fs_spikes = 0;
|
||||
let mut rs_spikes = 0;
|
||||
|
||||
// Same input to both
|
||||
for _ in 0..1000 {
|
||||
fs.receive_input(14.0);
|
||||
rs.receive_input(14.0);
|
||||
|
||||
if fs.update(1.0) { fs_spikes += 1; }
|
||||
if rs.update(1.0) { rs_spikes += 1; }
|
||||
}
|
||||
|
||||
// Fast spiking should fire more often
|
||||
assert!(fs_spikes > rs_spikes, "Fast spiking should fire more than regular");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recovery_dynamics() {
|
||||
let mut neuron = IzhikevichNeuron::regular_spiking();
|
||||
let initial_u = neuron.recovery();
|
||||
|
||||
// After spike, recovery should increase
|
||||
neuron.v = 35.0; // Above threshold
|
||||
neuron.update(1.0);
|
||||
|
||||
assert!(neuron.recovery() > initial_u, "Recovery should increase after spike");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subthreshold_dynamics() {
|
||||
let mut neuron = IzhikevichNeuron::regular_spiking();
|
||||
|
||||
// Weak input should not cause immediate spike
|
||||
neuron.receive_input(2.0);
|
||||
assert!(!neuron.update(1.0));
|
||||
|
||||
// Voltage should rise but not spike
|
||||
assert!(neuron.membrane_potential() > neuron.params.c);
|
||||
}
|
||||
}
|
||||
316
vendor/ruvector/examples/spiking-network/src/neuron/lif.rs
vendored
Normal file
316
vendor/ruvector/examples/spiking-network/src/neuron/lif.rs
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
//! Leaky Integrate-and-Fire (LIF) neuron model.
|
||||
//!
|
||||
//! The LIF model is the workhorse of neuromorphic computing:
|
||||
//! - Simple dynamics: membrane voltage leaks toward rest
|
||||
//! - Spikes when threshold crossed
|
||||
//! - Resets and enters refractory period
|
||||
//!
|
||||
//! ## ASIC Benefits
|
||||
//!
|
||||
//! - Single multiply-accumulate per timestep
|
||||
//! - No division (pre-computed decay factor)
|
||||
//! - 2-3 comparisons per update
|
||||
//! - ~100 gates in digital implementation
|
||||
|
||||
use super::{NeuronParams, NeuronState, SpikingNeuron, EnergyModel};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Parameters for LIF neuron.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LIFParams {
|
||||
/// Membrane time constant (ms) - controls leak rate
|
||||
pub tau_m: f32,
|
||||
/// Spike threshold (mV)
|
||||
pub threshold: f32,
|
||||
/// Reset potential after spike (mV)
|
||||
pub reset: f32,
|
||||
/// Resting membrane potential (mV)
|
||||
pub resting: f32,
|
||||
/// Refractory period (ms)
|
||||
pub refractory: f32,
|
||||
/// Membrane resistance (MOhm) - scales input current
|
||||
pub resistance: f32,
|
||||
}
|
||||
|
||||
impl Default for LIFParams {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tau_m: 20.0,
|
||||
threshold: -50.0,
|
||||
reset: -70.0,
|
||||
resting: -65.0,
|
||||
refractory: 2.0,
|
||||
resistance: 10.0, // 10 MOhm typical for cortical neurons
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NeuronParams for LIFParams {
|
||||
fn threshold(&self) -> f32 {
|
||||
self.threshold
|
||||
}
|
||||
|
||||
fn reset_potential(&self) -> f32 {
|
||||
self.reset
|
||||
}
|
||||
|
||||
fn resting_potential(&self) -> f32 {
|
||||
self.resting
|
||||
}
|
||||
|
||||
fn refractory_period(&self) -> f32 {
|
||||
self.refractory
|
||||
}
|
||||
|
||||
fn validate(&self) -> Option<String> {
|
||||
if self.tau_m <= 0.0 {
|
||||
return Some("tau_m must be positive".into());
|
||||
}
|
||||
if self.threshold <= self.reset {
|
||||
return Some("threshold must be greater than reset".into());
|
||||
}
|
||||
if self.refractory < 0.0 {
|
||||
return Some("refractory period cannot be negative".into());
|
||||
}
|
||||
if self.resistance <= 0.0 {
|
||||
return Some("resistance must be positive".into());
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Leaky Integrate-and-Fire neuron.
|
||||
///
|
||||
/// Implements the differential equation:
|
||||
/// ```text
|
||||
/// τ_m * dV/dt = -(V - V_rest) + R * I
|
||||
/// ```
|
||||
///
|
||||
/// With spike condition: V ≥ V_threshold
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LIFNeuron {
|
||||
/// Neuron parameters
|
||||
params: LIFParams,
|
||||
/// Current membrane potential (mV)
|
||||
membrane_potential: f32,
|
||||
/// Time remaining in refractory period (ms)
|
||||
refractory_remaining: f32,
|
||||
/// Accumulated input current for this timestep
|
||||
input_current: f32,
|
||||
/// Time since last spike (ms)
|
||||
time_since_spike: Option<f32>,
|
||||
/// Pre-computed decay factor for efficiency
|
||||
decay_factor: f32,
|
||||
}
|
||||
|
||||
impl LIFNeuron {
|
||||
/// Create LIF neuron with default parameters.
|
||||
pub fn with_defaults() -> Self {
|
||||
Self::new(LIFParams::default())
|
||||
}
|
||||
|
||||
/// Pre-compute decay factor for given timestep.
|
||||
///
|
||||
/// This avoids division in the hot path.
|
||||
/// decay = exp(-dt / tau_m) ≈ 1 - dt/tau_m for small dt
|
||||
fn compute_decay(&self, dt: f32) -> f32 {
|
||||
// Use linear approximation for ASIC compatibility
|
||||
// Error < 1% for dt < 2ms with tau_m = 20ms
|
||||
1.0 - dt / self.params.tau_m
|
||||
}
|
||||
|
||||
/// Get the pre-computed decay factor.
|
||||
pub fn decay_factor(&self) -> f32 {
|
||||
self.decay_factor
|
||||
}
|
||||
}
|
||||
|
||||
impl SpikingNeuron for LIFNeuron {
|
||||
type Params = LIFParams;
|
||||
|
||||
fn new(params: LIFParams) -> Self {
|
||||
let decay_factor = 1.0 - 1.0 / params.tau_m; // For dt=1ms default
|
||||
Self {
|
||||
params,
|
||||
membrane_potential: params.resting,
|
||||
refractory_remaining: 0.0,
|
||||
input_current: 0.0,
|
||||
time_since_spike: None,
|
||||
decay_factor,
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> NeuronState {
|
||||
NeuronState {
|
||||
membrane_potential: self.membrane_potential,
|
||||
time_since_spike: self.time_since_spike,
|
||||
is_refractory: self.refractory_remaining > 0.0,
|
||||
input_current: self.input_current,
|
||||
}
|
||||
}
|
||||
|
||||
fn params(&self) -> &Self::Params {
|
||||
&self.params
|
||||
}
|
||||
|
||||
fn receive_input(&mut self, current: f32) {
|
||||
// Accumulate input - this is the sparse event
|
||||
self.input_current += current;
|
||||
}
|
||||
|
||||
fn update(&mut self, dt: f32) -> bool {
|
||||
// Update time since spike
|
||||
if let Some(ref mut t) = self.time_since_spike {
|
||||
*t += dt;
|
||||
}
|
||||
|
||||
// Handle refractory period
|
||||
if self.refractory_remaining > 0.0 {
|
||||
self.refractory_remaining -= dt;
|
||||
self.input_current = 0.0; // Clear accumulated input
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute decay factor for this timestep
|
||||
let decay = self.compute_decay(dt);
|
||||
|
||||
// LIF dynamics: V = decay * V + (1-decay) * V_rest + R * I * dt / tau_m
|
||||
// Simplified: V = decay * (V - V_rest) + V_rest + R * I * dt / tau_m
|
||||
let v_diff = self.membrane_potential - self.params.resting;
|
||||
let input_term = self.params.resistance * self.input_current * dt / self.params.tau_m;
|
||||
|
||||
self.membrane_potential = decay * v_diff + self.params.resting + input_term;
|
||||
|
||||
// Clear input for next timestep
|
||||
self.input_current = 0.0;
|
||||
|
||||
// Check for spike
|
||||
if self.membrane_potential >= self.params.threshold {
|
||||
// Spike!
|
||||
self.membrane_potential = self.params.reset;
|
||||
self.refractory_remaining = self.params.refractory;
|
||||
self.time_since_spike = Some(0.0);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.membrane_potential = self.params.resting;
|
||||
self.refractory_remaining = 0.0;
|
||||
self.input_current = 0.0;
|
||||
self.time_since_spike = None;
|
||||
}
|
||||
|
||||
fn is_refractory(&self) -> bool {
|
||||
self.refractory_remaining > 0.0
|
||||
}
|
||||
|
||||
fn membrane_potential(&self) -> f32 {
|
||||
self.membrane_potential
|
||||
}
|
||||
|
||||
fn time_since_spike(&self) -> Option<f32> {
|
||||
self.time_since_spike
|
||||
}
|
||||
}
|
||||
|
||||
impl EnergyModel for LIFNeuron {
|
||||
fn update_energy(&self) -> f32 {
|
||||
// Estimate: 1 multiply, 3 adds, 2 comparisons
|
||||
// At 28nm: ~0.5 pJ per operation
|
||||
3.0 // picojoules
|
||||
}
|
||||
|
||||
fn spike_energy(&self) -> f32 {
|
||||
// Spike packet generation and routing
|
||||
10.0 // picojoules
|
||||
}
|
||||
|
||||
fn silicon_area(&self) -> f32 {
|
||||
// ~100 gates at 28nm ≈ 0.1 μm² per gate
|
||||
10.0 // square micrometers
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_lif_default_creation() {
|
||||
let neuron = LIFNeuron::with_defaults();
|
||||
assert_eq!(neuron.membrane_potential(), -65.0);
|
||||
assert!(!neuron.is_refractory());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lif_spike_generation() {
|
||||
let mut neuron = LIFNeuron::with_defaults();
|
||||
|
||||
// Inject strong current
|
||||
for _ in 0..100 {
|
||||
neuron.receive_input(5.0); // Strong input
|
||||
if neuron.update(1.0) {
|
||||
// Spiked!
|
||||
assert!(neuron.is_refractory());
|
||||
assert_eq!(neuron.membrane_potential(), neuron.params.reset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
panic!("Neuron should have spiked with strong input");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lif_refractory_period() {
|
||||
let params = LIFParams {
|
||||
refractory: 5.0,
|
||||
..Default::default()
|
||||
};
|
||||
let mut neuron = LIFNeuron::new(params);
|
||||
|
||||
// Force a spike
|
||||
neuron.membrane_potential = params.threshold + 1.0;
|
||||
neuron.update(1.0);
|
||||
|
||||
// Should be refractory
|
||||
assert!(neuron.is_refractory());
|
||||
|
||||
// Should not spike during refractory
|
||||
neuron.receive_input(100.0);
|
||||
assert!(!neuron.update(1.0));
|
||||
|
||||
// After refractory period
|
||||
for _ in 0..5 {
|
||||
neuron.update(1.0);
|
||||
}
|
||||
assert!(!neuron.is_refractory());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lif_leak_to_rest() {
|
||||
let mut neuron = LIFNeuron::with_defaults();
|
||||
neuron.membrane_potential = -55.0; // Above resting
|
||||
|
||||
// Without input, should decay toward resting
|
||||
for _ in 0..100 {
|
||||
neuron.update(1.0);
|
||||
}
|
||||
|
||||
// Should be close to resting potential
|
||||
assert!((neuron.membrane_potential() - (-65.0)).abs() < 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_params_validation() {
|
||||
let invalid = LIFParams {
|
||||
tau_m: -1.0,
|
||||
..Default::default()
|
||||
};
|
||||
assert!(invalid.validate().is_some());
|
||||
|
||||
let valid = LIFParams::default();
|
||||
assert!(valid.validate().is_none());
|
||||
}
|
||||
}
|
||||
41
vendor/ruvector/examples/spiking-network/src/neuron/mod.rs
vendored
Normal file
41
vendor/ruvector/examples/spiking-network/src/neuron/mod.rs
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
//! Spiking neuron models.
|
||||
//!
|
||||
//! This module provides biologically-inspired neuron models optimized for
|
||||
//! event-driven computation. Neurons stay silent until a threshold is crossed,
|
||||
//! eliminating wasted cycles on unchanged state.
|
||||
//!
|
||||
//! ## Available Models
|
||||
//!
|
||||
//! - **LIF (Leaky Integrate-and-Fire)**: Simple, efficient, ASIC-friendly
|
||||
//! - **Izhikevich**: Rich dynamics, biologically plausible spiking patterns
|
||||
//!
|
||||
//! ## ASIC Considerations
|
||||
//!
|
||||
//! These models are designed for minimal silicon cost:
|
||||
//! - Fixed-point compatible arithmetic
|
||||
//! - No division operations in hot paths
|
||||
//! - Predictable memory access patterns
|
||||
//! - Branch-friendly state machines
|
||||
|
||||
mod lif;
|
||||
mod izhikevich;
|
||||
mod traits;
|
||||
|
||||
pub use lif::{LIFNeuron, LIFParams};
|
||||
pub use izhikevich::{IzhikevichNeuron, IzhikevichParams, IzhikevichType};
|
||||
pub use traits::{NeuronParams, SpikingNeuron, NeuronState};
|
||||
|
||||
/// Default membrane time constant (ms)
|
||||
pub const DEFAULT_TAU_M: f32 = 20.0;
|
||||
|
||||
/// Default spike threshold (mV)
|
||||
pub const DEFAULT_THRESHOLD: f32 = -50.0;
|
||||
|
||||
/// Default resting potential (mV)
|
||||
pub const DEFAULT_RESTING: f32 = -65.0;
|
||||
|
||||
/// Default reset potential (mV)
|
||||
pub const DEFAULT_RESET: f32 = -70.0;
|
||||
|
||||
/// Default refractory period (ms)
|
||||
pub const DEFAULT_REFRACTORY: f32 = 2.0;
|
||||
108
vendor/ruvector/examples/spiking-network/src/neuron/traits.rs
vendored
Normal file
108
vendor/ruvector/examples/spiking-network/src/neuron/traits.rs
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
//! Trait definitions for spiking neurons.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// State of a spiking neuron.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct NeuronState {
|
||||
/// Membrane potential (mV)
|
||||
pub membrane_potential: f32,
|
||||
/// Time since last spike (ms), None if never spiked
|
||||
pub time_since_spike: Option<f32>,
|
||||
/// Whether the neuron is currently in refractory period
|
||||
pub is_refractory: bool,
|
||||
/// Accumulated input current for this timestep
|
||||
pub input_current: f32,
|
||||
}
|
||||
|
||||
impl Default for NeuronState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
membrane_potential: -65.0, // Resting potential
|
||||
time_since_spike: None,
|
||||
is_refractory: false,
|
||||
input_current: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters that define a spiking neuron's behavior.
|
||||
pub trait NeuronParams: Clone + Send + Sync {
|
||||
/// Get the spike threshold voltage
|
||||
fn threshold(&self) -> f32;
|
||||
|
||||
/// Get the reset voltage after spike
|
||||
fn reset_potential(&self) -> f32;
|
||||
|
||||
/// Get the resting membrane potential
|
||||
fn resting_potential(&self) -> f32;
|
||||
|
||||
/// Get the refractory period in milliseconds
|
||||
fn refractory_period(&self) -> f32;
|
||||
|
||||
/// Validate parameters, returning error message if invalid
|
||||
fn validate(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
/// Core trait for spiking neuron models.
|
||||
///
|
||||
/// Implementing types should be efficient for ASIC deployment:
|
||||
/// - Avoid floating-point division in `update()`
|
||||
/// - Use predictable branching
|
||||
/// - Minimize memory footprint
|
||||
pub trait SpikingNeuron: Clone + Send + Sync {
|
||||
/// Associated parameter type
|
||||
type Params: NeuronParams;
|
||||
|
||||
/// Create a new neuron with given parameters
|
||||
fn new(params: Self::Params) -> Self;
|
||||
|
||||
/// Get current neuron state
|
||||
fn state(&self) -> NeuronState;
|
||||
|
||||
/// Get neuron parameters
|
||||
fn params(&self) -> &Self::Params;
|
||||
|
||||
/// Add input current (from incoming spikes or external input)
|
||||
///
|
||||
/// This is a sparse operation - only called when input arrives.
|
||||
fn receive_input(&mut self, current: f32);
|
||||
|
||||
/// Update neuron state for one timestep.
|
||||
///
|
||||
/// Returns `true` if the neuron fires a spike.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `dt` - Time step in milliseconds
|
||||
///
|
||||
/// # ASIC Optimization
|
||||
/// This is the hot path. Implementations should:
|
||||
/// - Use only additions and multiplications
|
||||
/// - Avoid conditional branches where possible
|
||||
/// - Use fixed-point compatible operations
|
||||
fn update(&mut self, dt: f32) -> bool;
|
||||
|
||||
/// Reset neuron to initial state
|
||||
fn reset(&mut self);
|
||||
|
||||
/// Check if neuron is in refractory period
|
||||
fn is_refractory(&self) -> bool;
|
||||
|
||||
/// Get membrane potential
|
||||
fn membrane_potential(&self) -> f32;
|
||||
|
||||
/// Get time since last spike (if any)
|
||||
fn time_since_spike(&self) -> Option<f32>;
|
||||
}
|
||||
|
||||
/// Energy estimation for ASIC cost analysis.
|
||||
pub trait EnergyModel {
|
||||
/// Estimate energy cost for a single update step (picojoules)
|
||||
fn update_energy(&self) -> f32;
|
||||
|
||||
/// Estimate energy cost for spike emission (picojoules)
|
||||
fn spike_energy(&self) -> f32;
|
||||
|
||||
/// Estimate silicon area (square micrometers)
|
||||
fn silicon_area(&self) -> f32;
|
||||
}
|
||||
Reference in New Issue
Block a user