sc_neurocore_engine/bindings/
coba_lif.rs1use numpy::{IntoPyArray, PyArray1};
12use pyo3::exceptions::PyValueError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::neurons;
17
18pub(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#[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#[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}