sc_neurocore_engine/neurons/trivial/
mat.rs1const V_MIN: f64 = -200.0;
12const V_MAX: f64 = 200.0;
13const THETA_MAX: f64 = 1.0e9;
14
15#[derive(Clone, Debug, PartialEq)]
21pub struct MATNeuron {
22 pub v: f64,
24 pub theta1: f64,
26 pub theta2: f64,
28 pub refractory_remaining: f64,
30 pub omega: f64,
32 pub tau_m: f64,
34 pub tau_1: f64,
36 pub tau_2: f64,
38 pub alpha_1: f64,
40 pub alpha_2: f64,
42 pub resistance: f64,
44 pub refractory_period: f64,
46 pub dt: f64,
48}
49
50impl MATNeuron {
51 #[must_use]
53 pub fn new() -> Self {
54 Self {
55 v: 0.0,
56 theta1: 0.0,
57 theta2: 0.0,
58 refractory_remaining: 0.0,
59 omega: 19.0,
60 tau_m: 5.0,
61 tau_1: 10.0,
62 tau_2: 200.0,
63 alpha_1: 37.0,
64 alpha_2: 2.0,
65 resistance: 50.0,
66 refractory_period: 2.0,
67 dt: 0.001,
68 }
69 }
70
71 #[must_use]
73 pub fn intrinsically_bursting() -> Self {
74 Self {
75 omega: 26.0,
76 alpha_1: 1.7,
77 alpha_2: 2.0,
78 ..Self::new()
79 }
80 }
81
82 #[must_use]
84 pub fn fast_spiking() -> Self {
85 Self {
86 omega: 11.0,
87 alpha_1: 10.0,
88 alpha_2: 0.002,
89 ..Self::new()
90 }
91 }
92
93 #[must_use]
95 pub fn threshold(&self) -> f64 {
96 self.omega + self.theta1 + self.theta2
97 }
98
99 #[must_use]
101 pub fn validate(&self) -> bool {
102 let finite = [
103 self.v,
104 self.theta1,
105 self.theta2,
106 self.refractory_remaining,
107 self.omega,
108 self.tau_m,
109 self.tau_1,
110 self.tau_2,
111 self.alpha_1,
112 self.alpha_2,
113 self.resistance,
114 self.refractory_period,
115 self.dt,
116 ]
117 .iter()
118 .all(|value| value.is_finite());
119 finite
120 && (V_MIN..=V_MAX).contains(&self.v)
121 && (-THETA_MAX..=THETA_MAX).contains(&self.omega)
122 && [self.theta1, self.theta2, self.alpha_1, self.alpha_2]
123 .iter()
124 .all(|value| (0.0..=THETA_MAX).contains(value))
125 && self.tau_m > 0.0
126 && self.tau_1 > 0.0
127 && self.tau_2 > 0.0
128 && self.resistance > 0.0
129 && self.refractory_period >= 0.0
130 && self.dt > 0.0
131 && (0.0..=self.refractory_period).contains(&self.refractory_remaining)
132 }
133
134 pub fn try_step(&mut self, current: f64) -> Result<i32, &'static str> {
140 if !current.is_finite() || !self.validate() {
141 return Err("invalid MAT state, configuration, or current");
142 }
143 let v = self.v + self.dt * (-self.v + self.resistance * current) / self.tau_m;
144 let mut theta1 = self.theta1 * (-self.dt / self.tau_1).exp();
145 let mut theta2 = self.theta2 * (-self.dt / self.tau_2).exp();
146 let mut refractory = (self.refractory_remaining - self.dt).max(0.0);
147 if ![v, theta1, theta2, refractory]
148 .iter()
149 .all(|value| value.is_finite())
150 || !(V_MIN..=V_MAX).contains(&v)
151 || !(0.0..=THETA_MAX).contains(&theta1)
152 || !(0.0..=THETA_MAX).contains(&theta2)
153 {
154 return Err("MAT candidate outside safety envelope");
155 }
156 let spike = refractory == 0.0 && v >= self.omega + theta1 + theta2;
157 if spike {
158 theta1 += self.alpha_1;
159 theta2 += self.alpha_2;
160 refractory = self.refractory_period;
161 if theta1 > THETA_MAX || theta2 > THETA_MAX {
162 return Err("MAT post-spike threshold outside safety envelope");
163 }
164 }
165 self.v = v;
166 self.theta1 = theta1;
167 self.theta2 = theta2;
168 self.refractory_remaining = refractory;
169 Ok(i32::from(spike))
170 }
171
172 pub fn step(&mut self, current: f64) -> i32 {
174 self.try_step(current).unwrap_or(0)
175 }
176
177 pub fn reset(&mut self) {
179 self.v = 0.0;
180 self.theta1 = 0.0;
181 self.theta2 = 0.0;
182 self.refractory_remaining = 0.0;
183 }
184}
185
186impl Default for MATNeuron {
187 fn default() -> Self {
188 Self::new()
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn source_step_is_non_resetting() {
198 let mut neuron = MATNeuron {
199 v: 20.0,
200 ..MATNeuron::new()
201 };
202 let expected = 20.0 + neuron.dt * (-20.0) / neuron.tau_m;
203 assert_eq!(neuron.try_step(0.0), Ok(1));
204 assert_eq!(neuron.v, expected);
205 assert_eq!(neuron.theta1, 37.0);
206 assert_eq!(neuron.theta2, 2.0);
207 }
208
209 #[test]
210 fn source_reemits_after_refractory() {
211 let mut neuron = MATNeuron {
212 v: 25.0,
213 omega: 1.0,
214 alpha_1: 0.0,
215 alpha_2: 0.0,
216 tau_m: 1.0e9,
217 refractory_period: 2.0,
218 dt: 0.5,
219 ..MATNeuron::new()
220 };
221 assert_eq!(neuron.try_step(0.0), Ok(1));
222 assert_eq!(
223 (0..4)
224 .map(|_| neuron.try_step(0.0).unwrap())
225 .collect::<Vec<_>>(),
226 vec![0, 0, 0, 1]
227 );
228 }
229
230 #[test]
231 fn failure_is_atomic() {
232 let mut neuron = MATNeuron::new();
233 let before = neuron.clone();
234 assert!(neuron.try_step(f64::NAN).is_err());
235 assert_eq!(neuron, before);
236 }
237}