abfloor.py

"""abfloor.py -- the 2-power tower past Q(zeta_(2^k)): fields whose
largest abelian subfield F strictly contains C = Q(zeta_(2^k)).

QUESTION. Let K have 2 totally ramified (type (1, 0)), k >= 2 the
largest with C = Q(zeta_(2^k)) in K, F = K_ab strictly containing C,
f = 2^s f' its conductor (f' odd), N = lcm(4, f), H = G_N in G =
(Z/N)^*, m(a) as in the class-minimum theorem. (A) Does some a in H
have m(a) = 2^k, so L* = L(2^k) as at K_ab = C? (B) Does the Fermat
equivalence carry over to F, the Fermat primes past n_c = 2^k + 1
replaced by W_F, the norms n > n_c with n - 1 a power of 2 of places
of F? (C) Where W_F is empty or every m(a) exceeds 2^k, what then?

THE DERIVATION (by hand, before this code).
  (A) Write G = (Z/2^s)^* x (Z/f')^*, U_j = {u = 1 mod 2^j}. 2 totally
  ramified in K, so in F, puts the inertia group at 2, the image of
  (Z/2^s)^* x 1, onto G/H: H maps onto (Z/f')^*. C in F gives H in
  U_k x (Z/f')^*. K2 = {u : (u, 1) in H} is a subgroup of the cyclic
  U_k/U_s, so K2 = U_t/U_s, k <= t <= s; t = k would give H = U_k x
  (Z/f')^*, F = C; so t >= k + 1 (and s > k: at s = k, [F : Q] <=
  2^(k-1) = [C : Q]). The fibre of H over v is a coset u_v K2, and v ->
  u_v U_t is a homomorphism into the cyclic U_k/U_t; were its image in
  U_(k+1)/U_t, Q(zeta_(2^(k+1))) would lie in F, against k largest. So
  its preimage V0 of U_(k+1)/U_t has index 2, the kernel of a quadratic
  character chi = prod chi_l of (Z/f')^*, chi_l trivial or the Legendre
  symbol at each l | f', and every (u, v) in H with chi(v) = -1 has
  v2(u - 1) = k exactly. Choose v by the Chinese remainder theorem: at
  each l, v ≢ 1 mod l, a nonresidue where chi_l is Legendre; the sign is
  (-1)^|S|, S = {l : chi_l nontrivial} nonempty; if |S| is even, S has
  some l >= 5 (3 at most once), and a residue ≢ 1 mod l there (4) flips
  it. So m((u, v)) = 2^k: YES at every such K, L* = L(2^k) (theorem).
  Type (1, 0) is what carries it: without the onto map the odd parts can
  cover H. At N = 120, H = {1, 89, 101, 109}: 109 = 1 mod 3, 101 = 1
  mod 5, 89 = 1 mod 8, so m = 12, 20, 8, 120 and L* > L(4), F of degree
  8 with Q(i) and neither Q(zeta_8), Q(zeta_3) nor Q(zeta_5) in it, 2
  not totally ramified (H does not map onto (Z/15)^*).
  (B) K strictly contains F, so d >= 2 [F : Q] > 2^(k-1) = D, e = d, and
  the place over 2 is held to depth 2^k: L* = P_k S_c as at K_ab = C.
  An odd place of K with n - 1 a power of 2 has n a prime or 9 = 3^2;
  its place in F has norm n (F-norm 3 is not = 1 mod 4). So sigma_F2 =
  S_c W(K) with W(K) over places of K above the places of W_F: where
  W_F is empty, log(1 + margin) = log P_k > 0 at every degree, NO loss
  (theorem). A Fermat prime p = F_m past n_c (2^m >= k + 1) is in W_F
  when it splits completely in F (p ∤ N: p mod N in H) or ramifies with
  residue degree 1. The twist by 3: F_k^(3), N = 3 2^(k+1), H = {(u, v)
  : u in U_k, u in U_(k+1) iff v = 1 mod 3}, of degree 2^k, maps onto
  (Z/3)^*, holds C and not Q(zeta_(2^(k+1))); every F_m, m >= 1, is 2
  mod 3 (2^(2^m) = 1 mod 3), and 1 mod 2^(k+1) where 2^m >= k + 1, so (1, 2) is
  its class, not in H: no Fermat prime past n_c has a place of norm p in
  F_k^(3), 3 is ramified and not Fermat past n_c, and 9 is n_c at k = 3
  and no norm from k = 4 (9 ≢ 1 mod 16). So at every k >= 3, every K of
  type (1, 0) with K_ab = F_k^(3) has the F2 place topping all but
  finitely many columns, at every degree, with no hypothesis on the
  Fermat primes. At k = 2 the same twist, Q(i, sqrt 6), has 3 ramified
  with residue degree 2 (inert in Q(i)): one place of norm 9, W_F =
  {9} at weight 1/4; every F_m, m >= 2, has (6/F_m) = (3/F_m) = -1, so
  that is all of W_F, unconditionally.
  Where W_F is not empty, B its least norm, w_B the share of [F : Q]
  places of F of norm B, the realization over F is K_ab = C's verbatim
  with F for C (TRANSPLANT, re-derived): K = F[t]/g by the Chinese
  remainder theorem and Krasner's lemma, the places of W_F split
  completely in K, the others of residue degree m', at 2 an Eisenstein
  extension of degree m', and three primes above Y giving a closure
  over F of group S_(m'), m' >= 6, primitive and not abelian; for F'
  abelian in K, F'F lies between F and K and is abelian over F, so F'F
  = F and K_ab = F. The lemma (towerpast.lemma) carries w_B on B's
  terms only (TRANSPLANT, re-derived from its coeffs): B's terms scale
  as c w_B, the later norms' and the tail past Y as c, and every later
  norm read at weight 1 bounds it from above (weights are at most 1).
  So with no later norm and c w_B held, a lower w_B costs only d0 (and
  Y). E' (places off W_F below Y) is at most (1 + 1/X) [F : Q] n_c^-s
  (1 + n_c/(s - 1))/(1 - n_c^-s), s = m' b, m' = d0/[F : Q]. Two rows:
  (i) k = 2, F = Q(i, sqrt 6), (B, w_B) = (9, 1/4), no later norm, at
  (X, c, Y) = (1/7, 2, 10^5), so c w_B = 1/2 as in the K_ab = C row at
  k = 2, whose later norms 17, 257, 65537 this row lacks: M and M' are
  that row's -0.817, -20.37 (each reads B only through c w_B), g below
  its -0.474 by about lam0 c (9/17)^b ~ 0.3 less the doubled tail past
  Y (~10^-7); d0 = ceil(2 log 7 7^(log2 9)) about 1860, s ~ 1300, E'
  negligible. (ii) k = 4..7, F = F_k^(257), the twist by the Legendre
  symbol mod 257: 257 = 1 mod 2^(k+1), ramified in F with e = 2 and f =
  1, so w_B = 1/2; 65537 = 2 mod 257, a residue, so 65537 splits
  completely (weight 1), and the Fermat numbers past it read at weight
  1: at (X, c, Y) = (0.381, 1, 257^10), c w_B = 1/2 as in the k >= 4
  row, the later norms' terms doubled: c_z - c w_B ~ 0.00067, c_s ~
  0.00058, so g ~ -0.0016, M ~ -0.0027, M' ~ -8.5 (all below 0); d0 ~
  2186.
  So (B) carries over with W_F for the Fermat primes past n_c: no loss
  where W_F is empty (theorem); a loss at some degree wherever a lemma
  row closes at (B, w_B), its later norms at weight 1. Rows with w_B
  small and later norms at weight 1 can fail (c = c'/w_B multiplies
  them), so the loss side is a theorem only at the rows read
  (superseded: fweights.py closes it at every weight, X falling with
  w_B; these rows stand as instances).

PREDICTIONS (frozen before the engine).
  P1 Over every subgroup H of {a = 1 mod 4} in (Z/N)^*, N = 2^s f', s =
     2..6, f' odd, phi(N) <= 128, whose fixed field has lcm(4, f) = N
     and k >= 2: every H with 2 totally ramified (|I_2 H| = |G|, I_2 the
     classes = 1 mod f') and F != C has min m(a) = 2^k; at least one H
     without it has min m(a) > 2^k, N = 120's {1, 89, 101, 109} among
     them; no H with 2 totally ramified has k = s.
  P2 F_k^(3) at k = 2..15: conductor 3 2^(k+1), 2 totally ramified, k
     largest, degree 2^k; F_m mod N not in H at every m = 1..12 with 2^m
     >= k + 1; at k = 2 the place over 3 has residue degree 2 and e = 2;
     at k >= 3 no place of F_k^(3) has norm in {17, 257, 65537} past n_c.
  P3 Row (i): g, M, M' < 0 and E' < 10^-100; row (ii) at k = 4..7: g,
     M, M' < 0 and E' < 10^-20; 257 ramified in F_k^(257) with e = 2, f
     = 1, and 65537 mod N in H.
  KILL (frozen): a subgroup H of P1 with 2 totally ramified, F != C and
  min m(a) > 2^k (kills (A)); a Fermat prime among 17, 257, 65537 past
  n_c, or any F_m of P2's range, in the class group H of some F_k^(3), k
  >= 3 (kills the unconditional no-loss); row (i) or (ii) not closing
  at its frozen constants (leaves the loss side over that F open, the
  answer to (B) there "not shown").

CONTROLS (frozen).
  C1 POSITIVE for P1: the N = 120 subgroup is found with min m = 8 > 4.
  C2 m(a) and the enumerator at known fields: Q (N = 4, H = {1, 3}):
     min m = 2; Q(i) (N = 4, H = {1}): 4; Q(zeta_8) (N = 8, H = {1}): 8,
     2 totally ramified in both; Q(sqrt -7) (N = 28): 2 not totally
     ramified (it splits).
  C3 the lemma instrument: towerpast.lemma at the K_ab = C row k = 2,
     (9, 1/2, (17, 257, 65537), 1/7, 1, 10^5), reproduces g, M, M' =
     -0.474, -0.817, -20.37 to three places; and fails (g >= 0 or M >=
     0) at X = 0.45, where 2 l(X) + log X > 0, at row (i)'s other
     constants.
  C4 the splitting reader: in Q(i) (N = 4, H = {1}), 5 splits (two
     places of norm 5) and 3 has residue degree 2; in Q(zeta_8), 17
     splits completely.

FINDINGS (entered after the run, from its printed output).
  CONTROLS. C1: N = 120's H read, (min m, k, 2 totally ramified,
  degree) = (8, 2, False, 8). C2: 2, 4, 8; True, True, False (Q(sqrt -7)
  of conductor 7). C3: towerpast.lemma at the K_ab = C row k = 2 prints
  g -0.475044, M -0.817785, M' -20.3776, sillclass.py's own print; the
  row at X = 0.45 fails (g 74.25, M 0.214). C4: (1, 1, 2), (1, 2, 1),
  (1, 1, 4).
  P1 HOLDS: 85 moduli (every N = 0 mod 4 with phi(N) <= 128, s = 2..8),
  643 fields holding Q(i), 52 with 2 totally ramified, 45 of them past
  Q(zeta_(2^k)) (24 at k = 2, 12 at 3, 6 at 4, 2 at 5, 1 at 6); every
  one of the 45 has some m(a) = 2^k; 223 fields without total
  ramification and F != C have none (the first Q(zeta_12), N = 12, min
  m 12); none of the 45 has k = s.
  P2 HOLDS: F_k^(3) at k = 2..15 has degree 2^k, k read k, 2 totally
  ramified, conductor 3 2^(k+1) (read to k = 9); no F_m, m = 1..12 with
  2^m >= k + 1, in H; no norm 17, 257 or 65537 past n_c; 3 has e = 2
  throughout and f = 2, 2, then 2^(k-2) from k = 4 (g = 1 at k = 2, 2
  after), so at k = 2 one place of norm 9.
  P3 HOLDS: row (i), Q(i, sqrt 6), w_9 = 1/4: d0 1859, c_z 0.501050,
  c_s 0.007335, g -0.802489, M -0.817785, M' -20.3776, log10 E' <=
  -910.45. Row (ii), F_k^(257) at k = 4..7, 257 with e = 2, f = 1, w_B
  1/2, 65537 in H: d0 2186, c_z 0.500669, c_s 0.000585, g -0.001618, M
  -0.002721, M' -8.5179 at each k, E' 5.87e-233, 6.08e-143, 3.47e-84,
  2.05e-47.
  KILL MISSED: over every K of type (1, 0), k largest, L* = L(2^k)
  whatever K_ab is (theorem by (A); read at the 45 fields past
  Q(zeta_(2^k)) with phi(N) <= 128); no loss at any degree where W_F is
  empty, F_k^(3) such a field at every k >= 3 with no hypothesis on the
  Fermat primes; a loss at some degree at Q(i, sqrt 6), W_F = {9} at
  1/4, unconditionally, and at F_k^(257), k = 4..7, where 257 ramifies.
  The Fermat equivalence carries over with W_F for the Fermat primes
  past n_c wherever a lemma row closes at (B, w_B).

RUN RECORD. `python abfloor.py` under a 2048 MB cap: 9 of 9 checks, 0.1
s, peak commit 60.2 MB. The engine was appended in one write after the
frozen docstring and rehearsed at phi(N) <= 32 and 64. Changed from the
frozen text, named: C3's comparison is against sillclass.py's printed
-0.475044, -0.817785, -20.3776 (to six places), not the frozen -0.474,
a weaker upper bound; row
(i)'s E' is read in logs (the float underflows to 0, so its frozen
"E' < 10^-100" is read on log10 E' <= -910.45, the hand formula's
value). The subgroup enumerator was read against an independent one
(every <a, b, c>, the 2-ranks here at most 3) at N = 96, 120, 240,
252: 11, 27, 54, 30 subgroups, the same sets. Row (ii)'s later norms
stop at F_9 (a float past 2^1024 overflows); each dropped term is below
c (257/2^1024)^b < 10^-420 at b = log2(1/0.381), under every margin.
At review: the moduli had stopped at s = 6, missing N = 128, 256, 384
(P1 frozen at s = 2..6 and phi(N) <= 128 at once); they now run s =
2..8, checked against every N = 0 mod 4 with phi(N) <= 128 (phi(N) >=
sqrt(N/2) bounds the search), and N = 384 adds the 45th field. A
scratch copy at s = 2..6 prints "[FAIL] the moduli are every N = 0 mod
4 with phi(N) <= PHI_MAX  (82 against 85)". The derivation's "(Pepin's)"
is restated as the arithmetic it is, 2^(2^m) = 1 mod 3.
"""

