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 — Teeter GLIF5 PyO3 binding
8
9//! Python class and failure-atomic batch surface for canonical GLIF5.
10
11use numpy::{IntoPyArray, PyArray1};
12use pyo3::exceptions::PyFloatingPointError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16use crate::neurons::GLIFNeuron;
17
18type GLIFBatchOutput<'py> = (Bound<'py, PyArray1<f64>>, i64, f64, f64, f64, f64, f64, f64);
19
20py_neuron_default!(
21    "GLIFNeuron",
22    PyGLIFNeuron,
23    GLIFNeuron,
24    state v,
25    state theta_spike,
26    state i_asc1,
27    state i_asc2,
28    state theta_voltage,
29    state refractory_remaining
30);
31
32/// Register the canonical class and batch function.
33pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
34    module.add_class::<PyGLIFNeuron>()?;
35    module.add_function(wrap_pyfunction!(py_glif_simulate, module)?)?;
36    Ok(())
37}
38
39/// Execute the five-state exact-flow specialization under constant current.
40#[pyfunction]
41#[pyo3(signature = (
42    v0, theta_spike0, i_asc1_0, i_asc2_0, theta_voltage0, refractory_remaining0,
43    e_l, capacitance, resistance, theta_inf, b_spike, b_voltage, a_voltage,
44    k_asc1, k_asc2, f_v, delta_v, delta_theta_spike, f_asc1, f_asc2,
45    delta_i_asc1, delta_i_asc2, refractory_period, dt, n_steps, current
46))]
47#[allow(clippy::too_many_arguments)]
48fn py_glif_simulate<'py>(
49    py: Python<'py>,
50    v0: f64,
51    theta_spike0: f64,
52    i_asc1_0: f64,
53    i_asc2_0: f64,
54    theta_voltage0: f64,
55    refractory_remaining0: f64,
56    e_l: f64,
57    capacitance: f64,
58    resistance: f64,
59    theta_inf: f64,
60    b_spike: f64,
61    b_voltage: f64,
62    a_voltage: f64,
63    k_asc1: f64,
64    k_asc2: f64,
65    f_v: f64,
66    delta_v: f64,
67    delta_theta_spike: f64,
68    f_asc1: f64,
69    f_asc2: f64,
70    delta_i_asc1: f64,
71    delta_i_asc2: f64,
72    refractory_period: f64,
73    dt: f64,
74    n_steps: usize,
75    current: f64,
76) -> PyResult<GLIFBatchOutput<'py>> {
77    let mut neuron = GLIFNeuron {
78        v: v0,
79        theta_spike: theta_spike0,
80        i_asc1: i_asc1_0,
81        i_asc2: i_asc2_0,
82        theta_voltage: theta_voltage0,
83        refractory_remaining: refractory_remaining0,
84        e_l,
85        capacitance,
86        resistance,
87        theta_inf,
88        b_spike,
89        b_voltage,
90        a_voltage,
91        k_asc1,
92        k_asc2,
93        f_v,
94        delta_v,
95        delta_theta_spike,
96        f_asc1,
97        f_asc2,
98        delta_i_asc1,
99        delta_i_asc2,
100        refractory_period,
101        dt,
102    };
103    let Some((trace, events)) = neuron.try_simulate(n_steps, current) else {
104        return Err(PyFloatingPointError::new_err(
105            "GLIF5 Rust batch rejected an invalid candidate",
106        ));
107    };
108    Ok((
109        trace.into_pyarray(py),
110        events,
111        neuron.v,
112        neuron.theta_spike,
113        neuron.i_asc1,
114        neuron.i_asc2,
115        neuron.theta_voltage,
116        neuron.refractory_remaining,
117    ))
118}