Skip to main content

sc_neurocore_engine/neurons/trivial/
sigma_delta.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
8//! Sampled Yoon asynchronous pulse sigma-delta encoder.
9
10const STATE_LIMIT: f64 = 1.0e12;
11
12/// Integrating prefilter and local reconstruction-feedback state.
13#[derive(Clone, Debug)]
14pub struct SigmaDeltaNeuron {
15    /// Integrating prefilter output.
16    pub sigma: f64,
17    /// Locally reconstructed signal.
18    pub reconstruction: f64,
19    /// Reconstruction quantum; upper threshold is half this value.
20    pub delta: f64,
21    /// Exponential reconstruction time constant.
22    pub tau_reconstruction: f64,
23    /// Discrete sample interval.
24    pub dt: f64,
25}
26
27impl SigmaDeltaNeuron {
28    /// Construct the documented source-equation specialization.
29    pub fn new() -> Self {
30        Self {
31            sigma: 0.0,
32            reconstruction: 0.0,
33            delta: 1.0,
34            tau_reconstruction: 10.0,
35            dt: 0.1,
36        }
37    }
38
39    /// Validate complete state and configuration.
40    pub fn validate(&self) -> bool {
41        [
42            self.sigma,
43            self.reconstruction,
44            self.delta,
45            self.tau_reconstruction,
46            self.dt,
47        ]
48        .iter()
49        .all(|value| value.is_finite())
50            && self.sigma.abs() <= STATE_LIMIT
51            && self.reconstruction.abs() <= STATE_LIMIT
52            && self.delta > 0.0
53            && self.tau_reconstruction > 0.0
54            && self.dt > 0.0
55    }
56
57    /// Advance one atomic sampled APSDM transition.
58    pub fn try_step(&mut self, current: f64) -> Result<i32, &'static str> {
59        if !current.is_finite() || !self.validate() {
60            return Err("invalid SigmaDelta state, configuration, or current");
61        }
62        let sigma = self.sigma + self.dt * current;
63        let mut reconstruction = self.reconstruction * (-self.dt / self.tau_reconstruction).exp();
64        let spike = sigma - reconstruction >= 0.5 * self.delta;
65        if spike {
66            reconstruction += self.delta;
67        }
68        if !sigma.is_finite()
69            || !reconstruction.is_finite()
70            || sigma.abs() > STATE_LIMIT
71            || reconstruction.abs() > STATE_LIMIT
72        {
73            return Err("SigmaDelta candidate outside safety envelope");
74        }
75        self.sigma = sigma;
76        self.reconstruction = reconstruction;
77        Ok(i32::from(spike))
78    }
79
80    /// Engine adapter: invalid state emits no event.
81    pub fn step(&mut self, current: f64) -> i32 {
82        self.try_step(current).unwrap_or(0)
83    }
84
85    /// Clear dynamic state while retaining configuration.
86    pub fn reset(&mut self) {
87        self.sigma = 0.0;
88        self.reconstruction = 0.0;
89    }
90}
91
92impl Default for SigmaDeltaNeuron {
93    fn default() -> Self {
94        Self::new()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    #[test]
102    fn source_step_and_atomic_failure() {
103        let mut neuron = SigmaDeltaNeuron {
104            sigma: 0.49,
105            ..SigmaDeltaNeuron::new()
106        };
107        assert_eq!(neuron.try_step(0.2), Ok(1));
108        assert_eq!(neuron.sigma, 0.51);
109        let before = (neuron.sigma, neuron.reconstruction);
110        assert!(neuron.try_step(f64::NAN).is_err());
111        assert_eq!((neuron.sigma, neuron.reconstruction), before);
112    }
113}