sc_neurocore_engine/bindings/
adaptive_threshold_if.rs1use numpy::{IntoPyArray, PyReadonlyArray1};
10use pyo3::exceptions::{PyFloatingPointError, PyValueError};
11use pyo3::prelude::*;
12use pyo3::types::PyDict;
13
14use crate::neurons;
15use crate::neurons::trivial::adaptive_threshold_if::{self, AdaptiveThresholdIFError};
16
17py_neuron_default!("AdaptiveThresholdIFNeuron", PyAdaptiveThresholdIFNeuron, neurons::AdaptiveThresholdIFNeuron, state v, state theta);
18
19fn map_adaptive_threshold_if_error(error: AdaptiveThresholdIFError) -> PyErr {
20 match error {
21 AdaptiveThresholdIFError::NonFiniteCandidate => {
22 PyFloatingPointError::new_err(error.to_string())
23 }
24 _ => PyValueError::new_err(error.to_string()),
25 }
26}
27
28pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
30 module.add_class::<PyAdaptiveThresholdIFNeuron>()?;
31 module.add_function(wrap_pyfunction!(py_adaptive_threshold_if_simulate, module)?)?;
32 Ok(())
33}
34
35#[pyfunction]
37#[pyo3(signature = (v, theta, v_rest, v_reset, theta_rest, delta_theta, tau_m, tau_theta, dt, current))]
38#[allow(clippy::too_many_arguments)]
39fn py_adaptive_threshold_if_simulate<'py>(
40 py: Python<'py>,
41 v: f64,
42 theta: f64,
43 v_rest: f64,
44 v_reset: f64,
45 theta_rest: f64,
46 delta_theta: f64,
47 tau_m: f64,
48 tau_theta: f64,
49 dt: f64,
50 current: PyReadonlyArray1<'py, f64>,
51) -> PyResult<Py<PyAny>> {
52 let drive = current.as_slice()?;
53 let result = adaptive_threshold_if::simulate(
54 v,
55 theta,
56 v_rest,
57 v_reset,
58 theta_rest,
59 delta_theta,
60 tau_m,
61 tau_theta,
62 dt,
63 drive,
64 )
65 .map_err(map_adaptive_threshold_if_error)?;
66 let mapping = PyDict::new(py);
67 mapping.set_item("v", result.v.into_pyarray(py))?;
68 mapping.set_item("theta", result.theta.into_pyarray(py))?;
69 mapping.set_item("spikes", result.spikes.into_pyarray(py))?;
70 mapping.set_item("v_final", result.final_state[0])?;
71 mapping.set_item("theta_final", result.final_state[1])?;
72 mapping.set_item("spike_count", result.spike_count)?;
73 Ok(mapping.into_any().unbind())
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn configured_batch_rejects_nonfinite_drive_before_result_construction() {
82 let result = adaptive_threshold_if::simulate(
83 -65.0,
84 -50.0,
85 -65.0,
86 -65.0,
87 -50.0,
88 5.0,
89 10.0,
90 50.0,
91 0.1,
92 &[0.25, f64::NAN, 0.5],
93 );
94 assert!(matches!(
95 result,
96 Err(AdaptiveThresholdIFError::NonFiniteInput)
97 ));
98 }
99}