#[derive(Clone, Debug)]
pub struct GradedSynapseNeuron {
pub v: f64, pub c_m: f64, pub g_l: f64, pub e_l: f64, pub g_in: f64, pub v_half: f64, pub k_release: f64, pub v_min: f64, pub v_max: f64, pub v_threshold: f64, pub dt: f64,
pub gain: f64,
}
impl Default for GradedSynapseNeuron {
fn default() -> Self {
Self::new()
}
}
impl GradedSynapseNeuron {
pub fn new() -> Self {
Self {
v: -60.0,
c_m: 1.0,
g_l: 0.05, e_l: -60.0,
g_in: 0.1, v_half: -40.0, k_release: 5.0, v_min: -80.0,
v_max: -10.0,
v_threshold: -35.0, dt: 0.1,
gain: 1.0,
}
}
pub fn release(&self) -> f64 {
1.0 / (1.0 + (-(self.v - self.v_half) / self.k_release).exp())
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let v_prev = self.v;
let dv = (-self.g_l * (self.v - self.e_l) + self.g_in * input) / self.c_m;
self.v += self.dt * dv;
self.v = self.v.clamp(self.v_min, self.v_max);
if !self.v.is_finite() {
self.v = self.e_l;
}
if self.v >= self.v_threshold && v_prev < self.v_threshold {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[derive(Clone, Debug)]
pub struct GapJunctionNeuron {
pub v: f64, pub c_m: f64, pub g_l: f64, pub e_l: f64, pub g_gap: f64, pub i_tonic: f64, pub v_threshold: f64,
pub v_reset: f64,
pub refractory: f64, pub refrac_timer: f64,
pub rect_v0: f64, pub rect_a: f64, pub rect_gmin: f64, pub dt: f64,
pub gain: f64,
}
impl Default for GapJunctionNeuron {
fn default() -> Self {
Self::new()
}
}
impl GapJunctionNeuron {
pub fn new() -> Self {
Self {
v: -65.0,
c_m: 1.0,
g_l: 0.1,
e_l: -65.0,
g_gap: 0.15, i_tonic: 0.0, v_threshold: -50.0,
v_reset: -65.0,
refractory: 2.0, refrac_timer: 0.0,
rect_v0: 30.0, rect_a: 0.1, rect_gmin: 0.1, dt: 0.1,
gain: 1.0,
}
}
#[inline]
fn rect_conductance(&self, v_j: f64) -> f64 {
self.rect_gmin
+ (1.0 - self.rect_gmin) / (1.0 + (self.rect_a * (v_j.abs() - self.rect_v0)).exp())
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
if self.refrac_timer > 0.0 {
self.refrac_timer -= self.dt;
return 0;
}
let v_j = input - self.v;
let g_eff = self.g_gap * self.rect_conductance(v_j);
let i_gap = g_eff * v_j;
let dv = (-self.g_l * (self.v - self.e_l) + i_gap + self.i_tonic) / self.c_m;
self.v += self.dt * dv;
self.v = self.v.clamp(-100.0, 40.0);
if !self.v.is_finite() {
self.v = self.e_l;
}
if self.v >= self.v_threshold {
self.v = self.v_reset;
self.refrac_timer = self.refractory;
return 1;
}
0
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[derive(Clone, Debug)]
pub struct FrankenhaeUserHuxleyAxon {
pub v: f64, pub m: f64, pub h: f64, pub n: f64, pub p: f64, pub c_m: f64, pub p_na: f64, pub p_k: f64, pub p_p: f64, pub g_l: f64, pub e_l: f64, pub na_ratio: f64, pub k_ratio: f64, pub v_t: f64, pub dt: f64,
pub sub_steps: usize,
pub gain: f64,
}
impl Default for FrankenhaeUserHuxleyAxon {
fn default() -> Self {
Self::new()
}
}
impl FrankenhaeUserHuxleyAxon {
pub fn new() -> Self {
Self {
v: 0.0, m: 0.005,
h: 0.8,
n: 0.01,
p: 0.01,
c_m: 2.0, p_na: 88.4, p_k: 0.29, p_p: 5.96, g_l: 30.3, e_l: 0.026, na_ratio: 0.12, k_ratio: 48.0, v_t: 25.3, dt: 0.5, sub_steps: 50, gain: 1.0,
}
}
#[inline]
fn ghk_current(v: f64, c_ratio: f64, v_t: f64) -> f64 {
if v.abs() < 0.01 {
c_ratio - 1.0
} else {
let u = v / v_t;
let exp_neg_u = (-u).exp();
u * (c_ratio - exp_neg_u) / (1.0 - exp_neg_u)
}
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let dt_sub = self.dt / self.sub_steps as f64;
let v_prev = self.v;
for _ in 0..self.sub_steps {
let v = self.v;
let am = if (v - 22.0).abs() < 0.1 {
1.87
} else {
0.36 * (v - 22.0) / (1.0 - (-(v - 22.0) / 3.0).exp())
};
let bm = if (v - 13.0).abs() < 0.1 {
1.87
} else {
0.4 * (13.0 - v) / (1.0 - ((v - 13.0) / 20.0).exp())
};
let ah = if (v + 10.0).abs() < 0.1 {
0.08
} else {
0.1 * (-10.0 - v) / (1.0 - ((v + 10.0) / 6.0).exp())
};
let bh = 4.5 / (1.0 + ((45.0 - v) / 10.0).exp());
let an = if (v - 13.0).abs() < 0.1 {
0.1
} else {
0.02 * (v - 13.0) / (1.0 - (-(v - 13.0) / 10.0).exp())
};
let bn = if (v - 23.0).abs() < 0.1 {
0.05
} else {
0.05 * (23.0 - v) / (1.0 - ((v - 23.0) / 10.0).exp())
};
let ap = if (v - 21.0).abs() < 0.1 {
0.04
} else {
0.006 * (v - 21.0) / (1.0 - (-(v - 21.0) / 2.0).exp())
};
let bp = if (v + 4.0).abs() < 0.1 {
0.04
} else {
0.09 * (-4.0 - v) / (1.0 - ((v + 4.0) / 2.0).exp())
};
let am = am.max(0.0);
let bm = bm.max(0.0);
let ah = ah.max(0.0);
let bh = bh.max(0.0);
let an = an.max(0.0);
let bn = bn.max(0.0);
let ap = ap.max(0.0);
let bp = bp.max(0.0);
self.m += dt_sub * (am * (1.0 - self.m) - bm * self.m);
self.h += dt_sub * (ah * (1.0 - self.h) - bh * self.h);
self.n += dt_sub * (an * (1.0 - self.n) - bn * self.n);
self.p += dt_sub * (ap * (1.0 - self.p) - bp * self.p);
self.m = self.m.clamp(0.0, 1.0);
self.h = self.h.clamp(0.0, 1.0);
self.n = self.n.clamp(0.0, 1.0);
self.p = self.p.clamp(0.0, 1.0);
let i_na = self.p_na
* self.m
* self.m
* self.h
* Self::ghk_current(v, self.na_ratio, self.v_t);
let i_k = self.p_k * self.n * self.n * Self::ghk_current(v, self.k_ratio, self.v_t);
let i_p = self.p_p * self.p * self.p * Self::ghk_current(v, self.na_ratio, self.v_t);
let i_l = self.g_l * (self.v - self.e_l);
let dv = (-(i_na + i_k + i_p + i_l) + input) / self.c_m;
self.v += dt_sub * dv;
}
self.v = self.v.clamp(-50.0, 150.0);
if !self.v.is_finite() {
self.v = 0.0;
}
if !self.m.is_finite() {
self.m = 0.005;
}
if !self.h.is_finite() {
self.h = 0.8;
}
if !self.n.is_finite() {
self.n = 0.01;
}
if !self.p.is_finite() {
self.p = 0.01;
}
if self.v >= 40.0 && v_prev < 40.0 {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[derive(Clone, Debug)]
pub struct NodeOfRanvier {
pub v: f64, pub m: f64, pub h: f64, pub p: f64, pub s: f64, pub c_m: f64, pub g_nat: f64, pub g_nap: f64, pub g_ks: f64, pub g_l: f64, pub e_na: f64, pub e_k: f64, pub e_l: f64, pub dt: f64, pub sub_steps: usize,
pub gain: f64,
}
impl Default for NodeOfRanvier {
fn default() -> Self {
Self::new()
}
}
impl NodeOfRanvier {
pub fn new() -> Self {
Self {
v: -80.0,
m: 0.01,
h: 0.75,
p: 0.01,
s: 0.05,
c_m: 2.0, g_nat: 3000.0, g_nap: 5.0, g_ks: 80.0, g_l: 7.0, e_na: 50.0,
e_k: -90.0,
e_l: -90.0, dt: 0.5, sub_steps: 20, gain: 1.0,
}
}
#[inline]
fn boltz(v: f64, v_half: f64, k: f64) -> f64 {
1.0 / (1.0 + (-(v - v_half) / k).exp())
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let dt_sub = self.dt / self.sub_steps as f64;
let v_prev_ext = self.v;
for _ in 0..self.sub_steps {
let v = self.v;
let m_inf = Self::boltz(v, -26.8, 9.2);
let tau_m = 0.025 + 0.14 / (1.0 + ((v + 25.0) / 10.0).powi(2)).max(0.01);
self.m += dt_sub * (m_inf - self.m) / tau_m;
let h_inf = Self::boltz(v, -55.2, -7.4);
let tau_h = 0.6 + 4.0 / (1.0 + ((v + 45.0) / 10.0).powi(2)).max(0.01);
self.h += dt_sub * (h_inf - self.h) / tau_h;
let p_inf = Self::boltz(v, -44.0, 5.0);
let tau_p = 1.0 + 6.0 / (1.0 + ((v + 40.0) / 10.0).powi(2)).max(0.01);
self.p += dt_sub * (p_inf - self.p) / tau_p;
let s_inf = Self::boltz(v, -30.0, 10.0);
let tau_s = 20.0 + 60.0 / (1.0 + ((v + 30.0) / 15.0).powi(2)).max(0.01);
self.s += dt_sub * (s_inf - self.s) / tau_s;
self.m = self.m.clamp(0.0, 1.0);
self.h = self.h.clamp(0.0, 1.0);
self.p = self.p.clamp(0.0, 1.0);
self.s = self.s.clamp(0.0, 1.0);
let i_nat = self.g_nat * self.m.powi(3) * self.h * (v - self.e_na);
let i_nap = self.g_nap * self.p.powi(3) * (v - self.e_na);
let i_ks = self.g_ks * self.s * (v - self.e_k);
let i_l = self.g_l * (v - self.e_l);
let dv = (-(i_nat + i_nap + i_ks + i_l) + input) / self.c_m;
self.v += dt_sub * dv;
}
self.v = self.v.clamp(-120.0, 60.0);
if !self.v.is_finite() {
self.v = -80.0;
}
if !self.m.is_finite() {
self.m = 0.01;
}
if !self.h.is_finite() {
self.h = 0.75;
}
if !self.p.is_finite() {
self.p = 0.01;
}
if !self.s.is_finite() {
self.s = 0.05;
}
if self.v >= -10.0 && v_prev_ext < -10.0 {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[derive(Clone, Debug)]
pub struct MyelinatedAxon {
pub node: NodeOfRanvier,
pub v_inter: f64, pub c_inter: f64, pub g_l_myelin: f64, pub e_l_myelin: f64, pub g_para: f64, pub dt: f64,
pub gain: f64,
}
impl Default for MyelinatedAxon {
fn default() -> Self {
Self::new()
}
}
impl MyelinatedAxon {
pub fn new() -> Self {
Self {
node: NodeOfRanvier::new(),
v_inter: -80.0,
c_inter: 0.001, g_l_myelin: 0.001, e_l_myelin: -80.0,
g_para: 0.01, dt: 0.5,
gain: 1.0,
}
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let i_para_to_node = self.g_para * (self.v_inter - self.node.v);
let i_para_to_inter = self.g_para * (self.node.v - self.v_inter);
let dv_inter =
(-self.g_l_myelin * (self.v_inter - self.e_l_myelin) + i_para_to_inter) / self.c_inter;
self.v_inter += self.node.dt / self.node.sub_steps as f64 * dv_inter;
self.v_inter = self.v_inter.clamp(-120.0, 60.0);
if !self.v_inter.is_finite() {
self.v_inter = -80.0;
}
let total_input = input + i_para_to_node * 100.0; self.node.step(total_input)
}
pub fn v(&self) -> f64 {
self.node.v
}
pub fn reset(&mut self) {
self.node.reset();
self.v_inter = -80.0;
}
}
#[derive(Clone, Debug)]
pub struct CardiacPurkinjeFibre {
pub v: f64,
pub m: f64, pub h: f64, pub d: f64, pub f: f64, pub x_r: f64, pub y: f64, pub c_m: f64,
pub g_na: f64,
pub g_cal: f64,
pub g_kr: f64,
pub g_k1: f64,
pub g_f: f64, pub g_l: f64,
pub e_na: f64,
pub e_ca: f64,
pub e_k: f64,
pub e_f: f64, pub e_l: f64,
pub dt: f64,
pub sub_steps: usize,
pub gain: f64,
}
impl Default for CardiacPurkinjeFibre {
fn default() -> Self {
Self::new()
}
}
impl CardiacPurkinjeFibre {
pub fn new() -> Self {
Self {
v: -85.0,
m: 0.001,
h: 0.99,
d: 0.001,
f: 0.99,
x_r: 0.01,
y: 0.05,
c_m: 1.0,
g_na: 15.0, g_cal: 0.05, g_kr: 0.015, g_k1: 0.4, g_f: 0.01, g_l: 0.03,
e_na: 40.0,
e_ca: 65.0,
e_k: -90.0,
e_f: -20.0, e_l: -50.0,
dt: 0.5,
sub_steps: 10, gain: 1.0,
}
}
#[inline]
fn boltz(v: f64, vh: f64, k: f64) -> f64 {
1.0 / (1.0 + (-(v - vh) / k).exp())
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let dt_sub = self.dt / self.sub_steps as f64;
let v_prev = self.v;
for _ in 0..self.sub_steps {
let v = self.v;
let m_inf = Self::boltz(v, -40.0, 8.0);
let tau_m = 0.05 + 0.3 / (1.0 + ((v + 40.0) / 10.0).powi(2)).max(0.01);
self.m += dt_sub * (m_inf - self.m) / tau_m;
let h_inf = Self::boltz(v, -65.0, -7.0);
let tau_h = 0.5 + 8.0 / (1.0 + ((v + 65.0) / 15.0).powi(2)).max(0.01);
self.h += dt_sub * (h_inf - self.h) / tau_h;
let d_inf = Self::boltz(v, -10.0, 6.0);
let tau_d = 2.0 + 5.0 / (1.0 + ((v + 10.0) / 10.0).powi(2)).max(0.01);
self.d += dt_sub * (d_inf - self.d) / tau_d;
let f_inf = Self::boltz(v, -30.0, -8.0);
let tau_f = 20.0 + 100.0 / (1.0 + ((v + 30.0) / 10.0).powi(2)).max(0.01);
self.f += dt_sub * (f_inf - self.f) / tau_f;
let xr_inf = Self::boltz(v, -20.0, 10.0);
let tau_xr = 50.0 + 200.0 / (1.0 + ((v + 20.0) / 15.0).powi(2)).max(0.01);
self.x_r += dt_sub * (xr_inf - self.x_r) / tau_xr;
let y_inf = Self::boltz(v, -80.0, -10.0);
let tau_y = 100.0 + 500.0 / (1.0 + ((v + 80.0) / 20.0).powi(2)).max(0.01);
self.y += dt_sub * (y_inf - self.y) / tau_y;
self.m = self.m.clamp(0.0, 1.0);
self.h = self.h.clamp(0.0, 1.0);
self.d = self.d.clamp(0.0, 1.0);
self.f = self.f.clamp(0.0, 1.0);
self.x_r = self.x_r.clamp(0.0, 1.0);
self.y = self.y.clamp(0.0, 1.0);
let k1_inf = 1.0 / (1.0 + ((v - self.e_k + 10.0) / 10.0).exp());
let i_na = self.g_na * self.m.powi(3) * self.h * (v - self.e_na);
let i_cal = self.g_cal * self.d * self.f * (v - self.e_ca);
let i_kr = self.g_kr * self.x_r * (v - self.e_k);
let i_k1 = self.g_k1 * k1_inf * (v - self.e_k);
let i_f = self.g_f * self.y * (v - self.e_f);
let i_l = self.g_l * (v - self.e_l);
let dv = (-(i_na + i_cal + i_kr + i_k1 + i_f + i_l) + input) / self.c_m;
self.v += dt_sub * dv;
}
self.v = self.v.clamp(-120.0, 60.0);
if !self.v.is_finite() {
self.v = -85.0;
}
if !self.m.is_finite() {
self.m = 0.001;
}
if !self.h.is_finite() {
self.h = 0.99;
}
if !self.d.is_finite() {
self.d = 0.001;
}
if !self.f.is_finite() {
self.f = 0.99;
}
if !self.x_r.is_finite() {
self.x_r = 0.01;
}
if !self.y.is_finite() {
self.y = 0.05;
}
if self.v >= -20.0 && v_prev < -20.0 {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[derive(Clone, Debug)]
pub struct SmoothMuscleCell {
pub v: f64,
pub d: f64, pub f: f64, pub ca: f64, pub ca_store: f64, pub c_m: f64,
pub g_cal: f64, pub g_bk: f64, pub g_l: f64, pub e_ca: f64,
pub e_k: f64,
pub e_l: f64,
pub tau_ca: f64, pub v_serca: f64, pub k_serca: f64, pub ip3: f64, pub v_ip3r: f64, pub k_ip3: f64, pub k_ca_ip3: f64, pub kd_bk: f64, pub dt: f64,
pub sub_steps: usize,
pub gain: f64,
}
impl Default for SmoothMuscleCell {
fn default() -> Self {
Self::new()
}
}
impl SmoothMuscleCell {
pub fn new() -> Self {
Self {
v: -60.0,
d: 0.01,
f: 0.95,
ca: 0.1,
ca_store: 100.0, c_m: 1.0,
g_cal: 2.0, g_bk: 1.0, g_l: 0.1,
e_ca: 60.0,
e_k: -80.0,
e_l: -50.0,
tau_ca: 50.0,
v_serca: 0.5, k_serca: 0.3, ip3: 0.5, v_ip3r: 2.0, k_ip3: 0.3, k_ca_ip3: 0.3, kd_bk: 0.5, dt: 1.0, sub_steps: 4,
gain: 1.0,
}
}
#[inline]
fn boltz(v: f64, vh: f64, k: f64) -> f64 {
1.0 / (1.0 + (-(v - vh) / k).exp())
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let dt_sub = self.dt / self.sub_steps as f64;
let v_prev = self.v;
for _ in 0..self.sub_steps {
let v = self.v;
let d_inf = Self::boltz(v, -20.0, 6.0);
let tau_d = 5.0 + 20.0 / (1.0 + ((v + 20.0) / 10.0).powi(2)).max(0.01);
self.d += dt_sub * (d_inf - self.d) / tau_d;
let f_inf = Self::boltz(v, -35.0, -8.0);
let tau_f = 50.0 + 200.0 / (1.0 + ((v + 35.0) / 10.0).powi(2)).max(0.01);
self.f += dt_sub * (f_inf - self.f) / tau_f;
self.d = self.d.clamp(0.0, 1.0);
self.f = self.f.clamp(0.0, 1.0);
let bk_ca = self.ca * self.ca / (self.ca * self.ca + self.kd_bk * self.kd_bk);
let bk_v = Self::boltz(v, -10.0, 15.0);
let bk_inf = bk_ca * bk_v;
let i_cal = self.g_cal * self.d * self.f * (v - self.e_ca);
let i_bk = self.g_bk * bk_inf * (v - self.e_k);
let i_l = self.g_l * (v - self.e_l);
let dv = (-(i_cal + i_bk + i_l) + input) / self.c_m;
self.v += dt_sub * dv;
let ca_entry = if i_cal < 0.0 { -i_cal * 0.01 } else { 0.0 };
let ip3_act = self.ip3 / (self.ip3 + self.k_ip3);
let ca_act = self.ca / (self.ca + self.k_ca_ip3);
let ip3_release = self.v_ip3r * ip3_act * ca_act * self.ca_store;
let serca = self.v_serca * self.ca * self.ca
/ (self.ca * self.ca + self.k_serca * self.k_serca);
self.ca += dt_sub * (ca_entry + ip3_release - serca - self.ca / self.tau_ca);
self.ca_store += dt_sub * (serca - ip3_release);
self.ca = self.ca.max(0.0);
self.ca_store = self.ca_store.max(0.0);
}
self.v = self.v.clamp(-100.0, 40.0);
if !self.v.is_finite() {
self.v = -60.0;
}
if !self.ca.is_finite() {
self.ca = 0.1;
}
if !self.ca_store.is_finite() {
self.ca_store = 100.0;
}
if self.v >= -30.0 && v_prev < -30.0 {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[derive(Clone, Debug)]
pub struct EndocrineBetaCell {
pub v: f64,
pub n: f64, pub ca: f64, pub c_m: f64,
pub g_cal: f64, pub g_kdr: f64, pub g_katp: f64, pub g_kca: f64, pub g_l: f64,
pub e_ca: f64,
pub e_k: f64,
pub e_l: f64,
pub tau_ca: f64, pub kd_kca: f64, pub atp_level: f64, pub dt: f64,
pub sub_steps: usize,
pub gain: f64,
}
impl Default for EndocrineBetaCell {
fn default() -> Self {
Self::new()
}
}
impl EndocrineBetaCell {
pub fn new() -> Self {
Self {
v: -70.0,
n: 0.01,
ca: 0.1,
c_m: 1.0,
g_cal: 5.0, g_kdr: 4.0, g_katp: 3.0, g_kca: 2.0, g_l: 0.1,
e_ca: 50.0,
e_k: -75.0,
e_l: -30.0, tau_ca: 100.0, kd_kca: 0.5, atp_level: 0.3, dt: 0.5,
sub_steps: 4,
gain: 1.0,
}
}
#[inline]
fn boltz(v: f64, vh: f64, k: f64) -> f64 {
1.0 / (1.0 + (-(v - vh) / k).exp())
}
pub fn step(&mut self, current: f64) -> i32 {
let input = self.gain * current;
let dt_sub = self.dt / self.sub_steps as f64;
let v_prev = self.v;
for _ in 0..self.sub_steps {
let v = self.v;
let m_cal_inf = Self::boltz(v, -20.0, 8.0);
let n_inf = Self::boltz(v, -15.0, 6.0);
let tau_n = 5.0 + 20.0 / (1.0 + ((v + 15.0) / 10.0).powi(2)).max(0.01);
self.n += dt_sub * (n_inf - self.n) / tau_n;
self.n = self.n.clamp(0.0, 1.0);
let g_katp_eff = self.g_katp * (1.0 - self.atp_level);
let kca_inf = self.ca * self.ca / (self.ca * self.ca + self.kd_kca * self.kd_kca);
let i_cal = self.g_cal * m_cal_inf * (v - self.e_ca);
let i_kdr = self.g_kdr * self.n.powi(4) * (v - self.e_k);
let i_katp = g_katp_eff * (v - self.e_k);
let i_kca = self.g_kca * kca_inf * (v - self.e_k);
let i_l = self.g_l * (v - self.e_l);
let dv = (-(i_cal + i_kdr + i_katp + i_kca + i_l) + input) / self.c_m;
self.v += dt_sub * dv;
let ca_entry = if i_cal < 0.0 { -i_cal * 0.002 } else { 0.0 };
self.ca += dt_sub * (ca_entry - self.ca / self.tau_ca);
self.ca = self.ca.max(0.0);
}
self.v = self.v.clamp(-100.0, 40.0);
if !self.v.is_finite() {
self.v = -70.0;
}
if !self.n.is_finite() {
self.n = 0.01;
}
if !self.ca.is_finite() {
self.ca = 0.1;
}
if self.v >= -20.0 && v_prev < -20.0 {
1
} else {
0
}
}
pub fn reset(&mut self) {
*self = Self::new();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn graded_depolarises_with_input() {
let mut n = GradedSynapseNeuron::new();
let v0 = n.v;
for _ in 0..10_000 {
n.step(200.0);
}
assert!(
n.v > v0,
"Must depolarise with positive input: v0={v0}, v={}",
n.v
);
}
#[test]
fn graded_hyperpolarises_with_negative_input() {
let mut n = GradedSynapseNeuron::new();
let v0 = n.v;
for _ in 0..10_000 {
n.step(-200.0);
}
assert!(
n.v < v0,
"Must hyperpolarise with negative input: v0={v0}, v={}",
n.v
);
}
#[test]
fn graded_fires_with_strong_input() {
let mut n = GradedSynapseNeuron::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(500.0);
}
assert!(
spikes > 0,
"Must cross threshold with strong input, got {spikes}"
);
}
#[test]
fn graded_silent_without_input() {
let mut n = GradedSynapseNeuron::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(0.0);
}
assert_eq!(
spikes, 0,
"Must be silent without input (V starts at E_L), got {spikes}"
);
}
#[test]
fn graded_release_monotonic() {
let mut n = GradedSynapseNeuron::new();
n.v = -70.0;
let r_low = n.release();
n.v = -40.0;
let r_mid = n.release();
n.v = -10.0;
let r_high = n.release();
assert!(
r_low < r_mid && r_mid < r_high,
"Release must be monotonic: r_low={r_low:.3}, r_mid={r_mid:.3}, r_high={r_high:.3}"
);
}
#[test]
fn graded_release_bounded() {
let mut n = GradedSynapseNeuron::new();
n.v = -100.0;
assert!(n.release() >= 0.0 && n.release() <= 1.0);
n.v = 50.0;
assert!(n.release() >= 0.0 && n.release() <= 1.0);
}
#[test]
fn graded_v_saturates() {
let mut n = GradedSynapseNeuron::new();
for _ in 0..50_000 {
n.step(1e6);
}
assert!(
n.v <= n.v_max,
"V must not exceed v_max={}, got {}",
n.v_max,
n.v
);
n.reset();
for _ in 0..50_000 {
n.step(-1e6);
}
assert!(
n.v >= n.v_min,
"V must not go below v_min={}, got {}",
n.v_min,
n.v
);
}
#[test]
fn graded_nan_input_stays_finite() {
let mut n = GradedSynapseNeuron::new();
n.step(f64::NAN);
assert!(n.v.is_finite());
}
#[test]
fn graded_reset_clears_state() {
let mut n = GradedSynapseNeuron::new();
for _ in 0..10_000 {
n.step(500.0);
}
n.reset();
assert_eq!(n.v, -60.0);
}
#[test]
fn graded_performance_100k_steps() {
let start = std::time::Instant::now();
let mut n = GradedSynapseNeuron::new();
for _ in 0..100_000 {
std::hint::black_box(n.step(100.0));
}
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 50,
"100k steps must complete in <50ms"
);
}
#[test]
fn gap_fires_with_depolarising_drive() {
let mut n = GapJunctionNeuron::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(0.0); }
assert!(
spikes > 0,
"Gap junction must fire with depolarising drive, got {spikes}"
);
}
#[test]
fn gap_silent_at_rest() {
let mut n = GapJunctionNeuron::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(-65.0); }
assert_eq!(
spikes, 0,
"Must be silent when V_neighbor = E_L, got {spikes}"
);
}
#[test]
fn gap_junction_pulls_toward_neighbor() {
let mut n = GapJunctionNeuron::new(); for _ in 0..5_000 {
n.step(-40.0);
} assert!(
n.v > -65.0 || n.refrac_timer > 0.0,
"Gap junction must pull V toward neighbor: v={}",
n.v
);
}
#[test]
fn gap_stronger_coupling_more_spikes() {
let mut weak = GapJunctionNeuron::new();
weak.g_gap = 0.01;
let mut strong = GapJunctionNeuron::new();
strong.g_gap = 0.1;
let (mut sw, mut ss) = (0, 0);
for _ in 0..50_000 {
sw += weak.step(-20.0);
ss += strong.step(-20.0);
}
assert!(
ss >= sw,
"Stronger coupling → more spikes: strong={ss} vs weak={sw}"
);
}
#[test]
fn gap_refractory_enforced() {
let mut n = GapJunctionNeuron::new();
let mut first_spike_t = 0;
for t in 0..10_000 {
if n.step(0.0) == 1 {
first_spike_t = t;
break;
}
}
assert!(first_spike_t > 0, "Must spike");
assert!(n.refrac_timer > 0.0, "Must be in refractory after spike");
assert_eq!(n.step(0.0), 0, "Must not spike during refractory");
}
#[test]
fn gap_hyperpolarising_drive_silent() {
let mut n = GapJunctionNeuron::new();
let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(-100.0);
}
assert_eq!(
spikes, 0,
"Hyperpolarising drive must keep silent, got {spikes}"
);
}
#[test]
fn gap_tonic_current_depolarises() {
let mut n = GapJunctionNeuron::new();
n.i_tonic = 5.0; let mut spikes = 0;
for _ in 0..50_000 {
spikes += n.step(-65.0); }
assert!(
spikes > 0,
"Tonic current should produce spikes, got {spikes}"
);
}
#[test]
fn gap_nan_input_stays_finite() {
let mut n = GapJunctionNeuron::new();
n.step(f64::NAN);
assert!(n.v.is_finite());
}
#[test]
fn gap_reset_clears_state() {
let mut n = GapJunctionNeuron::new();
for _ in 0..10_000 {
n.step(-20.0);
}
n.reset();
assert_eq!(n.v, -65.0);
assert_eq!(n.refrac_timer, 0.0);
}
#[test]
fn gap_rectification_reduces_at_large_vj() {
let n = GapJunctionNeuron::new();
let g_small = n.rect_conductance(5.0); let g_large = n.rect_conductance(60.0); assert!(
g_small > g_large,
"Rectification must reduce g at large Vj: g(5)={g_small:.3} vs g(60)={g_large:.3}"
);
assert!(
g_large >= n.rect_gmin,
"Conductance must not drop below g_min={}: got {g_large:.3}",
n.rect_gmin
);
}
#[test]
fn gap_performance_100k_steps() {
let start = std::time::Instant::now();
let mut n = GapJunctionNeuron::new();
for _ in 0..100_000 {
std::hint::black_box(n.step(-20.0));
}
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 50,
"100k steps must complete in <50ms"
);
}
#[test]
fn fh_fires_with_input() {
let mut n = FrankenhaeUserHuxleyAxon::new();
let mut spikes = 0;
for _ in 0..2_000 {
spikes += n.step(2000.0);
}
assert!(
spikes > 0,
"FH axon must fire with strong input, got {spikes}"
);
}
#[test]
fn fh_silent_without_input() {
let mut n = FrankenhaeUserHuxleyAxon::new();
let mut spikes = 0;
for _ in 0..5_000 {
spikes += n.step(0.0);
}
assert_eq!(
spikes, 0,
"FH axon must be silent without input, got {spikes}"
);
}
#[test]
fn fh_action_potential_shape() {
let mut n = FrankenhaeUserHuxleyAxon::new();
let mut v_max = -100.0_f64;
for _ in 0..500 {
n.step(2000.0);
v_max = v_max.max(n.v);
}
assert!(v_max > 40.0, "AP peak should exceed 40 mV, got {v_max:.1}");
}
#[test]
fn fh_gating_evolves() {
let mut n = FrankenhaeUserHuxleyAxon::new();
let m0 = n.m;
let h0 = n.h;
for _ in 0..100 {
n.step(2000.0);
}
assert!(n.m != m0 || n.h != h0, "Gating variables must evolve");
}
#[test]
fn fh_four_gates() {
let mut n = FrankenhaeUserHuxleyAxon::new();
for _ in 0..200 {
n.step(2000.0);
}
assert!(
n.m > 0.005 || n.h < 0.8 || n.n > 0.01 || n.p > 0.01,
"All gates must evolve: m={:.3}, h={:.3}, n={:.3}, p={:.3}",
n.m,
n.h,
n.n,
n.p
);
}
#[test]
fn fh_stronger_input_more_spikes() {
let mut weak = FrankenhaeUserHuxleyAxon::new();
let mut strong = FrankenhaeUserHuxleyAxon::new();
let (mut sw, mut ss) = (0, 0);
for _ in 0..2_000 {
sw += weak.step(1000.0);
ss += strong.step(3000.0);
}
assert!(
ss >= sw,
"Stronger input → more spikes: strong={ss} vs weak={sw}"
);
}
#[test]
fn fh_all_gates_bounded() {
let mut n = FrankenhaeUserHuxleyAxon::new();
for _ in 0..2_000 {
n.step(3000.0);
}
assert!(n.m >= 0.0 && n.m <= 1.0, "m out of bounds: {}", n.m);
assert!(n.h >= 0.0 && n.h <= 1.0, "h out of bounds: {}", n.h);
assert!(n.n >= 0.0 && n.n <= 1.0, "n out of bounds: {}", n.n);
assert!(n.p >= 0.0 && n.p <= 1.0, "p out of bounds: {}", n.p);
}
#[test]
fn fh_nan_input_stays_finite() {
let mut n = FrankenhaeUserHuxleyAxon::new();
n.step(f64::NAN);
assert!(n.v.is_finite());
assert!(n.m.is_finite());
}
#[test]
fn fh_reset_clears_state() {
let mut n = FrankenhaeUserHuxleyAxon::new();
for _ in 0..500 {
n.step(2000.0);
}
n.reset();
assert_eq!(n.v, 0.0);
assert_eq!(n.m, 0.005);
assert_eq!(n.h, 0.8);
}
#[test]
fn fh_performance_1k_steps() {
let start = std::time::Instant::now();
let mut n = FrankenhaeUserHuxleyAxon::new();
for _ in 0..1_000 {
std::hint::black_box(n.step(1500.0));
}
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 100,
"1k steps must complete in <100ms"
);
}
#[test]
fn nor_fires_with_input() {
let mut n = NodeOfRanvier::new();
let mut spikes = 0;
for _ in 0..2_000 {
spikes += n.step(500.0);
}
assert!(
spikes > 0,
"Node of Ranvier must fire with input, got {spikes}"
);
}
#[test]
fn nor_silent_without_input() {
let mut n = NodeOfRanvier::new();
let mut spikes = 0;
for _ in 0..5_000 {
spikes += n.step(0.0);
}
assert_eq!(spikes, 0, "Must be silent without input, got {spikes}");
}
#[test]
fn nor_high_nat_density() {
let n = NodeOfRanvier::new();
assert!(
n.g_nat > 1000.0,
"Nodal transient Na should be very high: g_nat={}",
n.g_nat
);
}
#[test]
fn nor_has_persistent_na() {
let n = NodeOfRanvier::new();
assert!(
n.g_nap > 0.0,
"MRG model must have persistent Na current: g_nap={}",
n.g_nap
);
}
#[test]
fn nor_has_kv7_slow_k() {
let n = NodeOfRanvier::new();
assert!(
n.g_ks > 0.0,
"MRG model must have slow K (Kv7): g_ks={}",
n.g_ks
);
}
#[test]
fn nor_persistent_na_lowers_threshold() {
let mut with_nap = NodeOfRanvier::new();
let mut no_nap = NodeOfRanvier::new();
no_nap.g_nap = 0.0;
let (mut s_with, mut s_without) = (0, 0);
for _ in 0..2_000 {
s_with += with_nap.step(200.0);
s_without += no_nap.step(200.0);
}
assert!(
s_with >= s_without,
"Persistent Na should lower threshold: with={s_with} vs without={s_without}"
);
}
#[test]
fn nor_gating_evolves() {
let mut n = NodeOfRanvier::new();
let m0 = n.m;
let p0 = n.p;
for _ in 0..100 {
n.step(500.0);
}
assert!(
n.m != m0 || n.p != p0,
"Gating must evolve: m={:.3}, p={:.3}",
n.m,
n.p
);
}
#[test]
fn nor_nan_input_stays_finite() {
let mut n = NodeOfRanvier::new();
n.step(f64::NAN);
assert!(n.v.is_finite());
assert!(n.m.is_finite());
assert!(n.p.is_finite());
assert!(n.s.is_finite());
}
#[test]
fn nor_reset_clears_state() {
let mut n = NodeOfRanvier::new();
for _ in 0..500 {
n.step(500.0);
}
n.reset();
assert_eq!(n.v, -80.0);
assert_eq!(n.m, 0.01);
assert_eq!(n.p, 0.01);
assert_eq!(n.s, 0.05);
}
#[test]
fn nor_performance_1k_steps() {
let start = std::time::Instant::now();
let mut n = NodeOfRanvier::new();
for _ in 0..1_000 {
std::hint::black_box(n.step(500.0));
}
let elapsed = start.elapsed();
assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
}
#[test]
fn myelin_fires_with_input() {
let mut n = MyelinatedAxon::new();
let mut spikes = 0;
for _ in 0..2_000 {
spikes += n.step(500.0);
}
assert!(spikes > 0, "Myelinated axon must fire, got {spikes}");
}
#[test]
fn myelin_silent_without_input() {
let mut n = MyelinatedAxon::new();
let mut spikes = 0;
for _ in 0..5_000 {
spikes += n.step(0.0);
}
assert_eq!(spikes, 0, "Must be silent without input, got {spikes}");
}
#[test]
fn myelin_internode_coupling() {
let mut n = MyelinatedAxon::new();
let v_inter_0 = n.v_inter;
for _ in 0..500 {
n.step(500.0);
}
assert!(
(n.v_inter - v_inter_0).abs() > 0.001,
"Internode voltage should change with node activity: v_inter={}",
n.v_inter
);
}
#[test]
fn myelin_has_low_capacitance() {
let n = MyelinatedAxon::new();
assert!(
n.c_inter < 0.01,
"Myelin capacitance must be very low: {}",
n.c_inter
);
}
#[test]
fn myelin_has_low_myelin_leak() {
let n = MyelinatedAxon::new();
assert!(
n.g_l_myelin < 0.01,
"Myelin leak must be very low: {}",
n.g_l_myelin
);
}
#[test]
fn myelin_has_paranodal_seal() {
let n = MyelinatedAxon::new();
assert!(n.g_para > 0.0, "Must have paranodal seal conductance");
}
#[test]
fn myelin_stronger_input_more_spikes() {
let mut weak = MyelinatedAxon::new();
let mut strong = MyelinatedAxon::new();
let (mut sw, mut ss) = (0, 0);
for _ in 0..2_000 {
sw += weak.step(300.0);
ss += strong.step(1000.0);
}
assert!(ss >= sw, "Stronger → more spikes: strong={ss} vs weak={sw}");
}
#[test]
fn myelin_nan_input_stays_finite() {
let mut n = MyelinatedAxon::new();
n.step(f64::NAN);
assert!(n.v().is_finite());
assert!(n.v_inter.is_finite());
}
#[test]
fn myelin_reset_clears_state() {
let mut n = MyelinatedAxon::new();
for _ in 0..500 {
n.step(500.0);
}
n.reset();
assert_eq!(n.v_inter, -80.0);
assert_eq!(n.node.v, -80.0);
}
#[test]
fn myelin_performance_1k_steps() {
let start = std::time::Instant::now();
let mut n = MyelinatedAxon::new();
for _ in 0..1_000 {
std::hint::black_box(n.step(500.0));
}
let elapsed = start.elapsed();
assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
}
#[test]
fn cardiac_fires_with_input() {
let mut n = CardiacPurkinjeFibre::new();
let mut spikes = 0;
for _ in 0..2_000 {
spikes += n.step(5.0);
}
assert!(
spikes > 0,
"Cardiac Purkinje must fire with input, got {spikes}"
);
}
#[test]
fn cardiac_silent_without_input() {
let mut n = CardiacPurkinjeFibre::new();
n.g_f = 0.0; let mut spikes = 0;
for _ in 0..5_000 {
spikes += n.step(0.0);
}
assert!(
spikes <= 1,
"Must be essentially silent without pacemaker, got {spikes}"
);
}
#[test]
fn cardiac_has_funny_current() {
let n = CardiacPurkinjeFibre::new();
assert!(n.g_f > 0.0, "Must have funny current (If/HCN)");
}
#[test]
fn cardiac_has_cal() {
let n = CardiacPurkinjeFibre::new();
assert!(n.g_cal > 0.0, "Must have L-type Ca²⁺ for plateau");
}
#[test]
fn cardiac_has_inward_rectifier() {
let n = CardiacPurkinjeFibre::new();
assert!(n.g_k1 > 0.0, "Must have IK1 inward rectifier");
}
#[test]
fn cardiac_six_currents() {
let n = CardiacPurkinjeFibre::new();
assert!(
n.g_na > 0.0
&& n.g_cal > 0.0
&& n.g_kr > 0.0
&& n.g_k1 > 0.0
&& n.g_f > 0.0
&& n.g_l > 0.0,
"Must have all 6 currents"
);
}
#[test]
fn cardiac_gating_evolves() {
let mut n = CardiacPurkinjeFibre::new();
let d0 = n.d;
let y0 = n.y;
for _ in 0..200 {
n.step(5.0);
}
assert!(n.d != d0 || n.y != y0, "Gating must evolve");
}
#[test]
fn cardiac_nan_input_stays_finite() {
let mut n = CardiacPurkinjeFibre::new();
n.step(f64::NAN);
assert!(n.v.is_finite());
}
#[test]
fn cardiac_reset_clears_state() {
let mut n = CardiacPurkinjeFibre::new();
for _ in 0..500 {
n.step(5.0);
}
n.reset();
assert_eq!(n.v, -85.0);
assert_eq!(n.m, 0.001);
}
#[test]
fn cardiac_performance_1k_steps() {
let start = std::time::Instant::now();
let mut n = CardiacPurkinjeFibre::new();
for _ in 0..1_000 {
std::hint::black_box(n.step(3.0));
}
let elapsed = start.elapsed();
assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
}
#[test]
fn smooth_fires_with_input() {
let mut n = SmoothMuscleCell::new();
let mut spikes = 0;
for _ in 0..10_000 {
spikes += n.step(5.0);
}
assert!(
spikes > 0,
"Smooth muscle must produce slow waves, got {spikes}"
);
}
#[test]
fn smooth_silent_without_input() {
let mut n = SmoothMuscleCell::new();
n.ip3 = 0.0; let mut spikes = 0;
for _ in 0..5_000 {
spikes += n.step(0.0);
}
assert!(
spikes <= 1,
"Should be essentially silent without drive, got {spikes}"
);
}
#[test]
fn smooth_has_ip3_pathway() {
let n = SmoothMuscleCell::new();
assert!(n.v_ip3r > 0.0, "Must have IP3R release pathway");
assert!(n.ip3 > 0.0, "Must have tonic IP3");
}
#[test]
fn smooth_has_serca() {
let n = SmoothMuscleCell::new();
assert!(n.v_serca > 0.0, "Must have SERCA pump");
}
#[test]
fn smooth_ca_store_exists() {
let n = SmoothMuscleCell::new();
assert!(n.ca_store > 0.0, "Must have ER/SR Ca²⁺ store");
}
#[test]
fn smooth_has_bk() {
let n = SmoothMuscleCell::new();
assert!(n.g_bk > 0.0, "Must have BK (Ca²⁺-activated K) channel");
}
#[test]
fn smooth_no_fast_na() {
let n = SmoothMuscleCell::new();
assert!(n.g_cal > 0.0, "Depolarisation must be Ca²⁺-dependent");
}
#[test]
fn smooth_nan_stays_finite() {
let mut n = SmoothMuscleCell::new();
n.step(f64::NAN);
assert!(n.v.is_finite());
assert!(n.ca.is_finite());
}
#[test]
fn smooth_reset_clears() {
let mut n = SmoothMuscleCell::new();
for _ in 0..1000 {
n.step(3.0);
}
n.reset();
assert_eq!(n.v, -60.0);
assert_eq!(n.ca, 0.1);
assert_eq!(n.ca_store, 100.0);
}
#[test]
fn smooth_performance_1k_steps() {
let start = std::time::Instant::now();
let mut n = SmoothMuscleCell::new();
for _ in 0..1_000 {
std::hint::black_box(n.step(2.0));
}
let elapsed = start.elapsed();
assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
}
#[test]
fn beta_fires_with_glucose() {
let mut n = EndocrineBetaCell::new();
n.atp_level = 0.9; let mut spikes = 0;
for _ in 0..10_000 {
spikes += n.step(5.0);
}
assert!(
spikes > 0,
"Beta cell must burst with high glucose, got {spikes}"
);
}
#[test]
fn beta_silent_low_glucose() {
let mut n = EndocrineBetaCell::new();
n.atp_level = 0.0; let mut spikes = 0;
for _ in 0..5_000 {
spikes += n.step(0.0);
}
assert_eq!(
spikes, 0,
"Beta cell must be silent at low glucose, got {spikes}"
);
}
#[test]
fn beta_katp_glucose_dependent() {
let mut low = EndocrineBetaCell::new();
low.atp_level = 0.2;
let mut high = EndocrineBetaCell::new();
high.atp_level = 0.9;
let (mut sl, mut sh) = (0, 0);
for _ in 0..5_000 {
sl += low.step(1.0);
sh += high.step(1.0);
}
assert!(
sh >= sl,
"High glucose → more spikes: high={sh} vs low={sl}"
);
}
#[test]
fn beta_has_katp() {
let n = EndocrineBetaCell::new();
assert!(n.g_katp > 0.0, "Must have ATP-sensitive K channel");
}
#[test]
fn beta_has_kca_for_bursting() {
let n = EndocrineBetaCell::new();
assert!(n.g_kca > 0.0, "Must have IK_Ca for burst termination");
}
#[test]
fn beta_ca_rises_with_spiking() {
let mut n = EndocrineBetaCell::new();
n.atp_level = 0.8;
let ca0 = n.ca;
for _ in 0..5_000 {
n.step(2.0);
}
assert!(
n.ca > ca0,
"Ca²⁺ should rise during bursting: ca0={ca0}, ca={}",
n.ca
);
}
#[test]
fn beta_nan_stays_finite() {
let mut n = EndocrineBetaCell::new();
n.step(f64::NAN);
assert!(n.v.is_finite());
assert!(n.ca.is_finite());
}
#[test]
fn beta_reset_clears() {
let mut n = EndocrineBetaCell::new();
for _ in 0..1000 {
n.step(2.0);
}
n.reset();
assert_eq!(n.v, -70.0);
assert_eq!(n.ca, 0.1);
}
#[test]
fn beta_no_fast_na() {
let n = EndocrineBetaCell::new();
assert!(n.g_cal > 0.0, "CaL must drive depolarisation");
}
#[test]
fn beta_performance_1k_steps() {
let start = std::time::Instant::now();
let mut n = EndocrineBetaCell::new();
for _ in 0..1_000 {
std::hint::black_box(n.step(2.0));
}
let elapsed = start.elapsed();
assert!(elapsed.as_millis() < 50, "1k steps must complete in <50ms");
}
}