/-
Copyright (c) 2019 Abhimanyu Pallavi Sudhir. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Abhimanyu Pallavi Sudhir, Yury Kudryashov

! This file was ported from Lean 3 source module order.filter.filter_product
! leanprover-community/mathlib commit 23aa88e32dcc9d2a24cca7bc23268567ed4cd7d6
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Order.Filter.Ultrafilter
import Mathbin.Order.Filter.Germ

/-!
# Ultraproducts

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

If `φ` is an ultrafilter, then the space of germs of functions `f : α → β` at `φ` is called
the *ultraproduct*. In this file we prove properties of ultraproducts that rely on `φ` being an
ultrafilter. Definitions and properties that work for any filter should go to `order.filter.germ`.

## Tags

ultrafilter, ultraproduct
-/

universe u v

variable {α : Type u} {β : Type v} {φ : Ultrafilter α}

open Classical

namespace Filter

-- mathport name: «expr∀* , »
local notation3"∀* "(...)", "r:(scoped p => Filter.Eventually p φ) => r

namespace Germ

open Ultrafilter

-- mathport name: «exprβ*»
local notation "β*" => Germ (φ : Filter α) β

instance [DivisionSemiring β] : DivisionSemiring β* :=
  { Germ.semiring, Germ.divInvMonoid,
    Germ.nontrivial with
    mul_inv_cancel := fun f =>
      inductionOn f fun f hf =>
        coe_eq.2 <|
          (φ.em fun y => f y = 0).elim (fun H => (hf <| coe_eq.2 H).elim) fun H =>
            H.mono fun x => mul_inv_cancel
    inv_zero := coe_eq.2 <| by simp only [(· ∘ ·), inv_zero] }

instance [DivisionRing β] : DivisionRing β* :=
  { Germ.ring, Germ.divisionSemiring with }

instance [Semifield β] : Semifield β* :=
  { Germ.commSemiring, Germ.divisionSemiring with }

instance [Field β] : Field β* :=
  { Germ.commRing, Germ.divisionRing with }

#print Filter.Germ.coe_lt /-
theorem coe_lt [Preorder β] {f g : α → β} : (f : β*) < g ↔ ∀* x, f x < g x := by
  simp only [lt_iff_le_not_le, eventually_and, coe_le, eventually_not, eventually_le]
#align filter.germ.coe_lt Filter.Germ.coe_lt
-/

#print Filter.Germ.coe_pos /-
theorem coe_pos [Preorder β] [Zero β] {f : α → β} : 0 < (f : β*) ↔ ∀* x, 0 < f x :=
  coe_lt
#align filter.germ.coe_pos Filter.Germ.coe_pos
-/

#print Filter.Germ.const_lt /-
theorem const_lt [Preorder β] {x y : β} : x < y → (↑x : β*) < ↑y :=
  coe_lt.mpr ∘ liftRel_const
#align filter.germ.const_lt Filter.Germ.const_lt
-/

#print Filter.Germ.const_lt_iff /-
@[simp, norm_cast]
theorem const_lt_iff [Preorder β] {x y : β} : (↑x : β*) < ↑y ↔ x < y :=
  coe_lt.trans liftRel_const_iff
#align filter.germ.const_lt_iff Filter.Germ.const_lt_iff
-/

#print Filter.Germ.lt_def /-
theorem lt_def [Preorder β] : ((· < ·) : β* → β* → Prop) = LiftRel (· < ·) :=
  by
  ext (⟨f⟩⟨g⟩)
  exact coe_lt
#align filter.germ.lt_def Filter.Germ.lt_def
-/

instance [Sup β] : Sup β* :=
  ⟨map₂ (· ⊔ ·)⟩

instance [Inf β] : Inf β* :=
  ⟨map₂ (· ⊓ ·)⟩

#print Filter.Germ.const_sup /-
@[simp, norm_cast]
theorem const_sup [Sup β] (a b : β) : ↑(a ⊔ b) = (↑a ⊔ ↑b : β*) :=
  rfl
#align filter.germ.const_sup Filter.Germ.const_sup
-/

#print Filter.Germ.const_inf /-
@[simp, norm_cast]
theorem const_inf [Inf β] (a b : β) : ↑(a ⊓ b) = (↑a ⊓ ↑b : β*) :=
  rfl
#align filter.germ.const_inf Filter.Germ.const_inf
-/

instance [SemilatticeSup β] : SemilatticeSup β* :=
  { Germ.partialOrder with
    sup := (· ⊔ ·)
    le_sup_left := fun f g => inductionOn₂ f g fun f g => eventually_of_forall fun x => le_sup_left
    le_sup_right := fun f g =>
      inductionOn₂ f g fun f g => eventually_of_forall fun x => le_sup_right
    sup_le := fun f₁ f₂ g =>
      inductionOn₃ f₁ f₂ g fun f₁ f₂ g h₁ h₂ => h₂.mp <| h₁.mono fun x => sup_le }

instance [SemilatticeInf β] : SemilatticeInf β* :=
  { Germ.partialOrder with
    inf := (· ⊓ ·)
    inf_le_left := fun f g => inductionOn₂ f g fun f g => eventually_of_forall fun x => inf_le_left
    inf_le_right := fun f g =>
      inductionOn₂ f g fun f g => eventually_of_forall fun x => inf_le_right
    le_inf := fun f₁ f₂ g =>
      inductionOn₃ f₁ f₂ g fun f₁ f₂ g h₁ h₂ => h₂.mp <| h₁.mono fun x => le_inf }

instance [Lattice β] : Lattice β* :=
  { Germ.semilatticeSup, Germ.semilatticeInf with }

instance [DistribLattice β] : DistribLattice β* :=
  { Germ.semilatticeSup, Germ.semilatticeInf with
    le_sup_inf := fun f g h =>
      inductionOn₃ f g h fun f g h => eventually_of_forall fun _ => le_sup_inf }

instance [LE β] [IsTotal β (· ≤ ·)] : IsTotal β* (· ≤ ·) :=
  ⟨fun f g =>
    inductionOn₂ f g fun f g => eventually_or.1 <| eventually_of_forall fun x => total_of _ _ _⟩

/-- If `φ` is an ultrafilter then the ultraproduct is a linear order. -/
noncomputable instance [LinearOrder β] : LinearOrder β* :=
  Lattice.toLinearOrder _

@[to_additive]
instance [OrderedCommMonoid β] : OrderedCommMonoid β* :=
  { Germ.partialOrder, Germ.commMonoid with
    mul_le_mul_left := fun f g =>
      inductionOn₂ f g fun f g H h =>
        inductionOn h fun h => H.mono fun x H => mul_le_mul_left' H _ }

@[to_additive]
instance [OrderedCancelCommMonoid β] : OrderedCancelCommMonoid β* :=
  { Germ.partialOrder, Germ.orderedCommMonoid with
    le_of_mul_le_mul_left := fun f g h =>
      inductionOn₃ f g h fun f g h H => H.mono fun x => le_of_mul_le_mul_left' }

@[to_additive]
instance [OrderedCommGroup β] : OrderedCommGroup β* :=
  { Germ.orderedCancelCommMonoid, Germ.commGroup with }

@[to_additive]
noncomputable instance [LinearOrderedCommGroup β] : LinearOrderedCommGroup β* :=
  { Germ.orderedCommGroup, Germ.linearOrder with }

instance [OrderedSemiring β] : OrderedSemiring β* :=
  { Germ.semiring,
    Germ.orderedAddCommMonoid with
    zero_le_one := const_le zero_le_one
    mul_le_mul_of_nonneg_left := fun x y z =>
      inductionOn₃ x y z fun f g h hfg hh => hh.mp <| hfg.mono fun a => mul_le_mul_of_nonneg_left
    mul_le_mul_of_nonneg_right := fun x y z =>
      inductionOn₃ x y z fun f g h hfg hh => hh.mp <| hfg.mono fun a => mul_le_mul_of_nonneg_right }

instance [OrderedCommSemiring β] : OrderedCommSemiring β* :=
  { Germ.orderedSemiring, Germ.commSemiring with }

instance [OrderedRing β] : OrderedRing β* :=
  { Germ.ring,
    Germ.orderedAddCommGroup with
    zero_le_one := const_le zero_le_one
    mul_nonneg := fun x y =>
      inductionOn₂ x y fun f g hf hg => hg.mp <| hf.mono fun a => mul_nonneg }

instance [OrderedCommRing β] : OrderedCommRing β* :=
  { Germ.orderedRing, Germ.orderedCommSemiring with }

instance [StrictOrderedSemiring β] : StrictOrderedSemiring β* :=
  { Germ.orderedSemiring, Germ.orderedCancelAddCommMonoid,
    Germ.nontrivial with
    mul_lt_mul_of_pos_left := fun x y z =>
      inductionOn₃ x y z fun f g h hfg hh =>
        coe_lt.2 <| (coe_lt.1 hh).mp <| (coe_lt.1 hfg).mono fun a => mul_lt_mul_of_pos_left
    mul_lt_mul_of_pos_right := fun x y z =>
      inductionOn₃ x y z fun f g h hfg hh =>
        coe_lt.2 <| (coe_lt.1 hh).mp <| (coe_lt.1 hfg).mono fun a => mul_lt_mul_of_pos_right }

instance [StrictOrderedCommSemiring β] : StrictOrderedCommSemiring β* :=
  { Germ.strictOrderedSemiring, Germ.orderedCommSemiring with }

instance [StrictOrderedRing β] : StrictOrderedRing β* :=
  { Germ.ring,
    Germ.strictOrderedSemiring with
    zero_le_one := const_le zero_le_one
    mul_pos := fun x y =>
      inductionOn₂ x y fun f g hf hg =>
        coe_pos.2 <| (coe_pos.1 hg).mp <| (coe_pos.1 hf).mono fun x => mul_pos }

instance [StrictOrderedCommRing β] : StrictOrderedCommRing β* :=
  { Germ.strictOrderedRing, Germ.orderedCommRing with }

noncomputable instance [LinearOrderedRing β] : LinearOrderedRing β* :=
  { Germ.strictOrderedRing, Germ.linearOrder with }

noncomputable instance [LinearOrderedField β] : LinearOrderedField β* :=
  { Germ.linearOrderedRing, Germ.field with }

noncomputable instance [LinearOrderedCommRing β] : LinearOrderedCommRing β* :=
  { Germ.linearOrderedRing, Germ.commMonoid with }

/- warning: filter.germ.max_def -> Filter.Germ.max_def is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrder.{u2} β] (x : Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (y : Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β), Eq.{succ (max u1 u2)} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (LinearOrder.max.{max u1 u2} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.linearOrder.{u1, u2} α β φ _inst_1) x y) (Filter.Germ.map₂.{u1, u2, u2, u2} α β β β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) (LinearOrder.max.{u2} β _inst_1) x y)
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrder.{u2} β] (x : Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (y : Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β), Eq.{max (succ u1) (succ u2)} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Max.max.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (LinearOrder.toMax.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Filter.Germ.linearOrder.{u1, u2} α β φ _inst_1)) x y) (Filter.Germ.map₂.{u1, u2, u2, u2} α β β β (Ultrafilter.toFilter.{u1} α φ) (Max.max.{u2} β (LinearOrder.toMax.{u2} β _inst_1)) x y)
Case conversion may be inaccurate. Consider using '#align filter.germ.max_def Filter.Germ.max_defₓ'. -/
theorem max_def [LinearOrder β] (x y : β*) : max x y = map₂ max x y :=
  inductionOn₂ x y fun a b => by
    cases le_total (a : β*) b
    · rw [max_eq_right h, map₂_coe, coe_eq]
      exact h.mono fun i hi => (max_eq_right hi).symm
    · rw [max_eq_left h, map₂_coe, coe_eq]
      exact h.mono fun i hi => (max_eq_left hi).symm
#align filter.germ.max_def Filter.Germ.max_def

/- warning: filter.germ.min_def -> Filter.Germ.min_def is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [K : LinearOrder.{u2} β] (x : Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (y : Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β), Eq.{succ (max u1 u2)} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (LinearOrder.min.{max u1 u2} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.linearOrder.{u1, u2} α β φ K) x y) (Filter.Germ.map₂.{u1, u2, u2, u2} α β β β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) (LinearOrder.min.{u2} β K) x y)
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [K : LinearOrder.{u2} β] (x : Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (y : Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β), Eq.{max (succ u1) (succ u2)} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Min.min.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (LinearOrder.toMin.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Filter.Germ.linearOrder.{u1, u2} α β φ K)) x y) (Filter.Germ.map₂.{u1, u2, u2, u2} α β β β (Ultrafilter.toFilter.{u1} α φ) (Min.min.{u2} β (LinearOrder.toMin.{u2} β K)) x y)
Case conversion may be inaccurate. Consider using '#align filter.germ.min_def Filter.Germ.min_defₓ'. -/
theorem min_def [K : LinearOrder β] (x y : β*) : min x y = map₂ min x y :=
  inductionOn₂ x y fun a b => by
    cases le_total (a : β*) b
    · rw [min_eq_left h, map₂_coe, coe_eq]
      exact h.mono fun i hi => (min_eq_left hi).symm
    · rw [min_eq_right h, map₂_coe, coe_eq]
      exact h.mono fun i hi => (min_eq_right hi).symm
