sc_neurocore_engine/analysis/
rate.rs1fn build_kernel(kernel: &str, sigma_steps: usize) -> Vec<f64> {
11 match kernel {
12 "gaussian" => {
13 let hw = 3 * sigma_steps;
14 let mut k: Vec<f64> = (0..2 * hw + 1)
15 .map(|i| {
16 let x = i as f64 - hw as f64;
17 (-0.5 * (x / sigma_steps as f64).powi(2)).exp()
18 })
19 .collect();
20 let s: f64 = k.iter().sum();
21 k.iter_mut().for_each(|v| *v /= s);
22 k
23 }
24 "exponential" => {
25 let hw = 5 * sigma_steps;
26 let mut k: Vec<f64> = (0..hw)
27 .map(|i| (-(i as f64) / sigma_steps as f64).exp())
28 .collect();
29 let s: f64 = k.iter().sum();
30 k.iter_mut().for_each(|v| *v /= s);
31 k
32 }
33 "rectangular" => {
34 let hw = sigma_steps;
35 let len = 2 * hw + 1;
36 vec![1.0 / len as f64; len]
37 }
38 _ => vec![1.0],
39 }
40}
41
42fn convolve_same(signal: &[f64], kernel: &[f64]) -> Vec<f64> {
44 let n = signal.len();
45 let kn = kernel.len();
46 let pad = kn / 2;
47 let mut out = vec![0.0; n];
48 for i in 0..n {
49 let mut acc = 0.0;
50 for j in 0..kn {
51 let si = i as isize + j as isize - pad as isize;
52 if si >= 0 && (si as usize) < n {
53 acc += signal[si as usize] * kernel[j];
54 }
55 }
56 out[i] = acc;
57 }
58 out
59}
60
61pub fn instantaneous_rate(binary_train: &[f64], dt: f64, kernel: &str, sigma_ms: f64) -> Vec<f64> {
63 let sigma_steps = (sigma_ms / (dt * 1000.0)).round().max(1.0) as usize;
64 let mut k = build_kernel(kernel, sigma_steps);
65 let s: f64 = k.iter().sum();
67 if s > 0.0 {
68 let norm = s * dt;
69 k.iter_mut().for_each(|v| *v /= norm);
70 }
71 let mut k_raw = build_kernel(kernel, sigma_steps);
75 let s_raw: f64 = k_raw.iter().sum();
76 let norm = s_raw * dt;
77 k_raw.iter_mut().for_each(|v| *v /= norm);
78
79 convolve_same(binary_train, &k_raw)
80}
81
82pub fn population_rate(trains: &[Vec<f64>], dt: f64, sigma_ms: f64) -> Vec<f64> {
84 if trains.is_empty() {
85 return vec![];
86 }
87 let min_len = trains.iter().map(|t| t.len()).min().unwrap_or(0);
88 if min_len == 0 {
89 return vec![];
90 }
91 let mut total = vec![0.0; min_len];
92 for t in trains {
93 for (i, &v) in t.iter().take(min_len).enumerate() {
94 total[i] += v;
95 }
96 }
97 instantaneous_rate(&total, dt, "gaussian", sigma_ms)
98}
99
100pub fn psth(trials: &[Vec<f64>], bin_ms: f64, dt: f64) -> (Vec<f64>, Vec<f64>) {
103 if trials.is_empty() {
104 return (vec![], vec![]);
105 }
106 let max_len = trials.iter().map(|t| t.len()).max().unwrap_or(0);
107 let bin_steps = (bin_ms / (dt * 1000.0)).round().max(1.0) as usize;
108 let n_bins = max_len / bin_steps;
109 if n_bins == 0 {
110 return (vec![], vec![]);
111 }
112 let mut counts = vec![0.0; n_bins];
113 for trial in trials {
114 let usable = trial.len().min(n_bins * bin_steps);
115 let n_full = usable / bin_steps;
116 for b in 0..n_full {
117 let s: f64 = trial[b * bin_steps..(b + 1) * bin_steps].iter().sum();
118 counts[b] += s;
119 }
120 }
121 let n_trials = trials.len() as f64;
122 let bin_sec = bin_ms / 1000.0;
123 let rates: Vec<f64> = counts.iter().map(|&c| c / (n_trials * bin_sec)).collect();
124 let centers: Vec<f64> = (0..n_bins).map(|i| (i as f64 + 0.5) * bin_ms).collect();
125 (rates, centers)
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_instantaneous_rate_gaussian() {
134 let mut train = vec![0.0; 100];
135 train[50] = 1.0;
136 let rate = instantaneous_rate(&train, 0.001, "gaussian", 5.0);
137 assert_eq!(rate.len(), 100);
138 let peak_idx = rate
140 .iter()
141 .enumerate()
142 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
143 .unwrap()
144 .0;
145 assert!((peak_idx as i32 - 50).abs() <= 1);
146 assert!(rate[50] > 0.0);
147 }
148
149 #[test]
150 fn test_instantaneous_rate_rectangular() {
151 let train = vec![1.0; 10];
152 let rate = instantaneous_rate(&train, 0.001, "rectangular", 1.0);
153 assert_eq!(rate.len(), 10);
154 assert!(rate[5] > 500.0);
156 }
157
158 #[test]
159 fn test_population_rate() {
160 let t1 = vec![1.0, 0.0, 1.0, 0.0, 1.0];
161 let t2 = vec![0.0, 1.0, 0.0, 1.0, 0.0];
162 let rate = population_rate(&[t1, t2], 0.001, 1.0);
163 assert_eq!(rate.len(), 5);
164 assert!(rate[2] > 0.0);
166 }
167
168 #[test]
169 fn test_psth() {
170 let trial = vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0];
171 let (rates, centers) = psth(&[trial.clone(), trial], 5.0, 0.001);
172 assert_eq!(rates.len(), 2);
173 assert_eq!(centers.len(), 2);
174 assert!((centers[0] - 2.5).abs() < 0.01);
175 assert!(rates[0] > 0.0);
176 }
177
178 #[test]
179 fn test_psth_empty() {
180 let (r, c) = psth(&[], 10.0, 0.001);
181 assert!(r.is_empty());
182 assert!(c.is_empty());
183 }
184}