Skip to main content

sc_neurocore_engine/neurons/
rulkov_map.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Commercial license available
3// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4// © Code 2020–2026 Miroslav Šotek. All rights reserved.
5// ORCID: 0009-0009-3560-0851
6// Contact: www.anulum.li | protoscience@anulum.li
7// SC-NeuroCore — Rulkov discrete map neuron
8
9//! Rulkov discrete map neuron.
10
11/// Rulkov 2001 — piecewise nonlinear map for fast/slow bursting.
12#[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    pub x_threshold: f64,
20}
21
22impl RulkovMapNeuron {
23    pub fn new() -> Self {
24        Self {
25            x: -1.0,
26            y: -3.0,
27            alpha: 4.0,
28            sigma: -1.6,
29            mu: 0.001,
30            x_threshold: 0.0,
31        }
32    }
33    pub fn step(&mut self, current: f64) -> i32 {
34        let x_prev = self.x;
35        let x_new = if self.x <= 0.0 {
36            self.alpha / (1.0 - self.x) + self.y + current
37        } else if self.x < self.alpha + self.y + current {
38            self.alpha + self.y + current
39        } else {
40            -1.0
41        };
42        let y_new = self.y - self.mu * (self.x + 1.0) + self.mu * self.sigma;
43        self.x = x_new;
44        self.y = y_new;
45        if self.x >= self.x_threshold && x_prev < self.x_threshold {
46            1
47        } else {
48            0
49        }
50    }
51    /// Run `n_steps` under a constant input, returning the `x` trace and the
52    /// upward-crossing spike count. Reuses `step` so the trace is bit-identical
53    /// to the per-step path and to the Python reference. The final state is
54    /// left in `self.x` / `self.y`.
55    pub fn simulate(&mut self, n_steps: usize, current: f64) -> (Vec<f64>, i64) {
56        let mut trace = Vec::with_capacity(n_steps);
57        let mut spikes: i64 = 0;
58        for _ in 0..n_steps {
59            let spiked = self.step(current);
60            trace.push(self.x);
61            spikes += spiked as i64;
62        }
63        (trace, spikes)
64    }
65    pub fn reset(&mut self) {
66        self.x = -1.0;
67        self.y = -3.0;
68    }
69}
70impl Default for RulkovMapNeuron {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn rulkov_fires() {
82        let mut n = RulkovMapNeuron::new();
83        let t: i32 = (0..2000).map(|_| n.step(0.5)).sum();
84        assert!(t > 0);
85    }
86}