Zulip Chat Archive
Stream: mathlib4
Topic: simp requires a universe annotation?
Kim Morrison (May 13 2024 at 07:19):
Has anyone seen this phenomenon before:
fail_if_success
simp only [foo] -- Doesn't work as a simp lemma until we specify the universe levels explicitly!
simp only [foo.{v₁ + 1}] -- Succeeds
Kim Morrison (May 13 2024 at 07:20):
(If anyone wants to play with it, it is on branch#Bimon, in Mathlib/CategoryTheory/Monoidal/Bimon_.lean, in to_Mon_Comon_obj
.)
Kim Morrison (May 13 2024 at 07:21):
I'm mystified
Daniel Weber (May 13 2024 at 09:20):
Not sure what's going on there, but just apply Mon_.mul_one
seems to work
Kim Morrison (May 13 2024 at 09:29):
Thanks. I would like to get to the bottom of this, as this goal should work by simp
and this issue badly breaks automation.
Eric Wieser (May 13 2024 at 09:35):
Does it work as (foo)
?
Daniel Weber (May 13 2024 at 09:36):
Eric Wieser said:
Does it work as
(foo)
?
Seems like it, yeah
Daniel Weber (May 13 2024 at 09:42):
Comparing the traces, the relevant difference seems to be that for the one without a specific universe it fails to find anything for this term:
[Debug.Meta.Tactic.simp] no theorems found for post-rewriting { unop := { hom := M.X.one.op, one_hom := ⋯, mul_hom := ⋯ } }.unop
[Debug.Meta.Tactic.simp] no post-simprocs found for { unop := { hom := M.X.one.op, one_hom := ⋯, mul_hom := ⋯ } }.unop
whereas foo.{v₁ + 1}
has
[Meta.Tactic.simp.rewrite] foo.{v₁ +
1}:1000, {
unop :=
{ hom := M.X.one.op, one_hom := ⋯, mul_hom := ⋯ } }.unop ==> { hom := M.X.one.op, one_hom := ⋯, mul_hom := ⋯ }
Using (foo)
has almost the same output there, just (foo):1000
instead of foo.{v₁ + 1}:1000
.
Eric Wieser (May 13 2024 at 09:48):
Are those first logs for (foo)
or foo
?
Eric Wieser (May 13 2024 at 09:49):
I think the issue here is that simp [foo]
is shorthand for simp [@foo]
, and so you need to insert some other syntax to prevent the special case for identifiers taking over; either ()
or a universe annotation is enough to do that
Daniel Weber (May 13 2024 at 09:49):
Eric Wieser said:
Are those first logs for
(foo)
orfoo
?
The first logs are for foo
. (foo)
gives
[Meta.Tactic.simp.rewrite] (foo):1000, {
unop :=
{ hom := M.X.one.op, one_hom := ⋯, mul_hom := ⋯ } }.unop ==> { hom := M.X.one.op, one_hom := ⋯, mul_hom := ⋯ }
Daniel Weber (May 13 2024 at 09:54):
Shouldn't it also work for dsimp
? For dsimp
even foo.{v₁ + 1}
fails
Matthew Ballard (May 13 2024 at 18:00):
This works
@[simp] theorem foo {V} [Quiver V] {X Y x} :
@Quiver.Hom.unop V _ X Y no_index (Opposite.op (unop := x)) = x := rfl
Matthew Ballard (May 13 2024 at 18:04):
I am sure someone has a handy way to dump the keys for the two the cases. (I've got to go to Costco.)
Matthew Ballard (May 13 2024 at 18:05):
But my guess from this is that the keys are getting crunched down too much to match
Matthew Ballard (May 15 2024 at 11:44):
Another fix: make docs#Quiver.Hom.unop and docs#Quiver.Hom.op abbrev
.
Not sure of the impact of this.
Matthew Ballard (May 15 2024 at 11:46):
Bonus: you don't even need foo
anymore
Matthew Ballard (May 17 2024 at 18:47):
Performance impact: http://speed.lean-fro.org/mathlib4/run-detail/72df5316-31cc-4c76-b571-906f13c0dcce
Johan Commelin (May 18 2024 at 03:20):
Some proofs became longer to write. But performance also improved.
So... @Kim Morrison are you happy with this solution?
Kim Morrison (May 20 2024 at 01:33):
:writing:
Kim Morrison (May 20 2024 at 01:34):
Not concerned by the changes to proofs, just by nolint simpVarHead
, which I think is bad news.
Matthew Ballard (May 20 2024 at 12:47):
I responded to the comments there with agreement but I think broader discussion would be useful.
Now that docs#Quiver.Hom.unop_op and docs#Quiver.Hom.op_unop are covered by built in reduction rules, I thought we would be fine removing them from the simp
tree.
Matthew Ballard (May 20 2024 at 12:49):
But! Automation starts breaking immediately. Why? Well the following works fine for docs#CategoryTheory.Category.opposite
instance Category.opposite : Category.{v₁} Cᵒᵖ where
comp := fun ⟨f⟩ ⟨g⟩ => ⟨g ≫ f⟩
id := fun ⟨X⟩ => ⟨𝟙 X⟩
Whereas the current statement
instance Category.opposite : Category.{v₁} Cᵒᵖ where
comp f g := (g.unop ≫ f.unop).op
id X := (𝟙 (unop X)).op
is broken
Matthew Ballard (May 20 2024 at 12:49):
Eg #lean4 > optimal instance design for structure wrappers
Matthew Ballard (May 20 2024 at 12:55):
Also, I've never seen a simpVarHead
warning that isn't secretly provable with by simp
Matthew Ballard (May 21 2024 at 15:11):
Here is an example.
universe v u
class Quiver (V : Type u) where
Hom : V → V → Sort v
infixr:10 " ⟶ " => Quiver.Hom
namespace CategoryTheory
class CategoryStruct (obj : Type u) extends Quiver.{v + 1} obj : Type max u (v + 1) where
id : ∀ X : obj, Hom X X
comp : ∀ {X Y Z : obj}, (X ⟶ Y) → (Y ⟶ Z) → (X ⟶ Z)
notation "𝟙" => CategoryStruct.id
infixr:80 " ≫ " => CategoryStruct.comp
class Category (obj : Type u) extends CategoryStruct.{v} obj : Type max u (v + 1) where
comp_id : ∀ {X Y : obj} (f : X ⟶ Y), f ≫ 𝟙 Y = f
open Category in
attribute [simp] comp_id
end CategoryTheory
structure Opposite (α : Sort u) :=
op :: unop : α
notation:max α "ᵒᵖ" => Opposite α
open Opposite
namespace Quiver
instance opposite {V} [Quiver V] : Quiver Vᵒᵖ :=
⟨fun a b => (unop b ⟶ unop a)ᵒᵖ⟩
abbrev Hom.op {V} [Quiver V] {X Y : V} (f : X ⟶ Y) : op Y ⟶ op X := Opposite.op f
abbrev Hom.unop {V} [Quiver V] {X Y : Vᵒᵖ} (f : X ⟶ Y) : unop Y ⟶ unop X := Opposite.unop f
variable [Quiver.{v} C]
@[simp] -- we should remove this since it's proof is `simp` right? but doing so breaks the example
theorem Quiver.Hom.unop_op {X Y : C} (f : X ⟶ Y) : f.op.unop = f := by simp only -- red flag!
@[simp]
theorem Quiver.Hom.op_unop {X Y : Cᵒᵖ} (f : X ⟶ Y) : f.unop.op = f :=
rfl
end Quiver
open CategoryTheory Quiver Category
variable (C : Type u) [Category.{v} C]
example (X Y : Cᵒᵖ) (f : X ⟶ Y) : (f.unop ≫ (𝟙 (unop X)).op.unop).op = f := by simp?
Matthew Ballard (May 21 2024 at 15:12):
This takes the existing definitions in the library and makes docs#Quiver.Hom.op and docs#Quiver.Hom.unop abbrev
's.
Matthew Ballard (May 21 2024 at 15:13):
Now docs#Quiver.Hom.unop_op becomes a simp only
so we should remove @[simp]
.
Matthew Ballard (May 21 2024 at 15:13):
But that breaks the example
Matthew Ballard (May 21 2024 at 16:09):
If we change the Quiver
instance on the opposite to
instance opposite {V} [Quiver V] : Quiver Vᵒᵖ :=
⟨fun ⟨a⟩ ⟨b⟩ => (b ⟶ a)ᵒᵖ⟩
then
theorem Hom.op_unop' {X Y : Cᵒᵖ} (f : X ⟶ Y) : op (unop f) = f := by simp
works fine as does
example (X Y : Cᵒᵖ) (f : X ⟶ Y) : op (unop f ≫ (unop (op (𝟙 (unop X))))) = f := by simp
Matthew Ballard (May 21 2024 at 16:09):
Neither of these work with the original syntax
Matthew Ballard (May 21 2024 at 16:46):
[simp] theorems with bad keys ▼
comp_id, key: @CategoryStruct.comp _ _ _ _ _ _ (@CategoryStruct.id _ _ _)
Hmm, that seems bad
Matthew Ballard (May 21 2024 at 16:49):
If we go back to the original definition of opposite
, then
[simp] theorems with bad keys ▼
Hom.op_unop, key: @op (@Hom _ _ (@op _ _.1).1 (@op _ _.1).1) _.1
for
set_option diagnostics true in
example {X Y : Cᵒᵖ} (f : X ⟶ Y) : op (unop f) = f := by simp (config := { index := false })
Matthew Ballard (May 21 2024 at 16:55):
What the heck! Should what namespace things live in affect the behavior of simp
?
universe v u
class Quiver (V : Type u) where
Hom : V → V → Sort v
infixr:10 " ⟶ " => Quiver.Hom
structure Opposite (α : Sort u) :=
op :: unop : α
notation:max α "ᵒᵖ" => Opposite α
open Opposite
namespace Quiver
instance opposite {V} [Quiver V] : Quiver Vᵒᵖ :=
⟨fun a b => (unop b ⟶ unop a)ᵒᵖ⟩
abbrev Hom.op {V} [Quiver V] {X Y : V} (f : X ⟶ Y) : op Y ⟶ op X := Opposite.op f
abbrev Hom.unop {V} [Quiver V] {X Y : Vᵒᵖ} (f : X ⟶ Y) : unop Y ⟶ unop X := Opposite.unop f
variable (C : Type u) [Quiver.{v} C]
@[simp]
theorem Hom.op_unop {X Y : Cᵒᵖ} (f : X ⟶ Y) : f.unop.op = f := rfl
theorem Hom.bar {X Y : Cᵒᵖ} (f : X ⟶ Y) : op (unop f) = f := by simp -- works
theorem bar {X Y : Cᵒᵖ} (f : X ⟶ Y) : op (unop f) = f := by simp -- fails
Matthew Ballard (May 21 2024 at 17:05):
I can't move messages to another thread so I just copied the last one.
Mario Carneiro (May 21 2024 at 18:59):
Isn't the different namespace changing the name resolution of op
and unop
here?
Yaël Dillies (May 21 2024 at 18:59):
Yes, Matthew figured that out
Matthew Ballard (May 21 2024 at 18:59):
Yes. But hover was tricking me
Mario Carneiro (May 21 2024 at 18:59):
maybe you want to make Hom.op
and Hom.unop
protected
Matthew Ballard (May 21 2024 at 19:01):
My main issue is trying to figure out why the example at the start of the thread (EDIT: today!) didn’t work
Matthew Ballard (May 21 2024 at 19:02):
If X
is closed by simp
, should I need to add X
to the simp set?
Matthew Ballard (May 21 2024 at 19:03):
We have a linter for this so I thought the answer was no
Matthew Ballard (May 21 2024 at 19:03):
The example at the start runs counter to the expectation.
Yaël Dillies (May 21 2024 at 19:05):
Matthew Ballard said:
If
X
is closed bysimp
, should I need to addX
to the simp set?
Depends
Yaël Dillies (May 21 2024 at 19:06):
Possibly simp
is simplifying the equality itself, in which case simp-nf shouldn't complain
Yaël Dillies (May 21 2024 at 19:06):
The true test is whether the LHS gets simplified to the RHS (without simp seeing the equality)
Matthew Ballard (May 21 2024 at 19:10):
Hmm. Can you point me to an example?
Matthew Ballard (May 21 2024 at 19:12):
The issue goes away here with the index := false
config option on lean4 master
Yaël Dillies (May 21 2024 at 19:16):
Can't think of an actual example off the top of my head, but imagine we were making docs#Set.eq_empty_iff_forall_not_mem a simp lemma and we had a simp lemma foo (x): x ∉ fancySet
. Then fancySet = ∅
can be proved by simp, but simp can't simplify fancySet
to ∅
.
Matthew Ballard (May 21 2024 at 19:19):
I see
Yaël Dillies (May 21 2024 at 19:20):
Note that I'm not inventing anything. The simpNF
linter already follows this logic.
Matthew Ballard (May 21 2024 at 19:24):
This is the same behavior as in Lean 3?
Yaël Dillies (May 21 2024 at 19:26):
Basically. The only difference is that sometimes simp applies extensionality on the fly using congruence lemmas, meaning that it might actually be able to simplify fancySet
to ∅
in my toy example above. I don't really understand under which conditions this happens, though.
Matthew Ballard (May 22 2024 at 00:53):
It looks like you should assign a *
to any implicit argument taking [self : Class]
Matthew Ballard (May 22 2024 at 00:57):
And that structure eta has to be a bit more aggressive
Last updated: May 02 2025 at 03:31 UTC