sc_neurocore_engine/neurons/trivial/
energy_lif.rs1const V_MIN: f64 = -200.0;
11const V_MAX: f64 = 100.0;
12const ENERGY_MAX: f64 = 5.0;
13
14#[derive(Clone, Debug)]
16pub struct EnergyLIFNeuron {
17 pub v: f64,
18 pub epsilon: f64,
19 pub capacitance: f64,
20 pub g_leak: f64,
21 pub e_0: f64,
22 pub e_u: f64,
23 pub e_d: f64,
24 pub e_f: f64,
25 pub v_threshold: f64,
26 pub v_reset: f64,
27 pub alpha: f64,
28 pub epsilon_0: f64,
29 pub epsilon_c: f64,
30 pub delta: f64,
31 pub tau_e: f64,
32 pub dt: f64,
33}
34
35impl EnergyLIFNeuron {
36 pub fn new() -> Self {
38 Self {
39 v: -61.0,
40 epsilon: 0.32,
41 capacitance: 100.0,
42 g_leak: 9.0,
43 e_0: -62.5,
44 e_u: -58.5,
45 e_d: -40.0,
46 e_f: -62.0,
47 v_threshold: -59.0,
48 v_reset: -62.0,
49 alpha: 1.0,
50 epsilon_0: 0.5,
51 epsilon_c: 0.18,
52 delta: 0.01,
53 tau_e: 200.0,
54 dt: 0.1,
55 }
56 }
57
58 fn rhs(&self, v: f64, epsilon: f64, current: f64) -> (f64, f64) {
59 let leak = self.e_0 + (self.e_u - self.e_0) * (1.0 - epsilon / self.epsilon_0);
60 let dv = (self.g_leak * (leak - v) + current) / self.capacitance;
61 let production = (1.0 - epsilon / (self.alpha * self.epsilon_0)).powi(3);
62 let cost = (v - self.e_f) / (self.e_d - self.e_f);
63 (dv, (production - cost) / self.tau_e)
64 }
65
66 fn candidate(&self, current: f64) -> (f64, f64) {
67 let dt = self.dt;
68 let k1 = self.rhs(self.v, self.epsilon, current);
69 let k2 = self.rhs(
70 self.v + dt * k1.0 / 2.0,
71 self.epsilon + dt * k1.1 / 2.0,
72 current,
73 );
74 let k3 = self.rhs(
75 self.v + dt * k2.0 / 2.0,
76 self.epsilon + dt * k2.1 / 2.0,
77 current,
78 );
79 let k4 = self.rhs(self.v + dt * k3.0, self.epsilon + dt * k3.1, current);
80 (
81 self.v + dt * (k1.0 + 2.0 * k2.0 + 2.0 * k3.0 + k4.0) / 6.0,
82 self.epsilon + dt * (k1.1 + 2.0 * k2.1 + 2.0 * k3.1 + k4.1) / 6.0,
83 )
84 }
85
86 pub fn valid(&self) -> bool {
88 [
89 self.v,
90 self.e_0,
91 self.e_u,
92 self.e_d,
93 self.e_f,
94 self.v_threshold,
95 self.v_reset,
96 ]
97 .into_iter()
98 .all(f64::is_finite)
99 && (V_MIN..=V_MAX).contains(&self.v)
100 && (V_MIN..=V_MAX).contains(&self.v_reset)
101 && self.epsilon.is_finite()
102 && (0.0..=ENERGY_MAX).contains(&self.epsilon)
103 && [self.epsilon_0, self.epsilon_c, self.delta]
104 .into_iter()
105 .all(|x| x.is_finite() && x >= 0.0)
106 && [
107 self.capacitance,
108 self.g_leak,
109 self.alpha,
110 self.tau_e,
111 self.dt,
112 ]
113 .into_iter()
114 .all(|x| x.is_finite() && x > 0.0)
115 && self.e_d != self.e_f
116 && self.v_threshold > self.v_reset
117 && self.dt <= 1.0
118 && self.dt <= self.tau_e
119 }
120
121 pub fn try_step(&mut self, current: f64) -> Result<i32, &'static str> {
123 if !self.valid() || !current.is_finite() {
124 return Err("invalid EnergyLIF state, configuration, or current");
125 }
126 let (v, epsilon) = self.candidate(current);
127 if !(v.is_finite()
128 && (V_MIN..=V_MAX).contains(&v)
129 && epsilon.is_finite()
130 && (0.0..=ENERGY_MAX).contains(&epsilon))
131 {
132 return Err("EnergyLIF RK4 candidate outside safety envelope");
133 }
134 if v > self.v_threshold && epsilon > self.epsilon_c {
135 let after = epsilon - self.delta;
136 if !(0.0..=ENERGY_MAX).contains(&after) {
137 return Err("EnergyLIF post-spike energy outside safety envelope");
138 }
139 self.v = self.v_reset;
140 self.epsilon = after;
141 return Ok(1);
142 }
143 self.v = v;
144 self.epsilon = epsilon;
145 Ok(0)
146 }
147
148 pub fn step(&mut self, current: f64) -> i32 {
149 self.try_step(current).unwrap_or(-1)
150 }
151
152 pub fn reset(&mut self) {
154 self.v = self.e_0;
155 self.epsilon = self.alpha * self.epsilon_0;
156 }
157}
158
159impl Default for EnergyLIFNeuron {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 #[test]
169 fn source_transition_is_atomic() {
170 let mut n = EnergyLIFNeuron::new();
171 assert_eq!(n.step(80.0), 0);
172 let before = (n.v, n.epsilon);
173 assert_eq!(n.step(f64::NAN), -1);
174 assert_eq!((n.v, n.epsilon), before);
175 }
176}