central.py

Checks: TRIPLE: The weighted shortfall is a budget and a zero sum property.

Cited on: TRIPLE.

To run it: save central.py, with cubic.py, room.py beside it; needs mpmath.

"""central.py -- the explicit formula of zeta_H/zeta_K read as the budget
of a complex cubic field's principal shortfall, one nontrivial class at a
time.

QUESTION. Over the complex cubic fields with h >= 2, the trivial class is
short at the small primes: its level in prime powers misses a fixed
weight, about h - 1 times a per-class amount within a factor two across
h = 2 to 7, carried below a few hundred. Is that weight the budget the
explicit formula of zeta_H/zeta_K sets, per nontrivial class, from the
conductor |d_K| and the gamma factor alone?

THE ARGUMENT (written before the engine).
  (1) THE L-FUNCTION. H the Hilbert class field of K, unramified at the
      real place too. zeta_H/zeta_K = prod over chi != 1 of L(s, chi),
      each Ind chi of degree 3, conductor |d_K| N f(chi) = |d_K|, gamma
      Gamma_R(s)^2 Gamma_R(s + 1) (chi trivial at the real place), entire.
  (2) THE COEFFICIENT. Each prime ideal P of K is its own Euler factor
      (1 - chi(P) N(P)^-s)^-1, ramified over Q or not, so -L'/L summed
      over chi != 1 has at p^k the coefficient log p C(p^k),
        C(p^k) = sum over P | p with f_P | k of
                 f_P (h 1[(k/f_P)[P] = 0] - 1).
      At an unramified degree-1 place and k = 1 it is l(P) - 1, l the
      place's principal level (h when principal, 0 when not).
  (3) THE FORMULA, summed over chi and its conjugate (real), with
      Odlyzko's F_T(u) = f(u/T) of room.py:
        H(T) = (h - 1) B(T) + D(T),   B(T) = log |d_K| + G(T),
        D(T) = -2 sum_{p^k < e^T} log p C(p^k) F_T(k log p) / p^(k/2),
      H the sum of F_T^ over the zeros of every L(s, chi), >= 0 under
      GRH. D is the weighted SHORTFALL: a principal place adds
      -(h - 1) w, a non-principal one +w.
  (4) THE BUDGET. phi(T) = -B(T) per nontrivial class, a function of
      |d_K| and T alone, the same at every h. d = D/(h - 1) = phi + z,
      z = H/(h - 1) the zero sum per class, so d >= phi under GRH.
  (5) THE LIMIT. G(T) -> G(inf) = 2 psi(1/4) + psi(3/4) - 3 log pi =
      -12.975 (e^-G(inf) = 431472). Under GRH with no zero at the
      centre, each zero's T f^(gamma T) -> 0: for |x| >= 2 pi,
      x^2 - pi^2 >= 3 x^2 / 4 gives f^(x) <= 128 pi^2 / (9 x^4), so once
      T >= 2 pi / (least |gamma|) every term is at most 128 pi^2 /
      (9 gamma^4 T^3), summable since each L(s, chi) has N(t) =
      O(t log t) zeros to height t; G(T) -> G(inf) as F_T^ -> 2 pi
      delta_0 against the continuous psi terms; so H(T) -> 0 and
      D(T)/(h - 1) ->
      log(431472/|d_K|). At width T the budget is positive only for
      |d_K| < e^-G(T): 656, 2466, 6236, 12508, 21498, 33086, 46901 at
      T = 2 .. 8.

THE ENGINE. cubic.py: every complex cubic field with h >= 2 and
|d_K| <= 12000, the certified class group, the class of every place
over p <= 2980 (= e^8), checkpointed; the degree-2 place over a
partial prime has class -[P1], an inert prime's place is trivial. G(T)
from room.py's quadrature, at 20 digits there only.

PREDICTIONS, frozen before the engine.
  K1 at h = 2, H(T) from (2) equals room.py's H for L(s, rho), each
     field read afresh by room.py's read_field and coefficient table,
     T <= 6, to 1e-9.
  K2 H(T) >= 0 at every field and width T = 2 .. 8 (GRH; an error).
  K3 the formula at T = 7, 8 on zeta (first 100 zeros) and L(s, chi_-3)
     (zeros to t = 60), within 1e-4 (a bar above zeta's zero side
     itself, 1e-5 at T = 8: it reads the formula's cancellation).
  K4 THE BREAK: every class drawn uniformly under the relations (the
     split three summing to 0, [Q] = -2[P] at P^2 Q, P^3 in Cl[3])
     makes H(8) < 0 at >= 0.8 of the fields.
  K5 [Q] = -2[P] at P^2 Q, 3[P] = 0 at P^3, the split three summing to
     0, every place placed.
  Q1 median z(8) <= 0.1, median z(6) <= 0.3.
  Q2 per h = 2 .. 7 (cells of >= 20 fields) mean d(8)/mean phi(8) in
     [1, 1.15], while mean d(8)'s largest over least across h reads
     above 1.15 only as phi's does.
  Q3 the OLS slope of d(8) on log |d_K|: -1.2 to -0.8.
  Q4 printed, not judged: D_1 (the degree-1 places over unramified
     primes at k = 1; Q at P^2 Q is in D - D_1)
     over D at T = 6, 8, by 2-rank; z(T) by T - T_0, T_0 the largest
     width with phi <= 0 (1 where phi > 0 at every width).
  KILL BUDGET. Z/PHI(8) = mean z(8) / mean phi(8) over the fields. The
     shortfall per nontrivial class is the conductor's budget at wide
     support only if Z/PHI(8) <= 0.2 and some h cell of Q2 exists with
     none reading above 1.25 ("some cell exists" added after the full
     run, when a rehearsal with none read the clause vacuously).

K1 reads h = 2 only, where -[P1] = [P1]; the degree-2 class's sign at
h >= 3 is checked by nothing and moves no coefficient, since m(-c) = 0
exactly when mc = 0. Breaking the factor f_P in (2) fails K1 (worst
0.99) and K2 (least H -1.41) at a rehearsal's 44 fields.

FINDINGS.
  F0  CONTROLS (K1 to K5 held). The formula at T = 7, 8 on zeta and
      L(s, chi_-3) to 2.5e-7; 1848 fields to |d_K| <= 12000, all
      certified, 535 with h >= 2, no relation broken, no place unplaced;
      at the 220 fields with h = 2, H equals room.py's to 0.0e+00 (the
      same weights and integer coefficients in the same order); the least
      H over fields and widths 0.0011; uniform classes drive H(8) below 0
      at 456 of 535 (0.852).
  F1  THE BUDGET CARRIES THE SHORTFALL (KILL BUDGET MISSED, Q1 held).
      Per nontrivial class, mean d over mean phi at T = 5 .. 8: 1.270
      against 0.801, 1.621 against 1.342, 1.953 against 1.773, 2.245
      against 2.122; Z/PHI(8) = 0.058. The zero sum is skewed: median z
      0.265, 0.058, 0.043, 0.031 at T = 5 .. 8 against means 0.470,
      0.279, 0.179, 0.123. Below the budget's onset the zeros carry it:
      at T = 2, 3, where phi averages -2.15 and -0.82, d is still 0.28
      and 0.62.
  F2  THE WEIGHTED FLATNESS ACROSS h IS THE BUDGET'S (Q2 held; the
      question's delta is an unweighted count at a cut, not read
      here). Mean d(8) over
      mean phi(8) reads 1.013 (h = 2, 220 fields), 1.081 (3, 169), 1.053
      (4, 45), 1.120 (5, 45), 1.109 (7, 23); h = 6 (19 fields, below the
      cell's 20) 1.186. Across those five cells d(8)'s largest over least
      is 1.164 and phi(8)'s 1.195, mean log |d_K| running 8.54 to
      8.90: the budget spreads across h at least as much as the
      shortfall does.
  F3  THE CONDUCTOR'S SLOPE (Q3 held). d(8) on log |d_K|: -0.884 +-
      0.024 (OLS over 535 fields, se naive), phi's own slope -1.
  F4  printed, not judged. D_1/D: 0.483 (T = 6) and 0.562 (T = 8) at
      2-rank 0 (244 fields), 1.248 and 1.346 at 2-rank 1 (288), 1.389
      and 1.604 at 2-rank 2 (3): with a 2-torsion class the other
      terms add principal weight that the first powers more than pay,
      without one they carry about half (the squares the likely
      carrier, a reading: no print splits D - D_1). Median z
      by T - T_0 falls 2.891, 1.972, 1.190, 0.570, 0.180, 0.058, 0.037,
      0.021, 0.009, 0.003 from -2 to +7 (+7 over the 7 fields with
      phi > 0 at T = 2): by T_0 + 3, two widths past the
      first width with phi > 0, the zeros' part is 0.058.
  TIERS. (1) to (4) are property; F0's positivity is GRH read at 535
  fields; F1 to F4 are observations over this population; (5)'s limit
  is a property under GRH with no central zero, its proof the
  dominated convergence written there.

RUN RECORD. 5/5 controls. A rehearsal at --dmax 2000 (44 fields with
h >= 2) stopped on an empty Q2 list, guarded before the full run; the
full run 155.0 s (the population 95.7 s, checkpointed), peak commit
199.1 MB under a memory guard (512 MB); from the checkpoint 59.9 s,
221.3 MB, every line reproduced. With K1 reading its 220 fields afresh
through room.py's read_field (no checkpoint of room.py read), --fresh
163.8 s, peak commit 182.9 MB, every line reproduced.
"""

