mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
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)
331 lines
12 KiB
Python
331 lines
12 KiB
Python
#!/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()
|