census.rs

10.9 kB · rust · 305 lines

1use crate::count::series;2use crate::design::Design;3use crate::fit::{fit, roots, show};4use std::collections::BTreeMap;56pub fn klein(code: u128, base: usize) -> u128 {7    let d = Design::full(code, base);8    (1..4).map(|g| d.image(g).full_code()).fold(code, u128::min)9}1011pub fn describe(s: &[u128]) -> String {12    let best = (0..s.len().min(8))13        .filter_map(|k| fit(&s[k..]).map(|f| (k, f)))14        .filter(|(_, f)| f.margin >= 2 && f.poly.iter().all(|&c| c.abs() < 1 << 40))15        .min_by_key(|(k, f)| (f.poly.len(), *k));16    match best.map(|(k, f)| (k, Some(f))).unwrap_or((0, None)) {17        (_, None) => "no integer recurrence in range".to_string(),18        (k, Some(f)) => {19            let (found, rest) = roots(&f.poly);20            let tail = if rest.len() > 1 {21                format!(", factor with no integer root {}", show(&rest))22            } else {23                String::new()24            };25            let list: Vec<String> = found.iter().map(|r| r.to_string()).collect();26            format!(27                "from level {k}, order {}, margin {}, roots [{}]{tail}",28                f.poly.len() - 1,29                f.margin,30                list.join(" ")31            )32        }33    }34}3536pub fn census(base: usize, top: usize, deep: usize, codes: &[u128]) {37    let mut classes: BTreeMap<u128, Vec<u128>> = BTreeMap::new();38    for &code in codes {39        classes.entry(klein(code, base)).or_default().push(code);40    }41    let mut groups: BTreeMap<Vec<u128>, Vec<u128>> = BTreeMap::new();42    for &rep in classes.keys() {43        let s = series(base, &Design::full(rep, base).tile, top);44        groups.entry(s).or_default().push(rep);45    }46    let zero = groups47        .iter()48        .filter(|(s, _)| s.iter().all(|&x| x == 0))49        .map(|(_, r)| r.len())50        .sum::<usize>();51    let silent = groups52        .iter()53        .filter(|(s, _)| s.iter().all(|&x| x == 0))54        .flat_map(|(_, r)| r.iter().map(|c| classes[c].len()))55        .sum::<usize>();56    println!("BASE {base}: {} codes, {} Klein classes, {} nonzero loop sequences, {} classes and {} codes never loop, levels 0..{top}", codes.len(), classes.len(), groups.len() - usize::from(zero > 0), zero, silent);57    let mut rows: Vec<(&Vec<u128>, &Vec<u128>)> = groups58        .iter()59        .filter(|(s, _)| s.iter().any(|&x| x > 0))60        .collect();61    rows.sort_by_key(|(s, _)| (*s).clone());62    let mut tally = [0usize; 3];63    let mut known = 0;64    for (s, reps) in rows {65        let shown: Vec<String> = s.iter().take(9).map(|x| x.to_string()).collect();66        let names: Vec<String> = reps67            .iter()68            .map(|&c| {69                let d = Design::full(c, base);70                match d.parity_code() {71                    Some(p) if base > 2 => format!("{c} (parity {p})"),72                    _ => c.to_string(),73                }74            })75            .collect();76        let said = describe(s);77        tally[usize::from(said.contains("no integer root"))78            + 2 * usize::from(said.starts_with("no"))] += 1;79        let size: usize = reps.iter().map(|r| classes[r].len()).sum();80        let found = oeis(s);81        known += usize::from(found.starts_with("OEIS A"));82        println!(83            "  {} | {} | codes {} | {size} codes | {found}",84            shown.join(" "),85            said,86            names.join(" ")87        );88    }89    println!("  fits of margin at least 2: {} with integer roots only, {} with a factor of no integer root, {} with no fit", tally[0], tally[1], tally[2]);90    println!(91        "  in the OEIS by the first seven terms from the first nonzero one: {known} of {}",92        tally.iter().sum::<usize>()93    );94    let open: Vec<u128> = groups95        .iter()96        .filter(|(s, _)| describe(s).starts_with("no"))97        .map(|(_, r)| r[0])98        .collect();99    if deep > top && !open.is_empty() {100        census_deep(base, deep, &open);101    }102}103104fn census_deep(base: usize, top: usize, codes: &[u128]) {105    println!("  the sequences with no fit, rerun to level {top}");106    for &code in codes {107        let d = Design::full(code, base);108        let s = series(base, &d.tile, top);109        let shown: Vec<String> = s.iter().map(|x| x.to_string()).collect();110        println!(111            "    {} | kept {} | {} | {}",112            d.name(),113            d.kept(),114            shown.join(" "),115            describe(&s)116        );117    }118}119120pub fn bases() {121    for (base, top) in [(4usize, 12usize), (5, 10)] {122        let mut codes: Vec<u128> = (1..16u128)123            .map(|p| Design::new(p, base, 2).full_code())124            .collect();125        let all = (1u128 << (base * base)) - 1;126        codes.extend((0..base * base).map(|c| all ^ (1 << c)));127        let mut reps: Vec<u128> = codes.iter().map(|&c| klein(c, base)).collect();128        reps.sort();129        reps.dedup();130        let mut distinct: BTreeMap<Vec<u128>, bool> = BTreeMap::new();131        println!("BASE {base}: the fifteen parity codes and every one-cell deletion, {} Klein classes, levels 0..{top}", reps.len());132        for rep in reps {133            let d = Design::full(rep, base);134            let s = series(base, &d.tile, top);135            if s.iter().all(|&x| x == 0) {136                println!("  {} | kept {} | no loop", d.name(), d.kept());137                continue;138            }139            let shown: Vec<String> = s.iter().take(8).map(|x| x.to_string()).collect();140            let found = oeis(&s);141            distinct.insert(s.clone(), found.starts_with("OEIS A"));142            println!(143                "  {} | kept {} | {} | {} | {found}",144                d.name(),145                d.kept(),146                shown.join(" "),147                describe(&s)148            );149        }150        let known = distinct.values().filter(|&&hit| hit).count();151        println!("  {} distinct nonzero loop sequences, in the OEIS by the first seven terms from the first nonzero one: {known} of {}", distinct.len(), distinct.len());152    }153}154155pub fn lengths(base: usize, top: usize, codes: &[u128]) {156    for &code in codes {157        let d = Design::full(code, base);158        println!(159            "NEW LOOPS BY LENGTH in block strands, void strands counted, {}",160            d.name()161        );162        let mut kept = crate::count::unit(true);163        let mut longest = 0;164        for level in 0..top {165            kept = crate::count::glue(base, &d.tile, &kept, level + 1 < top);166            let row: Vec<String> = kept167                .lengths168                .iter()169                .map(|(l, c)| format!("{l}:{c}"))170                .collect();171            longest = longest.max(kept.lengths.keys().last().copied().unwrap_or(0));172            println!("  gluing {level} to {}: {}", level + 1, row.join(" "));173        }174        println!(175            "  longest new loop over gluings 0 to {}: {longest} strands",176            top - 1177        );178    }179}180181fn fibonacci(n: i64) -> i128 {182    let (mut a, mut b) = (1i128, 0i128);183    for _ in 0..n + 1 {184        (a, b) = (b, a + b);185    }186    a187}188189fn gain_law(code: u128, number: usize, rule: usize, n: i64) -> Option<i128> {190    let p = |b: i128, e: i64| b.pow(e as u32);191    let first = |v: i128, f: i128| Some(if n == 0 { v } else { f });192    match (code, number, rule) {193        (7, 2, 2) => first(0, p(2, n) - 2),194        (11, 2, 2) => first(0, if n > 0 { p(2, n - 1) } else { 0 }),195        (9, 2, 2) => Some(1),196        (7, 3, 2) => Some(5 * p(3, n) - 7 * n as i128 - 5),197        (14, 3, 2) => first(1, if n > 0 { 2 * p(3, n - 1) } else { 0 }),198        (9, 3, 2) => Some(2 * p(3, n)),199        (6, 3, 2) => Some(p(2, n + 1)),200        (11, 3, 2) | (13, 3, 2) => Some(p(2, n + 1) - 2),201        (13, 3, 3) => first(0, fibonacci(2 * n - 3)),202        _ => None,203    }204}205206fn loop_law(code: u128, number: usize, rule: usize, n: i64) -> Option<i128> {207    let p = |b: i128, e: i64| b.pow(e as u32);208    let first = |f: &dyn Fn() -> i128| Some(if n == 0 { 0 } else { f() });209    match (code, number, rule) {210        (7, 2, 2) => first(&|| p(3, n - 1) - p(2, n) + 1),211        (11, 2, 2) => first(&|| p(3, n - 1) - p(2, n - 1)),212        (9, 2, 2) => Some(p(2, n) - 1),213        (7, 3, 2) => Some((p(8, n) - 1) / 7 - p(3, n) + n as i128 + 1),214        (14, 3, 2) => first(&|| 2 * p(5, n - 1) - p(3, n - 1)),215        (9, 3, 2) => Some(p(5, n) - p(3, n)),216        (6, 3, 2) => Some(p(4, n) - p(2, n)),217        (11, 3, 2) | (13, 3, 2) => Some((p(7, n) - 6 * p(2, n) + 5) / 15),218        _ => None,219    }220}221222pub fn gains() {223    println!(224        "NAMED DESIGNS: loops L by level and new loops J = L(level + 1) - kept L(level) per gluing"225    );226    let named = [227        (7u128, 2usize, 2usize),228        (11, 2, 2),229        (9, 2, 2),230        (7, 3, 2),231        (14, 3, 2),232        (9, 3, 2),233        (6, 3, 2),234        (11, 3, 2),235        (13, 3, 2),236        (13, 3, 3),237        (287, 3, 3),238    ];239    for (code, number, rule) in named {240        let d = Design::new(code, number, rule);241        let top = if number == 2 { 20 } else { 14 };242        let s = series(number, &d.tile, top);243        let k = d.kept() as i128;244        let j: Vec<i128> = (0..top)245            .map(|n| s[n + 1] as i128 - k * s[n] as i128)246            .collect();247        let gain: Vec<String> = j.iter().map(|x| x.to_string()).collect();248        let shown: Vec<String> = s.iter().take(12).map(|x| x.to_string()).collect();249        println!(250            "  {} | kept {k}\n    L {}\n    J {}\n    {} | {}",251            d.name(),252            shown.join(" "),253            gain.join(" "),254            describe(&s),255            oeis(&s)256        );257        if gain_law(code, number, rule, 0).is_some() {258            let held = (0..top)259                .filter(|&n| gain_law(code, number, rule, n as i64) == Some(j[n]))260                .count();261            println!(262                "    stated J law holds at {held} of {top} gluings 0 to {}",263                top - 1264            );265        }266        if loop_law(code, number, rule, 0).is_some() {267            let held = (0..=top)268                .filter(|&n| loop_law(code, number, rule, n as i64) == Some(s[n] as i128))269                .count();270            println!(271                "    stated L law holds at {held} of {} levels 0 to {top}",272                top + 1273            );274        }275    }276}277278static DUMP: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();279280pub fn oeis(s: &[u128]) -> String {281    let dump = DUMP.get_or_init(|| {282        std::env::var("OEIS_STRIPPED")283            .ok()284            .and_then(|p| std::fs::read_to_string(p).ok())285    });286    let Some(dump) = dump else {287        return "OEIS not read".to_string();288    };289    let Some(k) = s.iter().position(|&x| x > 0) else {290        return String::new();291    };292    let window: Vec<String> = s[k..].iter().take(7).map(|x| x.to_string()).collect();293    let key = format!(",{},", window.join(","));294    let hits: Vec<&str> = dump295        .lines()296        .filter(|l| l.contains(&key))297        .filter_map(|l| l.split(' ').next())298        .take(3)299        .collect();300    if hits.is_empty() {301        "OEIS absent".to_string()302    } else {303        format!("OEIS {}", hits.join(" "))304    }305}