import hashlib
import inspect
import json
import os
import random
import sys
import tempfile
import time
from math import exp, log, sqrt

os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import mpmath  # noqa: E402
import cubic as C  # noqa: E402
import room as R  # noqa: E402

RESULTS = []


def _flag(name, default):
    if name in sys.argv:
        return sys.argv[sys.argv.index(name) + 1]
    return default


DMAX = int(_flag("--dmax", 12000))   # the population: |d_K| <= DMAX
WIDTHS = [2, 3, 4, 5, 6, 7, 8]
PMAX = int(exp(max(WIDTHS)))         # 2980
MIN_CELL = 20
SEED = 1657


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


def section(title):
    print(f"\n{title}")


PRIMES = [p for p in C.PRIMES if p <= PMAX]


def weights(T):
    """{(p, k): 2 log p F_T(k log p) / p^(k/2)} over p^k < e^T."""
    W = {}
    for p in PRIMES:
        k = 1
        while k * log(p) < T:
            W[(p, k)] = 2 * log(p) * R.f(k * log(p) / T) / p ** (k / 2)
            k += 1
    return W


W = {T: weights(T) for T in WIDTHS}
G = {}


# ---------------------------------------------------------------------------
# the population

def gneg(x, inv):
    return tuple((-a) % n for a, n in zip(x, inv))


