smear.rs

8.0 kB · rust · 219 lines

1use crate::lattice::{Family, Rule, FAMILIES};2use num_rational::Ratio;34type Q = Ratio<i128>;56fn whole(value: Q) -> i128 {7    value.floor().to_integer()8}910fn wrap(value: Q) -> Q {11    value - (value / 2).floor() * 212}1314fn side(step: i128, drift: i128, point: Q, grain: i128) -> Vec<([i128; 4], Q)> {15    let mut cuts: Vec<Q> = (0..=2 * grain).map(|j| Ratio::new(j, grain)).collect();16    if step != 0 {17        let span = 4 * grain * (step.abs() + drift.abs() + 2);18        for k in -span..=span {19            let v = (Ratio::new(k, grain) - point * drift) / step;20            if v >= Q::from_integer(0) && v <= Q::from_integer(2) {21                cuts.push(v);22            }23        }24    }25    cuts.sort();26    cuts.dedup();27    let mut out: Vec<([i128; 4], Q)> = Vec::new();28    for pair in cuts.windows(2) {29        if pair[0] == pair[1] {30            continue;31        }32        let mid = (pair[0] + pair[1]) / 2;33        let far = wrap(mid * step + point * drift);34        let key = [35            whole(mid).rem_euclid(2),36            whole(mid * grain) - whole(mid) * grain,37            whole(far).rem_euclid(2),38            whole(far * grain) - whole(far) * grain,39        ];40        let weight = (pair[1] - pair[0]) / 2;41        match out.iter_mut().find(|(seen, _)| *seen == key) {42            Some((_, total)) => *total += weight,43            None => out.push((key, weight)),44        }45    }46    out47}4849fn overtone(residue: i128, p: i128, q: i128) -> i128 {50    let seed = i128::from((p, q) == (0, 0) || (p, q) == (3, 1));51    if residue == 1 {52        seed53    } else {54        1 - seed55    }56}5758fn ink(rule: &Rule, sigma: i128, tau: i128, tone: i128) -> Q {59    let middle = (sigma + tau + tone).rem_euclid(2);60    Q::from_integer(i128::from(rule.filled(61        4 * sigma as i64,62        4 * middle as i64,63        4 * tau as i64,64    )))65}6667fn field(rule: &Rule, x: Q, z: Q, near: i128, far: i128, step: i128, drift: i128) -> [Q; 3] {68    let across = side(step, drift, x, 4);69    let down = side(step, drift, z, 2);70    let mut out = [Q::from_integer(0); 3];71    for (a, wide) in &across {72        for (c, tall) in &down {73            let weight = wide * tall;74            let one = ink(rule, a[0], c[0], overtone(near, a[1], c[1]));75            let two = ink(rule, a[2], c[2], overtone(far, a[3], c[3]));76            out[0] += weight * one;77            out[1] += weight * two;78            out[2] += weight * one * two;79        }80    }81    out82}8384fn clip(poly: &[(Q, Q)], keep: impl Fn(Q, Q) -> Q) -> Vec<(Q, Q)> {85    let zero = Q::from_integer(0);86    let mut out = Vec::new();87    for index in 0..poly.len() {88        let (p, q) = (poly[index], poly[(index + 1) % poly.len()]);89        let (fp, fq) = (keep(p.0, p.1), keep(q.0, q.1));90        if fp >= zero {91            out.push(p);92        }93        if (fp > zero && fq < zero) || (fp < zero && fq > zero) {94            let t = fp / (fp - fq);95            out.push((p.0 + (q.0 - p.0) * t, p.1 + (q.1 - p.1) * t));96        }97    }98    out99}100101fn moments(poly: &[(Q, Q)]) -> [Q; 4] {102    let mut out = [Q::from_integer(0); 4];103    for index in 0..poly.len() {104        let (x0, y0) = poly[index];105        let (x1, y1) = poly[(index + 1) % poly.len()];106        let cross = x0 * y1 - x1 * y0;107        out[0] += cross;108        out[1] += (x0 + x1) * cross;109        out[2] += (y0 + y1) * cross;110        out[3] += (x0 * y1 + x0 * y0 * 2 + x1 * y1 * 2 + x1 * y0) * cross;111    }112    [out[0] / 2, out[1] / 6, out[2] / 6, out[3] / 24]113}114115fn knots(drift: i128, grain: i128) -> Vec<Q> {116    if drift == 0 {117        return vec![Q::from_integer(0), Ratio::new(1, 2), Q::from_integer(1)];118    }119    let cells = grain * drift.abs();120    (0..=cells).map(|j| Ratio::new(j, cells)).collect()121}122123fn limit(rule: &Rule, near: i128, far: i128, step: i128, drift: i128) -> [Q; 3] {124    let across = knots(drift, 4);125    let down = knots(drift, 2);126    let mut totals = [Q::from_integer(0); 3];127    for xi in 0..across.len() - 1 {128        for zi in 0..down.len() - 1 {129            let (x0, x1) = (across[xi], across[xi + 1]);130            let (z0, z1) = (down[zi], down[zi + 1]);131            let cell = vec![(x0, z0), (x1, z0), (x1, z1), (x0, z1)];132            let cell = clip(&cell, |x, z| x + z - Ratio::new(1, 2));133            if cell.len() < 3 {134                continue;135            }136            let cell = clip(&cell, |x, z| Ratio::new(3, 2) - x - z);137            if cell.len() < 3 {138                continue;139            }140            let [area, mx, mz, mxz] = moments(&cell);141            let corner = |x: Q, z: Q| field(rule, x, z, near, far, step, drift);142            let (v00, v01) = (corner(x0, z0), corner(x0, z1));143            let (v10, v11) = (corner(x1, z0), corner(x1, z1));144            let (dx, dz) = (x1 - x0, z1 - z0);145            for slot in 0..3 {146                let c11 = (v11[slot] - v10[slot] - v01[slot] + v00[slot]) / (dx * dz);147                let c10 = (v10[slot] - v00[slot]) / dx - c11 * z0;148                let c01 = (v01[slot] - v00[slot]) / dz - c11 * x0;149                let c00 = v00[slot] - c10 * x0 - c01 * z0 - c11 * x0 * z0;150                totals[slot] += c00 * area + c10 * mx + c01 * mz + c11 * mxz;151            }152        }153    }154    let hexagon = Ratio::new(3, 4);155    [156        totals[0] / hexagon,157        totals[1] / hexagon,158        totals[2] / hexagon,159    ]160}161162const KINDS: [(&str, i128, i128); 4] = [163    ("doubling n = 2m+1", 2, 1),164    ("doubling n = 2m-1", 2, -1),165    ("adjacent n = m+2", 1, 2),166    ("gcd echo n = 3m", 3, 0),167];168169fn far_residue(near: i128, step: i128, drift: i128) -> i128 {170    (near * step + drift).rem_euclid(4)171}172173pub fn run() {174    println!("the pair limit as an exact rational: layer m of residue r mod 4 against layer n = step*m + drift, the phase (mX mod 2, mZ mod 2) equidistributed on the hexagon");175    println!("  the cut cell law s_y = s_x + s_z + w with w = 1 exactly at (p, q) = (0,0) and (3,1) when n = 1 mod 4 and its complement when n = 3 mod 4, integrated over 1/2 <= X + Z <= 3/2 in exact rationals");176    for family in FAMILIES {177        let rule = Rule::new(family);178        for (label, step, drift) in KINDS {179            let mut line = format!("  {} {label}:", family.name());180            for near in [1i128, 3] {181                let far = far_residue(near, step, drift);182                let [em, en, both] = limit(&rule, near, far, step, drift);183                let cov = both - em * en;184                let spread = em * (Q::from_integer(1) - em);185                let same = spread == en * (Q::from_integer(1) - en);186                let r = cov / spread;187                line.push_str(&format!(188                    "  m={near} mod 4: E {em} {en} cov {cov} r {r} = {:+.10}{}",189                    *r.numer() as f64 / *r.denom() as f64,190                    if same { "" } else { " SPREADS DIFFER" }191                ));192            }193            println!("{line}");194        }195    }196    println!("  the four doubling branches read one magnitude, 253/2160, with the sign law -chi4(m) chi4(n); the adjacent and echo carpet rows are -11/135 and 29/135; the tree and void doubling covariances are exactly 0");197    println!("  the exact full-hexagon Pearson against the limit, carpet: the gap times m is bounded, so the finite-layer reading is the limit plus O(1/m)");198    let carpet = Rule::new(Family::Carpet);199    for (m, n, step, drift) in [200        (301usize, 601usize, 2i128, -1i128),201        (601, 1201, 2, -1),202        (103, 205, 2, -1),203        (203, 405, 2, -1),204        (249, 251, 1, 2),205        (99, 297, 3, 0),206    ] {207        let (measured, _) = crate::pairs::pearson(m, n, &carpet);208        let near = (m as i128).rem_euclid(4);209        let [em, en, both] = limit(&carpet, near, far_residue(near, step, drift), step, drift);210        let cov = both - em * en;211        let value = cov / (em * (Q::from_integer(1) - em));212        let target = *value.numer() as f64 / *value.denom() as f64;213        println!(214            "  ({m},{n}): measured {measured:+.8}  limit {value} = {target:+.8}  gap {:.2e}  gap * m {:+.5}",215            (measured - target).abs(),216            (measured - target) * m as f64217        );218    }219}