Skip to content

Commit 40e1e09

Browse files
authored
fix: grind order nontermination and propagation issues (#11026)
This PR fixes a nontermination and missing propagation bug in `grind order`. It also register relevant case-splits for arithmetic. Closes #11001
1 parent d7e4f32 commit 40e1e09

File tree

10 files changed

+346
-12
lines changed

10 files changed

+346
-12
lines changed

src/Init/Grind/Order.lean

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,30 @@ theorem le_eq_true_of_lt {α} [LE α] [LT α] [Std.LawfulOrderLT α]
206206
simp; intro h
207207
exact Std.le_of_lt h
208208

209+
theorem le_eq_true {α} [LE α] [Std.IsPreorder α]
210+
{a : α} : (a ≤ a) = True := by
211+
simp; exact Std.le_refl a
212+
213+
theorem le_eq_true_k {α} [LE α] [LT α] [Std.LawfulOrderLT α] [Std.IsPreorder α] [Ring α] [OrderedRing α]
214+
{a : α} {k : Int} : (0 : Int).ble' k → (a ≤ a + k) = True := by
215+
simp
216+
intro h
217+
replace h := OrderedRing.nonneg_intCast_of_nonneg (R := α) _ h
218+
have h₁ := Std.le_refl a
219+
replace h₁ := OrderedAdd.add_le_add h₁ h
220+
simp [Semiring.add_zero] at h₁
221+
assumption
222+
223+
theorem lt_eq_true_k {α} [LE α] [LT α] [Std.LawfulOrderLT α] [Std.IsPreorder α] [Ring α] [OrderedRing α]
224+
{a : α} {k : Int} : (0 : Int).blt' k → (a < a + k) = True := by
225+
simp
226+
intro h
227+
replace h := OrderedRing.pos_intCast_of_pos (R := α) _ h
228+
have h₁ := Std.le_refl a
229+
replace h₁ := add_lt_add_of_le_of_lt h₁ h
230+
simp [Semiring.add_zero] at h₁
231+
assumption
232+
209233
theorem le_eq_true_of_le_k {α} [LE α] [LT α] [Std.LawfulOrderLT α] [Std.IsPreorder α] [Ring α] [OrderedRing α]
210234
{a b : α} {k₁ k₂ : Int} : k₁.ble' k₂ → a ≤ b + k₁ → (a ≤ b + k₂) = True := by
211235
simp; intro h₁ h₂
@@ -247,6 +271,38 @@ theorem lt_eq_true_of_le_k {α} [LE α] [LT α] [Std.LawfulOrderLT α] [Std.IsPr
247271

248272
/-! Theorems for propagating constraints to `False` -/
249273

274+
theorem lt_eq_false {α} [LE α] [LT α] [Std.LawfulOrderLT α]
275+
{a : α} : (a < a) = False := by
276+
simp; intro h
277+
have := Preorder.lt_irrefl a
278+
contradiction
279+
280+
theorem le_eq_false_k {α} [LE α] [LT α] [Std.LawfulOrderLT α] [Std.IsPreorder α] [Ring α] [OrderedRing α]
281+
{a : α} {k : Int} : k.blt' 0 → (a ≤ a + k) = False := by
282+
simp
283+
intro h
284+
replace h := OrderedRing.neg_intCast_of_neg (R := α) _ h
285+
have h₁ := Std.le_refl a
286+
replace h₁ := add_lt_add_of_le_of_lt h₁ h
287+
simp [Semiring.add_zero] at h₁
288+
intro h
289+
have := Std.lt_of_lt_of_le h₁ h
290+
have := Preorder.lt_irrefl (a + k)
291+
contradiction
292+
293+
theorem lt_eq_false_k {α} [LE α] [LT α] [Std.LawfulOrderLT α] [Std.IsPreorder α] [Ring α] [OrderedRing α]
294+
{a : α} {k : Int} : k.ble' 0 → (a < a + k) = False := by
295+
simp
296+
intro h
297+
replace h := OrderedRing.nonpos_intCast_of_nonpos (R := α) _ h
298+
have h₁ := Std.le_refl a
299+
replace h₁ := OrderedAdd.add_le_add h₁ h
300+
simp [Semiring.add_zero] at h₁
301+
intro h
302+
have := Std.lt_of_le_of_lt h₁ h
303+
have := Preorder.lt_irrefl (a + k)
304+
contradiction
305+
250306
theorem le_eq_false_of_lt {α} [LE α] [LT α] [Std.LawfulOrderLT α] [Std.IsPreorder α]
251307
{a b : α} : a < b → (b ≤ a) = False := by
252308
simp; intro h₁ h₂

src/Init/Grind/Ordered/Ring.lean

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ theorem pos_intCast_of_pos (a : Int) : 0 < a → 0 < (a : R) := by
189189
assumption
190190
next => omega
191191

192+
theorem neg_intCast_of_neg (a : Int) : a < 0 → (a : R) < 0 := by
193+
intro h
194+
have h : 0 < -a := by omega
195+
replace h := pos_intCast_of_pos (R := R) _ h
196+
simp [Ring.intCast_neg, OrderedAdd.neg_pos_iff] at h
197+
assumption
198+
192199
theorem nonneg_intCast_of_nonneg (a : Int) : 0 ≤ a → 0 ≤ (a : R) := by
193200
cases a
194201
next n =>
@@ -198,6 +205,13 @@ theorem nonneg_intCast_of_nonneg (a : Int) : 0 ≤ a → 0 ≤ (a : R) := by
198205
assumption
199206
next => omega
200207

208+
theorem nonpos_intCast_of_nonpos (a : Int) : a ≤ 0 → (a : R) ≤ 0 := by
209+
intro h
210+
have h : 0 ≤ -a := by omega
211+
replace h := nonneg_intCast_of_nonneg (R := R) _ h
212+
simp [Ring.intCast_neg, OrderedAdd.neg_nonneg_iff] at h
213+
assumption
214+
201215
instance [Ring R] [LE R] [LT R] [LawfulOrderLT R] [IsPreorder R] [OrderedRing R] :
202216
IsCharP R 0 := IsCharP.mk' _ _ <| by
203217
intro x
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/-
2+
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Leonardo de Moura
5+
-/
6+
module
7+
prelude
8+
public import Lean.Meta.Tactic.Grind.Types
9+
import Lean.Meta.Tactic.Grind.Arith.Util
10+
import Lean.Meta.Tactic.Grind.Arith.Cutsat.ToInt
11+
import Lean.Meta.Tactic.Grind.Arith.Linear.StructId
12+
public section
13+
namespace Lean.Meta.Grind.Arith
14+
15+
def isSupportedType (α : Expr) : GoalM Bool := do
16+
if isNatType α || isIntType α then
17+
return true
18+
else if (← Cutsat.getToIntId? α).isSome then
19+
return true
20+
else if (← Linear.getStructId? α).isSome then
21+
return true
22+
else
23+
return false
24+
25+
partial def isRelevantPred (e : Expr) : GoalM Bool :=
26+
match_expr e with
27+
| Not p => isRelevantPred p
28+
| LE.le α _ _ _ => isSupportedType α
29+
| LT.lt α _ _ _ => isSupportedType α
30+
| Eq α _ _ => isSupportedType α
31+
| Dvd.dvd α _ _ _ => isSupportedType α
32+
| _ => return false
33+
34+
end Lean.Meta.Grind.Arith

src/Lean/Meta/Tactic/Grind/Arith/Util.lean

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,6 @@ def isNatNum? (e : Expr) : Option Nat := Id.run do
7878
let .lit (.natVal k) := k | none
7979
some k
8080

81-
def isSupportedType (e : Expr) : Bool :=
82-
isNatType e || isIntType e
83-
84-
partial def isRelevantPred (e : Expr) : Bool :=
85-
match_expr e with
86-
| Not p => isRelevantPred p
87-
| LE.le α _ _ _ => isSupportedType α
88-
| Eq α _ _ => isSupportedType α
89-
| Dvd.dvd α _ _ _ => isSupportedType α
90-
| _ => false
91-
9281
def isArithTerm (e : Expr) : Bool :=
9382
match_expr e with
9483
| HAdd.hAdd _ _ _ _ _ _ => true

src/Lean/Meta/Tactic/Grind/Internalize.lean

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module
77
prelude
88
public import Lean.Meta.Tactic.Grind.Types
99
import Lean.Meta.Tactic.Grind.Arith.Cutsat.Types
10+
import Lean.Meta.Tactic.Grind.Arith.IsRelevant
1011
import Lean.Meta.Match.MatchEqs
1112
import Lean.Meta.Tactic.Grind.Util
1213
import Lean.Meta.Tactic.Grind.Beta
@@ -120,7 +121,7 @@ private def checkAndAddSplitCandidate (e : Expr) : GoalM Unit := do
120121
if (← getConfig).splitImp then
121122
if (← isProp d) then
122123
addSplitCandidate (.imp e (h ▸ rfl) currSplitSource)
123-
else if Arith.isRelevantPred d then
124+
else if (← Arith.isRelevantPred d) then
124125
-- TODO: should we keep lookahead after we implement non-chronological backtracking?
125126
if (← getConfig).lookahead then
126127
addLookaheadCandidate (.imp e (h ▸ rfl) currSplitSource)

src/Lean/Meta/Tactic/Grind/Order/Assert.lean

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ public def propagateEqTrue (c : Cnstr NodeId) (e : Expr) (u v : NodeId) (k k' :
8181
else
8282
pushEqTrue e h
8383

84+
public def propagateSelfEqTrue (c : Cnstr NodeId) (e : Expr) : OrderM Unit := do
85+
let u ← getExpr c.u
86+
assert! c.u == c.v
87+
let mut h ← mkPropagateSelfEqTrueProof u c.getWeight
88+
if let some he := c.h? then
89+
h := mkApp4 (mkConst ``Grind.Order.eq_trans_true) e c.e he h
90+
if let some (e', he) := (← get').cnstrsMapInv.find? { expr := e } then
91+
h := mkApp4 (mkConst ``Grind.Order.eq_trans_true) e' e he h
92+
pushEqTrue e' h
93+
else
94+
pushEqTrue e h
95+
8496
public def propagateEqFalse (c : Cnstr NodeId) (e : Expr) (u v : NodeId) (k k' : Weight) : OrderM Unit := do
8597
let kuv ← mkProofForPath u v
8698
let u ← getExpr u
@@ -94,6 +106,18 @@ public def propagateEqFalse (c : Cnstr NodeId) (e : Expr) (u v : NodeId) (k k' :
94106
else
95107
pushEqFalse e h
96108

109+
public def propagateSelfEqFalse (c : Cnstr NodeId) (e : Expr) : OrderM Unit := do
110+
let u ← getExpr c.u
111+
assert! c.u == c.v
112+
let mut h ← mkPropagateSelfEqFalseProof u c.getWeight
113+
if let some he := c.h? then
114+
h := mkApp4 (mkConst ``Grind.Order.eq_trans_false) e c.e he h
115+
if let some (e', he) := (← get').cnstrsMapInv.find? { expr := e } then
116+
h := mkApp4 (mkConst ``Grind.Order.eq_trans_false) e' e he h
117+
pushEqFalse e' h
118+
else
119+
pushEqFalse e h
120+
97121
/-- Propagates all pending constraints and equalities and resets to "to do" list. -/
98122
def propagatePending : OrderM Unit := do
99123
let todo := (← getStruct).propagate
@@ -199,6 +223,10 @@ node pairs.
199223
-/
200224
def addEdge (u : NodeId) (v : NodeId) (k : Weight) (h : Expr) : OrderM Unit := do
201225
if (← isInconsistent) then return ()
226+
if u == v then
227+
if k.isNeg then
228+
closeGoal (← mkSelfUnsatProof (← getExpr u) k h)
229+
return ()
202230
trace[grind.debug.order.add_edge] "{← getExpr u}, {← getExpr v}, {k}"
203231
if let some k' ← getDist? v u then
204232
if (k + k').isNeg then

src/Lean/Meta/Tactic/Grind/Order/Internalize.lean

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ def internalizeCnstr (e : Expr) (kind : CnstrKind) (lhs rhs : Expr) : OrderM Uni
184184
let v ← mkNode c.v
185185
let c := { c with u, v }
186186
let k' := c.getWeight
187+
if u == v then
188+
if k'.isNeg then
189+
propagateSelfEqFalse c e
190+
else
191+
propagateSelfEqTrue c e
192+
return ()
187193
if let some k ← getDist? u v then
188194
if k ≤ k' then
189195
propagateEqTrue c e u v k k'

src/Lean/Meta/Tactic/Grind/Order/Proof.lean

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,28 @@ public def mkPropagateEqTrueProof (u v : Expr) (k : Weight) (huv : Expr) (k' : W
153153
else
154154
mkPropagateEqTrueProofCore u v k huv k'
155155

156+
def mkPropagateSelfEqTrueProofOffset (u : Expr) (k : Weight) : OrderM Expr := do
157+
let declName := match k.strict with
158+
| false => ``Grind.Order.le_eq_true_k
159+
| true => ``Grind.Order.lt_eq_true_k
160+
let h ← mkOrdRingPrefix declName
161+
return mkApp3 h u (toExpr k.k) eagerReflBoolTrue
162+
163+
def mkPropagateSelfEqTrueProofCore (u : Expr) : OrderM Expr := do
164+
let h ← mkLePreorderPrefix ``Grind.Order.le_eq_true
165+
return mkApp h u
166+
167+
/--
168+
Constructs a proof of `e = True` where `e` is a term corresponding to the edge `u --(k) --> u`
169+
with `k` non-negative
170+
-/
171+
public def mkPropagateSelfEqTrueProof (u : Expr) (k : Weight) : OrderM Expr := do
172+
if (← isRing) then
173+
mkPropagateSelfEqTrueProofOffset u k
174+
else
175+
assert! !k.strict
176+
mkPropagateSelfEqTrueProofCore u
177+
156178
/--
157179
`u < v → (v ≤ u) = False
158180
-/
@@ -184,6 +206,50 @@ public def mkPropagateEqFalseProof (u v : Expr) (k : Weight) (huv : Expr) (k' :
184206
else
185207
mkPropagateEqFalseProofCore u v k huv k'
186208

209+
def mkPropagateSelfEqFalseProofOffset (u : Expr) (k : Weight) : OrderM Expr := do
210+
let declName := match k.strict with
211+
| false => ``Grind.Order.le_eq_false_k
212+
| true => ``Grind.Order.lt_eq_false_k
213+
let h ← mkOrdRingPrefix declName
214+
return mkApp3 h u (toExpr k.k) eagerReflBoolTrue
215+
216+
def mkPropagateSelfEqFalseProofCore (u : Expr) : OrderM Expr := do
217+
let h ← mkLeLtPrefix ``Grind.Order.lt_eq_false
218+
return mkApp h u
219+
220+
/--
221+
Constructs a proof of `e = False` where `e` is a term corresponding to the edge `u --(k) --> u` and
222+
`k` is negative.
223+
-/
224+
public def mkPropagateSelfEqFalseProof (u : Expr) (k : Weight) : OrderM Expr := do
225+
if (← isRing) then
226+
mkPropagateSelfEqFalseProofOffset u k
227+
else
228+
assert! k.strict
229+
mkPropagateSelfEqFalseProofCore u
230+
231+
def mkSelfUnsatProofCore (u : Expr) (h : Expr) : OrderM Expr := do
232+
let hf ← mkLeLtPreorderPrefix ``Grind.Order.lt_unsat
233+
return mkApp2 hf u h
234+
235+
def mkSelfUnsatProofOffset (u : Expr) (k : Weight) (h : Expr) : OrderM Expr := do
236+
let declName := if k.strict then
237+
``Grind.Order.lt_unsat_k
238+
else
239+
``Grind.Order.le_unsat_k
240+
let hf ← mkOrdRingPrefix declName
241+
return mkApp4 hf u (toExpr k.k) eagerReflBoolTrue h
242+
243+
/--
244+
Returns a proof of `False` using
245+
`u --(k)--> u` with proof `h` where `k` is negative
246+
-/
247+
public def mkSelfUnsatProof (u : Expr) (k : Weight) (h : Expr) : OrderM Expr := do
248+
if (← isRing) then
249+
mkSelfUnsatProofOffset u k h
250+
else
251+
mkSelfUnsatProofCore u h
252+
187253
def mkUnsatProofCore (u v : Expr) (k₁ : Weight) (h₁ : Expr) (k₂ : Weight) (h₂ : Expr) : OrderM Expr := do
188254
let h ← mkTransCoreProof u v u k₁.strict k₂.strict h₁ h₂
189255
assert! k₁.strict || k₂.strict

tests/lean/run/grind_11001.lean

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
example (n : Nat)
2+
(f : Nat → Rat → Rat)
3+
(x : Rat)
4+
(H : ∀ (x : Rat), 0 ≤ x →
5+
(4 < x → f n x < 2 * x) ∧ (x = 4 → f n x = 2 * x) ∧ (x < 42 * x < f n x)) :
6+
x ∈ [4] ↔ f n x = 2 * x := by
7+
fail_if_success grind
8+
sorry
9+
10+
example (n : Nat)
11+
(f : Nat → Rat → Rat)
12+
(x : Rat)
13+
(_ : x ≥ 0)
14+
(H : ∀ (x : Rat), 0 ≤ x →
15+
(4 < x → f n x < 2 * x) ∧ (x = 4 → f n x = 2 * x) ∧ (x < 42 * x < f n x)) :
16+
x ∈ [4] ↔ f n x = 2 * x := by
17+
grind
18+
19+
example (n : Nat)
20+
(f : Nat → Rat → Rat)
21+
(x : Rat)
22+
(_ : x ≥ 0)
23+
(H : ∀ (x : Rat), 0 ≤ x →
24+
(4 < x → f n x < 2 * x) ∧ (x = 4 → f n x = 2 * x) ∧ (x < 42 * x < f n x)) :
25+
f n x = 2 * x → x ∈ [4] := by
26+
grind
27+
28+
example (n : Nat)
29+
(f : Nat → Rat → Rat)
30+
(x : Rat)
31+
(H : ∀ (x : Rat), 0 ≤ x →
32+
(4 < x → f n x < 2 * x) ∧ (x = 4 → f n x = 2 * x) ∧ (x < 42 * x < f n x)) :
33+
x ∈ [4] → f n x = 2 * x := by
34+
grind

0 commit comments

Comments
 (0)