Skip to main content

sc_neurocore_engine/bindings/
coba_lif.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 — conductance-based LIF PyO3 binding
8
9//! Python binding for the Brette et al. 2007 conductance-based LIF cell.
10
11use numpy::{IntoPyArray, PyArray1};
12use pyo3::exceptions::PyValueError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::neurons;
17
18/// Register the COBA-LIF simulator with the extension module.
19pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
20    module.add_class::<PyCOBALIFNeuron>()?;
21    module.add_function(wrap_pyfunction!(py_coba_lif_simulate, module)?)?;
22    Ok(())
23}
24
25// COBALIFNeuron: step(current, delta_ge, delta_gi)
26#[pyclass(
27    name = "COBALIFNeuron",
28    module = "sc_neurocore_engine.sc_neurocore_engine"
29)]
30#[derive(Clone)]
31pub struct PyCOBALIFNeuron {
32    inner: neurons::COBALIFNeuron,
33}
34
35#[pymethods]
36impl PyCOBALIFNeuron {
37    #[new]
38    #[pyo3(signature = (
39        v=-60.0, g_e=0.0, g_i=0.0, refractory_time=0.0, c_m=200.0,
40        g_l=10.0, e_l=-60.0, e_e=0.0, e_i=-80.0, tau_e=5.0,
41        tau_i=10.0, v_threshold=-50.0, v_reset=-60.0,
42        refractory_period=5.0, dt=0.1
43    ))]
44    #[allow(clippy::too_many_arguments)]
45    fn new(
46        v: f64,
47        g_e: f64,
48        g_i: f64,
49        refractory_time: f64,
50        c_m: f64,
51        g_l: f64,
52        e_l: f64,
53        e_e: f64,
54        e_i: f64,
55        tau_e: f64,
56        tau_i: f64,
57        v_threshold: f64,
58        v_reset: f64,
59        refractory_period: f64,
60        dt: f64,
61    ) -> PyResult<Self> {
62        let inner = neurons::COBALIFNeuron {
63            v,
64            g_e,
65            g_i,
66            refractory_time,
67            c_m,
68            g_l,
69            e_l,
70            e_e,
71            e_i,
72            tau_e,
73            tau_i,
74            v_threshold,
75            v_reset,
76            refractory_period,
77            dt,
78        };
79        inner.validate().map_err(PyValueError::new_err)?;
80        Ok(Self { inner })
81    }
82    #[pyo3(signature = (current, delta_ge=0.0, delta_gi=0.0))]
83    fn step(&mut self, current: f64, delta_ge: f64, delta_gi: f64) -> PyResult<i32> {
84        self.inner
85            .try_step(current, delta_ge, delta_gi)
86            .map_err(PyValueError::new_err)
87    }
88    fn reset(&mut self) {
89        self.inner.reset();
90    }
91    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
92        let d = PyDict::new(py);
93        d.set_item("v", self.inner.v)?;
94        d.set_item("g_e", self.inner.g_e)?;
95        d.set_item("g_i", self.inner.g_i)?;
96        d.set_item("refractory_time", self.inner.refractory_time)?;
97        Ok(d.into_any().unbind())
98    }
99}
100
101/// Full-contract parity surface for the Brette et al. 2007 COBA LIF cell.
102#[pyfunction]
103#[pyo3(signature = (
104    v0, g_e0, g_i0, refractory_time0, c_m, g_l, e_l, e_e, e_i,
105    tau_e, tau_i, v_threshold, v_reset, refractory_period, dt, n_steps,
106    current, delta_ge, delta_gi
107))]
108#[allow(clippy::too_many_arguments, clippy::type_complexity)]
109fn py_coba_lif_simulate<'py>(
110    py: Python<'py>,
111    v0: f64,
112    g_e0: f64,
113    g_i0: f64,
114    refractory_time0: f64,
115    c_m: f64,
116    g_l: f64,
117    e_l: f64,
118    e_e: f64,
119    e_i: f64,
120    tau_e: f64,
121    tau_i: f64,
122    v_threshold: f64,
123    v_reset: f64,
124    refractory_period: f64,
125    dt: f64,
126    n_steps: usize,
127    current: f64,
128    delta_ge: f64,
129    delta_gi: f64,
130) -> PyResult<(Bound<'py, PyArray1<f64>>, i64, f64, f64, f64, f64)> {
131    let mut neuron = crate::neurons::COBALIFNeuron {
132        v: v0,
133        g_e: g_e0,
134        g_i: g_i0,
135        refractory_time: refractory_time0,
136        c_m,
137        g_l,
138        e_l,
139        e_e,
140        e_i,
141        tau_e,
142        tau_i,
143        v_threshold,
144        v_reset,
145        refractory_period,
146        dt,
147    };
148    neuron.validate().map_err(PyValueError::new_err)?;
149    if !current.is_finite()
150        || !delta_ge.is_finite()
151        || delta_ge < 0.0
152        || !delta_gi.is_finite()
153        || delta_gi < 0.0
154    {
155        return Err(PyValueError::new_err("invalid COBA LIF simulation input"));
156    }
157    let mut trace = Vec::with_capacity(n_steps);
158    let mut spikes = 0_i64;
159    for _ in 0..n_steps {
160        spikes += i64::from(
161            neuron
162                .try_step(current, delta_ge, delta_gi)
163                .map_err(PyValueError::new_err)?,
164        );
165        trace.push(neuron.v);
166    }
167    Ok((
168        trace.into_pyarray(py),
169        spikes,
170        neuron.v,
171        neuron.g_e,
172        neuron.g_i,
173        neuron.refractory_time,
174    ))
175}