1use crate::error::{Result, SynapseError};
11use crate::synapse::Synapse;
12use std::collections::{HashMap, VecDeque};
13use std::sync::{Arc, Mutex};
14
15#[derive(Debug, Clone)]
17pub struct Connection {
18 pub pre_neuron: usize,
20
21 pub post_neuron: usize,
23
24 pub synapse: Synapse,
26}
27
28#[derive(Debug, Clone)]
32pub struct GapJunction {
33 pub neuron1: usize,
35
36 pub neuron2: usize,
38
39 pub conductance: f64,
41
42 pub rectifying: bool,
44}
45
46impl GapJunction {
47 pub fn new(neuron1: usize, neuron2: usize, conductance: f64) -> Result<Self> {
49 if conductance < 0.0 {
50 return Err(SynapseError::InvalidWeight(conductance, 0.0, f64::INFINITY));
51 }
52
53 Ok(Self {
54 neuron1,
55 neuron2,
56 conductance,
57 rectifying: false,
58 })
59 }
60
61 pub fn current(&self, v1: f64, v2: f64) -> f64 {
63 let i12 = self.conductance * (v2 - v1);
64
65 if self.rectifying && i12 < 0.0 {
66 0.0
67 } else {
68 i12
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
77pub struct EphapticCoupling {
78 pub source_neuron: usize,
80
81 pub target_neuron: usize,
83
84 pub strength: f64,
86
87 pub distance: f64,
89
90 pub tau: f64,
92}
93
94impl EphapticCoupling {
95 pub fn new(source: usize, target: usize, distance: f64) -> Self {
97 let strength = 0.01 / (1.0 + distance / 100.0);
99
100 Self {
101 source_neuron: source,
102 target_neuron: target,
103 strength,
104 distance,
105 tau: 10.0, }
107 }
108
109 pub fn effect(&self, source_rate: f64) -> f64 {
114 self.strength * source_rate
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct NeuromodulatorState {
121 pub dopamine: f64,
123
124 pub serotonin: f64,
126
127 pub acetylcholine: f64,
129
130 pub norepinephrine: f64,
132
133 pub tau_decay: f64,
135}
136
137impl Default for NeuromodulatorState {
138 fn default() -> Self {
139 Self {
140 dopamine: 0.0,
141 serotonin: 0.0,
142 acetylcholine: 0.0,
143 norepinephrine: 0.0,
144 tau_decay: 1000.0, }
146 }
147}
148
149impl NeuromodulatorState {
150 pub fn new() -> Self {
152 Self::default()
153 }
154
155 pub fn update(&mut self, dt: f64) {
160 let decay = (-dt / self.tau_decay).exp();
162 self.dopamine *= decay;
163 self.serotonin *= decay;
164 self.acetylcholine *= decay;
165 self.norepinephrine *= decay;
166 }
167
168 pub fn release_dopamine(&mut self, amount: f64) {
170 self.dopamine += amount;
171 }
172
173 pub fn release_serotonin(&mut self, amount: f64) {
175 self.serotonin += amount;
176 }
177
178 pub fn release_acetylcholine(&mut self, amount: f64) {
180 self.acetylcholine += amount;
181 }
182
183 pub fn release_norepinephrine(&mut self, amount: f64) {
185 self.norepinephrine += amount;
186 }
187
188 pub fn weight_modulation(&self) -> f64 {
190 let da_effect = 1.0 + 0.5 * self.dopamine.min(1.0);
192
193 let sero_effect = 1.0 - 0.2 * self.serotonin.min(1.0);
195
196 let ach_effect = 1.0 + 0.3 * self.acetylcholine.min(1.0);
198
199 let ne_effect = 1.0 + 0.4 * self.norepinephrine.min(1.0);
201
202 da_effect * sero_effect * ach_effect * ne_effect
203 }
204}
205
206#[derive(Debug, Clone, Copy)]
208struct DelayedSpike {
209 #[allow(dead_code)]
211 target_neuron: usize,
212
213 #[allow(dead_code)]
215 connection_idx: usize,
216
217 arrival_time: f64,
219}
220
221pub struct SynapticNetwork {
225 pub n_neurons: usize,
227
228 pub connections: Vec<Connection>,
230
231 pub gap_junctions: Vec<GapJunction>,
233
234 pub ephaptic_couplings: Vec<EphapticCoupling>,
236
237 pub neuromodulators: NeuromodulatorState,
239
240 current_time: f64,
242
243 spike_queue: VecDeque<DelayedSpike>,
245
246 adjacency: HashMap<usize, Vec<usize>>,
248
249 neuron_voltages: Vec<f64>,
251}
252
253impl SynapticNetwork {
254 pub fn new(n_neurons: usize) -> Self {
259 Self {
260 n_neurons,
261 connections: Vec::new(),
262 gap_junctions: Vec::new(),
263 ephaptic_couplings: Vec::new(),
264 neuromodulators: NeuromodulatorState::new(),
265 current_time: 0.0,
266 spike_queue: VecDeque::new(),
267 adjacency: HashMap::new(),
268 neuron_voltages: vec![-65.0; n_neurons],
269 }
270 }
271
272 pub fn add_connection(&mut self, pre: usize, post: usize, synapse: Synapse) -> Result<()> {
274 if pre >= self.n_neurons {
275 return Err(SynapseError::NeuronNotFound(pre));
276 }
277 if post >= self.n_neurons {
278 return Err(SynapseError::NeuronNotFound(post));
279 }
280
281 let conn_idx = self.connections.len();
282 self.connections.push(Connection {
283 pre_neuron: pre,
284 post_neuron: post,
285 synapse,
286 });
287
288 self.adjacency.entry(pre).or_insert_with(Vec::new).push(conn_idx);
290
291 Ok(())
292 }
293
294 pub fn add_gap_junction(&mut self, neuron1: usize, neuron2: usize, conductance: f64) -> Result<()> {
296 if neuron1 >= self.n_neurons || neuron2 >= self.n_neurons {
297 return Err(SynapseError::NeuronNotFound(neuron1.max(neuron2)));
298 }
299
300 self.gap_junctions.push(GapJunction::new(neuron1, neuron2, conductance)?);
301 Ok(())
302 }
303
304 pub fn add_ephaptic_coupling(&mut self, source: usize, target: usize, distance: f64) -> Result<()> {
306 if source >= self.n_neurons || target >= self.n_neurons {
307 return Err(SynapseError::NeuronNotFound(source.max(target)));
308 }
309
310 self.ephaptic_couplings.push(EphapticCoupling::new(source, target, distance));
311 Ok(())
312 }
313
314 pub fn spike(&mut self, neuron_id: usize) -> Result<()> {
319 if neuron_id >= self.n_neurons {
320 return Err(SynapseError::NeuronNotFound(neuron_id));
321 }
322
323 if let Some(conn_indices) = self.adjacency.get(&neuron_id) {
325 for &conn_idx in conn_indices {
326 let conn = &mut self.connections[conn_idx];
327 let delay = conn.synapse.delay;
328
329 conn.synapse.presynaptic_spike(self.current_time)?;
331
332 self.spike_queue.push_back(DelayedSpike {
334 target_neuron: conn.post_neuron,
335 connection_idx: conn_idx,
336 arrival_time: self.current_time + delay,
337 });
338 }
339 }
340
341 Ok(())
342 }
343
344 pub fn update(&mut self, neuron_voltages: &[f64], dt: f64) -> Result<()> {
350 if neuron_voltages.len() != self.n_neurons {
351 return Err(SynapseError::InvalidNetwork(
352 format!("Expected {} voltages, got {}", self.n_neurons, neuron_voltages.len())
353 ));
354 }
355
356 self.neuron_voltages.copy_from_slice(neuron_voltages);
357 self.current_time += dt;
358
359 while let Some(spike) = self.spike_queue.front() {
361 if spike.arrival_time <= self.current_time {
362 let _spike = self.spike_queue.pop_front().unwrap();
363 } else {
365 break;
366 }
367 }
368
369 for conn in &mut self.connections {
371 let post_voltage = self.neuron_voltages[conn.post_neuron];
372 conn.synapse.update(self.current_time, post_voltage, dt)?;
373 }
374
375 self.neuromodulators.update(dt);
377
378 Ok(())
379 }
380
381 pub fn get_synaptic_current(&self, neuron_id: usize) -> Result<f64> {
389 if neuron_id >= self.n_neurons {
390 return Err(SynapseError::NeuronNotFound(neuron_id));
391 }
392
393 let voltage = self.neuron_voltages[neuron_id];
394 let mut total_current = 0.0;
395
396 for conn in &self.connections {
398 if conn.post_neuron == neuron_id {
399 let modulation = self.neuromodulators.weight_modulation();
400 total_current += conn.synapse.current(voltage) * modulation;
401 }
402 }
403
404 for gap in &self.gap_junctions {
406 if gap.neuron1 == neuron_id {
407 let v_other = self.neuron_voltages[gap.neuron2];
408 total_current += gap.current(voltage, v_other);
409 } else if gap.neuron2 == neuron_id {
410 let v_other = self.neuron_voltages[gap.neuron1];
411 total_current -= gap.current(v_other, voltage);
412 }
413 }
414
415 Ok(total_current)
416 }
417
418 pub fn get_inputs(&self, neuron_id: usize) -> Vec<&Connection> {
420 self.connections
421 .iter()
422 .filter(|c| c.post_neuron == neuron_id)
423 .collect()
424 }
425
426 pub fn get_outputs(&self, neuron_id: usize) -> Vec<&Connection> {
428 self.connections
429 .iter()
430 .filter(|c| c.pre_neuron == neuron_id)
431 .collect()
432 }
433
434 pub fn connectivity_stats(&self) -> NetworkStats {
436 let mut in_degrees = vec![0; self.n_neurons];
437 let mut out_degrees = vec![0; self.n_neurons];
438
439 for conn in &self.connections {
440 out_degrees[conn.pre_neuron] += 1;
441 in_degrees[conn.post_neuron] += 1;
442 }
443
444 NetworkStats {
445 n_neurons: self.n_neurons,
446 n_connections: self.connections.len(),
447 n_gap_junctions: self.gap_junctions.len(),
448 n_ephaptic: self.ephaptic_couplings.len(),
449 avg_in_degree: in_degrees.iter().sum::<usize>() as f64 / self.n_neurons as f64,
450 avg_out_degree: out_degrees.iter().sum::<usize>() as f64 / self.n_neurons as f64,
451 }
452 }
453
454 pub fn reset(&mut self) {
456 for conn in &mut self.connections {
457 conn.synapse.reset();
458 }
459 self.spike_queue.clear();
460 self.current_time = 0.0;
461 self.neuron_voltages.fill(-65.0);
462 }
463}
464
465#[derive(Debug, Clone)]
467pub struct NetworkStats {
468 pub n_neurons: usize,
470
471 pub n_connections: usize,
473
474 pub n_gap_junctions: usize,
476
477 pub n_ephaptic: usize,
479
480 pub avg_in_degree: f64,
482
483 pub avg_out_degree: f64,
485}
486
487pub type SharedNetwork = Arc<Mutex<SynapticNetwork>>;
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn test_network_creation() {
496 let net = SynapticNetwork::new(10);
497 assert_eq!(net.n_neurons, 10);
498 assert_eq!(net.connections.len(), 0);
499 }
500
501 #[test]
502 fn test_add_connection() {
503 let mut net = SynapticNetwork::new(3);
504 let syn = Synapse::excitatory(1.0, 1.0).unwrap();
505
506 net.add_connection(0, 1, syn).unwrap();
507 assert_eq!(net.connections.len(), 1);
508 }
509
510 #[test]
511 fn test_add_gap_junction() {
512 let mut net = SynapticNetwork::new(3);
513 net.add_gap_junction(0, 1, 0.5).unwrap();
514
515 assert_eq!(net.gap_junctions.len(), 1);
516 assert_eq!(net.gap_junctions[0].conductance, 0.5);
517 }
518
519 #[test]
520 fn test_spike_propagation() {
521 let mut net = SynapticNetwork::new(2);
522 let syn = Synapse::excitatory(1.0, 1.0).unwrap();
523 net.add_connection(0, 1, syn).unwrap();
524
525 net.spike(0).unwrap();
527
528 assert_eq!(net.spike_queue.len(), 1);
530 }
531
532 #[test]
533 fn test_network_update() {
534 let mut net = SynapticNetwork::new(2);
535 let syn = Synapse::excitatory(1.0, 1.0).unwrap();
536 net.add_connection(0, 1, syn).unwrap();
537
538 let voltages = vec![-65.0, -65.0];
539 net.update(&voltages, 0.1).unwrap();
540
541 assert_eq!(net.current_time, 0.1);
542 }
543
544 #[test]
545 fn test_neuromodulator_state() {
546 let mut nm = NeuromodulatorState::new();
547
548 nm.release_dopamine(0.5);
549 assert_eq!(nm.dopamine, 0.5);
550
551 nm.update(100.0);
553 assert!(nm.dopamine < 0.5);
554 }
555
556 #[test]
557 fn test_gap_junction_current() {
558 let gap = GapJunction::new(0, 1, 1.0).unwrap();
559
560 let i = gap.current(-70.0, -60.0);
561 assert!(i > 0.0);
563 }
564
565 #[test]
566 fn test_connectivity_stats() {
567 let mut net = SynapticNetwork::new(3);
568
569 let syn1 = Synapse::excitatory(1.0, 1.0).unwrap();
570 let syn2 = Synapse::excitatory(1.0, 1.0).unwrap();
571
572 net.add_connection(0, 1, syn1).unwrap();
573 net.add_connection(1, 2, syn2).unwrap();
574
575 let stats = net.connectivity_stats();
576 assert_eq!(stats.n_connections, 2);
577 assert_eq!(stats.n_neurons, 3);
578 }
579
580 #[test]
581 fn test_ephaptic_coupling() {
582 let eph = EphapticCoupling::new(0, 1, 50.0);
583
584 let effect = eph.effect(10.0); assert!(effect > 0.0);
586 }
587}