Skip to main content

sc_neurocore_engine/neurons/rate/
tsodyks_markram.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 — Tsodyks-Markram neuron model
8
9/// Tsodyks-Markram 1997 — LIF with short-term synaptic plasticity.
10#[derive(Clone, Debug)]
11pub struct TsodyksMarkramNeuron {
12    pub v: f64,
13    pub x: f64,
14    pub u: f64,
15    pub v_rest: f64,
16    pub v_reset: f64,
17    pub v_threshold: f64,
18    pub tau_m: f64,
19    pub tau_d: f64,
20    pub tau_f: f64,
21    pub u_se: f64,
22    pub a_se: f64,
23    pub r_m: f64,
24    pub dt: f64,
25}
26
27impl TsodyksMarkramNeuron {
28    pub fn new() -> Self {
29        Self {
30            v: -65.0,
31            x: 1.0,
32            u: 0.2,
33            v_rest: -65.0,
34            v_reset: -65.0,
35            v_threshold: -50.0,
36            tau_m: 20.0,
37            tau_d: 200.0,
38            tau_f: 600.0,
39            u_se: 0.2,
40            a_se: 50.0,
41            r_m: 1.0,
42            dt: 0.1,
43        }
44    }
45    pub fn step(&mut self, current: f64, presynaptic_spike: bool) -> i32 {
46        self.x += (1.0 - self.x) / self.tau_d * self.dt;
47        self.u += (self.u_se - self.u) / self.tau_f * self.dt;
48        let mut i_syn = 0.0;
49        if presynaptic_spike {
50            self.u += self.u_se * (1.0 - self.u);
51            i_syn = self.a_se * self.u * self.x;
52            self.x -= self.u * self.x;
53        }
54        self.v += (-(self.v - self.v_rest) + self.r_m * (i_syn + current)) / self.tau_m * self.dt;
55        if self.v >= self.v_threshold {
56            self.v = self.v_reset;
57            1
58        } else {
59            0
60        }
61    }
62    pub fn reset(&mut self) {
63        self.v = self.v_rest;
64        self.x = 1.0;
65        self.u = self.u_se;
66    }
67}
68impl Default for TsodyksMarkramNeuron {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn tm_fires() {
80        let mut n = TsodyksMarkramNeuron::new();
81        let t: i32 = (0..500).map(|_| n.step(50.0, false)).sum();
82        assert!(t > 0);
83    }
84
85    #[test]
86    fn tm_reset() {
87        let mut n = TsodyksMarkramNeuron::new();
88        for _ in 0..100 {
89            n.step(50.0, false);
90        }
91        n.reset();
92        assert!((n.v - n.v_rest).abs() < 1e-10);
93        assert!((n.x - 1.0).abs() < 1e-10);
94    }
95
96    #[test]
97    fn tm_bounded() {
98        let mut n = TsodyksMarkramNeuron::new();
99        for _ in 0..1000 {
100            n.step(1e4, false);
101        }
102        assert!(n.v.is_finite());
103    }
104
105    #[test]
106    fn tm_nan_no_panic() {
107        TsodyksMarkramNeuron::new().step(f64::NAN, false);
108    }
109
110    #[test]
111    fn tm_stp_depression() {
112        let mut n = TsodyksMarkramNeuron::new();
113        for _ in 0..500 {
114            n.step(50.0, true);
115        }
116        // With repeated presynaptic spikes, x (available fraction) should decrease
117        assert!(
118            n.x < 1.0,
119            "STP depression: x should be < 1.0 after spikes: {}",
120            n.x
121        );
122    }
123}