SilverSight/python/nr_bracket_validation.py
allaun 7c303624be refactor(core): typed Invariant/TensorType migration, CartanConnection cleanup
Bind.lean:
  - Add Invariant structure (Nat-backed identifier) with DecidableEq
  - Add Invariant.ofNat / Invariant.fromString (boundary-only string->Nat hash)
  - Add TensorType inductive enum (identity, riemannian, thermodynamic, etc.)
  - Migrate Metric.tensor from String to TensorType (AGENTS.md S1.5 compliance)
  - Migrate Witness.left_invariant/right_invariant from String to Invariant
  - Update all bind theorems and #eval call sites

BraidField.lean:
  - Update computePIST call sites to use Invariant.fromString

BraidEigensolid.lean:
  - Simplify eigensolid_trivial proof (drop unnecessary calc block)

CartanConnection.lean:
  - Extract C_int_cross_block_zero lemma
  - Extract factor_sum lemma (eliminates inline factor helper)
  - Extract mu_double_lift lemma (factors out triplicated pattern in
    Jacobiator_basis_zero_int, reducing it from ~30 lines to ~12)
  - Simplify Jacobiator_basis_all using refine+rw pattern

nr_bracket_validation.py:
  - Fix stale file path (Research Stack -> research-stack)
2026-06-28 00:11:28 -05:00