def gadd(x, y, inv):
    return tuple((a + b) % n for a, b, n in zip(x, y, inv))


def gmul(k, x, inv):
    return tuple((k * a) % n for a, n in zip(x, inv))


def read_field(key, dk):
    """[p, type, [[f, class], ...]] for every p <= PMAX; type n, s, p, r2
    (P^2 Q, P listed first), r3."""
    K = C.Field(key, dk)
    if not K.class_group(always_cert=True):
        return {"d": dk, "ok": False}
    inv = list(K.cl)
    rec = {"d": dk, "ok": True, "h": K.h, "inv": inv}
    if K.h < 2:
        return rec
    zero = tuple(0 for _ in inv)
    primes, bad, unplaced = [], 0, 0
    for p in PRIMES:
        typ = K.ptype(p)
        if typ == ((3, 1),):
            primes.append([p, "n", [[3, list(zero)]]])
            continue
        pls = [pl for pl in K.places(p) if pl.deg == 1]
        try:
            cs = [K.place_class(pl) for pl in pls]
        except ValueError:
            cs = [None]
        if any(c is None for c in cs):
            unplaced += 1
            continue
        cs = [tuple(c) for c in cs]
        if typ == ((1, 1), (1, 1), (1, 1)):
            bad += gadd(gadd(cs[0], cs[1], inv), cs[2], inv) != zero
            primes.append([p, "s", [[1, list(c)] for c in cs]])
        elif typ == ((1, 1), (2, 1)):
            primes.append([p, "p", [[1, list(cs[0])],
                                    [2, list(gneg(cs[0], inv))]]])
        elif typ == ((1, 1), (1, 2)):
            cP = [c for pl, c in zip(pls, cs) if pl.e == 2][0]
            cQ = [c for pl, c in zip(pls, cs) if pl.e == 1][0]
            bad += gadd(cQ, gmul(2, cP, inv), inv) != zero
            primes.append([p, "r2", [[1, list(cP)], [1, list(cQ)]]])
        elif typ == ((1, 3),):
            bad += gmul(3, cs[0], inv) != zero
            primes.append([p, "r3", [[1, list(cs[0])]]])
        else:
            raise ValueError("splitting %r" % (typ,))
    rec.update(primes=primes, bad=bad, unplaced=unplaced)
    return rec


