Skip to main content

sc_neurocore_engine/bindings/
hindmarsh_rose.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 — Hindmarsh-Rose PyO3 binding
8
9//! Python binding for the Hindmarsh-Rose three-state bursting model.
10
11use numpy::{IntoPyArray, PyArray1};
12use pyo3::exceptions::PyFloatingPointError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::neurons::HindmarshRoseNeuron;
17
18type PyHindmarshRoseBatch<'py> = (Bound<'py, PyArray1<f64>>, i64, f64, f64, f64);
19
20py_neuron_default!("HindmarshRoseNeuron", PyHindmarshRoseNeuron, HindmarshRoseNeuron, state x, state y, state z);
21
22/// Register the Hindmarsh-Rose class and batch simulator.
23pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
24    module.add_class::<PyHindmarshRoseNeuron>()?;
25    module.add_function(wrap_pyfunction!(py_hindmarsh_rose_simulate, module)?)?;
26    Ok(())
27}
28
29/// Parity contract with
30/// `sc_neurocore.neurons.models.hindmarsh_rose.HindmarshRoseNeuron.simulate`:
31/// for the same parameters and constant input the returned `x` trace, upward-
32/// crossing spike count, and final `(x, y, z)` state are bit-identical to the
33/// Python RK4 reference (the right-hand side is exact arithmetic — `x.powi(3)`
34/// = `x*x*x`, `x.powi(2)` = `x*x`, no transcendental functions — so even the
35/// chaotic bursting trace reproduces exactly).
36#[pyfunction]
37#[pyo3(signature = (x0, y0, z0, b, r, s, x_rest, dt, x_threshold, n_steps, current))]
38#[allow(clippy::too_many_arguments)]
39fn py_hindmarsh_rose_simulate<'py>(
40    py: Python<'py>,
41    x0: f64,
42    y0: f64,
43    z0: f64,
44    b: f64,
45    r: f64,
46    s: f64,
47    x_rest: f64,
48    dt: f64,
49    x_threshold: f64,
50    n_steps: usize,
51    current: f64,
52) -> PyResult<PyHindmarshRoseBatch<'py>> {
53    let mut neuron = HindmarshRoseNeuron {
54        x: x0,
55        y: y0,
56        z: z0,
57        b,
58        r,
59        s,
60        x_rest,
61        dt,
62        x_threshold,
63    };
64    let Some((trace, spikes)) = neuron.try_simulate(n_steps, current) else {
65        return Err(PyFloatingPointError::new_err(
66            "Hindmarsh-Rose Rust batch rejected an invalid candidate",
67        ));
68    };
69    Ok((trace.into_pyarray(py), spikes, neuron.x, neuron.y, neuron.z))
70}