Skip to main content

sc_neurocore_engine/bindings/
terman_wang.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 — Terman-Wang oscillator PyO3 binding
8
9//! Python binding for the Terman-Wang LEGION relaxation oscillator.
10
11use numpy::{IntoPyArray, PyArray1};
12use pyo3::exceptions::PyFloatingPointError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::neurons::TermanWangOscillator;
17
18type PyTermanWangBatch<'py> = (Bound<'py, PyArray1<f64>>, i64, f64, f64);
19
20py_neuron_default!("TermanWangOscillator", PyTermanWangOscillator, TermanWangOscillator, state v, state w);
21
22/// Register the Terman-Wang class and simulator with the extension module.
23pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
24    module.add_class::<PyTermanWangOscillator>()?;
25    module.add_function(wrap_pyfunction!(py_terman_wang_simulate, module)?)?;
26    Ok(())
27}
28
29/// N-step Terman-Wang (LEGION) relaxation-oscillator simulation.
30///
31/// Parity contract with
32/// `sc_neurocore.neurons.models.terman_wang.TermanWangOscillator.simulate`: for
33/// the same parameters and constant input the returned `v` trace, upward-crossing
34/// spike count, and final `(v, w)` state match the Python RK4 reference. The cubic
35/// uses `v.powi(3)` = `v*v*v` (matching the Python `v*v*v`); the `tanh` gating
36/// resolves to the same glibc symbol as Python on Linux, so this backend is
37/// bit-identical there. Julia, Go, and Mojo use their own libm `tanh` and are
38/// validated against bounded full traces with exact event counts on enrolled
39/// operating regimes.
40#[pyfunction]
41#[pyo3(signature = (v0, w0, alpha, beta, epsilon, rho, dt, v_peak, n_steps, current))]
42#[allow(clippy::too_many_arguments)]
43fn py_terman_wang_simulate<'py>(
44    py: Python<'py>,
45    v0: f64,
46    w0: f64,
47    alpha: f64,
48    beta: f64,
49    epsilon: f64,
50    rho: f64,
51    dt: f64,
52    v_peak: f64,
53    n_steps: usize,
54    current: f64,
55) -> PyResult<PyTermanWangBatch<'py>> {
56    let mut neuron = TermanWangOscillator {
57        v: v0,
58        w: w0,
59        alpha,
60        beta,
61        epsilon,
62        rho,
63        dt,
64        v_peak,
65    };
66    let Some((trace, spikes)) = neuron.try_simulate(n_steps, current) else {
67        return Err(PyFloatingPointError::new_err(
68            "Terman-Wang Rust batch rejected an invalid candidate",
69        ));
70    };
71    Ok((trace.into_pyarray(py), spikes, neuron.v, neuron.w))
72}