1use super::biophysical::safe_rate;
16
17#[derive(Clone, Debug)]
32pub struct PersistentNaNeuron {
33 pub v: f64,
34 pub h: f64, pub n: f64, pub p: f64, pub g_na: f64, pub g_nap: f64, pub g_k: f64, pub g_l: f64,
42 pub e_na: f64,
44 pub e_k: f64,
45 pub e_l: f64,
46 pub c_m: f64,
47 pub phi: f64,
48 pub dt: f64,
49 pub v_threshold: f64,
50 pub gain: f64,
51}
52
53impl Default for PersistentNaNeuron {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl PersistentNaNeuron {
60 pub fn new() -> Self {
61 Self {
62 v: -65.0,
63 h: 0.6,
64 n: 0.32,
65 p: 0.0,
66 g_na: 35.0,
67 g_nap: 0.15, g_k: 9.0,
69 g_l: 0.3, e_na: 55.0,
71 e_k: -90.0,
72 e_l: -65.0,
73 c_m: 1.0,
74 phi: 5.0,
75 dt: 0.5,
76 v_threshold: -20.0,
77 gain: 1.0,
78 }
79 }
80
81 pub fn step(&mut self, current: f64) -> i32 {
82 let input = self.gain * current;
83 let sub_steps = 50;
84 let sub_dt = self.dt / sub_steps as f64;
85 let mut fired = 0i32;
86
87 for _ in 0..sub_steps {
88 let v = self.v;
89
90 let alpha_m = safe_rate(0.1, 35.0, v, 10.0, 1.0);
92 let beta_m = 4.0 * (-(v + 60.0) / 18.0).exp();
93 let m_inf = alpha_m / (alpha_m + beta_m);
94
95 let alpha_h = 0.07 * (-(v + 58.0) / 20.0).exp();
96 let beta_h = 1.0 / (1.0 + (-(v + 28.0) / 10.0).exp());
97
98 let alpha_n = safe_rate(0.01, 34.0, v, 10.0, 0.1);
99 let beta_n = 0.125 * (-(v + 44.0) / 80.0).exp();
100
101 let p_inf = 1.0 / (1.0 + (-(v + 48.0) / 5.0).exp());
104 let tau_p = 10.0 + 40.0 / (1.0 + ((v + 48.0) / 10.0).powi(2));
105
106 self.h += sub_dt * self.phi * (alpha_h * (1.0 - self.h) - beta_h * self.h);
108 self.n += sub_dt * self.phi * (alpha_n * (1.0 - self.n) - beta_n * self.n);
109 self.p += sub_dt * (p_inf - self.p) / tau_p;
110
111 let i_na = self.g_na * m_inf.powi(3) * self.h * (v - self.e_na);
113 let i_nap = self.g_nap * self.p * (v - self.e_na);
114 let i_k = self.g_k * self.n.powi(4) * (v - self.e_k);
115 let i_l = self.g_l * (v - self.e_l);
116
117 let dv = (-i_na - i_nap - i_k - i_l + input) / self.c_m;
118 self.v += sub_dt * dv;
119
120 if self.v >= self.v_threshold {
121 fired = 1;
122 self.v = -65.0;
123 }
124 }
125
126 self.v = self.v.clamp(-100.0, 60.0);
128 if !self.v.is_finite() {
129 self.v = -65.0;
130 self.h = 0.6;
131 self.n = 0.32;
132 }
133 self.h = self.h.clamp(0.0, 1.0);
134 self.n = self.n.clamp(0.0, 1.0);
135 self.p = self.p.clamp(0.0, 1.0);
136
137 fired
138 }
139
140 pub fn reset(&mut self) {
141 *self = Self::new();
142 }
143}
144
145#[derive(Clone, Debug)]
163pub struct IhNeuron {
164 pub v: f64,
165 pub h: f64, pub n: f64, pub r: f64, pub g_na: f64,
170 pub g_k: f64,
171 pub g_h: f64, pub g_l: f64,
173 pub e_na: f64,
175 pub e_k: f64,
176 pub e_h: f64, pub e_l: f64,
178 pub c_m: f64,
179 pub phi: f64,
180 pub dt: f64,
181 pub v_threshold: f64,
182 pub gain: f64,
183}
184
185impl Default for IhNeuron {
186 fn default() -> Self {
187 Self::new()
188 }
189}
190
191impl IhNeuron {
192 pub fn new() -> Self {
193 Self {
194 v: -65.0,
195 h: 0.6,
196 n: 0.32,
197 r: 0.1,
198 g_na: 35.0,
199 g_k: 9.0,
200 g_h: 0.15,
201 g_l: 0.2,
202 e_na: 55.0,
203 e_k: -90.0,
204 e_h: -40.0,
205 e_l: -65.0,
206 c_m: 1.0,
207 phi: 5.0,
208 dt: 0.5,
209 v_threshold: -20.0,
210 gain: 1.0,
211 }
212 }
213
214 pub fn step(&mut self, current: f64) -> i32 {
215 let input = self.gain * current;
216 let sub_steps = 50;
217 let sub_dt = self.dt / sub_steps as f64;
218 let mut fired = 0i32;
219
220 for _ in 0..sub_steps {
221 let v = self.v;
222
223 let alpha_m = safe_rate(0.1, 35.0, v, 10.0, 1.0);
225 let beta_m = 4.0 * (-(v + 60.0) / 18.0).exp();
226 let m_inf = alpha_m / (alpha_m + beta_m);
227
228 let alpha_h = 0.07 * (-(v + 58.0) / 20.0).exp();
229 let beta_h = 1.0 / (1.0 + (-(v + 28.0) / 10.0).exp());
230
231 let alpha_n = safe_rate(0.01, 34.0, v, 10.0, 0.1);
232 let beta_n = 0.125 * (-(v + 44.0) / 80.0).exp();
233
234 let r_inf = 1.0 / (1.0 + ((v + 80.0) / 10.0).exp());
237 let tau_r = 100.0 + 200.0 / (1.0 + ((v + 70.0) / 10.0).exp());
238
239 self.h += sub_dt * self.phi * (alpha_h * (1.0 - self.h) - beta_h * self.h);
241 self.n += sub_dt * self.phi * (alpha_n * (1.0 - self.n) - beta_n * self.n);
242 self.r += sub_dt * (r_inf - self.r) / tau_r;
243
244 let i_na = self.g_na * m_inf.powi(3) * self.h * (v - self.e_na);
246 let i_k = self.g_k * self.n.powi(4) * (v - self.e_k);
247 let i_h = self.g_h * self.r * (v - self.e_h);
248 let i_l = self.g_l * (v - self.e_l);
249
250 let dv = (-i_na - i_k - i_h - i_l + input) / self.c_m;
251 self.v += sub_dt * dv;
252
253 if self.v >= self.v_threshold {
254 fired = 1;
255 self.v = -65.0;
256 }
257 }
258
259 self.v = self.v.clamp(-100.0, 60.0);
261 if !self.v.is_finite() {
262 self.v = -65.0;
263 self.h = 0.6;
264 self.n = 0.32;
265 }
266 self.h = self.h.clamp(0.0, 1.0);
267 self.n = self.n.clamp(0.0, 1.0);
268 self.r = self.r.clamp(0.0, 1.0);
269
270 fired
271 }
272
273 pub fn reset(&mut self) {
274 *self = Self::new();
275 }
276}
277
278#[derive(Clone, Debug)]
297pub struct TTypeCaNeuron {
298 pub v: f64,
299 pub h: f64, pub n: f64, pub s: f64, pub g_na: f64,
304 pub g_k: f64,
305 pub g_t: f64, pub g_l: f64,
307 pub e_na: f64,
309 pub e_k: f64,
310 pub e_ca: f64,
311 pub e_l: f64,
312 pub c_m: f64,
313 pub phi: f64,
314 pub dt: f64,
315 pub v_threshold: f64,
316 pub gain: f64,
317}
318
319impl Default for TTypeCaNeuron {
320 fn default() -> Self {
321 Self::new()
322 }
323}
324
325impl TTypeCaNeuron {
326 pub fn new() -> Self {
327 Self {
328 v: -65.0,
329 h: 0.6,
330 n: 0.32,
331 s: 0.9, g_na: 35.0,
333 g_k: 9.0,
334 g_t: 0.1, g_l: 0.2,
336 e_na: 55.0,
337 e_k: -90.0,
338 e_ca: 120.0,
339 e_l: -65.0,
340 c_m: 1.0,
341 phi: 5.0,
342 dt: 0.5,
343 v_threshold: -20.0,
344 gain: 1.0,
345 }
346 }
347
348 pub fn step(&mut self, current: f64) -> i32 {
349 let input = self.gain * current;
350 let sub_steps = 50;
351 let sub_dt = self.dt / sub_steps as f64;
352 let mut fired = 0i32;
353
354 for _ in 0..sub_steps {
355 let v = self.v;
356
357 let alpha_m = safe_rate(0.1, 35.0, v, 10.0, 1.0);
359 let beta_m = 4.0 * (-(v + 60.0) / 18.0).exp();
360 let m_inf = alpha_m / (alpha_m + beta_m);
361
362 let alpha_h = 0.07 * (-(v + 58.0) / 20.0).exp();
363 let beta_h = 1.0 / (1.0 + (-(v + 28.0) / 10.0).exp());
364
365 let alpha_n = safe_rate(0.01, 34.0, v, 10.0, 0.1);
366 let beta_n = 0.125 * (-(v + 44.0) / 80.0).exp();
367
368 let m_t_inf = 1.0 / (1.0 + (-(v + 52.0) / 5.0).exp());
370 let s_inf = 1.0 / (1.0 + ((v + 81.0) / 4.0).exp());
371 let tau_s = 30.0 + 100.0 / (1.0 + ((v + 75.0) / 10.0).exp());
372
373 self.h += sub_dt * self.phi * (alpha_h * (1.0 - self.h) - beta_h * self.h);
375 self.n += sub_dt * self.phi * (alpha_n * (1.0 - self.n) - beta_n * self.n);
376 self.s += sub_dt * (s_inf - self.s) / tau_s;
377
378 let i_na = self.g_na * m_inf.powi(3) * self.h * (v - self.e_na);
380 let i_k = self.g_k * self.n.powi(4) * (v - self.e_k);
381 let i_t = self.g_t * m_t_inf.powi(2) * self.s * (v - self.e_ca);
382 let i_l = self.g_l * (v - self.e_l);
383
384 let dv = (-i_na - i_k - i_t - i_l + input) / self.c_m;
385 self.v += sub_dt * dv;
386
387 if self.v >= self.v_threshold {
388 fired = 1;
389 self.v = -65.0;
390 self.s *= 0.3; }
392 }
393
394 self.v = self.v.clamp(-100.0, 60.0);
396 if !self.v.is_finite() {
397 self.v = -65.0;
398 self.h = 0.6;
399 self.n = 0.32;
400 }
401 self.h = self.h.clamp(0.0, 1.0);
402 self.n = self.n.clamp(0.0, 1.0);
403 self.s = self.s.clamp(0.0, 1.0);
404
405 fired
406 }
407
408 pub fn reset(&mut self) {
409 *self = Self::new();
410 }
411}
412
413#[derive(Clone, Debug)]
431pub struct ATypeKNeuron {
432 pub v: f64,
433 pub h: f64,
434 pub n: f64,
435 pub a: f64, pub b: f64, pub g_na: f64,
438 pub g_k: f64,
439 pub g_a: f64,
440 pub g_l: f64,
441 pub e_na: f64,
442 pub e_k: f64,
443 pub e_l: f64,
444 pub c_m: f64,
445 pub phi: f64,
446 pub dt: f64,
447 pub v_threshold: f64,
448 pub gain: f64,
449}
450
451impl Default for ATypeKNeuron {
452 fn default() -> Self {
453 Self::new()
454 }
455}
456
457impl ATypeKNeuron {
458 pub fn new() -> Self {
459 Self {
460 v: -65.0,
461 h: 0.6,
462 n: 0.32,
463 a: 0.1,
464 b: 0.8,
465 g_na: 35.0,
466 g_k: 9.0,
467 g_a: 8.0,
468 g_l: 0.1,
469 e_na: 55.0,
470 e_k: -90.0,
471 e_l: -65.0,
472 c_m: 1.0,
473 phi: 5.0,
474 dt: 0.5,
475 v_threshold: -20.0,
476 gain: 1.0,
477 }
478 }
479
480 pub fn step(&mut self, current: f64) -> i32 {
481 let input = self.gain * current;
482 let sub_steps = 50;
483 let sub_dt = self.dt / sub_steps as f64;
484 let mut fired = 0i32;
485
486 for _ in 0..sub_steps {
487 let v = self.v;
488
489 let alpha_m = safe_rate(0.1, 35.0, v, 10.0, 1.0);
490 let beta_m = 4.0 * (-(v + 60.0) / 18.0).exp();
491 let m_inf = alpha_m / (alpha_m + beta_m);
492
493 let alpha_h = 0.07 * (-(v + 58.0) / 20.0).exp();
494 let beta_h = 1.0 / (1.0 + (-(v + 28.0) / 10.0).exp());
495
496 let alpha_n = safe_rate(0.01, 34.0, v, 10.0, 0.1);
497 let beta_n = 0.125 * (-(v + 44.0) / 80.0).exp();
498
499 let a_inf = 1.0 / (1.0 + (-(v + 50.0) / 20.0).exp());
501 let tau_a = 2.0;
502 let b_inf = 1.0 / (1.0 + ((v + 70.0) / 6.0).exp());
503 let tau_b = 50.0;
504
505 self.h += sub_dt * self.phi * (alpha_h * (1.0 - self.h) - beta_h * self.h);
506 self.n += sub_dt * self.phi * (alpha_n * (1.0 - self.n) - beta_n * self.n);
507 self.a += sub_dt * (a_inf - self.a) / tau_a;
508 self.b += sub_dt * (b_inf - self.b) / tau_b;
509
510 let i_na = self.g_na * m_inf.powi(3) * self.h * (v - self.e_na);
511 let i_k = self.g_k * self.n.powi(4) * (v - self.e_k);
512 let i_a = self.g_a * self.a.powi(3) * self.b * (v - self.e_k);
513 let i_l = self.g_l * (v - self.e_l);
514
515 let dv = (-i_na - i_k - i_a - i_l + input) / self.c_m;
516 self.v += sub_dt * dv;
517
518 if self.v >= self.v_threshold {
519 fired = 1;
520 self.v = -65.0;
521 }
522 }
523
524 self.v = self.v.clamp(-100.0, 60.0);
525 if !self.v.is_finite() {
526 self.v = -65.0;
527 self.h = 0.6;
528 self.n = 0.32;
529 }
530 self.h = self.h.clamp(0.0, 1.0);
531 self.n = self.n.clamp(0.0, 1.0);
532 self.a = self.a.clamp(0.0, 1.0);
533 self.b = self.b.clamp(0.0, 1.0);
534
535 fired
536 }
537
538 pub fn reset(&mut self) {
539 *self = Self::new();
540 }
541}
542
543#[derive(Clone, Debug)]
563pub struct BKNeuron {
564 pub v: f64,
565 pub h: f64, pub n: f64, pub ca: f64, pub g_na: f64,
570 pub g_k: f64,
571 pub g_bk: f64, pub g_l: f64,
573 pub e_na: f64,
575 pub e_k: f64,
576 pub e_l: f64,
577 pub c_m: f64,
578 pub phi: f64,
579 pub tau_ca: f64, pub dt: f64,
581 pub v_threshold: f64,
582 pub gain: f64,
583}
584
585impl Default for BKNeuron {
586 fn default() -> Self {
587 Self::new()
588 }
589}
590
591impl BKNeuron {
592 pub fn new() -> Self {
593 Self {
594 v: -65.0,
595 h: 0.6,
596 n: 0.32,
597 ca: 0.0,
598 g_na: 35.0,
599 g_k: 9.0,
600 g_bk: 3.0,
601 g_l: 0.1,
602 e_na: 55.0,
603 e_k: -90.0,
604 e_l: -65.0,
605 c_m: 1.0,
606 phi: 5.0,
607 tau_ca: 50.0,
608 dt: 0.5,
609 v_threshold: -20.0,
610 gain: 1.0,
611 }
612 }
613
614 pub fn step(&mut self, current: f64) -> i32 {
615 let input = self.gain * current;
616 let sub_steps = 50;
617 let sub_dt = self.dt / sub_steps as f64;
618 let mut fired = 0i32;
619
620 for _ in 0..sub_steps {
621 let v = self.v;
622
623 let alpha_m = safe_rate(0.1, 35.0, v, 10.0, 1.0);
624 let beta_m = 4.0 * (-(v + 60.0) / 18.0).exp();
625 let m_inf = alpha_m / (alpha_m + beta_m);
626
627 let alpha_h = 0.07 * (-(v + 58.0) / 20.0).exp();
628 let beta_h = 1.0 / (1.0 + (-(v + 28.0) / 10.0).exp());
629
630 let alpha_n = safe_rate(0.01, 34.0, v, 10.0, 0.1);
631 let beta_n = 0.125 * (-(v + 44.0) / 80.0).exp();
632
633 let v_half_bk = 10.0 - 30.0 * (self.ca / (self.ca + 0.5));
637 let bk_inf = 1.0 / (1.0 + (-(v - v_half_bk) / 15.0).exp());
638
639 self.ca += sub_dt * (-self.ca / self.tau_ca);
641
642 self.h += sub_dt * self.phi * (alpha_h * (1.0 - self.h) - beta_h * self.h);
643 self.n += sub_dt * self.phi * (alpha_n * (1.0 - self.n) - beta_n * self.n);
644
645 let i_na = self.g_na * m_inf.powi(3) * self.h * (v - self.e_na);
646 let i_k = self.g_k * self.n.powi(4) * (v - self.e_k);
647 let i_bk = self.g_bk * bk_inf * (v - self.e_k);
648 let i_l = self.g_l * (v - self.e_l);
649
650 let dv = (-i_na - i_k - i_bk - i_l + input) / self.c_m;
651 self.v += sub_dt * dv;
652
653 if self.v >= self.v_threshold {
654 fired = 1;
655 self.v = -65.0;
656 self.ca += 0.3; }
658 }
659
660 self.v = self.v.clamp(-100.0, 60.0);
661 if !self.v.is_finite() {
662 self.v = -65.0;
663 self.h = 0.6;
664 self.n = 0.32;
665 }
666 if !self.ca.is_finite() {
667 self.ca = 0.0;
668 }
669 self.h = self.h.clamp(0.0, 1.0);
670 self.n = self.n.clamp(0.0, 1.0);
671 self.ca = self.ca.max(0.0);
672
673 fired
674 }
675
676 pub fn reset(&mut self) {
677 *self = Self::new();
678 }
679}
680
681#[derive(Clone, Debug)]
699pub struct SKNeuron {
700 pub v: f64,
701 pub h: f64,
702 pub n: f64,
703 pub ca: f64,
704 pub g_na: f64,
705 pub g_k: f64,
706 pub g_sk: f64,
707 pub g_l: f64,
708 pub e_na: f64,
709 pub e_k: f64,
710 pub e_l: f64,
711 pub c_m: f64,
712 pub phi: f64,
713 pub tau_ca: f64,
714 pub dt: f64,
715 pub v_threshold: f64,
716 pub gain: f64,
717}
718
719impl Default for SKNeuron {
720 fn default() -> Self {
721 Self::new()
722 }
723}
724
725impl SKNeuron {
726 pub fn new() -> Self {
727 Self {
728 v: -65.0,
729 h: 0.6,
730 n: 0.32,
731 ca: 0.0,
732 g_na: 35.0,
733 g_k: 9.0,
734 g_sk: 2.0,
735 g_l: 0.1,
736 e_na: 55.0,
737 e_k: -90.0,
738 e_l: -65.0,
739 c_m: 1.0,
740 phi: 5.0,
741 tau_ca: 150.0, dt: 0.5,
743 v_threshold: -20.0,
744 gain: 1.0,
745 }
746 }
747
748 pub fn step(&mut self, current: f64) -> i32 {
749 let input = self.gain * current;
750 let sub_steps = 50;
751 let sub_dt = self.dt / sub_steps as f64;
752 let mut fired = 0i32;
753
754 for _ in 0..sub_steps {
755 let v = self.v;
756
757 let alpha_m = safe_rate(0.1, 35.0, v, 10.0, 1.0);
758 let beta_m = 4.0 * (-(v + 60.0) / 18.0).exp();
759 let m_inf = alpha_m / (alpha_m + beta_m);
760
761 let alpha_h = 0.07 * (-(v + 58.0) / 20.0).exp();
762 let beta_h = 1.0 / (1.0 + (-(v + 28.0) / 10.0).exp());
763
764 let alpha_n = safe_rate(0.01, 34.0, v, 10.0, 0.1);
765 let beta_n = 0.125 * (-(v + 44.0) / 80.0).exp();
766
767 let ca2 = self.ca * self.ca;
769 let sk_inf = ca2 / (ca2 + 0.25); self.ca += sub_dt * (-self.ca / self.tau_ca);
773
774 self.h += sub_dt * self.phi * (alpha_h * (1.0 - self.h) - beta_h * self.h);
775 self.n += sub_dt * self.phi * (alpha_n * (1.0 - self.n) - beta_n * self.n);
776
777 let i_na = self.g_na * m_inf.powi(3) * self.h * (v - self.e_na);
778 let i_k = self.g_k * self.n.powi(4) * (v - self.e_k);
779 let i_sk = self.g_sk * sk_inf * (v - self.e_k);
780 let i_l = self.g_l * (v - self.e_l);
781
782 let dv = (-i_na - i_k - i_sk - i_l + input) / self.c_m;
783 self.v += sub_dt * dv;
784
785 if self.v >= self.v_threshold {
786 fired = 1;
787 self.v = -65.0;
788 self.ca += 0.2;
789 }
790 }
791
792 self.v = self.v.clamp(-100.0, 60.0);
793 if !self.v.is_finite() {
794 self.v = -65.0;
795 self.h = 0.6;
796 self.n = 0.32;
797 }
798 if !self.ca.is_finite() {
799 self.ca = 0.0;
800 }
801 self.h = self.h.clamp(0.0, 1.0);
802 self.n = self.n.clamp(0.0, 1.0);
803 self.ca = self.ca.max(0.0);
804
805 fired
806 }
807
808 pub fn reset(&mut self) {
809 *self = Self::new();
810 }
811}
812
813#[derive(Clone, Debug)]
834pub struct NMDANeuron {
835 pub v: f64,
836 pub h: f64,
837 pub n: f64,
838 pub s_nmda: f64, pub g_na: f64,
840 pub g_k: f64,
841 pub g_nmda: f64, pub g_l: f64,
843 pub e_na: f64,
844 pub e_k: f64,
845 pub e_nmda: f64, pub e_l: f64,
847 pub c_m: f64,
848 pub phi: f64,
849 pub mg_conc: f64, pub tau_rise: f64, pub tau_decay: f64, pub dt: f64,
853 pub v_threshold: f64,
854 pub gain: f64,
855}
856
857impl Default for NMDANeuron {
858 fn default() -> Self {
859 Self::new()
860 }
861}
862
863impl NMDANeuron {
864 pub fn new() -> Self {
865 Self {
866 v: -65.0,
867 h: 0.6,
868 n: 0.32,
869 s_nmda: 0.0,
870 g_na: 35.0,
871 g_k: 9.0,
872 g_nmda: 0.5,
873 g_l: 0.1,
874 e_na: 55.0,
875 e_k: -90.0,
876 e_nmda: 0.0, e_l: -65.0,
878 c_m: 1.0,
879 phi: 5.0,
880 mg_conc: 1.0,
881 tau_rise: 10.0,
882 tau_decay: 100.0,
883 dt: 0.5,
884 v_threshold: -20.0,
885 gain: 1.0,
886 }
887 }
888
889 pub fn step(&mut self, current: f64) -> i32 {
890 let input = self.gain * current;
891 let sub_steps = 50;
892 let sub_dt = self.dt / sub_steps as f64;
893 let mut fired = 0i32;
894
895 let drive = if input > 0.0 {
897 input / (input + 5.0)
898 } else {
899 0.0
900 };
901 let ds = (drive - self.s_nmda)
902 / if drive > self.s_nmda {
903 self.tau_rise
904 } else {
905 self.tau_decay
906 };
907 self.s_nmda += self.dt * ds;
908 self.s_nmda = self.s_nmda.clamp(0.0, 1.0);
909
910 for _ in 0..sub_steps {
911 let v = self.v;
912
913 let alpha_m = safe_rate(0.1, 35.0, v, 10.0, 1.0);
914 let beta_m = 4.0 * (-(v + 60.0) / 18.0).exp();
915 let m_inf = alpha_m / (alpha_m + beta_m);
916
917 let alpha_h = 0.07 * (-(v + 58.0) / 20.0).exp();
918 let beta_h = 1.0 / (1.0 + (-(v + 28.0) / 10.0).exp());
919
920 let alpha_n = safe_rate(0.01, 34.0, v, 10.0, 0.1);
921 let beta_n = 0.125 * (-(v + 44.0) / 80.0).exp();
922
923 let mg_block = 1.0 / (1.0 + (self.mg_conc / 3.57) * (-0.062 * v).exp());
926
927 self.h += sub_dt * self.phi * (alpha_h * (1.0 - self.h) - beta_h * self.h);
928 self.n += sub_dt * self.phi * (alpha_n * (1.0 - self.n) - beta_n * self.n);
929
930 let i_na = self.g_na * m_inf.powi(3) * self.h * (v - self.e_na);
931 let i_k = self.g_k * self.n.powi(4) * (v - self.e_k);
932 let i_nmda = self.g_nmda * self.s_nmda * mg_block * (v - self.e_nmda);
933 let i_l = self.g_l * (v - self.e_l);
934
935 let dv = (-i_na - i_k - i_nmda - i_l + input) / self.c_m;
936 self.v += sub_dt * dv;
937
938 if self.v >= self.v_threshold {
939 fired = 1;
940 self.v = -65.0;
941 }
942 }
943
944 self.v = self.v.clamp(-100.0, 60.0);
945 if !self.v.is_finite() {
946 self.v = -65.0;
947 self.h = 0.6;
948 self.n = 0.32;
949 }
950 if !self.s_nmda.is_finite() {
951 self.s_nmda = 0.0;
952 }
953 self.h = self.h.clamp(0.0, 1.0);
954 self.n = self.n.clamp(0.0, 1.0);
955
956 fired
957 }
958
959 pub fn reset(&mut self) {
960 *self = Self::new();
961 }
962}
963
964#[cfg(test)]
969mod tests {
970 use super::*;
971
972 #[test]
975 fn nap_fires_with_input() {
976 let mut n = PersistentNaNeuron::new();
977 let mut spikes = 0;
978 for _ in 0..2_000 {
979 spikes += n.step(2.0);
980 }
981 assert!(spikes > 5, "NaP neuron must fire with input, got {spikes}");
982 }
983
984 #[test]
985 fn nap_subthreshold_oscillations() {
986 let mut n = PersistentNaNeuron::new();
990 let mut spikes_inhibited = 0;
991 for _ in 0..10_000 {
992 spikes_inhibited += n.step(-2.0);
993 }
994 assert_eq!(
995 spikes_inhibited, 0,
996 "INaP neuron must be silent with inhibitory input, got {spikes_inhibited}"
997 );
998 }
999
1000 #[test]
1001 fn nap_lowers_threshold() {
1002 let mut with_nap = PersistentNaNeuron::new();
1004 let mut no_nap = PersistentNaNeuron::new();
1005 no_nap.g_nap = 0.0;
1006
1007 let input = 1.0;
1009 let mut spikes_nap = 0;
1010 let mut spikes_no = 0;
1011 for _ in 0..10_000 {
1012 spikes_nap += with_nap.step(input);
1013 spikes_no += no_nap.step(input);
1014 }
1015 assert!(
1016 spikes_nap >= spikes_no,
1017 "INaP must lower effective threshold: NaP={spikes_nap} vs none={spikes_no}"
1018 );
1019 }
1020
1021 #[test]
1022 fn nap_p_gate_activates_at_subthreshold() {
1023 let mut n = PersistentNaNeuron::new();
1025 n.v = -50.0;
1026 for _ in 0..1000 {
1028 let _ = n.step(0.0);
1030 }
1031 assert!(
1034 n.p > 0.01,
1035 "p gate must activate at subthreshold voltages, p={}",
1036 n.p
1037 );
1038 }
1039
1040 #[test]
1041 fn nap_increases_firing_rate() {
1042 let mut low = PersistentNaNeuron::new();
1044 low.g_nap = 0.2;
1045 let mut high = PersistentNaNeuron::new();
1046 high.g_nap = 1.5;
1047
1048 let input = 1.5;
1049 let mut spikes_low = 0;
1050 let mut spikes_high = 0;
1051 for _ in 0..10_000 {
1052 spikes_low += low.step(input);
1053 spikes_high += high.step(input);
1054 }
1055 assert!(
1056 spikes_high >= spikes_low,
1057 "Higher g_nap must increase firing: high={spikes_high} vs low={spikes_low}"
1058 );
1059 }
1060
1061 #[test]
1062 fn nap_negative_input_no_crash() {
1063 let mut n = PersistentNaNeuron::new();
1064 for _ in 0..10_000 {
1065 n.step(-100.0);
1066 }
1067 assert!(n.v.is_finite());
1068 assert!(n.v >= -100.0);
1069 }
1070
1071 #[test]
1072 fn nap_nan_input_stays_finite() {
1073 let mut n = PersistentNaNeuron::new();
1074 n.step(f64::NAN);
1075 assert!(n.v.is_finite());
1076 }
1077
1078 #[test]
1079 fn nap_extreme_input_bounded() {
1080 let mut n = PersistentNaNeuron::new();
1081 for _ in 0..1000 {
1082 n.step(1e6);
1083 }
1084 assert!(n.v.is_finite() && n.v <= 60.0);
1085 }
1086
1087 #[test]
1088 fn nap_reset_clears_state() {
1089 let mut n = PersistentNaNeuron::new();
1090 for _ in 0..1000 {
1091 n.step(10.0);
1092 }
1093 n.reset();
1094 assert_eq!(n.v, -65.0);
1095 assert_eq!(n.p, 0.0);
1096 assert_eq!(n.h, 0.6);
1097 }
1098
1099 #[test]
1100 fn nap_gates_bounded() {
1101 let mut n = PersistentNaNeuron::new();
1102 for _ in 0..10_000 {
1103 n.step(10.0);
1104 }
1105 assert!(n.h >= 0.0 && n.h <= 1.0);
1106 assert!(n.n >= 0.0 && n.n <= 1.0);
1107 assert!(n.p >= 0.0 && n.p <= 1.0);
1108 }
1109
1110 #[test]
1111 fn nap_performance_1k_steps() {
1112 let start = std::time::Instant::now();
1113 let mut n = PersistentNaNeuron::new();
1114 for _ in 0..1_000 {
1115 std::hint::black_box(n.step(5.0));
1116 }
1117 let elapsed = start.elapsed();
1118 assert!(
1119 elapsed.as_millis() < 200,
1120 "1k steps must complete in <200ms"
1121 );
1122 }
1123
1124 #[test]
1127 fn ih_fires_with_input() {
1128 let mut n = IhNeuron::new();
1129 let mut spikes = 0;
1130 for _ in 0..2_000 {
1131 spikes += n.step(2.0);
1132 }
1133 assert!(spikes > 5, "Ih neuron must fire with input, got {spikes}");
1134 }
1135
1136 #[test]
1137 fn ih_silent_without_input() {
1138 let mut n = IhNeuron::new();
1139 let mut spikes = 0;
1140 for _ in 0..10_000 {
1141 spikes += n.step(0.0);
1142 }
1143 assert_eq!(
1144 spikes, 0,
1145 "Ih neuron must be silent without input, got {spikes}"
1146 );
1147 }
1148
1149 #[test]
1150 fn ih_sag_potential() {
1151 let mut with_ih = IhNeuron::new();
1153 let mut no_ih = IhNeuron::new();
1154 no_ih.g_h = 0.0;
1155
1156 for _ in 0..4000 {
1158 with_ih.step(-3.0);
1159 no_ih.step(-3.0);
1160 }
1161 assert!(
1163 with_ih.v > no_ih.v,
1164 "Ih sag must depolarise from hyperpolarisation: Ih={:.1} vs no_Ih={:.1}",
1165 with_ih.v,
1166 no_ih.v
1167 );
1168 }
1169
1170 #[test]
1171 fn ih_r_gate_activates_on_hyperpolarisation() {
1172 let mut n = IhNeuron::new();
1173 let r_before = n.r;
1174 for _ in 0..4000 {
1176 n.step(-5.0);
1177 }
1178 assert!(
1179 n.r > r_before,
1180 "r gate must increase during hyperpolarisation, r={}",
1181 n.r
1182 );
1183 }
1184
1185 #[test]
1186 fn ih_rebound_excitation() {
1187 let mut n = IhNeuron::new();
1189 for _ in 0..4000 {
1191 n.step(-3.0);
1192 }
1193 let r_after_hyp = n.r;
1194 assert!(
1195 r_after_hyp > 0.2,
1196 "r must build up during hyperpolarisation, r={r_after_hyp}"
1197 );
1198
1199 let mut rebound_spikes = 0;
1201 for _ in 0..500 {
1202 rebound_spikes += n.step(1.5);
1203 }
1204
1205 let mut n2 = IhNeuron::new();
1207 let mut direct_spikes = 0;
1208 for _ in 0..500 {
1209 direct_spikes += n2.step(1.5);
1210 }
1211
1212 assert!(
1213 rebound_spikes >= direct_spikes,
1214 "Rebound should facilitate firing: rebound={rebound_spikes} vs direct={direct_spikes}"
1215 );
1216 }
1217
1218 #[test]
1219 fn ih_negative_input_no_crash() {
1220 let mut n = IhNeuron::new();
1221 for _ in 0..10_000 {
1222 n.step(-100.0);
1223 }
1224 assert!(n.v.is_finite());
1225 assert!(n.v >= -100.0);
1226 }
1227
1228 #[test]
1229 fn ih_nan_input_stays_finite() {
1230 let mut n = IhNeuron::new();
1231 n.step(f64::NAN);
1232 assert!(n.v.is_finite());
1233 }
1234
1235 #[test]
1236 fn ih_extreme_input_bounded() {
1237 let mut n = IhNeuron::new();
1238 for _ in 0..1000 {
1239 n.step(1e6);
1240 }
1241 assert!(n.v.is_finite() && n.v <= 60.0);
1242 }
1243
1244 #[test]
1245 fn ih_reset_clears_state() {
1246 let mut n = IhNeuron::new();
1247 for _ in 0..1000 {
1248 n.step(10.0);
1249 }
1250 n.reset();
1251 assert_eq!(n.v, -65.0);
1252 assert_eq!(n.r, 0.1);
1253 }
1254
1255 #[test]
1256 fn ih_gates_bounded() {
1257 let mut n = IhNeuron::new();
1258 for _ in 0..10_000 {
1259 n.step(10.0);
1260 }
1261 assert!(n.h >= 0.0 && n.h <= 1.0);
1262 assert!(n.n >= 0.0 && n.n <= 1.0);
1263 assert!(n.r >= 0.0 && n.r <= 1.0);
1264 }
1265
1266 #[test]
1267 fn ih_performance_1k_steps() {
1268 let start = std::time::Instant::now();
1269 let mut n = IhNeuron::new();
1270 for _ in 0..1_000 {
1271 std::hint::black_box(n.step(2.0));
1272 }
1273 let elapsed = start.elapsed();
1274 assert!(
1275 elapsed.as_millis() < 200,
1276 "1k steps must complete in <200ms"
1277 );
1278 }
1279
1280 #[test]
1283 fn ttype_fires_with_input() {
1284 let mut n = TTypeCaNeuron::new();
1285 let mut spikes = 0;
1286 for _ in 0..2_000 {
1287 spikes += n.step(2.0);
1288 }
1289 assert!(
1290 spikes > 5,
1291 "T-type neuron must fire with input, got {spikes}"
1292 );
1293 }
1294
1295 #[test]
1296 fn ttype_silent_without_input() {
1297 let mut n = TTypeCaNeuron::new();
1298 let mut spikes = 0;
1299 for _ in 0..10_000 {
1300 spikes += n.step(0.0);
1301 }
1302 assert_eq!(
1303 spikes, 0,
1304 "T-type neuron must be silent without input, got {spikes}"
1305 );
1306 }
1307
1308 #[test]
1309 fn ttype_rebound_burst() {
1310 let mut n = TTypeCaNeuron::new();
1312 for _ in 0..4000 {
1314 n.step(-3.0);
1315 }
1316 assert!(
1317 n.s > 0.3,
1318 "T-type must de-inactivate during hyperpolarisation, s={}",
1319 n.s
1320 );
1321
1322 let mut rebound_spikes = 0;
1324 for _ in 0..500 {
1325 rebound_spikes += n.step(1.5);
1326 }
1327
1328 let mut n2 = TTypeCaNeuron::new();
1330 n2.s = 0.05;
1331 let mut direct_spikes = 0;
1332 for _ in 0..500 {
1333 direct_spikes += n2.step(1.5);
1334 }
1335 assert!(
1336 rebound_spikes >= direct_spikes,
1337 "Rebound should facilitate firing: rebound={rebound_spikes} vs inact={direct_spikes}"
1338 );
1339 }
1340
1341 #[test]
1342 fn ttype_s_gate_de_inactivates_at_hyperpolarised() {
1343 let mut n = TTypeCaNeuron::new();
1344 n.v = -85.0;
1345 n.s = 0.1; for _ in 0..5000 {
1348 n.step(-5.0);
1349 }
1350 assert!(
1351 n.s > 0.5,
1352 "s must de-inactivate at hyperpolarised potentials, s={}",
1353 n.s
1354 );
1355 }
1356
1357 #[test]
1358 fn ttype_spike_inactivates_t_channel() {
1359 let mut n = TTypeCaNeuron::new();
1360 let s_before_spiking = n.s;
1361 let mut spiked = false;
1363 for _ in 0..2000 {
1364 if n.step(3.0) > 0 {
1365 spiked = true;
1366 break;
1367 }
1368 }
1369 if spiked {
1370 assert!(
1371 n.s < s_before_spiking,
1372 "Spike must inactivate T-type: before={s_before_spiking}, after={}",
1373 n.s
1374 );
1375 }
1376 }
1377
1378 #[test]
1379 fn ttype_negative_input_no_crash() {
1380 let mut n = TTypeCaNeuron::new();
1381 for _ in 0..10_000 {
1382 n.step(-100.0);
1383 }
1384 assert!(n.v.is_finite());
1385 assert!(n.v >= -100.0);
1386 }
1387
1388 #[test]
1389 fn ttype_nan_input_stays_finite() {
1390 let mut n = TTypeCaNeuron::new();
1391 n.step(f64::NAN);
1392 assert!(n.v.is_finite());
1393 }
1394
1395 #[test]
1396 fn ttype_extreme_input_bounded() {
1397 let mut n = TTypeCaNeuron::new();
1398 for _ in 0..1000 {
1399 n.step(1e6);
1400 }
1401 assert!(n.v.is_finite() && n.v <= 60.0);
1402 }
1403
1404 #[test]
1405 fn ttype_reset_clears_state() {
1406 let mut n = TTypeCaNeuron::new();
1407 for _ in 0..1000 {
1408 n.step(10.0);
1409 }
1410 n.reset();
1411 assert_eq!(n.v, -65.0);
1412 assert_eq!(n.s, 0.9);
1413 }
1414
1415 #[test]
1416 fn ttype_gates_bounded() {
1417 let mut n = TTypeCaNeuron::new();
1418 for _ in 0..10_000 {
1419 n.step(10.0);
1420 }
1421 assert!(n.h >= 0.0 && n.h <= 1.0);
1422 assert!(n.n >= 0.0 && n.n <= 1.0);
1423 assert!(n.s >= 0.0 && n.s <= 1.0);
1424 }
1425
1426 #[test]
1427 fn ttype_performance_1k_steps() {
1428 let start = std::time::Instant::now();
1429 let mut n = TTypeCaNeuron::new();
1430 for _ in 0..1_000 {
1431 std::hint::black_box(n.step(2.0));
1432 }
1433 let elapsed = start.elapsed();
1434 assert!(
1435 elapsed.as_millis() < 200,
1436 "1k steps must complete in <200ms"
1437 );
1438 }
1439
1440 #[test]
1443 fn atype_fires_with_input() {
1444 let mut n = ATypeKNeuron::new();
1445 let mut spikes = 0;
1446 for _ in 0..2_000 {
1447 spikes += n.step(3.0);
1448 }
1449 assert!(
1450 spikes > 5,
1451 "A-type neuron must fire with input, got {spikes}"
1452 );
1453 }
1454
1455 #[test]
1456 fn atype_silent_without_input() {
1457 let mut n = ATypeKNeuron::new();
1458 let mut spikes = 0;
1459 for _ in 0..10_000 {
1460 spikes += n.step(0.0);
1461 }
1462 assert_eq!(
1463 spikes, 0,
1464 "A-type neuron must be silent without input, got {spikes}"
1465 );
1466 }
1467
1468 #[test]
1469 fn atype_delays_first_spike() {
1470 let mut with_ia = ATypeKNeuron::new();
1472 let mut no_ia = ATypeKNeuron::new();
1473 no_ia.g_a = 0.0;
1474
1475 let input = 3.0;
1476 let mut time_with = 10_000usize;
1477 for i in 0..10_000 {
1478 if with_ia.step(input) > 0 {
1479 time_with = i;
1480 break;
1481 }
1482 }
1483 let mut time_no = 10_000usize;
1484 for i in 0..10_000 {
1485 if no_ia.step(input) > 0 {
1486 time_no = i;
1487 break;
1488 }
1489 }
1490 assert!(
1491 time_with >= time_no,
1492 "IA must delay first spike: with={time_with} vs without={time_no}"
1493 );
1494 }
1495
1496 #[test]
1497 fn atype_reduces_firing_rate() {
1498 let mut with_ia = ATypeKNeuron::new();
1500 let mut no_ia = ATypeKNeuron::new();
1501 no_ia.g_a = 0.0;
1502
1503 let input = 3.0;
1504 let mut spikes_ia = 0;
1505 let mut spikes_no = 0;
1506 for _ in 0..10_000 {
1507 spikes_ia += with_ia.step(input);
1508 spikes_no += no_ia.step(input);
1509 }
1510 assert!(
1511 spikes_no >= spikes_ia,
1512 "IA should reduce firing rate: IA={spikes_ia} vs none={spikes_no}"
1513 );
1514 }
1515
1516 #[test]
1517 fn atype_negative_input_no_crash() {
1518 let mut n = ATypeKNeuron::new();
1519 for _ in 0..10_000 {
1520 n.step(-100.0);
1521 }
1522 assert!(n.v.is_finite());
1523 assert!(n.v >= -100.0);
1524 }
1525
1526 #[test]
1527 fn atype_nan_input_stays_finite() {
1528 let mut n = ATypeKNeuron::new();
1529 n.step(f64::NAN);
1530 assert!(n.v.is_finite());
1531 }
1532
1533 #[test]
1534 fn atype_extreme_input_bounded() {
1535 let mut n = ATypeKNeuron::new();
1536 for _ in 0..1000 {
1537 n.step(1e6);
1538 }
1539 assert!(n.v.is_finite() && n.v <= 60.0);
1540 }
1541
1542 #[test]
1543 fn atype_reset_clears_state() {
1544 let mut n = ATypeKNeuron::new();
1545 for _ in 0..1000 {
1546 n.step(10.0);
1547 }
1548 n.reset();
1549 assert_eq!(n.v, -65.0);
1550 assert_eq!(n.a, 0.1);
1551 assert_eq!(n.b, 0.8);
1552 }
1553
1554 #[test]
1555 fn atype_gates_bounded() {
1556 let mut n = ATypeKNeuron::new();
1557 for _ in 0..10_000 {
1558 n.step(10.0);
1559 }
1560 assert!(n.a >= 0.0 && n.a <= 1.0);
1561 assert!(n.b >= 0.0 && n.b <= 1.0);
1562 }
1563
1564 #[test]
1565 fn atype_performance_1k_steps() {
1566 let start = std::time::Instant::now();
1567 let mut n = ATypeKNeuron::new();
1568 for _ in 0..1_000 {
1569 std::hint::black_box(n.step(3.0));
1570 }
1571 let elapsed = start.elapsed();
1572 assert!(
1573 elapsed.as_millis() < 200,
1574 "1k steps must complete in <200ms"
1575 );
1576 }
1577
1578 #[test]
1581 fn bk_fires_with_input() {
1582 let mut n = BKNeuron::new();
1583 let mut spikes = 0;
1584 for _ in 0..2_000 {
1585 spikes += n.step(3.0);
1586 }
1587 assert!(spikes > 5, "BK neuron must fire with input, got {spikes}");
1588 }
1589
1590 #[test]
1591 fn bk_silent_without_input() {
1592 let mut n = BKNeuron::new();
1593 let mut spikes = 0;
1594 for _ in 0..10_000 {
1595 spikes += n.step(0.0);
1596 }
1597 assert_eq!(
1598 spikes, 0,
1599 "BK neuron must be silent without input, got {spikes}"
1600 );
1601 }
1602
1603 #[test]
1604 fn bk_ca_accumulates_during_spiking() {
1605 let mut n = BKNeuron::new();
1606 assert_eq!(n.ca, 0.0);
1607 for _ in 0..5000 {
1608 n.step(5.0);
1609 }
1610 assert!(
1611 n.ca > 0.0,
1612 "Ca2+ must accumulate during spiking, ca={}",
1613 n.ca
1614 );
1615 }
1616
1617 #[test]
1618 fn bk_deepens_ahp() {
1619 let mut with_bk = BKNeuron::new();
1622 let mut no_bk = BKNeuron::new();
1623 no_bk.g_bk = 0.0;
1624
1625 for _ in 0..2000 {
1627 with_bk.step(5.0);
1628 no_bk.step(5.0);
1629 }
1630 assert!(with_bk.ca > 0.0, "BK neuron must have Ca2+ after spiking");
1634 }
1635
1636 #[test]
1637 fn bk_reduces_firing_rate() {
1638 let mut with_bk = BKNeuron::new();
1640 let mut no_bk = BKNeuron::new();
1641 no_bk.g_bk = 0.0;
1642
1643 let input = 3.0;
1644 let mut spikes_bk = 0;
1645 let mut spikes_no = 0;
1646 for _ in 0..10_000 {
1647 spikes_bk += with_bk.step(input);
1648 spikes_no += no_bk.step(input);
1649 }
1650 assert!(
1652 spikes_no >= spikes_bk,
1653 "BK should reduce firing: BK={spikes_bk} vs none={spikes_no}"
1654 );
1655 }
1656
1657 #[test]
1658 fn bk_negative_input_no_crash() {
1659 let mut n = BKNeuron::new();
1660 for _ in 0..10_000 {
1661 n.step(-100.0);
1662 }
1663 assert!(n.v.is_finite());
1664 }
1665
1666 #[test]
1667 fn bk_nan_input_stays_finite() {
1668 let mut n = BKNeuron::new();
1669 n.step(f64::NAN);
1670 assert!(n.v.is_finite());
1671 }
1672
1673 #[test]
1674 fn bk_extreme_input_bounded() {
1675 let mut n = BKNeuron::new();
1676 for _ in 0..1000 {
1677 n.step(1e6);
1678 }
1679 assert!(n.v.is_finite() && n.v <= 60.0);
1680 }
1681
1682 #[test]
1683 fn bk_reset_clears_state() {
1684 let mut n = BKNeuron::new();
1685 for _ in 0..1000 {
1686 n.step(10.0);
1687 }
1688 n.reset();
1689 assert_eq!(n.v, -65.0);
1690 assert_eq!(n.ca, 0.0);
1691 }
1692
1693 #[test]
1694 fn bk_performance_1k_steps() {
1695 let start = std::time::Instant::now();
1696 let mut n = BKNeuron::new();
1697 for _ in 0..1_000 {
1698 std::hint::black_box(n.step(3.0));
1699 }
1700 let elapsed = start.elapsed();
1701 assert!(
1702 elapsed.as_millis() < 200,
1703 "1k steps must complete in <200ms"
1704 );
1705 }
1706
1707 #[test]
1710 fn sk_fires_with_input() {
1711 let mut n = SKNeuron::new();
1712 let mut spikes = 0;
1713 for _ in 0..2_000 {
1714 spikes += n.step(2.0);
1715 }
1716 assert!(spikes > 5, "SK neuron must fire with input, got {spikes}");
1717 }
1718
1719 #[test]
1720 fn sk_silent_without_input() {
1721 let mut n = SKNeuron::new();
1722 let mut spikes = 0;
1723 for _ in 0..10_000 {
1724 spikes += n.step(0.0);
1725 }
1726 assert_eq!(
1727 spikes, 0,
1728 "SK neuron must be silent without input, got {spikes}"
1729 );
1730 }
1731
1732 #[test]
1733 fn sk_adaptation() {
1734 let mut n = SKNeuron::new();
1736 let input = 5.0;
1737 let mut early = 0;
1738 for _ in 0..2000 {
1739 early += n.step(input);
1740 }
1741 let mut late = 0;
1742 for _ in 0..2000 {
1743 late += n.step(input);
1744 }
1745 assert!(
1746 early >= late,
1747 "SK should cause adaptation: early={early}, late={late}"
1748 );
1749 }
1750
1751 #[test]
1752 fn sk_ca_dependent_only() {
1753 let n = SKNeuron::new();
1755 let ca2 = n.ca * n.ca;
1756 let sk_inf = ca2 / (ca2 + 0.25);
1757 assert!(
1758 sk_inf < 0.001,
1759 "SK must be inactive at ca=0, sk_inf={sk_inf}"
1760 );
1761 }
1762
1763 #[test]
1764 fn sk_reduces_firing_rate() {
1765 let mut with_sk = SKNeuron::new();
1766 let mut no_sk = SKNeuron::new();
1767 no_sk.g_sk = 0.0;
1768
1769 let input = 3.0;
1770 let mut spikes_sk = 0;
1771 let mut spikes_no = 0;
1772 for _ in 0..10_000 {
1773 spikes_sk += with_sk.step(input);
1774 spikes_no += no_sk.step(input);
1775 }
1776 assert!(
1777 spikes_no >= spikes_sk,
1778 "SK should reduce firing: SK={spikes_sk} vs none={spikes_no}"
1779 );
1780 }
1781
1782 #[test]
1783 fn sk_negative_input_no_crash() {
1784 let mut n = SKNeuron::new();
1785 for _ in 0..10_000 {
1786 n.step(-100.0);
1787 }
1788 assert!(n.v.is_finite());
1789 }
1790
1791 #[test]
1792 fn sk_nan_input_stays_finite() {
1793 let mut n = SKNeuron::new();
1794 n.step(f64::NAN);
1795 assert!(n.v.is_finite());
1796 }
1797
1798 #[test]
1799 fn sk_extreme_input_bounded() {
1800 let mut n = SKNeuron::new();
1801 for _ in 0..1000 {
1802 n.step(1e6);
1803 }
1804 assert!(n.v.is_finite() && n.v <= 60.0);
1805 }
1806
1807 #[test]
1808 fn sk_reset_clears_state() {
1809 let mut n = SKNeuron::new();
1810 for _ in 0..1000 {
1811 n.step(10.0);
1812 }
1813 n.reset();
1814 assert_eq!(n.v, -65.0);
1815 assert_eq!(n.ca, 0.0);
1816 }
1817
1818 #[test]
1819 fn sk_performance_1k_steps() {
1820 let start = std::time::Instant::now();
1821 let mut n = SKNeuron::new();
1822 for _ in 0..1_000 {
1823 std::hint::black_box(n.step(3.0));
1824 }
1825 let elapsed = start.elapsed();
1826 assert!(
1827 elapsed.as_millis() < 200,
1828 "1k steps must complete in <200ms"
1829 );
1830 }
1831
1832 #[test]
1835 fn nmda_fires_with_input() {
1836 let mut n = NMDANeuron::new();
1837 let mut spikes = 0;
1838 for _ in 0..2_000 {
1839 spikes += n.step(3.0);
1840 }
1841 assert!(spikes > 5, "NMDA neuron must fire with input, got {spikes}");
1842 }
1843
1844 #[test]
1845 fn nmda_silent_without_input() {
1846 let mut n = NMDANeuron::new();
1847 let mut spikes = 0;
1848 for _ in 0..10_000 {
1849 spikes += n.step(0.0);
1850 }
1851 assert_eq!(
1852 spikes, 0,
1853 "NMDA neuron must be silent without input, got {spikes}"
1854 );
1855 }
1856
1857 #[test]
1858 fn nmda_mg_block_at_rest() {
1859 let n = NMDANeuron::new();
1861 let mg_block = 1.0 / (1.0 + (n.mg_conc / 3.57) * (-0.062 * n.v).exp());
1862 assert!(
1863 mg_block < 0.1,
1864 "Mg2+ block must be strong at rest, B={mg_block}"
1865 );
1866 }
1867
1868 #[test]
1869 fn nmda_mg_relief_at_depolarised() {
1870 let mg_block = 1.0 / (1.0 + (1.0 / 3.57) * (-0.062 * (-20.0_f64)).exp());
1872 assert!(
1873 mg_block > 0.4,
1874 "Mg2+ block must be relieved at -20 mV, B={mg_block}"
1875 );
1876 }
1877
1878 #[test]
1879 fn nmda_s_builds_with_input() {
1880 let mut n = NMDANeuron::new();
1881 assert_eq!(n.s_nmda, 0.0);
1882 for _ in 0..2000 {
1883 n.step(5.0);
1884 }
1885 assert!(
1886 n.s_nmda > 0.0,
1887 "s_nmda must build with input, s={}",
1888 n.s_nmda
1889 );
1890 }
1891
1892 #[test]
1893 fn nmda_s_decays_without_input() {
1894 let mut n = NMDANeuron::new();
1895 for _ in 0..2000 {
1897 n.step(5.0);
1898 }
1899 let s_peak = n.s_nmda;
1900 for _ in 0..2000 {
1902 n.step(0.0);
1903 }
1904 assert!(n.s_nmda < s_peak, "s_nmda must decay after input removal");
1905 }
1906
1907 #[test]
1908 fn nmda_zero_mg_increases_current() {
1909 let mut with_mg = NMDANeuron::new();
1911 let mut no_mg = NMDANeuron::new();
1912 no_mg.mg_conc = 0.0;
1913
1914 let input = 2.0;
1915 let mut spikes_mg = 0;
1916 let mut spikes_no = 0;
1917 for _ in 0..10_000 {
1918 spikes_mg += with_mg.step(input);
1919 spikes_no += no_mg.step(input);
1920 }
1921 assert!(
1922 spikes_no >= spikes_mg,
1923 "No Mg2+ should increase NMDA current: no_mg={spikes_no} vs mg={spikes_mg}"
1924 );
1925 }
1926
1927 #[test]
1928 fn nmda_negative_input_no_crash() {
1929 let mut n = NMDANeuron::new();
1930 for _ in 0..10_000 {
1931 n.step(-100.0);
1932 }
1933 assert!(n.v.is_finite());
1934 }
1935
1936 #[test]
1937 fn nmda_nan_input_stays_finite() {
1938 let mut n = NMDANeuron::new();
1939 n.step(f64::NAN);
1940 assert!(n.v.is_finite());
1941 }
1942
1943 #[test]
1944 fn nmda_extreme_input_bounded() {
1945 let mut n = NMDANeuron::new();
1946 for _ in 0..1000 {
1947 n.step(1e6);
1948 }
1949 assert!(n.v.is_finite() && n.v <= 60.0);
1950 }
1951
1952 #[test]
1953 fn nmda_reset_clears_state() {
1954 let mut n = NMDANeuron::new();
1955 for _ in 0..1000 {
1956 n.step(10.0);
1957 }
1958 n.reset();
1959 assert_eq!(n.v, -65.0);
1960 assert_eq!(n.s_nmda, 0.0);
1961 }
1962
1963 #[test]
1964 fn nmda_performance_1k_steps() {
1965 let start = std::time::Instant::now();
1966 let mut n = NMDANeuron::new();
1967 for _ in 0..1_000 {
1968 std::hint::black_box(n.step(3.0));
1969 }
1970 let elapsed = start.elapsed();
1971 assert!(
1972 elapsed.as_millis() < 200,
1973 "1k steps must complete in <200ms"
1974 );
1975 }
1976}