conduction.py

11.0 kB · python · 290 lines

1import math2import sys3from fractions import Fraction4import time56import numpy as np7import scipy.sparse as sp8import scipy.sparse.linalg as spl910R3 = math.sqrt(3.0)11SIGMA = 1.0 / R31213# RENDER1415def odd_digits(n, level, k=1):16    i = np.arange(n ** level)17    out = []18    for _ in range(level):19        out.append((i % n) % 2 == 1)20        i = i // n21    out = np.array(out)22    if k > 1:23        out = np.repeat(out, k, axis=1)24    return out2526def render(n, level, k=1):27    o = odd_digits(n, level, k)28    void = np.zeros((o.shape[1], o.shape[1]), bool)29    for row in o:30        void |= np.outer(row, row)31    return ~void3233def obnosov(z):34    return math.sqrt((1 + 3 * z) / (3 + z))3536def fill(n):37    return n * n - ((n - 1) // 2) ** 23839# NETWORK4041def pair(a, b):42    s = a + b43    return np.where(s > 0, 2 * a * b / np.where(s > 0, s, 1), 0.0)4445def solve(n_nodes, rows, cols, w, diag, rhs):46    A = sp.coo_matrix((-w, (rows, cols)), shape=(n_nodes, n_nodes))47    A = (A + A.T + sp.diags(diag)).tocsc()48    return spl.spsolve(A, rhs, permc_spec="COLAMD")4950def network(a, wx, wy, left, right, right_value):51    nx, ny = a.shape52    live = a > 053    idx = -np.ones(a.shape, np.int64)54    idx[live] = np.arange(live.sum())55    n_nodes = int(live.sum())56    rows, cols, ws = [], [], []57    gx = pair(a[:-1, :], a[1:, :]) * wx[:-1, :]58    gy = pair(a[:, :-1], a[:, 1:]) * wy[:, :-1]59    for g, i0, i1 in ((gx, idx[:-1, :], idx[1:, :]), (gy, idx[:, :-1], idx[:, 1:])):60        m = g > 061        rows.append(i0[m])62        cols.append(i1[m])63        ws.append(g[m])64    rows, cols, ws = np.concatenate(rows), np.concatenate(cols), np.concatenate(ws)65    diag = np.bincount(rows, ws, n_nodes) + np.bincount(cols, ws, n_nodes)66    rhs = np.zeros(n_nodes)67    lm = live[0, :]68    diag[idx[0, lm]] += left[lm]69    rm = live[-1, :] & (right > 0)70    diag[idx[-1, rm]] += right[rm]71    rhs[idx[-1, rm]] += right[rm] * right_value72    u = solve(n_nodes, rows, cols, ws, diag, rhs)73    return float(np.sum(left[lm] * u[idx[0, lm]]))7475def conductance_full(a):76    a = np.asarray(a, float)77    one = np.ones(a.shape)78    return network(a, one, one, 2 * a[0, :], 2 * a[-1, :], 1.0)7980def conductance(a):81    a = np.asarray(a, float)82    s = a.shape[0]83    h = (s + 1) // 284    q = a[:h, :h].copy()85    wx = np.ones(q.shape)86    wy = np.ones(q.shape)87    rw = np.ones(h)88    if s % 2:89        wx[:, -1] = 0.590        rw[-1] = 0.591        center = q[-1, :] > 092        left = 2 * q[0, :] * rw93        g = pair(q[-2, :], q[-1, :]) * rw94        q2 = q[:-1, :]95        wx2, wy2 = wx[:-1, :], wy[:-1, :]96        right = np.where(center, g, 0.0)97        return 2 * network(q2, wx2, wy2, left, right, 0.5)98    left = 2 * q[0, :]99    right = 2 * q[-1, :]100    return 2 * network(q, wx, wy, left, right, 0.5)101102def rich(g1, g2, r=2.0, p=4.0 / 3.0):103    return g2 + (g2 - g1) / (r ** p - 1)104105def aitken(g1, g2, g3, r=2.0):106    e1, e2 = g2 - g1, g3 - g2107    p = math.log(e1 / e2) / math.log(r)108    return rich(g2, g3, r, p), p109110def level(n, lv, k):111    return conductance(render(n, lv, k).astype(float))112113# CELL114115def cell(m, z):116    a = np.ones((2 * m, 2 * m))117    a[m:, m:] = z118    return conductance_full(a)119120def cell3(m, rtol=1e-11):121    t = np.arange(2 * m) >= m122    up = t[:, None, None].astype(int) + t[None, :, None] + t[None, None, :]123    live = up <= 1124    s = 2 * m125    idx = -np.ones(live.shape, np.int64)126    n_nodes = int(live.sum())127    idx[live] = np.arange(n_nodes)128    rows, cols = [], []129    for ax in range(3):130        a = [slice(None)] * 3131        b = [slice(None)] * 3132        a[ax] = slice(0, -1)133        b[ax] = slice(1, None)134        both = live[tuple(a)] & live[tuple(b)]135        rows.append(idx[tuple(a)][both])136        cols.append(idx[tuple(b)][both])137    rows, cols = np.concatenate(rows), np.concatenate(cols)138    w = np.ones(len(rows))139    diag = np.bincount(rows, w, n_nodes) + np.bincount(cols, w, n_nodes)140    left = idx[0][live[0]]141    right = idx[-1][live[-1]]142    diag[left] += 2.0143    diag[right] += 2.0144    rhs = np.zeros(n_nodes)145    rhs[right] = 2.0146    A = sp.coo_matrix((-w, (rows, cols)), shape=(n_nodes, n_nodes))147    A = (A + A.T + sp.diags(diag)).tocsr()148    x0 = (np.indices(live.shape)[0][live] + 0.5) / s149    u, info = spl.cg(A, rhs, x0=x0, M=sp.diags(1.0 / diag), rtol=rtol, maxiter=20000)150    assert info == 0151    return 2.0 * float(np.sum(u[left])) / s152153def verb_cell():154    ms = (64, 128, 256, 512)155    g = [cell(m, 0.0) for m in ms]156    for i, m in enumerate(ms):157        line = f"insulating M={m} sigma={g[i]:.10f} err={g[i] - SIGMA:+.3e}"158        if i:159            line += f" order={math.log((g[i - 1] - SIGMA) / (g[i] - SIGMA)) / math.log(2):.4f} rich={rich(g[i - 1], g[i]):.10f}"160        print(line)161    e1, e2 = rich(g[1], g[2]), rich(g[2], g[3])162    bar = abs(e2 - e1)163    print(f"insulating sigma={e2:.10f} bar={bar:.1e} target={SIGMA:.10f} off={e2 - SIGMA:+.1e}")164    assert abs(e2 - SIGMA) < 2 * bar + 1e-9165    zs = (0.01, 1 / 9, 1 / 3, 3.0, 9.0, 100.0)166    val = {}167    for z in zs:168        h = [cell(m, z) for m in (64, 128, 256)]169        val[z], p = aitken(*h)170        print(f"contrast z={z:.6g} sigma={val[z]:.9f} order={p:.4f} obnosov={obnosov(z):.9f} off={val[z] - obnosov(z):+.1e}")171        assert abs(val[z] - obnosov(z)) < 6e-8172    for z in (0.01, 1 / 9, 1 / 3):173        prod = val[z] * val[1 / z if z != 1 / 3 else 3.0]174        print(f"keller z={z:.6g} sigma(z)*sigma(1/z)={prod:.9f}")175        assert abs(prod - 1) < 7e-8176    big = 1e8177    h = [cell(m, big) for m in (64, 128, 256)]178    s_inf, p = aitken(*h)179    print(f"conductor z=1e8 sigma={s_inf:.9f} order={p:.4f} sqrt3={R3:.9f} keller={e2 * s_inf:.9f}")180181def verb_side():182    for n in (11, 21, 41, 81):183        row = []184        for k in (4, 8, 16):185            row.append(n * (level(n, 1, k) - cell(k // 2, 0.0)))186        print(f"level1 N={n} N*(sigma_k(N,1)-sigma_k(inf,1)) k=4,8,16: " + " ".join(f"{x:.5f}" for x in row) + f" sigma(N,1)~{SIGMA + row[-1] / n:.6f}")187    for n in (5, 7, 9, 11, 15, 21, 31, 41):188        row = []189        for k in (1, 2, 4):190            if k * n * n > 1800 and k > 1:191                break192            row.append(level(n, 1, k) / level(n, 2, k))193        print(f"ratio N={n} sigma(N,1)/sigma(N,2) k=1,2,4: " + " ".join(f"{x:.5f}" for x in row) + f" N*(sqrt3-ratio)={n * (R3 - row[-1]):.4f}")194195def up(x):196    e = math.floor(math.log10(x))197    return f"{math.ceil(x / 10 ** e)}e{e}" if math.ceil(x / 10 ** e) < 10 else f"1e{e + 1}"198199def bounds(n):200    beta = (n + 1) / (2 * n) + (n - 1) / (n + 1)201    alpha = 2 * n / (n + 1)202    return beta, alpha203204def ratios(n, k, cap):205    g = [1.0]206    lv = 1207    while k * n ** (lv + 1) <= cap:208        if lv == 1:209            g.append(level(n, 1, k))210        g.append(level(n, lv + 1, k))211        lv += 1212    return [g[i] / g[i + 1] for i in range(1, len(g) - 1)]213214def verb_scale():215    rows = []216    for n in (3, 5, 7, 9, 11):217        beta, alpha = bounds(n)218        assert Fraction(3 * n * n + 1, 2 * n * (n + 1)) == Fraction(n + 1, 2 * n) + Fraction(n - 1, n + 1)219        seq = {k: ratios(n, k, 3200 if k == 1 else 2700) for k in (1, 2)}220        for k in (1, 2):221            print(f"scale N={n} k={k} rho(N,L) L=1..{len(seq[k])}: " + " ".join(f"{x:.5f}" for x in seq[k]))222            assert all(beta <= x <= alpha for x in seq[k])223        j = len(seq[2]) - 1224        mesh = abs(seq[1][j] - seq[2][j])225        kk = min((1, 2), key=lambda k: abs(seq[k][-1] - seq[k][-2]))226        drift = abs(seq[kk][-1] - seq[kk][-2])227        rho = seq[kk][-1]228        bar = max(mesh, drift)229        m = fill(n)230        dw = math.log(m * rho) / math.log(n)231        dbar = math.log((rho + bar) / rho) / math.log(n)232        law = 2 + math.log(3 * R3 / 4) / math.log(n)233        archie = math.log(rho) / math.log(n * n / m)234        print(f"table N={n} k={kk} rho={rho:.5f} mesh={mesh:.1e} drift={drift:.1e} bar={bar:.1e} dw_bar={dbar:.1e} bounds=[{beta:.5f}, {alpha:.5f}] dw={dw:.4f} law={law:.4f} (dw-2)logN={(dw - 2) * math.log(n):.4f} archie={archie:.4f} drift_k1={seq[1][0] - seq[1][-1]:.2e} drift_k2={seq[2][0] - seq[2][-1]:.2e} lower={(math.log(m * beta / n ** 2)):.4f} upper={(math.log(m * alpha / n ** 2)):.4f} archie_gap={math.log(3) / math.log(16 / 9) - archie:.4f} excess={(n - 1) ** 3 / (8 * n ** 3):.5f} dw-law={dw - law:.4f}")235        rows.append(f"| {n} | {kk} | " + ", ".join(f"{x:.5f}" for x in seq[kk]) + f" | `{rho:.5f} +- {up(bar)}` | `[{Fraction(3 * n * n + 1, 2 * n * (n + 1))}, {Fraction(2 * n, n + 1)}]` | `{dw:.4f} +- {up(dbar)}` | {law:.4f} |")236    print("\n".join(rows))237    print(f"limits sqrt3={R3:.5f} bounds=[1.5, 2] (dw-2)logN->{math.log(3 * R3 / 4):.4f} in [{math.log(9 / 8):.4f}, {math.log(1.5):.4f}] archie->{math.log(3) / math.log(16 / 9):.4f}")238239def verb_dim3():240    ms = (8, 16, 32, 64)241    g = [cell3(m) for m in ms]242    for m, x in zip(ms, g):243        print(f"dim3 M={m} sigma={x:.7f}")244    a1, p1 = aitken(*g[:3])245    a2, p2 = aitken(*g[1:])246    r2 = rich(g[2], g[3])247    bar = max(abs(a2 - a1), abs(a2 - r2))248    expo = math.log(1 / a2) / math.log(2)249    print(f"dim3 aitken(8,16,32)={a1:.6f} order={p1:.4f} aitken(16,32,64)={a2:.6f} order={p2:.4f} rich(32,64)={r2:.6f}")250    print(f"dim3 sigma={a2:.5f} bar={bar:.1e} exponent=log(1/sigma)/log2={expo:.4f} band=[{math.log(1 / (a2 - bar)) / math.log(2):.4f}, {math.log(1 / (a2 + bar)) / math.log(2):.4f}]")251252def verb_rate():253    ns = (9, 11, 15, 21, 31, 41)254    y = np.array([level(n, 1, 2) / level(n, 2, 2) for n in ns])255    x = np.array(ns, float)256    A = np.stack([np.ones_like(x), -1 / x, 1 / x ** (4 / 3)], axis=1)257    c, *_ = np.linalg.lstsq(A, y, rcond=None)258    res = y - A @ c259    print("rate N=" + ",".join(map(str, ns)) + " rho(N,1) k=2: " + " ".join(f"{v:.5f}" for v in y))260    print("rate N*(sqrt3-rho(N,1)): " + " ".join(f"{n * (R3 - v):.4f}" for n, v in zip(ns, y)))261    print(f"rate fit rho(N,1) = r - a/N + b/N^(4/3): r={c[0]:.4f} a={c[1]:.3f} b={c[2]:.3f} max residual={np.abs(res).max():.1e} sqrt3={R3:.4f}")262    for q in (5 / 4, 4 / 3, 3 / 2):263        A2 = np.stack([-1 / x, 1 / x ** q], axis=1)264        c2, *_ = np.linalg.lstsq(A2, y - R3, rcond=None)265        print(f"rate fit rho(N,1) = sqrt3 - a/N + b/N^{q:.4g}: a={c2[0]:.3f} b={c2[1]:.3f} max residual={np.abs(y - R3 - A2 @ c2).max():.1e}")266267def verb_dual():268    for n, lv in ((5, 1), (3, 2)):269        ext, bars = {}, {}270        for z in (0.0, 1e8):271            g = []272            for k in (8, 16, 32, 64):273                m = render(n, lv, k)274                g.append(conductance(np.where(m, 1.0, z)))275            ext[z], p = aitken(*g[1:])276            bars[z] = abs(ext[z] - aitken(*g[:3])[0])277            print(f"dual N={n} L={lv} z={z:.0e} k=8,16,32,64: " + " ".join(f"{x:.7f}" for x in g) + f" aitken={ext[z]:.7f} order={p:.3f} bar={bars[z]:.1e}")278        prod = ext[0.0] * ext[1e8]279        bar = bars[0.0] * ext[1e8] + bars[1e8] * ext[0.0]280        print(f"dual N={n} L={lv} sigma(0)*sigma(inf)={prod:.7f} bar={bar:.1e}")281        assert abs(prod - 1) < bar282283VERBS = {"cell": verb_cell, "dual": verb_dual, "side": verb_side, "rate": verb_rate, "scale": verb_scale, "dim3": verb_dim3}284285if __name__ == "__main__":286    names = sys.argv[1:] or list(VERBS)287    for name in names:288        t = time.time()289        VERBS[name]()290        print(f"{name} {time.time() - t:.1f} s", flush=True)