Skip to main content

sc_neurocore_engine/neurons/
maps.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later | Commercial license available
2// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
3// © Code 2020–2026 Miroslav Šotek. All rights reserved.
4// ORCID: 0009-0009-3560-0851
5// Contact: www.anulum.li | protoscience@anulum.li
6// SC-NeuroCore — Discrete map neuron models
7
8//! Discrete map neuron models.
9
10/// Chialvo 1995 — 2D discrete map neuron.
11#[derive(Clone, Debug)]
12pub struct ChialvoMapNeuron {
13    pub x: f64,
14    pub y: f64,
15    pub a: f64,
16    pub b: f64,
17    pub c: f64,
18    pub k: f64,
19    pub x_threshold: f64,
20}
21
22impl ChialvoMapNeuron {
23    pub fn new() -> Self {
24        Self {
25            x: 0.0,
26            y: 0.0,
27            a: 0.89,
28            b: 0.6,
29            c: 0.28,
30            k: 0.04,
31            x_threshold: 1.0,
32        }
33    }
34    pub fn step(&mut self, current: f64) -> i32 {
35        let x_prev = self.x;
36        let x_new = self.x * self.x * (self.y - self.x).exp() + self.k + current;
37        let y_new = self.a * self.y - self.b * self.x + self.c;
38        self.x = x_new;
39        self.y = y_new;
40        if self.x >= self.x_threshold && x_prev < self.x_threshold {
41            1
42        } else {
43            0
44        }
45    }
46    pub fn reset(&mut self) {
47        self.x = 0.0;
48        self.y = 0.0;
49    }
50}
51impl Default for ChialvoMapNeuron {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57/// Rulkov 2001 — piecewise nonlinear map for fast/slow bursting.
58#[derive(Clone, Debug)]
59pub struct RulkovMapNeuron {
60    pub x: f64,
61    pub y: f64,
62    pub alpha: f64,
63    pub sigma: f64,
64    pub mu: f64,
65    pub x_threshold: f64,
66}
67
68impl RulkovMapNeuron {
69    pub fn new() -> Self {
70        Self {
71            x: -1.0,
72            y: -3.0,
73            alpha: 4.0,
74            sigma: -1.6,
75            mu: 0.001,
76            x_threshold: 0.0,
77        }
78    }
79    pub fn step(&mut self, current: f64) -> i32 {
80        let x_prev = self.x;
81        let f = if self.x <= 0.0 {
82            self.alpha / (1.0 - self.x) + self.y
83        } else if self.x < self.alpha + self.y {
84            self.alpha + self.y
85        } else {
86            -1.0
87        };
88        let x_new = f + current;
89        let y_new = self.y - self.mu * (self.x + 1.0) + self.mu * self.sigma;
90        self.x = x_new;
91        self.y = y_new;
92        if self.x >= self.x_threshold && x_prev < self.x_threshold {
93            1
94        } else {
95            0
96        }
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/// Ibarz-Tanaka map — piecewise-linear spiking map.
110#[derive(Clone, Debug)]
111pub struct IbarzTanakaMapNeuron {
112    pub x: f64,
113    pub y: f64,
114    pub alpha: f64,
115    pub beta: f64,
116    pub mu: f64,
117    pub sigma: f64,
118    pub x_threshold: f64,
119    pub x_reset: f64,
120}
121
122impl IbarzTanakaMapNeuron {
123    pub fn new() -> Self {
124        Self {
125            x: -1.0,
126            y: -2.5,
127            alpha: 3.65,
128            beta: 0.25,
129            mu: 0.0005,
130            sigma: -1.6,
131            x_threshold: 3.0,
132            x_reset: -1.0,
133        }
134    }
135    pub fn step(&mut self, current: f64) -> i32 {
136        let f = if self.x <= 0.0 {
137            self.alpha / (1.0 - self.x)
138        } else {
139            self.alpha + self.beta * self.x
140        };
141        let x_new = f + self.y + current;
142        let y_new = self.y - self.mu * (self.x + 1.0) + self.mu * self.sigma;
143        self.x = x_new;
144        self.y = y_new;
145        if self.x >= self.x_threshold {
146            self.x = self.x_reset;
147            1
148        } else {
149            0
150        }
151    }
152    pub fn reset(&mut self) {
153        self.x = -1.0;
154        self.y = -2.5;
155    }
156}
157impl Default for IbarzTanakaMapNeuron {
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163/// Medvedev map — piecewise monotone 1D neuron map.
164#[derive(Clone, Debug)]
165pub struct MedvedevMapNeuron {
166    pub x: f64,
167    pub alpha: f64,
168    pub beta: f64,
169    pub x_threshold: f64,
170}
171
172impl MedvedevMapNeuron {
173    pub fn new() -> Self {
174        Self {
175            x: 0.0,
176            alpha: 3.5,
177            beta: 0.5,
178            x_threshold: 0.9,
179        }
180    }
181    pub fn step(&mut self, current: f64) -> i32 {
182        let x_prev = self.x;
183        let f = self.alpha * self.x * (1.0 - self.x) + self.beta * current;
184        self.x = f - f.floor();
185        if self.x >= self.x_threshold && x_prev < self.x_threshold {
186            1
187        } else {
188            0
189        }
190    }
191    pub fn reset(&mut self) {
192        self.x = 0.0;
193    }
194}
195impl Default for MedvedevMapNeuron {
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201/// Cazelles logistic map neuron — coupled 2D logistic with slow variable.
202#[derive(Clone, Debug)]
203pub struct CazellesMapNeuron {
204    pub x: f64,
205    pub y: f64,
206    pub a: f64,
207    pub epsilon: f64,
208    pub sigma: f64,
209    pub x_threshold: f64,
210}
211
212impl CazellesMapNeuron {
213    pub fn new() -> Self {
214        Self {
215            x: 0.1,
216            y: 0.0,
217            a: 3.8,
218            epsilon: 0.01,
219            sigma: 0.5,
220            x_threshold: 0.9,
221        }
222    }
223    pub fn step(&mut self, current: f64) -> i32 {
224        let x_prev = self.x;
225        let f = self.a * self.x * (1.0 - self.x);
226        let x_new = (f - self.y + current).clamp(-2.0, 2.0);
227        let y_new = self.y + self.epsilon * (self.x - self.sigma);
228        self.x = x_new;
229        self.y = y_new;
230        if self.x >= self.x_threshold && x_prev < self.x_threshold {
231            1
232        } else {
233            0
234        }
235    }
236    pub fn reset(&mut self) {
237        self.x = 0.1;
238        self.y = 0.0;
239    }
240}
241impl Default for CazellesMapNeuron {
242    fn default() -> Self {
243        Self::new()
244    }
245}
246
247/// Courbage-Nekorkin piecewise-linear Lorenz-type map.
248#[derive(Clone, Debug)]
249pub struct CourageNekorkinMapNeuron {
250    pub x: f64,
251    pub y: f64,
252    pub alpha: f64,
253    pub beta: f64,
254    pub j: f64,
255    pub x_threshold: f64,
256}
257
258impl CourageNekorkinMapNeuron {
259    pub fn new() -> Self {
260        Self {
261            x: 0.0,
262            y: 0.0,
263            alpha: 3.0,
264            beta: 0.001,
265            j: 0.1,
266            x_threshold: 1.0,
267        }
268    }
269    pub fn step(&mut self, current: f64) -> i32 {
270        let x_prev = self.x;
271        let f = if self.x.abs() < 1.0 {
272            self.alpha * self.x
273        } else {
274            self.alpha * self.x.signum()
275        };
276        let x_new = f - self.y + self.j + current;
277        let y_new = self.y + self.beta * self.x;
278        self.x = x_new;
279        self.y = y_new;
280        if self.x >= self.x_threshold && x_prev < self.x_threshold {
281            1
282        } else {
283            0
284        }
285    }
286    pub fn reset(&mut self) {
287        self.x = 0.0;
288        self.y = 0.0;
289    }
290}
291impl Default for CourageNekorkinMapNeuron {
292    fn default() -> Self {
293        Self::new()
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn chialvo_fires() {
303        let mut n = ChialvoMapNeuron::new();
304        let t: i32 = (0..1000).map(|_| n.step(1.0)).sum();
305        assert!(t > 0);
306    }
307    #[test]
308    fn rulkov_fires() {
309        let mut n = RulkovMapNeuron::new();
310        let t: i32 = (0..2000).map(|_| n.step(0.5)).sum();
311        assert!(t > 0);
312    }
313    #[test]
314    fn ibarz_fires() {
315        let mut n = IbarzTanakaMapNeuron::new();
316        let t: i32 = (0..2000).map(|_| n.step(2.0)).sum();
317        assert!(t > 0);
318    }
319    #[test]
320    fn medvedev_fires() {
321        let mut n = MedvedevMapNeuron {
322            x: 0.5,
323            ..Default::default()
324        };
325        let t: i32 = (0..500).map(|_| n.step(0.1)).sum();
326        assert!(t > 0);
327    }
328    #[test]
329    fn cazelles_fires() {
330        let mut n = CazellesMapNeuron::new();
331        let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
332        assert!(t > 0);
333    }
334    #[test]
335    fn cournekorkin_fires() {
336        let mut n = CourageNekorkinMapNeuron::new();
337        let t: i32 = (0..200).map(|_| n.step(0.5)).sum();
338        assert!(t > 0);
339    }
340}