#!/usr/bin/env python3 """Check exported exact matrices independently of the diagram routines. Usage: python anc/code/verify_marked_data.py anc/data/exact_*.json Dependencies: sympy; python-flint is recommended for speed. All verification checks remain active under Python's -O option. """ import json import math import sys from pathlib import Path from sympy import QQ, Rational, exp, I, pi from sympy.polys.matrices import DomainMatrix def require(condition, message): if not condition: raise ValueError("VERIFICATION FAILED: " + message) def verify(path): data = json.loads(Path(path).read_text()) par = data['parameters'] p, n, d = par['p'], par['N'], data['dimension'] eps, m = par['epsilon'], par['m'] require(isinstance(p, int) and p >= 3, 'p must be an integer >= 3') require(isinstance(n, int) and n >= 2 and n % 2 == 0, 'N must be a positive even size >= 2') require(eps in (-1, 1) and m == 1, 'expected epsilon = +/-1 and m = 1') x, y = Rational(par['x']), Rational(par['y']) require(x != 0 and y != 0, 'x and y must be nonzero') half = n // 2 catalan = math.comb(n, half) // (half + 1) require(d == catalan * (half + 1)**2, 'full dimension disagrees with face count') field = QQ.algebraic_field(exp(I*pi/p)) q = field.unit degree = len(field.mod.to_list()) - 1 metadata = data['field'] require(metadata['generator'] == 'q' and metadata['embedding'] == f'exp(i*pi/{p})' and metadata['degree'] == degree and metadata['coefficient_order'] == 'ascending powers 1,q,...' and [Rational(c) for c in metadata['modulus_descending']] == [Rational(str(c)) for c in field.mod.to_list()], 'field metadata disagrees with the parameter p') coeff = [] for row in data['coefficient_dictionary']: require(len(row) == degree, 'coefficient has the wrong field degree') value = field.zero for j, c in enumerate(row): value += field.from_sympy(Rational(c)) * q**j coeff.append(value) def matrix(info): shape = tuple(info['shape']) require(len(shape) == 2 and all(isinstance(k, int) and k >= 0 for k in shape), 'invalid matrix shape') dod, seen = {}, set() for r, c, k in info['entries']: require(0 <= r < shape[0] and 0 <= c < shape[1] and 0 <= k < len(coeff), 'sparse entry is out of bounds') require((r, c) not in seen, 'duplicate sparse entry') seen.add((r, c)) dod.setdefault(r, {})[c] = coeff[k] return DomainMatrix.from_dod(dod, shape, field).to_sparse() full = {name: matrix(info) for name, info in data['matrices'].items()} target = {name: matrix(info) for name, info in data['target_matrices'].items()} expected_names = {f'e_{i}' for i in range(n)} | {'Omega', 'F', 'Fbar'} require(set(full) == expected_names, 'missing or unexpected full matrix') require(all(G.shape == (d, d) for G in full.values()), 'full matrix shape mismatch') states = data['basis_states'] require(len(states) == d, 'wrong number of basis states') state_keys = [(tuple(s['matching']), tuple(s['face_a']), tuple(s['face_b'])) for s in states] require(len(set(state_keys)) == d, 'duplicate basis state') depth0 = [i for i, s in enumerate(states) if s['face_a'] == s['face_b']] require(len(depth0) == data['depth_zero_dimension'] == math.comb(n, half), 'depth-zero dimension disagrees with the face basis') e = [full[f'e_{i}'] for i in range(n)] om, F, Fb = full['Omega'], full['F'], full['Fbar'] ident = DomainMatrix.eye(d, field).to_sparse() beta = -q - q**(-1) for i in range(n): require((e[i]*e[i] - beta*e[i]).is_zero_matrix, f'e_{i} quadratic relation') require((om*e[i] - e[(i-1) % n]*om).is_zero_matrix, f'e_{i} rotation relation') if n > 2: for j in ((i-1) % n, (i+1) % n): require((e[i]*e[j]*e[i] - e[i]).is_zero_matrix, f'adjacent relation for e_{i}, e_{j}') for j in range(n): if (i-j) % n not in (0, 1, n-1): require((e[i]*e[j] - e[j]*e[i]).is_zero_matrix, 'distant commutation') require((om**n - ident).is_zero_matrix, 'full rotation relation') if n > 2: require(((om*e[0])**(n-1) - om**n*(om*e[0])).is_zero_matrix, 'affine closure relation') for H in (F, Fb): for G in e + [om]: require((H*G - G*H).is_zero_matrix, 'central commutation') require((F*Fb - Fb*F).is_zero_matrix, 'hoop commutation') f0 = coeff[data['target']['f0_coeff_id']] require(f0 == eps*(q**m + q**(-m)), 'f0 disagrees with the parameters') A, B = F - f0*ident, Fb - f0*ident for H in (A, B): require(H.extract(range(d), depth0).is_zero_matrix, 'scalar depth-zero action') ra, rb, rj = [], [], [] for k in range(1, 5): aa, bb = A**k, B**k ra.append(aa.rank()) rb.append(bb.rank()) rj.append(aa.vstack(bb).rank()) require(ra == data['ranks_F_minus_f0_powers'], 'full F ranks') require(rb == data['ranks_Fbar_minus_f0_powers'], 'full Fbar ranks') require(rj == data['joint_stacked_ranks'], 'joint stacked ranks') certificates = data['rank_certificates'] require(sorted(c['power'] for c in certificates) == [1, 2, 3], 'expected certificates for the first three powers') for certificate in certificates: power, rank = certificate['power'], certificate['rank'] operator = A**power rows, columns = certificate['minor_rows'], certificate['minor_columns'] require(rank == ra[power-1] and len(rows) == len(columns) == rank and len(set(rows)) == len(set(columns)) == rank, 'rank-certificate minor dimensions') det = operator.extract(rows, columns).det() require(bool(det) and det == coeff[certificate['minor_determinant_coeff_id']], 'rank-certificate determinant') right_kernel = matrix(certificate['right_kernel']) kr = d-rank require(right_kernel.shape == (d, kr), 'rank-certificate kernel dimensions') require((operator*right_kernel).is_zero_matrix, 'rank-certificate kernel') require((right_kernel.extract(certificate['kernel_identity_rows'], range(kr)) - DomainMatrix.eye(kr, field)).is_zero_matrix, 'kernel identity minor') K, t = target['kernel_basis'], data['target']['dimension'] power = data['target']['kernel_power'] require(isinstance(power, int) and 1 <= power < len(ra), 'primary kernel power') require(ra[power] == ra[power-1] == rb[power-1] == rj[power-1] == d-t, 'primary dimension, stabilization or joint-kernel equality') require(K.shape == (d, t) and K.rank() == t, 'primary basis rank or shape') require((A**power*K).is_zero_matrix, 'primary kernel equation') for name, G in full.items(): require(target[name].shape == (t, t) and (G*K - K*target[name]).is_zero_matrix, f'primary restriction of {name}') N, M, D = target['N'], target['M'], target['D'] one = DomainMatrix.eye(t, field) require((target['F'] - f0*one - N).is_zero_matrix, 'restricted F shift') require((target['Fbar'] - f0*one - M).is_zero_matrix, 'restricted Fbar shift') require((N-M-D).is_zero_matrix, 'restricted hoop difference') nr = [(N**k).rank() for k in range(1, 5)] require(nr == data['target']['nilpotent_ranks'], 'restricted nilpotent ranks') ranks = [t] + nr jordan = {str(j): ranks[j-1]-2*ranks[j]+ranks[j+1] for j in range(1, 4) if ranks[j-1]-2*ranks[j]+ranks[j+1]} require(jordan == data['target']['Jordan_blocks_by_size'], 'Jordan multiplicities') require(D.rank() == data['target']['rank_N_minus_M'], 'rank of hoop difference') require((N+M).rank() == data['target']['rank_N_plus_M'], 'rank of hoop sum') require((N*M).rank() == data['target']['rank_NM'], 'rank of hoop product') checks = {'N3_zero': (N**3).is_zero_matrix, 'M3_zero': (M**3).is_zero_matrix, 'NM_equals_N2': (N*M-N*N).is_zero_matrix, 'M2_equals_N2': (M*M-N*N).is_zero_matrix, 'ND_zero': (N*D).is_zero_matrix, 'D2_zero': (D*D).is_zero_matrix, 'F_equals_Fbar_on_target': D.is_zero_matrix} require(checks == data['target']['relations'], 'recorded restricted relations') require(all(v for k, v in checks.items() if k != 'F_equals_Fbar_on_target'), 'relations needed for the finite hoop-algebra span') words = [one, N, M, N*N, N*M, M*M, N**3, N*N*M, N*M*M, M**3] span = DomainMatrix([sum(Z.to_list(), []) for Z in words], (10, t*t), field) require(span.rank() == data['target']['hoop_algebra_dimension'], 'hoop-algebra dimension') zdata = data['depth_one'] Z, zt = matrix(zdata['basis']), zdata['dimension'] edges = zdata['edge_descriptors'] require(zdata['same_face_dimension'] == len(depth0) and zdata['edge_dimension'] == len(edges) == half*catalan and zt == len(depth0)+len(edges), 'tree-basis dimensions') require(Z.shape == (d, zt) and Z.rank() == zt, 'tree-basis rank or shape') # Recover the explicit columns from labels rather than trusting the export. state_index = {key: i for i, key in enumerate(state_keys)} expected_basis = {r: {c: field.one} for c, r in enumerate(depth0)} expected_action = {} g = coeff[zdata['g_coeff_id']] xx, yy = field.from_sympy(x), field.from_sympy(y) require(g == xx+xx**(-1)+eps*(yy+yy**(-1)), 'g disagrees with the parameters') for col, edge in enumerate(edges, len(depth0)): matching = tuple(edge['matching']) f, h = tuple(edge['face_f']), tuple(edge['face_h']) require(f < h, 'edge orientation must use the ordered face labels') r = state_index[(matching, f, f)] s = state_index[(matching, h, h)] require(edge['diagonal_rows'] == [r, s], 'edge diagonal-row labels') for row, value in ((state_index[(matching, f, h)], field.one), (state_index[(matching, h, f)], field.convert(eps))): expected_basis.setdefault(row, {})[col] = value expected_action.setdefault(r, {})[col] = g expected_action.setdefault(s, {})[col] = eps*g Zexpected = DomainMatrix.from_dod(expected_basis, (d, zt), field) require((Z-Zexpected).is_zero_matrix, 'explicit tree-basis columns') pivots = Z.transpose().rref()[1] left_inverse = Z.extract(pivots, range(zt)).inv() for name, G in full.items(): acted = G*Z restriction = left_inverse*acted.extract(pivots, range(zt)) require((Z*restriction-acted).is_zero_matrix, f'tree-submodule invariance under {name}') G = DomainMatrix.from_dod(expected_action, (d, zt), field) require((A*Z-G).is_zero_matrix and (B*Z-G).is_zero_matrix, 'tree-incidence formulas') require((A*Z).rank() == zdata['nilpotent_rank'], 'tree nilpotent rank') require((A*A*Z).is_zero_matrix and ((F-Fb)*Z).is_zero_matrix, 'tree square-zero and equal-hoop formulas') print(Path(path).name + ': all exact matrix, rank, submodule and central-algebra checks passed', flush=True) if __name__ == '__main__': if len(sys.argv) < 2: raise SystemExit('Specify one or more exported JSON files.') for path in sys.argv[1:]: verify(path)