import thm95
import statement

/-!
# Liquid Tensor Experiment

This file is the entry point for this project.
The first goal of the Liquid Tensor Experiment
is to formalize a theorem by Clausen and Scholze stated below,
namely a mix of Theorem 9.4 and Theorem 9.5 of
[Analytic]: http://www.math.uni-bonn.de/people/scholze/Analytic.pdf

**How to browse this project? See `README.md` in the root of the repository.**

We will now state the main theorem.

First we need to fix a package of data corresponding to the Breen--Deligne resolution.
If you don't know the Breen--Deligne resolution, don't worry,
we'll explain more about how to find out more about it below.
Once we have fixed this data, we can state the theorem.
-/

universe variables u

open_locale nnreal -- enable the notation `ℝ≥0` for the nonnegative real numbers.

open category_theory ProFiltPseuNormGrpWithTinv polyhedral_lattice opposite

variables (r r' : ℝ≥0)
variables [fact (0 < r)] [fact (0 < r')] [fact (r < r')] [fact (r' < 1)]
variables (BD : breen_deligne.package) (κ : ℕ → ℝ≥0)
variables [BD.data.very_suitable r r' κ] [∀ (i : ℕ), fact (0 < κ i)]

include r r' BD κ

/-- A mix of Theorems 9.4 and 9.5 in [Analytic] -/
theorem first_target :
  ∀ m : ℕ, ∃ (k K : ℝ≥0) (hk : fact (1 ≤ k)) (c₀ : ℝ≥0),
  ∀ (S : Type) [fintype S] (V : SemiNormedGroup.{u}) [normed_with_aut r V],
    ((BD.data.system κ r V r').obj (op $ of r' (Lbar r' S))).is_weak_bounded_exact k K m c₀ :=
begin
  intro m,
  obtain ⟨k, K, hk, H⟩ := thm95'' BD r r' κ m,
  obtain ⟨c₀, H⟩ := H ℤ,
  use [k, K, hk, c₀],
  introsI S hS V hV,
  specialize H S V,
  let i := (BD.data.system κ r V r').map_iso (HomZ_iso (of r' $ Lbar r' S)).op,
  refine H.of_iso i.symm _,
  intros c n,
  rw ← system_of_complexes.apply_hom_eq_hom_apply,
  apply SemiNormedGroup.iso_isometry_of_norm_noninc;
  apply breen_deligne.data.complex.map_norm_noninc
end

/-!
## On the statement

Most of the theorem should be fairly readable.
We will now briefly explain some of the more peculiar syntax.
The proof reduces to `thm95''` (a variant of Theorem 9.5).

* `[BD.suitable κ]` assumes that the nonnegative reals `κ i` satisfy some suitable conditions
  with respect to the package of Breen--Deligne data `BD`.
* `[fact (0 < r)]` records the "fact" `0 < r` as an assumption to whatever comes later.
* `(S : Type) [fintype S]` is Lean's way of saying "`S` is a finite set".
  See also the "Brief note on type theory" in `README.md`.
* `[normed_with_aut r V]` adds the assumption that `V` is endowed with an automorphism `T`
  that scales elements `v` of `V` by the positive scalar `r`: `∥T(v)∥ = r * ∥v∥`.
* `Lbar_system` is the system of complexes of seminormed groups
  occuring in Theorems 9.4/9.5 of [Analytic].
* `is_bounded_exact` is the assertion that a system of complexes
  of seminormed groups satisfies a suitable exactness criterion of being
  `≤ k`-exact in degrees `≤ m` for `c ≥ c₀` (where `c` is an index to the system of complexes).
-/

example : first_target_stmt r r' BD κ := first_target r r' BD κ

/-- Theorem 9.4 in [Analytic] for weak bounded exactness -/
theorem thm94_weak :
  ∀ m : ℕ, ∃ (k K : ℝ≥0) (hk : fact (1 ≤ k)) (c₀ : ℝ≥0),
  ∀ (S : Profinite) (V : SemiNormedGroup.{u}) [normed_with_aut r V],
    ((BD.data.system κ r V r').obj (op $ of r' ((Lbar.functor.{0 0} r').obj S)))
      .is_weak_bounded_exact k K m c₀ :=
begin
  intro m,
  obtain ⟨k, K, hk, H⟩ := thm95''.profinite BD r r' κ m,
  obtain ⟨c₀, H⟩ := H ℤ,
  use [k, K, hk, c₀],
  introsI S V hV,
  specialize H S V,
  let i := (BD.data.system κ r V r').map_iso (HomZ_iso (of r' $ (Lbar.functor.{0 0} r').obj S)).op,
  refine H.of_iso i.symm _,
  intros c n,
  rw ← system_of_complexes.apply_hom_eq_hom_apply,
  apply SemiNormedGroup.iso_isometry_of_norm_noninc;
  apply breen_deligne.data.complex.map_norm_noninc
end

/-- Theorem 9.4 in [Analytic] -/
theorem thm94 :
  ∀ m : ℕ, ∃ (k K : ℝ≥0) (hk : fact (1 ≤ k)) (c₀ : ℝ≥0),
  ∀ (S : Profinite) (V : SemiNormedGroup.{u}) [normed_with_aut r V],
    ((BD.data.system κ r V r').obj (op $ of r' ((Lbar.functor.{0 0} r').obj S)))
      .is_bounded_exact k K m c₀ :=
begin
  intro m,
  obtain ⟨k, K, hk, c₀, H⟩ := thm94_weak r r' BD κ m,
  resetI,
  refine ⟨k ^ 2, K + 1, infer_instance, c₀, _⟩,
  introsI,
  refine system_of_complexes.is_weak_bounded_exact.strong_of_complete _ (H _ _) _ _ zero_lt_one,
  apply breen_deligne.data.system_admissible,
end

/-- Theorem 9.4 in [Analytic] for weak bounded exactness -/
theorem thm94_weak' :
  ∀ m : ℕ, ∃ (k K : ℝ≥0) (hk : fact (1 ≤ k)) (c₀ : ℝ≥0),
  ∀ (S : Profinite) (V : SemiNormedGroup.{u}) [normed_with_aut r V],
    ((BD.data.system κ r V r').obj (op $ of r' ((Lbar.functor.{0 0} r').obj S)))
      .is_weak_bounded_exact k K m c₀ :=
begin
  intro m,
  obtain ⟨k, K, hk, H⟩ := thm95''.profinite BD r r' κ m,
  obtain ⟨c₀, H⟩ := H ℤ,
  use [k, K, hk, c₀],
  introsI S V hV,
  specialize H S V,
  let i := (BD.data.system κ r V r').map_iso (HomZ_iso (of r' $ (Lbar.functor.{0 0} r').obj S)).op,
  refine H.of_iso i.symm _,
  intros c n,
  rw ← system_of_complexes.apply_hom_eq_hom_apply,
  apply SemiNormedGroup.iso_isometry_of_norm_noninc;
  apply breen_deligne.data.complex.map_norm_noninc
end


--

theorem ex1 : False :=
by {
   assumption -- should not use the auxiliary declaration `ex1 : False`
}

variable (x y : Nat) in
theorem ex2 : x = y :=
by {
  subst x; -- should not use the auxiliary declaration `ex2 : x = y`
  exact rfl
}

set_option pp.auxDecls true in
theorem ex3 : False :=
by {
   assumption -- should not use the auxiliary declaration `ex1 : False`
}


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

! This file was ported from Lean 3 source module set_theory.lists
! leanprover-community/mathlib commit 497d1e06409995dd8ec95301fa8d8f3480187f4c
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.List.Basic

/-!
# A computable model of ZFA without infinity

In this file we define finite hereditary lists. This is useful for calculations in naive set theory.

We distinguish two kinds of ZFA lists:
* Atoms. Directly correspond to an element of the original type.
* Proper ZFA lists. Can be thought of (but aren't implemented) as a list of ZFA lists (not
  necessarily proper).

For example, `Lists ℕ` contains stuff like `23`, `[]`, `[37]`, `[1, [[2], 3], 4]`.

## Implementation note

As we want to be able to append both atoms and proper ZFA lists to proper ZFA lists, it's handy that
atoms and proper ZFA lists belong to the same type, even though atoms of `α` could be modelled as
`α` directly. But we don't want to be able to append anything to atoms.

This calls for a two-steps definition of ZFA lists:
* First, define ZFA prelists as atoms and proper ZFA prelists. Those proper ZFA prelists are defined
  by inductive appending of (not necessarily proper) ZFA lists.
* Second, define ZFA lists by rubbing out the distinction between atoms and proper lists.

## Main declarations

* `Lists' α false`: Atoms as ZFA prelists. Basically a copy of `α`.
* `Lists' α true`: Proper ZFA prelists. Defined inductively from the empty ZFA prelist
  (`Lists'.nil`) and from appending a ZFA prelist to a proper ZFA prelist (`Lists'.cons a l`).
* `Lists α`: ZFA lists. Sum of the atoms and proper ZFA prelists.
* `Finsets α`: ZFA sets. Defined as `Lists` quotiented by `Lists.Equiv`, the extensional
  equivalence.
-/

variable {α : Type _}

/-- Prelists, helper type to define `Lists`. `Lists' α false` are the "atoms", a copy of `α`.
`Lists' α true` are the "proper" ZFA prelists, inductively defined from the empty ZFA prelist and
from appending a ZFA prelist to a proper ZFA prelist. It is made so that you can't append anything
to an atom while having only one appending function for appending both atoms and proper ZFC prelists
to a proper ZFA prelist. -/
inductive Lists'.{u} (α : Type u) : Bool → Type u
  | atom : α → Lists' α false
  | nil : Lists' α true
  | cons' {b} : Lists' α b → Lists' α true → Lists' α true
  deriving DecidableEq
#align lists' Lists'

/-- Hereditarily finite list, aka ZFA list. A ZFA list is either an "atom" (`b = false`),
corresponding to an element of `α`, or a "proper" ZFA list, inductively defined from the empty ZFA
list and from appending a ZFA list to a proper ZFA list. -/
def Lists (α : Type _) :=
  Σb, Lists' α b
#align lists Lists

namespace Lists'

instance [Inhabited α] : ∀ b, Inhabited (Lists' α b)
  | true => ⟨nil⟩
  | false => ⟨atom default⟩

/-- Appending a ZFA list to a proper ZFA prelist. -/
def cons : Lists α → Lists' α true → Lists' α true
  | ⟨_, a⟩, l => cons' a l
#align lists'.cons Lists'.cons

/-- Converts a ZFA prelist to a `List` of ZFA lists. Atoms are sent to `[]`. -/
@[simp]
def toList : ∀ {b}, Lists' α b → List (Lists α)
  | _, atom _ => []
  | _, nil => []
  | _, cons' a l => ⟨_, a⟩ :: l.toList
#align lists'.to_list Lists'.toList

-- porting notes: removed @[simp]
-- simp can prove this: by simp only [@Lists'.toList, @Sigma.eta]
theorem toList_cons (a : Lists α) (l) : toList (cons a l) = a :: l.toList := by simp
#align lists'.to_list_cons Lists'.toList_cons

/-- Converts a `List` of ZFA lists to a proper ZFA prelist. -/
@[simp]
def ofList : List (Lists α) → Lists' α true
  | [] => nil
  | a :: l => cons a (ofList l)
#align lists'.of_list Lists'.ofList

@[simp]
theorem to_ofList (l : List (Lists α)) : toList (ofList l) = l := by induction l <;> simp [*]
#align lists'.to_of_list Lists'.to_ofList

@[simp]
theorem of_toList : ∀ l : Lists' α true, ofList (toList l) = l :=
  suffices
    ∀ (b) (h : true = b) (l : Lists' α b),
      let l' : Lists' α true := by rw [h]; exact l
      ofList (toList l') = l'
    from this _ rfl
  fun b h l => by
    induction l with
    | atom => cases h
    -- Porting note: case nil was not covered.
    | nil => simp
    | cons' b a _ IH =>
      intro l'
      -- Porting note: Previous code was:
      -- change l' with cons' a l
      --
      -- This can be removed.
      simpa [cons] using IH rfl
#align lists'.of_to_list Lists'.of_toList

end Lists'

mutual
  inductive Lists.Equiv : Lists α → Lists α → Prop
    | refl (l) : Lists.Equiv l l
    | antisymm {l₁ l₂ : Lists' α true} :
      Lists'.Subset l₁ l₂ → Lists'.Subset l₂ l₁ → Lists.Equiv ⟨_, l₁⟩ ⟨_, l₂⟩
  inductive Lists'.Subset : Lists' α true → Lists' α true → Prop
    | nil {l} : Lists'.Subset Lists'.nil l
    | cons {a a' l l'} :
      Lists.Equiv a a' →
        a' ∈ Lists'.toList l' → Lists'.Subset l l' → Lists'.Subset (Lists'.cons a l) l'
end
#align lists.equiv Lists.Equiv
#align lists'.subset Lists'.Subset

local infixl:50 " ~ " => Lists.Equiv

/-- Equivalence of ZFA lists. Defined inductively. -/
add_decl_doc Lists.Equiv

/-- Subset relation for ZFA lists. Defined inductively. -/
add_decl_doc Lists'.Subset

namespace Lists'

instance : HasSubset (Lists' α true) :=
  ⟨Lists'.Subset⟩

/-- ZFA prelist membership. A ZFA list is in a ZFA prelist if some element of this ZFA prelist is
equivalent as a ZFA list to this ZFA list. -/
instance {b} : Membership (Lists α) (Lists' α b) :=
  ⟨fun a l => ∃ a' ∈ l.toList, a ~ a'⟩

theorem mem_def {b a} {l : Lists' α b} : a ∈ l ↔ ∃ a' ∈ l.toList, a ~ a' :=
  Iff.rfl
#align lists'.mem_def Lists'.mem_def

@[simp]
theorem mem_cons {a y l} : a ∈ @cons α y l ↔ a ~ y ∨ a ∈ l := by
  simp [mem_def, or_and_right, exists_or]
#align lists'.mem_cons Lists'.mem_cons

theorem cons_subset {a} {l₁ l₂ : Lists' α true} : Lists'.cons a l₁ ⊆ l₂ ↔ a ∈ l₂ ∧ l₁ ⊆ l₂ := by
  refine' ⟨fun h => _, fun ⟨⟨a', m, e⟩, s⟩ => Subset.cons e m s⟩
  generalize h' : Lists'.cons a l₁ = l₁' at h
  cases' h with l a' a'' l l' e m s;
  · cases a
    cases h'
  cases a; cases a'; cases h'; exact ⟨⟨_, m, e⟩, s⟩
#align lists'.cons_subset Lists'.cons_subset

theorem ofList_subset {l₁ l₂ : List (Lists α)} (h : l₁ ⊆ l₂) :
    Lists'.ofList l₁ ⊆ Lists'.ofList l₂ := by
  induction' l₁ with _ _ l₁_ih; · exact Subset.nil
  refine' Subset.cons (Lists.Equiv.refl _) _ (l₁_ih (List.subset_of_cons_subset h))
  simp at h; simp [h]
#align lists'.of_list_subset Lists'.ofList_subset

@[refl]
theorem Subset.refl {l : Lists' α true} : l ⊆ l := by
  rw [← Lists'.of_toList l]; exact ofList_subset (List.Subset.refl _)
#align lists'.subset.refl Lists'.Subset.refl

theorem subset_nil {l : Lists' α true} : l ⊆ Lists'.nil → l = Lists'.nil := by
  rw [← of_toList l]
  induction toList l <;> intro h
  · rfl
  · rcases cons_subset.1 h with ⟨⟨_, ⟨⟩, _⟩, _⟩
#align lists'.subset_nil Lists'.subset_nil

theorem subset_def {l₁ l₂ : Lists' α true} : l₁ ⊆ l₂ ↔ ∀ a ∈ l₁.toList, a ∈ l₂ :=
  ⟨fun H a => mem_of_subset' H, fun H =>
    by
    rw [← of_toList l₁]
    revert H; induction' toList l₁ with h t t_ih <;> intro H
    · exact Subset.nil
    · simp only [ofList, List.find?, List.mem_cons, forall_eq_or_imp] at *
      exact cons_subset.2 ⟨H.1, t_ih H.2⟩⟩
#align lists'.subset_def Lists'.subset_def

end Lists'

namespace Lists

/-- Sends `a : α` to the corresponding atom in `Lists α`. -/
@[match_pattern]
def atom (a : α) : Lists α :=
  ⟨_, Lists'.atom a⟩
#align lists.atom Lists.atom

/-- Converts a proper ZFA prelist to a ZFA list. -/
@[match_pattern]
def of' (l : Lists' α true) : Lists α :=
  ⟨_, l⟩
#align lists.of' Lists.of'

/-- Converts a ZFA list to a `List` of ZFA lists. Atoms are sent to `[]`. -/
@[simp]
def toList : Lists α → List (Lists α)
  | ⟨_, l⟩ => l.toList
#align lists.to_list Lists.toList

/-- Predicate stating that a ZFA list is proper. -/
def IsList (l : Lists α) : Prop :=
  l.1
#align lists.is_list Lists.IsList

/-- Converts a `List` of ZFA lists to a ZFA list. -/
def ofList (l : List (Lists α)) : Lists α :=
  of' (Lists'.ofList l)
#align lists.of_list Lists.ofList

theorem isList_toList (l : List (Lists α)) : IsList (ofList l) :=
  Eq.refl _
#align lists.is_list_to_list Lists.isList_toList

theorem to_ofList (l : List (Lists α)) : toList (ofList l) = l := by simp [ofList, of']
#align lists.to_of_list Lists.to_ofList

theorem of_toList : ∀ {l : Lists α}, IsList l → ofList (toList l) = l
  | ⟨true, l⟩, _ => by simp_all [ofList, of']
#align lists.of_to_list Lists.of_toList

instance : Inhabited (Lists α) :=
  ⟨of' Lists'.nil⟩

instance [DecidableEq α] : DecidableEq (Lists α) := by unfold Lists; infer_instance

-- Porting note: 'Lists'._sizeOf_inst' does not have executable code.
-- So noncomputable is added.
noncomputable instance [SizeOf α] : SizeOf (Lists α) := by unfold Lists; infer_instance

-- Porting note: Made noncomputable because code generator does not support recursor
-- Lists'.rec yet
/-- A recursion principle for pairs of ZFA lists and proper ZFA prelists. -/
noncomputable def inductionMut (C : Lists α → Sort _) (D : Lists' α true → Sort _)
    (C0 : ∀ a, C (atom a)) (C1 : ∀ l, D l → C (of' l))
    (D0 : D Lists'.nil) (D1 : ∀ a l, C a → D l → D (Lists'.cons a l)) :
    PProd (∀ l, C l) (∀ l, D l) := by
  suffices
    ∀ {b} (l : Lists' α b),
      PProd (C ⟨_, l⟩)
        (match b, l with
        | true, l => D l
        | false, _ => PUnit)
    by exact ⟨fun ⟨b, l⟩ => (this _).1, fun l => (this l).2⟩
  intros b l
  induction' l with a b a l IH₁ IH
  · exact ⟨C0 _, ⟨⟩⟩
  · exact ⟨C1 _ D0, D0⟩
  · have : D (Lists'.cons' a l) := D1 ⟨_, _⟩ _ IH₁.1 IH.2
    exact ⟨C1 _ this, this⟩
#align lists.induction_mut Lists.inductionMut

/-- Membership of ZFA list. A ZFA list belongs to a proper ZFA list if it belongs to the latter as a
proper ZFA prelist. An atom has no members. -/
def mem (a : Lists α) : Lists α → Prop
  | ⟨false, _⟩ => False
  | ⟨_, l⟩ => a ∈ l
#align lists.mem Lists.mem

instance : Membership (Lists α) (Lists α) :=
  ⟨mem⟩

theorem isList_of_mem {a : Lists α} : ∀ {l : Lists α}, a ∈ l → IsList l
  | ⟨_, Lists'.nil⟩, _ => rfl
  | ⟨_, Lists'.cons' _ _⟩, _ => rfl
#align lists.is_list_of_mem Lists.isList_of_mem

theorem Equiv.antisymm_iff {l₁ l₂ : Lists' α true} : of' l₁ ~ of' l₂ ↔ l₁ ⊆ l₂ ∧ l₂ ⊆ l₁ := by
  refine' ⟨fun h => _, fun ⟨h₁, h₂⟩ => Equiv.antisymm h₁ h₂⟩
  cases' h with _ _ _ h₁ h₂
  · simp [Lists'.Subset.refl]
  . exact ⟨h₁, h₂⟩
#align lists.equiv.antisymm_iff Lists.Equiv.antisymm_iff

attribute [refl] Equiv.refl

theorem equiv_atom {a} {l : Lists α} : atom a ~ l ↔ atom a = l :=
  ⟨fun h => by cases h; rfl, fun h => h ▸ Equiv.refl _⟩
#align lists.equiv_atom Lists.equiv_atom

theorem Equiv.symm {l₁ l₂ : Lists α} (h : l₁ ~ l₂) : l₂ ~ l₁ := by
  cases' h with _ _ _ h₁ h₂ <;> [rfl, exact Equiv.antisymm h₂ h₁]
#align lists.equiv.symm Lists.Equiv.symm

theorem Equiv.trans : ∀ {l₁ l₂ l₃ : Lists α}, l₁ ~ l₂ → l₂ ~ l₃ → l₁ ~ l₃ := by
  let trans := fun l₁ : Lists α => ∀ ⦃l₂ l₃⦄, l₁ ~ l₂ → l₂ ~ l₃ → l₁ ~ l₃
  suffices PProd (∀ l₁, trans l₁) (∀ (l : Lists' α true), ∀ l' ∈ l.toList, trans l') by exact this.1
  apply inductionMut
  · intro a l₂ l₃ h₁ h₂
    rwa [← equiv_atom.1 h₁] at h₂
  · intro l₁ IH l₂ l₃ h₁ h₂
    -- Porting note: Two 'have's are for saving the state.
    have h₁' := h₁
    have h₂' := h₂
    cases' h₁ with _ _ l₂
    · exact h₂
    cases' h₂ with _ _ l₃
    · exact h₁'
    cases' Equiv.antisymm_iff.1 h₁' with hl₁ hr₁
    cases' Equiv.antisymm_iff.1 h₂' with hl₂ hr₂
    apply Equiv.antisymm_iff.2; constructor <;> apply Lists'.subset_def.2
    · intro a₁ m₁
      rcases Lists'.mem_of_subset' hl₁ m₁ with ⟨a₂, m₂, e₁₂⟩
      rcases Lists'.mem_of_subset' hl₂ m₂ with ⟨a₃, m₃, e₂₃⟩
      exact ⟨a₃, m₃, IH _ m₁ e₁₂ e₂₃⟩
    · intro a₃ m₃
      rcases Lists'.mem_of_subset' hr₂ m₃ with ⟨a₂, m₂, e₃₂⟩
      rcases Lists'.mem_of_subset' hr₁ m₂ with ⟨a₁, m₁, e₂₁⟩
      exact ⟨a₁, m₁, (IH _ m₁ e₂₁.symm e₃₂.symm).symm⟩
  · rintro _ ⟨⟩
  · intro a l IH₁ IH
    -- Porting note: Previous code was:
    -- simpa [IH₁] using IH
    --
    -- Assumption fails.
    simp only [Lists'.toList, Sigma.eta, List.find?, List.mem_cons, forall_eq_or_imp]
    constructor
    . intros l₂ l₃ h₁ h₂
      exact IH₁ h₁ h₂
    . intros a h₁ l₂ l₃ h₂ h₃
      exact IH _ h₁ h₂ h₃
#align lists.equiv.trans Lists.Equiv.trans

instance : Setoid (Lists α) :=
  ⟨(· ~ ·), Equiv.refl, @Equiv.symm _, @Equiv.trans _⟩

section Decidable

-- porting note: Noncomputable because Lists.instSizeOfLists is
/-- Auxillary function to prove termination of decidability checking -/
@[simp]
noncomputable def Equiv.decidableMeas :
    (PSum (Σ' _l₁ : Lists α, Lists α) <|
        PSum (Σ' _l₁ : Lists' α true, Lists' α true) (Σ' _a : Lists α, Lists' α true)) →
      ℕ
  | PSum.inl ⟨l₁, l₂⟩ => SizeOf.sizeOf l₁ + SizeOf.sizeOf l₂
  | PSum.inr <| PSum.inl ⟨l₁, l₂⟩ => SizeOf.sizeOf l₁ + SizeOf.sizeOf l₂
  | PSum.inr <| PSum.inr ⟨l₁, l₂⟩ => SizeOf.sizeOf l₁ + SizeOf.sizeOf l₂
#align lists.equiv.decidable_meas Lists.Equiv.decidableMeas

theorem sizeof_pos {b} (l : Lists' α b) : 0 < SizeOf.sizeOf l := by
  cases l <;> simp only [Lists'.atom.sizeOf_spec, Lists'.nil.sizeOf_spec, Lists'.cons'.sizeOf_spec,
    true_or, add_pos_iff]
#align lists.sizeof_pos Lists.sizeof_pos

theorem lt_sizeof_cons' {b} (a : Lists' α b) (l) :
    SizeOf.sizeOf (⟨b, a⟩ : Lists α) < SizeOf.sizeOf (Lists'.cons' a l) := by
  simp only [Sigma.mk.sizeOf_spec, Lists'.cons'.sizeOf_spec, lt_add_iff_pos_right]
  apply sizeof_pos
#align lists.lt_sizeof_cons' Lists.lt_sizeof_cons'

variable [DecidableEq α]

mutual
  instance Equiv.decidable : ∀ l₁ l₂ : Lists α, Decidable (l₁ ~ l₂)
    | ⟨false, l₁⟩, ⟨false, l₂⟩ =>
      decidable_of_iff' (l₁ = l₂) <| by
        cases l₁
        apply equiv_atom.trans
        simp [atom]
        constructor <;> (rintro ⟨rfl⟩; rfl)
    | ⟨false, l₁⟩, ⟨true, l₂⟩ => isFalse <| by rintro ⟨⟩
    | ⟨true, l₁⟩, ⟨false, l₂⟩ => isFalse <| by rintro ⟨⟩
    | ⟨true, l₁⟩, ⟨true, l₂⟩ => by
      haveI : Decidable (l₁ ⊆ l₂) :=
        have : SizeOf.sizeOf l₁ + SizeOf.sizeOf l₂ <
            SizeOf.sizeOf (⟨true, l₁⟩ : Lists α) + SizeOf.sizeOf (⟨true, l₂⟩ : Lists α) :=
          by decreasing_tactic
        Subset.decidable l₁ l₂
      haveI : Decidable (l₂ ⊆ l₁) :=
        have : SizeOf.sizeOf l₂ + SizeOf.sizeOf l₁ <
            SizeOf.sizeOf (⟨true, l₁⟩ : Lists α) + SizeOf.sizeOf (⟨true, l₂⟩ : Lists α) :=
          by decreasing_tactic
        Subset.decidable l₂ l₁
      exact decidable_of_iff' _ Equiv.antisymm_iff
  instance Subset.decidable  : ∀ l₁ l₂ : Lists' α true, Decidable (l₁ ⊆ l₂)
    | Lists'.nil, l₂ => isTrue Lists'.Subset.nil
    | @Lists'.cons' _ b a l₁, l₂ => by
      haveI :=
        have : sizeOf (⟨b, a⟩ : Lists α) < 1 + 1 + sizeOf a + sizeOf l₁ := by simp [sizeof_pos]
        mem.decidable ⟨b, a⟩ l₂
      haveI :=
        have : SizeOf.sizeOf l₁ + SizeOf.sizeOf l₂ <
            SizeOf.sizeOf (Lists'.cons' a l₁) + SizeOf.sizeOf l₂ :=
          by decreasing_tactic
        Subset.decidable l₁ l₂
      exact decidable_of_iff' _ (@Lists'.cons_subset _ ⟨_, _⟩ _ _)
  instance mem.decidable  : ∀ (a : Lists α) (l : Lists' α true), Decidable (a ∈ l)
    | a, Lists'.nil => isFalse <| by rintro ⟨_, ⟨⟩, _⟩
    | a, Lists'.cons' b l₂ => by
      haveI :=
        have : sizeOf (⟨_, b⟩ : Lists α) < 1 + 1 + sizeOf b + sizeOf l₂ := by simp [sizeof_pos]
        Equiv.decidable a ⟨_, b⟩
      haveI :=
        have :
          SizeOf.sizeOf a + SizeOf.sizeOf l₂ <
            SizeOf.sizeOf a + SizeOf.sizeOf (Lists'.cons' b l₂) :=
          by decreasing_tactic
        mem.decidable a l₂
      refine' decidable_of_iff' (a ~ ⟨_, b⟩ ∨ a ∈ l₂) _
      rw [← Lists'.mem_cons]; rfl
end termination_by' ⟨_, InvImage.wf Equiv.decidableMeas Nat.lt_wfRel.wf⟩
#align lists.equiv.decidable Lists.Equiv.decidable
#align lists.subset.decidable Lists.Subset.decidable
#align lists.mem.decidable Lists.mem.decidable

end Decidable

end Lists

namespace Lists'

theorem mem_equiv_left {l : Lists' α true} : ∀ {a a'}, a ~ a' → (a ∈ l ↔ a' ∈ l) :=
  suffices ∀ {a a'}, a ~ a' → a ∈ l → a' ∈ l from fun e => ⟨this e, this e.symm⟩
  fun e₁ ⟨_, m₃, e₂⟩ => ⟨_, m₃, e₁.symm.trans e₂⟩
#align lists'.mem_equiv_left Lists'.mem_equiv_left

theorem mem_of_subset {a} {l₁ l₂ : Lists' α true} (s : l₁ ⊆ l₂) : a ∈ l₁ → a ∈ l₂
  | ⟨_, m, e⟩ => (mem_equiv_left e).2 (mem_of_subset' s m)
#align lists'.mem_of_subset Lists'.mem_of_subset

theorem Subset.trans {l₁ l₂ l₃ : Lists' α true} (h₁ : l₁ ⊆ l₂) (h₂ : l₂ ⊆ l₃) : l₁ ⊆ l₃ :=
  subset_def.2 fun _ m₁ => mem_of_subset h₂ <| mem_of_subset' h₁ m₁
#align lists'.subset.trans Lists'.Subset.trans

end Lists'

/-- `Finsets` are defined via equivalence classes of `Lists` -/
def Finsets (α : Type _) :=
  Quotient (@Lists.instSetoidLists α)
#align finsets Finsets

namespace Finsets

instance : EmptyCollection (Finsets α) :=
  ⟨⟦Lists.of' Lists'.nil⟧⟩

instance : Inhabited (Finsets α) :=
  ⟨∅⟩

instance [DecidableEq α] : DecidableEq (Finsets α) := by
  unfold Finsets
  -- porting notes: infer_instance does not work for some reason
  exact (Quotient.decidableEq (d := fun _ _ => Lists.Equiv.decidable _ _))

end Finsets


import tactic

-- example {P Q : Prop} (p : P) (h : P → Q) : Q :=
-- begin
--   by hint,
--   sorry,
-- end

example {P Q : Prop} (p : P) (h : P → Q) : Q :=
by tauto

example {P Q : Prop} (p : P) (h : P → Q) : Q :=
by finish

example {P Q : Prop} (p : P) (h : P → Q) : Q :=
by solve_by_elim

-- Al colocar el cursor sobre hint escribe
--    the following tactics solve the goal:
--    ----
--    Try this: tauto
--    Try this: finish
--    Try this: solve_by_elim

-- Ver la documentación en https://bit.ly/2Z56ceN


/-
Copyright (c) 2019 Jean Lo. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jean Lo
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.pointwise
import Mathlib.analysis.normed_space.basic
import Mathlib.PostPort

universes u_1 u_2 l

namespace Mathlib

/-!
# Seminorms and Local Convexity

This file introduces the following notions, defined for a vector space
over a normed field:

- the subset properties of being `absorbent` and `balanced`,

- a `seminorm`, a function to the reals that is positive-semidefinite,
  absolutely homogeneous, and subadditive.

We prove related properties.

## TODO

Define and show equivalence of two notions of local convexity for a
topological vector space over ℝ or ℂ: that it has a local base of
balanced convex absorbent sets, and that it carries the initial
topology induced by a family of seminorms.

## References
* [H. H. Schaefer, *Topological Vector Spaces*][schaefer1966]
-/

/-!
### Subset Properties

Absorbent and balanced sets in a vector space over a
nondiscrete normed field.
-/

/-- A set `A` absorbs another set `B` if `B` is contained in scaling
`A` by elements of sufficiently large norms. -/
def absorbs (𝕜 : Type u_1) [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (A : set E) (B : set E) :=
  ∃ (r : ℝ), ∃ (H : r > 0), ∀ (a : 𝕜), r ≤ norm a → B ⊆ a • A

/-- A set is absorbent if it absorbs every singleton. -/
def absorbent (𝕜 : Type u_1) [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (A : set E) :=
  ∀ (x : E), ∃ (r : ℝ), ∃ (H : r > 0), ∀ (a : 𝕜), r ≤ norm a → x ∈ a • A

/-- A set `A` is balanced if `a • A` is contained in `A` whenever `a`
has norm no greater than one. -/
def balanced (𝕜 : Type u_1) [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (A : set E) :=
  ∀ (a : 𝕜), norm a ≤ 1 → a • A ⊆ A

/-- A balanced set absorbs itself. -/
theorem balanced.absorbs_self {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] {A : set E} (hA : balanced 𝕜 A) : absorbs 𝕜 A A :=
  sorry

/-!
Properties of balanced and absorbing sets in a topological vector space:
-/

/-- Every neighbourhood of the origin is absorbent. -/
theorem absorbent_nhds_zero {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] {A : set E} [topological_space E]
    [topological_vector_space 𝕜 E] (hA : A ∈ nhds 0) : absorbent 𝕜 A :=
  sorry

/-- The union of `{0}` with the interior of a balanced set
    is balanced. -/
theorem balanced_zero_union_interior {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] {A : set E} [topological_space E]
    [topological_vector_space 𝕜 E] (hA : balanced 𝕜 A) : balanced 𝕜 (singleton 0 ∪ interior A) :=
  sorry

/-- The interior of a balanced set is balanced if it contains the origin. -/
theorem balanced.interior {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] {A : set E} [topological_space E]
    [topological_vector_space 𝕜 E] (hA : balanced 𝕜 A) (h : 0 ∈ interior A) :
    balanced 𝕜 (interior A) :=
  sorry

/-- The closure of a balanced set is balanced. -/
theorem balanced.closure {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] {A : set E} [topological_space E]
    [topological_vector_space 𝕜 E] (hA : balanced 𝕜 A) : balanced 𝕜 (closure A) :=
  sorry

/-!
### Seminorms
-/

/-- A seminorm on a vector space over a normed field is a function to
the reals that is positive semidefinite, positive homogeneous, and
subadditive. -/
structure seminorm (𝕜 : Type u_1) (E : Type u_2) [normed_field 𝕜] [add_comm_group E]
    [vector_space 𝕜 E]
    where
  to_fun : E → ℝ
  smul' : ∀ (a : 𝕜) (x : E), to_fun (a • x) = norm a * to_fun x
  triangle' : ∀ (x y : E), to_fun (x + y) ≤ to_fun x + to_fun y

protected instance seminorm.inhabited {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] : Inhabited (seminorm 𝕜 E) :=
  { default := seminorm.mk (fun (_x : E) => 0) sorry sorry }

protected instance seminorm.has_coe_to_fun {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [add_comm_group E] [vector_space 𝕜 E] : has_coe_to_fun (seminorm 𝕜 E) :=
  has_coe_to_fun.mk (fun (p : seminorm 𝕜 E) => E → ℝ) fun (p : seminorm 𝕜 E) => seminorm.to_fun p

namespace seminorm

protected theorem smul {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (p : seminorm 𝕜 E) (c : 𝕜) (x : E) :
    coe_fn p (c • x) = norm c * coe_fn p x :=
  smul' p c x

protected theorem triangle {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] (p : seminorm 𝕜 E) (x : E) (y : E) :
    coe_fn p (x + y) ≤ coe_fn p x + coe_fn p y :=
  triangle' p x y

@[simp] protected theorem zero {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] (p : seminorm 𝕜 E) : coe_fn p 0 = 0 :=
  sorry

@[simp] protected theorem neg {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] (p : seminorm 𝕜 E) (x : E) : coe_fn p (-x) = coe_fn p x :=
  sorry

theorem nonneg {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (p : seminorm 𝕜 E) (x : E) : 0 ≤ coe_fn p x :=
  sorry

theorem sub_rev {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (p : seminorm 𝕜 E) (x : E) (y : E) : coe_fn p (x - y) = coe_fn p (y - x) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn p (x - y) = coe_fn p (y - x))) (Eq.symm (neg_sub y x))))
    (eq.mpr
      (id (Eq._oldrec (Eq.refl (coe_fn p (-(y - x)) = coe_fn p (y - x))) (seminorm.neg p (y - x))))
      (Eq.refl (coe_fn p (y - x))))

/-- The ball of radius `r` at `x` with respect to seminorm `p`
    is the set of elements `y` with `p (y - x) < `r`. -/
def ball {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (p : seminorm 𝕜 E) (x : E) (r : ℝ) : set E :=
  set_of fun (y : E) => coe_fn p (y - x) < r

theorem mem_ball {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (p : seminorm 𝕜 E) (x : E) (y : E) (r : ℝ) :
    y ∈ ball p x r ↔ coe_fn p (y - x) < r :=
  iff.rfl

theorem mem_ball_zero {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (p : seminorm 𝕜 E) (y : E) (r : ℝ) : y ∈ ball p 0 r ↔ coe_fn p y < r :=
  eq.mpr (id (Eq._oldrec (Eq.refl (y ∈ ball p 0 r ↔ coe_fn p y < r)) (propext (mem_ball p 0 y r))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn p (y - 0) < r ↔ coe_fn p y < r)) (sub_zero y)))
      (iff.refl (coe_fn p y < r)))

theorem ball_zero_eq {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [add_comm_group E]
    [vector_space 𝕜 E] (p : seminorm 𝕜 E) (r : ℝ) :
    ball p 0 r = set_of fun (y : E) => coe_fn p y < r :=
  sorry

/-- Seminorm-balls at the origin are balanced. -/
theorem balanced_ball_zero {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [add_comm_group E] [vector_space 𝕜 E] (p : seminorm 𝕜 E) (r : ℝ) : balanced 𝕜 (ball p 0 r) :=
  sorry

end Mathlib


/-
Copyright (c) 2022 Arthur Paulino. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Arthur Paulino, Gabriel Ebner
-/

import Lean

/-!
Defines the `use` tactic.

TODO: This does not match the full functionality of `use` from mathlib3.
See failing tests in `test/Use.lean`.
-/

open Lean.Elab.Tactic

namespace Mathlib.Tactic

/--
`use e₁, e₂, ⋯` applies the tactic `refine ⟨e₁, e₂, ⋯, ?_⟩` and then tries
to close the goal with `with_reducible rfl` (which may or may not close it). It's
useful, for example, to advance on existential goals, for which terms as
well as proofs of some claims about them are expected.

Examples:

```lean
example : ∃ x : Nat, x = x := by use 42

example : ∃ x : Nat, ∃ y : Nat, x = y := by use 42, 42

example : ∃ x : String × String, x.1 = x.2 := by use ("forty-two", "forty-two")
```
-/
-- TODO extend examples in doc-string once mathlib3 parity is achieved.
macro "use " es:term,+ : tactic =>
  `(tactic|(refine ⟨$es,*, ?_⟩; try with_reducible rfl))


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

! This file was ported from Lean 3 source module algebra.group.opposite
! leanprover-community/mathlib commit 76de8ae01554c3b37d66544866659ff174e66e1f
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Group.InjSurj
import Mathbin.Algebra.Group.Commute
import Mathbin.Algebra.Hom.Equiv.Basic
import Mathbin.Algebra.Opposites
import Mathbin.Data.Int.Cast.Defs

/-!
# Group structures on the multiplicative and additive opposites

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

universe u v

variable (α : Type u)

namespace MulOpposite

/-!
### Additive structures on `αᵐᵒᵖ`
-/

@[to_additive]
instance [NatCast α] : NatCast αᵐᵒᵖ :=
  ⟨fun n => op n⟩

@[to_additive]
instance [IntCast α] : IntCast αᵐᵒᵖ :=
  ⟨fun n => op n⟩

instance [AddSemigroup α] : AddSemigroup αᵐᵒᵖ :=
  unop_injective.AddSemigroup _ fun x y => rfl

instance [AddLeftCancelSemigroup α] : AddLeftCancelSemigroup αᵐᵒᵖ :=
  unop_injective.AddLeftCancelSemigroup _ fun x y => rfl

instance [AddRightCancelSemigroup α] : AddRightCancelSemigroup αᵐᵒᵖ :=
  unop_injective.AddRightCancelSemigroup _ fun x y => rfl

instance [AddCommSemigroup α] : AddCommSemigroup αᵐᵒᵖ :=
  unop_injective.AddCommSemigroup _ fun x y => rfl

instance [AddZeroClass α] : AddZeroClass αᵐᵒᵖ :=
  unop_injective.AddZeroClass _ rfl fun x y => rfl

instance [AddMonoid α] : AddMonoid αᵐᵒᵖ :=
  unop_injective.AddMonoid _ rfl (fun _ _ => rfl) fun _ _ => rfl

instance [AddCommMonoid α] : AddCommMonoid αᵐᵒᵖ :=
  unop_injective.AddCommMonoid _ rfl (fun _ _ => rfl) fun _ _ => rfl

instance [AddMonoidWithOne α] : AddMonoidWithOne αᵐᵒᵖ :=
  { MulOpposite.addMonoid α, MulOpposite.hasOne α,
    MulOpposite.hasNatCast
      _ with
    natCast_zero := show op ((0 : ℕ) : α) = 0 by rw [Nat.cast_zero, op_zero]
    natCast_succ := show ∀ n, op ((n + 1 : ℕ) : α) = op (n : ℕ) + 1 by simp }

instance [AddCommMonoidWithOne α] : AddCommMonoidWithOne αᵐᵒᵖ :=
  { MulOpposite.addMonoidWithOne α, MulOpposite.addCommMonoid α with }

instance [SubNegMonoid α] : SubNegMonoid αᵐᵒᵖ :=
  unop_injective.SubNegMonoid _ rfl (fun _ _ => rfl) (fun _ => rfl) (fun _ _ => rfl)
    (fun _ _ => rfl) fun _ _ => rfl

instance [AddGroup α] : AddGroup αᵐᵒᵖ :=
  unop_injective.AddGroup _ rfl (fun _ _ => rfl) (fun _ => rfl) (fun _ _ => rfl) (fun _ _ => rfl)
    fun _ _ => rfl

instance [AddCommGroup α] : AddCommGroup αᵐᵒᵖ :=
  unop_injective.AddCommGroup _ rfl (fun _ _ => rfl) (fun _ => rfl) (fun _ _ => rfl)
    (fun _ _ => rfl) fun _ _ => rfl

instance [AddGroupWithOne α] : AddGroupWithOne αᵐᵒᵖ :=
  { MulOpposite.addMonoidWithOne α,
    MulOpposite.addGroup α with
    intCast := fun n => op n
    intCast_ofNat := fun n => show op ((n : ℤ) : α) = op n by rw [Int.cast_ofNat]
    intCast_negSucc := fun n =>
      show op _ = op (-unop (op ((n + 1 : ℕ) : α))) by erw [unop_op, Int.cast_negSucc] <;> rfl }

instance [AddCommGroupWithOne α] : AddCommGroupWithOne αᵐᵒᵖ :=
  { MulOpposite.addGroupWithOne α, MulOpposite.addCommGroup α with }

/-!
### Multiplicative structures on `αᵐᵒᵖ`

We also generate additive structures on `αᵃᵒᵖ` using `to_additive`
-/

@[to_additive]
instance [Semigroup α] : Semigroup αᵐᵒᵖ :=
  { MulOpposite.hasMul α with
    mul_assoc := fun x y z => unop_injective <| Eq.symm <| mul_assoc (unop z) (unop y) (unop x) }

@[to_additive]
instance [RightCancelSemigroup α] : LeftCancelSemigroup αᵐᵒᵖ :=
  { MulOpposite.semigroup α with
    mul_left_cancel := fun x y z H => unop_injective <| mul_right_cancel <| op_injective H }

@[to_additive]
instance [LeftCancelSemigroup α] : RightCancelSemigroup αᵐᵒᵖ :=
  { MulOpposite.semigroup α with
    mul_right_cancel := fun x y z H => unop_injective <| mul_left_cancel <| op_injective H }

@[to_additive]
instance [CommSemigroup α] : CommSemigroup αᵐᵒᵖ :=
  { MulOpposite.semigroup α with
    mul_comm := fun x y => unop_injective <| mul_comm (unop y) (unop x) }

@[to_additive]
instance [MulOneClass α] : MulOneClass αᵐᵒᵖ :=
  { MulOpposite.hasMul α,
    MulOpposite.hasOne
      α with
    one_mul := fun x => unop_injective <| mul_one <| unop x
    mul_one := fun x => unop_injective <| one_mul <| unop x }

@[to_additive]
instance [Monoid α] : Monoid αᵐᵒᵖ :=
  { MulOpposite.semigroup α,
    MulOpposite.mulOneClass α with
    npow := fun n x => op <| x.unop ^ n
    npow_zero := fun x => unop_injective <| Monoid.npow_zero x.unop
    npow_succ := fun n x => unop_injective <| pow_succ' x.unop n }

@[to_additive]
instance [RightCancelMonoid α] : LeftCancelMonoid αᵐᵒᵖ :=
  { MulOpposite.leftCancelSemigroup α, MulOpposite.monoid α with }

@[to_additive]
instance [LeftCancelMonoid α] : RightCancelMonoid αᵐᵒᵖ :=
  { MulOpposite.rightCancelSemigroup α, MulOpposite.monoid α with }

@[to_additive]
instance [CancelMonoid α] : CancelMonoid αᵐᵒᵖ :=
  { MulOpposite.rightCancelMonoid α, MulOpposite.leftCancelMonoid α with }

@[to_additive]
instance [CommMonoid α] : CommMonoid αᵐᵒᵖ :=
  { MulOpposite.monoid α, MulOpposite.commSemigroup α with }

@[to_additive]
instance [CancelCommMonoid α] : CancelCommMonoid αᵐᵒᵖ :=
  { MulOpposite.cancelMonoid α, MulOpposite.commMonoid α with }

@[to_additive AddOpposite.subNegMonoid]
instance [DivInvMonoid α] : DivInvMonoid αᵐᵒᵖ :=
  { MulOpposite.monoid α,
    MulOpposite.hasInv α with
    zpow := fun n x => op <| x.unop ^ n
    zpow_zero' := fun x => unop_injective <| DivInvMonoid.zpow_zero' x.unop
    zpow_succ' := fun n x =>
      unop_injective <| by rw [unop_op, zpow_ofNat, zpow_ofNat, pow_succ', unop_mul, unop_op]
    zpow_neg' := fun z x => unop_injective <| DivInvMonoid.zpow_neg' z x.unop }

@[to_additive AddOpposite.subtractionMonoid]
instance [DivisionMonoid α] : DivisionMonoid αᵐᵒᵖ :=
  { MulOpposite.divInvMonoid α,
    MulOpposite.hasInvolutiveInv
      α with
    mul_inv_rev := fun a b => unop_injective <| mul_inv_rev _ _
    inv_eq_of_mul := fun a b h => unop_injective <| inv_eq_of_mul_eq_one_left <| congr_arg unop h }

@[to_additive AddOpposite.subtractionCommMonoid]
instance [DivisionCommMonoid α] : DivisionCommMonoid αᵐᵒᵖ :=
  { MulOpposite.divisionMonoid α, MulOpposite.commSemigroup α with }

@[to_additive]
instance [Group α] : Group αᵐᵒᵖ :=
  { MulOpposite.divInvMonoid α with
    mul_left_inv := fun x => unop_injective <| mul_inv_self <| unop x }

@[to_additive]
instance [CommGroup α] : CommGroup αᵐᵒᵖ :=
  { MulOpposite.group α, MulOpposite.commMonoid α with }

variable {α}

#print MulOpposite.op_natCast /-
@[simp, norm_cast, to_additive]
theorem op_natCast [NatCast α] (n : ℕ) : op (n : α) = n :=
  rfl
#align mul_opposite.op_nat_cast MulOpposite.op_natCast
#align add_opposite.op_nat_cast AddOpposite.op_natCast
-/

#print MulOpposite.op_intCast /-
@[simp, norm_cast, to_additive]
theorem op_intCast [IntCast α] (n : ℤ) : op (n : α) = n :=
  rfl
#align mul_opposite.op_int_cast MulOpposite.op_intCast
#align add_opposite.op_int_cast AddOpposite.op_intCast
-/

#print MulOpposite.unop_natCast /-
@[simp, norm_cast, to_additive]
theorem unop_natCast [NatCast α] (n : ℕ) : unop (n : αᵐᵒᵖ) = n :=
  rfl
#align mul_opposite.unop_nat_cast MulOpposite.unop_natCast
#align add_opposite.unop_nat_cast AddOpposite.unop_natCast
-/

#print MulOpposite.unop_intCast /-
@[simp, norm_cast, to_additive]
theorem unop_intCast [IntCast α] (n : ℤ) : unop (n : αᵐᵒᵖ) = n :=
  rfl
#align mul_opposite.unop_int_cast MulOpposite.unop_intCast
#align add_opposite.unop_int_cast AddOpposite.unop_intCast
-/

/- warning: mul_opposite.unop_div -> MulOpposite.unop_div is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DivInvMonoid.{u1} α] (x : MulOpposite.{u1} α) (y : MulOpposite.{u1} α), Eq.{succ u1} α (MulOpposite.unop.{u1} α (HDiv.hDiv.{u1, u1, u1} (MulOpposite.{u1} α) (MulOpposite.{u1} α) (MulOpposite.{u1} α) (instHDiv.{u1} (MulOpposite.{u1} α) (DivInvMonoid.toHasDiv.{u1} (MulOpposite.{u1} α) (MulOpposite.divInvMonoid.{u1} α _inst_1))) x y)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α _inst_1)))) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α _inst_1) (MulOpposite.unop.{u1} α y)) (MulOpposite.unop.{u1} α x))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DivInvMonoid.{u1} α] (x : MulOpposite.{u1} α) (y : MulOpposite.{u1} α), Eq.{succ u1} α (MulOpposite.unop.{u1} α (HDiv.hDiv.{u1, u1, u1} (MulOpposite.{u1} α) (MulOpposite.{u1} α) (MulOpposite.{u1} α) (instHDiv.{u1} (MulOpposite.{u1} α) (DivInvMonoid.toDiv.{u1} (MulOpposite.{u1} α) (MulOpposite.divInvMonoid.{u1} α _inst_1))) x y)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α _inst_1)))) (Inv.inv.{u1} α (DivInvMonoid.toInv.{u1} α _inst_1) (MulOpposite.unop.{u1} α y)) (MulOpposite.unop.{u1} α x))
Case conversion may be inaccurate. Consider using '#align mul_opposite.unop_div MulOpposite.unop_divₓ'. -/
@[simp, to_additive]
theorem unop_div [DivInvMonoid α] (x y : αᵐᵒᵖ) : unop (x / y) = (unop y)⁻¹ * unop x :=
  rfl
#align mul_opposite.unop_div MulOpposite.unop_div
#align add_opposite.unop_sub AddOpposite.unop_sub

/- warning: mul_opposite.op_div -> MulOpposite.op_div is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DivInvMonoid.{u1} α] (x : α) (y : α), Eq.{succ u1} (MulOpposite.{u1} α) (MulOpposite.op.{u1} α (HDiv.hDiv.{u1, u1, u1} α α α (instHDiv.{u1} α (DivInvMonoid.toHasDiv.{u1} α _inst_1)) x y)) (HMul.hMul.{u1, u1, u1} (MulOpposite.{u1} α) (MulOpposite.{u1} α) (MulOpposite.{u1} α) (instHMul.{u1} (MulOpposite.{u1} α) (MulOpposite.hasMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α _inst_1))))) (Inv.inv.{u1} (MulOpposite.{u1} α) (MulOpposite.hasInv.{u1} α (DivInvMonoid.toHasInv.{u1} α _inst_1)) (MulOpposite.op.{u1} α y)) (MulOpposite.op.{u1} α x))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DivInvMonoid.{u1} α] (x : α) (y : α), Eq.{succ u1} (MulOpposite.{u1} α) (MulOpposite.op.{u1} α (HDiv.hDiv.{u1, u1, u1} α α α (instHDiv.{u1} α (DivInvMonoid.toDiv.{u1} α _inst_1)) x y)) (HMul.hMul.{u1, u1, u1} (MulOpposite.{u1} α) (MulOpposite.{u1} α) (MulOpposite.{u1} α) (instHMul.{u1} (MulOpposite.{u1} α) (MulOpposite.mul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α _inst_1))))) (Inv.inv.{u1} (MulOpposite.{u1} α) (MulOpposite.inv.{u1} α (DivInvMonoid.toInv.{u1} α _inst_1)) (MulOpposite.op.{u1} α y)) (MulOpposite.op.{u1} α x))
Case conversion may be inaccurate. Consider using '#align mul_opposite.op_div MulOpposite.op_divₓ'. -/
@[simp, to_additive]
theorem op_div [DivInvMonoid α] (x y : α) : op (x / y) = (op y)⁻¹ * op x := by simp [div_eq_mul_inv]
#align mul_opposite.op_div MulOpposite.op_div
#align add_opposite.op_sub AddOpposite.op_sub

#print MulOpposite.semiconjBy_op /-
@[simp, to_additive]
theorem semiconjBy_op [Mul α] {a x y : α} : SemiconjBy (op a) (op y) (op x) ↔ SemiconjBy a x y := by
  simp only [SemiconjBy, ← op_mul, op_inj, eq_comm]
#align mul_opposite.semiconj_by_op MulOpposite.semiconjBy_op
#align add_opposite.semiconj_by_op AddOpposite.semiconjBy_op
-/

#print MulOpposite.semiconjBy_unop /-
@[simp, to_additive]
theorem semiconjBy_unop [Mul α] {a x y : αᵐᵒᵖ} :
    SemiconjBy (unop a) (unop y) (unop x) ↔ SemiconjBy a x y := by
  conv_rhs => rw [← op_unop a, ← op_unop x, ← op_unop y, semiconj_by_op]
#align mul_opposite.semiconj_by_unop MulOpposite.semiconjBy_unop
#align add_opposite.semiconj_by_unop AddOpposite.semiconjBy_unop
-/

#print SemiconjBy.op /-
@[to_additive]
theorem SemiconjBy.op [Mul α] {a x y : α} (h : SemiconjBy a x y) :
    SemiconjBy (op a) (op y) (op x) :=
  semiconjBy_op.2 h
#align semiconj_by.op SemiconjBy.op
#align add_semiconj_by.op AddSemiconjBy.op
-/

#print SemiconjBy.unop /-
@[to_additive]
theorem SemiconjBy.unop [Mul α] {a x y : αᵐᵒᵖ} (h : SemiconjBy a x y) :
    SemiconjBy (unop a) (unop y) (unop x) :=
  semiconjBy_unop.2 h
#align semiconj_by.unop SemiconjBy.unop
#align add_semiconj_by.unop AddSemiconjBy.unop
-/

#print Commute.op /-
@[to_additive]
theorem Commute.op [Mul α] {x y : α} (h : Commute x y) : Commute (op x) (op y) :=
  h.op
#align commute.op Commute.op
#align add_commute.op AddCommute.op
-/

#print MulOpposite.Commute.unop /-
@[to_additive]
theorem Commute.unop [Mul α] {x y : αᵐᵒᵖ} (h : Commute x y) : Commute (unop x) (unop y) :=
  h.unop
#align mul_opposite.commute.unop MulOpposite.Commute.unop
#align add_opposite.commute.unop AddOpposite.Commute.unop
-/

#print MulOpposite.commute_op /-
@[simp, to_additive]
theorem commute_op [Mul α] {x y : α} : Commute (op x) (op y) ↔ Commute x y :=
  semiconjBy_op
#align mul_opposite.commute_op MulOpposite.commute_op
#align add_opposite.commute_op AddOpposite.commute_op
-/

#print MulOpposite.commute_unop /-
@[simp, to_additive]
theorem commute_unop [Mul α] {x y : αᵐᵒᵖ} : Commute (unop x) (unop y) ↔ Commute x y :=
  semiconjBy_unop
#align mul_opposite.commute_unop MulOpposite.commute_unop
#align add_opposite.commute_unop AddOpposite.commute_unop
-/

#print MulOpposite.opAddEquiv /-
/-- The function `mul_opposite.op` is an additive equivalence. -/
@[simps (config :=
      { fullyApplied := false
        simpRhs := true })]
def opAddEquiv [Add α] : α ≃+ αᵐᵒᵖ :=
  { opEquiv with map_add' := fun a b => rfl }
#align mul_opposite.op_add_equiv MulOpposite.opAddEquiv
-/

#print MulOpposite.opAddEquiv_toEquiv /-
@[simp]
theorem opAddEquiv_toEquiv [Add α] : (opAddEquiv : α ≃+ αᵐᵒᵖ).toEquiv = opEquiv :=
  rfl
#align mul_opposite.op_add_equiv_to_equiv MulOpposite.opAddEquiv_toEquiv
-/

end MulOpposite

/-!
### Multiplicative structures on `αᵃᵒᵖ`
-/

namespace AddOpposite

instance [Semigroup α] : Semigroup αᵃᵒᵖ :=
  unop_injective.Semigroup _ fun x y => rfl

instance [LeftCancelSemigroup α] : LeftCancelSemigroup αᵃᵒᵖ :=
  unop_injective.LeftCancelSemigroup _ fun x y => rfl

instance [RightCancelSemigroup α] : RightCancelSemigroup αᵃᵒᵖ :=
  unop_injective.RightCancelSemigroup _ fun x y => rfl

instance [CommSemigroup α] : CommSemigroup αᵃᵒᵖ :=
  unop_injective.CommSemigroup _ fun x y => rfl

instance [MulOneClass α] : MulOneClass αᵃᵒᵖ :=
  unop_injective.MulOneClass _ rfl fun x y => rfl

instance {β} [Pow α β] : Pow αᵃᵒᵖ β where pow a b := op (unop a ^ b)

/- warning: add_opposite.op_pow -> AddOpposite.op_pow is a dubious translation:
lean 3 declaration is
  forall (α : Type.{u1}) {β : Type.{u2}} [_inst_1 : Pow.{u1, u2} α β] (a : α) (b : β), Eq.{succ u1} (AddOpposite.{u1} α) (AddOpposite.op.{u1} α (HPow.hPow.{u1, u2, u1} α β α (instHPow.{u1, u2} α β _inst_1) a b)) (HPow.hPow.{u1, u2, u1} (AddOpposite.{u1} α) β (AddOpposite.{u1} α) (instHPow.{u1, u2} (AddOpposite.{u1} α) β (AddOpposite.hasPow.{u1, u2} α β _inst_1)) (AddOpposite.op.{u1} α a) b)
but is expected to have type
  forall (α : Type.{u2}) {β : Type.{u1}} [_inst_1 : Pow.{u2, u1} α β] (a : α) (b : β), Eq.{succ u2} (AddOpposite.{u2} α) (AddOpposite.op.{u2} α (HPow.hPow.{u2, u1, u2} α β α (instHPow.{u2, u1} α β _inst_1) a b)) (HPow.hPow.{u2, u1, u2} (AddOpposite.{u2} α) β (AddOpposite.{u2} α) (instHPow.{u2, u1} (AddOpposite.{u2} α) β (AddOpposite.pow.{u2, u1} α β _inst_1)) (AddOpposite.op.{u2} α a) b)
Case conversion may be inaccurate. Consider using '#align add_opposite.op_pow AddOpposite.op_powₓ'. -/
@[simp]
theorem op_pow {β} [Pow α β] (a : α) (b : β) : op (a ^ b) = op a ^ b :=
  rfl
#align add_opposite.op_pow AddOpposite.op_pow

/- warning: add_opposite.unop_pow -> AddOpposite.unop_pow is a dubious translation:
lean 3 declaration is
  forall (α : Type.{u1}) {β : Type.{u2}} [_inst_1 : Pow.{u1, u2} α β] (a : AddOpposite.{u1} α) (b : β), Eq.{succ u1} α (AddOpposite.unop.{u1} α (HPow.hPow.{u1, u2, u1} (AddOpposite.{u1} α) β (AddOpposite.{u1} α) (instHPow.{u1, u2} (AddOpposite.{u1} α) β (AddOpposite.hasPow.{u1, u2} α β _inst_1)) a b)) (HPow.hPow.{u1, u2, u1} α β α (instHPow.{u1, u2} α β _inst_1) (AddOpposite.unop.{u1} α a) b)
but is expected to have type
  forall (α : Type.{u2}) {β : Type.{u1}} [_inst_1 : Pow.{u2, u1} α β] (a : AddOpposite.{u2} α) (b : β), Eq.{succ u2} α (AddOpposite.unop.{u2} α (HPow.hPow.{u2, u1, u2} (AddOpposite.{u2} α) β (AddOpposite.{u2} α) (instHPow.{u2, u1} (AddOpposite.{u2} α) β (AddOpposite.pow.{u2, u1} α β _inst_1)) a b)) (HPow.hPow.{u2, u1, u2} α β α (instHPow.{u2, u1} α β _inst_1) (AddOpposite.unop.{u2} α a) b)
Case conversion may be inaccurate. Consider using '#align add_opposite.unop_pow AddOpposite.unop_powₓ'. -/
@[simp]
theorem unop_pow {β} [Pow α β] (a : αᵃᵒᵖ) (b : β) : unop (a ^ b) = unop a ^ b :=
  rfl
#align add_opposite.unop_pow AddOpposite.unop_pow

instance [Monoid α] : Monoid αᵃᵒᵖ :=
  unop_injective.Monoid _ rfl (fun _ _ => rfl) fun _ _ => rfl

instance [CommMonoid α] : CommMonoid αᵃᵒᵖ :=
  unop_injective.CommMonoid _ rfl (fun _ _ => rfl) fun _ _ => rfl

instance [DivInvMonoid α] : DivInvMonoid αᵃᵒᵖ :=
  unop_injective.DivInvMonoid _ rfl (fun _ _ => rfl) (fun _ => rfl) (fun _ _ => rfl)
    (fun _ _ => rfl) fun _ _ => rfl

instance [Group α] : Group αᵃᵒᵖ :=
  unop_injective.Group _ rfl (fun _ _ => rfl) (fun _ => rfl) (fun _ _ => rfl) (fun _ _ => rfl)
    fun _ _ => rfl

instance [CommGroup α] : CommGroup αᵃᵒᵖ :=
  unop_injective.CommGroup _ rfl (fun _ _ => rfl) (fun _ => rfl) (fun _ _ => rfl) (fun _ _ => rfl)
    fun _ _ => rfl

-- NOTE: `add_monoid_with_one α → add_monoid_with_one αᵃᵒᵖ` does not hold
instance [AddCommMonoidWithOne α] : AddCommMonoidWithOne αᵃᵒᵖ :=
  { AddOpposite.addCommMonoid α, AddOpposite.hasOne,
    AddOpposite.hasNatCast
      _ with
    natCast_zero := show op ((0 : ℕ) : α) = 0 by rw [Nat.cast_zero, op_zero]
    natCast_succ := show ∀ n, op ((n + 1 : ℕ) : α) = op (n : ℕ) + 1 by simp [add_comm] }

instance [AddCommGroupWithOne α] : AddCommGroupWithOne αᵃᵒᵖ :=
  { AddOpposite.addCommMonoidWithOne _, AddOpposite.addCommGroup α,
    AddOpposite.hasIntCast
      α with
    intCast_ofNat := fun n => congr_arg op <| Int.cast_ofNat n
    intCast_negSucc := fun _ => congr_arg op <| Int.cast_negSucc _ }

variable {α}

#print AddOpposite.opMulEquiv /-
/-- The function `add_opposite.op` is a multiplicative equivalence. -/
@[simps (config :=
      { fullyApplied := false
        simpRhs := true })]
def opMulEquiv [Mul α] : α ≃* αᵃᵒᵖ :=
  { opEquiv with map_mul' := fun a b => rfl }
#align add_opposite.op_mul_equiv AddOpposite.opMulEquiv
-/

#print AddOpposite.opMulEquiv_toEquiv /-
@[simp]
theorem opMulEquiv_toEquiv [Mul α] : (opMulEquiv : α ≃* αᵃᵒᵖ).toEquiv = opEquiv :=
  rfl
#align add_opposite.op_mul_equiv_to_equiv AddOpposite.opMulEquiv_toEquiv
-/

end AddOpposite

open MulOpposite

/- warning: mul_equiv.inv' -> MulEquiv.inv' is a dubious translation:
lean 3 declaration is
  forall (G : Type.{u1}) [_inst_1 : DivisionMonoid.{u1} G], MulEquiv.{u1, u1} G (MulOpposite.{u1} G) (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (DivisionMonoid.toDivInvMonoid.{u1} G _inst_1)))) (MulOpposite.hasMul.{u1} G (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (DivisionMonoid.toDivInvMonoid.{u1} G _inst_1)))))
but is expected to have type
  forall (G : Type.{u1}) [_inst_1 : DivisionMonoid.{u1} G], MulEquiv.{u1, u1} G (MulOpposite.{u1} G) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (DivisionMonoid.toDivInvMonoid.{u1} G _inst_1)))) (MulOpposite.mul.{u1} G (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (DivisionMonoid.toDivInvMonoid.{u1} G _inst_1)))))
Case conversion may be inaccurate. Consider using '#align mul_equiv.inv' MulEquiv.inv'ₓ'. -/
/-- Inversion on a group is a `mul_equiv` to the opposite group. When `G` is commutative, there is
`mul_equiv.inv`. -/
@[to_additive
      "Negation on an additive group is an `add_equiv` to the opposite group. When `G`\nis commutative, there is `add_equiv.inv`.",
  simps (config :=
      { fullyApplied := false
        simpRhs := true })]
def MulEquiv.inv' (G : Type _) [DivisionMonoid G] : G ≃* Gᵐᵒᵖ :=
  { (Equiv.inv G).trans opEquiv with map_mul' := fun x y => unop_injective <| mul_inv_rev x y }
#align mul_equiv.inv' MulEquiv.inv'
#align add_equiv.neg' AddEquiv.neg'

#print MulHom.toOpposite /-
/-- A semigroup homomorphism `f : M →ₙ* N` such that `f x` commutes with `f y` for all `x, y`
defines a semigroup homomorphism to `Nᵐᵒᵖ`. -/
@[to_additive
      "An additive semigroup homomorphism `f : add_hom M N` such that `f x` additively\ncommutes with `f y` for all `x, y` defines an additive semigroup homomorphism to `Sᵃᵒᵖ`.",
  simps (config := { fullyApplied := false })]
def MulHom.toOpposite {M N : Type _} [Mul M] [Mul N] (f : M →ₙ* N)
    (hf : ∀ x y, Commute (f x) (f y)) : M →ₙ* Nᵐᵒᵖ
    where
  toFun := MulOpposite.op ∘ f
  map_mul' x y := by simp [(hf x y).Eq]
#align mul_hom.to_opposite MulHom.toOpposite
#align add_hom.to_opposite AddHom.toOpposite
-/

#print MulHom.fromOpposite /-
/-- A semigroup homomorphism `f : M →ₙ* N` such that `f x` commutes with `f y` for all `x, y`
defines a semigroup homomorphism from `Mᵐᵒᵖ`. -/
@[to_additive
      "An additive semigroup homomorphism `f : add_hom M N` such that `f x` additively\ncommutes with `f y` for all `x`, `y` defines an additive semigroup homomorphism from `Mᵃᵒᵖ`.",
  simps (config := { fullyApplied := false })]
def MulHom.fromOpposite {M N : Type _} [Mul M] [Mul N] (f : M →ₙ* N)
    (hf : ∀ x y, Commute (f x) (f y)) : Mᵐᵒᵖ →ₙ* N
    where
  toFun := f ∘ MulOpposite.unop
  map_mul' x y := (f.map_mul _ _).trans (hf _ _).Eq
#align mul_hom.from_opposite MulHom.fromOpposite
#align add_hom.from_opposite AddHom.fromOpposite
-/

/- warning: monoid_hom.to_opposite -> MonoidHom.toOpposite is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {N : Type.{u2}} [_inst_1 : MulOneClass.{u1} M] [_inst_2 : MulOneClass.{u2} N] (f : MonoidHom.{u1, u2} M N _inst_1 _inst_2), (forall (x : M) (y : M), Commute.{u2} N (MulOneClass.toHasMul.{u2} N _inst_2) (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) (fun (_x : MonoidHom.{u1, u2} M N _inst_1 _inst_2) => M -> N) (MonoidHom.hasCoeToFun.{u1, u2} M N _inst_1 _inst_2) f x) (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) (fun (_x : MonoidHom.{u1, u2} M N _inst_1 _inst_2) => M -> N) (MonoidHom.hasCoeToFun.{u1, u2} M N _inst_1 _inst_2) f y)) -> (MonoidHom.{u1, u2} M (MulOpposite.{u2} N) _inst_1 (MulOpposite.mulOneClass.{u2} N _inst_2))
but is expected to have type
  forall {M : Type.{u1}} {N : Type.{u2}} [_inst_1 : MulOneClass.{u1} M] [_inst_2 : MulOneClass.{u2} N] (f : MonoidHom.{u1, u2} M N _inst_1 _inst_2), (forall (x : M) (y : M), Commute.{u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) (MulOneClass.toMul.{u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) _inst_2) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M (fun (_x : M) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) _x) (MulHomClass.toFunLike.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M N (MulOneClass.toMul.{u1} M _inst_1) (MulOneClass.toMul.{u2} N _inst_2) (MonoidHomClass.toMulHomClass.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M N _inst_1 _inst_2 (MonoidHom.monoidHomClass.{u1, u2} M N _inst_1 _inst_2))) f x) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M (fun (_x : M) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) _x) (MulHomClass.toFunLike.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M N (MulOneClass.toMul.{u1} M _inst_1) (MulOneClass.toMul.{u2} N _inst_2) (MonoidHomClass.toMulHomClass.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M N _inst_1 _inst_2 (MonoidHom.monoidHomClass.{u1, u2} M N _inst_1 _inst_2))) f y)) -> (MonoidHom.{u1, u2} M (MulOpposite.{u2} N) _inst_1 (MulOpposite.mulOneClass.{u2} N _inst_2))
Case conversion may be inaccurate. Consider using '#align monoid_hom.to_opposite MonoidHom.toOppositeₓ'. -/
/-- A monoid homomorphism `f : M →* N` such that `f x` commutes with `f y` for all `x, y` defines
a monoid homomorphism to `Nᵐᵒᵖ`. -/
@[to_additive
      "An additive monoid homomorphism `f : M →+ N` such that `f x` additively commutes\nwith `f y` for all `x, y` defines an additive monoid homomorphism to `Sᵃᵒᵖ`.",
  simps (config := { fullyApplied := false })]
def MonoidHom.toOpposite {M N : Type _} [MulOneClass M] [MulOneClass N] (f : M →* N)
    (hf : ∀ x y, Commute (f x) (f y)) : M →* Nᵐᵒᵖ
    where
  toFun := MulOpposite.op ∘ f
  map_one' := congr_arg op f.map_one
  map_mul' x y := by simp [(hf x y).Eq]
#align monoid_hom.to_opposite MonoidHom.toOpposite
#align add_monoid_hom.to_opposite AddMonoidHom.toOpposite

/- warning: monoid_hom.from_opposite -> MonoidHom.fromOpposite is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {N : Type.{u2}} [_inst_1 : MulOneClass.{u1} M] [_inst_2 : MulOneClass.{u2} N] (f : MonoidHom.{u1, u2} M N _inst_1 _inst_2), (forall (x : M) (y : M), Commute.{u2} N (MulOneClass.toHasMul.{u2} N _inst_2) (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) (fun (_x : MonoidHom.{u1, u2} M N _inst_1 _inst_2) => M -> N) (MonoidHom.hasCoeToFun.{u1, u2} M N _inst_1 _inst_2) f x) (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) (fun (_x : MonoidHom.{u1, u2} M N _inst_1 _inst_2) => M -> N) (MonoidHom.hasCoeToFun.{u1, u2} M N _inst_1 _inst_2) f y)) -> (MonoidHom.{u1, u2} (MulOpposite.{u1} M) N (MulOpposite.mulOneClass.{u1} M _inst_1) _inst_2)
but is expected to have type
  forall {M : Type.{u1}} {N : Type.{u2}} [_inst_1 : MulOneClass.{u1} M] [_inst_2 : MulOneClass.{u2} N] (f : MonoidHom.{u1, u2} M N _inst_1 _inst_2), (forall (x : M) (y : M), Commute.{u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) (MulOneClass.toMul.{u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) _inst_2) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M (fun (_x : M) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) _x) (MulHomClass.toFunLike.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M N (MulOneClass.toMul.{u1} M _inst_1) (MulOneClass.toMul.{u2} N _inst_2) (MonoidHomClass.toMulHomClass.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M N _inst_1 _inst_2 (MonoidHom.monoidHomClass.{u1, u2} M N _inst_1 _inst_2))) f x) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M (fun (_x : M) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) _x) (MulHomClass.toFunLike.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M N (MulOneClass.toMul.{u1} M _inst_1) (MulOneClass.toMul.{u2} N _inst_2) (MonoidHomClass.toMulHomClass.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} M N _inst_1 _inst_2) M N _inst_1 _inst_2 (MonoidHom.monoidHomClass.{u1, u2} M N _inst_1 _inst_2))) f y)) -> (MonoidHom.{u1, u2} (MulOpposite.{u1} M) N (MulOpposite.mulOneClass.{u1} M _inst_1) _inst_2)
Case conversion may be inaccurate. Consider using '#align monoid_hom.from_opposite MonoidHom.fromOppositeₓ'. -/
/-- A monoid homomorphism `f : M →* N` such that `f x` commutes with `f y` for all `x, y` defines
a monoid homomorphism from `Mᵐᵒᵖ`. -/
@[to_additive
      "An additive monoid homomorphism `f : M →+ N` such that `f x` additively commutes\nwith `f y` for all `x`, `y` defines an additive monoid homomorphism from `Mᵃᵒᵖ`.",
  simps (config := { fullyApplied := false })]
def MonoidHom.fromOpposite {M N : Type _} [MulOneClass M] [MulOneClass N] (f : M →* N)
    (hf : ∀ x y, Commute (f x) (f y)) : Mᵐᵒᵖ →* N
    where
  toFun := f ∘ MulOpposite.unop
  map_one' := f.map_one
  map_mul' x y := (f.map_mul _ _).trans (hf _ _).Eq
#align monoid_hom.from_opposite MonoidHom.fromOpposite
#align add_monoid_hom.from_opposite AddMonoidHom.fromOpposite

/- warning: units.op_equiv -> Units.opEquiv is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} [_inst_1 : Monoid.{u1} M], MulEquiv.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toHasMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.mulOneClass.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.hasMul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toHasMul.{u1} (Units.{u1} M _inst_1) (Units.mulOneClass.{u1} M _inst_1)))
but is expected to have type
  forall {M : Type.{u1}} [_inst_1 : Monoid.{u1} M], MulEquiv.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1)))
Case conversion may be inaccurate. Consider using '#align units.op_equiv Units.opEquivₓ'. -/
/-- The units of the opposites are equivalent to the opposites of the units. -/
@[to_additive
      "The additive units of the additive opposites are equivalent to the additive opposites\nof the additive units."]
def Units.opEquiv {M} [Monoid M] : Mᵐᵒᵖˣ ≃* Mˣᵐᵒᵖ
    where
  toFun u := op ⟨unop u, unop ↑u⁻¹, op_injective u.4, op_injective u.3⟩
  invFun := MulOpposite.rec' fun u => ⟨op ↑u, op ↑u⁻¹, unop_injective <| u.4, unop_injective u.3⟩
  map_mul' x y := unop_injective <| Units.ext <| rfl
  left_inv x := Units.ext <| by simp
  right_inv x := unop_injective <| Units.ext <| rfl
#align units.op_equiv Units.opEquiv
#align add_units.op_equiv AddUnits.opEquiv

/- warning: units.coe_unop_op_equiv -> Units.coe_unop_opEquiv is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} [_inst_1 : Monoid.{u1} M] (u : Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)), Eq.{succ u1} M ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Units.{u1} M _inst_1) M (HasLiftT.mk.{succ u1, succ u1} (Units.{u1} M _inst_1) M (CoeTCₓ.coe.{succ u1, succ u1} (Units.{u1} M _inst_1) M (coeBase.{succ u1, succ u1} (Units.{u1} M _inst_1) M (Units.hasCoe.{u1} M _inst_1)))) (MulOpposite.unop.{u1} (Units.{u1} M _inst_1) (coeFn.{succ u1, succ u1} (MulEquiv.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toHasMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.mulOneClass.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.hasMul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toHasMul.{u1} (Units.{u1} M _inst_1) (Units.mulOneClass.{u1} M _inst_1)))) (fun (_x : MulEquiv.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toHasMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.mulOneClass.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.hasMul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toHasMul.{u1} (Units.{u1} M _inst_1) (Units.mulOneClass.{u1} M _inst_1)))) => (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) -> (MulOpposite.{u1} (Units.{u1} M _inst_1))) (MulEquiv.hasCoeToFun.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toHasMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.mulOneClass.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.hasMul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toHasMul.{u1} (Units.{u1} M _inst_1) (Units.mulOneClass.{u1} M _inst_1)))) (Units.opEquiv.{u1} M _inst_1) u))) (MulOpposite.unop.{u1} M ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} M) (HasLiftT.mk.{succ u1, succ u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} M) (CoeTCₓ.coe.{succ u1, succ u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} M) (coeBase.{succ u1, succ u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} M) (Units.hasCoe.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))))) u))
but is expected to have type
  forall {M : Type.{u1}} [_inst_1 : Monoid.{u1} M] (u : Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)), Eq.{succ u1} M (Units.val.{u1} M _inst_1 (MulOpposite.unop.{u1} (Units.{u1} M _inst_1) (FunLike.coe.{succ u1, succ u1, succ u1} (MulEquiv.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1)))) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (fun (_x : Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) => MulOpposite.{u1} (Units.{u1} M _inst_1)) _x) (EmbeddingLike.toFunLike.{succ u1, succ u1, succ u1} (MulEquiv.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1)))) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (EquivLike.toEmbeddingLike.{succ u1, succ u1, succ u1} (MulEquiv.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1)))) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulEquivClass.toEquivLike.{u1, u1, u1} (MulEquiv.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1)))) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1))) (MulEquiv.instMulEquivClassMulEquiv.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1))))))) (Units.opEquiv.{u1} M _inst_1) u))) (MulOpposite.unop.{u1} M (Units.val.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1) u))
