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