/-
Copyright (c) 2019 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin, Bhavik Mehta
-/
import category_theory.structured_arrow
import category_theory.punit
import category_theory.reflects_isomorphisms
import category_theory.epi_mono

/-!
# Over and under categories

Over (and under) categories are special cases of comma categories.
* If `L` is the identity functor and `R` is a constant functor, then `comma L R` is the "slice" or
  "over" category over the object `R` maps to.
* Conversely, if `L` is a constant functor and `R` is the identity functor, then `comma L R` is the
  "coslice" or "under" category under the object `L` maps to.

## Tags

comma, slice, coslice, over, under
-/

namespace category_theory

universes v₁ v₂ u₁ u₂ -- morphism levels before object levels. See note [category_theory universes].
variables {T : Type u₁} [category.{v₁} T]

/--
The over category has as objects arrows in `T` with codomain `X` and as morphisms commutative
triangles.

See https://stacks.math.columbia.edu/tag/001G.
-/
@[derive category]
def over (X : T) := costructured_arrow (𝟭 T) X

-- Satisfying the inhabited linter
instance over.inhabited [inhabited T] : inhabited (over (default : T)) :=
{ default :=
  { left := default,
    hom := 𝟙 _ } }

namespace over

variables {X : T}

@[ext] lemma over_morphism.ext {X : T} {U V : over X} {f g : U ⟶ V}
  (h : f.left = g.left) : f = g :=
by tidy

@[simp] lemma over_right (U : over X) : U.right = punit.star := by tidy

@[simp] lemma id_left (U : over X) : comma_morphism.left (𝟙 U) = 𝟙 U.left := rfl
@[simp] lemma comp_left (a b c : over X) (f : a ⟶ b) (g : b ⟶ c) :
  (f ≫ g).left = f.left ≫ g.left := rfl

@[simp, reassoc] lemma w {A B : over X} (f : A ⟶ B) : f.left ≫ B.hom = A.hom :=
by have := f.w; tidy

/-- To give an object in the over category, it suffices to give a morphism with codomain `X`. -/
@[simps]
def mk {X Y : T} (f : Y ⟶ X) : over X :=
costructured_arrow.mk f

/-- We can set up a coercion from arrows with codomain `X` to `over X`. This most likely should not
    be a global instance, but it is sometimes useful. -/
def coe_from_hom {X Y : T} : has_coe (Y ⟶ X) (over X) :=
{ coe := mk }

section
local attribute [instance] coe_from_hom

@[simp] lemma coe_hom {X Y : T} (f : Y ⟶ X) : (f : over X).hom = f := rfl
end

/-- To give a morphism in the over category, it suffices to give an arrow fitting in a commutative
    triangle. -/
@[simps]
def hom_mk {U V : over X} (f : U.left ⟶ V.left) (w : f ≫ V.hom = U.hom . obviously) :
  U ⟶ V :=
costructured_arrow.hom_mk f w

/--
Construct an isomorphism in the over category given isomorphisms of the objects whose forward
direction gives a commutative triangle.
-/
@[simps]
def iso_mk {f g : over X} (hl : f.left ≅ g.left) (hw : hl.hom ≫ g.hom = f.hom . obviously) :
  f ≅ g :=
costructured_arrow.iso_mk hl hw

section
variable (X)
/--
The forgetful functor mapping an arrow to its domain.

See https://stacks.math.columbia.edu/tag/001G.
-/
def forget : over X ⥤ T := comma.fst _ _

end

@[simp] lemma forget_obj {U : over X} : (forget X).obj U = U.left := rfl
@[simp] lemma forget_map {U V : over X} {f : U ⟶ V} : (forget X).map f = f.left := rfl

/-- The natural cocone over the forgetful functor `over X ⥤ T` with cocone point `X`. -/
@[simps] def forget_cocone (X : T) : limits.cocone (forget X) :=
{ X := X, ι := { app := comma.hom } }

/--
A morphism `f : X ⟶ Y` induces a functor `over X ⥤ over Y` in the obvious way.

See https://stacks.math.columbia.edu/tag/001G.
-/
def map {Y : T} (f : X ⟶ Y) : over X ⥤ over Y := comma.map_right _ $ discrete.nat_trans (λ _, f)

section
variables {Y : T} {f : X ⟶ Y} {U V : over X} {g : U ⟶ V}
@[simp] lemma map_obj_left : ((map f).obj U).left = U.left := rfl
@[simp] lemma map_obj_hom  : ((map f).obj U).hom  = U.hom ≫ f := rfl
@[simp] lemma map_map_left : ((map f).map g).left = g.left := rfl

/-- Mapping by the identity morphism is just the identity functor. -/
def map_id : map (𝟙 Y) ≅ 𝟭 _ :=
nat_iso.of_components (λ X, iso_mk (iso.refl _) (by tidy)) (by tidy)

/-- Mapping by the composite morphism `f ≫ g` is the same as mapping by `f` then by `g`. -/
def map_comp {Y Z : T} (f : X ⟶ Y) (g : Y ⟶ Z) : map (f ≫ g) ≅ map f ⋙ map g :=
nat_iso.of_components (λ X, iso_mk (iso.refl _) (by tidy)) (by tidy)

end

instance forget_reflects_iso : reflects_isomorphisms (forget X) :=
{ reflects := λ Y Z f t, by exactI
  ⟨⟨over.hom_mk (inv ((forget X).map f))
      ((as_iso ((forget X).map f)).inv_comp_eq.2 (over.w f).symm),
    by tidy⟩⟩ }

instance forget_faithful : faithful (forget X) := {}.

/--
If `k.left` is an epimorphism, then `k` is an epimorphism. In other words, `over.forget X` reflects
epimorphisms.
The converse does not hold without additional assumptions on the underlying category.
-/
-- TODO: Show the converse holds if `T` has binary products or pushouts.
lemma epi_of_epi_left {f g : over X} (k : f ⟶ g) [hk : epi k.left] : epi k :=
faithful_reflects_epi (forget X) hk

/--
If `k.left` is a monomorphism, then `k` is a monomorphism. In other words, `over.forget X` reflects
monomorphisms.
The converse of `category_theory.over.mono_left_of_mono`.

This lemma is not an instance, to avoid loops in type class inference.
-/
lemma mono_of_mono_left {f g : over X} (k : f ⟶ g) [hk : mono k.left] : mono k :=
faithful_reflects_mono (forget X) hk

/--
If `k` is a monomorphism, then `k.left` is a monomorphism. In other words, `over.forget X` preserves
monomorphisms.
The converse of `category_theory.over.mono_of_mono_left`.
-/
instance mono_left_of_mono {f g : over X} (k : f ⟶ g) [mono k] : mono k.left :=
begin
  refine ⟨λ (Y : T) l m a, _⟩,
  let l' : mk (m ≫ f.hom) ⟶ f := hom_mk l (by { dsimp, rw [←over.w k, reassoc_of a] }),
  suffices : l' = hom_mk m,
  { apply congr_arg comma_morphism.left this },
  rw ← cancel_mono k,
  ext,
  apply a,
end

section iterated_slice
variables (f : over X)

/-- Given f : Y ⟶ X, this is the obvious functor from (T/X)/f to T/Y -/
@[simps]
def iterated_slice_forward : over f ⥤ over f.left :=
{ obj := λ α, over.mk α.hom.left,
  map := λ α β κ, over.hom_mk κ.left.left (by { rw auto_param_eq, rw ← over.w κ, refl }) }

/-- Given f : Y ⟶ X, this is the obvious functor from T/Y to (T/X)/f -/
@[simps]
def iterated_slice_backward : over f.left ⥤ over f :=
{ obj := λ g, mk (hom_mk g.hom : mk (g.hom ≫ f.hom) ⟶ f),
  map := λ g h α, hom_mk (hom_mk α.left (w_assoc α f.hom)) (over_morphism.ext (w α)) }

/-- Given f : Y ⟶ X, we have an equivalence between (T/X)/f and T/Y -/
@[simps]
def iterated_slice_equiv : over f ≌ over f.left :=
{ functor := iterated_slice_forward f,
  inverse := iterated_slice_backward f,
  unit_iso :=
    nat_iso.of_components
    (λ g, over.iso_mk (over.iso_mk (iso.refl _) (by tidy)) (by tidy))
    (λ X Y g, by { ext, dsimp, simp }),
  counit_iso :=
    nat_iso.of_components
    (λ g, over.iso_mk (iso.refl _) (by tidy))
    (λ X Y g, by { ext, dsimp, simp }) }

lemma iterated_slice_forward_forget :
  iterated_slice_forward f ⋙ forget f.left = forget f ⋙ forget X :=
rfl

lemma iterated_slice_backward_forget_forget :
  iterated_slice_backward f ⋙ forget f ⋙ forget X = forget f.left :=
rfl

end iterated_slice

section
variables {D : Type u₂} [category.{v₂} D]

