datacortex_core/model/
order0.rs1use crate::state::state_map::StateMap;
11use crate::state::state_table::StateTable;
12
13pub struct Order0Model {
18 states: [u8; 256],
20 state_map: StateMap,
22}
23
24impl Order0Model {
25 pub fn new() -> Self {
27 Order0Model {
28 states: [0u8; 256],
29 state_map: StateMap::new(),
30 }
31 }
32
33 #[inline]
38 pub fn predict(&self, context: usize) -> u32 {
39 let state = self.states[context & 0xFF];
40 self.state_map.predict(state)
41 }
42
43 #[inline]
48 pub fn update(&mut self, context: usize, bit: u8) {
49 let ctx = context & 0xFF;
50 let state = self.states[ctx];
51
52 self.state_map.update(state, bit);
54
55 self.states[ctx] = StateTable::next(state, bit);
57 }
58}
59
60impl Default for Order0Model {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn initial_prediction_is_balanced() {
72 let model = Order0Model::new();
73 let p = model.predict(1);
75 assert_eq!(p, 2048, "initial prediction should be 2048");
76 }
77
78 #[test]
79 fn prediction_in_range() {
80 let model = Order0Model::new();
81 for c in 1..=255 {
82 let p = model.predict(c);
83 assert!(
84 (1..=4095).contains(&p),
85 "context {c}: pred {p} out of range"
86 );
87 }
88 }
89
90 #[test]
91 fn update_adapts_prediction() {
92 let mut model = Order0Model::new();
93 let before = model.predict(1);
94 model.update(1, 1);
95 let after = model.predict(1);
96 assert_ne!(before, after, "prediction should change after update");
100 }
101
102 #[test]
103 fn different_contexts_have_separate_states() {
104 let mut model = Order0Model::new();
105 for _ in 0..50 {
107 model.update(10, 1);
108 }
109 for _ in 0..50 {
111 model.update(5, 0);
112 }
113 let p5 = model.predict(5);
114 let p10 = model.predict(10);
115 assert!(
117 p10 > p5,
118 "context 10 (all 1s) should predict higher than context 5 (all 0s): p10={p10}, p5={p5}"
119 );
120 }
121
122 #[test]
123 fn simulate_byte_encoding() {
124 let mut model = Order0Model::new();
125 let byte: u8 = 0x42; let mut c: usize = 1;
128 for bpos in 0..8 {
129 let bit = (byte >> (7 - bpos)) & 1;
130 let _p = model.predict(c);
131 model.update(c, bit);
132 c = (c << 1) | bit as usize;
133 }
134 assert_eq!(c, 0x42 + 256);
136 }
137
138 #[test]
139 fn repeated_pattern_adapts() {
140 let mut model = Order0Model::new();
141 let byte: u8 = 0x41;
143 let mut total_surprise: f64 = 0.0;
144 let mut first_byte_surprise: f64 = 0.0;
145
146 for iteration in 0..20 {
147 let mut c: usize = 1;
148 let mut byte_surprise: f64 = 0.0;
149 for bpos in 0..8 {
150 let bit = (byte >> (7 - bpos)) & 1;
151 let p = model.predict(c);
152 let prob_of_bit = if bit == 1 {
154 p as f64 / 4096.0
155 } else {
156 1.0 - p as f64 / 4096.0
157 };
158 byte_surprise += -prob_of_bit.log2();
159 model.update(c, bit);
160 c = (c << 1) | bit as usize;
161 }
162 if iteration == 0 {
163 first_byte_surprise = byte_surprise;
164 }
165 total_surprise += byte_surprise;
166 }
167
168 let last_avg = total_surprise / 20.0;
169 assert!(
170 last_avg < first_byte_surprise,
171 "model should improve: first byte = {first_byte_surprise:.2} bits, avg = {last_avg:.2} bits"
172 );
173 }
174}