import math
import os
import sys
import time

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "code"))
os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")

import towerpast as tp  # noqa: E402

CHECKS = []
PHI_MAX = 128


def check(name, ok, detail=""):
    CHECKS.append(bool(ok))
    print(f"  [{'PASS' if ok else 'FAIL'}] {name}" + (f"  ({detail})"
                                                   if detail else ""))


def guard():
    if not all(CHECKS):
        print("a control failed: no verdict")
        sys.exit(1)


# ------------------------------------------------------------ arithmetic

def factor(n):
    out, p = {}, 2
    while p * p <= n:
        while n % p == 0:
            out[p] = out.get(p, 0) + 1
            n //= p
        p += 1
    if n > 1:
        out[n] = out.get(n, 0) + 1
    return out


def phi(n):
    r = n
    for p in factor(n):
        r = r // p * (p - 1)
    return r


def vp(n, p):
    v = 0
    while n % p == 0:
        n //= p
        v += 1
    return v


def m_of(a, N):
    """m(a) = prod l^min(v_l(a - 1), v_l(N)) over l | N."""
    r = 1
    for l, e in factor(N).items():
        t = (a - 1) % (l ** e)
        r *= l ** (e if t == 0 else min(vp(t, l), e))
    return r


def units(N):
    return [a for a in range(1, N) if math.gcd(a, N) == 1]


