interval.py
14.0 kB · python · 362 lines
1import math2import sys3import time45import numpy as np6from mpmath import iv, mp78GAMMA = 0.57721566490153299ZETA3 = 1.202056903159594210C_P = math.sqrt(2) - 4 / math.pi11K_2 = 2 * (4 / 3 + (36 / 35) * (7 * ZETA3 / 8 - 1))12TAIL = 3.41314# CLOSED FORMS1516def x0_float(N):17 return (N / math.pi) * (math.log(N + 3) + GAMMA + math.log(math.tan(3 * math.pi / 8 + math.pi / (4 * N)))) + C_P * (N + 1) ** 2 / (8 * N)1819def lam_float(N):20 return (N + 1) / 2 + x0_float(N) + 0.5 / math.sin(math.pi / (2 * N))2122def rate_iv(N):23 iv.prec = 12024 N = iv.mpf(N)25 pi = iv.pi26 cp = iv.sqrt(2) - 4 / pi27 x0 = (N / pi) * (iv.log(N + 3) + iv.euler + iv.log(iv.tan(3 * pi / 8 + pi / (4 * N)))) + cp * (N + 1) ** 2 / (8 * N)28 n = (N + 1) / 229 return (n + x0 + 1 / (2 * iv.sin(pi / (2 * N)))) / n3031def c_inf_iv():32 iv.prec = 12033 pi = iv.pi34 return 1 + 2 / pi + (2 / pi) * (iv.euler + iv.log(1 + iv.sqrt(2))) + (iv.sqrt(2) - 4 / pi) / 43536def tail_gap_iv(N, d, c):37 iv.prec = 12038 N = iv.mpf(N)39 return N ** (iv.mpf(1) / d) - (2 / iv.pi) * iv.log(N) - c - TAIL / N4041def exact_gap_iv(N, d):42 iv.prec = 12043 return iv.mpf(N) ** (iv.mpf(1) / d) - rate_iv(N)4445def first_tail(e, c_hi, lo, hi):46 f = lambda N: N ** e - (2 / math.pi) * math.log(N) - c_hi - TAIL / N47 while hi - lo > 1:48 mid = (lo + hi) // 249 if f(mid) > 0:50 hi = mid51 else:52 lo = mid53 return hi5455def down(x, d=5):56 v = mp.mpf(x.a.a)57 k = d - 1 - int(mp.floor(mp.log10(abs(v))))58 return mp.nstr(mp.floor(v * 10 ** k) / mp.mpf(10) ** k, d)5960def up(x, d=5):61 v = mp.mpf(x.b.b)62 k = d - 1 - int(mp.floor(mp.log10(abs(v))))63 return mp.nstr(mp.ceil(v * 10 ** k) / mp.mpf(10) ** k, d)6465def fup(x, d=6):66 return f"{math.ceil(x * 10 ** d) / 10 ** d:.{d}f}"6768def fdown(x, d=6):69 return f"{math.floor(x * 10 ** d) / 10 ** d:.{d}f}"7071def gdown(x, d=5):72 k = d - 1 - math.floor(math.log10(x))73 return f"{math.floor(x * 10 ** k) / 10 ** k:.{d - 1}e}"7475def gup(x, d=3):76 k = d - 1 - math.floor(math.log10(x))77 return f"{math.ceil(x * 10 ** k) / 10 ** k:.{d - 1}e}"7879def odd_up(N):80 return N if N % 2 else N + 18182# WALL8384def wall_at(d, label, lo, hi, mono):85 e = 1 / d86 c = c_inf_iv()87 c_hi = float(c.b)88 na = first_tail(e, c_hi, lo, hi)89 while tail_gap_iv(na, d, c).a <= 0:90 na += 191 assert tail_gap_iv(na - 1, d, c).a <= 0 or na - 1 < mono92 assert na >= max(mono, 101)93 N = odd_up(na)94 checked = 095 while True:96 g = exact_gap_iv(N - 2, d)97 if g.a <= 0:98 break99 N -= 2100 checked += 1101 below = exact_gap_iv(N - 2, d)102 top = odd_up(na)103 n0 = N104 for M in range(n0, top + 1, 2):105 assert exact_gap_iv(M, d).a > 0106 margin = iv.mpf(1) / d - iv.log(rate_iv(n0)) / iv.log(n0)107 print(f" {label}: tail bound holds for every base >= {na} (monotone from {mono}), closed form certified at every odd base {n0}..{top} ({(top - n0) // 2 + 1} bases), fails at {n0 - 2} with gap <= {up(below)}")108 print(f" {label}: wall {n0}, bar - alpha_1 >= {down(margin)} there, gap >= {down(exact_gap_iv(n0, d))}")109 return n0110111def maynard_alpha(q, consecutive_half):112 L = math.log(q)113 if consecutive_half:114 return math.log((2 + 2 / L) * (2 * q / (q + 1)) * L) / L115 return math.log((1 + 3 / L) * (q / (q - 1)) * L) / L116117def maynard_cross(half):118 lo, hi = 10, 10 ** 14119 while hi - lo > 1:120 mid = (lo + hi) // 2121 if maynard_alpha(mid, half) < 0.2:122 hi = mid123 else:124 lo = mid125 return hi126127def wall():128 t0 = time.time()129 c = c_inf_iv()130 print(f"c_inf = 1 + 2/pi + (2/pi)(gamma + log(1 + sqrt 2)) + (sqrt 2 - 4/pi)/4 <= {up(c, 9)}, tail {TAIL}/base from base 101")131 worst = None132 for N in list(range(101, 3002, 2)) + [94939, 200001, 10 ** 6 + 1, 10 ** 8 + 1]:133 r = rate_iv(N)134 tl = (2 / iv.pi) * iv.log(N) + c + TAIL / N135 assert r.b < tl.a136 gap = tl - r137 worst = gap if worst is None or gap.a < worst.a else worst138 print(f" direct: lambda/fill <= (2/pi) log base + c_inf + {TAIL}/base at every odd base 101..3001 and at 94939, 200001, 10^6 + 1, 10^8 + 1, smallest gap >= {down(worst)}")139 n5 = wall_at(5, "bar 1/5", 1000, 10 ** 7, 327)140 n4 = wall_at(4, "bar 1/4", 100, 10 ** 7, 43)141 iv.prec = 120142 low = lambda N: (2 * iv.mpf(N) / (iv.pi * (N + 1))) * iv.log(iv.mpf(N + 2) / 5) - iv.mpf(1) / 4 - ((2 / iv.pi) * iv.log(N) - iv.mpf(131) / 100)143 assert all(low(N).a > 0 for N in range(9, 10 ** 4, 2))144 assert ((2 / iv.pi) * iv.log(9) - iv.mpf(131) / 100).a > 0 and ((2 / iv.pi) * iv.log(7) - iv.mpf(131) / 100).b < 0145 tail = iv.mpf(106) / 100 - (2 / iv.pi) * iv.log(5) - (2 / iv.pi) * iv.log(iv.mpf(103) / 5) / 102146 assert tail.a > 0147 print(f" lower bound: (2 base/(pi (base+1))) log((base+2)/5) - 1/4 >= (2/pi) log base - 1.31 > 0 at every odd base 9..9999, and from 101 by 1.06 - (2/pi) log 5 - (2/pi) log((base+2)/5)/(base+1) >= {down(tail)}; the factor is negative at 7")148 floor = lambda N: (iv.mpf(N) ** (iv.mpf(1) / 5) - 2 * iv.mpf(N) / (N + 1))149 f27 = min(N for N in range(3, 200, 2) if all(floor(M).a > 0 for M in range(N, 200, 2)))150 assert floor(f27 - 2).b < 0151 print(f" density floor: 1 - alpha_base < 1/5 at every odd base >= {f27}, fails at {f27 - 2}")152 for N in (100003, 10 ** 6 + 3, 10 ** 9 + 7):153 r = rate_iv(N)154 a1 = iv.log(r) / iv.log(N)155 print(f" base {N}: base^alpha_1 = lambda/fill <= {up(r, 8)}, (2/pi) log base = {2 / math.pi * math.log(N):.6f}, alpha_1 <= {up(a1, 7)}")156 one = maynard_cross(False)157 half = maynard_cross(True)158 assert one == 1520573159 print(f" Maynard 2022 alpha_q below 1/5: one missing digit from {one} (calibration), consecutive half interval from {half}")160 print(f"wall in {time.time() - t0:.1f}s")161162# CHECK163164def grid_sums(N, ts):165 n = (N + 1) // 2166 r = np.arange(N, dtype=np.float64)167 G = np.empty(len(ts))168 S = np.empty(len(ts))169 step = max(1, 4_000_000 // N)170 for i in range(0, len(ts), step):171 t = ts[i:i + step, None]172 u = (t + r[None, :]) / N173 num = np.abs(np.sin(np.pi * n * u))174 den = np.sin(np.pi * u)175 with np.errstate(divide="ignore", invalid="ignore"):176 h = np.where(den > 0, num / np.where(den > 0, den, 1), n)177 G[i:i + step] = h.sum(1)178 S[i:i + step] = num.sum(1)179 return G, S180181def check_base(N, T):182 n = (N + 1) // 2183 ts = np.linspace(0.0, 0.5, T)184 G, S = grid_sums(N, ts)185 sig = np.mod(n * ts, 1.0)186 Sc = np.cos(np.pi * (sig - 0.5) / N) / np.sin(np.pi / (2 * N))187 assert np.max(np.abs(S - Sc) / Sc) < 1e-9188 x0 = x0_float(N)189 lam = lam_float(N)190 sn = np.sin(np.pi * ts)191 gb = n + x0 + sn * (x0 / 2 + K_2 * N / (4 * math.pi))192 low = (N / math.pi) * math.log((N + 2) / 5) - n / 4193 r1 = np.max(G / gb)194 r2 = np.max((G + S / 2) / (lam * (1 + sn / 2)))195 r3 = np.min(G) / low if low > 0 else float("inf")196 assert r1 < 1 and r2 < 1 and r3 > 1197 assert K_2 * N / (2 * math.pi) <= n + np.min(S) / 2198 L = math.log(N)199 return r1, r2, r3, G[0] / n - 2 / math.pi * L, np.max(G) / n - 2 * math.sqrt(2) / math.pi * L, ts[np.argmax(G)], np.min(G) / n - 2 / math.pi * L200201def levels_mp(N, i, s):202 mp.dps = 40203 n = (N + 1) // 2204 tot = mp.mpf(0)205 Y = N ** i206 for a in range(Y):207 v = mp.mpf(s) + mp.mpf(a) / Y208 p = mp.mpf(1)209 for j in range(i):210 w = v * N ** j211 w = w - mp.floor(w)212 if w == 0:213 p *= n214 else:215 p *= abs(mp.sin(mp.pi * n * w) / mp.sin(mp.pi * w))216 tot += p217 return tot218219def pieces(N, ts):220 n = (N + 1) // 2221 J = (n - 2) // 2222 K = (n - 1) // 2223 worst = [-1e9, -1e9, -1e9]224 for t in ts:225 m = 2 * np.arange(J + 1) + 1.0226 bp = np.sum(1 / (m + t) + 1 / (m - t)) - (math.log(N + 3) + GAMMA + K_2 * t * t)227 k = np.arange(1, K + 1, dtype=np.float64)228 le = np.sum(1 / (t + 2 * k)) if K else 0.0229 ho = 2 * k - t230 sg = np.sum(np.where(ho / N <= t, 1 / ho, -1 / ho)) if K else 0.0231 ap = le + sg - (math.log(N + 4) - 0.0757)232 dl = (t + 2 * k) / N233 dh = (2 * k - t) / N234 q = np.sum(1 / (2 * np.cos(np.pi * dl / 2))) + np.sum(1 / (2 * np.cos(np.pi * dh / 2))) if K else 0.0235 bq = q - (N / math.pi) * math.log(math.tan(3 * math.pi / 8 + math.pi / (4 * N)))236 worst = [max(worst[0], bp), max(worst[1], ap), max(worst[2], bq)]237 assert max(worst) < 0238 return worst239240def check():241 t0 = time.time()242 w = [-1e9] * 3243 for N in list(range(3, 402, 2)) + [1001, 10001, 100001]:244 p = pieces(N, np.linspace(0.0, 0.5, 201))245 w = [max(a, b) for a, b in zip(w, p)]246 print(f"pieces at every odd base 3..401 and 1001, 10001, 100001 on 201 shifts, smallest margin under the bound: bP >= {gdown(-w[0])}, aP >= {gdown(-w[1])}, bQ >= {gdown(-w[2])}")247 worst = [0, 0, 1e9]248 for N in range(3, 402, 2):249 r1, r2, r3, *_ = check_base(N, 801)250 worst = [max(worst[0], r1), max(worst[1], r2), min(worst[2], r3)]251 print(f"every odd base 3..401 at 801 shifts in [0, 1/2]: max G/bound <= {fup(worst[0])}, max T phi/(lambda phi) <= {fup(worst[1])}, min G/lower >= {fdown(worst[2])}")252 print("base, max G/bound, max T phi/(lambda phi), min G/lower, G(0)/fill - (2/pi) log base, max G/fill - (2 sqrt2/pi) log base, argmax t, min G/fill - (2/pi) log base")253 for N in (1001, 4001, 10001, 30001, 100001):254 r1, r2, r3, g0, gm, tm, gmin = check_base(N, 401)255 print(f" {N}: <= {fup(r1)} <= {fup(r2)} >= {fdown(r3)}, readings {g0:.6f} {gm:.6f} {tm:.4f} {gmin:.6f}")256 print(f" gamma' + 1 = {(2 / math.pi) * (GAMMA + math.log(8 / math.pi)) + 1:.6f}")257 print("levels at 40 digits, every level 1..top: base, top level, sum at the last shift, (3/2) lambda^top, its ratio")258 worst = 0.0259 for N, top in ((3, 8), (5, 5), (7, 4), (9, 4), (11, 3), (13, 3), (15, 3), (17, 3), (21, 3)):260 lam = lam_float(N)261 for i in range(1, top + 1):262 for s in (0, 0.5, 1 / (2 * N), (math.sqrt(5) - 1) / 2):263 v = levels_mp(N, i, s)264 bound = 1.5 * lam ** i265 worst = max(worst, float(v) / bound)266 assert v < bound267 if i == top:268 print(f" {N} {i} {mp.nstr(v, 10)} {fup(bound, 2)} {fup(float(v) / bound)}")269 print(f" largest sum/bound over all 4 shifts and levels 1..top: <= {fup(worst)}")270 err = 0.0271 for N in (5, 7, 9):272 n = (N + 1) // 2273 for s in (0, 0.5, 1 / (2 * N), (math.sqrt(5) - 1) / 2):274 t = math.fmod(N * N * s, 1.0)275 u = (t + np.arange(N)) / N276 h = np.array([abs(math.sin(math.pi * n * w) / math.sin(math.pi * w)) if w > 0 else n for w in u])277 g, _ = grid_sums(N, u)278 two = float(np.sum(h * g))279 err = max(err, abs(two - float(levels_mp(N, 2, s))) / two)280 assert err < 1e-9281 print(f" transfer identity sum_2(s) = (T^2 1)(base^2 s) at bases 5, 7, 9 and 4 shifts, largest relative error <= {gup(err)}")282 print(f"check in {time.time() - t0:.1f}s")283284# RATE285286def power(N, M, it):287 n = (N + 1) // 2288 t = (np.arange(M) + 0.5) / M289 r = np.arange(N)290 phi = np.ones(M)291 lo = hi = 0.0292 for _ in range(it):293 new = np.zeros(M)294 for k in range(0, M, max(1, 2_000_000 // N)):295 u = (t[k:k + max(1, 2_000_000 // N), None] + r[None, :]) / N296 h = np.abs(np.sin(np.pi * n * u) / np.sin(np.pi * u))297 new[k:k + u.shape[0]] = (h * np.interp(u.ravel(), t, phi, period=1.0).reshape(u.shape)).sum(1)298 q = new / phi299 lo, hi = q.min(), q.max()300 phi = new / new.max()301 return lo / n, hi / n, phi.min()302303def rate():304 t0 = time.time()305 print("power iteration on 1000 cells, readings: base, lambda/fill - (2/pi) log base low/high, min phi/max phi, base^(1/5) - lambda/fill")306 for N in (101, 1001, 10001):307 lo, hi, pm = power(N, 1000, 30)308 print(f" {N}: {lo - 2 / math.pi * math.log(N):.5f} {hi - 2 / math.pi * math.log(N):.5f} {pm:.4f} {N ** 0.2 - hi:.4f}")309 print("power iteration on 300 cells near the route's own crossing")310 for N in (60001, 70001, 80001):311 lo, hi, pm = power(N, 300, 12)312 print(f" {N}: {lo - 2 / math.pi * math.log(N):.5f} {hi - 2 / math.pi * math.log(N):.5f} {pm:.4f} {N ** 0.2 - hi:.4f}")313 one = lambda N: grid_sums(N, np.array([0.5]))[0][0] / ((N + 1) // 2)314 for e in (0.25, 0.2):315 lo, hi = 1001, 2_000_001316 while hi - lo > 2:317 mid = odd_up((lo + hi) // 2)318 if one(mid) < mid ** e:319 hi = mid320 else:321 lo = mid322 print(f" one-step reading G(1/2)/fill < base^{e} first at odd base {hi}, G(1/2)/fill - (2 sqrt2/pi) log base = {one(hi) - 2 * math.sqrt(2) / math.pi * math.log(hi):.5f} there")323 print(f"rate in {time.time() - t0:.1f}s")324325# METER326327def meter():328 t0 = time.time()329 X = 10 ** 7330 mu = np.ones(X + 1, dtype=np.int8)331 mu[0] = 0332 isp = np.ones(X + 1, dtype=bool)333 isp[:2] = False334 for p in range(2, int(X ** 0.5) + 1):335 if isp[p]:336 isp[p * p::p] = False337 for p in np.nonzero(isp)[0]:338 mu[p::p] *= -1339 if p * p <= X:340 mu[p * p::p * p] = 0341 k = np.arange(X + 1, dtype=np.int64)342 logk = np.log(np.maximum(k, 1))343 print("sanity only: base, x, A(x), M(x), max |M|/A, max |M|/sqrt(A), primes: sum log p/(kappa A) at x, kappa = p/(p+1)")344 for N in (101, 1009, 10007):345 h = (N - 1) // 2346 ok = np.ones(X + 1, dtype=bool)347 y = k.copy()348 while y.any():349 ok &= (y % N) <= h350 y //= N351 ok[0] = False352 A = np.cumsum(ok)353 M = np.cumsum(np.where(ok, mu, 0).astype(np.int64))354 sel = A >= 100355 th = np.sum(logk[ok & isp])356 kap = N / (N + 1)357 print(f" {N}: {X} {A[-1]} {M[-1]} {np.max(np.abs(M[sel]) / A[sel]):.4f} {np.max(np.abs(M[sel]) / np.sqrt(A[sel])):.3f} {th / (kap * A[-1]):.4f}")358 print(f"meter in {time.time() - t0:.1f}s")359360if __name__ == "__main__":361 verb = sys.argv[1] if len(sys.argv) > 1 else "wall"362 {"wall": wall, "check": check, "rate": rate, "meter": meter}[verb]()