1use crate::mixer::apm::APMStage;
20use crate::mixer::dual_mixer::{NUM_MODELS, byte_class};
21use crate::mixer::isse::IsseChain;
22use crate::mixer::multi_set_mixer::MultiSetMixer;
23use crate::model::cm_model::{AssociativeContextModel, ChecksumContextModel, ContextModel};
24use crate::model::dmc_model::DmcModel;
25use crate::model::indirect_model::IndirectModel;
26use crate::model::json_model::JsonModel;
27use crate::model::match_model::MatchModel;
28use crate::model::order0::Order0Model;
29use crate::model::ppm_model::{PpmConfig, PpmModel};
30use crate::model::run_model::RunModel;
31use crate::model::sparse_model::SparseModel;
32use crate::model::word_model::WordModel;
33
34#[derive(Debug, Clone)]
40pub struct CMConfig {
41 pub order1_size: usize,
43 pub order2_size: usize,
45 pub order3_size: usize,
47 pub order4_size: usize,
49 pub order5_size: usize,
51 pub order6_size: usize,
53 pub order7_size: usize,
55 pub order8_size: usize,
57 pub order9_size: usize,
59 pub match_ring_size: usize,
61 pub match_hash_size: usize,
63 pub word_size: usize,
65 pub sparse_size: usize,
67 pub run_size: usize,
69 pub json_size: usize,
71 pub ppm_config: PpmConfig,
73}
74
75impl CMConfig {
76 pub fn balanced() -> Self {
78 CMConfig {
79 order1_size: 1 << 25, order2_size: 1 << 24, order3_size: 1 << 25, order4_size: 1 << 25, order5_size: 1 << 25, order6_size: 1 << 24, order7_size: 1 << 25, order8_size: 1 << 25, order9_size: 1 << 24, match_ring_size: 16 << 20, match_hash_size: 8 << 20, word_size: 1 << 24, sparse_size: 1 << 23, run_size: 1 << 22, json_size: 1 << 23, ppm_config: PpmConfig::scaled_4x(),
95 }
96 }
97
98 pub fn max() -> Self {
102 CMConfig {
103 order1_size: 1 << 26, order2_size: 1 << 25, order3_size: 1 << 26, order4_size: 1 << 26, order5_size: 1 << 26, order6_size: 1 << 25, order7_size: 1 << 26, order8_size: 1 << 26, order9_size: 1 << 25, match_ring_size: 32 << 20, match_hash_size: 16 << 20, word_size: 1 << 25, sparse_size: 1 << 24, run_size: 1 << 23, json_size: 1 << 24, ppm_config: PpmConfig::scaled_4x(),
119 }
120 }
121}
122
123pub struct CMEngine {
125 order0: Order0Model,
128 order1: ContextModel,
130 order2: ContextModel,
132 order3: ChecksumContextModel,
134 order4: ChecksumContextModel,
136 order5: AssociativeContextModel,
138 order6: AssociativeContextModel,
140 order7: AssociativeContextModel,
142 order8: AssociativeContextModel,
144 order9: AssociativeContextModel,
146 match_model: MatchModel,
148 word_model: WordModel,
150 sparse_model: SparseModel,
152 run_model: RunModel,
154 json_model: JsonModel,
156 indirect_model: IndirectModel,
158 ppm_model: PpmModel,
161 dmc_model: DmcModel,
164
165 mixer: MultiSetMixer,
168 apm1: APMStage,
170 apm2: APMStage,
172 apm3: APMStage,
174 apm4: APMStage,
176 apm5: APMStage,
179 apm6: APMStage,
182 apm7: APMStage,
185 isse_model: IsseChain,
188
189 c0: u32,
192 c1: u8,
194 c2: u8,
196 c3: u8,
198 c4: u8,
200 c5: u8,
202 c6: u8,
204 c7: u8,
206 c8: u8,
208 c9: u8,
210 bpos: u8,
212 run_len: u8,
214 line_pos: u16,
216}
217
218impl CMEngine {
219 pub fn new() -> Self {
221 Self::with_config(CMConfig::balanced())
222 }
223
224 pub fn with_config(config: CMConfig) -> Self {
226 CMEngine {
227 order0: Order0Model::new(),
228 order1: ContextModel::new(config.order1_size),
229 order2: ContextModel::new(config.order2_size),
230 order3: ChecksumContextModel::new(config.order3_size),
231 order4: ChecksumContextModel::new(config.order4_size),
232 order5: AssociativeContextModel::new(config.order5_size),
233 order6: AssociativeContextModel::new(config.order6_size),
234 order7: AssociativeContextModel::new(config.order7_size),
235 order8: AssociativeContextModel::new(config.order8_size),
236 order9: AssociativeContextModel::new(config.order9_size),
237 match_model: MatchModel::with_sizes(config.match_ring_size, config.match_hash_size),
238 word_model: WordModel::with_size(config.word_size),
239 sparse_model: SparseModel::with_size(config.sparse_size),
240 run_model: RunModel::with_size(config.run_size),
241 json_model: JsonModel::with_size(config.json_size),
242 indirect_model: IndirectModel::new(),
243 ppm_model: PpmModel::with_config(config.ppm_config),
244 dmc_model: DmcModel::new_single(),
245 mixer: MultiSetMixer::new(),
246 apm1: APMStage::new(2048, 55), apm2: APMStage::new(16384, 30), apm3: APMStage::new(4096, 25), apm4: APMStage::new(4096, 15), apm5: APMStage::new(4096, 15), apm6: APMStage::new(2048, 12), apm7: APMStage::new(4096, 12), isse_model: IsseChain::new(),
254 c0: 1,
255 c1: 0,
256 c2: 0,
257 c3: 0,
258 c4: 0,
259 c5: 0,
260 c6: 0,
261 c7: 0,
262 c8: 0,
263 c9: 0,
264 bpos: 0,
265 run_len: 0,
266 line_pos: 0,
267 }
268 }
269
270 #[inline(always)]
275 pub fn predict(&mut self) -> u32 {
276 let c0 = self.c0;
278 let c1 = self.c1;
279 let c2 = self.c2;
280 let c3 = self.c3;
281 let c4 = self.c4;
282 let c5 = self.c5;
283 let c6 = self.c6;
284 let c7 = self.c7;
285 let bpos = self.bpos;
286
287 let p0 = self.order0.predict(c0 as usize);
289
290 let h1 = order1_hash(c1, c0);
292 let (p1_s, p1_r) = self.order1.predict_multi(h1);
293
294 let h2 = order2_hash(c2, c1, c0);
295 let (p2_s, p2_r) = self.order2.predict_multi(h2);
296
297 let h3 = order3_hash(c3, c2, c1, c0);
298 let (p3_s, p3_r) = self.order3.predict_multi(h3);
299
300 let h4 = order4_hash(c4, c3, c2, c1, c0);
301 let (p4_s, p4_r) = self.order4.predict_multi(h4);
302
303 let h5 = order5_hash(c5, c4, c3, c2, c1, c0);
304 let (p5_s, p5_r) = self.order5.predict_multi(h5);
305
306 let h6 = order6_hash(c6, c5, c4, c3, c2, c1, c0);
307 let (p6_s, p6_r) = self.order6.predict_multi(h6);
308
309 let h7 = order7_hash(c7, c6, c5, c4, c3, c2, c1, c0);
310 let (p7_s, p7_r) = self.order7.predict_multi(h7);
311
312 let c8 = self.c8;
313 let h8 = order8_hash(c8, c7, c6, c5, c4, c3, c2, c1, c0);
314 let (p8_s, p8_r) = self.order8.predict_multi(h8);
315
316 let c9 = self.c9;
317 let h9 = order9_hash(c9, c8, c7, c6, c5, c4, c3, c2, c1, c0);
318 let (p9_s, p9_r) = self.order9.predict_multi(h9);
319
320 let p_match = self.match_model.predict(c0, bpos, c1, c2, c3);
322
323 let p_word = self.word_model.predict(c0, bpos, c1);
325
326 let p_sparse = self.sparse_model.predict(c0, c1, c2, c3);
328
329 let p_run = self.run_model.predict(c0, bpos, c1);
331
332 let p_json = self.json_model.predict(c0, bpos, c1);
334
335 let p_indirect = self.indirect_model.predict(c0, bpos, c1);
337
338 let p_ppm = self.ppm_model.predict_bit(bpos, c0);
341
342 let p_dmc = self.dmc_model.predict();
344
345 let p_isse = self.isse_model.predict(c0, c1, c2, c3, bpos);
347
348 let predictions: [u32; NUM_MODELS] = [
352 p0, p1_s, p1_r, p2_s, p2_r, p3_s, p3_r, p4_s, p4_r, p5_s, p5_r, p6_s, p6_r, p7_s, p7_r,
353 p8_s, p8_r, p9_s, p9_r, p_match, p_word, p_sparse, p_run, p_json, p_indirect, p_ppm,
354 p_dmc, p_isse,
355 ];
356 let bclass = byte_class(c1);
357 let match_q = self.match_model.match_length_quantized();
358 let run_q = quantize_run_for_mixer(self.run_len);
359
360 let mixed = self
361 .mixer
362 .predict(&predictions, c0, c1, c2, bpos, bclass, match_q, run_q, 0);
363
364 let apm1_ctx = (((c0 as usize & 0xFF) << 3) | bpos as usize)
367 .wrapping_mul(5)
368 .wrapping_add(run_q as usize & 0x3)
369 & 2047;
370 let after_apm1 = self.apm1.predict(mixed, apm1_ctx);
371
372 let apm2_ctx = (((c1 as usize) << 3 | bpos as usize) * 8 + bclass as usize)
374 .wrapping_mul(17)
375 .wrapping_add(c2 as usize >> 4)
376 & 16383;
377 let after_apm2 = self.apm2.predict(after_apm1, apm2_ctx);
378
379 let apm3_ctx = ((match_q as usize * 512)
381 + ((c2 as usize >> 6) << 7)
382 + ((c1 as usize >> 4) << 3)
383 + bpos as usize)
384 .wrapping_mul(5)
385 .wrapping_add(match_q as usize)
386 & 4095;
387 let after_apm3 = self.apm3.predict(after_apm2, apm3_ctx);
388
389 let bc2 = byte_class(c2);
394 let apm4_ctx = (bclass as usize * 8 + bc2 as usize)
395 .wrapping_mul(33)
396 .wrapping_add(bpos as usize * 4 + run_q as usize)
397 & 4095;
398 let after_apm4 = self.apm4.predict(after_apm3, apm4_ctx);
399
400 let apm5_ctx = ((c3 as usize >> 4).wrapping_mul(67) + (c2 as usize >> 4))
403 .wrapping_mul(67)
404 .wrapping_add((c1 as usize >> 6) * 8 + bpos as usize)
405 & 4095;
406 let after_apm5 = self.apm5.predict(after_apm4, apm5_ctx);
407
408 let apm6_ctx = (match_q as usize * 64 + bclass as usize * 8 + bpos as usize) & 2047;
411 let after_apm6 = self.apm6.predict(after_apm5, apm6_ctx);
412
413 let line_pos_q = quantize_line_pos(self.line_pos);
416 let apm7_ctx = ((line_pos_q as usize).wrapping_mul(67) + (c0 as usize & 0xFF))
417 .wrapping_mul(67)
418 .wrapping_add(bpos as usize)
419 & 4095;
420 let final_p = self.apm7.predict(after_apm6, apm7_ctx);
421
422 final_p.clamp(1, 4095)
423 }
424
425 #[inline(always)]
429 pub fn update(&mut self, bit: u8) {
430 self.apm7.update(bit);
432 self.apm6.update(bit);
433 self.apm5.update(bit);
434 self.apm4.update(bit);
435 self.apm3.update(bit);
436 self.apm2.update(bit);
437 self.apm1.update(bit);
438
439 self.mixer.update(bit);
441
442 self.order0.update(self.c0 as usize, bit);
444 self.order1.update(bit);
445 self.order2.update(bit);
446 self.order3.update(bit);
447 self.order4.update(bit);
448 self.order5.update(bit);
449 self.order6.update(bit);
450 self.order7.update(bit);
451 self.order8.update(bit);
452 self.order9.update(bit);
453 self.match_model
454 .update(bit, self.bpos, self.c0, self.c1, self.c2);
455 self.word_model.update(bit);
456 self.sparse_model.update(bit);
457 self.run_model.update(bit);
458 self.json_model.update(bit);
459 self.indirect_model.update(bit);
460 self.dmc_model.update(bit);
461 self.isse_model.update(bit, self.c0, self.bpos);
462
463 self.c0 = (self.c0 << 1) | bit as u32;
465 self.bpos += 1;
466
467 if self.bpos >= 8 {
468 let byte = (self.c0 & 0xFF) as u8;
470 if byte == self.c1 {
472 self.run_len = self.run_len.saturating_add(1);
473 } else {
474 self.run_len = 1;
475 }
476 if byte == b'\n' {
478 self.line_pos = 0;
479 } else {
480 self.line_pos = self.line_pos.saturating_add(1);
481 }
482
483 self.ppm_model.update_byte(byte);
485
486 self.dmc_model.on_byte_complete(byte);
488
489 self.c9 = self.c8;
490 self.c8 = self.c7;
491 self.c7 = self.c6;
492 self.c6 = self.c5;
493 self.c5 = self.c4;
494 self.c4 = self.c3;
495 self.c3 = self.c2;
496 self.c2 = self.c1;
497 self.c1 = byte;
498 self.c0 = 1; self.bpos = 0;
500 }
501 }
502}
503
504impl Default for CMEngine {
505 fn default() -> Self {
506 Self::new()
507 }
508}
509
510const FNV_OFFSET: u32 = 0x811C9DC5;
517const FNV_PRIME: u32 = 0x01000193;
519
520#[inline]
522fn order1_hash(c1: u8, c0: u32) -> u32 {
523 let mut h = FNV_OFFSET;
524 h ^= c1 as u32;
525 h = h.wrapping_mul(FNV_PRIME);
526 h ^= c0 & 0xFF;
527 h = h.wrapping_mul(FNV_PRIME);
528 h
529}
530
531#[inline]
533fn order2_hash(c2: u8, c1: u8, c0: u32) -> u32 {
534 let mut h = FNV_OFFSET;
535 h ^= c2 as u32;
536 h = h.wrapping_mul(FNV_PRIME);
537 h ^= c1 as u32;
538 h = h.wrapping_mul(FNV_PRIME);
539 h ^= c0 & 0xFF;
540 h = h.wrapping_mul(FNV_PRIME);
541 h
542}
543
544#[inline]
546fn order3_hash(c3: u8, c2: u8, c1: u8, c0: u32) -> u32 {
547 let mut h = FNV_OFFSET;
548 h ^= c3 as u32;
549 h = h.wrapping_mul(FNV_PRIME);
550 h ^= c2 as u32;
551 h = h.wrapping_mul(FNV_PRIME);
552 h ^= c1 as u32;
553 h = h.wrapping_mul(FNV_PRIME);
554 h ^= c0 & 0xFF;
555 h = h.wrapping_mul(FNV_PRIME);
556 h
557}
558
559#[inline]
561fn order4_hash(c4: u8, c3: u8, c2: u8, c1: u8, c0: u32) -> u32 {
562 let mut h = FNV_OFFSET;
563 h ^= c4 as u32;
564 h = h.wrapping_mul(FNV_PRIME);
565 h ^= c3 as u32;
566 h = h.wrapping_mul(FNV_PRIME);
567 h ^= c2 as u32;
568 h = h.wrapping_mul(FNV_PRIME);
569 h ^= c1 as u32;
570 h = h.wrapping_mul(FNV_PRIME);
571 h ^= c0 & 0xFF;
572 h = h.wrapping_mul(FNV_PRIME);
573 h
574}
575
576#[inline]
578fn order5_hash(c5: u8, c4: u8, c3: u8, c2: u8, c1: u8, c0: u32) -> u32 {
579 let mut h = FNV_OFFSET;
580 h ^= c5 as u32;
581 h = h.wrapping_mul(FNV_PRIME);
582 h ^= c4 as u32;
583 h = h.wrapping_mul(FNV_PRIME);
584 h ^= c3 as u32;
585 h = h.wrapping_mul(FNV_PRIME);
586 h ^= c2 as u32;
587 h = h.wrapping_mul(FNV_PRIME);
588 h ^= c1 as u32;
589 h = h.wrapping_mul(FNV_PRIME);
590 h ^= c0 & 0xFF;
591 h = h.wrapping_mul(FNV_PRIME);
592 h
593}
594
595#[inline]
597fn order6_hash(c6: u8, c5: u8, c4: u8, c3: u8, c2: u8, c1: u8, c0: u32) -> u32 {
598 let mut h = FNV_OFFSET;
599 h ^= c6 as u32;
600 h = h.wrapping_mul(FNV_PRIME);
601 h ^= c5 as u32;
602 h = h.wrapping_mul(FNV_PRIME);
603 h ^= c4 as u32;
604 h = h.wrapping_mul(FNV_PRIME);
605 h ^= c3 as u32;
606 h = h.wrapping_mul(FNV_PRIME);
607 h ^= c2 as u32;
608 h = h.wrapping_mul(FNV_PRIME);
609 h ^= c1 as u32;
610 h = h.wrapping_mul(FNV_PRIME);
611 h ^= c0 & 0xFF;
612 h = h.wrapping_mul(FNV_PRIME);
613 h
614}
615
616#[inline]
618#[allow(clippy::too_many_arguments)]
619fn order7_hash(c7: u8, c6: u8, c5: u8, c4: u8, c3: u8, c2: u8, c1: u8, c0: u32) -> u32 {
620 let mut h = FNV_OFFSET;
621 h ^= c7 as u32;
622 h = h.wrapping_mul(FNV_PRIME);
623 h ^= c6 as u32;
624 h = h.wrapping_mul(FNV_PRIME);
625 h ^= c5 as u32;
626 h = h.wrapping_mul(FNV_PRIME);
627 h ^= c4 as u32;
628 h = h.wrapping_mul(FNV_PRIME);
629 h ^= c3 as u32;
630 h = h.wrapping_mul(FNV_PRIME);
631 h ^= c2 as u32;
632 h = h.wrapping_mul(FNV_PRIME);
633 h ^= c1 as u32;
634 h = h.wrapping_mul(FNV_PRIME);
635 h ^= c0 & 0xFF;
636 h = h.wrapping_mul(FNV_PRIME);
637 h
638}
639
640#[inline]
642#[allow(clippy::too_many_arguments)]
643fn order8_hash(c8: u8, c7: u8, c6: u8, c5: u8, c4: u8, c3: u8, c2: u8, c1: u8, c0: u32) -> u32 {
644 let mut h = FNV_OFFSET;
645 h ^= c8 as u32;
646 h = h.wrapping_mul(FNV_PRIME);
647 h ^= c7 as u32;
648 h = h.wrapping_mul(FNV_PRIME);
649 h ^= c6 as u32;
650 h = h.wrapping_mul(FNV_PRIME);
651 h ^= c5 as u32;
652 h = h.wrapping_mul(FNV_PRIME);
653 h ^= c4 as u32;
654 h = h.wrapping_mul(FNV_PRIME);
655 h ^= c3 as u32;
656 h = h.wrapping_mul(FNV_PRIME);
657 h ^= c2 as u32;
658 h = h.wrapping_mul(FNV_PRIME);
659 h ^= c1 as u32;
660 h = h.wrapping_mul(FNV_PRIME);
661 h ^= c0 & 0xFF;
662 h = h.wrapping_mul(FNV_PRIME);
663 h
664}
665
666#[inline]
668#[allow(clippy::too_many_arguments)]
669fn order9_hash(
670 c9: u8,
671 c8: u8,
672 c7: u8,
673 c6: u8,
674 c5: u8,
675 c4: u8,
676 c3: u8,
677 c2: u8,
678 c1: u8,
679 c0: u32,
680) -> u32 {
681 let mut h = FNV_OFFSET;
682 h ^= c9 as u32;
683 h = h.wrapping_mul(FNV_PRIME);
684 h ^= c8 as u32;
685 h = h.wrapping_mul(FNV_PRIME);
686 h ^= c7 as u32;
687 h = h.wrapping_mul(FNV_PRIME);
688 h ^= c6 as u32;
689 h = h.wrapping_mul(FNV_PRIME);
690 h ^= c5 as u32;
691 h = h.wrapping_mul(FNV_PRIME);
692 h ^= c4 as u32;
693 h = h.wrapping_mul(FNV_PRIME);
694 h ^= c3 as u32;
695 h = h.wrapping_mul(FNV_PRIME);
696 h ^= c2 as u32;
697 h = h.wrapping_mul(FNV_PRIME);
698 h ^= c1 as u32;
699 h = h.wrapping_mul(FNV_PRIME);
700 h ^= c0 & 0xFF;
701 h = h.wrapping_mul(FNV_PRIME);
702 h
703}
704
705#[inline]
707fn quantize_run_for_mixer(run_len: u8) -> u8 {
708 match run_len {
709 0..=1 => 0,
710 2..=3 => 1,
711 4..=8 => 2,
712 _ => 3,
713 }
714}
715
716#[inline]
718fn quantize_line_pos(line_pos: u16) -> u8 {
719 match line_pos {
720 0..=3 => 0, 4..=15 => 1, 16..=63 => 2, _ => 3, }
725}
726
727#[cfg(test)]
728mod tests {
729 use super::*;
730
731 #[test]
732 fn initial_prediction_is_balanced() {
733 let mut engine = CMEngine::new();
734 let p = engine.predict();
735 assert!(
737 (1800..=2200).contains(&p),
738 "initial prediction should be near 2048, got {p}"
739 );
740 }
741
742 #[test]
743 fn prediction_always_in_range() {
744 let mut engine = CMEngine::new();
745 let data = b"Hello, World! This is a test of the CM engine.";
746 for &byte in data {
747 for bpos in 0..8 {
748 let p = engine.predict();
749 assert!(
750 (1..=4095).contains(&p),
751 "prediction out of range at bpos {bpos}: {p}"
752 );
753 let bit = (byte >> (7 - bpos)) & 1;
754 engine.update(bit);
755 }
756 }
757 }
758
759 #[test]
760 fn context_state_tracks_correctly() {
761 let mut engine = CMEngine::new();
762 let byte: u8 = 0x42;
764 for bpos in 0..8 {
765 let _p = engine.predict();
766 let bit = (byte >> (7 - bpos)) & 1;
767 engine.update(bit);
768 }
769 assert_eq!(engine.c1, 0x42);
771 assert_eq!(engine.c0, 1);
772 assert_eq!(engine.bpos, 0);
773 }
774
775 #[test]
776 fn repeated_byte_adapts() {
777 let mut engine = CMEngine::new();
778 let byte: u8 = b'A';
779 let mut total_bits: f64 = 0.0;
780 let mut first_byte_bits: f64 = 0.0;
781
782 for iteration in 0..50 {
783 let mut byte_bits: f64 = 0.0;
784 for bpos in 0..8 {
785 let p = engine.predict();
786 let bit = (byte >> (7 - bpos)) & 1;
787 let prob_of_bit = if bit == 1 {
788 p as f64 / 4096.0
789 } else {
790 1.0 - p as f64 / 4096.0
791 };
792 byte_bits += -prob_of_bit.max(0.001).log2();
793 engine.update(bit);
794 }
795 if iteration == 0 {
796 first_byte_bits = byte_bits;
797 }
798 total_bits += byte_bits;
799 }
800
801 let avg = total_bits / 50.0;
802 assert!(
803 avg < first_byte_bits,
804 "engine should improve: first={first_byte_bits:.2}, avg={avg:.2}"
805 );
806 }
807
808 #[test]
809 fn hash_functions_differ() {
810 let h1 = order1_hash(65, 1);
811 let h2 = order2_hash(0, 65, 1);
812 let h3 = order3_hash(0, 0, 65, 1);
813 assert_ne!(h1, h2);
815 assert_ne!(h2, h3);
816 }
817
818 #[test]
819 fn engine_deterministic() {
820 let data = b"determinism test";
822 let mut e1 = CMEngine::new();
823 let mut e2 = CMEngine::new();
824
825 for &byte in data.iter() {
826 for bpos in 0..8 {
827 let p1 = e1.predict();
828 let p2 = e2.predict();
829 assert_eq!(p1, p2, "engines diverged at bpos {bpos}");
830 let bit = (byte >> (7 - bpos)) & 1;
831 e1.update(bit);
832 e2.update(bit);
833 }
834 }
835 }
836}