sc_neurocore_engine/neurons/biophysical/
durstewitz_dopamine.rs1#[derive(Clone, Debug)]
13pub struct DurstewitzDopamineNeuron {
14 pub v: f64,
15 pub h_na: f64,
16 pub n_k: f64,
17 pub g_na: f64,
18 pub g_k: f64,
19 pub g_nmda: f64,
20 pub g_l: f64,
21 pub e_na: f64,
22 pub e_k: f64,
23 pub e_nmda: f64,
24 pub e_l: f64,
25 pub mg: f64,
26 pub d1_level: f64,
27 pub g_nmda_scale: f64,
28 pub g_k_scale: f64,
29 pub v_shift_na: f64,
30 pub dt: f64,
31 pub v_threshold: f64,
32}
33
34impl DurstewitzDopamineNeuron {
35 pub fn new() -> Self {
36 Self {
37 v: -65.0,
38 h_na: 0.7,
39 n_k: 0.2,
40 g_na: 45.0,
41 g_k: 18.0,
42 g_nmda: 0.5,
43 g_l: 0.02,
44 e_na: 55.0,
45 e_k: -80.0,
46 e_nmda: 0.0,
47 e_l: -65.0,
48 mg: 1.0,
49 d1_level: 0.0,
50 g_nmda_scale: 2.5,
51 g_k_scale: 1.5,
52 v_shift_na: -5.0,
53 dt: 0.05,
54 v_threshold: -20.0,
55 }
56 }
57 fn derivatives(&self, v: f64, h_na: f64, n_k: f64, current: f64) -> [f64; 3] {
64 let v_sh = self.d1_level * self.v_shift_na;
65 let m_na_inf = 1.0 / (1.0 + (-(v + 30.0 + v_sh) / 9.5).exp());
66 let h_na_inf = 1.0 / (1.0 + ((v + 53.0) / 7.0).exp());
67 let n_k_inf = 1.0 / (1.0 + (-(v + 30.0) / 10.0).exp());
68 let tau_h = 0.5 + 14.0 / (1.0 + ((v + 50.0) / 12.0).exp());
69 let tau_n = 1.0 + 11.0 / (1.0 + ((v + 40.0) / 10.0).exp());
70 let d_h_na = (h_na_inf - h_na) / tau_h;
71 let d_n_k = (n_k_inf - n_k) / tau_n;
72 let mg_block = 1.0 / (1.0 + self.mg / 3.57 * (-0.062 * v).exp());
73 let nmda_g = self.g_nmda * (1.0 + self.d1_level * (self.g_nmda_scale - 1.0));
74 let k_g = self.g_k * (1.0 + self.d1_level * (self.g_k_scale - 1.0));
75 let i_na = self.g_na * m_na_inf * m_na_inf * m_na_inf * h_na * (v - self.e_na);
76 let i_k = k_g * n_k * n_k * n_k * n_k * (v - self.e_k);
77 let i_nmda = nmda_g * mg_block * (v - self.e_nmda);
78 let i_l = self.g_l * (v - self.e_l);
79 let d_v = -i_na - i_k - i_nmda - i_l + current;
80 [d_v, d_h_na, d_n_k]
81 }
82
83 fn rk4_substep(&self, s: [f64; 3], current: f64) -> [f64; 3] {
85 let dt = self.dt;
86 let k1 = self.derivatives(s[0], s[1], s[2], current);
87 let k2 = self.derivatives(
88 s[0] + 0.5 * dt * k1[0],
89 s[1] + 0.5 * dt * k1[1],
90 s[2] + 0.5 * dt * k1[2],
91 current,
92 );
93 let k3 = self.derivatives(
94 s[0] + 0.5 * dt * k2[0],
95 s[1] + 0.5 * dt * k2[1],
96 s[2] + 0.5 * dt * k2[2],
97 current,
98 );
99 let k4 = self.derivatives(
100 s[0] + dt * k3[0],
101 s[1] + dt * k3[1],
102 s[2] + dt * k3[2],
103 current,
104 );
105 let mut out = [0.0_f64; 3];
106 for i in 0..3 {
107 out[i] = s[i] + dt * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0;
108 }
109 out
110 }
111
112 pub fn step(&mut self, current: f64) -> i32 {
113 let v_prev = self.v;
114 let s = self.rk4_substep([self.v, self.h_na, self.n_k], current);
115 self.v = s[0];
116 self.h_na = s[1];
117 self.n_k = s[2];
118 if self.v >= self.v_threshold && v_prev < self.v_threshold {
119 1
120 } else {
121 0
122 }
123 }
124 pub fn reset(&mut self) {
125 self.v = -65.0;
126 self.h_na = 0.7;
127 self.n_k = 0.2;
128 }
129}
130impl Default for DurstewitzDopamineNeuron {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn default_matches_constructor_state() {
142 let default = DurstewitzDopamineNeuron::default();
143 let constructed = DurstewitzDopamineNeuron::new();
144 assert_eq!(default.v, constructed.v);
145 }
146
147 #[test]
148 fn durstewitz_fires() {
149 let mut n = DurstewitzDopamineNeuron::new();
150 let t: i32 = (0..1000).map(|_| n.step(3.0)).sum();
151 assert!(t > 0);
152 }
153
154 #[test]
156 fn durstewitz_low_activity_zero_input() {
157 let mut n = DurstewitzDopamineNeuron::new();
158 let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
159 assert!(n.v.is_finite());
161 }
162 #[test]
163 fn durstewitz_reset_clears_state() {
164 let mut n = DurstewitzDopamineNeuron::new();
165 for _ in 0..100 {
166 n.step(3.0);
167 }
168 n.reset();
169 assert!((n.v - (-65.0)).abs() < 1e-10);
170 }
171 #[test]
172 fn durstewitz_extreme_bounded() {
173 let mut n = DurstewitzDopamineNeuron::new();
174 for _ in 0..200 {
175 n.step(1e4);
176 }
177 assert!(n.v.is_finite());
178 }
179 #[test]
180 fn durstewitz_d1_modulation() {
181 let mut n_d1 = DurstewitzDopamineNeuron::new();
183 n_d1.d1_level = 1.0;
184 let mut n_no = DurstewitzDopamineNeuron::new();
185 n_no.d1_level = 0.0;
186 for _ in 0..1000 {
187 n_d1.step(3.0);
188 }
189 for _ in 0..1000 {
190 n_no.step(3.0);
191 }
192 assert!(n_d1.v.is_finite() && n_no.v.is_finite());
194 }
195 #[test]
196 fn durstewitz_mg_block() {
197 let n = DurstewitzDopamineNeuron::new();
198 let block = 1.0 / (1.0 + n.mg * (-0.062 * n.v).exp() / 3.57);
200 assert!(block < 0.1, "Mg²⁺ block at rest should be high: {}", block);
201 }
202 #[test]
203 fn durstewitz_negative_no_crash() {
204 let mut n = DurstewitzDopamineNeuron::new();
205 for _ in 0..200 {
206 n.step(-10.0);
207 }
208 assert!(n.v.is_finite());
209 }
210}