04d827b3b3
Restructures the textbook pipeline around the distinction the body-derived dataset had been blurring: a bench should be actual unsolved problems, and a reusable premise library should only ever contain statements that have really been proven, the way Mathlib itself works. - library_candidates/goodman-1.6.jsonl (renamed from datasets/goodman-1.6.jsonl): the book's own Theorem/Proposition/Lemma/Corollary claims from the main text. Not bench targets -- premises waiting to be proven once via a real lean-bench run, then assembled into the reusable library by build_library.py (which already only keeps success:true entries with their checked proof, unchanged by this commit). - datasets/goodman-1.6-exercises.jsonl (new): the actual bench, sourced from the book's Exercises 1.6 block via two new scripts, textbook/extract_exercises.py (slices the Exercises block, lists its numbered items, drops a known false-positive class where a wrapped cross-reference like "...Proposition\n1.6.19." looks like a real exercise heading) and textbook/formalize_exercises.py (LLM step, given both the exercises and body text together since many exercises say "Prove Proposition N.N.N" instead of restating the claim). - Both formalize.py and formalize_exercises.py's validate() now enforce that every checklist item is covered by at least one output entry (included or explicitly excluded with a reason); formalize_exercises.py did not have this yet and a first real run silently dropped 7 of 15 checklist items with no trace before this was added. - All 12 model-produced exercise entries needed real fixes before commit, the same review pass applied to the body pilot: 3 exercises literally ask to prove an already-stated body claim (Exercise 1.6.1 -> Proposition 1.6.7, 1.6.2 -> Proposition 1.6.9, 1.6.7 -> Proposition 1.6.18, 1.6.9 -> Corollary 1.6.20, 1.6.15 -> Lemma 1.6.26) and now reuse that claim's own already-checked statement verbatim instead of an independently re-derived (and in one case strictly weaker) restatement; two entries (1.6.2, 1.6.11) had literal math prose like "I(m, n)" or "gcd(n1,...,nk)" leaked into the statement field, which is not valid Lean; several had untyped/ambiguous gcd or Prime usage fixed the same way the earlier body fixes were (Nat.Prime, IsCoprime, explicit casts, IsGreatest instead of max on a Set); and Exercise 1.6.12 part (a) turned out to be a discussion question whose unconditional-identity reading is actually false (6, 10, 15 are jointly coprime but not pairwise coprime), so it was moved to the excluded file with a reason instead of committed as a false theorem. - Definitions can now be real, compiled Lean, not only JSON metadata: Definition 1.6.22 (gcd of a list of integers, previously a null placeholder since no single Mathlib constant matches it) now has a real hand-written `def IsGCDOfList`, and textbook/build_definitions.py compiles any manifest's non-null lean_code entries into lib/Definitions.lean. goodman-1-6-24 (library) and the new goodman-1-6-ex-11-b/c, goodman-1-6-ex-12 (bench) all now use IsGCDOfList instead of repeating an inline existential. - textbook/README.md rewritten around the two-track design and records how this is meant to connect to lean-agent's eventual goal-directed proof search against the growing library (not yet built). - New tests for extract_exercises.py and build_definitions.py; full local suite still green (18/19, the one failure is the pre-existing, unrelated lean_agent import error in test_dataset.py). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017vbLFFJiHSZqGZHEkjvG5x
222 lines
11 KiB
Markdown
222 lines
11 KiB
Markdown
# Textbook formalization pipeline
|
|
|
|
Turns one book (currently Goodman, *Algebra: Abstract and Concrete*) into two
|
|
distinct things `lean-bench`/`lean-agent` can use, not one:
|
|
|
|
- **The bench** (`datasets/goodman-1.6-exercises.jsonl`): real problems the reader
|
|
is meant to solve, sourced from the book's own **Exercises** at the end of each
|
|
section. These are the actual targets `lean-bench` scores an agent against.
|
|
- **The library candidates** (`library_candidates/goodman-1.6.jsonl`): the book's
|
|
stated Theorem/Proposition/Lemma/Corollary claims from the section's main text.
|
|
These are NOT bench targets -- they are premises waiting to be proven once and
|
|
then reused, the way Mathlib itself is a library of already-proven facts an agent
|
|
can cite while proving something new. An entry only belongs in the trustworthy,
|
|
reusable library after `lean-bench` has actually proven it for real (see step 5);
|
|
`build_library.py` already enforces this -- it only assembles entries whose
|
|
`results.jsonl` says `success: true`, with the exact proof that was checked, never
|
|
anything from this file directly.
|
|
- **Definitions** (`textbook/*.definitions.json` + `lib/Definitions.lean`): a
|
|
concept a bench/library statement depends on. When Mathlib already has it
|
|
(`mathlib_ref` set), statements just use that name directly. When it doesn't
|
|
(`mathlib_ref: null`), the manifest also carries real, hand-written Lean code
|
|
(`lean_code`), compiled by `build_definitions.py` into an actual importable
|
|
`.lean` file -- Lean can only ever verify a proof about a concept that exists as
|
|
real Lean code somewhere, a JSON description of a definition is not enough for it
|
|
to typecheck anything that mentions it.
|
|
|
|
This pipeline does not itself prove anything or run Lean; see "What this pipeline
|
|
does not do" below and `lean-agent`'s own README for how proving actually happens
|
|
(goal-directed proof search against the library, not a single-shot proof attempt --
|
|
see "How this is meant to connect to lean-agent" at the end of this file).
|
|
|
|
## What this pipeline does not do
|
|
|
|
- Does not produce proofs. Every `statement` field is an unproven target; a Lean
|
|
`sorry` would technically "complete" it, which is exactly why proving is left to
|
|
`lean-agent`/`lean-bench`, not this pipeline.
|
|
- Does not verify Lean statements type-check inside the Cowork sandbox -- there is
|
|
no Lean/Lake/Mathlib there (also architecturally incompatible: your local
|
|
`~/.elan` has macOS binaries, the sandbox is a Linux VM). `check_statements.py`
|
|
must run on your machine against a Mathlib-backed Lean project; see "Local
|
|
environment setup" below.
|
|
- Does not commit the textbook PDF or large raw/derived full-text extraction dumps
|
|
to git -- `.gitignore` excludes `*.pdf` and `textbook/*.txt`, since that's
|
|
copyrighted material that should only ever live on your machine. Only code, the
|
|
generated JSON/JSONL datasets (short Lean statements + your own short English
|
|
glosses), and the compiled `Definitions.lean` go into git.
|
|
|
|
## Usage
|
|
|
|
### 1. Extract one chapter's layout text from the PDF
|
|
|
|
```sh
|
|
pdftotext -layout -f <start pdf page> -l <end pdf page> uiowa-algebra.pdf textbook/chapterNN_layout.txt
|
|
```
|
|
|
|
Check page numbers with `pdfinfo`/`pypdf` first -- the book's printed page numbers
|
|
and the PDF's physical page numbers differ (front matter, table of contents).
|
|
|
|
### 2. Slice out a section's body and its exercises separately
|
|
|
|
```sh
|
|
python3 textbook/extract_section.py textbook/chapterNN_layout.txt \
|
|
--start-heading "1.6. Divisibility in the Integers" \
|
|
--end-heading "1.7. Modular Arithmetic" \
|
|
--section-prefix "1.6." \
|
|
--out-text textbook/section_1_6.txt \
|
|
--out-index textbook/section_1_6.index.json
|
|
|
|
python3 textbook/extract_exercises.py textbook/chapterNN_layout.txt \
|
|
--start-heading "Exercises 1.6" \
|
|
--end-heading "1.7. Modular Arithmetic" \
|
|
--section-prefix "1.6." \
|
|
--out-text textbook/exercises_1_6.txt \
|
|
--out-index textbook/exercises_1_6.index.json
|
|
```
|
|
|
|
Headings must match the layout text's own section-title lines **verbatim**.
|
|
`extract_exercises.py` drops obvious false positives (a sentence like "...converse
|
|
of Proposition\n1.6.19." can wrap so the bare number lands alone at a line start,
|
|
looking exactly like a real exercise heading) -- anything dropped is printed to
|
|
stderr, never silently lost.
|
|
|
|
### 3. Formalize the body (-> library candidates) and the exercises (-> bench) separately
|
|
|
|
```sh
|
|
export DASHSCOPE_API_KEY=... # or put it in lean-bench/.env (gitignored)
|
|
|
|
python3 textbook/formalize.py \
|
|
textbook/section_1_6.txt textbook/section_1_6.index.json \
|
|
--model qwen-max --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 \
|
|
--out-dataset library_candidates/goodman-1.6.jsonl \
|
|
--out-definitions textbook/goodman-1.6.definitions.json \
|
|
--out-excluded textbook/goodman-1.6.excluded.jsonl
|
|
|
|
python3 textbook/formalize_exercises.py \
|
|
textbook/section_1_6.txt textbook/exercises_1_6.txt textbook/exercises_1_6.index.json \
|
|
--model qwen-max --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 \
|
|
--out-dataset datasets/goodman-1.6-exercises.jsonl \
|
|
--out-excluded textbook/goodman-1.6-exercises.excluded.jsonl
|
|
```
|
|
|
|
`formalize_exercises.py` is given the body text too, because many exercises say
|
|
things like "Prove Proposition 1.6.9" instead of restating the claim -- the model
|
|
needs the body to know what that actually says, and is told to write out the full
|
|
claim, never a placeholder like "the statement of Proposition 1.6.9".
|
|
|
|
Both steps validate that every checklist item is covered by at least one output
|
|
entry (included or explicitly excluded with a reason) and raise an error if the
|
|
model silently drops one instead -- this caught a real bug during the exercises
|
|
pilot (7 of 15 checklist items were dropped with no trace on the first attempt).
|
|
|
|
The source PDF's math symbol extraction is unreliable (missing/garbled ≡, ∈,
|
|
superscripts), so this step leans on the model's own knowledge of standard textbook
|
|
mathematics rather than literal transcription. Candidate statements must go through
|
|
step 4 before you trust them -- in practice, even after that, expect to still need
|
|
to read and fix a meaningful fraction by hand (both the exercises and the original
|
|
body pilot needed real fixes to type mismatches, invalid syntax, and even one
|
|
mathematically false claim smuggled in as a "prove or disprove" exercise that got
|
|
mis-included as an unconditional theorem).
|
|
|
|
### 4. Check candidate statements type-check on your machine
|
|
|
|
```sh
|
|
python3 textbook/check_statements.py library_candidates/goodman-1.6.jsonl \
|
|
--lean-project /path/to/your/mathlib-project \
|
|
--lean-command '["lake","env","lean"]' \
|
|
--report textbook/goodman-1.6.elab-report.jsonl
|
|
|
|
python3 textbook/check_statements.py datasets/goodman-1.6-exercises.jsonl \
|
|
--lean-project /path/to/your/mathlib-project \
|
|
--lean-command '["lake","env","lean"]' \
|
|
--report textbook/goodman-1.6-exercises.elab-report.jsonl
|
|
```
|
|
|
|
This only checks "does this Lean statement type-check" (via `sorry`), not "is it
|
|
provable" or "is it faithful to the book" -- report any `FAIL` id and its Lean
|
|
diagnostics and they'll get fixed and pushed back.
|
|
|
|
### 5. Prove the bench targets for real with lean-agent/lean-bench
|
|
|
|
```sh
|
|
lean-bench datasets/goodman-1.6-exercises.jsonl --output results/goodman-1.6-001 \
|
|
--lean-project /path/to/your/mathlib-project --lean-command '["lake","env","lean"]' \
|
|
--model ... --base-url ...
|
|
```
|
|
|
|
### 6. Compile hand-written definitions into real, importable Lean
|
|
|
|
```sh
|
|
python3 textbook/build_definitions.py textbook/goodman-1.6.definitions.json --out lib/Definitions.lean
|
|
```
|
|
|
|
Only definitions with no Mathlib equivalent (`mathlib_ref: null`) contribute
|
|
anything here -- everything else is used directly by name. Any statement whose
|
|
`prelude` needs one of these also inlines the `def` itself (self-contained per
|
|
task, matching how `check_statements.py` elaborates one task at a time), so
|
|
`lib/Definitions.lean` is the single canonical copy and the inlined ones must stay
|
|
in sync with it by construction (generated from the same manifest).
|
|
|
|
### 7. Assemble proven library candidates into a reusable library file
|
|
|
|
```sh
|
|
python3 textbook/build_library.py library_candidates/goodman-1.6.jsonl \
|
|
results/goodman-1.6-001-library/results.jsonl --out lib/Chapter01Section06.lean
|
|
```
|
|
|
|
(This step needs its own `lean-bench` run over `library_candidates/`, proving those
|
|
statements the same way step 5 proves the exercises -- not yet done for section
|
|
1.6.) Only entries that were actually proven in that run go in, with the exact
|
|
proof that was checked, verbatim. Later chapters `import` this file to build on
|
|
already-proven results -- it's one flat `.lean` file, not a real Lake
|
|
package/module boundary.
|
|
|
|
## How this is meant to connect to lean-agent
|
|
|
|
This pipeline only produces data files; it is not the proof search itself. The
|
|
intended `lean-agent` workflow this feeds:
|
|
|
|
1. Given a target statement, check whether it's even valid Lean (this is what
|
|
`check_statements.py` does ahead of time, so `lean-agent` doesn't waste attempts
|
|
on a statement that can't type-check no matter what).
|
|
2. Track the current proof state as a list of open goals (the same thing the
|
|
[vscode-lean4](https://github.com/leanprover/vscode-lean4) plugin shows you
|
|
live) -- not attempt the whole theorem in one LLM call.
|
|
3. At each step, search a database of already-proven statements (`lib/*.lean`,
|
|
built by `build_library.py`, plus Mathlib itself) for something applicable to
|
|
the current goal -- the equivalent of Lean's `exact?`/`apply?` tactics, or a
|
|
semantic/embedding search over the library -- and if something is used, apply it
|
|
and fold the resulting subgoals back into the open-goals list.
|
|
3. Repeat until the goal list is empty (theorem proved) or a budget is exhausted
|
|
(forced stop, left as an open goal / `sorry`).
|
|
|
|
That proof-search loop isn't built yet -- `lean-bench`'s current `solve()` call is
|
|
still single-shot (one LLM call, checked by Lean, retried up to `--attempts` times
|
|
from scratch), not goal-directed. This pipeline's two-track split (bench targets
|
|
from exercises, growing library from proven body content) is what that loop will
|
|
need once it exists.
|
|
|
|
## Local environment setup: a Lake project with Mathlib
|
|
|
|
The Cowork sandbox has no Lean; `check_statements.py` and real `lean-agent`/
|
|
`lean-bench` runs both need to happen on your machine (or anywhere else with Lean).
|
|
If you don't have such a project yet:
|
|
|
|
```sh
|
|
cd ~/ProjectResearch # anywhere
|
|
lake +leanprover/lean4:v4.33.1 new mathlib-bench math
|
|
cd mathlib-bench
|
|
lake exe cache get # downloads Mathlib's precompiled cache -- much faster than building from source
|
|
lake build Mathlib # compiles Mathlib's own top-level .olean aggregator (cache get alone doesn't)
|
|
```
|
|
|
|
Then point `--lean-project` at `mathlib-bench` and `--lean-command` at
|
|
`'["lake","env","lean"]'`.
|
|
|
|
## Feedback loop
|
|
|
|
I (claude-bot) generate/edit files in ProjectResearch -> commit and push to
|
|
`Zayden-Jung/lean-bench` (using claude-bot's token) -> you `git pull` locally ->
|
|
run `check_statements.py` / `lean-bench` -> send back the failing ids and Lean's
|
|
diagnostics -> I fix and push again. Ordinary git round-trip, no extra tooling.
|