census.py

7.5 kB · python · 257 lines

1import subprocess2import sys3import time4from fractions import Fraction5from math import ceil, floor, log678def in_gasket(x, y, b):9    while x or y:10        u, v = x % b, y % b11        if u > 1 or v > 1 or (u and v):12            return False13        x //= b14        y //= b15    return True161718def direct(m):19    n = 3 ** m20    c = 021    for x in range(n):22        for y in range(n):23            if in_gasket(x, y, 2) and in_gasket(x, y, 3):24                c += 125    return c262728def count(m):29    pw = [3 ** i for i in range(m + 1)]30    bl = [p.bit_length() for p in pw]31    total = 03233    def walk(k, x, y):34        nonlocal total35        if k == 0:36            if x & y == 0:37                total += 138            return39        b = bl[k]40        hx = x >> b41        hy = y >> b42        if (hx & hy) and (hx & (hy + 1)) and ((hx + 1) & hy) and ((hx + 1) & (hy + 1)):43            return44        i = k - 145        p = pw[i]46        walk(i, x, y)47        walk(i, x + p, y)48        walk(i, x, y + p)4950    walk(m, 0, 0)51    return total525354def axis(m):55    return 2 ** (m + 1) - 1565758def terms(lo, hi):59    print("m  C(3^m)        2^(m+1)-1   C/prev    log_3(C)/m  sec")60    prev = None61    for m in range(lo, hi + 1):62        t0 = time.time()63        c = count(m)64        assert c >= axis(m)65        r = c / prev if prev else float("nan")66        e = log(c, 3) / m if m else float("nan")67        print("%-2d %-13d %-11d %-9.5f %-11.6f %.1f" % (m, c, axis(m), r, e, time.time() - t0))68        sys.stdout.flush()69        prev = c707172def control(hi):73    print("m  direct  pruned  axis  agree")74    for m in range(hi + 1):75        d = direct(m)76        c = count(m)77        print("%-2d %-7d %-7d %-5d %s" % (m, d, c, axis(m), d == c and c >= axis(m)))78        sys.stdout.flush()798081def down(x, k=6):82    return floor(x * 10 ** k) / 10 ** k838485def up(x, k=6):86    return ceil(x * 10 ** k) / 10 ** k878889def budget():90    da = log(3, 2)91    db = 1.092    ax = log(2, 3)93    print("dim A = log_2 3 = %.6f" % da)94    print("dim B = log_3 3 = %.6f" % db)95    print("budget dim A + dim B - 2 <= %.6f" % up(da + db - 2))96    print("axis exponent log_3 2 >= %.6f" % down(ax))97    print("excess >= %.6f" % down(ax - up(da + db - 2)))9899100def series(hi):101    return [count(m) for m in range(hi + 1)]102103104def bareiss(rows):105    n = len(rows)106    a = [row[:] for row in rows]107    sign = 1108    prev = 1109    for k in range(n - 1):110        if a[k][k] == 0:111            p = next((i for i in range(k + 1, n) if a[i][k]), None)112            if p is None:113                return 0114            a[k], a[p] = a[p], a[k]115            sign = -sign116        for i in range(k + 1, n):117            for j in range(k + 1, n):118                a[i][j] = (a[i][j] * a[k][k] - a[i][k] * a[k][j]) // prev119            a[i][k] = 0120        prev = a[k][k]121    return sign * a[n - 1][n - 1]122123124def solve(rows, rhs, r):125    aug = [[Fraction(v) for v in rows[i]] + [Fraction(rhs[i])] for i in range(len(rows))]126    piv = []127    row = 0128    for col in range(r):129        p = next((i for i in range(row, len(aug)) if aug[i][col]), None)130        if p is None:131            continue132        aug[row], aug[p] = aug[p], aug[row]133        d = aug[row][col]134        aug[row] = [v / d for v in aug[row]]135        for i in range(len(aug)):136            if i != row and aug[i][col]:137                f = aug[i][col]138                aug[i] = [aug[i][j] - f * aug[row][j] for j in range(r + 1)]139        piv.append(col)140        row += 1141    for i in range(row, len(aug)):142        if aug[i][r]:143            return "none", None144    c = [Fraction(0)] * r145    for i, col in enumerate(piv):146        c[col] = aug[i][r]147    return ("unique" if len(piv) == r else "free"), c148149150def charpoly_string(c):151    parts = ["x^%d" % len(c)]152    for i, v in enumerate(c):153        parts.append("- (%s)*x^%d" % (v, len(c) - 1 - i))154    return " ".join(parts)155156157def dominant_root(c):158    p = charpoly_string(c)159    out = subprocess.run(["gp", "-q"], input="v=polroots(%s); vecmax(abs(v))\n" % p,160                         capture_output=True, text=True)161    return out.stdout.strip()162163164def hankel(hi, rmax):165    a = series(hi)166    print("terms C(3^m), m = 0..%d" % hi)167    print(",".join(str(v) for v in a))168    print()169    print("k  det H_k (entries a[i+j], i,j < k)")170    for k in range(1, rmax + 2):171        if 2 * k - 1 > len(a):172            break173        print("%-2d %d" % (k, bareiss([[a[i + j] for j in range(k)] for i in range(k)])))174    print()175    print("r  eqs spare fit    coefficients c_1..c_r of a(n) = sum c_i a(n-i), n >= r")176    found = []177    for r in range(1, rmax + 1):178        rows = [[a[n - 1 - j] for j in range(r)] for n in range(r, len(a))]179        rhs = [a[n] for n in range(r, len(a))]180        st, c = solve(rows, rhs, r)181        print("%-2d %-3d %-5d %-6s %s" % (r, len(rows), len(rows) - r, st,182                                          "" if c is None else " ".join(str(v) for v in c)))183        if st != "none":184            found.append((r, c))185    print()186    if not found:187        print("no linear recurrence with constant coefficients of order r <= %d fits all %d terms"188              % (rmax, len(a)))189        print("last reading log_3 C(3^%d) / %d = %.6f" % (hi, hi, log(a[hi], 3) / hi))190        return191    for r, c in found:192        print("order %d characteristic polynomial %s" % (r, charpoly_string(c)))193        rho = dominant_root(c)194        print("order %d dominant root %s" % (r, rho))195        try:196            print("order %d log(root)/log(3) = %.6f" % (r, log(float(rho), 3)))197        except ValueError:198            print("order %d dominant root is not positive real" % r)199        print("order %d last reading log_3 C(3^%d) / %d = %.6f" % (r, hi, hi, log(a[hi], 3) / hi))200201202def first_order(a, rmax):203    for r in range(1, rmax + 1):204        rows = [[a[n - 1 - j] for j in range(r)] for n in range(r, len(a))]205        rhs = [a[n] for n in range(r, len(a))]206        st, c = solve(rows, rhs, r)207        if st != "none":208            return r, c209    return 0, None210211212def selftest():213    ok = True214    print("%-26s %-26s %-26s %s" % ("check", "expect", "got", "pass"))215216    def report(name, want, got):217        nonlocal ok218        hit = str(want) == str(got)219        ok = ok and hit220        print("%-26s %-26s %-26s %s" % (name, want, got, hit))221222    report("bareiss 1x1", 3, bareiss([[3]]))223    report("bareiss 2x2", -2, bareiss([[1, 2], [3, 4]]))224    report("bareiss 2x2 pivot swap", -6, bareiss([[0, 2], [3, 4]]))225    report("bareiss 2x2 singular", 0, bareiss([[1, 2], [2, 4]]))226    report("bareiss 3x3", 4, bareiss([[2, 1, 0], [1, 2, 1], [0, 1, 2]]))227    fib = [0, 1]228    while len(fib) < 20:229        fib.append(fib[-1] + fib[-2])230    r, c = first_order(fib, 6)231    report("fibonacci first order", 2, r)232    report("fibonacci charpoly", "x^2 - (1)*x^1 - (1)*x^0", charpoly_string(c))233    mix = [2 ** n + 3 ** n + 1 for n in range(20)]234    r, c = first_order(mix, 6)235    report("2^n + 3^n + 1 first order", 3, r)236    report("2^n + 3^n + 1 dominant root", "3.0000000", dominant_root(c)[:9])237    cube = [n ** 3 + 2 ** n for n in range(20)]238    r, c = first_order(cube, 8)239    report("n^3 + 2^n first order", 5, r)240    report("n^3 + 2^n charpoly", "x^5 - (6)*x^4 - (-14)*x^3 - (16)*x^2 - (-9)*x^1 - (2)*x^0",241           charpoly_string(c))242    print()243    print("all pass %s" % ok)244245246if __name__ == "__main__":247    v = sys.argv[1] if len(sys.argv) > 1 else "budget"248    if v == "terms":249        terms(int(sys.argv[2]), int(sys.argv[3]))250    elif v == "control":251        control(int(sys.argv[2]))252    elif v == "hankel":253        hankel(int(sys.argv[2]), int(sys.argv[3]) if len(sys.argv) > 3 else 12)254    elif v == "selftest":255        selftest()256    else:257        budget()