Compare commits

..

17 Commits

Author SHA1 Message Date
Leonardo de Moura
7b59c38963 feat: add appMap field to Goal 2024-12-28 16:17:00 -08:00
Leonardo de Moura
58943bc4d6 feat: add updateMT
Update modification time.
2024-12-28 16:17:00 -08:00
Leonardo de Moura
5930db946c fix: Int.reduceNeg simproc (#6468)
This PR fixes issue #6467 


closes #6467
2024-12-28 22:58:09 +00:00
Leonardo de Moura
3fc74854d7 fix: check function types when detecting congruences in grind (#6466)
This PR completes the implementation of `addCongrTable` in the (WIP)
`grind` tactic. It also adds a new test to demonstrate why the extra
check is needed. It also updates the field `cgRoot` (congruence root).
2024-12-28 19:53:02 +00:00
Leonardo de Moura
fe45ddd610 feat: projections in grind (#6465)
This PR adds support for projection functions to the (WIP) `grind`
tactic.
2024-12-27 23:50:58 +00:00
Leonardo de Moura
f545df9922 feat: literal values in grind (#6464)
This PR completes support for literal values in the (WIP) `grind`
tactic. `grind` now closes the goal whenever it merges two equivalence
classes with distinct literal values.
2024-12-27 22:18:56 +00:00
Leonardo de Moura
844e82e176 feat: constructors in grind (#6463)
This PR adds support for constructors to the (WIP) `grind` tactic. When
merging equivalence classes, `grind` checks for equalities between
constructors. If they are distinct, it closes the goal; if they are the
same, it applies injectivity.
2024-12-27 21:15:02 +00:00
Leonardo de Moura
2d7d3388e2 fix: missing Not propagation rule in grind (#6461)
This PR adds a new propagation rule for negation to the (WIP) `grind`
tactic.
2024-12-27 17:37:32 +00:00
Henrik Böving
c14e5ae7de chore: implement reduceCond for bv_decide (#6460)
This PR implements the equivalent of `reduceIte` for `cond` in
`bv_decide` as we switched to `bif` for the `if` normal form.
2024-12-27 10:12:52 +00:00
Leonardo de Moura
6a839796fd feat: add grind tactic (#6459)
This PR adds the (WIP) `grind` tactic. It currently generates a warning
message to make it clear that the tactic is not ready for production.
2024-12-27 03:48:01 +00:00
Leonardo de Moura
e76dc20200 feat: use compact congruence proofs in grind if applicable (#6458)
This PR adds support for compact congruence proofs in the (WIP) `grind`
tactic. The `mkCongrProof` function now verifies whether the congruence
proof can be constructed using only `congr`, `congrFun`, and `congrArg`,
avoiding the need to generate the more complex `hcongr` auxiliary
theorems.
2024-12-26 23:58:04 +00:00
Leonardo de Moura
dca874ea57 feat: congruence proofs for grind (#6457)
This PR adds support for generating congruence proofs for congruences
detected by the `grind` tactic.
2024-12-26 22:20:36 +00:00
Leonardo de Moura
c282d558fa fix: fix: bug in mkEqProof within grind (#6456)
This PR fixes another bug in the equality proof generator in the (WIP)
`grind` tactic.
2024-12-26 19:03:35 +00:00
Leonardo de Moura
57050be3ab fix: bug in mkEqProof within grind (#6455)
This PR fixes a bug in the equality proof generator in the (WIP) `grind`
tactic.
2024-12-26 18:25:11 +00:00
Henrik Böving
37b53b70d0 perf: improve bv_decide performance with large literals (#6453)
This PR improves bv_decide's performance in the presence of large
literals.

The core change of this PR is the reformulation of the reflection code
for literals to:
```diff
 def eval (assign : Assignment) : BVExpr w → BitVec w
   | .var idx =>
-    let ⟨bv⟩ := assign.get idx
-    bv.truncate w
+    let packedBv := assign.get idx
+    /-
+    This formulation improves performance, as in a well formed expression the condition always holds
+    so there is no need for the more involved `BitVec.truncate` logic.
+    -/
+    if h : packedBv.w = w then
+      h ▸ packedBv.bv
+    else
+      packedBv.bv.truncate w
```
The remainder is merely further simplifications that make the terms
smaller and easier to deal with in general. This change is motivated by
applying the following diff to the kernel:
```diff
diff --git a/src/kernel/type_checker.cpp b/src/kernel/type_checker.cpp
index b0e6844dca..f13bb96bd4 100644
--- a/src/kernel/type_checker.cpp
+++ b/src/kernel/type_checker.cpp
@@ -518,6 +518,7 @@ optional<constant_info> type_checker::is_delta(expr const & e) const {
 optional<expr> type_checker::unfold_definition_core(expr const & e) {
     if (is_constant(e)) {
         if (auto d = is_delta(e)) {
+//            std::cout << "Working on unfolding: " << d->get_name() << std::endl;
             if (length(const_levels(e)) == d->get_num_lparams()) {
                 if (m_diag) {
                     m_diag->record_unfold(d->get_name());
```
and observing that in the test case from #6043 we see a long series of
```
Working on unfolding: Bool.decEq
Working on unfolding: Bool.decEq.match_1
Working on unfolding: Bool.casesOn
Working on unfolding: Nat.ble
Working on unfolding: Nat.brecOn
Working on unfolding: Nat.beq.match_1
Working on unfolding: Nat.casesOn
Working on unfolding: Nat.casesOn
Working on unfolding: Nat.beq.match_1
Working on unfolding: Nat.casesOn
Working on unfolding: Nat.casesOn
```
the chain begins with `BitVec.truncate`, works through a few
abstractions and then continues like above forever, so I avoid the call
to truncate like this. It is not quite clear to me why removing `ofBool`
helps so much here, maybe some other kernel heuristic kicks in to rescue
us.

Either way this diff is a general improvement for reflection of `BitVec`
constants as we should never have to run `BitVec.truncate` again!

Fixes: #6043
2024-12-26 16:50:00 +00:00
Leonardo de Moura
8a1e50f0b9 feat: equality proof generation for grind (#6452)
This PR adds support for generating (small) proofs for any two
expressions that belong to the same equivalence class in the `grind`
tactic state.
2024-12-26 06:01:45 +00:00
Leonardo de Moura
bdcb7914b5 chore: check whether pointer equality implies structural equality in grind (#6451)
This PR checks whether in the internal state of the `grind` tactic
pointer equality implies structural equality.
2024-12-26 03:50:39 +00:00
44 changed files with 1142 additions and 361 deletions

View File

@@ -9,3 +9,4 @@ import Init.Grind.Tactics
import Init.Grind.Lemmas
import Init.Grind.Cases
import Init.Grind.Propagator
import Init.Grind.Util

View File

@@ -7,6 +7,7 @@ prelude
import Init.Core
import Init.SimpLemmas
import Init.Classical
import Init.ByCases
namespace Lean.Grind
@@ -41,6 +42,9 @@ theorem not_eq_of_eq_false {a : Prop} (h : a = False) : (Not a) = True := by sim
theorem eq_false_of_not_eq_true {a : Prop} (h : (Not a) = True) : a = False := by simp_all
theorem eq_true_of_not_eq_false {a : Prop} (h : (Not a) = False) : a = True := by simp_all
theorem false_of_not_eq_self {a : Prop} (h : (Not a) = a) : False := by
by_cases a <;> simp_all
/-! Eq -/
theorem eq_eq_of_eq_true_left {a b : Prop} (h : a = True) : (a = b) = b := by simp [h]

View File

@@ -19,7 +19,15 @@ structure Config where
eager : Bool := false
deriving Inhabited, BEq
end Lean.Grind
namespace Lean.Parser.Tactic
/-!
`grind` tactic and related tactics.
-/
end Lean.Grind
-- TODO: configuration option, parameters
syntax (name := grind) "grind" : tactic
end Lean.Parser.Tactic

View File

@@ -11,4 +11,9 @@ namespace Lean.Grind
/-- A helper gadget for annotating nested proofs in goals. -/
def nestedProof (p : Prop) (h : p) : p := h
set_option pp.proofs true
theorem nestedProof_congr (p q : Prop) (h : p = q) (hp : p) (hq : q) : HEq (nestedProof p hp) (nestedProof q hq) := by
subst h; apply HEq.refl
end Lean.Grind

View File

@@ -44,3 +44,4 @@ import Lean.Elab.Tactic.DiscrTreeKey
import Lean.Elab.Tactic.BVDecide
import Lean.Elab.Tactic.BoolToPropSimps
import Lean.Elab.Tactic.Classical
import Lean.Elab.Tactic.Grind

View File

@@ -82,7 +82,7 @@ instance : ToExpr Gate where
| .and => mkConst ``Gate.and
| .xor => mkConst ``Gate.xor
| .beq => mkConst ``Gate.beq
| .imp => mkConst ``Gate.imp
| .or => mkConst ``Gate.or
toTypeExpr := mkConst ``Gate
instance : ToExpr BVPred where

View File

@@ -91,7 +91,7 @@ where
| .and => ``Std.Tactic.BVDecide.Reflect.Bool.and_congr
| .xor => ``Std.Tactic.BVDecide.Reflect.Bool.xor_congr
| .beq => ``Std.Tactic.BVDecide.Reflect.Bool.beq_congr
| .imp => ``Std.Tactic.BVDecide.Reflect.Bool.imp_congr
| .or => ``Std.Tactic.BVDecide.Reflect.Bool.or_congr
/--
Construct the reified version of `Bool.not subExpr`.
@@ -136,7 +136,7 @@ def mkIte (discr lhs rhs : ReifiedBVLogical) (discrExpr lhsExpr rhsExpr : Expr)
lhsEvalExpr lhsProof?
rhsEvalExpr rhsProof? | return none
return mkApp9
(mkConst ``Std.Tactic.BVDecide.Reflect.Bool.ite_congr)
(mkConst ``Std.Tactic.BVDecide.Reflect.Bool.cond_congr)
discrExpr lhsExpr rhsExpr
discrEvalExpr lhsEvalExpr rhsEvalExpr
discrProof lhsProof rhsProof

View File

@@ -22,67 +22,70 @@ This function adds the two lemmas:
- `discrExpr = false → atomExpr = rhsExpr`
It assumes that `discrExpr`, `lhsExpr` and `rhsExpr` are the expressions corresponding to `discr`,
`lhs` and `rhs`. Furthermore it assumes that `atomExpr` is of the form
`if discrExpr = true then lhsExpr else rhsExpr`.
`bif discrExpr then lhsExpr else rhsExpr`.
-/
def addIfLemmas (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
def addCondLemmas (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : LemmaM Unit := do
let some trueLemma mkIfTrueLemma discr atom lhs rhs discrExpr atomExpr lhsExpr rhsExpr | return ()
let some trueLemma mkCondTrueLemma discr atom lhs discrExpr atomExpr lhsExpr rhsExpr | return ()
LemmaM.addLemma trueLemma
let some falseLemma mkIfFalseLemma discr atom lhs rhs discrExpr atomExpr lhsExpr rhsExpr | return ()
let some falseLemma mkCondFalseLemma discr atom rhs discrExpr atomExpr lhsExpr rhsExpr | return ()
LemmaM.addLemma falseLemma
where
mkIfTrueLemma (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : M (Option SatAtBVLogical) :=
mkIfLemma true discr atom lhs rhs discrExpr atomExpr lhsExpr rhsExpr
mkIfFalseLemma (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : M (Option SatAtBVLogical) :=
mkIfLemma false discr atom lhs rhs discrExpr atomExpr lhsExpr rhsExpr
mkIfLemma (discrVal : Bool) (discr : ReifiedBVLogical) (atom lhs rhs : ReifiedBVExpr)
mkCondTrueLemma (discr : ReifiedBVLogical) (atom lhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : M (Option SatAtBVLogical) := do
let resExpr := if discrVal then lhsExpr else rhsExpr
let resValExpr := if discrVal then lhs else rhs
let lemmaName :=
if discrVal then
``Std.Tactic.BVDecide.Reflect.BitVec.if_true
else
``Std.Tactic.BVDecide.Reflect.BitVec.if_false
let discrValExpr := toExpr discrVal
let discrVal ReifiedBVLogical.mkBoolConst discrVal
let resExpr := lhsExpr
let resValExpr := lhs
let lemmaName := ``Std.Tactic.BVDecide.Reflect.BitVec.cond_true
let eqDiscrExpr mkAppM ``BEq.beq #[discrExpr, discrValExpr]
let eqDiscr ReifiedBVLogical.mkGate discr discrVal discrExpr discrValExpr .beq
let notDiscrExpr := mkApp (mkConst ``Bool.not) discrExpr
let notDiscr ReifiedBVLogical.mkNot discr discrExpr
let eqBVExpr mkAppM ``BEq.beq #[atomExpr, resExpr]
let some eqBVPred ReifiedBVPred.mkBinPred atom resValExpr atomExpr resExpr .eq | return none
let eqBV ReifiedBVLogical.ofPred eqBVPred
let imp ReifiedBVLogical.mkGate eqDiscr eqBV eqDiscrExpr eqBVExpr .imp
let imp ReifiedBVLogical.mkGate notDiscr eqBV notDiscrExpr eqBVExpr .or
let proof := do
let evalExpr ReifiedBVLogical.mkEvalExpr imp.expr
let congrProof := ( imp.evalsAtAtoms).getD (ReifiedBVLogical.mkRefl evalExpr)
let lemmaProof := mkApp4 (mkConst lemmaName) (toExpr lhs.width) discrExpr lhsExpr rhsExpr
let trueExpr := mkConst ``Bool.true
let eqDiscrTrueExpr mkEq eqDiscrExpr trueExpr
let eqBVExprTrueExpr mkEq eqBVExpr trueExpr
let impExpr mkArrow eqDiscrTrueExpr eqBVExprTrueExpr
-- construct a `Decidable` instance for the implication using forall_prop_decidable
let decEqDiscrTrue := mkApp2 (mkConst ``instDecidableEqBool) eqDiscrExpr trueExpr
let decEqBVExprTrue := mkApp2 (mkConst ``instDecidableEqBool) eqBVExpr trueExpr
let impDecidable := mkApp4 (mkConst ``forall_prop_decidable)
eqDiscrTrueExpr
(.lam .anonymous eqDiscrTrueExpr eqBVExprTrueExpr .default)
decEqDiscrTrue
(.lam .anonymous eqDiscrTrueExpr decEqBVExprTrue .default)
let decideImpExpr := mkApp2 (mkConst ``Decidable.decide) impExpr impDecidable
-- !discr || (atom == rhs)
let impExpr := mkApp2 (mkConst ``Bool.or) notDiscrExpr eqBVExpr
return mkApp4
(mkConst ``Std.Tactic.BVDecide.Reflect.Bool.lemma_congr)
decideImpExpr
impExpr
evalExpr
congrProof
lemmaProof
return some imp.bvExpr, proof, imp.expr
mkCondFalseLemma (discr : ReifiedBVLogical) (atom rhs : ReifiedBVExpr)
(discrExpr atomExpr lhsExpr rhsExpr : Expr) : M (Option SatAtBVLogical) := do
let resExpr := rhsExpr
let resValExpr := rhs
let lemmaName := ``Std.Tactic.BVDecide.Reflect.BitVec.cond_false
let eqBVExpr mkAppM ``BEq.beq #[atomExpr, resExpr]
let some eqBVPred ReifiedBVPred.mkBinPred atom resValExpr atomExpr resExpr .eq | return none
let eqBV ReifiedBVLogical.ofPred eqBVPred
let imp ReifiedBVLogical.mkGate discr eqBV discrExpr eqBVExpr .or
let proof := do
let evalExpr ReifiedBVLogical.mkEvalExpr imp.expr
let congrProof := ( imp.evalsAtAtoms).getD (ReifiedBVLogical.mkRefl evalExpr)
let lemmaProof := mkApp4 (mkConst lemmaName) (toExpr rhs.width) discrExpr lhsExpr rhsExpr
-- discr || (atom == rhs)
let impExpr := mkApp2 (mkConst ``Bool.or) discrExpr eqBVExpr
return mkApp4
(mkConst ``Std.Tactic.BVDecide.Reflect.Bool.lemma_congr)
impExpr
evalExpr
congrProof
lemmaProof

View File

@@ -220,15 +220,12 @@ where
.rotateRight
``BVUnOp.rotateRight
``Std.Tactic.BVDecide.Reflect.BitVec.rotateRight_congr
| ite _ discrExpr _ lhsExpr rhsExpr =>
let_expr Eq α discrExpr val := discrExpr | return none
let_expr Bool := α | return none
let_expr Bool.true := val | return none
| cond _ discrExpr lhsExpr rhsExpr =>
let some atom ReifiedBVExpr.bitVecAtom x true | return none
let some discr ReifiedBVLogical.of discrExpr | return none
let some lhs goOrAtom lhsExpr | return none
let some rhs goOrAtom rhsExpr | return none
addIfLemmas discr atom lhs rhs discrExpr x lhsExpr rhsExpr
addCondLemmas discr atom lhs rhs discrExpr x lhsExpr rhsExpr
return some atom
| _ => return none
@@ -392,10 +389,7 @@ where
| Bool => gateReflection lhsExpr rhsExpr .beq
| BitVec _ => goPred t
| _ => return none
| ite _ discrExpr _ lhsExpr rhsExpr =>
let_expr Eq α discrExpr val := discrExpr | return none
let_expr Bool := α | return none
let_expr Bool.true := val | return none
| cond _ discrExpr lhsExpr rhsExpr =>
let some discr goOrAtom discrExpr | return none
let some lhs goOrAtom lhsExpr | return none
let some rhs goOrAtom rhsExpr | return none

View File

@@ -28,6 +28,18 @@ namespace Frontend.Normalize
open Lean.Meta
open Std.Tactic.BVDecide.Normalize
builtin_simproc [bv_normalize] reduceCond (cond _ _ _) := fun e => do
let_expr f@cond α c tb eb := e | return .continue
let r Simp.simp c
if r.expr.cleanupAnnotations.isConstOf ``Bool.true then
let pr := mkApp (mkApp4 (mkConst ``Bool.cond_pos f.constLevels!) α c tb eb) ( r.getProof)
return .visit { expr := tb, proof? := pr }
else if r.expr.cleanupAnnotations.isConstOf ``Bool.false then
let pr := mkApp (mkApp4 (mkConst ``Bool.cond_neg f.constLevels!) α c tb eb) ( r.getProof)
return .visit { expr := eb, proof? := pr }
else
return .continue
builtin_simproc [bv_normalize] eqToBEq (((_ : Bool) = (_ : Bool))) := fun e => do
let_expr Eq _ lhs rhs := e | return .continue
match_expr rhs with
@@ -127,8 +139,6 @@ builtin_simproc [bv_normalize] bv_add_const' (((_ : BitVec _) + (_ : BitVec _))
else
return .continue
attribute [builtin_bv_normalize_proc] reduceIte
/-- Return a number `k` such that `2^k = n`. -/
private def Nat.log2Exact (n : Nat) : Option Nat := do
guard <| n 0

View File

@@ -0,0 +1,27 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Tactics
import Lean.Meta.Tactic.Grind
import Lean.Elab.Tactic.Basic
namespace Lean.Elab.Tactic
open Meta
def grind (mvarId : MVarId) (mainDeclName : Name) : MetaM Unit := do
let mvarIds Grind.main mvarId mainDeclName
unless mvarIds.isEmpty do
throwError "`grind` failed\n{goalsToMessageData mvarIds}"
@[builtin_tactic Lean.Parser.Tactic.grind] def evalApplyRfl : Tactic := fun stx => do
match stx with
| `(tactic| grind) =>
logWarningAt stx "The `grind` tactic is experimental and still under development. Avoid using it in production projects"
let declName := ( Term.getDeclName?).getD `_grind
withMainContext do liftMetaFinishingTactic (grind · declName)
| _ => throwUnsupportedSyntax
end Lean.Elab.Tactic

View File

@@ -1648,6 +1648,23 @@ def isFalse (e : Expr) : Bool :=
def isTrue (e : Expr) : Bool :=
e.cleanupAnnotations.isConstOf ``True
/--
`getForallArity type` returns the arity of a `forall`-type. This function consumes nested annotations,
and performs pending beta reductions. It does **not** use whnf.
Examples:
- If `a` is `Nat`, `getForallArity a` returns `0`
- If `a` is `Nat → Bool`, `getForallArity a` returns `1`
-/
partial def getForallArity : Expr Nat
| .mdata _ b => getForallArity b
| .forallE _ _ b _ => getForallArity b + 1
| e =>
if e.isHeadBetaTarget then
getForallArity e.headBeta
else
let e' := e.cleanupAnnotations
if e != e' then getForallArity e' else 0
/--
Checks if an expression is a "natural number numeral in normal form",
i.e. of type `Nat`, and explicitly of the form `OfNat.ofNat n`

View File

@@ -176,6 +176,14 @@ def mkEqOfHEq (h : Expr) : MetaM Expr := do
| _ =>
throwAppBuilderException ``eq_of_heq m!"heterogeneous equality proof expected{indentExpr h}"
/-- Given `h : Eq a b`, returns a proof of `HEq a b`. -/
def mkHEqOfEq (h : Expr) : MetaM Expr := do
let hType infer h
let some (α, a, b) := hType.eq?
| throwAppBuilderException ``heq_of_eq m!"equality proof expected{indentExpr h}"
let u getLevel α
return mkApp4 (mkConst ``heq_of_eq [u]) α a b h
/--
If `e` is `@Eq.refl α a`, return `a`.
-/

View File

@@ -19,6 +19,7 @@ import Lean.Meta.Tactic.Grind.Proof
import Lean.Meta.Tactic.Grind.Propagate
import Lean.Meta.Tactic.Grind.PP
import Lean.Meta.Tactic.Grind.Simp
import Lean.Meta.Tactic.Grind.Ctor
namespace Lean
@@ -28,7 +29,10 @@ builtin_initialize registerTraceClass `grind.issues
builtin_initialize registerTraceClass `grind.add
builtin_initialize registerTraceClass `grind.pre
builtin_initialize registerTraceClass `grind.debug
builtin_initialize registerTraceClass `grind.debug.proofs
builtin_initialize registerTraceClass `grind.simp
builtin_initialize registerTraceClass `grind.congr
builtin_initialize registerTraceClass `grind.proof
builtin_initialize registerTraceClass `grind.proof.detail
end Lean

View File

@@ -9,58 +9,11 @@ import Lean.Meta.LitValues
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.Inv
import Lean.Meta.Tactic.Grind.PP
import Lean.Meta.Tactic.Grind.Ctor
import Lean.Meta.Tactic.Grind.Internalize
namespace Lean.Meta.Grind
/-- We use this auxiliary constant to mark delayed congruence proofs. -/
private def congrPlaceholderProof := mkConst (Name.mkSimple "[congruence]")
/-- Adds `e` to congruence table. -/
private def addCongrTable (e : Expr) : GoalM Unit := do
if let some { e := e' } := ( get).congrTable.find? { e } then
trace[grind.congr] "{e} = {e'}"
pushEqHEq e e' congrPlaceholderProof
-- TODO: we must check whether the types of the functions are the same
-- TODO: update cgRoot for `e`
else
modify fun s => { s with congrTable := s.congrTable.insert { e } }
partial def internalize (e : Expr) (generation : Nat) : GoalM Unit := do
if ( alreadyInternalized e) then return ()
match e with
| .bvar .. => unreachable!
| .sort .. => return ()
| .fvar .. | .letE .. | .lam .. | .forallE .. =>
mkENodeCore e (ctor := false) (interpreted := false) (generation := generation)
| .lit .. | .const .. =>
mkENode e generation
| .mvar ..
| .mdata ..
| .proj .. =>
trace[grind.issues] "unexpected term during internalization{indentExpr e}"
mkENodeCore e (ctor := false) (interpreted := false) (generation := generation)
| .app .. =>
if ( isLitValue e) then
-- We do not want to internalize the components of a literal value.
mkENode e generation
else e.withApp fun f args => do
if f.isConstOf ``Lean.Grind.nestedProof && args.size == 2 then
-- We only internalize the proposition. We can skip the proof because of
-- proof irrelevance
let c := args[0]!
internalize c generation
registerParent e c
else
unless f.isConst do
internalize f generation
registerParent e f
for h : i in [: args.size] do
let arg := args[i]
internalize arg generation
registerParent e arg
mkENode e generation
addCongrTable e
/--
The fields `target?` and `proof?` in `e`'s `ENode` are encoding a transitivity proof
from `e` to the root of the equivalence class
@@ -81,9 +34,6 @@ where
proof? := proofNew?
}
private def markAsInconsistent : GoalM Unit :=
modify fun s => { s with inconsistent := true }
/--
Remove `root` parents from the congruence table.
This is an auxiliary function performed while merging equivalence classes.
@@ -102,6 +52,35 @@ private def reinsertParents (parents : ParentSet) : GoalM Unit := do
for parent in parents do
addCongrTable parent
/-- Closes the goal when `True` and `False` are in the same equivalence class. -/
private def closeGoalWithTrueEqFalse : GoalM Unit := do
let mvarId := ( get).mvarId
unless ( mvarId.isAssigned) do
let trueEqFalse mkEqFalseProof ( getTrueExpr)
let falseProof mkEqMP trueEqFalse (mkConst ``True.intro)
closeGoal falseProof
/-- Closes the goal when `lhs` and `rhs` are both literal values and belong to the same equivalence class. -/
private def closeGoalWithValuesEq (lhs rhs : Expr) : GoalM Unit := do
let p mkEq lhs rhs
let hp mkEqProof lhs rhs
let d mkDecide p
let pEqFalse := mkApp3 (mkConst ``eq_false_of_decide) p d.appArg! (mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Bool) (mkConst ``false))
let falseProof mkEqMP pEqFalse hp
closeGoal falseProof
/--
Updates the modification time to `gmt` for the parents of `root`.
The modification time is used to decide which terms are considered during e-matching.
-/
private partial def updateMT (root : Expr) : GoalM Unit := do
let gmt := ( get).gmt
for parent in ( getParents root) do
let node getENode parent
if node.mt < gmt then
setENode parent { node with mt := gmt }
updateMT parent
private partial def addEqStep (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit := do
trace[grind.eq] "{lhs} {if isHEq then "" else "="} {rhs}"
let lhsNode getENode lhs
@@ -113,9 +92,11 @@ private partial def addEqStep (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit
let lhsRoot getENode lhsNode.root
let rhsRoot getENode rhsNode.root
let mut valueInconsistency := false
let mut trueEqFalse := false
if lhsRoot.interpreted && rhsRoot.interpreted then
if lhsNode.root.isTrue || rhsNode.root.isTrue then
markAsInconsistent
trueEqFalse := true
else
valueInconsistency := true
if (lhsRoot.interpreted && !rhsRoot.interpreted)
@@ -124,7 +105,14 @@ private partial def addEqStep (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit
go rhs lhs rhsNode lhsNode rhsRoot lhsRoot true
else
go lhs rhs lhsNode rhsNode lhsRoot rhsRoot false
-- TODO: propagate value inconsistency
if trueEqFalse then
closeGoalWithTrueEqFalse
unless ( isInconsistent) do
if lhsRoot.ctor && rhsRoot.ctor then
propagateCtor lhsRoot.self rhsRoot.self
unless ( isInconsistent) do
if valueInconsistency then
closeGoalWithValuesEq lhsRoot.self rhsRoot.self
trace[grind.debug] "after addEqStep, {← ppState}"
checkInvariants
where
@@ -145,7 +133,6 @@ where
flipped
}
let parents removeParents lhsRoot.self
-- TODO: set propagateBool
updateRoots lhs rhsNode.root
trace[grind.debug] "{← ppENodeRef lhs} new root {← ppENodeRef rhsNode.root}, {← ppENodeRef (← getRoot lhs)}"
reinsertParents parents
@@ -162,10 +149,11 @@ where
unless ( isInconsistent) do
for parent in parents do
propagateUp parent
unless ( isInconsistent) do
updateMT rhsRoot.self
updateRoots (lhs : Expr) (rootNew : Expr) : GoalM Unit := do
let rec loop (e : Expr) : GoalM Unit := do
-- TODO: propagateBool
let n getENode e
setENode e { n with root := rootNew }
unless ( isInconsistent) do

View File

@@ -0,0 +1,49 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Grind.Types
namespace Lean.Meta.Grind
private partial def propagateInjEqs (eqs : Expr) (proof : Expr) : GoalM Unit := do
match_expr eqs with
| And left right =>
propagateInjEqs left (.proj ``And 0 proof)
propagateInjEqs right (.proj ``And 1 proof)
| Eq _ lhs rhs => pushEq lhs rhs proof
| HEq _ lhs _ rhs => pushHEq lhs rhs proof
| _ =>
trace[grind.issues] "unexpected injectivity theorem result type{indentExpr eqs}"
return ()
/--
Given constructors `a` and `b`, propagate equalities if they are the same,
and close goal if they are different.
-/
def propagateCtor (a b : Expr) : GoalM Unit := do
let aType whnfD ( inferType a)
let bType whnfD ( inferType b)
unless ( withDefault <| isDefEq aType bType) do
return ()
let ctor₁ := a.getAppFn
let ctor₂ := b.getAppFn
if ctor₁ == ctor₂ then
let .const ctorName _ := a.getAppFn | return ()
let injDeclName := Name.mkStr ctorName "inj"
unless ( getEnv).contains injDeclName do return ()
let info getConstInfo injDeclName
let n := info.type.getForallArity
let mask : Array (Option Expr) := mkArray n none
let mask := mask.set! (n-1) (some ( mkEqProof a b))
let injLemma mkAppOptM injDeclName mask
propagateInjEqs ( inferType injLemma) injLemma
else
let .const declName _ := aType.getAppFn | return ()
let noConfusionDeclName := Name.mkStr declName "noConfusion"
unless ( getEnv).contains noConfusionDeclName do return ()
closeGoal ( mkNoConfusion ( getFalseExpr) ( mkEqProof a b))
end Lean.Meta.Grind

View File

@@ -0,0 +1,78 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Util
import Lean.Meta.LitValues
import Lean.Meta.Tactic.Grind.Types
namespace Lean.Meta.Grind
/-- Adds `e` to congruence table. -/
def addCongrTable (e : Expr) : GoalM Unit := do
if let some { e := e' } := ( get).congrTable.find? { e } then
-- `f` and `g` must have the same type.
-- See paper: Congruence Closure in Intensional Type Theory
let f := e.getAppFn
let g := e'.getAppFn
unless isSameExpr f g do
unless ( hasSameType f g) do
trace[grind.issues] "found congruence between{indentExpr e}\nand{indentExpr e'}\nbut functions have different types"
return ()
trace[grind.congr] "{e} = {e'}"
pushEqHEq e e' congrPlaceholderProof
let node getENode e
setENode e { node with cgRoot := e' }
else
modify fun s => { s with congrTable := s.congrTable.insert { e } }
private def updateAppMap (e : Expr) : GoalM Unit := do
let key := e.toHeadIndex
modify fun s => { s with
appMap := if let some es := s.appMap.find? key then
s.appMap.insert key (e :: es)
else
s.appMap.insert key [e]
}
partial def internalize (e : Expr) (generation : Nat) : GoalM Unit := do
if ( alreadyInternalized e) then return ()
match e with
| .bvar .. => unreachable!
| .sort .. => return ()
| .fvar .. | .letE .. | .lam .. | .forallE .. =>
mkENodeCore e (ctor := false) (interpreted := false) (generation := generation)
| .lit .. | .const .. =>
mkENode e generation
| .mvar ..
| .mdata ..
| .proj .. =>
trace[grind.issues] "unexpected term during internalization{indentExpr e}"
mkENodeCore e (ctor := false) (interpreted := false) (generation := generation)
| .app .. =>
if ( isLitValue e) then
-- We do not want to internalize the components of a literal value.
mkENode e generation
else e.withApp fun f args => do
if f.isConstOf ``Lean.Grind.nestedProof && args.size == 2 then
-- We only internalize the proposition. We can skip the proof because of
-- proof irrelevance
let c := args[0]!
internalize c generation
registerParent e c
else
unless f.isConst do
internalize f generation
registerParent e f
for h : i in [: args.size] do
let arg := args[i]
internalize arg generation
registerParent e arg
mkENode e generation
addCongrTable e
updateAppMap e
propagateUp e
end Lean.Meta.Grind

View File

@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.Proof
namespace Lean.Meta.Grind
@@ -19,6 +20,16 @@ private def checkEqc (root : ENode) : GoalM Unit := do
size := size + 1
-- The root of `curr` must be `root`
assert! isSameExpr ( getRoot curr) root.self
-- Check congruence root
if curr.isApp then
if let some { e } := ( get).congrTable.find? { e := curr } then
if ( hasSameType e.getAppFn curr.getAppFn) then
assert! isSameExpr e ( getENode curr).cgRoot
else
assert! isSameExpr curr ( getENode curr).cgRoot
-- If the equivalence class does not have HEq proofs, then the types must be definitionally equal.
unless root.heqProofs do
assert! ( hasSameType curr root.self)
-- Starting at `curr`, following the `target?` field leads to `root`.
let mut n := curr
repeat
@@ -38,13 +49,16 @@ private def checkParents (e : Expr) : GoalM Unit := do
if ( isRoot e) then
for parent in ( getParents e) do
let mut found := false
let checkChild (child : Expr) : GoalM Bool := do
let some childRoot getRoot? child | return false
return isSameExpr childRoot e
-- There is an argument `arg` s.t. root of `arg` is `e`.
for arg in parent.getAppArgs do
if let some argRoot getRoot? arg then
if isSameExpr argRoot e then
found := true
break
assert! found
if ( checkChild arg) then
found := true
break
unless found do
assert! ( checkChild parent.getAppFn)
else
-- All the parents are stored in the root of the equivalence class.
assert! ( getParents e).isEmpty
@@ -54,12 +68,23 @@ private def checkPtrEqImpliesStructEq : GoalM Unit := do
for h₁ : i in [: nodes.size] do
let n₁ := nodes[i]
for h₂ : j in [i+1 : nodes.size] do
let n₂ := nodes[i]
let n₂ := nodes[j]
-- We don't have multiple nodes for the same expression
assert! !isSameExpr n₁.self n₂.self
-- and the two expressions must not be structurally equal
assert! !Expr.equal n₁.self n₂.self
private def checkProofs : GoalM Unit := do
let eqcs getEqcs
for eqc in eqcs do
for a in eqc do
for b in eqc do
unless isSameExpr a b do
let p mkEqProof a b
trace[grind.debug.proofs] "{a} = {b}"
check p
trace[grind.debug.proofs] "checked: {← inferType p}"
/--
Checks basic invariants if `grind.debug` is enabled.
-/
@@ -71,5 +96,7 @@ def checkInvariants (expensive := false) : GoalM Unit := do
checkEqc node
if expensive then
checkPtrEqImpliesStructEq
if expensive && grind.debug.proofs.get ( getOptions) then
checkProofs
end Lean.Meta.Grind

View File

@@ -14,27 +14,6 @@ def ppENodeRef (e : Expr) : GoalM Format := do
let some n getENode? e | return "_"
return f!"#{n.idx}"
/-- Returns expressions in the given expression equivalence class. -/
partial def getEqc (e : Expr) : GoalM (List Expr) :=
go e e []
where
go (first : Expr) (e : Expr) (acc : List Expr) : GoalM (List Expr) := do
let next getNext e
let acc := e :: acc
if isSameExpr first next then
return acc
else
go first next acc
/-- Returns all equivalence classes in the current goal. -/
partial def getEqcs : GoalM (List (List Expr)) := do
let mut r := []
let nodes getENodes
for node in nodes do
if isSameExpr node.root node.self then
r := ( getEqc node.self) :: r
return r
/-- Helper function for pretty printing the state for debugging purposes. -/
def ppENodeDeclValue (e : Expr) : GoalM Format := do
if e.isApp && !( isLitValue e) then

View File

@@ -17,6 +17,7 @@ import Lean.Meta.Tactic.Grind.Cases
import Lean.Meta.Tactic.Grind.Injection
import Lean.Meta.Tactic.Grind.Core
import Lean.Meta.Tactic.Grind.Simp
import Lean.Meta.Tactic.Grind.Run
namespace Lean.Meta.Grind
namespace Preprocessor
@@ -98,6 +99,8 @@ def applyInjection? (goal : Goal) (fvarId : FVarId) : MetaM (Option Goal) := do
return none
partial def loop (goal : Goal) : PreM Unit := do
if goal.inconsistent then
return ()
match ( introNext goal) with
| .done =>
if let some mvarId goal.mvarId.byContra? then
@@ -160,7 +163,10 @@ def preprocess (mvarId : MVarId) (mainDeclName : Name) : MetaM Preprocessor.Stat
Preprocessor.preprocess mvarId |>.run |>.run mainDeclName
def main (mvarId : MVarId) (mainDeclName : Name) : MetaM (List MVarId) := do
let s preprocess mvarId mainDeclName
return s.goals.toList.map (·.mvarId)
let go : GrindM (List MVarId) := do
let s Preprocessor.preprocess mvarId |>.run
let goals := s.goals.toList.filter fun goal => !goal.inconsistent
return goals.map (·.mvarId)
go.run mainDeclName
end Lean.Meta.Grind

View File

@@ -0,0 +1,35 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.ProjFns
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.Internalize
namespace Lean.Meta.Grind
/--
If `parent` is a projection-application `proj_i c`,
check whether the root of the equivalence class containing `c` is a constructor-application `ctor ... a_i ...`.
If so, internalize the term `proj_i (ctor ... a_i ...)` and add the equality `proj_i (ctor ... a_i ...) = a_i`.
-/
def propagateProjEq (parent : Expr) : GoalM Unit := do
let .const declName _ := parent.getAppFn | return ()
let some info getProjectionFnInfo? declName | return ()
unless info.numParams + 1 == parent.getAppNumArgs do return ()
let arg := parent.appArg!
let ctor getRoot arg
unless ctor.isAppOf info.ctorName do return ()
if isSameExpr arg ctor then
let idx := info.numParams + info.i
unless idx < ctor.getAppNumArgs do return ()
let v := ctor.getArg! idx
pushEq parent v ( mkEqRefl v)
else
let newProj := mkApp parent.appFn! ctor
let newProj shareCommon newProj
internalize newProj ( getGeneration parent)
end Lean.Meta.Grind

View File

@@ -9,29 +9,231 @@ import Lean.Meta.Tactic.Grind.Types
namespace Lean.Meta.Grind
private def isEqProof (h : Expr) : MetaM Bool := do
return ( whnfD ( inferType h)).isAppOf ``Eq
private def flipProof (h : Expr) (flipped : Bool) (heq : Bool) : MetaM Expr := do
let mut h' := h
if ( pure heq <&&> isEqProof h') then
h' mkHEqOfEq h'
if flipped then
if heq then mkHEqSymm h' else mkEqSymm h'
else
return h'
private def mkRefl (a : Expr) (heq : Bool) : MetaM Expr :=
if heq then mkHEqRefl a else mkEqRefl a
private def mkTrans (h₁ h₂ : Expr) (heq : Bool) : MetaM Expr :=
if heq then
mkHEqTrans h₁ h₂
else
mkEqTrans h₁ h₂
private def mkTrans' (h₁ : Option Expr) (h₂ : Expr) (heq : Bool) : MetaM Expr := do
let some h₁ := h₁ | return h₂
mkTrans h₁ h₂ heq
/--
Given `h : HEq a b`, returns a proof `a = b` if `heq == false`.
Otherwise, it returns `h`.
-/
private def mkEqOfHEqIfNeeded (h : Expr) (heq : Bool) : MetaM Expr := do
if heq then return h else mkEqOfHEq h
/--
Given `lhs` and `rhs` that are in the same equivalence class,
find the common expression that are in the paths from `lhs` and `rhs` to
the root of their equivalence class.
Recall that this expression must exist since it is the root itself in the
worst case.
-/
private def findCommon (lhs rhs : Expr) : GoalM Expr := do
let mut visited : RBMap Nat Expr compare := {}
let mut it := lhs
-- Mark elements found following the path from `lhs` to the root.
repeat
let n getENode it
visited := visited.insert n.idx n.self
let some target := n.target? | break
it := target
-- Find the marked element from the path from `rhs` to the root.
it := rhs
repeat
let n getENode it
if let some common := visited.find? n.idx then
return common
let some target := n.target? | unreachable! --
it := target
unreachable!
/--
Returns `true` if we can construct a congruence proof for `lhs = rhs` using `congrArg`, `congrFun`, and `congr`.
`f` (`g`) is the function of the `lhs` (`rhs`) application. `numArgs` is the number of arguments.
-/
private partial def isCongrDefaultProofTarget (lhs rhs : Expr) (f g : Expr) (numArgs : Nat) : GoalM Bool := do
unless isSameExpr f g do return false
let info := ( getFunInfo f).paramInfo
let rec loop (lhs rhs : Expr) (i : Nat) : GoalM Bool := do
if lhs.isApp then
let a₁ := lhs.appArg!
let a₂ := rhs.appArg!
let i := i - 1
unless isSameExpr a₁ a₂ do
if h : i < info.size then
if info[i].hasFwdDeps then
-- Cannot use `congrArg` because there are forward dependencies
return false
else
return false -- Don't have information about argument
loop lhs.appFn! rhs.appFn! i
else
return true
loop lhs rhs numArgs
mutual
/--
Given `lhs` and `rhs` proof terms of the form `nestedProof p hp` and `nestedProof q hq`,
constructs a congruence proof for `HEq (nestedProof p hp) (nestedProof q hq)`.
`p` and `q` are in the same equivalence class.
-/
private partial def mkNestedProofCongr (lhs rhs : Expr) (heq : Bool) : GoalM Expr := do
let p := lhs.appFn!.appArg!
let hp := lhs.appArg!
let q := rhs.appFn!.appArg!
let hq := rhs.appArg!
let h := mkApp5 (mkConst ``Lean.Grind.nestedProof_congr) p q ( mkEqProofCore p q false) hp hq
mkEqOfHEqIfNeeded h heq
/--
Constructs a congruence proof for `lhs` and `rhs` using `congr`, `congrFun`, and `congrArg`.
This function assumes `isCongrDefaultProofTarget` returned `true`.
-/
private partial def mkCongrDefaultProof (lhs rhs : Expr) (heq : Bool) : GoalM Expr := do
let rec loop (lhs rhs : Expr) : GoalM (Option Expr) := do
if lhs.isApp then
let a₁ := lhs.appArg!
let a₂ := rhs.appArg!
if let some proof loop lhs.appFn! rhs.appFn! then
if isSameExpr a₁ a₂ then
mkCongrFun proof a₁
else
mkCongr proof ( mkEqProofCore a₁ a₂ false)
else if isSameExpr a₁ a₂ then
return none -- refl case
else
mkCongrArg lhs.appFn! ( mkEqProofCore a₁ a₂ false)
else
return none
let r := ( loop lhs rhs).get!
if heq then mkHEqOfEq r else return r
/-- Constructs a congruence proof for `lhs` and `rhs`. -/
private partial def mkCongrProof (lhs rhs : Expr) (heq : Bool) : GoalM Expr := do
let f := lhs.getAppFn
let g := rhs.getAppFn
let numArgs := lhs.getAppNumArgs
assert! rhs.getAppNumArgs == numArgs
if f.isConstOf ``Lean.Grind.nestedProof && g.isConstOf ``Lean.Grind.nestedProof && numArgs == 2 then
mkNestedProofCongr lhs rhs heq
else if ( isCongrDefaultProofTarget lhs rhs f g numArgs) then
mkCongrDefaultProof lhs rhs heq
else
let thm mkHCongrWithArity f numArgs
assert! thm.argKinds.size == numArgs
let rec loop (lhs rhs : Expr) (i : Nat) : GoalM Expr := do
let i := i - 1
if lhs.isApp then
let proof loop lhs.appFn! rhs.appFn! i
let a₁ := lhs.appArg!
let a₂ := rhs.appArg!
let k := thm.argKinds[i]!
return mkApp3 proof a₁ a₂ ( mkEqProofCore a₁ a₂ (k matches .heq))
else
return thm.proof
let proof loop lhs rhs numArgs
if isSameExpr f g then
mkEqOfHEqIfNeeded proof heq
else
/-
`lhs` is of the form `f a_1 ... a_n`
`rhs` is of the form `g b_1 ... b_n`
`proof : HEq (f a_1 ... a_n) (f b_1 ... b_n)`
We construct a proof for `HEq (f a_1 ... a_n) (g b_1 ... b_n)` using `Eq.ndrec`
-/
let motive withLocalDeclD ( mkFreshUserName `x) ( inferType f) fun x => do
mkLambdaFVars #[x] ( mkHEq lhs (mkAppN x rhs.getAppArgs))
let fEq mkEqProofCore f g false
let proof mkEqNDRec motive proof fEq
mkEqOfHEqIfNeeded proof heq
private partial def realizeEqProof (lhs rhs : Expr) (h : Expr) (flipped : Bool) (heq : Bool) : GoalM Expr := do
let h if h == congrPlaceholderProof then
mkCongrProof lhs rhs heq
else
flipProof h flipped heq
/-- Given `acc : lhs₀ = lhs`, returns a proof of `lhs₀ = common`. -/
private partial def mkProofTo (lhs : Expr) (common : Expr) (acc : Option Expr) (heq : Bool) : GoalM (Option Expr) := do
if isSameExpr lhs common then
return acc
let n getENode lhs
let some target := n.target? | unreachable!
let some h := n.proof? | unreachable!
let h realizeEqProof lhs target h n.flipped heq
-- h : lhs = target
let acc mkTrans' acc h heq
mkProofTo target common (some acc) heq
/-- Given `lhsEqCommon : lhs = common`, returns a proof for `lhs = rhs`. -/
private partial def mkProofFrom (rhs : Expr) (common : Expr) (lhsEqCommon? : Option Expr) (heq : Bool) : GoalM (Option Expr) := do
if isSameExpr rhs common then
return lhsEqCommon?
let n getENode rhs
let some target := n.target? | unreachable!
let some h := n.proof? | unreachable!
let h realizeEqProof target rhs h (!n.flipped) heq
-- `h : target = rhs`
let h' mkProofFrom target common lhsEqCommon? heq
-- `h' : lhs = target`
mkTrans' h' h heq
private partial def mkEqProofCore (lhs rhs : Expr) (heq : Bool) : GoalM Expr := do
if isSameExpr lhs rhs then
return ( mkRefl lhs heq)
let n₁ getENode lhs
let n₂ getENode rhs
assert! isSameExpr n₁.root n₂.root
let common findCommon lhs rhs
let lhsEqCommon? mkProofTo lhs common none heq
let some lhsEqRhs mkProofFrom rhs common lhsEqCommon? heq | unreachable!
return lhsEqRhs
end
/--
Returns a proof that `a = b` (or `HEq a b`).
It assumes `a` and `b` are in the same equivalence class.
-/
def mkEqProof (a b : Expr) : GoalM Expr := do
-- TODO
if ( isDefEq ( inferType a) ( inferType b)) then
mkSorry ( mkEq a b) (synthetic := false)
else
mkSorry ( mkHEq a b) (synthetic := false)
@[export lean_grind_mk_eq_proof]
def mkEqProofImpl (a b : Expr) : GoalM Expr := do
let p go
trace[grind.proof.detail] "{p}"
return p
where
go : GoalM Expr := do
let n getRootENode a
if !n.heqProofs then
trace[grind.proof] "{a} = {b}"
mkEqProofCore a b (heq := false)
else
if ( hasSameType a b) then
trace[grind.proof] "{a} = {b}"
mkEqOfHEq ( mkEqProofCore a b (heq := true))
else
trace[grind.proof] "{a} ≡ {b}"
mkEqProofCore a b (heq := true)
/--
Returns a proof that `a = True`.
It assumes `a` and `True` are in the same equivalence class.
-/
def mkEqTrueProof (a : Expr) : GoalM Expr := do
mkEqProof a ( getTrueExpr)
/--
Returns a proof that `a = False`.
It assumes `a` and `False` are in the same equivalence class.
-/
def mkEqFalseProof (a : Expr) : GoalM Expr := do
mkEqProof a ( getFalseExpr)
def mkHEqProof (a b : Expr) : GoalM Expr :=
mkEqProofCore a b (heq := true)
end Lean.Meta.Grind

View File

@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
prelude
import Init.Grind
import Lean.Meta.Tactic.Grind.Proof
import Lean.Meta.Tactic.Grind.PropagatorAttr
namespace Lean.Meta.Grind
@@ -105,12 +106,13 @@ This function performs the following:
- If `(Not a) = True`, propagates `a = False`.
-/
builtin_grind_propagator propagateNotDown Not := fun e => do
let_expr Not a := e | return ()
if ( isEqFalse e) then
let_expr Not a := e | return ()
pushEqTrue a <| mkApp2 (mkConst ``Lean.Grind.eq_true_of_not_eq_false) a ( mkEqFalseProof e)
else if ( isEqTrue e) then
let_expr Not a := e | return ()
pushEqFalse a <| mkApp2 (mkConst ``Lean.Grind.eq_false_of_not_eq_true) a ( mkEqTrueProof e)
else if ( isEqv e a) then
closeGoal <| mkApp2 (mkConst ``Lean.Grind.false_of_not_eq_self) a ( mkEqProof e a)
/-- Propagates `Eq` upwards -/
builtin_grind_propagator propagateEqUp Eq := fun e => do
@@ -120,7 +122,7 @@ builtin_grind_propagator propagateEqUp ↑Eq := fun e => do
else if ( isEqTrue b) then
pushEq e a <| mkApp3 (mkConst ``Lean.Grind.eq_eq_of_eq_true_right) a b ( mkEqTrueProof b)
else if ( isEqv a b) then
pushEqTrue e <| mkApp2 (mkConst ``of_eq_true) e ( mkEqProof a b)
pushEqTrue e <| mkApp2 (mkConst ``eq_true) e ( mkEqProof a b)
/-- Propagates `Eq` downwards -/
builtin_grind_propagator propagateEqDown Eq := fun e => do
@@ -134,4 +136,10 @@ builtin_grind_propagator propagateHEqDown ↓HEq := fun e => do
let_expr HEq _ a _ b := e | return ()
pushHEq a b <| mkApp2 (mkConst ``of_eq_true) e ( mkEqTrueProof e)
/-- Propagates `HEq` upwards -/
builtin_grind_propagator propagateHEqUp HEq := fun e => do
let_expr HEq _ a _ b := e | return ()
if ( isEqv a b) then
pushEqTrue e <| mkApp2 (mkConst ``eq_true) e ( mkHEqProof a b)
end Lean.Meta.Grind

View File

@@ -0,0 +1,61 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind
import Lean.Meta.Tactic.Grind.Proof
namespace Lean.Meta.Grind
/-- Builtin propagators. -/
structure BuiltinPropagators where
up : Std.HashMap Name Propagator := {}
down : Std.HashMap Name Propagator := {}
deriving Inhabited
builtin_initialize builtinPropagatorsRef : IO.Ref BuiltinPropagators IO.mkRef {}
private def registerBuiltinPropagatorCore (declName : Name) (up : Bool) (proc : Propagator) : IO Unit := do
unless ( initializing) do
throw (IO.userError s!"invalid builtin `grind` propagator declaration, it can only be registered during initialization")
if up then
if ( builtinPropagatorsRef.get).up.contains declName then
throw (IO.userError s!"invalid builtin `grind` upward propagator `{declName}`, it has already been declared")
builtinPropagatorsRef.modify fun { up, down } => { up := up.insert declName proc, down }
else
if ( builtinPropagatorsRef.get).down.contains declName then
throw (IO.userError s!"invalid builtin `grind` downward propagator `{declName}`, it has already been declared")
builtinPropagatorsRef.modify fun { up, down } => { up, down := down.insert declName proc }
def registerBuiltinUpwardPropagator (declName : Name) (proc : Propagator) : IO Unit :=
registerBuiltinPropagatorCore declName true proc
def registerBuiltinDownwardPropagator (declName : Name) (proc : Propagator) : IO Unit :=
registerBuiltinPropagatorCore declName false proc
private def addBuiltin (propagatorName : Name) (stx : Syntax) : AttrM Unit := do
let go : MetaM Unit := do
let up := stx[1].getKind == ``Lean.Parser.Tactic.simpPost
let addDeclName := if up then
``registerBuiltinUpwardPropagator
else
``registerBuiltinDownwardPropagator
let declName resolveGlobalConstNoOverload stx[2]
let val := mkAppN (mkConst addDeclName) #[toExpr declName, mkConst propagatorName]
let initDeclName mkFreshUserName (propagatorName ++ `declare)
declareBuiltin initDeclName val
go.run' {}
builtin_initialize
registerBuiltinAttribute {
ref := by exact decl_name%
name := `grindPropagatorBuiltinAttr
descr := "Builtin `grind` propagator procedure"
applicationTime := AttributeApplicationTime.afterCompilation
erase := fun _ => throwError "Not implemented yet, [-builtin_simproc]"
add := fun declName stx _ => addBuiltin declName stx
}
end Lean.Meta.Grind

View File

@@ -0,0 +1,53 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Grind.Lemmas
import Lean.Meta.Tactic.Grind.Types
import Lean.Meta.Tactic.Grind.PropagatorAttr
import Lean.Meta.Tactic.Grind.Proj
namespace Lean.Meta.Grind
def mkMethods : CoreM Methods := do
let builtinPropagators builtinPropagatorsRef.get
return {
propagateUp := fun e => do
let .const declName _ := e.getAppFn | return ()
propagateProjEq e
if let some prop := builtinPropagators.up[declName]? then
prop e
propagateDown := fun e => do
let .const declName _ := e.getAppFn | return ()
if let some prop := builtinPropagators.down[declName]? then
prop e
}
def GrindM.run (x : GrindM α) (mainDeclName : Name) : MetaM α := do
let scState := ShareCommon.State.mk _
let (falseExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``False)
let (trueExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``True)
let thms grindNormExt.getTheorems
let simprocs := #[( grindNormSimprocExt.getSimprocs)]
let simp Simp.mkContext
(config := { arith := true })
(simpTheorems := #[thms])
(congrTheorems := ( getSimpCongrTheorems))
x ( mkMethods).toMethodsRef { mainDeclName, simprocs, simp } |>.run' { scState, trueExpr, falseExpr }
@[inline] def GoalM.run (goal : Goal) (x : GoalM α) : GrindM (α × Goal) :=
goal.mvarId.withContext do StateRefT'.run x goal
@[inline] def GoalM.run' (goal : Goal) (x : GoalM Unit) : GrindM Goal :=
goal.mvarId.withContext do StateRefT'.run' (x *> get) goal
def mkGoal (mvarId : MVarId) : GrindM Goal := do
let trueExpr getTrueExpr
let falseExpr getFalseExpr
GoalM.run' { mvarId } do
mkENodeCore falseExpr (interpreted := true) (ctor := false) (generation := 0)
mkENodeCore trueExpr (interpreted := true) (ctor := false) (generation := 0)
end Lean.Meta.Grind

View File

@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
-/
prelude
import Lean.Util.ShareCommon
import Lean.HeadIndex
import Lean.Meta.Basic
import Lean.Meta.CongrTheorems
import Lean.Meta.AbstractNestedProofs
@@ -20,6 +21,9 @@ namespace Lean.Meta.Grind
-- inserted into the E-graph
unsafe ptrEq a b
/-- We use this auxiliary constant to mark delayed congruence proofs. -/
def congrPlaceholderProof := mkConst (Name.mkSimple "[congruence]")
/--
Returns `true` if `e` is `True`, `False`, or a literal value.
See `LitValues` for supported literals.
@@ -34,6 +38,12 @@ register_builtin_option grind.debug : Bool := {
descr := "check invariants after updates"
}
register_builtin_option grind.debug.proofs : Bool := {
defValue := false
group := "debug"
descr := "check proofs between the elements of all equivalence classes"
}
/-- Context for `GrindM` monad. -/
structure Context where
simp : Simp.Context
@@ -249,6 +259,12 @@ structure Goal where
enodes : ENodes := {}
parents : ParentMap := {}
congrTable : CongrTable enodes := {}
/--
A mapping from each function application index (`HeadIndex`) to a list of applications with that index.
Recall that the `HeadIndex` for a constant is its constant name, and for a free variable,
it is its unique id.
-/
appMap : PHashMap HeadIndex (List Expr) := {}
/-- Equations to be processed. -/
newEqs : Array NewEq := #[]
/-- `inconsistent := true` if `ENode`s for `True` and `False` are in the same equivalence class. -/
@@ -266,10 +282,6 @@ abbrev GoalM := StateRefT Goal GrindM
abbrev Propagator := Expr GoalM Unit
/-- Return `true` if the goal is inconsistent. -/
def isInconsistent : GoalM Bool :=
return ( get).inconsistent
/-- Returns `true` if `e` is the internalized `True` expression. -/
def isTrueExpr (e : Expr) : GrindM Bool :=
return isSameExpr e ( getTrueExpr)
@@ -291,6 +303,10 @@ def getENode (e : Expr) : GoalM ENode := do
| throwError "internal `grind` error, term has not been internalized{indentExpr e}"
return n
/-- Returns the generation of the given term. Is assumes it has been internalized -/
def getGeneration (e : Expr) : GoalM Nat :=
return ( getENode e).generation
/-- Returns `true` if `e` is in the equivalence class of `True`. -/
def isEqTrue (e : Expr) : GoalM Bool := do
let n getENode e
@@ -303,9 +319,12 @@ def isEqFalse (e : Expr) : GoalM Bool := do
/-- Returns `true` if `a` and `b` are in the same equivalence class. -/
def isEqv (a b : Expr) : GoalM Bool := do
let na getENode a
let nb getENode b
return isSameExpr na.root nb.root
if isSameExpr a b then
return true
else
let na getENode a
let nb getENode b
return isSameExpr na.root nb.root
/-- Returns `true` if the root of its equivalence class. -/
def isRoot (e : Expr) : GoalM Bool := do
@@ -321,6 +340,10 @@ def getRoot? (e : Expr) : GoalM (Option Expr) := do
def getRoot (e : Expr) : GoalM Expr :=
return ( getENode e).root
/-- Returns the root enode in the equivalence class of `e`. -/
def getRootENode (e : Expr) : GoalM ENode := do
getENode ( getRoot e)
/-- Returns the next element in the equivalence class of `e`. -/
def getNext (e : Expr) : GoalM Expr :=
return ( getENode e).next
@@ -340,8 +363,12 @@ Otherwise, it pushes `HEq lhs rhs`.
def pushEqCore (lhs rhs proof : Expr) (isHEq : Bool) : GoalM Unit :=
modify fun s => { s with newEqs := s.newEqs.push { lhs, rhs, proof, isHEq } }
/-- Return `true` if `a` and `b` have the same type. -/
def hasSameType (a b : Expr) : MetaM Bool :=
withDefault do isDefEq ( inferType a) ( inferType b)
@[inline] def pushEqHEq (lhs rhs proof : Expr) : GoalM Unit := do
if ( isDefEq ( inferType lhs) ( inferType rhs)) then
if ( hasSameType lhs rhs) then
pushEqCore lhs rhs proof (isHEq := false)
else
pushEqCore lhs rhs proof (isHEq := true)
@@ -428,6 +455,50 @@ def mkENode (e : Expr) (generation : Nat) : GoalM Unit := do
let interpreted isInterpreted e
mkENodeCore e interpreted ctor generation
/-- Return `true` if the goal is inconsistent. -/
def isInconsistent : GoalM Bool :=
return ( get).inconsistent
/--
Returns a proof that `a = b` (or `HEq a b`).
It assumes `a` and `b` are in the same equivalence class.
-/
-- Forward definition
@[extern "lean_grind_mk_eq_proof"]
opaque mkEqProof (a b : Expr) : GoalM Expr
/--
Returns a proof that `a = True`.
It assumes `a` and `True` are in the same equivalence class.
-/
def mkEqTrueProof (a : Expr) : GoalM Expr := do
mkEqProof a ( getTrueExpr)
/--
Returns a proof that `a = False`.
It assumes `a` and `False` are in the same equivalence class.
-/
def mkEqFalseProof (a : Expr) : GoalM Expr := do
mkEqProof a ( getFalseExpr)
/-- Marks current goal as inconsistent without assigning `mvarId`. -/
def markAsInconsistent : GoalM Unit := do
modify fun s => { s with inconsistent := true }
/--
Closes the current goal using the given proof of `False` and
marks it as inconsistent if it is not already marked so.
-/
def closeGoal (falseProof : Expr) : GoalM Unit := do
markAsInconsistent
let mvarId := ( get).mvarId
unless ( mvarId.isAssigned) do
let target mvarId.getType
if target.isFalse then
mvarId.assign falseProof
else
mvarId.assign ( mkFalseElim target falseProof)
/-- Returns all enodes in the goal -/
def getENodes : GoalM (Array ENode) := do
-- We must sort because we are using pointer addresses as keys in `enodes`
@@ -452,12 +523,12 @@ def forEachEqc (f : ENode → GoalM Unit) : GoalM Unit := do
if isSameExpr n.self n.root then
f n
private structure Methods where
structure Methods where
propagateUp : Propagator := fun _ => return ()
propagateDown : Propagator := fun _ => return ()
deriving Inhabited
private def Methods.toMethodsRef (m : Methods) : MethodsRef :=
def Methods.toMethodsRef (m : Methods) : MethodsRef :=
unsafe unsafeCast m
private def MethodsRef.toMethods (m : MethodsRef) : Methods :=
@@ -472,91 +543,25 @@ def propagateUp (e : Expr) : GoalM Unit := do
def propagateDown (e : Expr) : GoalM Unit := do
( getMethods).propagateDown e
/-- Builtin propagators. -/
structure BuiltinPropagators where
up : Std.HashMap Name Propagator := {}
down : Std.HashMap Name Propagator := {}
deriving Inhabited
builtin_initialize builtinPropagatorsRef : IO.Ref BuiltinPropagators IO.mkRef {}
private def registerBuiltinPropagatorCore (declName : Name) (up : Bool) (proc : Propagator) : IO Unit := do
unless ( initializing) do
throw (IO.userError s!"invalid builtin `grind` propagator declaration, it can only be registered during initialization")
if up then
if ( builtinPropagatorsRef.get).up.contains declName then
throw (IO.userError s!"invalid builtin `grind` upward propagator `{declName}`, it has already been declared")
builtinPropagatorsRef.modify fun { up, down } => { up := up.insert declName proc, down }
else
if ( builtinPropagatorsRef.get).down.contains declName then
throw (IO.userError s!"invalid builtin `grind` downward propagator `{declName}`, it has already been declared")
builtinPropagatorsRef.modify fun { up, down } => { up, down := down.insert declName proc }
def registerBuiltinUpwardPropagator (declName : Name) (proc : Propagator) : IO Unit :=
registerBuiltinPropagatorCore declName true proc
def registerBuiltinDownwardPropagator (declName : Name) (proc : Propagator) : IO Unit :=
registerBuiltinPropagatorCore declName false proc
private def addBuiltin (propagatorName : Name) (stx : Syntax) : AttrM Unit := do
let go : MetaM Unit := do
let up := stx[1].getKind == ``Lean.Parser.Tactic.simpPost
let addDeclName := if up then
``registerBuiltinUpwardPropagator
/-- Returns expressions in the given expression equivalence class. -/
partial def getEqc (e : Expr) : GoalM (List Expr) :=
go e e []
where
go (first : Expr) (e : Expr) (acc : List Expr) : GoalM (List Expr) := do
let next getNext e
let acc := e :: acc
if isSameExpr first next then
return acc
else
``registerBuiltinDownwardPropagator
let declName resolveGlobalConstNoOverload stx[2]
let val := mkAppN (mkConst addDeclName) #[toExpr declName, mkConst propagatorName]
let initDeclName mkFreshUserName (propagatorName ++ `declare)
declareBuiltin initDeclName val
go.run' {}
go first next acc
builtin_initialize
registerBuiltinAttribute {
ref := by exact decl_name%
name := `grindPropagatorBuiltinAttr
descr := "Builtin `grind` propagator procedure"
applicationTime := AttributeApplicationTime.afterCompilation
erase := fun _ => throwError "Not implemented yet, [-builtin_simproc]"
add := fun declName stx _ => addBuiltin declName stx
}
def mkMethods : CoreM Methods := do
let builtinPropagators builtinPropagatorsRef.get
return {
propagateUp := fun e => do
let .const declName _ := e.getAppFn | return ()
if let some prop := builtinPropagators.up[declName]? then
prop e
propagateDown := fun e => do
let .const declName _ := e.getAppFn | return ()
if let some prop := builtinPropagators.down[declName]? then
prop e
}
def GrindM.run (x : GrindM α) (mainDeclName : Name) : MetaM α := do
let scState := ShareCommon.State.mk _
let (falseExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``False)
let (trueExpr, scState) := ShareCommon.State.shareCommon scState (mkConst ``True)
let thms grindNormExt.getTheorems
let simprocs := #[( grindNormSimprocExt.getSimprocs)]
let simp Simp.mkContext
(config := { arith := true })
(simpTheorems := #[thms])
(congrTheorems := ( getSimpCongrTheorems))
x ( mkMethods).toMethodsRef { mainDeclName, simprocs, simp } |>.run' { scState, trueExpr, falseExpr }
@[inline] def GoalM.run (goal : Goal) (x : GoalM α) : GrindM (α × Goal) :=
goal.mvarId.withContext do StateRefT'.run x goal
@[inline] def GoalM.run' (goal : Goal) (x : GoalM Unit) : GrindM Goal :=
goal.mvarId.withContext do StateRefT'.run' (x *> get) goal
def mkGoal (mvarId : MVarId) : GrindM Goal := do
let trueExpr getTrueExpr
let falseExpr getFalseExpr
GoalM.run' { mvarId } do
mkENodeCore falseExpr (interpreted := true) (ctor := false) (generation := 0)
mkENodeCore trueExpr (interpreted := true) (ctor := false) (generation := 0)
/-- Returns all equivalence classes in the current goal. -/
partial def getEqcs : GoalM (List (List Expr)) := do
let mut r := []
let nodes getENodes
for node in nodes do
if isSameExpr node.root node.self then
r := ( getEqc node.self) :: r
return r
end Lean.Meta.Grind

View File

@@ -49,17 +49,13 @@ If they do, they must disable the following `simprocs`.
-/
builtin_dsimproc [simp, seval] reduceNeg ((- _ : Int)) := fun e => do
unless e.isAppOfArity ``Neg.neg 3 do return .continue
let arg := e.appArg!
let_expr Neg.neg _ _ arg e | return .continue
if arg.isAppOfArity ``OfNat.ofNat 3 then
-- We return .done to ensure `Neg.neg` is not unfolded even when `ground := true`.
return .done e
else
let some v fromExpr? arg | return .continue
if v < 0 then
return .done <| toExpr (- v)
else
return .done <| toExpr v
return .done <| toExpr (- v)
/-- Return `.done` for positive Int values. We don't want to unfold in the symbolic evaluator. -/
builtin_dsimproc [seval] isPosValue ((OfNat.ofNat _ : Int)) := fun e => do

View File

@@ -293,8 +293,15 @@ The semantics for `BVExpr`.
-/
def eval (assign : Assignment) : BVExpr w BitVec w
| .var idx =>
let bv := assign.get idx
bv.truncate w
let packedBv := assign.get idx
/-
This formulation improves performance, as in a well formed expression the condition always holds
so there is no need for the more involved `BitVec.truncate` logic.
-/
if h : packedBv.w = w then
h packedBv.bv
else
packedBv.bv.truncate w
| .const val => val
| .zeroExtend v expr => BitVec.zeroExtend v (eval assign expr)
| .extract start len expr => BitVec.extractLsb' start len (eval assign expr)
@@ -308,8 +315,13 @@ def eval (assign : Assignment) : BVExpr w → BitVec w
| .arithShiftRight lhs rhs => BitVec.sshiftRight' (eval assign lhs) (eval assign rhs)
@[simp]
theorem eval_var : eval assign ((.var idx) : BVExpr w) = (assign.get idx).bv.truncate _ := by
rfl
theorem eval_var : eval assign ((.var idx) : BVExpr w) = (assign.get idx).bv.truncate w := by
rw [eval]
split
· next h =>
subst h
simp
· rfl
@[simp]
theorem eval_const : eval assign (.const val) = val := by rfl
@@ -454,7 +466,7 @@ def eval (assign : BVExpr.Assignment) (expr : BVLogicalExpr) : Bool :=
@[simp] theorem eval_not : eval assign (.not x) = !eval assign x := rfl
@[simp] theorem eval_gate : eval assign (.gate g x y) = g.eval (eval assign x) (eval assign y) := rfl
@[simp] theorem eval_ite :
eval assign (.ite d l r) = if (eval assign d) then (eval assign l) else (eval assign r) := rfl
eval assign (.ite d l r) = bif (eval assign d) then (eval assign l) else (eval assign r) := rfl
def Sat (x : BVLogicalExpr) (assign : BVExpr.Assignment) : Prop := eval assign x = true

View File

@@ -18,7 +18,7 @@ inductive Gate
| and
| xor
| beq
| imp
| or
namespace Gate
@@ -26,13 +26,13 @@ def toString : Gate → String
| and => "&&"
| xor => "^^"
| beq => "=="
| imp => "->"
| or => "||"
def eval : Gate Bool Bool Bool
| and => (· && ·)
| xor => (· ^^ ·)
| beq => (· == ·)
| imp => (· ·)
| or => (· || ·)
end Gate
@@ -59,13 +59,13 @@ def eval (a : α → Bool) : BoolExpr α → Bool
| .const b => b
| .not x => !eval a x
| .gate g x y => g.eval (eval a x) (eval a y)
| .ite d l r => if d.eval a then l.eval a else r.eval a
| .ite d l r => bif d.eval a then l.eval a else r.eval a
@[simp] theorem eval_literal : eval a (.literal l) = a l := rfl
@[simp] theorem eval_const : eval a (.const b) = b := rfl
@[simp] theorem eval_not : eval a (.not x) = !eval a x := rfl
@[simp] theorem eval_gate : eval a (.gate g x y) = g.eval (eval a x) (eval a y) := rfl
@[simp] theorem eval_ite : eval a (.ite d l r) = if d.eval a then l.eval a else r.eval a := rfl
@[simp] theorem eval_ite : eval a (.ite d l r) = bif d.eval a then l.eval a else r.eval a := rfl
def Sat (a : α Bool) (x : BoolExpr α) : Prop := eval a x = true
def Unsat (x : BoolExpr α) : Prop := f, eval f x = false

View File

@@ -75,9 +75,9 @@ where
let ret := aig.mkBEqCached input
have := LawfulOperator.le_size (f := mkBEqCached) aig input
ret, by dsimp only [ret] at lextend rextend ; omega
| .imp =>
let ret := aig.mkImpCached input
have := LawfulOperator.le_size (f := mkImpCached) aig input
| .or =>
let ret := aig.mkOrCached input
have := LawfulOperator.le_size (f := mkOrCached) aig input
ret, by dsimp only [ret] at lextend rextend ; omega
namespace ofBoolExprCached
@@ -127,9 +127,9 @@ theorem go_decl_eq (idx) (aig : AIG β) (h : idx < aig.decls.size) (hbounds) :
| beq =>
simp only [go]
rw [AIG.LawfulOperator.decl_eq (f := mkBEqCached), rih, lih]
| imp =>
| or =>
simp only [go]
rw [AIG.LawfulOperator.decl_eq (f := mkImpCached), rih, lih]
rw [AIG.LawfulOperator.decl_eq (f := mkOrCached), rih, lih]
theorem go_isPrefix_aig {aig : AIG β} :
IsPrefix aig.decls (go aig expr atomHandler).val.aig.decls := by

View File

@@ -118,7 +118,6 @@ theorem BitVec.srem_umod (x y : BitVec w) :
rw [BitVec.srem_eq]
cases x.msb <;> cases y.msb <;> simp
attribute [bv_normalize] Bool.cond_eq_if
attribute [bv_normalize] BitVec.abs_eq
attribute [bv_normalize] BitVec.twoPow_eq

View File

@@ -41,7 +41,14 @@ attribute [bv_normalize] Bool.not_not
attribute [bv_normalize] Bool.and_self_left
attribute [bv_normalize] Bool.and_self_right
attribute [bv_normalize] eq_self
attribute [bv_normalize] ite_self
attribute [bv_normalize] Bool.cond_self
attribute [bv_normalize] cond_false
attribute [bv_normalize] cond_true
attribute [bv_normalize] Bool.cond_not
@[bv_normalize]
theorem if_eq_cond {b : Bool} {x y : α} : (if b = true then x else y) = (bif b then x else y) := by
rw [cond_eq_if]
@[bv_normalize]
theorem Bool.not_xor : (a b : Bool), !(a ^^ b) = (a == b) := by decide

View File

@@ -13,8 +13,6 @@ This module contains the `Prop` simplifying part of the `bv_normalize` simp set.
namespace Std.Tactic.BVDecide
namespace Frontend.Normalize
attribute [bv_normalize] ite_true
attribute [bv_normalize] ite_false
attribute [bv_normalize] dite_true
attribute [bv_normalize] dite_false
attribute [bv_normalize] and_true

View File

@@ -123,12 +123,12 @@ theorem umod_congr (lhs rhs lhs' rhs' : BitVec w) (h1 : lhs' = lhs) (h2 : rhs' =
(lhs' % rhs') = (lhs % rhs) := by
simp[*]
theorem if_true (discr : Bool) (lhs rhs : BitVec w) :
decide ((discr == true) = true ((if discr = true then lhs else rhs) == lhs) = true) = true := by
theorem cond_true (discr : Bool) (lhs rhs : BitVec w) :
(!discr || ((bif discr then lhs else rhs) == lhs)) = true := by
cases discr <;> simp
theorem if_false (discr : Bool) (lhs rhs : BitVec w) :
decide ((discr == false) = true ((if discr = true then lhs else rhs) == rhs) = true) = true := by
theorem cond_false (discr : Bool) (lhs rhs : BitVec w) :
(discr || ((bif discr then lhs else rhs) == rhs)) = true := by
cases discr <;> simp
end BitVec
@@ -150,13 +150,13 @@ theorem beq_congr (lhs rhs lhs' rhs' : Bool) (h1 : lhs' = lhs) (h2 : rhs' = rhs)
(lhs' == rhs') = (lhs == rhs) := by
simp[*]
theorem imp_congr (lhs rhs lhs' rhs' : Bool) (h1 : lhs' = lhs) (h2 : rhs' = rhs) :
(decide (lhs' rhs')) = (decide (lhs rhs)) := by
theorem or_congr (lhs rhs lhs' rhs' : Bool) (h1 : lhs' = lhs) (h2 : rhs' = rhs) :
(lhs' || rhs') = (lhs || rhs) := by
simp[*]
theorem ite_congr (discr lhs rhs discr' lhs' rhs' : Bool) (h1 : discr' = discr) (h2 : lhs' = lhs)
theorem cond_congr (discr lhs rhs discr' lhs' rhs' : Bool) (h1 : discr' = discr) (h2 : lhs' = lhs)
(h3 : rhs' = rhs) :
(if discr' = true then lhs' else rhs') = (if discr = true then lhs else rhs) := by
(bif discr' = true then lhs' else rhs') = (bif discr = true then lhs else rhs) := by
simp[*]
theorem false_of_eq_true_of_eq_false (h₁ : x = true) (h₂ : x = false) : False := by

6
tests/lean/run/6043.lean Normal file
View File

@@ -0,0 +1,6 @@
import Std.Tactic.BVDecide
theorem extracted_1 (x : BitVec 32) :
BitVec.ofBool (x != 51#32) &&& BitVec.ofBool (x != 50#32) =
BitVec.ofBool (4294967294#32 > x + 4294967244#32) := by
bv_decide

16
tests/lean/run/6467.lean Normal file
View File

@@ -0,0 +1,16 @@
theorem confuses_the_user : -(no_index (OfNat.ofNat <| nat_lit 2)) = -2 := by
simp
theorem ex : -(no_index (OfNat.ofNat <| nat_lit 2)) = (2 : Int) := by
simp only [Int.reduceNeg]
guard_target = -2 = 2
sorry
theorem its_true_really : -(no_index (OfNat.ofNat <| nat_lit 2)) = -2 := by
rfl
example : -(-(no_index (OfNat.ofNat <| nat_lit 2))) = 2 := by
simp
example : -(no_index (-(no_index (OfNat.ofNat <| nat_lit 2)))) = -(-(-(-3+1))) := by
simp

View File

@@ -85,6 +85,7 @@ example : (BitVec.twoPow 16 2) = 4#16 := by bv_normalize
example {x : BitVec 16} : x / (BitVec.twoPow 16 2) = x >>> 2 := by bv_normalize
example {x : BitVec 16} : x / (BitVec.ofNat 16 8) = x >>> 3 := by bv_normalize
example {x y : Bool} (h1 : x && y) : x || y := by bv_normalize
example (a b c: Bool) : (if a then b else c) = (if !a then c else b) := by bv_normalize
section

View File

@@ -23,17 +23,8 @@ theorem substructure_unit_3' (x y : BitVec 8) : Bool.xor (x = y) (y ≠ x) := by
theorem substructure_unit_4 (a b : Bool) : (a && b) = (b && a) := by
bv_decide
theorem substructure_unit_5 (a : Bool) (b c : BitVec 32) (h1 : b < c a) (h2 : a = true) : b < c := by
theorem substructure_unit_5 (x : BitVec 32) : (if x.getLsbD 0 then !x.getLsbD 0 else x.getLsbD 0) = false := by
bv_decide
theorem substructure_unit_6 (a b c: Bool) : (if a then b else c) = (if !a then c else b) := by
bv_decide
theorem substructure_unit_7 (a b c: Bool) : (bif a then b else c) = (bif !a then c else b) := by
bv_decide
theorem substructure_unit_8 (x : BitVec 32) : (if x.getLsbD 0 then !x.getLsbD 0 else x.getLsbD 0) = false := by
bv_decide
theorem substructure_unit_9 (x y : BitVec 32) (h : x.getLsbD 0 = false) : (if x.getLsbD 0 then x else y) = y := by
theorem substructure_unit_6 (x y : BitVec 32) (h : x.getLsbD 0 = false) : (if x.getLsbD 0 then x else y) = y := by
bv_decide

View File

@@ -13,6 +13,7 @@ elab "grind_test" : tactic => withMainContext do
logInfo eqc
set_option grind.debug true
set_option grind.debug.proofs true
/--
info: [d, f b, c, f a]

View File

@@ -0,0 +1,100 @@
set_option warningAsError false
set_option grind.debug true
set_option grind.debug.proofs true
example (a b : Nat) (f : Nat Nat) : (h₁ : a = b) f a = f b := by
grind
example (a b : Nat) (f : Nat Nat) : (h₁ : a = b) (h₂ : f a f b) False := by
grind
example (a b : Nat) (f : Nat Nat) : a = b f (f a) f (f b) False := by
grind
example (a b c : Nat) (f : Nat Nat) : a = b c = b f (f a) f (f c) False := by
grind
example (a b c : Nat) (f : Nat Nat Nat) : a = b c = b f (f a b) a f (f c c) c False := by
grind
example (a b c : Nat) (f : Nat Nat Nat) : a = b c = b f (f a b) a = f (f c c) c := by
grind
example (a b c d : Nat) : a = b b = c c = d a = d := by
grind
infix:50 "===" => HEq
example (a b c d : Nat) : a === b b = c c === d a === d := by
grind
example (a b c d : Nat) : a = b b = c c === d a === d := by
grind
opaque f {α : Type} : α α α := fun a _ => a
opaque g : Nat Nat
example (a b c : Nat) : a = b g a === g b := by
grind
example (a b c : Nat) : a = b c = b f (f a b) (g c) = f (f c a) (g b) := by
grind
example (a b c d e x y : Nat) : a = b a = x b = y c = d c = e c = b a = e := by
grind
namespace Ex1
opaque f (a b : Nat) : a > b Nat
opaque g : Nat Nat
example (a₁ a₂ b₁ b₂ c d : Nat)
(H₁ : a₁ > b₁)
(H₂ : a₂ > b₂) :
a₁ = c a₂ = c
b₁ = d d = b₂
g (g (f a₁ b₁ H₁)) = g (g (f a₂ b₂ H₂)) := by
grind
end Ex1
namespace Ex2
def f (α : Type) (a : α) : α := a
example (a : α) (b : β) : (h₁ : α = β) (h₂ : HEq a b) HEq (f α a) (f β b) := by
grind
end Ex2
example (f g : (α : Type) α α) (a : α) (b : β) : (h₁ : α = β) (h₂ : HEq a b) (h₃ : f = g) HEq (f α a) (g β b) := by
grind
set_option trace.grind.proof true in
set_option trace.grind.proof.detail true in
example (f : {α : Type} α Nat Bool Nat) (a b : Nat) : f a 0 true = v₁ f b 0 true = v₂ a = b v₁ = v₂ := by
grind
set_option trace.grind.proof true in
set_option trace.grind.proof.detail true in
example (f : {α : Type} α Nat Bool Nat) (a b : Nat) : f a b x = v₁ f a b y = v₂ x = y v₁ = v₂ := by
grind
set_option trace.grind.proof true in
set_option trace.grind.proof.detail true in
theorem ex1 (f : {α : Type} α Nat Bool Nat) (a b c : Nat) : f a b x = v₁ f c b y = v₂ a = c x = y v₁ = v₂ := by
grind
#print ex1
example (n1 n2 n3 : Nat) (v1 w1 : Vector Nat n1) (w1' : Vector Nat n3) (v2 w2 : Vector Nat n2) :
n1 = n3 v1 = w1 HEq w1 w1' v2 = w2 HEq (v1 ++ v2) (w1' ++ w2) := by
grind
example (n1 n2 n3 : Nat) (v1 w1 : Vector Nat n1) (w1' : Vector Nat n3) (v2 w2 : Vector Nat n2) :
HEq n1 n3 v1 = w1 HEq w1 w1' HEq v2 w2 HEq (v1 ++ v2) (w1' ++ w2) := by
grind
theorem ex2 (n1 n2 n3 : Nat) (v1 w1 v : Vector Nat n1) (w1' : Vector Nat n3) (v2 w2 w : Vector Nat n2) :
HEq n1 n3 v1 = w1 HEq w1 w1' HEq v2 w2 HEq (w1' ++ w2) (v ++ w) HEq (v1 ++ v2) (v ++ w) := by
grind
#print ex2

View File

@@ -13,6 +13,7 @@ elab "grind_test" : tactic => withMainContext do
logInfo ( getEqc n.self)
set_option grind.debug true
set_option grind.debug.proofs true
/-
Recall that array access terms, such as `a[i]`, have nested proofs.

View File

@@ -1,19 +1,11 @@
import Lean
open Lean Meta Elab Tactic Grind in
elab "grind_pre" : tactic => do
let declName := ( Term.getDeclName?).getD `_main
liftMetaTactic fun mvarId => do
Meta.Grind.main mvarId declName
abbrev f (a : α) := a
set_option grind.debug true
set_option grind.debug.proofs true
/--
warning: declaration uses 'sorry'
---
info: a b c : Bool
error: `grind` failed
a b c : Bool
p q : Prop
left✝ : a = true
right✝ : b = true c = true
@@ -22,34 +14,13 @@ right : q
x✝ : b = false a = false
⊢ False
-/
#guard_msgs in
#guard_msgs (error) in
theorem ex (h : (f a && (b || f (f c))) = true) (h' : p q) : b && a := by
grind_pre
trace_state
all_goals sorry
grind
open Lean.Grind.Eager in
/--
warning: declaration uses 'sorry'
---
info: a b c : Bool
p q : Prop
left✝ : a = true
h✝ : b = true
left : p
right : q
h : b = false
⊢ False
a b c : Bool
p q : Prop
left✝ : a = true
h✝ : b = true
left : p
right : q
h : a = false
⊢ False
error: `grind` failed
a b c : Bool
p q : Prop
left✝ : a = true
@@ -58,39 +29,32 @@ left : p
right : q
h : b = false
⊢ False
a b c : Bool
p q : Prop
left✝ : a = true
h✝ : c = true
left : p
right : q
h : a = false
⊢ False
-/
#guard_msgs in
#guard_msgs (error) in
theorem ex2 (h : (f a && (b || f (f c))) = true) (h' : p q) : b && a := by
grind_pre
trace_state
all_goals sorry
grind
def g (i : Nat) (j : Nat) (_ : i > j := by omega) := i + j
/--
error: `grind` failed
i j : Nat
h : j + 1 < i + 1
h✝ : j + 1 ≤ i
x✝ : ¬g (i + 1) j ⋯ = i + j + 1
⊢ False
-/
#guard_msgs (error) in
example (i j : Nat) (h : i + 1 > j + 1) : g (i+1) j = f ((fun x => x) i) + f j + 1 := by
grind_pre
next hn =>
guard_hyp hn : ¬g (i + 1) j _ = i + j + 1
simp_arith [g] at hn
grind
structure Point where
x : Nat
y : Int
/--
warning: declaration uses 'sorry'
---
info: a₁ : Point
error: `grind` failed
a₁ : Point
a₂ : Nat
a₃ : Int
as : List Point
@@ -104,15 +68,53 @@ y_eq : a₃ = b₃
tail_eq : as = bs
⊢ False
-/
#guard_msgs in
#guard_msgs (error) in
theorem ex3 (h : a₁ :: { x := a₂, y := a₃ : Point } :: as = b₁ :: { x := b₂, y := b₃} :: bs) : False := by
grind_pre
trace_state
sorry
grind
def h (a : α) := a
set_option trace.grind.pre true in
example (p : Prop) (a b c : Nat) : p a = 0 a = b h a = h c a = c c = a a = b b = a a = c := by
grind
set_option trace.grind.proof.detail true
set_option trace.grind.proof true
set_option trace.grind.pre true
example (p : Prop) (a b c : Nat) : p a = 0 a = b h a = h c a = c c = a a = b b = a a c := by
grind_pre
/--
error: `grind` failed
α : Type
a : α
p q r : Prop
h₁ : HEq p a
h₂ : HEq q a
h₃ : p = r
⊢ False
-/
#guard_msgs (error) in
example (a : α) (p q r : Prop) : (h₁ : HEq p a) (h₂ : HEq q a) (h₃ : p = r) False := by
grind
example (a b : Nat) (f : Nat Nat) : (h₁ : a = b) (h₂ : f a f b) False := by
grind
example (a : α) (p q r : Prop) : (h₁ : HEq p a) (h₂ : HEq q a) (h₃ : p = r) q = r := by
grind
/--
warning: declaration uses 'sorry'
---
info: [grind.issues] found congruence between
g b
and
f a
but functions have different types
-/
#guard_msgs in
set_option trace.grind.issues true in
set_option trace.grind.proof.detail false in
set_option trace.grind.proof false in
set_option trace.grind.pre false in
example (f : Nat Bool) (g : Int Bool) (a : Nat) (b : Int) : HEq f g HEq a b f a = g b := by
fail_if_success grind
sorry

View File

@@ -15,6 +15,7 @@ elab "grind_test" : tactic => withMainContext do
logInfo eqc
set_option grind.debug true
set_option grind.debug.proofs true
/--
info: true: [q, w]

View File

@@ -0,0 +1,77 @@
example (a b : List Nat) : a = [] b = [2] a = b False := by
grind
example (a b : List Nat) : a = b a = [] b = [2] False := by
grind
example (a b : Bool) : a = true b = false a = b False := by
grind
example (a b : Sum Nat Bool) : a = .inl c b = .inr true a = b False := by
grind
example (a b : Sum Nat Bool) : a = b a = .inl c b = .inr true a = b False := by
grind
inductive Foo (α : Type) : Nat Type where
| a (v : α) : Foo α 0
| b (n : α) (m : Nat) (v : Vector Nat m) : Foo α (2*m)
example (h₁ : Foo.b x 2 v = f₁) (h₂ : Foo.b y 2 w = f₂) : f₁ = f₂ x = y := by
grind
example (h₁ : Foo.a x = f₁) (h₂ : Foo.a y = f₂) : f₁ = f₂ x = y := by
grind
example (h₁ : a :: b = x) (h₂ : c :: d = y) : x = y a = c := by
grind
example (h : x = y) (h₁ : a :: b = x) (h₂ : c :: d = y) : a = c := by
grind
example (h : x = y) (h₁ : a :: b = x) (h₂ : c :: d = y) : b = d := by
grind
example (a b : Sum Nat Bool) : a = .inl x b = .inl y x y a = b False := by
grind
example (a b : Nat) : a = 1 b = 2 a = b False := by
grind
example (a b c : Int) : a = 1 c = -2 a = b c = b False := by
grind
example (a b : Char) : a = 'h' b = 'w' a = b False := by
grind
example (a b : String) : a = "hello" b = "world" a = b False := by
grind
example (a b c : String) : a = c a = "hello" c = "world" c = b False := by
grind
example (a b c : BitVec 32) : a = c a = 1#32 c = 2#32 c = b False := by
grind
example (a b c : UInt32) : a = c a = 1 c = 200 c = b False := by
grind
structure Boo (α : Type) where
a : α
b : α
c : α
example (a b d : Nat) (f : Nat Boo Nat) : (f d).1 a f d = b, v₁, v₂ b = a False := by
grind
def ex (a b c d : Nat) (f : Nat Boo Nat) : (f d).2 a f d = b, c, v₂ c = a False := by
grind
example (a b c : Nat) (f : Nat Nat) : { a := f b, c, b := 4 : Boo Nat }.1 f a f b = f c a = c False := by
grind
example (a b c : Nat) (f : Nat Nat) : p = { a := f b, c, b := 4 : Boo Nat } p.1 f a f b = f c a = c False := by
grind
example (a b c : Nat) (f : Nat Nat) : p.1 f a p = { a := f b, c, b := 4 : Boo Nat } f b = f c a = c False := by
grind