#align filter.germ.min_def Filter.Germ.min_def

/- warning: filter.germ.abs_def -> Filter.Germ.abs_def is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrderedAddCommGroup.{u2} β] (x : Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β), Eq.{succ (max u1 u2)} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Abs.abs.{max u1 u2} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Neg.toHasAbs.{max u1 u2} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasNeg.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β (SubNegMonoid.toHasNeg.{u2} β (AddGroup.toSubNegMonoid.{u2} β (AddCommGroup.toAddGroup.{u2} β (OrderedAddCommGroup.toAddCommGroup.{u2} β (LinearOrderedAddCommGroup.toOrderedAddCommGroup.{u2} β _inst_1)))))) (Filter.Germ.hasSup.{u1, u2} α β φ (SemilatticeSup.toHasSup.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β (LinearOrderedAddCommGroup.toLinearOrder.{u2} β _inst_1)))))) x) (Filter.Germ.map.{u1, u2, u2} α β β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) (Abs.abs.{u2} β (Neg.toHasAbs.{u2} β (SubNegMonoid.toHasNeg.{u2} β (AddGroup.toSubNegMonoid.{u2} β (AddCommGroup.toAddGroup.{u2} β (OrderedAddCommGroup.toAddCommGroup.{u2} β (LinearOrderedAddCommGroup.toOrderedAddCommGroup.{u2} β _inst_1))))) (SemilatticeSup.toHasSup.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β (LinearOrderedAddCommGroup.toLinearOrder.{u2} β _inst_1)))))) x)
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrderedAddCommGroup.{u2} β] (x : Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β), Eq.{max (succ u1) (succ u2)} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Abs.abs.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Neg.toHasAbs.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Filter.Germ.neg.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β (NegZeroClass.toNeg.{u2} β (SubNegZeroMonoid.toNegZeroClass.{u2} β (SubtractionMonoid.toSubNegZeroMonoid.{u2} β (SubtractionCommMonoid.toSubtractionMonoid.{u2} β (AddCommGroup.toDivisionAddCommMonoid.{u2} β (OrderedAddCommGroup.toAddCommGroup.{u2} β (LinearOrderedAddCommGroup.toOrderedAddCommGroup.{u2} β _inst_1)))))))) (Filter.Germ.sup.{u1, u2} α β φ (SemilatticeSup.toSup.{u2} β (Lattice.toSemilatticeSup.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β (LinearOrderedAddCommGroup.toLinearOrder.{u2} β _inst_1))))))) x) (Filter.Germ.map.{u1, u2, u2} α β β (Ultrafilter.toFilter.{u1} α φ) (Abs.abs.{u2} β (Neg.toHasAbs.{u2} β (NegZeroClass.toNeg.{u2} β (SubNegZeroMonoid.toNegZeroClass.{u2} β (SubtractionMonoid.toSubNegZeroMonoid.{u2} β (SubtractionCommMonoid.toSubtractionMonoid.{u2} β (AddCommGroup.toDivisionAddCommMonoid.{u2} β (OrderedAddCommGroup.toAddCommGroup.{u2} β (LinearOrderedAddCommGroup.toOrderedAddCommGroup.{u2} β _inst_1))))))) (SemilatticeSup.toSup.{u2} β (Lattice.toSemilatticeSup.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β (LinearOrderedAddCommGroup.toLinearOrder.{u2} β _inst_1))))))) x)
Case conversion may be inaccurate. Consider using '#align filter.germ.abs_def Filter.Germ.abs_defₓ'. -/
theorem abs_def [LinearOrderedAddCommGroup β] (x : β*) : |x| = map abs x :=
  inductionOn x fun a => rfl
#align filter.germ.abs_def Filter.Germ.abs_def

/- warning: filter.germ.const_max -> Filter.Germ.const_max is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrder.{u2} β] (x : β) (y : β), Eq.{succ (max u1 u2)} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) ((fun (a : Type.{u2}) (b : Type.{max u1 u2}) [self : HasLiftT.{succ u2, succ (max u1 u2)} a b] => self.0) β (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasLiftT.{u1, u2} α β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ)) (LinearOrder.max.{u2} β _inst_1 x y)) (LinearOrder.max.{max u1 u2} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.linearOrder.{u1, u2} α β φ _inst_1) ((fun (a : Type.{u2}) (b : Type.{max u1 u2}) [self : HasLiftT.{succ u2, succ (max u1 u2)} a b] => self.0) β (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasLiftT.{u1, u2} α β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ)) x) ((fun (a : Type.{u2}) (b : Type.{max u1 u2}) [self : HasLiftT.{succ u2, succ (max u1 u2)} a b] => self.0) β (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasLiftT.{u1, u2} α β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ)) y))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrder.{u2} β] (x : β) (y : β), Eq.{max (succ u1) (succ u2)} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Filter.Germ.const.{u1, u2} α β (Ultrafilter.toFilter.{u1} α φ) (Max.max.{u2} β (LinearOrder.toMax.{u2} β _inst_1) x y)) (Max.max.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (LinearOrder.toMax.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Filter.Germ.linearOrder.{u1, u2} α β φ _inst_1)) (Filter.Germ.const.{u1, u2} α β (Ultrafilter.toFilter.{u1} α φ) x) (Filter.Germ.const.{u1, u2} α β (Ultrafilter.toFilter.{u1} α φ) y))
Case conversion may be inaccurate. Consider using '#align filter.germ.const_max Filter.Germ.const_maxₓ'. -/
@[simp]
theorem const_max [LinearOrder β] (x y : β) : (↑(max x y : β) : β*) = max ↑x ↑y := by
  rw [max_def, map₂_const]
#align filter.germ.const_max Filter.Germ.const_max

/- warning: filter.germ.const_min -> Filter.Germ.const_min is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrder.{u2} β] (x : β) (y : β), Eq.{succ (max u1 u2)} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) ((fun (a : Type.{u2}) (b : Type.{max u1 u2}) [self : HasLiftT.{succ u2, succ (max u1 u2)} a b] => self.0) β (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasLiftT.{u1, u2} α β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ)) (LinearOrder.min.{u2} β _inst_1 x y)) (LinearOrder.min.{max u1 u2} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.linearOrder.{u1, u2} α β φ _inst_1) ((fun (a : Type.{u2}) (b : Type.{max u1 u2}) [self : HasLiftT.{succ u2, succ (max u1 u2)} a b] => self.0) β (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasLiftT.{u1, u2} α β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ)) x) ((fun (a : Type.{u2}) (b : Type.{max u1 u2}) [self : HasLiftT.{succ u2, succ (max u1 u2)} a b] => self.0) β (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasLiftT.{u1, u2} α β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ)) y))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrder.{u2} β] (x : β) (y : β), Eq.{max (succ u1) (succ u2)} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Filter.Germ.const.{u1, u2} α β (Ultrafilter.toFilter.{u1} α φ) (Min.min.{u2} β (LinearOrder.toMin.{u2} β _inst_1) x y)) (Min.min.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (LinearOrder.toMin.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Filter.Germ.linearOrder.{u1, u2} α β φ _inst_1)) (Filter.Germ.const.{u1, u2} α β (Ultrafilter.toFilter.{u1} α φ) x) (Filter.Germ.const.{u1, u2} α β (Ultrafilter.toFilter.{u1} α φ) y))
Case conversion may be inaccurate. Consider using '#align filter.germ.const_min Filter.Germ.const_minₓ'. -/
@[simp]
theorem const_min [LinearOrder β] (x y : β) : (↑(min x y : β) : β*) = min ↑x ↑y := by
  rw [min_def, map₂_const]
#align filter.germ.const_min Filter.Germ.const_min

/- warning: filter.germ.const_abs -> Filter.Germ.const_abs is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrderedAddCommGroup.{u2} β] (x : β), Eq.{succ (max u1 u2)} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) ((fun (a : Type.{u2}) (b : Type.{max u1 u2}) [self : HasLiftT.{succ u2, succ (max u1 u2)} a b] => self.0) β (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasLiftT.{u1, u2} α β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ)) (Abs.abs.{u2} β (Neg.toHasAbs.{u2} β (SubNegMonoid.toHasNeg.{u2} β (AddGroup.toSubNegMonoid.{u2} β (AddCommGroup.toAddGroup.{u2} β (OrderedAddCommGroup.toAddCommGroup.{u2} β (LinearOrderedAddCommGroup.toOrderedAddCommGroup.{u2} β _inst_1))))) (SemilatticeSup.toHasSup.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β (LinearOrderedAddCommGroup.toLinearOrder.{u2} β _inst_1))))) x)) (Abs.abs.{max u1 u2} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Neg.toHasAbs.{max u1 u2} (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasNeg.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β (SubNegMonoid.toHasNeg.{u2} β (AddGroup.toSubNegMonoid.{u2} β (AddCommGroup.toAddGroup.{u2} β (OrderedAddCommGroup.toAddCommGroup.{u2} β (LinearOrderedAddCommGroup.toOrderedAddCommGroup.{u2} β _inst_1)))))) (Filter.Germ.hasSup.{u1, u2} α β φ (SemilatticeSup.toHasSup.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β (LinearOrderedAddCommGroup.toLinearOrder.{u2} β _inst_1)))))) ((fun (a : Type.{u2}) (b : Type.{max u1 u2}) [self : HasLiftT.{succ u2, succ (max u1 u2)} a b] => self.0) β (Filter.Germ.{u1, u2} α ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ) β) (Filter.Germ.hasLiftT.{u1, u2} α β ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Ultrafilter.{u1} α) (Filter.{u1} α) (HasLiftT.mk.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (CoeTCₓ.coe.{succ u1, succ u1} (Ultrafilter.{u1} α) (Filter.{u1} α) (Ultrafilter.Filter.hasCoeT.{u1} α))) φ)) x))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {φ : Ultrafilter.{u1} α} [_inst_1 : LinearOrderedAddCommGroup.{u2} β] (x : β), Eq.{max (succ u1) (succ u2)} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Filter.Germ.const.{u1, u2} α β (Ultrafilter.toFilter.{u1} α φ) (Abs.abs.{u2} β (Neg.toHasAbs.{u2} β (NegZeroClass.toNeg.{u2} β (SubNegZeroMonoid.toNegZeroClass.{u2} β (SubtractionMonoid.toSubNegZeroMonoid.{u2} β (SubtractionCommMonoid.toSubtractionMonoid.{u2} β (AddCommGroup.toDivisionAddCommMonoid.{u2} β (OrderedAddCommGroup.toAddCommGroup.{u2} β (LinearOrderedAddCommGroup.toOrderedAddCommGroup.{u2} β _inst_1))))))) (SemilatticeSup.toSup.{u2} β (Lattice.toSemilatticeSup.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β (LinearOrderedAddCommGroup.toLinearOrder.{u2} β _inst_1)))))) x)) (Abs.abs.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Neg.toHasAbs.{max u1 u2} (Filter.Germ.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β) (Filter.Germ.neg.{u1, u2} α (Ultrafilter.toFilter.{u1} α φ) β (NegZeroClass.toNeg.{u2} β (SubNegZeroMonoid.toNegZeroClass.{u2} β (SubtractionMonoid.toSubNegZeroMonoid.{u2} β (SubtractionCommMonoid.toSubtractionMonoid.{u2} β (AddCommGroup.toDivisionAddCommMonoid.{u2} β (OrderedAddCommGroup.toAddCommGroup.{u2} β (LinearOrderedAddCommGroup.toOrderedAddCommGroup.{u2} β _inst_1)))))))) (Filter.Germ.sup.{u1, u2} α β φ (SemilatticeSup.toSup.{u2} β (Lattice.toSemilatticeSup.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β (LinearOrderedAddCommGroup.toLinearOrder.{u2} β _inst_1))))))) (Filter.Germ.const.{u1, u2} α β (Ultrafilter.toFilter.{u1} α φ) x))
Case conversion may be inaccurate. Consider using '#align filter.germ.const_abs Filter.Germ.const_absₓ'. -/
@[simp]
theorem const_abs [LinearOrderedAddCommGroup β] (x : β) : (↑(|x|) : β*) = |↑x| := by
  rw [abs_def, map_const]
#align filter.germ.const_abs Filter.Germ.const_abs

end Germ

end Filter


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

! This file was ported from Lean 3 source module group_theory.group_action.embedding
! leanprover-community/mathlib commit baba818b9acea366489e8ba32d2cc0fcaf50a1f7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.GroupTheory.GroupAction.Group
import Mathbin.GroupTheory.GroupAction.Pi

/-!
# Group actions on embeddings

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

This file provides a `mul_action G (α ↪ β)` instance that agrees with the `mul_action G (α → β)`
instances defined by `pi.mul_action`.

Note that unlike the `pi` instance, this requires `G` to be a group.
-/

universe u v w

