/-
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 logic.equiv.list
! leanprover-community/mathlib commit d11893b411025250c8e61ff2f12ccbd7ee35ab15
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Finset.Sort
import Mathbin.Data.Vector.Basic
import Mathbin.Logic.Denumerable

/-!
# Equivalences involving `list`-like types

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

This file defines some additional constructive equivalences using `encodable` and the pairing
function on `ℕ`.
-/

open Nat List

namespace Encodable

variable {α : Type _}

section List

variable [Encodable α]

#print Encodable.encodeList /-
/-- Explicit encoding function for `list α` -/
def encodeList : List α → ℕ
  | [] => 0
  | a :: l => succ (pair (encode a) (encode_list l))
#align encodable.encode_list Encodable.encodeList
-/

#print Encodable.decodeList /-
/-- Explicit decoding function for `list α` -/
def decodeList : ℕ → Option (List α)
  | 0 => some []
  | succ v =>
    match unpair v, unpair_right_le v with
    | (v₁, v₂), h =>
      have : v₂ < succ v := lt_succ_of_le h
      (· :: ·) <$> decode α v₁ <*> decode_list v₂
#align encodable.decode_list Encodable.decodeList
-/

#print List.encodable /-
/-- If `α` is encodable, then so is `list α`. This uses the `mkpair` and `unpair` functions from
`data.nat.pairing`. -/
instance List.encodable : Encodable (List α) :=
  ⟨encodeList, decodeList, fun l => by
    induction' l with a l IH <;> simp [encode_list, decode_list, unpair_mkpair, encodek, *]⟩
#align list.encodable List.encodable
-/

#print List.countable /-
instance List.countable {α : Type _} [Countable α] : Countable (List α) :=
  by
  haveI := Encodable.ofCountable α
  infer_instance
#align list.countable List.countable
-/

#print Encodable.encode_list_nil /-
@[simp]
theorem encode_list_nil : encode (@nil α) = 0 :=
  rfl
#align encodable.encode_list_nil Encodable.encode_list_nil
-/

#print Encodable.encode_list_cons /-
@[simp]
theorem encode_list_cons (a : α) (l : List α) :
    encode (a :: l) = succ (pair (encode a) (encode l)) :=
  rfl
#align encodable.encode_list_cons Encodable.encode_list_cons
-/

#print Encodable.decode_list_zero /-
@[simp]
theorem decode_list_zero : decode (List α) 0 = some [] :=
  show decodeList 0 = some [] by rw [decode_list]
#align encodable.decode_list_zero Encodable.decode_list_zero
-/

/- warning: encodable.decode_list_succ -> Encodable.decode_list_succ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Encodable.{u1} α] (v : Nat), Eq.{succ u1} (Option.{u1} (List.{u1} α)) (Encodable.decode.{u1} (List.{u1} α) (List.encodable.{u1} α _inst_1) (Nat.succ v)) (Seq.seq.{u1, u1} Option.{u1} (Applicative.toHasSeq.{u1, u1} Option.{u1} (Monad.toApplicative.{u1, u1} Option.{u1} Option.monad.{u1})) (List.{u1} α) (List.{u1} α) (Functor.map.{u1, u1} Option.{u1} (Traversable.toFunctor.{u1} Option.{u1} Option.traversable.{u1}) α ((List.{u1} α) -> (List.{u1} α)) (fun (_x : α) (_y : List.{u1} α) => List.cons.{u1} α _x _y) (Encodable.decode.{u1} α _inst_1 (Prod.fst.{0, 0} Nat Nat (Nat.unpair v)))) (Encodable.decode.{u1} (List.{u1} α) (List.encodable.{u1} α _inst_1) (Prod.snd.{0, 0} Nat Nat (Nat.unpair v))))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Encodable.{u1} α] (v : Nat), Eq.{succ u1} (Option.{u1} (List.{u1} α)) (Encodable.decode.{u1} (List.{u1} α) (List.encodable.{u1} α _inst_1) (Nat.succ v)) (Seq.seq.{u1, u1} Option.{u1} (Applicative.toSeq.{u1, u1} Option.{u1} (Alternative.toApplicative.{u1, u1} Option.{u1} instAlternativeOption.{u1})) (List.{u1} α) (List.{u1} α) (Functor.map.{u1, u1} Option.{u1} instFunctorOption.{u1} α ((List.{u1} α) -> (List.{u1} α)) (fun (_x : α) (_y : List.{u1} α) => List.cons.{u1} α _x _y) (Encodable.decode.{u1} α _inst_1 (Prod.fst.{0, 0} Nat Nat (Nat.unpair v)))) (fun (x._@.Mathlib.Logic.Equiv.List._hyg.562 : Unit) => Encodable.decode.{u1} (List.{u1} α) (List.encodable.{u1} α _inst_1) (Prod.snd.{0, 0} Nat Nat (Nat.unpair v))))
Case conversion may be inaccurate. Consider using '#align encodable.decode_list_succ Encodable.decode_list_succₓ'. -/
@[simp]
theorem decode_list_succ (v : ℕ) :
    decode (List α) (succ v) = (· :: ·) <$> decode α v.unpair.1 <*> decode (List α) v.unpair.2 :=
  show decodeList (succ v) = _ by
    cases' e : unpair v with v₁ v₂
    simp [decode_list, e]; rfl
#align encodable.decode_list_succ Encodable.decode_list_succ

#print Encodable.length_le_encode /-
theorem length_le_encode : ∀ l : List α, length l ≤ encode l
  | [] => zero_le _
  | a :: l => succ_le_succ <| (length_le_encode l).trans (right_le_pair _ _)
#align encodable.length_le_encode Encodable.length_le_encode
-/

end List

section Finset

variable [Encodable α]

private def enle : α → α → Prop :=
  encode ⁻¹'o (· ≤ ·)
#align encodable.enle encodable.enle

private theorem enle.is_linear_order : IsLinearOrder α Enle :=
  (RelEmbedding.preimage ⟨encode, encode_injective⟩ (· ≤ ·)).IsLinearOrder
#align encodable.enle.is_linear_order encodable.enle.is_linear_order

private def decidable_enle (a b : α) : Decidable (Enle a b) := by
  unfold enle Order.Preimage <;> infer_instance
#align encodable.decidable_enle encodable.decidable_enle

attribute [local instance] enle.is_linear_order decidable_enle

#print Encodable.encodeMultiset /-
/-- Explicit encoding function for `multiset α` -/
def encodeMultiset (s : Multiset α) : ℕ :=
  encode (s.sort Enle)
#align encodable.encode_multiset Encodable.encodeMultiset
-/

#print Encodable.decodeMultiset /-
/-- Explicit decoding function for `multiset α` -/
def decodeMultiset (n : ℕ) : Option (Multiset α) :=
  coe <$> decode (List α) n
#align encodable.decode_multiset Encodable.decodeMultiset
-/

#print Multiset.encodable /-
/-- If `α` is encodable, then so is `multiset α`. -/
instance Multiset.encodable : Encodable (Multiset α) :=
  ⟨encodeMultiset, decodeMultiset, fun s => by simp [encode_multiset, decode_multiset, encodek]⟩
#align multiset.encodable Multiset.encodable
-/

#print Multiset.countable /-
/-- If `α` is countable, then so is `multiset α`. -/
instance Multiset.countable {α : Type _} [Countable α] : Countable (Multiset α) :=
  Quotient.countable
#align multiset.countable Multiset.countable
-/

end Finset

#print Encodable.encodableOfList /-
/-- A listable type with decidable equality is encodable. -/
def encodableOfList [DecidableEq α] (l : List α) (H : ∀ x, x ∈ l) : Encodable α :=
  ⟨fun a => indexOf a l, l.get?, fun a => indexOf_get? (H _)⟩
#align encodable.encodable_of_list Encodable.encodableOfList
-/

#print Fintype.truncEncodable /-
/-- A finite type is encodable. Because the encoding is not unique, we wrap it in `trunc` to
preserve computability. -/
def Fintype.truncEncodable (α : Type _) [DecidableEq α] [Fintype α] : Trunc (Encodable α) :=
  @Quot.recOnSubsingleton' _ (fun s : Multiset α => (∀ x : α, x ∈ s) → Trunc (Encodable α)) _
    Finset.univ.1 (fun l H => Trunc.mk <| encodableOfList l H) Finset.mem_univ
#align fintype.trunc_encodable Fintype.truncEncodable
-/

#print Fintype.toEncodable /-
/-- A noncomputable way to arbitrarily choose an ordering on a finite type.
It is not made into a global instance, since it involves an arbitrary choice.
This can be locally made into an instance with `local attribute [instance] fintype.to_encodable`. -/
noncomputable def Fintype.toEncodable (α : Type _) [Fintype α] : Encodable α := by
  classical exact (Fintype.truncEncodable α).out
#align fintype.to_encodable Fintype.toEncodable
-/

#print Vector.encodable /-
/-- If `α` is encodable, then so is `vector α n`. -/
instance Vector.encodable [Encodable α] {n} : Encodable (Vector α n) :=
  Encodable.Subtype.encodable
#align vector.encodable Vector.encodable
-/

#print Vector.countable /-
/-- If `α` is countable, then so is `vector α n`. -/
instance Vector.countable [Countable α] {n} : Countable (Vector α n) :=
  Subtype.countable
#align vector.countable Vector.countable
-/

#print Encodable.finArrow /-
/-- If `α` is encodable, then so is `fin n → α`. -/
instance finArrow [Encodable α] {n} : Encodable (Fin n → α) :=
  ofEquiv _ (Equiv.vectorEquivFin _ _).symm
#align encodable.fin_arrow Encodable.finArrow
-/

#print Encodable.finPi /-
instance finPi (n) (π : Fin n → Type _) [∀ i, Encodable (π i)] : Encodable (∀ i, π i) :=
  ofEquiv _ (Equiv.piEquivSubtypeSigma (Fin n) π)
#align encodable.fin_pi Encodable.finPi
-/

#print Finset.encodable /-
/-- If `α` is encodable, then so is `finset α`. -/
instance Finset.encodable [Encodable α] : Encodable (Finset α) :=
  haveI := decidable_eq_of_encodable α
  of_equiv { s : Multiset α // s.Nodup }
    ⟨fun ⟨a, b⟩ => ⟨a, b⟩, fun ⟨a, b⟩ => ⟨a, b⟩, fun ⟨a, b⟩ => rfl, fun ⟨a, b⟩ => rfl⟩
#align finset.encodable Finset.encodable
-/

#print Finset.countable /-
/-- If `α` is countable, then so is `finset α`. -/
instance Finset.countable [Countable α] : Countable (Finset α) :=
  Finset.val_injective.Countable
#align finset.countable Finset.countable
-/

#print Encodable.fintypeArrow /-
-- TODO: Unify with `fintype_pi` and find a better name
/-- When `α` is finite and `β` is encodable, `α → β` is encodable too. Because the encoding is not
unique, we wrap it in `trunc` to preserve computability. -/
def fintypeArrow (α : Type _) (β : Type _) [DecidableEq α] [Fintype α] [Encodable β] :
    Trunc (Encodable (α → β)) :=
  (Fintype.truncEquivFin α).map fun f =>
    Encodable.ofEquiv (Fin (Fintype.card α) → β) <| Equiv.arrowCongr f (Equiv.refl _)
#align encodable.fintype_arrow Encodable.fintypeArrow
-/

#print Encodable.fintypePi /-
/-- When `α` is finite and all `π a` are encodable, `Π a, π a` is encodable too. Because the
encoding is not unique, we wrap it in `trunc` to preserve computability. -/
def fintypePi (α : Type _) (π : α → Type _) [DecidableEq α] [Fintype α] [∀ a, Encodable (π a)] :
    Trunc (Encodable (∀ a, π a)) :=
  (Fintype.truncEncodable α).bind fun a =>
    (@fintypeArrow α (Σa, π a) _ _ (@Encodable.Sigma.encodable _ _ a _)).bind fun f =>
      Trunc.mk <|
        @Encodable.ofEquiv _ _ (@Encodable.Subtype.encodable _ _ f _)
          (Equiv.piEquivSubtypeSigma α π)
#align encodable.fintype_pi Encodable.fintypePi
-/

#print Encodable.sortedUniv /-
/-- The elements of a `fintype` as a sorted list. -/
def sortedUniv (α) [Fintype α] [Encodable α] : List α :=
  Finset.univ.sort (Encodable.encode' α ⁻¹'o (· ≤ ·))
#align encodable.sorted_univ Encodable.sortedUniv
-/

#print Encodable.mem_sortedUniv /-
@[simp]
theorem mem_sortedUniv {α} [Fintype α] [Encodable α] (x : α) : x ∈ sortedUniv α :=
  (Finset.mem_sort _).2 (Finset.mem_univ _)
#align encodable.mem_sorted_univ Encodable.mem_sortedUniv
-/

#print Encodable.length_sortedUniv /-
@[simp]
theorem length_sortedUniv (α) [Fintype α] [Encodable α] : (sortedUniv α).length = Fintype.card α :=
  Finset.length_sort _
#align encodable.length_sorted_univ Encodable.length_sortedUniv
-/

#print Encodable.sortedUniv_nodup /-
@[simp]
theorem sortedUniv_nodup (α) [Fintype α] [Encodable α] : (sortedUniv α).Nodup :=
  Finset.sort_nodup _ _
#align encodable.sorted_univ_nodup Encodable.sortedUniv_nodup
-/

#print Encodable.sortedUniv_toFinset /-
@[simp]
theorem sortedUniv_toFinset (α) [Fintype α] [Encodable α] [DecidableEq α] :
    (sortedUniv α).toFinset = Finset.univ :=
  Finset.sort_toFinset _ _
#align encodable.sorted_univ_to_finset Encodable.sortedUniv_toFinset
-/

#print Encodable.fintypeEquivFin /-
/-- An encodable `fintype` is equivalent to the same size `fin`. -/
def fintypeEquivFin {α} [Fintype α] [Encodable α] : α ≃ Fin (Fintype.card α) :=
  by
  haveI : DecidableEq α := Encodable.decidableEqOfEncodable _
  trans
  · exact ((sorted_univ_nodup α).getEquivOfForallMemList _ mem_sorted_univ).symm
  exact Equiv.cast (congr_arg _ (length_sorted_univ α))
#align encodable.fintype_equiv_fin Encodable.fintypeEquivFin
-/

#print Encodable.fintypeArrowOfEncodable /-
/-- If `α` and `β` are encodable and `α` is a fintype, then `α → β` is encodable as well. -/
instance fintypeArrowOfEncodable {α β : Type _} [Encodable α] [Fintype α] [Encodable β] :
    Encodable (α → β) :=
  ofEquiv (Fin (Fintype.card α) → β) <| Equiv.arrowCongr fintypeEquivFin (Equiv.refl _)
#align encodable.fintype_arrow_of_encodable Encodable.fintypeArrowOfEncodable
-/

end Encodable

namespace Denumerable

variable {α : Type _} {β : Type _} [Denumerable α] [Denumerable β]

open Encodable

section List

/- warning: denumerable.denumerable_list_aux -> Denumerable.denumerable_list_aux is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Denumerable.{u1} α] (n : Nat), Exists.{succ u1} (List.{u1} α) (fun (a : List.{u1} α) => Exists.{0} (Membership.Mem.{u1, u1} (List.{u1} α) (Option.{u1} (List.{u1} α)) (Option.hasMem.{u1} (List.{u1} α)) a (Encodable.decodeList.{u1} α (Denumerable.toEncodable.{u1} α _inst_1) n)) (fun (H : Membership.Mem.{u1, u1} (List.{u1} α) (Option.{u1} (List.{u1} α)) (Option.hasMem.{u1} (List.{u1} α)) a (Encodable.decodeList.{u1} α (Denumerable.toEncodable.{u1} α _inst_1) n)) => Eq.{1} Nat (Encodable.encodeList.{u1} α (Denumerable.toEncodable.{u1} α _inst_1) a) n))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Denumerable.{u1} α] (n : Nat), Exists.{succ u1} (List.{u1} α) (fun (a : List.{u1} α) => And (Membership.mem.{u1, u1} (List.{u1} α) (Option.{u1} (List.{u1} α)) (Option.instMembershipOption.{u1} (List.{u1} α)) a (Encodable.decodeList.{u1} α (Denumerable.toEncodable.{u1} α _inst_1) n)) (Eq.{1} Nat (Encodable.encodeList.{u1} α (Denumerable.toEncodable.{u1} α _inst_1) a) n))
Case conversion may be inaccurate. Consider using '#align denumerable.denumerable_list_aux Denumerable.denumerable_list_auxₓ'. -/
theorem denumerable_list_aux : ∀ n : ℕ, ∃ a ∈ @decodeList α _ n, encodeList a = n
  | 0 => by rw [decode_list] <;> exact ⟨_, rfl, rfl⟩
  | succ v => by
    cases' e : unpair v with v₁ v₂
    have h := unpair_right_le v
    rw [e] at h
    rcases have : v₂ < succ v := lt_succ_of_le h
      denumerable_list_aux v₂ with
      ⟨a, h₁, h₂⟩
    rw [Option.mem_def] at h₁
    use of_nat α v₁ :: a
    simp [decode_list, e, h₂, h₁, encode_list, mkpair_unpair' e]
#align denumerable.denumerable_list_aux Denumerable.denumerable_list_aux

#print Denumerable.denumerableList /-
/-- If `α` is denumerable, then so is `list α`. -/
instance denumerableList : Denumerable (List α) :=
  ⟨denumerable_list_aux⟩
#align denumerable.denumerable_list Denumerable.denumerableList
-/

#print Denumerable.list_ofNat_zero /-
@[simp]
theorem list_ofNat_zero : ofNat (List α) 0 = [] := by rw [← @encode_list_nil α, of_nat_encode]
#align denumerable.list_of_nat_zero Denumerable.list_ofNat_zero
-/

#print Denumerable.list_ofNat_succ /-
@[simp]
theorem list_ofNat_succ (v : ℕ) :
    ofNat (List α) (succ v) = ofNat α v.unpair.1 :: ofNat (List α) v.unpair.2 :=
  ofNat_of_decode <|
    show decodeList (succ v) = _ by
      cases' e : unpair v with v₁ v₂
      simp [decode_list, e]
      rw [show decode_list v₂ = decode (List α) v₂ from rfl, decode_eq_of_nat] <;> rfl
#align denumerable.list_of_nat_succ Denumerable.list_ofNat_succ
-/

end List

section Multiset

#print Denumerable.lower /-
/-- Outputs the list of differences of the input list, that is
`lower [a₁, a₂, ...] n = [a₁ - n, a₂ - a₁, ...]` -/
def lower : List ℕ → ℕ → List ℕ
  | [], n => []
  | m :: l, n => (m - n) :: lower l m
#align denumerable.lower Denumerable.lower
-/

#print Denumerable.raise /-
/-- Outputs the list of partial sums of the input list, that is
`raise [a₁, a₂, ...] n = [n + a₁, n + a₁ + a₂, ...]` -/
def raise : List ℕ → ℕ → List ℕ
  | [], n => []
  | m :: l, n => (m + n) :: raise l (m + n)
#align denumerable.raise Denumerable.raise
-/

#print Denumerable.lower_raise /-
theorem lower_raise : ∀ l n, lower (raise l n) n = l
  | [], n => rfl
  | m :: l, n => by rw [raise, lower, add_tsub_cancel_right, lower_raise]
#align denumerable.lower_raise Denumerable.lower_raise
-/

#print Denumerable.raise_lower /-
theorem raise_lower : ∀ {l n}, List.Sorted (· ≤ ·) (n :: l) → raise (lower l n) n = l
  | [], n, h => rfl
  | m :: l, n, h =>
    by
    have : n ≤ m := List.rel_of_sorted_cons h _ (l.mem_cons_self _)
    simp [raise, lower, tsub_add_cancel_of_le this, raise_lower h.of_cons]
#align denumerable.raise_lower Denumerable.raise_lower
-/

#print Denumerable.raise_chain /-
theorem raise_chain : ∀ l n, List.Chain (· ≤ ·) n (raise l n)
  | [], n => List.Chain.nil
  | m :: l, n => List.Chain.cons (Nat.le_add_left _ _) (raise_chain _ _)
#align denumerable.raise_chain Denumerable.raise_chain
-/

#print Denumerable.raise_sorted /-
/-- `raise l n` is an non-decreasing sequence. -/
theorem raise_sorted : ∀ l n, List.Sorted (· ≤ ·) (raise l n)
  | [], n => List.sorted_nil
  | m :: l, n => List.chain_iff_pairwise.1 (raise_chain _ _)
#align denumerable.raise_sorted Denumerable.raise_sorted
-/

#print Denumerable.multiset /-
/-- If `α` is denumerable, then so is `multiset α`. Warning: this is *not* the same encoding as used
in `multiset.encodable`. -/
instance multiset : Denumerable (Multiset α) :=
  mk'
    ⟨fun s : Multiset α => encode <| lower ((s.map encode).sort (· ≤ ·)) 0, fun n =>
      Multiset.map (ofNat α) (raise (ofNat (List ℕ) n) 0), fun s => by
      have :=
          raise_lower (List.sorted_cons.2 ⟨fun n _ => zero_le n, (s.map encode).sort_sorted _⟩) <;>
        simp [-Multiset.coe_map, this],
      fun n => by
      simp [-Multiset.coe_map, List.mergeSort_eq_self _ (raise_sorted _ _), lower_raise]⟩
#align denumerable.multiset Denumerable.multiset
-/

end Multiset

section Finset

#print Denumerable.lower' /-
/-- Outputs the list of differences minus one of the input list, that is
`lower' [a₁, a₂, a₃, ...] n = [a₁ - n, a₂ - a₁ - 1, a₃ - a₂ - 1, ...]`. -/
def lower' : List ℕ → ℕ → List ℕ
  | [], n => []
  | m :: l, n => (m - n) :: lower' l (m + 1)
#align denumerable.lower' Denumerable.lower'
-/

#print Denumerable.raise' /-
/-- Outputs the list of partial sums plus one of the input list, that is
`raise [a₁, a₂, a₃, ...] n = [n + a₁, n + a₁ + a₂ + 1, n + a₁ + a₂ + a₃ + 2, ...]`. Adding one each
time ensures the elements are distinct. -/
def raise' : List ℕ → ℕ → List ℕ
  | [], n => []
  | m :: l, n => (m + n) :: raise' l (m + n + 1)
#align denumerable.raise' Denumerable.raise'
-/

#print Denumerable.lower_raise' /-
theorem lower_raise' : ∀ l n, lower' (raise' l n) n = l
  | [], n => rfl
  | m :: l, n => by simp [raise', lower', add_tsub_cancel_right, lower_raise']
#align denumerable.lower_raise' Denumerable.lower_raise'
-/

#print Denumerable.raise_lower' /-
theorem raise_lower' : ∀ {l n}, (∀ m ∈ l, n ≤ m) → List.Sorted (· < ·) l → raise' (lower' l n) n = l
  | [], n, h₁, h₂ => rfl
  | m :: l, n, h₁, h₂ => by
    have : n ≤ m := h₁ _ (l.mem_cons_self _)
    simp [raise', lower', tsub_add_cancel_of_le this,
      raise_lower' (List.rel_of_sorted_cons h₂ : ∀ a ∈ l, m < a) h₂.of_cons]
#align denumerable.raise_lower' Denumerable.raise_lower'
-/

#print Denumerable.raise'_chain /-
theorem raise'_chain : ∀ (l) {m n}, m < n → List.Chain (· < ·) m (raise' l n)
  | [], m, n, h => List.Chain.nil
  | a :: l, m, n, h =>
    List.Chain.cons (lt_of_lt_of_le h (Nat.le_add_left _ _)) (raise'_chain _ (lt_succ_self _))
#align denumerable.raise'_chain Denumerable.raise'_chain
-/

#print Denumerable.raise'_sorted /-
/-- `raise' l n` is a strictly increasing sequence. -/
theorem raise'_sorted : ∀ l n, List.Sorted (· < ·) (raise' l n)
  | [], n => List.sorted_nil
  | m :: l, n => List.chain_iff_pairwise.1 (raise'_chain _ (lt_succ_self _))
#align denumerable.raise'_sorted Denumerable.raise'_sorted
-/

#print Denumerable.raise'Finset /-
/-- Makes `raise' l n` into a finset. Elements are distinct thanks to `raise'_sorted`. -/
def raise'Finset (l : List ℕ) (n : ℕ) : Finset ℕ :=
  ⟨raise' l n, (raise'_sorted _ _).imp (@ne_of_lt _ _)⟩
#align denumerable.raise'_finset Denumerable.raise'Finset
-/

#print Denumerable.finset /-
/-- If `α` is denumerable, then so is `finset α`. Warning: this is *not* the same encoding as used
in `finset.encodable`. -/
instance finset : Denumerable (Finset α) :=
  mk'
    ⟨fun s : Finset α => encode <| lower' ((s.map (eqv α).toEmbedding).sort (· ≤ ·)) 0, fun n =>
      Finset.map (eqv α).symm.toEmbedding (raise'Finset (ofNat (List ℕ) n) 0), fun s =>
      Finset.eq_of_veq <| by
        simp [-Multiset.coe_map, raise'_finset,
          raise_lower' (fun n _ => zero_le n) (Finset.sort_sorted_lt _)],
      fun n => by
      simp [-Multiset.coe_map, Finset.map, raise'_finset, Finset.sort,
        List.mergeSort_eq_self (· ≤ ·) ((raise'_sorted _ _).imp (@le_of_lt _ _)), lower_raise']⟩
#align denumerable.finset Denumerable.finset
-/

end Finset

end Denumerable

namespace Equiv

#print Equiv.listUnitEquiv /-
/-- The type lists on unit is canonically equivalent to the natural numbers. -/
def listUnitEquiv : List Unit ≃ ℕ where
  toFun := List.length
  invFun n := List.replicate n ()
  left_inv u := List.length_injective (by simp)
  right_inv n := List.length_replicate n ()
#align equiv.list_unit_equiv Equiv.listUnitEquiv
-/

#print Equiv.listNatEquivNat /-
/-- `list ℕ` is equivalent to `ℕ`. -/
def listNatEquivNat : List ℕ ≃ ℕ :=
  Denumerable.eqv _
#align equiv.list_nat_equiv_nat Equiv.listNatEquivNat
-/

#print Equiv.listEquivSelfOfEquivNat /-
/-- If `α` is equivalent to `ℕ`, then `list α` is equivalent to `α`. -/
def listEquivSelfOfEquivNat {α : Type _} (e : α ≃ ℕ) : List α ≃ α :=
  calc
    List α ≃ List ℕ := listEquivOfEquiv e
    _ ≃ ℕ := listNatEquivNat
    _ ≃ α := e.symm

#align equiv.list_equiv_self_of_equiv_nat Equiv.listEquivSelfOfEquivNat
-/

end Equiv


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Bhavik Mehta
-/
import category_theory.pempty
import category_theory.limits.has_limits
import category_theory.epi_mono
import category_theory.category.preorder

/-!
# Initial and terminal objects in a category.

## References
* [Stacks: Initial and final objects](https://stacks.math.columbia.edu/tag/002B)
-/

noncomputable theory

universes w w' v v₁ v₂ u u₁ u₂

open category_theory

namespace category_theory.limits

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

/-- Construct a cone for the empty diagram given an object. -/
@[simps] def as_empty_cone (X : C) : cone (functor.empty.{w} C) := { X := X, π := by tidy }
/-- Construct a cocone for the empty diagram given an object. -/
@[simps] def as_empty_cocone (X : C) : cocone (functor.empty.{w} C) := { X := X, ι := by tidy }

/-- `X` is terminal if the cone it induces on the empty diagram is limiting. -/
abbreviation is_terminal (X : C) := is_limit (as_empty_cone.{v₁} X)
/-- `X` is initial if the cocone it induces on the empty diagram is colimiting. -/
abbreviation is_initial (X : C) := is_colimit (as_empty_cocone.{v₁} X)

/-- An object `Y` is terminal iff for every `X` there is a unique morphism `X ⟶ Y`. -/
def is_terminal_equiv_unique (F : discrete.{v₁} pempty ⥤ C) (Y : C) :
  is_limit (⟨Y, by tidy⟩ : cone F) ≃ ∀ X : C, unique (X ⟶ Y) :=
{ to_fun := λ t X, { default := t.lift ⟨X, by tidy⟩,
    uniq := λ f, t.uniq ⟨X, by tidy⟩ f (by tidy) },
  inv_fun := λ u, { lift := λ s, (u s.X).default, uniq' := λ s _ _, (u s.X).2 _ },
  left_inv := by tidy,
  right_inv := by tidy }

/-- An object `Y` is terminal if for every `X` there is a unique morphism `X ⟶ Y`
    (as an instance). -/
def is_terminal.of_unique (Y : C) [h : Π X : C, unique (X ⟶ Y)] : is_terminal Y :=
{ lift := λ s, (h s.X).default }

/-- If `α` is a preorder with top, then `⊤` is a terminal object. -/
def is_terminal_top {α : Type*} [preorder α] [order_top α] : is_terminal (⊤ : α) :=
is_terminal.of_unique _

/-- Transport a term of type `is_terminal` across an isomorphism. -/
def is_terminal.of_iso {Y Z : C} (hY : is_terminal Y) (i : Y ≅ Z) : is_terminal Z :=
is_limit.of_iso_limit hY
{ hom := { hom := i.hom },
  inv := { hom := i.inv } }

/-- An object `X` is initial iff for every `Y` there is a unique morphism `X ⟶ Y`. -/
def is_initial_equiv_unique (F : discrete.{v₁} pempty ⥤ C) (X : C) :
  is_colimit (⟨X, by tidy⟩ : cocone F) ≃ ∀ Y : C, unique (X ⟶ Y) :=
{ to_fun := λ t X, { default := t.desc ⟨X, by tidy⟩,
    uniq := λ f, t.uniq ⟨X, by tidy⟩ f (by tidy) },
  inv_fun := λ u, { desc := λ s, (u s.X).default, uniq' := λ s _ _, (u s.X).2 _ },
  left_inv := by tidy,
  right_inv := by tidy }

/-- An object `X` is initial if for every `Y` there is a unique morphism `X ⟶ Y`
    (as an instance). -/
def is_initial.of_unique (X : C) [h : Π Y : C, unique (X ⟶ Y)] : is_initial X :=
{ desc := λ s, (h s.X).default }

/-- If `α` is a preorder with bot, then `⊥` is an initial object. -/
def is_initial_bot {α : Type*} [preorder α] [order_bot α] : is_initial (⊥ : α) :=
is_initial.of_unique _

/-- Transport a term of type `is_initial` across an isomorphism. -/
def is_initial.of_iso {X Y : C} (hX : is_initial X) (i : X ≅ Y) : is_initial Y :=
is_colimit.of_iso_colimit hX
{ hom := { hom := i.hom },
  inv := { hom := i.inv } }

/-- Give the morphism to a terminal object from any other. -/
def is_terminal.from {X : C} (t : is_terminal X) (Y : C) : Y ⟶ X :=
t.lift (as_empty_cone Y)

/-- Any two morphisms to a terminal object are equal. -/
lemma is_terminal.hom_ext {X Y : C} (t : is_terminal X) (f g : Y ⟶ X) : f = g :=
t.hom_ext (by tidy)

@[simp] lemma is_terminal.comp_from {Z : C} (t : is_terminal Z) {X Y : C} (f : X ⟶ Y) :
  f ≫ t.from Y = t.from X :=
t.hom_ext _ _

@[simp] lemma is_terminal.from_self {X : C} (t : is_terminal X) : t.from X = 𝟙 X :=
t.hom_ext _ _

/-- Give the morphism from an initial object to any other. -/
def is_initial.to {X : C} (t : is_initial X) (Y : C) : X ⟶ Y :=
t.desc (as_empty_cocone Y)

/-- Any two morphisms from an initial object are equal. -/
lemma is_initial.hom_ext {X Y : C} (t : is_initial X) (f g : X ⟶ Y) : f = g :=
t.hom_ext (by tidy)

@[simp] lemma is_initial.to_comp {X : C} (t : is_initial X) {Y Z : C} (f : Y ⟶ Z) :
  t.to Y ≫ f = t.to Z :=
t.hom_ext _ _

@[simp] lemma is_initial.to_self {X : C} (t : is_initial X) : t.to X = 𝟙 X :=
t.hom_ext _ _

/-- Any morphism from a terminal object is split mono. -/
def is_terminal.split_mono_from {X Y : C} (t : is_terminal X) (f : X ⟶ Y) : split_mono f :=
⟨t.from _, t.hom_ext _ _⟩

/-- Any morphism to an initial object is split epi. -/
def is_initial.split_epi_to {X Y : C} (t : is_initial X) (f : Y ⟶ X) : split_epi f :=
⟨t.to _, t.hom_ext _ _⟩

/-- Any morphism from a terminal object is mono. -/
lemma is_terminal.mono_from {X Y : C} (t : is_terminal X) (f : X ⟶ Y) : mono f :=
by haveI := t.split_mono_from f; apply_instance

/-- Any morphism to an initial object is epi. -/
lemma is_initial.epi_to {X Y : C} (t : is_initial X) (f : Y ⟶ X) : epi f :=
by haveI := t.split_epi_to f; apply_instance

/-- If `T` and `T'` are terminal, they are isomorphic. -/
@[simps]
def is_terminal.unique_up_to_iso {T T' : C} (hT : is_terminal T) (hT' : is_terminal T') : T ≅ T' :=
{ hom := hT'.from _,
  inv := hT.from _ }

/-- If `I` and `I'` are initial, they are isomorphic. -/
@[simps]
def is_initial.unique_up_to_iso {I I' : C} (hI : is_initial I) (hI' : is_initial I') : I ≅ I' :=
{ hom := hI.to _,
  inv := hI'.to _ }

variable (C)

/--
A category has a terminal object if it has a limit over the empty diagram.
Use `has_terminal_of_unique` to construct instances.
-/
abbreviation has_terminal := has_limits_of_shape (discrete.{v₁} pempty) C
/--
A category has an initial object if it has a colimit over the empty diagram.
Use `has_initial_of_unique` to construct instances.
-/
abbreviation has_initial := has_colimits_of_shape (discrete.{v₁} pempty) C

section univ

variables (X : C) {F₁ : discrete.{w} pempty ⥤ C} {F₂ : discrete.{w'} pempty ⥤ C}

/-- Being terminal is independent of the empty diagram, its universe, and the cone over it,
    as long as the cone points are isomorphic. -/
def is_limit_change_empty_cone {c₁ : cone F₁} (hl : is_limit c₁)
  (c₂ : cone F₂) (hi : c₁.X ≅ c₂.X) : is_limit c₂ :=
{ lift := λ c, hl.lift ⟨c.X, by tidy⟩ ≫ hi.hom,
  fac' := λ _ j, j.elim,
  uniq' := λ c f _, by { erw ← hl.uniq ⟨c.X, by tidy⟩ (f ≫ hi.inv) (λ j, j.elim), simp } }

/-- Replacing an empty cone in `is_limit` by another with the same cone point
    is an equivalence. -/
def is_limit_empty_cone_equiv (c₁ : cone F₁) (c₂ : cone F₂) (h : c₁.X ≅ c₂.X) :
  is_limit c₁ ≃ is_limit c₂ :=
{ to_fun := λ hl, is_limit_change_empty_cone C hl c₂ h,
  inv_fun := λ hl, is_limit_change_empty_cone C hl c₁ h.symm,
  left_inv := by tidy,
  right_inv := by tidy }

lemma has_terminal_change_diagram (h : has_limit F₁) : has_limit F₂ :=
⟨⟨⟨⟨limit F₁, by tidy⟩, is_limit_change_empty_cone C (limit.is_limit F₁) _ (eq_to_iso rfl)⟩⟩⟩

lemma has_terminal_change_universe [h : has_limits_of_shape (discrete.{w} pempty) C] :
  has_limits_of_shape (discrete.{w'} pempty) C :=
{ has_limit := λ J, has_terminal_change_diagram C (let f := h.1 in f (functor.empty C)) }

/-- Being initial is independent of the empty diagram, its universe, and the cocone over it,
    as long as the cocone points are isomorphic. -/
def is_colimit_change_empty_cocone {c₁ : cocone F₁} (hl : is_colimit c₁)
  (c₂ : cocone F₂) (hi : c₁.X ≅ c₂.X) : is_colimit c₂ :=
{ desc := λ c, hi.inv ≫ hl.desc ⟨c.X, by tidy⟩,
  fac' := λ _ j, j.elim,
  uniq' := λ c f _, by { erw ← hl.uniq ⟨c.X, by tidy⟩ (hi.hom ≫ f) (λ j, j.elim), simp } }

/-- Replacing an empty cocone in `is_colimit` by another with the same cocone point
    is an equivalence. -/
def is_colimit_empty_cocone_equiv (c₁ : cocone F₁) (c₂ : cocone F₂) (h : c₁.X ≅ c₂.X) :
  is_colimit c₁ ≃ is_colimit c₂ :=
{ to_fun := λ hl, is_colimit_change_empty_cocone C hl c₂ h,
  inv_fun := λ hl, is_colimit_change_empty_cocone C hl c₁ h.symm,
  left_inv := by tidy,
  right_inv := by tidy }

lemma has_initial_change_diagram (h : has_colimit F₁) : has_colimit F₂ :=
⟨⟨⟨⟨colimit F₁, by tidy⟩,
   is_colimit_change_empty_cocone C (colimit.is_colimit F₁) _ (eq_to_iso rfl)⟩⟩⟩

end univ

/--
An arbitrary choice of terminal object, if one exists.
You can use the notation `⊤_ C`.
This object is characterized by having a unique morphism from any object.
-/
abbreviation terminal [has_terminal C] : C := limit (functor.empty.{v₁} C)
/--
An arbitrary choice of initial object, if one exists.
You can use the notation `⊥_ C`.
This object is characterized by having a unique morphism to any object.
-/
abbreviation initial [has_initial C] : C := colimit (functor.empty.{v₁} C)

notation `⊤_ ` C:20 := terminal C
notation `⊥_ ` C:20 := initial C

section
variables {C}

/-- We can more explicitly show that a category has a terminal object by specifying the object,
and showing there is a unique morphism to it from any other object. -/
lemma has_terminal_of_unique (X : C) [h : Π Y : C, unique (Y ⟶ X)] : has_terminal C :=
{ has_limit := λ F, has_limit.mk ⟨_, (is_terminal_equiv_unique F X).inv_fun h⟩ }

/-- We can more explicitly show that a category has an initial object by specifying the object,
and showing there is a unique morphism from it to any other object. -/
lemma has_initial_of_unique (X : C) [h : Π Y : C, unique (X ⟶ Y)] : has_initial C :=
{ has_colimit := λ F, has_colimit.mk ⟨_, (is_initial_equiv_unique F X).inv_fun h⟩ }

/-- The map from an object to the terminal object. -/
abbreviation terminal.from [has_terminal C] (P : C) : P ⟶ ⊤_ C :=
limit.lift (functor.empty C) (as_empty_cone P)
/-- The map to an object from the initial object. -/
abbreviation initial.to [has_initial C] (P : C) : ⊥_ C ⟶ P :=
colimit.desc (functor.empty C) (as_empty_cocone P)

/-- A terminal object is terminal. -/
def terminal_is_terminal [has_terminal C] : is_terminal (⊤_ C) :=
{ lift := λ s, terminal.from _ }

/-- An initial object is initial. -/
def initial_is_initial [has_initial C] : is_initial (⊥_ C) :=
{ desc := λ s, initial.to _ }

instance unique_to_terminal [has_terminal C] (P : C) : unique (P ⟶ ⊤_ C) :=
is_terminal_equiv_unique _ (⊤_ C) terminal_is_terminal P

instance unique_from_initial [has_initial C] (P : C) : unique (⊥_ C ⟶ P) :=
is_initial_equiv_unique _ (⊥_ C) initial_is_initial P

@[simp] lemma terminal.comp_from [has_terminal C] {P Q : C} (f : P ⟶ Q) :
  f ≫ terminal.from Q = terminal.from P :=
by tidy
@[simp] lemma initial.to_comp [has_initial C] {P Q : C} (f : P ⟶ Q) :
  initial.to P ≫ f = initial.to Q :=
by tidy

/-- Any morphism from a terminal object is split mono. -/
instance terminal.split_mono_from {Y : C} [has_terminal C] (f : ⊤_ C ⟶ Y) : split_mono f :=
is_terminal.split_mono_from terminal_is_terminal _

/-- Any morphism to an initial object is split epi. -/
instance initial.split_epi_to {Y : C} [has_initial C] (f : Y ⟶ ⊥_ C) : split_epi f :=
is_initial.split_epi_to initial_is_initial _

/-- An initial object is terminal in the opposite category. -/
def terminal_op_of_initial {X : C} (t : is_initial X) : is_terminal (opposite.op X) :=
{ lift := λ s, (t.to s.X.unop).op,
  uniq' := λ s m w, quiver.hom.unop_inj (t.hom_ext _ _) }

/-- An initial object in the opposite category is terminal in the original category. -/
def terminal_unop_of_initial {X : Cᵒᵖ} (t : is_initial X) : is_terminal X.unop :=
{ lift := λ s, (t.to (opposite.op s.X)).unop,
  uniq' := λ s m w, quiver.hom.op_inj (t.hom_ext _ _) }

/-- A terminal object is initial in the opposite category. -/
def initial_op_of_terminal {X : C} (t : is_terminal X) : is_initial (opposite.op X) :=
{ desc := λ s, (t.from s.X.unop).op,
  uniq' := λ s m w, quiver.hom.unop_inj (t.hom_ext _ _) }

/-- A terminal object in the opposite category is initial in the original category. -/
def initial_unop_of_terminal {X : Cᵒᵖ} (t : is_terminal X) : is_initial X.unop :=
{ desc := λ s, (t.from (opposite.op s.X)).unop,
  uniq' := λ s m w, quiver.hom.op_inj (t.hom_ext _ _) }

/-- A category is a `initial_mono_class` if the canonical morphism of an initial object is a
monomorphism.  In practice, this is most useful when given an arbitrary morphism out of the chosen
initial object, see `initial.mono_from`.
Given a terminal object, this is equivalent to the assumption that the unique morphism from initial
to terminal is a monomorphism, which is the second of Freyd's axioms for an AT category.

TODO: This is a condition satisfied by categories with zero objects and morphisms.
-/
class initial_mono_class (C : Type u₁) [category.{v₁} C] : Prop :=
(is_initial_mono_from : ∀ {I} (X : C) (hI : is_initial I), mono (hI.to X))

lemma is_initial.mono_from [initial_mono_class C] {I} {X : C} (hI : is_initial I) (f : I ⟶ X) :
  mono f :=
begin
  rw hI.hom_ext f (hI.to X),
  apply initial_mono_class.is_initial_mono_from,
end

@[priority 100]
instance initial.mono_from [has_initial C] [initial_mono_class C] (X : C) (f : ⊥_ C ⟶ X) :
  mono f :=
initial_is_initial.mono_from f

/-- To show a category is a `initial_mono_class` it suffices to give an initial object such that
every morphism out of it is a monomorphism. -/
lemma initial_mono_class.of_is_initial {I : C} (hI : is_initial I) (h : ∀ X, mono (hI.to X)) :
  initial_mono_class C :=
{ is_initial_mono_from := λ I' X hI',
  begin
    rw hI'.hom_ext (hI'.to X) ((hI'.unique_up_to_iso hI).hom ≫ hI.to X),
    apply mono_comp,
  end }

/-- To show a category is a `initial_mono_class` it suffices to show every morphism out of the
initial object is a monomorphism. -/
lemma initial_mono_class.of_initial [has_initial C] (h : ∀ X : C, mono (initial.to X)) :
  initial_mono_class C :=
initial_mono_class.of_is_initial initial_is_initial h

/-- To show a category is a `initial_mono_class` it suffices to show the unique morphism from an
initial object to a terminal object is a monomorphism. -/
lemma initial_mono_class.of_is_terminal {I T : C} (hI : is_initial I) (hT : is_terminal T)
  (f : mono (hI.to T)) :
  initial_mono_class C :=
initial_mono_class.of_is_initial hI (λ X, mono_of_mono_fac (hI.hom_ext (_ ≫ hT.from X) (hI.to T)))

/-- To show a category is a `initial_mono_class` it suffices to show the unique morphism from the
initial object to a terminal object is a monomorphism. -/
lemma initial_mono_class.of_terminal [has_initial C] [has_terminal C]
  (h : mono (initial.to (⊤_ C))) :
  initial_mono_class C :=
initial_mono_class.of_is_terminal initial_is_initial terminal_is_terminal h

section comparison
variables {D : Type u₂} [category.{v₂} D] (G : C ⥤ D)

/--
The comparison morphism from the image of a terminal object to the terminal object in the target
category.
This is an isomorphism iff `G` preserves terminal objects, see
`category_theory.limits.preserves_terminal.of_iso_comparison`.
-/
def terminal_comparison [has_terminal C] [has_terminal D] :
  G.obj (⊤_ C) ⟶ ⊤_ D :=
terminal.from _

/--
The comparison morphism from the initial object in the target category to the image of the initial
object.
-/
-- TODO: Show this is an isomorphism if and only if `G` preserves initial objects.
def initial_comparison [has_initial C] [has_initial D] :
  ⊥_ D ⟶ G.obj (⊥_ C) :=
initial.to _

end comparison

variables {J : Type u} [category.{v} J]

/-- From a functor `F : J ⥤ C`, given an initial object of `J`, construct a cone for `J`.
In `limit_of_diagram_initial` we show it is a limit cone. -/
@[simps]
def cone_of_diagram_initial
  {X : J} (tX : is_initial X) (F : J ⥤ C) : cone F :=
{ X := F.obj X,
  π :=
  { app := λ j, F.map (tX.to j),
    naturality' := λ j j' k,
    begin
      dsimp,
      rw [← F.map_comp, category.id_comp, tX.hom_ext (tX.to j ≫ k) (tX.to j')],
    end } }

/-- From a functor `F : J ⥤ C`, given an initial object of `J`, show the cone
`cone_of_diagram_initial` is a limit. -/
def limit_of_diagram_initial
  {X : J} (tX : is_initial X) (F : J ⥤ C) :
is_limit (cone_of_diagram_initial tX F) :=
{ lift := λ s, s.π.app X,
  uniq' := λ s m w,
    begin
      rw [← w X, cone_of_diagram_initial_π_app, tX.hom_ext (tX.to X) (𝟙 _)],
      dsimp, simp -- See note [dsimp, simp]
    end}

-- This is reducible to allow usage of lemmas about `cone_point_unique_up_to_iso`.
/-- For a functor `F : J ⥤ C`, if `J` has an initial object then the image of it is isomorphic
to the limit of `F`. -/
@[reducible]
def limit_of_initial (F : J ⥤ C)
  [has_initial J] [has_limit F] :
limit F ≅ F.obj (⊥_ J) :=
is_limit.cone_point_unique_up_to_iso
  (limit.is_limit _)
  (limit_of_diagram_initial initial_is_initial F)

/-- From a functor `F : J ⥤ C`, given a terminal object of `J`, construct a cone for `J`,
provided that the morphisms in the diagram are isomorphisms.
In `limit_of_diagram_terminal` we show it is a limit cone. -/
@[simps]
def cone_of_diagram_terminal {X : J} (hX : is_terminal X)
  (F : J ⥤ C) [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] : cone F :=
{ X := F.obj X,
  π :=
  { app := λ i, inv (F.map (hX.from _)),
    naturality' := begin
      intros i j f,
      dsimp,
      simp only [is_iso.eq_inv_comp, is_iso.comp_inv_eq, category.id_comp,
        ← F.map_comp, hX.hom_ext (hX.from i) (f ≫ hX.from j)],
    end } }

/-- From a functor `F : J ⥤ C`, given a terminal object of `J` and that the morphisms in the
diagram are isomorphisms, show the cone `cone_of_diagram_terminal` is a limit. -/
def limit_of_diagram_terminal {X : J} (hX : is_terminal X)
  (F : J ⥤ C) [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] :
  is_limit (cone_of_diagram_terminal hX F) :=
{ lift := λ S, S.π.app _ }

-- This is reducible to allow usage of lemmas about `cone_point_unique_up_to_iso`.
/-- For a functor `F : J ⥤ C`, if `J` has a terminal object and all the morphisms in the diagram
are isomorphisms, then the image of the terminal object is isomorphic to the limit of `F`. -/
@[reducible]
def limit_of_terminal (F : J ⥤ C)
  [has_terminal J] [has_limit F] [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] :
limit F ≅ F.obj (⊤_ J) :=
is_limit.cone_point_unique_up_to_iso
  (limit.is_limit _)
  (limit_of_diagram_terminal terminal_is_terminal F)

/-- From a functor `F : J ⥤ C`, given a terminal object of `J`, construct a cocone for `J`.
In `colimit_of_diagram_terminal` we show it is a colimit cocone. -/
@[simps]
def cocone_of_diagram_terminal
  {X : J} (tX : is_terminal X) (F : J ⥤ C) : cocone F :=
{ X := F.obj X,
  ι :=
  { app := λ j, F.map (tX.from j),
    naturality' := λ j j' k,
    begin
      dsimp,
      rw [← F.map_comp, category.comp_id, tX.hom_ext (k ≫ tX.from j') (tX.from j)],
    end } }

/-- From a functor `F : J ⥤ C`, given a terminal object of `J`, show the cocone
`cocone_of_diagram_terminal` is a colimit. -/
def colimit_of_diagram_terminal
  {X : J} (tX : is_terminal X) (F : J ⥤ C) :
is_colimit (cocone_of_diagram_terminal tX F) :=
{ desc := λ s, s.ι.app X,
  uniq' := λ s m w,
    by { rw [← w X, cocone_of_diagram_terminal_ι_app, tX.hom_ext (tX.from X) (𝟙 _)], simp } }

-- This is reducible to allow usage of lemmas about `cocone_point_unique_up_to_iso`.
/-- For a functor `F : J ⥤ C`, if `J` has a terminal object then the image of it is isomorphic
to the colimit of `F`. -/
@[reducible]
def colimit_of_terminal (F : J ⥤ C)
  [has_terminal J] [has_colimit F] :
colimit F ≅ F.obj (⊤_ J) :=
is_colimit.cocone_point_unique_up_to_iso
  (colimit.is_colimit _)
  (colimit_of_diagram_terminal terminal_is_terminal F)

/-- From a functor `F : J ⥤ C`, given an initial object of `J`, construct a cocone for `J`,
provided that the morphisms in the diagram are isomorphisms.
In `colimit_of_diagram_initial` we show it is a colimit cocone. -/
@[simps]
def cocone_of_diagram_initial {X : J} (hX : is_initial X) (F : J ⥤ C)
  [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] : cocone F :=
{ X := F.obj X,
  ι :=
  { app := λ i, inv (F.map (hX.to _)),
    naturality' := begin
      intros i j f,
      dsimp,
      simp only [is_iso.eq_inv_comp, is_iso.comp_inv_eq, category.comp_id,
        ← F.map_comp, hX.hom_ext (hX.to i ≫ f) (hX.to j)],
    end } }

/-- From a functor `F : J ⥤ C`, given an initial object of `J` and that the morphisms in the
diagram are isomorphisms, show the cone `cocone_of_diagram_initial` is a colimit. -/
def colimit_of_diagram_initial {X : J} (hX : is_initial X) (F : J ⥤ C)
  [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] : is_colimit (cocone_of_diagram_initial hX F) :=
{ desc := λ S, S.ι.app _ }

-- This is reducible to allow usage of lemmas about `cocone_point_unique_up_to_iso`.
/-- For a functor `F : J ⥤ C`, if `J` has an initial object and all the morphisms in the diagram
are isomorphisms, then the image of the initial object is isomorphic to the colimit of `F`. -/
@[reducible]
def colimit_of_initial (F : J ⥤ C)
  [has_initial J] [has_colimit F] [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] :
colimit F ≅ F.obj (⊥_ J) :=
is_colimit.cocone_point_unique_up_to_iso
  (colimit.is_colimit _)
  (colimit_of_diagram_initial initial_is_initial _)

/--
If `j` is initial in the index category, then the map `limit.π F j` is an isomorphism.
-/
lemma is_iso_π_of_is_initial {j : J} (I : is_initial j) (F : J ⥤ C) [has_limit F] :
  is_iso (limit.π F j) :=
⟨⟨limit.lift _ (cone_of_diagram_initial I F), ⟨by { ext, simp }, by simp⟩⟩⟩

instance is_iso_π_initial [has_initial J] (F : J ⥤ C) [has_limit F] :
  is_iso (limit.π F (⊥_ J)) :=
is_iso_π_of_is_initial (initial_is_initial) F

lemma is_iso_π_of_is_terminal {j : J} (I : is_terminal j) (F : J ⥤ C)
  [has_limit F] [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] : is_iso (limit.π F j) :=
⟨⟨limit.lift _ (cone_of_diagram_terminal I F), by { ext, simp }, by simp ⟩⟩

instance is_iso_π_terminal [has_terminal J] (F : J ⥤ C) [has_limit F]
  [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] : is_iso (limit.π F (⊤_ J)) :=
is_iso_π_of_is_terminal terminal_is_terminal F

/--
If `j` is terminal in the index category, then the map `colimit.ι F j` is an isomorphism.
-/
lemma is_iso_ι_of_is_terminal {j : J} (I : is_terminal j) (F : J ⥤ C) [has_colimit F] :
  is_iso (colimit.ι F j) :=
⟨⟨colimit.desc _ (cocone_of_diagram_terminal I F), ⟨by simp, by { ext, simp }⟩⟩⟩

instance is_iso_ι_terminal [has_terminal J] (F : J ⥤ C) [has_colimit F] :
  is_iso (colimit.ι F (⊤_ J)) :=
is_iso_ι_of_is_terminal (terminal_is_terminal) F

lemma is_iso_ι_of_is_initial {j : J} (I : is_initial j) (F : J ⥤ C)
  [has_colimit F] [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] : is_iso (colimit.ι F j) :=
⟨⟨colimit.desc _ (cocone_of_diagram_initial I F), ⟨by tidy, by { ext, simp }⟩⟩⟩

instance is_iso_ι_initial [has_initial J] (F : J ⥤ C) [has_colimit F]
  [∀ (i j : J) (f : i ⟶ j), is_iso (F.map f)] : is_iso (colimit.ι F (⊥_ J)) :=
is_iso_ι_of_is_initial initial_is_initial F

end

end category_theory.limits


/-
Copyright (c) 2022 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies

! This file was ported from Lean 3 source module topology.bornology.hom
! leanprover-community/mathlib commit fac369018417f980cec5fcdafc766a69f88d8cfe
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Topology.Bornology.Basic

/-!
# Locally bounded maps

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

This file defines locally bounded maps between bornologies.

We use the `fun_like` design, so each type of morphisms has a companion typeclass which is meant to
be satisfied by itself and all stricter types.

## Types of morphisms

* `locally_bounded_map`: Locally bounded maps. Maps which preserve boundedness.

## Typeclasses

* `locally_bounded_map_class`
-/

open Bornology Filter Function Set

variable {F α β γ δ : Type _}

#print LocallyBoundedMap /-
/-- The type of bounded maps from `α` to `β`, the maps which send a bounded set to a bounded set. -/
structure LocallyBoundedMap (α β : Type _) [Bornology α] [Bornology β] where
  toFun : α → β
  comap_cobounded_le' : (cobounded β).comap to_fun ≤ cobounded α
#align locally_bounded_map LocallyBoundedMap
-/

section

#print LocallyBoundedMapClass /-
/-- `locally_bounded_map_class F α β` states that `F` is a type of bounded maps.

You should extend this class when you extend `locally_bounded_map`. -/
class LocallyBoundedMapClass (F : Type _) (α β : outParam <| Type _) [Bornology α]
  [Bornology β] extends FunLike F α fun _ => β where
  comap_cobounded_le (f : F) : (cobounded β).comap f ≤ cobounded α
#align locally_bounded_map_class LocallyBoundedMapClass
-/

end

export LocallyBoundedMapClass (comap_cobounded_le)

/- warning: is_bounded.image -> IsBounded.image is a dubious translation:
lean 3 declaration is
  forall {F : Type.{u1}} {α : Type.{u2}} {β : Type.{u3}} [_inst_1 : Bornology.{u2} α] [_inst_2 : Bornology.{u3} β] [_inst_3 : LocallyBoundedMapClass.{u1, u2, u3} F α β _inst_1 _inst_2] {f : F} {s : Set.{u2} α}, (Bornology.IsBounded.{u2} α _inst_1 s) -> (Bornology.IsBounded.{u3} β _inst_2 (Set.image.{u2, u3} α β (coeFn.{succ u1, max (succ u2) (succ u3)} F (fun (_x : F) => α -> β) (FunLike.hasCoeToFun.{succ u1, succ u2, succ u3} F α (fun (_x : α) => β) (LocallyBoundedMapClass.toFunLike.{u1, u2, u3} F α β _inst_1 _inst_2 _inst_3)) f) s))
but is expected to have type
  forall {F : Type.{u1}} {α : Type.{u3}} {β : Type.{u2}} [_inst_1 : Bornology.{u3} α] [_inst_2 : Bornology.{u2} β] [_inst_3 : LocallyBoundedMapClass.{u1, u3, u2} F α β _inst_1 _inst_2] {f : F} {s : Set.{u3} α}, (Bornology.IsBounded.{u3} α _inst_1 s) -> (Bornology.IsBounded.{u2} β _inst_2 (Set.image.{u3, u2} α β (FunLike.coe.{succ u1, succ u3, succ u2} F α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{u1, u3, u2} F α β _inst_1 _inst_2 _inst_3) f) s))
Case conversion may be inaccurate. Consider using '#align is_bounded.image IsBounded.imageₓ'. -/
theorem IsBounded.image [Bornology α] [Bornology β] [LocallyBoundedMapClass F α β] {f : F}
    {s : Set α} (hs : IsBounded s) : IsBounded (f '' s) :=
  comap_cobounded_le_iff.1 (comap_cobounded_le f) hs
#align is_bounded.image IsBounded.image

instance [Bornology α] [Bornology β] [LocallyBoundedMapClass F α β] :
    CoeTC F (LocallyBoundedMap α β) :=
  ⟨fun f => ⟨f, comap_cobounded_le f⟩⟩

namespace LocallyBoundedMap

variable [Bornology α] [Bornology β] [Bornology γ] [Bornology δ]

instance : LocallyBoundedMapClass (LocallyBoundedMap α β) α β
    where
  coe f := f.toFun
  coe_injective' f g h := by
    cases f
    cases g
    congr
  comap_cobounded_le f := f.comap_cobounded_le'

/-- Helper instance for when there's too many metavariables to apply `fun_like.has_coe_to_fun`
directly. -/
instance : CoeFun (LocallyBoundedMap α β) fun _ => α → β :=
  FunLike.hasCoeToFun

/- warning: locally_bounded_map.to_fun_eq_coe clashes with [anonymous] -> [anonymous]
warning: locally_bounded_map.to_fun_eq_coe -> [anonymous] is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] {f : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2}, Eq.{max (succ u1) (succ u2)} (α -> β) (LocallyBoundedMap.toFun.{u1, u2} α β _inst_1 _inst_2 f) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) f)
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}}, (Nat -> α -> β) -> Nat -> (List.{u1} α) -> (List.{u2} β)
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.to_fun_eq_coe [anonymous]ₓ'. -/
@[simp]
theorem [anonymous] {f : LocallyBoundedMap α β} : f.toFun = (f : α → β) :=
  rfl
#align locally_bounded_map.to_fun_eq_coe [anonymous]

/- warning: locally_bounded_map.ext -> LocallyBoundedMap.ext is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] {f : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2} {g : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2}, (forall (a : α), Eq.{succ u2} β (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) f a) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) g a)) -> (Eq.{max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) f g)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Bornology.{u2} α] [_inst_2 : Bornology.{u1} β] {f : LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2} {g : LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2}, (forall (a : α), Eq.{succ u1} ((fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) a) (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u2 u1, u2, u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2)) f a) (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u2 u1, u2, u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2)) g a)) -> (Eq.{max (succ u2) (succ u1)} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) f g)
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.ext LocallyBoundedMap.extₓ'. -/
@[ext]
theorem ext {f g : LocallyBoundedMap α β} (h : ∀ a, f a = g a) : f = g :=
  FunLike.ext f g h
#align locally_bounded_map.ext LocallyBoundedMap.ext

/- warning: locally_bounded_map.copy -> LocallyBoundedMap.copy is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] (f : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (f' : α -> β), (Eq.{max (succ u1) (succ u2)} (α -> β) f' (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) f)) -> (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2)
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] (f : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (f' : α -> β), (Eq.{max (succ u1) (succ u2)} (α -> β) f' (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u1 u2, u1, u2} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2)) f)) -> (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2)
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.copy LocallyBoundedMap.copyₓ'. -/
/-- Copy of a `locally_bounded_map` with a new `to_fun` equal to the old one. Useful to fix
definitional equalities. -/
protected def copy (f : LocallyBoundedMap α β) (f' : α → β) (h : f' = f) : LocallyBoundedMap α β :=
  ⟨f', h.symm ▸ f.comap_cobounded_le'⟩
#align locally_bounded_map.copy LocallyBoundedMap.copy

/- warning: locally_bounded_map.coe_copy -> LocallyBoundedMap.coe_copy is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] (f : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (f' : α -> β) (h : Eq.{max (succ u1) (succ u2)} (α -> β) f' (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) f)), Eq.{max (succ u1) (succ u2)} (α -> β) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) (LocallyBoundedMap.copy.{u1, u2} α β _inst_1 _inst_2 f f' h)) f'
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Bornology.{u2} α] [_inst_2 : Bornology.{u1} β] (f : LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) (f' : α -> β) (h : Eq.{max (succ u2) (succ u1)} (α -> β) f' (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u2 u1, u2, u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2)) f)), Eq.{max (succ u2) (succ u1)} (forall (ᾰ : α), (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) ᾰ) (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u2 u1, u2, u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2)) (LocallyBoundedMap.copy.{u2, u1} α β _inst_1 _inst_2 f f' h)) f'
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.coe_copy LocallyBoundedMap.coe_copyₓ'. -/
@[simp]
theorem coe_copy (f : LocallyBoundedMap α β) (f' : α → β) (h : f' = f) : ⇑(f.copy f' h) = f' :=
  rfl
#align locally_bounded_map.coe_copy LocallyBoundedMap.coe_copy

/- warning: locally_bounded_map.copy_eq -> LocallyBoundedMap.copy_eq is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] (f : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (f' : α -> β) (h : Eq.{max (succ u1) (succ u2)} (α -> β) f' (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) f)), Eq.{max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (LocallyBoundedMap.copy.{u1, u2} α β _inst_1 _inst_2 f f' h) f
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Bornology.{u2} α] [_inst_2 : Bornology.{u1} β] (f : LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) (f' : α -> β) (h : Eq.{max (succ u2) (succ u1)} (α -> β) f' (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u2 u1, u2, u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2)) f)), Eq.{max (succ u2) (succ u1)} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) (LocallyBoundedMap.copy.{u2, u1} α β _inst_1 _inst_2 f f' h) f
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.copy_eq LocallyBoundedMap.copy_eqₓ'. -/
theorem copy_eq (f : LocallyBoundedMap α β) (f' : α → β) (h : f' = f) : f.copy f' h = f :=
  FunLike.ext' h
#align locally_bounded_map.copy_eq LocallyBoundedMap.copy_eq

#print LocallyBoundedMap.ofMapBounded /-
/-- Construct a `locally_bounded_map` from the fact that the function maps bounded sets to bounded
sets. -/
def ofMapBounded (f : α → β) (h) : LocallyBoundedMap α β :=
  ⟨f, comap_cobounded_le_iff.2 h⟩
#align locally_bounded_map.of_map_bounded LocallyBoundedMap.ofMapBounded
-/

/- warning: locally_bounded_map.coe_of_map_bounded -> LocallyBoundedMap.coe_ofMapBounded is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] (f : α -> β) {h : forall {{s : Set.{u1} α}}, (Bornology.IsBounded.{u1} α _inst_1 s) -> (Bornology.IsBounded.{u2} β _inst_2 (Set.image.{u1, u2} α β f s))}, Eq.{max (succ u1) (succ u2)} (α -> β) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) (LocallyBoundedMap.ofMapBounded.{u1, u2} α β _inst_1 _inst_2 f h)) f
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Bornology.{u2} α] [_inst_2 : Bornology.{u1} β] (f : α -> β) {h : forall {{s : Set.{u2} α}}, (Bornology.IsBounded.{u2} α _inst_1 s) -> (Bornology.IsBounded.{u1} β _inst_2 (Set.image.{u2, u1} α β f s))}, Eq.{max (succ u2) (succ u1)} (forall (ᾰ : α), (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) ᾰ) (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u2 u1, u2, u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2)) (LocallyBoundedMap.ofMapBounded.{u2, u1} α β _inst_1 _inst_2 f h)) f
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.coe_of_map_bounded LocallyBoundedMap.coe_ofMapBoundedₓ'. -/
@[simp]
theorem coe_ofMapBounded (f : α → β) {h} : ⇑(ofMapBounded f h) = f :=
  rfl
