sc_neurocore_engine/neurons/
rulkov_map.rs1#[derive(Clone, Debug)]
13pub struct RulkovMapNeuron {
14 pub x: f64,
15 pub y: f64,
16 pub alpha: f64,
17 pub sigma: f64,
18 pub mu: f64,
19}
20
21impl RulkovMapNeuron {
22 pub fn new() -> Self {
23 Self {
24 x: -1.0,
25 y: -3.0,
26 alpha: 4.0,
27 sigma: -1.6,
28 mu: 0.001,
29 }
30 }
31 fn valid_numeric_contract(&self) -> bool {
32 self.x.is_finite()
33 && self.y.is_finite()
34 && self.alpha.is_finite()
35 && self.alpha > 0.0
36 && self.sigma.is_finite()
37 && self.mu.is_finite()
38 && self.mu > 0.0
39 }
40
41 fn candidate(&self, current: f64) -> Option<(f64, f64, i32)> {
42 let boundary = self.alpha + self.y + current;
43 if !boundary.is_finite() {
44 return None;
45 }
46 let event = i32::from(self.x > 0.0 && self.x >= boundary);
47 let x_new = if self.x <= 0.0 {
48 let denominator = 1.0 - self.x;
49 if denominator <= 0.0 || !denominator.is_finite() {
50 return None;
51 }
52 self.alpha / denominator + self.y + current
53 } else if self.x < boundary {
54 boundary
55 } else {
56 -1.0
57 };
58 let y_new = self.y - self.mu * (self.x + 1.0) + self.mu * self.sigma;
59 if x_new.is_finite() && y_new.is_finite() {
60 Some((x_new, y_new, event))
61 } else {
62 None
63 }
64 }
65 pub fn try_step(&mut self, current: f64) -> Option<i32> {
67 if !self.valid_numeric_contract() || !current.is_finite() {
68 return None;
69 }
70 let (x_new, y_new, event) = self.candidate(current)?;
71 self.x = x_new;
72 self.y = y_new;
73 Some(event)
74 }
75 pub fn step(&mut self, current: f64) -> i32 {
77 self.try_step(current).unwrap_or(0)
78 }
79 pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
84 self.try_simulate(n_steps, current).unwrap_or_default()
85 }
86 pub fn try_simulate(&mut self, n_steps: usize, current: f64) -> Option<(Vec<f64>, i64)> {
88 let mut candidate = self.clone();
89 let mut trace = Vec::with_capacity(n_steps);
90 let mut events: i64 = 0;
91 for _ in 0..n_steps {
92 events += i64::from(candidate.try_step(current)?);
93 trace.push(candidate.x);
94 }
95 *self = candidate;
96 Some((trace, events))
97 }
98 pub fn reset(&mut self) {
99 self.x = -1.0;
100 self.y = -3.0;
101 }
102}
103impl Default for RulkovMapNeuron {
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn rulkov_fires() {
115 let mut n = RulkovMapNeuron::new();
116 let t: i32 = (0..2000).map(|_| n.step(0.5)).sum();
117 assert!(t > 0);
118 }
119
120 #[test]
121 fn source_event_is_rightmost_branch_execution() {
122 let mut neuron = RulkovMapNeuron {
123 x: 1.0,
124 y: -3.0,
125 ..Default::default()
126 };
127 assert_eq!(neuron.step(0.0), 1);
128 assert_eq!(neuron.x, -1.0);
129 }
130
131 #[test]
132 fn invalid_batch_is_failure_atomic() {
133 let mut neuron = RulkovMapNeuron::new();
134 neuron.alpha = f64::NAN;
135 let before = (neuron.x, neuron.y);
136 assert!(neuron.try_simulate(2, 0.0).is_none());
137 assert_eq!((neuron.x, neuron.y), before);
138 }
139}