/-- A functor `F : T ⥤ D` induces a functor `over X ⥤ over (F.obj X)` in the obvious way. -/
@[simps]
def post (F : T ⥤ D) : over X ⥤ over (F.obj X) :=
{ obj := λ Y, mk $ F.map Y.hom,
  map := λ Y₁ Y₂ f,
  { left := F.map f.left,
    w' := by tidy; erw [← F.map_comp, w] } }

end

end over

/-- The under category has as objects arrows with domain `X` and as morphisms commutative
    triangles. -/
@[derive category]
def under (X : T) := structured_arrow X (𝟭 T)

-- Satisfying the inhabited linter
instance under.inhabited [inhabited T] : inhabited (under (default : T)) :=
{ default :=
  { right := default,
    hom := 𝟙 _ } }

namespace under

variables {X : T}

@[ext] lemma under_morphism.ext {X : T} {U V : under X} {f g : U ⟶ V}
  (h : f.right = g.right) : f = g :=
by tidy

@[simp] lemma under_left (U : under X) : U.left = punit.star := by tidy

@[simp] lemma id_right (U : under X) : comma_morphism.right (𝟙 U) = 𝟙 U.right := rfl
@[simp] lemma comp_right (a b c : under X) (f : a ⟶ b) (g : b ⟶ c) :
  (f ≫ g).right = f.right ≫ g.right := rfl

@[simp, reassoc] lemma w {A B : under X} (f : A ⟶ B) : A.hom ≫ f.right = B.hom :=
by have := f.w; tidy

/-- To give an object in the under category, it suffices to give an arrow with domain `X`. -/
@[simps]
def mk {X Y : T} (f : X ⟶ Y) : under X :=
structured_arrow.mk f

/-- To give a morphism in the under category, it suffices to give a morphism fitting in a
    commutative triangle. -/
@[simps]
def hom_mk {U V : under X} (f : U.right ⟶ V.right) (w : U.hom ≫ f = V.hom . obviously) :
  U ⟶ V :=
structured_arrow.hom_mk f w

/--
Construct an isomorphism in the over category given isomorphisms of the objects whose forward
direction gives a commutative triangle.
-/
def iso_mk {f g : under X} (hr : f.right ≅ g.right) (hw : f.hom ≫ hr.hom = g.hom) : f ≅ g :=
structured_arrow.iso_mk hr hw

@[simp]
lemma iso_mk_hom_right {f g : under X} (hr : f.right ≅ g.right) (hw : f.hom ≫ hr.hom = g.hom) :
  (iso_mk hr hw).hom.right = hr.hom := rfl

@[simp]
lemma iso_mk_inv_right {f g : under X} (hr : f.right ≅ g.right) (hw : f.hom ≫ hr.hom = g.hom) :
  (iso_mk hr hw).inv.right = hr.inv := rfl

section
variables (X)
/-- The forgetful functor mapping an arrow to its domain. -/
def forget : under X ⥤ T := comma.snd _ _

end

@[simp] lemma forget_obj {U : under X} : (forget X).obj U = U.right := rfl
@[simp] lemma forget_map {U V : under X} {f : U ⟶ V} : (forget X).map f = f.right := rfl

/-- The natural cone over the forgetful functor `under X ⥤ T` with cone point `X`. -/
@[simps] def forget_cone (X : T) : limits.cone (forget X) :=
{ X := X, π := { app := comma.hom } }

/-- A morphism `X ⟶ Y` induces a functor `under Y ⥤ under X` in the obvious way. -/
def map {Y : T} (f : X ⟶ Y) : under Y ⥤ under X := comma.map_left _ $ discrete.nat_trans (λ _, f)

section
variables {Y : T} {f : X ⟶ Y} {U V : under Y} {g : U ⟶ V}
@[simp] lemma map_obj_right : ((map f).obj U).right = U.right := rfl
@[simp] lemma map_obj_hom   : ((map f).obj U).hom   = f ≫ U.hom := rfl
@[simp] lemma map_map_right : ((map f).map g).right = g.right := rfl

/-- Mapping by the identity morphism is just the identity functor. -/
def map_id : map (𝟙 Y) ≅ 𝟭 _ :=
nat_iso.of_components (λ X, iso_mk (iso.refl _) (by tidy)) (by tidy)

/-- Mapping by the composite morphism `f ≫ g` is the same as mapping by `f` then by `g`. -/
def map_comp {Y Z : T} (f : X ⟶ Y) (g : Y ⟶ Z) : map (f ≫ g) ≅ map g ⋙ map f :=
nat_iso.of_components (λ X, iso_mk (iso.refl _) (by tidy)) (by tidy)

end

instance forget_reflects_iso : reflects_isomorphisms (forget X) :=
{ reflects := λ Y Z f t, by exactI
  ⟨⟨under.hom_mk (inv ((under.forget X).map f)) ((is_iso.comp_inv_eq _).2 (under.w f).symm),
    by tidy⟩⟩ }

instance forget_faithful : faithful (forget X) := {}.

section
variables {D : Type u₂} [category.{v₂} D]

/-- A functor `F : T ⥤ D` induces a functor `under X ⥤ under (F.obj X)` in the obvious way. -/
@[simps]
def post {X : T} (F : T ⥤ D) : under X ⥤ under (F.obj X) :=
{ obj := λ Y, mk $ F.map Y.hom,
  map := λ Y₁ Y₂ f,
  { right := F.map f.right,
    w' := by tidy; erw [← F.map_comp, w] } }

end

end under

end category_theory


import GMLInit.Data.Index.Basic

open Sum (inl inr)

theorem List.reverseAux_step (z : α) (xs ys : List α) : List.reverseAux (z :: xs) ys = List.reverseAux xs (z :: ys) := rfl

namespace Index
variable {α} {xs ys : List α}

def reverseAux : {xs ys : List α} → Sum (Index xs) (Index ys) → Index (List.reverseAux xs ys)
| [], _, .inr j => j
| x :: xs, ys, .inl .head => (List.reverseAux_step x xs ys).symm ▸ reverseAux (.inr .head)
| x :: xs, ys, .inl (.tail i) => (List.reverseAux_step x xs ys).symm ▸ reverseAux (.inl i)
| x :: xs, ys, .inr j => (List.reverseAux_step x xs ys).symm ▸ reverseAux (.inr (.tail j))

def reverseTR {xs : List α} (i : Index xs) : Index xs.reverse := reverseAux (.inl i)

def appendTR {xs ys : List α} : Sum (Index xs) (Index ys) → Index (List.append xs ys)
| .inl i => List.append_eq_appendTR ▸ reverseAux (.inl i.reverseTR)
| .inr j => List.append_eq_appendTR ▸ reverseAux (.inr j)

end Index


import rescale.pseudo_normed_group

noncomputable theory

open_locale nnreal big_operators
local attribute [instance] type_pow

section

variables {r' : ℝ≥0} (M : Type*) (N : ℕ)

namespace profinitely_filtered_pseudo_normed_group

variables [profinitely_filtered_pseudo_normed_group M]

def unrescale (N : ℝ≥0) (M : Type*) [profinitely_filtered_pseudo_normed_group M] :
  comphaus_filtered_pseudo_normed_group_hom (rescale N M) M :=
comphaus_filtered_pseudo_normed_group_hom.mk_of_bound (add_monoid_hom.id _) N⁻¹
begin
  intro c,
  refine ⟨λ x hx, _, _⟩,
  { rwa mul_comm },
  { haveI : fact (c * N⁻¹ ≤ N⁻¹ * c) := ⟨(mul_comm _ _).le⟩,
    exact comphaus_filtered_pseudo_normed_group.continuous_cast_le (c * N⁻¹) (N⁻¹ * c) },
end

def rescale_proj (i : fin N) :
  comphaus_filtered_pseudo_normed_group_hom (rescale N (M ^ N)) M :=
(comphaus_filtered_pseudo_normed_group.pi_proj i).comp (unrescale N _)

lemma rescale_proj_bound_by (i : fin N) : (rescale_proj M N i).bound_by N⁻¹ :=
by { intros c x hx, rw [rescale.mem_filtration, mul_comm] at hx, exact hx i }

def sum_hom (N : ℕ) :
  comphaus_filtered_pseudo_normed_group_hom (rescale N (M ^ N)) M :=
∑ i, rescale_proj M N i

lemma sum_hom_apply (x) : sum_hom M N x = ∑ i, x i :=
comphaus_filtered_pseudo_normed_group_hom.sum_apply _ _ _

lemma sum_hom_strict [fact (0 < N)] : (sum_hom M N).strict :=
begin
  rw comphaus_filtered_pseudo_normed_group_hom.strict_iff_bound_by_one,
  have := comphaus_filtered_pseudo_normed_group_hom.sum_bound_by finset.univ
    (rescale_proj M N) (λ i, N⁻¹) (λ i _, rescale_proj_bound_by M N i),
  dsimp at this,
  simp only [finset.sum_const, finset.card_univ, fintype.card_fin, nsmul_eq_mul] at this,
  rwa [mul_inv_cancel] at this,
  apply ne_of_gt,
  norm_cast,
  exact fact.out _
end

end profinitely_filtered_pseudo_normed_group

namespace profinitely_filtered_pseudo_normed_group_with_Tinv

open profinitely_filtered_pseudo_normed_group

variables [profinitely_filtered_pseudo_normed_group_with_Tinv r' M] [fact (0 < r')] [fact (0 < N)]

def sum_hom :
  comphaus_filtered_pseudo_normed_group_with_Tinv_hom r' (rescale N (M ^ N)) M :=
comphaus_filtered_pseudo_normed_group_with_Tinv_hom.mk'
  (sum_hom M N)
  (sum_hom_strict M N).bound_by_one
  (λ x, by { simp only [sum_hom, comphaus_filtered_pseudo_normed_group_hom.sum_apply,
    comphaus_filtered_pseudo_normed_group_hom.map_sum], refl })

include r'

lemma sum_hom_apply (x) : sum_hom M N x = ∑ i, x i :=
sum_hom_apply _ _ _

end profinitely_filtered_pseudo_normed_group_with_Tinv

end


/-
Copyright (c) 2020 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.analysis.calculus.mean_value
import Mathlib.PostPort

namespace Mathlib

/-!
# L'Hôpital's rule for 0/0 indeterminate forms

In this file, we prove several forms of "L'Hopital's rule" for computing 0/0
indeterminate forms. The proof of `has_deriv_at.lhopital_zero_right_on_Ioo`
is based on the one given in the corresponding
[Wikibooks](https://en.wikibooks.org/wiki/Calculus/L%27H%C3%B4pital%27s_Rule)
chapter, and all other statements are derived from this one by composing by
carefully chosen functions.

Note that the filter `f'/g'` tends to isn't required to be one of `𝓝 a`,
`at_top` or `at_bot`. In fact, we give a slightly stronger statement by
allowing it to be any filter on `ℝ`.

Each statement is available in a `has_deriv_at` form and a `deriv` form, which
is denoted by each statement being in either the `has_deriv_at` or the `deriv`
namespace.
-/

/-!
## Interval-based versions

We start by proving statements where all conditions (derivability, `g' ≠ 0`) have
to be satisfied on an explicitely-provided interval.
-/

namespace has_deriv_at

theorem lhopital_zero_right_on_Ioo {a : ℝ} {b : ℝ} (hab : a < b) {l : filter ℝ} {f : ℝ → ℝ}
    {f' : ℝ → ℝ} {g : ℝ → ℝ} {g' : ℝ → ℝ}
    (hff' : ∀ (x : ℝ), x ∈ set.Ioo a b → has_deriv_at f (f' x) x)
    (hgg' : ∀ (x : ℝ), x ∈ set.Ioo a b → has_deriv_at g (g' x) x)
    (hg' : ∀ (x : ℝ), x ∈ set.Ioo a b → g' x ≠ 0)
    (hfa : filter.tendsto f (nhds_within a (set.Ioi a)) (nhds 0))
    (hga : filter.tendsto g (nhds_within a (set.Ioi a)) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) (nhds_within a (set.Ioi a)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.Ioi a)) l :=
  sorry

theorem lhopital_zero_right_on_Ico {a : ℝ} {b : ℝ} (hab : a < b) {l : filter ℝ} {f : ℝ → ℝ}
    {f' : ℝ → ℝ} {g : ℝ → ℝ} {g' : ℝ → ℝ}
    (hff' : ∀ (x : ℝ), x ∈ set.Ioo a b → has_deriv_at f (f' x) x)
    (hgg' : ∀ (x : ℝ), x ∈ set.Ioo a b → has_deriv_at g (g' x) x)
    (hcf : continuous_on f (set.Ico a b)) (hcg : continuous_on g (set.Ico a b))
    (hg' : ∀ (x : ℝ), x ∈ set.Ioo a b → g' x ≠ 0) (hfa : f a = 0) (hga : g a = 0)
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) (nhds_within a (set.Ioi a)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.Ioi a)) l :=
  sorry

theorem lhopital_zero_left_on_Ioo {a : ℝ} {b : ℝ} (hab : a < b) {l : filter ℝ} {f : ℝ → ℝ}
    {f' : ℝ → ℝ} {g : ℝ → ℝ} {g' : ℝ → ℝ}
    (hff' : ∀ (x : ℝ), x ∈ set.Ioo a b → has_deriv_at f (f' x) x)
    (hgg' : ∀ (x : ℝ), x ∈ set.Ioo a b → has_deriv_at g (g' x) x)
    (hg' : ∀ (x : ℝ), x ∈ set.Ioo a b → g' x ≠ 0)
    (hfb : filter.tendsto f (nhds_within b (set.Iio b)) (nhds 0))
    (hgb : filter.tendsto g (nhds_within b (set.Iio b)) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) (nhds_within b (set.Iio b)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within b (set.Iio b)) l :=
  sorry

theorem lhopital_zero_left_on_Ioc {a : ℝ} {b : ℝ} (hab : a < b) {l : filter ℝ} {f : ℝ → ℝ}
    {f' : ℝ → ℝ} {g : ℝ → ℝ} {g' : ℝ → ℝ}
    (hff' : ∀ (x : ℝ), x ∈ set.Ioo a b → has_deriv_at f (f' x) x)
    (hgg' : ∀ (x : ℝ), x ∈ set.Ioo a b → has_deriv_at g (g' x) x)
    (hcf : continuous_on f (set.Ioc a b)) (hcg : continuous_on g (set.Ioc a b))
    (hg' : ∀ (x : ℝ), x ∈ set.Ioo a b → g' x ≠ 0) (hfb : f b = 0) (hgb : g b = 0)
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) (nhds_within b (set.Iio b)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within b (set.Iio b)) l :=
  sorry

