Skip to main content

sc_neurocore_engine/bindings/
mihalas_niebur.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 — Mihalas-Niebur PyO3 binding
8
9//! Python binding for the Mihalas-Niebur 2009 generalised integrate-and-fire model.
10
11use numpy::{IntoPyArray, PyArray1};
12use pyo3::prelude::*;
13use pyo3::types::PyDict;
14
15use crate::neurons;
16
17py_neuron_default!("MihalasNieburNeuron", PyMihalasNieburNeuron, neurons::MihalasNieburNeuron, state v, state theta, state i1, state i2);
18
19/// Register the Mihalas-Niebur class and batch simulator.
20pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
21    module.add_class::<PyMihalasNieburNeuron>()?;
22    module.add_function(wrap_pyfunction!(py_mihalas_niebur_simulate, module)?)?;
23    Ok(())
24}
25
26/// Parity contract with
27/// `sc_neurocore.neurons.models.mihalas_niebur.MihalasNieburNeuron.simulate`:
28/// for the same parameters and constant input the returned `v` trace, total
29/// spike count, and final `(v, theta, i1, i2)` state are bit-identical to the
30/// Python RK4 reference. The Mihalas-Niebur 2009 generalised integrate-and-fire
31/// model has a purely linear right-hand side (no transcendental functions), so
32/// every RK4 stage is exact arithmetic and the trace reproduces to the last bit
33/// across Rust/Julia/Go (Mojo FMA-fuses, validated non-amplifying).
34#[pyfunction]
35#[pyo3(signature = (
36    v0, theta0, i1_0, i2_0, v_rest, v_reset, theta_reset, theta_inf,
37    tau_v, tau_theta, tau_1, tau_2, a, b, r1, r2, dt, n_steps, current
38))]
39#[allow(clippy::too_many_arguments)]
40fn py_mihalas_niebur_simulate<'py>(
41    py: Python<'py>,
42    v0: f64,
43    theta0: f64,
44    i1_0: f64,
45    i2_0: f64,
46    v_rest: f64,
47    v_reset: f64,
48    theta_reset: f64,
49    theta_inf: f64,
50    tau_v: f64,
51    tau_theta: f64,
52    tau_1: f64,
53    tau_2: f64,
54    a: f64,
55    b: f64,
56    r1: f64,
57    r2: f64,
58    dt: f64,
59    n_steps: usize,
60    current: f64,
61) -> (Bound<'py, PyArray1<f64>>, i64, f64, f64, f64, f64) {
62    let mut neuron = crate::neurons::MihalasNieburNeuron {
63        v: v0,
64        theta: theta0,
65        i1: i1_0,
66        i2: i2_0,
67        v_rest,
68        v_reset,
69        theta_reset,
70        theta_inf,
71        tau_v,
72        tau_theta,
73        tau_1,
74        tau_2,
75        a,
76        b,
77        r1,
78        r2,
79        dt,
80    };
81    let (trace, spikes) = neuron.simulate(n_steps, current);
82    (
83        trace.into_pyarray(py),
84        spikes,
85        neuron.v,
86        neuron.theta,
87        neuron.i1,
88        neuron.i2,
89    )
90}