sc_neurocore_engine/neuron/
adex.rs1#[derive(Clone, Debug)]
12pub struct AdExNeuron {
13 pub v: f64,
14 pub w: f64,
15 pub v_rest: f64,
16 pub v_reset: f64,
17 pub v_threshold: f64,
18 pub v_rh: f64,
19 pub delta_t: f64,
20 pub tau: f64,
21 pub tau_w: f64,
22 pub a: f64,
23 pub b: f64,
24 pub c_m: f64,
25 pub dt: f64,
26}
27
28impl Default for AdExNeuron {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl AdExNeuron {
35 pub fn new() -> Self {
36 Self {
37 v: -65.0,
38 w: 0.0,
39 v_rest: -65.0,
40 v_reset: -68.0,
41 v_threshold: -50.0,
42 v_rh: -55.0,
43 delta_t: 2.0,
44 tau: 20.0,
45 tau_w: 100.0,
46 a: 0.5,
47 b: 7.0,
48 c_m: 200.0,
49 dt: 0.1,
50 }
51 }
52
53 pub fn step(&mut self, current: f64) -> i32 {
54 if !self.v.is_finite()
55 || !self.w.is_finite()
56 || !self.v_rest.is_finite()
57 || !self.v_reset.is_finite()
58 || !self.v_threshold.is_finite()
59 || !self.v_rh.is_finite()
60 || !self.delta_t.is_finite()
61 || !self.tau.is_finite()
62 || !self.tau_w.is_finite()
63 || !self.a.is_finite()
64 || !self.b.is_finite()
65 || !self.c_m.is_finite()
66 || !self.dt.is_finite()
67 || !current.is_finite()
68 || self.delta_t <= 0.0
69 || self.tau <= 0.0
70 || self.tau_w <= 0.0
71 || self.c_m <= 0.0
72 || self.dt <= 0.0
73 {
74 return 0;
75 }
76
77 let exp_arg = ((self.v - self.v_rh) / self.delta_t).clamp(-20.0, 20.0);
78 let exp_term = self.delta_t * exp_arg.exp();
79 let dv = ((-(self.v - self.v_rest) + exp_term) / self.tau + (-self.w + current) / self.c_m)
80 * self.dt;
81 let dw = (self.a * (self.v - self.v_rest) - self.w) / self.tau_w * self.dt;
82 let next_v = self.v + dv;
83 let next_w = self.w + dw;
84 if !exp_term.is_finite()
85 || !dv.is_finite()
86 || !dw.is_finite()
87 || !next_v.is_finite()
88 || !next_w.is_finite()
89 {
90 return 0;
91 }
92
93 if next_v >= self.v_threshold {
94 let spike_w = next_w + self.b;
95 if !spike_w.is_finite() {
96 return 0;
97 }
98 self.v = self.v_reset;
99 self.w = spike_w;
100 1
101 } else {
102 self.v = next_v;
103 self.w = next_w;
104 0
105 }
106 }
107
108 pub fn reset(&mut self) {
109 self.v = self.v_rest;
110 self.w = 0.0;
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::AdExNeuron;
117
118 #[test]
119 fn strong_input_produces_spikes() {
120 let mut neuron = AdExNeuron::new();
121 let spikes: i32 = (0..2_000).map(|_| neuron.step(500.0)).sum();
122 assert!(spikes > 0, "AdEx must fire with strong input");
123 }
124
125 #[test]
126 fn adaptation_does_not_increase_late_rate() {
127 let mut neuron = AdExNeuron::new();
128 let first: i32 = (0..1_000).map(|_| neuron.step(400.0)).sum();
129 let second: i32 = (0..1_000).map(|_| neuron.step(400.0)).sum();
130 assert!(second <= first + 5, "first={first}, second={second}");
131 }
132
133 #[test]
134 fn matches_python_golden_spike_counts() {
135 for (current, expected) in [(0.0, 0), (200.0, 4), (500.0, 12)] {
136 let mut neuron = AdExNeuron::new();
137 let spikes: i32 = (0..1_000).map(|_| neuron.step(current)).sum();
138 assert_eq!(spikes, expected, "current={current}");
139 }
140 }
141
142 #[test]
143 fn invalid_input_is_mutation_free() {
144 let mut neuron = AdExNeuron::new();
145 let before = (neuron.v, neuron.w);
146 assert_eq!(neuron.step(f64::INFINITY), 0);
147 assert_eq!((neuron.v, neuron.w), before);
148 }
149
150 #[test]
151 fn nonfinite_candidate_is_mutation_free() {
152 let mut neuron = AdExNeuron::new();
153 neuron.dt = 1.0e308;
154 let before = (neuron.v, neuron.w);
155 assert_eq!(neuron.step(1.0e308), 0);
156 assert_eq!((neuron.v, neuron.w), before);
157 }
158
159 #[test]
160 fn no_input_remains_silent() {
161 let mut neuron = AdExNeuron::new();
162 let spikes: i32 = (0..1_000).map(|_| neuron.step(0.0)).sum();
163 assert_eq!(spikes, 0);
164 }
165
166 #[test]
167 fn negative_current_remains_silent() {
168 let mut neuron = AdExNeuron::new();
169 let spikes: i32 = (0..500).map(|_| neuron.step(-100.0)).sum();
170 assert_eq!(spikes, 0);
171 }
172
173 #[test]
174 fn reset_matches_fresh_neuron() {
175 let mut neuron = AdExNeuron::new();
176 for _ in 0..200 {
177 neuron.step(500.0);
178 }
179 assert!(neuron.w > 0.0);
180 neuron.reset();
181 assert_eq!(neuron.v, neuron.v_rest);
182 assert_eq!(neuron.w, 0.0);
183
184 let mut fresh = AdExNeuron::new();
185 let reset_spikes: i32 = (0..100).map(|_| neuron.step(500.0)).sum();
186 let fresh_spikes: i32 = (0..100).map(|_| fresh.step(500.0)).sum();
187 assert_eq!(reset_spikes, fresh_spikes);
188 }
189
190 #[test]
191 fn sustained_high_input_keeps_state_finite() {
192 let mut neuron = AdExNeuron::new();
193 for _ in 0..5_000 {
194 neuron.step(1_000.0);
195 }
196 assert!(neuron.v.is_finite());
197 assert!(neuron.w.is_finite());
198 }
199
200 #[test]
201 fn sustained_input_produces_many_spikes() {
202 let mut neuron = AdExNeuron::new();
203 let spikes: i32 = (0..10_000).map(|_| neuron.step(500.0)).sum();
204 assert!(spikes > 100, "got {spikes}");
205 assert!(neuron.v.is_finite());
206 }
207
208 #[test]
209 fn ten_thousand_steps_complete_within_smoke_limit() {
210 let mut neuron = AdExNeuron::new();
211 let start = std::time::Instant::now();
212 for _ in 0..10_000 {
213 neuron.step(500.0);
214 }
215 assert!(start.elapsed().as_millis() < 50);
216 }
217}