theorem lhopital_zero_at_top_on_Ioi {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {f' : ℝ → ℝ} {g : ℝ → ℝ}
    {g' : ℝ → ℝ} (hff' : ∀ (x : ℝ), x ∈ set.Ioi a → has_deriv_at f (f' x) x)
    (hgg' : ∀ (x : ℝ), x ∈ set.Ioi a → has_deriv_at g (g' x) x)
    (hg' : ∀ (x : ℝ), x ∈ set.Ioi a → g' x ≠ 0) (hftop : filter.tendsto f filter.at_top (nhds 0))
    (hgtop : filter.tendsto g filter.at_top (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) filter.at_top l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) filter.at_top l :=
  sorry

theorem lhopital_zero_at_bot_on_Iio {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {f' : ℝ → ℝ} {g : ℝ → ℝ}
    {g' : ℝ → ℝ} (hff' : ∀ (x : ℝ), x ∈ set.Iio a → has_deriv_at f (f' x) x)
    (hgg' : ∀ (x : ℝ), x ∈ set.Iio a → has_deriv_at g (g' x) x)
    (hg' : ∀ (x : ℝ), x ∈ set.Iio a → g' x ≠ 0) (hfbot : filter.tendsto f filter.at_bot (nhds 0))
    (hgbot : filter.tendsto g filter.at_bot (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) filter.at_bot l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) filter.at_bot l :=
  sorry

end has_deriv_at

namespace deriv

theorem lhopital_zero_right_on_Ioo {a : ℝ} {b : ℝ} (hab : a < b) {l : filter ℝ} {f : ℝ → ℝ}
    {g : ℝ → ℝ} (hdf : differentiable_on ℝ f (set.Ioo a b))
    (hg' : ∀ (x : ℝ), x ∈ set.Ioo a b → deriv g x ≠ 0)
    (hfa : filter.tendsto f (nhds_within a (set.Ioi a)) (nhds 0))
    (hga : filter.tendsto g (nhds_within a (set.Ioi a)) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) (nhds_within a (set.Ioi a)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.Ioi a)) l :=
  sorry

theorem lhopital_zero_right_on_Ico {a : ℝ} {b : ℝ} (hab : a < b) {l : filter ℝ} {f : ℝ → ℝ}
    {g : ℝ → ℝ} (hdf : differentiable_on ℝ f (set.Ioo a b)) (hcf : continuous_on f (set.Ico a b))
    (hcg : continuous_on g (set.Ico a b)) (hg' : ∀ (x : ℝ), x ∈ set.Ioo a b → deriv g x ≠ 0)
    (hfa : f a = 0) (hga : g a = 0)
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) (nhds_within a (set.Ioi a)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.Ioi a)) l :=
  sorry

theorem lhopital_zero_left_on_Ioo {a : ℝ} {b : ℝ} (hab : a < b) {l : filter ℝ} {f : ℝ → ℝ}
    {g : ℝ → ℝ} (hdf : differentiable_on ℝ f (set.Ioo a b))
    (hg' : ∀ (x : ℝ), x ∈ set.Ioo a b → deriv g x ≠ 0)
    (hfb : filter.tendsto f (nhds_within b (set.Iio b)) (nhds 0))
    (hgb : filter.tendsto g (nhds_within b (set.Iio b)) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) (nhds_within b (set.Iio b)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within b (set.Iio b)) l :=
  sorry

theorem lhopital_zero_at_top_on_Ioi {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {g : ℝ → ℝ}
    (hdf : differentiable_on ℝ f (set.Ioi a)) (hg' : ∀ (x : ℝ), x ∈ set.Ioi a → deriv g x ≠ 0)
    (hftop : filter.tendsto f filter.at_top (nhds 0))
    (hgtop : filter.tendsto g filter.at_top (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) filter.at_top l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) filter.at_top l :=
  sorry

theorem lhopital_zero_at_bot_on_Iio {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {g : ℝ → ℝ}
    (hdf : differentiable_on ℝ f (set.Iio a)) (hg' : ∀ (x : ℝ), x ∈ set.Iio a → deriv g x ≠ 0)
    (hfbot : filter.tendsto f filter.at_bot (nhds 0))
    (hgbot : filter.tendsto g filter.at_bot (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) filter.at_bot l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) filter.at_bot l :=
  sorry

end deriv

/-!
## Generic versions

The following statements no longer any explicit interval, as they only require
conditions holding eventually.
-/

namespace has_deriv_at

/-- L'Hôpital's rule for approaching a real from the right, `has_deriv_at` version -/
theorem lhopital_zero_nhds_right {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {f' : ℝ → ℝ} {g : ℝ → ℝ}
    {g' : ℝ → ℝ}
    (hff' : filter.eventually (fun (x : ℝ) => has_deriv_at f (f' x) x) (nhds_within a (set.Ioi a)))
    (hgg' : filter.eventually (fun (x : ℝ) => has_deriv_at g (g' x) x) (nhds_within a (set.Ioi a)))
    (hg' : filter.eventually (fun (x : ℝ) => g' x ≠ 0) (nhds_within a (set.Ioi a)))
    (hfa : filter.tendsto f (nhds_within a (set.Ioi a)) (nhds 0))
    (hga : filter.tendsto g (nhds_within a (set.Ioi a)) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) (nhds_within a (set.Ioi a)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.Ioi a)) l :=
  sorry

/-- L'Hôpital's rule for approaching a real from the left, `has_deriv_at` version -/
theorem lhopital_zero_nhds_left {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {f' : ℝ → ℝ} {g : ℝ → ℝ}
    {g' : ℝ → ℝ}
    (hff' : filter.eventually (fun (x : ℝ) => has_deriv_at f (f' x) x) (nhds_within a (set.Iio a)))
    (hgg' : filter.eventually (fun (x : ℝ) => has_deriv_at g (g' x) x) (nhds_within a (set.Iio a)))
    (hg' : filter.eventually (fun (x : ℝ) => g' x ≠ 0) (nhds_within a (set.Iio a)))
    (hfa : filter.tendsto f (nhds_within a (set.Iio a)) (nhds 0))
    (hga : filter.tendsto g (nhds_within a (set.Iio a)) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) (nhds_within a (set.Iio a)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.Iio a)) l :=
  sorry

/-- L'Hôpital's rule for approaching a real, `has_deriv_at` version. This
  does not require anything about the situation at `a` -/
theorem lhopital_zero_nhds' {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {f' : ℝ → ℝ} {g : ℝ → ℝ} {g' : ℝ → ℝ}
    (hff' :
      filter.eventually (fun (x : ℝ) => has_deriv_at f (f' x) x)
        (nhds_within a (set.univ \ singleton a)))
    (hgg' :
      filter.eventually (fun (x : ℝ) => has_deriv_at g (g' x) x)
        (nhds_within a (set.univ \ singleton a)))
    (hg' : filter.eventually (fun (x : ℝ) => g' x ≠ 0) (nhds_within a (set.univ \ singleton a)))
    (hfa : filter.tendsto f (nhds_within a (set.univ \ singleton a)) (nhds 0))
    (hga : filter.tendsto g (nhds_within a (set.univ \ singleton a)) (nhds 0))
    (hdiv :
      filter.tendsto (fun (x : ℝ) => f' x / g' x) (nhds_within a (set.univ \ singleton a)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.univ \ singleton a)) l :=
  sorry

/-- L'Hôpital's rule for approaching a real, `has_deriv_at` version -/
theorem lhopital_zero_nhds {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {f' : ℝ → ℝ} {g : ℝ → ℝ} {g' : ℝ → ℝ}
    (hff' : filter.eventually (fun (x : ℝ) => has_deriv_at f (f' x) x) (nhds a))
    (hgg' : filter.eventually (fun (x : ℝ) => has_deriv_at g (g' x) x) (nhds a))
    (hg' : filter.eventually (fun (x : ℝ) => g' x ≠ 0) (nhds a))
    (hfa : filter.tendsto f (nhds a) (nhds 0)) (hga : filter.tendsto g (nhds a) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) (nhds a) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.univ \ singleton a)) l :=
  lhopital_zero_nhds' (eventually_nhds_within_of_eventually_nhds hff')
    (eventually_nhds_within_of_eventually_nhds hgg') (eventually_nhds_within_of_eventually_nhds hg')
    (tendsto_nhds_within_of_tendsto_nhds hfa) (tendsto_nhds_within_of_tendsto_nhds hga)
    (tendsto_nhds_within_of_tendsto_nhds hdiv)

/-- L'Hôpital's rule for approaching +∞, `has_deriv_at` version -/
theorem lhopital_zero_at_top {l : filter ℝ} {f : ℝ → ℝ} {f' : ℝ → ℝ} {g : ℝ → ℝ} {g' : ℝ → ℝ}
    (hff' : filter.eventually (fun (x : ℝ) => has_deriv_at f (f' x) x) filter.at_top)
    (hgg' : filter.eventually (fun (x : ℝ) => has_deriv_at g (g' x) x) filter.at_top)
    (hg' : filter.eventually (fun (x : ℝ) => g' x ≠ 0) filter.at_top)
    (hftop : filter.tendsto f filter.at_top (nhds 0))
    (hgtop : filter.tendsto g filter.at_top (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) filter.at_top l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) filter.at_top l :=
  sorry

/-- L'Hôpital's rule for approaching -∞, `has_deriv_at` version -/
theorem lhopital_zero_at_bot {l : filter ℝ} {f : ℝ → ℝ} {f' : ℝ → ℝ} {g : ℝ → ℝ} {g' : ℝ → ℝ}
    (hff' : filter.eventually (fun (x : ℝ) => has_deriv_at f (f' x) x) filter.at_bot)
    (hgg' : filter.eventually (fun (x : ℝ) => has_deriv_at g (g' x) x) filter.at_bot)
    (hg' : filter.eventually (fun (x : ℝ) => g' x ≠ 0) filter.at_bot)
    (hfbot : filter.tendsto f filter.at_bot (nhds 0))
    (hgbot : filter.tendsto g filter.at_bot (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => f' x / g' x) filter.at_bot l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) filter.at_bot l :=
  sorry

end has_deriv_at

namespace deriv

/-- L'Hôpital's rule for approaching a real from the right, `deriv` version -/
theorem lhopital_zero_nhds_right {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {g : ℝ → ℝ}
    (hdf : filter.eventually (fun (x : ℝ) => differentiable_at ℝ f x) (nhds_within a (set.Ioi a)))
    (hg' : filter.eventually (fun (x : ℝ) => deriv g x ≠ 0) (nhds_within a (set.Ioi a)))
    (hfa : filter.tendsto f (nhds_within a (set.Ioi a)) (nhds 0))
    (hga : filter.tendsto g (nhds_within a (set.Ioi a)) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) (nhds_within a (set.Ioi a)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.Ioi a)) l :=
  sorry

/-- L'Hôpital's rule for approaching a real from the left, `deriv` version -/
theorem lhopital_zero_nhds_left {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {g : ℝ → ℝ}
    (hdf : filter.eventually (fun (x : ℝ) => differentiable_at ℝ f x) (nhds_within a (set.Iio a)))
    (hg' : filter.eventually (fun (x : ℝ) => deriv g x ≠ 0) (nhds_within a (set.Iio a)))
    (hfa : filter.tendsto f (nhds_within a (set.Iio a)) (nhds 0))
    (hga : filter.tendsto g (nhds_within a (set.Iio a)) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) (nhds_within a (set.Iio a)) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.Iio a)) l :=
  sorry

/-- L'Hôpital's rule for approaching a real, `deriv` version. This
  does not require anything about the situation at `a` -/
theorem lhopital_zero_nhds' {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {g : ℝ → ℝ}
    (hdf :
      filter.eventually (fun (x : ℝ) => differentiable_at ℝ f x)
        (nhds_within a (set.univ \ singleton a)))
    (hg' :
      filter.eventually (fun (x : ℝ) => deriv g x ≠ 0) (nhds_within a (set.univ \ singleton a)))
    (hfa : filter.tendsto f (nhds_within a (set.univ \ singleton a)) (nhds 0))
    (hga : filter.tendsto g (nhds_within a (set.univ \ singleton a)) (nhds 0))
    (hdiv :
      filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) (nhds_within a (set.univ \ singleton a))
        l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.univ \ singleton a)) l :=
  sorry

