"""cubtop.py -- does the place with residue field F_2 top the thermal place spectrum over every cubic field that has one? QUESTION. quadtop.py proves the F_2 place tops every quadratic field by a finite reduction; fieldtop.py finds it losing at a cyclic quartic (conductor 272). A cubic field holds up to three places of each norm, a place of norm 4 beside an F_2 place (2 = Q1 Q2), and a tame place over 2 (e = 3). Does the reduction carry to degree 3 and prove it there, or name the configurations where it fails? THE ARGUMENT. K cubic with a place of residue field F_2: 2 of type 111, 12, 1^2 1 or 1^3 (a residue degree 1 place); x = 2^-b. (1) THE COMPARISON (fieldtop.py's (1)) and the F_2 column (fieldtop.py's (2)): sigma_2 = T2 prod_R (1 + N(R)^-b), T2 = (1 - x)^-(g - 1), g = 3, 1, 2, 1 F_2 places for 111, 12, 1^2 1, 1^3. A norm in K is p, p^2 or p^3; p^3 - 1 is never a power of 2 and p^2 - 1 only at p = 3, so the Fermat norms are 3, 5, 9, 17, 257, 65537 and the Fermat primes past them, at most three places each, their factor below exp(2 n F_5^-b), n = 3. (2) THE EVEN COLUMNS. At 111 and 1^2 1 every place over 2 is an F_2 place, and each F_2 column holds the others without bound (1 | 1) and the Fermat-norm places at depth 1: one sum, one point. At 12 the place Q2 of norm 4 holds Q1 without bound (1 | 3) and each Fermat-norm place R at depth >= 1 (lambda(R) = 2^k | 3 2^oo), while the F_2 column holds no place over 2 (3 does not divide 1): C_F2 is inside C_Q2, so Q2's point is the lower at every b. Only odd columns race. (3) THE CHAIN AT ANY e. At Q over 2 with f = 1, v(2) = e, (1 + y)^2 - 1 = y (2 + y) has valuation >= t + min(e, t) =: phi_e(t) where v(y) >= t, and phi_e increases. So u^(2^v) = 1 mod Q^(phi_e^v(1)) for every 1-unit u, and an odd column of norm N, v = v_2(N - 1), holds Q to depth D_e(v) >= phi_e^v(1): v + 2 at e = 1 (the (Z/2^j)^* chain, exact), 2, 4, 6, 8 at e = 2, and 2, 4, 7, 10, ..., 3 v - 2 from v = 2 at e = 3. The place Q2 of norm 4 has lambda(Q2^j) | 3 2^(j-1) (F_4^* times 1 + 2y, which gains valuation >= 1 per squaring); a column over 3 has 3 | (N - 1) 3^oo, so holds Q2 to depth >= v + 1. Other columns drop Q2 (a factor >= 1). (4) THE BOUND (quadtop.py's (3), n = 3). sigma_P / sigma_2 >= A X / (T2 W): A the F_2 places' 1 + x + ... + x^(D_e(v)), times Q2's 1 + 4^-b + ... + 4^(-b(v+1)) at a column over 3; X = prod 1/(1 - N(R)^-2b) over the places R != P over P's prime with Fermat norm 2^k + 1, k <= v (held without bound, against sigma_2's depth 1); W = P's own 1 + N^-b where N is a Fermat norm, times every Fermat-norm place with k > v, times the tail. (5) THE FLOOR AND THE CEILING. zeta_K >= its local factors at 2, 3, 5, 17, 257, 65537 times (1 - p^-3b)^-1 at every other p (the inert factor, the least of the five types), less the bound on sigma_2: every b where this is >= 1 lies at or below beta_F2. Psi_2 <= zeta_K - 1 <= zeta^3 - 1, so beta_F2 < b_top, the least b with zeta(b)^3 < 2. (6) THE CLASSES AND THE BRACKET (quadtop.py's (5)-(6)). 4 types of 2 times 5 types of each of 3, 5, 17, 257, 65537 (111, 12, 3, 1^2 1, 1^3): 12500 configurations. A Galois cubic is cyclic, ramified only at 3 and at primes = 1 mod 3: with an F_2 place, 2 is 111, 3 is 111, 3 or 1^3, and 5, 17, 257, 65537 (each = 2 mod 3) are 111 or 3, 48 configurations. Generic classes v = 1, 2, 3, 4, 8, 16 and each Fermat-norm place's own class (v = k). DESIGN. One grid G = 1.0001 + 1e-4 i up to 4, every power of it cached. b_low: the largest point of G where the floor of (5) holds. b_top: the point past the last one with zeta^3 >= 2. R3: the bracket on G from b_low past b_top at every class of every configuration; for a failing column, the largest point of G where A X <= T2 W (b_c, the bound's own crossing, a print). Codes: s = 111, q = 12, i = 3, r = 1^2 1, t = 1^3; a config is the types of 3, 5, 17, 257, 65537. CONTROLS. (C) quadtop.py's quartic of conductor 272 through this engine: A X = 1.472359 < T2 W = 1.490681 at 1.54337, floor 1.4793. (C2) the twelve quadratic floors, each under its F_2 point. (CQ) the 486 quadratic configurations reprint quadtop.py's R: 0 failing, least 1.005847 (2 ramified, N = 3 own, config rssss). (C4) brute exponents of U^1 mod Q^j: Z_2[pi], pi^e = 2, e = 1, 2, 3, j <= 10, each lambda(Q^j) <= 2^v at j = D_e(v) for v = 1, 2, 3 (the deeper v the proof's alone); Z_2[omega] mod 2^j, j <= 5, the exponent divides 3 2^(j-1). PREDICTIONS, fixed before the first run. (h1) k > 0: the column of norm 3 own fails where 2 and 3 are 1^3 and 5, 17, 257, 65537 are 111. (h2) every configuration with 2 of type 111 passes. (h3) type 12 at 2 passes every class. (h4) failures sit only where 2 is 1^3 or 1^2 1, at v = 1 classes. KILL, as the print: "R3 ... k failing columns of 12500 configurations". k = 0 proves degree 3; k > 0 names leads, not losses, until a field realizes one. The verdicts are checks since: R3 its 675 failing columns, each the column of norm 3's own class at its own configuration, 2 of type 1^2 1 or 1^3; RG none failing. FINDINGS. C4: lambda(Q^j), j = 1..10, is 1, 2, 2, 4, 8, ..., 256 at e = 1; 1, 2, 4, 4, 4, 8, 8, 16, 16, 32 at e = 2; 1, 2, 4, 4, 8, 8, 8, 16, 16, 16 at e = 3 (2^ceil(log2 j) to j = 7, not at 8); the norm-4 exponent 3, 6, 12, 24, 48. C: A X = 1.472359 < T2 W = 1.490681, floor 1.4793. C2: floors 1.4458 to 1.5659. CQ: 0 failing, least 1.005847 at 2 r, rssss, N = 3 own. beta_F2 < 2.7511. R3: 675 failing columns of 12500 configurations, every one the column of norm 3's own class, 250 where 2 is 1^2 1 (least 0.958740) and 425 where 2 is 1^3 (least 0.901468, config qssss, b_low 1.5377, the bound's crossing b_c 1.8580). RG: the 48 cyclic configurations, 0 failing, least 1.025386 at tssss, the column of norm 3. Every failing configuration has 5 of type 111 or 1^2 1 (two or three places of norm 5), and 3 of type 12 or 1^3, or 1^2 1 (25, at 2 = 1^3 with 5 of type 111). Five (2, 3, 5) cells fail at all 125 configurations of 17, 257, 65537, each named by its nearest to passing: 2 = 1^2 1 with 3 = 12, 5 = 111 (qsiii, 0.977731) and 3 = 1^3, 5 = 111 (tsiii, 0.997619); 2 = 1^3 with 3 = 12, 5 = 1^2 1 (qriii, 0.998137, b_low 1.4628, b_c 1.4699), 3 = 12, 5 = 111 (qsiii, 0.930576) and 3 = 1^3, 5 = 111 (tsiii, 0.958309); two cells fail in part. So conditions at 2, 3 and 5 alone put a field in a failing configuration. The least ratio per 2's type at the norm-3 column: 1.011007 (111), 1.077337 (12), 0.958740 (1^2 1), 0.901468 (1^3); every generic class passes, the least v = 1 at 1.061189 (1^3, issss), every other class above 1.15. (h1) holds as to a failure and as to tssss failing; the least is qssss, where the F_2 column also holds a place of norm 9. (h2) holds. (h3) holds. (h4) holds: only 1^2 1 and 1^3, only the column of norm 3. READING. The reduction does not prove degree 3, and proves the cyclic cubic fields: the F_2 place tops every one with an F_2 place. The race is lost by the bound where 2 has one or two F_2 places, one ramified, and the F_2 column holds two or three places of norm 5 beside one or two of norm 3 and often one of norm 9, while the column of norm 3 is credited only the F_2 places to depth 2 (3 at the unramified one of 1^2 1) and its own conjugates over 3. An actual such field also credits that column every place of norm 2 3^a + 1 (7, 19, 163, 487, 1459) at depth 1, and its zeta_K exceeds the floor; whether one loses is a race read at a field. RUN RECORD. `python cubtop.py`: 6 of 6 checks, 122.6 s, peak commit 71.9 MB. """ import os import sys from itertools import product os.environ.setdefault("OPENBLAS_NUM_THREADS", "1") sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import numpy as np # noqa: E402 from quadtop import REL, FERMAT_NORMS, F5, STEP, quad_profile # noqa: E402 from fieldtop import kronecker # noqa: E402 from scipy.special import zeta # noqa: E402 CHECKS = [] TWO = {"s": [(1, 1, 3)], "q": [(1, 1, 1), (1, 2, 1)], "r": [(2, 1, 1), (1, 1, 1)], "t": [(3, 1, 1)]} def check(name, ok, detail=""): CHECKS.append(bool(ok)) print(f" [{'PASS' if ok else 'FAIL'}] {name}" + (f" ({detail})" if detail else "")) class Grid: """The points b, with every power N^-b cached.""" def __init__(self, b): self.b = np.atleast_1d(np.asarray(b, dtype=float)) self._pw, self._z = {}, {} def pw(self, N): if N not in self._pw: self._pw[N] = float(N) ** -self.b return self._pw[N] def zeta(self, n): if n not in self._z: self._z[n] = zeta(n * self.b) return self._z[n] def geo(self, N, d): """1 + N^-b + ... + N^(-b d).""" y = self.pw(N) return (1 - y ** (d + 1)) / (1 - y) def depth(e, v): """D_e(v): the depth an odd column with v holds an F_2 place of ramification e, by (3).""" if e == 1: return v + 2 t = 1 for _ in range(v): t += min(e, t) return t class Profile: """A field at its relevant primes: degree n, the places over 2 as (e, f, count), and per odd p a list of (norm, count).""" def __init__(self, n, two, odd): self.n, self.two, self.odd = n, two, odd def g2(self): return sum(c for e, f, c in self.two if f == 1) def zeta_floor(self, g): n = self.n z = g.zeta(n) * (1 - g.pw(2 ** n)) for e, f, c in self.two: z = z / (1 - g.pw(2 ** f)) ** c for p, pl in self.odd.items(): z = z * (1 - g.pw(p ** n)) for N, c in pl: z = z / (1 - g.pw(N)) ** c return z def fermat_places(self): """[(p, N, k, count)] of the odd places of Fermat norm.""" return [(p, N, FERMAT_NORMS[N], c) for p, pl in self.odd.items() for N, c in pl if N in FERMAT_NORMS] def tail(self, g): return np.exp(self.n * 2 * F5 ** -g.b) def sigma2(self, g): s = (1 - g.pw(2)) ** -(self.g2() - 1) for p, N, k, c in self.fermat_places(): s = s * (1 + g.pw(N)) ** c return s * self.tail(g) def b_low(self, g): """The largest point of g where the floor of (5) holds.""" idx = np.nonzero(self.zeta_floor(g) - self.sigma2(g) >= 1)[0] return None if len(idx) == 0 else int(idx[-1]) def columns(self): """(label, v, own, conj, over3) per column class: conj the norms of the other places over P's prime held without bound, each with whether it is a Fermat norm.""" out = [(f"v={v} generic", v, None, (), False) for v in (1, 2, 3, 4, 8, 16)] for p, N, k, c in self.fermat_places(): conj = [(N, True)] * (c - 1) for N2, c2 in self.odd[p]: if N2 != N and N2 in FERMAT_NORMS \ and FERMAT_NORMS[N2] <= k: conj += [(N2, True)] * c2 out.append((f"N={N} own", k, N, tuple(conj), p == 3)) return out def column_ratio(P, g, v, own=None, conj=(), over3=False): """(A X, T2 W) on g for an odd column with v, by (4).""" A = 1.0 for e, f, c in P.two: if f == 1: A = A * g.geo(2, depth(e, v)) ** c elif f == 2 and over3: A = A * g.geo(4, v + 1) ** c X = 1.0 for N, fermat in conj: y = g.pw(N) X = X / ((1 - y * y) if fermat else (1 - y)) W = (1 + g.pw(own)) if own else 1.0 for p, N, k, c in P.fermat_places(): if k > v: W = W * (1 + g.pw(N)) ** c W = W * P.tail(g) T2 = (1 - g.pw(2)) ** -(P.g2() - 1) return A * X, T2 * W def cubic_profile(t2, types): odd = {} for p, t in zip(REL, types): odd[p] = {"s": [(p, 3)], "q": [(p, 1), (p * p, 1)], "i": [(p ** 3, 1)], "r": [(p, 2)], "t": [(p, 1)]}[t] return Profile(3, TWO[t2], odd) def as_profile(Q): return Profile(Q.n, Q.two, Q.odd) # --------------------------------------------------------------------------- # controls def brute_chain(e, j): """The exponent of U^1 mod pi^j in Z_2[pi], pi^e = 2.""" M = -(-j // e) + 2 mod = 2 ** M def mul(a, b): out = [0] * e for i, ai in enumerate(a): for k, bk in enumerate(b): if i + k < e: out[i + k] += ai * bk else: out[i + k - e] += 2 * ai * bk return [c % mod for c in out] def val(a): best = 10 ** 9 for i, ai in enumerate(a): if ai % mod: w = 0 while ai % 2 == 0: ai //= 2 w += 1 best = min(best, e * w + i) return best lam = 1 ranges = [range(2 ** -(-(j - i) // e)) if j > i else range(1) for i in range(e)] for a in product(*ranges): if a[0] % 2 == 0: continue u, order = list(a), 1 one = [(u[0] - 1) % mod] + u[1:] while val(one) < j: u = mul(u, u) order *= 2 one = [(u[0] - 1) % mod] + u[1:] lam = max(lam, order) return lam def brute_norm4(j): """The exponent of (Z_2[omega] / 2^j)^*, omega^2 = -1 - omega.""" mod = 2 ** j def mul(a, b): return ((a[0] * b[0] - a[1] * b[1]) % mod, (a[0] * b[1] + a[1] * b[0] - a[1] * b[1]) % mod) lam = 1 for a0 in range(mod): for a1 in range(mod): if a0 % 2 == 0 and a1 % 2 == 0: continue u, order = (a0, a1), 1 while u != (1, 0): u = mul(u, (a0, a1)) order += 1 lam = np.lcm(lam, order) return int(lam) def control_chain(): print("CONTROL C4: the chains over 2, brute") ok = True for e in (1, 2, 3): lams = [brute_chain(e, j) for j in range(1, 11)] print(f" e={e}: lambda(Q^j), j = 1..10: {lams}") for v in (1, 2, 3): d = depth(e, v) if d <= 10: ok &= lams[d - 1] <= 2 ** v print(f" v={v}: D_e(v) = {d}, lambda = {lams[d - 1]} " f"<= {2 ** v}") l4 = [brute_norm4(j) for j in range(1, 6)] ok4 = all((3 * 2 ** (j - 1)) % lam == 0 for j, lam in zip(range(1, 6), l4)) print(f" norm 4: exponent mod 2^j, j = 1..5: {l4}") check("C4 every D_e(v) read is held at v; the norm-4 exponent " "divides 3 2^(j-1)", ok and ok4) def control(): print("CONTROL C: the cyclic quartic of conductor 272") P = Profile(4, [(4, 1, 1)], {3: [(9, 2)], 5: [(5, 4)], 17: [(17, 1)], 47: [(47, 4)]}) b2 = 1.54337 g = Grid(b2) x = 2 ** -b2 A = 1 + x + x * x X = (1 - 47 ** -b2) ** -3 W = (1 + 5 ** -b2) ** 4 * (1 + 9 ** -b2) ** 2 * (1 + 17 ** -b2) ax, tw = column_ratio(P, g, 1, conj=((47, False),) * 3) ax, tw = float(ax[0]), float(tw[0]) print(f" at b = {b2}: A X = {ax:.6f} (by hand {A * X:.6f}), " f"T2 W = {tw:.6f} (by hand {W:.6f})") G = Grid(1.0001 + STEP * np.arange(29999)) bl = G.b[P.b_low(G)] print(f" floor b_low = {bl:.4f}") check("C the norm-47 column beats the F2 column at its F2 point, and " "the floor sits below that point", ax < tw and abs(ax - A * X) < 1e-9 and abs(tw / W - 1) < 1e-8 and bl <= b2) return G def control_quadratic(G): print("\nCONTROL C2: the floor under the twelve quadratic fields read") pts = {-7: 1.66168, 17: 1.60213, -8: 1.63177, 161: 1.69138, 24: 1.56837, 156: 1.66461, 204: 1.66621, 264: 1.61826, -24: 1.67138, -84: 1.65813, -264: 1.69169, -276: 1.68330} ok = True for D, b2 in pts.items(): ty = "".join({1: "s", 0: "r", -1: "i"}[kronecker(D, p)] for p in (2,) + REL) i = as_profile(quad_profile(ty[0], ty[1:])).b_low(G) ok &= i is not None and G.b[i] <= b2 print(f" D={D}: config {ty}, floor {G.b[i]:.4f} <= F2 point {b2}") check("C2 every floor at or below its field's F2 point", ok) def top_index(G, n): """The point past the last one of G with zeta^n >= 2.""" return int(np.nonzero(G.zeta(1) ** n >= 2)[0][-1]) + 1 def sweep(G, profiles, top): """The bracket of (6) at every class of every profile: (failing list, least per (2's type, class), least overall).""" bad, worst, least = [], {}, None for key, P in profiles: lo = P.b_low(G) assert lo is not None and lo < top for label, v, own, conj, over3 in P.columns(): ax, tw = column_ratio(P, G, v, own, conj, over3) r = ax[lo + 1:top + 2] / tw[lo:top + 1] m = float(r.min()) if not (r > 1).all(): lose = np.nonzero(ax <= tw)[0] bc = G.b[lose[-1]] if len(lose) else None bad.append((key, label, G.b[lo], bc, m)) wk = (key[0], label) if wk not in worst or m < worst[wk][0]: worst[wk] = (m, key[1]) if least is None or m < least[0]: least = (m, key, label, G.b[lo]) return bad, worst, least def control_quad_sweep(G): print("\nCONTROL CQ: quadtop.py's 486 configurations through this " "engine") top = top_index(G, 2) profs = [((t2, "".join(ty)), as_profile(quad_profile(t2, ty))) for t2 in ("s", "r") for ty in product("sri", repeat=5)] bad, worst, least = sweep(G, profs, top) m, key, label, bl = least print(f" b_top {G.b[top]:.4f}; {len(bad)} failing; least {m:.6f} at " f"2 {key[0]}, config {key[1]}, {label}, b_low {bl:.4f}") check("CQ 0 failing, least 1.005847 at 2 r, rssss, N=3 own", not bad and abs(m - 1.005847) < 5e-7 and key == ("r", "rssss") and label == "N=3 own") def main(): control_chain() G = control() control_quadratic(G) control_quad_sweep(G) if not all(CHECKS): print("a control failed: no verdict") sys.exit(1) top = top_index(G, 3) print(f"\nbeta_F2 < {G.b[top]:.4f} at every cubic field (zeta^3 < 2)") profs = [((t2, "".join(ty)), cubic_profile(t2, ty)) for t2 in "sqrt" for ty in product("sqirt", repeat=5)] bad, worst, least = sweep(G, profs, top) print(f"R3 the bracket on [b_low, {G.b[top]:.4f}] by {STEP}: " f"{len(bad)} failing columns of {len(profs)} configurations") check("R3 675 failing columns, at 675 configurations, each the column " "of norm 3's own class with 2 of type 1^2 1 or 1^3", len(bad) == 675 and len({b[0] for b in bad}) == 675 and all(b[1] == "N=3 own" and b[0][0] in "rt" for b in bad)) gal = [(k, P) for k, P in profs if k[0] == "s" and k[1][0] in "sit" and set(k[1][1:]) <= set("si")] gbad, _, gleast = sweep(G, gal, top) print(f"RG the {len(gal)} cyclic configurations: {len(gbad)} failing, " f"least {gleast[0]:.6f} at config {gleast[1][1]}, {gleast[2]}") check("RG every column of the 48 cyclic configurations beats the F2 " "column on [b_low, b_top], bracketed", len(gal) == 48 and not gbad) print(" least A X(b+) / T2 W(b) per (2's type, class)") for wk in sorted(worst, key=lambda k: worst[k][0]): m, ty = worst[wk] print(f" 2 {wk[0]} {wk[1]:13s} {m:.6f} config {ty}") tally = {} for key, label, bl, bc, m in bad: tally.setdefault((key[0], label), []).append((m, key[1], bl, bc)) print(" failing, per (2's type, class): count, the least three") for wk in sorted(tally): rows = sorted(tally[wk]) print(f" 2 {wk[0]} {wk[1]:13s} {len(rows)}") for m, ty, bl, bc in rows[:3]: bcs = "none" if bc is None else f"{bc:.4f}" print(f" config {ty}: ratio {m:.6f}, b_low {bl:.4f}, " f"b_c {bcs}") print(" the failing configurations' types of 3 and of 5, per 2's type") failed = {} # per configuration, its least for key, label, bl, bc, m in bad: if key not in failed or m < failed[key][0]: failed[key] = (m, bl, bc) for t2 in "sqrt": rows = [k[1] for k in failed if k[0] == t2] if rows: c3 = {t: sum(r[0] == t for r in rows) for t in "sqirt"} c5 = {t: sum(r[1] == t for r in rows) for t in "sqirt"} print(f" 2 {t2}: 3 {c3}; 5 {c5}") print(" (2, 3, 5) cells where every one of the 125 configurations " "fails, with the one nearest to passing") cells = {} for key, (m, bl, bc) in failed.items(): cells.setdefault((key[0], key[1][:2]), []).append( (m, key[1], bl, bc)) for c in sorted(cells): if len(cells[c]) == 125: m, ty, bl, bc = max(cells[c]) bcs = "none" if bc is None else f"{bc:.4f}" print(f" 2 {c[0]}, 3 {c[1][0]}, 5 {c[1][1]}: nearest {ty}, " f"ratio {m:.6f}, b_low {bl:.4f}, b_c {bcs}") print(f" cells with some but not all failing: " f"{sum(1 for c in cells if len(cells[c]) < 125)}") print(f"{sum(CHECKS)} of {len(CHECKS)} checks pass") sys.exit(0 if all(CHECKS) else 1) if __name__ == "__main__": main()