1use std::fmt;
20
21use crate::rules::KernelId;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub struct VerifyRun {
26 pub k: u64,
28 pub n: u64,
30 pub contiguous: bool,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct RowTable {
37 pub runs: Vec<VerifyRun>,
39 pub carried: bool,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
45pub enum RowTableError {
46 #[error("a row table needs at least one sequence")]
48 Empty,
49 #[error("row table: {0}")]
51 Shape(String),
52 #[error("row table `{0}`: runs are `<k>x<n>`, `!` marking a fragmented run")]
54 Parse(String),
55}
56
57impl RowTable {
58 pub fn from_seqs(
61 ks: &[u64],
62 carried: bool,
63 contiguous: impl Fn(usize, usize) -> bool,
64 ) -> Result<Self, RowTableError> {
65 if ks.is_empty() {
66 return Err(RowTableError::Empty);
67 }
68 if ks.contains(&0) {
69 return Err(RowTableError::Shape("a sequence with no rows".into()));
70 }
71 let mut runs = Vec::new();
72 let mut g0 = 0;
73 while g0 < ks.len() {
74 let g1 = (g0..ks.len())
75 .find(|&i| ks[i] != ks[g0])
76 .unwrap_or(ks.len());
77 runs.push(VerifyRun {
78 k: ks[g0],
79 n: (g1 - g0) as u64,
80 contiguous: g1 - g0 > 1 && contiguous(g0, g1 - g0),
81 });
82 g0 = g1;
83 }
84 Ok(RowTable { runs, carried })
85 }
86
87 pub fn parse(s: &str) -> Result<Self, RowTableError> {
89 let bad = || RowTableError::Parse(s.to_string());
90 let mut runs = Vec::new();
91 let body = s.strip_prefix(UNCARRIED);
92 let carried = body.is_none();
93 for tok in body.unwrap_or(s).split_whitespace() {
94 let (body, contiguous) = match tok.strip_suffix('!') {
95 Some(b) => (b, false),
96 None => (tok, true),
97 };
98 let (k, n) = body.split_once('x').ok_or_else(bad)?;
99 let (k, n) = (k.parse().map_err(|_| bad())?, n.parse().map_err(|_| bad())?);
100 runs.push(VerifyRun { k, n, contiguous });
101 }
102 let t = RowTable { runs, carried };
103 t.check()?;
104 Ok(t)
105 }
106
107 fn check(&self) -> Result<(), RowTableError> {
108 if self.runs.is_empty() {
109 return Err(RowTableError::Empty);
110 }
111 if self.runs.iter().any(|r| r.k == 0 || r.n == 0) {
112 return Err(RowTableError::Shape(
113 "a run with no rows or no sequences".into(),
114 ));
115 }
116 if self.runs.iter().any(|r| r.n == 1 && r.contiguous) {
118 return Err(RowTableError::Shape(format!(
119 "`{self}`: a run of one sequence is never batched; mark it `!`"
120 )));
121 }
122 if self.runs.windows(2).any(|w| w[0].k == w[1].k) {
123 return Err(RowTableError::Shape(format!(
124 "`{self}`: adjacent runs of equal k are one run"
125 )));
126 }
127 Ok(())
128 }
129
130 pub fn rows(&self) -> u64 {
132 self.runs.iter().map(|r| r.k * r.n).sum()
133 }
134
135 pub fn seqs(&self) -> u64 {
137 self.runs.iter().map(|r| r.n).sum()
138 }
139
140 pub fn text(&self) -> String {
143 self.to_string()
144 }
145}
146
147impl fmt::Display for RowTable {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 let parts: Vec<String> = self
150 .runs
151 .iter()
152 .map(|r| format!("{}x{}{}", r.k, r.n, if r.contiguous { "" } else { "!" }))
153 .collect();
154 if !self.carried {
155 f.write_str(UNCARRIED)?;
156 }
157 f.write_str(&parts.join(" "))
158 }
159}
160
161const UNCARRIED: &str = "uncarried: ";
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
166pub enum Times {
167 Once,
169 PerSeq,
171 PerRow,
173 PerSeqRowButLast,
175}
176
177impl Times {
178 pub fn count(self, run: VerifyRun) -> u64 {
180 match self {
181 Times::Once => 1,
182 Times::PerSeq => run.n,
183 Times::PerRow => run.k * run.n,
184 Times::PerSeqRowButLast => run.k.saturating_sub(1) * run.n,
185 }
186 }
187
188 pub fn name(self) -> &'static str {
190 match self {
191 Times::Once => "once",
192 Times::PerSeq => "per_seq",
193 Times::PerRow => "per_row",
194 Times::PerSeqRowButLast => "per_seq_row_but_last",
195 }
196 }
197
198 pub fn parse(s: &str) -> Option<Self> {
200 [
201 Times::Once,
202 Times::PerSeq,
203 Times::PerRow,
204 Times::PerSeqRowButLast,
205 ]
206 .into_iter()
207 .find(|t| t.name() == s)
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct RunSelect {
214 pub k: (u64, u64),
216 pub n: (u64, u64),
218 pub contiguous: Option<bool>,
220 pub carried: Option<bool>,
222 pub launches: Vec<(KernelId, Times)>,
224 pub copies: Option<Times>,
226}
227
228impl RunSelect {
229 pub fn serves(&self, run: VerifyRun, carried: bool) -> bool {
231 (self.k.0..=self.k.1).contains(&run.k)
232 && (self.n.0..=self.n.1).contains(&run.n)
233 && self.contiguous.is_none_or(|c| c == run.contiguous)
234 && self.carried.is_none_or(|c| c == carried)
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct RunLaunches {
241 pub run: VerifyRun,
243 pub launches: Vec<(KernelId, Times)>,
245 pub copies: Option<Times>,
247}
248
249impl RunLaunches {
250 pub fn launch_count(&self) -> u64 {
252 self.launches.iter().map(|(_, t)| t.count(self.run)).sum()
253 }
254
255 pub fn copy_count(&self) -> u64 {
257 self.copies.map_or(0, |t| t.count(self.run))
258 }
259}
260
261pub fn resolve_runs(table: &RowTable, selects: &[RunSelect]) -> Option<Vec<RunLaunches>> {
264 table
265 .runs
266 .iter()
267 .map(|&run| {
268 selects
269 .iter()
270 .find(|s| s.serves(run, table.carried))
271 .map(|s| RunLaunches {
272 run,
273 launches: s.launches.clone(),
274 copies: s.copies,
275 })
276 })
277 .collect()
278}
279
280#[cfg(test)]
281#[path = "runs_tests.rs"]
282mod runs_tests;