1#[derive(Clone, Debug)]
36pub struct MontbrioMeanField {
37 pub r: f64, pub v: f64, pub delta: f64, pub eta: f64, pub tau: f64, pub j: f64, pub dt: f64,
44 pub r_threshold: f64,
45 pub gain: f64,
46}
47
48impl Default for MontbrioMeanField {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl MontbrioMeanField {
55 pub fn new() -> Self {
56 Self {
57 r: 0.01,
58 v: -2.0,
59 delta: 1.0,
60 eta: -5.0, tau: 1.0,
62 j: 15.0, dt: 0.01, r_threshold: 0.5,
65 gain: 1.0,
66 }
67 }
68
69 pub fn step(&mut self, current: f64) -> i32 {
70 let input = self.gain * current;
71 let r_prev = self.r;
72
73 let pi = std::f64::consts::PI;
74 let tau = self.tau;
75
76 let dr = (self.delta / (pi * tau * tau)) + (2.0 * self.r * self.v / tau);
78 let dv = (self.v * self.v + self.eta + input + self.j * tau * self.r
79 - (pi * tau * self.r).powi(2))
80 / tau;
81
82 self.r += self.dt * dr;
83 self.v += self.dt * dv;
84
85 self.r = self.r.clamp(0.0, 100.0);
87 self.v = self.v.clamp(-50.0, 50.0);
88 if !self.r.is_finite() {
89 self.r = 0.01;
90 }
91 if !self.v.is_finite() {
92 self.v = -2.0;
93 }
94
95 if self.r >= self.r_threshold && r_prev < self.r_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)]
127pub struct BrunelNetwork {
128 pub r_e: f64, pub r_i: f64, pub tau_e: f64, pub tau_i: f64, pub j_ee: f64, pub j_ei: f64, pub j_ie: f64, pub j_ii: f64, pub threshold: f64, pub gain_phi: f64, pub dt: f64,
139 pub r_threshold: f64, pub gain: f64,
141}
142
143impl Default for BrunelNetwork {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl BrunelNetwork {
150 pub fn new() -> Self {
151 Self {
152 r_e: 0.1,
153 r_i: 0.1,
154 tau_e: 20.0,
155 tau_i: 10.0,
156 j_ee: 0.2,
157 j_ei: 0.8, j_ie: 0.5,
159 j_ii: 0.2,
160 threshold: 0.0,
161 gain_phi: 1.0,
162 dt: 0.1,
163 r_threshold: 1.0,
164 gain: 1.0,
165 }
166 }
167
168 fn phi(&self, x: f64) -> f64 {
170 if x > self.threshold {
171 self.gain_phi * (x - self.threshold)
172 } else {
173 0.0
174 }
175 }
176
177 pub fn step(&mut self, current: f64) -> i32 {
178 let input = self.gain * current;
179 let r_e_prev = self.r_e;
180
181 let drive_e = self.j_ee * self.r_e - self.j_ei * self.r_i + input;
182 let drive_i = self.j_ie * self.r_e - self.j_ii * self.r_i;
183
184 let dr_e = (-self.r_e + self.phi(drive_e)) / self.tau_e;
185 let dr_i = (-self.r_i + self.phi(drive_i)) / self.tau_i;
186
187 self.r_e += self.dt * dr_e;
188 self.r_i += self.dt * dr_i;
189
190 if self.r_e < 0.0 {
192 self.r_e = 0.0;
193 }
194 if self.r_i < 0.0 {
195 self.r_i = 0.0;
196 }
197
198 if self.r_e > 200.0 {
200 self.r_e = 200.0;
201 }
202 if self.r_i > 200.0 {
203 self.r_i = 200.0;
204 }
205 if !self.r_e.is_finite() {
206 self.r_e = 0.1;
207 }
208 if !self.r_i.is_finite() {
209 self.r_i = 0.1;
210 }
211
212 if self.r_e >= self.r_threshold && r_e_prev < self.r_threshold {
214 1
215 } else {
216 0
217 }
218 }
219
220 pub fn reset(&mut self) {
221 *self = Self::new();
222 }
223}
224
225#[derive(Clone, Debug)]
242pub struct TUMNetwork {
243 pub r: f64, pub x: f64, pub u: f64, pub j: f64, pub u_base: f64, pub tau: f64, pub tau_d: f64, pub tau_f: f64, pub threshold: f64,
252 pub gain_phi: f64,
253 pub dt: f64,
254 pub r_threshold: f64,
255 pub gain: f64,
256}
257
258impl Default for TUMNetwork {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264impl TUMNetwork {
265 pub fn new() -> Self {
266 Self {
267 r: 0.1,
268 x: 1.0, u: 0.2, j: 5.0,
271 u_base: 0.2,
272 tau: 10.0,
273 tau_d: 200.0, tau_f: 50.0, threshold: 0.0,
276 gain_phi: 1.0,
277 dt: 0.1,
278 r_threshold: 1.0,
279 gain: 1.0,
280 }
281 }
282
283 fn phi(&self, x: f64) -> f64 {
284 if x > self.threshold {
285 self.gain_phi * (x - self.threshold)
286 } else {
287 0.0
288 }
289 }
290
291 pub fn step(&mut self, current: f64) -> i32 {
292 let input = self.gain * current;
293 let r_prev = self.r;
294
295 let dx = (1.0 - self.x) / self.tau_d - self.u * self.x * self.r;
297 let du = (self.u_base - self.u) / self.tau_f + self.u_base * (1.0 - self.u) * self.r;
298
299 self.x += self.dt * dx;
300 self.u += self.dt * du;
301
302 let effective_j = self.u * self.x * self.j;
304 let dr = (-self.r + self.phi(effective_j * self.r + input)) / self.tau;
305 self.r += self.dt * dr;
306
307 self.r = self.r.clamp(0.0, 200.0);
309 self.x = self.x.clamp(0.0, 1.0);
310 self.u = self.u.clamp(0.0, 1.0);
311 if !self.r.is_finite() {
312 self.r = 0.1;
313 }
314 if !self.x.is_finite() {
315 self.x = 1.0;
316 }
317 if !self.u.is_finite() {
318 self.u = 0.2;
319 }
320
321 if self.r >= self.r_threshold && r_prev < self.r_threshold {
322 1
323 } else {
324 0
325 }
326 }
327
328 pub fn reset(&mut self) {
329 *self = Self::new();
330 }
331}
332
333#[derive(Clone, Debug)]
349pub struct ElBoustaniNetwork {
350 pub r_e: f64,
351 pub r_i: f64,
352 pub s: f64, pub tau_e: f64,
354 pub tau_i: f64,
355 pub tau_s: f64, pub j_ampa: f64, pub j_nmda: f64, pub j_ei: f64,
359 pub j_ie: f64,
360 pub j_ii: f64,
361 pub gamma: f64, pub threshold: f64,
363 pub gain_phi: f64,
364 pub dt: f64,
365 pub r_threshold: f64,
366 pub gain: f64,
367}
368
369impl Default for ElBoustaniNetwork {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375impl ElBoustaniNetwork {
376 pub fn new() -> Self {
377 Self {
378 r_e: 0.1,
379 r_i: 0.1,
380 s: 0.0,
381 tau_e: 20.0,
382 tau_i: 10.0,
383 tau_s: 100.0,
384 j_ampa: 0.1,
385 j_nmda: 0.5,
386 j_ei: 0.8,
387 j_ie: 0.5,
388 j_ii: 0.2,
389 gamma: 0.641, threshold: 0.0,
391 gain_phi: 1.0,
392 dt: 0.1,
393 r_threshold: 1.0,
394 gain: 1.0,
395 }
396 }
397
398 fn phi(&self, x: f64) -> f64 {
399 if x > self.threshold {
400 self.gain_phi * (x - self.threshold)
401 } else {
402 0.0
403 }
404 }
405
406 pub fn step(&mut self, current: f64) -> i32 {
407 let input = self.gain * current;
408 let r_e_prev = self.r_e;
409
410 let ds = (-self.s + self.gamma * self.r_e * (1.0 - self.s)) / self.tau_s;
412 self.s += self.dt * ds;
413
414 let drive_e = self.j_ampa * self.r_e + self.j_nmda * self.s - self.j_ei * self.r_i + input;
416 let drive_i = self.j_ie * self.r_e - self.j_ii * self.r_i;
417
418 let dr_e = (-self.r_e + self.phi(drive_e)) / self.tau_e;
419 let dr_i = (-self.r_i + self.phi(drive_i)) / self.tau_i;
420
421 self.r_e += self.dt * dr_e;
422 self.r_i += self.dt * dr_i;
423
424 self.r_e = self.r_e.clamp(0.0, 200.0);
426 self.r_i = self.r_i.clamp(0.0, 200.0);
427 self.s = self.s.clamp(0.0, 1.0);
428 if !self.r_e.is_finite() {
429 self.r_e = 0.1;
430 }
431 if !self.r_i.is_finite() {
432 self.r_i = 0.1;
433 }
434 if !self.s.is_finite() {
435 self.s = 0.0;
436 }
437
438 if self.r_e >= self.r_threshold && r_e_prev < self.r_threshold {
439 1
440 } else {
441 0
442 }
443 }
444
445 pub fn reset(&mut self) {
446 *self = Self::new();
447 }
448}
449
450#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
459 fn mpr_fires_with_input() {
460 let mut n = MontbrioMeanField::new();
461 let mut spikes = 0;
462 for _ in 0..50_000 {
463 spikes += n.step(10.0);
464 }
465 assert!(
466 spikes > 0,
467 "MPR must produce bursts with strong input, got {spikes}"
468 );
469 }
470
471 #[test]
472 fn mpr_silent_without_input() {
473 let mut n = MontbrioMeanField::new();
475 let mut spikes = 0;
476 for _ in 0..50_000 {
477 spikes += n.step(0.0);
478 }
479 assert_eq!(
480 spikes, 0,
481 "MPR must be quiescent without input (eta<0), got {spikes}"
482 );
483 }
484
485 #[test]
486 fn mpr_rate_increases_with_input() {
487 let mut low = MontbrioMeanField::new();
488 let mut high = MontbrioMeanField::new();
489 for _ in 0..10_000 {
490 low.step(3.0);
491 high.step(15.0);
492 }
493 assert!(
494 high.r > low.r,
495 "Higher input → higher rate: high={:.3} vs low={:.3}",
496 high.r,
497 low.r
498 );
499 }
500
501 #[test]
502 fn mpr_two_ode_dynamics() {
503 let mut n = MontbrioMeanField::new();
505 let r0 = n.r;
506 let v0 = n.v;
507 for _ in 0..1000 {
508 n.step(5.0);
509 }
510 assert!(
511 n.r != r0 || n.v != v0,
512 "State must evolve from initial conditions"
513 );
514 }
515
516 #[test]
517 fn mpr_rate_non_negative() {
518 let mut n = MontbrioMeanField::new();
519 for _ in 0..50_000 {
520 n.step(-10.0);
521 }
522 assert!(n.r >= 0.0, "Rate must be non-negative, r={}", n.r);
523 }
524
525 #[test]
526 fn mpr_negative_input_no_crash() {
527 let mut n = MontbrioMeanField::new();
528 for _ in 0..50_000 {
529 n.step(-100.0);
530 }
531 assert!(n.r.is_finite());
532 assert!(n.v.is_finite());
533 }
534
535 #[test]
536 fn mpr_nan_input_stays_finite() {
537 let mut n = MontbrioMeanField::new();
538 n.step(f64::NAN);
539 assert!(n.r.is_finite());
540 assert!(n.v.is_finite());
541 }
542
543 #[test]
544 fn mpr_extreme_input_bounded() {
545 let mut n = MontbrioMeanField::new();
546 for _ in 0..10_000 {
547 n.step(1e6);
548 }
549 assert!(n.r.is_finite() && n.r <= 100.0);
550 assert!(n.v.is_finite() && n.v <= 50.0);
551 }
552
553 #[test]
554 fn mpr_reset_clears_state() {
555 let mut n = MontbrioMeanField::new();
556 for _ in 0..10_000 {
557 n.step(10.0);
558 }
559 n.reset();
560 assert_eq!(n.r, 0.01);
561 assert_eq!(n.v, -2.0);
562 }
563
564 #[test]
565 fn mpr_performance_100k_steps() {
566 let start = std::time::Instant::now();
567 let mut n = MontbrioMeanField::new();
568 for _ in 0..100_000 {
569 std::hint::black_box(n.step(5.0));
570 }
571 let elapsed = start.elapsed();
572 assert!(
573 elapsed.as_millis() < 50,
574 "100k steps must complete in <50ms"
575 );
576 }
577
578 #[test]
581 fn brunel_fires_with_input() {
582 let mut n = BrunelNetwork::new();
583 let mut spikes = 0;
584 for _ in 0..50_000 {
585 spikes += n.step(5.0);
586 }
587 assert!(
588 spikes > 0,
589 "Brunel must produce bursts with input, got {spikes}"
590 );
591 }
592
593 #[test]
594 fn brunel_silent_without_input() {
595 let mut n = BrunelNetwork::new();
596 let mut spikes = 0;
597 for _ in 0..50_000 {
598 spikes += n.step(0.0);
599 }
600 assert_eq!(
601 spikes, 0,
602 "Brunel must be quiescent without input, got {spikes}"
603 );
604 }
605
606 #[test]
607 fn brunel_ei_balance() {
608 let mut n = BrunelNetwork::new();
610 for _ in 0..50_000 {
611 n.step(3.0);
612 }
613 assert!(
614 n.r_e < 50.0,
615 "E/I balance should keep r_e bounded, r_e={}",
616 n.r_e
617 );
618 assert!(n.r_i >= 0.0, "r_i must be non-negative");
619 }
620
621 #[test]
622 fn brunel_inhibition_suppresses() {
623 let mut weak_inh = BrunelNetwork::new();
625 weak_inh.j_ei = 0.3;
626 let mut strong_inh = BrunelNetwork::new();
627 strong_inh.j_ei = 2.0;
628
629 for _ in 0..20_000 {
630 weak_inh.step(5.0);
631 strong_inh.step(5.0);
632 }
633 assert!(
634 weak_inh.r_e >= strong_inh.r_e,
635 "Stronger inhibition → lower E rate: weak={:.2} vs strong={:.2}",
636 weak_inh.r_e,
637 strong_inh.r_e
638 );
639 }
640
641 #[test]
642 fn brunel_rates_non_negative() {
643 let mut n = BrunelNetwork::new();
644 for _ in 0..50_000 {
645 n.step(-10.0);
646 }
647 assert!(n.r_e >= 0.0);
648 assert!(n.r_i >= 0.0);
649 }
650
651 #[test]
652 fn brunel_negative_input_no_crash() {
653 let mut n = BrunelNetwork::new();
654 for _ in 0..50_000 {
655 n.step(-100.0);
656 }
657 assert!(n.r_e.is_finite());
658 assert!(n.r_i.is_finite());
659 }
660
661 #[test]
662 fn brunel_nan_input_stays_finite() {
663 let mut n = BrunelNetwork::new();
664 n.step(f64::NAN);
665 assert!(n.r_e.is_finite());
666 assert!(n.r_i.is_finite());
667 }
668
669 #[test]
670 fn brunel_extreme_input_bounded() {
671 let mut n = BrunelNetwork::new();
672 for _ in 0..10_000 {
673 n.step(1e6);
674 }
675 assert!(n.r_e.is_finite() && n.r_e <= 200.0);
676 }
677
678 #[test]
679 fn brunel_reset_clears_state() {
680 let mut n = BrunelNetwork::new();
681 for _ in 0..10_000 {
682 n.step(5.0);
683 }
684 n.reset();
685 assert_eq!(n.r_e, 0.1);
686 assert_eq!(n.r_i, 0.1);
687 }
688
689 #[test]
690 fn brunel_performance_100k_steps() {
691 let start = std::time::Instant::now();
692 let mut n = BrunelNetwork::new();
693 for _ in 0..100_000 {
694 std::hint::black_box(n.step(3.0));
695 }
696 let elapsed = start.elapsed();
697 assert!(
698 elapsed.as_millis() < 50,
699 "100k steps must complete in <50ms"
700 );
701 }
702
703 #[test]
706 fn tum_fires_with_input() {
707 let mut n = TUMNetwork::new();
708 let mut spikes = 0;
709 for _ in 0..50_000 {
710 spikes += n.step(5.0);
711 }
712 assert!(
713 spikes > 0,
714 "TUM must produce bursts with input, got {spikes}"
715 );
716 }
717
718 #[test]
719 fn tum_silent_without_input() {
720 let mut n = TUMNetwork::new();
721 let mut spikes = 0;
722 for _ in 0..50_000 {
723 spikes += n.step(0.0);
724 }
725 assert_eq!(
726 spikes, 0,
727 "TUM must be quiescent without input, got {spikes}"
728 );
729 }
730
731 #[test]
732 fn tum_depression_reduces_rate() {
733 let mut n = TUMNetwork::new();
736 for _ in 0..20_000 {
738 n.step(8.0);
739 }
740 let r_sustained = n.r;
741 let x_depleted = n.x;
742 assert!(
743 x_depleted < 0.9,
744 "Sustained activity should deplete resources, x={x_depleted}"
745 );
746 n.reset();
748 for _ in 0..500 {
749 n.step(8.0);
750 }
751 let r_transient = n.r;
752 assert!(n.x < 1.0, "Resources should start depleting");
755 let _ = (r_transient, r_sustained);
757 }
758
759 #[test]
760 fn tum_facilitation_builds() {
761 let mut n = TUMNetwork::new();
763 let u0 = n.u;
764 for _ in 0..5_000 {
765 n.step(5.0);
766 }
767 assert!(
768 n.u > u0,
769 "Facilitation should increase u: u0={u0}, u_now={}",
770 n.u
771 );
772 }
773
774 #[test]
775 fn tum_stp_modulates_coupling() {
776 let mut n = TUMNetwork::new();
778 let eff_0 = n.u * n.x * n.j;
779 for _ in 0..10_000 {
780 n.step(5.0);
781 }
782 let eff_1 = n.u * n.x * n.j;
783 assert!(
784 (eff_0 - eff_1).abs() > 0.01,
785 "STP must modulate effective coupling: eff_0={eff_0:.3}, eff_1={eff_1:.3}"
786 );
787 }
788
789 #[test]
790 fn tum_rate_non_negative() {
791 let mut n = TUMNetwork::new();
792 for _ in 0..50_000 {
793 n.step(-10.0);
794 }
795 assert!(n.r >= 0.0, "Rate must be non-negative, r={}", n.r);
796 }
797
798 #[test]
799 fn tum_nan_input_stays_finite() {
800 let mut n = TUMNetwork::new();
801 n.step(f64::NAN);
802 assert!(n.r.is_finite());
803 assert!(n.x.is_finite());
804 assert!(n.u.is_finite());
805 }
806
807 #[test]
808 fn tum_extreme_input_bounded() {
809 let mut n = TUMNetwork::new();
810 for _ in 0..10_000 {
811 n.step(1e6);
812 }
813 assert!(n.r.is_finite() && n.r <= 200.0);
814 assert!(n.x >= 0.0 && n.x <= 1.0);
815 assert!(n.u >= 0.0 && n.u <= 1.0);
816 }
817
818 #[test]
819 fn tum_reset_clears_state() {
820 let mut n = TUMNetwork::new();
821 for _ in 0..10_000 {
822 n.step(5.0);
823 }
824 n.reset();
825 assert_eq!(n.r, 0.1);
826 assert_eq!(n.x, 1.0);
827 assert_eq!(n.u, 0.2);
828 }
829
830 #[test]
831 fn tum_performance_100k_steps() {
832 let start = std::time::Instant::now();
833 let mut n = TUMNetwork::new();
834 for _ in 0..100_000 {
835 std::hint::black_box(n.step(5.0));
836 }
837 let elapsed = start.elapsed();
838 assert!(
839 elapsed.as_millis() < 50,
840 "100k steps must complete in <50ms"
841 );
842 }
843
844 #[test]
847 fn elboustani_fires_with_input() {
848 let mut n = ElBoustaniNetwork::new();
849 let mut spikes = 0;
850 for _ in 0..50_000 {
851 spikes += n.step(5.0);
852 }
853 assert!(
854 spikes > 0,
855 "ElBoustani must produce bursts with input, got {spikes}"
856 );
857 }
858
859 #[test]
860 fn elboustani_silent_without_input() {
861 let mut n = ElBoustaniNetwork::new();
862 let mut spikes = 0;
863 for _ in 0..50_000 {
864 spikes += n.step(0.0);
865 }
866 assert_eq!(
867 spikes, 0,
868 "ElBoustani must be quiescent without input, got {spikes}"
869 );
870 }
871
872 #[test]
873 fn elboustani_nmda_builds_with_activity() {
874 let mut n = ElBoustaniNetwork::new();
876 let s0 = n.s;
877 for _ in 0..10_000 {
878 n.step(5.0);
879 }
880 assert!(
881 n.s > s0,
882 "NMDA gating should increase with activity: s0={s0}, s_now={}",
883 n.s
884 );
885 }
886
887 #[test]
888 fn elboustani_ei_balance() {
889 let mut n = ElBoustaniNetwork::new();
891 for _ in 0..50_000 {
892 n.step(3.0);
893 }
894 assert!(
895 n.r_e < 50.0,
896 "E/I balance should keep r_e bounded, r_e={}",
897 n.r_e
898 );
899 assert!(n.r_i >= 0.0, "r_i must be non-negative");
900 }
901
902 #[test]
903 fn elboustani_nmda_enhances_excitation() {
904 let mut with_nmda = ElBoustaniNetwork::new();
906 let mut no_nmda = ElBoustaniNetwork::new();
907 no_nmda.j_nmda = 0.0;
908 for _ in 0..20_000 {
909 with_nmda.step(3.0);
910 no_nmda.step(3.0);
911 }
912 assert!(
913 with_nmda.r_e >= no_nmda.r_e,
914 "NMDA should enhance excitation: with={:.3} vs without={:.3}",
915 with_nmda.r_e,
916 no_nmda.r_e
917 );
918 }
919
920 #[test]
921 fn elboustani_nmda_bounded() {
922 let mut n = ElBoustaniNetwork::new();
924 for _ in 0..50_000 {
925 n.step(10.0);
926 }
927 assert!(
928 n.s >= 0.0 && n.s <= 1.0,
929 "NMDA gating must be in [0,1], s={}",
930 n.s
931 );
932 }
933
934 #[test]
935 fn elboustani_rates_non_negative() {
936 let mut n = ElBoustaniNetwork::new();
937 for _ in 0..50_000 {
938 n.step(-10.0);
939 }
940 assert!(n.r_e >= 0.0);
941 assert!(n.r_i >= 0.0);
942 }
943
944 #[test]
945 fn elboustani_nan_input_stays_finite() {
946 let mut n = ElBoustaniNetwork::new();
947 n.step(f64::NAN);
948 assert!(n.r_e.is_finite());
949 assert!(n.r_i.is_finite());
950 assert!(n.s.is_finite());
951 }
952
953 #[test]
954 fn elboustani_extreme_input_bounded() {
955 let mut n = ElBoustaniNetwork::new();
956 for _ in 0..10_000 {
957 n.step(1e6);
958 }
959 assert!(n.r_e.is_finite() && n.r_e <= 200.0);
960 assert!(n.s >= 0.0 && n.s <= 1.0);
961 }
962
963 #[test]
964 fn elboustani_reset_clears_state() {
965 let mut n = ElBoustaniNetwork::new();
966 for _ in 0..10_000 {
967 n.step(5.0);
968 }
969 n.reset();
970 assert_eq!(n.r_e, 0.1);
971 assert_eq!(n.r_i, 0.1);
972 assert_eq!(n.s, 0.0);
973 }
974}