/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/

/-!
# More `char` instances

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

This file provides a `linear_order` instance on `char`. `char` is the type of Unicode scalar values.
-/

instance : linear_order char :=
{ le_refl := λ a, @le_refl ℕ _ _,
  le_trans := λ a b c, @le_trans ℕ _ _ _ _,
  le_antisymm := λ a b h₁ h₂,
    char.eq_of_veq $ le_antisymm h₁ h₂,
  le_total := λ a b, @le_total ℕ _ _ _,
  lt_iff_le_not_le := λ a b, @lt_iff_le_not_le ℕ _ _ _,
  decidable_le := char.decidable_le,
  decidable_eq := char.decidable_eq,
  decidable_lt := char.decidable_lt,
  ..char.has_le, ..char.has_lt }

lemma char.of_nat_to_nat {c : char} (h : is_valid_char c.to_nat) :
  char.of_nat c.to_nat = c :=
begin
  rw [char.of_nat, dif_pos h],
  cases c,
  simp [char.to_nat]
end


import data.real.basic

theorem AMC_2021_A_7 (x y:ℝ): (x*y-1)^2+(x+y)^2≥1:=
begin
  sorry
end


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Bhavik Mehta
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.limits.limits
import Mathlib.category_theory.limits.shapes.terminal
import Mathlib.category_theory.discrete_category
import Mathlib.category_theory.epi_mono
import Mathlib.PostPort

universes v l u_1 u_2 u u₂

namespace Mathlib

/-!
# Binary (co)products

We define a category `walking_pair`, which is the index category
for a binary (co)product diagram. A convenience method `pair X Y`
constructs the functor from the walking pair, hitting the given objects.

We define `prod X Y` and `coprod X Y` as limits and colimits of such functors.

Typeclasses `has_binary_products` and `has_binary_coproducts` assert the existence
of (co)limits shaped as walking pairs.

We include lemmas for simplifying equations involving projections and coprojections, and define
braiding and associating isomorphisms, and the product comparison morphism.