def conductor(H, N):
    """The least M | N with every unit = 1 mod M in H."""
    for M in sorted(d for d in range(1, N + 1) if N % d == 0):
        if all(a in H for a in range(1, N, M) if math.gcd(a, N) == 1):
            return M
    return N


def two_split(N):
    s = vp(N, 2)
    return s, N >> s


def k_of(H, N):
    s, _ = two_split(N)
    mod = 1 << s
    best = s
    for h in H:
        t = (h - 1) % mod
        best = min(best, s if t == 0 else vp(t, 2))
    return best


def totally_ramified_2(H, N, G):
    """|I_2 H| = |G|, I_2 the classes = 1 mod the odd part of N."""
    _, fo = two_split(N)
    I2 = [a for a in G if a % fo == 1 % fo]
    inter = sum(1 for a in I2 if a in H)
    return len(I2) * len(H) // inter == len(G)


def crt(r1, m1, r2, m2):
    return (r1 + m1 * ((r2 - r1) * pow(m1, -1, m2) % m2)) % (m1 * m2)


def splitting(p, H, N, deg):
    """(e, f, g) of p in the fixed field of H in Q(zeta_N)."""
    if N % p:
        a, e, HI = p % N, 1, H
    else:
        ep = vp(N, p)
        q = p ** ep
        Np = N // q
        I = {crt(1, Np, t, q) for t in range(q) if t % p}
        e = len(I) // len(I & H)
        HI = {h * i % N for h in H for i in I}
        a = crt(p % Np, Np, 1, q)
    f, x = 1, a
    while x not in HI:
        x, f = x * a % N, f + 1
    return e, f, deg // (e * f)


