1use crate::serde_ga3;
32use crate::VectorClock;
33use cliffy_core::GA3;
34use serde::{Deserialize, Serialize};
35use uuid::Uuid;
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct StateDelta {
45 #[serde(with = "serde_ga3")]
47 pub transform: GA3,
48 pub encoding: DeltaEncoding,
50 pub from_clock: VectorClock,
52 pub to_clock: VectorClock,
54 pub source_node: Uuid,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60pub enum DeltaEncoding {
61 Additive,
63 Multiplicative,
65 Compressed,
67}
68
69impl StateDelta {
70 pub fn additive(
72 transform: GA3,
73 from_clock: VectorClock,
74 to_clock: VectorClock,
75 source_node: Uuid,
76 ) -> Self {
77 Self {
78 transform,
79 encoding: DeltaEncoding::Additive,
80 from_clock,
81 to_clock,
82 source_node,
83 }
84 }
85
86 pub fn multiplicative(
88 transform: GA3,
89 from_clock: VectorClock,
90 to_clock: VectorClock,
91 source_node: Uuid,
92 ) -> Self {
93 Self {
94 transform,
95 encoding: DeltaEncoding::Multiplicative,
96 from_clock,
97 to_clock,
98 source_node,
99 }
100 }
101
102 pub fn compressed(
104 transform: GA3,
105 from_clock: VectorClock,
106 to_clock: VectorClock,
107 source_node: Uuid,
108 ) -> Self {
109 Self {
110 transform,
111 encoding: DeltaEncoding::Compressed,
112 from_clock,
113 to_clock,
114 source_node,
115 }
116 }
117
118 pub fn estimated_size(&self) -> usize {
120 8 * 8 + 32
122 }
123
124 pub fn is_applicable_to(&self, state_clock: &VectorClock) -> bool {
126 self.from_clock.happens_before(state_clock) || self.from_clock == *state_clock
128 }
129}
130
131pub fn compute_delta(from: &GA3, to: &GA3) -> GA3 {
136 to - from
137}
138
139pub fn compute_delta_compressed(from: &GA3, to: &GA3) -> GA3 {
144 let from_mag = from.magnitude();
150 let to_mag = to.magnitude();
151
152 if from_mag < 1e-10 {
153 return to - from;
155 }
156
157 let ratio = to_mag / from_mag;
159 let log_ratio = ratio.ln();
160
161 GA3::scalar(log_ratio)
163}
164
165pub fn apply_delta(state: &mut GA3, delta: &StateDelta) {
167 match delta.encoding {
168 DeltaEncoding::Additive => {
169 *state = &*state + &delta.transform;
170 }
171 DeltaEncoding::Multiplicative => {
172 let rev = delta.transform.reverse();
174 *state = delta
175 .transform
176 .geometric_product(state)
177 .geometric_product(&rev);
178 }
179 DeltaEncoding::Compressed => {
180 let exp_delta = delta.transform.exp();
182 *state = exp_delta.geometric_product(state);
183 }
184 }
185}
186
187pub fn apply_additive_delta(state: &mut GA3, delta: &GA3) {
189 *state = &*state + delta;
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct DeltaBatch {
198 pub deltas: Vec<StateDelta>,
200 pub combined_clock: VectorClock,
202}
203
204impl DeltaBatch {
205 pub fn new() -> Self {
207 Self {
208 deltas: Vec::new(),
209 combined_clock: VectorClock::new(),
210 }
211 }
212
213 pub fn push(&mut self, delta: StateDelta) {
215 self.combined_clock.update(&delta.to_clock);
216 self.deltas.push(delta);
217 }
218
219 pub fn is_empty(&self) -> bool {
221 self.deltas.is_empty()
222 }
223
224 pub fn len(&self) -> usize {
226 self.deltas.len()
227 }
228
229 pub fn combine_additive(&self) -> Option<GA3> {
233 if self.deltas.is_empty() {
234 return None;
235 }
236
237 if !self
239 .deltas
240 .iter()
241 .all(|d| d.encoding == DeltaEncoding::Additive)
242 {
243 return None;
244 }
245
246 let combined = self
248 .deltas
249 .iter()
250 .fold(GA3::zero(), |acc, d| &acc + &d.transform);
251
252 Some(combined)
253 }
254
255 pub fn apply_to(&self, state: &mut GA3) {
257 for delta in &self.deltas {
258 apply_delta(state, delta);
259 }
260 }
261
262 pub fn estimated_size(&self) -> usize {
264 self.deltas.iter().map(|d| d.estimated_size()).sum()
265 }
266}
267
268impl Default for DeltaBatch {
269 fn default() -> Self {
270 Self::new()
271 }
272}
273
274pub fn compute_savings(delta: &StateDelta, _full_state: &GA3) -> (usize, usize, f64) {
278 let delta_size = delta.estimated_size();
279 let full_size = 8 * 8; let savings = if full_size > 0 {
282 1.0 - (delta_size as f64 / full_size as f64)
283 } else {
284 0.0
285 };
286
287 (delta_size, full_size, savings)
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_compute_additive_delta() {
296 let from = GA3::scalar(3.0);
297 let to = GA3::scalar(7.0);
298
299 let delta = compute_delta(&from, &to);
300
301 assert!((delta.scalar_part() - 4.0).abs() < 1e-10);
303 }
304
305 #[test]
306 fn test_apply_additive_delta() {
307 let from = GA3::scalar(3.0);
308 let to = GA3::scalar(7.0);
309
310 let delta_transform = compute_delta(&from, &to);
311 let delta = StateDelta::additive(
312 delta_transform,
313 VectorClock::new(),
314 VectorClock::new(),
315 Uuid::new_v4(),
316 );
317
318 let mut state = from.clone();
319 apply_delta(&mut state, &delta);
320
321 assert!((state.scalar_part() - 7.0).abs() < 1e-10);
322 }
323
324 #[test]
325 fn test_apply_multiplicative_delta() {
326 let delta = StateDelta::multiplicative(
328 GA3::scalar(2.0),
329 VectorClock::new(),
330 VectorClock::new(),
331 Uuid::new_v4(),
332 );
333
334 let mut state = GA3::scalar(3.0);
335 apply_delta(&mut state, &delta);
336
337 assert!((state.scalar_part() - 12.0).abs() < 1e-10);
339 }
340
341 #[test]
342 fn test_apply_compressed_delta() {
343 let delta = StateDelta::compressed(
344 GA3::scalar(1.0), VectorClock::new(),
346 VectorClock::new(),
347 Uuid::new_v4(),
348 );
349
350 let mut state = GA3::scalar(1.0);
351 apply_delta(&mut state, &delta);
352
353 let expected = std::f64::consts::E;
355 assert!((state.scalar_part() - expected).abs() < 1e-10);
356 }
357
358 #[test]
359 fn test_delta_batch_combine() {
360 let mut batch = DeltaBatch::new();
361
362 batch.push(StateDelta::additive(
363 GA3::scalar(1.0),
364 VectorClock::new(),
365 VectorClock::new(),
366 Uuid::new_v4(),
367 ));
368
369 batch.push(StateDelta::additive(
370 GA3::scalar(2.0),
371 VectorClock::new(),
372 VectorClock::new(),
373 Uuid::new_v4(),
374 ));
375
376 batch.push(StateDelta::additive(
377 GA3::scalar(3.0),
378 VectorClock::new(),
379 VectorClock::new(),
380 Uuid::new_v4(),
381 ));
382
383 let combined = batch.combine_additive().unwrap();
384
385 assert!((combined.scalar_part() - 6.0).abs() < 1e-10);
387 }
388
389 #[test]
390 fn test_delta_batch_apply() {
391 let mut batch = DeltaBatch::new();
392
393 batch.push(StateDelta::additive(
394 GA3::scalar(1.0),
395 VectorClock::new(),
396 VectorClock::new(),
397 Uuid::new_v4(),
398 ));
399
400 batch.push(StateDelta::additive(
401 GA3::scalar(2.0),
402 VectorClock::new(),
403 VectorClock::new(),
404 Uuid::new_v4(),
405 ));
406
407 let mut state = GA3::scalar(10.0);
408 batch.apply_to(&mut state);
409
410 assert!((state.scalar_part() - 13.0).abs() < 1e-10);
412 }
413
414 #[test]
415 fn test_delta_applicability() {
416 let mut from_clock = VectorClock::new();
417 let node = Uuid::new_v4();
418 from_clock.tick(node);
419
420 let mut to_clock = from_clock.clone();
421 to_clock.tick(node);
422
423 let delta = StateDelta::additive(GA3::scalar(1.0), from_clock.clone(), to_clock, node);
424
425 assert!(delta.is_applicable_to(&from_clock));
427
428 let mut higher_clock = from_clock.clone();
430 higher_clock.tick(node);
431 assert!(delta.is_applicable_to(&higher_clock));
432 }
433
434 #[test]
435 fn test_compute_delta_compressed() {
436 let from = GA3::scalar(2.0);
437 let to = GA3::scalar(8.0);
438
439 let delta = compute_delta_compressed(&from, &to);
440
441 let expected = (8.0_f64 / 2.0_f64).ln();
443 assert!((delta.scalar_part() - expected).abs() < 1e-10);
444 }
445
446 #[test]
447 fn test_delta_encoding_equality() {
448 assert_eq!(DeltaEncoding::Additive, DeltaEncoding::Additive);
449 assert_ne!(DeltaEncoding::Additive, DeltaEncoding::Multiplicative);
450 }
451}