1#[derive(Debug, Clone)]
8pub struct VectorDelta {
9 pub id: usize,
11 pub delta: Vec<f32>,
13 pub scale: f32,
15 pub timestamp: u64,
17}
18
19impl VectorDelta {
20 pub fn new(id: usize, delta: Vec<f32>, scale: f32, timestamp: u64) -> Self {
22 Self {
23 id,
24 delta,
25 scale,
26 timestamp,
27 }
28 }
29
30 pub fn is_zero(&self) -> bool {
32 self.delta.iter().all(|&v| v == 0.0)
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct DeltaStats {
39 pub total_deltas: usize,
40 pub applied: usize,
41 pub magnitude: f32,
43 pub mean_change: f32,
45}
46
47pub struct DeltaEncoder;
49
50impl DeltaEncoder {
51 pub fn encode(old: &[f32], new: &[f32]) -> VectorDelta {
56 let len = old.len().max(new.len());
57 let delta: Vec<f32> = (0..len)
58 .map(|i| {
59 let o = old.get(i).copied().unwrap_or(0.0);
60 let n = new.get(i).copied().unwrap_or(0.0);
61 n - o
62 })
63 .collect();
64
65 let scale = delta.iter().map(|v| v.abs()).fold(0.0_f32, f32::max);
66
67 VectorDelta::new(0, delta, scale, 0)
68 }
69
70 pub fn apply(base: &[f32], delta: &VectorDelta) -> Vec<f32> {
74 let len = base.len().max(delta.delta.len());
75 (0..len)
76 .map(|i| {
77 let b = base.get(i).copied().unwrap_or(0.0);
78 let d = delta.delta.get(i).copied().unwrap_or(0.0);
79 b + d
80 })
81 .collect()
82 }
83
84 pub fn apply_sequence(base: &[f32], deltas: &[VectorDelta]) -> Vec<f32> {
86 deltas
87 .iter()
88 .fold(base.to_vec(), |acc, d| Self::apply(&acc, d))
89 }
90
91 pub fn compress(delta: &VectorDelta, threshold: f32) -> VectorDelta {
93 let compressed: Vec<f32> = delta
94 .delta
95 .iter()
96 .map(|&v| if v.abs() < threshold { 0.0 } else { v })
97 .collect();
98
99 let new_scale = compressed.iter().map(|v| v.abs()).fold(0.0_f32, f32::max);
100
101 VectorDelta::new(delta.id, compressed, new_scale, delta.timestamp)
102 }
103
104 pub fn merge(deltas: &[VectorDelta]) -> Option<VectorDelta> {
109 if deltas.is_empty() {
110 return None;
111 }
112
113 let len = deltas.iter().map(|d| d.delta.len()).max().unwrap_or(0);
114 let mut sum = vec![0.0f32; len];
115
116 for d in deltas {
117 for (i, &v) in d.delta.iter().enumerate() {
118 sum[i] += v;
119 }
120 }
121
122 let scale = sum.iter().map(|v| v.abs()).fold(0.0_f32, f32::max);
123 Some(VectorDelta::new(0, sum, scale, 0))
124 }
125
126 pub fn stats(deltas: &[VectorDelta]) -> DeltaStats {
128 let total_deltas = deltas.len();
129
130 if deltas.is_empty() {
131 return DeltaStats {
132 total_deltas: 0,
133 applied: 0,
134 magnitude: 0.0,
135 mean_change: 0.0,
136 };
137 }
138
139 let all_vals: Vec<f32> = deltas
141 .iter()
142 .flat_map(|d| d.delta.iter().copied())
143 .collect();
144 let sq_sum: f32 = all_vals.iter().map(|v| v * v).sum();
145 let magnitude = if all_vals.is_empty() {
146 0.0
147 } else {
148 (sq_sum / all_vals.len() as f32).sqrt()
149 };
150
151 let abs_sum: f32 = all_vals.iter().map(|v| v.abs()).sum();
153 let mean_change = if all_vals.is_empty() {
154 0.0
155 } else {
156 abs_sum / all_vals.len() as f32
157 };
158
159 DeltaStats {
160 total_deltas,
161 applied: total_deltas,
162 magnitude,
163 mean_change,
164 }
165 }
166
167 pub fn magnitude(delta: &VectorDelta) -> f32 {
169 let sq_sum: f32 = delta.delta.iter().map(|v| v * v).sum();
170 sq_sum.sqrt()
171 }
172
173 pub fn quantize_to_i8(delta: &VectorDelta) -> Vec<i8> {
178 if delta.scale == 0.0 {
179 return vec![0i8; delta.delta.len()];
180 }
181 delta
182 .delta
183 .iter()
184 .map(|&v| {
185 let quantized = (v / delta.scale * 127.0).round();
186 quantized.clamp(-127.0, 127.0) as i8
187 })
188 .collect()
189 }
190
191 pub fn from_i8(quantized: &[i8], scale: f32, id: usize, timestamp: u64) -> VectorDelta {
194 let delta: Vec<f32> = quantized
195 .iter()
196 .map(|&v| (v as f32) / 127.0 * scale)
197 .collect();
198 VectorDelta::new(id, delta, scale, timestamp)
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
205 use super::*;
206
207 const EPS: f32 = 1e-5;
208
209 fn approx_eq(a: f32, b: f32) -> bool {
210 (a - b).abs() < EPS
211 }
212
213 fn approx_vec(a: &[f32], b: &[f32]) -> bool {
214 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| approx_eq(*x, *y))
215 }
216
217 #[test]
220 fn test_encode_apply_round_trip() {
221 let old = vec![1.0, 2.0, 3.0];
222 let new = vec![2.0, 3.0, 4.0];
223 let delta = DeltaEncoder::encode(&old, &new);
224 let result = DeltaEncoder::apply(&old, &delta);
225 assert!(approx_vec(&result, &new));
226 }
227
228 #[test]
229 fn test_encode_zero_delta() {
230 let v = vec![1.0, 2.0, 3.0];
231 let delta = DeltaEncoder::encode(&v, &v);
232 assert!(delta.is_zero());
233 assert!(approx_eq(delta.scale, 0.0));
234 }
235
236 #[test]
237 fn test_encode_delta_values() {
238 let old = vec![0.0, 0.0, 0.0];
239 let new = vec![1.0, -2.0, 3.0];
240 let delta = DeltaEncoder::encode(&old, &new);
241 assert!(approx_eq(delta.delta[0], 1.0));
242 assert!(approx_eq(delta.delta[1], -2.0));
243 assert!(approx_eq(delta.delta[2], 3.0));
244 }
245
246 #[test]
247 fn test_encode_scale_is_max_abs() {
248 let old = vec![0.0, 0.0, 0.0];
249 let new = vec![1.0, 5.0, -3.0];
250 let delta = DeltaEncoder::encode(&old, &new);
251 assert!(approx_eq(delta.scale, 5.0));
252 }
253
254 #[test]
255 fn test_encode_different_lengths_pads_shorter() {
256 let old = vec![1.0, 2.0];
257 let new = vec![2.0, 3.0, 4.0];
258 let delta = DeltaEncoder::encode(&old, &new);
259 assert_eq!(delta.delta.len(), 3);
260 assert!(approx_eq(delta.delta[2], 4.0)); }
262
263 #[test]
266 fn test_apply_zero_delta_unchanged() {
267 let base = vec![1.0, 2.0, 3.0];
268 let delta = VectorDelta::new(0, vec![0.0, 0.0, 0.0], 0.0, 0);
269 let result = DeltaEncoder::apply(&base, &delta);
270 assert!(approx_vec(&result, &base));
271 }
272
273 #[test]
274 fn test_apply_positive_delta() {
275 let base = vec![1.0, 2.0];
276 let delta = VectorDelta::new(0, vec![0.5, -0.5], 0.5, 0);
277 let result = DeltaEncoder::apply(&base, &delta);
278 assert!(approx_eq(result[0], 1.5));
279 assert!(approx_eq(result[1], 1.5));
280 }
281
282 #[test]
285 fn test_apply_sequence_ordered() {
286 let base = vec![0.0, 0.0];
287 let d1 = VectorDelta::new(0, vec![1.0, 2.0], 2.0, 1);
288 let d2 = VectorDelta::new(0, vec![0.5, 0.5], 0.5, 2);
289 let result = DeltaEncoder::apply_sequence(&base, &[d1, d2]);
290 assert!(approx_eq(result[0], 1.5));
291 assert!(approx_eq(result[1], 2.5));
292 }
293
294 #[test]
295 fn test_apply_sequence_empty_deltas() {
296 let base = vec![1.0, 2.0, 3.0];
297 let result = DeltaEncoder::apply_sequence(&base, &[]);
298 assert!(approx_vec(&result, &base));
299 }
300
301 #[test]
302 fn test_apply_sequence_single_delta() {
303 let base = vec![1.0];
304 let d = VectorDelta::new(0, vec![1.0], 1.0, 0);
305 let result = DeltaEncoder::apply_sequence(&base, &[d]);
306 assert!(approx_eq(result[0], 2.0));
307 }
308
309 #[test]
312 fn test_compress_zeroes_below_threshold() {
313 let delta = VectorDelta::new(0, vec![0.5, 0.01, -0.001, 1.0], 1.0, 0);
314 let compressed = DeltaEncoder::compress(&delta, 0.05);
315 assert!(approx_eq(compressed.delta[1], 0.0));
316 assert!(approx_eq(compressed.delta[2], 0.0));
317 assert!(approx_eq(compressed.delta[0], 0.5));
318 assert!(approx_eq(compressed.delta[3], 1.0));
319 }
320
321 #[test]
322 fn test_compress_threshold_zero_unchanged() {
323 let delta = VectorDelta::new(0, vec![0.5, 0.01, 1.0], 1.0, 0);
324 let compressed = DeltaEncoder::compress(&delta, 0.0);
325 assert!(approx_vec(&compressed.delta, &delta.delta));
326 }
327
328 #[test]
329 fn test_compress_all_below_threshold() {
330 let delta = VectorDelta::new(0, vec![0.001, 0.002, 0.003], 0.003, 0);
331 let compressed = DeltaEncoder::compress(&delta, 0.01);
332 assert!(compressed.is_zero());
333 assert!(approx_eq(compressed.scale, 0.0));
334 }
335
336 #[test]
339 fn test_merge_empty_returns_none() {
340 assert!(DeltaEncoder::merge(&[]).is_none());
341 }
342
343 #[test]
344 fn test_merge_single_delta() -> Result<()> {
345 let d = VectorDelta::new(0, vec![1.0, 2.0], 2.0, 0);
346 let merged =
347 DeltaEncoder::merge(&[d]).expect("merge of non-empty slice should return Some");
348 assert!(approx_eq(merged.delta[0], 1.0));
349 assert!(approx_eq(merged.delta[1], 2.0));
350 Ok(())
351 }
352
353 #[test]
354 fn test_merge_sums_deltas() -> Result<()> {
355 let d1 = VectorDelta::new(0, vec![1.0, 2.0], 2.0, 0);
356 let d2 = VectorDelta::new(0, vec![3.0, 4.0], 4.0, 0);
357 let merged =
358 DeltaEncoder::merge(&[d1, d2]).expect("merge of non-empty slice should return Some");
359 assert!(approx_eq(merged.delta[0], 4.0));
360 assert!(approx_eq(merged.delta[1], 6.0));
361 Ok(())
362 }
363
364 #[test]
365 fn test_merge_scale_is_max_abs_sum() -> Result<()> {
366 let d1 = VectorDelta::new(0, vec![1.0], 1.0, 0);
367 let d2 = VectorDelta::new(0, vec![-3.0], 3.0, 0);
368 let merged =
369 DeltaEncoder::merge(&[d1, d2]).expect("merge of non-empty slice should return Some");
370 assert!(approx_eq(merged.scale, 2.0));
372 Ok(())
373 }
374
375 #[test]
378 fn test_stats_empty() {
379 let s = DeltaEncoder::stats(&[]);
380 assert_eq!(s.total_deltas, 0);
381 assert!(approx_eq(s.magnitude, 0.0));
382 }
383
384 #[test]
385 fn test_stats_total_deltas() {
386 let deltas: Vec<_> = (0..3)
387 .map(|i| VectorDelta::new(i, vec![1.0, 1.0], 1.0, 0))
388 .collect();
389 let s = DeltaEncoder::stats(&deltas);
390 assert_eq!(s.total_deltas, 3);
391 }
392
393 #[test]
394 fn test_stats_magnitude_positive() {
395 let d = VectorDelta::new(0, vec![1.0, 0.0], 1.0, 0);
396 let s = DeltaEncoder::stats(&[d]);
397 assert!(s.magnitude > 0.0);
398 }
399
400 #[test]
401 fn test_stats_mean_change_positive() {
402 let d = VectorDelta::new(0, vec![1.0, 2.0], 2.0, 0);
403 let s = DeltaEncoder::stats(&[d]);
404 assert!(s.mean_change > 0.0);
405 }
406
407 #[test]
410 fn test_quantize_to_i8_range() {
411 let delta = VectorDelta::new(0, vec![1.0, -1.0, 0.5, -0.5], 1.0, 0);
412 let q = DeltaEncoder::quantize_to_i8(&delta);
413 for &v in &q {
414 assert!(v >= -127);
415 }
416 }
417
418 #[test]
419 fn test_quantize_max_value() {
420 let delta = VectorDelta::new(0, vec![1.0], 1.0, 0);
421 let q = DeltaEncoder::quantize_to_i8(&delta);
422 assert_eq!(q[0], 127);
423 }
424
425 #[test]
426 fn test_quantize_min_value() {
427 let delta = VectorDelta::new(0, vec![-1.0], 1.0, 0);
428 let q = DeltaEncoder::quantize_to_i8(&delta);
429 assert_eq!(q[0], -127);
430 }
431
432 #[test]
433 fn test_quantize_zero_scale_all_zeros() {
434 let delta = VectorDelta::new(0, vec![0.0, 0.0], 0.0, 0);
435 let q = DeltaEncoder::quantize_to_i8(&delta);
436 assert!(q.iter().all(|&v| v == 0));
437 }
438
439 #[test]
440 fn test_from_i8_reconstruction_approx() {
441 let original = vec![1.0, -1.0, 0.5];
442 let old = vec![0.0, 0.0, 0.0];
443 let delta = DeltaEncoder::encode(&old, &original);
444 let q = DeltaEncoder::quantize_to_i8(&delta);
445 let reconstructed = DeltaEncoder::from_i8(&q, delta.scale, 0, 0);
446 for (a, b) in original.iter().zip(reconstructed.delta.iter()) {
448 assert!(
449 (a - b).abs() < delta.scale / 100.0 + 1e-4,
450 "Reconstruction error too large: {a} vs {b}"
451 );
452 }
453 }
454
455 #[test]
456 fn test_from_i8_preserves_id_and_timestamp() {
457 let q = vec![10i8, -20i8];
458 let d = DeltaEncoder::from_i8(&q, 1.0, 42, 99999);
459 assert_eq!(d.id, 42);
460 assert_eq!(d.timestamp, 99999);
461 }
462
463 #[test]
466 fn test_magnitude_zero_delta() {
467 let delta = VectorDelta::new(0, vec![0.0, 0.0, 0.0], 0.0, 0);
468 assert!(approx_eq(DeltaEncoder::magnitude(&delta), 0.0));
469 }
470
471 #[test]
472 fn test_magnitude_unit_vector() {
473 let delta = VectorDelta::new(0, vec![1.0, 0.0, 0.0], 1.0, 0);
474 assert!(approx_eq(DeltaEncoder::magnitude(&delta), 1.0));
475 }
476
477 #[test]
478 fn test_magnitude_known_value() {
479 let delta = VectorDelta::new(0, vec![3.0, 4.0], 4.0, 0);
481 assert!(approx_eq(DeltaEncoder::magnitude(&delta), 5.0));
482 }
483
484 #[test]
487 fn test_vector_delta_is_zero_true() {
488 let d = VectorDelta::new(0, vec![0.0, 0.0], 0.0, 0);
489 assert!(d.is_zero());
490 }
491
492 #[test]
493 fn test_vector_delta_is_zero_false() {
494 let d = VectorDelta::new(0, vec![0.0, 1.0], 1.0, 0);
495 assert!(!d.is_zero());
496 }
497
498 #[test]
499 fn test_encode_empty_vectors() {
500 let delta = DeltaEncoder::encode(&[], &[]);
501 assert!(delta.delta.is_empty());
502 assert!(approx_eq(delta.scale, 0.0));
503 }
504
505 #[test]
508 fn test_encode_preserves_length_when_equal() {
509 let old = vec![1.0, 2.0, 3.0, 4.0];
510 let new = vec![5.0, 6.0, 7.0, 8.0];
511 let delta = DeltaEncoder::encode(&old, &new);
512 assert_eq!(delta.delta.len(), 4);
513 }
514
515 #[test]
516 fn test_apply_extends_base_for_longer_delta() {
517 let base = vec![1.0];
518 let delta = VectorDelta::new(0, vec![0.5, 0.5, 0.5], 0.5, 0);
519 let result = DeltaEncoder::apply(&base, &delta);
520 assert_eq!(result.len(), 3);
521 assert!(approx_eq(result[0], 1.5));
522 assert!(approx_eq(result[1], 0.5)); assert!(approx_eq(result[2], 0.5)); }
525
526 #[test]
527 fn test_apply_sequence_three_deltas() {
528 let base = vec![0.0];
529 let d1 = VectorDelta::new(0, vec![1.0], 1.0, 1);
530 let d2 = VectorDelta::new(0, vec![2.0], 2.0, 2);
531 let d3 = VectorDelta::new(0, vec![3.0], 3.0, 3);
532 let result = DeltaEncoder::apply_sequence(&base, &[d1, d2, d3]);
533 assert!(approx_eq(result[0], 6.0));
534 }
535
536 #[test]
537 fn test_merge_three_deltas_sums_all() -> Result<()> {
538 let d1 = VectorDelta::new(1, vec![1.0, 0.0], 1.0, 0);
539 let d2 = VectorDelta::new(2, vec![2.0, 0.0], 2.0, 0);
540 let d3 = VectorDelta::new(3, vec![3.0, 0.0], 3.0, 0);
541 let merged = DeltaEncoder::merge(&[d1, d2, d3])
542 .expect("merge of non-empty slice should return Some");
543 assert!(approx_eq(merged.delta[0], 6.0));
544 assert_eq!(merged.id, 0); Ok(())
546 }
547
548 #[test]
549 fn test_quantize_mid_value() {
550 let delta = VectorDelta::new(0, vec![0.5], 1.0, 0);
552 let q = DeltaEncoder::quantize_to_i8(&delta);
553 assert_eq!(q[0], 64);
554 }
555
556 #[test]
557 fn test_from_i8_zero_values() {
558 let q = vec![0i8, 0i8, 0i8];
559 let d = DeltaEncoder::from_i8(&q, 2.0, 0, 0);
560 assert!(d.delta.iter().all(|&v| approx_eq(v, 0.0)));
561 }
562
563 #[test]
564 fn test_stats_applied_equals_total() {
565 let deltas: Vec<_> = (0..5)
566 .map(|i| VectorDelta::new(i, vec![1.0], 1.0, 0))
567 .collect();
568 let s = DeltaEncoder::stats(&deltas);
569 assert_eq!(s.applied, s.total_deltas);
570 assert_eq!(s.total_deltas, 5);
571 }
572}