mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
Implements the Next Steps of docs/SPECTRAL_CODEBOOK_ANALYSIS.md as python/spectral_codebook.py (stdlib-only; NumPy optional fast path): - Parses the 250 8x8 braid adjacency matrices from PIST/Matrices250.lean. - Primary fingerprint: exact integer characteristic-polynomial coefficients via Faddeev-LeVerrier in Fraction arithmetic (196 unique over 238 distinct matrices, vs 179 unique lambda at 4dp; 6 cospectral non-identical groups; 11 exact-duplicate matrix groups / 23 ids). - Corrects the analysis doc: the 9 'mid band' lambda in (0.5,1) are power-iteration non-convergence artifacts - exact rho = 1.0 for all 9 (peripheral spectra). spectral_radius is now the exact max root modulus (numpy eigvals or Durand-Kerner on the exact char poly); power-iteration lambda kept only for traceability. - Gap-aware quantization: dedupe to 238 distinct matrices, boundaries at gaps > 3x median gap, min-support guard (>=10 distinct per cluster), sparse-tail outlier flagging above lambda ~= 7.66. Result: 9 clusters. - Round trip encode(matrix) -> (codeword, index) -> decode -> equation_id verified bijective over all 250 in tests/test_spectral_codebook.py. - 278-row RRC/Q16_16Manifold corpus: 28 extra rows are repeated ids; boundaries reproduce exactly, no new gaps or clusters. - Emits data/spectral_codebook.json (schema spectral_codebook_v2) with explicit collision classes; docs/SPECTRAL_CODEBOOK_GENERATOR.md notes the hashMatrix base-5 injectivity gap and the ClassifyN threshold (1.5/4.0 Q16.16) vs analysis-doc (0.5/1.0) mismatch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
203 lines
8.7 KiB
Python
203 lines
8.7 KiB
Python
"""test_spectral_codebook.py — Gap-aware spectral codebook round-trip tests.
|
||
|
||
Validates the spectral_codebook module against the 250-matrix PIST corpus:
|
||
exact char-poly fingerprints (Cayley–Hamilton in integer arithmetic),
|
||
pure-stdlib vs NumPy spectral-radius agreement, gap-aware quantization
|
||
invariants, and the encode/decode round trip over all 250 equations.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
import unittest
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "python"))
|
||
|
||
import spectral_codebook as sc
|
||
|
||
|
||
def _mat_mul(a, b):
|
||
n = len(a)
|
||
return [
|
||
[sum(a[i][t] * b[t][j] for t in range(n)) for j in range(n)]
|
||
for i in range(n)
|
||
]
|
||
|
||
|
||
class TestSpectralCodebook(unittest.TestCase):
|
||
@classmethod
|
||
def setUpClass(cls):
|
||
cls.matrices = sc.parse_matrices_lean()
|
||
cls.codebook = sc.Codebook(cls.matrices)
|
||
|
||
# ── parsing ─────────────────────────────────────────────────────
|
||
|
||
def test_parse_250_8x8(self):
|
||
self.assertEqual(len(self.matrices), 250)
|
||
for eid, mat in self.matrices.items():
|
||
self.assertEqual(len(mat), 8, eid)
|
||
self.assertTrue(all(len(r) == 8 for r in mat), eid)
|
||
self.assertTrue(all(x >= 0 for r in mat for x in r), eid)
|
||
|
||
# ── exact char poly ─────────────────────────────────────────────
|
||
|
||
def test_charpoly_identity(self):
|
||
# det(λI − I) = (λ − 1)^8 → coefficients are signed binomials
|
||
ident = tuple(tuple(1 if i == j else 0 for j in range(8)) for i in range(8))
|
||
expect = (-8, 28, -56, 70, -56, 28, -8, 1)
|
||
self.assertEqual(sc.charpoly_coeffs(ident), expect)
|
||
|
||
def test_cayley_hamilton_integer(self):
|
||
"""p(M) = 0 exactly in integer arithmetic (spot check)."""
|
||
for eid in sorted(self.matrices)[::50]: # 5 spread-out matrices
|
||
m = [list(r) for r in self.matrices[eid]]
|
||
coeffs = sc.charpoly_coeffs(m)
|
||
n = len(m)
|
||
acc = [[1 if i == j else 0 for j in range(n)] for i in range(n)] # M^0
|
||
total = [[coeffs[-1] if i == j else 0 for j in range(n)] for i in range(n)]
|
||
for k in range(1, n + 1):
|
||
acc = _mat_mul(acc, m) # M^k
|
||
c = 1 if k == n else coeffs[n - 1 - k]
|
||
for i in range(n):
|
||
for j in range(n):
|
||
total[i][j] += c * acc[i][j]
|
||
self.assertTrue(
|
||
all(x == 0 for row in total for x in row),
|
||
f"Cayley–Hamilton failed for {eid}",
|
||
)
|
||
|
||
def test_charpoly_trace_relation(self):
|
||
for eid, mat in self.matrices.items():
|
||
coeffs = self.codebook.profiles[eid]["charpoly"]
|
||
self.assertEqual(coeffs[0], -sum(mat[i][i] for i in range(8)), eid)
|
||
|
||
# ── spectral radius ─────────────────────────────────────────────
|
||
|
||
def test_pure_root_finder_matches_numpy(self):
|
||
"""Durand–Kerner on the exact char poly agrees with the fast path."""
|
||
for eid in sorted(self.matrices)[::10]: # 25 matrices
|
||
p = self.codebook.profiles[eid]
|
||
pure = sc._durand_kerner_max_modulus(p["charpoly"])
|
||
self.assertAlmostEqual(pure, p["spectral_radius"], places=4, msg=eid)
|
||
|
||
def test_peripheral_spectra_are_unit(self):
|
||
"""The 9 'mid band' λ in the committed raw JSON (0.5 ≤ λ < 1) are
|
||
power-iteration non-convergence artifacts: exact ρ = 1 for all 9."""
|
||
import json
|
||
|
||
raw_path = Path(__file__).resolve().parent.parent / "data" / "spectral_codebook_raw.json"
|
||
raw = json.loads(raw_path.read_text())
|
||
artifacts = [
|
||
e["equation_id"] for e in raw["entries"]
|
||
if 0.5 <= e["spectral_radius"] < 1.0
|
||
]
|
||
self.assertEqual(len(artifacts), 9)
|
||
for eid in artifacts:
|
||
self.assertAlmostEqual(
|
||
self.codebook.profiles[eid]["spectral_radius"], 1.0, places=6, msg=eid
|
||
)
|
||
|
||
def test_nilpotent_class(self):
|
||
"""Every ρ = 0 matrix has char poly exactly x^8 (nilpotent), and the
|
||
pure path detects it with no iteration."""
|
||
zeros = [
|
||
eid for eid, p in self.codebook.profiles.items()
|
||
if p["spectral_radius"] == 0.0
|
||
]
|
||
self.assertEqual(len(zeros), 35)
|
||
for eid in zeros:
|
||
coeffs = self.codebook.profiles[eid]["charpoly"]
|
||
self.assertEqual(list(coeffs), [0] * 8, eid)
|
||
self.assertEqual(sc._durand_kerner_max_modulus(coeffs), 0.0)
|
||
|
||
# ── fingerprints & collisions ───────────────────────────────────
|
||
|
||
def test_charpoly_strictly_finer_than_lambda(self):
|
||
col = self.codebook.collision_report()
|
||
self.assertEqual(col["corpus_size"], 250)
|
||
self.assertEqual(col["distinct_matrices"], 238)
|
||
self.assertEqual(col["duplicate_matrix_groups"], 11)
|
||
self.assertEqual(col["duplicate_matrix_members"], 23)
|
||
self.assertGreater(col["unique_charpoly"], col["unique_lambda_4dp"])
|
||
self.assertGreater(
|
||
col["identified_by_charpoly"], col["identified_by_lambda_4dp"]
|
||
)
|
||
# char poly is NOT injective: collision classes must be explicit
|
||
self.assertGreater(col["charpoly_collision_groups"], 0)
|
||
self.assertEqual(
|
||
sum(len(v) for v in col["charpoly_collision_classes"].values()),
|
||
col["charpoly_collision_members"],
|
||
)
|
||
|
||
# ── quantization invariants ─────────────────────────────────────
|
||
|
||
def test_boundaries_sorted_and_gapped(self):
|
||
b = self.codebook.boundaries
|
||
self.assertEqual(b, sorted(b))
|
||
self.assertGreater(len(b), 0)
|
||
# no λ value sits on a boundary
|
||
for p in self.codebook.profiles.values():
|
||
self.assertNotIn(p["spectral_radius"], b)
|
||
|
||
def test_min_support_guard(self):
|
||
"""Every cluster holds ≥ MIN_SUPPORT distinct matrices."""
|
||
for row in self.codebook.cluster_table():
|
||
self.assertGreaterEqual(
|
||
row["distinct_matrices"], sc.MIN_SUPPORT, row["codeword"]
|
||
)
|
||
|
||
def test_cluster_partition_covers_corpus(self):
|
||
table = self.codebook.cluster_table()
|
||
self.assertEqual(sum(r["count"] for r in table), 250)
|
||
self.assertEqual(sum(r["distinct_matrices"] for r in table), 238)
|
||
|
||
# ── round trip ──────────────────────────────────────────────────
|
||
|
||
def test_decode_is_bijective_over_250(self):
|
||
seen = set()
|
||
for eid in self.matrices:
|
||
cw, idx = self.codebook.codeword_of(eid)
|
||
self.assertNotIn((cw, idx), seen)
|
||
seen.add((cw, idx))
|
||
self.assertEqual(self.codebook.decode(cw, idx), eid)
|
||
self.assertEqual(len(seen), 250)
|
||
|
||
def test_encode_decode_round_trip(self):
|
||
"""encode(matrix) → decode must return the same equation for every
|
||
unique matrix, and a byte-identical matrix for exact duplicates
|
||
(identical inputs are information-theoretically indistinguishable)."""
|
||
dup_members = {
|
||
eid
|
||
for ids in self.codebook.matrix_groups.values()
|
||
if len(ids) > 1
|
||
for eid in ids
|
||
}
|
||
for eid, mat in self.matrices.items():
|
||
cw, idx = self.codebook.encode([list(r) for r in mat])
|
||
self.assertIsNotNone(idx, eid)
|
||
decoded = self.codebook.decode(cw, idx)
|
||
if eid in dup_members:
|
||
self.assertEqual(self.matrices[decoded], mat, eid)
|
||
else:
|
||
self.assertEqual(decoded, eid)
|
||
|
||
def test_encode_novel_matrix(self):
|
||
novel = [[3 if i == j else 1 for j in range(8)] for i in range(8)]
|
||
cw, idx = self.codebook.encode(novel)
|
||
self.assertIsNone(idx)
|
||
self.assertTrue(cw.startswith("C"))
|
||
|
||
# ── 278-row manifold ────────────────────────────────────────────
|
||
|
||
def test_manifold_278_no_new_gaps(self):
|
||
mc = sc.manifold_check(self.codebook)
|
||
self.assertEqual(mc["rows"], 278)
|
||
self.assertEqual(mc["unique_ids"], 250)
|
||
self.assertEqual(mc["extra_rows"], 28)
|
||
self.assertEqual(mc["ids_without_matrix"], [])
|
||
self.assertTrue(mc["boundaries_match_250"])
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|