#align locally_bounded_map.coe_of_map_bounded LocallyBoundedMap.coe_ofMapBounded

/- warning: locally_bounded_map.of_map_bounded_apply -> LocallyBoundedMap.ofMapBounded_apply is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] (f : α -> β) {h : forall {{s : Set.{u1} α}}, (Bornology.IsBounded.{u1} α _inst_1 s) -> (Bornology.IsBounded.{u2} β _inst_2 (Set.image.{u1, u2} α β f s))} (a : α), Eq.{succ u2} β (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) (LocallyBoundedMap.ofMapBounded.{u1, u2} α β _inst_1 _inst_2 f h) a) (f a)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Bornology.{u2} α] [_inst_2 : Bornology.{u1} β] (f : α -> β) {h : forall {{s : Set.{u2} α}}, (Bornology.IsBounded.{u2} α _inst_1 s) -> (Bornology.IsBounded.{u1} β _inst_2 (Set.image.{u2, u1} α β f s))} (a : α), Eq.{succ u1} ((fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) a) (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u2 u1, u2, u1} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2)) (LocallyBoundedMap.ofMapBounded.{u2, u1} α β _inst_1 _inst_2 f h) a) (f a)
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.of_map_bounded_apply LocallyBoundedMap.ofMapBounded_applyₓ'. -/
@[simp]
theorem ofMapBounded_apply (f : α → β) {h} (a : α) : ofMapBounded f h a = f a :=
  rfl
