sponge_tube.py

9.4 kB · python · 271 lines

1import math2import time3from fractions import Fraction as Fr45import numpy as np6from mpmath import iv, mp78mp.prec = 133910iv.prec = 13311D = iv.log(20) / iv.log(3)12WEIGHT = iv.mpf(27) / 2013CELLW = (3, 2, 3)14LEVELS = 40015DEEP_LEVELS = 716TAIL_LEVELS = 80171819def num(x):20    return iv.mpf(x.numerator) / x.denominator if isinstance(x, Fr) else iv.mpf(x)212223def asin(u):24    return iv.atan2(u, iv.sqrt(1 - u * u))252627def a0(t, d):28    t = min(t, d)29    x, dd = num(t), num(d)30    if t == d:31        return iv.pi * dd * dd / 432    return x / 2 * iv.sqrt(dd * dd - x * x) + dd * dd / 2 * asin(x / dd)333435def a1(t, d):36    t = min(t, d)37    x, dd = num(t), num(d)38    if t == d:39        return dd**3 / 340    y = dd * dd - x * x41    return x * x * (3 * dd**4 - 3 * dd * dd * x * x + x**4) / (3 * (dd**3 + y * iv.sqrt(y)))424344def seg(t0, t1, alpha, beta, d):45    if t1 <= t0:46        return iv.mpf(0)47    return num(alpha) * (a0(t1, d) - a0(t0, d)) - num(beta) * (a1(t1, d) - a1(t0, d))484950def hole_j(s, d):51    return 4 * seg(Fr(0), s / 2, s, 2, d)525354def partial_j(s, c, d):55    left = seg(Fr(0), min(s / 2, c), s, 2, d)56    right = seg(max(Fr(0), s - c), s / 2, s, 2, d)57    if c <= s / 2:58        bottom = seg(Fr(0), c, c, 1, d)59    else:60        bottom = seg(Fr(0), s - c, c, 1, d) + seg(s - c, s / 2, s, 2, d)61    return left + right + 2 * bottom626364def column_weight(i, digits):65    w = 166    for _ in range(digits):67        w *= CELLW[i % 3]68        i //= 369    return w707172def columns_below(count, digits):73    if count >= 3**digits:74        return 8**digits75    total, prefix = 0, 176    for d in range(digits - 1, -1, -1):77        digit = (count // 3**d) % 378        total += prefix * sum(CELLW[:digit]) * 8**d79        prefix *= CELLW[digit]80    return total818283def wall_half(delta):84    total = iv.mpf(0)85    for m in range(1, LEVELS + 1):86        total += 8 ** (m - 1) * hole_j(Fr(1, 3 ** (m + 1)), delta)87    total /= 288    assert upper(total) <= lower(num(delta / 18))89    return total, num(delta * Fr(8, 9) ** LEVELS / 16)909192def strip(delta):93    total = iv.mpf(0)94    for m in range(1, LEVELS + 1):95        s = Fr(1, 3 ** (m + 1))96        ratio = delta / s97        full = int((ratio - 2) // 3) + 198        if full > 0:99            total += columns_below(min(full, 3 ** (m - 1)), m - 1) * hole_j(s, delta)100        cut = int((ratio - 1) // 3)101        if 0 <= cut < 3 ** (m - 1) and (3 * cut + 1) * s < delta < (3 * cut + 2) * s:102            total += column_weight(cut, m - 1) * partial_j(s, delta - (3 * cut + 1) * s, delta)103    assert upper(total) <= lower(num(delta * delta / 3))104    return total, num(delta * Fr(8, 9) ** LEVELS / 8)105106107def hole_rows(i, digits):108    rows = [0]109    for d in range(digits):110        digit = (i // 3**d) % 3111        allowed = (0, 2) if digit == 1 else (0, 1, 2)112        rows = [r + a * 3**d for r in rows for a in allowed]113    return sorted(rows)114115116def overlap(ints_a, ints_b):117    total, j = Fr(0), 0118    for lo, hi in ints_a:119        while j < len(ints_b) and ints_b[j][1] <= lo:120            j += 1121        k = j122        while k < len(ints_b) and ints_b[k][0] < hi:123            total += min(hi, ints_b[k][1]) - max(lo, ints_b[k][0])124            k += 1125    return total126127128def columns_meeting(lo, hi, s, digits):129    first = max(0, int((lo / s - 2) // 3))130    last = min(3**digits - 1, int((hi / s - 1) // 3) + 1)131    return [i for i in range(first, last + 1) if (3 * i + 2) * s > lo and (3 * i + 1) * s < hi]132133134def deep_bound(delta):135    total = Fr(0)136    cache = {}137    for m in range(1, DEEP_LEVELS + 1):138        sm = Fr(1, 3 ** (m + 1))139        wm = sm * sm / (4 * delta)140        for n in range(1, DEEP_LEVELS + 1):141            sn = Fr(1, 3 ** (n + 1))142            wn = sn * sn / (4 * delta)143            for i in columns_meeting(delta - wn, delta, sm, m - 1):144                vlen = min(delta, (3 * i + 2) * sm) - max(delta - wn, (3 * i + 1) * sm)145                if (m, i) not in cache:146                    cache[(m, i)] = [((3 * r + 1) * sm, (3 * r + 2) * sm) for r in hole_rows(i, m - 1)]147                for k in columns_meeting(delta - wm, delta, sn, n - 1):148                    ulen = min(delta, (3 * k + 2) * sn) - max(delta - wm, (3 * k + 1) * sn)149                    if (n, k) not in cache:150                        cache[(n, k)] = [((3 * r + 1) * sn, (3 * r + 2) * sn) for r in hole_rows(k, n - 1)]151                    total += vlen * ulen * overlap(cache[(m, i)], cache[(n, k)])152    for m in range(1, TAIL_LEVELS + 1):153        for n in range(1, TAIL_LEVELS + 1):154            if max(m, n) <= DEEP_LEVELS:155                continue156            wm = min(Fr(1, 3 ** (2 * m + 2)) / (4 * delta), delta)157            wn = min(Fr(1, 3 ** (2 * n + 2)) / (4 * delta), delta)158            count = min(math.ceil(wn * 3**m) + 2, math.ceil(wm * 3**n) + 2)159            total += wm * wn * min(Fr(count, 9), Fr(1, 3))160    total += 2 * Fr(1, 3 ** (2 * TAIL_LEVELS + 6)) / (36 * delta * delta) / Fr(8, 9) / 8161    return total162163164def tube(delta):165    d = num(delta)166    half, half_tail = wall_half(delta)167    strip_v, strip_tail = strip(delta)168    value = iv.pi * d * d - 8 * iv.sqrt(2) * d**3 + 8 * d * d + 48 * (half - strip_v)169    return value - 48 * strip_tail - 24 * num(deep_bound(delta)), value + 48 * half_tail170171172def tube_upper(delta):173    s_sum = Fr(0)174    for m in range(1, LEVELS + 1):175        s = Fr(1, 3 ** (m + 1))176        s_sum += 8 ** (m - 1) * s * delta * min(s, 4 * delta)177    s_sum += delta * Fr(8, 9) ** LEVELS / 8178    return iv.pi * num(delta) ** 2 + 24 * num(s_sum)179180181def periodic(eps, depth):182    lo = hi = iv.mpf(20) / 27183    for l in range(depth + 1):184        tl, th = tube(eps / 3**l)185        lo += WEIGHT**l * tl186        hi += WEIGHT**l * th187    hi += WEIGHT ** (depth + 1) * tube_upper(eps / 3 ** (depth + 1)) * iv.mpf(20) / 9188    scale = iv.exp((D - 3) * iv.log(num(eps)))189    return lower(lo * scale), upper(hi * scale)190191192def carpet_distance(v, z, side, depth=40):193    v, z = v / side, z / side194    s = np.full(v.shape, side)195    out = np.zeros(v.shape)196    done = np.zeros(v.shape, dtype=bool)197    for _ in range(depth):198        s = s / 3199        dv, dz = np.floor(v * 3).astype(int), np.floor(z * 3).astype(int)200        hole = (dv == 1) & (dz == 1) & ~done201        rel = np.minimum.reduce([v * 3 - 1, 2 - v * 3, z * 3 - 1, 2 - z * 3])202        out[hole] = s[hole] * rel[hole]203        done |= hole204        v, z = v * 3 - dv, z * 3 - dz205    return out206207208def raster(delta, n):209    h = 1 / (3 * n)210    grid = (np.arange(n) + 0.5) * h211    u, v, z = np.meshgrid(grid, grid, grid, indexing="ij")212    dist = np.full(u.shape, np.inf)213    dvz, duz = carpet_distance(v, z, 1 / 3), carpet_distance(u, z, 1 / 3)214    for t, dd in ((u, dvz), (1 / 3 - u, dvz), (v, duz), (1 / 3 - v, duz)):215        dist = np.minimum(dist, np.sqrt(t * t + dd * dd))216    arm = dist217    e = np.minimum.reduce([np.sqrt(np.minimum(u, 1 / 3 - u) ** 2 + np.minimum(v, 1 / 3 - v) ** 2), np.sqrt(np.minimum(u, 1 / 3 - u) ** 2 + np.minimum(z, 1 / 3 - z) ** 2), np.sqrt(np.minimum(v, 1 / 3 - v) ** 2 + np.minimum(z, 1 / 3 - z) ** 2)])218    r = np.sqrt(3) * h / 2219    inner = 6 * (arm <= delta - r).sum() + (e <= delta - r).sum()220    outer = 6 * (arm <= delta + r).sum() + (e <= delta + r).sum()221    return inner * h**3, outer * h**3222223224def lower(x):225    return mp.make_mpf(x._mpi_[0]) if hasattr(x, "_mpi_") else x226227228def upper(x):229    return mp.make_mpf(x._mpi_[1]) if hasattr(x, "_mpi_") else x230231232def down(x, k):233    return mp.mpf(int(mp.floor(lower(x) * 10**k))) / 10**k234235236def up(x, k):237    return mp.mpf(int(mp.ceil(upper(x) * 10**k))) / 10**k238239240def main():241    start = time.time()242    print(f"SPONGE: base 3, fill 20, D = log 20 / log 3 in [{mp.nstr(lower(D), 12)}, {mp.nstr(upper(D), 12)}], weight per level 27/20, constant term 20/27")243    print("TUBE: T(delta) = volume of F_delta inside the plus, delta in (0, 1/6], from the wall carpets and the centre cube edges, interval arithmetic at 133 bits")244    for delta in (Fr(1, 8), Fr(1, 12)):245        lo, hi = tube(delta)246        lo, hi = lower(lo), upper(hi)247        rl, rh = raster(float(delta), 120)248        print(f"  delta = {delta}: closed form in [{mp.nstr(down(lo, 9), 10)}, {mp.nstr(up(hi, 9), 10)}], raster band [{rl:.5f}, {rh:.5f}] at 120^3 cells per cube")249        assert rl <= float(lo) and float(hi) <= rh250    lo, hi = tube(Fr(1, 6))251    ident = (iv.pi + 8) / 36 - iv.sqrt(2) / 27252    assert lower(lo) <= upper(ident) and lower(ident) <= upper(hi)253    print(f"  delta = 1/6: A1 = V1 there, so T = (pi + 8)/36 - sqrt2/27 - 24 Deep = {mp.nstr(down(ident, 9), 10)} less at most {float(24 * deep_bound(Fr(1, 6))):.2e}; closed form in [{mp.nstr(down(lo, 9), 10)}, {mp.nstr(up(hi, 9), 10)}]")254    print("PERIODIC: p(eps) = eps^(D-3) (20/27 + sum_l (27/20)^l T(eps/3^l)), eps in (sqrt2/18, 1/6]")255    bands = {}256    for eps in (Fr(1, 12), Fr(1, 8), Fr(1, 6)):257        lo, hi = periodic(eps, 40)258        bands[eps] = (lo, hi)259        print(f"  eps = {eps}: p in [{mp.nstr(down(lo, 6), 7)}, {mp.nstr(up(hi, 6), 7)}]")260    gap = bands[Fr(1, 6)][0] - bands[Fr(1, 12)][1]261    gap2 = bands[Fr(1, 8)][0] - bands[Fr(1, 12)][1]262    assert gap > 0 and gap2 > 0263    print(f"  p(1/6) - p(1/12) >= {mp.nstr(down(gap, 6), 7)},  p(1/8) - p(1/12) >= {mp.nstr(down(gap2, 6), 7)}")264    swing = gap / bands[Fr(1, 12)][1]265    print(f"  relative swing (p(1/6) - p(1/12)) / p(1/12) >= {mp.nstr(down(swing * 100, 4), 5)} %")266    print(f"  doubly deep set, upper bound on its volume: {float(deep_bound(Fr(1, 6))):.3g} at delta = 1/6, {float(deep_bound(Fr(1, 8))):.3g} at delta = 1/8, {float(deep_bound(Fr(1, 12))):.3g} at delta = 1/12")267    print(f"wall {time.time() - start:.1f} s")268269270if __name__ == "__main__":271    main()