Skip to main content

sc_neurocore_engine/bindings/stochastic/
benda_herz.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 — source-faithful Benda-Herz PyO3 binding
8
9use numpy::{IntoPyArray, PyReadonlyArray1};
10use pyo3::exceptions::PyValueError;
11use pyo3::prelude::*;
12use pyo3::types::PyDict;
13
14use crate::neurons;
15
16#[pyclass(
17    name = "BendaHerzNeuron",
18    module = "sc_neurocore_engine.sc_neurocore_engine"
19)]
20#[derive(Clone)]
21pub struct PyBendaHerzNeuron {
22    inner: neurons::BendaHerzNeuron,
23}
24
25#[pymethods]
26impl PyBendaHerzNeuron {
27    #[new]
28    fn new() -> Self {
29        Self {
30            inner: neurons::BendaHerzNeuron::new(),
31        }
32    }
33
34    fn step(&mut self, current: f64) -> i32 {
35        self.inner.step(current)
36    }
37
38    fn reset(&mut self) {
39        self.inner.reset();
40    }
41
42    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
43        let d = PyDict::new(py);
44        d.set_item("a", self.inner.a)?;
45        d.set_item("phase", self.inner.phase)?;
46        Ok(d.into_any().unbind())
47    }
48}
49
50pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
51    module.add_class::<PyBendaHerzNeuron>()?;
52    module.add_function(wrap_pyfunction!(py_benda_herz_simulate, module)?)?;
53    Ok(())
54}
55
56#[pyfunction]
57#[pyo3(signature=(a,phase,onset_gain,rheobase,adaptation_slope,tau_a,dt,currents))]
58#[allow(clippy::too_many_arguments)]
59fn py_benda_herz_simulate<'py>(
60    py: Python<'py>,
61    a: f64,
62    phase: f64,
63    onset_gain: f64,
64    rheobase: f64,
65    adaptation_slope: f64,
66    tau_a: f64,
67    dt: f64,
68    currents: PyReadonlyArray1<'py, f64>,
69) -> PyResult<Py<PyAny>> {
70    let mut neuron = neurons::BendaHerzNeuron {
71        a,
72        phase,
73        onset_gain,
74        rheobase,
75        adaptation_slope,
76        tau_a,
77        dt,
78    };
79    if !neuron.valid() {
80        return Err(PyValueError::new_err("invalid Benda-Herz configuration"));
81    }
82    let mut adaptation = Vec::with_capacity(currents.len()?);
83    let mut phases = Vec::with_capacity(currents.len()?);
84    let mut events = Vec::with_capacity(currents.len()?);
85    for &current in currents.as_slice()? {
86        let event = neuron.step(current);
87        if event < 0 {
88            return Err(PyValueError::new_err("invalid Benda-Herz transition"));
89        }
90        adaptation.push(neuron.a);
91        phases.push(neuron.phase);
92        events.push(event);
93    }
94    let d = PyDict::new(py);
95    d.set_item("adaptation", adaptation.into_pyarray(py))?;
96    d.set_item("phases", phases.into_pyarray(py))?;
97    d.set_item("events", events.into_pyarray(py))?;
98    d.set_item("a_final", neuron.a)?;
99    d.set_item("phase_final", neuron.phase)?;
100    Ok(d.into_any().unbind())
101}