1use rand::{RngExt, SeedableRng};
11use rand_xoshiro::Xoshiro256PlusPlus;
12
13#[derive(Clone, Debug)]
16pub struct QuadraticIFNeuron {
17 pub v: f64,
18 pub v_reset: f64,
19 pub v_peak: f64,
20 pub dt: f64,
21}
22
23impl QuadraticIFNeuron {
24 pub fn new(v_reset: f64, v_peak: f64, dt: f64) -> Self {
25 Self {
26 v: v_reset,
27 v_reset,
28 v_peak,
29 dt,
30 }
31 }
32
33 fn valid_numeric_contract(&self) -> bool {
34 self.v.is_finite()
35 && self.v_reset.is_finite()
36 && self.v_peak.is_finite()
37 && self.dt.is_finite()
38 && self.v < self.v_peak
39 && self.v_reset < self.v_peak
40 && self.dt > 0.0
41 }
42
43 pub fn step(&mut self, current: f64) -> i32 {
44 if !self.valid_numeric_contract() || !current.is_finite() {
45 return 0;
46 }
47 let increment = (self.v * self.v + current) * self.dt;
48 let next_v = self.v + increment;
49 if !(increment.is_finite() && next_v.is_finite()) {
50 return 0;
51 }
52 self.v = next_v;
53 if self.v >= self.v_peak {
54 self.v = self.v_reset;
55 1
56 } else {
57 0
58 }
59 }
60
61 pub fn reset(&mut self) {
62 self.v = self.v_reset;
63 }
64}
65
66impl Default for QuadraticIFNeuron {
67 fn default() -> Self {
68 Self::new(-1.0, 1.0, 0.01)
69 }
70}
71
72#[derive(Clone, Debug)]
75pub struct ThetaNeuron {
76 pub theta: f64,
77 pub dt: f64,
78}
79
80impl ThetaNeuron {
81 pub fn new(dt: f64) -> Self {
82 Self { theta: 0.0, dt }
83 }
84
85 pub fn step(&mut self, current: f64) -> i32 {
86 let prev = self.theta;
87 self.theta += ((1.0 - self.theta.cos()) + (1.0 + self.theta.cos()) * current) * self.dt;
88 if self.theta >= std::f64::consts::PI && prev < std::f64::consts::PI {
89 self.theta -= 2.0 * std::f64::consts::PI;
90 1
91 } else {
92 0
93 }
94 }
95
96 pub fn reset(&mut self) {
97 self.theta = 0.0;
98 }
99}
100
101impl Default for ThetaNeuron {
102 fn default() -> Self {
103 Self::new(0.01)
104 }
105}
106
107#[derive(Clone, Debug)]
109pub struct PerfectIntegratorNeuron {
110 pub v: f64,
111 pub c_m: f64,
112 pub v_threshold: f64,
113 pub v_reset: f64,
114 pub dt: f64,
115}
116
117impl PerfectIntegratorNeuron {
118 pub fn new(c_m: f64, v_threshold: f64, dt: f64) -> Self {
119 Self {
120 v: 0.0,
121 c_m,
122 v_threshold,
123 v_reset: 0.0,
124 dt,
125 }
126 }
127
128 pub fn step(&mut self, current: f64) -> i32 {
129 self.v += (current / self.c_m) * self.dt;
130 if self.v >= self.v_threshold {
131 self.v = self.v_reset;
132 1
133 } else {
134 0
135 }
136 }
137
138 pub fn reset(&mut self) {
139 self.v = self.v_reset;
140 }
141}
142
143impl Default for PerfectIntegratorNeuron {
144 fn default() -> Self {
145 Self::new(1.0, 1.0, 0.1)
146 }
147}
148
149#[derive(Clone, Debug)]
151pub struct GatedLIFNeuron {
152 pub v: f64,
153 pub gate_v: f64,
154 pub gate_i: f64,
155 pub v_threshold: f64,
156 pub dt: f64,
157}
158
159impl GatedLIFNeuron {
160 pub fn new(gate_v: f64, gate_i: f64, v_threshold: f64) -> Self {
161 Self {
162 v: 0.0,
163 gate_v,
164 gate_i,
165 v_threshold,
166 dt: 1.0,
167 }
168 }
169
170 pub fn step(&mut self, current: f64) -> i32 {
171 self.v = self.gate_v * self.v + self.gate_i * current;
172 if self.v >= self.v_threshold {
173 self.v -= self.v_threshold;
174 1
175 } else {
176 0
177 }
178 }
179
180 pub fn reset(&mut self) {
181 self.v = 0.0;
182 }
183}
184
185impl Default for GatedLIFNeuron {
186 fn default() -> Self {
187 Self::new(0.9, 1.0, 1.0)
188 }
189}
190
191#[derive(Clone, Debug)]
193pub struct NonlinearLIFNeuron {
194 pub v: f64,
195 pub w: f64,
196 pub v_rest: f64,
197 pub v_crit: f64,
198 pub v_threshold: f64,
199 pub v_reset: f64,
200 pub a: f64,
201 pub b: f64,
202 pub tau_w: f64,
203 pub c_m: f64,
204 pub dt: f64,
205}
206
207impl NonlinearLIFNeuron {
208 pub fn new() -> Self {
209 Self {
210 v: -65.0,
211 w: 0.0,
212 v_rest: -65.0,
213 v_crit: -40.0,
214 v_threshold: -20.0,
215 v_reset: -65.0,
216 a: 0.04,
217 b: 0.5,
218 tau_w: 100.0,
219 c_m: 1.0,
220 dt: 0.1,
221 }
222 }
223
224 pub fn step(&mut self, current: f64) -> i32 {
225 let v_prev = self.v;
226 let cubic = self.a * (self.v - self.v_rest) * (self.v - self.v_crit);
227 self.v += (cubic - self.w + current) / self.c_m * self.dt;
228 self.w += (self.b * (self.v - self.v_rest) - self.w) / self.tau_w * self.dt;
229 if self.v >= self.v_threshold && v_prev < self.v_threshold {
230 self.v = self.v_reset;
231 1
232 } else {
233 0
234 }
235 }
236
237 pub fn reset(&mut self) {
238 self.v = self.v_rest;
239 self.w = 0.0;
240 }
241}
242
243impl Default for NonlinearLIFNeuron {
244 fn default() -> Self {
245 Self::new()
246 }
247}
248
249#[derive(Clone, Debug)]
251pub struct SFANeuron {
252 pub v: f64,
253 pub g_sfa: f64,
254 pub v_rest: f64,
255 pub v_reset: f64,
256 pub v_threshold: f64,
257 pub tau_m: f64,
258 pub tau_sfa: f64,
259 pub delta_g: f64,
260 pub e_k: f64,
261 pub resistance: f64,
262 pub dt: f64,
263}
264
265impl SFANeuron {
266 pub fn new() -> Self {
267 Self {
268 v: -70.0,
269 g_sfa: 0.0,
270 v_rest: -70.0,
271 v_reset: -70.0,
272 v_threshold: -50.0,
273 tau_m: 10.0,
274 tau_sfa: 200.0,
275 delta_g: 0.5,
276 e_k: -80.0,
277 resistance: 1.0,
278 dt: 1.0,
279 }
280 }
281
282 pub fn step(&mut self, current: f64) -> i32 {
283 self.v += (-(self.v - self.v_rest) - self.g_sfa * (self.v - self.e_k)
284 + self.resistance * current)
285 / self.tau_m
286 * self.dt;
287 self.g_sfa *= (-self.dt / self.tau_sfa).exp();
288 if self.v >= self.v_threshold {
289 self.v = self.v_reset;
290 self.g_sfa += self.delta_g;
291 1
292 } else {
293 0
294 }
295 }
296
297 pub fn reset(&mut self) {
298 self.v = self.v_rest;
299 self.g_sfa = 0.0;
300 }
301}
302
303impl Default for SFANeuron {
304 fn default() -> Self {
305 Self::new()
306 }
307}
308
309#[derive(Clone, Debug)]
311pub struct MATNeuron {
312 pub v: f64,
313 pub theta1: f64,
314 pub theta2: f64,
315 pub v_rest: f64,
316 pub v_reset: f64,
317 pub v_threshold_base: f64,
318 pub tau_m: f64,
319 pub tau_1: f64,
320 pub tau_2: f64,
321 pub h1: f64,
322 pub h2: f64,
323 pub resistance: f64,
324 pub dt: f64,
325}
326
327impl MATNeuron {
328 pub fn new() -> Self {
329 Self {
330 v: -70.0,
331 theta1: 0.0,
332 theta2: 0.0,
333 v_rest: -70.0,
334 v_reset: -70.0,
335 v_threshold_base: -50.0,
336 tau_m: 10.0,
337 tau_1: 10.0,
338 tau_2: 200.0,
339 h1: 5.0,
340 h2: 3.0,
341 resistance: 1.0,
342 dt: 1.0,
343 }
344 }
345
346 pub fn step(&mut self, current: f64) -> i32 {
347 self.v += (-(self.v - self.v_rest) + self.resistance * current) / self.tau_m * self.dt;
348 self.theta1 *= (-self.dt / self.tau_1).exp();
349 self.theta2 *= (-self.dt / self.tau_2).exp();
350 let threshold = self.v_threshold_base + self.theta1 + self.theta2;
351 if self.v >= threshold {
352 self.v = self.v_reset;
353 self.theta1 += self.h1;
354 self.theta2 += self.h2;
355 1
356 } else {
357 0
358 }
359 }
360
361 pub fn reset(&mut self) {
362 self.v = self.v_rest;
363 self.theta1 = 0.0;
364 self.theta2 = 0.0;
365 }
366}
367
368impl Default for MATNeuron {
369 fn default() -> Self {
370 Self::new()
371 }
372}
373
374#[derive(Clone, Debug)]
376pub struct EscapeRateNeuron {
377 pub v: f64,
378 pub v_rest: f64,
379 pub v_reset: f64,
380 pub v_threshold: f64,
381 pub tau_m: f64,
382 pub rho_0: f64,
383 pub delta_u: f64,
384 pub resistance: f64,
385 pub dt: f64,
386 rng: Xoshiro256PlusPlus,
387}
388
389impl EscapeRateNeuron {
390 pub fn new(seed: u64) -> Self {
391 Self {
392 v: -70.0,
393 v_rest: -70.0,
394 v_reset: -70.0,
395 v_threshold: -50.0,
396 tau_m: 10.0,
397 rho_0: 0.001,
398 delta_u: 3.0,
399 resistance: 1.0,
400 dt: 1.0,
401 rng: Xoshiro256PlusPlus::seed_from_u64(seed),
402 }
403 }
404
405 pub fn step(&mut self, current: f64) -> i32 {
406 self.v += (-(self.v - self.v_rest) + self.resistance * current) / self.tau_m * self.dt;
407 let rate = self.rho_0 * ((self.v - self.v_threshold) / self.delta_u).exp();
408 if self.rng.random::<f64>() < rate * self.dt {
409 self.v = self.v_reset;
410 1
411 } else {
412 0
413 }
414 }
415
416 pub fn reset(&mut self) {
417 self.v = self.v_rest;
418 }
419}
420
421#[derive(Clone, Debug)]
423pub struct KLIFNeuron {
424 pub v: f64,
425 pub k: f64,
426 pub alpha: f64,
427 pub v_threshold: f64,
428 pub v_reset: f64,
429}
430
431impl KLIFNeuron {
432 pub fn new(tau: f64, k: f64, dt: f64) -> Self {
433 Self {
434 v: 0.0,
435 k,
436 alpha: (-dt / tau).exp(),
437 v_threshold: 1.0,
438 v_reset: 0.0,
439 }
440 }
441
442 pub fn step(&mut self, current: f64) -> i32 {
443 self.v = self.alpha * self.v + self.k * current;
444 if self.v >= self.v_threshold {
445 self.v = self.v_reset;
446 1
447 } else {
448 0
449 }
450 }
451
452 pub fn reset(&mut self) {
453 self.v = 0.0;
454 }
455}
456
457impl Default for KLIFNeuron {
458 fn default() -> Self {
459 Self::new(10.0, 1.0, 1.0)
460 }
461}
462
463#[derive(Clone, Debug)]
465pub struct InhibitoryLIFNeuron {
466 pub v: f64,
467 pub inh_trace: f64,
468 pub alpha_m: f64,
469 pub alpha_inh: f64,
470 pub v_threshold: f64,
471 pub inh_strength: f64,
472}
473
474impl InhibitoryLIFNeuron {
475 pub fn new(tau_m: f64, tau_inh: f64, dt: f64) -> Self {
476 Self {
477 v: 0.0,
478 inh_trace: 0.0,
479 alpha_m: (-dt / tau_m).exp(),
480 alpha_inh: (-dt / tau_inh).exp(),
481 v_threshold: 1.0,
482 inh_strength: 0.5,
483 }
484 }
485
486 pub fn step(&mut self, current: f64) -> i32 {
487 self.inh_trace *= self.alpha_inh;
488 self.v = self.alpha_m * self.v + current - self.inh_strength * self.inh_trace;
489 if self.v >= self.v_threshold {
490 self.v = 0.0;
491 self.inh_trace += 1.0;
492 1
493 } else {
494 0
495 }
496 }
497
498 pub fn reset(&mut self) {
499 self.v = 0.0;
500 self.inh_trace = 0.0;
501 }
502}
503
504impl Default for InhibitoryLIFNeuron {
505 fn default() -> Self {
506 Self::new(10.0, 5.0, 1.0)
507 }
508}
509
510#[derive(Clone, Debug)]
512pub struct ComplementaryLIFNeuron {
513 pub v_pos: f64,
514 pub v_neg: f64,
515 pub alpha: f64,
516 pub v_threshold: f64,
517}
518
519impl ComplementaryLIFNeuron {
520 pub fn new(tau: f64, dt: f64, v_threshold: f64) -> Self {
521 Self {
522 v_pos: 0.0,
523 v_neg: 0.0,
524 alpha: (-dt / tau).exp(),
525 v_threshold,
526 }
527 }
528
529 pub fn step(&mut self, current: f64) -> i32 {
530 let inp_pos = current.max(0.0);
531 let inp_neg = (-current).max(0.0);
532 self.v_pos = self.alpha * self.v_pos + inp_pos;
533 self.v_neg = self.alpha * self.v_neg + inp_neg;
534 let diff = self.v_pos - self.v_neg;
535 if diff >= self.v_threshold {
536 self.v_pos = 0.0;
537 self.v_neg = 0.0;
538 1
539 } else if diff <= -self.v_threshold {
540 self.v_pos = 0.0;
541 self.v_neg = 0.0;
542 -1
543 } else {
544 0
545 }
546 }
547
548 pub fn reset(&mut self) {
549 self.v_pos = 0.0;
550 self.v_neg = 0.0;
551 }
552}
553
554impl Default for ComplementaryLIFNeuron {
555 fn default() -> Self {
556 Self::new(10.0, 1.0, 1.0)
557 }
558}
559
560#[derive(Clone, Debug)]
562pub struct ParametricLIFNeuron {
563 pub v: f64,
564 pub a: f64,
565 pub threshold: f64,
566}
567
568impl ParametricLIFNeuron {
569 pub fn new(a: f64, threshold: f64) -> Self {
570 Self {
571 v: 0.0,
572 a,
573 threshold,
574 }
575 }
576
577 pub fn step(&mut self, current: f64) -> i32 {
578 let alpha = 1.0 / (1.0 + (-self.a).exp());
579 let spike = if self.v >= self.threshold { 1 } else { 0 };
580 self.v = alpha * self.v * (1.0 - spike as f64) + current;
581 spike
582 }
583
584 pub fn reset(&mut self) {
585 self.v = 0.0;
586 }
587}
588
589impl Default for ParametricLIFNeuron {
590 fn default() -> Self {
591 Self::new(0.0, 1.0)
592 }
593}
594
595#[derive(Clone, Debug)]
597pub struct NonResettingLIFNeuron {
598 pub v: f64,
599 pub theta: f64,
600 pub v_rest: f64,
601 pub theta_rest: f64,
602 pub delta_theta: f64,
603 pub tau_m: f64,
604 pub tau_theta: f64,
605 pub r_m: f64,
606 pub dt: f64,
607}
608
609impl NonResettingLIFNeuron {
610 pub fn new() -> Self {
611 Self {
612 v: -65.0,
613 theta: -50.0,
614 v_rest: -65.0,
615 theta_rest: -50.0,
616 delta_theta: 5.0,
617 tau_m: 10.0,
618 tau_theta: 50.0,
619 r_m: 1.0,
620 dt: 0.1,
621 }
622 }
623
624 pub fn step(&mut self, current: f64) -> i32 {
625 self.v += (-(self.v - self.v_rest) + self.r_m * current) / self.tau_m * self.dt;
626 self.theta += (-(self.theta - self.theta_rest)) / self.tau_theta * self.dt;
627 if self.v >= self.theta {
628 self.theta += self.delta_theta;
629 1
630 } else {
631 0
632 }
633 }
634
635 pub fn reset(&mut self) {
636 self.v = self.v_rest;
637 self.theta = self.theta_rest;
638 }
639}
640
641impl Default for NonResettingLIFNeuron {
642 fn default() -> Self {
643 Self::new()
644 }
645}
646
647#[derive(Clone, Debug)]
649pub struct AdaptiveThresholdIFNeuron {
650 pub v: f64,
651 pub theta: f64,
652 pub v_rest: f64,
653 pub v_reset: f64,
654 pub theta_rest: f64,
655 pub delta_theta: f64,
656 pub tau_m: f64,
657 pub tau_theta: f64,
658 pub dt: f64,
659}
660
661impl AdaptiveThresholdIFNeuron {
662 pub fn new() -> Self {
663 Self {
664 v: -65.0,
665 theta: -50.0,
666 v_rest: -65.0,
667 v_reset: -65.0,
668 theta_rest: -50.0,
669 delta_theta: 5.0,
670 tau_m: 10.0,
671 tau_theta: 50.0,
672 dt: 0.1,
673 }
674 }
675
676 pub fn step(&mut self, current: f64) -> i32 {
677 self.v += (-(self.v - self.v_rest) + current) / self.tau_m * self.dt;
678 self.theta += (-(self.theta - self.theta_rest)) / self.tau_theta * self.dt;
679 if self.v >= self.theta {
680 self.v = self.v_reset;
681 self.theta += self.delta_theta;
682 1
683 } else {
684 0
685 }
686 }
687
688 pub fn reset(&mut self) {
689 self.v = self.v_rest;
690 self.theta = self.theta_rest;
691 }
692}
693
694impl Default for AdaptiveThresholdIFNeuron {
695 fn default() -> Self {
696 Self::new()
697 }
698}
699
700#[derive(Clone, Debug)]
702pub struct SigmaDeltaNeuron {
703 pub sigma: f64,
704 pub v_threshold: f64,
705}
706
707impl SigmaDeltaNeuron {
708 pub fn new(v_threshold: f64) -> Self {
709 Self {
710 sigma: 0.0,
711 v_threshold,
712 }
713 }
714
715 pub fn step(&mut self, current: f64) -> i32 {
716 self.sigma += current;
717 if self.sigma >= self.v_threshold {
718 self.sigma -= self.v_threshold;
719 1
720 } else if self.sigma <= -self.v_threshold {
721 self.sigma += self.v_threshold;
722 -1
723 } else {
724 0
725 }
726 }
727
728 pub fn reset(&mut self) {
729 self.sigma = 0.0;
730 }
731}
732
733impl Default for SigmaDeltaNeuron {
734 fn default() -> Self {
735 Self::new(1.0)
736 }
737}
738
739#[derive(Clone, Debug)]
741pub struct EnergyLIFNeuron {
742 pub v: f64,
743 pub epsilon: f64,
744 pub v_rest: f64,
745 pub v_reset: f64,
746 pub v_threshold: f64,
747 pub tau_m: f64,
748 pub tau_e: f64,
749 pub alpha: f64,
750 pub epsilon_0: f64,
751 pub resistance: f64,
752 pub dt: f64,
753}
754
755impl EnergyLIFNeuron {
756 pub fn new() -> Self {
757 Self {
758 v: -70.0,
759 epsilon: 1.0,
760 v_rest: -70.0,
761 v_reset: -70.0,
762 v_threshold: -50.0,
763 tau_m: 10.0,
764 tau_e: 500.0,
765 alpha: 0.1,
766 epsilon_0: 1.0,
767 resistance: 1.0,
768 dt: 1.0,
769 }
770 }
771
772 pub fn step(&mut self, current: f64) -> i32 {
773 let effective_r = self.resistance * self.epsilon;
774 self.v += (-(self.v - self.v_rest) + effective_r * current) / self.tau_m * self.dt;
775 self.epsilon += (self.epsilon_0 - self.epsilon) / self.tau_e * self.dt;
776 if self.v >= self.v_threshold && self.epsilon > 0.1 {
777 self.v = self.v_reset;
778 self.epsilon -= self.alpha;
779 1
780 } else if self.v >= self.v_threshold {
781 self.v = self.v_threshold;
782 0
783 } else {
784 0
785 }
786 }
787
788 pub fn reset(&mut self) {
789 self.v = self.v_rest;
790 self.epsilon = self.epsilon_0;
791 }
792}
793
794impl Default for EnergyLIFNeuron {
795 fn default() -> Self {
796 Self::new()
797 }
798}
799
800#[derive(Clone, Debug)]
802pub struct IntegerQIFNeuron {
803 pub v: i32,
804 pub k: i32,
805 pub v_threshold: i32,
806 pub v_reset: i32,
807 pub v_min: i32,
808}
809
810impl IntegerQIFNeuron {
811 pub fn new(k: i32, v_threshold: i32) -> Self {
812 Self {
813 v: 0,
814 k,
815 v_threshold,
816 v_reset: -v_threshold,
817 v_min: -2 * v_threshold,
818 }
819 }
820
821 pub fn step(&mut self, current: i32) -> i32 {
822 self.v = (self.v + (self.v.wrapping_mul(self.v) >> self.k) + current).max(self.v_min);
823 if self.v >= self.v_threshold {
824 self.v = self.v_reset;
825 1
826 } else {
827 0
828 }
829 }
830
831 pub fn reset(&mut self) {
832 self.v = 0;
833 }
834}
835
836impl Default for IntegerQIFNeuron {
837 fn default() -> Self {
838 Self::new(6, 1024)
839 }
840}
841
842#[derive(Clone, Debug)]
844pub struct ClosedFormContinuousNeuron {
845 pub x: f64,
846 pub w_tau: f64,
847 pub w_x: f64,
848 pub w_in: f64,
849 pub tau_base: f64,
850 pub bias: f64,
851 pub v_threshold: f64,
852 pub dt: f64,
853}
854
855impl ClosedFormContinuousNeuron {
856 pub fn new() -> Self {
857 Self {
858 x: 0.0,
859 w_tau: -0.5,
860 w_x: 0.8,
861 w_in: 1.0,
862 tau_base: 10.0,
863 bias: 0.0,
864 v_threshold: 1.0,
865 dt: 1.0,
866 }
867 }
868
869 pub fn step(&mut self, current: f64) -> i32 {
870 let sigma_tau = 1.0 / (1.0 + (-(self.w_tau * current + self.bias)).exp());
871 let tau_eff = (self.tau_base * sigma_tau).max(0.1);
872 let f_target = (self.w_x * self.x + self.w_in * current).tanh();
873 let decay = (-self.dt / tau_eff).exp();
874 self.x = self.x * decay + f_target * (1.0 - decay);
875 if self.x >= self.v_threshold {
876 self.x -= self.v_threshold;
877 1
878 } else {
879 0
880 }
881 }
882
883 pub fn reset(&mut self) {
884 self.x = 0.0;
885 }
886}
887
888impl Default for ClosedFormContinuousNeuron {
889 fn default() -> Self {
890 Self::new()
891 }
892}
893
894#[cfg(test)]
895mod tests {
896 use super::*;
897
898 #[test]
899 fn qif_fires_with_positive_input() {
900 let mut n = QuadraticIFNeuron::default();
901 let total: i32 = (0..1000).map(|_| n.step(0.5)).sum();
902 assert!(total > 0);
903 }
904
905 #[test]
906 fn theta_fires() {
907 let mut n = ThetaNeuron::default();
908 let total: i32 = (0..1000).map(|_| n.step(0.5)).sum();
909 assert!(total > 0);
910 }
911
912 #[test]
913 fn perfect_integrator_fires() {
914 let mut n = PerfectIntegratorNeuron::default();
915 let total: i32 = (0..100).map(|_| n.step(0.5)).sum();
916 assert!(total > 0);
917 }
918
919 #[test]
920 fn gated_lif_fires() {
921 let mut n = GatedLIFNeuron::default();
922 let total: i32 = (0..20).map(|_| n.step(0.5)).sum();
923 assert!(total > 0);
924 }
925
926 #[test]
927 fn nlif_fires() {
928 let mut n = NonlinearLIFNeuron::new();
929 let total: i32 = (0..2000).map(|_| n.step(500.0)).sum();
930 assert!(total > 0);
931 }
932
933 #[test]
934 fn sfa_fires_then_adapts() {
935 let mut n = SFANeuron::new();
936 let first: i32 = (0..100).map(|_| n.step(30.0)).sum();
937 let second: i32 = (0..100).map(|_| n.step(30.0)).sum();
938 assert!(first > 0);
939 assert!(second <= first + 2);
940 }
941
942 #[test]
943 fn mat_dual_threshold_adapts() {
944 let mut n = MATNeuron::new();
945 let total: i32 = (0..200).map(|_| n.step(30.0)).sum();
946 assert!(total > 0);
947 assert!(n.theta1 > 0.0 || n.theta2 > 0.0);
948 }
949
950 #[test]
951 fn escape_rate_stochastic() {
952 let mut n = EscapeRateNeuron::new(42);
953 let total: i32 = (0..1000).map(|_| n.step(30.0)).sum();
954 assert!(total > 0);
955 }
956
957 #[test]
958 fn klif_fires() {
959 let mut n = KLIFNeuron::default();
960 let total: i32 = (0..50).map(|_| n.step(0.5)).sum();
961 assert!(total > 0);
962 }
963
964 #[test]
965 fn ilif_self_inhibits() {
966 let mut n = InhibitoryLIFNeuron::default();
967 let total: i32 = (0..100).map(|_| n.step(0.8)).sum();
968 assert!(total > 0);
969 }
970
971 #[test]
972 fn clif_positive_spike() {
973 let mut n = ComplementaryLIFNeuron::default();
974 let total: i32 = (0..20).map(|_| n.step(0.5)).sum();
975 assert!(total > 0);
976 }
977
978 #[test]
979 fn plif_fires() {
980 let mut n = ParametricLIFNeuron::default();
981 let total: i32 = (0..20).map(|_| n.step(1.5)).sum();
982 assert!(total > 0);
983 }
984
985 #[test]
986 fn non_resetting_threshold_increases() {
987 let mut n = NonResettingLIFNeuron::new();
988 let initial = n.theta;
989 for _ in 0..5000 {
990 n.step(30.0);
991 }
992 assert!(n.theta > initial);
993 }
994
995 #[test]
996 fn adaptive_threshold_fires() {
997 let mut n = AdaptiveThresholdIFNeuron::new();
998 let total: i32 = (0..500).map(|_| n.step(30.0)).sum();
999 assert!(total > 0);
1000 }
1001
1002 #[test]
1003 fn sigma_delta_encodes() {
1004 let mut n = SigmaDeltaNeuron::default();
1005 let total: i32 = (0..10).map(|_| n.step(0.3)).sum();
1006 assert!(total > 0);
1007 }
1008
1009 #[test]
1010 fn energy_lif_fires() {
1011 let mut n = EnergyLIFNeuron::new();
1012 let total: i32 = (0..200).map(|_| n.step(30.0)).sum();
1013 assert!(total > 0);
1014 }
1015
1016 #[test]
1017 fn iqif_fires() {
1018 let mut n = IntegerQIFNeuron::default();
1019 let total: i32 = (0..200).map(|_| n.step(50)).sum();
1020 assert!(total > 0);
1021 }
1022
1023 #[test]
1024 fn cfc_activates() {
1025 let mut n = ClosedFormContinuousNeuron {
1026 v_threshold: 0.9,
1027 ..ClosedFormContinuousNeuron::new()
1028 };
1029 let total: i32 = (0..100).map(|_| n.step(5.0)).sum();
1030 assert!(total > 0);
1031 }
1032
1033 #[test]
1037 fn qif_silent_without_input() {
1038 let mut n = QuadraticIFNeuron::default();
1039 let t: i32 = (0..1000).map(|_| n.step(0.0)).sum();
1040 assert_eq!(t, 0);
1041 }
1042 #[test]
1043 fn qif_reset_clears_state() {
1044 let mut n = QuadraticIFNeuron::default();
1045 for _ in 0..100 {
1046 n.step(0.5);
1047 }
1048 n.reset();
1049 assert!((n.v - n.v_reset).abs() < 1e-10);
1050 }
1051 #[test]
1052 fn qif_bounded() {
1053 let mut n = QuadraticIFNeuron::default();
1054 for _ in 0..1000 {
1055 n.step(10.0);
1056 }
1057 assert!(n.v.is_finite());
1058 }
1059 #[test]
1060 fn qif_nan_no_panic() {
1061 let mut n = QuadraticIFNeuron::default();
1062 let before = n.v;
1063 assert_eq!(n.step(f64::NAN), 0);
1064 assert_eq!(n.v, before);
1065 }
1066 #[test]
1067 fn qif_nonfinite_increment_preserves_state() {
1068 let mut n = QuadraticIFNeuron {
1069 v: -1.0e200,
1070 ..Default::default()
1071 };
1072 let before = n.v;
1073 assert_eq!(n.step(0.0), 0);
1074 assert_eq!(n.v, before);
1075 }
1076 #[test]
1077 fn qif_negative_no_crash() {
1078 let mut n = QuadraticIFNeuron::default();
1079 for _ in 0..500 {
1080 n.step(-5.0);
1081 }
1082 assert!(n.v.is_finite());
1083 }
1084
1085 #[test]
1087 fn theta_silent_without_input() {
1088 let mut n = ThetaNeuron::default();
1089 let t: i32 = (0..1000).map(|_| n.step(0.0)).sum();
1090 assert_eq!(t, 0);
1091 }
1092 #[test]
1093 fn theta_reset_clears_state() {
1094 let mut n = ThetaNeuron::default();
1095 for _ in 0..100 {
1096 n.step(0.5);
1097 }
1098 n.reset();
1099 assert!((n.theta - 0.0).abs() < 1e-10);
1100 }
1101 #[test]
1102 fn theta_bounded() {
1103 let mut n = ThetaNeuron::default();
1104 for _ in 0..1000 {
1105 n.step(10.0);
1106 }
1107 assert!(n.theta.is_finite());
1108 }
1109 #[test]
1110 fn theta_nan_no_panic() {
1111 ThetaNeuron::default().step(f64::NAN);
1112 }
1113
1114 #[test]
1116 fn pi_silent_without_input() {
1117 let mut n = PerfectIntegratorNeuron::default();
1118 let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
1119 assert_eq!(t, 0);
1120 }
1121 #[test]
1122 fn pi_reset_clears_state() {
1123 let mut n = PerfectIntegratorNeuron::default();
1124 for _ in 0..50 {
1125 n.step(0.5);
1126 }
1127 n.reset();
1128 assert!((n.v - n.v_reset).abs() < 1e-10);
1129 }
1130 #[test]
1131 fn pi_bounded() {
1132 let mut n = PerfectIntegratorNeuron::default();
1133 for _ in 0..1000 {
1134 n.step(100.0);
1135 }
1136 assert!(n.v.is_finite());
1137 }
1138 #[test]
1139 fn pi_nan_no_panic() {
1140 PerfectIntegratorNeuron::default().step(f64::NAN);
1141 }
1142
1143 #[test]
1145 fn gated_lif_silent_without_input() {
1146 let mut n = GatedLIFNeuron::default();
1147 let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
1148 assert_eq!(t, 0);
1149 }
1150 #[test]
1151 fn gated_lif_reset_clears_state() {
1152 let mut n = GatedLIFNeuron::default();
1153 for _ in 0..20 {
1154 n.step(0.5);
1155 }
1156 n.reset();
1157 assert!((n.v - 0.0).abs() < 1e-10);
1158 }
1159 #[test]
1160 fn gated_lif_bounded() {
1161 let mut n = GatedLIFNeuron::default();
1162 for _ in 0..1000 {
1163 n.step(100.0);
1164 }
1165 assert!(n.v.is_finite());
1166 }
1167 #[test]
1168 fn gated_lif_nan_no_panic() {
1169 GatedLIFNeuron::default().step(f64::NAN);
1170 }
1171
1172 #[test]
1174 fn nlif_silent_without_input() {
1175 let mut n = NonlinearLIFNeuron::new();
1176 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
1177 assert_eq!(t, 0);
1178 }
1179 #[test]
1180 fn nlif_reset_clears_state() {
1181 let mut n = NonlinearLIFNeuron::new();
1182 for _ in 0..100 {
1183 n.step(500.0);
1184 }
1185 n.reset();
1186 assert!((n.v - n.v_rest).abs() < 1e-10);
1187 }
1188 #[test]
1189 fn nlif_bounded() {
1190 let mut n = NonlinearLIFNeuron::new();
1191 for _ in 0..2000 {
1192 n.step(1e4);
1193 }
1194 assert!(n.v.is_finite());
1195 }
1196 #[test]
1197 fn nlif_nan_no_panic() {
1198 NonlinearLIFNeuron::new().step(f64::NAN);
1199 }
1200 #[test]
1201 fn nlif_recovery_evolves() {
1202 let mut n = NonlinearLIFNeuron::new();
1203 for _ in 0..2000 {
1204 n.step(500.0);
1205 }
1206 assert!(
1207 n.w > 0.0,
1208 "recovery variable w should increase during spiking"
1209 );
1210 }
1211
1212 #[test]
1214 fn sfa_silent_without_input() {
1215 let mut n = SFANeuron::new();
1216 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
1217 assert_eq!(t, 0);
1218 }
1219 #[test]
1220 fn sfa_reset_clears_state() {
1221 let mut n = SFANeuron::new();
1222 for _ in 0..100 {
1223 n.step(30.0);
1224 }
1225 n.reset();
1226 assert!((n.v - n.v_rest).abs() < 1e-10);
1227 assert!((n.g_sfa - 0.0).abs() < 1e-10);
1228 }
1229 #[test]
1230 fn sfa_bounded() {
1231 let mut n = SFANeuron::new();
1232 for _ in 0..1000 {
1233 n.step(1e4);
1234 }
1235 assert!(n.v.is_finite());
1236 }
1237 #[test]
1238 fn sfa_nan_no_panic() {
1239 SFANeuron::new().step(f64::NAN);
1240 }
1241
1242 #[test]
1244 fn mat_silent_without_input() {
1245 let mut n = MATNeuron::new();
1246 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
1247 assert_eq!(t, 0);
1248 }
1249 #[test]
1250 fn mat_reset_clears_state() {
1251 let mut n = MATNeuron::new();
1252 for _ in 0..100 {
1253 n.step(30.0);
1254 }
1255 n.reset();
1256 assert!((n.v - n.v_rest).abs() < 1e-10);
1257 assert!((n.theta1 - 0.0).abs() < 1e-10);
1258 assert!((n.theta2 - 0.0).abs() < 1e-10);
1259 }
1260 #[test]
1261 fn mat_bounded() {
1262 let mut n = MATNeuron::new();
1263 for _ in 0..1000 {
1264 n.step(1e4);
1265 }
1266 assert!(n.v.is_finite());
1267 }
1268 #[test]
1269 fn mat_nan_no_panic() {
1270 MATNeuron::new().step(f64::NAN);
1271 }
1272
1273 #[test]
1275 fn escape_rate_reset_clears_state() {
1276 let mut n = EscapeRateNeuron::new(42);
1277 for _ in 0..100 {
1278 n.step(30.0);
1279 }
1280 n.reset();
1281 assert!((n.v - n.v_rest).abs() < 1e-10);
1282 }
1283 #[test]
1284 fn escape_rate_bounded() {
1285 let mut n = EscapeRateNeuron::new(42);
1286 for _ in 0..1000 {
1287 n.step(1e4);
1288 }
1289 assert!(n.v.is_finite());
1290 }
1291 #[test]
1292 fn escape_rate_nan_no_panic() {
1293 EscapeRateNeuron::new(42).step(f64::NAN);
1294 }
1295 #[test]
1296 fn escape_rate_seed_varies() {
1297 let mut n1 = EscapeRateNeuron::new(1);
1298 let mut n2 = EscapeRateNeuron::new(999);
1299 let t1: i32 = (0..1000).map(|_| n1.step(30.0)).sum();
1300 let t2: i32 = (0..1000).map(|_| n2.step(30.0)).sum();
1301 assert!(t1 > 0 && t2 > 0);
1302 }
1303
1304 #[test]
1306 fn klif_silent_without_input() {
1307 let mut n = KLIFNeuron::default();
1308 let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
1309 assert_eq!(t, 0);
1310 }
1311 #[test]
1312 fn klif_reset_clears_state() {
1313 let mut n = KLIFNeuron::default();
1314 for _ in 0..20 {
1315 n.step(0.5);
1316 }
1317 n.reset();
1318 assert!((n.v - 0.0).abs() < 1e-10);
1319 }
1320 #[test]
1321 fn klif_bounded() {
1322 let mut n = KLIFNeuron::default();
1323 for _ in 0..1000 {
1324 n.step(100.0);
1325 }
1326 assert!(n.v.is_finite());
1327 }
1328 #[test]
1329 fn klif_nan_no_panic() {
1330 KLIFNeuron::default().step(f64::NAN);
1331 }
1332
1333 #[test]
1335 fn ilif_silent_without_input() {
1336 let mut n = InhibitoryLIFNeuron::default();
1337 let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
1338 assert_eq!(t, 0);
1339 }
1340 #[test]
1341 fn ilif_reset_clears_state() {
1342 let mut n = InhibitoryLIFNeuron::default();
1343 for _ in 0..50 {
1344 n.step(0.8);
1345 }
1346 n.reset();
1347 assert!((n.v - 0.0).abs() < 1e-10);
1348 assert!((n.inh_trace - 0.0).abs() < 1e-10);
1349 }
1350 #[test]
1351 fn ilif_bounded() {
1352 let mut n = InhibitoryLIFNeuron::default();
1353 for _ in 0..1000 {
1354 n.step(100.0);
1355 }
1356 assert!(n.v.is_finite());
1357 }
1358 #[test]
1359 fn ilif_nan_no_panic() {
1360 InhibitoryLIFNeuron::default().step(f64::NAN);
1361 }
1362
1363 #[test]
1365 fn clif_silent_without_input() {
1366 let mut n = ComplementaryLIFNeuron::default();
1367 let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
1368 assert_eq!(t, 0);
1369 }
1370 #[test]
1371 fn clif_reset_clears_state() {
1372 let mut n = ComplementaryLIFNeuron::default();
1373 for _ in 0..20 {
1374 n.step(0.5);
1375 }
1376 n.reset();
1377 assert!((n.v_pos - 0.0).abs() < 1e-10);
1378 assert!((n.v_neg - 0.0).abs() < 1e-10);
1379 }
1380 #[test]
1381 fn clif_bounded() {
1382 let mut n = ComplementaryLIFNeuron::default();
1383 for _ in 0..1000 {
1384 n.step(100.0);
1385 }
1386 assert!(n.v_pos.is_finite());
1387 }
1388 #[test]
1389 fn clif_nan_no_panic() {
1390 ComplementaryLIFNeuron::default().step(f64::NAN);
1391 }
1392
1393 #[test]
1395 fn plif_silent_without_input() {
1396 let mut n = ParametricLIFNeuron::default();
1397 let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
1398 assert_eq!(t, 0);
1399 }
1400 #[test]
1401 fn plif_reset_clears_state() {
1402 let mut n = ParametricLIFNeuron::default();
1403 for _ in 0..20 {
1404 n.step(1.5);
1405 }
1406 n.reset();
1407 assert!((n.v - 0.0).abs() < 1e-10);
1408 }
1409 #[test]
1410 fn plif_bounded() {
1411 let mut n = ParametricLIFNeuron::default();
1412 for _ in 0..1000 {
1413 n.step(100.0);
1414 }
1415 assert!(n.v.is_finite());
1416 }
1417 #[test]
1418 fn plif_nan_no_panic() {
1419 ParametricLIFNeuron::default().step(f64::NAN);
1420 }
1421
1422 #[test]
1424 fn nrlif_reset_clears_state() {
1425 let mut n = NonResettingLIFNeuron::new();
1426 for _ in 0..500 {
1427 n.step(30.0);
1428 }
1429 n.reset();
1430 assert!((n.v - n.v_rest).abs() < 1e-10);
1431 assert!((n.theta - n.theta_rest).abs() < 1e-10);
1432 }
1433 #[test]
1434 fn nrlif_bounded() {
1435 let mut n = NonResettingLIFNeuron::new();
1436 for _ in 0..5000 {
1437 n.step(1e4);
1438 }
1439 assert!(n.v.is_finite());
1440 }
1441 #[test]
1442 fn nrlif_nan_no_panic() {
1443 NonResettingLIFNeuron::new().step(f64::NAN);
1444 }
1445 #[test]
1446 fn nrlif_negative_no_crash() {
1447 let mut n = NonResettingLIFNeuron::new();
1448 for _ in 0..500 {
1449 n.step(-10.0);
1450 }
1451 assert!(n.v.is_finite());
1452 }
1453
1454 #[test]
1456 fn atif_silent_without_input() {
1457 let mut n = AdaptiveThresholdIFNeuron::new();
1458 let t: i32 = (0..500).map(|_| n.step(0.0)).sum();
1459 assert_eq!(t, 0);
1460 }
1461 #[test]
1462 fn atif_reset_clears_state() {
1463 let mut n = AdaptiveThresholdIFNeuron::new();
1464 for _ in 0..100 {
1465 n.step(30.0);
1466 }
1467 n.reset();
1468 assert!((n.v - n.v_rest).abs() < 1e-10);
1469 }
1470 #[test]
1471 fn atif_bounded() {
1472 let mut n = AdaptiveThresholdIFNeuron::new();
1473 for _ in 0..1000 {
1474 n.step(1e4);
1475 }
1476 assert!(n.v.is_finite());
1477 }
1478 #[test]
1479 fn atif_nan_no_panic() {
1480 AdaptiveThresholdIFNeuron::new().step(f64::NAN);
1481 }
1482 #[test]
1483 fn atif_threshold_increases_with_spikes() {
1484 let mut n = AdaptiveThresholdIFNeuron::new();
1485 let theta_init = n.theta;
1486 for _ in 0..500 {
1487 n.step(30.0);
1488 }
1489 assert!(n.theta > theta_init, "threshold should adapt after spikes");
1490 }
1491
1492 #[test]
1494 fn sd_reset_clears_state() {
1495 let mut n = SigmaDeltaNeuron::default();
1496 for _ in 0..10 {
1497 n.step(0.3);
1498 }
1499 n.reset();
1500 assert!((n.sigma - 0.0).abs() < 1e-10);
1501 }
1502 #[test]
1503 fn sd_bounded() {
1504 let mut n = SigmaDeltaNeuron::default();
1505 for _ in 0..1000 {
1506 n.step(100.0);
1507 }
1508 assert!(n.sigma.is_finite());
1509 }
1510 #[test]
1511 fn sd_nan_no_panic() {
1512 SigmaDeltaNeuron::default().step(f64::NAN);
1513 }
1514
1515 #[test]
1517 fn energy_lif_silent_without_input() {
1518 let mut n = EnergyLIFNeuron::new();
1519 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
1520 assert_eq!(t, 0);
1521 }
1522 #[test]
1523 fn energy_lif_reset_clears_state() {
1524 let mut n = EnergyLIFNeuron::new();
1525 for _ in 0..100 {
1526 n.step(30.0);
1527 }
1528 n.reset();
1529 assert!((n.v - n.v_rest).abs() < 1e-10);
1530 }
1531 #[test]
1532 fn energy_lif_bounded() {
1533 let mut n = EnergyLIFNeuron::new();
1534 for _ in 0..1000 {
1535 n.step(1e4);
1536 }
1537 assert!(n.v.is_finite());
1538 }
1539 #[test]
1540 fn energy_lif_nan_no_panic() {
1541 EnergyLIFNeuron::new().step(f64::NAN);
1542 }
1543 #[test]
1544 fn energy_lif_epsilon_depletes() {
1545 let mut n = EnergyLIFNeuron::new();
1546 let e0 = n.epsilon;
1547 for _ in 0..200 {
1548 n.step(30.0);
1549 }
1550 assert!(n.epsilon < e0, "energy should deplete during spiking");
1551 }
1552
1553 #[test]
1555 fn iqif_silent_without_input() {
1556 let mut n = IntegerQIFNeuron::default();
1557 let t: i32 = (0..200).map(|_| n.step(0)).sum();
1558 assert_eq!(t, 0);
1559 }
1560 #[test]
1561 fn iqif_reset_clears_state() {
1562 let mut n = IntegerQIFNeuron::default();
1563 for _ in 0..100 {
1564 n.step(50);
1565 }
1566 n.reset();
1567 assert_eq!(n.v, 0);
1568 }
1569 #[test]
1570 fn iqif_bounded() {
1571 let mut n = IntegerQIFNeuron::default();
1572 for _ in 0..1000 {
1573 n.step(10000);
1574 }
1575 }
1577 #[test]
1578 fn iqif_negative_no_crash() {
1579 let mut n = IntegerQIFNeuron::default();
1580 for _ in 0..500 {
1581 n.step(-50);
1582 }
1583 }
1584
1585 #[test]
1587 fn cfc_reset_clears_state() {
1588 let mut n = ClosedFormContinuousNeuron::new();
1589 for _ in 0..50 {
1590 n.step(5.0);
1591 }
1592 n.reset();
1593 assert!((n.x - 0.0).abs() < 1e-10);
1594 }
1595 #[test]
1596 fn cfc_bounded() {
1597 let mut n = ClosedFormContinuousNeuron {
1598 v_threshold: 0.9,
1599 ..ClosedFormContinuousNeuron::new()
1600 };
1601 for _ in 0..1000 {
1602 n.step(100.0);
1603 }
1604 assert!(n.x.is_finite());
1605 }
1606 #[test]
1607 fn cfc_nan_no_panic() {
1608 ClosedFormContinuousNeuron::new().step(f64::NAN);
1609 }
1610
1611 #[test]
1614 fn stochastic_lif_fires_with_input() {
1615 let mut n = StochasticLIFNeuron::new(42);
1616 let total: i32 = (0..500).map(|_| n.step(2.0)).sum();
1617 assert!(total > 0, "StochasticLIF should fire with strong input");
1618 }
1619
1620 #[test]
1621 fn stochastic_lif_silent_without_input() {
1622 let mut n = StochasticLIFNeuron::new(42);
1623 let total: i32 = (0..500).map(|_| n.step(0.0)).sum();
1625 assert_eq!(
1626 total, 0,
1627 "StochasticLIF should be silent at zero input with no noise"
1628 );
1629 }
1630
1631 #[test]
1632 fn stochastic_lif_reset_clears_state() {
1633 let mut n = StochasticLIFNeuron::new(42);
1634 for _ in 0..100 {
1635 n.step(2.0);
1636 }
1637 n.reset();
1638 assert!(
1639 (n.v - n.v_rest).abs() < 1e-12,
1640 "reset must restore v to v_rest"
1641 );
1642 assert_eq!(
1643 n.refractory_counter, 0,
1644 "reset must clear refractory counter"
1645 );
1646 }
1647
1648 #[test]
1649 fn stochastic_lif_extreme_input_bounded() {
1650 let mut n = StochasticLIFNeuron::new(42);
1651 for _ in 0..1000 {
1652 n.step(1e6);
1653 }
1654 assert!(n.v.is_finite(), "v must stay finite under extreme input");
1655 }
1656
1657 #[test]
1658 fn stochastic_lif_nan_input_stays_finite() {
1659 let mut n = StochasticLIFNeuron::new(42);
1660 for _ in 0..10 {
1662 n.step(1.0);
1663 }
1664 let v_before = n.v;
1665 n.step(f64::NAN);
1666 let _ = v_before;
1669 }
1670
1671 #[test]
1672 fn stochastic_lif_negative_input_no_crash() {
1673 let mut n = StochasticLIFNeuron::new(42);
1674 for _ in 0..500 {
1675 n.step(-10.0);
1676 }
1677 assert!(n.v.is_finite(), "v must stay finite with negative input");
1678 }
1679
1680 #[test]
1681 fn stochastic_lif_noise_affects_firing() {
1682 let mut n_noisy = StochasticLIFNeuron::new(123);
1684 n_noisy.noise_std = 0.5;
1685 let total_noisy: i32 = (0..5000).map(|_| n_noisy.step(0.8)).sum();
1686
1687 let mut n_quiet = StochasticLIFNeuron::new(123);
1688 n_quiet.noise_std = 0.0;
1689 let total_quiet: i32 = (0..5000).map(|_| n_quiet.step(0.8)).sum();
1690
1691 assert!(
1694 total_noisy != total_quiet || total_noisy > 0,
1695 "noise should affect firing pattern"
1696 );
1697 }
1698
1699 #[test]
1700 fn stochastic_lif_refractory_blocks_spikes() {
1701 let mut n = StochasticLIFNeuron::new(42);
1702 n.refractory_period = 5;
1703 let mut spikes = Vec::new();
1704 for _ in 0..500 {
1705 spikes.push(n.step(3.0));
1706 }
1707 for (i, &s) in spikes.iter().enumerate() {
1709 if s == 1 {
1710 for j in 1..=5 {
1711 if i + j < spikes.len() {
1712 assert_eq!(
1713 spikes[i + j],
1714 0,
1715 "step {} after spike at {} must be silent (refractory)",
1716 j,
1717 i
1718 );
1719 }
1720 }
1721 }
1722 }
1723 }
1724}
1725
1726#[derive(Clone, Debug)]
1728pub struct StochasticLIFNeuron {
1729 pub v: f64,
1730 pub v_rest: f64,
1731 pub v_reset: f64,
1732 pub v_threshold: f64,
1733 pub tau_mem: f64,
1734 pub dt: f64,
1735 pub noise_std: f64,
1736 pub resistance: f64,
1737 pub refractory_period: i32,
1738 pub refractory_counter: i32,
1739 rng: Xoshiro256PlusPlus,
1740}
1741
1742impl StochasticLIFNeuron {
1743 pub fn new(seed: u64) -> Self {
1744 Self {
1745 v: 0.0,
1746 v_rest: 0.0,
1747 v_reset: 0.0,
1748 v_threshold: 1.0,
1749 tau_mem: 20.0,
1750 dt: 1.0,
1751 noise_std: 0.0,
1752 resistance: 1.0,
1753 refractory_period: 0,
1754 refractory_counter: 0,
1755 rng: Xoshiro256PlusPlus::seed_from_u64(seed),
1756 }
1757 }
1758
1759 pub fn step(&mut self, current: f64) -> i32 {
1760 if self.refractory_counter > 0 {
1761 self.refractory_counter -= 1;
1762 self.v = self.v_rest;
1763 return 0;
1764 }
1765 let dv_leak = -(self.v - self.v_rest) * (self.dt / self.tau_mem);
1766 let dv_input = self.resistance * current * self.dt;
1767 let mut dv_noise = 0.0;
1768 if self.noise_std > 0.0 {
1769 let u1: f64 = self.rng.random();
1770 let u2: f64 = self.rng.random();
1771 let z0 = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
1772 dv_noise = self.noise_std * self.dt.sqrt() * z0;
1773 }
1774 self.v += dv_leak + dv_input + dv_noise;
1775 if self.v >= self.v_threshold {
1776 self.v = self.v_reset;
1777 self.refractory_counter = self.refractory_period;
1778 1
1779 } else {
1780 0
1781 }
1782 }
1783
1784 pub fn reset(&mut self) {
1785 self.v = self.v_rest;
1786 self.refractory_counter = 0;
1787 }
1788}
1789
1790impl Default for StochasticLIFNeuron {
1791 fn default() -> Self {
1792 Self::new(42)
1793 }
1794}