classes.py

10.7 kB · python · 312 lines

1from fractions import Fraction2from itertools import product3from math import pi, prod45import numpy as np6from mpmath import zeta78CORNERS = [tuple(v) for v in product((0, 1), repeat=3)]9Z3 = float(zeta(3))10Z2 = float(zeta(2))1112def code_set(mask):13    return frozenset(v for i, v in enumerate(CORNERS) if mask >> i & 1)1415def code_mask(P):16    return sum(1 << i for i, v in enumerate(CORNERS) if v in P)1718def origin_filled(P):19    return 1 if (0, 0, 0) in P else 02021def prime_factors(n):22    ps, d = [], 223    while d * d <= n:24        if n % d == 0:25            ps.append(d)26            while n % d == 0:27                n //= d28        d += 129    if n > 1:30        ps.append(n)31    return ps3233def radical(n):34    r = 135    for p in prime_factors(n):36        r *= p37    return r3839def mobius(n):40    ps = prime_factors(n)41    r = 142    for p in ps:43        r *= p44    return 0 if r != n else (-1) ** len(ps)4546def divisors(n):47    return [d for d in range(1, n + 1) if n % d == 0]4849def digits_with(q, step, parity):50    return sum(1 for d in range(0, q, step) if d % 2 == parity)5152def corner_count(P, q, step=1):53    return sum(prod(digits_with(q, step, c) for c in v) for v in P)5455def bracket(P, q):56    k = corner_count(P, q)57    return sum(Fraction(mobius(e) * corner_count(P, q, e), k) for e in divisors(radical(q)))5859def rational_part(P, q):60    d = len(next(iter(P)))61    r = bracket(P, q)62    for p in prime_factors(q):63        r *= Fraction(p ** d, p ** d - 1)64    return r6566def bits(P):67    return [v[0] * 4 + v[1] * 2 + v[2] for v in sorted(P)]6869def difference_span(P):70    L = bits(P)71    basis = []72    for w in sorted({a ^ b for a in L for b in L}, reverse=True):73        v = w74        for b in basis:75            v = min(v, v ^ b)76        if v:77            basis.append(v)78    H = {0}79    for b in basis:80        H |= {h ^ b for h in H}81    return len(basis), H8283def classify(P):84    s2, H = difference_span(P)85    if s2 == 3:86        return "span", s287    if all(x in H for x in bits(P)):88        return "inside", s289    return "offset", s29091def weight(P):92    return tuple(sum(1 for v in P if sum(v) == j) for j in range(4))9394def is_subgroup(P):95    L = set(bits(P))96    return 0 in L and all((a ^ b) in L for a in L for b in L)9798def pinned_odd(P):99    return any(all(v[i] == 1 for v in P) for i in range(3))100101def even_band(P):102    return Fraction(8, 7) * (1 - Fraction(origin_filled(P), len(P)))103104def odd_limits(P, q):105    kind, s2 = classify(P)106    naive = rational_part(P, q)107    if kind == "span":108        return [naive]109    corrected = Fraction(8, 7) * naive * (1 - Fraction(1, 2 ** s2))110    if kind == "inside":111        return [corrected]112    return [Fraction(8, 7) * naive, corrected]113114def odd_limit_over_bases(P):115    kind, s2 = classify(P)116    if kind == "span":117        return Fraction(1)118    if kind == "inside":119        return Fraction(8, 7) * (1 - Fraction(1, 2 ** s2))120    return None121122def even_point_fraction(P, q, n):123    e, o = Fraction(q + 1, 2), Fraction(q - 1, 2)124    w = {v: e ** (3 - sum(v)) * o ** sum(v) for v in P}125    k = sum(w.values())126    total = Fraction(0)127    for t in CORNERS:128        lam = sum(w[v] * (-1) ** (t[0] * v[0] + t[1] * v[1] + t[2] * v[2]) for v in P) / k129        total += lam ** n130    return total / 8131132def predicted_level(P, q, n):133    if q % 2 == 0:134        return rational_part(P, q)135    return Fraction(8, 7) * rational_part(P, q) * (1 - even_point_fraction(P, q, n))136137def design(P, q):138    return np.array([v for v in product(range(q), repeat=3) if (v[0] % 2, v[1] % 2, v[2] % 2) in P], dtype=np.int64)139140def triangular(rows):141    M = [list(r) for r in rows]142    piv = 0143    for col in range(3):144        r = next((i for i in range(piv, len(M)) if M[i][col]), None)145        if r is None:146            continue147        M[piv], M[r] = M[r], M[piv]148        for i in range(piv + 1, len(M)):149            while M[i][col]:150                f = M[piv][col] // M[i][col]151                M[piv] = [a - f * b for a, b in zip(M[piv], M[i])]152                M[piv], M[i] = M[i], M[piv]153        piv += 1154    return piv, M155156def lattice(P, q):157    F = design(P, q).tolist()158    rows = [[a - b for a, b in zip(v, F[0])] for v in F[1:]]159    if not rows:160        return 0, 0161    rank, M = triangular(rows)162    if rank < 3:163        return rank, 0164    return 3, abs(M[0][0] * M[1][1] * M[2][2])165166def stack(F, q, n):167    pts = np.zeros((1, 3), dtype=np.int64)168    for _ in range(n):169        pts = (pts[:, None, :] * q + F[None, :, :]).reshape(-1, 3)170    return pts171172def visible(pts):173    g = np.gcd(np.gcd(pts[:, 0], pts[:, 1]), pts[:, 2])174    return int(np.count_nonzero(g == 1))175176def visible_fraction(P, q, n):177    F = design(P, q)178    if n == 1:179        return visible(F) / len(F)180    base = stack(F, q, n - 1)181    hits = 0182    for v in F:183        hits += visible(base * q + v)184    return hits / len(F) ** n185186CARPET = frozenset([(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)])187NET = frozenset([(1, 1, 0), (1, 0, 1), (0, 1, 1), (1, 1, 1)])188TREE = frozenset([(0, 0, 0), (0, 0, 1)])189VOID = frozenset([(0, 0, 0), (1, 1, 1)])190AXES = frozenset([(1, 0, 0), (0, 1, 0), (0, 0, 1)])191TOP = frozenset([(1, 1, 1)])192PINNED = frozenset([(1, 1, 0), (1, 0, 0)])193TWIN_SUB = frozenset([(0, 0, 0), (1, 0, 0), (0, 1, 0), (1, 1, 0)])194TWIN_SPAN = frozenset([(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 1, 1)])195FLAT_CARPET = frozenset([(0, 0), (1, 0), (0, 1)])196197FAMILIES = [("carpet", CARPET), ("net", NET), ("tree", TREE), ("void", VOID)]198199CASES = [200    ("carpet", CARPET, 2, 12),201    ("carpet", CARPET, 4, 5),202    ("net", NET, 4, 5),203    ("tree", TREE, 3, 7),204    ("tree", TREE, 5, 5),205    ("void", VOID, 3, 8),206    ("void", VOID, 5, 5),207    ("twin subgroup", TWIN_SUB, 3, 6),208    ("twin spanning", TWIN_SPAN, 3, 6),209    ("twin subgroup", TWIN_SUB, 4, 4),210    ("twin spanning", TWIN_SPAN, 4, 4),211]212213def band_report(codes):214    bad = [(code_mask(P), q) for P in codes for q in range(2, 41, 2) if rational_part(P, q) != even_band(P)]215    print("even bases: 255 codes x even q<=40, exceptions to (8/7)(1-t/|P|):", len(bad))216    band = sorted({even_band(P) for P in codes})217    print("even band values of delta*zeta(3):", len(band), " ".join(str(x) for x in band))218    flat = {rational_part(FLAT_CARPET, q) for q in range(2, 41, 2)}219    v = float(min(flat)) / Z2220    print("D=2 parity carpet at even q<=40: delta*zeta(2) =", " ".join(str(x) for x in flat),221          "delta =", repr(v), "16/(3 pi^2) =", repr(16 / (3 * pi ** 2)))222223def similarity_report(codes):224    bad = 0225    for P in codes:226        for q in range(3, 76, 2):227            for e in divisors(radical(q)):228                if corner_count(P, q, e) != corner_count(P, q // e):229                    bad += 1230    print("odd bases: 255 codes x odd q<=75, exceptions to k_e(q)=k(q/e):", bad)231232def class_report(codes):233    kinds = {}234    for P in codes:235        kind, s2 = classify(P)236        kinds.setdefault(kind, []).append(s2)237    print("class counts: span", len(kinds["span"]), "inside", len(kinds["inside"]), "offset", len(kinds["offset"]),238          "total", sum(len(v) for v in kinds.values()))239    print("inside by s2:", {s: kinds["inside"].count(s) for s in sorted(set(kinds["inside"]))})240    classes = {}241    for P in codes:242        classes.setdefault(weight(P), set()).add(classify(P)[0])243    mixed = [k for k in classes.values() if len(k) > 1]244    print("weight classes:", len(classes), "mixing regimes:", len(mixed),245          "spanning against not:", sum(1 for k in mixed if "span" in k),246          "corrected against no-limit:", sum(1 for k in mixed if k == {"inside", "offset"}))247    subgroups = sorted(code_mask(P) for P in codes if is_subgroup(P))248    print("subgroup codes:", len(subgroups), " ".join(str(m) for m in subgroups))249    stable = sorted(code_mask(P) for P in codes if odd_limit_over_bases(P) == even_band(P))250    print("parity-stable codes:", len(stable), "equal to subgroup codes:", stable == subgroups)251    print("codes with two subsequential limits:", 255 - len(stable))252253def lattice_report(codes):254    full = [P for P in codes if lattice(P, 2)[0] == 3]255    print("q=2: full-rank codes:", len(full), "smallest |P|:", min(len(P) for P in full),256          "indices:", sorted({lattice(P, 2)[1] for P in full}))257    print("even q=4,6,8,10: lattice indices over all codes:",258          sorted({lattice(P, q)[1] for P in codes for q in (4, 6, 8, 10)}))259    print("q=3: codes of lattice rank <=1:", sum(1 for P in codes if lattice(P, 3)[0] <= 1),260          "codes with a coordinate pinned odd:", sum(1 for P in codes if pinned_odd(P)))261262def family_report():263    print("family table, delta*zeta(3):", " ".join(name for name, _ in FAMILIES))264    print("  every even:", " ".join(str(even_band(P)) for _, P in FAMILIES))265    for q in (3, 5, 7, 9, 11):266        print("  q=%-2d       :" % q, " ".join(str(odd_limits(P, q)[-1]) for _, P in FAMILIES))267    print("  odd limit  :", " ".join(str(odd_limit_over_bases(P)) for _, P in FAMILIES))268    print("carpet rational parts at odd q=3,5,7,9,11:",269          " ".join("%.5f" % float(odd_limits(CARPET, q)[-1]) for q in (3, 5, 7, 9, 11)),270          "k(q):", " ".join(str(corner_count(CARPET, q)) for q in (3, 5, 7, 9, 11)),271          "dips 1/k(7) = 1/%d, k(3)/k(9) = %d/%d"272          % (corner_count(CARPET, 7), corner_count(CARPET, 3), corner_count(CARPET, 9)))273    print("net: odd q<=81 with rational part <= 1:", [q for q in range(3, 82, 2) if rational_part(NET, q) <= 1],274          "B(9) =", bracket(NET, 9), "= 1 - %d/%d" % (corner_count(NET, 3), corner_count(NET, 9)),275          "k(1) =", corner_count(NET, 1))276277def exact_report():278    codes = [code_set(m) for m in range(1, 256)]279    band_report(codes)280    similarity_report(codes)281    class_report(codes)282    lattice_report(codes)283    family_report()284285def measured_report():286    for name, P, q, n in CASES:287        k = len(design(P, q))288        got = visible_fraction(P, q, n)289        level = float(predicted_level(P, q, n)) / Z3290        lims = [float(x) / Z3 for x in odd_limits(P, q)] if q % 2 else [float(rational_part(P, q)) / Z3]291        print("%-14s q=%d n=%2d k=%3d measured=%.6f level=%.6f limits=%s"292              % (name, q, n, k, got, level, " ".join("%.6f" % x for x in lims)))293    print("pinned {110,100} q=3 odd levels:", " ".join("%.6f" % visible_fraction(PINNED, 3, n) for n in (3, 5, 7)),294          "outside the trichotomy, whose value is %.6f" % (float(odd_limits(PINNED, 3)[0]) / Z3))295296def oscillation_report(name, P, q, levels):297    lims = [float(x) / Z3 for x in odd_limits(P, q)]298    print("%s q=%d limits: odd %.6f even %.6f" % (name, q, lims[0], lims[1]))299    for n in range(1, levels + 1):300        print("  n=%d measured=%.6f level=%.6f" % (n, visible_fraction(P, q, n), float(predicted_level(P, q, n)) / Z3))301302def main():303    exact_report()304    print()305    measured_report()306    print()307    oscillation_report("top corner", TOP, 5, 8)308    print()309    oscillation_report("axes", AXES, 3, 7)310311if __name__ == "__main__":312    main()