Zulip Chat Archive
Stream: mathlib4
Topic: bad Decidable instances
Kim Morrison (Sep 20 2024 at 08:00):
Consider:
import Mathlib
set_option autoImplicit true
structure Vec (n : Nat) (α : Type _) where
toArray : Array α
size : toArray.size = n
namespace Vec
instance : GetElem (Vec n α) Nat α (fun _ i => i < n) where
getElem v i h := v.1[i]'(Nat.lt_of_lt_of_eq h v.2.symm)
@[simp] theorem getElem_mk (data : Array α) (size : data.size = n) (i : Nat) (h : i < n) :
(Vec.mk data size)[i] = data[i] := rfl
@[ext] theorem ext {v w : Vec n α} (h : ∀ i : Fin n, v[i] = w[i]) : v = w := by
cases v; cases w; congr
ext i
· simp_all
· simp at h
specialize h ⟨i, by omega⟩
simpa using h
def pop (v : Vec (n + 1) α) : Vec n α :=
⟨v.toArray.pop, by simp [v.size]⟩
end Vec
def Equiv.vecEquivFin (n : Nat) (α : Type _) : Vec n α ≃ (Fin n → α) where
toFun v i := v[i]
invFun f := ⟨.ofFn f, by simp⟩
left_inv v := by ext; simp
right_inv f := by ext; simp
instance [Fintype α] : Fintype (Vec n α) :=
Fintype.ofEquiv (Fin n → α) (Equiv.vecEquivFin n α).symm
inductive Gate : Nat → Type
| const : Bool → Gate 0
| if : ∀ {n}, Gate n → Gate n → Gate (n + 1)
namespace Gate
def and : Gate 2 := .if (.if (.const true) (.const false)) (.if (.const false) (.const false))
def eval (g : Gate n) (v : Vec n Bool) : Bool :=
match g, v with
| .const b, _ => b
| .if g₁ g₂, v => if v.1.back then eval g₁ v.pop else eval g₂ v.pop
example : ∀ v, and.eval v = (v[0] && v[1]) := by decide
end Gate
which fails with:
tactic 'decide' failed for proposition
∀ (v : Vec 2 Bool), and.eval v = (v[0] && v[1])
since its 'Decidable' instance
Fintype.decidableForallFintype
did not reduce to 'isTrue' or 'isFalse'.
After unfolding the instances 'decidable_of_decidable_of_iff', 'decidable_of_iff', 'instDecidableEqBool', 'Bool.decEq', 'Finset.decidableDforallFinset', 'Fintype.decidableForallFintype', 'List.decidableBAll', 'Multiset.decidableDforallMultiset', 'Multiset.decidableForallMultiset', 'Nat.decLe' and 'Nat.decLt', reduction got stuck at the 'Decidable' instance
match
and.eval
↑⟨{ toFun := ⇑(Equiv.vecEquivFin 2 Bool).symm, inj' := ⋯ }
({ toFun := fun f a ↦ f a ⋯, inj' := ⋯ }
(Multiset.Pi.cons ⟦List.pmap Fin.mk [1] ⋯⟧ ⟨0, ⋯⟩ true
(Multiset.Pi.cons ⟦List.pmap Fin.mk [] ⋯⟧ ⟨1, ⋯⟩ true (Multiset.Pi.empty fun a ↦ Bool)))),
⋯⟩,
(↑⟨{ toFun := ⇑(Equiv.vecEquivFin 2 Bool).symm, inj' := ⋯ }
({ toFun := fun f a ↦ f a ⋯, inj' := ⋯ }
(Multiset.Pi.cons ⟦List.pmap Fin.mk [1] ⋯⟧ ⟨0, ⋯⟩ true
(Multiset.Pi.cons ⟦List.pmap Fin.mk [] ⋯⟧ ⟨1, ⋯⟩ true (Multiset.Pi.empty fun a ↦ Bool)))),
⋯⟩)[0] &&
(↑⟨{ toFun := ⇑(Equiv.vecEquivFin 2 Bool).symm, inj' := ⋯ }
({ toFun := fun f a ↦ f a ⋯, inj' := ⋯ }
(Multiset.Pi.cons ⟦List.pmap Fin.mk [1] ⋯⟧ ⟨0, ⋯⟩ true
(Multiset.Pi.cons ⟦List.pmap Fin.mk [] ⋯⟧ ⟨1, ⋯⟩ true (Multiset.Pi.empty fun a ↦ Bool)))),
⋯⟩)[1] with
| false, false => isTrue ⋯
| false, true => isFalse ⋯
| true, false => isFalse ⋯
| true, true => isTrue ⋯
Would anyone be interested in finding the bad instances? If they can't be fixed, they should simply be removed --- there is no point providing Decidable instances that don't reduce.
Mario Carneiro (Sep 20 2024 at 08:20):
Kim Morrison said:
structure Vec (n : Nat) (α : Type _) where toArray : Array α size : toArray.size = n
we shouldn't need to copy paste this anymore since Vector
landed in batteries (unless you have reason to believe that the implementation of Vec
is implicated in this bug)
Eric Wieser (Sep 20 2024 at 08:23):
Kim Morrison said:
there is no point providing Decidable instances that don't reduce.
I don't agree with this claim; a non-reducing decidable instance still works with #eval
.
Eric Wieser (Sep 20 2024 at 08:23):
But indeed it's best to make them reduce
Eric Wieser (Sep 20 2024 at 08:23):
I suspect the problem here is that the Fintype instance doesn't reduce
Kim Morrison (Sep 20 2024 at 08:54):
Mario Carneiro said:
we shouldn't need to copy paste this anymore since
Vector
landed in batteries (unless you have reason to believe that the implementation ofVec
is implicated in this bug)
I keep forgetting this. In any case many basic simp lemmas seem to be missing for Vector
.
Kim Morrison (Sep 20 2024 at 08:57):
The problem remains with Vector
, but it is indeed shorter:
Kim Morrison (Sep 20 2024 at 08:58):
import Mathlib
set_option autoImplicit true
namespace Batteries
namespace Vector
@[simp] theorem getElem_mk (data : Array α) (size : data.size = n) (i : Nat) (h : i < n) :
(Vector.mk data size)[i] = data[i] := rfl
end Vector
end Batteries
open Batteries (Vector)
def Equiv.vecEquivFin (α : Type _) (n : Nat) : Vector α n ≃ (Fin n → α) where
toFun v i := v[i]
invFun f := ⟨.ofFn f, by simp⟩
left_inv v := by ext; simp
right_inv f := by ext; simp
inductive Gate : Nat → Type
| const : Bool → Gate 0
| if : ∀ {n}, Gate n → Gate n → Gate (n + 1)
namespace Gate
def and : Gate 2 := .if (.if (.const true) (.const false)) (.if (.const false) (.const false))
def eval (g : Gate n) (v : Vector Bool n) : Bool :=
match g, v with
| .const b, _ => b
| .if g₁ g₂, v => if v.1.back then eval g₁ v.pop else eval g₂ v.pop
def eval' (g : Gate n) (v : Fin n → Bool) : Bool :=
match g, v with
| .const b, _ => b
| .if g₁ g₂, v => if v 0 then eval' g₁ (v ∘ Fin.succ) else eval' g₂ (v ∘ Fin.succ)
example : ∀ f : Fin 2 → Bool, and.eval' f = (f 0 && f 1) := by decide -- works
example : ∀ f : Fin 2 → Bool, and.eval ((Equiv.vecEquivFin Bool 2).symm f) = (f 0 && f 1) := by decide -- fails
example : ∀ v, and.eval v = (v[0] && v[1]) := by decide -- fails
end Gate
Eric Wieser (Sep 20 2024 at 09:25):
Here's a shorter repro:
import Mathlib
namespace Batteries.Vector
@[simp] theorem getElem_mk {α} (data : Array α) (size : data.size = n) (i : Nat) (h : i < n) :
(Vector.mk data size)[i] = data[i] := rfl
end Batteries.Vector
open Batteries (Vector)
example : (⟨.ofFn ![1, 2], by simp⟩ : Vector ℕ 2)[0] = 1 := by decide
Eric Wieser (Sep 20 2024 at 09:26):
The only decidable instance here is instDecidableEqNat
, so I claim the title of this thread is wrong :)
Eric Wieser (Sep 20 2024 at 09:32):
A similar issue is
example : Array.ofFn ![1, 2] = #[1, 2] := by decide
Eric Wieser (Sep 20 2024 at 09:34):
That one is caused by Array.isEqvAux
being irreducible due to use of well-founded recursion
Kim Morrison (Sep 20 2024 at 11:38):
Excellent, thank you for the diagnosis, @Eric Wieser. This is super useful and I will try to fix this (or merge a fix!) asap next week.
Alex Meiburg (Oct 03 2024 at 20:02):
I got another bad decidable instance:
import Mathlib
example : (1/2:ℚ).num = (1/3:ℚ).num := by
decide
gives
1 goal
⊢ (1 / 2).num = (1 / 3).num
Messages (1)
mathlib-demo.lean:4:2
tactic 'decide' failed for proposition
(1 / 2).num = (1 / 3).num
since its 'Decidable' instance
(1 / 2).num.instDecidableEq (1 / 3).num
did not reduce to 'isTrue' or 'isFalse'.
After unfolding the instances 'Int.decEq' and 'Int.instDecidableEq', reduction got stuck at the 'Decidable' instance
match (1 / 2).num, (1 / 3).num with
| Int.ofNat a, Int.ofNat b =>
match decEq a b with
| isTrue h => isTrue ⋯
| isFalse h => isFalse ⋯
| Int.ofNat a, Int.negSucc a_1 => isFalse ⋯
| Int.negSucc a, Int.ofNat a_1 => isFalse ⋯
| Int.negSucc a, Int.negSucc b =>
match decEq a b with
| isTrue h => isTrue ⋯
| isFalse h => isFalse ⋯
All Messages (2)
Any ideas what's going on? I can't make much of this match issue
Eric Wieser (Oct 03 2024 at 20:05):
This fails because /
is irreducible
Eric Wieser (Oct 03 2024 at 20:05):
So once again, this is not a bad decidable instance, but a correct decidable instance applied to a bad argument
Alex Meiburg (Oct 03 2024 at 20:10):
What's the 'correct' way to simplify/close such a goal? And -- maybe more importantly -- if I have a much larger more complicated goal, and I want to close it with decide
? If I have a large expression that ultimately reduces to deciding the equality of two rational numbers, I expect decide
to be able to do that.
Eric Wieser (Oct 03 2024 at 20:39):
decide
can only decide the equality of rational numbers which ultimately reduce to a constructor application
Eric Wieser (Oct 03 2024 at 20:39):
norm_num
is the correct tool here
Eric Wieser (Oct 03 2024 at 20:40):
Alternatively, the fact that /
is irreducible is a bug
Mario Carneiro (Oct 03 2024 at 21:34):
well, the reason it is irreducible is because it unfolds to something terrible
Mario Carneiro (Oct 03 2024 at 21:34):
we really need some kind of @[unfold_on_closed_term]
attribute which allows decide
to see through it without letting x / y
be unfolded by unification
Alex Meiburg (Oct 04 2024 at 00:25):
So I tried norm_num
earlier but it's pain because there is a _very long_ list of simplification facts I need to feed it, and it's much slower than decide, and if I'm not careful I end up loops -- I often need four or five applications of norm_num where I turn certain simp lemmas off and on again. :(
Eric Wieser (Oct 04 2024 at 08:51):
What's your goal state?
Alex Meiburg (Oct 04 2024 at 16:55):
A pretty long formula that involves e.g. bernoulli numbers, fibonacci numbers, and several other functions that I need to evaluate. Here's another one I don't understand why it fails:
example : IsSquare 4 := by decide
This one native_decide
works but again, I don't understand why this fails though. It doesn't involve /
, right?
tactic 'decide' failed for proposition
IsSquare 4
since its 'Decidable' instance
Nat.instDecidablePredIsSquare 4
did not reduce to 'isTrue' or 'isFalse'.
After unfolding the instances 'decidable_of_decidable_of_iff', 'decidable_of_iff'', 'instDecidableEqBool', 'instDecidableEqNat', 'Bool.decEq', 'Nat.decEq', 'Nat.decLe' and 'Nat.instDecidablePredIsSquare', reduction got stuck at the 'Decidable' instance
match h : (Nat.sqrt 4 * Nat.sqrt 4).beq 4 with
| true => isTrue ⋯
| false => isFalse ⋯
Eric Wieser (Oct 04 2024 at 18:56):
The decision procedure involves Nat.sqrt, which is also irreducible
Eric Wieser (Oct 04 2024 at 18:56):
All of these changes are new-ish due to Lean changing the rules for reducibility
Jireh Loreaux (Oct 04 2024 at 19:32):
Is the solution to unseal
something?
Eric Wieser (Oct 04 2024 at 20:04):
Not really, I think well-founded recursion is the real cause
Last updated: May 02 2025 at 03:31 UTC