/-
Copyright (c) 2020 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import topology.algebra.module
import linear_algebra.multilinear

/-!
# Continuous multilinear maps

We define continuous multilinear maps as maps from `Π(i : ι), M₁ i` to `M₂` which are multilinear
and continuous, by extending the space of multilinear maps with a continuity assumption.
Here, `M₁ i` and `M₂` are modules over a ring `R`, and `ι` is an arbitrary type, and all these
spaces are also topological spaces.

## Main definitions

* `continuous_multilinear_map R M₁ M₂` is the space of continuous multilinear maps from
  `Π(i : ι), M₁ i` to `M₂`. We show that it is an `R`-module.

## Implementation notes

We mostly follow the API of multilinear maps.

## Notation

We introduce the notation `M [×n]→L[R] M'` for the space of continuous `n`-multilinear maps from
`M^n` to `M'`. This is a particular case of the general notion (where we allow varying dependent
types as the arguments of our continuous multilinear maps), but arguably the most important one,
especially when defining iterated derivatives.
-/

open function fin set
open_locale big_operators

universes u v w w₁ w₁' w₂ w₃ w₄
variables {R : Type u} {ι : Type v} {n : ℕ} {M : fin n.succ → Type w} {M₁ : ι → Type w₁}
  {M₁' : ι → Type w₁'} {M₂ : Type w₂} {M₃ : Type w₃} {M₄ : Type w₄} [decidable_eq ι]