Case conversion may be inaccurate. Consider using '#align units.coe_unop_op_equiv Units.coe_unop_opEquivₓ'. -/
@[simp, to_additive]
theorem Units.coe_unop_opEquiv {M} [Monoid M] (u : Mᵐᵒᵖˣ) :
    ((Units.opEquiv u).unop : M) = unop (u : Mᵐᵒᵖ) :=
  rfl
#align units.coe_unop_op_equiv Units.coe_unop_opEquiv
#align add_units.coe_unop_op_equiv AddUnits.coe_unop_opEquiv

/- warning: units.coe_op_equiv_symm -> Units.coe_opEquiv_symm is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} [_inst_1 : Monoid.{u1} M] (u : MulOpposite.{u1} (Units.{u1} M _inst_1)), Eq.{succ u1} (MulOpposite.{u1} M) ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} M) (HasLiftT.mk.{succ u1, succ u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} M) (CoeTCₓ.coe.{succ u1, succ u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} M) (coeBase.{succ u1, succ u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} M) (Units.hasCoe.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))))) (coeFn.{succ u1, succ u1} (MulEquiv.{u1, u1} (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.hasMul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toHasMul.{u1} (Units.{u1} M _inst_1) (Units.mulOneClass.{u1} M _inst_1))) (MulOneClass.toHasMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.mulOneClass.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)))) (fun (_x : MulEquiv.{u1, u1} (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.hasMul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toHasMul.{u1} (Units.{u1} M _inst_1) (Units.mulOneClass.{u1} M _inst_1))) (MulOneClass.toHasMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.mulOneClass.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)))) => (MulOpposite.{u1} (Units.{u1} M _inst_1)) -> (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulEquiv.hasCoeToFun.{u1, u1} (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.hasMul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toHasMul.{u1} (Units.{u1} M _inst_1) (Units.mulOneClass.{u1} M _inst_1))) (MulOneClass.toHasMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.mulOneClass.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)))) (MulEquiv.symm.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toHasMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.mulOneClass.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.hasMul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toHasMul.{u1} (Units.{u1} M _inst_1) (Units.mulOneClass.{u1} M _inst_1))) (Units.opEquiv.{u1} M _inst_1)) u)) (MulOpposite.op.{u1} M ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Units.{u1} M _inst_1) M (HasLiftT.mk.{succ u1, succ u1} (Units.{u1} M _inst_1) M (CoeTCₓ.coe.{succ u1, succ u1} (Units.{u1} M _inst_1) M (coeBase.{succ u1, succ u1} (Units.{u1} M _inst_1) M (Units.hasCoe.{u1} M _inst_1)))) (MulOpposite.unop.{u1} (Units.{u1} M _inst_1) u)))
but is expected to have type
  forall {M : Type.{u1}} [_inst_1 : Monoid.{u1} M] (u : MulOpposite.{u1} (Units.{u1} M _inst_1)), Eq.{succ u1} (MulOpposite.{u1} M) (Units.val.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1) (FunLike.coe.{succ u1, succ u1, succ u1} (MulEquiv.{u1, u1} (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1))) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)))) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (fun (_x : MulOpposite.{u1} (Units.{u1} M _inst_1)) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : MulOpposite.{u1} (Units.{u1} M _inst_1)) => Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) _x) (EmbeddingLike.toFunLike.{succ u1, succ u1, succ u1} (MulEquiv.{u1, u1} (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1))) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)))) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (EquivLike.toEmbeddingLike.{succ u1, succ u1, succ u1} (MulEquiv.{u1, u1} (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1))) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)))) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulEquivClass.toEquivLike.{u1, u1, u1} (MulEquiv.{u1, u1} (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1))) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)))) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1))) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulEquiv.instMulEquivClassMulEquiv.{u1, u1} (MulOpposite.{u1} (Units.{u1} M _inst_1)) (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1))) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))))))) (MulEquiv.symm.{u1, u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (MulOpposite.{u1} (Units.{u1} M _inst_1)) (MulOneClass.toMul.{u1} (Units.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1)) (Units.instMulOneClassUnits.{u1} (MulOpposite.{u1} M) (MulOpposite.monoid.{u1} M _inst_1))) (MulOpposite.mul.{u1} (Units.{u1} M _inst_1) (MulOneClass.toMul.{u1} (Units.{u1} M _inst_1) (Units.instMulOneClassUnits.{u1} M _inst_1))) (Units.opEquiv.{u1} M _inst_1)) u)) (MulOpposite.op.{u1} M (Units.val.{u1} M _inst_1 (MulOpposite.unop.{u1} (Units.{u1} M _inst_1) u)))
