1use rand::{RngExt, SeedableRng};
12use rand_xoshiro::Xoshiro256PlusPlus;
13
14pub fn safe_rate(
17 number_factor: f64,
18 v_offset: f64,
19 v: f64,
20 denom_scale: f64,
21 fallback: f64,
22) -> f64 {
23 let d = v + v_offset;
24 if d.abs() < 1e-7 {
25 fallback
26 } else {
27 number_factor * d / (1.0 - (-d / denom_scale).exp())
28 }
29}
30
31#[derive(Clone, Debug)]
33pub struct HodgkinHuxleyNeuron {
34 pub v: f64,
35 pub m: f64,
36 pub h: f64,
37 pub n: f64,
38 pub c_m: f64,
39 pub g_na: f64,
40 pub g_k: f64,
41 pub g_l: f64,
42 pub e_na: f64,
43 pub e_k: f64,
44 pub e_l: f64,
45 pub dt: f64,
46 pub v_threshold: f64,
47}
48
49impl HodgkinHuxleyNeuron {
50 pub fn new() -> Self {
51 Self {
52 v: -65.0,
53 m: 0.05,
54 h: 0.6,
55 n: 0.32,
56 c_m: 1.0,
57 g_na: 120.0,
58 g_k: 36.0,
59 g_l: 0.3,
60 e_na: 50.0,
61 e_k: -77.0,
62 e_l: -54.4,
63 dt: 0.01,
64 v_threshold: 0.0,
65 }
66 }
67 pub fn step(&mut self, current: f64) -> i32 {
68 let v_prev = self.v;
69 let steps = (1.0 / self.dt) as usize;
70 for _ in 0..steps {
71 let am = safe_rate(0.1, 40.0, self.v, 10.0, 1.0);
72 let bm = 4.0 * (-(self.v + 65.0) / 18.0).exp();
73 let ah = 0.07 * (-(self.v + 65.0) / 20.0).exp();
74 let bh = 1.0 / (1.0 + (-(self.v + 35.0) / 10.0).exp());
75 let an = safe_rate(0.01, 55.0, self.v, 10.0, 0.1);
76 let bn = 0.125 * (-(self.v + 65.0) / 80.0).exp();
77 self.m += (am * (1.0 - self.m) - bm * self.m) * self.dt;
78 self.h += (ah * (1.0 - self.h) - bh * self.h) * self.dt;
79 self.n += (an * (1.0 - self.n) - bn * self.n) * self.dt;
80 let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
81 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
82 let i_l = self.g_l * (self.v - self.e_l);
83 self.v += (-i_na - i_k - i_l + current) / self.c_m * self.dt;
84 }
85 if self.v >= self.v_threshold && v_prev < self.v_threshold {
86 1
87 } else {
88 0
89 }
90 }
91 pub fn reset(&mut self) {
92 self.v = -65.0;
93 self.m = 0.05;
94 self.h = 0.6;
95 self.n = 0.32;
96 }
97}
98impl Default for HodgkinHuxleyNeuron {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104#[derive(Clone, Debug)]
121pub struct TraubMilesNeuron {
122 pub v: f64,
123 pub m: f64,
124 pub h: f64,
125 pub n: f64,
126 pub g_na: f64,
127 pub g_k: f64,
128 pub g_l: f64,
129 pub e_na: f64,
130 pub e_k: f64,
131 pub e_l: f64,
132 pub dt: f64,
133 pub v_threshold: f64,
134}
135
136impl TraubMilesNeuron {
137 pub fn new() -> Self {
138 Self {
139 v: -67.0,
140 m: 0.05,
141 h: 0.6,
142 n: 0.3,
143 g_na: 100.0,
144 g_k: 80.0,
145 g_l: 0.1,
146 e_na: 50.0,
147 e_k: -100.0,
148 e_l: -67.0,
149 dt: 0.01,
150 v_threshold: -20.0,
151 }
152 }
153 pub fn step(&mut self, current: f64) -> i32 {
154 let v_prev = self.v;
155 for _ in 0..10 {
156 let am = safe_rate(0.32, 54.0, self.v, 4.0, 8.0);
157 let bm = safe_rate(-0.28, 27.0, self.v, -5.0, 5.6);
158 let ah = 0.128 * (-(self.v + 50.0) / 18.0).exp();
159 let bh = 4.0 / (1.0 + (-(self.v + 27.0) / 5.0).exp());
160 let an = safe_rate(0.032, 52.0, self.v, 5.0, 0.32);
161 let bn = 0.5 * (-(self.v + 57.0) / 40.0).exp();
162
163 self.m += (am * (1.0 - self.m) - bm * self.m) * self.dt;
164 self.h += (ah * (1.0 - self.h) - bh * self.h) * self.dt;
165 self.n += (an * (1.0 - self.n) - bn * self.n) * self.dt;
166
167 let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
168 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
169 let i_l = self.g_l * (self.v - self.e_l);
170 self.v += (-i_na - i_k - i_l + current) * self.dt;
171 }
172 if self.v >= self.v_threshold && v_prev < self.v_threshold {
173 1
174 } else {
175 0
176 }
177 }
178 pub fn reset(&mut self) {
179 *self = Self::new();
180 }
181}
182impl Default for TraubMilesNeuron {
183 fn default() -> Self {
184 Self::new()
185 }
186}
187
188#[derive(Clone, Debug)]
190pub struct WangBuzsakiNeuron {
191 pub v: f64,
192 pub h: f64,
193 pub n: f64,
194 pub g_na: f64,
195 pub g_k: f64,
196 pub g_l: f64,
197 pub e_na: f64,
198 pub e_k: f64,
199 pub e_l: f64,
200 pub c_m: f64,
201 pub phi: f64,
202 pub dt: f64,
203 pub v_threshold: f64,
204}
205
206impl WangBuzsakiNeuron {
207 pub fn new() -> Self {
208 Self {
209 v: -65.0,
210 h: 0.8,
211 n: 0.1,
212 g_na: 35.0,
213 g_k: 9.0,
214 g_l: 0.1,
215 e_na: 55.0,
216 e_k: -90.0,
217 e_l: -65.0,
218 c_m: 1.0,
219 phi: 5.0,
220 dt: 0.01,
221 v_threshold: -20.0,
222 }
223 }
224 pub fn step(&mut self, current: f64) -> i32 {
225 let v_prev = self.v;
226 let n_sub = (0.5 / self.dt.max(0.001)) as usize;
227 for _ in 0..n_sub {
228 let am = safe_rate(0.1, 35.0, self.v, 10.0, 1.0);
229 let bm = 4.0 * (-(self.v + 60.0) / 18.0).exp();
230 let m_inf = am / (am + bm);
231 let ah = 0.07 * (-(self.v + 58.0) / 20.0).exp();
232 let bh = 1.0 / (1.0 + (-(self.v + 28.0) / 10.0).exp());
233 let an = safe_rate(0.01, 34.0, self.v, 10.0, 0.1);
234 let bn = 0.125 * (-(self.v + 44.0) / 80.0).exp();
235 self.h += self.phi * (ah * (1.0 - self.h) - bh * self.h) * self.dt;
236 self.n += self.phi * (an * (1.0 - self.n) - bn * self.n) * self.dt;
237 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
238 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
239 let i_l = self.g_l * (self.v - self.e_l);
240 self.v += (-i_na - i_k - i_l + current) / self.c_m * self.dt;
241 }
242 if self.v >= self.v_threshold && v_prev < self.v_threshold {
243 1
244 } else {
245 0
246 }
247 }
248 pub fn reset(&mut self) {
249 self.v = -65.0;
250 self.h = 0.8;
251 self.n = 0.1;
252 }
253}
254impl Default for WangBuzsakiNeuron {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260#[derive(Clone, Debug)]
262pub struct ConnorStevensNeuron {
263 pub v: f64,
264 pub m: f64,
265 pub h: f64,
266 pub n: f64,
267 pub a: f64,
268 pub b: f64,
269 pub g_na: f64,
270 pub g_k: f64,
271 pub g_a: f64,
272 pub g_l: f64,
273 pub e_na: f64,
274 pub e_k: f64,
275 pub e_a: f64,
276 pub e_l: f64,
277 pub c_m: f64,
278 pub dt: f64,
279 pub v_threshold: f64,
280}
281
282impl ConnorStevensNeuron {
283 pub fn new() -> Self {
284 Self {
285 v: -68.0,
286 m: 0.01,
287 h: 0.99,
288 n: 0.1,
289 a: 0.5,
290 b: 0.1,
291 g_na: 120.0,
292 g_k: 20.0,
293 g_a: 47.7,
294 g_l: 0.3,
295 e_na: 55.0,
296 e_k: -72.0,
297 e_a: -75.0,
298 e_l: -17.0,
299 c_m: 1.0,
300 dt: 0.01,
301 v_threshold: 0.0,
302 }
303 }
304 pub fn step(&mut self, current: f64) -> i32 {
305 let v_prev = self.v;
306 for _ in 0..100 {
308 let am = safe_rate(0.38, 29.7, self.v, 10.0, 3.8);
309 let bm = 15.2 * (-(self.v + 54.7) / 18.0).exp();
310 let ah = 0.266 * (-(self.v + 48.0) / 20.0).exp();
311 let bh = 3.8 / (1.0 + (-(self.v + 18.0) / 10.0).exp());
312 let an = safe_rate(0.02, 45.7, self.v, 10.0, 0.2);
313 let bn = 0.25 * (-(self.v + 55.7) / 80.0).exp();
314 let a_inf = (0.0761 * ((self.v + 94.22) / 31.84).exp()
315 / (1.0 + ((self.v + 1.17) / 28.93).exp()))
316 .powf(1.0 / 3.0);
317 let b_inf = 1.0 / (1.0 + ((self.v + 53.3) / 14.54).exp()).powf(4.0);
318 let tau_a = 0.3632 + 1.158 / (1.0 + ((self.v + 55.96) / 20.12).exp());
319 let tau_b = 1.24 + 2.678 / (1.0 + ((self.v + 50.0) / 16.027).exp());
320 self.m += (am * (1.0 - self.m) - bm * self.m) * self.dt;
321 self.h += (ah * (1.0 - self.h) - bh * self.h) * self.dt;
322 self.n += (an * (1.0 - self.n) - bn * self.n) * self.dt;
323 self.a += (a_inf - self.a) / tau_a * self.dt;
324 self.b += (b_inf - self.b) / tau_b * self.dt;
325 let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
326 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
327 let i_a = self.g_a * self.a.powi(3) * self.b * (self.v - self.e_a);
328 let i_l = self.g_l * (self.v - self.e_l);
329 self.v += (-i_na - i_k - i_a - i_l + current) / self.c_m * self.dt;
330 }
331 if self.v >= self.v_threshold && v_prev < self.v_threshold {
332 1
333 } else {
334 0
335 }
336 }
337 pub fn reset(&mut self) {
338 self.v = -68.0;
339 self.m = 0.01;
340 self.h = 0.99;
341 self.n = 0.1;
342 self.a = 0.5;
343 self.b = 0.1;
344 }
345}
346impl Default for ConnorStevensNeuron {
347 fn default() -> Self {
348 Self::new()
349 }
350}
351
352#[derive(Clone, Debug)]
354pub struct DestexheThalamicNeuron {
355 pub v: f64,
356 pub h_na: f64,
357 pub n_k: f64,
358 pub m_t: f64,
359 pub h_t: f64,
360 pub g_na: f64,
361 pub g_k: f64,
362 pub g_t: f64,
363 pub g_l: f64,
364 pub e_na: f64,
365 pub e_k: f64,
366 pub e_ca: f64,
367 pub e_l: f64,
368 pub dt: f64,
369 pub v_threshold: f64,
370}
371
372impl DestexheThalamicNeuron {
373 pub fn new() -> Self {
374 Self {
375 v: -65.0,
376 h_na: 0.6,
377 n_k: 0.3,
378 m_t: 0.0,
379 h_t: 1.0,
380 g_na: 100.0,
381 g_k: 10.0,
382 g_t: 2.0,
383 g_l: 0.05,
384 e_na: 50.0,
385 e_k: -90.0,
386 e_ca: 120.0,
387 e_l: -70.0,
388 dt: 0.02,
389 v_threshold: -20.0,
390 }
391 }
392 pub fn step(&mut self, current: f64) -> i32 {
393 let v_prev = self.v;
394 for _ in 0..5 {
395 let m_na = 1.0 / (1.0 + (-(self.v + 37.0) / 7.0).exp());
396 let h_na_inf = 1.0 / (1.0 + ((self.v + 41.0) / 4.0).exp());
397 let n_inf = 1.0 / (1.0 + (-(self.v + 25.0) / 12.0).exp());
398 let m_t_inf = 1.0 / (1.0 + (-(self.v + 57.0) / 6.5).exp());
399 let h_t_inf = 1.0 / (1.0 + ((self.v + 81.0) / 4.0).exp());
400 let tau_h_na = (1.0
402 / (0.128 * (-(self.v + 46.0) / 18.0).exp()
403 + 4.0 / (1.0 + (-(self.v + 23.0) / 5.0).exp())))
404 .max(0.1);
405 let tau_n_k = (1.0 / (0.032 * 5.0 + 0.5 * (-(self.v + 40.0) / 40.0).exp())).max(0.1);
406 let tau_h_t = if self.v < -81.0 {
407 (30.8
408 + 211.4 * ((self.v + 115.2) / 5.0).exp()
409 / (1.0 + ((self.v + 86.0) / 3.2).exp()))
410 .max(0.1)
411 } else {
412 10.0
413 };
414 self.h_na += (h_na_inf - self.h_na) / tau_h_na * self.dt;
415 self.n_k += (n_inf - self.n_k) / tau_n_k * self.dt;
416 self.m_t = m_t_inf; self.h_t += (h_t_inf - self.h_t) / tau_h_t * self.dt;
418 let i_na = self.g_na * m_na.powi(3) * self.h_na * (self.v - self.e_na);
419 let i_k = self.g_k * self.n_k.powi(4) * (self.v - self.e_k);
420 let i_t = self.g_t * self.m_t.powi(2) * self.h_t * (self.v - self.e_ca);
421 let i_l = self.g_l * (self.v - self.e_l);
422 self.v += (-i_na - i_k - i_t - i_l + current) * self.dt;
423 }
424 if self.v >= self.v_threshold && v_prev < self.v_threshold {
425 1
426 } else {
427 0
428 }
429 }
430 pub fn reset(&mut self) {
431 self.v = -65.0;
432 self.h_na = 0.6;
433 self.n_k = 0.3;
434 self.m_t = 0.0;
435 self.h_t = 1.0;
436 }
437}
438impl Default for DestexheThalamicNeuron {
439 fn default() -> Self {
440 Self::new()
441 }
442}
443
444#[derive(Clone, Debug)]
446pub struct HuberBraunNeuron {
447 pub v: f64,
448 pub a_sd: f64,
449 pub a_sr: f64,
450 pub g_sd: f64,
451 pub g_sr: f64,
452 pub g_l: f64,
453 pub e_sd: f64,
454 pub e_sr: f64,
455 pub e_l: f64,
456 pub tau_sd: f64,
457 pub tau_sr: f64,
458 pub eta: f64,
459 pub dt: f64,
460 pub v_threshold: f64,
461}
462
463impl HuberBraunNeuron {
464 pub fn new() -> Self {
465 Self {
466 v: -50.0,
467 a_sd: 0.0,
468 a_sr: 0.0,
469 g_sd: 1.5,
470 g_sr: 0.4,
471 g_l: 0.1,
472 e_sd: 50.0,
473 e_sr: -90.0,
474 e_l: -60.0,
475 tau_sd: 10.0,
476 tau_sr: 20.0,
477 eta: 0.012,
478 dt: 0.1,
479 v_threshold: -20.0,
480 }
481 }
482 pub fn step(&mut self, current: f64) -> i32 {
483 let v_prev = self.v;
484 let sd_inf = 1.0 / (1.0 + (-(self.v + 40.0) / 6.0).exp());
486 let sr_inf = 1.0 / (1.0 + ((self.v + 40.0) / 6.0).exp());
487 self.a_sd += (sd_inf - self.a_sd) / self.tau_sd * self.dt;
488 self.a_sr += (sr_inf - self.a_sr) / self.tau_sr * self.dt;
489 let i_sd = self.g_sd * self.a_sd * (self.v - self.e_sd);
490 let i_sr = self.g_sr * self.a_sr * (self.v - self.e_sr);
491 let i_l = self.g_l * (self.v - self.e_l);
492 self.v += (-i_sd - i_sr - i_l + current) * self.dt;
493 if self.v >= self.v_threshold && v_prev < self.v_threshold {
494 1
495 } else {
496 0
497 }
498 }
499 pub fn reset(&mut self) {
500 self.v = -50.0;
501 self.a_sd = 0.0;
502 self.a_sr = 0.0;
503 }
504}
505impl Default for HuberBraunNeuron {
506 fn default() -> Self {
507 Self::new()
508 }
509}
510
511#[derive(Clone, Debug)]
513pub struct GolombFSNeuron {
514 pub v: f64,
515 pub h: f64,
516 pub n: f64,
517 pub p: f64,
518 pub g_na: f64,
519 pub g_k: f64,
520 pub g_kv3: f64,
521 pub g_l: f64,
522 pub e_na: f64,
523 pub e_k: f64,
524 pub e_l: f64,
525 pub dt: f64,
526 pub v_threshold: f64,
527}
528
529impl GolombFSNeuron {
530 pub fn new() -> Self {
531 Self {
532 v: -65.0,
533 h: 0.9,
534 n: 0.1,
535 p: 0.0,
536 g_na: 112.5,
537 g_k: 225.0,
538 g_kv3: 150.0,
539 g_l: 0.25,
540 e_na: 50.0,
541 e_k: -90.0,
542 e_l: -70.0,
543 dt: 0.01,
544 v_threshold: -20.0,
545 }
546 }
547 pub fn step(&mut self, current: f64) -> i32 {
548 let v_prev = self.v;
549 for _ in 0..10 {
550 let m_inf = 1.0 / (1.0 + (-(self.v + 24.0) / 11.5).exp());
551 let h_inf = 1.0 / (1.0 + ((self.v + 58.3) / 6.7).exp());
552 let n_inf = 1.0 / (1.0 + (-(self.v + 12.4) / 6.8).exp());
553 let p_inf = 1.0 / (1.0 + (-(self.v + 3.0) / 8.0).exp());
555 let tau_h = 0.5 + 14.0 / (1.0 + ((self.v + 60.0) / 12.0).exp());
557 let tau_n = 0.087 + 11.4 / (1.0 + ((self.v + 14.6) / 8.6).exp());
558 let tau_p = 0.1 + 4.0 / (1.0 + ((self.v + 25.0) / 10.0).exp());
559 self.h += (h_inf - self.h) / tau_h * self.dt;
560 self.n += (n_inf - self.n) / tau_n * self.dt;
561 self.p += (p_inf - self.p) / tau_p * self.dt;
562 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
563 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
565 let i_kv3 = self.g_kv3 * self.p.powi(2) * (self.v - self.e_k);
566 let i_l = self.g_l * (self.v - self.e_l);
567 self.v += (-i_na - i_k - i_kv3 - i_l + current) * self.dt;
568 }
569 if self.v >= self.v_threshold && v_prev < self.v_threshold {
570 1
571 } else {
572 0
573 }
574 }
575 pub fn reset(&mut self) {
576 self.v = -65.0;
577 self.h = 0.9;
578 self.n = 0.1;
579 self.p = 0.0;
580 }
581}
582impl Default for GolombFSNeuron {
583 fn default() -> Self {
584 Self::new()
585 }
586}
587
588#[derive(Clone, Debug)]
590pub struct PospischilNeuron {
591 pub v: f64,
592 pub m: f64,
593 pub h: f64,
594 pub n: f64,
595 pub p: f64,
596 pub g_na: f64,
597 pub g_k: f64,
598 pub g_m: f64,
599 pub g_l: f64,
600 pub e_na: f64,
601 pub e_k: f64,
602 pub e_l: f64,
603 pub c_m: f64,
604 pub vt: f64,
605 pub dt: f64,
606 pub v_threshold: f64,
607}
608
609impl PospischilNeuron {
610 pub fn new() -> Self {
611 Self {
612 v: -70.0,
613 m: 0.05,
614 h: 0.6,
615 n: 0.3,
616 p: 0.0,
617 g_na: 50.0,
618 g_k: 5.0,
619 g_m: 0.07,
620 g_l: 0.1,
621 e_na: 50.0,
622 e_k: -90.0,
623 e_l: -70.0,
624 c_m: 1.0,
625 vt: -56.2,
626 dt: 0.025,
627 v_threshold: -20.0,
628 }
629 }
630 pub fn step(&mut self, current: f64) -> i32 {
631 let v_prev = self.v;
632 for _ in 0..4 {
633 let dv = self.v - self.vt;
634 let x_m = dv - 13.0;
635 let am = if x_m.abs() < 1e-6 {
636 -0.32 * -4.0
637 } else {
638 -0.32 * x_m / ((-(x_m) / 4.0).exp() - 1.0)
639 };
640 let x_bm = dv - 40.0;
641 let bm = if x_bm.abs() < 1e-6 {
642 0.28 * 5.0
643 } else {
644 0.28 * x_bm / ((x_bm / 5.0).exp() - 1.0)
645 };
646 let ah = 0.128 * (-(dv - 17.0) / 18.0).exp();
647 let bh = 4.0 / (1.0 + (-(dv - 40.0) / 5.0).exp());
648 let x_n = dv - 15.0;
649 let an = if x_n.abs() < 1e-6 {
650 -0.032 * -5.0
651 } else {
652 -0.032 * x_n / ((-(x_n) / 5.0).exp() - 1.0)
653 };
654 let bn = 0.5 * (-(dv - 10.0) / 40.0).exp();
655 let p_inf = 1.0 / (1.0 + (-(self.v + 35.0) / 10.0).exp());
656 self.m += (am * (1.0 - self.m) - bm * self.m) * self.dt;
657 self.h += (ah * (1.0 - self.h) - bh * self.h) * self.dt;
658 self.n += (an * (1.0 - self.n) - bn * self.n) * self.dt;
659 let tau_p =
660 608.0 / (3.3 * ((self.v + 35.0) / 20.0).exp() + (-(self.v + 35.0) / 20.0).exp());
661 self.p += (p_inf - self.p) / tau_p * self.dt;
662 let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
663 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
664 let i_m = self.g_m * self.p * (self.v - self.e_k);
665 let i_l = self.g_l * (self.v - self.e_l);
666 self.v += (-i_na - i_k - i_m - i_l + current) / self.c_m * self.dt;
667 }
668 if self.v >= self.v_threshold && v_prev < self.v_threshold {
669 1
670 } else {
671 0
672 }
673 }
674 pub fn reset(&mut self) {
675 self.v = -70.0;
676 self.m = 0.05;
677 self.h = 0.6;
678 self.n = 0.3;
679 self.p = 0.0;
680 }
681}
682#[allow(clippy::derivable_impls)]
683impl Default for PospischilNeuron {
684 fn default() -> Self {
685 Self::new()
686 }
687}
688
689#[derive(Clone, Debug)]
691pub struct MainenSejnowskiNeuron {
692 pub vs: f64,
693 pub va: f64,
694 pub m: f64,
695 pub h: f64,
696 pub n: f64,
697 pub kappa: f64,
698 pub g_na: f64,
699 pub g_k: f64,
700 pub g_l: f64,
701 pub e_na: f64,
702 pub e_k: f64,
703 pub e_l: f64,
704 pub c_s: f64,
705 pub c_a: f64,
706 pub dt: f64,
707 pub v_threshold: f64,
708}
709
710impl MainenSejnowskiNeuron {
711 pub fn new() -> Self {
712 Self {
713 vs: -65.0,
714 va: -65.0,
715 m: 0.05,
716 h: 0.6,
717 n: 0.3,
718 kappa: 10.0,
719 g_na: 3000.0,
720 g_k: 1500.0,
721 g_l: 1.0,
722 e_na: 50.0,
723 e_k: -90.0,
724 e_l: -70.0,
725 c_s: 1.0,
726 c_a: 0.1,
727 dt: 0.005,
728 v_threshold: -20.0,
729 }
730 }
731 pub fn step(&mut self, current: f64) -> i32 {
732 let v_prev = self.vs;
733 for _ in 0..20 {
734 let x_am = self.va + 25.0;
736 let am = if x_am.abs() < 1e-6 {
737 0.182 * 9.0
738 } else {
739 0.182 * x_am / (1.0 - (-(x_am) / 9.0).exp() + 1e-12)
740 };
741 let bm = if x_am.abs() < 1e-6 {
742 0.124 * 9.0
743 } else {
744 -0.124 * x_am / (1.0 - ((x_am) / 9.0).exp() + 1e-12)
745 };
746 let x_ah = self.va + 40.0;
747 let ah = if x_ah.abs() < 1e-6 {
748 0.024 * 5.0
749 } else {
750 0.024 * x_ah / (1.0 - (-(x_ah) / 5.0).exp() + 1e-12)
751 };
752 let x_bh = self.va + 65.0;
753 let bh = if x_bh.abs() < 1e-6 {
754 0.0091 * 5.0
755 } else {
756 -0.0091 * x_bh / (1.0 - ((x_bh) / 5.0).exp() + 1e-12)
757 };
758 let x_an = self.va - 20.0;
759 let an = if x_an.abs() < 1e-6 {
760 0.02 * 9.0
761 } else {
762 0.02 * x_an / (1.0 - (-(x_an) / 9.0).exp() + 1e-12)
763 };
764 let bn = if x_an.abs() < 1e-6 {
765 0.002 * 9.0
766 } else {
767 -0.002 * x_an / (1.0 - ((x_an) / 9.0).exp() + 1e-12)
768 };
769 self.m = (self.m + (am * (1.0 - self.m) - bm * self.m) * self.dt).clamp(0.0, 1.0);
770 self.h = (self.h + (ah * (1.0 - self.h) - bh * self.h) * self.dt).clamp(0.0, 1.0);
771 self.n = (self.n + (an * (1.0 - self.n) - bn * self.n) * self.dt).clamp(0.0, 1.0);
772 let i_na = self.g_na * self.m.powi(3) * self.h * (self.va - self.e_na);
773 let i_k = self.g_k * self.n * (self.va - self.e_k);
774 let i_l_s = self.g_l * (self.vs - self.e_l);
775 self.vs = (self.vs
776 + (-i_l_s + self.kappa * (self.va - self.vs) + current) / self.c_s * self.dt)
777 .clamp(-200.0, 200.0);
778 self.va = (self.va
779 + (-i_na - i_k + self.kappa * (self.vs - self.va)) / self.c_a * self.dt)
780 .clamp(-200.0, 200.0);
781 }
782 if self.vs >= self.v_threshold && v_prev < self.v_threshold {
783 1
784 } else {
785 0
786 }
787 }
788 pub fn reset(&mut self) {
789 self.vs = -65.0;
790 self.va = -65.0;
791 self.m = 0.05;
792 self.h = 0.6;
793 self.n = 0.3;
794 }
795}
796impl Default for MainenSejnowskiNeuron {
797 fn default() -> Self {
798 Self::new()
799 }
800}
801
802#[derive(Clone, Debug)]
804pub struct DeSchutterPurkinjeNeuron {
805 pub v: f64,
806 pub h_na: f64,
807 pub n_k: f64,
808 pub m_cap: f64,
809 pub h_cap: f64,
810 pub q_kca: f64,
811 pub ca: f64,
812 pub g_na: f64,
813 pub g_k: f64,
814 pub g_cap: f64,
815 pub g_kca: f64,
816 pub g_l: f64,
817 pub e_na: f64,
818 pub e_k: f64,
819 pub e_ca: f64,
820 pub e_l: f64,
821 pub ca_decay: f64,
822 pub f_ca: f64,
823 pub dt: f64,
824 pub v_threshold: f64,
825}
826
827impl DeSchutterPurkinjeNeuron {
828 pub fn new() -> Self {
829 Self {
830 v: -68.0,
831 h_na: 0.8,
832 n_k: 0.1,
833 m_cap: 0.0,
834 h_cap: 0.9,
835 q_kca: 0.0,
836 ca: 0.0001,
837 g_na: 125.0,
838 g_k: 10.0,
839 g_cap: 45.0,
840 g_kca: 35.0,
841 g_l: 0.5,
842 e_na: 45.0,
843 e_k: -85.0,
844 e_ca: 135.0,
845 e_l: -68.0,
846 ca_decay: 0.02,
847 f_ca: 0.00024,
848 dt: 0.01,
849 v_threshold: -20.0,
850 }
851 }
852 pub fn step(&mut self, current: f64) -> i32 {
853 let v_prev = self.v;
854 for _ in 0..5 {
855 let m_na = 1.0 / (1.0 + (-(self.v + 35.0) / 7.5).exp());
856 let h_na_inf = 1.0 / (1.0 + ((self.v + 55.0) / 7.0).exp());
857 let n_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 15.0).exp());
858 let m_cap_inf = 1.0 / (1.0 + (-(self.v + 19.0) / 5.5).exp());
859 let h_cap_inf = 1.0 / (1.0 + ((self.v + 48.0) / 7.0).exp());
860 let q_inf = self.ca / (self.ca + 0.0002);
861 let tau_h_na = 0.5 + 14.0 / (1.0 + ((self.v + 40.0) / 12.0).exp());
862 let tau_n_k = 1.0 + 11.0 / (1.0 + ((self.v + 15.0) / 8.0).exp());
863 self.h_na += (h_na_inf - self.h_na) / tau_h_na * self.dt;
864 self.n_k += (n_inf - self.n_k) / tau_n_k * self.dt;
865 self.m_cap += (m_cap_inf - self.m_cap) / 0.3 * self.dt;
866 self.h_cap += (h_cap_inf - self.h_cap) / 45.0 * self.dt;
867 self.q_kca += (q_inf - self.q_kca) / 1.0 * self.dt;
868 let i_na = self.g_na * m_na.powi(3) * self.h_na * (self.v - self.e_na);
869 let i_k = self.g_k * self.n_k.powi(4) * (self.v - self.e_k);
870 let i_cap = self.g_cap * self.m_cap.powi(2) * self.h_cap * (self.v - self.e_ca);
871 let i_kca = self.g_kca * self.q_kca * (self.v - self.e_k);
872 let i_l = self.g_l * (self.v - self.e_l);
873 self.v += (-i_na - i_k - i_cap - i_kca - i_l + current) * self.dt;
874 self.ca = (self.ca + (-self.f_ca * i_cap - self.ca_decay * self.ca) * self.dt).max(0.0);
875 }
876 if self.v >= self.v_threshold && v_prev < self.v_threshold {
877 1
878 } else {
879 0
880 }
881 }
882 pub fn reset(&mut self) {
883 self.v = -68.0;
884 self.h_na = 0.8;
885 self.n_k = 0.1;
886 self.m_cap = 0.0;
887 self.h_cap = 0.9;
888 self.q_kca = 0.0;
889 self.ca = 0.0001;
890 }
891}
892impl Default for DeSchutterPurkinjeNeuron {
893 fn default() -> Self {
894 Self::new()
895 }
896}
897
898#[derive(Clone, Debug)]
900pub struct PlantR15Neuron {
901 pub v: f64,
902 pub m: f64,
903 pub h: f64,
904 pub n: f64,
905 pub ca: f64,
906 pub g_na: f64,
907 pub g_k: f64,
908 pub g_ca: f64,
909 pub g_l: f64,
910 pub g_kca: f64,
911 pub e_na: f64,
912 pub e_k: f64,
913 pub e_ca: f64,
914 pub e_l: f64,
915 pub c_m: f64,
916 pub k_ca: f64,
917 pub tau_ca: f64,
918 pub dt: f64,
919 pub v_threshold: f64,
920}
921
922impl PlantR15Neuron {
923 pub fn new() -> Self {
924 Self {
925 v: -50.0,
926 m: 0.05,
927 h: 0.6,
928 n: 0.3,
929 ca: 0.1,
930 g_na: 4.0,
931 g_k: 0.3,
932 g_ca: 0.004,
933 g_l: 0.003,
934 g_kca: 0.03,
935 e_na: 30.0,
936 e_k: -75.0,
937 e_ca: 140.0,
938 e_l: -40.0,
939 c_m: 1.0,
940 k_ca: 0.0085,
941 tau_ca: 500.0,
942 dt: 0.05,
943 v_threshold: -10.0,
944 }
945 }
946 pub fn step(&mut self, current: f64) -> i32 {
947 let v_prev = self.v;
948 for _ in 0..5 {
949 let am = safe_rate(0.1, 50.0, self.v, 10.0, 1.0);
950 let bm = 4.0 * (-(self.v + 75.0) / 18.0).exp();
951 let ah = 0.07 * (-(self.v + 50.0) / 20.0).exp();
952 let bh = 1.0 / (1.0 + (-(self.v + 20.0) / 10.0).exp());
953 let an = safe_rate(0.01, 55.0, self.v, 10.0, 0.1);
954 let bn = 0.125 * (-(self.v + 65.0) / 80.0).exp();
955 self.m += (am * (1.0 - self.m) - bm * self.m) * self.dt;
956 self.h += (ah * (1.0 - self.h) - bh * self.h) * self.dt;
957 self.n += (an * (1.0 - self.n) - bn * self.n) * self.dt;
958 let m_ca = 1.0 / (1.0 + (-(self.v + 25.0) / 5.0).exp());
959 let kca_act = self.ca / (0.5 + self.ca);
960 let i_na = self.g_na * self.m.powi(3) * self.h * (self.v - self.e_na);
961 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
962 let i_ca = self.g_ca * m_ca.powi(2) * (self.v - self.e_ca);
963 let i_kca = self.g_kca * kca_act * (self.v - self.e_k);
964 let i_l = self.g_l * (self.v - self.e_l);
965 self.v += (-i_na - i_k - i_ca - i_kca - i_l + current) / self.c_m * self.dt;
966 self.ca = (self.ca + (-self.k_ca * i_ca - self.ca / self.tau_ca) * self.dt).max(0.0);
967 }
968 if self.v >= self.v_threshold && v_prev < self.v_threshold {
969 1
970 } else {
971 0
972 }
973 }
974 pub fn reset(&mut self) {
975 self.v = -50.0;
976 self.m = 0.05;
977 self.h = 0.6;
978 self.n = 0.3;
979 self.ca = 0.1;
980 }
981}
982impl Default for PlantR15Neuron {
983 fn default() -> Self {
984 Self::new()
985 }
986}
987
988#[derive(Clone, Debug)]
990pub struct PrescottNeuron {
991 pub v: f64,
992 pub w: f64,
993 pub g_fast: f64,
994 pub g_slow: f64,
995 pub g_l: f64,
996 pub e_fast: f64,
997 pub e_slow: f64,
998 pub e_l: f64,
999 pub beta_w: f64,
1000 pub gamma_w: f64,
1001 pub tau_w: f64,
1002 pub phi: f64,
1003 pub dt: f64,
1004 pub v_threshold: f64,
1005}
1006
1007impl PrescottNeuron {
1008 pub fn new() -> Self {
1009 Self {
1010 v: -65.0,
1011 w: 0.0,
1012 g_fast: 20.0,
1013 g_slow: 20.0,
1014 g_l: 2.0,
1015 e_fast: 50.0,
1016 e_slow: -100.0,
1017 e_l: -70.0,
1018 beta_w: -21.0,
1019 gamma_w: 15.0,
1020 tau_w: 100.0,
1021 phi: 0.15,
1022 dt: 0.1,
1023 v_threshold: -20.0,
1024 }
1025 }
1026 pub fn step(&mut self, current: f64) -> i32 {
1027 let v_prev = self.v;
1028 let m_inf = 1.0 / (1.0 + (-(self.v + 20.0) / 15.0).exp());
1029 let w_inf = 1.0 / (1.0 + (-(self.v - self.beta_w) / self.gamma_w).exp());
1030 let i_fast = self.g_fast * m_inf * (self.v - self.e_fast);
1031 let i_slow = self.g_slow * self.w * (self.v - self.e_slow);
1032 let i_l = self.g_l * (self.v - self.e_l);
1033 self.v += (-i_fast - i_slow - i_l + current) * self.dt;
1034 self.w += self.phi * (w_inf - self.w) / self.tau_w * self.dt;
1035 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1036 1
1037 } else {
1038 0
1039 }
1040 }
1041 pub fn reset(&mut self) {
1042 self.v = -65.0;
1043 self.w = 0.0;
1044 }
1045}
1046impl Default for PrescottNeuron {
1047 fn default() -> Self {
1048 Self::new()
1049 }
1050}
1051
1052#[derive(Clone, Debug)]
1054pub struct MihalasNieburNeuron {
1055 pub v: f64,
1056 pub theta: f64,
1057 pub i1: f64,
1058 pub i2: f64,
1059 pub v_rest: f64,
1060 pub v_reset: f64,
1061 pub theta_reset: f64,
1062 pub theta_inf: f64,
1063 pub tau_v: f64,
1064 pub tau_theta: f64,
1065 pub tau_1: f64,
1066 pub tau_2: f64,
1067 pub a: f64,
1068 pub b: f64,
1069 pub r1: f64,
1070 pub r2: f64,
1071 pub dt: f64,
1072}
1073
1074impl MihalasNieburNeuron {
1075 pub fn new() -> Self {
1076 Self {
1077 v: 0.0,
1078 theta: 1.0,
1079 i1: 0.0,
1080 i2: 0.0,
1081 v_rest: 0.0,
1082 v_reset: 0.0,
1083 theta_reset: 1.0,
1084 theta_inf: 1.0,
1085 tau_v: 10.0,
1086 tau_theta: 100.0,
1087 tau_1: 10.0,
1088 tau_2: 200.0,
1089 a: 0.0,
1090 b: 0.0,
1091 r1: 0.0,
1092 r2: 0.0,
1093 dt: 1.0,
1094 }
1095 }
1096 pub fn step(&mut self, current: f64) -> i32 {
1097 self.v += (-(self.v - self.v_rest) + self.i1 + self.i2 + current) / self.tau_v * self.dt;
1098 self.theta += self.a * (self.v - self.v_rest)
1099 + (self.theta_inf - self.theta) / self.tau_theta * self.dt;
1100 self.i1 += -self.i1 / self.tau_1 * self.dt;
1101 self.i2 += -self.i2 / self.tau_2 * self.dt;
1102 if self.v >= self.theta {
1103 self.v = self.v_reset + self.b * (self.v - self.v_rest);
1104 self.theta = self.theta_reset.max(self.theta);
1105 self.i1 += self.r1;
1106 self.i2 += self.r2;
1107 1
1108 } else {
1109 0
1110 }
1111 }
1112 pub fn reset(&mut self) {
1113 self.v = self.v_rest;
1114 self.theta = self.theta_reset;
1115 self.i1 = 0.0;
1116 self.i2 = 0.0;
1117 }
1118}
1119impl Default for MihalasNieburNeuron {
1120 fn default() -> Self {
1121 Self::new()
1122 }
1123}
1124
1125#[derive(Clone, Debug)]
1127pub struct GLIFNeuron {
1128 pub v: f64,
1129 pub theta: f64,
1130 pub theta_inf: f64,
1131 pub i_asc1: f64,
1132 pub i_asc2: f64,
1133 pub v_rest: f64,
1134 pub v_reset: f64,
1135 pub tau_m: f64,
1136 pub tau_theta: f64,
1137 pub tau_asc1: f64,
1138 pub tau_asc2: f64,
1139 pub a_theta: f64,
1140 pub delta_theta: f64,
1141 pub r_asc1: f64,
1142 pub r_asc2: f64,
1143 pub resistance: f64,
1144 pub dt: f64,
1145}
1146
1147impl GLIFNeuron {
1148 pub fn new() -> Self {
1149 Self {
1150 v: -70.0,
1151 theta: -50.0,
1152 theta_inf: -50.0,
1153 i_asc1: 0.0,
1154 i_asc2: 0.0,
1155 v_rest: -70.0,
1156 v_reset: -70.0,
1157 tau_m: 10.0,
1158 tau_theta: 100.0,
1159 tau_asc1: 10.0,
1160 tau_asc2: 200.0,
1161 a_theta: 0.01,
1162 delta_theta: 2.0,
1163 r_asc1: 1.0,
1164 r_asc2: 0.5,
1165 resistance: 1.0,
1166 dt: 1.0,
1167 }
1168 }
1169 pub fn step(&mut self, current: f64) -> i32 {
1170 self.v += (-(self.v - self.v_rest) + self.resistance * current + self.i_asc1 + self.i_asc2)
1171 / self.tau_m
1172 * self.dt;
1173 self.theta += self.a_theta * (self.v - self.v_rest)
1174 + (self.theta_inf - self.theta) / self.tau_theta * self.dt;
1175 self.i_asc1 *= (-self.dt / self.tau_asc1).exp();
1176 self.i_asc2 *= (-self.dt / self.tau_asc2).exp();
1177 if self.v >= self.theta {
1178 self.v = self.v_reset;
1179 self.theta += self.delta_theta;
1180 self.i_asc1 += self.r_asc1;
1181 self.i_asc2 += self.r_asc2;
1182 1
1183 } else {
1184 0
1185 }
1186 }
1187 pub fn reset(&mut self) {
1188 self.v = self.v_rest;
1189 self.theta = self.theta_inf;
1190 self.i_asc1 = 0.0;
1191 self.i_asc2 = 0.0;
1192 }
1193}
1194impl Default for GLIFNeuron {
1195 fn default() -> Self {
1196 Self::new()
1197 }
1198}
1199
1200#[derive(Clone, Debug)]
1202pub struct GIFPopulationNeuron {
1203 pub v: f64,
1204 pub theta: f64,
1205 pub eta: f64,
1206 pub tau_m: f64,
1207 pub tau_eta: f64,
1208 pub delta_v: f64,
1209 pub lambda_0: f64,
1210 pub eta_increment: f64,
1211 pub v_rest: f64,
1212 pub v_reset: f64,
1213 pub dt: f64,
1214 rng: Xoshiro256PlusPlus,
1215}
1216
1217impl GIFPopulationNeuron {
1218 pub fn new(seed: u64) -> Self {
1219 Self {
1220 v: -65.0,
1221 theta: -50.0,
1222 eta: 0.0,
1223 tau_m: 20.0,
1224 tau_eta: 100.0,
1225 delta_v: 2.0,
1226 lambda_0: 0.001,
1227 eta_increment: 5.0,
1228 v_rest: -65.0,
1229 v_reset: -65.0,
1230 dt: 0.5,
1231 rng: Xoshiro256PlusPlus::seed_from_u64(seed),
1232 }
1233 }
1234 pub fn step(&mut self, current: f64) -> i32 {
1235 self.v += (-(self.v - self.v_rest) - self.eta + current) / self.tau_m * self.dt;
1237 self.eta *= (-self.dt / self.tau_eta).exp();
1239 let exponent = ((self.v - self.theta) / self.delta_v).min(20.0);
1241 let hazard = self.lambda_0 * exponent.exp();
1242 let p_spike = 1.0 - (-hazard * self.dt).exp();
1244 if self.rng.random::<f64>() < p_spike {
1245 self.v = self.v_reset;
1246 self.eta += self.eta_increment;
1247 1
1248 } else {
1249 0
1250 }
1251 }
1252 pub fn reset(&mut self) {
1253 self.v = -65.0;
1254 self.eta = 0.0;
1255 }
1256}
1257
1258#[derive(Clone, Debug)]
1260pub struct AvRonCardiacNeuron {
1261 pub v: f64,
1262 pub h: f64,
1263 pub n: f64,
1264 pub s: f64,
1265 pub g_na: f64,
1266 pub g_k: f64,
1267 pub g_s: f64,
1268 pub g_l: f64,
1269 pub e_na: f64,
1270 pub e_k: f64,
1271 pub e_s: f64,
1272 pub e_l: f64,
1273 pub dt: f64,
1274 pub v_threshold: f64,
1275}
1276
1277impl AvRonCardiacNeuron {
1278 pub fn new() -> Self {
1279 Self {
1280 v: -60.0,
1281 h: 0.6,
1282 n: 0.3,
1283 s: 0.5,
1284 g_na: 80.0,
1285 g_k: 40.0,
1286 g_s: 20.0,
1287 g_l: 0.1,
1288 e_na: 40.0,
1289 e_k: -80.0,
1290 e_s: -25.0,
1291 e_l: -60.0,
1292 dt: 0.02,
1293 v_threshold: -20.0,
1294 }
1295 }
1296 pub fn step(&mut self, current: f64) -> i32 {
1297 let v_prev = self.v;
1298 let m_inf = 1.0 / (1.0 + (-(self.v + 40.0) / 7.0).exp());
1299 let h_inf = 1.0 / (1.0 + ((self.v + 45.0) / 5.0).exp());
1300 let n_inf = 1.0 / (1.0 + (-(self.v + 40.0) / 15.0).exp());
1301 let s_inf = 1.0 / (1.0 + ((self.v + 35.0) / 3.0).exp());
1302 let tau_h = 1.0 + 12.0 / (1.0 + ((self.v + 50.0) / 8.0).exp());
1303 let tau_n = 1.0 + 8.0 / (1.0 + ((self.v + 35.0) / 8.0).exp());
1304 let tau_s = 200.0 + 1000.0 / (1.0 + ((self.v + 30.0) / 5.0).exp());
1305 self.h += (h_inf - self.h) / tau_h * self.dt;
1306 self.n += (n_inf - self.n) / tau_n * self.dt;
1307 self.s += (s_inf - self.s) / tau_s * self.dt;
1308 let i_na = self.g_na * m_inf.powi(3) * self.h * (self.v - self.e_na);
1309 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
1310 let i_s = self.g_s * self.s * (self.v - self.e_s);
1311 let i_l = self.g_l * (self.v - self.e_l);
1312 self.v += (-i_na - i_k - i_s - i_l + current) * self.dt;
1313 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1314 1
1315 } else {
1316 0
1317 }
1318 }
1319 pub fn reset(&mut self) {
1320 self.v = -60.0;
1321 self.h = 0.6;
1322 self.n = 0.3;
1323 self.s = 0.5;
1324 }
1325}
1326impl Default for AvRonCardiacNeuron {
1327 fn default() -> Self {
1328 Self::new()
1329 }
1330}
1331
1332#[derive(Clone, Debug)]
1334pub struct DurstewitzDopamineNeuron {
1335 pub v: f64,
1336 pub h_na: f64,
1337 pub n_k: f64,
1338 pub g_na: f64,
1339 pub g_k: f64,
1340 pub g_nmda: f64,
1341 pub g_l: f64,
1342 pub e_na: f64,
1343 pub e_k: f64,
1344 pub e_nmda: f64,
1345 pub e_l: f64,
1346 pub mg: f64,
1347 pub d1_level: f64,
1348 pub g_nmda_scale: f64,
1349 pub g_k_scale: f64,
1350 pub v_shift_na: f64,
1351 pub dt: f64,
1352 pub v_threshold: f64,
1353}
1354
1355impl DurstewitzDopamineNeuron {
1356 pub fn new() -> Self {
1357 Self {
1358 v: -65.0,
1359 h_na: 0.7,
1360 n_k: 0.2,
1361 g_na: 45.0,
1362 g_k: 18.0,
1363 g_nmda: 0.5,
1364 g_l: 0.02,
1365 e_na: 55.0,
1366 e_k: -80.0,
1367 e_nmda: 0.0,
1368 e_l: -65.0,
1369 mg: 1.0,
1370 d1_level: 0.0,
1371 g_nmda_scale: 2.5,
1372 g_k_scale: 1.5,
1373 v_shift_na: -5.0,
1374 dt: 0.05,
1375 v_threshold: -20.0,
1376 }
1377 }
1378 pub fn step(&mut self, current: f64) -> i32 {
1379 let v_prev = self.v;
1380 let shift = self.d1_level * self.v_shift_na;
1381 let m_inf = 1.0 / (1.0 + (-(self.v + 30.0 + shift) / 9.5).exp());
1382 let h_inf = 1.0 / (1.0 + ((self.v + 53.0) / 7.0).exp());
1383 let n_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 10.0).exp());
1384 let tau_h = 0.5 + 14.0 / (1.0 + ((self.v + 50.0) / 12.0).exp());
1385 let tau_n = 1.0 + 11.0 / (1.0 + ((self.v + 40.0) / 10.0).exp());
1386 self.h_na += (h_inf - self.h_na) / tau_h * self.dt;
1387 self.n_k += (n_inf - self.n_k) / tau_n * self.dt;
1388 let g_eff_nmda = self.g_nmda * (1.0 + self.d1_level * (self.g_nmda_scale - 1.0));
1389 let g_eff_k = self.g_k * (1.0 + self.d1_level * (self.g_k_scale - 1.0));
1390 let mg_block = 1.0 / (1.0 + self.mg * (-0.062 * self.v).exp() / 3.57);
1391 let i_na = self.g_na * m_inf.powi(3) * self.h_na * (self.v - self.e_na);
1392 let i_k = g_eff_k * self.n_k.powi(4) * (self.v - self.e_k);
1393 let i_nmda = g_eff_nmda * mg_block * (self.v - self.e_nmda);
1394 let i_l = self.g_l * (self.v - self.e_l);
1395 self.v += (-i_na - i_k - i_nmda - i_l + current) * self.dt;
1396 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1397 1
1398 } else {
1399 0
1400 }
1401 }
1402 pub fn reset(&mut self) {
1403 self.v = -65.0;
1404 self.h_na = 0.7;
1405 self.n_k = 0.2;
1406 }
1407}
1408impl Default for DurstewitzDopamineNeuron {
1409 fn default() -> Self {
1410 Self::new()
1411 }
1412}
1413
1414#[derive(Clone, Debug)]
1416pub struct HillTononiNeuron {
1417 pub v: f64,
1418 pub h_na: f64,
1419 pub n_k: f64,
1420 pub m_h: f64,
1421 pub h_t: f64,
1422 pub na_i: f64,
1423 pub g_na: f64,
1424 pub g_k: f64,
1425 pub g_h: f64,
1426 pub g_t: f64,
1427 pub g_kna: f64,
1428 pub g_l: f64,
1429 pub e_na: f64,
1430 pub e_k: f64,
1431 pub e_h: f64,
1432 pub e_ca: f64,
1433 pub e_l: f64,
1434 pub na_pump_max: f64,
1435 pub na_eq: f64,
1436 pub dt: f64,
1437 pub v_threshold: f64,
1438}
1439
1440impl HillTononiNeuron {
1441 pub fn new() -> Self {
1442 Self {
1443 v: -65.0,
1444 h_na: 0.6,
1445 n_k: 0.3,
1446 m_h: 0.0,
1447 h_t: 0.9,
1448 na_i: 5.0,
1449 g_na: 50.0,
1450 g_k: 5.0,
1451 g_h: 1.0,
1452 g_t: 3.0,
1453 g_kna: 1.33,
1454 g_l: 0.02,
1455 e_na: 50.0,
1456 e_k: -90.0,
1457 e_h: -43.0,
1458 e_ca: 120.0,
1459 e_l: -70.0,
1460 na_pump_max: 20.0,
1461 na_eq: 9.5,
1462 dt: 0.05,
1463 v_threshold: -20.0,
1464 }
1465 }
1466 pub fn step(&mut self, current: f64) -> i32 {
1467 let v_prev = self.v;
1468 let m_na_inf = 1.0 / (1.0 + (-(self.v + 38.0) / 10.0).exp());
1469 let h_na_inf = 1.0 / (1.0 + ((self.v + 43.0) / 6.0).exp());
1470 let n_k_inf = 1.0 / (1.0 + (-(self.v + 27.0) / 11.5).exp());
1471 let m_h_inf = 1.0 / (1.0 + ((self.v + 75.0) / 5.5).exp());
1472 let m_t_inf = 1.0 / (1.0 + (-(self.v + 59.0) / 6.2).exp());
1473 let h_t_inf = 1.0 / (1.0 + ((self.v + 83.0) / 4.0).exp());
1474 let w_kna = 0.37 / (1.0 + (38.7 / self.na_i.max(0.01)).powf(3.5));
1475 let tau_h_na = (1.0 + 10.0 / (1.0 + ((self.v + 40.0) / 10.0).exp())).max(0.1);
1476 let tau_n_k = (5.0 + 47.0 * (-(((self.v + 50.0) / 25.0).powi(2))).exp()).max(0.1);
1478 let tau_m_h = (20.0
1479 + 1000.0 / (((self.v + 71.5) / 14.2).exp() + (-(self.v + 89.0) / 11.6).exp()))
1480 .max(1.0);
1481 let tau_h_t = if self.v < -81.0 {
1482 (30.8 + 211.4 * ((self.v + 115.2) / 5.0).exp() / (1.0 + ((self.v + 86.0) / 3.2).exp()))
1483 .max(0.1)
1484 } else {
1485 10.0
1486 };
1487 self.h_na += (h_na_inf - self.h_na) / tau_h_na * self.dt;
1488 self.n_k += (n_k_inf - self.n_k) / tau_n_k * self.dt;
1489 self.m_h += (m_h_inf - self.m_h) / tau_m_h * self.dt;
1490 self.h_t += (h_t_inf - self.h_t) / tau_h_t * self.dt;
1491 let i_na = self.g_na * m_na_inf.powi(3) * self.h_na * (self.v - self.e_na);
1492 let i_k = self.g_k * self.n_k.powi(4) * (self.v - self.e_k);
1493 let i_h = self.g_h * self.m_h * (self.v - self.e_h);
1494 let i_t = self.g_t * m_t_inf.powi(2) * self.h_t * (self.v - self.e_ca);
1495 let i_kna = self.g_kna * w_kna * (self.v - self.e_k);
1496 let i_l = self.g_l * (self.v - self.e_l);
1497 self.v += (-i_na - i_k - i_h - i_t - i_kna - i_l + current) * self.dt;
1498 self.na_i = (self.na_i
1499 + (-0.001 * i_na - self.na_pump_max * (self.na_i / (self.na_i + self.na_eq)))
1500 * self.dt)
1501 .max(0.0);
1502 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1503 1
1504 } else {
1505 0
1506 }
1507 }
1508 pub fn reset(&mut self) {
1509 self.v = -65.0;
1510 self.h_na = 0.6;
1511 self.n_k = 0.3;
1512 self.m_h = 0.0;
1513 self.h_t = 0.9;
1514 self.na_i = 5.0;
1515 }
1516}
1517impl Default for HillTononiNeuron {
1518 fn default() -> Self {
1519 Self::new()
1520 }
1521}
1522
1523#[derive(Clone, Debug)]
1525pub struct BertramPhantomBurster {
1526 pub v: f64,
1527 pub s1: f64,
1528 pub s2: f64,
1529 pub g_ca: f64,
1530 pub g_k: f64,
1531 pub g_s1: f64,
1532 pub g_s2: f64,
1533 pub g_l: f64,
1534 pub e_ca: f64,
1535 pub e_k: f64,
1536 pub e_l: f64,
1537 pub c_m: f64,
1538 pub v_m: f64,
1539 pub s_m: f64,
1540 pub v_n: f64,
1541 pub s_n: f64,
1542 pub v_s1: f64,
1543 pub s_s1: f64,
1544 pub v_s2: f64,
1545 pub s_s2: f64,
1546 pub tau_s1: f64,
1547 pub tau_s2: f64,
1548 pub dt: f64,
1549 pub v_threshold: f64,
1550}
1551
1552impl BertramPhantomBurster {
1553 pub fn new() -> Self {
1554 Self {
1555 v: -50.0,
1556 s1: 0.1,
1557 s2: 0.1,
1558 g_ca: 3.6,
1559 g_k: 10.0,
1560 g_s1: 4.0,
1561 g_s2: 4.0,
1562 g_l: 0.2,
1563 e_ca: 25.0,
1564 e_k: -75.0,
1565 e_l: -40.0,
1566 c_m: 5.3,
1567 v_m: -20.0,
1568 s_m: 12.0,
1569 v_n: -16.0,
1570 s_n: 5.6,
1571 v_s1: -40.0,
1572 s_s1: 10.0,
1573 v_s2: -42.0,
1574 s_s2: 0.4,
1575 tau_s1: 20000.0,
1576 tau_s2: 100000.0,
1577 dt: 0.5,
1578 v_threshold: -20.0,
1579 }
1580 }
1581 pub fn step(&mut self, current: f64) -> i32 {
1582 let v_prev = self.v;
1583 let m_inf = 1.0 / (1.0 + (-(self.v - self.v_m) / self.s_m).exp());
1584 let n_inf = 1.0 / (1.0 + (-(self.v - self.v_n) / self.s_n).exp());
1585 let s1_inf = 1.0 / (1.0 + (-(self.v - self.v_s1) / self.s_s1).exp());
1586 let s2_inf = 1.0 / (1.0 + (-(self.v - self.v_s2) / self.s_s2).exp());
1587 let i_ca = self.g_ca * m_inf * (self.v - self.e_ca);
1588 let i_k = self.g_k * n_inf * (self.v - self.e_k);
1589 let i_s1 = self.g_s1 * self.s1 * (self.v - self.e_k);
1590 let i_s2 = self.g_s2 * self.s2 * (self.v - self.e_k);
1591 let i_l = self.g_l * (self.v - self.e_l);
1592 self.v += (-i_ca - i_k - i_s1 - i_s2 - i_l + current) / self.c_m * self.dt;
1593 self.s1 += (s1_inf - self.s1) / self.tau_s1 * self.dt;
1594 self.s2 += (s2_inf - self.s2) / self.tau_s2 * self.dt;
1595 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1596 1
1597 } else {
1598 0
1599 }
1600 }
1601 pub fn reset(&mut self) {
1602 self.v = -50.0;
1603 self.s1 = 0.1;
1604 self.s2 = 0.1;
1605 }
1606}
1607impl Default for BertramPhantomBurster {
1608 fn default() -> Self {
1609 Self::new()
1610 }
1611}
1612
1613#[derive(Clone, Debug)]
1615pub struct YamadaNeuron {
1616 pub v: f64,
1617 pub n: f64,
1618 pub q: f64,
1619 pub g_na: f64,
1620 pub g_k: f64,
1621 pub g_q: f64,
1622 pub g_l: f64,
1623 pub e_na: f64,
1624 pub e_k: f64,
1625 pub e_q: f64,
1626 pub e_l: f64,
1627 pub tau_q: f64,
1628 pub dt: f64,
1629 pub v_threshold: f64,
1630}
1631
1632impl YamadaNeuron {
1633 pub fn new() -> Self {
1634 Self {
1635 v: -60.0,
1636 n: 0.1,
1637 q: 0.0,
1638 g_na: 20.0,
1639 g_k: 10.0,
1640 g_q: 5.0,
1641 g_l: 0.5,
1642 e_na: 60.0,
1643 e_k: -80.0,
1644 e_q: -80.0,
1645 e_l: -60.0,
1646 tau_q: 300.0,
1647 dt: 0.05,
1648 v_threshold: -20.0,
1649 }
1650 }
1651 pub fn step(&mut self, current: f64) -> i32 {
1652 let v_prev = self.v;
1653 let m_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 9.5).exp());
1654 let n_inf = 1.0 / (1.0 + (-(self.v + 30.0) / 10.0).exp());
1655 let q_inf = 1.0 / (1.0 + (-(self.v + 50.0) / 10.0).exp());
1656 let tau_n = 1.0 + 7.5 / (1.0 + ((self.v + 40.0) / 12.0).exp());
1657 let i_na = self.g_na * m_inf.powi(3) * (1.0 - self.n) * (self.v - self.e_na);
1658 let i_k = self.g_k * self.n.powi(4) * (self.v - self.e_k);
1659 let i_q = self.g_q * self.q * (self.v - self.e_q);
1660 let i_l = self.g_l * (self.v - self.e_l);
1661 self.v += (-i_na - i_k - i_q - i_l + current) * self.dt;
1662 self.n += (n_inf - self.n) / tau_n * self.dt;
1663 self.q += (q_inf - self.q) / self.tau_q * self.dt;
1664 if self.v >= self.v_threshold && v_prev < self.v_threshold {
1665 1
1666 } else {
1667 0
1668 }
1669 }
1670 pub fn reset(&mut self) {
1671 self.v = -60.0;
1672 self.n = 0.1;
1673 self.q = 0.0;
1674 }
1675}
1676impl Default for YamadaNeuron {
1677 fn default() -> Self {
1678 Self::new()
1679 }
1680}
1681
1682#[cfg(test)]
1683mod tests {
1684 use super::*;
1685
1686 #[test]
1687 fn hh_fires() {
1688 let mut n = HodgkinHuxleyNeuron::new();
1689 let t: i32 = (0..100).map(|_| n.step(10.0)).sum();
1690 assert!(t > 0);
1691 }
1692 #[test]
1693 fn traub_fires() {
1694 let mut n = TraubMilesNeuron::new();
1695 let t: i32 = (0..200).map(|_| n.step(5.0)).sum();
1696 assert!(t > 0);
1697 }
1698 #[test]
1699 fn wb_fires() {
1700 let mut n = WangBuzsakiNeuron::new();
1701 let t: i32 = (0..200).map(|_| n.step(2.0)).sum();
1702 assert!(t > 0);
1703 }
1704 #[test]
1705 fn cs_fires() {
1706 let mut n = ConnorStevensNeuron::new();
1707 let t: i32 = (0..200).map(|_| n.step(10.0)).sum();
1708 assert!(t > 0);
1709 }
1710 #[test]
1711 fn destexhe_fires() {
1712 let mut n = DestexheThalamicNeuron::new();
1713 let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
1714 assert!(t > 0);
1715 }
1716 #[test]
1717 fn hb_fires() {
1718 let mut n = HuberBraunNeuron::new();
1719 let t: i32 = (0..5000).map(|_| n.step(10.0)).sum();
1720 assert!(t > 0);
1721 }
1722 #[test]
1723 fn golomb_fires() {
1724 let mut n = GolombFSNeuron::new();
1725 let t: i32 = (0..2000).map(|_| n.step(200.0)).sum();
1726 assert!(t > 0);
1727 }
1728 #[test]
1729 fn pospischil_fires() {
1730 let mut n = PospischilNeuron::new();
1731 let t: i32 = (0..200).map(|_| n.step(5.0)).sum();
1732 assert!(t > 0);
1733 }
1734 #[test]
1735 fn mainen_fires() {
1736 let mut n = MainenSejnowskiNeuron::new();
1737 let t: i32 = (0..5000).map(|_| n.step(500.0)).sum();
1738 assert!(t > 0);
1739 }
1740 #[test]
1741 fn purkinje_fires() {
1742 let mut n = DeSchutterPurkinjeNeuron::new();
1743 let t: i32 = (0..5000).map(|_| n.step(200.0)).sum();
1744 assert!(t > 0);
1745 }
1746 #[test]
1747 fn plant_r15_fires() {
1748 let mut n = PlantR15Neuron::new();
1749 let t: i32 = (0..500).map(|_| n.step(2.0)).sum();
1750 assert!(t > 0);
1751 }
1752 #[test]
1753 fn prescott_fires() {
1754 let mut n = PrescottNeuron::new();
1755 let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
1756 assert!(t > 0);
1757 }
1758 #[test]
1759 fn mn_fires() {
1760 let mut n = MihalasNieburNeuron::new();
1761 let t: i32 = (0..100).map(|_| n.step(5.0)).sum();
1762 assert!(t > 0);
1763 }
1764 #[test]
1765 fn glif_fires() {
1766 let mut n = GLIFNeuron::new();
1767 let t: i32 = (0..200).map(|_| n.step(30.0)).sum();
1768 assert!(t > 0);
1769 }
1770 #[test]
1771 fn gif_pop_fires() {
1772 let mut n = GIFPopulationNeuron::new(42);
1773 let t: i32 = (0..1000).map(|_| n.step(30.0)).sum();
1774 assert!(t > 0);
1775 }
1776 #[test]
1777 fn avron_fires() {
1778 let mut n = AvRonCardiacNeuron::new();
1779 let t: i32 = (0..2000).map(|_| n.step(5.0)).sum();
1780 assert!(t > 0);
1781 }
1782 #[test]
1783 fn durstewitz_fires() {
1784 let mut n = DurstewitzDopamineNeuron::new();
1785 let t: i32 = (0..1000).map(|_| n.step(3.0)).sum();
1786 assert!(t > 0);
1787 }
1788 #[test]
1789 fn hill_tononi_fires() {
1790 let mut n = HillTononiNeuron::new();
1791 let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
1792 assert!(t > 0);
1793 }
1794 #[test]
1795 fn bertram_fires() {
1796 let mut n = BertramPhantomBurster::new();
1797 let t: i32 = (0..10000).map(|_| n.step(200.0)).sum();
1798 assert!(t > 0);
1799 }
1800 #[test]
1801 fn yamada_fires() {
1802 let mut n = YamadaNeuron::new();
1803 let t: i32 = (0..2000).map(|_| n.step(5.0)).sum();
1804 assert!(t > 0);
1805 }
1806
1807 #[test]
1811 fn hh_silent_without_input() {
1812 let mut n = HodgkinHuxleyNeuron::new();
1813 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
1814 assert_eq!(t, 0);
1815 }
1816 #[test]
1817 fn hh_reset_clears_state() {
1818 let mut n = HodgkinHuxleyNeuron::new();
1819 for _ in 0..100 {
1820 n.step(10.0);
1821 }
1822 n.reset();
1823 assert!((n.v - (-65.0)).abs() < 1e-10);
1824 assert!((n.m - 0.05).abs() < 1e-10);
1825 assert!((n.h - 0.6).abs() < 1e-10);
1826 assert!((n.n - 0.32).abs() < 1e-10);
1827 }
1828 #[test]
1829 fn hh_extreme_input_bounded() {
1830 let mut n = HodgkinHuxleyNeuron::new();
1831 for _ in 0..200 {
1832 n.step(1e4);
1833 }
1834 assert!(n.v.is_finite());
1835 }
1836 #[test]
1837 fn hh_gates_bounded() {
1838 let mut n = HodgkinHuxleyNeuron::new();
1839 for _ in 0..500 {
1840 n.step(10.0);
1841 }
1842 assert!(n.m >= 0.0 && n.m <= 1.0, "m={}", n.m);
1843 assert!(n.h >= 0.0 && n.h <= 1.0, "h={}", n.h);
1844 assert!(n.n >= 0.0 && n.n <= 1.0, "n={}", n.n);
1845 }
1846 #[test]
1847 fn hh_negative_input_no_crash() {
1848 let mut n = HodgkinHuxleyNeuron::new();
1849 for _ in 0..200 {
1850 n.step(-20.0);
1851 }
1852 assert!(n.v.is_finite());
1853 }
1854 #[test]
1855 fn hh_nan_input_no_panic() {
1856 let mut n = HodgkinHuxleyNeuron::new();
1857 n.step(f64::NAN);
1858 }
1859 #[test]
1860 fn hh_sodium_potassium_opposition() {
1861 let mut n = HodgkinHuxleyNeuron::new();
1863 for _ in 0..50 {
1864 n.step(10.0);
1865 }
1866 assert!(n.n > 0.32, "K activation n should increase during spiking");
1868 }
1869
1870 #[test]
1872 fn traub_silent_without_input() {
1873 let mut n = TraubMilesNeuron::new();
1874 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
1875 assert_eq!(t, 0);
1876 }
1877 #[test]
1878 fn traub_reset_clears_state() {
1879 let mut n = TraubMilesNeuron::new();
1880 for _ in 0..100 {
1881 n.step(5.0);
1882 }
1883 n.reset();
1884 assert!((n.v - (-67.0)).abs() < 1e-10);
1885 }
1886 #[test]
1887 fn traub_extreme_bounded() {
1888 let mut n = TraubMilesNeuron::new();
1889 for _ in 0..200 {
1890 n.step(1e4);
1891 }
1892 assert!(n.v.is_finite());
1893 }
1894 #[test]
1895 fn traub_gates_bounded() {
1896 let mut n = TraubMilesNeuron::new();
1897 for _ in 0..500 {
1898 n.step(5.0);
1899 }
1900 assert!(n.m >= 0.0 && n.m <= 1.01);
1901 assert!(n.h >= 0.0 && n.h <= 1.01);
1902 assert!(n.n >= 0.0 && n.n <= 1.01);
1903 }
1904 #[test]
1905 fn traub_weak_negative_no_crash() {
1906 let mut n = TraubMilesNeuron::new();
1907 for _ in 0..200 {
1908 n.step(-5.0);
1909 }
1910 assert!(n.v.is_finite());
1911 }
1912 #[test]
1913 fn traub_nan_no_panic() {
1914 let mut n = TraubMilesNeuron::new();
1915 n.step(f64::NAN);
1916 }
1917
1918 #[test]
1920 fn wb_silent_without_input() {
1921 let mut n = WangBuzsakiNeuron::new();
1922 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
1923 assert_eq!(t, 0);
1924 }
1925 #[test]
1926 fn wb_reset_clears_state() {
1927 let mut n = WangBuzsakiNeuron::new();
1928 for _ in 0..100 {
1929 n.step(2.0);
1930 }
1931 n.reset();
1932 assert!((n.v - (-65.0)).abs() < 1e-10);
1933 }
1934 #[test]
1935 fn wb_extreme_bounded() {
1936 let mut n = WangBuzsakiNeuron::new();
1937 for _ in 0..200 {
1938 n.step(1e4);
1939 }
1940 assert!(n.v.is_finite());
1941 }
1942 #[test]
1943 fn wb_fast_spiking_high_rate() {
1944 let mut n = WangBuzsakiNeuron::new();
1946 let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
1947 assert!(t > 10, "WB FS should produce many spikes, got {}", t);
1948 }
1949 #[test]
1950 fn wb_gates_bounded() {
1951 let mut n = WangBuzsakiNeuron::new();
1952 for _ in 0..500 {
1953 n.step(2.0);
1954 }
1955 assert!(n.h >= 0.0 && n.h <= 1.0);
1956 assert!(n.n >= 0.0 && n.n <= 1.0);
1957 }
1958 #[test]
1959 fn wb_negative_no_crash() {
1960 let mut n = WangBuzsakiNeuron::new();
1961 for _ in 0..200 {
1962 n.step(-10.0);
1963 }
1964 assert!(n.v.is_finite());
1965 }
1966 #[test]
1967 fn wb_nan_no_panic() {
1968 let mut n = WangBuzsakiNeuron::new();
1969 n.step(f64::NAN);
1970 }
1971
1972 #[test]
1974 fn cs_silent_without_input() {
1975 let mut n = ConnorStevensNeuron::new();
1976 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
1977 assert_eq!(t, 0);
1978 }
1979 #[test]
1980 fn cs_reset_clears_state() {
1981 let mut n = ConnorStevensNeuron::new();
1982 for _ in 0..100 {
1983 n.step(10.0);
1984 }
1985 n.reset();
1986 assert!((n.v - (-68.0)).abs() < 1e-10);
1987 }
1988 #[test]
1989 fn cs_extreme_bounded() {
1990 let mut n = ConnorStevensNeuron::new();
1991 for _ in 0..200 {
1992 n.step(1e4);
1993 }
1994 assert!(n.v.is_finite());
1995 }
1996 #[test]
1997 fn cs_a_type_delays_spike() {
1998 let mut n_with_a = ConnorStevensNeuron::new();
2001 let mut n_no_a = ConnorStevensNeuron::new();
2002 n_no_a.g_a = 0.0;
2003 let spikes_with: i32 = (0..50).map(|_| n_with_a.step(8.0)).sum();
2004 let spikes_no: i32 = (0..50).map(|_| n_no_a.step(8.0)).sum();
2005 assert!(
2007 spikes_no >= spikes_with,
2008 "without A-type K, should fire more: no_a={} vs with_a={}",
2009 spikes_no,
2010 spikes_with
2011 );
2012 }
2013 #[test]
2014 fn cs_gates_bounded() {
2015 let mut n = ConnorStevensNeuron::new();
2016 for _ in 0..500 {
2017 n.step(10.0);
2018 }
2019 assert!(n.a >= 0.0 && n.a <= 1.5, "a={}", n.a); assert!(n.b >= 0.0 && n.b <= 1.0, "b={}", n.b);
2021 }
2022 #[test]
2023 fn cs_negative_no_crash() {
2024 let mut n = ConnorStevensNeuron::new();
2025 for _ in 0..200 {
2026 n.step(-20.0);
2027 }
2028 assert!(n.v.is_finite());
2029 }
2030
2031 #[test]
2033 fn destexhe_no_crash_zero_input() {
2034 let mut n = DestexheThalamicNeuron::new();
2035 let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
2036 assert!(n.v.is_finite());
2038 }
2039 #[test]
2040 fn destexhe_reset_clears_state() {
2041 let mut n = DestexheThalamicNeuron::new();
2042 for _ in 0..200 {
2043 n.step(5.0);
2044 }
2045 n.reset();
2046 assert!((n.v - (-65.0)).abs() < 1e-10);
2047 }
2048 #[test]
2049 fn destexhe_extreme_bounded() {
2050 let mut n = DestexheThalamicNeuron::new();
2051 for _ in 0..200 {
2052 n.step(1e4);
2053 }
2054 assert!(n.v.is_finite());
2055 }
2056 #[test]
2057 fn destexhe_t_current_rebound() {
2058 let v_hyp = -90.0_f64;
2062 let h_t_inf = 1.0 / (1.0 + ((v_hyp + 81.0) / 4.0).exp());
2063 assert!(h_t_inf > 0.9, "h_t_inf should be ~1 at V=-90: {}", h_t_inf);
2064
2065 let mut n = DestexheThalamicNeuron::new();
2067 for _ in 0..500 {
2068 n.step(-5.0);
2069 }
2070 let v_before_release = n.v;
2071 for _ in 0..500 {
2072 n.step(0.0);
2073 }
2074 assert!(n.v.is_finite());
2075 assert!(
2076 n.v > v_before_release,
2077 "V should increase after release (rebound)"
2078 );
2079 }
2080 #[test]
2081 fn destexhe_negative_no_crash() {
2082 let mut n = DestexheThalamicNeuron::new();
2083 for _ in 0..200 {
2084 n.step(-20.0);
2085 }
2086 assert!(n.v.is_finite());
2087 }
2088 #[test]
2089 fn destexhe_nan_no_panic() {
2090 let mut n = DestexheThalamicNeuron::new();
2091 n.step(f64::NAN);
2092 }
2093
2094 #[test]
2096 fn hb_silent_without_input() {
2097 let mut n = HuberBraunNeuron::new();
2098 let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
2099 assert!(n.v.is_finite());
2101 }
2102 #[test]
2103 fn hb_reset_clears_state() {
2104 let mut n = HuberBraunNeuron::new();
2105 for _ in 0..200 {
2106 n.step(10.0);
2107 }
2108 n.reset();
2109 assert!((n.v - (-50.0)).abs() < 1e-10);
2110 }
2111 #[test]
2112 fn hb_extreme_bounded() {
2113 let mut n = HuberBraunNeuron::new();
2114 for _ in 0..200 {
2115 n.step(1e4);
2116 }
2117 assert!(n.v.is_finite());
2118 }
2119 #[test]
2120 fn hb_negative_no_crash() {
2121 let mut n = HuberBraunNeuron::new();
2122 for _ in 0..500 {
2123 n.step(-10.0);
2124 }
2125 assert!(n.v.is_finite());
2126 }
2127 #[test]
2128 fn hb_nan_no_panic() {
2129 let mut n = HuberBraunNeuron::new();
2130 n.step(f64::NAN);
2131 }
2132
2133 #[test]
2135 fn golomb_silent_without_input() {
2136 let mut n = GolombFSNeuron::new();
2137 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
2138 assert_eq!(t, 0);
2139 }
2140 #[test]
2141 fn golomb_reset_clears_state() {
2142 let mut n = GolombFSNeuron::new();
2143 for _ in 0..100 {
2144 n.step(200.0);
2145 }
2146 n.reset();
2147 assert!((n.v - (-65.0)).abs() < 1e-10);
2148 }
2149 #[test]
2150 fn golomb_extreme_bounded() {
2151 let mut n = GolombFSNeuron::new();
2153 for _ in 0..200 {
2154 n.step(200.0);
2155 }
2156 assert!(n.v.is_finite());
2157 }
2158 #[test]
2159 fn golomb_kv3_enables_fast_spiking() {
2160 let mut n = GolombFSNeuron::new();
2162 let t: i32 = (0..5000).map(|_| n.step(300.0)).sum();
2163 assert!(t > 0, "Golomb FS should fire with strong input, got {}", t);
2164 }
2165 #[test]
2166 fn golomb_negative_no_crash() {
2167 let mut n = GolombFSNeuron::new();
2168 for _ in 0..200 {
2169 n.step(-100.0);
2170 }
2171 assert!(n.v.is_finite());
2172 }
2173 #[test]
2174 fn golomb_nan_no_panic() {
2175 let mut n = GolombFSNeuron::new();
2176 n.step(f64::NAN);
2177 }
2178
2179 #[test]
2181 fn pospischil_silent_without_input() {
2182 let mut n = PospischilNeuron::new();
2183 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
2184 assert_eq!(t, 0);
2185 }
2186 #[test]
2187 fn pospischil_reset_clears_state() {
2188 let mut n = PospischilNeuron::new();
2189 for _ in 0..100 {
2190 n.step(5.0);
2191 }
2192 n.reset();
2193 assert!((n.v - (-70.0)).abs() < 1e-10);
2194 }
2195 #[test]
2196 fn pospischil_moderate_input_stable() {
2197 let mut n = PospischilNeuron::new();
2198 for _ in 0..200 {
2199 n.step(10.0);
2200 }
2201 assert!(n.v.is_finite());
2202 }
2203 #[test]
2204 fn pospischil_m_current_present() {
2205 let mut n = PospischilNeuron::new();
2206 for _ in 0..200 {
2207 n.step(5.0);
2208 }
2209 assert!(n.p > 0.0, "M-current (p) should activate during spiking");
2210 }
2211 #[test]
2212 fn pospischil_negative_no_crash() {
2213 let mut n = PospischilNeuron::new();
2214 for _ in 0..200 {
2215 n.step(-10.0);
2216 }
2217 assert!(n.v.is_finite());
2218 }
2219 #[test]
2220 fn pospischil_nan_no_panic() {
2221 let mut n = PospischilNeuron::new();
2222 n.step(f64::NAN);
2223 }
2224
2225 #[test]
2227 fn mainen_stable_without_input() {
2228 let mut n = MainenSejnowskiNeuron::new();
2231 for _ in 0..500 {
2232 n.step(0.0);
2233 }
2234 assert!(n.vs.is_finite());
2235 assert!(n.va.is_finite());
2236 }
2237 #[test]
2238 fn mainen_reset_clears_state() {
2239 let mut n = MainenSejnowskiNeuron::new();
2240 for _ in 0..100 {
2241 n.step(500.0);
2242 }
2243 n.reset();
2244 assert!((n.vs - (-65.0)).abs() < 1e-10);
2245 assert!((n.va - (-65.0)).abs() < 1e-10);
2246 }
2247 #[test]
2248 fn mainen_moderate_input_stable() {
2249 let mut n = MainenSejnowskiNeuron::new();
2251 for _ in 0..200 {
2252 n.step(500.0);
2253 }
2254 let _ = n.vs; }
2258 #[test]
2259 fn mainen_two_compartments_coupled() {
2260 let n = MainenSejnowskiNeuron::new();
2261 assert!(n.kappa > 0.0, "coupling should be positive");
2263 }
2264 #[test]
2265 fn mainen_weak_negative_no_crash() {
2266 let mut n = MainenSejnowskiNeuron::new();
2267 for _ in 0..200 {
2268 n.step(-10.0);
2269 }
2270 assert!(n.vs.is_finite());
2272 }
2273 #[test]
2274 fn mainen_nan_no_panic() {
2275 let mut n = MainenSejnowskiNeuron::new();
2276 n.step(f64::NAN);
2277 }
2278
2279 #[test]
2281 fn purkinje_silent_without_input() {
2282 let mut n = DeSchutterPurkinjeNeuron::new();
2283 let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
2284 assert!(n.v.is_finite());
2286 }
2287 #[test]
2288 fn purkinje_reset_clears_state() {
2289 let mut n = DeSchutterPurkinjeNeuron::new();
2290 for _ in 0..100 {
2291 n.step(50.0);
2292 }
2293 n.reset();
2294 assert!((n.v - (-68.0)).abs() < 1e-10);
2295 assert!((n.ca - 0.0001).abs() < 1e-10);
2296 }
2297 #[test]
2298 fn purkinje_extreme_bounded() {
2299 let mut n = DeSchutterPurkinjeNeuron::new();
2300 for _ in 0..200 {
2301 n.step(1e4);
2302 }
2303 assert!(n.v.is_finite());
2304 }
2305 #[test]
2306 fn purkinje_ca_rises_with_spiking() {
2307 let mut n = DeSchutterPurkinjeNeuron::new();
2308 let ca_init = n.ca;
2309 for _ in 0..5000 {
2310 n.step(200.0);
2311 }
2312 assert!(n.ca > ca_init, "Ca²⁺ should rise during spiking");
2313 }
2314 #[test]
2315 fn purkinje_kca_activated_by_calcium() {
2316 let mut n = DeSchutterPurkinjeNeuron::new();
2317 for _ in 0..5000 {
2318 n.step(200.0);
2319 }
2320 assert!(
2321 n.q_kca > 0.0,
2322 "KCa should activate with Ca²⁺: q={}",
2323 n.q_kca
2324 );
2325 }
2326 #[test]
2327 fn purkinje_negative_no_crash() {
2328 let mut n = DeSchutterPurkinjeNeuron::new();
2329 for _ in 0..200 {
2330 n.step(-50.0);
2331 }
2332 assert!(n.v.is_finite());
2333 }
2334
2335 #[test]
2337 fn plant_r15_silent_without_input() {
2338 let mut n = PlantR15Neuron::new();
2339 for _ in 0..500 {
2341 n.step(0.0);
2342 }
2343 assert!(n.v.is_finite());
2344 }
2345 #[test]
2346 fn plant_r15_reset_clears_state() {
2347 let mut n = PlantR15Neuron::new();
2348 for _ in 0..100 {
2349 n.step(2.0);
2350 }
2351 n.reset();
2352 assert!((n.v - (-50.0)).abs() < 1e-10);
2353 assert!((n.ca - 0.1).abs() < 1e-10);
2354 }
2355 #[test]
2356 fn plant_r15_moderate_input_stable() {
2357 let mut n = PlantR15Neuron::new();
2359 for _ in 0..500 {
2360 n.step(2.0);
2361 }
2362 assert!(n.v.is_finite());
2363 }
2364 #[test]
2365 fn plant_r15_ca_dynamics() {
2366 let mut n = PlantR15Neuron::new();
2367 for _ in 0..500 {
2368 n.step(2.0);
2369 }
2370 assert!(n.ca >= 0.0, "Ca²⁺ must be non-negative");
2371 assert!(n.ca.is_finite());
2372 }
2373 #[test]
2374 fn plant_r15_weak_negative_no_crash() {
2375 let mut n = PlantR15Neuron::new();
2376 for _ in 0..200 {
2377 n.step(-1.0);
2378 }
2379 assert!(n.v.is_finite());
2380 }
2381 #[test]
2382 fn plant_r15_nan_no_panic() {
2383 let mut n = PlantR15Neuron::new();
2384 n.step(f64::NAN);
2385 }
2386
2387 #[test]
2389 fn prescott_zero_input_stable() {
2390 let mut n = PrescottNeuron::new();
2391 let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
2392 assert!(n.v.is_finite());
2394 }
2395 #[test]
2396 fn prescott_reset_clears_state() {
2397 let mut n = PrescottNeuron::new();
2398 for _ in 0..100 {
2399 n.step(5.0);
2400 }
2401 n.reset();
2402 assert!((n.v - (-65.0)).abs() < 1e-10);
2403 assert!((n.w - 0.0).abs() < 1e-10);
2404 }
2405 #[test]
2406 fn prescott_extreme_bounded() {
2407 let mut n = PrescottNeuron::new();
2408 for _ in 0..200 {
2409 n.step(1e4);
2410 }
2411 assert!(n.v.is_finite());
2412 }
2413 #[test]
2414 fn prescott_slow_var_adapts() {
2415 let mut n = PrescottNeuron::new();
2416 for _ in 0..500 {
2417 n.step(5.0);
2418 }
2419 assert!(n.w > 0.0, "slow variable w should activate during spiking");
2420 }
2421 #[test]
2422 fn prescott_negative_no_crash() {
2423 let mut n = PrescottNeuron::new();
2424 for _ in 0..200 {
2425 n.step(-10.0);
2426 }
2427 assert!(n.v.is_finite());
2428 }
2429 #[test]
2430 fn prescott_nan_no_panic() {
2431 let mut n = PrescottNeuron::new();
2432 n.step(f64::NAN);
2433 }
2434
2435 #[test]
2437 fn mn_silent_without_input() {
2438 let mut n = MihalasNieburNeuron::new();
2439 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
2440 assert_eq!(t, 0);
2441 }
2442 #[test]
2443 fn mn_reset_clears_state() {
2444 let mut n = MihalasNieburNeuron::new();
2445 for _ in 0..100 {
2446 n.step(5.0);
2447 }
2448 n.reset();
2449 assert!((n.v - n.v_rest).abs() < 1e-10);
2450 assert!((n.theta - n.theta_reset).abs() < 1e-10);
2451 }
2452 #[test]
2453 fn mn_extreme_bounded() {
2454 let mut n = MihalasNieburNeuron::new();
2455 for _ in 0..200 {
2456 n.step(1e4);
2457 }
2458 assert!(n.v.is_finite());
2459 }
2460 #[test]
2461 fn mn_adaptive_threshold() {
2462 let mut n = MihalasNieburNeuron::new();
2463 n.a = 0.1;
2464 for _ in 0..100 {
2465 n.step(5.0);
2466 }
2467 assert!(n.theta.is_finite());
2469 }
2470 #[test]
2471 fn mn_negative_no_crash() {
2472 let mut n = MihalasNieburNeuron::new();
2473 for _ in 0..200 {
2474 n.step(-10.0);
2475 }
2476 assert!(n.v.is_finite());
2477 }
2478 #[test]
2479 fn mn_nan_no_panic() {
2480 let mut n = MihalasNieburNeuron::new();
2481 n.step(f64::NAN);
2482 }
2483
2484 #[test]
2486 fn glif_silent_without_input() {
2487 let mut n = GLIFNeuron::new();
2488 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
2489 assert_eq!(t, 0);
2490 }
2491 #[test]
2492 fn glif_reset_clears_state() {
2493 let mut n = GLIFNeuron::new();
2494 for _ in 0..100 {
2495 n.step(30.0);
2496 }
2497 n.reset();
2498 assert!((n.v - n.v_rest).abs() < 1e-10);
2499 assert!((n.i_asc1).abs() < 1e-10);
2500 assert!((n.i_asc2).abs() < 1e-10);
2501 }
2502 #[test]
2503 fn glif_extreme_bounded() {
2504 let mut n = GLIFNeuron::new();
2505 for _ in 0..200 {
2506 n.step(1e4);
2507 }
2508 assert!(n.v.is_finite());
2509 }
2510 #[test]
2511 fn glif_threshold_adapts_after_spike() {
2512 let mut n = GLIFNeuron::new();
2513 let theta_init = n.theta;
2514 for _ in 0..200 {
2515 n.step(30.0);
2516 }
2517 assert!(
2518 n.theta > theta_init,
2519 "theta should increase after spikes (delta_theta > 0)"
2520 );
2521 }
2522 #[test]
2523 fn glif_afterspike_currents() {
2524 let mut n = GLIFNeuron::new();
2525 for _ in 0..200 {
2526 n.step(30.0);
2527 }
2528 assert!(n.v.is_finite());
2530 }
2531 #[test]
2532 fn glif_negative_no_crash() {
2533 let mut n = GLIFNeuron::new();
2534 for _ in 0..200 {
2535 n.step(-30.0);
2536 }
2537 assert!(n.v.is_finite());
2538 }
2539
2540 #[test]
2542 fn gif_pop_silent_without_input() {
2543 let mut n = GIFPopulationNeuron::new(42);
2544 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
2545 assert!(t < 5, "should be mostly silent at zero input, got {}", t);
2547 }
2548 #[test]
2549 fn gif_pop_reset_clears_state() {
2550 let mut n = GIFPopulationNeuron::new(42);
2551 for _ in 0..100 {
2552 n.step(30.0);
2553 }
2554 n.reset();
2555 assert!((n.v - (-65.0)).abs() < 1e-10);
2556 assert!((n.eta).abs() < 1e-10);
2557 }
2558 #[test]
2559 fn gif_pop_extreme_bounded() {
2560 let mut n = GIFPopulationNeuron::new(42);
2561 for _ in 0..200 {
2562 n.step(1e4);
2563 }
2564 assert!(n.v.is_finite());
2565 }
2566 #[test]
2567 fn gif_pop_stochastic_variability() {
2568 let mut n1 = GIFPopulationNeuron::new(1);
2570 let mut n2 = GIFPopulationNeuron::new(999);
2571 let t1: i32 = (0..1000).map(|_| n1.step(30.0)).sum();
2572 let t2: i32 = (0..1000).map(|_| n2.step(30.0)).sum();
2573 assert!(t1 > 0 && t2 > 0);
2575 }
2576 #[test]
2577 fn gif_pop_negative_no_crash() {
2578 let mut n = GIFPopulationNeuron::new(42);
2579 for _ in 0..200 {
2580 n.step(-30.0);
2581 }
2582 assert!(n.v.is_finite());
2583 }
2584
2585 #[test]
2587 fn avron_silent_without_input() {
2588 let mut n = AvRonCardiacNeuron::new();
2589 for _ in 0..500 {
2591 n.step(0.0);
2592 }
2593 assert!(n.v.is_finite());
2594 }
2595 #[test]
2596 fn avron_reset_clears_state() {
2597 let mut n = AvRonCardiacNeuron::new();
2598 for _ in 0..100 {
2599 n.step(5.0);
2600 }
2601 n.reset();
2602 assert!((n.v - (-60.0)).abs() < 1e-10);
2603 }
2604 #[test]
2605 fn avron_extreme_bounded() {
2606 let mut n = AvRonCardiacNeuron::new();
2607 for _ in 0..200 {
2608 n.step(1e4);
2609 }
2610 assert!(n.v.is_finite());
2611 }
2612 #[test]
2613 fn avron_slow_current() {
2614 let mut n = AvRonCardiacNeuron::new();
2615 for _ in 0..2000 {
2616 n.step(5.0);
2617 }
2618 assert!(n.s >= 0.0 && n.s <= 1.0, "s={}", n.s);
2620 }
2621 #[test]
2622 fn avron_negative_no_crash() {
2623 let mut n = AvRonCardiacNeuron::new();
2624 for _ in 0..500 {
2625 n.step(-10.0);
2626 }
2627 assert!(n.v.is_finite());
2628 }
2629 #[test]
2630 fn avron_nan_no_panic() {
2631 let mut n = AvRonCardiacNeuron::new();
2632 n.step(f64::NAN);
2633 }
2634
2635 #[test]
2637 fn durstewitz_low_activity_zero_input() {
2638 let mut n = DurstewitzDopamineNeuron::new();
2639 let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
2640 assert!(n.v.is_finite());
2642 }
2643 #[test]
2644 fn durstewitz_reset_clears_state() {
2645 let mut n = DurstewitzDopamineNeuron::new();
2646 for _ in 0..100 {
2647 n.step(3.0);
2648 }
2649 n.reset();
2650 assert!((n.v - (-65.0)).abs() < 1e-10);
2651 }
2652 #[test]
2653 fn durstewitz_extreme_bounded() {
2654 let mut n = DurstewitzDopamineNeuron::new();
2655 for _ in 0..200 {
2656 n.step(1e4);
2657 }
2658 assert!(n.v.is_finite());
2659 }
2660 #[test]
2661 fn durstewitz_d1_modulation() {
2662 let mut n_d1 = DurstewitzDopamineNeuron::new();
2664 n_d1.d1_level = 1.0;
2665 let mut n_no = DurstewitzDopamineNeuron::new();
2666 n_no.d1_level = 0.0;
2667 for _ in 0..1000 {
2668 n_d1.step(3.0);
2669 }
2670 for _ in 0..1000 {
2671 n_no.step(3.0);
2672 }
2673 assert!(n_d1.v.is_finite() && n_no.v.is_finite());
2675 }
2676 #[test]
2677 fn durstewitz_mg_block() {
2678 let n = DurstewitzDopamineNeuron::new();
2679 let block = 1.0 / (1.0 + n.mg * (-0.062 * n.v).exp() / 3.57);
2681 assert!(
2682 block < 0.1,
2683 "Mg²⁺ block at rest should be strong: {}",
2684 block
2685 );
2686 }
2687 #[test]
2688 fn durstewitz_negative_no_crash() {
2689 let mut n = DurstewitzDopamineNeuron::new();
2690 for _ in 0..200 {
2691 n.step(-10.0);
2692 }
2693 assert!(n.v.is_finite());
2694 }
2695
2696 #[test]
2698 fn hill_tononi_silent_without_input() {
2699 let mut n = HillTononiNeuron::new();
2700 let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
2701 assert!(n.v.is_finite());
2703 }
2704 #[test]
2705 fn hill_tononi_reset_clears_state() {
2706 let mut n = HillTononiNeuron::new();
2707 for _ in 0..100 {
2708 n.step(5.0);
2709 }
2710 n.reset();
2711 assert!((n.v - (-65.0)).abs() < 1e-10);
2712 assert!((n.na_i - 5.0).abs() < 1e-10);
2713 }
2714 #[test]
2715 fn hill_tononi_extreme_bounded() {
2716 let mut n = HillTononiNeuron::new();
2717 for _ in 0..200 {
2718 n.step(1e4);
2719 }
2720 assert!(n.v.is_finite());
2721 }
2722 #[test]
2723 fn hill_tononi_na_accumulation() {
2724 let mut n = HillTononiNeuron::new();
2725 for _ in 0..500 {
2726 n.step(5.0);
2727 }
2728 assert!(n.na_i.is_finite());
2730 assert!(n.na_i >= 0.0, "Na_i must be non-negative");
2731 }
2732 #[test]
2733 fn hill_tononi_negative_no_crash() {
2734 let mut n = HillTononiNeuron::new();
2735 for _ in 0..200 {
2736 n.step(-10.0);
2737 }
2738 assert!(n.v.is_finite());
2739 }
2740 #[test]
2741 fn hill_tononi_nan_no_panic() {
2742 let mut n = HillTononiNeuron::new();
2743 n.step(f64::NAN);
2744 }
2745
2746 #[test]
2748 fn bertram_silent_without_input() {
2749 let mut n = BertramPhantomBurster::new();
2750 for _ in 0..500 {
2751 n.step(0.0);
2752 }
2753 assert!(n.v.is_finite());
2754 }
2755 #[test]
2756 fn bertram_reset_clears_state() {
2757 let mut n = BertramPhantomBurster::new();
2758 for _ in 0..1000 {
2759 n.step(200.0);
2760 }
2761 n.reset();
2762 assert!((n.v - (-50.0)).abs() < 1e-10);
2763 assert!((n.s1 - 0.1).abs() < 1e-10);
2764 assert!((n.s2 - 0.1).abs() < 1e-10);
2765 }
2766 #[test]
2767 fn bertram_extreme_bounded() {
2768 let mut n = BertramPhantomBurster::new();
2769 for _ in 0..200 {
2770 n.step(1e4);
2771 }
2772 assert!(n.v.is_finite());
2773 }
2774 #[test]
2775 fn bertram_dual_slow_vars() {
2776 let mut n = BertramPhantomBurster::new();
2777 for _ in 0..10000 {
2778 n.step(200.0);
2779 }
2780 assert!(n.s1 >= 0.0 && n.s1 <= 1.0, "s1={}", n.s1);
2782 assert!(n.s2 >= 0.0 && n.s2 <= 1.0, "s2={}", n.s2);
2783 }
2784 #[test]
2785 fn bertram_negative_no_crash() {
2786 let mut n = BertramPhantomBurster::new();
2787 for _ in 0..200 {
2788 n.step(-100.0);
2789 }
2790 assert!(n.v.is_finite());
2791 }
2792 #[test]
2793 fn bertram_nan_no_panic() {
2794 let mut n = BertramPhantomBurster::new();
2795 n.step(f64::NAN);
2796 }
2797
2798 #[test]
2800 fn yamada_silent_without_input() {
2801 let mut n = YamadaNeuron::new();
2802 let t: i32 = (0..500).map(|_| n.step(0.0)).sum();
2803 assert_eq!(t, 0);
2804 }
2805 #[test]
2806 fn yamada_reset_clears_state() {
2807 let mut n = YamadaNeuron::new();
2808 for _ in 0..100 {
2809 n.step(5.0);
2810 }
2811 n.reset();
2812 assert!((n.v - (-60.0)).abs() < 1e-10);
2813 assert!((n.q - 0.0).abs() < 1e-10);
2814 }
2815 #[test]
2816 fn yamada_extreme_bounded() {
2817 let mut n = YamadaNeuron::new();
2818 for _ in 0..200 {
2819 n.step(1e4);
2820 }
2821 assert!(n.v.is_finite());
2822 }
2823 #[test]
2824 fn yamada_slow_q_adapts() {
2825 let mut n = YamadaNeuron::new();
2826 for _ in 0..2000 {
2827 n.step(5.0);
2828 }
2829 assert!(n.q > 0.0, "slow variable q should activate during spiking");
2830 }
2831 #[test]
2832 fn yamada_negative_no_crash() {
2833 let mut n = YamadaNeuron::new();
2834 for _ in 0..200 {
2835 n.step(-10.0);
2836 }
2837 assert!(n.v.is_finite());
2838 }
2839 #[test]
2840 fn yamada_nan_no_panic() {
2841 let mut n = YamadaNeuron::new();
2842 n.step(f64::NAN);
2843 }
2844}