|
| 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