"""precision.py -- how many digits of the unit u = p/pi^e fix the jump set of a field holding zeta_p, and what the digit cells weigh. QUESTION. Let K/Q_p be totally ramified of degree e, cut out by an Eisenstein F = x^e + sum p b_i x^i - p d with root pi, u = p/pi^e (triangle.py), holding zeta_p, so ubar = -1, the bend s = e/(p - 1) = c_0 p^M with p not dividing c_0, and e* = p s. staircase.py reads Pagano's jump set (Jump sets in local fields, arXiv:1810.09975) as the staircase of the class maxima H(m) = G(m) + e, where G(m) is the largest v(zeta_p h - 1) over h in U_1^(p^m). The canonical digits u_0, u_1, ... of u are the coefficients' digits re-encoded (triangle.py). Is the jump set a function of finitely many of them, how many, and how often does each jump set occur over the cells of that many digits? THE DERIVATION (on paper, before the engine). (1) THE BOUND. Every point of the jump set but the first has beta <= M and i <= e*, and rho(i) = min(p i, i + e) is increasing with rho^beta(e*) = e* + beta e, so its weight is at most e* + M e. So at N <= m <= M, G(m) = H(m) - e <= e* + (M - 1) e < T := e* + (M - 1) e + 1, while G(m) is infinite at m < N. The jump set is therefore a function of the truncations min(G(m), T), 1 <= m <= M + 1. (2) THE PRECISION. min(G(m), T) is read in U_1/U_T: the image of U_1^(p^m) there is (U_1/U_T)^(p^m), and it needs zeta_p modulo U_T. Replacing zeta_p by a power prime to p changes no G(m), since (zeta_p h)^a = zeta_p^a h^a keeps its level. O_K/pi^A is Z_p[pi]/(pi^A, p - u pi^e), fixed by u modulo pi^(A - e). Take A = T + (p - 2) s. Two fields whose u agree modulo pi^(A - e) share O/pi^A with pi matched to pi; the reduction of the one's zeta_p is a root of Phi_p(1 + y) modulo pi^A in the other, and v(Phi_p'(zeta)) = e - s = (p - 2) s, so by Hensel it lies within pi^(A - (p - 2) s) = pi^T of a power of the other's zeta_p. So the two share every min(G(m), T), and their jump sets agree. Here A - e = e* + (M - 2) e + 1 + (p - 2) s = M e + 1 (e* + (p - 2) s = 2 e). THE JUMP SET IS A FUNCTION OF u MODULO pi^(M e + 1): of u_1, ..., u_(M e), u_0 being -1. At M = 0 it reads no digit: the jump set is (c_0, 1). At p = 2 no Hensel step is needed, zeta_2 = -1. (3) THE DICHOTOMY AT AN ANCHOR. The anchor Q_p(zeta_(p^(M+1))) (c_0 = 1, e = p^M (p - 1)), and its tame twist pi^(c_0) = zeta - 1 at c_0 > 1, hold zeta_(p^(M+1)): N = M + 1 and the jump set is (c_0, M + 1) alone. A field whose u agrees with the anchor's modulo pi^(M e) shares min(G(m), T - 1) by (2) run one level lower, so G(m) >= e* + (M - 1) e at every m <= M. A finite G(m) at m < M is at most e* + (m - 1) e, below that, so G is infinite below M; and G(M) is infinite or exactly e* + (M - 1) e. So the field's jump set is the anchor's or the anchor's with the point (e*, M) at weight e* + M e added, and the second happens exactly when zeta_(p^(M+1)) is not in K. If one of the p cells u_(M e) = 0..p - 1 below the anchor's does, M e digits do not fix the jump set. (4) THE WEIGHT OF A CELL. Under Haar measure on the coefficients of Eisenstein polynomials of degree e over Q_p with dbar = -1, the coefficient digits are independent and uniform, so by the coefficient triangle (triangle.py) so are u_1, ..., u_(M e); by (2) the jump set is constant on each of the p^(M e) cells, and a jump set's probability is its cell count over p^(M e). ubar = -1 holds exactly when K contains E = Q_p(zeta_p): -u is then a principal unit, a (p - 1)-th power, and -p = (pi^s v)^(p - 1). Pagano's Theorem 1.8 is the same probability over Eisenstein polynomials of degree s over E. Both are Serre's measure on the fields (Serre, Une formule de masse, 1978): a field K in a fixed algebraic closure weighs q^(-c(K))/n, n = [K : base], so the masses sum to 1, c = d - e + 1 with d the exponent of the different over the base, q = p over Q_p and over E alike. For K containing E, d(K/Q_p) = d(K/E) + s (p - 2), so c over Q_p is d(K/E) + s (p - 2) - e + 1 and c over E is d(K/E) - s + 1, equal, and the factors 1/n, 1/e against 1/s, differ by the constant ratio e/s = p - 1, which normalizing each to a probability absorbs. So the p^(M e) cells fall on the jump sets in counts p^(M e) times the Haar mass of Pagano's Theorem 1.7. That mass is a hand computation: the relation vector's coordinate at a in T* = {a < e*, p not dividing a} u {e*} has p-order b >= 1 with P(b) = (p - 1)/p^b, independently, and the jump set is the frontier of the (a, b) under (b, rho^b(a)), conditioned on its least weight being e*. At e = 2: (1, 2), (3, 1) at 1/2; (1, 2), (4, 1) at 1/4; (1, 2) at 1/4; Serre's masses of the six ramified quadratics of Q_2 are 1/8 at sqrt 2, sqrt -2, sqrt 6, sqrt 10 and 1/4 at sqrt 3, sqrt -1, which carry those jump sets in that order, 4/8, 1/4, 1/4. THE ENGINE. The jump set by staircase.py's read_deep: G(n) from the filtered echelon of U_1^(p^n) in U_1/U_L, L = e* + (M + 1) e + 3 > T, zeta_p lifted from 1 + pi^s; the points are the changes of G (weld.py's jump_set), which staircase.py proved equal to the class maxima and to Pagano's own reading. Fields are made from digits by triangle.py: design(p, e, target) is the polynomial with no coefficient digit above level N whose u begins with target[0..N], and read(p, e, b, d, N) the digits of u. The law is the frontier recursion over the coordinates of T*, orders above B lumped (none may survive on an admissible frontier, asserted), in exact fractions. TRANSPLANTS. The class-maxima theorem and the equality of G's changes with Pagano's reading are staircase.py's, imported; the Hensel step in (2), the dichotomy (3) and the Serre comparison in (4) are this slate's. No digit ring and no greedy discrete log is used. PREDICTIONS, fixed before the run. C CONTROL, run first. x^2 - 2 reads (1, 2), (3, 1); x^6 + 3x + 3 reads (1, 2), (4, 1); x^18 + 3 reads (1, 3), (7, 2), (25, 1); x^12 - 2 reads (3, 3), (9, 2), (21, 1); Phi_8(x + 1) reads (1, 3); the law at e = 2 is (4)'s 1/2, 1/4, 1/4, summing to 1 by its normalization. B THE BOUND. Over the fields of P, its designs and the anchors' siblings, no point but the first weighs more than e* + M e. P THE PRECISION. At every random field (dbar = -1) the reduced design of its own first M e + 1 digits, and three designs agreeing with it to u_(M e) and random for e + 2 digits past, read its jump set. A THE ANCHORS. zeta_4, zeta_8, zeta_16 over 2, zeta_9 over 3, zeta_25 over 5, and Phi_8(x^3 + 1) (c_0 = 3, e = 12): the reduced design of the first M e + 1 digits reads (c_0, M + 1) alone, and each of the p - 1 designs with u_(M e) changed reads (c_0, M + 1), (e*, M) at weight e* + M e: 6, 16, 40, 15, 45, 48. L THE CELLS. Every coefficient-digit cell at levels 1..M e with dbar = -1 at (p, e) = (2, 2), (2, 4), (3, 6) (4, 256, 729 cells): its count per jump set is p^(M e) times the law, and one random tail past level M e reads the same jump set at every cell. KILLS, as printed observables: a C line off (nothing below is read); a point past e* + M e; one field or design off its jump set in P; one anchor design off in A; one count off the law, or one impure cell, in L. FINDINGS. Every prediction landed: 25/25 checks PASS, no kill fired. C The five fields read as predicted, x^18 + 3 at (1, 3), (7, 2), (25, 1), and the law at e = 2 is 1/2, 1/4, 1/4. P Nine (p, e), e = 2, 4, 8, 12, 16 at 2, 6, 12, 18 at 3 and 20 at 5, 65 fields, each read at four designs sharing its first M e + 1 digits: 0 off. A At all six anchors the own cell reads (c_0, M + 1) alone and every sibling adds (e*, M), at weights 6, 16, 40, 15, 45, 48; at zeta_9 and zeta_25 all p - 1 siblings do, so the anchor's is the one cell of its p holding zeta_(p^(M+1)). L The cells fall 2, 1, 1 at (2, 2); 128, 32, 32, 16, 16, 16, 8, 4, 2, 2 at (2, 4); 486, 162, 54, 18, 6, 3 at (3, 6): p^(M e) times the law at every jump set, and no cell impure. B Over 335 jump sets read, no point past e* + M e, attained at the anchors' siblings. Tiers: (2) a theorem given staircase.py's class-maxima theorem (so given Pagano's Theorem 1.5 and the proof of his Proposition 3.35); (3) a theorem, its second branch read at the six anchors; (4) a theorem given Pagano's Theorem 1.8 and Serre's mass formula. RUN RECORD. 25/25, 96.1 s wall, peak working set 14.6 MB under a memory guard, green on the first run. Run: python precision.py """ import os os.environ.setdefault("OPENBLAS_NUM_THREADS", "1") import random from fractions import Fraction from itertools import product from module_law import check, section, CHECKS, vp from triangle import read, design, cell_to_poly, random_poly from staircase import read_deep, cyclo from weld import jump_set, weight SEED = 1445 def jumpset(p, e, b, d): rec = read_deep(p, e, b, d) return tuple(jump_set(p, e, rec["G"], rec["M"] + 1)), rec def shape(p, e): s = e // (p - 1) M = vp(s, p) return s, M, s // p ** M, p * s def fmt(js): return " ".join(f"({i}, {b})" for i, b in js) # ------------------------------------------------------------ the law def rho_k(p, e, i, k): for _ in range(k): i = min(p * i, i + e) return i def absorb(front, pt, p, e): a, b = pt w = rho_k(p, e, a, b) for (a2, b2) in front: if b2 <= b and rho_k(p, e, a2, b2) <= w: return front kept = [(a2, b2) for (a2, b2) in front if not (b <= b2 and w <= rho_k(p, e, a2, b2))] return tuple(sorted(kept + [pt])) def law(p, e): """{jump set: mass} over the admissible frontiers, exact.""" s, M, c0, es = shape(p, e) T = [a for a in range(1, es) if a % p] + [es] B = 1 while rho_k(p, e, 1, B) < es: B += 1 B += 1 states = {(): Fraction(1)} for a in T: nxt = {} for front, m in states.items(): for b in range(1, B + 2): w = (Fraction(p - 1, p ** b) if b <= B else Fraction(1, p ** B)) f2 = absorb(front, (a, b), p, e) nxt[f2] = nxt.get(f2, Fraction(0)) + m * w states = nxt out = {} for fr, m in states.items(): if min(rho_k(p, e, a, b) for a, b in fr) != es: continue assert all(b <= B for _a, b in fr), "a lumped order survives" out[fr] = m tot = sum(out.values()) return {fr: m / tot for fr, m in out.items()} # ------------------------------------------------------------ sections BOUND = [] def note_bound(p, e, js): s, M, c0, es = shape(p, e) top = max((weight(p, e, i, bt) for i, bt in js if bt <= M), default=es) BOUND.append(top - es - M * e) CONTROLS = [("x^2 - 2", 2, 2, [0, 0], 1, ((1, 2), (3, 1))), ("x^6 + 3x + 3", 3, 6, [0, 1, 0, 0, 0, 0], -1, ((1, 2), (4, 1))), ("x^18 + 3", 3, 18, [0] * 18, -1, ((1, 3), (7, 2), (25, 1))), ("x^12 - 2", 2, 12, [0] * 12, 1, ((3, 3), (9, 2), (21, 1)))] def section_control(): section("C CONTROL: five fields and the law at e = 2") for name, p, e, b, d, want in CONTROLS: js, _ = jumpset(p, e, b, d) check(f"C {name}", js == want, fmt(js)) e, b, d = cyclo(2, 3, 1, []) js, _ = jumpset(2, e, b, d) check("C Phi_8(x + 1)", js == ((1, 3),), fmt(js)) L2 = law(2, 2) want = {((1, 2), (3, 1)): Fraction(1, 2), ((1, 2), (4, 1)): Fraction(1, 4), ((1, 2),): Fraction(1, 4)} check("C the law at e = 2", L2 == want, "; ".join(f"{fmt(k)} {v}" for k, v in sorted(L2.items()))) POP = [(2, 2, 8), (2, 4, 12), (2, 8, 10), (2, 12, 6), (2, 16, 4), (3, 6, 12), (3, 12, 6), (3, 18, 4), (5, 20, 3)] def section_precision(rng): section("P THE PRECISION: M e + 1 digits of u fix the jump set") for p, e, reps in POP: s, M, c0, es = shape(p, e) n = M * e bad, seen = 0, set() for _ in range(reps): b, d = random_poly(rng, p, e, n // e + 3, d0=p - 1) js, _ = jumpset(p, e, b, d) note_bound(p, e, js) seen.add(js) pre = read(p, e, b, d, n) tails = [pre] + [pre + [rng.randrange(p) for _ in range(e + 2)] for _ in range(3)] for t in tails: bb, dd = design(p, e, t) if read(p, e, bb, dd, len(t) - 1) != t: bad += 1 continue j2, _ = jumpset(p, e, bb, dd) note_bound(p, e, j2) bad += j2 != js check(f"P (p, e) = ({p}, {e}), M e = {n}", bad == 0, f"{reps} fields x 4 designs, {len(seen)} jump sets, " f"{bad} off") ANCHORS = [("zeta_4", 2, 2, 1), ("zeta_8", 2, 3, 1), ("zeta_16", 2, 4, 1), ("zeta_9", 3, 2, 1), ("zeta_25", 5, 2, 1), ("Phi_8(x^3 + 1)", 2, 3, 3)] def section_anchor(): section("A THE ANCHORS: u_(M e) decides the point (e*, M)") for name, p, N, k in ANCHORS: e, b, d = cyclo(p, N, k, []) s, M, c0, es = shape(p, e) n = M * e pre = read(p, e, b, d, n) own, _ = jumpset(p, e, *design(p, e, pre)) sibs = [] for v in range(p): if v == pre[n]: continue js, _ = jumpset(p, e, *design(p, e, pre[:n] + [v])) note_bound(p, e, js) sibs.append(js) ok = (own == ((c0, M + 1),) and all(j == ((c0, M + 1), (es, M)) for j in sibs)) check(f"A {name}: e = {e}, M e = {n}, weight e* + M e = {es + n}", ok, f"own {fmt(own)}; siblings " + "; ".join(fmt(j) for j in sibs)) def section_cells(rng): section("L THE CELLS: p^(M e) digit cells carry the law") for p, e in [(2, 2), (2, 4), (3, 6)]: s, M, c0, es = shape(p, e) n = M * e lw = law(p, e) counts, impure = {}, 0 for digs in product(range(p), repeat=n): b, d = cell_to_poly(p, e, p - 1, list(digs)) js, _ = jumpset(p, e, b, d) counts[js] = counts.get(js, 0) + 1 tail = list(digs) + [rng.randrange(p) for _ in range(e + 1)] j2, _ = jumpset(p, e, *cell_to_poly(p, e, p - 1, tail)) impure += j2 != js total = p ** n off = [js for js in set(counts) | set(lw) if counts.get(js, 0) != lw.get(js, 0) * total] check(f"L (p, e) = ({p}, {e}): {total} cells, {len(lw)} jump sets", not off and impure == 0, f"{len(off)} counts off the law, {impure} impure; " + ", ".join(str(counts[k]) for k in sorted(counts, key=lambda k: -counts[k]))) def main(): rng = random.Random(SEED) section_control() section_precision(rng) section_anchor() section_cells(rng) section("B THE BOUND") check("B no point past e* + M e", max(BOUND) <= 0, f"{len(BOUND)} jump sets, highest weight e* + M e + " f"{max(BOUND)}") print() print(f"{sum(CHECKS)}/{len(CHECKS)} checks PASS") if not all(CHECKS): raise SystemExit(1) if __name__ == "__main__": main()