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