slice_grammar.py

7.1 kB · python · 249 lines

1from fractions import Fraction23import numpy as np4from mpmath import mp, mpf5from mpmath import log as mlog6from mpmath import sqrt as msqrt78mp.dps = 4091011def digit_sums(b, rule):12    if rule == "odd":13        marked = {d for d in range(b) if d % 2 == 1}14    else:15        marked = {(b - 1) // 2}16    plain = np.zeros(b, dtype=np.int64)17    special = np.zeros(b, dtype=np.int64)18    for d in range(b):19        if d in marked:20            special[d] = 121        else:22            plain[d] = 123    square = np.convolve(plain, plain)24    total = np.convolve(square, plain) + 3 * np.convolve(special, square)25    return [int(v) for v in total]262728def at(c, i):29    if i < 0 or i >= len(c):30        return 031    return c[i]323334def transfer(b, c):35    row_mid = (at(c, (3 * b - 3) // 2), at(c, (3 * b - 1) // 2), at(c, (3 * b - 5) // 2))36    row_low = (at(c, (b - 3) // 2), at(c, (b - 1) // 2), at(c, (b - 5) // 2))37    row_high = (at(c, (5 * b - 3) // 2), at(c, (5 * b - 1) // 2), at(c, (5 * b - 5) // 2))38    return row_mid, row_low, row_high394041def apply_rows(rows, v):42    return tuple(r[0] * v[0] + r[1] * v[1] + r[2] * v[2] for r in rows)434445def tile_counts(b, c, levels):46    rows = transfer(b, c)47    states = [(0, 1, 0), (1, 0, 0), (0, 0, 1)]48    hexes = [states[1][0]]49    tris = [states[0][0] + states[2][0]]50    for _ in range(levels):51        states = [apply_rows(rows, v) for v in states]52        hexes.append(states[1][0])53        tris.append(states[0][0] + states[2][0])54    return hexes, tris555657def layer_counts(b, c, level):58    dist = {0: 1}59    nz = [(s, v) for s, v in enumerate(c) if v]60    for k in range(level):61        shift = b ** k62        nxt = {}63        for s0, v0 in dist.items():64            for s1, v1 in nz:65                key = s0 + s1 * shift66                nxt[key] = nxt.get(key, 0) + v0 * v167        dist = nxt68    m = (3 * b ** level - 1) // 269    return dist.get(m - 1, 0), dist.get(m, 0) + dist.get(m - 2, 0)707172def substitution(b, c, levels=9):73    hexes, tris = tile_counts(b, c, levels)74    m00, m10 = hexes[1], tris[1]75    denom = tris[1]76    m01 = Fraction(hexes[2] - m00 * hexes[1], denom)77    m11 = Fraction(tris[2] - m10 * hexes[1], denom)78    mat = [[Fraction(m00), m01], [Fraction(m10), m11]]79    ok = all(x.denominator == 1 for row in mat for x in row)80    for n in range(1, levels + 1):81        ph = mat[0][0] * hexes[n - 1] + mat[0][1] * tris[n - 1]82        pt = mat[1][0] * hexes[n - 1] + mat[1][1] * tris[n - 1]83        if ph != hexes[n] or pt != tris[n]:84            ok = False85    ints = [[int(x) for x in row] for row in mat] if ok else None86    return ints, hexes, tris, ok878889def spectral(tr, det, b):90    lam = (mpf(tr) + msqrt(mpf(tr) ** 2 - 4 * det)) / 291    return lam, mlog(lam) / mlog(b)929394def side(tr, det, fill, b):95    q = Fraction(fill, b)96    disc = Fraction(tr * tr - 4 * det)97    u = Fraction(tr) - 2 * q98    if u > 0:99        return 1100    if u == 0:101        return 1 if disc > 0 else 0102    val = disc - u * u103    return 1 if val > 0 else (0 if val == 0 else -1)104105106def closed_form(b):107    if b % 4 == 3:108        return [109            [Fraction(3 * (b + 1) * (3 * b - 1), 16), Fraction((b + 1) * (b + 5), 32)],110            [Fraction(3 * (b + 1) ** 2, 8), Fraction(3 * (b + 1) ** 2, 16)],111        ]112    return [113        [Fraction(3 * b * b + 6 * b + 7, 16), Fraction(3 * (b - 1) * (b + 3), 32)],114        [Fraction(3 * (b - 1) * (3 * b + 5), 8), Fraction((b + 3) ** 2, 16)],115    ]116117118def rule_text(tr, det):119    return "x%d %s%d" % (tr, "+" if -det >= 0 else "-", abs(det))120121122def report(b, rule, levels=9):123    c = digit_sums(b, rule)124    fill = sum(c)125    mat, hexes, tris, ok = substitution(b, c, levels)126    tr = mat[0][0] + mat[1][1]127    det = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]128    lam, dim = spectral(tr, det, b)129    solid = mlog(mpf(fill)) / mlog(b)130    return {131        "b": b,132        "rule": rule,133        "fill": fill,134        "cells": b ** 3,135        "matrix": mat,136        "closes": ok,137        "trace": tr,138        "det": det,139        "recurrence": rule_text(tr, det),140        "lam": lam,141        "dim": dim,142        "solid": solid,143        "minus_one": solid - 1,144        "hexes": hexes,145        "tris": tris,146        "side": side(tr, det, fill, b),147    }148149150def main():151    print("SLICE GRAMMAR, ODD BASES, AT MOST ONE ODD COORDINATE")152    bases = [3, 5, 7, 9]153    rows = {b: report(b, "odd") for b in bases}154    for b in bases:155        r = rows[b]156        print(157            "b=%d fill=%d/%d matrix=%s closes=%s rule=%s dim_slice=%.4f d-1=%.4f"158            % (159                b,160                r["fill"],161                r["cells"],162                r["matrix"],163                r["closes"],164                r["recurrence"],165                float(r["dim"]),166                float(r["minus_one"]),167            )168        )169    print(170        "four rules: %s"171        % " / ".join(rows[b]["recurrence"] for b in bases)172    )173    print(174        "dimensions: %s"175        % " / ".join("%.4f" % float(rows[b]["dim"]) for b in bases)176    )177    print(178        "d-1: %s" % " / ".join("%.4f" % float(rows[b]["minus_one"]) for b in bases)179    )180181    print("")182    print("BASE THREE EXTERNAL TARGET")183    print("matrix %s" % rows[3]["matrix"])184    print("hexagons %s" % ", ".join(str(v) for v in rows[3]["hexes"][:7]))185    print("triangles %s" % ", ".join(str(v) for v in rows[3]["tris"][:7]))186187    print("")188    print("INDEPENDENT LAYER CENSUS, NO TILE REDUCTION")189    for b, top in ((3, 6), (5, 5), (7, 4), (9, 4)):190        c = digit_sums(b, "odd")191        hexes, tris = tile_counts(b, c, top)192        agree = True193        for n in range(top + 1):194            dh, dt = layer_counts(b, c, n)195            if (dh, dt) != (hexes[n], tris[n]):196                agree = False197        print("b=%d levels 0..%d agree=%s" % (b, top, agree))198199    print("")200    print("CLOSED FORM AGAINST CENSUS")201    bad = []202    for b in range(3, 22, 2):203        r = report(b, "odd")204        cf = closed_form(b)205        got = [[Fraction(x) for x in row] for row in r["matrix"]]206        if cf != got or not r["closes"]:207            bad.append(b)208    print("odd bases 3..21 matched=%d mismatched=%s" % (10 - len(bad), bad))209210    print("")211    print("MOD FOUR SPLIT")212    wrong = []213    tested = 0214    for b in range(3, 402, 2):215        r = report(b, "odd", levels=5)216        tested += 1217        want = 1 if b % 4 == 3 else -1218        if r["side"] != want or not r["closes"]:219            wrong.append(b)220    print("odd bases 3..401 tested=%d exceptions=%s" % (tested, wrong))221222    print("")223    print("BASE FIVE, THE TWO DIGIT RULES")224    odd5 = rows[5]225    mid5 = report(5, "middle")226    print(227        "middle-digit rule fills %d of %d dim_slice=%.6f d-1=%.6f excess=%+.3e"228        % (229            mid5["fill"],230            mid5["cells"],231            float(mid5["dim"]),232            float(mid5["minus_one"]),233            float(mid5["dim"] - mid5["minus_one"]),234        )235    )236    print(237        "odd-coordinate rule fills %d of %d dim_slice=%.4f d-1=%.4f excess=%+.3e"238        % (239            odd5["fill"],240            odd5["cells"],241            float(odd5["dim"]),242            float(odd5["minus_one"]),243            float(odd5["dim"] - odd5["minus_one"]),244        )245    )246    print("middle-digit matrix %s rule %s" % (mid5["matrix"], mid5["recurrence"]))247248249main()