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}