carpet_tube.py

7.7 kB · python · 197 lines

1import math2import time3from fractions import Fraction45import numpy as np6from mpmath import mp, mpf7from scipy.ndimage import distance_transform_edt89mp.dps = 5010BASE = 311FILL = 812DIM = mp.log(FILL) / mp.log(BASE)13DIM_F = float(DIM)14LOW = (Fraction(1), Fraction(4, 5), Fraction(-4, 7))15HIGH = (Fraction(9, 8), Fraction(3, 10), Fraction(-1, 14))16SEAM = Fraction(379, 280)171819def hole_tube(side, eps):20    return side * side if side <= 2 * eps else 4 * eps * side - 4 * eps * eps212223def split_index(eps):24    m = 125    while Fraction(1, BASE**m) > 2 * eps:26        m += 127    return m282930def direct_sum(eps):31    m0 = split_index(eps)32    ring = sum(FILL ** (m - 1) * hole_tube(Fraction(1, BASE**m), eps) for m in range(1, m0))33    return ring + Fraction(FILL, BASE**2) ** (m0 - 1)343536def closed_v(eps):37    m0 = split_index(eps)38    a = Fraction(4, 5) * eps * (Fraction(FILL, BASE) ** (m0 - 1) - 1)39    b = Fraction(4, 7) * eps * eps * (FILL ** (m0 - 1) - 1)40    return a - b + Fraction(FILL, BASE**2) ** (m0 - 1)414243def branch(t):44    return LOW if t < Fraction(1, 2) else HIGH454647def to_mpf(x):48    return mpf(x.numerator) / x.denominator if isinstance(x, Fraction) else mpf(x)495051def profile(t):52    x = to_mpf(t)53    c0, c1, c2 = branch(t if isinstance(t, Fraction) else Fraction(float(x)))54    return x ** (DIM - 2) * (to_mpf(c0) + to_mpf(c1) * x + to_mpf(c2) * x * x)555657def profile_float(t):58    c0, c1, c2 = branch(Fraction(t))59    return t ** (DIM_F - 2) * (float(c0) + float(c1) * t + float(c2) * t * t)606162def stationary(coeffs):63    c0, c1, c2 = (to_mpf(c) for c in coeffs)64    a = -c2 * DIM65    b = -c1 * (DIM - 1)66    c = -c0 * (DIM - 2)67    disc = mp.sqrt(b * b - 4 * a * c)68    return sorted([(-b - disc) / (2 * a), (-b + disc) / (2 * a)])697071def filled_cells(level):72    cells = np.ones((1, 1), dtype=bool)73    for _ in range(level):74        block = np.ones((BASE, BASE), dtype=bool)75        block[1, 1] = False76        cells = np.kron(cells, block)77    return cells787980def hole_census(level):81    cells = filled_cells(level)82    side = BASE**level83    padded = np.pad(cells, 1, constant_values=True)84    counted = 085    for m in range(1, level + 1):86        hole = BASE ** (level - m)87        parents = filled_cells(m - 1)88        origins = np.argwhere(parents) * (BASE * hole) + hole89        assert len(origins) == FILL ** (m - 1)90        for i, j in origins:91            assert not cells[i : i + hole, j : j + hole].any()92            ring = padded[i : i + hole + 2, j : j + hole + 2].copy()93            ring[1:-1, 1:-1] = True94            assert ring.all()95        counted += len(origins) * hole * hole96    assert counted == side * side - FILL**level97    return level, FILL**level, side * side - FILL**level9899100def raster_tube(level, cells_in):101    cells = filled_cells(level)102    side = BASE**level103    dist = distance_transform_edt(~cells)104    count = cells.sum() + ((dist <= cells_in + 1e-9) & ~cells).sum()105    return Fraction(int(count), side * side)106107108def window(ulo, uhi, samples):109    worst = 0.0110    lo, hi = math.inf, -math.inf111    for u in np.linspace(ulo, uhi, samples):112        eps = math.exp(-u)113        n = math.floor(-math.log(eps) / math.log(BASE))114        t = eps * BASE**n115        if t >= 1:116            n -= 1117            t = eps * BASE**n118        if t < 1 / BASE:119            n += 1120            t = eps * BASE**n121        m0 = split_index(Fraction(eps))122        ring = sum(FILL ** (m - 1) * float(hole_tube(Fraction(1, BASE**m), Fraction(eps))) for m in range(1, m0))123        v = ring + (FILL / BASE**2) ** (m0 - 1)124        m = eps ** (DIM_F - 2) * v125        worst = max(worst, abs(m - profile_float(t)))126        lo, hi = min(lo, m), max(hi, m)127    return lo, hi, worst128129130def main():131    start = time.time()132    print(f"CARPET: base {BASE}, fill {FILL}, d = log {FILL} / log {BASE} = {mp.nstr(DIM, 12)}")133    print("HOLES: every hole is an open square whose boundary lies in the carpet, and the holes exhaust the complement")134    for level in (3, 4, 5):135        lv, filled, empty = hole_census(level)136        print(f"  level {lv}: {filled} filled cells, {empty} empty cells in 8^(m-1) holes of side 3^-m, m = 1..{lv}, every hole ringed by filled cells")137    print()138    print("TUBE: V(eps) = sum_m 8^(m-1) h(3^-m, eps), h(s, eps) = s^2 if s <= 2 eps else 4 eps s - 4 eps^2, against the closed form, exact rationals")139    grid = [Fraction(1, 3), Fraction(2, 5), Fraction(1, 2), Fraction(3, 5), Fraction(2, 3), Fraction(4, 5), Fraction(9, 10), Fraction(999, 1000)]140    checks = 0141    for n in range(1, 13):142        for t in grid:143            eps = t / BASE**n144            assert direct_sum(eps) == closed_v(eps)145            checks += 1146    print(f"  {checks} pairs (n, t), n = 1..12, t in {{1/3, 2/5, 1/2, 3/5, 2/3, 4/5, 9/10, 999/1000}}: direct sum equals closed form exactly")147    print()148    print("PROFILE: eps = 3^-n t, t in [1/3, 1), M(eps) = eps^(d-2) V(eps) -> G(t)")149    print("  G(t) = t^(d-2) (1 + 4t/5 - 4t^2/7) on [1/3, 1/2),  G(t) = t^(d-2) (9/8 + 3t/10 - t^2/14) on [1/2, 1)")150    half = Fraction(1, 2)151    left = sum(c * half**i for i, c in enumerate(LOW))152    right = sum(c * half**i for i, c in enumerate(HIGH))153    assert left == right == Fraction(44, 35)154    seam_low = Fraction(1, FILL) * sum(c * Fraction(1, 3) ** i for i, c in enumerate(LOW)) * 9155    seam_high = sum(HIGH)156    assert seam_low == seam_high == SEAM157    slope_low = sum(i * c * half ** (i - 1) for i, c in enumerate(LOW) if i)158    slope_high = sum(i * c * half ** (i - 1) for i, c in enumerate(HIGH) if i)159    assert slope_low == slope_high == Fraction(8, 35)160    print(f"  continuous at t = 1/2: both branches give 44/35 * 2^(2-d) = {mp.nstr(profile(half), 12)}, and C^1 there: both polynomial factors have slope 8/35")161    print(f"  periodic: G(1/3) = G(1) = 379/280 = {mp.nstr(mpf(379) / 280, 12)}")162    tmax = [r for r in stationary(LOW) if Fraction(1, 3) <= Fraction(float(r)) < half]163    tmin = [r for r in stationary(HIGH) if half <= Fraction(float(r)) < 1]164    assert len(tmax) == 1 and len(tmin) == 1165    tmax, tmin = tmax[0], tmin[0]166    gmax, gmin = profile(tmax), profile(tmin)167    scan = [profile_float(t) for t in np.linspace(1 / 3, 1, 200001)]168    assert min(scan) >= float(gmin) - 1e-12 and max(scan) <= float(gmax) + 1e-12169    swing = (gmax - gmin) / gmin170    scale = mpf(10) ** 11171    print(f"  maximum {mp.nstr(gmax, 15)}, safe {mp.nstr(mp.ceil(gmax * scale) / scale, 12)} up, at t = {mp.nstr(tmax, 9)} (root of (4/7) d t^2 - (4/5)(d-1) t - (d-2) = 0 in [1/3, 1/2))")172    print(f"  minimum {mp.nstr(gmin, 15)}, safe {mp.nstr(mp.floor(gmin * scale) / scale, 12)} down, at t = {mp.nstr(tmin, 9)} (root of (d/14) t^2 - (3/10)(d-1) t - (9/8)(d-2) = 0 in [1/2, 1))")173    print(f"  swing (max - min) / min = {mp.nstr(swing * 100, 8)} %   scan of 200001 points stays inside [min, max]")174    print(f"  Cantor design for scale: swing 3.53 %; the carpet swings {mp.nstr(swing * 100, 4)} %")175    print()176    print("MEASURED: the level sum at eps = e^-u against G(t)")177    lo, hi, worst = window(50.0, 60.0, 2001)178    print(f"  u in [50, 60]: min {lo:.12f}   max {hi:.12f}   max |M(eps) - G(t)| = {worst:.1e}")179    assert worst < 1e-9180    eps = math.exp(-60)181    print(f"  outer collar at u = 60: eps^(D-2) (4 eps + pi eps^2) = {eps ** (DIM_F - 2) * (4 * eps + math.pi * eps * eps):.1e}")182    print()183    print("RASTER: level-6 cells, Euclidean distance transform to the nearest filled cell centre, no hole lemma used")184    level, k = 6, 21185    side = BASE**level186    raster = raster_tube(level, k)187    exact = closed_v(Fraction(k, side))188    print(f"  cells within {k} cell widths of a filled centre, plus the filled cells: {raster} = {float(raster):.12f}")189    print(f"  exact V({k}/{side}) from the closed form: {exact} = {float(exact):.12f}")190    assert raster == exact191    print("  equal as rationals")192    print()193    print(f"wall {time.time() - start:.1f} s")194195196if __name__ == "__main__":197    main()