grindrecent tactic, announced: July 2025
meant to automatically provide proofs for easy goals
basic understanding of the workings of grind
focus on usage and user interface
see a lot of Mathlib examples
read the Language Reference for more detail
"virtual whiteboard"
to start: hypotheses and negated conclusion on the whiteboard
employs different engines to discover a proof
engines write discovered facts on the whiteboard for other engines to use
theorem exists_subset_or_subset_of_two_mul_lt_card
{α : Type*} [DecidableEq α] {X Y : Finset α}
{n : ℕ} (hXY : 2 * n < #(X ∪ Y)) :
∃ C : Finset α,
n < #C ∧ (C ⊆ X ∨ C ⊆ Y) := byα:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)⊢ ∃ C, n < #C ∧ (C ⊆ X ∨ C ⊆ Y)
grindAll goals completed! 🐙`grind` failed
α:Type u_1inst:DecidableEq αX Y:Finset αn:ℕhXY:2 * n < #(X ∪ Y)h:∀ (a : Finset α), #a ≤ n ∨ ¬a ⊆ X ∧ ¬a ⊆ Yleft:¬X ∪ Y ⊆ Xright:¬X ∪ Y ⊆ Yw:αh_3:¬(w ∈ X ∪ Y → w ∈ X)w_1:αh_5:¬(w_1 ∈ X ∪ Y → w_1 ∈ Y)⊢ False
[grind] Goal diagnostics
[facts] Asserted facts
- [prop] 2 * n + 1 ≤ #(X ∪ Y)
- [prop] ∀ (a : Finset α), #a ≤ n ∨ ¬a ⊆ X ∧ ¬a ⊆ Y
- [prop] #(X ∪ Y) ≤ n ∨ ¬X ∪ Y ⊆ X ∧ ¬X ∪ Y ⊆ Y
- [prop] (X ∪ Y ⊆ Y) = ∀ ⦃x : α⦄, x ∈ X ∪ Y → x ∈ Y
- [prop] (X ∪ Y ⊆ X) = ∀ ⦃x : α⦄, x ∈ X ∪ Y → x ∈ X
- [prop] ¬X ∪ Y ⊆ X
- [prop] ¬X ∪ Y ⊆ Y
- [prop] ¬(w ∈ X ∪ Y → w ∈ X)
- [prop] ¬(w_1 ∈ X ∪ Y → w_1 ∈ Y)
- [prop] w_1 ∈ X ∪ Y → ¬#(X ∪ Y) = 0
- [prop] w ∈ X ∪ Y → ¬#(X ∪ Y) = 0
- [prop] (w_1 ∈ X ∪ Y) = (w_1 ∈ X ∨ w_1 ∈ Y)
- [prop] (w ∈ X ∪ Y) = (w ∈ X ∨ w ∈ Y)
[eqc] True propositions
- [prop] 2 * n + 1 ≤ #(X ∪ Y)
- [prop] ∀ (a : Finset α), #a ≤ n ∨ ¬a ⊆ X ∧ ¬a ⊆ Y
- [prop] ¬X ∪ Y ⊆ X
- [prop] ¬X ∪ Y ⊆ Y
- [prop] ¬X ∪ Y ⊆ X ∧ ¬X ∪ Y ⊆ Y
- [prop] #(X ∪ Y) ≤ n ∨ ¬X ∪ Y ⊆ X ∧ ¬X ∪ Y ⊆ Y
- [prop] (X ∪ Y ⊆ X) = ∀ ⦃x : α⦄, x ∈ X ∪ Y → x ∈ X
- [prop] (X ∪ Y ⊆ Y) = ∀ ⦃x : α⦄, x ∈ X ∪ Y → x ∈ Y
- [prop] w ∈ X ∪ Y
- [prop] w_1 ∈ X ∪ Y
- [prop] ¬#(X ∪ Y) = 0
- [prop] w ∈ X ∨ w ∈ Y
- [prop] w_1 ∈ X ∨ w_1 ∈ Y
- [prop] (w ∈ X ∪ Y) = (w ∈ X ∨ w ∈ Y)
- [prop] (w_1 ∈ X ∪ Y) = (w_1 ∈ X ∨ w_1 ∈ Y)
- [prop] w_1 ∈ X
- [prop] w ∈ Y
- [prop] w ∈ X ∪ Y → ¬#(X ∪ Y) = 0
- [prop] w_1 ∈ X ∪ Y → ¬#(X ∪ Y) = 0
[eqc] False propositions
- [prop] X ∪ Y ⊆ X
- [prop] X ∪ Y ⊆ Y
- [prop] w ∈ X
- [prop] w_1 ∈ Y
- [prop] ∀ ⦃x : α⦄, x ∈ X ∪ Y → x ∈ X
- [prop] ∀ ⦃x : α⦄, x ∈ X ∪ Y → x ∈ Y
- [prop] w ∈ X ∪ Y → w ∈ X
- [prop] w_1 ∈ X ∪ Y → w_1 ∈ Y
- [prop] #(X ∪ Y) = 0
[cases] Case analyses
[cases] [2/2]: #(X ∪ Y) ≤ n ∨ ¬X ∪ Y ⊆ X ∧ ¬X ∪ Y ⊆ Y
- [cases] source: E-matching `h`
[ematch] E-matching patterns
- [thm] card_union_add_card_inter: [@card #3 (@Union.union (Finset _) _ #1 #0), @card _ #0]
- [thm] card_union_add_card_inter: [@card #3 (@Union.union (Finset _) _ #1 #0), @card _ #1]
- [thm] h: [@LE.le `[ℕ] `[instLENat] (@card `[α] #0) `[n]]
- [thm] h: [@card `[α] #0]
- [thm] h: [@Subset `[Finset α] `[instHasSubset] #0 `[X]]
- [thm] h: [@Subset `[Finset α] `[instHasSubset] #0 `[Y]]
- [thm] subset_iff: [@Subset (Finset #2) _ #1 #0]
- [thm] Set.subset_def: [@Subset _ _ #1 #0]
- [thm] card_ne_zero_of_mem: [@Membership.mem #3 (Finset _) _ #2 #1, @card _ #2]
- [thm] mem_union: [@Membership.mem #4 (Finset _) _ (@Union.union (Finset _) _ #2 #1) #0]
- [thm] Multiset.mem_add: [@Membership.mem #3 _ _ (@HAdd.hAdd _ _ _ _ #1 #0) #2]
- [thm] Set.mem_union: [@Membership.mem #3 _ _ (@Union.union _ _ #1 #0) #2]
- [thm] Verso.Genre.Manual.HtmlFeatures.mem_union: [@Membership.mem `[Verso.Genre.Manual.HtmlFeature] `[Verso.Genre.Manual.HtmlFeatures] `[Verso.Genre.Manual.HtmlFeatures.instMembershipHtmlFeature] (@Union.union `[Verso.Genre.Manual.HtmlFeatures] `[Verso.Genre.Manual.HtmlFeatures.instUnion] #1 #0) #2]
- [thm] Verso.Genre.Manual.HtmlFeatures.mem_iff_contains: [@Membership.mem `[Verso.Genre.Manual.HtmlFeature] `[Verso.Genre.Manual.HtmlFeatures] `[Verso.Genre.Manual.HtmlFeatures.instMembershipHtmlFeature] #1 #0]
[cutsat] Assignment satisfying linear constraints
- [assign] n := 0
- [assign] #(X ∪ Y) := 1
[grind] Diagnostics
[thm] E-Matching instances
- [thm] card_ne_zero_of_mem ↦ 2
- [thm] mem_union ↦ 2
- [thm] subset_iff ↦ 2
- [thm] h ↦ 1
example (n k : ℕ) (h1 : 2 * n = k) : k = 5 := byn:ℕk:ℕh1:2 * n = k⊢ k = 5
grindAll goals completed! 🐙`grind` failed
n k:ℕh1:2 * n = kh:¬k = 5⊢ False
[grind] Goal diagnostics
[facts] Asserted facts
- [prop] 2 * n = k
- [prop] ¬k = 5
[eqc] False propositions
- [prop] k = 5
[eqc] Equivalence classes
- [eqc] {k, 2 * n}
[cutsat] Assignment satisfying linear constraints
- [assign] n := 0
- [assign] k := 0
[ring] Ring `Lean.Grind.Ring.OfSemiring.Q ℕ`
[diseqs] Disequalities
- [_] ¬2 * ↑n + -5 = 0
"basic": no specialized solvers or lemmas
For a function f and a₁ = bₙ, ..., aₙ = bₙ in the context, grind equates f a₁ ... aₙ and f b₁ ... bₙ.
Deriving basic logical consequences of propositions in True and False classes, e.g.
Boolean Connectives (e.g. A ∧ B = ⊤ implies A = ⊤ and B = ⊤)
Inductive Types (e.g. if some a = some b then a = b)
Projections (e.g. if (a₁, a₂) = (b₁, b₂) then a₁ = a₂)
Splitting for example on if-expressions and match-statements.
splits can make grind slow
use option trace.grind.split to inspect splits
disable certain types of splits: grind -splitIfs, grind -splitMatch
limit (or increase) case depth: grind (splits := n) (default: 9)
use of tagged lemmas in grind
basic tag: @[grind *] where * is a modifier
index of lemmas and corresponding patterns
when pattern matches terms in context, tries to apply ("instantiate") the corresponding theorem
very powerful with thorough tagging
very slow with too general tagging
indexable: expression that starts with a constant other than Eq, HEq, Iff, And, Or and Not
For example: t = s (where s and t are some terms) cannot be used as a pattern
Theorems can have multi-patterns: multiple patterns need to match before the theorem is tried.
theorem card_ne_zero_of_mem {α : Type*} {s : Finset α}
{a : α} (h : a ∈ s) : #s ≠ 0 :=
(not_congr card_eq_zero).2 <| ne_empty_of_mem h
#s ≠ 0, #s = 0: not indexable, cannot be used as patterns
#s and a ∈ s individually are too general
Mathlib uses #s and a ∈ s as a multi-pattern
modifiers to grind pick patterns according to a specific algorithm
grind? shows which pattern is picked
arguments are indexed with numbers starting with 0 for the first argument
some modifiers require all arguments to be fixed ("covered") by the resulting pattern
=: left side of equalityfor equalities: use left side as a pattern
fails if left side doesn't cover all arguments
@[grind? =even_iff: [@Even `[ℤ] `[Int.instAdd] #0]]
lemma even_iff {n : ℤ} :
Even n ↔ n % 2 = 0 where
mp := fun ⟨m, hm⟩ ↦ byn:ℤx✝:Even nm:ℤhm:n = m + m⊢ n % 2 = 0 simp [← Int.two_mul, hm]All goals completed! 🐙
mpr h := ⟨n / 2, byn:ℤh:n % 2 = 0⊢ n = n / 2 + n / 2 grindAll goals completed! 🐙⟩
=_: right side of equalityfor equalities: use right side as a pattern
fails if right side doesn't cover all arguments
@[grind? =_toList_toArray: [@Vector.toList #2 #1 #0]]
theorem toList_toArray {α : Type*} {n : ℕ}
{xs : Vector α n} :
xs.toArray.toList = xs.toList :=
rfl
_=_: both sides of equality as two patterns
two patterns corresponding to = and =_
theorem is used if the left or the right side matches
@[simp, grind? _=_trans_symm: [@Iso.trans #6 #5 #2 #3 #4 (@Iso.symm _ _ #3 #2 #0) (@Iso.symm _ _ #4 #3 #1)]trans_symm: [@Iso.symm #6 #5 #4 #2 (@Iso.trans _ _ #4 #3 #2 #1 #0)]]
theorem trans_symm {C : Type*} [Category C]
{X Y Z : C} (α : X ≅ Y) (β : Y ≅ Z) :
(α ≪≫ β).symm = β.symm ≪≫ α.symm :=
rfl
→: starting with hypothesesadd hypotheses to multi-pattern until all arguments are covered
start with first hypothesis
@[aesop safe forward, grind? →eq_of_mem: [@Set.EqOn #7 #6 #4 #3 #5, @Membership.mem _ _ _ #5 #2]]
lemma EqOn.eq_of_mem {α β : Type*} {s : Set α}
{f₁ f₂ : α → β} {a : α} (h : s.EqOn f₁ f₂)
(ha : a ∈ s) : f₁ a = f₂ a :=
h ha
.: starting with conclusionadd conclusion or hypotheses to multi-pattern until all arguments are covered
start with conclusion and then first hypothesis
@[grind? ._root_.isClosed_dsupport: [@IsClosed #8 #3 (@dsupport _ #7 #6 #5 #4 _ #2 #1 #0)]]
theorem isClosed_dsupport {α β F V : Type*}
[FunLike F α β] [TopologicalSpace α]
[Zero β] [Zero V] {f : F → V} :
IsClosed (dsupport f) := byα:Type u_1β:Type u_2F:Type u_3V:Type u_4inst✝³:FunLike F α βinst✝²:TopologicalSpace αinst✝¹:Zero βinst✝:Zero Vf:F → V⊢ IsClosed (dsupport f)
grind [dsupport, isClosed_sInter]All goals completed! 🐙
more algorithms for the order of picking patterns, e.g. ←, ⇒ and ⇐
modifiers for specific types of theorems: e.g. inj, ext
read the Language Reference!
grind!minimally indexable: subexpression that has no indexable subexpression (up to some priorities)
grind! only picks minimal indexable subexpressions as patterns
grind! Exampledef f (a : Nat) : Nat :=
a + 1
def g (a : Nat) : Nat :=
a - 1
@[grind? .gf: [g (f #0)]]
theorem gf (x : Nat) : g (f x) = x := byx:ℕ⊢ g (f x) = x
simp [f, g]All goals completed! 🐙
example {a b c : ℕ} (h₁ : f b = a)
(h₂ : f c = a) : b = c := bya:ℕb:ℕc:ℕh₁:f b = ah₂:f c = a⊢ b = c
grindAll goals completed! 🐙`grind` failed
a b c:ℕh₁:f b = ah₂:f c = ah:¬b = c⊢ False
[grind] Goal diagnostics
[facts] Asserted facts
- [prop] f b = a
- [prop] f c = a
- [prop] ¬b = c
[eqc] False propositions
- [prop] b = c
[eqc] Equivalence classes
- [eqc] {a, f b, f c}
grind! Example@[grind!? .gf': [f #0]]
theorem gf' (x : Nat) : g (f x) = x := byx:ℕ⊢ g (f x) = x
simp [f, g]All goals completed! 🐙
example {a b c : ℕ} (h₁ : f b = a)
(h₂ : f c = a) : b = c := bya:ℕb:ℕc:ℕh₁:f b = ah₂:f c = a⊢ b = c
grindAll goals completed! 🐙
can be specified with grind_pattern
theorem mul_left_iff {M : Type*}
[Monoid M] {a b : M} (ha : IsUnit a) :
IsUnit (a * b) ↔ IsUnit b :=
show IsUnit (ha.unit * b) ↔ _ byAll goals completed! 🐙
simp [-IsUnit.unit_spec]All goals completed! 🐙
grind_pattern mul_left_iff =>
IsUnit a, IsUnit (a * b)
@[simp]
theorem count_false_add_count_true'
(l : List Bool) :
count false l + count true l = length l :=
count_not_add_count l true
grind_pattern count_false_add_count_true' =>
count false l
grind_pattern count_false_add_count_true' =>
count true l
Tagging with @[grind] gives suggestions.
@[grindTry these:
[apply] [grind =] for pattern: [g (f #0)]
[apply] [grind! .] for pattern: [f #0]]
theorem gf'' (x : Nat) : g (f x) = x := byx:ℕ⊢ g (f x) = x
simp [f, g]All goals completed! 🐙
bad tagging can make grind very slow
see instantiated theorems with trace.grind.ematch.instance
loops in tagging can be caught with #grind_lint
attribute [grind =] List.reverse_flatMap
List.flatMap_reverse
#grind_lint inspect List.reverse_flatMapTry this to display the actual theorem instances:
[apply] set_option trace.grind.ematch.instance true in
#grind_lint inspect List.reverse_flatMapinstantiating `List.reverse_flatMap` triggers 22 additional `grind` theorem instantiations
set_option trace.grind.ematch.instance true in
#grind_lint inspect List.reverse_flatMap[grind.ematch.instance] reverse_flatMap: (flatMap f l).reverse = flatMap (reverse ∘ f) l.reverse[grind.ematch.instance] flatMap_reverse: flatMap (reverse ∘ f) l.reverse = (flatMap (reverse ∘ reverse ∘ f) l).reverse[grind.ematch.instance] flatMap_def: flatMap (reverse ∘ f) l.reverse = (List.map (reverse ∘ f) l.reverse).flatten[grind.ematch.instance] flatMap_def: flatMap f l = (List.map f l).flatten[grind.ematch.instance] reverse_flatMap: (flatMap (reverse ∘ reverse ∘ f) l).reverse = flatMap (reverse ∘ reverse ∘ reverse ∘ f) l.reverse[grind.ematch.instance] flatMap_def: flatMap (reverse ∘ reverse ∘ f) l = (List.map (reverse ∘ reverse ∘ f) l).flatten[grind.ematch.instance] flatMap_reverse: flatMap (reverse ∘ reverse ∘ reverse ∘ f) l.reverse =
(flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l).reverse[grind.ematch.instance] flatMap_def: flatMap (reverse ∘ reverse ∘ reverse ∘ f) l.reverse =
(List.map (reverse ∘ reverse ∘ reverse ∘ f) l.reverse).flatten[grind.ematch.instance] reverse_flatMap: (flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l).reverse =
flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l.reverse[grind.ematch.instance] flatMap_def: flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l =
(List.map (reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l).flatten[grind.ematch.instance] flatMap_reverse: flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l.reverse =
(flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l).reverse[grind.ematch.instance] flatMap_def: flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l.reverse =
(List.map (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l.reverse).flatten[grind.ematch.instance] reverse_flatMap: (flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l).reverse =
flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l.reverse[grind.ematch.instance] flatMap_def: flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l =
(List.map (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l).flatten[grind.ematch.instance] flatMap_reverse: flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l.reverse =
(flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l).reverse[grind.ematch.instance] flatMap_def: flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l.reverse =
(List.map (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l.reverse).flatten[grind.ematch.instance] reverse_flatMap: (flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f)
l).reverse =
flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l.reverse[grind.ematch.instance] flatMap_def: flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l =
(List.map (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l).flatten[grind.ematch.instance] flatMap_reverse: flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f)
l.reverse =
(flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f)
l).reverse[grind.ematch.instance] flatMap_def: flatMap (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f)
l.reverse =
(List.map (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f)
l.reverse).flatten[grind.ematch.instance] reverse_flatMap: (flatMap
(reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f)
l).reverse =
flatMap
(reverse ∘
reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f)
l.reverse[grind.ematch.instance] flatMap_def: flatMap
(reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f) l =
(List.map (reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ reverse ∘ f)
l).flatteninstantiating `List.reverse_flatMap` triggers 22 additional `grind` theorem instantiations
Mathlib performs the following check:
#grind_lint check (min := 20) in module Mathlib
this gives
instantiating `Set.Icc.convexComb_symm` triggers 24 additional `grind` theorem instantiations instantiating `Path.symm_apply` triggers 24 additional `grind` theorem instantiations
grind_pattern reverse_flatMap => (l.flatMap f).reverse where
f =/= List.reverse ∘ _
grind_pattern flatMap_reverse => l.reverse.flatMap f where
f =/= List.reverse ∘ _
simp lemmas should usually also be tagged with grind =
exceptions: lemmas that introduce case distinctions on the right side
when a grind call fails despite you thinking that it shouldn't
identify missing tags either manually or using grind +suggestions
@[simp]
theorem isAdjMatrix_adjMatrix' (α : Type*)
{V : Type*} (G : SimpleGraph V)
[DecidableRel G.Adj] [Zero α] [One α] :
(G.adjMatrix α).IsAdjMatrix where
zero_or_one := byα:Type u_1V:Type u_2G:SimpleGraph Vinst✝²:DecidableRel G.Adjinst✝¹:Zero αinst✝:One α⊢ ∀ (i j : V), SimpleGraph.adjMatrix α G i j = 0 ∨ SimpleGraph.adjMatrix α G i j = 1 grind? +suggestionsAll goals completed! 🐙Try these:
[apply] grind only [SimpleGraph.adjMatrix_apply, #8bc53232e5fd58f8]
[apply] grind only [SimpleGraph.adjMatrix_apply]
[apply] grind =>
instantiate only [SimpleGraph.adjMatrix_apply]
cases #8bc53232e5fd58f8
generation of hypothesis/conclusion: 0
generation of expression generated through e-matching: one higher than the highest of the terms used
set upper generation limit for expressions considered: grind (gen := n) (default: 8)
grind employs several satellite solvers to solve problems of a specific nature
For example:
cutsat for linear integer arithmetic
ring for algebraic expressions in rings
linarith for linear arithmetic problems not solved by cutsat
not the same as Mathlib tactics of same name
grind defines special instances that are used in the solvers
e.g. Lean.Grind.ToInt, Lean.Grind.CommRing
Mathlib derives these instances from the standard Mathlib notions
e.g. CommRing.toGrindCommRing
theorem exists_subset_or_subset_of_two_mul_lt_card''
{α : Type*} [DecidableEq α] {X Y : Finset α}
{n : ℕ} (hXY : 2 * n < #(X ∪ Y)) :
∃ C : Finset α,
n < #C ∧ (C ⊆ X ∨ C ⊆ Y) := byα:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)⊢ ∃ C, n < #C ∧ (C ⊆ X ∨ C ⊆ Y)
grind =>
have : #(X ∪ Y) = #X + #(Y \ X) := byα:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)h✝:∀ (a : Finset α), #a ≤ n ∨ ¬a ⊆ X ∧ ¬a ⊆ Y⊢ #(X ∪ Y) = #X + #(Y \ X)
grind?All goals completed! 🐙Try these:
[apply] grind only [usr card_union_add_card_inter, usr card_sdiff_add_card_inter]
[apply] grind =>
instantiate only [usr card_union_add_card_inter]
instantiate only [usr card_sdiff_add_card_inter]
instantiate only [usr card_union_add_card_inter]
ac
finishAll goals completed! 🐙
grind_pattern card_union_add_card_inter => #(s ∪ t), s ∩ t
grind_pattern card_union_add_card_inter => s ∪ t, #(s ∩ t)
grind_pattern card_union_add_card_inter => #(s ∪ t), #s
grind_pattern card_union_add_card_inter => #(s ∪ t), #t
grind_pattern card_union_add_card_inter => #(s ∩ t), #s
grind_pattern card_union_add_card_inter => #(s ∩ t), #t
grind_pattern card_sdiff_add_card_inter =>
#(s \ t), #(s ∩ t)
grind_pattern card_sdiff_add_card_inter => #(s \ t), #s
Some features of Lean's tactic language also work in the interactive mode.
For example: all_goals, <;> and have
grind-specific tactics
use satellite solvers explicitly by writing their name, e.g. ring, linarith
cases expects an anchor which can be chosen with cases?
@[simp] lemma forall_mem_not_eq {α : Type*} {s : Finset α}
{a : α} : (∀ b ∈ s, ¬ a = b) ↔ a ∉ s := byα:Type u_1s:Finset αa:α⊢ (∀ b ∈ s, ¬a = b) ↔ a ∉ s
grind =>
cases?α:Type u_1s:Finset αa:αh✝:(∀ b ∈ s, ¬a = b) = (a ∈ s)⊢ FalseTry these:
[apply] cases #d39b for
(∀ b ∈ s, ¬a = b) = (a ∈ s)
sorryAll goals completed! 🐙declaration uses `sorry`
grind-specific tactics
finish: instructs grind to finish the proof itself
finish?: provides a more detailed proof in tactic language
lemma two_mul_ediv_two_of_even' {n : ℤ} :
Even n → 2 * (n / 2) = n := byn:ℤ⊢ Even n → 2 * (n / 2) = n
grind =>
finish?All goals completed! 🐙Try these:
[apply] instantiate only [= even_iff]
[apply] finish only [= even_iff]
grind-specific tactics
instantiate: use e-matching to instantiate theorems and consider additionally provided theorems
theorem isAdjMatrix_adjMatrix''' (α : Type*) {V : Type*}
(G : SimpleGraph V) [DecidableRel G.Adj] [Zero α]
[One α] : (G.adjMatrix α).IsAdjMatrix where
zero_or_one := byα:Type u_1V:Type u_2G:SimpleGraph Vinst✝²:DecidableRel G.Adjinst✝¹:Zero αinst✝:One α⊢ ∀ (i j : V), SimpleGraph.adjMatrix α G i j = 0 ∨ SimpleGraph.adjMatrix α G i j = 1
-- Mathlib proof: `grind [SimpleGraph.adjMatrix_apply]`
grind =>
instantiate [SimpleGraph.adjMatrix_apply]α:Type u_1V:Type u_2G:SimpleGraph Vinst✝²:DecidableRel G.Adjinst✝¹:Zero αinst✝:One αi✝:Vj✝:Vleft✝:¬SimpleGraph.adjMatrix α G i✝ j✝ = 0right✝:¬SimpleGraph.adjMatrix α G i✝ j✝ = 1⊢ False
finishAll goals completed! 🐙
output specific parts of the typical error messages
for example: show_splits, show_state, show_true,show_asserted and show_eqcs
theorem exists_subset_or_subset_of_two_mul_lt_card''''
{α : Type*} [DecidableEq α] {X Y : Finset α}
{n : ℕ} (hXY : 2 * n < #(X ∪ Y)) :
∃ C : Finset α,
n < #C ∧ (C ⊆ X ∨ C ⊆ Y) := byα:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)⊢ ∃ C, n < #C ∧ (C ⊆ X ∨ C ⊆ Y)
grind =>
have : #(X ∪ Y) = #X + #(Y \ X)α:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)h✝:∀ (a : Finset α), #a ≤ n ∨ ¬a ⊆ X ∧ ¬a ⊆ Ythis:#(X ∪ Y) = #X + #(Y \ X)⊢ False
show_eqcsα:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)h✝:∀ (a : Finset α), #a ≤ n ∨ ¬a ⊆ X ∧ ¬a ⊆ Ythis:#(X ∪ Y) = #X + #(Y \ X)⊢ False[eqc] Equivalence classes
- [eqc] {#(X ∪ Y), #X + #(Y \ X)}
[eqc] others
- [eqc] {↑(#(X ∪ Y)), ↑(#X + #(Y \ X))}
finishAll goals completed! 🐙
Local grind
using grind locally, e.g. when setting up basic theory around a definition
for theorems: @[local grind *] and local grind_pattern
for definitions in the same file: grind +locals
grind sets
Using grind only in a certain situation, e.e. only when proving things about a specific object
/-- The `compactness` attribute is a custom grind-set specialized to prove that sets are compact. It is called by the `compactness` tactic. -/ register_grind_attr compactness /-- The `closedness` attribute is a custom grind-set specialized to prove that sets are closed. It is called by the `closedness` tactic. -/ register_grind_attr closedness
grind setsattribute [compactness .] isCompact_Icc
@[to_dual self, simp, closedness =]
theorem closure_Icc' {α : Type*} [TopologicalSpace α]
[Preorder α] [OrderClosedTopology α] (a b : α) :
closure (Set.Icc a b) = Set.Icc a b :=
isClosed_Icc.closure_eq
example : IsCompact <|
closure (Set.Icc (1 : ℝ) (3 : ℝ)) := by⊢ IsCompact (closure (Set.Icc 1 3))
grind only [compactness, closedness]All goals completed! 🐙
no real consensus yet
a few suggestions here
example {α} [CommSemiring α] (x y : α) :
(x + y) ^ 2 = x ^ 2 + 2 • x * y + y ^ 2 := byα:Type u_1inst✝:CommSemiring αx:αy:α⊢ (x + y) ^ 2 = x ^ 2 + 2 • x * y + y ^ 2
ringAll goals completed! 🐙
example {α} [CommSemiring α] (x y : α) :
(x + y) ^ 2 = x ^ 2 + 2 • x * y + y ^ 2 := byα:Type u_1inst✝:CommSemiring αx:αy:α⊢ (x + y) ^ 2 = x ^ 2 + 2 • x * y + y ^ 2
grindAll goals completed! 🐙
lemma eq_of_natDegree_lt_card_of_eval_eq {R} [CommRing R]
[IsDomain R] (p q : R[X]) {ι} [Fintype ι] {f : ι → R}
(hf : Function.Injective f)
(heval : ∀ i : ι, eval (f i) p = eval (f i) q)
(hcard : max p.natDegree q.natDegree < Fintype.card ι) :
p = q := byR:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ p = q
rw [← sub_eq_zeroR:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ p - q = 0]R:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ p - q = 0
apply eq_zero_of_natDegree_lt_card_of_eval_eq_zero _ hfR:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ ∀ (i : ι), eval (f i) (p - q) = 0R:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ (p - q).natDegree < Fintype.card ι
·R:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ ∀ (i : ι), eval (f i) (p - q) = 0 simpa [eval_sub, sub_eq_zero]All goals completed! 🐙
·R:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ (p - q).natDegree < Fintype.card ι grind [natDegree_sub_le]All goals completed! 🐙
lemma eq_of_natDegree_lt_card_of_eval_eq' {R} [CommRing R]
[IsDomain R] (p q : R[X]) {ι} [Fintype ι] {f : ι → R}
(hf : Function.Injective f)
(heval : ∀ i : ι, eval (f i) p = eval (f i) q)
(hcard : max p.natDegree q.natDegree < Fintype.card ι) :
p = q := byR:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ p = q
rw [← sub_eq_zeroR:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ p - q = 0]R:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ p - q = 0
apply eq_zero_of_natDegree_lt_card_of_eval_eq_zero _ hfR:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ ∀ (i : ι), eval (f i) (p - q) = 0R:Type u_1inst✝²:CommRing Rinst✝¹:IsDomain Rp:R[X]q:R[X]ι:Type u_2inst✝:Fintype ιf:ι → Rhf:Function.Injective fheval:∀ (i : ι), eval (f i) p = eval (f i) qhcard:max p.natDegree q.natDegree < Fintype.card ι⊢ (p - q).natDegree < Fintype.card ι
all_goals grind [eval_sub, sub_eq_zero, natDegree_sub_le]All goals completed! 🐙
theorem exists_subset_or_subset_of_two_mul_lt_card5
{α : Type*} [DecidableEq α] {X Y : Finset α} {n : ℕ}
(hXY : 2 * n < #(X ∪ Y)) :
∃ C : Finset α, n < #C ∧ (C ⊆ X ∨ C ⊆ Y) := byα:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)⊢ ∃ C, n < #C ∧ (C ⊆ X ∨ C ⊆ Y)
grind =>
have : #(X ∪ Y) = #X + #(Y \ X)α:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)h✝:∀ (a : Finset α), #a ≤ n ∨ ¬a ⊆ X ∧ ¬a ⊆ Ythis:#(X ∪ Y) = #X + #(Y \ X)⊢ False
finishAll goals completed! 🐙
theorem exists_subset_or_subset_of_two_mul_lt_card6
{α : Type*} [DecidableEq α] {X Y : Finset α} {n : ℕ}
(hXY : 2 * n < #(X ∪ Y)) :
∃ C : Finset α, n < #C ∧ (C ⊆ X ∨ C ⊆ Y) := byα:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)⊢ ∃ C, n < #C ∧ (C ⊆ X ∨ C ⊆ Y)
have : #(X ∪ Y) = #X + #(Y \ X) := by grindAll goals completed! 🐙α:Type u_1inst✝:DecidableEq αX:Finset αY:Finset αn:ℕhXY:2 * n < #(X ∪ Y)this:#(X ∪ Y) = #X + #(Y \ X)⊢ ∃ C, n < #C ∧ (C ⊆ X ∨ C ⊆ Y)
grindAll goals completed! 🐙
grind bugs: grind? doesn't produce working proofs
can make bumping projects harder
Mathlib lints against this (verifyGrindOnly) but doesn't change present offences
Slides: scholzhannah.de/GrindSlides
Write-up: scholzhannah.de/GrindWriteUp