/-- L'Hôpital's rule for approaching a real, `deriv` version -/
theorem lhopital_zero_nhds {a : ℝ} {l : filter ℝ} {f : ℝ → ℝ} {g : ℝ → ℝ}
    (hdf : filter.eventually (fun (x : ℝ) => differentiable_at ℝ f x) (nhds a))
    (hg' : filter.eventually (fun (x : ℝ) => deriv g x ≠ 0) (nhds a))
    (hfa : filter.tendsto f (nhds a) (nhds 0)) (hga : filter.tendsto g (nhds a) (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) (nhds a) l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) (nhds_within a (set.univ \ singleton a)) l :=
  lhopital_zero_nhds' (eventually_nhds_within_of_eventually_nhds hdf)
    (eventually_nhds_within_of_eventually_nhds hg') (tendsto_nhds_within_of_tendsto_nhds hfa)
    (tendsto_nhds_within_of_tendsto_nhds hga) (tendsto_nhds_within_of_tendsto_nhds hdiv)

/-- L'Hôpital's rule for approaching +∞, `deriv` version -/
theorem lhopital_zero_at_top {l : filter ℝ} {f : ℝ → ℝ} {g : ℝ → ℝ}
    (hdf : filter.eventually (fun (x : ℝ) => differentiable_at ℝ f x) filter.at_top)
    (hg' : filter.eventually (fun (x : ℝ) => deriv g x ≠ 0) filter.at_top)
    (hftop : filter.tendsto f filter.at_top (nhds 0))
    (hgtop : filter.tendsto g filter.at_top (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) filter.at_top l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) filter.at_top l :=
  sorry

/-- L'Hôpital's rule for approaching -∞, `deriv` version -/
theorem lhopital_zero_at_bot {l : filter ℝ} {f : ℝ → ℝ} {g : ℝ → ℝ}
    (hdf : filter.eventually (fun (x : ℝ) => differentiable_at ℝ f x) filter.at_bot)
    (hg' : filter.eventually (fun (x : ℝ) => deriv g x ≠ 0) filter.at_bot)
    (hfbot : filter.tendsto f filter.at_bot (nhds 0))
    (hgbot : filter.tendsto g filter.at_bot (nhds 0))
    (hdiv : filter.tendsto (fun (x : ℝ) => deriv f x / deriv g x) filter.at_bot l) :
    filter.tendsto (fun (x : ℝ) => f x / g x) filter.at_bot l :=
  sorry

end Mathlib


/-
Copyright (c) 2020 David Wärn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: David Wärn
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.elements
import Mathlib.category_theory.single_obj
import Mathlib.group_theory.group_action.basic
import Mathlib.PostPort

universes u u_1

namespace Mathlib

/-!
# Actions as functors and as categories

From a multiplicative action M ↻ X, we can construct a functor from M to the category of
types, mapping the single object of M to X and an element `m : M` to map `X → X` given by
multiplication by `m`.
  This functor induces a category structure on X -- a special case of the category of elements.
A morphism `x ⟶ y` in this category is simply a scalar `m : M` such that `m • x = y`. In the case
where M is a group, this category is a groupoid -- the `action groupoid'.
-/

namespace category_theory

/-- A multiplicative action M ↻ X viewed as a functor mapping the single object of M to X
  and an element `m : M` to the map `X → X` given by multiplication by `m`. -/
@[simp] theorem action_as_functor_obj (M : Type u_1) [monoid M] (X : Type u) [mul_action M X]
    (_x : single_obj M) : functor.obj (action_as_functor M X) _x = X :=
  Eq.refl (functor.obj (action_as_functor M X) _x)

/-- A multiplicative action M ↻ X induces a category strucure on X, where a morphism
 from x to y is a scalar taking x to y. Due to implementation details, the object type
 of this category is not equal to X, but is in bijection with X. -/
def action_category (M : Type u_1) [monoid M] (X : Type u) [mul_action M X] :=
  functor.elements (action_as_functor M X)

namespace action_category

protected instance category_theory.groupoid (X : Type u) (G : Type u_1) [group G] [mul_action G X] :
    groupoid (action_category G X) :=
  category_theory.groupoid_of_elements (action_as_functor G X)

/-- The projection from the action category to the monoid, mapping a morphism to its
  label. -/
def π (M : Type u_1) [monoid M] (X : Type u) [mul_action M X] :
    action_category M X ⥤ single_obj M :=
  category_of_elements.π (action_as_functor M X)

@[simp] theorem π_map (M : Type u_1) [monoid M] (X : Type u) [mul_action M X]
    (p : action_category M X) (q : action_category M X) (f : p ⟶ q) :
    functor.map (π M X) f = subtype.val f :=
  rfl

@[simp] theorem π_obj (M : Type u_1) [monoid M] (X : Type u) [mul_action M X]
    (p : action_category M X) : functor.obj (π M X) p = single_obj.star M :=
  subsingleton.elim (functor.obj (π M X) p) (single_obj.star M)

/-- An object of the action category given by M ↻ X corresponds to an element of X. -/
def obj_equiv (M : Type u_1) [monoid M] (X : Type u) [mul_action M X] : X ≃ action_category M X :=
  equiv.mk (fun (x : X) => sigma.mk (single_obj.star M) x)
    (fun (p : action_category M X) => sigma.snd p) sorry sorry

theorem hom_as_subtype (M : Type u_1) [monoid M] (X : Type u) [mul_action M X]
    (p : action_category M X) (q : action_category M X) :
    (p ⟶ q) =
        Subtype
          fun (m : M) =>
            m • coe_fn (equiv.symm (obj_equiv M X)) p = coe_fn (equiv.symm (obj_equiv M X)) q :=
  rfl

protected instance inhabited (M : Type u_1) [monoid M] (X : Type u) [mul_action M X] [Inhabited X] :
    Inhabited (action_category M X) :=
  { default := coe_fn (obj_equiv M X) Inhabited.default }

/-- The stabilizer of a point is isomorphic to the endomorphism monoid at the
  corresponding point. In fact they are definitionally equivalent. -/
def stabilizer_iso_End (M : Type u_1) [monoid M] {X : Type u} [mul_action M X] (x : X) :
    ↥(mul_action.stabilizer.submonoid M x) ≃* End (coe_fn (obj_equiv M X) x) :=
  mul_equiv.refl ↥(mul_action.stabilizer.submonoid M x)

@[simp] theorem stabilizer_iso_End_apply (M : Type u_1) [monoid M] {X : Type u} [mul_action M X]
    (x : X) (f : ↥(mul_action.stabilizer.submonoid M x)) :
    mul_equiv.to_fun (stabilizer_iso_End M x) f = f :=
  rfl

@[simp] theorem stabilizer_iso_End_symm_apply (M : Type u_1) [monoid M] {X : Type u}
    [mul_action M X] (x : X) (f : End (coe_fn (obj_equiv M X) x)) :
    mul_equiv.inv_fun (stabilizer_iso_End M x) f = f :=
  rfl

end Mathlib


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Kenny Lau, Yury Kudryashov
-/
import order.preorder_hom
import dynamics.fixed_points.basic

/-!
# Fixed point construction on complete lattices

This file sets up the basic theory of fixed points of a monotone function in a complete lattice.

## Main definitions

* `preorder_hom.lfp`: The least fixed point of a bundled monotone function.
* `preorder_hom.gfp`: The greatest fixed point of a bundled monotone function.
* `preorder_hom.prev_fixed`: The greatest fixed point of a bundled monotone function smaller than or
  equal to a given element.
* `preorder_hom.next_fixed`: The least fixed point of a bundled monotone function greater than or
  equal to a given element.
* `fixed_points.complete_lattice`: The Knaster-Tarski theorem: fixed points of a monotone
  self-map of a complete lattice form themselves a complete lattice.

## Tags

fixed point, complete lattice, monotone function
-/

universes u v w
variables {α : Type u} {β : Type v} {γ : Type w}

open function (fixed_points is_fixed_pt)

namespace preorder_hom

section basic

variables [complete_lattice α] (f : α →ₘ α)

/-- Least fixed point of a monotone function -/
def lfp : (α →ₘ α) →ₘ α :=
{ to_fun := λ f, Inf {a | f a ≤ a},
  monotone' := λ f g hle, Inf_le_Inf $ λ a ha, (hle a).trans ha }

/-- Greatest fixed point of a monotone function -/
def gfp : (α →ₘ α) →ₘ α :=
{ to_fun := λ f, Sup {a | a ≤ f a},
  monotone' := λ f g hle, Sup_le_Sup $ λ a ha, le_trans ha (hle a) }

lemma lfp_le {a : α} (h : f a ≤ a) : lfp f ≤ a := Inf_le h

lemma lfp_le_fixed {a : α} (h : f a = a) : lfp f ≤ a := f.lfp_le h.le

lemma le_lfp {a : α} (h : ∀ b, f b ≤ b → a ≤ b) : a ≤ lfp f := le_Inf h

lemma map_le_lfp {a : α} (ha : a ≤ f.lfp) : f a ≤ f.lfp :=
f.le_lfp $ λ b hb, (f.mono $ le_Inf_iff.1 ha _ hb).trans hb

@[simp] lemma map_lfp : f (lfp f) = lfp f :=
have h : f (lfp f) ≤ lfp f, from f.map_le_lfp le_rfl,
h.antisymm $ f.lfp_le $ f.mono h

lemma is_fixed_pt_lfp : is_fixed_pt f f.lfp := f.map_lfp

lemma lfp_le_map {a : α} (ha : lfp f ≤ a) : lfp f ≤ f a :=
calc lfp f = f (lfp f) : f.map_lfp.symm
       ... ≤ f a       : f.mono ha

lemma is_least_lfp_le : is_least {a | f a ≤ a} (lfp f) :=
⟨f.map_lfp.le, λ a, f.lfp_le⟩

lemma is_least_lfp : is_least (fixed_points f) (lfp f) :=
⟨f.is_fixed_pt_lfp, λ a, f.lfp_le_fixed⟩

lemma lfp_induction {p : α → Prop} (step : ∀ a, p a → a ≤ lfp f → p (f a))
  (hSup : ∀ s, (∀ a ∈ s, p a) → p (Sup s)) :
  p (lfp f) :=
begin
  set s := {a | a ≤ lfp f ∧ p a},
  specialize hSup s (λ a, and.right),
  suffices : Sup s = lfp f, from this ▸ hSup,
  have h : Sup s ≤ lfp f := Sup_le (λ b, and.left),
  have hmem : f (Sup s) ∈ s, from ⟨f.map_le_lfp h, step _ hSup h⟩,
  exact h.antisymm (f.lfp_le $ le_Sup hmem)
end

lemma le_gfp {a : α} (h : a ≤ f a) : a ≤ gfp f :=
le_Sup h

lemma gfp_le {a : α} (h : ∀ b, b ≤ f b → b ≤ a) : gfp f ≤ a :=
Sup_le h

lemma is_fixed_pt_gfp : is_fixed_pt f (gfp f) := f.dual.is_fixed_pt_lfp

@[simp] lemma map_gfp : f (gfp f) = gfp f := f.dual.map_lfp

lemma map_le_gfp {a : α} (ha : a ≤ gfp f) : f a ≤ gfp f := f.dual.lfp_le_map ha

lemma gfp_le_map {a : α} (ha : gfp f ≤ a) : gfp f ≤ f a := f.dual.map_le_lfp ha

lemma is_greatest_gfp_le : is_greatest {a | a ≤ f a} (gfp f) :=
f.dual.is_least_lfp_le

lemma is_greatest_gfp : is_greatest (fixed_points f) (gfp f) :=
f.dual.is_least_lfp

lemma gfp_induction {p : α → Prop} (step : ∀ a, p a → gfp f ≤ a → p (f a))
  (hInf : ∀ s, (∀ a ∈ s, p a) → p (Inf s)) :
  p (gfp f) :=
f.dual.lfp_induction step hInf

end basic

section eqn

variables [complete_lattice α] [complete_lattice β] (f : β →ₘ α) (g : α →ₘ β)

-- Rolling rule
lemma map_lfp_comp : f (lfp (g.comp f)) = lfp (f.comp g) :=
le_antisymm ((f.comp g).map_lfp ▸ f.mono (lfp_le_fixed _ $ congr_arg g (f.comp g).map_lfp)) $
  lfp_le _ (congr_arg f (g.comp f).map_lfp).le

lemma map_gfp_comp : f ((g.comp f).gfp) = (f.comp g).gfp :=
f.dual.map_lfp_comp g.dual

-- Diagonal rule
lemma lfp_lfp (h : α →ₘ α →ₘ α) :
  lfp (lfp.comp h) = lfp h.on_diag :=
begin
  let a := lfp (lfp.comp h),
  refine (lfp_le _ _).antisymm (lfp_le _ (eq.le _)),
  { exact lfp_le _ h.on_diag.map_lfp.le },
  have ha : (lfp ∘ h) a = a := (lfp.comp h).map_lfp,
  calc h a a = h a (lfp (h a)) : congr_arg (h a) ha.symm
         ... = lfp (h a)       : (h a).map_lfp
         ... = a               : ha
end

lemma gfp_gfp (h : α →ₘ α →ₘ α) :
  gfp (gfp.comp h) = gfp h.on_diag :=
@lfp_lfp (order_dual α) _ $ (preorder_hom.dual_iso (order_dual α)
  (order_dual α)).symm.to_order_embedding.to_preorder_hom.comp h.dual

end eqn

section prev_next
variables [complete_lattice α] (f : α →ₘ α)

lemma gfp_const_inf_le (x : α) : gfp (const α x ⊓ f) ≤ x :=
gfp_le _ $ λ b hb, hb.trans inf_le_left

/-- Previous fixed point of a monotone map. If `f` is a monotone self-map of a complete lattice and
`x` is a point such that `f x ≤ x`, then `f.prev_fixed x hx` is the greatest fixed point of `f`
that is less than or equal to `x`. -/
def prev_fixed (x : α) (hx : f x ≤ x) : fixed_points f :=
⟨gfp (const α x ⊓ f),
  calc f (gfp (const α x ⊓ f)) = x ⊓ f (gfp (const α x ⊓ f)) :
    eq.symm $ inf_of_le_right $ (f.mono $ f.gfp_const_inf_le x).trans hx
  ... = gfp (const α x ⊓ f) : (const α x ⊓ f).map_gfp ⟩

/-- Next fixed point of a monotone map. If `f` is a monotone self-map of a complete lattice and
`x` is a point such that `x ≤ f x`, then `f.next_fixed x hx` is the least fixed point of `f`
that is greater than or equal to `x`. -/
def next_fixed (x : α) (hx : x ≤ f x) : fixed_points f :=
{ val := (const α x ⊔ f).lfp,
  .. f.dual.prev_fixed x hx }

lemma prev_fixed_le {x : α} (hx : f x ≤ x) : ↑(f.prev_fixed x hx) ≤ x :=
f.gfp_const_inf_le x

lemma le_next_fixed {x : α} (hx : x ≤ f x) : x ≤ f.next_fixed x hx :=
f.dual.prev_fixed_le hx

lemma next_fixed_le {x : α} (hx : x ≤ f x) {y : fixed_points f} (h : x ≤ y) :
  f.next_fixed x hx ≤ y :=
subtype.coe_le_coe.1 $ lfp_le _ $ sup_le h y.2.le

@[simp] lemma next_fixed_le_iff {x : α} (hx : x ≤ f x) {y : fixed_points f} :
  f.next_fixed x hx ≤ y ↔ x ≤ y :=
⟨λ h, (f.le_next_fixed hx).trans h, f.next_fixed_le hx⟩

@[simp]

lemma le_prev_fixed {x : α} (hx : f x ≤ x) {y : fixed_points f} (h : ↑y ≤ x) :
  y ≤ f.prev_fixed x hx :=
(f.le_prev_fixed_iff hx).2 h

lemma le_map_sup_fixed_points (x y : fixed_points f) : (x ⊔ y : α) ≤ f (x ⊔ y) :=
calc (x ⊔ y : α) = f x ⊔ f y : congr_arg2 (⊔) x.2.symm y.2.symm
             ... ≤ f (x ⊔ y) : f.mono.le_map_sup x y

lemma map_inf_fixed_points_le (x y : fixed_points f) : f (x ⊓ y) ≤ x ⊓ y :=
f.dual.le_map_sup_fixed_points x y

lemma le_map_Sup_subset_fixed_points (A : set α) (hA : A ⊆ fixed_points f) : Sup A ≤ f (Sup A) :=
Sup_le $ λ x hx, hA hx ▸ (f.mono $ le_Sup hx)

lemma map_Inf_subset_fixed_points_le (A : set α) (hA : A ⊆ fixed_points f) : f (Inf A) ≤ Inf A :=
le_Inf $ λ x hx, (hA hx) ▸ (f.mono $ Inf_le hx)

end prev_next

end preorder_hom

namespace fixed_points

open preorder_hom

variables [complete_lattice α] (f : α →ₘ α)

instance : semilattice_sup (fixed_points f) :=
{ sup := λ x y, f.next_fixed (x ⊔ y) (f.le_map_sup_fixed_points x y),
  le_sup_left := λ x y, subtype.coe_le_coe.1 $ le_sup_left.trans (f.le_next_fixed _),
  le_sup_right := λ x y, subtype.coe_le_coe.1 $ le_sup_right.trans (f.le_next_fixed _),
  sup_le := λ x y z hxz hyz, f.next_fixed_le _ $ sup_le hxz hyz,
  .. subtype.partial_order _ }

instance : semilattice_inf (fixed_points f) :=
{ inf := λ x y, f.prev_fixed (x ⊓ y) (f.map_inf_fixed_points_le x y),
  .. subtype.partial_order _, .. (order_dual.semilattice_inf (fixed_points f.dual))  }

instance : complete_semilattice_Sup (fixed_points f) :=
{ Sup := λ s, f.next_fixed (Sup (coe '' s))
    (f.le_map_Sup_subset_fixed_points (coe '' s) (λ z ⟨x, hx⟩, hx.2 ▸ x.2)),
  le_Sup := λ s x hx, subtype.coe_le_coe.1 $ le_trans (le_Sup $ set.mem_image_of_mem _ hx)
    (f.le_next_fixed _),
  Sup_le := λ s x hx, f.next_fixed_le _ $ Sup_le $ set.ball_image_iff.2 hx,
  .. subtype.partial_order _ }

instance : complete_semilattice_Inf (fixed_points f) :=
{ Inf := λ s, f.prev_fixed (Inf (coe '' s))
    (f.map_Inf_subset_fixed_points_le (coe '' s) (λ z ⟨x, hx⟩, hx.2 ▸ x.2)),
  le_Inf := λ s x hx, f.le_prev_fixed _ $ le_Inf $ set.ball_image_iff.2 hx,
  Inf_le := λ s x hx, subtype.coe_le_coe.1 $ le_trans (f.prev_fixed_le _)
    (Inf_le $ set.mem_image_of_mem _ hx),
  .. subtype.partial_order _ }

/-- **Knaster-Tarski Theorem**: The fixed points of `f` form a complete lattice. -/
instance : complete_lattice (fixed_points f) :=
{ top := ⟨f.gfp, f.is_fixed_pt_gfp⟩,
  bot := ⟨f.lfp, f.is_fixed_pt_lfp⟩,
  le_top := λ x, f.le_gfp x.2.ge,
  bot_le := λ x, f.lfp_le x.2.le,
  .. subtype.partial_order _,
  .. fixed_points.semilattice_sup f,
  .. fixed_points.semilattice_inf f,
  .. fixed_points.complete_semilattice_Sup f,
  .. fixed_points.complete_semilattice_Inf f }

end fixed_points


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Johannes Hölzl

Extended non-negative reals
-/
import topology.instances.nnreal data.real.ennreal
noncomputable theory
open classical set lattice filter metric
local attribute [instance] prop_decidable
variables {α : Type*} {β : Type*} {γ : Type*}

local notation `∞` := ennreal.infinity

namespace ennreal
variables {a b c d : ennreal} {r p q : nnreal}

section topological_space
open topological_space

/-- Topology on `ennreal`.

Note: this is different from the `emetric_space` topology. The `emetric_space` topology has
`is_open {⊤}`, while this topology doesn't have singleton elements. -/
instance : topological_space ennreal :=
topological_space.generate_from {s | ∃a, s = {b | a < b} ∨ s = {b | b < a}}

instance : orderable_topology ennreal := ⟨rfl⟩

instance : t2_space ennreal := by apply_instance

instance : second_countable_topology ennreal :=
⟨⟨⋃q ≥ (0:ℚ), {{a : ennreal | a < nnreal.of_real q}, {a : ennreal | ↑(nnreal.of_real q) < a}},
  countable_bUnion (countable_encodable _) $ assume a ha, countable_insert (countable_singleton _),
  le_antisymm
    (generate_from_le $ λ s h, begin
      rcases h with ⟨a, hs | hs⟩;
      [ rw show s = ⋃q∈{q:ℚ | 0 ≤ q ∧ a < nnreal.of_real q}, {b | ↑(nnreal.of_real q) < b},
           from set.ext (assume b, by simp [hs, @ennreal.lt_iff_exists_rat_btwn a b, and_assoc]),
        rw show s = ⋃q∈{q:ℚ | 0 ≤ q ∧ ↑(nnreal.of_real q) < a}, {b | b < ↑(nnreal.of_real q)},
           from set.ext (assume b, by simp [hs, @ennreal.lt_iff_exists_rat_btwn b a, and_comm, and_assoc])];
      { apply is_open_Union, intro q,
        apply is_open_Union, intro hq,
        exact generate_open.basic _ (mem_bUnion hq.1 $ by simp) }
    end)
    (generate_from_le $ by simp [or_imp_distrib, is_open_lt', is_open_gt'] {contextual := tt})⟩⟩

lemma embedding_coe : embedding (coe : nnreal → ennreal) :=
and.intro (assume a b, coe_eq_coe.1) $
begin
  refine le_antisymm _ _,
  { rw [orderable_topology.topology_eq_generate_intervals nnreal],
    refine generate_from_le (assume s ha, _),
    rcases ha with ⟨a, rfl | rfl⟩,
    exact ⟨{b : ennreal | ↑a < b}, @is_open_lt' ennreal ennreal.topological_space _ _ _, by simp⟩,
    exact ⟨{b : ennreal | b < ↑a}, @is_open_gt' ennreal ennreal.topological_space _ _ _, by simp⟩, },
  { rw [orderable_topology.topology_eq_generate_intervals ennreal,
      induced_le_iff_le_coinduced],
    refine generate_from_le (assume s ha, _),
    rcases ha with ⟨a, rfl | rfl⟩,
    show is_open {b : nnreal | a < ↑b},
    { cases a; simp [none_eq_top, some_eq_coe, is_open_lt'] },
    show is_open {b : nnreal | ↑b < a},
    { cases a; simp [none_eq_top, some_eq_coe, is_open_gt', is_open_const] } }
end

lemma is_open_ne_top : is_open {a : ennreal | a ≠ ⊤} :=
is_open_neg (is_closed_eq continuous_id continuous_const)

lemma coe_range_mem_nhds : range (coe : nnreal → ennreal) ∈ nhds (r : ennreal) :=
have {a : ennreal | a ≠ ⊤} = range (coe : nnreal → ennreal),
  from set.ext $ assume a, by cases a; simp [none_eq_top, some_eq_coe],
this ▸ mem_nhds_sets is_open_ne_top coe_ne_top

lemma tendsto_coe {f : filter α} {m : α → nnreal} {a : nnreal} :
  tendsto (λa, (m a : ennreal)) f (nhds ↑a) ↔ tendsto m f (nhds a) :=
embedding_coe.tendsto_nhds_iff.symm

lemma continuous_coe {α} [topological_space α] {f : α → nnreal} :
continuous (λa, (f a : ennreal)) ↔ continuous f :=
embedding_coe.continuous_iff.symm

lemma nhds_coe {r : nnreal} : nhds (r : ennreal) = (nhds r).map coe :=
by rw [embedding_coe.2, map_nhds_induced_eq coe_range_mem_nhds]

lemma nhds_coe_coe {r p : nnreal} : nhds ((r : ennreal), (p : ennreal)) =
  (nhds (r, p)).map (λp:nnreal×nnreal, (p.1, p.2)) :=
begin
  rw [(embedding_prod_mk embedding_coe embedding_coe).map_nhds_eq],
  rw [← prod_range_range_eq],
  exact prod_mem_nhds_sets coe_range_mem_nhds coe_range_mem_nhds
end

lemma continuous_of_real : continuous ennreal.of_real :=
continuous.comp nnreal.continuous_of_real (continuous_coe.2 continuous_id)

lemma tendsto_of_real {f : filter α} {m : α → ℝ} {a : ℝ} (h : tendsto m f (nhds a)) :
  tendsto (λa, ennreal.of_real (m a)) f (nhds (ennreal.of_real a)) :=
tendsto.comp h (continuous.tendsto continuous_of_real _)

lemma tendsto_to_nnreal {a : ennreal} : a ≠ ⊤ →
  tendsto (ennreal.to_nnreal) (nhds a) (nhds a.to_nnreal) :=
begin
  cases a; simp [some_eq_coe, none_eq_top, nhds_coe, tendsto_map'_iff, (∘)],
  exact tendsto_id
end

lemma tendsto_nhds_top {m : α → ennreal} {f : filter α}
  (h : ∀n:ℕ, {a | ↑n < m a} ∈ f) : tendsto m f (nhds ⊤) :=
tendsto_nhds_generate_from $ assume s hs,
match s, hs with
| _, ⟨none,   or.inl rfl⟩, hr := (lt_irrefl ⊤ hr).elim
| _, ⟨some r, or.inl rfl⟩, hr :=
  let ⟨n, hrn⟩ := exists_nat_gt r in
  mem_sets_of_superset (h n) $ assume a hnma, show ↑r < m a, from
    lt_trans (show (r : ennreal) < n, from (coe_nat n) ▸ coe_lt_coe.2 hrn) hnma
| _, ⟨a,      or.inr rfl⟩, hr := (not_top_lt $ show ⊤ < a, from hr).elim
end

lemma tendsto_coe_nnreal_nhds_top {α} {l : filter α} {f : α → nnreal} (h : tendsto f l at_top) :
  tendsto (λa, (f a : ennreal)) l (nhds (⊤:ennreal)) :=
tendsto_nhds_top $ assume n,
have {a : α | ↑(n+1) ≤ f a} ∈ l := h $ mem_at_top _,
mem_sets_of_superset this $ assume a (ha : ↑(n+1) ≤ f a),
begin
  rw [← coe_nat],
  dsimp,
  exact coe_lt_coe.2 (lt_of_lt_of_le (nat.cast_lt.2 (nat.lt_succ_self _)) ha)
end

instance : topological_add_monoid ennreal :=
⟨ continuous_iff_continuous_at.2 $
  have hl : ∀a:ennreal, tendsto (λ (p : ennreal × ennreal), p.fst + p.snd) (nhds (⊤, a)) (nhds ⊤), from
    assume a, tendsto_nhds_top $ assume n,
    have set.prod {a | ↑n < a } univ ∈ nhds ((⊤:ennreal), a), from
      prod_mem_nhds_sets (lt_mem_nhds $ coe_nat n ▸ coe_lt_top) univ_mem_sets,
    show {a : ennreal × ennreal | ↑n < a.fst + a.snd} ∈ nhds (⊤, a),
    begin filter_upwards [this] assume ⟨a₁, a₂⟩ ⟨h₁, h₂⟩, lt_of_lt_of_le h₁ (le_add_right $ le_refl _) end,
  begin
    rintro ⟨a₁, a₂⟩,
    cases a₁, { simp [continuous_at, none_eq_top, hl a₂], },
    cases a₂, { simp [continuous_at, none_eq_top, some_eq_coe, nhds_swap (a₁ : ennreal) ⊤,
                      tendsto_map'_iff, (∘), hl ↑a₁] },
    simp [continuous_at, some_eq_coe, nhds_coe_coe, tendsto_map'_iff, (∘)],
    simp only [coe_add.symm, tendsto_coe, tendsto_add']
  end ⟩

protected lemma tendsto_mul' (ha : a ≠ 0 ∨ b ≠ ⊤) (hb : b ≠ 0 ∨ a ≠ ⊤) :
  tendsto (λp:ennreal×ennreal, p.1 * p.2) (nhds (a, b)) (nhds (a * b)) :=
have ht : ∀b:ennreal, b ≠ 0 → tendsto (λp:ennreal×ennreal, p.1 * p.2) (nhds ((⊤:ennreal), b)) (nhds ⊤),
begin
  refine assume b hb, tendsto_nhds_top $ assume n, _,
  rcases dense (zero_lt_iff_ne_zero.2 hb) with ⟨ε', hε', hεb'⟩,
  rcases ennreal.lt_iff_exists_coe.1 hεb' with ⟨ε, rfl, h⟩,
  rcases exists_nat_gt (↑n / ε) with ⟨m, hm⟩,
  have hε : ε > 0, from coe_lt_coe.1 hε',
  refine mem_sets_of_superset (prod_mem_nhds_sets (lt_mem_nhds $ @coe_lt_top m) (lt_mem_nhds $ h)) _,
  rintros ⟨a₁, a₂⟩ ⟨h₁, h₂⟩,
  dsimp at h₁ h₂ ⊢,
  calc (n:ennreal) = ↑(((n:nnreal) / ε) * ε) :
    begin
      simp [nnreal.div_def],
      rw [mul_assoc, ← coe_mul, nnreal.inv_mul_cancel, coe_one, ← coe_nat, mul_one],
      exact zero_lt_iff_ne_zero.1 hε
    end
    ... < (↑m * ε : nnreal) : coe_lt_coe.2 $ mul_lt_mul hm (le_refl _) hε (nat.cast_nonneg _)
    ... ≤ a₁ * a₂ : by rw [coe_mul]; exact canonically_ordered_semiring.mul_le_mul
      (le_of_lt h₁)
      (le_of_lt h₂)
end,
begin
  cases a, {simp [none_eq_top] at hb, simp [none_eq_top, ht b hb, top_mul, hb] },
  cases b, {
    simp [none_eq_top] at ha,
    have ha' : a ≠ 0, from mt coe_eq_coe.2 ha,
    simp [*, nhds_swap (a : ennreal) ⊤, none_eq_top, some_eq_coe, top_mul, tendsto_map'_iff, (∘), mul_comm] },
  simp [some_eq_coe, nhds_coe_coe, tendsto_map'_iff, (∘)],
  simp only [coe_mul.symm, tendsto_coe, tendsto_mul']
end

protected lemma tendsto_mul {f : filter α} {ma : α → ennreal} {mb : α → ennreal} {a b : ennreal}
  (hma : tendsto ma f (nhds a)) (ha : a ≠ 0 ∨ b ≠ ⊤) (hmb : tendsto mb f (nhds b)) (hb : b ≠ 0 ∨ a ≠ ⊤) :
  tendsto (λa, ma a * mb a) f (nhds (a * b)) :=
show tendsto ((λp:ennreal×ennreal, p.1 * p.2) ∘ (λa, (ma a, mb a))) f (nhds (a * b)), from
tendsto.comp (tendsto_prod_mk_nhds hma hmb) (ennreal.tendsto_mul' ha hb)

protected lemma tendsto_mul_right {f : filter α} {m : α → ennreal} {a b : ennreal}
  (hm : tendsto m f (nhds b)) (hb : b ≠ 0 ∨ a ≠ ⊤) : tendsto (λb, a * m b) f (nhds (a * b)) :=
by_cases
  (assume : a = 0, by simp [this, tendsto_const_nhds])
  (assume ha : a ≠ 0, ennreal.tendsto_mul tendsto_const_nhds (or.inl ha) hm hb)

lemma Sup_add {s : set ennreal} (hs : s ≠ ∅) : Sup s + a = ⨆b∈s, b + a :=
have Sup ((λb, b + a) '' s) = Sup s + a,
  from is_lub_iff_Sup_eq.mp $ is_lub_of_is_lub_of_tendsto
    (assume x _ y _ h, add_le_add' h (le_refl _))
    is_lub_Sup
    hs
    (tendsto_add (tendsto_id' inf_le_left) tendsto_const_nhds),
by simp [Sup_image, -add_comm] at this; exact this.symm

lemma supr_add {ι : Sort*} {s : ι → ennreal} [h : nonempty ι] : supr s + a = ⨆b, s b + a :=
let ⟨x⟩ := h in
calc supr s + a = Sup (range s) + a : by simp [Sup_range]
  ... = (⨆b∈range s, b + a) : Sup_add $ ne_empty_iff_exists_mem.mpr ⟨s x, x, rfl⟩
  ... = _ : by simp [supr_range, -mem_range]

lemma add_supr {ι : Sort*} {s : ι → ennreal} [h : nonempty ι] : a + supr s = ⨆b, a + s b :=
by rw [add_comm, supr_add]; simp

lemma supr_add_supr {ι : Sort*} {f g : ι → ennreal} (h : ∀i j, ∃k, f i + g j ≤ f k + g k) :
  supr f + supr g = (⨆ a, f a + g a) :=
begin
  by_cases hι : nonempty ι,
  { letI := hι,
    refine le_antisymm _ (supr_le $ λ a, add_le_add' (le_supr _ _) (le_supr _ _)),
    simpa [add_supr, supr_add] using
      λ i j:ι, show f i + g j ≤ ⨆ a, f a + g a, from
      let ⟨k, hk⟩ := h i j in le_supr_of_le k hk },
  { have : ∀f:ι → ennreal, (⨆i, f i) = 0 := assume f, bot_unique (supr_le $ assume i, (hι ⟨i⟩).elim),
    rw [this, this, this, zero_add] }
end

lemma supr_add_supr_of_monotone {ι : Sort*} [semilattice_sup ι]
  {f g : ι → ennreal} (hf : monotone f) (hg : monotone g) :
  supr f + supr g = (⨆ a, f a + g a) :=
supr_add_supr $ assume i j, ⟨i ⊔ j, add_le_add' (hf $ le_sup_left) (hg $ le_sup_right)⟩

lemma finset_sum_supr_nat {α} {ι} [semilattice_sup ι] {s : finset α} {f : α → ι → ennreal}
  (hf : ∀a, monotone (f a)) :
  s.sum (λa, supr (f a)) = (⨆ n, s.sum (λa, f a n)) :=
begin
  refine finset.induction_on s _ _,
  { simp,
    exact (bot_unique $ supr_le $ assume i, le_refl ⊥).symm },
  { assume a s has ih,
    simp only [finset.sum_insert has],
    rw [ih, supr_add_supr_of_monotone (hf a)],
    assume i j h,
    exact (finset.sum_le_sum' $ assume a ha, hf a h) }
end

lemma mul_Sup {s : set ennreal} {a : ennreal} : a * Sup s = ⨆i∈s, a * i :=
begin
  by_cases hs : ∀x∈s, x = (0:ennreal),
  { have h₁ : Sup s = 0 := (bot_unique $ Sup_le $ assume a ha, (hs a ha).symm ▸ le_refl 0),
    have h₂ : (⨆i ∈ s, a * i) = 0 :=
      (bot_unique $ supr_le $ assume a, supr_le $ assume ha, by simp [hs a ha]),
    rw [h₁, h₂, mul_zero] },
  { simp only [not_forall] at hs,
    rcases hs with ⟨x, hx, hx0⟩,
    have s₀ : s ≠ ∅ := not_eq_empty_iff_exists.2 ⟨x, hx⟩,
    have s₁ : Sup s ≠ 0 :=
      zero_lt_iff_ne_zero.1 (lt_of_lt_of_le (zero_lt_iff_ne_zero.2 hx0) (le_Sup hx)),
    have : Sup ((λb, a * b) '' s) = a * Sup s :=
      is_lub_iff_Sup_eq.mp (is_lub_of_is_lub_of_tendsto
        (assume x _ y _ h, canonically_ordered_semiring.mul_le_mul (le_refl _) h)
        is_lub_Sup
        s₀
        (ennreal.tendsto_mul_right (tendsto_id' inf_le_left) (or.inl s₁))),
    rw [this.symm, Sup_image] }
end

lemma mul_supr {ι : Sort*} {f : ι → ennreal} {a : ennreal} : a * supr f = ⨆i, a * f i :=
by rw [← Sup_range, mul_Sup, supr_range]

lemma supr_mul {ι : Sort*} {f : ι → ennreal} {a : ennreal} : supr f * a = ⨆i, f i * a :=
by rw [mul_comm, mul_supr]; congr; funext; rw [mul_comm]

protected lemma tendsto_coe_sub : ∀{b:ennreal}, tendsto (λb:ennreal, ↑r - b) (nhds b) (nhds (↑r - b)) :=
begin
  refine (forall_ennreal.2 $ and.intro (assume a, _) _),
  { simp [@nhds_coe a, tendsto_map'_iff, (∘), tendsto_coe, coe_sub.symm],
    exact nnreal.tendsto_sub tendsto_const_nhds tendsto_id },
  simp,
  exact (tendsto.congr' (mem_sets_of_superset (lt_mem_nhds $ @coe_lt_top r) $
    by simp [le_of_lt] {contextual := tt})) tendsto_const_nhds
end

lemma sub_supr {ι : Sort*} [hι : nonempty ι] {b : ι → ennreal} (hr : a < ⊤) :
  a - (⨆i, b i) = (⨅i, a - b i) :=
let ⟨i⟩ := hι in
let ⟨r, eq, _⟩ := lt_iff_exists_coe.mp hr in
have Inf ((λb, ↑r - b) '' range b) = ↑r - (⨆i, b i),
  from is_glb_iff_Inf_eq.mp $ is_glb_of_is_lub_of_tendsto
    (assume x _ y _, sub_le_sub (le_refl _))
    is_lub_supr
    (ne_empty_of_mem ⟨i, rfl⟩)
    (tendsto.comp (tendsto_id' inf_le_left) ennreal.tendsto_coe_sub),
by rw [eq, ←this]; simp [Inf_image, infi_range, -mem_range]; exact le_refl _

end topological_space

section tsum

variables {f g : α → ennreal}

protected lemma is_sum_coe {f : α → nnreal} {r : nnreal} :
  is_sum (λa, (f a : ennreal)) ↑r ↔ is_sum f r :=
have (λs:finset α, s.sum (coe ∘ f)) = (coe : nnreal → ennreal) ∘ (λs:finset α, s.sum f),
  from funext $ assume s, ennreal.coe_finset_sum.symm,
by unfold is_sum; rw [this, tendsto_coe]

protected lemma tsum_coe_eq {f : α → nnreal} (h : is_sum f r) : (∑a, (f a : ennreal)) = r :=
tsum_eq_is_sum $ ennreal.is_sum_coe.2 $ h

protected lemma tsum_coe {f : α → nnreal} : has_sum f → (∑a, (f a : ennreal)) = ↑(tsum f)
| ⟨r, hr⟩ := by rw [tsum_eq_is_sum hr, ennreal.tsum_coe_eq hr]

protected lemma is_sum : is_sum f (⨆s:finset α, s.sum f) :=
tendsto_orderable.2
  ⟨assume a' ha',
    let ⟨s, hs⟩ := lt_supr_iff.mp ha' in
    mem_at_top_sets.mpr ⟨s, assume t ht, lt_of_lt_of_le hs $ finset.sum_le_sum_of_subset ht⟩,
  assume a' ha',
    univ_mem_sets' $ assume s,
    have s.sum f ≤ ⨆(s : finset α), s.sum f,
      from le_supr (λ(s : finset α), s.sum f) s,
    lt_of_le_of_lt this ha'⟩

@[simp] protected lemma has_sum : has_sum f := ⟨_, ennreal.is_sum⟩

protected lemma tsum_eq_supr_sum : (∑a, f a) = (⨆s:finset α, s.sum f) :=
tsum_eq_is_sum ennreal.is_sum

protected lemma tsum_sigma {β : α → Type*} (f : Πa, β a → ennreal) :
  (∑p:Σa, β a, f p.1 p.2) = (∑a b, f a b) :=
tsum_sigma (assume b, ennreal.has_sum) ennreal.has_sum

protected lemma tsum_prod {f : α → β → ennreal} : (∑p:α×β, f p.1 p.2) = (∑a, ∑b, f a b) :=
let j : α × β → (Σa:α, β) := λp, sigma.mk p.1 p.2 in
let i : (Σa:α, β) → α × β := λp, (p.1, p.2) in
let f' : (Σa:α, β) → ennreal := λp, f p.1 p.2 in
calc (∑p:α×β, f' (j p)) = (∑p:Σa:α, β, f p.1 p.2) :
    tsum_eq_tsum_of_iso j i (assume ⟨a, b⟩, rfl) (assume ⟨a, b⟩, rfl)
   ... = (∑a, ∑b, f a b) : ennreal.tsum_sigma f

protected lemma tsum_comm {f : α → β → ennreal} : (∑a, ∑b, f a b) = (∑b, ∑a, f a b) :=
let f' : α×β → ennreal := λp, f p.1 p.2 in
calc (∑a, ∑b, f a b) = (∑p:α×β, f' p) : ennreal.tsum_prod.symm
  ... = (∑p:β×α, f' (prod.swap p)) :
    (tsum_eq_tsum_of_iso prod.swap (@prod.swap α β) (assume ⟨a, b⟩, rfl) (assume ⟨a, b⟩, rfl)).symm
  ... = (∑b, ∑a, f' (prod.swap (b, a))) : @ennreal.tsum_prod β α (λb a, f' (prod.swap (b, a)))

protected lemma tsum_add : (∑a, f a + g a) = (∑a, f a) + (∑a, g a) :=
tsum_add ennreal.has_sum ennreal.has_sum

protected lemma tsum_le_tsum (h : ∀a, f a ≤ g a) : (∑a, f a) ≤ (∑a, g a) :=
tsum_le_tsum h ennreal.has_sum ennreal.has_sum

protected lemma tsum_eq_supr_nat {f : ℕ → ennreal} :
  (∑i:ℕ, f i) = (⨆i:ℕ, (finset.range i).sum f) :=
calc _ = (⨆s:finset ℕ, s.sum f) : ennreal.tsum_eq_supr_sum
  ... = (⨆i:ℕ, (finset.range i).sum f) : le_antisymm
    (supr_le_supr2 $ assume s,
      let ⟨n, hn⟩ := finset.exists_nat_subset_range s in
      ⟨n, finset.sum_le_sum_of_subset hn⟩)
    (supr_le_supr2 $ assume i, ⟨finset.range i, le_refl _⟩)

protected lemma le_tsum (a : α) : f a ≤ (∑a, f a) :=
calc f a = ({a} : finset α).sum f : by simp
  ... ≤ (⨆s:finset α, s.sum f) : le_supr (λs:finset α, s.sum f) _
  ... = (∑a, f a) : by rw [ennreal.tsum_eq_supr_sum]

protected lemma mul_tsum : (∑i, a * f i) = a * (∑i, f i) :=
if h : ∀i, f i = 0 then by simp [h] else
let ⟨i, (hi : f i ≠ 0)⟩ := classical.not_forall.mp h in
have sum_ne_0 : (∑i, f i) ≠ 0, from ne_of_gt $
  calc 0 < f i : lt_of_le_of_ne (zero_le _) hi.symm
    ... ≤ (∑i, f i) : ennreal.le_tsum _,
have tendsto (λs:finset α, s.sum ((*) a ∘ f)) at_top (nhds (a * (∑i, f i))),
  by rw [← show (*) a ∘ (λs:finset α, s.sum f) = λs, s.sum ((*) a ∘ f),
         from funext $ λ s, finset.mul_sum];
  exact ennreal.tendsto_mul_right (is_sum_tsum ennreal.has_sum) (or.inl sum_ne_0),
tsum_eq_is_sum this

protected lemma tsum_mul : (∑i, f i * a) = (∑i, f i) * a :=
by simp [mul_comm, ennreal.mul_tsum]

@[simp] lemma tsum_supr_eq {α : Type*} (a : α) {f : α → ennreal} :
  (∑b:α, ⨆ (h : a = b), f b) = f a :=
le_antisymm
  (by rw [ennreal.tsum_eq_supr_sum]; exact supr_le (assume s,
    calc s.sum (λb, ⨆ (h : a = b), f b) ≤ (finset.singleton a).sum (λb, ⨆ (h : a = b), f b) :
        finset.sum_le_sum_of_ne_zero $ assume b _ hb,
          suffices a = b, by simpa using this.symm,
          classical.by_contradiction $ assume h,
            by simpa [h] using hb
      ... = f a : by simp))
  (calc f a ≤ (⨆ (h : a = a), f a) : le_supr (λh:a=a, f a) rfl
    ... ≤ (∑b:α, ⨆ (h : a = b), f b) : ennreal.le_tsum _)

lemma is_sum_iff_tendsto_nat {f : ℕ → ennreal} (r : ennreal) :
  is_sum f r ↔ tendsto (λn:ℕ, (finset.range n).sum f) at_top (nhds r) :=
begin
  refine ⟨tendsto_sum_nat_of_is_sum, assume h, _⟩,
  rw [← supr_eq_of_tendsto _ h, ← ennreal.tsum_eq_supr_nat],
  { exact is_sum_tsum ennreal.has_sum },
  { exact assume s t hst, finset.sum_le_sum_of_subset (finset.range_subset.2 hst) }
end

end tsum

end ennreal

namespace nnreal

lemma exists_le_is_sum_of_le {f g : β → nnreal} {r : nnreal}
  (hgf : ∀b, g b ≤ f b) (hfr : is_sum f r) : ∃p≤r, is_sum g p :=
have (∑b, (g b : ennreal)) ≤ r,
begin
  refine is_sum_le (assume b, _) (is_sum_tsum ennreal.has_sum) (ennreal.is_sum_coe.2 hfr),
  exact ennreal.coe_le_coe.2 (hgf _)
end,
let ⟨p, eq, hpr⟩ := ennreal.le_coe_iff.1 this in
⟨p, hpr, ennreal.is_sum_coe.1 $ eq ▸ is_sum_tsum ennreal.has_sum⟩

lemma has_sum_of_le {f g : β → nnreal} (hgf : ∀b, g b ≤ f b) : has_sum f → has_sum g
| ⟨r, hfr⟩ := let ⟨p, _, hp⟩ := exists_le_is_sum_of_le hgf hfr in has_sum_spec hp

lemma is_sum_iff_tendsto_nat {f : ℕ → nnreal} (r : nnreal) :
  is_sum f r ↔ tendsto (λn:ℕ, (finset.range n).sum f) at_top (nhds r) :=
begin
  rw [← ennreal.is_sum_coe, ennreal.is_sum_iff_tendsto_nat],
  simp only [ennreal.coe_finset_sum.symm],
  exact ennreal.tendsto_coe
end

end nnreal

lemma has_sum_of_nonneg_of_le {f g : β → ℝ}
  (hg : ∀b, 0 ≤ g b) (hgf : ∀b, g b ≤ f b) (hf : has_sum f) : has_sum g :=
let f' (b : β) : nnreal := ⟨f b, le_trans (hg b) (hgf b)⟩ in
let g' (b : β) : nnreal := ⟨g b, hg b⟩ in
have has_sum f', from nnreal.has_sum_coe.1 hf,
have has_sum g', from
  nnreal.has_sum_of_le (assume b, (@nnreal.coe_le (g' b) (f' b)).2 $ hgf b) this,
show has_sum (λb, g' b : β → ℝ), from nnreal.has_sum_coe.2 this

lemma is_sum_iff_tendsto_nat_of_nonneg {f : ℕ → ℝ} (hf : ∀i, 0 ≤ f i) (r : ℝ) :
  is_sum f r ↔ tendsto (λn:ℕ, (finset.range n).sum f) at_top (nhds r) :=
⟨tendsto_sum_nat_of_is_sum,
  assume hfr,
  have 0 ≤ r := ge_of_tendsto at_top_ne_bot hfr $ univ_mem_sets' $ assume i,
    show 0 ≤ (finset.range i).sum f, from finset.zero_le_sum $ assume i _, hf i,
  let f' (n : ℕ) : nnreal := ⟨f n, hf n⟩, r' : nnreal := ⟨r, this⟩ in
  have f_eq : f = (λi:ℕ, (f' i : ℝ)) := rfl,
  have r_eq : r = r' := rfl,
  begin
    rw [f_eq, r_eq, nnreal.is_sum_coe, nnreal.is_sum_iff_tendsto_nat, ← nnreal.tendsto_coe],
    simp only [nnreal.sum_coe],
    exact hfr
  end⟩

lemma infi_real_pos_eq_infi_nnreal_pos {α : Type*} [complete_lattice α] {f : ℝ → α} :
  (⨅(n:ℝ) (h : n > 0), f n) = (⨅(n:nnreal) (h : n > 0), f n) :=
le_antisymm
  (le_infi $ assume n, le_infi $ assume hn, infi_le_of_le n $ infi_le _ (nnreal.coe_pos.2 hn))
  (le_infi $ assume r, le_infi $ assume hr, infi_le_of_le ⟨r, le_of_lt hr⟩ $ infi_le _ hr)

section
variables [emetric_space β]
open lattice ennreal filter emetric

/-- In an emetric ball, the distance between points is everywhere finite -/
lemma edist_ne_top_of_mem_ball {a : β} {r : ennreal} (x y : ball a r) : edist x.1 y.1 ≠ ⊤ :=
lt_top_iff_ne_top.1 $
calc edist x y ≤ edist a x + edist a y : edist_triangle_left x.1 y.1 a
  ... < r + r : by rw [edist_comm a x, edist_comm a y]; exact add_lt_add x.2 y.2
  ... ≤ ⊤ : le_top

/-- Each ball in an extended metric space gives us a metric space, as the edist
is everywhere finite. -/
def metric_space_emetric_ball (a : β) (r : ennreal) : metric_space (ball a r) :=
emetric_space.to_metric_space edist_ne_top_of_mem_ball

local attribute [instance] metric_space_emetric_ball

lemma nhds_eq_nhds_emetric_ball (a x : β) (r : ennreal) (h : x ∈ ball a r) :
  nhds x = map (coe : ball a r → β) (nhds ⟨x, h⟩) :=
(map_nhds_subtype_val_eq _ $ mem_nhds_sets emetric.is_open_ball h).symm
end

section
variable [emetric_space α]
open emetric

/-- Yet another metric characterization of Cauchy sequences on integers. This one is often the
most efficient. -/
lemma emetric.cauchy_seq_iff_le_tendsto_0 [inhabited β] [semilattice_sup β] {s : β → α} :
  cauchy_seq s ↔ (∃ (b: β → ennreal), (∀ n m N : β, N ≤ n → N ≤ m → edist (s n) (s m) ≤ b N)
                    ∧ (tendsto b at_top (nhds 0))) :=
⟨begin
  assume hs,
  rw emetric.cauchy_seq_iff at hs,
  /- `s` is Cauchy sequence. The sequence `b` will be constructed by taking
  the supremum of the distances between `s n` and `s m` for `n m ≥ N`-/
  let b := λN, Sup ((λ(p : β × β), edist (s p.1) (s p.2))''{p | p.1 ≥ N ∧ p.2 ≥ N}),
  --Prove that it bounds the distances of points in the Cauchy sequence
  have C : ∀ n m N, N ≤ n → N ≤ m → edist (s n) (s m) ≤ b N,
  { refine λm n N hm hn, le_Sup _,
    use (prod.mk m n),
    simp only [and_true, eq_self_iff_true, set.mem_set_of_eq],
    exact ⟨hm, hn⟩ },
  --Prove that it tends to `0`, by using the Cauchy property of `s`
  have D : tendsto b at_top (nhds 0),
  { refine tendsto_orderable.2 ⟨λa ha, absurd ha (ennreal.not_lt_zero), λε εpos, _⟩,
    rcases dense εpos with ⟨δ, δpos, δlt⟩,
    rcases hs δ δpos with ⟨N, hN⟩,
    refine filter.mem_at_top_sets.2 ⟨N, λn hn, _⟩,
    have : b n ≤ δ := Sup_le begin
      simp only [and_imp, set.mem_image, set.mem_set_of_eq, exists_imp_distrib, prod.exists],
      intros d p q hp hq hd,
      rw ← hd,
      exact le_of_lt (hN q p (le_trans hn hq) (le_trans hn hp))
    end,
    simpa using lt_of_le_of_lt this δlt },
  -- Conclude
  exact ⟨b, ⟨C, D⟩⟩
end,
begin
  rintros ⟨b, ⟨b_bound, b_lim⟩⟩,
  /-b : ℕ → ℝ, b_bound : ∀ (n m N : ℕ), N ≤ n → N ≤ m → edist (s n) (s m) ≤ b N,
    b_lim : tendsto b at_top (nhds 0)-/
  refine emetric.cauchy_seq_iff.2 (λε εpos, _),
  have : {n | b n < ε} ∈ at_top := (tendsto_orderable.1 b_lim ).2 _ εpos,
  rcases filter.mem_at_top_sets.1 this with ⟨N, hN⟩,
  exact ⟨N, λm n hm hn, calc
    edist (s n) (s m) ≤ b N : b_bound n m N hn hm
    ... < ε : (hN _ (le_refl N)) ⟩
end⟩

lemma continuous_of_le_add_edist {f : α → ennreal} (C : ennreal)
  (hC : C ≠ ⊤) (h : ∀x y, f x ≤ f y + C * edist x y) : continuous f :=
begin
  refine continuous_iff_continuous_at.2 (λx, tendsto_orderable.2 ⟨_, _⟩),
  show ∀e, e < f x → {y : α | e < f y} ∈ nhds x,
  { assume e he,
    let ε := min (f x - e) 1,
    have : ε < ⊤ := lt_of_le_of_lt (min_le_right _ _) (by simp [lt_top_iff_ne_top]),
    have : 0 < ε := by simp [ε, hC, he, ennreal.zero_lt_one],
    have : 0 < C⁻¹ * (ε/2) := bot_lt_iff_ne_bot.2 (by simp [hC, (ne_of_lt this).symm, ennreal.mul_eq_zero]),
    have I : C * (C⁻¹ * (ε/2)) < ε,
    { by_cases C_zero : C = 0,
      { simp [C_zero, ‹0 < ε›] },
      { calc C * (C⁻¹ * (ε/2)) = (C * C⁻¹) * (ε/2) : by simp [mul_assoc]
        ... = ε/2 : by simp [ennreal.mul_inv_cancel C_zero hC]
        ... < ε : ennreal.half_lt_self (bot_lt_iff_ne_bot.1 ‹0 < ε›) (lt_top_iff_ne_top.1 ‹ε < ⊤›) }},
    have : ball x (C⁻¹ * (ε/2)) ⊆ {y : α | e < f y},
    { rintros y hy,
      by_cases htop : f y = ⊤,
      { simp [htop, lt_top_iff_ne_top, ne_top_of_lt he] },
      { simp at hy,
        have : e + ε < f y + ε := calc
          e + ε ≤ e + (f x - e) : add_le_add_left' (min_le_left _ _)
          ... = f x : by simp [le_of_lt he]
          ... ≤ f y + C * edist x y : h x y
          ... = f y + C * edist y x : by simp [edist_comm]
          ... ≤ f y + C * (C⁻¹ * (ε/2)) :
            add_le_add_left' $ canonically_ordered_semiring.mul_le_mul (le_refl _) (le_of_lt hy)
          ... < f y + ε : (ennreal.add_lt_add_iff_left (lt_top_iff_ne_top.2 htop)).2 I,
        show e < f y, from
          (ennreal.add_lt_add_iff_right ‹ε < ⊤›).1 this }},
    apply filter.mem_sets_of_superset (ball_mem_nhds _ (‹0 < C⁻¹ * (ε/2)›)) this },
  show ∀e, f x < e → {y : α | f y < e} ∈ nhds x,
  { assume e he,
    let ε := min (e - f x) 1,
    have : ε < ⊤ := lt_of_le_of_lt (min_le_right _ _) (by simp [lt_top_iff_ne_top]),
    have : 0 < ε := by simp [ε, he, ennreal.zero_lt_one],
    have : 0 < C⁻¹ * (ε/2) := bot_lt_iff_ne_bot.2 (by simp [hC, (ne_of_lt this).symm, ennreal.mul_eq_zero]),
    have I : C * (C⁻¹ * (ε/2)) < ε,
    { by_cases C_zero : C = 0,
      simp [C_zero, ‹0 < ε›],
      calc C * (C⁻¹ * (ε/2)) = (C * C⁻¹) * (ε/2) : by simp [mul_assoc]
        ... = ε/2 : by simp [ennreal.mul_inv_cancel C_zero hC]
        ... < ε : ennreal.half_lt_self (bot_lt_iff_ne_bot.1 ‹0 < ε›) (lt_top_iff_ne_top.1 ‹ε < ⊤›) },
    have : ball x (C⁻¹ * (ε/2)) ⊆ {y : α | f y < e},
    { rintros y hy,
      have htop : f x ≠ ⊤ := ne_top_of_lt he,
      show f y < e, from calc
        f y ≤ f x + C * edist y x : h y x
        ... ≤ f x + C * (C⁻¹ * (ε/2)) :
            add_le_add_left' $ canonically_ordered_semiring.mul_le_mul (le_refl _) (le_of_lt hy)
        ... < f x + ε : (ennreal.add_lt_add_iff_left (lt_top_iff_ne_top.2 htop)).2 I
        ... ≤ f x + (e - f x) : add_le_add_left' (min_le_left _ _)
        ... = e : by simp [le_of_lt he] },
    apply filter.mem_sets_of_superset (ball_mem_nhds _ (‹0 < C⁻¹ * (ε/2)›)) this },
end

theorem continuous_edist' : continuous (λp:α×α, edist p.1 p.2) :=
begin
  apply continuous_of_le_add_edist 2 (by simp),
  rintros ⟨x, y⟩ ⟨x', y'⟩,
  calc edist x y ≤ edist x x' + edist x' y' + edist y' y : edist_triangle4 _ _ _ _
    ... = edist x' y' + (edist x x' + edist y y') : by simp [add_comm, edist_comm]
    ... ≤ edist x' y' + (edist (x, y) (x', y') + edist (x, y) (x', y')) :
      add_le_add_left' (add_le_add' (by simp [edist, le_refl]) (by simp [edist, le_refl]))
    ... = edist x' y' + 2 * edist (x, y) (x', y') : by rw [← mul_two, mul_comm]
end

theorem continuous_edist [topological_space β] {f g : β → α}
  (hf : continuous f) (hg : continuous g) : continuous (λb, edist (f b) (g b)) :=
(hf.prod_mk hg).comp continuous_edist'

theorem tendsto_edist {f g : β → α} {x : filter β} {a b : α}
  (hf : tendsto f x (nhds a)) (hg : tendsto g x (nhds b)) :
  tendsto (λx, edist (f x) (g x)) x (nhds (edist a b)) :=
have tendsto (λp:α×α, edist p.1 p.2) (nhds (a, b)) (nhds (edist a b)),
  from continuous_iff_continuous_at.mp continuous_edist' (a, b),
(hf.prod_mk hg).comp (by rw [nhds_prod_eq] at this; exact this)

end --section