_SRC = hashlib.sha1(
    open(os.path.join(HERE, "cubic.py"), "rb").read()
    .split(b'"""', 2)[2] + inspect.getsource(read_field).encode()
    + str(PMAX).encode()).hexdigest()[:12]
CKPT = os.path.join(tempfile.gettempdir(),
                    "central_%d_%s.json" % (DMAX, _SRC))


def population():
    if "--fresh" not in sys.argv and os.path.exists(CKPT):
        print("  read from the checkpoint %s" % CKPT)
        with open(CKPT) as fh:
            return json.load(fh)
    t0 = time.time()
    F = C.enumerate_fields(DMAX)
    print("  %d complex cubic fields with |d_K| <= %d" % (len(F), DMAX))
    recs = []
    for i, (key, dk) in enumerate(sorted(F.items(),
                                         key=lambda kv: (-kv[1], kv[0]))):
        recs.append(read_field(key, dk))
        if (i + 1) % 500 == 0:
            print("    %d fields read, %.0f s" % (i + 1, time.time() - t0),
                  flush=True)
    with open(CKPT, "w") as fh:
        json.dump(recs, fh)
    print("  read in %.1f s, checkpointed to %s" % (time.time() - t0, CKPT))
    return recs


# ---------------------------------------------------------------------------
# one field's budget

def coeff(primes, h, inv):
    """{(p, k): C(p^k)} over p^k <= e^8, (2); and the k = 1 part over
    unramified degree-1 places."""
    out, one = {}, {}
    for p, t, pls in primes:
        k = 1
        while k * log(p) < max(WIDTHS):
            c = 0
            for fP, cls in pls:
                if k % fP == 0:
                    m = k // fP
                    c += fP * (h * all((m * a) % n == 0
                                       for a, n in zip(cls, inv)) - 1)
            out[(p, k)] = c
            k += 1
        if t in ("s", "p"):
            one[p] = sum(h * all(a == 0 for a in cls) - 1
                         for fP, cls in pls if fP == 1)
    return out, one


class Budget:
    def __init__(self, rec, primes=None):
        self.d, self.h, inv = rec["d"], rec["h"], rec["inv"]
        self.inv = inv
        self.rank2 = sum(1 for n in inv if n % 2 == 0)
        logq = log(-self.d)
        c, one = coeff(primes or rec["primes"], self.h, inv)
        self.H, self.D, self.D1, self.phi = {}, {}, {}, {}
        for T in WIDTHS:
            D = -sum(w * c[pk] for pk, w in W[T].items())
            D1 = -sum(w * one[p] for (p, k), w in W[T].items()
                      if k == 1 and p in one)
            self.D[T], self.D1[T] = D, D1
            self.phi[T] = -(logq + G[T])
            self.H[T] = (self.h - 1) * (logq + G[T]) + D

    def z(self, T):
        return self.H[T] / (self.h - 1)

    def dd(self, T):
        return self.D[T] / (self.h - 1)


def planted(rec, rng):
    """Every class drawn uniformly under the relations (K4)."""
    inv, h = rec["inv"], rec["h"]
    zero = tuple(0 for _ in inv)
    allg = [()]
    for n in inv:
        allg = [g + (a,) for g in allg for a in range(n)]
    tors3 = [g for g in allg if gmul(3, g, inv) == zero]
    out = []
    for p, t, pls in rec["primes"]:
        if t == "n":
            out.append([p, t, pls])
        elif t == "s":
            a, b = rng.choice(allg), rng.choice(allg)
            c = gneg(gadd(a, b, inv), inv)
            out.append([p, t, [[1, list(a)], [1, list(b)], [1, list(c)]]])
        elif t == "p":
            a = rng.choice(allg)
            out.append([p, t, [[1, list(a)], [2, list(gneg(a, inv))]]])
        elif t == "r2":
            a = rng.choice(allg)
            out.append([p, t, [[1, list(a)],
                               [1, list(gneg(gmul(2, a, inv), inv))]]])
        else:
            out.append([p, t, [[1, list(rng.choice(tors3))]]])
    return out


# ---------------------------------------------------------------------------
# controls on the formula past room.py's widths

def zeros_chi3(tmax=60.0, step=0.05):
    zs = []
    a, va = 0.01, R.lchi3(0.01)
    while a < tmax:
        b = a + step
        vb = R.lchi3(b)
        if va * vb < 0:
            lo, hi, vlo = a, b, va
            for _ in range(34):
                mid = (lo + hi) / 2
                vm = R.lchi3(mid)
                if vm * vlo < 0:
                    hi = mid
                else:
                    lo, vlo = mid, vm
            zs.append((lo + hi) / 2)
        a, va = b, vb
    return zs


