precision.py
Checks: OBSERVATORY: The jump set reads M·e digits of u theorem.
Cited on: OBSERVATORY.
To run it: save precision.py, with clock.py, module_law.py, rung.py, staircase.py, tame.py, triangle.py, weld.py, wild.py beside it; standard library only.
"""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()