/-
Copyright (c) 2019 Reid Barton. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Reid Barton, Scott Morrison

! This file was ported from Lean 3 source module category_theory.filtered
! leanprover-community/mathlib commit ee05e9ce1322178f0c12004eb93c00d2c8c00ed2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.FinCategory
import Mathbin.CategoryTheory.Limits.Cones
import Mathbin.CategoryTheory.Adjunction.Basic
import Mathbin.CategoryTheory.Category.Preorder
import Mathbin.CategoryTheory.Category.Ulift

/-!
# Filtered categories

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

A category is filtered if every finite diagram admits a cocone.
We give a simple characterisation of this condition as
1. for every pair of objects there exists another object "to the right",
2. for every pair of parallel morphisms there exists a morphism to the right so the compositions
   are equal, and
3. there exists some object.

Filtered colimits are often better behaved than arbitrary colimits.
See `category_theory/limits/types` for some details.

Filtered categories are nice because colimits indexed by filtered categories tend to be
easier to describe than general colimits (and more often preserved by functors).

In this file we show that any functor from a finite category to a filtered category admits a cocone:
* `cocone_nonempty [fin_category J] [is_filtered C] (F : J ⥤ C) : nonempty (cocone F)`
More generally,
for any finite collection of objects and morphisms between them in a filtered category
(even if not closed under composition) there exists some object `Z` receiving maps from all of them,
so that all the triangles (one edge from the finite set, two from morphisms to `Z`) commute.
This formulation is often more useful in practice and is available via `sup_exists`,
which takes a finset of objects, and an indexed family (indexed by source and target)
of finsets of morphisms.

Furthermore, we give special support for two diagram categories: The `bowtie` and the `tulip`.
This is because these shapes show up in the proofs that forgetful functors of algebraic categories
(e.g. `Mon`, `CommRing`, ...) preserve filtered colimits.

All of the above API, except for the `bowtie` and the `tulip`, is also provided for cofiltered
categories.

## See also
In `category_theory.limits.filtered_colimit_commutes_finite_limit` we show that filtered colimits
commute with finite limits.

-/

open Function

-- declare the `v`'s first; see `category_theory.category` for an explanation
universe w v v₁ u u₁ u₂

namespace CategoryTheory

variable (C : Type u) [Category.{v} C]

#print CategoryTheory.IsFilteredOrEmpty /-
/-- A category `is_filtered_or_empty` if
1. for every pair of objects there exists another object "to the right", and
2. for every pair of parallel morphisms there exists a morphism to the right so the compositions
   are equal.
-/
class IsFilteredOrEmpty : Prop where
  cocone_objs : ∀ X Y : C, ∃ (Z : _)(f : X ⟶ Z)(g : Y ⟶ Z), True
  cocone_maps : ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), ∃ (Z : _)(h : Y ⟶ Z), f ≫ h = g ≫ h
#align category_theory.is_filtered_or_empty CategoryTheory.IsFilteredOrEmpty
-/

#print CategoryTheory.IsFiltered /-
/-- A category `is_filtered` if
1. for every pair of objects there exists another object "to the right",
2. for every pair of parallel morphisms there exists a morphism to the right so the compositions
   are equal, and
3. there exists some object.

See <https://stacks.math.columbia.edu/tag/002V>. (They also define a diagram being filtered.)
-/
class IsFiltered extends IsFilteredOrEmpty C : Prop where
  [Nonempty : Nonempty C]
#align category_theory.is_filtered CategoryTheory.IsFiltered
-/

#print CategoryTheory.isFilteredOrEmpty_of_semilatticeSup /-
instance (priority := 100) isFilteredOrEmpty_of_semilatticeSup (α : Type u) [SemilatticeSup α] :
    IsFilteredOrEmpty α
    where
  cocone_objs X Y := ⟨X ⊔ Y, homOfLE le_sup_left, homOfLE le_sup_right, trivial⟩
  cocone_maps X Y f g := ⟨Y, 𝟙 _, by ext⟩
#align category_theory.is_filtered_or_empty_of_semilattice_sup CategoryTheory.isFilteredOrEmpty_of_semilatticeSup
-/

#print CategoryTheory.isFiltered_of_semilatticeSup_nonempty /-
instance (priority := 100) isFiltered_of_semilatticeSup_nonempty (α : Type u) [SemilatticeSup α]
    [Nonempty α] : IsFiltered α where
#align category_theory.is_filtered_of_semilattice_sup_nonempty CategoryTheory.isFiltered_of_semilatticeSup_nonempty
-/

#print CategoryTheory.isFilteredOrEmpty_of_directed_le /-
instance (priority := 100) isFilteredOrEmpty_of_directed_le (α : Type u) [Preorder α]
    [IsDirected α (· ≤ ·)] : IsFilteredOrEmpty α
    where
  cocone_objs X Y :=
    let ⟨Z, h1, h2⟩ := exists_ge_ge X Y
    ⟨Z, homOfLE h1, homOfLE h2, trivial⟩
  cocone_maps X Y f g := ⟨Y, 𝟙 _, by simp⟩
#align category_theory.is_filtered_or_empty_of_directed_le CategoryTheory.isFilteredOrEmpty_of_directed_le
-/

#print CategoryTheory.isFiltered_of_directed_le_nonempty /-
instance (priority := 100) isFiltered_of_directed_le_nonempty (α : Type u) [Preorder α]
    [IsDirected α (· ≤ ·)] [Nonempty α] : IsFiltered α where
#align category_theory.is_filtered_of_directed_le_nonempty CategoryTheory.isFiltered_of_directed_le_nonempty
-/

-- Sanity checks
example (α : Type u) [SemilatticeSup α] [OrderBot α] : IsFiltered α := by infer_instance

example (α : Type u) [SemilatticeSup α] [OrderTop α] : IsFiltered α := by infer_instance

instance : IsFiltered (Discrete PUnit)
    where
  cocone_objs X Y := ⟨⟨PUnit.unit⟩, ⟨⟨by decide⟩⟩, ⟨⟨by decide⟩⟩, trivial⟩
  cocone_maps X Y f g := ⟨⟨PUnit.unit⟩, ⟨⟨by decide⟩⟩, by decide⟩
  Nonempty := ⟨⟨PUnit.unit⟩⟩

namespace IsFiltered

section AllowEmpty

variable {C} [IsFilteredOrEmpty C]

theorem cocone_objs : ∀ X Y : C, ∃ (Z : _)(f : X ⟶ Z)(g : Y ⟶ Z), True :=
  IsFilteredOrEmpty.cocone_objs
#align category_theory.is_filtered.cocone_objs CategoryTheory.IsFiltered.cocone_objs

theorem cocone_maps : ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), ∃ (Z : _)(h : Y ⟶ Z), f ≫ h = g ≫ h :=
  IsFilteredOrEmpty.cocone_maps
#align category_theory.is_filtered.cocone_maps CategoryTheory.IsFiltered.cocone_maps

