caedf2e6f4
- extract_section.py: slice a book section out of a pdftotext -layout dump and list its labeled Theorem/Proposition/Lemma/Corollary/Definition items. - formalize.py: LLM-formalize a section into Lean 4 candidate statements (preferring Mathlib vocabulary) plus a definitions manifest. - check_statements.py: elaboration-only sanity check against a real Mathlib-backed Lean project (must run outside the Cowork sandbox, which has no Lean toolchain and can't run macOS binaries anyway). - build_library.py: assemble solved lean-bench results into a reusable .lean file, reusing each winning proof verbatim. - llm.py: standalone OpenAI-compatible chat client + .env loader, kept independent of lean_agent so this pipeline works without it installed. - Source PDFs and raw/layout text dumps are gitignored (copyrighted material); only code and the final short Lean statement datasets are tracked.
74 lines
3.1 KiB
Python
74 lines
3.1 KiB
Python
"""Slice one book section out of a `pdftotext -layout` dump and list its labeled
|
|
Theorem/Proposition/Lemma/Corollary/Definition statements, as input for formalize.py.
|
|
|
|
Produces two files: the section's raw text, and a JSON checklist of labeled items
|
|
(kind, label, first line) in book order. The checklist exists so formalize.py's LLM
|
|
step has an explicit inventory to cover and cannot silently skip or invent items.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
LABEL_RE = re.compile(r'^(Definition|Theorem|Proposition|Lemma|Corollary)\s+(\d+\.\d+(?:\.\d+)?)\.?\s*(.*)$')
|
|
|
|
|
|
def find_heading_line(lines, heading):
|
|
for i, line in enumerate(lines):
|
|
if line.strip() == heading:
|
|
return i
|
|
raise ValueError(f'Heading not found: {heading!r}')
|
|
|
|
|
|
def extract_section(full_text, start_heading, end_heading):
|
|
lines = full_text.split('\n')
|
|
start = find_heading_line(lines, start_heading)
|
|
end = find_heading_line(lines, end_heading) if end_heading else len(lines)
|
|
if end <= start:
|
|
raise ValueError('end_heading must come after start_heading')
|
|
return '\n'.join(lines[start:end])
|
|
|
|
|
|
def labeled_statements(section_text, section_prefix):
|
|
"""Unique labeled items in section_text whose label starts with section_prefix
|
|
(e.g. '1.6.'), keeping only the first occurrence of each label: later matches are
|
|
almost always proof continuations across a page break or cross-references, not new
|
|
statements. Returned sorted in book order (numeric, not lexical)."""
|
|
seen = {}
|
|
for line in section_text.split('\n'):
|
|
line = line.strip()
|
|
m = LABEL_RE.match(line)
|
|
if not m:
|
|
continue
|
|
kind, label, rest = m.group(1), m.group(2), m.group(3)
|
|
if not label.startswith(section_prefix) or label in seen:
|
|
continue
|
|
seen[label] = {"kind": kind, "label": label, "first_line": line[:120]}
|
|
|
|
def sort_key(item):
|
|
return tuple(int(p) for p in item['label'].split('.'))
|
|
|
|
return sorted(seen.values(), key=sort_key)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Slice one section out of a pdftotext -layout dump')
|
|
parser.add_argument('layout_txt', type=Path)
|
|
parser.add_argument('--start-heading', required=True, help="e.g. '1.6. Divisibility in the Integers'")
|
|
parser.add_argument('--end-heading', help="e.g. '1.7. Modular Arithmetic'; omit for end of file")
|
|
parser.add_argument('--section-prefix', required=True, help="e.g. '1.6.'")
|
|
parser.add_argument('--out-text', type=Path, required=True)
|
|
parser.add_argument('--out-index', type=Path, required=True)
|
|
args = parser.parse_args()
|
|
|
|
full_text = args.layout_txt.read_text(encoding='utf-8')
|
|
section_text = extract_section(full_text, args.start_heading, args.end_heading)
|
|
args.out_text.write_text(section_text, encoding='utf-8')
|
|
index = labeled_statements(section_text, args.section_prefix)
|
|
args.out_index.write_text(json.dumps(index, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
print(f'{len(index)} labeled statements -> {args.out_index}')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|