batch.rsannotatedbatch.rssource155 lines · 5.1 KB · raw
1//! The batch planner: how each row reaches the model (contract
2//! *Execution*, "Request layout").
3//!
4//! A row with two or more distinct questions is sent as the state, each
5//! question a branch. A row with exactly one would be sent in that
6//! question's instructions, beside other rows' questions, but that layout
7//! has no accuracy measurement against row as state, so it waits on the
8//! layout-parity release gate (*Testing*). Until the gate passes it is
9//! unreachable: [`LayoutParity`] has no values, so no [`Planner`] can hold
10//! one, and every row is sent as the state.
11
12use std::collections::{HashMap, HashSet};
13use std::hash::Hash;
14
15use jev_protocol::{Json, ProtocolError};
16
17use crate::Layout;
18
19/// Evidence that row in instructions passed the layout-parity release
20/// gate: jev-orderby-bench's mean |Δp|, Spearman and position effect
21/// against row as state, recorded against the pinned model version.
22///
23/// It has not been run, so the type is uninhabited. Giving it a value is
24/// the change that enables row in instructions, and it belongs with the
25/// recorded result.
26#[derive(Clone, Copy, Debug)]
27pub enum LayoutParity {}
28
29/// Chooses each row's layout, and builds its state.
30#[derive(Clone, Copy, Debug, Default)]
31pub struct Planner {
32    /// Always `None` while [`LayoutParity`] is uninhabited.
33    parity: Option<LayoutParity>,
34}
35
36/// A row's layout as the [`Planner`] chose it. Only a planner makes one,
37/// and row in instructions carries the [`LayoutParity`] evidence, so a
38/// layout the gate has not passed cannot be written down, let alone
39/// handed to [`Planner::place`].
40#[derive(Clone, Copy, Debug)]
41pub struct Planned(Plan);
42
43#[derive(Clone, Copy, Debug)]
44enum Plan {
45    RowAsState,
46    RowInInstructions(LayoutParity),
47}
48
49impl Planned {
50    pub fn layout(self) -> Layout {
51        match self.0 {
52            Plan::RowAsState => Layout::RowAsState,
53            Plan::RowInInstructions(gate) => match gate {},
54        }
55    }
56}
57
58/// Where a row goes in its request: its layout and the request's
59/// `state`. Only a [`Planner`] makes one, so a layout the gate has not
60/// passed never reaches a request or a cache key.
61#[derive(Clone)]
62pub struct Placement {
63    layout: Layout,
64    state: Json,
65}
66
67impl Placement {
68    pub(crate) fn new(layout: Layout, state: Json) -> Self {
69        Placement { layout, state }
70    }
71
72    pub fn layout(&self) -> Layout {
73        self.layout
74    }
75
76    /// Under row as state, the row's canonical JSON.
77    pub fn state(&self) -> &Json {
78        &self.state
79    }
80}
81
82impl Planner {
83    pub const fn new() -> Self {
84        Planner { parity: None }
85    }
86
87    /// The layout for a row with `questions` distinct questions.
88    pub fn layout(&self, questions: usize) -> Planned {
89        match (questions, self.parity) {
90            (1, Some(gate)) => Planned(Plan::RowInInstructions(gate)),
91            _ => Planned(Plan::RowAsState),
92        }
93    }
94
95    /// Each row's layout, from the questions asked about it: `asks` pairs
96    /// a row with one question's serialized bytes, and a question asked
97    /// twice about the same row counts once.
98    pub fn layouts<'q, R: Hash + Eq>(&self, asks: impl IntoIterator<Item = (R, &'q [u8])>) -> HashMap<R, Planned> {
99        let mut questions: HashMap<R, HashSet<&'q [u8]>> = HashMap::new();
100        for (row, question) in asks {
101            questions.entry(row).or_default().insert(question);
102        }
103        questions.into_iter().map(|(row, q)| (row, self.layout(q.len()))).collect()
104    }
105
106    /// `row`, a JSON text, placed as `planned`. Refused when the row is
107    /// not JSON.
108    pub fn place(&self, planned: Planned, row: &str) -> Result<Placement, ProtocolError> {
109        match planned.0 {
110            Plan::RowAsState => Ok(Placement::new(Layout::RowAsState, Json::canonical(row)?)),
111            Plan::RowInInstructions(gate) => match gate {},
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn two_or_more_questions_are_row_as_state() {
122        let p = Planner::new();
123        assert_eq!(p.layout(2).layout(), Layout::RowAsState);
124        assert_eq!(p.layout(255).layout(), Layout::RowAsState);
125    }
126
127    #[test]
128    fn one_question_is_row_as_state_until_the_gate_passes() {
129        assert_eq!(Planner::new().layout(1).layout(), Layout::RowAsState);
130        assert_eq!(Planner::default().layout(1).layout(), Layout::RowAsState);
131    }
132
133    #[test]
134    fn layouts_count_distinct_questions_per_row() {
135        let (a, b): (&[u8], &[u8]) = (b"{\"noul\":1}", b"{\"noul\":2}");
136        let layouts = Planner::new().layouts([("r1", a), ("r1", b), ("r2", a), ("r2", a)]);
137        assert_eq!(layouts.len(), 2);
138        assert_eq!(layouts["r1"].layout(), Layout::RowAsState);
139        assert_eq!(layouts["r2"].layout(), Layout::RowAsState);
140    }
141
142    #[test]
143    fn row_as_state_sends_the_canonical_row() {
144        let p = Planner::new();
145        let placed = p.place(p.layout(1), r#"{ "b": 1, "a": "x" }"#).unwrap();
146        assert_eq!(placed.layout(), Layout::RowAsState);
147        assert_eq!(placed.state().as_str(), r#"{"a":"x","b":1}"#);
148    }
149
150    #[test]
151    fn a_row_that_is_not_json_is_refused() {
152        let p = Planner::new();
153        assert!(p.place(p.layout(2), "{").is_err());
154    }
155}