#align locally_bounded_map.of_map_bounded_apply LocallyBoundedMap.ofMapBounded_apply

variable (α)

#print LocallyBoundedMap.id /-
/-- `id` as a `locally_bounded_map`. -/
protected def id : LocallyBoundedMap α α :=
  ⟨id, comap_id.le⟩
#align locally_bounded_map.id LocallyBoundedMap.id
-/

instance : Inhabited (LocallyBoundedMap α α) :=
  ⟨LocallyBoundedMap.id α⟩

/- warning: locally_bounded_map.coe_id -> LocallyBoundedMap.coe_id is a dubious translation:
lean 3 declaration is
  forall (α : Type.{u1}) [_inst_1 : Bornology.{u1} α], Eq.{succ u1} (α -> α) (coeFn.{succ u1, succ u1} (LocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1) (fun (_x : LocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1) => α -> α) (LocallyBoundedMap.hasCoeToFun.{u1, u1} α α _inst_1 _inst_1) (LocallyBoundedMap.id.{u1} α _inst_1)) (id.{succ u1} α)
but is expected to have type
  forall (α : Type.{u1}) [_inst_1 : Bornology.{u1} α], Eq.{succ u1} (forall (ᾰ : α), (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => α) ᾰ) (FunLike.coe.{succ u1, succ u1, succ u1} (LocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => α) _x) (LocallyBoundedMapClass.toFunLike.{u1, u1, u1} (LocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1) α α _inst_1 _inst_1 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1)) (LocallyBoundedMap.id.{u1} α _inst_1)) (id.{succ u1} α)
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.coe_id LocallyBoundedMap.coe_idₓ'. -/
@[simp]
theorem coe_id : ⇑(LocallyBoundedMap.id α) = id :=
  rfl
