sc_neurocore_engine/
rall_dendrite.rs1pub struct RallDendriteRust {
14 pub n_branches: usize,
15 pub branch_length: usize,
16 #[allow(dead_code)]
17 tau: f64,
18 coupling: f64,
19 #[allow(dead_code)]
20 dt: f64,
21 decay: f64,
22 dt_over_tau: f64,
23 attenuation: Vec<f64>,
24 v: Vec<Vec<f64>>,
26 pub soma_v: f64,
27}
28
29impl RallDendriteRust {
30 pub fn new(n_branches: usize, branch_length: usize, tau: f64, coupling: f64, dt: f64) -> Self {
31 let decay = (-dt / tau).exp();
32 let dt_over_tau = dt / tau;
33 let parent_d = (n_branches as f64).powf(2.0 / 3.0);
35 let attenuation: Vec<f64> = (0..n_branches)
36 .map(|_| (1.0 / parent_d).powf(1.5))
37 .collect();
38
39 Self {
40 n_branches,
41 branch_length,
42 tau,
43 coupling,
44 dt,
45 decay,
46 dt_over_tau,
47 attenuation,
48 v: vec![vec![0.0; branch_length]; n_branches],
49 soma_v: 0.0,
50 }
51 }
52
53 pub fn step(&mut self, branch_inputs: &[f64]) -> f64 {
55 let nb = self.n_branches;
56 let bl = self.branch_length;
57
58 for b in 0..nb {
60 for k in 0..bl {
61 self.v[b][k] *= self.decay;
62 }
63 }
64
65 for b in 0..nb.min(branch_inputs.len()) {
67 self.v[b][bl - 1] += branch_inputs[b] * self.dt_over_tau;
68 }
69
70 for k in (1..bl).rev() {
72 for b in 0..nb {
73 let flow = self.coupling * (self.v[b][k] - self.v[b][k - 1]);
74 self.v[b][k] -= flow;
75 self.v[b][k - 1] += flow;
76 }
77 }
78
79 let mut soma_input = 0.0;
81 for b in 0..nb {
82 soma_input += self.v[b][0] * self.attenuation[b];
83 }
84 self.soma_v = self.decay * self.soma_v + soma_input * self.dt_over_tau;
85 self.soma_v
86 }
87
88 pub fn reset(&mut self) {
89 for b in &mut self.v {
90 b.fill(0.0);
91 }
92 self.soma_v = 0.0;
93 }
94
95 pub fn branch_voltages(&self) -> Vec<Vec<f64>> {
96 self.v.clone()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_initial_zero() {
106 let d = RallDendriteRust::new(4, 3, 10.0, 0.5, 1.0);
107 assert_eq!(d.soma_v, 0.0);
108 assert!(d.v.iter().all(|b| b.iter().all(|&v| v == 0.0)));
109 }
110
111 #[test]
112 fn test_input_reaches_soma() {
113 let mut d = RallDendriteRust::new(2, 3, 10.0, 0.5, 1.0);
114 for _ in 0..20 {
115 d.step(&[1.0, 0.0]);
116 }
117 assert!(d.soma_v > 0.0);
118 }
119
120 #[test]
121 fn test_more_branches_more_input() {
122 let mut d1 = RallDendriteRust::new(4, 2, 10.0, 0.5, 1.0);
123 let mut d2 = RallDendriteRust::new(4, 2, 10.0, 0.5, 1.0);
124 for _ in 0..20 {
125 d1.step(&[1.0, 0.0, 0.0, 0.0]);
126 d2.step(&[1.0, 1.0, 1.0, 1.0]);
127 }
128 assert!(d2.soma_v > d1.soma_v);
129 }
130
131 #[test]
132 fn test_reset() {
133 let mut d = RallDendriteRust::new(2, 2, 10.0, 0.5, 1.0);
134 d.step(&[5.0, 5.0]);
135 d.reset();
136 assert_eq!(d.soma_v, 0.0);
137 assert!(d.v.iter().all(|b| b.iter().all(|&v| v == 0.0)));
138 }
139
140 #[test]
141 fn test_distal_higher_than_proximal() {
142 let mut d = RallDendriteRust::new(1, 5, 20.0, 0.3, 1.0);
143 for _ in 0..10 {
144 d.step(&[2.0]);
145 }
146 let bv = &d.v[0];
147 assert!(bv[4] > bv[0], "Distal should be higher than proximal");
148 }
149}