#[derive(Clone, Debug)]
pub struct MontbrioMeanField {
pub r: f64, pub v: f64, pub delta: f64, pub eta: f64, pub tau: f64, pub j: f64, pub dt: f64,
pub r_threshold: f64,
pub gain: f64,
}
impl Default for MontbrioMeanField {
fn default() -> Self {
Self::new()
}
}
impl MontbrioMeanField {
pub fn new() -> Self {
Self {
r: 0.01,
v: -2.0,
delta: 1.0,
eta: -5.0, tau: 1.0,
j: 15.0, dt: 0.01, r_threshold: 0.5,
gain: 1.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let r_prev = self.r;
let pi = std::f64::consts::PI;
let tau = self.tau;
let dr = (self.delta / (pi * tau * tau)) + (2.0 * self.r * self.v / tau);
let dv = (self.v * self.v + self.eta + input + self.j * tau * self.r
- (pi * tau * self.r).powi(2))
/ tau;
self.r += self.dt * dr;
self.v += self.dt * dv;
self.r = self.r.clamp(0.0, 100.0);
self.v = self.v.clamp(-50.0, 50.0);
if !self.r.is_finite() {
self.r = 0.01;
}
if !self.v.is_finite() {
self.v = -2.0;
}
if self.r >= self.r_threshold && r_prev < self.r_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[derive(Clone, Debug)]
pub struct BrunelNetwork {
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,
pub r_threshold: f64, pub gain: f64,
}
impl Default for BrunelNetwork {
fn default() -> Self {
Self::new()
}
}
impl BrunelNetwork {
pub fn new() -> Self {
Self {
r_e: 0.1,
r_i: 0.1,
tau_e: 20.0,
tau_i: 10.0,
j_ee: 0.2,
j_ei: 0.8, j_ie: 0.5,
j_ii: 0.2,
threshold: 0.0,
gain_phi: 1.0,
dt: 0.1,
r_threshold: 1.0,
gain: 1.0,
}
}
fn phi(&self, x: f64) -> f64 {
if x > self.threshold {
self.gain_phi * (x - self.threshold)
} else {
0.0
}
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let r_e_prev = self.r_e;
let drive_e = self.j_ee * self.r_e - self.j_ei * self.r_i + input;
let drive_i = self.j_ie * self.r_e - self.j_ii * self.r_i;
let dr_e = (-self.r_e + self.phi(drive_e)) / self.tau_e;
let dr_i = (-self.r_i + self.phi(drive_i)) / self.tau_i;
self.r_e += self.dt * dr_e;
self.r_i += self.dt * dr_i;
if self.r_e < 0.0 {
self.r_e = 0.0;
}
if self.r_i < 0.0 {
self.r_i = 0.0;
}
if self.r_e > 200.0 {
self.r_e = 200.0;
}
if self.r_i > 200.0 {
self.r_i = 200.0;
}
if !self.r_e.is_finite() {
self.r_e = 0.1;
}
if !self.r_i.is_finite() {
self.r_i = 0.1;
}
if self.r_e >= self.r_threshold && r_e_prev < self.r_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[derive(Clone, Debug)]
pub struct TUMNetwork {
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,
pub gain_phi: f64,
pub dt: f64,
pub r_threshold: f64,
pub gain: f64,
}
impl Default for TUMNetwork {
fn default() -> Self {
Self::new()
}
}
impl TUMNetwork {
pub fn new() -> Self {
Self {
r: 0.1,
x: 1.0, u: 0.2, j: 5.0,
u_base: 0.2,
tau: 10.0,
tau_d: 200.0, tau_f: 50.0, threshold: 0.0,
gain_phi: 1.0,
dt: 0.1,
r_threshold: 1.0,
gain: 1.0,
}
}
fn phi(&self, x: f64) -> f64 {
if x > self.threshold {
self.gain_phi * (x - self.threshold)
} else {
0.0
}
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let r_prev = self.r;
let dx = (1.0 - self.x) / self.tau_d - self.u * self.x * self.r;
let du = (self.u_base - self.u) / self.tau_f + self.u_base * (1.0 - self.u) * self.r;
self.x += self.dt * dx;
self.u += self.dt * du;
let effective_j = self.u * self.x * self.j;
let dr = (-self.r + self.phi(effective_j * self.r + input)) / self.tau;
self.r += self.dt * dr;
self.r = self.r.clamp(0.0, 200.0);
self.x = self.x.clamp(0.0, 1.0);
self.u = self.u.clamp(0.0, 1.0);
if !self.r.is_finite() {
self.r = 0.1;
}
if !self.x.is_finite() {
self.x = 1.0;
}
if !self.u.is_finite() {
self.u = 0.2;
}
if self.r >= self.r_threshold && r_prev < self.r_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[derive(Clone, Debug)]
pub struct ElBoustaniNetwork {
pub r_e: f64,
pub r_i: f64,
pub s: f64, pub tau_e: f64,
pub tau_i: f64,
pub tau_s: f64, pub j_ampa: f64, pub j_nmda: f64, pub j_ei: f64,
pub j_ie: f64,
pub j_ii: f64,
pub gamma: f64, pub threshold: f64,
pub gain_phi: f64,
pub dt: f64,
pub r_threshold: f64,
pub gain: f64,
}
impl Default for ElBoustaniNetwork {
fn default() -> Self {
Self::new()
}
}
impl ElBoustaniNetwork {
pub fn new() -> Self {
Self {
r_e: 0.1,
r_i: 0.1,
s: 0.0,
tau_e: 20.0,
tau_i: 10.0,
tau_s: 100.0,
j_ampa: 0.1,
j_nmda: 0.5,
j_ei: 0.8,
j_ie: 0.5,
j_ii: 0.2,
gamma: 0.641, threshold: 0.0,
gain_phi: 1.0,
dt: 0.1,
r_threshold: 1.0,
gain: 1.0,
}
}
fn phi(&self, x: f64) -> f64 {
if x > self.threshold {
self.gain_phi * (x - self.threshold)
} else {
0.0
}
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let r_e_prev = self.r_e;
let ds = (-self.s + self.gamma * self.r_e * (1.0 - self.s)) / self.tau_s;
self.s += self.dt * ds;
let drive_e = self.j_ampa * self.r_e + self.j_nmda * self.s - self.j_ei * self.r_i + input;
let drive_i = self.j_ie * self.r_e - self.j_ii * self.r_i;
let dr_e = (-self.r_e + self.phi(drive_e)) / self.tau_e;
let dr_i = (-self.r_i + self.phi(drive_i)) / self.tau_i;
self.r_e += self.dt * dr_e;
self.r_i += self.dt * dr_i;
self.r_e = self.r_e.clamp(0.0, 200.0);
self.r_i = self.r_i.clamp(0.0, 200.0);
self.s = self.s.clamp(0.0, 1.0);
if !self.r_e.is_finite() {
self.r_e = 0.1;
}
if !self.r_i.is_finite() {
self.r_i = 0.1;
}
if !self.s.is_finite() {
self.s = 0.0;
}
if self.r_e >= self.r_threshold && r_e_prev < self.r_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mpr_fires_with_input() {
let mut n = MontbrioMeanField::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(10.0);
}
assert!(
spikes > 0,
"MPR must produce bursts with strong input, got {spikes}"
);
}
#[test]
fn mpr_silent_without_input() {
let mut n = MontbrioMeanField::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(0.0);
}
assert_eq!(
spikes, 0,
"MPR must be quiescent without input (eta<0), got {spikes}"
);
}
#[test]
fn mpr_rate_increases_with_input() {
let mut low = MontbrioMeanField::new();
let mut high = MontbrioMeanField::new();
for _ in 0..10_000 {
low.step(3.0);
high.step(15.0);
}
assert!(
high.r > low.r,
"Higher input → higher rate: high={:.3} vs low={:.3}",
high.r,
low.r
);
}
#[test]
fn mpr_two_ode_dynamics() {
let mut n = MontbrioMeanField::new();
let r0 = n.r;
let v0 = n.v;
for _ in 0..1000 {
n.step(5.0);
}
assert!(
n.r != r0 || n.v != v0,
"State must evolve from initial conditions"
);
}
#[test]
fn mpr_rate_non_negative() {
let mut n = MontbrioMeanField::new();
for _ in 0..50_000 {
n.step(-10.0);
}
assert!(n.r >= 0.0, "Rate must be non-negative, r={}", n.r);
}
#[test]
fn mpr_negative_input_no_crash() {
let mut n = MontbrioMeanField::new();
for _ in 0..50_000 {
n.step(-100.0);
}
assert!(n.r.is_finite());
assert!(n.v.is_finite());
}
#[test]
fn mpr_nan_input_stays_finite() {
let mut n = MontbrioMeanField::new();
n.step(f64::NAN);
assert!(n.r.is_finite());
assert!(n.v.is_finite());
}
#[test]
fn mpr_extreme_input_bounded() {
let mut n = MontbrioMeanField::new();
for _ in 0..10_000 {
n.step(1e6);
}
assert!(n.r.is_finite() && n.r <= 100.0);
assert!(n.v.is_finite() && n.v <= 50.0);
}
#[test]
fn mpr_reset_clears_state() {
let mut n = MontbrioMeanField::new();
for _ in 0..10_000 {
n.step(10.0);
}
n.reset();
assert_eq!(n.r, 0.01);
assert_eq!(n.v, -2.0);
}
#[test]
fn mpr_performance_100k_steps() {
let start = std::time::Instant::now();
let mut n = MontbrioMeanField::new();
for _ in 0..100_000 {
std::hint::black_box(n.step(5.0));
}
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 50,
"100k steps must complete in <50ms"
);
}
#[test]
fn brunel_fires_with_input() {
let mut n = BrunelNetwork::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(5.0);
}
assert!(
spikes > 0,
"Brunel must produce bursts with input, got {spikes}"
);
}
#[test]
fn brunel_silent_without_input() {
let mut n = BrunelNetwork::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(0.0);
}
assert_eq!(
spikes, 0,
"Brunel must be quiescent without input, got {spikes}"
);
}
#[test]
fn brunel_ei_balance() {
let mut n = BrunelNetwork::new();
for _ in 0..50_000 {
n.step(3.0);
}
assert!(
n.r_e < 50.0,
"E/I balance should keep r_e bounded, r_e={}",
n.r_e
);
assert!(n.r_i >= 0.0, "r_i must be non-negative");
}
#[test]
fn brunel_inhibition_suppresses() {
let mut weak_inh = BrunelNetwork::new();
weak_inh.j_ei = 0.3;
let mut strong_inh = BrunelNetwork::new();
strong_inh.j_ei = 2.0;
for _ in 0..20_000 {
weak_inh.step(5.0);
strong_inh.step(5.0);
}
assert!(
weak_inh.r_e >= strong_inh.r_e,
"Stronger inhibition → lower E rate: weak={:.2} vs strong={:.2}",
weak_inh.r_e,
strong_inh.r_e
);
}
#[test]
fn brunel_rates_non_negative() {
let mut n = BrunelNetwork::new();
for _ in 0..50_000 {
n.step(-10.0);
}
assert!(n.r_e >= 0.0);
assert!(n.r_i >= 0.0);
}
#[test]
fn brunel_negative_input_no_crash() {
let mut n = BrunelNetwork::new();
for _ in 0..50_000 {
n.step(-100.0);
}
assert!(n.r_e.is_finite());
assert!(n.r_i.is_finite());
}
#[test]
fn brunel_nan_input_stays_finite() {
let mut n = BrunelNetwork::new();
n.step(f64::NAN);
assert!(n.r_e.is_finite());
assert!(n.r_i.is_finite());
}
#[test]
fn brunel_extreme_input_bounded() {
let mut n = BrunelNetwork::new();
for _ in 0..10_000 {
n.step(1e6);
}
assert!(n.r_e.is_finite() && n.r_e <= 200.0);
}
#[test]
fn brunel_reset_clears_state() {
let mut n = BrunelNetwork::new();
for _ in 0..10_000 {
n.step(5.0);
}
n.reset();
assert_eq!(n.r_e, 0.1);
assert_eq!(n.r_i, 0.1);
}
#[test]
fn brunel_performance_100k_steps() {
let start = std::time::Instant::now();
let mut n = BrunelNetwork::new();
for _ in 0..100_000 {
std::hint::black_box(n.step(3.0));
}
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 50,
"100k steps must complete in <50ms"
);
}
#[test]
fn tum_fires_with_input() {
let mut n = TUMNetwork::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(5.0);
}
assert!(
spikes > 0,
"TUM must produce bursts with input, got {spikes}"
);
}
#[test]
fn tum_silent_without_input() {
let mut n = TUMNetwork::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(0.0);
}
assert_eq!(
spikes, 0,
"TUM must be quiescent without input, got {spikes}"
);
}
#[test]
fn tum_depression_reduces_rate() {
let mut n = TUMNetwork::new();
for _ in 0..20_000 {
n.step(8.0);
}
let r_sustained = n.r;
let x_depleted = n.x;
assert!(
x_depleted < 0.9,
"Sustained activity should deplete resources, x={x_depleted}"
);
n.reset();
for _ in 0..500 {
n.step(8.0);
}
let r_transient = n.r;
assert!(n.x < 1.0, "Resources should start depleting");
let _ = (r_transient, r_sustained);
}
#[test]
fn tum_facilitation_builds() {
let mut n = TUMNetwork::new();
let u0 = n.u;
for _ in 0..5_000 {
n.step(5.0);
}
assert!(
n.u > u0,
"Facilitation should increase u: u0={u0}, u_now={}",
n.u
);
}
#[test]
fn tum_stp_modulates_coupling() {
let mut n = TUMNetwork::new();
let eff_0 = n.u * n.x * n.j;
for _ in 0..10_000 {
n.step(5.0);
}
let eff_1 = n.u * n.x * n.j;
assert!(
(eff_0 - eff_1).abs() > 0.01,
"STP must modulate effective coupling: eff_0={eff_0:.3}, eff_1={eff_1:.3}"
);
}
#[test]
fn tum_rate_non_negative() {
let mut n = TUMNetwork::new();
for _ in 0..50_000 {
n.step(-10.0);
}
assert!(n.r >= 0.0, "Rate must be non-negative, r={}", n.r);
}
#[test]
fn tum_nan_input_stays_finite() {
let mut n = TUMNetwork::new();
n.step(f64::NAN);
assert!(n.r.is_finite());
assert!(n.x.is_finite());
assert!(n.u.is_finite());
}
#[test]
fn tum_extreme_input_bounded() {
let mut n = TUMNetwork::new();
for _ in 0..10_000 {
n.step(1e6);
}
assert!(n.r.is_finite() && n.r <= 200.0);
assert!(n.x >= 0.0 && n.x <= 1.0);
assert!(n.u >= 0.0 && n.u <= 1.0);
}
#[test]
fn tum_reset_clears_state() {
let mut n = TUMNetwork::new();
for _ in 0..10_000 {
n.step(5.0);
}
n.reset();
assert_eq!(n.r, 0.1);
assert_eq!(n.x, 1.0);
assert_eq!(n.u, 0.2);
}
#[test]
fn tum_performance_100k_steps() {
let start = std::time::Instant::now();
let mut n = TUMNetwork::new();
for _ in 0..100_000 {
std::hint::black_box(n.step(5.0));
}
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 50,
"100k steps must complete in <50ms"
);
}
#[test]
fn elboustani_fires_with_input() {
let mut n = ElBoustaniNetwork::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(5.0);
}
assert!(
spikes > 0,
"ElBoustani must produce bursts with input, got {spikes}"
);
}
#[test]
fn elboustani_silent_without_input() {
let mut n = ElBoustaniNetwork::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(0.0);
}
assert_eq!(
spikes, 0,
"ElBoustani must be quiescent without input, got {spikes}"
);
}
#[test]
fn elboustani_nmda_builds_with_activity() {
let mut n = ElBoustaniNetwork::new();
let s0 = n.s;
for _ in 0..10_000 {
n.step(5.0);
}
assert!(
n.s > s0,
"NMDA gating should increase with activity: s0={s0}, s_now={}",
n.s
);
}
#[test]
fn elboustani_ei_balance() {
let mut n = ElBoustaniNetwork::new();
for _ in 0..50_000 {
n.step(3.0);
}
assert!(
n.r_e < 50.0,
"E/I balance should keep r_e bounded, r_e={}",
n.r_e
);
assert!(n.r_i >= 0.0, "r_i must be non-negative");
}
#[test]
fn elboustani_nmda_enhances_excitation() {
let mut with_nmda = ElBoustaniNetwork::new();
let mut no_nmda = ElBoustaniNetwork::new();
no_nmda.j_nmda = 0.0;
for _ in 0..20_000 {
with_nmda.step(3.0);
no_nmda.step(3.0);
}
assert!(
with_nmda.r_e >= no_nmda.r_e,
"NMDA should enhance excitation: with={:.3} vs without={:.3}",
with_nmda.r_e,
no_nmda.r_e
);
}
#[test]
fn elboustani_nmda_bounded() {
let mut n = ElBoustaniNetwork::new();
for _ in 0..50_000 {
n.step(10.0);
}
assert!(
n.s >= 0.0 && n.s <= 1.0,
"NMDA gating must be in [0,1], s={}",
n.s
);
}
#[test]
fn elboustani_rates_non_negative() {
let mut n = ElBoustaniNetwork::new();
for _ in 0..50_000 {
n.step(-10.0);
}
assert!(n.r_e >= 0.0);
assert!(n.r_i >= 0.0);
}
#[test]
fn elboustani_nan_input_stays_finite() {
let mut n = ElBoustaniNetwork::new();
n.step(f64::NAN);
assert!(n.r_e.is_finite());
assert!(n.r_i.is_finite());
assert!(n.s.is_finite());
}
#[test]
fn elboustani_extreme_input_bounded() {
let mut n = ElBoustaniNetwork::new();
for _ in 0..10_000 {
n.step(1e6);
}
assert!(n.r_e.is_finite() && n.r_e <= 200.0);
assert!(n.s >= 0.0 && n.s <= 1.0);
}
#[test]
fn elboustani_reset_clears_state() {
let mut n = ElBoustaniNetwork::new();
for _ in 0..10_000 {
n.step(5.0);
}
n.reset();
assert_eq!(n.r_e, 0.1);
assert_eq!(n.r_i, 0.1);
assert_eq!(n.s, 0.0);
}
}