sc_neurocore_engine/neurons/biophysical/
traub_miles.rs1#[derive(Clone, Debug)]
28pub struct TraubMilesNeuron {
29 pub v: f64,
30 pub m: f64,
31 pub h: f64,
32 pub n: f64,
33 pub g_na: f64,
34 pub g_k: f64,
35 pub g_l: f64,
36 pub e_na: f64,
37 pub e_k: f64,
38 pub e_l: f64,
39 pub dt: f64,
40 pub v_threshold: f64,
41}
42
43impl TraubMilesNeuron {
44 pub fn new() -> Self {
45 Self {
46 v: -67.0,
47 m: 0.05,
48 h: 0.6,
49 n: 0.3,
50 g_na: 100.0,
51 g_k: 80.0,
52 g_l: 0.1,
53 e_na: 50.0,
54 e_k: -100.0,
55 e_l: -67.0,
56 dt: 0.01,
57 v_threshold: -20.0,
58 }
59 }
60 fn finite_gate(value: f64) -> bool {
61 value.is_finite() && (0.0..=1.0).contains(&value)
62 }
63 fn valid_runtime(&self) -> bool {
64 self.v.is_finite()
65 && Self::finite_gate(self.m)
66 && Self::finite_gate(self.h)
67 && Self::finite_gate(self.n)
68 && self.g_na.is_finite()
69 && self.g_na >= 0.0
70 && self.g_k.is_finite()
71 && self.g_k >= 0.0
72 && self.g_l.is_finite()
73 && self.g_l >= 0.0
74 && self.e_na.is_finite()
75 && self.e_k.is_finite()
76 && self.e_l.is_finite()
77 && self.dt.is_finite()
78 && self.dt > 0.0
79 && self.v_threshold.is_finite()
80 }
81 fn rates(v: f64) -> Option<(f64, f64, f64, f64, f64, f64)> {
82 let d = v + 54.0;
83 let am = if d.abs() > 1e-6 {
84 0.32 * d / (1.0 - (-d / 4.0).exp())
85 } else {
86 8.0
87 };
88 let d2 = v + 27.0;
89 let bm = if d2.abs() > 1e-6 {
90 0.28 * d2 / ((d2 / 5.0).exp() - 1.0)
91 } else {
92 5.6
93 };
94 let ah = 0.128 * (-(v + 50.0) / 18.0).exp();
95 let bh = 4.0 / (1.0 + (-(v + 27.0) / 5.0).exp());
96 let d3 = v + 52.0;
97 let an = if d3.abs() > 1e-6 {
98 0.032 * d3 / (1.0 - (-d3 / 5.0).exp())
99 } else {
100 0.32
101 };
102 let bn = 0.5 * (-(v + 57.0) / 40.0).exp();
103 if [am, bm, ah, bh, an, bn]
104 .iter()
105 .all(|rate| rate.is_finite() && *rate >= 0.0)
106 {
107 Some((am, bm, ah, bh, an, bn))
108 } else {
109 None
110 }
111 }
112 fn derivatives(
113 &self,
114 v: f64,
115 m: f64,
116 h: f64,
117 n: f64,
118 current: f64,
119 ) -> Option<(f64, f64, f64, f64)> {
120 if !v.is_finite() || !Self::finite_gate(m) || !Self::finite_gate(h) || !Self::finite_gate(n)
121 {
122 return None;
123 }
124 let (am, bm, ah, bh, an, bn) = Self::rates(v)?;
125 let dm = am * (1.0 - m) - bm * m;
126 let dh = ah * (1.0 - h) - bh * h;
127 let dn = an * (1.0 - n) - bn * n;
128 let i_na = self.g_na * m.powi(3) * h * (v - self.e_na);
129 let i_k = self.g_k * n.powi(4) * (v - self.e_k);
130 let i_l = self.g_l * (v - self.e_l);
131 let dv = -i_na - i_k - i_l + current;
132 if [dv, dm, dh, dn, i_na, i_k, i_l]
133 .iter()
134 .all(|value| value.is_finite())
135 {
136 Some((dv, dm, dh, dn))
137 } else {
138 None
139 }
140 }
141 fn rk4_substep(
142 &self,
143 v: f64,
144 m: f64,
145 h: f64,
146 n: f64,
147 current: f64,
148 ) -> Option<(f64, f64, f64, f64)> {
149 let (k1_v, k1_m, k1_h, k1_n) = self.derivatives(v, m, h, n, current)?;
150 let (k2_v, k2_m, k2_h, k2_n) = self.derivatives(
151 v + 0.5 * self.dt * k1_v,
152 m + 0.5 * self.dt * k1_m,
153 h + 0.5 * self.dt * k1_h,
154 n + 0.5 * self.dt * k1_n,
155 current,
156 )?;
157 let (k3_v, k3_m, k3_h, k3_n) = self.derivatives(
158 v + 0.5 * self.dt * k2_v,
159 m + 0.5 * self.dt * k2_m,
160 h + 0.5 * self.dt * k2_h,
161 n + 0.5 * self.dt * k2_n,
162 current,
163 )?;
164 let (k4_v, k4_m, k4_h, k4_n) = self.derivatives(
165 v + self.dt * k3_v,
166 m + self.dt * k3_m,
167 h + self.dt * k3_h,
168 n + self.dt * k3_n,
169 current,
170 )?;
171 let next_v = v + self.dt * (k1_v + 2.0 * k2_v + 2.0 * k3_v + k4_v) / 6.0;
172 let next_m = m + self.dt * (k1_m + 2.0 * k2_m + 2.0 * k3_m + k4_m) / 6.0;
173 let next_h = h + self.dt * (k1_h + 2.0 * k2_h + 2.0 * k3_h + k4_h) / 6.0;
174 let next_n = n + self.dt * (k1_n + 2.0 * k2_n + 2.0 * k3_n + k4_n) / 6.0;
175 if next_v.is_finite()
176 && Self::finite_gate(next_m)
177 && Self::finite_gate(next_h)
178 && Self::finite_gate(next_n)
179 {
180 Some((next_v, next_m, next_h, next_n))
181 } else {
182 None
183 }
184 }
185 pub fn step(&mut self, current: f64) -> i32 {
186 if !current.is_finite() || !self.valid_runtime() {
187 return 0;
188 }
189 let v_prev = self.v;
190 let mut v = self.v;
191 let mut m = self.m;
192 let mut h = self.h;
193 let mut n = self.n;
194 for _ in 0..10 {
195 let Some((next_v, next_m, next_h, next_n)) = self.rk4_substep(v, m, h, n, current)
196 else {
197 return 0;
198 };
199 v = next_v;
200 m = next_m;
201 h = next_h;
202 n = next_n;
203 }
204 self.v = v;
205 self.m = m;
206 self.h = h;
207 self.n = n;
208 if self.v >= self.v_threshold && v_prev < self.v_threshold {
209 1
210 } else {
211 0
212 }
213 }
214 pub fn reset(&mut self) {
215 *self = Self::new();
216 }
217}
218impl Default for TraubMilesNeuron {
219 fn default() -> Self {
220 Self::new()
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn default_matches_constructor_state() {
230 let default = TraubMilesNeuron::default();
231 let constructed = TraubMilesNeuron::new();
232 assert_eq!(default.v, constructed.v);
233 }
234
235 #[test]
236 fn removable_rate_singularities_use_finite_limits() {
237 for voltage in [-54.0, -27.0, -52.0] {
238 assert!(TraubMilesNeuron::rates(voltage).is_some());
239 }
240 assert!(TraubMilesNeuron::rates(-1.0e308).is_none());
241 }
242
243 #[test]
244 fn derivatives_reject_invalid_and_overflowing_states() {
245 let mut n = TraubMilesNeuron::new();
246 assert_eq!(n.derivatives(n.v, 2.0, n.h, n.n, 0.0), None);
247 n.e_na = -f64::MAX;
248 assert_eq!(n.derivatives(f64::MAX, 0.5, 0.5, 0.5, 0.0), None);
249 }
250
251 #[test]
252 fn invalid_rk4_candidate_preserves_state() {
253 for (voltage, dt, current) in [
254 (-200.0, 0.0001, 1.0e100),
255 (-150.0, 0.05623413251903491, 10_000.0),
256 (-200.0, 0.0031622776601683794, 0.0),
257 (0.0, 0.03162277660168379, 10_000.0),
258 ] {
259 let mut n = TraubMilesNeuron::new();
260 n.v = voltage;
261 n.dt = dt;
262 let before = (n.v, n.m, n.h, n.n);
263 assert_eq!(n.step(current), 0);
264 assert_eq!((n.v, n.m, n.h, n.n), before);
265 }
266 }
267
268 #[test]
269 fn traub_fires() {
270 let mut n = TraubMilesNeuron::new();
271 let t: i32 = (0..200).map(|_| n.step(5.0)).sum();
272 assert!(t > 0);
273 }
274
275 #[test]
277 fn traub_silent_without_input() {
278 let mut n = TraubMilesNeuron::new();
279 let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
280 assert_eq!(t, 0);
281 }
282 #[test]
283 fn traub_reset_clears_state() {
284 let mut n = TraubMilesNeuron::new();
285 for _ in 0..100 {
286 n.step(5.0);
287 }
288 n.reset();
289 assert!((n.v - (-67.0)).abs() < 1e-10);
290 }
291 #[test]
292 fn traub_extreme_bounded() {
293 let mut n = TraubMilesNeuron::new();
294 for _ in 0..200 {
295 n.step(1e4);
296 }
297 assert!(n.v.is_finite());
298 }
299 #[test]
300 fn traub_gates_bounded() {
301 let mut n = TraubMilesNeuron::new();
302 for _ in 0..500 {
303 n.step(5.0);
304 }
305 assert!(n.m >= 0.0 && n.m <= 1.01);
306 assert!(n.h >= 0.0 && n.h <= 1.01);
307 assert!(n.n >= 0.0 && n.n <= 1.01);
308 }
309 #[test]
310 fn traub_weak_negative_no_crash() {
311 let mut n = TraubMilesNeuron::new();
312 for _ in 0..200 {
313 n.step(-5.0);
314 }
315 assert!(n.v.is_finite());
316 }
317 #[test]
318 fn traub_nan_no_panic() {
319 let mut n = TraubMilesNeuron::new();
320 n.step(f64::NAN);
321 }
322 #[test]
323 fn traub_rk4_reference_point() {
324 let mut n = TraubMilesNeuron::new();
325 n.v = -63.5;
326 n.m = 0.08;
327 n.h = 0.55;
328 n.n = 0.32;
329 let spike = n.step(4.0);
330 assert_eq!(spike, 0);
331 assert!((n.v - (-65.6638958700765)).abs() < 1e-13);
332 assert!((n.m - 0.04237301812907925).abs() < 1e-15);
333 assert!((n.h - 0.5626824931070477).abs() < 1e-15);
334 assert!((n.n - 0.30356298261126924).abs() < 1e-15);
335 assert!((n.v - (-65.66233161606698)).abs() > 1e-3);
336 }
337}