1use crate::prelude::*;
2
3#[derive(Clone, Debug, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize))]
25pub struct LatencyEncoder {
26 max_latency: u64,
27 range: (f32, f32),
28}
29
30impl LatencyEncoder {
31 pub fn new(max_latency: u64, range: (f32, f32)) -> Self {
42 Self::try_new(max_latency, range).unwrap_or_else(|error| panic!("{error}"))
43 }
44
45 pub fn try_new(max_latency: u64, range: (f32, f32)) -> Result<Self, EncoderError> {
49 crate::error::validate_range("range", range)?;
50 Ok(Self { max_latency, range })
51 }
52
53 fn normalize(&self, value: f32) -> f64 {
54 let clamped = value.clamp(self.range.0, self.range.1) as f64;
56 let lo = self.range.0 as f64;
57 let hi = self.range.1 as f64;
58 (clamped - lo) / (hi - lo)
59 }
60
61 fn timestamp_for(&self, value: f32) -> u64 {
62 if self.max_latency == 0 {
63 return 0;
64 }
65 if value.is_nan() {
66 return self.max_latency;
67 }
68
69 let normalized = self.normalize(value);
70 ((1.0 - normalized) * self.max_latency as f64).round() as u64
71 }
72
73 fn timestamp_for_with_latency_scale(&self, value: f32, latency_scale: f32) -> u64 {
74 let scaled_latency = ((self.max_latency as f64) * (latency_scale as f64)).round() as u64;
75 if scaled_latency == 0 {
76 return 0;
77 }
78 if value.is_nan() {
79 return scaled_latency;
80 }
81
82 let normalized = self.normalize(value);
83 ((1.0 - normalized) * scaled_latency as f64).round() as u64
84 }
85
86 fn encode_with_latency_scale(&mut self, input: &[f32], latency_scale: f32) -> EncodedOutput {
87 let mut output = EncodedOutput::new();
88 output.spikes.reserve(input.len());
89
90 for (channel, &value) in input.iter().enumerate() {
91 let Ok(channel) = u16::try_from(channel) else {
92 break;
94 };
95 output.spikes.push(SpikeEvent {
96 channel,
97 timestamp: self.timestamp_for_with_latency_scale(value, latency_scale),
98 polarity: true,
99 });
100 }
101
102 output
103 }
104
105 pub fn encode_with_modulators(
109 &mut self,
110 input: &[f32],
111 modulators: &NeuroModulators,
112 gain_curves: &NeuromodulatorGainCurves,
113 ) -> EncodedOutput {
114 <Self as ModulatedEncoder>::encode_with_modulators(self, input, modulators, gain_curves)
115 }
116
117 pub fn encode_step_with_modulators(
119 &mut self,
120 input: &[f32],
121 modulators: &NeuroModulators,
122 gain_curves: &NeuromodulatorGainCurves,
123 ) -> EncodedOutput {
124 <Self as ModulatedEncoder>::encode_step_with_modulators(
125 self,
126 input,
127 modulators,
128 gain_curves,
129 )
130 }
131}
132
133impl Encoder for LatencyEncoder {
134 fn encode(&mut self, input: &[f32]) -> EncodedOutput {
135 let mut output = EncodedOutput::new();
136 output.spikes.reserve(input.len());
137
138 for (channel, &value) in input.iter().enumerate() {
139 let Ok(channel) = u16::try_from(channel) else {
140 break;
142 };
143 output.spikes.push(SpikeEvent {
144 channel,
145 timestamp: self.timestamp_for(value),
146 polarity: true,
147 });
148 }
149
150 output
151 }
152
153 fn encode_step(&mut self, input: &[f32]) -> EncodedOutput {
154 self.encode(input)
155 }
156
157 fn reset(&mut self) {
158 }
160}
161
162impl ModulatedEncoder for LatencyEncoder {
163 fn encode_with_gains(&mut self, input: &[f32], gains: EncodingGains) -> EncodedOutput {
164 self.encode_with_latency_scale(input, gains.sanitize().latency_scale)
165 }
166}
167
168#[cfg(feature = "serde")]
169impl<'de> serde::Deserialize<'de> for LatencyEncoder {
170 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
171 where
172 D: serde::Deserializer<'de>,
173 {
174 #[derive(serde::Deserialize)]
175 struct Helper {
176 max_latency: u64,
177 range: (f32, f32),
178 }
179
180 let helper = Helper::deserialize(deserializer)?;
181
182 Self::try_new(helper.max_latency, helper.range).map_err(serde::de::Error::custom)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn latency_encoder_emits_one_positive_spike_per_channel() {
192 let mut encoder = LatencyEncoder::new(10, (0.0, 1.0));
193
194 let output = encoder.encode(&[0.0, 0.5, 1.0]);
195
196 assert_eq!(output.spikes.len(), 3);
197 assert_eq!(
198 output.spikes,
199 vec![
200 SpikeEvent {
201 channel: 0,
202 timestamp: 10,
203 polarity: true,
204 },
205 SpikeEvent {
206 channel: 1,
207 timestamp: 5,
208 polarity: true,
209 },
210 SpikeEvent {
211 channel: 2,
212 timestamp: 0,
213 polarity: true,
214 },
215 ]
216 );
217 }
218
219 #[test]
220 fn test_latency_encoder_nan() {
221 let mut encoder = LatencyEncoder::new(10, (0.0, 1.0));
222 let output = encoder.encode(&[f32::NAN]);
223 assert_eq!(output.spikes[0].timestamp, 10);
224 }
225
226 #[test]
227 fn test_latency_encoder_reset() {
228 let mut encoder = LatencyEncoder::new(10, (0.0, 1.0));
229 encoder.reset(); }
231
232 #[test]
233 fn latency_encoder_stronger_inputs_fire_earlier() {
234 let mut encoder = LatencyEncoder::new(12, (0.0, 3.0));
235
236 let output = encoder.encode(&[0.5, 1.5, 2.5]);
237
238 assert_eq!(output.spikes.len(), 3);
239 assert!(output.spikes[0].timestamp > output.spikes[1].timestamp);
240 assert!(output.spikes[1].timestamp > output.spikes[2].timestamp);
241 }
242
243 #[test]
244 fn latency_encoder_clamps_inputs_to_range() {
245 let mut encoder = LatencyEncoder::new(8, (2.0, 6.0));
246
247 let output = encoder.encode(&[0.0, 2.0, 4.0, 6.0, 9.0]);
248
249 assert_eq!(
250 output
251 .spikes
252 .iter()
253 .map(|spike| spike.timestamp)
254 .collect::<Vec<_>>(),
255 vec![8, 8, 4, 0, 0]
256 );
257 }
258
259 #[test]
260 fn latency_encoder_encode_step_matches_encode() {
261 let mut encoder = LatencyEncoder::new(20, (-1.0, 1.0));
262 let input = [-1.0, -0.25, 0.75, 1.5];
263
264 let batch = encoder.encode(&input);
265 let step = encoder.encode_step(&input);
266
267 assert_eq!(batch, step);
268 }
269
270 #[test]
271 fn latency_encoder_handles_empty_input() {
272 let mut encoder = LatencyEncoder::new(5, (0.0, 1.0));
273
274 let output = encoder.encode(&[]);
275
276 assert!(output.spikes.is_empty());
277 }
278
279 #[test]
280 fn latency_encoder_nan_maps_to_max_latency() {
281 let mut encoder = LatencyEncoder::new(7, (0.0, 1.0));
282
283 let output = encoder.encode(&[f32::NAN, 1.0]);
284
285 assert_eq!(output.spikes[0].timestamp, 7);
286 assert_eq!(output.spikes[1].timestamp, 0);
287 }
288
289 #[test]
290 #[should_panic(expected = "range must be finite and min must be less than max")]
291 fn latency_encoder_rejects_invalid_range() {
292 let _ = LatencyEncoder::new(5, (1.0, 1.0));
293 }
294
295 #[test]
296 #[should_panic(expected = "range must be finite and min must be less than max")]
297 fn latency_encoder_rejects_infinite_range() {
298 let _ = LatencyEncoder::new(10, (f32::NEG_INFINITY, f32::INFINITY));
299 }
300
301 #[test]
302 fn latency_encoder_truncates_channel_overflow() {
303 let mut encoder = LatencyEncoder::new(1, (0.0, 1.0));
304 let input = vec![0.0f32; (u16::MAX as usize) + 2];
305 let output = encoder.encode(&input);
306 assert_eq!(output.spikes.len(), u16::MAX as usize + 1);
307 }
308
309 #[test]
310 fn latency_encoder_encode_with_modulators_identity() {
311 let mut encoder = LatencyEncoder::new(10, (0.0, 1.0));
312 let curves = NeuromodulatorGainCurves::default();
313 let mods = NeuroModulators::default();
314
315 let plain = encoder.encode(&[0.5]);
316 let modulated = encoder.encode_with_modulators(&[0.5], &mods, &curves);
317
318 assert_eq!(plain.spikes[0].timestamp, modulated.spikes[0].timestamp);
319 }
320
321 #[test]
322 fn latency_encoder_encode_with_modulators_latency_scale() {
323 let mut encoder = LatencyEncoder::new(10, (0.0, 1.0));
324 let curves = NeuromodulatorGainCurves {
325 dopamine: ModulatorGainCurves {
326 latency: Some(GainCurve::new((0.0, 1.0), (0.5, 0.5))),
327 ..Default::default()
328 },
329 ..Default::default()
330 };
331 let mods = NeuroModulators {
332 dopamine: 1.0,
333 ..Default::default()
334 };
335
336 let output = encoder.encode_with_modulators(&[0.5], &mods, &curves);
337 assert_eq!(output.spikes[0].timestamp, 3);
340 }
341
342 #[test]
343 fn latency_encoder_encode_step_with_modulators_matches_encode() {
344 let mut encoder = LatencyEncoder::new(10, (0.0, 1.0));
345 let curves = NeuromodulatorGainCurves::default();
346 let mods = NeuroModulators::default();
347
348 let batch = encoder.encode_with_modulators(&[0.5], &mods, &curves);
349 let step = encoder.encode_step_with_modulators(&[0.5], &mods, &curves);
350
351 assert_eq!(batch, step);
352 }
353
354 #[test]
355 fn latency_encoder_modulators_zero_scale_maps_to_zero() {
356 let mut encoder = LatencyEncoder::new(10, (0.0, 1.0));
357 let curves = NeuromodulatorGainCurves {
358 dopamine: ModulatorGainCurves {
359 latency: Some(GainCurve::new((0.0, 1.0), (1.0, 0.0))),
360 ..Default::default()
361 },
362 ..Default::default()
363 };
364 let mods = NeuroModulators {
365 dopamine: 1.0,
366 ..Default::default()
367 };
368
369 let output = encoder.encode_with_modulators(&[0.5, f32::NAN], &mods, &curves);
370 assert_eq!(output.spikes.len(), 2);
371 assert!(output.spikes.iter().all(|s| s.timestamp == 0));
372 }
373 #[test]
374 fn latency_encoder_supports_zero_max_latency() {
375 let mut encoder = LatencyEncoder::new(0, (0.0, 1.0));
376 let output = encoder.encode(&[0.0, 0.5, 1.0]);
377 assert_eq!(output.spikes.len(), 3);
378 assert!(output.spikes.iter().all(|s| s.timestamp == 0));
379 }
380
381 #[test]
382 fn latency_encoder_try_new_rejects_invalid_configuration() {
383 assert!(LatencyEncoder::try_new(0, (0.0, 1.0)).is_ok());
384 assert_eq!(
385 LatencyEncoder::try_new(1, (1.0, 1.0)).err(),
386 Some(EncoderError::InvalidRange { parameter: "range" })
387 );
388 }
389}