sc_neurocore_engine/
recorder.rs1#[derive(Clone, Debug)]
13pub struct SpikeRecorder {
14 pub buffer: Vec<u8>,
15 pub dt_ms: f64,
16}
17
18impl SpikeRecorder {
19 pub fn new(dt_ms: f64) -> Self {
20 Self {
21 buffer: Vec::new(),
22 dt_ms,
23 }
24 }
25
26 pub fn record(&mut self, spike: u8) {
27 debug_assert!(spike <= 1);
28 self.buffer.push(spike);
29 }
30
31 pub fn total_spikes(&self) -> u64 {
32 self.buffer.iter().map(|&s| s as u64).sum()
33 }
34
35 pub fn firing_rate_hz(&self) -> f64 {
36 let t = self.buffer.len();
37 if t == 0 {
38 return 0.0;
39 }
40 let duration_s = (t as f64 * self.dt_ms) / 1000.0;
41 if duration_s <= 0.0 {
42 return 0.0;
43 }
44 self.total_spikes() as f64 / duration_s
45 }
46
47 pub fn reset(&mut self) {
48 self.buffer.clear();
49 }
50
51 pub fn len(&self) -> usize {
52 self.buffer.len()
53 }
54
55 pub fn is_empty(&self) -> bool {
56 self.buffer.is_empty()
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn all_spikes_rate() {
66 let mut r = SpikeRecorder::new(1.0);
67 for _ in 0..1000 {
68 r.record(1);
69 }
70 assert_eq!(r.total_spikes(), 1000);
71 assert!((r.firing_rate_hz() - 1000.0).abs() < 1e-6);
72 }
73
74 #[test]
75 fn no_spikes_rate() {
76 let mut r = SpikeRecorder::new(1.0);
77 for _ in 0..100 {
78 r.record(0);
79 }
80 assert_eq!(r.total_spikes(), 0);
81 assert!(r.firing_rate_hz().abs() < 1e-12);
82 }
83
84 #[test]
85 fn half_spikes() {
86 let mut r = SpikeRecorder::new(1.0);
87 for i in 0..100 {
88 r.record((i % 2) as u8);
89 }
90 assert_eq!(r.total_spikes(), 50);
91 assert!((r.firing_rate_hz() - 500.0).abs() < 1e-6);
92 }
93
94 #[test]
95 fn empty_recorder() {
96 let r = SpikeRecorder::new(1.0);
97 assert!(r.firing_rate_hz().abs() < 1e-12);
98 assert!(r.is_empty());
99 }
100
101 #[test]
102 fn reset_clears() {
103 let mut r = SpikeRecorder::new(1.0);
104 r.record(1);
105 r.reset();
106 assert!(r.is_empty());
107 assert_eq!(r.total_spikes(), 0);
108 }
109}