sc_neurocore_engine/neurons/misc/
gap_junction.rs1#[derive(Clone, Debug)]
41pub struct GapJunctionNeuron {
42 pub v: f64, pub c_m: f64, pub g_l: f64, pub e_l: f64, pub g_gap: f64, pub i_tonic: f64, pub v_threshold: f64,
49 pub v_reset: f64,
50 pub refractory: f64, pub refrac_timer: f64,
52 pub rect_v0: f64, pub rect_a: f64, pub rect_gmin: f64, pub dt: f64,
57 pub gain: f64,
58}
59
60impl Default for GapJunctionNeuron {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl GapJunctionNeuron {
67 pub fn new() -> Self {
68 Self {
69 v: -65.0,
70 c_m: 1.0,
71 g_l: 0.1,
72 e_l: -65.0,
73 g_gap: 0.15, i_tonic: 0.0, v_threshold: -50.0,
76 v_reset: -65.0,
77 refractory: 2.0, refrac_timer: 0.0,
79 rect_v0: 30.0, rect_a: 0.1, rect_gmin: 0.1, dt: 0.1,
83 gain: 1.0,
84 }
85 }
86
87 #[inline]
93 fn rect_conductance(&self, v_j: f64) -> f64 {
94 self.rect_gmin
95 + (1.0 - self.rect_gmin) / (1.0 + (self.rect_a * (v_j.abs() - self.rect_v0)).exp())
96 }
97
98 pub fn step(&mut self, current: f64) -> i32 {
99 let input = self.gain * current;
101
102 if self.refrac_timer > 0.0 {
103 self.refrac_timer -= self.dt;
104 return 0;
105 }
106
107 let v_j = input - self.v;
109 let g_eff = self.g_gap * self.rect_conductance(v_j);
111 let i_gap = g_eff * v_j;
112 let dv = (-self.g_l * (self.v - self.e_l) + i_gap + self.i_tonic) / self.c_m;
113 self.v += self.dt * dv;
114
115 self.v = self.v.clamp(-100.0, 40.0);
117 if !self.v.is_finite() {
118 self.v = self.e_l;
119 }
120
121 if self.v >= self.v_threshold {
123 self.v = self.v_reset;
124 self.refrac_timer = self.refractory;
125 return 1;
126 }
127 0
128 }
129
130 pub fn reset(&mut self) {
131 *self = Self::new();
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
142 fn gap_fires_with_depolarising_drive() {
143 let mut n = GapJunctionNeuron::new();
145 let mut spikes = 0;
146 for _ in 0..50_000 {
147 spikes += n.step(0.0); }
149 assert!(
150 spikes > 0,
151 "Gap junction must fire with depolarising drive, got {spikes}"
152 );
153 }
154
155 #[test]
156 fn gap_silent_at_rest() {
157 let mut n = GapJunctionNeuron::new();
159 let mut spikes = 0;
160 for _ in 0..50_000 {
161 spikes += n.step(-65.0); }
163 assert_eq!(
164 spikes, 0,
165 "Must be silent when V_neighbor = E_L, got {spikes}"
166 );
167 }
168
169 #[test]
170 fn gap_junction_pulls_toward_neighbor() {
171 let mut n = GapJunctionNeuron::new(); for _ in 0..5_000 {
174 n.step(-40.0);
175 } assert!(
177 n.v > -65.0 || n.refrac_timer > 0.0,
178 "Gap junction must pull V toward neighbor: v={}",
179 n.v
180 );
181 }
182
183 #[test]
184 fn gap_stronger_coupling_more_spikes() {
185 let mut weak = GapJunctionNeuron::new();
186 weak.g_gap = 0.01;
187 let mut strong = GapJunctionNeuron::new();
188 strong.g_gap = 0.1;
189 let (mut sw, mut ss) = (0, 0);
190 for _ in 0..50_000 {
191 sw += weak.step(-20.0);
192 ss += strong.step(-20.0);
193 }
194 assert!(
195 ss >= sw,
196 "Stronger coupling → more spikes: strong={ss} vs weak={sw}"
197 );
198 }
199
200 #[test]
201 fn gap_refractory_enforced() {
202 let mut n = GapJunctionNeuron::new();
203 let mut first_spike_t = 0;
205 for t in 0..10_000 {
206 if n.step(0.0) == 1 {
207 first_spike_t = t;
208 break;
209 }
210 }
211 assert!(first_spike_t > 0, "Must spike");
212 assert!(n.refrac_timer > 0.0, "Must be in refractory after spike");
214 assert_eq!(n.step(0.0), 0, "Must not spike during refractory");
215 }
216
217 #[test]
218 fn gap_hyperpolarising_drive_silent() {
219 let mut n = GapJunctionNeuron::new();
221 let mut spikes = 0;
222 for _ in 0..50_000 {
223 spikes += n.step(-100.0);
224 }
225 assert_eq!(
226 spikes, 0,
227 "Hyperpolarising drive must keep silent, got {spikes}"
228 );
229 }
230
231 #[test]
232 fn gap_tonic_current_depolarises() {
233 let mut n = GapJunctionNeuron::new();
234 n.i_tonic = 5.0; let mut spikes = 0;
236 for _ in 0..50_000 {
237 spikes += n.step(-65.0); }
239 assert!(
240 spikes > 0,
241 "Tonic current should produce spikes, got {spikes}"
242 );
243 }
244
245 #[test]
246 fn gap_nan_input_stays_finite() {
247 let mut n = GapJunctionNeuron::new();
248 n.step(f64::NAN);
249 assert!(n.v.is_finite());
250 }
251
252 #[test]
253 fn gap_reset_clears_state() {
254 let mut n = GapJunctionNeuron::new();
255 for _ in 0..10_000 {
256 n.step(-20.0);
257 }
258 n.reset();
259 assert_eq!(n.v, -65.0);
260 assert_eq!(n.refrac_timer, 0.0);
261 }
262
263 #[test]
264 fn gap_rectification_reduces_at_large_vj() {
265 let n = GapJunctionNeuron::new();
267 let g_small = n.rect_conductance(5.0); let g_large = n.rect_conductance(60.0); assert!(
270 g_small > g_large,
271 "Rectification must reduce g at large Vj: g(5)={g_small:.3} vs g(60)={g_large:.3}"
272 );
273 assert!(
274 g_large >= n.rect_gmin,
275 "Conductance must not drop below g_min={}: got {g_large:.3}",
276 n.rect_gmin
277 );
278 }
279
280 #[test]
281 fn gap_performance_100k_steps() {
282 let start = std::time::Instant::now();
283 let mut n = GapJunctionNeuron::new();
284 for _ in 0..100_000 {
285 std::hint::black_box(n.step(-20.0));
286 }
287 let elapsed = start.elapsed();
288 assert!(
289 elapsed.as_millis() < 50,
290 "100k steps must complete in <50ms"
291 );
292 }
293
294 #[test]
295 fn gap_default_matches_constructor() {
296 let default = GapJunctionNeuron::default();
297 let constructed = GapJunctionNeuron::new();
298 assert_eq!(default.v, constructed.v);
299 assert_eq!(default.g_gap, constructed.g_gap);
300 assert_eq!(default.rect_v0, constructed.rect_v0);
301 }
302}