metrale_model_layers/speculative/
tree_shape.rs1use anyhow::{Result, bail};
20
21pub const MAX_RANK: usize = 4;
24pub const MAX_NODES: usize = 7;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct TreeShape {
30 pub counts: Vec<u8>,
33}
34
35impl TreeShape {
36 pub fn parse(s: &str) -> Result<Self> {
37 let counts: Vec<u8> = s
38 .split(',')
39 .map(|t| t.trim().parse::<u8>())
40 .collect::<std::result::Result<_, _>>()
41 .map_err(|e| anyhow::anyhow!("METRALE_TREE_SHAPE parse: {e}"))?;
42 let shape = Self { counts };
43 shape.validate()?;
44 Ok(shape)
45 }
46
47 pub fn validate(&self) -> Result<()> {
48 if self.counts.is_empty() {
49 bail!("tree shape: empty");
50 }
51 if self.counts.iter().any(|&c| c == 0 || c as usize > MAX_RANK) {
52 bail!("tree shape: per-depth count must be 1..={MAX_RANK}");
53 }
54 if self.nodes() > MAX_NODES {
55 bail!("tree shape: {} nodes > max {MAX_NODES}", self.nodes());
56 }
57 Ok(())
58 }
59
60 pub fn spine_len(&self) -> usize {
61 self.counts.len()
62 }
63
64 pub fn nodes(&self) -> usize {
66 self.counts.iter().map(|&c| c as usize).sum()
67 }
68
69 pub fn verify_width(&self) -> usize {
71 self.nodes() + 1
72 }
73
74 pub fn shape_id(&self) -> u64 {
77 self.counts
78 .iter()
79 .fold(0u64, |acc, &c| (acc << 4) | (c as u64))
80 }
81
82 pub fn is_chain(&self) -> bool {
84 self.counts.iter().all(|&c| c == 1)
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct HedgeNode {
91 pub depth: usize,
92 pub rank: usize,
95 pub token: u32,
96}
97
98#[derive(Debug, Clone)]
100pub struct TreeDraft {
101 pub shape: TreeShape,
102 pub spine: Vec<u32>,
104 pub hedges: Vec<HedgeNode>,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct TreeRow {
112 pub token: u32,
113 pub depth: usize,
114 pub parent_row: usize,
115}
116
117impl TreeDraft {
118 pub fn rows(&self, root_token: u32) -> Vec<TreeRow> {
122 let l = self.spine.len();
123 let mut rows = Vec::with_capacity(1 + l + self.hedges.len());
124 rows.push(TreeRow {
125 token: root_token,
126 depth: 0,
127 parent_row: 0,
128 });
129 for (i, &t) in self.spine.iter().enumerate() {
130 rows.push(TreeRow {
131 token: t,
132 depth: i + 1,
133 parent_row: i,
134 });
135 }
136 for h in &self.hedges {
137 rows.push(TreeRow {
138 token: h.token,
139 depth: h.depth,
140 parent_row: h.depth - 1,
141 });
142 }
143 rows
144 }
145
146 pub fn accept_path(&self, rows: &[TreeRow], v: &[u32]) -> (Vec<usize>, u32) {
157 let l = self.spine.len();
158 let mut path = Vec::with_capacity(l);
159 let mut cur = 0usize;
160 for d in 1..=l {
161 let want = v[cur];
162 let spine_row = d;
165 let mut next = None;
166 if rows[spine_row].token == want {
167 next = Some(spine_row);
168 } else {
169 for (i, h) in self.hedges.iter().enumerate() {
170 if h.depth == d && h.token == want {
171 next = Some(1 + l + i);
172 break;
173 }
174 }
175 }
176 match next {
177 Some(r) => {
178 path.push(r);
179 cur = r;
180 if r > l {
181 break;
182 }
183 }
184 None => break,
185 }
186 }
187 (path, v[cur])
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 fn draft(shape: &str, spine: &[u32], hedges: &[(usize, usize, u32)]) -> TreeDraft {
196 TreeDraft {
197 shape: TreeShape::parse(shape).unwrap(),
198 spine: spine.to_vec(),
199 hedges: hedges
200 .iter()
201 .map(|&(depth, rank, token)| HedgeNode { depth, rank, token })
202 .collect(),
203 }
204 }
205
206 #[test]
207 fn parse_and_validate() {
208 let s = TreeShape::parse("1,2,2,2").unwrap();
209 assert_eq!(s.spine_len(), 4);
210 assert_eq!(s.nodes(), 7);
211 assert_eq!(s.verify_width(), 8);
212 assert!(!s.is_chain());
213 assert!(TreeShape::parse("1,1").unwrap().is_chain());
214 assert!(TreeShape::parse("").is_err());
215 assert!(TreeShape::parse("1,0").is_err());
216 assert!(TreeShape::parse("5").is_err());
217 assert!(TreeShape::parse("2,2,2,2").is_err());
218 assert_ne!(
219 TreeShape::parse("1,2").unwrap().shape_id(),
220 TreeShape::parse("2,1").unwrap().shape_id()
221 );
222 }
223
224 #[test]
225 fn row_layout_spine_prefix() {
226 let t = draft("2,2", &[10, 20], &[(1, 2, 11), (2, 2, 21)]);
227 let rows = t.rows(5);
228 assert_eq!(rows.len(), 5);
229 assert_eq!(
230 rows[0],
231 TreeRow {
232 token: 5,
233 depth: 0,
234 parent_row: 0
235 }
236 );
237 assert_eq!(
238 rows[1],
239 TreeRow {
240 token: 10,
241 depth: 1,
242 parent_row: 0
243 }
244 );
245 assert_eq!(
246 rows[2],
247 TreeRow {
248 token: 20,
249 depth: 2,
250 parent_row: 1
251 }
252 );
253 assert_eq!(
254 rows[3],
255 TreeRow {
256 token: 11,
257 depth: 1,
258 parent_row: 0
259 }
260 );
261 assert_eq!(
262 rows[4],
263 TreeRow {
264 token: 21,
265 depth: 2,
266 parent_row: 1
267 }
268 );
269 }
270
271 #[test]
272 fn accept_full_spine() {
273 let t = draft("1,1", &[10, 20], &[]);
274 let rows = t.rows(5);
275 let (path, bonus) = t.accept_path(&rows, &[10, 20, 99]);
276 assert_eq!(path, vec![1, 2]);
277 assert_eq!(bonus, 99);
278 }
279
280 #[test]
281 fn accept_hedge_rescue_is_terminal() {
282 let t = draft("2,1", &[10, 20], &[(1, 2, 11)]);
285 let rows = t.rows(5);
286 let (path, bonus) = t.accept_path(&rows, &[11, 55, 66, 20]);
287 assert_eq!(path, vec![3]);
288 assert_eq!(bonus, 20);
289 }
290
291 #[test]
292 fn accept_hedge_after_spine_prefix_is_terminal() {
293 let t = draft("1,2,1", &[10, 20, 30], &[(2, 2, 21)]);
296 let rows = t.rows(5);
297 let (path, bonus) = t.accept_path(&rows, &[10, 21, 66, 77, 88]);
298 assert_eq!(path, vec![1, 4]);
299 assert_eq!(bonus, 88);
300 }
301
302 #[test]
303 fn accept_reject_all_gives_root_bonus() {
304 let t = draft("2,1", &[10, 20], &[(1, 2, 11)]);
305 let rows = t.rows(5);
306 let (path, bonus) = t.accept_path(&rows, &[42, 0, 0, 0]);
307 assert!(path.is_empty());
308 assert_eq!(bonus, 42);
309 }
310}