#print CategoryTheory.IsFiltered.max /-
/-- `max j j'` is an arbitrary choice of object to the right of both `j` and `j'`,
whose existence is ensured by `is_filtered`.
-/
noncomputable def max (j j' : C) : C :=
  (cocone_objs j j').some
#align category_theory.is_filtered.max CategoryTheory.IsFiltered.max
-/

#print CategoryTheory.IsFiltered.leftToMax /-
/-- `left_to_max j j'` is an arbitrary choice of morphism from `j` to `max j j'`,
whose existence is ensured by `is_filtered`.
-/
noncomputable def leftToMax (j j' : C) : j ⟶ max j j' :=
  (cocone_objs j j').choose_spec.some
#align category_theory.is_filtered.left_to_max CategoryTheory.IsFiltered.leftToMax
-/

#print CategoryTheory.IsFiltered.rightToMax /-
/-- `right_to_max j j'` is an arbitrary choice of morphism from `j'` to `max j j'`,
whose existence is ensured by `is_filtered`.
-/
noncomputable def rightToMax (j j' : C) : j' ⟶ max j j' :=
  (cocone_objs j j').choose_spec.choose_spec.some
#align category_theory.is_filtered.right_to_max CategoryTheory.IsFiltered.rightToMax
-/

#print CategoryTheory.IsFiltered.coeq /-
/-- `coeq f f'`, for morphisms `f f' : j ⟶ j'`, is an arbitrary choice of object
which admits a morphism `coeq_hom f f' : j' ⟶ coeq f f'` such that
`coeq_condition : f ≫ coeq_hom f f' = f' ≫ coeq_hom f f'`.
Its existence is ensured by `is_filtered`.
-/
noncomputable def coeq {j j' : C} (f f' : j ⟶ j') : C :=
  (cocone_maps f f').some
#align category_theory.is_filtered.coeq CategoryTheory.IsFiltered.coeq
-/

#print CategoryTheory.IsFiltered.coeqHom /-
/-- `coeq_hom f f'`, for morphisms `f f' : j ⟶ j'`, is an arbitrary choice of morphism
`coeq_hom f f' : j' ⟶ coeq f f'` such that
`coeq_condition : f ≫ coeq_hom f f' = f' ≫ coeq_hom f f'`.
Its existence is ensured by `is_filtered`.
-/
noncomputable def coeqHom {j j' : C} (f f' : j ⟶ j') : j' ⟶ coeq f f' :=
  (cocone_maps f f').choose_spec.some
#align category_theory.is_filtered.coeq_hom CategoryTheory.IsFiltered.coeqHom
-/

#print CategoryTheory.IsFiltered.coeq_condition /-
/-- `coeq_condition f f'`, for morphisms `f f' : j ⟶ j'`, is the proof that
`f ≫ coeq_hom f f' = f' ≫ coeq_hom f f'`.
-/
@[simp, reassoc.1]
theorem coeq_condition {j j' : C} (f f' : j ⟶ j') : f ≫ coeqHom f f' = f' ≫ coeqHom f f' :=
  (cocone_maps f f').choose_spec.choose_spec
#align category_theory.is_filtered.coeq_condition CategoryTheory.IsFiltered.coeq_condition
-/

end AllowEmpty

section Nonempty

open CategoryTheory.Limits

variable {C} [IsFiltered C]

#print CategoryTheory.IsFiltered.sup_objs_exists /-
/-- Any finite collection of objects in a filtered category has an object "to the right".
-/
theorem sup_objs_exists (O : Finset C) : ∃ S : C, ∀ {X}, X ∈ O → Nonempty (X ⟶ S) := by
  classical
    apply Finset.induction_on O
    · exact ⟨is_filtered.nonempty.some, by rintro - ⟨⟩⟩
    · rintro X O' nm ⟨S', w'⟩
      use max X S'
      rintro Y mY
      obtain rfl | h := eq_or_ne Y X
      · exact ⟨left_to_max _ _⟩
      · exact ⟨(w' (Finset.mem_of_mem_insert_of_ne mY h)).some ≫ right_to_max _ _⟩
#align category_theory.is_filtered.sup_objs_exists CategoryTheory.IsFiltered.sup_objs_exists
-/

variable (O : Finset C) (H : Finset (Σ'(X Y : C)(mX : X ∈ O)(mY : Y ∈ O), X ⟶ Y))

#print CategoryTheory.IsFiltered.sup_exists /-
/-- Given any `finset` of objects `{X, ...}` and
indexed collection of `finset`s of morphisms `{f, ...}` in `C`,
there exists an object `S`, with a morphism `T X : X ⟶ S` from each `X`,
such that the triangles commute: `f ≫ T Y = T X`, for `f : X ⟶ Y` in the `finset`.
-/
theorem sup_exists :
    ∃ (S : C)(T : ∀ {X : C}, X ∈ O → (X ⟶ S)),
      ∀ {X Y : C} (mX : X ∈ O) (mY : Y ∈ O) {f : X ⟶ Y},
        (⟨X, Y, mX, mY, f⟩ : Σ'(X Y : C)(mX : X ∈ O)(mY : Y ∈ O), X ⟶ Y) ∈ H → f ≫ T mY = T mX :=
  by
  classical
    apply Finset.induction_on H
    · obtain ⟨S, f⟩ := sup_objs_exists O
      refine' ⟨S, fun X mX => (f mX).some, _⟩
      rintro - - - - - ⟨⟩
    · rintro ⟨X, Y, mX, mY, f⟩ H' nmf ⟨S', T', w'⟩
      refine' ⟨coeq (f ≫ T' mY) (T' mX), fun Z mZ => T' mZ ≫ coeq_hom (f ≫ T' mY) (T' mX), _⟩
      intro X' Y' mX' mY' f' mf'
      rw [← category.assoc]
      by_cases h : X = X' ∧ Y = Y'
      · rcases h with ⟨rfl, rfl⟩
        by_cases hf : f = f'
        · subst hf
          apply coeq_condition
        · rw [@w' _ _ mX mY f' (by simpa [hf ∘ Eq.symm] using mf')]
      · rw [@w' _ _ mX' mY' f' _]
        apply Finset.mem_of_mem_insert_of_ne mf'
        contrapose! h
        obtain ⟨rfl, h⟩ := h
        rw [heq_iff_eq, PSigma.mk.inj_iff] at h
        exact ⟨rfl, h.1.symm⟩
#align category_theory.is_filtered.sup_exists CategoryTheory.IsFiltered.sup_exists
-/

#print CategoryTheory.IsFiltered.sup /-
/-- An arbitrary choice of object "to the right"
of a finite collection of objects `O` and morphisms `H`,
making all the triangles commute.
-/
noncomputable def sup : C :=
  (sup_exists O H).some
#align category_theory.is_filtered.sup CategoryTheory.IsFiltered.sup
-/

#print CategoryTheory.IsFiltered.toSup /-
/-- The morphisms to `sup O H`.
-/
noncomputable def toSup {X : C} (m : X ∈ O) : X ⟶ sup O H :=
  (sup_exists O H).choose_spec.some m
#align category_theory.is_filtered.to_sup CategoryTheory.IsFiltered.toSup
-/

#print CategoryTheory.IsFiltered.toSup_commutes /-
/-- The triangles of consisting of a morphism in `H` and the maps to `sup O H` commute.
-/
theorem toSup_commutes {X Y : C} (mX : X ∈ O) (mY : Y ∈ O) {f : X ⟶ Y}
    (mf : (⟨X, Y, mX, mY, f⟩ : Σ'(X Y : C)(mX : X ∈ O)(mY : Y ∈ O), X ⟶ Y) ∈ H) :
    f ≫ toSup O H mY = toSup O H mX :=
  (sup_exists O H).choose_spec.choose_spec mX mY mf
#align category_theory.is_filtered.to_sup_commutes CategoryTheory.IsFiltered.toSup_commutes
-/

variable {J : Type v} [SmallCategory J] [FinCategory J]

#print CategoryTheory.IsFiltered.cocone_nonempty /-
/-- If we have `is_filtered C`, then for any functor `F : J ⥤ C` with `fin_category J`,
there exists a cocone over `F`.
-/
theorem cocone_nonempty (F : J ⥤ C) : Nonempty (Cocone F) := by
  classical
    let O := finset.univ.image F.obj
    let H : Finset (Σ'(X Y : C)(mX : X ∈ O)(mY : Y ∈ O), X ⟶ Y) :=
      finset.univ.bUnion fun X : J =>
        finset.univ.bUnion fun Y : J =>
          finset.univ.image fun f : X ⟶ Y => ⟨F.obj X, F.obj Y, by simp, by simp, F.map f⟩
    obtain ⟨Z, f, w⟩ := sup_exists O H
    refine' ⟨⟨Z, ⟨fun X => f (by simp), _⟩⟩⟩
    intro j j' g
    dsimp
    simp only [category.comp_id]
    apply w
    simp only [Finset.mem_univ, Finset.mem_bunionᵢ, exists_and_left, exists_prop_of_true,
      Finset.mem_image]
    exact ⟨j, rfl, j', g, by simp⟩
#align category_theory.is_filtered.cocone_nonempty CategoryTheory.IsFiltered.cocone_nonempty
-/

#print CategoryTheory.IsFiltered.cocone /-
/-- An arbitrary choice of cocone over `F : J ⥤ C`, for `fin_category J` and `is_filtered C`.
-/
noncomputable def cocone (F : J ⥤ C) : Cocone F :=
  (cocone_nonempty F).some
#align category_theory.is_filtered.cocone CategoryTheory.IsFiltered.cocone
-/

variable {D : Type u₁} [Category.{v₁} D]

#print CategoryTheory.IsFiltered.of_right_adjoint /-
/-- If `C` is filtered, and we have a functor `R : C ⥤ D` with a left adjoint, then `D` is filtered.
-/
theorem of_right_adjoint {L : D ⥤ C} {R : C ⥤ D} (h : L ⊣ R) : IsFiltered D :=
  { cocone_objs := fun X Y =>
      ⟨_, h.homEquiv _ _ (leftToMax _ _), h.homEquiv _ _ (rightToMax _ _), ⟨⟩⟩
    cocone_maps := fun X Y f g =>
      ⟨_, h.homEquiv _ _ (coeqHom _ _), by
        rw [← h.hom_equiv_naturality_left, ← h.hom_equiv_naturality_left, coeq_condition]⟩
    Nonempty := IsFiltered.nonempty.map R.obj }
#align category_theory.is_filtered.of_right_adjoint CategoryTheory.IsFiltered.of_right_adjoint
-/

#print CategoryTheory.IsFiltered.of_isRightAdjoint /-
/-- If `C` is filtered, and we have a right adjoint functor `R : C ⥤ D`, then `D` is filtered. -/
theorem of_isRightAdjoint (R : C ⥤ D) [IsRightAdjoint R] : IsFiltered D :=
  of_right_adjoint (Adjunction.ofRightAdjoint R)
#align category_theory.is_filtered.of_is_right_adjoint CategoryTheory.IsFiltered.of_isRightAdjoint
-/

/- warning: category_theory.is_filtered.of_equivalence -> CategoryTheory.IsFiltered.of_equivalence is a dubious translation:
lean 3 declaration is
  forall {C : Type.{u3}} [_inst_1 : CategoryTheory.Category.{u1, u3} C] [_inst_2 : CategoryTheory.IsFiltered.{u1, u3} C _inst_1] {D : Type.{u4}} [_inst_5 : CategoryTheory.Category.{u2, u4} D], (CategoryTheory.Equivalence.{u1, u2, u3, u4} C _inst_1 D _inst_5) -> (CategoryTheory.IsFiltered.{u2, u4} D _inst_5)
but is expected to have type
  forall {C : Type.{u3}} [_inst_1 : CategoryTheory.Category.{u1, u3} C] [_inst_2 : CategoryTheory.IsFiltered.{u1, u3} C _inst_1] {D : Type.{u4}} [_inst_5 : CategoryTheory.Category.{u2, u4} D], (CategoryTheory.Equivalence.{u1, u2, u3, u4} C D _inst_1 _inst_5) -> (CategoryTheory.IsFiltered.{u2, u4} D _inst_5)
Case conversion may be inaccurate. Consider using '#align category_theory.is_filtered.of_equivalence CategoryTheory.IsFiltered.of_equivalenceₓ'. -/
/-- Being filtered is preserved by equivalence of categories. -/
theorem of_equivalence (h : C ≌ D) : IsFiltered D :=
  of_right_adjoint h.symm.toAdjunction
#align category_theory.is_filtered.of_equivalence CategoryTheory.IsFiltered.of_equivalence

end Nonempty

section SpecialShapes

variable {C} [IsFilteredOrEmpty C]

#print CategoryTheory.IsFiltered.max₃ /-
/-- `max₃ j₁ j₂ j₃` is an arbitrary choice of object to the right of `j₁`, `j₂` and `j₃`,
whose existence is ensured by `is_filtered`.
-/
noncomputable def max₃ (j₁ j₂ j₃ : C) : C :=
  max (max j₁ j₂) j₃
#align category_theory.is_filtered.max₃ CategoryTheory.IsFiltered.max₃
-/

#print CategoryTheory.IsFiltered.firstToMax₃ /-
/-- `first_to_max₃ j₁ j₂ j₃` is an arbitrary choice of morphism from `j₁` to `max₃ j₁ j₂ j₃`,
whose existence is ensured by `is_filtered`.
-/
noncomputable def firstToMax₃ (j₁ j₂ j₃ : C) : j₁ ⟶ max₃ j₁ j₂ j₃ :=
  leftToMax j₁ j₂ ≫ leftToMax (max j₁ j₂) j₃
#align category_theory.is_filtered.first_to_max₃ CategoryTheory.IsFiltered.firstToMax₃
-/

#print CategoryTheory.IsFiltered.secondToMax₃ /-
/-- `second_to_max₃ j₁ j₂ j₃` is an arbitrary choice of morphism from `j₂` to `max₃ j₁ j₂ j₃`,
whose existence is ensured by `is_filtered`.
-/
noncomputable def secondToMax₃ (j₁ j₂ j₃ : C) : j₂ ⟶ max₃ j₁ j₂ j₃ :=
  rightToMax j₁ j₂ ≫ leftToMax (max j₁ j₂) j₃
#align category_theory.is_filtered.second_to_max₃ CategoryTheory.IsFiltered.secondToMax₃
-/

#print CategoryTheory.IsFiltered.thirdToMax₃ /-
/-- `third_to_max₃ j₁ j₂ j₃` is an arbitrary choice of morphism from `j₃` to `max₃ j₁ j₂ j₃`,
whose existence is ensured by `is_filtered`.
-/
noncomputable def thirdToMax₃ (j₁ j₂ j₃ : C) : j₃ ⟶ max₃ j₁ j₂ j₃ :=
  rightToMax (max j₁ j₂) j₃
#align category_theory.is_filtered.third_to_max₃ CategoryTheory.IsFiltered.thirdToMax₃
-/

#print CategoryTheory.IsFiltered.coeq₃ /-
/-- `coeq₃ f g h`, for morphisms `f g h : j₁ ⟶ j₂`, is an arbitrary choice of object
which admits a morphism `coeq₃_hom f g h : j₂ ⟶ coeq₃ f g h` such that
`coeq₃_condition₁`, `coeq₃_condition₂` and `coeq₃_condition₃` are satisfied.
Its existence is ensured by `is_filtered`.
-/
noncomputable def coeq₃ {j₁ j₂ : C} (f g h : j₁ ⟶ j₂) : C :=
  coeq (coeqHom f g ≫ leftToMax (coeq f g) (coeq g h))
    (coeqHom g h ≫ rightToMax (coeq f g) (coeq g h))
#align category_theory.is_filtered.coeq₃ CategoryTheory.IsFiltered.coeq₃
-/

#print CategoryTheory.IsFiltered.coeq₃Hom /-
/-- `coeq₃_hom f g h`, for morphisms `f g h : j₁ ⟶ j₂`, is an arbitrary choice of morphism
`j₂ ⟶ coeq₃ f g h` such that `coeq₃_condition₁`, `coeq₃_condition₂` and `coeq₃_condition₃`
are satisfied. Its existence is ensured by `is_filtered`.
-/
noncomputable def coeq₃Hom {j₁ j₂ : C} (f g h : j₁ ⟶ j₂) : j₂ ⟶ coeq₃ f g h :=
  coeqHom f g ≫
    leftToMax (coeq f g) (coeq g h) ≫
      coeqHom (coeqHom f g ≫ leftToMax (coeq f g) (coeq g h))
        (coeqHom g h ≫ rightToMax (coeq f g) (coeq g h))
#align category_theory.is_filtered.coeq₃_hom CategoryTheory.IsFiltered.coeq₃Hom
-/

#print CategoryTheory.IsFiltered.coeq₃_condition₁ /-
theorem coeq₃_condition₁ {j₁ j₂ : C} (f g h : j₁ ⟶ j₂) : f ≫ coeq₃Hom f g h = g ≫ coeq₃Hom f g h :=
  by rw [coeq₃_hom, reassoc_of (coeq_condition f g)]
#align category_theory.is_filtered.coeq₃_condition₁ CategoryTheory.IsFiltered.coeq₃_condition₁
-/

#print CategoryTheory.IsFiltered.coeq₃_condition₂ /-
theorem coeq₃_condition₂ {j₁ j₂ : C} (f g h : j₁ ⟶ j₂) : g ≫ coeq₃Hom f g h = h ≫ coeq₃Hom f g h :=
  by
  dsimp [coeq₃_hom]
  slice_lhs 2 4 => rw [← category.assoc, coeq_condition _ _]
  slice_rhs 2 4 => rw [← category.assoc, coeq_condition _ _]
  slice_lhs 1 3 => rw [← category.assoc, coeq_condition _ _]
  simp only [category.assoc]
#align category_theory.is_filtered.coeq₃_condition₂ CategoryTheory.IsFiltered.coeq₃_condition₂
-/

#print CategoryTheory.IsFiltered.coeq₃_condition₃ /-
theorem coeq₃_condition₃ {j₁ j₂ : C} (f g h : j₁ ⟶ j₂) : f ≫ coeq₃Hom f g h = h ≫ coeq₃Hom f g h :=
  Eq.trans (coeq₃_condition₁ f g h) (coeq₃_condition₂ f g h)
#align category_theory.is_filtered.coeq₃_condition₃ CategoryTheory.IsFiltered.coeq₃_condition₃
-/

#print CategoryTheory.IsFiltered.span /-
/-- For every span `j ⟵ i ⟶ j'`, there
   exists a cocone `j ⟶ k ⟵ j'` such that the square commutes. -/
theorem span {i j j' : C} (f : i ⟶ j) (f' : i ⟶ j') :
    ∃ (k : C)(g : j ⟶ k)(g' : j' ⟶ k), f ≫ g = f' ≫ g' :=
  let ⟨K, G, G', _⟩ := cocone_objs j j'
  let ⟨k, e, he⟩ := cocone_maps (f ≫ G) (f' ≫ G')
  ⟨k, G ≫ e, G' ≫ e, by simpa only [← category.assoc] ⟩
#align category_theory.is_filtered.span CategoryTheory.IsFiltered.span
-/

#print CategoryTheory.IsFiltered.bowtie /-
/-- Given a "bowtie" of morphisms
```
 j₁   j₂
 |\  /|
 | \/ |
 | /\ |
 |/  \∣
 vv  vv
 k₁  k₂
```
in a filtered category, we can construct an object `s` and two morphisms from `k₁` and `k₂` to `s`,
making the resulting squares commute.
-/
theorem bowtie {j₁ j₂ k₁ k₂ : C} (f₁ : j₁ ⟶ k₁) (g₁ : j₁ ⟶ k₂) (f₂ : j₂ ⟶ k₁) (g₂ : j₂ ⟶ k₂) :
    ∃ (s : C)(α : k₁ ⟶ s)(β : k₂ ⟶ s), f₁ ≫ α = g₁ ≫ β ∧ f₂ ≫ α = g₂ ≫ β :=
  by
  obtain ⟨t, k₁t, k₂t, ht⟩ := span f₁ g₁
  obtain ⟨s, ts, hs⟩ := cocone_maps (f₂ ≫ k₁t) (g₂ ≫ k₂t)
  simp_rw [category.assoc] at hs
  exact ⟨s, k₁t ≫ ts, k₂t ≫ ts, by rw [reassoc_of ht], hs⟩
#align category_theory.is_filtered.bowtie CategoryTheory.IsFiltered.bowtie
-/

#print CategoryTheory.IsFiltered.tulip /-
/-- Given a "tulip" of morphisms
```
 j₁    j₂    j₃
 |\   / \   / |
 | \ /   \ /  |
 |  vv    vv  |
 \  k₁    k₂ /
  \         /
   \       /
    \     /
     \   /
      v v
       l
```
in a filtered category, we can construct an object `s` and three morphisms from `k₁`, `k₂` and `l`
to `s`, making the resulting squares commute.
-/
theorem tulip {j₁ j₂ j₃ k₁ k₂ l : C} (f₁ : j₁ ⟶ k₁) (f₂ : j₂ ⟶ k₁) (f₃ : j₂ ⟶ k₂) (f₄ : j₃ ⟶ k₂)
    (g₁ : j₁ ⟶ l) (g₂ : j₃ ⟶ l) :
    ∃ (s : C)(α : k₁ ⟶ s)(β : l ⟶ s)(γ : k₂ ⟶ s),
      f₁ ≫ α = g₁ ≫ β ∧ f₂ ≫ α = f₃ ≫ γ ∧ f₄ ≫ γ = g₂ ≫ β :=
  by
  obtain ⟨l', k₁l, k₂l, hl⟩ := span f₂ f₃
  obtain ⟨s, ls, l's, hs₁, hs₂⟩ := bowtie g₁ (f₁ ≫ k₁l) g₂ (f₄ ≫ k₂l)
  refine' ⟨s, k₁l ≫ l's, ls, k₂l ≫ l's, _, by rw [reassoc_of hl], _⟩ <;>
    simp only [hs₁, hs₂, category.assoc]
#align category_theory.is_filtered.tulip CategoryTheory.IsFiltered.tulip
-/

end SpecialShapes

end IsFiltered

#print CategoryTheory.IsCofilteredOrEmpty /-
/-- A category `is_cofiltered_or_empty` if
1. for every pair of objects there exists another object "to the left", and
2. for every pair of parallel morphisms there exists a morphism to the left so the compositions
   are equal.
-/
class IsCofilteredOrEmpty : Prop where
  cone_objs : ∀ X Y : C, ∃ (W : _)(f : W ⟶ X)(g : W ⟶ Y), True
  cone_maps : ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), ∃ (W : _)(h : W ⟶ X), h ≫ f = h ≫ g
#align category_theory.is_cofiltered_or_empty CategoryTheory.IsCofilteredOrEmpty
-/

#print CategoryTheory.IsCofiltered /-
/-- A category `is_cofiltered` if
1. for every pair of objects there exists another object "to the left",
2. for every pair of parallel morphisms there exists a morphism to the left so the compositions
   are equal, and
3. there exists some object.

See <https://stacks.math.columbia.edu/tag/04AZ>.
-/
class IsCofiltered extends IsCofilteredOrEmpty C : Prop where
  [Nonempty : Nonempty C]
#align category_theory.is_cofiltered CategoryTheory.IsCofiltered
-/

#print CategoryTheory.isCofilteredOrEmpty_of_semilatticeInf /-
instance (priority := 100) isCofilteredOrEmpty_of_semilatticeInf (α : Type u) [SemilatticeInf α] :
    IsCofilteredOrEmpty α
    where
  cone_objs X Y := ⟨X ⊓ Y, homOfLE inf_le_left, homOfLE inf_le_right, trivial⟩
  cone_maps X Y f g := ⟨X, 𝟙 _, by ext⟩
#align category_theory.is_cofiltered_or_empty_of_semilattice_inf CategoryTheory.isCofilteredOrEmpty_of_semilatticeInf
-/

#print CategoryTheory.isCofiltered_of_semilatticeInf_nonempty /-
instance (priority := 100) isCofiltered_of_semilatticeInf_nonempty (α : Type u) [SemilatticeInf α]
    [Nonempty α] : IsCofiltered α where
#align category_theory.is_cofiltered_of_semilattice_inf_nonempty CategoryTheory.isCofiltered_of_semilatticeInf_nonempty
-/

#print CategoryTheory.isCofilteredOrEmpty_of_directed_ge /-
instance (priority := 100) isCofilteredOrEmpty_of_directed_ge (α : Type u) [Preorder α]
    [IsDirected α (· ≥ ·)] : IsCofilteredOrEmpty α
    where
  cone_objs X Y :=
    let ⟨Z, hX, hY⟩ := exists_le_le X Y
    ⟨Z, homOfLE hX, homOfLE hY, trivial⟩
  cone_maps X Y f g := ⟨X, 𝟙 _, by simp⟩
#align category_theory.is_cofiltered_or_empty_of_directed_ge CategoryTheory.isCofilteredOrEmpty_of_directed_ge
-/

#print CategoryTheory.isCofiltered_of_directed_ge_nonempty /-
instance (priority := 100) isCofiltered_of_directed_ge_nonempty (α : Type u) [Preorder α]
    [IsDirected α (· ≥ ·)] [Nonempty α] : IsCofiltered α where
#align category_theory.is_cofiltered_of_directed_ge_nonempty CategoryTheory.isCofiltered_of_directed_ge_nonempty
-/

-- Sanity checks
example (α : Type u) [SemilatticeInf α] [OrderBot α] : IsCofiltered α := by infer_instance

example (α : Type u) [SemilatticeInf α] [OrderTop α] : IsCofiltered α := by infer_instance

instance : IsCofiltered (Discrete PUnit)
    where
  cone_objs X Y := ⟨⟨PUnit.unit⟩, ⟨⟨by decide⟩⟩, ⟨⟨by decide⟩⟩, trivial⟩
  cone_maps X Y f g := ⟨⟨PUnit.unit⟩, ⟨⟨by decide⟩⟩, by decide⟩
  Nonempty := ⟨⟨PUnit.unit⟩⟩

namespace IsCofiltered

section AllowEmpty

variable {C} [IsCofilteredOrEmpty C]

theorem cone_objs : ∀ X Y : C, ∃ (W : _)(f : W ⟶ X)(g : W ⟶ Y), True :=
  IsCofilteredOrEmpty.cone_objs
#align category_theory.is_cofiltered.cone_objs CategoryTheory.IsCofiltered.cone_objs

theorem cone_maps : ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), ∃ (W : _)(h : W ⟶ X), h ≫ f = h ≫ g :=
  IsCofilteredOrEmpty.cone_maps
#align category_theory.is_cofiltered.cone_maps CategoryTheory.IsCofiltered.cone_maps

#print CategoryTheory.IsCofiltered.min /-
/-- `min j j'` is an arbitrary choice of object to the left of both `j` and `j'`,
whose existence is ensured by `is_cofiltered`.
-/
noncomputable def min (j j' : C) : C :=
  (cone_objs j j').some
#align category_theory.is_cofiltered.min CategoryTheory.IsCofiltered.min
-/

#print CategoryTheory.IsCofiltered.minToLeft /-
/-- `min_to_left j j'` is an arbitrary choice of morphism from `min j j'` to `j`,
whose existence is ensured by `is_cofiltered`.
-/
noncomputable def minToLeft (j j' : C) : min j j' ⟶ j :=
  (cone_objs j j').choose_spec.some
#align category_theory.is_cofiltered.min_to_left CategoryTheory.IsCofiltered.minToLeft
-/

#print CategoryTheory.IsCofiltered.minToRight /-
/-- `min_to_right j j'` is an arbitrary choice of morphism from `min j j'` to `j'`,
whose existence is ensured by `is_cofiltered`.
-/
noncomputable def minToRight (j j' : C) : min j j' ⟶ j' :=
  (cone_objs j j').choose_spec.choose_spec.some
#align category_theory.is_cofiltered.min_to_right CategoryTheory.IsCofiltered.minToRight
-/

#print CategoryTheory.IsCofiltered.eq /-
/-- `eq f f'`, for morphisms `f f' : j ⟶ j'`, is an arbitrary choice of object
which admits a morphism `eq_hom f f' : eq f f' ⟶ j` such that
`eq_condition : eq_hom f f' ≫ f = eq_hom f f' ≫ f'`.
Its existence is ensured by `is_cofiltered`.
-/
noncomputable def eq {j j' : C} (f f' : j ⟶ j') : C :=
  (cone_maps f f').some
#align category_theory.is_cofiltered.eq CategoryTheory.IsCofiltered.eq
-/

#print CategoryTheory.IsCofiltered.eqHom /-
/-- `eq_hom f f'`, for morphisms `f f' : j ⟶ j'`, is an arbitrary choice of morphism
`eq_hom f f' : eq f f' ⟶ j` such that
`eq_condition : eq_hom f f' ≫ f = eq_hom f f' ≫ f'`.
Its existence is ensured by `is_cofiltered`.
-/
noncomputable def eqHom {j j' : C} (f f' : j ⟶ j') : eq f f' ⟶ j :=
  (cone_maps f f').choose_spec.some
#align category_theory.is_cofiltered.eq_hom CategoryTheory.IsCofiltered.eqHom
-/

#print CategoryTheory.IsCofiltered.eq_condition /-
/-- `eq_condition f f'`, for morphisms `f f' : j ⟶ j'`, is the proof that
`eq_hom f f' ≫ f = eq_hom f f' ≫ f'`.
-/
@[simp, reassoc.1]
theorem eq_condition {j j' : C} (f f' : j ⟶ j') : eqHom f f' ≫ f = eqHom f f' ≫ f' :=
  (cone_maps f f').choose_spec.choose_spec
#align category_theory.is_cofiltered.eq_condition CategoryTheory.IsCofiltered.eq_condition
-/

#print CategoryTheory.IsCofiltered.cospan /-
/-- For every cospan `j ⟶ i ⟵ j'`,
 there exists a cone `j ⟵ k ⟶ j'` such that the square commutes. -/
theorem cospan {i j j' : C} (f : j ⟶ i) (f' : j' ⟶ i) :
    ∃ (k : C)(g : k ⟶ j)(g' : k ⟶ j'), g ≫ f = g' ≫ f' :=
  let ⟨K, G, G', _⟩ := cone_objs j j'
  let ⟨k, e, he⟩ := cone_maps (G ≫ f) (G' ≫ f')
  ⟨k, e ≫ G, e ≫ G', by simpa only [category.assoc] using he⟩
#align category_theory.is_cofiltered.cospan CategoryTheory.IsCofiltered.cospan
-/

/- warning: category_theory.functor.ranges_directed -> CategoryTheory.Functor.ranges_directed is a dubious translation:
lean 3 declaration is
  forall {C : Type.{u2}} [_inst_1 : CategoryTheory.Category.{u1, u2} C] [_inst_2 : CategoryTheory.IsCofilteredOrEmpty.{u1, u2} C _inst_1] (F : CategoryTheory.Functor.{u1, u3, u2, succ u3} C _inst_1 Type.{u3} CategoryTheory.types.{u3}) (j : C), Directed.{u3, max 1 (succ u2) (succ u1)} (Set.{u3} (CategoryTheory.Functor.obj.{u1, u3, u2, succ u3} C _inst_1 Type.{u3} CategoryTheory.types.{u3} F j)) (PSigma.{succ u2, succ u1} C (fun (i : C) => Quiver.Hom.{succ u1, u2} C (CategoryTheory.CategoryStruct.toQuiver.{u1, u2} C (CategoryTheory.Category.toCategoryStruct.{u1, u2} C _inst_1)) i j)) (Superset.{u3} (Set.{u3} (CategoryTheory.Functor.obj.{u1, u3, u2, succ u3} C _inst_1 Type.{u3} CategoryTheory.types.{u3} F j)) (Set.hasSubset.{u3} (CategoryTheory.Functor.obj.{u1, u3, u2, succ u3} C _inst_1 Type.{u3} CategoryTheory.types.{u3} F j))) (fun (f : PSigma.{succ u2, succ u1} C (fun (i : C) => Quiver.Hom.{succ u1, u2} C (CategoryTheory.CategoryStruct.toQuiver.{u1, u2} C (CategoryTheory.Category.toCategoryStruct.{u1, u2} C _inst_1)) i j)) => Set.range.{u3, succ u3} (CategoryTheory.Functor.obj.{u1, u3, u2, succ u3} C _inst_1 Type.{u3} CategoryTheory.types.{u3} F j) (CategoryTheory.Functor.obj.{u1, u3, u2, succ u3} C _inst_1 Type.{u3} CategoryTheory.types.{u3} F (PSigma.fst.{succ u2, succ u1} C (fun (i : C) => Quiver.Hom.{succ u1, u2} C (CategoryTheory.CategoryStruct.toQuiver.{u1, u2} C (CategoryTheory.Category.toCategoryStruct.{u1, u2} C _inst_1)) i j) f)) (CategoryTheory.Functor.map.{u1, u3, u2, succ u3} C _inst_1 Type.{u3} CategoryTheory.types.{u3} F (PSigma.fst.{succ u2, succ u1} C (fun (i : C) => Quiver.Hom.{succ u1, u2} C (CategoryTheory.CategoryStruct.toQuiver.{u1, u2} C (CategoryTheory.Category.toCategoryStruct.{u1, u2} C _inst_1)) i j) f) j (PSigma.snd.{succ u2, succ u1} C (fun (i : C) => Quiver.Hom.{succ u1, u2} C (CategoryTheory.CategoryStruct.toQuiver.{u1, u2} C (CategoryTheory.Category.toCategoryStruct.{u1, u2} C _inst_1)) i j) f)))
but is expected to have type
  forall {C : Type.{u3}} [_inst_1 : CategoryTheory.Category.{u2, u3} C] [_inst_2 : CategoryTheory.IsCofilteredOrEmpty.{u2, u3} C _inst_1] (F : CategoryTheory.Functor.{u2, u1, u3, succ u1} C _inst_1 Type.{u1} CategoryTheory.types.{u1}) (j : C), Directed.{u1, max (succ u3) (succ u2)} (Set.{u1} (Prefunctor.obj.{succ u2, succ u1, u3, succ u1} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) Type.{u1} (CategoryTheory.CategoryStruct.toQuiver.{u1, succ u1} Type.{u1} (CategoryTheory.Category.toCategoryStruct.{u1, succ u1} Type.{u1} CategoryTheory.types.{u1})) (CategoryTheory.Functor.toPrefunctor.{u2, u1, u3, succ u1} C _inst_1 Type.{u1} CategoryTheory.types.{u1} F) j)) (PSigma.{succ u3, succ u2} C (fun (i : C) => Quiver.Hom.{succ u2, u3} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) i j)) (fun (x._@.Mathlib.CategoryTheory.Filtered._hyg.4398 : Set.{u1} (Prefunctor.obj.{succ u2, succ u1, u3, succ u1} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) Type.{u1} (CategoryTheory.CategoryStruct.toQuiver.{u1, succ u1} Type.{u1} (CategoryTheory.Category.toCategoryStruct.{u1, succ u1} Type.{u1} CategoryTheory.types.{u1})) (CategoryTheory.Functor.toPrefunctor.{u2, u1, u3, succ u1} C _inst_1 Type.{u1} CategoryTheory.types.{u1} F) j)) (x._@.Mathlib.CategoryTheory.Filtered._hyg.4400 : Set.{u1} (Prefunctor.obj.{succ u2, succ u1, u3, succ u1} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) Type.{u1} (CategoryTheory.CategoryStruct.toQuiver.{u1, succ u1} Type.{u1} (CategoryTheory.Category.toCategoryStruct.{u1, succ u1} Type.{u1} CategoryTheory.types.{u1})) (CategoryTheory.Functor.toPrefunctor.{u2, u1, u3, succ u1} C _inst_1 Type.{u1} CategoryTheory.types.{u1} F) j)) => Superset.{u1} (Set.{u1} (Prefunctor.obj.{succ u2, succ u1, u3, succ u1} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) Type.{u1} (CategoryTheory.CategoryStruct.toQuiver.{u1, succ u1} Type.{u1} (CategoryTheory.Category.toCategoryStruct.{u1, succ u1} Type.{u1} CategoryTheory.types.{u1})) (CategoryTheory.Functor.toPrefunctor.{u2, u1, u3, succ u1} C _inst_1 Type.{u1} CategoryTheory.types.{u1} F) j)) (Set.instHasSubsetSet.{u1} (Prefunctor.obj.{succ u2, succ u1, u3, succ u1} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) Type.{u1} (CategoryTheory.CategoryStruct.toQuiver.{u1, succ u1} Type.{u1} (CategoryTheory.Category.toCategoryStruct.{u1, succ u1} Type.{u1} CategoryTheory.types.{u1})) (CategoryTheory.Functor.toPrefunctor.{u2, u1, u3, succ u1} C _inst_1 Type.{u1} CategoryTheory.types.{u1} F) j)) x._@.Mathlib.CategoryTheory.Filtered._hyg.4398 x._@.Mathlib.CategoryTheory.Filtered._hyg.4400) (fun (f : PSigma.{succ u3, succ u2} C (fun (i : C) => Quiver.Hom.{succ u2, u3} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) i j)) => Set.range.{u1, succ u1} (Prefunctor.obj.{succ u2, succ u1, u3, succ u1} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) Type.{u1} (CategoryTheory.CategoryStruct.toQuiver.{u1, succ u1} Type.{u1} (CategoryTheory.Category.toCategoryStruct.{u1, succ u1} Type.{u1} CategoryTheory.types.{u1})) (CategoryTheory.Functor.toPrefunctor.{u2, u1, u3, succ u1} C _inst_1 Type.{u1} CategoryTheory.types.{u1} F) j) (Prefunctor.obj.{succ u2, succ u1, u3, succ u1} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) Type.{u1} (CategoryTheory.CategoryStruct.toQuiver.{u1, succ u1} Type.{u1} (CategoryTheory.Category.toCategoryStruct.{u1, succ u1} Type.{u1} CategoryTheory.types.{u1})) (CategoryTheory.Functor.toPrefunctor.{u2, u1, u3, succ u1} C _inst_1 Type.{u1} CategoryTheory.types.{u1} F) (PSigma.fst.{succ u3, succ u2} C (fun (i : C) => Quiver.Hom.{succ u2, u3} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) i j) f)) (Prefunctor.map.{succ u2, succ u1, u3, succ u1} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) Type.{u1} (CategoryTheory.CategoryStruct.toQuiver.{u1, succ u1} Type.{u1} (CategoryTheory.Category.toCategoryStruct.{u1, succ u1} Type.{u1} CategoryTheory.types.{u1})) (CategoryTheory.Functor.toPrefunctor.{u2, u1, u3, succ u1} C _inst_1 Type.{u1} CategoryTheory.types.{u1} F) (PSigma.fst.{succ u3, succ u2} C (fun (i : C) => Quiver.Hom.{succ u2, u3} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) i j) f) j (PSigma.snd.{succ u3, succ u2} C (fun (i : C) => Quiver.Hom.{succ u2, u3} C (CategoryTheory.CategoryStruct.toQuiver.{u2, u3} C (CategoryTheory.Category.toCategoryStruct.{u2, u3} C _inst_1)) i j) f)))
Case conversion may be inaccurate. Consider using '#align category_theory.functor.ranges_directed CategoryTheory.Functor.ranges_directedₓ'. -/
theorem CategoryTheory.Functor.ranges_directed (F : C ⥤ Type _) (j : C) :
    Directed (· ⊇ ·) fun f : Σ'i, i ⟶ j => Set.range (F.map f.2) := fun ⟨i, ij⟩ ⟨k, kj⟩ =>
  by
  let ⟨l, li, lk, e⟩ := cospan ij kj
  refine' ⟨⟨l, lk ≫ kj⟩, e ▸ _, _⟩ <;> simp_rw [F.map_comp] <;> apply Set.range_comp_subset_range
#align category_theory.functor.ranges_directed CategoryTheory.Functor.ranges_directed

end AllowEmpty

section Nonempty

open CategoryTheory.Limits

variable {C} [IsCofiltered C]

#print CategoryTheory.IsCofiltered.inf_objs_exists /-
/-- Any finite collection of objects in a cofiltered category has an object "to the left".
-/
theorem inf_objs_exists (O : Finset C) : ∃ S : C, ∀ {X}, X ∈ O → Nonempty (S ⟶ X) := by
  classical
    apply Finset.induction_on O
    · exact ⟨is_cofiltered.nonempty.some, by rintro - ⟨⟩⟩
    · rintro X O' nm ⟨S', w'⟩
      use min X S'
      rintro Y mY
      obtain rfl | h := eq_or_ne Y X
      · exact ⟨min_to_left _ _⟩
      · exact ⟨min_to_right _ _ ≫ (w' (Finset.mem_of_mem_insert_of_ne mY h)).some⟩
#align category_theory.is_cofiltered.inf_objs_exists CategoryTheory.IsCofiltered.inf_objs_exists
-/

variable (O : Finset C) (H : Finset (Σ'(X Y : C)(mX : X ∈ O)(mY : Y ∈ O), X ⟶ Y))

#print CategoryTheory.IsCofiltered.inf_exists /-
/-- Given any `finset` of objects `{X, ...}` and
indexed collection of `finset`s of morphisms `{f, ...}` in `C`,
there exists an object `S`, with a morphism `T X : S ⟶ X` from each `X`,
such that the triangles commute: `T X ≫ f = T Y`, for `f : X ⟶ Y` in the `finset`.
-/
theorem inf_exists :
    ∃ (S : C)(T : ∀ {X : C}, X ∈ O → (S ⟶ X)),
      ∀ {X Y : C} (mX : X ∈ O) (mY : Y ∈ O) {f : X ⟶ Y},
        (⟨X, Y, mX, mY, f⟩ : Σ'(X Y : C)(mX : X ∈ O)(mY : Y ∈ O), X ⟶ Y) ∈ H → T mX ≫ f = T mY :=
  by
  classical
    apply Finset.induction_on H
    · obtain ⟨S, f⟩ := inf_objs_exists O
      refine' ⟨S, fun X mX => (f mX).some, _⟩
      rintro - - - - - ⟨⟩
    · rintro ⟨X, Y, mX, mY, f⟩ H' nmf ⟨S', T', w'⟩
      refine' ⟨Eq (T' mX ≫ f) (T' mY), fun Z mZ => eq_hom (T' mX ≫ f) (T' mY) ≫ T' mZ, _⟩
      intro X' Y' mX' mY' f' mf'
      rw [category.assoc]
      by_cases h : X = X' ∧ Y = Y'
      · rcases h with ⟨rfl, rfl⟩
        by_cases hf : f = f'
        · subst hf
          apply eq_condition
        · rw [@w' _ _ mX mY f' (by simpa [hf ∘ Eq.symm] using mf')]
      · rw [@w' _ _ mX' mY' f' _]
        apply Finset.mem_of_mem_insert_of_ne mf'
        contrapose! h
        obtain ⟨rfl, h⟩ := h
        rw [heq_iff_eq, PSigma.mk.inj_iff] at h
        exact ⟨rfl, h.1.symm⟩
#align category_theory.is_cofiltered.inf_exists CategoryTheory.IsCofiltered.inf_exists
-/

#print CategoryTheory.IsCofiltered.inf /-
/-- An arbitrary choice of object "to the left"
of a finite collection of objects `O` and morphisms `H`,
making all the triangles commute.
-/
noncomputable def inf : C :=
  (inf_exists O H).some
#align category_theory.is_cofiltered.inf CategoryTheory.IsCofiltered.inf
-/

#print CategoryTheory.IsCofiltered.infTo /-
/-- The morphisms from `inf O H`.
-/
noncomputable def infTo {X : C} (m : X ∈ O) : inf O H ⟶ X :=
  (inf_exists O H).choose_spec.some m
#align category_theory.is_cofiltered.inf_to CategoryTheory.IsCofiltered.infTo
-/

#print CategoryTheory.IsCofiltered.infTo_commutes /-
/-- The triangles consisting of a morphism in `H` and the maps from `inf O H` commute.
-/
theorem infTo_commutes {X Y : C} (mX : X ∈ O) (mY : Y ∈ O) {f : X ⟶ Y}
    (mf : (⟨X, Y, mX, mY, f⟩ : Σ'(X Y : C)(mX : X ∈ O)(mY : Y ∈ O), X ⟶ Y) ∈ H) :
    infTo O H mX ≫ f = infTo O H mY :=
  (inf_exists O H).choose_spec.choose_spec mX mY mf
#align category_theory.is_cofiltered.inf_to_commutes CategoryTheory.IsCofiltered.infTo_commutes
-/

variable {J : Type w} [SmallCategory J] [FinCategory J]

#print CategoryTheory.IsCofiltered.cone_nonempty /-
/-- If we have `is_cofiltered C`, then for any functor `F : J ⥤ C` with `fin_category J`,
there exists a cone over `F`.
-/
theorem cone_nonempty (F : J ⥤ C) : Nonempty (Cone F) := by
  classical
    let O := finset.univ.image F.obj
    let H : Finset (Σ'(X Y : C)(mX : X ∈ O)(mY : Y ∈ O), X ⟶ Y) :=
      finset.univ.bUnion fun X : J =>
        finset.univ.bUnion fun Y : J =>
          finset.univ.image fun f : X ⟶ Y => ⟨F.obj X, F.obj Y, by simp, by simp, F.map f⟩
    obtain ⟨Z, f, w⟩ := inf_exists O H
    refine' ⟨⟨Z, ⟨fun X => f (by simp), _⟩⟩⟩
    intro j j' g
    dsimp
    simp only [category.id_comp]
    symm
    apply w
    simp only [Finset.mem_univ, Finset.mem_bunionᵢ, exists_and_left, exists_prop_of_true,
      Finset.mem_image]
    exact ⟨j, rfl, j', g, by simp⟩
#align category_theory.is_cofiltered.cone_nonempty CategoryTheory.IsCofiltered.cone_nonempty
-/

#print CategoryTheory.IsCofiltered.cone /-
/-- An arbitrary choice of cone over `F : J ⥤ C`, for `fin_category J` and `is_cofiltered C`.
-/
noncomputable def cone (F : J ⥤ C) : Cone F :=
  (cone_nonempty F).some
#align category_theory.is_cofiltered.cone CategoryTheory.IsCofiltered.cone
-/

variable {D : Type u₁} [Category.{v₁} D]

#print CategoryTheory.IsCofiltered.of_left_adjoint /-
/-- If `C` is cofiltered, and we have a functor `L : C ⥤ D` with a right adjoint,
then `D` is cofiltered.
-/
theorem of_left_adjoint {L : C ⥤ D} {R : D ⥤ C} (h : L ⊣ R) : IsCofiltered D :=
  { cone_objs := fun X Y =>
      ⟨L.obj (min (R.obj X) (R.obj Y)), (h.homEquiv _ X).symm (minToLeft _ _),
        (h.homEquiv _ Y).symm (minToRight _ _), ⟨⟩⟩
    cone_maps := fun X Y f g =>
      ⟨L.obj (eq (R.map f) (R.map g)), (h.homEquiv _ _).symm (eqHom _ _), by
        rw [← h.hom_equiv_naturality_right_symm, ← h.hom_equiv_naturality_right_symm, eq_condition]⟩
    Nonempty := IsCofiltered.nonempty.map L.obj }
#align category_theory.is_cofiltered.of_left_adjoint CategoryTheory.IsCofiltered.of_left_adjoint
-/

#print CategoryTheory.IsCofiltered.of_isLeftAdjoint /-
/-- If `C` is cofiltered, and we have a left adjoint functor `L : C ⥤ D`, then `D` is cofiltered. -/
theorem of_isLeftAdjoint (L : C ⥤ D) [IsLeftAdjoint L] : IsCofiltered D :=
  of_left_adjoint (Adjunction.ofLeftAdjoint L)
#align category_theory.is_cofiltered.of_is_left_adjoint CategoryTheory.IsCofiltered.of_isLeftAdjoint
-/

/- warning: category_theory.is_cofiltered.of_equivalence -> CategoryTheory.IsCofiltered.of_equivalence is a dubious translation:
lean 3 declaration is
  forall {C : Type.{u3}} [_inst_1 : CategoryTheory.Category.{u1, u3} C] [_inst_2 : CategoryTheory.IsCofiltered.{u1, u3} C _inst_1] {D : Type.{u4}} [_inst_5 : CategoryTheory.Category.{u2, u4} D], (CategoryTheory.Equivalence.{u1, u2, u3, u4} C _inst_1 D _inst_5) -> (CategoryTheory.IsCofiltered.{u2, u4} D _inst_5)
but is expected to have type
  forall {C : Type.{u3}} [_inst_1 : CategoryTheory.Category.{u1, u3} C] [_inst_2 : CategoryTheory.IsCofiltered.{u1, u3} C _inst_1] {D : Type.{u4}} [_inst_5 : CategoryTheory.Category.{u2, u4} D], (CategoryTheory.Equivalence.{u1, u2, u3, u4} C D _inst_1 _inst_5) -> (CategoryTheory.IsCofiltered.{u2, u4} D _inst_5)
Case conversion may be inaccurate. Consider using '#align category_theory.is_cofiltered.of_equivalence CategoryTheory.IsCofiltered.of_equivalenceₓ'. -/
/-- Being cofiltered is preserved by equivalence of categories. -/
theorem of_equivalence (h : C ≌ D) : IsCofiltered D :=
  of_left_adjoint h.toAdjunction
#align category_theory.is_cofiltered.of_equivalence CategoryTheory.IsCofiltered.of_equivalence

end Nonempty

end IsCofiltered

section Opposite

open Opposite

#print CategoryTheory.isCofiltered_op_of_isFiltered /-
instance isCofiltered_op_of_isFiltered [IsFiltered C] : IsCofiltered Cᵒᵖ
    where
  cone_objs X Y :=
    ⟨op (IsFiltered.max X.unop Y.unop), (IsFiltered.leftToMax _ _).op,
      (IsFiltered.rightToMax _ _).op, trivial⟩
  cone_maps X Y f g :=
    ⟨op (IsFiltered.coeq f.unop g.unop), (IsFiltered.coeqHom _ _).op,
      by
      rw [show f = f.unop.op by simp, show g = g.unop.op by simp, ← op_comp, ← op_comp]
      congr 1
      exact is_filtered.coeq_condition f.unop g.unop⟩
  Nonempty := ⟨op IsFiltered.nonempty.some⟩
#align category_theory.is_cofiltered_op_of_is_filtered CategoryTheory.isCofiltered_op_of_isFiltered
-/

#print CategoryTheory.isFiltered_op_of_isCofiltered /-
instance isFiltered_op_of_isCofiltered [IsCofiltered C] : IsFiltered Cᵒᵖ
    where
  cocone_objs X Y :=
    ⟨op (IsCofiltered.min X.unop Y.unop), (IsCofiltered.minToLeft X.unop Y.unop).op,
      (IsCofiltered.minToRight X.unop Y.unop).op, trivial⟩
  cocone_maps X Y f g :=
    ⟨op (IsCofiltered.eq f.unop g.unop), (IsCofiltered.eqHom f.unop g.unop).op,
      by
      rw [show f = f.unop.op by simp, show g = g.unop.op by simp, ← op_comp, ← op_comp]
      congr 1
      exact is_cofiltered.eq_condition f.unop g.unop⟩
  Nonempty := ⟨op IsCofiltered.nonempty.some⟩
#align category_theory.is_filtered_op_of_is_cofiltered CategoryTheory.isFiltered_op_of_isCofiltered
-/

end Opposite

section ULift

instance [IsFiltered C] : IsFiltered (ULift.{u₂} C) :=
  IsFiltered.of_equivalence ULift.equivalence

instance [IsCofiltered C] : IsCofiltered (ULift.{u₂} C) :=
  IsCofiltered.of_equivalence ULift.equivalence

instance [IsFiltered C] : IsFiltered (ULiftHom C) :=
  IsFiltered.of_equivalence ULiftHom.equiv

instance [IsCofiltered C] : IsCofiltered (ULiftHom C) :=
  IsCofiltered.of_equivalence ULiftHom.equiv

instance [IsFiltered C] : IsFiltered (AsSmall C) :=
  IsFiltered.of_equivalence AsSmall.equiv

instance [IsCofiltered C] : IsCofiltered (AsSmall C) :=
  IsCofiltered.of_equivalence AsSmall.equiv

end ULift

end CategoryTheory


import M4R.Algebra.Ring.Semiring

namespace M4R

  namespace NCRing
    open Group NCSemiring

    protected instance Product (α₁ : Type _) (α₂ : Type _) [NCRing α₁] [NCRing α₂] : NCRing (α₁ × α₂) where
      toNeg := (Group.Product α₁ α₂).toNeg
      add_neg := (Group.Product α₁ α₂).add_neg

    protected instance multi_product {ι : Type _} (fι : ι → Type _) [∀ i, NCRing (fι i)] : NCRing (MultiProd fι) where
      toNeg   := (Group.multi_product fι).toNeg
      add_neg := (Group.multi_product fι).add_neg

    theorem neg_mul [NCRing α] (a b : α) : -a * b = -(a * b) := by
      rw [←add_right_cancel _ _ (a * b), neg_add, ←mul_distrib_right, neg_add, zero_mul]
    theorem mul_neg [NCRing α] (a b : α) : a * -b = -(a * b) := by
      rw [←add_right_cancel _ _ (a * b), neg_add, ←mul_distrib_left, neg_add, mul_zero]
    theorem mul_neg_swap [NCRing α] (a b : α) : a * -b = -a * b := by
      rw [mul_neg, neg_mul]

    theorem neg_one_mul [NCRing α] (a : α) : -1 * a = -a := by
      rw [neg_mul, one_mul]
    theorem neg_one_mul_add [NCRing α] (a b : α) : -1 * (a + b) = -a + -b := by
      rw [mul_distrib_left, neg_one_mul, neg_one_mul]
    theorem neg_one_mul_add' [NCRing α] (a b : α) : -1 * (a + b) = -b + -a := by
      rw [neg_one_mul, neg_add_distrib]

    theorem sub_mul_distrib_left [NCRing α] (a b c : α) : a * (b - c) = a * b - a * c := by
      rw [sub_def, mul_distrib_left, mul_neg]; rfl
    theorem sub_mul_distrib_right [NCRing α] (a b c : α) : (a - b) * c = a * c - b * c := by
      rw [sub_def, mul_distrib_right, neg_mul]; rfl

    protected class constructor_ncr (α : Type _) extends Group.constructor_g α, One α, Mul α where
      mul_one           : ∀ a : α, a * 1 = a
      one_mul           : ∀ a : α, 1 * a = a
      mul_assoc         : ∀ a b c : α, (a * b) * c = a * (b * c)
      mul_distrib_left  : ∀ a b c : α, a * (b + c) = a * b + a * c
      mul_distrib_right : ∀ a b c : α, (a + b) * c = a * c + b * c

    protected def construct {α : Type _} (c : NCRing.constructor_ncr α) : NCRing α where
      toNCSemiring := NCSemiring.construct
        {
          toconstructor_cm  := {
            add_zero  := c.add_zero
            add_assoc := c.add_assoc
            add_comm  := fun a b => (Group.construct c.toconstructor_g).neg_inj (by
              rw [(Group.construct c.toconstructor_g).neg_add_distrib]
              have : ∀ a : α, -a = -1 * a := fun a => by
                rw [←(Group.construct c.toconstructor_g).add_right_cancel _ _ a,
                  (Group.construct c.toconstructor_g).neg_add]
                conv => rhs rhs rw [←c.one_mul a]
                rw [←c.mul_distrib_right, (Group.construct c.toconstructor_g).neg_add,
                  ←(Group.construct c.toconstructor_g).add_right_cancel _ _ (0 * a),
                  ←c.mul_distrib_right, c.add_zero, (Group.construct c.toconstructor_g).zero_add]
              rw [this (b + a), c.mul_distrib_left, ←this, ←this])
          }
          mul_one           := c.mul_one
          one_mul           := c.one_mul
          mul_assoc         := c.mul_assoc
          mul_distrib_left  := c.mul_distrib_left
          mul_distrib_right := c.mul_distrib_right
          mul_zero          := fun a => by
            rw [←(Group.construct c.toconstructor_g).add_right_cancel _ _ (a * 0),
              ←c.mul_distrib_left]
            conv => rhs rw [(Group.construct c.toconstructor_g).zero_add]
            rw [c.add_zero]
          zero_mul          := fun a => by
            rw [←(Group.construct c.toconstructor_g).add_right_cancel _ _ (0 * a),
              ←c.mul_distrib_right]
            conv => rhs rw [(Group.construct c.toconstructor_g).zero_add]
            rw [c.add_zero]
        }
      toNeg := (Group.construct c.toconstructor_g).toNeg
      add_neg := (Group.construct c.toconstructor_g).add_neg

    protected def to_constructor (α : Type _) [NCRing α] : NCRing.constructor_ncr α where
      toconstructor_g   := Group.to_constructor α
      mul_one           := NCSemiring.mul_one
      one_mul           := NCSemiring.one_mul
      mul_assoc         := NCSemiring.mul_assoc
      mul_distrib_left  := NCSemiring.mul_distrib_left
      mul_distrib_right := NCSemiring.mul_distrib_right

  end NCRing

  namespace Ring
    open NCSemiring

    protected instance Product (α₁ : Type _) (α₂ : Type _) [Ring α₁] [Ring α₂] : Ring (α₁ × α₂) where
      toNCRing := NCRing.Product α₁ α₂
      mul_comm := (Semiring.Product α₁ α₂).mul_comm

    protected instance multi_product {ι : Type _} (fι : ι → Type _) [∀ i, Ring (fι i)] : Ring (MultiProd fι) where
      mul_comm := (Semiring.multi_product fι).mul_comm

    def is_NonTrivial (α : Type _) [Ring α] : Prop := (1 : α) ≠ 0
    def is_NonTrivial.toNonTrivialRing [Ring α] (h : is_NonTrivial α) : NonTrivialRing α where
      toNonTrivial.one_neq_zero := h

    protected class constructor_r (α : Type _) extends AbelianGroup.constructor_ab α, One α, Mul α where
      mul_one           : ∀ a : α, a * 1 = a
      mul_assoc         : ∀ a b c : α, (a * b) * c = a * (b * c)
      mul_distrib_left  : ∀ a b c : α, a * (b + c) = a * b + a * c
      mul_comm          : ∀ a b : α, a * b = b * a

    protected def construct {α : Type _} (c : Ring.constructor_r α) : Ring α where
      toNCRing := NCRing.construct
        {
          mul_one           := c.mul_one
          one_mul           := fun a => by rw [c.mul_comm]; exact c.mul_one a
          mul_assoc         := c.mul_assoc
          mul_distrib_left  := c.mul_distrib_left
          mul_distrib_right := fun a b _ => by rw [c.mul_comm, c.mul_comm a, c.mul_comm b]; exact c.mul_distrib_left _ _ _
        }
      mul_comm := c.mul_comm

    protected def to_constructor (α : Type _) [Ring α] : Ring.constructor_r α where
      toconstructor_ab  := AbelianGroup.to_constructor α
      mul_one           := NCSemiring.mul_one
      mul_assoc         := NCSemiring.mul_assoc
      mul_distrib_left  := NCSemiring.mul_distrib_left
      mul_comm          := Semiring.mul_comm

  end Ring

  theorem NonTrivialRing.to_is_NonTrivial [NonTrivialRing α] : Ring.is_NonTrivial α := NonTrivial.one_neq_zero

  instance IntRing : NonTrivialRing Int where
    toRing := Ring.construct
      {
        toconstructor_ab  := IntGroup.to_constructor
        mul_one           := Int.mul_one
        mul_assoc         := Int.mul_assoc
        mul_distrib_left  := Int.mul_distrib_left
        mul_comm          := Int.mul_comm
      }
    toNonTrivial := IntNonTrivial

end M4R


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.continuous_on
import Mathlib.group_theory.submonoid.basic
import Mathlib.algebra.group.prod
import Mathlib.algebra.pointwise
import Mathlib.PostPort

universes u_5 l u_3 u_1 u_4 u_2

namespace Mathlib

/-!
# Theory of topological monoids

In this file we define mixin classes `has_continuous_mul` and `has_continuous_add`. While in many
applications the underlying type is a monoid (multiplicative or additive), we do not require this in
the definitions.
-/

/-- Basic hypothesis to talk about a topological additive monoid or a topological additive
semigroup. A topological additive monoid over `α`, for example, is obtained by requiring both the
instances `add_monoid α` and `has_continuous_add α`. -/
class has_continuous_add (M : Type u_5) [topological_space M] [Add M]
where
  continuous_add : continuous fun (p : M × M) => prod.fst p + prod.snd p

/-- Basic hypothesis to talk about a topological monoid or a topological semigroup.
A topological monoid over `α`, for example, is obtained by requiring both the instances `monoid α`
and `has_continuous_mul α`. -/
class has_continuous_mul (M : Type u_5) [topological_space M] [Mul M]
where
  continuous_mul : continuous fun (p : M × M) => prod.fst p * prod.snd p

theorem continuous_add {M : Type u_3} [topological_space M] [Add M] [has_continuous_add M] : continuous fun (p : M × M) => prod.fst p + prod.snd p :=
  has_continuous_add.continuous_add

theorem continuous.mul {α : Type u_1} {M : Type u_3} [topological_space M] [Mul M] [has_continuous_mul M] [topological_space α] {f : α → M} {g : α → M} (hf : continuous f) (hg : continuous g) : continuous fun (x : α) => f x * g x :=
  continuous.comp continuous_mul (continuous.prod_mk hf hg)

-- should `to_additive` be doing this?

theorem continuous_add_left {M : Type u_3} [topological_space M] [Add M] [has_continuous_add M] (a : M) : continuous fun (b : M) => a + b :=
  continuous.add continuous_const continuous_id

theorem continuous_add_right {M : Type u_3} [topological_space M] [Add M] [has_continuous_add M] (a : M) : continuous fun (b : M) => b + a :=
  continuous.add continuous_id continuous_const

theorem continuous_on.add {α : Type u_1} {M : Type u_3} [topological_space M] [Add M] [has_continuous_add M] [topological_space α] {f : α → M} {g : α → M} {s : set α} (hf : continuous_on f s) (hg : continuous_on g s) : continuous_on (fun (x : α) => f x + g x) s :=
  continuous.comp_continuous_on continuous_add (continuous_on.prod hf hg)

theorem tendsto_add {M : Type u_3} [topological_space M] [Add M] [has_continuous_add M] {a : M} {b : M} : filter.tendsto (fun (p : M × M) => prod.fst p + prod.snd p) (nhds (a, b)) (nhds (a + b)) :=
  iff.mp continuous_iff_continuous_at has_continuous_add.continuous_add (a, b)

theorem filter.tendsto.add {α : Type u_1} {M : Type u_3} [topological_space M] [Add M] [has_continuous_add M] {f : α → M} {g : α → M} {x : filter α} {a : M} {b : M} (hf : filter.tendsto f x (nhds a)) (hg : filter.tendsto g x (nhds b)) : filter.tendsto (fun (x : α) => f x + g x) x (nhds (a + b)) :=
  filter.tendsto.comp tendsto_add (filter.tendsto.prod_mk_nhds hf hg)

theorem tendsto.const_mul {α : Type u_1} {M : Type u_3} [topological_space M] [Mul M] [has_continuous_mul M] (b : M) {c : M} {f : α → M} {l : filter α} (h : filter.tendsto (fun (k : α) => f k) l (nhds c)) : filter.tendsto (fun (k : α) => b * f k) l (nhds (b * c)) :=
  filter.tendsto.mul tendsto_const_nhds h

theorem tendsto.add_const {α : Type u_1} {M : Type u_3} [topological_space M] [Add M] [has_continuous_add M] (b : M) {c : M} {f : α → M} {l : filter α} (h : filter.tendsto (fun (k : α) => f k) l (nhds c)) : filter.tendsto (fun (k : α) => f k + b) l (nhds (c + b)) :=
  filter.tendsto.add h tendsto_const_nhds

theorem continuous_at.add {α : Type u_1} {M : Type u_3} [topological_space M] [Add M] [has_continuous_add M] [topological_space α] {f : α → M} {g : α → M} {x : α} (hf : continuous_at f x) (hg : continuous_at g x) : continuous_at (fun (x : α) => f x + g x) x :=
  filter.tendsto.add hf hg

theorem continuous_within_at.add {α : Type u_1} {M : Type u_3} [topological_space M] [Add M] [has_continuous_add M] [topological_space α] {f : α → M} {g : α → M} {s : set α} {x : α} (hf : continuous_within_at f s x) (hg : continuous_within_at g s x) : continuous_within_at (fun (x : α) => f x + g x) s x :=
  filter.tendsto.add hf hg

protected instance prod.has_continuous_mul {M : Type u_3} {N : Type u_4} [topological_space M] [Mul M] [has_continuous_mul M] [topological_space N] [Mul N] [has_continuous_mul N] : has_continuous_mul (M × N) :=
  has_continuous_mul.mk
    (continuous.prod_mk
      (continuous.mul (continuous.comp continuous_fst continuous_fst) (continuous.comp continuous_fst continuous_snd))
      (continuous.mul (continuous.comp continuous_snd continuous_fst) (continuous.comp continuous_snd continuous_snd)))

protected instance has_continuous_mul_of_discrete_topology {N : Type u_4} [topological_space N] [Mul N] [discrete_topology N] : has_continuous_mul N :=
  has_continuous_mul.mk continuous_of_discrete_topology

theorem has_continuous_mul.of_nhds_one {M : Type (max u_1 u_2)} [monoid M] [topological_space M] (hmul : filter.tendsto (function.uncurry Mul.mul) (filter.prod (nhds 1) (nhds 1)) (nhds 1)) (hleft : ∀ (x₀ : M), nhds x₀ = filter.map (fun (x : M) => x₀ * x) (nhds 1)) (hright : ∀ (x₀ : M), nhds x₀ = filter.map (fun (x : M) => x * x₀) (nhds 1)) : has_continuous_mul M := sorry

theorem has_continuous_mul_of_comm_of_nhds_one (M : Type (max u_1 u_2)) [comm_monoid M] [topological_space M] (hmul : filter.tendsto (function.uncurry Mul.mul) (filter.prod (nhds 1) (nhds 1)) (nhds 1)) (hleft : ∀ (x₀ : M), nhds x₀ = filter.map (fun (x : M) => x₀ * x) (nhds 1)) : has_continuous_mul M := sorry

theorem add_submonoid.top_closure_add_self_subset {M : Type u_3} [topological_space M] [add_monoid M] [has_continuous_add M] (s : add_submonoid M) : closure ↑s + closure ↑s ⊆ closure ↑s := sorry

theorem submonoid.top_closure_mul_self_eq {M : Type u_3} [topological_space M] [monoid M] [has_continuous_mul M] (s : submonoid M) : closure ↑s * closure ↑s = closure ↑s := sorry

/-- The (topological-space) closure of a submonoid of a space `M` with `has_continuous_mul` is
itself a submonoid. -/
def add_submonoid.topological_closure {M : Type u_3} [topological_space M] [add_monoid M] [has_continuous_add M] (s : add_submonoid M) : add_submonoid M :=
  add_submonoid.mk (closure ↑s) sorry sorry

theorem submonoid.submonoid_topological_closure {M : Type u_3} [topological_space M] [monoid M] [has_continuous_mul M] (s : submonoid M) : s ≤ submonoid.topological_closure s :=
  subset_closure

theorem submonoid.is_closed_topological_closure {M : Type u_3} [topological_space M] [monoid M] [has_continuous_mul M] (s : submonoid M) : is_closed ↑(submonoid.topological_closure s) := sorry

theorem submonoid.topological_closure_minimal {M : Type u_3} [topological_space M] [monoid M] [has_continuous_mul M] (s : submonoid M) {t : submonoid M} (h : s ≤ t) (ht : is_closed ↑t) : submonoid.topological_closure s ≤ t :=
  closure_minimal h ht

theorem exists_open_nhds_zero_half {M : Type u_3} [topological_space M] [add_monoid M] [has_continuous_add M] {s : set M} (hs : s ∈ nhds 0) : ∃ (V : set M), is_open V ∧ 0 ∈ V ∧ ∀ (v : M), v ∈ V → ∀ (w : M), w ∈ V → v + w ∈ s := sorry

theorem exists_nhds_zero_half {M : Type u_3} [topological_space M] [add_monoid M] [has_continuous_add M] {s : set M} (hs : s ∈ nhds 0) : ∃ (V : set M), ∃ (H : V ∈ nhds 0), ∀ (v : M), v ∈ V → ∀ (w : M), w ∈ V → v + w ∈ s := sorry

theorem exists_nhds_one_split4 {M : Type u_3} [topological_space M] [monoid M] [has_continuous_mul M] {u : set M} (hu : u ∈ nhds 1) : ∃ (V : set M), ∃ (H : V ∈ nhds 1), ∀ {v w s t : M}, v ∈ V → w ∈ V → s ∈ V → t ∈ V → v * w * s * t ∈ u := sorry

/-- Given a neighborhood `U` of `1` there is an open neighborhood `V` of `1`
such that `VV ⊆ U`. -/
theorem exists_open_nhds_one_mul_subset {M : Type u_3} [topological_space M] [monoid M] [has_continuous_mul M] {U : set M} (hU : U ∈ nhds 1) : ∃ (V : set M), is_open V ∧ 1 ∈ V ∧ V * V ⊆ U := sorry

theorem tendsto_list_sum {α : Type u_1} {β : Type u_2} {M : Type u_3} [topological_space M] [add_monoid M] [has_continuous_add M] {f : β → α → M} {x : filter α} {a : β → M} (l : List β) : (∀ (c : β), c ∈ l → filter.tendsto (f c) x (nhds (a c))) →
  filter.tendsto (fun (b : α) => list.sum (list.map (fun (c : β) => f c b) l)) x (nhds (list.sum (list.map a l))) := sorry

theorem continuous_list_sum {α : Type u_1} {β : Type u_2} {M : Type u_3} [topological_space M] [add_monoid M] [has_continuous_add M] [topological_space α] {f : β → α → M} (l : List β) (h : ∀ (c : β), c ∈ l → continuous (f c)) : continuous fun (a : α) => list.sum (list.map (fun (c : β) => f c a) l) :=
  iff.mpr continuous_iff_continuous_at
    fun (x : α) => tendsto_list_sum l fun (c : β) (hc : c ∈ l) => iff.mp continuous_iff_continuous_at (h c hc) x

-- @[to_additive continuous_smul]

theorem continuous_pow {M : Type u_3} [topological_space M] [monoid M] [has_continuous_mul M] (n : ℕ) : continuous fun (a : M) => a ^ n := sorry

theorem continuous.pow {α : Type u_1} {M : Type u_3} [topological_space M] [monoid M] [has_continuous_mul M] {f : α → M} [topological_space α] (h : continuous f) (n : ℕ) : continuous fun (b : α) => f b ^ n :=
  continuous.comp (continuous_pow n) h

theorem submonoid.mem_nhds_one {M : Type u_3} [topological_space M] [comm_monoid M] (S : submonoid M) (oS : is_open ↑S) : ↑S ∈ nhds 1 :=
  mem_nhds_sets oS (submonoid.one_mem S)

theorem tendsto_multiset_prod {α : Type u_1} {β : Type u_2} {M : Type u_3} [topological_space M] [comm_monoid M] [has_continuous_mul M] {f : β → α → M} {x : filter α} {a : β → M} (s : multiset β) : (∀ (c : β), c ∈ s → filter.tendsto (f c) x (nhds (a c))) →
  filter.tendsto (fun (b : α) => multiset.prod (multiset.map (fun (c : β) => f c b) s)) x
    (nhds (multiset.prod (multiset.map a s))) := sorry

theorem tendsto_finset_sum {α : Type u_1} {β : Type u_2} {M : Type u_3} [topological_space M] [add_comm_monoid M] [has_continuous_add M] {f : β → α → M} {x : filter α} {a : β → M} (s : finset β) : (∀ (c : β), c ∈ s → filter.tendsto (f c) x (nhds (a c))) →
  filter.tendsto (fun (b : α) => finset.sum s fun (c : β) => f c b) x (nhds (finset.sum s fun (c : β) => a c)) :=
  tendsto_multiset_sum (finset.val s)

theorem continuous_multiset_prod {α : Type u_1} {β : Type u_2} {M : Type u_3} [topological_space M] [comm_monoid M] [has_continuous_mul M] [topological_space α] {f : β → α → M} (s : multiset β) : (∀ (c : β), c ∈ s → continuous (f c)) → continuous fun (a : α) => multiset.prod (multiset.map (fun (c : β) => f c a) s) := sorry

theorem continuous_finset_prod {α : Type u_1} {β : Type u_2} {M : Type u_3} [topological_space M] [comm_monoid M] [has_continuous_mul M] [topological_space α] {f : β → α → M} (s : finset β) : (∀ (c : β), c ∈ s → continuous (f c)) → continuous fun (a : α) => finset.prod s fun (c : β) => f c a :=
  continuous_multiset_prod (finset.val s)

-- should `to_additive` be doing this?

protected instance additive.has_continuous_add {M : Type u_1} [h : topological_space M] [Mul M] [has_continuous_mul M] : has_continuous_add (additive M) :=
  has_continuous_add.mk continuous_mul

protected instance multiplicative.has_continuous_mul {M : Type u_1} [h : topological_space M] [Add M] [has_continuous_add M] : has_continuous_mul (multiplicative M) :=
  has_continuous_mul.mk continuous_add


/- THE CUBE CATEGORY -/

universes v u

inductive cmonad (α : Type u)   | ll {}  : cmonad
                                | rr {}  : cmonad
                                | dim {} : α → cmonad
open cmonad

def bool_to_cmonad {α : Type u} : bool → cmonad α
| ff := ll
| tt := rr

instance inst_bool_to_cmonad (α : Type u) : has_coe bool (cmonad α) :=
⟨ bool_to_cmonad ⟩

@[inline] def cmor (α β) := α → cmonad β

@[inline] def cbind {α : Type u} {β : Type v} (x) (f : cmor α β) : cmonad β :=
match x with
| ll    := ll
| rr    := rr
| dim x := f x
end

def cmap {α : Type u} {β : Type v} (f : α → β) (x : cmonad α) : cmonad β :=
match x with
| ll    := ll
| rr    := rr
| dim x := dim (f x)
end

instance inst_cmonad : monad cmonad :=
{ monad . pure := @dim, bind := @cbind }

theorem cbind_bool {α β : Type u} (b : bool) (f : cmor α β) : (↑b >>= f) = ↑b :=
by cases b; reflexivity; reflexivity; reflexivity

theorem cbind_dim {α β : Type u} (a : α) (f : cmor α β) : (dim a >>= f) = f a :=
rfl

theorem cbind_assoc {α β γ : Type u} (a : cmonad α) (f : cmor α β) (g : cmor β γ) :
  ((a >>= f) >>= g) = (a >>= (λ x, f x >>= g)) :=
begin
 cases a with i, reflexivity, reflexivity,
 rw [cbind_dim, cbind_dim],
end

/- The identity cube map is just return is just dim -/

/- Composition of cube maps -/
def ccomp {α β γ} (g : cmor β γ) (f : cmor α β) : cmor α γ :=
λ x, do y <- f x,
        g y

infixl ` ∘c `:90 := ccomp

/- The monad laws for cmonad -/
theorem cid_left {α β} (f : cmor α β) (x : α) : (dim ∘c f) x = f x :=
begin
  simp[ccomp],
  exact  match (f x) with
         | ll    := rfl
         | rr    := rfl
         | dim a := rfl
         end
end

theorem cid_right {α β} (f : cmor α β) (x : α) : (f ∘c dim) x = f x :=
begin
  simp[ccomp],
  exact rfl
end

theorem ccomp_assoc {α β γ δ} (f : cmor α β) (g : cmor β γ) (h : cmor γ δ) (x) :
    ((h ∘c g) ∘c f) x = (h ∘c (g ∘c f)) x :=
begin
  simp[ccomp],
  exact match (f x) with
        | ll    := rfl
        | rr    := rfl
        | dim x := rfl
        end
end

/- TODO Instantiate cmonad as internal category -/

def is_strict {α β} (f : cmor α β) : Prop := ∀ a, ∃ x, f a = dim x


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.monoidal.functor
import Mathlib.category_theory.functorial
import Mathlib.PostPort

universes v₁ v₂ u₁ u₂ l

namespace Mathlib

/-!
# Unbundled lax monoidal functors

## Design considerations
The essential problem I've encountered that requires unbundled functors is
having an existing (non-monoidal) functor `F : C ⥤ D` between monoidal categories,
and wanting to assert that it has an extension to a lax monoidal functor.

The two options seem to be
1. Construct a separate `F' : lax_monoidal_functor C D`,
   and assert `F'.to_functor ≅ F`.
2. Introduce unbundled functors and unbundled lax monoidal functors,
   and construct `lax_monoidal F.obj`, then construct `F' := lax_monoidal_functor.of F.obj`.

Both have costs, but as for option 2. the cost is in library design,
while in option 1. the cost is users having to carry around additional isomorphisms forever,
I wanted to introduce unbundled functors.

TODO:
later, we may want to do this for strong monoidal functors as well,
but the immediate application, for enriched categories, only requires this notion.
-/

namespace category_theory

/-- An unbundled description of lax monoidal functors. -/
-- Perhaps in the future we'll redefine `lax_monoidal_functor` in terms of this,

-- but that isn't the immediate plan.

-- unit morphism

class lax_monoidal {C : Type u₁} [category C] [monoidal_category C] {D : Type u₂} [category D]
    [monoidal_category D] (F : C → D) [functorial F]
    where
  ε : 𝟙_ ⟶ F 𝟙_
  μ : (X Y : C) → F X ⊗ F Y ⟶ F (X ⊗ Y)
  μ_natural' :
    autoParam
      (∀ {X Y X' Y' : C} (f : X ⟶ Y) (g : X' ⟶ Y'),
        (map F f ⊗ map F g) ≫ μ Y Y' = μ X X' ≫ map F (f ⊗ g))
      (Lean.Syntax.ident Lean.SourceInfo.none (String.toSubstring "Mathlib.obviously")
        (Lean.Name.mkStr (Lean.Name.mkStr Lean.Name.anonymous "Mathlib") "obviously") [])
  associativity' :
    autoParam
      (∀ (X Y Z : C),
        (μ X Y ⊗ 𝟙) ≫ μ (X ⊗ Y) Z ≫ map F (iso.hom α_) = iso.hom α_ ≫ (𝟙 ⊗ μ Y Z) ≫ μ X (Y ⊗ Z))
      (Lean.Syntax.ident Lean.SourceInfo.none (String.toSubstring "Mathlib.obviously")
        (Lean.Name.mkStr (Lean.Name.mkStr Lean.Name.anonymous "Mathlib") "obviously") [])
  left_unitality' :
    autoParam (∀ (X : C), iso.hom λ_ = (ε ⊗ 𝟙) ≫ μ 𝟙_ X ≫ map F (iso.hom λ_))
      (Lean.Syntax.ident Lean.SourceInfo.none (String.toSubstring "Mathlib.obviously")
        (Lean.Name.mkStr (Lean.Name.mkStr Lean.Name.anonymous "Mathlib") "obviously") [])
  right_unitality' :
    autoParam (∀ (X : C), iso.hom ρ_ = (𝟙 ⊗ ε) ≫ μ X 𝟙_ ≫ map F (iso.hom ρ_))
      (Lean.Syntax.ident Lean.SourceInfo.none (String.toSubstring "Mathlib.obviously")
        (Lean.Name.mkStr (Lean.Name.mkStr Lean.Name.anonymous "Mathlib") "obviously") [])

-- tensorator

-- associativity of the tensorator

-- unitality

@[simp] theorem lax_monoidal.μ_natural {C : Type u₁} [category C] [monoidal_category C]
    {D : Type u₂} [category D] [monoidal_category D] {F : C → D} [functorial F] [c : lax_monoidal F]
    {X : C} {Y : C} {X' : C} {Y' : C} (f : X ⟶ Y) (g : X' ⟶ Y') :
    (map F f ⊗ map F g) ≫ lax_monoidal.μ F Y Y' = lax_monoidal.μ F X X' ≫ map F (f ⊗ g) :=
  sorry

theorem lax_monoidal.left_unitality {C : Type u₁} [category C] [monoidal_category C] {D : Type u₂}
    [category D] [monoidal_category D] {F : C → D} [functorial F] [c : lax_monoidal F] (X : C) :
    iso.hom λ_ = (lax_monoidal.ε F ⊗ 𝟙) ≫ lax_monoidal.μ F 𝟙_ X ≫ map F (iso.hom λ_) :=
  sorry

-- The unitality axioms cannot be used as simp lemmas because they require

theorem lax_monoidal.right_unitality {C : Type u₁} [category C] [monoidal_category C] {D : Type u₂}
    [category D] [monoidal_category D] {F : C → D} [functorial F] [c : lax_monoidal F] (X : C) :
    iso.hom ρ_ = (𝟙 ⊗ lax_monoidal.ε F) ≫ lax_monoidal.μ F X 𝟙_ ≫ map F (iso.hom ρ_) :=
  sorry

-- higher-order matching to figure out the `F` and `X` from `F X`.

@[simp] theorem lax_monoidal.associativity {C : Type u₁} [category C] [monoidal_category C]
    {D : Type u₂} [category D] [monoidal_category D] {F : C → D} [functorial F] [c : lax_monoidal F]
    (X : C) (Y : C) (Z : C) :
    (lax_monoidal.μ F X Y ⊗ 𝟙) ≫ lax_monoidal.μ F (X ⊗ Y) Z ≫ map F (iso.hom α_) =
        iso.hom α_ ≫ (𝟙 ⊗ lax_monoidal.μ F Y Z) ≫ lax_monoidal.μ F X (Y ⊗ Z) :=
  sorry

namespace lax_monoidal_functor

/--
Construct a bundled `lax_monoidal_functor` from the object level function
and `functorial` and `lax_monoidal` typeclasses.
-/
@[simp] theorem of_μ {C : Type u₁} [category C] [monoidal_category C] {D : Type u₂} [category D]
    [monoidal_category D] (F : C → D) [I₁ : functorial F] [I₂ : lax_monoidal F] (X : C) (Y : C) :
    μ (of F) X Y = lax_monoidal.μ F X Y :=
  Eq.refl (μ (of F) X Y)

end lax_monoidal_functor

protected instance lax_monoidal_functor.obj.lax_monoidal {C : Type u₁} [category C]
    [monoidal_category C] {D : Type u₂} [category D] [monoidal_category D]
    (F : lax_monoidal_functor C D) :
    lax_monoidal (functor.obj (lax_monoidal_functor.to_functor F)) :=
  lax_monoidal.mk (lax_monoidal_functor.ε F) (lax_monoidal_functor.μ F)

protected instance lax_monoidal_id {C : Type u₁} [category C] [monoidal_category C] :
    lax_monoidal id :=
  lax_monoidal.mk 𝟙 fun (X Y : C) => 𝟙

end Mathlib