1#[derive(Clone, Debug)]
39pub struct FrankenhaeUserHuxleyAxon {
40 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,
56 pub sub_steps: usize,
57 pub gain: f64,
58}
59
60impl Default for FrankenhaeUserHuxleyAxon {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl FrankenhaeUserHuxleyAxon {
67 pub fn new() -> Self {
68 Self {
69 v: 0.0, m: 0.005,
71 h: 0.8,
72 n: 0.01,
73 p: 0.01,
74 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,
89 }
90 }
91
92 #[inline]
106 fn ghk_current(v: f64, c_ratio: f64, v_t: f64) -> f64 {
107 if v.abs() < 0.01 {
108 c_ratio - 1.0
110 } else {
111 let u = v / v_t;
112 let exp_neg_u = (-u).exp();
113 u * (c_ratio - exp_neg_u) / (1.0 - exp_neg_u)
114 }
115 }
116
117 pub fn step(&mut self, current: f64) -> i32 {
118 let input = self.gain * current;
119 let dt_sub = self.dt / self.sub_steps as f64;
120 let v_prev = self.v;
121
122 for _ in 0..self.sub_steps {
123 let v = self.v;
124
125 let am = if (v - 22.0).abs() < 0.1 {
130 1.87
131 } else {
132 0.36 * (v - 22.0) / (1.0 - (-(v - 22.0) / 3.0).exp())
133 };
134 let bm = if (v - 13.0).abs() < 0.1 {
135 1.87
136 } else {
137 0.4 * (13.0 - v) / (1.0 - ((v - 13.0) / 20.0).exp())
138 };
139
140 let ah = if (v + 10.0).abs() < 0.1 {
142 0.08
143 } else {
144 0.1 * (-10.0 - v) / (1.0 - ((v + 10.0) / 6.0).exp())
145 };
146 let bh = 4.5 / (1.0 + ((45.0 - v) / 10.0).exp());
147
148 let an = if (v - 13.0).abs() < 0.1 {
150 0.1
151 } else {
152 0.02 * (v - 13.0) / (1.0 - (-(v - 13.0) / 10.0).exp())
153 };
154 let bn = if (v - 23.0).abs() < 0.1 {
155 0.05
156 } else {
157 0.05 * (23.0 - v) / (1.0 - ((v - 23.0) / 10.0).exp())
158 };
159
160 let ap = if (v - 21.0).abs() < 0.1 {
162 0.04
163 } else {
164 0.006 * (v - 21.0) / (1.0 - (-(v - 21.0) / 2.0).exp())
165 };
166 let bp = if (v + 4.0).abs() < 0.1 {
167 0.04
168 } else {
169 0.09 * (-4.0 - v) / (1.0 - ((v + 4.0) / 2.0).exp())
170 };
171
172 let am = am.max(0.0);
174 let bm = bm.max(0.0);
175 let ah = ah.max(0.0);
176 let bh = bh.max(0.0);
177 let an = an.max(0.0);
178 let bn = bn.max(0.0);
179 let ap = ap.max(0.0);
180 let bp = bp.max(0.0);
181
182 self.m += dt_sub * (am * (1.0 - self.m) - bm * self.m);
184 self.h += dt_sub * (ah * (1.0 - self.h) - bh * self.h);
185 self.n += dt_sub * (an * (1.0 - self.n) - bn * self.n);
186 self.p += dt_sub * (ap * (1.0 - self.p) - bp * self.p);
187
188 self.m = self.m.clamp(0.0, 1.0);
190 self.h = self.h.clamp(0.0, 1.0);
191 self.n = self.n.clamp(0.0, 1.0);
192 self.p = self.p.clamp(0.0, 1.0);
193
194 let i_na = self.p_na
196 * self.m
197 * self.m
198 * self.h
199 * Self::ghk_current(v, self.na_ratio, self.v_t);
200 let i_k = self.p_k * self.n * self.n * Self::ghk_current(v, self.k_ratio, self.v_t);
201 let i_p = self.p_p * self.p * self.p * Self::ghk_current(v, self.na_ratio, self.v_t);
203 let i_l = self.g_l * (self.v - self.e_l);
204
205 let dv = (-(i_na + i_k + i_p + i_l) + input) / self.c_m;
206 self.v += dt_sub * dv;
207 }
208
209 self.v = self.v.clamp(-50.0, 150.0);
211 if !self.v.is_finite() {
212 self.v = 0.0;
213 }
214 if !self.m.is_finite() {
215 self.m = 0.005;
216 }
217 if !self.h.is_finite() {
218 self.h = 0.8;
219 }
220 if !self.n.is_finite() {
221 self.n = 0.01;
222 }
223 if !self.p.is_finite() {
224 self.p = 0.01;
225 }
226
227 if self.v >= 40.0 && v_prev < 40.0 {
229 1
230 } else {
231 0
232 }
233 }
234
235 pub fn reset(&mut self) {
236 *self = Self::new();
237 }
238}
239
240#[derive(Clone, Debug)]
266pub struct NodeOfRanvier {
267 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,
282 pub gain: f64,
283}
284
285impl Default for NodeOfRanvier {
286 fn default() -> Self {
287 Self::new()
288 }
289}
290
291impl NodeOfRanvier {
292 pub fn new() -> Self {
293 Self {
294 v: -80.0,
295 m: 0.01,
296 h: 0.75,
297 p: 0.01,
298 s: 0.05,
299 c_m: 2.0, g_nat: 3000.0, g_nap: 5.0, g_ks: 80.0, g_l: 7.0, e_na: 50.0,
305 e_k: -90.0,
306 e_l: -90.0, dt: 0.5, sub_steps: 20, gain: 1.0,
310 }
311 }
312
313 #[inline]
315 fn boltz(v: f64, v_half: f64, k: f64) -> f64 {
316 1.0 / (1.0 + (-(v - v_half) / k).exp())
317 }
318
319 pub fn step(&mut self, current: f64) -> i32 {
320 let input = self.gain * current;
321 let dt_sub = self.dt / self.sub_steps as f64;
322 let v_prev_ext = self.v;
323
324 for _ in 0..self.sub_steps {
325 let v = self.v;
326
327 let m_inf = Self::boltz(v, -26.8, 9.2);
330 let tau_m = 0.025 + 0.14 / (1.0 + ((v + 25.0) / 10.0).powi(2)).max(0.01);
331 self.m += dt_sub * (m_inf - self.m) / tau_m;
332
333 let h_inf = Self::boltz(v, -55.2, -7.4);
336 let tau_h = 0.6 + 4.0 / (1.0 + ((v + 45.0) / 10.0).powi(2)).max(0.01);
337 self.h += dt_sub * (h_inf - self.h) / tau_h;
338
339 let p_inf = Self::boltz(v, -44.0, 5.0);
342 let tau_p = 1.0 + 6.0 / (1.0 + ((v + 40.0) / 10.0).powi(2)).max(0.01);
343 self.p += dt_sub * (p_inf - self.p) / tau_p;
344
345 let s_inf = Self::boltz(v, -30.0, 10.0);
348 let tau_s = 20.0 + 60.0 / (1.0 + ((v + 30.0) / 15.0).powi(2)).max(0.01);
349 self.s += dt_sub * (s_inf - self.s) / tau_s;
350
351 self.m = self.m.clamp(0.0, 1.0);
353 self.h = self.h.clamp(0.0, 1.0);
354 self.p = self.p.clamp(0.0, 1.0);
355 self.s = self.s.clamp(0.0, 1.0);
356
357 let i_nat = self.g_nat * self.m.powi(3) * self.h * (v - self.e_na);
359 let i_nap = self.g_nap * self.p.powi(3) * (v - self.e_na);
360 let i_ks = self.g_ks * self.s * (v - self.e_k);
361 let i_l = self.g_l * (v - self.e_l);
362
363 let dv = (-(i_nat + i_nap + i_ks + i_l) + input) / self.c_m;
364 self.v += dt_sub * dv;
365 }
366
367 self.v = self.v.clamp(-120.0, 60.0);
369 if !self.v.is_finite() {
370 self.v = -80.0;
371 }
372 if !self.m.is_finite() {
373 self.m = 0.01;
374 }
375 if !self.h.is_finite() {
376 self.h = 0.75;
377 }
378 if !self.p.is_finite() {
379 self.p = 0.01;
380 }
381 if !self.s.is_finite() {
382 self.s = 0.05;
383 }
384
385 if self.v >= -10.0 && v_prev_ext < -10.0 {
387 1
388 } else {
389 0
390 }
391 }
392
393 pub fn reset(&mut self) {
394 *self = Self::new();
395 }
396}
397
398#[derive(Clone, Debug)]
428pub struct MyelinatedAxon {
429 pub node: NodeOfRanvier,
431 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,
438 pub gain: f64,
439}
440
441impl Default for MyelinatedAxon {
442 fn default() -> Self {
443 Self::new()
444 }
445}
446
447impl MyelinatedAxon {
448 pub fn new() -> Self {
449 Self {
450 node: NodeOfRanvier::new(),
451 v_inter: -80.0,
452 c_inter: 0.001, g_l_myelin: 0.001, e_l_myelin: -80.0,
455 g_para: 0.01, dt: 0.5,
457 gain: 1.0,
458 }
459 }
460
461 pub fn step(&mut self, current: f64) -> i32 {
462 let input = self.gain * current;
463
464 let i_para_to_node = self.g_para * (self.v_inter - self.node.v);
466 let i_para_to_inter = self.g_para * (self.node.v - self.v_inter);
467
468 let dv_inter =
470 (-self.g_l_myelin * (self.v_inter - self.e_l_myelin) + i_para_to_inter) / self.c_inter;
471 self.v_inter += self.node.dt / self.node.sub_steps as f64 * dv_inter;
472
473 self.v_inter = self.v_inter.clamp(-120.0, 60.0);
475 if !self.v_inter.is_finite() {
476 self.v_inter = -80.0;
477 }
478
479 let total_input = input + i_para_to_node * 100.0; self.node.step(total_input)
483 }
484
485 pub fn v(&self) -> f64 {
487 self.node.v
488 }
489
490 pub fn reset(&mut self) {
491 self.node.reset();
492 self.v_inter = -80.0;
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499
500 #[test]
503 fn fh_fires_with_input() {
504 let mut n = FrankenhaeUserHuxleyAxon::new();
506 let mut spikes = 0;
507 for _ in 0..2_000 {
508 spikes += n.step(2000.0);
509 }
510 assert!(
511 spikes > 0,
512 "FH axon must fire with strong input, got {spikes}"
513 );
514 }
515
516 #[test]
517 fn fh_silent_without_input() {
518 let mut n = FrankenhaeUserHuxleyAxon::new();
519 let mut spikes = 0;
520 for _ in 0..5_000 {
521 spikes += n.step(0.0);
522 }
523 assert_eq!(
524 spikes, 0,
525 "FH axon must be silent without input, got {spikes}"
526 );
527 }
528
529 #[test]
530 fn fh_action_potential_shape() {
531 let mut n = FrankenhaeUserHuxleyAxon::new();
533 let mut v_max = -100.0_f64;
534 for _ in 0..500 {
535 n.step(2000.0);
536 v_max = v_max.max(n.v);
537 }
538 assert!(v_max > 40.0, "AP peak should exceed 40 mV, got {v_max:.1}");
539 }
540
541 #[test]
542 fn fh_gating_evolves() {
543 let mut n = FrankenhaeUserHuxleyAxon::new();
544 let m0 = n.m;
545 let h0 = n.h;
546 for _ in 0..100 {
547 n.step(2000.0);
548 }
549 assert!(n.m != m0 || n.h != h0, "Gating variables must evolve");
550 }
551
552 #[test]
553 fn fh_four_gates() {
554 let mut n = FrankenhaeUserHuxleyAxon::new();
556 for _ in 0..200 {
557 n.step(2000.0);
558 }
559 assert!(
562 n.m > 0.005 || n.h < 0.8 || n.n > 0.01 || n.p > 0.01,
563 "All gates must evolve: m={:.3}, h={:.3}, n={:.3}, p={:.3}",
564 n.m,
565 n.h,
566 n.n,
567 n.p
568 );
569 }
570
571 #[test]
572 fn fh_stronger_input_more_spikes() {
573 let mut weak = FrankenhaeUserHuxleyAxon::new();
574 let mut strong = FrankenhaeUserHuxleyAxon::new();
575 let (mut sw, mut ss) = (0, 0);
576 for _ in 0..2_000 {
577 sw += weak.step(1000.0);
578 ss += strong.step(3000.0);
579 }
580 assert!(
581 ss >= sw,
582 "Stronger input → more spikes: strong={ss} vs weak={sw}"
583 );
584 }
585
586 #[test]
587 fn fh_all_gates_bounded() {
588 let mut n = FrankenhaeUserHuxleyAxon::new();
589 for _ in 0..2_000 {
590 n.step(3000.0);
591 }
592 assert!(n.m >= 0.0 && n.m <= 1.0, "m out of bounds: {}", n.m);
593 assert!(n.h >= 0.0 && n.h <= 1.0, "h out of bounds: {}", n.h);
594 assert!(n.n >= 0.0 && n.n <= 1.0, "n out of bounds: {}", n.n);
595 assert!(n.p >= 0.0 && n.p <= 1.0, "p out of bounds: {}", n.p);
596 }
597
598 #[test]
599 fn fh_nan_input_stays_finite() {
600 let mut n = FrankenhaeUserHuxleyAxon::new();
601 n.step(f64::NAN);
602 assert!(n.v.is_finite());
603 assert!(n.m.is_finite());
604 }
605
606 #[test]
607 fn fh_reset_clears_state() {
608 let mut n = FrankenhaeUserHuxleyAxon::new();
609 for _ in 0..500 {
610 n.step(2000.0);
611 }
612 n.reset();
613 assert_eq!(n.v, 0.0);
614 assert_eq!(n.m, 0.005);
615 assert_eq!(n.h, 0.8);
616 }
617
618 #[test]
619 fn fh_performance_1k_steps() {
620 let start = std::time::Instant::now();
621 let mut n = FrankenhaeUserHuxleyAxon::new();
622 for _ in 0..1_000 {
623 std::hint::black_box(n.step(1500.0));
624 }
625 let elapsed = start.elapsed();
626 assert!(
628 elapsed.as_millis() < 100,
629 "1k steps must complete in <100ms"
630 );
631 }
632
633 #[test]
636 fn nor_fires_with_input() {
637 let mut n = NodeOfRanvier::new();
638 let mut spikes = 0;
639 for _ in 0..2_000 {
640 spikes += n.step(500.0);
641 }
642 assert!(
643 spikes > 0,
644 "Node of Ranvier must fire with input, got {spikes}"
645 );
646 }
647
648 #[test]
649 fn nor_silent_without_input() {
650 let mut n = NodeOfRanvier::new();
651 let mut spikes = 0;
652 for _ in 0..5_000 {
653 spikes += n.step(0.0);
654 }
655 assert_eq!(spikes, 0, "Must be silent without input, got {spikes}");
656 }
657
658 #[test]
659 fn nor_high_nat_density() {
660 let n = NodeOfRanvier::new();
662 assert!(
663 n.g_nat > 1000.0,
664 "Nodal transient Na should be very high: g_nat={}",
665 n.g_nat
666 );
667 }
668
669 #[test]
670 fn nor_has_persistent_na() {
671 let n = NodeOfRanvier::new();
673 assert!(
674 n.g_nap > 0.0,
675 "MRG model must have persistent Na current: g_nap={}",
676 n.g_nap
677 );
678 }
679
680 #[test]
681 fn nor_has_kv7_slow_k() {
682 let n = NodeOfRanvier::new();
684 assert!(
685 n.g_ks > 0.0,
686 "MRG model must have slow K (Kv7): g_ks={}",
687 n.g_ks
688 );
689 }
690
691 #[test]
692 fn nor_persistent_na_lowers_threshold() {
693 let mut with_nap = NodeOfRanvier::new();
695 let mut no_nap = NodeOfRanvier::new();
696 no_nap.g_nap = 0.0;
697 let (mut s_with, mut s_without) = (0, 0);
698 for _ in 0..2_000 {
699 s_with += with_nap.step(200.0);
700 s_without += no_nap.step(200.0);
701 }
702 assert!(
703 s_with >= s_without,
704 "Persistent Na should lower threshold: with={s_with} vs without={s_without}"
705 );
706 }
707
708 #[test]
709 fn nor_gating_evolves() {
710 let mut n = NodeOfRanvier::new();
711 let m0 = n.m;
712 let p0 = n.p;
713 for _ in 0..100 {
714 n.step(500.0);
715 }
716 assert!(
717 n.m != m0 || n.p != p0,
718 "Gating must evolve: m={:.3}, p={:.3}",
719 n.m,
720 n.p
721 );
722 }
723
724 #[test]
725 fn nor_nan_input_stays_finite() {
726 let mut n = NodeOfRanvier::new();
727 n.step(f64::NAN);
728 assert!(n.v.is_finite());
729 assert!(n.m.is_finite());
730 assert!(n.p.is_finite());
731 assert!(n.s.is_finite());
732 }
733
734 #[test]
735 fn nor_reset_clears_state() {
736 let mut n = NodeOfRanvier::new();
737 for _ in 0..500 {
738 n.step(500.0);
739 }
740 n.reset();
741 assert_eq!(n.v, -80.0);
742 assert_eq!(n.m, 0.01);
743 assert_eq!(n.p, 0.01);
744 assert_eq!(n.s, 0.05);
745 }
746
747 #[test]
748 fn nor_performance_1k_steps() {
749 let start = std::time::Instant::now();
750 let mut n = NodeOfRanvier::new();
751 for _ in 0..1_000 {
752 std::hint::black_box(n.step(500.0));
753 }
754 let elapsed = start.elapsed();
755 assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
756 }
757
758 #[test]
761 fn myelin_fires_with_input() {
762 let mut n = MyelinatedAxon::new();
763 let mut spikes = 0;
764 for _ in 0..2_000 {
765 spikes += n.step(500.0);
766 }
767 assert!(spikes > 0, "Myelinated axon must fire, got {spikes}");
768 }
769
770 #[test]
771 fn myelin_silent_without_input() {
772 let mut n = MyelinatedAxon::new();
773 let mut spikes = 0;
774 for _ in 0..5_000 {
775 spikes += n.step(0.0);
776 }
777 assert_eq!(spikes, 0, "Must be silent without input, got {spikes}");
778 }
779
780 #[test]
781 fn myelin_internode_coupling() {
782 let mut n = MyelinatedAxon::new();
784 let v_inter_0 = n.v_inter;
785 for _ in 0..500 {
786 n.step(500.0);
787 }
788 assert!(
789 (n.v_inter - v_inter_0).abs() > 0.001,
790 "Internode voltage should change with node activity: v_inter={}",
791 n.v_inter
792 );
793 }
794
795 #[test]
796 fn myelin_has_low_capacitance() {
797 let n = MyelinatedAxon::new();
798 assert!(
799 n.c_inter < 0.01,
800 "Myelin capacitance must be very low: {}",
801 n.c_inter
802 );
803 }
804
805 #[test]
806 fn myelin_has_low_myelin_leak() {
807 let n = MyelinatedAxon::new();
808 assert!(
809 n.g_l_myelin < 0.01,
810 "Myelin leak must be very low: {}",
811 n.g_l_myelin
812 );
813 }
814
815 #[test]
816 fn myelin_has_paranodal_seal() {
817 let n = MyelinatedAxon::new();
818 assert!(n.g_para > 0.0, "Must have paranodal seal conductance");
819 }
820
821 #[test]
822 fn myelin_stronger_input_more_spikes() {
823 let mut weak = MyelinatedAxon::new();
824 let mut strong = MyelinatedAxon::new();
825 let (mut sw, mut ss) = (0, 0);
826 for _ in 0..2_000 {
827 sw += weak.step(300.0);
828 ss += strong.step(1000.0);
829 }
830 assert!(ss >= sw, "Stronger → more spikes: strong={ss} vs weak={sw}");
831 }
832
833 #[test]
834 fn myelin_nan_input_stays_finite() {
835 let mut n = MyelinatedAxon::new();
836 n.step(f64::NAN);
837 assert!(n.v().is_finite());
838 assert!(n.v_inter.is_finite());
839 }
840
841 #[test]
842 fn myelin_reset_clears_state() {
843 let mut n = MyelinatedAxon::new();
844 for _ in 0..500 {
845 n.step(500.0);
846 }
847 n.reset();
848 assert_eq!(n.v_inter, -80.0);
849 assert_eq!(n.node.v, -80.0);
850 }
851
852 #[test]
853 fn myelin_performance_1k_steps() {
854 let start = std::time::Instant::now();
855 let mut n = MyelinatedAxon::new();
856 for _ in 0..1_000 {
857 std::hint::black_box(n.step(500.0));
858 }
859 let elapsed = start.elapsed();
860 assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
861 }
862
863 #[test]
864 fn myelinated_family_defaults_match_constructors() {
865 let fh_default = FrankenhaeUserHuxleyAxon::default();
866 let fh_constructed = FrankenhaeUserHuxleyAxon::new();
867 assert_eq!(fh_default.v, fh_constructed.v);
868 assert_eq!(fh_default.sub_steps, fh_constructed.sub_steps);
869
870 let node_default = NodeOfRanvier::default();
871 let node_constructed = NodeOfRanvier::new();
872 assert_eq!(node_default.v, node_constructed.v);
873 assert_eq!(node_default.g_nat, node_constructed.g_nat);
874
875 let axon_default = MyelinatedAxon::default();
876 let axon_constructed = MyelinatedAxon::new();
877 assert_eq!(axon_default.v_inter, axon_constructed.v_inter);
878 assert_eq!(axon_default.g_para, axon_constructed.g_para);
879 }
880
881 #[test]
882 fn fh_rate_singularities_use_finite_limits() {
883 for v in [22.0, 13.0, -10.0, 23.0, 21.0, -4.0] {
884 let mut n = FrankenhaeUserHuxleyAxon::new();
885 n.v = v;
886 n.dt = 0.0;
887 n.sub_steps = 1;
888 n.step(0.0);
889 assert!(n.v.is_finite(), "voltage must remain finite at V={v}");
890 assert!(n.m.is_finite(), "m gate must remain finite at V={v}");
891 assert!(n.h.is_finite(), "h gate must remain finite at V={v}");
892 assert!(n.n.is_finite(), "n gate must remain finite at V={v}");
893 assert!(n.p.is_finite(), "p gate must remain finite at V={v}");
894 }
895 }
896
897 #[test]
898 fn fh_nonfinite_gates_recover_to_initial_state() {
899 let mut n = FrankenhaeUserHuxleyAxon::new();
900 n.m = f64::NAN;
901 n.h = f64::NAN;
902 n.n = f64::NAN;
903 n.p = f64::NAN;
904 n.dt = 0.0;
905 n.sub_steps = 1;
906 n.step(0.0);
907 assert_eq!(n.m, 0.005);
908 assert_eq!(n.h, 0.8);
909 assert_eq!(n.n, 0.01);
910 assert_eq!(n.p, 0.01);
911 }
912
913 #[test]
914 fn myelin_nonfinite_internode_recovers_to_rest() {
915 let mut n = MyelinatedAxon::new();
916 n.v_inter = f64::NAN;
917 n.step(0.0);
918 assert_eq!(n.v_inter, -80.0);
919 assert!(n.node.v.is_finite());
920 }
921}