Skip to main content

sc_neurocore_engine/bindings/trivial/
energy_lif.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
8//! PyO3 exposure for source-faithful Fardet-Levina eLIF.
9
10use crate::neurons;
11use numpy::{IntoPyArray, PyReadonlyArray1};
12use pyo3::exceptions::PyValueError;
13use pyo3::prelude::*;
14use pyo3::types::PyDict;
15
16#[pyclass(
17    name = "EnergyLIFNeuron",
18    module = "sc_neurocore_engine.sc_neurocore_engine"
19)]
20#[derive(Clone)]
21pub struct PyEnergyLIFNeuron {
22    inner: neurons::EnergyLIFNeuron,
23}
24
25#[pymethods]
26impl PyEnergyLIFNeuron {
27    #[new]
28    #[pyo3(signature=(v=-61.0,epsilon=0.32,capacitance=100.0,g_leak=9.0,e_0=-62.5,e_u=-58.5,e_d=-40.0,e_f=-62.0,v_threshold=-59.0,v_reset=-62.0,alpha=1.0,epsilon_0=0.5,epsilon_c=0.18,delta=0.01,tau_e=200.0,dt=0.1))]
29    #[allow(clippy::too_many_arguments)]
30    fn new(
31        v: f64,
32        epsilon: f64,
33        capacitance: f64,
34        g_leak: f64,
35        e_0: f64,
36        e_u: f64,
37        e_d: f64,
38        e_f: f64,
39        v_threshold: f64,
40        v_reset: f64,
41        alpha: f64,
42        epsilon_0: f64,
43        epsilon_c: f64,
44        delta: f64,
45        tau_e: f64,
46        dt: f64,
47    ) -> PyResult<Self> {
48        let inner = neurons::EnergyLIFNeuron {
49            v,
50            epsilon,
51            capacitance,
52            g_leak,
53            e_0,
54            e_u,
55            e_d,
56            e_f,
57            v_threshold,
58            v_reset,
59            alpha,
60            epsilon_0,
61            epsilon_c,
62            delta,
63            tau_e,
64            dt,
65        };
66        if !inner.valid() {
67            return Err(PyValueError::new_err(
68                "invalid EnergyLIF state or configuration",
69            ));
70        }
71        Ok(Self { inner })
72    }
73    fn step(&mut self, current: f64) -> PyResult<i32> {
74        self.inner.try_step(current).map_err(PyValueError::new_err)
75    }
76    fn reset(&mut self) {
77        self.inner.reset();
78    }
79    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
80        let d = PyDict::new(py);
81        d.set_item("v", self.inner.v)?;
82        d.set_item("epsilon", self.inner.epsilon)?;
83        Ok(d.into_any().unbind())
84    }
85}
86
87#[pyfunction]
88#[pyo3(signature=(v,epsilon,capacitance,g_leak,e_0,e_u,e_d,e_f,v_threshold,v_reset,alpha,epsilon_0,epsilon_c,delta,tau_e,dt,currents))]
89#[allow(clippy::too_many_arguments)]
90fn py_energy_lif_simulate<'py>(
91    py: Python<'py>,
92    v: f64,
93    epsilon: f64,
94    capacitance: f64,
95    g_leak: f64,
96    e_0: f64,
97    e_u: f64,
98    e_d: f64,
99    e_f: f64,
100    v_threshold: f64,
101    v_reset: f64,
102    alpha: f64,
103    epsilon_0: f64,
104    epsilon_c: f64,
105    delta: f64,
106    tau_e: f64,
107    dt: f64,
108    currents: PyReadonlyArray1<'py, f64>,
109) -> PyResult<Py<PyAny>> {
110    let mut n = neurons::EnergyLIFNeuron {
111        v,
112        epsilon,
113        capacitance,
114        g_leak,
115        e_0,
116        e_u,
117        e_d,
118        e_f,
119        v_threshold,
120        v_reset,
121        alpha,
122        epsilon_0,
123        epsilon_c,
124        delta,
125        tau_e,
126        dt,
127    };
128    if !n.valid() {
129        return Err(PyValueError::new_err(
130            "invalid EnergyLIF state or configuration",
131        ));
132    }
133    let mut voltages = Vec::with_capacity(currents.len()?);
134    let mut energies = Vec::with_capacity(currents.len()?);
135    let mut events = Vec::with_capacity(currents.len()?);
136    for &current in currents.as_slice()? {
137        events.push(n.try_step(current).map_err(PyValueError::new_err)?);
138        voltages.push(n.v);
139        energies.push(n.epsilon);
140    }
141    let d = PyDict::new(py);
142    d.set_item("voltages", voltages.into_pyarray(py))?;
143    d.set_item("epsilon", energies.into_pyarray(py))?;
144    d.set_item("events", events.into_pyarray(py))?;
145    d.set_item("v_final", n.v)?;
146    d.set_item("epsilon_final", n.epsilon)?;
147    Ok(d.into_any().unbind())
148}
149
150pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
151    module.add_class::<PyEnergyLIFNeuron>()?;
152    module.add_function(wrap_pyfunction!(py_energy_lif_simulate, module)?)?;
153    Ok(())
154}