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 analysis profile of the Shilnikov-Rulkov discrete map neuron.
10
11/// Ibarz et al. (2007) analysis profile of the Shilnikov-Rulkov (2004) 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, event) = if self.v < lower {
44            (
45                -(self.alpha * self.alpha) / 4.0 - self.alpha + current + self.u,
46                0,
47            )
48        } else if self.v <= 0.0 {
49            (
50                self.alpha * self.v + (self.v + 1.0) * (self.v + 1.0) + current + self.u,
51                0,
52            )
53        } else if self.v < upper {
54            (upper, 0)
55        } else {
56            (-1.0, 1)
57        };
58        let u_next = self.u - self.mu * (self.v + 1.0 - self.sigma);
59        if !v_next.is_finite() || !u_next.is_finite() {
60            return Err("invalid Ibarz-Tanaka map candidate");
61        }
62        Ok((v_next, u_next, event))
63    }
64
65    /// Checked source-derived update; a rejected step leaves the state intact.
66    pub fn try_step(&mut self, current: f64) -> Result<i32, &'static str> {
67        if !self.v.is_finite() || !self.u.is_finite() || !self.parameters_are_valid() {
68            return Err("invalid Ibarz-Tanaka runtime state");
69        }
70        if !current.is_finite() {
71            return Err("invalid Ibarz-Tanaka current");
72        }
73        let (v_next, u_next, event) = self.candidate(current)?;
74        self.v = v_next;
75        self.u = u_next;
76        Ok(event)
77    }
78
79    /// Legacy infallible engine-class update; invalid input emits no event.
80    pub fn step(&mut self, current: f64) -> i32 {
81        self.try_step(current).unwrap_or(0)
82    }
83
84    /// Run checked Eq. 2-3 iterations and return the post-step `v` trace.
85    pub fn simulate(
86        &mut self,
87        n_steps: usize,
88        current: f64,
89    ) -> Result<(Vec<f64>, i64), &'static str> {
90        let mut candidate = self.clone();
91        let mut trace = Vec::with_capacity(n_steps);
92        let mut events = 0_i64;
93        for _ in 0..n_steps {
94            events += i64::from(candidate.try_step(current)?);
95            trace.push(candidate.v);
96        }
97        self.v = candidate.v;
98        self.u = candidate.u;
99        Ok((trace, events))
100    }
101
102    pub fn reset(&mut self) {
103        self.v = -1.0;
104        self.u = -0.1;
105    }
106}
107impl Default for IbarzTanakaMapNeuron {
108    fn default() -> Self {
109        Self::new()
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn ibarz_fires() {
119        let mut n = IbarzTanakaMapNeuron::new();
120        let t: i32 = (0..2000).map(|_| n.step(2.0)).sum();
121        assert!(t > 0);
122    }
123
124    #[test]
125    fn checked_batch_is_complete_and_failure_atomic() {
126        let mut neuron = IbarzTanakaMapNeuron::new();
127        let (trace, events) = neuron.simulate(1_000, 0.2).unwrap();
128        assert_eq!(trace.len(), 1_000);
129        assert_eq!(events, 33);
130        assert_eq!(trace.last().copied(), Some(neuron.v));
131
132        let before = (neuron.v, neuron.u);
133        assert!(neuron.simulate(4, f64::NAN).is_err());
134        assert_eq!((neuron.v, neuron.u), before);
135    }
136
137    #[test]
138    fn earlier_branch_precedence_prevents_false_reset_events() {
139        let mut neuron = IbarzTanakaMapNeuron::new();
140        assert!(neuron.v >= 1.0 - 5.0 + neuron.u);
141        assert_eq!(neuron.try_step(-5.0), Ok(0));
142        assert_ne!(neuron.v, -1.0);
143    }
144}