1use crate::prelude::*;
2use std::collections::VecDeque;
3use std::fmt;
4
5#[non_exhaustive]
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum PredictiveEncoderError {
9 HistoryDepthTooSmall,
11 NumChannelsTooLarge,
15 InvalidDeviationThreshold,
17}
18
19impl fmt::Display for PredictiveEncoderError {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 Self::HistoryDepthTooSmall => write!(f, "history_depth must be at least 5"),
23 Self::NumChannelsTooLarge => write!(
24 f,
25 "num_channels exceeds u16::MAX as usize + 1 (max addressable spike channels)"
26 ),
27 Self::InvalidDeviationThreshold => {
28 write!(f, "deviation_threshold must be finite and non-negative")
29 }
30 }
31 }
32}
33
34impl std::error::Error for PredictiveEncoderError {}
35
36impl From<PredictiveEncoderError> for EncoderError {
37 fn from(error: PredictiveEncoderError) -> Self {
38 match error {
39 PredictiveEncoderError::HistoryDepthTooSmall => {
40 EncoderError::HistoryDepthTooSmall { minimum: 5 }
41 }
42 PredictiveEncoderError::NumChannelsTooLarge => EncoderError::NumChannelsTooLarge,
43 PredictiveEncoderError::InvalidDeviationThreshold => EncoderError::NonNegativeFinite {
44 parameter: "deviation_threshold",
45 },
46 }
47 }
48}
49
50#[derive(Clone, Debug, PartialEq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize))]
106pub struct PredictiveEncoder {
107 history: Vec<VecDeque<f32>>,
108 thresholds: Vec<f32>,
109 history_depth: usize,
110 deviation_thresholds: Vec<(f32, u16)>,
111}
112
113impl PredictiveEncoder {
114 pub fn new(
128 history_depth: usize,
129 deviation_thresholds: Vec<(f32, u16)>,
130 num_channels: usize,
131 ) -> Result<Self, PredictiveEncoderError> {
132 Self::try_new(history_depth, deviation_thresholds, num_channels).map_err(
133 |error| match error {
134 EncoderError::HistoryDepthTooSmall { .. } => {
135 PredictiveEncoderError::HistoryDepthTooSmall
136 }
137 EncoderError::NumChannelsTooLarge => PredictiveEncoderError::NumChannelsTooLarge,
138 EncoderError::NonNegativeFinite {
139 parameter: "deviation_threshold",
140 } => PredictiveEncoderError::InvalidDeviationThreshold,
141 other => panic!("unexpected EncoderError from PredictiveEncoder::try_new: {other}"),
142 },
143 )
144 }
145
146 pub fn try_new(
153 history_depth: usize,
154 deviation_thresholds: Vec<(f32, u16)>,
155 num_channels: usize,
156 ) -> Result<Self, EncoderError> {
157 if history_depth < 5 {
158 return Err(EncoderError::HistoryDepthTooSmall { minimum: 5 });
159 }
160 for &(threshold, _) in &deviation_thresholds {
161 crate::error::validate_non_negative_finite("deviation_threshold", threshold)?;
162 }
163 crate::error::validate_channel_count(num_channels)?;
165 Ok(Self {
166 history: vec![VecDeque::with_capacity(history_depth); num_channels],
167 thresholds: vec![0.0; num_channels],
168 history_depth,
169 deviation_thresholds,
170 })
171 }
172
173 fn encode_with_threshold_scale(
174 &mut self,
175 input: &[f32],
176 threshold_scale: f32,
177 ) -> EncodedOutput {
178 let mut output = EncodedOutput::new();
179 for (i, &value) in input.iter().enumerate() {
180 if i >= self.history.len() {
181 break;
182 }
183 let channel_history = &mut self.history[i];
184
185 if channel_history.len() < 5 {
187 channel_history.push_back(value);
188
189 if channel_history.len() == 5 {
190 self.thresholds[i] = channel_history.iter().rev().take(5).sum::<f32>() / 5.0;
191 }
192
193 continue;
194 }
195
196 let prediction = self.thresholds[i];
197 let error = value - prediction;
198 let deviation = error.abs();
199
200 for &(threshold, _spike_val) in self.deviation_thresholds.iter().rev() {
201 if deviation > (threshold * threshold_scale).max(0.0) {
202 let Ok(channel) = u16::try_from(i) else {
203 break;
204 };
205 output.spikes.push(SpikeEvent {
206 channel,
207 timestamp: 0,
208 polarity: error >= 0.0,
209 });
210 break;
211 }
212 }
213
214 if channel_history.len() == self.history_depth {
215 channel_history.pop_front();
216 }
217 channel_history.push_back(value);
218
219 let recent_avg = channel_history.iter().rev().take(5).sum::<f32>() / 5.0;
220 self.thresholds[i] = 0.9 * self.thresholds[i] + 0.1 * recent_avg;
221 }
222 output
223 }
224
225 pub fn encode_with_modulators(
229 &mut self,
230 input: &[f32],
231 modulators: &NeuroModulators,
232 gain_curves: &NeuromodulatorGainCurves,
233 ) -> EncodedOutput {
234 <Self as ModulatedEncoder>::encode_with_modulators(self, input, modulators, gain_curves)
235 }
236
237 pub fn encode_step_with_modulators(
239 &mut self,
240 input: &[f32],
241 modulators: &NeuroModulators,
242 gain_curves: &NeuromodulatorGainCurves,
243 ) -> EncodedOutput {
244 <Self as ModulatedEncoder>::encode_step_with_modulators(
245 self,
246 input,
247 modulators,
248 gain_curves,
249 )
250 }
251}
252
253impl Encoder for PredictiveEncoder {
254 fn encode(&mut self, input: &[f32]) -> EncodedOutput {
255 self.encode_with_threshold_scale(input, 1.0)
256 }
257
258 fn encode_step(&mut self, input: &[f32]) -> EncodedOutput {
259 let safe_input = if input.len() > self.history.len() {
260 &input[..self.history.len()]
261 } else {
262 input
263 };
264 self.encode_with_threshold_scale(safe_input, 1.0)
265 }
266
267 fn reset(&mut self) {
268 for history in self.history.iter_mut() {
269 history.clear();
270 }
271 for threshold in self.thresholds.iter_mut() {
272 *threshold = 0.0;
273 }
274 }
275}
276
277impl ModulatedEncoder for PredictiveEncoder {
278 fn encode_with_gains(&mut self, input: &[f32], gains: EncodingGains) -> EncodedOutput {
279 let safe_input = if input.len() > self.history.len() {
280 &input[..self.history.len()]
281 } else {
282 input
283 };
284 self.encode_with_threshold_scale(safe_input, gains.sanitize().threshold_scale)
285 }
286}
287
288#[cfg(feature = "serde")]
289impl<'de> serde::Deserialize<'de> for PredictiveEncoder {
290 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
291 where
292 D: serde::Deserializer<'de>,
293 {
294 use std::collections::VecDeque;
295
296 #[derive(serde::Deserialize)]
297 struct Helper {
298 history: Vec<VecDeque<f32>>,
299 thresholds: Vec<f32>,
300 history_depth: usize,
301 deviation_thresholds: Vec<(f32, u16)>,
302 }
303
304 let helper = Helper::deserialize(deserializer)?;
305
306 if helper.history.len() != helper.thresholds.len() {
307 return Err(serde::de::Error::custom(format!(
308 "mismatched history length ({}) and thresholds length ({})",
309 helper.history.len(),
310 helper.thresholds.len()
311 )));
312 }
313
314 if helper.history.len() > u16::MAX as usize + 1 {
316 return Err(serde::de::Error::custom(
317 "num_channels exceeds u16::MAX as usize + 1 (max addressable spike channels)",
318 ));
319 }
320
321 if helper.history_depth < 5 {
322 return Err(serde::de::Error::custom("history_depth must be at least 5"));
323 }
324
325 for &(threshold, _) in &helper.deviation_thresholds {
327 crate::error::validate_non_negative_finite("deviation_threshold", threshold)
328 .map_err(serde::de::Error::custom)?;
329 }
330
331 for (i, deque) in helper.history.iter().enumerate() {
332 if deque.len() > helper.history_depth {
333 return Err(serde::de::Error::custom(format!(
334 "history channel {} length ({}) exceeds history_depth ({})",
335 i,
336 deque.len(),
337 helper.history_depth
338 )));
339 }
340 }
341
342 Ok(Self {
343 history: helper.history,
344 thresholds: helper.thresholds,
345 history_depth: helper.history_depth,
346 deviation_thresholds: helper.deviation_thresholds,
347 })
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn test_predictive_encoder_rejects_small_history_depth() {
357 let err = PredictiveEncoder::new(4, vec![(2.0, 1)], 1).err();
358 assert_eq!(err, Some(PredictiveEncoderError::HistoryDepthTooSmall));
359 assert_eq!(
360 PredictiveEncoderError::HistoryDepthTooSmall.to_string(),
361 "history_depth must be at least 5"
362 );
363 assert!(PredictiveEncoder::new(5, vec![(2.0, 1)], 1).is_ok());
364 assert!(PredictiveEncoder::new(0, vec![(2.0, 1)], 1).is_err());
365
366 assert_eq!(
368 PredictiveEncoder::try_new(4, vec![(2.0, 1)], 1).err(),
369 Some(EncoderError::HistoryDepthTooSmall { minimum: 5 })
370 );
371 assert_eq!(
372 PredictiveEncoder::try_new(5, vec![(2.0, 1)], u16::MAX as usize + 2).err(),
373 Some(EncoderError::NumChannelsTooLarge)
374 );
375 assert_eq!(
376 PredictiveEncoder::try_new(5, vec![(f32::NAN, 1)], 1).err(),
377 Some(EncoderError::NonNegativeFinite {
378 parameter: "deviation_threshold"
379 })
380 );
381 assert_eq!(
382 PredictiveEncoder::try_new(5, vec![(-1.0, 1)], 1).err(),
383 Some(EncoderError::NonNegativeFinite {
384 parameter: "deviation_threshold"
385 })
386 );
387 assert_eq!(
388 PredictiveEncoder::new(5, vec![(-1.0, 1)], 1).err(),
389 Some(PredictiveEncoderError::InvalidDeviationThreshold)
390 );
391 assert!(PredictiveEncoder::try_new(5, vec![(0.0, 1)], 1).is_ok());
392 }
393
394 #[test]
395 fn test_predictive_encoder_num_channels_u16_range() {
396 let max_ok = u16::MAX as usize + 1;
397 let first_bad = max_ok + 1;
398
399 assert_eq!(
401 PredictiveEncoder::new(5, vec![(0.2, 1)], first_bad).err(),
402 Some(PredictiveEncoderError::NumChannelsTooLarge)
403 );
404 assert!(
405 PredictiveEncoderError::NumChannelsTooLarge
406 .to_string()
407 .contains("u16::MAX")
408 );
409
410 let encoder =
412 PredictiveEncoder::new(5, vec![(0.2, 1)], max_ok).expect("max u16 channel count");
413 assert_eq!(encoder.history.len(), max_ok);
414 assert_eq!(encoder.thresholds.len(), max_ok);
415 }
416
417 #[test]
418 fn test_predictive_encoder() {
419 let mut encoder =
420 PredictiveEncoder::new(5, vec![(2.0, 1)], 1).expect("valid PredictiveEncoder");
421 let _output = encoder.encode(&[1.0]);
422 let _output = encoder.encode(&[1.0]);
423 let _output = encoder.encode(&[1.0]);
424 let _output = encoder.encode(&[1.0]);
425 let _output = encoder.encode(&[1.0]);
426 let output = encoder.encode(&[10.0]);
427 assert!(!output.spikes.is_empty());
428 }
429
430 #[test]
431 fn test_predictive_encoder_constant_signal_has_no_cold_start_burst() {
432 let mut encoder =
433 PredictiveEncoder::new(5, vec![(0.5, 1)], 1).expect("valid PredictiveEncoder");
434
435 for _ in 0..16 {
436 let output = encoder.encode(&[42.0]);
437 assert!(output.spikes.is_empty());
438 }
439 }
440
441 #[test]
442 fn test_predictive_encoder_short_history_warms_up_without_spikes() {
443 let mut encoder =
444 PredictiveEncoder::new(5, vec![(0.5, 1)], 1).expect("valid PredictiveEncoder");
445
446 for _ in 0..5 {
447 let output = encoder.encode(&[10.0]);
448 assert!(output.spikes.is_empty());
449 }
450
451 assert_eq!(encoder.history[0].len(), 5);
452 assert_eq!(encoder.thresholds[0], 10.0);
453 }
454
455 #[test]
456 fn test_predictive_encoder_positive_step_preserves_positive_polarity() {
457 let mut encoder =
458 PredictiveEncoder::new(5, vec![(1.0, 1)], 1).expect("valid PredictiveEncoder");
459 for _ in 0..5 {
460 assert!(encoder.encode(&[1.0]).spikes.is_empty());
461 }
462
463 let output = encoder.encode(&[4.0]);
464 assert_eq!(output.spikes.len(), 1);
465 assert!(output.spikes[0].polarity);
466 }
467
468 #[test]
469 fn test_predictive_encoder_negative_step_preserves_negative_polarity() {
470 let mut encoder =
471 PredictiveEncoder::new(5, vec![(1.0, 1)], 1).expect("valid PredictiveEncoder");
472 for _ in 0..5 {
473 assert!(encoder.encode(&[4.0]).spikes.is_empty());
474 }
475
476 let output = encoder.encode(&[1.0]);
477 assert_eq!(output.spikes.len(), 1);
478 assert!(!output.spikes[0].polarity);
479 }
480
481 #[test]
482 fn test_predictive_encoder_trend_uses_prior_prediction_before_update() {
483 let mut encoder =
484 PredictiveEncoder::new(5, vec![(0.75, 1)], 1).expect("valid PredictiveEncoder");
485 for value in [1.0, 2.0, 3.0, 4.0, 5.0] {
486 assert!(encoder.encode(&[value]).spikes.is_empty());
487 }
488 assert_eq!(encoder.thresholds[0], 3.0);
489
490 let output = encoder.encode(&[6.0]);
491 assert_eq!(output.spikes.len(), 1);
492 assert!(output.spikes[0].polarity);
493 assert_eq!(encoder.thresholds[0], 3.1);
494 }
495
496 #[test]
497 fn test_predictive_encoder_reset() {
498 let mut encoder =
499 PredictiveEncoder::new(5, vec![(2.0, 1)], 2).expect("valid PredictiveEncoder");
500 for _ in 0..6 {
501 encoder.encode(&[1.0, 2.0]);
502 }
503 encoder.reset();
504 assert!(encoder.history.iter().all(|h| h.is_empty()));
505 assert!(encoder.thresholds.iter().all(|&t| t == 0.0));
506 }
507
508 #[test]
509 fn test_predictive_encoder_reset_restarts_warmup_without_spikes() {
510 let mut encoder =
511 PredictiveEncoder::new(5, vec![(0.5, 1)], 1).expect("valid PredictiveEncoder");
512 for _ in 0..5 {
513 assert!(encoder.encode(&[1.0]).spikes.is_empty());
514 }
515 assert_eq!(encoder.encode(&[10.0]).spikes.len(), 1);
516
517 encoder.reset();
518
519 for _ in 0..5 {
520 assert!(encoder.encode(&[10.0]).spikes.is_empty());
521 }
522 assert_eq!(encoder.thresholds[0], 10.0);
523 }
524
525 #[test]
526 fn test_predictive_encoder_multi_channel() {
527 let mut encoder =
528 PredictiveEncoder::new(5, vec![(2.0, 1)], 3).expect("valid PredictiveEncoder");
529 for _ in 0..6 {
530 encoder.encode(&[1.0, 2.0, 3.0]);
531 }
532 let output = encoder.encode(&[10.0, 20.0, 30.0]);
533 assert!(!output.spikes.is_empty());
535 }
536
537 #[test]
538 fn test_predictive_encoder_input_truncation() {
539 let mut encoder =
540 PredictiveEncoder::new(5, vec![(2.0, 1)], 2).expect("valid PredictiveEncoder");
541 for _ in 0..6 {
542 encoder.encode(&[1.0, 2.0, 3.0, 4.0]);
544 }
545 let output = encoder.encode(&[10.0, 20.0, 30.0, 40.0]);
547 assert!(output.spikes.len() <= 2);
548 }
549
550 #[test]
551 fn test_predictive_encoder_step_input_truncation() {
552 let mut encoder =
553 PredictiveEncoder::new(5, vec![(2.0, 1)], 2).expect("valid PredictiveEncoder");
554 for _ in 0..6 {
555 encoder.encode_step(&[1.0, 2.0, 3.0]);
556 }
557 let output = encoder.encode_step(&[10.0, 20.0, 30.0]);
558 assert!(output.spikes.len() <= 2);
559 }
560
561 #[test]
562 fn test_predictive_encoder_encode_with_modulators() {
563 let mut encoder =
564 PredictiveEncoder::new(5, vec![(5.0, 1)], 1).expect("valid PredictiveEncoder");
565 let mods = NeuroModulators {
566 acetylcholine: 1.0,
567 ..Default::default()
568 };
569 let curves = NeuromodulatorGainCurves {
570 acetylcholine: ModulatorGainCurves {
571 threshold: Some(GainCurve::new((0.0, 1.0), (1.0, 0.5))),
572 ..Default::default()
573 },
574 ..Default::default()
575 };
576 for _ in 0..5 {
577 encoder.encode_with_modulators(&[1.0], &mods, &curves);
578 }
579 let output = encoder.encode_with_modulators(&[5.0], &mods, &curves);
580 assert_eq!(output.spikes.len(), 1);
581 }
582
583 #[test]
584 fn test_predictive_encoder_modulators_reduce_threshold() {
585 let mut encoder =
586 PredictiveEncoder::new(5, vec![(5.0, 1)], 1).expect("valid PredictiveEncoder");
587 let modulators = NeuroModulators {
588 acetylcholine: 1.0,
589 ..Default::default()
590 };
591 let gain_curves = NeuromodulatorGainCurves {
592 acetylcholine: ModulatorGainCurves {
593 threshold: Some(GainCurve::new((0.0, 1.0), (1.0, 0.5))),
594 ..Default::default()
595 },
596 ..Default::default()
597 };
598
599 for _ in 0..5 {
600 encoder.encode(&[1.0]);
601 }
602 assert!(encoder.encode(&[5.0]).spikes.is_empty());
603
604 encoder.reset();
605
606 for _ in 0..5 {
607 encoder.encode_step_with_modulators(&[1.0], &modulators, &gain_curves);
608 }
609 let output = encoder.encode_step_with_modulators(&[5.0], &modulators, &gain_curves);
610 assert_eq!(output.spikes.len(), 1);
611 }
612
613 #[test]
614 fn test_predictive_encoder_encode_with_modulators_truncate() {
615 let mut encoder =
616 PredictiveEncoder::new(5, vec![(5.0, 1)], 1).expect("valid PredictiveEncoder");
617 let mods = NeuroModulators {
618 acetylcholine: 1.0,
619 ..Default::default()
620 };
621 let curves = NeuromodulatorGainCurves {
622 acetylcholine: ModulatorGainCurves {
623 threshold: Some(GainCurve::new((0.0, 1.0), (1.0, 0.5))),
624 ..Default::default()
625 },
626 ..Default::default()
627 };
628 for _ in 0..5 {
629 encoder.encode_with_modulators(&[1.0, 2.0], &mods, &curves);
630 }
631 let output = encoder.encode_with_modulators(&[5.0, 6.0], &mods, &curves);
632 assert_eq!(output.spikes.len(), 1);
633 }
634
635 #[test]
636 fn test_predictive_encoder_step_shorter_input() {
637 let mut encoder =
638 PredictiveEncoder::new(5, vec![(2.0, 1)], 2).expect("valid PredictiveEncoder");
639 for _ in 0..6 {
640 encoder.encode_step(&[1.0]);
641 }
642 let output = encoder.encode_step(&[10.0]);
643 assert!(!output.spikes.is_empty());
644 }
645
646 #[cfg(feature = "serde")]
647 #[test]
648 fn test_predictive_serde_history_channel_too_long() {
649 let json = r#"{
650 "history": [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
651 "thresholds": [0.0],
652 "history_depth": 5,
653 "deviation_thresholds": []
654 }"#;
655 let res: Result<PredictiveEncoder, _> = serde_json::from_str(json);
656 assert!(res.is_err());
657 }
658
659 #[cfg(feature = "serde")]
660 #[test]
661 fn test_predictive_serde_rejects_too_many_channels() {
662 let max_ok = u16::MAX as usize + 1;
663 let first_bad = max_ok + 1;
664
665 let history: Vec<Vec<f32>> = vec![vec![]; first_bad];
667 let thresholds = vec![0.0f32; first_bad];
668 let value = serde_json::json!({
669 "history": history,
670 "thresholds": thresholds,
671 "history_depth": 5,
672 "deviation_thresholds": [[0.2, 1]],
673 });
674 let res: Result<PredictiveEncoder, _> = serde_json::from_value(value);
675 assert!(res.is_err());
676 let err = res.err().unwrap().to_string();
677 assert!(
678 err.contains("u16::MAX") || err.contains("num_channels"),
679 "unexpected error: {err}"
680 );
681
682 let history_ok: Vec<Vec<f32>> = vec![vec![]; max_ok];
684 let thresholds_ok = vec![0.0f32; max_ok];
685 let value_ok = serde_json::json!({
686 "history": history_ok,
687 "thresholds": thresholds_ok,
688 "history_depth": 5,
689 "deviation_thresholds": [[0.2, 1]],
690 });
691 let enc: PredictiveEncoder =
692 serde_json::from_value(value_ok).expect("max channel count deserializes");
693 assert_eq!(enc.history.len(), max_ok);
694 assert_eq!(enc.thresholds.len(), max_ok);
695 }
696}