sc_neurocore_engine/neurons/simple_spiking/
pernarowski.rs1#[derive(Clone, Debug)]
13pub struct PernarowskiNeuron {
14 pub v: f64,
15 pub w: f64,
16 pub z: f64,
17 pub alpha: f64,
18 pub beta: f64,
19 pub eps1: f64,
20 pub eps2: f64,
21 pub gamma: f64,
22 pub dt: f64,
23 pub v_threshold: f64,
24}
25
26impl PernarowskiNeuron {
27 pub fn new() -> Self {
28 Self {
29 v: -1.0,
30 w: 0.0,
31 z: 0.0,
32 alpha: 0.1,
33 beta: 0.5,
34 eps1: 0.1,
35 eps2: 0.001,
36 gamma: 0.5,
37 dt: 0.1,
38 v_threshold: 0.5,
39 }
40 }
41 fn is_valid(&self) -> bool {
42 self.v.is_finite()
43 && self.w.is_finite()
44 && self.z.is_finite()
45 && self.alpha.is_finite()
46 && self.beta.is_finite()
47 && self.eps1.is_finite()
48 && self.eps1 > 0.0
49 && self.eps2.is_finite()
50 && self.eps2 > 0.0
51 && self.gamma.is_finite()
52 && self.gamma > 0.0
53 && self.dt.is_finite()
54 && self.dt > 0.0
55 && self.v_threshold.is_finite()
56 }
57 fn derivatives(&self, v: f64, w: f64, z: f64, current: f64) -> Option<(f64, f64, f64)> {
58 if !(v.is_finite() && w.is_finite() && z.is_finite() && current.is_finite()) {
59 return None;
60 }
61 let dv = v - v.powi(3) / 3.0 - w - z + current;
62 let dw = self.eps1 * (v - self.gamma * w + self.alpha);
63 let dz = self.eps2 * (self.beta * (v + 0.7) - z);
64 if dv.is_finite() && dw.is_finite() && dz.is_finite() {
65 Some((dv, dw, dz))
66 } else {
67 None
68 }
69 }
70 fn rk4_candidate(&self, current: f64) -> Option<(f64, f64, f64)> {
71 let dt = self.dt;
72 let (k1v, k1w, k1z) = self.derivatives(self.v, self.w, self.z, current)?;
73 let (k2v, k2w, k2z) = self.derivatives(
74 self.v + 0.5 * dt * k1v,
75 self.w + 0.5 * dt * k1w,
76 self.z + 0.5 * dt * k1z,
77 current,
78 )?;
79 let (k3v, k3w, k3z) = self.derivatives(
80 self.v + 0.5 * dt * k2v,
81 self.w + 0.5 * dt * k2w,
82 self.z + 0.5 * dt * k2z,
83 current,
84 )?;
85 let (k4v, k4w, k4z) = self.derivatives(
86 self.v + dt * k3v,
87 self.w + dt * k3w,
88 self.z + dt * k3z,
89 current,
90 )?;
91 let v = self.v + dt * (k1v + 2.0 * k2v + 2.0 * k3v + k4v) / 6.0;
92 let w = self.w + dt * (k1w + 2.0 * k2w + 2.0 * k3w + k4w) / 6.0;
93 let z = self.z + dt * (k1z + 2.0 * k2z + 2.0 * k3z + k4z) / 6.0;
94 if v.is_finite() && w.is_finite() && z.is_finite() {
95 Some((v, w, z))
96 } else {
97 None
98 }
99 }
100 fn try_step(&mut self, current: f64) -> Option<i32> {
101 if !self.is_valid() || !current.is_finite() {
102 return None;
103 }
104 let v_prev = self.v;
105 let (v, w, z) = self.rk4_candidate(current)?;
106 self.v = v;
107 self.w = w;
108 self.z = z;
109 Some(if self.v >= self.v_threshold && v_prev < self.v_threshold {
110 1
111 } else {
112 0
113 })
114 }
115 pub fn step(&mut self, current: f64) -> i32 {
116 self.try_step(current).unwrap_or(0)
117 }
118 pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
126 self.try_simulate(n_steps, current).unwrap_or_default()
127 }
128 pub fn try_simulate(&mut self, n_steps: usize, current: f64) -> Option<(Vec<f64>, i64)> {
130 let mut candidate = self.clone();
131 let mut trace = Vec::with_capacity(n_steps);
132 let mut spikes: i64 = 0;
133 for _ in 0..n_steps {
134 let spiked = candidate.try_step(current)?;
135 trace.push(candidate.v);
136 spikes += spiked as i64;
137 }
138 *self = candidate;
139 Some((trace, spikes))
140 }
141 pub fn reset(&mut self) {
142 self.v = -1.0;
143 self.w = 0.0;
144 self.z = 0.0;
145 }
146}
147impl Default for PernarowskiNeuron {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn default_matches_constructor_state() {
159 let default = PernarowskiNeuron::default();
160 let constructed = PernarowskiNeuron::new();
161 assert_eq!(default.v, constructed.v);
162 }
163
164 #[test]
165 fn simulate_matches_repeated_step() {
166 let mut simulated = PernarowskiNeuron::new();
167 let mut repeated = PernarowskiNeuron::new();
168 let (trace, spikes) = simulated.simulate(2_000, 1.0);
169 let mut expected_trace = Vec::with_capacity(2_000);
170 let mut expected_spikes = 0_i64;
171 for _ in 0..2_000 {
172 if repeated.step(1.0) == 1 {
173 expected_spikes += 1;
174 }
175 expected_trace.push(repeated.v);
176 }
177 assert_eq!(trace, expected_trace);
178 assert_eq!(spikes, expected_spikes);
179 }
180
181 #[test]
182 fn pernarowski_fires() {
183 let mut n = PernarowskiNeuron::new();
184 let t: i32 = (0..2000).map(|_| n.step(1.0)).sum();
185 assert!(t > 0);
186 }
187
188 #[test]
189 fn pernarowski_reset_clears_state() {
190 let mut n = PernarowskiNeuron::new();
191 for _ in 0..500 {
192 n.step(1.0);
193 }
194 n.reset();
195 assert!((n.v - (-1.0)).abs() < 1e-10);
196 }
197
198 #[test]
199 fn pernarowski_bounded() {
200 let mut n = PernarowskiNeuron::new();
201 for _ in 0..2000 {
202 n.step(50.0);
203 }
204 assert!(n.v.is_finite());
205 }
206
207 #[test]
208 fn pernarowski_slow_z() {
209 let mut n = PernarowskiNeuron::new();
210 let z0 = n.z;
211 for _ in 0..2000 {
212 n.step(1.0);
213 }
214 assert!((n.z - z0).abs() > 1e-6, "slow z should evolve");
215 }
216
217 #[test]
218 fn pernarowski_matches_rk4_candidate() {
219 let mut n = PernarowskiNeuron::new();
220 n.v = -0.8;
221 n.w = 0.2;
222 n.z = -0.1;
223 let current = 0.5;
224 let dt = n.dt;
225 let rhs = |v: f64, w: f64, z: f64| {
226 (
227 v - v.powi(3) / 3.0 - w - z + current,
228 n.eps1 * (v - n.gamma * w + n.alpha),
229 n.eps2 * (n.beta * (v + 0.7) - z),
230 )
231 };
232 let (k1v, k1w, k1z) = rhs(n.v, n.w, n.z);
233 let (k2v, k2w, k2z) = rhs(
234 n.v + 0.5 * dt * k1v,
235 n.w + 0.5 * dt * k1w,
236 n.z + 0.5 * dt * k1z,
237 );
238 let (k3v, k3w, k3z) = rhs(
239 n.v + 0.5 * dt * k2v,
240 n.w + 0.5 * dt * k2w,
241 n.z + 0.5 * dt * k2z,
242 );
243 let (k4v, k4w, k4z) = rhs(n.v + dt * k3v, n.w + dt * k3w, n.z + dt * k3z);
244 let expected = (
245 n.v + dt * (k1v + 2.0 * k2v + 2.0 * k3v + k4v) / 6.0,
246 n.w + dt * (k1w + 2.0 * k2w + 2.0 * k3w + k4w) / 6.0,
247 n.z + dt * (k1z + 2.0 * k2z + 2.0 * k3z + k4z) / 6.0,
248 );
249
250 assert_eq!(n.step(current), 0);
251 assert!((n.v - expected.0).abs() < 1e-14);
252 assert!((n.w - expected.1).abs() < 1e-14);
253 assert!((n.z - expected.2).abs() < 1e-14);
254 }
255
256 #[test]
257 fn pernarowski_invalid_input_preserves_state() {
258 let mut n = PernarowskiNeuron::new();
259 let before = (n.v, n.w, n.z);
260 assert_eq!(n.step(f64::NAN), 0);
261 assert_eq!((n.v, n.w, n.z), before);
262 }
263
264 #[test]
265 fn pernarowski_overflow_candidate_preserves_state() {
266 let mut n = PernarowskiNeuron::new();
267 n.v = 1.0e160;
268 let before = (n.v, n.w, n.z);
269 assert_eq!(n.step(0.5), 0);
270 assert_eq!((n.v, n.w, n.z), before);
271 }
272
273 #[test]
274 fn try_simulate_rejects_overflow_without_mutation() {
275 let mut neuron = PernarowskiNeuron {
276 v: 1.0e103,
277 ..Default::default()
278 };
279 let before = (neuron.v, neuron.w, neuron.z);
280 assert!(neuron.try_simulate(2, 0.5).is_none());
281 assert_eq!((neuron.v, neuron.w, neuron.z), before);
282 }
283
284 #[test]
285 fn pernarowski_nan_no_panic() {
286 PernarowskiNeuron::new().step(f64::NAN);
287 }
288
289 #[test]
290 fn pernarowski_negative_no_crash() {
291 let mut n = PernarowskiNeuron::new();
292 for _ in 0..500 {
293 n.step(-5.0);
294 }
295 assert!(n.v.is_finite());
296 }
297}