star.rs
23.2 kB · rust · 620 lines
1use crate::lattice::{Family, Rule, FAMILIES};2use crate::sums::{mean, odds};3use mrlymath::six::{geometry::cut, FILL, GRID};4use mrlynum::series::{beta, dirichlet, CATALAN, EULER};5use num_rational::Ratio;67pub struct Slice {8 pub rows: usize,9 pub cols: usize,10 pub types: Vec<u8>,11}1213pub fn slice(family: Family, number: usize) -> Slice {14 let hex = cut(&family.cube(number)).expect("a cut cube");15 let (rows, cols) = (hex.height(), hex.width());16 let types = (0..rows * cols)17 .map(|at| hex.cell.types().at(at) as u8)18 .collect();19 Slice { rows, cols, types }20}2122impl Slice {23 pub fn ink(&self) -> Ratio<i64> {24 let filled = self.types.iter().filter(|cell| **cell == FILL).count();25 let inside = self.types.iter().filter(|cell| **cell != GRID).count();26 Ratio::new(filled as i64, inside as i64)27 }2829 fn coordinates(&self, number: usize) -> Vec<[f64; 3]> {30 let (n, cols) = (number as i64, self.cols as i64);31 let mut out = vec![[0.0; 3]; self.rows * self.cols];32 for row in 0..self.rows as i64 {33 let z = 2 * row;34 let target = 6 * n - 2 - z;35 let low = 0.max(target - (4 * n - 1));36 let reach = (4 * n - 1).min(target) - low + 1;37 let offset = (cols - reach) / 2;38 for step in 0..reach {39 let x = low + step;40 let scale = 4.0 * n as f64;41 out[(row * cols + offset + step) as usize] = [42 x as f64 / scale,43 (target - x) as f64 / scale,44 z as f64 / scale,45 ];46 }47 }48 out49 }50}5152pub fn law(family: Family, number: usize) -> Ratio<i64> {53 let n = number as i64;54 let chi = if (3 * n - 1) / 2 % 2 == 0 { 1 } else { -1 };55 let r = |p: i64, q: i64| Ratio::new(p, q);56 let carpet = r(1, 2) + r(chi, 8) + r(1, 2 * n) - r(chi, 8 * n * n);57 match family {58 Family::Carpet => carpet,59 Family::Net => r(1, 1) - carpet,60 Family::Tree => r(1, 4) + (r(1, 3) - r(chi, 12)) / r(n, 1) + r(1 - chi, 6 * n * n),61 Family::Void => r(1, 4) - r(chi, 4 * n) + r(1, 2 * n * n),62 }63}6465pub fn ink_laws(limit: usize) {66 println!("cut ink of every odd n <= {limit} against the closed forms, exact rationals");67 for family in FAMILIES {68 let mut matched = 0;69 let mut centre = 0;70 let mut layers = 0;71 for number in odds(limit) {72 let hex = slice(family, number);73 matched += usize::from(hex.ink() == law(family, number));74 centre += usize::from(hex.types[number * hex.cols + 2 * number - 1] == FILL);75 layers += 1;76 }77 println!(78 " {}: {matched}/{layers} layers match, centre cell ink in {centre}/{layers}",79 family.name()80 );81 }82}8384const HEIGHT: usize = 1200;85const WIDTH: usize = 2399;86const DEEPEST: usize = 111;8788fn sample(count: usize, extent: usize) -> Vec<usize> {89 (0..count)90 .map(|slot| ((slot as f64 + 0.5) / count as f64 * extent as f64).floor() as usize)91 .collect()92}9394fn raster<T: Copy>(hex: &Slice, cells: &[T]) -> Vec<T> {95 let lines = sample(HEIGHT, hex.rows);96 let slots = sample(WIDTH, hex.cols);97 let mut out = Vec::with_capacity(HEIGHT * WIDTH);98 for line in &lines {99 for slot in &slots {100 out.push(cells[line * hex.cols + slot]);101 }102 }103 out104}105106fn masked(values: &[f64], keep: &[bool]) -> f64 {107 let kept: Vec<f64> = values108 .iter()109 .zip(keep)110 .filter(|(_, inside)| **inside)111 .map(|(value, _)| *value)112 .collect();113 mean(&kept)114}115116pub fn ghost() {117 println!("carpet cut layers rendered on a {HEIGHT} by {WIDTH} raster, odd n <= {DEEPEST}");118 let layers: Vec<(Vec<bool>, Vec<bool>)> = odds(DEEPEST)119 .map(|number| {120 let hex = slice(Family::Carpet, number);121 let fill = raster(122 &hex,123 &hex.types.iter().map(|c| *c == FILL).collect::<Vec<bool>>(),124 );125 let inside = raster(126 &hex,127 &hex.types.iter().map(|c| *c != GRID).collect::<Vec<bool>>(),128 );129 (fill, inside)130 })131 .collect();132 let mut hexagon = vec![true; HEIGHT * WIDTH];133 for (_, inside) in &layers {134 for (keep, ok) in hexagon.iter_mut().zip(inside) {135 *keep &= ok;136 }137 }138 let deepest = slice(Family::Carpet, DEEPEST);139 let gaps = raster(&deepest, &deepest.coordinates(DEEPEST));140 let near = |cell: usize, width: f64| {141 let [x, y, z] = gaps[cell];142 (x - y).abs() < width || (y - z).abs() < width || (z - x).abs() < width143 };144 let star: Vec<bool> = (0..HEIGHT * WIDTH)145 .map(|cell| hexagon[cell] && near(cell, 0.004))146 .collect();147 let background: Vec<bool> = (0..HEIGHT * WIDTH)148 .map(|cell| hexagon[cell] && !near(cell, 0.02))149 .collect();150 println!(151 " pixels: hexagon {} star {} background {}",152 hexagon.iter().filter(|k| **k).count(),153 star.iter().filter(|k| **k).count(),154 background.iter().filter(|k| **k).count()155 );156 let mut total = vec![0.0f64; HEIGHT * WIDTH];157 for (count, (fill, _)) in layers.iter().enumerate() {158 for (slot, value) in total.iter_mut().zip(fill) {159 *slot += f64::from(*value);160 }161 let stacked = count + 1;162 if [5, 28, 56].contains(&stacked) {163 let field: Vec<f64> = total.iter().map(|value| value / stacked as f64).collect();164 let (star_ink, background_ink) = (masked(&field, &star), masked(&field, &background));165 println!(166 " layers {stacked:2}: star {star_ink:.5} background {background_ink:.5} star minus background {:+.5}",167 star_ink - background_ink168 );169 }170 }171}172173pub fn fading_lattice(rule: &Rule) {174 println!("carpet lattice frame, band |x-y| <= 0.01 of the cube side, per-layer excess over the exact ink law");175 let counts = [14usize, 28, 56, 100, 200, 400];176 let mut excesses = Vec::new();177 for number in odds(2 * counts[counts.len() - 1] - 1) {178 let n = number as i64;179 let size = 4 * n;180 let step = 1.0 / size as f64;181 let reach = 2.max((0.01 * size as f64).ceil() as i64 + 2);182 let (mut total, mut inked) = (0.0f64, 0.0f64);183 for index in 0..2 * n {184 let z = 2 * index;185 let target = 6 * n - 2 - z;186 for offset in -reach..=reach {187 let x = target.div_euclid(2) + offset;188 let y = target - x;189 if !(0..size).contains(&x) || !(0..size).contains(&y) {190 continue;191 }192 let d = (x - y) as f64 * step;193 let weight = ((d + step).min(0.01) - (d - step).max(-0.01)).max(0.0);194 total += weight;195 inked += weight * f64::from(rule.filled(x, y, z));196 }197 }198 let background = law(Family::Carpet, number);199 let background = *background.numer() as f64 / *background.denom() as f64;200 excesses.push(if total > 0.0 {201 inked / total - background202 } else {203 0.0204 });205 }206 let mut scaled = Vec::new();207 for count in counts {208 let excess = mean(&excesses[..count]);209 println!(210 " L = {count:3}: excess {excess:+.6} excess * L {:+.4}",211 excess * count as f64212 );213 scaled.push(excess * count as f64);214 }215 println!(216 " slope of excess * L against ln L: {:+.4} (100 to 200) {:+.4} (200 to 400)",217 (scaled[4] - scaled[3]) / 2f64.ln(),218 (scaled[5] - scaled[4]) / 2f64.ln()219 );220}221222fn law_value(number: usize) -> f64 {223 let value = law(Family::Carpet, number);224 *value.numer() as f64 / *value.denom() as f64225}226227fn arm_ink(rule: &Rule, n: i64, half: i64) -> f64 {228 let size = 4 * n;229 let (mut total, mut inked) = (0i64, 0i64);230 for index in 0..2 * n {231 let z = 2 * index;232 let target = 6 * n - 2 - z;233 let base = target / 2;234 for x in base - half - 1..=base + half + 1 {235 let y = target - x;236 if !(0..size).contains(&x) || !(0..size).contains(&y) || (x - y).abs() > half {237 continue;238 }239 total += 1;240 inked += i64::from(rule.filled(x, y, z));241 }242 }243 inked as f64 / total as f64244}245246fn arm_count(rule: &Rule, n: i64, axis: usize) -> Ratio<i64> {247 let size = 4 * n;248 let (mut total, mut inked) = (0i64, 0i64);249 for step in 0..size {250 let (x, y, z) = match axis {251 0 => (step, step, 6 * n - 2 - 2 * step),252 1 => (6 * n - 2 - 2 * step, step, step),253 _ => (step, 6 * n - 2 - 2 * step, step),254 };255 if !(0..size).contains(&x)256 || !(0..size).contains(&y)257 || !(0..size).contains(&z)258 || z % 2 != 0259 {260 continue;261 }262 total += 1;263 inked += i64::from(rule.filled(x, y, z));264 }265 Ratio::new(inked, total)266}267268fn arm_law(n: i64) -> Ratio<i64> {269 let rhythm = [0, 1, 0, -1, 0, -1, 0, 1][(n % 8) as usize];270 Ratio::new(1, 2) + Ratio::new(rhythm, 2 * n)271}272273fn nearest(value: f64) -> (i64, i64) {274 let mut best = (0i64, 1i64, f64::INFINITY);275 for denominator in 1..=400i64 {276 let numerator = (value * denominator as f64).round() as i64;277 let gap = (value - numerator as f64 / denominator as f64).abs();278 if gap < best.2 - 1e-12 {279 best = (numerator, denominator, gap);280 }281 }282 (best.0, best.1)283}284285const ARM_LIMIT: usize = 6400;286const LAW_LIMIT: usize = 2001;287const ARMS_LIMIT: usize = 221;288const WIDTH_LIMIT: usize = 800;289const HELD_LIMIT: usize = 1600;290const CHECK_LIMIT: usize = 3200;291292pub fn cell_frame(rule: &Rule) {293 println!(294 "carpet cell frame: star the exact arm x = y of the cut, background the exact ink law"295 );296 let (mut matched, mut layers) = (0, 0);297 for number in odds(LAW_LIMIT) {298 matched += usize::from(arm_count(rule, number as i64, 0) == arm_law(number as i64));299 layers += 1;300 }301 let (mut agreed, mut triples) = (0, 0);302 for number in odds(ARMS_LIMIT) {303 let n = number as i64;304 let inks = [305 arm_count(rule, n, 0),306 arm_count(rule, n, 1),307 arm_count(rule, n, 2),308 ];309 agreed += usize::from(inks[0] == inks[1] && inks[1] == inks[2]);310 triples += 1;311 }312 println!(313 " arm ink 1/2 + chi_8(n)/(2n), chi_8 the mod-8 character of Q(sqrt 2), exact rationals: {matched}/{layers} layers match at odd n <= {LAW_LIMIT}, three arms agree in {agreed}/{triples} at odd n <= {ARMS_LIMIT}"314 );315 let excesses: Vec<f64> = odds(2 * ARM_LIMIT - 1)316 .map(|number| arm_ink(rule, number as i64, 0) - law_value(number))317 .collect();318 let constant = (1.0 + 2f64.sqrt()).ln() / (2.0 * 2f64.sqrt())319 - CATALAN / 8.0320 - EULER / 4.0321 - 2f64.ln() / 2.0;322 let mut scaled = Vec::new();323 let mut residuals = Vec::new();324 println!(325 " L = 0 mod 4: the -chi/8 term sums to 0, residual is excess * L + (ln L)/4 - C, against -23/192 = {:+.8}",326 -23.0 / 192.0327 );328 for count in [28usize, 56, 100, 200, 400, 800, 1600, 3200, 6400] {329 let excess = mean(&excesses[..count]);330 let scale = excess * count as f64;331 let logged = scale + (count as f64).ln() / 4.0;332 println!(333 " L = {count:4}: excess {excess:+.8} excess * L {scale:+.6} excess * L + (ln L)/4 {logged:+.10} residual * L^2 {:+.8}",334 (logged - constant) * (count as f64) * (count as f64)335 );336 scaled.push(scale);337 residuals.push(logged - constant);338 }339 print!(" residual ratio per doubling from L = 100:");340 for step in 2..residuals.len() - 1 {341 print!(" {:.2}", residuals[step] / residuals[step + 1]);342 }343 println!();344 println!(345 " L = 2 mod 4: the -chi/8 term still sums to 0 and the character tail changes sign, against +25/192 = {:+.8}",346 25.0 / 192.0347 );348 for count in [102usize, 202, 402, 802, 1602, 3202] {349 let excess = mean(&excesses[..count]);350 let logged = excess * count as f64 + (count as f64).ln() / 4.0;351 println!(352 " L = {count:4}: excess * L + (ln L)/4 {logged:+.10} residual * L^2 {:+.8}",353 (logged - constant) * (count as f64) * (count as f64)354 );355 }356 println!(357 " L odd: the -chi/8 term sums to +1/8, so the constant is C + 1/8 and the error is O(1/L)"358 );359 for count in [27usize, 99, 401, 1601, 6399] {360 let excess = mean(&excesses[..count]);361 let logged = excess * count as f64 + (count as f64).ln() / 4.0;362 println!(363 " L = {count:4}: excess * L + (ln L)/4 - C {:+.6} less 1/8 {:+.8} times L {:+.6}",364 logged - constant,365 logged - constant - 0.125,366 (logged - constant - 0.125) * count as f64367 );368 }369 println!(370 " slope of excess * L against ln L: {:+.6} (1600 to 3200) {:+.6} (3200 to 6400)",371 (scaled[7] - scaled[6]) / 2f64.ln(),372 (scaled[8] - scaled[7]) / 2f64.ln()373 );374 println!(375 " constant ln(1+sqrt2)/(2 sqrt2) - G/8 - gamma/4 - (ln2)/2 = {constant:.10} G from its own series {:.10} L(1, chi_8) series {:.8} against ln(1+sqrt2)/sqrt2 {:.8}",376 beta(2.0, 2_000_000),377 dirichlet(1.0, &[0, 1, 0, -1, 0, -1, 0, 1], 20_000_000),378 (1.0 + 2f64.sqrt()).ln() / 2f64.sqrt()379 );380}381382// WIDTH FAMILY383384struct Width {385 k: i64,386 b: i64,387 kappa: Ratio<i64>,388 m: Ratio<i64>,389 q: Ratio<i64>,390}391392fn edge_run(k: i64) -> i64 {393 let hits = (-k..=k)394 .filter(|j| matches!(j.rem_euclid(8), 3 | 4 | 5))395 .count() as i64;396 hits + (k + 2).div_euclid(4) - k397}398399fn width_terms(half: i64) -> Width {400 let k = half.div_euclid(2);401 let b = i64::from(k.div_euclid(2) % 2 == 0);402 let run = edge_run(k);403 Width {404 k,405 b,406 kappa: Ratio::new(if k % 2 == 0 { -1 } else { 1 }, 8 * (2 * k + 1)),407 m: Ratio::new(-(k + b), 2 * (2 * k + 1)),408 q: Ratio::new(1 - 2 * run, 2 * (2 * k + 1)),409 }410}411412fn band_count(rule: &Rule, n: i64, half: i64) -> Ratio<i64> {413 let size = 4 * n;414 let (mut total, mut inked) = (0i64, 0i64);415 for index in 0..2 * n {416 let z = 2 * index;417 let target = 6 * n - 2 - z;418 let base = target / 2;419 for x in base - half - 1..=base + half + 1 {420 let y = target - x;421 if !(0..size).contains(&x) || !(0..size).contains(&y) || (x - y).abs() > half {422 continue;423 }424 total += 1;425 inked += i64::from(rule.filled(x, y, z));426 }427 }428 Ratio::new(inked, total)429}430431fn width_excess_law(half: i64, n: i64) -> Ratio<i64> {432 let w = width_terms(half);433 let chi = if (3 * n - 1) / 2 % 2 == 0 { 1 } else { -1 };434 let eight = [0i64, 1, 0, -1, 0, -1, 0, 1][(n % 8) as usize];435 w.kappa * chi + (w.m + w.q * eight) / n + Ratio::new(chi, 8 * n * n)436}437438fn flat(value: Ratio<i64>) -> f64 {439 *value.numer() as f64 / *value.denom() as f64440}441442const IDENTITY_LIMIT: usize = 201;443const LADDER_LIMIT: usize = 1602;444445pub fn cell_width_law(rule: &Rule) {446 println!("carpet cell frame, one closed form for every band half-width: excess_W(n) = kappa chi + (m + q chi_8(n))/n + chi/(8 n^2), exact rationals against the counted band");447 println!(" K = W/2 since x - y is even on the cut, b = 1 when floor(K/2) is even, and E(K) = #(|j| <= K, j = 3,4,5 mod 8) + floor((K+2)/4) - K is the tail's chi_8 weight, derived and not fitted");448 let periodic = (0..=200i64)449 .filter(|k| edge_run(*k) == [0i64, -1, -1, 0, 1, 2, 2, 1][(k % 8) as usize])450 .count();451 println!(" E(K) against the 8-periodic run 0 -1 -1 0 1 2 2 1, which a block of eight leaves alone since 6 + 2 - 8 = 0: {periodic}/201 at K = 0..200");452 println!(" the match is broken out by n mod 8 so no residue class hides; odd W repeats its even neighbour, so only the first W at each K is counted in the total");453 let (mut seen, mut kept, mut total) = (Vec::new(), 0usize, 0usize);454 for half in [455 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20, 24, 32, 36, 64,456 ] {457 let w = width_terms(half);458 let fresh = !seen.contains(&w.k);459 if fresh {460 seen.push(w.k);461 }462 let (mut hit, mut all) = ([0usize; 4], [0usize; 4]);463 for number in odds(IDENTITY_LIMIT) {464 let n = number as i64;465 if n < w.k {466 continue;467 }468 let slot = ((n % 8) / 2) as usize;469 all[slot] += 1;470 hit[slot] += usize::from(471 band_count(rule, n, half) - law(Family::Carpet, number)472 == width_excess_law(half, n),473 );474 }475 if fresh {476 kept += hit.iter().sum::<usize>();477 total += all.iter().sum::<usize>();478 }479 println!(480 " W = {half:2}: K {:2} b {} kappa {} m {} q {} slope m/2 {} = {:+.8} n = 1,3,5,7 mod 8 match {}/{} {}/{} {}/{} {}/{}",481 w.k,482 w.b,483 w.kappa,484 w.m,485 w.q,486 w.m / 2,487 flat(w.m) / 2.0,488 hit[0], all[0], hit[1], all[1], hit[2], all[2], hit[3], all[3]489 );490 }491 println!(" {} distinct half-widths, {kept}/{total} distinct checks; the nine odd rows above repeat their even neighbours and are not counted twice", seen.len());492}493494pub fn cell_width_ladders(rule: &Rule) {495 println!("carpet cell frame, the width family summed: L * excess_L = (m/2) ln L + C_W + O(1/L^2) at even L, C_W = m (ln 2 + gamma/2) + q L(1, chi_8) - G/8 + Delta_W");496 println!(" Delta_W is the exact contribution of the clipped layers n < K, the 1/L^2 coefficient is -q/4 + 1/64 + m/48 at L = 0 mod 4 and +q/4 + 1/64 + m/48 at L = 2 mod 4, and the odd-L constant is C_W - kappa");497 let lvalue = (1.0 + 2f64.sqrt()).ln() / 2f64.sqrt();498 for half in [0i64, 2, 4, 6, 8, 12, 16] {499 let w = width_terms(half);500 let excesses = width_excesses(rule, half, LADDER_LIMIT);501 let mut drift = 0.0f64;502 for number in odds(w.k as usize) {503 if (number as i64) < w.k {504 drift += excesses[(number - 1) / 2] - flat(width_excess_law(half, number as i64));505 }506 }507 let (m, q, kappa) = (flat(w.m), flat(w.q), flat(w.kappa));508 let slope = m / 2.0;509 let constant = m * (2f64.ln() + EULER / 2.0) + q * lvalue - CATALAN / 8.0 + drift;510 let miss = |count: usize| {511 mean(&excesses[..count]) * count as f64 - slope * (count as f64).ln() - constant512 };513 let window = |count: usize| {514 (mean(&excesses[..count]) * count as f64515 - mean(&excesses[..count / 2]) * (count / 2) as f64)516 / 2f64.ln()517 };518 println!(519 " W = {half:2}: slope {} = {slope:+.8} C_W {constant:+.10} Delta_W {drift:+.8}",520 w.m / 2521 );522 println!(523 " L = 0 mod 4: residual * L^2 {:+.7} {:+.7} {:+.7} at L = 400, 800, 1600 against -q/4 + 1/64 + m/48 = {:+.7}",524 miss(400) * 160000.0,525 miss(800) * 640000.0,526 miss(1600) * 2560000.0,527 -q / 4.0 + 1.0 / 64.0 + m / 48.0528 );529 println!(530 " L = 2 mod 4: residual * L^2 {:+.7} {:+.7} {:+.7} at L = 402, 802, 1602 against +q/4 + 1/64 + m/48 = {:+.7}",531 miss(402) * 402.0 * 402.0,532 miss(802) * 802.0 * 802.0,533 miss(1602) * 1602.0 * 1602.0,534 q / 4.0 + 1.0 / 64.0 + m / 48.0535 );536 println!(537 " L odd: (residual + kappa) * L {:+.6} {:+.6} at L = 401, 1601, so the odd-L constant is C_W - kappa with an O(1/L) error",538 (miss(401) + kappa) * 401.0,539 (miss(1601) + kappa) * 1601.0540 );541 println!(542 " sliding window L/2 to L: {:+.8} at L = 1600 against m/2 {:+.8}, {:+.8} at L = 1602 against m/2 + kappa/ln 2 {:+.8}",543 window(1600),544 slope,545 window(1602),546 slope + kappa / 2f64.ln()547 );548 }549}550551fn width_excesses(rule: &Rule, half: i64, limit: usize) -> Vec<f64> {552 odds(2 * limit - 1)553 .map(|number| arm_ink(rule, number as i64, half) - law_value(number))554 .collect()555}556557fn width_slope(excesses: &[f64], limit: usize) -> f64 {558 let near = mean(&excesses[..limit / 2]) * (limit / 2) as f64;559 let far = mean(&excesses[..limit]) * limit as f64;560 (far - near) / 2f64.ln()561}562563fn edge_law(half: i64) -> f64 {564 let w = width_terms(half);565 -(w.k as f64 + w.b as f64) / (4.0 * (2 * w.k + 1) as f64)566}567568pub fn cell_widths(rule: &Rule) {569 println!("carpet cell frame, band half-width W cells about the arm, slope of excess * L against ln L, K = W/2 and b = 1 when floor(K/2) is even");570 println!(" the twelve swept widths against the derived rational, with a best-rational search over denominators to 400 beside them as a blind reading");571 for half in [0i64, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32, 64] {572 let slope = width_slope(&width_excesses(rule, half, WIDTH_LIMIT), WIDTH_LIMIT);573 let (numerator, denominator) = nearest(slope);574 println!(575 " W = {half:2} cells at L = {WIDTH_LIMIT}: slope {slope:+.6} best rational {numerator}/{denominator} = {:+.6} search residual {:.1e} against -(K + b)/(4(2K + 1)) = {:+.6}",576 numerator as f64 / denominator as f64,577 (slope - numerator as f64 / denominator as f64).abs(),578 edge_law(half)579 );580 }581 println!(" odd W is not a new band: x - y is even on the arm, so W and W - 1 read one point set and one rational");582 for half in [0i64, 1, 2, 3] {583 let slope = width_slope(&width_excesses(rule, half, WIDTH_LIMIT), WIDTH_LIMIT);584 println!(585 " W = {half:2} cells at L = {WIDTH_LIMIT}: slope {slope:+.6} against -(K + b)/(4(2K + 1)) = {:+.6}",586 edge_law(half)587 );588 }589 println!(" the seven smallest widths recomputed to L = {CHECK_LIMIT}, four times the sweep, against the exact rational");590 for half in [0i64, 2, 4, 6, 8, 10, 12] {591 let excesses = width_excesses(rule, half, CHECK_LIMIT);592 let target = edge_law(half);593 let gaps: Vec<f64> = [WIDTH_LIMIT, HELD_LIMIT, CHECK_LIMIT]594 .iter()595 .map(|limit| (width_slope(&excesses, *limit) - target).abs())596 .collect();597 println!(598 " W = {half:2} cells: slope {:+.8} at L = {CHECK_LIMIT} against {target:+.8} gap {:.1e} (L = {WIDTH_LIMIT}) {:.1e} (L = {HELD_LIMIT}) {:.1e} (L = {CHECK_LIMIT})",599 width_slope(&excesses, CHECK_LIMIT),600 gaps[0],601 gaps[1],602 gaps[2]603 );604 }605 println!(606 " four widths held out of the sweep, predicted before measuring, at L = {HELD_LIMIT}"607 );608 for half in [14i64, 18, 28, 36] {609 let slope = width_slope(&width_excesses(rule, half, HELD_LIMIT), HELD_LIMIT);610 let target = edge_law(half);611 println!(612 " W = {half:2} cells: predicted {target:+.8} measured {slope:+.8} gap {:.1e}",613 (slope - target).abs()614 );615 }616 println!(617 " the width law's own limit as W grows is -1/8 = {:+.6}, the value the fixed-fraction lattice band measures",618 -0.125619 );620}