storey.py
Checks: OBSERVATORY: The last point gives the break of the next storey theorem; OBSERVATORY: A radical storey adds e to the first defect and passes the rest down theorem.
Cited on: OBSERVATORY.
To run it: save storey.py, with clock.py, module_law.py, rung.py, staircase.py, tame.py, triangle.py, weld.py, wild.py beside it; standard library only.
"""storey.py -- the next storey of the cyclotomic tower over a ramified
field, read off the last point of its jump set, and what one radical
storey does to the p^m-power defects of zeta_p.
QUESTION. Let K/Q_p be totally ramified of degree e, cut out by F =
x^e + sum_{i=1}^{e-1} p b_i x^i - p d with root pi (triangle.py), let
the bend s = e/(p - 1) be an integer and e* = p s, and let zeta, of
order p^N, generate the p-power roots of unity in K. Two questions.
(A) THE NEXT STOREY. How does K(zeta_(p^(N+1)))/K ramify, and can
the answer be read off Pagano's jump set (Jump sets in local
fields, arXiv:1810.09975), or off one coefficient level?
(B) THE DESCENT. With zeta_p in K, the p^m-POWER DEFECT is kappa_m =
the largest v(y^(p^m) - zeta_p) over principal units y, infinite
exactly when zeta_p is a p^m-th power. For a radical storey K =
C(pi_C^(1/p)) over a field C holding zeta_p, how is kappa_m(K)
made from the defects of C?
THE DERIVATION (on paper, before the engine).
(1) THE LAST POINT IS THE DEFECT OF THE NEXT STOREY. The weight of
zeta modulo p-th powers, the largest v(zeta h - 1) over h in
U_1^p, is the i of the jump set's last point (i_N, N): Pagano's
Proposition 3.39 at n = 1, since his g breaks at n = beta - N and
the point with beta = N gives g(1) = rho^0(i_N) = i_N. Kummer
theory: K(zeta_(p^(N+1))) = K(a^(1/p)) with a = zeta h, h a p-th
power, and a best such a has level i_N. Put alpha = 1 + (zeta_p -
1) Y; since (zeta_p - 1)^(p-1) = -p times a principal unit, alpha^p
= a becomes Y^p - Y = (a - 1)/(zeta_p - 1)^p up to terms of higher
valuation, whose right side has valuation i_N - e*. Fesenko and
Vostokov (Local Fields and Their Extensions, 2nd ed., ch. III,
(2.5)): a unit right side whose residue is not in the image of
x^p - x on F_p gives an unramified extension, and on F_p that
image is {0}; a right side of valuation -t with p not dividing t
gives a totally ramified one whose break is t. Below e* the i of
a point is prime to p, so:
K(zeta_(p^(N+1)))/K is unramified iff i_N = e*, and otherwise
totally ramified with its one break t = e* - i_N.
The unramified half is Pagano's Proposition 5.5 (e* is in I_K iff
the extension is unramified).
(2) THE RIGID READING. At N = 1 with p | s, zeta_p's weight i_1 is
kappa_1 = G(1) of the class maxima (staircase.py), above s, so the
point (i_1, 1) has weight i_1 + e = H(1), the highest landing of
the class (s/p, 1). When that class is rigid, H(1) = e* + dep, so
i_1 = s + dep and
t = e - dep,
dep being the class's departure: l = v(u + 1) at an odd prime
(rigid iff l < p) and over 2 at e >= 4 (rigid iff l = 1), and the
wild departure lam at e = 2 (rigid iff lam < 3).
(3) THE COLLAPSE. Let K/C be totally ramified of degree p with zeta_p
in C and O_K = O_C[pi_K], and let L <= e_K be the level (in v_K),
capped at e_K, to which pi_K^p agrees with an element of C. Every y
in O_K is sum_{iota<p} chi_iota pi_K^iota with chi_iota in O_C,
and y^p = sum_iota chi_iota^p pi_K^(p iota) mod p, so
y^p agrees with an element of C to level min(L, e_K); conversely
every chi in O_C is reached mod pi_K^L, since at f = 1 the map
sends the digits a_(iota,k) of the chi_iota to the level
p k + iota, one to one. A radical storey, pi_K^p = pi_C, has
L = e_K.
(4) THE LIFT. If y^p = chi + r with v(r) >= L and (p - 1) L >= e_K,
then y^(p^m) = chi^(p^(m-1)) mod pi_K^((m-1) e_K + L): the ell-th
binomial term has valuation at least
(m - 1 - v_p(ell)) e_K + ell L, and (ell - 1) L >= v_p(ell) e_K.
(5) THE DESCENT. For m >= 2, with A = p kappa_(m-1)(C):
kappa_m(K) = p kappa_(m-1)(C) when A < (m - 1) e_K + L,
kappa_m(K) >= (m - 1) e_K + L otherwise.
Every y has its chi, and v_K(chi^(p^(m-1)) - zeta_p) <= A below the
congruence level; the C-witness chi, lifted by (3), attains A. At a
radical storey the condition reads kappa_(m-1)(C) < m e_C. A
finite kappa_j is at most e* + (j - 1) e in any field, since past
that level every principal unit is a p^j-th power (above the bend
the p-th power carries U_i onto U_(i+e)). So at an odd prime the
condition holds at every finite kappa_(m-1)(C), since
p/(p - 1) < 2; over 2 it fails only at the boundary
kappa_(m-1)(C) = m e_C, the largest value allowed.
(6) THE FIRST DEFECT (Herbrand). Let N = 1 in C and in K. The
compositum E = K(zeta_(p^2)) over C is bicyclic, its degree-p
subfields C(zeta_(p^2)) with break t_C = e*_C - kappa_1(C) and K
with break e*_C (a p-th root of a uniformizer). Upper numbering
passes to quotients, so the first lower break of E/C is the
smaller upper break, t_C, and on (t_C, e*_C] the ramification
group is Gal(E/C(zeta_(p^2))), not Gal(E/K). Lower numbering
passes to subgroups, so Gal(E/K) has break t_C, and by (1) kappa_1(K) = e*_K - t_C:
kappa_1(K) = kappa_1(C) + e_K,
the break of the next storey unchanged up the storey. (A storey
whose own break t_(K/C) sits below t_C, a unit-root storey,
gives p kappa_1(C) + (p - 1) t_(K/C) by the same argument at the
second lower break, agreeing with the radical case at
t_(K/C) = t_C; no such storey is built here.)
THE ENGINE. kappa_m is staircase.py's G(m), the filtered echelon of
U_1^(p^m) modulo the level e* + (M + 1) e + 3, with zeta_p lifted from
1 + pi^s; that level exceeds every finite value up to m = M + 2, so a
value there is infinite. The jump set is Pagano's reading off a root of
order p^N found by staircase.py's pruned search. THE BREAK, read with no
defect: in a top field Z = Q_p(zeta_top), zeta_top a root of unity of
p-power order, cut out by Phi(1 + x) with pi_Z = zeta_top - 1,
sigma: zeta_top -> zeta_top^k has i(sigma) = v_Z(zeta_top^(k-1) - 1), and
the break of Z over the fixed field of sigma is i(sigma) - 1. THE
LABELS at the ramified quadratics of Q_2, read with no orbit: K(i)/K is
trivial iff -1 is a square in K and unramified iff -5 is (K(sqrt 5) is
the unramified quadratic), a square found by a search of y mod pi^6
with v(y^2 - n) > e* (past e* every principal unit is a square).
POPULATIONS. (A) the cyclotomic fields Q_2(zeta_8) over Q_2(sqrt 2),
Q_2(sqrt -2) and Q_2(i), Q_2(zeta_16) over Q_2(zeta_8), Q_3(zeta_9) over
Q_3(zeta_3); the six ramified quadratics of Q_2; x^6 + 3, x^12 + 3 and
the sextic x^6 + 6x^5 + 6x^4 + 3x^3 + 12; for (2), fields designed to a
departure (tame.py, wild.py) at p = 3, e = 6 and 18, l from 1 to 4, and
at p = 2, e = 4 and 8, l = 1 and 2, and e = 2, lam = 1, 2, 3.
(B) the tower x^(2 3^n) + 3 over Q_3(sqrt -3) to e = 54 and x^36 + 3
over x^12 + 3, x^(2^n) - 2 from x^4 - 2 over x^2 - 2 to e = 32, x^(3 e_C) - 6,
x^(3 e_C) + 3x^(2 e_C) + 3 at e_C = 6, x^(2 e_C) + 2 and x^(2 e_C) - 10
at e_C = 2; F(x^p) for random F at p = 2, e_C = 2, 4, 8 and p = 3,
e_C = 2, 6, with N = 1 in C; and the boundary specimen x^4 - 2x^2 + 6
over x^2 - 2x + 6 (Q_2(sqrt -5), where kappa_1 = 4 = 2 e_C).
PREDICTIONS, fixed before the run. The defects of the pure towers
x^(2 p^n) + 3 and x^(2^n) - 2 were printed by a trial of the engine
before this slate (they are the verifier's own values, 7, 25, 79 up
the first tower, 50 at x^36 + 3, and 3, 7, 15, 31, 63 for kappa_1);
every other value below is fresh.
C CONTROL, run first. The sigma breaks: Q_2(zeta_8) over Q_2(sqrt 2)
and Q_2(sqrt -2) 1, over Q_2(i) 3; Q_2(zeta_16) over Q_2(zeta_8) 7;
Q_3(zeta_9) over Q_3(zeta_3) 2. The fixed fields' last points: (3,
1), (3, 1), (1, 2), (1, 3), (1, 1), so e* - i_N equals each break.
Q THE QUADRATICS. Labels ramified at sqrt(2), sqrt(-2), sqrt(10),
sqrt(-10), unramified at sqrt(-5), trivial at i; N = 1 and i_1 = 3
< e* = 4 at the four ramified, N = 1 and i_1 = 4 = e* at sqrt(-5),
N = 2 at i.
N THE NEXT STOREY. x^6 + 3 reads i_1 = 7 (t = 2), x^12 + 3 reads 14
(t = 4), the sextic reads 9 = e* (unramified, at an odd prime with
f = 1).
R THE RIGID READING. At every designed field with N = 1 whose class
(s/p, 1) is rigid, i_1 = s + dep; the others print.
D THE DESCENT. At every storey with N = 1 in C: kappa_m(K) = p
kappa_(m-1)(C) at 2 <= m <= M_K + 2 wherever p kappa_(m-1)(C) <
m e_K, and kappa_m(K) >= m e_K wherever not (over 2, the boundary
only); and kappa_1(K) = kappa_1(C) + e_K wherever N = 1 in K.
B THE BOUNDARY SPECIMEN. kappa_1 = 8 = e*_K (the next storey stays
unramified), and kappa_2 >= 8, where the law's value would be 8.
KILLS, as printed observables: a C line off (nothing below is read); a
quadratic whose label disagrees with its N and i_1; an N line off; a
rigid field with i_1 != s + dep; a storey row off D; B off.
FINDINGS. Every prediction landed: 12/12 checks PASS, no kill fired.
C The sigma breaks print 1, 1, 3, 7, 2, and the fixed fields' last
points (3, 1), (3, 1), (1, 2), (1, 3), (1, 1) give e* - i_N equal
to each.
Q sqrt(+-2) and sqrt(+-10) ramified by squares, last point (3, 1),
t = 1; sqrt(-5) unramified, (4, 1) = (e*, 1); i trivial, N = 2 and
jump set (1, 2).
N x^6 + 3 reads (7, 1), t = 2; x^12 + 3 reads (14, 1), t = 4; the
sextic reads (9, 1) with e* = 9, so K(zeta_9)/K is unramified.
R All 8 rigid fields read i_1 = s + dep: t = 5, 4 at p = 3, e = 6,
17, 16 at e = 18, 3 and 7 at p = 2, e = 4 and 8, 1 and 0 at e = 2.
The non-rigid ones spread (l = 3, 4 at e = 18 both read t = 14).
D 81 defects m >= 2 and 24 first defects over 24 storeys, none off;
three storeys skipped for N = 2 in C. Over 2 the boundary was met
at 3 defects, each at m = 2 over a C with kappa_1 = 2 e_C = e*_C,
whose next storey is unramified, and there kappa_2(K) =
2 kappa_1(C) after all (8, 16, 32): the bound is attained at every
boundary met, an observation. Longest ladder: x^32 - 2 reads 63,
62, 60, 56, 48, 32, 32, kappa_1 = 31 + 32 by the first-defect
law and each later one twice the defect below one storey down.
B C: kappa 4, 2, 2; K: kappa 8, 8, 4, 4. The unramified next storey
stays unramified up the storey, and kappa_2 = 8 is the law's value
at the boundary too.
Tiers: (1) a theorem given Pagano's Propositions 3.39 and 5.5 and
Fesenko-Vostokov ch. III (2.5); (2) a theorem given (1) and
the class maxima; (3)-(5) a theorem with no outside result;
(6) a theorem given the Herbrand compatibilities, its second branch
(a storey breaking below t_C) proved and not exercised.
RUN RECORD. 12/12, 87.5 s wall, peak working set 15.4 MB under
a memory guard; x^54 + 3 is about 60 s of it. A first run died in D at Q_2
itself taken as C (e = 1, which the local-ring engine does not carry),
before any D line was read; Q_2 was dropped, x^2 - 2 over it standing
in only as a C.
"""
import os
os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
import random
from module_law import check, section, CHECKS
from weld import Units
from staircase import read_deep, root_search, pagano_reading, cyclo
from tame import departure, designed
import wild
SEED = 20260927
# ------------------------------------------------------------ the field
def last_point(p, e, b, d):
"""(N, i_N, the jump set, s, e*) of the field of (b, d)."""
rec = read_deep(p, e, b, d)
N = rec["N"]
root = root_search(p, e, b, d, N, rec["L"] + N * e + 1)
pts, gz = pagano_reading(p, e, rec, root)
last = [i for i, bt in pts if bt == N]
assert len(last) == 1 and last[0] == gz[1]
return N, last[0], pts, rec["s"], rec["es"]
def sigma_break(p, n, k):
"""The break of Q_p(zeta_(p^(n+1))) over the fixed field of
zeta -> zeta^k, from i(sigma) = v_L(zeta^(k-1) - 1)."""
e, b, d = cyclo(p, n + 1, 1, [])
U = Units(p, e, b, d, 4 * e)
zeta = U.gen(1)
return U.lead(U.pw(zeta, k - 1))[0] - 1
def is_square(p, e, b, d, n):
"""Some y mod pi^6 with v(y^2 - n) > e*: n a square in K."""
U = Units(p, e, b, d, 12)
es = p * e // (p - 1)
pis = [U.loc.pi_pow(j, U.mod) for j in range(6)]
for idx in range(1, 2 ** 6):
y = [0] * e
for j in range(6):
if idx >> j & 1:
y = [(a + x) % U.mod for a, x in zip(y, pis[j])]
z = U.mul(y, y)
z[0] = (z[0] - n) % U.mod
if U.loc.val(z, U.N) > es:
return True
return False
# ------------------------------------------------------------ populations
QUADS = [("Q_2(sqrt 2)", [0, 0], 1, "ramified"),
("Q_2(sqrt -2)", [0, 0], -1, "ramified"),
("Q_2(sqrt 10)", [0, 0], 5, "ramified"),
("Q_2(sqrt -10)", [0, 0], -5, "ramified"),
("Q_2(sqrt -5)", [0, -1], -3, "unramified"),
("Q_2(i)", [0, 1], -1, "trivial")]
SIGMA = [("Q_2(zeta_8) / Q_2(sqrt 2)", 2, 2, 7, 1, (2, [0, 0], 1)),
("Q_2(zeta_8) / Q_2(sqrt -2)", 2, 2, 3, 1, (2, [0, 0], -1)),
("Q_2(zeta_8) / Q_2(i)", 2, 2, 5, 3, cyclo(2, 2, 1, [])),
("Q_2(zeta_16) / Q_2(zeta_8)", 2, 3, 9, 7, cyclo(2, 3, 1, [])),
("Q_3(zeta_9) / Q_3(zeta_3)", 3, 1, 4, 2, cyclo(3, 1, 1, []))]
def radical(p, b, d):
"""F(x^p): the storey C(pi_C^(1/p)) over the field of F."""
e = len(b)
bk = [0] * (p * e)
for i in range(1, e):
bk[p * i] = b[i]
return bk, d
def towers(rng):
"""(name, p, b_C, d_C) with N = 1 in C; K is radical over it."""
out = [("x^2 + 3", 3, [0, 0], -1), ("x^6 + 3", 3, [0] * 6, -1),
("x^18 + 3", 3, [0] * 18, -1), ("x^12 + 3", 3, [0] * 12, -1),
("x^2 - 2", 2, [0, 0], 1),
("x^4 - 2", 2, [0] * 4, 1), ("x^8 - 2", 2, [0] * 8, 1),
("x^16 - 2", 2, [0] * 16, 1),
("x^6 - 6", 3, [0] * 6, 2),
("x^6 + 3x^4 + 3", 3, [0, 0, 0, 0, 1, 0], -1),
("x^2 + 2", 2, [0, 0], -1), ("x^2 - 10", 2, [0, 0], 5)]
for e in (2, 4, 8):
for _ in range(3):
b, d = wild.designed(rng, e, rng.randrange(1, 3 * e // 2 + 1),
2 * e + 4)
out.append((f"F(2, {e}) b={b[1:]} d={d}", 2, b, d))
for e in (2, 6):
for _ in range(3):
b, d = designed(rng, 3, e, rng.randrange(1, e + 2), 2 * e + 2)
out.append((f"F(3, {e}) b={b[1:]} d={d}", 3, b, d))
return out
def rigid_fields(rng):
"""(name, p, e, b, d, dep, rigid) designed to a departure."""
out = []
for e in (6, 18):
for l in (1, 2, 3, 4):
b, d = designed(rng, 3, e, l, 2 * e + 2)
out.append((f"p=3 e={e} l={l}", 3, e, b, d, l, l < 3))
for e in (4, 8):
for l in (1, 2):
b, d = wild.designed_l(rng, e, l, 2 * e + 2)
out.append((f"p=2 e={e} l={l}", 2, e, b, d, l, l == 1))
for lam in (1, 2, 3):
b, d = wild.designed(rng, 2, lam, 8)
out.append((f"p=2 e=2 lam={lam}", 2, 2, b, d, lam, lam < 3))
return out
# ------------------------------------------------------------ sections
def section_control():
section("C CONTROL: the sigma breaks against e* - i_N")
for name, p, n, k, want, (e, b, d) in SIGMA:
t = sigma_break(p, n, k)
N, i, pts, s, es = last_point(p, e, b, d)
check(f"C {name}: break {want}", t == want and es - i == want,
f"sigma reads {t}; fixed field N = {N}, jump set {pts}, "
f"e* - i_N = {es} - {i} = {es - i}")
def section_quadratics():
section("Q THE SIX RAMIFIED QUADRATICS: K(i)/K against the last point")
bad = 0
for name, b, d, want in QUADS:
if is_square(2, 2, b, d, -1):
label = "trivial"
elif is_square(2, 2, b, d, -5):
label = "unramified"
else:
label = "ramified"
N, i, pts, s, es = last_point(2, 2, b, d)
read = ("trivial" if N >= 2 else "unramified" if i == es
else "ramified")
bad += label != want or read != want or (N == 1 and label ==
"ramified" and i != 3)
print(f" {name:14s} squares: {label:10s} last point: N = {N}, "
f"i_N = {i}, t = {es - i if N == 1 else '-'} {pts}")
check("Q the labels read by squares equal the last point's reading",
bad == 0, f"{len(QUADS)} fields, {bad} off")
def section_next():
section("N THE NEXT STOREY: named fields")
for name, b, d, want in [("x^6 + 3", [0] * 6, -1, 7),
("x^12 + 3", [0] * 12, -1, 14),
("x^6 + 6x^5 + 6x^4 + 3x^3 + 12",
[0, 0, 0, 1, 2, 2], -4, 9)]:
e = len(b)
N, i, pts, s, es = last_point(3, e, b, d)
check(f"N {name}: i_1 = {want}", N == 1 and i == want,
f"N = {N}, jump set {pts}, e* = {es}, t = {es - i}"
+ (" (unramified)" if i == es else ""))
def section_rigid(rng):
section("R THE RIGID READING: t = e - dep at a rigid class (s/p, 1)")
n = bad = 0
for name, p, e, b, d, dep, rigid in rigid_fields(rng):
read = wild.wild_departure(e, b, d) if e == 2 else departure(
p, e, b, d)
N, i, pts, s, es = last_point(p, e, b, d)
ok = read == dep and (N >= 2 or not rigid or i == s + dep)
n += rigid and N == 1
bad += not ok
print(f" {name:18s} dep {read} N = {N} i_N = {i} "
f"t = {es - i if N == 1 else '-'} "
f"{'rigid: e - dep = ' + str(e - dep) if rigid else ''}"
f"{'' if ok else ' OFF'}")
check("R i_1 = s + dep at every rigid field with N = 1", bad == 0,
f"{n} rigid, {bad} off")
def section_descent(rng):
section("D THE DESCENT: kappa_m(K) from kappa_(m-1)(C), one radical "
"storey")
rows = bad = boundary = odd_boundary = rungs = 0
for name, p, b, d in towers(rng):
eC = len(b)
C = read_deep(p, eC, b, d)
if C["N"] != 1:
print(f" {name}: N = {C['N']} in C, skipped")
continue
bk, dk = radical(p, b, d)
eK = p * eC
K = read_deep(p, eK, bk, dk)
GC, GK, precC = C["G"], K["G"], C["L"]
line = []
for m in range(2, K["M"] + 3):
A = p * GC[m - 1]
rows += 1
if GC[m - 1] < precC and A < m * eK: # kappa finite in C
ok = GK[m] == A
line.append(f"{GK[m]}={p}*{GC[m - 1]}")
else:
boundary += 1
odd_boundary += p != 2
ok = GK[m] >= m * eK
line.append(f"{GK[m]}>={m * eK} (boundary)")
bad += not ok
if not ok:
line[-1] += " OFF"
if K["N"] == 1:
rungs += 1
ok = GK[1] == GC[1] + eK
bad += not ok
first = (f"kappa_1 {GC[1]} -> {GK[1]}"
f"{'' if ok else ' OFF'}")
else:
first = f"N = {K['N']} in K"
print(f" {name}: e_K = {eK}, {first}; kappa_2.. {', '.join(line)}")
check("D the descent and the first defect at every storey, the "
"boundary over 2 only", bad == 0 and odd_boundary == 0,
f"{rows} defects m >= 2 ({boundary} at the boundary, "
f"{odd_boundary} at odd p), {rungs} first defects, {bad} off")
def section_boundary():
section("B THE BOUNDARY SPECIMEN: x^4 - 2x^2 + 6 over Q_2(sqrt -5)")
C = read_deep(2, 2, [0, -1], -3)
K = read_deep(2, 4, [0, 0, -1, 0], -3)
ok = (C["G"][1] == 4 and K["N"] == 1 and K["G"][1] == 8
and K["G"][2] >= 8)
check("B kappa_1 = 8 = e*_K, kappa_2 >= 8", ok,
f"C: kappa {dict(list(C['G'].items())[1:])}; "
f"K: kappa {dict(list(K['G'].items())[1:])}")
def main():
rng = random.Random(SEED)
section_control()
if not all(CHECKS):
print("\ncontrol failed: nothing below is read")
raise SystemExit(1)
section_quadratics()
section_next()
section_rigid(rng)
section_descent(rng)
section_boundary()
print()
print(f"{sum(CHECKS)}/{len(CHECKS)} checks PASS")
if not all(CHECKS):
raise SystemExit(1)
if __name__ == "__main__":
main()