wild.py
Checks: CLOCK: The width theorem; OBSERVATORY: The anchor design of the 2e-th roots of unity theorem; observation; OBSERVATORY: The wild readout theorem; OBSERVATORY: The letters of K(ζ₈)/K at e = 4 rule.
Cited on: CLOCK, OBSERVATORY.
To run it: save wild.py, with clock.py, module_law.py, tame.py, triangle.py beside it; standard library only.
"""wild.py -- at p = 2, where a class of units lands past the bend: the
tame formula at every class but the top one, and the top class, whose
least landing is a head's width, read off the departure of the
Eisenstein polynomial from one fixed design.
QUESTION. Let K/Q_2 be totally ramified of degree e, cut out by
F = x^e + sum_{i=1}^{e-1} 2 b_i x^i - 2 d with root pi, and u = 2/pi^e
(triangle.py). The bend is s = e. A CLASS is a pair (c, m) with
c 2^m = e: the units z of level exactly c, which reach the bend after m
squarings; a unit's LANDING is v(z^(2^(m+1)) - 1). tame.py reads every
class at an odd prime as p s + min(l, p^m), l = v(u + 1). Which of
that survives at 2, and what does the top class (1, n), e = 2^n, read
in its place? Its least landing is clock.py's arrival, so the answer is
the width w at every place with a head over 2 (f = 1 and e = 2^n is
the whole head criterion there, since the residue of u is always
1 = -1).
THE OBJECTS READ. The departure l = v(u + 1) = min(e v_2(d + 1),
min_i (e v_2(b_i) + i)), as in tame.py (the terms of 1 + D, D = d -
sum b_i pi^i, sit at distinct levels mod e at every p); here l >= 1
always. The ANCHOR DESIGN is
A_e = x^e + 2 x^(e/2) + 4 x^(e/4) - 6 (the middle term absent at
e = 2),
and the WILD DEPARTURE of F is the least level (triangle.py's levels:
digit k of b_i at i + k e, digit k of d at k e) where a coefficient
digit of F differs from A_e's:
lam = min( e v_2(d - 3), e v_2(b_(e/2) - 1) + e/2,
e v_2(b_(e/4) - 2) + e/4, min_(other i) e v_2(b_i) + i ).
THE ARGUMENT (written before the engine).
(1) THE RECURSION. For z of level c write h_j = (z^(2^j) - 1)/pi^(c 2^j),
units for j <= m (the levels double below the bend, clock.py).
From (Z - 1)^2 + 2(Z - 1) and 2 = u pi^e,
h_j = h_(j-1)^2 + u pi^(c_j) h_(j-1), c_j = e - c 2^(j-1),
and z^(2^m) + 1 = pi^e (h_m + u), so the landing is
2e + v(S), S = u + h_m. Squaring a congruence mod pi^E keeps it
(2 A delta sits at e + E), and h_j^2 = h_(j-1)^4 mod pi^(2 c_j).
(2) EVERY CLASS BUT THE TOP. Let c >= 2, z = 1 + pi^c t, t = 1 +
t_1 pi + ..., t_1 in {0, 1}. Every c_j >= e/2 >= 2^m, so h_m =
t^(2^m) + [c = 2] u pi^(e/2) h_(m-1) mod pi^(2^m + 1), and
t^(2^m) = 1 + t_1 pi^(2^m) mod pi^(2^m + 1) (below the bend a
leading digit keeps its value under squaring). So S = (u + 1) +
(t_1 + [c = 2]) pi^(2^m) mod pi^(2^m + 1): rigid at l when
l < 2^m, and otherwise at least 2^m with one t_1 attaining it.
The least landing of (c, m) is 2e + min(l, 2^m): tame.py's
formula, the top class excepted, where 2^m = e and c_n = e/2 is
no longer past the precision needed.
(3) THE TOP CLASS. c = 1, e = 2^n, z = 1 + pi t. The chain in (1)
gives h_(n-1)^2 = t^e mod pi^(3e/2) and, for e >= 4,
h_(n-2) = t^(e/4) mod pi^(e/2), so
h_n = t^e + u pi^(e/2) t^(e/2) + u^2 pi^(5e/4) t^(e/4)
mod pi^(3e/2). Put t^(e/4) = 1 + pi^(e/4) q. Expanding the
squares,
S = S_0 + pi^e q^2 (q^2 + u) mod pi^(3e/2),
S_0 = 1 + u + u pi^(e/2) + u^2 pi^(5e/4).
Either t has level >= 2, so v(q) >= e/4, or q = 1 mod pi^(e/4)
(the same chain run on t), so v(q^2 - 1) >= e/2. Either way the
varying term sits at e + min(e/2, l) or deeper. If l < e/2,
v(S_0) = l; if l > e/2, v(S_0) = e/2; both lie below it. If
l = e/2 it sits at 3e/2. So v(S) = v(S_0) for every level-1 unit
whenever v(S_0) < 3e/2, and v(S) >= 3e/2 otherwise. At e = 2,
S = 1 + u + u pi mod pi^3 directly, t = 1 + pi g giving
pi^2 g (g + u) at level 3.
(4) THE CAP IS ATTAINED. g = 1 + pi^3 has v(g^e - 1) = 5e/2 exactly
(levels 3, 6, ..., 3e/4, then 3e/2 and 5e/2, never at the bend),
so if every z has v(z^e + 1) >= 5e/2, one of z and z g has
equality. The least landing of the top class is therefore
2e + min(v(S_0), 3e/2), and w = min(v(S_0), 3e/2).
(5) THE READING. The digit of S_0 at level r >= 1 is u_r plus a
function of u_1..u_(r-1) (carries go up by e, the pi^(e/2) and
pi^(5e/4) terms read lower digits), so v(S_0) >= R holds for one
prefix u_1..u_(R-1) only. u = 1 + pi^(e/2) + pi^(5e/4) mod
pi^(3e/2) is it: S_0 = 2 + pi^e = pi^e (u + 1), at level 3e/2.
And A_e has exactly that u below 3e/2: D = 3 - pi^(e/2) -
2 pi^(e/4) = 1 - pi^(e/2) + pi^e - pi^(5e/4) mod pi^(3e/2), and
(1 + pi^(e/2) + pi^(5e/4)) D = 1 there. By the coefficient
triangle (triangle.py) a prefix of u through level R - 1 is
printed by exactly the coefficient digits through R - 1, so
v(S_0) = lam capped at 3e/2:
w = min(lam, 3e/2) at every place over 2 with a head and
e >= 2,
one coefficient level of any Eisenstein polynomial of the field,
the departure from A_e where the odd prime's is from x^e + p.
The digit string of Q_2(zeta_(2e)) begins with A_e's, since
z = zeta_(2e) has S = 0.
TRANSPLANTS. The formula of (2) is tame.py's, whose step (1) at p = 2
covers c >= 3 and ties at c = 2; the tie is the [c = 2] term, argued
here and not assumed. The top class's 3e/2 - 1 digits and the
skeleton at e/2 and 5e/4 are the record this script replaces, there
proved by a multinomial enumeration; (3) and (4) do not use it.
PREDICTIONS, fixed before the run.
C CONTROL. (a) lam off the coefficients equals the first level where
triangle.py's digits of u leave those of A_e, and v(S_0) computed
in the field equals min(lam, that level cap), at every polynomial
used. (b) The class minimum over z mod pi^(c+3) equals that over
z mod pi^(c+5) at every class of e = 2, 4, 8, over polynomials
built as T's are (lam 1 to 3e/2 + 2, A_e, Phi_(2e)(x + 1), two
random), drawn apart from them, so over c + 4 as well. O reads
three digits and T four; O's other e and polynomials and T at
e = 16 and 32 lie past this control, and it vouches for the least
landing only, not for a rigid class's one value. (c) Every level-c
unit reaches level c 2^j after j squarings, j <= m. (d) A_e's u
is 1 + pi^(e/2) + pi^(5e/4) below 3e/2 at e = 4, 8, 16, 32, 64,
and x^2 + 2x - 6's is 1 + pi below 3.
T THE TOP CLASS. At e = 2, 4, 8, 16 and polynomials with lam = every
value from 1 to 3e/2 + 2, each with random digits above lam, plus
A_e itself, Phi_(2e)(x + 1) and random polynomials: the least
landing of (1, n) is 2e + min(lam, 3e/2), and when lam < 3e/2
every level-1 unit lands there. At e = 32, lam in {1, 16, 17, 40,
41, 47, 48, 50}, the same.
O THE OTHER CLASSES. At e = 4, 8, 16, 6, 10, 12, every class with
c >= 2 and polynomials with l = every value from 1 to e + 2 and
l infinite (x^e + 2), random above: the least landing is 2e +
min(l, 2^m), and when l < 2^m every unit of the class lands there.
W THE WIDTH. At the Eisenstein places with a head over 2 in clock.py
and designed ones at e = 4 and 8 with lam = 1 .. 3e/2 + 1: the
width clock.py reads off the tick ladder (the group exponent, not
the landing) is min(lam, 3e/2); the translate F(x - 2) has
min(lam', 3e/2) = min(lam, 3e/2); and over the quadratic fields
Q_2(sqrt delta), delta squarefree, 2 ramified, |delta| <= 400,
min(lam, 3) is 1 at delta = 2, 6 mod 8, 2 at 3 and 3 at 7 (carrier.py's
delta mod 8 law, a positive control).
D THE LETTERS AT e = 4. The field K(zeta_8) = K(i, sqrt 2) over a
totally ramified quartic K is read by three LETTERS, each of
delta = -1, 2, -2 split (a square in K), unramified (5 delta a
square) or ramified. Every Eisenstein quartic reduces to one of
the 256 CELLS of its coefficient digits at levels 1 to 8, and the
letters are functions of the cell (squares of units are decided
mod pi^9). Transplanted from one census of 49 quartics, the width
w = min(lam, 6) should sort the letters: w = 1 and w = 3 all
ramified; w = 2 exactly one letter not ramified; w = 4 -1
unramified, 2 and -2 ramified; w = 5 -1 split, the others
ramified; w = 6 all three split. The counts of cells at w = 1 to
6 are 128, 64, 32, 16, 8, 8, and the top class's least landing is
8 + w at every cell.
KILLS, as printed observables: any C line off (nothing below is read);
one (polynomial, class) whose least landing is off its formula, or a
rigid class with a unit landing elsewhere; one W place whose ladder
width is off min(lam, 3e/2), a translate whose capped departure
differs, or one delta off its residue's width. D is a map, not a kill:
it prints the letter triples each w carries, and a w whose triples
differ from the transplant is read as the table's correction; one
cell whose letters move under a lift above level 8, or whose top
class lands off 8 + w, is a kill.
FINDINGS. 21/21 checks PASS, no kill fired; D corrected its transplant.
C lam is the first digit of u off A_e's, and min(v(S_0), 3e/2) =
min(lam, 3e/2), at 69 polynomials, 0 off; the class minimum over
three digits equals that over five at 126 (polynomial, class), 0
off; A_e's u has nonzero digits below 3e/2 exactly at 0, e/2 and
5e/4 at e = 4 through 64 ([0, 32, 80] at 64), and x^2 + 2x - 6's
at 0 and 1.
T 81 polynomials over e = 2, 4, 8, 16, 32: the least landing of the
top class is 2e + min(lam, 3e/2) at every one, every value of
min(lam, 3e/2) from 1 to the cap realized at e <= 16, and at the
56 rigid readings every level-1 unit read (digits at levels 1 to 4)
lands there.
O 244 (polynomial, class) readings with c >= 2 over e = 4, 8, 16, 6,
10, 12: the least landing is 2e + min(l, 2^m) at every one, and at
the 35 rigid ones every unit read (digits at levels c to c + 2)
lands there.
W At the ten Eisenstein places with a head over 2 of clock.py and
twenty designed ones at e = 4 and 8, the ladder width is
min(lam, 3e/2): 1 to 6 then 6 over e = 4, 1 to 12 then 12 over
e = 8. The translates agree capped, and move lam above the cap
(Q_2(i) through x^2 - 2x + 2 reads lam = 3, its translate 5). Over
the 324 squarefree delta with 2 ramified and |delta| <= 400,
min(lam, 3) is 1 at delta = 2, 6 mod 8, 2 at 3 and 3 at 7, every
one.
D At each of the 256 cells the letters move under neither of two
random lifts, digits drawn at levels 9 to 16 (512 lifts), 5 is a
square at none, and the top class lands at 8 + w at
every cell, 128, 64, 32, 16, 8, 8 of them at w = 1 to 6. The
letter triples (-1, 2, -2) by w:
w = 1, 3, 5: ram ram ram, ram ram ram, split ram ram, at every
cell;
w = 2: all ram x16, and one letter off ram x48: -1 split x16,
-1 unram x16, 2 split, 2 unram, -2 split, -2 unram x4
each;
w = 4: unram ram ram x12, unram split unram x2, unram unram
split x2;
w = 6: split ram ram x4, split split split x2, split unram
unram x2.
So the transplant held at w = 1, 3 and 5 and failed at 2, 4 and
6: w = 2 carries 16 all-ramified cells and 16 with i in K, and w
= 6 is Q_2(zeta_8) at only 2 of its 8 cells. What w does fix is
the letter of -1 above 2: ramified at 3, unramified at 4, split
at 5 and 6. The constructed splits read as built, and the anchor
design A_4 = x^4 + 2x^2 + 4x - 6 reads split, unram, unram: i in
its field, 2 and -2 unramified, so it is not Q_2(zeta_8).
Tiers: (2) a theorem at p = 2 for every class with c >= 2, and (3) to
(5) a theorem for the top class and the width at every place over 2
with a head and e >= 2, their proof the argument above; lam's reading
off the coefficients a property; D a rule over the 256 cells, the
lifts sampled. C(a) reads its own draw, and the
readout sections redraw and use the designed lam without recomputing
it, so a wrong design shows only as a landing miss; Cc accumulates in
the readout sections and is checked beside their verdicts, not
before them.
RUN RECORD. 21/21, 1.3 s wall, peak working set 12.6 MB under
a memory guard, green on the first run (D added in a later pass, its
section printed once).
"""
import os
os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
import random
from module_law import check, section, CHECKS, vp
from triangle import Field, cell_to_poly, read, cyclotomic
from tame import departure, local, landings, translate, INF
import clock
SEED = 1425
def anchor_levels(e):
"""The levels where A_e has a nonzero coefficient digit."""
return {e // 2, e} | ({5 * e // 4} if e >= 4 else set())
def anchor(e):
return cell_to_poly(2, e, 1, [int(r in anchor_levels(e))
for r in range(1, 2 * e)])
def wild_departure(e, b, d):
"""lam: the least level where a coefficient digit leaves A_e's."""
ab, ad = anchor(e)
best = INF if d == ad else e * vp(d - ad, 2)
for i in range(1, e):
if b[i] != ab[i]:
best = min(best, e * vp(b[i] - ab[i], 2) + i)
return best
def designed(rng, e, lam, top):
"""(b, d) whose digits match A_e's below lam, leave them at lam and
are random above, to level top."""
digs = []
for r in range(1, top + 1):
a = int(r in anchor_levels(e))
digs.append(a if r < lam else 1 - a if r == lam
else rng.randrange(2))
return cell_to_poly(2, e, 1, digs)
def designed_l(rng, e, l, top):
"""(b, d) whose departure from x^e + 2 is exactly l (tame.py's)."""
digs = [0 if r < l else 1 if r == l else rng.randrange(2)
for r in range(1, top + 1)]
b, dp1 = cell_to_poly(2, e, 0, digs)
return b, dp1 - 1
def classes(e):
out, m = [], 0
while e % 2 ** m == 0:
out.append((e // 2 ** m, m))
m += 1
return out
def s_zero(f):
"""v(1 + u + u pi^(e/2) + u^2 pi^(5e/4)) in the field f."""
e, mod, u = f.e, f.mod, f.u
z = [(a + (i == 0)) % mod for i, a in enumerate(u)]
t = f.mul(u, f.pi_pow(e // 2))
z = [(a + x) % mod for a, x in zip(z, t)]
if e >= 4:
t = f.mul(f.mul(u, u), f.pi_pow(5 * e // 4))
z = [(a + x) % mod for a, x in zip(z, t)]
return f.val(z)
def first_off(x, y):
return next((r for r, (a, c) in enumerate(zip(x, y)) if a != c), INF)
def random_poly(rng, e):
b = [0] + [rng.randrange(2 ** 4) for _ in range(1, e)]
return b, 1 + 2 * rng.randrange(2 ** 4)
def top_polys(rng, e, lams):
top = 2 * e + 4
out = [(lam, designed(rng, e, lam, top)) for lam in lams]
out.append((INF, anchor(e)))
out.append((wild_departure(e, *cyclotomic(e)), cyclotomic(e)))
for _ in range(2):
b, d = random_poly(rng, e)
out.append((wild_departure(e, b, d), (b, d)))
return out
# ------------------------------------------------------------ sections
def section_control(rng):
section("C CONTROL: the wild departure, the digit depth, the anchor")
seen = bad = 0
for e in (2, 4, 8, 16):
cap = 3 * e // 2
L = cap + 3
ref = read(2, e, *anchor(e), L)
for lam, (b, d) in top_polys(rng, e, range(1, cap + 3)):
f = Field(2, e, b, d, L)
lw = wild_departure(e, b, d)
off = first_off(f.digits(), ref)
ok = lw == lam and off == (lw if lw <= L else INF)
ok &= min(s_zero(f), cap) == min(lw, cap)
seen += 1
bad += not ok
if not ok:
print(f" OFF e={e} lam={lam} lw={lw} digits off at "
f"{off} v(S_0)={s_zero(f)}")
check("Ca lam = the first digit off A_e's; min(v(S_0), 3e/2) = "
"min(lam, 3e/2)", bad == 0, f"{seen} polynomials, {bad} off")
seen = bad = 0
for e in (2, 4, 8):
for lam, (b, d) in top_polys(rng, e, range(1, 3 * e // 2 + 3)):
loc = local(2, e, b, d)
for (c, m) in classes(e):
seen += 1
bad += (min(landings(loc, c, m, 3)[0])
!= min(landings(loc, c, m, 5)[0]))
check("Cb the class minimum over three digits = over five",
bad == 0, f"{seen} (polynomial, class), {bad} off")
bad = 0
for e in (2, 4, 8, 16, 32, 64):
cap = 3 * e // 2
digs = read(2, e, *anchor(e), cap - 1)
on = [r for r in range(cap) if digs[r]]
want = [0, e // 2] + ([5 * e // 4] if e >= 4 else [])
bad += on != want
print(f" A_{e}: u's nonzero digits below {cap}: {on}")
check("Cd A_e's u is 1 + pi^(e/2) + pi^(5e/4) below 3e/2",
bad == 0, f"{bad} off")
def section_top(rng):
section("T THE TOP CLASS: least landing 2e + min(lam, 3e/2)")
rows = [(e, range(1, 3 * e // 2 + 3)) for e in (2, 4, 8, 16)]
rows.append((32, [1, 16, 17, 40, 41, 47, 48, 50]))
lv_bad = rigid = 0
for e, lams in rows:
cap, n = 3 * e // 2, classes(e)[-1][1]
seen = bad = 0
hist = {}
for lam, (b, d) in top_polys(rng, e, lams):
land, lv_ok, _ = landings(local(2, e, b, d), 1, n, 4)
lv_bad += not lv_ok
want = 2 * e + min(lam, cap)
ok = min(land) == want
if lam < cap:
rigid += 1
ok &= set(land) == {want}
seen += 1
bad += not ok
k = min(land) - 2 * e
hist[k] = hist.get(k, 0) + 1
if not ok:
print(f" OFF e={e} lam={lam}: min {min(land)} want "
f"{want}, spectrum {sorted(set(land))[:6]}")
check(f"T e = {e}, class (1, {n}), cap {cap}", bad == 0,
f"{seen} polynomials, {bad} off; least v(S) "
f"{sorted(hist.items())}")
check("Cc top: every level-1 unit reaches level 2^j after j "
"squarings",
lv_bad == 0, f"{lv_bad} off")
print(f" {rigid} rigid readings (lam < 3e/2)")
def section_other(rng):
section("O THE OTHER CLASSES: least landing 2e + min(l, 2^m), c >= 2")
total = rigid = lv_bad = 0
for e in (4, 8, 16, 6, 10, 12):
seen = bad = 0
top = 2 * e + 4
polys = [(l, designed_l(rng, e, l, top)) for l in range(1, e + 3)]
polys.append((INF, ([0] * e, -1)))
for _ in range(2):
b, d = random_poly(rng, e)
polys.append((departure(2, e, b, d), (b, d)))
for l, (b, d) in polys:
assert l == departure(2, e, b, d)
loc = local(2, e, b, d)
for (c, m) in classes(e):
if c < 2:
continue
land, lv_ok, _ = landings(loc, c, m, 3)
lv_bad += not lv_ok
want = 2 * e + min(l, 2 ** m)
ok = min(land) == want
if l < 2 ** m:
rigid += 1
ok &= set(land) == {want}
seen += 1
bad += not ok
if not ok:
print(f" OFF e={e} l={l} class ({c},{m}): min "
f"{min(land)} want {want}")
total += seen
check(f"O e = {e}, classes {[k for k in classes(e) if k[0] >= 2]}",
bad == 0, f"{seen} (polynomial, class), {bad} off")
check("Cc other: every level-c unit reaches level c 2^j after j "
"squarings",
lv_bad == 0, f"{lv_bad} off")
print(f" {total} (polynomial, class) readings, {rigid} of them rigid")
def ladder_width(loc):
n = clock.bend_power(loc)
lad = clock.ladder(loc, 4 * loc.e + 2)
return lad[n + 1] - 2 * loc.e
def quadratic(delta):
"""An Eisenstein polynomial of Q_2(sqrt delta), 2 ramified."""
if delta % 4 == 2:
return [0, 0], delta // 2
return [0, -1], (delta - 1) // 2
def squarefree(n):
n = abs(n)
return all(n % (q * q) for q in range(2, int(n ** 0.5) + 1))
def section_width(rng):
section("W THE WIDTH: clock.py's ladder width = min(lam, 3e/2) at 2")
rows = []
for loc in clock.PLACES:
if loc.p == 2 and loc.kind == "eis" and clock.criterion(loc):
e = loc.e
b = [0] + [loc.g[i] // 2 for i in range(1, e)]
rows.append((loc.name, e, b, -loc.g[0] // 2))
for e in (4, 8):
for lam in range(1, 3 * e // 2 + 2):
rows.append((f"designed lam={lam}", e,
*designed(rng, e, lam, 2 * e + 4)))
bad = 0
for name, e, b, d in rows:
cap = 3 * e // 2
lam = wild_departure(e, b, d)
w = ladder_width(local(2, e, b, d))
b2, d2 = translate(2, e, b, d)
lam2 = wild_departure(e, b2, d2)
ok = w == min(lam, cap) and min(lam2, cap) == min(lam, cap)
bad += not ok
print(f" {name:16s} e={e} lam={'inf' if lam >= INF else lam} "
f"lam'={'inf' if lam2 >= INF else lam2} ladder w={w}"
f"{'' if ok else ' OFF'}")
check("W the ladder's width is min(lam, 3e/2), the translate agrees",
bad == 0, f"{len(rows)} places, {bad} off")
seen, bad, by = 0, 0, {}
for delta in range(-400, 401):
if delta % 4 in (0, 1) or not squarefree(delta):
continue
b, d = quadratic(delta)
w = min(wild_departure(2, b, d), 3)
want = {2: 1, 6: 1, 3: 2, 7: 3}[delta % 8]
seen += 1
bad += w != want
by.setdefault(delta % 8, set()).add(w)
shown = {k: sorted(v) for k, v in sorted(by.items())}
check("W over Q_2(sqrt delta), |delta| <= 400: min(lam, 3) by delta "
"mod 8", bad == 0, f"{seen} delta, {bad} off; {shown}")
def is_square(f, x, top):
"""A unit x of f is a square iff x = y^2 mod pi^top for a unit y
mod pi^(top - e); top = 2e + 1 decides it (1 + 4 pi O is squares)."""
for idx in range(2 ** (top - f.e - 1)):
y = [1] + [0] * (f.e - 1)
for r in range(1, top - f.e):
if idx >> (r - 1) & 1:
y = [(a + x_) % f.mod for a, x_ in zip(y, f.pi_pow(r))]
diff = [(a - b_) % f.mod for a, b_ in zip(x, f.mul(y, y))]
if f.val(diff) >= top:
return True
return False
def letters(f):
"""The letters of -1, 2 = pi^e u and -2 at a quartic (pi^4 a square)."""
m = f.mod
top = 2 * f.e + 1
neg = [m - 1] + [0] * (f.e - 1)
five = [5] + [0] * (f.e - 1)
out = []
for x in (neg, f.u, f.mul(neg, f.u)):
out.append("split" if is_square(f, x, top) else "unram"
if is_square(f, f.mul(five, x), top) else "ram")
return tuple(out)
def section_letters(rng):
section("D THE LETTERS AT e = 4: K(zeta_8)/K against the width")
e, cap, n = 4, 6, 2
table, count = {}, {}
moved = land_bad = five_sq = 0
for idx in range(2 ** 8):
digs = [idx >> r & 1 for r in range(8)]
b, d = cell_to_poly(2, e, 1, digs)
w = min(wild_departure(e, b, d), cap)
f = Field(2, e, b, d, 12)
lt = letters(f)
five_sq += is_square(f, [5, 0, 0, 0], 2 * e + 1)
for _ in range(2):
b2, d2 = cell_to_poly(2, e, 1, digs + [rng.randrange(2)
for _ in range(8)])
moved += letters(Field(2, e, b2, d2, 12)) != lt
land, _, _ = landings(local(2, e, b, d), 1, n, 4)
land_bad += min(land) != 2 * e + w
count[w] = count.get(w, 0) + 1
table.setdefault(w, {}).setdefault(lt, 0)
table[w][lt] += 1
check("Da the letters are functions of the cell; 5 is never a square",
moved == 0 and five_sq == 0,
f"{moved} moved under 512 lifts, 5 square at {five_sq}")
check("Db the top class lands at 8 + w at every cell", land_bad == 0,
f"256 cells, {land_bad} off; cells by w "
f"{sorted(count.items())}")
for w in sorted(table):
print(f" w = {w}: " + "; ".join(
f"(-1 {a}, 2 {c}, -2 {g}) x{k}"
for (a, c, g), k in sorted(table[w].items())))
fixed = {"x^4 + 2x^2 + 2": (([0, 0, 1, 0], -1), 0),
"x^4 - 2": (([0, 0, 0, 0], 1), 1),
"x^4 + 2": (([0, 0, 0, 0], -1), 2),
"Q_2(zeta_8)": (cyclotomic(4), None)}
bad = 0
for name, ((b, d), slot) in fixed.items():
lt = letters(Field(2, e, b, d, 12))
ok = lt == ("split",) * 3 if slot is None else lt[slot] == "split"
bad += not ok
print(f" {name}: {lt}{'' if ok else ' OFF'}")
a4 = letters(Field(2, e, [0, 2, 1, 0], 3, 12))
bad += a4 != ("split", "unram", "unram")
print(f" A_4 = x^4 + 2x^2 + 4x - 6: {a4}")
check("Dc the constructed splits (i = 1 + pi^2, 2 = pi^4, -2 = pi^4, "
"zeta_8) and A_4's letters (-1 split, 2 and -2 unramified)",
bad == 0, f"{bad} off")
def main():
rng = random.Random(SEED)
section_control(rng)
if not all(CHECKS):
print("\ncontrol failed: nothing below is read")
raise SystemExit(1)
section_top(rng)
section_other(rng)
section_width(rng)
section_letters(rng)
print()
print(f"{sum(CHECKS)}/{len(CHECKS)} checks PASS")
if not all(CHECKS):
raise SystemExit(1)
if __name__ == "__main__":
main()