sc_neurocore_engine/bindings/
jansen_rit.rs1use numpy::{IntoPyArray, PyReadonlyArray1};
12use pyo3::exceptions::PyValueError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::neurons::JansenRitUnit;
17
18pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
20 module.add_class::<PyJansenRitUnit>()?;
21 module.add_function(wrap_pyfunction!(py_jansen_rit_simulate, module)?)?;
22 Ok(())
23}
24
25#[pyclass(
26 name = "JansenRitUnit",
27 module = "sc_neurocore_engine.sc_neurocore_engine"
28)]
29#[derive(Clone)]
30pub struct PyJansenRitUnit {
31 inner: JansenRitUnit,
32}
33
34#[pymethods]
35impl PyJansenRitUnit {
36 #[new]
37 #[pyo3(signature = (
38 y0=0.0, y3=0.0, y1=0.0, y4=0.0, y2=0.0, y5=0.0,
39 a_exc=3.25, b_exc=22.0, a_rate=100.0, b_rate=50.0,
40 c=135.0, e0=2.5, v0=6.0, r=0.56, dt=0.0001,
41 ))]
42 #[allow(clippy::too_many_arguments)]
43 fn new(
44 y0: f64,
45 y3: f64,
46 y1: f64,
47 y4: f64,
48 y2: f64,
49 y5: f64,
50 a_exc: f64,
51 b_exc: f64,
52 a_rate: f64,
53 b_rate: f64,
54 c: f64,
55 e0: f64,
56 v0: f64,
57 r: f64,
58 dt: f64,
59 ) -> PyResult<Self> {
60 let inner = JansenRitUnit::with_parameters(
61 y0, y3, y1, y4, y2, y5, a_exc, b_exc, a_rate, b_rate, c, e0, v0, r, dt,
62 )
63 .map_err(PyValueError::new_err)?;
64 Ok(Self { inner })
65 }
66
67 #[pyo3(signature = (p_ext=220.0))]
68 fn step(&mut self, p_ext: f64) -> PyResult<f64> {
69 self.inner.step(p_ext).map_err(PyValueError::new_err)
70 }
71
72 fn reset(&mut self) {
73 self.inner.reset();
74 }
75
76 fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
77 let mapping = PyDict::new(py);
78 mapping.set_item("y0", self.inner.y[0])?;
79 mapping.set_item("y3", self.inner.y[3])?;
80 mapping.set_item("y1", self.inner.y[1])?;
81 mapping.set_item("y4", self.inner.y[4])?;
82 mapping.set_item("y2", self.inner.y[2])?;
83 mapping.set_item("y5", self.inner.y[5])?;
84 mapping.set_item("y", self.inner.y.to_vec())?;
85 Ok(mapping.into_any().unbind())
86 }
87}
88
89#[pyfunction]
91#[pyo3(signature = (
92 y0_init, y3_init, y1_init, y4_init, y2_init, y5_init,
93 a_exc, b_exc, a_rate, b_rate, c, e0, v0, r, dt, p_ext,
94))]
95#[allow(clippy::too_many_arguments)]
96fn py_jansen_rit_simulate<'py>(
97 py: Python<'py>,
98 y0_init: f64,
99 y3_init: f64,
100 y1_init: f64,
101 y4_init: f64,
102 y2_init: f64,
103 y5_init: f64,
104 a_exc: f64,
105 b_exc: f64,
106 a_rate: f64,
107 b_rate: f64,
108 c: f64,
109 e0: f64,
110 v0: f64,
111 r: f64,
112 dt: f64,
113 p_ext: PyReadonlyArray1<'py, f64>,
114) -> PyResult<Py<PyAny>> {
115 let result = crate::neurons::jansen_rit::simulate(
116 y0_init,
117 y3_init,
118 y1_init,
119 y4_init,
120 y2_init,
121 y5_init,
122 a_exc,
123 b_exc,
124 a_rate,
125 b_rate,
126 c,
127 e0,
128 v0,
129 r,
130 dt,
131 p_ext.as_slice()?,
132 )
133 .map_err(PyValueError::new_err)?;
134 let mapping = PyDict::new(py);
135 mapping.set_item("y0", result.y0.into_pyarray(py))?;
136 mapping.set_item("y3", result.y3.into_pyarray(py))?;
137 mapping.set_item("y1", result.y1.into_pyarray(py))?;
138 mapping.set_item("y4", result.y4.into_pyarray(py))?;
139 mapping.set_item("y2", result.y2.into_pyarray(py))?;
140 mapping.set_item("y5", result.y5.into_pyarray(py))?;
141 mapping.set_item("eeg", result.eeg.into_pyarray(py))?;
142 mapping.set_item("y0_final", result.final_state[0])?;
143 mapping.set_item("y3_final", result.final_state[3])?;
144 mapping.set_item("y1_final", result.final_state[1])?;
145 mapping.set_item("y4_final", result.final_state[4])?;
146 mapping.set_item("y2_final", result.final_state[2])?;
147 mapping.set_item("y5_final", result.final_state[5])?;
148 Ok(mapping.into_any().unbind())
149}
150
151#[cfg(test)]
152mod tests {
153 #[test]
154 fn engine_batch_rejects_invalid_drive_without_partial_result() {
155 let result = crate::neurons::jansen_rit::simulate(
156 0.0,
157 0.0,
158 0.0,
159 0.0,
160 0.0,
161 0.0,
162 3.25,
163 22.0,
164 100.0,
165 50.0,
166 135.0,
167 2.5,
168 6.0,
169 0.56,
170 0.0001,
171 &[120.0, f64::NAN],
172 );
173 assert!(result.is_err());
174 }
175}