sc_neurocore_engine/neurons/trivial/
complementary_lif.rs1#[derive(Clone, Debug)]
11pub struct ComplementaryLIFNeuron {
12 pub v_pos: f64,
13 pub v_neg: f64,
14 pub alpha: f64,
15 pub v_threshold: f64,
16}
17
18impl ComplementaryLIFNeuron {
19 pub fn new(tau: f64, dt: f64, v_threshold: f64) -> Self {
20 Self {
21 v_pos: 0.0,
22 v_neg: 0.0,
23 alpha: (-dt / tau).exp(),
24 v_threshold,
25 }
26 }
27
28 pub fn step(&mut self, current: f64) -> i32 {
29 let inp_pos = current.max(0.0);
30 let inp_neg = (-current).max(0.0);
31 self.v_pos = self.alpha * self.v_pos + inp_pos;
32 self.v_neg = self.alpha * self.v_neg + inp_neg;
33 let diff = self.v_pos - self.v_neg;
34 if diff >= self.v_threshold {
35 self.v_pos = 0.0;
36 self.v_neg = 0.0;
37 1
38 } else if diff <= -self.v_threshold {
39 self.v_pos = 0.0;
40 self.v_neg = 0.0;
41 -1
42 } else {
43 0
44 }
45 }
46
47 pub fn reset(&mut self) {
48 self.v_pos = 0.0;
49 self.v_neg = 0.0;
50 }
51}
52
53impl Default for ComplementaryLIFNeuron {
54 fn default() -> Self {
55 Self::new(10.0, 1.0, 1.0)
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn clif_positive_spike() {
65 let mut n = ComplementaryLIFNeuron::default();
66 let total: i32 = (0..20).map(|_| n.step(0.5)).sum();
67 assert!(total > 0);
68 }
69 #[test]
70 fn clif_silent_without_input() {
71 let mut n = ComplementaryLIFNeuron::default();
72 let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
73 assert_eq!(t, 0);
74 }
75 #[test]
76 fn clif_reset_clears_state() {
77 let mut n = ComplementaryLIFNeuron::default();
78 for _ in 0..20 {
79 n.step(0.5);
80 }
81 n.reset();
82 assert!((n.v_pos - 0.0).abs() < 1e-10);
83 assert!((n.v_neg - 0.0).abs() < 1e-10);
84 }
85 #[test]
86 fn clif_bounded() {
87 let mut n = ComplementaryLIFNeuron::default();
88 for _ in 0..1000 {
89 n.step(100.0);
90 }
91 assert!(n.v_pos.is_finite());
92 }
93 #[test]
94 fn clif_nan_no_panic() {
95 ComplementaryLIFNeuron::default().step(f64::NAN);
96 }
97}