Skip to main content

sc_neurocore_engine/neurons/trivial/
sfa.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 — Spike-Frequency Adaptation Neuron
8
9/// Spike-Frequency Adaptation LIF. Benda & Herz 2003.
10#[derive(Clone, Debug)]
11pub struct SFANeuron {
12    pub v: f64,
13    pub g_sfa: f64,
14    pub v_rest: f64,
15    pub v_reset: f64,
16    pub v_threshold: f64,
17    pub tau_m: f64,
18    pub tau_sfa: f64,
19    pub delta_g: f64,
20    pub e_k: f64,
21    pub resistance: f64,
22    pub dt: f64,
23}
24
25impl SFANeuron {
26    pub fn new() -> Self {
27        Self {
28            v: -70.0,
29            g_sfa: 0.0,
30            v_rest: -70.0,
31            v_reset: -70.0,
32            v_threshold: -50.0,
33            tau_m: 10.0,
34            tau_sfa: 200.0,
35            delta_g: 0.5,
36            e_k: -80.0,
37            resistance: 1.0,
38            dt: 1.0,
39        }
40    }
41
42    pub fn step(&mut self, current: f64) -> i32 {
43        self.v += (-(self.v - self.v_rest) - self.g_sfa * (self.v - self.e_k)
44            + self.resistance * current)
45            / self.tau_m
46            * self.dt;
47        self.g_sfa *= (-self.dt / self.tau_sfa).exp();
48        if self.v >= self.v_threshold {
49            self.v = self.v_reset;
50            self.g_sfa += self.delta_g;
51            1
52        } else {
53            0
54        }
55    }
56
57    pub fn reset(&mut self) {
58        self.v = self.v_rest;
59        self.g_sfa = 0.0;
60    }
61}
62
63impl Default for SFANeuron {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn sfa_fires_then_adapts() {
75        let mut n = SFANeuron::new();
76        let first: i32 = (0..100).map(|_| n.step(30.0)).sum();
77        let second: i32 = (0..100).map(|_| n.step(30.0)).sum();
78        assert!(first > 0);
79        assert!(second <= first + 2);
80    }
81    #[test]
82    fn sfa_silent_without_input() {
83        let mut n = SFANeuron::new();
84        let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
85        assert_eq!(t, 0);
86    }
87    #[test]
88    fn sfa_reset_clears_state() {
89        let mut n = SFANeuron::new();
90        for _ in 0..100 {
91            n.step(30.0);
92        }
93        n.reset();
94        assert!((n.v - n.v_rest).abs() < 1e-10);
95        assert!((n.g_sfa - 0.0).abs() < 1e-10);
96    }
97    #[test]
98    fn sfa_bounded() {
99        let mut n = SFANeuron::new();
100        for _ in 0..1000 {
101            n.step(1e4);
102        }
103        assert!(n.v.is_finite());
104    }
105    #[test]
106    fn sfa_nan_no_panic() {
107        SFANeuron::new().step(f64::NAN);
108    }
109}