verify.py

10.5 kB · python · 332 lines

1# WALSH23from fractions import Fraction4from itertools import permutations56KRAW = [[1, 1, 1, 1], [3, 1, -1, -3], [3, -1, -1, 3], [1, -1, 1, -1]]78QUASI = {9    (0, 1): lambda n: (9 * n * n + 18 * n + 21) // 8,10    (0, 3): lambda n: (3 * n * n + 6 * n + 3) // 8,11    (1, 1): lambda n: (3 * n * n + 2 * n - 5) // 8,12    (1, 3): lambda n: (9 * n * n + 6 * n - 3) // 8,13    (2, 1): lambda n: (9 * n * n - 6 * n - 3) // 8,14    (2, 3): lambda n: (3 * n * n - 2 * n - 5) // 8,15    (3, 1): lambda n: (3 * n * n - 6 * n + 3) // 8,16    (3, 3): lambda n: (9 * n * n - 18 * n + 21) // 8,17}181920def corners():21    return [(a, b, c) for a in (0, 1) for b in (0, 1) for c in (0, 1)]222324def bit(p):25    return 4 * p[0] + 2 * p[1] + p[2]262728def design(code):29    return frozenset(p for p in corners() if code >> bit(p) & 1)303132def quasi(k, n):33    return QUASI[(k, n % 4)](n)343536def brute_counts(n):37    got = {e: 0 for e in corners()}38    for x in range(4 * n):39        for y in range(4 * n):40            z = 6 * n - 2 - x - y41            if z < 0 or z >= 4 * n or z % 2:42                continue43            got[((x // 4) % 2, (y // 4) % 2, (z // 4) % 2)] += 144    return got454647def macro_counts(n):48    got = {e: 0 for e in corners()}49    top = 6 * n - 250    for x in range(4 * n):51        lo = max(0, top - x - (4 * n - 1))52        hi = min(4 * n - 1, top - x)53        if lo > hi:54            continue55        ex = (x // 4) % 256        for c in range(8):57            if (c - x) % 2:58                continue59            first = lo + (c - lo) % 860            if first > hi:61                continue62            cnt = (hi - first) // 8 + 163            z = top - x - first64            got[(ex, (first // 4) % 2, (z // 4) % 2)] += cnt65    return got666768def solid_cut(n):69    top = 6 * n - 270    out = set()71    for x in range(4 * n):72        for y in range(4 * n):73            for z in range(0, 4 * n, 2):74                if x + y + z == top:75                    out.add((x, y, z))76    return out777879def plane_points(n):80    top = 6 * n - 281    out = set()82    for x in range(4 * n):83        for y in range(4 * n):84            z = top - x - y85            if 0 <= z < 4 * n and z % 2 == 0:86                out.add((x, y, z))87    return out888990def polymul(a, b, cap):91    r = [0] * (cap + 1)92    for i, ai in enumerate(a):93        if ai == 0 or i > cap:94            continue95        for j, bj in enumerate(b):96            if i + j > cap:97                break98            r[i + j] += ai * bj99    return r100101102def gf_extract(k, n):103    h = (n - 1) // 2104    cap = 3 * h + 1105    even = [0] * (cap + 1)106    for i in range(0, 2 * h + 1, 2):107        if i <= cap:108            even[i] = 1109    odd = [0] * (cap + 1)110    for i in range(1, 2 * h, 2):111        if i <= cap:112            odd[i] = 1113    p = [0] * (cap + 1)114    p[0] = 1115    if cap >= 1:116        p[1] = 6117    if cap >= 2:118        p[2] = 1119    for _ in range(3 - k):120        p = polymul(p, even, cap)121    for _ in range(k):122        p = polymul(p, odd, cap)123    return p[cap]124125126def box(m):127    if m >= 0 and m % 2 == 0:128        return (m + 2) * (m + 4) // 8129    return 0130131132def split_sum(k, n):133    h = (n - 1) // 2134    total = 0135    for a in (0, 1, 2):136        if (a - h - k - 1) % 2:137            continue138        rho = 6 if a == 1 else 1139        total += rho * (box(3 * h + 1 - k - a)140                        - (3 - k) * box(h - k - a - 1)141                        - k * box(h + 1 - k - a))142    return total143144145def spectrum(D):146    out = []147    for j in range(4):148        acc = 0149        for S in corners():150            if sum(S) != j:151                continue152            acc += sum((-1) ** sum(p[i] * S[i] for i in range(3)) for p in D)153        out.append(Fraction(acc, 8))154    return out155156157def spectrum_kraw(D):158    m = [0, 0, 0, 0]159    for p in D:160        m[sum(p)] += 1161    return [Fraction(sum(m[w] * KRAW[j][w] for w in range(4)), 8) for j in range(4)]162163164def ink_law(D, n):165    S = spectrum_kraw(D)166    s = -1 if n % 4 == 1 else 1167    return (S[0] - Fraction(1, 2) * S[3] * s168            + (Fraction(2, 3) * S[1] - Fraction(1, 3) * S[2] * s) / n169            + (Fraction(2, 3) * S[2]170               - (Fraction(1, 3) * S[1] + Fraction(1, 2) * S[3]) * s) / n ** 2)171172173def ink_exact(D, counts, n):174    return Fraction(sum(counts[p] for p in D), 6 * n * n)175176177def family_laws(n):178    s = -1 if n % 4 == 1 else 1179    carpet = (Fraction(1, 2) + Fraction(s, 8) + Fraction(1, 2 * n)180              - Fraction(s, 8 * n * n))181    tree = (Fraction(1, 4) + (Fraction(1, 3) - Fraction(s, 12)) / n182            + Fraction(1 - s, 6 * n * n))183    void = Fraction(1, 4) - Fraction(s, 4 * n) + Fraction(1, 2 * n * n)184    return {23: carpet, 232: 1 - carpet, 3: tree, 129: void}185186187def orbits():188    seen = {}189    reps = []190    for code in range(256):191        D = design(code)192        if D in seen:193            continue194        orbit = set()195        for perm in permutations(range(3)):196            for flip in corners():197                img = frozenset(198                    tuple(p[perm[i]] ^ flip[i] for i in range(3)) for p in D)199                orbit.add(img)200        for img in orbit:201            seen[img] = len(reps)202        reps.append((code, len(orbit)))203    return reps204205206def constituent(k, hpar, arg, raw=False):207    table = {(0, 0): (18, 18, 6), (0, 1): (6, 12, 6), (1, 0): (6, 4, 0),208             (1, 1): (18, 30, 12), (2, 0): (18, 6, 0), (2, 1): (6, 8, 2),209             (3, 0): (6, 0, 0), (3, 1): (18, 18, 6)}210    a, b, c = table[(k, hpar)]211    q = arg if raw else (arg - 1 - 2 * hpar) // 4212    return a * q * q + b * q + c213214215def main():216    small = list(range(1, 56, 2))217    for n in small:218        got = brute_counts(n)219        for e in corners():220            want = quasi(sum(e), n)221            assert got[e] == want, "counts n=%d eps=%s got %d want %d" % (222                n, e, got[e], want)223        total = sum(got.values())224        assert total == 6 * n * n, "slice size n=%d got %d want %d" % (225            n, total, 6 * n * n)226        for k in range(4):227            vals = {got[e] for e in corners() if sum(e) == k}228            assert len(vals) == 1, "weight split n=%d k=%d got %s" % (229                n, k, sorted(vals))230            g = gf_extract(k, n)231            assert g == quasi(k, n), "gf n=%d k=%d got %d want %d" % (232                n, k, g, quasi(k, n))233            f = split_sum(k, n)234            assert f == quasi(k, n), "finite sum n=%d k=%d got %d want %d" % (235                n, k, f, quasi(k, n))236        laws = family_laws(n)237        for code, want in laws.items():238            gotink = ink_law(design(code), n)239            assert gotink == want, "family n=%d code=%d got %s want %s" % (240                n, code, gotink, want)241        for code in range(256):242            D = design(code)243            gotink = ink_exact(D, got, n)244            want = ink_law(D, n)245            assert gotink == want, "ink n=%d code=%d got %s want %s" % (246                n, code, gotink, want)247        print("odd n = %d: eight counts, three routes, 256 inks all agree" % n)248249    for n in (101, 555, 999, 9991):250        got = macro_counts(n)251        for e in corners():252            want = quasi(sum(e), n)253            assert got[e] == want, "cold n=%d eps=%s got %d want %d" % (254                n, e, got[e], want)255        total = sum(got.values())256        assert total == 6 * n * n, "cold size n=%d got %d want %d" % (257            n, total, 6 * n * n)258        for code in range(256):259            D = design(code)260            gotink = ink_exact(D, got, n)261            want = ink_law(D, n)262            assert gotink == want, "cold ink n=%d code=%d got %s want %s" % (263                n, code, gotink, want)264        print("cold n = %d: interval count matches, 256 inks exact" % n)265266    for n in (1, 3, 5, 7, 9):267        cut = solid_cut(n)268        pts = plane_points(n)269        assert cut == pts, "solid cut n=%d got %d cells want %d" % (270            n, len(cut), len(pts))271        assert len(cut) == 6 * n * n, "solid cut size n=%d got %d want %d" % (272            n, len(cut), 6 * n * n)273    print("solid cut: n = 1, 3, 5, 7, 9 agree with the slice cell for cell")274275    nine = tuple(quasi(k, 9) for k in range(4))276    assert nine == (114, 32, 84, 24), "n=9 got %s want (114, 32, 84, 24)" % (277        nine,)278    assert tuple(gf_extract(k, 9) for k in range(4)) == nine, \279        "n=9 gf got %s want %s" % (tuple(gf_extract(k, 9) for k in range(4)), nine)280    print("n = 9 anchor: got %s want (114, 32, 84, 24)" % (nine,))281282    one = tuple(quasi(k, 1) for k in range(4))283    assert one == (6, 0, 0, 0), "n=1 got %s want (6, 0, 0, 0)" % (one,)284    print("n = 1 boundary: got %s want (6, 0, 0, 0)" % (one,))285286    ladder = set()287    for code in range(256):288        D = design(code)289        a = spectrum(D)290        b = spectrum_kraw(D)291        assert a == b, "krawtchouk code=%d got %s want %s" % (code, b, a)292        blink = -a[3] / 2293        assert (blink == 0) == (a[3] == 0), "blink zero code=%d" % code294        ladder.add(abs(blink))295    want = {Fraction(0), Fraction(1, 16), Fraction(1, 8), Fraction(3, 16),296            Fraction(1, 4)}297    assert ladder == want, "ladder got %s want %s" % (298        sorted(ladder), sorted(want))299    print("blink ladder: got %s want the same five rungs" % (300        sorted(str(v) for v in ladder),))301302    reps = orbits()303    assert len(reps) == 22, "classes got %d want 22" % len(reps)304    assert sum(size for _, size in reps) == 256, "orbit sizes got %d want 256" % \305        sum(size for _, size in reps)306    still = sum(1 for code, _ in reps if spectrum_kraw(design(code))[3] == 0)307    assert still == 9, "stationary classes got %d want 9" % still308    print("symmetry: got 22 classes covering 256 designs, 9 stationary")309310    for q in range(0, 12):311        for k in range(4):312            assert constituent(k, 0, 4 * q + 1) == quasi(k, 4 * q + 1), \313                "constituent k=%d n=%d" % (k, 4 * q + 1)314            assert constituent(k, 1, 4 * q + 3) == quasi(k, 4 * q + 3), \315                "constituent k=%d n=%d" % (k, 4 * q + 3)316            left = constituent(k, 0, -q - 1, True)317            right = constituent(3 - k, 1, q, True)318            assert left == right, "reciprocity q=%d k=%d got %d want %d" % (319                q, k, left, right)320    print("reciprocity: q -> -q-1 sends constituent k to constituent 3-k")321322    hexa = [18 * q * q + 18 * q + 6 for q in range(6)]323    assert hexa == [6, 42, 114, 222, 366, 546], "A164016 got %s" % hexa324    assert all(quasi(0, 4 * q + 1) == hexa[q] for q in range(6)), "A164016 k=0"325    assert all(quasi(3, 4 * q + 3) == hexa[q] for q in range(6)), "A164016 k=3"326    print("A164016: got %s want 6, 42, 114, 222, 366, 546" % hexa)327328    print("all green")329330331if __name__ == "__main__":332    main()