1pub use crate::mixture::OnlineBytePredictor;
32
33#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
35#[non_exhaustive]
36pub enum BitOrder {
37 #[default]
39 MsbFirst,
40 LsbFirst,
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46#[non_exhaustive]
47pub enum BitStreamSemantics {
48 BytePacked {
54 order: BitOrder,
56 },
57 BinaryTokens,
67}
68
69impl Default for BitStreamSemantics {
70 fn default() -> Self {
71 Self::BytePacked {
75 order: BitOrder::MsbFirst,
76 }
77 }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq)]
82pub struct BinaryPrediction {
83 pub p0: f64,
85 pub p1: f64,
87}
88
89impl BinaryPrediction {
90 pub fn from_prob_one(p1: f64, floor: f64) -> Self {
101 Self::checked_from_prob_one(p1, floor).unwrap_or_else(|| {
102 panic!(
103 "RateBackendPredictor emitted non-finite p1 to BinaryPrediction::from_prob_one; \
104 this is now a hard contract violation (predictors must emit only finite \
105 non-negative values). See prediction.rs module docs and BinaryPrediction ctors."
106 )
107 })
108 }
109
110 pub fn checked_from_prob_one(p1: f64, floor: f64) -> Option<Self> {
115 if !p1.is_finite() {
116 return None;
117 }
118 let floor = binary_floor(floor);
119 let p1 = p1.clamp(floor, 1.0 - floor);
120 Some(Self { p0: 1.0 - p1, p1 })
121 }
122
123 pub fn from_prob_one_exact(p1: f64) -> Self {
135 Self::checked_from_prob_one_exact(p1).unwrap_or_else(|| {
136 panic!(
137 "RateBackendPredictor emitted non-finite p1 to BinaryPrediction::from_prob_one_exact; \
138 this is now a hard contract violation (predictors must emit only finite \
139 non-negative values). See prediction.rs module docs and BinaryPrediction ctors."
140 )
141 })
142 }
143
144 pub fn checked_from_prob_one_exact(p1: f64) -> Option<Self> {
149 if !p1.is_finite() {
150 return None;
151 }
152 let p1 = p1.clamp(0.0, 1.0);
153 Some(Self { p0: 1.0 - p1, p1 })
154 }
155
156 #[inline]
158 pub fn prob(self, bit: bool) -> f64 {
159 if bit { self.p1 } else { self.p0 }
160 }
161}
162
163pub trait OnlineBitPredictor {
165 fn begin_bit_stream(
167 &mut self,
168 _total_bits: Option<u64>,
169 _semantics: BitStreamSemantics,
170 ) -> Result<(), String> {
171 Ok(())
172 }
173
174 fn finish_bit_stream(&mut self) -> Result<(), String> {
176 Ok(())
177 }
178
179 fn bit_prediction(&mut self) -> BinaryPrediction;
181
182 fn update_bit(&mut self, bit: bool);
184
185 fn update_bit_frozen(&mut self, bit: bool) {
187 self.update_bit(bit);
188 }
189}
190
191#[derive(Clone, Debug)]
193pub struct BytePrefixMass {
194 tree: [f64; 512],
195 node: usize,
196 order: BitOrder,
197 bits_seen: u8,
198 symbol: u8,
199}
200
201const BYTE_PREFIX_TREE_ROOT: usize = 1;
202const BYTE_PREFIX_TREE_LEAF_BASE: usize = 256;
203
204impl BytePrefixMass {
205 pub fn from_pdf(pdf: &[f64], order: BitOrder) -> Self {
213 let mut weights = [0.0f64; 256];
214 for (idx, weight) in weights.iter_mut().enumerate() {
215 *weight = pdf.get(idx).copied().unwrap_or(0.0);
216 }
217 Self::from_raw_weights(weights, order)
218 }
219
220 pub fn from_log_probs(log_probs: &[f64], order: BitOrder) -> Self {
230 let log_probs = &log_probs[..log_probs.len().min(256)];
231 let max_log = log_probs
232 .iter()
233 .copied()
234 .filter(|lp| lp.is_finite())
235 .fold(f64::NEG_INFINITY, f64::max);
236 let mut weights = [0.0f64; 256];
237 if max_log.is_finite() {
238 for (weight, &lp) in weights.iter_mut().zip(log_probs.iter()) {
239 *weight = if lp.is_finite() {
240 (lp - max_log).exp()
241 } else {
242 0.0
243 };
244 }
245 }
246 Self::from_raw_weights(weights, order)
247 }
248
249 pub fn from_cdf(cdf: [f64; 257], order: BitOrder) -> Self {
251 let mut weights = [0.0f64; 256];
252 for (idx, weight) in weights.iter_mut().enumerate() {
253 *weight = cdf[idx + 1] - cdf[idx];
254 }
255 Self::from_raw_weights(weights, order)
256 }
257
258 fn from_raw_weights(mut weights: [f64; 256], order: BitOrder) -> Self {
259 let mut total = 0.0f64;
260 for weight in &mut weights {
261 *weight = if weight.is_finite() && *weight > 0.0 {
262 *weight
263 } else {
264 0.0
265 };
266 total += *weight;
267 }
268 if !total.is_finite() || total <= 0.0 {
269 weights.fill(1.0 / 256.0);
270 } else {
271 let inv = 1.0 / total;
272 for weight in &mut weights {
273 *weight *= inv;
274 }
275 }
276 Self::from_normalized_pdf(weights, order)
277 }
278
279 fn from_normalized_pdf(pdf: [f64; 256], order: BitOrder) -> Self {
280 let mut tree = [0.0f64; 512];
281 for (symbol, &mass) in pdf.iter().enumerate() {
282 let leaf = BYTE_PREFIX_TREE_LEAF_BASE + byte_prefix_leaf_offset(order, symbol as u8);
283 tree[leaf] = mass;
284 }
285 for node in (1..BYTE_PREFIX_TREE_LEAF_BASE).rev() {
286 tree[node] = tree[node * 2] + tree[node * 2 + 1];
287 }
288 Self {
289 tree,
290 node: BYTE_PREFIX_TREE_ROOT,
291 order,
292 bits_seen: 0,
293 symbol: 0,
294 }
295 }
296
297 pub fn prediction(&self) -> BinaryPrediction {
299 if self.is_complete() {
300 return BinaryPrediction::from_prob_one_exact(0.5);
301 }
302 let total = self.tree[self.node];
303 if !total.is_finite() || total <= 0.0 {
304 return BinaryPrediction::from_prob_one_exact(0.5);
310 }
311 let one = self.tree[self.node * 2 + 1];
312 let p1 = one / total;
313 BinaryPrediction::from_prob_one_exact(p1)
314 }
315
316 pub fn observe(&mut self, bit: bool) {
318 debug_assert!(
319 !self.is_complete(),
320 "BytePrefixMass::observe called after a full byte was already observed"
321 );
322 if self.is_complete() {
323 return;
324 }
325 self.node = self.node * 2 + usize::from(bit);
326 match self.order {
327 BitOrder::MsbFirst => {
328 self.symbol |= u8::from(bit) << (7 - self.bits_seen);
329 }
330 BitOrder::LsbFirst => {
331 self.symbol |= u8::from(bit) << self.bits_seen;
332 }
333 }
334 self.bits_seen = self.bits_seen.saturating_add(1);
335 }
336
337 #[inline]
339 pub fn is_complete(&self) -> bool {
340 self.bits_seen >= 8
341 }
342
343 #[inline]
346 pub fn has_partial_bits(&self) -> bool {
347 self.bits_seen > 0 && !self.is_complete()
348 }
349
350 #[inline]
352 pub fn symbol(&self) -> u8 {
353 self.symbol
354 }
355}
356
357#[inline]
358fn byte_prefix_leaf_offset(order: BitOrder, symbol: u8) -> usize {
359 match order {
360 BitOrder::MsbFirst => usize::from(symbol),
361 BitOrder::LsbFirst => usize::from(symbol.reverse_bits()),
362 }
363}
364
365#[inline]
366fn binary_floor(floor: f64) -> f64 {
367 if floor.is_finite() {
368 floor.clamp(1e-12, 0.499_999_999_999)
369 } else {
370 1e-12
371 }
372}
373
374#[inline]
380pub(crate) fn binary_prediction_from_probs(p0: f64, p1: f64, floor: f64) -> BinaryPrediction {
381 assert!(
382 p0.is_finite() && p0 >= 0.0 && p1.is_finite() && p1 >= 0.0,
383 "RateBackendPredictor emitted invalid probability to binary_prediction_from_probs: p0={p0}, p1={p1}; \
384 Predictor contract violation (must emit only finite non-negative values)"
385 );
386 let sum: f64 = p0 + p1;
387 if sum > 0.0 {
388 BinaryPrediction::from_prob_one(p1 / sum, floor)
389 } else {
390 BinaryPrediction::from_prob_one_exact(0.5)
392 }
393}
394
395#[inline]
406pub(crate) fn binary_prediction_from_log_probs(
407 logp0: f64,
408 logp1: f64,
409 floor: f64,
410) -> BinaryPrediction {
411 if logp0.is_nan() || logp1.is_nan() {
412 panic!(
413 "RateBackendPredictor emitted NaN log probability to binary_prediction_from_log_probs: \
414 logp0={logp0}, logp1={logp1}; contract violation"
415 );
416 }
417 let max_log: f64 = logp0.max(logp1);
418 if max_log.is_infinite() {
419 if max_log == f64::NEG_INFINITY {
420 return BinaryPrediction::from_prob_one_exact(0.5);
422 }
423 panic!(
424 "RateBackendPredictor emitted +Inf log probability to binary_prediction_from_log_probs: \
425 logp0={logp0}, logp1={logp1}; contract violation"
426 );
427 }
428 let p0: f64 = (logp0 - max_log).exp();
431 let p1: f64 = (logp1 - max_log).exp();
432 binary_prediction_from_probs(p0, p1, floor)
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 fn normalize_pdf_for_test(mut pdf: [f64; 256]) -> [f64; 256] {
440 let sum: f64 = pdf.iter().sum();
441 for p in &mut pdf {
442 *p /= sum;
443 }
444 pdf
445 }
446
447 fn assert_binary_prediction_close(actual: BinaryPrediction, expected: BinaryPrediction) {
448 assert!((actual.p0 - expected.p0).abs() < 1e-12);
449 assert!((actual.p1 - expected.p1).abs() < 1e-12);
450 }
451
452 #[test]
453 fn checked_binary_prediction_constructors_reject_non_finite_prob_one() {
454 for p1 in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
455 assert!(BinaryPrediction::checked_from_prob_one(p1, 0.0).is_none());
456 assert!(BinaryPrediction::checked_from_prob_one_exact(p1).is_none());
457 }
458
459 assert_binary_prediction_close(
460 BinaryPrediction::checked_from_prob_one(1.2, 0.01).expect("finite p1 should construct"),
461 BinaryPrediction {
462 p0: 1.0 - 0.99,
463 p1: 0.99,
464 },
465 );
466 assert_eq!(
467 BinaryPrediction::checked_from_prob_one_exact(-0.5),
468 Some(BinaryPrediction { p0: 1.0, p1: 0.0 })
469 );
470 }
471
472 fn bit_at(symbol: u8, order: BitOrder, bit_idx: u8) -> bool {
473 match order {
474 BitOrder::MsbFirst => ((symbol >> (7 - bit_idx)) & 1) == 1,
475 BitOrder::LsbFirst => ((symbol >> bit_idx) & 1) == 1,
476 }
477 }
478
479 fn extend_observed_prefix(observed: &mut u8, order: BitOrder, bit_idx: u8, bit: bool) {
480 match order {
481 BitOrder::MsbFirst => *observed |= u8::from(bit) << (7 - bit_idx),
482 BitOrder::LsbFirst => *observed |= u8::from(bit) << bit_idx,
483 }
484 }
485
486 fn prefix_matches(value: u8, observed: u8, order: BitOrder, bits_seen: u8) -> bool {
487 for bit_idx in 0..bits_seen {
488 if bit_at(value, order, bit_idx) != bit_at(observed, order, bit_idx) {
489 return false;
490 }
491 }
492 true
493 }
494
495 fn direct_prediction(
496 pdf: &[f64; 256],
497 order: BitOrder,
498 observed: u8,
499 bits_seen: u8,
500 ) -> BinaryPrediction {
501 if bits_seen >= 8 {
502 return BinaryPrediction::from_prob_one_exact(0.5);
503 }
504 let mut p0 = 0.0f64;
505 let mut p1 = 0.0f64;
506 for (value, &mass) in pdf.iter().enumerate() {
507 let value = value as u8;
508 if !prefix_matches(value, observed, order, bits_seen) {
509 continue;
510 }
511 if bit_at(value, order, bits_seen) {
512 p1 += mass;
513 } else {
514 p0 += mass;
515 }
516 }
517 let total = p0 + p1;
518 let p1 = if total.is_finite() && total > 0.0 {
519 p1 / total
520 } else {
521 0.5
522 };
523 BinaryPrediction::from_prob_one_exact(p1)
524 }
525
526 fn cdf_from_pdf(pdf: &[f64; 256]) -> [f64; 257] {
527 let mut cdf = [0.0f64; 257];
528 let mut acc = 0.0f64;
529 for idx in 0..256usize {
530 acc += pdf[idx];
531 cdf[idx + 1] = acc;
532 }
533 cdf
534 }
535
536 #[test]
537 fn byte_prefix_product_matches_symbol_probability_msb() {
538 let mut pdf = [0.0f64; 256];
539 for (idx, slot) in pdf.iter_mut().enumerate() {
540 *slot = (idx + 1) as f64;
541 }
542 let pdf = normalize_pdf_for_test(pdf);
543
544 let symbol = 0b1010_0110u8;
545 let mut prefix = BytePrefixMass::from_pdf(&pdf, BitOrder::MsbFirst);
546 let mut product = 1.0f64;
547 for bit_idx in 0..8u8 {
548 let bit = ((symbol >> (7 - bit_idx)) & 1) == 1;
549 let pred = prefix.prediction();
550 product *= pred.prob(bit);
551 prefix.observe(bit);
552 }
553 assert!(prefix.is_complete());
554 assert_eq!(prefix.symbol(), symbol);
555 assert!((product - pdf[symbol as usize]).abs() < 1e-12);
556 }
557
558 #[test]
559 fn byte_prefix_product_matches_symbol_probability_lsb() {
560 let mut pdf = [0.0f64; 256];
561 for (idx, slot) in pdf.iter_mut().enumerate() {
562 *slot = (idx + 3) as f64;
563 }
564 let pdf = normalize_pdf_for_test(pdf);
565
566 let symbol = 0b1010_0110u8;
567 let mut prefix = BytePrefixMass::from_pdf(&pdf, BitOrder::LsbFirst);
568 let mut product = 1.0f64;
569 for bit_idx in 0..8u8 {
570 let bit = ((symbol >> bit_idx) & 1) == 1;
571 let pred = prefix.prediction();
572 product *= pred.prob(bit);
573 prefix.observe(bit);
574 }
575 assert!(prefix.is_complete());
576 assert_eq!(prefix.symbol(), symbol);
577 assert!((product - pdf[symbol as usize]).abs() < 1e-12);
578 }
579
580 #[test]
581 fn byte_prefix_preserves_zero_mass_until_coder_boundary() {
582 let mut pdf = [0.0f64; 256];
583 pdf[0b1010_0000] = 0.25;
584 pdf[0b1010_0001] = 0.75;
585
586 let mut prefix = BytePrefixMass::from_pdf(&pdf, BitOrder::MsbFirst);
587 for bit_idx in 0..4u8 {
588 let bit = ((0b1010_0000u8 >> (7 - bit_idx)) & 1) == 1;
589 let prediction = prefix.prediction();
590 assert_eq!(prediction.prob(bit), 1.0);
591 assert_eq!(prediction.prob(!bit), 0.0);
592 prefix.observe(bit);
593 }
594
595 let impossible = prefix.prediction();
596 assert_eq!(impossible.p1, 0.0);
597 assert_eq!(impossible.p0, 1.0);
598 }
599
600 #[test]
601 fn byte_prefix_msb_prediction_preserves_tiny_positive_tail_mass() {
602 let eps = 5e-17;
603 let mut pdf = [eps; 256];
604 pdf[0] = 1.0 - (255.0 * eps);
605 let pdf = normalize_pdf_for_test(pdf);
606
607 let prediction = BytePrefixMass::from_pdf(&pdf, BitOrder::MsbFirst).prediction();
608 let expected = pdf[128..256].iter().copied().sum::<f64>();
609 assert!(expected > 0.0);
610 assert!(prediction.p1 > 0.0);
611 assert!((prediction.p1 - expected).abs() < 1e-18);
612 }
613
614 #[test]
615 fn byte_prefix_lsb_prediction_preserves_tiny_positive_tail_mass() {
616 let eps = 5e-17;
617 let mut pdf = [eps; 256];
618 pdf[0] = 1.0 - (255.0 * eps);
619 let pdf = normalize_pdf_for_test(pdf);
620
621 let prediction = BytePrefixMass::from_pdf(&pdf, BitOrder::LsbFirst).prediction();
622 let expected = pdf
623 .iter()
624 .enumerate()
625 .filter(|(idx, _)| (idx & 1) == 1)
626 .map(|(_, &p)| p)
627 .sum::<f64>();
628 assert!(expected > 0.0);
629 assert!(prediction.p1 > 0.0);
630 assert!((prediction.p1 - expected).abs() < 1e-18);
631 }
632
633 #[test]
634 fn byte_prefix_lsb_preserves_zero_mass_until_coder_boundary() {
635 let mut pdf = [0.0f64; 256];
636 pdf[0b0000_1010] = 0.25;
637 pdf[0b1000_1010] = 0.75;
638
639 let symbol = 0b0000_1010u8;
640 let mut prefix = BytePrefixMass::from_pdf(&pdf, BitOrder::LsbFirst);
641 for bit_idx in 0..7u8 {
642 let bit = ((symbol >> bit_idx) & 1) == 1;
643 let prediction = prefix.prediction();
644 assert_eq!(prediction.prob(bit), 1.0);
645 assert_eq!(prediction.prob(!bit), 0.0);
646 prefix.observe(bit);
647 }
648 }
649
650 #[test]
651 fn byte_prefix_prediction_matches_direct_reference_for_both_orders() {
652 let mut pdf = [0.0f64; 256];
653 for (idx, slot) in pdf.iter_mut().enumerate() {
654 *slot = ((idx * 37 + 11) % 257 + 1) as f64;
655 }
656 let pdf = normalize_pdf_for_test(pdf);
657
658 for order in [BitOrder::MsbFirst, BitOrder::LsbFirst] {
659 for symbol in 0u8..=255u8 {
660 let mut prefix = BytePrefixMass::from_pdf(&pdf, order);
661 let mut observed = 0u8;
662 for bit_idx in 0..8u8 {
663 let expected = direct_prediction(&pdf, order, observed, bit_idx);
664 let actual = prefix.prediction();
665 assert_binary_prediction_close(actual, expected);
666
667 let bit = bit_at(symbol, order, bit_idx);
668 prefix.observe(bit);
669 extend_observed_prefix(&mut observed, order, bit_idx, bit);
670 }
671 assert!(prefix.is_complete());
672 assert_eq!(prefix.symbol(), symbol);
673 }
674 }
675 }
676
677 #[test]
678 fn byte_prefix_from_cdf_matches_from_pdf_for_both_orders() {
679 let mut pdf = [0.0f64; 256];
680 for (idx, slot) in pdf.iter_mut().enumerate() {
681 *slot = ((idx * 19 + 7) % 193 + 1) as f64;
682 }
683 let pdf = normalize_pdf_for_test(pdf);
684 let cdf = cdf_from_pdf(&pdf);
685
686 for order in [BitOrder::MsbFirst, BitOrder::LsbFirst] {
687 let symbol = 0b1010_0110u8;
688 let mut from_pdf = BytePrefixMass::from_pdf(&pdf, order);
689 let mut from_cdf = BytePrefixMass::from_cdf(cdf, order);
690 for bit_idx in 0..8u8 {
691 assert_binary_prediction_close(from_pdf.prediction(), from_cdf.prediction());
692 let bit = bit_at(symbol, order, bit_idx);
693 from_pdf.observe(bit);
694 from_cdf.observe(bit);
695 }
696 assert_eq!(from_pdf.symbol(), symbol);
697 assert_eq!(from_cdf.symbol(), symbol);
698 }
699 }
700
701 #[test]
702 fn byte_prefix_from_log_probs_matches_from_pdf_for_both_orders() {
703 let mut pdf = [0.0f64; 256];
704 for (idx, slot) in pdf.iter_mut().enumerate() {
705 *slot = ((idx * 23 + 5) % 211 + 1) as f64;
706 }
707 let pdf = normalize_pdf_for_test(pdf);
708
709 let mut log_probs = [f64::NEG_INFINITY; 256];
710 for (dst, &mass) in log_probs.iter_mut().zip(pdf.iter()) {
711 *dst = mass.ln() + 17.0;
712 }
713
714 for order in [BitOrder::MsbFirst, BitOrder::LsbFirst] {
715 let symbol = 0b1010_0110u8;
716 let mut from_pdf = BytePrefixMass::from_pdf(&pdf, order);
717 let mut from_log_probs = BytePrefixMass::from_log_probs(&log_probs, order);
718 for bit_idx in 0..8u8 {
719 assert_binary_prediction_close(from_pdf.prediction(), from_log_probs.prediction());
720 let bit = bit_at(symbol, order, bit_idx);
721 from_pdf.observe(bit);
722 from_log_probs.observe(bit);
723 }
724 assert_eq!(from_pdf.symbol(), symbol);
725 assert_eq!(from_log_probs.symbol(), symbol);
726 }
727 }
728
729 #[test]
730 fn binary_prediction_from_probs_normalizes_and_floors() {
731 let pred = binary_prediction_from_probs(2.0, 6.0, 1e-6);
732 assert!((pred.p0 - 0.25).abs() < 1e-12);
733 assert!((pred.p1 - 0.75).abs() < 1e-12);
734 }
735
736 #[test]
737 fn binary_prediction_from_probs_both_zero_returns_exact_half() {
738 let pred = binary_prediction_from_probs(0.0, 0.0, 1e-6);
739 assert!((pred.p1 - 0.5).abs() < 1e-12);
740 assert!((pred.p0 - 0.5).abs() < 1e-12);
741 }
742
743 #[test]
744 fn binary_prediction_from_log_probs_both_neg_inf_returns_exact_half() {
745 let pred = binary_prediction_from_log_probs(f64::NEG_INFINITY, f64::NEG_INFINITY, 1e-6);
746 assert!((pred.p1 - 0.5).abs() < 1e-12);
747 }
748
749 #[test]
750 #[should_panic(expected = "invalid probability")]
751 fn binary_prediction_from_probs_panics_on_nan() {
752 let _ = binary_prediction_from_probs(f64::NAN, 0.5, 1e-6);
753 }
754
755 #[test]
756 #[should_panic(expected = "invalid probability")]
757 fn binary_prediction_from_probs_panics_on_negative() {
758 let _ = binary_prediction_from_probs(-1.0, 0.5, 1e-6);
759 }
760
761 #[test]
762 #[should_panic(expected = "NaN log probability")]
763 fn binary_prediction_from_log_probs_panics_on_nan() {
764 let _ = binary_prediction_from_log_probs(f64::NAN, -1.0, 1e-6);
765 }
766
767 #[test]
768 #[should_panic(expected = "+Inf log probability")]
769 fn binary_prediction_from_log_probs_panics_on_pos_inf() {
770 let _ = binary_prediction_from_log_probs(0.0, f64::INFINITY, 1e-6);
771 }
772
773 #[test]
774 fn byte_prefix_partial_bits_reports_only_in_progress_prefixes() {
775 let pdf = [1.0 / 256.0; 256];
776 let mut prefix = BytePrefixMass::from_pdf(&pdf, BitOrder::MsbFirst);
777 assert!(!prefix.has_partial_bits());
778
779 prefix.observe(true);
780 assert!(prefix.has_partial_bits());
781
782 for _ in 1..8u8 {
783 prefix.observe(false);
784 }
785 assert!(prefix.is_complete());
786 assert!(!prefix.has_partial_bits());
787 }
788}