Files
lean4/tests/elab/grind_guide.lean
Garmelon 08eb78a5b2 chore: switch to new test/bench suite (#12590)
This PR sets up the new integrated test/bench suite. It then migrates
all benchmarks and some related tests to the new suite. There's also
some documentation and some linting.

For now, a lot of the old tests are left alone so this PR doesn't become
even larger than it already is. Eventually, all tests should be migrated
to the new suite though so there isn't a confusing mix of two systems.
2026-02-25 13:51:53 +00:00

306 lines
9.7 KiB
Lean4
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
module
@[expose] public section -- TODO: remove after we fix congr_eq
/-
`grind` is inspired by automated reasoning techniques developed in the world of Satisfiability Modulo Theories (SMT).
However, it is not an SMT solver and is not designed to solve massive combinatorial problems.
For example, it would crash if we tried to feed it some of the bit-blasted formulas produced by
`bv_decide`.
At the core of grind, we use a custom congruence closure algorithm for dependent type theory.
We accumulate terms that are known to be equal. You should think of it as a blackboard where
we keep all the facts we know about the current goal.
Remark: Terms known to be true (false) belong to the equivalence class of the term `True` (`False`).
-/
example (a b c : α) (f : α α) : f a = c a = b c = f b := by
grind
/-
`grind` normalizes terms before adding them to the `grind` state.
Goals:
- We want to minimize the number of different things we need to handle.
- We want to canonicalize instances, implicit terms, etc.
- We want to hash cons terms, i.e., structural equality == pointer equality (performance).
- ...
-/
set_option trace.grind.assert true in -- Trace asserted facts
example : a = 1 + 2 b = 3 a = b := by
grind
/-
`grind` applies many "constraint" propagation rules. They are used
to apply many builtin principles such as
- Injectivity of constructors.
- Beta reduction
- Extensionality theorems.
- Logical connectives.
- ...
-/
set_option trace.grind.eqc true in -- Trace equivalence classes being merged
example (f : Nat List Nat) : f a = [1, 2] f b = [] a b := by
grind
/-
`grind` does not case-split like a SMT solver. It uses the structure of the formula,
and splits on `if-then-else`-expressions, `match`-expressions, inductive predicates
marked with `[grind cases]`, etc.
-/
set_option trace.grind.split true in -- Trace case-splits performed by `grind`
example : (match x with
| 0 => 1
| _ + 1 => 2) > 0 := by
grind
example : (match x with
| 0 => 1
| _ + 1 => 2) > 0 := by
fail_if_success grind (splits := 0) -- `grind` fails if we don't allow it case-split.
sorry
/-
`grind` shows you the case-splits performed when it fails.
-/
example : (match x with
| 0 => 1
| _ + 1 => 2) > 1 := by
fail_if_success grind
sorry
/-
`simp` uses theorems as rewriting rules.
`grind` instantiates the theorems using E-matching.
Each theorem is annotated with patterns.
Whenever, it finds an instance of the pattern modulo
the equalities in its state, it instantites the theorem.
There is no rewriting happening. It is just accumulating facts.
-/
opaque f : Nat Nat
opaque g : Nat Nat
-- `[grind =]` instructs `grind` to use the left-hand side of the conclusion as the pattern.
@[grind =] theorem fg : f (g x) = x := sorry
set_option trace.grind.assert true in
set_option trace.grind.ematch.instance true in
example : f a = b a = g c b = c := by
grind
/-
We have the following variants:
- `[grind =_]`: use right-hand side of the conclusion.
- `[grind _=_]`: use both right and left-hand side of the conclusion.
- `[grind →]`: use terms in the antecedents as patterns. "Forward reasoning"
- `[grind ←]`: use terms in the conclusion as patterns. "Backward reasoning"
- `[grind =>]`: look for patterns from left to right.
- `[grind <=]`: look for patterns from right to left.
-/
opaque R : Nat Nat Prop
@[grind ] theorem Rtrans : R x y R y z R x z := sorry
@[grind ] theorem Rsymm : R x y R y x := sorry
set_option trace.grind.assert true in
example : R a b R c b R d c R a d := by
grind
/-
The attributes above are for convenience.
If an user wants total control over the selected pattern
they may use the command `grind_pattern`.
-/
opaque bla : Nat Nat
theorem blaThm : bla (bla x) = bla x := sorry
grind_pattern blaThm => bla (bla x)
set_option trace.grind.assert true in
example : bla a = b bla b = b := by
grind
/-
We also have
- `[grind ←=]`: equality driven backward reasoning.
-/
opaque U : Type
axiom mul : U U U
axiom one : U
axiom inv : U U
/--
In the following example, `grind` instantiates the theorem
whenever it learns a disequality `t = s` where `t` or `s` E-matches `inv a`
-/
@[grind =] theorem inv_of_mul : mul a b = one inv a = b :=
sorry
/-
Like `simp`, we also have `grind only`
-/
example : R a b R c b R d c R a d := by
fail_if_success grind only -- `Rtrans` and `Rsymm` were not applied
sorry
example : R a b R c b R d c R a d := by
grind only [ Rtrans, Rsymm]
example : R a b R c b R d c R a d := by
grind only [Rtrans, Rsymm]
/-
We also have `grind?`.
We currently do not try to minimize the number of generated arguments, but
we will do it in the future.
It behaves like `simp?`. If something was used, it will appear in the result.
-/
example : R a b R c b R d c R a d := by
grind?
/-
We can instruct `grind` to case-split on inductive predicates and types,
and use the constructors of an inductive predicate as E-matching theorems.
-/
abbrev Variable := String
def State := Variable Nat
inductive Stmt : Type where
| skip : Stmt
| assign : Variable (State Nat) Stmt
| seq : Stmt Stmt Stmt
| ifThenElse : (State Prop) Stmt Stmt Stmt
| whileDo : (State Prop) Stmt Stmt
infix:60 ";; " => Stmt.seq
export Stmt (skip assign seq ifThenElse whileDo)
set_option quotPrecheck false in
notation s:70 "[" x:70 "" n:70 "]" => (fun v if v = x then n else s v)
inductive BigStep : Stmt State State Prop where
| skip (s : State) : BigStep skip s s
| assign (x : Variable) (a : State Nat) (s : State) : BigStep (assign x a) s (s[x a s])
| seq {S T : Stmt} {s t u : State} (hS : BigStep S s t) (hT : BigStep T t u) :
BigStep (S;; T) s u
| if_true {B : State Prop} {s t : State} (hcond : B s) (S T : Stmt) (hbody : BigStep S s t) :
BigStep (ifThenElse B S T) s t
| if_false {B : State Prop} {s t : State} (hcond : ¬ B s) (S T : Stmt) (hbody : BigStep T s t) :
BigStep (ifThenElse B S T) s t
| while_true {B S s t u} (hcond : B s) (hbody : BigStep S s t) (hrest : BigStep (whileDo B S) t u) :
BigStep (whileDo B S) s u
| while_false {B S s} (hcond : ¬ B s) : BigStep (whileDo B S) s s
notation:55 "(" S:55 "," s:55 ")" " ==> " t:55 => BigStep S s t
example {B S T s t} (hcond : B s) : (ifThenElse B S T, s) ==> t (S, s) ==> t := by
grind [cases BigStep]
theorem cases_if_of_true {B S T s t} (hcond : B s) : (ifThenElse B S T, s) ==> t (S, s) ==> t := by
grind [cases BigStep]
theorem cases_if_of_false {B S T s t} (hcond : ¬ B s) : (ifThenElse B S T, s) ==> t (T, s) ==> t := by
grind [cases BigStep]
theorem if_iff {B S T s t} : (ifThenElse B S T, s) ==> t (B s (S, s) ==> t) (¬ B s (T, s) ==> t) := by
grind [cases BigStep, intro BigStep]
example {B S T s t} : (ifThenElse B S T, s) ==> t (B s (S, s) ==> t) (¬ B s (T, s) ==> t) := by
grind [BigStep] -- shortcut for `cases BigStep` and `intro BigStep`
/-
`grind` already has support for offset equalities and inequalities:
- `x + k ≤ y`, `x ≤ y + k`, `x + k = y`, ... where `k` is a numeral.
- Efficient algorithm.
- Very compact proofs.
- Exhaustive propagation, but it does not case-split on disequalities.
Very effective for examples using the notation `a[i]`
-/
-- Standard library will be
attribute [grind =] Array.size_set Array.getElem_set_self Array.getElem_set_ne
example (as bs cs ds : Array α) (v₁ v₂ v₃ : α)
(i₁ i₂ i₃ j : Nat)
(h₁ : i₁ < as.size)
(h₂ : as.set i₁ v₁ = bs)
(h₃ : i₂ < bs.size)
(h₃ : bs.set i₂ v₂ = cs)
(h₄ : i₃ < cs.size)
(h₅ : ds = cs.set i₃ v₃)
(h₆ : j i₁ j i₂ i₃ j)
(h₇ : j < ds.size)
(h₈ : j < as.size)
: ds[j] = as[j] := by
set_option diagnostics true in
grind
/-
`grind` has support for splitting on `match`-expressions with overlapping patterns.
It is quite complex in the dependent case, and many `cast`-operations have to be performed.
-/
namespace Ex
def f : List Nat List Nat Nat
| _, 1 :: _ :: _ => 1
| _, a :: _ => if a > 1 then 2 else 3
| _, _ => 0
example : z = a :: as y = z f x y > 0 := by
grind [f.eq_def]
end Ex
/- It gets quite messy with dependent pattern matching. -/
inductive Vec (α : Type u) : Nat Type u
| nil : Vec α 0
| cons : α Vec α n Vec α (n+1)
def h (v w : Vec α n) : Nat :=
match v, w with
| _, .cons _ (.cons _ _) => 20
| .nil, _ => 30
| _, _ => 40
example : b = .cons 1 .nil h a b = 40 := by
grind [h.eq_def]
/-
`try?` tactic is a driver around `grind` (and other tactics).
It tries many different things (e.g., applies function induction principle for you)
-/
def app : List α List α List α
| [], bs => bs
| a::as, bs => a :: app as bs
theorem app_assoc : app as (app bs cs) = app (app as bs) cs := by
try?
/-
`grind` has a notion of term "generation". It is useful for avoiding
unbounded instantiation.
-/
opaque bomb : Nat Nat
@[grind =] theorem bombEx : bomb x = bomb (bomb x) + 1 := sorry
example : bomb x > 10 := by
fail_if_success grind
sorry
/-
Roadmap:
- Improve `try?`
- Improve diagnostics. There are so many possible improvements.
- Improve heuristics (e.g., "weights", `grind` specific `[grind ext]`).
- Bug fixes.
- Linear integer arithmetic.
- More examples like `constProp.lean`.
- Commutative rings support (Q2).
- Annotate standard library (Q2).
- `grind?` argument minimization (Q2).
- Fixing `induction a <;> grind?` (Q2).
- `bv_decide` integration (Q2).
- E-matching modulo associativity and commutativity?
-/