Zulip Chat Archive

Stream: general

Topic: grind failures


Michael Stoll (Nov 27 2025 at 20:05):

(Maybe it is useful to have a thread that collects (perceived) failures of grind.)

I'm surprised that grind does not do the following :

import Mathlib

example {ε : } ( : 0 < ε) : 0 < ε / 2 := by
  try grind -- fails
  linarith -- works
  --  exact half_pos hε -- also works

(Even grind [half_pos] fails here.)

Here are some variants.

import Mathlib.Algebra.Order.Field.Basic

variable {R : Type} [Field R] [LinearOrder R] [IsStrictOrderedRing R]

lemma foo {ε : R} ( : 0 < ε) : 0 < ε / 2 := by
  try grind [half_pos] -- fails
  exact half_pos 

example {ε : R} ( : 0 < ε) : 0 < 2 * ε := by
  grind -- works

example {ε : R} ( : 0 < ε) : 0 < 2⁻¹ * ε := by
  try grind -- fails
  grind [Left.mul_pos] -- succeeds

example : (0 : R) < 2⁻¹ := by
  grind -- works

Michael Stoll (Nov 27 2025 at 20:15):

Another example showing that grind has problems with halves:

import Mathlib

example {a b x : } (ha : a < x / 2) (hb : b < x / 2) : a + b < x := by
  grind -- fails

Ilmārs Cīrulis (Nov 27 2025 at 22:03):

This works:

import Mathlib

example {a b x : } (ha : a < x / 2) (hb : b < x / 2) : a + b < x := by
  have h := add_halves x
  grind [add_lt_add]

But this doesn't:

import Mathlib

example {a b x : } (ha : a < x / 2) (hb : b < x / 2) : a + b < x := by
  grind [add_lt_add, add_halves]

Ilmārs Cīrulis (Nov 27 2025 at 22:04):

Not saying this is failure, more like a curiosity (for me).

Moritz Doll (Nov 28 2025 at 04:17):

Michael Stoll said:

(Maybe it is useful to have a thread that collects (perceived) failures of grind.)

I'm surprised that grind does not do the following :

import Mathlib

example {ε : } ( : 0 < ε) : 0 < ε / 2 := by
  try grind -- fails
  linarith -- works
  --  exact half_pos hε -- also works

(Even grind [half_pos] fails here.)

Here are some variants.

import Mathlib.Algebra.Order.Field.Basic

variable {R : Type} [Field R] [LinearOrder R] [IsStrictOrderedRing R]

lemma foo {ε : R} ( : 0 < ε) : 0 < ε / 2 := by
  try grind [half_pos] -- fails
  exact half_pos 

example {ε : R} ( : 0 < ε) : 0 < 2 * ε := by
  grind -- works

example {ε : R} ( : 0 < ε) : 0 < 2⁻¹ * ε := by
  try grind -- fails
  grind [Left.mul_pos] -- succeeds

example : (0 : R) < 2⁻¹ := by
  grind -- works

all of these can be solved by positivity (at least when you important enough of mathlib), so I don't know whether it is bad that grind fails

Kim Morrison (Nov 28 2025 at 04:48):

I think essentially all of these were fixed a few days ago. Can you try them again on nightly-testing-green and report which, if any, still fail?

Michael Stoll (Nov 28 2025 at 10:37):

Kim Morrison said:

Can you try them again on nightly-testing-green

On live.lean-lang.org with "Lean Nightly":

example {ε : Rat} ( : 0 < ε) : 0 < ε / 2 := by grind

example {ε : Rat} ( : 0 < ε) : 0 < 2⁻¹ * ε := by grind

example {a b x : Rat} (ha : a < x / 2) (hb : b < x / 2) : a + b < x := by grind

all work (but with "Latest Mathlib", they fail).

Kim Morrison (Nov 28 2025 at 11:10):

Great! These features won't be available with "Latest Mathlib" until mid-December.

Michael Stoll (Dec 03 2025 at 20:00):

Here is another example that I expected grind to solve.

import Mathlib

open Real

example {t : } (ht : 0 < t) : -2 * t < 0 := by
  grind -- works

example {t : } (ht : 0 < t) : -2 * π * t < 0 := by
  grind -- does not work

example {t : } (ht : 0 < t) : -2 * π * t < 0 := by
  grind [Real.pi_pos] -- does not work

example {t : } (ht : 0 < t) : -2 * π * t < 0 := by
  have := pi_pos
  grind -- does not work

-- the original problem
example {h t : } (hh : 0 < h) (ht : 0 < t) : -2 * π * t / h < 0 := by
  grind -- does not work