# ------------------------------------------------------- the enumeration

def subgroups(Gp, N):
    """Every subgroup of the abelian group Gp (a list of units mod N):
    the cyclic ones, closed under the join S<g> = union of S g^i."""
    one = frozenset([1])
    gens = []
    seen = set()
    for g in Gp:
        S, x = {1}, g
        while x not in S:
            S.add(x)
            x = x * g % N
        S = frozenset(S)
        if S not in seen:
            seen.add(S)
            gens.append(g)
    subs = {one} | seen
    frontier = list(subs)
    while frontier:
        new = []
        for S in frontier:
            for g in gens:
                if g in S:
                    continue
                J, x = set(S), g
                while x not in S:
                    J |= {s * x % N for s in S}
                    x = x * g % N
                J = frozenset(J)
                if J not in subs:
                    subs.add(J)
                    new.append(J)
        frontier = new
    return subs


def moduli():
    out = []
    for s in range(2, 9):
        for fo in range(1, 400, 2):
            N = (1 << s) * fo
            if phi(N) <= PHI_MAX:
                out.append(N)
    return out


def part_a():
    print(f"\n=== (A) every K_ab holding Q(i), N = 2^s f', s = 2..8, "
          f"phi(N) <= {PHI_MAX}")
    t0 = time.time()
    read = tr = trF = 0
    bad_tr, bad_other, ks_eq_s, by_k = [], [], [], {}
    found120 = None
    Ns = moduli()
    # every N = 0 mod 4 with phi(N) <= PHI_MAX, phi(N) >= sqrt(N/2)
    every = [N for N in range(4, 2 * PHI_MAX ** 2 + 1, 4)
             if phi(N) <= PHI_MAX]
    check("the moduli are every N = 0 mod 4 with phi(N) <= PHI_MAX",
          sorted(Ns) == every, f"{len(Ns)} against {len(every)}")
    for N in Ns:
        G = units(N)
        Gp = [a for a in G if a % 4 == 1]
        s, _ = two_split(N)
        for H in subgroups(Gp, N):
            f = conductor(H, N)
            if math.lcm(4, f) != N:
                continue
            k = k_of(H, N)
            read += 1
            deg = len(G) // len(H)
            mmin = min(m_of(h, N) for h in H)
            ram = totally_ramified_2(H, N, G)
            isC = deg == 1 << (k - 1)
            if N == 120 and H == {1, 89, 101, 109}:
                found120 = (mmin, k, ram, deg)
            if ram:
                tr += 1
                if k == s and not isC:
                    ks_eq_s.append((N, sorted(H)))
                if not isC:
                    trF += 1
                    by_k[k] = by_k.get(k, 0) + 1
                    if mmin != 1 << k:
                        bad_tr.append((N, sorted(H)[:6], mmin, k))
            elif not isC and mmin != 1 << k:
                bad_other.append((N, mmin, k, deg))
    print(f"  {len(Ns)} moduli, {read} fields holding Q(i) read, {tr} with "
          f"2 totally ramified, {trF} of them past Q(zeta_(2^k)) (by k: "
          f"{dict(sorted(by_k.items()))}), {time.time() - t0:.1f} s")
    print(f"  2 totally ramified, F != C, min m(a) != 2^k: {len(bad_tr)} "
          f"{bad_tr[:4]}")
    print(f"  2 not totally ramified, F != C, min m(a) > 2^k: "
          f"{len(bad_other)}, first {bad_other[:4]}")
    print(f"  N = 120, H = {{1, 89, 101, 109}}: (min m, k, 2 tot. ram., "
          f"degree) = {found120}")
    check("C1 POSITIVE: N = 120's H is read, min m = 8 > 4, 2 not totally "
          "ramified", found120 == (8, 2, False, 8))
    check("P1 every F past Q(zeta_(2^k)) with 2 totally ramified has some "
          "m(a) = 2^k; some F without it has none; none has k = s",
          not bad_tr and bad_other and not ks_eq_s and trF > 0)


