Skip to content

Commit 8573c88

Browse files
committed
doc: grind manual
This is a temporary placeholder. We will eventually move then manual to the reference manual.
1 parent 3dd12f8 commit 8573c88

File tree

1 file changed

+261
-0
lines changed

1 file changed

+261
-0
lines changed

doc/examples/grind.md

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
# Grind Tactic Manual
2+
3+
## 1. Quick Start
4+
5+
* **Availability** – `grind` ships with Lean 4 (no extra installation) and is usable in any Lean file—just write `by grind`. No extra `import` is required beyond what your own definitions already need.
6+
7+
* **Library support** – Lean’s standard library is already annotated with `[grind]` attributes, so common lemmas are discovered automatically. Mathlib will be annotated gradually, starting with its most frequently used theories.
8+
9+
* **First proof**
10+
11+
```lean
12+
example (a b c : Nat) (h₁ : a = b) (h₂ : b = c) : a = c := by
13+
grind
14+
```
15+
16+
This succeeds instantly using congruence closure.
17+
18+
* **Power examples** – showcasing `grind`'s satellite solvers:
19+
20+
* *Algebraic reasoning* (commutative‑ring solver):
21+
22+
```lean
23+
example [CommRing α] [NoNatZeroDivisors α] (a b c : α)
24+
: a + b + c = 3 →
25+
a^2 + b^2 + c^2 = 5 →
26+
a^3 + b^3 + c^3 = 7 →
27+
a^4 + b^4 = 9 - c^4 := by
28+
grind
29+
```
30+
31+
* *Finite‑field style reasoning* (works in `Fin 11`):
32+
33+
```lean
34+
example (x y : Fin 11) : x^2*y = 1 → x*y^2 = y → y*x = 1 := by
35+
grind
36+
```
37+
38+
* *Linear integer arithmetic with case analysis*:
39+
40+
```lean
41+
example (x y : Int) :
42+
27 ≤ 11*x + 13*y →
43+
11*x + 13*y ≤ 45 →
44+
-10 ≤ 7*x - 9*y →
45+
7*x - 9*y ≤ 4 → False := by
46+
grind
47+
```
48+
49+
* **Useful flags**
50+
51+
* `by grind (splits := 3) (ematch := 2)` – limit case splits / E‑matching rounds.
52+
53+
## 2. What is `grind`?
54+
55+
A proof‑automation tactic inspired by modern SMT solvers. **Picture a virtual white‑board:** every time `grind` discovers a new equality, inequality, or Boolean literal it writes that fact on the board, merges equivalent terms into buckets, and invites each engine to read from—and add back to—the same workspace. The cooperating engines are: congruence closure, constraint propagation, E‑matching, guided case analysis, and a suite of satellite theory solvers (linear integer arithmetic, commutative rings, …).Lean supports dependent types and a powerful type‑class system, and `grind` produces ordinary Lean proof terms for every fact it adds. 
56+
57+
## 3. What `grind` is *not*
58+
59+
`grind` is *not* designed for goals whose search space explodes combinatorially—think large‑`n` pigeonhole instances, graph‑coloring reductions, high‑order N‑queens boards, or a 200‑variable Sudoku encoded as Boolean constraints. Such encodings require thousands (or millions) of case‑splits that overwhelm `grind`’s branching search.
60+
61+
* **Bit‑level or pure Boolean combinatorial problems** → use **`bv_decide`**.
62+
`bv_decide` calls a state‑of‑the‑art SAT solver (e.g. CaDiCaL or Kissat) and then returns a *compact, machine‑checkable certificate*. All heavy search happens outside Lean; the certificate is replayed and verified inside Lean, so trust is preserved (verification time scales with certificate size).
63+
* **Full SMT problems that need substantial case analysis across multiple theories** (arrays, bit‑vectors, rich arithmetic, quantifiers, …) → use the forthcoming **`lean‑smt`** tactic—a tight Lean front‑end for CVC5 that replays unsat cores or models inside Lean.
64+
65+
## 4. Congruence Closure
66+
67+
### 4.1  What is congruence closure?
68+
69+
Congruence closure maintains **equivalence classes of terms** under the reflexive–symmetric–transitive closure of "is equal to" *and* the rule that equal arguments yield equal function results. Formally, if `a = a'` and `b = b'`, then `f a b = f a' b'` is added. The algorithm merges classes until a fixed point is reached.
70+
71+
Think of a **shared white‑board**:
72+
73+
1. Every hypothesis `h : t₁ = t₂` writes a line connecting `t₁` and `t₂`.
74+
2. Each merge paints both terms the same color. Soon whole constellations (`f a`, `g (f a)`, …) share the color.
75+
3. If `True` and `False` ever land in the same color—or likewise two different constructors of the **same inductive type** such as `none` and `some 1`—the goal is closed by contradiction.
76+
77+
### 4.2  How it differs from `simp`
78+
79+
* `simp` **rewrites** a goal, replacing occurrences of `t₁` with `t₂` as soon as it sees `h : t₁ = t₂`. The rewrite is directional and destructive.
80+
* `grind` **accumulates** equalities bidirectionally. No term is rewritten; instead, both representatives live in the same class. All other engines (E‑matching, theory solvers, propagation) can query these classes and add new facts, then the closure updates incrementally.
81+
82+
This makes congruence closure especially robust in the presence of symmetrical reasoning, mutual recursion, and large nestings of constructors where rewriting would duplicate work.
83+
84+
### 4.3  Minimal examples
85+
86+
```lean
87+
example {α} (f g : α → α) (x y : α) (h₁ : x = y) (h₂ : f y = g y) : f x = g x := by
88+
-- After h₁, x and y share a class; h₂ adds f y = g y; closure bridges to f x = g x
89+
grind
90+
91+
example (a b c : Nat) (h : a = b) : (a, c) = (b, c) := by
92+
-- Pair constructor obeys congruence, so once a = b the tuples are equal
93+
grind
94+
```
95+
96+
### 4.4  Debugging tip
97+
98+
When `grind` *fails* it prints the remaining subgoal **followed by all equivalence classes**. The two largest classes are shown as **True propositions** and **False propositions**, listing every literal currently known to be provable or refutable. Inspect these lists to spot missing facts or contradictory assumptions.
99+
100+
## 5. Constraint Propagation
101+
102+
Constraint propagation works on the **True** and **False** buckets of the white‑board. Whenever a literal is added to one of those buckets, `grind` fires dozens of small *forward rules* to push its logical consequences:
103+
104+
* Boolean connectives — e.g. if `A` is **True**, mark `A ∨ B` **True**; if `A ∧ B` is **True**, mark both `A` and `B` **True**; if `A ∧ B` is **False**, at least one of `A`, `B` becomes **False**.
105+
* Inductive datatypes — two different constructors (`none` vs `some _`) collapsing into the same class yield contradiction; equal tuples yield equal components.
106+
* Projections and casts — from `h : (x, y) = (x', y')` we derive `x = x'` and `y = y'`; any term `cast h a` is merged with `a` immediately (using a heterogeneous equality) so both live in the same class.
107+
* Structural eta and definitional equalities — `⟨a, b⟩.1` propagates to `a`, etc.
108+
109+
Below is a **representative slice** of the propagators so you can see the style they follow. Each follows the same skeleton: inspect the truth‑value of sub‑expressions, push equalities (`pushEq`) or truth‑values (`pushEqTrue` / `pushEqFalse`), and optionally close the goal if a contradiction (`closeGoal`) arises. A few high‑signal examples:
110+
111+
```lean
112+
/-- Propagate equalities *upwards* for conjunctions. -/
113+
builtin_grind_propagator propagateAndUp ↑And := fun e => do
114+
let_expr And a b := e | return ()
115+
if (← isEqTrue a) then
116+
-- a = True ⇒ (a ∧ b) = b
117+
pushEq e b <| mkApp3 (mkConst ``Grind.and_eq_of_eq_true_left) a b (← mkEqTrueProof a)
118+
else if (← isEqTrue b) then
119+
pushEq e a <| mkApp3 (mkConst ``Grind.and_eq_of_eq_true_right) a b (← mkEqTrueProof b)
120+
else if (← isEqFalse a) then
121+
pushEqFalse e <| mkApp3 (mkConst ``Grind.and_eq_of_eq_false_left) a b (← mkEqFalseProof a)
122+
else if (← isEqFalse b) then
123+
pushEqFalse e <| mkApp3 (mkConst ``Grind.and_eq_of_eq_false_right) a b (← mkEqFalseProof b)
124+
125+
/-- Truth flows *down* when the whole `And` is proven `True`. -/
126+
builtin_grind_propagator propagateAndDown ↓And := fun e => do
127+
if (← isEqTrue e) then
128+
let_expr And a b := e | return ()
129+
let h ← mkEqTrueProof e
130+
pushEqTrue a <| mkApp3 (mkConst ``Grind.eq_true_of_and_eq_true_left) a b h
131+
pushEqTrue b <| mkApp3 (mkConst ``Grind.eq_true_of_and_eq_true_right) a b h
132+
```
133+
134+
Other frequently‑triggered propagators follow the same pattern:
135+
136+
| Propagator | Handles | Notes |
137+
| ------------------------------------- | ------------------------------- | ---------------------------------------------- |
138+
| `propagateOrUp` / `propagateOrDown` | `a ∨ b` | True/False pushes for disjunctions |
139+
| `propagateNotUp` / `propagateNotDown` | `¬ a` | Links `¬ a` with the Boolean of `a` |
140+
| `propagateEqUp` / `propagateEqDown` | `a = b` | Bridges Booleans, detects constructor clash |
141+
| `propagateIte` / `propagateDIte` | `ite` / `dite` | Replaces chosen branch once condition is fixed |
142+
| `propagateEtaStruct` | structures tagged `[grind ext]` | Generates η‑expansion `a = ⟨a.1, …⟩` |
143+
144+
Many specialised variants for `Bool` mirror these rules exactly (e.g. `propagateBoolAndUp`).
145+
146+
#### 5.5  Propagation‑only examples
147+
148+
These goals are closed *purely* by constraint propagation—no case splits, no theory solvers:
149+
150+
```lean
151+
-- Boolean connective: a && !a is always false.
152+
example (a : Bool) : (a && !a) = false := by
153+
grind
154+
155+
-- Conditional (ite): once the condition is true, ite picks the 'then' branch.
156+
example (c : Bool) (t e : Nat) (h : c = true) : (if c then t else e) = t := by
157+
grind
158+
159+
-- Negation propagates truth downwards.
160+
example (a : Bool) (h : (!a) = true) : a = false := by
161+
grind
162+
```
163+
164+
These snippets run instantly because the relevant propagators (`propagateBoolAndUp`, `propagateIte`, `propagateBoolNotDown`) fire as soon as the hypotheses are internalised.
165+
166+
> **Note**  If you toggle `set_option trace.grind.eqc true`, `grind` will print a line every time two equivalence classes merge—handy for seeing propagation in action.
167+
168+
**Implementation tip** `grind` is still under active development. Until the API has stabilised we recommend **refraining from custom elaborators or satellite solvers**. If you really need a project‑local propagator, use the user‑facing `grind_propagator` command rather than `builtin_grind_propagator` (the latter is reserved for Lean’s own code). When adding new propagators keep them *small and orthogonal*—they should fire in ≤1 µs and either push one fact or close the goal. This keeps the propagation phase predictable and easy to debug.
169+
  `grind` is still under active development. Until the API has stabilised we recommend **refraining from custom elaborators or satellite solvers**. If you really need a project‑local propagator, use the user‑facing `grind_propagator` command rather than `builtin_grind_propagator` (the latter is reserved for Lean’s own code). When adding new propagators keep them *small and orthogonal*—they should fire in ≤1 µs and either push one fact or close the goal. This keeps the propagation phase predictable and easy to debug.
