"""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()