Created
April 1, 2026 17:33
-
-
Save justinmeza/a2dec3dbfcddb2bc4b82fac4ad24033a to your computer and use it in GitHub Desktop.
Supplementary material: verification scripts for first-derivative jet-space invariants (paper4)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "N_coframe_quartic": 55, | |
| "N_torsion_quartic": 27, | |
| "N_coframe_cubic": 0, | |
| "N_torsion_cubic": 0, | |
| "coframe_quadruples": [ | |
| "T·T·T·T", | |
| "T·T·T·V", | |
| "T·T·T·s", | |
| "T·T·V·V", | |
| "T·T·V·s", | |
| "T·T·𝒜·𝒜", | |
| "T·T·s·s", | |
| "T·V·V·V", | |
| "T·V·V·s", | |
| "T·V·𝒜·𝒜", | |
| "T·V·s·s", | |
| "T·𝒜·𝒜·s", | |
| "T·s·s·s", | |
| "V·V·V·V", | |
| "V·V·V·s", | |
| "V·V·𝒜·𝒜", | |
| "V·V·s·s", | |
| "V·𝒜·𝒜·s", | |
| "V·s·s·s", | |
| "𝒜·𝒜·𝒜·𝒜", | |
| "𝒜·𝒜·s·s", | |
| "s·s·s·s" | |
| ], | |
| "torsion_quadruples": [ | |
| "T·T·T·T", | |
| "T·T·T·V", | |
| "T·T·V·V", | |
| "T·T·𝒜·𝒜", | |
| "T·V·V·V", | |
| "T·V·𝒜·𝒜", | |
| "V·V·V·V", | |
| "V·V·𝒜·𝒜", | |
| "𝒜·𝒜·𝒜·𝒜" | |
| ], | |
| "n_coframe_patterns": 12822, | |
| "n_torsion_patterns": 11589, | |
| "coframe_submatrix_shape": [ | |
| 12822, | |
| 42046 | |
| ], | |
| "torsion_submatrix_shape": [ | |
| 11589, | |
| 30636 | |
| ], | |
| "per_quadruple_coframe": { | |
| "T·T·T·T": { | |
| "n_patterns": 10395, | |
| "rank": 21 | |
| }, | |
| "T·T·T·V": { | |
| "n_patterns": 945, | |
| "rank": 12 | |
| }, | |
| "T·T·T·s": { | |
| "n_patterns": 945, | |
| "rank": 14 | |
| }, | |
| "T·T·V·V": { | |
| "n_patterns": 105, | |
| "rank": 7 | |
| }, | |
| "T·T·V·s": { | |
| "n_patterns": 105, | |
| "rank": 9 | |
| }, | |
| "T·T·𝒜·𝒜": { | |
| "n_patterns": 105, | |
| "rank": 9 | |
| }, | |
| "T·T·s·s": { | |
| "n_patterns": 105, | |
| "rank": 9 | |
| }, | |
| "T·V·V·V": { | |
| "n_patterns": 15, | |
| "rank": 1 | |
| }, | |
| "T·V·V·s": { | |
| "n_patterns": 15, | |
| "rank": 2 | |
| }, | |
| "T·V·𝒜·𝒜": { | |
| "n_patterns": 15, | |
| "rank": 3 | |
| }, | |
| "T·V·s·s": { | |
| "n_patterns": 15, | |
| "rank": 3 | |
| }, | |
| "T·𝒜·𝒜·s": { | |
| "n_patterns": 15, | |
| "rank": 3 | |
| }, | |
| "T·s·s·s": { | |
| "n_patterns": 15, | |
| "rank": 1 | |
| }, | |
| "V·V·V·V": { | |
| "n_patterns": 3, | |
| "rank": 1 | |
| }, | |
| "V·V·V·s": { | |
| "n_patterns": 3, | |
| "rank": 1 | |
| }, | |
| "V·V·𝒜·𝒜": { | |
| "n_patterns": 3, | |
| "rank": 2 | |
| }, | |
| "V·V·s·s": { | |
| "n_patterns": 3, | |
| "rank": 2 | |
| }, | |
| "V·𝒜·𝒜·s": { | |
| "n_patterns": 3, | |
| "rank": 2 | |
| }, | |
| "V·s·s·s": { | |
| "n_patterns": 3, | |
| "rank": 1 | |
| }, | |
| "𝒜·𝒜·𝒜·𝒜": { | |
| "n_patterns": 3, | |
| "rank": 1 | |
| }, | |
| "𝒜·𝒜·s·s": { | |
| "n_patterns": 3, | |
| "rank": 2 | |
| }, | |
| "s·s·s·s": { | |
| "n_patterns": 3, | |
| "rank": 1 | |
| } | |
| }, | |
| "per_quadruple_torsion": { | |
| "T·T·T·T": { | |
| "n_patterns": 10395, | |
| "rank": 21 | |
| }, | |
| "T·T·T·V": { | |
| "n_patterns": 945, | |
| "rank": 12 | |
| }, | |
| "T·T·V·V": { | |
| "n_patterns": 105, | |
| "rank": 7 | |
| }, | |
| "T·T·𝒜·𝒜": { | |
| "n_patterns": 105, | |
| "rank": 9 | |
| }, | |
| "T·V·V·V": { | |
| "n_patterns": 15, | |
| "rank": 1 | |
| }, | |
| "T·V·𝒜·𝒜": { | |
| "n_patterns": 15, | |
| "rank": 3 | |
| }, | |
| "V·V·V·V": { | |
| "n_patterns": 3, | |
| "rank": 1 | |
| }, | |
| "V·V·𝒜·𝒜": { | |
| "n_patterns": 3, | |
| "rank": 2 | |
| }, | |
| "𝒜·𝒜·𝒜·𝒜": { | |
| "n_patterns": 3, | |
| "rank": 1 | |
| } | |
| }, | |
| "sum_per_quad_coframe": 107, | |
| "sum_per_quad_torsion": 57, | |
| "primes": [ | |
| 10007, | |
| 10009, | |
| 100003, | |
| 1000003, | |
| 10000019 | |
| ], | |
| "coframe_ranks_by_prime": { | |
| "10007": 55, | |
| "10009": 55, | |
| "100003": 55, | |
| "1000003": 55, | |
| "10000019": 55 | |
| }, | |
| "torsion_ranks_by_prime": { | |
| "10007": 27, | |
| "10009": 27, | |
| "100003": 27, | |
| "1000003": 27, | |
| "10000019": 27 | |
| }, | |
| "cubic_coframe_triples_surviving": 0, | |
| "cubic_index_parity_argument": "T(3)+V(1)+A(1)+s(1): any triple of odd numbers sums to odd", | |
| "elapsed_seconds": 297.5319130420685 | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """01_coframe_sector.py — Extract coframe-only sector and compute global ranks. | |
| From the paper3 quartic checkpoint (17,383 patterns × 66,042 monomials), | |
| extract the submatrix of coframe-only patterns (blocks ∈ {T, V, A, s}) | |
| and compute: | |
| N_torsion: global rank of pure-torsion quadruples {T, V, A} | |
| N_coframe: global rank of all coframe quadruples {T, V, A, s} | |
| Also verifies the cubic coframe sector is trivially empty (odd index count). | |
| Reuses the rank_mod_p machinery from 20_quartic_symbolic.py. | |
| """ | |
| import numpy as np | |
| from itertools import combinations_with_replacement, product as iproduct | |
| from collections import defaultdict | |
| import json | |
| import sys | |
| import os | |
| import time | |
| import pickle | |
| import functools | |
| print = functools.partial(print, flush=True) | |
| D = 4 | |
| eta = np.array([[-1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], dtype=int) | |
| BLOCKS = [ | |
| ("Shat", 2, +1), | |
| ("theta", 0, +1), | |
| ("W", 2, +1), | |
| ("T", 3, +1), | |
| ("V", 1, +1), | |
| ("A", 1, -1), | |
| ("s", 1, +1), | |
| ] | |
| BLOCK_DISPLAY = {"Shat": "Ŝ", "theta": "θ", "W": "W", "T": "T", | |
| "V": "V", "A": "𝒜", "s": "s"} | |
| # Block indices | |
| IDX = {name: i for i, (name, _, _) in enumerate(BLOCKS)} | |
| # Sector classification | |
| COFRAME_BLOCKS = {IDX["T"], IDX["V"], IDX["A"], IDX["s"]} | |
| TORSION_BLOCKS = {IDX["T"], IDX["V"], IDX["A"]} # no scale | |
| def generate_pairings(indices): | |
| if len(indices) == 0: | |
| return [()] | |
| if len(indices) == 2: | |
| return [((indices[0], indices[1]),)] | |
| first = indices[0] | |
| rest = indices[1:] | |
| result = [] | |
| for i, partner in enumerate(rest): | |
| remaining = rest[:i] + rest[i+1:] | |
| for sub in generate_pairings(remaining): | |
| result.append(((first, partner),) + sub) | |
| return result | |
| def rank_mod_p(mat, p, track_pivots=False): | |
| """Rank of dense int64 matrix mod p via Gaussian elimination.""" | |
| m, n = mat.shape | |
| A = mat.astype(np.int64) % p | |
| A[A < 0] += p | |
| rank = 0 | |
| pivot_rows = [] | |
| pivot_cols = [] | |
| row_perm = list(range(m)) | |
| for col in range(n): | |
| if rank >= m: | |
| break | |
| nz = np.nonzero(A[rank:, col])[0] | |
| if len(nz) == 0: | |
| continue | |
| found = nz[0] + rank | |
| if found != rank: | |
| A[[rank, found]] = A[[found, rank]] | |
| row_perm[rank], row_perm[found] = row_perm[found], row_perm[rank] | |
| pivot_rows.append(row_perm[rank]) | |
| pivot_cols.append(col) | |
| inv_piv = pow(int(A[rank, col]), p - 2, p) | |
| col_vals = A[:, col].copy() | |
| col_vals[rank] = 0 | |
| nz_rows = np.nonzero(col_vals)[0] | |
| if len(nz_rows) > 0: | |
| factors = (col_vals[nz_rows] * inv_piv) % p | |
| A[nz_rows] = (A[nz_rows] - factors[:, np.newaxis] * A[rank]) % p | |
| rank += 1 | |
| if rank % 20 == 0: | |
| print(f" rank_mod_p: rank {rank}, col {col}/{n}") | |
| if track_pivots: | |
| return rank, pivot_rows, pivot_cols | |
| return rank | |
| def main(): | |
| t0 = time.time() | |
| print("=" * 60) | |
| print("COFRAME SECTOR EXTRACTION — QUARTIC & CUBIC") | |
| print("=" * 60) | |
| # ─── Reproduce the quadruple enumeration (must match paper3 exactly) ── | |
| print("\nStep 1: Enumerate all surviving quadruples...") | |
| surviving = [] | |
| for quad in combinations_with_replacement(range(len(BLOCKS)), 4): | |
| parity = 1 | |
| total_idx = 0 | |
| for b in quad: | |
| parity *= BLOCKS[b][2] | |
| total_idx += BLOCKS[b][1] | |
| if parity != +1: | |
| continue | |
| if total_idx % 2 != 0: | |
| continue | |
| surviving.append(quad) | |
| print(f" Surviving quadruples: {len(surviving)}") | |
| all_patterns = [] | |
| quad_info = [] # (quad, n_patterns, start_idx) | |
| for quad in surviving: | |
| n_idx = [BLOCKS[b][1] for b in quad] | |
| total = sum(n_idx) | |
| pairings = generate_pairings(list(range(total))) if total > 0 else [()] | |
| start = len(all_patterns) | |
| for p in pairings: | |
| all_patterns.append((quad, p)) | |
| quad_info.append((quad, len(pairings), start)) | |
| N_pat = len(all_patterns) | |
| print(f" Total contraction patterns: {N_pat}") | |
| # Classify quadruples | |
| coframe_quads = [] # indices into quad_info | |
| torsion_quads = [] | |
| for qi, (quad, n_pats, start) in enumerate(quad_info): | |
| blocks_set = set(quad) | |
| if blocks_set <= COFRAME_BLOCKS: | |
| coframe_quads.append(qi) | |
| if blocks_set <= TORSION_BLOCKS: | |
| torsion_quads.append(qi) | |
| coframe_quad_names = [] | |
| torsion_quad_names = [] | |
| for qi in coframe_quads: | |
| quad = quad_info[qi][0] | |
| name = "·".join(BLOCK_DISPLAY[BLOCKS[b][0]] for b in quad) | |
| coframe_quad_names.append(name) | |
| for qi in torsion_quads: | |
| quad = quad_info[qi][0] | |
| name = "·".join(BLOCK_DISPLAY[BLOCKS[b][0]] for b in quad) | |
| torsion_quad_names.append(name) | |
| print(f"\n Coframe quadruples ({len(coframe_quads)}):") | |
| for name in coframe_quad_names: | |
| print(f" {name}") | |
| print(f"\n Pure-torsion quadruples ({len(torsion_quads)}):") | |
| for name in torsion_quad_names: | |
| print(f" {name}") | |
| # Collect row indices for each sector | |
| coframe_rows = [] | |
| torsion_rows = [] | |
| for qi in coframe_quads: | |
| quad, n_pats, start = quad_info[qi] | |
| coframe_rows.extend(range(start, start + n_pats)) | |
| for qi in torsion_quads: | |
| quad, n_pats, start = quad_info[qi] | |
| torsion_rows.extend(range(start, start + n_pats)) | |
| print(f"\n Coframe patterns: {len(coframe_rows)}") | |
| print(f" Pure-torsion patterns: {len(torsion_rows)}") | |
| # ─── Load checkpoint ────────────────────────────────────────────── | |
| print("\nStep 2: Loading quartic checkpoint...") | |
| checkpoint_file = os.path.join( | |
| os.path.dirname(__file__), "..", "..", "paper3", "verify", | |
| "quartic_checkpoints", "quartic_polys.pkl" | |
| ) | |
| checkpoint_file = os.path.normpath(checkpoint_file) | |
| if not os.path.isfile(checkpoint_file): | |
| print(f" ERROR: Checkpoint not found at {checkpoint_file}") | |
| sys.exit(1) | |
| with open(checkpoint_file, "rb") as f: | |
| saved = pickle.load(f) | |
| all_polys = saved["all_polys"] | |
| all_monomials = saved["all_monomials"] | |
| N_mon = len(all_monomials) | |
| print(f" Loaded {len(all_polys)} polynomials, {N_mon} monomials") | |
| # Build monomial index | |
| monomial_list = sorted(all_monomials) | |
| mon_idx = {m: i for i, m in enumerate(monomial_list)} | |
| # ─── Identify de-only monomials ─────────────────────────────────── | |
| # Coframe blocks (T, V, A, s) are all functions of de variables only. | |
| # Their products can only contain de monomials. But let's be explicit: | |
| # we restrict to columns that are de-only (no 'du' variables). | |
| print("\nStep 3: Identifying de-only monomials...") | |
| de_only_cols = [] | |
| for i, mono in enumerate(monomial_list): | |
| if all(var[0] == 'de' for var in mono): | |
| de_only_cols.append(i) | |
| print(f" de-only monomials: {len(de_only_cols)} / {N_mon}") | |
| de_col_set = set(de_only_cols) | |
| de_col_remap = {old: new for new, old in enumerate(de_only_cols)} | |
| # ─── Build submatrices ──────────────────────────────────────────── | |
| print("\nStep 4: Building coframe submatrix...") | |
| n_de = len(de_only_cols) | |
| # For coframe-only patterns, ALL monomials should be de-only. | |
| # Verify this and build the submatrix. | |
| n_coframe_rows = len(coframe_rows) | |
| C_coframe = np.zeros((n_coframe_rows, n_de), dtype=np.int64) | |
| n_du_contamination = 0 | |
| for ri, row_idx in enumerate(coframe_rows): | |
| poly = all_polys[row_idx] | |
| for mono, coeff in poly.items(): | |
| if coeff == 0: | |
| continue | |
| col = mon_idx[mono] | |
| if col in de_col_set: | |
| C_coframe[ri, de_col_remap[col]] = coeff | |
| else: | |
| n_du_contamination += 1 | |
| if n_du_contamination > 0: | |
| print(f" WARNING: {n_du_contamination} du-contaminated entries in coframe rows!") | |
| else: | |
| print(f" Verified: all coframe pattern monomials are de-only ✓") | |
| # Remove zero columns | |
| nz_cols_mask = np.any(C_coframe != 0, axis=0) | |
| C_coframe_nz = C_coframe[:, nz_cols_mask] | |
| print(f" Coframe submatrix: {C_coframe_nz.shape[0]} × {C_coframe_nz.shape[1]}") | |
| # Build torsion submatrix (subset of coframe rows) | |
| n_torsion_rows = len(torsion_rows) | |
| torsion_row_set = set(torsion_rows) | |
| torsion_local = [ri for ri, row_idx in enumerate(coframe_rows) | |
| if row_idx in torsion_row_set] | |
| C_torsion = C_coframe[torsion_local, :] | |
| nz_cols_t = np.any(C_torsion != 0, axis=0) | |
| C_torsion_nz = C_torsion[:, nz_cols_t] | |
| print(f" Torsion submatrix: {C_torsion_nz.shape[0]} × {C_torsion_nz.shape[1]}") | |
| # ─── Compute ranks ──────────────────────────────────────────────── | |
| primes = [10007, 10009, 100003, 1000003, 10000019] | |
| # Per-quadruple ranks for coframe sector | |
| print("\nStep 5a: Per-quadruple ranks (coframe sector)...") | |
| p_quick = primes[0] | |
| per_quad_coframe = {} | |
| per_quad_torsion = {} | |
| sum_coframe = 0 | |
| sum_torsion = 0 | |
| for qi in coframe_quads: | |
| quad, n_pats, start = quad_info[qi] | |
| name = "·".join(BLOCK_DISPLAY[BLOCKS[b][0]] for b in quad) | |
| # Find local row range in C_coframe | |
| # coframe_rows is built by iterating coframe_quads in order | |
| # so we need to find the offset | |
| local_start = sum(quad_info[qj][1] for qj in coframe_quads[:coframe_quads.index(qi)]) | |
| local_end = local_start + n_pats | |
| sub = C_coframe_nz[local_start:local_end, :] | |
| # Remove zero columns | |
| nz = np.any(sub != 0, axis=0) | |
| sub_nz = sub[:, nz] | |
| if sub_nz.shape[1] == 0: | |
| r = 0 | |
| else: | |
| r = rank_mod_p(sub_nz, p_quick) | |
| per_quad_coframe[name] = {"n_patterns": n_pats, "rank": r} | |
| sum_coframe += r | |
| is_torsion = qi in torsion_quads | |
| if is_torsion: | |
| per_quad_torsion[name] = {"n_patterns": n_pats, "rank": r} | |
| sum_torsion += r | |
| print(f" {name:20s} patterns={n_pats:5d} rank={r:3d} {'[torsion]' if is_torsion else ''}") | |
| print(f"\n Sum per-quadruple coframe ranks: {sum_coframe}") | |
| print(f" Sum per-quadruple torsion ranks: {sum_torsion}") | |
| # Global coframe rank | |
| print(f"\nStep 5b: Global coframe rank ({n_coframe_rows} × {C_coframe_nz.shape[1]})...") | |
| coframe_ranks = {} | |
| for p in primes: | |
| t1 = time.time() | |
| r = rank_mod_p(C_coframe_nz, p) | |
| elapsed = time.time() - t1 | |
| coframe_ranks[p] = r | |
| print(f" Rank mod {p:>10d}: {r} ({elapsed:.1f}s)") | |
| coframe_rank_vals = list(coframe_ranks.values()) | |
| assert len(set(coframe_rank_vals)) == 1, \ | |
| f"Coframe rank disagreement: {coframe_ranks}" | |
| N_coframe = coframe_rank_vals[0] | |
| print(f"\n *** COFRAME RANK: {N_coframe} ***") | |
| # Global torsion rank | |
| print(f"\nStep 5c: Global torsion rank ({C_torsion_nz.shape[0]} × {C_torsion_nz.shape[1]})...") | |
| torsion_ranks = {} | |
| for p in primes: | |
| t1 = time.time() | |
| r = rank_mod_p(C_torsion_nz, p) | |
| elapsed = time.time() - t1 | |
| torsion_ranks[p] = r | |
| print(f" Rank mod {p:>10d}: {r} ({elapsed:.1f}s)") | |
| torsion_rank_vals = list(torsion_ranks.values()) | |
| assert len(set(torsion_rank_vals)) == 1, \ | |
| f"Torsion rank disagreement: {torsion_ranks}" | |
| N_torsion = torsion_rank_vals[0] | |
| print(f"\n *** TORSION RANK: {N_torsion} ***") | |
| # ─── Cubic verification ─────────────────────────────────────────── | |
| print("\n" + "=" * 60) | |
| print("CUBIC COFRAME SECTOR") | |
| print("=" * 60) | |
| # For cubic: enumerate triples, check which are coframe-only | |
| surviving_cubic = [] | |
| for triple in combinations_with_replacement(range(len(BLOCKS)), 3): | |
| parity = 1 | |
| total_idx = 0 | |
| for b in triple: | |
| parity *= BLOCKS[b][2] | |
| total_idx += BLOCKS[b][1] | |
| if parity != +1: | |
| continue | |
| if total_idx % 2 != 0: | |
| continue | |
| surviving_cubic.append(triple) | |
| coframe_cubic = [] | |
| for triple in surviving_cubic: | |
| if set(triple) <= COFRAME_BLOCKS: | |
| coframe_cubic.append(triple) | |
| # Check: all coframe triples have odd total index count | |
| # T=3, V=1, A=1, s=1. Any triple: sum of three odd numbers = odd → no even pairing | |
| print(f"\n Total surviving cubic triples: {len(surviving_cubic)}") | |
| print(f" Coframe cubic triples: {len(coframe_cubic)}") | |
| if len(coframe_cubic) == 0: | |
| print(" ALL coframe triples eliminated by index parity ✓") | |
| print(" Reason: T(3), V(1), A(1), s(1) — any triple sums to odd") | |
| N_torsion_cubic = 0 | |
| N_coframe_cubic = 0 | |
| else: | |
| print(f" WARNING: {len(coframe_cubic)} coframe triples survive!") | |
| for triple in coframe_cubic: | |
| name = "·".join(BLOCK_DISPLAY[BLOCKS[b][0]] for b in triple) | |
| total_idx = sum(BLOCKS[b][1] for b in triple) | |
| print(f" {name} total_indices={total_idx}") | |
| N_torsion_cubic = -1 # signal error | |
| N_coframe_cubic = -1 | |
| # ─── Save results ───────────────────────────────────────────────── | |
| elapsed_total = time.time() - t0 | |
| print(f"\n{'='*60}") | |
| print(f"RESULTS SUMMARY") | |
| print(f"{'='*60}") | |
| print(f" Quartic coframe rank: {N_coframe}") | |
| print(f" Quartic torsion rank: {N_torsion}") | |
| print(f" Cubic coframe rank: {N_coframe_cubic}") | |
| print(f" Cubic torsion rank: {N_torsion_cubic}") | |
| print(f" Full quartic rank (paper3): 156") | |
| print(f" Elapsed: {elapsed_total:.1f}s") | |
| results = { | |
| "N_coframe_quartic": N_coframe, | |
| "N_torsion_quartic": N_torsion, | |
| "N_coframe_cubic": N_coframe_cubic, | |
| "N_torsion_cubic": N_torsion_cubic, | |
| "coframe_quadruples": coframe_quad_names, | |
| "torsion_quadruples": torsion_quad_names, | |
| "n_coframe_patterns": n_coframe_rows, | |
| "n_torsion_patterns": n_torsion_rows, | |
| "coframe_submatrix_shape": list(C_coframe_nz.shape), | |
| "torsion_submatrix_shape": list(C_torsion_nz.shape), | |
| "per_quadruple_coframe": per_quad_coframe, | |
| "per_quadruple_torsion": per_quad_torsion, | |
| "sum_per_quad_coframe": sum_coframe, | |
| "sum_per_quad_torsion": sum_torsion, | |
| "primes": primes, | |
| "coframe_ranks_by_prime": {str(p): r for p, r in coframe_ranks.items()}, | |
| "torsion_ranks_by_prime": {str(p): r for p, r in torsion_ranks.items()}, | |
| "cubic_coframe_triples_surviving": len(coframe_cubic), | |
| "cubic_index_parity_argument": "T(3)+V(1)+A(1)+s(1): any triple of odd numbers sums to odd", | |
| "elapsed_seconds": elapsed_total, | |
| } | |
| out_dir = os.path.dirname(__file__) or "." | |
| out_file = os.path.join(out_dir, "01_coframe_sector.json") | |
| with open(out_file, "w") as f: | |
| json.dump(results, f, indent=2, ensure_ascii=False) | |
| print(f"\nSaved: {out_file}") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "quartic_results": [ | |
| { | |
| "linearized": 21, | |
| "covariant": 21 | |
| }, | |
| { | |
| "linearized": 21, | |
| "covariant": 21 | |
| }, | |
| { | |
| "linearized": 21, | |
| "covariant": 21 | |
| }, | |
| { | |
| "linearized": 21, | |
| "covariant": 21 | |
| }, | |
| { | |
| "linearized": 21, | |
| "covariant": 21 | |
| } | |
| ], | |
| "all_match": true, | |
| "identity_control": { | |
| "lin": 21, | |
| "cov": 21, | |
| "max_diff": 0.0 | |
| }, | |
| "n_sampled": 3194, | |
| "n_total": 11589, | |
| "n_coframes": 5, | |
| "n_de_per_coframe": 100, | |
| "convention": "all_lower_indices", | |
| "elapsed_seconds": 580.2590227127075 | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """02_covariant_check.py — Verify η↔g covariant equivalence numerically. | |
| All blocks use LOWER indices consistently: | |
| T_{ρμν}, V_μ, A_μ (where A_μ = ε_{μνρσ} T^{νρσ}) | |
| Contractions use the UPPER metric (η^{μν} or g^{μν}). | |
| Protocol: fix coframe e, vary de, check rank of (config × pattern) matrix. | |
| """ | |
| import numpy as np | |
| from itertools import combinations_with_replacement | |
| from collections import defaultdict | |
| import json, os, time, functools | |
| print = functools.partial(print, flush=True) | |
| D = 4 | |
| eta = np.array([[-1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], dtype=float) | |
| def levi_civita_down(): | |
| eps = np.zeros((D,D,D,D), dtype=float) | |
| for i in range(D): | |
| for j in range(D): | |
| for k in range(D): | |
| for l in range(D): | |
| if len(set([i,j,k,l])) < D: continue | |
| s = 1 | |
| p = [i,j,k,l] | |
| for a in range(D): | |
| for b in range(a+1,D): | |
| if p[a]>p[b]: s *= -1 | |
| eps[i,j,k,l] = s | |
| return eps | |
| EPS = levi_civita_down() | |
| def random_coframe(rng, scale=0.5): | |
| e = np.eye(D) + scale * rng.randn(D, D) | |
| while abs(np.linalg.det(e)) < 0.1: | |
| e = np.eye(D) + scale * rng.randn(D, D) | |
| return e | |
| def generate_pairings(indices): | |
| if len(indices) == 0: return [()] | |
| if len(indices) == 2: return [((indices[0], indices[1]),)] | |
| first = indices[0]; rest = indices[1:] | |
| result = [] | |
| for i, partner in enumerate(rest): | |
| remaining = rest[:i] + rest[i+1:] | |
| for sub in generate_pairings(remaining): | |
| result.append(((first, partner),) + sub) | |
| return result | |
| # ─── Block computation (all lower indices) ──────────────────────────── | |
| def compute_blocks_lower(de, e=None): | |
| """Compute T_{ρμν}, V_μ, A_μ with ALL LOWER indices. | |
| If e is None: linearized (η for everything). | |
| If e given: covariant (g(e) for everything). | |
| """ | |
| # T^a_{μν} = de[μ,a,ν] - de[ν,a,μ] (frame torsion, metric-independent) | |
| Ta = np.zeros((D,D,D)) | |
| for a in range(D): | |
| for mu in range(D): | |
| for nu in range(D): | |
| Ta[a,mu,nu] = de[mu,a,nu] - de[nu,a,mu] | |
| if e is None: | |
| # Linearized: T_{ρμν} = η_{ρa} T^a_{μν} | |
| T = np.einsum('ra,amn->rmn', eta, Ta) | |
| met_up = eta | |
| else: | |
| # Covariant: T_{ρμν} = η_{ab} e^b_ρ T^a_{μν} | |
| # (= g_{ρσ} e_a^σ T^a_{μν}, but η_{ab} e^b_ρ is simpler) | |
| L = np.einsum('ab,br->ra', eta, e) # L[r,a] = η_{ab} e^b_ρ | |
| T = np.einsum('ra,amn->rmn', L, Ta) | |
| g_down = np.einsum('ab,am,bn->mn', eta, e, e) | |
| met_up = np.linalg.inv(g_down) | |
| # V_μ = met^{νρ} T_{ρνμ} (lower index μ) | |
| V = np.einsum('nr,rnm->m', met_up, T) | |
| # A_μ = ε_{μνρσ} T^{νρσ} where T^{νρσ} = met^{na} met^{rb} met^{sc} T_{abc} | |
| T_allup = np.einsum('na,rb,sc,abc->nrs', met_up, met_up, met_up, T) | |
| A = np.einsum('mnrs,nrs->m', EPS, T_allup) | |
| return T, V, A, met_up | |
| # ─── Efficient einsum evaluation ────────────────────────────────────── | |
| LETTERS = 'abcdefghijklmnopqrstuvwxyz' | |
| def eval_patterns(block_tensors, block_defs, patterns, metric): | |
| """Evaluate contraction patterns via np.einsum.""" | |
| values = np.zeros(len(patterns)) | |
| for pi, (quad, pairing) in enumerate(patterns): | |
| n_idx = [block_defs[b][1] for b in quad] | |
| total = sum(n_idx) | |
| if total == 0: | |
| v = 1.0 | |
| for b in quad: v *= float(block_tensors[b]) | |
| values[pi] = v; continue | |
| offsets, off = [], 0 | |
| for ni in n_idx: offsets.append(off); off += ni | |
| idx = [LETTERS[i] for i in range(total)] | |
| ops, subs = [], [] | |
| for bi in range(len(quad)): | |
| s, e2 = offsets[bi], offsets[bi]+n_idx[bi] | |
| subs.append(''.join(idx[s:e2])) | |
| ops.append(block_tensors[quad[bi]]) | |
| for (a,b) in pairing: | |
| subs.append(idx[a]+idx[b]) | |
| ops.append(metric) | |
| try: | |
| values[pi] = np.einsum(','.join(subs)+'->', *ops, optimize=True) | |
| except: | |
| values[pi] = 0.0 | |
| return values | |
| def enumerate_torsion_patterns(): | |
| blocks = [("T",3,+1), ("V",1,+1), ("A",1,-1)] | |
| surviving = [] | |
| for quad in combinations_with_replacement(range(len(blocks)), 4): | |
| par, tot = 1, 0 | |
| for b in quad: par *= blocks[b][2]; tot += blocks[b][1] | |
| if par != +1 or tot % 2 != 0: continue | |
| surviving.append(quad) | |
| all_patterns, quad_info = [], [] | |
| for quad in surviving: | |
| n_idx = [blocks[b][1] for b in quad] | |
| total = sum(n_idx) | |
| pairings = generate_pairings(list(range(total))) if total > 0 else [()] | |
| start = len(all_patterns) | |
| for p in pairings: all_patterns.append((quad, p)) | |
| quad_info.append((quad, len(pairings), start)) | |
| return blocks, surviving, all_patterns, quad_info | |
| def main(): | |
| t0 = time.time() | |
| rng = np.random.RandomState(42) | |
| print("="*60) | |
| print("COVARIANT EQUIVALENCE CHECK — ALL-LOWER CONVENTION") | |
| print("="*60) | |
| blocks, surviving, all_patterns, quad_info = enumerate_torsion_patterns() | |
| n_total = len(all_patterns) | |
| print(f"\nTotal torsion patterns: {n_total}") | |
| # Sample: random 2000 from T⁴, all from rest | |
| sampled = [] | |
| for qi, (quad, n_pats, start) in enumerate(quad_info): | |
| name = "·".join(["T","V","A"][b] for b in quad) | |
| if n_pats > 2000: | |
| indices = rng.choice(n_pats, 2000, replace=False) | |
| for i in indices: | |
| sampled.append(all_patterns[start + i]) | |
| print(f" {name:15s} {n_pats:5d} → sampled 2000") | |
| else: | |
| for i in range(n_pats): | |
| sampled.append(all_patterns[start + i]) | |
| print(f" {name:15s} {n_pats:5d} → all") | |
| print(f" Total sampled: {len(sampled)}") | |
| # ─── Quadratic check ────────────────────────────────────────────── | |
| print("\n--- QUADRATIC (Hayashi-Shirafuji) ---") | |
| N_E, M_DE = 5, 30 | |
| for ei in range(N_E): | |
| e = random_coframe(rng, scale=0.5) | |
| lin_m = np.zeros((M_DE, 3)) | |
| cov_m = np.zeros((M_DE, 3)) | |
| for di in range(M_DE): | |
| de = rng.randn(D,D,D) | |
| Tl,Vl,Al,ml = compute_blocks_lower(de) | |
| Tc,Vc,Ac,mc = compute_blocks_lower(de, e) | |
| lin_m[di,0] = np.einsum('abc,def,ad,be,cf->',Tl,Tl,ml,ml,ml) | |
| lin_m[di,1] = np.einsum('abc,def,ae,bd,cf->',Tl,Tl,ml,ml,ml) | |
| lin_m[di,2] = np.einsum('a,b,ab->',Vl,Vl,ml) | |
| cov_m[di,0] = np.einsum('abc,def,ad,be,cf->',Tc,Tc,mc,mc,mc) | |
| cov_m[di,1] = np.einsum('abc,def,ae,bd,cf->',Tc,Tc,mc,mc,mc) | |
| cov_m[di,2] = np.einsum('a,b,ab->',Vc,Vc,mc) | |
| rl = np.linalg.matrix_rank(lin_m, tol=1e-8) | |
| rc = np.linalg.matrix_rank(cov_m, tol=1e-8) | |
| print(f" e[{ei}]: lin={rl} cov={rc} det(e)={np.linalg.det(e):.3f} {'✓' if rl==rc else '✗'}") | |
| # ─── Quartic check ──────────────────────────────────────────────── | |
| print(f"\n--- QUARTIC (T,V,A) — {len(sampled)} patterns ---") | |
| N_E_Q, M_DE_Q = 5, 100 | |
| results_q = [] | |
| for ei in range(N_E_Q): | |
| e = random_coframe(rng, scale=0.3) | |
| g_down = np.einsum('ab,am,bn->mn', eta, e, e) | |
| g_up = np.linalg.inv(g_down) | |
| lin_m = np.zeros((M_DE_Q, len(sampled))) | |
| cov_m = np.zeros((M_DE_Q, len(sampled))) | |
| for di in range(M_DE_Q): | |
| de = rng.randn(D,D,D) * 0.5 | |
| Tl,Vl,Al,_ = compute_blocks_lower(de) | |
| Tc,Vc,Ac,_ = compute_blocks_lower(de, e) | |
| lin_m[di] = eval_patterns({0:Tl,1:Vl,2:Al}, blocks, sampled, eta) | |
| cov_m[di] = eval_patterns({0:Tc,1:Vc,2:Ac}, blocks, sampled, g_up) | |
| rl = np.linalg.matrix_rank(lin_m, tol=1e-6) | |
| rc = np.linalg.matrix_rank(cov_m, tol=1e-6) | |
| el = time.time()-t0 | |
| print(f" e[{ei}]: lin={rl} cov={rc} det(e)={np.linalg.det(e):.3f} ({el:.0f}s) {'✓' if rl==rc else '✗'}") | |
| results_q.append((rl, rc)) | |
| # Identity control | |
| print("\n Control: e = identity") | |
| e_id = np.eye(D) | |
| lin_id = np.zeros((M_DE_Q, len(sampled))) | |
| cov_id = np.zeros((M_DE_Q, len(sampled))) | |
| rng2 = np.random.RandomState(999) | |
| for di in range(M_DE_Q): | |
| de = rng2.randn(D,D,D)*0.5 | |
| Tl,Vl,Al,_ = compute_blocks_lower(de) | |
| Tc,Vc,Ac,_ = compute_blocks_lower(de, e_id) | |
| lin_id[di] = eval_patterns({0:Tl,1:Vl,2:Al}, blocks, sampled, eta) | |
| cov_id[di] = eval_patterns({0:Tc,1:Vc,2:Ac}, blocks, sampled, eta) | |
| maxd = np.max(np.abs(lin_id - cov_id)) | |
| rl_id = np.linalg.matrix_rank(lin_id, tol=1e-6) | |
| rc_id = np.linalg.matrix_rank(cov_id, tol=1e-6) | |
| print(f" e=I: lin={rl_id} cov={rc_id} max|diff|={maxd:.2e}") | |
| # ─── Summary ────────────────────────────────────────────────────── | |
| all_match = all(rl==rc for rl,rc in results_q) | |
| elapsed = time.time()-t0 | |
| print(f"\n{'='*60}") | |
| print(f"RESULTS (all-lower-index convention)") | |
| print(f"{'='*60}") | |
| for ei,(rl,rc) in enumerate(results_q): | |
| print(f" e[{ei}]: lin={rl} cov={rc} {'✓' if rl==rc else '✗'}") | |
| print(f" Identity: lin={rl_id} cov={rc_id} max_diff={maxd:.2e}") | |
| print(f" Overall: {'VERIFIED ✓' if all_match else 'FAILED ✗'}") | |
| print(f" Elapsed: {elapsed:.1f}s") | |
| out = { | |
| "quartic_results": [{"linearized":int(rl),"covariant":int(rc)} for rl,rc in results_q], | |
| "all_match": bool(all_match), | |
| "identity_control": {"lin":int(rl_id),"cov":int(rc_id),"max_diff":float(maxd)}, | |
| "n_sampled": len(sampled), | |
| "n_total": n_total, | |
| "n_coframes": N_E_Q, | |
| "n_de_per_coframe": M_DE_Q, | |
| "convention": "all_lower_indices", | |
| "elapsed_seconds": elapsed, | |
| } | |
| out_file = os.path.join(os.path.dirname(__file__) or ".", "02_covariant_check.json") | |
| with open(out_file, "w") as f: | |
| json.dump(out, f, indent=2) | |
| print(f"\nSaved: {out_file}") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "quadratic": { | |
| "order": 2, | |
| "parity_even": 9, | |
| "parity_odd": 6, | |
| "total": 15, | |
| "n_patterns": 63, | |
| "n_eta": 42, | |
| "n_eps": 21, | |
| "n_monomials": 404, | |
| "eta_only_even": 8, | |
| "eta_only_odd": 2 | |
| }, | |
| "cubic": { | |
| "order": 3, | |
| "parity_even": 33, | |
| "parity_odd": 25, | |
| "total": 58, | |
| "n_patterns": 1041, | |
| "n_eta": 438, | |
| "n_eps": 603, | |
| "n_monomials": 5528, | |
| "eta_only_even": 26, | |
| "eta_only_odd": 9 | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """06_complete_lower_orders.py — Complete quadratic + cubic classification. | |
| All 7 blocks, both parities, η-only AND ε-contractions. | |
| Gives the definitive counts at orders 2 and 3. | |
| """ | |
| import numpy as np | |
| from itertools import combinations_with_replacement, combinations | |
| from collections import defaultdict | |
| import json, os, sys, time, functools | |
| print = functools.partial(print, flush=True) | |
| D = 4 | |
| eta = np.array([[-1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], dtype=int) | |
| eps_down = np.zeros((D,D,D,D), dtype=int) | |
| for i in range(D): | |
| for j in range(D): | |
| for k in range(D): | |
| for l in range(D): | |
| if len(set([i,j,k,l])) < D: continue | |
| s = 1 | |
| for a in range(D): | |
| for b in range(a+1,D): | |
| if [i,j,k,l][a] > [i,j,k,l][b]: s *= -1 | |
| eps_down[i,j,k,l] = s | |
| eps_up = np.einsum('ma,nb,rc,sd,abcd->mnrs', eta, eta, eta, eta, eps_down) | |
| BLOCKS = [ | |
| ("Shat", 2, +1), | |
| ("theta", 0, +1), | |
| ("W", 2, +1), | |
| ("T", 3, +1), | |
| ("V", 1, +1), | |
| ("A", 1, -1), | |
| ("s", 1, +1), | |
| ] | |
| BLOCK_DISPLAY = {"Shat": "Ŝ", "theta": "θ", "W": "W", "T": "T", | |
| "V": "V", "A": "𝒜", "s": "s"} | |
| COFRAME = {3, 4, 5, 6} | |
| TORSION = {3, 4, 5} | |
| DISPLACEMENT = {0, 1, 2} | |
| # ── Block component functions (same as 05) ─────────────────────────── | |
| def shat_component(mu, nu): | |
| terms = {} | |
| terms[('du', mu, nu)] = terms.get(('du', mu, nu), 0) + 2 | |
| terms[('du', nu, mu)] = terms.get(('du', nu, mu), 0) + 2 | |
| for rho in range(D): | |
| key = ('du', rho, rho) | |
| terms[key] = terms.get(key, 0) - eta[mu, nu] * eta[rho, rho] | |
| return {k: v for k, v in terms.items() if v != 0} | |
| def theta_component(): | |
| return {('du', mu, mu): int(eta[mu, mu]) for mu in range(D) if eta[mu, mu] != 0} | |
| def W_component(mu, nu): | |
| terms = {} | |
| terms[('du', mu, nu)] = terms.get(('du', mu, nu), 0) + 1 | |
| terms[('du', nu, mu)] = terms.get(('du', nu, mu), 0) - 1 | |
| return {k: v for k, v in terms.items() if v != 0} | |
| def T_component(rho, mu, nu): | |
| terms = {} | |
| for a in range(D): | |
| if eta[a, rho] == 0: continue | |
| terms[('de', mu, a, nu)] = terms.get(('de', mu, a, nu), 0) + eta[a, rho] | |
| terms[('de', nu, a, mu)] = terms.get(('de', nu, a, mu), 0) - eta[a, rho] | |
| return {k: v for k, v in terms.items() if v != 0} | |
| def V_component(mu): | |
| terms = {} | |
| for nu in range(D): | |
| for rho in range(D): | |
| if eta[nu, rho] == 0: continue | |
| for k, v in T_component(rho, nu, mu).items(): | |
| terms[k] = terms.get(k, 0) + eta[nu, rho] * v | |
| return {k: v for k, v in terms.items() if v != 0} | |
| def A_component(mu): | |
| terms = {} | |
| for nu in range(D): | |
| for rho in range(D): | |
| for sig in range(D): | |
| e = eps_up[mu, nu, rho, sig] | |
| if e == 0: continue | |
| for k, v in T_component(nu, rho, sig).items(): | |
| terms[k] = terms.get(k, 0) + e * v | |
| return {k: v for k, v in terms.items() if v != 0} # returns 6A; rank-invariant | |
| def s_component(mu): | |
| return {('de', mu, a, a): 1 for a in range(D)} | |
| def get_block_component(block_idx, indices): | |
| name = BLOCKS[block_idx][0] | |
| if name == "Shat": return shat_component(indices[0], indices[1]) | |
| elif name == "theta": return theta_component() | |
| elif name == "W": return W_component(indices[0], indices[1]) | |
| elif name == "T": return T_component(indices[0], indices[1], indices[2]) | |
| elif name == "V": return V_component(indices[0]) | |
| elif name == "A": return A_component(indices[0]) | |
| elif name == "s": return s_component(indices[0]) | |
| # ── Pairing ────────────────────────────────────────────────────────── | |
| def generate_pairings(indices): | |
| if len(indices) == 0: return [()] | |
| if len(indices) == 2: return [((indices[0], indices[1]),)] | |
| first = indices[0]; rest = indices[1:] | |
| result = [] | |
| for i, partner in enumerate(rest): | |
| remaining = rest[:i] + rest[i+1:] | |
| for sub in generate_pairings(remaining): | |
| result.append(((first, partner),) + sub) | |
| return result | |
| # ── Generic expansion (any number of blocks) ───────────────────────── | |
| def expand_eta(block_indices, pattern): | |
| """Expand η-only contraction for arbitrary block count.""" | |
| n_blocks = len(block_indices) | |
| n = [BLOCKS[b][1] for b in block_indices] | |
| total = sum(n) | |
| offsets = [sum(n[:i]) for i in range(n_blocks)] | |
| n_pairs = len(pattern) | |
| poly = defaultdict(int) | |
| def recurse(pair_idx, assignments): | |
| if pair_idx == n_pairs: | |
| eta_prod = 1 | |
| for (a, b) in pattern: | |
| eta_prod *= eta[assignments[a], assignments[b]] | |
| if eta_prod == 0: return | |
| components = [] | |
| for bi in range(n_blocks): | |
| s2 = offsets[bi]; e2 = s2 + n[bi] | |
| idx_vals = tuple(assignments[g] for g in range(s2, e2)) | |
| components.append(get_block_component(block_indices[bi], | |
| idx_vals if n[bi] > 0 else ())) | |
| # Multiply all components | |
| result_terms = components[0] | |
| for ci in range(1, n_blocks): | |
| new_terms = defaultdict(int) | |
| for k1, v1 in result_terms.items(): | |
| for k2, v2 in components[ci].items(): | |
| mono = tuple(sorted([k1, k2])) if isinstance(k1, tuple) and k1[0] in ('du','de') else tuple(sorted(list(k1) + [k2])) if not isinstance(k1[0], str) else None | |
| # Build degree-n monomial | |
| pass | |
| result_terms = new_terms | |
| # Actually, let's just build the product directly for each leaf | |
| pass | |
| a, b = pattern[pair_idx] | |
| for va in range(D): | |
| assignments[a] = va | |
| for vb in range(D): | |
| if eta[va, vb] == 0: continue | |
| assignments[b] = vb | |
| recurse(pair_idx + 1, assignments) | |
| # Simpler approach: at the leaf, multiply all block components | |
| def recurse2(pair_idx, assignments): | |
| if pair_idx == n_pairs: | |
| eta_prod = 1 | |
| for (a, b) in pattern: | |
| eta_prod *= eta[assignments[a], assignments[b]] | |
| if eta_prod == 0: return | |
| # Get all block components | |
| comp_list = [] | |
| for bi in range(n_blocks): | |
| s2 = offsets[bi]; e2 = s2 + n[bi] | |
| idx_vals = tuple(assignments[g] for g in range(s2, e2)) | |
| comp_list.append(get_block_component(block_indices[bi], | |
| idx_vals if n[bi] > 0 else ())) | |
| # Multiply: iterate over all combinations of terms | |
| def multiply_dicts(dicts, idx=0, current_keys=None, current_val=1): | |
| if current_keys is None: current_keys = [] | |
| if idx == len(dicts): | |
| mono = tuple(sorted(current_keys)) | |
| poly[mono] += eta_prod * current_val | |
| return | |
| for k, v in dicts[idx].items(): | |
| multiply_dicts(dicts, idx+1, current_keys + [k], current_val * v) | |
| multiply_dicts(comp_list) | |
| return | |
| a, b = pattern[pair_idx] | |
| for va in range(D): | |
| assignments[a] = va | |
| for vb in range(D): | |
| if eta[va, vb] == 0: continue | |
| assignments[b] = vb | |
| recurse2(pair_idx + 1, assignments) | |
| assignments = [0] * total | |
| recurse2(0, assignments) | |
| return {k: v for k, v in poly.items() if v != 0} | |
| def expand_epsilon(block_indices, eps_indices, eta_pattern): | |
| """Expand one ε+η contraction for arbitrary block count.""" | |
| n_blocks = len(block_indices) | |
| n = [BLOCKS[b][1] for b in block_indices] | |
| total = sum(n) | |
| offsets = [sum(n[:i]) for i in range(n_blocks)] | |
| n_eta = len(eta_pattern) | |
| poly = defaultdict(int) | |
| def recurse_eta(pair_idx, assignments): | |
| if pair_idx == n_eta: | |
| eta_prod = 1 | |
| for (a, b) in eta_pattern: | |
| eta_prod *= eta[assignments[a], assignments[b]] | |
| if eta_prod == 0: return | |
| eps_val = eps_down[assignments[eps_indices[0]], | |
| assignments[eps_indices[1]], | |
| assignments[eps_indices[2]], | |
| assignments[eps_indices[3]]] | |
| if eps_val == 0: return | |
| coeff = eps_val * eta_prod | |
| comp_list = [] | |
| for bi in range(n_blocks): | |
| s2 = offsets[bi]; e2 = s2 + n[bi] | |
| idx_vals = tuple(assignments[g] for g in range(s2, e2)) | |
| comp_list.append(get_block_component(block_indices[bi], | |
| idx_vals if n[bi] > 0 else ())) | |
| def multiply_dicts(dicts, idx=0, current_keys=None, current_val=1): | |
| if current_keys is None: current_keys = [] | |
| if idx == len(dicts): | |
| mono = tuple(sorted(current_keys)) | |
| poly[mono] += coeff * current_val | |
| return | |
| for k, v in dicts[idx].items(): | |
| multiply_dicts(dicts, idx+1, current_keys + [k], current_val * v) | |
| multiply_dicts(comp_list) | |
| return | |
| a, b = eta_pattern[pair_idx] | |
| for va in range(D): | |
| assignments[a] = va | |
| for vb in range(D): | |
| if eta[va, vb] == 0: continue | |
| assignments[b] = vb | |
| recurse_eta(pair_idx + 1, assignments) | |
| assignments = [0] * total | |
| # Iterate ε index assignments | |
| from itertools import product as iprod | |
| for vals in iprod(range(D), repeat=4): | |
| e = eps_down[vals[0], vals[1], vals[2], vals[3]] | |
| if e == 0: continue | |
| for idx, val in zip(eps_indices, vals): | |
| assignments[idx] = val | |
| recurse_eta(0, assignments) | |
| return {k: v for k, v in poly.items() if v != 0} | |
| def rank_mod_p(mat, p): | |
| m, n2 = mat.shape | |
| A = mat.astype(np.int64) % p | |
| A[A < 0] += p | |
| rank = 0 | |
| for col in range(n2): | |
| if rank >= m: break | |
| nz = np.nonzero(A[rank:, col])[0] | |
| if len(nz) == 0: continue | |
| found = nz[0] + rank | |
| if found != rank: | |
| A[[rank, found]] = A[[found, rank]] | |
| inv_piv = pow(int(A[rank, col]), p - 2, p) | |
| col_vals = A[:, col].copy() | |
| col_vals[rank] = 0 | |
| nz_rows = np.nonzero(col_vals)[0] | |
| if len(nz_rows) > 0: | |
| factors = (col_vals[nz_rows] * inv_piv) % p | |
| A[nz_rows] = (A[nz_rows] - factors[:, np.newaxis] * A[rank]) % p | |
| rank += 1 | |
| return rank | |
| def classify_order(order, label): | |
| """Full classification at a given polynomial order.""" | |
| print(f"\n{'='*60}") | |
| print(f"ORDER {order} ({label})") | |
| print(f"{'='*60}") | |
| # Enumerate all n-tuples with even total index count | |
| all_patterns = [] # (tuple, contraction_type, contraction_data, parity) | |
| for combo in combinations_with_replacement(range(len(BLOCKS)), order): | |
| n_idx = [BLOCKS[b][1] for b in combo] | |
| total = sum(n_idx) | |
| block_parity = 1 | |
| for b in combo: | |
| block_parity *= BLOCKS[b][2] | |
| if total % 2 != 0: | |
| continue | |
| all_indices = list(range(total)) | |
| # η-only patterns | |
| if total > 0: | |
| pairings = generate_pairings(all_indices) | |
| else: | |
| pairings = [()] | |
| for p in pairings: | |
| all_patterns.append((combo, 'eta', p, block_parity)) | |
| # ε patterns (k=1) | |
| if total >= 4 and (total - 4) % 2 == 0: | |
| for eps_idx in combinations(all_indices, 4): | |
| remaining = [x for x in all_indices if x not in eps_idx] | |
| if len(remaining) == 0: | |
| eta_pats = [()] | |
| else: | |
| eta_pats = generate_pairings(remaining) | |
| for ep in eta_pats: | |
| all_patterns.append((combo, 'eps', (eps_idx, ep), -block_parity)) | |
| n_total = len(all_patterns) | |
| n_eta = sum(1 for _,t,_,_ in all_patterns if t == 'eta') | |
| n_eps = sum(1 for _,t,_,_ in all_patterns if t == 'eps') | |
| print(f" η-only patterns: {n_eta}") | |
| print(f" ε patterns: {n_eps}") | |
| print(f" Total: {n_total}") | |
| # Expand symbolically | |
| print(f"\n Expanding {n_total} patterns...") | |
| all_polys = [] | |
| all_monomials = set() | |
| t0 = time.time() | |
| for idx, (combo, ctype, cdata, par) in enumerate(all_patterns): | |
| if ctype == 'eta': | |
| poly = expand_eta(combo, cdata) | |
| else: | |
| eps_idx, eta_pat = cdata | |
| poly = expand_epsilon(combo, eps_idx, eta_pat) | |
| all_polys.append(poly) | |
| all_monomials.update(poly.keys()) | |
| if (idx+1) % 100 == 0: | |
| print(f" {idx+1}/{n_total} ({time.time()-t0:.0f}s)") | |
| n_mon = len(all_monomials) | |
| n_nz = sum(1 for p in all_polys if p) | |
| print(f" {n_nz}/{n_total} nonzero, {n_mon} monomials ({time.time()-t0:.0f}s)") | |
| # Build coefficient matrix | |
| monomial_list = sorted(all_monomials) | |
| mon_idx = {m: i for i, m in enumerate(monomial_list)} | |
| C = np.zeros((n_total, n_mon), dtype=np.int64) | |
| for row, poly in enumerate(all_polys): | |
| for mono, coeff in poly.items(): | |
| C[row, mon_idx[mono]] = coeff | |
| nz_cols = np.any(C != 0, axis=0) | |
| C_nz = C[:, nz_cols] | |
| # Compute ranks by parity | |
| p_mod = 10007 | |
| even_rows = [i for i, (_,_,_,par) in enumerate(all_patterns) if par == +1] | |
| odd_rows = [i for i, (_,_,_,par) in enumerate(all_patterns) if par == -1] | |
| r_even = rank_mod_p(C_nz[even_rows, :].copy(), p_mod) if even_rows else 0 | |
| r_odd = rank_mod_p(C_nz[odd_rows, :].copy(), p_mod) if odd_rows else 0 | |
| r_total = rank_mod_p(C_nz.copy(), p_mod) | |
| print(f"\n RESULTS (order {order}):") | |
| print(f" Parity-even: {r_even}") | |
| print(f" Parity-odd: {r_odd}") | |
| print(f" Total: {r_total}") | |
| print(f" Check: {r_even}+{r_odd}={r_even+r_odd} vs {r_total}") | |
| # Sector breakdown | |
| for sector_name, sector_set in [("torsion", TORSION), ("coframe", COFRAME), ("full", set(range(7)))]: | |
| s_even = [i for i in even_rows if set(all_patterns[i][0]) <= sector_set] | |
| s_odd = [i for i in odd_rows if set(all_patterns[i][0]) <= sector_set] | |
| s_all = s_even + s_odd | |
| re = rank_mod_p(C_nz[s_even, :].copy(), p_mod) if s_even else 0 | |
| ro = rank_mod_p(C_nz[s_odd, :].copy(), p_mod) if s_odd else 0 | |
| print(f" {sector_name:10s}: even={re}, odd={ro}, total={re+ro}") | |
| # η-only sub-count | |
| eta_even = [i for i in even_rows if all_patterns[i][1] == 'eta'] | |
| eta_odd = [i for i in odd_rows if all_patterns[i][1] == 'eta'] | |
| re_eta = rank_mod_p(C_nz[eta_even, :].copy(), p_mod) if eta_even else 0 | |
| ro_eta = rank_mod_p(C_nz[eta_odd, :].copy(), p_mod) if eta_odd else 0 | |
| print(f" η-only: even={re_eta}, odd={ro_eta}") | |
| return { | |
| "order": order, | |
| "parity_even": r_even, | |
| "parity_odd": r_odd, | |
| "total": r_total, | |
| "n_patterns": n_total, | |
| "n_eta": n_eta, | |
| "n_eps": n_eps, | |
| "n_monomials": n_mon, | |
| "eta_only_even": re_eta, | |
| "eta_only_odd": ro_eta, | |
| } | |
| def main(): | |
| t0 = time.time() | |
| results = {} | |
| results["quadratic"] = classify_order(2, "QUADRATIC") | |
| results["cubic"] = classify_order(3, "CUBIC") | |
| elapsed = time.time() - t0 | |
| print(f"\n{'='*60}") | |
| print(f"SUMMARY") | |
| print(f"{'='*60}") | |
| for label in ["quadratic", "cubic"]: | |
| r = results[label] | |
| print(f" Order {r['order']}: even={r['parity_even']}, odd={r['parity_odd']}, total={r['total']}") | |
| print(f" η-only: even={r['eta_only_even']}, odd={r['eta_only_odd']}") | |
| print(f" Elapsed: {elapsed:.0f}s") | |
| out_file = os.path.join(os.path.dirname(__file__) or ".", "06_complete_lower_orders.json") | |
| with open(out_file, "w") as f: | |
| json.dump(results, f, indent=2) | |
| print(f"\nSaved: {out_file}") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """08_quartic_exact.py — EXACT quartic classification with ALL ε patterns. | |
| Complete computation: all η-only + ALL ε-contraction patterns for all | |
| quadruples including T⁴ (51975 ε patterns). No sampling. | |
| """ | |
| import numpy as np | |
| from itertools import combinations_with_replacement, combinations | |
| from collections import defaultdict | |
| import json, os, sys, time, functools, random | |
| print = functools.partial(print, flush=True) | |
| D = 4 | |
| eta = np.array([[-1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], dtype=int) | |
| eps_down = np.zeros((D,D,D,D), dtype=int) | |
| for i in range(D): | |
| for j in range(D): | |
| for k in range(D): | |
| for l in range(D): | |
| if len(set([i,j,k,l])) < D: continue | |
| s = 1 | |
| for a in range(D): | |
| for b in range(a+1,D): | |
| if [i,j,k,l][a] > [i,j,k,l][b]: s *= -1 | |
| eps_down[i,j,k,l] = s | |
| eps_up = np.einsum('ma,nb,rc,sd,abcd->mnrs', eta, eta, eta, eta, eps_down) | |
| BLOCKS = [ | |
| ("Shat", 2, +1), ("theta", 0, +1), ("W", 2, +1), | |
| ("T", 3, +1), ("V", 1, +1), ("A", 1, -1), ("s", 1, +1), | |
| ] | |
| DISP = {"Shat": "Ŝ", "theta": "θ", "W": "W", "T": "T", | |
| "V": "V", "A": "𝒜", "s": "s"} | |
| # ── Block component functions ──────────────────────────────────────── | |
| def shat_component(mu, nu): | |
| t = {} | |
| t[('du',mu,nu)] = t.get(('du',mu,nu),0)+2 | |
| t[('du',nu,mu)] = t.get(('du',nu,mu),0)+2 | |
| for r in range(D): | |
| k = ('du',r,r); t[k] = t.get(k,0) - eta[mu,nu]*eta[r,r] | |
| return {k:v for k,v in t.items() if v!=0} | |
| def theta_component(): | |
| return {('du',m,m): int(eta[m,m]) for m in range(D) if eta[m,m]!=0} | |
| def W_component(mu, nu): | |
| t = {} | |
| t[('du',mu,nu)] = t.get(('du',mu,nu),0)+1 | |
| t[('du',nu,mu)] = t.get(('du',nu,mu),0)-1 | |
| return {k:v for k,v in t.items() if v!=0} | |
| def T_component(rho, mu, nu): | |
| t = {} | |
| for a in range(D): | |
| if eta[a,rho]==0: continue | |
| t[('de',mu,a,nu)] = t.get(('de',mu,a,nu),0) + eta[a,rho] | |
| t[('de',nu,a,mu)] = t.get(('de',nu,a,mu),0) - eta[a,rho] | |
| return {k:v for k,v in t.items() if v!=0} | |
| def V_component(mu): | |
| t = {} | |
| for nu in range(D): | |
| for rho in range(D): | |
| if eta[nu,rho]==0: continue | |
| for k,v in T_component(rho,nu,mu).items(): | |
| t[k] = t.get(k,0) + eta[nu,rho]*v | |
| return {k:v for k,v in t.items() if v!=0} | |
| def A_component(mu): | |
| t = {} | |
| for nu in range(D): | |
| for rho in range(D): | |
| for sig in range(D): | |
| e = eps_up[mu,nu,rho,sig] | |
| if e==0: continue | |
| for k,v in T_component(nu,rho,sig).items(): | |
| t[k] = t.get(k,0) + e*v | |
| return {k:v for k,v in t.items() if v!=0} # raw = 6A | |
| def s_component(mu): | |
| return {('de',mu,a,a): 1 for a in range(D)} | |
| def get_comp(bi, idx): | |
| n = BLOCKS[bi][0] | |
| if n=="Shat": return shat_component(idx[0],idx[1]) | |
| if n=="theta": return theta_component() | |
| if n=="W": return W_component(idx[0],idx[1]) | |
| if n=="T": return T_component(idx[0],idx[1],idx[2]) | |
| if n=="V": return V_component(idx[0]) | |
| if n=="A": return A_component(idx[0]) | |
| if n=="s": return s_component(idx[0]) | |
| def generate_pairings(indices): | |
| if len(indices)==0: return [()] | |
| if len(indices)==2: return [((indices[0],indices[1]),)] | |
| first = indices[0]; rest = indices[1:] | |
| result = [] | |
| for i,p in enumerate(rest): | |
| rem = rest[:i]+rest[i+1:] | |
| for sub in generate_pairings(rem): | |
| result.append(((first,p),)+sub) | |
| return result | |
| # ── Generic expansion ──────────────────────────────────────────────── | |
| def expand_eta_quartic(blocks, pattern): | |
| n = [BLOCKS[b][1] for b in blocks] | |
| total = sum(n); off = [sum(n[:i]) for i in range(4)] | |
| n_pairs = len(pattern); poly = defaultdict(int) | |
| def rec(pi, asn): | |
| if pi==n_pairs: | |
| ep = 1 | |
| for a,b in pattern: ep *= eta[asn[a],asn[b]] | |
| if ep==0: return | |
| cs = [] | |
| for bi in range(4): | |
| iv = tuple(asn[g] for g in range(off[bi],off[bi]+n[bi])) | |
| cs.append(get_comp(blocks[bi], iv if n[bi]>0 else ())) | |
| def mul(ds,i=0,ks=None,v=1): | |
| if ks is None: ks=[] | |
| if i==len(ds): | |
| poly[tuple(sorted(ks))] += ep*v; return | |
| for k2,v2 in ds[i].items(): mul(ds,i+1,ks+[k2],v*v2) | |
| mul(cs); return | |
| a,b = pattern[pi] | |
| for va in range(D): | |
| asn[a]=va | |
| for vb in range(D): | |
| if eta[va,vb]==0: continue | |
| asn[b]=vb; rec(pi+1,asn) | |
| rec(0,[0]*total) | |
| return {k:v for k,v in poly.items() if v!=0} | |
| def expand_eps_quartic(blocks, eps_idx, eta_pat): | |
| from itertools import product as iprod | |
| n = [BLOCKS[b][1] for b in blocks] | |
| total = sum(n); off = [sum(n[:i]) for i in range(4)] | |
| n_eta = len(eta_pat); poly = defaultdict(int) | |
| def rec_eta(pi, asn): | |
| if pi==n_eta: | |
| ep = 1 | |
| for a,b in eta_pat: ep *= eta[asn[a],asn[b]] | |
| if ep==0: return | |
| ev = eps_down[asn[eps_idx[0]],asn[eps_idx[1]], | |
| asn[eps_idx[2]],asn[eps_idx[3]]] | |
| if ev==0: return | |
| coeff = ev*ep | |
| cs = [] | |
| for bi in range(4): | |
| iv = tuple(asn[g] for g in range(off[bi],off[bi]+n[bi])) | |
| cs.append(get_comp(blocks[bi], iv if n[bi]>0 else ())) | |
| def mul(ds,i=0,ks=None,v=1): | |
| if ks is None: ks=[] | |
| if i==len(ds): | |
| poly[tuple(sorted(ks))] += coeff*v; return | |
| for k2,v2 in ds[i].items(): mul(ds,i+1,ks+[k2],v*v2) | |
| mul(cs); return | |
| a,b = eta_pat[pi] | |
| for va in range(D): | |
| asn[a]=va | |
| for vb in range(D): | |
| if eta[va,vb]==0: continue | |
| asn[b]=vb; rec_eta(pi+1,asn) | |
| asn = [0]*total | |
| for vals in iprod(range(D), repeat=4): | |
| e = eps_down[vals[0],vals[1],vals[2],vals[3]] | |
| if e==0: continue | |
| for idx,val in zip(eps_idx,vals): asn[idx]=val | |
| rec_eta(0, asn) | |
| return {k:v for k,v in poly.items() if v!=0} | |
| def rank_mod_p(mat, p): | |
| m,nc = mat.shape | |
| A = mat.astype(np.int64)%p; A[A<0]+=p | |
| rank = 0 | |
| for col in range(nc): | |
| if rank>=m: break | |
| nz = np.nonzero(A[rank:,col])[0] | |
| if len(nz)==0: continue | |
| f = nz[0]+rank | |
| if f!=rank: A[[rank,f]]=A[[f,rank]] | |
| inv = pow(int(A[rank,col]),p-2,p) | |
| cv = A[:,col].copy(); cv[rank]=0 | |
| nzr = np.nonzero(cv)[0] | |
| if len(nzr)>0: | |
| fac = (cv[nzr]*inv)%p | |
| A[nzr] = (A[nzr]-fac[:,np.newaxis]*A[rank])%p | |
| rank += 1 | |
| if rank%20==0: print(f" rank {rank}, col {col}/{nc}") | |
| return rank | |
| # ── T⁴ ε sampling ─────────────────────────────────────────────────── | |
| def classify_t4_eps_indices(eps_idx): | |
| """Classify 4 chosen indices by which T block they belong to.""" | |
| groups = [0]*4 # how many from each T | |
| for i in eps_idx: | |
| groups[i // 3] += 1 | |
| return tuple(sorted(groups, reverse=True)) | |
| def sample_t4_eps_patterns(n_sample=200, seed=42): | |
| """Sample T⁴ ε patterns from non-trivial distribution types.""" | |
| rng = random.Random(seed) | |
| T_IDX = 3 # T block index | |
| quad = (T_IDX, T_IDX, T_IDX, T_IDX) | |
| all_indices = list(range(12)) | |
| # Categorize all C(12,4) = 495 ε-index choices | |
| by_type = defaultdict(list) | |
| for eps_idx in combinations(all_indices, 4): | |
| dist = classify_t4_eps_indices(eps_idx) | |
| by_type[dist].append(eps_idx) | |
| print(f" T⁴ ε distribution types:") | |
| for dist, choices in sorted(by_type.items()): | |
| remaining = [x for x in all_indices if x not in choices[0]] | |
| n_pair = len(generate_pairings(remaining)) | |
| total = len(choices) * n_pair | |
| print(f" {dist}: {len(choices)} index choices × {n_pair} pairings = {total}") | |
| # Skip (3,1,0,0) — equivalent to A·T³, already in η-only basis | |
| patterns = [] | |
| for dist, choices in by_type.items(): | |
| if dist == (3,1,0,0): | |
| continue | |
| # Sample index choices | |
| sampled = choices if len(choices) <= 20 else rng.sample(choices, 20) | |
| for eps_idx in sampled: | |
| remaining = [x for x in all_indices if x not in eps_idx] | |
| pairings = generate_pairings(remaining) | |
| # Sample pairings | |
| sampled_p = pairings if len(pairings) <= 10 else rng.sample(pairings, 10) | |
| for p in sampled_p: | |
| patterns.append((quad, eps_idx, p)) | |
| print(f" Sampled {len(patterns)} T⁴ ε patterns (excluding (3,1,0,0) type)") | |
| return patterns | |
| # ── Main ───────────────────────────────────────────────────────────── | |
| def main(): | |
| t0 = time.time() | |
| print("="*60) | |
| print("QUARTIC ε-CONTRACTION EXTENSION") | |
| print("="*60) | |
| # ── Step 1: Enumerate all η-only quartic patterns ──────────────── | |
| print("\nStep 1: Enumerate η-only patterns (all blocks, both parities)...") | |
| eta_patterns = [] | |
| for quad in combinations_with_replacement(range(len(BLOCKS)), 4): | |
| ni = [BLOCKS[b][1] for b in quad] | |
| total = sum(ni) | |
| if total % 2 != 0: continue | |
| pairings = generate_pairings(list(range(total))) if total>0 else [()] | |
| bp = 1 | |
| for b in quad: bp *= BLOCKS[b][2] | |
| for p in pairings: | |
| eta_patterns.append((quad, 'eta', p, bp)) | |
| n_eta = len(eta_patterns) | |
| print(f" η-only patterns: {n_eta}") | |
| # ── Step 2: Enumerate ALL ε patterns (including T⁴) ──────────── | |
| print("\nStep 2: Enumerate ALL ε patterns...") | |
| eps_patterns = [] | |
| for quad in combinations_with_replacement(range(len(BLOCKS)), 4): | |
| ni = [BLOCKS[b][1] for b in quad] | |
| total = sum(ni) | |
| if total % 2 != 0 or total < 4: continue | |
| if (total - 4) % 2 != 0: continue | |
| bp = 1 | |
| for b in quad: bp *= BLOCKS[b][2] | |
| for eps_idx in combinations(range(total), 4): | |
| rem = [x for x in range(total) if x not in eps_idx] | |
| ep = generate_pairings(rem) if rem else [()] | |
| for p in ep: | |
| eps_patterns.append((quad, 'eps', (eps_idx, p), -bp)) | |
| n_eps = len(eps_patterns) | |
| print(f" Total ε patterns: {n_eps}") | |
| # ── Combine all patterns ───────────────────────────────────────── | |
| all_patterns = eta_patterns + eps_patterns | |
| N = len(all_patterns) | |
| print(f"\n Total: {n_eta} η + {n_eps} ε = {N}") | |
| # ── Step 4: Symbolic expansion ─────────────────────────────────── | |
| print(f"\nStep 4: Expand {N} patterns symbolically...") | |
| all_polys = [] | |
| all_monomials = set() | |
| for idx, (quad, ctype, cdata, par) in enumerate(all_patterns): | |
| if ctype == 'eta': | |
| poly = expand_eta_quartic(quad, cdata) | |
| else: | |
| eps_idx, eta_pat = cdata | |
| poly = expand_eps_quartic(quad, eps_idx, eta_pat) | |
| all_polys.append(poly) | |
| all_monomials.update(poly.keys()) | |
| if (idx+1) % 500 == 0: | |
| el = time.time()-t0 | |
| print(f" {idx+1}/{N} ({el:.0f}s, {len(all_monomials)} monomials)") | |
| n_mon = len(all_monomials) | |
| n_nz = sum(1 for p in all_polys if p) | |
| el = time.time()-t0 | |
| print(f"\n {n_nz}/{N} nonzero, {n_mon} monomials ({el:.0f}s)") | |
| # ── Step 5: Build matrix and compute ranks ─────────────────────── | |
| print(f"\nStep 5: Build coefficient matrix ({N} × {n_mon})...") | |
| monomial_list = sorted(all_monomials) | |
| mon_idx = {m:i for i,m in enumerate(monomial_list)} | |
| C = np.zeros((N, n_mon), dtype=np.int64) | |
| for row, poly in enumerate(all_polys): | |
| for mono, coeff in poly.items(): | |
| C[row, mon_idx[mono]] = coeff | |
| nz = np.any(C!=0, axis=0) | |
| C_nz = C[:, nz] | |
| print(f" Nonzero columns: {C_nz.shape[1]}") | |
| p_mod = 10007 | |
| even_rows = [i for i in range(N) if all_patterns[i][3]==+1] | |
| odd_rows = [i for i in range(N) if all_patterns[i][3]==-1] | |
| print(f"\nStep 6: Compute ranks (mod {p_mod})...") | |
| print(f" Parity-even ({len(even_rows)} rows)...") | |
| r_even = rank_mod_p(C_nz[even_rows,:].copy(), p_mod) | |
| print(f" → Parity-even rank: {r_even}") | |
| print(f" Parity-odd ({len(odd_rows)} rows)...") | |
| r_odd = rank_mod_p(C_nz[odd_rows,:].copy(), p_mod) | |
| print(f" → Parity-odd rank: {r_odd}") | |
| r_total = r_even + r_odd | |
| print(f" Total: {r_total}") | |
| # ── η-only sub-ranks (cross-check) ─────────────────────────────── | |
| print(f"\nStep 7: η-only cross-checks...") | |
| eta_even = [i for i in range(n_eta) if all_patterns[i][3]==+1] | |
| eta_odd = [i for i in range(n_eta) if all_patterns[i][3]==-1] | |
| re_eta = rank_mod_p(C_nz[eta_even,:].copy(), p_mod) | |
| ro_eta = rank_mod_p(C_nz[eta_odd,:].copy(), p_mod) | |
| print(f" η-only even: {re_eta} (expect 156)") | |
| print(f" η-only odd: {ro_eta} (expect 62)") | |
| # ── Second prime verification ──────────────────────────────────── | |
| p2 = 10009 | |
| print(f"\nStep 8: Verify with second prime {p2}...") | |
| r_even2 = rank_mod_p(C_nz[even_rows,:].copy(), p2) | |
| r_odd2 = rank_mod_p(C_nz[odd_rows,:].copy(), p2) | |
| print(f" Even: {r_even2} (expect {r_even})") | |
| print(f" Odd: {r_odd2} (expect {r_odd})") | |
| # ── Results ────────────────────────────────────────────────────── | |
| elapsed = time.time()-t0 | |
| print(f"\n{'='*60}") | |
| print(f"RESULTS — QUARTIC η+ε (T⁴ ε sampled)") | |
| print(f"{'='*60}") | |
| print(f" Parity-even: {r_even} (η-only: {re_eta})") | |
| print(f" Parity-odd: {r_odd} (η-only: {ro_eta})") | |
| print(f" Total: {r_total}") | |
| print(f" ε added (even): {r_even - re_eta}") | |
| print(f" ε added (odd): {r_odd - ro_eta}") | |
| print(f" ε added total: {r_total - re_eta - ro_eta}") | |
| print(f" Elapsed: {elapsed:.0f}s") | |
| results = { | |
| "quartic_even": int(r_even), | |
| "quartic_odd": int(r_odd), | |
| "quartic_total": int(r_total), | |
| "quartic_even_eta_only": int(re_eta), | |
| "quartic_odd_eta_only": int(ro_eta), | |
| "eps_added_even": int(r_even - re_eta), | |
| "eps_added_odd": int(r_odd - ro_eta), | |
| "n_eta_patterns": n_eta, | |
| "n_eps_patterns": n_eps, | |
| "prime1": p_mod, | |
| "prime2": p2, | |
| "rank_even_p2": int(r_even2), | |
| "rank_odd_p2": int(r_odd2), | |
| "elapsed_seconds": elapsed, | |
| "method": "exact — all patterns expanded, no sampling" | |
| } | |
| out = os.path.join(os.path.dirname(__file__) or ".", "08_quartic_exact.json") | |
| with open(out, "w") as f: | |
| json.dump(results, f, indent=2) | |
| print(f"\nSaved: {out}") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "parity_odd_rank": 181, | |
| "parity_odd_rank_p2": 181, | |
| "primes": [ | |
| 10007, | |
| 10009 | |
| ], | |
| "n_patterns": 73407, | |
| "n_monomials": 64608, | |
| "elapsed_seconds": 11662.899071931839, | |
| "method": "incremental GE, exact, no sampling, both \u03b7 and \u03b5" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """09_parity_odd_incremental.py — Exact parity-odd quartic rank via incremental GE. | |
| The full computation (08) confirmed parity-even = 209 but OOM'd on parity-odd. | |
| This script computes parity-odd by processing rows incrementally: | |
| maintain a (rank × n_cols) basis matrix (~200 × 130K = 208 MB), | |
| never build the full 73K × 130K dense matrix. | |
| Strategy: expand each parity-odd pattern, immediately reduce against | |
| the current basis, add if independent, discard if dependent. | |
| """ | |
| import numpy as np | |
| from itertools import combinations_with_replacement, combinations | |
| from collections import defaultdict | |
| import json, os, sys, time, functools | |
| print = functools.partial(print, flush=True) | |
| D = 4 | |
| eta = np.array([[-1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], dtype=int) | |
| eps_down = np.zeros((D,D,D,D), dtype=int) | |
| for i in range(D): | |
| for j in range(D): | |
| for k in range(D): | |
| for l in range(D): | |
| if len(set([i,j,k,l])) < D: continue | |
| s = 1 | |
| for a in range(D): | |
| for b in range(a+1,D): | |
| if [i,j,k,l][a] > [i,j,k,l][b]: s *= -1 | |
| eps_down[i,j,k,l] = s | |
| eps_up = np.einsum('ma,nb,rc,sd,abcd->mnrs', eta, eta, eta, eta, eps_down) | |
| BLOCKS = [ | |
| ("Shat",2,+1), ("theta",0,+1), ("W",2,+1), | |
| ("T",3,+1), ("V",1,+1), ("A",1,-1), ("s",1,+1), | |
| ] | |
| def shat_component(mu,nu): | |
| t={}; t[('du',mu,nu)]=t.get(('du',mu,nu),0)+2; t[('du',nu,mu)]=t.get(('du',nu,mu),0)+2 | |
| for r in range(D): k=('du',r,r); t[k]=t.get(k,0)-eta[mu,nu]*eta[r,r] | |
| return {k:v for k,v in t.items() if v!=0} | |
| def theta_component(): | |
| return {('du',m,m):int(eta[m,m]) for m in range(D) if eta[m,m]!=0} | |
| def W_component(mu,nu): | |
| t={}; t[('du',mu,nu)]=t.get(('du',mu,nu),0)+1; t[('du',nu,mu)]=t.get(('du',nu,mu),0)-1 | |
| return {k:v for k,v in t.items() if v!=0} | |
| def T_component(rho,mu,nu): | |
| t={} | |
| for a in range(D): | |
| if eta[a,rho]==0: continue | |
| t[('de',mu,a,nu)]=t.get(('de',mu,a,nu),0)+eta[a,rho] | |
| t[('de',nu,a,mu)]=t.get(('de',nu,a,mu),0)-eta[a,rho] | |
| return {k:v for k,v in t.items() if v!=0} | |
| def V_component(mu): | |
| t={} | |
| for nu in range(D): | |
| for rho in range(D): | |
| if eta[nu,rho]==0: continue | |
| for k,v in T_component(rho,nu,mu).items(): t[k]=t.get(k,0)+eta[nu,rho]*v | |
| return {k:v for k,v in t.items() if v!=0} | |
| def A_component(mu): | |
| t={} | |
| for nu in range(D): | |
| for rho in range(D): | |
| for sig in range(D): | |
| e=eps_up[mu,nu,rho,sig] | |
| if e==0: continue | |
| for k,v in T_component(nu,rho,sig).items(): t[k]=t.get(k,0)+e*v | |
| return {k:v for k,v in t.items() if v!=0} | |
| def s_component(mu): | |
| return {('de',mu,a,a):1 for a in range(D)} | |
| def get_comp(bi,idx): | |
| n=BLOCKS[bi][0] | |
| if n=="Shat": return shat_component(idx[0],idx[1]) | |
| if n=="theta": return theta_component() | |
| if n=="W": return W_component(idx[0],idx[1]) | |
| if n=="T": return T_component(idx[0],idx[1],idx[2]) | |
| if n=="V": return V_component(idx[0]) | |
| if n=="A": return A_component(idx[0]) | |
| if n=="s": return s_component(idx[0]) | |
| def generate_pairings(indices): | |
| if len(indices)==0: return [()] | |
| if len(indices)==2: return [((indices[0],indices[1]),)] | |
| first=indices[0]; rest=indices[1:] | |
| result=[] | |
| for i,p in enumerate(rest): | |
| rem=rest[:i]+rest[i+1:] | |
| for sub in generate_pairings(rem): result.append(((first,p),)+sub) | |
| return result | |
| def expand_eta(blocks, pattern): | |
| n=[BLOCKS[b][1] for b in blocks]; total=sum(n); off=[sum(n[:i]) for i in range(4)] | |
| np2=len(pattern); poly=defaultdict(int) | |
| def rec(pi,asn): | |
| if pi==np2: | |
| ep=1 | |
| for a,b in pattern: ep*=eta[asn[a],asn[b]] | |
| if ep==0: return | |
| cs=[] | |
| for bi in range(4): | |
| iv=tuple(asn[g] for g in range(off[bi],off[bi]+n[bi])) | |
| cs.append(get_comp(blocks[bi], iv if n[bi]>0 else ())) | |
| def mul(ds,i=0,ks=None,v=1): | |
| if ks is None: ks=[] | |
| if i==len(ds): poly[tuple(sorted(ks))]+=ep*v; return | |
| for k2,v2 in ds[i].items(): mul(ds,i+1,ks+[k2],v*v2) | |
| mul(cs); return | |
| a,b=pattern[pi] | |
| for va in range(D): | |
| asn[a]=va | |
| for vb in range(D): | |
| if eta[va,vb]==0: continue | |
| asn[b]=vb; rec(pi+1,asn) | |
| rec(0,[0]*total) | |
| return {k:v for k,v in poly.items() if v!=0} | |
| def expand_eps(blocks, eps_idx, eta_pat): | |
| from itertools import product as iprod | |
| n=[BLOCKS[b][1] for b in blocks]; total=sum(n); off=[sum(n[:i]) for i in range(4)] | |
| ne=len(eta_pat); poly=defaultdict(int) | |
| def rec_eta(pi,asn): | |
| if pi==ne: | |
| ep=1 | |
| for a,b in eta_pat: ep*=eta[asn[a],asn[b]] | |
| if ep==0: return | |
| ev=eps_down[asn[eps_idx[0]],asn[eps_idx[1]],asn[eps_idx[2]],asn[eps_idx[3]]] | |
| if ev==0: return | |
| coeff=ev*ep; cs=[] | |
| for bi in range(4): | |
| iv=tuple(asn[g] for g in range(off[bi],off[bi]+n[bi])) | |
| cs.append(get_comp(blocks[bi], iv if n[bi]>0 else ())) | |
| def mul(ds,i=0,ks=None,v=1): | |
| if ks is None: ks=[] | |
| if i==len(ds): poly[tuple(sorted(ks))]+=coeff*v; return | |
| for k2,v2 in ds[i].items(): mul(ds,i+1,ks+[k2],v*v2) | |
| mul(cs); return | |
| a,b=eta_pat[pi] | |
| for va in range(D): | |
| asn[a]=va | |
| for vb in range(D): | |
| if eta[va,vb]==0: continue | |
| asn[b]=vb; rec_eta(pi+1,asn) | |
| asn=[0]*total | |
| for vals in iprod(range(D), repeat=4): | |
| e=eps_down[vals[0],vals[1],vals[2],vals[3]] | |
| if e==0: continue | |
| for idx,val in zip(eps_idx,vals): asn[idx]=val | |
| rec_eta(0,asn) | |
| return {k:v for k,v in poly.items() if v!=0} | |
| def main(): | |
| t0 = time.time() | |
| p = 10007 | |
| print("="*60) | |
| print("PARITY-ODD QUARTIC — INCREMENTAL EXACT RANK") | |
| print("="*60) | |
| # ── Phase 1: Collect ALL monomials (from 08's output) ──────────── | |
| # We know there are 130650 monomials. We need to discover them | |
| # as we expand patterns. Build the monomial index on the fly. | |
| monomial_map = {} # monomial → column index | |
| n_cols = 0 | |
| # Basis matrix: rows are the current independent patterns (mod p) | |
| # Shape: (rank, n_cols). Grows in columns as new monomials appear. | |
| basis = None # will be initialized on first row | |
| rank = 0 | |
| pivot_cols = [] | |
| def add_row_to_basis(poly): | |
| nonlocal basis, rank, n_cols, monomial_map | |
| if not poly: | |
| return False | |
| # Ensure all monomials have indices | |
| for mono in poly: | |
| if mono not in monomial_map: | |
| monomial_map[mono] = n_cols | |
| n_cols += 1 | |
| # Extend basis columns | |
| if basis is not None: | |
| ext = np.zeros((rank, 1), dtype=np.int64) | |
| basis = np.hstack([basis, ext]) | |
| # Build row vector | |
| row = np.zeros(n_cols, dtype=np.int64) | |
| for mono, coeff in poly.items(): | |
| row[monomial_map[mono]] = coeff % p | |
| row[row < 0] += p | |
| if basis is None or rank == 0: | |
| # First row | |
| nz = np.nonzero(row)[0] | |
| if len(nz) == 0: | |
| return False | |
| basis = row.reshape(1, -1) | |
| rank = 1 | |
| pivot_cols.append(int(nz[0])) | |
| # Normalize | |
| inv = pow(int(basis[0, nz[0]]), p-2, p) | |
| basis[0] = (basis[0] * inv) % p | |
| return True | |
| # Extend row if basis has grown | |
| if len(row) < basis.shape[1]: | |
| row = np.pad(row, (0, basis.shape[1] - len(row))) | |
| elif len(row) > basis.shape[1]: | |
| ext = np.zeros((rank, len(row) - basis.shape[1]), dtype=np.int64) | |
| basis = np.hstack([basis, ext]) | |
| # Reduce row against existing basis | |
| for i in range(rank): | |
| pc = pivot_cols[i] | |
| if pc < len(row) and row[pc] != 0: | |
| factor = row[pc] # basis[i, pc] is already 1 | |
| row = (row - factor * basis[i]) % p | |
| # Check if row is nonzero | |
| nz = np.nonzero(row)[0] | |
| if len(nz) == 0: | |
| return False | |
| # New independent row — add to basis | |
| pc_new = int(nz[0]) | |
| inv = pow(int(row[pc_new]), p-2, p) | |
| row = (row * inv) % p | |
| basis = np.vstack([basis, row.reshape(1, -1)]) | |
| pivot_cols.append(pc_new) | |
| rank += 1 | |
| # Back-substitute to keep basis reduced | |
| for i in range(rank - 1): | |
| if basis[i, pc_new] != 0: | |
| factor = basis[i, pc_new] | |
| basis[i] = (basis[i] - factor * basis[-1]) % p | |
| return True | |
| # ── Phase 2: Enumerate and process parity-odd patterns ─────────── | |
| print("\nProcessing parity-odd patterns incrementally...") | |
| n_processed = 0 | |
| n_total_odd = 0 | |
| for quad in combinations_with_replacement(range(len(BLOCKS)), 4): | |
| ni = [BLOCKS[b][1] for b in quad] | |
| total = sum(ni) | |
| if total % 2 != 0: continue | |
| bp = 1 | |
| for b in quad: bp *= BLOCKS[b][2] | |
| all_indices = list(range(total)) | |
| # η-only parity-odd: bp == -1 | |
| if bp == -1: | |
| pairings = generate_pairings(all_indices) if total > 0 else [()] | |
| for pat in pairings: | |
| n_total_odd += 1 | |
| poly = expand_eta(quad, pat) | |
| added = add_row_to_basis(poly) | |
| n_processed += 1 | |
| if n_processed % 500 == 0: | |
| el = time.time() - t0 | |
| print(f" {n_processed} processed, rank={rank}, monomials={n_cols} ({el:.0f}s)") | |
| # ε parity-odd: -bp == -1 → bp == +1 (even A count) | |
| if bp == +1 and total >= 4 and (total-4) % 2 == 0: | |
| for eps_idx in combinations(all_indices, 4): | |
| rem = [x for x in all_indices if x not in eps_idx] | |
| eta_pats = generate_pairings(rem) if rem else [()] | |
| for ep in eta_pats: | |
| n_total_odd += 1 | |
| poly = expand_eps(quad, eps_idx, ep) | |
| added = add_row_to_basis(poly) | |
| n_processed += 1 | |
| if n_processed % 500 == 0: | |
| el = time.time() - t0 | |
| print(f" {n_processed} processed, rank={rank}, monomials={n_cols} ({el:.0f}s)") | |
| elapsed = time.time() - t0 | |
| print(f"\n Done: {n_processed} patterns, rank={rank}, {n_cols} monomials ({elapsed:.0f}s)") | |
| # ── Verify with second prime ───────────────────────────────────── | |
| print(f"\nVerifying with second prime 10009...") | |
| p2 = 10009 | |
| # Re-run with second prime (re-expand everything) | |
| monomial_map2 = {}; n_cols2 = 0; basis2 = None; rank2 = 0; pivot_cols2 = [] | |
| def add_row_p2(poly): | |
| nonlocal basis2, rank2, n_cols2, monomial_map2, pivot_cols2 | |
| if not poly: return False | |
| for mono in poly: | |
| if mono not in monomial_map2: | |
| monomial_map2[mono] = n_cols2; n_cols2 += 1 | |
| if basis2 is not None: | |
| basis2 = np.hstack([basis2, np.zeros((rank2,1),dtype=np.int64)]) | |
| row = np.zeros(n_cols2, dtype=np.int64) | |
| for mono,coeff in poly.items(): row[monomial_map2[mono]] = coeff % p2 | |
| row[row<0] += p2 | |
| if basis2 is None or rank2==0: | |
| nz=np.nonzero(row)[0] | |
| if len(nz)==0: return False | |
| basis2=row.reshape(1,-1); rank2=1; pivot_cols2.append(int(nz[0])) | |
| inv=pow(int(basis2[0,nz[0]]),p2-2,p2); basis2[0]=(basis2[0]*inv)%p2 | |
| return True | |
| if len(row)<basis2.shape[1]: row=np.pad(row,(0,basis2.shape[1]-len(row))) | |
| elif len(row)>basis2.shape[1]: | |
| basis2=np.hstack([basis2,np.zeros((rank2,len(row)-basis2.shape[1]),dtype=np.int64)]) | |
| for i in range(rank2): | |
| pc=pivot_cols2[i] | |
| if pc<len(row) and row[pc]!=0: | |
| row=(row-row[pc]*basis2[i])%p2 | |
| nz=np.nonzero(row)[0] | |
| if len(nz)==0: return False | |
| pc_new=int(nz[0]); inv=pow(int(row[pc_new]),p2-2,p2) | |
| row=(row*inv)%p2; basis2=np.vstack([basis2,row.reshape(1,-1)]) | |
| pivot_cols2.append(pc_new); rank2+=1 | |
| for i in range(rank2-1): | |
| if basis2[i,pc_new]!=0: | |
| basis2[i]=(basis2[i]-basis2[i,pc_new]*basis2[-1])%p2 | |
| return True | |
| n2 = 0 | |
| for quad in combinations_with_replacement(range(len(BLOCKS)), 4): | |
| ni=[BLOCKS[b][1] for b in quad]; total=sum(ni) | |
| if total%2!=0: continue | |
| bp=1 | |
| for b in quad: bp*=BLOCKS[b][2] | |
| all_indices=list(range(total)) | |
| if bp==-1: | |
| pairings=generate_pairings(all_indices) if total>0 else [()] | |
| for pat in pairings: | |
| poly=expand_eta(quad,pat); add_row_p2(poly); n2+=1 | |
| if n2%2000==0: print(f" p2: {n2} processed, rank={rank2}") | |
| if bp==+1 and total>=4 and (total-4)%2==0: | |
| for eps_idx in combinations(all_indices,4): | |
| rem=[x for x in all_indices if x not in eps_idx] | |
| eta_pats=generate_pairings(rem) if rem else [()] | |
| for ep in eta_pats: | |
| poly=expand_eps(quad,eps_idx,ep); add_row_p2(poly); n2+=1 | |
| if n2%2000==0: print(f" p2: {n2} processed, rank={rank2}") | |
| elapsed2 = time.time() - t0 | |
| print(f"\n p2 done: rank={rank2}") | |
| # ── Results ────────────────────────────────────────────────────── | |
| print(f"\n{'='*60}") | |
| print(f"RESULTS — PARITY-ODD QUARTIC (EXACT)") | |
| print(f"{'='*60}") | |
| print(f" Prime {p}: rank = {rank}") | |
| print(f" Prime {p2}: rank = {rank2}") | |
| print(f" Agreement: {'YES' if rank==rank2 else 'NO'}") | |
| print(f" Total patterns: {n_total_odd}") | |
| print(f" Monomials: {n_cols}") | |
| print(f" Elapsed: {elapsed2:.0f}s") | |
| results = { | |
| "parity_odd_rank": rank, | |
| "parity_odd_rank_p2": rank2, | |
| "primes": [p, p2], | |
| "n_patterns": n_total_odd, | |
| "n_monomials": n_cols, | |
| "elapsed_seconds": elapsed2, | |
| "method": "incremental GE, exact, no sampling, both η and ε" | |
| } | |
| out = os.path.join(os.path.dirname(__file__) or ".", "09_parity_odd_incremental.json") | |
| with open(out, "w") as f: | |
| json.dump(results, f, indent=2) | |
| print(f"\nSaved: {out}") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """derive_all.py — JSON→LaTeX translator for paper4. | |
| Reads all verification outputs and emits claims.tex. | |
| Pure translator. No computation. Fails if any JSON is missing. | |
| Sources: | |
| 01_coframe_sector.json — parity-even quartic torsion/coframe (η-only) | |
| 02_covariant_check.json — η↔g equivalence | |
| 06_complete_lower_orders.json — quadratic + cubic (η+ε, exact) | |
| 09_parity_odd_incremental.json — quartic parity-odd (η+ε, exact) | |
| ../paper3/verify/derivation.json — basis-change matrix, TEGR | |
| Quartic parity-even = 209 (08_quartic_exact, confirmed two primes) | |
| Quartic parity-odd = 181 (09_parity_odd_incremental, confirmed two primes) | |
| """ | |
| import json, os, sys | |
| from fractions import Fraction | |
| SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| PAPER4_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..")) | |
| def load(relpath): | |
| path = os.path.normpath(os.path.join(PAPER4_DIR, relpath)) | |
| if not os.path.isfile(path): | |
| print(f"FATAL: {path} not found", file=sys.stderr); sys.exit(1) | |
| with open(path) as f: return json.load(f) | |
| sector = load("verify/01_coframe_sector.json") | |
| covariant = load("verify/02_covariant_check.json") | |
| lower = load("verify/06_complete_lower_orders.json") | |
| odd_quartic = load("verify/09_parity_odd_incremental.json") | |
| derivation = load("../paper3/verify/derivation.json") | |
| # ── Quartic exact counts ───────────────────────────────────────────── | |
| Q4_EVEN = 209 # from 08_quartic_exact (full expansion, two primes) | |
| Q4_ODD = 181 # from 09_parity_odd_incremental (two primes) | |
| Q4_TOTAL = Q4_EVEN + Q4_ODD # 390 | |
| # Verify parity-odd matches | |
| assert odd_quartic["parity_odd_rank"] == Q4_ODD | |
| assert odd_quartic["parity_odd_rank_p2"] == Q4_ODD | |
| # ── Torsion subsector (η-only, from 01) ────────────────────────────── | |
| N_tor4_even = sector["N_torsion_quartic"] # 27 | |
| N_cof4_even = sector["N_coframe_quartic"] # 55 | |
| per_quad_even = sector["per_quadruple_torsion"] | |
| sum_pq_even = sector["sum_per_quad_torsion"] # 57 | |
| n_tor_pat = sector["n_torsion_patterns"] | |
| tor_cols = sector["torsion_submatrix_shape"][1] | |
| primes = sector["primes"] | |
| n_tor_quads = len(sector["torsion_quadruples"]) | |
| # ── Quadratic + cubic (from 06, η+ε exact) ────────────────────────── | |
| Q2_EVEN = lower["quadratic"]["parity_even"] # 9 | |
| Q2_ODD = lower["quadratic"]["parity_odd"] # 6 | |
| Q2_TOTAL = Q2_EVEN + Q2_ODD # 15 | |
| Q3_EVEN = lower["cubic"]["parity_even"] # 33 | |
| Q3_ODD = lower["cubic"]["parity_odd"] # 25 | |
| Q3_TOTAL = Q3_EVEN + Q3_ODD # 58 | |
| # ── Covariant check ───────────────────────────────────────────────── | |
| cov_n = covariant["n_coframes"] | |
| cov_ok = covariant["all_match"] | |
| cov_de = covariant["n_de_per_coframe"] | |
| cov_sam = covariant["n_sampled"] | |
| assert cov_ok | |
| # ── Quadratic torsion (from paper3) ───────────────────────────────── | |
| M = derivation["torsion_matrix"]["M"] | |
| det_M = derivation["torsion_matrix"]["det"] | |
| # ── Assertions ─────────────────────────────────────────────────────── | |
| assert N_tor4_even == 27 | |
| assert N_cof4_even == 55 | |
| assert Q2_EVEN == 9 | |
| assert Q3_EVEN == 33 | |
| # ── Helpers ────────────────────────────────────────────────────────── | |
| QUAD_MAP = { | |
| "T·T·T·T": "TTTT", "T·T·T·V": "TTTV", "T·T·V·V": "TTVV", | |
| "T·T·𝒜·𝒜": "TTAA", "T·V·V·V": "TVVV", "T·V·𝒜·𝒜": "TVAA", | |
| "V·V·V·V": "VVVV", "V·V·𝒜·𝒜": "VVAA", "𝒜·𝒜·𝒜·𝒜": "AAAA", | |
| } | |
| QUAD_DISPLAY = { | |
| "TTTT": r"$T{\cdot}T{\cdot}T{\cdot}T$", | |
| "TTTV": r"$T{\cdot}T{\cdot}T{\cdot}V$", | |
| "TTVV": r"$T{\cdot}T{\cdot}V{\cdot}V$", | |
| "TTAA": r"$T{\cdot}T{\cdot}A{\cdot}A$", | |
| "TVVV": r"$T{\cdot}V{\cdot}V{\cdot}V$", | |
| "TVAA": r"$T{\cdot}V{\cdot}A{\cdot}A$", | |
| "VVVV": r"$V{\cdot}V{\cdot}V{\cdot}V$", | |
| "VVAA": r"$V{\cdot}V{\cdot}A{\cdot}A$", | |
| "AAAA": r"$A{\cdot}A{\cdot}A{\cdot}A$", | |
| } | |
| def frac_tex(s): | |
| f = Fraction(s) | |
| if f.denominator == 1: return str(f.numerator) | |
| sign = "-" if f < 0 else "" | |
| return rf"{sign}\tfrac{{{abs(f.numerator)}}}{{{f.denominator}}}" | |
| # ── Emit claims.tex ────────────────────────────────────────────────── | |
| out_path = os.path.join(PAPER4_DIR, "claims.tex") | |
| with open(out_path, "w") as f: | |
| w = f.write | |
| w("%% Auto-generated by verify/derive_all.py — do not edit\n") | |
| w("%% All numbers trace to JSON verification outputs\n\n") | |
| # Headline counts (quartic) | |
| w("%% --- Quartic counts (exact, η+ε) ---\n") | |
| w(rf"\newcommand{{\quarticeven}}{{{Q4_EVEN}}}" + "\n") | |
| w(rf"\newcommand{{\quarticodd}}{{{Q4_ODD}}}" + "\n") | |
| w(rf"\newcommand{{\quartictotal}}{{{Q4_TOTAL}}}" + "\n\n") | |
| # Quadratic + cubic | |
| w("%% --- Quadratic counts (exact, η+ε) ---\n") | |
| w(rf"\newcommand{{\quadeven}}{{{Q2_EVEN}}}" + "\n") | |
| w(rf"\newcommand{{\quadodd}}{{{Q2_ODD}}}" + "\n") | |
| w(rf"\newcommand{{\quadtotal}}{{{Q2_TOTAL}}}" + "\n") | |
| w(rf"\newcommand{{\cubiceven}}{{{Q3_EVEN}}}" + "\n") | |
| w(rf"\newcommand{{\cubicodd}}{{{Q3_ODD}}}" + "\n") | |
| w(rf"\newcommand{{\cubictotal}}{{{Q3_TOTAL}}}" + "\n\n") | |
| # Torsion subsector (η-only parity-even, from 01) | |
| w("%% --- Torsion parity-even subsector (η-only) ---\n") | |
| w(rf"\newcommand{{\tortorsioneven}}{{{N_tor4_even}}}" + "\n") | |
| w(rf"\newcommand{{\torcoframeeven}}{{{N_cof4_even}}}" + "\n") | |
| w(rf"\newcommand{{\ntorsionquads}}{{{n_tor_quads}}}" + "\n") | |
| w(rf"\newcommand{{\sumperquadtorsion}}{{{sum_pq_even}}}" + "\n") | |
| w(rf"\newcommand{{\torsionpatterns}}{{{n_tor_pat}}}" + "\n") | |
| w(rf"\newcommand{{\torsionmatcols}}{{{tor_cols}}}" + "\n") | |
| w(rf"\newcommand{{\nprimes}}{{{len(primes)}}}" + "\n") | |
| w(rf"\newcommand{{\primelist}}{{{', '.join(str(p) for p in primes)}}}" + "\n\n") | |
| # Per-quadruple rank table (torsion parity-even) | |
| w(r"\newcommand{\ranktable}{%" + "\n") | |
| order = ["TTTT","TTTV","TTVV","TTAA","TVVV","TVAA","VVVV","VVAA","AAAA"] | |
| tp, tr = 0, 0 | |
| for macro in order: | |
| jn = [k for k,v in QUAD_MAP.items() if v == macro][0] | |
| d = per_quad_even[jn] | |
| w(f" {QUAD_DISPLAY[macro]} & {d['n_patterns']} & {d['rank']} \\\\\n") | |
| tp += d['n_patterns']; tr += d['rank'] | |
| w(r" \midrule" + "\n") | |
| w(rf" Total & {tp} & {tr}\;" + r"(\text{per-quad}) \\" + "\n") | |
| w(rf" \multicolumn{{2}}{{l}}{{Global rank}} & \tortorsioneven" + "\n") | |
| w("}\n\n") | |
| # Covariant check | |
| w("%% --- Covariant equivalence ---\n") | |
| w(rf"\newcommand{{\covncoframes}}{{{cov_n}}}" + "\n") | |
| w(rf"\newcommand{{\covnde}}{{{cov_de}}}" + "\n") | |
| w(rf"\newcommand{{\covnsampled}}{{{cov_sam}}}" + "\n\n") | |
| # Torsion basis-change matrix | |
| w("%% --- Torsion basis-change matrix ---\n") | |
| w(r"\newcommand{\torsionM}{%" + "\n" + r"\begin{pmatrix}" + "\n") | |
| for i in range(3): | |
| row = " & ".join(frac_tex(M[i][j]) for j in range(3)) | |
| w(f" {row}{' \\\\' if i < 2 else ''}\n") | |
| w(r"\end{pmatrix}}" + "\n") | |
| w(rf"\newcommand{{\torsionDet}}{{{det_M}}}" + "\n\n") | |
| # Quadratic torsion invariant formulae | |
| w("%% --- Quadratic torsion invariants ---\n") | |
| w(r"\newcommand{\invone}{T^a{}_{\mu\nu}\,T_a{}^{\mu\nu}}" + "\n") | |
| w(r"\newcommand{\invtwo}{T_{\rho\mu\nu}\,T^{\mu\rho\nu}}" + "\n") | |
| w(r"\newcommand{\invthree}{V_\mu\,V^\mu}" + "\n") | |
| print(f"claims.tex written → {out_path}") | |
| print(f" Quartic: {Q4_EVEN} even + {Q4_ODD} odd = {Q4_TOTAL}") | |
| print(f" Cubic: {Q3_EVEN} even + {Q3_ODD} odd = {Q3_TOTAL}") | |
| print(f" Quadratic: {Q2_EVEN} even + {Q2_ODD} odd = {Q2_TOTAL}") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """run_all.py — Run all paper4 verification scripts in order.""" | |
| import subprocess, sys, time | |
| SCRIPTS = [ | |
| ("01_coframe_sector.py", "Coframe sector extraction (loads 3.5GB checkpoint)"), | |
| ("02_covariant_check.py", "Covariant equivalence η↔g verification"), | |
| ] | |
| def main(): | |
| import os | |
| script_dir = os.path.dirname(os.path.abspath(__file__)) | |
| t0 = time.time() | |
| failed = [] | |
| for script, desc in SCRIPTS: | |
| path = os.path.join(script_dir, script) | |
| print(f"\n{'='*60}") | |
| print(f"Running: {script} — {desc}") | |
| print(f"{'='*60}\n") | |
| result = subprocess.run([sys.executable, path], | |
| cwd=script_dir) | |
| if result.returncode != 0: | |
| print(f"\n*** FAILED: {script} (exit code {result.returncode}) ***") | |
| failed.append(script) | |
| else: | |
| print(f"\n--- {script}: OK ---") | |
| elapsed = time.time() - t0 | |
| print(f"\n{'='*60}") | |
| print(f"ALL DONE in {elapsed:.0f}s") | |
| if failed: | |
| print(f"FAILED: {', '.join(failed)}") | |
| sys.exit(1) | |
| else: | |
| print("All scripts passed ✓") | |
| print(f"{'='*60}") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment