| 18 |
18 |
| 19 /// Spike merging heuristic selection |
19 /// Spike merging heuristic selection |
| 20 #[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)] |
20 #[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)] |
| 21 #[allow(dead_code)] |
21 #[allow(dead_code)] |
| 22 pub enum SpikeMergingMethod<F> { |
22 pub enum SpikeMergingMethod<F> { |
| 23 /// Try to merge spikes within a given radius of eachother |
23 /// Try to merge spikes within a given radius of each other, averaging the location |
| 24 HeuristicRadius(F), |
24 HeuristicRadius(F), |
| |
25 /// Try to merge spikes within a given radius of each other, attempting original locations |
| |
26 HeuristicRadiusNoInterp(F), |
| 25 /// No merging |
27 /// No merging |
| 26 None, |
28 None, |
| 27 } |
29 } |
| 28 |
30 |
| 29 // impl<F : Float> SpikeMergingMethod<F> { |
31 // impl<F : Float> SpikeMergingMethod<F> { |
| 38 |
40 |
| 39 impl<F : ClapFloat> std::fmt::Display for SpikeMergingMethod<F> { |
41 impl<F : ClapFloat> std::fmt::Display for SpikeMergingMethod<F> { |
| 40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
| 41 match self { |
43 match self { |
| 42 Self::None => write!(f, "none"), |
44 Self::None => write!(f, "none"), |
| 43 Self::HeuristicRadius(r) => std::fmt::Display::fmt(r, f), |
45 Self::HeuristicRadius(r) => write!(f, "i:{}", r), |
| |
46 Self::HeuristicRadiusNoInterp(r) => write!(f, "n:{}", r), |
| 44 } |
47 } |
| 45 } |
48 } |
| 46 } |
49 } |
| 47 |
50 |
| 48 impl<F : ClapFloat> std::str::FromStr for SpikeMergingMethod<F> { |
51 impl<F : ClapFloat> std::str::FromStr for SpikeMergingMethod<F> { |
| 75 /// an arbitrary value. This method will return that value for the *last* accepted merge, or |
90 /// an arbitrary value. This method will return that value for the *last* accepted merge, or |
| 76 /// [`None`] if no merge was accepted. |
91 /// [`None`] if no merge was accepted. |
| 77 /// |
92 /// |
| 78 /// This method is stable with respect to spike locations: on merge, the weight of existing |
93 /// This method is stable with respect to spike locations: on merge, the weight of existing |
| 79 /// spikes is set to zero, and a new one inserted at the end of the spike vector. |
94 /// spikes is set to zero, and a new one inserted at the end of the spike vector. |
| 80 fn merge_spikes<G, V>(&mut self, method : SpikeMergingMethod<F>, accept : G) -> Option<V> |
95 fn merge_spikes<G>(&mut self, method : SpikeMergingMethod<F>, accept : G) -> usize |
| 81 where G : Fn(&'_ Self) -> Option<V> { |
96 where G : FnMut(&'_ Self) -> bool { |
| 82 match method { |
97 match method { |
| 83 SpikeMergingMethod::HeuristicRadius(ρ) => self.do_merge_spikes_radius(ρ, accept), |
98 SpikeMergingMethod::HeuristicRadius(ρ) => |
| 84 SpikeMergingMethod::None => None, |
99 self.do_merge_spikes_radius(ρ, true, accept), |
| |
100 SpikeMergingMethod::HeuristicRadiusNoInterp(ρ) => |
| |
101 self.do_merge_spikes_radius(ρ, false, accept), |
| |
102 SpikeMergingMethod::None => 0, |
| 85 } |
103 } |
| 86 } |
104 } |
| 87 |
105 |
| 88 /// Attempt to merge spikes based on a value and a fitness function. |
106 /// Attempt to merge spikes based on a value and a fitness function. |
| 89 /// |
107 /// |
| 90 /// Calls [`SpikeMerging::merge_spikes`] with `accept` constructed from the composition of |
108 /// Calls [`SpikeMerging::merge_spikes`] with `accept` constructed from the composition of |
| 91 /// `value` and `fitness`, compared to initial fitness. Returns the last return value of `value` |
109 /// `value` and `fitness`, compared to initial fitness. Returns the last return value of `value` |
| 92 // for a merge accepted by `fitness`. If no merge was accepted, `value` applied to the initial |
110 // for a merge accepted by `fitness`. If no merge was accepted, `value` applied to the initial |
| 93 /// `self` is returned. |
111 /// `self` is returned. also the number of merges is returned; |
| 94 fn merge_spikes_fitness<G, H, V, O>( |
112 fn merge_spikes_fitness<G, H, V, O>( |
| 95 &mut self, |
113 &mut self, |
| 96 method : SpikeMergingMethod<F>, |
114 method : SpikeMergingMethod<F>, |
| 97 value : G, |
115 value : G, |
| 98 fitness : H |
116 fitness : H |
| 99 ) -> V |
117 ) -> (V, usize) |
| 100 where G : Fn(&'_ Self) -> V, |
118 where G : Fn(&'_ Self) -> V, |
| 101 H : Fn(&'_ V) -> O, |
119 H : Fn(&'_ V) -> O, |
| 102 O : PartialOrd { |
120 O : PartialOrd { |
| 103 let initial_res = value(self); |
121 let mut res = value(self); |
| 104 let initial_fitness = fitness(&initial_res); |
122 let initial_fitness = fitness(&res); |
| 105 self.merge_spikes(method, |μ| { |
123 let count = self.merge_spikes(method, |μ| { |
| 106 let res = value(μ); |
124 res = value(μ); |
| 107 (fitness(&res) <= initial_fitness).then_some(res) |
125 fitness(&res) <= initial_fitness |
| 108 }).unwrap_or(initial_res) |
126 }); |
| |
127 (res, count) |
| 109 } |
128 } |
| 110 |
129 |
| 111 /// Attempt to merge spikes that are within radius $ρ$ of each other (unspecified norm). |
130 /// Attempt to merge spikes that are within radius $ρ$ of each other (unspecified norm). |
| 112 /// |
131 /// |
| 113 /// This method implements [`SpikeMerging::merge_spikes`] for |
132 /// This method implements [`SpikeMerging::merge_spikes`] for |
| 114 /// [`SpikeMergingMethod::HeuristicRadius`]. The closure `accept` and the return value are |
133 /// [`SpikeMergingMethod::HeuristicRadius`]. The closure `accept` and the return value are |
| 115 /// as for that method. |
134 /// as for that method. |
| 116 fn do_merge_spikes_radius<G, V>(&mut self, ρ : F, accept : G) -> Option<V> |
135 fn do_merge_spikes_radius<G>(&mut self, ρ : F, interp : bool, accept : G) -> usize |
| 117 where G : Fn(&'_ Self) -> Option<V>; |
136 where G : FnMut(&'_ Self) -> bool; |
| 118 } |
137 } |
| 119 |
138 |
| 120 #[replace_float_literals(F::cast_from(literal))] |
139 #[replace_float_literals(F::cast_from(literal))] |
| 121 impl<F : Float, const N : usize> DiscreteMeasure<Loc<F, N>, F> { |
140 impl<F : Float, const N : usize> DiscreteMeasure<Loc<F, N>, F> { |
| 122 /// Attempts to merge spikes with indices `i` and `j`. |
141 /// Attempts to merge spikes with indices `i` and `j`. |
| 124 /// This assumes that the weights of the two spikes have already been checked not to be zero. |
143 /// This assumes that the weights of the two spikes have already been checked not to be zero. |
| 125 /// |
144 /// |
| 126 /// The parameter `res` points to the current “result” for [`SpikeMerging::merge_spikes`]. |
145 /// The parameter `res` points to the current “result” for [`SpikeMerging::merge_spikes`]. |
| 127 /// If the merge is accepted by `accept` returning a [`Some`], `res` will be replaced by its |
146 /// If the merge is accepted by `accept` returning a [`Some`], `res` will be replaced by its |
| 128 /// return value. |
147 /// return value. |
| 129 fn attempt_merge<G, V>( |
148 /// |
| |
149 /// Returns the index of `self.spikes` storing the new spike. |
| |
150 fn attempt_merge<G>( |
| 130 &mut self, |
151 &mut self, |
| 131 res : &mut Option<V>, |
|
| 132 i : usize, |
152 i : usize, |
| 133 j : usize, |
153 j : usize, |
| 134 accept : &G |
154 interp : bool, |
| 135 ) -> bool |
155 accept : &mut G |
| 136 where G : Fn(&'_ Self) -> Option<V> { |
156 ) -> Option<usize> |
| |
157 where G : FnMut(&'_ Self) -> bool { |
| 137 let &DeltaMeasure{ x : xi, α : αi } = &self.spikes[i]; |
158 let &DeltaMeasure{ x : xi, α : αi } = &self.spikes[i]; |
| 138 let &DeltaMeasure{ x : xj, α : αj } = &self.spikes[j]; |
159 let &DeltaMeasure{ x : xj, α : αj } = &self.spikes[j]; |
| 139 |
160 |
| 140 // Merge inplace |
161 if interp { |
| 141 self.spikes[i].α = 0.0; |
162 // Merge inplace |
| 142 self.spikes[j].α = 0.0; |
163 self.spikes[i].α = 0.0; |
| 143 //self.spikes.push(DeltaMeasure{ α : αi + αj, x : (xi + xj)/2.0 }); |
164 self.spikes[j].α = 0.0; |
| 144 self.spikes.push(DeltaMeasure{ α : αi + αj, x : (xi * αi + xj * αj) / (αi + αj) }); |
165 let αia = αi.abs(); |
| 145 match accept(self) { |
166 let αja = αj.abs(); |
| 146 some@Some(..) => { |
167 self.spikes.push(DeltaMeasure{ α : αi + αj, x : (xi * αia + xj * αja) / (αia + αja) }); |
| 147 // Merge accepted, update our return value |
168 if accept(self) { |
| 148 *res = some; |
169 Some(self.spikes.len()-1) |
| 149 // On next iteration process the newly merged spike. |
170 } else { |
| 150 //indices[k+1] = self.spikes.len() - 1; |
|
| 151 true |
|
| 152 }, |
|
| 153 None => { |
|
| 154 // Merge not accepted, restore modification |
171 // Merge not accepted, restore modification |
| 155 self.spikes[i].α = αi; |
172 self.spikes[i].α = αi; |
| 156 self.spikes[j].α = αj; |
173 self.spikes[j].α = αj; |
| 157 self.spikes.pop(); |
174 self.spikes.pop(); |
| 158 false |
175 None |
| 159 } |
176 } |
| 160 } |
177 } else { |
| 161 } |
178 // Attempt merge inplace, first combination |
| 162 |
179 self.spikes[i].α = αi + αj; |
| 163 /* |
180 self.spikes[j].α = 0.0; |
| 164 /// Attempts to merge spikes with indices i and j, acceptance through a delta. |
181 if accept(self) { |
| 165 fn attempt_merge_change<G, V>( |
182 // Merge accepted |
| 166 &mut self, |
183 Some(i) |
| 167 res : &mut Option<V>, |
184 } else { |
| 168 i : usize, |
185 // Attempt merge inplace, second combination |
| 169 j : usize, |
|
| 170 accept_change : &G |
|
| 171 ) -> bool |
|
| 172 where G : Fn(&'_ Self) -> Option<V> { |
|
| 173 let &DeltaMeasure{ x : xi, α : αi } = &self.spikes[i]; |
|
| 174 let &DeltaMeasure{ x : xj, α : αj } = &self.spikes[j]; |
|
| 175 let δ = DeltaMeasure{ α : αi + αj, x : (xi + xj)/2.0 }; |
|
| 176 let λ = [-self.spikes[i], -self.spikes[j], δ.clone()].into(); |
|
| 177 |
|
| 178 match accept_change(&λ) { |
|
| 179 some@Some(..) => { |
|
| 180 // Merge accepted, update our return value |
|
| 181 *res = some; |
|
| 182 self.spikes[i].α = 0.0; |
186 self.spikes[i].α = 0.0; |
| 183 self.spikes[j].α = 0.0; |
187 self.spikes[j].α = αi + αj; |
| 184 self.spikes.push(δ); |
188 if accept(self) { |
| 185 true |
189 // Merge accepted |
| 186 }, |
190 Some(j) |
| 187 None => { |
191 } else { |
| 188 false |
192 // Merge not accepted, restore modification |
| 189 } |
193 self.spikes[i].α = αi; |
| 190 } |
194 self.spikes[j].α = αj; |
| 191 }*/ |
195 None |
| 192 |
196 } |
| |
197 } |
| |
198 } |
| |
199 } |
| 193 } |
200 } |
| 194 |
201 |
| 195 /// Sorts a vector of indices into `slice` by `compare`. |
202 /// Sorts a vector of indices into `slice` by `compare`. |
| 196 /// |
203 /// |
| 197 /// The closure `compare` operators on references to elements of `slice`. |
204 /// The closure `compare` operators on references to elements of `slice`. |
| 205 } |
212 } |
| 206 |
213 |
| 207 #[replace_float_literals(F::cast_from(literal))] |
214 #[replace_float_literals(F::cast_from(literal))] |
| 208 impl<F : Float> SpikeMerging<F> for DiscreteMeasure<Loc<F, 1>, F> { |
215 impl<F : Float> SpikeMerging<F> for DiscreteMeasure<Loc<F, 1>, F> { |
| 209 |
216 |
| 210 fn do_merge_spikes_radius<G, V>( |
217 fn do_merge_spikes_radius<G>( |
| 211 &mut self, |
218 &mut self, |
| 212 ρ : F, |
219 ρ : F, |
| 213 accept : G |
220 interp : bool, |
| 214 ) -> Option<V> |
221 mut accept : G |
| 215 where G : Fn(&'_ Self) -> Option<V> { |
222 ) -> usize |
| |
223 where G : FnMut(&'_ Self) -> bool { |
| 216 // Sort by coordinate into an indexing array. |
224 // Sort by coordinate into an indexing array. |
| 217 let mut indices = sort_indices_by(&self.spikes, |&δ1, &δ2| { |
225 let mut indices = sort_indices_by(&self.spikes, |&δ1, &δ2| { |
| 218 let &Loc([x1]) = &δ1.x; |
226 let &Loc([x1]) = &δ1.x; |
| 219 let &Loc([x2]) = &δ2.x; |
227 let &Loc([x2]) = &δ2.x; |
| 220 // nan-ignoring ordering of floats |
228 // nan-ignoring ordering of floats |
| 221 NaNLeast(x1).cmp(&NaNLeast(x2)) |
229 NaNLeast(x1).cmp(&NaNLeast(x2)) |
| 222 }); |
230 }); |
| 223 |
231 |
| 224 // Initialise result |
232 // Initialise result |
| 225 let mut res = None; |
233 let mut count = 0; |
| 226 |
234 |
| 227 // Scan consecutive pairs and merge if close enough and accepted by `accept`. |
235 // Scan consecutive pairs and merge if close enough and accepted by `accept`. |
| 228 if indices.len() == 0 { |
236 if indices.len() == 0 { |
| 229 return res |
237 return count |
| 230 } |
238 } |
| 231 for k in 0..(indices.len()-1) { |
239 for k in 0..(indices.len()-1) { |
| 232 let i = indices[k]; |
240 let i = indices[k]; |
| 233 let j = indices[k+1]; |
241 let j = indices[k+1]; |
| 234 let &DeltaMeasure{ x : Loc([xi]), α : αi } = &self.spikes[i]; |
242 let &DeltaMeasure{ x : Loc([xi]), α : αi } = &self.spikes[i]; |
| 235 let &DeltaMeasure{ x : Loc([xj]), α : αj } = &self.spikes[j]; |
243 let &DeltaMeasure{ x : Loc([xj]), α : αj } = &self.spikes[j]; |
| 236 debug_assert!(xi <= xj); |
244 debug_assert!(xi <= xj); |
| 237 // If close enough, attempt merging |
245 // If close enough, attempt merging |
| 238 if αi != 0.0 && αj != 0.0 && xj <= xi + ρ { |
246 if αi != 0.0 && αj != 0.0 && xj <= xi + ρ { |
| 239 if self.attempt_merge(&mut res, i, j, &accept) { |
247 if let Some(l) = self.attempt_merge(i, j, interp, &mut accept) { |
| 240 indices[k+1] = self.spikes.len() - 1; |
248 // For this to work (the debug_assert! to not trigger above), the new |
| 241 } |
249 // coordinate produced by attempt_merge has to be at most xj. |
| 242 } |
250 indices[k+1] = l; |
| 243 } |
251 count += 1 |
| 244 |
252 } |
| 245 res |
253 } |
| |
254 } |
| |
255 |
| |
256 count |
| 246 } |
257 } |
| 247 } |
258 } |
| 248 |
259 |
| 249 /// Orders `δ1` and `δ1` according to the first coordinate. |
260 /// Orders `δ1` and `δ1` according to the first coordinate. |
| 250 fn compare_first_coordinate<F : Float>( |
261 fn compare_first_coordinate<F : Float>( |
| 258 } |
269 } |
| 259 |
270 |
| 260 #[replace_float_literals(F::cast_from(literal))] |
271 #[replace_float_literals(F::cast_from(literal))] |
| 261 impl<F : Float> SpikeMerging<F> for DiscreteMeasure<Loc<F, 2>, F> { |
272 impl<F : Float> SpikeMerging<F> for DiscreteMeasure<Loc<F, 2>, F> { |
| 262 |
273 |
| 263 fn do_merge_spikes_radius<G, V>(&mut self, ρ : F, accept : G) -> Option<V> |
274 fn do_merge_spikes_radius<G>(&mut self, ρ : F, interp : bool, mut accept : G) -> usize |
| 264 where G : Fn(&'_ Self) -> Option<V> { |
275 where G : FnMut(&'_ Self) -> bool { |
| 265 // Sort by first coordinate into an indexing array. |
276 // Sort by first coordinate into an indexing array. |
| 266 let mut indices = sort_indices_by(&self.spikes, compare_first_coordinate); |
277 let mut indices = sort_indices_by(&self.spikes, compare_first_coordinate); |
| 267 |
278 |
| 268 // Initialise result |
279 // Initialise result |
| 269 let mut res = None; |
280 let mut count = 0; |
| 270 let mut start_scan_2nd = 0; |
281 let mut start_scan_2nd = 0; |
| 271 |
282 |
| 272 // Scan in order |
283 // Scan in order |
| 273 if indices.len() == 0 { |
284 if indices.len() == 0 { |
| 274 return res |
285 return count |
| 275 } |
286 } |
| 276 for k in 0..indices.len()-1 { |
287 for k in 0..indices.len()-1 { |
| 277 let i = indices[k]; |
288 let i = indices[k]; |
| 278 let &DeltaMeasure{ x : Loc([xi1, xi2]), α : αi } = &self[i]; |
289 let &DeltaMeasure{ x : Loc([xi1, xi2]), α : αi } = &self[i]; |
| 279 |
290 |
| 322 } |
333 } |
| 323 } |
334 } |
| 324 |
335 |
| 325 // Attempt merging closest close-enough spike |
336 // Attempt merging closest close-enough spike |
| 326 if let Some((l, j, _)) = closest { |
337 if let Some((l, j, _)) = closest { |
| 327 if self.attempt_merge(&mut res, i, j, &accept) { |
338 if let Some(n) = self.attempt_merge(i, j, interp, &mut accept) { |
| 328 // If merge was succesfull, make new spike candidate for merging. |
339 // If merge was succesfull, make new spike candidate for merging. |
| 329 indices[l] = self.spikes.len() - 1; |
340 indices[l] = n; |
| |
341 count += 1; |
| 330 let compare = |i, j| compare_first_coordinate(&self.spikes[i], |
342 let compare = |i, j| compare_first_coordinate(&self.spikes[i], |
| 331 &self.spikes[j]); |
343 &self.spikes[j]); |
| 332 // Re-sort relevant range of indices |
344 // Re-sort relevant range of indices |
| 333 if l < k { |
345 if l < k { |
| 334 indices[l..k].sort_by(|&i, &j| compare(i, j)); |
346 indices[l..k].sort_by(|&i, &j| compare(i, j)); |