ladder.py
12.4 kB · python · 312 lines
1from fractions import Fraction2from math import comb, log3import mpmath4import numpy as np5import sympy as sp67DIGITS = ((0, 0), (1, 0), (0, 1))8LOG3 = log(3)9KAPPA = 3 - log(5) / LOG310WALL = 2 / (3 + log(5) / LOG3)1112def log3(x):13 return log(x) / LOG31415def beta(kappa):16 return kappa / (2 * kappa + 2 - KAPPA)1718def delta_counts(K):19 types = [(b, c, comb(K, b) * comb(K - b, c)) for b in range(K + 1) for c in range(K + 1 - b)]20 out = {}21 for b, c, m in types:22 for B, C, M in types:23 out[(b - B, c - C)] = out.get((b - B, c - C), 0) + m * M24 return out2526def carry_matrix(K):27 radius = (K - 1) // 228 states = [(i, j) for i in range(-radius, radius + 1) for j in range(-radius, radius + 1)]29 index = {s: i for i, s in enumerate(states)}30 M = [[0] * len(states) for _ in states]31 for s in states:32 for (d1, d2), w in delta_counts(K).items():33 z1, z2 = s[0] + d1, s[1] + d234 if z1 % 3 == 0 and z2 % 3 == 0:35 M[index[s]][index[(z1 // 3, z2 // 3)]] += w36 return M, index[(0, 0)]3738def energies(K, levels):39 M, zero = carry_matrix(K)40 v = [0] * len(M)41 v[zero] = 142 out = [1]43 for _ in range(levels):44 v = [sum(v[i] * M[i][j] for i in range(len(v))) for j in range(len(v))]45 out.append(v[zero])46 return out4748def gasket(a):49 pts = [(0, 0)]50 for l in range(a):51 pts = [(x + dx * 3**l, y + dy * 3**l) for x, y in pts for dx, dy in DIGITS]52 return pts5354def energy_direct(K, a):55 pts = gasket(a)56 top = K * (3**a - 1) + 157 sums = np.zeros((top, top), dtype=np.int64)58 sums[0, 0] = 159 for _ in range(K):60 fresh = np.zeros_like(sums)61 for x, y in pts:62 fresh[x:, y:] += sums[: top - x, : top - y]63 sums = fresh64 return sum(int(v) * int(v) for v in sums.flat if v)6566print("kappa = 3 - log_3 5 = %.6f, beta_0^(4) = %.6f, wall 2/(3 + log_3 5) = %.12f" % (KAPPA, beta(KAPPA), WALL))6768print("energies E_2K(G_a), carry matrix exact integers, a = 0..6")69rows = {}70for K in (2, 3, 4, 5):71 rows[K] = energies(K, 6)72 print("2K = %d:" % (2 * K), rows[K])73print("E_4 = 15^a for a <= 6:", all(rows[2][a] == 15**a for a in range(7)))74for K in (2, 3, 4, 5):75 top = 4 if K < 5 else 376 agree = all(energy_direct(K, a) == rows[K][a] for a in range(1, top + 1))77 print("2K = %d direct convolution agrees at a = 1..%d: %s" % (2 * K, top, agree))7879print("characteristic polynomials and Perron roots")80x = sp.symbols("x")81mpmath.mp.dps = 3082perron = {}83for K in (3, 4, 5):84 M, _ = carry_matrix(K)85 poly = sp.Matrix(M).charpoly(x).as_expr()86 print("2K = %d states %d factors: %s" % (2 * K, len(M), sp.factor(poly)))87 perron[K] = max(mpmath.mpf(str(r)) for f, _ in sp.factor_list(poly)[1] for r in sp.Poly(f, x).nroots(n=25) if r.is_real)88lam6 = 57 + 6 * mpmath.sqrt(46)89lam8 = 456 + 3 * mpmath.sqrt(11017)90lam10 = perron[5]91print("lambda_6 = 57 + 6 sqrt 46 = %s, matches largest factor root %s" % (mpmath.nstr(lam6, 12), abs(lam6 - perron[3]) < 1e-15))92print("lambda_8 = 456 + 3 sqrt 11017 = %s, matches largest factor root %s" % (mpmath.nstr(lam8, 12), abs(lam8 - perron[4]) < 1e-15))93print("lambda_10 largest root of the quartic factor = %s" % mpmath.nstr(lam10, 13))9495def reachable(A, start):96 seen, stack = {start}, [start]97 while stack:98 i = stack.pop()99 for j in range(len(A)):100 if A[i][j] and j not in seen:101 seen.add(j)102 stack.append(j)103 return seen104105def scc_zero(M, zero):106 T = [[M[j][i] for j in range(len(M))] for i in range(len(M))]107 S = sorted(reachable(M, zero) & reachable(T, zero))108 sub = [[M[i][j] for j in S] for i in S]109 return S, sub, all(len(reachable(sub, i)) == len(S) for i in range(len(S)))110111print("energy cap: strongly connected component of the zero state, Perron root, E_2K(G_a) <= lambda_2K^a")112for K in (2, 3, 4, 5):113 M, zero = carry_matrix(K)114 radius = (K - 1) // 2115 spread = max(max(abs(d1), abs(d2)) for d1, d2 in delta_counts(K))116 closed = (radius + spread) // 3 <= radius117 S, sub, irreducible = scc_zero(M, zero)118 root = max(mpmath.mpf(str(r)) for f, _ in sp.factor_list(sp.Matrix(sub).charpoly(x).as_expr())[1] for r in sp.Poly(f, x).nroots(n=25) if r.is_real)119 lam = mpmath.mpf(15) if K == 2 else perron[K]120 print("2K = %2d box radius %d digit spread %d closed %s scc %2d of %2d irreducible %s self loop %s perron equals lambda_2K %s cap holds a = 0..6 %s ratio at a = 6 %s" % (2 * K, radius, spread, closed, len(S), len(M), irreducible, M[zero][zero] > 0, abs(root - lam) < mpmath.mpf(10) ** -18, all(mpmath.mpf(rows[K][a]) <= lam**a for a in range(7)), mpmath.nstr(mpmath.mpf(rows[K][6]) / lam**6, 6)))121122QUARTIC = sp.Poly(x**4 - 7833 * x**3 + 7916949 * x**2 - 850684437 * x + 13054946580, x)123LO, HI = sp.Rational(66641136625, 10**7), sp.Rational(66641136626, 10**7)124print("lambda_10 certified by Sturm on the exact quartic: roots above %s %d, roots in the bracket %d, width %s" % (HI, QUARTIC.count_roots(HI, sp.oo), QUARTIC.count_roots(LO, HI), sp.nsimplify(HI - LO)))125KAP10 = 10 - mpmath.log(mpmath.mpf(HI.p) / HI.q) / mpmath.log(3)126127def truncate(v, d):128 q = int(mpmath.floor(v * mpmath.mpf(10) ** d))129 return "%d.%0*d" % (q // 10**d, d, q % 10**d)130131print("certified kappa_10 >= %s, beta_0^(10) >= %s, both truncated down at 12 places" % (truncate(KAP10, 12), truncate(beta(KAP10), 12)))132print("safe short edge, truncated down at 7 places: %s" % truncate(beta(KAP10), 7))133134print("ladder rungs: moments, kappa_2K, beta_0^(2K)")135kappas = {}136for K, lam in ((2, 15), (3, lam6), (4, lam8), (5, lam10)):137 kappas[K] = 2 * K - mpmath.log(lam) / mpmath.log(3)138 print("2K = %2d kappa %s beta %s" % (2 * K, mpmath.nstr(kappas[K], 13), mpmath.nstr(beta(kappas[K]), 12)))139print("tenth rung above eighth by %s" % mpmath.nstr(beta(kappas[5]) - beta(kappas[4]), 9))140print("Holder block exponents 2K = 6, 8, 10:", ", ".join(str(Fraction(2, 1) / (1 - Fraction(1, 2 * K))) for K in (3, 4, 5)))141142print("rows above the tenth, floating Perron roots of exact matrices")143for K in range(6, 11):144 M, _ = carry_matrix(K)145 lam = max(np.linalg.eigvals(np.array(M, dtype=float)).real)146 kappa = 2 * K - log3(lam)147 print("2K = %2d states %3d lambda %.6f beta %.9f wall - beta %.2e" % (2 * K, len(M), lam, beta(kappa), WALL - beta(kappa)))148149def primes(lo, hi):150 return list(sp.primerange(lo, hi + 1))151152def fourier(p, n):153 t = np.arange(p)154 e = np.exp(2j * np.pi * t / p)155 W = np.abs(1 + e[:, None] + e[None, :]) / 3156 F = np.ones((p, p))157 out = []158 for l in range(n):159 I = (t * pow(3, l, p)) % p160 F = F * W[np.ix_(I, I)]161 out.append(F.copy())162 return out163164print("moment identities on (Z/p)^2, primes 5..199")165checks, worst = 0, 0.0166for p in primes(5, 199):167 F = fourier(p, 6)168 for a in range(1, 6):169 targets = []170 if 3**a <= p:171 targets.append((2, p * p * 3.0 ** (-a)))172 if 2 * 3**a <= p:173 targets.append((4, p * p * (5 / 27) ** a))174 for K in (3, 4, 5):175 if K * (3**a - 1) < p and a < len(rows[K]):176 targets.append((2 * K, p * p * rows[K][a] / 3 ** (2 * K * a)))177 for power, target in targets:178 worst = max(worst, abs((F[a - 1] ** power).sum() - target) / target)179 checks += 1180print("identities checked %d, worst relative error %.1e" % (checks, worst))181182def half(p):183 a = 0184 while 2 * 3 ** (a + 1) <= p:185 a += 1186 return a187188KAPS = {K: float(kappas[K]) for K in (3, 4, 5)}189EDGE10 = KAPS[5] / (2 - KAPPA + 2 * KAPS[5])190191def master10(p, n):192 a = half(p)193 b = min(a - 1, n - 2 * a)194 if n < 2 * a or b < 0:195 return None196 return p * p * 3.0 ** (-((8 + KAPPA) * a + KAPS[5] * b) / 10)197198def master(p, n):199 a = half(p)200 bounds = [float(p * p)]201 if 3 ** (n // 2) <= p:202 bounds.append(p * p * 3.0 ** (-(n // 2)))203 if n >= 4 * a:204 bounds.append(p * p * 3.0 ** (-KAPPA * a))205 if n >= 3 * a:206 bounds.append(p * p * 3.0 ** (-(1 + KAPPA) * a / 2))207 if n >= 2 * a:208 b4 = min(a, n - 2 * a)209 bounds.append(p * p * 3.0 ** (-a / 2 - KAPPA * (a + b4) / 4))210 b = min(a - 1, n - 2 * a)211 if b >= 0:212 for K in (3, 4, 5):213 bounds.append(p * p * 3.0 ** (-((2 * K - 2 + KAPPA) * a + KAPS[K] * b) / (2 * K)))214 if n >= 2 * K * b:215 bounds.append(p * p * 3.0 ** (-KAPS[K] * b))216 return min(bounds)217218print("master bound against exact L_n(p), primes 5..199, n = 2..24, the min over all orders and the order-10 block alone")219solo, solo_worst, solo_where, solo_cases = 0, 0.0, None, 0220violations, worst, where = 0, 0.0, None221for p in primes(5, 199):222 for n, F in enumerate(fourier(p, 24), start=1):223 if n < 2:224 continue225 only = master10(p, n)226 if only is not None:227 solo_cases += 1228 r10 = F.sum() / only229 if r10 > 1 + 1e-9:230 solo += 1231 if r10 > solo_worst:232 solo_worst, solo_where = r10, (p, n)233 ratio = F.sum() / master(p, n)234 if ratio > 1 + 1e-9:235 violations += 1236 if ratio > worst:237 worst, where = ratio, (p, n)238print("min over all orders: violations %d, worst ratio L/bound %.4f at (p, n) = %s" % (violations, worst, where))239print("order-10 block alone: %d cases, violations %d, worst ratio L/bound %.4f at (p, n) = %s" % (solo_cases, solo, solo_worst, solo_where))240241def regime(p, n):242 a = half(p)243 if 2 * a > n:244 return None245 return "I" if n >= 4 * a else "II" if n >= 3 * a else "III"246247def tail_bound(p, n):248 a = half(p)249 r = regime(p, n)250 if r == "I":251 return p * p * (5 / 27) ** a252 if r == "II":253 return p * p * (5 / 81) ** (a / 2)254 return p * p * 3.0 ** (-a / 2) * (5 / 27) ** ((n - a) / 4)255256print("dyadic tail: exponents (kappa - 1)/8 = %.4f, (2 + kappa)/4 = %.4f" % ((KAPPA - 1) / 8, (2 + KAPPA) / 4))257print("tail constants 16/(kappa - 1) = %.2f, 10 (1 + kappa)/(kappa - 1) 2^((1 - kappa)/2) = %.2f, 8/(2 + kappa) = %.2f" % (16 / (KAPPA - 1), 10 * (1 + KAPPA) / (KAPPA - 1) * 2 ** ((1 - KAPPA) / 2), 8 / (2 + KAPPA)))258uncovered = [(p, n) for n in (8, 10, 12, 14, 16, 20, 24) for p in primes(5, 199) if p <= 3 ** (EDGE10 * n) and regime(p, n) is None]259print("regime cover below the order-10 edge 3^(%.12f n) for n in 8..24: uncovered %d" % (EDGE10, len(uncovered)))260fails, cases = 0, 0261for n in (8, 10, 12):262 for p in primes(5, 199):263 if regime(p, n) is None:264 continue265 cases += 1266 R = fourier(p, n)[-1].sum() - 1267 if R > tail_bound(p, n) * (1 + 1e-9):268 fails += 1269print("per-prime tail bounds at n = 8, 10, 12: %d cases, %d failures" % (cases, fails))270271LAMBDA = {K: 2 - KAPPA + 2 * KAPS[K] for K in (3, 4, 5)}272print("master-bound constants per order: Lambda_2K = 2 - kappa + 2 kappa_2K, edge kappa_2K/Lambda_2K, decay Lambda_2K/2K, geometric constant 2/(1 - 3^(-Lambda_2K/2K))")273for K in (3, 4, 5):274 print("2K = %2d Lambda %.9f edge %.12f decay %.9f constant %.4f" % (2 * K, LAMBDA[K], KAPS[K] / LAMBDA[K], LAMBDA[K] / (2 * K), 2 / (1 - 3.0 ** (-LAMBDA[K] / (2 * K)))))275276def gain10(a, n):277 return (KAPS[5] * n - LAMBDA[5] * a) / 10278279def four_term(n, z, eta):280 return 2 / z + 35 * z ** (1 - KAPPA) + 40 * 3.0 ** (-(KAPPA - 1) * n / 8) + 6 * 3.0 ** (-(LAMBDA[5] / 10) * eta * n)281282edge10 = EDGE10283bad, ratio = 0, 0.0284for eta in (0.001, 0.01, 0.05, 0.1):285 for n in range(6, 401):286 lo, hi = n // 3 + 1, int((edge10 - eta) * n)287 if hi < lo:288 continue289 block = [gain10(a, n) for a in range(lo, hi + 1)]290 total = sum(2 * 3.0 ** (-g) for g in block)291 cap = 6 * 3.0 ** (-(LAMBDA[5] / 10) * eta * n)292 if min(block) <= 0 or total > cap:293 bad += 1294 ratio = max(ratio, total / cap)295print("order-10 main range, primes with 3a > n: gains positive and geometric cap holds at eta in 0.001..0.1, n = 6..400, failures %d, worst sum/cap %.4f" % (bad, ratio))296297def energy_sum(p, n):298 t = np.arange(p)299 e = np.exp(2j * np.pi * t / p)300 W = np.abs(1 + e[:, None] + e[None, :]) / 3301 F = np.ones((p, p))302 for l in range(n):303 I = (t * pow(3, l, p)) % p304 F = F * W[np.ix_(I, I)]305 return F.sum()306307print("four-term dyadic bound against the exact prime sum, eta = 0.02")308for n in (6, 8, 10, 12):309 for z in (5, 11):310 top = 3.0 ** ((edge10 - 0.02) * n)311 total = sum(energy_sum(p, n) / (p * p) for p in primes(z + 1, int(top)))312 print("n = %2d z = %2d exact %.6f bound %.6f holds %s" % (n, z, total, four_term(n, z, 0.02), total <= four_term(n, z, 0.02)))