use rand::{RngExt, SeedableRng};
use rand_xoshiro::Xoshiro256PlusPlus;
#[derive(Clone, Debug)]
pub struct QuadraticIFNeuron {
pub v: f64,
pub v_reset: f64,
pub v_peak: f64,
pub dt: f64,
}
impl QuadraticIFNeuron {
pub fn new(v_reset: f64, v_peak: f64, dt: f64) -> Self {
Self {
v: v_reset,
v_reset,
v_peak,
dt,
}
}
fn valid_numeric_contract(&self) -> bool {
self.v.is_finite()
&& self.v_reset.is_finite()
&& self.v_peak.is_finite()
&& self.dt.is_finite()
&& self.v < self.v_peak
&& self.v_reset < self.v_peak
&& self.dt > 0.0
}
pub fn step(&mut self, current: f64) -> i32 {
if !self.valid_numeric_contract() || !current.is_finite() {
return 0;
}
let increment = (self.v * self.v + current) * self.dt;
let next_v = self.v + increment;
if !(increment.is_finite() && next_v.is_finite()) {
return 0;
}
self.v = next_v;
if self.v >= self.v_peak {
self.v = self.v_reset;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_reset;
}
}
impl Default for QuadraticIFNeuron {
fn default() -> Self {
Self::new(-1.0, 1.0, 0.01)
}
}
#[derive(Clone, Debug)]
pub struct ThetaNeuron {
pub theta: f64,
pub dt: f64,
}
impl ThetaNeuron {
pub fn new(dt: f64) -> Self {
Self { theta: 0.0, dt }
}
pub fn step(&mut self, current: f64) -> i32 {
let prev = self.theta;
self.theta += ((1.0 - self.theta.cos()) + (1.0 + self.theta.cos()) * current) * self.dt;
if self.theta >= std::f64::consts::PI && prev < std::f64::consts::PI {
self.theta -= 2.0 * std::f64::consts::PI;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.theta = 0.0;
}
}
impl Default for ThetaNeuron {
fn default() -> Self {
Self::new(0.01)
}
}
#[derive(Clone, Debug)]
pub struct PerfectIntegratorNeuron {
pub v: f64,
pub c_m: f64,
pub v_threshold: f64,
pub v_reset: f64,
pub dt: f64,
}
impl PerfectIntegratorNeuron {
pub fn new(c_m: f64, v_threshold: f64, dt: f64) -> Self {
Self {
v: 0.0,
c_m,
v_threshold,
v_reset: 0.0,
dt,
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.v += (current / self.c_m) * self.dt;
if self.v >= self.v_threshold {
self.v = self.v_reset;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_reset;
}
}
impl Default for PerfectIntegratorNeuron {
fn default() -> Self {
Self::new(1.0, 1.0, 0.1)
}
}
#[derive(Clone, Debug)]
pub struct GatedLIFNeuron {
pub v: f64,
pub gate_v: f64,
pub gate_i: f64,
pub v_threshold: f64,
pub dt: f64,
}
impl GatedLIFNeuron {
pub fn new(gate_v: f64, gate_i: f64, v_threshold: f64) -> Self {
Self {
v: 0.0,
gate_v,
gate_i,
v_threshold,
dt: 1.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.v = self.gate_v * self.v + self.gate_i * current;
if self.v >= self.v_threshold {
self.v -= self.v_threshold;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = 0.0;
}
}
impl Default for GatedLIFNeuron {
fn default() -> Self {
Self::new(0.9, 1.0, 1.0)
}
}
#[derive(Clone, Debug)]
pub struct NonlinearLIFNeuron {
pub v: f64,
pub w: f64,
pub v_rest: f64,
pub v_crit: f64,
pub v_threshold: f64,
pub v_reset: f64,
pub a: f64,
pub b: f64,
pub tau_w: f64,
pub c_m: f64,
pub dt: f64,
}
impl NonlinearLIFNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
w: 0.0,
v_rest: -65.0,
v_crit: -40.0,
v_threshold: -20.0,
v_reset: -65.0,
a: 0.04,
b: 0.5,
tau_w: 100.0,
c_m: 1.0,
dt: 0.1,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
let cubic = self.a * (self.v - self.v_rest) * (self.v - self.v_crit);
self.v += (cubic - self.w + current) / self.c_m * self.dt;
self.w += (self.b * (self.v - self.v_rest) - self.w) / self.tau_w * self.dt;
if self.v >= self.v_threshold && v_prev < self.v_threshold {
self.v = self.v_reset;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.w = 0.0;
}
}
impl Default for NonlinearLIFNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct SFANeuron {
pub v: f64,
pub g_sfa: f64,
pub v_rest: f64,
pub v_reset: f64,
pub v_threshold: f64,
pub tau_m: f64,
pub tau_sfa: f64,
pub delta_g: f64,
pub e_k: f64,
pub resistance: f64,
pub dt: f64,
}
impl SFANeuron {
pub fn new() -> Self {
Self {
v: -70.0,
g_sfa: 0.0,
v_rest: -70.0,
v_reset: -70.0,
v_threshold: -50.0,
tau_m: 10.0,
tau_sfa: 200.0,
delta_g: 0.5,
e_k: -80.0,
resistance: 1.0,
dt: 1.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.v += (-(self.v - self.v_rest) - self.g_sfa * (self.v - self.e_k)
+ self.resistance * current)
/ self.tau_m
* self.dt;
self.g_sfa *= (-self.dt / self.tau_sfa).exp();
if self.v >= self.v_threshold {
self.v = self.v_reset;
self.g_sfa += self.delta_g;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.g_sfa = 0.0;
}
}
impl Default for SFANeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct MATNeuron {
pub v: f64,
pub theta1: f64,
pub theta2: f64,
pub v_rest: f64,
pub v_reset: f64,
pub v_threshold_base: f64,
pub tau_m: f64,
pub tau_1: f64,
pub tau_2: f64,
pub h1: f64,
pub h2: f64,
pub resistance: f64,
pub dt: f64,
}
impl MATNeuron {
pub fn new() -> Self {
Self {
v: -70.0,
theta1: 0.0,
theta2: 0.0,
v_rest: -70.0,
v_reset: -70.0,
v_threshold_base: -50.0,
tau_m: 10.0,
tau_1: 10.0,
tau_2: 200.0,
h1: 5.0,
h2: 3.0,
resistance: 1.0,
dt: 1.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.v += (-(self.v - self.v_rest) + self.resistance * current) / self.tau_m * self.dt;
self.theta1 *= (-self.dt / self.tau_1).exp();
self.theta2 *= (-self.dt / self.tau_2).exp();
let threshold = self.v_threshold_base + self.theta1 + self.theta2;
if self.v >= threshold {
self.v = self.v_reset;
self.theta1 += self.h1;
self.theta2 += self.h2;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.theta1 = 0.0;
self.theta2 = 0.0;
}
}
impl Default for MATNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct EscapeRateNeuron {
pub v: f64,
pub v_rest: f64,
pub v_reset: f64,
pub v_threshold: f64,
pub tau_m: f64,
pub rho_0: f64,
pub delta_u: f64,
pub resistance: f64,
pub dt: f64,
rng: Xoshiro256PlusPlus,
}
impl EscapeRateNeuron {
pub fn new(seed: u64) -> Self {
Self {
v: -70.0,
v_rest: -70.0,
v_reset: -70.0,
v_threshold: -50.0,
tau_m: 10.0,
rho_0: 0.001,
delta_u: 3.0,
resistance: 1.0,
dt: 1.0,
rng: Xoshiro256PlusPlus::seed_from_u64(seed),
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.v += (-(self.v - self.v_rest) + self.resistance * current) / self.tau_m * self.dt;
let rate = self.rho_0 * ((self.v - self.v_threshold) / self.delta_u).exp();
if self.rng.random::<f64>() < rate * self.dt {
self.v = self.v_reset;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
}
}
#[derive(Clone, Debug)]
pub struct KLIFNeuron {
pub v: f64,
pub k: f64,
pub alpha: f64,
pub v_threshold: f64,
pub v_reset: f64,
}
impl KLIFNeuron {
pub fn new(tau: f64, k: f64, dt: f64) -> Self {
Self {
v: 0.0,
k,
alpha: (-dt / tau).exp(),
v_threshold: 1.0,
v_reset: 0.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.v = self.alpha * self.v + self.k * current;
if self.v >= self.v_threshold {
self.v = self.v_reset;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = 0.0;
}
}
impl Default for KLIFNeuron {
fn default() -> Self {
Self::new(10.0, 1.0, 1.0)
}
}
#[derive(Clone, Debug)]
pub struct InhibitoryLIFNeuron {
pub v: f64,
pub inh_trace: f64,
pub alpha_m: f64,
pub alpha_inh: f64,
pub v_threshold: f64,
pub inh_strength: f64,
}
impl InhibitoryLIFNeuron {
pub fn new(tau_m: f64, tau_inh: f64, dt: f64) -> Self {
Self {
v: 0.0,
inh_trace: 0.0,
alpha_m: (-dt / tau_m).exp(),
alpha_inh: (-dt / tau_inh).exp(),
v_threshold: 1.0,
inh_strength: 0.5,
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.inh_trace *= self.alpha_inh;
self.v = self.alpha_m * self.v + current - self.inh_strength * self.inh_trace;
if self.v >= self.v_threshold {
self.v = 0.0;
self.inh_trace += 1.0;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = 0.0;
self.inh_trace = 0.0;
}
}
impl Default for InhibitoryLIFNeuron {
fn default() -> Self {
Self::new(10.0, 5.0, 1.0)
}
}
#[derive(Clone, Debug)]
pub struct ComplementaryLIFNeuron {
pub v_pos: f64,
pub v_neg: f64,
pub alpha: f64,
pub v_threshold: f64,
}
impl ComplementaryLIFNeuron {
pub fn new(tau: f64, dt: f64, v_threshold: f64) -> Self {
Self {
v_pos: 0.0,
v_neg: 0.0,
alpha: (-dt / tau).exp(),
v_threshold,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let inp_pos = current.max(0.0);
let inp_neg = (-current).max(0.0);
self.v_pos = self.alpha * self.v_pos + inp_pos;
self.v_neg = self.alpha * self.v_neg + inp_neg;
let diff = self.v_pos - self.v_neg;
if diff >= self.v_threshold {
self.v_pos = 0.0;
self.v_neg = 0.0;
1
} else if diff <= -self.v_threshold {
self.v_pos = 0.0;
self.v_neg = 0.0;
-1
} else {
0
}
}
pub fn reset(&mut self) {
self.v_pos = 0.0;
self.v_neg = 0.0;
}
}
impl Default for ComplementaryLIFNeuron {
fn default() -> Self {
Self::new(10.0, 1.0, 1.0)
}
}
#[derive(Clone, Debug)]
pub struct ParametricLIFNeuron {
pub v: f64,
pub a: f64,
pub threshold: f64,
}
impl ParametricLIFNeuron {
pub fn new(a: f64, threshold: f64) -> Self {
Self {
v: 0.0,
a,
threshold,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let alpha = 1.0 / (1.0 + (-self.a).exp());
let spike = if self.v >= self.threshold { 1 } else { 0 };
self.v = alpha * self.v * (1.0 - spike as f64) + current;
spike
}
pub fn reset(&mut self) {
self.v = 0.0;
}
}
impl Default for ParametricLIFNeuron {
fn default() -> Self {
Self::new(0.0, 1.0)
}
}
#[derive(Clone, Debug)]
pub struct NonResettingLIFNeuron {
pub v: f64,
pub theta: f64,
pub v_rest: f64,
pub theta_rest: f64,
pub delta_theta: f64,
pub tau_m: f64,
pub tau_theta: f64,
pub r_m: f64,
pub dt: f64,
}
impl NonResettingLIFNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
theta: -50.0,
v_rest: -65.0,
theta_rest: -50.0,
delta_theta: 5.0,
tau_m: 10.0,
tau_theta: 50.0,
r_m: 1.0,
dt: 0.1,
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.v += (-(self.v - self.v_rest) + self.r_m * current) / self.tau_m * self.dt;
self.theta += (-(self.theta - self.theta_rest)) / self.tau_theta * self.dt;
if self.v >= self.theta {
self.theta += self.delta_theta;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.theta = self.theta_rest;
}
}
impl Default for NonResettingLIFNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct AdaptiveThresholdIFNeuron {
pub v: f64,
pub theta: f64,
pub v_rest: f64,
pub v_reset: f64,
pub theta_rest: f64,
pub delta_theta: f64,
pub tau_m: f64,
pub tau_theta: f64,
pub dt: f64,
}
impl AdaptiveThresholdIFNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
theta: -50.0,
v_rest: -65.0,
v_reset: -65.0,
theta_rest: -50.0,
delta_theta: 5.0,
tau_m: 10.0,
tau_theta: 50.0,
dt: 0.1,
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.v += (-(self.v - self.v_rest) + current) / self.tau_m * self.dt;
self.theta += (-(self.theta - self.theta_rest)) / self.tau_theta * self.dt;
if self.v >= self.theta {
self.v = self.v_reset;
self.theta += self.delta_theta;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.theta = self.theta_rest;
}
}
impl Default for AdaptiveThresholdIFNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct SigmaDeltaNeuron {
pub sigma: f64,
pub v_threshold: f64,
}
impl SigmaDeltaNeuron {
pub fn new(v_threshold: f64) -> Self {
Self {
sigma: 0.0,
v_threshold,
}
}
pub fn step(&mut self, current: f64) -> i32 {
self.sigma += current;
if self.sigma >= self.v_threshold {
self.sigma -= self.v_threshold;
1
} else if self.sigma <= -self.v_threshold {
self.sigma += self.v_threshold;
-1
} else {
0
}
}
pub fn reset(&mut self) {
self.sigma = 0.0;
}
}
impl Default for SigmaDeltaNeuron {
fn default() -> Self {
Self::new(1.0)
}
}
#[derive(Clone, Debug)]
pub struct EnergyLIFNeuron {
pub v: f64,
pub epsilon: f64,
pub v_rest: f64,
pub v_reset: f64,
pub v_threshold: f64,
pub tau_m: f64,
pub tau_e: f64,
pub alpha: f64,
pub epsilon_0: f64,
pub resistance: f64,
pub dt: f64,
}
impl EnergyLIFNeuron {
pub fn new() -> Self {
Self {
v: -70.0,
epsilon: 1.0,
v_rest: -70.0,
v_reset: -70.0,
v_threshold: -50.0,
tau_m: 10.0,
tau_e: 500.0,
alpha: 0.1,
epsilon_0: 1.0,
resistance: 1.0,
dt: 1.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let effective_r = self.resistance * self.epsilon;
self.v += (-(self.v - self.v_rest) + effective_r * current) / self.tau_m * self.dt;
self.epsilon += (self.epsilon_0 - self.epsilon) / self.tau_e * self.dt;
if self.v >= self.v_threshold && self.epsilon > 0.1 {
self.v = self.v_reset;
self.epsilon -= self.alpha;
1
} else if self.v >= self.v_threshold {
self.v = self.v_threshold;
0
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.epsilon = self.epsilon_0;
}
}
impl Default for EnergyLIFNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct IntegerQIFNeuron {
pub v: i32,
pub k: i32,
pub v_threshold: i32,
pub v_reset: i32,
pub v_min: i32,
}
impl IntegerQIFNeuron {
pub fn new(k: i32, v_threshold: i32) -> Self {
Self {
v: 0,
k,
v_threshold,
v_reset: -v_threshold,
v_min: -2 * v_threshold,
}
}
pub fn step(&mut self, current: i32) -> i32 {
self.v = (self.v + (self.v.wrapping_mul(self.v) >> self.k) + current).max(self.v_min);
if self.v >= self.v_threshold {
self.v = self.v_reset;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = 0;
}
}
impl Default for IntegerQIFNeuron {
fn default() -> Self {
Self::new(6, 1024)
}
}
#[derive(Clone, Debug)]
pub struct ClosedFormContinuousNeuron {
pub x: f64,
pub w_tau: f64,
pub w_x: f64,
pub w_in: f64,
pub tau_base: f64,
pub bias: f64,
pub v_threshold: f64,
pub dt: f64,
}
impl ClosedFormContinuousNeuron {
pub fn new() -> Self {
Self {
x: 0.0,
w_tau: -0.5,
w_x: 0.8,
w_in: 1.0,
tau_base: 10.0,
bias: 0.0,
v_threshold: 1.0,
dt: 1.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let sigma_tau = 1.0 / (1.0 + (-(self.w_tau * current + self.bias)).exp());
let tau_eff = (self.tau_base * sigma_tau).max(0.1);
let f_target = (self.w_x * self.x + self.w_in * current).tanh();
let decay = (-self.dt / tau_eff).exp();
self.x = self.x * decay + f_target * (1.0 - decay);
if self.x >= self.v_threshold {
self.x -= self.v_threshold;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.x = 0.0;
}
}
impl Default for ClosedFormContinuousNeuron {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn qif_fires_with_positive_input() {
let mut n = QuadraticIFNeuron::default();
let total: i32 = (0..1000).map(|_| n.step(0.5)).sum();
assert!(total > 0);
}
#[test]
fn theta_fires() {
let mut n = ThetaNeuron::default();
let total: i32 = (0..1000).map(|_| n.step(0.5)).sum();
assert!(total > 0);
}
#[test]
fn perfect_integrator_fires() {
let mut n = PerfectIntegratorNeuron::default();
let total: i32 = (0..100).map(|_| n.step(0.5)).sum();
assert!(total > 0);
}
#[test]
fn gated_lif_fires() {
let mut n = GatedLIFNeuron::default();
let total: i32 = (0..20).map(|_| n.step(0.5)).sum();
assert!(total > 0);
}
#[test]
fn nlif_fires() {
let mut n = NonlinearLIFNeuron::new();
let total: i32 = (0..2000).map(|_| n.step(500.0)).sum();
assert!(total > 0);
}
#[test]
fn sfa_fires_then_adapts() {
let mut n = SFANeuron::new();
let first: i32 = (0..100).map(|_| n.step(30.0)).sum();
let second: i32 = (0..100).map(|_| n.step(30.0)).sum();
assert!(first > 0);
assert!(second <= first + 2);
}
#[test]
fn mat_dual_threshold_adapts() {
let mut n = MATNeuron::new();
let total: i32 = (0..200).map(|_| n.step(30.0)).sum();
assert!(total > 0);
assert!(n.theta1 > 0.0 || n.theta2 > 0.0);
}
#[test]
fn escape_rate_stochastic() {
let mut n = EscapeRateNeuron::new(42);
let total: i32 = (0..1000).map(|_| n.step(30.0)).sum();
assert!(total > 0);
}
#[test]
fn klif_fires() {
let mut n = KLIFNeuron::default();
let total: i32 = (0..50).map(|_| n.step(0.5)).sum();
assert!(total > 0);
}
#[test]
fn ilif_self_inhibits() {
let mut n = InhibitoryLIFNeuron::default();
let total: i32 = (0..100).map(|_| n.step(0.8)).sum();
assert!(total > 0);
}
#[test]
fn clif_positive_spike() {
let mut n = ComplementaryLIFNeuron::default();
let total: i32 = (0..20).map(|_| n.step(0.5)).sum();
assert!(total > 0);
}
#[test]
fn plif_fires() {
let mut n = ParametricLIFNeuron::default();
let total: i32 = (0..20).map(|_| n.step(1.5)).sum();
assert!(total > 0);
}
#[test]
fn non_resetting_threshold_increases() {
let mut n = NonResettingLIFNeuron::new();
let initial = n.theta;
for _ in 0..5000 {
n.step(30.0);
}
assert!(n.theta > initial);
}
#[test]
fn adaptive_threshold_fires() {
let mut n = AdaptiveThresholdIFNeuron::new();
let total: i32 = (0..500).map(|_| n.step(30.0)).sum();
assert!(total > 0);
}
#[test]
fn sigma_delta_encodes() {
let mut n = SigmaDeltaNeuron::default();
let total: i32 = (0..10).map(|_| n.step(0.3)).sum();
assert!(total > 0);
}
#[test]
fn energy_lif_fires() {
let mut n = EnergyLIFNeuron::new();
let total: i32 = (0..200).map(|_| n.step(30.0)).sum();
assert!(total > 0);
}
#[test]
fn iqif_fires() {
let mut n = IntegerQIFNeuron::default();
let total: i32 = (0..200).map(|_| n.step(50)).sum();
assert!(total > 0);
}
#[test]
fn cfc_activates() {
let mut n = ClosedFormContinuousNeuron {
v_threshold: 0.9,
..ClosedFormContinuousNeuron::new()
};
let total: i32 = (0..100).map(|_| n.step(5.0)).sum();
assert!(total > 0);
}
#[test]
fn qif_silent_without_input() {
let mut n = QuadraticIFNeuron::default();
let t: i32 = (0..1000).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn qif_reset_clears_state() {
let mut n = QuadraticIFNeuron::default();
for _ in 0..100 {
n.step(0.5);
}
n.reset();
assert!((n.v - n.v_reset).abs() < 1e-10);
}
#[test]
fn qif_bounded() {
let mut n = QuadraticIFNeuron::default();
for _ in 0..1000 {
n.step(10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn qif_nan_no_panic() {
let mut n = QuadraticIFNeuron::default();
let before = n.v;
assert_eq!(n.step(f64::NAN), 0);
assert_eq!(n.v, before);
}
#[test]
fn qif_nonfinite_increment_preserves_state() {
let mut n = QuadraticIFNeuron {
v: -1.0e200,
..Default::default()
};
let before = n.v;
assert_eq!(n.step(0.0), 0);
assert_eq!(n.v, before);
}
#[test]
fn qif_negative_no_crash() {
let mut n = QuadraticIFNeuron::default();
for _ in 0..500 {
n.step(-5.0);
}
assert!(n.v.is_finite());
}
#[test]
fn theta_silent_without_input() {
let mut n = ThetaNeuron::default();
let t: i32 = (0..1000).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn theta_reset_clears_state() {
let mut n = ThetaNeuron::default();
for _ in 0..100 {
n.step(0.5);
}
n.reset();
assert!((n.theta - 0.0).abs() < 1e-10);
}
#[test]
fn theta_bounded() {
let mut n = ThetaNeuron::default();
for _ in 0..1000 {
n.step(10.0);
}
assert!(n.theta.is_finite());
}
#[test]
fn theta_nan_no_panic() {
ThetaNeuron::default().step(f64::NAN);
}
#[test]
fn pi_silent_without_input() {
let mut n = PerfectIntegratorNeuron::default();
let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn pi_reset_clears_state() {
let mut n = PerfectIntegratorNeuron::default();
for _ in 0..50 {
n.step(0.5);
}
n.reset();
assert!((n.v - n.v_reset).abs() < 1e-10);
}
#[test]
fn pi_bounded() {
let mut n = PerfectIntegratorNeuron::default();
for _ in 0..1000 {
n.step(100.0);
}
assert!(n.v.is_finite());
}
#[test]
fn pi_nan_no_panic() {
PerfectIntegratorNeuron::default().step(f64::NAN);
}
#[test]
fn gated_lif_silent_without_input() {
let mut n = GatedLIFNeuron::default();
let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn gated_lif_reset_clears_state() {
let mut n = GatedLIFNeuron::default();
for _ in 0..20 {
n.step(0.5);
}
n.reset();
assert!((n.v - 0.0).abs() < 1e-10);
}
#[test]
fn gated_lif_bounded() {
let mut n = GatedLIFNeuron::default();
for _ in 0..1000 {
n.step(100.0);
}
assert!(n.v.is_finite());
}
#[test]
fn gated_lif_nan_no_panic() {
GatedLIFNeuron::default().step(f64::NAN);
}
#[test]
fn nlif_silent_without_input() {
let mut n = NonlinearLIFNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn nlif_reset_clears_state() {
let mut n = NonlinearLIFNeuron::new();
for _ in 0..100 {
n.step(500.0);
}
n.reset();
assert!((n.v - n.v_rest).abs() < 1e-10);
}
#[test]
fn nlif_bounded() {
let mut n = NonlinearLIFNeuron::new();
for _ in 0..2000 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn nlif_nan_no_panic() {
NonlinearLIFNeuron::new().step(f64::NAN);
}
#[test]
fn nlif_recovery_evolves() {
let mut n = NonlinearLIFNeuron::new();
for _ in 0..2000 {
n.step(500.0);
}
assert!(
n.w > 0.0,
"recovery variable w should increase during spiking"
);
}
#[test]
fn sfa_silent_without_input() {
let mut n = SFANeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn sfa_reset_clears_state() {
let mut n = SFANeuron::new();
for _ in 0..100 {
n.step(30.0);
}
n.reset();
assert!((n.v - n.v_rest).abs() < 1e-10);
assert!((n.g_sfa - 0.0).abs() < 1e-10);
}
#[test]
fn sfa_bounded() {
let mut n = SFANeuron::new();
for _ in 0..1000 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn sfa_nan_no_panic() {
SFANeuron::new().step(f64::NAN);
}
#[test]
fn mat_silent_without_input() {
let mut n = MATNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn mat_reset_clears_state() {
let mut n = MATNeuron::new();
for _ in 0..100 {
n.step(30.0);
}
n.reset();
assert!((n.v - n.v_rest).abs() < 1e-10);
assert!((n.theta1 - 0.0).abs() < 1e-10);
assert!((n.theta2 - 0.0).abs() < 1e-10);
}
#[test]
fn mat_bounded() {
let mut n = MATNeuron::new();
for _ in 0..1000 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn mat_nan_no_panic() {
MATNeuron::new().step(f64::NAN);
}
#[test]
fn escape_rate_reset_clears_state() {
let mut n = EscapeRateNeuron::new(42);
for _ in 0..100 {
n.step(30.0);
}
n.reset();
assert!((n.v - n.v_rest).abs() < 1e-10);
}
#[test]
fn escape_rate_bounded() {
let mut n = EscapeRateNeuron::new(42);
for _ in 0..1000 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn escape_rate_nan_no_panic() {
EscapeRateNeuron::new(42).step(f64::NAN);
}
#[test]
fn escape_rate_seed_varies() {
let mut n1 = EscapeRateNeuron::new(1);
let mut n2 = EscapeRateNeuron::new(999);
let t1: i32 = (0..1000).map(|_| n1.step(30.0)).sum();
let t2: i32 = (0..1000).map(|_| n2.step(30.0)).sum();
assert!(t1 > 0 && t2 > 0);
}
#[test]
fn klif_silent_without_input() {
let mut n = KLIFNeuron::default();
let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn klif_reset_clears_state() {
let mut n = KLIFNeuron::default();
for _ in 0..20 {
n.step(0.5);
}
n.reset();
assert!((n.v - 0.0).abs() < 1e-10);
}
#[test]
fn klif_bounded() {
let mut n = KLIFNeuron::default();
for _ in 0..1000 {
n.step(100.0);
}
assert!(n.v.is_finite());
}
#[test]
fn klif_nan_no_panic() {
KLIFNeuron::default().step(f64::NAN);
}
#[test]
fn ilif_silent_without_input() {
let mut n = InhibitoryLIFNeuron::default();
let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn ilif_reset_clears_state() {
let mut n = InhibitoryLIFNeuron::default();
for _ in 0..50 {
n.step(0.8);
}
n.reset();
assert!((n.v - 0.0).abs() < 1e-10);
assert!((n.inh_trace - 0.0).abs() < 1e-10);
}
#[test]
fn ilif_bounded() {
let mut n = InhibitoryLIFNeuron::default();
for _ in 0..1000 {
n.step(100.0);
}
assert!(n.v.is_finite());
}
#[test]
fn ilif_nan_no_panic() {
InhibitoryLIFNeuron::default().step(f64::NAN);
}
#[test]
fn clif_silent_without_input() {
let mut n = ComplementaryLIFNeuron::default();
let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn clif_reset_clears_state() {
let mut n = ComplementaryLIFNeuron::default();
for _ in 0..20 {
n.step(0.5);
}
n.reset();
assert!((n.v_pos - 0.0).abs() < 1e-10);
assert!((n.v_neg - 0.0).abs() < 1e-10);
}
#[test]
fn clif_bounded() {
let mut n = ComplementaryLIFNeuron::default();
for _ in 0..1000 {
n.step(100.0);
}
assert!(n.v_pos.is_finite());
}
#[test]
fn clif_nan_no_panic() {
ComplementaryLIFNeuron::default().step(f64::NAN);
}
#[test]
fn plif_silent_without_input() {
let mut n = ParametricLIFNeuron::default();
let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn plif_reset_clears_state() {
let mut n = ParametricLIFNeuron::default();
for _ in 0..20 {
n.step(1.5);
}
n.reset();
assert!((n.v - 0.0).abs() < 1e-10);
}
#[test]
fn plif_bounded() {
let mut n = ParametricLIFNeuron::default();
for _ in 0..1000 {
n.step(100.0);
}
assert!(n.v.is_finite());
}
#[test]
fn plif_nan_no_panic() {
ParametricLIFNeuron::default().step(f64::NAN);
}
#[test]
fn nrlif_reset_clears_state() {
let mut n = NonResettingLIFNeuron::new();
for _ in 0..500 {
n.step(30.0);
}
n.reset();
assert!((n.v - n.v_rest).abs() < 1e-10);
assert!((n.theta - n.theta_rest).abs() < 1e-10);
}
#[test]
fn nrlif_bounded() {
let mut n = NonResettingLIFNeuron::new();
for _ in 0..5000 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn nrlif_nan_no_panic() {
NonResettingLIFNeuron::new().step(f64::NAN);
}
#[test]
fn nrlif_negative_no_crash() {
let mut n = NonResettingLIFNeuron::new();
for _ in 0..500 {
n.step(-10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn atif_silent_without_input() {
let mut n = AdaptiveThresholdIFNeuron::new();
let t: i32 = (0..500).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn atif_reset_clears_state() {
let mut n = AdaptiveThresholdIFNeuron::new();
for _ in 0..100 {
n.step(30.0);
}
n.reset();
assert!((n.v - n.v_rest).abs() < 1e-10);
}
#[test]
fn atif_bounded() {
let mut n = AdaptiveThresholdIFNeuron::new();
for _ in 0..1000 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn atif_nan_no_panic() {
AdaptiveThresholdIFNeuron::new().step(f64::NAN);
}
#[test]
fn atif_threshold_increases_with_spikes() {
let mut n = AdaptiveThresholdIFNeuron::new();
let theta_init = n.theta;
for _ in 0..500 {
n.step(30.0);
}
assert!(n.theta > theta_init, "threshold should adapt after spikes");
}
#[test]
fn sd_reset_clears_state() {
let mut n = SigmaDeltaNeuron::default();
for _ in 0..10 {
n.step(0.3);
}
n.reset();
assert!((n.sigma - 0.0).abs() < 1e-10);
}
#[test]
fn sd_bounded() {
let mut n = SigmaDeltaNeuron::default();
for _ in 0..1000 {
n.step(100.0);
}
assert!(n.sigma.is_finite());
}
#[test]
fn sd_nan_no_panic() {
SigmaDeltaNeuron::default().step(f64::NAN);
}
#[test]
fn energy_lif_silent_without_input() {
let mut n = EnergyLIFNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn energy_lif_reset_clears_state() {
let mut n = EnergyLIFNeuron::new();
for _ in 0..100 {
n.step(30.0);
}
n.reset();
assert!((n.v - n.v_rest).abs() < 1e-10);
}
#[test]
fn energy_lif_bounded() {
let mut n = EnergyLIFNeuron::new();
for _ in 0..1000 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn energy_lif_nan_no_panic() {
EnergyLIFNeuron::new().step(f64::NAN);
}
#[test]
fn energy_lif_epsilon_depletes() {
let mut n = EnergyLIFNeuron::new();
let e0 = n.epsilon;
for _ in 0..200 {
n.step(30.0);
}
assert!(n.epsilon < e0, "energy should deplete during spiking");
}
#[test]
fn iqif_silent_without_input() {
let mut n = IntegerQIFNeuron::default();
let t: i32 = (0..200).map(|_| n.step(0)).sum();
assert_eq!(t, 0);
}
#[test]
fn iqif_reset_clears_state() {
let mut n = IntegerQIFNeuron::default();
for _ in 0..100 {
n.step(50);
}
n.reset();
assert_eq!(n.v, 0);
}
#[test]
fn iqif_bounded() {
let mut n = IntegerQIFNeuron::default();
for _ in 0..1000 {
n.step(10000);
}
}
#[test]
fn iqif_negative_no_crash() {
let mut n = IntegerQIFNeuron::default();
for _ in 0..500 {
n.step(-50);
}
}
#[test]
fn cfc_reset_clears_state() {
let mut n = ClosedFormContinuousNeuron::new();
for _ in 0..50 {
n.step(5.0);
}
n.reset();
assert!((n.x - 0.0).abs() < 1e-10);
}
#[test]
fn cfc_bounded() {
let mut n = ClosedFormContinuousNeuron {
v_threshold: 0.9,
..ClosedFormContinuousNeuron::new()
};
for _ in 0..1000 {
n.step(100.0);
}
assert!(n.x.is_finite());
}
#[test]
fn cfc_nan_no_panic() {
ClosedFormContinuousNeuron::new().step(f64::NAN);
}
#[test]
fn stochastic_lif_fires_with_input() {
let mut n = StochasticLIFNeuron::new(42);
let total: i32 = (0..500).map(|_| n.step(2.0)).sum();
assert!(total > 0, "StochasticLIF should fire with strong input");
}
#[test]
fn stochastic_lif_silent_without_input() {
let mut n = StochasticLIFNeuron::new(42);
let total: i32 = (0..500).map(|_| n.step(0.0)).sum();
assert_eq!(
total, 0,
"StochasticLIF should be silent at zero input with no noise"
);
}
#[test]
fn stochastic_lif_reset_clears_state() {
let mut n = StochasticLIFNeuron::new(42);
for _ in 0..100 {
n.step(2.0);
}
n.reset();
assert!(
(n.v - n.v_rest).abs() < 1e-12,
"reset must restore v to v_rest"
);
assert_eq!(
n.refractory_counter, 0,
"reset must clear refractory counter"
);
}
#[test]
fn stochastic_lif_extreme_input_bounded() {
let mut n = StochasticLIFNeuron::new(42);
for _ in 0..1000 {
n.step(1e6);
}
assert!(n.v.is_finite(), "v must stay finite under extreme input");
}
#[test]
fn stochastic_lif_nan_input_stays_finite() {
let mut n = StochasticLIFNeuron::new(42);
for _ in 0..10 {
n.step(1.0);
}
let v_before = n.v;
n.step(f64::NAN);
let _ = v_before;
}
#[test]
fn stochastic_lif_negative_input_no_crash() {
let mut n = StochasticLIFNeuron::new(42);
for _ in 0..500 {
n.step(-10.0);
}
assert!(n.v.is_finite(), "v must stay finite with negative input");
}
#[test]
fn stochastic_lif_noise_affects_firing() {
let mut n_noisy = StochasticLIFNeuron::new(123);
n_noisy.noise_std = 0.5;
let total_noisy: i32 = (0..5000).map(|_| n_noisy.step(0.8)).sum();
let mut n_quiet = StochasticLIFNeuron::new(123);
n_quiet.noise_std = 0.0;
let total_quiet: i32 = (0..5000).map(|_| n_quiet.step(0.8)).sum();
assert!(
total_noisy != total_quiet || total_noisy > 0,
"noise should affect firing pattern"
);
}
#[test]
fn stochastic_lif_refractory_blocks_spikes() {
let mut n = StochasticLIFNeuron::new(42);
n.refractory_period = 5;
let mut spikes = Vec::new();
for _ in 0..500 {
spikes.push(n.step(3.0));
}
for (i, &s) in spikes.iter().enumerate() {
if s == 1 {
for j in 1..=5 {
if i + j < spikes.len() {
assert_eq!(
spikes[i + j],
0,
"step {} after spike at {} must be silent (refractory)",
j,
i
);
}
}
}
}
}
}
#[derive(Clone, Debug)]
pub struct StochasticLIFNeuron {
pub v: f64,
pub v_rest: f64,
pub v_reset: f64,
pub v_threshold: f64,
pub tau_mem: f64,
pub dt: f64,
pub noise_std: f64,
pub resistance: f64,
pub refractory_period: i32,
pub refractory_counter: i32,
rng: Xoshiro256PlusPlus,
}
impl StochasticLIFNeuron {
pub fn new(seed: u64) -> Self {
Self {
v: 0.0,
v_rest: 0.0,
v_reset: 0.0,
v_threshold: 1.0,
tau_mem: 20.0,
dt: 1.0,
noise_std: 0.0,
resistance: 1.0,
refractory_period: 0,
refractory_counter: 0,
rng: Xoshiro256PlusPlus::seed_from_u64(seed),
}
}
pub fn step(&mut self, current: f64) -> i32 {
if self.refractory_counter > 0 {
self.refractory_counter -= 1;
self.v = self.v_rest;
return 0;
}
let dv_leak = -(self.v - self.v_rest) * (self.dt / self.tau_mem);
let dv_input = self.resistance * current * self.dt;
let mut dv_noise = 0.0;
if self.noise_std > 0.0 {
let u1: f64 = self.rng.random();
let u2: f64 = self.rng.random();
let z0 = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
dv_noise = self.noise_std * self.dt.sqrt() * z0;
}
self.v += dv_leak + dv_input + dv_noise;
if self.v >= self.v_threshold {
self.v = self.v_reset;
self.refractory_counter = self.refractory_period;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.refractory_counter = 0;
}
}
impl Default for StochasticLIFNeuron {
fn default() -> Self {
Self::new(42)
}
}