jsr.py
15.3 kB · python · 414 lines
1from fractions import Fraction23CODES = list(range(1, 16))4FRAME = ("gamma", "h", "v", "phi")56# GEOMETRY78def tile(c):9 return [(c >> 0) & 3, (c >> 2) & 3]1011def draw(word):12 rows, n = [1], 113 for c in word:14 t = tile(c)15 out = []16 for r in rows:17 for p in (0, 1):18 acc = 019 for j in range(n):20 if (r >> j) & 1:21 acc |= t[p] << (2 * j)22 out.append(acc)23 rows, n = out, 2 * n24 return rows, n2526def fill(rows):27 return sum(bin(r).count("1") for r in rows)2829def hruns(rows):30 return sum(bin(r & ~(r << 1)).count("1") for r in rows)3132def vruns(rows):33 total, prev = 0, 034 for r in rows:35 total += bin(r & ~prev).count("1")36 prev = r37 return total3839def comp(rows, n):40 seen = [0] * len(rows)41 total = 042 for i in range(len(rows)):43 free = rows[i] & ~seen[i]44 while free:45 b = free & -free46 total += 147 stack = [(i, b.bit_length() - 1)]48 seen[i] |= b49 while stack:50 a, j = stack.pop()51 for da, dj in ((1, 0), (-1, 0), (0, 1), (0, -1)):52 x, y = a + da, j + dj53 if 0 <= x < len(rows) and 0 <= y < n:54 m = 1 << y55 if (rows[x] & m) and not (seen[x] & m):56 seen[x] |= m57 stack.append((x, y))58 free = rows[i] & ~seen[i]59 return total6061CACHE = {}6263def obs(word):64 if word not in CACHE:65 rows, n = draw(word)66 CACHE[word] = (comp(rows, n), hruns(rows), vruns(rows), fill(rows))67 return CACHE[word]6869# EXACT LINEAR ALGEBRA7071def solve(basis, target):72 m, ncol = len(basis), len(target)73 aug = [[basis[i][j] for i in range(m)] + [target[j]] for j in range(ncol)]74 piv, top = [], 075 for col in range(m):76 p = next((k for k in range(top, ncol) if aug[k][col] != 0), None)77 if p is None:78 continue79 aug[top], aug[p] = aug[p], aug[top]80 d = aug[top][col]81 aug[top] = [x / d for x in aug[top]]82 for k in range(ncol):83 if k != top and aug[k][col] != 0:84 f = aug[k][col]85 aug[k] = [a - f * b for a, b in zip(aug[k], aug[top])]86 piv.append((top, col))87 top += 188 if any(aug[k][m] != 0 for k in range(top, ncol)):89 return None90 x = [Fraction(0)] * m91 for r, c in piv:92 x[c] = aug[r][m]93 return x9495def mul(A, B):96 d = len(A)97 return [[sum(A[i][k] * B[k][j] for k in range(d)) for j in range(d)] for i in range(d)]9899def det(A):100 d = len(A)101 if d == 1:102 return A[0][0]103 return sum((-1) ** j * A[0][j] * det([row[:j] + row[j + 1:] for row in A[1:]]) for j in range(d))104105def eye(d):106 return [[1 if i == j else 0 for j in range(d)] for i in range(d)]107108def apply(A, x):109 return [sum(A[i][j] * x[j] for j in range(len(x))) for i in range(len(A))]110111def floor_root(num, den, L, digits=9):112 scale = 10 ** digits113 lo = int((num / den) ** (1.0 / L) * scale) - 4114 while (lo + 1) ** L * den <= num * scale ** L:115 lo += 1116 while lo ** L * den > num * scale ** L:117 lo -= 1118 return "%d.%0*d" % (lo // scale, digits, lo % scale)119120def ceil_root(num, den, L, digits=9):121 scale = 10 ** digits122 hi = int((num / den) ** (1.0 / L) * scale) + 4123 while (hi - 1) ** L * den >= num * scale ** L:124 hi -= 1125 while hi ** L * den < num * scale ** L:126 hi += 1127 return "%d.%0*d" % (hi // scale, digits, hi % scale)128129def words(alphabet, length):130 if length == 0:131 yield ()132 return133 for w in words(alphabet, length - 1):134 for c in alphabet:135 yield w + (c,)136137def seeded(alphabet, count, lo, hi):138 state, out = 20260906, []139 for _ in range(count):140 state = (1103515245 * state + 12345) % (1 << 31)141 length = lo + state % (hi - lo + 1)142 w = []143 for _ in range(length):144 state = (1103515245 * state + 12345) % (1 << 31)145 w.append(alphabet[state % len(alphabet)])146 out.append(tuple(w))147 return out148149# THE REPRESENTATION150151SUFFIXES = [()] + [(c,) for c in CODES] + [(a, b) for a in CODES for b in CODES]152153def row_of(u):154 return [Fraction(obs(u + s)[0]) for s in SUFFIXES]155156def build():157 basis, rows, mats = [()], [row_of(())], {c: [] for c in CODES}158 i = 0159 while i < len(basis):160 for c in CODES:161 r = row_of(basis[i] + (c,))162 x = solve(rows, r)163 if x is None:164 basis.append(basis[i] + (c,))165 rows.append(r)166 x = solve(rows, r)167 mats[c].append(x)168 i += 1169 d = len(basis)170 for c in CODES:171 for line in mats[c]:172 line.extend([Fraction(0)] * (d - len(line)))173 mats[c] = [[int(x) for x in line] for line in mats[c]]174 return basis, mats175176print("JSR SCHEDULES")177print()178print("== THE REPRESENTATION ==")179basis, M = build()180print("Hankel rank %d, basis words %s" % (len(basis), [w if w else "e" for w in basis]))181lam = [1] + [0] * (len(basis) - 1)182gamma = [obs(u)[0] for u in basis]183h = [obs(u)[1] for u in basis]184v = [obs(u)[2] for u in basis]185phi = [obs(u)[3] for u in basis]186print("lambda %s, gamma %s, h %s, v %s, phi %s" % (lam, gamma, h, v, phi))187classes = {}188for c in CODES:189 classes.setdefault(tuple(map(tuple, M[c])), []).append(c)190CLASS = sorted(classes.values(), key=lambda g: g[0])191print("class partition %s" % (CLASS,))192assert len(CLASS) == 6193noncomm = sum(1 for i in range(6) for j in range(i + 1, 6)194 if mul(M[CLASS[i][0]], M[CLASS[j][0]]) != mul(M[CLASS[j][0]], M[CLASS[i][0]]))195print("non-commuting class pairs %d of 15" % noncomm)196for g in CLASS:197 print(" M_%s = %s" % (g, M[g[0]]))198199# THE OBSERVABLE FRAME200201print()202print("== THE OBSERVABLE FRAME ==")203G = [[gamma[i], h[i], v[i], phi[i]] for i in range(4)]204print("frame columns gamma %s, h %s, v %s, phi %s, det %d" % (gamma, h, v, phi, det(G)))205assert h == apply(M[3], gamma) and v == apply(M[5], gamma) and phi == apply(M[1], gamma)206print("h, v and phi are M_3 gamma, M_5 gamma and M_1 gamma, so all four frame vectors are comp read at one appended letter")207assert abs(det(G)) == 1208Ginv = None209adj = [[Fraction((-1) ** (i + j) * det([r[:i] + r[i + 1:] for k, r in enumerate(G) if k != j]), det(G))210 for j in range(4)] for i in range(4)]211Ginv = adj212assert mul(Ginv, G) == eye(4)213T = {}214for c in CODES:215 t = mul(Ginv, mul(M[c], G))216 T[c] = [[int(x) for x in row] for row in t]217 assert all(Fraction(T[c][i][j]) == t[i][j] for i in range(4) for j in range(4))218print("frame images, columns of T_c in the frame (gamma, h, v, phi):")219for g in CLASS:220 c = g[0]221 rows, n = draw((c,))222 ob = obs((c,))223 cols = [[T[c][i][j] for i in range(4)] for j in range(4)]224 csum = [sum(col) for col in cols]225 print(" code %-2d k=%d M_c gamma=%s M_c h=%s M_c v=%s M_c phi=%s column sums %s"226 % (c, ob[3], cols[0], cols[1], cols[2], cols[3], csum))227 assert csum == [ob[0], ob[1], ob[2], ob[3]]228 assert max(csum) == ob[3]229print("every column sum is (comp, rows, cols, fill) of the letter's own tile, and the largest is the fill")230231# THE FOUR TRANSFER LAWS, CHECKED AGAINST DRAWN CELLS232233print()234print("== THE TRANSFER LAWS ==")235short = [w for L in range(0, 4) for w in words(CODES, L)]236seeds = seeded(CODES, 240, 4, 7)237sweep = short + seeds238bad = 0239for w in sweep:240 y = obs(w)241 for c in CODES:242 z = obs(w + (c,))243 pred = tuple(sum(y[i] * T[c][i][j] for i in range(4)) for j in range(4))244 if pred != z:245 bad += 1246assert bad == 0247print("(comp, H, V, fill) at wc equals (comp, H, V, fill) at w times T_c on %d words, all %d of length at most 3 and %d seeded of length 4 to 7, times 15 letters, %d mismatches"248 % (len(sweep), len(short), len(seeds), bad))249rep_bad = 0250for w in sweep:251 P = eye(4)252 for c in w:253 P = mul(P, M[c])254 val = sum(lam[i] * sum(P[i][j] * gamma[j] for j in range(4)) for i in range(4))255 hv = sum(lam[i] * sum(P[i][j] * h[j] for j in range(4)) for i in range(4))256 vv = sum(lam[i] * sum(P[i][j] * v[j] for j in range(4)) for i in range(4))257 fv = sum(lam[i] * sum(P[i][j] * phi[j] for j in range(4)) for i in range(4))258 if (val, hv, vv, fv) != obs(w):259 rep_bad += 1260assert rep_bad == 0261print("lambda M_w applied to gamma, h, v, phi reads comp, H, V, fill on the same %d words, %d of length at most 3 and %d seeded, %d mismatches"262 % (len(sweep), len(short), len(seeds), rep_bad))263264# TRIANGULARITY AND SPECTRUM265266print()267print("== TRIANGULARITY ==")268for g in CLASS:269 c = g[0]270 assert all(T[c][i][j] == 0 for i in range(4) for j in range(4) if j > i)271 assert all(T[c][i][j] >= 0 for i in range(4) for j in range(4))272 diag = [T[c][i][i] for i in range(4)]273 poly = [t for t in range(5)]274 vals = [det([[t * (1 if i == j else 0) - M[c][i][j] for j in range(4)] for i in range(4)]) for t in poly]275 pred = [(t - diag[0]) * (t - diag[1]) * (t - diag[2]) * (t - diag[3]) for t in poly]276 assert vals == pred277 print(" code %-2d T_c lower triangular, nonnegative, diagonal %s, char poly roots %s, rho = %d = fill"278 % (c, diag, sorted(diag), max(diag)))279 assert max(diag) == obs((c,))[3]280281# THE CROSS-POLYTOPE CERTIFICATE282283print()284print("== THE CERTIFICATE ==")285print("P = conv{+/- gamma, +/- h, +/- v, +/- phi}; residual is k_c minus the frame l1 norm of the image")286for g in CLASS:287 c = g[0]288 k = obs((c,))[3]289 res = []290 for j, name in enumerate(FRAME):291 col = [T[c][i][j] for i in range(4)]292 res.append(k - sum(abs(x) for x in col))293 print(" code %-2d k=%d residuals at gamma, h, v, phi: %s" % (c, k, res))294 assert all(r >= 0 for r in res)295print("M_c P is inside k_c P at every code, so the gauge of P, the frame l1 norm N(a gamma + b h + c v + d phi) = |a| + |b| + |c| + |d|, is an extremal norm with ||M_c|| = k_c")296297# JSR AND LSR OVER EVERY SUBFAMILY298299print()300print("== JSR AND LSR ==")301K = {c: obs((c,))[3] for c in CODES}302pairs = [(a, b) for i, a in enumerate(CODES) for b in CODES[i + 1:]]303print("pairs of distinct letters %d, pairs of distinct classes %d" % (len(pairs), 15))304print("class pair table (a, b, JSR, LSR):")305for i in range(6):306 for j in range(i + 1, 6):307 a, b = CLASS[i][0], CLASS[j][0]308 print(" (%d, %d) JSR %d LSR %d" % (a, b, max(K[a], K[b]), min(K[a], K[b])))309distinct = sum(1 for a, b in pairs if K[a] != K[b])310print("of the %d pairs of distinct letters, %d carry two different fills and %d carry one" % (len(pairs), distinct, len(pairs) - distinct))311print("JSR = max fill and LSR = min fill on every one of them, each attained by a one-letter word")312313# THE BRACKET ON TWO NAMED PAIRS314315print()316print("== THE BRACKET ==")317LMAX = 16318for pair in [(3, 6), (3, 7)]:319 a, b = pair320 r = max(K[a], K[b])321 cur = {(): eye(4)}322 best_lo, best_word, best_up, best_min = {}, {}, {}, {}323 for L in range(1, LMAX + 1):324 nxt = {}325 for w, P in cur.items():326 for c in pair:327 nxt[w + (c,)] = mul(P, T[c])328 cur = nxt329 lo, arg, up, worst = -1, None, 0, None330 for w, P in cur.items():331 sr = max(P[i][i] for i in range(4))332 if sr > lo:333 lo, arg = sr, w334 if worst is None or sr < worst:335 worst = sr336 nrm = max(sum(P[i][j] for i in range(4)) for j in range(4))337 if nrm > up:338 up = nrm339 best_lo[L], best_word[L], best_up[L], best_min[L] = lo, arg, up, worst340 assert lo <= up341 assert lo == r ** L342 assert up == r ** L343 assert worst == min(K[a], K[b]) ** L344 print("pair {%d, %d}, JSR candidate %d" % (a, b, r))345 for L in (1, 2, 4, 8, 12, LMAX):346 w = "".join(str(x) for x in best_word[L])347 print(" L=%-2d lower %s from word %s, spectral radius %d; upper %s from max frame 1-norm %d"348 % (L, floor_root(best_lo[L], 1, L), w, best_lo[L], ceil_root(best_up[L], 1, L), best_up[L]))349 print(" the best word's rate is exactly %d at every length 1 to %d, so it stops improving at length 1" % (r, LMAX))350 print(" the worst word's spectral radius is exactly %d^L at every length, so the lower spectral radius is %d"351 % (min(K[a], K[b]), min(K[a], K[b])))352 for k in (2, 4, 6):353 num = K[a] ** k + K[b] ** k354 lift_lo = floor_root(num, 2, k)355 lift_up = ceil_root(num, 1, k)356 print(" Blondel-Nesterov lifting at k=%d on the 2 letters: lower 2^(-1/%d) (%d^%d + %d^%d)^(1/%d) = %s, upper (%d^%d + %d^%d)^(1/%d) = %s"357 % (k, k, K[a], k, K[b], k, k, lift_lo, K[a], k, K[b], k, k, lift_up))358 assert Fraction(lift_lo) <= Fraction(lift_up)359 assert Fraction(lift_lo) <= r <= Fraction(lift_up)360 assert num >= r ** k361 print(" the scan alone brackets it at [%s, %s] at L=%d, the lifting alone at [%s, %s] at k=6, and the certificate closes it at %d exactly"362 % (floor_root(best_lo[LMAX], 1, LMAX), ceil_root(best_up[LMAX], 1, LMAX), LMAX,363 floor_root(K[a] ** 6 + K[b] ** 6, 2, 6), ceil_root(K[a] ** 6 + K[b] ** 6, 1, 6), r))364365# THE TELESCOPE BLOCK366367print()368print("== THE TELESCOPE ==")369A = [[M[3][i][j] for j in range(2)] for i in range(2)]370B = [[M[6][i][j] for j in range(2)] for i in range(2)]371assert all(M[3][i][j] == 0 for i in range(4) for j in (2, 3))372assert all(M[6][i][j] == 0 for i in range(4) for j in (2, 3))373print("on {3, 6} the last two columns of both matrices vanish, leading blocks A = %s, B = %s" % (A, B))374p = [1, 2]375assert apply(A, p) == [2 * x for x in p]376assert apply(B, [1, 1]) == [2 * x for x in p] and apply(B, p) == [2 * x for x in p]377print("A p = 2 p and B has range span(p) at p = %s, so span(p) is invariant under both and the pair is reducible" % p)378print("in the basis (gamma, p) both blocks are lower triangular, A = diag(1, 2) and B = [[0, 0], [2, 2]]")379for L in (1, 2, 3, 6, 10):380 vals = set()381 for w in words((3, 6), L):382 P = eye(4)383 for c in w:384 P = mul(P, T[c])385 vals.add(max(P[i][i] for i in range(4)))386 print(" every word of length %d has spectral radius %s, so its rate is exactly 2" % (L, sorted(vals)))387 assert vals == {2 ** L}388389# THE DEAD ROUTES390391print()392print("== WHAT DIES ==")393for g in CLASS:394 c = g[0]395 assert apply(M[c], phi) == [K[c] * x for x in phi]396print("phi = %s is a right eigenvector of every M_c with eigenvalue the fill, so span(phi) is a common invariant line and no subfamily is irreducible" % phi)397orbit = {tuple(phi)}398for _ in range(4):399 orbit |= {tuple(Fraction(t, K[c]) for t in apply(M[c], list(x))) for x in orbit for c in (3, 7)}400 orbit = {tuple(int(t) if t.denominator == 1 else t for t in x) for x in orbit}401assert orbit == {tuple(phi)}402print("the polytope algorithm started at the leading eigenvector of the length-1 spectrum maximizing product, each image normalised by the letter fill k_c, closes on %d vertex up to sign and spans dimension 1 of 4" % len(orbit))403norms = []404for L in (1, 2, 4, 8, 16, 32):405 P = eye(4)406 for _ in range(L):407 P = mul(P, M[3])408 norms.append(max(abs(P[i][j]) for i in range(4) for j in range(4)))409print("max |entry(M_3^L)| at L = 1, 2, 4, 8, 16, 32 reads %s, matching 2^(L+2) - 2" % norms)410assert norms == [2 ** (L + 2) - 2 for L in (1, 2, 4, 8, 16, 32)]411print("comp(A_(3^L)) = %s at the same lengths, so the JSR rate log 2 is not the component rate 0"412 % [obs((3,) * L)[0] for L in (1, 2, 4, 8)])413print()414print("every assertion passed")