sc_neurocore_engine/neurons/
nagumo_sato_map.rs1#![warn(missing_docs)]
12
13#[derive(Clone, Debug)]
15pub struct NagumoSatoMapNeuron {
16 pub y: f64,
18 pub k: f64,
20 pub alpha: f64,
22 pub bias: f64,
24}
25
26impl Default for NagumoSatoMapNeuron {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl NagumoSatoMapNeuron {
33 pub fn new() -> Self {
35 Self {
36 y: 0.1,
37 k: 0.6,
38 alpha: 1.0,
39 bias: 0.2,
40 }
41 }
42
43 fn heaviside(value: f64) -> i32 {
44 i32::from(value >= 0.0)
45 }
46
47 fn valid(&self) -> bool {
48 [self.y, self.k, self.alpha, self.bias]
49 .iter()
50 .all(|value| value.is_finite())
51 && (0.0..1.0).contains(&self.k)
52 && self.alpha > 0.0
53 }
54
55 pub fn output(&self) -> i32 {
57 Self::heaviside(self.y)
58 }
59
60 pub fn try_step(&mut self, current: f64) -> Result<i32, NagumoSatoMapError> {
62 if !self.valid() {
63 return Err(NagumoSatoMapError::InvalidConfiguration);
64 }
65 if !current.is_finite() {
66 return Err(NagumoSatoMapError::NonFiniteInput);
67 }
68 let next_y = self.k * self.y - self.alpha * f64::from(self.output()) + self.bias + current;
69 if !next_y.is_finite() {
70 return Err(NagumoSatoMapError::NonFiniteCandidate);
71 }
72 let event = Self::heaviside(next_y);
73 self.y = next_y;
74 Ok(event)
75 }
76
77 pub fn step(&mut self, current: f64) -> i32 {
79 self.try_step(current).unwrap_or(0)
80 }
81
82 pub fn reset(&mut self) {
84 self.y = 0.1;
85 }
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub enum NagumoSatoMapError {
91 InvalidConfiguration,
93 NonFiniteInput,
95 NonFiniteCandidate,
97 StepLimitExceeded,
99}
100
101impl std::fmt::Display for NagumoSatoMapError {
102 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 formatter.write_str(match self {
104 Self::InvalidConfiguration => "invalid Nagumo-Sato state or parameters",
105 Self::NonFiniteInput => "current must contain only finite values",
106 Self::NonFiniteCandidate => "Nagumo-Sato map candidate must be finite",
107 Self::StepLimitExceeded => "current exceeds the signed-32-bit step limit",
108 })
109 }
110}
111
112impl std::error::Error for NagumoSatoMapError {}
113
114#[derive(Clone, Debug)]
116pub struct NagumoSatoMapBatchResult {
117 pub y: Vec<f64>,
119 pub x: Vec<u8>,
121 pub spikes: Vec<u8>,
123 pub y_final: f64,
125 pub x_final: u8,
127 pub spike_count: usize,
129}
130
131pub fn simulate_nagumo_sato_map(
133 y: f64,
134 k: f64,
135 alpha: f64,
136 bias: f64,
137 current: &[f64],
138) -> Result<NagumoSatoMapBatchResult, NagumoSatoMapError> {
139 if current.len() > i32::MAX as usize {
140 return Err(NagumoSatoMapError::StepLimitExceeded);
141 }
142 let mut neuron = NagumoSatoMapNeuron { y, k, alpha, bias };
143 if !neuron.valid() {
144 return Err(NagumoSatoMapError::InvalidConfiguration);
145 }
146 if current.iter().any(|value| !value.is_finite()) {
147 return Err(NagumoSatoMapError::NonFiniteInput);
148 }
149 let mut y_trace = Vec::with_capacity(current.len());
150 let mut output = Vec::with_capacity(current.len());
151 let mut spike_count = 0usize;
152 for &drive in current {
153 let event = neuron.try_step(drive)? as u8;
154 y_trace.push(neuron.y);
155 output.push(event);
156 spike_count += event as usize;
157 }
158 Ok(NagumoSatoMapBatchResult {
159 y: y_trace,
160 x: output.clone(),
161 spikes: output,
162 y_final: neuron.y,
163 x_final: neuron.output() as u8,
164 spike_count,
165 })
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn first_steps_match_source_equation() {
174 let mut neuron = NagumoSatoMapNeuron::new();
175 assert_eq!(neuron.try_step(0.0), Ok(0));
176 assert!((neuron.y - (-0.74)).abs() < 1.0e-15);
177 assert_eq!(neuron.try_step(0.0), Ok(0));
178 assert!((neuron.y - (-0.244)).abs() < 1.0e-15);
179 assert_eq!(neuron.try_step(0.0), Ok(1));
180 assert!((neuron.y - 0.0536).abs() < 1.0e-15);
181 }
182
183 #[test]
184 fn invalid_batch_is_atomic() {
185 assert_eq!(
186 simulate_nagumo_sato_map(0.1, 0.6, 1.0, 0.2, &[0.0, f64::NAN]).unwrap_err(),
187 NagumoSatoMapError::NonFiniteInput
188 );
189 }
190
191 #[test]
192 fn reset_preserves_parameters() {
193 let mut neuron = NagumoSatoMapNeuron {
194 y: -2.0,
195 k: 0.5,
196 alpha: 2.0,
197 bias: 0.7,
198 };
199 neuron.reset();
200 assert_eq!(
201 (neuron.y, neuron.k, neuron.alpha, neuron.bias),
202 (0.1, 0.5, 2.0, 0.7)
203 );
204 }
205}