sc_neurocore_engine/analysis/
point_process.rs1use super::basic;
10
11pub fn conditional_intensity(binary_train: &[i32], dt: f64, window_ms: f64) -> Vec<f64> {
15 let n = binary_train.len();
16 if n == 0 {
17 return vec![];
18 }
19 let w = (window_ms / (dt * 1000.0)).round().max(1.0) as usize;
20 let kernel_sum_inv = 1.0 / (w as f64 * dt);
21
22 let half = w / 2;
24 let mut result = vec![0.0f64; n];
25 for i in 0..n {
26 let lo = i.saturating_sub(half);
27 let hi = (i + w - half).min(n);
28 let mut sum = 0.0;
29 for j in lo..hi {
30 sum += binary_train[j] as f64;
31 }
32 result[i] = sum * kernel_sum_inv;
33 }
34 result
35}
36
37pub fn isi_hazard_function(binary_train: &[i32], dt: f64, bins: usize) -> (Vec<f64>, Vec<f64>) {
41 let intervals = basic::isi(binary_train, dt);
42 if intervals.len() < 5 {
43 return (vec![], vec![]);
44 }
45 let (hist, edges) = histogram(&intervals, bins);
46 let bin_width = edges[1] - edges[0];
47 let n = intervals.len() as f64;
48 let centres: Vec<f64> = (0..bins).map(|k| (edges[k] + edges[k + 1]) / 2.0).collect();
49 let pdf: Vec<f64> = hist.iter().map(|&c| c as f64 / (n * bin_width)).collect();
50 let mut cum = 0.0;
51 let mut survivor = vec![0.0; bins];
52 for k in 0..bins {
53 cum += pdf[k] * bin_width;
54 survivor[k] = (1.0 - cum).max(1e-30);
55 }
56 let hazard: Vec<f64> = (0..bins).map(|k| pdf[k] / survivor[k]).collect();
57 (hazard, centres)
58}
59
60pub fn isi_survivor_function(binary_train: &[i32], dt: f64, bins: usize) -> (Vec<f64>, Vec<f64>) {
64 let intervals = basic::isi(binary_train, dt);
65 if intervals.len() < 2 {
66 return (vec![], vec![]);
67 }
68 let mut sorted = intervals.clone();
69 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
70 let n = sorted.len() as f64;
71 let max_isi = sorted[sorted.len() - 1];
72 let edges: Vec<f64> = (0..=bins)
73 .map(|k| k as f64 * max_isi / bins as f64)
74 .collect();
75 let centres: Vec<f64> = (0..bins).map(|k| (edges[k] + edges[k + 1]) / 2.0).collect();
76 let survivor: Vec<f64> = centres
77 .iter()
78 .map(|&t| sorted.iter().filter(|&&v| v > t).count() as f64 / n)
79 .collect();
80 (survivor, centres)
81}
82
83pub fn renewal_density(binary_train: &[i32], dt: f64, bins: usize) -> (Vec<f64>, Vec<f64>) {
87 let intervals = basic::isi(binary_train, dt);
88 if intervals.len() < 5 {
89 return (vec![], vec![]);
90 }
91 let (hist, edges) = histogram(&intervals, bins);
92 let bin_width = edges[1] - edges[0];
93 let n = intervals.len() as f64;
94 let centres: Vec<f64> = (0..bins).map(|k| (edges[k] + edges[k + 1]) / 2.0).collect();
95 let pdf: Vec<f64> = hist.iter().map(|&c| c as f64 / (n * bin_width)).collect();
96 let mean_isi: f64 = intervals.iter().sum::<f64>() / n;
97 let mean_rate = if mean_isi > 0.0 { 1.0 / mean_isi } else { 1.0 };
98 let density: Vec<f64> = pdf.iter().map(|&p| p / mean_rate).collect();
99 (density, centres)
100}
101
102fn histogram(data: &[f64], bins: usize) -> (Vec<usize>, Vec<f64>) {
104 let min = data.iter().cloned().fold(f64::INFINITY, f64::min);
105 let max = data.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
106 let range = if (max - min).abs() < 1e-30 {
107 1.0
108 } else {
109 max - min
110 };
111 let edges: Vec<f64> = (0..=bins)
112 .map(|k| min + k as f64 * range / bins as f64)
113 .collect();
114 let mut counts = vec![0usize; bins];
115 for &v in data {
116 let mut k = ((v - min) / range * bins as f64) as usize;
117 if k >= bins {
118 k = bins - 1;
119 }
120 counts[k] += 1;
121 }
122 (counts, edges)
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 fn make_train() -> Vec<i32> {
130 let mut t = vec![0i32; 200];
132 for i in (0..200).step_by(10) {
133 t[i] = 1;
134 }
135 t
136 }
137
138 #[test]
139 fn test_conditional_intensity_shape() {
140 let train = make_train();
141 let ci = conditional_intensity(&train, 0.001, 50.0);
142 assert_eq!(ci.len(), 200);
143 let mid = ci[100];
145 assert!(
146 (mid - 100.0).abs() < 50.0,
147 "CI midpoint {mid} not near 100 Hz"
148 );
149 }
150
151 #[test]
152 fn test_conditional_intensity_empty() {
153 assert!(conditional_intensity(&[], 0.001, 10.0).is_empty());
154 }
155
156 #[test]
157 fn test_conditional_intensity_single() {
158 let ci = conditional_intensity(&[1], 0.001, 10.0);
159 assert_eq!(ci.len(), 1);
160 }
161
162 #[test]
163 fn test_isi_hazard_basic() {
164 let train = make_train();
165 let (hazard, centres) = isi_hazard_function(&train, 0.001, 20);
166 assert!(!hazard.is_empty());
167 assert_eq!(hazard.len(), centres.len());
168 assert!(hazard.iter().all(|&h| h >= 0.0));
170 }
171
172 #[test]
173 fn test_isi_hazard_few_spikes() {
174 let train = vec![0, 1, 0, 0, 0, 1, 0]; let (h, c) = isi_hazard_function(&train, 0.001, 10);
176 assert!(h.is_empty());
177 assert!(c.is_empty());
178 }
179
180 #[test]
181 fn test_isi_survivor_basic() {
182 let train = make_train();
183 let (surv, centres) = isi_survivor_function(&train, 0.001, 20);
184 assert!(!surv.is_empty());
185 assert_eq!(surv.len(), centres.len());
186 assert!(surv[0] >= surv[surv.len() - 1]);
188 }
189
190 #[test]
191 fn test_isi_survivor_few_spikes() {
192 let train = vec![1, 0];
193 let (s, c) = isi_survivor_function(&train, 0.001, 10);
194 assert!(s.is_empty());
195 assert!(c.is_empty());
196 }
197
198 #[test]
199 fn test_renewal_density_basic() {
200 let train = make_train();
201 let (dens, centres) = renewal_density(&train, 0.001, 20);
202 assert!(!dens.is_empty());
203 assert_eq!(dens.len(), centres.len());
204 assert!(dens.iter().all(|&d| d >= 0.0));
205 }
206
207 #[test]
208 fn test_renewal_density_few_spikes() {
209 let (d, c) = renewal_density(&[0, 1, 0], 0.001, 10);
210 assert!(d.is_empty());
211 assert!(c.is_empty());
212 }
213
214 #[test]
215 fn test_histogram_helper() {
216 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
217 let (counts, edges) = histogram(&data, 4);
218 assert_eq!(counts.len(), 4);
219 assert_eq!(edges.len(), 5);
220 let total: usize = counts.iter().sum();
221 assert_eq!(total, 5);
222 }
223
224 #[test]
225 fn test_regular_spike_isi_uniformity() {
226 let train = make_train();
227 let intervals = basic::isi(&train, 0.001);
229 assert!(intervals.iter().all(|&i| (i - 0.01).abs() < 1e-12));
230 }
231}