sc_neurocore_engine/neurons/interneurons/
vip_neuron.rs1#[derive(Clone, Debug)]
18pub struct VIPNeuron {
19 pub v: f64,
20 pub h: f64,
21 pub n: f64,
22 pub a: f64, pub b: f64, pub g_na: f64,
26 pub g_k: f64,
27 pub g_a: f64,
28 pub g_l: f64,
29 pub e_na: f64,
31 pub e_k: f64,
32 pub e_l: f64,
33 pub c_m: f64,
34 pub dt: f64,
35 pub v_threshold: f64,
36}
37
38impl VIPNeuron {
39 pub fn new() -> Self {
40 Self {
41 v: -65.0,
42 h: 0.8,
43 n: 0.1,
44 a: 0.0,
45 b: 0.9,
46 g_na: 35.0, g_k: 6.0,
48 g_a: 8.0, g_l: 0.01, e_na: 55.0,
51 e_k: -90.0,
52 e_l: -65.0,
53 c_m: 0.5, dt: 0.025,
55 v_threshold: -20.0,
56 }
57 }
58
59 fn derivatives(&self, v: f64, h: f64, n: f64, a: f64, b: f64, current: f64) -> [f64; 5] {
62 let m_inf = 1.0 / (1.0 + (-(v + 30.0) / 9.5).exp());
63 let h_inf = 1.0 / (1.0 + ((v + 53.0) / 7.0).exp());
64 let tau_h = 0.37 + 2.78 / (1.0 + ((v + 40.5) / 6.0).exp());
65 let n_inf = 1.0 / (1.0 + (-(v + 30.0) / 10.0).exp());
66 let tau_n = 0.37 + 1.85 / (1.0 + ((v + 27.0) / 15.0).exp());
67 let a_inf = 1.0 / (1.0 + (-(v + 50.0) / 20.0).exp());
68 let b_inf = 1.0 / (1.0 + ((v + 78.0) / 6.0).exp());
69 let dh = (h_inf - h) / tau_h;
70 let dn = (n_inf - n) / tau_n;
71 let da = (a_inf - a) / 5.0;
72 let db = (b_inf - b) / 50.0;
73 let i_na = self.g_na * m_inf * m_inf * m_inf * h * (v - self.e_na);
74 let i_k = self.g_k * n * n * n * n * (v - self.e_k);
75 let i_a = self.g_a * a * a * a * b * (v - self.e_k);
76 let i_l = self.g_l * (v - self.e_l);
77 let dv = (-i_na - i_k - i_a - i_l + current) / self.c_m;
78 [dv, dh, dn, da, db]
79 }
80
81 fn rk4_substep(&self, s: [f64; 5], current: f64) -> [f64; 5] {
84 let dt = self.dt;
85 let k1 = self.derivatives(s[0], s[1], s[2], s[3], s[4], current);
86 let k2 = self.derivatives(
87 s[0] + 0.5 * dt * k1[0],
88 s[1] + 0.5 * dt * k1[1],
89 s[2] + 0.5 * dt * k1[2],
90 s[3] + 0.5 * dt * k1[3],
91 s[4] + 0.5 * dt * k1[4],
92 current,
93 );
94 let k3 = self.derivatives(
95 s[0] + 0.5 * dt * k2[0],
96 s[1] + 0.5 * dt * k2[1],
97 s[2] + 0.5 * dt * k2[2],
98 s[3] + 0.5 * dt * k2[3],
99 s[4] + 0.5 * dt * k2[4],
100 current,
101 );
102 let k4 = self.derivatives(
103 s[0] + dt * k3[0],
104 s[1] + dt * k3[1],
105 s[2] + dt * k3[2],
106 s[3] + dt * k3[3],
107 s[4] + dt * k3[4],
108 current,
109 );
110 let mut out = [0.0_f64; 5];
111 for i in 0..5 {
112 out[i] = s[i] + dt * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0;
113 }
114 out
115 }
116
117 pub fn step(&mut self, current: f64) -> i32 {
118 let v_prev = self.v;
119 let mut s = [self.v, self.h, self.n, self.a, self.b];
120 for _ in 0..4 {
121 s = self.rk4_substep(s, current);
122 }
123 self.v = s[0];
124 self.h = s[1];
125 self.n = s[2];
126 self.a = s[3];
127 self.b = s[4];
128 if self.v >= self.v_threshold && v_prev < self.v_threshold {
129 1
130 } else {
131 0
132 }
133 }
134
135 pub fn reset(&mut self) {
136 self.v = -65.0;
137 self.h = 0.8;
138 self.n = 0.1;
139 self.a = 0.0;
140 self.b = 0.9;
141 }
142}
143
144impl Default for VIPNeuron {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn vip_fires_with_input() {
160 let mut n = VIPNeuron::new();
161 let spikes: i32 = (0..10000).map(|_| n.step(2.0)).sum();
162 assert!(spikes > 0, "VIP must fire with sustained input");
163 }
164
165 #[test]
166 fn vip_no_fire_without_input() {
167 let mut n = VIPNeuron::new();
168 let spikes: i32 = (0..5000).map(|_| n.step(0.0)).sum();
169 assert_eq!(spikes, 0);
170 }
171
172 #[test]
173 fn vip_accommodation() {
174 let mut n = VIPNeuron::new();
177 let onset: i32 = (0..500).map(|_| n.step(3.0)).sum();
179 for _ in 0..5000 {
181 n.step(3.0);
182 }
183 let steady: i32 = (0..500).map(|_| n.step(3.0)).sum();
185 assert!(
187 steady >= onset,
188 "VIP steady-state ({steady}) should fire >= onset ({onset})"
189 );
190 }
191
192 #[test]
193 fn vip_reset_roundtrip() {
194 let mut n = VIPNeuron::new();
195 for _ in 0..5000 {
196 n.step(3.0);
197 }
198 n.reset();
199 let mut fresh = VIPNeuron::new();
200 let r1: i32 = (0..2000).map(|_| n.step(3.0)).sum();
201 let r2: i32 = (0..2000).map(|_| fresh.step(3.0)).sum();
202 assert_eq!(r1, r2);
203 }
204
205 #[test]
206 fn vip_voltage_bounded() {
207 let mut n = VIPNeuron::new();
208 for _ in 0..20000 {
209 n.step(5.0);
210 }
211 assert!(n.v.is_finite());
212 }
213
214 #[test]
215 #[ignore = "wall-clock performance smoke; use Criterion benches for timing evidence"]
216 fn vip_performance_10k_steps() {
217 let mut n = VIPNeuron::new();
218 let start = std::time::Instant::now();
219 for _ in 0..10_000 {
220 n.step(3.0);
221 }
222 assert!(start.elapsed().as_millis() < 100);
223 }
224}