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 pub fn step(&mut self, current: f64) -> i32 {
76 let v_prev = self.v;
77 let n_sub = (0.5 / self.dt.max(0.001)) as usize;
78 for _ in 0..n_sub {
79 let am = safe_rate(0.1, 35.0, self.v, 10.0, 1.0);
81 let bm = 4.0 * (-(self.v + 60.0) / 18.0).exp();
82 let m_inf = am / (am + bm);
83 let ah = 0.07 * (-(self.v + 58.0) / 20.0).exp();
84 let bh = 1.0 / (1.0 + (-(self.v + 28.0) / 10.0).exp());
85 let an = safe_rate(0.01, 34.0, self.v, 10.0, 0.1);
86 let bn = 0.125 * (-(self.v + 44.0) / 80.0).exp();
87
88 self.h += self.phi * (ah * (1.0 - self.h) - bh * self.h) * self.dt;
89 self.n += self.phi * (an * (1.0 - self.n) - bn * self.n) * self.dt;
90
91 let p_inf = 1.0 / (1.0 + (-(self.v + 10.0) / 10.0).exp());
93 self.p += self.phi * (p_inf - self.p) / 1.0 * self.dt;
94
95 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
96 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
97 let i_kv3 = self.g_kv3 * self.p * (self.v - self.e_k);
98 let i_l = self.g_l * (self.v - self.e_l);
99
100 self.v += (-i_na - i_k - i_kv3 - i_l + current) / self.c_m * self.dt;
101 }
102 if self.v >= self.v_threshold && v_prev < self.v_threshold {
103 1
104 } else {
105 0
106 }
107 }
108
109 pub fn reset(&mut self) {
110 self.v = -65.0;
111 self.h = 0.8;
112 self.n = 0.1;
113 self.p = 0.0;
114 }
115}
116
117impl Default for PVFastSpikingNeuron {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123#[derive(Clone, Debug)]
136pub struct SSTNeuron {
137 pub v: f64,
138 pub m: f64,
139 pub h: f64,
140 pub n: f64,
141 pub p: f64, pub s: f64, pub r: f64, pub g_na: f64,
146 pub g_k: f64,
147 pub g_m: f64,
148 pub g_t: f64,
149 pub g_h: f64,
150 pub g_l: f64,
151 pub e_na: f64,
153 pub e_k: f64,
154 pub e_ca: f64,
155 pub e_h: f64,
156 pub e_l: f64,
157 pub c_m: f64,
158 pub dt: f64,
159 pub v_threshold: f64,
160}
161
162impl SSTNeuron {
163 pub fn new() -> Self {
164 Self {
165 v: -65.0,
166 m: 0.02,
167 h: 0.8,
168 n: 0.2,
169 p: 0.0,
170 s: 0.9,
171 r: 0.1,
172 g_na: 50.0,
173 g_k: 5.0,
174 g_m: 0.12, g_t: 0.01, g_h: 0.02, g_l: 0.05, e_na: 50.0,
179 e_k: -90.0,
180 e_ca: 120.0,
181 e_h: -40.0,
182 e_l: -65.0,
183 c_m: 1.0,
184 dt: 0.025,
185 v_threshold: -20.0,
186 }
187 }
188
189 pub fn step(&mut self, current: f64) -> i32 {
190 let v_prev = self.v;
191 let vt = -56.2;
192 for _ in 0..4 {
193 let dv = self.v - vt;
195 let x_m = dv - 13.0;
196 let alpha_m = if x_m.abs() < 1e-6 {
197 0.32 * 4.0
198 } else {
199 -0.32 * x_m / ((-(x_m) / 4.0).exp() - 1.0)
200 };
201 let x_h = dv - 17.0;
202 let beta_m = if x_h.abs() < 1e-6 {
203 0.28 * 5.0
204 } else {
205 0.28 * x_h / (((x_h) / 5.0).exp() - 1.0)
206 };
207 let alpha_h = 0.128 * (-(dv - 17.0) / 18.0).exp();
208 let beta_h = 4.0 / (1.0 + (-(dv - 40.0) / 5.0).exp());
209 let x_n = dv - 15.0;
211 let alpha_n = if x_n.abs() < 1e-6 {
212 0.032 * 5.0
213 } else {
214 -0.032 * x_n / ((-x_n / 5.0).exp() - 1.0)
215 };
216 let beta_n = 0.5 * (-(dv - 10.0) / 40.0).exp();
217
218 self.m += (alpha_m * (1.0 - self.m) - beta_m * self.m) * self.dt;
219 self.h += (alpha_h * (1.0 - self.h) - beta_h * self.h) * self.dt;
220 self.n += (alpha_n * (1.0 - self.n) - beta_n * self.n) * self.dt;
221
222 let p_inf = 1.0 / (1.0 + (-(self.v + 35.0) / 10.0).exp());
224 let tau_p =
225 400.0 / (3.3 * ((self.v + 35.0) / 20.0).exp() + (-(self.v + 35.0) / 20.0).exp());
226 self.p += (p_inf - self.p) / tau_p * self.dt;
227
228 let m_t_inf = 1.0 / (1.0 + (-(self.v + 57.0) / 6.2).exp());
230 let s_inf = 1.0 / (1.0 + ((self.v + 81.0) / 4.0).exp());
231 let tau_s = 30.0 + 200.0 / (1.0 + ((self.v + 70.0) / 5.0).exp());
232 self.s += (s_inf - self.s) / tau_s * self.dt;
233
234 let r_inf = 1.0 / (1.0 + ((self.v + 80.0) / 10.0).exp());
236 let tau_r =
237 100.0 + 500.0 / ((-(self.v + 70.0) / 20.0).exp() + ((self.v + 70.0) / 20.0).exp());
238 self.r += (r_inf - self.r) / tau_r * self.dt;
239
240 let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
241 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
242 let i_m = self.g_m * self.p * (self.v - self.e_k);
243 let i_t = self.g_t * m_t_inf.powi(2) * self.s * (self.v - self.e_ca);
244 let i_h = self.g_h * self.r * (self.v - self.e_h);
245 let i_l = self.g_l * (self.v - self.e_l);
246
247 self.v += (-i_na - i_k - i_m - i_t - i_h - i_l + current) / self.c_m * self.dt;
248 }
249 if self.v >= self.v_threshold && v_prev < self.v_threshold {
250 1
251 } else {
252 0
253 }
254 }
255
256 pub fn reset(&mut self) {
257 self.v = -65.0;
258 self.m = 0.02;
259 self.h = 0.8;
260 self.n = 0.2;
261 self.p = 0.0;
262 self.s = 0.9;
263 self.r = 0.1;
264 }
265}
266
267impl Default for SSTNeuron {
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273#[derive(Clone, Debug)]
286pub struct VIPNeuron {
287 pub v: f64,
288 pub h: f64,
289 pub n: f64,
290 pub a: f64, pub b: f64, pub g_na: f64,
294 pub g_k: f64,
295 pub g_a: f64,
296 pub g_l: f64,
297 pub e_na: f64,
299 pub e_k: f64,
300 pub e_l: f64,
301 pub c_m: f64,
302 pub dt: f64,
303 pub v_threshold: f64,
304}
305
306impl VIPNeuron {
307 pub fn new() -> Self {
308 Self {
309 v: -65.0,
310 h: 0.8,
311 n: 0.1,
312 a: 0.0,
313 b: 0.9,
314 g_na: 35.0, g_k: 6.0,
316 g_a: 8.0, g_l: 0.01, e_na: 55.0,
319 e_k: -90.0,
320 e_l: -65.0,
321 c_m: 0.5, dt: 0.025,
323 v_threshold: -20.0,
324 }
325 }
326
327 pub fn step(&mut self, current: f64) -> i32 {
328 let v_prev = self.v;
329 for _ in 0..4 {
330 let m_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 9.5).exp());
332 let h_inf = 1.0 / (1.0 + ((self.v + 53.0) / 7.0).exp());
333 let tau_h = 0.37 + 2.78 / (1.0 + ((self.v + 40.5) / 6.0).exp());
334 self.h += (h_inf - self.h) / tau_h * self.dt;
335
336 let n_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 10.0).exp());
338 let tau_n = 0.37 + 1.85 / (1.0 + ((self.v + 27.0) / 15.0).exp());
339 self.n += (n_inf - self.n) / tau_n * self.dt;
340
341 let a_inf = 1.0 / (1.0 + (-(self.v + 50.0) / 20.0).exp());
343 let b_inf = 1.0 / (1.0 + ((self.v + 78.0) / 6.0).exp());
344 let tau_a = 5.0;
345 let tau_b = 50.0;
346 self.a += (a_inf - self.a) / tau_a * self.dt;
347 self.b += (b_inf - self.b) / tau_b * self.dt;
348
349 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
350 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
351 let i_a = self.g_a * self.a.powi(3) * self.b * (self.v - self.e_k);
352 let i_l = self.g_l * (self.v - self.e_l);
353
354 self.v += (-i_na - i_k - i_a - i_l + current) / self.c_m * self.dt;
355 }
356 if self.v >= self.v_threshold && v_prev < self.v_threshold {
357 1
358 } else {
359 0
360 }
361 }
362
363 pub fn reset(&mut self) {
364 self.v = -65.0;
365 self.h = 0.8;
366 self.n = 0.1;
367 self.a = 0.0;
368 self.b = 0.9;
369 }
370}
371
372impl Default for VIPNeuron {
373 fn default() -> Self {
374 Self::new()
375 }
376}
377
378#[derive(Clone, Debug)]
389pub struct ChandelierNeuron {
390 pub v: f64,
391 pub h: f64,
392 pub n: f64,
393 pub d: f64, pub p: f64, pub g_na: f64,
397 pub g_k: f64,
398 pub g_kv1: f64,
399 pub g_kv3: f64,
400 pub g_l: f64,
401 pub e_na: f64,
403 pub e_k: f64,
404 pub e_l: f64,
405 pub c_m: f64,
406 pub phi: f64,
407 pub dt: f64,
408 pub v_threshold: f64,
409}
410
411impl ChandelierNeuron {
412 pub fn new() -> Self {
413 Self {
414 v: -65.0,
415 h: 0.8,
416 n: 0.1,
417 d: 0.0,
418 p: 0.0,
419 g_na: 35.0,
420 g_k: 9.0,
421 g_kv1: 3.0, g_kv3: 4.0, g_l: 0.1,
424 e_na: 55.0,
425 e_k: -90.0,
426 e_l: -65.0,
427 c_m: 1.0,
428 phi: 5.0,
429 dt: 0.01,
430 v_threshold: -20.0,
431 }
432 }
433
434 pub fn step(&mut self, current: f64) -> i32 {
435 let v_prev = self.v;
436 let n_sub = (0.5 / self.dt.max(0.001)) as usize;
437 for _ in 0..n_sub {
438 let am = safe_rate(0.1, 35.0, self.v, 10.0, 1.0);
440 let bm = 4.0 * (-(self.v + 60.0) / 18.0).exp();
441 let m_inf = am / (am + bm);
442 let ah = 0.07 * (-(self.v + 58.0) / 20.0).exp();
443 let bh = 1.0 / (1.0 + (-(self.v + 28.0) / 10.0).exp());
444 let an = safe_rate(0.01, 34.0, self.v, 10.0, 0.1);
445 let bn = 0.125 * (-(self.v + 44.0) / 80.0).exp();
446
447 self.h += self.phi * (ah * (1.0 - self.h) - bh * self.h) * self.dt;
448 self.n += self.phi * (an * (1.0 - self.n) - bn * self.n) * self.dt;
449
450 let d_inf = 1.0 / (1.0 + (-(self.v + 50.0) / 10.0).exp());
452 let tau_d = 150.0;
453 self.d += (d_inf - self.d) / tau_d * self.dt;
454
455 let p_inf = 1.0 / (1.0 + (-(self.v + 10.0) / 10.0).exp());
457 self.p += self.phi * (p_inf - self.p) / 1.0 * self.dt;
458
459 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
460 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
461 let i_kv1 = self.g_kv1 * self.d.powi(4) * (self.v - self.e_k);
462 let i_kv3 = self.g_kv3 * self.p * (self.v - self.e_k);
463 let i_l = self.g_l * (self.v - self.e_l);
464
465 self.v += (-i_na - i_k - i_kv1 - i_kv3 - i_l + current) / self.c_m * self.dt;
466 }
467 if self.v >= self.v_threshold && v_prev < self.v_threshold {
468 1
469 } else {
470 0
471 }
472 }
473
474 pub fn reset(&mut self) {
475 self.v = -65.0;
476 self.h = 0.8;
477 self.n = 0.1;
478 self.d = 0.0;
479 self.p = 0.0;
480 }
481}
482
483impl Default for ChandelierNeuron {
484 fn default() -> Self {
485 Self::new()
486 }
487}
488
489#[derive(Clone, Debug)]
501pub struct CerebellarBasketNeuron {
502 pub v: f64,
503 pub h: f64,
504 pub n: f64,
505 pub a: f64,
506 pub b: f64,
507 pub ca: f64, pub g_na: f64,
510 pub g_k: f64,
511 pub g_a: f64,
512 pub g_kca: f64,
513 pub g_l: f64,
514 pub e_na: f64,
516 pub e_k: f64,
517 pub e_l: f64,
518 pub c_m: f64,
519 pub phi: f64,
520 pub dt: f64,
521 pub v_threshold: f64,
522}
523
524impl CerebellarBasketNeuron {
525 pub fn new() -> Self {
526 Self {
527 v: -65.0,
528 h: 0.8,
529 n: 0.1,
530 a: 0.0,
531 b: 0.9,
532 ca: 0.05,
533 g_na: 35.0,
534 g_k: 9.0,
535 g_a: 3.0,
536 g_kca: 2.0,
537 g_l: 0.1,
538 e_na: 55.0,
539 e_k: -90.0,
540 e_l: -65.0,
541 c_m: 1.0,
542 phi: 5.0,
543 dt: 0.01,
544 v_threshold: -20.0,
545 }
546 }
547
548 pub fn step(&mut self, current: f64) -> i32 {
549 let v_prev = self.v;
550 let n_sub = (0.5 / self.dt.max(0.001)) as usize;
551 for _ in 0..n_sub {
552 let am = safe_rate(0.1, 35.0, self.v, 10.0, 1.0);
554 let bm = 4.0 * (-(self.v + 60.0) / 18.0).exp();
555 let m_inf = am / (am + bm);
556 let ah = 0.07 * (-(self.v + 58.0) / 20.0).exp();
557 let bh = 1.0 / (1.0 + (-(self.v + 28.0) / 10.0).exp());
558 let an = safe_rate(0.01, 34.0, self.v, 10.0, 0.1);
559 let bn = 0.125 * (-(self.v + 44.0) / 80.0).exp();
560
561 self.h += self.phi * (ah * (1.0 - self.h) - bh * self.h) * self.dt;
562 self.n += self.phi * (an * (1.0 - self.n) - bn * self.n) * self.dt;
563
564 let a_inf = 1.0 / (1.0 + (-(self.v + 45.0) / 15.0).exp());
566 let b_inf = 1.0 / (1.0 + ((self.v + 75.0) / 8.0).exp());
567 self.a += self.phi * (a_inf - self.a) / 5.0 * self.dt;
568 self.b += (b_inf - self.b) / 50.0 * self.dt;
569
570 let q_inf = self.ca / (self.ca + 0.2);
572
573 let i_ca_entry = if self.v > -20.0 {
575 0.01 * (self.v + 20.0)
576 } else {
577 0.0
578 };
579 self.ca += (-self.ca / 80.0 + i_ca_entry) * self.dt;
580 if self.ca < 0.0 {
581 self.ca = 0.0;
582 }
583
584 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
585 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
586 let i_a = self.g_a * self.a.powi(3) * self.b * (self.v - self.e_k);
587 let i_kca = self.g_kca * q_inf * (self.v - self.e_k);
588 let i_l = self.g_l * (self.v - self.e_l);
589
590 self.v += (-i_na - i_k - i_a - i_kca - i_l + current) / self.c_m * self.dt;
591 }
592 if self.v >= self.v_threshold && v_prev < self.v_threshold {
593 1
594 } else {
595 0
596 }
597 }
598
599 pub fn reset(&mut self) {
600 self.v = -65.0;
601 self.h = 0.8;
602 self.n = 0.1;
603 self.a = 0.0;
604 self.b = 0.9;
605 self.ca = 0.05;
606 }
607}
608
609impl Default for CerebellarBasketNeuron {
610 fn default() -> Self {
611 Self::new()
612 }
613}
614
615#[derive(Clone, Debug)]
627pub struct MartinottiNeuron {
628 pub v: f64,
629 pub m: f64,
630 pub h: f64,
631 pub n: f64,
632 pub p: f64, pub s: f64, pub g_na: f64,
636 pub g_k: f64,
637 pub g_m: f64,
638 pub g_t: f64,
639 pub g_l: f64,
640 pub e_na: f64,
642 pub e_k: f64,
643 pub e_ca: f64,
644 pub e_l: f64,
645 pub c_m: f64,
646 pub dt: f64,
647 pub v_threshold: f64,
648}
649
650impl MartinottiNeuron {
651 pub fn new() -> Self {
652 Self {
653 v: -65.0,
654 m: 0.02,
655 h: 0.8,
656 n: 0.2,
657 p: 0.0,
658 s: 0.9,
659 g_na: 40.0,
660 g_k: 5.0,
661 g_m: 0.25, g_t: 0.01, g_l: 0.05, e_na: 50.0,
665 e_k: -90.0,
666 e_ca: 120.0,
667 e_l: -65.0,
668 c_m: 0.8,
669 dt: 0.025,
670 v_threshold: -20.0,
671 }
672 }
673
674 pub fn step(&mut self, current: f64) -> i32 {
675 let v_prev = self.v;
676 let vt = -56.2;
677 for _ in 0..4 {
678 let dv = self.v - vt;
679 let x_m = dv - 13.0;
681 let alpha_m = if x_m.abs() < 1e-6 {
682 0.32 * 4.0
683 } else {
684 -0.32 * x_m / ((-x_m / 4.0).exp() - 1.0)
685 };
686 let x_h = dv - 17.0;
687 let beta_m = if x_h.abs() < 1e-6 {
688 0.28 * 5.0
689 } else {
690 0.28 * x_h / ((x_h / 5.0).exp() - 1.0)
691 };
692 let alpha_h = 0.128 * (-(dv - 17.0) / 18.0).exp();
693 let beta_h = 4.0 / (1.0 + (-(dv - 40.0) / 5.0).exp());
694 let x_n = dv - 15.0;
696 let alpha_n = if x_n.abs() < 1e-6 {
697 0.032 * 5.0
698 } else {
699 -0.032 * x_n / ((-x_n / 5.0).exp() - 1.0)
700 };
701 let beta_n = 0.5 * (-(dv - 10.0) / 40.0).exp();
702
703 self.m += (alpha_m * (1.0 - self.m) - beta_m * self.m) * self.dt;
704 self.h += (alpha_h * (1.0 - self.h) - beta_h * self.h) * self.dt;
705 self.n += (alpha_n * (1.0 - self.n) - beta_n * self.n) * self.dt;
706
707 let p_inf = 1.0 / (1.0 + (-(self.v + 35.0) / 10.0).exp());
709 let tau_p =
710 400.0 / (3.3 * ((self.v + 35.0) / 20.0).exp() + (-(self.v + 35.0) / 20.0).exp());
711 self.p += (p_inf - self.p) / tau_p * self.dt;
712
713 let m_t_inf = 1.0 / (1.0 + (-(self.v + 57.0) / 6.2).exp());
715 let s_inf = 1.0 / (1.0 + ((self.v + 81.0) / 4.0).exp());
716 let tau_s = 30.0 + 200.0 / (1.0 + ((self.v + 70.0) / 5.0).exp());
717 self.s += (s_inf - self.s) / tau_s * self.dt;
718
719 let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
720 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
721 let i_m = self.g_m * self.p * (self.v - self.e_k);
722 let i_t = self.g_t * m_t_inf.powi(2) * self.s * (self.v - self.e_ca);
723 let i_l = self.g_l * (self.v - self.e_l);
724
725 self.v += (-i_na - i_k - i_m - i_t - i_l + current) / self.c_m * self.dt;
726 }
727 if self.v >= self.v_threshold && v_prev < self.v_threshold {
728 1
729 } else {
730 0
731 }
732 }
733
734 pub fn reset(&mut self) {
735 self.v = -65.0;
736 self.m = 0.02;
737 self.h = 0.8;
738 self.n = 0.2;
739 self.p = 0.0;
740 self.s = 0.9;
741 }
742}
743
744impl Default for MartinottiNeuron {
745 fn default() -> Self {
746 Self::new()
747 }
748}
749
750#[cfg(test)]
755mod tests {
756 use super::*;
757
758 #[test]
761 fn pv_fires_with_input() {
762 let mut n = PVFastSpikingNeuron::new();
763 let spikes: i32 = (0..5000).map(|_| n.step(2.0)).sum();
764 assert!(spikes > 0, "PV+ must fire with sustained input");
765 }
766
767 #[test]
768 fn pv_no_fire_without_input() {
769 let mut n = PVFastSpikingNeuron::new();
770 let spikes: i32 = (0..2000).map(|_| n.step(0.0)).sum();
771 assert_eq!(spikes, 0);
772 }
773
774 #[test]
775 fn pv_negative_current_no_fire() {
776 let mut n = PVFastSpikingNeuron::new();
777 let spikes: i32 = (0..1000).map(|_| n.step(-1.0)).sum();
778 assert_eq!(spikes, 0);
779 }
780
781 #[test]
782 fn pv_high_firing_rate() {
783 let mut n = PVFastSpikingNeuron::new();
785 let spikes: i32 = (0..5000).map(|_| n.step(5.0)).sum();
786 assert!(spikes > 100, "PV+ should fire at high rate: got {spikes}");
787 }
788
789 #[test]
790 fn pv_reset_roundtrip() {
791 let mut n = PVFastSpikingNeuron::new();
792 for _ in 0..1000 {
793 n.step(3.0);
794 }
795 n.reset();
796 let mut fresh = PVFastSpikingNeuron::new();
797 let r1: i32 = (0..500).map(|_| n.step(3.0)).sum();
798 let r2: i32 = (0..500).map(|_| fresh.step(3.0)).sum();
799 assert_eq!(r1, r2);
800 }
801
802 #[test]
803 fn pv_voltage_bounded() {
804 let mut n = PVFastSpikingNeuron::new();
805 for _ in 0..5000 {
806 n.step(5.0);
807 }
808 assert!(n.v.is_finite());
809 assert!(n.h.is_finite());
810 assert!(n.n.is_finite());
811 }
812
813 #[test]
814 fn pv_performance_5k_steps() {
815 let mut n = PVFastSpikingNeuron::new();
816 let start = std::time::Instant::now();
817 for _ in 0..5_000 {
818 n.step(3.0);
819 }
820 assert!(
821 start.elapsed().as_millis() < 500,
822 "5k steps took {:?}",
823 start.elapsed()
824 );
825 }
826
827 #[test]
830 fn sst_fires_with_input() {
831 let mut n = SSTNeuron::new();
832 let spikes: i32 = (0..10000).map(|_| n.step(5.0)).sum();
833 assert!(spikes > 0, "SST+ must fire with sustained input");
834 }
835
836 #[test]
837 fn sst_no_fire_without_input() {
838 let mut n = SSTNeuron::new();
839 let spikes: i32 = (0..5000).map(|_| n.step(0.0)).sum();
840 assert_eq!(spikes, 0);
841 }
842
843 #[test]
844 fn sst_adaptation_reduces_rate() {
845 let mut n = SSTNeuron::new();
846 let first_half: i32 = (0..5000).map(|_| n.step(5.0)).sum();
847 let second_half: i32 = (0..5000).map(|_| n.step(5.0)).sum();
848 assert!(
850 second_half <= first_half + 3,
851 "SST+ should adapt: first={first_half}, second={second_half}"
852 );
853 }
854
855 #[test]
856 fn sst_reset_roundtrip() {
857 let mut n = SSTNeuron::new();
858 for _ in 0..5000 {
859 n.step(5.0);
860 }
861 n.reset();
862 let mut fresh = SSTNeuron::new();
863 let r1: i32 = (0..2000).map(|_| n.step(5.0)).sum();
864 let r2: i32 = (0..2000).map(|_| fresh.step(5.0)).sum();
865 assert_eq!(r1, r2);
866 }
867
868 #[test]
869 fn sst_voltage_bounded() {
870 let mut n = SSTNeuron::new();
871 for _ in 0..20000 {
872 n.step(10.0);
873 }
874 assert!(n.v.is_finite());
875 assert!(n.p.is_finite());
876 assert!(n.s.is_finite());
877 }
878
879 #[test]
880 fn sst_performance_10k_steps() {
881 let mut n = SSTNeuron::new();
882 let start = std::time::Instant::now();
883 for _ in 0..10_000 {
884 n.step(5.0);
885 }
886 assert!(start.elapsed().as_millis() < 100);
887 }
888
889 #[test]
892 fn vip_fires_with_input() {
893 let mut n = VIPNeuron::new();
894 let spikes: i32 = (0..10000).map(|_| n.step(2.0)).sum();
895 assert!(spikes > 0, "VIP must fire with sustained input");
896 }
897
898 #[test]
899 fn vip_no_fire_without_input() {
900 let mut n = VIPNeuron::new();
901 let spikes: i32 = (0..5000).map(|_| n.step(0.0)).sum();
902 assert_eq!(spikes, 0);
903 }
904
905 #[test]
906 fn vip_accommodation() {
907 let mut n = VIPNeuron::new();
910 let onset: i32 = (0..500).map(|_| n.step(3.0)).sum();
912 for _ in 0..5000 {
914 n.step(3.0);
915 }
916 let steady: i32 = (0..500).map(|_| n.step(3.0)).sum();
918 assert!(
920 steady >= onset,
921 "VIP steady-state ({steady}) should fire >= onset ({onset})"
922 );
923 }
924
925 #[test]
926 fn vip_reset_roundtrip() {
927 let mut n = VIPNeuron::new();
928 for _ in 0..5000 {
929 n.step(3.0);
930 }
931 n.reset();
932 let mut fresh = VIPNeuron::new();
933 let r1: i32 = (0..2000).map(|_| n.step(3.0)).sum();
934 let r2: i32 = (0..2000).map(|_| fresh.step(3.0)).sum();
935 assert_eq!(r1, r2);
936 }
937
938 #[test]
939 fn vip_voltage_bounded() {
940 let mut n = VIPNeuron::new();
941 for _ in 0..20000 {
942 n.step(5.0);
943 }
944 assert!(n.v.is_finite());
945 }
946
947 #[test]
948 fn vip_performance_10k_steps() {
949 let mut n = VIPNeuron::new();
950 let start = std::time::Instant::now();
951 for _ in 0..10_000 {
952 n.step(3.0);
953 }
954 assert!(start.elapsed().as_millis() < 100);
955 }
956
957 #[test]
960 fn chandelier_fires_with_input() {
961 let mut n = ChandelierNeuron::new();
962 let spikes: i32 = (0..5000).map(|_| n.step(3.0)).sum();
963 assert!(spikes > 0, "Chandelier must fire with sustained input");
964 }
965
966 #[test]
967 fn chandelier_no_fire_without_input() {
968 let mut n = ChandelierNeuron::new();
969 let spikes: i32 = (0..2000).map(|_| n.step(0.0)).sum();
970 assert_eq!(spikes, 0);
971 }
972
973 #[test]
974 fn chandelier_has_kv1_delay_current() {
975 let mut ch = ChandelierNeuron::new();
978 let mut pv = PVFastSpikingNeuron::new();
979 let ch_spikes: i32 = (0..5000).map(|_| ch.step(3.0)).sum();
980 let pv_spikes: i32 = (0..5000).map(|_| pv.step(3.0)).sum();
981 assert!(ch_spikes > 0, "Chandelier must fire");
983 assert!(pv_spikes > 0, "PV+ must fire");
984 assert!(
985 ch_spikes <= pv_spikes + 10,
986 "Chandelier ({ch_spikes}) should fire <= PV+ ({pv_spikes}) due to Kv1"
987 );
988 }
989
990 #[test]
991 fn chandelier_reset_roundtrip() {
992 let mut n = ChandelierNeuron::new();
993 for _ in 0..1000 {
994 n.step(3.0);
995 }
996 n.reset();
997 let mut fresh = ChandelierNeuron::new();
998 let r1: i32 = (0..500).map(|_| n.step(3.0)).sum();
999 let r2: i32 = (0..500).map(|_| fresh.step(3.0)).sum();
1000 assert_eq!(r1, r2);
1001 }
1002
1003 #[test]
1004 fn chandelier_voltage_bounded() {
1005 let mut n = ChandelierNeuron::new();
1006 for _ in 0..5000 {
1007 n.step(5.0);
1008 }
1009 assert!(n.v.is_finite());
1010 }
1011
1012 #[test]
1013 fn chandelier_performance_5k_steps() {
1014 let mut n = ChandelierNeuron::new();
1015 let start = std::time::Instant::now();
1016 for _ in 0..5_000 {
1017 n.step(3.0);
1018 }
1019 assert!(
1020 start.elapsed().as_millis() < 500,
1021 "5k steps took {:?}",
1022 start.elapsed()
1023 );
1024 }
1025
1026 #[test]
1029 fn basket_fires_with_input() {
1030 let mut n = CerebellarBasketNeuron::new();
1031 let spikes: i32 = (0..5000).map(|_| n.step(3.0)).sum();
1032 assert!(spikes > 0, "Basket cell must fire with sustained input");
1033 }
1034
1035 #[test]
1036 fn basket_no_fire_without_input() {
1037 let mut n = CerebellarBasketNeuron::new();
1038 let spikes: i32 = (0..2000).map(|_| n.step(0.0)).sum();
1039 assert_eq!(spikes, 0);
1040 }
1041
1042 #[test]
1043 fn basket_ca_dynamics_during_spiking() {
1044 let mut n = CerebellarBasketNeuron::new();
1046 for _ in 0..5000 {
1048 n.step(3.0);
1049 }
1050 let ca_spiking = n.ca;
1051 let mut n2 = CerebellarBasketNeuron::new();
1053 n2.ca = ca_spiking;
1054 for _ in 0..5000 {
1055 n2.step(0.0);
1056 }
1057 assert!(
1058 ca_spiking > n2.ca,
1059 "spiking Ca ({ca_spiking:.4}) should exceed resting Ca ({:.4})",
1060 n2.ca
1061 );
1062 }
1063
1064 #[test]
1065 fn basket_reset_roundtrip() {
1066 let mut n = CerebellarBasketNeuron::new();
1067 for _ in 0..2000 {
1068 n.step(3.0);
1069 }
1070 n.reset();
1071 assert_eq!(n.ca, 0.05);
1072 let mut fresh = CerebellarBasketNeuron::new();
1073 let r1: i32 = (0..1000).map(|_| n.step(3.0)).sum();
1074 let r2: i32 = (0..1000).map(|_| fresh.step(3.0)).sum();
1075 assert_eq!(r1, r2);
1076 }
1077
1078 #[test]
1079 fn basket_voltage_bounded() {
1080 let mut n = CerebellarBasketNeuron::new();
1081 for _ in 0..5000 {
1082 n.step(5.0);
1083 }
1084 assert!(n.v.is_finite());
1085 assert!(n.ca.is_finite());
1086 assert!(n.ca >= 0.0);
1087 }
1088
1089 #[test]
1090 fn basket_performance_5k_steps() {
1091 let mut n = CerebellarBasketNeuron::new();
1092 let start = std::time::Instant::now();
1093 for _ in 0..5_000 {
1094 n.step(3.0);
1095 }
1096 assert!(
1097 start.elapsed().as_millis() < 500,
1098 "5k steps took {:?}",
1099 start.elapsed()
1100 );
1101 }
1102
1103 #[test]
1106 fn martinotti_fires_with_input() {
1107 let mut n = MartinottiNeuron::new();
1108 let spikes: i32 = (0..10000).map(|_| n.step(3.0)).sum();
1109 assert!(spikes > 0, "Martinotti must fire with sustained input");
1110 }
1111
1112 #[test]
1113 fn martinotti_no_fire_without_input() {
1114 let mut n = MartinottiNeuron::new();
1115 let spikes: i32 = (0..5000).map(|_| n.step(0.0)).sum();
1116 assert_eq!(spikes, 0);
1117 }
1118
1119 #[test]
1120 fn martinotti_strong_adaptation() {
1121 let mut n = MartinottiNeuron::new();
1122 let first: i32 = (0..5000).map(|_| n.step(4.0)).sum();
1123 let second: i32 = (0..5000).map(|_| n.step(4.0)).sum();
1124 assert!(
1125 second <= first + 3,
1126 "Martinotti should strongly adapt: first={first}, second={second}"
1127 );
1128 }
1129
1130 #[test]
1131 fn martinotti_adapts_more_than_sst() {
1132 let mut mc = MartinottiNeuron::new();
1134 let mut sst = SSTNeuron::new();
1135 let mc_spikes: i32 = (0..10000).map(|_| mc.step(4.0)).sum();
1137 let sst_spikes: i32 = (0..10000).map(|_| sst.step(4.0)).sum();
1138 assert!(mc_spikes > 0, "Martinotti should fire: got {mc_spikes}");
1141 assert!(sst_spikes > 0, "SST should fire: got {sst_spikes}");
1142 }
1143
1144 #[test]
1145 fn martinotti_reset_roundtrip() {
1146 let mut n = MartinottiNeuron::new();
1147 for _ in 0..5000 {
1148 n.step(4.0);
1149 }
1150 n.reset();
1151 let mut fresh = MartinottiNeuron::new();
1152 let r1: i32 = (0..2000).map(|_| n.step(4.0)).sum();
1153 let r2: i32 = (0..2000).map(|_| fresh.step(4.0)).sum();
1154 assert_eq!(r1, r2);
1155 }
1156
1157 #[test]
1158 fn martinotti_voltage_bounded() {
1159 let mut n = MartinottiNeuron::new();
1160 for _ in 0..20000 {
1161 n.step(10.0);
1162 }
1163 assert!(n.v.is_finite());
1164 assert!(n.p.is_finite());
1165 }
1166
1167 #[test]
1168 fn martinotti_performance_10k_steps() {
1169 let mut n = MartinottiNeuron::new();
1170 let start = std::time::Instant::now();
1171 for _ in 0..10_000 {
1172 n.step(4.0);
1173 }
1174 assert!(start.elapsed().as_millis() < 100);
1175 }
1176}