1use serde::{Deserialize, Serialize};
2
3use crate::error::{PolyKvError, Result};
4
5pub type CodecId = String;
7
8pub const CODEC_FIB_K4_N32: &str = "fib_k4_n32";
10pub const CODEC_TURBO_8BIT: &str = "turbo_8bit";
11pub const CODEC_EXACT_FALLBACK: &str = "exact_f32_fallback";
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub struct FibConfig {
16 pub k: u32,
18 pub n: u32,
20 pub training_samples: u32,
22 pub lloyd_restarts: u32,
24 pub lloyd_iterations: u32,
26}
27
28impl FibConfig {
29 pub fn default_k4_n32() -> Self {
31 Self {
32 k: 4,
33 n: 32,
34 training_samples: 2048,
35 lloyd_restarts: 4,
36 lloyd_iterations: 8,
37 }
38 }
39
40 pub fn validate(&self) -> Result<()> {
42 if self.k == 0 {
43 return Err(PolyKvError::InvalidPolicy("fib k must be > 0".into()));
44 }
45 if self.n < 2 {
46 return Err(PolyKvError::InvalidPolicy("fib N must be >= 2".into()));
47 }
48 if self.k > 256 {
49 return Err(PolyKvError::InvalidPolicy("fib k must be <= 256".into()));
50 }
51 if self.n > 1_048_576 {
52 return Err(PolyKvError::InvalidPolicy(
53 "fib N must be <= 1,048,576".into(),
54 ));
55 }
56 if self.training_samples == 0 {
57 return Err(PolyKvError::InvalidPolicy(
58 "fib training_samples must be > 0".into(),
59 ));
60 }
61 if self.lloyd_restarts == 0 {
62 return Err(PolyKvError::InvalidPolicy(
63 "fib lloyd_restarts must be > 0".into(),
64 ));
65 }
66 if self.lloyd_iterations == 0 {
67 return Err(PolyKvError::InvalidPolicy(
68 "fib lloyd_iterations must be > 0".into(),
69 ));
70 }
71 Ok(())
72 }
73
74 pub fn nominal_compression_ratio(&self) -> f64 {
79 50.0
80 }
81
82 pub fn expected_compression_ratio(&self) -> f64 {
84 50.0
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct TurboConfig {
91 pub bits: u8,
93 pub projections: usize,
95}
96
97impl TurboConfig {
98 pub fn default_8bit() -> Self {
100 Self {
101 bits: 8,
102 projections: 32,
103 }
104 }
105
106 pub fn validate(&self) -> Result<()> {
108 if self.bits < 2 || self.bits > 16 {
109 return Err(PolyKvError::InvalidPolicy(format!(
110 "turbo bits must be 2–16, got {}",
111 self.bits
112 )));
113 }
114 if self.projections == 0 {
115 return Err(PolyKvError::InvalidPolicy(
116 "turbo projections must be > 0".into(),
117 ));
118 }
119 Ok(())
120 }
121
122 pub fn expected_compression_ratio(&self) -> f64 {
124 8.0
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134pub struct CompressionPolicy {
135 pub shared_codec: CodecId,
137 pub shell_codec: CodecId,
139 pub fib_config: FibConfig,
141 pub turbo_config: TurboConfig,
143}
144
145impl CompressionPolicy {
146 pub fn default_two_tier() -> Self {
148 Self {
149 shared_codec: CODEC_FIB_K4_N32.into(),
150 shell_codec: CODEC_TURBO_8BIT.into(),
151 fib_config: FibConfig::default_k4_n32(),
152 turbo_config: TurboConfig::default_8bit(),
153 }
154 }
155
156 pub fn validate(&self) -> Result<()> {
158 if self.shared_codec != CODEC_FIB_K4_N32 && self.shared_codec != CODEC_EXACT_FALLBACK {
159 return Err(PolyKvError::InvalidPolicy(format!(
160 "shared_codec must be '{}' or '{}', got '{}'",
161 CODEC_FIB_K4_N32, CODEC_EXACT_FALLBACK, self.shared_codec
162 )));
163 }
164 if self.shell_codec != CODEC_TURBO_8BIT && self.shell_codec != CODEC_EXACT_FALLBACK {
165 return Err(PolyKvError::InvalidPolicy(format!(
166 "shell_codec must be '{}' or '{}', got '{}'",
167 CODEC_TURBO_8BIT, CODEC_EXACT_FALLBACK, self.shell_codec
168 )));
169 }
170 self.fib_config.validate()?;
171 self.turbo_config.validate()?;
172 Ok(())
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_default_policy_validates() {
182 let policy = CompressionPolicy::default_two_tier();
183 assert!(policy.validate().is_ok());
184 }
185
186 #[test]
187 fn test_fib_config_invalid_k_rejected() {
188 let mut config = FibConfig::default_k4_n32();
189 config.k = 0;
190 assert!(config.validate().is_err());
191 }
192
193 #[test]
194 fn test_fib_config_invalid_n_rejected() {
195 let mut config = FibConfig::default_k4_n32();
196 config.n = 1;
197 assert!(config.validate().is_err());
198 }
199
200 #[test]
201 fn test_turbo_config_invalid_bits_rejected() {
202 let mut config = TurboConfig::default_8bit();
203 config.bits = 1;
204 assert!(config.validate().is_err());
205 }
206}