#!/usr/bin/env python3 """ PIST Fiedler-Aware Chiral Boundary Detection Extends PIST spectral analysis (SpectralN.lean) with Fiedler vector sign-pattern analysis for chiral boundary classification. References: - formal/SilverSight/PIST/SpectralN.lean (shift-deflation, Fiedler) - formal/SilverSight/PIST/CartanConnection.lean (D=1792, crossing weights) - formal/CoreFormalism/BraidStateN.lean (chiral state enum) """ import numpy as np from typing import Tuple, Optional # ── PIST constants (from I₂) ────────────────────────────────────────── SIGMA_Q16 = 9984 # 39/256 in Q16_16 units TAU_Q16 = 9362 # 1/7 ≈ 9362/65536 D = 1792 # lcm(7, 256) SCALE = 65536 CHIRAL_LABELS = ["achiral_stable", "left_handed", "right_handed", "chiral_scarred"] def build_laplacian_8x8(cross_coupling: float = 1e-6) -> np.ndarray: """Build graph Laplacian from the Sidon crossing matrix. The crossing matrix C has: C[i,i] = σ = 39/256 (self-weight, diagonal) C[i,j] = τ = 1/7 (paired strands, same block) C[i,j] = ε (cross-block, small coupling) The small cross-block coupling ε breaks the 4-block degeneracy so the Fiedler vector is well-defined. Args: cross_coupling: tiny cross-block weight to regularize (default 1e-6) Adjacency A = off-diagonal entries. Degree D[i,i] = sum_j A[i,j]. Laplacian L = D - A. """ C = np.zeros((8, 8), dtype=np.float64) for i in range(8): C[i, i] = 39 / 256 for j in range(8): if i != j: if i // 2 == j // 2: C[i, j] = 1 / 7 else: C[i, j] = cross_coupling A = C.copy() np.fill_diagonal(A, 0.0) D = np.diag(A.sum(axis=1)) L = D - A return L def power_iteration(mat: np.ndarray, max_iter: int = 100, tol: float = 1e-8) -> Tuple[float, np.ndarray]: """Dominant eigenvalue and eigenvector via power iteration. Args: mat: n×n symmetric matrix max_iter: maximum iterations tol: convergence tolerance (residual) Returns: (eigenvalue, eigenvector) """ n = mat.shape[0] v = np.arange(1.0, n + 1.0) for _ in range(max_iter): mv = mat @ v eig = np.dot(v, mv) / np.dot(v, v) norm = np.linalg.norm(mv) if norm < 1e-15: break v_new = mv / norm resid = np.linalg.norm(mv - eig * v) / n v = v_new if resid < tol: break mv = mat @ v eig = np.dot(v, mv) / np.dot(v, v) return eig, v def fiedler_vector(L: np.ndarray) -> Tuple[float, np.ndarray]: """Compute Fiedler value (2nd smallest eigenvalue) and vector. Uses full eigendecomposition. For n=8 this is trivially small. For larger n, use shift-deflation power iteration (SpectralN.lean). Args: L: n×n Laplacian matrix Returns: (fiedler_value, fiedler_vector) """ eig_vals, eig_vecs = np.linalg.eigh(L) # Fiedler = second smallest eigenvalue fiedler_val = eig_vals[1] fiedler_vec = eig_vecs[:, 1] return fiedler_val, fiedler_vec def classify_chiral_boundary(fiedler_vec: np.ndarray) -> str: """Classify chiral boundary state from Fiedler vector sign pattern. The Fiedler vector has one component per strand (8 total, 4 pairs). Sign pattern across paired strands determines chirality: Pattern | Chirality --------------------------------------------------------- All + (or all -) | achiral_stable (no boundary) Mixed per pair (+, -) | left_handed (mass bias) Mixed per pair (-, +) | right_handed (vector bias) Both pairs strongly mixed | chiral_scarred (topological defect) Args: fiedler_vec: 8-component Fiedler eigenvector Returns: chiral label string """ sign = np.sign(fiedler_vec) # Count sign flips within each pair intra_flips = 0 for k in range(4): if sign[2*k] != sign[2*k+1]: intra_flips += 1 # Count sign flips between adjacent pairs inter_flips = 0 for k in range(3): if sign[2*k+1] != sign[2*k+2]: inter_flips += 1 # Compute pair-wise net sign: bias within each pair bias = [] for k in range(4): pair_sign = fiedler_vec[2*k] + fiedler_vec[2*k+1] bias.append(pair_sign) net_bias = sum(bias) # Classification rules if intra_flips == 0 and inter_flips == 0: return "achiral_stable" # all same sign elif intra_flips > 0 and net_bias < 0: return "left_handed" # mass bias (negative) elif intra_flips > 0 and net_bias > 0: return "right_handed" # vector bias (positive) else: return "chiral_scarred" # mixed topological defect def compute_chiral_boundary_profile(C_matrix: Optional[np.ndarray] = None) -> dict: """Full chiral boundary analysis of the Sidon crossing matrix. Returns: dict with keys: fiedler_value, fiedler_vector, chiral_label, intra_pair_flips, inter_pair_flips, spectral_gap """ if C_matrix is not None: L = build_laplacian_from_matrix(C_matrix) else: L = build_laplacian_8x8() # Fiedler analysis f_val, f_vec = fiedler_vector(L) chiral_label = classify_chiral_boundary(f_vec) # Spectral gap lambda_max, _ = power_iteration(L) spectral_gap = lambda_max - f_val return { "fiedler_value": float(f_val), "fiedler_vector": f_vec.tolist(), "chiral_label": chiral_label, "intra_pair_flips": sum(1 for k in range(4) if np.sign(f_vec[2*k]) != np.sign(f_vec[2*k+1])), "inter_pair_flips": sum(1 for k in range(3) if np.sign(f_vec[2*k+1]) != np.sign(f_vec[2*k+2])), "spectral_gap": float(spectral_gap), "dominant_eigenvalue": float(lambda_max), } def build_laplacian_from_matrix(mat: np.ndarray) -> np.ndarray: """Build Laplacian from arbitrary 8x8 matrix. Args: mat: 8×8 adjacency/intensity matrix (Int or float) Returns: 8×8 Laplacian """ A = np.abs(mat).astype(np.float64) np.fill_diagonal(A, 0.0) D = np.diag(A.sum(axis=1)) return D - A # ── Demo ────────────────────────────────────────────────────────────── def demo(): print("=" * 60) print("PIST Fiedler-Aware Chiral Boundary Detection") print("=" * 60) L = build_laplacian_8x8() print(f"\nLaplacian L:\n{L}") lambda_max, v1 = power_iteration(L) print(f"\nλ_max (dominant): {lambda_max:.6f}") f_val, f_vec = fiedler_vector(L) print(f"Fiedler value (λ₂): {f_val:.6f}") print(f"Spectral gap: {lambda_max - f_val:.6f}") print(f"Fiedler vector: {np.array2string(f_vec, precision=6, suppress_small=True)}") print(f"Sign pattern: {np.array2string(np.sign(f_vec), precision=0, suppress_small=True)}") profile = compute_chiral_boundary_profile() print(f"\nChiral classification: {profile['chiral_label']}") print(f"Intra-pair sign flips: {profile['intra_pair_flips']}") print(f"Inter-pair sign flips: {profile['inter_pair_flips']}") # Test on perturbed matrices print("\n--- Perturbation analysis ---") # Left-handed perturbation: add negative bias to pair (0,1) L_pert = L.copy() L_pert[0, 0] += 0.5 # increase degree for strand 0 fv, _ = fiedler_vector(L_pert) print(f"Left-bias perturbation: Fiedler={fv:.6f}, chiral={classify_chiral_boundary(_)}") if __name__ == "__main__": demo()