gaussian_zeta.py
7.9 kB · python · 229 lines
1import math2import time3from fractions import Fraction45import numpy as np6from mpmath import mp, mpf, bernoulli, catalan, zeta78DIGITS = 909CORNERS = [(0, 0), (0, 1), (1, 0), (1, 1)]10DIAGONAL = [(0, 0), (1, 1)]11MATE = [(0, 1), (1, 0)]12EXACT_TO = 8013LIMITS = [49, 199, 999]14SHELL_MAX = 100000015RADIUS = 600016STATED = {17 1: "1/24", 2: "1/4", 3: "7/24", 4: "1/4", 5: "7/24", 6: "1/2", 7: "13/24",18 8: "1/8", 9: "1/6", 10: "3/8", 11: "5/12", 12: "3/8", 13: "5/12",19 14: "5/8", 15: "2/3",20}212223def code_of(filled):24 return sum(1 << k for k, cell in enumerate(CORNERS) if cell in filled)252627def fill(filled, n):28 table = np.zeros((2, 2), dtype=bool)29 for i, j in filled:30 table[i, j] = True31 parity = np.arange(n) % 232 return int(np.count_nonzero(table[parity[:, None], parity[None, :]]))333435def fluctuation(filled, n):36 return Fraction(fill(filled, n), n * n) - Fraction(len(filled), 4)373839def pi_machin():40 def arctan_inv(x):41 total = mpf(0)42 term = mpf(1) / x43 k = 044 while term > mpf(10) ** (-DIGITS - 10):45 total += term / (2 * k + 1) * (1 if k % 2 == 0 else -1)46 term /= x * x47 k += 148 return total4950 return 16 * arctan_inv(5) - 4 * arctan_inv(239)515253def odd_zeta(s, m=600, k=14):54 total = sum(mpf(1) / mpf(2 * j - 1) ** s for j in range(1, m))55 a = mpf(2 * m - 1)56 total += a ** (1 - s) / (2 * (s - 1)) + a ** (-s) / 257 for step in range(1, k + 1):58 poch = mpf(1)59 for i in range(2 * step - 1):60 poch *= s + i61 derivative = -(mpf(2) ** (2 * step - 1)) * poch * a ** (-(s + 2 * step - 1))62 total -= bernoulli(2 * step) / mp.factorial(2 * step) * derivative63 return total646566def lambda_part():67 print("DIAGONAL DESIGN, FILLED CORNERS (0,0) AND (1,1), CODE", code_of(DIAGONAL))68 even_ok = True69 odd_ok = True70 for n in range(1, EXACT_TO + 1):71 value = fluctuation(DIAGONAL, n)72 if n % 2 == 0:73 even_ok = even_ok and value == 074 else:75 odd_ok = odd_ok and value == Fraction(1, 2 * n * n)76 if n <= 11:77 print(f" n={n:2d} fill {fill(DIAGONAL, n):4d} fluctuation {value}")78 print(f" even n <= {EXACT_TO} fluctuation exactly 0: {even_ok}")79 print(f" odd n <= {EXACT_TO} fluctuation exactly 1/(2n^2): {odd_ok}")80 forms_ok = all(81 fill(DIAGONAL, 2 * m) == 2 * m * m82 and fill(DIAGONAL, 2 * m - 1) == m * m + (m - 1) ** 283 for m in range(1, EXACT_TO // 2 + 1)84 )85 print(f" fill(2m) = 2m^2 and fill(2m-1) = m^2 + (m-1)^2: {forms_ok}")86 mate_ok = all(87 fluctuation(MATE, n) == (0 if n % 2 == 0 else Fraction(-1, 2 * n * n))88 for n in range(1, 41)89 )90 print(f" orbit mate code {code_of(MATE)} fluctuation -1/(2n^2) on odd n <= 40: {mate_ok}")91 print()9293 print("EXACT RATIONAL REDUCTION")94 z2 = Fraction(1, 2) * (1 - Fraction(1, 16)) / 9095 z4 = Fraction(1, 2) * (1 - Fraction(1, 64)) / 94596 print(f" (1/2)(1-2^-4)/90 = {z2} target 1/192 {z2 == Fraction(1, 192)}")97 print(f" (1/2)(1-2^-6)/945 = {z4} target 1/1920 {z4 == Fraction(1, 1920)}")98 print()99100 print(f"CONSTANTS AT {DIGITS} DIGITS")101 mp.dps = DIGITS102 machin = pi_machin()103 print(f" pi machin {mp.nstr(machin, 60)}")104 print(f" pi library {mp.nstr(mp.pi, 60)}")105 print(f" pi difference {mp.nstr(abs(machin - mp.pi), 3)}")106 for s in (4, 6):107 direct = odd_zeta(s)108 identity = (1 - mpf(2) ** (-s)) * zeta(s)109 print(f" lambda({s}) two routes differ {mp.nstr(abs(direct - identity), 3)}")110 targets = {}111 cases = ((2, 4, 192, "0.50733901580"), (4, 6, 1920, "0.50072353832"))112 for s, power, denom, prefix in cases:113 left = odd_zeta(s + 2) / 2114 right = machin ** power / denom115 targets[s] = right116 text = mp.nstr(left, 50)117 print(f" Z({s}) = lambda({s + 2})/2 {text}")118 print(f" pi^{power}/{denom:<5} {mp.nstr(right, 50)}")119 gap = mp.nstr(abs(left - right), 3)120 print(f" difference {gap} page prefix {prefix}: {text.startswith(prefix)}")121 print()122123 print("SERIES SUMMED FROM COUNTED FILLS, NO CLOSED FORM")124 counted = [fluctuation(DIAGONAL, n) for n in range(1, LIMITS[-1] + 1)]125 for s in (2, 4):126 total = mpf(0)127 for n, value in enumerate(counted, start=1):128 if value:129 total += mpf(value.numerator) / value.denominator / mpf(n) ** s130 if n in LIMITS:131 gap = abs(total - targets[s])132 print(f" s={s} n <= {n:4d} partial {mp.nstr(total, 16)} gap {mp.nstr(gap, 2)}")133 print()134135136def shell_counts(nmax):137 bound = math.isqrt(nmax)138 axis = np.arange(-bound, bound + 1)139 norm = axis[:, None] ** 2 + axis[None, :] ** 2140 cls = (np.abs(axis)[:, None] % 2) * 2 + (np.abs(axis)[None, :] % 2)141 keep = norm <= nmax142 return [np.bincount(norm[keep & (cls == c)], minlength=nmax + 1) for c in range(4)]143144145def chi4_excess(nmax):146 out = np.zeros(nmax + 1, dtype=np.int64)147 for d in range(1, nmax + 1, 2):148 out[d::d] += 1 if d % 4 == 1 else -1149 return out150151152def q_poly(a, t):153 return a[0] * t * t + a[3] * t * (1 - t) + (a[1] + a[2]) * (1 - t) / 2154155156def indicators(code):157 return [(code >> k) & 1 for k in range(4)]158159160def lattice_class_sums(radius):161 axis = np.arange(-radius, radius + 1, dtype=np.float64)162 limit = float(radius) ** 2163 out = [0.0] * 4164 for ri in range(2):165 for rj in range(2):166 rows = axis[np.abs(axis) % 2 == ri]167 cols = axis[np.abs(axis) % 2 == rj]168 squares = cols * cols169 pieces = []170 for i in rows:171 norm = i * i + squares172 norm = norm[(norm <= limit) & (norm > 0)]173 pieces.append(float(np.sum(1.0 / (norm * norm))))174 out[ri * 2 + rj] = math.fsum(pieces)175 return out176177178def gaussian_part():179 print(f"SHELL IDENTITIES ON Z^2, n <= {SHELL_MAX}")180 ee, eo, oe, oo = shell_counts(SHELL_MAX)181 r2 = ee + eo + oe + oo182 print(f" r2(n) = 4(d1(n) - d3(n)): {bool(np.all(r2[1:] == 4 * chi4_excess(SHELL_MAX)[1:]))}")183 want = np.zeros(SHELL_MAX + 1, dtype=np.int64)184 want[4::4] = r2[1 : SHELL_MAX // 4 + 1]185 print(f" S_ee(4m) = r2(m), zero elsewhere: {bool(np.all(ee[1:] == want[1:]))}")186 want = np.zeros(SHELL_MAX + 1, dtype=np.int64)187 want[2::2] = (eo + oe)[1 : SHELL_MAX // 2 + 1]188 print(f" S_oo(2m) = S_mix(m), zero elsewhere: {bool(np.all(oo[1:] == want[1:]))}")189 print(f" S_eo = S_oe: {bool(np.all(eo == oe))}")190 print(" so S_mix = (1-t) S and Q_c(t) = a_ee t^2 + a_oo t(1-t) + (a_eo + a_oe)(1-t)/2")191 print()192193 print("THE FIFTEEN NONEMPTY DESIGNS AT s = 2, Z_c(2) / (pi^2 G)")194 mp.dps = 50195 for code in range(1, 16):196 value = q_poly(indicators(code), Fraction(1, 4)) * Fraction(2, 3)197 ok = str(value) == STATED[code]198 print(f" code {code:2d} {str(value):>5} page {STATED[code]:>5} {ok}")199 z7 = mpf(13) / 24 * mp.pi ** 2 * catalan200 print(f" code 7 (Sierpinski) 13 pi^2 G / 24 = {mp.nstr(z7, 20)} page 4.8967847822")201 print()202203 print(f"TRUNCATED LATTICE SUMS, RADIUS {RADIUS}, TAIL pi/(4 R^2) PER CLASS")204 sums = lattice_class_sums(RADIUS)205 tail = math.pi / (4.0 * RADIUS * RADIUS)206 base = 4.0 * (math.pi ** 2 / 6.0) * float(catalan)207 worst = 0.0208 for code in range(1, 16):209 a = indicators(code)210 measured = sum(sums[k] for k in range(4) if a[k]) + tail * sum(a)211 predicted = base * float(q_poly(a, Fraction(1, 4)))212 gap = abs(measured - predicted)213 worst = max(worst, gap)214 print(f" code {code:2d} predicted {predicted:.12f} measured {measured:.12f} gap {gap:.1e}")215 print(f" worst gap {worst:.1e}")216 print()217218219def main():220 start = time.time()221 lambda_part()222 gaussian_part()223 print(f"DOMAIN fluctuation n <= {EXACT_TO}, series n <= {LIMITS[-1]}, "224 f"shells n <= {SHELL_MAX}, radius {RADIUS}")225 print(f"WALL {time.time() - start:.1f} s")226227228if __name__ == "__main__":229 main()