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 — source-faithful Mihalas-Niebur PyO3 binding
8
9//! Python class and failure-signalling batch surface for Mihalaş-Niebur dynamics.
10
11use numpy::{IntoPyArray, PyArray1};
12use pyo3::exceptions::PyFloatingPointError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::neurons::MihalasNieburNeuron;
17
18type MihalasNieburBatchOutput<'py> = (Bound<'py, PyArray1<f64>>, i64, f64, f64, f64, f64);
19
20py_neuron_default!(
21    "MihalasNieburNeuron",
22    PyMihalasNieburNeuron,
23    MihalasNieburNeuron,
24    state v,
25    state theta,
26    state i1,
27    state i2
28);
29
30/// Register the Mihalas-Niebur class and batch simulator.
31pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
32    module.add_class::<PyMihalasNieburNeuron>()?;
33    module.add_function(wrap_pyfunction!(py_mihalas_niebur_simulate, module)?)?;
34    Ok(())
35}
36
37/// Execute equations 2.1–2.2 with fixed-grid RK4 and the published reset map.
38#[pyfunction]
39#[pyo3(signature = (
40    v0, theta0, i1_0, i2_0, v_rest, v_reset, theta_reset, theta_inf,
41    leak_rate, threshold_voltage_coupling, threshold_decay_rate,
42    current_decay_rate_1, current_decay_rate_2, current_retention_1,
43    current_retention_2, current_jump_1, current_jump_2, dt, n_steps, current
44))]
45#[allow(clippy::too_many_arguments)]
46fn py_mihalas_niebur_simulate<'py>(
47    py: Python<'py>,
48    v0: f64,
49    theta0: f64,
50    i1_0: f64,
51    i2_0: f64,
52    v_rest: f64,
53    v_reset: f64,
54    theta_reset: f64,
55    theta_inf: f64,
56    leak_rate: f64,
57    threshold_voltage_coupling: f64,
58    threshold_decay_rate: f64,
59    current_decay_rate_1: f64,
60    current_decay_rate_2: f64,
61    current_retention_1: f64,
62    current_retention_2: f64,
63    current_jump_1: f64,
64    current_jump_2: f64,
65    dt: f64,
66    n_steps: usize,
67    current: f64,
68) -> PyResult<MihalasNieburBatchOutput<'py>> {
69    let mut neuron = MihalasNieburNeuron {
70        v: v0,
71        theta: theta0,
72        i1: i1_0,
73        i2: i2_0,
74        v_rest,
75        v_reset,
76        theta_reset,
77        theta_inf,
78        leak_rate,
79        threshold_voltage_coupling,
80        threshold_decay_rate,
81        current_decay_rate_1,
82        current_decay_rate_2,
83        current_retention_1,
84        current_retention_2,
85        current_jump_1,
86        current_jump_2,
87        dt,
88    };
89    let Some((trace, events)) = neuron.try_simulate(n_steps, current) else {
90        return Err(PyFloatingPointError::new_err(
91            "Mihalas-Niebur Rust batch rejected an invalid candidate",
92        ));
93    };
94    Ok((
95        trace.into_pyarray(py),
96        events,
97        neuron.v,
98        neuron.theta,
99        neuron.i1,
100        neuron.i2,
101    ))
102}