verify.py

14.3 kB · python · 422 lines

1from fractions import Fraction2from math import comb, gcd, log, sqrt34# PINCER56F = ((0, 0), (1, 0), (0, 1))7FS = set(F)8PHI = (1 + sqrt(5)) / 29KAPPA = 3 - log(5, 3)1011def near(got, want, tol, what):12    assert abs(got - want) <= tol, "%s: got %.12f want %.12f" % (what, got, want)1314def same(got, want, what):15    assert got == want, "%s: got %r want %r" % (what, got, want)1617def klass(a, b):18    if a % 3 and b % 3:19        if (a - b) % 3 == 0:20            return "eq", 021        k = 022        s = a + b23        while s % 3 == 0:24            s //= 325            k += 126        return "opp", k27    x = a if a % 3 == 0 else b28    k = 029    while x % 3 == 0:30        x //= 331        k += 132    return "div", k3334def oriented(a, b):35    if a % 3 == 0:36        return a, b37    if b % 3 == 0:38        return b, a39    return a, b4041def edges(a, b):42    out = [[] for _ in range(a * b)]43    for c1 in range(a):44        for c2 in range(b):45            s = c1 * b + c246            for d in range(3):47                x = a * d + c148                y = b * d + c249                if (x % 3, y % 3) in FS:50                    out[s].append((d, (x // 3) * b + (y // 3)))51    return out5253def rowsums(out, w):54    v = [1] * len(out)55    for _ in range(w):56        v = [sum(v[t] for _, t in row) for row in out]57    return v5859def fib(n):60    x, y = 0, 161    for _ in range(n):62        x, y = y, x + y63    return x6465def dfree(k, w):66    p = 167    for r in range(k):68        m = len([i for i in range(1, w + 1) if i % k == r])69        p *= fib(m + 2)70    return p7172def dfree_brute(k, w):73    c = 074    for mask in range(1 << w):75        ok = True76        for i in range(w - k):77            if (mask >> i) & 1 and (mask >> (i + k)) & 1:78                ok = False79                break80        if ok:81            c += 182    return c8384def rays(h):85    return [(a, b) for a in range(1, h + 1) for b in range(a, h + 1) if gcd(a, b) == 1]8687def typ(cls, a, b, s):88    c1, c2 = divmod(s, b)89    return c1 % 3 if cls == "div" else (c1 + c2) % 39091# LADDER9293def rung(lam, order):94    k = order - log(lam, 3)95    return k / (2 * k + 2 - KAPPA)9697def delta_counts(K):98    types = [(b, c, comb(K, b) * comb(K - b, c)) for b in range(K + 1) for c in range(K + 1 - b)]99    out = {}100    for b, c, m in types:101        for u, v, w in types:102            out[(b - u, c - v)] = out.get((b - u, c - v), 0) + m * w103    return out104105def carry(K):106    r = (K - 1) // 2107    states = [(i, j) for i in range(-r, r + 1) for j in range(-r, r + 1)]108    at = {s: i for i, s in enumerate(states)}109    M = [[0] * len(states) for _ in states]110    for s in states:111        for (d1, d2), w in delta_counts(K).items():112            z1, z2 = s[0] + d1, s[1] + d2113            if z1 % 3 == 0 and z2 % 3 == 0:114                M[at[s]][at[(z1 // 3, z2 // 3)]] += w115    return M, at[(0, 0)], r116117def energies(K, levels):118    M, zero, _ = carry(K)119    v = [0] * len(M)120    v[zero] = 1121    out = [1]122    for _ in range(levels):123        v = [sum(v[i] * M[i][j] for i in range(len(v))) for j in range(len(v))]124        out.append(v[zero])125    return out126127def energy_direct(K, a):128    pts = [(0, 0)]129    for l in range(a):130        pts = [(x + dx * 3 ** l, y + dy * 3 ** l) for x, y in pts for dx, dy in F]131    dist = {(0, 0): 1}132    for _ in range(K):133        fresh = {}134        for (x, y), c in dist.items():135            for dx, dy in pts:136                key = (x + dx, y + dy)137                fresh[key] = fresh.get(key, 0) + c138        dist = fresh139    return sum(c * c for c in dist.values())140141def charpoly(M):142    n = len(M)143    A = [[Fraction(x) for x in row] for row in M]144    coeffs = [Fraction(1)]145    N = [[Fraction(int(i == j)) for j in range(n)] for i in range(n)]146    for k in range(1, n + 1):147        AN = [[sum(A[i][t] * N[t][j] for t in range(n)) for j in range(n)] for i in range(n)]148        c = -sum(AN[i][i] for i in range(n)) / k149        coeffs.append(c)150        N = [[AN[i][j] + (c if i == j else 0) for j in range(n)] for i in range(n)]151    return [int(c) for c in coeffs]152153def polymul(a, b):154    out = [0] * (len(a) + len(b) - 1)155    for i, x in enumerate(a):156        for j, y in enumerate(b):157            out[i + j] += x * y158    return out159160def trim(p):161    i = 0162    while i < len(p) - 1 and p[i] == 0:163        i += 1164    return p[i:]165166def deriv(p):167    n = len(p) - 1168    return trim([p[i] * (n - i) for i in range(n)]) if n else [Fraction(0)]169170def polyrem(a, b):171    a = [Fraction(x) for x in a]172    b = [Fraction(x) for x in b]173    while len(a) >= len(b) and any(a):174        f = a[0] / b[0]175        for i in range(len(b)):176            a[i] -= f * b[i]177        a = trim(a)178    return a179180def sturm(p):181    chain = [trim([Fraction(x) for x in p])]182    chain.append(deriv(chain[0]))183    while len(chain[-1]) > 1:184        r = polyrem(chain[-2], chain[-1])185        if not any(r):186            break187        chain.append([-c for c in r])188    return chain189190def variations(vals):191    s = [v for v in vals if v != 0]192    return sum(1 for i in range(len(s) - 1) if (s[i] > 0) != (s[i + 1] > 0))193194def value(p, t):195    v = Fraction(0)196    for c in p:197        v = v * t + c198    return v199200def sign_changes(chain, t):201    return variations([value(q, t) for q in chain])202203def sign_changes_infinity(chain):204    return variations([q[0] for q in chain])205206def squarefree(p):207    chain = sturm(p)208    return len(chain[-1]) == 1 and chain[-1][0] != 0209210def real_roots_above(p, t):211    chain = sturm(p)212    return sign_changes(chain, t) - sign_changes_infinity(chain)213214def real_roots_between(p, lo, hi):215    chain = sturm(p)216    return sign_changes(chain, lo) - sign_changes(chain, hi)217218QUARTIC = [1, -7833, 7916949, -850684437, 13054946580]219220OTHERS = [[1, 0], [1, -120], [1, -450, 12231], [1, -2190, 282096, -5186835], [1, -990, 116154, -2569725]]221222MULTIPLICITY = [6, 1, 1, 2, 2]223224ENERGIES = [1, 4653, 28967859, 190911254427, 1270015973323281, 8461182216374750493]225226LO = Fraction(66641136625, 10 ** 7)227228HI = Fraction(66641136626, 10 ** 7)229230def ladder():231    for K in range(1, 9):232        r = (K - 1) // 2233        assert (r + K) // 3 <= r, "order %d carry box: got (r + K) // 3 = %d want <= %d" % (2 * K, (r + K) // 3, r)234        assert max(max(abs(d1), abs(d2)) for d1, d2 in delta_counts(K)) == K, "order %d digit spread: want %d" % (2 * K, K)235    same(energies(2, 6), [15 ** a for a in range(7)], "E_4(G_a) = 15^a")236    M, zero, r = carry(5)237    same((len(M), r), (25, 2), "order-10 carry matrix shape")238    same(energies(5, 5), ENERGIES, "E_10(G_a) from the carry matrix")239    for a in range(1, 4):240        same(energy_direct(5, a), ENERGIES[a], "E_10(G_%d) by direct convolution" % a)241    poly = charpoly(M)242    same(len(poly) - 1, 25, "charpoly degree")243    product = QUARTIC244    for f, m in zip(OTHERS, MULTIPLICITY):245        for _ in range(m):246            product = polymul(product, f)247    same(product, poly, "charpoly factorisation")248    assert squarefree(QUARTIC), "quartic factor: got a repeated root want squarefree"249    same(real_roots_above(QUARTIC, HI), 0, "quartic real roots above 6664.1136626")250    same(real_roots_between(QUARTIC, LO, HI), 1, "quartic real roots in the bracket")251    for f in OTHERS[1:]:252        assert squarefree(f), "factor %r: got a repeated root want squarefree" % f253        same(real_roots_above(f, HI), 0, "factor %r real roots above 6664.1136626" % f)254    k10 = 10 - log(float(HI), 3)255    b10 = rung(float(HI), 10)256    assert k10 > 1.985805792698, "certified kappa_10: got %.12f want > 1.985805792698" % k10257    assert b10 > 0.447597813453, "certified rung 10: got %.12f want > 0.447597813453" % b10258    assert b10 > 0.4475978, "short edge: got %.7f want > 0.4475978" % b10259    assert b10 < rung(6664.113662506, 10), "certified rung 10 must sit below the floating value"260    print("ladder: order-10 carry matrix 25 states, energies to a = 5, exact charpoly factorisation, Sturm gives lambda_10 < 6664.1136626 and beta_0^(10) > 0.447597813453")261262# PINCER263264def constants():265    L = log(PHI, 3)266    near(1 / (2 - L), 0.6402121938, 5e-10, "top edge")267    near((1 - L) / (2 - L), 0.3597878, 5e-7, "c star")268    x = 1.5269    for _ in range(80):270        x -= (x ** 3 - x * x - 1) / (3 * x * x - 2 * x)271    near(x, 1.4655712319, 5e-10, "supergolden root")272    near(1 / (2 - log(x, 3)), 0.6053028664, 5e-10, "supergolden edge")273    near(2 / (3 + log(5, 3)), 0.447930988, 5e-9, "ladder cap")274    near(rung(456 + 3 * sqrt(11017), 8), 0.446717310462, 5e-12, "rung 8")275    near(rung(6664.113662506, 10), 0.447597813454, 5e-12, "rung 10")276    near(1 / (2 - log(1.0639086, 3)), 0.5145062, 5e-8, "quarantined 0.5145062")277    near(1 / (2 - log(1.0997454, 3)), 0.5226147, 5e-8, "quarantined 0.5226147")278    print("constants: edge 0.6402121938, rungs 0.446717310462 / 0.447597813454, cap 0.447930988")279280def subsets():281    for k in range(1, 6):282        for w in range(1, 15):283            same(dfree(k, w), dfree_brute(k, w), "D_%d(%d)" % (k, w))284    for k in range(1, 7):285        for w in range(1, 61):286            assert dfree(k, w) <= PHI ** (w + k), "D_%d(%d) exceeds phi^(w+k)" % (k, w)287    print("subsets: D_k(w) exact for w <= 14, k <= 5; D_k(w) <= phi^(w+k) for k <= 6, w <= 60")288289def branching(h):290    n2 = 0291    for (a, b) in rays(h):292        cls, k = klass(a, b)293        a, b = oriented(a, b)294        out = edges(a, b)295        for s, row in enumerate(out):296            if cls == "eq":297                assert len(row) <= 1, "eq ray (%d,%d) state %d: got %d digits want <= 1" % (a, b, s, len(row))298                continue299            t = typ(cls, a, b, s)300            want = {0: 2, 1: 1, 2: 0}[t] if cls == "div" else {0: 1, 1: 2, 2: 0}[t]301            same(len(row), want, "ray (%d,%d) state %d type %d" % (a, b, s, t))302            if want == 2:303                n2 += 1304                same(len({d % 3 for d, _ in row}), 2, "ray (%d,%d) state %d digits" % (a, b, s))305    print("branching: exact 2/1/0 counts at every state of every ray of height <= %d (%d branching states)" % (h, n2))306307def delay(h):308    tested = 0309    for (a, b) in rays(h):310        cls, k = klass(a, b)311        if cls == "eq":312            continue313        a, b = oriented(a, b)314        out = edges(a, b)315        for s in range(len(out)):316            paths = [(s, [])]317            for _ in range(k):318                nxt = []319                for st, seq in paths:320                    for d, t in out[st]:321                        nxt.append((t, seq + [(d, typ(cls, a, b, t))]))322                paths = nxt323            if not paths:324                continue325            for j in range(k - 1):326                vals = {seq[j][1] for _, seq in paths}327                same(len(vals), 1, "ray (%d,%d) state %d step %d predetermination" % (a, b, s, j + 1))328            heads = {}329            for _, seq in paths:330                heads.setdefault(seq[0][0], set()).add(seq[k - 1][1])331            if len(heads) == 2:332                (d1, t1), (d2, t2) = list(heads.items())333                same(len(t1) * len(t2), 1, "ray (%d,%d) state %d split determinism" % (a, b, s))334                assert t1 != t2, "ray (%d,%d) state %d: got equal types %r want distinct" % (a, b, s, t1)335                tested += 1336    print("delay: predetermination and the k-step split at every state of every div/opp ray of height <= %d (%d splits)" % (h, tested))337338def burst(h, wmax):339    for (a, b) in rays(h):340        cls, k = klass(a, b)341        a, b = oriented(a, b)342        out = edges(a, b)343        v = [1] * len(out)344        for w in range(1, wmax + 1):345            v = [sum(v[t] for _, t in row) for row in out]346            cap = 1 if cls == "eq" else dfree(k, w)347            got = max(v)348            assert got <= cap, "ray (%d,%d) w=%d: got P_w %d want <= %d" % (a, b, w, got, cap)349    print("burst: P_w(s) <= D_k(w) at every start state, every ray of height <= %d, every w <= %d" % (h, wmax))350351def saturation():352    same(rowsums(edges(9, 1), 36)[0], 45765225, "P_36 at (9,1)")353    same(dfree(2, 36), 45765225, "D_2(36)")354    same(rowsums(edges(27, 1), 36)[0], 53582633, "P_36 at (27,1)")355    same(dfree(3, 36), 53582633, "D_3(36)")356    print("saturation: P_36 = D_2(36) = 45765225 at (9,1) and P_36 = D_3(36) = 53582633 at (27,1)")357358def catalogue():359    cat = rays(40)360    same(len(cat), 490, "primitive rays of height <= 40")361    shifts = [(1, 3), (1, 9), (1, 27)]362    body = [r for r in cat if r != (1, 1)]363    same(len(body), 489, "catalogue after removing (1,1)")364    nonshift = [r for r in body if r not in shifts]365    same(len(nonshift), 486, "non-shift rays")366    best = []367    wsum = 0.0368    wnum = 0.0369    for (a, b) in body:370        w = 1.0 / (a * a + b * b)371        if (a, b) in shifts:372            r = PHI373        else:374            r = max(rowsums(edges(*oriented(a, b)), 55)) ** (1.0 / 55)375            assert r < PHI, "ray (%d,%d): got certificate %.10f want < phi" % (a, b, r)376            best.append((r, (a, b)))377        wsum += w378        wnum += w * r379    best.sort(reverse=True)380    top = [(p, round(r, 10)) for r, p in best[:3]]381    same(top, [((4, 9), 1.481203426), ((3, 10), 1.4765525267), ((1, 12), 1.4728541511)], "top three certificates")382    near(wnum / wsum, 1.0997454, 5e-8, "inverse-square-weighted upper mean")383    print("catalogue: 490 rays of height <= 40, 489 = 486 + 3 after removing (1,1), top certificate 1.4812034260 at (4,9), weighted upper mean 1.0997454")384385def census(jmax):386    for j in range(1, jmax + 1):387        lo, hi = 3 ** j, 3 ** (j + 1)388        counts = {}389        for (a, b) in rays(hi - 1):390            if max(a, b) < lo:391                continue392            cls, k = klass(a, b)393            counts[(cls, k)] = counts.get((cls, k), 0) + 1394        for (cls, k), c in counts.items():395            if cls == "eq":396                cap = 9 * 3 ** (2 * j)397            elif cls == "div":398                cap = 9 * 3 ** (2 * j - k)399                assert k <= j, "octave %d div: got k %d want <= %d" % (j, k, j)400            else:401                cap = 18 * 3 ** (2 * j - k)402                assert k <= j + 1, "octave %d opp: got k %d want <= %d" % (j, k, j + 1)403            assert c <= cap, "octave %d %s k=%d: got %d want <= %d" % (j, cls, k, c, cap)404        d1 = counts.get(("div", 1), 0)405        want = 3 ** (2 * j - 1)406        assert d1 >= want, "octave %d depth-one census: got %d want >= %d" % (j, d1, want)407    print("census: div, opp and eq upper bounds and the depth-one count against 3^(2j-1) for every octave 1 <= j <= %d" % jmax)408409def main():410    constants()411    ladder()412    subsets()413    branching(24)414    delay(24)415    burst(24, 20)416    saturation()417    catalogue()418    census(4)419    print("all green")420421if __name__ == "__main__":422    main()