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.
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