Skip to main content

sc_neurocore_engine/bindings/population/
brunel_wang.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 — source-complete Brunel-Wang PyO3 binding
8
9use pyo3::exceptions::PyValueError;
10use pyo3::prelude::*;
11
12use crate::neurons;
13
14/// Python boundary for the configurable Brunel-Wang pyramidal cell.
15#[pyclass(
16    name = "BrunelWangNeuron",
17    module = "sc_neurocore_engine.sc_neurocore_engine"
18)]
19#[derive(Clone)]
20pub struct PyBrunelWangNeuron {
21    inner: neurons::BrunelWangNeuron,
22}
23
24#[pymethods]
25impl PyBrunelWangNeuron {
26    #[new]
27    #[pyo3(signature = (v=-70.0, v_rest=-70.0, v_reset=-55.0, v_threshold=-50.0, tau_m=20.0, tau_ref=2.0, g_ampa_ext=2.08, g_ampa_rec=0.104, g_nmda=0.327, g_gaba=1.25, v_ampa=0.0, v_nmda=0.0, v_gaba=-70.0, c_m=0.5, mg_conc=1.0, dt=0.1, ref_remaining=0.0))]
28    #[allow(clippy::too_many_arguments)]
29    fn new(
30        v: f64,
31        v_rest: f64,
32        v_reset: f64,
33        v_threshold: f64,
34        tau_m: f64,
35        tau_ref: f64,
36        g_ampa_ext: f64,
37        g_ampa_rec: f64,
38        g_nmda: f64,
39        g_gaba: f64,
40        v_ampa: f64,
41        v_nmda: f64,
42        v_gaba: f64,
43        c_m: f64,
44        mg_conc: f64,
45        dt: f64,
46        ref_remaining: f64,
47    ) -> PyResult<Self> {
48        let mut inner = neurons::BrunelWangNeuron::new();
49        inner.v = v;
50        inner.v_rest = v_rest;
51        inner.v_reset = v_reset;
52        inner.v_threshold = v_threshold;
53        inner.tau_m = tau_m;
54        inner.tau_ref = tau_ref;
55        inner.g_ampa_ext = g_ampa_ext;
56        inner.g_ampa_rec = g_ampa_rec;
57        inner.g_nmda = g_nmda;
58        inner.g_gaba = g_gaba;
59        inner.v_ampa = v_ampa;
60        inner.v_nmda = v_nmda;
61        inner.v_gaba = v_gaba;
62        inner.c_m = c_m;
63        inner.mg_conc = mg_conc;
64        inner.dt = dt;
65        inner.ref_remaining = ref_remaining;
66        inner
67            .try_step_full(0.0, 0.0, 0.0, 0.0)
68            .map_err(PyValueError::new_err)?;
69        inner.v = v;
70        inner.ref_remaining = ref_remaining;
71        Ok(Self { inner })
72    }
73
74    /// Advance one atomic midpoint-RK2 step over four aggregate gates.
75    #[pyo3(signature = (i_ampa_ext=0.0, s_ampa_rec=0.0, s_nmda_rec=0.0, s_gaba=0.0))]
76    fn step(
77        &mut self,
78        i_ampa_ext: f64,
79        s_ampa_rec: f64,
80        s_nmda_rec: f64,
81        s_gaba: f64,
82    ) -> PyResult<i32> {
83        self.inner
84            .try_step_full(i_ampa_ext, s_ampa_rec, s_nmda_rec, s_gaba)
85            .map_err(PyValueError::new_err)
86    }
87
88    /// Reset membrane and refractory state while preserving configuration.
89    fn reset(&mut self) {
90        self.inner.reset();
91    }
92
93    /// Return `(voltage, refractory_remaining_ms)`.
94    fn get_state(&self) -> (f64, f64) {
95        self.inner.state()
96    }
97}
98
99pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
100    module.add_class::<PyBrunelWangNeuron>()?;
101    Ok(())
102}