-- simplify by removing `π`
example {h t : } (hh : 0 < h) (ht : 0 < t) : -2 * t / h < 0 := by
  grind -- does not work

example {h t : } (hh : 0 < h) (ht : 0 < t) : 0 < t / h := by
  grind [div_pos] -- does not work

example {h t : } (hh : 0 < h) (ht : 0 < t) : 0 < h * t := by
  grind [mul_pos] -- works (but doesn't without `[mul_pos]`)

Jovan Gerbscheid (Dec 05 2025 at 17:44):

Somehow I expected grind to solve this:

example (f : Prop  Nat) (p q : Prop) : f (p  q) = f (q  p) := by grind

Snir Broshi (Dec 05 2025 at 19:17):

#new members > Weirdness with cutsat
#lean4 > possible grind regression?
#mathlib4>simp only + grind failure

Kim Morrison (Dec 08 2025 at 03:45):

Michael Stoll said:

Here is another example that I expected grind to solve.

import Mathlib

open Real

example {t : } (ht : 0 < t) : -2 * t < 0 := by
  grind -- works

example {t : } (ht : 0 < t) : -2 * π * t < 0 := by
  grind -- does not work

example {t : } (ht : 0 < t) : -2 * π * t < 0 := by
  grind [Real.pi_pos] -- does not work

example {t : } (ht : 0 < t) : -2 * π * t < 0 := by
  have := pi_pos
  grind -- does not work

-- the original problem
example {h t : } (hh : 0 < h) (ht : 0 < t) : -2 * π * t / h < 0 := by
  grind -- does not work

-- simplify by removing `π`
example {h t : } (hh : 0 < h) (ht : 0 < t) : -2 * t / h < 0 := by
  grind -- does not work

example {h t : } (hh : 0 < h) (ht : 0 < t) : 0 < t / h := by
  grind [div_pos] -- does not work

example {h t : } (hh : 0 < h) (ht : 0 < t) : 0 < h * t := by
  grind [mul_pos] -- works (but doesn't without `[mul_pos]`)

There is no non-linear arithmetic in grind yet.

Bhavik Mehta (Dec 08 2025 at 11:12):

Should mul_pos become a grind lemma? Should we add annotations to make the penultimate example work too?

Michael Stoll (Dec 08 2025 at 11:18):

Kim Morrison said:

There is no non-linear arithmetic in grind yet.

Fair enough. But:

example {t : } (ht : 0 < t) : -2 * π * t < 0 := by
  grind -- does not work

I would consider this a linear problem (π is a constant).

example {h t : } (hh : 0 < h) (ht : 0 < t) : 0 < t / h := by
  grind [div_pos] -- does not work

example {h t : } (hh : 0 < h) (ht : 0 < t) : 0 < h * t := by
  grind [mul_pos] -- works

Why does the second work, but the first does not?
I'm willing to help grind along, but right now I find it hard to predict what will work.

In any case, are there plans to extend the scope of grind so that it eventually will be able to solve goals like this? (The word "yet" in your message gives me some hope...)

Michael Stoll (Dec 08 2025 at 11:39):

On a more positive note, I'm trying to use grind whenever possible to deal with "obvious" stuff, and I find that it already works quite well. E.g., in my formalization of the Conditional Convergence Theorem, it is used 80 times, about once every 8 lines (including documentation and comments).

Michael Stoll (Dec 08 2025 at 17:02):

Another surprise: #lean4 > grind needs `sub_eq_zero` @ 💬 (where one has to tell grind explicitly to use one_mul).

Michael Stoll (Dec 08 2025 at 22:12):

import Mathlib

example (n : ) : 0 < 2 ^ n := by grind -- fails

Bhavik Mehta (Dec 09 2025 at 00:51):

I often add Nat.pow_pos to grind in projects, with a pattern, to help with this last case:

grind_pattern Nat.pow_pos => a ^ n

example (n : Nat) : 0 < 2 ^ n := by grind

Kim Morrison (Dec 09 2025 at 07:41):

Yes, we should definitely add this with the isValue guard on a.

Kim Morrison (Dec 09 2025 at 07:41):

I don't think in this case it makes sense to think of π as a constant. :-)

Kim Morrison (Dec 09 2025 at 07:42):

Yes, working out how to do non-linear arithmetic with grind, through some combination of annotations, internally defined propagators, and perhaps new grind modules, is on the roadmap for next quarter.

Bhavik Mehta (Dec 09 2025 at 13:02):

Actually, on Lean nightly Michael's last example already succeeds, because there's a pattern for Nat.pow_pos (which is more reasonable than mine) there already

Michael Stoll (Feb 02 2026 at 18:41):

Would it be reasonable to make docs#zero_le_one a grind lemma?
I noticed this gap in a context like this:

import Mathlib

example {ι M : Type*} [DecidableEq ι] [Zero M] [One M] [LinearOrder M] [ZeroLEOneClass M] (i j : ι) {x : M} (h : 0  x) :
    0  if i = j then x else 1 := by
  -- grind -- fails
  grind [zero_le_one]

Jovan Gerbscheid (Feb 02 2026 at 18:45):

The following works if you use Fin n instead of Fin (n + 1):

example (a b n : Nat) (ha : a < n + 1) (hb : b < n + 1) :
    (a, ha : Fin (n + 1)) < b, hb  a < b := by
  grind

Michael Stoll (Feb 02 2026 at 18:46):

Is this supposed to answer my question?

Jovan Gerbscheid (Feb 02 2026 at 18:47):

No, sorry, just another grind failure (you reminded me that I wanted to post this one)

Jovan Gerbscheid (Feb 02 2026 at 18:53):

Michael Stoll said:

Would it be reasonable to make docs#zero_le_one a grind lemma?

grind knows this as long as it can find a Lean.Grind.OrderedRing instance. Are there other cases where we want grind to know this?

Michael Stoll (Feb 02 2026 at 18:55):

See the example above. I was trying to make a lemma as general as possible (which was originally stated for real numbers), which made grind fail at some place in the proof, where it was working before.

Jovan Gerbscheid (Feb 02 2026 at 19:06):

But how do you plan to make it work with transitivity? This doesn't seem to work:

example {M : Type*} [Zero M] [One M] [LinearOrder M] [ZeroLEOneClass M] {x y : M} (h : 1  x) (h' : y  0) :
    y  x := by
  -- grind -- fails
  grind [zero_le_one]

Michael Stoll (Feb 02 2026 at 19:11):

This works:

import Mathlib

example {M : Type*} [Zero M] [One M] [LinearOrder M] [ZeroLEOneClass M] {x y : M} (h : 1  x) (h' : y  0) :
    y  x := by
  grind [zero_le_one' M]

(but not as grind [zero_le_one'] without instantiating the type :frown:)

Bhavik Mehta (Feb 02 2026 at 19:23):

Jovan Gerbscheid said:

The following works if you use Fin n instead of Fin (n + 1):

example (a b n : Nat) (ha : a < n + 1) (hb : b < n + 1) :
    (a, ha : Fin (n + 1)) < b, hb  a < b := by
  grind

lean4#12245

Kim Morrison (Feb 02 2026 at 21:03):

Michael Stoll said:

Would it be reasonable to make docs#zero_le_one a grind lemma?
I noticed this gap in a context like this:

import Mathlib

example {ι M : Type*} [DecidableEq ι] [Zero M] [One M] [LinearOrder M] [ZeroLEOneClass M] (i j : ι) {x : M} (h : 0  x) :
    0  if i = j then x else 1 := by
  -- grind -- fails
  grind [zero_le_one]

grind isn't really intended to do algebra outside of the situations where it can do it properly. So no, we generally don't want to tag lemmas that enable grind to "brute force" algebra/inequality proofs that it could otherwise do with "reasonable" typeclass hypotheses. If you want to do this, use local/scoped/namespaces attributes, or just pass the lemmas to grind at the call site.

Andrew Yang (Feb 04 2026 at 10:49):

I am really surprised that grind cannot do this. Am I missing something?

import Mathlib

/-- failed -/
#guard_msgs (substring := true) in
example (n m : ) (hn : 0 < n) : m < (m + 2) * n := by grind

Snir Broshi (Feb 04 2026 at 11:12):

I guess that multiplication by a variable isn't linear so lia can't work it out without cases n first

Snir Broshi (Feb 04 2026 at 11:17):

I tried to trigger grinds case-splitting but ended up finding a bug:

/-- error: unknown metavariable `?_uniq.893` -/
#guard_msgs in
example (n m : Nat) (hn : n  0) (h : match n with | .zero | .succ _ => 1 = 1) : m  m * n := by
  grind

Pim Otte (Feb 16 2026 at 07:16):

I'm not entirely sure if this is in scope for grind, but it would be really nice to be able to do this kind of stuff:

Demathlibified:

theorem test {f : R  R} {P : R  Prop} {LE' : R  R  Prop}:
  ( ib,  ia,  x, LE' x ia  P x  LE' (f x) ib)   M,  z,  x, P x  LE' x z  LE' (f x) M := by
  grind

Closer to my actual usecase:

import Mathlib

theorem test {f :   } {D : Set } :
  ( (ib : ),  ia,  x  ia, x  D  f x  ib)   (M : ),  z,  x  D, x  z  f x  M := by
  grind

Kim Morrison (Feb 16 2026 at 10:51):

Andrew Yang said:

I am really surprised that grind cannot do this. Am I missing something?

import Mathlib

/-- failed -/
#guard_msgs (substring := true) in
example (n m : ) (hn : 0 < n) : m < (m + 2) * n := by grind

grind doesn't do any non-linear inequalities (yet)

Kim Morrison (Feb 16 2026 at 10:51):

Pim Otte said:

I'm not entirely sure if this is in scope for grind, but it would be really nice to be able to do this kind of stuff:

Demathlibified:

theorem test {f : R  R} {P : R  Prop} {LE' : R  R  Prop}:
  ( ib,  ia,  x, LE' x ia  P x  LE' (f x) ib)   M,  z,  x, P x  LE' x z  LE' (f x) M := by
  grind

Closer to my actual usecase:

import Mathlib

theorem test {f :   } {D : Set } :
  ( (ib : ),  ia,  x  ia, x  D  f x  ib)   (M : ),  z,  x  D, x  z  f x  M := by
  grind

grind typically won't solve problems that require picking witnesses for existentials.

Jovan Gerbscheid (Feb 16 2026 at 10:57):

I was very surprised to see grind succeed on this:

import Mathlib

lemma LT.lt.exists_disjoint_Iio_Ioi' [LinearOrder α] (a b : α) (h : a < b) :
     a' > a,  b' < b,  x < a',  y > b', x < y := by
  grind only

How does this work?

Pim Otte (Feb 16 2026 at 11:00):

Kim Morrison said:

...

grind typically won't solve problems that require picking witnesses for existentials.

Just to double-check, the thing with this example is that it doesn't require picking a witness (since "the same choice" works). The following does work:

import Mathlib

theorem test {f :   } {D : Set } :
  ( (ib : ),  ia,  x  ia, x  D  f x  ib)   (M : ),  z,  x  D, x  z  f x  M := by
  refine forall_congr' fun M  ?_
  refine exists_congr ?_
  grind

(Still totally understand if this is out-of-scope for grind, I can imagine this is an edge case)

Michael Stoll (Feb 16 2026 at 11:06):

Perhaps it would make sense to add these two points (that seem to come up fairly regularly here) to the documentation of grind?

Violeta Hernández (Feb 20 2026 at 10:03):

Jovan Gerbscheid ha dicho:

How does this work?

My guess is that it's reproducing this proof:

lemma LT.lt.exists_disjoint_Iio_Ioi' {α} [LinearOrder α] (a b : α) (h : a < b) :
     a' > a,  b' < b,  x < a',  y > b', x < y := by
  by_contra! H
  obtain c, hc, d, hd, hdc := H b h a h
  obtain e, he, f, hf, hef := H d hd c hc
  order

Violeta Hernández (Feb 20 2026 at 10:12):

If you do want to prove this manually, a less mystical proof might be to case on whether Ioo a b is empty. If a < x < b for some x then a' = b' = x satisfies the property. Otherwise, b' = a and a' = b does.

Jovan Gerbscheid (Feb 20 2026 at 10:14):

Yes, that's the proof I imagined as well.

Jovan Gerbscheid (Feb 20 2026 at 12:01):

I encountered the following in #35563

variable {α : Type} [Lean.Grind.IntModule α] [LE α] [LT α] [Std.IsPreorder α] [Std.LawfulOrderLT α]

example (a : α) : 0 < a  a  0 := by
  grind -- `grind linarith` internal error, structure is not a preorder

Jovan Gerbscheid (Feb 20 2026 at 15:04):

In the same PR, I encountered a panic (an assertion violation) in grind (https://github.com/leanprover-community/mathlib4/pull/35563#issuecomment-3935035240). I'm not sure how to go about debugging this because the panic only happened once, in CI, and after re-running, CI is happy now. It's also not clear wether this panic actually has anything to do with the changes of this PR.

I remember in the early days of grind there were problems with grind being inconsisted due to the use of pointer cache. Could it be that this problem still hasn't fully been resolved?

Chris Henson (Feb 20 2026 at 15:14):

This same error appears sporadically in a particular module of CSLib, which at least suggests to me that certain kinds of proofs trigger this panic. I first reported this at #lean4 > PANIC at _private.Lean.Meta.Tactic.Grind.Proof, which links to some earlier occurrences.


Last updated: Feb 28 2026 at 14:05 UTC