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) or foo?

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

#12936

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 by simp, should I need to add X 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