sc_neurocore_engine/neurons/biophysical/
wang_buzsaki.rs1use super::safe_rate;
12
13#[derive(Clone, Debug)]
15pub struct WangBuzsakiNeuron {
16 pub v: f64,
17 pub h: f64,
18 pub n: f64,
19 pub g_na: f64,
20 pub g_k: f64,
21 pub g_l: f64,
22 pub e_na: f64,
23 pub e_k: f64,
24 pub e_l: f64,
25 pub c_m: f64,
26 pub phi: f64,
27 pub dt: f64,
28 pub v_threshold: f64,
29}
30
31impl WangBuzsakiNeuron {
32 pub fn new() -> Self {
33 Self {
34 v: -65.0,
35 h: 0.8,
36 n: 0.1,
37 g_na: 35.0,
38 g_k: 9.0,
39 g_l: 0.1,
40 e_na: 55.0,
41 e_k: -90.0,
42 e_l: -65.0,
43 c_m: 1.0,
44 phi: 5.0,
45 dt: 0.01,
46 v_threshold: -20.0,
47 }
48 }
49 pub fn step(&mut self, current: f64) -> i32 {
50 let v_prev = self.v;
51 let n_sub = (0.5 / self.dt.max(0.001)) as usize;
52 for _ in 0..n_sub {
53 let am = safe_rate(0.1, 35.0, self.v, 10.0, 1.0);
54 let bm = 4.0 * (-(self.v + 60.0) / 18.0).exp();
55 let m_inf = am / (am + bm);
56 let ah = 0.07 * (-(self.v + 58.0) / 20.0).exp();
57 let bh = 1.0 / (1.0 + (-(self.v + 28.0) / 10.0).exp());
58 let an = safe_rate(0.01, 34.0, self.v, 10.0, 0.1);
59 let bn = 0.125 * (-(self.v + 44.0) / 80.0).exp();
60 self.h += self.phi * (ah * (1.0 - self.h) - bh * self.h) * self.dt;
61 self.n += self.phi * (an * (1.0 - self.n) - bn * self.n) * self.dt;
62 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
63 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
64 let i_l = self.g_l * (self.v - self.e_l);
65 self.v += (-i_na - i_k - i_l + current) / self.c_m * self.dt;
66 }
67 if self.v >= self.v_threshold && v_prev < self.v_threshold {
68 1
69 } else {
70 0
71 }
72 }
73 pub fn reset(&mut self) {
74 self.v = -65.0;
75 self.h = 0.8;
76 self.n = 0.1;
77 }
78}
79impl Default for WangBuzsakiNeuron {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn default_matches_constructor_state() {
91 let default = WangBuzsakiNeuron::default();
92 let constructed = WangBuzsakiNeuron::new();
93 assert_eq!(default.v, constructed.v);
94 }
95
96 #[test]
97 fn wb_fires() {
98 let mut n = WangBuzsakiNeuron::new();
99 let t: i32 = (0..200).map(|_| n.step(2.0)).sum();
100 assert!(t > 0);
101 }
102
103 #[test]
105 fn wb_silent_without_input() {
106 let mut n = WangBuzsakiNeuron::new();
107 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
108 assert_eq!(t, 0);
109 }
110 #[test]
111 fn wb_reset_clears_state() {
112 let mut n = WangBuzsakiNeuron::new();
113 for _ in 0..100 {
114 n.step(2.0);
115 }
116 n.reset();
117 assert!((n.v - (-65.0)).abs() < 1e-10);
118 }
119 #[test]
120 fn wb_extreme_bounded() {
121 let mut n = WangBuzsakiNeuron::new();
122 for _ in 0..200 {
123 n.step(1e4);
124 }
125 assert!(n.v.is_finite());
126 }
127 #[test]
128 fn wb_fast_spiking_high_rate() {
129 let mut n = WangBuzsakiNeuron::new();
131 let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
132 assert!(t > 10, "WB FS should produce many spikes, got {}", t);
133 }
134 #[test]
135 fn wb_gates_bounded() {
136 let mut n = WangBuzsakiNeuron::new();
137 for _ in 0..500 {
138 n.step(2.0);
139 }
140 assert!(n.h >= 0.0 && n.h <= 1.0);
141 assert!(n.n >= 0.0 && n.n <= 1.0);
142 }
143 #[test]
144 fn wb_negative_no_crash() {
145 let mut n = WangBuzsakiNeuron::new();
146 for _ in 0..200 {
147 n.step(-10.0);
148 }
149 assert!(n.v.is_finite());
150 }
151 #[test]
152 fn wb_nan_no_panic() {
153 let mut n = WangBuzsakiNeuron::new();
154 n.step(f64::NAN);
155 }
156}