Skip to main content

sc_neurocore_engine/neurons/rate/
compte_wm.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 — Compte working-memory neuron model
8
9/// Compte WM — NMDA-based working-memory neuron. Compte et al. 2000.
10#[derive(Clone, Debug)]
11pub struct CompteWMNeuron {
12    pub v: f64,
13    pub s_ampa: f64,
14    pub s_nmda: f64,
15    pub x_nmda: f64,
16    pub s_gaba: f64,
17    pub g_l: f64,
18    pub g_ampa: f64,
19    pub g_nmda: f64,
20    pub g_gaba: f64,
21    pub e_l: f64,
22    pub e_exc: f64,
23    pub e_inh: f64,
24    pub c_m: f64,
25    pub mg: f64,
26    pub tau_ampa: f64,
27    pub tau_nmda: f64,
28    pub tau_x: f64,
29    pub alpha_nmda: f64,
30    pub v_threshold: f64,
31    pub v_reset: f64,
32    pub dt: f64,
33}
34
35impl CompteWMNeuron {
36    pub fn new() -> Self {
37        Self {
38            v: -70.0,
39            s_ampa: 0.0,
40            s_nmda: 0.0,
41            x_nmda: 0.0,
42            s_gaba: 0.0,
43            g_l: 0.025,
44            g_ampa: 0.005,
45            g_nmda: 0.165,
46            g_gaba: 0.013,
47            e_l: -70.0,
48            e_exc: 0.0,
49            e_inh: -70.0,
50            c_m: 0.5,
51            mg: 1.0,
52            tau_ampa: 2.0,
53            tau_nmda: 100.0,
54            tau_x: 2.0,
55            alpha_nmda: 0.5,
56            v_threshold: -50.0,
57            v_reset: -55.0,
58            dt: 0.1,
59        }
60    }
61    pub fn step(&mut self, current: f64, spike_in: bool) -> i32 {
62        if spike_in {
63            self.s_ampa += 1.0;
64            self.x_nmda += 1.0;
65        }
66        self.s_ampa *= (-self.dt / self.tau_ampa).exp();
67        self.s_nmda += (-self.s_nmda / self.tau_nmda
68            + self.alpha_nmda * self.x_nmda * (1.0 - self.s_nmda))
69            * self.dt;
70        self.x_nmda *= (-self.dt / self.tau_x).exp();
71        self.s_gaba *= (-self.dt / 5.0).exp();
72        let mg_block = 1.0 / (1.0 + self.mg / 3.57 * (-0.062 * self.v).exp());
73        let i_l = self.g_l * (self.v - self.e_l);
74        let i_ampa = self.g_ampa * self.s_ampa * (self.v - self.e_exc);
75        let i_nmda = self.g_nmda * mg_block * self.s_nmda * (self.v - self.e_exc);
76        let i_gaba = self.g_gaba * self.s_gaba * (self.v - self.e_inh);
77        self.v += (-i_l - i_ampa - i_nmda - i_gaba + current) / self.c_m * self.dt;
78        if self.v >= self.v_threshold {
79            self.v = self.v_reset;
80            self.s_gaba += 1.0;
81            1
82        } else {
83            0
84        }
85    }
86    pub fn reset(&mut self) {
87        self.v = self.e_l;
88        self.s_ampa = 0.0;
89        self.s_nmda = 0.0;
90        self.x_nmda = 0.0;
91        self.s_gaba = 0.0;
92    }
93}
94impl Default for CompteWMNeuron {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn compte_fires() {
106        let mut n = CompteWMNeuron::new();
107        let t: i32 = (0..500).map(|_| n.step(5.0, false)).sum();
108        assert!(t > 0);
109    }
110
111    #[test]
112    fn compte_reset() {
113        let mut n = CompteWMNeuron::new();
114        for _ in 0..100 {
115            n.step(5.0, false);
116        }
117        n.reset();
118        assert!((n.v - n.e_l).abs() < 1e-10);
119    }
120
121    #[test]
122    fn compte_nan_no_panic() {
123        CompteWMNeuron::new().step(f64::NAN, false);
124    }
125}