sc_neurocore_engine/bindings/
resonate_and_fire.rs1use numpy::{IntoPyArray, PyReadonlyArray1};
10use pyo3::exceptions::{PyFloatingPointError, PyValueError};
11use pyo3::prelude::*;
12use pyo3::types::PyDict;
13
14use crate::neurons;
15use crate::neurons::simple_spiking::resonate_and_fire::{self, ResonateAndFireError};
16
17py_neuron_default!("ResonateAndFireNeuron", PyResonateAndFireNeuron, neurons::ResonateAndFireNeuron, state x, state y);
18
19fn map_resonate_and_fire_error(error: ResonateAndFireError) -> PyErr {
20 match error {
21 ResonateAndFireError::NonFiniteCandidate => {
22 PyFloatingPointError::new_err(error.to_string())
23 }
24 _ => PyValueError::new_err(error.to_string()),
25 }
26}
27
28pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
30 module.add_class::<PyResonateAndFireNeuron>()?;
31 module.add_function(wrap_pyfunction!(py_resonate_and_fire_simulate, module)?)?;
32 Ok(())
33}
34
35#[pyfunction]
37#[pyo3(signature = (x, y, b, omega, threshold, dt, current))]
38fn py_resonate_and_fire_simulate<'py>(
39 py: Python<'py>,
40 x: f64,
41 y: f64,
42 b: f64,
43 omega: f64,
44 threshold: f64,
45 dt: f64,
46 current: PyReadonlyArray1<'py, f64>,
47) -> PyResult<Py<PyAny>> {
48 let drive = current.as_slice()?;
49 let result = resonate_and_fire::simulate(x, y, b, omega, threshold, dt, drive)
50 .map_err(map_resonate_and_fire_error)?;
51 let mapping = PyDict::new(py);
52 mapping.set_item("x", result.x.into_pyarray(py))?;
53 mapping.set_item("y", result.y.into_pyarray(py))?;
54 mapping.set_item("spikes", result.spikes.into_pyarray(py))?;
55 mapping.set_item("x_final", result.final_state[0])?;
56 mapping.set_item("y_final", result.final_state[1])?;
57 mapping.set_item("spike_count", result.spike_count)?;
58 Ok(mapping.into_any().unbind())
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn configured_batch_rejects_nonfinite_drive_before_result_construction() {
67 let result =
68 resonate_and_fire::simulate(0.0, 0.0, -1.0, 10.0, 1.0, 0.01, &[0.25, f64::NAN, 0.5]);
69 assert!(matches!(result, Err(ResonateAndFireError::NonFiniteInput)));
70 }
71}