1use rand::{RngExt, SeedableRng};
12use rand_xoshiro::Xoshiro256PlusPlus;
13
14#[derive(Clone, Debug)]
16pub struct FitzHughNagumoNeuron {
17 pub v: f64,
18 pub w: f64,
19 pub a: f64,
20 pub b: f64,
21 pub epsilon: f64,
22 pub dt: f64,
23 pub v_threshold: f64,
24}
25
26impl FitzHughNagumoNeuron {
27 pub fn new() -> Self {
28 Self {
29 v: -1.0,
30 w: -0.5,
31 a: 0.7,
32 b: 0.8,
33 epsilon: 0.08,
34 dt: 0.1,
35 v_threshold: 1.0,
36 }
37 }
38
39 pub fn step(&mut self, current: f64) -> i32 {
40 if !(current.is_finite() && self.is_valid()) {
41 return -1;
42 }
43 let v_prev = self.v;
44 let Some((new_v, new_w)) = self.rk4_candidate(current) else {
45 return -1;
46 };
47 if !(new_v.is_finite() && new_w.is_finite()) {
48 return -1;
49 }
50 self.v = new_v;
51 self.w = new_w;
52 if self.v >= self.v_threshold && v_prev < self.v_threshold {
53 1
54 } else {
55 0
56 }
57 }
58
59 pub fn reset(&mut self) {
60 self.v = -1.0;
61 self.w = -0.5;
62 }
63
64 pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
70 let mut trace = Vec::with_capacity(n_steps);
71 let mut spikes: i64 = 0;
72 for _ in 0..n_steps {
73 let spiked = self.step(current);
74 trace.push(self.v);
75 if spiked == 1 {
76 spikes += 1;
77 }
78 }
79 (trace, spikes)
80 }
81
82 fn is_valid(&self) -> bool {
83 self.v.is_finite()
84 && self.w.is_finite()
85 && self.a.is_finite()
86 && self.b.is_finite()
87 && self.epsilon.is_finite()
88 && self.dt.is_finite()
89 && self.v_threshold.is_finite()
90 && self.b > 0.0
91 && self.epsilon > 0.0
92 && self.dt > 0.0
93 }
94
95 fn rhs(&self, v: f64, w: f64, current: f64) -> Option<(f64, f64)> {
96 if !(v.is_finite() && w.is_finite() && current.is_finite()) {
97 return None;
98 }
99 let dv = v - v.powi(3) / 3.0 - w + current;
100 let dw = self.epsilon * (v + self.a - self.b * w);
101 if dv.is_finite() && dw.is_finite() {
102 Some((dv, dw))
103 } else {
104 None
105 }
106 }
107
108 fn rk4_candidate(&self, current: f64) -> Option<(f64, f64)> {
109 let (v0, w0, dt) = (self.v, self.w, self.dt);
110 let (k1v, k1w) = self.rhs(v0, w0, current)?;
111 let (k2v, k2w) = self.rhs(v0 + 0.5 * dt * k1v, w0 + 0.5 * dt * k1w, current)?;
112 let (k3v, k3w) = self.rhs(v0 + 0.5 * dt * k2v, w0 + 0.5 * dt * k2w, current)?;
113 let (k4v, k4w) = self.rhs(v0 + dt * k3v, w0 + dt * k3w, current)?;
114 Some((
115 v0 + dt * (k1v + 2.0 * k2v + 2.0 * k3v + k4v) / 6.0,
116 w0 + dt * (k1w + 2.0 * k2w + 2.0 * k3w + k4w) / 6.0,
117 ))
118 }
119}
120impl Default for FitzHughNagumoNeuron {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126#[derive(Clone, Debug)]
128pub struct MorrisLecarNeuron {
129 pub v: f64,
130 pub w: f64,
131 pub c_m: f64,
132 pub g_ca: f64,
133 pub g_k: f64,
134 pub g_l: f64,
135 pub e_ca: f64,
136 pub e_k: f64,
137 pub e_l: f64,
138 pub v1: f64,
139 pub v2: f64,
140 pub v3: f64,
141 pub v4: f64,
142 pub phi: f64,
143 pub dt: f64,
144 pub v_threshold: f64,
145}
146
147impl MorrisLecarNeuron {
148 pub fn new() -> Self {
149 Self {
150 v: -60.0,
151 w: 0.0,
152 c_m: 20.0,
153 g_ca: 4.0,
154 g_k: 8.0,
155 g_l: 2.0,
156 e_ca: 120.0,
157 e_k: -84.0,
158 e_l: -60.0,
159 v1: -1.2,
160 v2: 18.0,
161 v3: 12.0,
162 v4: 17.4,
163 phi: 1.0 / 15.0,
164 dt: 0.1,
165 v_threshold: 0.0,
166 }
167 }
168 fn valid_numeric_contract(&self) -> bool {
169 self.v.is_finite()
170 && self.w.is_finite()
171 && self.c_m.is_finite()
172 && self.g_ca.is_finite()
173 && self.g_k.is_finite()
174 && self.g_l.is_finite()
175 && self.e_ca.is_finite()
176 && self.e_k.is_finite()
177 && self.e_l.is_finite()
178 && self.v1.is_finite()
179 && self.v2.is_finite()
180 && self.v3.is_finite()
181 && self.v4.is_finite()
182 && self.phi.is_finite()
183 && self.dt.is_finite()
184 && self.v_threshold.is_finite()
185 && self.c_m > 0.0
186 && self.g_ca > 0.0
187 && self.g_k > 0.0
188 && self.g_l > 0.0
189 && self.v2 > 0.0
190 && self.v4 > 0.0
191 && self.phi > 0.0
192 && self.dt > 0.0
193 && (0.0..=1.0).contains(&self.w)
194 }
195
196 fn m_inf(&self, v: f64) -> f64 {
197 0.5 * (1.0 + ((v - self.v1) / self.v2).tanh())
198 }
199
200 fn w_inf(&self, v: f64) -> f64 {
201 0.5 * (1.0 + ((v - self.v3) / self.v4).tanh())
202 }
203
204 fn lambda(&self, v: f64) -> f64 {
205 self.phi * ((v - self.v3) / (2.0 * self.v4)).cosh()
206 }
207
208 fn rhs(&self, v: f64, w: f64, current: f64) -> Option<(f64, f64)> {
209 if !(v.is_finite() && w.is_finite() && current.is_finite() && (0.0..=1.0).contains(&w)) {
210 return None;
211 }
212 let m_inf = self.m_inf(v);
213 let w_inf = self.w_inf(v);
214 let lam = self.lambda(v);
215 if !(m_inf.is_finite() && w_inf.is_finite() && lam.is_finite()) {
216 return None;
217 }
218 let i_ca = self.g_ca * m_inf * (v - self.e_ca);
219 let i_k = self.g_k * w * (v - self.e_k);
220 let i_l = self.g_l * (v - self.e_l);
221 let dv = (-i_ca - i_k - i_l + current) / self.c_m;
222 let dw = lam * (w_inf - w);
223 if dv.is_finite() && dw.is_finite() {
224 Some((dv, dw))
225 } else {
226 None
227 }
228 }
229
230 pub fn step(&mut self, current: f64) -> i32 {
231 if !self.valid_numeric_contract() || !current.is_finite() {
232 return 0;
233 }
234 let v_prev = self.v;
235 let Some((k1_v, k1_w)) = self.rhs(self.v, self.w, current) else {
236 return 0;
237 };
238 let Some((k2_v, k2_w)) = self.rhs(
239 self.v + 0.5 * self.dt * k1_v,
240 self.w + 0.5 * self.dt * k1_w,
241 current,
242 ) else {
243 return 0;
244 };
245 let Some((k3_v, k3_w)) = self.rhs(
246 self.v + 0.5 * self.dt * k2_v,
247 self.w + 0.5 * self.dt * k2_w,
248 current,
249 ) else {
250 return 0;
251 };
252 let Some((k4_v, k4_w)) =
253 self.rhs(self.v + self.dt * k3_v, self.w + self.dt * k3_w, current)
254 else {
255 return 0;
256 };
257 let next_v = self.v + self.dt * (k1_v + 2.0 * k2_v + 2.0 * k3_v + k4_v) / 6.0;
258 let next_w = self.w + self.dt * (k1_w + 2.0 * k2_w + 2.0 * k3_w + k4_w) / 6.0;
259 if !(next_v.is_finite() && next_w.is_finite() && (0.0..=1.0).contains(&next_w)) {
260 return 0;
261 }
262 self.v = next_v;
263 self.w = next_w;
264 if self.v >= self.v_threshold && v_prev < self.v_threshold {
265 1
266 } else {
267 0
268 }
269 }
270 pub fn reset(&mut self) {
271 self.v = -60.0;
272 self.w = 0.0;
273 }
274}
275impl Default for MorrisLecarNeuron {
276 fn default() -> Self {
277 Self::new()
278 }
279}
280
281#[derive(Clone, Debug)]
283pub struct HindmarshRoseNeuron {
284 pub x: f64,
285 pub y: f64,
286 pub z: f64,
287 pub b: f64,
288 pub r: f64,
289 pub s: f64,
290 pub x_rest: f64,
291 pub dt: f64,
292 pub x_threshold: f64,
293}
294
295impl HindmarshRoseNeuron {
296 pub fn new() -> Self {
297 Self {
298 x: -1.6,
299 y: -10.0,
300 z: 2.0,
301 b: 3.0,
302 r: 0.001,
303 s: 4.0,
304 x_rest: -1.6,
305 dt: 0.1,
306 x_threshold: 1.0,
307 }
308 }
309 fn valid_state(&self) -> bool {
310 self.x.is_finite()
311 && self.y.is_finite()
312 && self.z.is_finite()
313 && self.b.is_finite()
314 && self.r.is_finite()
315 && self.s.is_finite()
316 && self.x_rest.is_finite()
317 && self.dt.is_finite()
318 && self.x_threshold.is_finite()
319 && self.r > 0.0
320 && self.s > 0.0
321 && self.dt > 0.0
322 }
323 fn derivatives(&self, x: f64, y: f64, z: f64, current: f64) -> Option<(f64, f64, f64)> {
324 if !(x.is_finite() && y.is_finite() && z.is_finite() && current.is_finite()) {
325 return None;
326 }
327 let derivative = (
328 y - x.powi(3) + self.b * x.powi(2) - z + current,
329 1.0 - 5.0 * x.powi(2) - y,
330 self.r * (self.s * (x - self.x_rest) - z),
331 );
332 if derivative.0.is_finite() && derivative.1.is_finite() && derivative.2.is_finite() {
333 Some(derivative)
334 } else {
335 None
336 }
337 }
338 pub fn step(&mut self, current: f64) -> i32 {
339 if !self.valid_state() || !current.is_finite() {
340 return 0;
341 }
342 let x_prev = self.x;
343 let (x0, y0, z0) = (self.x, self.y, self.z);
344 let dt = self.dt;
345 let Some(k1) = self.derivatives(x0, y0, z0, current) else {
346 return 0;
347 };
348 let Some(k2) = self.derivatives(
349 x0 + 0.5 * dt * k1.0,
350 y0 + 0.5 * dt * k1.1,
351 z0 + 0.5 * dt * k1.2,
352 current,
353 ) else {
354 return 0;
355 };
356 let Some(k3) = self.derivatives(
357 x0 + 0.5 * dt * k2.0,
358 y0 + 0.5 * dt * k2.1,
359 z0 + 0.5 * dt * k2.2,
360 current,
361 ) else {
362 return 0;
363 };
364 let Some(k4) = self.derivatives(x0 + dt * k3.0, y0 + dt * k3.1, z0 + dt * k3.2, current)
365 else {
366 return 0;
367 };
368 let next_x = x0 + (dt / 6.0) * (k1.0 + 2.0 * k2.0 + 2.0 * k3.0 + k4.0);
369 let next_y = y0 + (dt / 6.0) * (k1.1 + 2.0 * k2.1 + 2.0 * k3.1 + k4.1);
370 let next_z = z0 + (dt / 6.0) * (k1.2 + 2.0 * k2.2 + 2.0 * k3.2 + k4.2);
371 if !(next_x.is_finite() && next_y.is_finite() && next_z.is_finite()) {
372 return 0;
373 }
374 self.x = next_x;
375 self.y = next_y;
376 self.z = next_z;
377 if self.x >= self.x_threshold && x_prev < self.x_threshold {
378 1
379 } else {
380 0
381 }
382 }
383 pub fn reset(&mut self) {
384 self.x = -1.6;
385 self.y = -10.0;
386 self.z = 2.0;
387 }
388
389 pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
396 let mut trace = Vec::with_capacity(n_steps);
397 let mut spikes: i64 = 0;
398 for _ in 0..n_steps {
399 let spiked = self.step(current);
400 trace.push(self.x);
401 if spiked == 1 {
402 spikes += 1;
403 }
404 }
405 (trace, spikes)
406 }
407}
408impl Default for HindmarshRoseNeuron {
409 fn default() -> Self {
410 Self::new()
411 }
412}
413
414#[derive(Clone, Debug)]
416pub struct ResonateAndFireNeuron {
417 pub x: f64,
418 pub y: f64,
419 pub b: f64,
420 pub omega: f64,
421 pub threshold: f64,
422 pub dt: f64,
423}
424
425impl ResonateAndFireNeuron {
426 pub fn new() -> Self {
427 Self {
428 x: 0.0,
429 y: 0.0,
430 b: -0.1,
431 omega: 1.0,
432 threshold: 1.0,
433 dt: 0.05,
434 }
435 }
436 pub fn step(&mut self, current: f64) -> i32 {
437 let dx = (self.b * self.x - self.omega * self.y + current) * self.dt;
439 let dy = (self.omega * self.x + self.b * self.y) * self.dt;
440 self.x += dx;
441 self.y += dy;
442 let r = (self.x * self.x + self.y * self.y).sqrt();
443 if r >= self.threshold {
444 self.x = 0.0;
445 self.y = 0.0;
446 1
447 } else {
448 0
449 }
450 }
451 pub fn reset(&mut self) {
452 self.x = 0.0;
453 self.y = 0.0;
454 }
455}
456impl Default for ResonateAndFireNeuron {
457 fn default() -> Self {
458 Self::new()
459 }
460}
461
462#[derive(Clone, Debug)]
465pub struct BalancedResonateAndFireNeuron {
466 pub x: f64,
467 pub y: f64,
468 pub q: f64,
469 pub omega: f64,
470 pub b_offset: f64,
471 pub threshold: f64,
472 pub gamma: f64,
473 pub dt: f64,
474}
475
476pub fn brf_sustain_oscillation_boundary(omega: f64, dt: f64) -> f64 {
477 let scaled = dt * omega;
478 (-1.0 + (1.0 - scaled * scaled).max(0.0).sqrt()) / dt
479}
480
481impl BalancedResonateAndFireNeuron {
482 pub fn new() -> Self {
483 Self {
484 x: 0.0,
485 y: 0.0,
486 q: 0.0,
487 omega: 10.0,
488 b_offset: 1.0,
489 threshold: 1.0,
490 gamma: 0.9,
491 dt: 0.01,
492 }
493 }
494
495 pub fn p_omega(&self) -> f64 {
496 brf_sustain_oscillation_boundary(self.omega, self.dt)
497 }
498
499 pub fn damping(&self) -> f64 {
500 self.p_omega() - self.b_offset - self.q
501 }
502
503 pub fn dynamic_threshold(&self) -> f64 {
504 self.threshold + self.q
505 }
506
507 pub fn step(&mut self, current: f64) -> i32 {
508 if !(self.dt.is_finite()
509 && self.omega.is_finite()
510 && self.dt > 0.0
511 && self.omega > 0.0
512 && self.dt * self.omega <= 1.0)
513 {
514 return 0;
515 }
516 let b_t = self.damping();
517 let theta_t = self.dynamic_threshold();
518 let x_prev = self.x;
519 let y_prev = self.y;
520 self.x = x_prev + self.dt * (b_t * x_prev - self.omega * y_prev + current);
521 self.y = y_prev + self.dt * (self.omega * x_prev + b_t * y_prev);
522 let spike = if self.x >= theta_t { 1 } else { 0 };
523 self.q = self.gamma * self.q + spike as f64;
524 spike
525 }
526
527 pub fn reset(&mut self) {
528 self.x = 0.0;
529 self.y = 0.0;
530 self.q = 0.0;
531 }
532}
533impl Default for BalancedResonateAndFireNeuron {
534 fn default() -> Self {
535 Self::new()
536 }
537}
538
539#[derive(Clone, Debug)]
541pub struct FitzHughRinzelNeuron {
542 pub v: f64,
543 pub w: f64,
544 pub y: f64,
545 pub a: f64,
546 pub b: f64,
547 pub c: f64,
548 pub d: f64,
549 pub delta: f64,
550 pub mu: f64,
551 pub dt: f64,
552 pub v_threshold: f64,
553}
554
555impl FitzHughRinzelNeuron {
556 pub fn new() -> Self {
557 Self {
558 v: -1.0,
559 w: -0.5,
560 y: 0.0,
561 a: 0.7,
562 b: 0.8,
563 c: -0.775,
564 d: 1.0,
565 delta: 0.08,
566 mu: 0.0001,
567 dt: 0.1,
568 v_threshold: 1.0,
569 }
570 }
571
572 fn valid_numeric_contract(&self) -> bool {
573 self.v.is_finite()
574 && self.w.is_finite()
575 && self.y.is_finite()
576 && self.a.is_finite()
577 && self.b.is_finite()
578 && self.c.is_finite()
579 && self.d.is_finite()
580 && self.delta.is_finite()
581 && self.mu.is_finite()
582 && self.dt.is_finite()
583 && self.v_threshold.is_finite()
584 && self.b > 0.0
585 && self.d > 0.0
586 && self.delta > 0.0
587 && self.mu > 0.0
588 && self.dt > 0.0
589 }
590
591 fn derivatives(&self, v: f64, w: f64, y: f64, current: f64) -> Option<(f64, f64, f64)> {
592 if !(v.is_finite() && w.is_finite() && y.is_finite() && current.is_finite()) {
593 return None;
594 }
595 let dv = v - v.powi(3) / 3.0 - w + y + current;
596 let dw = self.delta * (self.a + v - self.b * w);
597 let dy = self.mu * (self.c - v - self.d * y);
598 if dv.is_finite() && dw.is_finite() && dy.is_finite() {
599 Some((dv, dw, dy))
600 } else {
601 None
602 }
603 }
604
605 fn rk4_candidate(&self, current: f64) -> Option<(f64, f64, f64)> {
606 let (v0, w0, y0, dt) = (self.v, self.w, self.y, self.dt);
607 let (k1v, k1w, k1y) = self.derivatives(v0, w0, y0, current)?;
608 let (k2v, k2w, k2y) = self.derivatives(
609 v0 + 0.5 * dt * k1v,
610 w0 + 0.5 * dt * k1w,
611 y0 + 0.5 * dt * k1y,
612 current,
613 )?;
614 let (k3v, k3w, k3y) = self.derivatives(
615 v0 + 0.5 * dt * k2v,
616 w0 + 0.5 * dt * k2w,
617 y0 + 0.5 * dt * k2y,
618 current,
619 )?;
620 let (k4v, k4w, k4y) =
621 self.derivatives(v0 + dt * k3v, w0 + dt * k3w, y0 + dt * k3y, current)?;
622 Some((
623 v0 + dt * (k1v + 2.0 * k2v + 2.0 * k3v + k4v) / 6.0,
624 w0 + dt * (k1w + 2.0 * k2w + 2.0 * k3w + k4w) / 6.0,
625 y0 + dt * (k1y + 2.0 * k2y + 2.0 * k3y + k4y) / 6.0,
626 ))
627 }
628
629 pub fn step(&mut self, current: f64) -> i32 {
630 if !self.valid_numeric_contract() || !current.is_finite() {
631 return 0;
632 }
633 let v_prev = self.v;
634 let Some((next_v, next_w, next_y)) = self.rk4_candidate(current) else {
635 return 0;
636 };
637 if !(next_v.is_finite() && next_w.is_finite() && next_y.is_finite()) {
638 return 0;
639 }
640 self.v = next_v;
641 self.w = next_w;
642 self.y = next_y;
643 if self.v >= self.v_threshold && v_prev < self.v_threshold {
644 1
645 } else {
646 0
647 }
648 }
649
650 pub fn reset(&mut self) {
651 self.v = -1.0;
652 self.w = -0.5;
653 self.y = 0.0;
654 }
655
656 pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
662 let mut trace = Vec::with_capacity(n_steps);
663 let mut spikes: i64 = 0;
664 for _ in 0..n_steps {
665 let spiked = self.step(current);
666 trace.push(self.v);
667 if spiked == 1 {
668 spikes += 1;
669 }
670 }
671 (trace, spikes)
672 }
673}
674impl Default for FitzHughRinzelNeuron {
675 fn default() -> Self {
676 Self::new()
677 }
678}
679
680#[derive(Clone, Debug)]
682pub struct McKeanNeuron {
683 pub v: f64,
684 pub w: f64,
685 pub a: f64,
686 pub epsilon: f64,
687 pub gamma: f64,
688 pub dt: f64,
689 pub v_peak: f64,
690}
691
692impl McKeanNeuron {
693 pub fn new() -> Self {
694 Self {
695 v: 0.0,
696 w: 0.0,
697 a: 0.25,
698 epsilon: 0.01,
699 gamma: 0.5,
700 dt: 0.1,
701 v_peak: 0.8,
702 }
703 }
704 fn f_v(&self, v: f64) -> f64 {
705 let half_a = self.a / 2.0;
706 let mid = (1.0 + self.a) / 2.0;
707 if v < half_a {
708 -v
709 } else if v < mid {
710 v - self.a
711 } else {
712 1.0 - v
713 }
714 }
715 fn valid_numeric_contract(&self) -> bool {
716 self.v.is_finite()
717 && self.w.is_finite()
718 && self.a.is_finite()
719 && self.epsilon.is_finite()
720 && self.gamma.is_finite()
721 && self.dt.is_finite()
722 && self.v_peak.is_finite()
723 && self.a > 0.0
724 && self.a < 1.0
725 && self.epsilon > 0.0
726 && self.gamma > 0.0
727 && self.dt > 0.0
728 }
729 fn derivatives(&self, v: f64, w: f64, current: f64) -> Option<(f64, f64)> {
730 if !(v.is_finite() && w.is_finite() && current.is_finite()) {
731 return None;
732 }
733 let dv = self.f_v(v) - w + current;
734 let dw = self.epsilon * (v - self.gamma * w);
735 if dv.is_finite() && dw.is_finite() {
736 Some((dv, dw))
737 } else {
738 None
739 }
740 }
741 fn rk4_candidate(&self, current: f64) -> Option<(f64, f64)> {
742 let v0 = self.v;
743 let w0 = self.w;
744 let dt = self.dt;
745 let k1 = self.derivatives(v0, w0, current)?;
746 let k2 = self.derivatives(v0 + 0.5 * dt * k1.0, w0 + 0.5 * dt * k1.1, current)?;
747 let k3 = self.derivatives(v0 + 0.5 * dt * k2.0, w0 + 0.5 * dt * k2.1, current)?;
748 let k4 = self.derivatives(v0 + dt * k3.0, w0 + dt * k3.1, current)?;
749 let next_v = v0 + dt * (k1.0 + 2.0 * k2.0 + 2.0 * k3.0 + k4.0) / 6.0;
750 let next_w = w0 + dt * (k1.1 + 2.0 * k2.1 + 2.0 * k3.1 + k4.1) / 6.0;
751 if next_v.is_finite() && next_w.is_finite() {
752 Some((next_v, next_w))
753 } else {
754 None
755 }
756 }
757 pub fn step(&mut self, current: f64) -> i32 {
758 if !self.valid_numeric_contract() || !current.is_finite() {
759 return 0;
760 }
761 let v_prev = self.v;
762 let (next_v, next_w) = match self.rk4_candidate(current) {
763 Some(candidate) => candidate,
764 None => return 0,
765 };
766 self.v = next_v;
767 self.w = next_w;
768 if self.v >= self.v_peak && v_prev < self.v_peak {
769 1
770 } else {
771 0
772 }
773 }
774 pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
780 let mut trace = Vec::with_capacity(n_steps);
781 let mut spikes: i64 = 0;
782 for _ in 0..n_steps {
783 let spiked = self.step(current);
784 trace.push(self.v);
785 spikes += spiked as i64;
786 }
787 (trace, spikes)
788 }
789 pub fn reset(&mut self) {
790 self.v = 0.0;
791 self.w = 0.0;
792 }
793}
794impl Default for McKeanNeuron {
795 fn default() -> Self {
796 Self::new()
797 }
798}
799
800#[derive(Clone, Debug)]
802pub struct TermanWangOscillator {
803 pub v: f64,
804 pub w: f64,
805 pub alpha: f64,
806 pub beta: f64,
807 pub epsilon: f64,
808 pub rho: f64,
809 pub dt: f64,
810 pub v_peak: f64,
811}
812
813impl TermanWangOscillator {
814 pub fn new() -> Self {
815 Self {
816 v: -1.5,
817 w: -0.5,
818 alpha: 3.0,
819 beta: 0.2,
820 epsilon: 0.02,
821 rho: 0.0,
822 dt: 0.05,
823 v_peak: 1.5,
824 }
825 }
826 fn valid_numeric_contract(&self) -> bool {
827 self.v.is_finite()
828 && self.w.is_finite()
829 && self.alpha.is_finite()
830 && self.beta.is_finite()
831 && self.epsilon.is_finite()
832 && self.rho.is_finite()
833 && self.dt.is_finite()
834 && self.v_peak.is_finite()
835 && self.beta > 0.0
836 && self.epsilon > 0.0
837 && self.dt > 0.0
838 }
839 fn derivatives(&self, v: f64, w: f64, current: f64) -> Option<(f64, f64)> {
840 if !(v.is_finite() && w.is_finite() && current.is_finite()) {
841 return None;
842 }
843 let f = 3.0 * v - v.powi(3) + 2.0;
844 let g = self.alpha * (1.0 + (v / self.beta).tanh());
845 let dv = f - w + current + self.rho;
846 let dw = self.epsilon * (g - w);
847 if dv.is_finite() && dw.is_finite() {
848 Some((dv, dw))
849 } else {
850 None
851 }
852 }
853 fn rk4_candidate(&self, current: f64) -> Option<(f64, f64)> {
854 let dt = self.dt;
855 let (k1v, k1w) = self.derivatives(self.v, self.w, current)?;
856 let (k2v, k2w) =
857 self.derivatives(self.v + 0.5 * dt * k1v, self.w + 0.5 * dt * k1w, current)?;
858 let (k3v, k3w) =
859 self.derivatives(self.v + 0.5 * dt * k2v, self.w + 0.5 * dt * k2w, current)?;
860 let (k4v, k4w) = self.derivatives(self.v + dt * k3v, self.w + dt * k3w, current)?;
861 let v = self.v + dt * (k1v + 2.0 * k2v + 2.0 * k3v + k4v) / 6.0;
862 let w = self.w + dt * (k1w + 2.0 * k2w + 2.0 * k3w + k4w) / 6.0;
863 if v.is_finite() && w.is_finite() {
864 Some((v, w))
865 } else {
866 None
867 }
868 }
869
870 pub fn step(&mut self, current: f64) -> i32 {
871 if !self.valid_numeric_contract() || !current.is_finite() {
872 return 0;
873 }
874 let v_prev = self.v;
875 let Some((next_v, next_w)) = self.rk4_candidate(current) else {
876 return 0;
877 };
878 self.v = next_v;
879 self.w = next_w;
880 if self.v >= self.v_peak && v_prev < self.v_peak {
881 1
882 } else {
883 0
884 }
885 }
886 pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
893 let mut trace = Vec::with_capacity(n_steps);
894 let mut spikes: i64 = 0;
895 for _ in 0..n_steps {
896 let spiked = self.step(current);
897 trace.push(self.v);
898 spikes += spiked as i64;
899 }
900 (trace, spikes)
901 }
902 pub fn reset(&mut self) {
903 self.v = -1.5;
904 self.w = -0.5;
905 }
906}
907impl Default for TermanWangOscillator {
908 fn default() -> Self {
909 Self::new()
910 }
911}
912
913#[derive(Clone, Debug)]
915pub struct BendaHerzNeuron {
916 pub a: f64,
917 pub f_max: f64,
918 pub beta: f64,
919 pub i_half: f64,
920 pub tau_a: f64,
921 pub delta_a: f64,
922 pub dt: f64,
923 rng: Xoshiro256PlusPlus,
924}
925
926impl BendaHerzNeuron {
927 pub fn new(seed: u64) -> Self {
928 Self {
929 a: 0.0,
930 f_max: 200.0,
931 beta: 0.1,
932 i_half: 5.0,
933 tau_a: 100.0,
934 delta_a: 0.5,
935 dt: 1.0,
936 rng: Xoshiro256PlusPlus::seed_from_u64(seed),
937 }
938 }
939 pub fn step(&mut self, current: f64) -> i32 {
940 let x = current - self.a;
941 let rate = self.f_max / (1.0 + (-self.beta * (x - self.i_half)).exp());
942 self.a += (-self.a / self.tau_a + self.delta_a * rate) * self.dt;
943 let p = rate * self.dt / 1000.0;
944 if self.rng.random::<f64>() < p {
945 1
946 } else {
947 0
948 }
949 }
950 pub fn reset(&mut self) {
951 self.a = 0.0;
952 }
953}
954
955#[derive(Clone, Debug)]
957pub struct AlphaNeuron {
958 pub v: f64,
959 pub i_exc: f64,
960 pub i_inh: f64,
961 pub v_rest: f64,
962 pub v_threshold: f64,
963 pub tau_v: f64,
964 pub tau_exc: f64,
965 pub tau_inh: f64,
966 pub dt: f64,
967}
968
969impl AlphaNeuron {
970 pub fn new() -> Self {
971 Self {
972 v: 0.0,
973 i_exc: 0.0,
974 i_inh: 0.0,
975 v_rest: 0.0,
976 v_threshold: 1.0,
977 tau_v: 20.0,
978 tau_exc: 5.0,
979 tau_inh: 10.0,
980 dt: 1.0,
981 }
982 }
983 pub fn step(&mut self, exc_current: f64, inh_current: f64) -> i32 {
984 self.i_exc += (-self.i_exc / self.tau_exc + exc_current) * self.dt;
985 self.i_inh += (-self.i_inh / self.tau_inh + inh_current) * self.dt;
986 self.v += (-(self.v - self.v_rest) + self.i_exc - self.i_inh) / self.tau_v * self.dt;
987 if self.v >= self.v_threshold {
988 self.v = self.v_rest;
989 1
990 } else {
991 0
992 }
993 }
994 pub fn reset(&mut self) {
995 self.v = self.v_rest;
996 self.i_exc = 0.0;
997 self.i_inh = 0.0;
998 }
999}
1000impl Default for AlphaNeuron {
1001 fn default() -> Self {
1002 Self::new()
1003 }
1004}
1005
1006#[derive(Clone, Debug)]
1008pub struct COBALIFNeuron {
1009 pub v: f64,
1010 pub g_e: f64,
1011 pub g_i: f64,
1012 pub c_m: f64,
1013 pub g_l: f64,
1014 pub e_l: f64,
1015 pub e_e: f64,
1016 pub e_i: f64,
1017 pub tau_e: f64,
1018 pub tau_i: f64,
1019 pub v_threshold: f64,
1020 pub v_reset: f64,
1021 pub dt: f64,
1022}
1023
1024impl COBALIFNeuron {
1025 pub fn new() -> Self {
1026 Self {
1027 v: -65.0,
1028 g_e: 0.0,
1029 g_i: 0.0,
1030 c_m: 200.0,
1031 g_l: 10.0,
1032 e_l: -65.0,
1033 e_e: 0.0,
1034 e_i: -80.0,
1035 tau_e: 5.0,
1036 tau_i: 10.0,
1037 v_threshold: -50.0,
1038 v_reset: -65.0,
1039 dt: 0.1,
1040 }
1041 }
1042 pub fn step(&mut self, current: f64, delta_ge: f64, delta_gi: f64) -> i32 {
1043 self.g_e += delta_ge;
1044 self.g_i += delta_gi;
1045 let i_syn = self.g_e * (self.v - self.e_e) + self.g_i * (self.v - self.e_i);
1046 self.v += (-self.g_l * (self.v - self.e_l) - i_syn + current) / self.c_m * self.dt;
1047 self.g_e *= (-self.dt / self.tau_e).exp();
1048 self.g_i *= (-self.dt / self.tau_i).exp();
1049 if self.v >= self.v_threshold {
1050 self.v = self.v_reset;
1051 1
1052 } else {
1053 0
1054 }
1055 }
1056 pub fn reset(&mut self) {
1057 self.v = self.e_l;
1058 self.g_e = 0.0;
1059 self.g_i = 0.0;
1060 }
1061}
1062impl Default for COBALIFNeuron {
1063 fn default() -> Self {
1064 Self::new()
1065 }
1066}
1067
1068#[derive(Clone, Debug)]
1070pub struct GutkinErmentroutNeuron {
1071 pub v: f64,
1072 pub n: f64,
1073 pub g_na: f64,
1074 pub g_k: f64,
1075 pub g_l: f64,
1076 pub e_na: f64,
1077 pub e_k: f64,
1078 pub e_l: f64,
1079 pub dt: f64,
1080 pub v_threshold: f64,
1081}
1082
1083impl GutkinErmentroutNeuron {
1084 pub fn new() -> Self {
1085 Self {
1086 v: -65.0,
1087 n: 0.1,
1088 g_na: 20.0,
1089 g_k: 10.0,
1090 g_l: 8.0,
1091 e_na: 60.0,
1092 e_k: -90.0,
1093 e_l: -80.0,
1094 dt: 0.05,
1095 v_threshold: -20.0,
1096 }
1097 }
1098 fn valid_numeric_contract(&self) -> bool {
1099 self.v.is_finite()
1100 && self.n.is_finite()
1101 && (0.0..=1.0).contains(&self.n)
1102 && self.g_na.is_finite()
1103 && self.g_na >= 0.0
1104 && self.g_k.is_finite()
1105 && self.g_k >= 0.0
1106 && self.g_l.is_finite()
1107 && self.g_l >= 0.0
1108 && self.e_na.is_finite()
1109 && self.e_k.is_finite()
1110 && self.e_l.is_finite()
1111 && self.dt.is_finite()
1112 && self.dt > 0.0
1113 && self.v_threshold.is_finite()
1114 }
1115
1116 fn m_inf(v: f64) -> f64 {
1117 1.0 / (1.0 + (-(v + 20.0) / 15.0).exp())
1118 }
1119
1120 fn n_inf(v: f64) -> f64 {
1121 1.0 / (1.0 + (-(v + 25.0) / 5.0).exp())
1122 }
1123
1124 fn rhs(&self, v: f64, n_gate: f64, current: f64) -> Option<(f64, f64)> {
1125 if !(v.is_finite() && n_gate.is_finite() && current.is_finite()) {
1126 return None;
1127 }
1128 if !(0.0..=1.0).contains(&n_gate) {
1129 return None;
1130 }
1131 let m_inf = Self::m_inf(v);
1132 let n_inf = Self::n_inf(v);
1133 if !(m_inf.is_finite() && n_inf.is_finite()) {
1134 return None;
1135 }
1136 let i_na = self.g_na * m_inf * (v - self.e_na);
1137 let i_k = self.g_k * n_gate * (v - self.e_k);
1138 let i_l = self.g_l * (v - self.e_l);
1139 let dv = -i_na - i_k - i_l + current;
1140 let dn = n_inf - n_gate;
1141 if dv.is_finite() && dn.is_finite() {
1142 Some((dv, dn))
1143 } else {
1144 None
1145 }
1146 }
1147
1148 pub fn step(&mut self, current: f64) -> i32 {
1149 if !self.valid_numeric_contract() || !current.is_finite() {
1150 return 0;
1151 }
1152 let v_prev = self.v;
1153 let Some((k1_v, k1_n)) = self.rhs(self.v, self.n, current) else {
1154 return 0;
1155 };
1156 let Some((k2_v, k2_n)) = self.rhs(
1157 self.v + 0.5 * self.dt * k1_v,
1158 self.n + 0.5 * self.dt * k1_n,
1159 current,
1160 ) else {
1161 return 0;
1162 };
1163 let Some((k3_v, k3_n)) = self.rhs(
1164 self.v + 0.5 * self.dt * k2_v,
1165 self.n + 0.5 * self.dt * k2_n,
1166 current,
1167 ) else {
1168 return 0;
1169 };
1170 let Some((k4_v, k4_n)) =
1171 self.rhs(self.v + self.dt * k3_v, self.n + self.dt * k3_n, current)
1172 else {
1173 return 0;
1174 };
1175 let next_v = self.v + self.dt * (k1_v + 2.0 * k2_v + 2.0 * k3_v + k4_v) / 6.0;
1176 let next_n = self.n + self.dt * (k1_n + 2.0 * k2_n + 2.0 * k3_n + k4_n) / 6.0;
1177 if !(next_v.is_finite() && next_n.is_finite() && (0.0..=1.0).contains(&next_n)) {
1178 return 0;
1179 }
1180 self.v = next_v;
1181 self.n = next_n;
1182 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1183 1
1184 } else {
1185 0
1186 }
1187 }
1188 pub fn reset(&mut self) {
1189 self.v = -65.0;
1190 self.n = 0.1;
1191 }
1192}
1193impl Default for GutkinErmentroutNeuron {
1194 fn default() -> Self {
1195 Self::new()
1196 }
1197}
1198
1199#[derive(Clone, Debug)]
1201pub struct WilsonHRNeuron {
1202 pub v: f64,
1203 pub r: f64,
1204 pub tau_r: f64,
1205 pub v_peak: f64,
1206 pub dt: f64,
1207}
1208
1209impl WilsonHRNeuron {
1210 pub fn new() -> Self {
1211 Self {
1212 v: -0.7,
1213 r: 0.1,
1214 tau_r: 1.9,
1215 v_peak: 0.4,
1216 dt: 0.05,
1217 }
1218 }
1219 fn valid_numeric_contract(&self) -> bool {
1220 self.v.is_finite()
1221 && self.r.is_finite()
1222 && self.tau_r.is_finite()
1223 && self.tau_r > 0.0
1224 && self.v_peak.is_finite()
1225 && self.dt.is_finite()
1226 && self.dt > 0.0
1227 }
1228 fn poly(v: f64) -> f64 {
1229 -(17.81 + 47.71 * v + 32.63 * v * v) * (v - 0.55)
1230 }
1231 fn derivatives(&self, v: f64, r: f64, current: f64) -> Option<(f64, f64)> {
1232 if !(v.is_finite() && r.is_finite() && current.is_finite()) {
1233 return None;
1234 }
1235 let poly = Self::poly(v);
1236 let syn = -26.0 * r * (v + 0.92);
1237 let dv = poly + syn + current;
1238 let dr = (-r + 1.35 * v + 1.03) / self.tau_r;
1239 if poly.is_finite() && syn.is_finite() && dv.is_finite() && dr.is_finite() {
1240 Some((dv, dr))
1241 } else {
1242 None
1243 }
1244 }
1245 fn rk4_candidate(&self, current: f64) -> Option<(f64, f64)> {
1246 let v0 = self.v;
1247 let r0 = self.r;
1248 let dt = self.dt;
1249 let k1 = self.derivatives(v0, r0, current)?;
1250 let k2 = self.derivatives(v0 + 0.5 * dt * k1.0, r0 + 0.5 * dt * k1.1, current)?;
1251 let k3 = self.derivatives(v0 + 0.5 * dt * k2.0, r0 + 0.5 * dt * k2.1, current)?;
1252 let k4 = self.derivatives(v0 + dt * k3.0, r0 + dt * k3.1, current)?;
1253 let next_v = v0 + dt * (k1.0 + 2.0 * k2.0 + 2.0 * k3.0 + k4.0) / 6.0;
1254 let next_r = r0 + dt * (k1.1 + 2.0 * k2.1 + 2.0 * k3.1 + k4.1) / 6.0;
1255 if next_v.is_finite() && next_r.is_finite() {
1256 Some((next_v, next_r))
1257 } else {
1258 None
1259 }
1260 }
1261 pub fn step(&mut self, current: f64) -> i32 {
1262 if !self.valid_numeric_contract() || !current.is_finite() {
1263 return 0;
1264 }
1265 let (next_v, next_r) = match self.rk4_candidate(current) {
1266 Some(candidate) => candidate,
1267 None => return 0,
1268 };
1269 self.v = next_v;
1270 self.r = next_r;
1271 if self.v >= self.v_peak {
1273 self.v = -0.7;
1274 1
1275 } else {
1276 0
1277 }
1278 }
1279 pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
1285 let mut trace = Vec::with_capacity(n_steps);
1286 let mut spikes: i64 = 0;
1287 for _ in 0..n_steps {
1288 let spiked = self.step(current);
1289 trace.push(self.v);
1290 spikes += spiked as i64;
1291 }
1292 (trace, spikes)
1293 }
1294 pub fn reset(&mut self) {
1295 self.v = -0.7;
1296 self.r = 0.1;
1297 }
1298}
1299impl Default for WilsonHRNeuron {
1300 fn default() -> Self {
1301 Self::new()
1302 }
1303}
1304
1305#[derive(Clone, Debug)]
1307pub struct ChayNeuron {
1308 pub v: f64,
1309 pub n: f64,
1310 pub ca: f64,
1311 pub g_ca: f64,
1312 pub g_k: f64,
1313 pub g_kca: f64,
1314 pub g_l: f64,
1315 pub e_ca: f64,
1316 pub e_k: f64,
1317 pub e_l: f64,
1318 pub rho: f64,
1319 pub alpha_ca: f64,
1320 pub k_ca: f64,
1321 pub dt: f64,
1322 pub v_threshold: f64,
1323}
1324
1325impl ChayNeuron {
1326 pub fn new() -> Self {
1327 Self {
1328 v: -50.0,
1329 n: 0.1,
1330 ca: 0.1,
1331 g_ca: 25.0,
1332 g_k: 1400.0,
1333 g_kca: 12.0,
1334 g_l: 7.0,
1335 e_ca: 100.0,
1336 e_k: -75.0,
1337 e_l: -40.0,
1338 rho: 0.00015,
1339 alpha_ca: 0.002,
1340 k_ca: 0.04,
1341 dt: 0.02,
1342 v_threshold: -20.0,
1343 }
1344 }
1345 pub fn step(&mut self, current: f64) -> i32 {
1346 if !current.is_finite() || !self.dt.is_finite() || self.dt <= 0.0 {
1347 return 0;
1348 }
1349
1350 let v_initial = self.v;
1351 let mut v = self.v;
1352 let mut n = self.n;
1353 let mut ca = self.ca;
1354 let substeps = (self.dt / 0.001_f64).ceil().max(1.0) as usize;
1355 let h = self.dt / substeps as f64;
1356 let mut crossed = false;
1357
1358 for _ in 0..substeps {
1359 let m_inf = 1.0 / (1.0 + (-(v + 25.0) / 8.0).clamp(-700.0, 700.0).exp());
1360 let n_inf = 1.0 / (1.0 + (-(v + 18.0) / 14.0).clamp(-700.0, 700.0).exp());
1361 let d = (v + 18.0).abs().max(0.01);
1362 let tau_n = 1.0 / (0.01 * d);
1363 let ca_denominator = ca + 1.0;
1364 if ca_denominator <= 0.0 {
1365 return 0;
1366 }
1367 let kca_act = ca / ca_denominator;
1368 let i_ca = self.g_ca * m_inf * (v - self.e_ca);
1369 let i_k = self.g_k * n * (v - self.e_k);
1370 let i_kca = self.g_kca * kca_act * (v - self.e_k);
1371 let i_l = self.g_l * (v - self.e_l);
1372
1373 let v_next = v + (-i_ca - i_k - i_kca - i_l + current) * h;
1374 let n_next = n + (n_inf - n) / tau_n.max(0.01) * h;
1375 let ca_next = ca + self.rho * (-self.alpha_ca * i_ca - self.k_ca * ca) * h;
1376 if !v_next.is_finite()
1377 || !n_next.is_finite()
1378 || !ca_next.is_finite()
1379 || !(-200.0..=200.0).contains(&v_next)
1380 || !(0.0..=1.0).contains(&n_next)
1381 || !(0.0..=100.0).contains(&ca_next)
1382 {
1383 return 0;
1384 }
1385 crossed = crossed || (v_next >= self.v_threshold && v < self.v_threshold);
1386 v = v_next;
1387 n = n_next;
1388 ca = ca_next;
1389 }
1390
1391 self.v = v;
1392 self.n = n;
1393 self.ca = ca;
1394 if crossed && v_initial < self.v_threshold {
1395 1
1396 } else {
1397 0
1398 }
1399 }
1400 pub fn reset(&mut self) {
1401 self.v = -50.0;
1402 self.n = 0.1;
1403 self.ca = 0.1;
1404 }
1405}
1406impl Default for ChayNeuron {
1407 fn default() -> Self {
1408 Self::new()
1409 }
1410}
1411
1412#[derive(Clone, Debug)]
1414pub struct ChayKeizerNeuron {
1415 pub v: f64,
1416 pub n: f64,
1417 pub ca: f64,
1418 pub g_ca: f64,
1419 pub g_k: f64,
1420 pub g_kca: f64,
1421 pub g_l: f64,
1422 pub e_ca: f64,
1423 pub e_k: f64,
1424 pub e_l: f64,
1425 pub k_d: f64,
1426 pub f_ca: f64,
1427 pub k_ca: f64,
1428 pub dt: f64,
1429 pub v_threshold: f64,
1430}
1431
1432impl ChayKeizerNeuron {
1433 pub fn new() -> Self {
1434 Self {
1435 v: -50.0,
1436 n: 0.01,
1437 ca: 0.1,
1438 g_ca: 20.0,
1439 g_k: 25.0,
1440 g_kca: 12.0,
1441 g_l: 0.1,
1442 e_ca: 100.0,
1443 e_k: -75.0,
1444 e_l: -40.0,
1445 k_d: 1.0,
1446 f_ca: 0.004,
1447 k_ca: 0.03,
1448 dt: 0.02,
1449 v_threshold: -20.0,
1450 }
1451 }
1452 pub fn step(&mut self, current: f64) -> i32 {
1453 let v_prev = self.v;
1454 let m_inf = 1.0 / (1.0 + (-(self.v + 25.0) / 8.0).exp());
1455 let n_inf = 1.0 / (1.0 + (-(self.v + 18.0) / 14.0).exp());
1456 let tau_n = (20.0 / (1.0 + ((self.v + 18.0) / 14.0).exp())).max(0.1);
1457 let q_kca = self.ca / (self.ca + self.k_d);
1458 let i_ca = self.g_ca * m_inf * (self.v - self.e_ca);
1459 let i_k = self.g_k * self.n * (self.v - self.e_k);
1460 let i_kca = self.g_kca * q_kca * (self.v - self.e_k);
1461 let i_l = self.g_l * (self.v - self.e_l);
1462 self.v += (-i_ca - i_k - i_kca - i_l + current) * self.dt;
1463 self.v = self.v.clamp(-200.0, 200.0);
1464 self.n += (n_inf - self.n) / tau_n * self.dt;
1465 self.ca = (self.ca + (-self.f_ca * i_ca - self.k_ca * self.ca) * self.dt).max(0.0);
1466 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1467 1
1468 } else {
1469 0
1470 }
1471 }
1472 pub fn reset(&mut self) {
1473 self.v = -50.0;
1474 self.n = 0.01;
1475 self.ca = 0.1;
1476 }
1477}
1478impl Default for ChayKeizerNeuron {
1479 fn default() -> Self {
1480 Self::new()
1481 }
1482}
1483
1484#[derive(Clone, Debug)]
1486pub struct ShermanRinzelKeizerNeuron {
1487 pub v: f64,
1488 pub n: f64,
1489 pub s: f64,
1490 pub g_ca: f64,
1491 pub g_k: f64,
1492 pub g_s: f64,
1493 pub e_ca: f64,
1494 pub e_k: f64,
1495 pub tau_s: f64,
1496 pub dt: f64,
1497 pub v_threshold: f64,
1498}
1499
1500impl ShermanRinzelKeizerNeuron {
1501 pub fn new() -> Self {
1502 Self {
1503 v: -50.0,
1504 n: 0.1,
1505 s: 0.1,
1506 g_ca: 3.6,
1507 g_k: 10.0,
1508 g_s: 4.0,
1509 e_ca: 25.0,
1510 e_k: -75.0,
1511 tau_s: 5000.0,
1512 dt: 0.5,
1513 v_threshold: -20.0,
1514 }
1515 }
1516 pub fn step(&mut self, current: f64) -> i32 {
1517 let v_prev = self.v;
1518 let m_inf = 1.0 / (1.0 + (-(self.v + 20.0) / 12.0).exp());
1519 let n_inf = 1.0 / (1.0 + (-(self.v + 16.0) / 5.0).exp());
1520 let s_inf = 1.0 / (1.0 + (-(self.v + 35.0) / 10.0).exp());
1521 let tau_n = 9.09;
1522 let i_ca = self.g_ca * m_inf * (self.v - self.e_ca);
1523 let i_k = self.g_k * self.n * (self.v - self.e_k);
1524 let i_s = self.g_s * self.s * (self.v - self.e_k);
1525 self.v += (-i_ca - i_k - i_s + current) * self.dt;
1526 self.n += (n_inf - self.n) / tau_n * self.dt;
1527 self.s += (s_inf - self.s) / self.tau_s * self.dt;
1528 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1529 1
1530 } else {
1531 0
1532 }
1533 }
1534 pub fn reset(&mut self) {
1535 self.v = -50.0;
1536 self.n = 0.1;
1537 self.s = 0.1;
1538 }
1539}
1540impl Default for ShermanRinzelKeizerNeuron {
1541 fn default() -> Self {
1542 Self::new()
1543 }
1544}
1545
1546#[derive(Clone, Debug)]
1548pub struct ButeraRespiratoryNeuron {
1549 pub v: f64,
1550 pub n: f64,
1551 pub h_nap: f64,
1552 pub g_na: f64,
1553 pub g_nap: f64,
1554 pub g_k: f64,
1555 pub g_l: f64,
1556 pub e_na: f64,
1557 pub e_k: f64,
1558 pub e_l: f64,
1559 pub tau_h: f64,
1560 pub dt: f64,
1561 pub v_threshold: f64,
1562}
1563
1564impl ButeraRespiratoryNeuron {
1565 pub fn new() -> Self {
1566 Self {
1567 v: -50.0,
1568 n: 0.01,
1569 h_nap: 0.5,
1570 g_na: 28.0,
1571 g_nap: 2.8,
1572 g_k: 11.2,
1573 g_l: 2.8,
1574 e_na: 50.0,
1575 e_k: -85.0,
1576 e_l: -65.0,
1577 tau_h: 10000.0,
1578 dt: 0.1,
1579 v_threshold: -20.0,
1580 }
1581 }
1582 fn butera_valid_state(v: f64, n: f64, h_nap: f64) -> bool {
1583 [v, n, h_nap].iter().all(|x| x.is_finite())
1584 && (-200.0..=100.0).contains(&v)
1585 && (-0.05..=1.05).contains(&n)
1586 && (-0.05..=1.05).contains(&h_nap)
1587 }
1588
1589 fn butera_valid_static(&self) -> bool {
1590 [
1591 self.g_na,
1592 self.g_nap,
1593 self.g_k,
1594 self.g_l,
1595 self.e_na,
1596 self.e_k,
1597 self.e_l,
1598 self.tau_h,
1599 self.dt,
1600 self.v_threshold,
1601 ]
1602 .iter()
1603 .all(|x| x.is_finite())
1604 && self.g_na >= 0.0
1605 && self.g_nap >= 0.0
1606 && self.g_k >= 0.0
1607 && self.g_l >= 0.0
1608 && self.tau_h > 0.0
1609 && self.dt > 0.0
1610 }
1611
1612 fn butera_derivatives(&self, state: (f64, f64, f64), current: f64) -> Option<(f64, f64, f64)> {
1613 let (mut v, mut n, mut h_nap) = state;
1614 if !(v.is_finite() && n.is_finite() && h_nap.is_finite() && current.is_finite()) {
1615 return None;
1616 }
1617 v = v.clamp(-200.0, 100.0);
1618 n = n.clamp(0.0, 1.0);
1619 h_nap = h_nap.clamp(0.0, 1.0);
1620 let m_na = 1.0 / (1.0 + (-(v + 34.0) / 5.0).exp());
1621 let n_inf = 1.0 / (1.0 + (-(v + 29.0) / 4.0).exp());
1622 let m_nap = 1.0 / (1.0 + (-(v + 40.0) / 6.0).exp());
1623 let h_nap_inf = 1.0 / (1.0 + ((v + 48.0) / 6.0).exp());
1624 let tau_n = (10.0 / ((v + 29.0) / 8.0).cosh().max(1e-12)).max(0.01);
1625 let tau_h_eff = (self.tau_h / ((v + 48.0) / 12.0).cosh().max(1e-12)).max(0.1);
1626 let i_na = self.g_na * m_na.powi(3) * (1.0 - n) * (v - self.e_na);
1627 let i_nap = self.g_nap * m_nap * h_nap * (v - self.e_na);
1628 let i_k = self.g_k * n.powi(4) * (v - self.e_k);
1629 let i_l = self.g_l * (v - self.e_l);
1630 let deriv = (
1631 -i_na - i_nap - i_k - i_l + current,
1632 (n_inf - n) / tau_n,
1633 (h_nap_inf - h_nap) / tau_h_eff,
1634 );
1635 [deriv.0, deriv.1, deriv.2]
1636 .iter()
1637 .all(|x| x.is_finite())
1638 .then_some(deriv)
1639 }
1640
1641 fn butera_rk4_candidate(&self, current: f64) -> Option<(f64, f64, f64)> {
1642 if !self.butera_valid_static()
1643 || !current.is_finite()
1644 || !Self::butera_valid_state(self.v, self.n, self.h_nap)
1645 {
1646 return None;
1647 }
1648 let state = (self.v, self.n, self.h_nap);
1649 let k1 = self.butera_derivatives(state, current)?;
1650 let k2_state = (
1651 state.0 + 0.5 * self.dt * k1.0,
1652 state.1 + 0.5 * self.dt * k1.1,
1653 state.2 + 0.5 * self.dt * k1.2,
1654 );
1655 let k2 = self.butera_derivatives(k2_state, current)?;
1656 let k3_state = (
1657 state.0 + 0.5 * self.dt * k2.0,
1658 state.1 + 0.5 * self.dt * k2.1,
1659 state.2 + 0.5 * self.dt * k2.2,
1660 );
1661 let k3 = self.butera_derivatives(k3_state, current)?;
1662 let k4_state = (
1663 state.0 + self.dt * k3.0,
1664 state.1 + self.dt * k3.1,
1665 state.2 + self.dt * k3.2,
1666 );
1667 let k4 = self.butera_derivatives(k4_state, current)?;
1668 let candidate = (
1669 state.0 + self.dt * (k1.0 + 2.0 * k2.0 + 2.0 * k3.0 + k4.0) / 6.0,
1670 state.1 + self.dt * (k1.1 + 2.0 * k2.1 + 2.0 * k3.1 + k4.1) / 6.0,
1671 state.2 + self.dt * (k1.2 + 2.0 * k2.2 + 2.0 * k3.2 + k4.2) / 6.0,
1672 );
1673 if candidate.0.is_finite() && candidate.1.is_finite() && candidate.2.is_finite() {
1674 Some((
1675 candidate.0.clamp(-200.0, 100.0),
1676 candidate.1.clamp(0.0, 1.0),
1677 candidate.2.clamp(0.0, 1.0),
1678 ))
1679 } else {
1680 None
1681 }
1682 }
1683
1684 pub fn step(&mut self, current: f64) -> i32 {
1685 let v_prev = self.v;
1686 let Some((v, n, h_nap)) = self.butera_rk4_candidate(current) else {
1687 return 0;
1688 };
1689 self.v = v;
1690 self.n = n;
1691 self.h_nap = h_nap;
1692 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1693 1
1694 } else {
1695 0
1696 }
1697 }
1698 pub fn reset(&mut self) {
1699 self.v = -50.0;
1700 self.n = 0.01;
1701 self.h_nap = 0.5;
1702 }
1703}
1704impl Default for ButeraRespiratoryNeuron {
1705 fn default() -> Self {
1706 Self::new()
1707 }
1708}
1709
1710#[derive(Clone, Debug)]
1712pub struct EPropALIFNeuron {
1713 pub v: f64,
1714 pub a: f64,
1715 pub e_trace: f64,
1716 pub alpha_m: f64,
1717 pub alpha_a: f64,
1718 pub v_threshold_base: f64,
1719 pub beta: f64,
1720}
1721
1722impl EPropALIFNeuron {
1723 pub fn new(tau_m: f64, tau_a: f64, dt: f64) -> Self {
1724 Self {
1725 v: 0.0,
1726 a: 0.0,
1727 e_trace: 0.0,
1728 alpha_m: (-dt / tau_m).exp(),
1729 alpha_a: (-dt / tau_a).exp(),
1730 v_threshold_base: 1.0,
1731 beta: 0.07,
1732 }
1733 }
1734 pub fn step(&mut self, current: f64) -> i32 {
1735 self.v = self.alpha_m * self.v + current;
1736 let threshold = self.v_threshold_base + self.beta * self.a;
1737 let psi = ((1.0 - (self.v - threshold).abs()) * 0.3).max(0.0);
1738 self.e_trace = self.alpha_a * self.e_trace + psi;
1739 if self.v >= threshold {
1740 self.v = 0.0;
1741 self.a = self.alpha_a * self.a + 1.0;
1742 1
1743 } else {
1744 self.a *= self.alpha_a;
1745 0
1746 }
1747 }
1748 pub fn reset(&mut self) {
1749 self.v = 0.0;
1750 self.a = 0.0;
1751 self.e_trace = 0.0;
1752 }
1753}
1754
1755impl Default for EPropALIFNeuron {
1756 fn default() -> Self {
1757 Self::new(20.0, 200.0, 1.0)
1758 }
1759}
1760
1761#[derive(Clone, Debug)]
1763pub struct SuperSpikeNeuron {
1764 pub v: f64,
1765 pub trace: f64,
1766 pub alpha_m: f64,
1767 pub alpha_e: f64,
1768 pub v_threshold: f64,
1769 pub v_reset: f64,
1770 pub beta_sg: f64,
1771}
1772
1773impl SuperSpikeNeuron {
1774 pub fn new(tau_m: f64, tau_e: f64, dt: f64) -> Self {
1775 Self {
1776 v: 0.0,
1777 trace: 0.0,
1778 alpha_m: (-dt / tau_m).exp(),
1779 alpha_e: (-dt / tau_e).exp(),
1780 v_threshold: 1.0,
1781 v_reset: 0.0,
1782 beta_sg: 10.0,
1783 }
1784 }
1785 pub fn step(&mut self, current: f64) -> i32 {
1786 self.v = self.alpha_m * self.v + current;
1787 let sg = 1.0 / (self.beta_sg * (self.v - self.v_threshold).abs() + 1.0).powi(2);
1788 self.trace = self.alpha_e * self.trace + sg;
1789 if self.v >= self.v_threshold {
1790 self.v = self.v_reset;
1791 1
1792 } else {
1793 0
1794 }
1795 }
1796 pub fn reset(&mut self) {
1797 self.v = 0.0;
1798 self.trace = 0.0;
1799 }
1800}
1801
1802impl Default for SuperSpikeNeuron {
1803 fn default() -> Self {
1804 Self::new(10.0, 10.0, 1.0)
1805 }
1806}
1807
1808#[derive(Clone, Debug)]
1810pub struct LearnableNeuronModel {
1811 pub v: f64,
1812 pub alpha: f64,
1813 pub beta: f64,
1814 pub gamma: f64,
1815 pub v_threshold: f64,
1816 pub f_slope: f64,
1817 pub f_shift: f64,
1818}
1819
1820impl LearnableNeuronModel {
1821 pub fn new() -> Self {
1822 Self {
1823 v: 0.0,
1824 alpha: 0.9,
1825 beta: 0.1,
1826 gamma: 0.05,
1827 v_threshold: 1.0,
1828 f_slope: 5.0,
1829 f_shift: 0.5,
1830 }
1831 }
1832 pub fn step(&mut self, current: f64) -> i32 {
1833 let f_v = 1.0 / (1.0 + (-(self.f_slope * (self.v - self.f_shift))).exp());
1834 self.v = self.alpha * self.v + self.beta * current + self.gamma * f_v;
1835 if self.v >= self.v_threshold {
1836 self.v = 0.0;
1837 1
1838 } else {
1839 0
1840 }
1841 }
1842 pub fn reset(&mut self) {
1843 self.v = 0.0;
1844 }
1845}
1846impl Default for LearnableNeuronModel {
1847 fn default() -> Self {
1848 Self::new()
1849 }
1850}
1851
1852#[derive(Clone, Debug)]
1854pub struct PernarowskiNeuron {
1855 pub v: f64,
1856 pub w: f64,
1857 pub z: f64,
1858 pub alpha: f64,
1859 pub beta: f64,
1860 pub eps1: f64,
1861 pub eps2: f64,
1862 pub gamma: f64,
1863 pub dt: f64,
1864 pub v_threshold: f64,
1865}
1866
1867impl PernarowskiNeuron {
1868 pub fn new() -> Self {
1869 Self {
1870 v: -1.0,
1871 w: 0.0,
1872 z: 0.0,
1873 alpha: 0.1,
1874 beta: 0.5,
1875 eps1: 0.1,
1876 eps2: 0.001,
1877 gamma: 0.5,
1878 dt: 0.1,
1879 v_threshold: 0.5,
1880 }
1881 }
1882 fn is_valid(&self) -> bool {
1883 self.v.is_finite()
1884 && self.w.is_finite()
1885 && self.z.is_finite()
1886 && self.alpha.is_finite()
1887 && self.beta.is_finite()
1888 && self.eps1.is_finite()
1889 && self.eps1 > 0.0
1890 && self.eps2.is_finite()
1891 && self.eps2 > 0.0
1892 && self.gamma.is_finite()
1893 && self.gamma > 0.0
1894 && self.dt.is_finite()
1895 && self.dt > 0.0
1896 && self.v_threshold.is_finite()
1897 }
1898 fn derivatives(&self, v: f64, w: f64, z: f64, current: f64) -> Option<(f64, f64, f64)> {
1899 if !(v.is_finite() && w.is_finite() && z.is_finite() && current.is_finite()) {
1900 return None;
1901 }
1902 let dv = v - v.powi(3) / 3.0 - w - z + current;
1903 let dw = self.eps1 * (v - self.gamma * w + self.alpha);
1904 let dz = self.eps2 * (self.beta * (v + 0.7) - z);
1905 if dv.is_finite() && dw.is_finite() && dz.is_finite() {
1906 Some((dv, dw, dz))
1907 } else {
1908 None
1909 }
1910 }
1911 fn rk4_candidate(&self, current: f64) -> Option<(f64, f64, f64)> {
1912 let dt = self.dt;
1913 let (k1v, k1w, k1z) = self.derivatives(self.v, self.w, self.z, current)?;
1914 let (k2v, k2w, k2z) = self.derivatives(
1915 self.v + 0.5 * dt * k1v,
1916 self.w + 0.5 * dt * k1w,
1917 self.z + 0.5 * dt * k1z,
1918 current,
1919 )?;
1920 let (k3v, k3w, k3z) = self.derivatives(
1921 self.v + 0.5 * dt * k2v,
1922 self.w + 0.5 * dt * k2w,
1923 self.z + 0.5 * dt * k2z,
1924 current,
1925 )?;
1926 let (k4v, k4w, k4z) = self.derivatives(
1927 self.v + dt * k3v,
1928 self.w + dt * k3w,
1929 self.z + dt * k3z,
1930 current,
1931 )?;
1932 let v = self.v + dt * (k1v + 2.0 * k2v + 2.0 * k3v + k4v) / 6.0;
1933 let w = self.w + dt * (k1w + 2.0 * k2w + 2.0 * k3w + k4w) / 6.0;
1934 let z = self.z + dt * (k1z + 2.0 * k2z + 2.0 * k3z + k4z) / 6.0;
1935 if v.is_finite() && w.is_finite() && z.is_finite() {
1936 Some((v, w, z))
1937 } else {
1938 None
1939 }
1940 }
1941 pub fn step(&mut self, current: f64) -> i32 {
1942 if !self.is_valid() || !current.is_finite() {
1943 return 0;
1944 }
1945 let v_prev = self.v;
1946 let Some((v, w, z)) = self.rk4_candidate(current) else {
1947 return 0;
1948 };
1949 self.v = v;
1950 self.w = w;
1951 self.z = z;
1952 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1953 1
1954 } else {
1955 0
1956 }
1957 }
1958 pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
1965 let mut trace = Vec::with_capacity(n_steps);
1966 let mut spikes: i64 = 0;
1967 for _ in 0..n_steps {
1968 let spiked = self.step(current);
1969 trace.push(self.v);
1970 spikes += spiked as i64;
1971 }
1972 (trace, spikes)
1973 }
1974 pub fn reset(&mut self) {
1975 self.v = -1.0;
1976 self.w = 0.0;
1977 self.z = 0.0;
1978 }
1979}
1980impl Default for PernarowskiNeuron {
1981 fn default() -> Self {
1982 Self::new()
1983 }
1984}
1985
1986#[derive(Clone, Debug)]
2003pub struct BrunelWangNeuron {
2004 pub v: f64,
2005 pub v_rest: f64,
2006 pub v_reset: f64,
2007 pub v_threshold: f64,
2008 pub tau_m: f64,
2009 pub tau_ref: f64,
2010 pub g_ampa_ext: f64,
2011 pub g_ampa_rec: f64,
2012 pub g_nmda: f64,
2013 pub g_gaba: f64,
2014 pub v_ampa: f64,
2015 pub v_nmda: f64,
2016 pub v_gaba: f64,
2017 pub c_m: f64,
2018 pub mg_conc: f64,
2019 pub dt: f64,
2020 pub ref_remaining: f64,
2021 pub gain: f64,
2022}
2023
2024impl BrunelWangNeuron {
2025 pub fn new() -> Self {
2026 Self {
2027 v: -70.0,
2028 v_rest: -70.0,
2029 v_reset: -55.0,
2030 v_threshold: -50.0,
2031 tau_m: 20.0,
2032 tau_ref: 2.0,
2033 g_ampa_ext: 2.1,
2034 g_ampa_rec: 0.05,
2035 g_nmda: 0.165,
2036 g_gaba: 1.3,
2037 v_ampa: 0.0,
2038 v_nmda: 0.0,
2039 v_gaba: -70.0,
2040 c_m: 0.5,
2041 mg_conc: 1.0,
2042 dt: 0.1,
2043 ref_remaining: 0.0,
2044 gain: 1.0,
2045 }
2046 }
2047
2048 #[inline]
2050 fn nmda_mg_block(&self, v: f64) -> f64 {
2051 1.0 / (1.0 + self.mg_conc / 3.57 * (-0.062 * v).exp())
2052 }
2053
2054 pub fn step_full(
2056 &mut self,
2057 i_ampa_ext: f64,
2058 s_ampa_rec: f64,
2059 s_nmda_rec: f64,
2060 s_gaba: f64,
2061 ) -> i32 {
2062 if self.ref_remaining > 0.0 {
2063 self.ref_remaining -= self.dt;
2064 return 0;
2065 }
2066
2067 let v = self.v;
2068
2069 let i_ampa = -self.g_ampa_ext * (v - self.v_ampa) * i_ampa_ext
2071 - self.g_ampa_rec * (v - self.v_ampa) * s_ampa_rec;
2072 let i_nmda = -self.g_nmda * self.nmda_mg_block(v) * (v - self.v_nmda) * s_nmda_rec;
2073 let i_gaba = -self.g_gaba * (v - self.v_gaba) * s_gaba;
2074
2075 let i_leak = -(v - self.v_rest) / self.tau_m;
2077 let dv = (i_leak + (i_ampa + i_nmda + i_gaba) / self.c_m) * self.dt;
2078 self.v += dv;
2079
2080 if self.v >= self.v_threshold {
2081 self.v = self.v_reset;
2082 self.ref_remaining = self.tau_ref;
2083 1
2084 } else {
2085 0
2086 }
2087 }
2088
2089 pub fn step(&mut self, current: f64) -> i32 {
2091 self.step_full(self.gain * current, 0.0, 0.0, 0.0)
2092 }
2093
2094 pub fn reset(&mut self) {
2095 self.v = self.v_rest;
2096 self.ref_remaining = 0.0;
2097 }
2098}
2099
2100impl Default for BrunelWangNeuron {
2101 fn default() -> Self {
2102 Self::new()
2103 }
2104}
2105
2106#[cfg(test)]
2107mod tests {
2108 use super::*;
2109
2110 #[test]
2111 fn fhn_fires() {
2112 let mut n = FitzHughNagumoNeuron::new();
2113 let t: i32 = (0..2000).map(|_| n.step(1.0)).sum();
2114 assert!(t > 0);
2115 }
2116 #[test]
2117 fn morris_lecar_fires() {
2118 let mut n = MorrisLecarNeuron::new();
2119 let t: i32 = (0..2000).map(|_| n.step(100.0)).sum();
2120 assert!(t > 0);
2121 }
2122 #[test]
2123 fn hr_fires() {
2124 let mut n = HindmarshRoseNeuron::new();
2125 let t: i32 = (0..2000).map(|_| n.step(3.0)).sum();
2126 assert!(t > 0);
2127 }
2128 #[test]
2129 fn rnf_fires() {
2130 let mut n = ResonateAndFireNeuron::new();
2131 let t: i32 = (0..5000).map(|_| n.step(3.0)).sum();
2132 assert!(t > 0);
2133 }
2134 #[test]
2135 fn fhr_fires() {
2136 let mut n = FitzHughRinzelNeuron::new();
2137 let t: i32 = (0..2000).map(|_| n.step(1.0)).sum();
2138 assert!(t > 0);
2139 }
2140 #[test]
2141 fn mckean_fires() {
2142 let mut n = McKeanNeuron::new();
2143 let t: i32 = (0..2000).map(|_| n.step(0.5)).sum();
2144 assert!(t > 0);
2145 }
2146 #[test]
2147 fn tw_fires() {
2148 let mut n = TermanWangOscillator::new();
2149 let t: i32 = (0..2000).map(|_| n.step(0.5)).sum();
2150 assert!(t > 0);
2151 }
2152 #[test]
2153 fn benda_herz_fires() {
2154 let mut n = BendaHerzNeuron::new(42);
2155 let t: i32 = (0..10000).map(|_| n.step(20.0)).sum();
2156 assert!(t > 0);
2157 }
2158 #[test]
2159 fn alpha_fires() {
2160 let mut n = AlphaNeuron::new();
2161 let t: i32 = (0..100).map(|_| n.step(0.5, 0.0)).sum();
2162 assert!(t > 0);
2163 }
2164 #[test]
2165 fn coba_fires() {
2166 let mut n = COBALIFNeuron::new();
2167 let t: i32 = (0..2000).map(|_| n.step(500.0, 0.0, 0.0)).sum();
2168 assert!(t > 0);
2169 }
2170 #[test]
2171 fn gutkin_fires() {
2172 let mut n = GutkinErmentroutNeuron::new();
2173 let t: i32 = (0..2000).map(|_| n.step(15.0)).sum();
2174 assert!(t > 0);
2175 }
2176 #[test]
2177 fn wilson_hr_fires() {
2178 let mut n = WilsonHRNeuron::new();
2179 let t: i32 = (0..2000).map(|_| n.step(10.0)).sum();
2180 assert!(t > 0);
2181 }
2182 #[test]
2183 fn chay_drive_changes_state_without_leaving_physical_bounds() {
2184 let mut rest = ChayNeuron::new();
2185 let mut driven = ChayNeuron::new();
2186 for _ in 0..500 {
2187 rest.step(0.0);
2188 driven.step(5.0);
2189 }
2190 assert!(driven.v > rest.v);
2191 assert!((0.0..=1.0).contains(&driven.n));
2192 assert!(driven.ca >= 0.0);
2193 }
2194 #[test]
2195 fn chay_keizer_fires() {
2196 let mut n = ChayKeizerNeuron::new();
2197 let t: i32 = (0..5000).map(|_| n.step(10.0)).sum();
2198 assert!(t > 0);
2199 }
2200 #[test]
2201 fn srk_fires() {
2202 let mut n = ShermanRinzelKeizerNeuron::new();
2203 let t: i32 = (0..5000).map(|_| n.step(5.0)).sum();
2204 assert!(t > 0);
2205 }
2206 #[test]
2207 fn butera_fires() {
2208 let mut n = ButeraRespiratoryNeuron::new();
2209 let t: i32 = (0..20000).map(|_| n.step(50.0)).sum();
2210 assert!(t > 0);
2211 }
2212 #[test]
2213 fn eprop_fires() {
2214 let mut n = EPropALIFNeuron::default();
2215 let t: i32 = (0..50).map(|_| n.step(0.5)).sum();
2216 assert!(t > 0);
2217 }
2218 #[test]
2219 fn superspike_fires() {
2220 let mut n = SuperSpikeNeuron::default();
2221 let t: i32 = (0..50).map(|_| n.step(0.5)).sum();
2222 assert!(t > 0);
2223 }
2224 #[test]
2225 fn lnm_fires() {
2226 let mut n = LearnableNeuronModel::new();
2227 let t: i32 = (0..50).map(|_| n.step(2.0)).sum();
2228 assert!(t > 0);
2229 }
2230 #[test]
2231 fn pernarowski_fires() {
2232 let mut n = PernarowskiNeuron::new();
2233 let t: i32 = (0..2000).map(|_| n.step(1.0)).sum();
2234 assert!(t > 0);
2235 }
2236
2237 #[test]
2241 fn fhn_silent_without_input() {
2242 let mut n = FitzHughNagumoNeuron::new();
2243 let t: i32 = (0..2000).map(|_| n.step(0.0)).sum();
2244 assert_eq!(t, 0);
2245 }
2246 #[test]
2247 fn fhn_reset_clears_state() {
2248 let mut n = FitzHughNagumoNeuron::new();
2249 for _ in 0..500 {
2250 n.step(1.0);
2251 }
2252 n.reset();
2253 assert!((n.v - (-1.0)).abs() < 1e-10);
2254 assert!((n.w - (-0.5)).abs() < 1e-10);
2255 }
2256 #[test]
2257 fn fhn_moderate_input_stable() {
2258 let mut n = FitzHughNagumoNeuron::new();
2259 for _ in 0..2000 {
2260 n.step(2.0);
2261 }
2262 assert!(n.v.is_finite());
2263 }
2264 #[test]
2265 fn fhn_recovery_variable() {
2266 let mut n = FitzHughNagumoNeuron::new();
2267 for _ in 0..2000 {
2268 n.step(1.0);
2269 }
2270 assert!((n.w - (-0.5)).abs() > 0.01, "recovery w should change");
2272 }
2273 #[test]
2274 fn fhn_invalid_input_preserves_state() {
2275 let mut n = FitzHughNagumoNeuron::new();
2276 let before = n.clone();
2277 assert_eq!(n.step(f64::NAN), -1);
2278 assert_eq!(n.v, before.v);
2279 assert_eq!(n.w, before.w);
2280 }
2281 #[test]
2282 fn fhn_overflow_candidate_preserves_state() {
2283 let mut n = FitzHughNagumoNeuron::new();
2284 n.v = 1.0e103;
2285 let before = n.clone();
2286 assert_eq!(n.step(0.0), -1);
2287 assert_eq!(n.v, before.v);
2288 assert_eq!(n.w, before.w);
2289 }
2290 #[test]
2291 fn fhn_negative_no_crash() {
2292 let mut n = FitzHughNagumoNeuron::new();
2293 for _ in 0..500 {
2294 n.step(-5.0);
2295 }
2296 assert!(n.v.is_finite());
2297 }
2298
2299 #[test]
2301 fn ml_silent_without_input() {
2302 let mut n = MorrisLecarNeuron::new();
2303 let t: i32 = (0..500).map(|_| n.step(0.0)).sum();
2304 assert_eq!(t, 0);
2305 }
2306 #[test]
2307 fn ml_reset_clears_state() {
2308 let mut n = MorrisLecarNeuron::new();
2309 for _ in 0..500 {
2310 n.step(100.0);
2311 }
2312 n.reset();
2313 assert!((n.v - (-60.0)).abs() < 1e-10);
2314 }
2315 #[test]
2316 fn ml_moderate_input_stable() {
2317 let mut n = MorrisLecarNeuron::new();
2318 for _ in 0..2000 {
2319 n.step(200.0);
2320 }
2321 assert!(n.v.is_finite());
2322 }
2323 #[test]
2324 fn ml_rk4_separates_from_forward_euler() {
2325 let mut n = MorrisLecarNeuron::new();
2326 let v0 = n.v;
2327 let w0 = n.w;
2328 let current = 50.0;
2329 let m_inf = 0.5 * (1.0 + ((v0 - n.v1) / n.v2).tanh());
2330 let w_inf = 0.5 * (1.0 + ((v0 - n.v3) / n.v4).tanh());
2331 let lam = n.phi * ((v0 - n.v3) / (2.0 * n.v4)).cosh();
2332 let i_ca = n.g_ca * m_inf * (v0 - n.e_ca);
2333 let i_k = n.g_k * w0 * (v0 - n.e_k);
2334 let i_l = n.g_l * (v0 - n.e_l);
2335 let euler_v = v0 + (-i_ca - i_k - i_l + current) / n.c_m * n.dt;
2336 let euler_w = w0 + lam * (w_inf - w0) * n.dt;
2337
2338 assert_eq!(n.step(current), 0);
2339
2340 assert!((n.v - euler_v).abs() > 1e-6);
2341 assert!((n.w - euler_w).abs() > 1e-8);
2342 assert!(n.v.is_finite());
2343 assert!((0.0..=1.0).contains(&n.w));
2344 }
2345 #[test]
2346 fn ml_nan_no_panic() {
2347 let mut n = MorrisLecarNeuron::new();
2348 let before = (n.v, n.w);
2349 assert_eq!(n.step(f64::NAN), 0);
2350 assert_eq!((n.v, n.w), before);
2351 }
2352 #[test]
2353 fn ml_overflow_candidate_preserves_state() {
2354 let mut n = MorrisLecarNeuron {
2355 v: 1.0e6,
2356 w: 0.25,
2357 ..Default::default()
2358 };
2359 let before = (n.v, n.w);
2360 assert_eq!(n.step(0.0), 0);
2361 assert_eq!((n.v, n.w), before);
2362 }
2363 #[test]
2364 fn ml_negative_no_crash() {
2365 let mut n = MorrisLecarNeuron::new();
2366 for _ in 0..500 {
2367 n.step(-50.0);
2368 }
2369 assert!(n.v.is_finite());
2370 }
2371 #[test]
2372 fn ml_k_gating_bounded() {
2373 let mut n = MorrisLecarNeuron::new();
2374 for _ in 0..2000 {
2375 n.step(100.0);
2376 }
2377 assert!(n.w >= 0.0 && n.w <= 1.0, "w={}", n.w);
2378 }
2379
2380 #[test]
2382 fn hr_reset_clears_state() {
2383 let mut n = HindmarshRoseNeuron::new();
2384 for _ in 0..500 {
2385 n.step(3.0);
2386 }
2387 n.reset();
2388 assert!((n.x - (-1.6)).abs() < 1e-10);
2389 }
2390 #[test]
2391 fn hr_moderate_input_stable() {
2392 let mut n = HindmarshRoseNeuron::new();
2393 for _ in 0..2000 {
2394 n.step(5.0);
2395 }
2396 assert!(n.x.is_finite());
2397 }
2398 #[test]
2399 fn hr_slow_z_evolves() {
2400 let mut n = HindmarshRoseNeuron::new();
2401 let z0 = n.z;
2402 for _ in 0..2000 {
2403 n.step(3.0);
2404 }
2405 assert!((n.z - z0).abs() > 0.001, "slow variable z should evolve");
2406 }
2407 #[test]
2408 fn hr_nan_no_panic() {
2409 HindmarshRoseNeuron::new().step(f64::NAN);
2410 }
2411 #[test]
2412 fn hr_negative_no_crash() {
2413 let mut n = HindmarshRoseNeuron::new();
2414 for _ in 0..500 {
2415 n.step(-1.0);
2416 }
2417 assert!(n.x.is_finite());
2418 }
2419
2420 #[test]
2422 fn rnf_reset_clears_state() {
2423 let mut n = ResonateAndFireNeuron::new();
2424 for _ in 0..500 {
2425 n.step(3.0);
2426 }
2427 n.reset();
2428 assert!((n.x - 0.0).abs() < 1e-10);
2429 }
2430 #[test]
2431 fn rnf_bounded() {
2432 let mut n = ResonateAndFireNeuron::new();
2433 for _ in 0..5000 {
2434 n.step(100.0);
2435 }
2436 assert!(n.x.is_finite());
2437 }
2438 #[test]
2439 fn rnf_nan_no_panic() {
2440 ResonateAndFireNeuron::new().step(f64::NAN);
2441 }
2442 #[test]
2443 fn rnf_negative_no_crash() {
2444 let mut n = ResonateAndFireNeuron::new();
2445 for _ in 0..500 {
2446 n.step(-5.0);
2447 }
2448 assert!(n.x.is_finite());
2449 }
2450 #[test]
2451 fn rnf_subthreshold_oscillation() {
2452 let mut n = ResonateAndFireNeuron::new();
2453 for _ in 0..100 {
2454 n.step(0.5);
2455 }
2456 assert!(n.x.abs() > 0.0 || n.y.abs() > 0.0);
2457 }
2458
2459 #[test]
2460 fn brf_boundary_matches_algorithm() {
2461 let p = brf_sustain_oscillation_boundary(10.0, 0.01);
2462 let expected = (-1.0 + (1.0_f64 - 0.1_f64 * 0.1_f64).sqrt()) / 0.01;
2463 assert!((p - expected).abs() < 1e-12);
2464 }
2465
2466 #[test]
2467 fn brf_step_matches_algorithm_one_step() {
2468 let mut n = BalancedResonateAndFireNeuron {
2469 x: 0.2,
2470 y: -0.1,
2471 q: 0.3,
2472 omega: 12.0,
2473 b_offset: 0.75,
2474 threshold: 1.0,
2475 gamma: 0.9,
2476 dt: 0.01,
2477 };
2478 let p_omega = brf_sustain_oscillation_boundary(12.0, 0.01);
2479 let b_t = p_omega - 0.75 - 0.3;
2480 let expected_x = 0.2 + 0.01 * (b_t * 0.2 - 12.0 * -0.1 + 2.0);
2481 let expected_y = -0.1 + 0.01 * (12.0 * 0.2 + b_t * -0.1);
2482 let expected_spike = if expected_x >= 1.3 { 1 } else { 0 };
2483 let spike = n.step(2.0);
2484 assert_eq!(spike, expected_spike);
2485 assert!((n.x - expected_x).abs() < 1e-12);
2486 assert!((n.y - expected_y).abs() < 1e-12);
2487 assert!((n.q - (0.9 * 0.3 + expected_spike as f64)).abs() < 1e-12);
2488 }
2489
2490 #[test]
2491 fn brf_reset_clears_membrane_and_refractory_state() {
2492 let mut n = BalancedResonateAndFireNeuron::new();
2493 assert_eq!(n.step(200.0), 1);
2494 n.reset();
2495 assert_eq!(n.x, 0.0);
2496 assert_eq!(n.y, 0.0);
2497 assert_eq!(n.q, 0.0);
2498 }
2499
2500 #[test]
2502 fn fhr_reset_clears_state() {
2503 let mut n = FitzHughRinzelNeuron::new();
2504 for _ in 0..500 {
2505 n.step(1.0);
2506 }
2507 n.reset();
2508 assert!((n.v - (-1.0)).abs() < 1e-10);
2509 }
2510 #[test]
2511 fn fhr_bounded() {
2512 let mut n = FitzHughRinzelNeuron::new();
2513 for _ in 0..2000 {
2514 n.step(50.0);
2515 }
2516 assert!(n.v.is_finite());
2517 }
2518 #[test]
2519 fn fhr_invalid_input_preserves_state() {
2520 let mut n = FitzHughRinzelNeuron::new();
2521 let before = (n.v, n.w, n.y);
2522 assert_eq!(n.step(f64::NAN), 0);
2523 assert_eq!((n.v, n.w, n.y), before);
2524 }
2525 #[test]
2526 fn fhr_matches_rk4_candidate() {
2527 let mut n = FitzHughRinzelNeuron::new();
2528 n.v = -1.0;
2529 n.w = 0.2;
2530 n.y = 0.1;
2531 let expected = n.rk4_candidate(0.5).unwrap();
2532 assert_eq!(n.step(0.5), 0);
2533 assert!((n.v - expected.0).abs() < 1.0e-15);
2534 assert!((n.w - expected.1).abs() < 1.0e-15);
2535 assert!((n.y - expected.2).abs() < 1.0e-15);
2536 }
2537 #[test]
2538 fn fhr_overflow_candidate_preserves_state() {
2539 let mut n = FitzHughRinzelNeuron {
2540 v: 1.0e155,
2541 ..Default::default()
2542 };
2543 let before = (n.v, n.w, n.y);
2544 assert_eq!(n.step(0.5), 0);
2545 assert_eq!((n.v, n.w, n.y), before);
2546 }
2547 #[test]
2548 fn fhr_negative_no_crash() {
2549 let mut n = FitzHughRinzelNeuron::new();
2550 for _ in 0..500 {
2551 n.step(-5.0);
2552 }
2553 assert!(n.v.is_finite());
2554 }
2555 #[test]
2556 fn fhr_slow_y_variable() {
2557 let mut n = FitzHughRinzelNeuron::new();
2558 let y0 = n.y;
2559 for _ in 0..2000 {
2560 n.step(1.0);
2561 }
2562 assert!((n.y - y0).abs() > 1e-6, "slow y should evolve in 3D model");
2563 }
2564
2565 #[test]
2567 fn mckean_reset_clears_state() {
2568 let mut n = McKeanNeuron::new();
2569 for _ in 0..500 {
2570 n.step(0.5);
2571 }
2572 n.reset();
2573 assert!((n.v - 0.0).abs() < 1e-10);
2574 }
2575 #[test]
2576 fn mckean_bounded() {
2577 let mut n = McKeanNeuron::new();
2578 for _ in 0..2000 {
2579 n.step(50.0);
2580 }
2581 assert!(n.v.is_finite());
2582 }
2583 #[test]
2584 fn mckean_matches_rk4_candidate() {
2585 fn f(v: f64, a: f64) -> f64 {
2586 let half_a = a / 2.0;
2587 let mid = (1.0 + a) / 2.0;
2588 if v < half_a {
2589 -v
2590 } else if v < mid {
2591 v - a
2592 } else {
2593 1.0 - v
2594 }
2595 }
2596 fn rhs(n: &McKeanNeuron, v: f64, w: f64, current: f64) -> (f64, f64) {
2597 (f(v, n.a) - w + current, n.epsilon * (v - n.gamma * w))
2598 }
2599
2600 let mut n = McKeanNeuron {
2601 v: 0.2,
2602 w: -0.1,
2603 ..Default::default()
2604 };
2605 let current = 0.5;
2606 let v0 = n.v;
2607 let w0 = n.w;
2608 let dt = n.dt;
2609 let k1 = rhs(&n, v0, w0, current);
2610 let k2 = rhs(&n, v0 + 0.5 * dt * k1.0, w0 + 0.5 * dt * k1.1, current);
2611 let k3 = rhs(&n, v0 + 0.5 * dt * k2.0, w0 + 0.5 * dt * k2.1, current);
2612 let k4 = rhs(&n, v0 + dt * k3.0, w0 + dt * k3.1, current);
2613 let expected_v = v0 + dt * (k1.0 + 2.0 * k2.0 + 2.0 * k3.0 + k4.0) / 6.0;
2614 let expected_w = w0 + dt * (k1.1 + 2.0 * k2.1 + 2.0 * k3.1 + k4.1) / 6.0;
2615
2616 assert_eq!(n.step(current), 0);
2617 assert!((n.v - expected_v).abs() < 1e-14);
2618 assert!((n.w - expected_w).abs() < 1e-14);
2619 }
2620 #[test]
2621 fn mckean_nan_no_panic() {
2622 let mut n = McKeanNeuron::new();
2623 let before = (n.v, n.w);
2624 assert_eq!(n.step(f64::NAN), 0);
2625 assert_eq!((n.v, n.w), before);
2626 }
2627 #[test]
2628 fn mckean_overflow_candidate_preserves_state() {
2629 let mut n = McKeanNeuron {
2630 v: 1.0e308,
2631 w: -1.7e308,
2632 ..Default::default()
2633 };
2634 let before = (n.v, n.w);
2635 assert_eq!(n.step(1.7e308), 0);
2636 assert_eq!((n.v, n.w), before);
2637 }
2638 #[test]
2639 fn mckean_negative_no_crash() {
2640 let mut n = McKeanNeuron::new();
2641 for _ in 0..500 {
2642 n.step(-5.0);
2643 }
2644 assert!(n.v.is_finite());
2645 }
2646
2647 #[test]
2649 fn tw_reset_clears_state() {
2650 let mut n = TermanWangOscillator::new();
2651 for _ in 0..500 {
2652 n.step(0.5);
2653 }
2654 n.reset();
2655 assert!((n.v - (-1.5)).abs() < 1e-10);
2656 }
2657 #[test]
2658 fn tw_bounded() {
2659 let mut n = TermanWangOscillator::new();
2660 for _ in 0..2000 {
2661 n.step(50.0);
2662 }
2663 assert!(n.v.is_finite());
2664 }
2665 #[test]
2666 fn tw_matches_rk4_candidate() {
2667 let mut n = TermanWangOscillator::new();
2668 n.v = -1.2;
2669 n.w = -0.25;
2670 let current = 1.0;
2671 let dt = n.dt;
2672 let rhs = |v: f64, w: f64| {
2673 let f = 3.0 * v - v.powi(3) + 2.0;
2674 let g = n.alpha * (1.0 + (v / n.beta).tanh());
2675 (f - w + current + n.rho, n.epsilon * (g - w))
2676 };
2677 let (k1v, k1w) = rhs(n.v, n.w);
2678 let (k2v, k2w) = rhs(n.v + 0.5 * dt * k1v, n.w + 0.5 * dt * k1w);
2679 let (k3v, k3w) = rhs(n.v + 0.5 * dt * k2v, n.w + 0.5 * dt * k2w);
2680 let (k4v, k4w) = rhs(n.v + dt * k3v, n.w + dt * k3w);
2681 let expected = (
2682 n.v + dt * (k1v + 2.0 * k2v + 2.0 * k3v + k4v) / 6.0,
2683 n.w + dt * (k1w + 2.0 * k2w + 2.0 * k3w + k4w) / 6.0,
2684 );
2685
2686 assert_eq!(n.step(current), 0);
2687 assert!((n.v - expected.0).abs() < 1e-14);
2688 assert!((n.w - expected.1).abs() < 1e-14);
2689 }
2690 #[test]
2691 fn tw_nan_no_panic() {
2692 let mut n = TermanWangOscillator::new();
2693 let before = (n.v, n.w);
2694 assert_eq!(n.step(f64::NAN), 0);
2695 assert_eq!((n.v, n.w), before);
2696 }
2697 #[test]
2698 fn tw_overflow_candidate_preserves_state() {
2699 let mut n = TermanWangOscillator {
2700 v: 1.0e308,
2701 ..Default::default()
2702 };
2703 let before = (n.v, n.w);
2704 assert_eq!(n.step(1.0), 0);
2705 assert_eq!((n.v, n.w), before);
2706 }
2707 #[test]
2708 fn tw_negative_no_crash() {
2709 let mut n = TermanWangOscillator::new();
2710 for _ in 0..500 {
2711 n.step(-5.0);
2712 }
2713 assert!(n.v.is_finite());
2714 }
2715
2716 #[test]
2718 fn benda_herz_reset_clears_state() {
2719 let mut n = BendaHerzNeuron::new(42);
2720 for _ in 0..100 {
2721 n.step(20.0);
2722 }
2723 n.reset();
2724 assert!((n.a - 0.0).abs() < 1e-10);
2725 }
2726 #[test]
2727 fn benda_herz_bounded() {
2728 let mut n = BendaHerzNeuron::new(42);
2729 for _ in 0..10000 {
2730 n.step(1e4);
2731 }
2732 assert!(n.a.is_finite());
2733 }
2734 #[test]
2735 fn benda_herz_adaptation() {
2736 let mut n = BendaHerzNeuron::new(42);
2737 for _ in 0..10000 {
2738 n.step(20.0);
2739 }
2740 assert!(n.a > 0.0, "adaptation variable a should increase: {}", n.a);
2741 }
2742 #[test]
2743 fn benda_herz_nan_no_panic() {
2744 BendaHerzNeuron::new(42).step(f64::NAN);
2745 }
2746 #[test]
2747 fn benda_herz_negative_no_crash() {
2748 let mut n = BendaHerzNeuron::new(42);
2749 for _ in 0..500 {
2750 n.step(-10.0);
2751 }
2752 assert!(n.a.is_finite());
2753 }
2754
2755 #[test]
2757 fn alpha_reset_clears_state() {
2758 let mut n = AlphaNeuron::new();
2759 for _ in 0..50 {
2760 n.step(0.5, 0.0);
2761 }
2762 n.reset();
2763 assert!((n.v - 0.0).abs() < 1e-10);
2764 }
2765 #[test]
2766 fn alpha_bounded() {
2767 let mut n = AlphaNeuron::new();
2768 for _ in 0..1000 {
2769 n.step(100.0, 0.0);
2770 }
2771 assert!(n.v.is_finite());
2772 }
2773 #[test]
2774 fn alpha_spike_input_drives() {
2775 let mut n = AlphaNeuron::new();
2776 for _ in 0..100 {
2777 n.step(0.0, 1.0);
2778 }
2779 assert!(n.v.is_finite());
2781 }
2782 #[test]
2783 fn alpha_nan_no_panic() {
2784 AlphaNeuron::new().step(f64::NAN, 0.0);
2785 }
2786 #[test]
2787 fn alpha_negative_no_crash() {
2788 let mut n = AlphaNeuron::new();
2789 for _ in 0..100 {
2790 n.step(-5.0, 0.0);
2791 }
2792 assert!(n.v.is_finite());
2793 }
2794
2795 #[test]
2797 fn coba_reset_clears_state() {
2798 let mut n = COBALIFNeuron::new();
2799 for _ in 0..100 {
2800 n.step(500.0, 0.0, 0.0);
2801 }
2802 n.reset();
2803 assert!((n.v - n.e_l).abs() < 1e-10);
2804 }
2805 #[test]
2806 fn coba_bounded() {
2807 let mut n = COBALIFNeuron::new();
2808 for _ in 0..2000 {
2809 n.step(1e5, 0.0, 0.0);
2810 }
2811 assert!(n.v.is_finite());
2812 }
2813 #[test]
2814 fn coba_inhibition_suppresses() {
2815 let mut n_exc = COBALIFNeuron::new();
2816 let mut n_inh = COBALIFNeuron::new();
2817 let t_exc: i32 = (0..2000).map(|_| n_exc.step(500.0, 0.0, 0.0)).sum();
2818 let t_inh: i32 = (0..2000).map(|_| n_inh.step(500.0, 0.0, 5.0)).sum();
2819 assert!(t_inh <= t_exc, "inhibition should reduce spiking");
2820 }
2821 #[test]
2822 fn coba_nan_no_panic() {
2823 COBALIFNeuron::new().step(f64::NAN, 0.0, 0.0);
2824 }
2825 #[test]
2826 fn coba_negative_no_crash() {
2827 let mut n = COBALIFNeuron::new();
2828 for _ in 0..500 {
2829 n.step(-100.0, 0.0, 0.0);
2830 }
2831 assert!(n.v.is_finite());
2832 }
2833
2834 #[test]
2836 fn gutkin_reset_clears_state() {
2837 let mut n = GutkinErmentroutNeuron::new();
2838 for _ in 0..500 {
2839 n.step(15.0);
2840 }
2841 n.reset();
2842 assert!((n.v - (-65.0)).abs() < 1e-10);
2843 }
2844 #[test]
2845 fn gutkin_bounded() {
2846 let mut n = GutkinErmentroutNeuron::new();
2847 for _ in 0..2000 {
2848 n.step(1e4);
2849 }
2850 assert!(n.v.is_finite());
2851 }
2852 #[test]
2853 fn gutkin_nan_no_panic() {
2854 GutkinErmentroutNeuron::new().step(f64::NAN);
2855 }
2856 #[test]
2857 fn gutkin_matches_rk4_candidate() {
2858 fn rhs(n: &GutkinErmentroutNeuron, v: f64, n_gate: f64, current: f64) -> (f64, f64) {
2859 let m_inf = 1.0 / (1.0 + (-(v + 20.0) / 15.0).exp());
2860 let n_inf = 1.0 / (1.0 + (-(v + 25.0) / 5.0).exp());
2861 let i_na = n.g_na * m_inf * (v - n.e_na);
2862 let i_k = n.g_k * n_gate * (v - n.e_k);
2863 let i_l = n.g_l * (v - n.e_l);
2864 (-i_na - i_k - i_l + current, n_inf - n_gate)
2865 }
2866
2867 let mut n = GutkinErmentroutNeuron::new();
2868 let current = 5.0;
2869 let v0 = n.v;
2870 let n0 = n.n;
2871 let dt = n.dt;
2872 let (k1_v, k1_n) = rhs(&n, v0, n0, current);
2873 let (k2_v, k2_n) = rhs(&n, v0 + 0.5 * dt * k1_v, n0 + 0.5 * dt * k1_n, current);
2874 let (k3_v, k3_n) = rhs(&n, v0 + 0.5 * dt * k2_v, n0 + 0.5 * dt * k2_n, current);
2875 let (k4_v, k4_n) = rhs(&n, v0 + dt * k3_v, n0 + dt * k3_n, current);
2876 let expected_v = v0 + dt * (k1_v + 2.0 * k2_v + 2.0 * k3_v + k4_v) / 6.0;
2877 let expected_n = n0 + dt * (k1_n + 2.0 * k2_n + 2.0 * k3_n + k4_n) / 6.0;
2878
2879 n.step(current);
2880
2881 assert!((n.v - expected_v).abs() < 1e-12);
2882 assert!((n.n - expected_n).abs() < 1e-12);
2883 }
2884 #[test]
2885 fn gutkin_invalid_candidate_preserves_state() {
2886 let mut n = GutkinErmentroutNeuron {
2887 dt: 100.0,
2888 ..Default::default()
2889 };
2890 let v0 = n.v;
2891 let n0 = n.n;
2892 assert_eq!(n.step(1.0e9), 0);
2893 assert_eq!(n.v, v0);
2894 assert_eq!(n.n, n0);
2895 }
2896 #[test]
2897 fn gutkin_negative_no_crash() {
2898 let mut n = GutkinErmentroutNeuron::new();
2899 for _ in 0..500 {
2900 n.step(-10.0);
2901 }
2902 assert!(n.v.is_finite());
2903 }
2904
2905 #[test]
2907 fn wilson_hr_reset_clears_state() {
2908 let mut n = WilsonHRNeuron::new();
2909 for _ in 0..500 {
2910 n.step(0.5);
2911 }
2912 n.reset();
2913 assert!((n.v - (-0.7)).abs() < 1e-10);
2914 }
2915 #[test]
2916 fn wilson_hr_moderate_stable() {
2917 let mut n = WilsonHRNeuron::new();
2918 for _ in 0..2000 {
2919 n.step(1.0);
2920 }
2921 assert!(n.v.is_finite());
2922 }
2923 #[test]
2924 fn wilson_hr_matches_rk4_candidate() {
2925 fn rhs(n: &WilsonHRNeuron, v: f64, r: f64, current: f64) -> (f64, f64) {
2926 (
2927 WilsonHRNeuron::poly(v) - 26.0 * r * (v + 0.92) + current,
2928 (-r + 1.35 * v + 1.03) / n.tau_r,
2929 )
2930 }
2931
2932 let mut n = WilsonHRNeuron {
2933 v: -0.4,
2934 r: 0.08,
2935 ..Default::default()
2936 };
2937 let current = 0.3;
2938 let v0 = n.v;
2939 let r0 = n.r;
2940 let dt = n.dt;
2941 let k1 = rhs(&n, v0, r0, current);
2942 let k2 = rhs(&n, v0 + 0.5 * dt * k1.0, r0 + 0.5 * dt * k1.1, current);
2943 let k3 = rhs(&n, v0 + 0.5 * dt * k2.0, r0 + 0.5 * dt * k2.1, current);
2944 let k4 = rhs(&n, v0 + dt * k3.0, r0 + dt * k3.1, current);
2945 let expected_v = v0 + dt * (k1.0 + 2.0 * k2.0 + 2.0 * k3.0 + k4.0) / 6.0;
2946 let expected_r = r0 + dt * (k1.1 + 2.0 * k2.1 + 2.0 * k3.1 + k4.1) / 6.0;
2947
2948 assert_eq!(n.step(current), 0);
2949 assert!((n.v - expected_v).abs() < 1e-14);
2950 assert!((n.r - expected_r).abs() < 1e-14);
2951 }
2952 #[test]
2953 fn wilson_hr_nan_no_panic() {
2954 let mut n = WilsonHRNeuron::new();
2955 let before = (n.v, n.r);
2956 assert_eq!(n.step(f64::NAN), 0);
2957 assert_eq!((n.v, n.r), before);
2958 }
2959 #[test]
2960 fn wilson_hr_overflow_candidate_preserves_state() {
2961 let mut n = WilsonHRNeuron {
2962 v: 1.0e308,
2963 ..Default::default()
2964 };
2965 let before = (n.v, n.r);
2966 assert_eq!(n.step(0.3), 0);
2967 assert_eq!((n.v, n.r), before);
2968 }
2969 #[test]
2970 fn wilson_hr_negative_no_crash() {
2971 let mut n = WilsonHRNeuron::new();
2972 for _ in 0..500 {
2973 n.step(-5.0);
2974 }
2975 assert!(n.v.is_finite());
2976 }
2977
2978 #[test]
2980 fn chay_reset_clears_state() {
2981 let mut n = ChayNeuron::new();
2982 for _ in 0..1000 {
2983 n.step(20.0);
2984 }
2985 n.reset();
2986 assert!((n.v - (-50.0)).abs() < 1e-10);
2987 }
2988 #[test]
2989 fn chay_bounded() {
2990 let mut n = ChayNeuron::new();
2991 for _ in 0..5000 {
2992 n.step(200.0);
2993 }
2994 assert!(n.v.is_finite());
2995 }
2996 #[test]
2997 fn chay_ca_nonneg() {
2998 let mut n = ChayNeuron::new();
2999 for _ in 0..5000 {
3000 n.step(20.0);
3001 }
3002 assert!(n.ca >= 0.0, "Ca²⁺ must be non-negative");
3003 }
3004 #[test]
3005 fn chay_nan_no_panic() {
3006 ChayNeuron::new().step(f64::NAN);
3007 }
3008 #[test]
3009 fn chay_negative_no_crash() {
3010 let mut n = ChayNeuron::new();
3011 for _ in 0..500 {
3012 n.step(-10.0);
3013 }
3014 assert!(n.v.is_finite());
3015 }
3016
3017 #[test]
3019 fn chay_keizer_reset_clears_state() {
3020 let mut n = ChayKeizerNeuron::new();
3021 for _ in 0..1000 {
3022 n.step(10.0);
3023 }
3024 n.reset();
3025 assert!((n.v - (-50.0)).abs() < 1e-10);
3026 }
3027 #[test]
3028 fn chay_keizer_bounded() {
3029 let mut n = ChayKeizerNeuron::new();
3030 for _ in 0..5000 {
3031 n.step(100.0);
3032 }
3033 assert!(n.v.is_finite());
3034 }
3035 #[test]
3036 fn chay_keizer_nan_no_panic() {
3037 ChayKeizerNeuron::new().step(f64::NAN);
3038 }
3039 #[test]
3040 fn chay_keizer_negative_no_crash() {
3041 let mut n = ChayKeizerNeuron::new();
3042 for _ in 0..500 {
3043 n.step(-10.0);
3044 }
3045 assert!(n.v.is_finite());
3046 }
3047
3048 #[test]
3050 fn srk_reset_clears_state() {
3051 let mut n = ShermanRinzelKeizerNeuron::new();
3052 for _ in 0..1000 {
3053 n.step(5.0);
3054 }
3055 n.reset();
3056 assert!((n.v - (-50.0)).abs() < 1e-10);
3057 }
3058 #[test]
3059 fn srk_bounded() {
3060 let mut n = ShermanRinzelKeizerNeuron::new();
3061 for _ in 0..5000 {
3062 n.step(100.0);
3063 }
3064 assert!(n.v.is_finite());
3065 }
3066 #[test]
3067 fn srk_nan_no_panic() {
3068 ShermanRinzelKeizerNeuron::new().step(f64::NAN);
3069 }
3070 #[test]
3071 fn srk_negative_no_crash() {
3072 let mut n = ShermanRinzelKeizerNeuron::new();
3073 for _ in 0..500 {
3074 n.step(-5.0);
3075 }
3076 assert!(n.v.is_finite());
3077 }
3078
3079 #[test]
3081 fn butera_reset_clears_state() {
3082 let mut n = ButeraRespiratoryNeuron::new();
3083 for _ in 0..1000 {
3084 n.step(50.0);
3085 }
3086 n.reset();
3087 assert!((n.v - (-50.0)).abs() < 1e-10);
3088 }
3089 #[test]
3090 fn butera_bounded() {
3091 let mut n = ButeraRespiratoryNeuron::new();
3092 for _ in 0..5000 {
3093 n.step(500.0);
3094 }
3095 assert!(n.v.is_finite());
3096 }
3097 #[test]
3098 fn butera_nan_no_panic() {
3099 ButeraRespiratoryNeuron::new().step(f64::NAN);
3100 }
3101 #[test]
3102 fn butera_nan_preserves_state() {
3103 let mut n = ButeraRespiratoryNeuron::new();
3104 let before = (n.v, n.n, n.h_nap);
3105 assert_eq!(n.step(f64::NAN), 0);
3106 assert_eq!((n.v, n.n, n.h_nap), before);
3107 }
3108 #[test]
3109 fn butera_negative_no_crash() {
3110 let mut n = ButeraRespiratoryNeuron::new();
3111 for _ in 0..500 {
3112 n.step(-20.0);
3113 }
3114 assert!(n.v.is_finite());
3115 }
3116
3117 #[test]
3119 fn eprop_reset_clears_state() {
3120 let mut n = EPropALIFNeuron::default();
3121 for _ in 0..50 {
3122 n.step(0.5);
3123 }
3124 n.reset();
3125 assert!((n.v - 0.0).abs() < 1e-10);
3126 }
3127 #[test]
3128 fn eprop_bounded() {
3129 let mut n = EPropALIFNeuron::default();
3130 for _ in 0..1000 {
3131 n.step(100.0);
3132 }
3133 assert!(n.v.is_finite());
3134 }
3135 #[test]
3136 fn eprop_adaptation() {
3137 let mut n = EPropALIFNeuron::default();
3138 for _ in 0..50 {
3139 n.step(0.5);
3140 }
3141 assert!(n.a.is_finite());
3143 }
3144 #[test]
3145 fn eprop_nan_no_panic() {
3146 EPropALIFNeuron::default().step(f64::NAN);
3147 }
3148
3149 #[test]
3151 fn superspike_reset_clears_state() {
3152 let mut n = SuperSpikeNeuron::default();
3153 for _ in 0..50 {
3154 n.step(0.5);
3155 }
3156 n.reset();
3157 assert!((n.v - 0.0).abs() < 1e-10);
3158 }
3159 #[test]
3160 fn superspike_bounded() {
3161 let mut n = SuperSpikeNeuron::default();
3162 for _ in 0..1000 {
3163 n.step(100.0);
3164 }
3165 assert!(n.v.is_finite());
3166 }
3167 #[test]
3168 fn superspike_trace_evolves() {
3169 let mut n = SuperSpikeNeuron::default();
3170 for _ in 0..50 {
3171 n.step(0.5);
3172 }
3173 assert!(n.trace.is_finite());
3174 }
3175 #[test]
3176 fn superspike_nan_no_panic() {
3177 SuperSpikeNeuron::default().step(f64::NAN);
3178 }
3179
3180 #[test]
3182 fn lnm_reset_clears_state() {
3183 let mut n = LearnableNeuronModel::new();
3184 for _ in 0..50 {
3185 n.step(2.0);
3186 }
3187 n.reset();
3188 assert!((n.v - 0.0).abs() < 1e-10);
3189 }
3190 #[test]
3191 fn lnm_bounded() {
3192 let mut n = LearnableNeuronModel::new();
3193 for _ in 0..1000 {
3194 n.step(100.0);
3195 }
3196 assert!(n.v.is_finite());
3197 }
3198 #[test]
3199 fn lnm_nan_no_panic() {
3200 LearnableNeuronModel::new().step(f64::NAN);
3201 }
3202
3203 #[test]
3205 fn pernarowski_reset_clears_state() {
3206 let mut n = PernarowskiNeuron::new();
3207 for _ in 0..500 {
3208 n.step(1.0);
3209 }
3210 n.reset();
3211 assert!((n.v - (-1.0)).abs() < 1e-10);
3212 }
3213 #[test]
3214 fn pernarowski_bounded() {
3215 let mut n = PernarowskiNeuron::new();
3216 for _ in 0..2000 {
3217 n.step(50.0);
3218 }
3219 assert!(n.v.is_finite());
3220 }
3221 #[test]
3222 fn pernarowski_slow_z() {
3223 let mut n = PernarowskiNeuron::new();
3224 let z0 = n.z;
3225 for _ in 0..2000 {
3226 n.step(1.0);
3227 }
3228 assert!((n.z - z0).abs() > 1e-6, "slow z should evolve");
3229 }
3230 #[test]
3231 fn pernarowski_matches_rk4_candidate() {
3232 let mut n = PernarowskiNeuron::new();
3233 n.v = -0.8;
3234 n.w = 0.2;
3235 n.z = -0.1;
3236 let current = 0.5;
3237 let dt = n.dt;
3238 let rhs = |v: f64, w: f64, z: f64| {
3239 (
3240 v - v.powi(3) / 3.0 - w - z + current,
3241 n.eps1 * (v - n.gamma * w + n.alpha),
3242 n.eps2 * (n.beta * (v + 0.7) - z),
3243 )
3244 };
3245 let (k1v, k1w, k1z) = rhs(n.v, n.w, n.z);
3246 let (k2v, k2w, k2z) = rhs(
3247 n.v + 0.5 * dt * k1v,
3248 n.w + 0.5 * dt * k1w,
3249 n.z + 0.5 * dt * k1z,
3250 );
3251 let (k3v, k3w, k3z) = rhs(
3252 n.v + 0.5 * dt * k2v,
3253 n.w + 0.5 * dt * k2w,
3254 n.z + 0.5 * dt * k2z,
3255 );
3256 let (k4v, k4w, k4z) = rhs(n.v + dt * k3v, n.w + dt * k3w, n.z + dt * k3z);
3257 let expected = (
3258 n.v + dt * (k1v + 2.0 * k2v + 2.0 * k3v + k4v) / 6.0,
3259 n.w + dt * (k1w + 2.0 * k2w + 2.0 * k3w + k4w) / 6.0,
3260 n.z + dt * (k1z + 2.0 * k2z + 2.0 * k3z + k4z) / 6.0,
3261 );
3262
3263 assert_eq!(n.step(current), 0);
3264 assert!((n.v - expected.0).abs() < 1e-14);
3265 assert!((n.w - expected.1).abs() < 1e-14);
3266 assert!((n.z - expected.2).abs() < 1e-14);
3267 }
3268 #[test]
3269 fn pernarowski_invalid_input_preserves_state() {
3270 let mut n = PernarowskiNeuron::new();
3271 let before = (n.v, n.w, n.z);
3272 assert_eq!(n.step(f64::NAN), 0);
3273 assert_eq!((n.v, n.w, n.z), before);
3274 }
3275 #[test]
3276 fn pernarowski_overflow_candidate_preserves_state() {
3277 let mut n = PernarowskiNeuron::new();
3278 n.v = 1.0e160;
3279 let before = (n.v, n.w, n.z);
3280 assert_eq!(n.step(0.5), 0);
3281 assert_eq!((n.v, n.w, n.z), before);
3282 }
3283 #[test]
3284 fn pernarowski_nan_no_panic() {
3285 PernarowskiNeuron::new().step(f64::NAN);
3286 }
3287 #[test]
3288 fn pernarowski_negative_no_crash() {
3289 let mut n = PernarowskiNeuron::new();
3290 for _ in 0..500 {
3291 n.step(-5.0);
3292 }
3293 assert!(n.v.is_finite());
3294 }
3295
3296 #[test]
3299 fn brunel_wang_fires_with_ampa_ext() {
3300 let mut n = BrunelWangNeuron::new();
3301 let mut spikes = 0;
3302 for _ in 0..5000 {
3303 spikes += n.step(5.0);
3304 }
3305 assert!(
3306 spikes > 0,
3307 "Must fire with external AMPA drive, got {spikes}"
3308 );
3309 }
3310
3311 #[test]
3312 fn brunel_wang_silent_without_input() {
3313 let mut n = BrunelWangNeuron::new();
3314 let spikes: i32 = (0..10_000).map(|_| n.step(0.0)).sum();
3315 assert_eq!(spikes, 0, "Must be silent without input");
3316 }
3317
3318 #[test]
3319 fn brunel_wang_nmda_mg_block() {
3320 let n = BrunelWangNeuron::new();
3321 let block_rest = n.nmda_mg_block(-70.0);
3323 let block_depol = n.nmda_mg_block(0.0);
3325 assert!(
3326 block_depol > block_rest,
3327 "Mg²⁺ block should weaken with depolarisation: rest={block_rest:.3} depol={block_depol:.3}"
3328 );
3329 assert!(
3331 block_rest < 0.1,
3332 "Block at -70 mV should be < 0.1, got {block_rest:.3}"
3333 );
3334 assert!(
3336 block_depol > 0.5,
3337 "Block at 0 mV should be > 0.5, got {block_depol:.3}"
3338 );
3339 }
3340
3341 #[test]
3342 fn brunel_wang_full_step_nmda_drive() {
3343 let mut n = BrunelWangNeuron::new();
3345 n.v = -55.0; let mut spikes = 0;
3347 for _ in 0..1000 {
3348 spikes += n.step_full(0.0, 0.0, 1.0, 0.0); }
3350 assert!(
3351 spikes > 0,
3352 "NMDA drive at depolarised V should cause spikes"
3353 );
3354 }
3355
3356 #[test]
3357 fn brunel_wang_gaba_suppresses() {
3358 let mut with_gaba = BrunelWangNeuron::new();
3359 let mut no_gaba = BrunelWangNeuron::new();
3360 let mut spikes_gaba = 0;
3361 let mut spikes_no = 0;
3362 for _ in 0..5000 {
3363 spikes_gaba += with_gaba.step_full(3.0, 0.0, 0.0, 1.0); spikes_no += no_gaba.step_full(3.0, 0.0, 0.0, 0.0);
3365 }
3366 assert!(
3367 spikes_no >= spikes_gaba,
3368 "GABA should suppress: no_gaba={spikes_no}, with_gaba={spikes_gaba}"
3369 );
3370 }
3371
3372 #[test]
3373 fn brunel_wang_refractory() {
3374 let mut n = BrunelWangNeuron::new();
3375 while n.step(10.0) == 0 {}
3377 assert!(n.ref_remaining > 0.0, "Should be refractory after spike");
3379 assert_eq!(
3381 n.step(100.0),
3382 0,
3383 "Should not spike during refractory period"
3384 );
3385 }
3386
3387 #[test]
3388 fn brunel_wang_reset() {
3389 let mut n = BrunelWangNeuron::new();
3390 for _ in 0..1000 {
3391 n.step(5.0);
3392 }
3393 n.reset();
3394 assert_eq!(n.v, n.v_rest);
3395 assert_eq!(n.ref_remaining, 0.0);
3396 }
3397
3398 #[test]
3399 fn brunel_wang_voltage_bounded() {
3400 let mut n = BrunelWangNeuron::new();
3401 for _ in 0..10_000 {
3402 n.step(100.0);
3403 }
3404 assert!(n.v.is_finite(), "V must stay finite");
3405 assert!(
3407 n.v <= n.v_threshold,
3408 "V should be at or below threshold (reset clamp)"
3409 );
3410 }
3411
3412 #[test]
3413 fn brunel_wang_nan_input() {
3414 let mut n = BrunelWangNeuron::new();
3415 n.step(f64::NAN);
3416 }
3418
3419 #[test]
3420 fn brunel_wang_performance() {
3421 let start = std::time::Instant::now();
3422 let mut n = BrunelWangNeuron::new();
3423 for _ in 0..10_000 {
3424 std::hint::black_box(n.step(3.0));
3425 }
3426 let elapsed = start.elapsed();
3427 assert!(
3428 elapsed.as_millis() < 50,
3429 "10k steps in <50ms, took {}ms",
3430 elapsed.as_millis()
3431 );
3432 }
3433}