Skip to main content

sc_neurocore_engine/neurons/trivial/
energy_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 — Energy-aware LIF Neuron
8
9/// Energy-aware LIF — metabolic cost modulates gain. Sengupta et al. 2013.
10#[derive(Clone, Debug)]
11pub struct EnergyLIFNeuron {
12    pub v: f64,
13    pub epsilon: f64,
14    pub v_rest: f64,
15    pub v_reset: f64,
16    pub v_threshold: f64,
17    pub tau_m: f64,
18    pub tau_e: f64,
19    pub alpha: f64,
20    pub epsilon_0: f64,
21    pub resistance: f64,
22    pub dt: f64,
23}
24
25impl EnergyLIFNeuron {
26    pub fn new() -> Self {
27        Self {
28            v: -70.0,
29            epsilon: 1.0,
30            v_rest: -70.0,
31            v_reset: -70.0,
32            v_threshold: -50.0,
33            tau_m: 10.0,
34            tau_e: 500.0,
35            alpha: 0.1,
36            epsilon_0: 1.0,
37            resistance: 1.0,
38            dt: 1.0,
39        }
40    }
41
42    pub fn step(&mut self, current: f64) -> i32 {
43        let effective_r = self.resistance * self.epsilon;
44        self.v += (-(self.v - self.v_rest) + effective_r * current) / self.tau_m * self.dt;
45        self.epsilon += (self.epsilon_0 - self.epsilon) / self.tau_e * self.dt;
46        if self.v >= self.v_threshold && self.epsilon > 0.1 {
47            self.v = self.v_reset;
48            self.epsilon -= self.alpha;
49            1
50        } else if self.v >= self.v_threshold {
51            self.v = self.v_threshold;
52            0
53        } else {
54            0
55        }
56    }
57
58    pub fn reset(&mut self) {
59        self.v = self.v_rest;
60        self.epsilon = self.epsilon_0;
61    }
62}
63
64impl Default for EnergyLIFNeuron {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn energy_lif_fires() {
76        let mut n = EnergyLIFNeuron::new();
77        let total: i32 = (0..200).map(|_| n.step(30.0)).sum();
78        assert!(total > 0);
79    }
80    #[test]
81    fn energy_lif_silent_without_input() {
82        let mut n = EnergyLIFNeuron::new();
83        let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
84        assert_eq!(t, 0);
85    }
86    #[test]
87    fn energy_lif_reset_clears_state() {
88        let mut n = EnergyLIFNeuron::new();
89        for _ in 0..100 {
90            n.step(30.0);
91        }
92        n.reset();
93        assert!((n.v - n.v_rest).abs() < 1e-10);
94    }
95    #[test]
96    fn energy_lif_bounded() {
97        let mut n = EnergyLIFNeuron::new();
98        for _ in 0..1000 {
99            n.step(1e4);
100        }
101        assert!(n.v.is_finite());
102    }
103    #[test]
104    fn energy_lif_nan_no_panic() {
105        EnergyLIFNeuron::new().step(f64::NAN);
106    }
107    #[test]
108    fn energy_lif_epsilon_depletes() {
109        let mut n = EnergyLIFNeuron::new();
110        let e0 = n.epsilon;
111        for _ in 0..200 {
112            n.step(30.0);
113        }
114        assert!(n.epsilon < e0, "energy should deplete during spiking");
115    }
116}