#align locally_bounded_map.coe_id LocallyBoundedMap.coe_id

variable {α}

/- warning: locally_bounded_map.id_apply -> LocallyBoundedMap.id_apply is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Bornology.{u1} α] (a : α), Eq.{succ u1} α (coeFn.{succ u1, succ u1} (LocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1) (fun (_x : LocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1) => α -> α) (LocallyBoundedMap.hasCoeToFun.{u1, u1} α α _inst_1 _inst_1) (LocallyBoundedMap.id.{u1} α _inst_1) a) a
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Bornology.{u1} α] (a : α), Eq.{succ u1} ((fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => α) a) (FunLike.coe.{succ u1, succ u1, succ u1} (LocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => α) _x) (LocallyBoundedMapClass.toFunLike.{u1, u1, u1} (LocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1) α α _inst_1 _inst_1 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u1, u1} α α _inst_1 _inst_1)) (LocallyBoundedMap.id.{u1} α _inst_1) a) a
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.id_apply LocallyBoundedMap.id_applyₓ'. -/
@[simp]
theorem id_apply (a : α) : LocallyBoundedMap.id α a = a :=
  rfl
#align locally_bounded_map.id_apply LocallyBoundedMap.id_apply

#print LocallyBoundedMap.comp /-
/-- Composition of `locally_bounded_map`s as a `locally_bounded_map`. -/
def comp (f : LocallyBoundedMap β γ) (g : LocallyBoundedMap α β) : LocallyBoundedMap α γ
    where
  toFun := f ∘ g
  comap_cobounded_le' :=
    comap_comap.ge.trans <| (comap_mono f.comap_cobounded_le').trans g.comap_cobounded_le'
#align locally_bounded_map.comp LocallyBoundedMap.comp
-/

/- warning: locally_bounded_map.coe_comp -> LocallyBoundedMap.coe_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] [_inst_3 : Bornology.{u3} γ] (f : LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) (g : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2), Eq.{max (succ u1) (succ u3)} (α -> γ) (coeFn.{max (succ u1) (succ u3), max (succ u1) (succ u3)} (LocallyBoundedMap.{u1, u3} α γ _inst_1 _inst_3) (fun (_x : LocallyBoundedMap.{u1, u3} α γ _inst_1 _inst_3) => α -> γ) (LocallyBoundedMap.hasCoeToFun.{u1, u3} α γ _inst_1 _inst_3) (LocallyBoundedMap.comp.{u1, u2, u3} α β γ _inst_1 _inst_2 _inst_3 f g)) (Function.comp.{succ u1, succ u2, succ u3} α β γ (coeFn.{max (succ u2) (succ u3), max (succ u2) (succ u3)} (LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) (fun (_x : LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) => β -> γ) (LocallyBoundedMap.hasCoeToFun.{u2, u3} β γ _inst_2 _inst_3) f) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) g))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u3}} {γ : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u3} β] [_inst_3 : Bornology.{u2} γ] (f : LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3) (g : LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2), Eq.{max (succ u1) (succ u2)} (forall (ᾰ : α), (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => γ) ᾰ) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (LocallyBoundedMap.{u1, u2} α γ _inst_1 _inst_3) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => γ) _x) (LocallyBoundedMapClass.toFunLike.{max u1 u2, u1, u2} (LocallyBoundedMap.{u1, u2} α γ _inst_1 _inst_3) α γ _inst_1 _inst_3 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u1, u2} α γ _inst_1 _inst_3)) (LocallyBoundedMap.comp.{u1, u3, u2} α β γ _inst_1 _inst_2 _inst_3 f g)) (Function.comp.{succ u1, succ u3, succ u2} α β γ (FunLike.coe.{max (succ u3) (succ u2), succ u3, succ u2} (LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3) β (fun (_x : β) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : β) => γ) _x) (LocallyBoundedMapClass.toFunLike.{max u3 u2, u3, u2} (LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3) β γ _inst_2 _inst_3 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3)) f) (FunLike.coe.{max (succ u1) (succ u3), succ u1, succ u3} (LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u1 u3, u1, u3} (LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2)) g))
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.coe_comp LocallyBoundedMap.coe_compₓ'. -/
@[simp]
theorem coe_comp (f : LocallyBoundedMap β γ) (g : LocallyBoundedMap α β) : ⇑(f.comp g) = f ∘ g :=
  rfl
