verify.py

10.1 kB · python · 306 lines

1import cmath2import math3import sys4import time56# DESIGNS78def strings(q, F, L):9    out = [0]10    for _ in range(L):11        out = [v * q + d for v in out for d in F]12    return sorted(set(v for v in out if v > 0))1314def indigits(n, q, F):15    if n == 0:16        return False17    while n:18        if n % q not in F:19            return False20        n //= q21    return True2223def member(q, F, N):24    return [False] + [indigits(n, q, F) for n in range(1, N + 1)]2526def report(name, got, want, tol):27    ok = abs(got - want) <= tol28    print("  %-56s %.3e <= %.1e  %s" % (name, abs(got - want), tol, "ok" if ok else "FAILED"))29    assert ok, "%s: got %r, wanted %r within %r" % (name, got, want, tol)3031def exact(name, got, want):32    print("  %-56s %s  %s" % (name, want, "ok" if got == want else "FAILED"))33    assert got == want, "%s: got %r, wanted %r" % (name, got, want)3435# INVERSE3637def inverse(q, F, N):38    inside = member(q, F, N)39    assert inside[1], "1 must lie in the design"40    nu = [0] * (N + 1)41    nu[1] = 142    for n in range(2, N + 1):43        total = 044        d = 245        while d * d <= n:46            if n % d == 0:47                if inside[d]:48                    total += nu[n // d]49                e = n // d50                if e != d and inside[e]:51                    total += nu[n // e]52            d += 153        if inside[n]:54            total += nu[1]55        nu[n] = -total56    return nu, inside5758def convolve(nu, inside, N):59    out = [0] * (N + 1)60    for a in range(1, N + 1):61        if not inside[a]:62            continue63        for b in range(1, N // a + 1):64            out[a * b] += nu[b]65    return out6667def mobius(N):68    mu = [1] * (N + 1)69    prime = [True] * (N + 1)70    for p in range(2, N + 1):71        if not prime[p]:72            continue73        for m in range(p, N + 1, p):74            if m != p:75                prime[m] = False76            mu[m] = -mu[m]77        for m in range(p * p, N + 1, p * p):78            mu[m] = 079    mu[0] = 080    return mu8182def semigroup(inside, N):83    reach = [False] * (N + 1)84    reach[1] = True85    for n in range(2, N + 1):86        d = 187        while d * d <= n:88            if n % d == 0:89                if inside[d] and reach[n // d]:90                    reach[n] = True91                    break92                if inside[n // d] and reach[d]:93                    reach[n] = True94                    break95            d += 196    return reach9798def check_inverse():99    print("THE REPLACEMENT IDENTITY zeta_F N_F = 1")100    N = 3 ** 8101    nu, inside = inverse(3, [0, 1], N)102    conv = convolve(nu, inside, N)103    exact("base 3 {0,1}: (1_S * nu_F)(1) to n = 6561", conv[1], 1)104    exact("base 3 {0,1}: (1_S * nu_F)(n) = 0 for 2 <= n <= 6561", sum(abs(v) for v in conv[2:]), 0)105    reach = semigroup(inside, N)106    for n in (9, 27, 36):107        exact("base 3 {0,1}: %d in the semigroup, nu_F = 0" % n, (reach[n], nu[n]), (True, 0))108    for n in (16, 48, 52):109        exact("base 3 {0,1}: %d in the semigroup, outside S_F" % n, (reach[n], inside[n]), (True, False))110    for n in range(1, N + 1):111        assert nu[n] == 0 or reach[n], "nu_F supported outside the semigroup at n = %d" % n112    exact("base 3 {0,1}: support inside the semigroup, strictly", True, True)113    for q, L in ((2, 13), (3, 8)):114        M = q ** L115        nuf, _ = inverse(q, list(range(q)), M)116        mu = mobius(M)117        exact("base %d full digit set: nu_F = mu term for term to n = %d" % (q, M), nuf[1:] == mu[1:], True)118    return nu, inside119120# THE WALL121122def repunit(q, a):123    return (q ** a - 1) // (q - 1)124125def witness(q, F):126    missing = sorted(set(range(q)) - set(F))127    inner = [c for c in missing if c >= 2]128    if inner:129        c = min(inner)130        return "repunit", repunit(q, c), repunit(q, c + 1)131    if q % 2:132        return "odd base", 2, (q * q + 1) // 2133    return "even base", q * q - 1, q * q + 1134135def check_wall():136    print("THE WALL: the indicator is multiplicative only at the full digit set")137    tally = {"repunit": 0, "odd base": 0, "even base": 0}138    seen = blind = full = 0139    for q in range(2, 8):140        for code in range(1, 1 << q):141            F = [d for d in range(q) if code >> d & 1]142            seen += 1143            if 1 not in F:144                blind += 1145                continue146            if len(F) == q:147                full += 1148                continue149            kind, m, n = witness(q, F)150            assert math.gcd(m, n) == 1, "witness not coprime at q = %d, F = %s" % (q, F)151            fm = indigits(m, q, F)152            fn = indigits(n, q, F)153            fmn = indigits(m * n, q, F)154            assert fmn != (fm and fn), "witness failed at q = %d, F = %s, pair (%d, %d)" % (q, F, m, n)155            tally[kind] += 1156    exact("nonempty digit sets examined, 2 <= q <= 7", seen, 246)157    exact("sets with 1 outside F, killed at f(1)", blind, 120)158    exact("full digit sets, multiplicative", full, 6)159    exact("sets witnessed, by branch", tally, {"repunit": 114, "odd base": 3, "even base": 3})160    exact("base 3, F = {0,1}: the constructed pair", witness(3, [0, 1]), ("repunit", 4, 13))161    exact("base 2, F = {1}: the constructed pair", witness(2, [1]), ("even base", 3, 5))162    exact("base 12, F = {1}: the constructed pair", witness(12, [1]), ("repunit", 13, 157))163164# THE POSITION PRODUCT165166def position(q, F, L):167    Q = q ** L168    root = [cmath.exp(-2j * math.pi * j / Q) for j in range(Q)]169    grid = []170    for a in range(Q):171        z = complex(1)172        for i in range(L):173            w = 0j174            for d in F:175                w += root[(-d * (q ** i) * a) % Q]176            z *= w177        grid.append(z)178    live = set(strings(q, F, L))179    if 0 in F:180        live.add(0)181    worst = 0.0182    for n in range(Q):183        acc = 0j184        step = (n % Q)185        idx = 0186        for a in range(Q):187            acc += grid[a] * root[idx]188            idx += step189            if idx >= Q:190                idx -= Q191        acc /= Q192        want = 1.0 if n in live else 0.0193        worst = max(worst, abs(acc - want))194    return worst195196def check_position():197    print("THE POSITION PRODUCT: int G_L(t) e(-nt) dt is the indicator of the level")198    report("base 3 {0,1}, L = 5, every n < 243", position(3, [0, 1], 5), 0.0, 1e-10)199    report("base 10 missing 9, L = 3, every n < 1000", position(10, list(range(9)), 3), 0.0, 1e-9)200201# THE DESIGN ZETA202203def design_zeta(q, F, s, J=260, cut=14.0):204    k = len(F)205    nonzero = [a for a in F if a > 0]206    gamma = [float(k)] + [float(sum(a ** l for a in F)) for l in range(1, J + 1)]207    depth = 1208    while q ** depth < 5000:209        depth += 1210    small = strings(q, F, depth)211    value = {}212    for j in range(J, -1, -1):213        w = s + j214        if w.real >= cut:215            value[j] = sum(complex(n) ** (-w) for n in small)216            continue217        acc = sum(complex(a) ** (-w) for a in nonzero)218        binom = complex(1)219        for l in range(1, J - j + 1):220            binom = binom * (-w - (l - 1)) / l221            acc += binom * q ** (-w - l) * gamma[l] * value[j + l]222        value[j] = acc / (1.0 - k * q ** (-w))223    return value[0]224225def check_zeta():226    print("THE DESIGN ZETA: the peel evaluator, then the two boxed zeros")227    for q, F, L, s in ((3, [0, 1], 15, 2.0), (10, list(range(9)), 5, 2.0)):228        k = len(F)229        brute = sum(float(n) ** (-s) for n in strings(q, F, L))230        tail = k * (k * q ** (-s)) ** L / (1.0 - k * q ** (-s))231        got = design_zeta(q, F, complex(s, 0.0))232        report("base %d, k = %d: peel against the direct sum at s = 2" % (q, k), got.real, brute, tail)233    rho3 = complex(0.720790, 28.605680)234    rho10 = complex(1.001590, 2.739200)235    at3 = abs(design_zeta(3, [0, 1], rho3))236    off3 = abs(design_zeta(3, [0, 1], rho3 + 1j))237    at10 = abs(design_zeta(10, list(range(9)), rho10))238    off10 = abs(design_zeta(10, list(range(9)), rho10 + 1j))239    print("  base 3 {0,1}:      |zeta_F| = %.3e at the box centre, %.3e one unit up" % (at3, off3))240    print("  base 10 missing 9: |zeta_F| = %.3e at the box centre, %.3e one unit up" % (at10, off10))241    assert at3 < 1e-3 < off3, "base 3 box centre is not small against its control"242    assert at10 < 1e-3 < off10, "base 10 box centre is not small against its control"243    exact("both centres below 1e-3, both controls above", True, True)244    a = 2245    base, scaled = [0, 1], [0, 2]246    left = set(strings(3, scaled, 9))247    right = set(a * n for n in strings(3, base, 9))248    exact("base 3: S_{2F} = 2 S_F below 3^9", left == right, True)249    probe = complex(0.6, 3.1)250    report("base 3: zeta_{0,2}(s) = 2^(-s) zeta_{0,1}(s)", design_zeta(3, scaled, probe), complex(a) ** (-probe) * design_zeta(3, base, probe), 1e-11)251252# THE LYNDON PRODUCT253254def lyndon(k, L):255    total = 0256    for d in range(1, L + 1):257        if L % d == 0:258            total += moebius_int(d) * k ** (L // d)259    assert total % L == 0, "Lyndon count is not an integer at k = %d, L = %d" % (k, L)260    return total // L261262def moebius_int(n):263    result, d = 1, 2264    while d * d <= n:265        if n % d == 0:266            n //= d267            if n % d == 0:268                return 0269            result = -result270        d += 1271    return -result if n > 1 else result272273def check_lyndon():274    print("THE LYNDON PRODUCT: the free monoid on F factors over its Lyndon words")275    top = 16276    for k in (2, 3, 4, 9, 10):277        series = [1] + [0] * top278        for L in range(1, top + 1):279            c = lyndon(k, L)280            factor = [0] * (top + 1)281            for j in range(0, top // L + 1):282                factor[j * L] = math.comb(c + j - 1, j)283            fresh = [0] * (top + 1)284            for i in range(top + 1):285                if not series[i]:286                    continue287                for j in range(0, top + 1 - i, L):288                    fresh[i + j] += series[i] * factor[j]289            series = fresh290        want = [k ** i for i in range(top + 1)]291        exact("k = %2d: the Lyndon product is 1/(1 - k u) through u^%d" % (k, top), series, want)292    exact("c_2(L), L = 1..10, is A001037", [lyndon(2, L) for L in range(1, 11)], [2, 1, 2, 3, 6, 9, 18, 30, 56, 99])293294# VERIFY295296def main():297    clock = time.time()298    check_wall()299    check_inverse()300    check_position()301    check_lyndon()302    check_zeta()303    print("all checks green in %.1f seconds" % (time.time() - clock))304305if __name__ == "__main__":306    main()