witness.py
40.2 kB · python · 1311 lines
1from math import gcd23GASKET = ((0, 0), (1, 0), (0, 1))4FIB = [0, 1, 1]5while len(FIB) < 200:6 FIB.append(FIB[-1] + FIB[-2])789def die(name, got, want):10 raise SystemExit("FAIL %s\n got %r\n want %r" % (name, got, want))111213def check(name, got, want):14 if got != want:15 die(name, got, want)16 print("ok %s" % name)171819# GASKET POINTS2021def points(n):22 pts = [(0, 0)]23 for _ in range(n):24 nxt = []25 for (x, y) in pts:26 for (dx, dy) in GASKET:27 nxt.append((3 * x + dx, 3 * y + dy))28 pts = nxt29 return pts303132def in_gasket(x, y, n):33 if x >= 3 ** n or y >= 3 ** n:34 return False35 while x or y:36 a, b = x % 3, y % 337 if a > 1 or b > 1 or (a and b):38 return False39 x //= 340 y //= 341 return True424344def is_shift_pair(s, t):45 if min(s, t) != 1:46 return False47 u = max(s, t)48 while u % 3 == 0:49 u //= 350 return u == 1515253# CARRY AUTOMATON5455def carry(s, t):56 index = {(0, 0): 0}57 order = [(0, 0)]58 edges = []59 i = 060 while i < len(order):61 a, b = order[i]62 out = []63 for d in range(3):64 u = (s * d + a) % 365 v = (t * d + b) % 366 if u < 2 and v < 2:67 nxt = ((s * d + a) // 3, (t * d + b) // 3)68 if nxt not in index:69 index[nxt] = len(order)70 order.append(nxt)71 out.append((u, v, index[nxt]))72 edges.append(out)73 i += 174 return edges757677def carry_live(edges):78 back = [[] for _ in edges]79 for i, out in enumerate(edges):80 for (u, v, j) in out:81 back[j].append(i)82 seen = {0}83 stack = [0]84 while stack:85 x = stack.pop()86 for y in back[x]:87 if y not in seen:88 seen.add(y)89 stack.append(y)90 keep = sorted(seen)91 place = {v: i for i, v in enumerate(keep)}92 return [[(u, v, place[j]) for (u, v, j) in edges[x] if j in place] for x in keep]939495def ray_mass(z1, z2, n):96 e = carry_live(carry(z1, z2))97 k = len(e)98 adj = [[j for (u, v, j) in e[i] if not (u and v)] for i in range(k)]99 cur = [0] * k100 cur[0] = 1101 out = [1]102 for _ in range(n):103 nxt = [0] * k104 for i in range(k):105 c = cur[i]106 if c:107 for j in adj[i]:108 nxt[j] += c109 cur = nxt110 out.append(cur[0])111 return [v - 1 for v in out]112113114# FREE AUTOMATON AS A CONSTRAINED TENSOR SQUARE115116def free_automaton(s, t):117 index = {(0, 0, 0, 0): 0}118 order = [(0, 0, 0, 0)]119 edges = []120 i = 0121 while i < len(order):122 a1, a2, b1, b2 = order[i]123 out = []124 for dx in range(3):125 for dy in range(3):126 u = ((s * dx + a1) % 3, (s * dy + a2) % 3)127 v = ((t * dx + b1) % 3, (t * dy + b2) % 3)128 if u in GASKET and v in GASKET:129 nxt = ((s * dx + a1) // 3, (s * dy + a2) // 3,130 (t * dx + b1) // 3, (t * dy + b2) // 3)131 if nxt not in index:132 index[nxt] = len(order)133 order.append(nxt)134 out.append(index[nxt])135 edges.append(out)136 i += 1137 return edges138139140def free_returns(edges, n):141 count = [0] * len(edges)142 count[0] = 1143 out = []144 for _ in range(n + 1):145 out.append(count[0])146 nxt = [0] * len(edges)147 for i, outs in enumerate(edges):148 for e in outs:149 nxt[e] += count[i]150 count = nxt151 return out152153154def tensor_returns(s, t, n):155 e = carry_live(carry(s, t))156 k = len(e)157 S = [[j for (u, v, j) in e[i]] for i in range(k)]158 U = [[j for (u, v, j) in e[i] if u] for i in range(k)]159 V = [[j for (u, v, j) in e[i] if v] for i in range(k)]160 W = [[j for (u, v, j) in e[i] if u and v] for i in range(k)]161 X = [[0] * k for _ in range(k)]162 X[0][0] = 1163 out = [1]164 for _ in range(n):165 half = []166 for A in (S, U, V, W):167 Y = []168 for i in range(k):169 acc = [0] * k170 for p in A[i]:171 xp = X[p]172 for q in range(k):173 if xp[q]:174 acc[q] += xp[q]175 Y.append(acc)176 half.append(Y)177 Ys, Yu, Yv, Yw = half178 Z = [[0] * k for _ in range(k)]179 for j in range(k):180 for i in range(k):181 a = 0182 for q in S[j]:183 a += Ys[i][q]184 for q in U[j]:185 a -= Yu[i][q]186 for q in V[j]:187 a -= Yv[i][q]188 for q in W[j]:189 a += Yw[i][q]190 Z[i][j] = a191 X = Z192 out.append(X[0][0])193 return out194195196# BOX CONSTRUCTION197198def box(s, t, n):199 W = (3 ** n - 1) // (2 * max(s, t))200 c = 0201 for z1 in range(1, W):202 for z2 in range(1, W - z1 + 1):203 if in_gasket(s * z1, s * z2, n) and in_gasket(t * z1, t * z2, n):204 c += 1205 return c206207208def fibre(s, t, n):209 binary = set(sum(((u >> i) & 1) * 3 ** i for i in range(n)) for u in range(2 ** n))210 return sum(1 for u in range(1, 3 ** n // t + 1) if s * u in binary and t * u in binary)211212213# CENSUS214215def ray_census(n):216 by = {}217 for (x, y) in points(n):218 if x and y:219 g = gcd(x, y)220 by.setdefault((x // g, y // g), []).append(g)221 return by222223224def residual(n):225 by = ray_census(n)226 R = 0227 weight = {}228 pair = {}229 for r, gs in by.items():230 if len(gs) < 2:231 continue232 rs = r[0] + r[1]233 for a in gs:234 for b in gs:235 if a == b:236 continue237 d = gcd(a, b)238 if is_shift_pair(a // d, b // d):239 continue240 R += 1241 weight[d * rs] = weight.get(d * rs, 0) + 1242 k = (a // d, b // d)243 if k[0] > k[1]:244 k = (k[1], k[0])245 pair[k] = pair.get(k, 0) + 1246 return R, weight, pair, by247248249def no_adjacent(n):250 out = []251 for m in range(1, 3 ** (n - 1)):252 x, prev, ok = m, 0, True253 while x:254 d = x % 3255 if d > 1 or (d and prev):256 ok = False257 break258 prev, x = d, x // 3259 if ok:260 out.append(m)261 return out262263264def is_three_power_ratio(a, b):265 if a > b:266 a, b = b, a267 if b % a:268 return False269 q = b // a270 while q % 3 == 0:271 q //= 3272 return q == 1273274275# LAW 1: THE CONSTRAINED TENSOR SQUARE276277def law_tensor():278 bad = 0279 n = 9280 tested = 0281 for s in range(1, 40):282 for t in range(s + 1, 40):283 if gcd(s, t) != 1:284 continue285 tested += 1286 if free_returns(free_automaton(s, t), n) != tensor_returns(s, t, n):287 bad += 1288 check("tensor square reproduces B(s,t) return counts, n <= 9", (tested, bad), (473, 0))289 sizes = []290 for (s, t) in ((365, 1094), (41, 122), (25, 52), (31, 40)):291 sizes.append((len(carry_live(carry(s, t))), len(free_automaton(s, t))))292 check("carry states against reachable B states",293 sizes, [(729, 26931), (81, 835), (38, 393), (35, 354)])294295296# LAW 2: THE BOX CONSTRUCTION297298def law_box():299 n = 9300 bad = 0301 tested = 0302 for s in range(1, 30):303 for t in range(s + 1, 60):304 if gcd(s, t) != 1:305 continue306 tested += 1307 want = free_returns(free_automaton(s, t), n)[n] - 1 - 2 * fibre(s, t, n)308 if want != box(s, t, n):309 bad += 1310 check("box construction against B(s,t), n = 9", (tested, bad), (812, 0))311 got = []312 for (s, t) in ((365, 1094), (41, 122), (122, 123), (1, 2460), (2431, 2458)):313 for n in (9, 12):314 a = tensor_returns(s, t, n)[n] - 1 - 2 * fibre(s, t, n)315 got.append((a, box(s, t, n)))316 check("box against tensor at large multipliers, n = 9 and 12",317 [x for x in got if x[0] != x[1]], [])318 check("witness counts at the four extreme pairs, n = 12",319 [g[0] for g in got], [2, 180, 50, 172, 50, 172, 2, 12, 2, 8])320321322# LAW 3: WEIGHT FOUR AND THE GOLDEN CEILING323324def law_weights():325 R = {}326 weight = {}327 pair = {}328 for n in range(4, 14):329 R[n], weight[n], pair[n], _ = residual(n)330 check("R(n) for n = 4..13", [R[n] for n in range(4, 14)],331 [20, 88, 432, 1624, 5512, 15896, 46064, 124928, 335704, 863848])332 scaled = 0333 bad = 0334 for n in range(5, 14):335 for w, c in weight[n].items():336 if w % 3 == 0:337 scaled += 1338 if weight[n - 1].get(w // 3, 0) != c:339 bad += 1340 check("R_{3w}(n) = R_w(n-1)", (scaled, bad), (1869, 0))341 check("no witness of weight below four",342 sorted(set(min(weight[n]) for n in range(4, 14))), [4])343 fours = []344 ok = True345 for n in range(4, 13):346 F = no_adjacent(n)347 if len(F) != FIB[n + 1] - 1:348 ok = False349 c = sum(1 for a in F for b in F350 if a != b and gcd(a, b) == 1 and not is_three_power_ratio(a, b))351 fours.append(2 * c)352 if 2 * c != weight[n].get(4, 0):353 ok = False354 check("R_4(n) counted by the no-adjacent-ones set, n = 4..12",355 (ok, fours), (True, [12, 36, 108, 336, 988, 2596, 6672,356 17480, 45720]))357 check("|F_n| = Fib(n+1) - 1 for n = 4..12",358 [len(no_adjacent(n)) for n in range(4, 13)],359 [FIB[n + 1] - 1 for n in range(4, 13)])360 tops = []361 for n in range(6, 14):362 thr = (3 ** n - 1) // 10363 big = [v for k, v in pair[n].items() if k[1] > thr]364 tops.append((len(big), sorted(set(big))))365 check("every pair above (3^n-1)/10 contributes exactly 4, n = 6..13",366 tops, [(18, [4]), (57, [4]), (163, [4]), (402, [4]), (1019, [4]),367 (2702, [4]), (7060, [4]), (18607, [4])])368 check("heaviest ray mass is Fib(n+1) - 1, n = 1..40",369 ray_mass(1, 3, 40), [FIB[n + 1] - 1 for n in range(41)])370 N = 40371 ref = [FIB[n + 1] - 1 for n in range(N + 1)]372 tested = 0373 breaches = 0374 ties = []375 for z1 in range(1, 121):376 for z2 in range(z1, 241):377 if gcd(z1, z2) != 1:378 continue379 tested += 1380 m = ray_mass(z1, z2, N)381 for n in range(1, N + 1):382 if m[n] > ref[n]:383 breaches += 1384 if m[N] == ref[N]:385 ties.append((z1, z2))386 check("golden ceiling M_n(z) <= Fib(n+1) - 1 over the box, n <= 40",387 (tested, breaches, ties), (13158, 0, [(1, 3)]))388 N = 45389 ref = [FIB[m + 1] - 1 for m in range(N + 1)]390 bin3 = [sum(((u >> i) & 1) * 3 ** i for i in range(8)) for u in range(1, 256)]391 nad = no_adjacent(8)392 families = [393 [(a, b) for a in bin3 for b in bin3 if a < b],394 [(a, b) for a in nad for b in nad if a < b],395 [(1, t) for t in range(2, 3000)],396 [(a, a + 1) for a in range(1, 1500)],397 [(a, 3 * a - 1) for a in range(1, 1200)],398 [(a, 3 * a + 1) for a in range(1, 1200)],399 ]400 sizes = []401 breaches = 0402 for family in families:403 seen = 0404 for (a, b) in family:405 if gcd(a, b) != 1:406 continue407 seen += 1408 m = ray_mass(a, b, N)409 for j in range(1, N + 1):410 if m[j] > ref[j]:411 breaches += 1412 sizes.append(seen)413 check("golden ceiling on six adversarial families, n <= 45",414 (sizes, sum(sizes), breaches),415 ([16940, 253, 2998, 1499, 1199, 1199], 24088, 0))416417418# LAW 4: THE RESIDUAL TO LEVEL 17419420def law_deep():421 import numpy as np422 E, R = [], []423 for n in range(13, 18):424 X = np.zeros(1, dtype=np.int32)425 Y = np.zeros(1, dtype=np.int32)426 for _ in range(n):427 X = np.concatenate((3 * X, 3 * X + 1, 3 * X))428 Y = np.concatenate((3 * Y, 3 * Y, 3 * Y + 1))429 keep = (X > 0) & (Y > 0)430 X, Y = X[keep], Y[keep]431 del keep432 g = np.gcd(X, Y)433 X //= g434 Y //= g435 del g436 key = X.astype(np.int64)437 key *= 3 ** n438 key += Y439 del X, Y440 key.sort()441 idx = np.flatnonzero(np.concatenate(([True], key[1:] != key[:-1])))442 m = np.diff(np.concatenate((idx, [len(key)]))).astype(np.int64)443 e = int((m * m).sum())444 del key, idx, m445 E.append(e)446 R.append(e - (3 ** n - 2 ** (n + 1) + 1) - (3 ** n - 4 * 2 ** n + 2 * n + 3))447 check("E(n) for n = 13..17", E,448 [4003372, 11679626, 34050692, 99800950, 292848756])449 check("R(n) for n = 13..17", R,450 [863848, 2211960, 5549452, 14100688, 35354824])451 peak3 = max(range(len(R)), key=lambda i: R[i] / 3 ** (13 + i))452 fall3 = all(R[i + 1] / 3 ** (14 + i) < R[i] / 3 ** (13 + i) for i in range(4))453 p = (3 + 5 ** 0.5) / 2454 fallp = all(R[i + 1] / p ** (14 + i) < R[i] / p ** (13 + i) for i in range(4))455 check("R/3^n and R/phi^2n both fall through n = 17",456 (peak3, fall3, fallp), (0, True, True))457 check("R(17)/3^17 and R(17)/phi^34 truncated down",458 (int(R[4] / 3 ** 17 * 10 ** 7), int(R[4] / p ** 17 * 10 ** 7)),459 (2737709, 27724831))460461462# LAW 5: THE CEILING AS A THEOREM463464465def direction_automaton(a, b):466 index = {0: 0}467 order = [0]468 edges = []469 i = 0470 while i < len(order):471 c = order[i]472 out = []473 for eps in (0, b, -a):474 if (c + eps) % 3 == 0:475 nxt = (c + eps) // 3476 if nxt not in index:477 index[nxt] = len(order)478 order.append(nxt)479 out.append((eps, index[nxt]))480 edges.append(out)481 i += 1482 return order, edges483484485def direction_live(a, b):486 order, edges = direction_automaton(a, b)487 back = [[] for _ in edges]488 for i, out in enumerate(edges):489 for (eps, j) in out:490 back[j].append(i)491 seen = {0}492 stack = [0]493 while stack:494 x = stack.pop()495 for y in back[x]:496 if y not in seen:497 seen.add(y)498 stack.append(y)499 keep = sorted(seen)500 place = {v: i for i, v in enumerate(keep)}501 return ([order[x] for x in keep],502 [[(eps, place[j]) for (eps, j) in edges[x] if j in place]503 for x in keep])504505506def direction_mass(a, b, n):507 order, edges = direction_live(a, b)508 cur = [0] * len(edges)509 cur[0] = 1510 out = [1]511 for _ in range(n):512 nxt = [0] * len(edges)513 for i in range(len(edges)):514 if cur[i]:515 for (eps, j) in edges[i]:516 nxt[j] += cur[i]517 cur = nxt518 out.append(cur[0])519 return [v - 1 for v in out]520521522def state_profile(a, b, n):523 order, edges = direction_live(a, b)524 tab = [[1 if order[i] == 0 else 0 for i in range(len(edges))]]525 for _ in range(n):526 prev = tab[-1]527 tab.append([sum(prev[j] for (eps, j) in edges[i])528 for i in range(len(edges))])529 return [max(r) for r in tab]530531532def first_returns(a, b, n):533 order, edges = direction_live(a, b)534 cur = [0] * len(edges)535 cur[0] = 1536 f = [0] * (n + 1)537 for m in range(1, n + 1):538 nxt = [0] * len(edges)539 for i in range(len(edges)):540 if cur[i]:541 for (eps, j) in edges[i]:542 nxt[j] += cur[i]543 f[m] = nxt[0]544 nxt[0] = 0545 cur = nxt546 return f547548549def valuation3(x):550 k = 0551 while x % 3 == 0:552 x //= 3553 k += 1554 return k555556557def double_branch(order, edges):558 degs = [len(o) for o in edges]559 return any(degs[i] == 2 and all(degs[j] == 2 for (eps, j) in edges[i])560 for i in range(len(edges)))561562563def is_shift_ray(a, b):564 lo, hi = min(a, b), max(a, b)565 if lo != 1:566 return False567 while hi % 3 == 0:568 hi //= 3569 return hi == 1570571572def certificate_holds(a, b, den, alpha, beta):573 order, edges = direction_live(a, b)574 if len(order) != len(alpha) or len(order) != len(beta):575 return False576 if alpha[0] != den or beta[0] != 0:577 return False578 for i in range(len(edges)):579 if alpha[i] < 0:580 return False581 if sum(alpha[j] for (eps, j) in edges[i]) > alpha[i] + beta[i]:582 return False583 if sum(beta[j] for (eps, j) in edges[i]) > alpha[i]:584 return False585 return True586587588def binary_multiples(w, n):589 cur = [0] * w590 cur[0] = 1591 for i in range(n):592 p = pow(3, i, w)593 nxt = list(cur)594 for r in range(w):595 if cur[r]:596 nxt[(r + p) % w] += cur[r]597 cur = nxt598 return cur[0] - 1599600601def shift_product(j, n):602 if n <= j:603 return 1604 total = 1605 for r in range(j):606 m = len(range(r, n - j, j))607 total *= FIB[m + 2]608 return total609610611CERTIFICATES = {612 (1, 90): (18, [18, 0, 5, 6, 9, 2, 10, 4, 8],613 [0, 18, 4, -4, 5, 6, 8, 1, 2]),614 (4, 117): (40, [40, 0, 5, 14, 6, 13, 11, 27, 17, 1],615 [0, 40, 1, -1, 5, 14, 6, 13, 11, 4]),616 (9, 73): (381,617 [381, 0, 46, 49, 78, 17, 101, 23, 66, 163, 39, 83, 232, 32,618 62, 149],619 [0, 381, 32, -32, 46, 49, 62, 16, 17, 101, 23, 66, 149, 14,620 39, 83]),621 (9, 82): (18, [18, 0, 5, 6, 9, 2, 10, 4, 8],622 [0, 18, 4, -4, 5, 6, 8, 1, 2]),623 (9, 235): (2013,624 [2013, 0, 16, 66, 27, 55, 43, 121, 70, 176, 113, 297, 183,625 473, 296, 770, 479, 1243, 775, 11],626 [0, 2013, 11, -11, 16, 66, 27, 55, 43, 121, 70, 176, 113,627 297, 183, 473, 296, 770, 479, 5]),628 (10, 81): (18, [18, 0, 5, 6, 9, 2, 10, 4, 8],629 [0, 18, 4, -4, 5, 6, 8, 1, 2]),630 (13, 108): (40, [40, 0, 5, 14, 6, 13, 11, 27, 17, 1],631 [0, 40, 1, -1, 5, 14, 6, 13, 11, 4]),632 (27, 217): (2013,633 [2013, 0, 16, 66, 27, 55, 43, 121, 70, 176, 113, 297, 183,634 473, 296, 770, 479, 1243, 775, 11],635 [0, 2013, 11, -11, 16, 66, 27, 55, 43, 121, 70, 176, 113,636 297, 183, 473, 296, 770, 479, 5]),637 (27, 226): (34, [34, 0, 0, 1, 1, 0, 1, 1, 2, 1, 3, 5, 8, 13, 20, 1],638 [0, 34, 1, -1, 0, 1, 1, 0, 1, 1, 2, 3, 5, 8, 14, -1]),639}640641642def law_ceiling():643 tested = mismatch = 0644 for a in range(1, 40):645 for b in range(1, 40):646 if gcd(a, b) != 1:647 continue648 tested += 1649 if direction_mass(a, b, 20) != ray_mass(a, b, 20):650 mismatch += 1651 check("direction carry automaton against the gasket-digit automaton",652 (tested, mismatch), (947, 0))653 box = occupied = bounded = single = k1 = nodouble = 0654 twoplus = anomaly = states = renewal = 0655 exceptions = []656 twos = []657 for z1 in range(1, 121):658 for z2 in range(z1, 241):659 if gcd(z1, z2) != 1:660 continue661 box += 1662 d = (z1 % 3 == 0) + (z2 % 3 == 0) + ((z1 + z2) % 3 == 0)663 if d > 1:664 twoplus += 1665 order, edges = direction_live(z1, z2)666 if len(order) == 1:667 continue668 if d == 0:669 anomaly += 1670 occupied += 1671 states = max(states, len(order))672 if all(-z1 <= 2 * c <= z2 for c in order):673 bounded += 1674 degs = [len(o) for o in edges]675 classes = set(order[i] % 3 for i in range(len(order))676 if degs[i] == 2)677 if max(degs) <= 2 and len(classes) <= 1:678 single += 1679 q = z1 if z1 % 3 == 0 else (z2 if z2 % 3 == 0 else z1 + z2)680 if valuation3(q) == 1:681 k1 += 1682 if not double_branch(order, edges):683 nodouble += 1684 else:685 exceptions.append((z1, z2))686 f = first_returns(z1, z2, 46)687 if f[1] == 1 and all(688 sum(f[j] * FIB[m + 1 - j] for j in range(2, m + 1))689 <= FIB[m - 1] for m in range(2, 47)):690 renewal += 1691 if f[2]:692 twos.append((z1, z2))693 check("carry states of a direction lie in [-a/2, b/2]",694 (box, occupied, states, bounded), (13158, 218, 37, 218))695 check("out-degree at most two, branch states in one class mod 3",696 single, 218)697 check("at most one of z1, z2, w is divisible by three, and no occupied "698 "direction has none of them divisible", (twoplus, anomaly), (0, 0))699 check("directions settled by the branch argument over the box",700 (k1, nodouble, exceptions),701 (107, 206, [(1, 9), (1, 27), (1, 81), (1, 90), (4, 117), (9, 73),702 (9, 82), (9, 235), (10, 81), (13, 108), (27, 217),703 (27, 226)]))704 check("renewal criterion on every occupied direction of the box, n <= 46",705 (renewal, twos), (218, [(1, 3)]))706 good = sorted(k for k, v in CERTIFICATES.items()707 if certificate_holds(k[0], k[1], v[0], v[1], v[2]))708 check("Fibonacci certificates for the nine non-shift exceptions",709 good, [(1, 90), (4, 117), (9, 73), (9, 82), (9, 235), (10, 81),710 (13, 108), (27, 217), (27, 226)])711 identity = all(FIB[p + 2] * FIB[q + 2]712 == FIB[p + q + 3] - FIB[p + 1] * FIB[q + 1]713 for p in range(60) for q in range(60))714 cases = over = strict = 0715 for j in range(1, 14):716 for m in range(1, 46):717 cases += 1718 if shift_product(j, m) > FIB[m + 1]:719 over += 1720 if j >= 2 and m >= 2 and shift_product(j, m) >= FIB[m + 1]:721 strict += 1722 check("Fibonacci product identity and the shift-ray ceiling",723 (identity, cases, over, strict), (True, 585, 0, 0))724 tested = breaches = 0725 for z1 in range(1, 31):726 for z2 in range(z1, 61):727 if gcd(z1, z2) != 1:728 continue729 tested += 1730 if direction_mass(z1, z2, 12)[12] > binary_multiples(z1 + z2, 12):731 breaches += 1732 check("ray mass is at most the count of binary multiples of the weight",733 (tested, breaches), (829, 0))734 check("binary multiples of the weight outgrow the ceiling",735 ([binary_multiples(w, 24) for w in (4, 10, 28, 82)], FIB[25] - 1),736 ([4196351, 1683971, 613817, 228519], 75024))737 profile = state_profile(1, 9, 12)738 fails = 0739 for z1 in range(1, 121):740 for z2 in range(z1, 241):741 if gcd(z1, z2) != 1:742 continue743 h = state_profile(z1, z2, 20)744 if any(h[m] > h[m - 1] + h[m - 2] for m in range(2, 21)):745 fails += 1746 check("the state maximum breaks the Fibonacci recursion",747 (profile[:7], profile[4] > profile[3] + profile[2], fails),748 ([1, 1, 1, 2, 4, 6, 9], True, 8))749750751# LAW 6: THE PROVED CASES ON THE ADVERSARIAL FAMILIES752753754def law_families():755 bin3 = [sum(((u >> i) & 1) * 3 ** i for i in range(8)) for u in range(1, 256)]756 nad = no_adjacent(8)757 families = [758 [(a, b) for a in bin3 for b in bin3 if a < b],759 [(a, b) for a in nad for b in nad if a < b],760 [(1, t) for t in range(2, 3000)],761 [(a, a + 1) for a in range(1, 1500)],762 [(a, 3 * a - 1) for a in range(1, 1200)],763 [(a, 3 * a + 1) for a in range(1, 1200)],764 ]765 seen = set()766 multiplicity = 0767 for family in families:768 for (a, b) in family:769 if gcd(a, b) != 1:770 continue771 multiplicity += 1772 seen.add((a, b))773 inside = set((a, b) for (a, b) in seen if a <= 120 and b <= 240)774 zero = nodouble = shift = 0775 left = []776 for (a, b) in sorted(seen - inside):777 order, edges = direction_live(a, b)778 if len(order) == 1:779 zero += 1780 elif not double_branch(order, edges):781 nodouble += 1782 elif is_shift_ray(a, b):783 shift += 1784 else:785 left.append((a, b))786 check("the six families overlap, and their union splits",787 (multiplicity, len(seen), len(inside), len(seen) - len(inside),788 zero, nodouble, shift, len(left)),789 (24088, 23435, 717, 22718, 20945, 1693, 3, 77))790 breaches = 0791 wn, wd = 0, 1792 for (a, b) in left:793 m = direction_mass(a, b, 60)794 for j in range(1, 61):795 top = FIB[j + 1] - 1796 if m[j] > top:797 breaches += 1798 if top and m[j] * wd > wn * top:799 wn, wd = m[j], top800 check("the directions left to the enumeration hold to n = 60",801 (breaches, -(-wn * 10000 // wd)), (0, 1516))802803804# LAW 7: THE GOLDEN POTENTIAL805806807def qnorm(p, q, d):808 if d < 0:809 p, q, d = -p, -q, -d810 g = gcd(gcd(abs(p), abs(q)), d)811 if g > 1:812 p //= g813 q //= g814 d //= g815 return (p, q, d)816817818def qadd(x, y):819 return qnorm(x[0] * y[2] + y[0] * x[2],820 x[1] * y[2] + y[1] * x[2], x[2] * y[2])821822823def qsub(x, y):824 return qnorm(x[0] * y[2] - y[0] * x[2],825 x[1] * y[2] - y[1] * x[2], x[2] * y[2])826827828def qmul(x, y):829 return qnorm(x[0] * y[0] + x[1] * y[1],830 x[0] * y[1] + x[1] * y[0] + x[1] * y[1], x[2] * y[2])831832833def qinv(x):834 p, q, d = x835 n = p * p + p * q - q * q836 return qnorm(d * (p + q), -d * q, n)837838839def qsgn(x):840 p, q, d = x841 hi, lo = 2 * p + q, q842 if hi >= 0 and lo >= 0:843 return 0 if hi == 0 and lo == 0 else 1844 if hi <= 0 and lo <= 0:845 return 0 if hi == 0 and lo == 0 else -1846 s = hi * hi - 5 * lo * lo847 if hi > 0:848 return 1 if s > 0 else (0 if s == 0 else -1)849 return -1 if s > 0 else (0 if s == 0 else 1)850851852QZERO = (0, 0, 1)853QONE = (1, 0, 1)854QPHI = (0, 1, 1)855QINVPHI = (-1, 1, 1)856QINVPHI2 = (2, -1, 1)857858859def golden_potential(a, b):860 order, edges = direction_live(a, b)861 n = len(order)862 if n == 1:863 return None864 m = n - 1865 rows = [[QZERO] * (m + 1) for _ in range(m)]866 for r in range(m):867 rows[r][r] = QPHI868 for (eps, j) in edges[r + 1]:869 if j == 0:870 rows[r][m] = qadd(rows[r][m], QONE)871 else:872 rows[r][j - 1] = qsub(rows[r][j - 1], QONE)873 for col in range(m):874 piv = None875 for r in range(col, m):876 if qsgn(rows[r][col]):877 piv = r878 break879 if piv is None:880 return "singular"881 rows[col], rows[piv] = rows[piv], rows[col]882 scale = qinv(rows[col][col])883 rows[col] = [qmul(v, scale) if qsgn(v) else QZERO for v in rows[col]]884 for r in range(m):885 if r != col and qsgn(rows[r][col]):886 f = rows[r][col]887 rows[r] = [qsub(rows[r][k], qmul(f, rows[col][k]))888 if qsgn(rows[col][k]) else rows[r][k]889 for k in range(m + 1)]890 u = [QONE] + [rows[r][m] for r in range(m)]891 return u, order, edges892893894def potential_value(u, edges):895 tot = QZERO896 for (eps, j) in edges[0]:897 if j:898 tot = qadd(tot, u[j])899 return tot900901902def potential_valid(u, edges):903 if u[0] != QONE or any(qsgn(v) <= 0 for v in u):904 return False905 for i in range(1, len(edges)):906 s = QZERO907 for (eps, j) in edges[i]:908 s = qadd(s, u[j])909 if qsgn(qsub(qmul(QPHI, u[i]), s)) < 0:910 return False911 return True912913914def stress_directions():915 out = set()916 for j in range(1, 9):917 out.add((1, 3 ** j))918 for i in range(0, 7):919 for j in range(0, 8):920 a, b = 3 ** i, 3 ** i + 3 ** j921 g = gcd(a, b)922 a, b = a // g, b // g923 if b > a:924 out.add((a, b))925 for k in range(1, 7):926 for m in range(1, 40):927 for (a, b) in ((m, 3 ** k * m + 1), (1, 3 ** k * m),928 (3 ** k, 3 ** k + m), (3 ** k, 3 ** k + 3 * m + 1)):929 if a > b:930 a, b = b, a931 if a >= 1 and b > a and b <= 6000 and gcd(a, b) == 1:932 out.add((a, b))933 return out934935936def law_partition():937 box = []938 for z1 in range(1, 121):939 for z2 in range(z1, 241):940 if gcd(z1, z2) == 1:941 box.append((z1, z2))942 occupied = certified = passing = 0943 over = []944 values = {}945 for (a, b) in box:946 r = golden_potential(a, b)947 if r is None:948 continue949 occupied += 1950 u, order, edges = r951 if potential_valid(u, edges):952 certified += 1953 tot = potential_value(u, edges)954 values.setdefault(tot, []).append((a, b))955 if qsgn(qsub(QINVPHI2, tot)) >= 0:956 passing += 1957 else:958 over.append((a, b))959 check("the golden potential certifies the box away from the shift rays",960 (occupied, certified, passing, over),961 (218, 218, 214, [(1, 3), (1, 9), (1, 27), (1, 81)]))962 ranked = []963 for _ in range(3):964 top = None965 for k in values:966 if k not in ranked and (top is None or qsgn(qsub(k, top)) > 0):967 top = k968 ranked.append(top)969 check("the top of the golden potential over the box",970 ([(k, sorted(values[k])) for k in ranked], len(values)),971 ([((-1, 1, 1), [(1, 3), (1, 9), (1, 27), (1, 81)]),972 ((2, -1, 1), [(1, 12), (3, 10), (4, 9)]),973 ((-14, 9, 2), [(1, 90), (9, 82), (10, 81)])], 57))974 tail = peak = 0975 for (a, b) in box:976 r = golden_potential(a, b)977 if r is None:978 continue979 u, order, edges = r980 tot = potential_value(u, edges)981 f = first_returns(a, b, 46)982 s = QZERO983 p = QONE984 for j in range(1, 47):985 p = qmul(p, QINVPHI)986 if j >= 2 and f[j]:987 s = qadd(s, qmul((f[j], 0, 1), p))988 if f[1] == 1 and qsgn(qsub(qmul(QINVPHI, tot), s)) >= 0:989 tail += 1990 if qsgn(qsub(QINVPHI, tot)) >= 0:991 peak += 1992 check("the potential dominates the first-return series and no direction "993 "beats the shift rays", (tail, peak), (218, 218))994 seen = set(box)995 bin3 = [sum(((u >> i) & 1) * 3 ** i for i in range(8)) for u in range(1, 256)]996 nad = no_adjacent(8)997 families = [998 [(a, b) for a in bin3 for b in bin3 if a < b],999 [(a, b) for a in nad for b in nad if a < b],1000 [(1, t) for t in range(2, 3000)],1001 [(a, a + 1) for a in range(1, 1500)],1002 [(a, 3 * a - 1) for a in range(1, 1200)],1003 [(a, 3 * a + 1) for a in range(1, 1200)],1004 ]1005 for family in families:1006 for (a, b) in family:1007 if gcd(a, b) == 1:1008 seen.add((a, b))1009 seen |= stress_directions()1010 total = len(seen)1011 occupied = certified = passing = states = 01012 over = []1013 gap = []1014 best = QZERO1015 attain = []1016 for (a, b) in sorted(seen):1017 r = golden_potential(a, b)1018 if r is None:1019 continue1020 occupied += 11021 u, order, edges = r1022 states = max(states, len(order))1023 if potential_valid(u, edges):1024 certified += 11025 tot = potential_value(u, edges)1026 if qsgn(qsub(QINVPHI2, tot)) >= 0:1027 passing += 11028 if qsgn(qsub(tot, best)) > 0:1029 best, attain = tot, [(a, b)]1030 elif tot == best:1031 attain.append((a, b))1032 else:1033 over.append((a, b, tot))1034 if tot != QINVPHI:1035 gap.append((a, b, tot))1036 check("the golden potential over the box, the six families and the "1037 "high-valuation stress list",1038 (total, occupied, certified, states, passing),1039 (36037, 1995, 1995, 256, 1987))1040 check("every direction over the criterion is a shift ray at exactly "1041 "one over phi", (over, gap),1042 ([(1, 3 ** j, QINVPHI) for j in range(1, 9)], []))1043 check("the criterion is attained exactly on the supergolden directions",1044 (best, attain), (QINVPHI2, [(1, 12), (3, 10), (4, 9)]))1045 zero = residue = 01046 for (z1, z2) in box:1047 if z1 % 3 and z2 % 3:1048 residue += 11049 if len(direction_live(z1, z2)[0]) == 1:1050 zero += 11051 check("occupancy needs three to divide one coordinate",1052 (len(box), residue, zero, len(box) - zero),1053 (13158, 6566, 12940, 218))105410551056# LAW 8: THE DEGREE POTENTIAL105710581059def split3(a, b):1060 q, p = (a, b) if a % 3 == 0 else (b, a)1061 k = 01062 q1 = q1063 while q1 % 3 == 0:1064 q1 //= 31065 k += 11066 return p, k, q1106710681069def phipow(e):1070 r = QONE1071 for _ in range(abs(e)):1072 r = qmul(r, QPHI if e > 0 else QINVPHI)1073 return r107410751076def degree_potential(edges):1077 return [QONE if len(o) == 2 else QINVPHI for o in edges]107810791080def super_solution(edges, pi):1081 for i in range(1, len(edges)):1082 s = QZERO1083 for (eps, j) in edges[i]:1084 s = qadd(s, pi[j])1085 if qsgn(qsub(qmul(QPHI, pi[i]), s)) < 0:1086 return False1087 return True108810891090def sweep(edges, d):1091 pi = degree_potential(edges)1092 pi[0] = QONE1093 for _ in range(d):1094 nxt = [QONE] + [QZERO] * (len(edges) - 1)1095 for i in range(1, len(edges)):1096 s = QZERO1097 for (eps, j) in edges[i]:1098 s = qadd(s, pi[j])1099 nxt[i] = qmul(QINVPHI, s)1100 pi = nxt1101 tot = QZERO1102 for (eps, j) in edges[0]:1103 if j:1104 tot = qadd(tot, pi[j])1105 return tot110611071108def base3(x):1109 d = []1110 while x:1111 d.append(x % 3)1112 x //= 31113 return d or [0]111411151116def burst_floor(a, b, k, q1):1117 sign = 1 if b % 3 == 0 else -11118 out = []1119 for m in range(1, 3 ** k):1120 if m % 3 != 1:1121 continue1122 x, ok = m, True1123 while x:1124 if x % 3 > 1:1125 ok = False1126 break1127 x //= 31128 if ok:1129 out.append(sign * q1 * m)1130 return out113111321133def law_degree():1134 seen = set()1135 for z1 in range(1, 121):1136 for z2 in range(z1, 241):1137 if gcd(z1, z2) == 1:1138 seen.add((z1, z2))1139 bin3 = [sum(((u >> i) & 1) * 3 ** i for i in range(8)) for u in range(1, 256)]1140 nad = no_adjacent(8)1141 families = [1142 [(a, b) for a in bin3 for b in bin3 if a < b],1143 [(a, b) for a in nad for b in nad if a < b],1144 [(1, t) for t in range(2, 3000)],1145 [(a, a + 1) for a in range(1, 1500)],1146 [(a, 3 * a - 1) for a in range(1, 1200)],1147 [(a, 3 * a + 1) for a in range(1, 1200)],1148 ]1149 for family in families:1150 for (a, b) in family:1151 if gcd(a, b) == 1:1152 seen.add((a, b))1153 seen |= stress_directions()1154 triple = eligible = occupied = 01155 resbad = burst = burstbad = 01156 valid = nodouble = doublevalid = settled = 01157 depths = {}1158 missed = []1159 k1 = k1class = 01160 k1bad = []1161 quantbad = []1162 attain = []1163 for (a, b) in sorted(seen):1164 p, k, q1 = split3(a, b)1165 if a % 3 == 0 or b % 3 == 0:1166 triple += 11167 if (q1 - p) % 3 == 0:1168 eligible += 11169 order, edges = direction_live(a, b)1170 if len(order) == 1:1171 continue1172 occupied += 11173 if (q1 - p) % 3:1174 resbad += 11175 f = first_returns(a, b, max(k, 2))1176 if any(f[j] for j in range(2, k + 1)):1177 burst += 11178 if not double_branch(order, edges):1179 nodouble += 11180 pi = degree_potential(edges)1181 if super_solution(edges, pi):1182 valid += 11183 if double_branch(order, edges):1184 doublevalid += 11185 hit = None1186 for d in range(25):1187 if qsgn(qsub(QINVPHI2, sweep(edges, d))) >= 0:1188 hit = d1189 break1190 if hit is None:1191 missed.append((a, b))1192 else:1193 settled += 11194 depths[hit] = depths.get(hit, 0) + 11195 else:1196 missed.append((a, b))1197 u = golden_potential(a, b)[0]1198 tot = potential_value(u, edges)1199 place = {c: i for i, c in enumerate(order)}1200 floor = burst_floor(a, b, k, q1)1201 rung = QZERO1202 for c in floor:1203 if c in place:1204 rung = qadd(rung, u[place[c]])1205 if len(floor) != 2 ** (k - 1) or \1206 qmul(phipow(-(k - 1)), rung) != tot:1207 burstbad += 11208 if 2 * p <= 3 ** k * q1:1209 c = p if b % 3 == 0 else -p1210 if c not in place or u[place[c]] != QINVPHI:1211 burstbad += 11212 if k != 1:1213 continue1214 k1 += 11215 t = valuation3(q1 - p) if q1 != p else None1216 if t is None:1217 continue1218 bound = qmul(QINVPHI, qsub(QONE, phipow(-max(t, 2))))1219 if qsgn(qsub(bound, tot)) < 0:1220 quantbad.append((a, b, t))1221 if tot == bound:1222 attain.append((a, b))1223 if t <= 2:1224 k1class += 11225 if qsgn(qsub(QINVPHI2, tot)) < 0:1226 k1bad.append((a, b, t))1227 check("occupancy needs the residue match q1 = p mod 3",1228 (triple, eligible, occupied, resbad), (20193, 15556, 1995, 0))1229 check("no first return has length between two and v3(q)",1230 (occupied, burst), (1995, 0))1231 check("the burst identity and the value at the near predecessor",1232 (occupied, burstbad), (1995, 0))1233 check("the degree potential is a super-solution beyond the branch case",1234 (occupied, nodouble, valid, doublevalid), (1995, 1902, 1968, 66))1235 check("the swept degree potential settles all but the shift rays and "1236 "twenty-one directions",1237 (settled, sorted(depths.items()),1238 [z for z in missed if is_shift_ray(*z)],1239 [z for z in missed if not is_shift_ray(*z)]),1240 (1966, [(1, 1804), (3, 101), (4, 44), (5, 11), (6, 6)],1241 [(1, 3 ** j) for j in range(1, 9)],1242 [(1, 756), (1, 2196), (1, 2214), (1, 2268), (1, 2430), (9, 2188),1243 (10, 2187), (13, 1080), (13, 3267), (27, 730), (27, 2188),1244 (28, 729), (28, 2187), (40, 1053), (81, 2188), (82, 2187),1245 (91, 2214), (121, 3159), (243, 2188), (244, 2187), (819, 2539)]))1246 check("the golden partition bound at v3(q) = 1",1247 (k1, k1class, k1bad, quantbad, attain),1248 (757, 512, [], [], [(1, 12), (3, 10)]))1249 short = []1250 for a in range(1, 260):1251 for b in range(1, 1100):1252 if gcd(a, b) != 1 or (a % 3 and b % 3):1253 continue1254 if len(direction_live(a, b)[0]) == 1:1255 continue1256 f = first_returns(a, b, 4)1257 if f[2] or f[3]:1258 short.append((min(a, b), max(a, b), f[2], f[3]))1259 check("the first returns of length two and three are classified",1260 sorted(set(short)),1261 [(1, 3, 1, 0), (1, 9, 0, 1), (1, 12, 0, 1), (3, 10, 0, 1),1262 (4, 9, 0, 1)])1263 tested = bad = 01264 quartic = qmul(qmul(QPHI, QPHI), qmul(QPHI, QPHI))1265 for a in range(1, 40):1266 for b in range(1, 120):1267 if gcd(a, b) != 1 or (a % 3 and b % 3):1268 continue1269 order, edges = direction_live(a, b)1270 if len(order) == 1:1271 continue1272 tested += 11273 tot = potential_value(golden_potential(a, b)[0], edges)1274 den = qsub(QINVPHI2, qmul(QINVPHI, tot))1275 closed = qinv(den) if qsgn(den) > 0 else None1276 mass = direction_mass(a, b, 46)1277 partial = QZERO1278 w = QONE1279 for n in range(47):1280 partial = qadd(partial, qmul((mass[n] + 1, 0, 1), w))1281 w = qmul(w, QINVPHI)1282 layer = QZERO1283 for m in range(1, 3 ** 11 // (a + b)):1284 x, y = base3(a * m), base3(b * m)1285 if max(x) < 2 and max(y) < 2 and \1286 not any(dx and dy for dx, dy in zip(x, y)):1287 layer = qadd(layer, phipow(-len(base3((a + b) * m))))1288 ok = closed is None or qsgn(qsub(closed, partial)) >= 01289 ok = ok and (closed is not None1290 and qsgn(qsub(quartic, closed)) >= 0) == \1291 (qsgn(qsub(QINVPHI2, tot)) >= 0)1292 ok = ok and (qsgn(qsub(QPHI, layer)) >= 01293 or qsgn(qsub(tot, QINVPHI2)) > 0)1294 bad += not ok1295 check("the criterion restated as a golden series and as a multiplier count",1296 (tested, bad), (111, 0))129712981299def main():1300 law_tensor()1301 law_box()1302 law_weights()1303 law_deep()1304 law_ceiling()1305 law_families()1306 law_partition()1307 law_degree()1308 print("gasket witness weights: every law holds")130913101311main()