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}