#align locally_bounded_map.coe_comp LocallyBoundedMap.coe_comp

/- warning: locally_bounded_map.comp_apply -> LocallyBoundedMap.comp_apply is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] [_inst_3 : Bornology.{u3} γ] (f : LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) (g : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (a : α), Eq.{succ u3} γ (coeFn.{max (succ u1) (succ u3), max (succ u1) (succ u3)} (LocallyBoundedMap.{u1, u3} α γ _inst_1 _inst_3) (fun (_x : LocallyBoundedMap.{u1, u3} α γ _inst_1 _inst_3) => α -> γ) (LocallyBoundedMap.hasCoeToFun.{u1, u3} α γ _inst_1 _inst_3) (LocallyBoundedMap.comp.{u1, u2, u3} α β γ _inst_1 _inst_2 _inst_3 f g) a) (coeFn.{max (succ u2) (succ u3), max (succ u2) (succ u3)} (LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) (fun (_x : LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) => β -> γ) (LocallyBoundedMap.hasCoeToFun.{u2, u3} β γ _inst_2 _inst_3) f (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) g a))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u3}} {γ : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u3} β] [_inst_3 : Bornology.{u2} γ] (f : LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3) (g : LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2) (a : α), Eq.{succ u2} ((fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => γ) a) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (LocallyBoundedMap.{u1, u2} α γ _inst_1 _inst_3) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => γ) _x) (LocallyBoundedMapClass.toFunLike.{max u1 u2, u1, u2} (LocallyBoundedMap.{u1, u2} α γ _inst_1 _inst_3) α γ _inst_1 _inst_3 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u1, u2} α γ _inst_1 _inst_3)) (LocallyBoundedMap.comp.{u1, u3, u2} α β γ _inst_1 _inst_2 _inst_3 f g) a) (FunLike.coe.{max (succ u3) (succ u2), succ u3, succ u2} (LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3) β (fun (_x : β) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : β) => γ) _x) (LocallyBoundedMapClass.toFunLike.{max u3 u2, u3, u2} (LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3) β γ _inst_2 _inst_3 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3)) f (FunLike.coe.{max (succ u1) (succ u3), succ u1, succ u3} (LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u1 u3, u1, u3} (LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2)) g a))
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.comp_apply LocallyBoundedMap.comp_applyₓ'. -/
@[simp]
theorem comp_apply (f : LocallyBoundedMap β γ) (g : LocallyBoundedMap α β) (a : α) :
    f.comp g a = f (g a) :=
  rfl
