mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
feat(spectral): f/Cartan integration — exact spiral packing, f(n) layout, Cartan Δ-floor, torus-winding fix
STEP 1 — integer spiral-index packing (EXACT): pack_spiral_index/ unpack_spiral_index with corpus-wide base=2·max|c|+1 and offset-encoded digits, recorded in the codebook JSON header; round-trip verified over all 250 matrices. Fixes the sign bug in integration_sprint.py's integer path (unoffset signed packing collapsed (-1,1) with (1,0) in base 2). STEP 2 — f(n) layout (DECORATIVE): per-entry radius_sq + angle_frac, computed in decimal arithmetic (indices reach ~1e44 >> 2^52); verified against VERIFICATION_LOG V007 f(20121); min angular separation ≈4.64e-6 of a turn reported. STEP 3 — Cartan Δ-floor (--cartan-floor): Δ=17/1792 exact (CartanConnection.lean:70) as Fisher-distance resolution floor on Δ₇; 13 sub-Δ pairs merge 196 fingerprints into 187 Δ-resolution codewords vs 9 gap-rule clusters; emitted alongside, not replacing, the 3×-median rule. Caveat asserted in tests: the |c|/Σ|c| simplex projection is lossy (one corpus pair at Fisher distance 0) — similarity, not identity. STEP 4 — torus-winding saturation fix: TorusWinding gains a_exact/b_exact (plain ℤ, no Q16.16 clamp); torus_to_spiral_index prefers them, making the round trip lossless (regression-tested at n=1e9). BraidEigensolid.lean needs the same widening — noted, Lean untouched. Also corrects charpoly_codebook.py's stale docstring counts (192/180 → 196/182, cross-checked against the exact Faddeev–LeVerrier fingerprints: identical on all 250 matrices). Tests: 43 passed (19 new). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e81caa4a6a
commit
359effdfc6
7 changed files with 2126 additions and 239 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,7 @@
|
|||
**Date:** 2026-07-01
|
||||
**Module:** `python/spectral_codebook.py`
|
||||
**Output:** `data/spectral_codebook.json`
|
||||
**Tests:** `tests/test_spectral_codebook.py` (15 tests)
|
||||
**Tests:** `tests/test_spectral_codebook.py` (43 tests)
|
||||
|
||||
Implements the "Next Steps" of `docs/SPECTRAL_CODEBOOK_ANALYSIS.md`: a
|
||||
gap-aware spectral codebook over the 250 8×8 integer braid adjacency
|
||||
|
|
@ -16,6 +16,7 @@ encode/decode round trip and an exact, float-free fingerprint per matrix.
|
|||
python3 python/spectral_codebook.py # build + report + write JSON
|
||||
python3 python/spectral_codebook.py --check-manifold # also run the 278-row corpus
|
||||
python3 python/spectral_codebook.py --gap-factor 3 --min-support 10 --no-write
|
||||
python3 python/spectral_codebook.py --cartan-floor # + Fisher/Δ=17/1792 floor report
|
||||
python3 tests/test_spectral_codebook.py # or python3 -m pytest tests/test_spectral_codebook.py
|
||||
```
|
||||
|
||||
|
|
@ -122,6 +123,73 @@ matrices, no id lacks a matrix). Rerunning gap quantization on the 278-row
|
|||
corpus reproduces the 250-matrix boundaries exactly: **no new gaps, no new
|
||||
clusters, all extra rows fall inside existing clusters.**
|
||||
|
||||
## Φ-corkscrew and Cartan layers (f/Cartan integration batch)
|
||||
|
||||
Four layers added on top of the fingerprints, each labelled **exact**
|
||||
(integer arithmetic, Lean-provable) or **decorative** (placement/reporting
|
||||
only, carries no information):
|
||||
|
||||
### 1. Integer spiral-index packing — EXACT
|
||||
|
||||
`pack_spiral_index` / `unpack_spiral_index` with corpus-wide parameters
|
||||
recorded in the JSON header (`phi_corkscrew.packing`): offset = max|c| over
|
||||
all fingerprint coefficients (164 234 on this corpus), base = 2·offset + 1
|
||||
(328 469). Digits are offset-encoded (`c + offset ∈ [0, base)`), so
|
||||
`spiral_index = Σ (cᵢ + offset)·baseⁱ` is the base-B positional numeral of
|
||||
the fingerprint — injective on fingerprints by uniqueness of positional
|
||||
representation, inverted exactly by `unpack_spiral_index`, and verified by
|
||||
round trip over all 250 matrices in the test suite. It inherits charpoly's
|
||||
non-injectivity on *matrices* (cospectral classes share one index), so
|
||||
identity still requires the within-cluster index.
|
||||
|
||||
This **supersedes** the float phinary packing that `integration_sprint.py`
|
||||
used historically (`phinary += c·φ⁻ⁱ` then truncation — not injective).
|
||||
The integer path in `integration_sprint.phi_corkscrew_index` is fixed the
|
||||
same way (offset-encoded digits; its unoffset signed packing collapsed
|
||||
e.g. `(-1, 1)` with `(1, 0)` in base 2), but its base is per-call — for
|
||||
corpus-comparable indices use the codebook's recorded (base, offset).
|
||||
|
||||
### 2. f(n) layout — DECORATIVE
|
||||
|
||||
`f(n) = (√n·cos(nψ), √n·sin(nψ))`, ψ = 2π/φ². Injective because 1/φ² is
|
||||
irrational; low-discrepancy by the three-distance theorem;
|
||||
information-neutral (everything decodes from `spiral_index` alone). Each
|
||||
entry stores `layout.radius_sq` (= the spiral index, exact int) and
|
||||
`layout.angle_frac` (fraction of a full turn). Angles are computed in
|
||||
`decimal` arithmetic at full precision because spiral indices reach
|
||||
~10⁴⁴ ≫ 2⁵², where float64 retains no fractional bits of n/φ². Verified
|
||||
against `VERIFICATION_LOG.md` V007: f(20121) = (−137.80079576,
|
||||
−33.64432624). Measured minimum pairwise angular separation across the
|
||||
corpus: ≈ 4.64 × 10⁻⁶ of a turn (positive, as irrationality requires).
|
||||
|
||||
### 3. Cartan ∆-floor quantization (`--cartan-floor`) — EXACT ∆, reported floor
|
||||
|
||||
∆ = **17/1792** exactly (λ_min of the Cartan crossing blocks,
|
||||
`CartanConnection.lean:70`, `docs/cartan_fingerprint.md`), adopted as the
|
||||
resolution floor on the Fisher metric of Δ₇:
|
||||
`d_F(p, q) = 2·arccos(Σ√(pᵢqᵢ))` with `pᵢ = |cᵢ|/Σ|cⱼ|` (all-zero
|
||||
fingerprint → uniform, by convention). Measured on this corpus: **13
|
||||
fingerprint pairs fall below ∆**, merging 196 fingerprints into **187
|
||||
∆-resolution codewords**, vs **9 clusters** from the 3×-median-gap λ rule.
|
||||
Both rules are emitted side by side; neither replaces the other.
|
||||
|
||||
Caveat, asserted in the tests: the simplex projection destroys sign and
|
||||
scale, so distinct fingerprints can collide on Δ₇ (this corpus has a pair
|
||||
at Fisher distance exactly 0). The Fisher/∆ layer is a **similarity**
|
||||
metric, not an identity key.
|
||||
|
||||
### 4. Torus-winding saturation fix — EXACT
|
||||
|
||||
`pist_braid_bridge.TorusWinding` now carries `a_exact`/`b_exact` (plain ℤ,
|
||||
no clamp) alongside the legacy Q16.16 raws, which silently saturate above
|
||||
32767 (i.e. for spiral_index > 65535 — and codebook indices reach ~10⁴⁴).
|
||||
`torus_to_spiral_index` prefers the exact fields, making the round trip
|
||||
lossless for every index (regression-tested at n = 10⁹); legacy windings
|
||||
without exact fields keep the old Q16 behaviour. The matching Lean type in
|
||||
`BraidEigensolid.lean` needs the same widening (Int fields alongside
|
||||
Q16_16) before this layer can be mirrored formally — noted in the
|
||||
dataclass docstring, Lean file intentionally untouched.
|
||||
|
||||
## Notes for the Lean side
|
||||
|
||||
- **Identity layer vs. similarity layer.** `ClassifyN.hashMatrix` is a
|
||||
|
|
@ -222,11 +290,16 @@ equation provenance; this sync does not (and cannot) connect to it.
|
|||
|
||||
## Output schema (`data/spectral_codebook.json`)
|
||||
|
||||
- header: `schema` (`spectral_codebook_v2`), `quantization` (factor,
|
||||
min support, median gap, kept/candidate/suppressed boundaries,
|
||||
sparse-tail cutoff), `clusters` (table above), `collisions` (all counts
|
||||
and explicit collision classes), `manifold_278` (when `--check-manifold`).
|
||||
- header: `schema` (`spectral_codebook_v2`), `phi_corkscrew` (packing
|
||||
base/offset + layout semantics and measured min angular separation),
|
||||
`quantization` (factor, min support, median gap,
|
||||
kept/candidate/suppressed boundaries, sparse-tail cutoff), `clusters`
|
||||
(table above), `collisions` (all counts and explicit collision classes),
|
||||
`manifold_278` (when `--check-manifold`), `cartan_floor` (when
|
||||
`--cartan-floor`: sub-Δ Fisher pairs, Δ-resolution codeword count,
|
||||
rule comparison).
|
||||
- `entries[]`: `equation_id`, `codeword`, `index`, `spectral_radius`
|
||||
(exact), `lambda_power_iteration` (legacy), `charpoly` (8 exact ints),
|
||||
`density`, `frobenius`, `trace`, optional `sparse_tail`,
|
||||
`density`, `frobenius`, `trace`, `spiral_index` (exact int packing of
|
||||
charpoly), `layout` (`radius_sq`, `angle_frac`), optional `sparse_tail`,
|
||||
optional `identical_matrix_ids`.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ polynomial coefficients as the codebook key.
|
|||
|
||||
Key advantages over power-iteration spectral radius:
|
||||
- Exact (integer coefficients, no float precision issues)
|
||||
- 192 unique fingerprints vs 180 from spectral radius
|
||||
- 196 unique fingerprints vs 182 from spectral radius (measured on the
|
||||
250-matrix corpus; identical coefficients to the exact Faddeev–LeVerrier
|
||||
fingerprints in spectral_codebook.py on all 250 matrices)
|
||||
- Cayley-Hamilton verifiable in ℤ (fits integer-only doctrine)
|
||||
- Guaranteed L∞ minimum distance of 1 between distinct codewords
|
||||
|
||||
|
|
|
|||
|
|
@ -282,13 +282,17 @@ def phi_corkscrew_index(spectral_coeffs: np.ndarray) -> int:
|
|||
|
||||
# If coefficients are integers (charpoly), use them directly
|
||||
if np.all(coeffs == np.round(coeffs)):
|
||||
# Integer packing: shift each coefficient into a unique digit position
|
||||
# Base = max(|coeff|) + 1 to guarantee injectivity
|
||||
# Offset-encoded positional packing: digit = c + abs_max ∈ [0, base)
|
||||
# with base = 2·abs_max + 1. Packing signed coefficients without
|
||||
# the offset is NOT injective: (-1, 1) and (1, 0) both give 1 in
|
||||
# base 2. base/offset are per-call here; for corpus-comparable
|
||||
# indices use spectral_codebook.pack_spiral_index with the
|
||||
# corpus-wide (base, offset) recorded in the codebook JSON.
|
||||
abs_max = int(np.max(np.abs(coeffs))) if len(coeffs) > 0 else 0
|
||||
base = max(abs_max + 1, 2)
|
||||
base = max(2 * abs_max + 1, 2)
|
||||
result = 0
|
||||
for i, c in enumerate(coeffs):
|
||||
result += int(c) * (base ** i)
|
||||
result += (int(c) + abs_max) * (base ** i)
|
||||
return result
|
||||
|
||||
# Float coefficients: normalize to [0, 1], quantize to 16-bit integers
|
||||
|
|
|
|||
|
|
@ -334,9 +334,17 @@ class TorusWinding:
|
|||
Matches: SilverSight.BraidEigensolid.TorusWinding
|
||||
a = spatial winding (latitude cycle)
|
||||
b = phase/torsion winding (longitude cycle)
|
||||
|
||||
The Q16_16 raws are LOSSY above value 32767 (Q16_MAX_RAW clamp);
|
||||
a_exact/b_exact carry the plain-ℤ winding with no clamp and are the
|
||||
authoritative values when present. BraidEigensolid.lean's
|
||||
TorusWinding needs the same widening (Int fields alongside the
|
||||
Q16_16 raws) before this exact layer can be mirrored formally.
|
||||
"""
|
||||
a: int # Q16_16 raw
|
||||
b: int # Q16_16 raw
|
||||
a: int # Q16_16 raw — saturates for values > 32767
|
||||
b: int # Q16_16 raw — saturates for values > 32767
|
||||
a_exact: Optional[int] = None # plain ℤ winding, no clamp
|
||||
b_exact: Optional[int] = None # plain ℤ winding, no clamp
|
||||
|
||||
def to_float(self) -> Tuple[float, float]:
|
||||
return q16_to_float(self.a), q16_to_float(self.b)
|
||||
|
|
@ -356,8 +364,9 @@ def spiral_to_torus_winding(spiral_index: int) -> TorusWinding:
|
|||
|
||||
⚠️ SATURATION WARNING: b_raw uses Q16.16 encoding, which clamps at
|
||||
Q16_MAX_RAW / 65536 ≈ 32767.99. For spiral_index > 65535, the
|
||||
phase winding b silently saturates. Use torus_winding_safe() for
|
||||
indices that may exceed this range.
|
||||
phase winding b silently saturates. The a_exact/b_exact fields carry
|
||||
the unclamped integers; torus_to_spiral_index prefers them, so the
|
||||
round trip is exact for every spiral_index.
|
||||
|
||||
This mirrors: TorusWinding in BraidEigensolid.lean
|
||||
"""
|
||||
|
|
@ -366,7 +375,7 @@ def spiral_to_torus_winding(spiral_index: int) -> TorusWinding:
|
|||
b_val = spiral_index // phi_step
|
||||
a_raw = float_to_q16(float(a_val))
|
||||
b_raw = float_to_q16(float(b_val))
|
||||
return TorusWinding(a=a_raw, b=b_raw)
|
||||
return TorusWinding(a=a_raw, b=b_raw, a_exact=a_val, b_exact=b_val)
|
||||
|
||||
|
||||
def spiral_to_torus_winding_safe(spiral_index: int) -> Tuple[TorusWinding, bool]:
|
||||
|
|
@ -385,18 +394,22 @@ def spiral_to_torus_winding_safe(spiral_index: int) -> Tuple[TorusWinding, bool]
|
|||
saturated = b_val > max_q16_val
|
||||
a_raw = float_to_q16(float(a_val))
|
||||
b_raw = float_to_q16(float(b_val))
|
||||
return TorusWinding(a=a_raw, b=b_raw), saturated
|
||||
return TorusWinding(a=a_raw, b=b_raw, a_exact=a_val, b_exact=b_val), saturated
|
||||
|
||||
|
||||
def torus_to_spiral_index(w: TorusWinding) -> int:
|
||||
"""Inverse: recover spiral index from torus winding.
|
||||
|
||||
n = φ_step · b + a (in integer approximation)
|
||||
n = φ_step · b + a
|
||||
|
||||
⚠️ For spiral_index > 65535, this will return a saturated value.
|
||||
Use spiral_to_torus_winding_safe() to detect this case.
|
||||
Prefers the exact ℤ fields when present (lossless for all n);
|
||||
falls back to the Q16.16 raws, which saturate for
|
||||
spiral_index > 65535 — use spiral_to_torus_winding_safe() to
|
||||
detect that case on legacy windings without exact fields.
|
||||
"""
|
||||
phi_step = int(round(PHI))
|
||||
if w.a_exact is not None and w.b_exact is not None:
|
||||
return phi_step * w.b_exact + w.a_exact
|
||||
b_int = int(round(q16_to_float(w.b)))
|
||||
a_int = int(round(q16_to_float(w.a)))
|
||||
return phi_step * b_int + a_int
|
||||
|
|
|
|||
|
|
@ -257,6 +257,186 @@ def spectral_profile(mat: Sequence[Sequence[int]]) -> dict:
|
|||
}
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
# Φ-corkscrew: integer spiral-index packing (exact) + f(n) layout
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# The packing layer is EXACT and provable: with a corpus-wide base
|
||||
# B = 2·max|c| + 1 and offset o = max|c|,
|
||||
# every offset digit c_i + o lies in [0, B), so
|
||||
# n = Σ (c_i + o)·B^i
|
||||
# is the base-B positional numeral of the digit tuple — injective on
|
||||
# char-poly fingerprints by uniqueness of positional representation, and
|
||||
# unpack_spiral_index inverts it exactly. It inherits charpoly's
|
||||
# NON-injectivity on matrices (cospectral classes share one index).
|
||||
#
|
||||
# The layout layer f(n) = (√n·cos(nψ), √n·sin(nψ)), ψ = 2π/φ², is
|
||||
# DECORATIVE placement only: injective because 1/φ² is irrational
|
||||
# (distinct n give distinct angles), information-neutral (everything is
|
||||
# recoverable from n alone), low-discrepancy by the three-distance
|
||||
# theorem. It carries no information beyond spiral_index.
|
||||
|
||||
def packing_params(fingerprints: Sequence[Sequence[int]]) -> Tuple[int, int]:
|
||||
"""Corpus-wide (base, offset) for spiral-index packing.
|
||||
|
||||
offset = max|c| over every coefficient of every fingerprint;
|
||||
base = 2·offset + 1, so offset-encoded digits fill [0, base).
|
||||
"""
|
||||
offset = max((abs(c) for fp in fingerprints for c in fp), default=0)
|
||||
return 2 * offset + 1, offset
|
||||
|
||||
|
||||
def pack_spiral_index(coeffs: Sequence[int], base: int, offset: int) -> int:
|
||||
"""Little-endian base-B packing of offset-encoded coefficients.
|
||||
|
||||
Exact integer arithmetic; injective for digits in range (raises
|
||||
otherwise). Replaces the float phinary packing of
|
||||
integration_sprint.phi_corkscrew_index, which was not injective.
|
||||
"""
|
||||
n = 0
|
||||
for i, c in enumerate(coeffs):
|
||||
d = c + offset
|
||||
if not 0 <= d < base:
|
||||
raise ValueError(
|
||||
f"coefficient {c} out of packing range for "
|
||||
f"base={base}, offset={offset}")
|
||||
n += d * base ** i
|
||||
return n
|
||||
|
||||
|
||||
def unpack_spiral_index(n: int, base: int, offset: int,
|
||||
length: int = 8) -> Tuple[int, ...]:
|
||||
"""Exact inverse of pack_spiral_index."""
|
||||
if n < 0:
|
||||
raise ValueError("spiral index must be non-negative")
|
||||
out: List[int] = []
|
||||
for _ in range(length):
|
||||
n, d = divmod(n, base)
|
||||
out.append(d - offset)
|
||||
if n:
|
||||
raise ValueError("spiral index exceeds length digits in this base")
|
||||
return tuple(out)
|
||||
|
||||
|
||||
# 1/φ² = (3 − √5)/2: the fractional rotation per step of the corkscrew.
|
||||
def angle_frac(n: int) -> float:
|
||||
"""frac(n/φ²) — the corkscrew angle of index n as a fraction of 2π.
|
||||
|
||||
Computed in decimal arithmetic at a precision that covers the integer
|
||||
part of n·(3−√5)/2, because float64 loses the fractional part
|
||||
entirely once n exceeds 2^52 (spiral indices here reach base^8).
|
||||
"""
|
||||
from decimal import Decimal, getcontext
|
||||
getcontext().prec = len(str(abs(n))) + 25
|
||||
alpha = (Decimal(3) - Decimal(5).sqrt()) / 2
|
||||
return float((Decimal(n) * alpha) % 1)
|
||||
|
||||
|
||||
def corkscrew_xy(n: int) -> Tuple[float, float]:
|
||||
"""f(n) = (√n·cos(nψ), √n·sin(nψ)), ψ = 2π/φ². Placement only."""
|
||||
theta = 2.0 * math.pi * angle_frac(n)
|
||||
r = math.sqrt(n)
|
||||
return r * math.cos(theta), r * math.sin(theta)
|
||||
|
||||
|
||||
def min_angular_separation(indices: Sequence[int]) -> Optional[float]:
|
||||
"""Minimum circular distance (as a fraction of a full turn) between
|
||||
the corkscrew angles of distinct spiral indices."""
|
||||
uniq = sorted({angle_frac(n) for n in set(indices)})
|
||||
if len(uniq) < 2:
|
||||
return None
|
||||
gaps = [uniq[i + 1] - uniq[i] for i in range(len(uniq) - 1)]
|
||||
gaps.append(1.0 - uniq[-1] + uniq[0]) # wrap-around
|
||||
return min(gaps)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
# Cartan ∆-floor quantization (Fisher metric on Δ₇)
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# ∆ = 17/1792 is the minimum eigenvalue of the Cartan crossing blocks,
|
||||
# proven exact in formal/.../CartanConnection.lean:70 (see
|
||||
# docs/cartan_fingerprint.md). It is adopted here as the resolution
|
||||
# floor on the Fisher metric: two codewords closer than ∆ are treated
|
||||
# as indistinguishable at operator resolution. This gives a PRINCIPLED
|
||||
# alternative to the 3×-median-gap heuristic; both are emitted so the
|
||||
# rules can be compared, neither replaces the other.
|
||||
|
||||
CARTAN_DELTA = Fraction(17, 1792) # exact; CartanConnection.lean:70
|
||||
|
||||
|
||||
def simplex_project(coeffs: Sequence[int]) -> Tuple[float, ...]:
|
||||
"""Project a fingerprint onto Δ₇: p_i = |c_i| / Σ|c_j|.
|
||||
|
||||
Convention: the all-zero fingerprint (nilpotent x⁸ class) maps to
|
||||
the uniform distribution — flagged in the report, since that choice
|
||||
is a convention, not a theorem.
|
||||
"""
|
||||
total = sum(abs(c) for c in coeffs)
|
||||
k = len(coeffs)
|
||||
if total == 0:
|
||||
return tuple(1.0 / k for _ in range(k))
|
||||
return tuple(abs(c) / total for c in coeffs)
|
||||
|
||||
|
||||
def fisher_distance(p: Sequence[float], q: Sequence[float]) -> float:
|
||||
"""d_F(p, q) = 2·arccos(Σ√(p_i·q_i)) — Fisher geodesic distance on
|
||||
the probability simplex (Bhattacharyya angle × 2)."""
|
||||
bc = sum(math.sqrt(a * b) for a, b in zip(p, q))
|
||||
return 2.0 * math.acos(min(1.0, bc))
|
||||
|
||||
|
||||
def cartan_floor_report(profiles: Dict[str, dict]) -> dict:
|
||||
"""All-pairs Fisher distances between distinct fingerprints; pairs
|
||||
below ∆ = 17/1792 are merged into ∆-resolution components (union-
|
||||
find), giving the codeword count the Cartan floor supports."""
|
||||
fp_rep: Dict[Tuple[int, ...], str] = {}
|
||||
for eid in sorted(profiles):
|
||||
fp_rep.setdefault(tuple(profiles[eid]["charpoly"]), eid)
|
||||
fps = list(fp_rep)
|
||||
pts = [simplex_project(fp) for fp in fps]
|
||||
delta = float(CARTAN_DELTA)
|
||||
|
||||
parent = list(range(len(fps)))
|
||||
|
||||
def find(i: int) -> int:
|
||||
while parent[i] != i:
|
||||
parent[i] = parent[parent[i]]
|
||||
i = parent[i]
|
||||
return i
|
||||
|
||||
sub_delta: List[dict] = []
|
||||
min_d, min_pair = None, None
|
||||
for i in range(len(fps)):
|
||||
for j in range(i + 1, len(fps)):
|
||||
d = fisher_distance(pts[i], pts[j])
|
||||
if min_d is None or d < min_d:
|
||||
min_d, min_pair = d, (fp_rep[fps[i]], fp_rep[fps[j]])
|
||||
if d < delta:
|
||||
sub_delta.append({
|
||||
"representatives": [fp_rep[fps[i]], fp_rep[fps[j]]],
|
||||
"fisher_distance": round(d, 8),
|
||||
})
|
||||
ri, rj = find(i), find(j)
|
||||
if ri != rj:
|
||||
parent[rj] = ri
|
||||
components = len({find(i) for i in range(len(fps))})
|
||||
return {
|
||||
"delta_exact": "17/1792",
|
||||
"delta_source": "CartanConnection.lean:70 (docs/cartan_fingerprint.md)",
|
||||
"delta_float": delta,
|
||||
"metric": "Fisher on Δ₇: d_F = 2·arccos(Σ√(p_i·q_i)); "
|
||||
"p_i = |c_i|/Σ|c_j| (all-zero fingerprint → uniform, "
|
||||
"by convention)",
|
||||
"distinct_fingerprints": len(fps),
|
||||
"pairs_below_delta": len(sub_delta),
|
||||
"sub_delta_pairs": sub_delta[:50],
|
||||
"delta_resolution_codewords": components,
|
||||
"min_fisher_distance": round(min_d, 8) if min_d is not None else None,
|
||||
"min_fisher_pair": list(min_pair) if min_pair else None,
|
||||
}
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
# Gap-aware quantization (deduped, sample-size guarded)
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -350,6 +530,14 @@ class Codebook:
|
|||
eid: spectral_profile(mat) for eid, mat in matrices.items()
|
||||
}
|
||||
|
||||
# Φ-corkscrew integer packing (corpus-wide base/offset, exact)
|
||||
self.pack_base, self.pack_offset = packing_params(
|
||||
[p["charpoly"] for p in self.profiles.values()])
|
||||
self.spiral_indices: Dict[str, int] = {
|
||||
eid: pack_spiral_index(p["charpoly"], self.pack_base, self.pack_offset)
|
||||
for eid, p in self.profiles.items()
|
||||
}
|
||||
|
||||
# dedupe byte-identical matrices BEFORE gap analysis
|
||||
self.matrix_groups: Dict[Matrix, List[str]] = {}
|
||||
for eid in sorted(self.profiles):
|
||||
|
|
@ -480,6 +668,7 @@ class Codebook:
|
|||
for eid in sorted(self.profiles):
|
||||
cw, idx = self._assign[eid]
|
||||
p = self.profiles[eid]
|
||||
n = self.spiral_indices[eid]
|
||||
entry = {
|
||||
"equation_id": eid,
|
||||
"codeword": cw,
|
||||
|
|
@ -490,6 +679,11 @@ class Codebook:
|
|||
"density": p["density"],
|
||||
"frobenius": p["frobenius"],
|
||||
"trace": p["trace"],
|
||||
"spiral_index": n,
|
||||
"layout": {
|
||||
"radius_sq": n,
|
||||
"angle_frac": round(angle_frac(n), 12),
|
||||
},
|
||||
}
|
||||
if self.is_sparse_tail(p["spectral_radius"]):
|
||||
entry["sparse_tail"] = True
|
||||
|
|
@ -507,6 +701,33 @@ class Codebook:
|
|||
"characteristic polynomial (charpoly); lambda_power_iteration is "
|
||||
"the legacy estimate and is unreliable on peripheral spectra"
|
||||
),
|
||||
"phi_corkscrew": {
|
||||
"packing": {
|
||||
"base": self.pack_base,
|
||||
"offset": self.pack_offset,
|
||||
"digits": 8,
|
||||
"order": "little-endian: charpoly c1 is digit 0",
|
||||
"semantics": (
|
||||
"spiral_index = Σ (c_i + offset)·base^i — exact "
|
||||
"integer, injective on charpoly fingerprints "
|
||||
"(unpack_spiral_index inverts it exactly); inherits "
|
||||
"charpoly's non-injectivity on matrices, so identity "
|
||||
"still needs the within-cluster index"
|
||||
),
|
||||
},
|
||||
"layout": {
|
||||
"psi": "2π/φ² (golden angle)",
|
||||
"angle_frac": "frac(n·(3−√5)/2), decimal-exact precision",
|
||||
"min_angular_separation_frac": (
|
||||
min_angular_separation(list(self.spiral_indices.values()))
|
||||
),
|
||||
"semantics": (
|
||||
"decorative placement only: f(n) is injective because "
|
||||
"1/φ² is irrational, and carries no information beyond "
|
||||
"spiral_index"
|
||||
),
|
||||
},
|
||||
},
|
||||
"quantization": {
|
||||
"method": "gap_aware_deduped_min_support",
|
||||
"gap_factor": self.gap_factor,
|
||||
|
|
@ -584,6 +805,9 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|||
help="Minimum distinct matrices per cluster (default 10)")
|
||||
ap.add_argument("--check-manifold", action="store_true",
|
||||
help="Also run over the 278-row RRC/Q16_16Manifold corpus")
|
||||
ap.add_argument("--cartan-floor", action="store_true",
|
||||
help="Also emit the Cartan ∆=17/1792 Fisher-floor report "
|
||||
"(alongside, not replacing, the gap rule)")
|
||||
ap.add_argument("--no-write", action="store_true",
|
||||
help="Report only; do not write the JSON")
|
||||
ap.add_argument("--sync-db", action="store_true",
|
||||
|
|
@ -638,6 +862,21 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|||
print(f" boundaries match 250-matrix codebook: {mc['boundaries_match_250']} "
|
||||
f"({mc['clusters']} clusters)")
|
||||
|
||||
if args.cartan_floor:
|
||||
cf = cartan_floor_report(cb.profiles)
|
||||
doc["cartan_floor"] = cf
|
||||
gap_clusters = len(q["boundaries"]) + 1
|
||||
print(f"\n Cartan ∆-floor (∆ = {cf['delta_exact']} ≈ "
|
||||
f"{cf['delta_float']:.6f}, {cf['delta_source']}):")
|
||||
print(f" distinct fingerprints on Δ₇ : {cf['distinct_fingerprints']}")
|
||||
print(f" pairs below ∆ (Fisher) : {cf['pairs_below_delta']}")
|
||||
print(f" min Fisher distance : {cf['min_fisher_distance']} "
|
||||
f"{cf['min_fisher_pair']}")
|
||||
print(f" rule comparison — codewords : "
|
||||
f"∆-floor {cf['delta_resolution_codewords']} vs "
|
||||
f"3×-median-gap {gap_clusters} (λ clusters); both emitted, "
|
||||
f"neither replaces the other")
|
||||
|
||||
if args.sync_db:
|
||||
import spectral_codebook_db
|
||||
print("\n DB sync (dry run):")
|
||||
|
|
|
|||
|
|
@ -320,5 +320,189 @@ class TestSpectralCodebookDbSync(unittest.TestCase):
|
|||
os.environ["NEON_PG"] = old
|
||||
|
||||
|
||||
class TestPhiCorkscrewPacking(unittest.TestCase):
|
||||
"""STEP 1: integer spiral-index packing — exact, injective, reversible."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.matrices = sc.parse_matrices_lean()
|
||||
cls.codebook = sc.Codebook(cls.matrices)
|
||||
|
||||
def test_base_is_odd_and_covers_corpus(self):
|
||||
base, offset = self.codebook.pack_base, self.codebook.pack_offset
|
||||
self.assertEqual(base, 2 * offset + 1)
|
||||
for p in self.codebook.profiles.values():
|
||||
for c in p["charpoly"]:
|
||||
self.assertLessEqual(abs(c), offset)
|
||||
|
||||
def test_round_trip_all_250(self):
|
||||
base, offset = self.codebook.pack_base, self.codebook.pack_offset
|
||||
for eid, p in self.codebook.profiles.items():
|
||||
fp = tuple(p["charpoly"])
|
||||
n = self.codebook.spiral_indices[eid]
|
||||
self.assertEqual(sc.unpack_spiral_index(n, base, offset, 8), fp, eid)
|
||||
|
||||
def test_injective_on_fingerprints(self):
|
||||
by_fp = {}
|
||||
for eid, p in self.codebook.profiles.items():
|
||||
by_fp.setdefault(tuple(p["charpoly"]), set()).add(
|
||||
self.codebook.spiral_indices[eid])
|
||||
# one index per fingerprint …
|
||||
for fp, idxs in by_fp.items():
|
||||
self.assertEqual(len(idxs), 1, fp)
|
||||
# … and distinct fingerprints get distinct indices
|
||||
all_idx = [next(iter(v)) for v in by_fp.values()]
|
||||
self.assertEqual(len(set(all_idx)), len(by_fp))
|
||||
|
||||
def test_signed_collision_regression(self):
|
||||
# The unoffset packing collapsed (-1, 1) and (1, 0); ours must not.
|
||||
base, offset = sc.packing_params([(-1, 1), (1, 0)])
|
||||
a = sc.pack_spiral_index((-1, 1), base, offset)
|
||||
b = sc.pack_spiral_index((1, 0), base, offset)
|
||||
self.assertNotEqual(a, b)
|
||||
|
||||
def test_out_of_range_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
sc.pack_spiral_index((5,), base=3, offset=1)
|
||||
|
||||
def test_integration_sprint_integer_path_fixed(self):
|
||||
import numpy as np
|
||||
import integration_sprint as isp
|
||||
# same shape, same abs_max (=1) → same per-call base/offset, so the
|
||||
# historical base-2 collision pair must now pack to distinct indices
|
||||
a = isp.phi_corkscrew_index(np.array([-1.0, 1.0, 0.0]))
|
||||
b = isp.phi_corkscrew_index(np.array([1.0, 0.0, -1.0]))
|
||||
self.assertNotEqual(a, b)
|
||||
|
||||
|
||||
class TestCorkscrewLayout(unittest.TestCase):
|
||||
"""STEP 2: f(n) layout — decorative placement, verified against V007."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.matrices = sc.parse_matrices_lean()
|
||||
cls.codebook = sc.Codebook(cls.matrices)
|
||||
|
||||
def test_v007_reference_point(self):
|
||||
# VERIFICATION_LOG.md V007: f(20121) = (-137.80079576, -33.64432624)
|
||||
x, y = sc.corkscrew_xy(20121)
|
||||
self.assertAlmostEqual(x, -137.80079576, places=5)
|
||||
self.assertAlmostEqual(y, -33.64432624, places=5)
|
||||
|
||||
def test_angle_frac_in_unit_interval(self):
|
||||
for eid, n in self.codebook.spiral_indices.items():
|
||||
f = sc.angle_frac(n)
|
||||
self.assertGreaterEqual(f, 0.0, eid)
|
||||
self.assertLess(f, 1.0, eid)
|
||||
|
||||
def test_angle_frac_exceeds_float64_naive(self):
|
||||
# for n >> 2^52 the float product n·α has no fractional bits left;
|
||||
# the decimal path must still give a nontrivial fraction
|
||||
n = 10 ** 40 + 7
|
||||
f = sc.angle_frac(n)
|
||||
self.assertGreaterEqual(f, 0.0)
|
||||
self.assertLess(f, 1.0)
|
||||
# consecutive indices step by frac(α): distinguishable at any scale
|
||||
g = sc.angle_frac(n + 1)
|
||||
alpha_frac = 0.3819660112501051 # frac((3−√5)/2)
|
||||
diff = (g - f) % 1.0
|
||||
self.assertAlmostEqual(diff, alpha_frac, places=9)
|
||||
|
||||
def test_layout_in_json_and_information_neutral(self):
|
||||
doc = self.codebook.to_json()
|
||||
pc = doc["phi_corkscrew"]
|
||||
self.assertEqual(pc["packing"]["base"], self.codebook.pack_base)
|
||||
sep = pc["layout"]["min_angular_separation_frac"]
|
||||
self.assertIsNotNone(sep)
|
||||
self.assertGreater(sep, 0.0)
|
||||
for entry in doc["entries"]:
|
||||
# radius² IS the spiral index — layout adds no information
|
||||
self.assertEqual(entry["layout"]["radius_sq"], entry["spiral_index"])
|
||||
|
||||
|
||||
class TestCartanFloor(unittest.TestCase):
|
||||
"""STEP 3: Cartan ∆ = 17/1792 as Fisher-distance resolution floor."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.matrices = sc.parse_matrices_lean()
|
||||
cls.codebook = sc.Codebook(cls.matrices)
|
||||
cls.report = sc.cartan_floor_report(cls.codebook.profiles)
|
||||
|
||||
def test_delta_exact_value(self):
|
||||
from fractions import Fraction
|
||||
self.assertEqual(sc.CARTAN_DELTA, Fraction(17, 1792))
|
||||
|
||||
def test_fisher_metric_axioms(self):
|
||||
p = sc.simplex_project((1, -2, 3, 0, 0, 0, 0, 4))
|
||||
q = sc.simplex_project((0, 5, 0, 1, 0, 0, 0, 0))
|
||||
self.assertAlmostEqual(sc.fisher_distance(p, p), 0.0, places=12)
|
||||
self.assertAlmostEqual(sc.fisher_distance(p, q),
|
||||
sc.fisher_distance(q, p), places=12)
|
||||
# disjoint support → maximal distance π
|
||||
u = sc.simplex_project((1, 0, 0, 0, 0, 0, 0, 0))
|
||||
v = sc.simplex_project((0, 1, 0, 0, 0, 0, 0, 0))
|
||||
import math
|
||||
self.assertAlmostEqual(sc.fisher_distance(u, v), math.pi, places=12)
|
||||
|
||||
def test_simplex_projection_sums_to_one(self):
|
||||
for p in self.codebook.profiles.values():
|
||||
pt = sc.simplex_project(p["charpoly"])
|
||||
self.assertAlmostEqual(sum(pt), 1.0, places=12)
|
||||
|
||||
def test_zero_fingerprint_convention(self):
|
||||
pt = sc.simplex_project((0,) * 8)
|
||||
self.assertEqual(pt, tuple(1.0 / 8 for _ in range(8)))
|
||||
|
||||
def test_report_consistency(self):
|
||||
r = self.report
|
||||
self.assertEqual(r["distinct_fingerprints"], 196)
|
||||
# merging k sub-∆ pairs can remove at most k components
|
||||
self.assertGreaterEqual(
|
||||
r["delta_resolution_codewords"],
|
||||
r["distinct_fingerprints"] - r["pairs_below_delta"])
|
||||
self.assertLessEqual(r["delta_resolution_codewords"],
|
||||
r["distinct_fingerprints"])
|
||||
|
||||
def test_simplex_projection_is_lossy(self):
|
||||
# |c|/Σ|c| destroys sign+scale: distinct fingerprints can collide on
|
||||
# Δ₇ (min Fisher distance 0.0 in this corpus) — similarity layer,
|
||||
# NOT an identity key. This is asserted so the doc claim stays true.
|
||||
self.assertEqual(self.report["min_fisher_distance"], 0.0)
|
||||
|
||||
|
||||
class TestTorusWindingExact(unittest.TestCase):
|
||||
"""STEP 4: torus-winding round trip must not saturate at n ≥ 65536."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
import pist_braid_bridge as pbb
|
||||
cls.pbb = pbb
|
||||
|
||||
def test_round_trip_regression_1e9(self):
|
||||
pbb = self.pbb
|
||||
for n in (0, 1, 65535, 65536, 10 ** 9):
|
||||
w = pbb.spiral_to_torus_winding(n)
|
||||
self.assertEqual(pbb.torus_to_spiral_index(w), n, n)
|
||||
|
||||
def test_exact_fields_populated(self):
|
||||
pbb = self.pbb
|
||||
w = pbb.spiral_to_torus_winding(10 ** 9)
|
||||
self.assertEqual(w.a_exact, 10 ** 9 % 2)
|
||||
self.assertEqual(w.b_exact, 10 ** 9 // 2)
|
||||
|
||||
def test_q16_raws_still_saturate_and_are_flagged(self):
|
||||
pbb = self.pbb
|
||||
w, saturated = pbb.spiral_to_torus_winding_safe(10 ** 9)
|
||||
self.assertTrue(saturated)
|
||||
self.assertEqual(w.b, pbb.Q16_MAX_RAW) # legacy raw clamps…
|
||||
self.assertEqual(pbb.torus_to_spiral_index(w), 10 ** 9) # …exact wins
|
||||
|
||||
def test_legacy_winding_without_exact_fields(self):
|
||||
pbb = self.pbb
|
||||
w = pbb.TorusWinding(a=pbb.float_to_q16(1.0), b=pbb.float_to_q16(21.0))
|
||||
self.assertEqual(pbb.torus_to_spiral_index(w), 43)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue