sc_neurocore_engine/neurons/interneurons/
sst_neuron.rs1#[derive(Clone, Debug)]
18pub struct SSTNeuron {
19 pub v: f64,
20 pub m: f64,
21 pub h: f64,
22 pub n: f64,
23 pub p: f64, pub s: f64, pub r: f64, pub g_na: f64,
28 pub g_k: f64,
29 pub g_m: f64,
30 pub g_t: f64,
31 pub g_h: f64,
32 pub g_l: f64,
33 pub e_na: f64,
35 pub e_k: f64,
36 pub e_ca: f64,
37 pub e_h: f64,
38 pub e_l: f64,
39 pub c_m: f64,
40 pub dt: f64,
41 pub v_threshold: f64,
42}
43
44impl SSTNeuron {
45 pub fn new() -> Self {
46 Self {
47 v: -65.0,
48 m: 0.02,
49 h: 0.8,
50 n: 0.2,
51 p: 0.0,
52 s: 0.9,
53 r: 0.1,
54 g_na: 50.0,
55 g_k: 5.0,
56 g_m: 0.12, g_t: 0.01, g_h: 0.02, g_l: 0.05, e_na: 50.0,
61 e_k: -90.0,
62 e_ca: 120.0,
63 e_h: -40.0,
64 e_l: -65.0,
65 c_m: 1.0,
66 dt: 0.025,
67 v_threshold: -20.0,
68 }
69 }
70
71 fn derivatives(
76 &self,
77 v: f64,
78 m: f64,
79 h: f64,
80 n: f64,
81 p: f64,
82 s: f64,
83 r: f64,
84 current: f64,
85 ) -> [f64; 7] {
86 let dvt = v - (-56.2);
87 let asing = |num: f64, slope: f64, limit: f64| {
88 if num.abs() < 1e-6 {
89 limit
90 } else {
91 num / ((num / slope).exp() - 1.0)
92 }
93 };
94 let alpha_m = -0.32 * asing(dvt - 13.0, -4.0, -4.0);
95 let beta_m = 0.28 * asing(dvt - 40.0, 5.0, 5.0);
96 let alpha_h = 0.128 * (-(dvt - 17.0) / 18.0).exp();
97 let beta_h = 4.0 / (1.0 + (-(dvt - 40.0) / 5.0).exp());
98 let alpha_n = -0.032 * asing(dvt - 15.0, -5.0, -5.0);
99 let beta_n = 0.5 * (-(dvt - 10.0) / 40.0).exp();
100 let dm = alpha_m * (1.0 - m) - beta_m * m;
101 let dh = alpha_h * (1.0 - h) - beta_h * h;
102 let dn = alpha_n * (1.0 - n) - beta_n * n;
103 let p_inf = 1.0 / (1.0 + (-(v + 35.0) / 10.0).exp());
104 let tau_p = 400.0 / (3.3 * ((v + 35.0) / 20.0).exp() + (-(v + 35.0) / 20.0).exp());
105 let dp = (p_inf - p) / tau_p;
106 let m_t_inf = 1.0 / (1.0 + (-(v + 57.0) / 6.2).exp());
107 let s_inf = 1.0 / (1.0 + ((v + 81.0) / 4.0).exp());
108 let tau_s = 30.0 + 200.0 / (1.0 + ((v + 70.0) / 5.0).exp());
109 let ds = (s_inf - s) / tau_s;
110 let r_inf = 1.0 / (1.0 + ((v + 80.0) / 10.0).exp());
111 let tau_r = 100.0 + 500.0 / ((-(v + 70.0) / 20.0).exp() + ((v + 70.0) / 20.0).exp());
112 let dr = (r_inf - r) / tau_r;
113 let i_na = self.g_na * m * m * m * h * (v - self.e_na);
114 let i_k = self.g_k * n * n * n * n * (v - self.e_k);
115 let i_m = self.g_m * p * (v - self.e_k);
116 let i_t = self.g_t * m_t_inf * m_t_inf * s * (v - self.e_ca);
117 let i_h = self.g_h * r * (v - self.e_h);
118 let i_l = self.g_l * (v - self.e_l);
119 let dvdt = (-i_na - i_k - i_m - i_t - i_h - i_l + current) / self.c_m;
120 [dvdt, dm, dh, dn, dp, ds, dr]
121 }
122
123 fn rk4_substep(&self, st: [f64; 7], current: f64) -> [f64; 7] {
126 let dt = self.dt;
127 let k1 = self.derivatives(st[0], st[1], st[2], st[3], st[4], st[5], st[6], current);
128 let mut a = [0.0_f64; 7];
129 for i in 0..7 {
130 a[i] = st[i] + 0.5 * dt * k1[i];
131 }
132 let k2 = self.derivatives(a[0], a[1], a[2], a[3], a[4], a[5], a[6], current);
133 for i in 0..7 {
134 a[i] = st[i] + 0.5 * dt * k2[i];
135 }
136 let k3 = self.derivatives(a[0], a[1], a[2], a[3], a[4], a[5], a[6], current);
137 for i in 0..7 {
138 a[i] = st[i] + dt * k3[i];
139 }
140 let k4 = self.derivatives(a[0], a[1], a[2], a[3], a[4], a[5], a[6], current);
141 let mut out = [0.0_f64; 7];
142 for i in 0..7 {
143 out[i] = st[i] + dt * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0;
144 }
145 out
146 }
147
148 pub fn step(&mut self, current: f64) -> i32 {
149 let v_prev = self.v;
150 let mut st = [self.v, self.m, self.h, self.n, self.p, self.s, self.r];
151 for _ in 0..4 {
152 st = self.rk4_substep(st, current);
153 }
154 self.v = st[0];
155 self.m = st[1];
156 self.h = st[2];
157 self.n = st[3];
158 self.p = st[4];
159 self.s = st[5];
160 self.r = st[6];
161 if self.v >= self.v_threshold && v_prev < self.v_threshold {
162 1
163 } else {
164 0
165 }
166 }
167
168 pub fn reset(&mut self) {
169 self.v = -65.0;
170 self.m = 0.02;
171 self.h = 0.8;
172 self.n = 0.2;
173 self.p = 0.0;
174 self.s = 0.9;
175 self.r = 0.1;
176 }
177}
178
179impl Default for SSTNeuron {
180 fn default() -> Self {
181 Self::new()
182 }
183}
184
185#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn sst_fires_with_input() {
195 let mut n = SSTNeuron::new();
196 let spikes: i32 = (0..10000).map(|_| n.step(5.0)).sum();
197 assert!(spikes > 0, "SST+ must fire with sustained input");
198 }
199
200 #[test]
201 fn sst_no_fire_without_input() {
202 let mut n = SSTNeuron::new();
203 let spikes: i32 = (0..5000).map(|_| n.step(0.0)).sum();
204 assert_eq!(spikes, 0);
205 }
206
207 #[test]
208 fn sst_adaptation_reduces_rate() {
209 let mut n = SSTNeuron::new();
210 let first_half: i32 = (0..5000).map(|_| n.step(5.0)).sum();
211 let second_half: i32 = (0..5000).map(|_| n.step(5.0)).sum();
212 assert!(
214 second_half <= first_half + 3,
215 "SST+ should adapt: first={first_half}, second={second_half}"
216 );
217 }
218
219 #[test]
220 fn sst_reset_roundtrip() {
221 let mut n = SSTNeuron::new();
222 for _ in 0..5000 {
223 n.step(5.0);
224 }
225 n.reset();
226 let mut fresh = SSTNeuron::new();
227 let r1: i32 = (0..2000).map(|_| n.step(5.0)).sum();
228 let r2: i32 = (0..2000).map(|_| fresh.step(5.0)).sum();
229 assert_eq!(r1, r2);
230 }
231
232 #[test]
233 fn sst_voltage_bounded() {
234 let mut n = SSTNeuron::new();
235 for _ in 0..20000 {
236 n.step(10.0);
237 }
238 assert!(n.v.is_finite());
239 assert!(n.p.is_finite());
240 assert!(n.s.is_finite());
241 }
242
243 #[test]
244 #[ignore = "wall-clock performance smoke; use Criterion benches for timing evidence"]
245 fn sst_performance_10k_steps() {
246 let mut n = SSTNeuron::new();
247 let start = std::time::Instant::now();
248 for _ in 0..10_000 {
249 n.step(5.0);
250 }
251 let elapsed = start.elapsed();
252 assert!(
253 elapsed.as_millis() < 500,
254 "10k SST steps took {:?}",
255 elapsed
256 );
257 }
258}