Skip to main content

sc_neurocore_engine/neurons/biophysical/
destexhe_thalamic.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 — Destexhe Thalamic Neuron Model
8
9//! Destexhe thalamocortical relay-neuron dynamics.
10
11/// Destexhe thalamocortical relay neuron with T-current. Destexhe et al. 1993.
12#[derive(Clone, Debug)]
13pub struct DestexheThalamicNeuron {
14    pub v: f64,
15    pub h_na: f64,
16    pub n_k: f64,
17    pub m_t: f64,
18    pub h_t: f64,
19    pub g_na: f64,
20    pub g_k: f64,
21    pub g_t: f64,
22    pub g_l: f64,
23    pub e_na: f64,
24    pub e_k: f64,
25    pub e_ca: f64,
26    pub e_l: f64,
27    pub dt: f64,
28    pub v_threshold: f64,
29}
30
31impl DestexheThalamicNeuron {
32    pub fn new() -> Self {
33        Self {
34            v: -65.0,
35            h_na: 0.6,
36            n_k: 0.3,
37            m_t: 0.0,
38            h_t: 1.0,
39            g_na: 100.0,
40            g_k: 10.0,
41            g_t: 2.0,
42            g_l: 0.05,
43            e_na: 50.0,
44            e_k: -90.0,
45            e_ca: 120.0,
46            e_l: -70.0,
47            dt: 0.02,
48            v_threshold: -20.0,
49        }
50    }
51    pub fn step(&mut self, current: f64) -> i32 {
52        let v_prev = self.v;
53        for _ in 0..5 {
54            let m_na = 1.0 / (1.0 + (-(self.v + 37.0) / 7.0).exp());
55            let h_na_inf = 1.0 / (1.0 + ((self.v + 41.0) / 4.0).exp());
56            let n_inf = 1.0 / (1.0 + (-(self.v + 25.0) / 12.0).exp());
57            let m_t_inf = 1.0 / (1.0 + (-(self.v + 57.0) / 6.5).exp());
58            let h_t_inf = 1.0 / (1.0 + ((self.v + 81.0) / 4.0).exp());
59            // Voltage-dependent time constants (Destexhe 1993)
60            let tau_h_na = (1.0
61                / (0.128 * (-(self.v + 46.0) / 18.0).exp()
62                    + 4.0 / (1.0 + (-(self.v + 23.0) / 5.0).exp())))
63            .max(0.1);
64            let tau_n_k = (1.0 / (0.032 * 5.0 + 0.5 * (-(self.v + 40.0) / 40.0).exp())).max(0.1);
65            let tau_h_t = if self.v < -81.0 {
66                (30.8
67                    + 211.4 * ((self.v + 115.2) / 5.0).exp()
68                        / (1.0 + ((self.v + 86.0) / 3.2).exp()))
69                .max(0.1)
70            } else {
71                10.0
72            };
73            self.h_na += (h_na_inf - self.h_na) / tau_h_na * self.dt;
74            self.n_k += (n_inf - self.n_k) / tau_n_k * self.dt;
75            self.m_t = m_t_inf; // instantaneous (no ODE)
76            self.h_t += (h_t_inf - self.h_t) / tau_h_t * self.dt;
77            let i_na = self.g_na * m_na.powi(3) * self.h_na * (self.v - self.e_na);
78            let i_k = self.g_k * self.n_k.powi(4) * (self.v - self.e_k);
79            let i_t = self.g_t * self.m_t.powi(2) * self.h_t * (self.v - self.e_ca);
80            let i_l = self.g_l * (self.v - self.e_l);
81            self.v += (-i_na - i_k - i_t - i_l + current) * self.dt;
82        }
83        if self.v >= self.v_threshold && v_prev < self.v_threshold {
84            1
85        } else {
86            0
87        }
88    }
89    pub fn reset(&mut self) {
90        self.v = -65.0;
91        self.h_na = 0.6;
92        self.n_k = 0.3;
93        self.m_t = 0.0;
94        self.h_t = 1.0;
95    }
96}
97impl Default for DestexheThalamicNeuron {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn default_matches_constructor_state() {
109        let default = DestexheThalamicNeuron::default();
110        let constructed = DestexheThalamicNeuron::new();
111        assert_eq!(default.v, constructed.v);
112    }
113
114    #[test]
115    fn destexhe_fires() {
116        let mut n = DestexheThalamicNeuron::new();
117        let t: i32 = (0..500).map(|_| n.step(5.0)).sum();
118        assert!(t > 0);
119    }
120
121    // -- Destexhe Thalamic --
122    #[test]
123    fn destexhe_no_crash_zero_input() {
124        let mut n = DestexheThalamicNeuron::new();
125        let _t: i32 = (0..500).map(|_| n.step(0.0)).sum();
126        // Thalamic relays may have spontaneous activity via T-current
127        assert!(n.v.is_finite());
128    }
129    #[test]
130    fn destexhe_reset_clears_state() {
131        let mut n = DestexheThalamicNeuron::new();
132        for _ in 0..200 {
133            n.step(5.0);
134        }
135        n.reset();
136        assert!((n.v - (-65.0)).abs() < 1e-10);
137    }
138    #[test]
139    fn destexhe_extreme_bounded() {
140        let mut n = DestexheThalamicNeuron::new();
141        for _ in 0..200 {
142            n.step(1e4);
143        }
144        assert!(n.v.is_finite());
145    }
146    #[test]
147    fn destexhe_t_current_rebound() {
148        // T-type Ca²⁺ deinactivation: h_t_inf is high at hyperpolarised V
149        // but voltage-dependent tau_h_t is very large (~37 s at V=-85),
150        // so actual h_t recovery is slow — verify steady-state property instead.
151        let v_hyp = -90.0_f64;
152        let h_t_inf = 1.0 / (1.0 + ((v_hyp + 81.0) / 4.0).exp());
153        assert!(h_t_inf > 0.9, "h_t_inf should be ~1 at V=-90: {}", h_t_inf);
154
155        // Verify model stability through hyperpolarise-release cycle
156        let mut n = DestexheThalamicNeuron::new();
157        for _ in 0..500 {
158            n.step(-5.0);
159        }
160        let v_before_release = n.v;
161        for _ in 0..500 {
162            n.step(0.0);
163        }
164        assert!(n.v.is_finite());
165        assert!(
166            n.v > v_before_release,
167            "V should increase after release (rebound)"
168        );
169    }
170    #[test]
171    fn destexhe_negative_no_crash() {
172        let mut n = DestexheThalamicNeuron::new();
173        for _ in 0..200 {
174            n.step(-20.0);
175        }
176        assert!(n.v.is_finite());
177    }
178    #[test]
179    fn destexhe_nan_no_panic() {
180        let mut n = DestexheThalamicNeuron::new();
181        n.step(f64::NAN);
182    }
183}