1use crate::prelude::*;
2use std::collections::VecDeque;
3
4#[derive(Clone, Debug, PartialEq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize))]
47pub struct TemporalEncoder {
48 history: Vec<VecDeque<f32>>,
49 history_depth: usize,
50 change_thresholds: Vec<(f32, u16)>,
51}
52
53impl TemporalEncoder {
54 pub fn new(
62 history_depth: usize,
63 change_thresholds: Vec<(f32, u16)>,
64 num_channels: usize,
65 ) -> Self {
66 Self::try_new(history_depth, change_thresholds, num_channels)
67 .expect("invalid TemporalEncoder configuration")
68 }
69
70 pub fn try_new(
74 history_depth: usize,
75 change_thresholds: Vec<(f32, u16)>,
76 num_channels: usize,
77 ) -> Result<Self, EncoderError> {
78 if history_depth < 6 {
79 return Err(EncoderError::HistoryDepthTooSmall { minimum: 6 });
80 }
81 for &(threshold, _) in &change_thresholds {
82 crate::error::validate_non_negative_finite("change_threshold", threshold)?;
83 }
84 crate::error::validate_channel_count(num_channels)?;
85 Ok(Self {
86 history: vec![VecDeque::with_capacity(history_depth); num_channels],
87 history_depth,
88 change_thresholds,
89 })
90 }
91
92 fn encode_with_threshold_scale(
93 &mut self,
94 input: &[f32],
95 threshold_scale: f32,
96 ) -> EncodedOutput {
97 let mut output = EncodedOutput::new();
98 for (i, &value) in input.iter().enumerate() {
99 if i >= self.history.len() {
100 break;
101 }
102 let Ok(channel) = u16::try_from(i) else {
103 break;
105 };
106 let channel_history = &mut self.history[i];
107 if channel_history.len() == self.history_depth {
108 channel_history.pop_front();
109 }
110 channel_history.push_back(value);
111
112 if channel_history.len() < 6 {
113 continue;
114 }
115
116 let recent_avg = channel_history.iter().rev().take(3).sum::<f32>() / 3.0;
117 let older_avg = channel_history.iter().rev().skip(3).take(3).sum::<f32>() / 3.0;
118 let change = (recent_avg - older_avg).abs();
119
120 for &(threshold, _spike_val) in self.change_thresholds.iter().rev() {
121 if change > (threshold * threshold_scale).max(0.0) {
122 output.spikes.push(SpikeEvent {
123 channel,
124 timestamp: 0, polarity: true, });
127 break; }
129 }
130 }
131 output
132 }
133
134 pub fn encode_with_modulators(
138 &mut self,
139 input: &[f32],
140 modulators: &NeuroModulators,
141 gain_curves: &NeuromodulatorGainCurves,
142 ) -> EncodedOutput {
143 <Self as ModulatedEncoder>::encode_with_modulators(self, input, modulators, gain_curves)
144 }
145
146 pub fn encode_step_with_modulators(
148 &mut self,
149 input: &[f32],
150 modulators: &NeuroModulators,
151 gain_curves: &NeuromodulatorGainCurves,
152 ) -> EncodedOutput {
153 <Self as ModulatedEncoder>::encode_step_with_modulators(
154 self,
155 input,
156 modulators,
157 gain_curves,
158 )
159 }
160}
161
162impl Encoder for TemporalEncoder {
163 fn encode(&mut self, input: &[f32]) -> EncodedOutput {
164 self.encode_with_threshold_scale(input, 1.0)
165 }
166
167 fn encode_step(&mut self, input: &[f32]) -> EncodedOutput {
168 let safe_input = if input.len() > self.history.len() {
169 &input[..self.history.len()]
170 } else {
171 input
172 };
173 self.encode_with_threshold_scale(safe_input, 1.0)
174 }
175
176 fn reset(&mut self) {
177 for history in self.history.iter_mut() {
178 history.clear();
179 }
180 }
181}
182
183impl ModulatedEncoder for TemporalEncoder {
184 fn encode_with_gains(&mut self, input: &[f32], gains: EncodingGains) -> EncodedOutput {
185 let safe_input = if input.len() > self.history.len() {
186 &input[..self.history.len()]
187 } else {
188 input
189 };
190 self.encode_with_threshold_scale(safe_input, gains.sanitize().threshold_scale)
191 }
192}
193
194#[cfg(feature = "serde")]
195impl<'de> serde::Deserialize<'de> for TemporalEncoder {
196 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
197 where
198 D: serde::Deserializer<'de>,
199 {
200 use std::collections::VecDeque;
201
202 #[derive(serde::Deserialize)]
203 struct Helper {
204 history: Vec<VecDeque<f32>>,
205 history_depth: usize,
206 change_thresholds: Vec<(f32, u16)>,
207 }
208
209 let helper = Helper::deserialize(deserializer)?;
210
211 if helper.history_depth < 6 {
212 return Err(serde::de::Error::custom(
213 EncoderError::HistoryDepthTooSmall { minimum: 6 },
214 ));
215 }
216 crate::error::validate_channel_count(helper.history.len())
217 .map_err(serde::de::Error::custom)?;
218
219 for &(threshold, _) in &helper.change_thresholds {
221 crate::error::validate_non_negative_finite("change_threshold", threshold)
222 .map_err(serde::de::Error::custom)?;
223 }
224
225 for (i, deque) in helper.history.iter().enumerate() {
226 if deque.len() > helper.history_depth {
227 return Err(serde::de::Error::custom(format!(
228 "history channel {} length ({}) exceeds history_depth ({})",
229 i,
230 deque.len(),
231 helper.history_depth
232 )));
233 }
234 }
235
236 Ok(Self {
237 history: helper.history,
238 history_depth: helper.history_depth,
239 change_thresholds: helper.change_thresholds,
240 })
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_temporal_encoder() {
250 let mut encoder = TemporalEncoder::new(6, vec![(2.0, 1), (5.0, 2)], 1);
251 let _output = encoder.encode(&[1.0]);
252 let _output = encoder.encode(&[1.0]);
253 let _output = encoder.encode(&[1.0]);
254 let _output = encoder.encode(&[8.0]);
255 let _output = encoder.encode(&[8.0]);
256 let output = encoder.encode(&[8.0]);
257 assert!(!output.spikes.is_empty());
258 }
259
260 #[test]
261 fn test_temporal_encoder_modulators_reduce_threshold() {
262 let mut encoder = TemporalEncoder::new(6, vec![(4.5, 1)], 1);
263 let modulators = NeuroModulators {
264 tempo: 1.0,
265 ..Default::default()
266 };
267 let gain_curves = NeuromodulatorGainCurves {
268 tempo: ModulatorGainCurves {
269 threshold: Some(GainCurve::new((0.0, 1.0), (1.0, 0.5))),
270 ..Default::default()
271 },
272 ..Default::default()
273 };
274
275 for _ in 0..3 {
276 encoder.encode(&[1.0]);
277 }
278 for _ in 0..2 {
279 encoder.encode(&[5.0]);
280 }
281 assert!(encoder.encode(&[5.0]).spikes.is_empty());
282
283 encoder.reset();
284
285 for _ in 0..3 {
286 encoder.encode_step_with_modulators(&[1.0], &modulators, &gain_curves);
287 }
288 for _ in 0..2 {
289 encoder.encode_step_with_modulators(&[5.0], &modulators, &gain_curves);
290 }
291 let output = encoder.encode_step_with_modulators(&[5.0], &modulators, &gain_curves);
292 assert_eq!(output.spikes.len(), 1);
293 }
294
295 #[test]
296 fn test_temporal_encoder_encode_with_modulators() {
297 let mut encoder = TemporalEncoder::new(6, vec![(4.5, 1)], 1);
298 let modulators = NeuroModulators {
299 tempo: 1.0,
300 ..Default::default()
301 };
302 let gain_curves = NeuromodulatorGainCurves {
303 tempo: ModulatorGainCurves {
304 threshold: Some(GainCurve::new((0.0, 1.0), (1.0, 0.5))),
305 ..Default::default()
306 },
307 ..Default::default()
308 };
309
310 for _ in 0..3 {
311 encoder.encode_with_modulators(&[1.0], &modulators, &gain_curves);
312 }
313 for _ in 0..2 {
314 encoder.encode_with_modulators(&[5.0], &modulators, &gain_curves);
315 }
316 let output = encoder.encode_with_modulators(&[5.0], &modulators, &gain_curves);
317 assert_eq!(output.spikes.len(), 1);
318 }
319
320 #[test]
321 fn test_temporal_encoder_step_longer_input() {
322 let mut encoder = TemporalEncoder::new(6, vec![(4.5, 1)], 2);
323 let output = encoder.encode_step(&[1.0, 2.0, 3.0]);
324 assert!(output.spikes.len() <= 2);
325 }
326
327 #[cfg(feature = "serde")]
328 #[test]
329 fn test_temporal_serde_history_channel_too_long() {
330 let json = r#"{
331 "history": [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
332 "history_depth": 6,
333 "change_thresholds": []
334 }"#;
335 let res: Result<TemporalEncoder, _> = serde_json::from_str(json);
336 assert!(res.is_err());
337 }
338
339 #[cfg(feature = "serde")]
340 #[test]
341 fn test_temporal_serde_rejects_invalid_thresholds() {
342 let negative = r#"{
343 "history": [[]],
344 "history_depth": 6,
345 "change_thresholds": [[-0.5, 1]]
346 }"#;
347 let res: Result<TemporalEncoder, _> = serde_json::from_str(negative);
348 assert!(
349 res.is_err(),
350 "negative change_threshold must fail deserialize"
351 );
352
353 let ok = r#"{
354 "history": [[]],
355 "history_depth": 6,
356 "change_thresholds": [[0.0, 1], [1.5, 2]]
357 }"#;
358 let res: Result<TemporalEncoder, _> = serde_json::from_str(ok);
359 assert!(res.is_ok());
360 }
361 #[test]
362 fn test_temporal_encoder_try_new_validation() {
363 assert_eq!(
364 TemporalEncoder::try_new(5, vec![(1.0, 1)], 1).err(),
365 Some(EncoderError::HistoryDepthTooSmall { minimum: 6 })
366 );
367 assert_eq!(
368 TemporalEncoder::try_new(6, vec![(1.0, 1)], u16::MAX as usize + 2).err(),
369 Some(EncoderError::NumChannelsTooLarge)
370 );
371 assert_eq!(
372 TemporalEncoder::try_new(6, vec![(f32::NAN, 1)], 1).err(),
373 Some(EncoderError::NonNegativeFinite {
374 parameter: "change_threshold"
375 })
376 );
377 assert_eq!(
378 TemporalEncoder::try_new(6, vec![(-0.5, 1)], 1).err(),
379 Some(EncoderError::NonNegativeFinite {
380 parameter: "change_threshold"
381 })
382 );
383 assert!(TemporalEncoder::try_new(6, vec![(0.0, 1)], 1).is_ok());
384 }
385}