Skip to main content

sc_neurocore_engine/neurons/trivial/
mat.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 — Multi-timescale Adaptive Threshold Neuron
8
9/// Multi-timescale Adaptive Threshold. Kobayashi et al. 2009.
10#[derive(Clone, Debug)]
11pub struct MATNeuron {
12    pub v: f64,
13    pub theta1: f64,
14    pub theta2: f64,
15    pub v_rest: f64,
16    pub v_reset: f64,
17    pub v_threshold_base: f64,
18    pub tau_m: f64,
19    pub tau_1: f64,
20    pub tau_2: f64,
21    pub h1: f64,
22    pub h2: f64,
23    pub resistance: f64,
24    pub dt: f64,
25}
26
27impl MATNeuron {
28    pub fn new() -> Self {
29        Self {
30            v: -70.0,
31            theta1: 0.0,
32            theta2: 0.0,
33            v_rest: -70.0,
34            v_reset: -70.0,
35            v_threshold_base: -50.0,
36            tau_m: 10.0,
37            tau_1: 10.0,
38            tau_2: 200.0,
39            h1: 5.0,
40            h2: 3.0,
41            resistance: 1.0,
42            dt: 1.0,
43        }
44    }
45
46    pub fn step(&mut self, current: f64) -> i32 {
47        self.v += (-(self.v - self.v_rest) + self.resistance * current) / self.tau_m * self.dt;
48        self.theta1 *= (-self.dt / self.tau_1).exp();
49        self.theta2 *= (-self.dt / self.tau_2).exp();
50        let threshold = self.v_threshold_base + self.theta1 + self.theta2;
51        if self.v >= threshold {
52            self.v = self.v_reset;
53            self.theta1 += self.h1;
54            self.theta2 += self.h2;
55            1
56        } else {
57            0
58        }
59    }
60
61    pub fn reset(&mut self) {
62        self.v = self.v_rest;
63        self.theta1 = 0.0;
64        self.theta2 = 0.0;
65    }
66}
67
68impl Default for MATNeuron {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn mat_dual_threshold_adapts() {
80        let mut n = MATNeuron::new();
81        let total: i32 = (0..200).map(|_| n.step(30.0)).sum();
82        assert!(total > 0);
83        assert!(n.theta1 > 0.0 || n.theta2 > 0.0);
84    }
85    #[test]
86    fn mat_silent_without_input() {
87        let mut n = MATNeuron::new();
88        let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
89        assert_eq!(t, 0);
90    }
91    #[test]
92    fn mat_reset_clears_state() {
93        let mut n = MATNeuron::new();
94        for _ in 0..100 {
95            n.step(30.0);
96        }
97        n.reset();
98        assert!((n.v - n.v_rest).abs() < 1e-10);
99        assert!((n.theta1 - 0.0).abs() < 1e-10);
100        assert!((n.theta2 - 0.0).abs() < 1e-10);
101    }
102    #[test]
103    fn mat_bounded() {
104        let mut n = MATNeuron::new();
105        for _ in 0..1000 {
106            n.step(1e4);
107        }
108        assert!(n.v.is_finite());
109    }
110    #[test]
111    fn mat_nan_no_panic() {
112        MATNeuron::new().step(f64::NAN);
113    }
114}