use crate::prelude::*;
use std::collections::VecDeque;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TemporalEncoder {
history: Vec<VecDeque<f32>>,
history_depth: usize,
change_thresholds: Vec<(f32, u16)>,
}
impl TemporalEncoder {
pub fn new(
history_depth: usize,
change_thresholds: Vec<(f32, u16)>,
num_channels: usize,
) -> Self {
Self::try_new(history_depth, change_thresholds, num_channels)
.expect("invalid TemporalEncoder configuration")
}
pub fn try_new(
history_depth: usize,
change_thresholds: Vec<(f32, u16)>,
num_channels: usize,
) -> Result<Self, EncoderError> {
if history_depth < 6 {
return Err(EncoderError::HistoryDepthTooSmall { minimum: 6 });
}
for &(threshold, _) in &change_thresholds {
crate::error::validate_non_negative_finite("change_threshold", threshold)?;
}
crate::error::validate_channel_count(num_channels)?;
Ok(Self {
history: vec![VecDeque::with_capacity(history_depth); num_channels],
history_depth,
change_thresholds,
})
}
fn encode_with_threshold_scale(
&mut self,
input: &[f32],
threshold_scale: f32,
) -> EncodedOutput {
let mut output = EncodedOutput::new();
for (i, &value) in input.iter().enumerate() {
if i >= self.history.len() {
break;
}
let Ok(channel) = u16::try_from(i) else {
break;
};
let channel_history = &mut self.history[i];
if channel_history.len() == self.history_depth {
channel_history.pop_front();
}
channel_history.push_back(value);
if channel_history.len() < 6 {
continue;
}
let recent_avg = channel_history.iter().rev().take(3).sum::<f32>() / 3.0;
let older_avg = channel_history.iter().rev().skip(3).take(3).sum::<f32>() / 3.0;
let change = (recent_avg - older_avg).abs();
for &(threshold, _spike_val) in self.change_thresholds.iter().rev() {
if change > (threshold * threshold_scale).max(0.0) {
output.spikes.push(SpikeEvent {
channel,
timestamp: 0, polarity: true, });
break; }
}
}
output
}
pub fn encode_with_modulators(
&mut self,
input: &[f32],
modulators: &NeuroModulators,
gain_curves: &NeuromodulatorGainCurves,
) -> EncodedOutput {
<Self as ModulatedEncoder>::encode_with_modulators(self, input, modulators, gain_curves)
}
pub fn encode_step_with_modulators(
&mut self,
input: &[f32],
modulators: &NeuroModulators,
gain_curves: &NeuromodulatorGainCurves,
) -> EncodedOutput {
<Self as ModulatedEncoder>::encode_step_with_modulators(
self,
input,
modulators,
gain_curves,
)
}
}
impl Encoder for TemporalEncoder {
fn encode(&mut self, input: &[f32]) -> EncodedOutput {
self.encode_with_threshold_scale(input, 1.0)
}
fn encode_step(&mut self, input: &[f32]) -> EncodedOutput {
let safe_input = if input.len() > self.history.len() {
&input[..self.history.len()]
} else {
input
};
self.encode_with_threshold_scale(safe_input, 1.0)
}
fn reset(&mut self) {
for history in self.history.iter_mut() {
history.clear();
}
}
}
impl ModulatedEncoder for TemporalEncoder {
fn encode_with_gains(&mut self, input: &[f32], gains: EncodingGains) -> EncodedOutput {
let safe_input = if input.len() > self.history.len() {
&input[..self.history.len()]
} else {
input
};
self.encode_with_threshold_scale(safe_input, gains.sanitize().threshold_scale)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for TemporalEncoder {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::collections::VecDeque;
#[derive(serde::Deserialize)]
struct Helper {
history: Vec<VecDeque<f32>>,
history_depth: usize,
change_thresholds: Vec<(f32, u16)>,
}
let helper = Helper::deserialize(deserializer)?;
if helper.history_depth < 6 {
return Err(serde::de::Error::custom(
EncoderError::HistoryDepthTooSmall { minimum: 6 },
));
}
crate::error::validate_channel_count(helper.history.len())
.map_err(serde::de::Error::custom)?;
for &(threshold, _) in &helper.change_thresholds {
crate::error::validate_non_negative_finite("change_threshold", threshold)
.map_err(serde::de::Error::custom)?;
}
for (i, deque) in helper.history.iter().enumerate() {
if deque.len() > helper.history_depth {
return Err(serde::de::Error::custom(format!(
"history channel {} length ({}) exceeds history_depth ({})",
i,
deque.len(),
helper.history_depth
)));
}
}
Ok(Self {
history: helper.history,
history_depth: helper.history_depth,
change_thresholds: helper.change_thresholds,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_temporal_encoder() {
let mut encoder = TemporalEncoder::new(6, vec![(2.0, 1), (5.0, 2)], 1);
let _output = encoder.encode(&[1.0]);
let _output = encoder.encode(&[1.0]);
let _output = encoder.encode(&[1.0]);
let _output = encoder.encode(&[8.0]);
let _output = encoder.encode(&[8.0]);
let output = encoder.encode(&[8.0]);
assert!(!output.spikes.is_empty());
}
#[test]
fn test_temporal_encoder_modulators_reduce_threshold() {
let mut encoder = TemporalEncoder::new(6, vec![(4.5, 1)], 1);
let modulators = NeuroModulators {
tempo: 1.0,
..Default::default()
};
let gain_curves = NeuromodulatorGainCurves {
tempo: ModulatorGainCurves {
threshold: Some(GainCurve::new((0.0, 1.0), (1.0, 0.5))),
..Default::default()
},
..Default::default()
};
for _ in 0..3 {
encoder.encode(&[1.0]);
}
for _ in 0..2 {
encoder.encode(&[5.0]);
}
assert!(encoder.encode(&[5.0]).spikes.is_empty());
encoder.reset();
for _ in 0..3 {
encoder.encode_step_with_modulators(&[1.0], &modulators, &gain_curves);
}
for _ in 0..2 {
encoder.encode_step_with_modulators(&[5.0], &modulators, &gain_curves);
}
let output = encoder.encode_step_with_modulators(&[5.0], &modulators, &gain_curves);
assert_eq!(output.spikes.len(), 1);
}
#[test]
fn test_temporal_encoder_encode_with_modulators() {
let mut encoder = TemporalEncoder::new(6, vec![(4.5, 1)], 1);
let modulators = NeuroModulators {
tempo: 1.0,
..Default::default()
};
let gain_curves = NeuromodulatorGainCurves {
tempo: ModulatorGainCurves {
threshold: Some(GainCurve::new((0.0, 1.0), (1.0, 0.5))),
..Default::default()
},
..Default::default()
};
for _ in 0..3 {
encoder.encode_with_modulators(&[1.0], &modulators, &gain_curves);
}
for _ in 0..2 {
encoder.encode_with_modulators(&[5.0], &modulators, &gain_curves);
}
let output = encoder.encode_with_modulators(&[5.0], &modulators, &gain_curves);
assert_eq!(output.spikes.len(), 1);
}
#[test]
fn test_temporal_encoder_step_longer_input() {
let mut encoder = TemporalEncoder::new(6, vec![(4.5, 1)], 2);
let output = encoder.encode_step(&[1.0, 2.0, 3.0]);
assert!(output.spikes.len() <= 2);
}
#[cfg(feature = "serde")]
#[test]
fn test_temporal_serde_history_channel_too_long() {
let json = r#"{
"history": [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
"history_depth": 6,
"change_thresholds": []
}"#;
let res: Result<TemporalEncoder, _> = serde_json::from_str(json);
assert!(res.is_err());
}
#[cfg(feature = "serde")]
#[test]
fn test_temporal_serde_rejects_invalid_thresholds() {
let negative = r#"{
"history": [[]],
"history_depth": 6,
"change_thresholds": [[-0.5, 1]]
}"#;
let res: Result<TemporalEncoder, _> = serde_json::from_str(negative);
assert!(
res.is_err(),
"negative change_threshold must fail deserialize"
);
let ok = r#"{
"history": [[]],
"history_depth": 6,
"change_thresholds": [[0.0, 1], [1.5, 2]]
}"#;
let res: Result<TemporalEncoder, _> = serde_json::from_str(ok);
assert!(res.is_ok());
}
#[test]
fn test_temporal_encoder_try_new_validation() {
assert_eq!(
TemporalEncoder::try_new(5, vec![(1.0, 1)], 1).err(),
Some(EncoderError::HistoryDepthTooSmall { minimum: 6 })
);
assert_eq!(
TemporalEncoder::try_new(6, vec![(1.0, 1)], u16::MAX as usize + 2).err(),
Some(EncoderError::NumChannelsTooLarge)
);
assert_eq!(
TemporalEncoder::try_new(6, vec![(f32::NAN, 1)], 1).err(),
Some(EncoderError::NonNegativeFinite {
parameter: "change_threshold"
})
);
assert_eq!(
TemporalEncoder::try_new(6, vec![(-0.5, 1)], 1).err(),
Some(EncoderError::NonNegativeFinite {
parameter: "change_threshold"
})
);
assert!(TemporalEncoder::try_new(6, vec![(0.0, 1)], 1).is_ok());
}
}