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}