variable {G G' α β : Type _}

namespace Function.Embedding

@[to_additive Function.Embedding.hasVadd]
instance [Group G] [MulAction G β] : SMul G (α ↪ β) :=
  ⟨fun g f => f.trans (MulAction.toPerm g).toEmbedding⟩

/- warning: function.embedding.smul_def -> Function.Embedding.smul_def is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} {α : Type.{u2}} {β : Type.{u3}} [_inst_1 : Group.{u1} G] [_inst_2 : MulAction.{u1, u3} G β (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))] (g : G) (f : Function.Embedding.{succ u2, succ u3} α β), Eq.{succ (max u2 u3)} (Function.Embedding.{succ u2, succ u3} α β) (SMul.smul.{u1, max u2 u3} G (Function.Embedding.{succ u2, succ u3} α β) (Function.Embedding.hasSmul.{u1, u2, u3} G α β _inst_1 _inst_2) g f) (Function.Embedding.trans.{succ u2, succ u3, succ u3} α β β f (Equiv.toEmbedding.{succ u3, succ u3} β β (MulAction.toPerm.{u1, u3} G β _inst_1 _inst_2 g)))
but is expected to have type
  forall {G : Type.{u3}} {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Group.{u3} G] [_inst_2 : MulAction.{u3, u2} G β (DivInvMonoid.toMonoid.{u3} G (Group.toDivInvMonoid.{u3} G _inst_1))] (g : G) (f : Function.Embedding.{succ u1, succ u2} α β), Eq.{max (succ u1) (succ u2)} (Function.Embedding.{succ u1, succ u2} α β) (HSMul.hSMul.{u3, max u1 u2, max u1 u2} G (Function.Embedding.{succ u1, succ u2} α β) (Function.Embedding.{succ u1, succ u2} α β) (instHSMul.{u3, max u1 u2} G (Function.Embedding.{succ u1, succ u2} α β) (Function.Embedding.smul.{u3, u1, u2} G α β _inst_1 _inst_2)) g f) (Function.Embedding.trans.{succ u1, succ u2, succ u2} α β β f (Equiv.toEmbedding.{succ u2, succ u2} β β (MulAction.toPerm.{u3, u2} G β _inst_1 _inst_2 g)))
Case conversion may be inaccurate. Consider using '#align function.embedding.smul_def Function.Embedding.smul_defₓ'. -/
@[to_additive]
theorem smul_def [Group G] [MulAction G β] (g : G) (f : α ↪ β) :
    g • f = f.trans (MulAction.toPerm g).toEmbedding :=
  rfl
#align function.embedding.smul_def Function.Embedding.smul_def
#align function.embedding.vadd_def Function.Embedding.vadd_def

/- warning: function.embedding.smul_apply -> Function.Embedding.smul_apply is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} {α : Type.{u2}} {β : Type.{u3}} [_inst_1 : Group.{u1} G] [_inst_2 : MulAction.{u1, u3} G β (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))] (g : G) (f : Function.Embedding.{succ u2, succ u3} α β) (a : α), Eq.{succ u3} β (coeFn.{max 1 (succ u2) (succ u3), max (succ u2) (succ u3)} (Function.Embedding.{succ u2, succ u3} α β) (fun (_x : Function.Embedding.{succ u2, succ u3} α β) => α -> β) (Function.Embedding.hasCoeToFun.{succ u2, succ u3} α β) (SMul.smul.{u1, max u2 u3} G (Function.Embedding.{succ u2, succ u3} α β) (Function.Embedding.hasSmul.{u1, u2, u3} G α β _inst_1 _inst_2) g f) a) (SMul.smul.{u1, u3} G β (MulAction.toHasSmul.{u1, u3} G β (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)) _inst_2) g (coeFn.{max 1 (succ u2) (succ u3), max (succ u2) (succ u3)} (Function.Embedding.{succ u2, succ u3} α β) (fun (_x : Function.Embedding.{succ u2, succ u3} α β) => α -> β) (Function.Embedding.hasCoeToFun.{succ u2, succ u3} α β) f a))
but is expected to have type
  forall {G : Type.{u3}} {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Group.{u3} G] [_inst_2 : MulAction.{u3, u2} G β (DivInvMonoid.toMonoid.{u3} G (Group.toDivInvMonoid.{u3} G _inst_1))] (g : G) (f : Function.Embedding.{succ u1, succ u2} α β) (a : α), Eq.{succ u2} ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) a) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Function.Embedding.{succ u1, succ u2} α β) α (fun (_x : α) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) _x) (EmbeddingLike.toFunLike.{max (succ u1) (succ u2), succ u1, succ u2} (Function.Embedding.{succ u1, succ u2} α β) α β (Function.instEmbeddingLikeEmbedding.{succ u1, succ u2} α β)) (HSMul.hSMul.{u3, max u1 u2, max u1 u2} G (Function.Embedding.{succ u1, succ u2} α β) (Function.Embedding.{succ u1, succ u2} α β) (instHSMul.{u3, max u1 u2} G (Function.Embedding.{succ u1, succ u2} α β) (Function.Embedding.smul.{u3, u1, u2} G α β _inst_1 _inst_2)) g f) a) (HSMul.hSMul.{u3, u2, u2} G ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) a) ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) a) (instHSMul.{u3, u2} G ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) a) (MulAction.toSMul.{u3, u2} G ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) a) (DivInvMonoid.toMonoid.{u3} G (Group.toDivInvMonoid.{u3} G _inst_1)) _inst_2)) g (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Function.Embedding.{succ u1, succ u2} α β) α (fun (_x : α) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) _x) (EmbeddingLike.toFunLike.{max (succ u1) (succ u2), succ u1, succ u2} (Function.Embedding.{succ u1, succ u2} α β) α β (Function.instEmbeddingLikeEmbedding.{succ u1, succ u2} α β)) f a))
Case conversion may be inaccurate. Consider using '#align function.embedding.smul_apply Function.Embedding.smul_applyₓ'. -/
@[simp, to_additive]
theorem smul_apply [Group G] [MulAction G β] (g : G) (f : α ↪ β) (a : α) : (g • f) a = g • f a :=
  rfl
#align function.embedding.smul_apply Function.Embedding.smul_apply
#align function.embedding.vadd_apply Function.Embedding.vadd_apply

/- warning: function.embedding.coe_smul -> Function.Embedding.coe_smul is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} {α : Type.{u2}} {β : Type.{u3}} [_inst_1 : Group.{u1} G] [_inst_2 : MulAction.{u1, u3} G β (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))] (g : G) (f : Function.Embedding.{succ u2, succ u3} α β), Eq.{succ (max u2 u3)} (α -> β) (coeFn.{succ (max u2 u3), succ (max u2 u3)} (Function.Embedding.{succ u2, succ u3} α β) (fun (_x : Function.Embedding.{succ u2, succ u3} α β) => α -> β) (Function.Embedding.hasCoeToFun.{succ u2, succ u3} α β) (SMul.smul.{u1, max u2 u3} G (Function.Embedding.{succ u2, succ u3} α β) (Function.Embedding.hasSmul.{u1, u2, u3} G α β _inst_1 _inst_2) g f)) (SMul.smul.{u1, max u2 u3} G (α -> β) (Function.hasSMul.{u2, u1, u3} α G β (MulAction.toHasSmul.{u1, u3} G β (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)) _inst_2)) g (coeFn.{max 1 (succ u2) (succ u3), max (succ u2) (succ u3)} (Function.Embedding.{succ u2, succ u3} α β) (fun (_x : Function.Embedding.{succ u2, succ u3} α β) => α -> β) (Function.Embedding.hasCoeToFun.{succ u2, succ u3} α β) f))
but is expected to have type
  forall {G : Type.{u3}} {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Group.{u3} G] [_inst_2 : MulAction.{u3, u2} G β (DivInvMonoid.toMonoid.{u3} G (Group.toDivInvMonoid.{u3} G _inst_1))] (g : G) (f : Function.Embedding.{succ u1, succ u2} α β), Eq.{max (succ u1) (succ u2)} (forall (ᾰ : α), (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) ᾰ) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Function.Embedding.{succ u1, succ u2} α β) α (fun (_x : α) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) _x) (EmbeddingLike.toFunLike.{max (succ u1) (succ u2), succ u1, succ u2} (Function.Embedding.{succ u1, succ u2} α β) α β (Function.instEmbeddingLikeEmbedding.{succ u1, succ u2} α β)) (HSMul.hSMul.{u3, max u1 u2, max u1 u2} G (Function.Embedding.{succ u1, succ u2} α β) (Function.Embedding.{succ u1, succ u2} α β) (instHSMul.{u3, max u1 u2} G (Function.Embedding.{succ u1, succ u2} α β) (Function.Embedding.smul.{u3, u1, u2} G α β _inst_1 _inst_2)) g f)) (HSMul.hSMul.{u3, max u1 u2, max u1 u2} G (forall (a : α), (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) a) (forall (ᾰ : α), (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) ᾰ) (instHSMul.{u3, max u1 u2} G (forall (a : α), (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) a) (Pi.instSMul.{u1, u2, u3} α G (fun (a : α) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) a) (fun (i : α) => MulAction.toSMul.{u3, u2} G ((fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) i) (DivInvMonoid.toMonoid.{u3} G (Group.toDivInvMonoid.{u3} G _inst_1)) _inst_2))) g (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Function.Embedding.{succ u1, succ u2} α β) α (fun (_x : α) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : α) => β) _x) (EmbeddingLike.toFunLike.{max (succ u1) (succ u2), succ u1, succ u2} (Function.Embedding.{succ u1, succ u2} α β) α β (Function.instEmbeddingLikeEmbedding.{succ u1, succ u2} α β)) f))
Case conversion may be inaccurate. Consider using '#align function.embedding.coe_smul Function.Embedding.coe_smulₓ'. -/
@[to_additive]
theorem coe_smul [Group G] [MulAction G β] (g : G) (f : α ↪ β) : ⇑(g • f) = g • f :=
  rfl
#align function.embedding.coe_smul Function.Embedding.coe_smul
#align function.embedding.coe_vadd Function.Embedding.coe_vadd

instance [Group G] [Group G'] [SMul G G'] [MulAction G β] [MulAction G' β] [IsScalarTower G G' β] :
    IsScalarTower G G' (α ↪ β) :=
  ⟨fun x y z => Function.Embedding.ext fun i => smul_assoc x y (z i)⟩

