1use rayon::prelude::*;
15
16use crate::neuron::*;
17use crate::neurons::*;
18
19macro_rules! wrap_2arg_f64 {
24 ($name:ident, $inner:ty, $v:ident, $extra:expr) => {
25 #[derive(Clone, Debug)]
26 pub struct $name(pub $inner);
27 impl $name {
28 pub fn new() -> Self {
29 Self(<$inner>::new())
30 }
31 pub fn step(&mut self, current: f64) -> i32 {
32 self.0.step(current, $extra)
33 }
34 pub fn reset(&mut self) {
35 self.0.reset();
36 }
37 pub fn v(&self) -> f64 {
38 self.0.$v as f64
39 }
40 }
41 impl Default for $name {
42 fn default() -> Self {
43 Self::new()
44 }
45 }
46 };
47}
48
49macro_rules! wrap_3arg {
51 ($name:ident, $inner:ty, $v:ident, $e2:expr, $e3:expr) => {
52 #[derive(Clone, Debug)]
53 pub struct $name(pub $inner);
54 impl $name {
55 pub fn new() -> Self {
56 Self(<$inner>::new())
57 }
58 pub fn step(&mut self, current: f64) -> i32 {
59 self.0.step(current, $e2, $e3)
60 }
61 pub fn reset(&mut self) {
62 self.0.reset();
63 }
64 pub fn v(&self) -> f64 {
65 self.0.$v as f64
66 }
67 }
68 impl Default for $name {
69 fn default() -> Self {
70 Self::new()
71 }
72 }
73 };
74}
75
76macro_rules! wrap_i32_input {
78 ($name:ident, $inner:ty, $v:ident, $ctor:expr) => {
79 #[derive(Clone, Debug)]
80 pub struct $name(pub $inner);
81 impl $name {
82 pub fn new() -> Self {
83 Self($ctor)
84 }
85 pub fn step(&mut self, current: f64) -> i32 {
86 self.0.step(current as i32)
87 }
88 pub fn reset(&mut self) {
89 self.0.reset();
90 }
91 pub fn v(&self) -> f64 {
92 self.0.$v as f64
93 }
94 }
95 impl Default for $name {
96 fn default() -> Self {
97 Self::new()
98 }
99 }
100 };
101}
102
103macro_rules! wrap_graded {
105 ($name:ident, $inner:ty, $v:ident, $threshold:expr) => {
106 #[derive(Clone, Debug)]
107 pub struct $name(pub $inner);
108 impl $name {
109 pub fn new() -> Self {
110 Self(<$inner>::new())
111 }
112 pub fn step(&mut self, current: f64) -> i32 {
113 let out = self.0.step(current);
114 if out > $threshold {
115 1
116 } else {
117 0
118 }
119 }
120 pub fn reset(&mut self) {
121 self.0.reset();
122 }
123 pub fn v(&self) -> f64 {
124 self.0.$v as f64
125 }
126 }
127 impl Default for $name {
128 fn default() -> Self {
129 Self::new()
130 }
131 }
132 };
133}
134
135wrap_2arg_f64!(WrAlpha, AlphaNeuron, v, 0.0_f64);
137wrap_3arg!(WrCOBALIF, COBALIFNeuron, v, 0.0_f64, 0.0_f64);
138wrap_2arg_f64!(WrCompteWM, CompteWMNeuron, v, false);
139wrap_2arg_f64!(WrTsodyksMarkram, TsodyksMarkramNeuron, v, false);
140wrap_2arg_f64!(WrPinskyRinzel, PinskyRinzelNeuron, v_s, 0.0_f64);
141wrap_2arg_f64!(WrHayL5, HayL5PyramidalNeuron, v_s, 0.0_f64);
142wrap_2arg_f64!(WrTwoCompLIF, TwoCompartmentLIFNeuron, v_s, 0.0_f64);
143
144wrap_i32_input!(WrLoihiCUBA, LoihiCUBANeuron, v, LoihiCUBANeuron::new());
146wrap_i32_input!(WrLoihi2, Loihi2Neuron, s1, Loihi2Neuron::new());
147wrap_i32_input!(WrSpiNNaker2, SpiNNaker2Neuron, v, SpiNNaker2Neuron::new());
148wrap_i32_input!(WrTrueNorth, TrueNorthNeuron, v, TrueNorthNeuron::new(256));
149wrap_i32_input!(
150 WrIntegerQIF,
151 IntegerQIFNeuron,
152 v,
153 IntegerQIFNeuron::new(1, 1000)
154);
155
156wrap_graded!(WrSigmoidRate, SigmoidRateNeuron, r, 0.5);
158wrap_graded!(WrThresholdLinear, ThresholdLinearRateNeuron, r, 0.5);
159wrap_graded!(WrAstrocyte, AstrocyteModel, ca, 0.1);
160wrap_graded!(WrInnerHairCell, InnerHairCell, v, 0.0);
161wrap_graded!(WrOuterHairCell, OuterHairCell, v, 0.0);
162wrap_graded!(WrRodPhotoreceptor, RodPhotoreceptor, v, 0.0);
163wrap_graded!(WrConePhotoreceptor, ConePhotoreceptor, v, 0.0);
164wrap_graded!(WrTasteReceptor, TasteReceptorCell, v, 0.0);
165
166#[allow(clippy::large_enum_variant)]
173pub enum NeuronVariant {
174 Izhikevich(Izhikevich),
176 AdEx(AdExNeuron),
177 ExpIF(ExpIfNeuron),
178 Lapicque(LapicqueNeuron),
179 HomeostaticLif(HomeostaticLif),
180
181 HodgkinHuxley(HodgkinHuxleyNeuron),
183 TraubMiles(TraubMilesNeuron),
184 WangBuzsaki(WangBuzsakiNeuron),
185 ConnorStevens(ConnorStevensNeuron),
186 DestexheThalamic(DestexheThalamicNeuron),
187 HuberBraun(HuberBraunNeuron),
188 GolombFS(GolombFSNeuron),
189 Pospischil(PospischilNeuron),
190 MainenSejnowski(MainenSejnowskiNeuron),
191 DeSchutterPurkinje(DeSchutterPurkinjeNeuron),
192 PlantR15(PlantR15Neuron),
193 Prescott(PrescottNeuron),
194 MihalasNiebur(MihalasNieburNeuron),
195 GLIF(GLIFNeuron),
196 GIFPopulation(GIFPopulationNeuron),
197 AvRonCardiac(AvRonCardiacNeuron),
198 DurstewitzDopamine(DurstewitzDopamineNeuron),
199 HillTononi(HillTononiNeuron),
200 BertramPhantom(BertramPhantomBurster),
201 Yamada(YamadaNeuron),
202
203 FitzHughNagumo(FitzHughNagumoNeuron),
205 MorrisLecar(MorrisLecarNeuron),
206 HindmarshRose(HindmarshRoseNeuron),
207 ResonateAndFire(ResonateAndFireNeuron),
208 BalancedResonateAndFire(BalancedResonateAndFireNeuron),
209 FitzHughRinzel(FitzHughRinzelNeuron),
210 McKean(McKeanNeuron),
211 TermanWang(TermanWangOscillator),
212 GutkinErmentrout(GutkinErmentroutNeuron),
213 WilsonHR(WilsonHRNeuron),
214 Chay(ChayNeuron),
215 ChayKeizer(ChayKeizerNeuron),
216 ShermanRinzelKeizer(ShermanRinzelKeizerNeuron),
217 ButeraRespiratory(ButeraRespiratoryNeuron),
218 EPropALIF(EPropALIFNeuron),
219 SuperSpike(SuperSpikeNeuron),
220 LearnableNeuron(LearnableNeuronModel),
221 Pernarowski(PernarowskiNeuron),
222
223 QuadraticIF(QuadraticIFNeuron),
225 Theta(ThetaNeuron),
226 PerfectIntegrator(PerfectIntegratorNeuron),
227 GatedLIF(GatedLIFNeuron),
228 NonlinearLIF(NonlinearLIFNeuron),
229 SFA(SFANeuron),
230 MAT(MATNeuron),
231 KLIF(KLIFNeuron),
232 InhibitoryLIF(InhibitoryLIFNeuron),
233 ComplementaryLIF(ComplementaryLIFNeuron),
234 ParametricLIF(ParametricLIFNeuron),
235 NonResettingLIF(NonResettingLIFNeuron),
236 AdaptiveThresholdIF(AdaptiveThresholdIFNeuron),
237 SigmaDelta(SigmaDeltaNeuron),
238 EnergyLIF(EnergyLIFNeuron),
239 ClosedFormContinuous(ClosedFormContinuousNeuron),
240
241 ChialvoMap(ChialvoMapNeuron),
243 RulkovMap(RulkovMapNeuron),
244 IbarzTanakaMap(IbarzTanakaMapNeuron),
245 MedvedevMap(MedvedevMapNeuron),
246 CazellesMap(CazellesMapNeuron),
247 CourageNekorkinMap(CourageNekorkinMapNeuron),
248 AiharaMap(AiharaMapNeuron),
249 KilincBhattMap(KilincBhattMapNeuron),
250 ErmentroutKopellMap(ErmentroutKopellMapNeuron),
251
252 BrainScaleSAdEx(BrainScaleSAdExNeuron),
254 SpiNNakerLIF(SpiNNakerLIFNeuron),
255 NeuroGrid(NeuroGridNeuron),
256 DPI(DPINeuron),
257 Akida(AkidaNeuron),
258 StochasticLIF(StochasticLIFNeuron),
259
260 MarderSTG(MarderSTGNeuron),
262 RallCable(RallCableNeuron),
263 BoothRinzel(BoothRinzelNeuron),
264 Dendrify(DendrifyNeuron),
265
266 LiquidTimeConstant(LiquidTimeConstantNeuron),
268 ParallelSpiking(ParallelSpikingNeuron),
269 FractionalLIF(FractionalLIFNeuron),
270
271 StochasticIF(StochasticIFNeuron),
273 GalvesLocherbach(GalvesLocherbachNeuron),
274 SpikeResponse(SpikeResponseNeuron),
275 GLM(GLMNeuron),
276 Arcane(ArcaneNeuron),
277
278 MultiTimescale(MultiTimescaleNeuron),
281 AttentionGated(AttentionGatedNeuron),
282 PredictiveCoding(PredictiveCodingNeuron),
283 SelfReferential(SelfReferentialNeuron),
284 CompositionalBinding(CompositionalBindingNeuron),
285 DifferentiableSurrogate(DifferentiableSurrogateNeuron),
286 ContinuousAttractor(ContinuousAttractorNeuron),
287 MetaPlastic(MetaPlasticNeuron),
288 BendaHerz(BendaHerzNeuron),
290 BrunelWang(BrunelWangNeuron),
291 Poisson(PoissonNeuron),
293 InhomogeneousPoisson(InhomogeneousPoissonNeuron),
294 GammaRenewal(GammaRenewalNeuron),
295 EscapeRate(EscapeRateNeuron),
296 PVFastSpiking(PVFastSpikingNeuron),
298 SST(SSTNeuron),
299 VIP(VIPNeuron),
300 Chandelier(ChandelierNeuron),
301 CerebellarBasket(CerebellarBasketNeuron),
302 Martinotti(MartinottiNeuron),
303
304 AlphaMotor(AlphaMotorNeuron),
306 GammaMotor(GammaMotorNeuron),
307 UpperMotor(UpperMotorNeuron),
308 Renshaw(RenshawCell),
309 MotorUnitCell(MotorUnit),
310
311 RetinalGanglion(RetinalGanglionCell),
313 Merkel(MerkelCell),
314 Pacinian(PacinianCorpuscle),
315 NociceptorCell(Nociceptor),
316 OlfactoryReceptor(OlfactoryReceptorNeuron),
317
318 Granule(GranuleCell),
320 Golgi(GolgiCell),
321 Stellate(StellateCell),
322 Lugaro(LugaroCell),
323 UnipolarBrush(UnipolarBrushCell),
324 DCN(DCNNeuron),
325
326 PersistentNa(PersistentNaNeuron),
328 Ih(IhNeuron),
329 TTypeCa(TTypeCaNeuron),
330 ATypeK(ATypeKNeuron),
331 BK(BKNeuron),
332 SK(SKNeuron),
333 NMDA(NMDANeuron),
334
335 MontbrioMPR(MontbrioMeanField),
337 Brunel(BrunelNetwork),
338 TUM(TUMNetwork),
339 ElBoustani(ElBoustaniNetwork),
340
341 GradedSynapse(GradedSynapseNeuron),
343 GapJunction(GapJunctionNeuron),
344 FHAxon(FrankenhaeUserHuxleyAxon),
345 NodeOfRanvier(NodeOfRanvier),
346 MyelinAxon(MyelinatedAxon),
347 CardiacPurkinje(CardiacPurkinjeFibre),
348 SmoothMuscle(SmoothMuscleCell),
349 BetaCell(EndocrineBetaCell),
350
351 WrAlphaCell(WrAlpha),
354 WrCOBALIFCell(WrCOBALIF),
355 WrCompteWMCell(WrCompteWM),
356 WrTsodyksMarkramCell(WrTsodyksMarkram),
357 WrPinskyRinzelCell(WrPinskyRinzel),
358 WrHayL5Cell(WrHayL5),
359 WrTwoCompLIFCell(WrTwoCompLIF),
360 WrLoihiCUBACell(WrLoihiCUBA),
362 WrLoihi2Cell(WrLoihi2),
363 WrSpiNNaker2Cell(WrSpiNNaker2),
364 WrTrueNorthCell(WrTrueNorth),
365 WrIntegerQIFCell(WrIntegerQIF),
366 WrSigmoidRateCell(WrSigmoidRate),
368 WrThresholdLinearCell(WrThresholdLinear),
369 WrAstrocyteCell(WrAstrocyte),
370 WrInnerHairCellCell(WrInnerHairCell),
371 WrOuterHairCellCell(WrOuterHairCell),
372 WrRodPhotoreceptorCell(WrRodPhotoreceptor),
373 WrConePhotoreceptorCell(WrConePhotoreceptor),
374 WrTasteReceptorCell(WrTasteReceptor),
375}
376
377macro_rules! dispatch_step {
378 ($self:expr, $current:expr,
379 $($variant:ident),* $(,)?) => {
380 match $self {
381 $(NeuronVariant::$variant(n) => n.step($current),)*
382 }
383 };
384}
385
386macro_rules! dispatch_reset {
387 ($self:expr,
388 $($variant:ident),* $(,)?) => {
389 match $self {
390 $(NeuronVariant::$variant(n) => n.reset(),)*
391 }
392 };
393}
394
395macro_rules! all_variants {
397 ($mac:ident, $($args:tt)*) => {
398 $mac!($($args)*
399 Izhikevich, AdEx, ExpIF, Lapicque, HomeostaticLif,
400 HodgkinHuxley, TraubMiles, WangBuzsaki, ConnorStevens,
401 DestexheThalamic, HuberBraun, GolombFS,
402 Pospischil, MainenSejnowski, DeSchutterPurkinje,
403 PlantR15, Prescott, MihalasNiebur, GLIF, GIFPopulation,
404 AvRonCardiac, DurstewitzDopamine, HillTononi, BertramPhantom, Yamada, Akida, StochasticLIF,
405 FitzHughNagumo, MorrisLecar, HindmarshRose, ResonateAndFire, BalancedResonateAndFire,
406 FitzHughRinzel, McKean, TermanWang, GutkinErmentrout, WilsonHR,
407 Chay, ChayKeizer, ShermanRinzelKeizer, ButeraRespiratory,
408 EPropALIF, SuperSpike, LearnableNeuron, Pernarowski,
409 QuadraticIF, Theta, PerfectIntegrator, GatedLIF, NonlinearLIF,
410 SFA, MAT, KLIF, InhibitoryLIF, ComplementaryLIF, ParametricLIF,
411 NonResettingLIF, AdaptiveThresholdIF, SigmaDelta, EnergyLIF,
412 ClosedFormContinuous,
413 ChialvoMap, RulkovMap, IbarzTanakaMap, MedvedevMap,
414 CazellesMap, CourageNekorkinMap, AiharaMap, KilincBhattMap, ErmentroutKopellMap,
415 BrainScaleSAdEx, SpiNNakerLIF, NeuroGrid, DPI,
416 MarderSTG, RallCable, BoothRinzel, Dendrify,
417 LiquidTimeConstant, ParallelSpiking, FractionalLIF,
418 StochasticIF, GalvesLocherbach, SpikeResponse, GLM,
419 Arcane,
420 MultiTimescale, AttentionGated, PredictiveCoding,
421 SelfReferential, CompositionalBinding, DifferentiableSurrogate,
422 ContinuousAttractor, MetaPlastic,
423 BendaHerz, BrunelWang,
424 Poisson, InhomogeneousPoisson, GammaRenewal, EscapeRate,
425 PVFastSpiking, SST, VIP, Chandelier, CerebellarBasket, Martinotti,
426 AlphaMotor, GammaMotor, UpperMotor, Renshaw, MotorUnitCell,
427 RetinalGanglion, Merkel, Pacinian, NociceptorCell, OlfactoryReceptor,
428 Granule, Golgi, Stellate, Lugaro, UnipolarBrush, DCN,
429 PersistentNa, Ih, TTypeCa, ATypeK, BK, SK, NMDA,
430 MontbrioMPR, Brunel, TUM, ElBoustani,
431 GradedSynapse, GapJunction, FHAxon, NodeOfRanvier, MyelinAxon, CardiacPurkinje,
432 SmoothMuscle, BetaCell,
433 WrAlphaCell, WrCOBALIFCell, WrCompteWMCell, WrTsodyksMarkramCell,
434 WrPinskyRinzelCell, WrHayL5Cell, WrTwoCompLIFCell,
435 WrLoihiCUBACell, WrLoihi2Cell, WrSpiNNaker2Cell, WrTrueNorthCell, WrIntegerQIFCell,
436 WrSigmoidRateCell, WrThresholdLinearCell, WrAstrocyteCell,
437 WrInnerHairCellCell, WrOuterHairCellCell,
438 WrRodPhotoreceptorCell, WrConePhotoreceptorCell, WrTasteReceptorCell,
439 )
440 };
441}
442
443impl NeuronVariant {
444 pub fn step(&mut self, current: f64) -> i32 {
445 all_variants!(dispatch_step, self, current,)
446 }
447
448 pub fn reset(&mut self) {
449 all_variants!(dispatch_reset, self,)
450 }
451
452 pub fn soma_voltage(&self) -> f64 {
453 match self {
454 NeuronVariant::Izhikevich(n) => n.v,
455 NeuronVariant::AdEx(n) => n.v,
456 NeuronVariant::ExpIF(n) => n.v,
457 NeuronVariant::Lapicque(n) => n.v,
458 NeuronVariant::HomeostaticLif(n) => n.v,
459 NeuronVariant::HodgkinHuxley(n) => n.v,
460 NeuronVariant::TraubMiles(n) => n.v,
461 NeuronVariant::WangBuzsaki(n) => n.v,
462 NeuronVariant::ConnorStevens(n) => n.v,
463 NeuronVariant::DestexheThalamic(n) => n.v,
464 NeuronVariant::HuberBraun(n) => n.v,
465 NeuronVariant::GolombFS(n) => n.v,
466 NeuronVariant::Pospischil(n) => n.v,
467 NeuronVariant::MainenSejnowski(n) => n.vs,
468 NeuronVariant::DeSchutterPurkinje(n) => n.v,
469 NeuronVariant::PlantR15(n) => n.v,
470 NeuronVariant::Prescott(n) => n.v,
471 NeuronVariant::MihalasNiebur(n) => n.v,
472 NeuronVariant::GLIF(n) => n.v,
473 NeuronVariant::GIFPopulation(n) => n.v,
474 NeuronVariant::AvRonCardiac(n) => n.v,
475 NeuronVariant::DurstewitzDopamine(n) => n.v,
476 NeuronVariant::HillTononi(n) => n.v,
477 NeuronVariant::BertramPhantom(n) => n.v,
478 NeuronVariant::Yamada(n) => n.v,
479 NeuronVariant::FitzHughNagumo(n) => n.v,
480 NeuronVariant::MorrisLecar(n) => n.v,
481 NeuronVariant::HindmarshRose(n) => n.x,
482 NeuronVariant::ResonateAndFire(n) => n.x,
483 NeuronVariant::BalancedResonateAndFire(n) => n.x,
484 NeuronVariant::FitzHughRinzel(n) => n.v,
485 NeuronVariant::McKean(n) => n.v,
486 NeuronVariant::TermanWang(n) => n.v,
487 NeuronVariant::GutkinErmentrout(n) => n.v,
488 NeuronVariant::WilsonHR(n) => n.v,
489 NeuronVariant::Chay(n) => n.v,
490 NeuronVariant::ChayKeizer(n) => n.v,
491 NeuronVariant::ShermanRinzelKeizer(n) => n.v,
492 NeuronVariant::ButeraRespiratory(n) => n.v,
493 NeuronVariant::EPropALIF(n) => n.v,
494 NeuronVariant::SuperSpike(n) => n.v,
495 NeuronVariant::LearnableNeuron(n) => n.v,
496 NeuronVariant::Pernarowski(n) => n.v,
497 NeuronVariant::QuadraticIF(n) => n.v,
498 NeuronVariant::Theta(n) => n.theta,
499 NeuronVariant::PerfectIntegrator(n) => n.v,
500 NeuronVariant::GatedLIF(n) => n.v,
501 NeuronVariant::NonlinearLIF(n) => n.v,
502 NeuronVariant::SFA(n) => n.v,
503 NeuronVariant::MAT(n) => n.v,
504 NeuronVariant::KLIF(n) => n.v,
505 NeuronVariant::InhibitoryLIF(n) => n.v,
506 NeuronVariant::ComplementaryLIF(n) => n.v_pos,
507 NeuronVariant::ParametricLIF(n) => n.v,
508 NeuronVariant::NonResettingLIF(n) => n.v,
509 NeuronVariant::AdaptiveThresholdIF(n) => n.v,
510 NeuronVariant::SigmaDelta(n) => n.sigma,
511 NeuronVariant::EnergyLIF(n) => n.v,
512 NeuronVariant::ClosedFormContinuous(n) => n.x,
513 NeuronVariant::ChialvoMap(n) => n.x,
514 NeuronVariant::RulkovMap(n) => n.x,
515 NeuronVariant::IbarzTanakaMap(n) => n.x,
516 NeuronVariant::MedvedevMap(n) => n.x,
517 NeuronVariant::CazellesMap(n) => n.x,
518 NeuronVariant::CourageNekorkinMap(n) => n.x,
519 NeuronVariant::AiharaMap(n) => n.x,
520 NeuronVariant::KilincBhattMap(n) => n.x,
521 NeuronVariant::ErmentroutKopellMap(n) => n.theta,
522 NeuronVariant::BrainScaleSAdEx(n) => n.v,
523 NeuronVariant::SpiNNakerLIF(n) => n.v,
524 NeuronVariant::NeuroGrid(n) => n.v_s,
525 NeuronVariant::DPI(n) => n.i_mem,
526 NeuronVariant::MarderSTG(n) => n.v,
527 NeuronVariant::RallCable(n) => n.v.first().copied().unwrap_or(0.0),
528 NeuronVariant::BoothRinzel(n) => n.vs,
529 NeuronVariant::Dendrify(n) => n.v_s,
530 NeuronVariant::LiquidTimeConstant(n) => n.x,
531 NeuronVariant::ParallelSpiking(_) => 0.0,
532 NeuronVariant::FractionalLIF(n) => n.v,
533 NeuronVariant::StochasticIF(n) => n.v,
534 NeuronVariant::GalvesLocherbach(n) => n.v,
535 NeuronVariant::SpikeResponse(n) => n.v,
536 NeuronVariant::GLM(n) => n.mu,
537 NeuronVariant::Arcane(n) => n.v_fast,
538 NeuronVariant::MultiTimescale(n) => n.v_fast,
540 NeuronVariant::AttentionGated(n) => n.v,
541 NeuronVariant::PredictiveCoding(n) => n.v,
542 NeuronVariant::SelfReferential(n) => n.v,
543 NeuronVariant::CompositionalBinding(_) => 0.0,
544 NeuronVariant::DifferentiableSurrogate(n) => n.v,
545 NeuronVariant::ContinuousAttractor(_) => 0.0,
546 NeuronVariant::MetaPlastic(n) => n.v,
547 NeuronVariant::BendaHerz(n) => n.a,
548 NeuronVariant::BrunelWang(n) => n.v,
549 NeuronVariant::Poisson(_) => 0.0,
550 NeuronVariant::InhomogeneousPoisson(_) => 0.0,
551 NeuronVariant::GammaRenewal(_) => 0.0,
552 NeuronVariant::EscapeRate(n) => n.v,
553 NeuronVariant::Akida(n) => n.v as f64,
554 NeuronVariant::StochasticLIF(n) => n.v,
555 NeuronVariant::PVFastSpiking(n) => n.v,
557 NeuronVariant::SST(n) => n.v,
558 NeuronVariant::VIP(n) => n.v,
559 NeuronVariant::Chandelier(n) => n.v,
560 NeuronVariant::CerebellarBasket(n) => n.v,
561 NeuronVariant::Martinotti(n) => n.v,
562 NeuronVariant::AlphaMotor(n) => n.v,
564 NeuronVariant::GammaMotor(n) => n.v,
565 NeuronVariant::UpperMotor(n) => n.v,
566 NeuronVariant::Renshaw(n) => n.v,
567 NeuronVariant::MotorUnitCell(n) => n.v,
568 NeuronVariant::RetinalGanglion(n) => n.baseline, NeuronVariant::Merkel(n) => n.v,
571 NeuronVariant::Pacinian(n) => n.v,
572 NeuronVariant::NociceptorCell(n) => n.v,
573 NeuronVariant::OlfactoryReceptor(n) => n.v,
574 NeuronVariant::Granule(n) => n.v,
576 NeuronVariant::Golgi(n) => n.v,
577 NeuronVariant::Stellate(n) => n.v,
578 NeuronVariant::Lugaro(n) => n.v,
579 NeuronVariant::UnipolarBrush(n) => n.v,
580 NeuronVariant::DCN(n) => n.v,
581 NeuronVariant::PersistentNa(n) => n.v,
583 NeuronVariant::Ih(n) => n.v,
584 NeuronVariant::TTypeCa(n) => n.v,
585 NeuronVariant::ATypeK(n) => n.v,
586 NeuronVariant::BK(n) => n.v,
587 NeuronVariant::SK(n) => n.v,
588 NeuronVariant::NMDA(n) => n.v,
589 NeuronVariant::MontbrioMPR(n) => n.v,
591 NeuronVariant::Brunel(n) => n.r_e,
592 NeuronVariant::TUM(n) => n.r,
593 NeuronVariant::ElBoustani(n) => n.r_e,
594 NeuronVariant::GradedSynapse(n) => n.v,
596 NeuronVariant::GapJunction(n) => n.v,
597 NeuronVariant::FHAxon(n) => n.v,
598 NeuronVariant::NodeOfRanvier(n) => n.v,
599 NeuronVariant::MyelinAxon(n) => n.v(),
600 NeuronVariant::CardiacPurkinje(n) => n.v,
601 NeuronVariant::SmoothMuscle(n) => n.v,
602 NeuronVariant::BetaCell(n) => n.v,
603 NeuronVariant::WrAlphaCell(n) => n.v(),
605 NeuronVariant::WrCOBALIFCell(n) => n.v(),
606 NeuronVariant::WrCompteWMCell(n) => n.v(),
607 NeuronVariant::WrTsodyksMarkramCell(n) => n.v(),
608 NeuronVariant::WrPinskyRinzelCell(n) => n.v(),
609 NeuronVariant::WrHayL5Cell(n) => n.v(),
610 NeuronVariant::WrTwoCompLIFCell(n) => n.v(),
611 NeuronVariant::WrLoihiCUBACell(n) => n.v(),
612 NeuronVariant::WrLoihi2Cell(n) => n.v(),
613 NeuronVariant::WrSpiNNaker2Cell(n) => n.v(),
614 NeuronVariant::WrTrueNorthCell(n) => n.v(),
615 NeuronVariant::WrIntegerQIFCell(n) => n.v(),
616 NeuronVariant::WrSigmoidRateCell(n) => n.v(),
617 NeuronVariant::WrThresholdLinearCell(n) => n.v(),
618 NeuronVariant::WrAstrocyteCell(n) => n.v(),
619 NeuronVariant::WrInnerHairCellCell(n) => n.v(),
620 NeuronVariant::WrOuterHairCellCell(n) => n.v(),
621 NeuronVariant::WrRodPhotoreceptorCell(n) => n.v(),
622 NeuronVariant::WrConePhotoreceptorCell(n) => n.v(),
623 NeuronVariant::WrTasteReceptorCell(n) => n.v(),
624 }
625 }
626}
627
628pub struct PopulationRunner {
631 neurons: Vec<NeuronVariant>,
632 spikes: Vec<u8>,
633 currents: Vec<f64>,
634}
635
636const CHUNK_SIZE: usize = 256;
637
638impl PopulationRunner {
639 pub fn new(neurons: Vec<NeuronVariant>) -> Self {
640 let n = neurons.len();
641 Self {
642 neurons,
643 spikes: vec![0u8; n],
644 currents: vec![0.0; n],
645 }
646 }
647
648 pub fn len(&self) -> usize {
649 self.neurons.len()
650 }
651
652 pub fn is_empty(&self) -> bool {
653 self.neurons.is_empty()
654 }
655
656 pub fn step_all(&mut self) {
657 let neurons = &mut self.neurons;
658 let spikes = &mut self.spikes;
659 let currents = &self.currents;
660
661 neurons
662 .par_chunks_mut(CHUNK_SIZE)
663 .zip(spikes.par_chunks_mut(CHUNK_SIZE))
664 .zip(currents.par_chunks(CHUNK_SIZE))
665 .for_each(|((n_chunk, s_chunk), c_chunk)| {
666 for i in 0..n_chunk.len() {
667 let fired = n_chunk[i].step(c_chunk[i]);
668 s_chunk[i] = if fired != 0 { 1 } else { 0 };
669 }
670 });
671 }
672
673 pub fn reset_all(&mut self) {
674 for n in &mut self.neurons {
675 n.reset();
676 }
677 self.spikes.fill(0);
678 self.currents.fill(0.0);
679 }
680
681 pub fn reset_currents(&mut self) {
682 self.currents.fill(0.0);
683 }
684
685 pub fn set_currents(&mut self, currents: &[f64]) -> Result<(), String> {
686 if currents.len() != self.currents.len() {
687 return Err(format!(
688 "current vector length mismatch: got {}, expected {}",
689 currents.len(),
690 self.currents.len()
691 ));
692 }
693 self.currents.copy_from_slice(currents);
694 Ok(())
695 }
696
697 pub fn collect_voltages(&self) -> Vec<f64> {
698 self.neurons.iter().map(|n| n.soma_voltage()).collect()
699 }
700}
701
702pub struct ProjectionRunner {
706 pub src_pop: usize,
707 pub tgt_pop: usize,
708 row_offsets: Vec<usize>,
709 col_indices: Vec<usize>,
710 values: Vec<f64>,
711 delay_steps: usize,
712 delay_buffer: Vec<Vec<u8>>,
713 buf_idx: usize,
714}
715
716impl ProjectionRunner {
717 pub fn new(
718 src_pop: usize,
719 tgt_pop: usize,
720 row_offsets: Vec<usize>,
721 col_indices: Vec<usize>,
722 values: Vec<f64>,
723 delay_steps: usize,
724 ) -> Self {
725 let n_delay = if delay_steps > 0 { delay_steps } else { 0 };
726 let n_src = if row_offsets.is_empty() {
727 0
728 } else {
729 row_offsets.len() - 1
730 };
731 let delay_buffer = if n_delay > 0 {
732 vec![vec![0u8; n_src]; n_delay]
733 } else {
734 Vec::new()
735 };
736 Self {
737 src_pop,
738 tgt_pop,
739 row_offsets,
740 col_indices,
741 values,
742 delay_steps: n_delay,
743 delay_buffer,
744 buf_idx: 0,
745 }
746 }
747
748 pub fn propagate(&mut self, src_spikes: &[u8], tgt_currents: &mut [f64]) {
750 let spikes = if self.delay_steps > 0 {
751 let delayed = &self.delay_buffer[self.buf_idx];
752 let out: Vec<u8> = delayed.clone();
753 self.delay_buffer[self.buf_idx] = src_spikes.to_vec();
754 self.buf_idx = (self.buf_idx + 1) % self.delay_steps;
755 out
756 } else {
757 src_spikes.to_vec()
758 };
759
760 let n_src = self.row_offsets.len().saturating_sub(1);
761 for i in 0..n_src {
762 if spikes.get(i).copied().unwrap_or(0) == 0 {
763 continue;
764 }
765 let start = self.row_offsets[i];
766 let end = self.row_offsets[i + 1];
767 for k in start..end {
768 let j = self.col_indices[k];
769 if j < tgt_currents.len() {
770 tgt_currents[j] += self.values[k];
771 }
772 }
773 }
774 }
775}
776
777pub struct SimResults {
780 pub spike_counts: Vec<usize>,
781 pub spike_data: Vec<Vec<u64>>,
784 pub voltages: Vec<Vec<f64>>,
785}
786
787pub struct NetworkRunner {
790 pub populations: Vec<PopulationRunner>,
791 pub projections: Vec<ProjectionRunner>,
792}
793
794impl NetworkRunner {
795 pub fn new() -> Self {
796 Self {
797 populations: Vec::new(),
798 projections: Vec::new(),
799 }
800 }
801
802 pub fn add_population(&mut self, pop: PopulationRunner) -> usize {
803 let idx = self.populations.len();
804 self.populations.push(pop);
805 idx
806 }
807
808 pub fn add_projection(&mut self, proj: ProjectionRunner) {
809 self.projections.push(proj);
810 }
811
812 pub fn step_population_with_currents(
813 &mut self,
814 pop_idx: usize,
815 currents: &[f64],
816 ) -> Result<(Vec<u8>, Vec<f64>), String> {
817 let pop = self
818 .populations
819 .get_mut(pop_idx)
820 .ok_or_else(|| format!("population index {pop_idx} out of range"))?;
821 pop.set_currents(currents)?;
822 pop.step_all();
823 Ok((pop.spikes.clone(), pop.collect_voltages()))
824 }
825
826 pub fn run(&mut self, n_steps: usize) -> SimResults {
827 let n_pops = self.populations.len();
828 let mut spike_counts = vec![0usize; n_pops];
829 let mut spike_data: Vec<Vec<u64>> = vec![Vec::new(); n_pops];
830
831 for t in 0..n_steps {
832 for pop in &mut self.populations {
834 pop.reset_currents();
835 }
836
837 for proj_idx in 0..self.projections.len() {
841 let src = self.projections[proj_idx].src_pop;
842 let tgt = self.projections[proj_idx].tgt_pop;
843 if src == tgt {
844 let spikes_copy = self.populations[src].spikes.clone();
846 let currents = &mut self.populations[tgt].currents;
847 self.projections[proj_idx].propagate(&spikes_copy, currents);
848 } else {
849 let pops_ptr = self.populations.as_mut_ptr();
851 let src_spikes = unsafe { &(*pops_ptr.add(src)).spikes };
852 let tgt_currents = unsafe { &mut (*pops_ptr.add(tgt)).currents };
853 self.projections[proj_idx].propagate(src_spikes, tgt_currents);
854 }
855 }
856
857 for (pop_idx, pop) in self.populations.iter_mut().enumerate() {
859 pop.step_all();
860 for (nid, &spike) in pop.spikes.iter().enumerate() {
861 if spike != 0 {
862 spike_counts[pop_idx] += 1;
863 spike_data[pop_idx].push(((nid as u64) << 32) | (t as u64));
864 }
865 }
866 }
867 }
868
869 let voltages: Vec<Vec<f64>> = self
870 .populations
871 .iter()
872 .map(|p| p.collect_voltages())
873 .collect();
874
875 SimResults {
876 spike_counts,
877 spike_data,
878 voltages,
879 }
880 }
881}
882
883impl Default for NetworkRunner {
884 fn default() -> Self {
885 Self::new()
886 }
887}
888
889pub fn create_population(model_name: &str, n: usize) -> Result<PopulationRunner, String> {
893 let neurons: Vec<NeuronVariant> = (0..n)
894 .map(|_| create_neuron(model_name))
895 .collect::<Result<_, _>>()?;
896 Ok(PopulationRunner::new(neurons))
897}
898
899pub fn create_neuron(name: &str) -> Result<NeuronVariant, String> {
900 match name {
901 "Izhikevich" => Ok(NeuronVariant::Izhikevich(Izhikevich::regular_spiking())),
902 "AdEx" | "AdExNeuron" => Ok(NeuronVariant::AdEx(AdExNeuron::new())),
903 "ExpIF" | "ExpIfNeuron" => Ok(NeuronVariant::ExpIF(ExpIfNeuron::new())),
904 "Lapicque" | "LapicqueNeuron" => Ok(NeuronVariant::Lapicque(LapicqueNeuron::new(
905 20.0, 1.0, 1.0, 1.0,
906 ))),
907 "HomeostaticLif" => Ok(NeuronVariant::HomeostaticLif(
908 HomeostaticLif::with_defaults(),
909 )),
910 "HodgkinHuxley" | "HodgkinHuxleyNeuron" => {
911 Ok(NeuronVariant::HodgkinHuxley(HodgkinHuxleyNeuron::new()))
912 }
913 "TraubMiles" | "TraubMilesNeuron" => Ok(NeuronVariant::TraubMiles(TraubMilesNeuron::new())),
914 "WangBuzsaki" | "WangBuzsakiNeuron" => {
915 Ok(NeuronVariant::WangBuzsaki(WangBuzsakiNeuron::new()))
916 }
917 "ConnorStevens" | "ConnorStevensNeuron" => {
918 Ok(NeuronVariant::ConnorStevens(ConnorStevensNeuron::new()))
919 }
920 "DestexheThalamic" | "DestexheThalamicNeuron" => Ok(NeuronVariant::DestexheThalamic(
921 DestexheThalamicNeuron::new(),
922 )),
923 "HuberBraun" | "HuberBraunNeuron" => Ok(NeuronVariant::HuberBraun(HuberBraunNeuron::new())),
924 "GolombFS" | "GolombFSNeuron" => Ok(NeuronVariant::GolombFS(GolombFSNeuron::new())),
925 "Pospischil" | "PospischilNeuron" => Ok(NeuronVariant::Pospischil(PospischilNeuron::new())),
926 "MainenSejnowski" | "MainenSejnowskiNeuron" => {
927 Ok(NeuronVariant::MainenSejnowski(MainenSejnowskiNeuron::new()))
928 }
929 "DeSchutterPurkinje" | "DeSchutterPurkinjeNeuron" => Ok(NeuronVariant::DeSchutterPurkinje(
930 DeSchutterPurkinjeNeuron::new(),
931 )),
932 "PlantR15" | "PlantR15Neuron" => Ok(NeuronVariant::PlantR15(PlantR15Neuron::new())),
933 "Prescott" | "PrescottNeuron" => Ok(NeuronVariant::Prescott(PrescottNeuron::new())),
934 "MihalasNiebur" | "MihalasNieburNeuron" => {
935 Ok(NeuronVariant::MihalasNiebur(MihalasNieburNeuron::new()))
936 }
937 "GLIF" | "GLIFNeuron" => Ok(NeuronVariant::GLIF(GLIFNeuron::new())),
938 "GIFPopulation" | "GIFPopulationNeuron" => {
939 Ok(NeuronVariant::GIFPopulation(GIFPopulationNeuron::new(42)))
940 }
941 "AvRonCardiac" | "AvRonCardiacNeuron" => {
942 Ok(NeuronVariant::AvRonCardiac(AvRonCardiacNeuron::new()))
943 }
944 "DurstewitzDopamine" | "DurstewitzDopamineNeuron" => Ok(NeuronVariant::DurstewitzDopamine(
945 DurstewitzDopamineNeuron::new(),
946 )),
947 "HillTononi" | "HillTononiNeuron" => Ok(NeuronVariant::HillTononi(HillTononiNeuron::new())),
948 "BertramPhantom" | "BertramPhantomBurster" => {
949 Ok(NeuronVariant::BertramPhantom(BertramPhantomBurster::new()))
950 }
951 "Yamada" | "YamadaNeuron" => Ok(NeuronVariant::Yamada(YamadaNeuron::new())),
952 "FitzHughNagumo" | "FitzHughNagumoNeuron" => {
953 Ok(NeuronVariant::FitzHughNagumo(FitzHughNagumoNeuron::new()))
954 }
955 "MorrisLecar" | "MorrisLecarNeuron" => {
956 Ok(NeuronVariant::MorrisLecar(MorrisLecarNeuron::new()))
957 }
958 "HindmarshRose" | "HindmarshRoseNeuron" => {
959 Ok(NeuronVariant::HindmarshRose(HindmarshRoseNeuron::new()))
960 }
961 "ResonateAndFire" | "ResonateAndFireNeuron" => {
962 Ok(NeuronVariant::ResonateAndFire(ResonateAndFireNeuron::new()))
963 }
964 "BalancedResonateAndFire" | "BalancedResonateAndFireNeuron" => Ok(
965 NeuronVariant::BalancedResonateAndFire(BalancedResonateAndFireNeuron::new()),
966 ),
967 "FitzHughRinzel" | "FitzHughRinzelNeuron" => {
968 Ok(NeuronVariant::FitzHughRinzel(FitzHughRinzelNeuron::new()))
969 }
970 "McKean" | "McKeanNeuron" => Ok(NeuronVariant::McKean(McKeanNeuron::new())),
971 "TermanWang" | "TermanWangOscillator" => {
972 Ok(NeuronVariant::TermanWang(TermanWangOscillator::new()))
973 }
974 "GutkinErmentrout" | "GutkinErmentroutNeuron" => Ok(NeuronVariant::GutkinErmentrout(
975 GutkinErmentroutNeuron::new(),
976 )),
977 "WilsonHR" | "WilsonHRNeuron" => Ok(NeuronVariant::WilsonHR(WilsonHRNeuron::new())),
978 "Chay" | "ChayNeuron" => Ok(NeuronVariant::Chay(ChayNeuron::new())),
979 "ChayKeizer" | "ChayKeizerNeuron" => Ok(NeuronVariant::ChayKeizer(ChayKeizerNeuron::new())),
980 "ShermanRinzelKeizer" | "ShermanRinzelKeizerNeuron" => Ok(
981 NeuronVariant::ShermanRinzelKeizer(ShermanRinzelKeizerNeuron::new()),
982 ),
983 "ButeraRespiratory" | "ButeraRespiratoryNeuron" => Ok(NeuronVariant::ButeraRespiratory(
984 ButeraRespiratoryNeuron::new(),
985 )),
986 "EPropALIF" | "EPropALIFNeuron" => Ok(NeuronVariant::EPropALIF(EPropALIFNeuron::default())),
987 "SuperSpike" | "SuperSpikeNeuron" => {
988 Ok(NeuronVariant::SuperSpike(SuperSpikeNeuron::default()))
989 }
990 "LearnableNeuron" | "LearnableNeuronModel" => {
991 Ok(NeuronVariant::LearnableNeuron(LearnableNeuronModel::new()))
992 }
993 "Pernarowski" | "PernarowskiNeuron" => {
994 Ok(NeuronVariant::Pernarowski(PernarowskiNeuron::new()))
995 }
996 "QuadraticIF" | "QuadraticIFNeuron" => {
997 Ok(NeuronVariant::QuadraticIF(QuadraticIFNeuron::default()))
998 }
999 "Theta" | "ThetaNeuron" => Ok(NeuronVariant::Theta(ThetaNeuron::default())),
1000 "PerfectIntegrator" | "PerfectIntegratorNeuron" => Ok(NeuronVariant::PerfectIntegrator(
1001 PerfectIntegratorNeuron::default(),
1002 )),
1003 "GatedLIF" | "GatedLIFNeuron" => Ok(NeuronVariant::GatedLIF(GatedLIFNeuron::default())),
1004 "NonlinearLIF" | "NonlinearLIFNeuron" => {
1005 Ok(NeuronVariant::NonlinearLIF(NonlinearLIFNeuron::new()))
1006 }
1007 "SFA" | "SFANeuron" => Ok(NeuronVariant::SFA(SFANeuron::new())),
1008 "MAT" | "MATNeuron" => Ok(NeuronVariant::MAT(MATNeuron::new())),
1009 "KLIF" | "KLIFNeuron" => Ok(NeuronVariant::KLIF(KLIFNeuron::default())),
1010 "InhibitoryLIF" | "InhibitoryLIFNeuron" => {
1011 Ok(NeuronVariant::InhibitoryLIF(InhibitoryLIFNeuron::default()))
1012 }
1013 "ComplementaryLIF" | "ComplementaryLIFNeuron" => Ok(NeuronVariant::ComplementaryLIF(
1014 ComplementaryLIFNeuron::default(),
1015 )),
1016 "ParametricLIF" | "ParametricLIFNeuron" => {
1017 Ok(NeuronVariant::ParametricLIF(ParametricLIFNeuron::default()))
1018 }
1019 "NonResettingLIF" | "NonResettingLIFNeuron" => {
1020 Ok(NeuronVariant::NonResettingLIF(NonResettingLIFNeuron::new()))
1021 }
1022 "AdaptiveThresholdIF" | "AdaptiveThresholdIFNeuron" => Ok(
1023 NeuronVariant::AdaptiveThresholdIF(AdaptiveThresholdIFNeuron::new()),
1024 ),
1025 "SigmaDelta" | "SigmaDeltaNeuron" => {
1026 Ok(NeuronVariant::SigmaDelta(SigmaDeltaNeuron::default()))
1027 }
1028 "EnergyLIF" | "EnergyLIFNeuron" => Ok(NeuronVariant::EnergyLIF(EnergyLIFNeuron::new())),
1029 "ClosedFormContinuous" | "ClosedFormContinuousNeuron" => Ok(
1030 NeuronVariant::ClosedFormContinuous(ClosedFormContinuousNeuron::new()),
1031 ),
1032 "ChialvoMap" | "ChialvoMapNeuron" => Ok(NeuronVariant::ChialvoMap(ChialvoMapNeuron::new())),
1033 "RulkovMap" | "RulkovMapNeuron" => Ok(NeuronVariant::RulkovMap(RulkovMapNeuron::new())),
1034 "IbarzTanakaMap" | "IbarzTanakaMapNeuron" => {
1035 Ok(NeuronVariant::IbarzTanakaMap(IbarzTanakaMapNeuron::new()))
1036 }
1037 "MedvedevMap" | "MedvedevMapNeuron" => {
1038 Ok(NeuronVariant::MedvedevMap(MedvedevMapNeuron::default()))
1039 }
1040 "CazellesMap" | "CazellesMapNeuron" => {
1041 Ok(NeuronVariant::CazellesMap(CazellesMapNeuron::new()))
1042 }
1043 "CourageNekorkinMap" | "CourageNekorkinMapNeuron" => Ok(NeuronVariant::CourageNekorkinMap(
1044 CourageNekorkinMapNeuron::new(),
1045 )),
1046 "AiharaMap" | "AiharaMapNeuron" => Ok(NeuronVariant::AiharaMap(AiharaMapNeuron::new())),
1047 "KilincBhattMap" | "KilincBhattMapNeuron" => {
1048 Ok(NeuronVariant::KilincBhattMap(KilincBhattMapNeuron::new()))
1049 }
1050 "ErmentroutKopellMap" | "ErmentroutKopellMapNeuron" => Ok(
1051 NeuronVariant::ErmentroutKopellMap(ErmentroutKopellMapNeuron::new()),
1052 ),
1053 "BrainScaleSAdEx" | "BrainScaleSAdExNeuron" => {
1054 Ok(NeuronVariant::BrainScaleSAdEx(BrainScaleSAdExNeuron::new()))
1055 }
1056 "SpiNNakerLIF" | "SpiNNakerLIFNeuron" => {
1057 Ok(NeuronVariant::SpiNNakerLIF(SpiNNakerLIFNeuron::new()))
1058 }
1059 "NeuroGrid" | "NeuroGridNeuron" => Ok(NeuronVariant::NeuroGrid(NeuroGridNeuron::new())),
1060 "DPI" | "DPINeuron" => Ok(NeuronVariant::DPI(DPINeuron::new())),
1061 "MarderSTG" | "MarderSTGNeuron" => Ok(NeuronVariant::MarderSTG(MarderSTGNeuron::new())),
1062 "RallCable" | "RallCableNeuron" => Ok(NeuronVariant::RallCable(RallCableNeuron::new(5))),
1063 "BoothRinzel" | "BoothRinzelNeuron" => {
1064 Ok(NeuronVariant::BoothRinzel(BoothRinzelNeuron::new()))
1065 }
1066 "Dendrify" | "DendrifyNeuron" => Ok(NeuronVariant::Dendrify(DendrifyNeuron::new())),
1067 "Akida" | "AkidaNeuron" => Ok(NeuronVariant::Akida(AkidaNeuron::new(100))),
1068 "StochasticLIF" | "StochasticLIFNeuron" => {
1069 Ok(NeuronVariant::StochasticLIF(StochasticLIFNeuron::new(42)))
1070 }
1071 "LiquidTimeConstant" | "LiquidTimeConstantNeuron" => Ok(NeuronVariant::LiquidTimeConstant(
1072 LiquidTimeConstantNeuron::new(),
1073 )),
1074 "ParallelSpiking" | "ParallelSpikingNeuron" => Ok(NeuronVariant::ParallelSpiking(
1075 ParallelSpikingNeuron::new(4, 0.5),
1076 )),
1077 "FractionalLIF" | "FractionalLIFNeuron" => Ok(NeuronVariant::FractionalLIF(
1078 FractionalLIFNeuron::new(0.8, 50),
1079 )),
1080 "StochasticIF" | "StochasticIFNeuron" => {
1081 Ok(NeuronVariant::StochasticIF(StochasticIFNeuron::new(42)))
1082 }
1083 "GalvesLocherbach" | "GalvesLocherbachNeuron" => Ok(NeuronVariant::GalvesLocherbach(
1084 GalvesLocherbachNeuron::new(42),
1085 )),
1086 "SpikeResponse" | "SpikeResponseNeuron" => {
1087 Ok(NeuronVariant::SpikeResponse(SpikeResponseNeuron::new()))
1088 }
1089 "GLM" | "GLMNeuron" => Ok(NeuronVariant::GLM(GLMNeuron::new(5, 10, 42))),
1090 "Arcane" | "ArcaneNeuron" => Ok(NeuronVariant::Arcane(ArcaneNeuron::new())),
1091 "MultiTimescale" | "MultiTimescaleNeuron" => {
1093 Ok(NeuronVariant::MultiTimescale(MultiTimescaleNeuron::new()))
1094 }
1095 "AttentionGated" | "AttentionGatedNeuron" => {
1096 Ok(NeuronVariant::AttentionGated(AttentionGatedNeuron::new()))
1097 }
1098 "PredictiveCoding" | "PredictiveCodingNeuron" => Ok(NeuronVariant::PredictiveCoding(
1099 PredictiveCodingNeuron::new(),
1100 )),
1101 "SelfReferential" | "SelfReferentialNeuron" => {
1102 Ok(NeuronVariant::SelfReferential(SelfReferentialNeuron::new()))
1103 }
1104 "CompositionalBinding" | "CompositionalBindingNeuron" => Ok(
1105 NeuronVariant::CompositionalBinding(CompositionalBindingNeuron::new()),
1106 ),
1107 "DifferentiableSurrogate" | "DifferentiableSurrogateNeuron" => Ok(
1108 NeuronVariant::DifferentiableSurrogate(DifferentiableSurrogateNeuron::new()),
1109 ),
1110 "ContinuousAttractor" | "ContinuousAttractorNeuron" => Ok(
1111 NeuronVariant::ContinuousAttractor(ContinuousAttractorNeuron::new(8)),
1112 ),
1113 "MetaPlastic" | "MetaPlasticNeuron" => {
1114 Ok(NeuronVariant::MetaPlastic(MetaPlasticNeuron::new()))
1115 }
1116 "BendaHerz" | "BendaHerzNeuron" => Ok(NeuronVariant::BendaHerz(BendaHerzNeuron::new(42))),
1117 "BrunelWang" | "BrunelWangNeuron" => Ok(NeuronVariant::BrunelWang(BrunelWangNeuron::new())),
1118 "Poisson" | "PoissonNeuron" => {
1119 Ok(NeuronVariant::Poisson(PoissonNeuron::new(50.0, 1.0, 42)))
1120 }
1121 "InhomogeneousPoisson" | "InhomogeneousPoissonNeuron" => Ok(
1122 NeuronVariant::InhomogeneousPoisson(InhomogeneousPoissonNeuron::new(1.0, 42)),
1123 ),
1124 "GammaRenewal" | "GammaRenewalNeuron" => Ok(NeuronVariant::GammaRenewal(
1125 GammaRenewalNeuron::new(50.0, 3, 42),
1126 )),
1127 "EscapeRate" | "EscapeRateNeuron" => {
1128 Ok(NeuronVariant::EscapeRate(EscapeRateNeuron::new(42)))
1129 }
1130 "PVFastSpiking" | "PVFastSpikingNeuron" => {
1132 Ok(NeuronVariant::PVFastSpiking(PVFastSpikingNeuron::new()))
1133 }
1134 "SST" | "SSTNeuron" => Ok(NeuronVariant::SST(SSTNeuron::new())),
1135 "VIP" | "VIPNeuron" => Ok(NeuronVariant::VIP(VIPNeuron::new())),
1136 "Chandelier" | "ChandelierNeuron" => Ok(NeuronVariant::Chandelier(ChandelierNeuron::new())),
1137 "CerebellarBasket" | "CerebellarBasketNeuron" => Ok(NeuronVariant::CerebellarBasket(
1138 CerebellarBasketNeuron::new(),
1139 )),
1140 "Martinotti" | "MartinottiNeuron" => Ok(NeuronVariant::Martinotti(MartinottiNeuron::new())),
1141 "AlphaMotor" | "AlphaMotorNeuron" => Ok(NeuronVariant::AlphaMotor(AlphaMotorNeuron::new())),
1143 "GammaMotor" | "GammaMotorNeuron" => Ok(NeuronVariant::GammaMotor(GammaMotorNeuron::new())),
1144 "UpperMotor" | "UpperMotorNeuron" => Ok(NeuronVariant::UpperMotor(UpperMotorNeuron::new())),
1145 "Renshaw" | "RenshawCell" => Ok(NeuronVariant::Renshaw(RenshawCell::new())),
1146 "MotorUnit" => Ok(NeuronVariant::MotorUnitCell(MotorUnit::new())),
1147 "RetinalGanglion" | "RetinalGanglionCell" => {
1149 Ok(NeuronVariant::RetinalGanglion(RetinalGanglionCell::new()))
1150 }
1151 "Merkel" | "MerkelCell" => Ok(NeuronVariant::Merkel(MerkelCell::new())),
1152 "Pacinian" | "PacinianCorpuscle" => Ok(NeuronVariant::Pacinian(PacinianCorpuscle::new())),
1153 "Nociceptor" => Ok(NeuronVariant::NociceptorCell(Nociceptor::new())),
1154 "OlfactoryReceptor" | "OlfactoryReceptorNeuron" => Ok(NeuronVariant::OlfactoryReceptor(
1155 OlfactoryReceptorNeuron::new(),
1156 )),
1157 "GranuleCell" | "Granule" => Ok(NeuronVariant::Granule(GranuleCell::new())),
1159 "GolgiCell" | "Golgi" => Ok(NeuronVariant::Golgi(GolgiCell::new())),
1160 "StellateCell" | "Stellate" => Ok(NeuronVariant::Stellate(StellateCell::new())),
1161 "LugaroCell" | "Lugaro" => Ok(NeuronVariant::Lugaro(LugaroCell::new())),
1162 "UnipolarBrushCell" | "UBC" => Ok(NeuronVariant::UnipolarBrush(UnipolarBrushCell::new())),
1163 "DCNNeuron" | "DCN" => Ok(NeuronVariant::DCN(DCNNeuron::new())),
1164 "PersistentNa" | "PersistentNaNeuron" => {
1166 Ok(NeuronVariant::PersistentNa(PersistentNaNeuron::new()))
1167 }
1168 "Ih" | "IhNeuron" => Ok(NeuronVariant::Ih(IhNeuron::new())),
1169 "TTypeCa" | "TTypeCaNeuron" => Ok(NeuronVariant::TTypeCa(TTypeCaNeuron::new())),
1170 "ATypeK" | "ATypeKNeuron" => Ok(NeuronVariant::ATypeK(ATypeKNeuron::new())),
1171 "BK" | "BKNeuron" => Ok(NeuronVariant::BK(BKNeuron::new())),
1172 "SK" | "SKNeuron" => Ok(NeuronVariant::SK(SKNeuron::new())),
1173 "NMDA" | "NMDANeuron" => Ok(NeuronVariant::NMDA(NMDANeuron::new())),
1174 "MontbrioMeanField" | "MPR" => Ok(NeuronVariant::MontbrioMPR(MontbrioMeanField::new())),
1176 "BrunelNetwork" | "Brunel" => Ok(NeuronVariant::Brunel(BrunelNetwork::new())),
1177 "TUMNetwork" | "TUM" => Ok(NeuronVariant::TUM(TUMNetwork::new())),
1178 "ElBoustaniNetwork" | "ElBoustani" => {
1179 Ok(NeuronVariant::ElBoustani(ElBoustaniNetwork::new()))
1180 }
1181 "GradedSynapseNeuron" | "GradedSynapse" => {
1183 Ok(NeuronVariant::GradedSynapse(GradedSynapseNeuron::new()))
1184 }
1185 "GapJunctionNeuron" | "GapJunction" => {
1186 Ok(NeuronVariant::GapJunction(GapJunctionNeuron::new()))
1187 }
1188 "FrankenhaeUserHuxleyAxon" | "FHAxon" => {
1189 Ok(NeuronVariant::FHAxon(FrankenhaeUserHuxleyAxon::new()))
1190 }
1191 "NodeOfRanvier" => Ok(NeuronVariant::NodeOfRanvier(NodeOfRanvier::new())),
1192 "MyelinatedAxon" | "MyelinAxon" => Ok(NeuronVariant::MyelinAxon(MyelinatedAxon::new())),
1193 "CardiacPurkinjeFibre" | "CardiacPurkinje" => {
1194 Ok(NeuronVariant::CardiacPurkinje(CardiacPurkinjeFibre::new()))
1195 }
1196 "SmoothMuscleCell" | "SmoothMuscle" => {
1197 Ok(NeuronVariant::SmoothMuscle(SmoothMuscleCell::new()))
1198 }
1199 "EndocrineBetaCell" | "BetaCell" => Ok(NeuronVariant::BetaCell(EndocrineBetaCell::new())),
1200 "AlphaNeuron" | "Alpha" => Ok(NeuronVariant::WrAlphaCell(WrAlpha::new())),
1202 "COBALIFNeuron" | "COBALIF" => Ok(NeuronVariant::WrCOBALIFCell(WrCOBALIF::new())),
1203 "CompteWMNeuron" | "CompteWM" => Ok(NeuronVariant::WrCompteWMCell(WrCompteWM::new())),
1204 "TsodyksMarkramNeuron" | "TsodyksMarkram" => {
1205 Ok(NeuronVariant::WrTsodyksMarkramCell(WrTsodyksMarkram::new()))
1206 }
1207 "PinskyRinzelNeuron" | "PinskyRinzel" => {
1208 Ok(NeuronVariant::WrPinskyRinzelCell(WrPinskyRinzel::new()))
1209 }
1210 "HayL5PyramidalNeuron" | "HayL5" => Ok(NeuronVariant::WrHayL5Cell(WrHayL5::new())),
1211 "TwoCompartmentLIFNeuron" | "TwoCompLIF" => {
1212 Ok(NeuronVariant::WrTwoCompLIFCell(WrTwoCompLIF::new()))
1213 }
1214 "LoihiCUBANeuron" | "LoihiCUBA" => Ok(NeuronVariant::WrLoihiCUBACell(WrLoihiCUBA::new())),
1216 "Loihi2Neuron" | "Loihi2" => Ok(NeuronVariant::WrLoihi2Cell(WrLoihi2::new())),
1217 "SpiNNaker2Neuron" | "SpiNNaker2" => {
1218 Ok(NeuronVariant::WrSpiNNaker2Cell(WrSpiNNaker2::new()))
1219 }
1220 "TrueNorthNeuron" | "TrueNorth" => Ok(NeuronVariant::WrTrueNorthCell(WrTrueNorth::new())),
1221 "IntegerQIFNeuron" | "IntegerQIF" => {
1222 Ok(NeuronVariant::WrIntegerQIFCell(WrIntegerQIF::new()))
1223 }
1224 "SigmoidRateNeuron" | "SigmoidRate" => {
1226 Ok(NeuronVariant::WrSigmoidRateCell(WrSigmoidRate::new()))
1227 }
1228 "ThresholdLinearRateNeuron" | "ThresholdLinearRate" => Ok(
1229 NeuronVariant::WrThresholdLinearCell(WrThresholdLinear::new()),
1230 ),
1231 "AstrocyteModel" | "Astrocyte" => Ok(NeuronVariant::WrAstrocyteCell(WrAstrocyte::new())),
1232 "InnerHairCell" | "IHC" => Ok(NeuronVariant::WrInnerHairCellCell(WrInnerHairCell::new())),
1233 "OuterHairCell" | "OHC" => Ok(NeuronVariant::WrOuterHairCellCell(WrOuterHairCell::new())),
1234 "RodPhotoreceptor" | "Rod" => Ok(NeuronVariant::WrRodPhotoreceptorCell(
1235 WrRodPhotoreceptor::new(),
1236 )),
1237 "ConePhotoreceptor" | "Cone" => Ok(NeuronVariant::WrConePhotoreceptorCell(
1238 WrConePhotoreceptor::new(),
1239 )),
1240 "TasteReceptorCell" | "TasteReceptor" => {
1241 Ok(NeuronVariant::WrTasteReceptorCell(WrTasteReceptor::new()))
1242 }
1243 _ => Err(format!("Unsupported model: '{name}'")),
1244 }
1245}
1246
1247pub fn supported_models() -> Vec<&'static str> {
1249 vec![
1250 "Izhikevich",
1251 "AdEx",
1252 "ExpIF",
1253 "Lapicque",
1254 "HomeostaticLif",
1255 "HodgkinHuxley",
1256 "TraubMiles",
1257 "WangBuzsaki",
1258 "ConnorStevens",
1259 "DestexheThalamic",
1260 "HuberBraun",
1261 "GolombFS",
1262 "Pospischil",
1263 "MainenSejnowski",
1264 "DeSchutterPurkinje",
1265 "PlantR15",
1266 "Prescott",
1267 "MihalasNiebur",
1268 "GLIF",
1269 "GIFPopulation",
1270 "AvRonCardiac",
1271 "DurstewitzDopamine",
1272 "HillTononi",
1273 "BertramPhantom",
1274 "Yamada",
1275 "FitzHughNagumo",
1276 "MorrisLecar",
1277 "HindmarshRose",
1278 "ResonateAndFire",
1279 "BalancedResonateAndFire",
1280 "FitzHughRinzel",
1281 "McKean",
1282 "TermanWang",
1283 "GutkinErmentrout",
1284 "WilsonHR",
1285 "Akida",
1286 "StochasticLIF",
1287 "Chay",
1288 "ChayKeizer",
1289 "ShermanRinzelKeizer",
1290 "ButeraRespiratory",
1291 "EPropALIF",
1292 "SuperSpike",
1293 "LearnableNeuron",
1294 "Pernarowski",
1295 "QuadraticIF",
1296 "Theta",
1297 "PerfectIntegrator",
1298 "GatedLIF",
1299 "NonlinearLIF",
1300 "SFA",
1301 "MAT",
1302 "KLIF",
1303 "InhibitoryLIF",
1304 "ComplementaryLIF",
1305 "ParametricLIF",
1306 "NonResettingLIF",
1307 "AdaptiveThresholdIF",
1308 "SigmaDelta",
1309 "EnergyLIF",
1310 "ClosedFormContinuous",
1311 "ChialvoMap",
1312 "RulkovMap",
1313 "IbarzTanakaMap",
1314 "MedvedevMap",
1315 "CazellesMap",
1316 "CourageNekorkinMap",
1317 "AiharaMap",
1318 "KilincBhattMap",
1319 "ErmentroutKopellMap",
1320 "BrainScaleSAdEx",
1321 "SpiNNakerLIF",
1322 "NeuroGrid",
1323 "DPI",
1324 "MarderSTG",
1325 "RallCable",
1326 "BoothRinzel",
1327 "Dendrify",
1328 "LiquidTimeConstant",
1329 "ParallelSpiking",
1330 "FractionalLIF",
1331 "StochasticIF",
1332 "GalvesLocherbach",
1333 "SpikeResponse",
1334 "GLM",
1335 "ArcaneNeuron",
1336 "MultiTimescale",
1338 "AttentionGated",
1339 "PredictiveCoding",
1340 "SelfReferential",
1341 "CompositionalBinding",
1342 "DifferentiableSurrogate",
1343 "ContinuousAttractor",
1344 "MetaPlastic",
1345 "BendaHerz",
1346 "Poisson",
1348 "InhomogeneousPoisson",
1349 "GammaRenewal",
1350 "EscapeRate",
1351 "BrunelWangNeuron",
1352 "PVFastSpiking",
1354 "SST",
1355 "VIP",
1356 "Chandelier",
1357 "CerebellarBasket",
1358 "Martinotti",
1359 "AlphaMotor",
1361 "GammaMotor",
1362 "UpperMotor",
1363 "Renshaw",
1364 "MotorUnit",
1365 "RetinalGanglion",
1367 "Merkel",
1368 "Pacinian",
1369 "Nociceptor",
1370 "OlfactoryReceptor",
1371 "GranuleCell",
1373 "GolgiCell",
1374 "StellateCell",
1375 "LugaroCell",
1376 "UnipolarBrushCell",
1377 "DCNNeuron",
1378 "PersistentNa",
1380 "Ih",
1381 "TTypeCa",
1382 "ATypeK",
1383 "BK",
1384 "SK",
1385 "NMDA",
1386 "MontbrioMeanField",
1388 "BrunelNetwork",
1389 "TUMNetwork",
1390 "ElBoustaniNetwork",
1391 "GradedSynapseNeuron",
1393 "GapJunctionNeuron",
1394 "FrankenhaeUserHuxleyAxon",
1395 "NodeOfRanvier",
1396 "MyelinatedAxon",
1397 "CardiacPurkinjeFibre",
1398 "SmoothMuscleCell",
1399 "EndocrineBetaCell",
1400 "AlphaNeuron",
1402 "COBALIFNeuron",
1403 "CompteWMNeuron",
1404 "TsodyksMarkramNeuron",
1405 "PinskyRinzelNeuron",
1406 "HayL5PyramidalNeuron",
1407 "TwoCompartmentLIFNeuron",
1408 "LoihiCUBANeuron",
1410 "Loihi2Neuron",
1411 "SpiNNaker2Neuron",
1412 "TrueNorthNeuron",
1413 "IntegerQIFNeuron",
1414 "SigmoidRateNeuron",
1416 "ThresholdLinearRateNeuron",
1417 "AstrocyteModel",
1418 "InnerHairCell",
1419 "OuterHairCell",
1420 "RodPhotoreceptor",
1421 "ConePhotoreceptor",
1422 "TasteReceptorCell",
1423 ]
1424}
1425
1426#[cfg(test)]
1429mod tests {
1430 use super::*;
1431
1432 #[test]
1433 fn izhikevich_population_spikes() {
1434 let mut pop = create_population("Izhikevich", 10).unwrap();
1435 let mut total_spikes = 0usize;
1436 for _ in 0..100 {
1437 pop.currents.fill(10.0);
1438 pop.step_all();
1439 total_spikes += pop.spikes.iter().filter(|&&s| s != 0).count();
1440 }
1441 assert!(
1442 total_spikes > 0,
1443 "10 Izhikevich neurons must spike with I=10"
1444 );
1445 }
1446
1447 #[test]
1448 fn projection_propagates_spikes() {
1449 let row_offsets = vec![0, 2, 4];
1450 let col_indices = vec![0, 1, 0, 1];
1451 let values = vec![5.0, 3.0, 2.0, 4.0];
1452
1453 let mut proj = ProjectionRunner::new(0, 1, row_offsets, col_indices, values, 0);
1454 let src_spikes = vec![1u8, 0];
1455 let mut tgt_currents = vec![0.0; 2];
1456 proj.propagate(&src_spikes, &mut tgt_currents);
1457
1458 assert!((tgt_currents[0] - 5.0).abs() < 1e-10);
1459 assert!((tgt_currents[1] - 3.0).abs() < 1e-10);
1460 }
1461
1462 #[test]
1463 fn single_population_step_accepts_external_currents() {
1464 let mut runner = NetworkRunner::new();
1465 let idx = runner.add_population(create_population("Lapicque", 3).unwrap());
1466
1467 let (spikes, voltages) = runner
1468 .step_population_with_currents(idx, &[1.0, 2.0, 3.0])
1469 .unwrap();
1470
1471 assert_eq!(spikes.len(), 3);
1472 assert_eq!(voltages.len(), 3);
1473 assert!(spikes.iter().all(|&s| s <= 1));
1474 assert!(voltages.iter().all(|v| v.is_finite()));
1475 assert!(runner
1476 .step_population_with_currents(idx, &[1.0, 2.0])
1477 .is_err());
1478 assert!(runner
1479 .step_population_with_currents(idx + 1, &[1.0, 2.0, 3.0])
1480 .is_err());
1481 }
1482
1483 #[test]
1484 fn all_to_all_network_100_steps() {
1485 let mut runner = NetworkRunner::new();
1486 let pop = create_population("Izhikevich", 4).unwrap();
1487 runner.add_population(pop);
1488
1489 let mut row_offsets = Vec::new();
1491 let mut col_indices = Vec::new();
1492 let mut values = Vec::new();
1493 let mut offset = 0;
1494 for _i in 0..4 {
1495 row_offsets.push(offset);
1496 for j in 0..4 {
1497 col_indices.push(j);
1498 values.push(2.0);
1499 offset += 1;
1500 }
1501 }
1502 row_offsets.push(offset);
1503
1504 let proj = ProjectionRunner::new(0, 0, row_offsets, col_indices, values, 0);
1505 runner.add_projection(proj);
1506
1507 for n in &mut runner.populations[0].neurons {
1509 if let NeuronVariant::Izhikevich(iz) = n {
1510 iz.v = -50.0;
1511 }
1512 }
1513
1514 let results = runner.run(100);
1515 assert_eq!(results.spike_counts.len(), 1);
1516 assert_eq!(results.voltages.len(), 1);
1517 assert_eq!(results.voltages[0].len(), 4);
1518 }
1519
1520 #[test]
1521 fn mixed_hh_adex_network() {
1522 let mut runner = NetworkRunner::new();
1523
1524 let hh_pop = create_population("HodgkinHuxley", 3).unwrap();
1525 let adex_pop = create_population("AdEx", 3).unwrap();
1526 let hh_idx = runner.add_population(hh_pop);
1527 let adex_idx = runner.add_population(adex_pop);
1528
1529 let row_offsets = vec![0, 3, 6, 9];
1531 let col_indices = vec![0, 1, 2, 0, 1, 2, 0, 1, 2];
1532 let values = vec![100.0; 9];
1533 let proj = ProjectionRunner::new(hh_idx, adex_idx, row_offsets, col_indices, values, 0);
1534 runner.add_projection(proj);
1535
1536 runner.populations[0].currents.fill(15.0);
1538
1539 let results = runner.run(50);
1540 assert_eq!(results.spike_counts.len(), 2);
1541 assert_eq!(results.voltages.len(), 2);
1542 }
1543
1544 #[test]
1545 fn large_network_performance() {
1546 let n = 1000;
1547 let mut pop = create_population("Izhikevich", n).unwrap();
1548 for _ in 0..1000 {
1550 pop.currents.fill(10.0);
1551 pop.step_all();
1552 }
1553 let total: usize = pop.spikes.iter().map(|&s| s as usize).sum();
1554 let _ = total;
1556 let voltages = pop.collect_voltages();
1558 assert_eq!(voltages.len(), n);
1559 for v in &voltages {
1560 assert!(v.is_finite(), "voltage must be finite");
1561 }
1562 }
1563
1564 #[test]
1565 fn batch_simulate_single_neuron() {
1566 let mut neuron = create_neuron("AdEx").unwrap();
1567 let n_steps = 1000;
1568 let current = 500.0;
1569 let mut voltages = Vec::with_capacity(n_steps);
1570 let mut spikes = Vec::new();
1571 for t in 0..n_steps {
1572 let fired = neuron.step(current);
1573 voltages.push(neuron.soma_voltage());
1574 if fired != 0 {
1575 spikes.push(t);
1576 }
1577 }
1578 assert_eq!(voltages.len(), n_steps);
1579 assert!(voltages.iter().all(|v| v.is_finite()));
1580 assert!(!spikes.is_empty(), "AdEx with I=10 should spike");
1581 }
1582
1583 #[test]
1584 fn create_neuron_all_supported() {
1585 for name in supported_models() {
1586 let result = create_neuron(name);
1587 assert!(
1588 result.is_ok(),
1589 "create_neuron({name}) failed: {:?}",
1590 result.err()
1591 );
1592 }
1593 }
1594
1595 #[test]
1598 fn interneuron_population_create_step_reset() {
1599 for name in &[
1600 "PVFastSpiking",
1601 "SST",
1602 "VIP",
1603 "Chandelier",
1604 "CerebellarBasket",
1605 "Martinotti",
1606 ] {
1607 let mut pop = create_population(name, 5).unwrap();
1608 pop.currents.fill(3.0);
1609 for _ in 0..100 {
1610 pop.step_all();
1611 }
1612 let voltages = pop.collect_voltages();
1613 assert_eq!(voltages.len(), 5, "{name}: voltage count mismatch");
1614 for v in &voltages {
1615 assert!(v.is_finite(), "{name}: non-finite voltage {v}");
1616 }
1617 pop.reset_all();
1618 let v_after_reset = pop.collect_voltages();
1619 for v in &v_after_reset {
1620 assert!(v.is_finite(), "{name}: non-finite after reset");
1621 }
1622 }
1623 }
1624
1625 #[test]
1626 fn interneuron_mixed_network() {
1627 let mut runner = NetworkRunner::new();
1628 let pv_pop = create_population("PVFastSpiking", 3).unwrap();
1629 let sst_pop = create_population("SST", 3).unwrap();
1630 let pv_idx = runner.add_population(pv_pop);
1631 let sst_idx = runner.add_population(sst_pop);
1632
1633 let row_offsets = vec![0, 3, 6, 9];
1635 let col_indices = vec![0, 1, 2, 0, 1, 2, 0, 1, 2];
1636 let values = vec![1.0; 9];
1637 let proj = ProjectionRunner::new(pv_idx, sst_idx, row_offsets, col_indices, values, 0);
1638 runner.add_projection(proj);
1639
1640 runner.populations[0].currents.fill(3.0);
1641 let results = runner.run(50);
1642 assert_eq!(results.spike_counts.len(), 2);
1643 assert_eq!(results.voltages.len(), 2);
1644 for pop_voltages in &results.voltages {
1645 for v in pop_voltages {
1646 assert!(v.is_finite());
1647 }
1648 }
1649 }
1650
1651 #[test]
1654 fn sensory_spiking_population_create_step() {
1655 for name in &[
1656 "RetinalGanglion",
1657 "Merkel",
1658 "Pacinian",
1659 "Nociceptor",
1660 "OlfactoryReceptor",
1661 ] {
1662 let mut pop = create_population(name, 5).unwrap();
1663 pop.currents.fill(20.0);
1664 for _ in 0..200 {
1665 pop.step_all();
1666 }
1667 let voltages = pop.collect_voltages();
1668 assert_eq!(voltages.len(), 5, "{name}: voltage count mismatch");
1669 for v in &voltages {
1670 assert!(v.is_finite(), "{name}: non-finite voltage {v}");
1671 }
1672 }
1673 }
1674
1675 #[test]
1678 fn all_models_nan_input_stays_finite() {
1679 let fragile_models = &[
1682 "PVFastSpiking",
1683 "SST",
1684 "VIP",
1685 "Chandelier",
1686 "CerebellarBasket",
1687 "Martinotti",
1688 "RetinalGanglion",
1689 "Merkel",
1690 "Pacinian",
1691 "Nociceptor",
1692 "OlfactoryReceptor",
1693 ];
1694 for name in fragile_models {
1695 let mut neuron = create_neuron(name).unwrap();
1696 for _ in 0..100 {
1698 neuron.step(2.0);
1699 }
1700 for _ in 0..10 {
1702 let _ = neuron.step(f64::NAN);
1703 }
1704 neuron.reset();
1706 let v = neuron.soma_voltage();
1707 assert!(
1708 v.is_finite(),
1709 "{name}: voltage not finite after reset from NaN: {v}"
1710 );
1711 }
1712 }
1713
1714 #[test]
1715 fn all_models_extreme_input_stays_finite() {
1716 let models = &[
1717 "PVFastSpiking",
1718 "SST",
1719 "VIP",
1720 "Chandelier",
1721 "CerebellarBasket",
1722 "Martinotti",
1723 "RetinalGanglion",
1724 "Merkel",
1725 "Pacinian",
1726 "Nociceptor",
1727 "OlfactoryReceptor",
1728 ];
1729 for name in models {
1730 let mut neuron = create_neuron(name).unwrap();
1731 for _ in 0..50 {
1733 neuron.step(1e6);
1734 }
1735 neuron.reset();
1736 let v = neuron.soma_voltage();
1737 assert!(
1738 v.is_finite(),
1739 "{name}: non-finite after large positive input"
1740 );
1741
1742 for _ in 0..50 {
1744 neuron.step(-1e6);
1745 }
1746 neuron.reset();
1747 let v = neuron.soma_voltage();
1748 assert!(
1749 v.is_finite(),
1750 "{name}: non-finite after large negative input"
1751 );
1752 }
1753 }
1754}