def control_wide():
    zz = [float(mpmath.im(mpmath.zetazero(n))) for n in range(1, 101)]
    z3 = zeros_chi3()
    chi = {1: 1, 2: -1, 0: 0}
    worst = 0.0
    for T in (7, 8):
        lhs = 2 * sum(T * float(R.fhat(g * T)) for g in zz)
        rhs = R.pole(T) + R.gamma_piece(T, 0) - sum(W[T].values())
        print("  zeta, T = %d: zeros %.8f, formula %.8f" % (T, lhs, rhs))
        worst = max(worst, abs(lhs - rhs))
        lhs = 2 * sum(T * float(R.fhat(g * T)) for g in z3)
        rhs = log(3) + R.gamma_piece(T, 1) - sum(
            w * chi[p % 3] ** k for (p, k), w in W[T].items())
        print("  L(s, chi_-3), T = %d: %d zeros %.8f, formula %.8f"
              % (T, len(z3), lhs, rhs))
        worst = max(worst, abs(lhs - rhs))
    check("K3 the formula at T = 7, 8 on zeta and L(s, chi_-3)",
          worst < 1e-4, "worst %.1e" % worst)


def control_room(budgets):
    """K1: at h = 2 the budget's H is room.py's H for L(s, rho)."""
    mine = {}
    for b in budgets:
        if b.h == 2:
            mine.setdefault(b.d, []).append(b)
    theirs = {}
    for key, dk in C.enumerate_fields(DMAX).items():
        if dk in mine:
            r = R.read_field(key, dk)
            if r.get("ok") and r.get("inv") == [2]:
                theirs.setdefault(dk, []).append(r)
    worst, n, skipped = 0.0, 0, 0
    for d, bs in mine.items():
        rs = theirs.get(d, [])
        if len(bs) != 1 or len(rs) != 1:
            skipped += 1
            continue
        r, b = rs[0], bs[0]
        pr = {p: (t, v) for p, t, v in r["primes"]}
        for T in WIDTHS:
            if T > 6:
                continue
            h_room = log(-d) + G[T] - sum(
                w * R.coeff(*pr[p], k) for (p, k), w in W[T].items())
            worst = max(worst, abs(h_room - b.H[T]))
        n += 1
    check("K1 H at h = 2 is room.py's H for L(s, rho), T <= 6",
          n > 0 and worst < 1e-9,
          "%d fields, %d skipped (a shared discriminant), worst %.1e"
          % (n, skipped, worst))


# ---------------------------------------------------------------------------
# statistics

