Skip to main content

sc_neurocore_engine/bindings/
mckean.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
8//! PyO3 exposure for the source-bound McKean Heaviside system.
9
10use crate::neurons::McKeanNeuron;
11use numpy::{IntoPyArray, PyReadonlyArray1};
12use pyo3::exceptions::PyValueError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16#[pyclass(
17    name = "McKeanNeuron",
18    module = "sc_neurocore_engine.sc_neurocore_engine"
19)]
20#[derive(Clone)]
21pub struct PyMcKeanNeuron {
22    inner: McKeanNeuron,
23}
24
25#[pymethods]
26impl PyMcKeanNeuron {
27    #[new]
28    #[pyo3(signature=(v=0.0,w=0.0,a=0.25,lambda_=1.0,mu=1.0,b=0.01,dt=0.1))]
29    fn new(v: f64, w: f64, a: f64, lambda_: f64, mu: f64, b: f64, dt: f64) -> PyResult<Self> {
30        let inner = McKeanNeuron {
31            v,
32            w,
33            a,
34            lambda: lambda_,
35            mu,
36            b,
37            dt,
38        };
39        if !inner.valid() {
40            return Err(PyValueError::new_err(
41                "invalid McKean state or configuration",
42            ));
43        }
44        Ok(Self { inner })
45    }
46    fn step(&mut self, current: f64) -> PyResult<i32> {
47        self.inner.try_step(current).map_err(PyValueError::new_err)
48    }
49    fn reset(&mut self) {
50        self.inner.reset();
51    }
52    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
53        let d = PyDict::new(py);
54        d.set_item("v", self.inner.v)?;
55        d.set_item("w", self.inner.w)?;
56        Ok(d.into_any().unbind())
57    }
58}
59
60#[pyfunction]
61#[pyo3(signature=(v,w,a,lambda_,mu,b,dt,currents))]
62#[allow(clippy::too_many_arguments)]
63fn py_mckean_simulate<'py>(
64    py: Python<'py>,
65    v: f64,
66    w: f64,
67    a: f64,
68    lambda_: f64,
69    mu: f64,
70    b: f64,
71    dt: f64,
72    currents: PyReadonlyArray1<'py, f64>,
73) -> PyResult<Py<PyAny>> {
74    let mut n = McKeanNeuron {
75        v,
76        w,
77        a,
78        lambda: lambda_,
79        mu,
80        b,
81        dt,
82    };
83    if !n.valid() {
84        return Err(PyValueError::new_err(
85            "invalid McKean state or configuration",
86        ));
87    }
88    let mut voltages = Vec::with_capacity(currents.len()?);
89    let mut recovery = Vec::with_capacity(currents.len()?);
90    let mut events = Vec::with_capacity(currents.len()?);
91    for &current in currents.as_slice()? {
92        events.push(n.try_step(current).map_err(PyValueError::new_err)?);
93        voltages.push(n.v);
94        recovery.push(n.w);
95    }
96    let d = PyDict::new(py);
97    d.set_item("voltages", voltages.into_pyarray(py))?;
98    d.set_item("recovery", recovery.into_pyarray(py))?;
99    d.set_item("events", events.into_pyarray(py))?;
100    d.set_item("v_final", n.v)?;
101    d.set_item("w_final", n.w)?;
102    Ok(d.into_any().unbind())
103}
104
105pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
106    module.add_class::<PyMcKeanNeuron>()?;
107    module.add_function(wrap_pyfunction!(py_mckean_simulate, module)?)?;
108    Ok(())
109}