fix: BEq support in grind (#8536)

This PR fixes the support for `LawfulBEq` and `BEq` in `grind`.
This commit is contained in:
Leonardo de Moura 2025-05-29 19:47:40 -04:00 committed by GitHub
parent 020da5bffb
commit 4316629119
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 74 additions and 40 deletions

View file

@ -27,7 +27,7 @@ class EquivBEq (α) [BEq α] : Prop extends PartialEquivBEq α, ReflBEq α
theorem BEq.symm [BEq α] [PartialEquivBEq α] {a b : α} : a == b → b == a :=
PartialEquivBEq.symm
@[grind] theorem BEq.comm [BEq α] [PartialEquivBEq α] {a b : α} : (a == b) = (b == a) :=
theorem BEq.comm [BEq α] [PartialEquivBEq α] {a b : α} : (a == b) = (b == a) :=
Bool.eq_iff_iff.2 ⟨BEq.symm, BEq.symm⟩
theorem bne_comm [BEq α] [PartialEquivBEq α] {a b : α} : (a != b) = (b != a) := by

View file

@ -89,6 +89,12 @@ theorem beq_eq_true_of_eq {α : Type u} {_ : BEq α} {_ : LawfulBEq α} {a b :
theorem beq_eq_false_of_diseq {α : Type u} {_ : BEq α} {_ : LawfulBEq α} {a b : α} (h : ¬ a = b) : (a == b) = false := by
simp[*]
theorem eq_of_beq_eq_true {α : Type u} {_ : BEq α} {_ : LawfulBEq α} {a b : α} (h : (a == b) = true) : a = b := by
simp [beq_iff_eq.mp h]
theorem ne_of_beq_eq_false {α : Type u} {_ : BEq α} {_ : LawfulBEq α} {a b : α} (h : (a == b) = false) : (a = b) = False := by
simp [beq_eq_false_iff_ne.mp h]
/-! Bool.and -/
theorem Bool.and_eq_of_eq_true_left {a b : Bool} (h : a = true) : (a && b) = b := by simp [h]

View file

@ -173,6 +173,25 @@ builtin_grind_propagator propagateEqDown ↓Eq := fun e => do
for thm in (← getExtTheorems α) do
instantiateExtTheorem thm e
private def getLawfulBEqInst? (u : List Level) (α : Expr) (binst : Expr) : MetaM (Option Expr) := do
let lawfulBEq := mkApp2 (mkConst ``LawfulBEq u) α binst
let .some linst ← trySynthInstance lawfulBEq | return none
return some linst
/-
Note about `BEq.beq`
Given `a b : α` in a context where we have `[BEq α] [LawfulBEq α]`
The normalizer (aka `simp`) fails to normalize `if a == b then ... else ...` to `if a = b then ... else ...` using
```
theorem beq_iff_eq [BEq α] [LawfulBEq α] {a b : α} : a == b ↔ a = b :=
⟨eq_of_beq, beq_of_eq⟩
```
The main issue is that `ite_congr` requires that the resulting proposition to be decidable,
and we don't have `[DecidableEq α]`. Thus, the normalization step fails.
The following propagators for `BEq.beq` ensure `grind` does not assume this normalization
rule has been applied.
-/
builtin_grind_propagator propagateBEqUp ↑BEq.beq := fun e => do
/-
`grind` uses the normalization rule `Bool.beq_eq_decide_eq`, but it is only applicable if
@ -181,17 +200,27 @@ builtin_grind_propagator propagateBEqUp ↑BEq.beq := fun e => do
Thus, we have added this propagator as a backup.
-/
let_expr f@BEq.beq α binst a b := e | return ()
let u := f.constLevels!
if (← isEqv a b) then
let u := f.constLevels!
let lawfulBEq := mkApp2 (mkConst ``LawfulBEq u) α binst
let .some linst ← trySynthInstance lawfulBEq | return ()
let some linst ← getLawfulBEqInst? u α binst | return ()
pushEqBoolTrue e <| mkApp6 (mkConst ``Grind.beq_eq_true_of_eq u) α binst linst a b (← mkEqProof a b)
else if let some h ← mkDiseqProof? a b then
let u := f.constLevels!
let lawfulBEq := mkApp2 (mkConst ``LawfulBEq u) α binst
let .some linst ← trySynthInstance lawfulBEq | return ()
let some linst ← getLawfulBEqInst? u α binst | return ()
pushEqBoolFalse e <| mkApp6 (mkConst ``Grind.beq_eq_false_of_diseq u) α binst linst a b h
builtin_grind_propagator propagateBEqDown ↓BEq.beq := fun e => do
/- See comment above -/
let_expr f@BEq.beq α binst a b := e | return ()
let u := f.constLevels!
if (← isEqBoolTrue e) then
let some linst ← getLawfulBEqInst? u α binst | return ()
pushEq a b <| mkApp6 (mkConst ``Grind.eq_of_beq_eq_true u) α binst linst a b (← mkEqProof e (← getBoolTrueExpr))
else if (← isEqBoolFalse e) then
let some linst ← getLawfulBEqInst? u α binst | return ()
let eq ← shareCommon (mkApp3 (mkConst ``Eq [u.head!.succ]) α a b)
internalize eq (← getGeneration a)
pushEqFalse eq <| mkApp6 (mkConst ``Grind.ne_of_beq_eq_false u) α binst linst a b (← mkEqProof e (← getBoolFalseExpr))
/-- Propagates `EqMatch` downwards -/
builtin_grind_propagator propagateEqMatchDown ↓Grind.EqMatch := fun e => do
if (← isEqTrue e) then

View file

@ -1,32 +0,0 @@
open List
set_option grind.warning false
variable [BEq α] [LawfulBEq α]
-- These tests should move back to `tests/lean/run/grind_list_count.lean` once fixed.
theorem count_pos_iff {a : α} {l : List α} : 0 < count a l ↔ a ∈ l := by
induction l with grind -- fails, having proved `head = a` is false and `head == a` is true.
theorem one_le_count_iff {a : α} {l : List α} : 1 ≤ count a l ↔ a ∈ l := by
induction l with grind -- fails, similarly
theorem count_eq_zero_of_not_mem {a : α} {l : List α} (h : a ∉ l) : count a l = 0 := by
induction l with grind -- fails
theorem count_eq_zero {l : List α} : count a l = 0 ↔ a ∉ l := by
induction l with grind -- fails
theorem count_filter {l : List α} (h : p a) : count a (filter p l) = count a l := by
induction l with grind -- similarly
theorem count_le_count_map {β} [BEq β] [LawfulBEq β] {l : List α} {f : α → β} {x : α} :
count x l ≤ count (f x) (map f l) := by
induction l with grind
theorem count_erase {a b : α} {l : List α} : count a (l.erase b) = count a l - if b == a then 1 else 0 := by
induction l with grind [-List.count_erase]
-- fails with inconsistent equivalence clases:
-- [] {head == a, false}
-- [] {b == a, head == b, true}

View file

@ -1,3 +1,4 @@
set_option grind.warning false
example (f : Bool → Nat) : f (a && b) = 0 → a = false → f false = 0 := by grind (splits := 0)
example (f : Bool → Nat) : f (a && b) = 0 → b = false → f false = 0 := by grind (splits := 0)
example (f : Bool → Nat) : f (a && b) = 0 → a = true → f b = 0 := by grind (splits := 0)
@ -31,3 +32,7 @@ example (a b : Bool) : (a ^^ b, c) = d → d = (false, true) → a = b := by gri
example (a b : Bool) : (a == b, c) = d → d = (true, true) → a = true → true = b := by grind (splits := 0)
example (h : α = β) (a : α) (b : β) : h ▸ a = b → HEq a b := by grind
example {α : Type u} [BEq α] [LawfulBEq α] (x : Nat) (a b : α)
: x = (if a == b then 2 else 1) → x = (if (b == a) then 1 else 2) → False := by
grind

View file

@ -24,6 +24,10 @@ where
eraseDupsBy.loop (· == ·) l seenl = fastEraseDups.go l seenl seen := by
induction l generalizing seenl seen with
| nil => grind [eraseDupsBy.loop, fastEraseDups.go]
| cons x => cases h : seenl.contains x <;> grind [eraseDupsBy.loop, fastEraseDups.go]
| cons x =>
-- In the following example `BEq` is not lawful. To complete the proof we need to add `BEq.comm`
-- TODO: add support for arbitrary partial equivalence and equivalence relations.
-- Remark: `BEq.comm` is noise when `BEq` is lawful.
cases h : seenl.contains x <;> grind [eraseDupsBy.loop, fastEraseDups.go, BEq.comm]
end List

View file

@ -186,4 +186,26 @@ theorem count_erase_self {a : α} {l : List α} :
theorem count_erase_of_ne (ab : a ≠ b) {l : List α} : count a (l.erase b) = count a l := by
grind
theorem count_pos_iff {a : α} {l : List α} : 0 < count a l ↔ a ∈ l := by
induction l with grind
theorem one_le_count_iff {a : α} {l : List α} : 1 ≤ count a l ↔ a ∈ l := by
induction l with grind
theorem count_eq_zero_of_not_mem {a : α} {l : List α} (h : a ∉ l) : count a l = 0 := by
induction l with grind
theorem count_eq_zero {l : List α} : count a l = 0 ↔ a ∉ l := by
induction l with grind
theorem count_filter {l : List α} (h : p a) : count a (filter p l) = count a l := by
induction l with grind
theorem count_le_count_map {β} [BEq β] [LawfulBEq β] {l : List α} {f : α → β} {x : α} :
count x l ≤ count (f x) (map f l) := by
induction l with grind
theorem count_erase {a b : α} {l : List α} : count a (l.erase b) = count a l - if b == a then 1 else 0 := by
induction l <;> grind [-List.count_erase]
end count