def median(v):
    v = sorted(v)
    n = len(v)
    return (v[n // 2] + v[(n - 1) // 2]) / 2


def mean(v):
    return sum(v) / len(v)


def ols(x, y):
    n = len(x)
    mx, my = mean(x), mean(y)
    sxx = sum((a - mx) ** 2 for a in x)
    b = sum((a - mx) * (c - my) for a, c in zip(x, y)) / sxx
    res = sum((c - my - b * (a - mx)) ** 2 for a, c in zip(x, y))
    return b, sqrt(res / (n - 2) / sxx)


def main():
    t0 = time.time()
    section("A  THE FORMULA")
    with mpmath.workdps(20):
        for T in WIDTHS:
            G[T] = 2 * R.gamma_piece(T, 0) + R.gamma_piece(T, 1)
        Ginf = float(2 * mpmath.digamma(0.25) + mpmath.digamma(0.75)
                     - 3 * mpmath.log(mpmath.pi))
        print("  T    G(T)       e^-G(T)")
        for T in WIDTHS:
            print("  %-3g %9.4f %10.0f" % (T, G[T], exp(-G[T])))
        print("  oo  %9.4f %10.0f" % (Ginf, exp(-Ginf)))
        control_wide()

    section("B  THE POPULATION")
    recs = population()
    pop = [r for r in recs if r["ok"] and "primes" in r]
    bad = sum(r["bad"] for r in pop)
    unpl = sum(r["unplaced"] for r in pop)
    print("  fields %d, uncertified %d, h >= 2: %d"
          % (len(recs), sum(not r["ok"] for r in recs), len(pop)))
    check("K5 [Q] = -2[P], 3[P] = 0, split sum, every place placed",
          bad == 0 and unpl == 0 and all(r["ok"] for r in recs),
          "bad %d, unplaced %d" % (bad, unpl))
    bs = [Budget(r) for r in pop]
    control_room(bs)
    least = min(b.H[T] for b in bs for T in WIDTHS)
    check("K2 H(T) >= 0 at every field and width (GRH)", least >= -1e-9,
          "least H %.4f" % least)
    rng = random.Random(SEED)
    neg = sum(Budget(r, planted(r, rng)).H[8] < 0 for r in pop)
    check("K4 uniform classes drive H(8) below 0 at >= 0.8 of the fields",
          neg >= 0.8 * len(pop), "%d of %d = %.3f"
          % (neg, len(pop), neg / len(pop)))
    if not all(RESULTS):
        print("\na control failed; nothing below is read")
        raise SystemExit(1)

    section("C  THE BUDGET PER NONTRIVIAL CLASS")
    print("  T   median z  mean z   mean phi  mean d   mean d1  Z/PHI")
    for T in WIDTHS:
        zs = [b.z(T) for b in bs]
        ph = [b.phi[T] for b in bs]
        print("  %d %9.4f %8.4f %9.4f %8.4f %8.4f %7.4f"
              % (T, median(zs), mean(zs), mean(ph),
                 mean([b.dd(T) for b in bs]),
                 mean([b.D1[T] / (b.h - 1) for b in bs]),
                 mean(zs) / mean(ph)))
    zp = mean([b.z(8) for b in bs]) / mean([b.phi[8] for b in bs])
    print("  Q1 median z(8) %.4f (<= 0.1), median z(6) %.4f (<= 0.3)"
          % (median([b.z(8) for b in bs]), median([b.z(6) for b in bs])))

    section("D  ACROSS h")
    print("  h   fields  mean log|d_K|  mean d(8)  mean phi(8)  ratio")
    cells = {}
    for b in bs:
        cells.setdefault(b.h, []).append(b)
    worst = 0.0
    dm, pm = [], []
    for h in sorted(cells):
        c = cells[h]
        md, mp = mean([b.dd(8) for b in c]), mean([b.phi[8] for b in c])
        tag = ""
        if 2 <= h <= 7 and len(c) >= MIN_CELL:
            worst = max(worst, md / mp)
            dm.append(md)
            pm.append(mp)
            tag = " *"
        print("  %-3d %6d %13.3f %10.4f %12.4f %7.4f%s"
              % (h, len(c), mean([log(-b.d) for b in c]), md, mp,
                 md / mp, tag))
    if dm:
        print("  (* a Q2 cell) d(8) largest over least %.3f, phi(8)'s %.3f;"
              " largest cell ratio %.4f" % (max(dm) / min(dm),
                                            max(pm) / min(pm), worst))
    b3, se3 = ols([log(-b.d) for b in bs], [b.dd(8) for b in bs])
    print("  Q3 slope of d(8) on log |d_K| %.3f +- %.3f (-1.2 to -0.8)"
          % (b3, se3))

    section("E  WHERE IT SITS (printed)")
    for T in (6, 8):
        for r2 in sorted({b.rank2 for b in bs}):
            c = [b for b in bs if b.rank2 == r2]
            print("  T = %d, 2-rank %d (%d fields): D_1/D %.3f"
                  % (T, r2, len(c), sum(b.D1[T] for b in c)
                     / sum(b.D[T] for b in c)))
    print("  z = d - phi by T - T_0 (T_0 where phi changes sign):")
    rows = {}
    for b in bs:
        lo = max([T for T in WIDTHS if b.phi[T] <= 0], default=None)
        for T in WIDTHS:
            key = T - (lo if lo is not None else 1)
            rows.setdefault(key, []).append(b.z(T))
    for key in sorted(rows):
        print("    T - T_0 = %+d: median z %.4f (%d)"
              % (key, median(rows[key]), len(rows[key])))

    section("F  THE VERDICT")
    print("  Z/PHI(8) = %.4f" % zp)
    alive = zp <= 0.2 and bool(dm) and worst <= 1.25
    print("  KILL BUDGET %s: Z/PHI(8) %.4f (bar 0.2), largest h cell %.4f"
          " (bar 1.25)" % ("MISSED" if alive else "FIRED", zp, worst))
    print("\n%d/%d controls, %.1f s" % (sum(RESULTS), len(RESULTS),
                                        time.time() - t0))
    raise SystemExit(0 if all(RESULTS) else 1)


if __name__ == "__main__":
    main()