use crate::{evaluate_linear_fixed, fit_linear_fixed};
use alice_core::compression;
pub struct CompressedModelBatch {
pub data: Vec<u8>,
pub count: usize,
}
pub fn compress_coefficients(coefficients: &[(i32, i32)]) -> std::io::Result<CompressedModelBatch> {
let floats: Vec<f32> = coefficients
.iter()
.flat_map(|&(slope, intercept)| [slope as f32, intercept as f32])
.collect();
let data = compression::compress_residual_quantized(&floats, 16, 6)?;
Ok(CompressedModelBatch {
data,
count: coefficients.len(),
})
}
pub fn decompress_coefficients(batch: &CompressedModelBatch) -> std::io::Result<Vec<(i32, i32)>> {
let floats = compression::decompress_residual_quantized(&batch.data)?;
let coefficients: Vec<(i32, i32)> = floats
.chunks_exact(2)
.map(|pair| (pair[0] as i32, pair[1] as i32))
.collect();
Ok(coefficients)
}
pub fn fit_and_compress(sensor_batches: &[&[i32]]) -> std::io::Result<CompressedModelBatch> {
let coefficients: Vec<(i32, i32)> = sensor_batches
.iter()
.map(|data| fit_linear_fixed(data))
.collect();
compress_coefficients(&coefficients)
}
pub fn decompress_and_evaluate(batch: &CompressedModelBatch, x: i32) -> std::io::Result<Vec<i32>> {
let coefficients = decompress_coefficients(batch)?;
Ok(coefficients
.iter()
.map(|&(slope, intercept)| evaluate_linear_fixed(slope, intercept, x))
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Q16_SHIFT;
#[test]
fn test_compress_decompress_roundtrip() {
let coefficients: Vec<(i32, i32)> = (0..10)
.map(|i| {
(i * 10 * (1 << Q16_SHIFT), 100 * (1 << Q16_SHIFT))
})
.collect();
let batch = compress_coefficients(&coefficients).unwrap();
assert!(batch.count == 10);
assert!(!batch.data.is_empty());
let recovered = decompress_coefficients(&batch).unwrap();
assert_eq!(recovered.len(), 10);
for (orig, rec) in coefficients.iter().zip(recovered.iter()) {
let slope_err = (orig.0 as f64 - rec.0 as f64).abs();
let intercept_err = (orig.1 as f64 - rec.1 as f64).abs();
assert!(
slope_err < orig.0.abs() as f64 * 0.1 + 1.0,
"slope error too large: {} vs {}",
orig.0,
rec.0
);
let _ = intercept_err;
}
}
#[test]
fn test_fit_and_compress() {
let data1 = [100, 200, 300, 400, 500];
let data2 = [50, 50, 50, 50, 50]; let data3 = [0, 10, 20, 30, 40];
let batch = fit_and_compress(&[&data1, &data2, &data3]).unwrap();
assert_eq!(batch.count, 3);
let evaluated = decompress_and_evaluate(&batch, 2).unwrap();
assert_eq!(evaluated.len(), 3);
}
#[test]
fn test_compress_empty() {
let batch = compress_coefficients(&[]).unwrap();
assert_eq!(batch.count, 0);
}
#[test]
fn test_compress_single() {
let coeffs = [(1 << Q16_SHIFT, 50 << Q16_SHIFT)];
let batch = compress_coefficients(&coeffs).unwrap();
assert_eq!(batch.count, 1);
assert!(!batch.data.is_empty());
}
#[test]
fn test_decompress_and_evaluate_at_zero() {
let coefficients = vec![(10 << Q16_SHIFT, 100 << Q16_SHIFT)];
let batch = compress_coefficients(&coefficients).unwrap();
let evaluated = decompress_and_evaluate(&batch, 0).unwrap();
assert_eq!(evaluated.len(), 1);
}
#[test]
fn test_fit_and_compress_single_stream() {
let data = [100, 200, 300];
let batch = fit_and_compress(&[&data]).unwrap();
assert_eq!(batch.count, 1);
}
#[test]
fn test_compressed_batch_has_data() {
let coefficients: Vec<(i32, i32)> = (0..50)
.map(|i| (i * (1 << Q16_SHIFT), 100 * (1 << Q16_SHIFT)))
.collect();
let batch = compress_coefficients(&coefficients).unwrap();
assert_eq!(batch.count, 50);
assert!(batch.data.len() < 400);
}
#[test]
fn test_fit_and_compress_empty() {
let batch = fit_and_compress(&[]).unwrap();
assert_eq!(batch.count, 0);
}
}