293 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
nr_bracket_validation.py — Real-data validation of d_CE μ = 0 (Gate C)
Tests the NijenhuisRichardson bracket breakglass on actual braid state data
from the RRC EntropyCandidates pipeline. Two test suites:
Suite A — Basis vectors v_i = e_i e_7 (Fin 7 basis of V).
Expected: ‖d_CE μ‖_∞ = 0 (exact) — mirrors Lean Jacobiator_basis_all.
Suite B — Real strand phase vectors from RRC entropy candidates.
Extracts phaseAcc.x (and .y) from each of the 8 strands,
projects into V = ker(Σ), and evaluates d_CE μ.
Should be near machine-zero if the formal proof matches reality.
Reference: SilverSight/formal/SilverSight/PIST/CartanConnection.lean
"""
from __future__ import annotations
import json
import math
import re
import sys
# ─── Crossing matrix C (exact rationals) ─────────────────────────────────────
SIGMA = 39 / 256 # diagonal weight
TAU = 1 / 7 # same-block off-diagonal weight
def crossing_matrix() -> list[list[float]]:
C = [[0.0] * 8 for _ in range(8)]
for i in range(8):
for j in range(8):
if i == j:
C[i][j] = SIGMA
elif i // 2 == j // 2:
C[i][j] = TAU
return C
C = crossing_matrix()
def mat_vec_mul(M: list[list[float]], v: list[float]) -> list[float]:
return [sum(M[i][j] * v[j] for j in range(8)) for i in range(8)]
def vec_add(v: list[float], w: list[float]) -> list[float]:
return [a + b for a, b in zip(v, w)]
def vec_sub(v: list[float], w: list[float]) -> list[float]:
return [a - b for a, b in zip(v, w)]
def inf_norm(v: list[float]) -> float:
return max(abs(x) for x in v)
def project_into_V(v: list[float]) -> list[float]:
mean = sum(v) / 8.0
return [x - mean for x in v]
def mu(X: list[float], Y: list[float]) -> list[float]:
CX = mat_vec_mul(C, X)
CY = mat_vec_mul(C, Y)
return [CX[k] * Y[k] - X[k] * CY[k] for k in range(8)]
def jacobiator(X: list[float], Y: list[float], Z: list[float]) -> list[float]:
return vec_add(
mu(mu(X, Y), Z),
vec_add(mu(mu(Y, Z), X), mu(mu(Z, X), Y))
)
# ─── Suite A: Basis vectors ──────────────────────────────────────────────────
def basis_vec(k: int) -> list[float]:
v = [0.0] * 8
v[k] = 1.0
v[7] = -1.0
return v
def run_suite_a() -> dict:
non_zero: list[tuple[int, int, int, float]] = []
worst = 0.0
for i in range(7):
vi = basis_vec(i)
for j in range(7):
vj = basis_vec(j)
for k in range(7):
vk = basis_vec(k)
J = jacobiator(vi, vj, vk)
nrm = inf_norm(J)
if nrm > worst:
worst = nrm
if nrm > 1e-12:
non_zero.append((i, j, k, nrm))
return {
"total_triples": 7 ** 3,
"non_zero_count": len(non_zero),
"worst_inf_norm": worst,
"all_zero": worst < 1e-12,
}
# ─── Suite B: Parse Candidates.lean (stateful line-based) ────────────────────
def parse_candidates(lines: list[str]) -> list[list[tuple[int, int, int, int]]]:
"""
Extract (x_raw, y_raw, slot, kappa_raw) per strand per candidate.
Each candidate has 8 strand blocks like:
| ⟨0, _⟩ => { phaseAcc := { x := Q16_16.ofRawInt 37813, y := Q16_16.ofRawInt 45787 }
, parity := false
, slot := 1
, residue := Q16_16.ofRawInt 0
, jitter := Q16_16.ofRawInt 0
, bracket := { lower := ...
, kappa := Q16_16.ofRawInt 44921
"""
x_pat = re.compile(r"x\s*:=\s*Q16_16\.ofRawInt\s+(-?\d+)")
y_pat = re.compile(r"y\s*:=\s*Q16_16\.ofRawInt\s+(-?\d+)")
slot_pat = re.compile(r"slot\s*:=\s*(\d+)")
kappa_pat = re.compile(r"kappa\s*:=\s*Q16_16\.ofRawInt\s+(-?\d+)")
candidates: list[list[tuple[int, int, int, int]]] = []
current: list[tuple[int, int, int, int]] = []
buf: dict[str, int | None] = {"x": None, "y": None, "slot": None, "kappa": None}
def flush():
nonlocal buf, current
if all(v is not None for v in buf.values()):
current.append((
buf["x"], buf["y"], buf["slot"], buf["kappa"]
))
buf = {"x": None, "y": None, "slot": None, "kappa": None}
for line in lines:
if "phaseAcc" in line:
flush() # commit previous strand if pending
x_m = x_pat.search(line)
y_m = y_pat.search(line)
slot_m = slot_pat.search(line)
kappa_m = kappa_pat.search(line)
if x_m:
buf["x"] = int(x_m.group(1))
if y_m:
buf["y"] = int(y_m.group(1))
if slot_m:
buf["slot"] = int(slot_m.group(1))
if kappa_m:
buf["kappa"] = int(kappa_m.group(1))
# When we have all 4 fields, make a strand entry
if all(v is not None for v in buf.values()):
flush()
if len(current) == 8:
candidates.append(current)
current = []
return candidates
def run_suite_b(candidates: list[list[tuple[int, int, int, int]]]) -> dict:
Q16 = 65536.0
results = []
for idx, cand in enumerate(candidates):
X = [s[0] / Q16 for s in cand]
Y = [s[1] / Q16 for s in cand]
Z = [s[3] / Q16 for s in cand]
Xv = project_into_V(X)
Yv = project_into_V(Y)
Zv = project_into_V(Z)
tests: list[tuple[str, list[float], list[float], list[float]]] = [
("X,X,X", Xv, Xv, Xv),
("Y,Y,Y", Yv, Yv, Yv),
("Z,Z,Z", Zv, Zv, Zv),
("X,Y,Z", Xv, Yv, Zv),
("X,X,Y", Xv, Xv, Yv),
("Y,Y,X", Yv, Yv, Xv),
("X,Y,Y", Xv, Yv, Yv),
("X,Z,Y", Xv, Zv, Yv),
]
worst = 0.0
details = []
for label, A, B, D in tests:
J = jacobiator(A, B, D)
nrm = inf_norm(J)
if nrm > worst:
worst = nrm
details.append({"triple": label, "inf_norm": round(nrm, 12)})
results.append({
"candidate_idx": idx,
"worst_inf_norm": round(worst, 12),
"all_near_zero": worst < 1e-6,
"details": details,
})
overall_worst = max((r["worst_inf_norm"] for r in results), default=0.0)
return {
"candidates_tested": len(candidates),
"results": results,
"overall_worst": overall_worst,
"overall_pass": all(r["all_near_zero"] for r in results),
}
# ─── Main ─────────────────────────────────────────────────────────────────────
def main():
print("=" * 60)
print("NR Bracket Validation — d_CE μ = 0 on real data")
print("=" * 60)
# Suite A
print("\n─── Suite A: Basis vectors (343 triples) ───")
sa = run_suite_a()
print(f" Total triples: {sa['total_triples']}")
print(f" Non-zero found: {sa['non_zero_count']}")
print(f" Worst ‖J‖_∞: {sa['worst_inf_norm']:.2e}")
print(f" All zero (exact): {sa['all_zero']}")
candidates_file = (
"/home/allaun/research-stack/lean/Semantics/"
"Semantics/RRC/EntropyCandidates/Candidates.lean"
)
print(f"\n─── Suite B: Real candidates ({candidates_file}) ───")
sb = None
try:
with open(candidates_file) as f:
lines = f.readlines()
candidates = parse_candidates(lines)
print(f" Candidates parsed: {len(candidates)}")
if len(candidates) == 0:
print(" ✗ WARNING: No candidates parsed — check regex patterns")
# Debug: show first 30 lines containing relevant keywords
for i, ln in enumerate(lines[:200]):
if any(kw in ln for kw in ["phaseAcc", "slot", "kappa", "ofRawInt"]):
print(f" L{i+1}: {ln.rstrip()}")
else:
sb = run_suite_b(candidates)
print(f" Tested: {sb['candidates_tested']} candidates")
print(f" Overall worst ‖J‖_∞: {sb['overall_worst']:.6e}")
print(f" Overall pass: {sb['overall_pass']}")
for cr in sb["results"]:
status = "" if cr["all_near_zero"] else ""
print(f" {status} Candidate {cr['candidate_idx']}: "
f"worst ‖J‖_∞ = {cr['worst_inf_norm']:.6e}")
for d in cr["details"]:
if d["inf_norm"] > 1e-9:
print(f"{d['triple']}: {d['inf_norm']:.6e}")
except FileNotFoundError:
print(f" ✗ Candidates.lean not found at {candidates_file}")
print("\n" + "=" * 60)
verdict_a = "✓ PASS" if sa["all_zero"] else "✗ FAIL"
print(f" Suite A (basis): {verdict_a}")
if len(candidates) > 0:
verdict_b = "✓ PASS" if sb["overall_pass"] else "✗ FAIL"
print(f" Suite B (real): {verdict_b}")
print("=" * 60)
# Save JSON receipt
receipt = {
"schema": "nr_bracket_validation_v1",
"suite_a": sa,
"suite_b": sb if len(candidates) > 0 else None,
"verdict": {
"suite_a": "PASS" if sa["all_zero"] else "FAIL",
"suite_b": "PASS" if (len(candidates) > 0 and sb["overall_pass"]) else "FAIL" if len(candidates) > 0 else "SKIP",
},
}
receipt_path = "/home/allaun/SilverSight/python/nr_bracket_validation_receipt.json"
with open(receipt_path, "w") as f:
json.dump(receipt, f, indent=2)
print(f"\n Receipt saved: {receipt_path}")
if __name__ == "__main__":
main()