zeta_family.py

19.8 kB · python · 452 lines

1import os2import sys3import time45import mpmath as mp67HERE = os.path.dirname(os.path.abspath(__file__))8sys.path.insert(0, os.path.join(HERE, "..", "design-zeta"))9sys.path.insert(0, os.path.join(HERE, "..", "zeta-locus"))1011from design_zeta import strip12from zeta_locus import ZCache, alpha_of, build, comb, disc, line, polish, seek, tag, teeth1314RHO = mp.mpf("0.45")15RHO2 = mp.mpf("0.6")16LO = mp.mpf("-0.92")17HI = mp.mpf("3.02")18TOLT = mp.mpf("0.05")19TOLS = mp.mpf("0.05")2021Q3 = [(3, (1,)), (3, (0, 1)), (3, (1, 2)), (3, (0, 1, 2))]22Q4 = [(4, (1,)), (4, (0, 1)), (4, (1, 2)), (4, (1, 3)), (4, (2, 3)),23      (4, (0, 1, 2)), (4, (0, 1, 3)), (4, (0, 2, 3)), (4, (1, 2, 3)), (4, (0, 1, 2, 3))]24Q5 = [(5, (0, 1)), (5, (1, 2))]25HALF = [(9, (0, 1, 2)), (16, (0, 1, 2, 3))]26WIDE = [(10, tuple(range(9)))]27CONTROL = [(2, (0, 1))]28RUNGS = [(5, (0, 1, 2, 3)), (10, tuple(range(8)))]29NEAR = [(3, (0, 1)), (4, (0, 1, 2)), (5, (0, 1, 2, 3)), (10, tuple(range(8))),30        (10, tuple(range(9))), (2, (0, 1)), (3, (0, 1, 2)), (4, (0, 1, 2, 3))]31FIX = [(3, (0, 1)), (4, (0, 1)), (4, (1, 2)), (4, (1, 3)), (4, (2, 3)), (5, (0, 1)),32       (4, (0, 1, 2)), (5, (0, 1, 2, 3)), (10, tuple(range(8))), (2, (0, 1))]33TWO = [(3, (0, 1)), (4, (0, 1)), (4, (1, 2)), (4, (1, 3)), (4, (2, 3))]34LADDER = [(3, (0, 1)), (4, (0, 1, 2)), (5, (0, 1, 2, 3)), (10, tuple(range(8))), (2, (0, 1))]35SETS = {"q3": Q3, "q4": Q4, "q5": Q5, "half": HALF, "wide": WIDE, "ctl": CONTROL,36        "near": NEAR, "fix": FIX, "two": TWO, "ladder": LADDER, "rungs": RUNGS, "q34": Q3 + Q4,37        "all": Q3 + Q4 + Q5 + HALF + CONTROL + RUNGS + WIDE}38CAP = {(10, tuple(range(8))): 25.0}3940# THE LATTICE4142def period(q):43    return 2 * mp.pi / mp.log(q)4445def height(q, ymax):46    per = period(q)47    y = mp.mpf(ymax)48    j0 = int(mp.floor(y / per))49    for j in (j0, j0 + 1):50        if abs(y - j * per) < mp.mpf("0.5"):51            y = (j + mp.mpf("0.5")) * per52    return y5354def lattice(q, F, c):55    k = len(F)56    g1 = mp.mpf(sum(F))57    out = []58    for rec in c:59        out.append({"lvl": 0, "j": rec["j"], "s0": rec["s0"], "r": rec["r"],60                    "null": rec["null"]})61        s1 = rec["s0"] - 162        r1 = s1 * g1 * rec["r"] / (k * (q - 1))63        out.append({"lvl": 1, "j": rec["j"], "s0": s1, "r": r1,64                    "null": rec["null"] or abs(s1) < mp.mpf("1e-15") or g1 == 0})65    return out6667def assign(z, lat, rho=RHO):68    for p in lat:69        if abs(z - p["s0"]) < rho:70            if p["null"]:71                if abs(z - p["s0"]) < mp.mpf("1e-7"):72                    return "cofactor", p73                continue74            return "tooth", p75    return "second", None7677def classify(zs, lat, rho):78    kinds = [(z,) + assign(z, lat, rho) for z in sorted(zs, key=lambda w: mp.im(w))]79    sec = [z for z, kd, p in kinds if kd == "second"]80    tth = [z for z, kd, p in kinds if kd == "tooth"]81    cof = [z for z, kd, p in kinds if kd == "cofactor"]82    lv1 = [z for z, kd, p in kinds if kd == "tooth" and p["lvl"] == 1]83    return sec, tth, cof, lv18485# THE HUNT8687def hunt(d, dp, f, x0, x1, res, seeds, fine=0):88    nx, ny = [(11, 7), (17, 11), (25, 15)][fine]89    extra = [3, 8, 14][fine]90    zs = list(seeds)91    short = []92    for a, b, w, mxx in res:93        want = int(mp.nint(abs(w)))94        got = [z for z in zs if a <= mp.im(z) < b]95        if len(got) >= want:96            continue97        for z0 in seek(f, x0, x1, a, b, want + extra, nx, ny):98            if len(got) >= want:99                break100            z, v = polish(d, z0, (b - a) / 40, x1 - x0, dp)101            if z is None or mp.re(z) < x0 or mp.re(z) > x1:102                continue103            if mp.im(z) < a - mp.mpf("0.02") or mp.im(z) > b + mp.mpf("0.02"):104                continue105            if any(abs(z - y) < mp.mpf("1e-8") for y in zs):106                continue107            zs.append(z)108            if a <= mp.im(z) < b:109                got.append(z)110        if len(got) < want:111            short.append((a, b, want, len(got)))112    return zs, short113114def sweep(q, F, ymax, show=True):115    t0 = time.time()116    y = height(q, ymax)117    a = alpha_of(q, F)118    per = period(q)119    x0, x1 = a + LO, a + HI120    c = comb(q, F, y, verbose=False)121    lat = lattice(q, F, c)122    d = build(q, F, dps=25, tol=mp.mpf(10) ** -10)123    dp = build(q, F, dps=32, tol=mp.mpf(10) ** -18)124    f = ZCache(d)125    res = strip(f, x0, x1, mp.mpf("0.02"), y, max(4, int(y / 2)), 20)126    tot = int(mp.nint(sum(r[2] for r in res)))127    mx = max(r[3] for r in res)128    seeds, discs = [], 0129    for rec in c:130        w = int(mp.nint(rec["w"]))131        if mp.mpf("0.02") + RHO < mp.im(rec["s0"]) < y - RHO:132            discs += w133        if w <= 0:134            continue135        have = [rec["z"]] if rec["z"] is not None and abs(rec["z"] - rec["s0"]) < RHO else []136        if rec["null"]:137            have.append(rec["s0"])138        if len(have) < w:139            have = teeth(f, dp, rec["s0"], RHO, w, False)140        for z in have:141            if x0 < mp.re(z) < x1 and mp.mpf("0.02") < mp.im(z) < y:142                if not any(abs(z - w2) < mp.mpf("1e-8") for w2 in seeds):143                    seeds.append(z)144    zs, short = hunt(d, dp, f, x0, x1, res, seeds)145    for step in (1, 2):146        if short or len(zs) < tot:147            zs, short = hunt(d, dp, f, x0, x1, res, zs, fine=step)148    sec, tth, cof, lv1 = classify(zs, lat, RHO)149    sec6 = classify(zs, lat, RHO2)[0]150    live = [p for p in lat if not p["null"]]151    us = sorted(min(abs(z - p["s0"]) for p in live) for z in sec) if live and sec else []152    out = {"q": q, "F": F, "y": y, "alpha": a, "per": per, "tot": tot, "mx": mx,153           "sec": sec, "tth": tth, "cof": cof, "lv1": lv1, "short": short, "us": us,154           "lat": lat, "sec_n": len(sec), "kq": mp.mpf(len(F)) / q, "discs": discs,155           "sec6": sec6, "y0": mp.mpf(ymax),156           "below": [z for z in sec if mp.im(z) < mp.mpf(ymax)]}157    if not show:158        return out159    line("FAMILY", tag(q, F), "alpha", mp.nstr(a, 12), "alpha/2", mp.nstr(a / 2, 12),160         "k/q", mp.nstr(out["kq"], 8), "period", mp.nstr(per, 10), "T", mp.nstr(y, 10),161         "strip Re", mp.nstr(x0, 8), mp.nstr(x1, 8))162    line("  SPLIT winding", tot, "located", len(zs), "teeth", len(tth),163         "level one teeth", len(lv1), "cofactor only", len(cof),164         "N_2 at rho 0.45", len(sec), "N_2 at rho 0.6", len(sec6),165         "N_2 at rho 0.45 below Im", mp.nstr(mp.mpf(ymax), 6), len(out["below"]),166         "pole disc zeros", discs, "winding less discs", tot - discs,167         "max phase step", mp.nstr(mx, 4), "short boxes", short, "evals", f.n,168         "sec", round(time.time() - t0, 1))169    line("  RADIUS second family distance to the nearest live pole: min",170         "-" if not us else mp.nstr(us[0], 8), "in [0.45,0.6)",171         sum(1 for u in us if u < RHO2), "in [0.45,0.9)",172         sum(1 for u in us if u < mp.mpf("0.9")), "of", len(us),173         "first five", " ".join(mp.nstr(u, 8) for u in us[:5]))174    r10 = [p for p in lat if p["lvl"] == 1 and p["j"] <= 1]175    line("  LEVEL ONE residues derived from r_(0,j)",176         " ".join("j " + str(p["j"]) + " abs r " + mp.nstr(abs(p["r"]), 6)177                  + " null " + str(p["null"]) for p in r10))178    for z in sec:179        t = mp.im(z) / per180        line("  SEC", tag(q, F), "Re", mp.nstr(mp.re(z), 12), "Im", mp.nstr(mp.im(z), 12),181             "Im/per", mp.nstr(t, 10), "frac", mp.nstr(t - mp.floor(t), 8),182             "Re-alpha/2", mp.nstr(mp.re(z) - a / 2, 10),183             "Re-alpha", mp.nstr(mp.re(z) - a, 10))184    if sec:185        rm = max(sec, key=lambda z: mp.re(z))186        ra = max(zs, key=lambda z: mp.re(z))187        line("  RIGHTMOST", tag(q, F), "second family Re", mp.nstr(mp.re(rm), 12), "at Im",188             mp.nstr(mp.im(rm), 12), "right of alpha", mp.re(rm) > a,189             "Mertens exponent of nu_F at least",190             mp.nstr(mp.re(rm), 10) if mp.re(rm) > a else "-",191             "| any zero Re", mp.nstr(mp.re(ra), 12), "at Im", mp.nstr(mp.im(ra), 12),192             "right of alpha", mp.re(ra) > a, "exponent at least",193             mp.nstr(mp.re(ra), 10) if mp.re(ra) > a else "-")194    return out195196# SYMMETRY197198def symmetry(got):199    line("SYMMETRY expectation on the full set c = 1/2 = alpha/2 and every zero self paired,",200         "on a design no functional equation and so failure at the third zero")201    tally = [0, 0, 0, 0, 0, mp.mpf(0)]202    for key in got:203        o = got[key]204        sec = sorted(o["sec"], key=lambda z: mp.im(z))205        if len(sec) < 3:206            line("  ", tag(*key), "second family", len(sec), "too few to test")207            continue208        cF = (mp.re(sec[0]) + mp.re(sec[1])) / 2209        ims = sorted(mp.im(z) for z in sec)210        mgap = min(ims[i + 1] - ims[i] for i in range(len(ims) - 1))211        res = [mp.re(z) for z in sec]212        blo, bhi = min(res), max(res)213        ov = max(mp.mpf(0), min(bhi, cF + TOLS) - max(blo, cF - TOLS))214        pself = ov / (bhi - blo) if bhi > blo else mp.mpf(1)215        ok, bad, worst, first, selfp = 0, 0, mp.mpf(0), None, 0216        for z in sec[2:]:217            want = 2 * cF - mp.re(z)218            if abs(mp.re(z) - cF) < TOLS:219                ok += 1220                selfp += 1221                continue222            best = mp.inf223            for w in sec:224                if abs(w - z) < mp.mpf("1e-9"):225                    continue226                best = min(best, max(abs(mp.im(w) - mp.im(z)), abs(mp.re(w) - want)))227            if best < max(TOLT, TOLS):228                ok += 1229            else:230                bad += 1231                worst = max(worst, mp.mpf(0) if best == mp.inf else best)232                if first is None:233                    first = (z, want, best)234        a = o["alpha"]235        tally[0] += 1236        tally[1] += ok237        tally[2] += ok + bad238        tally[4] += selfp239        tally[5] += pself * (ok + bad)240        if first is not None and abs(first[0] - sec[2]) < mp.mpf("1e-9"):241            tally[3] += 1242        line("  ", tag(*key), "c_F", mp.nstr(cF, 10), "from Im",243             mp.nstr(mp.im(sec[0]), 8), mp.nstr(mp.im(sec[1]), 8),244             "c_F - alpha/2", mp.nstr(cF - a / 2, 8), "c_F - alpha", mp.nstr(cF - a, 8),245             "c_F - 1/2", mp.nstr(cF - mp.mpf("0.5"), 8),246             "paired", str(ok) + "/" + str(ok + bad),247             "worst residual", "-" if first is None else mp.nstr(worst, 8),248             "first failure", "-" if first is None else249             mp.nstr(mp.re(first[0]), 8) + "+" + mp.nstr(mp.im(first[0]), 8) + "i wants Re "250             + mp.nstr(first[1], 8) + " nearest " + mp.nstr(first[2], 6),251             "self pairs", selfp, "reflection partners", ok - selfp,252             "chance self pair rate", mp.nstr(pself, 6), "expected",253             mp.nstr(pself * (ok + bad), 6), "min ordinate gap", mp.nstr(mgap, 8),254             "test tolerance", mp.nstr(TOLT, 3))255    line("  CHANCE every pair recorded is a self pair, a real part landing within",256         mp.nstr(TOLS, 3), "of c_F; the reflection branch needs two zeros within",257         mp.nstr(TOLT, 3), "in Im and the smallest ordinate gap in any design is far above it")258    line("  TALLY self pairs", tally[4], "reflection partners", tally[1] - tally[4],259         "expected self pairs by chance", mp.nstr(tally[5], 6), "of", tally[2],260         "observed", tally[1], "rate", mp.nstr(mp.mpf(tally[1]) / max(1, tally[2]), 6),261         "against chance", mp.nstr(tally[5] / max(1, tally[2]), 6))262    line("  TALLY designs tested", tally[0], "zeros paired",263         str(tally[1]) + "/" + str(tally[2]), "designs failing at the third zero", tally[3])264265def shadow(got):266    line("SHADOW expectation derived from the full set, where the second family IS the zeros of",267         "zeta: if the family is continuous in F its ordinates converge as alpha -> 1")268    line("  the null for an unrelated ordinate set of the same density is a quarter of the mean",269         "gap between consecutive zeta ordinates in the range, and the full set reads exactly 0")270    zz = [mp.im(mp.zetazero(n)) for n in range(1, 40)]271    rows = []272    for key in got:273        o = got[key]274        sec = sorted(o["sec"], key=lambda z: mp.im(z))275        if len(sec) < 3:276            continue277        band = [t for t in zz if mp.im(sec[0]) - 2 < t < mp.im(sec[-1]) + 2]278        if len(band) < 2:279            continue280        gap = (band[-1] - band[0]) / (len(band) - 1)281        near = [min(zz, key=lambda t: abs(mp.im(z) - t)) for z in sec]282        ds = [abs(mp.im(z) - t) for z, t in zip(sec, near)]283        used = len(set(mp.nstr(t, 12) for t in near))284        dr = [abs(mp.re(z) - mp.mpf("0.5")) for z in sec]285        rows.append((o["alpha"], tag(*key), len(sec), sum(ds) / len(ds), max(ds),286                     gap / 4, sum(dr) / len(dr), max(dr), used))287    for a, t, n, md, xd, null, mr, xr, used in sorted(rows):288        line("  ", t, "alpha", mp.nstr(a, 10), "n", n, "mean dist to a zeta ordinate",289             mp.nstr(md, 8), "max", mp.nstr(xd, 8), "null gap/4", mp.nstr(null, 8),290             "ratio to null", mp.nstr(md / null, 8), "distinct ordinates", used,291             "| mean abs(Re - 1/2)", mp.nstr(mr, 8), "max", mp.nstr(xr, 8))292293# THE TRANSPORT READING294295def mertens(got):296    line("MERTENS a zero at Re rho > alpha forces the Mertens exponent of the design's own",297         "Mobius nu_F to be at least Re rho")298    rows = []299    for key in got:300        o = got[key]301        zs = o["sec"] + o["tth"]302        if not zs:303            continue304        ra = max(zs, key=lambda z: mp.re(z))305        rs = max(o["sec"], key=lambda z: mp.re(z)) if o["sec"] else None306        rows.append((tag(*key), o["alpha"], ra, rs))307    for t, a, ra, rs in rows:308        line("  ", t, "alpha", mp.nstr(a, 10), "rightmost zero Re", mp.nstr(mp.re(ra), 12),309             "at Im", mp.nstr(mp.im(ra), 12), "exponent bound",310             mp.nstr(mp.re(ra), 10) if mp.re(ra) > a else "none",311             "rightmost second family Re", "-" if rs is None else mp.nstr(mp.re(rs), 12),312             "second family bound", "none" if rs is None or mp.re(rs) <= a313             else mp.nstr(mp.re(rs), 10))314    line("  BOUNDS designs with a proved exponent bound",315         sum(1 for t, a, ra, rs in rows if mp.re(ra) > a), "of", len(rows),316         "and from the second family alone",317         sum(1 for t, a, ra, rs in rows if rs is not None and mp.re(rs) > a))318319# COUNT320321def wcount(q, F, ymax):322    t0 = time.time()323    y = height(q, ymax)324    a = alpha_of(q, F)325    per = period(q)326    d = build(q, F, dps=25, tol=mp.mpf(10) ** -10)327    f = ZCache(d)328    res = strip(f, a + LO, a + HI, mp.mpf("0.02"), y, max(4, int(y / 10)), 20)329    tot = int(mp.nint(sum(r[2] for r in res)))330    mx = max(r[3] for r in res)331    tt, t6, j = 0, 0, 1332    while j * per + RHO2 < y:333        s0 = a + 2 * mp.pi * 1j * j / mp.log(q)334        tt += int(mp.nint(disc(f, s0, RHO)[0]))335        t6 += int(mp.nint(disc(f, s0, RHO2)[0]))336        j += 1337    w0 = int(mp.nint(disc(f, mp.mpc(a, 0), RHO)[0]))338    line("  WCOUNT", tag(q, F), "T", mp.nstr(y, 10), "winding", tot,339         "teeth at rho 0.45", tt, "N_2 at rho 0.45", tot - tt,340         "teeth at rho 0.6", t6, "N_2 at rho 0.6", tot - t6,341         "j=0 disc", w0, "max phase step", mp.nstr(mx, 4),342         "evals", f.n, "sec", round(time.time() - t0, 1))343    return y, tot - tt, tot - t6344345def count(which, ymax):346    line("COUNT expectation from the classical N(T) = (T/2 pi) log(T/2 pi e) + O(log T):",347         "c = 1/(2 pi) =", mp.nstr(1 / (2 * mp.pi), 10), "d = -(1 + log 2 pi)/(2 pi) =",348         mp.nstr(-(1 + mp.log(2 * mp.pi)) / (2 * mp.pi), 10))349    line("  N_2(T) is the winding of Z on the box less the zeros of Z in every pole disc,",350         "both by the argument principle; no zero is located")351    rows = []352    for q, F in which:353        t1, n1, m1 = wcount(q, F, ymax)354        t2, n2, m2 = wcount(q, F, 2 * ymax)355        cf = (mp.mpf(n2) / t2 - mp.mpf(n1) / t1) / (mp.log(t2) - mp.log(t1))356        df = mp.mpf(n1) / t1 - cf * mp.log(t1)357        rows.append((q, F, t1, n1, t2, n2, cf, df, m1, m2))358    line("  READ c_F and d_F from the two heights, N_2(T) = c_F T log T + d_F T")359    for q, F, t1, n1, t2, n2, cf, df, m1, m2 in rows:360        line("   ", tag(q, F), "alpha", mp.nstr(alpha_of(q, F), 10), "k", len(F),361             "T1", mp.nstr(t1, 8), "N_2 at rho 0.45", n1, "at rho 0.6", m1,362             "T2", mp.nstr(t2, 8), "N_2 at rho 0.45", n2, "at rho 0.6", m2,363             "c_F", mp.nstr(cf, 10), "d_F", mp.nstr(df, 10),364             "c_F 2 pi/log q", mp.nstr(cf * 2 * mp.pi / mp.log(q), 10))365    groups = {}366    for q, F, t1, n1, t2, n2, cf, df, m1, m2 in rows:367        groups.setdefault(mp.nstr(alpha_of(q, F), 8), []).append((tag(q, F), cf))368    for a in sorted(groups):369        fam = groups[a]370        if len(fam) < 2:371            continue372        line("   EQUAL ALPHA", a, "c_F spread",373             mp.nstr(max(x for t, x in fam) - min(x for t, x in fam), 8),374             " ".join(t + " " + mp.nstr(x, 8) for t, x in fam))375    ks = {}376    for q, F, t1, n1, t2, n2, cf, df, m1, m2 in rows:377        ks.setdefault(len(F), []).append((tag(q, F), cf))378    for k in sorted(ks):379        fam = ks[k]380        if len(fam) < 2:381            continue382        line("   EQUAL k", k, "c_F spread",383             mp.nstr(max(x for t, x in fam) - min(x for t, x in fam), 8),384             " ".join(t + " " + mp.nstr(x, 8) for t, x in fam))385386# THE FULL SET LIMIT387388def limit(got):389    line("LIMIT expectation: if RH is the alpha -> 1 face of MrlyMath the second family's",390         "Re s - alpha/2 contracts to 0 as k/q -> 1, spread 0 on the full set")391    rows = []392    for key in got:393        o = got[key]394        sec = o["sec"]395        if not sec:396            continue397        a = o["alpha"]398        v = [mp.re(z) - a / 2 for z in sec]399        m = sum(v) / len(v)400        sd = mp.sqrt(sum((x - m) ** 2 for x in v) / len(v))401        rows.append((o["kq"], key, a, len(v), m, sd, max(v) - min(v), max(abs(x) for x in v)))402    for kq, key, a, n, m, sd, spread, mab in sorted(rows):403        line("  ", tag(*key), "k/q", mp.nstr(kq, 8), "alpha", mp.nstr(a, 10),404             "1-alpha", mp.nstr(1 - a, 10), "n", n, "mean Re-alpha/2", mp.nstr(m, 10),405             "sd", mp.nstr(sd, 10), "spread", mp.nstr(spread, 10),406             "max abs", mp.nstr(mab, 10), "max abs/(1-alpha)",407             "-" if a >= 1 else mp.nstr(mab / (1 - a), 8))408    rat = [(mab / (1 - a), sd / (1 - a), abs(m) / (1 - a), tag(*key))409           for kq, key, a, n, m, sd, spread, mab in rows if a < 1]410    if rat:411        line("  BAND over the designs with alpha < 1: max abs/(1-alpha) in [",412             mp.nstr(min(x[0] for x in rat), 8), ",", mp.nstr(max(x[0] for x in rat), 8),413             "] low", min(rat)[3], "high", max(rat)[3],414             "; sd/(1-alpha) in [", mp.nstr(min(x[1] for x in rat), 8), ",",415             mp.nstr(max(x[1] for x in rat), 8),416             "]; abs mean/(1-alpha) in [", mp.nstr(min(x[2] for x in rat), 8), ",",417             mp.nstr(max(x[2] for x in rat), 8), "]")418    full = [x for x in rows if x[2] >= 1]419    if full:420        line("  FULL SET controls max abs",421             " ".join(tag(*x[1]) + " " + mp.nstr(x[7], 6) for x in full))422423# STUDY424425def main():426    argv = sys.argv[1:]427    verb = argv[0] if argv else "family"428    ymax = float(argv[1]) if len(argv) > 1 else 40.0429    which = SETS[argv[2]] if len(argv) > 2 else SETS["all"]430    t0 = time.time()431    if verb == "count":432        count(which, ymax)433    else:434        line("FAMILY the zeros of zeta_F left when every comb of the pole lattice is stripped;",435             "a zero within abs(u) <", mp.nstr(RHO, 3),436             "of a level zero or level one pole with nonvanishing residue is a tooth")437        line("BLIND the ladder does not reach tolerance in alpha - 1 < Re s < alpha - 0.92,",438             "so every count is a count on alpha - 0.92 < Re s < alpha + 3.02")439        got = {}440        for q, F in which:441            got[(q, F)] = sweep(q, F, min(ymax, CAP.get((q, F), ymax)))442        if verb in ("symmetry", "tests"):443            symmetry(got)444        if verb in ("limit", "tests"):445            limit(got)446            shadow(got)447        if verb in ("family", "tests"):448            mertens(got)449    line("seconds", round(time.time() - t0, 1))450451if __name__ == "__main__":452    main()