verify.py
58.8 kB · python · 1781 lines
1from fractions import Fraction2from itertools import combinations3from math import gcd45# GASKET67GASKET = ((0, 0), (1, 0), (0, 1))8GASKET_SET = set(GASKET)9CELLS = [(a, b) for a in range(3) for b in range(3)]10PERMS = [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]111213def die(name, got, want):14 raise AssertionError("%s: got %r, want %r" % (name, got, want))151617def check(name, got, want):18 if got != want:19 die(name, got, want)202122def points(design, level):23 pts = [(0, 0)]24 for i in range(level):25 p = 3 ** i26 pts = [(x + dx * p, y + dy * p) for (x, y) in pts for (dx, dy) in design]27 return pts282930def rays(pts):31 out = set()32 for (x, y) in pts:33 if x == 0 and y == 0:34 continue35 g = gcd(x, y)36 out.add((x // g, y // g))37 return out383940def diagonal_to(design, level):41 seen = set()42 for L in range(1, level + 1):43 pts = points(design, L)44 n = sum(1 for p in pts if p != (0, 0))45 seen = rays(pts)46 if len(seen) != n:47 return False, 048 return True, sum(1 for (x, y) in seen if x == 0 or y == 0)495051def code(design):52 return sum(2 ** (3 * a + b) for (a, b) in design)535455def graph(phi):56 return tuple((j, phi[j]) for j in range(3))575859def cross_coefficient(phi, d0, u, v):60 return (phi[d0] * (u - v) - d0 * (phi[u] - phi[v])) % 3616263def mass_edges(a, b):64 index = {0: 0}65 order = [0]66 edges = []67 i = 068 while i < len(order):69 c = order[i]70 out = []71 for (dx, dy) in GASKET:72 v = c + b * dx - a * dy73 if v % 3 == 0:74 w = v // 375 if w not in index:76 index[w] = len(order)77 order.append(w)78 out.append(index[w])79 edges.append(out)80 i += 181 return edges828384def mass(a, b, n):85 cur = {0: 1}86 for _ in range(n):87 nxt = {}88 for c, k in cur.items():89 for (dx, dy) in GASKET:90 v = c + b * dx - a * dy91 if v % 3 == 0:92 w = v // 393 nxt[w] = nxt.get(w, 0) + k94 cur = nxt95 return cur.get(0, 0) - 1969798def mass_brute(a, b, n):99 total = 0100 for (x, y) in points(GASKET, n):101 if (x, y) != (0, 0) and x * b == y * a:102 total += 1103 return total104105106def mass_split(a, b, n):107 half = n // 2108 table = {}109 for (x, y) in points(GASKET, half):110 v = b * x - a * y111 table[v] = table.get(v, 0) + 1112 p = 3 ** half113 total = 0114 for (x, y) in points(GASKET, n - half):115 total += table.get(-p * (b * x - a * y), 0)116 return total - 1117118119FIB = [0, 1, 1]120LUC = [2, 1]121122123def fibonacci(k):124 while len(FIB) <= k:125 FIB.append(FIB[-1] + FIB[-2])126 return FIB[k]127128129def lucas(k):130 while len(LUC) <= k:131 LUC.append(LUC[-1] + LUC[-2])132 return LUC[k]133134135def shift_product(j, n):136 if n <= j:137 return 1138 total = 1139 for r in range(j):140 m = len(range(r, n - j, j))141 total *= fibonacci(m + 2)142 return total143144145def narayana(n):146 a = [1, 1, 1]147 while len(a) <= n:148 a.append(a[-1] + a[-3])149 return a[n]150151152def quartic(n):153 c = [1, 2, 3, 4]154 while len(c) <= n:155 c.append(c[-1] + c[-4])156 return c[n]157158159def automaton(s, t):160 start = (0, 0, 0, 0)161 index = {start: 0}162 order = [start]163 edges = []164 i = 0165 while i < len(order):166 a1, a2, b1, b2 = order[i]167 out = []168 for (dx, dy) in GASKET:169 u = ((s * dx + a1) % 3, (s * dy + a2) % 3)170 v = ((t * dx + b1) % 3, (t * dy + b2) % 3)171 if u in GASKET_SET and v in GASKET_SET:172 nxt = ((s * dx + a1) // 3, (s * dy + a2) // 3,173 (t * dx + b1) // 3, (t * dy + b2) // 3)174 if nxt not in index:175 index[nxt] = len(order)176 order.append(nxt)177 out.append(index[nxt])178 edges.append(out)179 i += 1180 return edges181182183def free_automaton(s, t):184 start = (0, 0, 0, 0)185 index = {start: 0}186 order = [start]187 edges = []188 i = 0189 while i < len(order):190 a1, a2, b1, b2 = order[i]191 out = []192 for dx in range(3):193 for dy in range(3):194 u = ((s * dx + a1) % 3, (s * dy + a2) % 3)195 v = ((t * dx + b1) % 3, (t * dy + b2) % 3)196 if u in GASKET_SET and v in GASKET_SET:197 nxt = ((s * dx + a1) // 3, (s * dy + a2) // 3,198 (t * dx + b1) // 3, (t * dy + b2) // 3)199 if nxt not in index:200 index[nxt] = len(order)201 order.append(nxt)202 out.append(index[nxt])203 edges.append(out)204 i += 1205 return edges206207208def live(edges):209 back = [[] for _ in edges]210 for i, outs in enumerate(edges):211 for e in outs:212 back[e].append(i)213 seen = {0}214 stack = [0]215 while stack:216 v = stack.pop()217 for u in back[v]:218 if u not in seen:219 seen.add(u)220 stack.append(u)221 keep = sorted(seen)222 place = {v: i for i, v in enumerate(keep)}223 return [[place[e] for e in edges[v] if e in place] for v in keep]224225226def pair_brute(s, t, n):227 pts = set(points(GASKET, n))228 return sum(1 for (x, y) in pts229 if (s * x, s * y) in pts and (t * x, t * y) in pts)230231232def return_counts(edges, m):233 count = [0] * len(edges)234 count[0] = 1235 out = []236 for _ in range(m + 1):237 out.append(count[0])238 nxt = [0] * len(edges)239 for i, outs in enumerate(edges):240 for e in outs:241 nxt[e] += count[i]242 count = nxt243 return out244245246def charpoly(edges):247 n = len(edges)248 m = [[1 if i == j else 0 for j in range(n)] for i in range(n)]249 coeffs = [1]250 for k in range(1, n + 1):251 am = [[0] * n for _ in range(n)]252 for i, outs in enumerate(edges):253 row = am[i]254 for e in outs:255 src = m[e]256 for j in range(n):257 row[j] += src[j]258 trace = sum(am[i][i] for i in range(n))259 if trace % k != 0:260 die("Faddeev-LeVerrier integrality", trace % k, 0)261 ck = -(trace // k)262 coeffs.append(ck)263 for i in range(n):264 am[i][i] += ck265 m = am266 return coeffs267268269def shift_coefficients(coeffs, r):270 p = list(coeffs)271 out = []272 for _ in range(len(coeffs)):273 acc = 0274 nb = []275 for a in p:276 acc = acc * r + a277 nb.append(acc)278 out.append(nb[-1])279 p = nb[:-1]280 return out281282283def evaluate(coeffs, x):284 acc = 0285 for a in coeffs:286 acc = acc * x + a287 return acc288289290def divides(d, f):291 f = list(f)292 while len(f) >= len(d):293 if f[0] % d[0] != 0:294 return False295 q = f[0] // d[0]296 for i in range(len(d)):297 f[i] -= q * d[i]298 if f[0] != 0:299 return False300 f.pop(0)301 return all(c == 0 for c in f)302303304def no_root_above(coeffs, r):305 return all(v >= 0 for v in shift_coefficients(coeffs, r))306307308def bisect(coeffs, steps):309 lo, hi = Fraction(0), Fraction(4)310 for _ in range(steps):311 mid = (lo + hi) / 2312 if no_root_above(coeffs, mid):313 hi = mid314 else:315 lo = mid316 return lo, hi317318319def in_window(coeffs, lo, hi):320 return evaluate(coeffs, lo) < 0 and no_root_above(coeffs, hi)321322323def word_counts(edges, m):324 count = [0] * len(edges)325 count[0] = 1326 out = []327 for _ in range(m + 1):328 out.append(sum(count))329 nxt = [0] * len(edges)330 for i, outs in enumerate(edges):331 for e in outs:332 nxt[e] += count[i]333 count = nxt334 return out335336337# CENSUS OF DESIGNS338339def census_designs():340 survivors = []341 for design in combinations(CELLS, 3):342 ok, fibres = diagonal_to(design, 6)343 if ok:344 survivors.append((design, fibres))345 check("no-two-collinear subsets to level 6", len(survivors), 10)346 perms = [(d, f) for (d, f) in survivors347 if len(set(a for a, b in d)) == 3 and len(set(b for a, b in d)) == 3]348 check("permutation designs among survivors", len(perms), 4)349 check("survivors with exactly two fibre rays",350 sorted(d for (d, f) in survivors if f == 2), sorted(d for (d, f) in perms))351 want_codes = sorted([98, 140, 266, 84])352 check("codes of the four diagonal designs",353 sorted(code(d) for (d, f) in perms), want_codes)354 check("code 148 is not a permutation design",355 sorted(c for c in CELLS if 148 >> (3 * c[0] + c[1]) & 1),356 [(0, 2), (1, 1), (2, 1)])357 good = sorted(graph(p) for p in PERMS if p[0] != 0)358 check("diagonal designs are exactly the phi(0) nonzero graphs",359 sorted(tuple(sorted(d)) for (d, f) in perms),360 sorted(tuple(sorted(g)) for g in good))361 extra = sorted(d for (d, f) in survivors if f != 2)362 check("the six non-permutation survivors", extra, [363 ((0, 1), (1, 1), (2, 1)),364 ((0, 2), (1, 2), (2, 2)),365 ((1, 0), (1, 1), (1, 2)),366 ((1, 1), (1, 2), (2, 1)),367 ((1, 2), (2, 1), (2, 2)),368 ((2, 0), (2, 1), (2, 2)),369 ])370 print("84 subsets to level 6: 10 with no two points collinear, 4 of them permutation designs")371372373def census_cross():374 for phi in PERMS:375 got = set()376 for d0 in range(3):377 for u in range(3):378 for v in range(3):379 if u != v:380 got.add(cross_coefficient(phi, d0, u, v) != 0)381 check("affine collapse of the cross coefficient",382 cross_coefficient(phi, d0, u, v), (phi[0] * (u - v)) % 3)383 check("18 cross cases for phi=%r" % (phi,), got, {phi[0] != 0})384 print("cross coefficient: nonzero in all 18 cases exactly when phi(0) is nonzero")385386387def census_diagonality():388 for phi in PERMS:389 if phi[0] == 0:390 continue391 design = graph(phi)392 for n in range(1, 9):393 pts = points(design, n)394 check("Z(n) for phi=%r at n=%d" % (phi, n),395 len(rays(pts)) - 2, 3 ** n - 2)396 check("Z at n=0 for phi=%r" % (phi,), len(rays(points(design, 0))), 0)397 identity = points(graph((0, 1, 2)), 1)398 check("identity witness at level 1", sorted(identity), [(0, 0), (1, 1), (2, 2)])399 check("identity determinant", 1 * 2 - 1 * 2, 0)400 doubling = points(graph((0, 2, 1)), 2)401 check("doubling witnesses at level 2", ((1, 2) in doubling, (3, 6) in doubling), (True, True))402 check("doubling determinant", 1 * 6 - 2 * 3, 0)403 print("Z_F(n) = 3^n - 2 for all four diagonal designs, n = 1..8; both phi(0) = 0 designs collapse")404405406# RAY MASSES407408def census_masses():409 got = [mass(3, 1, n) for n in range(1, 31)]410 check("M(3,1) to n=30", got, [fibonacci(n + 1) - 1 for n in range(1, 31)])411 got = [mass(1, 12, n) for n in range(1, 31)]412 check("M(1,12) to n=30", got, [narayana(n) - 1 for n in range(1, 31)])413 got = [mass(7, 3, n) for n in range(3, 31)]414 check("M(7,3) to n=30", got, [quartic(n - 3) - 1 for n in range(3, 31)])415 for j in range(1, 14):416 got = [mass(3 ** j, 1, n) for n in range(1, 31)]417 check("M(3^%d,1) to n=30" % j, got,418 [shift_product(j, n) - 1 for n in range(1, 31)])419 for (a, b) in [(3, 1), (1, 12), (7, 3), (9, 1), (27, 1), (12, 13)]:420 for n in range(1, 11):421 check("automaton against brute force at (%d,%d), n=%d" % (a, b, n),422 mass(a, b, n), mass_brute(a, b, n))423 for n in range(1, 15):424 check("automaton against split enumeration at (%d,%d), n=%d" % (a, b, n),425 mass(a, b, n), mass_split(a, b, n))426 squares = [(n, mass(7, 3, n)) for n in range(1, 31)427 if mass(7, 3, n) > 0 and int(round(mass(7, 3, n) ** 0.5)) ** 2 == mass(7, 3, n)]428 check("perfect-square masses on (7,3) to n=30", squares,429 [(4, 1), (7, 4), (9, 9), (12, 25), (14, 49)])430 check("rational roots of x^4 - x^3 - 1",431 [r for r in (1, -1) if r ** 4 - r ** 3 - 1 == 0], [])432 check("integer quadratic factorisations of x^4 - x^3 - 1",433 [(a, b, c, d) for b, d in ((1, -1), (-1, 1))434 for a in range(-9, 10) for c in range(-9, 10)435 if a + c == -1 and b + d + a * c == 0 and a * d + b * c == 0], [])436 print("mass laws on (3,1), (1,12), (7,3) and the thirteen shift rays: exact to n = 30")437438439def nonneg(x, y):440 if x >= 0 and y >= 0:441 return True442 if x < 0 and y < 0:443 return False444 if y >= 0:445 return 5 * y * y >= x * x446 return x * x >= 5 * y * y447448449def power_root5(a, b, j):450 p, q = 1, 0451 for _ in range(j):452 p, q = a * p + 5 * b * q, a * q + b * p453 return p, q454455456def shift_strings(n, j):457 total = 0458 for z in range(2 ** (n - j)):459 d = [(z >> i) & 1 for i in range(n - j)]460 if all(not (d[i] and d[i - j]) for i in range(j, len(d))):461 total += 1462 return total - 1463464465def census_recurrences():466 for (a, b, want, seeds) in [467 (3, 1, [1, -1, -1], [fibonacci(n + 1) for n in range(0, 3)]),468 (1, 12, [1, -1, 0, -1], [narayana(n) for n in range(0, 4)]),469 (7, 3, [1, -1, 0, 0, -1], [quartic(n) for n in range(0, 5)])]:470 edges = live(mass_edges(a, b))471 check("live carry states at (%d,%d)" % (a, b), len(edges), len(want) - 1)472 check("carry characteristic polynomial at (%d,%d)" % (a, b),473 charpoly(edges), want)474 k = len(want) - 1475 got = return_counts(edges, 2 * k + 12)476 off = 3 if (a, b) == (7, 3) else 0477 check("automaton seeds at (%d,%d)" % (a, b), got[off:off + k], seeds[:k])478 for m in range(k, len(got)):479 check("annihilator residual at (%d,%d), m=%d" % (a, b, m),480 sum(want[i] * got[m - i] for i in range(k + 1)), 0)481 print("mass recurrences are Cayley-Hamilton on 2, 3 and 4 live carry states")482483484def census_shift():485 for k in range(2, 400):486 check("F(%d) <= 2 F(%d)" % (k, k - 1),487 fibonacci(k) <= 2 * fibonacci(k - 1), True)488 for n in range(1, 12):489 for j in range(1, n + 1):490 check("shift strings at n=%d, j=%d" % (n, j),491 (mass(3 ** j, 1, n), shift_strings(n, j)),492 (shift_product(j, n) - 1, shift_product(j, n) - 1))493 for n in range(1, 61):494 for j in range(1, n + 1):495 q, rem = divmod(n - j, j)496 check("block form at n=%d, j=%d" % (n, j), shift_product(j, n),497 fibonacci(q + 3) ** rem * fibonacci(q + 2) ** (j - rem))498 p, r = power_root5(3, -1, j)499 m = shift_product(j, n) - 1500 check("per-j bound at n=%d, j=%d" % (n, j),501 nonneg(p * lucas(n) + 5 * r * fibonacci(n) - 2 * m,502 p * fibonacci(n) + r * lucas(n)), True)503 for n in range(1, 161):504 sh = 2 * sum((shift_product(j, n) - 1) ** 2 for j in range(1, n + 1))505 el, ef = lucas(2 * n), fibonacci(2 * n)506 check("family bound at n=%d" % n,507 nonneg(4 * el + 60 * ef - 22 * sh, 12 * el + 4 * ef), True)508 if n >= 100:509 check("family limit window below at n=%d" % n,510 nonneg(2000000 * sh - 2198212 * el, -2198212 * ef), True)511 check("family limit window above at n=%d" % n,512 nonneg(2198214 * el - 2000000 * sh, 2198214 * ef), True)513 print("shift family: M_n(3^j,1) < (3 - sqrt5)^j phi^n and sum of squares "514 "in (2.198212, 2.198214) phi^2n from n = 100")515516517def census_gasket(n):518 pts = points(GASKET, n)519 check("gasket size at n=%d" % n, len(pts), 3 ** n)520 counts = {}521 for (x, y) in pts:522 if x == 0 or y == 0:523 continue524 g = gcd(x, y)525 r = (x // g, y // g)526 counts[r] = counts.get(r, 0) + 1527 total = sum(counts.values())528 check("non-fibre mass at n=%d" % n, total, 3 ** n - 2 ** (n + 1) + 1)529 return counts530531532def census_rays():533 second, residual = [], []534 for n in range(1, 13):535 counts = census_gasket(n)536 z = sum(m * m for m in counts.values())537 second.append(z)538 residual.append(z - (3 ** n - 2 ** (n + 1) + 1)539 - (3 ** n - 4 * 2 ** n + 2 * n + 3))540 check("shift-ray second moment at n=%d" % n,541 sum(m * m for r, m in counts.items() if is_shift_pair(*r)),542 2 * sum((shift_product(j, n) - 1) ** 2 for j in range(1, n + 1)))543 for n in range(1, 21):544 check("shift-multiplier closed form at n=%d" % n,545 2 * sum(3 ** (n - j) - 2 ** (n - j + 1) + 1 for j in range(1, n)),546 3 ** n - 4 * 2 ** n + 2 * n + 3)547 check("E(n) for n = 1..12", second,548 [0, 2, 16, 98, 396, 1522, 5248, 17118, 52212, 158042, 466960, 1374038])549 check("R(n) for n = 1..12", residual,550 [0, 0, 0, 20, 88, 432, 1624, 5512, 15896, 46064, 124928, 335704])551 check("occupied non-fibre rays at n=12", len(counts), 345318)552 check("non-fibre mass at n=12", sum(counts.values()), 523250)553 top = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:10]554 check("ten heaviest rays at n=12", top, [555 ((1, 3), 232), ((3, 1), 232), ((1, 9), 168), ((9, 1), 168),556 ((1, 27), 124), ((27, 1), 124), ((1, 81), 80), ((81, 1), 80),557 ((1, 243), 71), ((243, 1), 71)])558 check("mirror symmetry of ray masses at n=12",559 all(counts[(b, a)] == m for (a, b), m in counts.items()), True)560 print("gasket census to n = 12: 345318 occupied non-fibre rays carrying 523250 points, "561 "E(12) = 1374038, R(12) = 335704")562563564# PAIR CENSUS565566def ray_masses(n):567 out = {}568 for (x, y) in points(GASKET, n):569 if x == 0 or y == 0:570 continue571 g = gcd(x, y)572 r = (x // g, y // g)573 if r in out:574 out[r].append(g)575 else:576 out[r] = [g]577 return out578579580def is_shift_pair(s, t):581 if min(s, t) != 1:582 return False583 u = max(s, t)584 while u % 3 == 0:585 u //= 3586 return u == 1587588589def census_pairs():590 n = 9591 pts = set(points(GASKET, n))592 by_ray = ray_masses(n)593 check("occupied non-fibre rays at n=9", len(by_ray), 12170)594 Z = sum(len(v) ** 2 for v in by_ray.values())595 T = 3 ** n - 2 ** (n + 1) + 1596 S = 3 ** n - 4 * 2 ** n + 2 * n + 3597 check("E(9), diagonal, 3-power family and residual at n=9",598 (Z, T, S, Z - T - S), (52212, 18660, 17656, 15896))599 check("shift-ray share of the second moment at n=9",600 sum(len(v) ** 2 for r, v in by_ray.items() if is_shift_pair(*r)), 11852)601 census, inside = {}, {}602 for ray, gs in by_ray.items():603 for g in gs:604 for h in gs:605 if g == h:606 continue607 d = gcd(g, h)608 key = (g // d, h // d)609 census[key] = census.get(key, 0) + 1610 hit = 1 if (d * ray[0], d * ray[1]) in pts else 0611 inside[key] = inside.get(key, 0) + hit612 check("ordered off-diagonal collinear pairs at n=9",613 sum(census.values()), Z - T)614 check("shift-multiplier family at n=9",615 sum(v for k, v in census.items() if is_shift_pair(*k)), S)616 check("active ordered multiplier pairs at n=9", len(census), 2656)617 check("active unordered multiplier pairs at n=9", len(census) // 2, 1328)618 check("shift pairs among them",619 sorted(k for k in census if is_shift_pair(*k)),620 [(1, 3 ** j) for j in range(1, 8)] + [(3 ** j, 1) for j in range(1, 8)])621 check("largest multiplier at n=9", max(max(k) for k in census), 2460)622 short = [k for k in census if census[k] != inside[k]]623 check("ordered pairs whose witness leaves the gasket", len(short), 482)624 check("witnesses outside the gasket",625 sum(census[k] - inside[k] for k in short), 2540)626 check("every such pair has both multipliers above one",627 min(min(k) for k in short), 2)628 worst = max(census[k] - inside[k] for k in short)629 check("worst such pairs",630 (worst, sorted(k for k in short if census[k] - inside[k] == worst),631 inside[(41, 122)]),632 (50, [(41, 122), (122, 41), (122, 123), (123, 122)], 0))633 binary = set()634 for u in range(2 ** n):635 binary.add(sum(((u >> i) & 1) * 3 ** i for i in range(n)))636 for (s, t) in sorted(k for k in census if k[0] < k[1]):637 got = return_counts(live(automaton(s, t)), n)[n]638 fib = sum(1 for u in binary639 if u > 0 and s * u in binary and t * u in binary)640 check("A(%d,%d) against brute force at n=9" % (s, t),641 got - 1 - 2 * fib, inside[(s, t)])642 if t > 52:643 continue644 got = return_counts(live(free_automaton(s, t)), n)[n]645 fib = sum(1 for u in range(1, 3 ** n // t + 1)646 if s * u in binary and t * u in binary)647 check("B(%d,%d) against brute force at n=9" % (s, t),648 got - 1 - 2 * fib, census[(s, t)])649 small = [k for k in census if k[0] < k[1] and k[1] <= 52]650 check("unordered pairs of height at most 52 at n=9", len(small), 103)651 check("collinear pairs they carry", sum(census[k] for k in small), 11330)652 print("n = 9 pair census: 2656 active ordered multiplier pairs, 482 of them "653 "with a witness off the gasket")654655656# SPECTRAL GAP657658def census_spectrum():659 pairs = [(s, t) for s in range(1, 53) for t in range(s + 1, 53) if gcd(s, t) == 1]660 check("coprime unordered pairs with max at most 52", len(pairs), 829)661 reachable = {p: automaton(*p) for p in pairs}662 trimmed = {p: live(reachable[p]) for p in pairs}663 check("largest reachable state set", max(len(reachable[p]) for p in pairs), 45)664 check("largest live state set", max(len(trimmed[p]) for p in pairs), 33)665 check("(1,16) reachable states", len(reachable[(1, 16)]), 11)666 check("(1,16) live states", len(trimmed[(1, 16)]), 1)667 plastic = bisect(charpoly(reachable[(1, 16)]), 52)668 check("(1,16) reachable radius is the plastic number",669 (Fraction(13247179572, 10 ** 10) < plastic[0],670 plastic[1] < Fraction(13247179573, 10 ** 10)), (True, True))671 polys = {p: charpoly(trimmed[p]) for p in pairs}672 at_three, at_two, between, below = [], [], [], []673 for p in pairs:674 c = polys[p]675 if not no_root_above(c, 2):676 if evaluate(c, 3) == 0 and no_root_above(c, 3):677 at_three.append(p)678 else:679 between.append(p)680 elif evaluate(c, 2) == 0:681 at_two.append(p)682 else:683 below.append(p)684 check("pairs of spectral radius 3", at_three, [(1, 3), (1, 9), (1, 27)])685 check("pairs in the open interval (2,3)", between, [])686 check("non-shift pairs attaining exactly 2", len(at_two), 20)687 check("the twenty attainers", at_two, [688 (1, 4), (1, 7), (1, 10), (1, 12), (1, 21), (1, 28), (1, 30), (1, 36),689 (3, 4), (3, 7), (3, 10), (3, 28), (4, 9), (4, 27), (7, 9), (7, 27),690 (9, 10), (9, 28), (10, 27), (27, 28)])691 top_lo, top_hi = Fraction(16956207695598, 10 ** 13), Fraction(16956207695599, 10 ** 13)692 next_lo, next_hi = Fraction(16769719158912, 10 ** 13), Fraction(16769719158913, 10 ** 13)693 ties = [p for p in below if not no_root_above(polys[p], Fraction(42, 25))]694 check("pairs with radius above 42/25", len(ties), 25)695 check("the simplest attainer", ties[0], (1, 13))696 check("(12,13) is among them", (12, 13) in ties, True)697 for p in ties:698 check("largest radius below 2, window at %r" % (p,),699 in_window(polys[p], top_lo, top_hi), True)700 second = [p for p in below if p not in ties701 and not no_root_above(polys[p], Fraction(167, 100))]702 check("pairs with radius above 167/100 and below the largest", second,703 [(1, 31), (3, 31), (9, 31), (27, 31)])704 for p in second:705 check("next radius down, window at %r" % (p,),706 in_window(polys[p], next_lo, next_hi), True)707 lo, hi = bisect(polys[(1, 13)], 52)708 check("exact bisection lands inside the quoted window",709 (top_lo <= lo, hi <= top_hi), (True, True))710 for p in [(2, 9), (1, 18)]:711 check("live automaton at %r" % (p,), (trimmed[p], polys[p]), ([[0]], [1, -1]))712 check("(1,4) characteristic polynomial", polys[(1, 4)], [1, -1, -2, 0])713 check("(1,4) live states", len(trimmed[(1, 4)]), 3)714 counts = word_counts(trimmed[(1, 4)], 15)715 check("(1,4) admissible word counts to m=15", counts,716 [(2 ** (m + 2) - (-1) ** m) // 3 for m in range(16)])717 for m in range(2, 16):718 check("(1,4) recurrence residual at m=%d" % m,719 counts[m] - counts[m - 1] - 2 * counts[m - 2], 0)720 returns = return_counts(trimmed[(1, 4)], 15)721 check("(1,4) return counts to m=15", returns,722 [(2 ** (m + 1) + (-1) ** m) // 3 for m in range(16)])723 for (s, t) in [(1, 4), (1, 16), (3, 7), (12, 13)]:724 got = return_counts(trimmed[(s, t)], 8)725 check("return counts against brute force at (%d,%d)" % (s, t), got,726 [pair_brute(s, t, n) for n in range(9)])727 print("829 coprime pairs with max at most 52: radius 3 on three pairs, exactly 2 on twenty, nothing in between")728 free = {p: live(free_automaton(*p)) for p in pairs}729 check("largest free live state set", max(len(free[p]) for p in pairs), 167)730 check("(1,16) free live states", len(free[(1, 16)]), 1)731 for p in pairs:732 if p[0] == 1:733 check("free and gasket-digit automata agree at %r" % (p,),734 free[p], trimmed[p])735 fpolys = {p: charpoly(free[p]) for p in pairs}736 f3, f2, fmid = [], [], []737 for p in pairs:738 c = fpolys[p]739 if not no_root_above(c, 2):740 (f3 if evaluate(c, 3) == 0 and no_root_above(c, 3) else fmid).append(p)741 elif evaluate(c, 2) == 0:742 f2.append(p)743 check("free pairs of spectral radius 3", f3, at_three)744 check("free pairs in the open interval (2,3)", fmid, [])745 check("free pairs attaining exactly 2", f2, at_two)746 fstrict = [p for p in pairs if p not in f3 and p not in f2]747 flo, fhi = Fraction(18488475886, 10 ** 10), Fraction(18488475887, 10 ** 10)748 ftop = [p for p in fstrict if not no_root_above(fpolys[p], flo)]749 check("free pairs above 1.8488475886", ftop,750 [(4, 13), (4, 39), (12, 13), (13, 36)])751 for p in ftop:752 check("largest free radius below 2, window at %r" % (p,),753 in_window(fpolys[p], flo, fhi), True)754 check("free bisection at (4,13) lands inside the quoted window",755 (flo <= bisect(fpolys[(4, 13)], 52)[0],756 bisect(fpolys[(4, 13)], 52)[1] <= fhi), (True, True))757 theta = [1, -1, 0, -2]758 check("theta is the gasket-digit ceiling below 2", in_window(theta, top_lo, top_hi), True)759 fabove = [p for p in fstrict if not no_root_above(fpolys[p], top_hi)]760 fat = [p for p in fstrict if p not in fabove and not no_root_above(fpolys[p], top_lo)]761 check("free pairs strictly above theta and below 2", len(fabove), 19)762 check("free pairs whose radius is exactly theta", len(fat), 25)763 check("free pairs reaching or beating theta", len(fabove) + len(fat), 44)764 check("the 25 carry the theta factor and the 19 do not",765 (all(divides(theta, fpolys[p]) for p in fat),766 [p for p in fabove if divides(theta, fpolys[p])]), (True, []))767 for p in fat:768 check("radius exactly theta, window at %r" % (p,),769 in_window(fpolys[p], top_lo, top_hi), True)770 check("(4,13) free closed paths at n=60",771 return_counts(free[(4, 13)], 60)[60], 4583352807133551)772 check("(4,13) gasket-digit closed paths at n=60 are smaller",773 return_counts(trimmed[(4, 13)], 60)[60] < 4583352807133551, True)774 print("the free-digit automaton keeps the gap at three, two and the empty "775 "interval, but its largest radius below 2 is 1.8488475886 on four pairs")776777778# WITNESS WEIGHTS779780def carry_automaton(s, t):781 index = {(0, 0): 0}782 order = [(0, 0)]783 edges = []784 i = 0785 while i < len(order):786 a, b = order[i]787 out = []788 for d in range(3):789 u = (s * d + a) % 3790 v = (t * d + b) % 3791 if u < 2 and v < 2:792 nxt = ((s * d + a) // 3, (t * d + b) // 3)793 if nxt not in index:794 index[nxt] = len(order)795 order.append(nxt)796 out.append((u, v, index[nxt]))797 edges.append(out)798 i += 1799 return edges800801802def carry_live(edges):803 back = [[] for _ in edges]804 for i, out in enumerate(edges):805 for (u, v, j) in out:806 back[j].append(i)807 seen = {0}808 stack = [0]809 while stack:810 x = stack.pop()811 for y in back[x]:812 if y not in seen:813 seen.add(y)814 stack.append(y)815 keep = sorted(seen)816 place = {v: i for i, v in enumerate(keep)}817 return [[(u, v, place[j]) for (u, v, j) in edges[x] if j in place] for x in keep]818819820def tensor_returns(s, t, n):821 e = carry_live(carry_automaton(s, t))822 k = len(e)823 S = [[j for (u, v, j) in e[i]] for i in range(k)]824 U = [[j for (u, v, j) in e[i] if u] for i in range(k)]825 V = [[j for (u, v, j) in e[i] if v] for i in range(k)]826 W = [[j for (u, v, j) in e[i] if u and v] for i in range(k)]827 X = [[0] * k for _ in range(k)]828 X[0][0] = 1829 out = [1]830 for _ in range(n):831 half = []832 for A in (S, U, V, W):833 Y = []834 for i in range(k):835 acc = [0] * k836 for p in A[i]:837 xp = X[p]838 for q in range(k):839 if xp[q]:840 acc[q] += xp[q]841 Y.append(acc)842 half.append(Y)843 Ys, Yu, Yv, Yw = half844 Z = [[0] * k for _ in range(k)]845 for j in range(k):846 for i in range(k):847 a = 0848 for q in S[j]:849 a += Ys[i][q]850 for q in U[j]:851 a -= Yu[i][q]852 for q in V[j]:853 a -= Yv[i][q]854 for q in W[j]:855 a += Yw[i][q]856 Z[i][j] = a857 X = Z858 out.append(X[0][0])859 return out860861862def witness_mass(z1, z2, n):863 e = carry_live(carry_automaton(z1, z2))864 k = len(e)865 adj = [[j for (u, v, j) in e[i] if not (u and v)] for i in range(k)]866 cur = [0] * k867 cur[0] = 1868 out = [1]869 for _ in range(n):870 nxt = [0] * k871 for i in range(k):872 c = cur[i]873 if c:874 for j in adj[i]:875 nxt[j] += c876 cur = nxt877 out.append(cur[0])878 return [v - 1 for v in out]879880881def in_gasket(x, y, n):882 if x >= 3 ** n or y >= 3 ** n:883 return False884 while x or y:885 a, b = x % 3, y % 3886 if a > 1 or b > 1 or (a and b):887 return False888 x //= 3889 y //= 3890 return True891892893def box_count(s, t, n):894 w = (3 ** n - 1) // (2 * max(s, t))895 c = 0896 for z1 in range(1, w):897 for z2 in range(1, w - z1 + 1):898 if in_gasket(s * z1, s * z2, n) and in_gasket(t * z1, t * z2, n):899 c += 1900 return c901902903def fibre_count(s, t, n):904 binary = set(sum(((u >> i) & 1) * 3 ** i for i in range(n)) for u in range(2 ** n))905 return sum(1 for u in range(1, 3 ** n // t + 1) if s * u in binary and t * u in binary)906907908def no_adjacent(n):909 out = []910 for m in range(1, 3 ** (n - 1)):911 x, prev, ok = m, 0, True912 while x:913 d = x % 3914 if d > 1 or (d and prev):915 ok = False916 break917 prev, x = d, x // 3918 if ok:919 out.append(m)920 return out921922923def three_power_ratio(a, b):924 if a > b:925 a, b = b, a926 if b % a:927 return False928 q = b // a929 while q % 3 == 0:930 q //= 3931 return q == 1932933934def residual_layers(n):935 by = {}936 for (x, y) in points(GASKET, n):937 if x and y:938 g = gcd(x, y)939 by.setdefault((x // g, y // g), []).append(g)940 total = 0941 weight = {}942 pair = {}943 for r, gs in by.items():944 if len(gs) < 2:945 continue946 rs = r[0] + r[1]947 for a in gs:948 for b in gs:949 if a == b:950 continue951 d = gcd(a, b)952 if is_shift_pair(a // d, b // d):953 continue954 total += 1955 weight[d * rs] = weight.get(d * rs, 0) + 1956 k = (a // d, b // d)957 if k[0] > k[1]:958 k = (k[1], k[0])959 pair[k] = pair.get(k, 0) + 1960 return total, weight, pair961962963def ceiling_families():964 bin3 = [sum(((u >> i) & 1) * 3 ** i for i in range(7)) for u in range(1, 128)]965 nad = []966 for m in range(1, 3 ** 7):967 x, prev, ok = m, 0, True968 while x:969 d = x % 3970 if d > 1 or (d and prev):971 ok = False972 break973 prev, x = d, x // 3974 if ok:975 nad.append(m)976 return [977 [(a, b) for a in bin3 for b in bin3 if a < b],978 [(a, b) for a in nad for b in nad if a < b],979 [(1, t) for t in range(2, 3000)],980 [(a, a + 1) for a in range(1, 1500)],981 [(a, 3 * a - 1) for a in range(1, 1200)],982 [(a, 3 * a + 1) for a in range(1, 1200)],983 ]984985986def second_moment(n):987 keys = []988 for (x, y) in points(GASKET, n):989 if x and y:990 g = gcd(x, y)991 keys.append((x // g) * 3 ** n + y // g)992 keys.sort()993 total = 0994 run = 1995 for i in range(1, len(keys)):996 if keys[i] == keys[i - 1]:997 run += 1998 else:999 total += run * run1000 run = 11001 return total + run * run100210031004# THE PROVED CEILING100510061007def direction_automaton(a, b):1008 index = {0: 0}1009 order = [0]1010 edges = []1011 i = 01012 while i < len(order):1013 c = order[i]1014 out = []1015 for eps in (0, b, -a):1016 if (c + eps) % 3 == 0:1017 nxt = (c + eps) // 31018 if nxt not in index:1019 index[nxt] = len(order)1020 order.append(nxt)1021 out.append((eps, index[nxt]))1022 edges.append(out)1023 i += 11024 return order, edges102510261027def direction_live(a, b):1028 order, edges = direction_automaton(a, b)1029 back = [[] for _ in edges]1030 for i, out in enumerate(edges):1031 for (eps, j) in out:1032 back[j].append(i)1033 seen = {0}1034 stack = [0]1035 while stack:1036 x = stack.pop()1037 for y in back[x]:1038 if y not in seen:1039 seen.add(y)1040 stack.append(y)1041 keep = sorted(seen)1042 place = {v: i for i, v in enumerate(keep)}1043 return ([order[x] for x in keep],1044 [[(eps, place[j]) for (eps, j) in edges[x] if j in place]1045 for x in keep])104610471048def direction_mass(a, b, n):1049 order, edges = direction_live(a, b)1050 cur = [0] * len(edges)1051 cur[0] = 11052 out = [1]1053 for _ in range(n):1054 nxt = [0] * len(edges)1055 for i in range(len(edges)):1056 if cur[i]:1057 for (eps, j) in edges[i]:1058 nxt[j] += cur[i]1059 cur = nxt1060 out.append(cur[0])1061 return [v - 1 for v in out]106210631064def state_profile(order, edges, n):1065 tab = [[1 if order[i] == 0 else 0 for i in range(len(edges))]]1066 for _ in range(n):1067 prev = tab[-1]1068 tab.append([sum(prev[j] for (eps, j) in edges[i])1069 for i in range(len(edges))])1070 return [max(r) for r in tab]107110721073def valuation3(x):1074 k = 01075 while x % 3 == 0:1076 x //= 31077 k += 11078 return k107910801081def shift_ray(a, b):1082 lo, hi = min(a, b), max(a, b)1083 if lo != 1:1084 return False1085 while hi % 3 == 0:1086 hi //= 31087 return hi == 1108810891090def certificate_holds(a, b, den, alpha, beta):1091 order, edges = direction_live(a, b)1092 if len(order) != len(alpha) or len(order) != len(beta):1093 return False1094 if alpha[0] != den or beta[0] != 0:1095 return False1096 for i in range(len(edges)):1097 if alpha[i] < 0:1098 return False1099 if sum(alpha[j] for (eps, j) in edges[i]) > alpha[i] + beta[i]:1100 return False1101 if sum(beta[j] for (eps, j) in edges[i]) > alpha[i]:1102 return False1103 return True110411051106def binary_multiples(w, n):1107 cur = [0] * w1108 cur[0] = 11109 for i in range(n):1110 p = pow(3, i, w)1111 nxt = list(cur)1112 for r in range(w):1113 if cur[r]:1114 nxt[(r + p) % w] += cur[r]1115 cur = nxt1116 return cur[0] - 1111711181119def qnorm(p, q, d):1120 if d < 0:1121 p, q, d = -p, -q, -d1122 g = gcd(gcd(abs(p), abs(q)), d)1123 if g > 1:1124 p //= g1125 q //= g1126 d //= g1127 return (p, q, d)112811291130def qadd(x, y):1131 return qnorm(x[0] * y[2] + y[0] * x[2],1132 x[1] * y[2] + y[1] * x[2], x[2] * y[2])113311341135def qsub(x, y):1136 return qnorm(x[0] * y[2] - y[0] * x[2],1137 x[1] * y[2] - y[1] * x[2], x[2] * y[2])113811391140def qmul(x, y):1141 return qnorm(x[0] * y[0] + x[1] * y[1],1142 x[0] * y[1] + x[1] * y[0] + x[1] * y[1], x[2] * y[2])114311441145def qinv(x):1146 p, q, d = x1147 return qnorm(d * (p + q), -d * q, p * p + p * q - q * q)114811491150def qsgn(x):1151 p, q, d = x1152 hi, lo = 2 * p + q, q1153 if hi >= 0 and lo >= 0:1154 return 0 if hi == 0 and lo == 0 else 11155 if hi <= 0 and lo <= 0:1156 return 0 if hi == 0 and lo == 0 else -11157 s = hi * hi - 5 * lo * lo1158 if hi > 0:1159 return 1 if s > 0 else (0 if s == 0 else -1)1160 return -1 if s > 0 else (0 if s == 0 else 1)116111621163QZERO = (0, 0, 1)1164QONE = (1, 0, 1)1165QPHI = (0, 1, 1)1166QINVPHI = (-1, 1, 1)1167QINVPHI2 = (2, -1, 1)116811691170def golden_potential(order, edges):1171 n = len(order)1172 if n == 1:1173 return None1174 m = n - 11175 rows = [[QZERO] * (m + 1) for _ in range(m)]1176 for r in range(m):1177 rows[r][r] = QPHI1178 for (eps, j) in edges[r + 1]:1179 if j == 0:1180 rows[r][m] = qadd(rows[r][m], QONE)1181 else:1182 rows[r][j - 1] = qsub(rows[r][j - 1], QONE)1183 for col in range(m):1184 piv = None1185 for r in range(col, m):1186 if qsgn(rows[r][col]):1187 piv = r1188 break1189 if piv is None:1190 return "singular"1191 rows[col], rows[piv] = rows[piv], rows[col]1192 scale = qinv(rows[col][col])1193 rows[col] = [qmul(v, scale) if qsgn(v) else QZERO for v in rows[col]]1194 for r in range(m):1195 if r != col and qsgn(rows[r][col]):1196 f = rows[r][col]1197 rows[r] = [qsub(rows[r][k], qmul(f, rows[col][k]))1198 if qsgn(rows[col][k]) else rows[r][k]1199 for k in range(m + 1)]1200 return [QONE] + [rows[r][m] for r in range(m)]120112021203def potential_value(u, edges):1204 tot = QZERO1205 for (eps, j) in edges[0]:1206 if j:1207 tot = qadd(tot, u[j])1208 return tot120912101211def potential_valid(u, edges):1212 if u[0] != QONE or any(qsgn(v) <= 0 for v in u):1213 return False1214 for i in range(1, len(edges)):1215 s = QZERO1216 for (eps, j) in edges[i]:1217 s = qadd(s, u[j])1218 if qsgn(qsub(qmul(QPHI, u[i]), s)) < 0:1219 return False1220 return True122112221223CERTIFICATES = {1224 (1, 90): (18, [18, 0, 5, 6, 9, 2, 10, 4, 8],1225 [0, 18, 4, -4, 5, 6, 8, 1, 2]),1226 (4, 117): (40, [40, 0, 5, 14, 6, 13, 11, 27, 17, 1],1227 [0, 40, 1, -1, 5, 14, 6, 13, 11, 4]),1228 (9, 73): (381,1229 [381, 0, 46, 49, 78, 17, 101, 23, 66, 163, 39, 83, 232, 32,1230 62, 149],1231 [0, 381, 32, -32, 46, 49, 62, 16, 17, 101, 23, 66, 149, 14,1232 39, 83]),1233 (9, 82): (18, [18, 0, 5, 6, 9, 2, 10, 4, 8],1234 [0, 18, 4, -4, 5, 6, 8, 1, 2]),1235 (9, 235): (2013,1236 [2013, 0, 16, 66, 27, 55, 43, 121, 70, 176, 113, 297, 183,1237 473, 296, 770, 479, 1243, 775, 11],1238 [0, 2013, 11, -11, 16, 66, 27, 55, 43, 121, 70, 176, 113,1239 297, 183, 473, 296, 770, 479, 5]),1240 (10, 81): (18, [18, 0, 5, 6, 9, 2, 10, 4, 8],1241 [0, 18, 4, -4, 5, 6, 8, 1, 2]),1242 (13, 108): (40, [40, 0, 5, 14, 6, 13, 11, 27, 17, 1],1243 [0, 40, 1, -1, 5, 14, 6, 13, 11, 4]),1244 (27, 217): (2013,1245 [2013, 0, 16, 66, 27, 55, 43, 121, 70, 176, 113, 297, 183,1246 473, 296, 770, 479, 1243, 775, 11],1247 [0, 2013, 11, -11, 16, 66, 27, 55, 43, 121, 70, 176, 113,1248 297, 183, 473, 296, 770, 479, 5]),1249 (27, 226): (34, [34, 0, 0, 1, 1, 0, 1, 1, 2, 1, 3, 5, 8, 13, 20, 1],1250 [0, 34, 1, -1, 0, 1, 1, 0, 1, 1, 2, 3, 5, 8, 14, -1]),1251}125212531254def census_ceiling():1255 tested = mismatch = 01256 for a in range(1, 40):1257 for b in range(1, 40):1258 if gcd(a, b) != 1:1259 continue1260 tested += 11261 if direction_mass(a, b, 20) != witness_mass(a, b, 20):1262 mismatch += 11263 check("direction carry automaton against the gasket-digit automaton",1264 (tested, mismatch), (947, 0))1265 box = occupied = bounded = single = k1 = nodouble = 01266 twoplus = anomaly = states = fails = 01267 residue = certified = passing = 01268 best = QZERO1269 attain = []1270 over = []1271 exceptions = []1272 for z1 in range(1, 121):1273 for z2 in range(z1, 241):1274 if gcd(z1, z2) != 1:1275 continue1276 box += 11277 d = (z1 % 3 == 0) + (z2 % 3 == 0) + ((z1 + z2) % 3 == 0)1278 if d > 1:1279 twoplus += 11280 if z1 % 3 and z2 % 3:1281 residue += 11282 order, edges = direction_live(z1, z2)1283 if len(order) == 1:1284 continue1285 if d == 0:1286 anomaly += 11287 occupied += 11288 states = max(states, len(order))1289 if all(-z1 <= 2 * c <= z2 for c in order):1290 bounded += 11291 degs = [len(o) for o in edges]1292 classes = set(order[i] % 3 for i in range(len(order))1293 if degs[i] == 2)1294 if max(degs) <= 2 and len(classes) <= 1:1295 single += 11296 q = z1 if z1 % 3 == 0 else (z2 if z2 % 3 == 0 else z1 + z2)1297 if valuation3(q) == 1:1298 k1 += 11299 if not any(degs[i] == 21300 and all(degs[j] == 2 for (eps, j) in edges[i])1301 for i in range(len(edges))):1302 nodouble += 11303 else:1304 exceptions.append((z1, z2))1305 h = state_profile(order, edges, 20)1306 if any(h[m] > h[m - 1] + h[m - 2] for m in range(2, 21)):1307 fails += 11308 u = golden_potential(order, edges)1309 if potential_valid(u, edges):1310 certified += 11311 tot = potential_value(u, edges)1312 if qsgn(qsub(QINVPHI2, tot)) >= 0:1313 passing += 11314 if qsgn(qsub(tot, best)) > 0:1315 best, attain = tot, [(z1, z2)]1316 elif tot == best:1317 attain.append((z1, z2))1318 else:1319 over.append((z1, z2, tot))1320 check("carry states of a direction lie in [-a/2, b/2]",1321 (box, occupied, states, bounded), (13158, 218, 37, 218))1322 check("out-degree at most two, branch states in one class mod 3",1323 single, 218)1324 check("at most one of z1, z2, z1+z2 is divisible by three, and no "1325 "occupied direction has none of them divisible",1326 (twoplus, anomaly), (0, 0))1327 check("three divides a coordinate of every occupied direction",1328 (residue, box - residue), (6566, 6592))1329 check("the golden potential certifies the box away from the shift rays",1330 (certified, passing, [(z1, z2) for (z1, z2, tot) in over],1331 sorted(set(tot for (z1, z2, tot) in over))),1332 (218, 214, [(1, 3), (1, 9), (1, 27), (1, 81)], [QINVPHI]))1333 check("the golden potential is largest on the supergolden directions",1334 (best, attain), (QINVPHI2, [(1, 12), (3, 10), (4, 9)]))1335 check("directions settled by the branch argument over the box",1336 (k1, nodouble, exceptions),1337 (107, 206, [(1, 9), (1, 27), (1, 81), (1, 90), (4, 117), (9, 73),1338 (9, 82), (9, 235), (10, 81), (13, 108), (27, 217),1339 (27, 226)]))1340 good = sorted(k for k, v in CERTIFICATES.items()1341 if certificate_holds(k[0], k[1], v[0], v[1], v[2]))1342 check("Fibonacci certificates for the nine non-shift exceptions",1343 good, [(1, 90), (4, 117), (9, 73), (9, 82), (9, 235), (10, 81),1344 (13, 108), (27, 217), (27, 226)])1345 identity = all(fibonacci(p + 2) * fibonacci(q + 2)1346 == fibonacci(p + q + 3) - fibonacci(p + 1) * fibonacci(q + 1)1347 for p in range(60) for q in range(60))1348 cases = 01349 over = 01350 strict = 01351 for j in range(1, 14):1352 for m in range(1, 46):1353 cases += 11354 if shift_product(j, m) > fibonacci(m + 1):1355 over += 11356 if j >= 2 and m >= 2 and shift_product(j, m) >= fibonacci(m + 1):1357 strict += 11358 check("Fibonacci product identity and the shift-ray ceiling",1359 (identity, cases, over, strict), (True, 585, 0, 0))1360 tested = breaches = 01361 for z1 in range(1, 31):1362 for z2 in range(z1, 61):1363 if gcd(z1, z2) != 1:1364 continue1365 tested += 11366 if direction_mass(z1, z2, 12)[12] > binary_multiples(z1 + z2, 12):1367 breaches += 11368 check("ray mass is at most the count of binary multiples of the weight",1369 (tested, breaches), (829, 0))1370 check("binary multiples of the weight outgrow the ceiling",1371 ([binary_multiples(w, 24) for w in (4, 10, 28, 82)],1372 fibonacci(25) - 1),1373 ([4196351, 1683971, 613817, 228519], 75024))1374 seen = set()1375 multiplicity = 01376 for family in ceiling_families():1377 for (a, b) in family:1378 if gcd(a, b) != 1:1379 continue1380 multiplicity += 11381 seen.add((a, b))1382 inside = set((a, b) for (a, b) in seen if a <= 120 and b <= 240)1383 zero = case2 = case3 = enumonly = 01384 outside = outpass = 01385 outover = []1386 for (a, b) in sorted(seen - inside):1387 order, edges = direction_live(a, b)1388 if len(order) == 1:1389 zero += 11390 continue1391 outside += 11392 u = golden_potential(order, edges)1393 if potential_valid(u, edges) and \1394 qsgn(qsub(QINVPHI2, potential_value(u, edges))) >= 0:1395 outpass += 11396 else:1397 outover.append((a, b, potential_value(u, edges)))1398 degs = [len(o) for o in edges]1399 if not any(degs[i] == 21400 and all(degs[j] == 2 for (eps, j) in edges[i])1401 for i in range(len(edges))):1402 case2 += 11403 elif shift_ray(a, b):1404 case3 += 11405 else:1406 enumonly += 11407 check("the six adversarial families overlap, and their union splits",1408 (multiplicity, len(seen), len(inside), len(seen) - len(inside),1409 zero, case2, case3, enumonly),1410 (11369, 10862, 717, 10145, 9498, 608, 3, 36))1411 check("the golden potential outside the box, shift rays excepted",1412 (outside, outpass, outover),1413 (647, 644, [(1, 243, QINVPHI), (1, 729, QINVPHI),1414 (1, 2187, QINVPHI)]))1415 profile = state_profile(*direction_live(1, 9), 12)1416 check("the state maximum breaks the Fibonacci recursion",1417 (profile[:7], profile[4] > profile[3] + profile[2], fails),1418 ([1, 1, 1, 2, 4, 6, 9], True, 8))1419 print("golden ceiling: proved for every direction of the box")142014211422def split_three(a, b):1423 q, p = (a, b) if a % 3 == 0 else (b, a)1424 k = 01425 q1 = q1426 while q1 % 3 == 0:1427 q1 //= 31428 k += 11429 return p, k, q1143014311432def phi_power(e):1433 r = QONE1434 for _ in range(abs(e)):1435 r = qmul(r, QPHI if e > 0 else QINVPHI)1436 return r143714381439def first_returns(edges, n):1440 cur = [0] * len(edges)1441 cur[0] = 11442 f = [0] * (n + 1)1443 for m in range(1, n + 1):1444 nxt = [0] * len(edges)1445 for i in range(len(edges)):1446 if cur[i]:1447 for (eps, j) in edges[i]:1448 nxt[j] += cur[i]1449 f[m] = nxt[0]1450 nxt[0] = 01451 cur = nxt1452 return f145314541455def degree_potential(edges):1456 return [QONE if len(o) == 2 else QINVPHI for o in edges]145714581459def super_solution(edges, pi):1460 for i in range(1, len(edges)):1461 s = QZERO1462 for (eps, j) in edges[i]:1463 s = qadd(s, pi[j])1464 if qsgn(qsub(qmul(QPHI, pi[i]), s)) < 0:1465 return False1466 return True146714681469def swept_bound(edges, d):1470 pi = degree_potential(edges)1471 pi[0] = QONE1472 for _ in range(d):1473 nxt = [QONE] + [QZERO] * (len(edges) - 1)1474 for i in range(1, len(edges)):1475 s = QZERO1476 for (eps, j) in edges[i]:1477 s = qadd(s, pi[j])1478 nxt[i] = qmul(QINVPHI, s)1479 pi = nxt1480 tot = QZERO1481 for (eps, j) in edges[0]:1482 if j:1483 tot = qadd(tot, pi[j])1484 return tot148514861487def burst_floor(a, b, k, q1):1488 sign = 1 if b % 3 == 0 else -11489 out = []1490 for m in range(1, 3 ** k):1491 if m % 3 != 1:1492 continue1493 x, ok = m, True1494 while x:1495 if x % 3 > 1:1496 ok = False1497 break1498 x //= 31499 if ok:1500 out.append(sign * q1 * m)1501 return out150215031504def census_degree():1505 seen = set()1506 for z1 in range(1, 121):1507 for z2 in range(z1, 241):1508 if gcd(z1, z2) == 1:1509 seen.add((z1, z2))1510 for family in ceiling_families():1511 for (a, b) in family:1512 if gcd(a, b) == 1:1513 seen.add((a, b))1514 triple = eligible = occupied = resbad = burst = 01515 burstbad = 01516 nodouble = valid = doublevalid = settled = 01517 depths = {}1518 missed = []1519 k1 = k1class = 01520 k1bad = []1521 quantbad = []1522 attain = []1523 for (a, b) in sorted(seen):1524 p, k, q1 = split_three(a, b)1525 if a % 3 == 0 or b % 3 == 0:1526 triple += 11527 if (q1 - p) % 3 == 0:1528 eligible += 11529 order, edges = direction_live(a, b)1530 if len(order) == 1:1531 continue1532 occupied += 11533 if (q1 - p) % 3:1534 resbad += 11535 f = first_returns(edges, max(k, 2))1536 if any(f[j] for j in range(2, k + 1)):1537 burst += 11538 degs = [len(o) for o in edges]1539 double = any(degs[i] == 21540 and all(degs[j] == 2 for (eps, j) in edges[i])1541 for i in range(len(edges)))1542 if not double:1543 nodouble += 11544 if super_solution(edges, degree_potential(edges)):1545 valid += 11546 if double:1547 doublevalid += 11548 hit = None1549 for d in range(25):1550 if qsgn(qsub(QINVPHI2, swept_bound(edges, d))) >= 0:1551 hit = d1552 break1553 if hit is None:1554 missed.append((a, b))1555 else:1556 settled += 11557 depths[hit] = depths.get(hit, 0) + 11558 else:1559 missed.append((a, b))1560 u = golden_potential(order, edges)1561 tot = potential_value(u, edges)1562 place = {c: i for i, c in enumerate(order)}1563 floor = burst_floor(a, b, k, q1)1564 rung = QZERO1565 for c in floor:1566 if c in place:1567 rung = qadd(rung, u[place[c]])1568 if len(floor) != 2 ** (k - 1) or \1569 qmul(phi_power(-(k - 1)), rung) != tot:1570 burstbad += 11571 if 2 * p <= 3 ** k * q1:1572 c = p if b % 3 == 0 else -p1573 if c not in place or u[place[c]] != QINVPHI:1574 burstbad += 11575 if k != 1:1576 continue1577 k1 += 11578 if q1 == p:1579 continue1580 t = valuation3(q1 - p)1581 bound = qmul(QINVPHI, qsub(QONE, phi_power(-max(t, 2))))1582 if qsgn(qsub(bound, tot)) < 0:1583 quantbad.append((a, b, t))1584 if tot == bound:1585 attain.append((a, b))1586 if t <= 2:1587 k1class += 11588 if qsgn(qsub(QINVPHI2, tot)) < 0:1589 k1bad.append((a, b, t))1590 check("occupancy needs the residue match q1 = p mod 3",1591 (len(seen), triple, eligible, occupied, resbad),1592 (23303, 11691, 7103, 865, 0))1593 check("no first return has length between two and v3(q)",1594 (occupied, burst), (865, 0))1595 check("the burst identity and the value at the near predecessor",1596 (occupied, burstbad), (865, 0))1597 check("the degree potential is a super-solution beyond the branch case",1598 (nodouble, valid, doublevalid), (814, 851, 37))1599 check("the swept degree potential settles all but sixteen directions",1600 (settled, sorted(depths.items()),1601 [z for z in missed if shift_ray(*z)],1602 [z for z in missed if not shift_ray(*z)]),1603 (849, [(1, 760), (3, 48), (4, 31), (5, 7), (6, 3)],1604 [(1, 3 ** j) for j in range(1, 8)],1605 [(1, 756), (1, 2196), (1, 2214), (1, 2268), (1, 2430),1606 (13, 1080), (27, 730), (28, 729), (40, 1053)]))1607 check("the golden partition bound at v3(q) = 1",1608 (k1, k1class, k1bad, quantbad, attain),1609 (360, 261, [], [], [(1, 12), (3, 10)]))1610 short = []1611 for a in range(1, 130):1612 for b in range(1, 800):1613 if gcd(a, b) != 1 or (a % 3 and b % 3):1614 continue1615 order, edges = direction_live(a, b)1616 if len(order) == 1:1617 continue1618 f = first_returns(edges, 4)1619 if f[2] or f[3]:1620 short.append((min(a, b), max(a, b), f[2], f[3]))1621 check("the first returns of length two and three are classified",1622 sorted(set(short)),1623 [(1, 3, 1, 0), (1, 9, 0, 1), (1, 12, 0, 1), (3, 10, 0, 1),1624 (4, 9, 0, 1)])1625 print("degree potential: the golden partition bound on an infinite class")162616271628def census_witness():1629 n = 91630 bad = 01631 tested = 01632 for s in range(1, 40):1633 for t in range(s + 1, 40):1634 if gcd(s, t) != 1:1635 continue1636 tested += 11637 if free_returns(free_automaton(s, t), n) != tensor_returns(s, t, n):1638 bad += 11639 check("tensor square against B(s,t) return counts to n=9", (tested, bad), (473, 0))1640 check("carry states against reachable B states",1641 [(len(carry_live(carry_automaton(s, t))), len(free_automaton(s, t)))1642 for (s, t) in ((365, 1094), (41, 122), (25, 52), (31, 40))],1643 [(729, 26931), (81, 835), (38, 393), (35, 354)])1644 bad = 01645 tested = 01646 for s in range(1, 30):1647 for t in range(s + 1, 60):1648 if gcd(s, t) != 1:1649 continue1650 tested += 11651 want = free_returns(free_automaton(s, t), n)[n] - 1 - 2 * fibre_count(s, t, n)1652 if want != box_count(s, t, n):1653 bad += 11654 check("box construction against B(s,t) at n=9", (tested, bad), (812, 0))1655 got = []1656 for (s, t) in ((365, 1094), (41, 122), (122, 123), (1, 2460), (2431, 2458)):1657 for m in (9, 12):1658 got.append((tensor_returns(s, t, m)[m] - 1 - 2 * fibre_count(s, t, m),1659 box_count(s, t, m)))1660 check("box against tensor at large multipliers",1661 ([x for x in got if x[0] != x[1]], [g[0] for g in got]),1662 ([], [2, 180, 50, 172, 50, 172, 2, 12, 2, 8]))1663 R, weight, pair = {}, {}, {}1664 for m in range(4, 14):1665 R[m], weight[m], pair[m] = residual_layers(m)1666 check("R(n) for n=4..13", [R[m] for m in range(4, 14)],1667 [20, 88, 432, 1624, 5512, 15896, 46064, 124928, 335704, 863848])1668 scaled = bad = 01669 for m in range(5, 14):1670 for w, c in weight[m].items():1671 if w % 3 == 0:1672 scaled += 11673 if weight[m - 1].get(w // 3, 0) != c:1674 bad += 11675 check("weight layers scale as R_3w(n) = R_w(n-1)", (scaled, bad), (1869, 0))1676 check("no witness of weight below four",1677 sorted(set(min(weight[m]) for m in range(4, 14))), [4])1678 check("largest multiplier is floor(3^n/8) for n = 4..13",1679 [max(k[1] for k in pair[m]) for m in range(4, 14)],1680 [3 ** m // 8 for m in range(4, 14)])1681 fours = []1682 sets = []1683 for m in range(4, 13):1684 F = no_adjacent(m)1685 sets.append(len(F))1686 fours.append(2 * sum(1 for a in F for b in F if a != b1687 and gcd(a, b) == 1 and not three_power_ratio(a, b)))1688 check("R_4(n) from the no-adjacent-ones set, n=4..12",1689 (fours, [weight[m].get(4, 0) for m in range(4, 13)]),1690 ([12, 36, 108, 336, 988, 2596, 6672, 17480, 45720],1691 [12, 36, 108, 336, 988, 2596, 6672, 17480, 45720]))1692 check("size of the no-adjacent-ones set is Fibonacci",1693 sets, [fibonacci(m + 1) - 1 for m in range(4, 13)])1694 check("weight-four orbit at n=13", sum(fours) + weight[13].get(4, 0), 194096)1695 tops = []1696 for m in range(6, 14):1697 thr = (3 ** m - 1) // 101698 big = [v for k, v in pair[m].items() if k[1] > thr]1699 tops.append((len(big), sorted(set(big))))1700 check("pairs above (3^n-1)/10 each contribute exactly four",1701 tops, [(18, [4]), (57, [4]), (163, [4]), (402, [4]), (1019, [4]),1702 (2702, [4]), (7060, [4]), (18607, [4])])1703 N = 401704 ref = [fibonacci(m + 1) - 1 for m in range(N + 1)]1705 check("heaviest ray mass is Fibonacci", witness_mass(1, 3, N), ref)1706 tested = breaches = 01707 ties = []1708 for z1 in range(1, 121):1709 for z2 in range(z1, 241):1710 if gcd(z1, z2) != 1:1711 continue1712 tested += 11713 m = witness_mass(z1, z2, N)1714 for j in range(1, N + 1):1715 if m[j] > ref[j]:1716 breaches += 11717 if m[N] == ref[N]:1718 ties.append((z1, z2))1719 check("golden ceiling on ray mass over the box, n <= 40",1720 (tested, breaches, ties), (13158, 0, [(1, 3)]))1721 N = 451722 ref = [fibonacci(m + 1) - 1 for m in range(N + 1)]1723 sizes = []1724 breaches = 01725 for family in ceiling_families():1726 seen = 01727 for (a, b) in family:1728 if gcd(a, b) != 1:1729 continue1730 seen += 11731 m = witness_mass(a, b, N)1732 for j in range(1, N + 1):1733 if m[j] > ref[j]:1734 breaches += 11735 sizes.append(seen)1736 check("golden ceiling on six adversarial families, n <= 45",1737 (sizes, sum(sizes), breaches),1738 ([4221, 253, 2998, 1499, 1199, 1199], 11369, 0))1739 moments = []1740 for m in (13, 14):1741 E = second_moment(m)1742 D = 3 ** m - 2 ** (m + 1) + 11743 S = 3 ** m - 4 * 2 ** m + 2 * m + 31744 moments.append((E, E - D - S))1745 check("second moment and residual at n = 13 and 14",1746 moments, [(4003372, 863848), (11679626, 2211960)])1747 print("witness weights: weight four is Fibonacci and the ceiling holds")174817491750def free_returns(edges, n):1751 count = [0] * len(edges)1752 count[0] = 11753 out = []1754 for _ in range(n + 1):1755 out.append(count[0])1756 nxt = [0] * len(edges)1757 for i, outs in enumerate(edges):1758 for e in outs:1759 nxt[e] += count[i]1760 count = nxt1761 return out176217631764def main():1765 census_designs()1766 census_cross()1767 census_diagonality()1768 census_masses()1769 census_recurrences()1770 census_shift()1771 census_rays()1772 census_pairs()1773 census_spectrum()1774 census_witness()1775 census_ceiling()1776 census_degree()1777 print("all green")177817791780if __name__ == "__main__":1781 main()