level_tiles.py

23.1 kB · python · 637 lines

1import itertools2import math3import subprocess4import sys5import time6from collections import defaultdict7from functools import lru_cache89# ARITHMETIC1011@lru_cache(maxsize=None)12def factor(n):13    out, p = {}, 214    while p * p <= n:15        while n % p == 0:16            out[p] = out.get(p, 0) + 117            n //= p18        p += 119    if n > 1:20        out[n] = out.get(n, 0) + 121    return tuple(sorted(out.items()))2223def phi(n):24    r = n25    for p, _ in factor(n):26        r = r // p * (p - 1)27    return r2829@lru_cache(maxsize=None)30def divisors(n):31    ds = [1]32    for p, e in factor(n):33        ds = [d * p ** i for d in ds for i in range(e + 1)]34    return tuple(sorted(ds))3536def prime_power(n):37    f = factor(n)38    return f[0] if len(f) == 1 else None3940def polydiv(num, den):41    num, q = list(num), [0] * max(len(num) - len(den) + 1, 1)42    for i in range(len(num) - len(den), -1, -1):43        c = num[i + len(den) - 1] // den[-1]44        q[i] = c45        if c:46            for j, d in enumerate(den):47                num[i + j] -= c * d48    rem = num[: len(den) - 1]49    while rem and rem[-1] == 0:50        rem.pop()51    return q, rem5253@lru_cache(maxsize=None)54def cyclo(m):55    poly = [-1] + [0] * (m - 1) + [1]56    for d in divisors(m)[:-1]:57        poly, _ = polydiv(poly, cyclo(d))58    return tuple(poly)5960def mask(A):61    out = [0] * (max(A) + 1)62    for a in A:63        out[a] += 164    return out6566def valuation(poly, m):67    v, c = 0, cyclo(m)68    while True:69        q, r = polydiv(poly, c)70        if r:71            return v72        v, poly = v + 1, q7374# ZERO SETS7576def zero_set(F):77    F = [f - min(F) for f in F]78    deg, poly, out = max(F), mask(F), {}79    for m in range(2, 2 * deg * deg + 3):80        if phi(m) <= deg:81            red = [0] * m82            for f in F:83                red[f % m] += 184            if not polydiv(red + [0], cyclo(m))[1]:85                out[m] = valuation(poly, m)86    return out8788def level_zero_set(Z, b, n):89    out = defaultdict(int)90    for i in range(n):91        B = b ** i92        for u, mu in Z.items():93            for g in divisors(B):94                if math.gcd(u, B // g) == 1:95                    out[u * g] += mu96    return dict(out)9798def in_level(M, Z, b, n):99    return any(M // math.gcd(M, b ** i) in Z for i in range(n))100101def level_set(F, b, n):102    A = [0]103    for i in range(n):104        A = [a + f * b ** i for a in A for f in F]105    return A106107def exps(Z):108    out = defaultdict(set)109    for m in Z:110        pp = prime_power(m)111        if pp:112            out[pp[0]].add(pp[1])113    return out114115def level_exps(Z, b, n):116    out = defaultdict(set)117    fb = dict(factor(b))118    for p, cs in exps(Z).items():119        e = fb.get(p, 0)120        for c in cs:121            for i in range(n if e else 1):122                out[p].add(c + e * i)123    return out124125# COVEN-MEYEROWITZ126127def t1(k, E):128    return k == math.prod(p ** len(cs) for p, cs in E.items())129130def t2_fail(E, member):131    ps = sorted(E)132    for r in range(2, len(ps) + 1):133        for sub in itertools.combinations(ps, r):134            for choice in itertools.product(*[sorted(E[p]) for p in sub]):135                M = math.prod(p ** a for p, a in zip(sub, choice))136                if not member(M):137                    return M138    return None139140def omega(n):141    return len(factor(n))142143def verdict(F, b, n, Z=None, size=None):144    Z = zero_set(F) if Z is None else Z145    k = (size or len(F)) ** n146    E = level_exps(Z, b, n)147    if not t1(k, E):148        return "no", "T1", E149    bad = t2_fail(E, lambda M: in_level(M, Z, b, n))150    if bad is None:151        return "tile", "", E152    return ("no" if omega(k) <= 2 else "open"), "T2 at %d" % bad, E153154def depth(F, b, nmax, Z=None, size=None):155    Z = zero_set(F) if Z is None else Z156    for n in range(1, nmax + 1):157        v, why, _ = verdict(F, b, n, Z, size)158        if v != "tile":159            return n - 1, v, why160    return nmax, "tile", ""161162def condition_p(F, b, Z):163    fb = dict(factor(b))164    E = exps(Z)165    if not t1(len(F), E):166        return False167    for p, cs in E.items():168        e = fb.get(p, 0)169        if e == 0 or len({c % e for c in cs}) < len(cs):170            return False171    return True172173def horizon(Z, b):174    fb = dict(factor(b))175    E = exps(Z)176    C = defaultdict(int)177    for u in Z:178        for p, a in factor(u):179            C[p] = max(C[p], a)180    G = max((-(-C[p] // fb[p]) for p in E), default=0)181    return 1 + G * (2 * len(E) - 1)182183def every_level(Z, size, b):184    E = exps(Z)185    fb = dict(factor(b))186    if not t1(size, E):187        return False, 0188    for p, cs in E.items():189        e = fb.get(p, 0)190        if e == 0 or len({c % e for c in cs}) < len(cs):191            return False, 0192    n0 = horizon(Z, b)193    for n in range(1, n0 + 1):194        if t2_fail(level_exps(Z, b, n), lambda M: in_level(M, Z, b, n)) is not None:195            return False, n0196    return True, n0197198def t1_depth(F, b, Z):199    fb = dict(factor(b))200    E = exps(Z)201    if not t1(len(F), E):202        return 0203    best = math.inf204    for p, cs in E.items():205        e = fb.get(p, 0)206        if e == 0:207            return 1208        for c, d in itertools.combinations(sorted(cs), 2):209            if (d - c) % e == 0:210                best = min(best, (d - c) // e)211    return best212213def tiles_residues(F, b):214    if b % len(F):215        return False216    return complement(F, b) is not None217218def normalize(F):219    m = min(F)220    g = math.gcd(*[f - m for f in F])221    return tuple(sorted((f - m) // g for f in F))222223# CERTIFICATES224225def cm_complement(A, E):226    L = math.prod(p ** max(cs) for p, cs in E.items())227    B = [1]228    for p, cs in E.items():229        t = L // p ** max(cs)230        for a in range(1, max(cs) + 1):231            if a not in cs:232                c = cyclo(p ** a)233                f = [0] * ((len(c) - 1) * t + 1)234                for j, x in enumerate(c):235                    f[j * t] = x236                B = polymul(B, f)237    return L, B238239def polymul(a, b):240    out = [0] * (len(a) + len(b) - 1)241    for i, x in enumerate(a):242        if x:243            for j, y in enumerate(b):244                if y:245                    out[i + j] += x * y246    return out247248def certify(A, E):249    L, B = cm_complement(A, E)250    if any(c not in (0, 1) for c in B):251        return L, None252    Bs = [i for i, c in enumerate(B) if c]253    seen = set((a + x) % L for a in A for x in Bs)254    return L, (Bs if len(seen) == L == len(A) * len(Bs) else None)255256def complement(A, N, cap=2_000_000):257    R = sorted(set(a % N for a in A))258    if len(R) < len(A) or N % len(A):259        return None260    full = (1 << N) - 1261    base = sum(1 << r for r in R)262    rot = lambda s: ((base << s) | (base >> (N - s))) & full263    steps = [0]264    def rec(cov, B):265        if cov == full:266            return B267        steps[0] += 1268        if steps[0] > cap:269            raise TimeoutError270        r = ((cov + 1) & ~cov).bit_length() - 1271        for a in R:272            s = (r - a) % N273            m = rot(s)274            if not cov & m:275                got = rec(cov | m, B + [s])276                if got is not None:277                    return got278        return None279    return rec(0, [])280281# SPECTRA282283def spectrum(A, Zl, cap=3_000_000):284    L = math.lcm(*Zl)285    D = [d for d in range(1, L) if L // math.gcd(d, L) in Zl]286    idx = {d: i for i, d in enumerate(D)}287    nb = []288    for d in D:289        m = 0290        for e in D:291            if e != d and ((e - d) % L) in idx:292                m |= 1 << idx[e]293        nb.append(m)294    target = len(A) - 1295    steps = [0]296    def bk(size, cand, chosen):297        if size == target:298            return chosen299        steps[0] += 1300        if steps[0] > cap:301            raise TimeoutError302        while cand:303            if size + bin(cand).count("1") < target:304                return None305            v = cand.bit_length() - 1306            got = bk(size + 1, cand & nb[v], chosen + [D[v]])307            if got is not None:308                return got309            cand &= ~(1 << v)310        return None311    if target == 0:312        return L, [0]313    for m in sorted(Zl):314        v = idx[L // m]315        got = bk(1, nb[v], [L // m])316        if got is not None:317            return L, [0] + got318    return L, None319320def check_spectrum(A, L, S):321    for x, y in itertools.combinations(S, 2):322        z = sum(complex(math.cos(2 * math.pi * a * (x - y) / L), math.sin(2 * math.pi * a * (x - y) / L)) for a in A)323        if abs(z) > 1e-6:324            return False325    return True326327# PARI328329def gp(lines):330    out = subprocess.run(["gp", "-q", "-D", "parisizemax=2G"], input="\n".join(lines) + "\n", capture_output=True, text=True).stdout331    return out.strip().splitlines()332333def pari_cyclo(polys):334    cmd = []335    for P in polys:336        s = "+".join("x^%d" % a for a in P)337        cmd.append("{f=factor(%s);print(vector(#f~,j,[poliscyclo(f[j,1]),f[j,2]]))}" % s)338    out = []339    for line in gp(cmd):340        pairs = eval(line.replace(";", ","))341        out.append({m: e for m, e in pairs if m})342    return out343344def pari_unimodular(polys):345    cmd = ["default(realprecision,60);"]346    for P in polys:347        s = "+".join("x^%d" % a for a in P)348        cmd.append("{f=factor(%s);g=1;for(j=1,#f~,if(!poliscyclo(f[j,1]),g*=f[j,1]^f[j,2]));h=gcd(g,polrecip(g));c=0;if(poldegree(h)>0,r=polroots(h);for(j=1,#r,if(abs(abs(r[j])-1)<1e-30,c++)));print(c)}" % s)349    return [int(x) for x in gp(cmd)]350351# VERBS352353def digit_sets(b, sizes=None):354    for r in range(1, b):355        if sizes and r + 1 not in sizes:356            continue357        for rest in itertools.combinations(range(1, b), r):358            yield (0,) + rest359360def mirror_rep(F):361    G = tuple(sorted(max(F) - f for f in F))362    return min(F, G)363364def ppfactor(F, p, a):365    q, cnt = p ** (a - 1), defaultdict(int)366    for f in F:367        cnt[f % (p * q)] += 1368    return all(cnt[r] == cnt[r + j * q] for r in range(q) for j in range(1, p))369370def hand():371    F, b = (0, 2), 3372    A = level_set(F, b, 2)373    Z = zero_set(F)374    v, why, E = verdict(F, b, 2, Z)375    print("level 2 of {0,2} at base 3:", sorted(A))376    print("PARI cyclotomic factors of the mask:", pari_cyclo([A])[0])377    print("index lemma:", level_zero_set(Z, b, 2))378    print("T1: |A_2| = %d against prod over S of Phi_s(1) = %d, S = %s" % (len(A), math.prod(p ** len(c) for p, c in E.items()), {p: sorted(c) for p, c in E.items()}))379    print("verdict:", v, why)380    found = [N for N in range(4, 129, 4) if complement(A, N) is not None]381    print("complements in Z/N for 4 | N <= 128:", found or "none")382383def index():384    polys, preds, t0 = [], [], time.time()385    for b in range(2, 9):386        for F in digit_sets(b):387            Z = zero_set(F)388            for n in range(1, (4 if b <= 4 else 3) + 1):389                A = level_set(F, b, n)390                polys.append(A)391                preds.append(level_zero_set(Z, b, n))392    got = pari_cyclo(polys)393    bad = sum(1 for g, p in zip(got, preds) if g != p)394    print("masks factored by PARI: %d (bases 2..8, every digit set with 0, levels 1..3, level 4 at bases <= 4)" % len(polys))395    print("cyclotomic factorisation equal to the index lemma, with multiplicity: %d of %d" % (len(polys) - bad, len(polys)))396    print("time %.1f s" % (time.time() - t0))397398def census(bmax=16, nmax=8):399    t0 = time.time()400    print("base   sets  tileZb  every  T1every  raw  norm  hor  depth histogram (inf = every level)")401    cxs, tot = [], defaultdict(int)402    for b in range(2, bmax + 1):403        row, hist = defaultdict(int), defaultdict(int)404        for F in digit_sets(b):405            Z = zero_set(F)406            d, v, why = depth(F, b, nmax, Z)407            every, n0 = every_level(Z, len(F), b)408            P = condition_p(F, b, Z)409            T1d = t1_depth(F, b, Z)410            tz = tiles_residues(F, b)411            row["sets"] += 1412            row["tz"] += tz413            row["every"] += every414            row["P"] += P415            hist["inf" if every else (d if d < nmax else ">=%d" % nmax)] += 1416            assert P == (T1d == math.inf)417            T1read = next((n - 1 for n in range(1, 17) if not t1(len(F) ** n, level_exps(Z, b, n))), 16)418            assert T1read == min(T1d, 16)419            T2read = next((n - 1 for n in range(1, 17) if t2_fail(level_exps(Z, b, n), lambda M: in_level(M, Z, b, n)) is not None), 16)420            assert d <= T1d421            if omega(len(F)) <= 2:422                assert d == min(T1read, T2read, nmax)423                row["t1law"] += 1424                row["t2cut"] += d < min(T1d, nmax)425            assert (not every) or (P and d == nmax)426            assert (not tz) or every427            if P:428                far = all(t2_fail(level_exps(Z, b, n), lambda M: in_level(M, Z, b, n)) is None for n in range(1, n0 + 7))429                assert far == every430                row["horizon"] = max(row["horizon"], n0)431            if prime_power(len(F)):432                assert d == min(T1d, nmax) and every == P433            if prime_power(b):434                assert every == tz435            if every:436                assert b % len(F) == 0437                if not prime_power(len(F)) and b % math.prod(p ** max(c) for p, c in exps(Z).items()):438                    row["wide"] += 1439            if every and not tz:440                row["raw"] += 1441                if not tiles_residues(normalize(F), b):442                    row["norm"] += 1443                    cxs.append((b, F))444        for key in row:445            tot[key] += row[key]446        h = " ".join("%s:%d" % (k, hist[k]) for k in sorted(hist, key=lambda x: (isinstance(x, str), str(x).rjust(4))))447        print("%4d %6d %7d %6d %8d %4d %5d %4d  %s" % (b, row["sets"], row["tz"], row["every"], row["P"], row["raw"], row["norm"], row["horizon"], h))448    print("bases 2..%d, every digit set with 0 and at least two digits: %d sets; tile Z/b %d; T1 and T2 at every level %d; T1 at every level %d" % (bmax, tot["sets"], tot["tz"], tot["every"], tot["P"]))449    print("T1 depth formula equal to the T1 depth read level by level to level 16 on all %d sets; tiling depth equal to the least of T1 depth, T2 depth and %d on the %d sets of size with at most two primes, T2 cutting below the T1 depth on %d" % (tot["sets"], nmax, tot["t1law"], tot["t2cut"]))450    print("every level tiles but F does not tile Z/b: %d; and F/gcd does not either: %d; every-level sets with a composite non-prime-power size and lcm(S_F) not dividing b: %d" % (tot["raw"], tot["norm"], tot["wide"]))451    reps = sorted(set((b, mirror_rep(F)) for b, F in cxs))452    print("normalized counterexamples up to mirror (base, digits, zero set of hat F at roots of unity):")453    for b, F in reps:454        print("  %d %s %s" % (b, F, sorted(zero_set(F))))455    print("time %.1f s" % (time.time() - t0))456457def search(bmax=8, nmax=2, Nmax=512, cbmax=12, cnmax=3):458    t0 = time.time()459    agree, total, timeouts = 0, 0, 0460    for b in range(2, bmax + 1):461        for F in digit_sets(b):462            Z = zero_set(F)463            for n in range(1, nmax + 1):464                A = level_set(F, b, n)465                v, why, E = verdict(F, b, n, Z)466                found = None467                try:468                    for N in range(len(A), Nmax + 1, len(A)):469                        if complement(A, N) is not None:470                            found = N471                            break472                except TimeoutError:473                    timeouts += 1474                    continue475                total += 1476                ok = (found is not None) == (v == "tile")477                agree += ok478                if not ok:479                    print("  disagreement", b, F, n, v, why, found)480    print("levels at bases <= %d, levels <= %d, checked by exhaustive complement search in Z/N for |A| | N <= %d: %d; agreeing with the T1 T2 verdict: %d; search capped: %d" % (bmax, nmax, Nmax, total, agree, timeouts))481    certs, tiles = 0, 0482    for b in range(2, cbmax + 1):483        for F in digit_sets(b):484            Z = zero_set(F)485            for n in range(1, cnmax + 1):486                v, why, E = verdict(F, b, n, Z)487                if v == "tile":488                    tiles += 1489                    L, B = certify(level_set(F, b, n), E)490                    certs += B is not None491    print("tile verdicts at bases <= %d, levels <= %d: %d; certified by the Coven-Meyerowitz complement with A + B checked to be all residues mod lcm(S): %d" % (cbmax, cnmax, tiles, certs))492    print("time %.1f s" % (time.time() - t0))493494def witness():495    for F, b, n in (((0, 2), 6, 3), ((0, 1, 8, 9), 12, 3), ((0, 1, 2, 6, 7, 8), 18, 2), ((0, 1, 4, 5), 6, 3), ((0, 1, 8, 9), 10, 3)):496        Z = zero_set(F)497        print("digits %s base %d: zero set of hat F at roots of unity %s, normalized %s tiles Z/%d: %s" % (F, b, sorted(Z), normalize(F), b, tiles_residues(normalize(F), b)))498        Ls = [level_set(F, b, m) for m in range(1, n + 1)]499        facs = pari_cyclo(Ls)500        for m, (A, fac) in enumerate(zip(Ls, facs), 1):501            v, why, E = verdict(F, b, m, Z)502            line = "  level %d: |A| = %d, PARI cyclotomic part %s, S = %s, verdict %s %s" % (m, len(A), dict(sorted(fac.items())), {p: sorted(c) for p, c in sorted(E.items())}, v, why)503            if v == "tile":504                L, B = certify(A, E)505                line += ", complement %s mod %d" % (B if len(B) <= 8 else "of size %d" % len(B), L)506            print(line)507    for F in ((0, 1, 8, 9), (0, 3, 8, 11)):508        hits = [v for v in range(1, 13) if complement([v * f for f in F], 12) is not None]509        z2 = sorted({a for v in range(1, 13) for a in exps(zero_set(tuple(v * f for f in F)))[2]})510        print("multiples v %s with v = 1..12 (all classes mod 12) tiling Z/12: %s; exponents of 2 in S_vF over these v: %s" % (F, hits or "none", z2))511    for F, b in (((0, 1, 4, 5), 6), ((0, 1, 8, 9), 10), ((0, 1, 256, 257), 18)):512        Z = zero_set(F)513        print("T1 depth of %s at base %d: %s; tiling depth read to level 10: %d" % (F, b, t1_depth(F, b, Z), depth(F, b, 10, Z)[0]))514    print("digit sets that satisfy T1 at every level, by base and size with two primes:")515    t2search()516517def t2search(bmax=30, cap=200_000):518    t0 = time.time()519    for b in range(2, bmax + 1):520        for k in divisors(b):521            if omega(k) < 2 or k == b:522                continue523            if math.comb(b - 1, k - 1) > cap:524                print("base %d, |F| = %d: %d digit sets, skipped" % (b, k, math.comb(b - 1, k - 1)))525                continue526            fb = dict(factor(b))527            cnt, passp, own, later = 0, 0, 0, []528            for F in digit_sets(b, {k}):529                cnt += 1530                E = defaultdict(set)531                for p in fb:532                    a = 1533                    while phi(p ** a) <= b - 1:534                        if ppfactor(F, p, a):535                            E[p].add(a)536                        a += 1537                if not t1(k, E) or any(len({c % fb[p] for c in cs}) < len(cs) for p, cs in E.items()):538                    continue539                Z = zero_set(F)540                if not condition_p(F, b, Z):541                    continue542                passp += 1543                d, v, why = depth(F, b, 6, Z)544                every, n0 = every_level(Z, k, b)545                assert every == (d == 6)546                if d == 0:547                    own += 1548                elif d < 6:549                    later.append((d, mirror_rep(F), why))550            reps = sorted(set(later))551            print("base %d, |F| = %d: %d digit sets, %d with T1 at every level, %d fail T2 already at level 1, %d tile and fail T2 at a level <= 6 (%d up to mirror)%s" % (b, k, cnt, passp, own, len(later), len(reps), (": " + ", ".join("%s at level %d (%s)" % (F, d + 1, w) for d, F, w in reps[:6])) if reps else ""))552    print("time %.1f s" % (time.time() - t0))553554def spectral(bmax=12, nmax=3, amax=36):555    t0 = time.time()556    sets = [(b, F) for b in range(2, bmax + 1) for F in digit_sets(b)]557    uni = pari_unimodular([list(F) for b, F in sets])558    print("digit sets with a root on the unit circle that is not a root of unity: %d of %d" % (sum(1 for u in uni if u), len(sets)))559    tally, caps, odd = defaultdict(int), 0, []560    for (b, F), u in zip(sets, uni):561        Z = zero_set(F)562        for n in range(1, nmax + 1):563            if len(F) ** n > amax:564                continue565            A = level_set(F, b, n)566            v, why, E = verdict(F, b, n, Z)567            Zl = level_zero_set(Z, b, n)568            S = None569            if Zl:570                try:571                    L, S = spectrum(A, set(Zl))572                except TimeoutError:573                    caps += 1574                    continue575            if S is not None:576                assert check_spectrum(A, L, S)577                w = "spectral"578            else:579                w = "undecided" if u else "not spectral"580            tally[(v, w)] += 1581            if (v == "tile") != (w == "spectral") and w != "undecided":582                odd.append((b, F, n, v, w))583    for key in sorted(tally):584        print("  tiling verdict %-5s  %-13s %d" % (key[0], key[1], tally[key]))585    for x in odd[:10]:586        print("  split:", x)587    print("levels with |A| <= %d at bases <= %d, levels <= %d: %d; tile and spectral disagree on %d; search capped: %d; time %.1f s" % (amax, bmax, nmax, sum(tally.values()), len(odd), caps, time.time() - t0))588589def lwz(N, m, L, p):590    if p % N or p % L:591        return False592    d = max(i for i in range(0, 200) if math.gcd(m * L // math.gcd(m * L, p ** i), L) != 1)593    return (m // math.gcd(m, p ** d)) % N == 0594595def product_zero_set(N, m, L):596    Z = defaultdict(int)597    for u in divisors(N)[1:]:598        Z[u] += 1599    for v in divisors(L)[1:]:600        for g in divisors(m):601            if math.gcd(v, m // g) == 1:602                Z[v * g] += 1603    return dict(Z)604605def measure(pmax=24, cmax=12):606    t0 = time.time()607    agree, total, opens, bad = 0, 0, defaultdict(int), []608    for p in range(2, pmax + 1):609        for N in range(2, cmax + 1):610            for L in range(2, cmax + 1):611                for m in range(N, p * p + 1):612                    Z = product_zero_set(N, m, L)613                    ours, n0 = every_level(Z, N * L, p)614                    if not ours and omega(N * L) > 2 and condition_p(range(N * L), p, Z):615                        opens[lwz(N, m, L, p)] += 1616                        continue617                    theirs = lwz(N, m, L, p)618                    total += 1619                    agree += ours == theirs620                    if ours != theirs and len(bad) < 10:621                        bad.append((N, m, L, p, ours, theirs))622    print("product-form digit sets D_N + m D_L at base p <= %d, 2 <= N, L <= %d, N <= m <= p^2: %d" % (pmax, cmax, total))623    print("T1 and T2 at every level  ==  the spectral condition of Liu-Wang-Zheng Theorem 1.3: %d of %d" % (agree, total))624    print("skipped, T1 at every level and T2 failing with three primes in N L: %d, of which the spectral condition holds on %d" % (sum(opens.values()), opens[True]))625    for x in bad:626        print("  disagreement N=%d m=%d L=%d p=%d ours=%s lwz=%s" % x)627    cons = [(N, p) for p in range(2, 65) for N in range(2, p + 1)]628    ok = sum(1 for N, p in cons if every_level(zero_set(tuple(range(N))), N, p)[0] == (p % N == 0))629    print("consecutive digits {0..N-1} at base p <= 64: T1 and T2 at every level iff N | p on %d of %d" % (ok, len(cons)))630    print("time %.1f s" % (time.time() - t0))631632VERBS = {"hand": hand, "index": index, "census": census, "search": search, "witness": witness, "spectral": spectral, "measure": measure}633634if __name__ == "__main__":635    for v in sys.argv[1:] or list(VERBS):636        print("== %s" % v)637        VERBS[v]()