#align locally_bounded_map.comp_apply LocallyBoundedMap.comp_apply

/- warning: locally_bounded_map.comp_assoc -> LocallyBoundedMap.comp_assoc is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] [_inst_3 : Bornology.{u3} γ] [_inst_4 : Bornology.{u4} δ] (f : LocallyBoundedMap.{u3, u4} γ δ _inst_3 _inst_4) (g : LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) (h : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2), Eq.{max (succ u1) (succ u4)} (LocallyBoundedMap.{u1, u4} α δ _inst_1 _inst_4) (LocallyBoundedMap.comp.{u1, u2, u4} α β δ _inst_1 _inst_2 _inst_4 (LocallyBoundedMap.comp.{u2, u3, u4} β γ δ _inst_2 _inst_3 _inst_4 f g) h) (LocallyBoundedMap.comp.{u1, u3, u4} α γ δ _inst_1 _inst_3 _inst_4 f (LocallyBoundedMap.comp.{u1, u2, u3} α β γ _inst_1 _inst_2 _inst_3 g h))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u4}} {δ : Type.{u3}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] [_inst_3 : Bornology.{u4} γ] [_inst_4 : Bornology.{u3} δ] (f : LocallyBoundedMap.{u4, u3} γ δ _inst_3 _inst_4) (g : LocallyBoundedMap.{u2, u4} β γ _inst_2 _inst_3) (h : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2), Eq.{max (succ u1) (succ u3)} (LocallyBoundedMap.{u1, u3} α δ _inst_1 _inst_4) (LocallyBoundedMap.comp.{u1, u2, u3} α β δ _inst_1 _inst_2 _inst_4 (LocallyBoundedMap.comp.{u2, u4, u3} β γ δ _inst_2 _inst_3 _inst_4 f g) h) (LocallyBoundedMap.comp.{u1, u4, u3} α γ δ _inst_1 _inst_3 _inst_4 f (LocallyBoundedMap.comp.{u1, u2, u4} α β γ _inst_1 _inst_2 _inst_3 g h))
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.comp_assoc LocallyBoundedMap.comp_assocₓ'. -/
@[simp]
theorem comp_assoc (f : LocallyBoundedMap γ δ) (g : LocallyBoundedMap β γ)
    (h : LocallyBoundedMap α β) : (f.comp g).comp h = f.comp (g.comp h) :=
  rfl
