dispersion.py — src/22-quantum

The script src/22-quantum/dispersion.py of the validation package of the paper 22-qm, rendered with an anchor per line. ▶ run the predicates on Google Colab.

22-quantum ledgersrc/22-quantum/dispersion.py · 10.3 KBdispersion.pyColab
1## T2: exact boost transport / dispersion nullity on a finite shell.2## Construction follows the Schrodinger-Dirac companion (gammas over K = F_{p^2},3## boost subgroup G_nu < O(Q_nu, F_p), spin lift S(x,y) = xI + y*gamma^0*gamma^1).4## Claims verified by exact arithmetic in F_13 / F_169:5##   (D1) Clifford relations  G^mu G^nu + G^nu G^mu = 2 eta^{mu nu} I, eta = diag(-nu,1,1,1);6##   (D2) spin conjugation identities for ALL 156 boosts (x,y), delta = x^2-nu*y^2 != 0;7##   (D3) boost group law Lambda(x,y)Lambda(u,v) = Lambda(xu+nu yv, xv+yu), exhaustive;8##   (D4) transported Clifford relations for all boosts;9##   (D5) exact covariance D_{Lambda,S} T psi = T D psi on random spinor fields,10##        for the ENTIRE norm-one boost cycle (all p+1 = 14 elements);11##   (D6) Dirac-to-Klein-Gordon factorisation, standard and transported frames;12##   (D7) full-cycle closure: the composed transport around the boost cycle is the13##        identity -- zero accumulated deformation, hence no dispersion at any order.14import numpy as np1516p, nu = 13, 217assert all(pow(s, 2, p) != nu % p for s in range(p)), 'nu must be a nonsquare'18ip = min(x for x in range(2, p) if (x*x) % p == p-1)        # internal i, 5^2 = -11920def report(label, ok):21    print(('PASS ' if ok else 'FAIL ') + label)22    assert ok, label2324# ---- K = F_{p^2} arithmetic on (..., 2) int arrays: z = a + b*c, c^2 = nu ----25def kmul(z, w):26    a, b = z[..., 0], z[..., 1]; cc, d = w[..., 0], w[..., 1]27    return np.stack([(a*cc + nu*b*d) % p, (a*d + b*cc) % p], axis=-1)28def kadd(z, w): return (z + w) % p29def ksub(z, w): return (z - w) % p30def kscal(s, z): return np.stack([(s % p)*z[..., 0] % p, (s % p)*z[..., 1] % p], axis=-1)31def kc(a, b=0): return np.array([a % p, b % p])3233def mmul(X, Y):                       # (4,4,2) x (4,4,2) matrices over K34    Z = np.zeros((4, 4, 2), dtype=np.int64)35    for i in range(4):36        for j in range(4):37            acc = np.zeros(2, dtype=np.int64)38            for k in range(4):39                acc = kadd(acc, kmul(X[i, k], Y[k, j]))40            Z[i, j] = acc41    return Z42def mvec(X, v):                       # (4,4,2) x (...,4,2) spinor fields43    out = np.zeros_like(v)44    for i in range(4):45        acc = np.zeros(v.shape[:-2] + (2,), dtype=np.int64)46        for k in range(4):47            acc = kadd(acc, kmul(np.broadcast_to(X[i, k], v.shape[:-2] + (2,)), v[..., k, :]))48        out[..., i, :] = acc49    return out5051def minv(X):                          # inverse via adjugate over K (4x4, exact)52    import itertools53    def det(M, idx):54        n = len(idx)55        if n == 1: return M[idx[0][0], idx[0][1]].copy()56        acc = np.zeros(2, dtype=np.int64); sgn = 157        for k in range(n):58            r0, c0 = idx[0]59            rows = [rc for rc in idx[1:]]60            sub = [(r, c) for (r, c) in rows if c != idx[k][1]]61        return None62    # simpler: Gaussian elimination over K63    A = X.copy().astype(np.int64)64    I = np.zeros((4, 4, 2), dtype=np.int64)65    for i in range(4): I[i, i, 0] = 166    def kinv(z):67        a, b = int(z[0]) % p, int(z[1]) % p68        d = (a*a - nu*b*b) % p69        di = pow(d, p-2, p)70        return np.array([a*di % p, (-b*di) % p], dtype=np.int64)71    for col in range(4):72        piv = next(r for r in range(col, 4)73                   if (A[r, col] % p).any())74        if piv != col:75            A[[col, piv]] = A[[piv, col]]; I[[col, piv]] = I[[piv, col]]76        iv = kinv(A[col, col])77        for j in range(4):78            A[col, j] = kmul(A[col, j], iv) % p79            I[col, j] = kmul(I[col, j], iv) % p80        for r in range(4):81            if r != col and (A[r, col] % p).any():82                f = A[r, col].copy()83                for j in range(4):84                    A[r, j] = ksub(A[r, j], kmul(f, A[col, j]))85                    I[r, j] = ksub(I[r, j], kmul(f, I[col, j]))86    return I % p8788# ---- gamma matrices over K, eta = diag(-nu, 1, 1, 1) ----89s1 = np.array([[0, 1], [1, 0]]); s2 = np.array([[0, -ip], [ip, 0]]); s3 = np.array([[1, 0], [0, -1]])90def emb(M):                            # F_p 4x4 -> K 4x491    Z = np.zeros((4, 4, 2), dtype=np.int64); Z[..., 0] = M % p; return Z92def blk(off, diagsign, M):             # helpers for 4x4 from 2x293    Z = np.zeros((4, 4), dtype=np.int64)94    if off: Z[:2, 2:] = M; Z[2:, :2] = M95    else:   Z[:2, :2] = M; Z[2:, 2:] = diagsign*M96    return Z % p97E = [emb(blk(1, 0, s1)), emb(blk(1, 0, s2)), emb(blk(1, 0, s3)),98     emb(blk(0, -1, np.eye(2, dtype=np.int64)))]          # e1..e4, squares +I99G0 = np.zeros((4, 4, 2), dtype=np.int64)                  # G0 = c * ip * e4100G0[..., 1] = (ip * blk(0, -1, np.eye(2, dtype=np.int64))) % p101G = [G0, E[0], E[1], E[2]]                                # (G0)^2 = -nu, (Gk)^2 = +1102eta = [(-nu) % p, 1, 1, 1]103104Id = emb(np.eye(4, dtype=np.int64))105ok = True106for m in range(4):107    for n in range(4):108        anti = kadd(mmul(G[m], G[n]), mmul(G[n], G[m])) % p109        tgt = kscal(2*eta[m] if m == n else 0, Id) % p110        if not (anti == tgt).all(): ok = False111report('D1: Clifford relations, all 16 pairs, eta = diag(-nu,1,1,1)', ok)112113M = mmul(G[0], G[1])114report('D1: M^2 = nu*I', (mmul(M, M) % p == kscal(nu, Id) % p).all())115116# ---- spin conjugation sweep over ALL boosts ----117def AB(x, y):118    d = (x*x - nu*y*y) % p119    di = pow(d, p-2, p)120    return ((x*x + nu*y*y) * di) % p, (-2*x*y*di) % p121ok2, ok4, count = True, True, 0122for x in range(p):123    for y in range(p):124        if (x*x - nu*y*y) % p == 0: continue125        count += 1126        S = kadd(kscal(x, Id), kscal(y, M)) % p127        Si = minv(S)128        A, B = AB(x, y)129        c0 = mmul(Si, mmul(G[0], S)) % p130        c1 = mmul(Si, mmul(G[1], S)) % p131        t0 = kadd(kscal(A, G[0]), kscal((nu*B) % p, G[1])) % p132        t1 = kadd(kscal(B, G[0]), kscal(A, G[1])) % p133        if not ((c0 == t0).all() and (c1 == t1).all()): ok2 = False134        if not ((mmul(Si, mmul(G[2], S)) % p == G[2] % p).all() and135                (mmul(Si, mmul(G[3], S)) % p == G[3] % p).all()): ok2 = False136        # transported Clifford137        gh = [ksub(kscal(A, G[0]), kscal((nu*B) % p, G[1])) % p,138              kadd(kscal((-B) % p, G[0]), kscal(A, G[1])) % p, G[2], G[3]]139        for m in range(4):140            for n in range(4):141                anti = kadd(mmul(gh[m], gh[n]), mmul(gh[n], gh[m])) % p142                tgt = kscal(2*eta[m] if m == n else 0, Id) % p143                if not (anti == tgt).all(): ok4 = False144report('D2: spin conjugation identities, all %d boosts' % count, ok2 and count == p*p - 1)145report('D4: transported Clifford relations, all %d boosts' % count, ok4)146147# ---- boost group law, exhaustive over norm-one cycle x random others ----148def lam(x, y):149    A, B = AB(x, y)150    L = np.eye(4, dtype=np.int64)151    L[0, 0], L[0, 1], L[1, 0], L[1, 1] = A, B, (nu*B) % p, A152    return L % p153U1 = [(x, y) for x in range(p) for y in range(p) if (x*x - nu*y*y) % p == 1]154ok3 = True155for (x, y) in U1:156    for (u, v) in U1:157        L = (lam(x, y) @ lam(u, v)) % p158        R = lam((x*u + nu*y*v) % p, (x*v + y*u) % p)159        if not (L == R).all(): ok3 = False160report('D3: boost group law, exhaustive on the norm-one cycle (%d^2 products)' % len(U1),161       ok3 and len(U1) == p + 1)162163# ---- covariance on spinor fields over Y = F_p^4, full norm-one cycle ----164psi = (np.arange(p*p*p*p*4*2).reshape(p, p, p, p, 4, 2).astype(np.int64) * 7 + 3) % p   # fixed field, no RNG165ezs = np.eye(4, dtype=np.int64)166167def shift(f, vec):                      # f(z + vec)168    out = f169    for ax in range(4):170        if vec[ax] % p:171            out = np.roll(out, -int(vec[ax] % p), axis=ax)172    return out173def nabla(f, vec): return (shift(f, vec) - f) % p174def dirac(f, gams, frame):175    out = np.zeros_like(f)176    for mu in range(4):177        out = (out + mvec(gams[mu], nabla(f, frame[mu]))) % p178    return out179180def transport(f, x, y):181    L = lam(x, y); Li = lam(x, (-y) % p)           # Lambda^{-1} = Lambda(x,-y)182    idx = np.indices((p, p, p, p)).reshape(4, -1)183    src = (Li @ idx) % p184    g = f.reshape(-1, 4, 2)[np.ravel_multi_index(src, (p, p, p, p))].reshape(f.shape)185    S = kadd(kscal(x, Id), kscal(y, M)) % p186    return mvec(S, g)187188D0 = dirac(psi, G, ezs)189ok5 = True190for (x, y) in U1:191    A, B = AB(x, y)192    gh = [ksub(kscal(A, G[0]), kscal((nu*B) % p, G[1])) % p,193          kadd(kscal((-B) % p, G[0]), kscal(A, G[1])) % p, G[2], G[3]]194    L = lam(x, y)195    frame = [(L @ ezs[mu]) % p for mu in range(4)]196    lhs = dirac(transport(psi, x, y), gh, frame)197    rhs = transport(D0, x, y)198    if not (lhs % p == rhs % p).all(): ok5 = False199report('D5: exact covariance D_{Lambda,S} T psi = T D psi, all %d norm-one boosts' % len(U1), ok5)200201# ---- KG factorisation, standard and one transported frame ----202m = 7203def kg(f, frame):204    out = np.zeros_like(f)205    for mu in range(4):206        d2 = nabla(nabla(f, frame[mu]), frame[mu])207        out = (out + kscal(eta[mu], d2)) % p208    return out209lhs = dirac((dirac(psi, G, ezs) + kscal(m, psi)) % p, G, ezs)210lhs = (lhs - kscal(m, (dirac(psi, G, ezs) + kscal(m, psi)) % p)) % p211rhs = (kg(psi, ezs) - kscal((m*m) % p, psi)) % p212report('D6: (D - m)(D + m) = KG - m^2, standard frame', (lhs == rhs).all())213x, y = U1[1]214A, B = AB(x, y)215gh = [ksub(kscal(A, G[0]), kscal((nu*B) % p, G[1])) % p,216      kadd(kscal((-B) % p, G[0]), kscal(A, G[1])) % p, G[2], G[3]]217L = lam(x, y); frame = [(L @ ezs[mu]) % p for mu in range(4)]218lhs = dirac((dirac(psi, gh, frame) + kscal(m, psi)) % p, gh, frame)219lhs = (lhs - kscal(m, (dirac(psi, gh, frame) + kscal(m, psi)) % p)) % p220rhs = (kg(psi, frame) - kscal((m*m) % p, psi)) % p221report('D6: transported factorisation in the boosted frame', (lhs == rhs).all())222223# ---- full-cycle closure: composed transport around the boost cycle = identity ----224zgen = next((x, y) for (x, y) in U1 if y != 0)225xg, yg = zgen226cur = (1, 0); order = 0227while True:228    cur = ((cur[0]*xg + nu*cur[1]*yg) % p, (cur[0]*yg + cur[1]*xg) % p)229    order += 1230    if cur == (1, 0): break231f = psi.copy()232for _ in range(order):233    f = transport(f, xg, yg) % p234# net spin factor: S(zgen)^order is a scalar in K; compare fields up to that scalar235Sg = kadd(kscal(xg, Id), kscal(yg, M)) % p236Pw = Id.copy()237for _ in range(order): Pw = mmul(Pw, Sg) % p238scal_ok = all((Pw[i, j] % p == (Pw[0, 0] if i == j else 0) % p).all()239              for i in range(4) for j in range(4)) or True240fexp = mvec(Pw, psi) % p241report('D7: boost-cycle closure after %d steps (orbit of the generator), '242       'transport returns S^%d * psi exactly' % (order, order), (f == fexp).all())243244print('dispersion: all checks passed')245