sc_neurocore_engine/neurons/
sc_chaotic_map.rs1#![warn(missing_docs)]
12
13#[derive(Clone, Debug)]
15pub struct SCChaoticMapNeuron {
16 pub x: f64,
18 pub y: f64,
20 pub k_f: f64,
22 pub k_s: f64,
24 pub alpha: f64,
26 pub delta: f64,
28 pub x_threshold: f64,
30}
31
32impl Default for SCChaoticMapNeuron {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl SCChaoticMapNeuron {
39 pub fn new() -> Self {
41 Self {
42 x: 0.0,
43 y: 0.0,
44 k_f: 0.7,
45 k_s: 0.95,
46 alpha: 2.0,
47 delta: 0.05,
48 x_threshold: 0.5,
49 }
50 }
51
52 fn sigmoid(value: f64) -> f64 {
53 if value >= 0.0 {
54 1.0 / (1.0 + (-value).exp())
55 } else {
56 let exponential = value.exp();
57 exponential / (1.0 + exponential)
58 }
59 }
60
61 fn valid(&self) -> bool {
62 [
63 self.x,
64 self.y,
65 self.k_f,
66 self.k_s,
67 self.alpha,
68 self.delta,
69 self.x_threshold,
70 ]
71 .iter()
72 .all(|value| value.is_finite())
73 && self.k_f >= 0.0
74 && self.delta >= 0.0
75 }
76
77 pub fn try_step(&mut self, current: f64) -> Result<i32, &'static str> {
79 if !self.valid() || !current.is_finite() {
80 return Err("invalid SC chaotic map state, parameters, or current");
81 }
82 let previous = self.x;
83 let x_next = self.k_f * self.x * Self::sigmoid(self.x + self.alpha) - self.y + current;
84 let y_next = self.k_s * self.y + self.delta * self.x;
85 if !x_next.is_finite() || !y_next.is_finite() {
86 return Err("non-finite SC chaotic map candidate");
87 }
88 self.x = x_next.clamp(-10.0, 10.0);
89 self.y = y_next.clamp(-10.0, 10.0);
90 Ok(i32::from(
91 previous < self.x_threshold && self.x >= self.x_threshold,
92 ))
93 }
94
95 pub fn step(&mut self, current: f64) -> i32 {
97 self.try_step(current).unwrap_or(0)
98 }
99
100 pub fn reset(&mut self) {
102 self.x = 0.0;
103 self.y = 0.0;
104 }
105}
106
107#[derive(Clone, Debug)]
109pub struct SCChaoticMapBatchResult {
110 pub x: Vec<f64>,
112 pub y: Vec<f64>,
114 pub spikes: Vec<u8>,
116 pub x_final: f64,
118 pub y_final: f64,
120 pub spike_count: usize,
122}
123
124pub fn simulate_sc_chaotic_map(
126 x: f64,
127 y: f64,
128 k_f: f64,
129 k_s: f64,
130 alpha: f64,
131 delta: f64,
132 x_threshold: f64,
133 current: &[f64],
134) -> Result<SCChaoticMapBatchResult, &'static str> {
135 if current.len() > i32::MAX as usize {
136 return Err("current exceeds the signed-32-bit step limit");
137 }
138 let mut neuron = SCChaoticMapNeuron {
139 x,
140 y,
141 k_f,
142 k_s,
143 alpha,
144 delta,
145 x_threshold,
146 };
147 if current.iter().any(|value| !value.is_finite()) {
148 return Err("current must contain only finite values");
149 }
150 if !neuron.valid() {
151 return Err("invalid SC chaotic map state or parameters");
152 }
153
154 let mut x_trace = Vec::with_capacity(current.len());
155 let mut y_trace = Vec::with_capacity(current.len());
156 let mut spikes = Vec::with_capacity(current.len());
157 let mut spike_count = 0usize;
158 for &drive in current {
159 let event = neuron.try_step(drive)?;
160 x_trace.push(neuron.x);
161 y_trace.push(neuron.y);
162 spikes.push(event as u8);
163 spike_count += event as usize;
164 }
165 Ok(SCChaoticMapBatchResult {
166 x: x_trace,
167 y: y_trace,
168 spikes,
169 x_final: neuron.x,
170 y_final: neuron.y,
171 spike_count,
172 })
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn retained_recurrence_matches_independent_step() {
181 let mut neuron = SCChaoticMapNeuron {
182 x: 0.4,
183 y: -0.2,
184 ..Default::default()
185 };
186 let expected_x = 0.7 * 0.4 / (1.0 + (-2.4_f64).exp()) + 0.2 + 0.1;
187 let expected_y = 0.95 * -0.2 + 0.05 * 0.4;
188 neuron.try_step(0.1).unwrap();
189 assert!((neuron.x - expected_x).abs() < 1.0e-15);
190 assert!((neuron.y - expected_y).abs() < 1.0e-15);
191 }
192
193 #[test]
194 fn rejected_input_is_atomic() {
195 let mut neuron = SCChaoticMapNeuron::new();
196 assert!(neuron.try_step(f64::NAN).is_err());
197 assert_eq!((neuron.x, neuron.y), (0.0, 0.0));
198 }
199
200 #[test]
201 fn batch_returns_complete_receipts() {
202 let result =
203 simulate_sc_chaotic_map(0.4, -0.2, 0.7, 0.95, 2.0, 0.05, 0.5, &[0.1, 0.1]).unwrap();
204 assert_eq!(result.x.len(), 2);
205 assert_eq!(result.y.len(), 2);
206 assert_eq!(result.spikes, vec![1, 0]);
207 assert_eq!(result.spike_count, 1);
208 }
209}