Skip to main content

sc_neurocore_engine/neurons/
hardware.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Commercial license available
3// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4// © Code 2020–2026 Miroslav Šotek. All rights reserved.
5// ORCID: 0009-0009-3560-0851
6// Contact: www.anulum.li | protoscience@anulum.li
7// SC-NeuroCore — Hardware neuromorphic chip emulators
8
9//! Hardware neuromorphic chip emulators.
10
11/// Loihi CUBA LIF — Intel Loihi 1 fixed-point neuron. Davies et al. 2018.
12#[derive(Clone, Debug)]
13pub struct LoihiCUBANeuron {
14    pub v: i32,
15    pub u: i32,
16    pub tau_v: i32,
17    pub tau_u: i32,
18    pub v_threshold: i32,
19    pub v_reset: i32,
20}
21
22impl LoihiCUBANeuron {
23    pub fn new() -> Self {
24        Self {
25            v: 0,
26            u: 0,
27            tau_v: 10,
28            tau_u: 5,
29            v_threshold: 1000,
30            v_reset: 0,
31        }
32    }
33    pub fn step(&mut self, weighted_input: i32) -> i32 {
34        self.u = self.u - self.u / self.tau_u + weighted_input;
35        self.v = self.v - self.v / self.tau_v + self.u;
36        if self.v >= self.v_threshold {
37            self.v = self.v_reset;
38            1
39        } else {
40            0
41        }
42    }
43    pub fn reset(&mut self) {
44        self.v = 0;
45        self.u = 0;
46    }
47}
48impl Default for LoihiCUBANeuron {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54/// Loihi 2 — Intel Loihi 2 three-state integer neuron.
55#[derive(Clone, Debug)]
56pub struct Loihi2Neuron {
57    pub s1: i32,
58    pub s2: i32,
59    pub s3: i32,
60    pub tau1: i32,
61    pub tau2: i32,
62    pub tau3: i32,
63    pub w12: i32,
64    pub w13: i32,
65    pub w23: i32,
66    pub s1_threshold: i32,
67    pub s1_reset: i32,
68    pub s3_incr: i32,
69}
70
71impl Loihi2Neuron {
72    pub fn new() -> Self {
73        Self {
74            s1: 0,
75            s2: 0,
76            s3: 0,
77            tau1: 10,
78            tau2: 5,
79            tau3: 50,
80            w12: 1,
81            w13: 0,
82            w23: 0,
83            s1_threshold: 1000,
84            s1_reset: 0,
85            s3_incr: 10,
86        }
87    }
88    pub fn step(&mut self, weighted_input: i32) -> i32 {
89        self.s3 -= self.s3 / self.tau3;
90        self.s2 = self.s2 - self.s2 / self.tau2 + weighted_input + self.w23 * self.s3;
91        self.s1 = self.s1 - self.s1 / self.tau1 + self.w12 * self.s2 + self.w13 * self.s3;
92        if self.s1 >= self.s1_threshold {
93            self.s1 = self.s1_reset;
94            self.s3 += self.s3_incr;
95            1
96        } else {
97            0
98        }
99    }
100    pub fn reset(&mut self) {
101        self.s1 = 0;
102        self.s2 = 0;
103        self.s3 = 0;
104    }
105}
106impl Default for Loihi2Neuron {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112/// TrueNorth — IBM TrueNorth digital crossbar neuron. Merolla et al. 2014.
113#[derive(Clone, Debug)]
114pub struct TrueNorthNeuron {
115    pub v: i32,
116    pub leak: i32,
117    pub threshold: i32,
118    pub v_reset: i32,
119}
120
121impl TrueNorthNeuron {
122    pub fn new(threshold: i32) -> Self {
123        Self {
124            v: 0,
125            leak: 0,
126            threshold,
127            v_reset: 0,
128        }
129    }
130    pub fn step(&mut self, weighted_input: i32) -> i32 {
131        self.v += weighted_input - self.leak;
132        if self.v >= self.threshold {
133            self.v = self.v_reset;
134            1
135        } else {
136            0
137        }
138    }
139    pub fn reset(&mut self) {
140        self.v = 0;
141    }
142}
143impl Default for TrueNorthNeuron {
144    fn default() -> Self {
145        Self::new(100)
146    }
147}
148
149/// BrainScaleS AdEx — Heidelberg analog wafer-scale. Schemmel et al. 2010.
150#[derive(Clone, Debug)]
151pub struct BrainScaleSAdExNeuron {
152    pub v: f64,
153    pub w: f64,
154    pub v_rest: f64,
155    pub v_reset: f64,
156    pub v_threshold: f64,
157    pub delta_t: f64,
158    pub v_rh: f64,
159    pub tau: f64,
160    pub tau_w: f64,
161    pub a: f64,
162    pub b: f64,
163    pub hw_speedup: f64,
164    pub dt: f64,
165}
166
167impl BrainScaleSAdExNeuron {
168    pub fn new() -> Self {
169        Self {
170            v: -65.0,
171            w: 0.0,
172            v_rest: -65.0,
173            v_reset: -68.0,
174            v_threshold: -50.0,
175            delta_t: 2.0,
176            v_rh: -55.0,
177            tau: 20.0,
178            tau_w: 100.0,
179            a: 0.5,
180            b: 7.0,
181            hw_speedup: 1000.0,
182            dt: 0.1,
183        }
184    }
185    pub fn step(&mut self, current: f64) -> i32 {
186        let exp_arg = ((self.v - self.v_rh) / self.delta_t).clamp(-20.0, 20.0);
187        let exp_term = self.delta_t * exp_arg.exp();
188        let dv = (-(self.v - self.v_rest) + exp_term - self.w + current) / self.tau * self.dt;
189        let dw = (self.a * (self.v - self.v_rest) - self.w) / self.tau_w * self.dt;
190        self.v += dv;
191        self.w += dw;
192        if self.v >= self.v_threshold {
193            self.v = self.v_reset;
194            self.w += self.b;
195            1
196        } else {
197            0
198        }
199    }
200    pub fn reset(&mut self) {
201        self.v = self.v_rest;
202        self.w = 0.0;
203    }
204}
205impl Default for BrainScaleSAdExNeuron {
206    fn default() -> Self {
207        Self::new()
208    }
209}
210
211/// SpiNNaker LIF — ARM Cortex-M4 digital LIF with refractory. Furber et al. 2014.
212#[derive(Clone, Debug)]
213pub struct SpiNNakerLIFNeuron {
214    pub v: f64,
215    pub v_rest: f64,
216    pub v_reset: f64,
217    pub v_threshold: f64,
218    pub tau_m: f64,
219    pub i_offset: f64,
220    pub tau_refrac: f64,
221    pub refrac_count: f64,
222    pub dt: f64,
223}
224
225impl SpiNNakerLIFNeuron {
226    pub fn new() -> Self {
227        Self {
228            v: -70.0,
229            v_rest: -70.0,
230            v_reset: -70.0,
231            v_threshold: -50.0,
232            tau_m: 20.0,
233            i_offset: 0.0,
234            tau_refrac: 2.0,
235            refrac_count: 0.0,
236            dt: 1.0,
237        }
238    }
239    pub fn step(&mut self, current: f64) -> i32 {
240        if self.refrac_count > 0.0 {
241            self.refrac_count -= self.dt;
242            return 0;
243        }
244        self.v += (-(self.v - self.v_rest) + current + self.i_offset) / self.tau_m * self.dt;
245        if self.v >= self.v_threshold {
246            self.v = self.v_reset;
247            self.refrac_count = self.tau_refrac;
248            1
249        } else {
250            0
251        }
252    }
253    pub fn reset(&mut self) {
254        self.v = self.v_rest;
255        self.refrac_count = 0.0;
256    }
257}
258impl Default for SpiNNakerLIFNeuron {
259    fn default() -> Self {
260        Self::new()
261    }
262}
263
264/// SpiNNaker2 — TU Dresden ARM Cortex-M4F fixed-point LIF.
265#[derive(Clone, Debug)]
266pub struct SpiNNaker2Neuron {
267    pub v: i32,
268    pub v_rest: i32,
269    pub v_reset: i32,
270    pub v_threshold: i32,
271    pub decay_mult: i32,
272    pub decay_shift: i32,
273    pub refrac_steps: i32,
274    pub refrac_count: i32,
275}
276
277impl SpiNNaker2Neuron {
278    pub fn new() -> Self {
279        Self {
280            v: 0,
281            v_rest: 0,
282            v_reset: 0,
283            v_threshold: 1024,
284            decay_mult: 243,
285            decay_shift: 8,
286            refrac_steps: 2,
287            refrac_count: 0,
288        }
289    }
290    pub fn step(&mut self, current: i32) -> i32 {
291        if self.refrac_count > 0 {
292            self.refrac_count -= 1;
293            return 0;
294        }
295        self.v = ((self.v - self.v_rest).wrapping_mul(self.decay_mult) >> self.decay_shift)
296            + self.v_rest
297            + current;
298        if self.v >= self.v_threshold {
299            self.v = self.v_reset;
300            self.refrac_count = self.refrac_steps;
301            1
302        } else {
303            0
304        }
305    }
306    pub fn reset(&mut self) {
307        self.v = self.v_rest;
308        self.refrac_count = 0;
309    }
310}
311impl Default for SpiNNaker2Neuron {
312    fn default() -> Self {
313        Self::new()
314    }
315}
316
317/// DPI — Differential Pair Integrator (log-domain analog). Bartolozzi & Indiveri 2007.
318#[derive(Clone, Debug)]
319pub struct DPINeuron {
320    pub i_mem: f64,
321    pub i_threshold: f64,
322    pub i_reset: f64,
323    pub i_leak: f64,
324    pub tau: f64,
325    pub gain: f64,
326    pub dt: f64,
327}
328
329impl DPINeuron {
330    pub fn new() -> Self {
331        Self {
332            i_mem: 0.0,
333            i_threshold: 1.0,
334            i_reset: 0.0,
335            i_leak: 0.01,
336            tau: 20.0,
337            gain: 1.0,
338            dt: 1.0,
339        }
340    }
341    pub fn step(&mut self, i_syn: f64) -> i32 {
342        self.i_mem += (-self.i_mem + self.gain * i_syn + self.i_leak) / self.tau * self.dt;
343        if self.i_mem >= self.i_threshold {
344            self.i_mem = self.i_reset;
345            1
346        } else {
347            0
348        }
349    }
350    pub fn reset(&mut self) {
351        self.i_mem = 0.0;
352    }
353}
354impl Default for DPINeuron {
355    fn default() -> Self {
356        Self::new()
357    }
358}
359
360/// Akida — BrainChip event-domain rank-order neuron.
361#[derive(Clone, Debug)]
362pub struct AkidaNeuron {
363    pub v: i32,
364    pub threshold: i32,
365    pub modulation: f64,
366    pub rank: i32,
367    pub spiked: bool,
368}
369
370impl AkidaNeuron {
371    pub fn new(threshold: i32) -> Self {
372        Self {
373            v: 0,
374            threshold,
375            modulation: 0.75,
376            rank: 0,
377            spiked: false,
378        }
379    }
380    pub fn step(&mut self, weight: f64) -> i32 {
381        if self.spiked {
382            return 0;
383        }
384        self.v += (weight * self.modulation.powi(self.rank)) as i32;
385        self.rank += 1;
386        if self.v >= self.threshold {
387            self.spiked = true;
388            1
389        } else {
390            0
391        }
392    }
393    pub fn reset(&mut self) {
394        self.v = 0;
395        self.rank = 0;
396        self.spiked = false;
397    }
398}
399impl Default for AkidaNeuron {
400    fn default() -> Self {
401        Self::new(100)
402    }
403}
404
405/// NeuroGrid — Boahen 2014 subthreshold analog 2-compartment.
406#[derive(Clone, Debug)]
407pub struct NeuroGridNeuron {
408    pub v_s: f64,
409    pub v_d: f64,
410    pub tau_s: f64,
411    pub tau_d: f64,
412    pub g_c: f64,
413    pub delta_t: f64,
414    pub v_rest: f64,
415    pub v_threshold: f64,
416    pub v_peak: f64,
417    pub v_reset: f64,
418    pub dt: f64,
419}
420
421impl NeuroGridNeuron {
422    pub fn new() -> Self {
423        Self {
424            v_s: -65.0,
425            v_d: -65.0,
426            tau_s: 20.0,
427            tau_d: 50.0,
428            g_c: 0.5,
429            delta_t: 2.0,
430            v_rest: -65.0,
431            v_threshold: -50.0,
432            v_peak: 20.0,
433            v_reset: -65.0,
434            dt: 0.1,
435        }
436    }
437    fn valid(&self) -> bool {
438        self.v_s.is_finite()
439            && self.v_d.is_finite()
440            && self.tau_s.is_finite()
441            && self.tau_s > 0.0
442            && self.tau_d.is_finite()
443            && self.tau_d > 0.0
444            && self.g_c.is_finite()
445            && self.g_c >= 0.0
446            && self.delta_t.is_finite()
447            && self.delta_t > 0.0
448            && self.v_rest.is_finite()
449            && self.v_threshold.is_finite()
450            && self.v_peak.is_finite()
451            && self.v_reset.is_finite()
452            && self.dt.is_finite()
453            && self.dt > 0.0
454    }
455
456    fn derivatives(&self, v_s: f64, v_d: f64, current: f64) -> (f64, f64) {
457        let v_s_eff = v_s.min(self.v_peak);
458        let dv_d = (-(v_d - self.v_rest) + current - self.g_c * (v_d - v_s_eff)) / self.tau_d;
459        let exp_arg = ((v_s_eff - self.v_threshold) / self.delta_t).min(20.0);
460        let exp_term = self.delta_t * exp_arg.exp();
461        let dv_s = (-(v_s_eff - self.v_rest) + exp_term + self.g_c * (v_d - v_s_eff)) / self.tau_s;
462        (dv_s, dv_d)
463    }
464
465    fn rk4_substep(&self, v_s: f64, v_d: f64, current: f64) -> (f64, f64) {
466        let dt = self.dt;
467        let (k1s, k1d) = self.derivatives(v_s, v_d, current);
468        let (k2s, k2d) = self.derivatives(v_s + 0.5 * dt * k1s, v_d + 0.5 * dt * k1d, current);
469        let (k3s, k3d) = self.derivatives(v_s + 0.5 * dt * k2s, v_d + 0.5 * dt * k2d, current);
470        let (k4s, k4d) = self.derivatives(v_s + dt * k3s, v_d + dt * k3d, current);
471        (
472            v_s + dt * (k1s + 2.0 * k2s + 2.0 * k3s + k4s) / 6.0,
473            v_d + dt * (k1d + 2.0 * k2d + 2.0 * k3d + k4d) / 6.0,
474        )
475    }
476
477    pub fn step(&mut self, current: f64) -> i32 {
478        if !current.is_finite() || !self.valid() {
479            return 0;
480        }
481        let (next_v_s, next_v_d) = self.rk4_substep(self.v_s, self.v_d, current);
482        if !next_v_s.is_finite() || !next_v_d.is_finite() {
483            return 0;
484        }
485        self.v_d = next_v_d;
486        if next_v_s >= self.v_peak {
487            self.v_s = self.v_reset;
488            1
489        } else {
490            self.v_s = next_v_s;
491            0
492        }
493    }
494    pub fn reset(&mut self) {
495        self.v_s = -65.0;
496        self.v_d = -65.0;
497    }
498}
499impl Default for NeuroGridNeuron {
500    fn default() -> Self {
501        Self::new()
502    }
503}
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508
509    #[test]
510    fn loihi_cuba_fires() {
511        let mut n = LoihiCUBANeuron::new();
512        let t: i32 = (0..200).map(|_| n.step(100)).sum();
513        assert!(t > 0);
514    }
515    #[test]
516    fn loihi2_fires() {
517        let mut n = Loihi2Neuron {
518            tau3: 8,
519            ..Loihi2Neuron::new()
520        };
521        let t: i32 = (0..500).map(|_| n.step(200)).sum();
522        assert!(t > 0);
523    }
524    #[test]
525    fn truenorth_fires() {
526        let mut n = TrueNorthNeuron::default();
527        let t: i32 = (0..10).map(|_| n.step(50)).sum();
528        assert!(t > 0);
529    }
530    #[test]
531    fn brainscales_fires() {
532        let mut n = BrainScaleSAdExNeuron::new();
533        let t: i32 = (0..2000).map(|_| n.step(500.0)).sum();
534        assert!(t > 0);
535    }
536    #[test]
537    fn spinnaker_fires() {
538        let mut n = SpiNNakerLIFNeuron::new();
539        let t: i32 = (0..200).map(|_| n.step(30.0)).sum();
540        assert!(t > 0);
541    }
542    #[test]
543    fn spinnaker2_fires() {
544        let mut n = SpiNNaker2Neuron::new();
545        let t: i32 = (0..200).map(|_| n.step(100)).sum();
546        assert!(t > 0);
547    }
548    #[test]
549    fn dpi_fires() {
550        let mut n = DPINeuron::new();
551        let t: i32 = (0..100).map(|_| n.step(1.0)).sum();
552        assert!(t > 0);
553    }
554    #[test]
555    fn akida_fires() {
556        let mut n = AkidaNeuron::default();
557        let t: i32 = (0..10).map(|_| n.step(50.0)).sum();
558        assert!(t > 0);
559    }
560    #[test]
561    fn neurogrid_fires() {
562        let mut n = NeuroGridNeuron::new();
563        let t: i32 = (0..2000).map(|_| n.step(500.0)).sum();
564        assert!(t > 0);
565    }
566
567    // ── Multi-angle tests for hardware models ──
568
569    // -- LoihiCUBA --
570    #[test]
571    fn loihi_cuba_silent() {
572        let mut n = LoihiCUBANeuron::new();
573        let t: i32 = (0..200).map(|_| n.step(0)).sum();
574        assert_eq!(t, 0);
575    }
576    #[test]
577    fn loihi_cuba_reset() {
578        let mut n = LoihiCUBANeuron::new();
579        for _ in 0..50 {
580            n.step(100);
581        }
582        n.reset();
583        assert_eq!(n.v, 0);
584        assert_eq!(n.u, 0);
585    }
586    #[test]
587    fn loihi_cuba_bounded() {
588        let mut n = LoihiCUBANeuron::new();
589        for _ in 0..1000 {
590            n.step(10000);
591        }
592    }
593
594    // -- Loihi2 --
595    #[test]
596    fn loihi2_silent() {
597        let mut n = Loihi2Neuron::new();
598        let t: i32 = (0..200).map(|_| n.step(0)).sum();
599        assert_eq!(t, 0);
600    }
601    #[test]
602    fn loihi2_reset() {
603        let mut n = Loihi2Neuron::new();
604        for _ in 0..50 {
605            n.step(200);
606        }
607        n.reset();
608        assert_eq!(n.s1, 0);
609    }
610    #[test]
611    fn loihi2_bounded() {
612        let mut n = Loihi2Neuron {
613            tau3: 8,
614            ..Loihi2Neuron::new()
615        };
616        for _ in 0..1000 {
617            n.step(10000);
618        }
619    }
620
621    // -- TrueNorth --
622    #[test]
623    fn truenorth_silent() {
624        let mut n = TrueNorthNeuron::default();
625        let t: i32 = (0..100).map(|_| n.step(0)).sum();
626        assert_eq!(t, 0);
627    }
628    #[test]
629    fn truenorth_reset() {
630        let mut n = TrueNorthNeuron::default();
631        for _ in 0..10 {
632            n.step(50);
633        }
634        n.reset();
635        assert_eq!(n.v, 0);
636    }
637
638    // -- BrainScaleSAdEx --
639    #[test]
640    fn brainscales_silent() {
641        let mut n = BrainScaleSAdExNeuron::new();
642        let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
643        assert_eq!(t, 0);
644    }
645    #[test]
646    fn brainscales_reset() {
647        let mut n = BrainScaleSAdExNeuron::new();
648        for _ in 0..100 {
649            n.step(500.0);
650        }
651        n.reset();
652        assert!((n.v - n.v_rest).abs() < 1e-10);
653    }
654    #[test]
655    fn brainscales_bounded() {
656        let mut n = BrainScaleSAdExNeuron::new();
657        for _ in 0..2000 {
658            n.step(1e4);
659        }
660        assert!(n.v.is_finite());
661    }
662    #[test]
663    fn brainscales_nan_no_panic() {
664        BrainScaleSAdExNeuron::new().step(f64::NAN);
665    }
666
667    // -- SpiNNakerLIF --
668    #[test]
669    fn spinnaker_silent() {
670        let mut n = SpiNNakerLIFNeuron::new();
671        let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
672        assert_eq!(t, 0);
673    }
674    #[test]
675    fn spinnaker_reset() {
676        let mut n = SpiNNakerLIFNeuron::new();
677        for _ in 0..50 {
678            n.step(30.0);
679        }
680        n.reset();
681        assert!((n.v - n.v_rest).abs() < 1e-10);
682    }
683    #[test]
684    fn spinnaker_bounded() {
685        let mut n = SpiNNakerLIFNeuron::new();
686        for _ in 0..1000 {
687            n.step(1e4);
688        }
689        assert!(n.v.is_finite());
690    }
691    #[test]
692    fn spinnaker_nan_no_panic() {
693        SpiNNakerLIFNeuron::new().step(f64::NAN);
694    }
695
696    // -- SpiNNaker2 --
697    #[test]
698    fn spinnaker2_silent() {
699        let mut n = SpiNNaker2Neuron::new();
700        let t: i32 = (0..200).map(|_| n.step(0)).sum();
701        assert_eq!(t, 0);
702    }
703    #[test]
704    fn spinnaker2_reset() {
705        let mut n = SpiNNaker2Neuron::new();
706        for _ in 0..50 {
707            n.step(100);
708        }
709        n.reset();
710    }
711    #[test]
712    fn spinnaker2_bounded() {
713        let mut n = SpiNNaker2Neuron::new();
714        for _ in 0..1000 {
715            n.step(10000);
716        }
717    }
718
719    // -- DPI --
720    #[test]
721    fn dpi_silent() {
722        let mut n = DPINeuron::new();
723        let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
724        assert_eq!(t, 0);
725    }
726    #[test]
727    fn dpi_reset() {
728        let mut n = DPINeuron::new();
729        for _ in 0..50 {
730            n.step(1.0);
731        }
732        n.reset();
733    }
734    #[test]
735    fn dpi_nan_no_panic() {
736        DPINeuron::new().step(f64::NAN);
737    }
738
739    // -- Akida --
740    #[test]
741    fn akida_silent() {
742        let mut n = AkidaNeuron::default();
743        let t: i32 = (0..100).map(|_| n.step(0.0)).sum();
744        assert_eq!(t, 0);
745    }
746    #[test]
747    fn akida_reset() {
748        let mut n = AkidaNeuron::default();
749        for _ in 0..10 {
750            n.step(50.0);
751        }
752        n.reset();
753    }
754    #[test]
755    fn akida_nan_no_panic() {
756        AkidaNeuron::default().step(f64::NAN);
757    }
758
759    // -- NeuroGrid --
760    #[test]
761    fn neurogrid_silent() {
762        let mut n = NeuroGridNeuron::new();
763        let t: i32 = (0..200).map(|_| n.step(0.0)).sum();
764        assert_eq!(t, 0);
765    }
766    #[test]
767    fn neurogrid_reset() {
768        let mut n = NeuroGridNeuron::new();
769        for _ in 0..100 {
770            n.step(500.0);
771        }
772        n.reset();
773        assert!((n.v_s - (-65.0)).abs() < 1e-10);
774    }
775    #[test]
776    fn neurogrid_bounded() {
777        let mut n = NeuroGridNeuron::new();
778        for _ in 0..2000 {
779            n.step(1e4);
780        }
781        assert!(n.v_s.is_finite());
782    }
783    #[test]
784    fn neurogrid_nan_no_panic() {
785        NeuroGridNeuron::new().step(f64::NAN);
786    }
787    #[test]
788    fn neurogrid_rk4_anchor() {
789        let mut n = NeuroGridNeuron::new();
790        let spikes: i32 = (0..20_000).map(|_| n.step(100.0)).sum();
791        assert_eq!(spikes, 94);
792        assert!(n.v_s.is_finite());
793        assert!(n.v_d.is_finite());
794    }
795    #[test]
796    fn neurogrid_invalid_input_preserves_state() {
797        let mut n = NeuroGridNeuron::new();
798        for _ in 0..10 {
799            n.step(100.0);
800        }
801        let old = (n.v_s, n.v_d);
802        assert_eq!(n.step(f64::INFINITY), 0);
803        assert_eq!((n.v_s, n.v_d), old);
804    }
805}