Skip to main content

sc_neurocore_engine/neurons/trivial/
nonlinear_lif.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Commercial license available
3// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4// © Code 2020–2026 Miroslav Šotek. All rights reserved.
5// ORCID: 0009-0009-3560-0851
6// Contact: www.anulum.li | protoscience@anulum.li
7// SC-NeuroCore — Nonlinear LIF Neuron
8
9/// Nonlinear LIF — cubic f-I curve with adaptation. Touboul & Brette 2008.
10#[derive(Clone, Debug)]
11pub struct NonlinearLIFNeuron {
12    pub v: f64,
13    pub w: f64,
14    pub v_rest: f64,
15    pub v_crit: f64,
16    pub v_threshold: f64,
17    pub v_reset: f64,
18    pub a: f64,
19    pub b: f64,
20    pub tau_w: f64,
21    pub c_m: f64,
22    pub dt: f64,
23}
24
25impl NonlinearLIFNeuron {
26    pub fn new() -> Self {
27        Self {
28            v: -65.0,
29            w: 0.0,
30            v_rest: -65.0,
31            v_crit: -40.0,
32            v_threshold: -20.0,
33            v_reset: -65.0,
34            a: 0.04,
35            b: 0.5,
36            tau_w: 100.0,
37            c_m: 1.0,
38            dt: 0.1,
39        }
40    }
41
42    pub fn step(&mut self, current: f64) -> i32 {
43        let v_prev = self.v;
44        let cubic = self.a * (self.v - self.v_rest) * (self.v - self.v_crit);
45        self.v += (cubic - self.w + current) / self.c_m * self.dt;
46        self.w += (self.b * (self.v - self.v_rest) - self.w) / self.tau_w * self.dt;
47        if self.v >= self.v_threshold && v_prev < self.v_threshold {
48            self.v = self.v_reset;
49            1
50        } else {
51            0
52        }
53    }
54
55    pub fn reset(&mut self) {
56        self.v = self.v_rest;
57        self.w = 0.0;
58    }
59}
60
61impl Default for NonlinearLIFNeuron {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn nlif_fires() {
73        let mut n = NonlinearLIFNeuron::new();
74        let total: i32 = (0..2000).map(|_| n.step(500.0)).sum();
75        assert!(total > 0);
76    }
77    #[test]
78    fn nlif_silent_without_input() {
79        let mut n = NonlinearLIFNeuron::new();
80        let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
81        assert_eq!(t, 0);
82    }
83    #[test]
84    fn nlif_reset_clears_state() {
85        let mut n = NonlinearLIFNeuron::new();
86        for _ in 0..100 {
87            n.step(500.0);
88        }
89        n.reset();
90        assert!((n.v - n.v_rest).abs() < 1e-10);
91    }
92    #[test]
93    fn nlif_bounded() {
94        let mut n = NonlinearLIFNeuron::new();
95        for _ in 0..2000 {
96            n.step(1e4);
97        }
98        assert!(n.v.is_finite());
99    }
100    #[test]
101    fn nlif_nan_no_panic() {
102        NonlinearLIFNeuron::new().step(f64::NAN);
103    }
104    #[test]
105    fn nlif_recovery_evolves() {
106        let mut n = NonlinearLIFNeuron::new();
107        for _ in 0..2000 {
108            n.step(500.0);
109        }
110        assert!(
111            n.w > 0.0,
112            "recovery variable w should increase during spiking"
113        );
114    }
115}