use std::f32;
#[derive(Debug, Clone)]
pub struct PredictiveLayer {
prediction: Vec<f32>,
residual_threshold: f32,
learning_rate: f32,
}
impl PredictiveLayer {
pub fn new(size: usize, threshold: f32) -> Self {
Self {
prediction: vec![0.0; size],
residual_threshold: threshold,
learning_rate: 0.1,
}
}
pub fn with_learning_rate(size: usize, threshold: f32, learning_rate: f32) -> Self {
Self {
prediction: vec![0.0; size],
residual_threshold: threshold,
learning_rate,
}
}
pub fn compute_residual(&self, actual: &[f32]) -> Vec<f32> {
assert_eq!(actual.len(), self.prediction.len(), "Input size mismatch");
actual
.iter()
.zip(self.prediction.iter())
.map(|(a, p)| a - p)
.collect()
}
pub fn should_transmit(&self, actual: &[f32]) -> bool {
let residual = self.compute_residual(actual);
let rms = self.residual_rms(&residual);
rms > self.residual_threshold
}
pub fn update_prediction(&mut self, actual: &[f32], learning_rate: f32) {
assert_eq!(actual.len(), self.prediction.len(), "Input size mismatch");
for (pred, &act) in self.prediction.iter_mut().zip(actual.iter()) {
*pred = (1.0 - learning_rate) * *pred + learning_rate * act;
}
}
pub fn update(&mut self, actual: &[f32]) {
self.update_prediction(actual, self.learning_rate);
}
pub fn residual_gated_write(&mut self, actual: &[f32]) -> Option<Vec<f32>> {
if self.should_transmit(actual) {
let residual = self.compute_residual(actual);
self.update(actual);
Some(residual)
} else {
self.update(actual);
None
}
}
pub fn prediction(&self) -> &[f32] {
&self.prediction
}
pub fn set_threshold(&mut self, threshold: f32) {
self.residual_threshold = threshold;
}
pub fn threshold(&self) -> f32 {
self.residual_threshold
}
fn residual_rms(&self, residual: &[f32]) -> f32 {
if residual.is_empty() {
return 0.0;
}
let sum_squares: f32 = residual.iter().map(|r| r * r).sum();
(sum_squares / residual.len() as f32).sqrt()
}
pub fn compression_stats(&self, attempts: &[bool]) -> f32 {
if attempts.is_empty() {
return 0.0;
}
let transmissions = attempts.iter().filter(|&&x| x).count();
transmissions as f32 / attempts.len() as f32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_predictive_layer() {
let layer = PredictiveLayer::new(10, 0.1);
assert_eq!(layer.prediction.len(), 10);
assert_eq!(layer.residual_threshold, 0.1);
assert!(layer.prediction.iter().all(|&x| x == 0.0));
}
#[test]
fn test_compute_residual() {
let layer = PredictiveLayer::new(3, 0.1);
let actual = vec![1.0, 2.0, 3.0];
let residual = layer.compute_residual(&actual);
assert_eq!(residual, vec![1.0, 2.0, 3.0]); }
#[test]
fn test_update_prediction() {
let mut layer = PredictiveLayer::new(3, 0.1);
let actual = vec![1.0, 2.0, 3.0];
layer.update_prediction(&actual, 0.5);
assert_eq!(layer.prediction, vec![0.5, 1.0, 1.5]);
}
#[test]
fn test_should_transmit() {
let layer = PredictiveLayer::new(4, 0.5);
let small_change = vec![0.1, 0.1, 0.1, 0.1];
assert!(!layer.should_transmit(&small_change));
let large_change = vec![1.0, 1.0, 1.0, 1.0];
assert!(layer.should_transmit(&large_change));
}
#[test]
fn test_residual_gated_write() {
let mut layer = PredictiveLayer::new(4, 0.5);
let small_change = vec![0.1, 0.1, 0.1, 0.1];
let result = layer.residual_gated_write(&small_change);
assert!(result.is_none());
let large_change = vec![1.0, 1.0, 1.0, 1.0];
let result = layer.residual_gated_write(&large_change);
assert!(result.is_some());
let residual = result.unwrap();
assert!(residual.iter().all(|&r| r.abs() > 0.0));
}
#[test]
fn test_prediction_convergence() {
let mut layer = PredictiveLayer::with_learning_rate(3, 0.1, 0.2);
let signal = vec![1.0, 2.0, 3.0];
for _ in 0..50 {
layer.update(&signal);
}
for (pred, &actual) in layer.prediction.iter().zip(signal.iter()) {
assert!(
(pred - actual).abs() < 0.05,
"Prediction {} did not converge to {}",
pred,
actual
);
}
}
#[test]
fn test_compression_ratio() {
let mut layer = PredictiveLayer::new(4, 0.3);
let mut attempts = Vec::new();
let stable_signal = vec![1.0, 1.0, 1.0, 1.0];
for _ in 0..100 {
let transmitted = layer.should_transmit(&stable_signal);
attempts.push(transmitted);
layer.update(&stable_signal);
}
let compression = layer.compression_stats(&attempts);
assert!(
compression < 0.5,
"Compression ratio too low: {}",
compression
);
}
#[test]
fn test_residual_rms() {
let layer = PredictiveLayer::new(4, 0.1);
let residual = vec![1.0, 1.0, 1.0, 1.0];
let rms = layer.residual_rms(&residual);
assert!((rms - 1.0).abs() < 0.001);
let zero_residual = vec![0.0, 0.0, 0.0, 0.0];
let rms = layer.residual_rms(&zero_residual);
assert_eq!(rms, 0.0);
}
#[test]
fn test_bandwidth_reduction() {
let mut layer = PredictiveLayer::with_learning_rate(8, 0.2, 0.3);
let mut transmission_count = 0;
let total_attempts = 1000;
let mut signal = vec![0.0; 8];
for i in 0..total_attempts {
let noise = (i as f32 * 0.01).sin() * 0.1;
signal[0] = 1.0 + noise;
if layer.residual_gated_write(&signal).is_some() {
transmission_count += 1;
}
}
let reduction = 1.0 - (transmission_count as f32 / total_attempts as f32);
assert!(
reduction > 0.5,
"Bandwidth reduction too low: {:.1}%",
reduction * 100.0
);
}
}