mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
1069 lines
41 KiB
Python
1069 lines
41 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
rrc_refactor_oracle.py — Chaos-game-driven RRC self-refactoring oracle.
|
||
|
||
Uses the Burgers representation chaos game engine to drive refactoring
|
||
decisions on the RRC domain manifold graph.
|
||
|
||
Loop per iteration:
|
||
1. Read current manifold graph (354 nodes, 33 edges)
|
||
2. Build adjacency matrix from graph edges
|
||
3. Run BurgersChaosGame quantum walk (eigenvector centrality)
|
||
4. Compute centrality deltas from previous iteration
|
||
5. Generate refactoring commands:
|
||
- LOW centrality → PRUNE (dead-end representation)
|
||
- HIGH centrality delta → PROMOTE (split into sub-specializations)
|
||
- SIMILAR centrality + strong edge → MERGE (redundant connection)
|
||
- LOW internal edge density → SPLIT cluster
|
||
6. Apply commands → new manifold graph (sacrificial copy)
|
||
7. Emit receipt with delta summary
|
||
8. Goto 1
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import copy
|
||
import hashlib
|
||
import json
|
||
import math
|
||
import sys
|
||
import time
|
||
from collections import defaultdict
|
||
from datetime import datetime, timezone
|
||
from pathlib import Path
|
||
from typing import Any, Optional
|
||
|
||
import numpy as np
|
||
|
||
SHIM = Path(__file__).resolve().parent
|
||
sys.path.insert(0, str(SHIM))
|
||
from burgers_chaos_game import BurgersChaosGame # quantum walk engine
|
||
|
||
PRUNE_CENTRALITY_THRESHOLD = 0.01 # nodes below this get pruned
|
||
PROMOTE_DELTA_THRESHOLD = 0.05 # centrality delta above this gets promoted
|
||
MERGE_EDGE_SIMILARITY = 0.15 # centrality diff below this + edge = merge
|
||
CLUSTER_DENSITY_THRESHOLD = 0.1 # cluster density below this = split target
|
||
MAX_ITERATIONS = 10 # safety limit
|
||
MAX_MERGES_PER_ITER = 10 # optimal from SLO sweep (max speedup before community collapse)
|
||
MINIMUM_EDGE_DENSITY = 0.005 # if graph is sparser, trigger inference fallback
|
||
|
||
GRAPH_BUILDER = SHIM / "rrc_domain_manifold_graph.py"
|
||
MANIFOLD_ASSIGNMENT = SHIM.parent.parent / "shared-data/data/rrc_manifold_assignment_v1.json"
|
||
|
||
# Greek epigenetic state → PRUNE/MERGE preference
|
||
# Higher protect_level = less likely to be pruned or merged away
|
||
GREEK_PROTECT: dict[str, int] = {
|
||
"Φ": 5, # Stable — never prune, prefer as merge target
|
||
"Λ": 4, # Attractor — rarely prune
|
||
"Κ": 3, # Poised — prune only if centrality is extremely low
|
||
"Ρ": 3, # Regulated — same as Κ
|
||
"Σ": 2, # Symmetric — merge with caution
|
||
"Π": 2, # Potential — merge with caution
|
||
"Ω": 1, # Terminal — prune freely
|
||
"Ζ": 0, # Boundary — prune first
|
||
}
|
||
|
||
GREEK_COMPATIBLE: dict[str, list[str]] = {
|
||
"Φ": ["Λ", "Κ", "Σ"],
|
||
"Λ": ["Φ", "Ρ", "Π"],
|
||
"Κ": ["Φ", "Ω", "Ζ"],
|
||
"Ρ": ["Λ", "Ω", "Π"],
|
||
"Ω": ["Κ", "Ρ", "Ζ"],
|
||
"Σ": ["Φ", "Π", "Ζ"],
|
||
"Π": ["Λ", "Ρ", "Σ"],
|
||
"Ζ": ["Κ", "Ω", "Σ"],
|
||
}
|
||
|
||
# Route → keywords for graph node Greek annotation (same as rrc_manifold_assign.py)
|
||
ROUTE_GREEK_KEYWORDS: list[tuple[str, list[str], str, int, str, str]] = [
|
||
("thermodynamic_energy", [
|
||
"energy", "entropy", "heat", "carnot", "landauer", "temperature",
|
||
"thermodynamic", "thermal", "dissipation", "efficiency", "joule",
|
||
], "Φ", 0, "ambidextrous", "forward"),
|
||
("geometry_topology", [
|
||
"geodesic", "metric", "stereographic", "euclidean", "manifold",
|
||
"curvature", "riemann", "tensor", "topology", "holonomy",
|
||
"connection", "bundle", "chart", "topological", "topology",
|
||
], "Λ", 45, "left", "forward"),
|
||
("cognitive_load", [
|
||
"cognitive", "load", "emotional", "signal", "attention",
|
||
"salience", "novelty", "surprise", "gate",
|
||
], "Κ", 135, "left", "forward"),
|
||
("compression_route", [
|
||
"compress", "hutter", "encoding", "codec",
|
||
"bit", "rate", "distortion", "redundancy",
|
||
], "Σ", 225, "right", "reverse"),
|
||
("magnetic_signal", [
|
||
"magnetic", "magneto", "field", "wave", "plasma",
|
||
"flux", "induction", "mhd", "alfven",
|
||
], "Π", 270, "right", "reverse"),
|
||
("control_signal", [
|
||
"control", "gate", "overflow", "gain", "tuning",
|
||
"cascade", "feedback", "regulator", "threshold",
|
||
], "Ρ", 90, "ambidextrous", "forward"),
|
||
("chaotic_couch", [
|
||
"chaotic", "couch", "soliton", "turbulence", "vortex",
|
||
"strange", "attractor", "lyapunov",
|
||
], "Ω", 180, "ambidextrous", "reverse"),
|
||
("number_theory", [
|
||
"prime", "modulo", "congruence", "diophantine", "integer",
|
||
"arithmetic", "logarithm", "lower_bound", "bound", "number",
|
||
"pell", "exponential", "s-unit", "goormaghtigh",
|
||
], "Ζ", 315, "right", "reverse"),
|
||
]
|
||
|
||
# =========================================================================
|
||
|
||
|
||
def load_manifold_graph(path: Path) -> dict:
|
||
"""Load a domain_manifold_graph_v1 JSON."""
|
||
return json.loads(path.read_text())
|
||
|
||
|
||
def load_greek_assignment(path: Path | None = None) -> dict[str, dict]:
|
||
"""Load Greek epigenetic states from manifold assignment, keyed by equation name."""
|
||
if path is None:
|
||
path = MANIFOLD_ASSIGNMENT
|
||
if not path.exists():
|
||
print(f" Greek assignment not found at {path}, skipping enrichment",
|
||
file=sys.stderr)
|
||
return {}
|
||
data = json.loads(path.read_text())
|
||
greek_map: dict[str, dict] = {}
|
||
for eq in data.get("equations", []):
|
||
name = eq.get("name", "")
|
||
greek_map[name] = {
|
||
"greek": eq.get("greek_state", "Ζ"),
|
||
"phase": eq.get("greek_phase", 315),
|
||
"chirality": eq.get("greek_chirality", "right"),
|
||
"direction": eq.get("greek_direction", "reverse"),
|
||
"regime": eq.get("regime", ""),
|
||
"route": eq.get("route", ""),
|
||
}
|
||
print(f" Loaded {len(greek_map)} Greek states from assignment",
|
||
file=sys.stderr)
|
||
return greek_map
|
||
|
||
|
||
def enrich_graph_by_keywords(graph: dict) -> dict:
|
||
"""Annotate manifold graph nodes with Greek epigenetic state by keyword matching.
|
||
|
||
Uses the same route keyword patterns as rrc_manifold_assign.py but applied
|
||
at the graph-node level (domain category labels vs individual equation names).
|
||
|
||
Returns the graph with Greek state, phase, chirality, direction added to each node.
|
||
"""
|
||
for node in graph.get("nodes", []):
|
||
label = (node.get("label", "") + " " + node.get("id", "")).lower()
|
||
best_route = None
|
||
best_score = 0
|
||
best_greek = "Ζ"
|
||
best_phase = 315
|
||
best_chirality = "right"
|
||
best_direction = "reverse"
|
||
for route, keywords, greek, phase, chirality, direction in ROUTE_GREEK_KEYWORDS:
|
||
score = sum(1 for kw in keywords if kw.lower() in label)
|
||
if score > best_score:
|
||
best_score = score
|
||
best_route = route
|
||
best_greek = greek
|
||
best_phase = phase
|
||
best_chirality = chirality
|
||
best_direction = direction
|
||
node["greek_state"] = best_greek
|
||
node["greek_route"] = best_route or ""
|
||
node["greek_phase"] = best_phase
|
||
node["greek_chirality"] = best_chirality
|
||
node["greek_direction"] = best_direction
|
||
node["greek_match_score"] = best_score
|
||
return graph
|
||
|
||
|
||
def enrich_graph_with_greek(graph: dict, greek_map: dict[str, dict]) -> dict:
|
||
"""Annotate manifold graph nodes with Greek epigenetic state.
|
||
|
||
Two-phase strategy:
|
||
1. Try exact name/id match against the assignment equations (greek_map).
|
||
2. Fall back to keyword matching on graph node label + id.
|
||
"""
|
||
for node in graph.get("nodes", []):
|
||
label = node.get("label", "")
|
||
nid = node.get("id", "")
|
||
matched = greek_map.get(label) or greek_map.get(nid)
|
||
if matched is None:
|
||
for eq_name, gs in greek_map.items():
|
||
if eq_name.lower() in label.lower() or label.lower() in eq_name.lower():
|
||
matched = gs
|
||
break
|
||
if matched:
|
||
node["greek_state"] = matched["greek"]
|
||
node["greek_phase"] = matched["phase"]
|
||
node["greek_chirality"] = matched["chirality"]
|
||
node["greek_direction"] = matched["direction"]
|
||
node["greek_match_score"] = 10
|
||
continue
|
||
# Fallback: keyword matching at route level
|
||
label_lower = (label + " " + nid).lower()
|
||
best_route = None
|
||
best_score = 0
|
||
best_greek = "Ζ"
|
||
best_phase = 315
|
||
best_chirality = "right"
|
||
best_direction = "reverse"
|
||
for route, keywords, greek, phase, chirality, direction in ROUTE_GREEK_KEYWORDS:
|
||
score = sum(1 for kw in keywords if kw.lower() in label_lower)
|
||
if score > best_score:
|
||
best_score = score
|
||
best_route = route
|
||
best_greek = greek
|
||
best_phase = phase
|
||
best_chirality = chirality
|
||
best_direction = direction
|
||
node["greek_state"] = best_greek
|
||
node["greek_route"] = best_route or ""
|
||
node["greek_phase"] = best_phase
|
||
node["greek_chirality"] = best_chirality
|
||
node["greek_direction"] = best_direction
|
||
node["greek_match_score"] = best_score
|
||
return graph
|
||
|
||
|
||
def greek_spectrum(graph: dict) -> dict:
|
||
"""Compute Greek epigenetic state distribution across graph nodes."""
|
||
counts = defaultdict(int)
|
||
phase_counts = defaultdict(int)
|
||
chirality_counts = defaultdict(int)
|
||
direction_counts = defaultdict(int)
|
||
route_counts = defaultdict(int)
|
||
score_buckets = {"high": 0, "medium": 0, "low": 0, "none": 0}
|
||
for node in graph.get("nodes", []):
|
||
gs = node.get("greek_state", "Ζ")
|
||
counts[gs] += 1
|
||
phase_counts[node.get("greek_phase", 315)] += 1
|
||
chirality_counts[node.get("greek_chirality", "right")] += 1
|
||
direction_counts[node.get("greek_direction", "reverse")] += 1
|
||
route_counts[node.get("greek_route", "")] += 1
|
||
score = node.get("greek_match_score", 0)
|
||
if score >= 3:
|
||
score_buckets["high"] += 1
|
||
elif score >= 1:
|
||
score_buckets["medium"] += 1
|
||
elif score > 0:
|
||
score_buckets["low"] += 1
|
||
else:
|
||
score_buckets["none"] += 1
|
||
return {
|
||
"by_greek": dict(counts),
|
||
"by_phase": dict(phase_counts),
|
||
"by_chirality": dict(chirality_counts),
|
||
"by_direction": dict(direction_counts),
|
||
"by_route": dict(route_counts),
|
||
"match_quality": score_buckets,
|
||
}
|
||
|
||
|
||
def infer_edges_from_metadata(graph: dict) -> list[dict]:
|
||
"""Build edges from intrinsic node metadata without external access.
|
||
|
||
Covers all edge types that don't require live arXiv queries:
|
||
- kernel_internal: nodes sharing the same kernel
|
||
- kernel_hub: cross-kernel hub connections
|
||
- dimension_chain: adjacency along dimension axis
|
||
- shared_paper: shared paper IDs in node metadata (unverified)
|
||
- topic_overlap: token overlap in node labels
|
||
"""
|
||
nodes = graph.get("nodes", [])
|
||
edges = graph.get("edges", [])
|
||
existing_pairs = set()
|
||
for e in edges:
|
||
existing_pairs.add(tuple(sorted([e["source"], e["target"]])))
|
||
|
||
new_edges: list[dict] = []
|
||
added: set = set(existing_pairs)
|
||
|
||
def add(src: str, tgt: str, etype: str, **kw):
|
||
pair = tuple(sorted([src, tgt]))
|
||
if pair not in added:
|
||
added.add(pair)
|
||
new_edges.append({"source": src, "target": tgt, "type": etype, **kw})
|
||
|
||
# Group nodes by kernel
|
||
kernel_nodes: dict[str, list[dict]] = defaultdict(list)
|
||
for n in nodes:
|
||
kernel_nodes[n.get("kernel", "other")].append(n)
|
||
node_map = {n["id"]: n for n in nodes}
|
||
|
||
# Edge type 1: kernel_internal — 15% random within same kernel
|
||
import random
|
||
rng = random.Random(42)
|
||
for kernel, members in kernel_nodes.items():
|
||
if len(members) < 2:
|
||
continue
|
||
for i in range(len(members)):
|
||
for j in range(i + 1, len(members)):
|
||
if rng.random() < 0.15:
|
||
add(members[i]["id"], members[j]["id"],
|
||
"kernel_internal",
|
||
kernel=kernel,
|
||
dimension=members[i].get("dimension"))
|
||
|
||
# Edge type 2: kernel_hub — connect hubs (highest-degree node) across kernels
|
||
hubs: dict[str, str] = {}
|
||
for kernel, members in kernel_nodes.items():
|
||
hubs[kernel] = members[len(members) // 2]["id"] # deterministic hub
|
||
kernel_names = list(kernel_nodes.keys())
|
||
for i in range(len(kernel_names)):
|
||
for j in range(i + 1, len(kernel_names)):
|
||
add(hubs[kernel_names[i]], hubs[kernel_names[j]],
|
||
"kernel_hub",
|
||
from_kernel=kernel_names[i],
|
||
to_kernel=kernel_names[j])
|
||
|
||
# Edge type 3: dimension_chain — adjacent dimensions
|
||
dim_groups: dict[int, list[dict]] = defaultdict(list)
|
||
for n in nodes:
|
||
dim_groups[n.get("dimension", -1)].append(n)
|
||
for dim in sorted(dim_groups):
|
||
if dim + 1 in dim_groups:
|
||
a = dim_groups[dim][0]
|
||
b = dim_groups[dim + 1][0]
|
||
add(a["id"], b["id"], "dimension_chain",
|
||
from_dim=dim, to_dim=dim + 1)
|
||
|
||
# Edge type 4: shared_paper — unverified, from node metadata
|
||
paper_nodes: dict[str, list[str]] = defaultdict(list)
|
||
for n in nodes:
|
||
for pid in n.get("papers", []):
|
||
paper_nodes[pid].append(n["id"])
|
||
for pid, nids in paper_nodes.items():
|
||
for i in range(len(nids)):
|
||
for j in range(i + 1, len(nids)):
|
||
add(nids[i], nids[j], "shared_paper",
|
||
paper_id=pid, verified=False)
|
||
|
||
# Edge type 5: topic_overlap — label token overlap
|
||
for i in range(len(nodes)):
|
||
ni = nodes[i]
|
||
ni_tokens = set(ni.get("label", "").lower().split())
|
||
for j in range(i + 1, len(nodes)):
|
||
nj = nodes[j]
|
||
overlap = ni_tokens & set(nj.get("label", "").lower().split())
|
||
if len(overlap) >= 2:
|
||
add(ni["id"], nj["id"], "topic_overlap",
|
||
overlap_count=len(overlap),
|
||
shared_terms=list(overlap)[:5])
|
||
|
||
return new_edges
|
||
|
||
|
||
def build_or_load_graph(path: Path, skip_live: bool = False) -> tuple[dict, str]:
|
||
"""Load graph, ensuring minimum edge density.
|
||
|
||
Strategy:
|
||
1. Load graph from file
|
||
2. If density ≥ threshold: return as-is (origin: file)
|
||
3. If density < threshold and not skip_live: try live rebuild
|
||
4. If live rebuild fails or skip_live: apply intrinsic inference
|
||
|
||
Returns (graph, origin_string) where origin_string is one of:
|
||
"file" — loaded as-is, sufficient edges
|
||
"live" — rebuilt via external graph builder
|
||
"inferred" — fallback: edges built from node metadata
|
||
"""
|
||
graph = load_manifold_graph(path)
|
||
nodes = graph.get("nodes", [])
|
||
edges = graph.get("edges", [])
|
||
n = len(nodes)
|
||
density = (2 * len(edges)) / max(n * (n - 1), 1)
|
||
|
||
if density >= MINIMUM_EDGE_DENSITY:
|
||
return graph, "file"
|
||
|
||
print(f" Graph density {density:.6f} < threshold {MINIMUM_EDGE_DENSITY}",
|
||
file=sys.stderr)
|
||
|
||
# Try live rebuild
|
||
origin = "file"
|
||
if not skip_live and GRAPH_BUILDER.exists():
|
||
try:
|
||
print(f" Attempting live rebuild via {GRAPH_BUILDER.name}...",
|
||
file=sys.stderr)
|
||
import subprocess
|
||
r = subprocess.run(
|
||
[sys.executable, str(GRAPH_BUILDER)],
|
||
capture_output=True, text=True, timeout=120,
|
||
)
|
||
if r.returncode == 0 and path.exists():
|
||
graph = load_manifold_graph(path)
|
||
edges = graph.get("edges", [])
|
||
density = (2 * len(edges)) / max(n * (n - 1), 1)
|
||
if density >= MINIMUM_EDGE_DENSITY:
|
||
print(f" Live rebuild succeeded: {len(edges)} edges",
|
||
file=sys.stderr)
|
||
return graph, "live"
|
||
else:
|
||
print(f" Live rebuild produced {len(edges)} edges "
|
||
f"(density {density:.6f}), still too sparse",
|
||
file=sys.stderr)
|
||
else:
|
||
print(f" Live rebuild failed (rc={r.returncode}): "
|
||
f"{r.stderr.strip()[-200:]}", file=sys.stderr)
|
||
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
|
||
print(f" Live rebuild exception: {e}", file=sys.stderr)
|
||
|
||
# Fallback: intrinsic inference
|
||
print(f" Falling back to intrinsic edge inference from metadata...",
|
||
file=sys.stderr)
|
||
new_edges = infer_edges_from_metadata(graph)
|
||
graph["edges"] = graph.get("edges", []) + new_edges
|
||
graph["stats"] = {
|
||
"total_nodes": len(nodes),
|
||
"total_edges": len(graph["edges"]),
|
||
"by_type": {t: sum(1 for e in graph["edges"] if e["type"] == t)
|
||
for t in set(e["type"] for e in graph["edges"])},
|
||
"inferred": True,
|
||
"inferred_edge_count": len(new_edges),
|
||
}
|
||
density = (2 * len(graph["edges"])) / max(len(nodes) * (len(nodes) - 1), 1)
|
||
print(f" Inference produced {len(new_edges)} new edges, "
|
||
f"density now {density:.6f}", file=sys.stderr)
|
||
return graph, "inferred"
|
||
|
||
|
||
def graph_to_adjacency(graph: dict) -> tuple[np.ndarray, list[str]]:
|
||
"""Convert domain manifold graph to adjacency matrix + node labels.
|
||
|
||
Nodes = unique node IDs from the graph.
|
||
Edges from the edge list.
|
||
Returns (N×N adjacency, [node_id_0, ..., node_id_{N-1}]).
|
||
"""
|
||
nodes = graph.get("nodes", [])
|
||
edges = graph.get("edges", [])
|
||
|
||
node_ids = [n["id"] for n in nodes]
|
||
id_to_idx = {nid: i for i, nid in enumerate(node_ids)}
|
||
N = len(node_ids)
|
||
|
||
A = np.zeros((N, N), dtype=np.float64)
|
||
|
||
for e in edges:
|
||
src = e.get("source", "")
|
||
tgt = e.get("target", "")
|
||
if src in id_to_idx and tgt in id_to_idx:
|
||
i, j = id_to_idx[src], id_to_idx[tgt]
|
||
A[i, j] = A[j, i] = 1.0
|
||
|
||
return A, node_ids
|
||
|
||
|
||
def graph_stats(graph: dict) -> dict:
|
||
"""Compute structural statistics of the manifold graph."""
|
||
nodes = graph.get("nodes", [])
|
||
edges = graph.get("edges", [])
|
||
|
||
# Count by kernel/dimension
|
||
kernel_counts: dict[str, int] = defaultdict(int)
|
||
dim_counts: dict[int, int] = defaultdict(int)
|
||
for n in nodes:
|
||
kernel_counts[n.get("kernel", "unknown")] += 1
|
||
dim_counts[n.get("dimension", -1)] += 1
|
||
|
||
edge_types: dict[str, int] = defaultdict(int)
|
||
for e in edges:
|
||
edge_types[e.get("type", "unknown")] += 1
|
||
|
||
return {
|
||
"num_nodes": len(nodes),
|
||
"num_edges": len(edges),
|
||
"density": (2 * len(edges)) / max(len(nodes) * (len(nodes) - 1), 1),
|
||
"kernel_counts": dict(kernel_counts),
|
||
"dimension_counts": dict(dim_counts),
|
||
"edge_type_counts": dict(edge_types),
|
||
}
|
||
|
||
|
||
# =========================================================================
|
||
# II. Refactoring Command Generator
|
||
# =========================================================================
|
||
|
||
|
||
def generate_refactoring_commands(
|
||
graph: dict,
|
||
centrality: np.ndarray,
|
||
node_ids: list[str],
|
||
prev_centrality: Optional[dict[str, float]] = None,
|
||
*,
|
||
greek_map: Optional[dict[str, dict]] = None,
|
||
prune_threshold: float = PRUNE_CENTRALITY_THRESHOLD,
|
||
promote_threshold: float = PROMOTE_DELTA_THRESHOLD,
|
||
merge_similarity: float = MERGE_EDGE_SIMILARITY,
|
||
max_merges: int = MAX_MERGES_PER_ITER,
|
||
) -> list[dict]:
|
||
"""Generate refactoring commands from centrality analysis.
|
||
|
||
When greek_map is provided, PRUNE/MERGE decisions are modulated by
|
||
Greek epigenetic state: stable states (Φ, Λ) are protected from
|
||
pruning; incompatible Greek states are deprioritized for merges.
|
||
|
||
Returns list of command dicts:
|
||
{ "action": "prune"|"promote"|"merge"|"split",
|
||
"reason": str, "nodes": [str, ...], "detail": Any }
|
||
"""
|
||
commands: list[dict] = []
|
||
|
||
nodes_list = graph.get("nodes", [])
|
||
node_lookup = {n["id"]: n for n in nodes_list}
|
||
|
||
# Build centrality map keyed by node ID for ID-based delta
|
||
cent_map: dict[str, float] = dict(zip(node_ids, map(float, centrality)))
|
||
prev_map: dict[str, float] = prev_centrality or {}
|
||
|
||
# --- PRUNE: nodes with very low centrality, modulated by Greek state ---
|
||
for i, nid in enumerate(node_ids):
|
||
node = node_lookup.get(nid, {})
|
||
gs = (greek_map or {}).get(nid, {}).get("greek") or node.get("greek_state", "Ζ")
|
||
protect = GREEK_PROTECT.get(gs, 0)
|
||
# Scale threshold: protected states need much lower centrality to be pruned
|
||
effective_threshold = prune_threshold * (1.0 / (1.0 + protect * 2))
|
||
if centrality[i] < effective_threshold:
|
||
commands.append({
|
||
"action": "prune",
|
||
"reason": (
|
||
f"centrality {centrality[i]:.4f} < effective threshold "
|
||
f"{effective_threshold:.4f} (greek={gs}, protect={protect})"
|
||
),
|
||
"nodes": [nid],
|
||
"detail": {
|
||
"label": node.get("label", nid),
|
||
"kernel": node.get("kernel", ""),
|
||
"centrality": float(centrality[i]),
|
||
"greek_state": gs,
|
||
"protect_level": protect,
|
||
},
|
||
})
|
||
|
||
# --- PROMOTE: nodes with rising centrality that are potential hubs ---
|
||
if prev_map:
|
||
for nid, cent in cent_map.items():
|
||
prev = prev_map.get(nid)
|
||
if prev is not None:
|
||
delta = cent - prev
|
||
if delta > promote_threshold:
|
||
node = node_lookup.get(nid, {})
|
||
commands.append({
|
||
"action": "promote",
|
||
"reason": f"centrality delta {delta:.4f} > threshold {promote_threshold}",
|
||
"nodes": [nid],
|
||
"detail": {
|
||
"label": node.get("label", nid),
|
||
"kernel": node.get("kernel", ""),
|
||
"centrality": cent,
|
||
"delta": round(delta, 6),
|
||
},
|
||
})
|
||
|
||
# --- MERGE: pairs with edge + similar centrality, modulated by Greek compatibility ---
|
||
adj, _ = graph_to_adjacency(graph)
|
||
merge_candidates: list[tuple[float, int, int]] = []
|
||
for i in range(len(node_ids)):
|
||
for j in range(i + 1, len(node_ids)):
|
||
if adj[i, j] > 0:
|
||
diff = abs(centrality[i] - centrality[j])
|
||
if diff < merge_similarity:
|
||
priority = centrality[i] * centrality[j]
|
||
# Boost priority for compatible Greek states
|
||
if greek_map is not None:
|
||
ni = node_lookup.get(node_ids[i], {})
|
||
nj = node_lookup.get(node_ids[j], {})
|
||
gs_i = (greek_map.get(node_ids[i], {}).get("greek")
|
||
or ni.get("greek_state", "Ζ"))
|
||
gs_j = (greek_map.get(node_ids[j], {}).get("greek")
|
||
or nj.get("greek_state", "Ζ"))
|
||
if gs_i in GREEK_COMPATIBLE.get(gs_j, []):
|
||
priority *= 1.5 # boost compatible merges
|
||
elif gs_i == gs_j:
|
||
priority *= 1.3 # same-state merges are safe
|
||
else:
|
||
priority *= 0.5 # incompatible states penalized
|
||
merge_candidates.append((priority, i, j))
|
||
# Rank by boosted centrality product and cap
|
||
merge_candidates.sort(reverse=True, key=lambda x: x[0])
|
||
for priority, i, j in merge_candidates[:max_merges]:
|
||
ni = node_lookup.get(node_ids[i], {})
|
||
nj = node_lookup.get(node_ids[j], {})
|
||
gs_i = ni.get("greek_state", "Ζ")
|
||
gs_j = nj.get("greek_state", "Ζ")
|
||
commands.append({
|
||
"action": "merge",
|
||
"reason": (
|
||
f"centrality prod {priority:.6f}, diff "
|
||
f"{abs(centrality[i] - centrality[j]):.4f}, "
|
||
f"greek={gs_i}×{gs_j}"
|
||
),
|
||
"nodes": [node_ids[i], node_ids[j]],
|
||
"detail": {
|
||
"labels": [ni.get("label", node_ids[i]),
|
||
nj.get("label", node_ids[j])],
|
||
"centralities": [float(centrality[i]),
|
||
float(centrality[j])],
|
||
"greek_states": [gs_i, gs_j],
|
||
"compatible": gs_i in GREEK_COMPATIBLE.get(gs_j, []),
|
||
},
|
||
})
|
||
|
||
return commands
|
||
|
||
|
||
# =========================================================================
|
||
# III. Refactoring Engine
|
||
# =========================================================================
|
||
|
||
|
||
def apply_refactoring(
|
||
graph: dict,
|
||
commands: list[dict],
|
||
dry_run: bool = True,
|
||
) -> dict:
|
||
"""Apply refactoring commands to produce a new graph.
|
||
|
||
In dry_run mode, only reports what would change.
|
||
In live mode, actually modifies the graph.
|
||
|
||
Returns the (possibly modified) graph and operation log.
|
||
"""
|
||
if dry_run:
|
||
return graph, commands # just report
|
||
|
||
new_graph = copy.deepcopy(graph)
|
||
nodes = new_graph.get("nodes", [])
|
||
edges = new_graph.get("edges", [])
|
||
removed_ids: set[str] = set()
|
||
operation_log: list[dict] = []
|
||
|
||
for cmd in commands:
|
||
action = cmd["action"]
|
||
|
||
if action == "prune":
|
||
for nid in cmd["nodes"]:
|
||
# Remove from node list
|
||
nodes[:] = [n for n in nodes if n["id"] != nid]
|
||
removed_ids.add(nid)
|
||
operation_log.append({
|
||
"action": "pruned",
|
||
"node_id": nid,
|
||
"reason": cmd["reason"],
|
||
})
|
||
|
||
# Remove edges incident to pruned nodes
|
||
edges[:] = [
|
||
e for e in edges
|
||
if e["source"] not in removed_ids
|
||
and e["target"] not in removed_ids
|
||
]
|
||
|
||
elif action == "merge":
|
||
# Keep the first node, remove the second
|
||
keep_id, remove_id = cmd["nodes"]
|
||
nodes[:] = [n for n in nodes if n["id"] != remove_id]
|
||
removed_ids.add(remove_id)
|
||
operation_log.append({
|
||
"action": "merged",
|
||
"kept": keep_id,
|
||
"removed": remove_id,
|
||
"reason": cmd["reason"],
|
||
})
|
||
|
||
# Reroute edges from removed to kept
|
||
for e in edges:
|
||
if e["source"] == remove_id:
|
||
e["source"] = keep_id
|
||
if e["target"] == remove_id:
|
||
e["target"] = keep_id
|
||
|
||
# Remove self-loops from reroute
|
||
edges[:] = [e for e in edges if e["source"] != e["target"]]
|
||
|
||
elif action == "promote":
|
||
for nid in cmd["nodes"]:
|
||
# Find the node
|
||
node = next((n for n in nodes if n["id"] == nid), None)
|
||
if node:
|
||
# Add a specialization suffix
|
||
new_id = f"{nid}__special"
|
||
new_node = copy.deepcopy(node)
|
||
new_node["id"] = new_id
|
||
new_node["label"] = node.get("label", "") + " (specialized)"
|
||
new_node["dimension"] = min(5, node.get("dimension", 0) + 1)
|
||
nodes.append(new_node)
|
||
operation_log.append({
|
||
"action": "promoted",
|
||
"original": nid,
|
||
"created": new_id,
|
||
"reason": cmd["reason"],
|
||
})
|
||
# Connect the new node back to the original
|
||
edges.append({
|
||
"source": nid,
|
||
"target": new_id,
|
||
"type": "specialization",
|
||
"promoted": True,
|
||
})
|
||
|
||
elif action == "split":
|
||
for nid in cmd["nodes"]:
|
||
node = next((n for n in nodes if n["id"] == nid), None)
|
||
if node:
|
||
split_a = f"{nid}__a"
|
||
split_b = f"{nid}__b"
|
||
for new_id in (split_a, split_b):
|
||
new_node = copy.deepcopy(node)
|
||
new_node["id"] = new_id
|
||
new_node["label"] = node.get("label", "") + (
|
||
" (A)" if new_id.endswith("__a") else " (B)"
|
||
)
|
||
nodes.append(new_node)
|
||
removed_ids.add(nid)
|
||
nodes[:] = [n for n in nodes if n["id"] != nid]
|
||
operation_log.append({
|
||
"action": "split",
|
||
"original": nid,
|
||
"created": [split_a, split_b],
|
||
"reason": cmd["reason"],
|
||
})
|
||
|
||
# Regenerate stats
|
||
new_edges = [e for e in edges if e["source"] not in removed_ids
|
||
and e["target"] not in removed_ids]
|
||
deduped = []
|
||
seen_pairs = set()
|
||
for e in new_edges:
|
||
pair = tuple(sorted([e["source"], e["target"]]))
|
||
if pair not in seen_pairs:
|
||
seen_pairs.add(pair)
|
||
deduped.append(e)
|
||
new_graph["edges"] = deduped
|
||
new_graph["stats"] = graph_stats(new_graph)
|
||
|
||
return new_graph, operation_log
|
||
|
||
|
||
# =========================================================================
|
||
# IV. Main Loop
|
||
# =========================================================================
|
||
|
||
|
||
def run_refactor_loop(
|
||
graph: dict,
|
||
max_iterations: int = MAX_ITERATIONS,
|
||
dry_run: bool = True,
|
||
*,
|
||
greek_map: Optional[dict[str, dict]] = None,
|
||
prune_threshold: float = PRUNE_CENTRALITY_THRESHOLD,
|
||
promote_threshold: float = PROMOTE_DELTA_THRESHOLD,
|
||
merge_similarity: float = MERGE_EDGE_SIMILARITY,
|
||
max_merges: int = MAX_MERGES_PER_ITER,
|
||
origin: str = "file",
|
||
) -> dict:
|
||
"""Run the chaos-game-driven refactoring loop.
|
||
|
||
Each iteration:
|
||
1. Build adjacency from current graph
|
||
2. Compute centrality via BurgersChaosGame
|
||
3. Compare with previous centrality
|
||
4. Generate commands
|
||
5. Apply commands
|
||
6. Check convergence: if no commands generated, stop
|
||
|
||
Returns full history as a receipt dict.
|
||
"""
|
||
iteration_history: list[dict] = []
|
||
current_graph = copy.deepcopy(graph)
|
||
prev_cent_map: dict[str, float] = {}
|
||
|
||
for iteration in range(max_iterations):
|
||
t0 = time.time()
|
||
|
||
# 1. Build adjacency
|
||
A, node_ids = graph_to_adjacency(current_graph)
|
||
n_nodes = A.shape[0]
|
||
|
||
if n_nodes == 0:
|
||
iteration_history.append({
|
||
"iteration": iteration,
|
||
"status": "empty_graph",
|
||
"runtime_s": round(time.time() - t0, 4),
|
||
})
|
||
break
|
||
|
||
# 2. Quantum walk centrality
|
||
game = BurgersChaosGame(A, node_ids)
|
||
centrality = game.eigenvector_centrality()
|
||
|
||
# Top 5 by centrality
|
||
ranked = game.rank_nodes(centrality)
|
||
top_5 = [(n, round(s, 6)) for n, s in ranked[:5]]
|
||
|
||
# 3. Generate commands
|
||
commands = generate_refactoring_commands(
|
||
current_graph, centrality, node_ids, prev_cent_map,
|
||
greek_map=greek_map,
|
||
prune_threshold=prune_threshold,
|
||
promote_threshold=promote_threshold,
|
||
merge_similarity=merge_similarity,
|
||
max_merges=max_merges,
|
||
)
|
||
|
||
n_prune = sum(1 for c in commands if c["action"] == "prune")
|
||
n_promote = sum(1 for c in commands if c["action"] == "promote")
|
||
n_merge = sum(1 for c in commands if c["action"] == "merge")
|
||
n_split = sum(1 for c in commands if c["action"] == "split")
|
||
|
||
iter_record = {
|
||
"iteration": iteration,
|
||
"runtime_s": round(time.time() - t0, 4),
|
||
"node_count": n_nodes,
|
||
"edge_count": len(current_graph.get("edges", [])),
|
||
"top_5_centrality": top_5,
|
||
"top_node": top_5[0] if top_5 else None,
|
||
"commands_generated": len(commands),
|
||
"command_breakdown": {
|
||
"prune": n_prune,
|
||
"promote": n_promote,
|
||
"merge": n_merge,
|
||
"split": n_split,
|
||
},
|
||
"preview_commands": [
|
||
{
|
||
"action": c["action"],
|
||
"nodes_preview": c["nodes"][:3],
|
||
"reason": c["reason"][:120],
|
||
}
|
||
for c in commands[:10] # first 10 for readability
|
||
],
|
||
}
|
||
|
||
iteration_history.append(iter_record)
|
||
|
||
# 4. Apply (or preview)
|
||
if not dry_run and commands:
|
||
new_graph, op_log = apply_refactoring(
|
||
current_graph, commands, dry_run=False,
|
||
)
|
||
current_graph = new_graph
|
||
iter_record["applied"] = len(op_log) if op_log else 0
|
||
|
||
# 5. Save current centrality for next iteration's delta
|
||
prev_cent_map = dict(zip(node_ids, map(float, centrality)))
|
||
|
||
# 6. Convergence
|
||
if not commands:
|
||
break
|
||
|
||
return {
|
||
"schema": "rrc_refactor_oracle_v1",
|
||
"claim_boundary": (
|
||
"chaos-game-quantum-walk-refactoring-oracle;"
|
||
"greek-epigenetic-enrichment"
|
||
),
|
||
"sacrificial_graph_source": "domain_manifold_graph_v1.json",
|
||
"graph_origin": origin,
|
||
"greek_spectrum": greek_spectrum(current_graph),
|
||
"parameters": {
|
||
"prune_centrality_threshold": PRUNE_CENTRALITY_THRESHOLD,
|
||
"promote_delta_threshold": PROMOTE_DELTA_THRESHOLD,
|
||
"merge_edge_similarity": MERGE_EDGE_SIMILARITY,
|
||
"max_iterations": max_iterations,
|
||
"max_merges_per_iter": max_merges,
|
||
"dry_run": dry_run,
|
||
},
|
||
"initial_stats": graph_stats(graph),
|
||
"iteration_history": iteration_history,
|
||
"final_stats": graph_stats(current_graph),
|
||
"summary": {
|
||
"total_iterations": len(iteration_history),
|
||
"total_commands": sum(
|
||
h["commands_generated"] for h in iteration_history
|
||
) if iteration_history else 0,
|
||
"converged": not iteration_history[-1]["commands_generated"]
|
||
if iteration_history else False,
|
||
"top_node_final": iteration_history[-1]["top_node"]
|
||
if iteration_history else None,
|
||
},
|
||
"final_graph": current_graph,
|
||
"computed_at": datetime.now(timezone.utc).isoformat(),
|
||
}
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser(
|
||
description="RRC Chaos-Game Refactoring Oracle"
|
||
)
|
||
parser.add_argument(
|
||
"--graph", "-g",
|
||
default="/tmp/rrc_sacrificial_manifold_v1.json",
|
||
help="Path to manifold graph JSON (default: sacrificial copy)",
|
||
)
|
||
parser.add_argument(
|
||
"--max-iterations", type=int, default=MAX_ITERATIONS,
|
||
)
|
||
parser.add_argument(
|
||
"--max-merges", type=int, default=MAX_MERGES_PER_ITER,
|
||
help="Max merges per iteration (default: %(default)s)",
|
||
)
|
||
parser.add_argument(
|
||
"--apply", action="store_true",
|
||
help="Actually apply refactoring (default: dry-run preview)",
|
||
)
|
||
parser.add_argument(
|
||
"--output", "-o", default=None,
|
||
help="Output receipt path",
|
||
)
|
||
parser.add_argument(
|
||
"--threshold-prune", type=float, default=PRUNE_CENTRALITY_THRESHOLD,
|
||
)
|
||
parser.add_argument(
|
||
"--threshold-promote", type=float, default=PROMOTE_DELTA_THRESHOLD,
|
||
)
|
||
parser.add_argument(
|
||
"--threshold-merge", type=float, default=MERGE_EDGE_SIMILARITY,
|
||
)
|
||
parser.add_argument(
|
||
"--no-live", action="store_true",
|
||
help="Skip live arxiv rebuild; force intrinsic fallback",
|
||
)
|
||
parser.add_argument(
|
||
"--save-graph", default=None,
|
||
help="Save final modified graph to this path (auto: input path if --apply)",
|
||
)
|
||
parser.add_argument(
|
||
"--greek-assignment", default=None,
|
||
help="Path to manifold assignment JSON with Greek states (default: shared-data auto-detect)",
|
||
)
|
||
|
||
args = parser.parse_args()
|
||
|
||
graph_path = Path(args.graph)
|
||
if not graph_path.exists():
|
||
print(f"ERROR: graph not found at {graph_path}", file=sys.stderr)
|
||
print("Run: cp <repo>/shared-data/data/domain_manifold_graph_v1.json "
|
||
"/tmp/rrc_sacrificial_manifold_v1.json", file=sys.stderr)
|
||
return 1
|
||
|
||
graph, origin = build_or_load_graph(graph_path, skip_live=args.no_live)
|
||
|
||
# Load Greek epigenetic states and enrich graph nodes
|
||
greek_path = Path(args.greek_assignment) if args.greek_assignment else None
|
||
greek_map = load_greek_assignment(greek_path)
|
||
if greek_map:
|
||
graph = enrich_graph_with_greek(graph, greek_map)
|
||
n_enriched = sum(1 for n in graph.get("nodes", [])
|
||
if n.get("greek_match_score", 0) >= 5)
|
||
else:
|
||
# Keyword-only enrichment when no assignment file is available
|
||
graph = enrich_graph_by_keywords(graph)
|
||
n_enriched = sum(1 for n in graph.get("nodes", [])
|
||
if n.get("greek_match_score", 0) >= 1)
|
||
greek_spec = greek_spectrum(graph)
|
||
n_high = greek_spec["match_quality"]["high"]
|
||
n_med = greek_spec["match_quality"]["medium"]
|
||
n_low = greek_spec["match_quality"]["low"]
|
||
n_none = greek_spec["match_quality"]["none"]
|
||
print(f" Greek enrichment: {n_enriched}/{len(graph.get('nodes', []))} "
|
||
f"nodes annotated (high={n_high}, med={n_med}, low={n_low}, none={n_none})",
|
||
file=sys.stderr)
|
||
print(f" Greek spectrum: {dict(greek_spec['by_greek'])}",
|
||
file=sys.stderr)
|
||
|
||
stats = graph_stats(graph)
|
||
|
||
print(f"RRC Refactoring Oracle — on sacrificial copy")
|
||
print(f" Origin: {origin}")
|
||
print(f" Initial: {stats['num_nodes']} nodes, {stats['num_edges']} edges, "
|
||
f"density {stats['density']:.6f}")
|
||
print(f" Dry run: {not args.apply}")
|
||
print(f" Max iterations: {args.max_iterations}")
|
||
print(f" Max merges/iter: {args.max_merges}")
|
||
print(f" Thresholds: prune={args.threshold_prune}, "
|
||
f"promote={args.threshold_promote}, "
|
||
f"merge={args.threshold_merge}")
|
||
if origin == "inferred":
|
||
print(f" Inferred edge count: "
|
||
f"{stats.get('edge_type_counts', {}).get('kernel_internal', 0)} "
|
||
f"kernel_internal, "
|
||
f"{stats.get('edge_type_counts', {}).get('shared_paper', 0)} "
|
||
f"shared_paper, "
|
||
f"{stats.get('edge_type_counts', {}).get('kernel_hub', 0)} "
|
||
f"kernel_hub, "
|
||
f"{stats.get('edge_type_counts', {}).get('dimension_chain', 0)} "
|
||
f"dimension_chain, "
|
||
f"{stats.get('edge_type_counts', {}).get('topic_overlap', 0)} "
|
||
f"topic_overlap")
|
||
print()
|
||
|
||
result = run_refactor_loop(
|
||
graph,
|
||
max_iterations=args.max_iterations,
|
||
dry_run=not args.apply,
|
||
greek_map=greek_map,
|
||
prune_threshold=args.threshold_prune,
|
||
promote_threshold=args.threshold_promote,
|
||
merge_similarity=args.threshold_merge,
|
||
max_merges=args.max_merges,
|
||
origin=origin,
|
||
)
|
||
|
||
# Print results
|
||
summary = result["summary"]
|
||
initial = result["initial_stats"]
|
||
final = result["final_stats"]
|
||
|
||
print(f"Refactoring Complete — {summary['total_iterations']} iterations")
|
||
print(f" Commands generated: {summary['total_commands']}")
|
||
print(f" Converged: {summary['converged']}")
|
||
print(f" Nodes: {initial['num_nodes']} → {final['num_nodes']}")
|
||
print(f" Edges: {initial['num_edges']} → {final['num_edges']}")
|
||
|
||
for h in result["iteration_history"]:
|
||
cmds = h["command_breakdown"]
|
||
top = h["top_node"]
|
||
print(
|
||
f" Iter {h['iteration']}: "
|
||
f"{h['node_count']} nodes, "
|
||
f"top={top[0] if top else '?'} ({top[1] if top else '?'}), "
|
||
f"commands: P{cmds['prune']}+M{cmds['merge']}"
|
||
f"+R{cmds['promote']}+S{cmds['split']}"
|
||
f" ({h['commands_generated']} total)"
|
||
)
|
||
|
||
# Save final graph if requested
|
||
if args.apply and args.save_graph is None:
|
||
args.save_graph = str(graph_path) # overwrite in-place by default
|
||
if args.save_graph and result.get("final_graph"):
|
||
save_path = Path(args.save_graph)
|
||
final_graph = result["final_graph"]
|
||
final_graph["stats"] = final_graph_stats = graph_stats(final_graph)
|
||
save_path.write_text(json.dumps(final_graph, indent=2, default=str))
|
||
print(f" Saved refactored graph to {save_path}")
|
||
# Update summary with actual final graph stats
|
||
result["final_stats"] = final_graph_stats
|
||
|
||
# Emit receipt
|
||
if args.output:
|
||
output_path = Path(args.output)
|
||
else:
|
||
output_path = SHIM / "rrc_refactor_oracle_receipt.json"
|
||
|
||
receipt = {k: v for k, v in result.items() if k != "final_graph"}
|
||
canonical = json.dumps(receipt, sort_keys=True, separators=(",", ":"))
|
||
receipt["receipt_sha256"] = hashlib.sha256(canonical.encode()).hexdigest()
|
||
output_path.write_text(json.dumps(receipt, indent=2, default=str))
|
||
|
||
print(f"\nReceipt: {output_path}")
|
||
print(f"SHA256: {receipt['receipt_sha256']}")
|
||
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|