Skip to main content

sc_neurocore_engine/neurons/
ibarz_tanaka_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 — Ibarz-Tanaka discrete map neuron
8
9//! Ibarz-Tanaka discrete map neuron.
10
11/// Ibarz-Tanaka (2007) four-branch Rulkov map.
12#[derive(Clone, Debug)]
13pub struct IbarzTanakaMapNeuron {
14    pub v: f64,
15    pub u: f64,
16    pub alpha: f64,
17    pub mu: f64,
18    pub sigma: f64,
19}
20
21impl IbarzTanakaMapNeuron {
22    pub fn new() -> Self {
23        Self {
24            v: -1.0,
25            u: -0.1,
26            alpha: 1.0,
27            mu: 0.001,
28            sigma: 0.1,
29        }
30    }
31
32    fn parameters_are_valid(&self) -> bool {
33        self.alpha.is_finite()
34            && self.mu.is_finite()
35            && self.sigma.is_finite()
36            && self.alpha > 0.0
37            && self.mu > 0.0
38    }
39
40    fn candidate(&self, current: f64) -> Result<(f64, f64, i32), &'static str> {
41        let lower = -1.0 - self.alpha / 2.0;
42        let upper = 1.0 + current + self.u;
43        let v_next = if self.v < lower {
44            -(self.alpha * self.alpha) / 4.0 - self.alpha + current + self.u
45        } else if self.v <= 0.0 {
46            self.alpha * self.v + (self.v + 1.0) * (self.v + 1.0) + current + self.u
47        } else if self.v < upper {
48            upper
49        } else {
50            -1.0
51        };
52        let u_next = self.u - self.mu * (self.v + 1.0 - self.sigma);
53        if !v_next.is_finite() || !u_next.is_finite() {
54            return Err("invalid Ibarz-Tanaka map candidate");
55        }
56        Ok((v_next, u_next, i32::from(self.v >= upper)))
57    }
58
59    /// Checked source-derived update; a rejected step leaves the state intact.
60    pub fn try_step(&mut self, current: f64) -> Result<i32, &'static str> {
61        if !self.v.is_finite() || !self.u.is_finite() || !self.parameters_are_valid() {
62            return Err("invalid Ibarz-Tanaka runtime state");
63        }
64        if !current.is_finite() {
65            return Err("invalid Ibarz-Tanaka current");
66        }
67        let (v_next, u_next, event) = self.candidate(current)?;
68        self.v = v_next;
69        self.u = u_next;
70        Ok(event)
71    }
72
73    /// Legacy infallible engine-class update; invalid input emits no event.
74    pub fn step(&mut self, current: f64) -> i32 {
75        self.try_step(current).unwrap_or(0)
76    }
77
78    /// Run checked Eq. 2-3 iterations and return the post-step `v` trace.
79    pub fn simulate(
80        &mut self,
81        n_steps: usize,
82        current: f64,
83    ) -> Result<(Vec<f64>, i64), &'static str> {
84        let mut trace = Vec::with_capacity(n_steps);
85        let mut events = 0_i64;
86        for _ in 0..n_steps {
87            events += i64::from(self.try_step(current)?);
88            trace.push(self.v);
89        }
90        Ok((trace, events))
91    }
92
93    pub fn reset(&mut self) {
94        self.v = -1.0;
95        self.u = -0.1;
96    }
97}
98impl Default for IbarzTanakaMapNeuron {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn ibarz_fires() {
110        let mut n = IbarzTanakaMapNeuron::new();
111        let t: i32 = (0..2000).map(|_| n.step(2.0)).sum();
112        assert!(t > 0);
113    }
114}