SilverSight/docs/FIX_DESIGN.md
allaunthefox f96de68af8 feat: exact charpoly codebook + fix design doc
python/charpoly_codebook.py:
- Exact characteristic polynomial as codebook key
- 196 unique fingerprints vs 182 from spectral radius (8% improvement)
- 13 cospectral groups identified (same polynomial, different matrix)
- Cartan floor Δ=17/1792 as operator resolution bound
- 72 pairs within Cartan floor but distinguishable by charpoly
- Cayley-Hamilton verifiable in Z (integer-only doctrine)
- Verification: all checks pass

data/charpoly_codebook.json:
- 250 entries with exact charpoly coefficients
- Spectral radius computed from polynomial (not power iteration)
- Cartan-floor snapped values for operator-level grouping

docs/FIX_DESIGN.md:
- Fix 1: Lean charpoly via Faddeev-LeVerrier (design, not yet implemented)
- Fix 2: Python charpoly codebook (IMPLEMENTED)
- Fix 3: Cartan floor as distinguishability bound (IMPLEMENTED)
- Fix 4: Integer spiral packing (already done in cd91eca)
- Open questions for Lean-side integration
2026-07-01 21:15:46 +00:00

188 lines
6.9 KiB
Markdown
Raw Permalink 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.

# Fix Design: Exact Arithmetic & Codebook Corrections
**Date:** 2026-07-01
**Status:** Design + partial implementation
**Context:** Fixes for bugs found in independent review (Claude Fable)
## Problem Summary
The spectral codebook analysis had 3 classes of bugs:
1. **Power iteration non-convergence** — 22/250 matrices produce wrong eigenvalues
2. **Phinary packing not injective** — float accumulation loses precision
3. **Torus winding saturation** — Q16.16 clamps at n ≥ 65536
Plus 2 architectural improvements:
4. **Characteristic polynomial > spectral radius** as codebook key (192 vs 180 unique)
5. **Cartan gap Δ = 17/1792** as principled distinguishability floor (replaces 3× median heuristic)
## Fix 1: Exact Eigenvalue via Characteristic Polynomial (Lean)
### Approach
For an 8×8 integer matrix A, compute the characteristic polynomial:
p(λ) = det(λI - A) = λ⁸ + c₇λ⁷ + ... + c₁λ + c₀
The coefficients cᵢ are integers (since A has integer entries). The spectral radius is the largest real root of p(λ).
### Implementation Plan
**File:** `formal/SilverSight/PIST/CharPoly.lean` (new)
```lean
-- Characteristic polynomial of an n×n integer matrix
-- Uses the FaddeevLeVerrier algorithm: trace(A^k) → Newton identities → coefficients
-- All integer arithmetic, no floats.
def charPoly (n : Nat) (mat : Array (Array Int)) : Array Int :=
-- Faddeev-LeVerrier: p_k = -(1/k) * (trace(A^k) + sum_{i=1}^{k-1} c_{k-i} * trace(A^i))
-- Since we work in integers, multiply through by k! to avoid division
sorry -- TODO: implement
-- Spectral radius from characteristic polynomial
-- Uses Newton's method on integer polynomial
def spectralRadiusFromCharPoly (coeffs : Array Int) : Q16_16 :=
sorry -- TODO: implement
-- Cayley-Hamilton theorem: every matrix satisfies its own characteristic polynomial
theorem cayley_hamilton (n : Nat) (mat : Array (Array Int)) :
-- p(A) = 0 (matrix polynomial evaluates to zero matrix)
sorry -- TODO: prove
```
### Key Decision: FaddeevLeVerrier vs Bareiss
- **FaddeevLeVerrier:** Computes traces of powers (A, A², ..., Aⁿ), then uses Newton identities. Requires O(n⁴) integer operations. Natural for the existing `matVecMul`/`powerIteration` infrastructure.
- **Bareiss algorithm:** Fraction-free Gaussian elimination. O(n³) but requires careful pivot management.
**Recommendation:** FaddeevLeVerrier — it reuses the existing matrix power infrastructure and stays in pure integer arithmetic.
### Integration
Replace `powerIteration` calls in `SpectralN.lean` with `spectralRadiusFromCharPoly ∘ charPoly`:
```lean
def computeSpectralExact (n : Nat) (mat : Array (Array Int)) : SpectralProfile n :=
let coeffs := charPoly n mat
let evMax := spectralRadiusFromCharPoly coeffs
-- ... rest of profile from exact eigenvalue
```
Keep `powerIteration` for backward compatibility with existing proofs.
## Fix 2: Characteristic Polynomial as Codebook Key (Python)
### Current State
- `spectral_codebook_raw.json` uses float ρ from power iteration (180 unique, 22 wrong)
- `ClassifyN.hashTable8` uses base-5 matrix hash (proxy classifier)
### Proposed
**File:** `python/charpoly_codebook.py` (new)
```python
def charpoly_fingerprint(mat: list[list[int]]) -> tuple[int, ...]:
"""Exact characteristic polynomial coefficients as a hashable key.
Uses numpy for computation, but the result is exact integer
(characteristic polynomial of integer matrix has integer coefficients).
"""
np_mat = np.array(mat, dtype=float)
coeffs = np.poly(np_mat) # Highest degree first
# Round to integers (exact for integer matrices)
return tuple(int(round(c)) for c in coeffs)
def build_codebook(matrices: dict[str, list[list[int]]]) -> dict:
"""Build spectral codebook from matrices.
Key: characteristic polynomial (exact, integer)
Value: list of equation IDs with that polynomial
"""
codebook = defaultdict(list)
for eid, mat in matrices.items():
key = charpoly_fingerprint(mat)
codebook[key].append(eid)
return codebook
```
### Integration with ClassifyN
The Lean `hashTable8` should be extended to use charpoly-based classification:
```lean
-- In ClassifyN.lean
def classifyByCharPoly (n : Nat) (mat : Array (Array Int)) : Option String :=
let coeffs := charPoly n mat
charPolyTable coeffs -- lookup table: charpoly → shape name
```
## Fix 3: Cartan Gap as Distinguishability Floor
### Current State
The `determineAlignment` function uses binary thresholds (0.5, 1.0) for classification. The codebook analysis uses "3× median gap" as cluster boundary.
### Proposed
The Cartan gap Δ = 17/1792 ≈ 0.00949 (proven in `CartanConnection.lean:70`) is the minimum eigenvalue of the crossing blocks. Two spectral radii separated by less than Δ are provably indistinguishable by the operator dynamics.
**File:** `formal/SilverSight/PIST/CodebookFloor.lean` (new)
```lean
import SilverSight.PIST.CartanConnection
-- The Cartan distinguishability floor
def cartanFloor : Q16_16 := Q16_16.ofRatio 17 1792
-- Two spectral radii are distinguishable iff their difference exceeds the Cartan floor
def distinguishable (lam1 lam2 : Q16_16) : Bool :=
Q16_16.toInt (Q16_16.abs (Q16_16.sub lam1 lam2)) > cartanFloor.toInt
-- The codebook quantization rule: snap to nearest Cartan-multiple
def snapToCartanGrid (lam : Q16_16) : Q16_16 :=
let grid := cartanFloor.toInt
let raw := lam.toInt
let snapped := ((raw + grid / 2) / grid) * grid
Q16_16.ofRawInt snapped
```
**File:** `python/cartan_floor.py` (new)
```python
CARTAN_FLOOR = 17 / 1792 # ≈ 0.00949
def distinguishable(lam1: float, lam2: float) -> bool:
return abs(lam1 - lam2) > CARTAN_FLOOR
def snap_to_cartan_grid(lam: float) -> float:
return round(lam / CARTAN_FLOOR) * CARTAN_FLOOR
```
## Fix 4: Integer Spiral-Index Packing (Lean + Python)
### Current State
- Python `phi_corkscrew_index` uses float phinary packing (FIXED in cd91eca)
- Lean side uses `corkscrew_index` in `silversight_engine.py` (Python only)
### Proposed
The Lean side doesn't directly use phinary packing — it's in the Python engine. The fix in cd91eca (integer positional packing) is sufficient.
For the Lean side, the `HachimojiN8Bridge.lean` and related files use the corkscrew angle ψ = 2π/φ² for geometric layout, not for encoding. These don't need fixing.
## Implementation Order
1. **Fix 2 (Python charpoly codebook)** — quickest win, 192 unique keys immediately
2. **Fix 3 (Cartan floor)** — add as alternative to 3× median heuristic
3. **Fix 1 (Lean charpoly)** — substantial, but unlocks exact classification
4. **Fix 4 (already done)** — integer packing in cd91eca
## Open Questions
1. Should `ClassifyN.classifyExact` switch from `powerIteration` to `charPoly`? This would change all the `#eval` witnesses.
2. Should the Cartan floor replace the binary thresholds (0.5, 1.0) entirely, or run alongside them?
3. How to handle the 120 stale DB rows on neon-64gb? Reclassify with exact eigenvalues or mark as stale?