anf.py

10.4 kB · python · 291 lines

1import time2from itertools import product3from math import gcd45import numpy as np67BASE2_DIMS = (2, 3, 4)8IDENTITY_CASES = ((2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (5, 2), (7, 2))9COMPOSITE_BASES = (4, 6, 8, 9, 10, 12)10PRIME_BASES = (2, 3, 5, 7, 11, 13)11CLAIMED_TABLE = {2: (2, 2, 3, 4), 3: (4, 4, 6, 6)}1213def cells(q, d):14    return list(product(range(q), repeat=d))1516def strides(q, d):17    return [q ** (d - 1 - a) for a in range(d)]1819def mod_inverse(a, q):20    old_r, r = a % q, q21    old_s, s = 1, 022    while r:23        quotient = old_r // r24        old_r, r = r, old_r - quotient * r25        old_s, s = s, old_s - quotient * s26    return old_s % q2728def vandermonde(q):29    return [[pow(i, j, q) for j in range(q)] for i in range(q)]3031def vandermonde_det_mod(q):32    out = 133    for i in range(q):34        for j in range(i + 1, q):35            out = out * (j - i) % q36    return out3738def invert_mod(matrix, q):39    n = len(matrix)40    aug = [list(row) + [int(i == j) for j in range(n)] for i, row in enumerate(matrix)]41    for col in range(n):42        pivot = next((r for r in range(col, n) if gcd(aug[r][col] % q, q) == 1), None)43        if pivot is None:44            return None45        aug[col], aug[pivot] = aug[pivot], aug[col]46        inv = mod_inverse(aug[col][col], q)47        aug[col] = [x * inv % q for x in aug[col]]48        for r in range(n):49            if r != col and aug[r][col] % q:50                factor = aug[r][col]51                aug[r] = [(x - factor * y) % q for x, y in zip(aug[r], aug[col])]52    return [row[n:] for row in aug]5354def axis_transform(values, q, d, rows):55    size = q ** d56    coeff = list(values)57    for stride in strides(q, d):58        nxt = [0] * size59        for base in range(size):60            if (base // stride) % q:61                continue62            line = [coeff[base + t * stride] for t in range(q)]63            for e in range(q):64                nxt[base + e * stride] = sum(rows[e][t] * line[t] for t in range(q)) % q65        coeff = nxt66    return coeff6768def gfq_eval(coeff, q, cs):69    out = []70    for x in cs:71        acc = 072        for value, e in zip(coeff, cs):73            if value:74                term = value75                for xi, ei in zip(x, e):76                    term = term * pow(xi, ei, q) % q77                acc = (acc + term) % q78        out.append(acc)79    return out8081def int_mobius(values, q, d):82    size = q ** d83    coeff = list(values)84    for stride in strides(q, d):85        for level in range(q - 1, 0, -1):86            for index in range(size):87                if (index // stride) % q == level:88                    coeff[index] -= coeff[index - stride]89    return coeff9091def int_eval(coeff, cs):92    out = []93    for x in cs:94        acc = 095        for value, m in zip(coeff, cs):96            if value and all(xi >= mi for xi, mi in zip(x, m)):97                acc += value98        out.append(acc)99    return out100101def degree_of(coeff, cs):102    return max((sum(e) for value, e in zip(coeff, cs) if value), default=-1)103104def tensor_power(m, d, q):105    out = np.array(m, dtype=np.int64)106    for _ in range(d - 1):107        out = np.kron(out, np.array(m, dtype=np.int64)) % q108    return out109110def eval_matrix(q, d):111    cs = cells(q, d)112    return np.array([[np.prod([pow(xi, ei, q) for xi, ei in zip(x, e)]) % q for e in cs] for x in cs], dtype=np.int64)113114def int_basis_matrix(q, d):115    cs = cells(q, d)116    return np.array([[int(all(xi >= mi for xi, mi in zip(x, m))) for m in cs] for x in cs], dtype=np.int64)117118def int_mobius_matrix(q, d):119    n = q ** d120    columns = [int_mobius([int(i == j) for i in range(n)], q, d) for j in range(n)]121    return np.array(columns, dtype=np.int64).T122123def xor_anf_masks(table, d):124    out = [0] * (1 << d)125    for s in range(1 << d):126        acc = 0127        sub = s128        while True:129            acc ^= table[sub]130            if sub == 0:131                break132            sub = (sub - 1) & s133        out[s] = acc134    return out135136def signed_subset_real(table, d):137    out = [0] * (1 << d)138    for s in range(1 << d):139        acc = 0140        sub = s141        while True:142            if table[sub]:143                acc += -1 if bin(s ^ sub).count("1") & 1 else 1144            if sub == 0:145                break146            sub = (sub - 1) & s147        out[s] = acc148    return out149150def reversed_mask(s, d):151    return sum(((s >> i) & 1) << (d - 1 - i) for i in range(d))152153def code_to_table(code, n):154    return [(code >> i) & 1 for i in range(n)]155156def named(q, d):157    cs = cells(q, d)158    rules = (159        ("void", lambda c: all(v == c[0] for v in c)),160        ("tree", lambda c: all(a == 0 or c[a] == 0 for a in range(d))),161        ("carpet", lambda c: sum(c) <= 1),162        ("net", lambda c: sum(c) >= d - 1),163    )164    return [(name, [int(rule(c)) for c in cs]) for name, rule in rules]165166def fractal_shapes():167    carpet = [int(c != (1, 1)) for c in cells(3, 2)]168    sponge = [int(c.count(1) <= 1) for c in cells(3, 3)]169    return (("sierpinski carpet", 2, carpet), ("menger sponge", 3, sponge))170171def histogram(counts):172    return ", ".join(f"{k}: {counts[k]}" for k in sorted(counts))173174def verdict(ok):175    return "PASS" if ok else "FAIL"176177def block_inverse():178    print("THE INVERSE VANDERMONDE OVER GF(q)")179    for q in PRIME_BASES:180        v = vandermonde(q)181        inv = invert_mod(v, q)182        ok = inv is not None and np.array_equal(np.array(inv) @ np.array(v) % q, np.eye(q, dtype=np.int64))183        print(f"  q={q}  gcd(det, q) = {gcd(vandermonde_det_mod(q), q)}  invertible {inv is not None}  {verdict(ok)}")184    inv2 = invert_mod(vandermonde(2), 2)185    print(f"  q=2  inverse {inv2}  {verdict(inv2 == [[1, 0], [1, 1]])}")186    for q in COMPOSITE_BASES:187        inv = invert_mod(vandermonde(q), q)188        print(f"  q={q}  gcd(det, q) = {gcd(vandermonde_det_mod(q), q)}  invertible {inv is not None}  {verdict(inv is None)}")189    print()190191def block_identity():192    print("THE TENSORED TRANSFORM INVERTS EVALUATION, ALL VALUE TABLES AT ONCE")193    for q, d in IDENTITY_CASES:194        n = q ** d195        t = tensor_power(invert_mod(vandermonde(q), q), d, q)196        e = eval_matrix(q, d)197        eye = np.eye(n, dtype=np.int64)198        ok = np.array_equal(t @ e % q, eye) and np.array_equal(e @ t % q, eye)199        print(f"  q={q} D={d}  T E = E T = I on {n} coordinates  covers all {q}^{n} tables  {verdict(ok)}")200    print("THE INTEGER MOBIUS INVERTS THE DOWNWARD-CLOSED BASIS")201    for q, d in ((2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (4, 2), (4, 3), (6, 2), (9, 2)):202        n = q ** d203        ok = np.array_equal(int_basis_matrix(q, d) @ int_mobius_matrix(q, d), np.eye(n, dtype=np.int64))204        print(f"  q={q} D={d}  B M = I over Z on {n} coordinates  {verdict(ok)}")205    print()206207def block_base2():208    print("BASE 2, EVERY DESIGN, AGAINST THE CLASSICAL XOR ANF")209    rows = invert_mod(vandermonde(2), 2)210    for d in BASE2_DIMS:211        cs = cells(2, d)212        n = 1 << d213        bad_rt = bad_anf = bad_naive = bad_int = 0214        for code in range(1 << n):215            table = code_to_table(code, n)216            coeff = axis_transform(table, 2, d, rows)217            if gfq_eval(coeff, 2, cs) != table:218                bad_rt += 1219            masked = xor_anf_masks([table[reversed_mask(s, d)] for s in range(n)], d)220            support = [int(v != 0) for v in coeff]221            if [masked[reversed_mask(s, d)] for s in range(n)] != support:222                bad_anf += 1223            if masked != support:224                bad_naive += 1225            integer = int_mobius(table, 2, d)226            real = signed_subset_real([table[reversed_mask(s, d)] for s in range(n)], d)227            if int_eval(integer, cs) != table or [real[reversed_mask(s, d)] for s in range(n)] != integer:228                bad_int += 1229        ok = bad_rt == 0 and bad_anf == 0 and bad_int == 0230        print(f"  D={d}  designs {1 << n:>5}  GF(2) roundtrip fails {bad_rt}  XOR ANF diffs after reversal {bad_anf}  diffs without reversal {bad_naive}  integer fails {bad_int}  {verdict(ok)}")231    print()232233def block_base3():234    print("BASE 3, D=2, ALL 512 DESIGNS")235    cs = cells(3, 2)236    rows = invert_mod(vandermonde(3), 3)237    bad_gf = bad_int = 0238    hist_gf, hist_int = {}, {}239    for code in range(512):240        table = code_to_table(code, 9)241        coeff = axis_transform(table, 3, 2, rows)242        if gfq_eval(coeff, 3, cs) != table:243            bad_gf += 1244        integer = int_mobius(table, 3, 2)245        if int_eval(integer, cs) != table:246            bad_int += 1247        dg, di = degree_of(coeff, cs), degree_of(integer, cs)248        hist_gf[dg] = hist_gf.get(dg, 0) + 1249        hist_int[di] = hist_int.get(di, 0) + 1250    print(f"  designs 512  GF(3) roundtrip fails {bad_gf}  integer roundtrip fails {bad_int}  {verdict(bad_gf == 0 and bad_int == 0)}")251    print(f"  GF(3) degree histogram    {histogram(hist_gf)}")252    print(f"  integer degree histogram  {histogram(hist_int)}")253    print(f"  designs of GF(3) degree 1: {hist_gf.get(1, 0)}  {verdict(hist_gf.get(1, 0) == 0)}")254    print()255256def block_table():257    print("THE BASE-3 DEGREE TABLE OF THE FOUR RULES")258    rows = invert_mod(vandermonde(3), 3)259    ok = True260    for d in (2, 3):261        cs = cells(3, d)262        for (name, table), claim in zip(named(3, d), CLAIMED_TABLE[d]):263            dg = degree_of(axis_transform(table, 3, d, rows), cs)264            di = degree_of(int_mobius(table, 3, d), cs)265            ok = ok and dg == claim266            print(f"  D={d} {name:<7} cells {sum(table):>2}/{3 ** d}  GF(3) deg {dg}  page {claim}  integer deg {di}  {verdict(dg == claim)}")267    print(f"  all eight entries  {verdict(ok)}")268    print()269    print("THE FRACTALS THE ROWS ARE NAMED AFTER")270    for name, d, shape in fractal_shapes():271        cs = cells(3, d)272        dg = degree_of(axis_transform(shape, 3, d, rows), cs)273        di = degree_of(int_mobius(shape, 3, d), cs)274        rule = dict(named(3, d))["carpet"]275        rdg = degree_of(axis_transform(rule, 3, d, rows), cs)276        print(f"  {name:<18} D={d}  cells {sum(shape):>2}/{3 ** d}  GF(3) deg {dg}  integer deg {di}  ceiling D(q-1) = {2 * d}")277        print(f"  carpet row         D={d}  cells {sum(rule):>2}/{3 ** d}  GF(3) deg {rdg}  same set as the fractal {rule == shape}")278    print()279280def main():281    start = time.time()282    block_inverse()283    block_identity()284    block_base2()285    block_base3()286    block_table()287    print(f"domain: base 2 at D = {', '.join(map(str, BASE2_DIMS))} exhaustive, base 3 at D = 2 exhaustive, base 3 at D = 3 by the matrix identity")288    print(f"wall {time.time() - start:.1f}s")289290if __name__ == "__main__":291    main()