mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
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
188 lines
6.9 KiB
Markdown
188 lines
6.9 KiB
Markdown
# 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 Faddeev–LeVerrier 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: Faddeev–LeVerrier vs Bareiss
|
||
|
||
- **Faddeev–LeVerrier:** 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:** Faddeev–LeVerrier — 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?
|