170+
171+
We continuously expand and refine the rule set—expect the **Info View** to show increasingly rich True/False buckets over time. The full equivalence classes are displayed automatically **only when `grind` fails**, and only for the first subgoal it could not close—use this output to inspect missing facts and understand why the subgoal remains open.
172+
173+
## 6. Case Analysis
174+
175+
### 6.1  Selection heuristics
176+
177+
`grind` decides which sub‑term to split on by combining three sources of signal:
178+
179+
1. **Structural flags** — quick booleans that enable whole syntactic classes:
180+
181+
* `splitIte`  (default **true**) → split every `if … then … else …` term.
182+
* `splitMatch := true` → split on all `match` expressions (the `grind` analogue of Lean’s `split` tactic, just like `splitIte`).
183+
* `splitImp`  (default **false**) → when `true` splits on any hypothesis `A → B` whose antecedent `A` is **propositional**. Arithmetic antecedents are special‑cased: if `A` is an arithmetic literal (``, `=`, `¬`, `Dvd`, …) `grind` will split **even when `splitImp := false`** so the integer solver can propagate facts.
184+
185+
👉 Shorthand toggles: `by grind -splitIte +splitImp` expands to `by grind (splitIte := false) (splitImp := true)`.
186+
2\. **Global limit**`splits := n` caps the *depth* of the search tree. Once a branch performs `n` splits `grind` stops splitting further in that branch; if the branch cannot be closed it reports that the split threshold has been reached.
187+
3\. **Manual annotations** — you may mark *any* inductive predicate or structure with
188+
189+
```lean
190+
attribute [grind cases] Even Sorted
191+
```
192+
193+
and `grind` will treat every instance of that predicate as a split candidate.
194+
195+
### 6.2  Examples
196+
197+
```lean
198+
-- splitIte demonstration
199+
example (c : Bool) (x y : Nat) (h : (if c then x else y) = 0) :
200+
x = 0 ∨ y = 0 := by
201+
grind
202+
203+
example (c : Bool) (x y : Nat) (h : (if c then x else y) = 0) :
204+
x = 0 ∨ y = 0 := by
205+
-- The following tactic fails because we need one case split
206+
fail_if_success grind (splits := 0)
207+
grind (splits := 1)
208+
209+
-- User‑defined predicate with [grind cases]
210+
inductive Even : Nat → Prop
211+
| zero : Even 0
212+
| step : Even n → Even (n+2)
213+
214+
attribute [grind cases] Even
215+
216+
example (h : Even 5) : False := by
217+
-- With the attribute, grind immediately splits on the Even hypothesis
218+
grind
219+
220+
example (h : Even (n + 2)) : Even n := by
221+
grind
222+
223+
example (h : y = match x with | 0 => 1 | _ => 2) : y > 0 := by
224+
-- `grind` fails if we disable `splitMatch`
225+
fail_if_success grind -splitMatch
226+
grind
227+
```
228+
229+
### 6.3  Tips
230+
231+
* Increase `splits` *only* when the goal genuinely needs deeper branching; each extra level multiplies the search space.
232+
* Disable `splitMatch` when large pattern‑matching definitions explode the tree.
233+
* You can combine flags: `by grind -splitMatch (splits := 10) +splitImp`.
234+
* The `[grind cases]` attribute is *scoped*; you can use the modifiers `local`/`scoped` if you only want extra splits inside a section or namespace.
235+
236+
## 7. E‑matching
237+
238+
TBD
239+
Pattern annotations (`[grind =]`, `[grind ->]`, …), anti‑patterns, local vs global attributes, debugging with the attribute `[grind?]`. Flags: `ematch`, `instances`, `matchEqs`.
240+
241+
## 8. Linear Integer Arithmetic Solver
242+
TBD
243+
Model‑building CutSAT‑style procedure, model‑based theory combination. Flags: `+qlia`, `-mbtc`.
244+
245+
## 9. Algebraic Solver (Commutative Rings, Fields)
246+
TBD
247+
Grobner‑style basis construction, class parameters (`IsCharP`, `NoNatZeroDivisors`), step budget `algSteps`.
248+
249+
## 10. Normalizer / Pre‑processor
250+
TBD
251+
Canonicalization pass; extending with `[grind norm]` (expert only).
252+
253+
## 11. Diagnostics
254+
TBD
255+
Threshold notices, learned equivalence classes, integer assignments, algebraic basis, performed splits, instance statistics.
256+
257+
## 12. Troubleshooting & FAQ
258+
TBD
259+
260+
## 13. Bigger Examples
261+
TBD

0 commit comments

Comments
 (0)