ratio.py

42.3 kB · python · 1196 lines

1import argparse2import time3from math import ceil, floor, gcd, log45import numpy as np67def up(x, d):8    return ceil(x * 10 ** d) / 10 ** d910def down(x, d):11    return floor(x * 10 ** d) / 10 ** d1213# INCREMENT AUTOMATON1415def level(z1, z2):16    start = z1 // 317    if start == 0:18        return 0, 019    dist = {start: 1}20    frontier = [start]21    d = 122    while frontier:23        d += 124        nxt = []25        for j in frontier:26            if j % 3 == 0:27                moves = (j // 3, (j + z1) // 3)28            elif (j - z2) % 3 == 0:29                moves = ((j - z2) // 3,)30            else:31                continue32            for k in moves:33                if k == 0:34                    return d, len(dist)35                if k not in dist:36                    dist[k] = d37                    nxt.append(k)38        frontier = nxt39    return 0, len(dist)4041def reach_size(z1, z2):42    start = z1 // 343    if start == 0:44        return 045    seen = {0, start}46    stack = [start]47    while stack:48        j = stack.pop()49        if j % 3 == 0:50            moves = (j // 3, (j + z1) // 3)51        elif (j - z2) % 3 == 0:52            moves = ((j - z2) // 3,)53        else:54            continue55        for k in moves:56            if k not in seen:57                seen.add(k)58                stack.append(k)59    return len(seen)6061def band_cap(z1, z2):62    return (z1 - 1) // 2 + (z2 - 1) // 2 + 16364def succ(j, z1, z2):65    if j % 3 == 0:66        return (j // 3, (j + z1) // 3)67    if (j - z2) % 3 == 0:68        return ((j - z2) // 3,)69    return ()7071def reach(z1, z2):72    start = z1 // 373    seen = {start}74    stack = [start]75    hit = 076    while stack:77        for k in succ(stack.pop(), z1, z2):78            if k == 0:79                hit = 180            if k not in seen:81                seen.add(k)82                stack.append(k)83    return hit, len(seen)8485def core(z1, z2):86    lo = -((z2 - 1) // 2)87    hi = (z1 - 1) // 288    deg = {}89    for j in range(lo, hi + 1):90        deg[j] = sum(1 for k in succ(j, z1, z2) if lo <= k <= hi)91    dead = [j for j in deg if j and not deg[j]]92    while dead:93        j = dead.pop()94        del deg[j]95        for p in (3 * j, 3 * j - z1, 3 * j + z2):96            if lo <= p <= hi and p in deg and p != 0 and j in succ(p, z1, z2):97                deg[p] -= 198                if not deg[p]:99                    dead.append(p)100    live = set(deg)101    start = z1 // 3102    seen = {start}103    stack = [start]104    ok = start in live105    while stack:106        for k in succ(stack.pop(), z1, z2):107            if k in live:108                ok = True109            if k not in seen:110                seen.add(k)111                stack.append(k)112    return ok, len(live)113114def base3(n):115    s = ""116    while n:117        s = str(n % 3) + s118        n //= 3119    return s or "0"120121BETA = log(2) / log(3)122123def binaries(k):124    out = [0]125    for i in range(k):126        out = out + [a + 3 ** i for a in out]127    return out128129def pairs(cap):130    for z1 in range(3, cap + 1, 3):131        for z2 in range(1, cap + 1):132            if z2 % 3 and gcd(z1, z2) == 1:133                yield z1, z2134135# PAIR CARRY AUTOMATON136137OUT = ((0, 0), (0, 1), (1, 0))138139def carry_occupied(z1, z2):140    seen = set()141    stack = []142    for d in (1, 2):143        s1 = d * z1144        s2 = d * z2145        if (s1 % 3, s2 % 3) in OUT:146            key = (s1 // 3) * z2 + (s2 // 3)147            if key not in seen:148                seen.add(key)149                stack.append((s1 // 3, s2 // 3))150    while stack:151        c1, c2 = stack.pop()152        e1 = c1 % 3153        if e1 == 2:154            continue155        for e2 in ((0, 1) if e1 == 0 else (0,)):156            d = (e2 - c2) * pow(z2 % 3, -1, 3) % 3157            n1 = (d * z1 + c1) // 3158            n2 = (d * z2 + c2) // 3159            if n1 == 0 and n2 == 0:160                return True, len(seen)161            key = n1 * z2 + n2162            if key not in seen:163                seen.add(key)164                stack.append((n1, n2))165    return False, len(seen)166167# BRUTE GASKET168169def brute(n, cap):170    out = {}171    for lab in range(3 ** n):172        u = v = 0173        w = lab174        for i in range(n):175            d = w % 3176            w //= 3177            if d == 1:178                u += 3 ** i179            elif d == 2:180                v += 3 ** i181        if u and v:182            g = gcd(u, v)183            z = (u // g, v // g)184            if max(z) <= cap:185                k = 1186                while 3 ** k <= max(u, v):187                    k += 1188                if z not in out or k < out[z]:189                    out[z] = k190    return out191192# SWEEP193194def cmd_sweep(args):195    cap = args.cap196    t = time.time()197    hs = []198    ws = []199    ls = []200    for z1, z2 in pairs(cap):201        d, _ = level(z1, z2)202        if d:203            hs.append(max(z1, z2))204            ws.append(z1 + z2)205            ls.append(d)206    secs = time.time() - t207    h = np.array(hs, dtype=np.int64)208    w = np.array(ws, dtype=np.int64)209    lv = np.array(ls, dtype=np.int64)210    print("convention height max(z1,z2), ordered directions, occupied at some level, no level cap")211    print("x A(x) theta local")212    ladder = []213    x = 32214    while x <= cap:215        ladder.append(x)216        x *= 2217    loc = []218    prev = None219    for x in ladder:220        a = 2 * int((h <= x).sum())221        th = log(a) / log(x)222        if prev:223            loc.append(log(a / prev[1]) / log(x / prev[0]))224        print(x, a, round(th, 4), round(loc[-1], 4) if prev else "-")225        prev = (x, a)226    print("theta band", down(min(log(2 * (h <= x).sum()) / log(x) for x in ladder[-4:]), 4),227          up(max(log(2 * (h <= x).sum()) / log(x) for x in ladder[-4:]), 4),228          "local band", down(min(loc[-3:]), 4), up(max(loc[-3:]), 4),229          "over x", ladder[-4], ladder[-1])230    print("W Zsum Zmean Zmax argmax logZmax/logW")231    cnt = np.bincount(w, minlength=cap + 1)[: cap + 1]232    zexp = []233    for x in ladder:234        seg = cnt[: x + 1]235        top = 2 * int(seg.max())236        zexp.append(log(top) / log(x))237        print(x, int(2 * seg.sum()), round(2 * seg.sum() / x, 4), top, int(seg.argmax()),238              round(zexp[-1], 4))239    print("Zmax exponent band", down(min(zexp), 4), up(max(zexp), 4),240          "over W", ladder[0], ladder[-1], "needed below", 0.8073)241    print("x meanlev maxlev meanc maxc share_lev_below_1.8073_log3_x")242    for x in ladder:243        sel = h <= x244        c = lv[sel] / (np.log(h[sel]) / log(3))245        print(x, round(float(lv[sel].mean()), 3), int(lv[sel].max()),246              round(float(c.mean()), 3), round(float(c.max()), 3),247              round(float((lv[sel] <= 1.8073 * log(x) / log(3)).mean()), 4))248    print("secs", round(secs, 1))249250# LEVELS251252def cmd_levels(args):253    cap = args.cap254    t = time.time()255    tab = np.zeros(200, dtype=np.int64)256    for z1, z2 in pairs(cap):257        d, _ = level(z1, z2)258        if d:259            tab[d] += 2260    cum = np.cumsum(tab)261    print("cap", cap, "A(inf)", int(cum[-1]))262    print("n A(n,cap) new")263    for n in range(args.lo, args.hi + 1):264        print(n, int(cum[n]), int(tab[n]))265    print("secs", round(time.time() - t, 1))266267# MISSING DIGIT MULTIPLES268269def dcount(n, q):270    v = np.zeros(q, dtype=np.int64)271    v[0] = 1272    idx = np.arange(q)273    a = (3 * idx) % q274    b = (3 * idx + 1) % q275    for _ in range(n):276        nv = np.zeros(q, dtype=np.int64)277        nv[a] = v278        nv[b] += v279        v = nv280    return int(v[0])281282def cmd_multiples(args):283    n = args.n284    print("uniform test D_n(q) q / 2^n over q <= Q coprime to 3, n =", n)285    worst = (0.0, 0)286    for q in range(2, args.qmax + 1):287        if q % 3 == 0:288            continue289        r = dcount(n, q) * q / 2 ** n290        if r > worst[0]:291            worst = (r, q)292    print("Q", args.qmax, "max ratio", round(worst[0], 4), "at q", worst[1])293    print("h q=1+3^h D_2h(q) 2^h 2^(2h)/q ratio")294    for h in range(1, args.hmax + 1):295        q = 1 + 3 ** h296        d = dcount(2 * h, q)297        print(h, q, d, 2 ** h, round(4 ** h / q, 2), round(d * q / 4 ** h, 3))298299# BAND300301def cmd_band(args):302    t = time.time()303    big = (0, None)304    frac = (0, 1, None)305    bad = 0306    for z1, z2 in pairs(args.cap):307        r = reach_size(z1, z2)308        cap = band_cap(z1, z2)309        if r > cap:310            bad += 1311        if r > big[0]:312            big = (r, (z1, z2))313        if r * frac[1] > frac[0] * cap:314            frac = (r, cap, (z1, z2))315    print("cap", args.cap, "violations of the halved cap", bad)316    print("largest reachable set", big[0], "at", big[1], "halved cap", band_cap(*big[1]),317          "fraction", up(big[0] / band_cap(*big[1]), 4))318    print("fullest reachable set", frac[0], "of", frac[1], "at", frac[2],319          "fraction", up(frac[0] / frac[1], 4))320    print("secs", round(time.time() - t, 1))321322# WEIGHT LAYERS323324def cmd_layers(args):325    cap = args.cap326    t = time.time()327    Z = [0] * (cap + 1)328    bad = 0329    for z1 in range(3, cap + 1, 3):330        for z2 in range(1, cap + 1):331            if z2 % 3 == 0 or gcd(z1, z2) != 1:332                continue333            w = z1 + z2334            if w > cap:335                break336            if level(z1, z2)[0]:337                Z[w] += 2338                if w <= 3 * z1 <= 2 * w:339                    bad += 1340    secs = time.time() - t341    print("cap", cap, "occupied directions with z1/w inside [1/3,2/3]", bad)342    print("octave argmax_Z Zmax base3 argmax_exp exp base3 argmax_const const base3")343    x = 32344    binary = True345    while 2 * x - 1 <= cap:346        seg = range(x, 2 * x)347        mz = max(seg, key=lambda w: Z[w])348        me = max(seg, key=lambda w: log(Z[w]) / log(w) if Z[w] else 0.0)349        mc = max(seg, key=lambda w: Z[w] / w ** BETA)350        print(x, mz, Z[mz], base3(mz), me, up(log(Z[me]) / log(me), 4), base3(me),351              mc, up(Z[mc] / mc ** BETA, 4), base3(mc))352        binary = binary and all(set(base3(a)) <= {"0", "1"} for a in (mz, me, mc))353        x *= 2354    print("every argmax of all three columns binary base 3", binary)355    e = max(range(4, cap + 1), key=lambda w: log(Z[w]) / log(w) if Z[w] else 0.0)356    c = max(range(4, cap + 1), key=lambda w: Z[w] / w ** BETA)357    print("max exp", up(log(Z[e]) / log(e), 4), "at", e,358          "max const", up(Z[c] / c ** BETA, 4), "at", c,359          "needed exp below", 0.8073, "secs", round(secs, 1))360361def cmd_weights(args):362    print("w Z(w) phi3 logZ/logw Z/w^(log2/log3) meanreach/sqrt(w) secs")363    for w in args.w:364        t = time.time()365        z = n = r = 0366        for z1 in range(3, w, 3):367            if gcd(z1, w) != 1:368                continue369            n += 1370            hit, s = reach(z1, w - z1)371            z += 2 * hit372            r += s373        print(w, z, 2 * n, up(log(z) / log(w), 4), up(z / w ** BETA, 4),374              up(r / n / w ** 0.5, 4), round(time.time() - t, 1))375376def cmd_core(args):377    print("w Z(w) Zinf(w) Zinf/Z coremean band secs")378    for w in args.w:379        t = time.time()380        z = h = n = c = 0381        for z1 in range(3, w, 3):382            if gcd(z1, w) != 1:383                continue384            n += 1385            z += 2 * reach(z1, w - z1)[0]386            ok, sz = core(z1, w - z1)387            h += 2 * ok388            c += sz389        print(w, z, h, up(h / z, 4), round(c / n, 1), (w - 1) // 2,390              round(time.time() - t, 1))391392# SLOPE COVER393394def tri(k, base):395    a = np.zeros(1, dtype=np.int64)396    b = np.zeros(1, dtype=np.int64)397    for i in range(k):398        p = 3 ** (base + i)399        a = np.concatenate([a, a + p, a])400        b = np.concatenate([b, b, b + p])401    return a, b402403def cover(n, e, low=13):404    m = n + e405    if 3 * 3 ** (n + m) // 2 + 3 ** n >= 2 ** 63:406        raise SystemExit("int64 overflow: reduce --extra or n")407    low = min(low, m)408    al, bl = tri(low, 0)409    ah, bh = tri(m - low, low)410    N = 3 ** n411    M = 3 ** m412    hit = np.zeros(N + 1, dtype=bool)413    u = np.empty(len(al), dtype=np.int64)414    v = np.empty(len(al), dtype=np.int64)415    c = np.empty(len(al), dtype=np.int64)416    d = np.empty(len(al), dtype=np.int64)417    for i in range(len(ah)):418        np.add(al, ah[i], out=u)419        np.add(bl, bh[i], out=v)420        np.add(u, M, out=c)421        np.add(c, v, out=d)422        c *= N423        c //= d424        hit[c] = True425        np.add(u, v, out=d)426        d += M427        np.multiply(u, N, out=c)428        c //= d429        hit[c] = True430    return int(hit[:N].sum())431432def cmd_box(args):433    print("cover counts both swap halves and is a lower estimate of the saturated cover")434    print("saturation at n =", args.lo, "over extra digits 0 ..", args.extra)435    for e in range(args.extra + 1):436        print(args.lo, e, cover(args.lo, e))437    print("n cover(3^-n) 3^n cover*n/3^n log_3 cover / n local secs")438    prev = None439    for n in range(args.lo, args.hi + 1):440        t = time.time()441        c = cover(n, args.extra)442        print(n, c, 3 ** n, down(c * n / 3 ** n, 4), down(log(c) / (n * log(3)), 4),443              "-" if prev is None else down(log(c / prev) / log(3), 4),444              round(time.time() - t, 1))445        prev = c446447# REPUNIT448449def witness(z1, z2):450    start = z1 // 3451    par = {start: (None, z1)}452    frontier = [start]453    while frontier:454        nxt = []455        for j in frontier:456            for k in succ(j, z1, z2):457                a = k * 3 - j458                if k == 0:459                    path = [a]460                    cur = j461                    while cur is not None:462                        path.append(par[cur][1])463                        cur = par[cur][0]464                    e = sum(3 ** i for i, b in enumerate(reversed(path)) if b)465                    return e // (z1 + z2)466                if k not in par:467                    par[k] = (j, a)468                    nxt.append(k)469        frontier = nxt470    return 0471472def factor(n):473    out = []474    p = 2475    while p * p <= n:476        if n % p == 0:477            out.append(p)478            while n % p == 0:479                n //= p480        p += 1481    if n > 1:482        out.append(n)483    return out484485def order3(q):486    d = 1487    x = 3 % q488    while x != 1:489        x = x * 3 % q490        d += 1491    return d492493def count_multiples(k, q):494    v = np.zeros(q, dtype=np.int64)495    v[0] = 1496    idx = np.arange(q)497    for i in range(k):498        nv = v.copy()499        np.add.at(nv, (idx + pow(3, i, q)) % q, v)500        v = nv501    return int(v[0])502503def fourier_multiples(k, q):504    d = order3(q)505    t = np.arange(q)506    P = np.ones(q, dtype=complex)507    for r in range(d):508        P *= 1 + np.exp(2j * np.pi * t * pow(3, r, q) / q)509    return (P ** (k // d)).sum() / q510511def floor_formula(k):512    ps = factor((3 ** k - 1) // 2)513    exact = 0514    four = 0515    for mask in range(2 ** len(ps)):516        q = 1517        for i, p in enumerate(ps):518            if mask >> i & 1:519                q *= p520        sign = (-1) ** bin(mask).count("1")521        exact += sign * count_multiples(k, q)522        four += sign * (2 ** k if q == 1 else fourier_multiples(k, q))523    return ps, exact, four524525def lift_union(k):526    w = (3 ** k - 1) // 2527    pw = np.array([3 ** i for i in range(k)], dtype=np.int64)528    S = np.arange(2 ** k)529    bits = ((S[:, None] >> np.arange(k)) & 1).astype(np.int64)530    occ = set()531    total = 0532    heur = 0.0533    for T in range(0, 2 ** k, 2):534        tb = np.array([(T >> i) & 1 for i in range(k)], dtype=np.int64)535        m = 1 + 2 * int(tb.dot(pw))536        heur += 2 ** k / m537        A = bits.dot(pw * (1 - tb) + pw * tb * 3 ** k)538        for a in A[A % m == 0]:539            z = int(a) // m540            if 0 < z < w and gcd(z, w) == 1:541                occ.add(z)542                total += 1543    return occ, total, heur544545def cmd_repunit(args):546    print("floor Phi_k = #{submask a of R_k : 0 < a < R_k, gcd(a, R_k) = 1}; Occ_T = directions with a witness K_T, "547          "T inside [1, k-1]; lift = |union of Occ_T|, liftsum = Sum |Occ_T|, heur = Sum 2^k / m_T; "548          "deep = Z - lift; delta = Phi / 2^k; X = (Z - Phi) / Phi; maxdigits = longest minimal witness m R_k, maxcol = most uses of one column mod k by a minimal witness")549    print("k R_k primes Phi_k formula fourier Z(R_k) excess lift liftsum heur deep Z/R_k^beta delta X maxdigits maxcol secs")550    lists = {}551    for k in range(2, args.kmax + 1):552        t = time.time()553        w = (3 ** k - 1) // 2554        subs = set(a for a in binaries(k) if 0 < a < w and gcd(a, w) == 1)555        ps, exact, four = floor_formula(k)556        occ = []557        for z1 in range(3, w, 3):558            if gcd(z1, w) != 1:559                continue560            m = witness(z1, w - z1)561            if m:562                occ.append((z1, m))563        Z = 2 * len(occ)564        if k <= args.liftmax:565            lu, liftsum, heur = lift_union(k)566            lift, deep, heur = len(lu), Z - len(lu), round(heur, 1)567        else:568            lift = deep = liftsum = heur = "-"569        digits = maxcol = 0570        for z1, m in occ:571            if z1 in subs:572                continue573            cols = [0] * k574            for i, ch in enumerate(reversed(base3(m * w))):575                cols[i % k] += ch == "1"576            digits = max(digits, len(base3(m * w)))577            maxcol = max(maxcol, max(cols))578        print(k, w, ps, len(subs), exact, round(four.real, 3) if abs(four.imag) < 1e-6 else four,579              Z, Z - len(subs), lift, liftsum, heur, deep, up(Z / w ** BETA, 4), down(len(subs) / 2 ** k, 4),580              down((Z - len(subs)) / len(subs), 4), digits, maxcol, round(time.time() - t, 1))581        if k in (7, 8, 9):582            lists[k] = [(z1, w - z1, m) for z1, m in occ if z1 not in subs]583    for k, rows in lists.items():584        print("non-submask occupied directions of weight R_" + str(k), "=", (3 ** k - 1) // 2,585              "one of each swap pair, 3 | z1; columns z1 z2 m base3(z1) base3(z2) base3(m) base3(m z1) base3(m z2) base3(m w)")586        for z1, z2, m in rows:587            print(z1, z2, m, base3(z1), base3(z2), base3(m), base3(m * z1), base3(m * z2), base3(m * (z1 + z2)))588589# LIFT UNION590591def subset_sums(ws):592    out = np.zeros(1, dtype=np.int64)593    for x in ws:594        out = np.concatenate((out, out + x))595    return out596597def submask_floor(k):598    w = (3 ** k - 1) // 2599    b = subset_sums([3 ** i for i in range(k)])600    return int(((b > 0) & (b < w) & (np.gcd(b, w) == 1)).sum())601602def lift_scan(k):603    w = (3 ** k - 1) // 2604    h = max(k // 2, 1)605    v1 = {}606    for tl in range(0, 1 << h, 2):607        v1[tl] = subset_sums([3 ** (i + k) if tl >> i & 1 else 3 ** i for i in range(h)])608    v2 = {}609    for th in range(1 << (k - h)):610        v2[th] = subset_sums([3 ** (i + k) if th >> (i - h) & 1 else 3 ** i for i in range(h, k)])611    occ = set()612    total = 0613    model = 0.0614    best = (0.0, 0, 0)615    for th in range(1 << (k - h)):616        ah = sum(3 ** i for i in range(h, k) if th >> (i - h) & 1)617        V2 = v2[th]618        for tl in range(0, 1 << h, 2):619            al = sum(3 ** i for i in range(h) if tl >> i & 1)620            T = tl | (th << h)621            m = 1 + 2 * (al + ah)622            model += 2 ** k / m623            z = mod_match(v1[tl], V2, m, w)624            total += int(z.size)625            occ.update(z.tolist())626            ratio = z.size * m / 2 ** k627            if ratio > best[0]:628                best = (ratio, m, T)629    return occ, total, model, best630631def occ_of(k, T):632    w = (3 ** k - 1) // 2633    m = 1 + 2 * sum(3 ** i for i in range(k) if T >> i & 1)634    A = subset_sums([3 ** (i + k) if T >> i & 1 else 3 ** i for i in range(k)])635    z = A[A % m == 0] // m636    z = z[(z > 0) & (z < w) & (np.gcd(z, w) == 1)]637    return m, set(z.tolist())638639def cyclotomic_occ(t):640    k = 2 * t + 1641    w = (3 ** k - 1) // 2642    m = 3 ** (2 * t) - 3 ** t + 1643    out = set()644    for S in range(1, 1 << (t - 1)):645        c = sum(3 ** (1 + i) for i in range(t - 1) if S >> i & 1)646        z = c * (3 ** t + 1)647        if gcd(z, w) == 1:648            out.add(z)649            out.add(w - z)650    return m, out651652def cmd_lifts(args):653    print("Occ_T = {A / m_T : A submask of K_T = m_T R_k, m_T | A, 0 < A < K_T, gcd(A / m_T, R_k) = 1}, T inside [1, k-1], m_T = 1 + 2 a_T; "654          "U = |union of Occ_T|, sum = Sum_T |Occ_T|, model = Sum_T 2^k / m_T, peak = max_T |Occ_T| m_T / 2^k at multiplier mstar; "655          "Phi = submask floor, agg = sum 2^k / (Phi model) is the aggregate against the model after the coprime cut; "656          "cyc = |Occ_T| at T = [t, 2t-1] when k = 2t+1, pred its cyclotomic lower bound; deep = Z(R_k) - U where the band automaton reaches")657    print("k R_k Phi U sum L U/2^k U/Phi U/sum agg peak mstar cyc pred Z deep secs")658    band = []659    for k in range(2, args.kmax + 1):660        t = time.time()661        w = (3 ** k - 1) // 2662        phi = submask_floor(k)663        occ, total, model, best = lift_scan(k)664        if k % 2 and k >= 5:665            j = k // 2666            mstar, low = cyclotomic_occ(j)667            cyc = len(occ_of(k, ((1 << j) - 1) << j)[1])668            pred = len(low)669        else:670            cyc = pred = mstar = "-"671        if k <= args.zmax:672            z = 2 * sum(1 for z1 in range(3, w, 3) if gcd(z1, w) == 1 and witness(z1, w - z1))673            deep = z - len(occ)674        else:675            z = deep = "-"676        agg = total * 2 ** k / (phi * model)677        band.append((len(occ) / phi, agg, model / 2 ** k, best[0], len(occ) / total))678        print(k, w, phi, len(occ), total, down(model / 2 ** k, 5), down(len(occ) / 2 ** k, 4),679              down(len(occ) / phi, 4), down(len(occ) / total, 4), down(agg, 4),680              down(best[0], 3), best[1], cyc, pred, z, deep, round(time.time() - t, 1))681    tail = band[args.lo - 2:]682    names = ("U/Phi", "agg", "L", "peak", "U/sum")683    print("bands over k =", args.lo, "..", args.kmax, "".join(684        " " + n + " [" + str(down(min(r[i] for r in tail), 5)) + ", " + str(up(max(r[i] for r in tail), 5)) + "]"685        for i, n in enumerate(names)))686687def raw_scan(k):688    h = max(k // 2, 1)689    v1 = {}690    for tl in range(0, 1 << h, 2):691        v1[tl] = subset_sums([3 ** (i + k) if tl >> i & 1 else 3 ** i for i in range(h)])692    v2 = {}693    for th in range(1 << (k - h)):694        v2[th] = subset_sums([3 ** (i + k) if th >> (i - h) & 1 else 3 ** i for i in range(h, k)])695    ms = []696    ns = []697    tops = []698    for th in range(1 << (k - h)):699        ah = sum(3 ** i for i in range(h, k) if th >> (i - h) & 1)700        V2 = v2[th]701        for tl in range(0, 1 << h, 2):702            al = sum(3 ** i for i in range(h) if tl >> i & 1)703            T = tl | (th << h)704            m = 1 + 2 * (al + ah)705            if m == 1:706                n = 1 << k707            else:708                r2s = np.sort(V2 % m)709                key = (m - v1[tl] % m) % m710                n = int((np.searchsorted(r2s, key, side="right") - np.searchsorted(r2s, key, side="left")).sum())711            ms.append(m)712            ns.append(n)713            tops.append(T.bit_length() - 1)714    return np.array(ms, dtype=np.int64), np.array(ns, dtype=np.int64), np.array(tops)715716def residue_dist(k, T):717    m = 1 + 2 * sum(3 ** i for i in range(k) if T >> i & 1)718    v = np.zeros(m, dtype=np.int64)719    v[0] = 1720    for i in range(k):721        p = i + k if T >> i & 1 else i722        v = v + np.roll(v, pow(3, p, m))723    return m, v724725def fourier_direct(k, T, m):726    u = np.arange(m)727    F = np.ones(m, dtype=complex)728    for i in range(k):729        p = i + k if T >> i & 1 else i730        F *= 1 + np.exp(2j * np.pi * (u * pow(3, p, m) % m) / m)731    return F732733def phi2p_T(p, t):734    k = (p - 1) * t + 1735    T = 0736    for i in range(1, p - 1, 2):737        for j in range(t):738            T |= 1 << (t * i + j)739    return k, T740741def cyclotomic_poly(n, x):742    num = 1743    den = 1744    for d in range(1, n + 1):745        if n % d == 0:746            mu = mobius(n // d)747            if mu == 1:748                num *= x ** d - 1749            elif mu == -1:750                den *= x ** d - 1751    return num // den752753def mobius(n):754    ps = factor(n)755    m = n756    for p in ps:757        if m % (p * p) == 0:758            return 0759    return (-1) ** len(ps)760761def raw_count(k, T):762    m = 1 + 2 * sum(3 ** i for i in range(k) if T >> i & 1)763    A = subset_sums([3 ** (i + k) if T >> i & 1 else 3 ** i for i in range(k)])764    return m, int((A % m == 0).sum())765766def no_one_digits(u, m, lo, hi):767    ok = np.ones(u.size, dtype=bool)768    for j in range(lo, hi + 1):769        ok &= (u * 3 ** j // m) % 3 != 1770    return ok771772def cmd_agg(args):773    print("N_T = #{A submask of K_T : m_T | A} with A = 0 and K_T kept, T over every subset of [1, k-1] with {} included; "774          "F_T(u) = Prod_{p in supp K_T} (1 + e(u 3^p / m_T)); identity N_T = (1/m_T) Sum_u F_T(u), the u = 0 term 2^k / m_T; "775          "M = Sum_T N_T, L = Sum_T 1/m_T, share = (M - 2^k L) / 2^k is the aggregate u != 0 part, "776          "agg' = Sum_T (N_T - 2) / (2^k L) the cut-free aggregate against the model, "777          "Abs = Sum_T (1/m_T) Sum_{u != 0} |F_T(u)| / 2^k, top = max_T max_{u != 0} F_T(u) / 2^k at T*, "778          "iderr = max over T and u of |F_T from the product - F_T from the FFT|, imag = max |Im F_T|")779    print("k M M/2^k L share agg' Abs Abs/prev top T* iderr imag secs")780    prev = None781    for k in range(args.ulo, args.umax + 1):782        t0 = time.time()783        M = 0784        L = 0.0785        absum = 0.0786        top = (0.0, 0)787        iderr = 0.0788        imag = 0.0789        for T in range(0, 1 << k, 2):790            m, v = residue_dist(k, T)791            F = np.fft.fft(v)792            imag = max(imag, float(np.abs(F.imag).max()))793            Fr = F.real794            n = int(v[0])795            if abs(Fr.mean() - n) > 1e-6 * max(n, 1):796                raise SystemExit("identity fails at k %d T %d" % (k, T))797            if k <= args.directmax:798                D = fourier_direct(k, T, m)799                iderr = max(iderr, float(np.abs(D - np.conj(F)).max()))800            M += n801            L += 1 / m802            if m > 1:803                absum += (np.abs(Fr).sum() - 2 ** k) / m804                mx = float(Fr[1:].max()) / 2 ** k805                if mx > top[0]:806                    top = (mx, T)807        share = (M - 2 ** k * L) / 2 ** k808        aggp = (M - 2 * (1 << (k - 1))) / (2 ** k * L)809        absk = absum / 2 ** k810        Tset = [i for i in range(k) if top[1] >> i & 1]811        print(k, M, down(M / 2 ** k, 5), down(L, 5), down(share, 5), down(aggp, 5), down(absk, 4),812              "-" if prev is None else down(absk / prev, 4), down(top[0], 5), Tset,813              "%.1e" % iderr if k <= args.directmax else "-", "%.1e" % imag, round(time.time() - t0, 1))814        prev = absk815    print("mass at the cyclotomic T = [t, 2t-1], k = 2t+1, m = Phi_6(3^t): N_T against 2^t, F_T(1)/2^k against its bound 1 - 13 9^(-t) for t >= 2, "816          "share_no1 = the share of Sum_{u != 0} F_T(u) carried by the u whose digits d_2..d_t of u/m avoid 1, frac_no1 = how many such u per m")817    print("t k m N_T 2^t F(1)/2^k bound share_no1 frac_no1")818    for t in range(2, args.cycmax + 1):819        k = 2 * t + 1820        T = ((1 << t) - 1) << t821        m, v = residue_dist(k, T)822        F = np.fft.fft(v).real823        u = np.arange(m)824        ok = no_one_digits(u, m, 2, t)825        ok[0] = False826        rest = F[1:].sum()827        print(t, k, m, int(v[0]), 2 ** t, down(F[1] / 2 ** k, 6), down(1 - 13 * 9.0 ** (-t), 6),828              down(F[ok].sum() / rest, 4), down(ok.sum() / m, 5))829    print("the antipodal family: p odd, t >= 1, 0 <= s <= t, k = (p-1) t + s, T = Union_{i odd <= p-2} [ti, ti+t-1], "830          "m_T = (3^(pt) + 1) / (3^t + 1), N_T = 2^((p-1)(t-s)/2 + s) exactly, 2^t at s = t; the verb raises on any mismatch; "831          "printed per (p, t): the k range, m_T, whether m_T is that quotient, and N_T against pred at every s")832    print("p t k_lo..k_hi m_T quotient N_T(s=0..t) pred(s=0..t) equal")833    for p in range(3, 20, 2):834        for t in range(1, 20):835            if (p - 1) * t > args.kmax:836                break837            T = phi2p_T(p, t)[1]838            m = 1 + 2 * sum(3 ** i for i in range(64) if T >> i & 1)839            ns = []840            preds = []841            ok = True842            for s in range(0, t + 1):843                k = (p - 1) * t + s844                if k > args.kmax:845                    break846                n = raw_count(k, T)[1]847                pred = 2 ** ((p - 1) * (t - s) // 2 + s)848                ns.append(n)849                preds.append(pred)850                ok &= n == pred851            print(p, t, "%d..%d" % ((p - 1) * t, (p - 1) * t + len(ns) - 1), m, m == (3 ** (p * t) + 1) // (3 ** t + 1), ns, preds, ok)852            if not ok or m != (3 ** (p * t) + 1) // (3 ** t + 1):853                raise SystemExit("antipodal family fails at p %d t %d" % (p, t))854    print("the cut-free aggregate to k = kmax by meet in the middle: M, M/2^k, L, share, agg', "855          "and the excess by top digit t = max T, X_t = Sum_{max T = t} (N_T - 2 - 2^k / m_T) / 2^k at its argmax")856    print("k M M/2^k L share agg' t* X_t* secs")857    band = []858    for k in range(2, args.kmax + 1):859        t0 = time.time()860        ms, ns, tops = raw_scan(k)861        M = int(ns.sum())862        L = float((1.0 / ms).sum())863        share = (M - 2 ** k * L) / 2 ** k864        aggp = (M - 2 * (1 << (k - 1))) / (2 ** k * L)865        ex = (ns - 2 - 2.0 ** k / ms) / 2 ** k866        ex[ms == 1] = 0867        xt = np.array([ex[tops == t].sum() for t in range(k)])868        ts = int(xt.argmax())869        band.append((M / 2 ** k, share, aggp))870        print(k, M, down(M / 2 ** k, 5), down(L, 5), down(share, 5), down(aggp, 5), ts, down(xt[ts], 5), round(time.time() - t0, 1))871    tail = band[args.lo - 2:]872    names = ("M/2^k", "share", "agg'")873    print("bands over k =", args.lo, "..", args.kmax, "".join(874        " " + n + " [" + str(down(min(r[i] for r in tail), 5)) + ", " + str(up(max(r[i] for r in tail), 5)) + "]"875        for i, n in enumerate(names)))876877# DEEP TAIL878879def col_sums(opts):880    out = np.zeros(1, dtype=np.int64)881    for o in opts:882        out = np.concatenate([out + v for v in o])883    return out884885def block_half(k, cols, slots, key):886    opts = []887    g = 0888    for i, r in enumerate(cols):889        e = key // 3 ** i % 3890        if slots == 1:891            opts.append([0, 3 ** (r + k * e)])892        else:893            ps = [3 ** (r + k * j) for j in range(3) if j != e]894            opts.append([0, ps[0], ps[1], ps[0] + ps[1]])895        g += 3 ** r * (0 if e == 0 else 1 if e == 1 else 3 ** k + 1)896    return col_sums(opts), g897898def mod_match(V1, V2, m, w):899    r2 = V2 % m900    order = np.argsort(r2, kind="stable")901    r2s = r2[order]902    key = (m - V1 % m) % m903    lo = np.searchsorted(r2s, key, side="left")904    hi = np.searchsorted(r2s, key, side="right")905    cnt = hi - lo906    nz = np.nonzero(cnt)[0]907    if nz.size == 0:908        return np.zeros(0, dtype=np.int64)909    reps = cnt[nz]910    left = np.repeat(nz, reps)911    base = np.repeat(np.cumsum(reps) - reps, reps)912    pos = np.repeat(lo[nz], reps) + np.arange(reps.sum()) - base913    z = (V1[left] + V2[order[pos]]) // m914    return z[(z > 0) & (z < w) & (np.gcd(z, w) == 1)]915916def block3_scan(k, slots):917    if 3 * k * log(3) / log(2) > 62:918        raise ValueError("the depth-3 census is int64 bound to k <= 13")919    w = (3 ** k - 1) // 2920    X = 3 ** k921    h = max(k // 2, 1)922    low = [block_half(k, list(range(h)), slots, key) for key in range(3 ** h)]923    occ = set()924    for kh in range(3 ** (k - h)):925        V2, g2 = block_half(k, list(range(h, k)), slots, kh)926        for kl, (V1, g1) in enumerate(low):927            if (kl % 3 == 0) != (slots == 1):928                continue929            g = g1 + g2930            m = 1 + 2 * g if slots == 1 else X * (X + 1) - 2 * g931            occ.update(mod_match(V1, V2, m, w).tolist())932    return occ933934def column_vectors(k, b):935    n = b * k936    S = np.arange(1, 1 << n)937    bits = ((S[:, None] >> np.arange(n)) & 1).astype(np.int64)938    val = bits.dot(np.array([3 ** i for i in range(n)], dtype=np.int64))939    keep = val % ((3 ** k - 1) // 2) == 0940    return bits[keep].reshape(-1, b, k).sum(axis=1)941942def lift_lengths(k):943    w = (3 ** k - 1) // 2944    dep = {}945    for z1 in range(3, w, 3):946        if gcd(z1, w) != 1:947            continue948        m = witness(z1, w - z1)949        if m:950            d = len(base3(m * w))951            dep[z1] = d952            dep[w - z1] = d953    return dep954955def cmd_tail(args):956    print("d(z) is the base-3 length of m(z) R_k for the minimal witness m(z) of an occupied direction of weight R_k, "957          "and the depth b(z) = ceil(d(z) / k) is the number of k-blocks that lift fills; "958          "U = |Union_T Occ_T| is depth at most 2, V = #{b(z) <= 3} the depth-3 census, Z = Z(R_k), tail = Z - U the deep tail; "959          "cap = V - U is the tail captured at depth 3 and share = cap / tail; "960          "fam = |U union the depth-3 lift families|, new1 and new2 what the all-1 and the all-2 family add beyond U; "961          "the depth histogram of the tail is printed under each row")962    print("k R_k U V Z tail cap share fam new1 new2 maxdepth secs")963    rows = []964    for k in range(2, args.kmax + 1):965        t = time.time()966        w = (3 ** k - 1) // 2967        occ, _, _, _ = lift_scan(k)968        dep = lift_lengths(k)969        deep = sorted(set(dep) - occ)970        hist = {}971        for z in deep:972            b = -(-dep[z] // k)973            hist[b] = hist.get(b, 0) + 1974        V = len(occ) + sum(1 for z in deep if dep[z] <= 3 * k)975        fam = new1 = new2 = "-"976        o1 = set()977        if k <= min(max(args.onemax, args.famax), 13):978            o1 = block3_scan(k, 1)979            new1 = len(o1 - occ)980        if k <= min(args.famax, 13):981            o2 = block3_scan(k, 2)982            new2 = len(o2 - occ)983            fam = len(occ | o1 | o2)984            if fam != V:985                raise ValueError("depth-3 family census " + str(fam) + " against the automaton " + str(V) + " at k = " + str(k))986        share = down((V - len(occ)) / len(deep), 4) if deep else "-"987        rows.append((k, len(deep), V - len(occ)))988        print(k, w, len(occ), V, len(dep), len(deep), V - len(occ), share, fam, new1, new2,989              max(hist) if hist else 0, round(time.time() - t, 1))990        if hist:991            print("  depth histogram of the tail at k =", k, sorted(hist.items()))992    live = [r for r in rows if r[1]]993    print("tail", [r[1] for r in live], "captured", [r[2] for r in live], "at k =", [r[0] for r in live])994    print("tail growth over two steps", [down(live[i + 2][1] / live[i][1], 4) for i in range(len(live) - 2)],995          "against 4 for 2^k, captured growth", [down(live[i + 2][2] / live[i][2], 4) for i in range(len(live) - 2) if live[i][2]])996997# CHECKS998999def cmd_check(args):1000    cap = 601001    for n in (6, 9, 12):1002        b = brute(n, cap)1003        miss = [z for z in b if level(*(z if z[0] % 3 == 0 else z[::-1]))[0] == 0]1004        levbad = [z for z in b1005                  if level(*(z if z[0] % 3 == 0 else z[::-1]))[0] != b[z]]1006        print("brute n", n, "rays", len(b), "not occupied by automaton", len(miss),1007              "level mismatch", len(levbad))1008    b = brute(12, cap)1009    extra = [(a, c) for a in range(1, cap + 1) for c in range(1, cap + 1)1010             if gcd(a, c) == 1 and (a % 3 == 0) != (c % 3 == 0)1011             and level(*((a, c) if a % 3 == 0 else (c, a)))[0]1012             and (a, c) not in b]1013    print("automaton occupied but absent from brute n=12", len(extra))1014    bad = sym = gap = core_bad = 01015    for z1, z2 in pairs(120):1016        d, size = level(z1, z2)1017        o, _ = carry_occupied(z1, z2)1018        if (d > 0) != o:1019            bad += 11020        if reach_size(z1, z2) > band_cap(z1, z2):1021            sym += 11022        if d and z1 + z2 <= 3 * z1 <= 2 * (z1 + z2):1023            gap += 11024        if d and not core(z1, z2)[0]:1025            core_bad += 11026    print("j vs pair-carry disagreements to 120", bad, "band violations", sym,1027          "occupied with z1/w inside [1/3,2/3]", gap,1028          "occupied not reaching the core", core_bad)1029    print("gasket rays with u/(u+v) inside [1/3,2/3] at n=12",1030          sum(1 for a, c in brute(12, 3 ** 12) if a + c <= 3 * a <= 2 * (a + c)))1031    b = brute(12, 60)1032    print("gasket rays symmetric under swap", all((c, a) in b for a, c in b),1033          "every ray has exactly one coordinate divisible by 3",1034          all((a % 3 == 0) != (c % 3 == 0) for a, c in b),1035          "so no direction with 3 dividing neither coordinate is occupied")1036    print("k w submask_floor Z(w) w^(log2/log3) max_lev floor_beats_w^0.6309")1037    for k in range(2, 9):1038        wt = (3 ** k - 1) // 21039        sub = [a for a in binaries(k) if 0 < a < wt and gcd(a, wt) == 1]1040        got = [a for a in range(1, wt) if gcd(a, wt) == 11041               and (a % 3 == 0) != ((wt - a) % 3 == 0)1042               and level(*((a, wt - a) if a % 3 == 0 else (wt - a, a)))[0]]1043        lv = max(level(*((a, wt - a) if a % 3 == 0 else (wt - a, a)))[0] for a in got)1044        print(k, wt, len(sub), len(got), round(wt ** (log(2) / log(3)), 1), lv,1045              len(sub) > wt ** (log(2) / log(3)))1046    R = {}1047    for k in range(2, 7):1048        s = set()1049        for lab in range(3 ** k):1050            u = v = 01051            m = lab1052            for i in range(k):1053                d = m % 31054                m //= 31055                if d == 1:1056                    u += 3 ** i1057                elif d == 2:1058                    v += 3 ** i1059            if u and v % 3:1060                s.add(u * pow(v, -1, 3 ** k) % 3 ** k)1061        R[k] = s1062    Z = [0] * 7291063    recov = miss = 01064    for z1 in range(3, 729, 3):1065        for z2 in range(1, 729):1066            if z2 % 3 == 0 or gcd(z1, z2) != 1 or z1 + z2 > 728:1067                continue1068            if not level(z1, z2)[0]:1069                continue1070            w = z1 + z21071            Z[w] += 21072            k = 11073            while 3 ** k <= w:1074                k += 11075            r = z1 * pow(z2, -1, 3 ** k) % 3 ** k1076            if r * w * pow(1 + r, -1, 3 ** k) % 3 ** k != z1:1077                recov += 11078            if 2 <= k <= 6 and r not in R[k]:1079                miss += 11080    over = 01081    for w in range(4, 729):1082        k = 11083        while 3 ** k <= w:1084            k += 11085        if Z[w] > 2 * len(R[k]):1086            over += 11087    print("|R_k| for k = 2..6", [len(R[k]) for k in range(2, 7)],1088          "recovery failures", recov, "residues outside R_k", miss,1089          "weights breaking Z(w) <= 2|R_k|", over)1090    for k in (7, 8):1091        occ, total, model, best = lift_scan(k)1092        direct = set()1093        dtot = 01094        for T in range(0, 1 << k, 2):1095            m, o = occ_of(k, T)1096            direct |= o1097            dtot += len(o)1098        print("lift meet-in-the-middle against direct submask enumeration at k", k,1099              "union", len(occ), len(direct), "sum", total, dtot, "agree", occ == direct and total == dtot)1100    for t in range(2, 7):1101        k = 2 * t + 11102        m, low = cyclotomic_occ(t)1103        mt, full = occ_of(k, ((1 << t) - 1) << t)1104        print("cyclotomic lift t", t, "k", k, "m_T", m, "= 3^(2t)-3^t+1", m == mt,1105              "divides 3^(3t)+1", (3 ** (3 * t) + 1) % m == 0, "cofactor 3^t+1 coprime to R_k",1106              gcd(3 ** t + 1, (3 ** k - 1) // 2) == 1, "predicted", len(low), "actual", len(full),1107              "equal", low == full)1108    for k in (7, 8):1109        ms, ns, tops = raw_scan(k)1110        bad = 01111        for j, T in enumerate(range(0, 1 << k, 2)):1112            m, v = residue_dist(k, T)1113            F = fourier_direct(k, T, m)1114            if m != ms[j] or int(v[0]) != ns[j] or abs(F.sum() / m - ns[j]) > 1e-6:1115                bad += 11116        fam = all(raw_count((p - 1) * t + s, phi2p_T(p, t)[1])[1] == 2 ** ((p - 1) * (t - s) // 2 + s)1117                  for p, t in ((3, 2), (3, 3), (5, 2), (7, 1), (9, 1)) for s in range(t) if (p - 1) * t + s <= 8)1118        print("Fourier identity N_T = (1/m_T) Sum_u Prod_p (1 + e(u 3^p / m_T)) against the residue DP and the meet-in-the-middle count at k",1119              k, "over", len(ms), "sets T, failures", bad, "antipodal family N_T = 2^((p-1)(t-s)/2 + s) at k <= 8", fam)1120    rigid = sum(1 for k in range(2, 6) for c in column_vectors(k, 3) if len(set(c.tolist())) > 1)1121    branch = [c.tolist() for c in column_vectors(3, 4) if len(set(c.tolist())) > 1]1122    cen = []1123    for k in (7, 8, 9):1124        fam = len(lift_scan(k)[0] | block3_scan(k, 1) | block3_scan(k, 2))1125        Z = len(lift_lengths(k))1126        assert fam == Z, (k, fam, Z)1127        cen.append((k, fam))1128    print("binary multiples of R_k below 3^(3k) carry a constant column vector at k = 2..5, exceptions", rigid,1129          "and at four blocks the rigidity breaks, non-constant vectors at k = 3", len(branch), "first", branch[0],1130          "; the depth-3 family census equals Z(R_k) at", cen)1131    worst = max(sum(1 / (1 + 2 * sum(3 ** i for i in range(k) if T >> i & 1))1132                    for T in range(0, 1 << k, 2)) for k in range(2, 13))1133    print("Sum_T 1/m_T over T inside [1, k-1] at k = 2..12 stays below 3/2, largest", down(worst, 5))1134    print("regression A(inf, 3^5) >= 474 and A(9, 3^7) = 2818 are checked by levels")11351136def main():1137    p = argparse.ArgumentParser()1138    s = p.add_subparsers(dest="cmd", required=True)1139    a = s.add_parser("sweep")1140    a.add_argument("cap", type=int)1141    a.set_defaults(fn=cmd_sweep)1142    b = s.add_parser("levels")1143    b.add_argument("cap", type=int)1144    b.add_argument("--lo", type=int, default=2)1145    b.add_argument("--hi", type=int, default=24)1146    b.set_defaults(fn=cmd_levels)1147    c = s.add_parser("multiples")1148    c.add_argument("n", type=int)1149    c.add_argument("--qmax", type=int, default=500)1150    c.add_argument("--hmax", type=int, default=8)1151    c.set_defaults(fn=cmd_multiples)1152    e = s.add_parser("band")1153    e.add_argument("cap", type=int)1154    e.set_defaults(fn=cmd_band)1155    f = s.add_parser("layers")1156    f.add_argument("cap", type=int)1157    f.set_defaults(fn=cmd_layers)1158    g = s.add_parser("weights")1159    g.add_argument("w", type=int, nargs="+")1160    g.set_defaults(fn=cmd_weights)1161    h = s.add_parser("core")1162    h.add_argument("w", type=int, nargs="+")1163    h.set_defaults(fn=cmd_core)1164    i = s.add_parser("box")1165    i.add_argument("lo", type=int)1166    i.add_argument("hi", type=int)1167    i.add_argument("--extra", type=int, default=3)1168    i.set_defaults(fn=cmd_box)1169    j = s.add_parser("repunit")1170    j.add_argument("--kmax", type=int, default=13)1171    j.add_argument("--liftmax", type=int, default=15)1172    j.set_defaults(fn=cmd_repunit)1173    l = s.add_parser("lifts")1174    l.add_argument("--kmax", type=int, default=15)1175    l.add_argument("--zmax", type=int, default=13)1176    l.add_argument("--lo", type=int, default=11)1177    l.set_defaults(fn=cmd_lifts)1178    q = s.add_parser("tail")1179    q.add_argument("--kmax", type=int, default=13)1180    q.add_argument("--famax", type=int, default=12)1181    q.add_argument("--onemax", type=int, default=13)1182    q.set_defaults(fn=cmd_tail)1183    n = s.add_parser("agg")1184    n.add_argument("--kmax", type=int, default=19)1185    n.add_argument("--umax", type=int, default=11)1186    n.add_argument("--ulo", type=int, default=5)1187    n.add_argument("--directmax", type=int, default=9)1188    n.add_argument("--cycmax", type=int, default=5)1189    n.add_argument("--lo", type=int, default=11)1190    n.set_defaults(fn=cmd_agg)1191    d = s.add_parser("check")1192    d.set_defaults(fn=cmd_check)1193    args = p.parse_args()1194    args.fn(args)11951196main()