# ------------------------------------------------------------ the twists

def twist(k, q):
    """F_k^(q): H = {(u, v): u in U_k, u in U_(k+1) iff (v/q) = 1}."""
    N = q << (k + 1)
    m2 = 1 << (k + 1)
    res = {pow(t, 2, q) for t in range(1, q)}
    H = set()
    for v in range(1, q):
        u = 1 if v in res else 1 + (1 << k)
        H.add(crt(u, m2, v, q))
    return N, H


def fermat(m):
    return (1 << (1 << m)) + 1


def part_b():
    print("\n=== (B) the twist by 3, k = 2..15")
    ok = True
    for k in range(2, 16):
        N, H = twist(k, 3)
        G_order = phi(N)
        deg = G_order // len(H)
        kk = k_of(H, N)
        I2 = [crt(u, 1 << (k + 1), 1, 3) for u in range(1, 1 << (k + 1), 2)]
        inter = sum(1 for a in I2 if a in H)
        ram = len(I2) * len(H) // inter == G_order
        f = conductor(H, N) if k <= 9 else None
        hits = [m for m in range(1, 13) if (1 << m) >= k + 1
                and fermat(m) % N in H]
        nc = (1 << k) + 1
        norm_hits = []
        for p in (17, 257, 65537):
            if p > nc and splitting(p, H, N, deg)[1] == 1:
                norm_hits.append(p)
        e3, f3, g3 = splitting(3, H, N, deg)
        print(f"  k = {k}: N {N}, degree {deg}, k read {kk}, 2 tot. ram. "
              f"{ram}, conductor {f if f else '(read to k = 9)'}; F_m in H "
              f"at {hits}; norms 17, 257, 65537 past n_c held {norm_hits}; "
              f"3: e {e3}, f {f3}, g {g3}")
        good = (deg == 1 << k and kk == k and ram and not hits
                and (f is None or f == N))
        if k >= 3:
            good &= not norm_hits
        else:
            good &= (e3, f3) == (2, 2)
        ok &= good
    check("P2 F_k^(3): degree 2^k, k largest, 2 totally ramified, no F_m "
          "(m <= 12) in H, no norm 17, 257, 65537 past n_c from k = 3; at "
          "k = 2, 3 has e = f = 2", ok)


