Skip to content

Commit d5f312e

Browse files
leodemourakim-em
authored andcommitted
feat: offset constraints support for the grind tactic (leanprover#6603)
This PR implements support for offset constraints in the `grind` tactic. Several features are still missing, such as constraint propagation and support for offset equalities, but `grind` can already solve examples like the following: ```lean example (a b c : Nat) : a ≤ b → b + 2 ≤ c → a + 1 ≤ c := by grind example (a b c : Nat) : a ≤ b → b ≤ c → a ≤ c := by grind example (a b c : Nat) : a + 1 ≤ b → b + 1 ≤ c → a + 2 ≤ c := by grind example (a b c : Nat) : a + 1 ≤ b → b + 1 ≤ c → a + 1 ≤ c := by grind example (a b c : Nat) : a + 1 ≤ b → b ≤ c + 2 → a ≤ c + 1 := by grind example (a b c : Nat) : a + 2 ≤ b → b ≤ c + 2 → a ≤ c := by grind ``` --------- Co-authored-by: Kim Morrison <[email protected]>
1 parent 659ac2b commit d5f312e

19 files changed

+859
-212
lines changed

src/Init/Grind/Offset.lean

+41-156
Original file line numberDiff line numberDiff line change
@@ -7,159 +7,44 @@ prelude
77
import Init.Core
88
import Init.Omega
99

10-
namespace Lean.Grind.Offset
11-
12-
abbrev Var := Nat
13-
abbrev Context := Lean.RArray Nat
14-
15-
def fixedVar := 100000000 -- Any big number should work here
16-
17-
def Var.denote (ctx : Context) (v : Var) : Nat :=
18-
bif v == fixedVar then 1 else ctx.get v
19-
20-
structure Cnstr where
21-
x : Var
22-
y : Var
23-
k : Nat := 0
24-
l : Bool := true
25-
deriving Repr, DecidableEq, Inhabited
26-
27-
def Cnstr.denote (c : Cnstr) (ctx : Context) : Prop :=
28-
if c.l then
29-
c.x.denote ctx + c.k ≤ c.y.denote ctx
30-
else
31-
c.x.denote ctx ≤ c.y.denote ctx + c.k
32-
33-
def trivialCnstr : Cnstr := { x := 0, y := 0, k := 0, l := true }
34-
35-
@[simp] theorem denote_trivial (ctx : Context) : trivialCnstr.denote ctx := by
36-
simp [Cnstr.denote, trivialCnstr]
37-
38-
def Cnstr.trans (c₁ c₂ : Cnstr) : Cnstr :=
39-
if c₁.y = c₂.x then
40-
let { x, k := k₁, l := l₁, .. } := c₁
41-
let { y, k := k₂, l := l₂, .. } := c₂
42-
match l₁, l₂ with
43-
| false, false =>
44-
{ x, y, k := k₁ + k₂, l := false }
45-
| false, true =>
46-
if k₁ < k₂ then
47-
{ x, y, k := k₂ - k₁, l := true }
48-
else
49-
{ x, y, k := k₁ - k₂, l := false }
50-
| true, false =>
51-
if k₁ < k₂ then
52-
{ x, y, k := k₂ - k₁, l := false }
53-
else
54-
{ x, y, k := k₁ - k₂, l := true }
55-
| true, true =>
56-
{ x, y, k := k₁ + k₂, l := true }
57-
else
58-
trivialCnstr
59-
60-
@[simp] theorem Cnstr.denote_trans_easy (ctx : Context) (c₁ c₂ : Cnstr) (h : c₁.y ≠ c₂.x) : (c₁.trans c₂).denote ctx := by
61-
simp [*, Cnstr.trans]
62-
63-
@[simp] theorem Cnstr.denote_trans (ctx : Context) (c₁ c₂ : Cnstr) : c₁.denote ctx → c₂.denote ctx → (c₁.trans c₂).denote ctx := by
64-
by_cases c₁.y = c₂.x
65-
case neg => simp [*]
66-
simp [trans, *]
67-
let { x, k := k₁, l := l₁, .. } := c₁
68-
let { y, k := k₂, l := l₂, .. } := c₂
69-
simp_all; split
70-
· simp [denote]; omega
71-
· split <;> simp [denote] <;> omega
72-
· split <;> simp [denote] <;> omega
73-
· simp [denote]; omega
74-
75-
def Cnstr.isTrivial (c : Cnstr) : Bool := c.x == c.y && c.k == 0
76-
77-
theorem Cnstr.of_isTrivial (ctx : Context) (c : Cnstr) : c.isTrivial = true → c.denote ctx := by
78-
cases c; simp [isTrivial]; intros; simp [*, denote]
79-
80-
def Cnstr.isFalse (c : Cnstr) : Bool := c.x == c.y && c.k != 0 && c.l == true
81-
82-
theorem Cnstr.of_isFalse (ctx : Context) {c : Cnstr} : c.isFalse = true → ¬c.denote ctx := by
83-
cases c; simp [isFalse]; intros; simp [*, denote]; omega
84-
85-
def Cnstrs := List Cnstr
86-
87-
def Cnstrs.denoteAnd' (ctx : Context) (c₁ : Cnstr) (c₂ : Cnstrs) : Prop :=
88-
match c₂ with
89-
| [] => c₁.denote ctx
90-
| c::cs => c₁.denote ctx ∧ Cnstrs.denoteAnd' ctx c cs
91-
92-
theorem Cnstrs.denote'_trans (ctx : Context) (c₁ c : Cnstr) (cs : Cnstrs) : c₁.denote ctx → denoteAnd' ctx c cs → denoteAnd' ctx (c₁.trans c) cs := by
93-
induction cs
94-
next => simp [denoteAnd', *]; apply Cnstr.denote_trans
95-
next c cs ih => simp [denoteAnd']; intros; simp [*]
96-
97-
def Cnstrs.trans' (c₁ : Cnstr) (c₂ : Cnstrs) : Cnstr :=
98-
match c₂ with
99-
| [] => c₁
100-
| c::c₂ => trans' (c₁.trans c) c₂
101-
102-
@[simp] theorem Cnstrs.denote'_trans' (ctx : Context) (c₁ : Cnstr) (c₂ : Cnstrs) : denoteAnd' ctx c₁ c₂ → (trans' c₁ c₂).denote ctx := by
103-
induction c₂ generalizing c₁
104-
next => intros; simp_all [trans', denoteAnd']
105-
next c cs ih => simp [denoteAnd']; intros; simp [trans']; apply ih; apply denote'_trans <;> assumption
106-
107-
def Cnstrs.denoteAnd (ctx : Context) (c : Cnstrs) : Prop :=
108-
match c with
109-
| [] => True
110-
| c::cs => denoteAnd' ctx c cs
111-
112-
def Cnstrs.trans (c : Cnstrs) : Cnstr :=
113-
match c with
114-
| [] => trivialCnstr
115-
| c::cs => trans' c cs
116-
117-
theorem Cnstrs.of_denoteAnd_trans {ctx : Context} {c : Cnstrs} : c.denoteAnd ctx → c.trans.denote ctx := by
118-
cases c <;> simp [*, trans, denoteAnd] <;> intros <;> simp [*]
119-
120-
def Cnstrs.isFalse (c : Cnstrs) : Bool :=
121-
c.trans.isFalse
122-
123-
theorem Cnstrs.unsat' (ctx : Context) (c : Cnstrs) : c.isFalse = true → ¬ c.denoteAnd ctx := by
124-
simp [isFalse]; intro h₁ h₂
125-
have := of_denoteAnd_trans h₂
126-
have := Cnstr.of_isFalse ctx h₁
127-
contradiction
128-
129-
/-- `denote ctx [c_1, ..., c_n] C` is `c_1.denote ctx → ... → c_n.denote ctx → C` -/
130-
def Cnstrs.denote (ctx : Context) (cs : Cnstrs) (C : Prop) : Prop :=
131-
match cs with
132-
| [] => C
133-
| c::cs => c.denote ctx → denote ctx cs C
134-
135-
theorem Cnstrs.not_denoteAnd'_eq (ctx : Context) (c : Cnstr) (cs : Cnstrs) (C : Prop) : (denoteAnd' ctx c cs → C) = denote ctx (c::cs) C := by
136-
simp [denote]
137-
induction cs generalizing c
138-
next => simp [denoteAnd', denote]
139-
next c' cs ih =>
140-
simp [denoteAnd', denote, *]
141-
142-
theorem Cnstrs.not_denoteAnd_eq (ctx : Context) (cs : Cnstrs) (C : Prop) : (denoteAnd ctx cs → C) = denote ctx cs C := by
143-
cases cs
144-
next => simp [denoteAnd, denote]
145-
next c cs => apply not_denoteAnd'_eq
146-
147-
def Cnstr.isImpliedBy (cs : Cnstrs) (c : Cnstr) : Bool :=
148-
cs.trans == c
149-
150-
/-! Main theorems used by `grind`. -/
151-
152-
/-- Auxiliary theorem used by `grind` to prove that a system of offset inequalities is unsatisfiable. -/
153-
theorem Cnstrs.unsat (ctx : Context) (cs : Cnstrs) : cs.isFalse = true → cs.denote ctx False := by
154-
intro h
155-
rw [← not_denoteAnd_eq]
156-
apply unsat'
157-
assumption
158-
159-
/-- Auxiliary theorem used by `grind` to prove an implied offset inequality. -/
160-
theorem Cnstrs.imp (ctx : Context) (cs : Cnstrs) (c : Cnstr) (h : c.isImpliedBy cs = true) : cs.denote ctx (c.denote ctx) := by
161-
rw [← eq_of_beq h]
162-
rw [← not_denoteAnd_eq]
163-
apply of_denoteAnd_trans
164-
165-
end Lean.Grind.Offset
10+
namespace Lean.Grind
11+
def isLt (x y : Nat) : Bool := x < y
12+
13+
theorem Nat.le_ro (u w v k : Nat) : u ≤ w → w ≤ v + k → u ≤ v + k := by
14+
omega
15+
theorem Nat.le_lo (u w v k : Nat) : u ≤ w → w + k ≤ v → u + k ≤ v := by
16+
omega
17+
theorem Nat.lo_le (u w v k : Nat) : u + k ≤ w → w ≤ v → u + k ≤ v := by
18+
omega
19+
theorem Nat.lo_lo (u w v k₁ k₂ : Nat) : u + k₁ ≤ w → w + k₂ ≤ v → u + (k₁ + k₂) ≤ v := by
20+
omega
21+
theorem Nat.lo_ro_1 (u w v k₁ k₂ : Nat) : isLt k₂ k₁ = true → u + k₁ ≤ w → w ≤ v + k₂ → u + (k₁ - k₂) ≤ v := by
22+
simp [isLt]; omega
23+
theorem Nat.lo_ro_2 (u w v k₁ k₂ : Nat) : u + k₁ ≤ w → w ≤ v + k₂ → u ≤ v + (k₂ - k₁) := by
24+
omega
25+
theorem Nat.ro_le (u w v k : Nat) : u ≤ w + k → w ≤ v → u ≤ v + k := by
26+
omega
27+
theorem Nat.ro_lo_1 (u w v k₁ k₂ : Nat) : u ≤ w + k₁ → w + k₂ ≤ v → u ≤ v + (k₁ - k₂) := by
28+
omega
29+
theorem Nat.ro_lo_2 (u w v k₁ k₂ : Nat) : isLt k₁ k₂ = true → u ≤ w + k₁ → w + k₂ ≤ v → u + (k₂ - k₁) ≤ v := by
30+
simp [isLt]; omega
31+
theorem Nat.ro_ro (u w v k₁ k₂ : Nat) : u ≤ w + k₁ → w ≤ v + k₂ → u ≤ v + (k₁ + k₂) := by
32+
omega
33+
34+
theorem Nat.of_le_eq_false (u v : Nat) : ((u ≤ v) = False) → v + 1 ≤ u := by
35+
simp; omega
36+
theorem Nat.of_lo_eq_false_1 (u v : Nat) : ((u + 1 ≤ v) = False) → v ≤ u := by
37+
simp; omega
38+
theorem Nat.of_lo_eq_false (u v k : Nat) : ((u + k ≤ v) = False) → v ≤ u + (k-1) := by
39+
simp; omega
40+
theorem Nat.of_ro_eq_false (u v k : Nat) : ((u ≤ v + k) = False) → v + (k+1) ≤ u := by
41+
simp; omega
42+
43+
theorem Nat.unsat_le_lo (u v k : Nat) : isLt 0 k = true → u ≤ v → v + k ≤ u → False := by
44+
simp [isLt]; omega
45+
theorem Nat.unsat_lo_lo (u v k₁ k₂ : Nat) : isLt 0 (k₁+k₂) = true → u + k₁ ≤ v → v + k₂ ≤ u → False := by
46+
simp [isLt]; omega
47+
theorem Nat.unsat_lo_ro (u v k₁ k₂ : Nat) : isLt k₂ k₁ = true → u + k₁ ≤ v → v ≤ u + k₂ → False := by
48+
simp [isLt]; omega
49+
50+
end Lean.Grind

src/Lean/Data/AssocList.lean

+7-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ abbrev empty : AssocList α β :=
2424

2525
instance : EmptyCollection (AssocList α β) := ⟨empty⟩
2626

27-
abbrev insert (m : AssocList α β) (k : α) (v : β) : AssocList α β :=
27+
abbrev insertNew (m : AssocList α β) (k : α) (v : β) : AssocList α β :=
2828
m.cons k v
2929

3030
def isEmpty : AssocList α β → Bool
@@ -77,6 +77,12 @@ def replace [BEq α] (a : α) (b : β) : AssocList α β → AssocList α β
7777
| true => cons a b es
7878
| false => cons k v (replace a b es)
7979

80+
def insert [BEq α] (m : AssocList α β) (k : α) (v : β) : AssocList α β :=
81+
if m.contains k then
82+
m.replace k v
83+
else
84+
m.insertNew k v
85+
8086
def erase [BEq α] (a : α) : AssocList α β → AssocList α β
8187
| nil => nil
8288
| cons k v es => match k == a with

src/Lean/Meta/AppBuilder.lean

+8-4
Original file line numberDiff line numberDiff line change
@@ -569,12 +569,16 @@ def mkLetBodyCongr (a h : Expr) : MetaM Expr :=
569569
mkAppM ``let_body_congr #[a, h]
570570

571571
/-- Return `of_eq_true h` -/
572-
def mkOfEqTrue (h : Expr) : MetaM Expr :=
573-
mkAppM ``of_eq_true #[h]
572+
def mkOfEqTrue (h : Expr) : MetaM Expr := do
573+
match_expr h with
574+
| eq_true _ h => return h
575+
| _ => mkAppM ``of_eq_true #[h]
574576

575577
/-- Return `eq_true h` -/
576-
def mkEqTrue (h : Expr) : MetaM Expr :=
577-
mkAppM ``eq_true #[h]
578+
def mkEqTrue (h : Expr) : MetaM Expr := do
579+
match_expr h with
580+
| of_eq_true _ h => return h
581+
| _ => return mkApp2 (mkConst ``eq_true) (← inferType h) h
578582

579583
/--
580584
Return `eq_false h`

src/Lean/Meta/Tactic/FVarSubst.lean

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def insert (s : FVarSubst) (fvarId : FVarId) (v : Expr) : FVarSubst :=
3535
if s.contains fvarId then s
3636
else
3737
let map := s.map.mapVal fun e => e.replaceFVarId fvarId v;
38-
{ map := map.insert fvarId v }
38+
{ map := map.insertNew fvarId v }
3939

4040
def erase (s : FVarSubst) (fvarId : FVarId) : FVarSubst :=
4141
{ map := s.map.erase fvarId }

src/Lean/Meta/Tactic/Grind.lean

+7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import Lean.Meta.Tactic.Grind.EMatchTheorem
2424
import Lean.Meta.Tactic.Grind.EMatch
2525
import Lean.Meta.Tactic.Grind.Main
2626
import Lean.Meta.Tactic.Grind.CasesMatch
27+
import Lean.Meta.Tactic.Grind.Arith
2728

2829
namespace Lean
2930

@@ -42,6 +43,10 @@ builtin_initialize registerTraceClass `grind.simp
4243
builtin_initialize registerTraceClass `grind.split
4344
builtin_initialize registerTraceClass `grind.split.candidate
4445
builtin_initialize registerTraceClass `grind.split.resolved
46+
builtin_initialize registerTraceClass `grind.offset
47+
builtin_initialize registerTraceClass `grind.offset.dist
48+
builtin_initialize registerTraceClass `grind.offset.internalize
49+
builtin_initialize registerTraceClass `grind.offset.internalize.term (inherited := true)
4550

4651
/-! Trace options for `grind` developers -/
4752
builtin_initialize registerTraceClass `grind.debug
@@ -54,4 +59,6 @@ builtin_initialize registerTraceClass `grind.debug.final
5459
builtin_initialize registerTraceClass `grind.debug.forallPropagator
5560
builtin_initialize registerTraceClass `grind.debug.split
5661
builtin_initialize registerTraceClass `grind.debug.canon
62+
builtin_initialize registerTraceClass `grind.debug.offset
63+
builtin_initialize registerTraceClass `grind.debug.offset.proof
5764
end Lean

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

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
prelude
7+
import Lean.Meta.Tactic.Grind.Arith.Util
8+
import Lean.Meta.Tactic.Grind.Arith.Types
9+
import Lean.Meta.Tactic.Grind.Arith.Offset
10+
import Lean.Meta.Tactic.Grind.Arith.Main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
prelude
7+
import Lean.Meta.Tactic.Grind.Arith.Offset
8+
9+
namespace Lean.Meta.Grind.Arith
10+
11+
namespace Offset
12+
13+
def internalizeTerm (_e : Expr) (_a : Expr) (_k : Nat) : GoalM Unit := do
14+
-- TODO
15+
return ()
16+
17+
def internalizeCnstr (e : Expr) : GoalM Unit := do
18+
let some c := isNatOffsetCnstr? e | return ()
19+
let c := { c with
20+
a := (← mkNode c.a)
21+
b := (← mkNode c.b)
22+
}
23+
trace[grind.offset.internalize] "{e} ↦ {c}"
24+
modify' fun s => { s with
25+
cnstrs := s.cnstrs.insert { expr := e } c
26+
}
27+
28+
end Offset
29+
30+
def internalize (e : Expr) : GoalM Unit := do
31+
Offset.internalizeCnstr e
32+
33+
end Lean.Meta.Grind.Arith
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
prelude
7+
import Lean.Meta.Tactic.Grind.Arith.Offset
8+
9+
namespace Lean.Meta.Grind.Arith
10+
11+
def checkInvariants : GoalM Unit :=
12+
Offset.checkInvariants
13+
14+
end Lean.Meta.Grind.Arith
+34
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+
prelude
7+
import Lean.Meta.Tactic.Grind.PropagatorAttr
8+
import Lean.Meta.Tactic.Grind.Arith.Offset
9+
10+
namespace Lean.Meta.Grind.Arith
11+
12+
namespace Offset
13+
def isCnstr? (e : Expr) : GoalM (Option (Cnstr NodeId)) :=
14+
return (← get).arith.offset.cnstrs.find? { expr := e }
15+
16+
def assertTrue (c : Cnstr NodeId) (p : Expr) : GoalM Unit := do
17+
addEdge c.a c.b c.k (← mkOfEqTrue p)
18+
19+
def assertFalse (c : Cnstr NodeId) (p : Expr) : GoalM Unit := do
20+
let p := mkOfNegEqFalse (← get').nodes c p
21+
let c := c.neg
22+
addEdge c.a c.b c.k p
23+
24+
end Offset
25+
26+
builtin_grind_propagator propagateLE ↓LE.le := fun e => do
27+
if (← isEqTrue e) then
28+
if let some c ← Offset.isCnstr? e then
29+
Offset.assertTrue c (← mkEqTrueProof e)
30+
if (← isEqFalse e) then
31+
if let some c ← Offset.isCnstr? e then
32+
Offset.assertFalse c (← mkEqFalseProof e)
33+
34+
end Lean.Meta.Grind.Arith

0 commit comments

Comments
 (0)