verify.py

12.8 kB · python · 399 lines

1from itertools import product23# KRONECKER45CODES = tuple(range(1, 16))6LIB3 = (3, 5, 6, 7, 9, 10, 11, 12, 13, 14)7K2 = (3, 5, 6, 9, 10, 12)8K3 = (7, 11, 13, 14, 15)91011def tile(c):12    return [((c >> i) & 1) | (((c >> (i + 2)) & 1) << 1) for i in range(2)]131415def kron(A, wa, B, wb):16    out = []17    for ra in A:18        for rb in B:19            v = 020            for j in range(wa):21                if (ra >> j) & 1:22                    v |= rb << (j * wb)23            out.append(v)24    return out252627def grid(w):28    rows, wid = [1], 129    for c in w:30        rows = kron(rows, wid, tile(c), 2)31        wid *= 232    return rows, wid333435def runs(row, n):36    out = []37    j = 038    while j < n:39        if (row >> j) & 1:40            k = j41            while k < n and (row >> k) & 1:42                k += 143            out.append((j, k))44            j = k45        else:46            j += 147    return out484950def label(rows, n, diag):51    parent = []52    segs = []5354    def find(x):55        while parent[x] != x:56            parent[x] = parent[parent[x]]57            x = parent[x]58        return x5960    prev = []61    for i in range(n):62        cur = []63        for (a, b) in runs(rows[i], n):64            idx = len(parent)65            parent.append(idx)66            segs.append((i, a, b))67            for (pa, pb, pi) in prev:68                touch = (pa <= b and a <= pb) if diag else (pa < b and a < pb)69                if touch:70                    ra, rb = find(pi), find(idx)71                    if ra != rb:72                        parent[rb] = ra73            cur.append((a, b, idx))74        prev = cur75    return parent, segs, find767778# OBSERVABLES7980def components(rows, n, diag=False):81    parent, segs, find = label(rows, n, diag)82    return len({find(i) for i in range(len(parent))})838485def enclosed(rows, n, diag):86    full = (1 << n) - 187    parent, segs, find = label([full ^ r for r in rows], n, diag)88    open_roots = set()89    roots = set()90    for i, (r, a, b) in enumerate(segs):91        root = find(i)92        roots.add(root)93        if r == 0 or r == n - 1 or a == 0 or b == n:94            open_roots.add(root)95    return len(roots - open_roots)969798def border_components(rows, n):99    parent, segs, find = label(rows, n, False)100    touching = set()101    for i, (r, a, b) in enumerate(segs):102        if r == 0 or r == n - 1 or a == 0 or b == n:103            touching.add(find(i))104    return len(touching)105106107def fill(rows):108    return sum(bin(r).count("1") for r in rows)109110111def perimeter(rows, n):112    h = sum(bin(r & (r >> 1)).count("1") for r in rows)113    v = sum(bin(rows[i] & rows[i + 1]).count("1") for i in range(n - 1))114    return 4 * fill(rows) - 2 * h - 2 * v115116117def interior(rows, n):118    band = ((1 << (n - 1)) - 2) if n >= 2 else 0119    total = 0120    for i in range(1, n - 1):121        m = rows[i] & (rows[i] >> 1) & (rows[i] << 1) & rows[i - 1] & rows[i + 1] & band122        total += bin(m).count("1")123    return total124125126def boundary(rows, n):127    return fill(rows) - interior(rows, n)128129130def maindiag(rows, n):131    return sum((rows[i] >> i) & 1 for i in range(n))132133134def antidiag(rows, n):135    return sum((rows[i] >> (n - 1 - i)) & 1 for i in range(n))136137138def antiprofile(rows, n):139    p = [0] * (2 * n - 1)140    for i in range(n):141        for j in range(n):142            if (rows[i] >> j) & 1:143                p[i + j] += 1144    return tuple(p)145146147def hcontact(rows, n):148    return sum(1 for i in range(n) if (rows[i] >> (n - 1)) & 1 and rows[i] & 1)149150151def vcontact(rows, n):152    return bin(rows[0] & rows[n - 1]).count("1")153154155NAMES = ("components", "boundary", "euler", "holes", "perimeter", "maindiag", "antidiag", "antiprofile", "fill")156157158def observe(w):159    rows, n = grid(w)160    c = components(rows, n)161    return (c, boundary(rows, n), c - enclosed(rows, n, True), enclosed(rows, n, False),162            perimeter(rows, n), maindiag(rows, n), antidiag(rows, n), antiprofile(rows, n), fill(rows))163164165def landscape(codes, L):166    vals = {w: observe(w) for w in product(codes, repeat=L)}167    groups = {}168    for w in vals:169        groups.setdefault(tuple(sorted(w)), []).append(w)170    multi = [v for v in groups.values() if len(v) >= 2]171    sens = {}172    for i, name in enumerate(NAMES):173        sens[name] = sum(1 for v in multi if len({vals[w][i] for w in v}) > 1)174    return vals, len(groups), len(multi), sens175176177def first_sensitive(vals, index):178    groups = {}179    for w in vals:180        groups.setdefault(tuple(sorted(w)), []).append(w)181    for key in sorted(groups):182        v = groups[key]183        if len(v) >= 2 and len({vals[w][index] for w in v}) > 1:184            return key185    return None186187188# COCYCLE189190CLASSES = {191    (1, 2, 4, 8): ((1, 0, 0, 0), (2, 0, 0, 0), (2, 0, 0, 0), (4, 0, 0, 0)),192    (6, 9): ((2, 0, 0, 0), (4, 0, 0, 0), (4, 0, 0, 0), (8, 0, 0, 0)),193    (3, 12): ((0, 1, 0, 0), (-2, 3, 0, 0), (0, 2, 0, 0), (-4, 6, 0, 0)),194    (5, 10): ((0, 0, 1, 0), (0, 0, 2, 0), (-2, 0, 3, 0), (-4, 0, 6, 0)),195    (7, 11, 13, 14): ((-1, 1, 1, 0), (-4, 3, 2, 0), (-4, 2, 3, 0), (-8, 4, 4, 1)),196    (15,): ((0, 0, 0, 1), (0, 0, -2, 3), (0, -2, 0, 3), (4, -6, -6, 9)),197}198MAT = {}199for klass, m in CLASSES.items():200    for c in klass:201        MAT[c] = m202203204def matmul(A, B):205    return tuple(tuple(sum(A[i][t] * B[t][j] for t in range(4)) for j in range(4)) for i in range(4))206207208def represent(w):209    v = (1, 0, 0, 0)210    for c in w:211        M = MAT[c]212        v = tuple(sum(v[i] * M[i][j] for i in range(4)) for j in range(4))213    return sum(v)214215216PRIME = (1 << 31) - 1217218219def hankel_rank(table, prefixes, index):220    rows = [[table[u + v][index] % PRIME for v in prefixes] for u in prefixes]221    r, col, m, ncol = 0, 0, len(rows), len(prefixes)222    while col < ncol and r < m:223        piv = next((i for i in range(r, m) if rows[i][col]), None)224        if piv is None:225            col += 1226            continue227        rows[r], rows[piv] = rows[piv], rows[r]228        inv = pow(rows[r][col], PRIME - 2, PRIME)229        pr = [x * inv % PRIME for x in rows[r]]230        rows[r] = pr231        for i in range(r + 1, m):232            f = rows[i][col]233            if f:234                rows[i] = [(a - f * b) % PRIME for a, b in zip(rows[i], pr)]235        r += 1236        col += 1237    return r238239240# CHECKS241242def check(name, got, want):243    assert got == want, f"{name}: got {got}, want {want}"244    print(f"{name}: {got}")245246247def main():248    check("comp(3,6)", observe((3, 6))[0], 4)249    check("comp(6,3)", observe((6, 3))[0], 2)250251    sensitive_k2 = sorted(p for p in product(K2, repeat=2) if p[0] < p[1]252                          and observe(p)[0] != observe((p[1], p[0]))[0])253    check("least order-sensitive pair of two-cell designs", sensitive_k2[0], (3, 6))254255    bad = 0256    for a in range(16):257        for b in range(16):258            ra, na = grid((a, b))259            rb, nb = grid((b, a))260            if boundary(ra, na) != boundary(rb, nb):261                bad += 1262    check("boundary-cell asymmetries over all 256 ordered code pairs", bad, 0)263264    def three(c):265        return frozenset({(0, 0), (0, 1), (1, 0), (1, 1)} - {c})266267    def cells(c):268        return {(i, j) for i in range(2) for j in range(2) if (c >> (i + 2 * j)) & 1}269270    bad = 0271    for a in range(16):272        for b in range(16):273            fa, fb = cells(a), cells(b)274            formula = sum(1 for p in [(0, 0), (0, 1), (1, 0), (1, 1)]275                          if three(p) <= fa and three((1 - p[0], 1 - p[1])) <= fb)276            rows, n = grid((a, b))277            if formula != interior(rows, n):278                bad += 1279    check("interior-formula mismatches over all 256 ordered code pairs", bad, 0)280281    bad = 0282    for w in product(CODES, repeat=3):283        rows, n = grid(w)284        prod = 1285        for c in w:286            prod *= sum(1 for d in range(2) if (c >> (d + 2 * d)) & 1)287        if maindiag(rows, n) != prod:288            bad += 1289    check("main-diagonal product-formula mismatches over all 3375 words of length 3", bad, 0)290291    bad = 0292    for w in product(CODES, repeat=3):293        rows, n = grid(w)294        ph, pv = 1, 1295        for c in w:296            t = tile(c)297            ph *= hcontact(t, 2)298            pv *= vcontact(t, 2)299        if (hcontact(rows, n), vcontact(rows, n)) != (ph, pv):300            bad += 1301    check("contact-law mismatches over all 3375 words of length 3", bad, 0)302303    adjacent, diagonal = (3, 5, 10, 12), (6, 9)304    bad = 0305    for a in adjacent:306        for d in diagonal:307            if (observe((a, d))[0], observe((d, a))[0]) != (4, 2):308                bad += 1309    check("adjacent-times-diagonal deviations from (4,2) over all 8 ordered pairs", bad, 0)310311    bad = sum(1 for a in adjacent for b in adjacent if observe((a, b))[0] != observe((b, a))[0])312    check("adjacent-times-adjacent asymmetries over all 16 ordered pairs", bad, 0)313    bad = sum(1 for a in diagonal for b in diagonal if observe((a, b))[0] != 4)314    check("diagonal-times-diagonal deviations from 4 over all 4 ordered pairs", bad, 0)315    bad = sum(1 for a in K3 for b in K3 if observe((a, b))[0] != 1)316    check("k>=3 deviations from 1 over all 25 ordered pairs", bad, 0)317318    bad = 0319    for c1 in CODES:320        for c2 in CODES:321            for L in (2, 3):322                block = grid((c1, c2))323                rows, n = block324                for _ in range(L - 1):325                    rows = kron(rows, n, block[0], block[1])326                    n *= block[1]327                if (rows, n) != grid((c1, c2) * L):328                    bad += 1329    check("block-reduction mismatches over 225 blocks at periods 2 and 3", bad, 0)330331    v2, g2, m2, s2 = landscape(CODES, 2)332    check("length-2 multisets", (len(v2), g2, m2), (225, 120, 105))333    check("length-2 order-sensitive multisets", tuple(s2[k] for k in NAMES),334          (74, 0, 78, 10, 78, 0, 0, 99, 0))335336    v3, g3, m3, s3 = landscape(LIB3, 3)337    check("length-3 multisets over the ten-code library", (len(v3), g3, m3), (1000, 220, 210))338    check("length-3 order-sensitive multisets", tuple(s3[k] for k in NAMES),339          (188, 36, 188, 100, 188, 0, 0, 204, 0))340341    check("length-2 words with euler != components - holes",342          sum(1 for o in v2.values() if o[2] != o[0] - o[3]), 10)343    check("length-3 words with euler != components - holes",344          sum(1 for o in v3.values() if o[2] != o[0] - o[3]), 168)345346    check("anti-diagonal profiles of (1,2) and (2,1)", (observe((1, 2))[7], observe((2, 1))[7]),347          ((0, 1, 0, 0, 0, 0, 0), (0, 0, 1, 0, 0, 0, 0)))348    check("lexicographically first anti-diagonal-profile witness", first_sensitive(v2, 7), (1, 2))349    check("perimeters of (1,3) and (3,1)", (observe((1, 3))[4], observe((3, 1))[4]), (6, 8))350    check("lexicographically first perimeter witness", first_sensitive(v2, 4), (1, 3))351    check("boundary cells of (1,3) and (3,1)", (observe((1, 3))[1], observe((3, 1))[1]), (2, 2))352353    table = {(): (1, 1, 1, 0)}354    peak = {}355    for L in (1, 2, 3, 4):356        best = 0357        for w in product(CODES, repeat=L):358            rows, n = grid(w)359            c = components(rows, n)360            table[w] = (c, boundary(rows, n), c - enclosed(rows, n, True), enclosed(rows, n, False))361            best = max(best, c)362        peak[L] = best363    check("words of length at most 4", len(table), 54241)364    check("representation mismatches over all 54241 words of length at most 4",365          sum(1 for w, o in table.items() if represent(w) != o[0]), 0)366    check("largest component count at lengths 1 to 4", tuple(peak[L] for L in (1, 2, 3, 4)),367          (2, 8, 32, 128))368369    for L in range(1, 7):370        rows, n = grid((15,) * (L - 1) + (6,))371        check(f"components of (15^{L - 1}, 6)", components(rows, n), 2 * 4 ** (L - 1))372    for L in range(1, 8):373        rows, n = grid((15,) * (L - 1) + (3,))374        check(f"border-meeting components of (15^{L - 1}, 3)", border_components(rows, n), 2 ** (L - 1))375376    prefixes = [()] + [(a,) for a in CODES] + [(a, b) for a in CODES for b in CODES]377    check("Hankel ranks of the length-4 truncation",378          tuple(hankel_rank(table, prefixes, i) for i in (0, 2, 1, 3)), (4, 4, 8, 11))379380    klass = list(CLASSES)381    pairs = [(i, j) for i in range(6) for j in range(i + 1, 6)]382    noncommuting = [(klass[i], klass[j]) for (i, j) in pairs383                    if matmul(CLASSES[klass[i]], CLASSES[klass[j]]) != matmul(CLASSES[klass[j]], CLASSES[klass[i]])]384    check("noncommuting class pairs out of 15", len(noncommuting), 14)385    commuting = [(klass[i], klass[j]) for (i, j) in pairs if (klass[i], klass[j]) not in noncommuting]386    check("commuting class pair", commuting, [((1, 2, 4, 8), (6, 9))])387    check("M(6,9) equals twice M(1,2,4,8)", CLASSES[(6, 9)],388          tuple(tuple(2 * x for x in row) for row in CLASSES[(1, 2, 4, 8)]))389390    for (a, b, want) in ((3, 5, 2), (7, 15, 1)):391        check(f"M({a}) and M({b}) commute", matmul(MAT[a], MAT[b]) == matmul(MAT[b], MAT[a]), False)392        check(f"components of ({a},{b}) and of ({b},{a})",393              (observe((a, b))[0], observe((b, a))[0]), (want, want))394395    print("all green")396397398if __name__ == "__main__":399    main()