sc_neurocore_engine/neurons/biophysical/
yamada.rs1#[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 #[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}