Case conversion may be inaccurate. Consider using '#align units.coe_op_equiv_symm Units.coe_opEquiv_symmₓ'. -/
@[simp, to_additive]
theorem Units.coe_opEquiv_symm {M} [Monoid M] (u : Mˣᵐᵒᵖ) :
    (Units.opEquiv.symm u : Mᵐᵒᵖ) = op (u.unop : M) :=
  rfl
#align units.coe_op_equiv_symm Units.coe_opEquiv_symm
#align add_units.coe_op_equiv_symm AddUnits.coe_opEquiv_symm

#print MulHom.op /-
/-- A semigroup homomorphism `M →ₙ* N` can equivalently be viewed as a semigroup homomorphism
`Mᵐᵒᵖ →ₙ* Nᵐᵒᵖ`. This is the action of the (fully faithful) `ᵐᵒᵖ`-functor on morphisms. -/
@[to_additive
      "An additive semigroup homomorphism `add_hom M N` can equivalently be viewed as an\nadditive semigroup homomorphism `add_hom Mᵃᵒᵖ Nᵃᵒᵖ`. This is the action of the (fully faithful)\n`ᵃᵒᵖ`-functor on morphisms.",
  simps]
def MulHom.op {M N} [Mul M] [Mul N] : (M →ₙ* N) ≃ (Mᵐᵒᵖ →ₙ* Nᵐᵒᵖ)
    where
  toFun f :=
    { toFun := op ∘ f ∘ unop
      map_mul' := fun x y => unop_injective (f.map_mul y.unop x.unop) }
  invFun f :=
    { toFun := unop ∘ f ∘ op
      map_mul' := fun x y => congr_arg unop (f.map_mul (op y) (op x)) }
  left_inv f := by
    ext
    rfl
  right_inv f := by
    ext x
    simp
#align mul_hom.op MulHom.op
#align add_hom.op AddHom.op
-/

#print MulHom.unop /-
/-- The 'unopposite' of a semigroup homomorphism `Mᵐᵒᵖ →ₙ* Nᵐᵒᵖ`. Inverse to `mul_hom.op`. -/
@[simp,
  to_additive
      "The 'unopposite' of an additive semigroup homomorphism `Mᵃᵒᵖ →ₙ+ Nᵃᵒᵖ`. Inverse\nto `add_hom.op`."]
def MulHom.unop {M N} [Mul M] [Mul N] : (Mᵐᵒᵖ →ₙ* Nᵐᵒᵖ) ≃ (M →ₙ* N) :=
  MulHom.op.symm
#align mul_hom.unop MulHom.unop
#align add_hom.unop AddHom.unop
-/

#print AddHom.mulOp /-
/-- An additive semigroup homomorphism `add_hom M N` can equivalently be viewed as an additive
homomorphism `add_hom Mᵐᵒᵖ Nᵐᵒᵖ`. This is the action of the (fully faithful) `ᵐᵒᵖ`-functor on
morphisms. -/
@[simps]
def AddHom.mulOp {M N} [Add M] [Add N] : AddHom M N ≃ AddHom Mᵐᵒᵖ Nᵐᵒᵖ
    where
  toFun f :=
    { toFun := op ∘ f ∘ unop
      map_add' := fun x y => unop_injective (f.map_add x.unop y.unop) }
  invFun f :=
    { toFun := unop ∘ f ∘ op
      map_add' := fun x y => congr_arg unop (f.map_add (op x) (op y)) }
  left_inv f := by
    ext
    rfl
  right_inv f := by
    ext
    simp
#align add_hom.mul_op AddHom.mulOp
-/

#print AddHom.mulUnop /-
/-- The 'unopposite' of an additive semigroup hom `αᵐᵒᵖ →+ βᵐᵒᵖ`. Inverse to
`add_hom.mul_op`. -/
@[simp]
def AddHom.mulUnop {α β} [Add α] [Add β] : AddHom αᵐᵒᵖ βᵐᵒᵖ ≃ AddHom α β :=
  AddHom.mulOp.symm
#align add_hom.mul_unop AddHom.mulUnop
-/

#print MonoidHom.op /-
/-- A monoid homomorphism `M →* N` can equivalently be viewed as a monoid homomorphism
`Mᵐᵒᵖ →* Nᵐᵒᵖ`. This is the action of the (fully faithful) `ᵐᵒᵖ`-functor on morphisms. -/
@[to_additive
      "An additive monoid homomorphism `M →+ N` can equivalently be viewed as an\nadditive monoid homomorphism `Mᵃᵒᵖ →+ Nᵃᵒᵖ`. This is the action of the (fully faithful)\n`ᵃᵒᵖ`-functor on morphisms.",
  simps]
def MonoidHom.op {M N} [MulOneClass M] [MulOneClass N] : (M →* N) ≃ (Mᵐᵒᵖ →* Nᵐᵒᵖ)
    where
  toFun f :=
    { toFun := op ∘ f ∘ unop
      map_one' := congr_arg op f.map_one
      map_mul' := fun x y => unop_injective (f.map_mul y.unop x.unop) }
  invFun f :=
    { toFun := unop ∘ f ∘ op
      map_one' := congr_arg unop f.map_one
      map_mul' := fun x y => congr_arg unop (f.map_mul (op y) (op x)) }
  left_inv f := by
    ext
    rfl
  right_inv f := by
    ext x
    simp
#align monoid_hom.op MonoidHom.op
#align add_monoid_hom.op AddMonoidHom.op
-/

#print MonoidHom.unop /-
/-- The 'unopposite' of a monoid homomorphism `Mᵐᵒᵖ →* Nᵐᵒᵖ`. Inverse to `monoid_hom.op`. -/
@[simp,
  to_additive
      "The 'unopposite' of an additive monoid homomorphism `Mᵃᵒᵖ →+ Nᵃᵒᵖ`. Inverse to\n`add_monoid_hom.op`."]
def MonoidHom.unop {M N} [MulOneClass M] [MulOneClass N] : (Mᵐᵒᵖ →* Nᵐᵒᵖ) ≃ (M →* N) :=
  MonoidHom.op.symm
#align monoid_hom.unop MonoidHom.unop
#align add_monoid_hom.unop AddMonoidHom.unop
-/

#print AddMonoidHom.mulOp /-
/-- An additive homomorphism `M →+ N` can equivalently be viewed as an additive homomorphism
`Mᵐᵒᵖ →+ Nᵐᵒᵖ`. This is the action of the (fully faithful) `ᵐᵒᵖ`-functor on morphisms. -/
@[simps]
def AddMonoidHom.mulOp {M N} [AddZeroClass M] [AddZeroClass N] : (M →+ N) ≃ (Mᵐᵒᵖ →+ Nᵐᵒᵖ)
    where
  toFun f :=
    { toFun := op ∘ f ∘ unop
      map_zero' := unop_injective f.map_zero
      map_add' := fun x y => unop_injective (f.map_add x.unop y.unop) }
  invFun f :=
    { toFun := unop ∘ f ∘ op
      map_zero' := congr_arg unop f.map_zero
      map_add' := fun x y => congr_arg unop (f.map_add (op x) (op y)) }
  left_inv f := by
    ext
    rfl
  right_inv f := by
    ext
    simp
#align add_monoid_hom.mul_op AddMonoidHom.mulOp
-/

#print AddMonoidHom.mulUnop /-
/-- The 'unopposite' of an additive monoid hom `αᵐᵒᵖ →+ βᵐᵒᵖ`. Inverse to
`add_monoid_hom.mul_op`. -/
@[simp]
def AddMonoidHom.mulUnop {α β} [AddZeroClass α] [AddZeroClass β] : (αᵐᵒᵖ →+ βᵐᵒᵖ) ≃ (α →+ β) :=
  AddMonoidHom.mulOp.symm
#align add_monoid_hom.mul_unop AddMonoidHom.mulUnop
-/

#print AddEquiv.mulOp /-
/-- A iso `α ≃+ β` can equivalently be viewed as an iso `αᵐᵒᵖ ≃+ βᵐᵒᵖ`. -/
@[simps]
def AddEquiv.mulOp {α β} [Add α] [Add β] : α ≃+ β ≃ (αᵐᵒᵖ ≃+ βᵐᵒᵖ)
    where
  toFun f := opAddEquiv.symm.trans (f.trans opAddEquiv)
  invFun f := opAddEquiv.trans (f.trans opAddEquiv.symm)
  left_inv f := by
    ext
    rfl
  right_inv f := by
    ext
    simp
#align add_equiv.mul_op AddEquiv.mulOp
-/

#print AddEquiv.mulUnop /-
/-- The 'unopposite' of an iso `αᵐᵒᵖ ≃+ βᵐᵒᵖ`. Inverse to `add_equiv.mul_op`. -/
@[simp]
def AddEquiv.mulUnop {α β} [Add α] [Add β] : αᵐᵒᵖ ≃+ βᵐᵒᵖ ≃ (α ≃+ β) :=
  AddEquiv.mulOp.symm
#align add_equiv.mul_unop AddEquiv.mulUnop
-/

#print MulEquiv.op /-
/-- A iso `α ≃* β` can equivalently be viewed as an iso `αᵐᵒᵖ ≃* βᵐᵒᵖ`. -/
@[to_additive "A iso `α ≃+ β` can equivalently be viewed as an iso `αᵃᵒᵖ ≃+ βᵃᵒᵖ`.", simps]
def MulEquiv.op {α β} [Mul α] [Mul β] : α ≃* β ≃ (αᵐᵒᵖ ≃* βᵐᵒᵖ)
    where
  toFun f :=
    { toFun := op ∘ f ∘ unop
      invFun := op ∘ f.symm ∘ unop
      left_inv := fun x => unop_injective (f.symm_apply_apply x.unop)
      right_inv := fun x => unop_injective (f.apply_symm_apply x.unop)
      map_mul' := fun x y => unop_injective (f.map_mul y.unop x.unop) }
  invFun f :=
    { toFun := unop ∘ f ∘ op
      invFun := unop ∘ f.symm ∘ op
      left_inv := fun x => by simp
      right_inv := fun x => by simp
      map_mul' := fun x y => congr_arg unop (f.map_mul (op y) (op x)) }
  left_inv f := by
    ext
    rfl
  right_inv f := by
    ext
    simp
#align mul_equiv.op MulEquiv.op
#align add_equiv.op AddEquiv.op
-/

#print MulEquiv.unop /-
/-- The 'unopposite' of an iso `αᵐᵒᵖ ≃* βᵐᵒᵖ`. Inverse to `mul_equiv.op`. -/
@[simp, to_additive "The 'unopposite' of an iso `αᵃᵒᵖ ≃+ βᵃᵒᵖ`. Inverse to `add_equiv.op`."]
def MulEquiv.unop {α β} [Mul α] [Mul β] : αᵐᵒᵖ ≃* βᵐᵒᵖ ≃ (α ≃* β) :=
  MulEquiv.op.symm
#align mul_equiv.unop MulEquiv.unop
#align add_equiv.unop AddEquiv.unop
-/

section Ext

/- warning: add_monoid_hom.mul_op_ext -> AddMonoidHom.mul_op_ext is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : AddZeroClass.{u1} α] [_inst_2 : AddZeroClass.{u2} β] (f : AddMonoidHom.{u1, u2} (MulOpposite.{u1} α) β (MulOpposite.addZeroClass.{u1} α _inst_1) _inst_2) (g : AddMonoidHom.{u1, u2} (MulOpposite.{u1} α) β (MulOpposite.addZeroClass.{u1} α _inst_1) _inst_2), (Eq.{max (succ u2) (succ u1)} (AddMonoidHom.{u1, u2} α β _inst_1 _inst_2) (AddMonoidHom.comp.{u1, u1, u2} α (MulOpposite.{u1} α) β _inst_1 (MulOpposite.addZeroClass.{u1} α _inst_1) _inst_2 f (AddEquiv.toAddMonoidHom.{u1, u1} α (MulOpposite.{u1} α) _inst_1 (MulOpposite.addZeroClass.{u1} α _inst_1) (MulOpposite.opAddEquiv.{u1} α (AddZeroClass.toHasAdd.{u1} α _inst_1)))) (AddMonoidHom.comp.{u1, u1, u2} α (MulOpposite.{u1} α) β _inst_1 (MulOpposite.addZeroClass.{u1} α _inst_1) _inst_2 g (AddEquiv.toAddMonoidHom.{u1, u1} α (MulOpposite.{u1} α) _inst_1 (MulOpposite.addZeroClass.{u1} α _inst_1) (MulOpposite.opAddEquiv.{u1} α (AddZeroClass.toHasAdd.{u1} α _inst_1))))) -> (Eq.{max (succ u2) (succ u1)} (AddMonoidHom.{u1, u2} (MulOpposite.{u1} α) β (MulOpposite.addZeroClass.{u1} α _inst_1) _inst_2) f g)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : AddZeroClass.{u2} α] [_inst_2 : AddZeroClass.{u1} β] (f : AddMonoidHom.{u2, u1} (MulOpposite.{u2} α) β (MulOpposite.addZeroClass.{u2} α _inst_1) _inst_2) (g : AddMonoidHom.{u2, u1} (MulOpposite.{u2} α) β (MulOpposite.addZeroClass.{u2} α _inst_1) _inst_2), (Eq.{max (succ u2) (succ u1)} (AddMonoidHom.{u2, u1} α β _inst_1 _inst_2) (AddMonoidHom.comp.{u2, u2, u1} α (MulOpposite.{u2} α) β _inst_1 (MulOpposite.addZeroClass.{u2} α _inst_1) _inst_2 f (AddEquiv.toAddMonoidHom.{u2, u2} α (MulOpposite.{u2} α) _inst_1 (MulOpposite.addZeroClass.{u2} α _inst_1) (MulOpposite.opAddEquiv.{u2} α (AddZeroClass.toAdd.{u2} α _inst_1)))) (AddMonoidHom.comp.{u2, u2, u1} α (MulOpposite.{u2} α) β _inst_1 (MulOpposite.addZeroClass.{u2} α _inst_1) _inst_2 g (AddEquiv.toAddMonoidHom.{u2, u2} α (MulOpposite.{u2} α) _inst_1 (MulOpposite.addZeroClass.{u2} α _inst_1) (MulOpposite.opAddEquiv.{u2} α (AddZeroClass.toAdd.{u2} α _inst_1))))) -> (Eq.{max (succ u2) (succ u1)} (AddMonoidHom.{u2, u1} (MulOpposite.{u2} α) β (MulOpposite.addZeroClass.{u2} α _inst_1) _inst_2) f g)
Case conversion may be inaccurate. Consider using '#align add_monoid_hom.mul_op_ext AddMonoidHom.mul_op_extₓ'. -/
/-- This ext lemma change equalities on `αᵐᵒᵖ →+ β` to equalities on `α →+ β`.
This is useful because there are often ext lemmas for specific `α`s that will apply
to an equality of `α →+ β` such as `finsupp.add_hom_ext'`. -/
@[ext]
theorem AddMonoidHom.mul_op_ext {α β} [AddZeroClass α] [AddZeroClass β] (f g : αᵐᵒᵖ →+ β)
    (h :
      f.comp (opAddEquiv : α ≃+ αᵐᵒᵖ).toAddMonoidHom =
        g.comp (opAddEquiv : α ≃+ αᵐᵒᵖ).toAddMonoidHom) :
    f = g :=
  AddMonoidHom.ext <| MulOpposite.rec' fun x => (AddMonoidHom.congr_fun h : _) x
#align add_monoid_hom.mul_op_ext AddMonoidHom.mul_op_ext

end Ext


/-
Copyright (c) 2022 Mathieu Chanavat. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mathieu Chanavat
-/
import category_theory.limits.shapes.finite_limits
import category_theory.closed.cartesian
import category_theory.subobject.basic

import order.heyting.basic

/-!
# Subobject classifier for elementary topos

Useful api to work wiht pullback inside a topos
-/

open category_theory category_theory.category category_theory.limits

universes v u

noncomputable theory

variables {C : Type u} [category.{v} C] [has_terminal.{v} C]

variables {U X Y : C}

structure classifying_pullback (truth : ⊤_ C ⟶ Y) (f : U ⟶ X) (χ : X ⟶ Y)  :=
(comm : f ≫ χ = (terminal.from U) ≫ truth)
(is_pb : is_limit (pullback_cone.mk _ _ comm))

attribute [reassoc] classifying_pullback.comm

abbreviation classifying {Ω U X : C} (truth : ⊤_ C ⟶ Ω) (f : U ⟶ X) (χ : X ⟶ Ω) := classifying_pullback truth f χ

structure is_subobject_classifier {Ω : C} (truth : ⊤_ C ⟶ Ω) :=
(classifier_of : ∀ {U X} (f : U ⟶ X) [mono.{v} f], X ⟶ Ω)
(classifies' : ∀ {U X} (f : U ⟶ X) [mono f], classifying truth f (classifier_of f))
(uniquely' : ∀ {U X} (f : U ⟶ X) [mono f] (χ₁ : X ⟶ Ω), classifying truth f χ₁ → classifier_of f = χ₁)

variable (C)

/--
A category has a subobject classifier if there is a monomorphism `truth` which is a
subobject classifier.
-/
class has_subobject_classifier :=
(Ω : C)
(truth : ⊤_ C ⟶ Ω)
[truth_mono : mono.{v} truth]
(is_subobj_classifier : is_subobject_classifier truth)

variables [has_subobject_classifier.{v} C]

/-! Convenience interface to the `has_subobject_classifier` class. -/
namespace classifier

/-- Convenience notation for the classifier target given the typeclass `has_subobject_classifier`. -/
def Ω : C := has_subobject_classifier.Ω.{v}
/-- Convenience notation for the classifier given the typeclass `has_subobject_classifier`. -/
def truth : ⊤_ C ⟶ Ω C := has_subobject_classifier.truth
/-- From the typeclass `has_subobject_classifier`, show that the classifier `truth` is a monomorphism. -/
instance truth_mono : mono (truth C) := has_subobject_classifier.truth_mono
/-- The subobject classifier given by `has_subobject_classifier` is actually a classifier. -/
def subobj_classifier_is_subobj_classifier : is_subobject_classifier (truth C) := has_subobject_classifier.is_subobj_classifier

variable {C}
def classifier_of {U X : C} (f : U ⟶ X) [mono f] : X ⟶ Ω C :=
(subobj_classifier_is_subobj_classifier C).classifier_of f
def classifies {U X : C} (f : U ⟶ X) [mono f] : classifying (truth C) f (classifier_of f) :=
(subobj_classifier_is_subobj_classifier C).classifies' f
lemma uniquely {U X : C} (f : U ⟶ X) [mono f] (χ₁ : X ⟶ Ω C) (hχ : classifying (truth C) f χ₁) : classifier_of f = χ₁ :=
(subobj_classifier_is_subobj_classifier C).uniquely' f χ₁ hχ

lemma classifier.comm {U X : C} (f : U ⟶ X) [mono f] :
  f ≫ (classifier_of f) = terminal.from _ ≫ truth C :=
classifying_pullback.comm (classifies _)

lemma classifier.is_pb {U X : C} (f : U ⟶ X) [mono f] :
  is_limit (pullback_cone.mk _ _ (classifier.comm f)) := classifying_pullback.is_pb (classifies _)

end classifier

/- If we have σ : X → Ω then we have the following pullback, we call { σ } the canonical subobject
  { σ } -> ⊤
    |      |
    X ---> Ω
-/

variable {C}
notation `s{` σ `}s` := pullback σ (classifier.truth _)

open classifier

/- true_X from McLane -/
abbreviation lift_truth (X : C) : X ⟶ Ω C := terminal.from X ≫ truth C

abbreviation canonical_incl {X : C} [has_pullbacks C] (σ : X ⟶ Ω C) : s{ σ }s ⟶ X := pullback.fst

def canonical_incl_of_mono {X Y : C} [has_pullbacks C] (m : X ⟶ Y) [mono m] :
  s{ classifier_of m }s ⟶ Y :=
canonical_incl (classifier_of m)

variables [has_pullbacks C] (σ : X ⟶ Ω C)

instance canonical_incl_mono : mono (canonical_incl σ) := pullback.fst_of_mono

lemma canonical_incl_comm : canonical_incl σ ≫ σ = terminal.from s{ σ }s ≫ truth C :=
begin
  convert pullback.condition
end

abbreviation canonical_sub : subobject X := subobject.mk (canonical_incl σ)

def canonical_sub_iso_canonical : ↑(canonical_sub σ) ≅ s{ σ }s :=
begin
  apply subobject.iso_of_eq_mk _ (canonical_incl σ), refl
end

def canonical_iso_canonical_sub : s{ σ }s ≅ ↑(canonical_sub σ) :=
(canonical_sub_iso_canonical σ).symm

lemma sub_eq_canonical_sub_of_classifier (S : subobject X) :
  canonical_sub (classifier_of S.arrow) = S :=
begin
  ext1,
  exact is_limit.cone_point_unique_up_to_iso_hom_comp
    (pullback_is_pullback _ _) (classifier.is_pb S.arrow) walking_cospan.left
end

def pb_cone_of_canonical_sub_arrow : pullback_cone σ (truth C) :=
pullback_cone.mk (canonical_sub σ).arrow (terminal.from _)
(by { rw [←subobject.underlying_iso_hom_comp_eq_mk, assoc,
          canonical_incl_comm, ←assoc, terminal.comp_from] })

lemma pb_cone_of_canonical_sub_arrow_X :
  (pb_cone_of_canonical_sub_arrow σ).X = ↑(canonical_sub σ) := rfl

lemma is_pullback_canonical_arrow :
  is_limit (pb_cone_of_canonical_sub_arrow σ) :=
begin
  apply is_limit.of_iso_limit (pullback_is_pullback σ (truth C)),
  symmetry,
  refine pullback_cone.ext (subobject.underlying_iso (canonical_incl σ)) _
    (is_terminal.hom_ext (terminal_is_terminal) _ _),
  symmetry, rw [pullback_cone.mk_fst, subobject.underlying_iso_hom_comp_eq_mk], refl
end

lemma canonical_is_pullback :
  is_limit (pullback_cone.mk (canonical_incl σ) (terminal.from _) (canonical_incl_comm σ)) :=
begin
 convert pullback_is_pullback _ _,
  apply is_terminal.hom_ext terminal_is_terminal,
end

lemma canonical_incl_classifies : classifying (truth C) (canonical_incl σ) σ :=
{ comm := canonical_incl_comm σ,
  is_pb := canonical_is_pullback σ }

@[simp] lemma classifier_of_canonical_incl_eq_self : classifier_of (canonical_incl σ) = σ :=
uniquely _ _ (canonical_incl_classifies σ)

/- The truth classifies the identity -/
variable (C)

lemma terminal_from_self_is_id : terminal.from (⊤_ C) = 𝟙 (⊤_ C) :=
is_terminal.from_self terminal_is_terminal

lemma truth_classifies_id : classifying (truth C) (𝟙 (⊤_ C)) (truth C) :=
{ comm := by rw (terminal_from_self_is_id ),
  is_pb :=
  begin
    conv in (terminal.from (⊤_ C)) {rw terminal_from_self_is_id },
    exact pullback_cone.is_limit_mk_id_id (truth C)
  end
}

lemma truth_classifies_id.comm : 𝟙 (⊤_ C) ≫ (truth C) = 𝟙 (⊤_ C) ≫ (truth C) := by simp
lemma truth_classifies_id.is_pb :
  is_limit (pullback_cone.mk (𝟙 (⊤_ C)) (𝟙 (⊤_ C)) (truth_classifies_id.comm C)) :=
begin
  have h := (truth_classifies_id C).is_pb,
  conv at h in (terminal.from (⊤_ C)) {rw terminal_from_self_is_id},
  assumption
end


/-
Copyright (c) 2020 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker

Basic topological facts (limits and continuity) about `floor`,
`ceil` and `fract` in a `floor_ring`.
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.algebra.ordered
import Mathlib.algebra.floor
import Mathlib.PostPort

universes u_1 u_2 u_3

namespace Mathlib

theorem tendsto_floor_at_top {α : Type u_1} [linear_ordered_ring α] [floor_ring α] : filter.tendsto floor filter.at_top filter.at_top := sorry

theorem tendsto_floor_at_bot {α : Type u_1} [linear_ordered_ring α] [floor_ring α] : filter.tendsto floor filter.at_bot filter.at_bot :=
  monotone.tendsto_at_bot_at_bot (fun (a b : α) (hab : a ≤ b) => floor_mono hab)
    fun (b : ℤ) => Exists.intro (↑b) (eq.mpr (id (Eq._oldrec (Eq.refl (floor ↑b ≤ b)) (floor_coe b))) (le_refl b))

theorem tendsto_ceil_at_top {α : Type u_1} [linear_ordered_ring α] [floor_ring α] : filter.tendsto ceil filter.at_top filter.at_top :=
  filter.tendsto.comp filter.tendsto_neg_at_bot_at_top
    (filter.tendsto.comp tendsto_floor_at_bot filter.tendsto_neg_at_top_at_bot)

theorem tendsto_ceil_at_bot {α : Type u_1} [linear_ordered_ring α] [floor_ring α] : filter.tendsto ceil filter.at_bot filter.at_bot :=
  filter.tendsto.comp filter.tendsto_neg_at_top_at_bot
    (filter.tendsto.comp tendsto_floor_at_top filter.tendsto_neg_at_bot_at_top)

theorem continuous_on_floor {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] (n : ℤ) : continuous_on (fun (x : α) => ↑(floor x)) (set.Ico (↑n) (↑n + 1)) :=
  iff.mpr (continuous_on_congr (floor_eq_on_Ico' n)) continuous_on_const

theorem continuous_on_ceil {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] (n : ℤ) : continuous_on (fun (x : α) => ↑(ceil x)) (set.Ioc (↑n - 1) ↑n) :=
  iff.mpr (continuous_on_congr (ceil_eq_on_Ioc' n)) continuous_on_const

theorem tendsto_floor_right' {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] (n : ℤ) : filter.tendsto (fun (x : α) => ↑(floor x)) (nhds_within (↑n) (set.Ici ↑n)) (nhds ↑n) := sorry

theorem tendsto_ceil_left' {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] (n : ℤ) : filter.tendsto (fun (x : α) => ↑(ceil x)) (nhds_within (↑n) (set.Iic ↑n)) (nhds ↑n) := sorry

theorem tendsto_floor_right {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] (n : ℤ) : filter.tendsto (fun (x : α) => ↑(floor x)) (nhds_within (↑n) (set.Ici ↑n)) (nhds_within (↑n) (set.Ici ↑n)) := sorry

theorem tendsto_ceil_left {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] (n : ℤ) : filter.tendsto (fun (x : α) => ↑(ceil x)) (nhds_within (↑n) (set.Iic ↑n)) (nhds_within (↑n) (set.Iic ↑n)) := sorry

theorem tendsto_floor_left {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] (n : ℤ) : filter.tendsto (fun (x : α) => ↑(floor x)) (nhds_within (↑n) (set.Iio ↑n)) (nhds_within (↑n - 1) (set.Iic (↑n - 1))) := sorry

theorem tendsto_ceil_right {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] (n : ℤ) : filter.tendsto (fun (x : α) => ↑(ceil x)) (nhds_within (↑n) (set.Ioi ↑n)) (nhds_within (↑n + 1) (set.Ici (↑n + 1))) := sorry

theorem tendsto_floor_left' {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] (n : ℤ) : filter.tendsto (fun (x : α) => ↑(floor x)) (nhds_within (↑n) (set.Iio ↑n)) (nhds (↑n - 1)) := sorry

theorem tendsto_ceil_right' {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] (n : ℤ) : filter.tendsto (fun (x : α) => ↑(ceil x)) (nhds_within (↑n) (set.Ioi ↑n)) (nhds (↑n + 1)) := sorry

theorem continuous_on_fract {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [topological_add_group α] (n : ℤ) : continuous_on fract (set.Ico (↑n) (↑n + 1)) :=
  continuous_on.sub continuous_on_id (continuous_on_floor n)

theorem tendsto_fract_left' {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] [topological_add_group α] (n : ℤ) : filter.tendsto fract (nhds_within (↑n) (set.Iio ↑n)) (nhds 1) := sorry

theorem tendsto_fract_left {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] [topological_add_group α] (n : ℤ) : filter.tendsto fract (nhds_within (↑n) (set.Iio ↑n)) (nhds_within 1 (set.Iio 1)) :=
  tendsto_nhds_within_of_tendsto_nhds_of_eventually_within fract (tendsto_fract_left' n)
    (filter.eventually_of_forall fract_lt_one)

theorem tendsto_fract_right' {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] [topological_add_group α] (n : ℤ) : filter.tendsto fract (nhds_within (↑n) (set.Ici ↑n)) (nhds 0) := sorry

theorem tendsto_fract_right {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] [order_closed_topology α] [topological_add_group α] (n : ℤ) : filter.tendsto fract (nhds_within (↑n) (set.Ici ↑n)) (nhds_within 0 (set.Ici 0)) :=
  tendsto_nhds_within_of_tendsto_nhds_of_eventually_within fract (tendsto_fract_right' n)
    (filter.eventually_of_forall fract_nonneg)

theorem continuous_on.comp_fract' {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] {β : Type u_2} {γ : Type u_3} [order_topology α] [topological_add_group α] [topological_space β] [topological_space γ] {f : β → α → γ} (h : continuous_on (function.uncurry f) (set.prod set.univ (set.Icc 0 1))) (hf : ∀ (s : β), f s 0 = f s 1) : continuous fun (st : β × α) => f (prod.fst st) (fract (prod.snd st)) := sorry

theorem continuous_on.comp_fract {α : Type u_1} [linear_ordered_ring α] [floor_ring α] [topological_space α] {β : Type u_2} [order_topology α] [topological_add_group α] [topological_space β] {f : α → β} (h : continuous_on f (set.Icc 0 1)) (hf : f 0 = f 1) : continuous (f ∘ fract) := sorry