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