Skip to main content

sc_neurocore_engine/neurons/biophysical/
yamada.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 — Yamada Neuron Model
8
9//! Yamada subcritical Hopf-burster dynamics.
10
11/// Yamada 1989 — subcritical Hopf burster.
12#[derive(Clone, Debug)]
13pub struct YamadaNeuron {
14    pub v: f64,
15    pub n: f64,
16    pub q: f64,
17    pub g_na: f64,
18    pub g_k: f64,
19    pub g_q: f64,
20    pub g_l: f64,
21    pub e_na: f64,
22    pub e_k: f64,
23    pub e_q: f64,
24    pub e_l: f64,
25    pub tau_q: f64,
26    pub dt: f64,
27    pub v_threshold: f64,
28}
29
30impl YamadaNeuron {
31    pub fn new() -> Self {
32        Self {
33            v: -60.0,
34            n: 0.1,
35            q: 0.0,
36            g_na: 20.0,
37            g_k: 10.0,
38            g_q: 5.0,
39            g_l: 0.5,
40            e_na: 60.0,
41            e_k: -80.0,
42            e_q: -80.0,
43            e_l: -60.0,
44            tau_q: 300.0,
45            dt: 0.05,
46            v_threshold: -20.0,
47        }
48    }
49    pub fn step(&mut self, current: f64) -> i32 {
50        let v_prev = self.v;
51        let m_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 9.5).exp());
52        let n_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 10.0).exp());
53        let q_inf = 1.0 / (1.0 + (-(self.v + 50.0) / 10.0).exp());
54        let tau_n = 1.0 + 7.5 / (1.0 + ((self.v + 40.0) / 12.0).exp());
55        let i_na = self.g_na * m_inf.powi(3) * (1.0 - self.n) * (self.v - self.e_na);
56        let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
57        let i_q = self.g_q * self.q * (self.v - self.e_q);
58        let i_l = self.g_l * (self.v - self.e_l);
59        self.v += (-i_na - i_k - i_q - i_l + current) * self.dt;
60        self.n += (n_inf - self.n) / tau_n * self.dt;
61        self.q += (q_inf - self.q) / self.tau_q * self.dt;
62        if self.v >= self.v_threshold && v_prev < self.v_threshold {
63            1
64        } else {
65            0
66        }
67    }
68    pub fn reset(&mut self) {
69        self.v = -60.0;
70        self.n = 0.1;
71        self.q = 0.0;
72    }
73}
74impl Default for YamadaNeuron {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn default_matches_constructor_state() {
86        let default = YamadaNeuron::default();
87        let constructed = YamadaNeuron::new();
88        assert_eq!(default.v, constructed.v);
89    }
90
91    #[test]
92    fn yamada_fires() {
93        let mut n = YamadaNeuron::new();
94        let t: i32 = (0..2000).map(|_| n.step(5.0)).sum();
95        assert!(t > 0);
96    }
97
98    // -- Yamada --
99    #[test]
100    fn yamada_silent_without_input() {
101        let mut n = YamadaNeuron::new();
102        let t: i32 = (0..500).map(|_| n.step(0.0)).sum();
103        assert_eq!(t, 0);
104    }
105    #[test]
106    fn yamada_reset_clears_state() {
107        let mut n = YamadaNeuron::new();
108        for _ in 0..100 {
109            n.step(5.0);
110        }
111        n.reset();
112        assert!((n.v - (-60.0)).abs() < 1e-10);
113        assert!((n.q - 0.0).abs() < 1e-10);
114    }
115    #[test]
116    fn yamada_extreme_bounded() {
117        let mut n = YamadaNeuron::new();
118        for _ in 0..200 {
119            n.step(1e4);
120        }
121        assert!(n.v.is_finite());
122    }
123    #[test]
124    fn yamada_slow_q_adapts() {
125        let mut n = YamadaNeuron::new();
126        for _ in 0..2000 {
127            n.step(5.0);
128        }
129        assert!(n.q > 0.0, "slow variable q should activate during spiking");
130    }
131    #[test]
132    fn yamada_negative_no_crash() {
133        let mut n = YamadaNeuron::new();
134        for _ in 0..200 {
135            n.step(-10.0);
136        }
137        assert!(n.v.is_finite());
138    }
139    #[test]
140    fn yamada_nan_no_panic() {
141        let mut n = YamadaNeuron::new();
142        n.step(f64::NAN);
143    }
144}