use rand::{RngExt, SeedableRng};
use rand_xoshiro::Xoshiro256PlusPlus;
pub fn safe_rate(
number_factor: f64,
v_offset: f64,
v: f64,
denom_scale: f64,
fallback: f64,
) -> f64 {
let d = v + v_offset;
if d.abs() < 1e-7 {
fallback
} else {
number_factor * d / (1.0 - (-d / denom_scale).exp())
}
}
#[derive(Clone, Debug)]
pub struct HodgkinHuxleyNeuron {
pub v: f64,
pub m: f64,
pub h: f64,
pub n: f64,
pub c_m: f64,
pub g_na: f64,
pub g_k: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_l: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl HodgkinHuxleyNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
m: 0.05,
h: 0.6,
n: 0.32,
c_m: 1.0,
g_na: 120.0,
g_k: 36.0,
g_l: 0.3,
e_na: 50.0,
e_k: -77.0,
e_l: -54.4,
dt: 0.01,
v_threshold: 0.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
let steps = (1.0 / self.dt) as usize;
for _ in 0..steps {
let am = safe_rate(0.1, 40.0, self.v, 10.0, 1.0);
let bm = 4.0 * (-(self.v + 65.0) / 18.0).exp();
let ah = 0.07 * (-(self.v + 65.0) / 20.0).exp();
let bh = 1.0 / (1.0 + (-(self.v + 35.0) / 10.0).exp());
let an = safe_rate(0.01, 55.0, self.v, 10.0, 0.1);
let bn = 0.125 * (-(self.v + 65.0) / 80.0).exp();
self.m += (am * (1.0 - self.m) - bm * self.m) * self.dt;
self.h += (ah * (1.0 - self.h) - bh * self.h) * self.dt;
self.n += (an * (1.0 - self.n) - bn * self.n) * self.dt;
let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_l + current) / self.c_m * self.dt;
}
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -65.0;
self.m = 0.05;
self.h = 0.6;
self.n = 0.32;
}
}
impl Default for HodgkinHuxleyNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct TraubMilesNeuron {
pub v: f64,
pub m: f64,
pub h: f64,
pub n: f64,
pub g_na: f64,
pub g_k: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_l: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl TraubMilesNeuron {
pub fn new() -> Self {
Self {
v: -67.0,
m: 0.05,
h: 0.6,
n: 0.3,
g_na: 100.0,
g_k: 80.0,
g_l: 0.1,
e_na: 50.0,
e_k: -100.0,
e_l: -67.0,
dt: 0.01,
v_threshold: -20.0,
}
}
fn finite_gate(value: f64) -> bool {
value.is_finite() && (0.0..=1.0).contains(&value)
}
fn valid_runtime(&self) -> bool {
self.v.is_finite()
&& Self::finite_gate(self.m)
&& Self::finite_gate(self.h)
&& Self::finite_gate(self.n)
&& self.g_na.is_finite()
&& self.g_na >= 0.0
&& self.g_k.is_finite()
&& self.g_k >= 0.0
&& self.g_l.is_finite()
&& self.g_l >= 0.0
&& self.e_na.is_finite()
&& self.e_k.is_finite()
&& self.e_l.is_finite()
&& self.dt.is_finite()
&& self.dt > 0.0
&& self.v_threshold.is_finite()
}
fn rates(v: f64) -> Option<(f64, f64, f64, f64, f64, f64)> {
let d = v + 54.0;
let am = if d.abs() > 1e-6 {
0.32 * d / (1.0 - (-d / 4.0).exp())
} else {
8.0
};
let d2 = v + 27.0;
let bm = if d2.abs() > 1e-6 {
0.28 * d2 / ((d2 / 5.0).exp() - 1.0)
} else {
5.6
};
let ah = 0.128 * (-(v + 50.0) / 18.0).exp();
let bh = 4.0 / (1.0 + (-(v + 27.0) / 5.0).exp());
let d3 = v + 52.0;
let an = if d3.abs() > 1e-6 {
0.032 * d3 / (1.0 - (-d3 / 5.0).exp())
} else {
0.32
};
let bn = 0.5 * (-(v + 57.0) / 40.0).exp();
if [am, bm, ah, bh, an, bn]
.iter()
.all(|rate| rate.is_finite() && *rate >= 0.0)
{
Some((am, bm, ah, bh, an, bn))
} else {
None
}
}
fn derivatives(
&self,
v: f64,
m: f64,
h: f64,
n: f64,
current: f64,
) -> Option<(f64, f64, f64, f64)> {
if !v.is_finite() || !Self::finite_gate(m) || !Self::finite_gate(h) || !Self::finite_gate(n)
{
return None;
}
let (am, bm, ah, bh, an, bn) = Self::rates(v)?;
let dm = am * (1.0 - m) - bm * m;
let dh = ah * (1.0 - h) - bh * h;
let dn = an * (1.0 - n) - bn * n;
let i_na = self.g_na * m.powi(3) * h * (v - self.e_na);
let i_k = self.g_k * n.powi(4) * (v - self.e_k);
let i_l = self.g_l * (v - self.e_l);
let dv = -i_na - i_k - i_l + current;
if [dv, dm, dh, dn, i_na, i_k, i_l]
.iter()
.all(|value| value.is_finite())
{
Some((dv, dm, dh, dn))
} else {
None
}
}
fn rk4_substep(
&self,
v: f64,
m: f64,
h: f64,
n: f64,
current: f64,
) -> Option<(f64, f64, f64, f64)> {
let (k1_v, k1_m, k1_h, k1_n) = self.derivatives(v, m, h, n, current)?;
let (k2_v, k2_m, k2_h, k2_n) = self.derivatives(
v + 0.5 * self.dt * k1_v,
m + 0.5 * self.dt * k1_m,
h + 0.5 * self.dt * k1_h,
n + 0.5 * self.dt * k1_n,
current,
)?;
let (k3_v, k3_m, k3_h, k3_n) = self.derivatives(
v + 0.5 * self.dt * k2_v,
m + 0.5 * self.dt * k2_m,
h + 0.5 * self.dt * k2_h,
n + 0.5 * self.dt * k2_n,
current,
)?;
let (k4_v, k4_m, k4_h, k4_n) = self.derivatives(
v + self.dt * k3_v,
m + self.dt * k3_m,
h + self.dt * k3_h,
n + self.dt * k3_n,
current,
)?;
let next_v = v + self.dt * (k1_v + 2.0 * k2_v + 2.0 * k3_v + k4_v) / 6.0;
let next_m = m + self.dt * (k1_m + 2.0 * k2_m + 2.0 * k3_m + k4_m) / 6.0;
let next_h = h + self.dt * (k1_h + 2.0 * k2_h + 2.0 * k3_h + k4_h) / 6.0;
let next_n = n + self.dt * (k1_n + 2.0 * k2_n + 2.0 * k3_n + k4_n) / 6.0;
if next_v.is_finite()
&& Self::finite_gate(next_m)
&& Self::finite_gate(next_h)
&& Self::finite_gate(next_n)
{
Some((next_v, next_m, next_h, next_n))
} else {
None
}
}
pub fn step(&mut self, current: f64) -> i32 {
if !current.is_finite() || !self.valid_runtime() {
return 0;
}
let v_prev = self.v;
let mut v = self.v;
let mut m = self.m;
let mut h = self.h;
let mut n = self.n;
for _ in 0..10 {
let Some((next_v, next_m, next_h, next_n)) = self.rk4_substep(v, m, h, n, current)
else {
return 0;
};
v = next_v;
m = next_m;
h = next_h;
n = next_n;
}
self.v = v;
self.m = m;
self.h = h;
self.n = n;
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
impl Default for TraubMilesNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct WangBuzsakiNeuron {
pub v: f64,
pub h: f64,
pub n: f64,
pub g_na: f64,
pub g_k: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_l: f64,
pub c_m: f64,
pub phi: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl WangBuzsakiNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
h: 0.8,
n: 0.1,
g_na: 35.0,
g_k: 9.0,
g_l: 0.1,
e_na: 55.0,
e_k: -90.0,
e_l: -65.0,
c_m: 1.0,
phi: 5.0,
dt: 0.01,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
let n_sub = (0.5 / self.dt.max(0.001)) as usize;
for _ in 0..n_sub {
let am = safe_rate(0.1, 35.0, self.v, 10.0, 1.0);
let bm = 4.0 * (-(self.v + 60.0) / 18.0).exp();
let m_inf = am / (am + bm);
let ah = 0.07 * (-(self.v + 58.0) / 20.0).exp();
let bh = 1.0 / (1.0 + (-(self.v + 28.0) / 10.0).exp());
let an = safe_rate(0.01, 34.0, self.v, 10.0, 0.1);
let bn = 0.125 * (-(self.v + 44.0) / 80.0).exp();
self.h += self.phi * (ah * (1.0 - self.h) - bh * self.h) * self.dt;
self.n += self.phi * (an * (1.0 - self.n) - bn * self.n) * self.dt;
let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_l + current) / self.c_m * self.dt;
}
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -65.0;
self.h = 0.8;
self.n = 0.1;
}
}
impl Default for WangBuzsakiNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct ConnorStevensNeuron {
pub v: f64,
pub m: f64,
pub h: f64,
pub n: f64,
pub a: f64,
pub b: f64,
pub g_na: f64,
pub g_k: f64,
pub g_a: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_a: f64,
pub e_l: f64,
pub c_m: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl ConnorStevensNeuron {
pub fn new() -> Self {
Self {
v: -68.0,
m: 0.01,
h: 0.99,
n: 0.1,
a: 0.5,
b: 0.1,
g_na: 120.0,
g_k: 20.0,
g_a: 47.7,
g_l: 0.3,
e_na: 55.0,
e_k: -72.0,
e_a: -75.0,
e_l: -17.0,
c_m: 1.0,
dt: 0.01,
v_threshold: 0.0,
}
}
fn cs_safe_exp(x: f64) -> Option<f64> {
if x.is_finite() && x <= 700.0 {
Some(x.exp())
} else {
None
}
}
fn cs_safe_rate(scale: f64, shift: f64, v: f64, denom: f64) -> Option<f64> {
let delta = v + shift;
let x = delta / denom;
if x.abs() < 1e-9 {
return Some(scale * denom);
}
let e = Self::cs_safe_exp(-x)?;
let value = scale * delta / (1.0 - e);
value.is_finite().then_some(value)
}
fn cs_valid_state(v: f64, m: f64, h: f64, n: f64, a: f64, b: f64) -> bool {
[v, m, h, n, a, b].iter().all(|x| x.is_finite())
&& (-250.0..=250.0).contains(&v)
&& (-0.05..=1.05).contains(&m)
&& (-0.05..=1.05).contains(&h)
&& (-0.05..=1.05).contains(&n)
&& (-0.05..=1.5).contains(&a)
&& (-0.05..=1.05).contains(&b)
}
fn cs_valid_static(&self) -> bool {
[
self.g_na,
self.g_k,
self.g_a,
self.g_l,
self.e_na,
self.e_k,
self.e_a,
self.e_l,
self.c_m,
self.dt,
self.v_threshold,
]
.iter()
.all(|x| x.is_finite())
&& self.g_na >= 0.0
&& self.g_k >= 0.0
&& self.g_a >= 0.0
&& self.g_l >= 0.0
&& self.c_m > 0.0
&& self.dt > 0.0
}
fn cs_derivatives(
&self,
state: (f64, f64, f64, f64, f64, f64),
current: f64,
) -> Option<(f64, f64, f64, f64, f64, f64)> {
let (v, m, h, n, a, b) = state;
let am = Self::cs_safe_rate(0.38, 29.7, v, 10.0)?;
let bm = 15.2 * Self::cs_safe_exp(-(v + 54.7) / 18.0)?;
let ah = 0.266 * Self::cs_safe_exp(-(v + 48.0) / 20.0)?;
let bh = 3.8 / (1.0 + Self::cs_safe_exp(-(v + 18.0) / 10.0)?);
let an = Self::cs_safe_rate(0.02, 45.7, v, 10.0)?;
let bn = 0.25 * Self::cs_safe_exp(-(v + 55.7) / 80.0)?;
let a_base = 0.0761 * Self::cs_safe_exp((v + 94.22) / 31.84)?
/ (1.0 + Self::cs_safe_exp((v + 1.17) / 28.93)?);
if !a_base.is_finite() || a_base < 0.0 {
return None;
}
let a_inf = a_base.powf(1.0 / 3.0);
let tau_a = 0.3632 + 1.158 / (1.0 + Self::cs_safe_exp((v + 55.96) / 20.12)?);
let b_base = 1.0 / (1.0 + Self::cs_safe_exp((v + 53.3) / 14.54)?);
let b_inf = b_base.powf(4.0);
let tau_b = 1.24 + 2.678 / (1.0 + Self::cs_safe_exp((v + 50.0) / 16.027)?);
let i_na = self.g_na * m.powi(3) * h * (v - self.e_na);
let i_k = self.g_k * n.powi(4) * (v - self.e_k);
let i_a = self.g_a * a.powi(3) * b * (v - self.e_a);
let i_l = self.g_l * (v - self.e_l);
let deriv = (
(-i_na - i_k - i_a - i_l + current) / self.c_m,
am * (1.0 - m) - bm * m,
ah * (1.0 - h) - bh * h,
an * (1.0 - n) - bn * n,
(a_inf - a) / tau_a,
(b_inf - b) / tau_b,
);
[deriv.0, deriv.1, deriv.2, deriv.3, deriv.4, deriv.5]
.iter()
.all(|x| x.is_finite())
.then_some(deriv)
}
fn cs_rk4_candidate(&self, current: f64) -> Option<(f64, f64, f64, f64, f64, f64)> {
if !self.cs_valid_static()
|| !current.is_finite()
|| !Self::cs_valid_state(self.v, self.m, self.h, self.n, self.a, self.b)
{
return None;
}
let mut state = (self.v, self.m, self.h, self.n, self.a, self.b);
let substeps = (1.0 / self.dt.max(0.001)) as usize;
for _ in 0..substeps {
let k1 = self.cs_derivatives(state, current)?;
let k2_state = (
state.0 + 0.5 * self.dt * k1.0,
state.1 + 0.5 * self.dt * k1.1,
state.2 + 0.5 * self.dt * k1.2,
state.3 + 0.5 * self.dt * k1.3,
state.4 + 0.5 * self.dt * k1.4,
state.5 + 0.5 * self.dt * k1.5,
);
let k2 = self.cs_derivatives(k2_state, current)?;
let k3_state = (
state.0 + 0.5 * self.dt * k2.0,
state.1 + 0.5 * self.dt * k2.1,
state.2 + 0.5 * self.dt * k2.2,
state.3 + 0.5 * self.dt * k2.3,
state.4 + 0.5 * self.dt * k2.4,
state.5 + 0.5 * self.dt * k2.5,
);
let k3 = self.cs_derivatives(k3_state, current)?;
let k4_state = (
state.0 + self.dt * k3.0,
state.1 + self.dt * k3.1,
state.2 + self.dt * k3.2,
state.3 + self.dt * k3.3,
state.4 + self.dt * k3.4,
state.5 + self.dt * k3.5,
);
let k4 = self.cs_derivatives(k4_state, current)?;
state = (
state.0 + self.dt * (k1.0 + 2.0 * k2.0 + 2.0 * k3.0 + k4.0) / 6.0,
state.1 + self.dt * (k1.1 + 2.0 * k2.1 + 2.0 * k3.1 + k4.1) / 6.0,
state.2 + self.dt * (k1.2 + 2.0 * k2.2 + 2.0 * k3.2 + k4.2) / 6.0,
state.3 + self.dt * (k1.3 + 2.0 * k2.3 + 2.0 * k3.3 + k4.3) / 6.0,
state.4 + self.dt * (k1.4 + 2.0 * k2.4 + 2.0 * k3.4 + k4.4) / 6.0,
state.5 + self.dt * (k1.5 + 2.0 * k2.5 + 2.0 * k3.5 + k4.5) / 6.0,
);
if !Self::cs_valid_state(state.0, state.1, state.2, state.3, state.4, state.5) {
return None;
}
}
Some(state)
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
let Some((v, m, h, n, a, b)) = self.cs_rk4_candidate(current) else {
return 0;
};
self.v = v;
self.m = m;
self.h = h;
self.n = n;
self.a = a;
self.b = b;
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -68.0;
self.m = 0.01;
self.h = 0.99;
self.n = 0.1;
self.a = 0.5;
self.b = 0.1;
}
}
impl Default for ConnorStevensNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct DestexheThalamicNeuron {
pub v: f64,
pub h_na: f64,
pub n_k: f64,
pub m_t: f64,
pub h_t: f64,
pub g_na: f64,
pub g_k: f64,
pub g_t: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_ca: f64,
pub e_l: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl DestexheThalamicNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
h_na: 0.6,
n_k: 0.3,
m_t: 0.0,
h_t: 1.0,
g_na: 100.0,
g_k: 10.0,
g_t: 2.0,
g_l: 0.05,
e_na: 50.0,
e_k: -90.0,
e_ca: 120.0,
e_l: -70.0,
dt: 0.02,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
for _ in 0..5 {
let m_na = 1.0 / (1.0 + (-(self.v + 37.0) / 7.0).exp());
let h_na_inf = 1.0 / (1.0 + ((self.v + 41.0) / 4.0).exp());
let n_inf = 1.0 / (1.0 + (-(self.v + 25.0) / 12.0).exp());
let m_t_inf = 1.0 / (1.0 + (-(self.v + 57.0) / 6.5).exp());
let h_t_inf = 1.0 / (1.0 + ((self.v + 81.0) / 4.0).exp());
let tau_h_na = (1.0
/ (0.128 * (-(self.v + 46.0) / 18.0).exp()
+ 4.0 / (1.0 + (-(self.v + 23.0) / 5.0).exp())))
.max(0.1);
let tau_n_k = (1.0 / (0.032 * 5.0 + 0.5 * (-(self.v + 40.0) / 40.0).exp())).max(0.1);
let tau_h_t = if self.v < -81.0 {
(30.8
+ 211.4 * ((self.v + 115.2) / 5.0).exp()
/ (1.0 + ((self.v + 86.0) / 3.2).exp()))
.max(0.1)
} else {
10.0
};
self.h_na += (h_na_inf - self.h_na) / tau_h_na * self.dt;
self.n_k += (n_inf - self.n_k) / tau_n_k * self.dt;
self.m_t = m_t_inf; self.h_t += (h_t_inf - self.h_t) / tau_h_t * self.dt;
let i_na = self.g_na * m_na.powi(3) * self.h_na * (self.v - self.e_na);
let i_k = self.g_k * self.n_k.powi(4) * (self.v - self.e_k);
let i_t = self.g_t * self.m_t.powi(2) * self.h_t * (self.v - self.e_ca);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_t - i_l + current) * self.dt;
}
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -65.0;
self.h_na = 0.6;
self.n_k = 0.3;
self.m_t = 0.0;
self.h_t = 1.0;
}
}
impl Default for DestexheThalamicNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct HuberBraunNeuron {
pub v: f64,
pub a_sd: f64,
pub a_sr: f64,
pub g_sd: f64,
pub g_sr: f64,
pub g_l: f64,
pub e_sd: f64,
pub e_sr: f64,
pub e_l: f64,
pub tau_sd: f64,
pub tau_sr: f64,
pub eta: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl HuberBraunNeuron {
pub fn new() -> Self {
Self {
v: -50.0,
a_sd: 0.0,
a_sr: 0.0,
g_sd: 1.5,
g_sr: 0.4,
g_l: 0.1,
e_sd: 50.0,
e_sr: -90.0,
e_l: -60.0,
tau_sd: 10.0,
tau_sr: 20.0,
eta: 0.012,
dt: 0.1,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
let sd_inf = 1.0 / (1.0 + (-(self.v + 40.0) / 6.0).exp());
let sr_inf = 1.0 / (1.0 + ((self.v + 40.0) / 6.0).exp());
self.a_sd += (sd_inf - self.a_sd) / self.tau_sd * self.dt;
self.a_sr += (sr_inf - self.a_sr) / self.tau_sr * self.dt;
let i_sd = self.g_sd * self.a_sd * (self.v - self.e_sd);
let i_sr = self.g_sr * self.a_sr * (self.v - self.e_sr);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_sd - i_sr - i_l + current) * self.dt;
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -50.0;
self.a_sd = 0.0;
self.a_sr = 0.0;
}
}
impl Default for HuberBraunNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct GolombFSNeuron {
pub v: f64,
pub h: f64,
pub n: f64,
pub p: f64,
pub g_na: f64,
pub g_k: f64,
pub g_kv3: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_l: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl GolombFSNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
h: 0.9,
n: 0.1,
p: 0.0,
g_na: 112.5,
g_k: 225.0,
g_kv3: 150.0,
g_l: 0.25,
e_na: 50.0,
e_k: -90.0,
e_l: -70.0,
dt: 0.01,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
for _ in 0..10 {
let m_inf = 1.0 / (1.0 + (-(self.v + 24.0) / 11.5).exp());
let h_inf = 1.0 / (1.0 + ((self.v + 58.3) / 6.7).exp());
let n_inf = 1.0 / (1.0 + (-(self.v + 12.4) / 6.8).exp());
let p_inf = 1.0 / (1.0 + (-(self.v + 3.0) / 8.0).exp());
let tau_h = 0.5 + 14.0 / (1.0 + ((self.v + 60.0) / 12.0).exp());
let tau_n = 0.087 + 11.4 / (1.0 + ((self.v + 14.6) / 8.6).exp());
let tau_p = 0.1 + 4.0 / (1.0 + ((self.v + 25.0) / 10.0).exp());
self.h += (h_inf - self.h) / tau_h * self.dt;
self.n += (n_inf - self.n) / tau_n * self.dt;
self.p += (p_inf - self.p) / tau_p * self.dt;
let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
let i_kv3 = self.g_kv3 * self.p.powi(2) * (self.v - self.e_k);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_kv3 - i_l + current) * self.dt;
}
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -65.0;
self.h = 0.9;
self.n = 0.1;
self.p = 0.0;
}
}
impl Default for GolombFSNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct PospischilNeuron {
pub v: f64,
pub m: f64,
pub h: f64,
pub n: f64,
pub p: f64,
pub g_na: f64,
pub g_k: f64,
pub g_m: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_l: f64,
pub c_m: f64,
pub vt: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl PospischilNeuron {
pub fn new() -> Self {
Self {
v: -70.0,
m: 0.05,
h: 0.6,
n: 0.3,
p: 0.0,
g_na: 50.0,
g_k: 5.0,
g_m: 0.07,
g_l: 0.1,
e_na: 50.0,
e_k: -90.0,
e_l: -70.0,
c_m: 1.0,
vt: -56.2,
dt: 0.025,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
for _ in 0..4 {
let dv = self.v - self.vt;
let x_m = dv - 13.0;
let am = if x_m.abs() < 1e-6 {
-0.32 * -4.0
} else {
-0.32 * x_m / ((-(x_m) / 4.0).exp() - 1.0)
};
let x_bm = dv - 40.0;
let bm = if x_bm.abs() < 1e-6 {
0.28 * 5.0
} else {
0.28 * x_bm / ((x_bm / 5.0).exp() - 1.0)
};
let ah = 0.128 * (-(dv - 17.0) / 18.0).exp();
let bh = 4.0 / (1.0 + (-(dv - 40.0) / 5.0).exp());
let x_n = dv - 15.0;
let an = if x_n.abs() < 1e-6 {
-0.032 * -5.0
} else {
-0.032 * x_n / ((-(x_n) / 5.0).exp() - 1.0)
};
let bn = 0.5 * (-(dv - 10.0) / 40.0).exp();
let p_inf = 1.0 / (1.0 + (-(self.v + 35.0) / 10.0).exp());
self.m += (am * (1.0 - self.m) - bm * self.m) * self.dt;
self.h += (ah * (1.0 - self.h) - bh * self.h) * self.dt;
self.n += (an * (1.0 - self.n) - bn * self.n) * self.dt;
let tau_p =
608.0 / (3.3 * ((self.v + 35.0) / 20.0).exp() + (-(self.v + 35.0) / 20.0).exp());
self.p += (p_inf - self.p) / tau_p * self.dt;
let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
let i_m = self.g_m * self.p * (self.v - self.e_k);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_m - i_l + current) / self.c_m * self.dt;
}
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -70.0;
self.m = 0.05;
self.h = 0.6;
self.n = 0.3;
self.p = 0.0;
}
}
impl Default for PospischilNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct MainenSejnowskiNeuron {
pub vs: f64,
pub va: f64,
pub m: f64,
pub h: f64,
pub n: f64,
pub kappa: f64,
pub g_na: f64,
pub g_k: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_l: f64,
pub c_s: f64,
pub c_a: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl MainenSejnowskiNeuron {
pub fn new() -> Self {
Self {
vs: -65.0,
va: -65.0,
m: 0.05,
h: 0.6,
n: 0.3,
kappa: 10.0,
g_na: 3000.0,
g_k: 1500.0,
g_l: 1.0,
e_na: 50.0,
e_k: -90.0,
e_l: -70.0,
c_s: 1.0,
c_a: 0.1,
dt: 0.005,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.vs;
for _ in 0..20 {
let x_am = self.va + 25.0;
let am = if x_am.abs() < 1e-6 {
0.182 * 9.0
} else {
0.182 * x_am / (1.0 - (-(x_am) / 9.0).exp() + 1e-12)
};
let bm = if x_am.abs() < 1e-6 {
0.124 * 9.0
} else {
-0.124 * x_am / (1.0 - ((x_am) / 9.0).exp() + 1e-12)
};
let x_ah = self.va + 40.0;
let ah = if x_ah.abs() < 1e-6 {
0.024 * 5.0
} else {
0.024 * x_ah / (1.0 - (-(x_ah) / 5.0).exp() + 1e-12)
};
let x_bh = self.va + 65.0;
let bh = if x_bh.abs() < 1e-6 {
0.0091 * 5.0
} else {
-0.0091 * x_bh / (1.0 - ((x_bh) / 5.0).exp() + 1e-12)
};
let x_an = self.va - 20.0;
let an = if x_an.abs() < 1e-6 {
0.02 * 9.0
} else {
0.02 * x_an / (1.0 - (-(x_an) / 9.0).exp() + 1e-12)
};
let bn = if x_an.abs() < 1e-6 {
0.002 * 9.0
} else {
-0.002 * x_an / (1.0 - ((x_an) / 9.0).exp() + 1e-12)
};
self.m = (self.m + (am * (1.0 - self.m) - bm * self.m) * self.dt).clamp(0.0, 1.0);
self.h = (self.h + (ah * (1.0 - self.h) - bh * self.h) * self.dt).clamp(0.0, 1.0);
self.n = (self.n + (an * (1.0 - self.n) - bn * self.n) * self.dt).clamp(0.0, 1.0);
let i_na = self.g_na * self.m.powi(3) * self.h * (self.va - self.e_na);
let i_k = self.g_k * self.n * (self.va - self.e_k);
let i_l_s = self.g_l * (self.vs - self.e_l);
self.vs = (self.vs
+ (-i_l_s + self.kappa * (self.va - self.vs) + current) / self.c_s * self.dt)
.clamp(-200.0, 200.0);
self.va = (self.va
+ (-i_na - i_k + self.kappa * (self.vs - self.va)) / self.c_a * self.dt)
.clamp(-200.0, 200.0);
}
if self.vs >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.vs = -65.0;
self.va = -65.0;
self.m = 0.05;
self.h = 0.6;
self.n = 0.3;
}
}
impl Default for MainenSejnowskiNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct DeSchutterPurkinjeNeuron {
pub v: f64,
pub h_na: f64,
pub n_k: f64,
pub m_cap: f64,
pub h_cap: f64,
pub q_kca: f64,
pub ca: f64,
pub g_na: f64,
pub g_k: f64,
pub g_cap: f64,
pub g_kca: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_ca: f64,
pub e_l: f64,
pub ca_decay: f64,
pub f_ca: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl DeSchutterPurkinjeNeuron {
pub fn new() -> Self {
Self {
v: -68.0,
h_na: 0.8,
n_k: 0.1,
m_cap: 0.0,
h_cap: 0.9,
q_kca: 0.0,
ca: 0.0001,
g_na: 125.0,
g_k: 10.0,
g_cap: 45.0,
g_kca: 35.0,
g_l: 0.5,
e_na: 45.0,
e_k: -85.0,
e_ca: 135.0,
e_l: -68.0,
ca_decay: 0.02,
f_ca: 0.00024,
dt: 0.01,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
for _ in 0..5 {
let m_na = 1.0 / (1.0 + (-(self.v + 35.0) / 7.5).exp());
let h_na_inf = 1.0 / (1.0 + ((self.v + 55.0) / 7.0).exp());
let n_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 15.0).exp());
let m_cap_inf = 1.0 / (1.0 + (-(self.v + 19.0) / 5.5).exp());
let h_cap_inf = 1.0 / (1.0 + ((self.v + 48.0) / 7.0).exp());
let q_inf = self.ca / (self.ca + 0.0002);
let tau_h_na = 0.5 + 14.0 / (1.0 + ((self.v + 40.0) / 12.0).exp());
let tau_n_k = 1.0 + 11.0 / (1.0 + ((self.v + 15.0) / 8.0).exp());
self.h_na += (h_na_inf - self.h_na) / tau_h_na * self.dt;
self.n_k += (n_inf - self.n_k) / tau_n_k * self.dt;
self.m_cap += (m_cap_inf - self.m_cap) / 0.3 * self.dt;
self.h_cap += (h_cap_inf - self.h_cap) / 45.0 * self.dt;
self.q_kca += (q_inf - self.q_kca) / 1.0 * self.dt;
let i_na = self.g_na * m_na.powi(3) * self.h_na * (self.v - self.e_na);
let i_k = self.g_k * self.n_k.powi(4) * (self.v - self.e_k);
let i_cap = self.g_cap * self.m_cap.powi(2) * self.h_cap * (self.v - self.e_ca);
let i_kca = self.g_kca * self.q_kca * (self.v - self.e_k);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_cap - i_kca - i_l + current) * self.dt;
self.ca = (self.ca + (-self.f_ca * i_cap - self.ca_decay * self.ca) * self.dt).max(0.0);
}
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -68.0;
self.h_na = 0.8;
self.n_k = 0.1;
self.m_cap = 0.0;
self.h_cap = 0.9;
self.q_kca = 0.0;
self.ca = 0.0001;
}
}
impl Default for DeSchutterPurkinjeNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct PlantR15Neuron {
pub v: f64,
pub m: f64,
pub h: f64,
pub n: f64,
pub ca: f64,
pub g_na: f64,
pub g_k: f64,
pub g_ca: f64,
pub g_l: f64,
pub g_kca: f64,
pub e_na: f64,
pub e_k: f64,
pub e_ca: f64,
pub e_l: f64,
pub c_m: f64,
pub k_ca: f64,
pub tau_ca: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl PlantR15Neuron {
pub fn new() -> Self {
Self {
v: -50.0,
m: 0.05,
h: 0.6,
n: 0.3,
ca: 0.1,
g_na: 4.0,
g_k: 0.3,
g_ca: 0.004,
g_l: 0.003,
g_kca: 0.03,
e_na: 30.0,
e_k: -75.0,
e_ca: 140.0,
e_l: -40.0,
c_m: 1.0,
k_ca: 0.0085,
tau_ca: 500.0,
dt: 0.05,
v_threshold: -10.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
for _ in 0..5 {
let am = safe_rate(0.1, 50.0, self.v, 10.0, 1.0);
let bm = 4.0 * (-(self.v + 75.0) / 18.0).exp();
let ah = 0.07 * (-(self.v + 50.0) / 20.0).exp();
let bh = 1.0 / (1.0 + (-(self.v + 20.0) / 10.0).exp());
let an = safe_rate(0.01, 55.0, self.v, 10.0, 0.1);
let bn = 0.125 * (-(self.v + 65.0) / 80.0).exp();
self.m += (am * (1.0 - self.m) - bm * self.m) * self.dt;
self.h += (ah * (1.0 - self.h) - bh * self.h) * self.dt;
self.n += (an * (1.0 - self.n) - bn * self.n) * self.dt;
let m_ca = 1.0 / (1.0 + (-(self.v + 25.0) / 5.0).exp());
let kca_act = self.ca / (0.5 + self.ca);
let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
let i_ca = self.g_ca * m_ca.powi(2) * (self.v - self.e_ca);
let i_kca = self.g_kca * kca_act * (self.v - self.e_k);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_ca - i_kca - i_l + current) / self.c_m * self.dt;
self.ca = (self.ca + (-self.k_ca * i_ca - self.ca / self.tau_ca) * self.dt).max(0.0);
}
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -50.0;
self.m = 0.05;
self.h = 0.6;
self.n = 0.3;
self.ca = 0.1;
}
}
impl Default for PlantR15Neuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct PrescottNeuron {
pub v: f64,
pub w: f64,
pub g_fast: f64,
pub g_slow: f64,
pub g_l: f64,
pub e_fast: f64,
pub e_slow: f64,
pub e_l: f64,
pub beta_w: f64,
pub gamma_w: f64,
pub tau_w: f64,
pub phi: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl PrescottNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
w: 0.0,
g_fast: 20.0,
g_slow: 20.0,
g_l: 2.0,
e_fast: 50.0,
e_slow: -100.0,
e_l: -70.0,
beta_w: -21.0,
gamma_w: 15.0,
tau_w: 100.0,
phi: 0.15,
dt: 0.1,
v_threshold: -20.0,
}
}
fn sigmoid(x: f64) -> f64 {
if x >= 0.0 {
let z = (-x).exp();
1.0 / (1.0 + z)
} else {
let z = x.exp();
z / (1.0 + z)
}
}
fn valid_state(v: f64, w: f64) -> bool {
v.is_finite() && w.is_finite() && (0.0..=1.0).contains(&w)
}
fn valid_runtime(&self) -> bool {
Self::valid_state(self.v, self.w)
&& self.g_fast.is_finite()
&& self.g_fast >= 0.0
&& self.g_slow.is_finite()
&& self.g_slow >= 0.0
&& self.g_l.is_finite()
&& self.g_l >= 0.0
&& self.e_fast.is_finite()
&& self.e_slow.is_finite()
&& self.e_l.is_finite()
&& self.beta_w.is_finite()
&& self.gamma_w.is_finite()
&& self.gamma_w > 0.0
&& self.tau_w.is_finite()
&& self.tau_w > 0.0
&& self.phi.is_finite()
&& self.phi >= 0.0
&& self.dt.is_finite()
&& self.dt > 0.0
&& self.v_threshold.is_finite()
}
fn derivatives(&self, v: f64, w: f64, current: f64) -> Option<(f64, f64)> {
if !Self::valid_state(v, w) {
return None;
}
let m_inf = Self::sigmoid((v + 20.0) / 15.0);
let w_inf = Self::sigmoid((v - self.beta_w) / self.gamma_w);
let i_fast = self.g_fast * m_inf * (v - self.e_fast);
let i_slow = self.g_slow * w * (v - self.e_slow);
let i_l = self.g_l * (v - self.e_l);
let dv = -i_fast - i_slow - i_l + current;
let dw = self.phi * (w_inf - w) / self.tau_w;
if dv.is_finite() && dw.is_finite() {
Some((dv, dw))
} else {
None
}
}
fn rk4_step(&self, current: f64) -> Option<(f64, f64)> {
let dt = self.dt;
let (k1_v, k1_w) = self.derivatives(self.v, self.w, current)?;
let (k2_v, k2_w) =
self.derivatives(self.v + 0.5 * dt * k1_v, self.w + 0.5 * dt * k1_w, current)?;
let (k3_v, k3_w) =
self.derivatives(self.v + 0.5 * dt * k2_v, self.w + 0.5 * dt * k2_w, current)?;
let (k4_v, k4_w) = self.derivatives(self.v + dt * k3_v, self.w + dt * k3_w, current)?;
let next_v = self.v + dt * (k1_v + 2.0 * k2_v + 2.0 * k3_v + k4_v) / 6.0;
let next_w = self.w + dt * (k1_w + 2.0 * k2_w + 2.0 * k3_w + k4_w) / 6.0;
if Self::valid_state(next_v, next_w) {
Some((next_v, next_w))
} else {
None
}
}
pub fn step(&mut self, current: f64) -> i32 {
if !current.is_finite() || !self.valid_runtime() {
return 0;
}
let v_prev = self.v;
let Some((next_v, next_w)) = self.rk4_step(current) else {
return 0;
};
self.v = next_v;
self.w = next_w;
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -65.0;
self.w = 0.0;
}
}
impl Default for PrescottNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct MihalasNieburNeuron {
pub v: f64,
pub theta: f64,
pub i1: f64,
pub i2: f64,
pub v_rest: f64,
pub v_reset: f64,
pub theta_reset: f64,
pub theta_inf: f64,
pub tau_v: f64,
pub tau_theta: f64,
pub tau_1: f64,
pub tau_2: f64,
pub a: f64,
pub b: f64,
pub r1: f64,
pub r2: f64,
pub dt: f64,
}
impl MihalasNieburNeuron {
pub fn new() -> Self {
Self {
v: 0.0,
theta: 1.0,
i1: 0.0,
i2: 0.0,
v_rest: 0.0,
v_reset: 0.0,
theta_reset: 1.0,
theta_inf: 1.0,
tau_v: 10.0,
tau_theta: 100.0,
tau_1: 10.0,
tau_2: 200.0,
a: 0.0,
b: 0.0,
r1: 0.0,
r2: 0.0,
dt: 1.0,
}
}
fn finite_values(values: &[f64]) -> bool {
values.iter().all(|value| value.is_finite())
}
fn valid_runtime(&self) -> bool {
Self::finite_values(&[
self.v,
self.theta,
self.i1,
self.i2,
self.v_rest,
self.v_reset,
self.theta_reset,
self.theta_inf,
self.tau_v,
self.tau_theta,
self.tau_1,
self.tau_2,
self.a,
self.b,
self.r1,
self.r2,
self.dt,
]) && self.tau_v > 0.0
&& self.tau_theta > 0.0
&& self.tau_1 > 0.0
&& self.tau_2 > 0.0
&& self.dt > 0.0
}
fn derivatives(&self, v: f64, theta: f64, i1: f64, i2: f64, current: f64) -> [f64; 4] {
[
(-(v - self.v_rest) + i1 + i2 + current) / self.tau_v,
(self.theta_inf - theta + self.a * (v - self.v_rest)) / self.tau_theta,
-i1 / self.tau_1,
-i2 / self.tau_2,
]
}
fn add_scaled(state: [f64; 4], slope: [f64; 4], scale: f64) -> [f64; 4] {
[
state[0] + scale * slope[0],
state[1] + scale * slope[1],
state[2] + scale * slope[2],
state[3] + scale * slope[3],
]
}
fn rk4_candidate(&self, current: f64) -> Option<[f64; 4]> {
let state = [self.v, self.theta, self.i1, self.i2];
let half_dt = 0.5 * self.dt;
let k1 = self.derivatives(state[0], state[1], state[2], state[3], current);
let s2 = Self::add_scaled(state, k1, half_dt);
let k2 = self.derivatives(s2[0], s2[1], s2[2], s2[3], current);
let s3 = Self::add_scaled(state, k2, half_dt);
let k3 = self.derivatives(s3[0], s3[1], s3[2], s3[3], current);
let s4 = Self::add_scaled(state, k3, self.dt);
let k4 = self.derivatives(s4[0], s4[1], s4[2], s4[3], current);
let candidate = [
state[0] + self.dt * (k1[0] + 2.0 * k2[0] + 2.0 * k3[0] + k4[0]) / 6.0,
state[1] + self.dt * (k1[1] + 2.0 * k2[1] + 2.0 * k3[1] + k4[1]) / 6.0,
state[2] + self.dt * (k1[2] + 2.0 * k2[2] + 2.0 * k3[2] + k4[2]) / 6.0,
state[3] + self.dt * (k1[3] + 2.0 * k2[3] + 2.0 * k3[3] + k4[3]) / 6.0,
];
if Self::finite_values(&candidate) {
Some(candidate)
} else {
None
}
}
pub fn step(&mut self, current: f64) -> i32 {
if !current.is_finite() || !self.valid_runtime() {
return 0;
}
let Some(candidate) = self.rk4_candidate(current) else {
return 0;
};
self.v = candidate[0];
self.theta = candidate[1];
self.i1 = candidate[2];
self.i2 = candidate[3];
if self.v >= self.theta {
self.v = self.v_reset + self.b * (self.v - self.v_rest);
self.theta = self.theta_reset.max(self.theta);
self.i1 += self.r1;
self.i2 += self.r2;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.theta = self.theta_reset;
self.i1 = 0.0;
self.i2 = 0.0;
}
}
impl Default for MihalasNieburNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct GLIFNeuron {
pub v: f64,
pub theta: f64,
pub theta_inf: f64,
pub i_asc1: f64,
pub i_asc2: f64,
pub v_rest: f64,
pub v_reset: f64,
pub tau_m: f64,
pub tau_theta: f64,
pub tau_asc1: f64,
pub tau_asc2: f64,
pub a_theta: f64,
pub delta_theta: f64,
pub r_asc1: f64,
pub r_asc2: f64,
pub resistance: f64,
pub dt: f64,
}
impl GLIFNeuron {
pub fn new() -> Self {
Self {
v: -70.0,
theta: -50.0,
theta_inf: -50.0,
i_asc1: 0.0,
i_asc2: 0.0,
v_rest: -70.0,
v_reset: -70.0,
tau_m: 10.0,
tau_theta: 100.0,
tau_asc1: 10.0,
tau_asc2: 200.0,
a_theta: 0.01,
delta_theta: 2.0,
r_asc1: 1.0,
r_asc2: 0.5,
resistance: 1.0,
dt: 1.0,
}
}
fn finite_values(values: &[f64]) -> bool {
values.iter().all(|value| value.is_finite())
}
fn valid_runtime(&self) -> bool {
Self::finite_values(&[
self.v,
self.theta,
self.theta_inf,
self.i_asc1,
self.i_asc2,
self.v_rest,
self.v_reset,
self.tau_m,
self.tau_theta,
self.tau_asc1,
self.tau_asc2,
self.a_theta,
self.delta_theta,
self.r_asc1,
self.r_asc2,
self.resistance,
self.dt,
]) && self.tau_m > 0.0
&& self.tau_theta > 0.0
&& self.tau_asc1 > 0.0
&& self.tau_asc2 > 0.0
&& self.dt > 0.0
&& self.delta_theta >= 0.0
&& self.resistance >= 0.0
}
fn derivatives(&self, v: f64, theta: f64, i_asc1: f64, i_asc2: f64, current: f64) -> [f64; 4] {
[
(-(v - self.v_rest) + self.resistance * current + i_asc1 + i_asc2) / self.tau_m,
(self.theta_inf - theta + self.a_theta * (v - self.v_rest)) / self.tau_theta,
-i_asc1 / self.tau_asc1,
-i_asc2 / self.tau_asc2,
]
}
fn add_scaled(state: [f64; 4], slope: [f64; 4], scale: f64) -> [f64; 4] {
[
state[0] + scale * slope[0],
state[1] + scale * slope[1],
state[2] + scale * slope[2],
state[3] + scale * slope[3],
]
}
fn rk4_candidate(&self, current: f64) -> Option<[f64; 4]> {
let state = [self.v, self.theta, self.i_asc1, self.i_asc2];
let half_dt = 0.5 * self.dt;
let k1 = self.derivatives(state[0], state[1], state[2], state[3], current);
let s2 = Self::add_scaled(state, k1, half_dt);
let k2 = self.derivatives(s2[0], s2[1], s2[2], s2[3], current);
let s3 = Self::add_scaled(state, k2, half_dt);
let k3 = self.derivatives(s3[0], s3[1], s3[2], s3[3], current);
let s4 = Self::add_scaled(state, k3, self.dt);
let k4 = self.derivatives(s4[0], s4[1], s4[2], s4[3], current);
let candidate = [
state[0] + self.dt * (k1[0] + 2.0 * k2[0] + 2.0 * k3[0] + k4[0]) / 6.0,
state[1] + self.dt * (k1[1] + 2.0 * k2[1] + 2.0 * k3[1] + k4[1]) / 6.0,
state[2] + self.dt * (k1[2] + 2.0 * k2[2] + 2.0 * k3[2] + k4[2]) / 6.0,
state[3] + self.dt * (k1[3] + 2.0 * k2[3] + 2.0 * k3[3] + k4[3]) / 6.0,
];
if Self::finite_values(&candidate) {
Some(candidate)
} else {
None
}
}
pub fn step(&mut self, current: f64) -> i32 {
if !current.is_finite() || !self.valid_runtime() {
return 0;
}
let Some(candidate) = self.rk4_candidate(current) else {
return 0;
};
self.v = candidate[0];
self.theta = candidate[1];
self.i_asc1 = candidate[2];
self.i_asc2 = candidate[3];
if self.v >= self.theta {
self.v = self.v_reset;
self.theta += self.delta_theta;
self.i_asc1 += self.r_asc1;
self.i_asc2 += self.r_asc2;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.theta = self.theta_inf;
self.i_asc1 = 0.0;
self.i_asc2 = 0.0;
}
}
impl Default for GLIFNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct GIFPopulationNeuron {
pub v: f64,
pub theta: f64,
pub eta: f64,
pub tau_m: f64,
pub tau_eta: f64,
pub delta_v: f64,
pub lambda_0: f64,
pub eta_increment: f64,
pub v_rest: f64,
pub v_reset: f64,
pub dt: f64,
pub seed: u64,
rng: Xoshiro256PlusPlus,
}
impl GIFPopulationNeuron {
pub fn new(seed: u64) -> Self {
Self {
v: -65.0,
theta: -50.0,
eta: 0.0,
tau_m: 20.0,
tau_eta: 100.0,
delta_v: 2.0,
lambda_0: 0.001,
eta_increment: 5.0,
v_rest: -65.0,
v_reset: -65.0,
dt: 0.5,
seed,
rng: Xoshiro256PlusPlus::seed_from_u64(seed),
}
}
fn finite_values(values: &[f64]) -> bool {
values.iter().all(|value| value.is_finite())
}
fn valid_runtime(&self) -> bool {
Self::finite_values(&[
self.v,
self.theta,
self.eta,
self.tau_m,
self.tau_eta,
self.delta_v,
self.lambda_0,
self.eta_increment,
self.v_rest,
self.v_reset,
self.dt,
]) && self.tau_m > 0.0
&& self.tau_eta > 0.0
&& self.delta_v > 0.0
&& self.lambda_0 >= 0.0
&& self.dt > 0.0
}
fn advance_subthreshold(&self, current: f64) -> Option<(f64, f64)> {
let eta_decay = (-self.dt / self.tau_eta).exp();
let membrane_decay = (-self.dt / self.tau_m).exp();
let x0 = self.v - self.v_rest - current;
let eta_new = self.eta * eta_decay;
let x_new = if (self.tau_m - self.tau_eta).abs() <= 1e-12 {
membrane_decay * (x0 - self.eta * self.dt / self.tau_m)
} else {
let coupling = self.tau_eta / (self.tau_eta - self.tau_m);
x0 * membrane_decay - self.eta * coupling * (eta_decay - membrane_decay)
};
let v_new = self.v_rest + current + x_new;
if Self::finite_values(&[v_new, eta_new]) {
Some((v_new, eta_new))
} else {
None
}
}
fn spike_probability(&self, voltage: f64) -> f64 {
if self.lambda_0 == 0.0 {
return 0.0;
}
let exponent = ((voltage - self.theta) / self.delta_v).clamp(-745.0, 20.0);
let hazard = self.lambda_0 * exponent.exp();
(1.0 - (-hazard * self.dt).exp()).clamp(0.0, 1.0)
}
pub fn step(&mut self, current: f64) -> i32 {
if !current.is_finite() || !self.valid_runtime() {
return 0;
}
let Some((v_candidate, eta_candidate)) = self.advance_subthreshold(current) else {
return 0;
};
self.v = v_candidate;
self.eta = eta_candidate;
if self.rng.random::<f64>() < self.spike_probability(self.v) {
self.v = self.v_reset;
self.eta += self.eta_increment;
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = self.v_rest;
self.eta = 0.0;
self.rng = Xoshiro256PlusPlus::seed_from_u64(self.seed);
}
}
#[derive(Clone, Debug)]
pub struct AvRonCardiacNeuron {
pub v: f64,
pub h: f64,
pub n: f64,
pub s: f64,
pub g_na: f64,
pub g_k: f64,
pub g_s: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_s: f64,
pub e_l: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl AvRonCardiacNeuron {
pub fn new() -> Self {
Self {
v: -60.0,
h: 0.6,
n: 0.3,
s: 0.5,
g_na: 80.0,
g_k: 40.0,
g_s: 20.0,
g_l: 0.1,
e_na: 40.0,
e_k: -80.0,
e_s: -25.0,
e_l: -60.0,
dt: 0.02,
v_threshold: -20.0,
}
}
fn finite_values(values: &[f64]) -> bool {
values.iter().all(|value| value.is_finite())
}
fn gate_in_range(value: f64) -> bool {
(0.0..=1.0).contains(&value)
}
fn bounded_exp(value: f64) -> f64 {
value.clamp(-745.0, 709.0).exp()
}
fn sigmoid_pos(value: f64) -> f64 {
1.0 / (1.0 + Self::bounded_exp(-value))
}
fn sigmoid_neg(value: f64) -> f64 {
1.0 / (1.0 + Self::bounded_exp(value))
}
fn valid_runtime(&self) -> bool {
Self::finite_values(&[
self.v,
self.h,
self.n,
self.s,
self.g_na,
self.g_k,
self.g_s,
self.g_l,
self.e_na,
self.e_k,
self.e_s,
self.e_l,
self.dt,
self.v_threshold,
]) && self.dt > 0.0
&& self.g_na >= 0.0
&& self.g_k >= 0.0
&& self.g_s >= 0.0
&& self.g_l >= 0.0
&& Self::gate_in_range(self.h)
&& Self::gate_in_range(self.n)
&& Self::gate_in_range(self.s)
}
fn rates(&self, voltage: f64) -> [f64; 7] {
[
Self::sigmoid_pos((voltage + 40.0) / 7.0),
Self::sigmoid_neg((voltage + 45.0) / 5.0),
Self::sigmoid_pos((voltage + 40.0) / 15.0),
Self::sigmoid_neg((voltage + 35.0) / 3.0),
1.0 + 12.0 * Self::sigmoid_neg((voltage + 50.0) / 8.0),
1.0 + 8.0 * Self::sigmoid_neg((voltage + 35.0) / 8.0),
200.0 + 1000.0 * Self::sigmoid_neg((voltage + 30.0) / 5.0),
]
}
fn derivatives(&self, state: [f64; 4], current: f64) -> [f64; 4] {
let [voltage, h_gate, n_gate, s_gate] = state;
if !Self::finite_values(&state)
|| !Self::gate_in_range(h_gate)
|| !Self::gate_in_range(n_gate)
|| !Self::gate_in_range(s_gate)
{
return [f64::NAN; 4];
}
let rates = self.rates(voltage);
let i_na = self.g_na * rates[0].powi(3) * h_gate * (voltage - self.e_na);
let i_k = self.g_k * n_gate.powi(4) * (voltage - self.e_k);
let i_s = self.g_s * s_gate * (voltage - self.e_s);
let i_l = self.g_l * (voltage - self.e_l);
[
-i_na - i_k - i_s - i_l + current,
(rates[1] - h_gate) / rates[4],
(rates[2] - n_gate) / rates[5],
(rates[3] - s_gate) / rates[6],
]
}
fn add_scaled(state: [f64; 4], slope: [f64; 4], scale: f64) -> [f64; 4] {
[
state[0] + scale * slope[0],
state[1] + scale * slope[1],
state[2] + scale * slope[2],
state[3] + scale * slope[3],
]
}
fn rk4_candidate(&self, current: f64) -> Option<[f64; 4]> {
let state = [self.v, self.h, self.n, self.s];
let half_dt = 0.5 * self.dt;
let k1 = self.derivatives(state, current);
let k2 = self.derivatives(Self::add_scaled(state, k1, half_dt), current);
let k3 = self.derivatives(Self::add_scaled(state, k2, half_dt), current);
let k4 = self.derivatives(Self::add_scaled(state, k3, self.dt), current);
let candidate = [
state[0] + self.dt * (k1[0] + 2.0 * k2[0] + 2.0 * k3[0] + k4[0]) / 6.0,
state[1] + self.dt * (k1[1] + 2.0 * k2[1] + 2.0 * k3[1] + k4[1]) / 6.0,
state[2] + self.dt * (k1[2] + 2.0 * k2[2] + 2.0 * k3[2] + k4[2]) / 6.0,
state[3] + self.dt * (k1[3] + 2.0 * k2[3] + 2.0 * k3[3] + k4[3]) / 6.0,
];
if Self::finite_values(&candidate)
&& Self::gate_in_range(candidate[1])
&& Self::gate_in_range(candidate[2])
&& Self::gate_in_range(candidate[3])
{
Some(candidate)
} else {
None
}
}
pub fn step(&mut self, current: f64) -> i32 {
if !current.is_finite() || !self.valid_runtime() {
return 0;
}
let v_prev = self.v;
let Some(candidate) = self.rk4_candidate(current) else {
return 0;
};
self.v = candidate[0];
self.h = candidate[1];
self.n = candidate[2];
self.s = candidate[3];
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -60.0;
self.h = 0.6;
self.n = 0.3;
self.s = 0.5;
}
}
impl Default for AvRonCardiacNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct DurstewitzDopamineNeuron {
pub v: f64,
pub h_na: f64,
pub n_k: f64,
pub g_na: f64,
pub g_k: f64,
pub g_nmda: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_nmda: f64,
pub e_l: f64,
pub mg: f64,
pub d1_level: f64,
pub g_nmda_scale: f64,
pub g_k_scale: f64,
pub v_shift_na: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl DurstewitzDopamineNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
h_na: 0.7,
n_k: 0.2,
g_na: 45.0,
g_k: 18.0,
g_nmda: 0.5,
g_l: 0.02,
e_na: 55.0,
e_k: -80.0,
e_nmda: 0.0,
e_l: -65.0,
mg: 1.0,
d1_level: 0.0,
g_nmda_scale: 2.5,
g_k_scale: 1.5,
v_shift_na: -5.0,
dt: 0.05,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
let shift = self.d1_level * self.v_shift_na;
let m_inf = 1.0 / (1.0 + (-(self.v + 30.0 + shift) / 9.5).exp());
let h_inf = 1.0 / (1.0 + ((self.v + 53.0) / 7.0).exp());
let n_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 10.0).exp());
let tau_h = 0.5 + 14.0 / (1.0 + ((self.v + 50.0) / 12.0).exp());
let tau_n = 1.0 + 11.0 / (1.0 + ((self.v + 40.0) / 10.0).exp());
self.h_na += (h_inf - self.h_na) / tau_h * self.dt;
self.n_k += (n_inf - self.n_k) / tau_n * self.dt;
let g_eff_nmda = self.g_nmda * (1.0 + self.d1_level * (self.g_nmda_scale - 1.0));
let g_eff_k = self.g_k * (1.0 + self.d1_level * (self.g_k_scale - 1.0));
let mg_block = 1.0 / (1.0 + self.mg * (-0.062 * self.v).exp() / 3.57);
let i_na = self.g_na * m_inf.powi(3) * self.h_na * (self.v - self.e_na);
let i_k = g_eff_k * self.n_k.powi(4) * (self.v - self.e_k);
let i_nmda = g_eff_nmda * mg_block * (self.v - self.e_nmda);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_nmda - i_l + current) * self.dt;
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -65.0;
self.h_na = 0.7;
self.n_k = 0.2;
}
}
impl Default for DurstewitzDopamineNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct HillTononiNeuron {
pub v: f64,
pub h_na: f64,
pub n_k: f64,
pub m_h: f64,
pub h_t: f64,
pub na_i: f64,
pub g_na: f64,
pub g_k: f64,
pub g_h: f64,
pub g_t: f64,
pub g_kna: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_h: f64,
pub e_ca: f64,
pub e_l: f64,
pub na_pump_max: f64,
pub na_eq: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl HillTononiNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
h_na: 0.6,
n_k: 0.3,
m_h: 0.0,
h_t: 0.9,
na_i: 5.0,
g_na: 50.0,
g_k: 5.0,
g_h: 1.0,
g_t: 3.0,
g_kna: 1.33,
g_l: 0.02,
e_na: 50.0,
e_k: -90.0,
e_h: -43.0,
e_ca: 120.0,
e_l: -70.0,
na_pump_max: 20.0,
na_eq: 9.5,
dt: 0.05,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
let m_na_inf = 1.0 / (1.0 + (-(self.v + 38.0) / 10.0).exp());
let h_na_inf = 1.0 / (1.0 + ((self.v + 43.0) / 6.0).exp());
let n_k_inf = 1.0 / (1.0 + (-(self.v + 27.0) / 11.5).exp());
let m_h_inf = 1.0 / (1.0 + ((self.v + 75.0) / 5.5).exp());
let m_t_inf = 1.0 / (1.0 + (-(self.v + 59.0) / 6.2).exp());
let h_t_inf = 1.0 / (1.0 + ((self.v + 83.0) / 4.0).exp());
let w_kna = 0.37 / (1.0 + (38.7 / self.na_i.max(0.01)).powf(3.5));
let tau_h_na = (1.0 + 10.0 / (1.0 + ((self.v + 40.0) / 10.0).exp())).max(0.1);
let tau_n_k = (5.0 + 47.0 * (-(((self.v + 50.0) / 25.0).powi(2))).exp()).max(0.1);
let tau_m_h = (20.0
+ 1000.0 / (((self.v + 71.5) / 14.2).exp() + (-(self.v + 89.0) / 11.6).exp()))
.max(1.0);
let tau_h_t = if self.v < -81.0 {
(30.8 + 211.4 * ((self.v + 115.2) / 5.0).exp() / (1.0 + ((self.v + 86.0) / 3.2).exp()))
.max(0.1)
} else {
10.0
};
self.h_na += (h_na_inf - self.h_na) / tau_h_na * self.dt;
self.n_k += (n_k_inf - self.n_k) / tau_n_k * self.dt;
self.m_h += (m_h_inf - self.m_h) / tau_m_h * self.dt;
self.h_t += (h_t_inf - self.h_t) / tau_h_t * self.dt;
let i_na = self.g_na * m_na_inf.powi(3) * self.h_na * (self.v - self.e_na);
let i_k = self.g_k * self.n_k.powi(4) * (self.v - self.e_k);
let i_h = self.g_h * self.m_h * (self.v - self.e_h);
let i_t = self.g_t * m_t_inf.powi(2) * self.h_t * (self.v - self.e_ca);
let i_kna = self.g_kna * w_kna * (self.v - self.e_k);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_h - i_t - i_kna - i_l + current) * self.dt;
self.na_i = (self.na_i
+ (-0.001 * i_na - self.na_pump_max * (self.na_i / (self.na_i + self.na_eq)))
* self.dt)
.max(0.0);
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -65.0;
self.h_na = 0.6;
self.n_k = 0.3;
self.m_h = 0.0;
self.h_t = 0.9;
self.na_i = 5.0;
}
}
impl Default for HillTononiNeuron {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct BertramPhantomBurster {
pub v: f64,
pub s1: f64,
pub s2: f64,
pub g_ca: f64,
pub g_k: f64,
pub g_s1: f64,
pub g_s2: f64,
pub g_l: f64,
pub e_ca: f64,
pub e_k: f64,
pub e_l: f64,
pub c_m: f64,
pub v_m: f64,
pub s_m: f64,
pub v_n: f64,
pub s_n: f64,
pub v_s1: f64,
pub s_s1: f64,
pub v_s2: f64,
pub s_s2: f64,
pub tau_s1: f64,
pub tau_s2: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl BertramPhantomBurster {
pub fn new() -> Self {
Self {
v: -50.0,
s1: 0.1,
s2: 0.1,
g_ca: 3.6,
g_k: 10.0,
g_s1: 4.0,
g_s2: 4.0,
g_l: 0.2,
e_ca: 25.0,
e_k: -75.0,
e_l: -40.0,
c_m: 5.3,
v_m: -20.0,
s_m: 12.0,
v_n: -16.0,
s_n: 5.6,
v_s1: -40.0,
s_s1: 10.0,
v_s2: -42.0,
s_s2: 0.4,
tau_s1: 20000.0,
tau_s2: 100000.0,
dt: 0.5,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
let m_inf = 1.0 / (1.0 + (-(self.v - self.v_m) / self.s_m).exp());
let n_inf = 1.0 / (1.0 + (-(self.v - self.v_n) / self.s_n).exp());
let s1_inf = 1.0 / (1.0 + (-(self.v - self.v_s1) / self.s_s1).exp());
let s2_inf = 1.0 / (1.0 + (-(self.v - self.v_s2) / self.s_s2).exp());
let i_ca = self.g_ca * m_inf * (self.v - self.e_ca);
let i_k = self.g_k * n_inf * (self.v - self.e_k);
let i_s1 = self.g_s1 * self.s1 * (self.v - self.e_k);
let i_s2 = self.g_s2 * self.s2 * (self.v - self.e_k);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_ca - i_k - i_s1 - i_s2 - i_l + current) / self.c_m * self.dt;
self.s1 += (s1_inf - self.s1) / self.tau_s1 * self.dt;
self.s2 += (s2_inf - self.s2) / self.tau_s2 * self.dt;
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -50.0;
self.s1 = 0.1;
self.s2 = 0.1;
}
}
impl Default for BertramPhantomBurster {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct YamadaNeuron {
pub v: f64,
pub n: f64,
pub q: f64,
pub g_na: f64,
pub g_k: f64,
pub g_q: f64,
pub g_l: f64,
pub e_na: f64,
pub e_k: f64,
pub e_q: f64,
pub e_l: f64,
pub tau_q: f64,
pub dt: f64,
pub v_threshold: f64,
}
impl YamadaNeuron {
pub fn new() -> Self {
Self {
v: -60.0,
n: 0.1,
q: 0.0,
g_na: 20.0,
g_k: 10.0,
g_q: 5.0,
g_l: 0.5,
e_na: 60.0,
e_k: -80.0,
e_q: -80.0,
e_l: -60.0,
tau_q: 300.0,
dt: 0.05,
v_threshold: -20.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let v_prev = self.v;
let m_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 9.5).exp());
let n_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 10.0).exp());
let q_inf = 1.0 / (1.0 + (-(self.v + 50.0) / 10.0).exp());
let tau_n = 1.0 + 7.5 / (1.0 + ((self.v + 40.0) / 12.0).exp());
let i_na = self.g_na * m_inf.powi(3) * (1.0 - self.n) * (self.v - self.e_na);
let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
let i_q = self.g_q * self.q * (self.v - self.e_q);
let i_l = self.g_l * (self.v - self.e_l);
self.v += (-i_na - i_k - i_q - i_l + current) * self.dt;
self.n += (n_inf - self.n) / tau_n * self.dt;
self.q += (q_inf - self.q) / self.tau_q * self.dt;
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
self.v = -60.0;
self.n = 0.1;
self.q = 0.0;
}
}
impl Default for YamadaNeuron {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hh_fires() {
let mut n = HodgkinHuxleyNeuron::new();
let t: i32 = (0..100).map(|_| n.step(10.0)).sum();
assert!(t > 0);
}
#[test]
fn traub_fires() {
let mut n = TraubMilesNeuron::new();
let t: i32 = (0..200).map(|_| n.step(5.0)).sum();
assert!(t > 0);
}
#[test]
fn wb_fires() {
let mut n = WangBuzsakiNeuron::new();
let t: i32 = (0..200).map(|_| n.step(2.0)).sum();
assert!(t > 0);
}
#[test]
fn cs_fires() {
let mut n = ConnorStevensNeuron::new();
let t: i32 = (0..200).map(|_| n.step(10.0)).sum();
assert!(t > 0);
}
#[test]
fn destexhe_fires() {
let mut n = DestexheThalamicNeuron::new();
let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
assert!(t > 0);
}
#[test]
fn hb_fires() {
let mut n = HuberBraunNeuron::new();
let t: i32 = (0..5000).map(|_| n.step(10.0)).sum();
assert!(t > 0);
}
#[test]
fn golomb_fires() {
let mut n = GolombFSNeuron::new();
let t: i32 = (0..2000).map(|_| n.step(200.0)).sum();
assert!(t > 0);
}
#[test]
fn pospischil_fires() {
let mut n = PospischilNeuron::new();
let t: i32 = (0..200).map(|_| n.step(5.0)).sum();
assert!(t > 0);
}
#[test]
fn mainen_fires() {
let mut n = MainenSejnowskiNeuron::new();
let t: i32 = (0..5000).map(|_| n.step(500.0)).sum();
assert!(t > 0);
}
#[test]
fn purkinje_fires() {
let mut n = DeSchutterPurkinjeNeuron::new();
let t: i32 = (0..5000).map(|_| n.step(200.0)).sum();
assert!(t > 0);
}
#[test]
fn plant_r15_fires() {
let mut n = PlantR15Neuron::new();
let t: i32 = (0..500).map(|_| n.step(2.0)).sum();
assert!(t > 0);
}
#[test]
fn prescott_fires() {
let mut n = PrescottNeuron::new();
let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
assert!(t > 0);
}
#[test]
fn mn_fires() {
let mut n = MihalasNieburNeuron::new();
let t: i32 = (0..100).map(|_| n.step(5.0)).sum();
assert!(t > 0);
}
#[test]
fn glif_fires() {
let mut n = GLIFNeuron::new();
let t: i32 = (0..200).map(|_| n.step(30.0)).sum();
assert!(t > 0);
}
#[test]
fn gif_pop_fires() {
let mut n = GIFPopulationNeuron::new(42);
let t: i32 = (0..1000).map(|_| n.step(30.0)).sum();
assert!(t > 0);
}
#[test]
fn avron_fires() {
let mut n = AvRonCardiacNeuron::new();
let t: i32 = (0..2000).map(|_| n.step(5.0)).sum();
assert!(t > 0);
}
#[test]
fn durstewitz_fires() {
let mut n = DurstewitzDopamineNeuron::new();
let t: i32 = (0..1000).map(|_| n.step(3.0)).sum();
assert!(t > 0);
}
#[test]
fn hill_tononi_fires() {
let mut n = HillTononiNeuron::new();
let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
assert!(t > 0);
}
#[test]
fn bertram_fires() {
let mut n = BertramPhantomBurster::new();
let t: i32 = (0..10000).map(|_| n.step(200.0)).sum();
assert!(t > 0);
}
#[test]
fn yamada_fires() {
let mut n = YamadaNeuron::new();
let t: i32 = (0..2000).map(|_| n.step(5.0)).sum();
assert!(t > 0);
}
#[test]
fn hh_silent_without_input() {
let mut n = HodgkinHuxleyNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn hh_reset_clears_state() {
let mut n = HodgkinHuxleyNeuron::new();
for _ in 0..100 {
n.step(10.0);
}
n.reset();
assert!((n.v - (-65.0)).abs() < 1e-10);
assert!((n.m - 0.05).abs() < 1e-10);
assert!((n.h - 0.6).abs() < 1e-10);
assert!((n.n - 0.32).abs() < 1e-10);
}
#[test]
fn hh_extreme_input_bounded() {
let mut n = HodgkinHuxleyNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn hh_gates_bounded() {
let mut n = HodgkinHuxleyNeuron::new();
for _ in 0..500 {
n.step(10.0);
}
assert!(n.m >= 0.0 && n.m <= 1.0, "m={}", n.m);
assert!(n.h >= 0.0 && n.h <= 1.0, "h={}", n.h);
assert!(n.n >= 0.0 && n.n <= 1.0, "n={}", n.n);
}
#[test]
fn hh_negative_input_no_crash() {
let mut n = HodgkinHuxleyNeuron::new();
for _ in 0..200 {
n.step(-20.0);
}
assert!(n.v.is_finite());
}
#[test]
fn hh_nan_input_no_panic() {
let mut n = HodgkinHuxleyNeuron::new();
n.step(f64::NAN);
}
#[test]
fn hh_sodium_potassium_opposition() {
let mut n = HodgkinHuxleyNeuron::new();
for _ in 0..50 {
n.step(10.0);
}
assert!(n.n > 0.32, "K activation n should increase during spiking");
}
#[test]
fn traub_silent_without_input() {
let mut n = TraubMilesNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn traub_reset_clears_state() {
let mut n = TraubMilesNeuron::new();
for _ in 0..100 {
n.step(5.0);
}
n.reset();
assert!((n.v - (-67.0)).abs() < 1e-10);
}
#[test]
fn traub_extreme_bounded() {
let mut n = TraubMilesNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn traub_gates_bounded() {
let mut n = TraubMilesNeuron::new();
for _ in 0..500 {
n.step(5.0);
}
assert!(n.m >= 0.0 && n.m <= 1.01);
assert!(n.h >= 0.0 && n.h <= 1.01);
assert!(n.n >= 0.0 && n.n <= 1.01);
}
#[test]
fn traub_weak_negative_no_crash() {
let mut n = TraubMilesNeuron::new();
for _ in 0..200 {
n.step(-5.0);
}
assert!(n.v.is_finite());
}
#[test]
fn traub_nan_no_panic() {
let mut n = TraubMilesNeuron::new();
n.step(f64::NAN);
}
#[test]
fn traub_rk4_reference_point() {
let mut n = TraubMilesNeuron::new();
n.v = -63.5;
n.m = 0.08;
n.h = 0.55;
n.n = 0.32;
let spike = n.step(4.0);
assert_eq!(spike, 0);
assert!((n.v - (-65.6638958700765)).abs() < 1e-13);
assert!((n.m - 0.04237301812907925).abs() < 1e-15);
assert!((n.h - 0.5626824931070477).abs() < 1e-15);
assert!((n.n - 0.30356298261126924).abs() < 1e-15);
assert!((n.v - (-65.66233161606698)).abs() > 1e-3);
}
#[test]
fn wb_silent_without_input() {
let mut n = WangBuzsakiNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn wb_reset_clears_state() {
let mut n = WangBuzsakiNeuron::new();
for _ in 0..100 {
n.step(2.0);
}
n.reset();
assert!((n.v - (-65.0)).abs() < 1e-10);
}
#[test]
fn wb_extreme_bounded() {
let mut n = WangBuzsakiNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn wb_fast_spiking_high_rate() {
let mut n = WangBuzsakiNeuron::new();
let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
assert!(t > 10, "WB FS should produce many spikes, got {}", t);
}
#[test]
fn wb_gates_bounded() {
let mut n = WangBuzsakiNeuron::new();
for _ in 0..500 {
n.step(2.0);
}
assert!(n.h >= 0.0 && n.h <= 1.0);
assert!(n.n >= 0.0 && n.n <= 1.0);
}
#[test]
fn wb_negative_no_crash() {
let mut n = WangBuzsakiNeuron::new();
for _ in 0..200 {
n.step(-10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn wb_nan_no_panic() {
let mut n = WangBuzsakiNeuron::new();
n.step(f64::NAN);
}
#[test]
fn cs_silent_without_input() {
let mut n = ConnorStevensNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn cs_reset_clears_state() {
let mut n = ConnorStevensNeuron::new();
for _ in 0..100 {
n.step(10.0);
}
n.reset();
assert!((n.v - (-68.0)).abs() < 1e-10);
}
#[test]
fn cs_extreme_bounded() {
let mut n = ConnorStevensNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn cs_a_type_delays_spike() {
let mut n_with_a = ConnorStevensNeuron::new();
let mut n_no_a = ConnorStevensNeuron::new();
n_no_a.g_a = 0.0;
let spikes_with: i32 = (0..50).map(|_| n_with_a.step(8.0)).sum();
let spikes_no: i32 = (0..50).map(|_| n_no_a.step(8.0)).sum();
assert!(
spikes_no >= spikes_with,
"without A-type K, should fire more: no_a={} vs with_a={}",
spikes_no,
spikes_with
);
}
#[test]
fn cs_gates_bounded() {
let mut n = ConnorStevensNeuron::new();
for _ in 0..500 {
n.step(10.0);
}
assert!(n.a >= 0.0 && n.a <= 1.5, "a={}", n.a); assert!(n.b >= 0.0 && n.b <= 1.0, "b={}", n.b);
}
#[test]
fn cs_negative_no_crash() {
let mut n = ConnorStevensNeuron::new();
for _ in 0..200 {
n.step(-20.0);
}
assert!(n.v.is_finite());
}
#[test]
fn cs_invalid_current_preserves_state() {
let mut n = ConnorStevensNeuron::new();
let before = (n.v, n.m, n.h, n.n, n.a, n.b);
assert_eq!(n.step(f64::NAN), 0);
assert_eq!((n.v, n.m, n.h, n.n, n.a, n.b), before);
}
#[test]
fn cs_corrupt_runtime_state_preserves_state() {
let mut n = ConnorStevensNeuron::new();
n.b = f64::INFINITY;
let before = (n.v, n.m, n.h, n.n, n.a, n.b);
assert_eq!(n.step(6.0), 0);
assert_eq!((n.v, n.m, n.h, n.n, n.a, n.b), before);
}
#[test]
fn destexhe_no_crash_zero_input() {
let mut n = DestexheThalamicNeuron::new();
let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
assert!(n.v.is_finite());
}
#[test]
fn destexhe_reset_clears_state() {
let mut n = DestexheThalamicNeuron::new();
for _ in 0..200 {
n.step(5.0);
}
n.reset();
assert!((n.v - (-65.0)).abs() < 1e-10);
}
#[test]
fn destexhe_extreme_bounded() {
let mut n = DestexheThalamicNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn destexhe_t_current_rebound() {
let v_hyp = -90.0_f64;
let h_t_inf = 1.0 / (1.0 + ((v_hyp + 81.0) / 4.0).exp());
assert!(h_t_inf > 0.9, "h_t_inf should be ~1 at V=-90: {}", h_t_inf);
let mut n = DestexheThalamicNeuron::new();
for _ in 0..500 {
n.step(-5.0);
}
let v_before_release = n.v;
for _ in 0..500 {
n.step(0.0);
}
assert!(n.v.is_finite());
assert!(
n.v > v_before_release,
"V should increase after release (rebound)"
);
}
#[test]
fn destexhe_negative_no_crash() {
let mut n = DestexheThalamicNeuron::new();
for _ in 0..200 {
n.step(-20.0);
}
assert!(n.v.is_finite());
}
#[test]
fn destexhe_nan_no_panic() {
let mut n = DestexheThalamicNeuron::new();
n.step(f64::NAN);
}
#[test]
fn hb_silent_without_input() {
let mut n = HuberBraunNeuron::new();
let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
assert!(n.v.is_finite());
}
#[test]
fn hb_reset_clears_state() {
let mut n = HuberBraunNeuron::new();
for _ in 0..200 {
n.step(10.0);
}
n.reset();
assert!((n.v - (-50.0)).abs() < 1e-10);
}
#[test]
fn hb_extreme_bounded() {
let mut n = HuberBraunNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn hb_negative_no_crash() {
let mut n = HuberBraunNeuron::new();
for _ in 0..500 {
n.step(-10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn hb_nan_no_panic() {
let mut n = HuberBraunNeuron::new();
n.step(f64::NAN);
}
#[test]
fn golomb_silent_without_input() {
let mut n = GolombFSNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn golomb_reset_clears_state() {
let mut n = GolombFSNeuron::new();
for _ in 0..100 {
n.step(200.0);
}
n.reset();
assert!((n.v - (-65.0)).abs() < 1e-10);
}
#[test]
fn golomb_extreme_bounded() {
let mut n = GolombFSNeuron::new();
for _ in 0..200 {
n.step(200.0);
}
assert!(n.v.is_finite());
}
#[test]
fn golomb_kv3_enables_fast_spiking() {
let mut n = GolombFSNeuron::new();
let t: i32 = (0..5000).map(|_| n.step(300.0)).sum();
assert!(t > 0, "Golomb FS should fire with high input, got {}", t);
}
#[test]
fn golomb_negative_no_crash() {
let mut n = GolombFSNeuron::new();
for _ in 0..200 {
n.step(-100.0);
}
assert!(n.v.is_finite());
}
#[test]
fn golomb_nan_no_panic() {
let mut n = GolombFSNeuron::new();
n.step(f64::NAN);
}
#[test]
fn pospischil_silent_without_input() {
let mut n = PospischilNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn pospischil_reset_clears_state() {
let mut n = PospischilNeuron::new();
for _ in 0..100 {
n.step(5.0);
}
n.reset();
assert!((n.v - (-70.0)).abs() < 1e-10);
}
#[test]
fn pospischil_moderate_input_stable() {
let mut n = PospischilNeuron::new();
for _ in 0..200 {
n.step(10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn pospischil_m_current_present() {
let mut n = PospischilNeuron::new();
for _ in 0..200 {
n.step(5.0);
}
assert!(n.p > 0.0, "M-current (p) should activate during spiking");
}
#[test]
fn pospischil_negative_no_crash() {
let mut n = PospischilNeuron::new();
for _ in 0..200 {
n.step(-10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn pospischil_nan_no_panic() {
let mut n = PospischilNeuron::new();
n.step(f64::NAN);
}
#[test]
fn mainen_stable_without_input() {
let mut n = MainenSejnowskiNeuron::new();
for _ in 0..500 {
n.step(0.0);
}
assert!(n.vs.is_finite());
assert!(n.va.is_finite());
}
#[test]
fn mainen_reset_clears_state() {
let mut n = MainenSejnowskiNeuron::new();
for _ in 0..100 {
n.step(500.0);
}
n.reset();
assert!((n.vs - (-65.0)).abs() < 1e-10);
assert!((n.va - (-65.0)).abs() < 1e-10);
}
#[test]
fn mainen_moderate_input_stable() {
let mut n = MainenSejnowskiNeuron::new();
for _ in 0..200 {
n.step(500.0);
}
let _ = n.vs; }
#[test]
fn mainen_two_compartments_coupled() {
let n = MainenSejnowskiNeuron::new();
assert!(n.kappa > 0.0, "coupling should be positive");
}
#[test]
fn mainen_weak_negative_no_crash() {
let mut n = MainenSejnowskiNeuron::new();
for _ in 0..200 {
n.step(-10.0);
}
assert!(n.vs.is_finite());
}
#[test]
fn mainen_nan_no_panic() {
let mut n = MainenSejnowskiNeuron::new();
n.step(f64::NAN);
}
#[test]
fn purkinje_silent_without_input() {
let mut n = DeSchutterPurkinjeNeuron::new();
let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
assert!(n.v.is_finite());
}
#[test]
fn purkinje_reset_clears_state() {
let mut n = DeSchutterPurkinjeNeuron::new();
for _ in 0..100 {
n.step(50.0);
}
n.reset();
assert!((n.v - (-68.0)).abs() < 1e-10);
assert!((n.ca - 0.0001).abs() < 1e-10);
}
#[test]
fn purkinje_extreme_bounded() {
let mut n = DeSchutterPurkinjeNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn purkinje_ca_rises_with_spiking() {
let mut n = DeSchutterPurkinjeNeuron::new();
let ca_init = n.ca;
for _ in 0..5000 {
n.step(200.0);
}
assert!(n.ca > ca_init, "Ca²⁺ should rise during spiking");
}
#[test]
fn purkinje_kca_activated_by_calcium() {
let mut n = DeSchutterPurkinjeNeuron::new();
for _ in 0..5000 {
n.step(200.0);
}
assert!(
n.q_kca > 0.0,
"KCa should activate with Ca²⁺: q={}",
n.q_kca
);
}
#[test]
fn purkinje_negative_no_crash() {
let mut n = DeSchutterPurkinjeNeuron::new();
for _ in 0..200 {
n.step(-50.0);
}
assert!(n.v.is_finite());
}
#[test]
fn plant_r15_silent_without_input() {
let mut n = PlantR15Neuron::new();
for _ in 0..500 {
n.step(0.0);
}
assert!(n.v.is_finite());
}
#[test]
fn plant_r15_reset_clears_state() {
let mut n = PlantR15Neuron::new();
for _ in 0..100 {
n.step(2.0);
}
n.reset();
assert!((n.v - (-50.0)).abs() < 1e-10);
assert!((n.ca - 0.1).abs() < 1e-10);
}
#[test]
fn plant_r15_moderate_input_stable() {
let mut n = PlantR15Neuron::new();
for _ in 0..500 {
n.step(2.0);
}
assert!(n.v.is_finite());
}
#[test]
fn plant_r15_ca_dynamics() {
let mut n = PlantR15Neuron::new();
for _ in 0..500 {
n.step(2.0);
}
assert!(n.ca >= 0.0, "Ca²⁺ must be non-negative");
assert!(n.ca.is_finite());
}
#[test]
fn plant_r15_weak_negative_no_crash() {
let mut n = PlantR15Neuron::new();
for _ in 0..200 {
n.step(-1.0);
}
assert!(n.v.is_finite());
}
#[test]
fn plant_r15_nan_no_panic() {
let mut n = PlantR15Neuron::new();
n.step(f64::NAN);
}
#[test]
fn prescott_zero_input_stable() {
let mut n = PrescottNeuron::new();
let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
assert!(n.v.is_finite());
}
#[test]
fn prescott_reset_clears_state() {
let mut n = PrescottNeuron::new();
for _ in 0..100 {
n.step(5.0);
}
n.reset();
assert!((n.v - (-65.0)).abs() < 1e-10);
assert!((n.w - 0.0).abs() < 1e-10);
}
#[test]
fn prescott_rk4_reference_point() {
let mut n = PrescottNeuron::new();
assert_eq!(n.step(50.0), 0);
assert!((n.v - (-44.498914201492525)).abs() < 1e-12);
assert!((n.w - 1.4035864179018786e-05).abs() < 1e-17);
}
#[test]
fn prescott_extreme_bounded() {
let mut n = PrescottNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn prescott_slow_var_adapts() {
let mut n = PrescottNeuron::new();
for _ in 0..500 {
n.step(5.0);
}
assert!(n.w > 0.0, "slow variable w should activate during spiking");
}
#[test]
fn prescott_negative_no_crash() {
let mut n = PrescottNeuron::new();
for _ in 0..200 {
n.step(-10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn prescott_nan_no_panic() {
let mut n = PrescottNeuron::new();
n.step(f64::NAN);
}
#[test]
fn mn_silent_without_input() {
let mut n = MihalasNieburNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn mn_reset_clears_state() {
let mut n = MihalasNieburNeuron::new();
for _ in 0..100 {
n.step(5.0);
}
n.reset();
assert!((n.v - n.v_rest).abs() < 1e-10);
assert!((n.theta - n.theta_reset).abs() < 1e-10);
}
#[test]
fn mn_rk4_reference_point() {
let mut n = MihalasNieburNeuron::new();
assert_eq!(n.step(0.5), 0);
assert!((n.v - 0.04758125).abs() < 1e-12);
assert!((n.theta - 1.0).abs() < 1e-15);
assert!((n.i1 - 0.0).abs() < 1e-15);
assert!((n.i2 - 0.0).abs() < 1e-15);
}
#[test]
fn mn_spike_reset_uses_b() {
let mut n = MihalasNieburNeuron::new();
n.v = 0.99;
n.b = 0.5;
n.r1 = 1.25;
n.r2 = -0.25;
assert_eq!(n.step(2.0), 1);
assert!((n.v - 0.5430570625).abs() < 1e-12);
assert!((n.i1 - 1.25).abs() < 1e-15);
assert!((n.i2 - (-0.25)).abs() < 1e-15);
}
#[test]
fn mn_invalid_input_preserves_state() {
let mut n = MihalasNieburNeuron::new();
n.v = 0.2;
n.i1 = 0.3;
let before = (n.v, n.theta, n.i1, n.i2);
assert_eq!(n.step(f64::NAN), 0);
assert_eq!((n.v, n.theta, n.i1, n.i2), before);
}
#[test]
fn mn_extreme_bounded() {
let mut n = MihalasNieburNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn mn_adaptive_threshold() {
let mut n = MihalasNieburNeuron::new();
n.a = 0.1;
for _ in 0..100 {
n.step(5.0);
}
assert!(n.theta.is_finite());
}
#[test]
fn mn_negative_no_crash() {
let mut n = MihalasNieburNeuron::new();
for _ in 0..200 {
n.step(-10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn mn_nan_no_panic() {
let mut n = MihalasNieburNeuron::new();
n.step(f64::NAN);
}
#[test]
fn glif_silent_without_input() {
let mut n = GLIFNeuron::new();
let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn glif_reset_clears_state() {
let mut n = GLIFNeuron::new();
for _ in 0..100 {
n.step(30.0);
}
n.reset();
assert!((n.v - n.v_rest).abs() < 1e-10);
assert!((n.i_asc1).abs() < 1e-10);
assert!((n.i_asc2).abs() < 1e-10);
}
#[test]
fn glif_rk4_reference_point() {
let mut n = GLIFNeuron::new();
n.v = -68.0;
n.theta = -45.0;
n.i_asc1 = 0.4;
n.i_asc2 = -0.2;
assert_eq!(n.step(4.0), 0);
assert!((n.v - (-67.7924658728125)).abs() < 1e-12);
assert!((n.theta - (-45.049541282631253)).abs() < 1e-12);
assert!((n.i_asc1 - 0.361935).abs() < 1e-12);
assert!((n.i_asc2 - (-0.19900249583333334)).abs() < 1e-10);
}
#[test]
fn glif_spike_reset_adds_candidate_threshold() {
let mut n = GLIFNeuron::new();
n.v = -51.0;
n.theta = -50.5;
n.delta_theta = 2.5;
n.r_asc1 = 1.25;
n.r_asc2 = -0.25;
assert_eq!(n.step(40.0), 1);
assert!((n.v - (-70.0)).abs() < 1e-12);
assert!((n.theta - (-47.9930331381625)).abs() < 1e-12);
assert!((n.i_asc1 - 1.25).abs() < 1e-12);
assert!((n.i_asc2 - (-0.25)).abs() < 1e-12);
}
#[test]
fn glif_invalid_input_preserves_state() {
let mut n = GLIFNeuron::new();
n.v = -68.0;
n.i_asc1 = 0.4;
let before = (n.v, n.theta, n.i_asc1, n.i_asc2);
assert_eq!(n.step(f64::NAN), 0);
assert_eq!((n.v, n.theta, n.i_asc1, n.i_asc2), before);
}
#[test]
fn glif_extreme_bounded() {
let mut n = GLIFNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn glif_threshold_adapts_after_spike() {
let mut n = GLIFNeuron::new();
let theta_init = n.theta;
for _ in 0..200 {
n.step(30.0);
}
assert!(
n.theta > theta_init,
"theta should increase after spikes (delta_theta > 0)"
);
}
#[test]
fn glif_afterspike_currents() {
let mut n = GLIFNeuron::new();
for _ in 0..200 {
n.step(30.0);
}
assert!(n.v.is_finite());
}
#[test]
fn glif_negative_no_crash() {
let mut n = GLIFNeuron::new();
for _ in 0..200 {
n.step(-30.0);
}
assert!(n.v.is_finite());
}
#[test]
fn gif_pop_exact_subthreshold_reference_point() {
let mut n = GIFPopulationNeuron::new(7);
n.v = -68.0;
n.eta = 0.4;
assert_eq!(n.step(4.0), 0);
assert!((n.v - (-67.8370206677805)).abs() < 1e-12);
assert!((n.eta - 0.398004991677073).abs() < 1e-15);
}
#[test]
fn gif_pop_forced_spike_adds_decayed_adaptation() {
let mut n = GIFPopulationNeuron::new(42);
n.v = -51.0;
n.eta = 0.3;
n.theta = -90.0;
n.lambda_0 = 1.0e9;
assert_eq!(n.step(0.0), 1);
assert!((n.v - n.v_reset).abs() < 1e-12);
assert!((n.eta - 5.298503743757805).abs() < 1e-15);
}
#[test]
fn gif_pop_invalid_input_preserves_state() {
let mut n = GIFPopulationNeuron::new(42);
n.v = -62.0;
n.eta = 0.75;
let before = (n.v, n.eta);
assert_eq!(n.step(f64::NAN), 0);
n.tau_m = 0.0;
assert_eq!(n.step(1.0), 0);
assert_eq!((n.v, n.eta), before);
}
#[test]
fn gif_pop_seeded_reset_replays() {
let mut n = GIFPopulationNeuron::new(123);
n.theta = -90.0;
n.lambda_0 = 1.0e9;
let first: Vec<i32> = (0..3).map(|_| n.step(0.0)).collect();
n.reset();
let replay: Vec<i32> = (0..3).map(|_| n.step(0.0)).collect();
assert_eq!(first, replay);
assert!(n.eta > n.eta_increment);
}
#[test]
fn gif_pop_negative_drive_remains_finite() {
let mut n = GIFPopulationNeuron::new(42);
for _ in 0..200 {
n.step(-30.0);
}
assert!(n.v.is_finite());
assert!(n.eta.is_finite());
}
#[test]
fn avron_rk4_reference_point() {
let mut n = AvRonCardiacNeuron::new();
n.v = -55.0;
n.h = 0.55;
n.n = 0.35;
n.s = 0.45;
assert_eq!(n.step(2.0), 0);
assert!((n.v - (-50.0840498399381)).abs() < 1e-12);
assert!((n.h - 0.5506609782132562).abs() < 1e-15);
assert!((n.n - 0.34988677751350306).abs() < 1e-15);
assert!((n.s - 0.4500091998827305).abs() < 1e-15);
}
#[test]
fn avron_invalid_input_preserves_state() {
let mut n = AvRonCardiacNeuron::new();
n.v = -52.0;
n.h = 0.4;
n.n = 0.2;
n.s = 0.8;
let before = (n.v, n.h, n.n, n.s);
assert_eq!(n.step(f64::NAN), 0);
n.dt = 0.0;
assert_eq!(n.step(1.0), 0);
assert_eq!((n.v, n.h, n.n, n.s), before);
}
#[test]
fn avron_rejects_corrupted_gate_state() {
let mut n = AvRonCardiacNeuron::new();
n.h = 1.2;
let before = (n.v, n.h, n.n, n.s);
assert_eq!(n.step(1.0), 0);
assert_eq!((n.v, n.h, n.n, n.s), before);
}
#[test]
fn avron_slow_current_remains_physical() {
let mut n = AvRonCardiacNeuron::new();
for _ in 0..1000 {
n.step(5.0);
assert!((0.0..=1.0).contains(&n.h));
assert!((0.0..=1.0).contains(&n.n));
assert!((0.0..=1.0).contains(&n.s));
}
assert!(n.v.is_finite());
}
#[test]
fn avron_reset_clears_state() {
let mut n = AvRonCardiacNeuron::new();
n.v = -40.0;
n.h = 0.1;
n.n = 0.9;
n.s = 0.2;
n.reset();
assert_eq!((n.v, n.h, n.n, n.s), (-60.0, 0.6, 0.3, 0.5));
}
#[test]
fn durstewitz_low_activity_zero_input() {
let mut n = DurstewitzDopamineNeuron::new();
let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
assert!(n.v.is_finite());
}
#[test]
fn durstewitz_reset_clears_state() {
let mut n = DurstewitzDopamineNeuron::new();
for _ in 0..100 {
n.step(3.0);
}
n.reset();
assert!((n.v - (-65.0)).abs() < 1e-10);
}
#[test]
fn durstewitz_extreme_bounded() {
let mut n = DurstewitzDopamineNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn durstewitz_d1_modulation() {
let mut n_d1 = DurstewitzDopamineNeuron::new();
n_d1.d1_level = 1.0;
let mut n_no = DurstewitzDopamineNeuron::new();
n_no.d1_level = 0.0;
for _ in 0..1000 {
n_d1.step(3.0);
}
for _ in 0..1000 {
n_no.step(3.0);
}
assert!(n_d1.v.is_finite() && n_no.v.is_finite());
}
#[test]
fn durstewitz_mg_block() {
let n = DurstewitzDopamineNeuron::new();
let block = 1.0 / (1.0 + n.mg * (-0.062 * n.v).exp() / 3.57);
assert!(block < 0.1, "Mg²⁺ block at rest should be high: {}", block);
}
#[test]
fn durstewitz_negative_no_crash() {
let mut n = DurstewitzDopamineNeuron::new();
for _ in 0..200 {
n.step(-10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn hill_tononi_silent_without_input() {
let mut n = HillTononiNeuron::new();
let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
assert!(n.v.is_finite());
}
#[test]
fn hill_tononi_reset_clears_state() {
let mut n = HillTononiNeuron::new();
for _ in 0..100 {
n.step(5.0);
}
n.reset();
assert!((n.v - (-65.0)).abs() < 1e-10);
assert!((n.na_i - 5.0).abs() < 1e-10);
}
#[test]
fn hill_tononi_extreme_bounded() {
let mut n = HillTononiNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn hill_tononi_na_accumulation() {
let mut n = HillTononiNeuron::new();
for _ in 0..500 {
n.step(5.0);
}
assert!(n.na_i.is_finite());
assert!(n.na_i >= 0.0, "Na_i must be non-negative");
}
#[test]
fn hill_tononi_negative_no_crash() {
let mut n = HillTononiNeuron::new();
for _ in 0..200 {
n.step(-10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn hill_tononi_nan_no_panic() {
let mut n = HillTononiNeuron::new();
n.step(f64::NAN);
}
#[test]
fn bertram_silent_without_input() {
let mut n = BertramPhantomBurster::new();
for _ in 0..500 {
n.step(0.0);
}
assert!(n.v.is_finite());
}
#[test]
fn bertram_reset_clears_state() {
let mut n = BertramPhantomBurster::new();
for _ in 0..1000 {
n.step(200.0);
}
n.reset();
assert!((n.v - (-50.0)).abs() < 1e-10);
assert!((n.s1 - 0.1).abs() < 1e-10);
assert!((n.s2 - 0.1).abs() < 1e-10);
}
#[test]
fn bertram_extreme_bounded() {
let mut n = BertramPhantomBurster::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn bertram_dual_slow_vars() {
let mut n = BertramPhantomBurster::new();
for _ in 0..10000 {
n.step(200.0);
}
assert!(n.s1 >= 0.0 && n.s1 <= 1.0, "s1={}", n.s1);
assert!(n.s2 >= 0.0 && n.s2 <= 1.0, "s2={}", n.s2);
}
#[test]
fn bertram_negative_no_crash() {
let mut n = BertramPhantomBurster::new();
for _ in 0..200 {
n.step(-100.0);
}
assert!(n.v.is_finite());
}
#[test]
fn bertram_nan_no_panic() {
let mut n = BertramPhantomBurster::new();
n.step(f64::NAN);
}
#[test]
fn yamada_silent_without_input() {
let mut n = YamadaNeuron::new();
let t: i32 = (0..500).map(|_| n.step(0.0)).sum();
assert_eq!(t, 0);
}
#[test]
fn yamada_reset_clears_state() {
let mut n = YamadaNeuron::new();
for _ in 0..100 {
n.step(5.0);
}
n.reset();
assert!((n.v - (-60.0)).abs() < 1e-10);
assert!((n.q - 0.0).abs() < 1e-10);
}
#[test]
fn yamada_extreme_bounded() {
let mut n = YamadaNeuron::new();
for _ in 0..200 {
n.step(1e4);
}
assert!(n.v.is_finite());
}
#[test]
fn yamada_slow_q_adapts() {
let mut n = YamadaNeuron::new();
for _ in 0..2000 {
n.step(5.0);
}
assert!(n.q > 0.0, "slow variable q should activate during spiking");
}
#[test]
fn yamada_negative_no_crash() {
let mut n = YamadaNeuron::new();
for _ in 0..200 {
n.step(-10.0);
}
assert!(n.v.is_finite());
}
#[test]
fn yamada_nan_no_panic() {
let mut n = YamadaNeuron::new();
n.step(f64::NAN);
}
}