main.rs
25.0 kB · rust · 782 lines
1use mrlylab::roulette::{nodes, spread, Nodes};2use mrlynum::factor::gcd;3use mrlynum::spirograph::{pencils, trace, track, Pencil, Track};4use std::f64::consts::TAU;56const TOL: f64 = 4e-4;7const GRAIN: usize = 400;8const FLOOR: usize = 6000;9const CEIL: usize = 24000;10const ROOTS: usize = 2_000_000;11const DEEP: usize = 5;12const GRIT: usize = 12;1314// THE DESIGNS1516struct Design {17 name: &'static str,18 types: Vec<u8>,19 width: usize,20 height: usize,21}2223impl Design {24 fn far(&self) -> f64 {25 let (w, h) = (self.width as i64, self.height as i64);26 let mut far: f64 = 0.0;27 for i in 0..self.height {28 for j in 0..self.width {29 if self.types[i * self.width + j] == 0 {30 continue;31 }32 let (i, j) = (i as i64, j as i64);33 let seat = ((2 * j + 1 - w) as f64 / 2.0, (h - 2 * i - 1) as f64 / 2.0);34 far = far.max(seat.0.hypot(seat.1));35 }36 }37 far38 }3940 fn reach(&self, target: f64) -> f64 {41 target * (self.width as f64 / 2.0).hypot(self.height as f64 / 2.0) / self.far()42 }4344 fn fits(&self, target: f64) -> bool {45 (0.05..=2.0).contains(&self.reach(target))46 }4748 fn seats(&self, target: f64) -> Vec<Pencil> {49 pencils(50 &self.types,51 self.width,52 self.height,53 "fill",54 self.reach(target),55 0.0,56 1,57 )58 .unwrap()59 }60}6162fn designs() -> Vec<Design> {63 vec![64 Design {65 name: "pin",66 types: vec![1, 0, 0],67 width: 3,68 height: 1,69 },70 Design {71 name: "two",72 types: vec![1, 0, 0, 0, 0, 0, 0, 1, 0],73 width: 3,74 height: 3,75 },76 Design {77 name: "plus",78 types: vec![0, 1, 0, 1, 0, 1, 0, 1, 0],79 width: 3,80 height: 3,81 },82 Design {83 name: "carpet",84 types: vec![1, 1, 1, 1, 0, 1, 1, 1, 1],85 width: 3,86 height: 3,87 },88 ]89}9091// THE RUN9293struct Run {94 curves: usize,95 least: f64,96 most: f64,97 count: Nodes,98}99100fn beat(kind: &str, a: usize, b: usize) -> usize {101 if kind == "in" {102 a - b103 } else {104 a + b105 }106}107108fn wide(kind: &str, a: usize, b: usize) -> f64 {109 beat(kind, a, b) as f64 / b as f64110}111112fn bound(kind: &str, a: usize, b: usize) -> f64 {113 wide(kind, a, b).min(1.0)114}115116fn grain(kind: &str, a: usize, b: usize) -> usize {117 (GRAIN * beat(kind, a, b).max(b)).clamp(FLOOR, CEIL)118}119120fn lay(kind: &str, a: usize, b: usize) -> Track {121 track(kind, a, b, 4, 1).unwrap()122}123124fn sweep(kind: &str, a: usize, b: usize, design: &Design, target: f64, samples: usize) -> Run {125 grip(kind, a, b, design, target, samples, TOL)126}127128fn grip(129 kind: &str,130 a: usize,131 b: usize,132 design: &Design,133 target: f64,134 samples: usize,135 tol: f64,136) -> Run {137 let path = lay(kind, a, b);138 let seats = spread(&path, &design.seats(target), true);139 let reaches: Vec<f64> = seats.iter().map(|p| p.x.hypot(p.y)).collect();140 Run {141 curves: seats.len(),142 least: reaches.iter().cloned().fold(f64::MAX, f64::min),143 most: reaches.iter().cloned().fold(0.0, f64::max),144 count: nodes(&path, &seats, samples, tol).unwrap(),145 }146}147148fn law(a: usize, b: usize, k: usize) -> usize {149 a * b * k * (k - 1) + k * a * (b - 1)150}151152fn ratios(kind: &str, top: usize, deep: usize) -> Vec<(usize, usize)> {153 let mut out = Vec::new();154 for b in 1..=deep {155 for a in 1..=top {156 if gcd(a, b) == 1 && (kind == "out" || a > b) {157 out.push((a, b));158 }159 }160 }161 out162}163164// THE ROOTS165166fn roots(wide: f64, b: usize, m: usize, cp: f64, cq: f64, phase: f64, steps: usize) -> usize {167 let gap = (cp - cq).abs();168 let mean = 4.0 * cp * cq;169 let mut out = 0;170 for sign in [1.0, -1.0] {171 let f = |k: usize| {172 let x = TAU * (k as f64 + 0.5) / steps as f64;173 let ripple = (m as f64 * x + phase).sin();174 2.0 * wide * (b as f64 * x).sin() - sign * (gap * gap + mean * ripple * ripple).sqrt()175 };176 let mut prev = f(steps - 1);177 for k in 0..steps {178 let now = f(k);179 if (prev < 0.0) != (now < 0.0) {180 out += 1;181 }182 prev = now;183 }184 }185 out186}187188fn torus(189 kind: &str,190 a: usize,191 b: usize,192 p: (f64, f64),193 q: (f64, f64),194 steps: usize,195) -> (usize, usize) {196 let (cp, cq) = (p.0.hypot(p.1), q.0.hypot(q.1));197 let phase = (p.1.atan2(p.0) - q.1.atan2(q.0)) / 2.0;198 let count = roots(wide(kind, a, b), b, beat(kind, a, b), cp, cq, phase, steps);199 (count, if cp == cq && phase == 0.0 { 4 } else { 0 })200}201202fn main() {203 let want = std::env::args().nth(1);204 let pick = |name: &str| match want.as_deref() {205 Some(verb) => verb == name,206 None => true,207 };208 if pick("scratch") {209 scratch();210 }211 if pick("law") {212 census();213 }214 if pick("gap") {215 gap();216 }217 if pick("torus") {218 reduction();219 }220 if pick("centre") {221 centre();222 }223 if pick("regions") {224 regions();225 }226}227228// THE SCRATCH229230fn at_reach(design: &Design, reach: f64) -> f64 {231 let circum = (design.width as f64 / 2.0).hypot(design.height as f64 / 2.0);232 reach * design.far() / circum233}234235fn scratch() {236 let all = designs();237 let named = |name: &str| all.iter().position(|d| d.name == name).unwrap();238 let (pin, two, carpet) = (239 &all[named("pin")],240 &all[named("two")],241 &all[named("carpet")],242 );243 println!("THE SCRATCH");244 let mut row = Vec::new();245 for a in [3, 4, 5, 6, 7] {246 let run = sweep("in", a, 1, carpet, at_reach(carpet, 0.3), FLOOR);247 row.push(format!(248 "{a}/1 k{} {} {}",249 run.curves,250 run.count.paired(),251 run.count.selved()252 ));253 }254 println!(255 "- carpet fills inside at reach 0.30, ratio a/1: {}",256 row.join(" ")257 );258 println!(" against 2ab C(k,2) = 56a and k a(b - 1) = 0");259 let mut row = Vec::new();260 for reach in [0.2, 0.4, 0.6, 0.8, 0.9, 1.0, 1.2] {261 let target = at_reach(carpet, reach);262 let run = sweep("in", 7, 3, carpet, target, grain("in", 7, 3));263 row.push(format!(264 "{reach:.1}|p|{:.2}:{}+{}{}",265 run.most,266 run.count.paired(),267 run.count.selved(),268 if run.count.crowded > 0 { "*" } else { "" }269 ));270 }271 println!(272 "- carpet fills inside 7/3 by reach, pairs + selves: {}",273 row.join(" ")274 );275 println!(" against 2ab C(8,2) + 8 a(b - 1) = 1176 + 112 = 1288 below the threshold");276 for target in [0.5, 0.9] {277 let mut row = Vec::new();278 for (a, b) in [279 (3, 1),280 (4, 1),281 (5, 1),282 (7, 1),283 (5, 2),284 (7, 2),285 (7, 3),286 (8, 3),287 (11, 4),288 ] {289 let run = sweep("in", a, b, pin, target, grain("in", a, b));290 row.push(format!("{a}/{b}:{}", run.count.selved()));291 }292 println!("- one pencil inside at |p| = {target}: {}", row.join(" "));293 }294 for target in [0.5, 0.9] {295 let mut row = Vec::new();296 for (a, b) in [(3, 1), (4, 1), (5, 1), (7, 1), (5, 2), (7, 3)] {297 let run = sweep("out", a, b, pin, target, grain("out", a, b));298 row.push(format!("{a}/{b}:{}", run.count.selved()));299 }300 println!("- one pencil outside at |p| = {target}: {}", row.join(" "));301 }302 let run = sweep("in", 7, 4, two, at_reach(two, 0.9), grain("in", 7, 4));303 println!(304 "- two carpet curves inside 7/4 at reach 0.90: k {} pairs {} selves {:?} against 56 and 21",305 run.curves,306 run.count.paired(),307 run.count.selves308 );309 let mut row = Vec::new();310 for reach in [0.76, 0.78, 0.79, 0.80, 0.81, 0.84] {311 let run = sweep(312 "in",313 7,314 3,315 carpet,316 at_reach(carpet, reach),317 grain("in", 7, 3),318 );319 row.push(format!(320 "{reach:.2}:{}+{} most {} crowded {}",321 run.count.paired(),322 run.count.selved(),323 run.count.most,324 run.count.crowded325 ));326 }327 println!(328 "- carpet fills inside 7/3 near the alignment: {}",329 row.join(" ")330 );331}332333// THE LAW334335fn faults(run: &Run, a: usize, b: usize) -> Option<String> {336 let (want, pair) = (a * (b - 1), 2 * a * b);337 let stray = run.count.selves.iter().any(|&count| count != want)338 || run.count.pairs.iter().any(|&count| count != pair);339 if !stray {340 return None;341 }342 Some(format!(343 "selves {:?} against {want}, pairs {:?} against {pair}",344 run.count.selves, run.count.pairs345 ))346}347348fn branches(most: usize) -> usize {349 let mut n = 1;350 while n * (n - 1) / 2 < most {351 n += 1;352 }353 n354}355356fn settle(357 kind: &str,358 a: usize,359 b: usize,360 design: &Design,361 target: f64,362 samples: usize,363) -> (Run, usize, bool) {364 let mut steps = samples;365 let mut run = sweep(kind, a, b, design, target, steps);366 let mut seen = vec![run.count.total()];367 for turn in 1..=DEEP {368 steps = 2 * steps - 1;369 run = sweep(kind, a, b, design, target, steps);370 seen.push(run.count.total());371 let last = seen.len() - 1;372 if last >= 2 && seen[last] == seen[last - 1] && seen[last - 1] == seen[last - 2] {373 return (run, turn, true);374 }375 }376 (run, DEEP, false)377}378379fn census() {380 println!("THE LAW");381 let all = designs();382 let targets = [0.1, 0.25, 0.4, 0.55, 0.7, 0.85, 0.95];383 let (mut cells, mut counted, mut shown) = (0, 0, 0);384 let (mut broken, mut crowded, mut shaky, mut near, mut wobbly) = (0, 0, 0, 0, 0);385 let mut blurred = 0;386 for kind in ["in", "out"] {387 for (a, b) in ratios(kind, 20, 10) {388 let edge = bound(kind, a, b);389 for design in &all {390 for target in targets {391 if target >= edge || !design.fits(target) {392 continue;393 }394 let samples = grain(kind, a, b);395 let (run, turns, steady) = settle(kind, a, b, design, target, samples);396 cells += 1;397 counted += run.count.total();398 shaky += usize::from(turns > 2);399 wobbly += usize::from(!steady);400 let head = format!(401 "{kind} {a}/{b} {} |p| {:.3} k {}",402 design.name, run.most, run.curves403 );404 if !steady {405 println!(406 "- wobbly {head}: {} nodes over {DEEP} doublings, never three counts alike",407 run.count.total()408 );409 }410 let mut aligned = false;411 if run.count.crowded > 0 {412 let tight = grip(kind, a, b, design, target, samples, TOL / 10.0);413 aligned = tight.count.crowded > 0;414 if aligned {415 crowded += 1;416 if shown < 8 {417 shown += 1;418 println!(419 "- crowded {head}: {} of {} nodes take {} branches",420 tight.count.crowded,421 tight.count.total(),422 branches(tight.count.most)423 );424 }425 } else {426 near += 1;427 }428 }429 let fault = faults(&run, a, b);430 if fault.is_some() || run.count.total() != law(a, b, run.curves) {431 let fault = fault.unwrap_or_else(|| "the total misses".to_string());432 if aligned {433 blurred += 1;434 println!(435 "- blurred {head}: {fault}, {} nodes take {} branches",436 run.count.crowded,437 branches(run.count.most)438 );439 } else {440 broken += 1;441 println!(442 "- broken {head}: {fault}, settled at {} against {} at the base {} samples and the law {}",443 run.count.total(),444 grip(kind, a, b, design, target, samples, TOL).count.total(),445 samples,446 law(a, b, run.curves)447 );448 }449 }450 }451 }452 }453 }454 println!("- cells {cells}, nodes {counted}, broken {broken}, crowded {crowded} of which {blurred} disagree and are excluded, near {near}");455 println!("- every count is three sample counts alike, the last two doubled: {shaky} cells needed a further doubling, {wobbly} never settled");456}457458// THE GAP459460fn crest(b: usize, m: usize) -> f64 {461 let steps = 400_000;462 let ridge = |k: usize| {463 let x = std::f64::consts::PI * k as f64 / steps as f64;464 (b as f64 * x).sin().abs() / (m as f64 * x).sin().abs()465 };466 let (mut back, mut here) = (ridge(1), ridge(2));467 let mut least = f64::MAX;468 for k in 3..steps {469 let next = ridge(k);470 if here > back && here >= next {471 least = least.min(here);472 }473 (back, here) = (here, next);474 }475 least476}477478fn gap() {479 println!("THE GAP");480 let all = designs();481 let named = |name: &str| all.iter().position(|d| d.name == name).unwrap();482 let (pin, two) = (&all[named("pin")], &all[named("two")]);483 let levels = [0.5, 0.9, 1.02, 1.1, 1.3, 1.6, 2.0, 2.6, 3.4, 4.5, 6.0];484 let (mut cells, mut stray, mut broken, mut bumps, mut tails) = (0, 0, 0, 0, 0);485 let mut ladders = Vec::new();486 for (a, b) in ratios("in", 20, 10) {487 let (m, edge) = (a - b, wide("in", a, b));488 if edge >= 1.0 {489 continue;490 }491 let top = crest(b, m).min(b as f64 / m as f64);492 let mut rungs: Vec<(f64, usize)> = Vec::new();493 for level in levels {494 let target = level * edge;495 if target >= 1.0 || !pin.fits(target) {496 continue;497 }498 let (run, ..) = settle("in", a, b, pin, target, grain("in", a, b));499 let count = run.count.selved();500 cells += 1;501 stray += usize::from(count % a != 0);502 if (level < top) != (count == a * (b - 1)) {503 broken += 1;504 }505 if let Some(&(_, last)) = rungs.last() {506 bumps += usize::from(count > last);507 }508 rungs.push((level, count));509 }510 if let Some(&(level, count)) = rungs.last() {511 tails += usize::from(level > 1.0 && count == a * m);512 }513 let row: Vec<String> = rungs514 .iter()515 .map(|(level, count)| format!("{level}:{}a", count / a))516 .collect();517 ladders.push(format!(518 "{a}/{b} b - 1 = {} m = {m} crest {top:.3}: {}",519 b - 1,520 row.join(" ")521 ));522 }523 println!("- cells {cells}, counts not a multiple of a {stray}, cells the crest misreads {broken}, rungs that rise {bumps}");524 println!("- the ladder by C/A, the seat over the centre path, one pencil inside:");525 for line in ladders.iter().take(6) {526 println!(" {line}");527 }528 println!(" ladders {}, ending at a(a - b) {tails}", ladders.len());529 let mut strays = Vec::new();530 for (a, b) in ratios("in", 20, 10) {531 let edge = wide("in", a, b);532 if edge >= 1.0 {533 continue;534 }535 for level in [1.1, 1.6, 2.4] {536 let target = level * edge;537 if target >= 1.0 || !two.fits(target) {538 continue;539 }540 let (run, ..) = settle("in", a, b, two, target, grain("in", a, b));541 if run.least <= edge || run.curves < 2 {542 continue;543 }544 let seen: Vec<String> = run.count.pairs.iter().map(|c| c.to_string()).collect();545 if run.count.pairs.iter().any(|&c| c != 2 * a * b) {546 strays.push(format!(547 "{a}/{b} |p| {:.2} to {:.2}: {} against 2ab {}",548 run.least,549 run.most,550 seen.join(" "),551 2 * a * b552 ));553 }554 }555 }556 let seats = two.seats(0.9);557 let path = lay("in", 7, 4);558 let kept = spread(&path, &seats, true);559 let hit = nodes(&path, &kept, grain("in", 7, 4), TOL).unwrap();560 println!(561 "- one seat past the edge is enough: in 7/4 the two design at |p| {:.3} and {:.3}, the edge at 0.750 and the crest at {:.3}, reads pair {} against 2ab 56 while both selves hold at {:?} against a(b - 1) 21",562 kept[0].x.hypot(kept[0].y),563 kept[1].x.hypot(kept[1].y),564 crest(4, 3).min(4.0 / 3.0),565 hit.paired(),566 hit.selves567 );568 println!("- two curves past the edge do not read 2ab, and read no one number a ratio:");569 for line in strays.iter().take(5) {570 println!(" {line}");571 }572 println!(" cells with a stray pair count {}", strays.len());573 println!("- at C/A = 1 exactly the curve runs through the centre, a branches meeting there, so the reach below is neither the law nor a multiple of a and no census cell sits on it:");574 for (a, b) in [(7, 5), (7, 6), (9, 5)] {575 let edge = wide("in", a, b);576 let mut row = Vec::new();577 for step in 0..9 {578 let target = edge * (0.8 + 0.05 * step as f64);579 let (run, ..) = settle("in", a, b, pin, target, grain("in", a, b));580 row.push(format!("{:.3}:{}", run.most, run.count.selved()));581 }582 println!(583 "- across the edge at {a}/{b}, (a - b)/b = {edge:.3}, a(b - 1) = {}: {}",584 a * (b - 1),585 row.join(" ")586 );587 }588}589590// THE TORUS591592fn reduction() {593 println!("THE TORUS");594 let all = designs();595 for (kind, a, b, name, target) in [596 ("in", 7, 3, "pin", 0.5),597 ("in", 7, 3, "pin", 0.9),598 ("in", 11, 4, "two", 0.5),599 ("in", 7, 5, "two", 0.3),600 ("in", 7, 5, "pin", 0.9),601 ("in", 7, 6, "pin", 0.9),602 ("out", 5, 2, "two", 0.9),603 ("out", 7, 3, "carpet", 0.4),604 ("out", 1, 10, "carpet", 0.1),605 ("in", 7, 4, "two", 0.6),606 ("in", 20, 3, "carpet", 0.25),607 ("out", 10, 9, "carpet", 0.4),608 ] {609 let design = all.iter().find(|d| d.name == name).unwrap();610 let path = lay(kind, a, b);611 let seats = spread(&path, &design.seats(target), true);612 let count = nodes(&path, &seats, grain(kind, a, b), TOL).unwrap();613 let mut reads = [0, 0];614 for (turn, steps) in [ROOTS, 4 * ROOTS].into_iter().enumerate() {615 for (i, one) in seats.iter().enumerate() {616 for (j, other) in seats.iter().enumerate().skip(i) {617 let (r, drop) = torus(kind, a, b, (one.x, one.y), (other.x, other.y), steps);618 reads[turn] += if i == j {619 a * (r - drop) / 4620 } else {621 a * r / 2622 };623 }624 }625 }626 println!(627 "- {kind} {a}/{b} {name} |p| {target} k {}: polyline {} against torus {} at {ROOTS} roots and {} at {}, and the law {}",628 seats.len(),629 count.total(),630 reads[0],631 reads[1],632 4 * ROOTS,633 law(a, b, seats.len())634 );635 }636}637638// THE CENTRE639640fn core() -> Design {641 Design {642 name: "core",643 types: vec![1, 0, 0, 0, 1, 0, 0, 0, 0],644 width: 3,645 height: 3,646 }647}648649fn centre() {650 println!("THE CENTRE");651 let hub = core();652 for (a, b) in [(3, 1), (5, 2), (7, 3)] {653 let path = lay("in", a, b);654 let seats = spread(&path, &hub.seats(0.5), true);655 let samples = grain("in", a, b);656 let count = nodes(&path, &seats, samples, TOL).unwrap();657 let coarse = nodes(&path, &seats, samples / 2, TOL).unwrap();658 println!(659 "- in {a}/{b}, a seat at |p| 0.5 beside a seat at the wheel's centre: the pair reads {} crossings against 2ab {}, and the flood reads {} regions at 1600 and {} at 2400, against 2a + a(b - 1) + 2 = {} and the law's 2ab + a(b - 1) + 2 = {}",660 count.paired(),661 2 * a * b,662 flood(&path, &seats, 24000, 1600),663 flood(&path, &seats, 24000, 2400),664 2 * a + a * (b - 1) + 2,665 2 * a * b + a * (b - 1) + 2666 );667 println!(668 " the centre curve is one circle traced b times, so its own polyline self count is meaningless: {} at {samples} samples against {} at {}, and the seat off centre keeps its {} against a(b - 1) {}",669 count.selves[1],670 coarse.selves[1],671 samples / 2,672 count.selves[0],673 a * (b - 1)674 );675 }676}677678// THE REGIONS679680fn flood(path: &Track, seats: &[Pencil], samples: usize, side: usize) -> usize {681 let trail = trace(path, seats, samples).unwrap();682 let (mut low, mut high) = ([f64::MAX; 2], [f64::MIN; 2]);683 for pair in trail.chunks_exact(2) {684 for k in 0..2 {685 low[k] = low[k].min(f64::from(pair[k]));686 high[k] = high[k].max(f64::from(pair[k]));687 }688 }689 let span = (high[0] - low[0]).max(high[1] - low[1]);690 let place = |p: [f64; 2]| {691 let step = |v: f64, k: usize| {692 (((v - low[k]) / span * (side - 5) as f64) as usize + 2).min(side - 1)693 };694 (step(p[0], 0), step(p[1], 1))695 };696 let mut wall = vec![false; side * side];697 for k in 0..seats.len() {698 for i in 0..samples - 1 {699 let base = (k * samples + i) * 2;700 let one = [f64::from(trail[base]), f64::from(trail[base + 1])];701 let two = [f64::from(trail[base + 2]), f64::from(trail[base + 3])];702 let (from, to) = (place(one), place(two));703 let far = from.0.abs_diff(to.0).max(from.1.abs_diff(to.1));704 for step in 0..=2 * far {705 let share = step as f64 / (2 * far).max(1) as f64;706 let point = [707 one[0] + (two[0] - one[0]) * share,708 one[1] + (two[1] - one[1]) * share,709 ];710 let (x, y) = place(point);711 wall[x * side + y] = true;712 }713 }714 }715 let mut seen = vec![false; side * side];716 let mut faces = 0;717 for start in 0..side * side {718 if wall[start] || seen[start] {719 continue;720 }721 let mut stack = vec![start];722 seen[start] = true;723 let mut size = 0;724 while let Some(cell) = stack.pop() {725 size += 1;726 let (x, y) = (cell / side, cell % side);727 for (dx, dy) in [(1, 0), (side - 1, 0), (0, 1), (0, side - 1)] {728 let (nx, ny) = ((x + dx) % side, (y + dy) % side);729 let next = nx * side + ny;730 if !wall[next] && !seen[next] {731 seen[next] = true;732 stack.push(next);733 }734 }735 }736 faces += usize::from(size >= GRIT);737 }738 faces739}740741fn regions() {742 println!("THE REGIONS");743 let all = designs();744 let named = |name: &str| all.iter().position(|d| d.name == name).unwrap();745 let (pin, two, carpet) = (746 &all[named("pin")],747 &all[named("two")],748 &all[named("carpet")],749 );750 for (kind, a, b, design, target, note) in [751 ("in", 3, 1, pin, 0.5, "one seat, no crossing, Jordan"),752 ("in", 5, 2, pin, 0.5, "one seat"),753 ("in", 7, 3, pin, 0.5, "one seat"),754 ("in", 7, 3, pin, 0.9, "one seat"),755 ("in", 3, 1, two, 0.5, "two seats"),756 ("in", 5, 2, two, 0.5, "two seats"),757 ("in", 7, 4, two, 0.9, "two seats past the seat threshold"),758 (759 "in",760 7,761 3,762 carpet,763 0.527,764 "eight seats at an alignment reach",765 ),766 ] {767 let path = lay(kind, a, b);768 let seats = spread(&path, &design.seats(target), true);769 let count = nodes(&path, &seats, grain(kind, a, b), TOL).unwrap();770 println!(771 "- {kind} {a}/{b} {} at |p| {target}, {note}: crossings {} points {} branches {} so E - V + 2 = {}, flood {} at 1600 and {} at 2400, the law {}",772 design.name,773 count.total(),774 count.points,775 count.branches,776 count.branches - count.points + 2,777 flood(&path, &seats, 24000, 1600),778 flood(&path, &seats, 24000, 2400),779 law(a, b, seats.len())780 );781 }782}