main.rs
12.0 kB · rust · 339 lines
1mod census;2mod hunt;3mod rows;4mod tables;56use census::{Census, CHAMPIONS, MISSES, WINDOWS};7use mrlylab::ledger::{designs, Closed, Cost, Measure, Tier, SPACES, TERMS};8use rows::{Row, Sheet, Stop, CAP, CEILING, CELLS};9use std::env;10use std::path::{Path, PathBuf};11use std::time::Instant;1213fn definition() -> Vec<String> {14 vec![15 "A registry row is one (design, measure, axis) key of `mrlylab::ledger::keys` over the four tiers.".to_string(),16 format!("A row's rendered window is its first `min({CAP}, B)` terms, `B` the leading terms whose footprint fits {CELLS} cells, under the ledger's own budget of {CELLS} cells a term."),17 "A term's footprint is 1 cell for a closed measure, `number^dimension + level * span` for a convolved measure, `number^(dimension * level)` for a grid measure.".to_string(),18 format!("A row whose rendered terms are strictly increasing stops at the first term above {CEILING}; the count of rows truncated this way is printed, never assumed to lose nothing."),19 format!("Row `R` writes `n` iff `n` is a term of `R` inside `R`'s rendered window and `1 <= n <= {CEILING}`."),20 "Multiplicity counts rows, not `(row, index)` pairs: a row writing `n` at several indices counts once.".to_string(),21 "An integer `n` appears iff some row writes it, and is missed iff no row writes it.".to_string(),22 ]23}2425fn expected(tier: Tier) -> usize {26 let cost = match tier {27 Tier::Closed => Cost::Closed,28 Tier::Convolved => Cost::Convolved,29 _ => Cost::Grid,30 };31 let axes = match tier {32 Tier::Closed | Tier::Convolved => 2,33 _ => 1,34 };35 SPACES36 .iter()37 .map(|&(dimension, base)| {38 let designs = designs(dimension, base)39 .expect("the ledger spaces are walkable")40 .len();41 let measures = Measure::ALL42 .iter()43 .filter(|measure| measure.cost() == cost && measure.applies(dimension, base))44 .count();45 designs * measures * axes46 })47 .sum()48}4950fn verdict(agree: bool) -> &'static str {51 if agree {52 "PASS"53 } else {54 "FAIL"55 }56}5758fn list(values: &[i128]) -> String {59 values60 .iter()61 .map(|value| value.to_string())62 .collect::<Vec<_>>()63 .join(" ")64}6566fn stops(batch: &[&Row], stop: Stop) -> usize {67 batch.iter().filter(|row| row.stop == stop).count()68}6970fn registry(sheet: &Sheet) -> Vec<String> {71 let mut out = Vec::new();72 for &(tier, count) in &sheet.tiers {73 let batch: Vec<&Row> = sheet.rows.iter().filter(|row| row.tier == tier).collect();74 out.push(format!(75 "registry tier {} rows {} derived {} {} ceiling {} cap {} budget {} silent {}",76 tier.slug(),77 count,78 expected(tier),79 verdict(count == expected(tier)),80 stops(&batch, Stop::Ceiling),81 stops(&batch, Stop::Cap),82 stops(&batch, Stop::Budget),83 batch.iter().filter(|row| row.written.is_empty()).count()84 ));85 }86 let total: usize = sheet.tiers.iter().map(|&(_, count)| count).sum();87 let all: Vec<&Row> = sheet.rows.iter().collect();88 out.push(format!(89 "registry rows {total} rendered {} unread {}",90 sheet.rows.len(),91 sheet.unread92 ));93 out.push(format!(94 "registry stop ceiling {} cap {} budget {} silent {}",95 stops(&all, Stop::Ceiling),96 stops(&all, Stop::Cap),97 stops(&all, Stop::Budget),98 all.iter().filter(|row| row.written.is_empty()).count()99 ));100 out101}102103fn checks(sheet: &Sheet, book: &Census) {104 println!("CHECKS");105 let total: u64 = book.counts.iter().map(|&count| count as u64).sum();106 let walked: u64 = sheet.rows.iter().map(|row| row.written.len() as u64).sum();107 println!(108 "checks incidences histogram {total} row walk {walked} {}",109 verdict(total == walked)110 );111 let mut tried = 0u64;112 let mut wrong = 0u64;113 for row in &sheet.rows {114 let Some(form) = &row.form else {115 continue;116 };117 match form {118 Closed::Recurrence(coefficients) => {119 for index in coefficients.len()..row.head.len() {120 if let Some(value) = rows::replay(coefficients, &row.head, index) {121 tried += 1;122 wrong += u64::from(value != row.head[index]);123 }124 }125 }126 _ => {127 for (index, &term) in row.head.iter().enumerate() {128 if let Some(value) = rows::predict(form, index) {129 tried += 1;130 wrong += u64::from(value != term);131 }132 }133 }134 }135 }136 println!(137 "checks closed forms against rendered terms {tried} mismatches {wrong} {}",138 verdict(wrong == 0)139 );140 for window in WINDOWS {141 let (never, once, many) = census::split(book, window);142 println!(143 "checks window {window} never {never} once {once} multiple {many} sum {} {}",144 never + once + many,145 verdict(never + once + many == window)146 );147 }148 let classics: [(&str, &[i128]); 5] = [149 ("sequence_dim=2_code=7_measure=fills_axis=side", &[8, 21, 40, 65]),150 ("sequence_dim=2_code=7_measure=fills_axis=level", &[8, 64, 512]),151 ("sequence_dim=2_code=7_measure=voids_axis=level", &[1, 17, 217]),152 ("sequence_dim=3_code=23_measure=fills_axis=side", &[20, 81, 208, 425]),153 ("sequence_dim=3_code=23_measure=surface_axis=level", &[72, 1056, 18048]),154 ];155 for (name, head) in classics {156 let row = sheet157 .rows158 .iter()159 .find(|row| row.name == name)160 .expect("the classic is a registry row");161 let seen = row.head.len() >= head.len() && row.head[..head.len()] == *head;162 let written = head163 .iter()164 .all(|&term| term > CEILING || book.counts[term as usize] > 0);165 println!(166 "checks classic {name} head {} written {} {}",167 verdict(seen),168 verdict(written),169 list(&row.head)170 );171 }172 let surface = sheet173 .rows174 .iter()175 .find(|row| row.name == "sequence_dim=3_code=23_measure=surface_axis=level")176 .expect("the sponge surface is a registry row");177 let obeys = (2..surface.head.len()).all(|index| {178 surface.head[index] == 28 * surface.head[index - 1] - 160 * surface.head[index - 2]179 });180 println!(181 "checks recurrence a(L) = 28 a(L-1) - 160 a(L-2) on {} terms {}",182 surface.head.len(),183 verdict(obeys)184 );185 let (top, count) = census::champions(book, 1)[0];186 let walk = census::writers(sheet, top as i128).len();187 println!(188 "checks champion {top} histogram {count} row walk {walk} {}",189 verdict(count as usize == walk)190 );191 println!(192 "checks incidences rows {} pairs {} terms at or below zero {}",193 book.incidences,194 book.incidences + book.repeats,195 book.low196 );197}198199fn tables(book: &Census) {200 println!("WINDOWS");201 for window in WINDOWS {202 let (never, once, many) = census::split(book, window);203 println!(204 "window {window} never {never} once {once} multiple {many} written {} share {:.4}",205 once + many,206 (once + many) as f64 / window as f64207 );208 }209 println!();210 println!("CHAMPIONS");211 for (rank, (value, count)) in census::champions(book, CHAMPIONS).into_iter().enumerate() {212 println!("champion {} {value} rows {count}", rank + 1);213 }214 println!();215 println!("MISSES");216 let first = census::misses(book, MISSES);217 println!(218 "misses first {MISSES} {}",219 first220 .iter()221 .map(|value| value.to_string())222 .collect::<Vec<_>>()223 .join(" ")224 );225 println!();226 println!("DECADES");227 for band in census::bands(book) {228 println!(229 "decade {}..{} width {} missed {} density {:.6}",230 band.first,231 band.last,232 band.width(),233 band.missed,234 band.density()235 );236 }237}238239fn emit(out: &Path, sheet: &Sheet, book: &Census, lines: &[String]) {240 std::fs::create_dir_all(out).expect("the output directory is writable");241 let table: Vec<Vec<String>> = sheet242 .rows243 .iter()244 .map(|row| {245 vec![246 row.name.clone(),247 row.tier.slug().to_string(),248 row.form249 .as_ref()250 .map_or_else(|| "none".to_string(), |form| form.text()),251 list(&row.head),252 list(&row.written),253 row.stop.slug().to_string(),254 ]255 })256 .collect();257 tables::write_csv(258 &out.join("rows.csv"),259 &["key", "tier", "closed", "head", "written", "stop"],260 &table,261 );262 let multiset: Vec<Vec<String>> = (1..=CEILING as usize)263 .map(|value| vec![value.to_string(), book.counts[value].to_string()])264 .collect();265 tables::write_csv(&out.join("multiset.csv"), &["integer", "rows"], &multiset);266 let mut page = vec![267 "# Integer Census Manifest".to_string(),268 String::new(),269 "## DEFINITION".to_string(),270 String::new(),271 ];272 page.extend(definition().iter().map(|line| format!("- {line}")));273 page.push(String::new());274 page.push("## REGISTRY".to_string());275 page.push(String::new());276 page.extend(lines.iter().map(|line| format!("- {line}")));277 page.push(String::new());278 page.push("## CENSUS".to_string());279 page.push(String::new());280 for window in WINDOWS {281 let (never, once, many) = census::split(book, window);282 page.push(format!(283 "- window `1..={window}`: never {never}, once {once}, multiple {many}."284 ));285 }286 page.push(format!(287 "- incidences: {} `(row, integer)` pairs, {} `(row, index, integer)` pairs, {} terms at or below zero.",288 book.incidences,289 book.incidences + book.repeats,290 book.low291 ));292 page.push(String::new());293 page.push("## FILES".to_string());294 page.push(String::new());295 page.push(format!(296 "- `rows.csv`: one line a registry row, {} lines and a header.",297 sheet.rows.len()298 ));299 page.push(format!("- `rows.csv` fields: `key`, `tier`, `closed` (the closed form or `none`), `head` (the first {TERMS} rendered terms, space separated), `written` (the distinct terms of the rendered window inside `1..={CEILING}`, ascending, space separated, empty when the row writes none), `stop` (`ceiling`, `cap` or `budget`)."));300 page.push(format!("- `multiset.csv`: `integer,rows` for every `n` in `1..={CEILING}`, {} lines and a header, `rows` the row multiplicity.", CEILING));301 page.push("- `multiset.csv` is the fold of the `written` column of `rows.csv`, so `rows.csv` rebuilds it; the fold is not invertible the other way.".to_string());302 tables::write_lines(&out.join("MANIFEST.md"), &page);303}304305fn main() {306 let out = env::args()307 .nth(1)308 .map(PathBuf::from)309 .unwrap_or_else(|| env::temp_dir().join("integer-census"));310 println!("DEFINITION");311 for line in definition() {312 println!("{line}");313 }314 println!();315 let clock = Instant::now();316 let sheet = rows::read();317 let walk = clock.elapsed().as_secs_f64();318 let book = census::build(&sheet);319 println!("REGISTRY");320 let lines = registry(&sheet);321 for line in &lines {322 println!("{line}");323 }324 println!("registry walk {walk:.1}s on {} threads", rows::THREADS);325 println!();326 tables(&book);327 println!();328 hunt::report(&sheet, &book);329 println!();330 checks(&sheet, &book);331 println!();332 emit(&out, &sheet, &book, &lines);333 println!("FILES");334 println!(335 "files rows.csv multiset.csv MANIFEST.md in {}",336 out.display()337 );338 println!("files run {:.1}s", clock.elapsed().as_secs_f64());339}