/-- Continuous multilinear maps over the ring `R`, from `Πi, M₁ i` to `M₂` where `M₁ i` and `M₂`
are modules over `R` with a topological structure. In applications, there will be compatibility
conditions between the algebraic and the topological structures, but this is not needed for the
definition. -/
structure continuous_multilinear_map (R : Type u) {ι : Type v} (M₁ : ι → Type w₁) (M₂ : Type w₂)
  [decidable_eq ι] [semiring R] [∀i, add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
  [∀i, module R (M₁ i)] [module R M₂] [∀i, topological_space (M₁ i)] [topological_space M₂]
  extends multilinear_map R M₁ M₂ :=
(cont : continuous to_fun)

notation M `[×`:25 n `]→L[`:25 R `] ` M' := continuous_multilinear_map R (λ (i : fin n), M) M'

namespace continuous_multilinear_map

section semiring

variables [semiring R]
[Πi, add_comm_monoid (M i)] [Πi, add_comm_monoid (M₁ i)] [Πi, add_comm_monoid (M₁' i)]
  [add_comm_monoid M₂] [add_comm_monoid M₃] [add_comm_monoid M₄] [Π i, module R (M i)]
  [Π i, module R (M₁ i)]  [Π i, module R (M₁' i)] [module R M₂]
  [module R M₃] [module R M₄]
  [Π i, topological_space (M i)] [Π i, topological_space (M₁ i)] [Π i, topological_space (M₁' i)]
  [topological_space M₂] [topological_space M₃] [topological_space M₄]
(f f' : continuous_multilinear_map R M₁ M₂)

instance : has_coe_to_fun (continuous_multilinear_map R M₁ M₂) :=
⟨_, λ f, f.to_multilinear_map.to_fun⟩

@[continuity] lemma coe_continuous : continuous (f : (Π i, M₁ i) → M₂) := f.cont

@[simp] lemma coe_coe : (f.to_multilinear_map : (Π i, M₁ i) → M₂) = f := rfl

theorem to_multilinear_map_inj :
  function.injective (continuous_multilinear_map.to_multilinear_map :
    continuous_multilinear_map R M₁ M₂ → multilinear_map R M₁ M₂)
| ⟨f, hf⟩ ⟨g, hg⟩ rfl := rfl

@[ext] theorem ext {f f' : continuous_multilinear_map R M₁ M₂} (H : ∀ x, f x = f' x) : f = f' :=
to_multilinear_map_inj $ multilinear_map.ext H

@[simp] lemma map_add (m : Πi, M₁ i) (i : ι) (x y : M₁ i) :
  f (update m i (x + y)) = f (update m i x) + f (update m i y) :=
f.map_add' m i x y

@[simp] lemma map_smul (m : Πi, M₁ i) (i : ι) (c : R) (x : M₁ i) :
  f (update m i (c • x)) = c • f (update m i x) :=
f.map_smul' m i c x

lemma map_coord_zero {m : Πi, M₁ i} (i : ι) (h : m i = 0) : f m = 0 :=
f.to_multilinear_map.map_coord_zero i h

@[simp] lemma map_zero [nonempty ι] : f 0 = 0 :=
f.to_multilinear_map.map_zero

instance : has_zero (continuous_multilinear_map R M₁ M₂) :=
⟨{ cont := continuous_const, ..(0 : multilinear_map R M₁ M₂) }⟩

instance : inhabited (continuous_multilinear_map R M₁ M₂) := ⟨0⟩

@[simp] lemma zero_apply (m : Πi, M₁ i) : (0 : continuous_multilinear_map R M₁ M₂) m = 0 := rfl

section has_continuous_add
variable [has_continuous_add M₂]

instance : has_add (continuous_multilinear_map R M₁ M₂) :=
⟨λ f f', ⟨f.to_multilinear_map + f'.to_multilinear_map, f.cont.add f'.cont⟩⟩

@[simp] lemma add_apply (m : Πi, M₁ i) : (f + f') m = f m + f' m := rfl

@[simp] lemma to_multilinear_map_add (f g : continuous_multilinear_map R M₁ M₂) :
  (f + g).to_multilinear_map = f.to_multilinear_map + g.to_multilinear_map :=
rfl

instance add_comm_monoid : add_comm_monoid (continuous_multilinear_map R M₁ M₂) :=
to_multilinear_map_inj.add_comm_monoid _ rfl (λ _ _, rfl)

/-- Evaluation of a `continuous_multilinear_map` at a vector as an `add_monoid_hom`. -/
def apply_add_hom (m : Π i, M₁ i) : continuous_multilinear_map R M₁ M₂ →+ M₂ :=
⟨λ f, f m, rfl, λ _ _, rfl⟩

@[simp] lemma sum_apply {α : Type*} (f : α → continuous_multilinear_map R M₁ M₂)
  (m : Πi, M₁ i) {s : finset α} : (∑ a in s, f a) m = ∑ a in s, f a m :=
(apply_add_hom m).map_sum f s

end has_continuous_add

/-- If `f` is a continuous multilinear map, then `f.to_continuous_linear_map m i` is the continuous
linear map obtained by fixing all coordinates but `i` equal to those of `m`, and varying the
`i`-th coordinate. -/
def to_continuous_linear_map (m : Πi, M₁ i) (i : ι) : M₁ i →L[R] M₂ :=
{ cont := f.cont.comp (continuous_const.update i continuous_id),
  .. f.to_multilinear_map.to_linear_map m i }

/-- The cartesian product of two continuous multilinear maps, as a continuous multilinear map. -/
def prod (f : continuous_multilinear_map R M₁ M₂) (g : continuous_multilinear_map R M₁ M₃) :
  continuous_multilinear_map R M₁ (M₂ × M₃) :=
{ cont := f.cont.prod_mk g.cont,
  .. f.to_multilinear_map.prod g.to_multilinear_map }

@[simp] lemma prod_apply
  (f : continuous_multilinear_map R M₁ M₂) (g : continuous_multilinear_map R M₁ M₃) (m : Πi, M₁ i) :
  (f.prod g) m = (f m, g m) := rfl

/-- Combine a family of continuous multilinear maps with the same domain and codomains `M' i` into a
continuous multilinear map taking values in the space of functions `Π i, M' i`. -/
def pi {ι' : Type*} {M' : ι' → Type*} [Π i, add_comm_group (M' i)] [Π i, topological_space (M' i)]
  [Π i, module R (M' i)] (f : Π i, continuous_multilinear_map R M₁ (M' i)) :
  continuous_multilinear_map R M₁ (Π i, M' i) :=
{ cont := continuous_pi $ λ i, (f i).coe_continuous,
  to_multilinear_map := multilinear_map.pi (λ i, (f i).to_multilinear_map) }

@[simp] lemma coe_pi {ι' : Type*} {M' : ι' → Type*} [Π i, add_comm_group (M' i)]
  [Π i, topological_space (M' i)] [Π i, module R (M' i)]
  (f : Π i, continuous_multilinear_map R M₁ (M' i)) :
  ⇑(pi f) = λ m j, f j m :=
rfl

lemma pi_apply {ι' : Type*} {M' : ι' → Type*} [Π i, add_comm_group (M' i)]
  [Π i, topological_space (M' i)] [Π i, module R (M' i)]
  (f : Π i, continuous_multilinear_map R M₁ (M' i)) (m : Π i, M₁ i) (j : ι') :
  pi f m j = f j m :=
rfl

/-- If `g` is continuous multilinear and `f` is a collection of continuous linear maps,
then `g (f₁ m₁, ..., fₙ mₙ)` is again a continuous multilinear map, that we call
`g.comp_continuous_linear_map f`. -/
def comp_continuous_linear_map
  (g : continuous_multilinear_map R M₁' M₄) (f : Π i : ι, M₁ i →L[R] M₁' i) :
  continuous_multilinear_map R M₁ M₄ :=
{ cont := g.cont.comp $ continuous_pi $ λj, (f j).cont.comp $ continuous_apply _,
  .. g.to_multilinear_map.comp_linear_map (λ i, (f i).to_linear_map) }

@[simp] lemma comp_continuous_linear_map_apply (g : continuous_multilinear_map R M₁' M₄)
  (f : Π i : ι, M₁ i →L[R] M₁' i) (m : Π i, M₁ i) :
  g.comp_continuous_linear_map f m = g (λ i, f i $ m i) :=
rfl

/-- In the specific case of continuous multilinear maps on spaces indexed by `fin (n+1)`, where one
can build an element of `Π(i : fin (n+1)), M i` using `cons`, one can express directly the
additivity of a multilinear map along the first variable. -/
lemma cons_add (f : continuous_multilinear_map R M M₂) (m : Π(i : fin n), M i.succ) (x y : M 0) :
  f (cons (x+y) m) = f (cons x m) + f (cons y m) :=
f.to_multilinear_map.cons_add m x y

/-- In the specific case of continuous multilinear maps on spaces indexed by `fin (n+1)`, where one
can build an element of `Π(i : fin (n+1)), M i` using `cons`, one can express directly the
multiplicativity of a multilinear map along the first variable. -/
lemma cons_smul
  (f : continuous_multilinear_map R M M₂) (m : Π(i : fin n), M i.succ) (c : R) (x : M 0) :
  f (cons (c • x) m) = c • f (cons x m) :=
f.to_multilinear_map.cons_smul m c x

lemma map_piecewise_add (m m' : Πi, M₁ i) (t : finset ι) :
  f (t.piecewise (m + m') m') = ∑ s in t.powerset, f (s.piecewise m m') :=
f.to_multilinear_map.map_piecewise_add _ _ _

/-- Additivity of a continuous multilinear map along all coordinates at the same time,
writing `f (m + m')` as the sum  of `f (s.piecewise m m')` over all sets `s`. -/
lemma map_add_univ [fintype ι] (m m' : Πi, M₁ i) :
  f (m + m') = ∑ s : finset ι, f (s.piecewise m m') :=
f.to_multilinear_map.map_add_univ _ _

section apply_sum

open fintype finset

variables {α : ι → Type*} [fintype ι] (g : Π i, α i → M₁ i) (A : Π i, finset (α i))

/-- If `f` is continuous multilinear, then `f (Σ_{j₁ ∈ A₁} g₁ j₁, ..., Σ_{jₙ ∈ Aₙ} gₙ jₙ)` is the
sum of `f (g₁ (r 1), ..., gₙ (r n))` where `r` ranges over all functions with `r 1 ∈ A₁`, ...,
`r n ∈ Aₙ`. This follows from multilinearity by expanding successively with respect to each
coordinate. -/

lemma map_sum_finset  :
  f (λ i, ∑ j in A i, g i j) = ∑ r in pi_finset A, f (λ i, g i (r i)) :=
f.to_multilinear_map.map_sum_finset _ _

/-- If `f` is continuous multilinear, then `f (Σ_{j₁} g₁ j₁, ..., Σ_{jₙ} gₙ jₙ)` is the sum of
`f (g₁ (r 1), ..., gₙ (r n))` where `r` ranges over all functions `r`. This follows from
multilinearity by expanding successively with respect to each coordinate. -/
lemma map_sum [∀ i, fintype (α i)] :
  f (λ i, ∑ j, g i j) = ∑ r : Π i, α i, f (λ i, g i (r i)) :=
f.to_multilinear_map.map_sum _

end apply_sum

section restrict_scalar

variables (R) {A : Type*} [semiring A] [has_scalar R A] [Π (i : ι), module A (M₁ i)]
  [module A M₂] [∀ i, is_scalar_tower R A (M₁ i)] [is_scalar_tower R A M₂]

/-- Reinterpret an `A`-multilinear map as an `R`-multilinear map, if `A` is an algebra over `R`
and their actions on all involved modules agree with the action of `R` on `A`. -/
def restrict_scalars (f : continuous_multilinear_map A M₁ M₂) :
  continuous_multilinear_map R M₁ M₂ :=
{ to_multilinear_map := f.to_multilinear_map.restrict_scalars R,
  cont := f.cont }

@[simp] lemma coe_restrict_scalars (f : continuous_multilinear_map A M₁ M₂) :
  ⇑(f.restrict_scalars R) = f := rfl

end restrict_scalar

end semiring

section ring

variables [ring R] [∀i, add_comm_group (M₁ i)] [add_comm_group M₂]
[∀i, module R (M₁ i)] [module R M₂] [∀i, topological_space (M₁ i)] [topological_space M₂]
(f f' : continuous_multilinear_map R M₁ M₂)

@[simp] lemma map_sub (m : Πi, M₁ i) (i : ι) (x y : M₁ i) :
  f (update m i (x - y)) = f (update m i x) - f (update m i y) :=
f.to_multilinear_map.map_sub _ _ _ _

section topological_add_group
variable [topological_add_group M₂]

instance : has_neg (continuous_multilinear_map R M₁ M₂) :=
⟨λ f, {cont := f.cont.neg, ..(-f.to_multilinear_map)}⟩

@[simp] lemma neg_apply (m : Πi, M₁ i) : (-f) m = - (f m) := rfl

instance : has_sub (continuous_multilinear_map R M₁ M₂) :=
⟨λ f g, { cont := f.cont.sub g.cont, .. (f.to_multilinear_map - g.to_multilinear_map) }⟩

@[simp] lemma sub_apply (m : Πi, M₁ i) : (f - f') m = f m - f' m := rfl

instance : add_comm_group (continuous_multilinear_map R M₁ M₂) :=
to_multilinear_map_inj.add_comm_group _ rfl (λ _ _, rfl) (λ _, rfl) (λ _ _, rfl)

end topological_add_group

end ring

section comm_semiring

variables [comm_semiring R]
[∀i, add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
[∀i, module R (M₁ i)] [module R M₂]
[∀i, topological_space (M₁ i)] [topological_space M₂]
(f : continuous_multilinear_map R M₁ M₂)

lemma map_piecewise_smul (c : ι → R) (m : Πi, M₁ i) (s : finset ι) :
  f (s.piecewise (λ i, c i • m i) m) = (∏ i in s, c i) • f m :=
f.to_multilinear_map.map_piecewise_smul _ _ _

/-- Multiplicativity of a continuous multilinear map along all coordinates at the same time,
writing `f (λ i, c i • m i)` as `(∏ i, c i) • f m`. -/
lemma map_smul_univ [fintype ι] (c : ι → R) (m : Πi, M₁ i) :
  f (λ i, c i • m i) = (∏ i, c i) • f m :=
f.to_multilinear_map.map_smul_univ _ _

variables {R' A : Type*} [comm_semiring R'] [semiring A] [algebra R' A]
  [Π i, module A (M₁ i)] [module R' M₂] [module A M₂] [is_scalar_tower R' A M₂]
  [topological_space R'] [has_continuous_smul R' M₂]

instance : has_scalar R' (continuous_multilinear_map A M₁ M₂) :=
⟨λ c f, { cont := continuous_const.smul f.cont, .. c • f.to_multilinear_map }⟩

@[simp] lemma smul_apply (f : continuous_multilinear_map A M₁ M₂) (c : R') (m : Πi, M₁ i) :
  (c • f) m = c • f m := rfl

@[simp] lemma to_multilinear_map_smul (c : R') (f : continuous_multilinear_map A M₁ M₂) :
  (c • f).to_multilinear_map = c • f.to_multilinear_map :=
rfl

instance {R''} [comm_semiring R''] [has_scalar R' R''] [algebra R'' A]
  [module R'' M₂] [is_scalar_tower R'' A M₂] [is_scalar_tower R' R'' M₂]
  [topological_space R''] [has_continuous_smul R'' M₂]:
  is_scalar_tower R' R'' (continuous_multilinear_map A M₁ M₂) :=
⟨λ c₁ c₂ f, ext $ λ x, smul_assoc _ _ _⟩

variable [has_continuous_add M₂]

/-- The space of continuous multilinear maps over an algebra over `R` is a module over `R`, for the
pointwise addition and scalar multiplication. -/
instance : module R' (continuous_multilinear_map A M₁ M₂) :=
{ one_smul := λ f, ext $ λ x, one_smul _ _,
  mul_smul := λ c₁ c₂ f, ext $ λ x, mul_smul _ _ _,
  smul_zero := λ r, ext $ λ x, smul_zero _,
  smul_add := λ r f₁ f₂, ext $ λ x, smul_add _ _ _,
  add_smul := λ r₁ r₂ f, ext $ λ x, add_smul _ _ _,
  zero_smul := λ f, ext $ λ x, zero_smul _ _ }

/-- Linear map version of the map `to_multilinear_map` associating to a continuous multilinear map
the corresponding multilinear map. -/
@[simps] def to_multilinear_map_linear :
  (continuous_multilinear_map A M₁ M₂) →ₗ[R'] (multilinear_map A M₁ M₂) :=
{ to_fun    := λ f, f.to_multilinear_map,
  map_add'  := λ f g, rfl,
  map_smul' := λ c f, rfl }

end comm_semiring

end continuous_multilinear_map

namespace continuous_linear_map
variables [ring R] [∀i, add_comm_group (M₁ i)] [add_comm_group M₂] [add_comm_group M₃]
[∀i, module R (M₁ i)] [module R M₂] [module R M₃]
[∀i, topological_space (M₁ i)] [topological_space M₂] [topological_space M₃]

/-- Composing a continuous multilinear map with a continuous linear map gives again a
continuous multilinear map. -/
def comp_continuous_multilinear_map (g : M₂ →L[R] M₃) (f : continuous_multilinear_map R M₁ M₂) :
  continuous_multilinear_map R M₁ M₃ :=
{ cont := g.cont.comp f.cont,
  .. g.to_linear_map.comp_multilinear_map f.to_multilinear_map }

@[simp] lemma comp_continuous_multilinear_map_coe (g : M₂ →L[R] M₃)
  (f : continuous_multilinear_map R M₁ M₂) :
  ((g.comp_continuous_multilinear_map f) : (Πi, M₁ i) → M₃) =
  (g : M₂ → M₃) ∘ (f : (Πi, M₁ i) → M₂) :=
by { ext m, refl }

end continuous_linear_map


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

import topology.metric_space.basic
import topology.uniform_space.equicontinuity
/-!
# Equicontinuity in metric spaces

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

This files contains various facts about (uniform) equicontinuity in metric spaces. Most
importantly, we prove the usual characterization of equicontinuity of `F` at `x₀` in the case of
(pseudo) metric spaces: `∀ ε > 0, ∃ δ > 0, ∀ x, dist x x₀ < δ → ∀ i, dist (F i x₀) (F i x) < ε`,
and we prove that functions sharing a common (local or global) continuity modulus are
(locally or uniformly) equicontinuous.

## Main statements

* `equicontinuous_at_iff`: characterization of equicontinuity for families of functions between
  (pseudo) metric spaces.
* `equicontinuous_at_of_continuity_modulus`: convenient way to prove equicontinuity at a point of
  a family of functions to a (pseudo) metric space by showing that they share a common *local*
  continuity modulus.
* `uniform_equicontinuous_of_continuity_modulus`: convenient way to prove uniform equicontinuity
  of a family of functions to a (pseudo) metric space by showing that they share a common *global*
  continuity modulus.

## Tags

equicontinuity, continuity modulus
-/

open filter
open_locale topology uniformity

variables {α β ι : Type*} [pseudo_metric_space α]

namespace metric

/-- Characterization of equicontinuity for families of functions taking values in a (pseudo) metric
space. -/
lemma equicontinuous_at_iff_right {ι : Type*} [topological_space β] {F : ι → β → α} {x₀ : β} :
  equicontinuous_at F x₀ ↔ ∀ ε > 0, ∀ᶠ x in 𝓝 x₀, ∀ i, dist (F i x₀) (F i x) < ε :=
uniformity_basis_dist.equicontinuous_at_iff_right

/-- Characterization of equicontinuity for families of functions between (pseudo) metric spaces. -/
lemma equicontinuous_at_iff {ι : Type*} [pseudo_metric_space β] {F : ι → β → α} {x₀ : β} :
  equicontinuous_at F x₀ ↔ ∀ ε > 0, ∃ δ > 0, ∀ x, dist x x₀ < δ → ∀ i, dist (F i x₀) (F i x) < ε :=
nhds_basis_ball.equicontinuous_at_iff uniformity_basis_dist

/-- Reformulation of `equicontinuous_at_iff_pair` for families of functions taking values in a
(pseudo) metric space. -/
protected lemma equicontinuous_at_iff_pair {ι : Type*} [topological_space β] {F : ι → β → α}
  {x₀ : β} :
  equicontinuous_at F x₀ ↔ ∀ ε > 0, ∃ U ∈ 𝓝 x₀, ∀ (x x' ∈ U), ∀ i, dist (F i x) (F i x') < ε :=
begin
  rw equicontinuous_at_iff_pair,
  split; intros H,
  { intros ε hε,
    refine exists_imp_exists (λ V, exists_imp_exists $ λ hV h, _) (H _ (dist_mem_uniformity hε)),
    exact λ x hx x' hx', h _ hx _ hx' },
  { intros U hU,
    rcases mem_uniformity_dist.mp hU with ⟨ε, hε, hεU⟩,
    refine exists_imp_exists (λ V, exists_imp_exists $ λ hV h, _) (H _ hε),
    exact λ x hx x' hx' i, hεU (h _ hx _ hx' i) }
end

/-- Characterization of uniform equicontinuity for families of functions taking values in a
(pseudo) metric space. -/
lemma uniform_equicontinuous_iff_right {ι : Type*} [uniform_space β] {F : ι → β → α} :
  uniform_equicontinuous F ↔
  ∀ ε > 0, ∀ᶠ (xy : β × β) in 𝓤 β, ∀ i, dist (F i xy.1) (F i xy.2) < ε :=
uniformity_basis_dist.uniform_equicontinuous_iff_right

/-- Characterization of uniform equicontinuity for families of functions between
(pseudo) metric spaces. -/
lemma uniform_equicontinuous_iff {ι : Type*} [pseudo_metric_space β] {F : ι → β → α} :
  uniform_equicontinuous F ↔
  ∀ ε > 0, ∃ δ > 0, ∀ x y, dist x y < δ → ∀ i, dist (F i x) (F i y) < ε :=
uniformity_basis_dist.uniform_equicontinuous_iff uniformity_basis_dist

/-- For a family of functions to a (pseudo) metric spaces, a convenient way to prove
equicontinuity at a point is to show that all of the functions share a common *local* continuity
modulus. -/
lemma equicontinuous_at_of_continuity_modulus {ι : Type*} [topological_space β] {x₀ : β}
  (b : β → ℝ)
  (b_lim : tendsto b (𝓝 x₀) (𝓝 0))
  (F : ι → β → α)
  (H : ∀ᶠ x in 𝓝 x₀, ∀ i, dist (F i x₀) (F i x) ≤ b x) :
  equicontinuous_at F x₀ :=
begin
  rw metric.equicontinuous_at_iff_right,
  intros ε ε0,
  filter_upwards [b_lim (Iio_mem_nhds ε0), H] using λ x hx₁ hx₂ i, (hx₂ i).trans_lt hx₁
end

/-- For a family of functions between (pseudo) metric spaces, a convenient way to prove
uniform equicontinuity is to show that all of the functions share a common *global* continuity
modulus. -/
lemma uniform_equicontinuous_of_continuity_modulus {ι : Type*} [pseudo_metric_space β] (b : ℝ → ℝ)
  (b_lim : tendsto b (𝓝 0) (𝓝 0))
  (F : ι → β → α)
  (H : ∀ (x y : β) i, dist (F i x) (F i y) ≤ b (dist x y)) :
  uniform_equicontinuous F :=
begin
  rw metric.uniform_equicontinuous_iff,
  intros ε ε0,
  rcases tendsto_nhds_nhds.1 b_lim ε ε0 with ⟨δ, δ0, hδ⟩,
  refine ⟨δ, δ0, λ x y hxy i, _⟩,
  calc
    dist (F i x) (F i y) ≤ b (dist x y) : H x y i
    ... ≤ |b (dist x y)| : le_abs_self _
    ... = dist (b (dist x y)) 0 : by simp [real.dist_eq]
    ... < ε : hδ (by simpa only [real.dist_eq, tsub_zero, abs_dist] using hxy)
end

/-- For a family of functions between (pseudo) metric spaces, a convenient way to prove
equicontinuity is to show that all of the functions share a common *global* continuity modulus. -/
lemma equicontinuous_of_continuity_modulus {ι : Type*} [pseudo_metric_space β] (b : ℝ → ℝ)
  (b_lim : tendsto b (𝓝 0) (𝓝 0))
  (F : ι → β → α)
  (H : ∀ (x y : β) i, dist (F i x) (F i y) ≤ b (dist x y)) :
  equicontinuous F :=
(uniform_equicontinuous_of_continuity_modulus b b_lim F H).equicontinuous

end metric


/-
Copyright (c) 2019 Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot

! This file was ported from Lean 3 source module topology.uniform_space.abstract_completion
! leanprover-community/mathlib commit dc6c365e751e34d100e80fe6e314c3c3e0fd2988
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Topology.UniformSpace.UniformEmbedding
import Mathlib.Topology.UniformSpace.Equiv

/-!
# Abstract theory of Hausdorff completions of uniform spaces

This file characterizes Hausdorff completions of a uniform space α as complete Hausdorff spaces
equipped with a map from α which has dense image and induce the original uniform structure on α.
Assuming these properties we "extend" uniformly continuous maps from α to complete Hausdorff spaces
to the completions of α. This is the universal property expected from a completion.
It is then used to extend uniformly continuous maps from α to α' to maps between
completions of α and α'.

This file does not construct any such completion, it only study consequences of their existence.
The first advantage is that formal properties are clearly highlighted without interference from
construction details. The second advantage is that this framework can then be used to compare
different completion constructions. See `Topology/UniformSpace/CompareReals` for an example.
Of course the comparison comes from the universal property as usual.

A general explicit construction of completions is done in `UniformSpace/Completion`, leading
to a functor from uniform spaces to complete Hausdorff uniform spaces that is left adjoint to the
inclusion, see `UniformSpace/UniformSpaceCat` for the category packaging.

## Implementation notes

A tiny technical advantage of using a characteristic predicate such as the properties listed in
`AbstractCompletion` instead of stating the universal property is that the universal property
derived from the predicate is more universe polymorphic.

## References

We don't know any traditional text discussing this. Real world mathematics simply silently
identify the results of any two constructions that lead to something one could reasonably
call a completion.

## Tags

uniform spaces, completion, universal property
-/

noncomputable section

attribute [local instance] Classical.propDecidable

open Filter Set Function

universe u

/-- A completion of `α` is the data of a complete separated uniform space (from the same universe)
and a map from `α` with dense range and inducing the original uniform structure on `α`. -/
structure AbstractCompletion (α : Type u) [UniformSpace α] where
  /-- The underlying space of the completion. -/
  space : Type u
  /-- A map from a space to its completion. -/
  coe : α → space
  /-- The completion carries a uniform structure. -/
  uniformStruct : UniformSpace space
  /-- The completion is complete. -/
  complete : CompleteSpace space
  /-- The completion is a separated space. -/
  separation : SeparatedSpace space
  /-- The map into the completion is uniform-inducing. -/
  uniformInducing : UniformInducing coe
  /-- The map into the completion has dense range. -/
  dense : DenseRange coe
#align abstract_completion AbstractCompletion

attribute [local instance]
  AbstractCompletion.uniformStruct AbstractCompletion.complete AbstractCompletion.separation

namespace AbstractCompletion

variable {α : Type _} [UniformSpace α] (pkg : AbstractCompletion α)

local notation "hatα" => pkg.space

local notation "ι" => pkg.coe

/-- If `α` is complete, then it is an abstract completion of itself. -/
def ofComplete [SeparatedSpace α] [CompleteSpace α] : AbstractCompletion α :=
  mk α id inferInstance inferInstance inferInstance uniformInducing_id denseRange_id
#align abstract_completion.of_complete AbstractCompletion.ofComplete

theorem closure_range : closure (range ι) = univ :=
  pkg.dense.closure_range
#align abstract_completion.closure_range AbstractCompletion.closure_range

theorem denseInducing : DenseInducing ι :=
  ⟨pkg.uniformInducing.inducing, pkg.dense⟩
#align abstract_completion.dense_inducing AbstractCompletion.denseInducing

theorem uniformContinuous_coe : UniformContinuous ι :=
  UniformInducing.uniformContinuous pkg.uniformInducing
#align abstract_completion.uniform_continuous_coe AbstractCompletion.uniformContinuous_coe

theorem continuous_coe : Continuous ι :=
  pkg.uniformContinuous_coe.continuous
#align abstract_completion.continuous_coe AbstractCompletion.continuous_coe

@[elab_as_elim]
theorem induction_on {p : hatα → Prop} (a : hatα) (hp : IsClosed { a | p a }) (ih : ∀ a, p (ι a)) :
    p a :=
  isClosed_property pkg.dense hp ih a
#align abstract_completion.induction_on AbstractCompletion.induction_on

variable {β : Type _}

protected theorem funext [TopologicalSpace β] [T2Space β] {f g : hatα → β} (hf : Continuous f)
    (hg : Continuous g) (h : ∀ a, f (ι a) = g (ι a)) : f = g :=
  funext fun a => pkg.induction_on a (isClosed_eq hf hg) h
#align abstract_completion.funext AbstractCompletion.funext

variable [UniformSpace β]

section Extend

/-- Extension of maps to completions -/
protected def extend (f : α → β) : hatα → β :=
  if UniformContinuous f then pkg.denseInducing.extend f else fun x => f (pkg.dense.some x)
#align abstract_completion.extend AbstractCompletion.extend

variable {f : α → β}

theorem extend_def (hf : UniformContinuous f) : pkg.extend f = pkg.denseInducing.extend f :=
  if_pos hf
#align abstract_completion.extend_def AbstractCompletion.extend_def

theorem extend_coe [T2Space β] (hf : UniformContinuous f) (a : α) : (pkg.extend f) (ι a) = f a := by
  rw [pkg.extend_def hf]
  exact pkg.denseInducing.extend_eq hf.continuous a
#align abstract_completion.extend_coe AbstractCompletion.extend_coe

variable [CompleteSpace β]

theorem uniformContinuous_extend : UniformContinuous (pkg.extend f) := by
  by_cases hf : UniformContinuous f
  · rw [pkg.extend_def hf]
    exact uniformContinuous_uniformly_extend pkg.uniformInducing pkg.dense hf
  · change UniformContinuous (ite _ _ _)
    rw [if_neg hf]
    exact uniformContinuous_of_const fun a b => by congr 1
#align abstract_completion.uniform_continuous_extend AbstractCompletion.uniformContinuous_extend

theorem continuous_extend : Continuous (pkg.extend f) :=
  pkg.uniformContinuous_extend.continuous
#align abstract_completion.continuous_extend AbstractCompletion.continuous_extend

variable [SeparatedSpace β]

theorem extend_unique (hf : UniformContinuous f) {g : hatα → β} (hg : UniformContinuous g)
    (h : ∀ a : α, f a = g (ι a)) : pkg.extend f = g := by
  apply pkg.funext pkg.continuous_extend hg.continuous
  simpa only [pkg.extend_coe hf] using h
#align abstract_completion.extend_unique AbstractCompletion.extend_unique

@[simp]
theorem extend_comp_coe {f : hatα → β} (hf : UniformContinuous f) : pkg.extend (f ∘ ι) = f :=
  funext fun x =>
    pkg.induction_on x (isClosed_eq pkg.continuous_extend hf.continuous) fun y =>
      pkg.extend_coe (hf.comp <| pkg.uniformContinuous_coe) y
#align abstract_completion.extend_comp_coe AbstractCompletion.extend_comp_coe

end Extend

section MapSec

variable (pkg' : AbstractCompletion β)

local notation "hatβ" => pkg'.space

local notation "ι'" => pkg'.coe

/-- Lifting maps to completions -/
protected def map (f : α → β) : hatα → hatβ :=
  pkg.extend (ι' ∘ f)
#align abstract_completion.map AbstractCompletion.map

local notation "map" => pkg.map pkg'

variable (f : α → β)

theorem uniformContinuous_map : UniformContinuous (map f) :=
  pkg.uniformContinuous_extend
#align abstract_completion.uniform_continuous_map AbstractCompletion.uniformContinuous_map

@[continuity]
theorem continuous_map : Continuous (map f) :=
  pkg.continuous_extend
#align abstract_completion.continuous_map AbstractCompletion.continuous_map

variable {f}

@[simp]
theorem map_coe (hf : UniformContinuous f) (a : α) : map f (ι a) = ι' (f a) :=
  pkg.extend_coe (pkg'.uniformContinuous_coe.comp hf) a
#align abstract_completion.map_coe AbstractCompletion.map_coe

theorem map_unique {f : α → β} {g : hatα → hatβ} (hg : UniformContinuous g)
    (h : ∀ a, ι' (f a) = g (ι a)) : map f = g :=
  pkg.funext (pkg.continuous_map _ _) hg.continuous <|
    by
    intro a
    change pkg.extend (ι' ∘ f) _ = _
    simp only [(· ∘ ·), h, ←comp_apply (f := g)]
    rw [pkg.extend_coe (hg.comp pkg.uniformContinuous_coe)]
#align abstract_completion.map_unique AbstractCompletion.map_unique

@[simp]
theorem map_id : pkg.map pkg id = id :=
  pkg.map_unique pkg uniformContinuous_id fun _ => rfl
#align abstract_completion.map_id AbstractCompletion.map_id

variable {γ : Type _} [UniformSpace γ]

theorem extend_map [CompleteSpace γ] [SeparatedSpace γ] {f : β → γ} {g : α → β}
    (hf : UniformContinuous f) (hg : UniformContinuous g) :
    pkg'.extend f ∘ map g = pkg.extend (f ∘ g) :=
  pkg.funext (pkg'.continuous_extend.comp (pkg.continuous_map pkg' _)) pkg.continuous_extend
    fun a => by
    rw [pkg.extend_coe (hf.comp hg), comp_apply, pkg.map_coe pkg' hg, pkg'.extend_coe hf]
    rfl
#align abstract_completion.extend_map AbstractCompletion.extend_map

variable (pkg'' : AbstractCompletion γ)

theorem map_comp {g : β → γ} {f : α → β} (hg : UniformContinuous g) (hf : UniformContinuous f) :
    pkg'.map pkg'' g ∘ pkg.map pkg' f = pkg.map pkg'' (g ∘ f) :=
  pkg.extend_map pkg' (pkg''.uniformContinuous_coe.comp hg) hf
#align abstract_completion.map_comp AbstractCompletion.map_comp

end MapSec

section Compare

-- We can now compare two completion packages for the same uniform space
variable (pkg' : AbstractCompletion α)

/-- The comparison map between two completions of the same uniform space. -/
def compare : pkg.space → pkg'.space :=
  pkg.extend pkg'.coe
#align abstract_completion.compare AbstractCompletion.compare

theorem uniformContinuous_compare : UniformContinuous (pkg.compare pkg') :=
  pkg.uniformContinuous_extend
#align abstract_completion.uniform_continuous_compare AbstractCompletion.uniformContinuous_compare

theorem compare_coe (a : α) : pkg.compare pkg' (pkg.coe a) = pkg'.coe a :=
  pkg.extend_coe pkg'.uniformContinuous_coe a
#align abstract_completion.compare_coe AbstractCompletion.compare_coe

theorem inverse_compare : pkg.compare pkg' ∘ pkg'.compare pkg = id := by
  have uc := pkg.uniformContinuous_compare pkg'
  have uc' := pkg'.uniformContinuous_compare pkg
  apply pkg'.funext (uc.comp uc').continuous continuous_id
  intro a
  rw [comp_apply, pkg'.compare_coe pkg, pkg.compare_coe pkg']
  rfl
#align abstract_completion.inverse_compare AbstractCompletion.inverse_compare

/-- The uniform bijection between two completions of the same uniform space. -/
def compareEquiv : pkg.space ≃ᵤ pkg'.space
    where
  toFun := pkg.compare pkg'
  invFun := pkg'.compare pkg
  left_inv := congr_fun (pkg'.inverse_compare pkg)
  right_inv := congr_fun (pkg.inverse_compare pkg')
  uniformContinuous_toFun := uniformContinuous_compare _ _
  uniformContinuous_invFun := uniformContinuous_compare _ _
#align abstract_completion.compare_equiv AbstractCompletion.compareEquiv

theorem uniformContinuous_compareEquiv : UniformContinuous (pkg.compareEquiv pkg') :=
  pkg.uniformContinuous_compare pkg'
#align abstract_completion.uniform_continuous_compare_equiv AbstractCompletion.uniformContinuous_compareEquiv

theorem uniformContinuous_compareEquiv_symm : UniformContinuous (pkg.compareEquiv pkg').symm :=
  pkg'.uniformContinuous_compare pkg
#align abstract_completion.uniform_continuous_compare_equiv_symm AbstractCompletion.uniformContinuous_compareEquiv_symm

end Compare

section Prod

variable (pkg' : AbstractCompletion β)

local notation "hatβ" => pkg'.space

local notation "ι'" => pkg'.coe

/-- Products of completions -/
protected def prod : AbstractCompletion (α × β)
    where
  space := hatα × hatβ
  coe p := ⟨ι p.1, ι' p.2⟩
  uniformStruct := inferInstance
  complete := inferInstance
  separation := inferInstance
  uniformInducing := UniformInducing.prod pkg.uniformInducing pkg'.uniformInducing
  dense := DenseRange.prod_map pkg.dense pkg'.dense
#align abstract_completion.prod AbstractCompletion.prod

end Prod

section Extension₂

variable (pkg' : AbstractCompletion β)

local notation "hatβ" => pkg'.space

local notation "ι'" => pkg'.coe

variable {γ : Type _} [UniformSpace γ]

open Function

/-- Extend two variable map to completions. -/
protected def extend₂ (f : α → β → γ) : hatα → hatβ → γ :=
  curry <| (pkg.prod pkg').extend (uncurry f)
#align abstract_completion.extend₂ AbstractCompletion.extend₂

section SeparatedSpace

variable [SeparatedSpace γ] {f : α → β → γ}

theorem extension₂_coe_coe (hf : UniformContinuous <| uncurry f) (a : α) (b : β) :
    pkg.extend₂ pkg' f (ι a) (ι' b) = f a b :=
  show (pkg.prod pkg').extend (uncurry f) ((pkg.prod pkg').coe (a, b)) = uncurry f (a, b) from
    (pkg.prod pkg').extend_coe hf _
#align abstract_completion.extension₂_coe_coe AbstractCompletion.extension₂_coe_coe

end SeparatedSpace

variable {f : α → β → γ}

variable [CompleteSpace γ] (f)

theorem uniformContinuous_extension₂ : UniformContinuous₂ (pkg.extend₂ pkg' f) := by
  rw [uniformContinuous₂_def, AbstractCompletion.extend₂, uncurry_curry]
  apply uniformContinuous_extend
#align abstract_completion.uniform_continuous_extension₂ AbstractCompletion.uniformContinuous_extension₂

end Extension₂

section Map₂

variable (pkg' : AbstractCompletion β)

local notation "hatβ" => pkg'.space

local notation "ι'" => pkg'.coe

variable {γ : Type _} [UniformSpace γ] (pkg'' : AbstractCompletion γ)

local notation "hatγ" => pkg''.space

local notation "ι''" => pkg''.coe

local notation f " ∘₂ " g => bicompr f g

/-- Lift two variable maps to completions. -/
protected def map₂ (f : α → β → γ) : hatα → hatβ → hatγ :=
  pkg.extend₂ pkg' (pkg''.coe ∘₂ f)
#align abstract_completion.map₂ AbstractCompletion.map₂

theorem uniformContinuous_map₂ (f : α → β → γ) : UniformContinuous₂ (pkg.map₂ pkg' pkg'' f) :=
  AbstractCompletion.uniformContinuous_extension₂ pkg pkg' _
#align abstract_completion.uniform_continuous_map₂ AbstractCompletion.uniformContinuous_map₂

theorem continuous_map₂ {δ} [TopologicalSpace δ] {f : α → β → γ} {a : δ → hatα} {b : δ → hatβ}
    (ha : Continuous a) (hb : Continuous b) :
    Continuous fun d : δ => pkg.map₂ pkg' pkg'' f (a d) (b d) :=
  ((pkg.uniformContinuous_map₂ pkg' pkg'' f).continuous.comp (Continuous.prod_mk ha hb) : _)
#align abstract_completion.continuous_map₂ AbstractCompletion.continuous_map₂

end Map₂

end AbstractCompletion


import ConjInvLength.LengthBound
open Letter

structure ProvedSplit (l: Letter)(w : Word) where
  fst : Word
  snd: Word
  proof : w = fst ++ [l] ++ snd

-- Split with first piece empty when head matches the splitting letter.
def ProvedSplit.head (x: Letter) (ys: Word) : ProvedSplit x (x :: ys) :=
  ⟨[], ys, rfl⟩

-- Prepend to a proved split of the tail (`l` and `ys` implicit).
def ProvedSplit.prepend{l: Letter}{ys : Word} (x: Letter)
        (ps: ProvedSplit l ys) : ProvedSplit l (x :: ys) :=
      let newFst := x :: ps.fst
      let newSnd := ps.snd
      have newProof : x :: ys = newFst ++ [l] ++ newSnd  :=
        by
          rw [ps.proof]
          simp
      ⟨newFst, newSnd, newProof⟩

-- all proved splits of a word
def provedSplits(z: Letter) : (w : Word) → List (ProvedSplit z w)
  | [] => []
  | x :: ys =>
    let tailSplits := (provedSplits z ys).map (ProvedSplit.prepend x)
    if c:x = z then
      let headSplit : ProvedSplit z (x :: ys) := by rw [c] ; exact ProvedSplit.head z ys
      headSplit :: tailSplits
    else tailSplits

abbrev Length := Word → Nat

def conjInv(l: Length) : Prop := (x : Letter) → (g : Word) → l (g^x) = l (g)

def triangIneq(l: Length) : Prop := (g h : Word) → l (g ++ h) ≤ l g + l h

def normalized(l: Length) : Prop := (x : Letter) → l [x] = 1

def emptyWord(l: Length) : Prop := l [] = 0

structure ProvedBound(g: Word):  Type where
  bound: Nat
  pf : (l: Length) → emptyWord l →
           normalized l → conjInv l → triangIneq l → l g ≤ bound

theorem conj_split (x: Letter) (ys fst snd: Word) :
          ys = fst ++ [x⁻¹] ++ snd → x :: ys = fst^x ++ snd :=
            by
              intro hyp
              have expand : fst^x = [x] ++ fst ++ [x⁻¹] := rfl
              rw [expand]
              rw [hyp]
              simp

-- deducing bound using `l (xh₁x⁻¹h₂) ≤ b₁ + b₂` given `l (hᵢ) ≤ bᵢ`, `i = 1, 2`
def ProvedBound.headMatches(x: Letter)(ys fst snd: Word)
  (eqn : ys = fst ++ [x⁻¹] ++ snd) :
  ProvedBound fst → ProvedBound snd → ProvedBound (x :: ys) :=
    fun pb1 pb2 =>
    let bound := pb1.bound + pb2.bound
    let pf :
      (l: Length) → emptyWord l → normalized l → conjInv l → triangIneq l →
          l (x :: ys) ≤ bound := by
            intros l emp norm conj triang
            rw [conj_split x ys fst snd eqn]
            have lem : l (fst ^ x ++ snd) ≤ l (fst^x) + l snd := by apply triang
            have clem : l (fst^x) = l fst := by apply conj
            rw [clem] at lem
            apply Nat.le_trans lem
            have l1 : l fst ≤ pb1.bound := pb1.pf l emp norm conj triang
            have l2 : l snd ≤ pb2.bound := pb2.pf l emp norm conj triang
            apply Nat.add_le_add l1 l2
    ⟨bound, pf⟩

-- deducing `l(xh) ≤ b + 1` given `l(h) ≤ b`
def ProvedBound.prepend{w : Word} (x: Letter)
        (ps: ProvedBound w) : ProvedBound (x :: w) :=
      let newBound := ps.bound + 1
      ⟨newBound, fun l emp norm conj triang => by
        let prev := ps.pf l emp norm conj triang
        have lemTri : l (x :: w) ≤ l ([x]) + l w := by apply triang [x]
        apply Nat.le_trans lemTri
        rw [norm x]
        rw [Nat.add_comm]
        apply Nat.add_le_add_right prev⟩

-- `l(e) ≤ 0`
def ProvedBound.emptyWord: ProvedBound [] :=
  ⟨0, fun l emp _ _ _ => by rw [emp]⟩

-- the best proved bound for a word
def ProvedBound.min {w: Word}: ProvedBound w → List (ProvedBound w) →
    ProvedBound w :=
        fun head tail =>
          tail.foldl (fun pb1 pb2 =>
            if pb1.bound ≤ pb2.bound then pb1 else pb2) head

theorem splitFirst{l: Letter}{w: Word}(ps: ProvedSplit l w):
          ps.fst.length + 1 ≤ w.length :=
          by
            let lem : (ps.fst ++ [l] ++ ps.snd).length =
                (ps.fst ++ [l]).length + ps.snd.length := by apply List.length_append
            rw [← ps.proof] at lem
            rw [lem]
            have lem2 : List.length (ps.fst ++ [l]) = ps.fst.length + 1 := by
                apply List.length_append
            rw [lem2]
            apply Nat.le_add_right

theorem splitSecond{l: Letter}{w: Word}(ps: ProvedSplit l w):
          ps.snd.length + 1 ≤ w.length :=
          by
            let lem : (ps.fst ++ [l] ++ ps.snd).length =
                (ps.fst ++ [l]).length + ps.snd.length := by apply List.length_append
            rw [← ps.proof] at lem
            rw [lem]
            have lem2 : List.length (ps.fst ++ [l]) = ps.fst.length + 1 := by
                apply List.length_append
            rw [lem2]
            rw [Nat.add_assoc]
            rw [Nat.add_comm]
            apply Nat.le_add_left

-- bound with proof for words
def provedBound : (w: Word) → ProvedBound w := fun w =>
  match h:w with
  | [] => ProvedBound.emptyWord
  | x :: ys =>
    have lb : (List.length ys) < List.length (x :: ys) := by
      simp [List.length_cons, Nat.le_refl]
    let head := ProvedBound.prepend x (provedBound ys)
    let splits := provedSplits x⁻¹  ys
    have wl:  w.length = ys.length + 1 := by
      rw[h]
      rfl
    let tail := splits.map (fun ps =>
      have l1 : ps.fst.length + 1 ≤ ys.length + 1 :=
        by
        have lm := splitFirst ps
        apply Nat.le_trans lm
        apply Nat.le_succ
      have l2 : ps.snd.length + 1 ≤ ys.length + 1 :=
        by
        have lm := splitSecond ps
        apply Nat.le_trans lm
        apply Nat.le_succ
      ProvedBound.headMatches x ys ps.fst ps.snd ps.proof
        (provedBound ps.fst) (provedBound ps.snd))
    ProvedBound.min head tail
termination_by  _ w => w.length
decreasing_by assumption

#eval (provedBound ([α, α, β, α!, β!])).bound

#eval (provedBound ([α, α, β, α!, β!]^2)).bound


import data.int.basic
import tactic

variables (a b c : ℤ)

example (h1 : a ∣ b) (h2 : a ∣ c) : a ∣ b+c :=
begin
  cases h1 with k1 hk1,
  rw hk1,
  cases h2 with k2 hk2,
  rw hk2,
  use k1+k2,
  ring,
end


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Sébastien Gouëzel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.metric_space.isometry
import Mathlib.topology.instances.ennreal
import Mathlib.PostPort

universes u v

namespace Mathlib

/-!
# Hausdorff distance

The Hausdorff distance on subsets of a metric (or emetric) space.

Given two subsets `s` and `t` of a metric space, their Hausdorff distance is the smallest `d`
such that any point `s` is within `d` of a point in `t`, and conversely. This quantity
is often infinite (think of `s` bounded and `t` unbounded), and therefore better
expressed in the setting of emetric spaces.

## Main definitions

This files introduces:
* `inf_edist x s`, the infimum edistance of a point `x` to a set `s` in an emetric space
* `Hausdorff_edist s t`, the Hausdorff edistance of two sets in an emetric space
* Versions of these notions on metric spaces, called respectively `inf_dist` and
`Hausdorff_dist`.
-/

namespace emetric

/-! ### Distance of a point to a set as a function into `ennreal`. -/

/-- The minimal edistance of a point to a set -/
def inf_edist {α : Type u} [emetric_space α] (x : α) (s : set α) : ennreal := Inf (edist x '' s)

@[simp] theorem inf_edist_empty {α : Type u} [emetric_space α] {x : α} : inf_edist x ∅ = ⊤ := sorry

/-- The edist to a union is the minimum of the edists -/
@[simp] theorem inf_edist_union {α : Type u} [emetric_space α] {x : α} {s : set α} {t : set α} :
    inf_edist x (s ∪ t) = inf_edist x s ⊓ inf_edist x t :=
  sorry

/-- The edist to a singleton is the edistance to the single point of this singleton -/
@[simp] theorem inf_edist_singleton {α : Type u} [emetric_space α] {x : α} {y : α} :
    inf_edist x (singleton y) = edist x y :=
  sorry

/-- The edist to a set is bounded above by the edist to any of its points -/
theorem inf_edist_le_edist_of_mem {α : Type u} [emetric_space α] {x : α} {y : α} {s : set α}
    (h : y ∈ s) : inf_edist x s ≤ edist x y :=
  Inf_le
    (iff.mpr (set.mem_image (edist x) s (edist x y))
      (Exists.intro y { left := h, right := Eq.refl (edist x y) }))

/-- If a point `x` belongs to `s`, then its edist to `s` vanishes -/
theorem inf_edist_zero_of_mem {α : Type u} [emetric_space α] {x : α} {s : set α} (h : x ∈ s) :
    inf_edist x s = 0 :=
  iff.mp nonpos_iff_eq_zero (edist_self x ▸ inf_edist_le_edist_of_mem h)

/-- The edist is monotonous with respect to inclusion -/
theorem inf_edist_le_inf_edist_of_subset {α : Type u} [emetric_space α] {x : α} {s : set α}
    {t : set α} (h : s ⊆ t) : inf_edist x t ≤ inf_edist x s :=
  Inf_le_Inf (set.image_subset (edist x) h)

/-- If the edist to a set is `< r`, there exists a point in the set at edistance `< r` -/
theorem exists_edist_lt_of_inf_edist_lt {α : Type u} [emetric_space α] {x : α} {s : set α}
    {r : ennreal} (h : inf_edist x s < r) : ∃ (y : α), ∃ (H : y ∈ s), edist x y < r :=
  sorry

/-- The edist of `x` to `s` is bounded by the sum of the edist of `y` to `s` and
the edist from `x` to `y` -/
theorem inf_edist_le_inf_edist_add_edist {α : Type u} [emetric_space α] {x : α} {y : α}
    {s : set α} : inf_edist x s ≤ inf_edist y s + edist x y :=
  sorry

/-- The edist to a set depends continuously on the point -/
theorem continuous_inf_edist {α : Type u} [emetric_space α] {s : set α} :
    continuous fun (x : α) => inf_edist x s :=
  sorry

/-- The edist to a set and to its closure coincide -/
theorem inf_edist_closure {α : Type u} [emetric_space α] {x : α} {s : set α} :
    inf_edist x (closure s) = inf_edist x s :=
  sorry

/-- A point belongs to the closure of `s` iff its infimum edistance to this set vanishes -/
theorem mem_closure_iff_inf_edist_zero {α : Type u} [emetric_space α] {x : α} {s : set α} :
    x ∈ closure s ↔ inf_edist x s = 0 :=
  sorry

/-- Given a closed set `s`, a point belongs to `s` iff its infimum edistance to this set vanishes -/
theorem mem_iff_ind_edist_zero_of_closed {α : Type u} [emetric_space α] {x : α} {s : set α}
    (h : is_closed s) : x ∈ s ↔ inf_edist x s = 0 :=
  sorry

/-- The infimum edistance is invariant under isometries -/
theorem inf_edist_image {α : Type u} {β : Type v} [emetric_space α] [emetric_space β] {x : α}
    {t : set α} {Φ : α → β} (hΦ : isometry Φ) : inf_edist (Φ x) (Φ '' t) = inf_edist x t :=
  sorry

/-! ### The Hausdorff distance as a function into `ennreal`. -/

/-- The Hausdorff edistance between two sets is the smallest `r` such that each set
is contained in the `r`-neighborhood of the other one -/
def Hausdorff_edist {α : Type u} [emetric_space α] (s : set α) (t : set α) : ennreal :=
  Sup ((fun (x : α) => inf_edist x t) '' s) ⊔ Sup ((fun (x : α) => inf_edist x s) '' t)

theorem Hausdorff_edist_def {α : Type u} [emetric_space α] (s : set α) (t : set α) :
    Hausdorff_edist s t =
        Sup ((fun (x : α) => inf_edist x t) '' s) ⊔ Sup ((fun (x : α) => inf_edist x s) '' t) :=
  rfl

/-- The Hausdorff edistance of a set to itself vanishes -/
@[simp] theorem Hausdorff_edist_self {α : Type u} [emetric_space α] {s : set α} :
    Hausdorff_edist s s = 0 :=
  sorry

/-- The Haudorff edistances of `s` to `t` and of `t` to `s` coincide -/
theorem Hausdorff_edist_comm {α : Type u} [emetric_space α] {s : set α} {t : set α} :
    Hausdorff_edist s t = Hausdorff_edist t s :=
  sorry

/-- Bounding the Hausdorff edistance by bounding the edistance of any point
in each set to the other set -/
theorem Hausdorff_edist_le_of_inf_edist {α : Type u} [emetric_space α] {s : set α} {t : set α}
    {r : ennreal} (H1 : ∀ (x : α), x ∈ s → inf_edist x t ≤ r)
    (H2 : ∀ (x : α), x ∈ t → inf_edist x s ≤ r) : Hausdorff_edist s t ≤ r :=
  sorry

/-- Bounding the Hausdorff edistance by exhibiting, for any point in each set,
another point in the other set at controlled distance -/
theorem Hausdorff_edist_le_of_mem_edist {α : Type u} [emetric_space α] {s : set α} {t : set α}
    {r : ennreal} (H1 : ∀ (x : α) (H : x ∈ s), ∃ (y : α), ∃ (H : y ∈ t), edist x y ≤ r)
    (H2 : ∀ (x : α) (H : x ∈ t), ∃ (y : α), ∃ (H : y ∈ s), edist x y ≤ r) :
    Hausdorff_edist s t ≤ r :=
  sorry

/-- The distance to a set is controlled by the Hausdorff distance -/
theorem inf_edist_le_Hausdorff_edist_of_mem {α : Type u} [emetric_space α] {x : α} {s : set α}
    {t : set α} (h : x ∈ s) : inf_edist x t ≤ Hausdorff_edist s t :=
  eq.mpr (id (Eq._oldrec (Eq.refl (inf_edist x t ≤ Hausdorff_edist s t)) (Hausdorff_edist_def s t)))
    (le_trans (le_Sup (set.mem_image_of_mem (fun (x : α) => inf_edist x t) h)) le_sup_left)

/-- If the Hausdorff distance is `<r`, then any point in one of the sets has
a corresponding point at distance `<r` in the other set -/
theorem exists_edist_lt_of_Hausdorff_edist_lt {α : Type u} [emetric_space α] {x : α} {s : set α}
    {t : set α} {r : ennreal} (h : x ∈ s) (H : Hausdorff_edist s t < r) :
    ∃ (y : α), ∃ (H : y ∈ t), edist x y < r :=
  exists_edist_lt_of_inf_edist_lt
    (lt_of_le_of_lt
      (le_trans (le_Sup (set.mem_image_of_mem (fun (x : α) => inf_edist x t) h)) le_sup_left)
      (eq.mp (Eq._oldrec (Eq.refl (Hausdorff_edist s t < r)) (Hausdorff_edist_def s t)) H))

/-- The distance from `x` to `s` or `t` is controlled in terms of the Hausdorff distance
between `s` and `t` -/
theorem inf_edist_le_inf_edist_add_Hausdorff_edist {α : Type u} [emetric_space α] {x : α}
    {s : set α} {t : set α} : inf_edist x t ≤ inf_edist x s + Hausdorff_edist s t :=
  sorry

/-- The Hausdorff edistance is invariant under eisometries -/
theorem Hausdorff_edist_image {α : Type u} {β : Type v} [emetric_space α] [emetric_space β]
    {s : set α} {t : set α} {Φ : α → β} (h : isometry Φ) :
    Hausdorff_edist (Φ '' s) (Φ '' t) = Hausdorff_edist s t :=
  sorry

/-- The Hausdorff distance is controlled by the diameter of the union -/
theorem Hausdorff_edist_le_ediam {α : Type u} [emetric_space α] {s : set α} {t : set α}
    (hs : set.nonempty s) (ht : set.nonempty t) : Hausdorff_edist s t ≤ diam (s ∪ t) :=
  sorry

/-- The Hausdorff distance satisfies the triangular inequality -/
theorem Hausdorff_edist_triangle {α : Type u} [emetric_space α] {s : set α} {t : set α}
    {u : set α} : Hausdorff_edist s u ≤ Hausdorff_edist s t + Hausdorff_edist t u :=
  sorry

/-- The Hausdorff edistance between a set and its closure vanishes -/
@[simp] theorem Hausdorff_edist_self_closure {α : Type u} [emetric_space α] {s : set α} :
    Hausdorff_edist s (closure s) = 0 :=
  sorry

/-- Replacing a set by its closure does not change the Hausdorff edistance. -/
@[simp] theorem Hausdorff_edist_closure₁ {α : Type u} [emetric_space α] {s : set α} {t : set α} :
    Hausdorff_edist (closure s) t = Hausdorff_edist s t :=
  sorry

/-- Replacing a set by its closure does not change the Hausdorff edistance. -/
@[simp] theorem Hausdorff_edist_closure₂ {α : Type u} [emetric_space α] {s : set α} {t : set α} :
    Hausdorff_edist s (closure t) = Hausdorff_edist s t :=
  sorry

/-- The Hausdorff edistance between sets or their closures is the same -/
@[simp] theorem Hausdorff_edist_closure {α : Type u} [emetric_space α] {s : set α} {t : set α} :
    Hausdorff_edist (closure s) (closure t) = Hausdorff_edist s t :=
  sorry

/-- Two sets are at zero Hausdorff edistance if and only if they have the same closure -/
theorem Hausdorff_edist_zero_iff_closure_eq_closure {α : Type u} [emetric_space α] {s : set α}
    {t : set α} : Hausdorff_edist s t = 0 ↔ closure s = closure t :=
  sorry

/-- Two closed sets are at zero Hausdorff edistance if and only if they coincide -/
theorem Hausdorff_edist_zero_iff_eq_of_closed {α : Type u} [emetric_space α] {s : set α} {t : set α}
    (hs : is_closed s) (ht : is_closed t) : Hausdorff_edist s t = 0 ↔ s = t :=
  sorry

/-- The Haudorff edistance to the empty set is infinite -/
theorem Hausdorff_edist_empty {α : Type u} [emetric_space α] {s : set α} (ne : set.nonempty s) :
    Hausdorff_edist s ∅ = ⊤ :=
  sorry

/-- If a set is at finite Hausdorff edistance of a nonempty set, it is nonempty -/
theorem nonempty_of_Hausdorff_edist_ne_top {α : Type u} [emetric_space α] {s : set α} {t : set α}
    (hs : set.nonempty s) (fin : Hausdorff_edist s t ≠ ⊤) : set.nonempty t :=
  or.elim (set.eq_empty_or_nonempty t)
    (fun (ht : t = ∅) => false.elim (fin (Eq.symm ht ▸ Hausdorff_edist_empty hs))) id

theorem empty_or_nonempty_of_Hausdorff_edist_ne_top {α : Type u} [emetric_space α] {s : set α}
    {t : set α} (fin : Hausdorff_edist s t ≠ ⊤) : s = ∅ ∧ t = ∅ ∨ set.nonempty s ∧ set.nonempty t :=
  sorry

/-! Now, we turn to the same notions in metric spaces. To avoid the difficulties related to
`Inf` and `Sup` on `ℝ` (which is only conditionally complete), we use the notions in `ennreal`
formulated in terms of the edistance, and coerce them to `ℝ`.
Then their properties follow readily from the corresponding properties in `ennreal`,
modulo some tedious rewriting of inequalities from one to the other. -/

namespace metric

/-! ### Distance of a point to a set as a function into `ℝ`. -/

/-- The minimal distance of a point to a set -/
def Mathlib.metric.inf_dist {α : Type u} [metric_space α] (x : α) (s : set α) : ℝ :=
  ennreal.to_real (inf_edist x s)

/-- the minimal distance is always nonnegative -/
theorem Mathlib.metric.inf_dist_nonneg {α : Type u} [metric_space α] {s : set α} {x : α} :
    0 ≤ metric.inf_dist x s :=
  sorry

/-- the minimal distance to the empty set is 0 (if you want to have the more reasonable
value ∞ instead, use `inf_edist`, which takes values in ennreal) -/
@[simp] theorem Mathlib.metric.inf_dist_empty {α : Type u} [metric_space α] {x : α} :
    metric.inf_dist x ∅ = 0 :=
  sorry

/-- In a metric space, the minimal edistance to a nonempty set is finite -/
theorem Mathlib.metric.inf_edist_ne_top {α : Type u} [metric_space α] {s : set α} {x : α}
    (h : set.nonempty s) : inf_edist x s ≠ ⊤ :=
  sorry

/-- The minimal distance of a point to a set containing it vanishes -/
theorem Mathlib.metric.inf_dist_zero_of_mem {α : Type u} [metric_space α] {s : set α} {x : α}
    (h : x ∈ s) : metric.inf_dist x s = 0 :=
  sorry

/-- The minimal distance to a singleton is the distance to the unique point in this singleton -/
@[simp] theorem Mathlib.metric.inf_dist_singleton {α : Type u} [metric_space α] {x : α} {y : α} :
    metric.inf_dist x (singleton y) = dist x y :=
  sorry

/-- The minimal distance to a set is bounded by the distance to any point in this set -/
theorem Mathlib.metric.inf_dist_le_dist_of_mem {α : Type u} [metric_space α] {s : set α} {x : α}
    {y : α} (h : y ∈ s) : metric.inf_dist x s ≤ dist x y :=
  sorry

/-- The minimal distance is monotonous with respect to inclusion -/
theorem Mathlib.metric.inf_dist_le_inf_dist_of_subset {α : Type u} [metric_space α] {s : set α}
    {t : set α} {x : α} (h : s ⊆ t) (hs : set.nonempty s) :
    metric.inf_dist x t ≤ metric.inf_dist x s :=
  sorry

/-- If the minimal distance to a set is `<r`, there exists a point in this set at distance `<r` -/
theorem Mathlib.metric.exists_dist_lt_of_inf_dist_lt {α : Type u} [metric_space α] {s : set α}
    {x : α} {r : ℝ} (h : metric.inf_dist x s < r) (hs : set.nonempty s) :
    ∃ (y : α), ∃ (H : y ∈ s), dist x y < r :=
  sorry

/-- The minimal distance from `x` to `s` is bounded by the distance from `y` to `s`, modulo
the distance between `x` and `y` -/
theorem Mathlib.metric.inf_dist_le_inf_dist_add_dist {α : Type u} [metric_space α] {s : set α}
    {x : α} {y : α} : metric.inf_dist x s ≤ metric.inf_dist y s + dist x y :=
  sorry

/-- The minimal distance to a set is Lipschitz in point with constant 1 -/
theorem Mathlib.metric.lipschitz_inf_dist_pt {α : Type u} [metric_space α] (s : set α) :
    lipschitz_with 1 fun (x : α) => metric.inf_dist x s :=
  lipschitz_with.of_le_add fun (x y : α) => metric.inf_dist_le_inf_dist_add_dist

/-- The minimal distance to a set is uniformly continuous in point -/
theorem Mathlib.metric.uniform_continuous_inf_dist_pt {α : Type u} [metric_space α] (s : set α) :
    uniform_continuous fun (x : α) => metric.inf_dist x s :=
  lipschitz_with.uniform_continuous (metric.lipschitz_inf_dist_pt s)

/-- The minimal distance to a set is continuous in point -/
theorem Mathlib.metric.continuous_inf_dist_pt {α : Type u} [metric_space α] (s : set α) :
    continuous fun (x : α) => metric.inf_dist x s :=
  uniform_continuous.continuous (metric.uniform_continuous_inf_dist_pt s)

/-- The minimal distance to a set and its closure coincide -/
theorem Mathlib.metric.inf_dist_eq_closure {α : Type u} [metric_space α] {s : set α} {x : α} :
    metric.inf_dist x (closure s) = metric.inf_dist x s :=
  sorry

/-- A point belongs to the closure of `s` iff its infimum distance to this set vanishes -/
theorem Mathlib.metric.mem_closure_iff_inf_dist_zero {α : Type u} [metric_space α] {s : set α}
    {x : α} (h : set.nonempty s) : x ∈ closure s ↔ metric.inf_dist x s = 0 :=
  sorry

/-- Given a closed set `s`, a point belongs to `s` iff its infimum distance to this set vanishes -/
theorem Mathlib.metric.mem_iff_inf_dist_zero_of_closed {α : Type u} [metric_space α] {s : set α}
    {x : α} (h : is_closed s) (hs : set.nonempty s) : x ∈ s ↔ metric.inf_dist x s = 0 :=
  eq.mp (Eq._oldrec (Eq.refl (x ∈ closure s ↔ metric.inf_dist x s = 0)) (is_closed.closure_eq h))
    (metric.mem_closure_iff_inf_dist_zero hs)

/-- The infimum distance is invariant under isometries -/
theorem Mathlib.metric.inf_dist_image {α : Type u} {β : Type v} [metric_space α] [metric_space β]
    {t : set α} {x : α} {Φ : α → β} (hΦ : isometry Φ) :
    metric.inf_dist (Φ x) (Φ '' t) = metric.inf_dist x t :=
  sorry

/-! ### Distance of a point to a set as a function into `ℝ≥0`. -/

/-- The minimal distance of a point to a set as a `ℝ≥0` -/
def Mathlib.metric.inf_nndist {α : Type u} [metric_space α] (x : α) (s : set α) : nnreal :=
  ennreal.to_nnreal (inf_edist x s)

@[simp] theorem Mathlib.metric.coe_inf_nndist {α : Type u} [metric_space α] {s : set α} {x : α} :
    ↑(metric.inf_nndist x s) = metric.inf_dist x s :=
  rfl

/-- The minimal distance to a set (as `ℝ≥0`) is Lipschitz in point with constant 1 -/
theorem Mathlib.metric.lipschitz_inf_nndist_pt {α : Type u} [metric_space α] (s : set α) :
    lipschitz_with 1 fun (x : α) => metric.inf_nndist x s :=
  lipschitz_with.of_le_add fun (x y : α) => metric.inf_dist_le_inf_dist_add_dist

/-- The minimal distance to a set (as `ℝ≥0`) is uniformly continuous in point -/
theorem Mathlib.metric.uniform_continuous_inf_nndist_pt {α : Type u} [metric_space α] (s : set α) :
    uniform_continuous fun (x : α) => metric.inf_nndist x s :=
  lipschitz_with.uniform_continuous (metric.lipschitz_inf_nndist_pt s)

/-- The minimal distance to a set (as `ℝ≥0`) is continuous in point -/
theorem Mathlib.metric.continuous_inf_nndist_pt {α : Type u} [metric_space α] (s : set α) :
    continuous fun (x : α) => metric.inf_nndist x s :=
  uniform_continuous.continuous (metric.uniform_continuous_inf_nndist_pt s)

/-! ### The Hausdorff distance as a function into `ℝ`. -/

/-- The Hausdorff distance between two sets is the smallest nonnegative `r` such that each set is
included in the `r`-neighborhood of the other. If there is no such `r`, it is defined to
be `0`, arbitrarily -/
def Mathlib.metric.Hausdorff_dist {α : Type u} [metric_space α] (s : set α) (t : set α) : ℝ :=
  ennreal.to_real (Hausdorff_edist s t)

/-- The Hausdorff distance is nonnegative -/
theorem Mathlib.metric.Hausdorff_dist_nonneg {α : Type u} [metric_space α] {s : set α} {t : set α} :
    0 ≤ metric.Hausdorff_dist s t :=
  sorry

/-- If two sets are nonempty and bounded in a metric space, they are at finite Hausdorff
edistance. -/
theorem Mathlib.metric.Hausdorff_edist_ne_top_of_nonempty_of_bounded {α : Type u} [metric_space α]
    {s : set α} {t : set α} (hs : set.nonempty s) (ht : set.nonempty t) (bs : metric.bounded s)
    (bt : metric.bounded t) : Hausdorff_edist s t ≠ ⊤ :=
  sorry

/-- The Hausdorff distance between a set and itself is zero -/
@[simp] theorem Mathlib.metric.Hausdorff_dist_self_zero {α : Type u} [metric_space α] {s : set α} :
    metric.Hausdorff_dist s s = 0 :=
  sorry

/-- The Hausdorff distance from `s` to `t` and from `t` to `s` coincide -/
theorem Mathlib.metric.Hausdorff_dist_comm {α : Type u} [metric_space α] {s : set α} {t : set α} :
    metric.Hausdorff_dist s t = metric.Hausdorff_dist t s :=
  sorry

/-- The Hausdorff distance to the empty set vanishes (if you want to have the more reasonable
value ∞ instead, use `Hausdorff_edist`, which takes values in ennreal) -/
@[simp] theorem Mathlib.metric.Hausdorff_dist_empty {α : Type u} [metric_space α] {s : set α} :
    metric.Hausdorff_dist s ∅ = 0 :=
  sorry

/-- The Hausdorff distance to the empty set vanishes (if you want to have the more reasonable
value ∞ instead, use `Hausdorff_edist`, which takes values in ennreal) -/
@[simp] theorem Mathlib.metric.Hausdorff_dist_empty' {α : Type u} [metric_space α] {s : set α} :
    metric.Hausdorff_dist ∅ s = 0 :=
  sorry

/-- Bounding the Hausdorff distance by bounding the distance of any point
in each set to the other set -/
theorem Mathlib.metric.Hausdorff_dist_le_of_inf_dist {α : Type u} [metric_space α] {s : set α}
    {t : set α} {r : ℝ} (hr : 0 ≤ r) (H1 : ∀ (x : α), x ∈ s → metric.inf_dist x t ≤ r)
    (H2 : ∀ (x : α), x ∈ t → metric.inf_dist x s ≤ r) : metric.Hausdorff_dist s t ≤ r :=
  sorry

/-- Bounding the Hausdorff distance by exhibiting, for any point in each set,
another point in the other set at controlled distance -/
theorem Mathlib.metric.Hausdorff_dist_le_of_mem_dist {α : Type u} [metric_space α] {s : set α}
    {t : set α} {r : ℝ} (hr : 0 ≤ r)
    (H1 : ∀ (x : α) (H : x ∈ s), ∃ (y : α), ∃ (H : y ∈ t), dist x y ≤ r)
    (H2 : ∀ (x : α) (H : x ∈ t), ∃ (y : α), ∃ (H : y ∈ s), dist x y ≤ r) :
    metric.Hausdorff_dist s t ≤ r :=
  sorry

/-- The Hausdorff distance is controlled by the diameter of the union -/
theorem Mathlib.metric.Hausdorff_dist_le_diam {α : Type u} [metric_space α] {s : set α} {t : set α}
    (hs : set.nonempty s) (bs : metric.bounded s) (ht : set.nonempty t) (bt : metric.bounded t) :
    metric.Hausdorff_dist s t ≤ metric.diam (s ∪ t) :=
  sorry

/-- The distance to a set is controlled by the Hausdorff distance -/
theorem Mathlib.metric.inf_dist_le_Hausdorff_dist_of_mem {α : Type u} [metric_space α] {s : set α}
    {t : set α} {x : α} (hx : x ∈ s) (fin : Hausdorff_edist s t ≠ ⊤) :
    metric.inf_dist x t ≤ metric.Hausdorff_dist s t :=
  sorry

/-- If the Hausdorff distance is `<r`, then any point in one of the sets is at distance
`<r` of a point in the other set -/
theorem Mathlib.metric.exists_dist_lt_of_Hausdorff_dist_lt {α : Type u} [metric_space α] {s : set α}
    {t : set α} {x : α} {r : ℝ} (h : x ∈ s) (H : metric.Hausdorff_dist s t < r)
    (fin : Hausdorff_edist s t ≠ ⊤) : ∃ (y : α), ∃ (H : y ∈ t), dist x y < r :=
  sorry

/-- If the Hausdorff distance is `<r`, then any point in one of the sets is at distance
`<r` of a point in the other set -/
theorem Mathlib.metric.exists_dist_lt_of_Hausdorff_dist_lt' {α : Type u} [metric_space α]
    {s : set α} {t : set α} {y : α} {r : ℝ} (h : y ∈ t) (H : metric.Hausdorff_dist s t < r)
    (fin : Hausdorff_edist s t ≠ ⊤) : ∃ (x : α), ∃ (H : x ∈ s), dist x y < r :=
  sorry

/-- The infimum distance to `s` and `t` are the same, up to the Hausdorff distance
between `s` and `t` -/
theorem Mathlib.metric.inf_dist_le_inf_dist_add_Hausdorff_dist {α : Type u} [metric_space α]
    {s : set α} {t : set α} {x : α} (fin : Hausdorff_edist s t ≠ ⊤) :
    metric.inf_dist x t ≤ metric.inf_dist x s + metric.Hausdorff_dist s t :=
  sorry

/-- The Hausdorff distance is invariant under isometries -/
theorem Mathlib.metric.Hausdorff_dist_image {α : Type u} {β : Type v} [metric_space α]
    [metric_space β] {s : set α} {t : set α} {Φ : α → β} (h : isometry Φ) :
    metric.Hausdorff_dist (Φ '' s) (Φ '' t) = metric.Hausdorff_dist s t :=
  sorry

/-- The Hausdorff distance satisfies the triangular inequality -/
theorem Mathlib.metric.Hausdorff_dist_triangle {α : Type u} [metric_space α] {s : set α} {t : set α}
    {u : set α} (fin : Hausdorff_edist s t ≠ ⊤) :
    metric.Hausdorff_dist s u ≤ metric.Hausdorff_dist s t + metric.Hausdorff_dist t u :=
  sorry

/-- The Hausdorff distance satisfies the triangular inequality -/
theorem Mathlib.metric.Hausdorff_dist_triangle' {α : Type u} [metric_space α] {s : set α}
    {t : set α} {u : set α} (fin : Hausdorff_edist t u ≠ ⊤) :
    metric.Hausdorff_dist s u ≤ metric.Hausdorff_dist s t + metric.Hausdorff_dist t u :=
  sorry

/-- The Hausdorff distance between a set and its closure vanish -/
@[simp] theorem Mathlib.metric.Hausdorff_dist_self_closure {α : Type u} [metric_space α]
    {s : set α} : metric.Hausdorff_dist s (closure s) = 0 :=
  sorry

/-- Replacing a set by its closure does not change the Hausdorff distance. -/
@[simp] theorem Mathlib.metric.Hausdorff_dist_closure₁ {α : Type u} [metric_space α] {s : set α}
    {t : set α} : metric.Hausdorff_dist (closure s) t = metric.Hausdorff_dist s t :=
  sorry

/-- Replacing a set by its closure does not change the Hausdorff distance. -/
@[simp] theorem Mathlib.metric.Hausdorff_dist_closure₂ {α : Type u} [metric_space α] {s : set α}
    {t : set α} : metric.Hausdorff_dist s (closure t) = metric.Hausdorff_dist s t :=
  sorry

/-- The Hausdorff distance between two sets and their closures coincide -/
@[simp] theorem Mathlib.metric.Hausdorff_dist_closure {α : Type u} [metric_space α] {s : set α}
    {t : set α} : metric.Hausdorff_dist (closure s) (closure t) = metric.Hausdorff_dist s t :=
  sorry

/-- Two sets are at zero Hausdorff distance if and only if they have the same closures -/
theorem Mathlib.metric.Hausdorff_dist_zero_iff_closure_eq_closure {α : Type u} [metric_space α]
    {s : set α} {t : set α} (fin : Hausdorff_edist s t ≠ ⊤) :
    metric.Hausdorff_dist s t = 0 ↔ closure s = closure t :=
  sorry

/-- Two closed sets are at zero Hausdorff distance if and only if they coincide -/
theorem Mathlib.metric.Hausdorff_dist_zero_iff_eq_of_closed {α : Type u} [metric_space α]
    {s : set α} {t : set α} (hs : is_closed s) (ht : is_closed t) (fin : Hausdorff_edist s t ≠ ⊤) :
    metric.Hausdorff_dist s t = 0 ↔ s = t :=
  sorry

end Mathlib


/-
Copyright (c) 2018 Jan-David Salchow. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jan-David Salchow, Patrick Massot
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.bases
import Mathlib.topology.subset_properties
import Mathlib.topology.metric_space.basic
import Mathlib.PostPort

universes u_1 u_3 l u_2

namespace Mathlib

/-!
# Sequences in topological spaces

In this file we define sequences in topological spaces and show how they are related to
filters and the topology. In particular, we
* define the sequential closure of a set and prove that it's contained in the closure,
* define a type class "sequential_space" in which closure and sequential closure agree,
* define sequential continuity and show that it coincides with continuity in sequential spaces,
* provide an instance that shows that every first-countable (and in particular metric) space is
  a sequential space.
* define sequential compactness, prove that compactness implies sequential compactness in first
  countable spaces, and prove they are equivalent for uniform spaces having a countable uniformity
  basis (in particular metric spaces).
-/

/-! ### Sequential closures, sequential continuity, and sequential spaces. -/

/-- A sequence converges in the sence of topological spaces iff the associated statement for filter
holds. -/
theorem topological_space.seq_tendsto_iff {α : Type u_1} [topological_space α] {x : ℕ → α} {limit : α} : filter.tendsto x filter.at_top (nhds limit) ↔
  ∀ (U : set α), limit ∈ U → is_open U → ∃ (N : ℕ), ∀ (n : ℕ), n ≥ N → x n ∈ U := sorry

/-- The sequential closure of a subset M ⊆ α of a topological space α is
the set of all p ∈ α which arise as limit of sequences in M. -/
def sequential_closure {α : Type u_1} [topological_space α] (M : set α) : set α :=
  set_of fun (p : α) => ∃ (x : ℕ → α), (∀ (n : ℕ), x n ∈ M) ∧ filter.tendsto x filter.at_top (nhds p)

theorem subset_sequential_closure {α : Type u_1} [topological_space α] (M : set α) : M ⊆ sequential_closure M :=
  fun (p : α) (_x : p ∈ M) =>
    (fun (this : p ∈ sequential_closure M) => this)
      (Exists.intro (fun (n : ℕ) => p) { left := fun (n : ℕ) => _x, right := tendsto_const_nhds })

/-- A set `s` is sequentially closed if for any converging sequence `x n` of elements of `s`,
the limit belongs to `s` as well. -/
def is_seq_closed {α : Type u_1} [topological_space α] (s : set α) :=
  s = sequential_closure s

/-- A convenience lemma for showing that a set is sequentially closed. -/
theorem is_seq_closed_of_def {α : Type u_1} [topological_space α] {A : set α} (h : ∀ (x : ℕ → α) (p : α), (∀ (n : ℕ), x n ∈ A) → filter.tendsto x filter.at_top (nhds p) → p ∈ A) : is_seq_closed A := sorry

/-- The sequential closure of a set is contained in the closure of that set.
The converse is not true. -/
theorem sequential_closure_subset_closure {α : Type u_1} [topological_space α] (M : set α) : sequential_closure M ⊆ closure M := sorry

/-- A set is sequentially closed if it is closed. -/
theorem is_seq_closed_of_is_closed {α : Type u_1} [topological_space α] (M : set α) (_x : is_closed M) : is_seq_closed M :=
  (fun (this : sequential_closure M ⊆ M) => set.eq_of_subset_of_subset (subset_sequential_closure M) this)
    (trans_rel_left has_subset.subset (sequential_closure_subset_closure M) (is_closed.closure_eq _x))

/-- The limit of a convergent sequence in a sequentially closed set is in that set.-/
theorem mem_of_is_seq_closed {α : Type u_1} [topological_space α] {A : set α} (_x : is_seq_closed A) {x : ℕ → α} : (∀ (n : ℕ), x n ∈ A) → ∀ {limit : α}, filter.tendsto x filter.at_top (nhds limit) → limit ∈ A := sorry

/-- The limit of a convergent sequence in a closed set is in that set.-/
theorem mem_of_is_closed_sequential {α : Type u_1} [topological_space α] {A : set α} (_x : is_closed A) {x : ℕ → α} : (∀ (n : ℕ), x n ∈ A) → ∀ {limit : α}, filter.tendsto x filter.at_top (nhds limit) → limit ∈ A :=
  fun (_x_1 : ∀ (n : ℕ), x n ∈ A) {limit : α} (_x_2 : filter.tendsto x filter.at_top (nhds limit)) =>
    mem_of_is_seq_closed (is_seq_closed_of_is_closed A _x) _x_1 _x_2

/-- A sequential space is a space in which 'sequences are enough to probe the topology'. This can be
 formalised by demanding that the sequential closure and the closure coincide. The following
 statements show that other topological properties can be deduced from sequences in sequential
 spaces. -/
class sequential_space (α : Type u_3) [topological_space α]
where
  sequential_closure_eq_closure : ∀ (M : set α), sequential_closure M = closure M

/-- In a sequential space, a set is closed iff it's sequentially closed. -/
theorem is_seq_closed_iff_is_closed {α : Type u_1} [topological_space α] [sequential_space α] {M : set α} : is_seq_closed M ↔ is_closed M := sorry

/-- In a sequential space, a point belongs to the closure of a set iff it is a limit of a sequence
taking values in this set. -/
theorem mem_closure_iff_seq_limit {α : Type u_1} [topological_space α] [sequential_space α] {s : set α} {a : α} : a ∈ closure s ↔ ∃ (x : ℕ → α), (∀ (n : ℕ), x n ∈ s) ∧ filter.tendsto x filter.at_top (nhds a) := sorry

/-- A function between topological spaces is sequentially continuous if it commutes with limit of
 convergent sequences. -/
def sequentially_continuous {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] (f : α → β) :=
  ∀ (x : ℕ → α) {limit : α},
    filter.tendsto x filter.at_top (nhds limit) → filter.tendsto (f ∘ x) filter.at_top (nhds (f limit))

/- A continuous function is sequentially continuous. -/

theorem continuous.to_sequentially_continuous {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} (_x : continuous f) : sequentially_continuous f := sorry

/-- In a sequential space, continuity and sequential continuity coincide. -/
theorem continuous_iff_sequentially_continuous {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] {f : α → β} [sequential_space α] : continuous f ↔ sequentially_continuous f := sorry

namespace topological_space

namespace first_countable_topology

/-- Every first-countable space is sequential. -/
protected instance sequential_space {α : Type u_1} [topological_space α] [first_countable_topology α] : sequential_space α :=
  sequential_space.mk
    ((fun (this : ∀ (M : set α), sequential_closure M = closure M) => this)
      fun (M : set α) =>
        (fun (this : closure M ⊆ sequential_closure M) => set.subset.antisymm (sequential_closure_subset_closure M) this)
          fun (p : α) (hp : p ∈ closure M) => sorry)

end first_countable_topology

end topological_space

/-- A set `s` is sequentially compact if every sequence taking values in `s` has a
converging subsequence. -/
def is_seq_compact {α : Type u_1} [topological_space α] (s : set α) :=
  ∀ {u : ℕ → α},
    (∀ (n : ℕ), u n ∈ s) →
      ∃ (x : α), ∃ (H : x ∈ s), ∃ (φ : ℕ → ℕ), strict_mono φ ∧ filter.tendsto (u ∘ φ) filter.at_top (nhds x)

/-- A space `α` is sequentially compact if every sequence in `α` has a
converging subsequence. -/
class seq_compact_space (α : Type u_3) [topological_space α]
where
  seq_compact_univ : is_seq_compact set.univ

theorem is_seq_compact.subseq_of_frequently_in {α : Type u_1} [topological_space α] {s : set α} (hs : is_seq_compact s) {u : ℕ → α} (hu : filter.frequently (fun (n : ℕ) => u n ∈ s) filter.at_top) : ∃ (x : α), ∃ (H : x ∈ s), ∃ (φ : ℕ → ℕ), strict_mono φ ∧ filter.tendsto (u ∘ φ) filter.at_top (nhds x) := sorry

theorem seq_compact_space.tendsto_subseq {α : Type u_1} [topological_space α] [seq_compact_space α] (u : ℕ → α) : ∃ (x : α), ∃ (φ : ℕ → ℕ), strict_mono φ ∧ filter.tendsto (u ∘ φ) filter.at_top (nhds x) := sorry

theorem is_compact.is_seq_compact {α : Type u_1} [topological_space α] [topological_space.first_countable_topology α] {s : set α} (hs : is_compact s) : is_seq_compact s := sorry

theorem is_compact.tendsto_subseq' {α : Type u_1} [topological_space α] [topological_space.first_countable_topology α] {s : set α} {u : ℕ → α} (hs : is_compact s) (hu : filter.frequently (fun (n : ℕ) => u n ∈ s) filter.at_top) : ∃ (x : α), ∃ (H : x ∈ s), ∃ (φ : ℕ → ℕ), strict_mono φ ∧ filter.tendsto (u ∘ φ) filter.at_top (nhds x) :=
  is_seq_compact.subseq_of_frequently_in (is_compact.is_seq_compact hs) hu

theorem is_compact.tendsto_subseq {α : Type u_1} [topological_space α] [topological_space.first_countable_topology α] {s : set α} {u : ℕ → α} (hs : is_compact s) (hu : ∀ (n : ℕ), u n ∈ s) : ∃ (x : α), ∃ (H : x ∈ s), ∃ (φ : ℕ → ℕ), strict_mono φ ∧ filter.tendsto (u ∘ φ) filter.at_top (nhds x) :=
  is_compact.is_seq_compact hs hu

protected instance first_countable_topology.seq_compact_of_compact {α : Type u_1} [topological_space α] [topological_space.first_countable_topology α] [compact_space α] : seq_compact_space α :=
  seq_compact_space.mk (is_compact.is_seq_compact compact_univ)

theorem compact_space.tendsto_subseq {α : Type u_1} [topological_space α] [topological_space.first_countable_topology α] [compact_space α] (u : ℕ → α) : ∃ (x : α), ∃ (φ : ℕ → ℕ), strict_mono φ ∧ filter.tendsto (u ∘ φ) filter.at_top (nhds x) :=
  seq_compact_space.tendsto_subseq u

theorem lebesgue_number_lemma_seq {β : Type u_2} [uniform_space β] {s : set β} {ι : Type u_1} {c : ι → set β} (hs : is_seq_compact s) (hc₁ : ∀ (i : ι), is_open (c i)) (hc₂ : s ⊆ set.Union fun (i : ι) => c i) (hU : filter.is_countably_generated (uniformity β)) : ∃ (V : set (β × β)),
  ∃ (H : V ∈ uniformity β), symmetric_rel V ∧ ∀ (x : β), x ∈ s → ∃ (i : ι), uniform_space.ball x V ⊆ c i := sorry

theorem is_seq_compact.totally_bounded {β : Type u_2} [uniform_space β] {s : set β} (h : is_seq_compact s) : totally_bounded s := sorry

protected theorem is_seq_compact.is_compact {β : Type u_2} [uniform_space β] {s : set β} (h : filter.is_countably_generated (uniformity β)) (hs : is_seq_compact s) : is_compact s := sorry

protected theorem uniform_space.compact_iff_seq_compact {β : Type u_2} [uniform_space β] {s : set β} (h : filter.is_countably_generated (uniformity β)) : is_compact s ↔ is_seq_compact s :=
  { mp := fun (H : is_compact s) => is_compact.is_seq_compact H,
    mpr := fun (H : is_seq_compact s) => is_seq_compact.is_compact h H }

theorem uniform_space.compact_space_iff_seq_compact_space {β : Type u_2} [uniform_space β] (H : filter.is_countably_generated (uniformity β)) : compact_space β ↔ seq_compact_space β := sorry

/-- A version of Bolzano-Weistrass: in a metric space, is_compact s ↔ is_seq_compact s -/
theorem metric.compact_iff_seq_compact {β : Type u_2} [metric_space β] {s : set β} : is_compact s ↔ is_seq_compact s :=
  uniform_space.compact_iff_seq_compact emetric.uniformity_has_countable_basis

/-- A version of Bolzano-Weistrass: in a proper metric space (eg. $ℝ^n$),
every bounded sequence has a converging subsequence. This version assumes only
that the sequence is frequently in some bounded set. -/
theorem tendsto_subseq_of_frequently_bounded {β : Type u_2} [metric_space β] {s : set β} [proper_space β] (hs : metric.bounded s) {u : ℕ → β} (hu : filter.frequently (fun (n : ℕ) => u n ∈ s) filter.at_top) : ∃ (b : β), ∃ (H : b ∈ closure s), ∃ (φ : ℕ → ℕ), strict_mono φ ∧ filter.tendsto (u ∘ φ) filter.at_top (nhds b) := sorry

/-- A version of Bolzano-Weistrass: in a proper metric space (eg. $ℝ^n$),
every bounded sequence has a converging subsequence. -/
theorem tendsto_subseq_of_bounded {β : Type u_2} [metric_space β] {s : set β} [proper_space β] (hs : metric.bounded s) {u : ℕ → β} (hu : ∀ (n : ℕ), u n ∈ s) : ∃ (b : β), ∃ (H : b ∈ closure s), ∃ (φ : ℕ → ℕ), strict_mono φ ∧ filter.tendsto (u ∘ φ) filter.at_top (nhds b) :=
  tendsto_subseq_of_frequently_bounded hs (filter.frequently_of_forall hu)

theorem metric.compact_space_iff_seq_compact_space {β : Type u_2} [metric_space β] : compact_space β ↔ seq_compact_space β :=
  uniform_space.compact_space_iff_seq_compact_space emetric.uniformity_has_countable_basis

theorem seq_compact.lebesgue_number_lemma_of_metric {β : Type u_2} [metric_space β] {s : set β} {ι : Type u_1} {c : ι → set β} (hs : is_seq_compact s) (hc₁ : ∀ (i : ι), is_open (c i)) (hc₂ : s ⊆ set.Union fun (i : ι) => c i) : ∃ (δ : ℝ), ∃ (H : δ > 0), ∀ (x : β), x ∈ s → ∃ (i : ι), metric.ball x δ ⊆ c i := sorry


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem mathd_algebra_89
  (b : ℝ)
  (h₀ : b ≠ 0) :
  (7 * b^3)^2 * (4 * b^2)^(-(3:ℤ)) = 49 / 64 :=
begin
  ring_nf,
  field_simp,
  norm_cast,
  refine (div_eq_iff _).mpr _,
  { norm_num,
    assumption },
  ring,
end


/-
Copyright (c) 2021 Shing Tak Lam. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Shing Tak Lam
-/

import topology.homotopy.basic
import topology.path_connected
import analysis.convex.basic

/-!
# Homotopy between paths

In this file, we define a `homotopy` between two `path`s. In addition, we define a relation
`homotopic` on `path`s, and prove that it is an equivalence relation.

## Definitions

* `path.homotopy p₀ p₁` is the type of homotopies between paths `p₀` and `p₁`
* `path.homotopy.refl p` is the constant homotopy between `p` and itself
* `path.homotopy.symm F` is the `path.homotopy p₁ p₀` defined by reversing the homotopy
* `path.homotopy.trans F G`, where `F : path.homotopy p₀ p₁`, `G : path.homotopy p₁ p₂` is the
  `path.homotopy p₀ p₂` defined by putting the first homotopy on `[0, 1/2]` and the second on
  `[1/2, 1]`
* `path.homotopy.hcomp F G`, where `F : path.homotopy p₀ q₀` and `G : path.homotopy p₁ q₁` is
  a `path.homotopy (p₀.trans p₁) (q₀.trans q₁)`
* `path.homotopic p₀ p₁` is the relation saying that there is a homotopy between `p₀` and `p₁`
* `path.homotopic.setoid x₀ x₁` is the setoid on `path`s from `path.homotopic`
* `path.homotopic.quotient x₀ x₁` is the quotient type from `path x₀ x₀` by `path.homotopic.setoid`

-/

universes u v

variables {X : Type u} {Y : Type v} [topological_space X] [topological_space Y]
variables {x₀ x₁ x₂ x₃ : X}

noncomputable theory

open_locale unit_interval

namespace path

/--
The type of homotopies between two paths.
-/
abbreviation homotopy (p₀ p₁ : path x₀ x₁) :=
continuous_map.homotopy_rel p₀.to_continuous_map p₁.to_continuous_map {0, 1}

namespace homotopy

section

variables {p₀ p₁ : path x₀ x₁}

instance : has_coe_to_fun (homotopy p₀ p₁) (λ _, I × I → X) := ⟨λ F, F.to_fun⟩

lemma coe_fn_injective : @function.injective (homotopy p₀ p₁) (I × I → X) coe_fn :=
continuous_map.homotopy_with.coe_fn_injective

@[simp]
lemma source (F : homotopy p₀ p₁) (t : I) : F (t, 0) = x₀ :=
begin
  simp_rw [←p₀.source],
  apply continuous_map.homotopy_rel.eq_fst,
  simp,
end

@[simp]
lemma target (F : homotopy p₀ p₁) (t : I) : F (t, 1) = x₁ :=
begin
  simp_rw [←p₁.target],
  apply continuous_map.homotopy_rel.eq_snd,
  simp,
end

/--
Evaluating a path homotopy at an intermediate point, giving us a `path`.
-/
def eval (F : homotopy p₀ p₁) (t : I) : path x₀ x₁ :=
{ to_fun := F.to_homotopy.curry t,
  source' := by simp,
  target' := by simp }

@[simp]
lemma eval_zero (F : homotopy p₀ p₁) : F.eval 0 = p₀ :=
begin
  ext t,
  simp [eval],
end

@[simp]
lemma eval_one (F : homotopy p₀ p₁) : F.eval 1 = p₁ :=
begin
  ext t,
  simp [eval],
end

end

section

variables {p₀ p₁ p₂ : path x₀ x₁}

/--
Given a path `p`, we can define a `homotopy p p` by `F (t, x) = p x`
-/
@[simps]
def refl (p : path x₀ x₁) : homotopy p p :=
continuous_map.homotopy_rel.refl p.to_continuous_map {0, 1}

/--
Given a `homotopy p₀ p₁`, we can define a `homotopy p₁ p₀` by reversing the homotopy.
-/
@[simps]
def symm (F : homotopy p₀ p₁) : homotopy p₁ p₀ :=
continuous_map.homotopy_rel.symm F

@[simp]
lemma symm_symm (F : homotopy p₀ p₁) : F.symm.symm = F :=
continuous_map.homotopy_rel.symm_symm F

/--
Given `homotopy p₀ p₁` and `homotopy p₁ p₂`, we can define a `homotopy p₀ p₂` by putting the first
homotopy on `[0, 1/2]` and the second on `[1/2, 1]`.
-/
def trans (F : homotopy p₀ p₁) (G : homotopy p₁ p₂) : homotopy p₀ p₂ :=
continuous_map.homotopy_rel.trans F G

lemma trans_apply (F : homotopy p₀ p₁) (G : homotopy p₁ p₂) (x : I × I) :
  (F.trans G) x =
    if h : (x.1 : ℝ) ≤ 1/2 then
      F (⟨2 * x.1, (unit_interval.mul_pos_mem_iff zero_lt_two).2 ⟨x.1.2.1, h⟩⟩, x.2)
    else
      G (⟨2 * x.1 - 1, unit_interval.two_mul_sub_one_mem_iff.2 ⟨(not_le.1 h).le, x.1.2.2⟩⟩, x.2) :=
continuous_map.homotopy_rel.trans_apply _ _ _

lemma symm_trans (F : homotopy p₀ p₁) (G : homotopy p₁ p₂) :
  (F.trans G).symm = G.symm.trans F.symm :=
continuous_map.homotopy_rel.symm_trans _ _

/--
Casting a `homotopy p₀ p₁` to a `homotopy q₀ q₁` where `p₀ = q₀` and `p₁ = q₁`.
-/
@[simps]
def cast {p₀ p₁ q₀ q₁ : path x₀ x₁} (F : homotopy p₀ p₁) (h₀ : p₀ = q₀) (h₁ : p₁ = q₁) :
  homotopy q₀ q₁ :=
continuous_map.homotopy_rel.cast F (congr_arg _ h₀) (congr_arg _ h₁)

end

section

variables {p₀ q₀ : path x₀ x₁} {p₁ q₁ : path x₁ x₂}

/--
Suppose `p₀` and `q₀` are paths from `x₀` to `x₁`, `p₁` and `q₁` are paths from `x₁` to `x₂`.
Furthermore, suppose `F : homotopy p₀ q₀` and `G : homotopy p₁ q₁`. Then we can define a homotopy
from `p₀.trans p₁` to `q₀.trans q₁`.
-/
def hcomp (F : homotopy p₀ q₀) (G : homotopy p₁ q₁) :
  homotopy (p₀.trans p₁) (q₀.trans q₁) :=
{ to_fun := λ x,
  if (x.2 : ℝ) ≤ 1/2 then
    (F.eval x.1).extend (2 * x.2)
  else
    (G.eval x.1).extend (2 * x.2 - 1),
  continuous_to_fun := begin
    refine continuous_if_le (continuous_induced_dom.comp continuous_snd) continuous_const
      (F.to_homotopy.continuous.comp (by continuity)).continuous_on
      (G.to_homotopy.continuous.comp (by continuity)).continuous_on _,
    intros x hx,
    norm_num [hx]
  end,
  map_zero_left' := λ x, by norm_num [path.trans],
  map_one_left' := λ x, by norm_num [path.trans],
  prop' := begin
    rintros x t ht,
    cases ht,
    { rw ht,
      simp },
    { rw set.mem_singleton_iff at ht,
      rw ht,
      norm_num }
  end }

lemma hcomp_apply (F : homotopy p₀ q₀) (G : homotopy p₁ q₁) (x : I × I) :
  F.hcomp G x =
  if h : (x.2 : ℝ) ≤ 1/2 then
    F.eval x.1 ⟨2 * x.2, (unit_interval.mul_pos_mem_iff zero_lt_two).2 ⟨x.2.2.1, h⟩⟩
  else
    G.eval x.1 ⟨2 * x.2 - 1, unit_interval.two_mul_sub_one_mem_iff.2 ⟨(not_le.1 h).le, x.2.2.2⟩⟩ :=
show ite _ _ _ = _, by split_ifs; exact path.extend_extends _ _

lemma hcomp_half (F : homotopy p₀ q₀) (G : homotopy p₁ q₁) (t : I) :
  F.hcomp G (t, ⟨1/2, by norm_num, by norm_num⟩) = x₁ :=
show ite _ _ _ = _, by norm_num

end

/--
Suppose `p` is a path, then we have a homotopy from `p` to `p.reparam f` by the convexity of `I`.
-/
def reparam (p  : path x₀ x₁) (f : I → I) (hf : continuous f) (hf₀ : f 0 = 0) (hf₁ : f 1 = 1) :
  homotopy p (p.reparam f hf hf₀ hf₁) :=
{ to_fun := λ x, p ⟨σ x.1 * x.2 + x.1 * f x.2,
    show (σ x.1 : ℝ) • (x.2 : ℝ) + (x.1 : ℝ) • (f x.2 : ℝ) ∈ I, from convex_Icc _ _ x.2.2 (f x.2).2
    (by unit_interval) (by unit_interval) (by simp)⟩,
  map_zero_left' := λ x, by norm_num,
  map_one_left' := λ x, by norm_num,
  prop' := λ t x hx,
  begin
    cases hx,
    { rw hx, norm_num [hf₀] },
    { rw set.mem_singleton_iff at hx,
      rw hx,
      norm_num [hf₁] }
  end }

/--
Suppose `F : homotopy p q`. Then we have a `homotopy p.symm q.symm` by reversing the second
argument.
-/
@[simps]
def symm₂ {p q : path x₀ x₁} (F : p.homotopy q) : p.symm.homotopy q.symm :=
{ to_fun := λ x, F ⟨x.1, σ x.2⟩,
  map_zero_left' := by simp [path.symm],
  map_one_left' := by simp [path.symm],
  prop' := λ t x hx, begin
    cases hx,
    { rw hx, simp },
    { rw set.mem_singleton_iff at hx,
      rw hx,
      simp }
  end }

/--
Given `F : homotopy p q`, and `f : C(X, Y)`, we can define a homotopy from `p.map f.continuous` to
`q.map f.continuous`.
-/
@[simps]
def map {p q : path x₀ x₁} (F : p.homotopy q) (f : C(X, Y)) :
  homotopy (p.map f.continuous) (q.map f.continuous) :=
{ to_fun := f ∘ F,
  map_zero_left' := by simp,
  map_one_left' := by simp,
  prop' := λ t x hx, begin
    cases hx,
    { simp [hx] },
    { rw set.mem_singleton_iff at hx,
      simp [hx] }
  end }

end homotopy

/--
Two paths `p₀` and `p₁` are `path.homotopic` if there exists a `homotopy` between them.
-/
def homotopic (p₀ p₁ : path x₀ x₁) : Prop := nonempty (p₀.homotopy p₁)

namespace homotopic

@[refl]

@[symm]
lemma symm ⦃p₀ p₁ : path x₀ x₁⦄ (h : p₀.homotopic p₁) : p₁.homotopic p₀ := h.map homotopy.symm

@[trans]
lemma trans ⦃p₀ p₁ p₂ : path x₀ x₁⦄ (h₀ : p₀.homotopic p₁) (h₁ : p₁.homotopic p₂) :
  p₀.homotopic p₂ := h₀.map2 homotopy.trans h₁

lemma equivalence : equivalence (@homotopic X _ x₀ x₁) := ⟨refl, symm, trans⟩

lemma map {p q : path x₀ x₁} (h : p.homotopic q) (f : C(X, Y)) :
  homotopic (p.map f.continuous) (q.map f.continuous) := h.map (λ F, F.map f)

lemma hcomp {p₀ p₁ : path x₀ x₁} {q₀ q₁ : path x₁ x₂} (hp : p₀.homotopic p₁)
  (hq : q₀.homotopic q₁) : (p₀.trans q₀).homotopic (p₁.trans q₁) := hp.map2 homotopy.hcomp hq

/--
The setoid on `path`s defined by the equivalence relation `path.homotopic`. That is, two paths are
equivalent if there is a `homotopy` between them.
-/
protected def setoid (x₀ x₁ : X) : setoid (path x₀ x₁) := ⟨homotopic, equivalence⟩

/--
The quotient on `path x₀ x₁` by the equivalence relation `path.homotopic`.
-/
protected def quotient (x₀ x₁ : X) := quotient (homotopic.setoid x₀ x₁)

local attribute [instance] homotopic.setoid

instance : inhabited (homotopic.quotient () ()) := ⟨quotient.mk $ path.refl ()⟩

/-- The composition of path homotopy classes. This is `path.trans` descended to the quotient. -/
def quotient.comp (P₀ : path.homotopic.quotient x₀ x₁) (P₁ : path.homotopic.quotient x₁ x₂) :
  path.homotopic.quotient x₀ x₂ :=
quotient.map₂ path.trans (λ (p₀ : path x₀ x₁) p₁ hp (q₀ : path x₁ x₂) q₁ hq, (hcomp hp hq)) P₀ P₁

lemma comp_lift (P₀ : path x₀ x₁) (P₁ : path x₁ x₂) : ⟦P₀.trans P₁⟧ = quotient.comp ⟦P₀⟧ ⟦P₁⟧ := rfl

/-- The image of a path homotopy class `P₀` under a map `f`.
    This is `path.map` descended to the quotient -/
def quotient.map_fn (P₀ : path.homotopic.quotient x₀ x₁) (f : C(X, Y)) :
  path.homotopic.quotient (f x₀) (f x₁) :=
quotient.map (λ (q : path x₀ x₁), q.map f.continuous) (λ p₀ p₁ h, path.homotopic.map h f) P₀

lemma map_lift (P₀ : path x₀ x₁) (f : C(X, Y)) :
  ⟦P₀.map f.continuous⟧ = quotient.map_fn ⟦P₀⟧ f := rfl

lemma hpath_hext {p₁ : path x₀ x₁} {p₂ : path x₂ x₃} (hp : ∀ t, p₁ t = p₂ t) : ⟦p₁⟧ == ⟦p₂⟧ :=
begin
  obtain rfl : x₀ = x₂ := by { convert hp 0; simp, },
  obtain rfl : x₁ = x₃ := by { convert hp 1; simp, },
  rw heq_iff_eq, congr, ext t, exact hp t,
end

end homotopic

end path

namespace continuous_map.homotopy

/--
Given a homotopy H: f ∼ g, get the path traced by the point `x` as it moves from
`f x` to `g x`
-/
def eval_at {X : Type*} {Y : Type*} [topological_space X] [topological_space Y] {f g : C(X, Y)}
  (H : continuous_map.homotopy f g) (x : X) : path (f x) (g x) :=
{ to_fun := λ t, H (t, x),
  source' := H.apply_zero x,
  target' := H.apply_one x, }

end continuous_map.homotopy