## References
* [Stacks: Products of pairs](https://stacks.math.columbia.edu/tag/001R)
* [Stacks: coproducts of pairs](https://stacks.math.columbia.edu/tag/04AN)
-/

namespace category_theory.limits

/-- The type of objects for the diagram indexing a binary (co)product. -/
inductive walking_pair where
| left : walking_pair
| right : walking_pair

/--
The equivalence swapping left and right.
-/
def walking_pair.swap : walking_pair ≃ walking_pair :=
  equiv.mk (fun (j : walking_pair) => walking_pair.rec_on j walking_pair.right walking_pair.left)
    (fun (j : walking_pair) => walking_pair.rec_on j walking_pair.right walking_pair.left) sorry
    sorry

@[simp] theorem walking_pair.swap_apply_left :
    coe_fn walking_pair.swap walking_pair.left = walking_pair.right :=
  rfl

@[simp] theorem walking_pair.swap_apply_right :
    coe_fn walking_pair.swap walking_pair.right = walking_pair.left :=
  rfl

@[simp] theorem walking_pair.swap_symm_apply_tt :
    coe_fn (equiv.symm walking_pair.swap) walking_pair.left = walking_pair.right :=
  rfl

@[simp] theorem walking_pair.swap_symm_apply_ff :
    coe_fn (equiv.symm walking_pair.swap) walking_pair.right = walking_pair.left :=
  rfl

/--
An equivalence from `walking_pair` to `bool`, sometimes useful when reindexing limits.
-/
def walking_pair.equiv_bool : walking_pair ≃ Bool :=
  equiv.mk (fun (j : walking_pair) => walking_pair.rec_on j tt false)
    (fun (b : Bool) => bool.rec_on b walking_pair.right walking_pair.left) sorry sorry

@[simp] theorem walking_pair.equiv_bool_apply_left :
    coe_fn walking_pair.equiv_bool walking_pair.left = tt :=
  rfl

@[simp] theorem walking_pair.equiv_bool_apply_right :
    coe_fn walking_pair.equiv_bool walking_pair.right = false :=
  rfl

@[simp] theorem walking_pair.equiv_bool_symm_apply_tt :
    coe_fn (equiv.symm walking_pair.equiv_bool) tt = walking_pair.left :=
  rfl

@[simp] theorem walking_pair.equiv_bool_symm_apply_ff :
    coe_fn (equiv.symm walking_pair.equiv_bool) false = walking_pair.right :=
  rfl

/-- The diagram on the walking pair, sending the two points to `X` and `Y`. -/
def pair {C : Type u} [category C] (X : C) (Y : C) : discrete walking_pair ⥤ C :=
  discrete.functor fun (j : walking_pair) => walking_pair.cases_on j X Y

@[simp] theorem pair_obj_left {C : Type u} [category C] (X : C) (Y : C) :
    functor.obj (pair X Y) walking_pair.left = X :=
  rfl

@[simp] theorem pair_obj_right {C : Type u} [category C] (X : C) (Y : C) :
    functor.obj (pair X Y) walking_pair.right = Y :=
  rfl

/-- The natural transformation between two functors out of the walking pair, specified by its components. -/
def map_pair {C : Type u} [category C] {F : discrete walking_pair ⥤ C}
    {G : discrete walking_pair ⥤ C}
    (f : functor.obj F walking_pair.left ⟶ functor.obj G walking_pair.left)
    (g : functor.obj F walking_pair.right ⟶ functor.obj G walking_pair.right) : F ⟶ G :=
  nat_trans.mk fun (j : discrete walking_pair) => walking_pair.cases_on j f g

@[simp] theorem map_pair_left {C : Type u} [category C] {F : discrete walking_pair ⥤ C}
    {G : discrete walking_pair ⥤ C}
    (f : functor.obj F walking_pair.left ⟶ functor.obj G walking_pair.left)
    (g : functor.obj F walking_pair.right ⟶ functor.obj G walking_pair.right) :
    nat_trans.app (map_pair f g) walking_pair.left = f :=
  rfl

@[simp] theorem map_pair_right {C : Type u} [category C] {F : discrete walking_pair ⥤ C}
    {G : discrete walking_pair ⥤ C}
    (f : functor.obj F walking_pair.left ⟶ functor.obj G walking_pair.left)
    (g : functor.obj F walking_pair.right ⟶ functor.obj G walking_pair.right) :
    nat_trans.app (map_pair f g) walking_pair.right = g :=
  rfl

/-- The natural isomorphism between two functors out of the walking pair, specified by its components. -/
def map_pair_iso {C : Type u} [category C] {F : discrete walking_pair ⥤ C}
    {G : discrete walking_pair ⥤ C}
    (f : functor.obj F walking_pair.left ≅ functor.obj G walking_pair.left)
    (g : functor.obj F walking_pair.right ≅ functor.obj G walking_pair.right) : F ≅ G :=
  nat_iso.of_components (fun (j : discrete walking_pair) => walking_pair.cases_on j f g) sorry

/-- Every functor out of the walking pair is naturally isomorphic (actually, equal) to a `pair` -/
@[simp] theorem diagram_iso_pair_inv_app {C : Type u} [category C] (F : discrete walking_pair ⥤ C)
    (X : discrete walking_pair) :
    nat_trans.app (iso.inv (diagram_iso_pair F)) X =
        iso.inv
          (walking_pair.rec (iso.refl (functor.obj F walking_pair.left))
            (iso.refl (functor.obj F walking_pair.right)) X) :=
  Eq.refl
    (iso.inv
      (walking_pair.rec (iso.refl (functor.obj F walking_pair.left))
        (iso.refl (functor.obj F walking_pair.right)) X))

/-- The natural isomorphism between `pair X Y ⋙ F` and `pair (F.obj X) (F.obj Y)`. -/
def pair_comp {C : Type u} [category C] {D : Type u} [category D] (X : C) (Y : C) (F : C ⥤ D) :
    pair X Y ⋙ F ≅ pair (functor.obj F X) (functor.obj F Y) :=
  diagram_iso_pair (pair X Y ⋙ F)

/-- A binary fan is just a cone on a diagram indexing a product. -/
def binary_fan {C : Type u} [category C] (X : C) (Y : C) := cone (pair X Y)

/-- The first projection of a binary fan. -/
def binary_fan.fst {C : Type u} [category C] {X : C} {Y : C} (s : binary_fan X Y) :
    functor.obj (functor.obj (functor.const (discrete walking_pair)) (cone.X s)) walking_pair.left ⟶
        functor.obj (pair X Y) walking_pair.left :=
  nat_trans.app (cone.π s) walking_pair.left

/-- The second projection of a binary fan. -/
def binary_fan.snd {C : Type u} [category C] {X : C} {Y : C} (s : binary_fan X Y) :
    functor.obj (functor.obj (functor.const (discrete walking_pair)) (cone.X s))
          walking_pair.right ⟶
        functor.obj (pair X Y) walking_pair.right :=
  nat_trans.app (cone.π s) walking_pair.right

@[simp] theorem binary_fan.π_app_left {C : Type u} [category C] {X : C} {Y : C}
    (s : binary_fan X Y) : nat_trans.app (cone.π s) walking_pair.left = binary_fan.fst s :=
  rfl

@[simp] theorem binary_fan.π_app_right {C : Type u} [category C] {X : C} {Y : C}
    (s : binary_fan X Y) : nat_trans.app (cone.π s) walking_pair.right = binary_fan.snd s :=
  rfl

theorem binary_fan.is_limit.hom_ext {C : Type u} [category C] {W : C} {X : C} {Y : C}
    {s : binary_fan X Y} (h : is_limit s) {f : W ⟶ cone.X s} {g : W ⟶ cone.X s}
    (h₁ : f ≫ binary_fan.fst s = g ≫ binary_fan.fst s)
    (h₂ : f ≫ binary_fan.snd s = g ≫ binary_fan.snd s) : f = g :=
  is_limit.hom_ext h fun (j : discrete walking_pair) => walking_pair.cases_on j h₁ h₂

/-- A binary cofan is just a cocone on a diagram indexing a coproduct. -/
def binary_cofan {C : Type u} [category C] (X : C) (Y : C) := cocone (pair X Y)

/-- The first inclusion of a binary cofan. -/
def binary_cofan.inl {C : Type u} [category C] {X : C} {Y : C} (s : binary_cofan X Y) :
    functor.obj (pair X Y) walking_pair.left ⟶
        functor.obj (functor.obj (functor.const (discrete walking_pair)) (cocone.X s))
          walking_pair.left :=
  nat_trans.app (cocone.ι s) walking_pair.left

/-- The second inclusion of a binary cofan. -/
def binary_cofan.inr {C : Type u} [category C] {X : C} {Y : C} (s : binary_cofan X Y) :
    functor.obj (pair X Y) walking_pair.right ⟶
        functor.obj (functor.obj (functor.const (discrete walking_pair)) (cocone.X s))
          walking_pair.right :=
  nat_trans.app (cocone.ι s) walking_pair.right

@[simp] theorem binary_cofan.ι_app_left {C : Type u} [category C] {X : C} {Y : C}
    (s : binary_cofan X Y) : nat_trans.app (cocone.ι s) walking_pair.left = binary_cofan.inl s :=
  rfl

@[simp] theorem binary_cofan.ι_app_right {C : Type u} [category C] {X : C} {Y : C}
    (s : binary_cofan X Y) : nat_trans.app (cocone.ι s) walking_pair.right = binary_cofan.inr s :=
  rfl

theorem binary_cofan.is_colimit.hom_ext {C : Type u} [category C] {W : C} {X : C} {Y : C}
    {s : binary_cofan X Y} (h : is_colimit s) {f : cocone.X s ⟶ W} {g : cocone.X s ⟶ W}
    (h₁ : binary_cofan.inl s ≫ f = binary_cofan.inl s ≫ g)
    (h₂ : binary_cofan.inr s ≫ f = binary_cofan.inr s ≫ g) : f = g :=
  is_colimit.hom_ext h fun (j : discrete walking_pair) => walking_pair.cases_on j h₁ h₂

/-- A binary fan with vertex `P` consists of the two projections `π₁ : P ⟶ X` and `π₂ : P ⟶ Y`. -/
def binary_fan.mk {C : Type u} [category C] {X : C} {Y : C} {P : C} (π₁ : P ⟶ X) (π₂ : P ⟶ Y) :
    binary_fan X Y :=
  cone.mk P (nat_trans.mk fun (j : discrete walking_pair) => walking_pair.cases_on j π₁ π₂)

/-- A binary cofan with vertex `P` consists of the two inclusions `ι₁ : X ⟶ P` and `ι₂ : Y ⟶ P`. -/
def binary_cofan.mk {C : Type u} [category C] {X : C} {Y : C} {P : C} (ι₁ : X ⟶ P) (ι₂ : Y ⟶ P) :
    binary_cofan X Y :=
  cocone.mk P (nat_trans.mk fun (j : discrete walking_pair) => walking_pair.cases_on j ι₁ ι₂)

@[simp] theorem binary_fan.mk_π_app_left {C : Type u} [category C] {X : C} {Y : C} {P : C}
    (π₁ : P ⟶ X) (π₂ : P ⟶ Y) :
    nat_trans.app (cone.π (binary_fan.mk π₁ π₂)) walking_pair.left = π₁ :=
  rfl

@[simp] theorem binary_fan.mk_π_app_right {C : Type u} [category C] {X : C} {Y : C} {P : C}
    (π₁ : P ⟶ X) (π₂ : P ⟶ Y) :
    nat_trans.app (cone.π (binary_fan.mk π₁ π₂)) walking_pair.right = π₂ :=
  rfl

@[simp] theorem binary_cofan.mk_ι_app_left {C : Type u} [category C] {X : C} {Y : C} {P : C}
    (ι₁ : X ⟶ P) (ι₂ : Y ⟶ P) :
    nat_trans.app (cocone.ι (binary_cofan.mk ι₁ ι₂)) walking_pair.left = ι₁ :=
  rfl

@[simp] theorem binary_cofan.mk_ι_app_right {C : Type u} [category C] {X : C} {Y : C} {P : C}
    (ι₁ : X ⟶ P) (ι₂ : Y ⟶ P) :
    nat_trans.app (cocone.ι (binary_cofan.mk ι₁ ι₂)) walking_pair.right = ι₂ :=
  rfl

/-- If `s` is a limit binary fan over `X` and `Y`, then every pair of morphisms `f : W ⟶ X` and
    `g : W ⟶ Y` induces a morphism `l : W ⟶ s.X` satisfying `l ≫ s.fst = f` and `l ≫ s.snd = g`.
    -/
@[simp] theorem binary_fan.is_limit.lift'_coe {C : Type u} [category C] {W : C} {X : C} {Y : C}
    {s : binary_fan X Y} (h : is_limit s) (f : W ⟶ X) (g : W ⟶ Y) :
    ↑(binary_fan.is_limit.lift' h f g) = is_limit.lift h (binary_fan.mk f g) :=
  Eq.refl ↑(binary_fan.is_limit.lift' h f g)

/-- If `s` is a colimit binary cofan over `X` and `Y`,, then every pair of morphisms `f : X ⟶ W` and
    `g : Y ⟶ W` induces a morphism `l : s.X ⟶ W` satisfying `s.inl ≫ l = f` and `s.inr ≫ l = g`.
    -/
@[simp] theorem binary_cofan.is_colimit.desc'_coe {C : Type u} [category C] {W : C} {X : C} {Y : C}
    {s : binary_cofan X Y} (h : is_colimit s) (f : X ⟶ W) (g : Y ⟶ W) :
    ↑(binary_cofan.is_colimit.desc' h f g) = is_colimit.desc h (binary_cofan.mk f g) :=
  Eq.refl ↑(binary_cofan.is_colimit.desc' h f g)

/-- An abbreviation for `has_limit (pair X Y)`. -/
/-- An abbreviation for `has_colimit (pair X Y)`. -/
def has_binary_product {C : Type u} [category C] (X : C) (Y : C) := has_limit (pair X Y)

def has_binary_coproduct {C : Type u} [category C] (X : C) (Y : C) := has_colimit (pair X Y)

/-- If we have a product of `X` and `Y`, we can access it using `prod X Y` or
    `X ⨯ Y`. -/
def prod {C : Type u} [category C] (X : C) (Y : C) [has_binary_product X Y] : C := limit (pair X Y)

/-- If we have a coproduct of `X` and `Y`, we can access it using `coprod X Y ` or
    `X ⨿ Y`. -/
def coprod {C : Type u} [category C] (X : C) (Y : C) [has_binary_coproduct X Y] : C :=
  colimit (pair X Y)

infixl:20 " ⨯ " => Mathlib.category_theory.limits.prod

infixl:20 " ⨿ " => Mathlib.category_theory.limits.coprod

/-- The projection map to the first component of the product. -/
def prod.fst {C : Type u} [category C] {X : C} {Y : C} [has_binary_product X Y] : X ⨯ Y ⟶ X :=
  limit.π (pair X Y) walking_pair.left

/-- The projecton map to the second component of the product. -/
def prod.snd {C : Type u} [category C] {X : C} {Y : C} [has_binary_product X Y] : X ⨯ Y ⟶ Y :=
  limit.π (pair X Y) walking_pair.right

/-- The inclusion map from the first component of the coproduct. -/
def coprod.inl {C : Type u} [category C] {X : C} {Y : C} [has_binary_coproduct X Y] : X ⟶ X ⨿ Y :=
  colimit.ι (pair X Y) walking_pair.left

/-- The inclusion map from the second component of the coproduct. -/
def coprod.inr {C : Type u} [category C] {X : C} {Y : C} [has_binary_coproduct X Y] : Y ⟶ X ⨿ Y :=
  colimit.ι (pair X Y) walking_pair.right

/-- The binary fan constructed from the projection maps is a limit. -/
def prod_is_prod {C : Type u} [category C] (X : C) (Y : C) [has_binary_product X Y] :
    is_limit (binary_fan.mk prod.fst prod.snd) :=
  is_limit.of_iso_limit (limit.is_limit (pair X Y))
    (cones.ext (iso.refl (cone.X (limit.cone (pair X Y)))) sorry)

/-- The binary cofan constructed from the coprojection maps is a colimit. -/
def coprod_is_coprod {C : Type u} [category C] {X : C} {Y : C} [has_binary_coproduct X Y] :
    is_colimit (binary_cofan.mk coprod.inl coprod.inr) :=
  is_colimit.of_iso_colimit (colimit.is_colimit (pair X Y))
    (cocones.ext (iso.refl (cocone.X (colimit.cocone (pair X Y)))) sorry)

theorem prod.hom_ext {C : Type u} [category C] {W : C} {X : C} {Y : C} [has_binary_product X Y]
    {f : W ⟶ X ⨯ Y} {g : W ⟶ X ⨯ Y} (h₁ : f ≫ prod.fst = g ≫ prod.fst)
    (h₂ : f ≫ prod.snd = g ≫ prod.snd) : f = g :=
  binary_fan.is_limit.hom_ext (limit.is_limit (pair X Y)) h₁ h₂

theorem coprod.hom_ext {C : Type u} [category C] {W : C} {X : C} {Y : C} [has_binary_coproduct X Y]
    {f : X ⨿ Y ⟶ W} {g : X ⨿ Y ⟶ W} (h₁ : coprod.inl ≫ f = coprod.inl ≫ g)
    (h₂ : coprod.inr ≫ f = coprod.inr ≫ g) : f = g :=
  binary_cofan.is_colimit.hom_ext (colimit.is_colimit (pair X Y)) h₁ h₂

/-- If the product of `X` and `Y` exists, then every pair of morphisms `f : W ⟶ X` and `g : W ⟶ Y`
    induces a morphism `prod.lift f g : W ⟶ X ⨯ Y`. -/
def prod.lift {C : Type u} [category C] {W : C} {X : C} {Y : C} [has_binary_product X Y] (f : W ⟶ X)
    (g : W ⟶ Y) : W ⟶ X ⨯ Y :=
  limit.lift (pair X Y) (binary_fan.mk f g)

/-- diagonal arrow of the binary product in the category `fam I` -/
def diag {C : Type u} [category C] (X : C) [has_binary_product X X] : X ⟶ X ⨯ X := prod.lift 𝟙 𝟙

/-- If the coproduct of `X` and `Y` exists, then every pair of morphisms `f : X ⟶ W` and
    `g : Y ⟶ W` induces a morphism `coprod.desc f g : X ⨿ Y ⟶ W`. -/
def coprod.desc {C : Type u} [category C] {W : C} {X : C} {Y : C} [has_binary_coproduct X Y]
    (f : X ⟶ W) (g : Y ⟶ W) : X ⨿ Y ⟶ W :=
  colimit.desc (pair X Y) (binary_cofan.mk f g)

/-- codiagonal arrow of the binary coproduct -/
def codiag {C : Type u} [category C] (X : C) [has_binary_coproduct X X] : X ⨿ X ⟶ X :=
  coprod.desc 𝟙 𝟙

@[simp] theorem prod.lift_fst_assoc {C : Type u} [category C] {W : C} {X : C} {Y : C}
    [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y) {X' : C} (f' : X ⟶ X') :
    prod.lift f g ≫ prod.fst ≫ f' = f ≫ f' :=
  sorry

@[simp] theorem prod.lift_snd {C : Type u} [category C] {W : C} {X : C} {Y : C}
    [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y) : prod.lift f g ≫ prod.snd = g :=
  limit.lift_π (binary_fan.mk f g) walking_pair.right

-- The simp linter says simp can prove the reassoc version of this lemma.

theorem coprod.inl_desc_assoc {C : Type u} [category C] {W : C} {X : C} {Y : C}
    [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W) {X' : C} (f' : W ⟶ X') :
    coprod.inl ≫ coprod.desc f g ≫ f' = f ≫ f' :=
  sorry

-- The simp linter says simp can prove the reassoc version of this lemma.

theorem coprod.inr_desc_assoc {C : Type u} [category C] {W : C} {X : C} {Y : C}
    [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W) {X' : C} (f' : W ⟶ X') :
    coprod.inr ≫ coprod.desc f g ≫ f' = g ≫ f' :=
  sorry

protected instance prod.mono_lift_of_mono_left {C : Type u} [category C] {W : C} {X : C} {Y : C}
    [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y) [mono f] : mono (prod.lift f g) :=
  mono_of_mono_fac (prod.lift_fst f g)

protected instance prod.mono_lift_of_mono_right {C : Type u} [category C] {W : C} {X : C} {Y : C}
    [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y) [mono g] : mono (prod.lift f g) :=
  mono_of_mono_fac (prod.lift_snd f g)

protected instance coprod.epi_desc_of_epi_left {C : Type u} [category C] {W : C} {X : C} {Y : C}
    [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W) [epi f] : epi (coprod.desc f g) :=
  epi_of_epi_fac (coprod.inl_desc f g)

protected instance coprod.epi_desc_of_epi_right {C : Type u} [category C] {W : C} {X : C} {Y : C}
    [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W) [epi g] : epi (coprod.desc f g) :=
  epi_of_epi_fac (coprod.inr_desc f g)

/-- If the product of `X` and `Y` exists, then every pair of morphisms `f : W ⟶ X` and `g : W ⟶ Y`
    induces a morphism `l : W ⟶ X ⨯ Y` satisfying `l ≫ prod.fst = f` and `l ≫ prod.snd = g`. -/
def prod.lift' {C : Type u} [category C] {W : C} {X : C} {Y : C} [has_binary_product X Y]
    (f : W ⟶ X) (g : W ⟶ Y) : Subtype fun (l : W ⟶ X ⨯ Y) => l ≫ prod.fst = f ∧ l ≫ prod.snd = g :=
  { val := prod.lift f g, property := sorry }

/-- If the coproduct of `X` and `Y` exists, then every pair of morphisms `f : X ⟶ W` and
    `g : Y ⟶ W` induces a morphism `l : X ⨿ Y ⟶ W` satisfying `coprod.inl ≫ l = f` and
    `coprod.inr ≫ l = g`. -/
def coprod.desc' {C : Type u} [category C] {W : C} {X : C} {Y : C} [has_binary_coproduct X Y]
    (f : X ⟶ W) (g : Y ⟶ W) :
    Subtype fun (l : X ⨿ Y ⟶ W) => coprod.inl ≫ l = f ∧ coprod.inr ≫ l = g :=
  { val := coprod.desc f g, property := sorry }

/-- If the products `W ⨯ X` and `Y ⨯ Z` exist, then every pair of morphisms `f : W ⟶ Y` and
    `g : X ⟶ Z` induces a morphism `prod.map f g : W ⨯ X ⟶ Y ⨯ Z`. -/
def prod.map {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C} [has_binary_product W X]
    [has_binary_product Y Z] (f : W ⟶ Y) (g : X ⟶ Z) : W ⨯ X ⟶ Y ⨯ Z :=
  lim_map (map_pair f g)

/-- If the coproducts `W ⨿ X` and `Y ⨿ Z` exist, then every pair of morphisms `f : W ⟶ Y` and
    `g : W ⟶ Z` induces a morphism `coprod.map f g : W ⨿ X ⟶ Y ⨿ Z`. -/
def coprod.map {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C} [has_binary_coproduct W X]
    [has_binary_coproduct Y Z] (f : W ⟶ Y) (g : X ⟶ Z) : W ⨿ X ⟶ Y ⨿ Z :=
  colim_map (map_pair f g)

-- Making the reassoc version of this a simp lemma seems to be more harmful than helpful.

theorem prod.comp_lift_assoc {C : Type u} [category C] {V : C} {W : C} {X : C} {Y : C}
    [has_binary_product X Y] (f : V ⟶ W) (g : W ⟶ X) (h : W ⟶ Y) {X' : C} (f' : X ⨯ Y ⟶ X') :
    f ≫ prod.lift g h ≫ f' = prod.lift (f ≫ g) (f ≫ h) ≫ f' :=
  sorry

theorem prod.comp_diag {C : Type u} [category C] {X : C} {Y : C} [has_binary_product Y Y]
    (f : X ⟶ Y) : f ≫ diag Y = prod.lift f f :=
  sorry

@[simp] theorem prod.map_fst {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C}
    [has_binary_product W X] [has_binary_product Y Z] (f : W ⟶ Y) (g : X ⟶ Z) :
    prod.map f g ≫ prod.fst = prod.fst ≫ f :=
  lim_map_π (map_pair f g) walking_pair.left

@[simp] theorem prod.map_snd_assoc {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C}
    [has_binary_product W X] [has_binary_product Y Z] (f : W ⟶ Y) (g : X ⟶ Z) {X' : C}
    (f' : Z ⟶ X') : prod.map f g ≫ prod.snd ≫ f' = prod.snd ≫ g ≫ f' :=
  sorry

@[simp] theorem prod.map_id_id {C : Type u} [category C] {X : C} {Y : C} [has_binary_product X Y] :
    prod.map 𝟙 𝟙 = 𝟙 :=
  sorry

@[simp] theorem prod.lift_fst_snd {C : Type u} [category C] {X : C} {Y : C}
    [has_binary_product X Y] : prod.lift prod.fst prod.snd = 𝟙 :=
  sorry

@[simp] theorem prod.lift_map {C : Type u} [category C] {V : C} {W : C} {X : C} {Y : C} {Z : C}
    [has_binary_product W X] [has_binary_product Y Z] (f : V ⟶ W) (g : V ⟶ X) (h : W ⟶ Y)
    (k : X ⟶ Z) : prod.lift f g ≫ prod.map h k = prod.lift (f ≫ h) (g ≫ k) :=
  sorry

@[simp] theorem prod.lift_fst_comp_snd_comp {C : Type u} [category C] {W : C} {X : C} {Y : C}
    {Z : C} [has_binary_product W Y] [has_binary_product X Z] (g : W ⟶ X) (g' : Y ⟶ Z) :
    prod.lift (prod.fst ≫ g) (prod.snd ≫ g') = prod.map g g' :=
  sorry

-- We take the right hand side here to be simp normal form, as this way composition lemmas for

-- `f ≫ h` and `g ≫ k` can fire (eg `id_comp`) , while `map_fst` and `map_snd` can still work just

-- as well.

@[simp] theorem prod.map_map_assoc {C : Type u} [category C] {A₁ : C} {A₂ : C} {A₃ : C} {B₁ : C}
    {B₂ : C} {B₃ : C} [has_binary_product A₁ B₁] [has_binary_product A₂ B₂]
    [has_binary_product A₃ B₃] (f : A₁ ⟶ A₂) (g : B₁ ⟶ B₂) (h : A₂ ⟶ A₃) (k : B₂ ⟶ B₃) {X' : C}
    (f' : A₃ ⨯ B₃ ⟶ X') : prod.map f g ≫ prod.map h k ≫ f' = prod.map (f ≫ h) (g ≫ k) ≫ f' :=
  sorry

-- TODO: is it necessary to weaken the assumption here?

theorem prod.map_swap {C : Type u} [category C] {A : C} {B : C} {X : C} {Y : C} (f : A ⟶ B)
    (g : X ⟶ Y) [has_limits_of_shape (discrete walking_pair) C] :
    prod.map 𝟙 f ≫ prod.map g 𝟙 = prod.map g 𝟙 ≫ prod.map 𝟙 f :=
  sorry

theorem prod.map_comp_id {C : Type u} [category C] {X : C} {Y : C} {Z : C} {W : C} (f : X ⟶ Y)
    (g : Y ⟶ Z) [has_binary_product X W] [has_binary_product Z W] [has_binary_product Y W] :
    prod.map (f ≫ g) 𝟙 = prod.map f 𝟙 ≫ prod.map g 𝟙 :=
  sorry

theorem prod.map_id_comp {C : Type u} [category C] {X : C} {Y : C} {Z : C} {W : C} (f : X ⟶ Y)
    (g : Y ⟶ Z) [has_binary_product W X] [has_binary_product W Y] [has_binary_product W Z] :
    prod.map 𝟙 (f ≫ g) = prod.map 𝟙 f ≫ prod.map 𝟙 g :=
  sorry

/-- If the products `W ⨯ X` and `Y ⨯ Z` exist, then every pair of isomorphisms `f : W ≅ Y` and
    `g : X ≅ Z` induces an isomorphism `prod.map_iso f g : W ⨯ X ≅ Y ⨯ Z`. -/
@[simp] theorem prod.map_iso_inv {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C}
    [has_binary_product W X] [has_binary_product Y Z] (f : W ≅ Y) (g : X ≅ Z) :
    iso.inv (prod.map_iso f g) = prod.map (iso.inv f) (iso.inv g) :=
  Eq.refl (iso.inv (prod.map_iso f g))

protected instance is_iso_prod {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C}
    [has_binary_product W X] [has_binary_product Y Z] (f : W ⟶ Y) (g : X ⟶ Z) [is_iso f]
    [is_iso g] : is_iso (prod.map f g) :=
  is_iso.of_iso (prod.map_iso (as_iso f) (as_iso g))

@[simp] theorem prod.diag_map {C : Type u} [category C] {X : C} {Y : C} (f : X ⟶ Y)
    [has_binary_product X X] [has_binary_product Y Y] : diag X ≫ prod.map f f = f ≫ diag Y :=
  sorry

@[simp] theorem prod.diag_map_fst_snd_assoc {C : Type u} [category C] {X : C} {Y : C}
    [has_binary_product X Y] [has_binary_product (X ⨯ Y) (X ⨯ Y)] {X' : C} (f' : X ⨯ Y ⟶ X') :
    diag (X ⨯ Y) ≫ prod.map prod.fst prod.snd ≫ f' = f' :=
  sorry

@[simp] theorem prod.diag_map_fst_snd_comp_assoc {C : Type u} [category C]
    [has_limits_of_shape (discrete walking_pair) C] {X : C} {X' : C} {Y : C} {Y' : C} (g : X ⟶ Y)
    (g' : X' ⟶ Y') :
    ∀ {X'_1 : C} (f' : Y ⨯ Y' ⟶ X'_1),
        diag (X ⨯ X') ≫ prod.map (prod.fst ≫ g) (prod.snd ≫ g') ≫ f' = prod.map g g' ≫ f' :=
  sorry

protected instance diag.category_theory.split_mono {C : Type u} [category C] {X : C}
    [has_binary_product X X] : split_mono (diag X) :=
  split_mono.mk prod.fst

@[simp] theorem coprod.desc_comp_assoc {C : Type u} [category C] {V : C} {W : C} {X : C} {Y : C}
    [has_binary_coproduct X Y] (f : V ⟶ W) (g : X ⟶ V) (h : Y ⟶ V) {X' : C} (f' : W ⟶ X') :
    coprod.desc g h ≫ f ≫ f' = coprod.desc (g ≫ f) (h ≫ f) ≫ f' :=
  sorry

theorem coprod.diag_comp {C : Type u} [category C] {X : C} {Y : C} [has_binary_coproduct X X]
    (f : X ⟶ Y) : codiag X ≫ f = coprod.desc f f :=
  sorry

@[simp] theorem coprod.inl_map {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C}
    [has_binary_coproduct W X] [has_binary_coproduct Y Z] (f : W ⟶ Y) (g : X ⟶ Z) :
    coprod.inl ≫ coprod.map f g = f ≫ coprod.inl :=
  ι_colim_map (map_pair f g) walking_pair.left

@[simp] theorem coprod.inr_map {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C}
    [has_binary_coproduct W X] [has_binary_coproduct Y Z] (f : W ⟶ Y) (g : X ⟶ Z) :
    coprod.inr ≫ coprod.map f g = g ≫ coprod.inr :=
  ι_colim_map (map_pair f g) walking_pair.right

@[simp] theorem coprod.map_id_id {C : Type u} [category C] {X : C} {Y : C}
    [has_binary_coproduct X Y] : coprod.map 𝟙 𝟙 = 𝟙 :=
  sorry

@[simp] theorem coprod.desc_inl_inr {C : Type u} [category C] {X : C} {Y : C}
    [has_binary_coproduct X Y] : coprod.desc coprod.inl coprod.inr = 𝟙 :=
  sorry

-- The simp linter says simp can prove the reassoc version of this lemma.

@[simp] theorem coprod.map_desc {C : Type u} [category C] {S : C} {T : C} {U : C} {V : C} {W : C}
    [has_binary_coproduct U W] [has_binary_coproduct T V] (f : U ⟶ S) (g : W ⟶ S) (h : T ⟶ U)
    (k : V ⟶ W) : coprod.map h k ≫ coprod.desc f g = coprod.desc (h ≫ f) (k ≫ g) :=
  sorry

@[simp] theorem coprod.desc_comp_inl_comp_inr {C : Type u} [category C] {W : C} {X : C} {Y : C}
    {Z : C} [has_binary_coproduct W Y] [has_binary_coproduct X Z] (g : W ⟶ X) (g' : Y ⟶ Z) :
    coprod.desc (g ≫ coprod.inl) (g' ≫ coprod.inr) = coprod.map g g' :=
  sorry

-- We take the right hand side here to be simp normal form, as this way composition lemmas for

-- `f ≫ h` and `g ≫ k` can fire (eg `id_comp`) , while `inl_map` and `inr_map` can still work just

-- as well.

@[simp] theorem coprod.map_map {C : Type u} [category C] {A₁ : C} {A₂ : C} {A₃ : C} {B₁ : C}
    {B₂ : C} {B₃ : C} [has_binary_coproduct A₁ B₁] [has_binary_coproduct A₂ B₂]
    [has_binary_coproduct A₃ B₃] (f : A₁ ⟶ A₂) (g : B₁ ⟶ B₂) (h : A₂ ⟶ A₃) (k : B₂ ⟶ B₃) :
    coprod.map f g ≫ coprod.map h k = coprod.map (f ≫ h) (g ≫ k) :=
  sorry

-- I don't think it's a good idea to make any of the following three simp lemmas.

theorem coprod.map_swap_assoc {C : Type u} [category C] {A : C} {B : C} {X : C} {Y : C} (f : A ⟶ B)
    (g : X ⟶ Y) [has_colimits_of_shape (discrete walking_pair) C] {X' : C} (f' : Y ⨿ B ⟶ X') :
    coprod.map 𝟙 f ≫ coprod.map g 𝟙 ≫ f' = coprod.map g 𝟙 ≫ coprod.map 𝟙 f ≫ f' :=
  sorry

theorem coprod.map_comp_id {C : Type u} [category C] {X : C} {Y : C} {Z : C} {W : C} (f : X ⟶ Y)
    (g : Y ⟶ Z) [has_binary_coproduct Z W] [has_binary_coproduct Y W] [has_binary_coproduct X W] :
    coprod.map (f ≫ g) 𝟙 = coprod.map f 𝟙 ≫ coprod.map g 𝟙 :=
  sorry

theorem coprod.map_id_comp {C : Type u} [category C] {X : C} {Y : C} {Z : C} {W : C} (f : X ⟶ Y)
    (g : Y ⟶ Z) [has_binary_coproduct W X] [has_binary_coproduct W Y] [has_binary_coproduct W Z] :
    coprod.map 𝟙 (f ≫ g) = coprod.map 𝟙 f ≫ coprod.map 𝟙 g :=
  sorry

/-- If the coproducts `W ⨿ X` and `Y ⨿ Z` exist, then every pair of isomorphisms `f : W ≅ Y` and
    `g : W ≅ Z` induces a isomorphism `coprod.map_iso f g : W ⨿ X ≅ Y ⨿ Z`. -/
@[simp] theorem coprod.map_iso_hom {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C}
    [has_binary_coproduct W X] [has_binary_coproduct Y Z] (f : W ≅ Y) (g : X ≅ Z) :
    iso.hom (coprod.map_iso f g) = coprod.map (iso.hom f) (iso.hom g) :=
  Eq.refl (iso.hom (coprod.map_iso f g))

protected instance is_iso_coprod {C : Type u} [category C] {W : C} {X : C} {Y : C} {Z : C}
    [has_binary_coproduct W X] [has_binary_coproduct Y Z] (f : W ⟶ Y) (g : X ⟶ Z) [is_iso f]
    [is_iso g] : is_iso (coprod.map f g) :=
  is_iso.of_iso (coprod.map_iso (as_iso f) (as_iso g))

-- The simp linter says simp can prove the reassoc version of this lemma.

@[simp] theorem coprod.map_codiag {C : Type u} [category C] {X : C} {Y : C} (f : X ⟶ Y)
    [has_binary_coproduct X X] [has_binary_coproduct Y Y] :
    coprod.map f f ≫ codiag Y = codiag X ≫ f :=
  sorry

-- The simp linter says simp can prove the reassoc version of this lemma.

theorem coprod.map_inl_inr_codiag_assoc {C : Type u} [category C] {X : C} {Y : C}
    [has_binary_coproduct X Y] [has_binary_coproduct (X ⨿ Y) (X ⨿ Y)] {X' : C} (f' : X ⨿ Y ⟶ X') :
    coprod.map coprod.inl coprod.inr ≫ codiag (X ⨿ Y) ≫ f' = f' :=
  sorry

-- The simp linter says simp can prove the reassoc version of this lemma.

@[simp] theorem coprod.map_comp_inl_inr_codiag {C : Type u} [category C]
    [has_colimits_of_shape (discrete walking_pair) C] {X : C} {X' : C} {Y : C} {Y' : C} (g : X ⟶ Y)
    (g' : X' ⟶ Y') :
    coprod.map (g ≫ coprod.inl) (g' ≫ coprod.inr) ≫ codiag (Y ⨿ Y') = coprod.map g g' :=
  sorry

/--
`has_binary_products` represents a choice of product for every pair of objects.

See https://stacks.math.columbia.edu/tag/001T.
-/
def has_binary_products (C : Type u) [category C] := has_limits_of_shape (discrete walking_pair) C

/--
`has_binary_coproducts` represents a choice of coproduct for every pair of objects.

See https://stacks.math.columbia.edu/tag/04AP.
-/
def has_binary_coproducts (C : Type u) [category C] :=
  has_colimits_of_shape (discrete walking_pair) C

/-- If `C` has all limits of diagrams `pair X Y`, then it has all binary products -/
theorem has_binary_products_of_has_limit_pair (C : Type u) [category C]
    [∀ {X Y : C}, has_limit (pair X Y)] : has_binary_products C :=
  has_limits_of_shape.mk
    fun (F : discrete walking_pair ⥤ C) => has_limit_of_iso (iso.symm (diagram_iso_pair F))

/-- If `C` has all colimits of diagrams `pair X Y`, then it has all binary coproducts -/
theorem has_binary_coproducts_of_has_colimit_pair (C : Type u) [category C]
    [∀ {X Y : C}, has_colimit (pair X Y)] : has_binary_coproducts C :=
  has_colimits_of_shape.mk
    fun (F : discrete walking_pair ⥤ C) => has_colimit_of_iso (diagram_iso_pair F)

/-- The braiding isomorphism which swaps a binary product. -/
@[simp] theorem prod.braiding_hom {C : Type u} [category C] (P : C) (Q : C) [has_binary_product P Q]
    [has_binary_product Q P] : iso.hom (prod.braiding P Q) = prod.lift prod.snd prod.fst :=
  Eq.refl (iso.hom (prod.braiding P Q))

/-- The braiding isomorphism can be passed through a map by swapping the order. -/
theorem braid_natural {C : Type u} [category C] [has_binary_products C] {W : C} {X : C} {Y : C}
    {Z : C} (f : X ⟶ Y) (g : Z ⟶ W) :
    prod.map f g ≫ iso.hom (prod.braiding Y W) = iso.hom (prod.braiding X Z) ≫ prod.map g f :=
  sorry

theorem prod.symmetry'_assoc {C : Type u} [category C] (P : C) (Q : C) [has_binary_product P Q]
    [has_binary_product Q P] {X' : C} (f' : P ⨯ Q ⟶ X') :
    prod.lift prod.snd prod.fst ≫ prod.lift prod.snd prod.fst ≫ f' = f' :=
  sorry

/-- The braiding isomorphism is symmetric. -/
theorem prod.symmetry_assoc {C : Type u} [category C] (P : C) (Q : C) [has_binary_product P Q]
    [has_binary_product Q P] {X' : C} (f' : P ⨯ Q ⟶ X') :
    iso.hom (prod.braiding P Q) ≫ iso.hom (prod.braiding Q P) ≫ f' = f' :=
  sorry

/-- The associator isomorphism for binary products. -/
@[simp] theorem prod.associator_hom {C : Type u} [category C] [has_binary_products C] (P : C)
    (Q : C) (R : C) :
    iso.hom (prod.associator P Q R) =
        prod.lift (prod.fst ≫ prod.fst) (prod.lift (prod.fst ≫ prod.snd) prod.snd) :=
  Eq.refl (iso.hom (prod.associator P Q R))

theorem prod.pentagon_assoc {C : Type u} [category C] [has_binary_products C] (W : C) (X : C)
    (Y : C) (Z : C) {X' : C} (f' : W ⨯ (X ⨯ (Y ⨯ Z)) ⟶ X') :
    prod.map (iso.hom (prod.associator W X Y)) 𝟙 ≫
          iso.hom (prod.associator W (X ⨯ Y) Z) ≫
            prod.map 𝟙 (iso.hom (prod.associator X Y Z)) ≫ f' =
        iso.hom (prod.associator (W ⨯ X) Y Z) ≫ iso.hom (prod.associator W X (Y ⨯ Z)) ≫ f' :=
  sorry

theorem prod.associator_naturality_assoc {C : Type u} [category C] [has_binary_products C] {X₁ : C}
    {X₂ : C} {X₃ : C} {Y₁ : C} {Y₂ : C} {Y₃ : C} (f₁ : X₁ ⟶ Y₁) (f₂ : X₂ ⟶ Y₂) (f₃ : X₃ ⟶ Y₃)
    {X' : C} (f' : Y₁ ⨯ (Y₂ ⨯ Y₃) ⟶ X') :
    prod.map (prod.map f₁ f₂) f₃ ≫ iso.hom (prod.associator Y₁ Y₂ Y₃) ≫ f' =
        iso.hom (prod.associator X₁ X₂ X₃) ≫ prod.map f₁ (prod.map f₂ f₃) ≫ f' :=
  sorry

/-- The left unitor isomorphism for binary products with the terminal object. -/
def prod.left_unitor {C : Type u} [category C] [has_terminal C] (P : C)
    [has_binary_product (⊤_C) P] : (⊤_C) ⨯ P ≅ P :=
  iso.mk prod.snd (prod.lift (terminal.from P) 𝟙)

/-- The right unitor isomorphism for binary products with the terminal object. -/
def prod.right_unitor {C : Type u} [category C] [has_terminal C] (P : C)
    [has_binary_product P (⊤_C)] : P ⨯ (⊤_C) ≅ P :=
  iso.mk prod.fst (prod.lift 𝟙 (terminal.from P))

theorem prod.left_unitor_hom_naturality_assoc {C : Type u} [category C] {X : C} {Y : C}
    [has_terminal C] [has_binary_products C] (f : X ⟶ Y) {X' : C} (f' : Y ⟶ X') :
    prod.map 𝟙 f ≫ iso.hom (prod.left_unitor Y) ≫ f' = iso.hom (prod.left_unitor X) ≫ f ≫ f' :=
  sorry

theorem prod.left_unitor_inv_naturality {C : Type u} [category C] {X : C} {Y : C} [has_terminal C]
    [has_binary_products C] (f : X ⟶ Y) :
    iso.inv (prod.left_unitor X) ≫ prod.map 𝟙 f = f ≫ iso.inv (prod.left_unitor Y) :=
  sorry

theorem prod.right_unitor_hom_naturality_assoc {C : Type u} [category C] {X : C} {Y : C}
    [has_terminal C] [has_binary_products C] (f : X ⟶ Y) {X' : C} (f' : Y ⟶ X') :
    prod.map f 𝟙 ≫ iso.hom (prod.right_unitor Y) ≫ f' = iso.hom (prod.right_unitor X) ≫ f ≫ f' :=
  sorry

theorem prod_right_unitor_inv_naturality {C : Type u} [category C] {X : C} {Y : C} [has_terminal C]
    [has_binary_products C] (f : X ⟶ Y) :
    iso.inv (prod.right_unitor X) ≫ prod.map f 𝟙 = f ≫ iso.inv (prod.right_unitor Y) :=
  sorry

theorem prod.triangle {C : Type u} [category C] [has_terminal C] [has_binary_products C] (X : C)
    (Y : C) :
    iso.hom (prod.associator X (⊤_C) Y) ≫ prod.map 𝟙 (iso.hom (prod.left_unitor Y)) =
        prod.map (iso.hom (prod.right_unitor X)) 𝟙 :=
  sorry

/-- The braiding isomorphism which swaps a binary coproduct. -/
@[simp] theorem coprod.braiding_hom {C : Type u} [category C] [has_binary_coproducts C] (P : C)
    (Q : C) : iso.hom (coprod.braiding P Q) = coprod.desc coprod.inr coprod.inl :=
  Eq.refl (iso.hom (coprod.braiding P Q))

theorem coprod.symmetry'_assoc {C : Type u} [category C] [has_binary_coproducts C] (P : C) (Q : C)
    {X' : C} (f' : P ⨿ Q ⟶ X') :
    coprod.desc coprod.inr coprod.inl ≫ coprod.desc coprod.inr coprod.inl ≫ f' = f' :=
  sorry

/-- The braiding isomorphism is symmetric. -/
theorem coprod.symmetry {C : Type u} [category C] [has_binary_coproducts C] (P : C) (Q : C) :
    iso.hom (coprod.braiding P Q) ≫ iso.hom (coprod.braiding Q P) = 𝟙 :=
  coprod.symmetry' P Q

/-- The associator isomorphism for binary coproducts. -/
@[simp] theorem coprod.associator_inv {C : Type u} [category C] [has_binary_coproducts C] (P : C)
    (Q : C) (R : C) :
    iso.inv (coprod.associator P Q R) =
        coprod.desc (coprod.inl ≫ coprod.inl) (coprod.desc (coprod.inr ≫ coprod.inl) coprod.inr) :=
  Eq.refl (iso.inv (coprod.associator P Q R))

theorem coprod.pentagon {C : Type u} [category C] [has_binary_coproducts C] (W : C) (X : C) (Y : C)
    (Z : C) :
    coprod.map (iso.hom (coprod.associator W X Y)) 𝟙 ≫
          iso.hom (coprod.associator W (X ⨿ Y) Z) ≫
            coprod.map 𝟙 (iso.hom (coprod.associator X Y Z)) =
        iso.hom (coprod.associator (W ⨿ X) Y Z) ≫ iso.hom (coprod.associator W X (Y ⨿ Z)) :=
  sorry

theorem coprod.associator_naturality {C : Type u} [category C] [has_binary_coproducts C] {X₁ : C}
    {X₂ : C} {X₃ : C} {Y₁ : C} {Y₂ : C} {Y₃ : C} (f₁ : X₁ ⟶ Y₁) (f₂ : X₂ ⟶ Y₂) (f₃ : X₃ ⟶ Y₃) :
    coprod.map (coprod.map f₁ f₂) f₃ ≫ iso.hom (coprod.associator Y₁ Y₂ Y₃) =
        iso.hom (coprod.associator X₁ X₂ X₃) ≫ coprod.map f₁ (coprod.map f₂ f₃) :=
  sorry

/-- The left unitor isomorphism for binary coproducts with the initial object. -/
@[simp] theorem coprod.left_unitor_inv {C : Type u} [category C] [has_binary_coproducts C]
    [has_initial C] (P : C) : iso.inv (coprod.left_unitor P) = coprod.inr :=
  Eq.refl (iso.inv (coprod.left_unitor P))

/-- The right unitor isomorphism for binary coproducts with the initial object. -/
@[simp] theorem coprod.right_unitor_hom {C : Type u} [category C] [has_binary_coproducts C]
    [has_initial C] (P : C) : iso.hom (coprod.right_unitor P) = coprod.desc 𝟙 (initial.to P) :=
  Eq.refl (iso.hom (coprod.right_unitor P))

theorem coprod.triangle {C : Type u} [category C] [has_binary_coproducts C] [has_initial C] (X : C)
    (Y : C) :
    iso.hom (coprod.associator X (⊥_C) Y) ≫ coprod.map 𝟙 (iso.hom (coprod.left_unitor Y)) =
        coprod.map (iso.hom (coprod.right_unitor X)) 𝟙 :=
  sorry

/-- The binary product functor. -/
@[simp] theorem prod.functor_obj_map {C : Type u} [category C] [has_binary_products C] (X : C)
    (Y : C) (Z : C) (g : Y ⟶ Z) : functor.map (functor.obj prod.functor X) g = prod.map 𝟙 g :=
  Eq.refl (functor.map (functor.obj prod.functor X) g)

/-- The product functor can be decomposed. -/
def prod.functor_left_comp {C : Type u} [category C] [has_binary_products C] (X : C) (Y : C) :
    functor.obj prod.functor (X ⨯ Y) ≅ functor.obj prod.functor Y ⋙ functor.obj prod.functor X :=
  nat_iso.of_components (prod.associator X Y) sorry

/-- The binary coproduct functor. -/
@[simp] theorem coprod.functor_obj_map {C : Type u} [category C] [has_binary_coproducts C] (X : C)
    (Y : C) (Z : C) (g : Y ⟶ Z) : functor.map (functor.obj coprod.functor X) g = coprod.map 𝟙 g :=
  Eq.refl (functor.map (functor.obj coprod.functor X) g)

/-- The coproduct functor can be decomposed. -/
def coprod.functor_left_comp {C : Type u} [category C] [has_binary_coproducts C] (X : C) (Y : C) :
    functor.obj coprod.functor (X ⨿ Y) ≅
        functor.obj coprod.functor Y ⋙ functor.obj coprod.functor X :=
  nat_iso.of_components (coprod.associator X Y) sorry

/--
The product comparison morphism.

In `category_theory/limits/preserves` we show this is always an iso iff F preserves binary products.
-/
def prod_comparison {C : Type u} [category C] {D : Type u₂} [category D] (F : C ⥤ D) (A : C) (B : C)
    [has_binary_product A B] [has_binary_product (functor.obj F A) (functor.obj F B)] :
    functor.obj F (A ⨯ B) ⟶ functor.obj F A ⨯ functor.obj F B :=
  prod.lift (functor.map F prod.fst) (functor.map F prod.snd)

@[simp] theorem prod_comparison_fst_assoc {C : Type u} [category C] {D : Type u₂} [category D]
    (F : C ⥤ D) {A : C} {B : C} [has_binary_product A B]
    [has_binary_product (functor.obj F A) (functor.obj F B)] {X' : D} (f' : functor.obj F A ⟶ X') :
    prod_comparison F A B ≫ prod.fst ≫ f' = functor.map F prod.fst ≫ f' :=
  sorry

@[simp] theorem prod_comparison_snd_assoc {C : Type u} [category C] {D : Type u₂} [category D]
    (F : C ⥤ D) {A : C} {B : C} [has_binary_product A B]
    [has_binary_product (functor.obj F A) (functor.obj F B)] {X' : D} (f' : functor.obj F B ⟶ X') :
    prod_comparison F A B ≫ prod.snd ≫ f' = functor.map F prod.snd ≫ f' :=
  sorry

/-- Naturality of the prod_comparison morphism in both arguments. -/
theorem prod_comparison_natural_assoc {C : Type u} [category C] {D : Type u₂} [category D]
    (F : C ⥤ D) {A : C} {A' : C} {B : C} {B' : C} [has_binary_product A B]
    [has_binary_product A' B'] [has_binary_product (functor.obj F A) (functor.obj F B)]
    [has_binary_product (functor.obj F A') (functor.obj F B')] (f : A ⟶ A') (g : B ⟶ B') {X' : D}
    (f' : functor.obj F A' ⨯ functor.obj F B' ⟶ X') :
    functor.map F (prod.map f g) ≫ prod_comparison F A' B' ≫ f' =
        prod_comparison F A B ≫ prod.map (functor.map F f) (functor.map F g) ≫ f' :=
  sorry

/--
The product comparison morphism from `F(A ⨯ -)` to `FA ⨯ F-`, whose components are given by
`prod_comparison`.
-/
def prod_comparison_nat_trans {C : Type u} [category C] {D : Type u₂} [category D]
    [has_binary_products C] [has_binary_products D] (F : C ⥤ D) (A : C) :
    functor.obj prod.functor A ⋙ F ⟶ F ⋙ functor.obj prod.functor (functor.obj F A) :=
  nat_trans.mk fun (B : C) => prod_comparison F A B

theorem inv_prod_comparison_map_fst_assoc {C : Type u} [category C] {D : Type u₂} [category D]
    (F : C ⥤ D) {A : C} {B : C} [has_binary_product A B]
    [has_binary_product (functor.obj F A) (functor.obj F B)] [is_iso (prod_comparison F A B)]
    {X' : D} (f' : functor.obj F A ⟶ X') :
    inv (prod_comparison F A B) ≫ functor.map F prod.fst ≫ f' = prod.fst ≫ f' :=
  sorry

theorem inv_prod_comparison_map_snd_assoc {C : Type u} [category C] {D : Type u₂} [category D]
    (F : C ⥤ D) {A : C} {B : C} [has_binary_product A B]
    [has_binary_product (functor.obj F A) (functor.obj F B)] [is_iso (prod_comparison F A B)]
    {X' : D} (f' : functor.obj F B ⟶ X') :
    inv (prod_comparison F A B) ≫ functor.map F prod.snd ≫ f' = prod.snd ≫ f' :=
  sorry

/-- If the product comparison morphism is an iso, its inverse is natural. -/
theorem prod_comparison_inv_natural {C : Type u} [category C] {D : Type u₂} [category D] (F : C ⥤ D)
    {A : C} {A' : C} {B : C} {B' : C} [has_binary_product A B] [has_binary_product A' B']
    [has_binary_product (functor.obj F A) (functor.obj F B)]
    [has_binary_product (functor.obj F A') (functor.obj F B')] (f : A ⟶ A') (g : B ⟶ B')
    [is_iso (prod_comparison F A B)] [is_iso (prod_comparison F A' B')] :
    inv (prod_comparison F A B) ≫ functor.map F (prod.map f g) =
        prod.map (functor.map F f) (functor.map F g) ≫ inv (prod_comparison F A' B') :=
  sorry

/--
The natural isomorphism `F(A ⨯ -) ≅ FA ⨯ F-`, provided each `prod_comparison F A B` is an
isomorphism (as `B` changes).
-/
def prod_comparison_nat_iso {C : Type u} [category C] {D : Type u₂} [category D] (F : C ⥤ D)
    [has_binary_products C] [has_binary_products D] (A : C)
    [(B : C) → is_iso (prod_comparison F A B)] :
    functor.obj prod.functor A ⋙ F ≅ F ⋙ functor.obj prod.functor (functor.obj F A) :=
  iso.mk (prod_comparison_nat_trans F A) (inv (nat_trans.mk fun (B : C) => prod_comparison F A B))

end Mathlib


universes u

def len {α : Type u} : List α → List α → Nat
| [],    bs => bs.length
| a::as, bs => len as bs + 1

theorem ex1 : len [1, 2] [3, 4] = 4 :=
rfl


import classes.context_free.closure_properties.bijection

/-- The class of context-free languages is closed under permutation of terminals. -/
theorem CF_of_permute_CF {T : Type} (π : equiv.perm T) (L : language T) :
  is_CF L  →  is_CF (permute_lang L π)  :=
CF_of_bijemap_CF π L


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Reid Barton, Patrick Massot, Scott Morrison
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.monad.limits
import Mathlib.topology.uniform_space.completion
import Mathlib.topology.category.Top.basic
import Mathlib.PostPort

universes u u_1 l

namespace Mathlib

/-!
# The category of uniform spaces

We construct the category of uniform spaces, show that the complete separated uniform spaces
form a reflective subcategory, and hence possess all limits that uniform spaces do.

TODO: show that uniform spaces actually have all limits!
-/

/-- A (bundled) uniform space. -/
def UniformSpace :=
  category_theory.bundled uniform_space

namespace UniformSpace

/-- The information required to build morphisms for `UniformSpace`. -/
protected instance uniform_continuous.category_theory.unbundled_hom : category_theory.unbundled_hom uniform_continuous :=
  category_theory.unbundled_hom.mk uniform_continuous_id uniform_continuous.comp

protected instance has_coe_to_sort : has_coe_to_sort UniformSpace :=
  category_theory.bundled.has_coe_to_sort

protected instance uniform_space (x : UniformSpace) : uniform_space ↥x :=
  category_theory.bundled.str x

/-- Construct a bundled `UniformSpace` from the underlying type and the typeclass. -/
def of (α : Type u) [uniform_space α] : UniformSpace :=
  category_theory.bundled.mk α

protected instance inhabited : Inhabited UniformSpace :=
  { default := of empty }

@[simp] theorem coe_of (X : Type u) [uniform_space X] : ↥(of X) = X :=
  rfl

protected instance category_theory.has_hom.hom.has_coe_to_fun (X : UniformSpace) (Y : UniformSpace) : has_coe_to_fun (X ⟶ Y) :=
  has_coe_to_fun.mk (fun (_x : X ⟶ Y) => ↥X → ↥Y) (category_theory.functor.map (category_theory.forget UniformSpace))

@[simp] theorem coe_comp {X : UniformSpace} {Y : UniformSpace} {Z : UniformSpace} (f : X ⟶ Y) (g : Y ⟶ Z) : ⇑(f ≫ g) = ⇑g ∘ ⇑f :=
  rfl

@[simp] theorem coe_id (X : UniformSpace) : ⇑𝟙 = id :=
  rfl

@[simp] theorem coe_mk {X : UniformSpace} {Y : UniformSpace} (f : ↥X → ↥Y) (hf : uniform_continuous f) : ⇑{ val := f, property := hf } = f :=
  rfl

theorem hom_ext {X : UniformSpace} {Y : UniformSpace} {f : X ⟶ Y} {g : X ⟶ Y} : ⇑f = ⇑g → f = g :=
  subtype.eq

/-- The forgetful functor from uniform spaces to topological spaces. -/
protected instance has_forget_to_Top : category_theory.has_forget₂ UniformSpace Top :=
  category_theory.has_forget₂.mk
    (category_theory.functor.mk (fun (X : UniformSpace) => Top.of ↥X)
      fun (X Y : UniformSpace) (f : X ⟶ Y) => continuous_map.mk ⇑f)

end UniformSpace

/-- A (bundled) complete separated uniform space. -/
structure CpltSepUniformSpace
where
  α : Type u
  is_uniform_space : uniform_space α
  is_complete_space : complete_space α
  is_separated : separated_space α

namespace CpltSepUniformSpace

protected instance has_coe_to_sort : has_coe_to_sort CpltSepUniformSpace :=
  has_coe_to_sort.mk (Type u) α

def to_UniformSpace (X : CpltSepUniformSpace) : UniformSpace :=
  UniformSpace.of ↥X

protected instance complete_space (X : CpltSepUniformSpace) : complete_space (category_theory.bundled.α (to_UniformSpace X)) :=
  is_complete_space X

protected instance separated_space (X : CpltSepUniformSpace) : separated_space (category_theory.bundled.α (to_UniformSpace X)) :=
  is_separated X

/-- Construct a bundled `UniformSpace` from the underlying type and the appropriate typeclasses. -/
def of (X : Type u) [uniform_space X] [complete_space X] [separated_space X] : CpltSepUniformSpace :=
  mk X

@[simp] theorem coe_of (X : Type u) [uniform_space X] [complete_space X] [separated_space X] : ↥(of X) = X :=
  rfl

protected instance inhabited : Inhabited CpltSepUniformSpace :=
  { default := of empty }

/-- The category instance on `CpltSepUniformSpace`. -/
protected instance category : category_theory.large_category CpltSepUniformSpace :=
  category_theory.induced_category.category to_UniformSpace

/-- The concrete category instance on `CpltSepUniformSpace`. -/
protected instance concrete_category : category_theory.concrete_category CpltSepUniformSpace :=
  category_theory.induced_category.concrete_category to_UniformSpace

protected instance has_forget_to_UniformSpace : category_theory.has_forget₂ CpltSepUniformSpace UniformSpace :=
  category_theory.induced_category.has_forget₂ to_UniformSpace

end CpltSepUniformSpace

namespace UniformSpace

/-- The functor turning uniform spaces into complete separated uniform spaces. -/
def completion_functor : UniformSpace ⥤ CpltSepUniformSpace :=
  category_theory.functor.mk (fun (X : UniformSpace) => CpltSepUniformSpace.of (uniform_space.completion ↥X))
    fun (X Y : UniformSpace) (f : X ⟶ Y) => { val := uniform_space.completion.map (subtype.val f), property := sorry }

/-- The inclusion of a uniform space into its completion. -/
def completion_hom (X : UniformSpace) : X ⟶
  category_theory.functor.obj (category_theory.forget₂ CpltSepUniformSpace UniformSpace)
    (category_theory.functor.obj completion_functor X) :=
  { val := coe, property := sorry }

@[simp] theorem completion_hom_val (X : UniformSpace) (x : ↥X) : coe_fn (completion_hom X) x = ↑x :=
  rfl

/-- The mate of a morphism from a `UniformSpace` to a `CpltSepUniformSpace`. -/
def extension_hom {X : UniformSpace} {Y : CpltSepUniformSpace} (f : X ⟶ category_theory.functor.obj (category_theory.forget₂ CpltSepUniformSpace UniformSpace) Y) : category_theory.functor.obj completion_functor X ⟶ Y :=
  { val := uniform_space.completion.extension ⇑f, property := sorry }

@[simp] theorem extension_hom_val {X : UniformSpace} {Y : CpltSepUniformSpace} (f : X ⟶ category_theory.functor.obj (category_theory.forget₂ CpltSepUniformSpace UniformSpace) Y) (x : ↥(CpltSepUniformSpace.to_UniformSpace (category_theory.functor.obj completion_functor X))) : coe_fn (extension_hom f) x = uniform_space.completion.extension (⇑f) x :=
  rfl

@[simp] theorem extension_comp_coe {X : UniformSpace} {Y : CpltSepUniformSpace} (f : CpltSepUniformSpace.to_UniformSpace (CpltSepUniformSpace.of (uniform_space.completion ↥X)) ⟶
  CpltSepUniformSpace.to_UniformSpace Y) : extension_hom (completion_hom X ≫ f) = f := sorry

/-- The completion functor is left adjoint to the forgetful functor. -/
def adj : completion_functor ⊣ category_theory.forget₂ CpltSepUniformSpace UniformSpace :=
  category_theory.adjunction.mk_of_hom_equiv
    (category_theory.adjunction.core_hom_equiv.mk
      fun (X : UniformSpace) (Y : CpltSepUniformSpace) =>
        equiv.mk (fun (f : category_theory.functor.obj completion_functor X ⟶ Y) => completion_hom X ≫ f)
          (fun (f : X ⟶ category_theory.functor.obj (category_theory.forget₂ CpltSepUniformSpace UniformSpace) Y) =>
            extension_hom f)
          sorry sorry)

protected instance category_theory.forget₂.category_theory.is_right_adjoint : category_theory.is_right_adjoint (category_theory.forget₂ CpltSepUniformSpace UniformSpace) :=
  category_theory.is_right_adjoint.mk completion_functor adj

protected instance category_theory.forget₂.category_theory.reflective : category_theory.reflective (category_theory.forget₂ CpltSepUniformSpace UniformSpace) :=
  category_theory.reflective.mk


--

def f1 (x : Nat) (b : Bool) : type_of% x :=
let r : type_of% (x+1) := x+1;
r + 1

theorem ex1 : f1 1 true = 3 :=
rfl

def f2 (x : Nat) (b : Bool) : type_of% x :=
let r : type_of% b := x+1; -- error
r + 1

def f3 (x : Nat) (b : Bool) : type_of% b :=
let r (x!1 : type_of% x) : type_of% b := x > 1;
r x

def f4 (x : Nat) : Nat :=
let y : Nat := x
let y := ensure_type_of% y "invalid reassignment, term" y == 1 -- error
y + 1

def f5 (x : Nat) : Nat :=
let y : Nat := x
let y := ensure_type_of% y "invalid reassignment, term" (y+1)
y + 1

def f6 (x : Nat) : Nat :=
ensure_expected_type% "natural number expected, value" true


/-
Copyright (c) 2018 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Callum Sutton, Yury Kudryashov
Ported by: Winston Yin

! This file was ported from Lean 3 source module algebra.hom.equiv.basic
! leanprover-community/mathlib commit 67f362670ed961bcb80239dc40ca18bcd4289c77
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Hom.Group
import Mathlib.Data.FunLike.Equiv
import Mathlib.Logic.Equiv.Basic
import Mathlib.Data.Pi.Algebra

/-!
# Multiplicative and additive equivs

In this file we define two extensions of `Equiv` called `AddEquiv` and `MulEquiv`, which are
datatypes representing isomorphisms of `AddMonoid`s/`AddGroup`s and `Monoid`s/`Group`s.

## Notations

* ``infix ` ≃* `:25 := MulEquiv``
* ``infix ` ≃+ `:25 := AddEquiv``

The extended equivs all have coercions to functions, and the coercions are the canonical
notation when treating the isomorphisms as maps.

## Tags

Equiv, MulEquiv, AddEquiv
-/

variable {F α β A B M N P Q G H : Type _}

/-- Makes a multiplicative inverse from a bijection which preserves multiplication. -/
@[to_additive "Makes an additive inverse from a bijection which preserves addition."]
def MulHom.inverse [Mul M] [Mul N] (f : M →ₙ* N) (g : N → M) (h₁ : Function.LeftInverse g f)
  (h₂ : Function.RightInverse g f) : N →ₙ* M where
  toFun := g
  map_mul' x y :=
    calc
      g (x * y) = g (f (g x) * f (g y)) := by rw [h₂ x, h₂ y]
      _ = g (f (g x * g y)) := by rw [f.map_mul]
      _ = g x * g y := h₁ _

#align mul_hom.inverse MulHom.inverse
#align add_hom.inverse AddHom.inverse

/-- The inverse of a bijective `MonoidHom` is a `MonoidHom`. -/
@[to_additive (attr := simps) "The inverse of a bijective `AddMonoidHom` is an `AddMonoidHom`."]
def MonoidHom.inverse {A B : Type _} [Monoid A] [Monoid B] (f : A →* B) (g : B → A)
  (h₁ : Function.LeftInverse g f) (h₂ : Function.RightInverse g f) : B →* A :=
  { (f : A →ₙ* B).inverse g h₁ h₂ with toFun := g, map_one' := by rw [← f.map_one, h₁] }
#align monoid_hom.inverse MonoidHom.inverse
#align add_monoid_hom.inverse AddMonoidHom.inverse
#align monoid_hom.inverse_apply MonoidHom.inverse_apply
#align add_monoid_hom.inverse_apply AddMonoidHom.inverse_apply

/-- `AddEquiv α β` is the type of an equiv `α ≃ β` which preserves addition. -/
structure AddEquiv (A B : Type _) [Add A] [Add B] extends A ≃ B, AddHom A B
#align add_equiv AddEquiv

/-- `AddEquivClass F A B` states that `F` is a type of addition-preserving morphisms.
You should extend this class when you extend `AddEquiv`. -/
class AddEquivClass (F : Type _) (A B : outParam (Type _)) [Add A] [Add B]
    extends EquivLike F A B where
  /-- Preserves addition. -/
  map_add : ∀ (f : F) (a b), f (a + b) = f a + f b
#align add_equiv_class AddEquivClass

/-- The `Equiv` underlying an `AddEquiv`. -/
add_decl_doc AddEquiv.toEquiv
#align add_equiv.to_equiv AddEquiv.toEquiv

/-- The `AddHom` underlying a `AddEquiv`. -/
add_decl_doc AddEquiv.toAddHom
#align add_equiv.to_add_hom AddEquiv.toAddHom

/-- `MulEquiv α β` is the type of an equiv `α ≃ β` which preserves multiplication. -/
@[to_additive]
structure MulEquiv (M N : Type _) [Mul M] [Mul N] extends M ≃ N, M →ₙ* N
-- Porting note: remove when `to_additive` can do this
-- https://github.com/leanprover-community/mathlib4/issues/660
attribute [to_additive existing] MulEquiv.toMulHom
#align mul_equiv MulEquiv

/-- The `Equiv` underlying a `MulEquiv`. -/
add_decl_doc MulEquiv.toEquiv
#align mul_equiv.to_equiv MulEquiv.toEquiv

/-- The `MulHom` underlying a `MulEquiv`. -/
add_decl_doc MulEquiv.toMulHom
#align mul_equiv.to_mul_hom MulEquiv.toMulHom

/-- `MulEquivClass F A B` states that `F` is a type of multiplication-preserving morphisms.
You should extend this class when you extend `MulEquiv`. -/
@[to_additive]
class MulEquivClass (F : Type _) (A B : outParam (Type _)) [Mul A] [Mul B] extends
  EquivLike F A B where
  /-- Preserves multiplication. -/
  map_mul : ∀ (f : F) (a b), f (a * b) = f a * f b
#align mul_equiv_class MulEquivClass

/-- Notation for a `MulEquiv`. -/
infixl:25 " ≃* " => MulEquiv

/-- Notation for an `AddEquiv`. -/
infixl:25 " ≃+ " => AddEquiv

namespace MulEquivClass

variable (F)

-- See note [lower instance priority]
@[to_additive]
instance (priority := 100) (F : Type _) {M N : Type _}
    {_ : Mul M} {_ : Mul N} [h : MulEquivClass F M N] : MulHomClass F M N :=
  { h with coe := h.coe, coe_injective' := FunLike.coe_injective' }

-- See note [lower instance priority]
@[to_additive]
instance (priority := 100) {_ : MulOneClass M} {_ : MulOneClass N} [MulEquivClass F M N] :
  MonoidHomClass F M N :=
  { MulEquivClass.instMulHomClass F with
    coe := fun _ => _,
    map_one := fun e =>
      calc
        e 1 = e 1 * 1 := (mul_one _).symm
        _ = e 1 * e (MulEquivClass.toEquivLike.inv e (1 : N) : M) :=
          congr_arg _ (MulEquivClass.toEquivLike.right_inv e 1).symm
        _ = e (MulEquivClass.toEquivLike.inv e (1 : N)) := by rw [← map_mul, one_mul]
        _ = 1 := MulEquivClass.toEquivLike.right_inv e 1
         }

-- See note [lower instance priority]
instance (priority := 100) toMonoidWithZeroHomClass
  {α β : Type _} {_ : MulZeroOneClass α} {_ : MulZeroOneClass β} [MulEquivClass F α β] :
  MonoidWithZeroHomClass F α β :=
  { MulEquivClass.instMonoidHomClass _ with
    map_zero := fun e =>
      calc
        e 0 = e 0 * e (EquivLike.inv e 0) := by rw [← map_mul, zero_mul]
        _ = 0 := by simp }
#align mul_equiv_class.to_monoid_with_zero_hom_class MulEquivClass.toMonoidWithZeroHomClass

variable {F}

@[to_additive (attr := simp)]
theorem map_eq_one_iff {M N} [MulOneClass M] [MulOneClass N] [MulEquivClass F M N] (h : F) {x : M} :
  h x = 1 ↔ x = 1 := _root_.map_eq_one_iff h (EquivLike.injective h)
#align mul_equiv_class.map_eq_one_iff MulEquivClass.map_eq_one_iff
#align add_equiv_class.map_eq_zero_iff AddEquivClass.map_eq_zero_iff

@[to_additive]
theorem map_ne_one_iff {M N} [MulOneClass M] [MulOneClass N] [MulEquivClass F M N] (h : F) {x : M} :
  h x ≠ 1 ↔ x ≠ 1 := _root_.map_ne_one_iff h (EquivLike.injective h)
#align mul_equiv_class.map_ne_one_iff MulEquivClass.map_ne_one_iff
#align add_equiv_class.map_ne_zero_iff AddEquivClass.map_ne_zero_iff

end MulEquivClass

/-- Turn an element of a type `F` satisfying `MulEquivClass F α β` into an actual
`MulEquiv`. This is declared as the default coercion from `F` to `α ≃* β`. -/
@[to_additive (attr := coe)
"Turn an element of a type `F` satisfying `AddEquivClass F α β` into an actual
`AddEquiv`. This is declared as the default coercion from `F` to `α ≃+ β`."]
def MulEquivClass.toMulEquiv [Mul α] [Mul β] [MulEquivClass F α β] (f : F) : α ≃* β :=
{ (f : α ≃ β), (f : α →ₙ* β) with }

/-- Any type satisfying `MulEquivClass` can be cast into `MulEquiv` via
`MulEquivClass.toMulEquiv`. -/
@[to_additive "Any type satisfying `AddEquivClass` can be cast into `AddEquiv` via
`AddEquivClass.toAddEquiv`. "]
instance [Mul α] [Mul β] [MulEquivClass F α β] : CoeTC F (α ≃* β) :=
  ⟨MulEquivClass.toMulEquiv⟩

namespace MulEquiv

@[to_additive]
instance [Mul M] [Mul N] : MulEquivClass (M ≃* N) M N where
  coe f := f.toFun
  inv f := f.invFun
  left_inv f := f.left_inv
  right_inv f := f.right_inv
  coe_injective' f g h₁ h₂ := by
    cases f
    cases g
    congr
    apply Equiv.coe_fn_injective h₁
  map_mul := map_mul'

variable [Mul M] [Mul N] [Mul P] [Mul Q]

@[to_additive (attr := simp)]
theorem toEquiv_eq_coe (f : M ≃* N) : f.toEquiv = f :=
  rfl
#align mul_equiv.to_equiv_eq_coe MulEquiv.toEquiv_eq_coe
#align add_equiv.to_equiv_eq_coe AddEquiv.toEquiv_eq_coe

-- Porting note: `to_fun_eq_coe` no longer needed in Lean4
#noalign mul_equiv.to_fun_eq_coe
#noalign add_equiv.to_fun_eq_coe

@[to_additive (attr := simp)]
theorem coe_toEquiv (f : M ≃* N) : ⇑(f : M ≃ N) = f := rfl
#align mul_equiv.coe_to_equiv MulEquiv.coe_toEquiv
#align add_equiv.coe_to_equiv AddEquiv.coe_toEquiv

-- porting note: todo: `MulHom.coe_mk` simplifies `↑f.toMulHom` to `f.toMulHom.toFun`,
-- not `f.toEquiv.toFun`; use higher priority as a workaround
@[to_additive (attr := simp 1100)]
theorem coe_toMulHom {f : M ≃* N} : (f.toMulHom : M → N) = f := rfl
#align mul_equiv.coe_to_mul_hom MulEquiv.coe_toMulHom
#align add_equiv.coe_to_add_hom AddEquiv.coe_toAddHom

/-- A multiplicative isomorphism preserves multiplication. -/
@[to_additive "An additive isomorphism preserves addition."]
protected theorem map_mul (f : M ≃* N) : ∀ x y, f (x * y) = f x * f y :=
  _root_.map_mul f
#align mul_equiv.map_mul MulEquiv.map_mul
#align add_equiv.map_add AddEquiv.map_add

/-- Makes a multiplicative isomorphism from a bijection which preserves multiplication. -/
@[to_additive "Makes an additive isomorphism from a bijection which preserves addition."]
def mk' (f : M ≃ N) (h : ∀ x y, f (x * y) = f x * f y) : M ≃* N := ⟨f, h⟩
#align mul_equiv.mk' MulEquiv.mk'
#align add_equiv.mk' AddEquiv.mk'

@[to_additive]
protected theorem bijective (e : M ≃* N) : Function.Bijective e :=
  EquivLike.bijective e
#align mul_equiv.bijective MulEquiv.bijective
#align add_equiv.bijective AddEquiv.bijective

@[to_additive]
protected theorem injective (e : M ≃* N) : Function.Injective e :=
  EquivLike.injective e
#align mul_equiv.injective MulEquiv.injective
#align add_equiv.injective AddEquiv.injective

@[to_additive]
protected theorem surjective (e : M ≃* N) : Function.Surjective e :=
  EquivLike.surjective e
#align mul_equiv.surjective MulEquiv.surjective
#align add_equiv.surjective AddEquiv.surjective

/-- The identity map is a multiplicative isomorphism. -/
@[to_additive (attr := refl) "The identity map is an additive isomorphism."]
def refl (M : Type _) [Mul M] : M ≃* M :=
  { Equiv.refl _ with map_mul' := fun _ _ => rfl }
#align mul_equiv.refl MulEquiv.refl
#align add_equiv.refl AddEquiv.refl

@[to_additive]
instance : Inhabited (M ≃* M) := ⟨refl M⟩

/-- The inverse of an isomorphism is an isomorphism. -/
@[to_additive (attr := symm) "The inverse of an isomorphism is an isomorphism."]
def symm {M N : Type _} [Mul M] [Mul N] (h : M ≃* N) : N ≃* M :=
  ⟨h.toEquiv.symm, (h.toMulHom.inverse h.toEquiv.symm h.left_inv h.right_inv).map_mul⟩
#align mul_equiv.symm MulEquiv.symm
#align add_equiv.symm AddEquiv.symm

@[to_additive] -- porting note: no longer a `simp`, see below
theorem invFun_eq_symm {f : M ≃* N} : f.invFun = f.symm := rfl
#align mul_equiv.inv_fun_eq_symm MulEquiv.invFun_eq_symm
-- Porting note: to_additive translated the name incorrectly in mathlib 3.
#align add_equiv.neg_fun_eq_symm AddEquiv.invFun_eq_symm

@[to_additive (attr := simp)]

@[to_additive (attr := simp)]
theorem equivLike_inv_eq_symm (f : M ≃* N) : EquivLike.inv f = f.symm := rfl

-- we don't hyperlink the note in the additive version, since that breaks syntax highlighting
-- in the whole file.

/-- See Note [custom simps projection] -/
@[to_additive "See Note [custom simps projection]"] -- this comment fixes the syntax highlighting "
def Simps.symm_apply (e : M ≃* N) : N → M :=
  e.symm
#align mul_equiv.simps.symm_apply MulEquiv.Simps.symm_apply
#align add_equiv.simps.symm_apply AddEquiv.Simps.symm_apply

initialize_simps_projections AddEquiv (toFun → apply, invFun → symm_apply)

initialize_simps_projections MulEquiv (toFun → apply, invFun → symm_apply)

@[to_additive (attr := simp)]
theorem toEquiv_symm (f : M ≃* N) : (f.symm : N ≃ M) = (f : M ≃ N).symm := rfl
#align mul_equiv.to_equiv_symm MulEquiv.toEquiv_symm
#align add_equiv.to_equiv_symm AddEquiv.toEquiv_symm

-- porting note: doesn't align with Mathlib 3 because `MulEquiv.mk` has a new signature
@[to_additive (attr := simp)]
theorem coe_mk (f : M ≃ N) (hf : ∀ x y, f (x * y) = f x * f y) : (mk f hf : M → N) = f := rfl
#align mul_equiv.coe_mk MulEquiv.coe_mkₓ
#align add_equiv.coe_mk AddEquiv.coe_mkₓ

-- Porting note: `toEquiv_mk` no longer needed in Lean4
#noalign mul_equiv.to_equiv_mk
#noalign add_equiv.to_equiv_mk

@[to_additive (attr := simp)]
theorem symm_symm (f : M ≃* N) : f.symm.symm = f := rfl
#align mul_equiv.symm_symm MulEquiv.symm_symm
#align add_equiv.symm_symm AddEquiv.symm_symm

@[to_additive]
theorem symm_bijective : Function.Bijective (symm : M ≃* N → N ≃* M) :=
  Equiv.bijective ⟨symm, symm, symm_symm, symm_symm⟩
#align mul_equiv.symm_bijective MulEquiv.symm_bijective
#align add_equiv.symm_bijective AddEquiv.symm_bijective

-- Porting note: this doesn't really align with mathlib3's `symm_mk`,
-- because the signature of `MulEquiv.mk` has changed.
@[to_additive (attr := simp)]
theorem symm_mk (f : M ≃ N) (h) :
  (MulEquiv.mk f h).symm = ⟨f.symm, (MulEquiv.mk f h).symm.map_mul'⟩ := rfl
#align mul_equiv.symm_mk MulEquiv.symm_mkₓ
#align add_equiv.symm_mk AddEquiv.symm_mkₓ

@[to_additive (attr := simp)]
theorem refl_symm : (refl M).symm = refl M := rfl
#align mul_equiv.refl_symm MulEquiv.refl_symm
#align add_equiv.refl_symm AddEquiv.refl_symm

/-- Transitivity of multiplication-preserving isomorphisms -/
@[to_additive (attr := trans) "Transitivity of addition-preserving isomorphisms"]
def trans (h1 : M ≃* N) (h2 : N ≃* P) : M ≃* P :=
  { h1.toEquiv.trans h2.toEquiv with
    map_mul' := fun x y => show h2 (h1 (x * y)) = h2 (h1 x) * h2 (h1 y) by
      rw [h1.map_mul, h2.map_mul] }
#align mul_equiv.trans MulEquiv.trans
#align add_equiv.trans AddEquiv.trans

/-- `e.symm` is a right inverse of `e`, written as `e (e.symm y) = y`. -/
@[to_additive (attr := simp) "`e.symm` is a right inverse of `e`, written as `e (e.symm y) = y`."]
theorem apply_symm_apply (e : M ≃* N) (y : N) : e (e.symm y) = y :=
  e.toEquiv.apply_symm_apply y
#align mul_equiv.apply_symm_apply MulEquiv.apply_symm_apply
#align add_equiv.apply_symm_apply AddEquiv.apply_symm_apply

/-- `e.symm` is a left inverse of `e`, written as `e.symm (e y) = y`. -/
@[to_additive (attr := simp) "`e.symm` is a left inverse of `e`, written as `e.symm (e y) = y`."]
theorem symm_apply_apply (e : M ≃* N) (x : M) : e.symm (e x) = x :=
  e.toEquiv.symm_apply_apply x
#align mul_equiv.symm_apply_apply MulEquiv.symm_apply_apply
#align add_equiv.symm_apply_apply AddEquiv.symm_apply_apply

@[to_additive (attr := simp)]
theorem symm_comp_self (e : M ≃* N) : e.symm ∘ e = id :=
  funext e.symm_apply_apply
#align mul_equiv.symm_comp_self MulEquiv.symm_comp_self
#align add_equiv.symm_comp_self AddEquiv.symm_comp_self

@[to_additive (attr := simp)]
theorem self_comp_symm (e : M ≃* N) : e ∘ e.symm = id :=
  funext e.apply_symm_apply
#align mul_equiv.self_comp_symm MulEquiv.self_comp_symm
#align add_equiv.self_comp_symm AddEquiv.self_comp_symm

@[to_additive (attr := simp)]
theorem coe_refl : ↑(refl M) = id := rfl
#align mul_equiv.coe_refl MulEquiv.coe_refl
#align add_equiv.coe_refl AddEquiv.coe_refl

@[to_additive (attr := simp)]
theorem refl_apply (m : M) : refl M m = m := rfl
#align mul_equiv.refl_apply MulEquiv.refl_apply
#align add_equiv.refl_apply AddEquiv.refl_apply

@[to_additive (attr := simp)]
theorem coe_trans (e₁ : M ≃* N) (e₂ : N ≃* P) : ↑(e₁.trans e₂) = e₂ ∘ e₁ := rfl
#align mul_equiv.coe_trans MulEquiv.coe_trans
#align add_equiv.coe_trans AddEquiv.coe_trans

@[to_additive (attr := simp)]
theorem trans_apply (e₁ : M ≃* N) (e₂ : N ≃* P) (m : M) : e₁.trans e₂ m = e₂ (e₁ m) := rfl
#align mul_equiv.trans_apply MulEquiv.trans_apply
#align add_equiv.trans_apply AddEquiv.trans_apply

@[to_additive (attr := simp)]
theorem symm_trans_apply (e₁ : M ≃* N) (e₂ : N ≃* P) (p : P) :
  (e₁.trans e₂).symm p = e₁.symm (e₂.symm p) := rfl
#align mul_equiv.symm_trans_apply MulEquiv.symm_trans_apply
#align add_equiv.symm_trans_apply AddEquiv.symm_trans_apply

-- Porting note: `simp` can prove this
@[to_additive]
theorem apply_eq_iff_eq (e : M ≃* N) {x y : M} : e x = e y ↔ x = y :=
  e.injective.eq_iff
#align mul_equiv.apply_eq_iff_eq MulEquiv.apply_eq_iff_eq
#align add_equiv.apply_eq_iff_eq AddEquiv.apply_eq_iff_eq

@[to_additive]
theorem apply_eq_iff_symm_apply (e : M ≃* N) {x : M} {y : N} : e x = y ↔ x = e.symm y :=
  e.toEquiv.apply_eq_iff_eq_symm_apply
#align mul_equiv.apply_eq_iff_symm_apply MulEquiv.apply_eq_iff_symm_apply
#align add_equiv.apply_eq_iff_symm_apply AddEquiv.apply_eq_iff_symm_apply

@[to_additive]
theorem symm_apply_eq (e : M ≃* N) {x y} : e.symm x = y ↔ x = e y :=
  e.toEquiv.symm_apply_eq
#align mul_equiv.symm_apply_eq MulEquiv.symm_apply_eq
#align add_equiv.symm_apply_eq AddEquiv.symm_apply_eq

@[to_additive]
theorem eq_symm_apply (e : M ≃* N) {x y} : y = e.symm x ↔ e y = x :=
  e.toEquiv.eq_symm_apply
#align mul_equiv.eq_symm_apply MulEquiv.eq_symm_apply
#align add_equiv.eq_symm_apply AddEquiv.eq_symm_apply

@[to_additive]
theorem eq_comp_symm {α : Type _} (e : M ≃* N) (f : N → α) (g : M → α) :
  f = g ∘ e.symm ↔ f ∘ e = g :=
  e.toEquiv.eq_comp_symm f g
#align mul_equiv.eq_comp_symm MulEquiv.eq_comp_symm
#align add_equiv.eq_comp_symm AddEquiv.eq_comp_symm

@[to_additive]
theorem comp_symm_eq {α : Type _} (e : M ≃* N) (f : N → α) (g : M → α) :
  g ∘ e.symm = f ↔ g = f ∘ e :=
  e.toEquiv.comp_symm_eq f g
#align mul_equiv.comp_symm_eq MulEquiv.comp_symm_eq
#align add_equiv.comp_symm_eq AddEquiv.comp_symm_eq

@[to_additive]
theorem eq_symm_comp {α : Type _} (e : M ≃* N) (f : α → M) (g : α → N) :
  f = e.symm ∘ g ↔ e ∘ f = g :=
  e.toEquiv.eq_symm_comp f g
#align mul_equiv.eq_symm_comp MulEquiv.eq_symm_comp
#align add_equiv.eq_symm_comp AddEquiv.eq_symm_comp

@[to_additive]
theorem symm_comp_eq {α : Type _} (e : M ≃* N) (f : α → M) (g : α → N) :
  e.symm ∘ g = f ↔ g = e ∘ f :=
  e.toEquiv.symm_comp_eq f g
#align mul_equiv.symm_comp_eq MulEquiv.symm_comp_eq
#align add_equiv.symm_comp_eq AddEquiv.symm_comp_eq

@[to_additive (attr := simp)]
theorem symm_trans_self (e : M ≃* N) : e.symm.trans e = refl N :=
  FunLike.ext _ _ e.apply_symm_apply
#align mul_equiv.symm_trans_self MulEquiv.symm_trans_self
#align add_equiv.symm_trans_self AddEquiv.symm_trans_self

@[to_additive (attr := simp)]
theorem self_trans_symm (e : M ≃* N) : e.trans e.symm = refl M :=
  FunLike.ext _ _ e.symm_apply_apply
#align mul_equiv.self_trans_symm MulEquiv.self_trans_symm
#align add_equiv.self_trans_symm AddEquiv.self_trans_symm

-- Porting note: `simp` can prove this
@[to_additive]
theorem coe_monoidHom_refl {M} [MulOneClass M] : (refl M : M →* M) = MonoidHom.id M := rfl
#align mul_equiv.coe_monoid_hom_refl MulEquiv.coe_monoidHom_refl
#align add_equiv.coe_add_monoid_hom_refl AddEquiv.coe_addMonoidHom_refl

-- Porting note: `simp` can prove this
@[to_additive]
theorem coe_monoidHom_trans {M N P} [MulOneClass M] [MulOneClass N] [MulOneClass P]
  (e₁ : M ≃* N) (e₂ : N ≃* P) :
  (e₁.trans e₂ : M →* P) = (e₂ : N →* P).comp ↑e₁ := rfl
#align mul_equiv.coe_monoid_hom_trans MulEquiv.coe_monoidHom_trans
#align add_equiv.coe_add_monoid_hom_trans AddEquiv.coe_addMonoidHom_trans

/-- Two multiplicative isomorphisms agree if they are defined by the
same underlying function. -/
@[to_additive (attr := ext)
  "Two additive isomorphisms agree if they are defined by the same underlying function."]
theorem ext {f g : MulEquiv M N} (h : ∀ x, f x = g x) : f = g :=
  FunLike.ext f g h
#align mul_equiv.ext MulEquiv.ext
#align add_equiv.ext AddEquiv.ext

@[to_additive]
theorem ext_iff {f g : MulEquiv M N} : f = g ↔ ∀ x, f x = g x :=
  FunLike.ext_iff
#align mul_equiv.ext_iff MulEquiv.ext_iff
#align add_equiv.ext_iff AddEquiv.ext_iff

@[to_additive (attr := simp)]
theorem mk_coe (e : M ≃* N) (e' h₁ h₂ h₃) : (⟨⟨e, e', h₁, h₂⟩, h₃⟩ : M ≃* N) = e :=
  ext fun _ => rfl
#align mul_equiv.mk_coe MulEquiv.mk_coe
#align add_equiv.mk_coe AddEquiv.mk_coe

@[to_additive (attr := simp)]
theorem mk_coe' (e : M ≃* N) (f h₁ h₂ h₃) : (MulEquiv.mk ⟨f, e, h₁, h₂⟩ h₃ : N ≃* M) = e.symm :=
  symm_bijective.injective <| ext fun _ => rfl
#align mul_equiv.mk_coe' MulEquiv.mk_coe'
#align add_equiv.mk_coe' AddEquiv.mk_coe'

@[to_additive]
protected theorem congr_arg {f : MulEquiv M N} {x x' : M} : x = x' → f x = f x' :=
  FunLike.congr_arg f
#align mul_equiv.congr_arg MulEquiv.congr_arg
#align add_equiv.congr_arg AddEquiv.congr_arg

@[to_additive]
protected theorem congr_fun {f g : MulEquiv M N} (h : f = g) (x : M) : f x = g x :=
  FunLike.congr_fun h x
#align mul_equiv.congr_fun MulEquiv.congr_fun
#align add_equiv.congr_fun AddEquiv.congr_fun

/-- The `MulEquiv` between two monoids with a unique element. -/
@[to_additive "The `AddEquiv` between two `AddMonoid`s with a unique element."]
def mulEquivOfUnique {M N} [Unique M] [Unique N] [Mul M] [Mul N] : M ≃* N :=
  { Equiv.equivOfUnique M N with map_mul' := fun _ _ => Subsingleton.elim _ _ }
#align mul_equiv.mul_equiv_of_unique MulEquiv.mulEquivOfUnique
#align add_equiv.add_equiv_of_unique AddEquiv.addEquivOfUnique

/-- There is a unique monoid homomorphism between two monoids with a unique element. -/
@[to_additive "There is a unique additive monoid homomorphism between two additive monoids with
  a unique element."]
instance {M N} [Unique M] [Unique N] [Mul M] [Mul N] : Unique (M ≃* N) where
  default := mulEquivOfUnique
  uniq _ := ext fun _ => Subsingleton.elim _ _

/-!
## Monoids
-/

/-- A multiplicative isomorphism of monoids sends `1` to `1` (and is hence a monoid isomorphism). -/
@[to_additive
  "An additive isomorphism of additive monoids sends `0` to `0`
  (and is hence an additive monoid isomorphism)."]
protected theorem map_one {M N} [MulOneClass M] [MulOneClass N] (h : M ≃* N) : h 1 = 1 :=
  _root_.map_one h
#align mul_equiv.map_one MulEquiv.map_one
#align add_equiv.map_zero AddEquiv.map_zero

@[to_additive]
protected theorem map_eq_one_iff {M N} [MulOneClass M] [MulOneClass N] (h : M ≃* N) {x : M} :
  h x = 1 ↔ x = 1 :=
  MulEquivClass.map_eq_one_iff h
#align mul_equiv.map_eq_one_iff MulEquiv.map_eq_one_iff
#align add_equiv.map_eq_zero_iff AddEquiv.map_eq_zero_iff

@[to_additive]
theorem map_ne_one_iff {M N} [MulOneClass M] [MulOneClass N] (h : M ≃* N) {x : M} :
  h x ≠ 1 ↔ x ≠ 1 :=
  MulEquivClass.map_ne_one_iff h
#align mul_equiv.map_ne_one_iff MulEquiv.map_ne_one_iff
#align add_equiv.map_ne_zero_iff AddEquiv.map_ne_zero_iff

/-- A bijective `Semigroup` homomorphism is an isomorphism -/
@[to_additive (attr := simps! apply) "A bijective `AddSemigroup` homomorphism is an isomorphism"]
noncomputable def ofBijective {M N F} [Mul M] [Mul N] [MulHomClass F M N]
(f : F) (hf : Function.Bijective f) :
    M ≃* N :=
  { Equiv.ofBijective f hf with map_mul' := map_mul f }
#align mul_equiv.of_bijective MulEquiv.ofBijective
#align add_equiv.of_bijective AddEquiv.ofBijective
#align mul_equiv.of_bijective_apply MulEquiv.ofBijective_apply
#align add_equiv.of_bijective_apply AddEquiv.ofBijective_apply

-- porting note: todo: simplify `symm_apply` to `surjInv`?
@[to_additive (attr := simp)]
theorem ofBijective_apply_symm_apply {M N} [MulOneClass M] [MulOneClass N] {n : N} (f : M →* N)
    (hf : Function.Bijective f) : f ((Equiv.ofBijective f hf).symm n) = n :=
  (MulEquiv.ofBijective f hf).apply_symm_apply n
#align mul_equiv.of_bijective_apply_symm_apply MulEquiv.ofBijective_apply_symm_apply
#align add_equiv.of_bijective_apply_symm_apply AddEquiv.ofBijective_apply_symm_apply

/-- Extract the forward direction of a multiplicative equivalence
as a multiplication-preserving function.
-/
@[to_additive "Extract the forward direction of an additive equivalence
  as an addition-preserving function."]
def toMonoidHom {M N} [MulOneClass M] [MulOneClass N] (h : M ≃* N) : M →* N :=
  { h with map_one' := h.map_one }
#align mul_equiv.to_monoid_hom MulEquiv.toMonoidHom
#align add_equiv.to_add_monoid_hom AddEquiv.toAddMonoidHom

@[to_additive (attr := simp)]
theorem coe_toMonoidHom {M N} [MulOneClass M] [MulOneClass N] (e : M ≃* N) :
  ⇑e.toMonoidHom = e := rfl
#align mul_equiv.coe_to_monoid_hom MulEquiv.coe_toMonoidHom
#align add_equiv.coe_to_add_monoid_hom AddEquiv.coe_toAddMonoidHom

set_option linter.deprecated false in
@[to_additive]
theorem toMonoidHom_injective {M N} [MulOneClass M] [MulOneClass N] :
  Function.Injective (toMonoidHom : M ≃* N → M →* N) :=
  fun _ _ h => MulEquiv.ext (MonoidHom.ext_iff.1 h)
#align mul_equiv.to_monoid_hom_injective MulEquiv.toMonoidHom_injective
#align add_equiv.to_add_monoid_hom_injective AddEquiv.toAddMonoidHom_injective

/-- A multiplicative analogue of `Equiv.arrowCongr`,
where the equivalence between the targets is multiplicative.
-/
@[to_additive (attr := simps apply) "An additive analogue of `Equiv.arrowCongr`,
  where the equivalence between the targets is additive."]
def arrowCongr {M N P Q : Type _} [Mul P] [Mul Q] (f : M ≃ N) (g : P ≃* Q) :
  (M → P) ≃* (N → Q) where
  toFun h n := g (h (f.symm n))
  invFun k m := g.symm (k (f m))
  left_inv h := by ext; simp
  right_inv k := by ext; simp
  map_mul' h k := by ext; simp
#align mul_equiv.arrow_congr MulEquiv.arrowCongr
#align add_equiv.arrow_congr AddEquiv.arrowCongr
#align mul_equiv.arrow_congr_apply MulEquiv.arrowCongr_apply
#align add_equiv.arrow_congr_apply AddEquiv.arrowCongr_apply

/-- A multiplicative analogue of `Equiv.arrowCongr`,
for multiplicative maps from a monoid to a commutative monoid.
-/
@[to_additive (attr := simps apply)
  "An additive analogue of `Equiv.arrowCongr`,
  for additive maps from an additive monoid to a commutative additive monoid."]
-- porting note: @[simps apply] removed because it was making a lemma which
-- wasn't in simp normal form.
def monoidHomCongr {M N P Q} [MulOneClass M] [MulOneClass N] [CommMonoid P] [CommMonoid Q]
  (f : M ≃* N) (g : P ≃* Q) :
  (M →* P) ≃* (N →* Q) where
  toFun h := g.toMonoidHom.comp (h.comp f.symm.toMonoidHom)
  invFun k := g.symm.toMonoidHom.comp (k.comp f.toMonoidHom)
  left_inv h := by ext; simp
  right_inv k := by ext; simp
  map_mul' h k := by ext; simp
#align mul_equiv.monoid_hom_congr MulEquiv.monoidHomCongr
#align add_equiv.add_monoid_hom_congr AddEquiv.addMonoidHomCongr
#align mul_equiv.monoid_hom_congr_apply MulEquiv.monoidHomCongr_apply
#align add_equiv.add_monoid_hom_congr_apply AddEquiv.addMonoidHomCongr_apply

/-- A family of multiplicative equivalences `Π j, (Ms j ≃* Ns j)` generates a
multiplicative equivalence between `Π j, Ms j` and `Π j, Ns j`.

This is the `MulEquiv` version of `Equiv.piCongrRight`, and the dependent version of
`MulEquiv.arrowCongr`.
-/
@[to_additive (attr := simps apply)
  "A family of additive equivalences `Π j, (Ms j ≃+ Ns j)`
  generates an additive equivalence between `Π j, Ms j` and `Π j, Ns j`.

  This is the `AddEquiv` version of `Equiv.piCongrRight`, and the dependent version of
  `AddEquiv.arrowCongr`."]
def piCongrRight {η : Type _} {Ms Ns : η → Type _} [∀ j, Mul (Ms j)] [∀ j, Mul (Ns j)]
  (es : ∀ j, Ms j ≃* Ns j) : (∀ j, Ms j) ≃* ∀ j, Ns j :=
  { Equiv.piCongrRight fun j => (es j).toEquiv with
    toFun := fun x j => es j (x j),
    invFun := fun x j => (es j).symm (x j),
    map_mul' := fun x y => funext fun j => (es j).map_mul (x j) (y j) }
#align mul_equiv.Pi_congr_right MulEquiv.piCongrRight
#align add_equiv.Pi_congr_right AddEquiv.piCongrRight
#align mul_equiv.Pi_congr_right_apply MulEquiv.piCongrRight_apply
#align add_equiv.Pi_congr_right_apply AddEquiv.piCongrRight_apply

@[to_additive (attr := simp)]
theorem piCongrRight_refl {η : Type _} {Ms : η → Type _} [∀ j, Mul (Ms j)] :
  (piCongrRight fun j => MulEquiv.refl (Ms j)) = MulEquiv.refl _ := rfl
#align mul_equiv.Pi_congr_right_refl MulEquiv.piCongrRight_refl
#align add_equiv.Pi_congr_right_refl AddEquiv.piCongrRight_refl

@[to_additive (attr := simp)]
theorem piCongrRight_symm {η : Type _} {Ms Ns : η → Type _} [∀ j, Mul (Ms j)] [∀ j, Mul (Ns j)]
  (es : ∀ j, Ms j ≃* Ns j) : (piCongrRight es).symm = piCongrRight fun i => (es i).symm := rfl
#align mul_equiv.Pi_congr_right_symm MulEquiv.piCongrRight_symm
#align add_equiv.Pi_congr_right_symm AddEquiv.piCongrRight_symm

@[to_additive (attr := simp)]
theorem piCongrRight_trans {η : Type _} {Ms Ns Ps : η → Type _} [∀ j, Mul (Ms j)]
  [∀ j, Mul (Ns j)] [∀ j, Mul (Ps j)] (es : ∀ j, Ms j ≃* Ns j) (fs : ∀ j, Ns j ≃* Ps j) :
  (piCongrRight es).trans (piCongrRight fs) = piCongrRight fun i => (es i).trans (fs i) := rfl
#align mul_equiv.Pi_congr_right_trans MulEquiv.piCongrRight_trans
#align add_equiv.Pi_congr_right_trans AddEquiv.piCongrRight_trans

/-- A family indexed by a nonempty subsingleton type is equivalent to the element at the single
index. -/
@[to_additive (attr := simps!)
  "A family indexed by a nonempty subsingleton type is
  equivalent to the element at the single index."]
def piSubsingleton {ι : Type _} (M : ι → Type _) [∀ j, Mul (M j)] [Subsingleton ι]
  (i : ι) : (∀ j, M j) ≃* M i :=
  { Equiv.piSubsingleton M i with map_mul' := fun _ _ => Pi.mul_apply _ _ _ }
#align mul_equiv.Pi_subsingleton MulEquiv.piSubsingleton
#align add_equiv.Pi_subsingleton AddEquiv.piSubsingleton
#align mul_equiv.Pi_subsingleton_apply MulEquiv.piSubsingleton_apply
#align add_equiv.Pi_subsingleton_apply AddEquiv.piSubsingleton_apply
#align mul_equiv.Pi_subsingleton_symm_apply MulEquiv.piSubsingleton_symm_apply
#align add_equiv.Pi_subsingleton_symm_apply AddEquiv.piSubsingleton_symm_apply

/-!
# Groups
-/

/-- A multiplicative equivalence of groups preserves inversion. -/
@[to_additive "An additive equivalence of additive groups preserves negation."]
protected theorem map_inv [Group G] [DivisionMonoid H] (h : G ≃* H) (x : G) :
  h x⁻¹ = (h x)⁻¹ :=
  _root_.map_inv h x
#align mul_equiv.map_inv MulEquiv.map_inv
#align add_equiv.map_neg AddEquiv.map_neg

/-- A multiplicative equivalence of groups preserves division. -/
@[to_additive "An additive equivalence of additive groups preserves subtractions."]
protected theorem map_div [Group G] [DivisionMonoid H] (h : G ≃* H) (x y : G) :
  h (x / y) = h x / h y :=
  _root_.map_div h x y
#align mul_equiv.map_div MulEquiv.map_div
#align add_equiv.map_sub AddEquiv.map_sub

end MulEquiv

-- porting note: we want to add
-- `@[simps (config := { fullyApplied := false })]`
-- here, but it generates simp lemmas which aren't in simp normal form
-- (they have `toFun` in)
/-- Given a pair of multiplicative homomorphisms `f`, `g` such that `g.comp f = id` and
`f.comp g = id`, returns an multiplicative equivalence with `toFun = f` and `invFun = g`. This
constructor is useful if the underlying type(s) have specialized `ext` lemmas for multiplicative
homomorphisms. -/
@[to_additive
  "Given a pair of additive homomorphisms `f`, `g` such that `g.comp f = id` and
  `f.comp g = id`, returns an additive equivalence with `toFun = f` and `invFun = g`. This
  constructor is useful if the underlying type(s) have specialized `ext` lemmas for additive
  homomorphisms."]
def MulHom.toMulEquiv [Mul M] [Mul N] (f : M →ₙ* N) (g : N →ₙ* M) (h₁ : g.comp f = MulHom.id _)
  (h₂ : f.comp g = MulHom.id _) : M ≃* N where
  toFun := f
  invFun := g
  left_inv := FunLike.congr_fun h₁
  right_inv := FunLike.congr_fun h₂
  map_mul' := f.map_mul
#align mul_hom.to_mul_equiv MulHom.toMulEquiv
#align add_hom.to_add_equiv AddHom.toAddEquiv

-- porting note: the next two lemmas were added manually because `@[simps]` is generating
-- lemmas with `toFun` in
@[to_additive (attr := simp)]
theorem MulHom.toMulEquiv_apply [Mul M] [Mul N] (f : M →ₙ* N) (g : N →ₙ* M)
    (h₁ : g.comp f = MulHom.id _) (h₂ : f.comp g = MulHom.id _) :
    ((MulHom.toMulEquiv f g h₁ h₂) : M → N) = f :=
  rfl
#align mul_hom.to_mul_equiv_apply MulHom.toMulEquiv_apply
#align add_hom.to_add_equiv_apply AddHom.toAddEquiv_apply

@[to_additive (attr := simp)]
theorem MulHom.toMulEquiv_symm_apply [Mul M] [Mul N] (f : M →ₙ* N) (g : N →ₙ* M)
    (h₁ : g.comp f = MulHom.id _) (h₂ : f.comp g = MulHom.id _) :
    (MulEquiv.symm (MulHom.toMulEquiv f g h₁ h₂) : N → M) = ↑g :=
  rfl
#align mul_hom.to_mul_equiv_symm_apply MulHom.toMulEquiv_symm_apply
#align add_hom.to_add_equiv_symm_apply AddHom.toAddEquiv_symm_apply

/-- Given a pair of monoid homomorphisms `f`, `g` such that `g.comp f = id` and `f.comp g = id`,
returns an multiplicative equivalence with `toFun = f` and `invFun = g`.  This constructor is
useful if the underlying type(s) have specialized `ext` lemmas for monoid homomorphisms. -/
@[to_additive (attr := simps (config := { fullyApplied := false }))
  "Given a pair of additive monoid homomorphisms `f`, `g` such that `g.comp f = id`
  and `f.comp g = id`, returns an additive equivalence with `toFun = f` and `invFun = g`.  This
  constructor is useful if the underlying type(s) have specialized `ext` lemmas for additive
  monoid homomorphisms."]
def MonoidHom.toMulEquiv [MulOneClass M] [MulOneClass N] (f : M →* N) (g : N →* M)
  (h₁ : g.comp f = MonoidHom.id _) (h₂ : f.comp g = MonoidHom.id _) : M ≃* N where
  toFun := f
  invFun := g
  left_inv := FunLike.congr_fun h₁
  right_inv := FunLike.congr_fun h₂
  map_mul' := f.map_mul
#align monoid_hom.to_mul_equiv MonoidHom.toMulEquiv
#align add_monoid_hom.to_add_equiv AddMonoidHom.toAddEquiv
#align monoid_hom.to_mul_equiv_apply MonoidHom.toMulEquiv_apply
#align add_monoid_hom.to_add_equiv_apply AddMonoidHom.toAddEquiv_apply
#align monoid_hom.to_mul_equiv_symm_apply MonoidHom.toMulEquiv_symm_apply
#align add_monoid_hom.to_add_equiv_symm_apply AddMonoidHom.toAddEquiv_symm_apply

namespace Equiv

section InvolutiveInv

variable (G) [InvolutiveInv G]

/-- Inversion on a `Group` or `GroupWithZero` is a permutation of the underlying type. -/
@[to_additive (attr := simps! (config := { fullyApplied := false }) apply)
    "Negation on an `AddGroup` is a permutation of the underlying type."]
protected def inv : Perm G :=
  inv_involutive.toPerm _
#align equiv.inv Equiv.inv
#align equiv.neg Equiv.neg
#align equiv.inv_apply Equiv.inv_apply
#align equiv.neg_apply Equiv.neg_apply

variable {G}

@[to_additive (attr := simp)]
theorem inv_symm : (Equiv.inv G).symm = Equiv.inv G := rfl
#align equiv.inv_symm Equiv.inv_symm
#align equiv.neg_symm Equiv.neg_symm

end InvolutiveInv

end Equiv