Skip to content

Commit 106708e

Browse files
authored
feat: grind linarith module infrastructure (#8677)
This PR adds the basic infrastructure for the linarith module in `grind`.
1 parent 666fb5c commit 106708e

File tree

11 files changed

+466
-3
lines changed

11 files changed

+466
-3
lines changed

src/Init/Grind/CommRing/Poly.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,11 +1144,11 @@ end Stepwise
11441144

11451145
def Poly.denoteAsIntModule [CommRing α] (ctx : Context α) (p : Poly) : α :=
11461146
match p with
1147-
| .num k => Int.cast k * 1
1147+
| .num k => Int.cast k * One.one
11481148
| .add k m p => Int.cast k * m.denote ctx + denote ctx p
11491149

11501150
theorem Poly.denoteAsIntModule_eq_denote {α} [CommRing α] (ctx : Context α) (p : Poly) : p.denoteAsIntModule ctx = p.denote ctx := by
1151-
induction p <;> simp [*, denoteAsIntModule, denote, mul_one]
1151+
induction p <;> simp [*, denoteAsIntModule, denote, mul_one, One.one]
11521152

11531153
open Stepwise
11541154

src/Init/Grind/Ordered/Linarith.lean

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,11 @@ theorem diseq_split {α} [IntModule α] [LinearOrder α] [IntModule.IsOrdered α
329329
simp [h₁] at h
330330
rw [← neg_pos_iff, neg_hmul, neg_neg, one_hmul]; assumption
331331

332+
theorem diseq_split_resolve {α} [IntModule α] [LinearOrder α] [IntModule.IsOrdered α] (ctx : Context α) (p₁ p₂ : Poly)
333+
: diseq_split_cert p₁ p₂ → p₁.denote' ctx ≠ 0 → ¬p₁.denote' ctx < 0 → p₂.denote' ctx < 0 := by
334+
intro h₁ h₂ h₃
335+
exact (diseq_split ctx p₁ p₂ h₁ h₂).resolve_left h₃
336+
332337
def eq_diseq_combine_cert (p₁ p₂ p₃ : Poly) : Bool :=
333338
let a₁ := p₁.leadCoeff.natAbs
334339
let a₂ := p₂.leadCoeff.natAbs

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ import Lean.Meta.Tactic.Grind.Arith.Main
1111
import Lean.Meta.Tactic.Grind.Arith.Offset
1212
import Lean.Meta.Tactic.Grind.Arith.Cutsat
1313
import Lean.Meta.Tactic.Grind.Arith.CommRing
14+
import Lean.Meta.Tactic.Grind.Arith.Linear
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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.Linear.Types
8+
import Lean.Meta.Tactic.Grind.Arith.Linear.Util
9+
import Lean.Meta.Tactic.Grind.Arith.Linear.Var
10+
import Lean.Meta.Tactic.Grind.Arith.Linear.StructId
11+
import Lean.Meta.Tactic.Grind.Arith.Linear.IneqCnstr
12+
13+
namespace Lean
14+
15+
builtin_initialize registerTraceClass `grind.linarith
16+
builtin_initialize registerTraceClass `grind.linarith.internalize
17+
builtin_initialize registerTraceClass `grind.linarith.assert
18+
builtin_initialize registerTraceClass `grind.linarith.assert.unsat (inherited := true)
19+
builtin_initialize registerTraceClass `grind.linarith.assert.trivial (inherited := true)
20+
21+
end Lean
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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.Linear.Var
8+
import Lean.Meta.Tactic.Grind.Arith.Linear.StructId
9+
10+
namespace Lean.Meta.Grind.Arith.Linear
11+
12+
def propagateIneq (e : Expr) (eqTrue : Bool) (strict : Bool) : GoalM Unit := do
13+
trace[grind.linarith] "{e}, {eqTrue}, {strict}"
14+
let args := e.getAppArgs
15+
unless args.size == 4 do return ()
16+
let α := args[0]!
17+
let some structId ← getStructId? α | return ()
18+
trace[grind.linarith] "structId: {structId}"
19+
-- TODO
20+
return ()
21+
22+
end Lean.Meta.Grind.Arith.Linear
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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 Init.Grind.Ordered.Module
8+
import Lean.Meta.Tactic.Grind.Simp
9+
import Lean.Meta.Tactic.Grind.Arith.Linear.Util
10+
import Lean.Meta.Tactic.Grind.Arith.Linear.Var
11+
12+
namespace Lean.Meta.Grind.Arith.Linear
13+
14+
private def internalizeFn (fn : Expr) : GoalM Expr := do
15+
shareCommon (← canon fn)
16+
17+
open Grind.Linarith (Poly)
18+
19+
def getStructId? (type : Expr) : GoalM (Option Nat) := do
20+
if let some id? := (← get').typeIdOf.find? { expr := type } then
21+
return id?
22+
else
23+
let id? ← go?
24+
modify' fun s => { s with typeIdOf := s.typeIdOf.insert { expr := type } id? }
25+
return id?
26+
where
27+
go? : GoalM (Option Nat) := do
28+
let u ← getDecLevel type
29+
let getInst? (declName : Name) : GoalM (Option Expr) := do
30+
let instType := mkApp (mkConst declName [u]) type
31+
return LOption.toOption (← trySynthInstance instType)
32+
let getInst (declName : Name) : GoalM Expr := do
33+
let instType := mkApp (mkConst declName [u]) type
34+
let .some inst ← trySynthInstance instType
35+
| throwError "`grind linarith` failed to find instance{indentExpr instType}"
36+
return inst
37+
let getBinHomoInst (declName : Name) : GoalM Expr := do
38+
let instType := mkApp3 (mkConst declName [u, u, u]) type type type
39+
let .some inst ← trySynthInstance instType
40+
| throwError "`grind linarith` failed to find instance{indentExpr instType}"
41+
return inst
42+
let getHMulInst : GoalM Expr := do
43+
let instType := mkApp3 (mkConst ``HMul [0, u, u]) Int.mkType type type
44+
let .some inst ← trySynthInstance instType
45+
| throwError "`grind linarith` failed to find instance{indentExpr instType}"
46+
return inst
47+
let checkToFieldDefEq? (parentInst? : Option Expr) (inst? : Option Expr) (toFieldName : Name) : GoalM (Option Expr) := do
48+
let some parentInst := parentInst? | return none
49+
let some inst := inst? | return none
50+
let toField := mkApp2 (mkConst toFieldName [u]) type inst
51+
unless (← withDefault <| isDefEq parentInst toField) do
52+
reportIssue! "`grind linarith` expected{indentExpr parentInst}\nto be definitionally equal to{indentExpr toField}"
53+
return none
54+
return some inst
55+
let ensureToFieldDefEq (parentInst : Expr) (inst : Expr) (toFieldName : Name) : GoalM Unit := do
56+
let toField := mkApp2 (mkConst toFieldName [u]) type inst
57+
unless (← withDefault <| isDefEq parentInst toField) do
58+
throwError "`grind linarith` expected{indentExpr parentInst}\nto be definitionally equal to{indentExpr toField}"
59+
let ensureToHomoFieldDefEq (parentInst : Expr) (inst : Expr) (toFieldName : Name) (toHeteroName : Name) : GoalM Unit := do
60+
let toField := mkApp2 (mkConst toFieldName [u]) type inst
61+
let heteroToField := mkApp2 (mkConst toHeteroName [u]) type toField
62+
unless (← withDefault <| isDefEq parentInst heteroToField) do
63+
throwError "`grind linarith` expected{indentExpr parentInst}\nto be definitionally equal to{indentExpr heteroToField}"
64+
let some intModuleInst ← getInst? ``Grind.IntModule | return none
65+
let zeroInst ← getInst ``Zero
66+
let zero := mkApp2 (mkConst ``Zero.zero [u]) type zeroInst
67+
let addInst ← getBinHomoInst ``HAdd
68+
let addFn := mkApp4 (mkConst ``HAdd.hAdd [u, u, u]) type type type addInst
69+
let subInst ← getBinHomoInst ``HSub
70+
let subFn := mkApp4 (mkConst ``HSub.hSub [u, u, u]) type type type subInst
71+
let negInst ← getInst ``Neg
72+
let negFn := mkApp2 (mkConst ``Neg.neg [u]) type negInst
73+
let hmulInst ← getHMulInst
74+
let hmulFn := mkApp4 (mkConst ``HMul.hMul [0, u, u]) Int.mkType type type hmulInst
75+
ensureToFieldDefEq zeroInst intModuleInst ``Grind.IntModule.toZero
76+
ensureToHomoFieldDefEq addInst intModuleInst ``Grind.IntModule.toAdd ``instHAdd
77+
ensureToHomoFieldDefEq subInst intModuleInst ``Grind.IntModule.toSub ``instHSub
78+
ensureToFieldDefEq negInst intModuleInst ``Grind.IntModule.toNeg
79+
ensureToFieldDefEq hmulInst intModuleInst ``Grind.IntModule.toHMul
80+
let some preorderInst ← getInst? ``Grind.Preorder | return none
81+
let leInst ← getInst ``LE
82+
let ltInst ← getInst ``LT
83+
let leFn := mkApp2 (mkConst ``LE.le [u]) type leInst
84+
let ltFn := mkApp2 (mkConst ``LT.lt [u]) type ltInst
85+
ensureToFieldDefEq leInst preorderInst ``Grind.Preorder.toLE
86+
ensureToFieldDefEq ltInst preorderInst ``Grind.Preorder.toLT
87+
let partialInst? ← checkToFieldDefEq? (some preorderInst) (← getInst? ``Grind.PartialOrder) ``Grind.PartialOrder.toPreorder
88+
let linearInst? ← checkToFieldDefEq? partialInst? (← getInst? ``Grind.LinearOrder) ``Grind.LinearOrder.toPartialOrder
89+
let isOrderedType := mkApp3 (mkConst ``Grind.IntModule.IsOrdered [u]) type preorderInst intModuleInst
90+
let .some isOrdInst ← trySynthInstance isOrderedType | return none
91+
let getSMulFn? : GoalM (Option Expr) := do
92+
let smulType := mkApp2 (mkConst ``SMul [0, u]) Int.mkType type
93+
let .some smulInst ← trySynthInstance smulType | return none
94+
let smulFn := mkApp3 (mkConst ``SMul.smul [0, u]) Int.mkType type smulInst
95+
if (← withDefault <| isDefEq hmulFn smulFn) then
96+
return smulFn
97+
reportIssue! "`grind linarith` expected{indentExpr hmulFn}\nto be definitionally equal to{indentExpr smulFn}"
98+
return none
99+
let smulFn? ← getSMulFn?
100+
let ringInst? ← getInst? ``Grind.Ring
101+
let getOne? : GoalM (Option Expr) := do
102+
let some oneInst ← getInst? ``One | return none
103+
let one := mkApp2 (mkConst ``One.one [u]) type oneInst
104+
let one' ← mkNumeral type 1
105+
unless (← withDefault <| isDefEq one one') do reportIssue! "`grind linarith` expected{indentExpr one}\nto be definitionally equal to{indentExpr one'}"
106+
return some one
107+
let one? ← getOne?
108+
let commRingInst? ← getInst? ``Grind.CommRing
109+
let getRingIsOrdInst? : GoalM (Option Expr) := do
110+
let some ringInst := ringInst? | return none
111+
let isOrdType := mkApp3 (mkConst ``Grind.Ring.IsOrdered [u]) type ringInst preorderInst
112+
return LOption.toOption (← trySynthInstance isOrdType)
113+
let ringIsOrdInst? ← getRingIsOrdInst?
114+
let getNoNatZeroDivInst? : GoalM (Option Expr) := do
115+
let hmulNat := mkApp3 (mkConst ``HMul [0, u, u]) Nat.mkType type type
116+
let .some hmulInst ← trySynthInstance hmulNat | return none
117+
let noNatZeroDivType := mkApp3 (mkConst ``Grind.NoNatZeroDivisors [u]) type zeroInst hmulInst
118+
return LOption.toOption (← trySynthInstance noNatZeroDivType)
119+
let noNatDivInst? ← getNoNatZeroDivInst?
120+
let id := (← get').structs.size
121+
let struct : Struct := {
122+
id, type, u, intModuleInst, preorderInst, isOrdInst, partialInst?, linearInst?, noNatDivInst?
123+
leFn, ltFn, addFn, subFn, negFn, hmulFn, smulFn?, zero, one?
124+
ringInst?, commRingInst?, ringIsOrdInst?
125+
}
126+
modify' fun s => { s with structs := s.structs.push struct }
127+
if let some one := one? then
128+
if ringInst?.isSome then LinearM.run id do
129+
-- Create `1` variable, and assert strict lower bound `0 < 1`
130+
let x ← mkVar one
131+
let p := Poly.add (-1) x .nil
132+
modifyStruct fun s => { s with
133+
lowers := s.lowers.modify x fun cs => cs.push { p, h := .oneGtZero, strict := true }
134+
}
135+
return some id
136+
137+
end Lean.Meta.Grind.Arith.Linear
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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 Std.Internal.Rat
8+
import Init.Grind.Ordered.Linarith
9+
import Lean.Data.PersistentArray
10+
import Lean.Meta.Tactic.Grind.ExprPtr
11+
12+
namespace Lean.Meta.Grind.Arith.Linear
13+
export Lean.Grind.Linarith (Var Poly)
14+
export Std.Internal (Rat)
15+
abbrev LinExpr := Lean.Grind.Linarith.Expr
16+
17+
deriving instance Hashable for Poly
18+
19+
mutual
20+
/-- A equality constraint and its justification/proof. -/
21+
structure EqCnstr where
22+
p : Poly
23+
h : EqCnstrProof
24+
25+
inductive EqCnstrProof where
26+
| /-- An equality `a = b` coming from the core. -/
27+
core (a b : Expr) (la lb : LinExpr)
28+
| combine (c₁ : EqCnstr) (c₂ : EqCnstr)
29+
| ofLe (c₁ : IneqCnstr) (c₂ : IneqCnstr)
30+
| norm (c₁ : EqCnstr) (k : Nat)
31+
32+
/-- An inequality constraint and its justification/proof. -/
33+
structure IneqCnstr where
34+
p : Poly
35+
strict : Bool
36+
h : IneqCnstrProof
37+
38+
inductive IneqCnstrProof where
39+
| core (e : Expr) (lhs rhs : LinExpr)
40+
| notCore (e : Expr) (lhs rhs : LinExpr)
41+
| combine (c₁ : IneqCnstr) (c₂ : IneqCnstr)
42+
| combineEq (c₁ : IneqCnstr) (c₂ : EqCnstr)
43+
| norm (c₁ : IneqCnstr) (k : Nat)
44+
| dec (h : FVarId)
45+
| ofDiseqSplit (c₁ : DiseqCnstr) (decVar : FVarId) (h : UnsatProof) (decVars : Array FVarId)
46+
| oneGtZero
47+
48+
structure DiseqCnstr where
49+
p : Poly
50+
h : DiseqCnstrProof
51+
52+
inductive DiseqCnstrProof where
53+
| core (e : Expr) (lhs rhs : LinExpr)
54+
| combineEq (c₁ : DiseqCnstr) (c₂ : EqCnstr)
55+
| combineEq' (c₁ : DiseqCnstr) (c₂ : EqCnstr)
56+
57+
-- Only used if `LinearOrder` instance is not available
58+
structure NotIneqCnstr where
59+
p : Poly
60+
strict : Bool
61+
h : NotIneqCnstrProof
62+
63+
inductive NotIneqCnstrProof where
64+
| core (e : Expr) (lhs rhs : LinExpr)
65+
-- TODO: norm, and combineEq
66+
67+
inductive UnsatProof where
68+
| diseqUnsat (c : DiseqCnstr)
69+
| ltUnsat (c : IneqCnstr)
70+
-- TODO: IneqCnstr + NotIneqCnstr
71+
72+
end
73+
74+
/--
75+
State for each algebraic structure by this module.
76+
Each type must be at least implement the instances `IntModule`, `Preorder`, and `IntModule.IsOrdered`
77+
-/
78+
structure Struct where
79+
id : Nat
80+
type : Expr
81+
/-- Cached `getDecLevel type` -/
82+
u : Level
83+
/-- `IntModule` instance -/
84+
intModuleInst : Expr
85+
/-- `Preorder` instance -/
86+
preorderInst : Expr
87+
/-- `IntModule.IsOrdered` instance with `Preorder` -/
88+
isOrdInst : Expr
89+
/-- `PartialOrder` instance if available -/
90+
partialInst? : Option Expr
91+
/-- `LinearOrder` instance if available -/
92+
linearInst? : Option Expr
93+
/-- `NoNatZeroDivisors` -/
94+
noNatDivInst? : Option Expr
95+
/-- `Ring` instance -/
96+
ringInst? : Option Expr
97+
/-- `CommRing` instance -/
98+
commRingInst? : Option Expr
99+
/-- `Ring.IsOrdered` instance with `Preorder` -/
100+
ringIsOrdInst? : Option Expr
101+
zero : Expr
102+
one? : Option Expr
103+
leFn : Expr
104+
ltFn : Expr
105+
addFn : Expr
106+
hmulFn : Expr
107+
smulFn? : Option Expr
108+
subFn : Expr
109+
negFn : Expr
110+
/--
111+
Mapping from variables to their denotations.
112+
Remark each variable can be in only one ring.
113+
-/
114+
vars : PArray Expr := {}
115+
/-- Mapping from `Expr` to a variable representing it. -/
116+
varMap : PHashMap ExprPtr Var := {}
117+
/--
118+
Mapping from variables to their "lower" bounds. We say a relational constraint `c` is a lower bound for a variable `x`
119+
if `x` is the maximal variable in `c`, and `x` coefficient in `c` is negative.
120+
-/
121+
lowers : PArray (PArray IneqCnstr) := {}
122+
/--
123+
Mapping from variables to their "upper" bounds. We say a relational constraint `c` is a upper bound for a variable `x`
124+
if `x` is the maximal variable in `c`, and `x` coefficient in `c` is positive.
125+
-/
126+
uppers : PArray (PArray IneqCnstr) := {}
127+
/--
128+
Mapping from variables to their disequalities. We say a disequality constraint `c` is a disequality for a variable `x`
129+
if `x` is the maximal variable in `c`.
130+
-/
131+
diseqs : PArray (PArray DiseqCnstr) := {}
132+
notIneqs : PArray (PArray NotIneqCnstr) := {}
133+
/--
134+
Mapping from variable to equation constraint used to eliminate it. `solved` variables should not occur in
135+
`dvdCnstrs`, `lowers`, or `uppers`.
136+
-/
137+
elimEqs : PArray (Option EqCnstr) := {}
138+
/-- Partial assignment being constructed by linarith. -/
139+
assignment : PArray Rat := {}
140+
/--
141+
`caseSplits` is `true` if linarith is searching for model and already performed case splits.
142+
This information is used to decide whether a conflict should immediately close the
143+
current `grind` goal or not.
144+
-/
145+
caseSplits : Bool := false
146+
/--
147+
`conflict?` is `some ..` if a contradictory constraint was derived.
148+
This field is only set when `caseSplits` is `true`. Otherwise, we
149+
can convert `UnsatProof` into a Lean term and close the current `grind` goal.
150+
-/
151+
conflict? : Option UnsatProof := none
152+
/--
153+
Cache decision variables used when splitting on disequalities.
154+
This is necessary because the same disequality may be in different conflicts.
155+
-/
156+
diseqSplits : PHashMap Poly FVarId := {}
157+
158+
/-- State for all `IntModule` types detected by `grind`. -/
159+
structure State where
160+
/--
161+
Structures detected.
162+
We expect to find a small number of `IntModule`s in a given goal. Thus, using `Array` is fine here.
163+
-/
164+
structs : Array Struct := {}
165+
/--
166+
Mapping from types to its "structure id". We cache failures using `none`.
167+
`typeIdOf[type]` is `some id`, then `id < structs.size`. -/
168+
typeIdOf : PHashMap ExprPtr (Option Nat) := {}
169+
/- Mapping from expressions/terms to their structure ids. -/
170+
exprToStructId : PHashMap ExprPtr Nat := {}
171+
deriving Inhabited
172+
173+
end Lean.Meta.Grind.Arith.Linear

0 commit comments

Comments
 (0)