Skip to main content

sc_neurocore_engine/neurons/
trivial.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later | Commercial license available
2// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
3// © Code 2020–2026 Miroslav Šotek. All rights reserved.
4// ORCID: 0009-0009-3560-0851
5// Contact: www.anulum.li | protoscience@anulum.li
6// SC-NeuroCore — Simple integrate-and-fire variants
7
8//! Simple integrate-and-fire variants.
9
10use rand::{RngExt, SeedableRng};
11use rand_xoshiro::Xoshiro256PlusPlus;
12
13/// Quadratic Integrate-and-Fire — canonical Type-I excitability.
14/// dv/dt = v² + I, reset at v_peak.
15#[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    pub fn step(&mut self, current: f64) -> i32 {
34        self.v += (self.v * self.v + current) * self.dt;
35        if self.v >= self.v_peak {
36            self.v = self.v_reset;
37            1
38        } else {
39            0
40        }
41    }
42
43    pub fn reset(&mut self) {
44        self.v = self.v_reset;
45    }
46}
47
48impl Default for QuadraticIFNeuron {
49    fn default() -> Self {
50        Self::new(-1.0, 1.0, 0.01)
51    }
52}
53
54/// Theta neuron — Ermentrout & Kopell canonical form.
55/// dθ/dt = (1 - cosθ) + (1 + cosθ)·I, spike at θ crossing π.
56#[derive(Clone, Debug)]
57pub struct ThetaNeuron {
58    pub theta: f64,
59    pub dt: f64,
60}
61
62impl ThetaNeuron {
63    pub fn new(dt: f64) -> Self {
64        Self { theta: 0.0, dt }
65    }
66
67    pub fn step(&mut self, current: f64) -> i32 {
68        let prev = self.theta;
69        self.theta += ((1.0 - self.theta.cos()) + (1.0 + self.theta.cos()) * current) * self.dt;
70        if self.theta >= std::f64::consts::PI && prev < std::f64::consts::PI {
71            self.theta -= 2.0 * std::f64::consts::PI;
72            1
73        } else {
74            0
75        }
76    }
77
78    pub fn reset(&mut self) {
79        self.theta = 0.0;
80    }
81}
82
83impl Default for ThetaNeuron {
84    fn default() -> Self {
85        Self::new(0.01)
86    }
87}
88
89/// Perfect integrator — no leak, pure capacitive charging.
90#[derive(Clone, Debug)]
91pub struct PerfectIntegratorNeuron {
92    pub v: f64,
93    pub c_m: f64,
94    pub v_threshold: f64,
95    pub v_reset: f64,
96    pub dt: f64,
97}
98
99impl PerfectIntegratorNeuron {
100    pub fn new(c_m: f64, v_threshold: f64, dt: f64) -> Self {
101        Self {
102            v: 0.0,
103            c_m,
104            v_threshold,
105            v_reset: 0.0,
106            dt,
107        }
108    }
109
110    pub fn step(&mut self, current: f64) -> i32 {
111        self.v += (current / self.c_m) * self.dt;
112        if self.v >= self.v_threshold {
113            self.v = self.v_reset;
114            1
115        } else {
116            0
117        }
118    }
119
120    pub fn reset(&mut self) {
121        self.v = self.v_reset;
122    }
123}
124
125impl Default for PerfectIntegratorNeuron {
126    fn default() -> Self {
127        Self::new(1.0, 1.0, 0.1)
128    }
129}
130
131/// Gated LIF — learnable decay and input gates.
132#[derive(Clone, Debug)]
133pub struct GatedLIFNeuron {
134    pub v: f64,
135    pub gate_v: f64,
136    pub gate_i: f64,
137    pub v_threshold: f64,
138    pub dt: f64,
139}
140
141impl GatedLIFNeuron {
142    pub fn new(gate_v: f64, gate_i: f64, v_threshold: f64) -> Self {
143        Self {
144            v: 0.0,
145            gate_v,
146            gate_i,
147            v_threshold,
148            dt: 1.0,
149        }
150    }
151
152    pub fn step(&mut self, current: f64) -> i32 {
153        self.v = self.gate_v * self.v + self.gate_i * current;
154        if self.v >= self.v_threshold {
155            self.v -= self.v_threshold;
156            1
157        } else {
158            0
159        }
160    }
161
162    pub fn reset(&mut self) {
163        self.v = 0.0;
164    }
165}
166
167impl Default for GatedLIFNeuron {
168    fn default() -> Self {
169        Self::new(0.9, 1.0, 1.0)
170    }
171}
172
173/// Nonlinear LIF — cubic f-I curve with adaptation. Touboul & Brette 2008.
174#[derive(Clone, Debug)]
175pub struct NonlinearLIFNeuron {
176    pub v: f64,
177    pub w: f64,
178    pub v_rest: f64,
179    pub v_crit: f64,
180    pub v_threshold: f64,
181    pub v_reset: f64,
182    pub a: f64,
183    pub b: f64,
184    pub tau_w: f64,
185    pub c_m: f64,
186    pub dt: f64,
187}
188
189impl NonlinearLIFNeuron {
190    pub fn new() -> Self {
191        Self {
192            v: -65.0,
193            w: 0.0,
194            v_rest: -65.0,
195            v_crit: -40.0,
196            v_threshold: -20.0,
197            v_reset: -65.0,
198            a: 0.04,
199            b: 0.5,
200            tau_w: 100.0,
201            c_m: 1.0,
202            dt: 0.1,
203        }
204    }
205
206    pub fn step(&mut self, current: f64) -> i32 {
207        let v_prev = self.v;
208        let cubic = self.a * (self.v - self.v_rest) * (self.v - self.v_crit);
209        self.v += (cubic - self.w + current) / self.c_m * self.dt;
210        self.w += (self.b * (self.v - self.v_rest) - self.w) / self.tau_w * self.dt;
211        if self.v >= self.v_threshold && v_prev < self.v_threshold {
212            self.v = self.v_reset;
213            1
214        } else {
215            0
216        }
217    }
218
219    pub fn reset(&mut self) {
220        self.v = self.v_rest;
221        self.w = 0.0;
222    }
223}
224
225impl Default for NonlinearLIFNeuron {
226    fn default() -> Self {
227        Self::new()
228    }
229}
230
231/// Spike-Frequency Adaptation LIF. Benda & Herz 2003.
232#[derive(Clone, Debug)]
233pub struct SFANeuron {
234    pub v: f64,
235    pub g_sfa: f64,
236    pub v_rest: f64,
237    pub v_reset: f64,
238    pub v_threshold: f64,
239    pub tau_m: f64,
240    pub tau_sfa: f64,
241    pub delta_g: f64,
242    pub e_k: f64,
243    pub resistance: f64,
244    pub dt: f64,
245}
246
247impl SFANeuron {
248    pub fn new() -> Self {
249        Self {
250            v: -70.0,
251            g_sfa: 0.0,
252            v_rest: -70.0,
253            v_reset: -70.0,
254            v_threshold: -50.0,
255            tau_m: 10.0,
256            tau_sfa: 200.0,
257            delta_g: 0.5,
258            e_k: -80.0,
259            resistance: 1.0,
260            dt: 1.0,
261        }
262    }
263
264    pub fn step(&mut self, current: f64) -> i32 {
265        self.v += (-(self.v - self.v_rest) - self.g_sfa * (self.v - self.e_k)
266            + self.resistance * current)
267            / self.tau_m
268            * self.dt;
269        self.g_sfa *= (-self.dt / self.tau_sfa).exp();
270        if self.v >= self.v_threshold {
271            self.v = self.v_reset;
272            self.g_sfa += self.delta_g;
273            1
274        } else {
275            0
276        }
277    }
278
279    pub fn reset(&mut self) {
280        self.v = self.v_rest;
281        self.g_sfa = 0.0;
282    }
283}
284
285impl Default for SFANeuron {
286    fn default() -> Self {
287        Self::new()
288    }
289}
290
291/// Multi-timescale Adaptive Threshold. Kobayashi et al. 2009.
292#[derive(Clone, Debug)]
293pub struct MATNeuron {
294    pub v: f64,
295    pub theta1: f64,
296    pub theta2: f64,
297    pub v_rest: f64,
298    pub v_reset: f64,
299    pub v_threshold_base: f64,
300    pub tau_m: f64,
301    pub tau_1: f64,
302    pub tau_2: f64,
303    pub h1: f64,
304    pub h2: f64,
305    pub resistance: f64,
306    pub dt: f64,
307}
308
309impl MATNeuron {
310    pub fn new() -> Self {
311        Self {
312            v: -70.0,
313            theta1: 0.0,
314            theta2: 0.0,
315            v_rest: -70.0,
316            v_reset: -70.0,
317            v_threshold_base: -50.0,
318            tau_m: 10.0,
319            tau_1: 10.0,
320            tau_2: 200.0,
321            h1: 5.0,
322            h2: 3.0,
323            resistance: 1.0,
324            dt: 1.0,
325        }
326    }
327
328    pub fn step(&mut self, current: f64) -> i32 {
329        self.v += (-(self.v - self.v_rest) + self.resistance * current) / self.tau_m * self.dt;
330        self.theta1 *= (-self.dt / self.tau_1).exp();
331        self.theta2 *= (-self.dt / self.tau_2).exp();
332        let threshold = self.v_threshold_base + self.theta1 + self.theta2;
333        if self.v >= threshold {
334            self.v = self.v_reset;
335            self.theta1 += self.h1;
336            self.theta2 += self.h2;
337            1
338        } else {
339            0
340        }
341    }
342
343    pub fn reset(&mut self) {
344        self.v = self.v_rest;
345        self.theta1 = 0.0;
346        self.theta2 = 0.0;
347    }
348}
349
350impl Default for MATNeuron {
351    fn default() -> Self {
352        Self::new()
353    }
354}
355
356/// Escape-rate neuron — stochastic IF with exponential hazard. Gerstner 2000.
357#[derive(Clone, Debug)]
358pub struct EscapeRateNeuron {
359    pub v: f64,
360    pub v_rest: f64,
361    pub v_reset: f64,
362    pub v_threshold: f64,
363    pub tau_m: f64,
364    pub rho_0: f64,
365    pub delta_u: f64,
366    pub resistance: f64,
367    pub dt: f64,
368    rng: Xoshiro256PlusPlus,
369}
370
371impl EscapeRateNeuron {
372    pub fn new(seed: u64) -> Self {
373        Self {
374            v: -70.0,
375            v_rest: -70.0,
376            v_reset: -70.0,
377            v_threshold: -50.0,
378            tau_m: 10.0,
379            rho_0: 0.001,
380            delta_u: 3.0,
381            resistance: 1.0,
382            dt: 1.0,
383            rng: Xoshiro256PlusPlus::seed_from_u64(seed),
384        }
385    }
386
387    pub fn step(&mut self, current: f64) -> i32 {
388        self.v += (-(self.v - self.v_rest) + self.resistance * current) / self.tau_m * self.dt;
389        let rate = self.rho_0 * ((self.v - self.v_threshold) / self.delta_u).exp();
390        if self.rng.random::<f64>() < rate * self.dt {
391            self.v = self.v_reset;
392            1
393        } else {
394            0
395        }
396    }
397
398    pub fn reset(&mut self) {
399        self.v = self.v_rest;
400    }
401}
402
403/// KLIF — learnable LIF with scaling factor k. Eshraghian et al. 2021.
404#[derive(Clone, Debug)]
405pub struct KLIFNeuron {
406    pub v: f64,
407    pub k: f64,
408    pub alpha: f64,
409    pub v_threshold: f64,
410    pub v_reset: f64,
411}
412
413impl KLIFNeuron {
414    pub fn new(tau: f64, k: f64, dt: f64) -> Self {
415        Self {
416            v: 0.0,
417            k,
418            alpha: (-dt / tau).exp(),
419            v_threshold: 1.0,
420            v_reset: 0.0,
421        }
422    }
423
424    pub fn step(&mut self, current: f64) -> i32 {
425        self.v = self.alpha * self.v + self.k * current;
426        if self.v >= self.v_threshold {
427            self.v = self.v_reset;
428            1
429        } else {
430            0
431        }
432    }
433
434    pub fn reset(&mut self) {
435        self.v = 0.0;
436    }
437}
438
439impl Default for KLIFNeuron {
440    fn default() -> Self {
441        Self::new(10.0, 1.0, 1.0)
442    }
443}
444
445/// Inhibitory LIF — LIF with self-inhibition trace.
446#[derive(Clone, Debug)]
447pub struct InhibitoryLIFNeuron {
448    pub v: f64,
449    pub inh_trace: f64,
450    pub alpha_m: f64,
451    pub alpha_inh: f64,
452    pub v_threshold: f64,
453    pub inh_strength: f64,
454}
455
456impl InhibitoryLIFNeuron {
457    pub fn new(tau_m: f64, tau_inh: f64, dt: f64) -> Self {
458        Self {
459            v: 0.0,
460            inh_trace: 0.0,
461            alpha_m: (-dt / tau_m).exp(),
462            alpha_inh: (-dt / tau_inh).exp(),
463            v_threshold: 1.0,
464            inh_strength: 0.5,
465        }
466    }
467
468    pub fn step(&mut self, current: f64) -> i32 {
469        self.inh_trace *= self.alpha_inh;
470        self.v = self.alpha_m * self.v + current - self.inh_strength * self.inh_trace;
471        if self.v >= self.v_threshold {
472            self.v = 0.0;
473            self.inh_trace += 1.0;
474            1
475        } else {
476            0
477        }
478    }
479
480    pub fn reset(&mut self) {
481        self.v = 0.0;
482        self.inh_trace = 0.0;
483    }
484}
485
486impl Default for InhibitoryLIFNeuron {
487    fn default() -> Self {
488        Self::new(10.0, 5.0, 1.0)
489    }
490}
491
492/// Complementary LIF — dual-path excitatory/inhibitory.
493#[derive(Clone, Debug)]
494pub struct ComplementaryLIFNeuron {
495    pub v_pos: f64,
496    pub v_neg: f64,
497    pub alpha: f64,
498    pub v_threshold: f64,
499}
500
501impl ComplementaryLIFNeuron {
502    pub fn new(tau: f64, dt: f64, v_threshold: f64) -> Self {
503        Self {
504            v_pos: 0.0,
505            v_neg: 0.0,
506            alpha: (-dt / tau).exp(),
507            v_threshold,
508        }
509    }
510
511    pub fn step(&mut self, current: f64) -> i32 {
512        let inp_pos = current.max(0.0);
513        let inp_neg = (-current).max(0.0);
514        self.v_pos = self.alpha * self.v_pos + inp_pos;
515        self.v_neg = self.alpha * self.v_neg + inp_neg;
516        let diff = self.v_pos - self.v_neg;
517        if diff >= self.v_threshold {
518            self.v_pos = 0.0;
519            self.v_neg = 0.0;
520            1
521        } else if diff <= -self.v_threshold {
522            self.v_pos = 0.0;
523            self.v_neg = 0.0;
524            -1
525        } else {
526            0
527        }
528    }
529
530    pub fn reset(&mut self) {
531        self.v_pos = 0.0;
532        self.v_neg = 0.0;
533    }
534}
535
536impl Default for ComplementaryLIFNeuron {
537    fn default() -> Self {
538        Self::new(10.0, 1.0, 1.0)
539    }
540}
541
542/// Parametric LIF — learnable decay via sigmoid(a). Fang et al. 2021.
543#[derive(Clone, Debug)]
544pub struct ParametricLIFNeuron {
545    pub v: f64,
546    pub a: f64,
547    pub threshold: f64,
548}
549
550impl ParametricLIFNeuron {
551    pub fn new(a: f64, threshold: f64) -> Self {
552        Self {
553            v: 0.0,
554            a,
555            threshold,
556        }
557    }
558
559    pub fn step(&mut self, current: f64) -> i32 {
560        let alpha = 1.0 / (1.0 + (-self.a).exp());
561        let spike = if self.v >= self.threshold { 1 } else { 0 };
562        self.v = alpha * self.v * (1.0 - spike as f64) + current;
563        spike
564    }
565
566    pub fn reset(&mut self) {
567        self.v = 0.0;
568    }
569}
570
571impl Default for ParametricLIFNeuron {
572    fn default() -> Self {
573        Self::new(0.0, 1.0)
574    }
575}
576
577/// Non-resetting LIF with adaptive threshold. Brette 2004.
578#[derive(Clone, Debug)]
579pub struct NonResettingLIFNeuron {
580    pub v: f64,
581    pub theta: f64,
582    pub v_rest: f64,
583    pub theta_rest: f64,
584    pub delta_theta: f64,
585    pub tau_m: f64,
586    pub tau_theta: f64,
587    pub r_m: f64,
588    pub dt: f64,
589}
590
591impl NonResettingLIFNeuron {
592    pub fn new() -> Self {
593        Self {
594            v: -65.0,
595            theta: -50.0,
596            v_rest: -65.0,
597            theta_rest: -50.0,
598            delta_theta: 5.0,
599            tau_m: 10.0,
600            tau_theta: 50.0,
601            r_m: 1.0,
602            dt: 0.1,
603        }
604    }
605
606    pub fn step(&mut self, current: f64) -> i32 {
607        self.v += (-(self.v - self.v_rest) + self.r_m * current) / self.tau_m * self.dt;
608        self.theta += (-(self.theta - self.theta_rest)) / self.tau_theta * self.dt;
609        if self.v >= self.theta {
610            self.theta += self.delta_theta;
611            1
612        } else {
613            0
614        }
615    }
616
617    pub fn reset(&mut self) {
618        self.v = self.v_rest;
619        self.theta = self.theta_rest;
620    }
621}
622
623impl Default for NonResettingLIFNeuron {
624    fn default() -> Self {
625        Self::new()
626    }
627}
628
629/// Adaptive-threshold IF. Platkiewicz & Brette 2010.
630#[derive(Clone, Debug)]
631pub struct AdaptiveThresholdIFNeuron {
632    pub v: f64,
633    pub theta: f64,
634    pub v_rest: f64,
635    pub v_reset: f64,
636    pub theta_rest: f64,
637    pub delta_theta: f64,
638    pub tau_m: f64,
639    pub tau_theta: f64,
640    pub dt: f64,
641}
642
643impl AdaptiveThresholdIFNeuron {
644    pub fn new() -> Self {
645        Self {
646            v: -65.0,
647            theta: -50.0,
648            v_rest: -65.0,
649            v_reset: -65.0,
650            theta_rest: -50.0,
651            delta_theta: 5.0,
652            tau_m: 10.0,
653            tau_theta: 50.0,
654            dt: 0.1,
655        }
656    }
657
658    pub fn step(&mut self, current: f64) -> i32 {
659        self.v += (-(self.v - self.v_rest) + current) / self.tau_m * self.dt;
660        self.theta += (-(self.theta - self.theta_rest)) / self.tau_theta * self.dt;
661        if self.v >= self.theta {
662            self.v = self.v_reset;
663            self.theta += self.delta_theta;
664            1
665        } else {
666            0
667        }
668    }
669
670    pub fn reset(&mut self) {
671        self.v = self.v_rest;
672        self.theta = self.theta_rest;
673    }
674}
675
676impl Default for AdaptiveThresholdIFNeuron {
677    fn default() -> Self {
678        Self::new()
679    }
680}
681
682/// Sigma-Delta neuron — first-order delta modulation.
683#[derive(Clone, Debug)]
684pub struct SigmaDeltaNeuron {
685    pub sigma: f64,
686    pub v_threshold: f64,
687}
688
689impl SigmaDeltaNeuron {
690    pub fn new(v_threshold: f64) -> Self {
691        Self {
692            sigma: 0.0,
693            v_threshold,
694        }
695    }
696
697    pub fn step(&mut self, current: f64) -> i32 {
698        self.sigma += current;
699        if self.sigma >= self.v_threshold {
700            self.sigma -= self.v_threshold;
701            1
702        } else if self.sigma <= -self.v_threshold {
703            self.sigma += self.v_threshold;
704            -1
705        } else {
706            0
707        }
708    }
709
710    pub fn reset(&mut self) {
711        self.sigma = 0.0;
712    }
713}
714
715impl Default for SigmaDeltaNeuron {
716    fn default() -> Self {
717        Self::new(1.0)
718    }
719}
720
721/// Energy-aware LIF — metabolic cost modulates gain. Sengupta et al. 2013.
722#[derive(Clone, Debug)]
723pub struct EnergyLIFNeuron {
724    pub v: f64,
725    pub epsilon: f64,
726    pub v_rest: f64,
727    pub v_reset: f64,
728    pub v_threshold: f64,
729    pub tau_m: f64,
730    pub tau_e: f64,
731    pub alpha: f64,
732    pub epsilon_0: f64,
733    pub resistance: f64,
734    pub dt: f64,
735}
736
737impl EnergyLIFNeuron {
738    pub fn new() -> Self {
739        Self {
740            v: -70.0,
741            epsilon: 1.0,
742            v_rest: -70.0,
743            v_reset: -70.0,
744            v_threshold: -50.0,
745            tau_m: 10.0,
746            tau_e: 500.0,
747            alpha: 0.1,
748            epsilon_0: 1.0,
749            resistance: 1.0,
750            dt: 1.0,
751        }
752    }
753
754    pub fn step(&mut self, current: f64) -> i32 {
755        let effective_r = self.resistance * self.epsilon;
756        self.v += (-(self.v - self.v_rest) + effective_r * current) / self.tau_m * self.dt;
757        self.epsilon += (self.epsilon_0 - self.epsilon) / self.tau_e * self.dt;
758        if self.v >= self.v_threshold && self.epsilon > 0.1 {
759            self.v = self.v_reset;
760            self.epsilon -= self.alpha;
761            1
762        } else if self.v >= self.v_threshold {
763            self.v = self.v_threshold;
764            0
765        } else {
766            0
767        }
768    }
769
770    pub fn reset(&mut self) {
771        self.v = self.v_rest;
772        self.epsilon = self.epsilon_0;
773    }
774}
775
776impl Default for EnergyLIFNeuron {
777    fn default() -> Self {
778        Self::new()
779    }
780}
781
782/// Integer QIF — fixed-point quadratic IF for digital hardware.
783#[derive(Clone, Debug)]
784pub struct IntegerQIFNeuron {
785    pub v: i32,
786    pub k: i32,
787    pub v_threshold: i32,
788    pub v_reset: i32,
789    pub v_min: i32,
790}
791
792impl IntegerQIFNeuron {
793    pub fn new(k: i32, v_threshold: i32) -> Self {
794        Self {
795            v: 0,
796            k,
797            v_threshold,
798            v_reset: -v_threshold,
799            v_min: -2 * v_threshold,
800        }
801    }
802
803    pub fn step(&mut self, current: i32) -> i32 {
804        self.v = (self.v + (self.v.wrapping_mul(self.v) >> self.k) + current).max(self.v_min);
805        if self.v >= self.v_threshold {
806            self.v = self.v_reset;
807            1
808        } else {
809            0
810        }
811    }
812
813    pub fn reset(&mut self) {
814        self.v = 0;
815    }
816}
817
818impl Default for IntegerQIFNeuron {
819    fn default() -> Self {
820        Self::new(6, 1024)
821    }
822}
823
824/// Closed-form Continuous-depth (CfC) neuron. Hasani et al. 2022.
825#[derive(Clone, Debug)]
826pub struct ClosedFormContinuousNeuron {
827    pub x: f64,
828    pub w_tau: f64,
829    pub w_x: f64,
830    pub w_in: f64,
831    pub tau_base: f64,
832    pub bias: f64,
833    pub v_threshold: f64,
834    pub dt: f64,
835}
836
837impl ClosedFormContinuousNeuron {
838    pub fn new() -> Self {
839        Self {
840            x: 0.0,
841            w_tau: -0.5,
842            w_x: 0.8,
843            w_in: 1.0,
844            tau_base: 10.0,
845            bias: 0.0,
846            v_threshold: 1.0,
847            dt: 1.0,
848        }
849    }
850
851    pub fn step(&mut self, current: f64) -> i32 {
852        let sigma_tau = 1.0 / (1.0 + (-(self.w_tau * current + self.bias)).exp());
853        let tau_eff = (self.tau_base * sigma_tau).max(0.1);
854        let f_target = (self.w_x * self.x + self.w_in * current).tanh();
855        let decay = (-self.dt / tau_eff).exp();
856        self.x = self.x * decay + f_target * (1.0 - decay);
857        if self.x >= self.v_threshold {
858            self.x -= self.v_threshold;
859            1
860        } else {
861            0
862        }
863    }
864
865    pub fn reset(&mut self) {
866        self.x = 0.0;
867    }
868}
869
870impl Default for ClosedFormContinuousNeuron {
871    fn default() -> Self {
872        Self::new()
873    }
874}
875
876#[cfg(test)]
877mod tests {
878    use super::*;
879
880    #[test]
881    fn qif_fires_with_positive_input() {
882        let mut n = QuadraticIFNeuron::default();
883        let total: i32 = (0..1000).map(|_| n.step(0.5)).sum();
884        assert!(total > 0);
885    }
886
887    #[test]
888    fn theta_fires() {
889        let mut n = ThetaNeuron::default();
890        let total: i32 = (0..1000).map(|_| n.step(0.5)).sum();
891        assert!(total > 0);
892    }
893
894    #[test]
895    fn perfect_integrator_fires() {
896        let mut n = PerfectIntegratorNeuron::default();
897        let total: i32 = (0..100).map(|_| n.step(0.5)).sum();
898        assert!(total > 0);
899    }
900
901    #[test]
902    fn gated_lif_fires() {
903        let mut n = GatedLIFNeuron::default();
904        let total: i32 = (0..20).map(|_| n.step(0.5)).sum();
905        assert!(total > 0);
906    }
907
908    #[test]
909    fn nlif_fires() {
910        let mut n = NonlinearLIFNeuron::new();
911        let total: i32 = (0..2000).map(|_| n.step(500.0)).sum();
912        assert!(total > 0);
913    }
914
915    #[test]
916    fn sfa_fires_then_adapts() {
917        let mut n = SFANeuron::new();
918        let first: i32 = (0..100).map(|_| n.step(30.0)).sum();
919        let second: i32 = (0..100).map(|_| n.step(30.0)).sum();
920        assert!(first > 0);
921        assert!(second <= first + 2);
922    }
923
924    #[test]
925    fn mat_dual_threshold_adapts() {
926        let mut n = MATNeuron::new();
927        let total: i32 = (0..200).map(|_| n.step(30.0)).sum();
928        assert!(total > 0);
929        assert!(n.theta1 > 0.0 || n.theta2 > 0.0);
930    }
931
932    #[test]
933    fn escape_rate_stochastic() {
934        let mut n = EscapeRateNeuron::new(42);
935        let total: i32 = (0..1000).map(|_| n.step(30.0)).sum();
936        assert!(total > 0);
937    }
938
939    #[test]
940    fn klif_fires() {
941        let mut n = KLIFNeuron::default();
942        let total: i32 = (0..50).map(|_| n.step(0.5)).sum();
943        assert!(total > 0);
944    }
945
946    #[test]
947    fn ilif_self_inhibits() {
948        let mut n = InhibitoryLIFNeuron::default();
949        let total: i32 = (0..100).map(|_| n.step(0.8)).sum();
950        assert!(total > 0);
951    }
952
953    #[test]
954    fn clif_positive_spike() {
955        let mut n = ComplementaryLIFNeuron::default();
956        let total: i32 = (0..20).map(|_| n.step(0.5)).sum();
957        assert!(total > 0);
958    }
959
960    #[test]
961    fn plif_fires() {
962        let mut n = ParametricLIFNeuron::default();
963        let total: i32 = (0..20).map(|_| n.step(1.5)).sum();
964        assert!(total > 0);
965    }
966
967    #[test]
968    fn non_resetting_threshold_increases() {
969        let mut n = NonResettingLIFNeuron::new();
970        let initial = n.theta;
971        for _ in 0..5000 {
972            n.step(30.0);
973        }
974        assert!(n.theta > initial);
975    }
976
977    #[test]
978    fn adaptive_threshold_fires() {
979        let mut n = AdaptiveThresholdIFNeuron::new();
980        let total: i32 = (0..500).map(|_| n.step(30.0)).sum();
981        assert!(total > 0);
982    }
983
984    #[test]
985    fn sigma_delta_encodes() {
986        let mut n = SigmaDeltaNeuron::default();
987        let total: i32 = (0..10).map(|_| n.step(0.3)).sum();
988        assert!(total > 0);
989    }
990
991    #[test]
992    fn energy_lif_fires() {
993        let mut n = EnergyLIFNeuron::new();
994        let total: i32 = (0..200).map(|_| n.step(30.0)).sum();
995        assert!(total > 0);
996    }
997
998    #[test]
999    fn iqif_fires() {
1000        let mut n = IntegerQIFNeuron::default();
1001        let total: i32 = (0..200).map(|_| n.step(50)).sum();
1002        assert!(total > 0);
1003    }
1004
1005    #[test]
1006    fn cfc_activates() {
1007        let mut n = ClosedFormContinuousNeuron {
1008            v_threshold: 0.9,
1009            ..ClosedFormContinuousNeuron::new()
1010        };
1011        let total: i32 = (0..100).map(|_| n.step(5.0)).sum();
1012        assert!(total > 0);
1013    }
1014}