Skip to main content

sc_neurocore_engine/
pyo3_neurons.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later | Commercial license available
2// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
3// © Code 2020–2026 Miroslav Šotek. All rights reserved.
4// ORCID: 0009-0009-3560-0851
5// Contact: www.anulum.li | protoscience@anulum.li
6// SC-NeuroCore — PyO3 wrappers for all neuron models
7
8//! PyO3 wrappers for all neuron models.
9//!
10//! Each wrapper follows the same pattern:
11//!   #[pyclass(name = "Model")] struct Py<Model> { inner: neurons::<Model> }
12//!   #[pymethods] impl Py<Model> { #[new] fn new(...) -> Self; fn step(...); fn reset(&mut self); fn get_state(...) }
13
14use numpy::{IntoPyArray, PyArray1};
15use pyo3::prelude::*;
16use pyo3::types::PyDict;
17
18use crate::neurons;
19
20macro_rules! py_neuron_default {
21    ($pylit:literal, $pyname:ident, $rust:ty $(, state $sname:ident)*) => {
22        #[pyclass(name = $pylit, module = "sc_neurocore_engine.sc_neurocore_engine")]
23        #[derive(Clone)]
24        pub struct $pyname { inner: $rust }
25
26        #[pymethods]
27        impl $pyname {
28            #[new]
29            fn new() -> Self { Self { inner: <$rust>::default() } }
30
31            fn step(&mut self, current: f64) -> i32 { self.inner.step(current) }
32
33            fn reset(&mut self) { self.inner.reset(); }
34
35            fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
36                let d = PyDict::new(py);
37                $(d.set_item(stringify!($sname), self.inner.$sname)?;)*
38                Ok(d.into_any().unbind())
39            }
40        }
41    };
42}
43
44// ═══════════════════════════════════════════════════════════════════
45// ai_optimized.rs models
46// ═══════════════════════════════════════════════════════════════════
47
48py_neuron_default!("MultiTimescaleNeuron", PyMultiTimescaleNeuron, neurons::MultiTimescaleNeuron, state v_fast, state v_medium, state v_slow);
49py_neuron_default!("AttentionGatedNeuron", PyAttentionGatedNeuron, neurons::AttentionGatedNeuron, state v);
50py_neuron_default!("PredictiveCodingNeuron", PyPredictiveCodingNeuron, neurons::PredictiveCodingNeuron, state v, state pred);
51py_neuron_default!("SelfReferentialNeuron", PySelfReferentialNeuron, neurons::SelfReferentialNeuron, state v);
52py_neuron_default!("CompositionalBindingNeuron", PyCompositionalBindingNeuron, neurons::CompositionalBindingNeuron, state phi, state amplitude);
53py_neuron_default!("DifferentiableSurrogateNeuron", PyDifferentiableSurrogateNeuron, neurons::DifferentiableSurrogateNeuron, state v);
54py_neuron_default!("MetaPlasticNeuron", PyMetaPlasticNeuron, neurons::MetaPlasticNeuron, state v, state error_trace, state expected_reward);
55
56py_neuron_default!("ArcaneNeuron", PyArcaneNeuron, neurons::ArcaneNeuron, state v_fast, state v_work, state v_deep);
57
58// ContinuousAttractorNeuron needs n_units param
59#[pyclass(
60    name = "RustContinuousAttractorNeuron",
61    module = "sc_neurocore_engine.sc_neurocore_engine"
62)]
63#[derive(Clone)]
64pub struct PyContinuousAttractorNeuron {
65    inner: neurons::ContinuousAttractorNeuron,
66}
67
68#[pymethods]
69impl PyContinuousAttractorNeuron {
70    #[new]
71    #[pyo3(signature = (n_units=16))]
72    fn new(n_units: usize) -> Self {
73        Self {
74            inner: neurons::ContinuousAttractorNeuron::new(n_units),
75        }
76    }
77    fn step(&mut self, current: f64) -> i32 {
78        self.inner.step(current)
79    }
80    fn bump_position(&self) -> usize {
81        self.inner.bump_position()
82    }
83    fn reset(&mut self) {
84        self.inner.reset();
85    }
86    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
87        let d = PyDict::new(py);
88        d.set_item("u", self.inner.u.clone().into_pyarray(py))?;
89        Ok(d.into_any().unbind())
90    }
91}
92
93// ═══════════════════════════════════════════════════════════════════
94// trivial.rs models
95// ═══════════════════════════════════════════════════════════════════
96
97py_neuron_default!("QuadraticIFNeuron", PyQuadraticIFNeuron, neurons::QuadraticIFNeuron, state v);
98py_neuron_default!("ThetaNeuron", PyThetaNeuron, neurons::ThetaNeuron, state theta);
99py_neuron_default!("PerfectIntegratorNeuron", PyPerfectIntegratorNeuron, neurons::PerfectIntegratorNeuron, state v);
100py_neuron_default!("GatedLIFNeuron", PyGatedLIFNeuron, neurons::GatedLIFNeuron, state v);
101py_neuron_default!("NonlinearLIFNeuron", PyNonlinearLIFNeuron, neurons::NonlinearLIFNeuron, state v, state w);
102py_neuron_default!("SFANeuron", PySFANeuron, neurons::SFANeuron, state v, state g_sfa);
103py_neuron_default!("MATNeuron", PyMATNeuron, neurons::MATNeuron, state v, state theta1, state theta2);
104py_neuron_default!("KLIFNeuron", PyKLIFNeuron, neurons::KLIFNeuron, state v);
105py_neuron_default!("InhibitoryLIFNeuron", PyInhibitoryLIFNeuron, neurons::InhibitoryLIFNeuron, state v, state inh_trace);
106py_neuron_default!("ComplementaryLIFNeuron", PyComplementaryLIFNeuron, neurons::ComplementaryLIFNeuron, state v_pos, state v_neg);
107py_neuron_default!("ParametricLIFNeuron", PyParametricLIFNeuron, neurons::ParametricLIFNeuron, state v);
108py_neuron_default!("NonResettingLIFNeuron", PyNonResettingLIFNeuron, neurons::NonResettingLIFNeuron, state v, state theta);
109py_neuron_default!("AdaptiveThresholdIFNeuron", PyAdaptiveThresholdIFNeuron, neurons::AdaptiveThresholdIFNeuron, state v, state theta);
110py_neuron_default!("SigmaDeltaNeuron", PySigmaDeltaNeuron, neurons::SigmaDeltaNeuron, state sigma);
111py_neuron_default!("EnergyLIFNeuron", PyEnergyLIFNeuron, neurons::EnergyLIFNeuron, state v, state epsilon);
112py_neuron_default!("ClosedFormContinuousNeuron", PyClosedFormContinuousNeuron, neurons::ClosedFormContinuousNeuron, state x);
113
114#[pyclass(
115    name = "IntegerQIFNeuron",
116    module = "sc_neurocore_engine.sc_neurocore_engine"
117)]
118#[derive(Clone)]
119pub struct PyIntegerQIFNeuron {
120    inner: neurons::IntegerQIFNeuron,
121}
122
123#[pymethods]
124impl PyIntegerQIFNeuron {
125    #[new]
126    #[pyo3(signature = (k=6, v_threshold=1024))]
127    fn new(k: i32, v_threshold: i32) -> Self {
128        Self {
129            inner: neurons::IntegerQIFNeuron::new(k, v_threshold),
130        }
131    }
132    fn step(&mut self, current: i32) -> i32 {
133        self.inner.step(current)
134    }
135    fn reset(&mut self) {
136        self.inner.reset();
137    }
138    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
139        let d = PyDict::new(py);
140        d.set_item("v", self.inner.v)?;
141        Ok(d.into_any().unbind())
142    }
143}
144
145// EscapeRateNeuron needs seed
146#[pyclass(
147    name = "EscapeRateNeuron",
148    module = "sc_neurocore_engine.sc_neurocore_engine"
149)]
150#[derive(Clone)]
151pub struct PyEscapeRateNeuron {
152    inner: neurons::EscapeRateNeuron,
153}
154
155#[pymethods]
156impl PyEscapeRateNeuron {
157    #[new]
158    #[pyo3(signature = (seed=42))]
159    fn new(seed: u64) -> Self {
160        Self {
161            inner: neurons::EscapeRateNeuron::new(seed),
162        }
163    }
164    fn step(&mut self, current: f64) -> i32 {
165        self.inner.step(current)
166    }
167    fn reset(&mut self) {
168        self.inner.reset();
169    }
170    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
171        let d = PyDict::new(py);
172        d.set_item("v", self.inner.v)?;
173        Ok(d.into_any().unbind())
174    }
175}
176
177// ═══════════════════════════════════════════════════════════════════
178// simple_spiking.rs models
179// ═══════════════════════════════════════════════════════════════════
180
181py_neuron_default!("FitzHughNagumoNeuron", PyFitzHughNagumoNeuron, neurons::FitzHughNagumoNeuron, state v, state w);
182py_neuron_default!("MorrisLecarNeuron", PyMorrisLecarNeuron, neurons::MorrisLecarNeuron, state v, state w);
183py_neuron_default!("HindmarshRoseNeuron", PyHindmarshRoseNeuron, neurons::HindmarshRoseNeuron, state x, state y, state z);
184py_neuron_default!("ResonateAndFireNeuron", PyResonateAndFireNeuron, neurons::ResonateAndFireNeuron, state x, state y);
185py_neuron_default!("FitzHughRinzelNeuron", PyFitzHughRinzelNeuron, neurons::FitzHughRinzelNeuron, state v, state w, state y);
186py_neuron_default!("McKeanNeuron", PyMcKeanNeuron, neurons::McKeanNeuron, state v, state w);
187py_neuron_default!("TermanWangOscillator", PyTermanWangOscillator, neurons::TermanWangOscillator, state v, state w);
188py_neuron_default!("GutkinErmentroutNeuron", PyGutkinErmentroutNeuron, neurons::GutkinErmentroutNeuron, state v, state n);
189py_neuron_default!("WilsonHRNeuron", PyWilsonHRNeuron, neurons::WilsonHRNeuron, state v, state r);
190py_neuron_default!("ChayNeuron", PyChayNeuron, neurons::ChayNeuron, state v, state n, state ca);
191py_neuron_default!("ChayKeizerNeuron", PyChayKeizerNeuron, neurons::ChayKeizerNeuron, state v, state n, state ca);
192py_neuron_default!("ShermanRinzelKeizerNeuron", PyShermanRinzelKeizerNeuron, neurons::ShermanRinzelKeizerNeuron, state v, state n, state s);
193py_neuron_default!("ButeraRespiratoryNeuron", PyButeraRespiratoryNeuron, neurons::ButeraRespiratoryNeuron, state v, state n, state h_nap);
194py_neuron_default!("LearnableNeuronModel", PyLearnableNeuronModel, neurons::LearnableNeuronModel, state v);
195py_neuron_default!("PernarowskiNeuron", PyPernarowskiNeuron, neurons::PernarowskiNeuron, state v, state w, state z);
196
197// AlphaNeuron: step(exc, inh)
198#[pyclass(
199    name = "AlphaNeuron",
200    module = "sc_neurocore_engine.sc_neurocore_engine"
201)]
202#[derive(Clone)]
203pub struct PyAlphaNeuron {
204    inner: neurons::AlphaNeuron,
205}
206
207#[pymethods]
208impl PyAlphaNeuron {
209    #[new]
210    fn new() -> Self {
211        Self {
212            inner: neurons::AlphaNeuron::new(),
213        }
214    }
215    #[pyo3(signature = (exc_current, inh_current=0.0))]
216    fn step(&mut self, exc_current: f64, inh_current: f64) -> i32 {
217        self.inner.step(exc_current, inh_current)
218    }
219    fn reset(&mut self) {
220        self.inner.reset();
221    }
222    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
223        let d = PyDict::new(py);
224        d.set_item("v", self.inner.v)?;
225        d.set_item("i_exc", self.inner.i_exc)?;
226        d.set_item("i_inh", self.inner.i_inh)?;
227        Ok(d.into_any().unbind())
228    }
229}
230
231// COBALIFNeuron: step(current, delta_ge, delta_gi)
232#[pyclass(
233    name = "COBALIFNeuron",
234    module = "sc_neurocore_engine.sc_neurocore_engine"
235)]
236#[derive(Clone)]
237pub struct PyCOBALIFNeuron {
238    inner: neurons::COBALIFNeuron,
239}
240
241#[pymethods]
242impl PyCOBALIFNeuron {
243    #[new]
244    fn new() -> Self {
245        Self {
246            inner: neurons::COBALIFNeuron::new(),
247        }
248    }
249    #[pyo3(signature = (current, delta_ge=0.0, delta_gi=0.0))]
250    fn step(&mut self, current: f64, delta_ge: f64, delta_gi: f64) -> i32 {
251        self.inner.step(current, delta_ge, delta_gi)
252    }
253    fn reset(&mut self) {
254        self.inner.reset();
255    }
256    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
257        let d = PyDict::new(py);
258        d.set_item("v", self.inner.v)?;
259        d.set_item("g_e", self.inner.g_e)?;
260        d.set_item("g_i", self.inner.g_i)?;
261        Ok(d.into_any().unbind())
262    }
263}
264
265// EPropALIFNeuron: needs tau params
266#[pyclass(
267    name = "EPropALIFNeuron",
268    module = "sc_neurocore_engine.sc_neurocore_engine"
269)]
270#[derive(Clone)]
271pub struct PyEPropALIFNeuron {
272    inner: neurons::EPropALIFNeuron,
273}
274
275#[pymethods]
276impl PyEPropALIFNeuron {
277    #[new]
278    #[pyo3(signature = (tau_m=20.0, tau_a=200.0, dt=1.0))]
279    fn new(tau_m: f64, tau_a: f64, dt: f64) -> Self {
280        Self {
281            inner: neurons::EPropALIFNeuron::new(tau_m, tau_a, dt),
282        }
283    }
284    fn step(&mut self, current: f64) -> i32 {
285        self.inner.step(current)
286    }
287    fn reset(&mut self) {
288        self.inner.reset();
289    }
290    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
291        let d = PyDict::new(py);
292        d.set_item("v", self.inner.v)?;
293        d.set_item("a", self.inner.a)?;
294        d.set_item("e_trace", self.inner.e_trace)?;
295        Ok(d.into_any().unbind())
296    }
297}
298
299// SuperSpikeNeuron: needs tau params
300#[pyclass(
301    name = "SuperSpikeNeuron",
302    module = "sc_neurocore_engine.sc_neurocore_engine"
303)]
304#[derive(Clone)]
305pub struct PySuperSpikeNeuron {
306    inner: neurons::SuperSpikeNeuron,
307}
308
309#[pymethods]
310impl PySuperSpikeNeuron {
311    #[new]
312    #[pyo3(signature = (tau_m=10.0, tau_e=10.0, dt=1.0))]
313    fn new(tau_m: f64, tau_e: f64, dt: f64) -> Self {
314        Self {
315            inner: neurons::SuperSpikeNeuron::new(tau_m, tau_e, dt),
316        }
317    }
318    fn step(&mut self, current: f64) -> i32 {
319        self.inner.step(current)
320    }
321    fn reset(&mut self) {
322        self.inner.reset();
323    }
324    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
325        let d = PyDict::new(py);
326        d.set_item("v", self.inner.v)?;
327        d.set_item("trace", self.inner.trace)?;
328        Ok(d.into_any().unbind())
329    }
330}
331
332// BendaHerzNeuron: needs seed
333#[pyclass(
334    name = "BendaHerzNeuron",
335    module = "sc_neurocore_engine.sc_neurocore_engine"
336)]
337#[derive(Clone)]
338pub struct PyBendaHerzNeuron {
339    inner: neurons::BendaHerzNeuron,
340}
341
342#[pymethods]
343impl PyBendaHerzNeuron {
344    #[new]
345    #[pyo3(signature = (seed=42))]
346    fn new(seed: u64) -> Self {
347        Self {
348            inner: neurons::BendaHerzNeuron::new(seed),
349        }
350    }
351    fn step(&mut self, current: f64) -> i32 {
352        self.inner.step(current)
353    }
354    fn reset(&mut self) {
355        self.inner.reset();
356    }
357    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
358        let d = PyDict::new(py);
359        d.set_item("a", self.inner.a)?;
360        Ok(d.into_any().unbind())
361    }
362}
363
364// ═══════════════════════════════════════════════════════════════════
365// maps.rs models
366// ═══════════════════════════════════════════════════════════════════
367
368py_neuron_default!("ChialvoMapNeuron", PyChialvoMapNeuron, neurons::ChialvoMapNeuron, state x, state y);
369py_neuron_default!("RulkovMapNeuron", PyRulkovMapNeuron, neurons::RulkovMapNeuron, state x, state y);
370py_neuron_default!("IbarzTanakaMapNeuron", PyIbarzTanakaMapNeuron, neurons::IbarzTanakaMapNeuron, state x, state y);
371py_neuron_default!("MedvedevMapNeuron", PyMedvedevMapNeuron, neurons::MedvedevMapNeuron, state x);
372py_neuron_default!("CazellesMapNeuron", PyCazellesMapNeuron, neurons::CazellesMapNeuron, state x, state y);
373py_neuron_default!("CourageNekorkinMapNeuron", PyCourageNekorkinMapNeuron, neurons::CourageNekorkinMapNeuron, state x, state y);
374
375// ═══════════════════════════════════════════════════════════════════
376// biophysical.rs models
377// ═══════════════════════════════════════════════════════════════════
378
379py_neuron_default!("HodgkinHuxleyNeuron", PyHodgkinHuxleyNeuron, neurons::HodgkinHuxleyNeuron, state v, state m, state h, state n);
380py_neuron_default!("TraubMilesNeuron", PyTraubMilesNeuron, neurons::TraubMilesNeuron, state v, state m, state h, state n);
381py_neuron_default!("WangBuzsakiNeuron", PyWangBuzsakiNeuron, neurons::WangBuzsakiNeuron, state v, state h, state n);
382py_neuron_default!("ConnorStevensNeuron", PyConnorStevensNeuron, neurons::ConnorStevensNeuron, state v, state m, state h, state n, state a, state b);
383py_neuron_default!("DestexheThalamicNeuron", PyDestexheThalamicNeuron, neurons::DestexheThalamicNeuron, state v, state h_na, state n_k, state m_t, state h_t);
384py_neuron_default!("HuberBraunNeuron", PyHuberBraunNeuron, neurons::HuberBraunNeuron, state v, state a_sd, state a_sr);
385py_neuron_default!("GolombFSNeuron", PyGolombFSNeuron, neurons::GolombFSNeuron, state v, state h, state n, state p);
386py_neuron_default!("PospischilNeuron", PyPospischilNeuron, neurons::PospischilNeuron, state v, state m, state h, state n, state p);
387py_neuron_default!("MainenSejnowskiNeuron", PyMainenSejnowskiNeuron, neurons::MainenSejnowskiNeuron, state vs, state va, state m, state h, state n);
388py_neuron_default!("DeSchutterPurkinjeNeuron", PyDeSchutterPurkinjeNeuron, neurons::DeSchutterPurkinjeNeuron, state v, state h_na, state n_k, state m_cap, state h_cap, state q_kca, state ca);
389py_neuron_default!("PlantR15Neuron", PyPlantR15Neuron, neurons::PlantR15Neuron, state v, state m, state h, state n, state ca);
390py_neuron_default!("PrescottNeuron", PyPrescottNeuron, neurons::PrescottNeuron, state v, state w);
391py_neuron_default!("MihalasNieburNeuron", PyMihalasNieburNeuron, neurons::MihalasNieburNeuron, state v, state theta, state i1, state i2);
392py_neuron_default!("GLIFNeuron", PyGLIFNeuron, neurons::GLIFNeuron, state v, state theta, state i_asc1, state i_asc2);
393py_neuron_default!("AvRonCardiacNeuron", PyAvRonCardiacNeuron, neurons::AvRonCardiacNeuron, state v, state h, state n, state s);
394py_neuron_default!("DurstewitzDopamineNeuron", PyDurstewitzDopamineNeuron, neurons::DurstewitzDopamineNeuron, state v, state h_na, state n_k);
395py_neuron_default!("HillTononiNeuron", PyHillTononiNeuron, neurons::HillTononiNeuron, state v, state h_na, state n_k, state m_h, state h_t, state na_i);
396py_neuron_default!("BertramPhantomBurster", PyBertramPhantomBurster, neurons::BertramPhantomBurster, state v, state s1, state s2);
397py_neuron_default!("YamadaNeuron", PyYamadaNeuron, neurons::YamadaNeuron, state v, state n, state q);
398
399// GIFPopulationNeuron: needs seed
400#[pyclass(
401    name = "GIFPopulationNeuron",
402    module = "sc_neurocore_engine.sc_neurocore_engine"
403)]
404#[derive(Clone)]
405pub struct PyGIFPopulationNeuron {
406    inner: neurons::GIFPopulationNeuron,
407}
408
409#[pymethods]
410impl PyGIFPopulationNeuron {
411    #[new]
412    #[pyo3(signature = (seed=42))]
413    fn new(seed: u64) -> Self {
414        Self {
415            inner: neurons::GIFPopulationNeuron::new(seed),
416        }
417    }
418    fn step(&mut self, current: f64) -> i32 {
419        self.inner.step(current)
420    }
421    fn reset(&mut self) {
422        self.inner.reset();
423    }
424    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
425        let d = PyDict::new(py);
426        d.set_item("v", self.inner.v)?;
427        d.set_item("theta", self.inner.theta)?;
428        d.set_item("eta", self.inner.eta)?;
429        Ok(d.into_any().unbind())
430    }
431}
432
433// ═══════════════════════════════════════════════════════════════════
434// multi_compartment.rs models
435// ═══════════════════════════════════════════════════════════════════
436
437// PinskyRinzelNeuron: step(current_soma, current_dend)
438#[pyclass(
439    name = "PinskyRinzelNeuron",
440    module = "sc_neurocore_engine.sc_neurocore_engine"
441)]
442#[derive(Clone)]
443pub struct PyPinskyRinzelNeuron {
444    inner: neurons::PinskyRinzelNeuron,
445}
446
447#[pymethods]
448impl PyPinskyRinzelNeuron {
449    #[new]
450    fn new() -> Self {
451        Self {
452            inner: neurons::PinskyRinzelNeuron::new(),
453        }
454    }
455    #[pyo3(signature = (current_soma, current_dend=0.0))]
456    fn step(&mut self, current_soma: f64, current_dend: f64) -> i32 {
457        self.inner.step(current_soma, current_dend)
458    }
459    fn reset(&mut self) {
460        self.inner.reset();
461    }
462    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
463        let d = PyDict::new(py);
464        d.set_item("v_s", self.inner.v_s)?;
465        d.set_item("v_d", self.inner.v_d)?;
466        Ok(d.into_any().unbind())
467    }
468}
469
470// HayL5PyramidalNeuron: step(current_soma, current_tuft)
471#[pyclass(
472    name = "HayL5PyramidalNeuron",
473    module = "sc_neurocore_engine.sc_neurocore_engine"
474)]
475#[derive(Clone)]
476pub struct PyHayL5PyramidalNeuron {
477    inner: neurons::HayL5PyramidalNeuron,
478}
479
480#[pymethods]
481impl PyHayL5PyramidalNeuron {
482    #[new]
483    fn new() -> Self {
484        Self {
485            inner: neurons::HayL5PyramidalNeuron::new(),
486        }
487    }
488    #[pyo3(signature = (current_soma, current_tuft=0.0))]
489    fn step(&mut self, current_soma: f64, current_tuft: f64) -> i32 {
490        self.inner.step(current_soma, current_tuft)
491    }
492    fn reset(&mut self) {
493        self.inner.reset();
494    }
495    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
496        let d = PyDict::new(py);
497        d.set_item("v_s", self.inner.v_s)?;
498        d.set_item("v_t", self.inner.v_t)?;
499        d.set_item("v_a", self.inner.v_a)?;
500        Ok(d.into_any().unbind())
501    }
502}
503
504py_neuron_default!("MarderSTGNeuron", PyMarderSTGNeuron, neurons::MarderSTGNeuron, state v, state ca);
505py_neuron_default!("BoothRinzelNeuron", PyBoothRinzelNeuron, neurons::BoothRinzelNeuron, state vs, state vd, state ca);
506py_neuron_default!("DendrifyNeuron", PyDendrifyNeuron, neurons::DendrifyNeuron, state v_s, state v_d);
507
508// RallCableNeuron: variable compartments
509#[pyclass(
510    name = "RallCableNeuron",
511    module = "sc_neurocore_engine.sc_neurocore_engine"
512)]
513#[derive(Clone)]
514pub struct PyRallCableNeuron {
515    inner: neurons::RallCableNeuron,
516}
517
518#[pymethods]
519impl PyRallCableNeuron {
520    #[new]
521    #[pyo3(signature = (n_comp=5))]
522    fn new(n_comp: usize) -> Self {
523        Self {
524            inner: neurons::RallCableNeuron::new(n_comp),
525        }
526    }
527    fn step(&mut self, current: f64) -> i32 {
528        self.inner.step(current)
529    }
530    fn reset(&mut self) {
531        self.inner.reset();
532    }
533    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
534        let d = PyDict::new(py);
535        d.set_item("v", self.inner.v.clone())?;
536        Ok(d.into_any().unbind())
537    }
538}
539
540// TwoCompartmentLIFNeuron: step(i_soma, i_dend)
541#[pyclass(
542    name = "TwoCompartmentLIFNeuron",
543    module = "sc_neurocore_engine.sc_neurocore_engine"
544)]
545#[derive(Clone)]
546pub struct PyTwoCompartmentLIFNeuron {
547    inner: neurons::TwoCompartmentLIFNeuron,
548}
549
550#[pymethods]
551impl PyTwoCompartmentLIFNeuron {
552    #[new]
553    fn new() -> Self {
554        Self {
555            inner: neurons::TwoCompartmentLIFNeuron::new(),
556        }
557    }
558    #[pyo3(signature = (i_soma, i_dend=0.0))]
559    fn step(&mut self, i_soma: f64, i_dend: f64) -> i32 {
560        self.inner.step(i_soma, i_dend)
561    }
562    fn reset(&mut self) {
563        self.inner.reset();
564    }
565    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
566        let d = PyDict::new(py);
567        d.set_item("v_s", self.inner.v_s)?;
568        d.set_item("v_d", self.inner.v_d)?;
569        Ok(d.into_any().unbind())
570    }
571}
572
573// ═══════════════════════════════════════════════════════════════════
574// special.rs models (stochastic / population / neural mass)
575// ═══════════════════════════════════════════════════════════════════
576
577#[pyclass(
578    name = "PoissonNeuron",
579    module = "sc_neurocore_engine.sc_neurocore_engine"
580)]
581#[derive(Clone)]
582pub struct PyPoissonNeuron {
583    inner: neurons::PoissonNeuron,
584}
585
586#[pymethods]
587impl PyPoissonNeuron {
588    #[new]
589    #[pyo3(signature = (rate_hz=100.0, dt_ms=1.0, seed=42))]
590    fn new(rate_hz: f64, dt_ms: f64, seed: u64) -> Self {
591        Self {
592            inner: neurons::PoissonNeuron::new(rate_hz, dt_ms, seed),
593        }
594    }
595    #[pyo3(signature = (rate_override=-1.0))]
596    fn step(&mut self, rate_override: f64) -> i32 {
597        self.inner.step(rate_override)
598    }
599    fn reset(&mut self) {
600        self.inner.reset();
601    }
602}
603
604#[pyclass(
605    name = "InhomogeneousPoissonNeuron",
606    module = "sc_neurocore_engine.sc_neurocore_engine"
607)]
608#[derive(Clone)]
609pub struct PyInhomogeneousPoissonNeuron {
610    inner: neurons::InhomogeneousPoissonNeuron,
611}
612
613#[pymethods]
614impl PyInhomogeneousPoissonNeuron {
615    #[new]
616    #[pyo3(signature = (dt_ms=1.0, seed=42))]
617    fn new(dt_ms: f64, seed: u64) -> Self {
618        Self {
619            inner: neurons::InhomogeneousPoissonNeuron::new(dt_ms, seed),
620        }
621    }
622    fn step(&mut self, rate_hz: f64) -> i32 {
623        self.inner.step(rate_hz)
624    }
625    fn reset(&mut self) {
626        self.inner.reset();
627    }
628}
629
630#[pyclass(
631    name = "GammaRenewalNeuron",
632    module = "sc_neurocore_engine.sc_neurocore_engine"
633)]
634#[derive(Clone)]
635pub struct PyGammaRenewalNeuron {
636    inner: neurons::GammaRenewalNeuron,
637}
638
639#[pymethods]
640impl PyGammaRenewalNeuron {
641    #[new]
642    #[pyo3(signature = (rate_hz=50.0, shape_k=3, seed=42))]
643    fn new(rate_hz: f64, shape_k: u32, seed: u64) -> Self {
644        Self {
645            inner: neurons::GammaRenewalNeuron::new(rate_hz, shape_k, seed),
646        }
647    }
648    #[pyo3(signature = (rate_override=-1.0))]
649    fn step(&mut self, rate_override: f64) -> i32 {
650        self.inner.step(rate_override)
651    }
652    fn reset(&mut self) {
653        self.inner.reset();
654    }
655}
656
657#[pyclass(
658    name = "StochasticIFNeuron",
659    module = "sc_neurocore_engine.sc_neurocore_engine"
660)]
661#[derive(Clone)]
662pub struct PyStochasticIFNeuron {
663    inner: neurons::StochasticIFNeuron,
664}
665
666#[pymethods]
667impl PyStochasticIFNeuron {
668    #[new]
669    #[pyo3(signature = (seed=42))]
670    fn new(seed: u64) -> Self {
671        Self {
672            inner: neurons::StochasticIFNeuron::new(seed),
673        }
674    }
675    fn step(&mut self, current: f64) -> i32 {
676        self.inner.step(current)
677    }
678    fn reset(&mut self) {
679        self.inner.reset();
680    }
681    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
682        let d = PyDict::new(py);
683        d.set_item("v", self.inner.v)?;
684        Ok(d.into_any().unbind())
685    }
686}
687
688#[pyclass(
689    name = "GalvesLocherbachNeuron",
690    module = "sc_neurocore_engine.sc_neurocore_engine"
691)]
692#[derive(Clone)]
693pub struct PyGalvesLocherbachNeuron {
694    inner: neurons::GalvesLocherbachNeuron,
695}
696
697#[pymethods]
698impl PyGalvesLocherbachNeuron {
699    #[new]
700    #[pyo3(signature = (seed=42))]
701    fn new(seed: u64) -> Self {
702        Self {
703            inner: neurons::GalvesLocherbachNeuron::new(seed),
704        }
705    }
706    fn step(&mut self, weighted_input: f64) -> i32 {
707        self.inner.step(weighted_input)
708    }
709    fn reset(&mut self) {
710        self.inner.reset();
711    }
712    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
713        let d = PyDict::new(py);
714        d.set_item("v", self.inner.v)?;
715        Ok(d.into_any().unbind())
716    }
717}
718
719py_neuron_default!("SpikeResponseNeuron", PySpikeResponseNeuron, neurons::SpikeResponseNeuron, state v, state time_since_spike);
720
721// GLMNeuron: needs n_k, n_h, seed
722#[pyclass(name = "GLMNeuron", module = "sc_neurocore_engine.sc_neurocore_engine")]
723#[derive(Clone)]
724pub struct PyGLMNeuron {
725    inner: neurons::GLMNeuron,
726}
727
728#[pymethods]
729impl PyGLMNeuron {
730    #[new]
731    #[pyo3(signature = (n_k=10, n_h=20, seed=42))]
732    fn new(n_k: usize, n_h: usize, seed: u64) -> Self {
733        Self {
734            inner: neurons::GLMNeuron::new(n_k, n_h, seed),
735        }
736    }
737    fn step(&mut self, stimulus: f64) -> i32 {
738        self.inner.step(stimulus)
739    }
740    fn reset(&mut self) {
741        self.inner.reset();
742    }
743}
744
745// WilsonCowanUnit: step returns f64
746#[pyclass(
747    name = "WilsonCowanUnit",
748    module = "sc_neurocore_engine.sc_neurocore_engine"
749)]
750#[derive(Clone)]
751pub struct PyWilsonCowanUnit {
752    inner: neurons::WilsonCowanUnit,
753}
754
755#[pymethods]
756impl PyWilsonCowanUnit {
757    #[new]
758    fn new() -> Self {
759        Self {
760            inner: neurons::WilsonCowanUnit::new(),
761        }
762    }
763    #[pyo3(signature = (ext_input=0.0))]
764    fn step(&mut self, ext_input: f64) -> f64 {
765        self.inner.step(ext_input)
766    }
767    fn reset(&mut self) {
768        self.inner.reset();
769    }
770    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
771        let d = PyDict::new(py);
772        d.set_item("e", self.inner.e)?;
773        d.set_item("i", self.inner.i)?;
774        Ok(d.into_any().unbind())
775    }
776}
777
778// JansenRitUnit: step returns f64
779#[pyclass(
780    name = "JansenRitUnit",
781    module = "sc_neurocore_engine.sc_neurocore_engine"
782)]
783#[derive(Clone)]
784pub struct PyJansenRitUnit {
785    inner: neurons::JansenRitUnit,
786}
787
788#[pymethods]
789impl PyJansenRitUnit {
790    #[new]
791    fn new() -> Self {
792        Self {
793            inner: neurons::JansenRitUnit::new(),
794        }
795    }
796    #[pyo3(signature = (p_ext=220.0))]
797    fn step(&mut self, p_ext: f64) -> f64 {
798        self.inner.step(p_ext)
799    }
800    fn reset(&mut self) {
801        self.inner.reset();
802    }
803    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
804        let d = PyDict::new(py);
805        d.set_item("y", self.inner.y.to_vec())?;
806        Ok(d.into_any().unbind())
807    }
808}
809
810// WongWangUnit: step(stim1, stim2) -> (f64, f64)
811#[pyclass(
812    name = "WongWangUnit",
813    module = "sc_neurocore_engine.sc_neurocore_engine"
814)]
815#[derive(Clone)]
816pub struct PyWongWangUnit {
817    inner: neurons::WongWangUnit,
818}
819
820#[pymethods]
821impl PyWongWangUnit {
822    #[new]
823    #[pyo3(signature = (seed=42))]
824    fn new(seed: u64) -> Self {
825        Self {
826            inner: neurons::WongWangUnit::new(seed),
827        }
828    }
829    #[pyo3(signature = (stim1=0.0, stim2=0.0))]
830    fn step(&mut self, stim1: f64, stim2: f64) -> (f64, f64) {
831        self.inner.step(stim1, stim2)
832    }
833    fn reset(&mut self) {
834        self.inner.reset();
835    }
836    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
837        let d = PyDict::new(py);
838        d.set_item("s1", self.inner.s1)?;
839        d.set_item("s2", self.inner.s2)?;
840        Ok(d.into_any().unbind())
841    }
842}
843
844// ErmentroutKopellPopulation: step returns f64
845#[pyclass(
846    name = "ErmentroutKopellPopulation",
847    module = "sc_neurocore_engine.sc_neurocore_engine"
848)]
849#[derive(Clone)]
850pub struct PyErmentroutKopellPopulation {
851    inner: neurons::ErmentroutKopellPopulation,
852}
853
854#[pymethods]
855impl PyErmentroutKopellPopulation {
856    #[new]
857    fn new() -> Self {
858        Self {
859            inner: neurons::ErmentroutKopellPopulation::new(),
860        }
861    }
862    #[pyo3(signature = (ext_input=0.0))]
863    fn step(&mut self, ext_input: f64) -> f64 {
864        self.inner.step(ext_input)
865    }
866    fn reset(&mut self) {
867        self.inner.reset();
868    }
869    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
870        let d = PyDict::new(py);
871        d.set_item("r", self.inner.r)?;
872        d.set_item("v", self.inner.v)?;
873        Ok(d.into_any().unbind())
874    }
875}
876
877// WendlingNeuron: step returns f64
878#[pyclass(
879    name = "WendlingNeuron",
880    module = "sc_neurocore_engine.sc_neurocore_engine"
881)]
882#[derive(Clone)]
883pub struct PyWendlingNeuron {
884    inner: neurons::WendlingNeuron,
885}
886
887#[pymethods]
888impl PyWendlingNeuron {
889    #[new]
890    fn new() -> Self {
891        Self {
892            inner: neurons::WendlingNeuron::new(),
893        }
894    }
895    #[pyo3(signature = (p_ext=220.0))]
896    fn step(&mut self, p_ext: f64) -> f64 {
897        self.inner.step(p_ext)
898    }
899    fn reset(&mut self) {
900        self.inner.reset();
901    }
902}
903
904// LarterBreakspearNeuron: step returns f64
905#[pyclass(
906    name = "LarterBreakspearNeuron",
907    module = "sc_neurocore_engine.sc_neurocore_engine"
908)]
909#[derive(Clone)]
910pub struct PyLarterBreakspearNeuron {
911    inner: neurons::LarterBreakspearNeuron,
912}
913
914#[pymethods]
915impl PyLarterBreakspearNeuron {
916    #[new]
917    fn new() -> Self {
918        Self {
919            inner: neurons::LarterBreakspearNeuron::new(),
920        }
921    }
922    #[pyo3(signature = (coupling=0.0))]
923    fn step(&mut self, coupling: f64) -> f64 {
924        self.inner.step(coupling)
925    }
926    fn reset(&mut self) {
927        self.inner.reset();
928    }
929    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
930        let d = PyDict::new(py);
931        d.set_item("v", self.inner.v)?;
932        d.set_item("w", self.inner.w)?;
933        d.set_item("z", self.inner.z)?;
934        Ok(d.into_any().unbind())
935    }
936}
937
938// ═══════════════════════════════════════════════════════════════════
939// hardware.rs models
940// ═══════════════════════════════════════════════════════════════════
941
942#[pyclass(
943    name = "LoihiCUBANeuron",
944    module = "sc_neurocore_engine.sc_neurocore_engine"
945)]
946#[derive(Clone)]
947pub struct PyLoihiCUBANeuron {
948    inner: neurons::LoihiCUBANeuron,
949}
950
951#[pymethods]
952impl PyLoihiCUBANeuron {
953    #[new]
954    fn new() -> Self {
955        Self {
956            inner: neurons::LoihiCUBANeuron::new(),
957        }
958    }
959    fn step(&mut self, weighted_input: i32) -> i32 {
960        self.inner.step(weighted_input)
961    }
962    fn reset(&mut self) {
963        self.inner.reset();
964    }
965    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
966        let d = PyDict::new(py);
967        d.set_item("v", self.inner.v)?;
968        d.set_item("u", self.inner.u)?;
969        Ok(d.into_any().unbind())
970    }
971}
972
973#[pyclass(
974    name = "Loihi2Neuron",
975    module = "sc_neurocore_engine.sc_neurocore_engine"
976)]
977#[derive(Clone)]
978pub struct PyLoihi2Neuron {
979    inner: neurons::Loihi2Neuron,
980}
981
982#[pymethods]
983impl PyLoihi2Neuron {
984    #[new]
985    fn new() -> Self {
986        Self {
987            inner: neurons::Loihi2Neuron::new(),
988        }
989    }
990    fn step(&mut self, weighted_input: i32) -> i32 {
991        self.inner.step(weighted_input)
992    }
993    fn reset(&mut self) {
994        self.inner.reset();
995    }
996    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
997        let d = PyDict::new(py);
998        d.set_item("s1", self.inner.s1)?;
999        d.set_item("s2", self.inner.s2)?;
1000        d.set_item("s3", self.inner.s3)?;
1001        Ok(d.into_any().unbind())
1002    }
1003}
1004
1005#[pyclass(
1006    name = "TrueNorthNeuron",
1007    module = "sc_neurocore_engine.sc_neurocore_engine"
1008)]
1009#[derive(Clone)]
1010pub struct PyTrueNorthNeuron {
1011    inner: neurons::TrueNorthNeuron,
1012}
1013
1014#[pymethods]
1015impl PyTrueNorthNeuron {
1016    #[new]
1017    #[pyo3(signature = (threshold=100))]
1018    fn new(threshold: i32) -> Self {
1019        Self {
1020            inner: neurons::TrueNorthNeuron::new(threshold),
1021        }
1022    }
1023    fn step(&mut self, weighted_input: i32) -> i32 {
1024        self.inner.step(weighted_input)
1025    }
1026    fn reset(&mut self) {
1027        self.inner.reset();
1028    }
1029    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1030        let d = PyDict::new(py);
1031        d.set_item("v", self.inner.v)?;
1032        Ok(d.into_any().unbind())
1033    }
1034}
1035
1036py_neuron_default!("BrainScaleSAdExNeuron", PyBrainScaleSAdExNeuron, neurons::BrainScaleSAdExNeuron, state v, state w);
1037py_neuron_default!("SpiNNakerLIFNeuron", PySpiNNakerLIFNeuron, neurons::SpiNNakerLIFNeuron, state v, state refrac_count);
1038
1039#[pyclass(
1040    name = "SpiNNaker2Neuron",
1041    module = "sc_neurocore_engine.sc_neurocore_engine"
1042)]
1043#[derive(Clone)]
1044pub struct PySpiNNaker2Neuron {
1045    inner: neurons::SpiNNaker2Neuron,
1046}
1047
1048#[pymethods]
1049impl PySpiNNaker2Neuron {
1050    #[new]
1051    fn new() -> Self {
1052        Self {
1053            inner: neurons::SpiNNaker2Neuron::new(),
1054        }
1055    }
1056    fn step(&mut self, current: i32) -> i32 {
1057        self.inner.step(current)
1058    }
1059    fn reset(&mut self) {
1060        self.inner.reset();
1061    }
1062    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1063        let d = PyDict::new(py);
1064        d.set_item("v", self.inner.v)?;
1065        Ok(d.into_any().unbind())
1066    }
1067}
1068
1069py_neuron_default!("DPINeuron", PyDPINeuron, neurons::DPINeuron, state i_mem);
1070
1071#[pyclass(
1072    name = "AkidaNeuron",
1073    module = "sc_neurocore_engine.sc_neurocore_engine"
1074)]
1075#[derive(Clone)]
1076pub struct PyAkidaNeuron {
1077    inner: neurons::AkidaNeuron,
1078}
1079
1080#[pymethods]
1081impl PyAkidaNeuron {
1082    #[new]
1083    #[pyo3(signature = (threshold=100))]
1084    fn new(threshold: i32) -> Self {
1085        Self {
1086            inner: neurons::AkidaNeuron::new(threshold),
1087        }
1088    }
1089    fn step(&mut self, weight: i32) -> i32 {
1090        self.inner.step(weight)
1091    }
1092    fn reset(&mut self) {
1093        self.inner.reset();
1094    }
1095    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1096        let d = PyDict::new(py);
1097        d.set_item("v", self.inner.v)?;
1098        d.set_item("rank", self.inner.rank)?;
1099        Ok(d.into_any().unbind())
1100    }
1101}
1102
1103py_neuron_default!("NeuroGridNeuron", PyNeuroGridNeuron, neurons::NeuroGridNeuron, state v_s, state v_d);
1104
1105// ═══════════════════════════════════════════════════════════════════
1106// rate.rs models
1107// ═══════════════════════════════════════════════════════════════════
1108
1109#[pyclass(
1110    name = "McCullochPittsNeuron",
1111    module = "sc_neurocore_engine.sc_neurocore_engine"
1112)]
1113#[derive(Clone)]
1114pub struct PyMcCullochPittsNeuron {
1115    inner: neurons::McCullochPittsNeuron,
1116}
1117
1118#[pymethods]
1119impl PyMcCullochPittsNeuron {
1120    #[new]
1121    #[pyo3(signature = (theta=1.0))]
1122    fn new(theta: f64) -> Self {
1123        Self {
1124            inner: neurons::McCullochPittsNeuron::new(theta),
1125        }
1126    }
1127    fn step(&self, weighted_input: f64) -> i32 {
1128        self.inner.step(weighted_input)
1129    }
1130}
1131
1132// SigmoidRateNeuron: step returns f64
1133#[pyclass(
1134    name = "SigmoidRateNeuron",
1135    module = "sc_neurocore_engine.sc_neurocore_engine"
1136)]
1137#[derive(Clone)]
1138pub struct PySigmoidRateNeuron {
1139    inner: neurons::SigmoidRateNeuron,
1140}
1141
1142#[pymethods]
1143impl PySigmoidRateNeuron {
1144    #[new]
1145    fn new() -> Self {
1146        Self {
1147            inner: neurons::SigmoidRateNeuron::new(),
1148        }
1149    }
1150    fn step(&mut self, current: f64) -> f64 {
1151        self.inner.step(current)
1152    }
1153    fn reset(&mut self) {
1154        self.inner.reset();
1155    }
1156    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1157        let d = PyDict::new(py);
1158        d.set_item("r", self.inner.r)?;
1159        Ok(d.into_any().unbind())
1160    }
1161}
1162
1163// ThresholdLinearRateNeuron: step returns f64
1164#[pyclass(
1165    name = "ThresholdLinearRateNeuron",
1166    module = "sc_neurocore_engine.sc_neurocore_engine"
1167)]
1168#[derive(Clone)]
1169pub struct PyThresholdLinearRateNeuron {
1170    inner: neurons::ThresholdLinearRateNeuron,
1171}
1172
1173#[pymethods]
1174impl PyThresholdLinearRateNeuron {
1175    #[new]
1176    fn new() -> Self {
1177        Self {
1178            inner: neurons::ThresholdLinearRateNeuron::new(),
1179        }
1180    }
1181    fn step(&mut self, current: f64) -> f64 {
1182        self.inner.step(current)
1183    }
1184    fn reset(&mut self) {
1185        self.inner.reset();
1186    }
1187}
1188
1189// AstrocyteModel: step returns f64
1190#[pyclass(
1191    name = "AstrocyteModel",
1192    module = "sc_neurocore_engine.sc_neurocore_engine"
1193)]
1194#[derive(Clone)]
1195pub struct PyAstrocyteModel {
1196    inner: neurons::AstrocyteModel,
1197}
1198
1199#[pymethods]
1200impl PyAstrocyteModel {
1201    #[new]
1202    fn new() -> Self {
1203        Self {
1204            inner: neurons::AstrocyteModel::new(),
1205        }
1206    }
1207    fn step(&mut self, current: f64) -> f64 {
1208        self.inner.step(current)
1209    }
1210    fn reset(&mut self) {
1211        self.inner.reset();
1212    }
1213    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1214        let d = PyDict::new(py);
1215        d.set_item("ca", self.inner.ca)?;
1216        d.set_item("h", self.inner.h)?;
1217        d.set_item("ip3", self.inner.ip3)?;
1218        Ok(d.into_any().unbind())
1219    }
1220}
1221
1222// TsodyksMarkramNeuron: step(current, presynaptic_spike)
1223#[pyclass(
1224    name = "TsodyksMarkramNeuron",
1225    module = "sc_neurocore_engine.sc_neurocore_engine"
1226)]
1227#[derive(Clone)]
1228pub struct PyTsodyksMarkramNeuron {
1229    inner: neurons::TsodyksMarkramNeuron,
1230}
1231
1232#[pymethods]
1233impl PyTsodyksMarkramNeuron {
1234    #[new]
1235    fn new() -> Self {
1236        Self {
1237            inner: neurons::TsodyksMarkramNeuron::new(),
1238        }
1239    }
1240    #[pyo3(signature = (current, presynaptic_spike=false))]
1241    fn step(&mut self, current: f64, presynaptic_spike: bool) -> i32 {
1242        self.inner.step(current, presynaptic_spike)
1243    }
1244    fn reset(&mut self) {
1245        self.inner.reset();
1246    }
1247    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1248        let d = PyDict::new(py);
1249        d.set_item("v", self.inner.v)?;
1250        d.set_item("x", self.inner.x)?;
1251        d.set_item("u", self.inner.u)?;
1252        Ok(d.into_any().unbind())
1253    }
1254}
1255
1256py_neuron_default!("LiquidTimeConstantNeuron", PyLiquidTimeConstantNeuron, neurons::LiquidTimeConstantNeuron, state x);
1257
1258// CompteWMNeuron: step(current, spike_in)
1259#[pyclass(
1260    name = "CompteWMNeuron",
1261    module = "sc_neurocore_engine.sc_neurocore_engine"
1262)]
1263#[derive(Clone)]
1264pub struct PyCompteWMNeuron {
1265    inner: neurons::CompteWMNeuron,
1266}
1267
1268#[pymethods]
1269impl PyCompteWMNeuron {
1270    #[new]
1271    fn new() -> Self {
1272        Self {
1273            inner: neurons::CompteWMNeuron::new(),
1274        }
1275    }
1276    #[pyo3(signature = (current, spike_in=false))]
1277    fn step(&mut self, current: f64, spike_in: bool) -> i32 {
1278        self.inner.step(current, spike_in)
1279    }
1280    fn reset(&mut self) {
1281        self.inner.reset();
1282    }
1283    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1284        let d = PyDict::new(py);
1285        d.set_item("v", self.inner.v)?;
1286        d.set_item("s_nmda", self.inner.s_nmda)?;
1287        Ok(d.into_any().unbind())
1288    }
1289}
1290
1291// SiegertTransferFunction: step returns f64, no &mut self
1292#[pyclass(
1293    name = "SiegertTransferFunction",
1294    module = "sc_neurocore_engine.sc_neurocore_engine"
1295)]
1296#[derive(Clone)]
1297pub struct PySiegertTransferFunction {
1298    inner: neurons::SiegertTransferFunction,
1299}
1300
1301#[pymethods]
1302impl PySiegertTransferFunction {
1303    #[new]
1304    fn new() -> Self {
1305        Self {
1306            inner: neurons::SiegertTransferFunction::new(),
1307        }
1308    }
1309    fn step(&self, current: f64) -> f64 {
1310        self.inner.step(current)
1311    }
1312}
1313
1314// ═══════════════════════════════════════════════════════════════════
1315// rate.rs models requiring non-default constructors or Vec state
1316// ═══════════════════════════════════════════════════════════════════
1317
1318#[pyclass(
1319    name = "FractionalLIFNeuron",
1320    module = "sc_neurocore_engine.sc_neurocore_engine"
1321)]
1322#[derive(Clone)]
1323pub struct PyFractionalLIFNeuron {
1324    inner: neurons::FractionalLIFNeuron,
1325}
1326
1327#[pymethods]
1328impl PyFractionalLIFNeuron {
1329    #[new]
1330    #[pyo3(signature = (alpha=0.8, max_hist=50))]
1331    fn new(alpha: f64, max_hist: usize) -> Self {
1332        Self {
1333            inner: neurons::FractionalLIFNeuron::new(alpha, max_hist),
1334        }
1335    }
1336    fn step(&mut self, current: f64) -> i32 {
1337        self.inner.step(current)
1338    }
1339    fn reset(&mut self) {
1340        self.inner.reset();
1341    }
1342    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1343        let d = PyDict::new(py);
1344        d.set_item("v", self.inner.v)?;
1345        Ok(d.into_any().unbind())
1346    }
1347}
1348
1349#[pyclass(
1350    name = "ParallelSpikingNeuron",
1351    module = "sc_neurocore_engine.sc_neurocore_engine"
1352)]
1353#[derive(Clone)]
1354pub struct PyParallelSpikingNeuron {
1355    inner: neurons::ParallelSpikingNeuron,
1356}
1357
1358#[pymethods]
1359impl PyParallelSpikingNeuron {
1360    #[new]
1361    #[pyo3(signature = (kernel_size=8, v_threshold=1.0))]
1362    fn new(kernel_size: usize, v_threshold: f64) -> Self {
1363        Self {
1364            inner: neurons::ParallelSpikingNeuron::new(kernel_size, v_threshold),
1365        }
1366    }
1367    fn step(&mut self, current: f64) -> i32 {
1368        self.inner.step(current)
1369    }
1370    fn reset(&mut self) {
1371        self.inner.reset();
1372    }
1373}
1374
1375#[pyclass(
1376    name = "AmariNeuralField",
1377    module = "sc_neurocore_engine.sc_neurocore_engine"
1378)]
1379#[derive(Clone)]
1380pub struct PyAmariNeuralField {
1381    inner: neurons::AmariNeuralField,
1382}
1383
1384#[pymethods]
1385impl PyAmariNeuralField {
1386    #[new]
1387    #[pyo3(signature = (n=64))]
1388    fn new(n: usize) -> Self {
1389        Self {
1390            inner: neurons::AmariNeuralField::new(n),
1391        }
1392    }
1393    fn step(&mut self, input: Vec<f64>) -> f64 {
1394        self.inner.step(&input)
1395    }
1396    fn reset(&mut self) {
1397        self.inner.reset();
1398    }
1399    fn get_state<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1<f64>> {
1400        self.inner.u.clone().into_pyarray(py)
1401    }
1402}
1403
1404// ═══════════════════════════════════════════════════════════════════
1405// Registration function — call from lib.rs pymodule init
1406// ═══════════════════════════════════════════════════════════════════
1407
1408pub fn register_neuron_classes(m: &Bound<'_, PyModule>) -> PyResult<()> {
1409    // ai_optimized
1410    m.add_class::<PyMultiTimescaleNeuron>()?;
1411    m.add_class::<PyAttentionGatedNeuron>()?;
1412    m.add_class::<PyPredictiveCodingNeuron>()?;
1413    m.add_class::<PySelfReferentialNeuron>()?;
1414    m.add_class::<PyCompositionalBindingNeuron>()?;
1415    m.add_class::<PyDifferentiableSurrogateNeuron>()?;
1416    m.add_class::<PyContinuousAttractorNeuron>()?;
1417    m.add_class::<PyMetaPlasticNeuron>()?;
1418    // trivial
1419    m.add_class::<PyQuadraticIFNeuron>()?;
1420    m.add_class::<PyThetaNeuron>()?;
1421    m.add_class::<PyPerfectIntegratorNeuron>()?;
1422    m.add_class::<PyGatedLIFNeuron>()?;
1423    m.add_class::<PyNonlinearLIFNeuron>()?;
1424    m.add_class::<PySFANeuron>()?;
1425    m.add_class::<PyMATNeuron>()?;
1426    m.add_class::<PyEscapeRateNeuron>()?;
1427    m.add_class::<PyKLIFNeuron>()?;
1428    m.add_class::<PyInhibitoryLIFNeuron>()?;
1429    m.add_class::<PyComplementaryLIFNeuron>()?;
1430    m.add_class::<PyParametricLIFNeuron>()?;
1431    m.add_class::<PyNonResettingLIFNeuron>()?;
1432    m.add_class::<PyAdaptiveThresholdIFNeuron>()?;
1433    m.add_class::<PySigmaDeltaNeuron>()?;
1434    m.add_class::<PyEnergyLIFNeuron>()?;
1435    m.add_class::<PyIntegerQIFNeuron>()?;
1436    m.add_class::<PyClosedFormContinuousNeuron>()?;
1437    // simple_spiking
1438    m.add_class::<PyFitzHughNagumoNeuron>()?;
1439    m.add_class::<PyMorrisLecarNeuron>()?;
1440    m.add_class::<PyHindmarshRoseNeuron>()?;
1441    m.add_class::<PyResonateAndFireNeuron>()?;
1442    m.add_class::<PyFitzHughRinzelNeuron>()?;
1443    m.add_class::<PyMcKeanNeuron>()?;
1444    m.add_class::<PyTermanWangOscillator>()?;
1445    m.add_class::<PyBendaHerzNeuron>()?;
1446    m.add_class::<PyAlphaNeuron>()?;
1447    m.add_class::<PyCOBALIFNeuron>()?;
1448    m.add_class::<PyGutkinErmentroutNeuron>()?;
1449    m.add_class::<PyWilsonHRNeuron>()?;
1450    m.add_class::<PyChayNeuron>()?;
1451    m.add_class::<PyChayKeizerNeuron>()?;
1452    m.add_class::<PyShermanRinzelKeizerNeuron>()?;
1453    m.add_class::<PyButeraRespiratoryNeuron>()?;
1454    m.add_class::<PyEPropALIFNeuron>()?;
1455    m.add_class::<PySuperSpikeNeuron>()?;
1456    m.add_class::<PyLearnableNeuronModel>()?;
1457    m.add_class::<PyPernarowskiNeuron>()?;
1458    // maps
1459    m.add_class::<PyChialvoMapNeuron>()?;
1460    m.add_class::<PyRulkovMapNeuron>()?;
1461    m.add_class::<PyIbarzTanakaMapNeuron>()?;
1462    m.add_class::<PyMedvedevMapNeuron>()?;
1463    m.add_class::<PyCazellesMapNeuron>()?;
1464    m.add_class::<PyCourageNekorkinMapNeuron>()?;
1465    // biophysical
1466    m.add_class::<PyHodgkinHuxleyNeuron>()?;
1467    m.add_class::<PyTraubMilesNeuron>()?;
1468    m.add_class::<PyWangBuzsakiNeuron>()?;
1469    m.add_class::<PyConnorStevensNeuron>()?;
1470    m.add_class::<PyDestexheThalamicNeuron>()?;
1471    m.add_class::<PyHuberBraunNeuron>()?;
1472    m.add_class::<PyGolombFSNeuron>()?;
1473    m.add_class::<PyPospischilNeuron>()?;
1474    m.add_class::<PyMainenSejnowskiNeuron>()?;
1475    m.add_class::<PyDeSchutterPurkinjeNeuron>()?;
1476    m.add_class::<PyPlantR15Neuron>()?;
1477    m.add_class::<PyPrescottNeuron>()?;
1478    m.add_class::<PyMihalasNieburNeuron>()?;
1479    m.add_class::<PyGLIFNeuron>()?;
1480    m.add_class::<PyGIFPopulationNeuron>()?;
1481    m.add_class::<PyAvRonCardiacNeuron>()?;
1482    m.add_class::<PyDurstewitzDopamineNeuron>()?;
1483    m.add_class::<PyHillTononiNeuron>()?;
1484    m.add_class::<PyBertramPhantomBurster>()?;
1485    m.add_class::<PyYamadaNeuron>()?;
1486    // multi_compartment
1487    m.add_class::<PyPinskyRinzelNeuron>()?;
1488    m.add_class::<PyHayL5PyramidalNeuron>()?;
1489    m.add_class::<PyMarderSTGNeuron>()?;
1490    m.add_class::<PyRallCableNeuron>()?;
1491    m.add_class::<PyBoothRinzelNeuron>()?;
1492    m.add_class::<PyDendrifyNeuron>()?;
1493    m.add_class::<PyTwoCompartmentLIFNeuron>()?;
1494    // special
1495    m.add_class::<PyPoissonNeuron>()?;
1496    m.add_class::<PyInhomogeneousPoissonNeuron>()?;
1497    m.add_class::<PyGammaRenewalNeuron>()?;
1498    m.add_class::<PyStochasticIFNeuron>()?;
1499    m.add_class::<PyGalvesLocherbachNeuron>()?;
1500    m.add_class::<PySpikeResponseNeuron>()?;
1501    m.add_class::<PyGLMNeuron>()?;
1502    m.add_class::<PyWilsonCowanUnit>()?;
1503    m.add_class::<PyJansenRitUnit>()?;
1504    m.add_class::<PyWongWangUnit>()?;
1505    m.add_class::<PyErmentroutKopellPopulation>()?;
1506    m.add_class::<PyWendlingNeuron>()?;
1507    m.add_class::<PyLarterBreakspearNeuron>()?;
1508    // hardware
1509    m.add_class::<PyLoihiCUBANeuron>()?;
1510    m.add_class::<PyLoihi2Neuron>()?;
1511    m.add_class::<PyTrueNorthNeuron>()?;
1512    m.add_class::<PyBrainScaleSAdExNeuron>()?;
1513    m.add_class::<PySpiNNakerLIFNeuron>()?;
1514    m.add_class::<PySpiNNaker2Neuron>()?;
1515    m.add_class::<PyDPINeuron>()?;
1516    m.add_class::<PyAkidaNeuron>()?;
1517    m.add_class::<PyNeuroGridNeuron>()?;
1518    // rate
1519    m.add_class::<PyMcCullochPittsNeuron>()?;
1520    m.add_class::<PySigmoidRateNeuron>()?;
1521    m.add_class::<PyThresholdLinearRateNeuron>()?;
1522    m.add_class::<PyAstrocyteModel>()?;
1523    m.add_class::<PyTsodyksMarkramNeuron>()?;
1524    m.add_class::<PyLiquidTimeConstantNeuron>()?;
1525    m.add_class::<PyCompteWMNeuron>()?;
1526    m.add_class::<PySiegertTransferFunction>()?;
1527    m.add_class::<PyFractionalLIFNeuron>()?;
1528    m.add_class::<PyParallelSpikingNeuron>()?;
1529    m.add_class::<PyAmariNeuralField>()?;
1530    m.add_class::<PyLeakyCompeteFireNeuron>()?;
1531    m.add_class::<PyArcaneNeuron>()?;
1532    Ok(())
1533}
1534
1535// LeakyCompeteFireNeuron: step(Vec) -> Vec
1536#[pyclass(
1537    name = "LeakyCompeteFireNeuron",
1538    module = "sc_neurocore_engine.sc_neurocore_engine"
1539)]
1540#[derive(Clone)]
1541pub struct PyLeakyCompeteFireNeuron {
1542    inner: neurons::LeakyCompeteFireNeuron,
1543}
1544
1545#[pymethods]
1546impl PyLeakyCompeteFireNeuron {
1547    #[new]
1548    #[pyo3(signature = (n_units=4))]
1549    fn new(n_units: usize) -> Self {
1550        Self {
1551            inner: neurons::LeakyCompeteFireNeuron::new(n_units),
1552        }
1553    }
1554    fn step(&mut self, currents: Vec<f64>) -> Vec<i32> {
1555        self.inner.step(&currents)
1556    }
1557    fn reset(&mut self) {
1558        self.inner.reset();
1559    }
1560    fn get_state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
1561        let d = PyDict::new(py);
1562        d.set_item("v", self.inner.v.clone())?;
1563        Ok(d.into_any().unbind())
1564    }
1565}