Skip to main content

sc_neurocore_engine/bindings/
ermentrout_kopell_map.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 — Ermentrout-Kopell map PyO3 binding
8
9//! Python binding for the Ermentrout-Kopell Type-I theta map.
10
11use numpy::{IntoPyArray, PyArray1};
12use pyo3::exceptions::PyFloatingPointError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::neurons::ErmentroutKopellMapNeuron;
17
18py_neuron_default!("ErmentroutKopellMapNeuron", PyErmentroutKopellMapNeuron, ErmentroutKopellMapNeuron, state theta);
19
20/// Register the Ermentrout-Kopell map class and simulator.
21pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
22    module.add_class::<PyErmentroutKopellMapNeuron>()?;
23    module.add_function(wrap_pyfunction!(py_ermentrout_kopell_map_simulate, module)?)?;
24    Ok(())
25}
26
27/// Parity contract with
28/// `sc_neurocore.neurons.models.ermentrout_kopell_map_neuron.ErmentroutKopellMapNeuron.simulate`:
29/// for the same parameters and constant input the returned `theta` trace,
30/// upward-crossing spike count, and final `theta` state match the Python
31/// reference bit-for-bit on a shared libm (the only transcendental is `cos`,
32/// and the non-chaotic phase flow does not amplify ULP differences). This is a
33/// one-dimensional phase map, so there is no second state.
34#[pyfunction]
35#[pyo3(signature = (theta0, dt, gain, theta_threshold, n_steps, current))]
36fn py_ermentrout_kopell_map_simulate<'py>(
37    py: Python<'py>,
38    theta0: f64,
39    dt: f64,
40    gain: f64,
41    theta_threshold: f64,
42    n_steps: usize,
43    current: f64,
44) -> PyResult<(Bound<'py, PyArray1<f64>>, i64, f64)> {
45    let mut neuron = ErmentroutKopellMapNeuron {
46        theta: theta0,
47        dt,
48        gain,
49        theta_threshold,
50    };
51    let (trace, spikes) = neuron
52        .simulate(n_steps, current)
53        .map_err(PyFloatingPointError::new_err)?;
54    Ok((trace.into_pyarray(py), spikes, neuron.theta))
55}