sc_neurocore_engine/bindings/
network_runner.rs1use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
12use pyo3::exceptions::PyValueError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::network_runner;
17
18pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
20 module.add_class::<PyNetworkRunner>()?;
21 module.add_function(wrap_pyfunction!(py_batch_simulate, module)?)?;
22 Ok(())
23}
24
25#[pyclass(
26 name = "NetworkRunner",
27 module = "sc_neurocore_engine.sc_neurocore_engine"
28)]
29pub struct PyNetworkRunner {
30 inner: network_runner::NetworkRunner,
31}
32
33#[pymethods]
34impl PyNetworkRunner {
35 #[new]
36 fn new() -> Self {
37 Self {
38 inner: network_runner::NetworkRunner::new(),
39 }
40 }
41
42 fn add_population(&mut self, model: &str, n: usize) -> PyResult<usize> {
43 let pop = network_runner::create_population(model, n).map_err(PyValueError::new_err)?;
44 Ok(self.inner.add_population(pop))
45 }
46
47 #[pyo3(signature = (src, tgt, row_offsets, col_indices, values, delay=0))]
48 fn add_projection(
49 &mut self,
50 src: usize,
51 tgt: usize,
52 row_offsets: Vec<i64>,
53 col_indices: Vec<i64>,
54 values: Vec<f64>,
55 delay: usize,
56 ) {
57 let ro: Vec<usize> = row_offsets.iter().map(|&x| x as usize).collect();
58 let ci: Vec<usize> = col_indices.iter().map(|&x| x as usize).collect();
59 let proj = network_runner::ProjectionRunner::new(src, tgt, ro, ci, values, delay);
60 self.inner.add_projection(proj);
61 }
62
63 fn step_population<'py>(
64 &mut self,
65 py: Python<'py>,
66 pop_index: usize,
67 currents: PyReadonlyArray1<'py, f64>,
68 ) -> PyResult<Py<PyAny>> {
69 let currents = currents.as_slice()?;
70 let (spikes, voltages) = self
71 .inner
72 .step_population_with_currents(pop_index, currents)
73 .map_err(PyValueError::new_err)?;
74 let dict = PyDict::new(py);
75 dict.set_item("spikes", spikes.into_pyarray(py))?;
76 dict.set_item("voltages", voltages.into_pyarray(py))?;
77 Ok(dict.into_any().unbind())
78 }
79
80 fn run<'py>(&mut self, py: Python<'py>, n_steps: usize) -> PyResult<Py<PyAny>> {
81 let results = self.inner.run(n_steps);
82 let dict = PyDict::new(py);
83 let spike_counts: Vec<u64> = results.spike_counts.iter().map(|&c| c as u64).collect();
84 dict.set_item("spike_counts", spike_counts.into_pyarray(py))?;
85 let spike_data: Vec<Py<PyArray1<u64>>> = results
86 .spike_data
87 .into_iter()
88 .map(|v: Vec<u64>| v.into_pyarray(py).unbind())
89 .collect();
90 dict.set_item("spike_data", spike_data)?;
91 let voltages: Vec<Py<PyArray1<f64>>> = results
92 .voltages
93 .into_iter()
94 .map(|v: Vec<f64>| v.into_pyarray(py).unbind())
95 .collect();
96 dict.set_item("voltages", voltages)?;
97 Ok(dict.into_any().unbind())
98 }
99
100 #[staticmethod]
101 fn supported_models() -> Vec<&'static str> {
102 network_runner::supported_models()
103 }
104}
105
106#[pyfunction]
109fn py_batch_simulate<'py>(
110 py: Python<'py>,
111 model_name: &str,
112 n_steps: usize,
113 current_trace: PyReadonlyArray1<'py, f64>,
114) -> PyResult<Py<PyAny>> {
115 let mut neuron = network_runner::create_neuron(model_name).map_err(PyValueError::new_err)?;
116 let currents = current_trace.as_slice()?;
117 let steps = n_steps.min(currents.len());
118
119 let mut voltages = vec![0.0f64; steps];
120 let mut spikes: Vec<u64> = Vec::new();
121
122 for t in 0..steps {
123 let fired = neuron.step(currents[t]);
124 voltages[t] = neuron.soma_voltage();
125 if fired != 0 {
126 spikes.push(t as u64);
127 }
128 }
129
130 let d = PyDict::new(py);
131 d.set_item("voltages", voltages.into_pyarray(py))?;
132 d.set_item("spikes", spikes.into_pyarray(py))?;
133 d.set_item("n_steps", steps)?;
134 Ok(d.into_any().unbind())
135}