main.rs

11.7 kB · rust · 370 lines

1mod design;2mod figure;3mod gasket;4mod spectral;5mod walkers;67use design::{components, plane, sponge, Graph, BASE};8use mrlycore::{Rng, Tensor};9use std::path::Path;1011const SUBJECTS: [u128; 7] = [79, 95, 127, 239, 255, 495, 511];1213struct Subject {14    name: String,15    fill: usize,16    base: usize,17    df: f64,18    levels: (usize, usize),19    grid: fn(usize) -> Tensor,20    walk_level: usize,21}2223fn base3(code: u128) -> Subject {24    let name = format!("bang dim 2, base 3, code {code}");25    let fill = code.count_ones() as usize;26    Subject {27        name,28        fill,29        base: BASE,30        df: (fill as f64).ln() / (BASE as f64).ln(),31        levels: (4, 5),32        grid: match code {33            79 => |level| plane(79, BASE, level),34            95 => |level| plane(95, BASE, level),35            127 => |level| plane(127, BASE, level),36            239 => |level| plane(239, BASE, level),37            255 => |level| plane(255, BASE, level),38            495 => |level| plane(495, BASE, level),39            _ => |level| plane(511, BASE, level),40        },41        walk_level: 5,42    }43}4445fn subjects() -> Vec<Subject> {46    let mut out: Vec<Subject> = SUBJECTS.iter().map(|code| base3(*code)).collect();47    out.push(Subject {48        name: "bang dim 2, code 7".to_string(),49        fill: 3,50        base: 2,51        df: 3f64.ln() / 2f64.ln(),52        levels: (6, 7),53        grid: |level| plane(7, 2, level),54        walk_level: 8,55    });56    out.push(Subject {57        name: "bang dim 3, base 3, code 23".to_string(),58        fill: 20,59        base: BASE,60        df: 20f64.ln() / 3f64.ln(),61        levels: (3, 4),62        grid: sponge,63        walk_level: 4,64    });65    out66}6768fn giant_graph(grid: &Tensor) -> Graph {69    Graph::of(&components(grid).giant)70}7172fn pair(subject: &Subject, coarse: usize, fine: usize) -> (Vec<f64>, usize) {73    let low = giant_graph(&(subject.grid)(coarse));74    let high = giant_graph(&(subject.grid)(fine));75    let nodes = high.nodes();76    (77        spectral::exponents(78            &spectral::low(&low),79            &spectral::low(&high),80            subject.base as f64,81        ),82        nodes,83    )84}8586fn list(values: &[f64], places: usize) -> String {87    values88        .iter()89        .map(|value| format!("{value:.places$}"))90        .collect::<Vec<_>>()91        .join(" ")92}9394fn anchors() {95    println!("ANCHORS, SPECTRAL");96    let low = spectral::low(&gasket::build(7).graph);97    let high = spectral::low(&gasket::build(8).graph);98    let tau: Vec<f64> = (1..=4).map(|mode| low[mode] / high[mode]).collect();99    println!(100        "gasket tau levels 7 to 8, modes 1 to 4: {} (exact 5)",101        list(&tau, 5)102    );103    println!(104        "gasket d_w from tau: {} (exact {:.6})",105        list(106            &tau.iter().map(|t| t.ln() / 2f64.ln()).collect::<Vec<_>>(),107            5108        ),109        5f64.ln() / 2f64.ln()110    );111    for (code, name, levels) in [(511u128, "solid", (4usize, 5usize)), (7, "path", (5, 6))] {112        let mut spectra = Vec::new();113        for level in [levels.0, levels.1] {114            let graph = Graph::of(&plane(code, BASE, level));115            let side = BASE.pow(level as u32) as f64;116            let values = spectral::low(&graph);117            let exact = 2.0 - 2.0 * (std::f64::consts::PI / side).cos();118            println!(119                "{name} level {level}: nodes {} lambda_2 {:.12} closed form {:.12} gap {:.1e}",120                graph.nodes(),121                values[1],122                exact,123                (values[1] - exact).abs()124            );125            spectra.push(values);126        }127        let est = spectral::exponents(&spectra[0], &spectra[1], BASE as f64);128        println!(129            "{name} d_w levels {} to {} from lambda_2: {:.5}",130            levels.0, levels.1, est[0]131        );132    }133}134135fn census() -> Vec<u128> {136    println!();137    println!("CLASSES AND SPANNING");138    let group = design::group();139    let classes = design::classes(&group);140    let orbit_sum: usize = classes.iter().map(|class| class.1).sum();141    println!(142        "group order {} classes {} orbit sum {orbit_sum}",143        group.len(),144        classes.len()145    );146    let canonical = |code: u128| *design::orbit(&group, code).iter().next().expect("an orbit");147    let mut spanning: Vec<u128> = Vec::new();148    let mut spanning_all: Vec<u128> = Vec::new();149    let mut single: Vec<u128> = Vec::new();150    let mut strays: Vec<(u128, usize, f64)> = Vec::new();151    for code in 1..512u128 {152        let at5 = components(&plane(code, BASE, 5));153        if at5.spanning {154            spanning.push(code);155            if (1..5).all(|level| components(&plane(code, BASE, level)).spanning) {156                spanning_all.push(code);157            }158            if at5.count == 1 {159                single.push(code);160            } else {161                strays.push((code, at5.count, at5.share));162            }163        }164    }165    let reps_of = |codes: &[u128]| {166        let mut reps: Vec<u128> = codes.iter().map(|code| canonical(*code)).collect();167        reps.sort_unstable();168        reps.dedup();169        reps170    };171    let reps = reps_of(&spanning);172    println!(173        "codes whose giant component touches all four walls at level 5: {} of 511, in {} classes with reps {:?}",174        spanning.len(),175        reps.len(),176        reps177    );178    println!(179        "of those, spanning at every level 1 to 5: {}",180        spanning_all.len()181    );182    let strict = reps_of(&single);183    println!(184        "codes that are one component touching all four walls at level 5: {} of 511, in {} classes with reps {:?}",185        single.len(),186        strict.len(),187        strict188    );189    println!("spanning codes carrying stray components at level 5, with component count and giant share:");190    for (code, count, share) in &strays {191        println!(192            "  code {code} class {} components {count} share {share:.4}",193            canonical(*code)194        );195    }196    for rep in &reps {197        let members: Vec<u128> = spanning198            .iter()199            .copied()200            .filter(|code| canonical(*code) == *rep)201            .collect();202        let size = design::orbit(&group, *rep).len();203        println!(204            "  class {rep} fill {} orbit {size} spanning members {:?}",205            rep.count_ones(),206            members207        );208    }209    println!("representatives: giant share at levels 4 and 5, components at 5, spanning at 5");210    for (rep, size) in &classes {211        if *rep == 0 {212            continue;213        }214        let at4 = components(&plane(*rep, BASE, 4));215        let at5 = components(&plane(*rep, BASE, 5));216        println!(217            "  rep {rep:>3} fill {} orbit {size:>2} share {:.4} {:.4} components {:>5} spanning {}",218            rep.count_ones(),219            at4.share,220            at5.share,221            at5.count,222            at5.spanning223        );224    }225    spanning226}227228fn spectral_census(subjects: &[Subject]) -> Vec<f64> {229    println!();230    println!(231        "SPECTRAL CENSUS, d_w FROM LAMBDA_2 AT THE UPPER LEVEL PAIR, DRIFT FROM THE PAIR BELOW"232    );233    let mut out = Vec::new();234    for subject in subjects {235        let (coarse, fine) = subject.levels;236        let (below, _) = pair(subject, coarse - 1, coarse);237        let (step, nodes) = pair(subject, coarse, fine);238        let dw = step[0];239        let kmin = step.iter().copied().fold(f64::INFINITY, f64::min);240        let kmax = step.iter().copied().fold(f64::NEG_INFINITY, f64::max);241        println!(242            "{:>16} fill {:>2} d_f {:.4} nodes {nodes:>6} levels {coarse} to {fine} modes [{}] below [{}] d_w {dw:.4} spread {:.4} drift {:.4} d_s {:.4}",243            subject.name,244            subject.fill,245            subject.df,246            list(&step, 4),247            list(&below, 4),248            kmax - kmin,249            (dw - below[0]).abs(),250            2.0 * subject.df / dw251        );252        out.push(dw);253    }254    out255}256257fn anomaly() {258    println!();259    println!("CODE 127, THE SLOW MODE AT LEVEL 4");260    let grid = plane(127, BASE, 4);261    let graph = giant_graph(&grid);262    let mode = spectral::slow_mode(&graph);263    let block = 27usize;264    let mut inside = 0.0;265    let mut total = 0.0;266    let (mut row_sum, mut col_sum) = (0.0, 0.0);267    for (node, flat) in graph.cells.iter().enumerate() {268        let at = design::coords(*flat, &graph.shape);269        let weight = mode[node] * mode[node];270        total += weight;271        row_sum += weight * at[0] as f64;272        col_sum += weight * at[1] as f64;273        if at[0] >= 2 * block && at[1] < block {274            inside += weight;275        }276    }277    println!(278        "weight of mode 1 inside the block under tile cell (2,0), rows 54 to 80 cols 0 to 26: {:.4}; centroid row {:.1} col {:.1} of 81",279        inside / total,280        row_sum / total,281        col_sum / total282    );283}284285fn walker_census(subjects: &[Subject]) -> Vec<f64> {286    println!();287    println!(288        "ANCHORS, WALKERS, {} BLIND ANTS, SEED {}",289        walkers::WALKERS,290        walkers::SEED291    );292    let mut rng = Rng::new(walkers::SEED);293    let solid = plane(511, BASE, 5);294    let side = BASE.pow(5) as f64;295    let trace = walkers::grid_walk(&solid, &mut rng, walkers::WALKERS, 5.0);296    let (dw, drift) = walkers::fit(&trace, (side / 6.0).powi(2));297    println!("solid level 5 d_w {dw:.4} drift {drift:.4} (exact 2)");298    let path = plane(7, BASE, 6);299    let side = BASE.pow(6) as f64;300    let trace = walkers::grid_walk(&path, &mut rng, walkers::WALKERS, 6.0);301    let (dw, drift) = walkers::fit(&trace, (side / 6.0).powi(2));302    println!("path level 6 d_w {dw:.4} drift {drift:.4} (exact 2)");303    let gasket = gasket::build(8);304    let trace = walkers::gasket_walk(&gasket, &mut rng, walkers::WALKERS);305    let (dw, drift) = walkers::fit(&trace, (256.0f64 / 6.0).powi(2));306    let exact = 5f64.ln() / 2f64.ln();307    println!(308        "gasket level 8 d_w {dw:.4} drift {drift:.4} (exact {exact:.4}, off by {:.2}%)",309        (dw - exact).abs() / exact * 100.0310    );311    println!();312    println!("WALKER CENSUS, d_w FROM THE DISPLACEMENT FIT, DRIFT BETWEEN THE WINDOW HALVES");313    let mut out = Vec::new();314    for subject in subjects {315        let giant = components(&(subject.grid)(subject.walk_level)).giant;316        let nodes = giant.bytes().iter().filter(|cell| **cell != 0).count();317        let narrow = giant.shape.iter().min().copied().unwrap_or(0) as f64;318        let trace = walkers::grid_walk(&giant, &mut rng, walkers::WALKERS, 5.0);319        let (dw, drift) = walkers::fit(&trace, (narrow / 6.0).powi(2));320        println!(321            "{:>16} level {} nodes {nodes:>6} d_w {dw:.4} drift {drift:.4} d_s {:.4}",322            subject.name,323            subject.walk_level,324            2.0 * subject.df / dw325        );326        out.push(dw);327    }328    out329}330331fn main() {332    let subjects = subjects();333    anchors();334    census();335    let spectral = spectral_census(&subjects);336    anomaly();337    let walker = walker_census(&subjects);338    println!();339    println!("CENSUS TABLE: design, fill, d_f, d_w spectral, d_w walkers, d_s from the walkers");340    for (index, subject) in subjects.iter().enumerate() {341        println!(342            "{:>16} {:>2} {:.4} {:.3} {:.3} {:.2}",343            subject.name,344            subject.fill,345            subject.df,346            spectral[index],347            walker[index],348            2.0 * subject.df / walker[index]349        );350    }351    println!(352        "d_w at or above 2 on every subject: {}",353        spectral.iter().chain(&walker).all(|dw| *dw >= 2.0 - 0.01)354    );355    let here = Path::new(env!("CARGO_MANIFEST_DIR"));356    let figure = here357        .join("..")358        .join("..")359        .join("figures")360        .join("walks-fig.png");361    figure::write(362        &figure,363        &figure::Series {364            spectral,365            walker,366            fractal: subjects.iter().map(|subject| subject.df).collect(),367        },368    );369    println!("wrote figures/walks-fig.png");370}