#align locally_bounded_map.comp_assoc LocallyBoundedMap.comp_assoc

/- warning: locally_bounded_map.comp_id -> LocallyBoundedMap.comp_id is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] (f : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2), Eq.{max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (LocallyBoundedMap.comp.{u1, u1, u2} α α β _inst_1 _inst_1 _inst_2 f (LocallyBoundedMap.id.{u1} α _inst_1)) f
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Bornology.{u2} α] [_inst_2 : Bornology.{u1} β] (f : LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2), Eq.{max (succ u2) (succ u1)} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) (LocallyBoundedMap.comp.{u2, u2, u1} α α β _inst_1 _inst_1 _inst_2 f (LocallyBoundedMap.id.{u2} α _inst_1)) f
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.comp_id LocallyBoundedMap.comp_idₓ'. -/
@[simp]
theorem comp_id (f : LocallyBoundedMap α β) : f.comp (LocallyBoundedMap.id α) = f :=
  ext fun a => rfl
#align locally_bounded_map.comp_id LocallyBoundedMap.comp_id

/- warning: locally_bounded_map.id_comp -> LocallyBoundedMap.id_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] (f : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2), Eq.{max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (LocallyBoundedMap.comp.{u1, u2, u2} α β β _inst_1 _inst_2 _inst_2 (LocallyBoundedMap.id.{u2} β _inst_2) f) f
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Bornology.{u2} α] [_inst_2 : Bornology.{u1} β] (f : LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2), Eq.{max (succ u2) (succ u1)} (LocallyBoundedMap.{u2, u1} α β _inst_1 _inst_2) (LocallyBoundedMap.comp.{u2, u1, u1} α β β _inst_1 _inst_2 _inst_2 (LocallyBoundedMap.id.{u1} β _inst_2) f) f
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.id_comp LocallyBoundedMap.id_compₓ'. -/
@[simp]
theorem id_comp (f : LocallyBoundedMap α β) : (LocallyBoundedMap.id β).comp f = f :=
  ext fun a => rfl
#align locally_bounded_map.id_comp LocallyBoundedMap.id_comp

/- warning: locally_bounded_map.cancel_right -> LocallyBoundedMap.cancel_right is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] [_inst_3 : Bornology.{u3} γ] {g₁ : LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3} {g₂ : LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3} {f : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2}, (Function.Surjective.{succ u1, succ u2} α β (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) (fun (_x : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) => α -> β) (LocallyBoundedMap.hasCoeToFun.{u1, u2} α β _inst_1 _inst_2) f)) -> (Iff (Eq.{max (succ u1) (succ u3)} (LocallyBoundedMap.{u1, u3} α γ _inst_1 _inst_3) (LocallyBoundedMap.comp.{u1, u2, u3} α β γ _inst_1 _inst_2 _inst_3 g₁ f) (LocallyBoundedMap.comp.{u1, u2, u3} α β γ _inst_1 _inst_2 _inst_3 g₂ f)) (Eq.{max (succ u2) (succ u3)} (LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) g₁ g₂))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u3}} {γ : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u3} β] [_inst_3 : Bornology.{u2} γ] {g₁ : LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3} {g₂ : LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3} {f : LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2}, (Function.Surjective.{succ u1, succ u3} α β (FunLike.coe.{max (succ u1) (succ u3), succ u1, succ u3} (LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2) α (fun (_x : α) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : α) => β) _x) (LocallyBoundedMapClass.toFunLike.{max u1 u3, u1, u3} (LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2) α β _inst_1 _inst_2 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2)) f)) -> (Iff (Eq.{max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α γ _inst_1 _inst_3) (LocallyBoundedMap.comp.{u1, u3, u2} α β γ _inst_1 _inst_2 _inst_3 g₁ f) (LocallyBoundedMap.comp.{u1, u3, u2} α β γ _inst_1 _inst_2 _inst_3 g₂ f)) (Eq.{max (succ u3) (succ u2)} (LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3) g₁ g₂))
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.cancel_right LocallyBoundedMap.cancel_rightₓ'. -/
theorem cancel_right {g₁ g₂ : LocallyBoundedMap β γ} {f : LocallyBoundedMap α β}
    (hf : Surjective f) : g₁.comp f = g₂.comp f ↔ g₁ = g₂ :=
  ⟨fun h => ext <| hf.forall.2 <| FunLike.ext_iff.1 h, congr_arg _⟩
#align locally_bounded_map.cancel_right LocallyBoundedMap.cancel_right

/- warning: locally_bounded_map.cancel_left -> LocallyBoundedMap.cancel_left is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u2} β] [_inst_3 : Bornology.{u3} γ] {g : LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3} {f₁ : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2} {f₂ : LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2}, (Function.Injective.{succ u2, succ u3} β γ (coeFn.{max (succ u2) (succ u3), max (succ u2) (succ u3)} (LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) (fun (_x : LocallyBoundedMap.{u2, u3} β γ _inst_2 _inst_3) => β -> γ) (LocallyBoundedMap.hasCoeToFun.{u2, u3} β γ _inst_2 _inst_3) g)) -> (Iff (Eq.{max (succ u1) (succ u3)} (LocallyBoundedMap.{u1, u3} α γ _inst_1 _inst_3) (LocallyBoundedMap.comp.{u1, u2, u3} α β γ _inst_1 _inst_2 _inst_3 g f₁) (LocallyBoundedMap.comp.{u1, u2, u3} α β γ _inst_1 _inst_2 _inst_3 g f₂)) (Eq.{max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α β _inst_1 _inst_2) f₁ f₂))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u3}} {γ : Type.{u2}} [_inst_1 : Bornology.{u1} α] [_inst_2 : Bornology.{u3} β] [_inst_3 : Bornology.{u2} γ] {g : LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3} {f₁ : LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2} {f₂ : LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2}, (Function.Injective.{succ u3, succ u2} β γ (FunLike.coe.{max (succ u3) (succ u2), succ u3, succ u2} (LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3) β (fun (_x : β) => (fun (x._@.Mathlib.Topology.Bornology.Hom._hyg.92 : β) => γ) _x) (LocallyBoundedMapClass.toFunLike.{max u3 u2, u3, u2} (LocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3) β γ _inst_2 _inst_3 (LocallyBoundedMap.instLocallyBoundedMapClassLocallyBoundedMap.{u3, u2} β γ _inst_2 _inst_3)) g)) -> (Iff (Eq.{max (succ u1) (succ u2)} (LocallyBoundedMap.{u1, u2} α γ _inst_1 _inst_3) (LocallyBoundedMap.comp.{u1, u3, u2} α β γ _inst_1 _inst_2 _inst_3 g f₁) (LocallyBoundedMap.comp.{u1, u3, u2} α β γ _inst_1 _inst_2 _inst_3 g f₂)) (Eq.{max (succ u1) (succ u3)} (LocallyBoundedMap.{u1, u3} α β _inst_1 _inst_2) f₁ f₂))
Case conversion may be inaccurate. Consider using '#align locally_bounded_map.cancel_left LocallyBoundedMap.cancel_leftₓ'. -/
theorem cancel_left {g : LocallyBoundedMap β γ} {f₁ f₂ : LocallyBoundedMap α β} (hg : Injective g) :
    g.comp f₁ = g.comp f₂ ↔ f₁ = f₂ :=
  ⟨fun h => ext fun a => hg <| by rw [← comp_apply, h, comp_apply], congr_arg _⟩
#align locally_bounded_map.cancel_left LocallyBoundedMap.cancel_left

end LocallyBoundedMap