@[to_additive]
instance [Group G] [Group G'] [MulAction G β] [MulAction G' β] [SMulCommClass G G' β] :
    SMulCommClass G G' (α ↪ β) :=
  ⟨fun x y z => Function.Embedding.ext fun i => smul_comm x y (z i)⟩

instance [Group G] [MulAction G β] [MulAction Gᵐᵒᵖ β] [IsCentralScalar G β] :
    IsCentralScalar G (α ↪ β) :=
  ⟨fun r m => Function.Embedding.ext fun i => op_smul_eq_smul _ _⟩

@[to_additive]
instance [Group G] [MulAction G β] : MulAction G (α ↪ β) :=
  FunLike.coe_injective.MulAction _ coe_smul

end Function.Embedding


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

! This file was ported from Lean 3 source module algebraic_topology.fundamental_groupoid.simply_connected
! leanprover-community/mathlib commit 38341f11ded9e2bc1371eb42caad69ecacf8f541
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.AlgebraicTopology.FundamentalGroupoid.InducedMaps
import Mathbin.Topology.Homotopy.Contractible
import Mathbin.CategoryTheory.Punit
import Mathbin.AlgebraicTopology.FundamentalGroupoid.Punit

/-!
# Simply connected spaces
This file defines simply connected spaces.
A topological space is simply connected if its fundamental groupoid is equivalent to `unit`.

## Main theorems
  - `simply_connected_iff_unique_homotopic` - A space is simply connected if and only if it is
    nonempty and there is a unique path up to homotopy between any two points

  - `simply_connected_space.of_contractible` - A contractible space is simply connected
-/

noncomputable section

open CategoryTheory

open ContinuousMap

open ContinuousMap

/- ./././Mathport/Syntax/Translate/Command.lean:388:30: infer kinds are unsupported in Lean 4: #[`equiv_unit] [] -/
/-- A simply connected space is one whose fundamental groupoid is equivalent to `discrete unit` -/
class SimplyConnectedSpace (X : Type _) [TopologicalSpace X] : Prop where
  equiv_unit : Nonempty (FundamentalGroupoid X ≌ Discrete Unit)
#align simply_connected_space SimplyConnectedSpace

theorem simply_connected_def (X : Type _) [TopologicalSpace X] :
    SimplyConnectedSpace X ↔ Nonempty (FundamentalGroupoid X ≌ Discrete Unit) :=
  ⟨fun h => @SimplyConnectedSpace.equiv_unit X _ h, fun h => ⟨h⟩⟩
#align simply_connected_def simply_connected_def

theorem simply_connected_iff_unique_homotopic (X : Type _) [TopologicalSpace X] :
    SimplyConnectedSpace X ↔
      Nonempty X ∧ ∀ x y : X, Nonempty (Unique (Path.Homotopic.Quotient x y)) :=
  by
  rw [simply_connected_def, equiv_punit_iff_unique]
  rfl
#align simply_connected_iff_unique_homotopic simply_connected_iff_unique_homotopic

namespace SimplyConnectedSpace

variable {X : Type _} [TopologicalSpace X] [SimplyConnectedSpace X]

instance (x y : X) : Subsingleton (Path.Homotopic.Quotient x y) :=
  @Unique.subsingleton _
    (Nonempty.some
      (by
        rw [simply_connected_iff_unique_homotopic] at *
        tauto))

attribute [local instance] Path.Homotopic.setoid

instance (priority := 100) : PathConnectedSpace X :=
  let unique_homotopic := (simply_connected_iff_unique_homotopic X).mp inferInstance
  { Nonempty := unique_homotopic.1
    Joined := fun x y => ⟨(unique_homotopic.2 x y).some.default.out⟩ }

/-- In a simply connected space, any two paths are homotopic -/
theorem paths_homotopic {x y : X} (p₁ p₂ : Path x y) : Path.Homotopic p₁ p₂ := by
  simpa using @Subsingleton.elim (Path.Homotopic.Quotient x y) _ ⟦p₁⟧ ⟦p₂⟧
#align simply_connected_space.paths_homotopic SimplyConnectedSpace.paths_homotopic

instance (priority := 100) ofContractible (Y : Type _) [TopologicalSpace Y] [ContractibleSpace Y] :
    SimplyConnectedSpace Y
    where equiv_unit :=
    let H : TopCat.of Y ≃ₕ TopCat.of Unit := (ContractibleSpace.hequiv_unit Y).some
    ⟨(FundamentalGroupoidFunctor.equivOfHomotopyEquiv H).trans
        FundamentalGroupoid.punitEquivDiscretePunit⟩
#align simply_connected_space.of_contractible SimplyConnectedSpace.ofContractible

end SimplyConnectedSpace

attribute [local instance] Path.Homotopic.setoid

/-- A space is simply connected iff it is path connected, and there is at most one path
  up to homotopy between any two points. -/
theorem simply_connected_iff_paths_homotopic {Y : Type _} [TopologicalSpace Y] :
    SimplyConnectedSpace Y ↔
      PathConnectedSpace Y ∧ ∀ x y : Y, Subsingleton (Path.Homotopic.Quotient x y) :=
  ⟨by
    intro
    constructor <;> infer_instance, fun h => by
    cases h; rw [simply_connected_iff_unique_homotopic]
    exact ⟨inferInstance, fun x y => ⟨uniqueOfSubsingleton ⟦PathConnectedSpace.somePath x y⟧⟩⟩⟩
#align simply_connected_iff_paths_homotopic simply_connected_iff_paths_homotopic

/-- Another version of `simply_connected_iff_paths_homotopic` -/
theorem simply_connected_iff_paths_homotopic' {Y : Type _} [TopologicalSpace Y] :
    SimplyConnectedSpace Y ↔
      PathConnectedSpace Y ∧ ∀ {x y : Y} (p₁ p₂ : Path x y), Path.Homotopic p₁ p₂ :=
  by
  convert simply_connected_iff_paths_homotopic
  simp [Path.Homotopic.Quotient, Setoid.eq_top_iff]; rfl
#align simply_connected_iff_paths_homotopic' simply_connected_iff_paths_homotopic'


/-
Copyright (c) 2019 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov, Scott Morrison, Simon Hudon
-/
import category_theory.groupoid
import data.equiv.mul_add

/-!
# Endomorphisms

Definition and basic properties of endomorphisms and automorphisms of an object in a category.

For each `X : C`, we provide `End X := X ⟶ X` with a monoid structure,
and `Aut X := X ≅ X ` with a group structure.
-/

universes v v' u u'

namespace category_theory

/-- Endomorphisms of an object in a category. Arguments order in multiplication agrees with
`function.comp`, not with `category.comp`. -/
def End {C : Type u} [category_struct.{v} C] (X : C) := X ⟶ X

namespace End

section struct

variables {C : Type u} [category_struct.{v} C] (X : C)

instance has_one : has_one (End X) := ⟨𝟙 X⟩
instance inhabited : inhabited (End X) := ⟨𝟙 X⟩

/-- Multiplication of endomorphisms agrees with `function.comp`, not `category_struct.comp`. -/
instance has_mul : has_mul (End X) := ⟨λ x y, y ≫ x⟩

variable {X}

/-- Assist the typechecker by expressing a morphism `X ⟶ X` as a term of `End X`. -/
def of (f : X ⟶ X) : End X := f

/-- Assist the typechecker by expressing an endomorphism `f : End X` as a term of `X ⟶ X`. -/
def as_hom (f : End X) : X ⟶ X := f

@[simp] lemma one_def : (1 : End X) = 𝟙 X := rfl

@[simp] lemma mul_def (xs ys : End X) : xs * ys = ys ≫ xs := rfl

end struct

/-- Endomorphisms of an object form a monoid -/
instance monoid {C : Type u} [category.{v} C] {X : C} : monoid (End X) :=
{ mul_one := category.id_comp,
  one_mul := category.comp_id,
  mul_assoc := λ x y z, (category.assoc z y x).symm,
  ..End.has_mul X, ..End.has_one X }

/-- In a groupoid, endomorphisms form a group -/
instance group {C : Type u} [groupoid.{v} C] (X : C) : group (End X) :=
{ mul_left_inv := groupoid.comp_inv, inv := groupoid.inv, ..End.monoid }

end End

lemma is_unit_iff_is_iso {C : Type u} [category.{v} C] {X : C} (f : End X) :
  is_unit (f : End X) ↔ is_iso f :=
⟨λ h, { out := ⟨h.unit.inv, ⟨h.unit.inv_val, h.unit.val_inv⟩⟩ },
  λ h, by exactI ⟨⟨f, inv f, by simp, by simp⟩, rfl⟩⟩

variables {C : Type u} [category.{v} C] (X : C)

/--
Automorphisms of an object in a category.

The order of arguments in multiplication agrees with
`function.comp`, not with `category.comp`.
-/
def Aut (X : C) := X ≅ X

attribute [ext Aut] iso.ext

namespace Aut

instance inhabited : inhabited (Aut X) := ⟨iso.refl X⟩

instance : group (Aut X) :=
by refine_struct
{ one := iso.refl X,
  inv := iso.symm,
  mul := flip iso.trans,
  div := _,
  npow := @npow_rec (Aut X) ⟨iso.refl X⟩ ⟨flip iso.trans⟩,
  zpow := @zpow_rec (Aut X) ⟨iso.refl X⟩ ⟨flip iso.trans⟩ ⟨iso.symm⟩ };
intros; try { refl }; ext;
simp [flip, (*), monoid.mul, mul_one_class.mul, mul_one_class.one, has_one.one, monoid.one,
  has_inv.inv]

/--
Units in the monoid of endomorphisms of an object
are (multiplicatively) equivalent to automorphisms of that object.
-/
def units_End_equiv_Aut : units (End X) ≃* Aut X :=
{ to_fun := λ f, ⟨f.1, f.2, f.4, f.3⟩,
  inv_fun := λ f, ⟨f.1, f.2, f.4, f.3⟩,
  left_inv := λ ⟨f₁, f₂, f₃, f₄⟩, rfl,
  right_inv := λ ⟨f₁, f₂, f₃, f₄⟩, rfl,
  map_mul' := λ f g, by rcases f; rcases g; refl }

end Aut

namespace functor

variables {D : Type u'} [category.{v'} D] (f : C ⥤ D) (X)

/-- `f.map` as a monoid hom between endomorphism monoids. -/
@[simps] def map_End : End X →* End (f.obj X) :=
{ to_fun := functor.map f,
  map_mul' := λ x y, f.map_comp y x,
  map_one' := f.map_id X }

/-- `f.map_iso` as a group hom between automorphism groups. -/
def map_Aut : Aut X →* Aut (f.obj X) :=
{ to_fun := f.map_iso,
  map_mul' := λ x y, f.map_iso_trans y x,
  map_one' := f.map_iso_refl X }

end functor

end category_theory


import data.real.basic
import data.finset.basic
import data.analysis.filter
import topology.basic
import analysis.calculus.mean_value
import tactic

/-
Как установить:
https://leanprover-community.github.io/install/windows.html
После установки elan и пакета mathlibtools для python через pip, выполнить
leanproject get-mathlib-cache, чтобы скачать уже собранный mathlib.

Источники:
https://www.ma.imperial.ac.uk/~buzzard/xena/natural_number_game/

https://leanprover-community.github.io/100.html
https://leanprover.github.io/theorem_proving_in_lean/quantifiers_and_equality.html#the-existential-quantifier
https://leanprover.github.io/theorem_proving_in_lean/tactics.html
https://leanprover-community.github.io/mathlib_docs/order/filter/basic.html
https://leanprover.github.io/logic_and_proof/first_order_logic_in_lean.html
-/

-- constants a b : ℝ

open filter set
open_locale filter topological_space pointwise

/-
notation a `<` b := real.lt_cauchy a b
notation a `=` b := real.equiv_Cauchy a b
notation a `≤` b := (a < b) ∨ (a = b)
-/

/-

notation `[` a `;` b `]` := set.Icc a b

constant f: [a; b] → ℝ

definition segment_partitions := { P | P ⊆ [a; b] ∧ set.finite P ∧ a ∈ P ∧ b ∈ P }

constant P: segment_partitions

#check P ∈ segment_partitions-/

/- #check set.finite.to_finset P-/

/- #check finset.to_list (set.to_finset P) -/

/- definition darboux_lower_sum := (finset.sort set.to_finite P) -/

definition series_partial_sum (series_items: ℕ → ℝ): ℕ → ℝ :=
  (λ (n : ℕ), (finset.range n).sum (λ (k : ℕ), series_items k))
/-structure series (item_func: ℕ → ℝ): Type :=
  (item : ℕ → ℝ := item_func)
  (partial_sum: ℕ → ℝ := series_partial_sum item_func) -/

notation `|` x `|` := algebra.abs x

definition sequence_converges (seq_a: ℕ → ℝ): Prop := ∃ (l : ℝ), ∀ (ε : ℝ), (ε > 0 → (∃(N : ℕ), ∀ (n : ℕ), n > N → | seq_a n - l | < ε))

definition series_converges (series_a: ℕ → ℝ) /-(series_a: series series_item_a)-/ : Prop := sequence_converges (series_partial_sum series_a)

constant series_a: ℕ → ℝ
--#check ∀ ε > 0, ∃ N ∈ ℕ, ∀ n > N, ∀ (p : ℕ), ((finset.range p).sum (λ (k : ℕ), series_a (n + k))) < ε

definition cauchy_sequence_condition (seq_a: ℕ → ℝ) := ∀ (ε : ℝ), (ε > 0 → (∃ (N : ℕ), ∀ (n : ℕ), n > N → (∀ (p : ℕ),
    (seq_a (n + p) - seq_a n) < ε)))

theorem cauchy_for_sequences (seq_a: ℕ → ℝ):
  (sequence_converges seq_a) ↔ (cauchy_sequence_condition seq_a)
:=
begin
  apply iff.intro,

  assume h : sequence_converges seq_a,
  intro ε,
  assume h₂ : (ε > 0),
  exists.intro l from h,

end

theorem cauchy_for_series (series_a: ℕ → ℝ):
  (series_converges series_a) ↔
  ∀ (ε : ℝ), (ε > 0 → (∃ (N : ℕ), ∀ (n : ℕ), n > N → (∀ (p : ℕ),
    (finset.range p).sum (λ (k : ℕ), series_a (n + k)) < ε
  )))
:=
begin
  sorry,
end
/- theorem (a_n: series) (b_n: series) : () := sorry,-/


import data.real.basic tactic utils
import rates

namespace leu_pin

section

  open_locale big_operators
  open finset (range Ico)

  @[reducible] def δ (ψ χ : ℕ → ℕ) (k : ℕ) : ℕ := max (ψ $ 3 * k + 2) ((χ $ 3 * k + 2) + 1)

  variables (a b c s : ℕ → ℝ) (M : ℕ) (ψ χ θ δ₀ : ℕ → ℕ)
  variables (a_nonneg : ∀ (n : ℕ), 0 ≤ a n) (a_lt_one : ∀ n : ℕ, a n < 1) (a_le_one : ∀ (n : ℕ), a n ≤ 1)
  variables (c_nonneg : ∀ (n : ℕ), 0 ≤ c n) (s_nonneg : ∀ (n : ℕ), 0 ≤ s n)
  variables (main_ineq : ∀ (n : ℕ), s (n + 1) ≤ (1 - a n) * s n + a n * b n + c n)

  include a_nonneg a_le_one c_nonneg s_nonneg main_ineq
  lemma leu_pin_3_2 (p N : ℕ) (h : ∀ n : ℕ, N ≤ n → b n ≤ 1 / (p + 1)) :
  ∀ n m : ℕ, N ≤ n → s (n + m + 1) ≤ (∏ i in range (m + 1), (1 - a (n + i))) * s n + (1 - ∏ i in range (m + 1), (1 - a (n + i))) * (1 / (p + 1)) + ∑ i in range (m + 1), c (n + i) :=
  begin
    intros n m h_N_le_n,
    induction m,
    case zero {
      simp only [add_zero, finset.prod_singleton, sub_sub_cancel, finset.sum_singleton, finset.range_one],
      specialize_all [n] at *,
      specialize h.n h_N_le_n,
      nlinarith,
    },
    case succ: m ih {
      have one_sub_a_nonneg : 0 ≤ 1 - a (n + m + 1) := sub_nonneg.mpr (a_le_one $ n + m + 1),
      have one_sub_a_le_one : 1 - a (n + m + 1) ≤ 1 := sub_le_self _ (a_nonneg $ n + m + 1),
      set A := ∏ i in range (m + 1), (1 - a (n + i)),

      have h₁ : (1 - a (n + m + 1)) * A = (∏ i in range (m + 1 + 1), (1 - (a $ n + i))) := by {
        dsimp [A],
        have : (1 - (a $ n + m + 1)) = (λ i, (1 - a (n + i))) (m + 1) := by { dsimp only, refl, },
        rw [this, ←finset.prod_range_succ_comm],
      },

      calc s (n + m.succ + 1)
          ≤ (1 - (a $ n + m + 1)) * (s $ n + m + 1) + (a $ n + m + 1) * (b $ n + m + 1) + (c $ n + m + 1) : by apply main_ineq
      ... ≤ (1 - (a $ n + m + 1)) * (A * s n + (1 - A) * (1 / (↑p + 1)) + ∑ (i : ℕ) in range (m + 1), c (n + i)) + (a $ n + m + 1) * (b $ n + m + 1) + (c $ n + m + 1) : by nlinarith
      ... ≤ (1 - (a $ n + m + 1)) * (A * s n + (1 - A) * (1 / (↑p + 1)) + ∑ (i : ℕ) in range (m + 1), c (n + i)) + (a $ n + m + 1) * (1 / (↑p + 1)) + (c $ n + m + 1) : by nlinarith [a_nonneg (n + m + 1), h (n + m + 1) (by linarith)]
      ... = (1 - (a $ n + m + 1)) * (A * s n) + (1 - (a $ n + m + 1)) * (1 - A) * (1 / (↑p + 1)) + (1 - (a $ n + m + 1)) * ( ∑ (i : ℕ) in range (m + 1), c (n + i)) + (a $ n + m + 1) * (1 / (↑p + 1)) + (c $ n + m + 1) : by ring
      ... = (1 - (a $ n + m + 1)) * (A * s n) + ((1 - (a $ n + m + 1)) * (1 - A) + (a $ n + m + 1)) * (1 / (↑p + 1)) + (1 - (a $ n + m + 1)) * ( ∑ (i : ℕ) in range (m + 1), c (n + i)) + (c $ n + m + 1) : by ring
      ... = (((1 - (a $ n + m + 1)) * A) * s n) + ((1 - (a $ n + m + 1)) * (1 - A) + (a $ n + m + 1)) * (1 / (↑p + 1)) + (1 - (a $ n + m + 1)) * ( ∑ (i : ℕ) in range (m + 1), c (n + i)) + (c $ n + m + 1) : by rw mul_assoc
      ... = ((∏ i in range (m.succ + 1), (1 - (a $ n + i))) * s n) + ((1 - (a $ n + m + 1)) * (1 - A) + (a $ n + m + 1)) * (1 / (↑p + 1)) + (1 - (a $ n + m + 1)) * ( ∑ (i : ℕ) in range (m + 1), c (n + i)) + (c $ n + m + 1) : by rw h₁
      ... = ((∏ i in range (m.succ + 1), (1 - (a $ n + i))) * s n) + ((1 - (a $ n + m + 1)) - (1 - (a $ n + m + 1)) * A + (a $ n + m + 1)) * (1 / (↑p + 1)) + (1 - (a $ n + m + 1)) * ( ∑ (i : ℕ) in range (m + 1), c (n + i)) + (c $ n + m + 1) : by ring
      ... = ((∏ i in range (m.succ + 1), (1 - (a $ n + i))) * s n) + (1 - (1 - (a $ n + m + 1)) * A) * (1 / (↑p + 1)) + (1 - (a $ n + m + 1)) * ( ∑ (i : ℕ) in range (m + 1), c (n + i)) + (c $ n + m + 1) : by ring
      ... = ((∏ i in range (m.succ + 1), (1 - (a $ n + i))) * s n) + (1 - (∏ i in range (m.succ + 1), (1 - (a $ n + i)))) * (1 / (↑p + 1)) + (1 - (a $ n + m + 1)) * ( ∑ (i : ℕ) in range (m + 1), c (n + i)) + (c $ n + m + 1) : by rw h₁
      ... ≤ ((∏ i in range (m.succ + 1), (1 - (a $ n + i))) * s n) + (1 - (∏ i in range (m.succ + 1), (1 - (a $ n + i)))) * (1 / (↑p + 1)) + ∑ i in range (m.succ + 1), c (n + i) : by {
        nth_rewrite 1 finset.sum_range_succ,
        have h₂ : (1 - a (n + m + 1)) * ∑ (i : ℕ) in range (m + 1), c (n + i) ≤ ∑ (x : ℕ) in range (m.succ), c (n + x) := mul_le_of_le_one_left (finset.sum_nonneg (λ i _, c_nonneg $ n + i)) one_sub_a_le_one,
        rw [(show n + m + 1 = n + m.succ, by rw add_assoc), ←add_assoc],
        repeat { rw [add_comm _ (c $ n + m.succ)] },
        repeat { apply add_le_add_left },
        exact h₂,
      }
    }
  end

  lemma leu_pin_3_3
  (M_pos : 0 < M)
  (s_le_M : ∀ n, s n ≤ M)
  (b_le_inv_k_succ : ∀ k n : ℕ, ψ k ≤ n → b n ≤ 1 / (k + 1))
  (χ_cauchy_modulus_sum_c : is_cauchy_modulus χ (λ n, ∑ i in range (n + 1), c i)) :
  ∀ k m n : ℕ, δ ψ χ k ≤ n →
  s (n + m + 1) ≤ M * (∏ i in range (m + 1), (1 - a (n + i))) + 2 / (↑(3 * k + 2) + 1):=
  begin
    -- intros k m n δ_le_n,
    have h₁ : ∀ k, ψ (3 * k + 2) ≤ δ ψ χ k := λ _, le_max_left _ _,
    have h₂ : ∀ k n, δ ψ χ k ≤ n → ψ (3 * k + 2) ≤ n := λ _ _ δ_le_n, le_trans (h₁ _) δ_le_n,
    have h₃ : ∀ k n, ψ (3 * k + 2) ≤ n → b n ≤ 1 / (↑(3 * k + 2) + 1):= λ k n ψ_le_n, b_le_inv_k_succ _ _ ψ_le_n,
    have h₄ : ∀ k n, δ ψ χ k ≤ n → b n ≤  1 / (↑(3 * k + 2) + 1) := λ k n δ_le_n, h₃ _ _ (h₂ _ _ δ_le_n),
    intros k m n δ_le_n,
    have := leu_pin_3_2 a b c s a_nonneg a_le_one c_nonneg s_nonneg main_ineq (3 * k + 2) (δ ψ χ k) (h₄ k) n m δ_le_n,

    have : 0 ≤ (∏ i in range (m + 1), (1 - (a $ n + i))) := finset.prod_nonneg (λ i _, sub_nonneg.mpr $ a_le_one $ n + i),

    have h₅ : s (n + m + 1) ≤ M * (∏ i in range (m + 1), (1 - (a $ n + i))) + (1 / (↑(3 * k + 2) + 1)) + ∑ i in range (m + 1), c (n + i) :=
    calc s (n + m + 1)
        ≤ (∏ (i : ℕ) in range (m + 1), (1 - a (n + i))) * M + (1 - ∏ (i : ℕ) in range (m + 1), (1 - a (n + i))) * (1 / (↑(3 * k + 2) + 1)) + ∑ (i : ℕ) in range (m + 1), c (n + i) : by nlinarith [s_le_M n]
    ... ≤ (∏ (i : ℕ) in range (m + 1), (1 - a (n + i))) * M + (1 / (↑(3 * k + 2) + 1)) + ∑ (i : ℕ) in range (m + 1), c (n + i) : by {
      -- have h₀' : (∏ (i : ℕ) in range (m + 1), (1 - a (n + i))) ≤ 1 := finset.prod_le_one (λ i _, sub_nonneg.mpr $ a_le_one $ n + i) (λ i _, sub_le_self 1 $ a_nonneg $ n + i),
      have h₁' : (1 - ∏ (i : ℕ) in range (m + 1), (1 - a (n + i))) ≤ 1 := sub_le_self 1 this,
      have h₂' : (0 : ℝ) ≤ (1 / (↑(3 * k + 2) + 1)) := one_div_nonneg.mpr (by { have : (0 : ℝ) ≤ (↑(3 * k + 2)) := nat.cast_nonneg (3 * k + 2), nlinarith }),
      have : (1 - ∏ (i : ℕ) in range (m + 1), (1 - a (n + i))) * (1 / (↑(3 * k + 2) + 1)) ≤  1 / (↑(3 * k + 2) + 1) := mul_le_of_le_one_left h₂' h₁',
      nlinarith,
    }
    ... ≤ M * (∏ i in range (m + 1), (1 - (a $ n + i))) + (1 / (↑(3 * k + 2) + 1)) + ∑ i in range (m + 1), c (n + i) : by rw [mul_comm _ ↑M],

    have h₆ : (χ $ 3 * k + 2) + 1 ≤ n := le_trans (le_max_right _ _) δ_le_n,

    set r : ℕ := n + m - χ (3 * k + 2),

    have h₇ : ∑ i in range (m + 1), c (n + i) ≤ 1 / (↑(3 * k + 2) + 1) :=
    calc ∑ i in range (m + 1), c (n + i)
        = ∑ i in Ico n (n + m + 1), c i : by {
          have : n + m + 1 - n = m + 1 := by {
            rw add_assoc,
            simp only [nat.add_sub_cancel_left],
          },
          rw ←this,
          rw finset.sum_Ico_eq_sum_range,
        }
    ... ≤ ∑ i in Ico ((χ $ 3 * k + 2) + 1) (n + m + 1), c i : by {
          apply @finset.sum_mono_set_of_nonneg ℕ ℝ _ _ c_nonneg,
          apply le_Ico_of_le_left _ h₆,
        }
    ... = ∑ i in Ico 0 (n + m + 1), c i - ∑ i in Ico 0 (χ (3 * k + 2) + 1), c i : by {
      have h₁' : ∑ i in Ico ((χ $ 3 * k + 2) + 1) (n + m + 1), c i + ∑ i in Ico 0 (χ (3 * k + 2) + 1), c i =  ∑ i in Ico 0 (n + m + 1), c i := by {
        have : Ico 0 (n + m + 1) \ Ico 0 (χ (3 * k + 2) + 1) = Ico (χ (3 * k + 2) + 1) (n + m + 1) := by apply finset.Ico.diff_left,
        rw ←this,
        apply finset.sum_sdiff,
        have h₂' : (χ (3 * k + 2)).succ ≤ n + m + 1 := by {
            have h₁'' : χ (3 * k + 2) + 1 ≤ n + 1 := add_le_add_right (le_trans (nat.le_succ _) h₆) 1,
            have h₂'' : n + 1 ≤ n + m + 1 := by {
              rw [add_comm n m, add_assoc],
              exact le_add_self,
            },
            exact le_trans h₁'' h₂'',
        },
        refine (finset.Ico.subset_iff (nat.succ_pos _)).mpr ⟨le_rfl, h₂'⟩,
      },
      exact eq_sub_of_add_eq h₁',
    }
    ... = ∑ i in range (n + m + 1), c i - ∑ i in range (χ (3 * k + 2) + 1), c i : by {
      repeat { rw finset.range_eq_Ico },
    }
    ... = ∑ i in range (χ (3 * k + 2) + r + 1), c i - ∑ i in range (χ (3 * k + 2) + 1), c i : by {
      have : n + m = χ (3 * k + 2) + r := by {
        -- simp [r],
        have : χ (3 * k + 2) ≤ n + m := by nlinarith,
        rw [←nat.add_sub_assoc this _],
        simp only [nat.add_sub_cancel_left],
      },
      rw this,
    }
    ... ≤ 1 / (↑(3 * k + 2) + 1) : by {
      specialize χ_cauchy_modulus_sum_c (3 * k + 2) (χ $ 3 * k + 2) r (le_refl _),
      rw real.dist_eq at χ_cauchy_modulus_sum_c,
      rw [abs_eq_self.mpr _] at χ_cauchy_modulus_sum_c,
      exact χ_cauchy_modulus_sum_c,
      dsimp only,
      rw [show χ (3 * k + 2) + r + 1 = χ (3 * k + 2) + 1 + r, by ring],
      apply nonneg_sub_of_nonneg_sum c_nonneg,
    },

    calc s (n + m + 1)
        ≤ M * (∏ i in range (m + 1), (1 - (a $ n + i))) + (1 / (↑(3 * k + 2) + 1)) + ∑ i in range (m + 1), c (n + i) : h₅
    ... ≤ M * (∏ i in range (m + 1), (1 - (a $ n + i))) + (1 / (↑(3 * k + 2) + 1)) + 1 / (↑(3 * k + 2) + 1) : by nlinarith
    ... = ↑M * ∏ (i : ℕ) in range (m + 1), (1 - a (n + i)) + 2 / (↑(3 * k + 2) + 1) : by ring,
  end

  omit a_le_one
  include a_lt_one
  lemma leu_pin_3_5
  (M_pos : 0 < M)
  (s_le_M : ∀ n, s n ≤ M)
  (b_le_inv_k_succ : ∀ k n : ℕ, ψ k ≤ n → b n ≤ 1 / ↑(k + 1))
  (χ_cauchy_modulus_sum_c : is_cauchy_modulus χ (λ n, ∑ i in range (n + 1), c i))
  -- (a_lt_one : ∀ n : ℕ, a n < 1)
  (θ_conv_rate_one_sub_a : is_rate_of_convergence_towards θ (λ n, ∏ i in range (n + 1), (1 - a i)) 0)
  (one_div_delta_le_one_sub_a : ∀ k : ℕ, (1 : ℝ) / (δ₀ k) ≤ ∏ i in range (δ ψ χ k), (1 - a i))
  : is_rate_of_convergence_towards (λ k, (max (θ $ 3 * M * (k + 1) * (δ₀ k) - 1) (δ ψ χ k) + 1)) s 0
  :=
  begin
    intros k n h,
    set σ := (λ (k : ℕ), max (θ (3 * M * (k + 1) * δ₀ k - 1)) (δ ψ χ k) + 1) with eq_σ,
    set m := n - δ ψ χ k - 1,
    have l_3_3 := leu_pin_3_3 a b c s M ψ χ a_nonneg (λ j, le_of_lt (a_lt_one j)) c_nonneg s_nonneg main_ineq M_pos s_le_M b_le_inv_k_succ χ_cauchy_modulus_sum_c k m (δ ψ χ k) (le_refl _),
    rw [real.dist_0_eq_abs, abs_of_nonneg (s_nonneg _)],
    have h₁ := finset.prod_range_add (1 - a) (δ ψ χ k) (m + 1),
    have h₂ : (∏ (x : ℕ) in range (δ ψ χ k + (m + 1)), (1 - a) x) / (∏ (x : ℕ) in range (δ ψ χ k), (1 - a) x) = (∏ (x : ℕ) in range (m + 1), (1 - a) (δ ψ χ k + x)) := by {
      refine (cancel_factors.cancel_factors_eq_div (eq.symm h₁) (ne.symm _)).symm,
      apply ne.symm,

      apply finset.prod_ne_zero_iff.mpr (λ i _, ne_of_gt $ _),
      work_on_goal 3 {
        simp only [pi.one_apply, pi.sub_apply],
        specialize a_lt_one i,
        exact sub_pos.mpr a_lt_one,
      },
      apply_instance, apply_instance,
    },

    have h₃ : n = δ ψ χ k + m + 1 := sorry,

    have h₄ : 1 / (∏ (x : ℕ) in range (δ ψ χ k), (1 - a) x) ≤ δ₀ k := sorry,

    have : ∏ i in range (δ ψ χ k + (m + 1)), (1 - a i) ≤ 1 / (3 * M * δ₀ k * (k + 1)) := sorry,

    have h₃ : ↑M * ∏ (i : ℕ) in range (m + 1), (1 - a (δ ψ χ k + i)) + 2 / (3 * ↑k + 2) ≤ 1 / (k + 1) :=
    calc ↑M * ∏ (i : ℕ) in range (m + 1), (1 - a (δ ψ χ k + i)) + 2 / (3 * ↑k + 2)
        = ↑M * ((∏ (x : ℕ) in range (δ ψ χ k + (m + 1)), (1 - a) x) / ∏ (x : ℕ) in range (δ ψ χ k), (1 - a) x) + 2 / (3 * ↑k + 2) : by rw h₂; simp only [pi.one_apply, pi.sub_apply]
    ... = ↑M * ((∏ (x : ℕ) in range (δ ψ χ k + (m + 1)), (1 - a) x)) * (1 / ∏ (x : ℕ) in range (δ ψ χ k), (1 - a) x) + 2 / (3 * ↑k + 2) : by ring
    ... ≤ ↑M * ((∏ (x : ℕ) in range (δ ψ χ k + (m + 1)), (1 - a) x)) * δ₀ k + 2 / (3 * ↑k + 2) : by {
      have : 0 ≤ ↑M * ((∏ (x : ℕ) in range (δ ψ χ k + (m + 1)), (1 - a) x)) := sorry,
      nlinarith,
    }
    ... ≤ ↑M * (1 / (3 * M * δ₀ k * (k + 1))) * δ₀ k + 2 / (3 * ↑k + 2) : by {
      have : 0 ≤ (M : ℝ) := sorry,

    }
    ... ≤ 1 / (k + 1) : sorry

  end

  -- nu ne trebuie
  -- lemma leu_pin_3_6
  -- (s_le_M : ∀ n : ℕ, s n ≤ M)
  -- (b_le_inv_k_succ : ∀ p n : ℕ, ψ p ≤ n → b n ≤ 1 / (p + 1))
  -- (θ_conv_rate_one_sub_a:  is_rate_of_convergence_towards θ (λ n, ∏ i in range (n + 1), (1 - a i)) 0)
  -- (one_div_delta_le_one_sub_a : ∀ k : ℕ, (1 : ℝ) / (δ k) ≤ ∏ i in range (δ k), (1 - a i)) :
  -- is_rate_of_convergence_towards (λ k, max (θ $ 2 * M * (δ k) * (k + 1) - 1) (δ k) + 1) s 0 :=
  -- sorry

end

end leu_pin


import Mathlib.Algebra.Group.Basic
import Mathlib.Algebra.Group.Defs

structure Graph(V: Type) (E: Type) where
  null : E
  init : E → V
  bar : E → E
  barInv : bar ∘ bar = id
  barNoFP : ∀ e: E, bar e ≠ e

variable {V: Type}{E: Type}[DecidableEq V][DecidableEq E]{x₁ x₂ : V}

@[inline] def term (graph: Graph V E): E → V :=
  fun e => graph.init (graph.bar e)

inductive EdgePath (graph: Graph V E): V → V → Type where
  | single : (x: V) → EdgePath graph x x
  | cons : {x y z : V} → (e : E) → graph.init e = x → term graph e = y →
        EdgePath graph y z → EdgePath graph x z

def EdgePath.length {graph: Graph V E}{x y: V}: EdgePath graph x y → Nat
  | EdgePath.single x => 0
  | EdgePath.cons  _ _ _ path => length path + 1

theorem bar_involution {G : Graph V E} : (ex : E) → G.bar (G.bar ex) = ex := by
  intro ex
  apply congr G.barInv (Eq.refl ex)

open EdgePath

-- concatenates two edgepaths
def multiply {G : Graph V E} {x y z : V}: (EdgePath G x y) → (EdgePath G y z) → (EdgePath G x z) := by
intro p q
match p with
| single x => exact q
| cons ex h1 h2 exy => exact (cons ex h1 h2 (multiply exy q))

--proves that the endpoint of the reverse of an edge is the start point of the edge
theorem term_bar_equals_init {G : Graph V E} {x : V}{e : E}: G.init e = x → (term G (G.bar e) = x) :=
by
intro h
have h₁ : G.bar (G.bar e) = e := congr G.barInv (Eq.refl e)
have h₂ : G.init (G.bar (G.bar e)) = G.init e := congrArg G.init h₁
apply Eq.trans h₂ h

--proves that initial vertex of reversed edge is the terminal vertex of the original edge
theorem init_bar_equals_term {G : Graph V E} {x :V} {e : E}: (term G e = x) → G.init (G.bar e) = x:= by
intro hyp
have : G.init (G.bar e) = term G e := by rfl
exact Eq.trans this hyp

-- proves associativity of path multiplication
theorem mult_assoc {G : Graph V E} (p : EdgePath G w x) (q : EdgePath G x y) (r : EdgePath G y z):
      (multiply (multiply p q) r) = (multiply p (multiply q r)) := by
      induction p with
      | single w => simp[multiply]
      | cons ex h1 h2 exy ih => simp[multiply, ih]

--proves that "single x" is an identity element for multiplication
theorem mult_const {G : Graph V E} (p : EdgePath G x y) :
      (multiply p (single y)) = p := by
      induction p with
      | single x => simp [multiply]
      | cons ex h1 h2 exy ih => simp[multiply, ih]

-- reverses an edgepath
def inverse {G : Graph V E} {x y : V}: (EdgePath G x y) → (EdgePath G y x)
| single x => single x
| cons ex h₁ h₂ exy => multiply (inverse exy) (cons (G.bar (ex)) h₂ (term_bar_equals_init h₁) (single x))

--defines an edgepath with a single edge
def basicpath {G : Graph V E} (x : V) (ex : E) (h₁ : G.init ex = x) (h₂ : term G ex = y): EdgePath G x y := cons ex h₁ h₂ (single y)

--proves that multiplication with a basicpath gives inductive definition of edgepath
theorem basicpath_mult {G : Graph V E} {x y z : V} (ex : E) (h₁ : G.init ex = x) (h₂ : term G ex = y) (exy : EdgePath G y z) : multiply (basicpath x ex h₁ h₂) exy = cons ex h₁ h₂ exy := by
    simp[basicpath, multiply]

--defines inverse in terms of basicpath
theorem inverse_lemma {G : Graph V E} {x y z : V} (ex : E) (h₁ : G.init ex = x) (h₂ : term G ex = y) (exy : EdgePath G y z) : inverse (cons ex h₁ h₂ exy) = multiply (inverse exy) (basicpath y (G.bar ex) h₂ (term_bar_equals_init h₁) ) := by rfl

--proves fundamental relation between inverse and multiply
theorem inverse_mult {G : Graph V E} (x y z : V) (p : EdgePath G x y) (q : EdgePath G y z) (r : EdgePath G y x) (s : EdgePath G z y) (h₁ : r = inverse p) (h₂ : s = inverse q) :
      inverse (multiply p q) = multiply s r := by
      induction p with
      | single x =>
            have p₁ : multiply (single x) q = q := by rfl
            rw[p₁]
            have p₂ : r = single x := by rw[h₁] ; rfl
            rw[p₂]
            rw[mult_const s]
            simp[h₂]
      | cons ex t₁ t₂ exy ih =>
          have p₃ : multiply (cons ex t₁ t₂ exy) q = cons ex t₁ t₂ (multiply exy q) := by simp[mult_assoc, basicpath_mult, multiply]
          rw[p₃]
          --simp[inverse]
          have p₄ : r = multiply (inverse exy) (basicpath _ (G.bar ex)  t₂ (term_bar_equals_init t₁)) := by simp[h₁, inverse_lemma]
          rw[p₄]
          have p₅ : multiply s (inverse exy) = inverse ( multiply exy q) := by simp[ih q (inverse exy) s rfl h₂]
          let p₆ := inverse_lemma ex t₁ t₂ (multiply exy q)
          rw[p₆]
          simp[Eq.symm (p₅), mult_assoc]

--proves that inverse is an involution on paths
theorem inverse_involution  {G : Graph V E} {x y : V} (p : EdgePath G x y) : inverse (inverse p) = p := by
  induction p with
  | single x => rfl
  | cons ex h₁ h₂ exy ih =>
    have p₁ : inverse (cons ex h₁ h₂ exy) = multiply (inverse exy) (inverse (basicpath _ ex h₁ h₂)) := by rfl
    rw[p₁]
    let k := inverse_mult _ _ _ (inverse exy)  (inverse (basicpath _ ex h₁ h₂)) exy (inverse (inverse (basicpath _ ex _ h₂))) (Eq.symm ih) rfl
    rw[k]
    have p₂ : inverse (inverse (basicpath _ ex h₁ h₂)) = basicpath _ ex h₁ h₂ := by
      simp[basicpath, inverse]
      have p₃ : G.bar (G.bar ex) = ex := by apply congr G.barInv (Eq.refl ex)
      simp[p₃]
      rfl
    rw[p₂]
    apply basicpath_mult ex h₁ h₂ exy

--helper function for reducePath, that reduces the first two edges of the path
def reducePathAux {G : Graph V E} {x y z : V} (ex : E) (h₁ : G.init ex = x) (h₂ : term G ex = y) (exy : EdgePath G y z) : { rp : EdgePath G x z // rp.length ≤ (cons ex h₁ h₂ exy).length} := by
cases exy with
 | single x => exact ⟨cons ex h₁ h₂ (single y), by simp⟩
 | cons ey h₃ h₄ eyz =>
   apply
  if c : (x = term G ey) ∧ (ey = G.bar (ex)) then
  ⟨ Eq.symm (Eq.trans (And.left c) h₄) ▸ eyz, by
      have h₅ : (Eq.symm (Eq.trans (And.left c) h₄) ▸ eyz).length = eyz.length := by
        let ih := Eq.symm (Eq.trans (And.left c) h₄)
        cases ih with
        | refl => rfl
      simp[EdgePath.length, h₅, Nat.le_trans (Nat.le_succ (length eyz)) (Nat.le_succ (length eyz +1))]
  ⟩
  else
  ⟨ cons ex h₁ h₂ (cons ey h₃ h₄ (eyz)), by apply Nat.le_refl⟩

-- reduces given path such that no two consecutive edges are inverses of each other
def reducePath {G : Graph V E} : (p : EdgePath G x y ) →
  {rp : EdgePath G x y // rp.length ≤ p.length}
| single x => ⟨single x, by simp⟩
| cons ex h₁ h₂ ey'y =>
  let ⟨ey'z, ih1⟩ := reducePath ey'y
  let ⟨ ez, ih⟩ := reducePathAux ex h₁ h₂ ey'z
  ⟨ ez , by
          have : length (cons ex h₁ h₂ ey'z) = length ey'z + 1 := by simp[length]
          let k := this ▸ ih
          have pr : length ey'z +1 ≤ length ey'y +1 := by apply Nat.succ_le_succ ih1
          simp[length, Nat.le_trans k pr] ⟩

  termination_by _ _ _ _ p => p.length
  decreasing_by
  simp_wf
  simp[EdgePath.length, Nat.lt_succ_self]

--defines moves allowing homotopy of edgepaths
inductive basicht {G : Graph V E} : EdgePath G x y → EdgePath G x y → Sort where
  | consht : (x : V) → (basicht (single x) (single x)) -- constant homotopy
  | cancel : (ex ex' : E) → { x w y : V} → (p : EdgePath G x y) → -- cancelling consecutive opposite edges from a path
            (h : G.init ex = x) → (h' : term G ex = w) → (t : G.bar ex = ex') →
            basicht p (cons ex h h' (cons ex' (t ▸ init_bar_equals_term h') (t ▸ term_bar_equals_init h) p))
  | mult : {x y z : V} → {p q : EdgePath G y z} →   -- adding an edge to homotopic paths
          basicht p q → (ex : E) →(h' : G.init ex = x) → ( h : term G ex = y)→
          basicht (cons ex h' h p) (cons ex h' h q)

--defines the set of homotopy classes of edgepaths from x to y
def ht (G : Graph V E) (x y : V) := Quot (@basicht V E G x y )

def htclass {G : Graph V E} {x y : V} ( p : EdgePath G x y) : ht G x y :=
  Quot.mk (@basicht V E G x y ) p

def homotopy {G : Graph V E} {x y : V} ( p' q' : EdgePath G x y) : Prop :=
  htclass p' = htclass q'

--proves that homotopy is a transitive relation
theorem homotopy_trans {G : Graph V E} {x y : V} (p q r : EdgePath G x y) : homotopy p q → homotopy q r → homotopy p r := by
  intro h₁ h₂
  simp[homotopy, htclass]
  have p₁ : Quot.mk basicht p = Quot.mk basicht q := by apply h₁
  have p₂ : Quot.mk basicht q = Quot.mk basicht r := by apply h₂
  apply Eq.trans p₁ p₂

--proves that homotopy is preserved by multiplying an edge to the left
theorem homotopy_left_mult_edge {G : Graph V E} {x y z : V}: (p q : EdgePath G y z) → homotopy p q → (ex : E) →(h1 : G.init ex = x) → ( h : term G ex = y)→ homotopy (cons ex h1 h p) (cons ex h1 h q) := by
  intro p q h ex h1 h2
  let func (r : EdgePath G y z) : ht G x z := htclass (cons ex h1 h2 r)
  have g : (r₁ r₂ : EdgePath G y z) →  basicht r₁ r₂ → func r₁ = func r₂ := by
     intro r₁ r₂ h₁
     let t := basicht.mult h₁ ex h1 h2
     have : htclass (cons ex h1 h2 r₁) = htclass (cons ex h1 h2 r₂) := by simp[htclass]; apply Quot.sound t
     simp[this]
  simp[homotopy]
  let k := Quot.lift func g
  apply congrArg k h

--proves that homotopy is left multiplicative
theorem homotopy_left_mult {G : Graph V E} {x y z : V} (p1 p2 : EdgePath G y z) (q : EdgePath G x y) (h :homotopy p1 p2):
         (homotopy (multiply q p1) (multiply q p2)) := by
         induction q with
        | single w  =>
          simp [multiply, h]
        | cons ex h1 h2 exy ih =>
          let c := ih p1 p2 h
          have t₁ : multiply (cons ex h1 h2 exy) p1 = cons ex h1 h2 (multiply exy p1) := by simp[multiply, mult_assoc]
          have t₂ : multiply (cons ex h1 h2 exy) p2 = cons ex h1 h2 (multiply exy p2) := by simp[multiply, mult_assoc]
          rw[t₁, t₂]
          simp[homotopy_left_mult_edge, c]

--proves that homotopy is reflexive
theorem homotopy_rfl {G :Graph V E} {x y : V} (p : EdgePath G x y) : homotopy p p := by rfl

--proves that path is homotopic to itself after appending cancelling pair of edges at its end
theorem induct_homotopy_inverse_cancel {G : Graph V E}  (ey ey' : E)  { x w y : V} (p : EdgePath G x y) (h₁ : G.init ey = y) (h₂ : term G ey = w) (t : G.bar ey = ey') (h₃ : G.init ey' = w) (h₄ : term G ey' = y) :
           homotopy p (multiply p (cons ey h₁ h₂ (cons ey' h₃ h₄ (single y)) )) := by
           induction p with
           | single x => simp[multiply, basicpath, homotopy, htclass, Quot.sound (basicht.cancel ey ey' (single x) h₁ h₂ t)]
           | cons ex t₁ t₂ exy ih =>
            let x := ih h₁ h₄
            have p₁ : homotopy (cons ex t₁ t₂ exy) (cons ex t₁ t₂ (multiply exy (cons ey h₁ h₂ (cons ey' h₃ h₄ (single _))))) := by simp[homotopy_left_mult_edge _ _ x ex t₁ t₂]
            have :  cons ex t₁ t₂ (multiply exy (cons ey h₁ h₂ (cons ey' h₃ h₄ (single _)))) = multiply (cons ex t₁ t₂ exy) (cons ey h₁ h₂ (cons ey' h₃ h₄ (single _))) := by simp[multiply]
            simp[basicpath_mult ey h₁ h₂, basicpath_mult ey' h₃ h₄]
            apply this ▸ p₁

--proves that a pair of paths remains homotopic after appending a single edge at their ends
theorem induct_homotopy_inverse_mult {G : Graph V E} {x y z : V} (p q : EdgePath G x y) (ey : E) (h₁ : G.init ey = y) (h₂ : term G ey = z) : homotopy p q → homotopy (multiply p (basicpath y ey h₁ h₂)) (multiply q (basicpath y ey h₁ h₂)) := by
    intro h
    let func : EdgePath G x y → ht G x z := by intro p ; exact htclass (multiply p (basicpath y ey h₁ h₂))
    have g : (s₁ s₂ : EdgePath G x y) → (basicht s₁ s₂) → func s₁ = func s₂ := by
      intro s₁ s₂ ht
      simp
      induction ht with
      | consht x => simp[homotopy_rfl]
      | cancel ex ex' r t₁ t₂ t₃ =>
        let x := basicht.cancel ex ex' (multiply r (basicpath _ ey h₁ h₂)) t₁ t₂ t₃
        simp[multiply, htclass]
        apply Quot.sound x
      | @mult _ _ _ p₁ p₂ b ex t₁ t₂ ih =>
        let x := homotopy_left_mult_edge _ _ (ih p₁ p₂ h₁ (Quot.sound b)) ex t₁ t₂
        simp[multiply, x]
        apply x
    simp[homotopy]
    let k := Quot.lift func g
    apply congrArg k h

--proves that homotopy of paths implies homotopy of their inverses
theorem homotopy_inverse {G : Graph V E} {x y : V} (p₁ p₂: EdgePath G x y) (q₁ q₂ : EdgePath G y x) (h₁ : q₁ = inverse p₁) (h₂ : q₂ = inverse p₂) : homotopy p₁ p₂ → homotopy q₁ q₂ := by
  intro h₀
  let func : EdgePath G x y → ht G y x := by intro p ; exact htclass (inverse p)
    have g : (s₁ s₂ : EdgePath G x y) → (h : basicht s₁ s₂) →  homotopy (inverse s₁) (inverse s₂) := by
      intro s₁ s₂ h
      induction h with
        | consht x => simp[inverse, homotopy_rfl]

        | cancel ex ex' p t₁ t₂ t₃ =>
          simp[htclass, inverse, mult_assoc]
          apply induct_homotopy_inverse_cancel (G.bar ex') (G.bar ex) (inverse p) (by rw[← t₃]; rw[bar_involution ex]; apply t₁) (by rw[← t₃]; rw[(bar_involution ex)]; apply t₂) (by rw[bar_involution ex']; apply (Eq.symm t₃)) (init_bar_equals_term t₂) (term_bar_equals_init t₁)

        | @mult _ _ _ s₁ s₂ b ex t₁ t₂ ih =>
          simp[homotopy, inverse]
          let c := ih s₁ s₂ (inverse s₁) (inverse s₂) rfl rfl (Quot.sound b)
          let k := induct_homotopy_inverse_mult (inverse s₁) (inverse s₂) (G.bar ex) t₂ (term_bar_equals_init t₁) c
          apply k
  let k₂ := Quot.lift func g
  apply h₁ ▸ h₂ ▸ (congrArg k₂ h₀)

--homotopy_inverse with lesser arguments
theorem homotopy_inverse_quick  {G : Graph V E} {x y : V} {p₁ p₂: EdgePath G x y} : homotopy p₁ p₂ → homotopy (inverse p₁) (inverse p₂) := by
 apply homotopy_inverse p₁ p₂ (inverse p₁) (inverse p₂) rfl rfl

-- proves that homotopy is right multiplicative
theorem homotopy_right_mult {G : Graph V E} {x y z : V} (p₁ p₂ : EdgePath G x y) (q : EdgePath G y z) (h :homotopy p₁ p₂):
         (homotopy (multiply p₁ q) (multiply p₂ q)) := by
         have r₁ : homotopy (inverse p₁) (inverse p₂) := by apply homotopy_inverse_quick h
         have r₂ : homotopy (multiply (inverse q) (inverse p₁)) (multiply (inverse q) (inverse p₂)) := by apply homotopy_left_mult ; apply r₁
         have r₃ : homotopy (inverse (multiply (inverse q) (inverse p₁))) (inverse (multiply (inverse q) (inverse p₂))) := by apply homotopy_inverse_quick r₂
         let s₁ := inverse_mult x y z p₁ q (inverse p₁) (inverse q) rfl rfl
         let s₂ := inverse_involution (multiply p₁ q)
         let s₃ := Eq.trans (Eq.symm s₂) (congrArg inverse s₁)
         let t₁ := inverse_mult x y z p₂ q (inverse p₂) (inverse q) rfl rfl
         let t₂ := inverse_involution (multiply p₂ q)
         let t₃ := Eq.trans (Eq.symm t₂) (congrArg inverse t₁)
         apply s₃ ▸ t₃ ▸ r₃

--defines multiplication of homotopy class with a path to its left
def homotopy_left_multiplication {G :Graph V E} {x y z : V} (p₁ : EdgePath G x y) : ht G y z →  ht G x z := by
  let func: EdgePath G y z → ht G x z := by intro p ; exact htclass (multiply p₁ p)
  have g : (q₁ q₂ : EdgePath G y z) → basicht q₁ q₂ → func q₁ = func q₂ := by
          intro q₁ q₂ h
          simp
          have h' : homotopy q₁ q₂ := by simp[homotopy, htclass, Quot.sound h ]
          apply homotopy_left_mult q₁ q₂ p₁ h'
  apply Quot.lift func g

--proves that the multiplication defined above equals homotopy class of multiplied paths
theorem homotopy_left_multiplication_class {G :Graph V E} {x y z : V} (p₁ : EdgePath G x y) (p₂ : EdgePath G y z): homotopy_left_multiplication p₁ (htclass p₂) = htclass (multiply p₁ p₂) := by
  simp[htclass, homotopy_left_multiplication]

--defines multiplication of homotopy
def homotopy_multiplication : ht G x y → ht G y z → ht G x z := by
  intro p₁ p₂
  let func (p : EdgePath G x y) : ht G x z := homotopy_left_multiplication p p₂
  have g : (q₁ q₂ : EdgePath G x y) → basicht q₁ q₂ → func q₁ = func q₂  := by
      intro q₁ q₂ h
      let crop (p : ht G y z) : Prop := homotopy_left_multiplication q₁ p = homotopy_left_multiplication q₂ p
      have g' : (p : ht G y z) → crop p := by
        intro p
        have g'' : (p' : EdgePath G y z) → crop (htclass p') := by
          intro p'
          have h' : homotopy q₁ q₂ := by simp[homotopy, htclass, Quot.sound h]
          have : htclass (multiply q₁ p') = htclass (multiply q₂ p') := by
            let prop := homotopy_right_mult q₁ q₂ p' h'
            rw[prop]
          simp[homotopy_left_multiplication_class, this]
        apply Quot.ind g''
      let hh := g' p₂
      have : homotopy_left_multiplication q₁ p₂ = homotopy_left_multiplication q₂ p₂ := by simp[hh]
      simp[this]
  let k := (Quot.lift (fun x => func x) g)
  apply k p₁

infixl: 65 " # " => homotopy_multiplication

--proves that # is associative up to multiplication by a pair of paths and one homotopy class
theorem homotopy_mult_path_path_assoc {G :Graph V E} {w x y z : V} (p : EdgePath G w x) (q : EdgePath G x y): (r : ht G y z) → (htclass (multiply p q)) # r = (htclass p) # ((htclass q) # r) := by
  apply Quot.ind
  intro b
  simp[htclass, homotopy_multiplication]
  have p₁ : homotopy_left_multiplication (multiply p q) (Quot.mk basicht b) = htclass ( multiply (multiply p q) b) := by rfl
  have p₂ : (homotopy_left_multiplication q (Quot.mk basicht b)) = htclass (multiply q b) := by rfl
  have p₃ : (homotopy_left_multiplication p (homotopy_left_multiplication q (Quot.mk basicht b))) = htclass (multiply p (multiply q b)) := by rw[p₂] ; rfl
  rw[p₁,p₃]
  simp[mult_assoc]

--proves that # is associative up to multiplication by one path and a pair of homotopy classes
theorem homotopy_mult_path_assoc {G :Graph V E} {w x y z : V} (p : EdgePath G w x) (r : ht G y z): (q : ht G x y) → (htclass p # q) # r = (htclass p) # (q # r) := by
  apply Quot.ind
  intro a
  simp[htclass]
  have p₁ : Quot.mk basicht p # Quot.mk basicht a = htclass (multiply p a) := by rfl
  rw[p₁]
  apply homotopy_mult_path_path_assoc p a r

--proves that # is associative on homotopy classes
theorem homotopy_mult_assoc {G :Graph V E} {w x y z : V} (b : ht G x y) (c : ht G y z) : (a : ht G w x) → ((a # b) # c ) = (a # (b # c) )  := by
  apply Quot.ind
  intro a
  apply homotopy_mult_path_assoc a c b

--proves that reducePathAux preserves the homotopy class
theorem homotopy_reducePathAux {G : Graph V E} {x y z : V} (ex : E) (h₁ : G.init ex = x) (h₂ : term G ex = y) (exy : EdgePath G y z) : homotopy (cons ex h₁ h₂ exy) (reducePathAux ex h₁ h₂ exy) := by
  simp[homotopy, htclass]
  cases exy with
  | single y =>
      have : reducePathAux ex h₁ h₂ (single y) = cons ex h₁ h₂ (single y) := by rfl
      rfl
  | cons ey h₃ h₄ eyz => apply
    if c : (x = term G ey) ∧ (ey = G.bar (ex)) then by
      let ih := Eq.symm (Eq.trans (And.left c) h₄)
      cases ih with
      |refl =>
          have : basicht (ih ▸ eyz)  (cons ex h₁ h₂ (cons ey h₃ h₄ eyz))  := by
            let j := @basicht.cancel V E G ex ey x y _ (ih ▸ eyz) h₁ h₂ (Eq.symm c.2)
            apply j
          have pr : (reducePathAux ex h₁ h₂ (cons ey h₃ h₄ eyz)).1 = (ih ▸ eyz) := by simp[reducePathAux, c]
          let k := Quot.sound (Eq.symm pr ▸ this)
          exact (Eq.symm k)
    else by
      have : reducePathAux ex h₁ h₂ (cons ey h₃ h₄ eyz) = cons ex h₁ h₂ (cons ey h₃ h₄ (eyz)) := by simp[reducePathAux, c]
      simp [this]

--proves that reducePath preserves the homotopy class
theorem homotopy_reducePath {G : Graph V E} {x y : V} (p₁ : EdgePath G x y): homotopy p₁ (reducePath p₁) := by
        induction p₁ with
        | single x =>
          simp[homotopy, htclass]
          have h₁ : ( @reducePath V E _ _ x x G (single x)).1 = single x  := by rfl
          have h₂ : single x = (reducePath (single x)).1 := by exact (Eq.symm h₁)
          apply Quot.sound (h₂ ▸ basicht.consht x)

        | cons ex h₁ h₂ exy ih' =>
          let p₃ := reducePath exy
          let q := p₃.1
          let p₄ := reducePathAux ex h₁ h₂ q
          have : p₄.1 = (reducePath (cons ex h₁ h₂ exy)).1 := by rfl
          have t₁ : homotopy (cons ex h₁ h₂ q) (p₄.1) := by apply homotopy_reducePathAux ex h₁ h₂ q
          have t₂ : homotopy exy q := by apply ih'
          have t₃ : homotopy (cons ex h₁ h₂ exy) (cons ex h₁ h₂ q) := by apply homotopy_left_mult_edge exy q t₂ ex h₁ h₂
          apply homotopy_trans (cons ex h₁ h₂ exy) (cons ex h₁ h₂ q) (reducePath (cons ex h₁ h₂ exy)) t₃ (this ▸ t₁)

instance ht_mul {G : Graph V E} {x : V} :  Mul (ht G x x) where
mul : ht G x x → ht G x x → ht G x x := fun a b => homotopy_multiplication a b

instance ht_one {G : Graph V E} {x : V} : One (ht G x x) where
one : ht G x x := htclass (single x)

theorem ht_mult_assoc {G : Graph V E} {x : V} (a b c : ht G x x) : ((a # b) # c) = (a # ( b # c)) := by
let k := @homotopy_mult_assoc V E G x x x x
let l (a b c : ht G x x) := k b c a
apply (l a b c)

theorem ht_right_identity {G : Graph V E} {x : V} : (a₀ : ht G x x) → ht_mul.mul a₀  (@ht_one V E G x).one = a₀ := by
have  k (b : EdgePath G x x) : homotopy_multiplication (htclass b) (htclass (single x)) = (htclass b) := by
    let l :=mult_const b ▸ homotopy_left_multiplication_class b (single x)
    have : (htclass b) # (htclass (single x)) = homotopy_left_multiplication b (htclass (single x)) := by rfl
    apply Eq.trans this l
have k₂ (b : EdgePath G x x) : ht_mul.mul (htclass b) (@ht_one V E G x).one = htclass b := by
  apply k
apply Quot.ind
apply k₂

theorem ht_left_identity {G : Graph V E} {x : V} : (a₀ : ht G x x) → ht_mul.mul (@ht_one V E G x).one a₀ = a₀ := by
have  k (b : EdgePath G x x) : homotopy_multiplication (htclass (single x)) (htclass b) = (htclass b) := by
    have : (htclass b) # (htclass (single x)) = (htclass (multiply b (single x))) := by rfl
    apply (mult_const b) ▸ this
have k₂ (b : EdgePath G x x) : ht_mul.mul (@ht_one V E G x).one (htclass b)  = htclass b := by
  apply k
apply Quot.ind
apply k₂

def ht_semigroup (G : Graph V E) (x : V) := @Semigroup.mk (ht G x x) (ht_mul) (by apply ht_mult_assoc)

def ht_monoid (G : Graph V E) (x : V) := @Monoid.mk (ht G x x) (ht_semigroup G x) (ht_one) (ht_right_identity) (ht_left_identity) (npow_rec) (by simp[npow_rec]) (by simp[npow_rec])


import group_theory.semidirect_product
import coprod.free_group
/-!
# The P functor

Given a group `G` an element `p` of the group `P G` can be used as a certificate
of congruences modulo `r : G` in `G`. There are two surjective maps `P G →* G`,
`lhs r` and `right`. `lhs r p` and `right p` will always be congruent modulo
`r` in `G`, and so `p` is a certificate of this congruence. This is used is the
`group_thingy` tactic.
-/

variables {G : Type} [group G] [decidable_eq G]
variables {H : Type} [group H] [decidable_eq H]

open free_group semidirect_product function

/-- `mul_free` is the extension of the left regular action of `G` to an
  action of `G` on `free_group G` -/
def mul_free : G →* mul_aut (free_group G) :=
{ to_fun := λ g, free_group.equiv (equiv.mul_left g),
  map_mul' := λ _ _, mul_equiv.to_monoid_hom_injective $ free_group.hom_ext $
    by simp [mul_aut.mul_def, equiv.perm.mul_def, mul_assoc],
  map_one' := mul_equiv.to_monoid_hom_injective $ free_group.hom_ext $
    by simp [mul_aut.mul_def, equiv.perm.mul_def, mul_assoc] }

@[simp] lemma mul_free_of' (g : G) (h : G) (n : C∞) :
  mul_free g (of' h n) = of' (g * h) n :=
by simp [mul_free]

@[simp] lemma mul_free_of (g : G) (h : G) : mul_free g (of h) = of (g * h) :=
by simp [mul_free, of_eq_of']

@[simp] lemma mul_free_inv_of (g : G) (h : G) : (mul_free g)⁻¹ (of h) = of (g⁻¹ * h) :=
by rw [← monoid_hom.map_inv, mul_free_of]

variable (G)

/-- The group `P G` is used as a certificate of congruences of elements of `G`
  modulo a relation `r : G`. -/
@[reducible] def P : Type* := free_group G ⋊[mul_free] G

instance [decidable_eq G] : decidable_eq (P G) :=
semidirect_product.decidable_eq _ _ _

variable {G}
namespace P

variables {r : G}

instance : group (P G) := semidirect_product.group

/-- `p` is a certificate of the congruence between `lhs r p` and `right p` modulo `r`  -/
def lhs (r : G) : P G →* G :=
semidirect_product.lift (free_group.lift (λ g, mul_aut.conj g r))
  (monoid_hom.id _)
  (λ g, free_group.hom_ext
    (by simp [of'_eq_of_pow, gpowers_hom, mul_assoc]))

@[simp] lemma lhs_comp_inr : (lhs r).comp inr = monoid_hom.id _ :=
by simp [lhs]

@[simp] lemma lhs_inr (g : G) : lhs r (inr g) = g := by simp [lhs]

@[simp] lemma lhs_comp_inl : (lhs r).comp inl = free_group.lift (λ g, mul_aut.conj g r) :=
by simp [lhs]

lemma lhs_inl (w : free_group G) :
  lhs r (inl w) = free_group.lift (λ g, mul_aut.conj g r) w :=
by simp [lhs]

/--  -/
def trans (p q : P G) : P G :=
⟨p.1 * q.1, q.2⟩

lemma trans_eq (p q : P G) : trans p q = p * inr p.right⁻¹ * q :=
semidirect_product.ext _ _ (by simp [trans, right_hom]) (by simp [trans, right_hom])

@[simp] lemma trans_right (p q : P G) : (p.trans q).right = q.right :=
by simp [right_hom, trans]

@[simp] lemma lhs_trans (p q : P G) : lhs r (p.trans q) = lhs r p * p.right⁻¹ * lhs r q :=
by simp [lhs, right_hom, trans_eq]

section map

/-- This is mainly used with `f` being `remove_subscript` which is not injective,
  nor is it injective on the words on which it is used. -/
def map (f : G →* H) (hf : injective f) : P G →* P H :=
semidirect_product.map (free_group.embedding ⟨f, hf⟩) f
  (λ g, free_group.hom_ext (by simp))

@[simp] lemma map_id : map (monoid_hom.id G) injective_id = monoid_hom.id (P G) :=
semidirect_product.hom_ext (free_group.hom_ext (by simp [map]))
  (monoid_hom.ext $ by simp [map])

/-- `change_r w` take a certificate of a congruence modulo `w * r * w⁻¹`
  and returns a certificate of a congruence modulo `r` -/
def change_r (w : G) : P G →* P G :=
semidirect_product.map (free_group.equiv (equiv.mul_right w)).to_monoid_hom
  (monoid_hom.id _)
  (λ g, free_group.hom_ext $ by simp [mul_assoc])

@[simp] lemma lhs_comp_change_r (r w : G) :
  (lhs r).comp (change_r w) = lhs (w * r * w⁻¹) :=
semidirect_product.hom_ext (free_group.hom_ext
  (by simp [change_r, mul_assoc, lhs_inl]))
  (by simp [monoid_hom.comp_assoc, change_r])

@[simp] lemma right_hom_comp_change_r (w : G) (x : P G) :
  right_hom.comp (change_r w) = right_hom := rfl

end map

end P

/-- `solver r T` is defined to be the type `free_group ι → option (P (free_group ι))`.
By convention, a `solver r T` will solve the word problem with respect to `r` and `T` -/
@[reducible] def solver {ι : Type} [decidable_eq ι] (r : free_group ι) (T : set ι) : Type :=
free_group ι → option (P (free_group ι))