use serde::{Deserialize, Serialize};
use crate::error::{PolyKvError, Result};
pub type CodecId = String;
pub const CODEC_FIB_K4_N32: &str = "fib_k4_n32";
pub const CODEC_TURBO_8BIT: &str = "turbo_8bit";
pub const CODEC_EXACT_FALLBACK: &str = "exact_f32_fallback";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FibConfig {
pub k: u32,
pub n: u32,
pub training_samples: u32,
pub lloyd_restarts: u32,
pub lloyd_iterations: u32,
}
impl FibConfig {
pub fn default_k4_n32() -> Self {
Self {
k: 4,
n: 32,
training_samples: 2048,
lloyd_restarts: 4,
lloyd_iterations: 8,
}
}
pub fn validate(&self) -> Result<()> {
if self.k == 0 {
return Err(PolyKvError::InvalidPolicy("fib k must be > 0".into()));
}
if self.n < 2 {
return Err(PolyKvError::InvalidPolicy("fib N must be >= 2".into()));
}
if self.k > 256 {
return Err(PolyKvError::InvalidPolicy("fib k must be <= 256".into()));
}
if self.n > 1_048_576 {
return Err(PolyKvError::InvalidPolicy(
"fib N must be <= 1,048,576".into(),
));
}
if self.training_samples == 0 {
return Err(PolyKvError::InvalidPolicy(
"fib training_samples must be > 0".into(),
));
}
if self.lloyd_restarts == 0 {
return Err(PolyKvError::InvalidPolicy(
"fib lloyd_restarts must be > 0".into(),
));
}
if self.lloyd_iterations == 0 {
return Err(PolyKvError::InvalidPolicy(
"fib lloyd_iterations must be > 0".into(),
));
}
Ok(())
}
pub fn nominal_compression_ratio(&self) -> f64 {
let bits_per_index = (self.n as f64).log2().ceil();
(self.k as f64 * 32.0) / bits_per_index
}
pub fn expected_compression_ratio(&self) -> f64 {
50.0
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TurboConfig {
pub bits: u8,
pub projections: usize,
}
impl TurboConfig {
pub fn default_8bit() -> Self {
Self {
bits: 8,
projections: 32,
}
}
pub fn validate(&self) -> Result<()> {
if self.bits < 2 || self.bits > 16 {
return Err(PolyKvError::InvalidPolicy(format!(
"turbo bits must be 2–16, got {}",
self.bits
)));
}
if self.projections == 0 {
return Err(PolyKvError::InvalidPolicy(
"turbo projections must be > 0".into(),
));
}
Ok(())
}
pub fn expected_compression_ratio(&self) -> f64 {
8.0
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CompressionPolicy {
pub shared_codec: CodecId,
pub shell_codec: CodecId,
pub fib_config: FibConfig,
pub turbo_config: TurboConfig,
}
impl CompressionPolicy {
pub fn default_two_tier() -> Self {
Self {
shared_codec: CODEC_FIB_K4_N32.into(),
shell_codec: CODEC_TURBO_8BIT.into(),
fib_config: FibConfig::default_k4_n32(),
turbo_config: TurboConfig::default_8bit(),
}
}
pub fn validate(&self) -> Result<()> {
if self.shared_codec != CODEC_FIB_K4_N32 && self.shared_codec != CODEC_EXACT_FALLBACK {
return Err(PolyKvError::InvalidPolicy(format!(
"shared_codec must be '{}' or '{}', got '{}'",
CODEC_FIB_K4_N32, CODEC_EXACT_FALLBACK, self.shared_codec
)));
}
if self.shell_codec != CODEC_TURBO_8BIT && self.shell_codec != CODEC_EXACT_FALLBACK {
return Err(PolyKvError::InvalidPolicy(format!(
"shell_codec must be '{}' or '{}', got '{}'",
CODEC_TURBO_8BIT, CODEC_EXACT_FALLBACK, self.shell_codec
)));
}
self.fib_config.validate()?;
self.turbo_config.validate()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_policy_validates() {
let policy = CompressionPolicy::default_two_tier();
assert!(policy.validate().is_ok());
}
#[test]
fn test_fib_config_invalid_k_rejected() {
let mut config = FibConfig::default_k4_n32();
config.k = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_fib_config_invalid_n_rejected() {
let mut config = FibConfig::default_k4_n32();
config.n = 1;
assert!(config.validate().is_err());
}
#[test]
fn test_turbo_config_invalid_bits_rejected() {
let mut config = TurboConfig::default_8bit();
config.bits = 1;
assert!(config.validate().is_err());
}
}