mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
376 lines
13 KiB
Python
376 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Eigenbasis Review of the Full Physics Constraint Graph
|
|
|
|
Reviews all 773 equations through the lens of eigenvectors of the symmetrized
|
|
constraint adjacency matrix. The eigenvectors define the "natural storage modes"
|
|
of the physics knowledge graph — equations that co-locate in eigenvector space
|
|
are structurally related regardless of domain assignment.
|
|
|
|
Outputs:
|
|
1. Spectral gap analysis (which eigenvalues dominate)
|
|
2. Eigenvector coherence clusters (equations that co-locate)
|
|
3. Chiral reclassification under spectral projection
|
|
4. Equations that shift domain/layer under eigenbasis
|
|
5. Topologically isolated equations (zero spectral connectivity)
|
|
"""
|
|
|
|
import sys
|
|
sys.path.insert(0, "/home/allaun")
|
|
|
|
import json
|
|
import sqlite3
|
|
import numpy as np
|
|
from typing import Dict, List, Tuple, Set
|
|
from collections import defaultdict
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
@dataclass
|
|
class EigenEquation:
|
|
eq_id: int
|
|
name: str
|
|
domain: str
|
|
layer: int
|
|
eigenvector_idx: int
|
|
eigenvector_coordinate: float
|
|
eigenvalue: float
|
|
spectral_mass: float # |coordinate| * |eigenvalue|
|
|
chiral_old: str
|
|
chiral_new: str
|
|
shifted: bool
|
|
|
|
|
|
def load_graph(db_path: str):
|
|
"""Load full constraint graph including all chain edges and any direct edges."""
|
|
conn = sqlite3.connect(db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
cur = conn.cursor()
|
|
|
|
edges = []
|
|
cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='invariant_chains'")
|
|
if cur.fetchone():
|
|
for (a, b) in [("layer1_eq_id","layer2_eq_id"),("layer2_eq_id","layer3_eq_id"),("layer3_eq_id","layer4_eq_id")]:
|
|
cur.execute(f"SELECT DISTINCT {a} src, {b} dst FROM invariant_chains WHERE {a} IS NOT NULL AND {b} IS NOT NULL AND {a}!=0 AND {b}!=0")
|
|
for r in cur.fetchall():
|
|
edges.append((int(r["src"]), int(r["dst"])))
|
|
|
|
edges = list(set(edges))
|
|
|
|
all_ids = set()
|
|
for s, d in edges:
|
|
all_ids.add(s); all_ids.add(d)
|
|
node_ids = sorted(all_ids)
|
|
|
|
# Equation metadata
|
|
cur.execute("""
|
|
SELECT e.id, e.title, d.name as domain, d.ontological_layer
|
|
FROM equations e JOIN domains d ON e.domain_id=d.id
|
|
ORDER BY e.id
|
|
""")
|
|
eq_meta = {}
|
|
for r in cur.fetchall():
|
|
eq_meta[r["id"]] = {
|
|
"title": r["title"],
|
|
"domain": r["domain"],
|
|
"layer": r["ontological_layer"] or 0,
|
|
}
|
|
|
|
# Existing chiral
|
|
cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='chiral_eigenmass'")
|
|
old_chiral = {}
|
|
if cur.fetchone():
|
|
cur.execute("SELECT equation_id, chiral_state, chiral_residual FROM chiral_eigenmass")
|
|
for r in cur.fetchall():
|
|
old_chiral[r["equation_id"]] = {
|
|
"state": r["chiral_state"],
|
|
"residual": r["chiral_residual"],
|
|
}
|
|
|
|
conn.close()
|
|
return edges, node_ids, eq_meta, old_chiral
|
|
|
|
|
|
def build_adjacency(edges, node_ids):
|
|
"""Build symmetrized adjacency matrix."""
|
|
node_map = {eid: i for i, eid in enumerate(node_ids)}
|
|
n = len(node_ids)
|
|
A = np.zeros((n, n), dtype=np.float64)
|
|
|
|
for src, dst in edges:
|
|
if src in node_map and dst in node_map:
|
|
i, j = node_map[src], node_map[dst]
|
|
A[i, j] += 1.0
|
|
|
|
S = (A + A.T) / 2.0
|
|
return A, S, node_map
|
|
|
|
|
|
def compute_eigen(S):
|
|
"""Compute eigen-decomposition, sorted by descending abs eigenvalue."""
|
|
e_vals, e_vecs = np.linalg.eigh(S)
|
|
# Sort by descending absolute eigenvalue
|
|
order = np.argsort(-np.abs(e_vals))
|
|
e_vals = e_vals[order]
|
|
e_vecs = e_vecs[:, order]
|
|
return e_vals, e_vecs
|
|
|
|
|
|
def compute_spectral_mass(e_vals, e_vecs):
|
|
"""Compute spectral mass per node per eigenmode."""
|
|
n_modes = len(e_vals)
|
|
masses = np.zeros((len(e_vecs), n_modes))
|
|
for k in range(n_modes):
|
|
masses[:, k] = np.abs(e_vecs[:, k]) * np.abs(e_vals[k])
|
|
return masses
|
|
|
|
|
|
def classify_chiral_from_spectral(node_idx, e_vals, e_vecs, masses):
|
|
"""
|
|
Classify chiral state from spectral properties.
|
|
|
|
In eigenbasis terms:
|
|
- A node is "achiral_stable" if it has high spectral mass concentrated
|
|
in a few dominant modes and low across-mode dispersion.
|
|
- "left_handed_mass_bias" if its forward (AMVR) projection dominates
|
|
- "right_handed_vector_bias" if its reverse (AVMR) projection dominates
|
|
- "chiral_scarred" if spectral mass is anomalously low or dispersed
|
|
"""
|
|
total_mass = masses[node_idx].sum()
|
|
if total_mass < 1e-12:
|
|
return "chiral_scarred", 1.0
|
|
|
|
# Concentration: what fraction in top 3 modes?
|
|
top3 = np.sort(masses[node_idx])[-3:].sum()
|
|
concentration = top3 / total_mass
|
|
|
|
# Participation ratio (inverse of IPR = how many modes participate)
|
|
if total_mass > 0:
|
|
m_norm = masses[node_idx] / total_mass
|
|
ipr = (m_norm ** 2).sum()
|
|
participation = 1.0 / ipr if ipr > 0 else float('inf')
|
|
else:
|
|
participation = 0
|
|
|
|
total_modes = len(e_vals)
|
|
pr_ratio = participation / total_modes
|
|
|
|
# Chiral residual = 1 - concentration * pr_ratio (clamped)
|
|
chiral_residual = 1.0 - min(1.0, concentration * min(1.0, pr_ratio))
|
|
|
|
if concentration > 0.7 and pr_ratio < 0.3:
|
|
state = "achiral_stable"
|
|
elif concentration < 0.3:
|
|
state = "chiral_scarred"
|
|
elif masses[node_idx, :len(e_vals)//2].sum() > masses[node_idx, len(e_vals)//2:].sum():
|
|
state = "left_handed_mass_bias" # positive-half modes dominate
|
|
else:
|
|
state = "right_handed_vector_bias" # negative-half modes dominate
|
|
|
|
return state, chiral_residual
|
|
|
|
|
|
def detect_shifts(node_ids, eq_meta, old_chiral, e_vals, e_vecs, masses):
|
|
"""Detect equations whose classification shifts under eigenbasis."""
|
|
shifts = []
|
|
for i, eid in enumerate(node_ids):
|
|
state_new, res_new = classify_chiral_from_spectral(i, e_vals, e_vecs, masses)
|
|
meta = eq_meta.get(eid, {"title": f"Eq_{eid}", "domain": "Unknown", "layer": 0})
|
|
old = old_chiral.get(eid, {"state": "unknown", "residual": 0.0})
|
|
|
|
shifted = (state_new != old["state"])
|
|
|
|
best_mode = np.argmax(masses[i])
|
|
coordinate = e_vecs[i, best_mode]
|
|
|
|
shifts.append(EigenEquation(
|
|
eq_id=eid,
|
|
name=meta["title"],
|
|
domain=meta["domain"],
|
|
layer=meta["layer"],
|
|
eigenvector_idx=best_mode,
|
|
eigenvector_coordinate=float(coordinate),
|
|
eigenvalue=float(e_vals[best_mode]),
|
|
spectral_mass=float(masses[i].sum()),
|
|
chiral_old=old["state"],
|
|
chiral_new=state_new,
|
|
shifted=shifted,
|
|
))
|
|
|
|
return shifts
|
|
|
|
|
|
def find_spectral_clusters(e_vecs, node_ids, eq_meta, top_modes=5):
|
|
"""Find equations that cluster together in eigenvector space."""
|
|
clusters = defaultdict(list)
|
|
for i, eid in enumerate(node_ids):
|
|
coords = e_vecs[i, :top_modes]
|
|
label = eid % 100 # simplistic, good enough for review
|
|
for k in range(top_modes):
|
|
key = (k, round(coords[k] * 100))
|
|
clusters[key].append(eid)
|
|
return clusters
|
|
|
|
|
|
def find_isolated(node_ids, mass_matrix, threshold=1e-6):
|
|
"""Find equations with near-zero spectral connectivity."""
|
|
isolated = []
|
|
for i, eid in enumerate(node_ids):
|
|
if mass_matrix[i].sum() < threshold:
|
|
isolated.append(eid)
|
|
return isolated
|
|
|
|
|
|
def run_eigenbasis_review(db_path: str) -> dict:
|
|
print("Loading graph...")
|
|
edges, node_ids, eq_meta, old_chiral = load_graph(db_path)
|
|
print(f" {len(edges)} edges, {len(node_ids)} nodes")
|
|
|
|
A, S, node_map = build_adjacency(edges, node_ids)
|
|
print(f" Adjacency: {A.sum():.0f} total edges, {np.count_nonzero(A)} directed connections")
|
|
|
|
print("Computing eigen-decomposition...")
|
|
e_vals, e_vecs = compute_eigen(S)
|
|
|
|
# Spectral gap
|
|
gaps = np.diff(np.abs(e_vals))
|
|
max_gap_idx = np.argmax(np.abs(gaps))
|
|
print(f" {len(e_vals)} eigenvalues, range=[{e_vals[0]:.3f}, {e_vals[-1]:.3f}]")
|
|
print(f" Max spectral gap at index {max_gap_idx}: {gaps[max_gap_idx]:.3f}")
|
|
|
|
masses = compute_spectral_mass(e_vals, e_vecs)
|
|
|
|
print("Detecting shifts...")
|
|
eq_shifts = detect_shifts(node_ids, eq_meta, old_chiral, e_vals, e_vecs, masses)
|
|
|
|
shifted = [eq for eq in eq_shifts if eq.shifted]
|
|
not_found = [eq for eq in eq_shifts if eq.chiral_old == "unknown"]
|
|
isolated = find_isolated(node_ids, masses)
|
|
|
|
print(f" Shifts: {len(shifted)} equations changed classification")
|
|
print(f" New (not in old chiral): {len(not_found)} equations")
|
|
print(f" Isolated (near-zero spectral mass): {len(isolated)} equations")
|
|
|
|
clusters = find_spectral_clusters(e_vecs, node_ids, eq_meta)
|
|
|
|
# Build report
|
|
report = {
|
|
"num_nodes": len(node_ids),
|
|
"num_edges": len(edges),
|
|
"num_eigenvalues": len(e_vals),
|
|
"eigenvalue_range": [float(e_vals[0]), float(e_vals[-1])],
|
|
"spectral_gap_max_idx": int(max_gap_idx),
|
|
"spectral_gap_max_value": float(gaps[max_gap_idx]),
|
|
"num_shifted": len(shifted),
|
|
"num_new_equations": len(not_found),
|
|
"num_isolated": len(isolated),
|
|
"shifted_equations": [
|
|
{
|
|
"eq_id": eq.eq_id,
|
|
"name": eq.name[:80],
|
|
"domain": eq.domain,
|
|
"layer": eq.layer,
|
|
"chiral_old": eq.chiral_old,
|
|
"chiral_new": eq.chiral_new,
|
|
"dominant_mode": eq.eigenvector_idx,
|
|
"spectral_mass": round(eq.spectral_mass, 6),
|
|
"eigenvalue": round(float(eq.eigenvalue), 4),
|
|
}
|
|
for eq in sorted(shifted, key=lambda x: -x.spectral_mass)
|
|
],
|
|
"new_equations": [
|
|
{
|
|
"eq_id": eq.eq_id,
|
|
"name": eq.name[:80],
|
|
"domain": eq.domain,
|
|
"chiral_new": eq.chiral_new,
|
|
"spectral_mass": round(eq.spectral_mass, 6),
|
|
}
|
|
for eq in sorted(not_found, key=lambda x: -x.spectral_mass)
|
|
],
|
|
"isolated_equations": [
|
|
{
|
|
"eq_id": eid,
|
|
"name": eq_meta.get(eid, {"title": f"Eq_{eid}"})["title"][:80],
|
|
"domain": eq_meta.get(eid, {"domain": "Unknown"})["domain"],
|
|
}
|
|
for eid in sorted(isolated)
|
|
],
|
|
"top_eigenmodes": [
|
|
{
|
|
"mode": k,
|
|
"eigenvalue": round(float(e_vals[k]), 4),
|
|
"num_nodes_below_threshold": int((np.abs(e_vecs[:, k]) > 0.01).sum()),
|
|
"top_nodes": [
|
|
{
|
|
"eq_id": node_ids[i],
|
|
"name": eq_meta.get(node_ids[i], {"title": f"Eq_{node_ids[i]}"})["title"][:60],
|
|
"coordinate": round(float(e_vecs[i, k]), 6),
|
|
}
|
|
for i in np.argsort(-np.abs(e_vecs[:, k]))[:10]
|
|
],
|
|
}
|
|
for k in range(min(5, len(e_vals)))
|
|
],
|
|
}
|
|
|
|
return report
|
|
|
|
|
|
def print_report(report: dict):
|
|
"""Print human-readable eigenbasis review."""
|
|
print()
|
|
print("=" * 70)
|
|
print("EIGENBASIS REVIEW — Constraint Graph Spectral Analysis")
|
|
print("=" * 70)
|
|
print(f" Nodes: {report['num_nodes']}")
|
|
print(f" Edges: {report['num_edges']}")
|
|
print(f" Eigenvalues: {report['num_eigenvalues']}")
|
|
print(f" Range: [{report['eigenvalue_range'][0]:.3f}, {report['eigenvalue_range'][1]:.3f}]")
|
|
print(f" Max spectral gap: {report['spectral_gap_max_value']:.3f} (mode {report['spectral_gap_max_idx']})")
|
|
print()
|
|
|
|
print(f" Shifted classifications: {report['num_shifted']}")
|
|
print(f" New equations (not in chiral_eigenmass): {report['num_new_equations']}")
|
|
print(f" Topologically isolated: {report['num_isolated']}")
|
|
print()
|
|
|
|
if report["shifted_equations"]:
|
|
print("--- SHIFTED EQUATIONS ---")
|
|
print(f" {'ID':>5} {'Name':60s} {'Old':20s} {'New':20s} {'Mass':>10s}")
|
|
print(f" {'-'*5} {'-'*60} {'-'*20} {'-'*20} {'-'*10}")
|
|
for eq in report["shifted_equations"]:
|
|
print(f" {eq['eq_id']:>5} {eq['name'][:60]:60s} {eq['chiral_old']:20s} {eq['chiral_new']:20s} {eq['spectral_mass']:10.6f}")
|
|
print()
|
|
|
|
if report["new_equations"]:
|
|
print("--- NEW EQUATIONS (not in old chiral) ---")
|
|
for eq in report["new_equations"][:15]:
|
|
print(f" #{eq['eq_id']:>4} {eq['chiral_new']:25s} {eq['name'][:70]}")
|
|
if len(report["new_equations"]) > 15:
|
|
print(f" ... and {len(report['new_equations']) - 15} more")
|
|
print()
|
|
|
|
if report["isolated_equations"]:
|
|
print("--- ISOLATED EQUATIONS (near-zero spectral mass) ---")
|
|
for eq in report["isolated_equations"][:10]:
|
|
print(f" #{eq['eq_id']:>4} {eq['domain']:25s} {eq['name'][:60]}")
|
|
print()
|
|
|
|
print("--- TOP EIGENMODES ---")
|
|
for mode in report["top_eigenmodes"]:
|
|
print(f" Mode {mode['mode']}: λ = {mode['eigenvalue']:.4f} ({mode['num_nodes_below_threshold']} nodes |coord| > 0.01)")
|
|
for n in mode["top_nodes"][:5]:
|
|
print(f" #{n['eq_id']:>4} coord={n['coordinate']:+8.4f} {n['name']}")
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
db_path = "/home/allaun/physics_equations.db"
|
|
report = run_eigenbasis_review(db_path)
|
|
print_report(report)
|
|
|
|
# Save to file
|
|
with open("/home/allaun/cff/eigenbasis_review.json", "w") as f:
|
|
json.dump(report, f, indent=2, sort_keys=True, default=str)
|
|
print("Report saved to /home/allaun/cff/eigenbasis_review.json")
|