mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
- Update .gitignore with **/target/ for Rust build artifacts - Add eval receipts to UniversalBridge.lean (compile-time verification comments) - Add PCIe Idle-Cycle Compute Harvester to ROADMAP.md - Clean up deprecated scripts, generated Verilog, and old tools (23 deletions) - Stage new infrastructure: Xen/Alpine embedded surface, QFOX topology manager - Stage new probes: boundary activation field, holographic carving - Stage new applications: finance manager, script roots - Stage new research spec: PCIe idle-cycle substrate
268 lines
8.5 KiB
Python
268 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
BFS-Prover-V2 integration for Research Stack.
|
|
Calls Ollama-local BFS-Prover-V2-32B to generate Lean 4 proofs for sorry theorems.
|
|
|
|
Usage:
|
|
python scripts/bf4prover.py <lean_file> [--model MODEL] [--dry-run]
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
import time
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "-q"])
|
|
import requests
|
|
|
|
OLLAMA_URL = "http://localhost:11434/api/generate"
|
|
DEFAULT_MODEL = "zeyu-zheng/BFS-Prover-V2-7B:q8_0"
|
|
|
|
|
|
def extract_context(lean_path: Path) -> str:
|
|
"""Extract imports and key definitions from the top of a Lean file."""
|
|
with open(lean_path, "r", encoding="utf-8") as f:
|
|
lines = f.readlines()
|
|
|
|
# Grab imports and namespace declarations
|
|
context = []
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
if stripped.startswith("import ") or stripped.startswith("open "):
|
|
context.append(stripped)
|
|
elif stripped.startswith("namespace ") or stripped.startswith("structure "):
|
|
context.append(stripped)
|
|
elif stripped.startswith("def ") or stripped.startswith("theorem "):
|
|
# Stop at first theorem/def unless it's a small helper
|
|
break
|
|
return "\n".join(context)
|
|
|
|
|
|
def extract_sorry_blocks(lean_path: Path):
|
|
"""
|
|
Extract theorem statements whose proof body contains `sorry`.
|
|
Returns list of dicts: {name, statement_text, start_line, end_line}
|
|
"""
|
|
with open(lean_path, "r", encoding="utf-8") as f:
|
|
lines = f.readlines()
|
|
|
|
blocks = []
|
|
i = 0
|
|
while i < len(lines):
|
|
# Look for theorem/lemma/def lines
|
|
m = re.match(r"^\s*(theorem|lemma)\s+(\w+)", lines[i])
|
|
if m:
|
|
name = m.group(2)
|
|
start = i
|
|
# Find the end of this declaration (next theorem/lemma/def/end/section at same or lower indent)
|
|
j = i + 1
|
|
while j < len(lines):
|
|
if re.match(r"^\s*(theorem|lemma|def|instance|#|end\b|section\b)", lines[j]):
|
|
break
|
|
j += 1
|
|
body = "".join(lines[start:j])
|
|
if "sorry" in body and "TODO(lean-port)" in body:
|
|
blocks.append({
|
|
"name": name,
|
|
"statement_text": body,
|
|
"start_line": start + 1, # 1-indexed
|
|
"end_line": j, # 1-indexed exclusive
|
|
})
|
|
i = j
|
|
else:
|
|
i += 1
|
|
return blocks
|
|
|
|
|
|
def build_prompt(context: str, theorem_stmt: str) -> str:
|
|
"""Build a prompt for BFS-Prover-V2."""
|
|
# Strip out the old proof attempt / comments / sorry for a clean theorem statement
|
|
clean_stmt = re.sub(r"by\s+--.*TODO\(lean-port\).*sorry", "by", theorem_stmt, flags=re.DOTALL)
|
|
clean_stmt = re.sub(r"by\s+sorry", "by", clean_stmt, flags=re.DOTALL)
|
|
clean_stmt = re.sub(r"/\-.*?\-", "", clean_stmt, flags=re.DOTALL)
|
|
clean_stmt = textwrap.dedent(clean_stmt).strip()
|
|
|
|
prompt = f"""You are BFS-Prover-V2, a state-of-the-art Lean 4 theorem prover.
|
|
Complete the proof of the following theorem using only standard Lean 4 tactics.
|
|
Do not use `sorry`. Output ONLY the tactic block (starting with `by` or the first tactic).
|
|
|
|
### Context
|
|
```lean
|
|
{context}
|
|
```
|
|
|
|
### Theorem
|
|
```lean
|
|
{clean_stmt}
|
|
```
|
|
|
|
### Proof
|
|
"""
|
|
return prompt
|
|
|
|
|
|
def call_ollama(prompt: str, model: str, timeout: int = 180) -> str:
|
|
"""Call Ollama generate API."""
|
|
payload = {
|
|
"model": model,
|
|
"prompt": prompt,
|
|
"stream": False,
|
|
"options": {
|
|
"temperature": 0.1,
|
|
"num_predict": 512,
|
|
},
|
|
}
|
|
resp = requests.post(OLLAMA_URL, json=payload, timeout=timeout)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
return data.get("response", "").strip()
|
|
|
|
|
|
def extract_tactics(raw: str) -> str:
|
|
"""Extract the tactic block from model output."""
|
|
# If wrapped in ```lean ... ```
|
|
m = re.search(r"```(?:lean)?\s*(.*?)```", raw, re.DOTALL)
|
|
if m:
|
|
return m.group(1).strip()
|
|
# If it starts with 'by'
|
|
m = re.search(r"(by\b.*?)(?:\n\n|$)", raw, re.DOTALL)
|
|
if m:
|
|
return m.group(1).strip()
|
|
# Otherwise just return the raw text if it looks like tactics
|
|
raw_stripped = raw.strip()
|
|
if raw_stripped.startswith("by ") or raw_stripped.startswith(" "):
|
|
return raw_stripped
|
|
return raw_stripped
|
|
|
|
|
|
def replace_proof(lean_path: Path, block: dict, tactics: str) -> bool:
|
|
"""Replace the sorry block with the suggested tactics. Return True if written."""
|
|
with open(lean_path, "r", encoding="utf-8") as f:
|
|
lines = f.readlines()
|
|
|
|
start_idx = block["start_line"] - 1
|
|
end_idx = block["end_line"] - 1
|
|
|
|
# Reconstruct: keep the theorem signature line, replace body with new tactics
|
|
sig_lines = []
|
|
for k in range(start_idx, end_idx):
|
|
line = lines[k]
|
|
if line.strip().startswith("-- TODO") or line.strip() == "sorry" or "sorry" in line:
|
|
break
|
|
sig_lines.append(line)
|
|
|
|
# Build replacement
|
|
indent = " "
|
|
new_body = f"{indent}-- bf4prover-generated proof\n"
|
|
for tactic_line in tactics.splitlines():
|
|
new_body += f"{indent}{tactic_line}\n"
|
|
|
|
new_lines = lines[:start_idx] + sig_lines + [new_body] + lines[end_idx:]
|
|
backup = lean_path.with_suffix(".lean.bak")
|
|
lean_path.rename(backup)
|
|
with open(lean_path, "w", encoding="utf-8") as f:
|
|
f.writelines(new_lines)
|
|
return True
|
|
|
|
|
|
def try_compile(lean_path: Path, package_dir: Path) -> tuple[bool, str]:
|
|
"""Run lake build for the specific module. Returns (success, output)."""
|
|
rel = lean_path.relative_to(package_dir)
|
|
mod_name = str(rel.with_suffix("")).replace("/", ".")
|
|
proc = subprocess.run(
|
|
["lake", "build", f"+{mod_name}"],
|
|
cwd=package_dir,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=120,
|
|
)
|
|
success = proc.returncode == 0
|
|
return success, proc.stdout + proc.stderr
|
|
|
|
|
|
def restore_backup(lean_path: Path):
|
|
backup = lean_path.with_suffix(".lean.bak")
|
|
if backup.exists():
|
|
backup.rename(lean_path)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="BFS-Prover-V2 sorry fixer")
|
|
parser.add_argument("lean_file", help="Path to the .lean file to repair")
|
|
parser.add_argument("--model", default=DEFAULT_MODEL, help="Ollama model tag")
|
|
parser.add_argument("--dry-run", action="store_true", help="Print prompts without calling Ollama")
|
|
parser.add_argument("--package-dir", default="0-Core-Formalism/lean/Semantics", help="Lake package root")
|
|
args = parser.parse_args()
|
|
|
|
lean_path = Path(args.lean_file).resolve()
|
|
package_dir = Path(args.package_dir).resolve()
|
|
|
|
if not lean_path.exists():
|
|
print(f"ERROR: File not found: {lean_path}")
|
|
sys.exit(1)
|
|
|
|
context = extract_context(lean_path)
|
|
blocks = extract_sorry_blocks(lean_path)
|
|
|
|
if not blocks:
|
|
print(f"No TODO(lean-port) sorry blocks found in {lean_path}")
|
|
sys.exit(0)
|
|
|
|
print(f"Found {len(blocks)} sorry block(s) in {lean_path.name}")
|
|
|
|
for block in blocks:
|
|
name = block["name"]
|
|
print(f"\n=== {name} (lines {block['start_line']}-{block['end_line']}) ===")
|
|
|
|
prompt = build_prompt(context, block["statement_text"])
|
|
if args.dry_run:
|
|
print("\n--- PROMPT ---")
|
|
print(prompt)
|
|
print("--- END PROMPT ---")
|
|
continue
|
|
|
|
print("Calling Ollama BFS-Prover-V2...")
|
|
try:
|
|
raw = call_ollama(prompt, args.model)
|
|
except requests.exceptions.ConnectionError:
|
|
print("ERROR: Cannot connect to Ollama. Is it running? (ollama serve)")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"ERROR calling Ollama: {e}")
|
|
continue
|
|
|
|
tactics = extract_tactics(raw)
|
|
print(f"Suggested tactics:\n{tactics}")
|
|
|
|
# Apply and test
|
|
ok = replace_proof(lean_path, block, tactics)
|
|
if not ok:
|
|
print("Failed to apply patch.")
|
|
continue
|
|
|
|
success, output = try_compile(lean_path, package_dir)
|
|
if success:
|
|
print(f"✅ {name} COMPILES!")
|
|
# Clean up backup
|
|
backup = lean_path.with_suffix(".lean.bak")
|
|
if backup.exists():
|
|
backup.unlink()
|
|
else:
|
|
print(f"❌ {name} FAILED to compile. Restoring backup.")
|
|
print(output[-800:]) # tail of output
|
|
restore_backup(lean_path)
|
|
|
|
time.sleep(1) # Be nice to Ollama
|
|
|
|
print("\nDone.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|