Skip to main content

sc_neurocore_engine/neurons/rate/
astrocyte_model.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 — Li-Rinzel astrocyte model
8
9/// Li-Rinzel IP3R astrocyte model — Ca²⁺ dynamics.
10#[derive(Clone, Debug)]
11pub struct AstrocyteModel {
12    pub ca: f64,
13    pub h: f64,
14    pub ip3: f64,
15    pub v_er: f64,
16    pub k_er: f64,
17    pub v_serca: f64,
18    pub d1: f64,
19    pub d2: f64,
20    pub d3: f64,
21    pub d5: f64,
22    pub c0: f64,
23    pub c1: f64,
24    pub dt: f64,
25}
26
27impl AstrocyteModel {
28    pub fn new() -> Self {
29        Self {
30            ca: 0.05,
31            h: 0.8,
32            ip3: 0.5,
33            v_er: 0.9,
34            k_er: 0.15,
35            v_serca: 0.4,
36            d1: 0.13,
37            d2: 1.049,
38            d3: 0.9434,
39            d5: 0.08234,
40            c0: 2.0,
41            c1: 0.185,
42            dt: 0.01,
43        }
44    }
45    pub fn step(&mut self, current: f64) -> f64 {
46        let ca_er = (self.c0 - self.ca) / self.c1;
47        let m_inf = self.ip3 / (self.ip3 + self.d1);
48        let n_inf = self.ca / (self.ca + self.d5);
49        let j_chan = self.v_er * (m_inf * n_inf * self.h).powi(3) * (ca_er - self.ca);
50        let j_leak = self.k_er * (ca_er - self.ca);
51        let j_pump = self.v_serca * self.ca.powi(2) / (self.ca.powi(2) + self.k_er.powi(2));
52        let q2 = self.d2 * (self.ip3 + self.d1) / (self.ip3 + self.d3);
53        let h_inf = q2 / (q2 + self.ca);
54        let tau_h = 1.0 / (0.2 * (q2 + self.ca));
55        self.ca += (j_chan + j_leak - j_pump + current) * self.dt;
56        self.ca = self.ca.max(0.0);
57        self.h += (h_inf - self.h) / tau_h * self.dt;
58        self.ca
59    }
60    pub fn reset(&mut self) {
61        self.ca = 0.05;
62        self.h = 0.8;
63        self.ip3 = 0.5;
64    }
65}
66impl Default for AstrocyteModel {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn astrocyte_ca() {
78        let mut n = AstrocyteModel::new();
79        let mut max_ca = 0.0_f64;
80        for _ in 0..5000 {
81            let c = n.step(0.1);
82            max_ca = max_ca.max(c);
83        }
84        assert!(max_ca > 0.05);
85    }
86
87    #[test]
88    fn astrocyte_reset() {
89        let mut n = AstrocyteModel::new();
90        for _ in 0..1000 {
91            n.step(0.1);
92        }
93        n.reset();
94        assert!((n.ca - 0.05).abs() < 1e-10);
95    }
96
97    #[test]
98    fn astrocyte_nan_no_panic() {
99        AstrocyteModel::new().step(f64::NAN);
100    }
101
102    #[test]
103    fn astrocyte_ca_nonneg() {
104        let mut n = AstrocyteModel::new();
105        for _ in 0..5000 {
106            n.step(0.1);
107        }
108        assert!(n.ca >= 0.0, "Ca²⁺ must be non-negative");
109    }
110}