Research-Stack/4-Infrastructure/shim/shape_index.py
allaun 475f6319ea chore(repo): push local 768-commit branch state onto clean remote baseline
This squashes all local history (768 commits) onto the scrubbed PR #90
baseline. Individual commits were lost during filter-repo corruption;
the working tree content is preserved intact.

Build: N/A (working tree state only)
2026-06-15 22:46:50 -05:00

331 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Shape-index mapping for the Lean corpus.
For each theorem, compute its Sidon sumset signature and FAMM shape class,
then index by shape for nearest-neighbor proof template search.
Usage:
python3 shape_index.py --corpus /path/to/lean_corpus --out index.json
Output:
index.json — shape-indexed theorem database
shapes/ — per-file FAMM classification receipts
"""
import argparse
import hashlib
import json
import os
import re
import sys
import time
from collections import defaultdict, Counter
from pathlib import Path
from datetime import datetime, timezone
Q16_SCALE = 65536
def q16(f):
return max(-2147483648, min(2147483647, int(f * Q16_SCALE)))
# ─── Sidon signature from theorem structure ───────────────────────────────────
def classify_lean_file(content: str, path: str) -> list[dict]:
"""
Extract theorems/defs from a .lean file and compute shape signatures.
"""
results = []
lines = content.split("\n")
# Find theorem/lemma/def lines and estimate constraint graph
for i, line in enumerate(lines):
stripped = line.strip()
# Match theorem/lemma/def signatures
m = re.match(
r"(theorem|lemma|def)\s+(\w+)\s*(.*?)(?::=|:=)",
stripped,
)
if not m:
continue
kind = m.group(1)
name = m.group(2)
sig = m.group(3)[:120]
# Estimate constraint graph from the signature
# Count variables, types, and arrows — these define the Sidon graph size
n_vars = len(re.findall(r"\b([a-zα-ω]\w*)\s*:", sig))
n_types = len(re.findall(r"(|||||Nat|Int|Rat|Real|Complex|Q16_16|Matrix|Fin\s+\d+)", sig))
n_arrows = sig.count("") + sig.count("->")
n_parens = sig.count("(") + sig.count(")")
complexity = len(sig.strip())
# Estimate Sidon graph size from number of variables + types
# (more interacting parts → larger graph)
graph_size = max(2, n_vars + n_types // 2)
# Assign power-of-2 Sidon addresses up to graph_size
sidon_addrs = [1 << i for i in range(min(graph_size, 8))]
n_addrs = len(sidon_addrs)
# Compute pairwise sums (complete graph)
if n_addrs >= 2:
sums = [sidon_addrs[i] + sidon_addrs[j]
for i in range(n_addrs) for j in range(i + 1, n_addrs)]
n_pairs = len(sums)
unique_sums = len(set(sums))
sumset_density = unique_sums / max(1, n_pairs)
else:
n_pairs = 0
unique_sums = 0
sumset_density = 1.0
# Estimate closure fraction from structural features
# The more variables and arrows, the lower the closure (more constraint collisions)
raw_closure = 1.0 / max(1.0, n_vars + 0.5 * n_arrows + 0.3 * complexity / 40)
closure_frac = min(raw_closure, 1.0)
scar_pressure = 1.0 - closure_frac
# Compute noise estimate: the fraction of the signature that's
# unmapped structural overhead (parentheses, type annotations)
noise_ratio = min(1.0, n_parens / max(1, complexity * 2))
# Spectral radius (in Q16_16 units, normalized to [0, 4])
spectral_raw = q16(closure_frac * 4.0)
# RGB color gate (from PIST/Classify.lean)
signal_threshold = q16(2.0)
max_raw = q16(4.0)
r = Q16_SCALE if spectral_raw >= max_raw else 0
if spectral_raw >= signal_threshold:
g = (spectral_raw - signal_threshold) * Q16_SCALE // (max_raw - signal_threshold)
else:
g = 0
b = spectral_raw * Q16_SCALE // signal_threshold if spectral_raw < signal_threshold else 0
rrc_shape = "CognitiveLoadField" if r > 0 else (
"SignalShapedRouteCompiler" if g > 0 else "NoiseFloor"
)
# FAMM state
if scar_pressure > 0.85:
famm_state = "HOLD"
elif scar_pressure > 0.5:
famm_state = "INSPECT"
else:
famm_state = "ACCEPT"
results.append({
"name": name,
"kind": kind,
"path": path,
"line": i + 1,
"signature": sig,
"constraint_graph": {
"n_vars": n_vars,
"n_types": n_types,
"n_arrows": n_arrows,
"graph_size": n_addrs,
"complexity_chars": complexity,
},
"sidon_signature": {
"addresses": sidon_addrs,
"pairwise_sums": sorted(list(set(sums))) if n_addrs >= 2 else [],
"n_pairs": n_pairs,
"unique_sums": unique_sums,
"sumset_density": round(sumset_density, 4),
"noise_ratio": round(noise_ratio, 4),
},
"famm": {
"closure_fraction": round(closure_frac, 4),
"scar_pressure": round(scar_pressure, 4),
"famm_state": famm_state,
"rrc_shape": rrc_shape,
"spectral_radius_raw": spectral_raw,
"rgb": {"r": r, "g": g, "b": b},
},
})
return results
# ─── Index building ──────────────────────────────────────────────────────────
def build_index(corpus_dir: str, out_path: str, max_files: int = 0):
corpus = Path(corpus_dir)
files = list(corpus.rglob("*.lean"))
if max_files > 0:
files = files[:max_files]
print(f"Scanning {len(files)} Lean files in {corpus_dir}...")
all_theorems = []
shape_index = defaultdict(list) # rrc_shape → [theorem entries]
stats = Counter()
start = time.time()
for fpath in files:
try:
content = fpath.read_text(encoding="utf-8", errors="replace")
except Exception:
continue
theorems = classify_lean_file(content, str(fpath.relative_to(corpus)))
for t in theorems:
shape = t["famm"]["rrc_shape"]
shape_index[shape].append(t)
all_theorems.append(t)
stats["theorems"] += 1
stats["files"] += 1
elapsed = time.time() - start
# Build output
index = {
"meta": {
"corpus_dir": str(corpus),
"files_scanned": stats["files"],
"theorems_indexed": stats["theorems"],
"elapsed_s": round(elapsed, 1),
"theorems_per_sec": round(stats["theorems"] / elapsed, 1) if elapsed > 0 else 0,
"built_at": datetime.now(timezone.utc).isoformat(),
"schema": "shape_index_v1",
},
"shape_summary": {
shape: len(entries)
for shape, entries in sorted(shape_index.items())
},
"by_shape": {
shape: entries for shape, entries in shape_index.items()
},
"all_theorems": all_theorems,
}
with open(out_path, "w") as f:
json.dump(index, f, indent=2)
print(f"\nIndexed {stats['theorems']} theorems from {stats['files']} files")
print(f"Shape distribution: {dict(index['shape_summary'])}")
print(f"Output: {out_path}")
def build_index_multi(corpus_dirs: list, out_path: str, max_files: int = 0):
"""Build index across multiple corpus directories."""
all_theorems = []
shape_index = defaultdict(list)
stats = Counter()
corpus_paths = [Path(c) for c in corpus_dirs]
start = time.time()
for corpus in corpus_paths:
if not corpus.exists():
print(f"Skipping non-existent: {corpus}")
continue
files = list(corpus.rglob("*.lean"))
if max_files > 0:
files = files[:max_files]
print(f" {corpus}: {len(files)} files")
for fpath in files:
try:
content = fpath.read_text(encoding="utf-8", errors="replace")
except Exception:
continue
rel_path = fpath.name # use just filename to avoid path collisions
theorems = classify_lean_file(content, rel_path)
for t in theorems:
shape = t["famm"]["rrc_shape"]
shape_index[shape].append(t)
all_theorems.append(t)
stats["theorems"] += 1
stats["files"] += 1
elapsed = time.time() - start
index = {
"meta": {
"corpus_dirs": [str(c) for c in corpus_paths],
"files_scanned": stats["files"],
"theorems_indexed": stats["theorems"],
"elapsed_s": round(elapsed, 1),
"theorems_per_sec": round(stats["theorems"] / elapsed, 1) if elapsed > 0 else 0,
"built_at": datetime.now(timezone.utc).isoformat(),
"schema": "shape_index_v1",
},
"shape_summary": {
shape: len(entries)
for shape, entries in sorted(shape_index.items())
},
"by_shape": {
shape: entries for shape, entries in shape_index.items()
},
"all_theorems": all_theorems,
}
with open(out_path, "w") as f:
json.dump(index, f, indent=2)
print(f"\nIndexed {stats['theorems']} theorems from {stats['files']} files")
print(f"Shape distribution: {dict(index['shape_summary'])}")
print(f"Output: {out_path}")
# ─── Query: nearest neighbor by shape ────────────────────────────────────────
def nearest_by_shape(unsolved_sig: dict, index_path: str, top_k: int = 3) -> list[dict]:
"""
Given an unsolved problem's Sidon signature, find the nearest neighbors
by sumset density distance.
"""
with open(index_path) as f:
index = json.load(f)
target_density = unsolved_sig.get("sumset_density", 0.5)
target_vars = unsolved_sig.get("n_vars", 1)
target_state = unsolved_sig.get("target_state", "ACCEPT")
scored = []
for t in index["all_theorems"]:
density = t["sidon_signature"]["sumset_density"]
n_vars = t["constraint_graph"]["n_vars"]
famm_state = t["famm"]["famm_state"]
# Distance: density difference + var difference
d_density = abs(density - target_density)
d_vars = abs(n_vars - target_vars) * 0.1
d_state = 0 if famm_state == target_state else 2.0
distance = d_density + d_vars + d_state
scored.append((distance, t))
scored.sort(key=lambda x: x[0])
return [t for d, t in scored[:top_k]]
# ─── CLI ─────────────────────────────────────────────────────────────────────
def main():
ap = argparse.ArgumentParser(description="Shape-index Lean theorems by FAMM class")
ap.add_argument("--corpus", action="append", default=[],
help="Corpus directory (can be repeated)")
ap.add_argument("--out", default="/home/allaun/lean_corpus/shape_index_full.json")
ap.add_argument("--nearest", help="Query nearest neighbors by shape signature JSON")
ap.add_argument("--top-k", type=int, default=3, help="Neighbors to return")
ap.add_argument("--max-files", type=int, default=0, help="Limit files (0 = all)")
args = ap.parse_args()
if args.nearest:
sig = json.loads(args.nearest)
results = nearest_by_shape(sig, args.out, args.top_k)
print(json.dumps(results, indent=2))
return
if not args.corpus:
args.corpus = ["/home/allaun/lean_corpus",
"/home/allaun/Research Stack/0-Core-Formalism"]
build_index_multi(args.corpus, args.out, args.max_files)
if __name__ == "__main__":
main()