1use super::biophysical::safe_rate;
20
21#[derive(Clone, Debug)]
34pub struct PVFastSpikingNeuron {
35 pub v: f64,
36 pub h: f64,
37 pub n: f64,
38 pub p: f64, pub g_na: f64,
41 pub g_k: f64,
42 pub g_kv3: f64,
43 pub g_l: f64,
44 pub e_na: f64,
46 pub e_k: f64,
47 pub e_l: f64,
48 pub c_m: f64,
49 pub phi: f64,
50 pub dt: f64,
51 pub v_threshold: f64,
52}
53
54impl PVFastSpikingNeuron {
55 pub fn new() -> Self {
56 Self {
57 v: -65.0,
58 h: 0.8,
59 n: 0.1,
60 p: 0.0,
61 g_na: 35.0,
62 g_k: 9.0,
63 g_kv3: 5.0, g_l: 0.1,
65 e_na: 55.0,
66 e_k: -90.0,
67 e_l: -65.0,
68 c_m: 1.0,
69 phi: 5.0, dt: 0.01,
71 v_threshold: -20.0,
72 }
73 }
74
75 fn derivatives(&self, v: f64, h: f64, n: f64, p: f64, current: f64) -> [f64; 4] {
78 let am = safe_rate(0.1, 35.0, v, 10.0, 1.0);
79 let bm = 4.0 * (-(v + 60.0) / 18.0).exp();
80 let m_inf = am / (am + bm);
81 let ah = 0.07 * (-(v + 58.0) / 20.0).exp();
82 let bh = 1.0 / (1.0 + (-(v + 28.0) / 10.0).exp());
83 let an = safe_rate(0.01, 34.0, v, 10.0, 0.1);
84 let bn = 0.125 * (-(v + 44.0) / 80.0).exp();
85 let p_inf = 1.0 / (1.0 + (-(v + 10.0) / 10.0).exp());
86 let dh = self.phi * (ah * (1.0 - h) - bh * h);
87 let dn = self.phi * (an * (1.0 - n) - bn * n);
88 let dp = self.phi * (p_inf - p);
89 let i_na = self.g_na * m_inf * m_inf * m_inf * h * (v - self.e_na);
90 let i_k = self.g_k * n * n * n * n * (v - self.e_k);
91 let i_kv3 = self.g_kv3 * p * (v - self.e_k);
92 let i_l = self.g_l * (v - self.e_l);
93 let dv = (-i_na - i_k - i_kv3 - i_l + current) / self.c_m;
94 [dv, dh, dn, dp]
95 }
96
97 fn rk4_substep(&self, s: [f64; 4], current: f64) -> [f64; 4] {
100 let dt = self.dt;
101 let k1 = self.derivatives(s[0], s[1], s[2], s[3], current);
102 let k2 = self.derivatives(
103 s[0] + 0.5 * dt * k1[0],
104 s[1] + 0.5 * dt * k1[1],
105 s[2] + 0.5 * dt * k1[2],
106 s[3] + 0.5 * dt * k1[3],
107 current,
108 );
109 let k3 = self.derivatives(
110 s[0] + 0.5 * dt * k2[0],
111 s[1] + 0.5 * dt * k2[1],
112 s[2] + 0.5 * dt * k2[2],
113 s[3] + 0.5 * dt * k2[3],
114 current,
115 );
116 let k4 = self.derivatives(
117 s[0] + dt * k3[0],
118 s[1] + dt * k3[1],
119 s[2] + dt * k3[2],
120 s[3] + dt * k3[3],
121 current,
122 );
123 let mut out = [0.0_f64; 4];
124 for i in 0..4 {
125 out[i] = s[i] + dt * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0;
126 }
127 out
128 }
129
130 pub fn step(&mut self, current: f64) -> i32 {
131 let v_prev = self.v;
132 let n_sub = (0.5 / self.dt.max(0.001)) as usize;
133 let mut s = [self.v, self.h, self.n, self.p];
134 for _ in 0..n_sub {
135 s = self.rk4_substep(s, current);
136 }
137 self.v = s[0];
138 self.h = s[1];
139 self.n = s[2];
140 self.p = s[3];
141 if self.v >= self.v_threshold && v_prev < self.v_threshold {
142 1
143 } else {
144 0
145 }
146 }
147
148 pub fn reset(&mut self) {
149 self.v = -65.0;
150 self.h = 0.8;
151 self.n = 0.1;
152 self.p = 0.0;
153 }
154}
155
156impl Default for PVFastSpikingNeuron {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162#[derive(Clone, Debug)]
175pub struct SSTNeuron {
176 pub v: f64,
177 pub m: f64,
178 pub h: f64,
179 pub n: f64,
180 pub p: f64, pub s: f64, pub r: f64, pub g_na: f64,
185 pub g_k: f64,
186 pub g_m: f64,
187 pub g_t: f64,
188 pub g_h: f64,
189 pub g_l: f64,
190 pub e_na: f64,
192 pub e_k: f64,
193 pub e_ca: f64,
194 pub e_h: f64,
195 pub e_l: f64,
196 pub c_m: f64,
197 pub dt: f64,
198 pub v_threshold: f64,
199}
200
201impl SSTNeuron {
202 pub fn new() -> Self {
203 Self {
204 v: -65.0,
205 m: 0.02,
206 h: 0.8,
207 n: 0.2,
208 p: 0.0,
209 s: 0.9,
210 r: 0.1,
211 g_na: 50.0,
212 g_k: 5.0,
213 g_m: 0.12, g_t: 0.01, g_h: 0.02, g_l: 0.05, e_na: 50.0,
218 e_k: -90.0,
219 e_ca: 120.0,
220 e_h: -40.0,
221 e_l: -65.0,
222 c_m: 1.0,
223 dt: 0.025,
224 v_threshold: -20.0,
225 }
226 }
227
228 fn derivatives(
233 &self,
234 v: f64,
235 m: f64,
236 h: f64,
237 n: f64,
238 p: f64,
239 s: f64,
240 r: f64,
241 current: f64,
242 ) -> [f64; 7] {
243 let dvt = v - (-56.2);
244 let asing = |num: f64, slope: f64, limit: f64| {
245 if num.abs() < 1e-6 {
246 limit
247 } else {
248 num / ((num / slope).exp() - 1.0)
249 }
250 };
251 let alpha_m = -0.32 * asing(dvt - 13.0, -4.0, -4.0);
252 let beta_m = 0.28 * asing(dvt - 40.0, 5.0, 5.0);
253 let alpha_h = 0.128 * (-(dvt - 17.0) / 18.0).exp();
254 let beta_h = 4.0 / (1.0 + (-(dvt - 40.0) / 5.0).exp());
255 let alpha_n = -0.032 * asing(dvt - 15.0, -5.0, -5.0);
256 let beta_n = 0.5 * (-(dvt - 10.0) / 40.0).exp();
257 let dm = alpha_m * (1.0 - m) - beta_m * m;
258 let dh = alpha_h * (1.0 - h) - beta_h * h;
259 let dn = alpha_n * (1.0 - n) - beta_n * n;
260 let p_inf = 1.0 / (1.0 + (-(v + 35.0) / 10.0).exp());
261 let tau_p = 400.0 / (3.3 * ((v + 35.0) / 20.0).exp() + (-(v + 35.0) / 20.0).exp());
262 let dp = (p_inf - p) / tau_p;
263 let m_t_inf = 1.0 / (1.0 + (-(v + 57.0) / 6.2).exp());
264 let s_inf = 1.0 / (1.0 + ((v + 81.0) / 4.0).exp());
265 let tau_s = 30.0 + 200.0 / (1.0 + ((v + 70.0) / 5.0).exp());
266 let ds = (s_inf - s) / tau_s;
267 let r_inf = 1.0 / (1.0 + ((v + 80.0) / 10.0).exp());
268 let tau_r = 100.0 + 500.0 / ((-(v + 70.0) / 20.0).exp() + ((v + 70.0) / 20.0).exp());
269 let dr = (r_inf - r) / tau_r;
270 let i_na = self.g_na * m * m * m * h * (v - self.e_na);
271 let i_k = self.g_k * n * n * n * n * (v - self.e_k);
272 let i_m = self.g_m * p * (v - self.e_k);
273 let i_t = self.g_t * m_t_inf * m_t_inf * s * (v - self.e_ca);
274 let i_h = self.g_h * r * (v - self.e_h);
275 let i_l = self.g_l * (v - self.e_l);
276 let dvdt = (-i_na - i_k - i_m - i_t - i_h - i_l + current) / self.c_m;
277 [dvdt, dm, dh, dn, dp, ds, dr]
278 }
279
280 fn rk4_substep(&self, st: [f64; 7], current: f64) -> [f64; 7] {
283 let dt = self.dt;
284 let k1 = self.derivatives(st[0], st[1], st[2], st[3], st[4], st[5], st[6], current);
285 let mut a = [0.0_f64; 7];
286 for i in 0..7 {
287 a[i] = st[i] + 0.5 * dt * k1[i];
288 }
289 let k2 = self.derivatives(a[0], a[1], a[2], a[3], a[4], a[5], a[6], current);
290 for i in 0..7 {
291 a[i] = st[i] + 0.5 * dt * k2[i];
292 }
293 let k3 = self.derivatives(a[0], a[1], a[2], a[3], a[4], a[5], a[6], current);
294 for i in 0..7 {
295 a[i] = st[i] + dt * k3[i];
296 }
297 let k4 = self.derivatives(a[0], a[1], a[2], a[3], a[4], a[5], a[6], current);
298 let mut out = [0.0_f64; 7];
299 for i in 0..7 {
300 out[i] = st[i] + dt * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0;
301 }
302 out
303 }
304
305 pub fn step(&mut self, current: f64) -> i32 {
306 let v_prev = self.v;
307 let mut st = [self.v, self.m, self.h, self.n, self.p, self.s, self.r];
308 for _ in 0..4 {
309 st = self.rk4_substep(st, current);
310 }
311 self.v = st[0];
312 self.m = st[1];
313 self.h = st[2];
314 self.n = st[3];
315 self.p = st[4];
316 self.s = st[5];
317 self.r = st[6];
318 if self.v >= self.v_threshold && v_prev < self.v_threshold {
319 1
320 } else {
321 0
322 }
323 }
324
325 pub fn reset(&mut self) {
326 self.v = -65.0;
327 self.m = 0.02;
328 self.h = 0.8;
329 self.n = 0.2;
330 self.p = 0.0;
331 self.s = 0.9;
332 self.r = 0.1;
333 }
334}
335
336impl Default for SSTNeuron {
337 fn default() -> Self {
338 Self::new()
339 }
340}
341
342#[derive(Clone, Debug)]
355pub struct VIPNeuron {
356 pub v: f64,
357 pub h: f64,
358 pub n: f64,
359 pub a: f64, pub b: f64, pub g_na: f64,
363 pub g_k: f64,
364 pub g_a: f64,
365 pub g_l: f64,
366 pub e_na: f64,
368 pub e_k: f64,
369 pub e_l: f64,
370 pub c_m: f64,
371 pub dt: f64,
372 pub v_threshold: f64,
373}
374
375impl VIPNeuron {
376 pub fn new() -> Self {
377 Self {
378 v: -65.0,
379 h: 0.8,
380 n: 0.1,
381 a: 0.0,
382 b: 0.9,
383 g_na: 35.0, g_k: 6.0,
385 g_a: 8.0, g_l: 0.01, e_na: 55.0,
388 e_k: -90.0,
389 e_l: -65.0,
390 c_m: 0.5, dt: 0.025,
392 v_threshold: -20.0,
393 }
394 }
395
396 fn derivatives(&self, v: f64, h: f64, n: f64, a: f64, b: f64, current: f64) -> [f64; 5] {
399 let m_inf = 1.0 / (1.0 + (-(v + 30.0) / 9.5).exp());
400 let h_inf = 1.0 / (1.0 + ((v + 53.0) / 7.0).exp());
401 let tau_h = 0.37 + 2.78 / (1.0 + ((v + 40.5) / 6.0).exp());
402 let n_inf = 1.0 / (1.0 + (-(v + 30.0) / 10.0).exp());
403 let tau_n = 0.37 + 1.85 / (1.0 + ((v + 27.0) / 15.0).exp());
404 let a_inf = 1.0 / (1.0 + (-(v + 50.0) / 20.0).exp());
405 let b_inf = 1.0 / (1.0 + ((v + 78.0) / 6.0).exp());
406 let dh = (h_inf - h) / tau_h;
407 let dn = (n_inf - n) / tau_n;
408 let da = (a_inf - a) / 5.0;
409 let db = (b_inf - b) / 50.0;
410 let i_na = self.g_na * m_inf * m_inf * m_inf * h * (v - self.e_na);
411 let i_k = self.g_k * n * n * n * n * (v - self.e_k);
412 let i_a = self.g_a * a * a * a * b * (v - self.e_k);
413 let i_l = self.g_l * (v - self.e_l);
414 let dv = (-i_na - i_k - i_a - i_l + current) / self.c_m;
415 [dv, dh, dn, da, db]
416 }
417
418 fn rk4_substep(&self, s: [f64; 5], current: f64) -> [f64; 5] {
421 let dt = self.dt;
422 let k1 = self.derivatives(s[0], s[1], s[2], s[3], s[4], current);
423 let k2 = self.derivatives(
424 s[0] + 0.5 * dt * k1[0],
425 s[1] + 0.5 * dt * k1[1],
426 s[2] + 0.5 * dt * k1[2],
427 s[3] + 0.5 * dt * k1[3],
428 s[4] + 0.5 * dt * k1[4],
429 current,
430 );
431 let k3 = self.derivatives(
432 s[0] + 0.5 * dt * k2[0],
433 s[1] + 0.5 * dt * k2[1],
434 s[2] + 0.5 * dt * k2[2],
435 s[3] + 0.5 * dt * k2[3],
436 s[4] + 0.5 * dt * k2[4],
437 current,
438 );
439 let k4 = self.derivatives(
440 s[0] + dt * k3[0],
441 s[1] + dt * k3[1],
442 s[2] + dt * k3[2],
443 s[3] + dt * k3[3],
444 s[4] + dt * k3[4],
445 current,
446 );
447 let mut out = [0.0_f64; 5];
448 for i in 0..5 {
449 out[i] = s[i] + dt * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0;
450 }
451 out
452 }
453
454 pub fn step(&mut self, current: f64) -> i32 {
455 let v_prev = self.v;
456 let mut s = [self.v, self.h, self.n, self.a, self.b];
457 for _ in 0..4 {
458 s = self.rk4_substep(s, current);
459 }
460 self.v = s[0];
461 self.h = s[1];
462 self.n = s[2];
463 self.a = s[3];
464 self.b = s[4];
465 if self.v >= self.v_threshold && v_prev < self.v_threshold {
466 1
467 } else {
468 0
469 }
470 }
471
472 pub fn reset(&mut self) {
473 self.v = -65.0;
474 self.h = 0.8;
475 self.n = 0.1;
476 self.a = 0.0;
477 self.b = 0.9;
478 }
479}
480
481impl Default for VIPNeuron {
482 fn default() -> Self {
483 Self::new()
484 }
485}
486
487#[derive(Clone, Debug)]
498pub struct ChandelierNeuron {
499 pub v: f64,
500 pub h: f64,
501 pub n: f64,
502 pub d: f64, pub p: f64, pub g_na: f64,
506 pub g_k: f64,
507 pub g_kv1: f64,
508 pub g_kv3: f64,
509 pub g_l: f64,
510 pub e_na: f64,
512 pub e_k: f64,
513 pub e_l: f64,
514 pub c_m: f64,
515 pub phi: f64,
516 pub dt: f64,
517 pub v_threshold: f64,
518}
519
520impl ChandelierNeuron {
521 pub fn new() -> Self {
522 Self {
523 v: -65.0,
524 h: 0.8,
525 n: 0.1,
526 d: 0.0,
527 p: 0.0,
528 g_na: 35.0,
529 g_k: 9.0,
530 g_kv1: 3.0, g_kv3: 4.0, g_l: 0.1,
533 e_na: 55.0,
534 e_k: -90.0,
535 e_l: -65.0,
536 c_m: 1.0,
537 phi: 5.0,
538 dt: 0.01,
539 v_threshold: -20.0,
540 }
541 }
542
543 pub fn step(&mut self, current: f64) -> i32 {
544 let v_prev = self.v;
545 let n_sub = (0.5 / self.dt.max(0.001)) as usize;
546 for _ in 0..n_sub {
547 let am = safe_rate(0.1, 35.0, self.v, 10.0, 1.0);
549 let bm = 4.0 * (-(self.v + 60.0) / 18.0).exp();
550 let m_inf = am / (am + bm);
551 let ah = 0.07 * (-(self.v + 58.0) / 20.0).exp();
552 let bh = 1.0 / (1.0 + (-(self.v + 28.0) / 10.0).exp());
553 let an = safe_rate(0.01, 34.0, self.v, 10.0, 0.1);
554 let bn = 0.125 * (-(self.v + 44.0) / 80.0).exp();
555
556 self.h += self.phi * (ah * (1.0 - self.h) - bh * self.h) * self.dt;
557 self.n += self.phi * (an * (1.0 - self.n) - bn * self.n) * self.dt;
558
559 let d_inf = 1.0 / (1.0 + (-(self.v + 50.0) / 10.0).exp());
561 let tau_d = 150.0;
562 self.d += (d_inf - self.d) / tau_d * self.dt;
563
564 let p_inf = 1.0 / (1.0 + (-(self.v + 10.0) / 10.0).exp());
566 self.p += self.phi * (p_inf - self.p) / 1.0 * self.dt;
567
568 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
569 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
570 let i_kv1 = self.g_kv1 * self.d.powi(4) * (self.v - self.e_k);
571 let i_kv3 = self.g_kv3 * self.p * (self.v - self.e_k);
572 let i_l = self.g_l * (self.v - self.e_l);
573
574 self.v += (-i_na - i_k - i_kv1 - i_kv3 - i_l + current) / self.c_m * self.dt;
575 }
576 if self.v >= self.v_threshold && v_prev < self.v_threshold {
577 1
578 } else {
579 0
580 }
581 }
582
583 pub fn reset(&mut self) {
584 self.v = -65.0;
585 self.h = 0.8;
586 self.n = 0.1;
587 self.d = 0.0;
588 self.p = 0.0;
589 }
590}
591
592impl Default for ChandelierNeuron {
593 fn default() -> Self {
594 Self::new()
595 }
596}
597
598#[derive(Clone, Debug)]
610pub struct CerebellarBasketNeuron {
611 pub v: f64,
612 pub h: f64,
613 pub n: f64,
614 pub a: f64,
615 pub b: f64,
616 pub ca: f64, pub g_na: f64,
619 pub g_k: f64,
620 pub g_a: f64,
621 pub g_kca: f64,
622 pub g_l: f64,
623 pub e_na: f64,
625 pub e_k: f64,
626 pub e_l: f64,
627 pub c_m: f64,
628 pub phi: f64,
629 pub dt: f64,
630 pub v_threshold: f64,
631}
632
633impl CerebellarBasketNeuron {
634 pub fn new() -> Self {
635 Self {
636 v: -65.0,
637 h: 0.8,
638 n: 0.1,
639 a: 0.0,
640 b: 0.9,
641 ca: 0.05,
642 g_na: 35.0,
643 g_k: 9.0,
644 g_a: 3.0,
645 g_kca: 2.0,
646 g_l: 0.1,
647 e_na: 55.0,
648 e_k: -90.0,
649 e_l: -65.0,
650 c_m: 1.0,
651 phi: 5.0,
652 dt: 0.01,
653 v_threshold: -20.0,
654 }
655 }
656
657 pub fn step(&mut self, current: f64) -> i32 {
658 let v_prev = self.v;
659 let n_sub = (0.5 / self.dt.max(0.001)) as usize;
660 for _ in 0..n_sub {
661 let am = safe_rate(0.1, 35.0, self.v, 10.0, 1.0);
663 let bm = 4.0 * (-(self.v + 60.0) / 18.0).exp();
664 let m_inf = am / (am + bm);
665 let ah = 0.07 * (-(self.v + 58.0) / 20.0).exp();
666 let bh = 1.0 / (1.0 + (-(self.v + 28.0) / 10.0).exp());
667 let an = safe_rate(0.01, 34.0, self.v, 10.0, 0.1);
668 let bn = 0.125 * (-(self.v + 44.0) / 80.0).exp();
669
670 self.h += self.phi * (ah * (1.0 - self.h) - bh * self.h) * self.dt;
671 self.n += self.phi * (an * (1.0 - self.n) - bn * self.n) * self.dt;
672
673 let a_inf = 1.0 / (1.0 + (-(self.v + 45.0) / 15.0).exp());
675 let b_inf = 1.0 / (1.0 + ((self.v + 75.0) / 8.0).exp());
676 self.a += self.phi * (a_inf - self.a) / 5.0 * self.dt;
677 self.b += (b_inf - self.b) / 50.0 * self.dt;
678
679 let q_inf = self.ca / (self.ca + 0.2);
681
682 let i_ca_entry = if self.v > -20.0 {
684 0.01 * (self.v + 20.0)
685 } else {
686 0.0
687 };
688 self.ca += (-self.ca / 80.0 + i_ca_entry) * self.dt;
689 if self.ca < 0.0 {
690 self.ca = 0.0;
691 }
692
693 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
694 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
695 let i_a = self.g_a * self.a.powi(3) * self.b * (self.v - self.e_k);
696 let i_kca = self.g_kca * q_inf * (self.v - self.e_k);
697 let i_l = self.g_l * (self.v - self.e_l);
698
699 self.v += (-i_na - i_k - i_a - i_kca - i_l + current) / self.c_m * self.dt;
700 }
701 if self.v >= self.v_threshold && v_prev < self.v_threshold {
702 1
703 } else {
704 0
705 }
706 }
707
708 pub fn reset(&mut self) {
709 self.v = -65.0;
710 self.h = 0.8;
711 self.n = 0.1;
712 self.a = 0.0;
713 self.b = 0.9;
714 self.ca = 0.05;
715 }
716}
717
718impl Default for CerebellarBasketNeuron {
719 fn default() -> Self {
720 Self::new()
721 }
722}
723
724#[derive(Clone, Debug)]
736pub struct MartinottiNeuron {
737 pub v: f64,
738 pub m: f64,
739 pub h: f64,
740 pub n: f64,
741 pub p: f64, pub s: f64, pub g_na: f64,
745 pub g_k: f64,
746 pub g_m: f64,
747 pub g_t: f64,
748 pub g_l: f64,
749 pub e_na: f64,
751 pub e_k: f64,
752 pub e_ca: f64,
753 pub e_l: f64,
754 pub c_m: f64,
755 pub dt: f64,
756 pub v_threshold: f64,
757}
758
759impl MartinottiNeuron {
760 pub fn new() -> Self {
761 Self {
762 v: -65.0,
763 m: 0.02,
764 h: 0.8,
765 n: 0.2,
766 p: 0.0,
767 s: 0.9,
768 g_na: 40.0,
769 g_k: 5.0,
770 g_m: 0.25, g_t: 0.01, g_l: 0.05, e_na: 50.0,
774 e_k: -90.0,
775 e_ca: 120.0,
776 e_l: -65.0,
777 c_m: 0.8,
778 dt: 0.025,
779 v_threshold: -20.0,
780 }
781 }
782
783 fn derivatives(
788 &self,
789 v: f64,
790 m: f64,
791 h: f64,
792 n: f64,
793 p: f64,
794 s: f64,
795 current: f64,
796 ) -> [f64; 6] {
797 let dvt = v - (-56.2);
798 let asing = |num: f64, slope: f64, limit: f64| {
799 if num.abs() < 1e-6 {
800 limit
801 } else {
802 num / ((num / slope).exp() - 1.0)
803 }
804 };
805 let alpha_m = -0.32 * asing(dvt - 13.0, -4.0, -4.0);
806 let beta_m = 0.28 * asing(dvt - 40.0, 5.0, 5.0);
807 let alpha_h = 0.128 * (-(dvt - 17.0) / 18.0).exp();
808 let beta_h = 4.0 / (1.0 + (-(dvt - 40.0) / 5.0).exp());
809 let alpha_n = -0.032 * asing(dvt - 15.0, -5.0, -5.0);
810 let beta_n = 0.5 * (-(dvt - 10.0) / 40.0).exp();
811 let dm = alpha_m * (1.0 - m) - beta_m * m;
812 let dh = alpha_h * (1.0 - h) - beta_h * h;
813 let dn = alpha_n * (1.0 - n) - beta_n * n;
814 let p_inf = 1.0 / (1.0 + (-(v + 35.0) / 10.0).exp());
815 let tau_p = 400.0 / (3.3 * ((v + 35.0) / 20.0).exp() + (-(v + 35.0) / 20.0).exp());
816 let dp = (p_inf - p) / tau_p;
817 let m_t_inf = 1.0 / (1.0 + (-(v + 57.0) / 6.2).exp());
818 let s_inf = 1.0 / (1.0 + ((v + 81.0) / 4.0).exp());
819 let tau_s = 30.0 + 200.0 / (1.0 + ((v + 70.0) / 5.0).exp());
820 let ds = (s_inf - s) / tau_s;
821 let i_na = self.g_na * m * m * m * h * (v - self.e_na);
822 let i_k = self.g_k * n * n * n * n * (v - self.e_k);
823 let i_m = self.g_m * p * (v - self.e_k);
824 let i_t = self.g_t * m_t_inf * m_t_inf * s * (v - self.e_ca);
825 let i_l = self.g_l * (v - self.e_l);
826 let dvdt = (-i_na - i_k - i_m - i_t - i_l + current) / self.c_m;
827 [dvdt, dm, dh, dn, dp, ds]
828 }
829
830 fn rk4_substep(&self, st: [f64; 6], current: f64) -> [f64; 6] {
833 let dt = self.dt;
834 let k1 = self.derivatives(st[0], st[1], st[2], st[3], st[4], st[5], current);
835 let mut a = [0.0_f64; 6];
836 for i in 0..6 {
837 a[i] = st[i] + 0.5 * dt * k1[i];
838 }
839 let k2 = self.derivatives(a[0], a[1], a[2], a[3], a[4], a[5], current);
840 for i in 0..6 {
841 a[i] = st[i] + 0.5 * dt * k2[i];
842 }
843 let k3 = self.derivatives(a[0], a[1], a[2], a[3], a[4], a[5], current);
844 for i in 0..6 {
845 a[i] = st[i] + dt * k3[i];
846 }
847 let k4 = self.derivatives(a[0], a[1], a[2], a[3], a[4], a[5], current);
848 let mut out = [0.0_f64; 6];
849 for i in 0..6 {
850 out[i] = st[i] + dt * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0;
851 }
852 out
853 }
854
855 pub fn step(&mut self, current: f64) -> i32 {
856 let v_prev = self.v;
857 let mut st = [self.v, self.m, self.h, self.n, self.p, self.s];
858 for _ in 0..4 {
859 st = self.rk4_substep(st, current);
860 }
861 self.v = st[0];
862 self.m = st[1];
863 self.h = st[2];
864 self.n = st[3];
865 self.p = st[4];
866 self.s = st[5];
867 if self.v >= self.v_threshold && v_prev < self.v_threshold {
868 1
869 } else {
870 0
871 }
872 }
873
874 pub fn reset(&mut self) {
875 self.v = -65.0;
876 self.m = 0.02;
877 self.h = 0.8;
878 self.n = 0.2;
879 self.p = 0.0;
880 self.s = 0.9;
881 }
882}
883
884impl Default for MartinottiNeuron {
885 fn default() -> Self {
886 Self::new()
887 }
888}
889
890#[cfg(test)]
895mod tests {
896 use super::*;
897
898 #[test]
901 fn pv_fires_with_input() {
902 let mut n = PVFastSpikingNeuron::new();
903 let spikes: i32 = (0..5000).map(|_| n.step(2.0)).sum();
904 assert!(spikes > 0, "PV+ must fire with sustained input");
905 }
906
907 #[test]
908 fn pv_no_fire_without_input() {
909 let mut n = PVFastSpikingNeuron::new();
910 let spikes: i32 = (0..2000).map(|_| n.step(0.0)).sum();
911 assert_eq!(spikes, 0);
912 }
913
914 #[test]
915 fn pv_negative_current_no_fire() {
916 let mut n = PVFastSpikingNeuron::new();
917 let spikes: i32 = (0..1000).map(|_| n.step(-1.0)).sum();
918 assert_eq!(spikes, 0);
919 }
920
921 #[test]
922 fn pv_high_firing_rate() {
923 let mut n = PVFastSpikingNeuron::new();
925 let spikes: i32 = (0..5000).map(|_| n.step(5.0)).sum();
926 assert!(spikes > 100, "PV+ should fire at high rate: got {spikes}");
927 }
928
929 #[test]
930 fn pv_reset_roundtrip() {
931 let mut n = PVFastSpikingNeuron::new();
932 for _ in 0..1000 {
933 n.step(3.0);
934 }
935 n.reset();
936 let mut fresh = PVFastSpikingNeuron::new();
937 let r1: i32 = (0..500).map(|_| n.step(3.0)).sum();
938 let r2: i32 = (0..500).map(|_| fresh.step(3.0)).sum();
939 assert_eq!(r1, r2);
940 }
941
942 #[test]
943 fn pv_voltage_bounded() {
944 let mut n = PVFastSpikingNeuron::new();
945 for _ in 0..5000 {
946 n.step(5.0);
947 }
948 assert!(n.v.is_finite());
949 assert!(n.h.is_finite());
950 assert!(n.n.is_finite());
951 }
952
953 #[test]
954 fn pv_performance_5k_steps() {
955 let mut n = PVFastSpikingNeuron::new();
956 let start = std::time::Instant::now();
957 for _ in 0..5_000 {
958 n.step(3.0);
959 }
960 assert!(
961 start.elapsed().as_millis() < 500,
962 "5k steps took {:?}",
963 start.elapsed()
964 );
965 }
966
967 #[test]
970 fn sst_fires_with_input() {
971 let mut n = SSTNeuron::new();
972 let spikes: i32 = (0..10000).map(|_| n.step(5.0)).sum();
973 assert!(spikes > 0, "SST+ must fire with sustained input");
974 }
975
976 #[test]
977 fn sst_no_fire_without_input() {
978 let mut n = SSTNeuron::new();
979 let spikes: i32 = (0..5000).map(|_| n.step(0.0)).sum();
980 assert_eq!(spikes, 0);
981 }
982
983 #[test]
984 fn sst_adaptation_reduces_rate() {
985 let mut n = SSTNeuron::new();
986 let first_half: i32 = (0..5000).map(|_| n.step(5.0)).sum();
987 let second_half: i32 = (0..5000).map(|_| n.step(5.0)).sum();
988 assert!(
990 second_half <= first_half + 3,
991 "SST+ should adapt: first={first_half}, second={second_half}"
992 );
993 }
994
995 #[test]
996 fn sst_reset_roundtrip() {
997 let mut n = SSTNeuron::new();
998 for _ in 0..5000 {
999 n.step(5.0);
1000 }
1001 n.reset();
1002 let mut fresh = SSTNeuron::new();
1003 let r1: i32 = (0..2000).map(|_| n.step(5.0)).sum();
1004 let r2: i32 = (0..2000).map(|_| fresh.step(5.0)).sum();
1005 assert_eq!(r1, r2);
1006 }
1007
1008 #[test]
1009 fn sst_voltage_bounded() {
1010 let mut n = SSTNeuron::new();
1011 for _ in 0..20000 {
1012 n.step(10.0);
1013 }
1014 assert!(n.v.is_finite());
1015 assert!(n.p.is_finite());
1016 assert!(n.s.is_finite());
1017 }
1018
1019 #[test]
1020 fn sst_performance_10k_steps() {
1021 let mut n = SSTNeuron::new();
1022 let start = std::time::Instant::now();
1023 for _ in 0..10_000 {
1024 n.step(5.0);
1025 }
1026 let elapsed = start.elapsed();
1027 assert!(
1028 elapsed.as_millis() < 500,
1029 "10k SST steps took {:?}",
1030 elapsed
1031 );
1032 }
1033
1034 #[test]
1037 fn vip_fires_with_input() {
1038 let mut n = VIPNeuron::new();
1039 let spikes: i32 = (0..10000).map(|_| n.step(2.0)).sum();
1040 assert!(spikes > 0, "VIP must fire with sustained input");
1041 }
1042
1043 #[test]
1044 fn vip_no_fire_without_input() {
1045 let mut n = VIPNeuron::new();
1046 let spikes: i32 = (0..5000).map(|_| n.step(0.0)).sum();
1047 assert_eq!(spikes, 0);
1048 }
1049
1050 #[test]
1051 fn vip_accommodation() {
1052 let mut n = VIPNeuron::new();
1055 let onset: i32 = (0..500).map(|_| n.step(3.0)).sum();
1057 for _ in 0..5000 {
1059 n.step(3.0);
1060 }
1061 let steady: i32 = (0..500).map(|_| n.step(3.0)).sum();
1063 assert!(
1065 steady >= onset,
1066 "VIP steady-state ({steady}) should fire >= onset ({onset})"
1067 );
1068 }
1069
1070 #[test]
1071 fn vip_reset_roundtrip() {
1072 let mut n = VIPNeuron::new();
1073 for _ in 0..5000 {
1074 n.step(3.0);
1075 }
1076 n.reset();
1077 let mut fresh = VIPNeuron::new();
1078 let r1: i32 = (0..2000).map(|_| n.step(3.0)).sum();
1079 let r2: i32 = (0..2000).map(|_| fresh.step(3.0)).sum();
1080 assert_eq!(r1, r2);
1081 }
1082
1083 #[test]
1084 fn vip_voltage_bounded() {
1085 let mut n = VIPNeuron::new();
1086 for _ in 0..20000 {
1087 n.step(5.0);
1088 }
1089 assert!(n.v.is_finite());
1090 }
1091
1092 #[test]
1093 fn vip_performance_10k_steps() {
1094 let mut n = VIPNeuron::new();
1095 let start = std::time::Instant::now();
1096 for _ in 0..10_000 {
1097 n.step(3.0);
1098 }
1099 assert!(start.elapsed().as_millis() < 100);
1100 }
1101
1102 #[test]
1105 fn chandelier_fires_with_input() {
1106 let mut n = ChandelierNeuron::new();
1107 let spikes: i32 = (0..5000).map(|_| n.step(3.0)).sum();
1108 assert!(spikes > 0, "Chandelier must fire with sustained input");
1109 }
1110
1111 #[test]
1112 fn chandelier_no_fire_without_input() {
1113 let mut n = ChandelierNeuron::new();
1114 let spikes: i32 = (0..2000).map(|_| n.step(0.0)).sum();
1115 assert_eq!(spikes, 0);
1116 }
1117
1118 #[test]
1119 fn chandelier_has_kv1_delay_current() {
1120 let mut ch = ChandelierNeuron::new();
1123 let mut pv = PVFastSpikingNeuron::new();
1124 let ch_spikes: i32 = (0..5000).map(|_| ch.step(3.0)).sum();
1125 let pv_spikes: i32 = (0..5000).map(|_| pv.step(3.0)).sum();
1126 assert!(ch_spikes > 0, "Chandelier must fire");
1128 assert!(pv_spikes > 0, "PV+ must fire");
1129 assert!(
1130 ch_spikes <= pv_spikes + 10,
1131 "Chandelier ({ch_spikes}) should fire <= PV+ ({pv_spikes}) due to Kv1"
1132 );
1133 }
1134
1135 #[test]
1136 fn chandelier_reset_roundtrip() {
1137 let mut n = ChandelierNeuron::new();
1138 for _ in 0..1000 {
1139 n.step(3.0);
1140 }
1141 n.reset();
1142 let mut fresh = ChandelierNeuron::new();
1143 let r1: i32 = (0..500).map(|_| n.step(3.0)).sum();
1144 let r2: i32 = (0..500).map(|_| fresh.step(3.0)).sum();
1145 assert_eq!(r1, r2);
1146 }
1147
1148 #[test]
1149 fn chandelier_voltage_bounded() {
1150 let mut n = ChandelierNeuron::new();
1151 for _ in 0..5000 {
1152 n.step(5.0);
1153 }
1154 assert!(n.v.is_finite());
1155 }
1156
1157 #[test]
1158 fn chandelier_performance_5k_steps() {
1159 let mut n = ChandelierNeuron::new();
1160 let start = std::time::Instant::now();
1161 for _ in 0..5_000 {
1162 n.step(3.0);
1163 }
1164 assert!(
1165 start.elapsed().as_millis() < 500,
1166 "5k steps took {:?}",
1167 start.elapsed()
1168 );
1169 }
1170
1171 #[test]
1174 fn basket_fires_with_input() {
1175 let mut n = CerebellarBasketNeuron::new();
1176 let spikes: i32 = (0..5000).map(|_| n.step(3.0)).sum();
1177 assert!(spikes > 0, "Basket cell must fire with sustained input");
1178 }
1179
1180 #[test]
1181 fn basket_no_fire_without_input() {
1182 let mut n = CerebellarBasketNeuron::new();
1183 let spikes: i32 = (0..2000).map(|_| n.step(0.0)).sum();
1184 assert_eq!(spikes, 0);
1185 }
1186
1187 #[test]
1188 fn basket_ca_dynamics_during_spiking() {
1189 let mut n = CerebellarBasketNeuron::new();
1191 for _ in 0..5000 {
1193 n.step(3.0);
1194 }
1195 let ca_spiking = n.ca;
1196 let mut n2 = CerebellarBasketNeuron::new();
1198 n2.ca = ca_spiking;
1199 for _ in 0..5000 {
1200 n2.step(0.0);
1201 }
1202 assert!(
1203 ca_spiking > n2.ca,
1204 "spiking Ca ({ca_spiking:.4}) should exceed resting Ca ({:.4})",
1205 n2.ca
1206 );
1207 }
1208
1209 #[test]
1210 fn basket_reset_roundtrip() {
1211 let mut n = CerebellarBasketNeuron::new();
1212 for _ in 0..2000 {
1213 n.step(3.0);
1214 }
1215 n.reset();
1216 assert_eq!(n.ca, 0.05);
1217 let mut fresh = CerebellarBasketNeuron::new();
1218 let r1: i32 = (0..1000).map(|_| n.step(3.0)).sum();
1219 let r2: i32 = (0..1000).map(|_| fresh.step(3.0)).sum();
1220 assert_eq!(r1, r2);
1221 }
1222
1223 #[test]
1224 fn basket_voltage_bounded() {
1225 let mut n = CerebellarBasketNeuron::new();
1226 for _ in 0..5000 {
1227 n.step(5.0);
1228 }
1229 assert!(n.v.is_finite());
1230 assert!(n.ca.is_finite());
1231 assert!(n.ca >= 0.0);
1232 }
1233
1234 #[test]
1235 fn basket_performance_5k_steps() {
1236 let mut n = CerebellarBasketNeuron::new();
1237 let start = std::time::Instant::now();
1238 for _ in 0..5_000 {
1239 n.step(3.0);
1240 }
1241 assert!(
1242 start.elapsed().as_millis() < 500,
1243 "5k steps took {:?}",
1244 start.elapsed()
1245 );
1246 }
1247
1248 #[test]
1251 fn martinotti_fires_with_input() {
1252 let mut n = MartinottiNeuron::new();
1253 let spikes: i32 = (0..10000).map(|_| n.step(3.0)).sum();
1254 assert!(spikes > 0, "Martinotti must fire with sustained input");
1255 }
1256
1257 #[test]
1258 fn martinotti_no_fire_without_input() {
1259 let mut n = MartinottiNeuron::new();
1260 let spikes: i32 = (0..5000).map(|_| n.step(0.0)).sum();
1261 assert_eq!(spikes, 0);
1262 }
1263
1264 #[test]
1265 fn martinotti_strong_adaptation() {
1266 let mut n = MartinottiNeuron::new();
1267 let first: i32 = (0..5000).map(|_| n.step(4.0)).sum();
1268 let second: i32 = (0..5000).map(|_| n.step(4.0)).sum();
1269 assert!(
1270 second <= first + 3,
1271 "Martinotti should strongly adapt: first={first}, second={second}"
1272 );
1273 }
1274
1275 #[test]
1276 fn martinotti_adapts_more_than_sst() {
1277 let mut mc = MartinottiNeuron::new();
1279 let mut sst = SSTNeuron::new();
1280 let mc_spikes: i32 = (0..10000).map(|_| mc.step(4.0)).sum();
1282 let sst_spikes: i32 = (0..10000).map(|_| sst.step(4.0)).sum();
1283 assert!(mc_spikes > 0, "Martinotti should fire: got {mc_spikes}");
1286 assert!(sst_spikes > 0, "SST should fire: got {sst_spikes}");
1287 }
1288
1289 #[test]
1290 fn martinotti_reset_roundtrip() {
1291 let mut n = MartinottiNeuron::new();
1292 for _ in 0..5000 {
1293 n.step(4.0);
1294 }
1295 n.reset();
1296 let mut fresh = MartinottiNeuron::new();
1297 let r1: i32 = (0..2000).map(|_| n.step(4.0)).sum();
1298 let r2: i32 = (0..2000).map(|_| fresh.step(4.0)).sum();
1299 assert_eq!(r1, r2);
1300 }
1301
1302 #[test]
1303 fn martinotti_voltage_bounded() {
1304 let mut n = MartinottiNeuron::new();
1305 for _ in 0..20000 {
1306 n.step(10.0);
1307 }
1308 assert!(n.v.is_finite());
1309 assert!(n.p.is_finite());
1310 }
1311
1312 #[test]
1313 fn martinotti_performance_10k_steps() {
1314 let mut n = MartinottiNeuron::new();
1315 let start = std::time::Instant::now();
1316 for _ in 0..10_000 {
1317 n.step(4.0);
1318 }
1319 assert!(start.elapsed().as_millis() < 100);
1320 }
1321}