Skip to content

Commit 69c8f13

Browse files
authored
feat: proof construction for grind order (#10590)
This PR implements proof term construction for `grind order`.
1 parent 39beb25 commit 69c8f13

File tree

8 files changed

+318
-42
lines changed

8 files changed

+318
-42
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ public import Lean.Meta.Tactic.Grind.Order.Types
99
public import Lean.Meta.Tactic.Grind.Order.Internalize
1010
public import Lean.Meta.Tactic.Grind.Order.StructId
1111
public import Lean.Meta.Tactic.Grind.Order.OrderM
12+
public import Lean.Meta.Tactic.Grind.Order.Assert
13+
public import Lean.Meta.Tactic.Grind.Order.Util
1214
public section
1315
namespace Lean.Meta.Grind.Order
1416
builtin_initialize registerTraceClass `grind.order
17+
builtin_initialize registerTraceClass `grind.order.assert
1518
builtin_initialize registerTraceClass `grind.order.internalize
19+
builtin_initialize registerTraceClass `grind.order.internalize.term
1620

1721
builtin_initialize
1822
orderExt.setMethods
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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.Order.OrderM
9+
import Init.Grind.Propagator
10+
import Lean.Meta.Tactic.Grind.PropagatorAttr
11+
import Lean.Meta.Tactic.Grind.Order.Util
12+
import Lean.Meta.Tactic.Grind.Order.Proof
13+
namespace Lean.Meta.Grind.Order
14+
/--
15+
Returns a proof for `u + k ≤ v` (or `u ≤ v + k`) where `k` is the
16+
shortest path between `u` and `v`.
17+
-/
18+
partial def mkProofForPath (u v : NodeId) : OrderM Expr := do
19+
go (← getProof u v)
20+
where
21+
go (p : ProofInfo) : OrderM Expr := do
22+
if u == p.w then
23+
return p.proof
24+
else
25+
let p' ← getProof u p.w
26+
go (← mkTrans p' p v)
27+
28+
/-- Adds `p` to the list of things to be propagated. -/
29+
def pushToPropagate (p : ToPropagate) : OrderM Unit :=
30+
modifyStruct fun s => { s with propagate := p :: s.propagate }
31+
32+
/-
33+
def propagateEqTrue (e : Expr) (u v : NodeId) (k k' : Int) : OrderM Unit := do
34+
let kuv ← mkProofForPath u v
35+
let u ← getExpr u
36+
let v ← getExpr v
37+
pushEqTrue e <| mkPropagateEqTrueProof u v k kuv k'
38+
39+
private def propagateEqFalse (e : Expr) (u v : NodeId) (k k' : Int) : OrderM Unit := do
40+
let kuv ← mkProofForPath u v
41+
let u ← getExpr u
42+
let v ← getExpr v
43+
pushEqFalse e <| mkPropagateEqFalseProof u v k kuv k'
44+
-/
45+
46+
/--
47+
Auxiliary function for implementing theory propagation.
48+
Traverses the constraints `c` (representing an expression `e`) s.t.
49+
`c.u = u` and `c.v = v`, it removes `c` from the list of constraints
50+
associated with `(u, v)` IF
51+
- `e` is already assigned, or
52+
- `f c e` returns true
53+
-/
54+
@[inline]
55+
private def updateCnstrsOf (u v : NodeId) (f : Cnstr NodeId → Expr → OrderM Bool) : OrderM Unit := do
56+
if let some cs := (← getStruct).cnstrsOf.find? (u, v) then
57+
let cs' ← cs.filterM fun (c, e) => do
58+
if (← isEqTrue e <||> isEqFalse e) then
59+
return false -- constraint was already assigned
60+
else
61+
return !(← f c e)
62+
modifyStruct fun s => { s with cnstrsOf := s.cnstrsOf.insert (u, v) cs' }
63+
64+
def assertTrue (c : Cnstr NodeId) (p : Expr) : OrderM Unit := do
65+
trace[grind.order.assert] "{p} = True: {← c.pp}"
66+
67+
def assertFalse (c : Cnstr NodeId) (p : Expr) : OrderM Unit := do
68+
trace[grind.order.assert] "{p} = False: {← c.pp}"
69+
70+
def getStructIdOf? (e : Expr) : GoalM (Option Nat) := do
71+
return (← get').exprToStructId.find? { expr := e }
72+
73+
builtin_grind_propagator propagateLE ↓LE.le := fun e => do
74+
let some structId ← getStructIdOf? e | return ()
75+
OrderM.run structId do
76+
let some c ← getCnstr? e | return ()
77+
if (← isEqTrue e) then
78+
assertTrue c e
79+
else if (← isEqFalse e) then
80+
assertFalse c e
81+
82+
end Lean.Meta.Grind.Order

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

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,60 @@ def mkCnstr? (e : Expr) (kind : CnstrKind) (lhs rhs : Expr) : OrderM (Option (Cn
140140
else
141141
return some { kind, u := lhs, v := rhs }
142142

143+
def setStructId (e : Expr) : OrderM Unit := do
144+
let structId ← getStructId
145+
modify' fun s => { s with
146+
exprToStructId := s.exprToStructId.insert { expr := e } structId
147+
}
148+
149+
def mkNode (e : Expr) : OrderM NodeId := do
150+
if let some nodeId := (← getStruct).nodeMap.find? { expr := e } then
151+
return nodeId
152+
let nodeId : NodeId := (← getStruct).nodes.size
153+
trace[grind.order.internalize.term] "{e} ↦ #{nodeId}"
154+
modifyStruct fun s => { s with
155+
nodes := s.nodes.push e
156+
nodeMap := s.nodeMap.insert { expr := e } nodeId
157+
sources := s.sources.push {}
158+
targets := s.targets.push {}
159+
proofs := s.proofs.push {}
160+
}
161+
setStructId e
162+
/-
163+
**Note**: not all node expressions have been internalized because this solver
164+
creates auxiliary terms.
165+
-/
166+
if (← alreadyInternalized e) then
167+
orderExt.markTerm e
168+
return nodeId
169+
143170
def internalizeCnstr (e : Expr) (kind : CnstrKind) (lhs rhs : Expr) : OrderM Unit := do
144-
let some cnstr ← mkCnstr? e kind lhs rhs | return ()
145-
trace[grind.order.internalize] "{cnstr.u}, {cnstr.v}, {cnstr.k}"
171+
let some c ← mkCnstr? e kind lhs rhs | return ()
172+
trace[grind.order.internalize] "{c.u}, {c.v}, {c.k}"
146173
if grind.debug.get (← getOptions) then
147-
if let some h := cnstr.h? then check h
148-
-- **TODO**: update data-structures
174+
if let some h := c.h? then check h
175+
let u ← mkNode c.u
176+
let v ← mkNode c.v
177+
let c := { c with u, v }
178+
/-
179+
-- **TODO**: propagate
180+
if let some k ← getDist? u v then
181+
if k ≤ c.k then
182+
propagateEqTrue e u v k c.k
183+
return ()
184+
if let some k ← getDist? v u then
185+
if k + c.k < 0 then
186+
propagateEqFalse e v u k c.k
187+
return ()
188+
trace[grind.offset.internalize] "{e} ↦ {c}"
189+
-/
190+
setStructId e
191+
modifyStruct fun s => { s with
192+
cnstrs := s.cnstrs.insert { expr := e } c
193+
cnstrsOf :=
194+
let cs := if let some cs := s.cnstrsOf.find? (u, v) then (c, e) :: cs else [(c, e)]
195+
s.cnstrsOf.insert (u, v) cs
196+
}
149197

150198
def hasLt : OrderM Bool :=
151199
return (← getStruct).ltFn?.isSome
@@ -183,19 +231,23 @@ def adapt (α : Expr) (e : Expr) : GoalM (Expr × Expr) := do
183231
else
184232
return (α, e)
185233

234+
def alreadyInternalized (e : Expr) : OrderM Bool := do
235+
let s ← getStruct
236+
return s.cnstrs.contains { expr := e } || s.nodeMap.contains { expr := e }
237+
186238
public def internalize (e : Expr) (parent? : Option Expr) : GoalM Unit := do
187239
unless (← getConfig).order do return ()
188240
let some α := getType? e | return ()
189241
let (α, e) ← adapt α e
190242
if isForbiddenParent parent? then return ()
191-
let some structId ← getStructId? α | return ()
192-
OrderM.run structId do
193-
match_expr e with
194-
| LE.le _ _ lhs rhs => internalizeCnstr e .le lhs rhs
195-
| LT.lt _ _ lhs rhs => if (← hasLt) then internalizeCnstr e .lt lhs rhs
196-
| Eq _ lhs rhs => internalizeCnstr e .eq lhs rhs
197-
| _ =>
198-
-- TODO
199-
return ()
243+
if let some structId ← getStructId? α then OrderM.run structId do
244+
if (← alreadyInternalized e) then return ()
245+
match_expr e with
246+
| LE.le _ _ lhs rhs => internalizeCnstr e .le lhs rhs
247+
| LT.lt _ _ lhs rhs => if (← hasLt) then internalizeCnstr e .lt lhs rhs
248+
| Eq _ lhs rhs => internalizeCnstr e .eq lhs rhs
249+
| _ =>
250+
-- TODO
251+
return ()
200252

201253
end Lean.Meta.Grind.Order

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,26 @@ def modifyStruct (f : Struct → Struct) : OrderM Unit := do
3232
let structId ← getStructId
3333
modify' fun s => { s with structs := s.structs.modify structId f }
3434

35+
def getExpr (u : NodeId) : OrderM Expr := do
36+
return (← getStruct).nodes[u]!
37+
38+
def getDist? (u v : NodeId) : OrderM (Option Weight) := do
39+
return (← getStruct).targets[u]!.find? v
40+
41+
def getProof? (u v : NodeId) : OrderM (Option ProofInfo) := do
42+
return (← getStruct).proofs[u]!.find? v
43+
44+
def getNodeId (e : Expr) : OrderM NodeId := do
45+
let some nodeId := (← getStruct).nodeMap.find? { expr := e }
46+
| throwError "internal `grind` error, term has not been internalized by order module{indentExpr e}"
47+
return nodeId
48+
49+
def getProof (u v : NodeId) : OrderM ProofInfo := do
50+
let some p ← getProof? u v
51+
| throwError "internal `grind` error, failed to construct proof for{indentExpr (← getExpr u)}\nand{indentExpr (← getExpr v)}"
52+
return p
53+
54+
def getCnstr? (e : Expr) : OrderM (Option (Cnstr NodeId)) :=
55+
return (← getStruct).cnstrs.find? { expr := e }
56+
3557
end Lean.Meta.Grind.Order
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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.Order.OrderM
9+
public import Lean.Meta.Tactic.Grind.Arith.CommRing.RingM
10+
import Init.Grind.Order
11+
namespace Lean.Meta.Grind.Order
12+
/--
13+
Returns `declName α leInst isPreorderInst`
14+
-/
15+
def mkLePreorderPrefix (declName : Name) : OrderM Expr := do
16+
let s ← getStruct
17+
return mkApp3 (mkConst declName [s.u]) s.type s.leInst s.isPreorderInst
18+
19+
/--
20+
Returns `declName α leInst ltInst lawfulOrderLtInst isPreorderInst`
21+
-/
22+
def mkLeLtPrefix (declName : Name) : OrderM Expr := do
23+
let s ← getStruct
24+
return mkApp5 (mkConst declName [s.u]) s.type s.leInst s.ltInst?.get! s.lawfulOrderLTInst?.get! s.isPreorderInst
25+
26+
/--
27+
Returns `declName α leInst ltInst lawfulOrderLtInst isPreorderInst ringInst ordRingInst`
28+
-/
29+
def mkOrdRingPrefix (declName : Name) : OrderM Expr := do
30+
let s ← getStruct
31+
let h ← mkLeLtPrefix declName
32+
return mkApp2 h s.ringInst?.get! s.orderedRingInst?.get!
33+
34+
/--
35+
Assume `p₁` is `{ w := u, k := k₁, proof := p₁ }` and `p₂` is `{ w := w, k := k₂, proof := p₂ }`
36+
`p₁` is the proof for edge `u → w` and `p₂` the proof for edge `w -> v`.
37+
Then, this function returns a proof for edge `u -> v`.
38+
39+
Remark: for orders that do not support offsets.
40+
-/
41+
def mkTransCore (p₁ : ProofInfo) (p₂ : ProofInfo) (v : NodeId) : OrderM ProofInfo := do
42+
let { w := u, k.strict := s₁, proof := h₁, .. } := p₁
43+
let { w, k.strict := s₂, proof := h₂, .. } := p₂
44+
let h ← match s₁, s₂ with
45+
| false, false => mkLePreorderPrefix ``Grind.Order.le_trans
46+
| false, true => mkLeLtPrefix ``Grind.Order.le_lt_trans
47+
| true, false => mkLeLtPrefix ``Grind.Order.lt_le_trans
48+
| true, true => mkLeLtPrefix ``Grind.Order.lt_trans
49+
let ns := (← getStruct).nodes
50+
let h := mkApp5 h ns[u]! ns[w]! ns[v]! h₁ h₂
51+
return { w := p₁.w, k.strict := s₁ || s₂, proof := h }
52+
53+
/--
54+
Assume `p₁` is `{ w := u, k := k₁, proof := p₁ }` and `p₂` is `{ w := w, k := k₂, proof := p₂ }`
55+
`p₁` is the proof for edge `u -(k₁)→ w` and `p₂` the proof for edge `w -(k₂)-> v`.
56+
Then, this function returns a proof for edge `u -(k₁+k₂) -> v`.
57+
58+
Remark: for orders that support offsets.
59+
-/
60+
def mkTransOffset (p₁ : ProofInfo) (p₂ : ProofInfo) (v : NodeId) : OrderM ProofInfo := do
61+
let { w := u, k.k := k₁, k.strict := s₁, proof := h₁, .. } := p₁
62+
let { w, k.k := k₂, k.strict := s₂, proof := h₂, .. } := p₂
63+
let h ← match s₁, s₂ with
64+
| false, false => mkOrdRingPrefix ``Grind.Order.le_trans_k
65+
| false, true => mkOrdRingPrefix ``Grind.Order.le_lt_trans_k
66+
| true, false => mkOrdRingPrefix ``Grind.Order.lt_le_trans_k
67+
| true, true => mkOrdRingPrefix ``Grind.Order.lt_trans_k
68+
let k := k₁ + k₂
69+
let ns := (← getStruct).nodes
70+
let h := mkApp6 h ns[u]! ns[w]! ns[v]! (toExpr k₁) (toExpr k₂) (toExpr k)
71+
let h := mkApp3 h h₁ h₂ eagerReflBoolTrue
72+
return { w := p₁.w, k.k := k, k.strict := s₁ || s₂, proof := h }
73+
74+
/--
75+
Assume `p₁` is `{ w := u, k := k₁, proof := p₁ }` and `p₂` is `{ w := w, k := k₂, proof := p₂ }`
76+
`p₁` is the proof for edge `u -(k₁)→ w` and `p₂` the proof for edge `w -(k₂)-> v`.
77+
Then, this function returns a proof for edge `u -(k₁+k₂) -> v`.
78+
79+
Remark: if the order does not support offset `k₁` and `k₂` are zero.
80+
-/
81+
public def mkTrans (p₁ : ProofInfo) (p₂ : ProofInfo) (v : NodeId) : OrderM ProofInfo := do
82+
let s ← getStruct
83+
if s.orderedRingInst?.isSome then
84+
mkTransOffset p₁ p₂ v
85+
else
86+
mkTransCore p₁ p₂ v
87+
88+
end Lean.Meta.Grind.Order

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,25 @@ where
5555
pure (inst?, some ltFn)
5656
else
5757
pure (none, none)
58-
let (ringId?, orderedRingInst?, isCommRing) ← if lawfulOrderLTInst?.isNone then
59-
pure (none, none, false)
58+
let (ringId?, ringInst?, orderedRingInst?, isCommRing) ← if lawfulOrderLTInst?.isNone then
59+
pure (none, none, none, false)
6060
else if let some ringId ← getCommRingId? type then
61+
let ringInst ← RingM.run ringId do return (← getRing).ringInst
6162
let semiringInst ← RingM.run ringId do return (← getRing).semiringInst
6263
let some ordRingInst ← mkOrderedRingInst? u type semiringInst leInst ltInst?.get! isPreorderInst
63-
| pure (none, none, true)
64-
pure (some ringId, some ordRingInst, true)
64+
| pure (none, none, none, true)
65+
pure (some ringId, some ringInst, some ordRingInst, true)
6566
else if let some ringId ← getNonCommRingId? type then
6667
let semiringInst ← NonCommRingM.run ringId do return (← getRing).semiringInst
68+
let ringInst ← NonCommRingM.run ringId do return (← getRing).ringInst
6769
let some ordRingInst ← mkOrderedRingInst? u type semiringInst leInst ltInst?.get! isPreorderInst
68-
| pure (none, none, true)
69-
pure (some ringId, some ordRingInst, false)
70+
| pure (none, none, none, true)
71+
pure (some ringId, some ringInst, some ordRingInst, false)
7072
else
71-
pure (none, none, false)
73+
pure (none, none, none, false)
7274
let id := (← get').structs.size
7375
let struct := {
74-
id, type, u, leInst, isPreorderInst, ltInst?, leFn, isPartialInst?, orderedRingInst?
76+
id, type, u, leInst, isPreorderInst, ltInst?, leFn, isPartialInst?, ringInst?, orderedRingInst?
7577
isLinearPreInst?, ltFn?, lawfulOrderLTInst?, ringId?, isCommRing
7678
}
7779
modify' fun s => { s with structs := s.structs.push struct }

0 commit comments

Comments
 (0)