Skip to content

Commit 1377325

Browse files
committed
feat: ac grind action
1 parent a6a973a commit 1377325

File tree

6 files changed

+133
-36
lines changed

6 files changed

+133
-36
lines changed

src/Lean/Elab/Tactic/Grind/BuiltinTactic.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def evalCheck (tacticName : Name) (k : GoalM Bool)
101101
evalCheck `ring Arith.CommRing.check Arith.CommRing.pp?
102102

103103
@[builtin_grind_tactic ac] def evalAC : GrindTactic := fun _ => do
104-
evalCheck `ac AC.check AC.pp?
104+
evalCheck `ac AC.check' AC.pp?
105105

106106
def logTheoremAnchor (proof : Expr) : TermElabM Unit := do
107107
let stx ← getRef

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public import Lean.Meta.Tactic.Grind.AC.ToExpr
1717
public import Lean.Meta.Tactic.Grind.AC.VarRename
1818
public import Lean.Meta.Tactic.Grind.AC.PP
1919
public import Lean.Meta.Tactic.Grind.AC.Inv
20+
public import Lean.Meta.Tactic.Grind.AC.Action
2021
public section
2122
namespace Lean.Meta.Grind.AC
2223
builtin_initialize registerTraceClass `grind.ac
@@ -36,7 +37,7 @@ builtin_initialize
3637
(internalize := AC.internalize)
3738
(newEq := AC.processNewEq)
3839
(newDiseq := AC.processNewDiseq)
39-
(check := AC.check)
40+
(check := AC.check')
4041
(checkInv := AC.checkInvariants)
4142
(mkTactic? := return some (← `(grind| ac)))
4243

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+
module
7+
prelude
8+
public import Lean.Meta.Tactic.Grind.Action
9+
import Lean.Meta.Tactic.Grind.AC.Eq
10+
namespace Lean.Meta.Grind.Action
11+
12+
/-- Linear integer arithmetic action. -/
13+
public def ac : Action := fun goal kna kp => do
14+
let (result, goal') ← GoalM.run goal AC.check
15+
match result with
16+
| .none => kna goal'
17+
| .progress => kp goal'
18+
| .propagated => concatTactic (← kp goal') `(grind| ac)
19+
| .closed => closeWith `(grind| ac)
20+
21+
end Lean.Meta.Grind.Action

src/Lean/Meta/Tactic/Grind/AC/Eq.lean

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
66
module
77
prelude
88
public import Lean.Meta.Tactic.Grind.AC.Util
9+
public import Lean.Meta.Tactic.Grind.CheckResult
910
import Lean.Meta.Tactic.Grind.AC.DenoteExpr
1011
import Lean.Meta.Tactic.Grind.AC.Proof
1112
import Lean.Meta.Tactic.Grind.AC.Seq
@@ -445,60 +446,65 @@ def mkEqData (e : Expr) (r : AC.Expr) : ACM EqData := do
445446

446447
abbrev PropagateEqMap := Std.HashMap AC.Seq EqData
447448

448-
private def propagateEqs : ACM Unit := do
449-
if (← isInconsistent) then return ()
449+
private def propagateEqs : ACM Bool := do
450+
if (← isInconsistent) then return false
450451
/-
451452
This is a very simple procedure that does not use any indexing data-structure.
452453
We don't even cache the simplified expressions.
453454
TODO: optimize
454455
-/
455-
let mut map : PropagateEqMap := {}
456-
for e in (← getStruct).vars do
457-
if (← checkMaxSteps) then return ()
458-
let r ← asACExpr e
459-
map ← process map e r
460-
for (e, r) in (← getStruct).denoteEntries do
461-
if (← checkMaxSteps) then return ()
462-
map ← process map e r
456+
let go : StateT (Bool × PropagateEqMap) ACM Unit := do
457+
for e in (← getStruct).vars do
458+
if (← checkMaxSteps) then return
459+
let r ← asACExpr e
460+
process e r
461+
for (e, r) in (← getStruct).denoteEntries do
462+
if (← checkMaxSteps) then return
463+
process e r
464+
let (_, (propagated, _)) ← go.run (false, {})
465+
return propagated
463466
where
464-
process (map : PropagateEqMap) (e : Expr) (r : AC.Expr) : ACM PropagateEqMap := do
467+
process (e : Expr) (r : AC.Expr) : StateT (Bool × PropagateEqMap) ACM Unit := do
465468
let d ← mkEqData e r
466469
let s := d.c.rhs
467470
trace[grind.debug.ac.eq] "{e}, s: {← s.denoteExpr}"
468-
if let some d' := map[s]? then
471+
if let some d' := (← get).2[s]? then
469472
trace[grind.debug.ac.eq] "found [{← isEqv d.e d'.e}]: {d.e}, {d'.e}"
470473
unless (← isEqv d.e d'.e) do
471474
propagateEq d.e d'.e d.r d'.r d.c d'.c
472-
return map
475+
modify fun s => (true, s.2)
473476
else
474-
return map.insert s d
477+
modify fun (propagated, map) => (propagated, map.insert s d)
475478

476-
private def checkStruct : ACM Bool := do
477-
unless (← needCheck) do return false
479+
private def checkStruct : ACM CheckResult := do
480+
unless (← needCheck) do return .none
478481
trace_goal[grind.debug.ac.check] "{(← getStruct).op}"
479482
repeat
480483
checkSystem "ac"
481484
let some c ← getNext? | break
482485
trace_goal[grind.debug.ac.check] "{← c.denoteExpr}"
483486
c.addToBasis
484-
if (← isInconsistent) then return true
485-
if (← checkMaxSteps) then return true
487+
if (← isInconsistent) then return .closed
488+
if (← checkMaxSteps) then return .progress
486489
checkDiseqs
487-
propagateEqs
488490
modifyStruct fun s => { s with recheck := false }
489-
return true
491+
if (← propagateEqs) then return .propagated
492+
return .progress
490493

491-
def check : GoalM Bool := do profileitM Exception "grind ac" (← getOptions) do
492-
if (← checkMaxSteps) then return false
493-
let mut progress := false
494+
def check : GoalM CheckResult := do profileitM Exception "grind ac" (← getOptions) do
495+
if (← checkMaxSteps) then return .none
496+
let mut result : CheckResult := .none
494497
checkInvariants
495498
try
496499
for opId in *...(← get').structs.size do
497500
let r ← ACM.run opId checkStruct
498-
progress := progress || r
499-
if (← isInconsistent) then return true
500-
return progress
501+
result := result.join r
502+
if (← isInconsistent) then return .closed
503+
return result
501504
finally
502505
checkInvariants
503506

507+
def check' : GoalM Bool :=
508+
return (← check) != .none
509+
504510
end Lean.Meta.Grind.AC

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,13 @@ def concatTactic (r : ActionResult) (mk : GrindM (TSyntax `grind)) : GrindM Acti
224224
else
225225
return r
226226

227+
/-- Returns `.closed [← mk]` if tracing is enabled, and `.closed []` otherwise. -/
228+
def closeWith (mk : GrindM (TSyntax `grind)) : GrindM ActionResult := do
229+
if (← getConfig).trace then
230+
return .closed [(← mk)]
231+
else
232+
return .closed []
233+
227234
/--
228235
A terminal action which closes the goal or not.
229236
This kind of action may make progress, but we only include `mkTac` into the resulting tactic sequence
@@ -232,16 +239,10 @@ if it closed the goal.
232239
public def terminalAction (check : GoalM Bool) (mkTac : GrindM (TSyntax `grind)) : Action := fun goal kna kp => do
233240
let (progress, goal') ← GoalM.run goal check
234241
if progress then
235-
let r ← kp goal'
236-
/-
237-
**Note**: terminal actions may make progress by computing a valid assignment satisfying all constraints.
238-
That said, unless it closed the goal, it is pointless to include the tactic in the resulting
239-
sequence because no information has been communicated to the other solvers.
240-
-/
241242
if goal'.inconsistent then
242-
concatTactic r mkTac
243+
closeWith mkTac
243244
else
244-
return r
245+
kp goal'
245246
else
246247
kna goal'
247248

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
public section
10+
namespace Lean.Meta.Grind
11+
/--
12+
Result type for satisfiability checking procedures.
13+
-/
14+
inductive CheckResult where
15+
| /-- No progress -/
16+
none
17+
| /-- Updated basis, simplified equations. -/
18+
progress
19+
| /-- Propagated equations back to the core. -/
20+
propagated
21+
| /-- Closed the goal. -/
22+
closed
23+
deriving BEq, Inhabited, Repr
24+
25+
def CheckResult.lt (r₁ r₂ : CheckResult) : Bool :=
26+
match r₁, r₂ with
27+
| _, .none => false
28+
| .none, _ => true
29+
| _, .progress => false
30+
| .progress, _ => true
31+
| _, .propagated => false
32+
| .propagated, _ => true
33+
| .closed, .closed => false
34+
35+
def CheckResult.le (r₁ r₂ : CheckResult) : Bool :=
36+
r₁ == r₂ || r₁.lt r₂
37+
38+
/--
39+
Joins two results. It uses the order `.none < .progress < .propagated < .closed`
40+
-/
41+
def CheckResult.join (r₁ r₂ : CheckResult) : CheckResult :=
42+
match r₁, r₂ with
43+
| .none, _ => r₂
44+
| _, .none => r₁
45+
| .progress, _ => r₂
46+
| _, .progress => r₁
47+
| .propagated, _ => r₂
48+
| _, .propagated => r₁
49+
| .closed, .closed => .closed
50+
51+
/-! Sanity check theorems -/
52+
section
53+
open CheckResult
54+
example : lt .none .progress := rfl
55+
example : lt .progress .propagated := rfl
56+
example : lt .propagated .closed := rfl
57+
example {x} : lt x x = false := by cases x <;> rfl
58+
example {x y z} : lt x y → lt y z → lt x z := by cases x <;> cases y <;> cases z <;> decide
59+
example {x y z} : le x y → le y z → le x z := by cases x <;> cases y <;> cases z <;> decide
60+
example {x y} : le x y ↔ x = y ∨ lt x y := by cases x <;> cases y <;> simp +decide
61+
example {x} : le x x := by cases x <;> rfl
62+
example {x y} : le x y → le y x → x = y := by cases x <;> cases y <;> simp +decide
63+
example {x y} : le x (join x y) := by cases x <;> cases y <;> rfl
64+
example {y x} : le y (join x y) := by cases x <;> cases y <;> rfl
65+
example {x} : join x x = x := by cases x <;> rfl
66+
end
67+
68+
end Lean.Meta.Grind

0 commit comments

Comments
 (0)