# --------------------------------------------------------- the lemma rows

def e_off(DF, nc, d0, X):
    """E' + E'/x: at most DF places per p off W_F, norms distinct prime
    powers >= nc^m', m' >= d0/DF; 1/x <= 1/X is safe since each term's
    (2/q)^b falls in b, as towerpast.e3."""
    s = (d0 / DF) * -math.log2(X)
    return (1 + 1 / X) * DF * nc ** -s * (1 + nc / (s - 1)) / (1 - nc ** -s)


def show(tag, L):
    print(f"  {tag}: d0 {L['d0']}, c_z {L['cz']:.6f}, c_s {L['cs']:.6f}, "
          f"g {L['G']:.6f}, M {L['M']:.6f}, M' {L['Mp']:.4f}, E' "
          f"{L['E']:.3g}")


def closes(L):
    return L["G"] < 0 and L["M"] < 0 and L["Mp"] < 0


def controls():
    print("=== CONTROLS")
    a9 = math.log2(9)
    L = tp.lemma(1 / 7, 1.0, 0.5, a9, 9, (17, 257, 65537), 1e5, 0.0)
    show("C3 the K_ab = C row at k = 2", L)
    check("C3 towerpast.lemma reproduces sillclass's -0.475044, -0.817785, "
          "-20.3776", f"{L['G']:.6f} {L['M']:.6f} {L['Mp']:.4f}"
          == "-0.475044 -0.817785 -20.3776")
    Lf = tp.lemma(0.45, 2.0, 0.25, a9, 9, (), 1e5, 0.0)
    show("C3 row (i) at X = 0.45", Lf)
    check("C3 POSITIVE: row (i) fails at X = 0.45", not closes(Lf))
    m_q = min(m_of(a, 4) for a in (1, 3))
    m_i, m_8 = m_of(1, 4), m_of(1, 8)
    tr_i = totally_ramified_2({1}, 4, units(4))
    tr_8 = totally_ramified_2({1}, 8, units(8))
    H7 = {a for a in units(28) if pow(a, 3, 7) == 1}
    tr_7 = totally_ramified_2(H7, 28, units(28))
    check("C2 min m at Q, Q(i), Q(zeta_8) = 2, 4, 8; 2 totally ramified in "
          "Q(i), Q(zeta_8), not in Q(sqrt -7)",
          (m_q, m_i, m_8, tr_i, tr_8, tr_7) == (2, 4, 8, True, True, False),
          f"{m_q}, {m_i}, {m_8}; {tr_i}, {tr_8}, {tr_7}; Q(sqrt -7)'s "
          f"conductor {conductor(H7, 28)}")
    s5, s3 = splitting(5, {1}, 4, 2), splitting(3, {1}, 4, 2)
    s17 = splitting(17, {1}, 8, 4)
    check("C4 Q(i): 5 splits, 3 has f = 2; Q(zeta_8): 17 splits completely",
          s5 == (1, 1, 2) and s3 == (1, 2, 1) and s17 == (1, 1, 4),
          f"{s5}, {s3}, {s17}")
    guard()


