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