Skip to main content

sc_neurocore_engine/bindings/
glif.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 — GLIF PyO3 binding
8
9//! Python binding for the Allen GLIF5 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!("GLIFNeuron", PyGLIFNeuron, neurons::GLIFNeuron, state v, state theta, state i_asc1, state i_asc2);
18
19/// Register the GLIF class and batch simulator.
20pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
21    module.add_class::<PyGLIFNeuron>()?;
22    module.add_function(wrap_pyfunction!(py_glif_simulate, module)?)?;
23    Ok(())
24}
25
26/// Parity contract with
27/// `sc_neurocore.neurons.models.glif.GLIFNeuron.simulate`: for the same
28/// parameters and constant input the returned `v` trace, total spike count, and
29/// final `(v, theta, i_asc1, i_asc2)` state are bit-identical to the Python RK4
30/// reference. The Allen GLIF5 model has a purely linear right-hand side (no
31/// transcendental functions), so every RK4 stage is exact arithmetic and the
32/// trace reproduces to the last bit across Rust/Julia/Go (Mojo FMA-fuses,
33/// validated non-amplifying).
34#[pyfunction]
35#[pyo3(signature = (
36    v0, theta0, theta_inf, i_asc1_0, i_asc2_0, v_rest, v_reset, tau_m, tau_theta,
37    tau_asc1, tau_asc2, a_theta, delta_theta, r_asc1, r_asc2, resistance, dt,
38    n_steps, current
39))]
40#[allow(clippy::too_many_arguments)]
41fn py_glif_simulate<'py>(
42    py: Python<'py>,
43    v0: f64,
44    theta0: f64,
45    theta_inf: f64,
46    i_asc1_0: f64,
47    i_asc2_0: f64,
48    v_rest: f64,
49    v_reset: f64,
50    tau_m: f64,
51    tau_theta: f64,
52    tau_asc1: f64,
53    tau_asc2: f64,
54    a_theta: f64,
55    delta_theta: f64,
56    r_asc1: f64,
57    r_asc2: f64,
58    resistance: f64,
59    dt: f64,
60    n_steps: usize,
61    current: f64,
62) -> (Bound<'py, PyArray1<f64>>, i64, f64, f64, f64, f64) {
63    let mut neuron = crate::neurons::GLIFNeuron {
64        v: v0,
65        theta: theta0,
66        theta_inf,
67        i_asc1: i_asc1_0,
68        i_asc2: i_asc2_0,
69        v_rest,
70        v_reset,
71        tau_m,
72        tau_theta,
73        tau_asc1,
74        tau_asc2,
75        a_theta,
76        delta_theta,
77        r_asc1,
78        r_asc2,
79        resistance,
80        dt,
81    };
82    let (trace, spikes) = neuron.simulate(n_steps, current);
83    (
84        trace.into_pyarray(py),
85        spikes,
86        neuron.v,
87        neuron.theta,
88        neuron.i_asc1,
89        neuron.i_asc2,
90    )
91}