def part_c():
    print("\n=== (C) the loss rows over F")
    a9 = math.log2(9)
    N, H = twist(2, 3)
    e3, f3, g3 = splitting(3, H, N, 4)
    w9 = g3 / 4
    L = tp.lemma(1 / 7, 2.0, w9, a9, 9, (), 1e5, 0.0)
    L = tp.lemma(1 / 7, 2.0, w9, a9, 9, (), 1e5, e_off(4, 5, L["d0"], 1 / 7))
    show(f"(i) Q(i, sqrt 6), (9, {w9})", L)
    s = (L["d0"] / 4) * math.log2(7)
    lgE = math.log10(8 * 4 * (1 + 5 / (s - 1))) - s * math.log10(5)
    print(f"  (i) E' in logs (the float reads 0, an underflow): log10 E' <= "
          f"{lgE:.2f}")
    ok_i = closes(L) and lgE < -100 and w9 == 0.25
    ok_ii = True
    a257 = math.log2(257)
    later = (65537,) + tuple(fermat(m) for m in range(5, 10))
    for k in range(4, 8):
        N, H = twist(k, 257)
        deg = phi(N) // len(H)
        e, f, g = splitting(257, H, N, deg)
        sp = 65537 % N in H
        wB = g / deg
        X = 0.381
        L = tp.lemma(X, 1.0, wB, a257, 257, later, 257.0 ** 10, 0.0)
        L = tp.lemma(X, 1.0, wB, a257, 257, later, 257.0 ** 10,
                     e_off(deg, (1 << k) + 1, L["d0"], X))
        show(f"(ii) k = {k}, F_k^(257), degree {deg}, 257: e {e}, f {f}, "
             f"w_B {wB}; 65537 in H {sp}", L)
        ok_ii &= closes(L) and L["E"] < 1e-20 and (e, f) == (2, 1) and sp
    check("P3 row (i) closes, E' < 1e-100; row (ii) closes at k = 4..7, "
          "E' < 1e-20, 257 with e = 2, f = 1, 65537 in H", ok_i and ok_ii)
    return ok_i and ok_ii


def main():
    t0 = time.time()
    controls()
    part_a()
    part_b()
    rows = part_c()
    if all(CHECKS):
        print("  KILL MISSED: (A) holds at every field read, F_k^(3) holds "
              "no norm of W past n_c, and both loss rows close")
    elif not rows:
        print("  a loss row did not close: the loss side over that F is "
              "not shown")
    print(f"\n{sum(CHECKS)} of {len(CHECKS)} checks, {time.time() - t0:.1f} s")
    return 0 if all(CHECKS) else 1


if __name__ == "__main__":
    sys.exit(main())