Skip to main content

sc_neurocore_engine/neurons/multi_compartment/
dendrify.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 — Dendrify neuron model
8
9//! Dendrify multi-compartment neuron model.
10
11/// Dendrify — two-compartment with active dendritic spike (NMDA-like plateau).
12#[derive(Clone, Debug)]
13pub struct DendrifyNeuron {
14    pub v_s: f64,
15    pub v_d: f64,
16    pub d_active: bool,
17    pub d_timer: f64,
18    pub tau_s: f64,
19    pub tau_d: f64,
20    pub g_c: f64,
21    pub d_threshold: f64,
22    pub d_amplitude: f64,
23    pub d_duration: f64,
24    pub v_rest: f64,
25    pub v_threshold: f64,
26    pub v_reset: f64,
27    pub dt: f64,
28}
29
30impl DendrifyNeuron {
31    pub fn new() -> Self {
32        Self {
33            v_s: -65.0,
34            v_d: -65.0,
35            d_active: false,
36            d_timer: 0.0,
37            tau_s: 10.0,
38            tau_d: 20.0,
39            g_c: 0.8,
40            d_threshold: -35.0,
41            d_amplitude: 30.0,
42            d_duration: 10.0,
43            v_rest: -65.0,
44            v_threshold: -50.0,
45            v_reset: -65.0,
46            dt: 0.1,
47        }
48    }
49    pub fn step(&mut self, current: f64) -> i32 {
50        let d_input = if self.d_active { self.d_amplitude } else { 0.0 };
51        self.v_d += (-(self.v_d - self.v_rest) + current + d_input
52            - self.g_c * (self.v_d - self.v_s))
53            / self.tau_d
54            * self.dt;
55        self.v_s +=
56            (-(self.v_s - self.v_rest) + self.g_c * (self.v_d - self.v_s)) / self.tau_s * self.dt;
57        if self.d_active {
58            self.d_timer -= self.dt;
59            if self.d_timer <= 0.0 {
60                self.d_active = false;
61            }
62        } else if self.v_d >= self.d_threshold {
63            self.d_active = true;
64            self.d_timer = self.d_duration;
65        }
66        if self.v_s >= self.v_threshold {
67            self.v_s = self.v_reset;
68            1
69        } else {
70            0
71        }
72    }
73    pub fn reset(&mut self) {
74        self.v_s = -65.0;
75        self.v_d = -65.0;
76        self.d_active = false;
77        self.d_timer = 0.0;
78    }
79}
80impl Default for DendrifyNeuron {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn dendrify_fires() {
92        let mut n = DendrifyNeuron::new();
93        let t: i32 = (0..2000).map(|_| n.step(50.0)).sum();
94        assert!(t > 0);
95    }
96
97    #[test]
98    fn dendrify_reset() {
99        let mut n = DendrifyNeuron::new();
100        for _ in 0..100 {
101            n.step(50.0);
102        }
103        n.reset();
104        assert!((n.v_s - (-65.0)).abs() < 1e-10);
105    }
106
107    #[test]
108    fn dendrify_bounded() {
109        let mut n = DendrifyNeuron::new();
110        for _ in 0..2000 {
111            n.step(200.0);
112        }
113        assert!(n.v_s.is_finite());
114    }
115
116    #[test]
117    fn dendrify_nan_no_panic() {
118        DendrifyNeuron::new().step(f64::NAN);
119    }
120}