main.rs

19.3 kB · rust · 658 lines

1use mrlyrs::math::bang::factory::create;2use mrlyrs::math::bang::Code;3use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};4use std::env;5use std::hash::{BuildHasherDefault, Hasher};6use std::time::Instant;78// HASH910#[derive(Default)]11struct Fx(u64);1213impl Hasher for Fx {14    fn finish(&self) -> u64 {15        self.016    }17    fn write(&mut self, bytes: &[u8]) {18        for &byte in bytes {19            self.write_u64(byte as u64);20        }21    }22    fn write_u64(&mut self, n: u64) {23        self.0 = (self.0.rotate_left(5) ^ n).wrapping_mul(0x517c_c1b7_2722_0a95);24    }25}2627type Fast = BuildHasherDefault<Fx>;2829// PICTURE3031#[derive(Clone, PartialEq, Eq)]32struct Pic {33    side: usize,34    cells: Vec<u8>,35}3637impl Pic {38    fn one() -> Pic {39        Pic {40            side: 1,41            cells: vec![1],42        }43    }44    fn block(window: [u8; 4]) -> Pic {45        Pic {46            side: 2,47            cells: window.to_vec(),48        }49    }50    fn at(&self, row: usize, col: usize) -> u8 {51        self.cells[row * self.side + col]52    }53    fn pairs(&self) -> BTreeSet<[u8; 4]> {54        let mut out = BTreeSet::new();55        for row in 0..self.side - 1 {56            for col in 0..self.side - 1 {57                out.insert([58                    self.at(row, col),59                    self.at(row, col + 1),60                    self.at(row + 1, col),61                    self.at(row + 1, col + 1),62                ]);63            }64        }65        out66    }67}6869// DESIGN7071#[derive(Clone)]72struct Design {73    base: usize,74    code: u32,75    tile: Vec<u8>,76}7778impl Design {79    fn new(base: usize, code: u32) -> Design {80        let tile = (0..base * base)81            .map(|cell| ((code >> cell) & 1) as u8)82            .collect();83        Design { base, code, tile }84    }85    fn filled(&self) -> Vec<(usize, usize)> {86        let b = self.base;87        (0..b * b)88            .filter(|&cell| self.tile[cell] == 1)89            .map(|cell| (cell / b, cell % b))90            .collect()91    }92    fn empty(&self) -> bool {93        self.code == 094    }95    fn full(&self) -> bool {96        self.tile.iter().all(|&cell| cell == 1)97    }98    fn degenerate(&self) -> bool {99        let cells = self.filled();100        let last = self.base - 1;101        !cells.is_empty()102            && (cells.iter().all(|c| c.0 == 0)103                || cells.iter().all(|c| c.0 == last)104                || cells.iter().all(|c| c.1 == 0)105                || cells.iter().all(|c| c.1 == last))106    }107    fn image(&self, g: usize) -> Design {108        let b = self.base;109        let mut code = 0u32;110        for (row, col) in self.filled() {111            let (mut r, mut c) = if g & 4 != 0 { (col, row) } else { (row, col) };112            for _ in 0..g & 3 {113                (r, c) = (c, b - 1 - r);114            }115            code |= 1 << (r * b + c);116        }117        Design::new(b, code)118    }119    fn canonical(&self) -> u32 {120        (0..8).map(|g| self.image(g).code).min().unwrap()121    }122    fn substitute(&self, pic: &Pic) -> Pic {123        let b = self.base;124        let side = pic.side * b;125        let mut cells = vec![0u8; side * side];126        for row in 0..side {127            for col in 0..side {128                cells[row * side + col] =129                    pic.at(row / b, col / b) & self.tile[(row % b) * b + col % b];130            }131        }132        Pic { side, cells }133    }134    fn render(&self, level: usize) -> Pic {135        let mut pic = Pic::one();136        for _ in 0..level {137            pic = self.substitute(&pic);138        }139        pic140    }141    fn language(&self) -> (Vec<[u8; 4]>, usize) {142        let mut set = self.render(1).pairs();143        let mut level = 1;144        loop {145            let mut next = BTreeSet::new();146            for window in &set {147                next.extend(self.substitute(&Pic::block(*window)).pairs());148            }149            assert!(150                next.is_superset(&set),151                "the 2 x 2 windows of a render shrank one level up"152            );153            if next == set {154                return (set.into_iter().collect(), level);155            }156            set = next;157            level += 1;158        }159    }160}161162// COUNT163164fn depth(base: usize, top: usize) -> usize {165    let mut n = 0;166    while base.pow(n as u32) + 1 < top {167        n += 1;168    }169    n170}171172fn count(design: &Design, top: usize) -> Vec<usize> {173    let (pairs, _) = design.language();174    let n = depth(design.base, top);175    let pics: Vec<Pic> = pairs176        .iter()177        .map(|window| {178            let mut pic = Pic::block(*window);179            for _ in 0..n {180                pic = design.substitute(&pic);181            }182            pic183        })184        .collect();185    let side = 2 * design.base.pow(n as u32);186    let mut ids: Vec<Vec<u32>> = pics187        .iter()188        .map(|pic| pic.cells.iter().map(|&cell| cell as u32).collect())189        .collect();190    let values: BTreeSet<u8> = pics191        .iter()192        .flat_map(|pic| pic.cells.iter().copied())193        .collect();194    let mut out = vec![values.len()];195    for k in 1..top {196        let wide = side - k + 1;197        let narrow = wide - 1;198        let mut map: HashMap<u64, u32, Fast> = HashMap::default();199        for (pic, id) in pics.iter().zip(ids.iter_mut()) {200            let mut next = vec![0u32; narrow * narrow];201            for x in 0..narrow {202                for y in 0..narrow {203                    let key = (id[x * wide + y] as u64) << 34204                        | (id[(x + 1) * wide + y + 1] as u64) << 2205                        | (pic.at(x + k, y) as u64) << 1206                        | pic.at(x, y + k) as u64;207                    let fresh = map.len() as u32;208                    next[x * narrow + y] = *map.entry(key).or_insert(fresh);209                }210            }211            *id = next;212        }213        assert!(map.len() < 1 << 30, "window names overflow the key");214        out.push(map.len());215    }216    out217}218219// SCAN220221fn brute(pic: &Pic, top: usize) -> Vec<usize> {222    let side = pic.side;223    let mut out = Vec::new();224    for k in 1..=top.min(side).min(64) {225        let mask = if k == 64 { u64::MAX } else { (1u64 << k) - 1 };226        let wide = side - k + 1;227        let mut words = vec![0u64; side * wide];228        for row in 0..side {229            let mut word = 0u64;230            for col in 0..side {231                word = ((word << 1) | pic.at(row, col) as u64) & mask;232                if col + 1 >= k {233                    words[row * wide + col + 1 - k] = word;234                }235            }236        }237        let mut seen: HashSet<Vec<u64>, Fast> = HashSet::default();238        for x in 0..wide {239            for y in 0..wide {240                seen.insert((0..k).map(|t| words[(x + t) * wide + y]).collect());241            }242        }243        out.push(seen.len());244    }245    out246}247248fn crate_render(design: &Design, level: usize) -> Pic {249    let b = design.base;250    let tensor = create(Code::from(design.code as u64), b, 2, b, level).unwrap();251    Pic {252        side: b.pow(level as u32),253        cells: tensor.bytes().unwrap().to_vec(),254    }255}256257// KIND258259fn blocks(pairs: &BTreeSet<[u8; 4]>) -> Option<(usize, u32, u32)> {260    for s in 1..=2usize {261        let total = 1u32 << (s * s);262        for a in 0..total {263            for b in a + 1..total {264                let free = (0..16u32).all(|arrangement| {265                    let side = 2 * s;266                    let mut cells = vec![0u8; side * side];267                    for row in 0..side {268                        for col in 0..side {269                            let slot = (row / s) * 2 + col / s;270                            let block = if (arrangement >> slot) & 1 == 1 { b } else { a };271                            cells[row * side + col] =272                                ((block >> ((row % s) * s + col % s)) & 1) as u8;273                        }274                    }275                    Pic { side, cells }.pairs().is_subset(pairs)276                });277                if free {278                    return Some((s, a, b));279                }280            }281        }282    }283    None284}285286fn branching(states: usize, edges: &[(usize, usize)]) -> bool {287    let mut reach = vec![vec![false; states]; states];288    for &(from, to) in edges {289        reach[from][to] = true;290    }291    for mid in 0..states {292        for from in 0..states {293            for to in 0..states {294                if reach[from][mid] && reach[mid][to] {295                    reach[from][to] = true;296                }297            }298        }299    }300    (0..states).any(|root| {301        let class: Vec<usize> = (0..states)302            .filter(|&v| reach[root][v] && reach[v][root])303            .collect();304        let inner = edges305            .iter()306            .filter(|(f, t)| class.contains(f) && class.contains(t))307            .count();308        !class.is_empty() && inner > class.len()309    })310}311312fn lines(pairs: &BTreeSet<[u8; 4]>) -> Option<&'static str> {313    let has = |w: [u8; 4]| pairs.contains(&w);314    let mut two = Vec::new();315    let mut cols = Vec::new();316    for a in 0..2u8 {317        for b in 0..2u8 {318            if has([a, a, b, b]) {319                two.push((a as usize, b as usize));320            }321            if has([a, b, a, b]) {322                cols.push((a as usize, b as usize));323            }324        }325    }326    let mut diag = Vec::new();327    let mut anti = Vec::new();328    for p in 0..2u8 {329        for q in 0..2u8 {330            for s in 0..2u8 {331                let from = (p * 2 + q) as usize;332                let to = (q * 2 + s) as usize;333                if has([q, s, p, q]) {334                    diag.push((from, to));335                }336                if has([p, q, q, s]) {337                    anti.push((from, to));338                }339            }340        }341    }342    if branching(2, &two) {343        Some("rows")344    } else if branching(2, &cols) {345        Some("columns")346    } else if branching(4, &diag) {347        Some("diagonals")348    } else if branching(4, &anti) {349        Some("antidiagonals")350    } else {351        None352    }353}354355fn xor(pairs: &BTreeSet<[u8; 4]>) -> bool {356    [(0, 1, 2), (1, 0, 3), (2, 0, 3), (3, 1, 2)]357        .iter()358        .any(|&(corner, left, right)| {359            let rule: BTreeSet<[u8; 4]> = (0..16u8)360                .map(|bits| [bits & 1, (bits >> 1) & 1, (bits >> 2) & 1, (bits >> 3) & 1])361                .filter(|w| w[corner] == w[left] ^ w[right])362                .collect();363            &rule == pairs364        })365}366367fn shape(s: usize, block: u32) -> String {368    (0..s)369        .map(|row| {370            (0..s)371                .map(|col| ((block >> (row * s + col)) & 1).to_string())372                .collect::<String>()373        })374        .collect::<Vec<_>>()375        .join("/")376}377378fn aligned(design: &Design, m: usize) -> bool {379    let (pairs, _) = design.language();380    let unit = design.base.pow(m as u32);381    let render = design.render(m);382    pairs.iter().all(|window| {383        let mut pic = Pic::block(*window);384        for _ in 0..m {385            pic = design.substitute(&pic);386        }387        (0..=unit).all(|u| {388            (0..=unit).all(|v| {389                (u % unit == 0 && v % unit == 0)390                    || (0..unit).any(|r| (0..unit).any(|c| pic.at(u + r, v + c) != render.at(r, c)))391            })392        })393    })394}395396fn recognised(design: &Design) -> Option<usize> {397    (1..=3).find(|&m| aligned(design, m))398}399400fn kind(design: &Design) -> String {401    if design.empty() {402        return "sft empty".into();403    }404    if design.full() {405        return "sft full".into();406    }407    if design.degenerate() {408        return "sft boundary".into();409    }410    let pairs: BTreeSet<[u8; 4]> = design.language().0.into_iter().collect();411    if let Some((s, a, b)) = blocks(&pairs) {412        return format!("not-sft blocks {} {}", shape(s, a), shape(s, b));413    }414    if xor(&pairs) {415        return "not-sft xor".into();416    }417    if let Some(direction) = lines(&pairs) {418        return format!("not-sft lines {direction}");419    }420    "open".into()421}422423// CENSUS424425fn orbits(base: usize) -> Vec<(u32, usize)> {426    let mut sizes: BTreeMap<u32, usize> = BTreeMap::new();427    for code in 0..1u32 << (base * base) {428        *sizes429            .entry(Design::new(base, code).canonical())430            .or_insert(0) += 1;431    }432    sizes.into_iter().collect()433}434435fn row(values: &[usize]) -> String {436    values437        .iter()438        .map(|v| v.to_string())439        .collect::<Vec<_>>()440        .join(",")441}442443fn periodic(p: &[usize]) -> Option<usize> {444    p.iter()445        .enumerate()446        .map(|(i, &v)| (i + 1, v))447        .find(|&(k, v)| 2 * v <= k * k)448        .map(|(k, _)| k)449}450451// FORMULAS452453fn carpet(k: i64) -> i64 {454    let mut n = 1i64;455    while 3 * n + 1 < k {456        n *= 3;457    }458    if k <= 2 * n + 1 {459        6 * k * k + 12 * (n - 1) * k - 10 * n * n - 12 * n + 8460    } else {461        2 * k * k + (24 * n - 4) * k - 18 * n * n - 24 * n + 4462    }463}464465fn gasket(k: i64) -> i64 {466    4 * k * k - 6 * k + 4467}468469fn formula(base: usize, code: u32, p: &[usize]) -> Option<String> {470    let rule: fn(i64) -> i64 = match (base, code) {471        (3, 495) => carpet,472        (2, 7) => gasket,473        _ => return None,474    };475    let first = if base == 3 { 2 } else { 1 };476    let miss = (first..=p.len()).find(|&k| rule(k as i64) != p[k - 1] as i64);477    Some(match miss {478        Some(k) => format!("formula fails at {k}"),479        None => format!("formula holds for {first} <= k <= {}", p.len()),480    })481}482483fn quadratic(p: &[usize]) -> Option<String> {484    let v: Vec<i64> = p.iter().map(|&x| x as i64).collect();485    (1..=3usize).find_map(|first| {486        let i = first - 1;487        if v.len() < i + 4 {488            return None;489        }490        let d2 = v[i + 2] - 2 * v[i + 1] + v[i];491        if (i..v.len() - 2).any(|j| v[j + 2] - 2 * v[j + 1] + v[j] != d2) {492            return None;493        }494        let k = first as i64;495        let b2 = 2 * (v[i + 1] - v[i]) - d2 * (2 * k + 1);496        let c2 = 2 * v[i] - d2 * k * k - b2 * k;497        if d2 % 2 != 0 || b2 % 2 != 0 || c2 % 2 != 0 {498            return Some(format!(499                "quadratic from {first} ({d2} k^2 + {b2} k + {c2})/2"500            ));501        }502        Some(format!(503            "quadratic from {first} {} k^2 + {} k + {}",504            d2 / 2,505            b2 / 2,506            c2 / 2507        ))508    })509}510511// VERBS512513fn verb_count(base: usize, top: usize, only: Option<u32>) {514    let codes: Vec<(u32, usize)> = match only {515        Some(code) => vec![(code, 1)],516        None => orbits(base),517    };518    let mut distinct: BTreeSet<Vec<usize>> = BTreeSet::new();519    let mut tally: BTreeMap<&str, (usize, usize)> = BTreeMap::new();520    for (code, size) in codes {521        let design = Design::new(base, code);522        let p = count(&design, top);523        if only.is_none() {524            for g in 1..8 {525                let image = design.image(g);526                assert_eq!(527                    count(&image, top.min(12)),528                    p[..top.min(12)],529                    "code {code} image {g}"530                );531            }532        }533        let (pairs, level) = design.language();534        let (tag, shown) = if design.degenerate() {535            (format!("boundary edge {}", row(&p)), vec![1; p.len()])536        } else {537            ("plane".to_string(), p)538        };539        let flat = match periodic(&shown) {540            Some(k) => format!("periodic@{k}"),541            None => "above".into(),542        };543        println!(544            "base {base} code {code} orbit {size} fill {} pairs {} settle {level} {flat} p {} {tag}",545            design.filled().len(),546            pairs.len(),547            row(&shown)548        );549        let class = if shown.iter().all(|&v| v == 1) {550            "constant"551        } else if periodic(&shown).is_some() {552            "periodic"553        } else {554            "above"555        };556        let entry = tally.entry(class).or_insert((0, 0));557        entry.0 += 1;558        entry.1 += size;559        distinct.insert(shown.clone());560        if let Some(line) = quadratic(&shown) {561            println!("base {base} code {code} {line}");562        }563        if let Some(line) = formula(base, code, &shown) {564            println!("base {base} code {code} {line}");565        }566    }567    if only.is_none() {568        summary(base, &distinct, &tally);569    }570}571572fn summary(base: usize, distinct: &BTreeSet<Vec<usize>>, tally: &BTreeMap<&str, (usize, usize)>) {573    for (class, (orbits, codes)) in tally {574        println!("base {base} class {class} orbits {orbits} codes {codes}");575    }576    println!("base {base} distinct sequences {}", distinct.len());577}578579fn verb_pairs(base: usize, code: u32) {580    let (pairs, level) = Design::new(base, code).language();581    let shown: Vec<String> = pairs582        .iter()583        .map(|w| format!("{}{}/{}{}", w[0], w[1], w[2], w[3]))584        .collect();585    println!(586        "base {base} code {code} settle {level} pairs {} {}",587        pairs.len(),588        shown.join(" ")589    );590}591592fn verb_scan(base: usize, level: usize, top: usize, only: Option<u32>) {593    let carpet = crate_render(&Design::new(3, 495), level.min(6));594    let tile = create(Code::from(7u64), 3, 2, 2, level.min(6)).unwrap();595    assert_eq!(596        carpet.cells,597        tile.bytes().unwrap(),598        "code 7 side 3 is not base 3 code 495"599    );600    let mut worst = 0usize;601    let codes: Vec<(u32, usize)> = match only {602        Some(code) => vec![(code, 1)],603        None => orbits(base),604    };605    for (code, _) in codes {606        let design = Design::new(base, code);607        let pic = crate_render(&design, level);608        assert!(609            pic == design.render(level),610            "code {code} renders differently"611        );612        let seen = brute(&pic, top);613        let p = count(&design, seen.len());614        let gaps = seen.iter().zip(&p).filter(|(a, b)| a != b).count();615        worst = worst.max(gaps);616        println!(617            "base {base} code {code} level {level} scan {} gaps {gaps}",618            row(&seen)619        );620    }621    println!("base {base} level {level} worst {worst}");622}623624fn verb_kind(base: usize) {625    let mut tally: BTreeMap<String, usize> = BTreeMap::new();626    for (code, size) in orbits(base) {627        let design = Design::new(base, code);628        let verdict = match (629            design.empty() || design.full() || design.degenerate(),630            recognised(&design),631        ) {632            (true, _) => kind(&design),633            (false, Some(m)) => format!("{} recognised {m}", kind(&design)),634            (false, None) => format!("{} unrecognised", kind(&design)),635        };636        let head = verdict.split(' ').take(2).collect::<Vec<_>>().join(" ");637        *tally.entry(head).or_insert(0) += size;638        println!("base {base} code {code} orbit {size} {verdict}");639    }640    for (head, total) in tally {641        println!("base {base} codes {total} {head}");642    }643}644645fn main() {646    let args: Vec<String> = env::args().collect();647    let arg =648        |i: usize, default: usize| args.get(i).and_then(|s| s.parse().ok()).unwrap_or(default);649    let clock = Instant::now();650    match args.get(1).map(String::as_str) {651        Some("count") => verb_count(arg(2, 3), arg(3, 28), args.get(4).and_then(|s| s.parse().ok())),652        Some("scan") => verb_scan(arg(2, 3), arg(3, 6), arg(4, 12), args.get(5).and_then(|s| s.parse().ok())),653        Some("kind") => verb_kind(arg(2, 3)),654        Some("pairs") => verb_pairs(arg(2, 3), arg(3, 495) as u32),655        _ => eprintln!("verbs: count <base> <top> [code], scan <base> <level> <top> [code], kind <base>, pairs <base> <code>"),656    }657    eprintln!("seconds {:.1}", clock.elapsed().as_secs_f64());658}