1#[derive(Clone, Debug)]
38pub struct GradedSynapseNeuron {
39 pub v: f64, pub c_m: f64, pub g_l: f64, pub e_l: f64, pub g_in: f64, pub v_half: f64, pub k_release: f64, pub v_min: f64, pub v_max: f64, pub v_threshold: f64, pub dt: f64,
50 pub gain: f64,
51}
52
53impl Default for GradedSynapseNeuron {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl GradedSynapseNeuron {
60 pub fn new() -> Self {
61 Self {
62 v: -60.0,
63 c_m: 1.0,
64 g_l: 0.05, e_l: -60.0,
66 g_in: 0.1, v_half: -40.0, k_release: 5.0, v_min: -80.0,
70 v_max: -10.0,
71 v_threshold: -35.0, dt: 0.1,
73 gain: 1.0,
74 }
75 }
76
77 pub fn release(&self) -> f64 {
79 1.0 / (1.0 + (-(self.v - self.v_half) / self.k_release).exp())
80 }
81
82 pub fn step(&mut self, current: f64) -> i32 {
83 let input = self.gain * current;
84 let v_prev = self.v;
85
86 let dv = (-self.g_l * (self.v - self.e_l) + self.g_in * input) / self.c_m;
87 self.v += self.dt * dv;
88
89 self.v = self.v.clamp(self.v_min, self.v_max);
91 if !self.v.is_finite() {
92 self.v = self.e_l;
93 }
94
95 if self.v >= self.v_threshold && v_prev < self.v_threshold {
97 1
98 } else {
99 0
100 }
101 }
102
103 pub fn reset(&mut self) {
104 *self = Self::new();
105 }
106}
107
108#[derive(Clone, Debug)]
138pub struct GapJunctionNeuron {
139 pub v: f64, pub c_m: f64, pub g_l: f64, pub e_l: f64, pub g_gap: f64, pub i_tonic: f64, pub v_threshold: f64,
146 pub v_reset: f64,
147 pub refractory: f64, pub refrac_timer: f64,
149 pub rect_v0: f64, pub rect_a: f64, pub rect_gmin: f64, pub dt: f64,
154 pub gain: f64,
155}
156
157impl Default for GapJunctionNeuron {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163impl GapJunctionNeuron {
164 pub fn new() -> Self {
165 Self {
166 v: -65.0,
167 c_m: 1.0,
168 g_l: 0.1,
169 e_l: -65.0,
170 g_gap: 0.15, i_tonic: 0.0, v_threshold: -50.0,
173 v_reset: -65.0,
174 refractory: 2.0, refrac_timer: 0.0,
176 rect_v0: 30.0, rect_a: 0.1, rect_gmin: 0.1, dt: 0.1,
180 gain: 1.0,
181 }
182 }
183
184 #[inline]
190 fn rect_conductance(&self, v_j: f64) -> f64 {
191 self.rect_gmin
192 + (1.0 - self.rect_gmin) / (1.0 + (self.rect_a * (v_j.abs() - self.rect_v0)).exp())
193 }
194
195 pub fn step(&mut self, current: f64) -> i32 {
196 let input = self.gain * current;
198
199 if self.refrac_timer > 0.0 {
200 self.refrac_timer -= self.dt;
201 return 0;
202 }
203
204 let v_j = input - self.v;
206 let g_eff = self.g_gap * self.rect_conductance(v_j);
208 let i_gap = g_eff * v_j;
209 let dv = (-self.g_l * (self.v - self.e_l) + i_gap + self.i_tonic) / self.c_m;
210 self.v += self.dt * dv;
211
212 self.v = self.v.clamp(-100.0, 40.0);
214 if !self.v.is_finite() {
215 self.v = self.e_l;
216 }
217
218 if self.v >= self.v_threshold {
220 self.v = self.v_reset;
221 self.refrac_timer = self.refractory;
222 return 1;
223 }
224 0
225 }
226
227 pub fn reset(&mut self) {
228 *self = Self::new();
229 }
230}
231
232#[derive(Clone, Debug)]
260pub struct FrankenhaeUserHuxleyAxon {
261 pub v: f64, pub m: f64, pub h: f64, pub n: f64, pub p: f64, pub c_m: f64, pub p_na: f64, pub p_k: f64, pub p_p: f64, pub g_l: f64, pub e_l: f64, pub na_ratio: f64, pub k_ratio: f64, pub v_t: f64, pub dt: f64,
277 pub sub_steps: usize,
278 pub gain: f64,
279}
280
281impl Default for FrankenhaeUserHuxleyAxon {
282 fn default() -> Self {
283 Self::new()
284 }
285}
286
287impl FrankenhaeUserHuxleyAxon {
288 pub fn new() -> Self {
289 Self {
290 v: 0.0, m: 0.005,
292 h: 0.8,
293 n: 0.01,
294 p: 0.01,
295 c_m: 2.0, p_na: 88.4, p_k: 0.29, p_p: 5.96, g_l: 30.3, e_l: 0.026, na_ratio: 0.12, k_ratio: 48.0, v_t: 25.3, dt: 0.5, sub_steps: 50, gain: 1.0,
310 }
311 }
312
313 #[inline]
327 fn ghk_current(v: f64, c_ratio: f64, v_t: f64) -> f64 {
328 if v.abs() < 0.01 {
329 c_ratio - 1.0
331 } else {
332 let u = v / v_t;
333 let exp_neg_u = (-u).exp();
334 u * (c_ratio - exp_neg_u) / (1.0 - exp_neg_u)
335 }
336 }
337
338 pub fn step(&mut self, current: f64) -> i32 {
339 let input = self.gain * current;
340 let dt_sub = self.dt / self.sub_steps as f64;
341 let v_prev = self.v;
342
343 for _ in 0..self.sub_steps {
344 let v = self.v;
345
346 let am = if (v - 22.0).abs() < 0.1 {
351 1.87
352 } else {
353 0.36 * (v - 22.0) / (1.0 - (-(v - 22.0) / 3.0).exp())
354 };
355 let bm = if (v - 13.0).abs() < 0.1 {
356 1.87
357 } else {
358 0.4 * (13.0 - v) / (1.0 - ((v - 13.0) / 20.0).exp())
359 };
360
361 let ah = if (v + 10.0).abs() < 0.1 {
363 0.08
364 } else {
365 0.1 * (-10.0 - v) / (1.0 - ((v + 10.0) / 6.0).exp())
366 };
367 let bh = 4.5 / (1.0 + ((45.0 - v) / 10.0).exp());
368
369 let an = if (v - 13.0).abs() < 0.1 {
371 0.1
372 } else {
373 0.02 * (v - 13.0) / (1.0 - (-(v - 13.0) / 10.0).exp())
374 };
375 let bn = if (v - 23.0).abs() < 0.1 {
376 0.05
377 } else {
378 0.05 * (23.0 - v) / (1.0 - ((v - 23.0) / 10.0).exp())
379 };
380
381 let ap = if (v - 21.0).abs() < 0.1 {
383 0.04
384 } else {
385 0.006 * (v - 21.0) / (1.0 - (-(v - 21.0) / 2.0).exp())
386 };
387 let bp = if (v + 4.0).abs() < 0.1 {
388 0.04
389 } else {
390 0.09 * (-4.0 - v) / (1.0 - ((v + 4.0) / 2.0).exp())
391 };
392
393 let am = am.max(0.0);
395 let bm = bm.max(0.0);
396 let ah = ah.max(0.0);
397 let bh = bh.max(0.0);
398 let an = an.max(0.0);
399 let bn = bn.max(0.0);
400 let ap = ap.max(0.0);
401 let bp = bp.max(0.0);
402
403 self.m += dt_sub * (am * (1.0 - self.m) - bm * self.m);
405 self.h += dt_sub * (ah * (1.0 - self.h) - bh * self.h);
406 self.n += dt_sub * (an * (1.0 - self.n) - bn * self.n);
407 self.p += dt_sub * (ap * (1.0 - self.p) - bp * self.p);
408
409 self.m = self.m.clamp(0.0, 1.0);
411 self.h = self.h.clamp(0.0, 1.0);
412 self.n = self.n.clamp(0.0, 1.0);
413 self.p = self.p.clamp(0.0, 1.0);
414
415 let i_na = self.p_na
417 * self.m
418 * self.m
419 * self.h
420 * Self::ghk_current(v, self.na_ratio, self.v_t);
421 let i_k = self.p_k * self.n * self.n * Self::ghk_current(v, self.k_ratio, self.v_t);
422 let i_p = self.p_p * self.p * self.p * Self::ghk_current(v, self.na_ratio, self.v_t);
424 let i_l = self.g_l * (self.v - self.e_l);
425
426 let dv = (-(i_na + i_k + i_p + i_l) + input) / self.c_m;
427 self.v += dt_sub * dv;
428 }
429
430 self.v = self.v.clamp(-50.0, 150.0);
432 if !self.v.is_finite() {
433 self.v = 0.0;
434 }
435 if !self.m.is_finite() {
436 self.m = 0.005;
437 }
438 if !self.h.is_finite() {
439 self.h = 0.8;
440 }
441 if !self.n.is_finite() {
442 self.n = 0.01;
443 }
444 if !self.p.is_finite() {
445 self.p = 0.01;
446 }
447
448 if self.v >= 40.0 && v_prev < 40.0 {
450 1
451 } else {
452 0
453 }
454 }
455
456 pub fn reset(&mut self) {
457 *self = Self::new();
458 }
459}
460
461#[derive(Clone, Debug)]
487pub struct NodeOfRanvier {
488 pub v: f64, pub m: f64, pub h: f64, pub p: f64, pub s: f64, pub c_m: f64, pub g_nat: f64, pub g_nap: f64, pub g_ks: f64, pub g_l: f64, pub e_na: f64, pub e_k: f64, pub e_l: f64, pub dt: f64, pub sub_steps: usize,
503 pub gain: f64,
504}
505
506impl Default for NodeOfRanvier {
507 fn default() -> Self {
508 Self::new()
509 }
510}
511
512impl NodeOfRanvier {
513 pub fn new() -> Self {
514 Self {
515 v: -80.0,
516 m: 0.01,
517 h: 0.75,
518 p: 0.01,
519 s: 0.05,
520 c_m: 2.0, g_nat: 3000.0, g_nap: 5.0, g_ks: 80.0, g_l: 7.0, e_na: 50.0,
526 e_k: -90.0,
527 e_l: -90.0, dt: 0.5, sub_steps: 20, gain: 1.0,
531 }
532 }
533
534 #[inline]
536 fn boltz(v: f64, v_half: f64, k: f64) -> f64 {
537 1.0 / (1.0 + (-(v - v_half) / k).exp())
538 }
539
540 pub fn step(&mut self, current: f64) -> i32 {
541 let input = self.gain * current;
542 let dt_sub = self.dt / self.sub_steps as f64;
543 let v_prev_ext = self.v;
544
545 for _ in 0..self.sub_steps {
546 let v = self.v;
547
548 let m_inf = Self::boltz(v, -26.8, 9.2);
551 let tau_m = 0.025 + 0.14 / (1.0 + ((v + 25.0) / 10.0).powi(2)).max(0.01);
552 self.m += dt_sub * (m_inf - self.m) / tau_m;
553
554 let h_inf = Self::boltz(v, -55.2, -7.4);
557 let tau_h = 0.6 + 4.0 / (1.0 + ((v + 45.0) / 10.0).powi(2)).max(0.01);
558 self.h += dt_sub * (h_inf - self.h) / tau_h;
559
560 let p_inf = Self::boltz(v, -44.0, 5.0);
563 let tau_p = 1.0 + 6.0 / (1.0 + ((v + 40.0) / 10.0).powi(2)).max(0.01);
564 self.p += dt_sub * (p_inf - self.p) / tau_p;
565
566 let s_inf = Self::boltz(v, -30.0, 10.0);
569 let tau_s = 20.0 + 60.0 / (1.0 + ((v + 30.0) / 15.0).powi(2)).max(0.01);
570 self.s += dt_sub * (s_inf - self.s) / tau_s;
571
572 self.m = self.m.clamp(0.0, 1.0);
574 self.h = self.h.clamp(0.0, 1.0);
575 self.p = self.p.clamp(0.0, 1.0);
576 self.s = self.s.clamp(0.0, 1.0);
577
578 let i_nat = self.g_nat * self.m.powi(3) * self.h * (v - self.e_na);
580 let i_nap = self.g_nap * self.p.powi(3) * (v - self.e_na);
581 let i_ks = self.g_ks * self.s * (v - self.e_k);
582 let i_l = self.g_l * (v - self.e_l);
583
584 let dv = (-(i_nat + i_nap + i_ks + i_l) + input) / self.c_m;
585 self.v += dt_sub * dv;
586 }
587
588 self.v = self.v.clamp(-120.0, 60.0);
590 if !self.v.is_finite() {
591 self.v = -80.0;
592 }
593 if !self.m.is_finite() {
594 self.m = 0.01;
595 }
596 if !self.h.is_finite() {
597 self.h = 0.75;
598 }
599 if !self.p.is_finite() {
600 self.p = 0.01;
601 }
602 if !self.s.is_finite() {
603 self.s = 0.05;
604 }
605
606 if self.v >= -10.0 && v_prev_ext < -10.0 {
608 1
609 } else {
610 0
611 }
612 }
613
614 pub fn reset(&mut self) {
615 *self = Self::new();
616 }
617}
618
619#[derive(Clone, Debug)]
649pub struct MyelinatedAxon {
650 pub node: NodeOfRanvier,
652 pub v_inter: f64, pub c_inter: f64, pub g_l_myelin: f64, pub e_l_myelin: f64, pub g_para: f64, pub dt: f64,
659 pub gain: f64,
660}
661
662impl Default for MyelinatedAxon {
663 fn default() -> Self {
664 Self::new()
665 }
666}
667
668impl MyelinatedAxon {
669 pub fn new() -> Self {
670 Self {
671 node: NodeOfRanvier::new(),
672 v_inter: -80.0,
673 c_inter: 0.001, g_l_myelin: 0.001, e_l_myelin: -80.0,
676 g_para: 0.01, dt: 0.5,
678 gain: 1.0,
679 }
680 }
681
682 pub fn step(&mut self, current: f64) -> i32 {
683 let input = self.gain * current;
684
685 let i_para_to_node = self.g_para * (self.v_inter - self.node.v);
687 let i_para_to_inter = self.g_para * (self.node.v - self.v_inter);
688
689 let dv_inter =
691 (-self.g_l_myelin * (self.v_inter - self.e_l_myelin) + i_para_to_inter) / self.c_inter;
692 self.v_inter += self.node.dt / self.node.sub_steps as f64 * dv_inter;
693
694 self.v_inter = self.v_inter.clamp(-120.0, 60.0);
696 if !self.v_inter.is_finite() {
697 self.v_inter = -80.0;
698 }
699
700 let total_input = input + i_para_to_node * 100.0; self.node.step(total_input)
704 }
705
706 pub fn v(&self) -> f64 {
708 self.node.v
709 }
710
711 pub fn reset(&mut self) {
712 self.node.reset();
713 self.v_inter = -80.0;
714 }
715}
716
717#[derive(Clone, Debug)]
746pub struct CardiacPurkinjeFibre {
747 pub v: f64,
748 pub m: f64, pub h: f64, pub d: f64, pub f: f64, pub x_r: f64, pub y: f64, pub c_m: f64,
755 pub g_na: f64,
756 pub g_cal: f64,
757 pub g_kr: f64,
758 pub g_k1: f64,
759 pub g_f: f64, pub g_l: f64,
761 pub e_na: f64,
762 pub e_ca: f64,
763 pub e_k: f64,
764 pub e_f: f64, pub e_l: f64,
766 pub dt: f64,
767 pub sub_steps: usize,
768 pub gain: f64,
769}
770
771impl Default for CardiacPurkinjeFibre {
772 fn default() -> Self {
773 Self::new()
774 }
775}
776
777impl CardiacPurkinjeFibre {
778 pub fn new() -> Self {
779 Self {
780 v: -85.0,
781 m: 0.001,
782 h: 0.99,
783 d: 0.001,
784 f: 0.99,
785 x_r: 0.01,
786 y: 0.05,
787 c_m: 1.0,
788 g_na: 15.0, g_cal: 0.05, g_kr: 0.015, g_k1: 0.4, g_f: 0.01, g_l: 0.03,
794 e_na: 40.0,
795 e_ca: 65.0,
796 e_k: -90.0,
797 e_f: -20.0, e_l: -50.0,
799 dt: 0.5,
800 sub_steps: 10, gain: 1.0,
802 }
803 }
804
805 #[inline]
806 fn boltz(v: f64, vh: f64, k: f64) -> f64 {
807 1.0 / (1.0 + (-(v - vh) / k).exp())
808 }
809
810 pub fn step(&mut self, current: f64) -> i32 {
811 let input = self.gain * current;
812 let dt_sub = self.dt / self.sub_steps as f64;
813 let v_prev = self.v;
814
815 for _ in 0..self.sub_steps {
816 let v = self.v;
817
818 let m_inf = Self::boltz(v, -40.0, 8.0);
820 let tau_m = 0.05 + 0.3 / (1.0 + ((v + 40.0) / 10.0).powi(2)).max(0.01);
821 self.m += dt_sub * (m_inf - self.m) / tau_m;
822
823 let h_inf = Self::boltz(v, -65.0, -7.0);
825 let tau_h = 0.5 + 8.0 / (1.0 + ((v + 65.0) / 15.0).powi(2)).max(0.01);
826 self.h += dt_sub * (h_inf - self.h) / tau_h;
827
828 let d_inf = Self::boltz(v, -10.0, 6.0);
830 let tau_d = 2.0 + 5.0 / (1.0 + ((v + 10.0) / 10.0).powi(2)).max(0.01);
831 self.d += dt_sub * (d_inf - self.d) / tau_d;
832
833 let f_inf = Self::boltz(v, -30.0, -8.0);
835 let tau_f = 20.0 + 100.0 / (1.0 + ((v + 30.0) / 10.0).powi(2)).max(0.01);
836 self.f += dt_sub * (f_inf - self.f) / tau_f;
837
838 let xr_inf = Self::boltz(v, -20.0, 10.0);
840 let tau_xr = 50.0 + 200.0 / (1.0 + ((v + 20.0) / 15.0).powi(2)).max(0.01);
841 self.x_r += dt_sub * (xr_inf - self.x_r) / tau_xr;
842
843 let y_inf = Self::boltz(v, -80.0, -10.0);
845 let tau_y = 100.0 + 500.0 / (1.0 + ((v + 80.0) / 20.0).powi(2)).max(0.01);
846 self.y += dt_sub * (y_inf - self.y) / tau_y;
847
848 self.m = self.m.clamp(0.0, 1.0);
850 self.h = self.h.clamp(0.0, 1.0);
851 self.d = self.d.clamp(0.0, 1.0);
852 self.f = self.f.clamp(0.0, 1.0);
853 self.x_r = self.x_r.clamp(0.0, 1.0);
854 self.y = self.y.clamp(0.0, 1.0);
855
856 let k1_inf = 1.0 / (1.0 + ((v - self.e_k + 10.0) / 10.0).exp());
858
859 let i_na = self.g_na * self.m.powi(3) * self.h * (v - self.e_na);
861 let i_cal = self.g_cal * self.d * self.f * (v - self.e_ca);
862 let i_kr = self.g_kr * self.x_r * (v - self.e_k);
863 let i_k1 = self.g_k1 * k1_inf * (v - self.e_k);
864 let i_f = self.g_f * self.y * (v - self.e_f);
865 let i_l = self.g_l * (v - self.e_l);
866
867 let dv = (-(i_na + i_cal + i_kr + i_k1 + i_f + i_l) + input) / self.c_m;
868 self.v += dt_sub * dv;
869 }
870
871 self.v = self.v.clamp(-120.0, 60.0);
873 if !self.v.is_finite() {
874 self.v = -85.0;
875 }
876 if !self.m.is_finite() {
877 self.m = 0.001;
878 }
879 if !self.h.is_finite() {
880 self.h = 0.99;
881 }
882 if !self.d.is_finite() {
883 self.d = 0.001;
884 }
885 if !self.f.is_finite() {
886 self.f = 0.99;
887 }
888 if !self.x_r.is_finite() {
889 self.x_r = 0.01;
890 }
891 if !self.y.is_finite() {
892 self.y = 0.05;
893 }
894
895 if self.v >= -20.0 && v_prev < -20.0 {
897 1
898 } else {
899 0
900 }
901 }
902
903 pub fn reset(&mut self) {
904 *self = Self::new();
905 }
906}
907
908#[derive(Clone, Debug)]
929pub struct SmoothMuscleCell {
930 pub v: f64,
931 pub d: f64, pub f: f64, pub ca: f64, pub ca_store: f64, pub c_m: f64,
936 pub g_cal: f64, pub g_bk: f64, pub g_l: f64, pub e_ca: f64,
940 pub e_k: f64,
941 pub e_l: f64,
942 pub tau_ca: f64, pub v_serca: f64, pub k_serca: f64, pub ip3: f64, pub v_ip3r: f64, pub k_ip3: f64, pub k_ca_ip3: f64, pub kd_bk: f64, pub dt: f64,
951 pub sub_steps: usize,
952 pub gain: f64,
953}
954
955impl Default for SmoothMuscleCell {
956 fn default() -> Self {
957 Self::new()
958 }
959}
960
961impl SmoothMuscleCell {
962 pub fn new() -> Self {
963 Self {
964 v: -60.0,
965 d: 0.01,
966 f: 0.95,
967 ca: 0.1,
968 ca_store: 100.0, c_m: 1.0,
970 g_cal: 2.0, g_bk: 1.0, g_l: 0.1,
973 e_ca: 60.0,
974 e_k: -80.0,
975 e_l: -50.0,
976 tau_ca: 50.0,
977 v_serca: 0.5, k_serca: 0.3, ip3: 0.5, v_ip3r: 2.0, k_ip3: 0.3, k_ca_ip3: 0.3, kd_bk: 0.5, dt: 1.0, sub_steps: 4,
986 gain: 1.0,
987 }
988 }
989
990 #[inline]
991 fn boltz(v: f64, vh: f64, k: f64) -> f64 {
992 1.0 / (1.0 + (-(v - vh) / k).exp())
993 }
994
995 pub fn step(&mut self, current: f64) -> i32 {
996 let input = self.gain * current;
997 let dt_sub = self.dt / self.sub_steps as f64;
998 let v_prev = self.v;
999
1000 for _ in 0..self.sub_steps {
1001 let v = self.v;
1002
1003 let d_inf = Self::boltz(v, -20.0, 6.0);
1005 let tau_d = 5.0 + 20.0 / (1.0 + ((v + 20.0) / 10.0).powi(2)).max(0.01);
1006 self.d += dt_sub * (d_inf - self.d) / tau_d;
1007
1008 let f_inf = Self::boltz(v, -35.0, -8.0);
1010 let tau_f = 50.0 + 200.0 / (1.0 + ((v + 35.0) / 10.0).powi(2)).max(0.01);
1011 self.f += dt_sub * (f_inf - self.f) / tau_f;
1012
1013 self.d = self.d.clamp(0.0, 1.0);
1014 self.f = self.f.clamp(0.0, 1.0);
1015
1016 let bk_ca = self.ca * self.ca / (self.ca * self.ca + self.kd_bk * self.kd_bk);
1018 let bk_v = Self::boltz(v, -10.0, 15.0);
1019 let bk_inf = bk_ca * bk_v;
1020
1021 let i_cal = self.g_cal * self.d * self.f * (v - self.e_ca);
1023 let i_bk = self.g_bk * bk_inf * (v - self.e_k);
1024 let i_l = self.g_l * (v - self.e_l);
1025
1026 let dv = (-(i_cal + i_bk + i_l) + input) / self.c_m;
1027 self.v += dt_sub * dv;
1028
1029 let ca_entry = if i_cal < 0.0 { -i_cal * 0.01 } else { 0.0 };
1032
1033 let ip3_act = self.ip3 / (self.ip3 + self.k_ip3);
1035 let ca_act = self.ca / (self.ca + self.k_ca_ip3);
1036 let ip3_release = self.v_ip3r * ip3_act * ca_act * self.ca_store;
1037
1038 let serca = self.v_serca * self.ca * self.ca
1040 / (self.ca * self.ca + self.k_serca * self.k_serca);
1041
1042 self.ca += dt_sub * (ca_entry + ip3_release - serca - self.ca / self.tau_ca);
1044 self.ca_store += dt_sub * (serca - ip3_release);
1045
1046 self.ca = self.ca.max(0.0);
1047 self.ca_store = self.ca_store.max(0.0);
1048 }
1049
1050 self.v = self.v.clamp(-100.0, 40.0);
1052 if !self.v.is_finite() {
1053 self.v = -60.0;
1054 }
1055 if !self.ca.is_finite() {
1056 self.ca = 0.1;
1057 }
1058 if !self.ca_store.is_finite() {
1059 self.ca_store = 100.0;
1060 }
1061
1062 if self.v >= -30.0 && v_prev < -30.0 {
1064 1
1065 } else {
1066 0
1067 }
1068 }
1069
1070 pub fn reset(&mut self) {
1071 *self = Self::new();
1072 }
1073}
1074
1075#[derive(Clone, Debug)]
1099pub struct EndocrineBetaCell {
1100 pub v: f64,
1101 pub n: f64, pub ca: f64, pub c_m: f64,
1104 pub g_cal: f64, pub g_kdr: f64, pub g_katp: f64, pub g_kca: f64, pub g_l: f64,
1109 pub e_ca: f64,
1110 pub e_k: f64,
1111 pub e_l: f64,
1112 pub tau_ca: f64, pub kd_kca: f64, pub atp_level: f64, pub dt: f64,
1116 pub sub_steps: usize,
1117 pub gain: f64,
1118}
1119
1120impl Default for EndocrineBetaCell {
1121 fn default() -> Self {
1122 Self::new()
1123 }
1124}
1125
1126impl EndocrineBetaCell {
1127 pub fn new() -> Self {
1128 Self {
1129 v: -70.0,
1130 n: 0.01,
1131 ca: 0.1,
1132 c_m: 1.0,
1133 g_cal: 5.0, g_kdr: 4.0, g_katp: 3.0, g_kca: 2.0, g_l: 0.1,
1138 e_ca: 50.0,
1139 e_k: -75.0,
1140 e_l: -30.0, tau_ca: 100.0, kd_kca: 0.5, atp_level: 0.3, dt: 0.5,
1145 sub_steps: 4,
1146 gain: 1.0,
1147 }
1148 }
1149
1150 #[inline]
1151 fn boltz(v: f64, vh: f64, k: f64) -> f64 {
1152 1.0 / (1.0 + (-(v - vh) / k).exp())
1153 }
1154
1155 pub fn step(&mut self, current: f64) -> i32 {
1156 let input = self.gain * current;
1157 let dt_sub = self.dt / self.sub_steps as f64;
1158 let v_prev = self.v;
1159
1160 for _ in 0..self.sub_steps {
1161 let v = self.v;
1162
1163 let m_cal_inf = Self::boltz(v, -20.0, 8.0);
1165
1166 let n_inf = Self::boltz(v, -15.0, 6.0);
1168 let tau_n = 5.0 + 20.0 / (1.0 + ((v + 15.0) / 10.0).powi(2)).max(0.01);
1169 self.n += dt_sub * (n_inf - self.n) / tau_n;
1170 self.n = self.n.clamp(0.0, 1.0);
1171
1172 let g_katp_eff = self.g_katp * (1.0 - self.atp_level);
1175
1176 let kca_inf = self.ca * self.ca / (self.ca * self.ca + self.kd_kca * self.kd_kca);
1178
1179 let i_cal = self.g_cal * m_cal_inf * (v - self.e_ca);
1181 let i_kdr = self.g_kdr * self.n.powi(4) * (v - self.e_k);
1182 let i_katp = g_katp_eff * (v - self.e_k);
1183 let i_kca = self.g_kca * kca_inf * (v - self.e_k);
1184 let i_l = self.g_l * (v - self.e_l);
1185
1186 let dv = (-(i_cal + i_kdr + i_katp + i_kca + i_l) + input) / self.c_m;
1187 self.v += dt_sub * dv;
1188
1189 let ca_entry = if i_cal < 0.0 { -i_cal * 0.002 } else { 0.0 };
1191 self.ca += dt_sub * (ca_entry - self.ca / self.tau_ca);
1192 self.ca = self.ca.max(0.0);
1193 }
1194
1195 self.v = self.v.clamp(-100.0, 40.0);
1197 if !self.v.is_finite() {
1198 self.v = -70.0;
1199 }
1200 if !self.n.is_finite() {
1201 self.n = 0.01;
1202 }
1203 if !self.ca.is_finite() {
1204 self.ca = 0.1;
1205 }
1206
1207 if self.v >= -20.0 && v_prev < -20.0 {
1209 1
1210 } else {
1211 0
1212 }
1213 }
1214
1215 pub fn reset(&mut self) {
1216 *self = Self::new();
1217 }
1218}
1219
1220#[cfg(test)]
1225mod tests {
1226 use super::*;
1227
1228 #[test]
1231 fn graded_depolarises_with_input() {
1232 let mut n = GradedSynapseNeuron::new();
1233 let v0 = n.v;
1234 for _ in 0..10_000 {
1235 n.step(200.0);
1236 }
1237 assert!(
1238 n.v > v0,
1239 "Must depolarise with positive input: v0={v0}, v={}",
1240 n.v
1241 );
1242 }
1243
1244 #[test]
1245 fn graded_hyperpolarises_with_negative_input() {
1246 let mut n = GradedSynapseNeuron::new();
1247 let v0 = n.v;
1248 for _ in 0..10_000 {
1249 n.step(-200.0);
1250 }
1251 assert!(
1252 n.v < v0,
1253 "Must hyperpolarise with negative input: v0={v0}, v={}",
1254 n.v
1255 );
1256 }
1257
1258 #[test]
1259 fn graded_fires_with_strong_input() {
1260 let mut n = GradedSynapseNeuron::new();
1261 let mut spikes = 0;
1262 for _ in 0..50_000 {
1263 spikes += n.step(500.0);
1264 }
1265 assert!(
1266 spikes > 0,
1267 "Must cross threshold with strong input, got {spikes}"
1268 );
1269 }
1270
1271 #[test]
1272 fn graded_silent_without_input() {
1273 let mut n = GradedSynapseNeuron::new();
1274 let mut spikes = 0;
1275 for _ in 0..50_000 {
1276 spikes += n.step(0.0);
1277 }
1278 assert_eq!(
1279 spikes, 0,
1280 "Must be silent without input (V starts at E_L), got {spikes}"
1281 );
1282 }
1283
1284 #[test]
1285 fn graded_release_monotonic() {
1286 let mut n = GradedSynapseNeuron::new();
1288 n.v = -70.0;
1289 let r_low = n.release();
1290 n.v = -40.0;
1291 let r_mid = n.release();
1292 n.v = -10.0;
1293 let r_high = n.release();
1294 assert!(
1295 r_low < r_mid && r_mid < r_high,
1296 "Release must be monotonic: r_low={r_low:.3}, r_mid={r_mid:.3}, r_high={r_high:.3}"
1297 );
1298 }
1299
1300 #[test]
1301 fn graded_release_bounded() {
1302 let mut n = GradedSynapseNeuron::new();
1303 n.v = -100.0;
1304 assert!(n.release() >= 0.0 && n.release() <= 1.0);
1305 n.v = 50.0;
1306 assert!(n.release() >= 0.0 && n.release() <= 1.0);
1307 }
1308
1309 #[test]
1310 fn graded_v_saturates() {
1311 let mut n = GradedSynapseNeuron::new();
1312 for _ in 0..50_000 {
1313 n.step(1e6);
1314 }
1315 assert!(
1316 n.v <= n.v_max,
1317 "V must not exceed v_max={}, got {}",
1318 n.v_max,
1319 n.v
1320 );
1321 n.reset();
1322 for _ in 0..50_000 {
1323 n.step(-1e6);
1324 }
1325 assert!(
1326 n.v >= n.v_min,
1327 "V must not go below v_min={}, got {}",
1328 n.v_min,
1329 n.v
1330 );
1331 }
1332
1333 #[test]
1334 fn graded_nan_input_stays_finite() {
1335 let mut n = GradedSynapseNeuron::new();
1336 n.step(f64::NAN);
1337 assert!(n.v.is_finite());
1338 }
1339
1340 #[test]
1341 fn graded_reset_clears_state() {
1342 let mut n = GradedSynapseNeuron::new();
1343 for _ in 0..10_000 {
1344 n.step(500.0);
1345 }
1346 n.reset();
1347 assert_eq!(n.v, -60.0);
1348 }
1349
1350 #[test]
1351 fn graded_performance_100k_steps() {
1352 let start = std::time::Instant::now();
1353 let mut n = GradedSynapseNeuron::new();
1354 for _ in 0..100_000 {
1355 std::hint::black_box(n.step(100.0));
1356 }
1357 let elapsed = start.elapsed();
1358 assert!(
1359 elapsed.as_millis() < 50,
1360 "100k steps must complete in <50ms"
1361 );
1362 }
1363
1364 #[test]
1367 fn gap_fires_with_depolarising_drive() {
1368 let mut n = GapJunctionNeuron::new();
1370 let mut spikes = 0;
1371 for _ in 0..50_000 {
1372 spikes += n.step(0.0); }
1374 assert!(
1375 spikes > 0,
1376 "Gap junction must fire with depolarising drive, got {spikes}"
1377 );
1378 }
1379
1380 #[test]
1381 fn gap_silent_at_rest() {
1382 let mut n = GapJunctionNeuron::new();
1384 let mut spikes = 0;
1385 for _ in 0..50_000 {
1386 spikes += n.step(-65.0); }
1388 assert_eq!(
1389 spikes, 0,
1390 "Must be silent when V_neighbor = E_L, got {spikes}"
1391 );
1392 }
1393
1394 #[test]
1395 fn gap_junction_pulls_toward_neighbor() {
1396 let mut n = GapJunctionNeuron::new(); for _ in 0..5_000 {
1399 n.step(-40.0);
1400 } assert!(
1402 n.v > -65.0 || n.refrac_timer > 0.0,
1403 "Gap junction must pull V toward neighbor: v={}",
1404 n.v
1405 );
1406 }
1407
1408 #[test]
1409 fn gap_stronger_coupling_more_spikes() {
1410 let mut weak = GapJunctionNeuron::new();
1411 weak.g_gap = 0.01;
1412 let mut strong = GapJunctionNeuron::new();
1413 strong.g_gap = 0.1;
1414 let (mut sw, mut ss) = (0, 0);
1415 for _ in 0..50_000 {
1416 sw += weak.step(-20.0);
1417 ss += strong.step(-20.0);
1418 }
1419 assert!(
1420 ss >= sw,
1421 "Stronger coupling → more spikes: strong={ss} vs weak={sw}"
1422 );
1423 }
1424
1425 #[test]
1426 fn gap_refractory_enforced() {
1427 let mut n = GapJunctionNeuron::new();
1428 let mut first_spike_t = 0;
1430 for t in 0..10_000 {
1431 if n.step(0.0) == 1 {
1432 first_spike_t = t;
1433 break;
1434 }
1435 }
1436 assert!(first_spike_t > 0, "Must spike");
1437 assert!(n.refrac_timer > 0.0, "Must be in refractory after spike");
1439 assert_eq!(n.step(0.0), 0, "Must not spike during refractory");
1440 }
1441
1442 #[test]
1443 fn gap_hyperpolarising_drive_silent() {
1444 let mut n = GapJunctionNeuron::new();
1446 let mut spikes = 0;
1447 for _ in 0..50_000 {
1448 spikes += n.step(-100.0);
1449 }
1450 assert_eq!(
1451 spikes, 0,
1452 "Hyperpolarising drive must keep silent, got {spikes}"
1453 );
1454 }
1455
1456 #[test]
1457 fn gap_tonic_current_depolarises() {
1458 let mut n = GapJunctionNeuron::new();
1459 n.i_tonic = 5.0; let mut spikes = 0;
1461 for _ in 0..50_000 {
1462 spikes += n.step(-65.0); }
1464 assert!(
1465 spikes > 0,
1466 "Tonic current should produce spikes, got {spikes}"
1467 );
1468 }
1469
1470 #[test]
1471 fn gap_nan_input_stays_finite() {
1472 let mut n = GapJunctionNeuron::new();
1473 n.step(f64::NAN);
1474 assert!(n.v.is_finite());
1475 }
1476
1477 #[test]
1478 fn gap_reset_clears_state() {
1479 let mut n = GapJunctionNeuron::new();
1480 for _ in 0..10_000 {
1481 n.step(-20.0);
1482 }
1483 n.reset();
1484 assert_eq!(n.v, -65.0);
1485 assert_eq!(n.refrac_timer, 0.0);
1486 }
1487
1488 #[test]
1489 fn gap_rectification_reduces_at_large_vj() {
1490 let n = GapJunctionNeuron::new();
1492 let g_small = n.rect_conductance(5.0); let g_large = n.rect_conductance(60.0); assert!(
1495 g_small > g_large,
1496 "Rectification must reduce g at large Vj: g(5)={g_small:.3} vs g(60)={g_large:.3}"
1497 );
1498 assert!(
1499 g_large >= n.rect_gmin,
1500 "Conductance must not drop below g_min={}: got {g_large:.3}",
1501 n.rect_gmin
1502 );
1503 }
1504
1505 #[test]
1506 fn gap_performance_100k_steps() {
1507 let start = std::time::Instant::now();
1508 let mut n = GapJunctionNeuron::new();
1509 for _ in 0..100_000 {
1510 std::hint::black_box(n.step(-20.0));
1511 }
1512 let elapsed = start.elapsed();
1513 assert!(
1514 elapsed.as_millis() < 50,
1515 "100k steps must complete in <50ms"
1516 );
1517 }
1518
1519 #[test]
1522 fn fh_fires_with_input() {
1523 let mut n = FrankenhaeUserHuxleyAxon::new();
1525 let mut spikes = 0;
1526 for _ in 0..2_000 {
1527 spikes += n.step(2000.0);
1528 }
1529 assert!(
1530 spikes > 0,
1531 "FH axon must fire with strong input, got {spikes}"
1532 );
1533 }
1534
1535 #[test]
1536 fn fh_silent_without_input() {
1537 let mut n = FrankenhaeUserHuxleyAxon::new();
1538 let mut spikes = 0;
1539 for _ in 0..5_000 {
1540 spikes += n.step(0.0);
1541 }
1542 assert_eq!(
1543 spikes, 0,
1544 "FH axon must be silent without input, got {spikes}"
1545 );
1546 }
1547
1548 #[test]
1549 fn fh_action_potential_shape() {
1550 let mut n = FrankenhaeUserHuxleyAxon::new();
1552 let mut v_max = -100.0_f64;
1553 for _ in 0..500 {
1554 n.step(2000.0);
1555 v_max = v_max.max(n.v);
1556 }
1557 assert!(v_max > 40.0, "AP peak should exceed 40 mV, got {v_max:.1}");
1558 }
1559
1560 #[test]
1561 fn fh_gating_evolves() {
1562 let mut n = FrankenhaeUserHuxleyAxon::new();
1563 let m0 = n.m;
1564 let h0 = n.h;
1565 for _ in 0..100 {
1566 n.step(2000.0);
1567 }
1568 assert!(n.m != m0 || n.h != h0, "Gating variables must evolve");
1569 }
1570
1571 #[test]
1572 fn fh_four_gates() {
1573 let mut n = FrankenhaeUserHuxleyAxon::new();
1575 for _ in 0..200 {
1576 n.step(2000.0);
1577 }
1578 assert!(
1581 n.m > 0.005 || n.h < 0.8 || n.n > 0.01 || n.p > 0.01,
1582 "All gates must evolve: m={:.3}, h={:.3}, n={:.3}, p={:.3}",
1583 n.m,
1584 n.h,
1585 n.n,
1586 n.p
1587 );
1588 }
1589
1590 #[test]
1591 fn fh_stronger_input_more_spikes() {
1592 let mut weak = FrankenhaeUserHuxleyAxon::new();
1593 let mut strong = FrankenhaeUserHuxleyAxon::new();
1594 let (mut sw, mut ss) = (0, 0);
1595 for _ in 0..2_000 {
1596 sw += weak.step(1000.0);
1597 ss += strong.step(3000.0);
1598 }
1599 assert!(
1600 ss >= sw,
1601 "Stronger input → more spikes: strong={ss} vs weak={sw}"
1602 );
1603 }
1604
1605 #[test]
1606 fn fh_all_gates_bounded() {
1607 let mut n = FrankenhaeUserHuxleyAxon::new();
1608 for _ in 0..2_000 {
1609 n.step(3000.0);
1610 }
1611 assert!(n.m >= 0.0 && n.m <= 1.0, "m out of bounds: {}", n.m);
1612 assert!(n.h >= 0.0 && n.h <= 1.0, "h out of bounds: {}", n.h);
1613 assert!(n.n >= 0.0 && n.n <= 1.0, "n out of bounds: {}", n.n);
1614 assert!(n.p >= 0.0 && n.p <= 1.0, "p out of bounds: {}", n.p);
1615 }
1616
1617 #[test]
1618 fn fh_nan_input_stays_finite() {
1619 let mut n = FrankenhaeUserHuxleyAxon::new();
1620 n.step(f64::NAN);
1621 assert!(n.v.is_finite());
1622 assert!(n.m.is_finite());
1623 }
1624
1625 #[test]
1626 fn fh_reset_clears_state() {
1627 let mut n = FrankenhaeUserHuxleyAxon::new();
1628 for _ in 0..500 {
1629 n.step(2000.0);
1630 }
1631 n.reset();
1632 assert_eq!(n.v, 0.0);
1633 assert_eq!(n.m, 0.005);
1634 assert_eq!(n.h, 0.8);
1635 }
1636
1637 #[test]
1638 fn fh_performance_1k_steps() {
1639 let start = std::time::Instant::now();
1640 let mut n = FrankenhaeUserHuxleyAxon::new();
1641 for _ in 0..1_000 {
1642 std::hint::black_box(n.step(1500.0));
1643 }
1644 let elapsed = start.elapsed();
1645 assert!(
1647 elapsed.as_millis() < 100,
1648 "1k steps must complete in <100ms"
1649 );
1650 }
1651
1652 #[test]
1655 fn nor_fires_with_input() {
1656 let mut n = NodeOfRanvier::new();
1657 let mut spikes = 0;
1658 for _ in 0..2_000 {
1659 spikes += n.step(500.0);
1660 }
1661 assert!(
1662 spikes > 0,
1663 "Node of Ranvier must fire with input, got {spikes}"
1664 );
1665 }
1666
1667 #[test]
1668 fn nor_silent_without_input() {
1669 let mut n = NodeOfRanvier::new();
1670 let mut spikes = 0;
1671 for _ in 0..5_000 {
1672 spikes += n.step(0.0);
1673 }
1674 assert_eq!(spikes, 0, "Must be silent without input, got {spikes}");
1675 }
1676
1677 #[test]
1678 fn nor_high_nat_density() {
1679 let n = NodeOfRanvier::new();
1681 assert!(
1682 n.g_nat > 1000.0,
1683 "Nodal transient Na should be very high: g_nat={}",
1684 n.g_nat
1685 );
1686 }
1687
1688 #[test]
1689 fn nor_has_persistent_na() {
1690 let n = NodeOfRanvier::new();
1692 assert!(
1693 n.g_nap > 0.0,
1694 "MRG model must have persistent Na current: g_nap={}",
1695 n.g_nap
1696 );
1697 }
1698
1699 #[test]
1700 fn nor_has_kv7_slow_k() {
1701 let n = NodeOfRanvier::new();
1703 assert!(
1704 n.g_ks > 0.0,
1705 "MRG model must have slow K (Kv7): g_ks={}",
1706 n.g_ks
1707 );
1708 }
1709
1710 #[test]
1711 fn nor_persistent_na_lowers_threshold() {
1712 let mut with_nap = NodeOfRanvier::new();
1714 let mut no_nap = NodeOfRanvier::new();
1715 no_nap.g_nap = 0.0;
1716 let (mut s_with, mut s_without) = (0, 0);
1717 for _ in 0..2_000 {
1718 s_with += with_nap.step(200.0);
1719 s_without += no_nap.step(200.0);
1720 }
1721 assert!(
1722 s_with >= s_without,
1723 "Persistent Na should lower threshold: with={s_with} vs without={s_without}"
1724 );
1725 }
1726
1727 #[test]
1728 fn nor_gating_evolves() {
1729 let mut n = NodeOfRanvier::new();
1730 let m0 = n.m;
1731 let p0 = n.p;
1732 for _ in 0..100 {
1733 n.step(500.0);
1734 }
1735 assert!(
1736 n.m != m0 || n.p != p0,
1737 "Gating must evolve: m={:.3}, p={:.3}",
1738 n.m,
1739 n.p
1740 );
1741 }
1742
1743 #[test]
1744 fn nor_nan_input_stays_finite() {
1745 let mut n = NodeOfRanvier::new();
1746 n.step(f64::NAN);
1747 assert!(n.v.is_finite());
1748 assert!(n.m.is_finite());
1749 assert!(n.p.is_finite());
1750 assert!(n.s.is_finite());
1751 }
1752
1753 #[test]
1754 fn nor_reset_clears_state() {
1755 let mut n = NodeOfRanvier::new();
1756 for _ in 0..500 {
1757 n.step(500.0);
1758 }
1759 n.reset();
1760 assert_eq!(n.v, -80.0);
1761 assert_eq!(n.m, 0.01);
1762 assert_eq!(n.p, 0.01);
1763 assert_eq!(n.s, 0.05);
1764 }
1765
1766 #[test]
1767 fn nor_performance_1k_steps() {
1768 let start = std::time::Instant::now();
1769 let mut n = NodeOfRanvier::new();
1770 for _ in 0..1_000 {
1771 std::hint::black_box(n.step(500.0));
1772 }
1773 let elapsed = start.elapsed();
1774 assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
1775 }
1776
1777 #[test]
1780 fn myelin_fires_with_input() {
1781 let mut n = MyelinatedAxon::new();
1782 let mut spikes = 0;
1783 for _ in 0..2_000 {
1784 spikes += n.step(500.0);
1785 }
1786 assert!(spikes > 0, "Myelinated axon must fire, got {spikes}");
1787 }
1788
1789 #[test]
1790 fn myelin_silent_without_input() {
1791 let mut n = MyelinatedAxon::new();
1792 let mut spikes = 0;
1793 for _ in 0..5_000 {
1794 spikes += n.step(0.0);
1795 }
1796 assert_eq!(spikes, 0, "Must be silent without input, got {spikes}");
1797 }
1798
1799 #[test]
1800 fn myelin_internode_coupling() {
1801 let mut n = MyelinatedAxon::new();
1803 let v_inter_0 = n.v_inter;
1804 for _ in 0..500 {
1805 n.step(500.0);
1806 }
1807 assert!(
1808 (n.v_inter - v_inter_0).abs() > 0.001,
1809 "Internode voltage should change with node activity: v_inter={}",
1810 n.v_inter
1811 );
1812 }
1813
1814 #[test]
1815 fn myelin_has_low_capacitance() {
1816 let n = MyelinatedAxon::new();
1817 assert!(
1818 n.c_inter < 0.01,
1819 "Myelin capacitance must be very low: {}",
1820 n.c_inter
1821 );
1822 }
1823
1824 #[test]
1825 fn myelin_has_low_myelin_leak() {
1826 let n = MyelinatedAxon::new();
1827 assert!(
1828 n.g_l_myelin < 0.01,
1829 "Myelin leak must be very low: {}",
1830 n.g_l_myelin
1831 );
1832 }
1833
1834 #[test]
1835 fn myelin_has_paranodal_seal() {
1836 let n = MyelinatedAxon::new();
1837 assert!(n.g_para > 0.0, "Must have paranodal seal conductance");
1838 }
1839
1840 #[test]
1841 fn myelin_stronger_input_more_spikes() {
1842 let mut weak = MyelinatedAxon::new();
1843 let mut strong = MyelinatedAxon::new();
1844 let (mut sw, mut ss) = (0, 0);
1845 for _ in 0..2_000 {
1846 sw += weak.step(300.0);
1847 ss += strong.step(1000.0);
1848 }
1849 assert!(ss >= sw, "Stronger → more spikes: strong={ss} vs weak={sw}");
1850 }
1851
1852 #[test]
1853 fn myelin_nan_input_stays_finite() {
1854 let mut n = MyelinatedAxon::new();
1855 n.step(f64::NAN);
1856 assert!(n.v().is_finite());
1857 assert!(n.v_inter.is_finite());
1858 }
1859
1860 #[test]
1861 fn myelin_reset_clears_state() {
1862 let mut n = MyelinatedAxon::new();
1863 for _ in 0..500 {
1864 n.step(500.0);
1865 }
1866 n.reset();
1867 assert_eq!(n.v_inter, -80.0);
1868 assert_eq!(n.node.v, -80.0);
1869 }
1870
1871 #[test]
1872 fn myelin_performance_1k_steps() {
1873 let start = std::time::Instant::now();
1874 let mut n = MyelinatedAxon::new();
1875 for _ in 0..1_000 {
1876 std::hint::black_box(n.step(500.0));
1877 }
1878 let elapsed = start.elapsed();
1879 assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
1880 }
1881
1882 #[test]
1885 fn cardiac_fires_with_input() {
1886 let mut n = CardiacPurkinjeFibre::new();
1887 let mut spikes = 0;
1888 for _ in 0..2_000 {
1889 spikes += n.step(5.0);
1890 }
1891 assert!(
1892 spikes > 0,
1893 "Cardiac Purkinje must fire with input, got {spikes}"
1894 );
1895 }
1896
1897 #[test]
1898 fn cardiac_silent_without_input() {
1899 let mut n = CardiacPurkinjeFibre::new();
1901 n.g_f = 0.0; let mut spikes = 0;
1903 for _ in 0..5_000 {
1904 spikes += n.step(0.0);
1905 }
1906 assert!(
1907 spikes <= 1,
1908 "Must be essentially silent without pacemaker, got {spikes}"
1909 );
1910 }
1911
1912 #[test]
1913 fn cardiac_has_funny_current() {
1914 let n = CardiacPurkinjeFibre::new();
1916 assert!(n.g_f > 0.0, "Must have funny current (If/HCN)");
1917 }
1918
1919 #[test]
1920 fn cardiac_has_cal() {
1921 let n = CardiacPurkinjeFibre::new();
1923 assert!(n.g_cal > 0.0, "Must have L-type Ca²⁺ for plateau");
1924 }
1925
1926 #[test]
1927 fn cardiac_has_inward_rectifier() {
1928 let n = CardiacPurkinjeFibre::new();
1930 assert!(n.g_k1 > 0.0, "Must have IK1 inward rectifier");
1931 }
1932
1933 #[test]
1934 fn cardiac_six_currents() {
1935 let n = CardiacPurkinjeFibre::new();
1936 assert!(
1937 n.g_na > 0.0
1938 && n.g_cal > 0.0
1939 && n.g_kr > 0.0
1940 && n.g_k1 > 0.0
1941 && n.g_f > 0.0
1942 && n.g_l > 0.0,
1943 "Must have all 6 currents"
1944 );
1945 }
1946
1947 #[test]
1948 fn cardiac_gating_evolves() {
1949 let mut n = CardiacPurkinjeFibre::new();
1950 let d0 = n.d;
1951 let y0 = n.y;
1952 for _ in 0..200 {
1953 n.step(5.0);
1954 }
1955 assert!(n.d != d0 || n.y != y0, "Gating must evolve");
1956 }
1957
1958 #[test]
1959 fn cardiac_nan_input_stays_finite() {
1960 let mut n = CardiacPurkinjeFibre::new();
1961 n.step(f64::NAN);
1962 assert!(n.v.is_finite());
1963 }
1964
1965 #[test]
1966 fn cardiac_reset_clears_state() {
1967 let mut n = CardiacPurkinjeFibre::new();
1968 for _ in 0..500 {
1969 n.step(5.0);
1970 }
1971 n.reset();
1972 assert_eq!(n.v, -85.0);
1973 assert_eq!(n.m, 0.001);
1974 }
1975
1976 #[test]
1977 fn cardiac_performance_1k_steps() {
1978 let start = std::time::Instant::now();
1979 let mut n = CardiacPurkinjeFibre::new();
1980 for _ in 0..1_000 {
1981 std::hint::black_box(n.step(3.0));
1982 }
1983 let elapsed = start.elapsed();
1984 assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
1985 }
1986
1987 #[test]
1990 fn smooth_fires_with_input() {
1991 let mut n = SmoothMuscleCell::new();
1992 let mut spikes = 0;
1993 for _ in 0..10_000 {
1994 spikes += n.step(5.0);
1995 }
1996 assert!(
1997 spikes > 0,
1998 "Smooth muscle must produce slow waves, got {spikes}"
1999 );
2000 }
2001
2002 #[test]
2003 fn smooth_silent_without_input() {
2004 let mut n = SmoothMuscleCell::new();
2005 n.ip3 = 0.0; let mut spikes = 0;
2007 for _ in 0..5_000 {
2008 spikes += n.step(0.0);
2009 }
2010 assert!(
2011 spikes <= 1,
2012 "Should be essentially silent without drive, got {spikes}"
2013 );
2014 }
2015
2016 #[test]
2017 fn smooth_has_ip3_pathway() {
2018 let n = SmoothMuscleCell::new();
2019 assert!(n.v_ip3r > 0.0, "Must have IP3R release pathway");
2020 assert!(n.ip3 > 0.0, "Must have tonic IP3");
2021 }
2022
2023 #[test]
2024 fn smooth_has_serca() {
2025 let n = SmoothMuscleCell::new();
2026 assert!(n.v_serca > 0.0, "Must have SERCA pump");
2027 }
2028
2029 #[test]
2030 fn smooth_ca_store_exists() {
2031 let n = SmoothMuscleCell::new();
2032 assert!(n.ca_store > 0.0, "Must have ER/SR Ca²⁺ store");
2033 }
2034
2035 #[test]
2036 fn smooth_has_bk() {
2037 let n = SmoothMuscleCell::new();
2038 assert!(n.g_bk > 0.0, "Must have BK (Ca²⁺-activated K) channel");
2039 }
2040
2041 #[test]
2042 fn smooth_no_fast_na() {
2043 let n = SmoothMuscleCell::new();
2045 assert!(n.g_cal > 0.0, "Depolarisation must be Ca²⁺-dependent");
2047 }
2048
2049 #[test]
2050 fn smooth_nan_stays_finite() {
2051 let mut n = SmoothMuscleCell::new();
2052 n.step(f64::NAN);
2053 assert!(n.v.is_finite());
2054 assert!(n.ca.is_finite());
2055 }
2056
2057 #[test]
2058 fn smooth_reset_clears() {
2059 let mut n = SmoothMuscleCell::new();
2060 for _ in 0..1000 {
2061 n.step(3.0);
2062 }
2063 n.reset();
2064 assert_eq!(n.v, -60.0);
2065 assert_eq!(n.ca, 0.1);
2066 assert_eq!(n.ca_store, 100.0);
2067 }
2068
2069 #[test]
2070 fn smooth_performance_1k_steps() {
2071 let start = std::time::Instant::now();
2072 let mut n = SmoothMuscleCell::new();
2073 for _ in 0..1_000 {
2074 std::hint::black_box(n.step(2.0));
2075 }
2076 let elapsed = start.elapsed();
2077 assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
2078 }
2079
2080 #[test]
2083 fn beta_fires_with_glucose() {
2084 let mut n = EndocrineBetaCell::new();
2085 n.atp_level = 0.9; let mut spikes = 0;
2087 for _ in 0..10_000 {
2088 spikes += n.step(5.0);
2089 }
2090 assert!(
2091 spikes > 0,
2092 "Beta cell must burst with high glucose, got {spikes}"
2093 );
2094 }
2095
2096 #[test]
2097 fn beta_silent_low_glucose() {
2098 let mut n = EndocrineBetaCell::new();
2099 n.atp_level = 0.0; let mut spikes = 0;
2101 for _ in 0..5_000 {
2102 spikes += n.step(0.0);
2103 }
2104 assert_eq!(
2105 spikes, 0,
2106 "Beta cell must be silent at low glucose, got {spikes}"
2107 );
2108 }
2109
2110 #[test]
2111 fn beta_katp_glucose_dependent() {
2112 let mut low = EndocrineBetaCell::new();
2114 low.atp_level = 0.2;
2115 let mut high = EndocrineBetaCell::new();
2116 high.atp_level = 0.9;
2117 let (mut sl, mut sh) = (0, 0);
2118 for _ in 0..5_000 {
2119 sl += low.step(1.0);
2120 sh += high.step(1.0);
2121 }
2122 assert!(
2123 sh >= sl,
2124 "High glucose → more spikes: high={sh} vs low={sl}"
2125 );
2126 }
2127
2128 #[test]
2129 fn beta_has_katp() {
2130 let n = EndocrineBetaCell::new();
2131 assert!(n.g_katp > 0.0, "Must have ATP-sensitive K channel");
2132 }
2133
2134 #[test]
2135 fn beta_has_kca_for_bursting() {
2136 let n = EndocrineBetaCell::new();
2137 assert!(n.g_kca > 0.0, "Must have IK_Ca for burst termination");
2138 }
2139
2140 #[test]
2141 fn beta_ca_rises_with_spiking() {
2142 let mut n = EndocrineBetaCell::new();
2143 n.atp_level = 0.8;
2144 let ca0 = n.ca;
2145 for _ in 0..5_000 {
2146 n.step(2.0);
2147 }
2148 assert!(
2149 n.ca > ca0,
2150 "Ca²⁺ should rise during bursting: ca0={ca0}, ca={}",
2151 n.ca
2152 );
2153 }
2154
2155 #[test]
2156 fn beta_nan_stays_finite() {
2157 let mut n = EndocrineBetaCell::new();
2158 n.step(f64::NAN);
2159 assert!(n.v.is_finite());
2160 assert!(n.ca.is_finite());
2161 }
2162
2163 #[test]
2164 fn beta_reset_clears() {
2165 let mut n = EndocrineBetaCell::new();
2166 for _ in 0..1000 {
2167 n.step(2.0);
2168 }
2169 n.reset();
2170 assert_eq!(n.v, -70.0);
2171 assert_eq!(n.ca, 0.1);
2172 }
2173
2174 #[test]
2175 fn beta_no_fast_na() {
2176 let n = EndocrineBetaCell::new();
2178 assert!(n.g_cal > 0.0, "CaL must drive depolarisation");
2179 }
2180
2181 #[test]
2182 fn beta_performance_1k_steps() {
2183 let start = std::time::Instant::now();
2184 let mut n = EndocrineBetaCell::new();
2185 for _ in 0..1_000 {
2186 std::hint::black_box(n.step(2.0));
2187 }
2188 let elapsed = start.elapsed();
2189 assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
2190 }
2191}