Skip to main content

sc_neurocore_engine/bindings/
iqif.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 — Integer QIF PyO3 binding
8
9//! Python binding for the Wu et al. (2021) integer QIF neuron.
10
11use numpy::{IntoPyArray, PyArray1};
12use pyo3::exceptions::PyValueError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::neurons;
17
18/// Register the integer QIF batch simulator with the extension module.
19pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
20    module.add_class::<PyIntegerQIFNeuron>()?;
21    module.add_function(wrap_pyfunction!(py_iqif_simulate, module)?)?;
22    Ok(())
23}
24
25#[pyclass(
26    name = "IntegerQIFNeuron",
27    module = "sc_neurocore_engine.sc_neurocore_engine"
28)]
29#[derive(Clone)]
30pub struct PyIntegerQIFNeuron {
31    inner: neurons::IntegerQIFNeuron,
32}
33
34#[pymethods]
35impl PyIntegerQIFNeuron {
36    #[new]
37    #[pyo3(signature = (v=128, v_rest=128, v_threshold=200, v_reset=128, a=1, b=1, v_max=255, v_min=0))]
38    #[allow(clippy::too_many_arguments)]
39    fn new(
40        v: i32,
41        v_rest: i32,
42        v_threshold: i32,
43        v_reset: i32,
44        a: i32,
45        b: i32,
46        v_max: i32,
47        v_min: i32,
48    ) -> PyResult<Self> {
49        Ok(Self {
50            inner: neurons::IntegerQIFNeuron::with_parameters(
51                v,
52                v_rest,
53                v_threshold,
54                v_reset,
55                a,
56                b,
57                v_max,
58                v_min,
59            )
60            .map_err(PyValueError::new_err)?,
61        })
62    }
63    fn step(&mut self, current: i32) -> PyResult<i32> {
64        self.inner.try_step(current).map_err(PyValueError::new_err)
65    }
66    fn reset(&mut self) {
67        self.inner.reset();
68    }
69    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
70        let d = PyDict::new(py);
71        d.set_item("v", self.inner.v)?;
72        d.set_item("v_rest", self.inner.v_rest)?;
73        d.set_item("v_threshold", self.inner.v_threshold)?;
74        d.set_item("v_reset", self.inner.v_reset)?;
75        d.set_item("a", self.inner.a)?;
76        d.set_item("b", self.inner.b)?;
77        d.set_item("v_max", self.inner.v_max)?;
78        d.set_item("v_min", self.inner.v_min)?;
79        Ok(d.into_any().unbind())
80    }
81}
82
83/// Full-contract Wu et al. (2021) IQIF integer batch.
84#[pyfunction]
85#[pyo3(signature = (v, v_rest, v_threshold, v_reset, a, b, v_max, v_min, n_steps, current))]
86#[allow(clippy::too_many_arguments, clippy::type_complexity)]
87fn py_iqif_simulate<'py>(
88    py: Python<'py>,
89    v: i32,
90    v_rest: i32,
91    v_threshold: i32,
92    v_reset: i32,
93    a: i32,
94    b: i32,
95    v_max: i32,
96    v_min: i32,
97    n_steps: usize,
98    current: i32,
99) -> PyResult<(Bound<'py, PyArray1<i64>>, i64, i64)> {
100    let mut neuron = crate::neurons::IntegerQIFNeuron::with_parameters(
101        v,
102        v_rest,
103        v_threshold,
104        v_reset,
105        a,
106        b,
107        v_max,
108        v_min,
109    )
110    .map_err(PyValueError::new_err)?;
111    let mut trace = Vec::with_capacity(n_steps);
112    let mut spikes = 0_i64;
113    for _ in 0..n_steps {
114        spikes += i64::from(neuron.try_step(current).map_err(PyValueError::new_err)?);
115        trace.push(neuron.v);
116    }
117    Ok((trace.into_pyarray(py), spikes, neuron.v))
118}