/-
Copyright (c) 2023 Rémy Degenne. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Rémy Degenne

! This file was ported from Lean 3 source module probability.kernel.composition
! leanprover-community/mathlib commit 97d1aa955750bd57a7eeef91de310e633881670b
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Probability.Kernel.Basic

/-!
# Product and composition of kernels

We define
* the composition-product `κ ⊗ₖ η` of two s-finite kernels `κ : kernel α β` and
  `η : kernel (α × β) γ`, a kernel from `α` to `β × γ`.
* the map and comap of a kernel along a measurable function.
* the composition `η ∘ₖ κ` of s-finite kernels `κ : kernel α β` and `η : kernel β γ`,
  a kernel from `α` to `γ`.
* the product `prod κ η` of s-finite kernels `κ : kernel α β` and `η : kernel α γ`,
  a kernel from `α` to `β × γ`.

A note on names:
The composition-product `kernel α β → kernel (α × β) γ → kernel α (β × γ)` is named composition in
[kallenberg2021] and product on the wikipedia article on transition kernels.
Most papers studying categories of kernels call composition the map we call composition. We adopt
that convention because it fits better with the use of the name `comp` elsewhere in mathlib.

## Main definitions

Kernels built from other kernels:
* `comp_prod (κ : kernel α β) (η : kernel (α × β) γ) : kernel α (β × γ)`: composition-product of 2
  s-finite kernels. We define a notation `κ ⊗ₖ η = comp_prod κ η`.
  `∫⁻ bc, f bc ∂((κ ⊗ₖ η) a) = ∫⁻ b, ∫⁻ c, f (b, c) ∂(η (a, b)) ∂(κ a)`
* `map (κ : kernel α β) (f : β → γ) (hf : measurable f) : kernel α γ`
  `∫⁻ c, g c ∂(map κ f hf a) = ∫⁻ b, g (f b) ∂(κ a)`
* `comap (κ : kernel α β) (f : γ → α) (hf : measurable f) : kernel γ β`
  `∫⁻ b, g b ∂(comap κ f hf c) = ∫⁻ b, g b ∂(κ (f c))`
* `comp (η : kernel β γ) (κ : kernel α β) : kernel α γ`: composition of 2 s-finite kernels.
  We define a notation `η ∘ₖ κ = comp η κ`.
  `∫⁻ c, g c ∂((η ∘ₖ κ) a) = ∫⁻ b, ∫⁻ c, g c ∂(η b) ∂(κ a)`
* `prod (κ : kernel α β) (η : kernel α γ) : kernel α (β × γ)`: product of 2 s-finite kernels.
  `∫⁻ bc, f bc ∂(prod κ η a) = ∫⁻ b, ∫⁻ c, f (b, c) ∂(η a) ∂(κ a)`

## Main statements

* `lintegral_comp_prod`, `lintegral_map`, `lintegral_comap`, `lintegral_comp`, `lintegral_prod`:
  Lebesgue integral of a function against a composition-product/map/comap/composition/product of
  kernels.
* Instances of the form `<class>.<operation>` where class is one of `is_markov_kernel`,
  `is_finite_kernel`, `is_s_finite_kernel` and operation is one of `comp_prod`, `map`, `comap`,
  `comp`, `prod`. These instances state that the three classes are stable by the various operations.

## Notations

* `κ ⊗ₖ η = probability_theory.kernel.comp_prod κ η`
* `η ∘ₖ κ = probability_theory.kernel.comp η κ`

-/

open MeasureTheory

open ENNReal

namespace ProbabilityTheory

namespace Kernel

variable {α β ι : Type _} {mα : MeasurableSpace α} {mβ : MeasurableSpace β}

include mα mβ

section CompositionProduct

/-!
### Composition-Product of kernels

We define a kernel composition-product
`comp_prod : kernel α β → kernel (α × β) γ → kernel α (β × γ)`.
-/

variable {γ : Type _} {mγ : MeasurableSpace γ} {s : Set (β × γ)}

include mγ

/-- Auxiliary function for the definition of the composition-product of two kernels.
For all `a : α`, `comp_prod_fun κ η a` is a countably additive function with value zero on the empty
set, and the composition-product of kernels is defined in `kernel.comp_prod` through
`measure.of_measurable`. -/
noncomputable def compProdFun (κ : kernel α β) (η : kernel (α × β) γ) (a : α) (s : Set (β × γ)) :
    ℝ≥0∞ :=
  ∫⁻ b, η (a, b) { c | (b, c) ∈ s } ∂κ a
#align probability_theory.kernel.comp_prod_fun ProbabilityTheory.kernel.compProdFun

theorem compProdFun_empty (κ : kernel α β) (η : kernel (α × β) γ) (a : α) :
    compProdFun κ η a ∅ = 0 := by
  simp only [comp_prod_fun, Set.mem_empty_iff_false, Set.setOf_false, measure_empty,
    lintegral_const, MulZeroClass.zero_mul]
#align probability_theory.kernel.comp_prod_fun_empty ProbabilityTheory.kernel.compProdFun_empty

theorem compProdFun_unionᵢ (κ : kernel α β) (η : kernel (α × β) γ) [IsSFiniteKernel η] (a : α)
    (f : ℕ → Set (β × γ)) (hf_meas : ∀ i, MeasurableSet (f i))
    (hf_disj : Pairwise (Disjoint on f)) :
    compProdFun κ η a (⋃ i, f i) = ∑' i, compProdFun κ η a (f i) :=
  by
  have h_Union :
    (fun b => η (a, b) { c : γ | (b, c) ∈ ⋃ i, f i }) = fun b =>
      η (a, b) (⋃ i, { c : γ | (b, c) ∈ f i }) :=
    by
    ext1 b
    congr with c
    simp only [Set.mem_unionᵢ, Set.supᵢ_eq_unionᵢ, Set.mem_setOf_eq]
    rfl
  rw [comp_prod_fun, h_Union]
  have h_tsum :
    (fun b => η (a, b) (⋃ i, { c : γ | (b, c) ∈ f i })) = fun b =>
      ∑' i, η (a, b) { c : γ | (b, c) ∈ f i } :=
    by
    ext1 b
    rw [measure_Union]
    · intro i j hij s hsi hsj c hcs
      have hbci : {(b, c)} ⊆ f i := by
        rw [Set.singleton_subset_iff]
        exact hsi hcs
      have hbcj : {(b, c)} ⊆ f j := by
        rw [Set.singleton_subset_iff]
        exact hsj hcs
      simpa only [Set.bot_eq_empty, Set.le_eq_subset, Set.singleton_subset_iff,
        Set.mem_empty_iff_false] using hf_disj hij hbci hbcj
    · exact fun i => (@measurable_prod_mk_left β γ _ _ b) _ (hf_meas i)
  rw [h_tsum, lintegral_tsum]
  · rfl
  · intro i
    have hm : MeasurableSet { p : (α × β) × γ | (p.1.2, p.2) ∈ f i } :=
      measurable_fst.snd.prod_mk measurable_snd (hf_meas i)
    exact ((measurable_prod_mk_mem η hm).comp measurable_prod_mk_left).AeMeasurable
#align probability_theory.kernel.comp_prod_fun_Union ProbabilityTheory.kernel.compProdFun_unionᵢ

theorem compProdFun_tsum_right (κ : kernel α β) (η : kernel (α × β) γ) [IsSFiniteKernel η] (a : α)
    (hs : MeasurableSet s) : compProdFun κ η a s = ∑' n, compProdFun κ (seq η n) a s :=
  by
  simp_rw [comp_prod_fun, (measure_sum_seq η _).symm]
  have :
    (∫⁻ b, measure.sum (fun n => seq η n (a, b)) { c : γ | (b, c) ∈ s } ∂κ a) =
      ∫⁻ b, ∑' n, seq η n (a, b) { c : γ | (b, c) ∈ s } ∂κ a :=
    by
    congr
    ext1 b
    rw [measure.sum_apply]
    exact measurable_prod_mk_left hs
  rw [this, lintegral_tsum fun n : ℕ => _]
  exact
    ((measurable_prod_mk_mem (seq η n) ((measurable_fst.snd.prod_mk measurable_snd) hs)).comp
        measurable_prod_mk_left).AeMeasurable
#align probability_theory.kernel.comp_prod_fun_tsum_right ProbabilityTheory.kernel.compProdFun_tsum_right

theorem compProdFun_tsum_left (κ : kernel α β) (η : kernel (α × β) γ) [IsSFiniteKernel κ] (a : α)
    (s : Set (β × γ)) : compProdFun κ η a s = ∑' n, compProdFun (seq κ n) η a s := by
  simp_rw [comp_prod_fun, (measure_sum_seq κ _).symm, lintegral_sum_measure]
#align probability_theory.kernel.comp_prod_fun_tsum_left ProbabilityTheory.kernel.compProdFun_tsum_left

/- ./././Mathport/Syntax/Translate/Expr.lean:107:6: warning: expanding binder group (n m) -/
theorem compProdFun_eq_tsum (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] (a : α) (hs : MeasurableSet s) :
    compProdFun κ η a s = ∑' (n) (m), compProdFun (seq κ n) (seq η m) a s := by
  simp_rw [comp_prod_fun_tsum_left κ η a s, comp_prod_fun_tsum_right _ η a hs]
#align probability_theory.kernel.comp_prod_fun_eq_tsum ProbabilityTheory.kernel.compProdFun_eq_tsum

/-- Auxiliary lemma for `measurable_comp_prod_fun`. -/
theorem measurable_compProdFun_of_finite (κ : kernel α β) [IsFiniteKernel κ] (η : kernel (α × β) γ)
    [IsFiniteKernel η] (hs : MeasurableSet s) : Measurable fun a => compProdFun κ η a s :=
  by
  simp only [comp_prod_fun]
  have h_meas : Measurable (Function.uncurry fun a b => η (a, b) { c : γ | (b, c) ∈ s }) :=
    by
    have :
      (Function.uncurry fun a b => η (a, b) { c : γ | (b, c) ∈ s }) = fun p =>
        η p { c : γ | (p.2, c) ∈ s } :=
      by
      ext1 p
      have hp_eq_mk : p = (p.fst, p.snd) := prod.mk.eta.symm
      rw [hp_eq_mk, Function.uncurry_apply_pair]
    rw [this]
    exact measurable_prod_mk_mem η (measurable_fst.snd.prod_mk measurable_snd hs)
  exact measurable_lintegral κ h_meas
#align probability_theory.kernel.measurable_comp_prod_fun_of_finite ProbabilityTheory.kernel.measurable_compProdFun_of_finite

theorem measurable_compProdFun (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] (hs : MeasurableSet s) : Measurable fun a => compProdFun κ η a s :=
  by
  simp_rw [comp_prod_fun_tsum_right κ η _ hs]
  refine' Measurable.eNNReal_tsum fun n => _
  simp only [comp_prod_fun]
  have h_meas : Measurable (Function.uncurry fun a b => seq η n (a, b) { c : γ | (b, c) ∈ s }) :=
    by
    have :
      (Function.uncurry fun a b => seq η n (a, b) { c : γ | (b, c) ∈ s }) = fun p =>
        seq η n p { c : γ | (p.2, c) ∈ s } :=
      by
      ext1 p
      have hp_eq_mk : p = (p.fst, p.snd) := prod.mk.eta.symm
      rw [hp_eq_mk, Function.uncurry_apply_pair]
    rw [this]
    exact measurable_prod_mk_mem (seq η n) (measurable_fst.snd.prod_mk measurable_snd hs)
  exact measurable_lintegral κ h_meas
#align probability_theory.kernel.measurable_comp_prod_fun ProbabilityTheory.kernel.measurable_compProdFun

/-- Composition-Product of kernels. It verifies
`∫⁻ bc, f bc ∂(comp_prod κ η a) = ∫⁻ b, ∫⁻ c, f (b, c) ∂(η (a, b)) ∂(κ a)`
(see `lintegral_comp_prod`). -/
noncomputable def compProd (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] : kernel α (β × γ)
    where
  val a :=
    Measure.ofMeasurable (fun s hs => compProdFun κ η a s) (compProdFun_empty κ η a)
      (compProdFun_unionᵢ κ η a)
  property := by
    refine' measure.measurable_of_measurable_coe _ fun s hs => _
    have :
      (fun a =>
          measure.of_measurable (fun s hs => comp_prod_fun κ η a s) (comp_prod_fun_empty κ η a)
            (comp_prod_fun_Union κ η a) s) =
        fun a => comp_prod_fun κ η a s :=
      by
      ext1 a
      rwa [measure.of_measurable_apply]
    rw [this]
    exact measurable_comp_prod_fun κ η hs
#align probability_theory.kernel.comp_prod ProbabilityTheory.kernel.compProd

-- mathport name: kernel.comp_prod
scoped[ProbabilityTheory] infixl:100 " ⊗ₖ " => ProbabilityTheory.kernel.compProd

theorem compProd_apply_eq_compProdFun (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] (a : α) (hs : MeasurableSet s) : (κ ⊗ₖ η) a s = compProdFun κ η a s :=
  by
  rw [comp_prod]
  change
    measure.of_measurable (fun s hs => comp_prod_fun κ η a s) (comp_prod_fun_empty κ η a)
        (comp_prod_fun_Union κ η a) s =
      ∫⁻ b, η (a, b) { c | (b, c) ∈ s } ∂κ a
  rw [measure.of_measurable_apply _ hs]
  rfl
#align probability_theory.kernel.comp_prod_apply_eq_comp_prod_fun ProbabilityTheory.kernel.compProd_apply_eq_compProdFun

theorem compProd_apply (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] (a : α) (hs : MeasurableSet s) :
    (κ ⊗ₖ η) a s = ∫⁻ b, η (a, b) { c | (b, c) ∈ s } ∂κ a :=
  compProd_apply_eq_compProdFun κ η a hs
#align probability_theory.kernel.comp_prod_apply ProbabilityTheory.kernel.compProd_apply

/-- Lebesgue integral against the composition-product of two kernels. -/
theorem lintegral_comp_prod' (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] (a : α) {f : β → γ → ℝ≥0∞} (hf : Measurable (Function.uncurry f)) :
    (∫⁻ bc, f bc.1 bc.2 ∂(κ ⊗ₖ η) a) = ∫⁻ b, ∫⁻ c, f b c ∂η (a, b) ∂κ a :=
  by
  let F : ℕ → simple_func (β × γ) ℝ≥0∞ := simple_func.eapprox (Function.uncurry f)
  have h : ∀ a, (⨆ n, F n a) = Function.uncurry f a :=
    simple_func.supr_eapprox_apply (Function.uncurry f) hf
  simp only [Prod.forall, Function.uncurry_apply_pair] at h
  simp_rw [← h, Prod.mk.eta]
  have h_mono : Monotone F := fun i j hij b =>
    simple_func.monotone_eapprox (Function.uncurry f) hij _
  rw [lintegral_supr (fun n => (F n).Measurable) h_mono]
  have : ∀ b, (∫⁻ c, ⨆ n, F n (b, c) ∂η (a, b)) = ⨆ n, ∫⁻ c, F n (b, c) ∂η (a, b) :=
    by
    intro a
    rw [lintegral_supr]
    · exact fun n => (F n).Measurable.comp measurable_prod_mk_left
    · exact fun i j hij b => h_mono hij _
  simp_rw [this]
  have h_some_meas_integral :
    ∀ f' : simple_func (β × γ) ℝ≥0∞, Measurable fun b => ∫⁻ c, f' (b, c) ∂η (a, b) :=
    by
    intro f'
    have :
      (fun b => ∫⁻ c, f' (b, c) ∂η (a, b)) =
        (fun ab => ∫⁻ c, f' (ab.2, c) ∂η ab) ∘ fun b => (a, b) :=
      by
      ext1 ab
      rfl
    rw [this]
    refine' Measurable.comp _ measurable_prod_mk_left
    exact
      measurable_lintegral η
        ((simple_func.measurable _).comp (measurable_fst.snd.prod_mk measurable_snd))
  rw [lintegral_supr]
  rotate_left
  · exact fun n => h_some_meas_integral (F n)
  · exact fun i j hij b => lintegral_mono fun c => h_mono hij _
  congr
  ext1 n
  refine' simple_func.induction _ _ (F n)
  · intro c s hs
    simp only [simple_func.const_zero, simple_func.coe_piecewise, simple_func.coe_const,
      simple_func.coe_zero, Set.piecewise_eq_indicator, lintegral_indicator_const hs]
    rw [comp_prod_apply κ η _ hs, ← lintegral_const_mul c _]
    swap
    ·
      exact
        (measurable_prod_mk_mem η ((measurable_fst.snd.prod_mk measurable_snd) hs)).comp
          measurable_prod_mk_left
    congr
    ext1 b
    rw [lintegral_indicator_const_comp measurable_prod_mk_left hs]
    rfl
  · intro f f' h_disj hf_eq hf'_eq
    simp_rw [simple_func.coe_add, Pi.add_apply]
    change
      (∫⁻ x, (f : β × γ → ℝ≥0∞) x + f' x ∂(κ ⊗ₖ η) a) =
        ∫⁻ b, ∫⁻ c : γ, f (b, c) + f' (b, c) ∂η (a, b) ∂κ a
    rw [lintegral_add_left (simple_func.measurable _), hf_eq, hf'_eq, ← lintegral_add_left]
    swap
    · exact h_some_meas_integral f
    congr with b
    rw [← lintegral_add_left ((simple_func.measurable _).comp measurable_prod_mk_left)]
#align probability_theory.kernel.lintegral_comp_prod' ProbabilityTheory.kernel.lintegral_comp_prod'

/-- Lebesgue integral against the composition-product of two kernels. -/
theorem lintegral_compProd (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] (a : α) {f : β × γ → ℝ≥0∞} (hf : Measurable f) :
    (∫⁻ bc, f bc ∂(κ ⊗ₖ η) a) = ∫⁻ b, ∫⁻ c, f (b, c) ∂η (a, b) ∂κ a :=
  by
  let g := Function.curry f
  change (∫⁻ bc, f bc ∂(κ ⊗ₖ η) a) = ∫⁻ b, ∫⁻ c, g b c ∂η (a, b) ∂κ a
  rw [← lintegral_comp_prod']
  · simp_rw [g, Function.curry_apply, Prod.mk.eta]
  · simp_rw [g, Function.uncurry_curry]
    exact hf
#align probability_theory.kernel.lintegral_comp_prod ProbabilityTheory.kernel.lintegral_compProd

/- ./././Mathport/Syntax/Translate/Expr.lean:107:6: warning: expanding binder group (n m) -/
theorem compProd_eq_tsum_compProd (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] (a : α) (hs : MeasurableSet s) :
    (κ ⊗ₖ η) a s = ∑' (n : ℕ) (m : ℕ), (seq κ n ⊗ₖ seq η m) a s :=
  by
  simp_rw [comp_prod_apply_eq_comp_prod_fun _ _ _ hs]
  exact comp_prod_fun_eq_tsum κ η a hs
#align probability_theory.kernel.comp_prod_eq_tsum_comp_prod ProbabilityTheory.kernel.compProd_eq_tsum_compProd

theorem compProd_eq_sum_compProd (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] : κ ⊗ₖ η = kernel.sum fun n => kernel.sum fun m => seq κ n ⊗ₖ seq η m :=
  by
  ext (a s hs) : 2
  simp_rw [kernel.sum_apply' _ a hs]
  rw [comp_prod_eq_tsum_comp_prod κ η a hs]
#align probability_theory.kernel.comp_prod_eq_sum_comp_prod ProbabilityTheory.kernel.compProd_eq_sum_compProd

theorem compProd_eq_sum_compProd_left (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] : κ ⊗ₖ η = kernel.sum fun n => seq κ n ⊗ₖ η :=
  by
  rw [comp_prod_eq_sum_comp_prod]
  congr with (n a s hs)
  simp_rw [kernel.sum_apply' _ _ hs, comp_prod_apply_eq_comp_prod_fun _ _ _ hs,
    comp_prod_fun_tsum_right _ η a hs]
#align probability_theory.kernel.comp_prod_eq_sum_comp_prod_left ProbabilityTheory.kernel.compProd_eq_sum_compProd_left

theorem compProd_eq_sum_compProd_right (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] : κ ⊗ₖ η = kernel.sum fun n => κ ⊗ₖ seq η n :=
  by
  rw [comp_prod_eq_sum_comp_prod]
  simp_rw [comp_prod_eq_sum_comp_prod_left κ _]
  rw [kernel.sum_comm]
#align probability_theory.kernel.comp_prod_eq_sum_comp_prod_right ProbabilityTheory.kernel.compProd_eq_sum_compProd_right

instance IsMarkovKernel.compProd (κ : kernel α β) [IsMarkovKernel κ] (η : kernel (α × β) γ)
    [IsMarkovKernel η] : IsMarkovKernel (κ ⊗ₖ η) :=
  ⟨fun a =>
    ⟨by
      rw [comp_prod_apply κ η a MeasurableSet.univ]
      simp only [Set.mem_univ, Set.setOf_true, measure_univ, lintegral_one]⟩⟩
#align probability_theory.kernel.is_markov_kernel.comp_prod ProbabilityTheory.kernel.IsMarkovKernel.compProd

theorem compProd_apply_univ_le (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsFiniteKernel η] (a : α) : (κ ⊗ₖ η) a Set.univ ≤ κ a Set.univ * IsFiniteKernel.bound η :=
  by
  rw [comp_prod_apply κ η a MeasurableSet.univ]
  simp only [Set.mem_univ, Set.setOf_true]
  let Cη := is_finite_kernel.bound η
  calc
    (∫⁻ b, η (a, b) Set.univ ∂κ a) ≤ ∫⁻ b, Cη ∂κ a :=
      lintegral_mono fun b => measure_le_bound η (a, b) Set.univ
    _ = Cη * κ a Set.univ := (lintegral_const Cη)
    _ = κ a Set.univ * Cη := mul_comm _ _

#align probability_theory.kernel.comp_prod_apply_univ_le ProbabilityTheory.kernel.compProd_apply_univ_le

instance IsFiniteKernel.compProd (κ : kernel α β) [IsFiniteKernel κ] (η : kernel (α × β) γ)
    [IsFiniteKernel η] : IsFiniteKernel (κ ⊗ₖ η) :=
  ⟨⟨IsFiniteKernel.bound κ * IsFiniteKernel.bound η,
      ENNReal.mul_lt_top (IsFiniteKernel.bound_ne_top κ) (IsFiniteKernel.bound_ne_top η), fun a =>
      calc
        (κ ⊗ₖ η) a Set.univ ≤ κ a Set.univ * IsFiniteKernel.bound η := compProd_apply_univ_le κ η a
        _ ≤ IsFiniteKernel.bound κ * IsFiniteKernel.bound η :=
          mul_le_mul (measure_le_bound κ a Set.univ) le_rfl (zero_le _) (zero_le _)
        ⟩⟩
#align probability_theory.kernel.is_finite_kernel.comp_prod ProbabilityTheory.kernel.IsFiniteKernel.compProd

instance IsSFiniteKernel.compProd (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel (α × β) γ)
    [IsSFiniteKernel η] : IsSFiniteKernel (κ ⊗ₖ η) :=
  by
  rw [comp_prod_eq_sum_comp_prod]
  exact kernel.is_s_finite_kernel_sum fun n => kernel.is_s_finite_kernel_sum inferInstance
#align probability_theory.kernel.is_s_finite_kernel.comp_prod ProbabilityTheory.kernel.IsSFiniteKernel.compProd

end CompositionProduct

section MapComap

/-! ### map, comap -/

variable {γ : Type _} {mγ : MeasurableSpace γ} {f : β → γ} {g : γ → α}

include mγ

/-- The pushforward of a kernel along a measurable function.
We include measurability in the assumptions instead of using junk values
to make sure that typeclass inference can infer that the `map` of a Markov kernel
is again a Markov kernel. -/
noncomputable def map (κ : kernel α β) (f : β → γ) (hf : Measurable f) : kernel α γ
    where
  val a := (κ a).map f
  property := (Measure.measurable_map _ hf).comp (kernel.measurable κ)
#align probability_theory.kernel.map ProbabilityTheory.kernel.map

theorem map_apply (κ : kernel α β) (hf : Measurable f) (a : α) : map κ f hf a = (κ a).map f :=
  rfl
#align probability_theory.kernel.map_apply ProbabilityTheory.kernel.map_apply

theorem map_apply' (κ : kernel α β) (hf : Measurable f) (a : α) {s : Set γ} (hs : MeasurableSet s) :
    map κ f hf a s = κ a (f ⁻¹' s) := by rw [map_apply, measure.map_apply hf hs]
#align probability_theory.kernel.map_apply' ProbabilityTheory.kernel.map_apply'

theorem lintegral_map (κ : kernel α β) (hf : Measurable f) (a : α) {g' : γ → ℝ≥0∞}
    (hg : Measurable g') : (∫⁻ b, g' b ∂map κ f hf a) = ∫⁻ a, g' (f a) ∂κ a := by
  rw [map_apply _ hf, lintegral_map hg hf]
#align probability_theory.kernel.lintegral_map ProbabilityTheory.kernel.lintegral_map

theorem sum_map_seq (κ : kernel α β) [IsSFiniteKernel κ] (hf : Measurable f) :
    (kernel.sum fun n => map (seq κ n) f hf) = map κ f hf :=
  by
  ext (a s hs) : 2
  rw [kernel.sum_apply, map_apply' κ hf a hs, measure.sum_apply _ hs, ← measure_sum_seq κ,
    measure.sum_apply _ (hf hs)]
  simp_rw [map_apply' _ hf _ hs]
#align probability_theory.kernel.sum_map_seq ProbabilityTheory.kernel.sum_map_seq

instance IsMarkovKernel.map (κ : kernel α β) [IsMarkovKernel κ] (hf : Measurable f) :
    IsMarkovKernel (map κ f hf) :=
  ⟨fun a => ⟨by rw [map_apply' κ hf a MeasurableSet.univ, Set.preimage_univ, measure_univ]⟩⟩
#align probability_theory.kernel.is_markov_kernel.map ProbabilityTheory.kernel.IsMarkovKernel.map

instance IsFiniteKernel.map (κ : kernel α β) [IsFiniteKernel κ] (hf : Measurable f) :
    IsFiniteKernel (map κ f hf) :=
  by
  refine' ⟨⟨is_finite_kernel.bound κ, is_finite_kernel.bound_lt_top κ, fun a => _⟩⟩
  rw [map_apply' κ hf a MeasurableSet.univ]
  exact measure_le_bound κ a _
#align probability_theory.kernel.is_finite_kernel.map ProbabilityTheory.kernel.IsFiniteKernel.map

instance IsSFiniteKernel.map (κ : kernel α β) [IsSFiniteKernel κ] (hf : Measurable f) :
    IsSFiniteKernel (map κ f hf) :=
  ⟨⟨fun n => map (seq κ n) f hf, inferInstance, (sum_map_seq κ hf).symm⟩⟩
#align probability_theory.kernel.is_s_finite_kernel.map ProbabilityTheory.kernel.IsSFiniteKernel.map

/-- Pullback of a kernel, such that for each set s `comap κ g hg c s = κ (g c) s`.
We include measurability in the assumptions instead of using junk values
to make sure that typeclass inference can infer that the `comap` of a Markov kernel
is again a Markov kernel. -/
def comap (κ : kernel α β) (g : γ → α) (hg : Measurable g) : kernel γ β
    where
  val a := κ (g a)
  property := (kernel.measurable κ).comp hg
#align probability_theory.kernel.comap ProbabilityTheory.kernel.comap

theorem comap_apply (κ : kernel α β) (hg : Measurable g) (c : γ) : comap κ g hg c = κ (g c) :=
  rfl
#align probability_theory.kernel.comap_apply ProbabilityTheory.kernel.comap_apply

theorem comap_apply' (κ : kernel α β) (hg : Measurable g) (c : γ) (s : Set β) :
    comap κ g hg c s = κ (g c) s :=
  rfl
#align probability_theory.kernel.comap_apply' ProbabilityTheory.kernel.comap_apply'

theorem lintegral_comap (κ : kernel α β) (hg : Measurable g) (c : γ) (g' : β → ℝ≥0∞) :
    (∫⁻ b, g' b ∂comap κ g hg c) = ∫⁻ b, g' b ∂κ (g c) :=
  rfl
#align probability_theory.kernel.lintegral_comap ProbabilityTheory.kernel.lintegral_comap

theorem sum_comap_seq (κ : kernel α β) [IsSFiniteKernel κ] (hg : Measurable g) :
    (kernel.sum fun n => comap (seq κ n) g hg) = comap κ g hg :=
  by
  ext (a s hs) : 2
  rw [kernel.sum_apply, comap_apply' κ hg a s, measure.sum_apply _ hs, ← measure_sum_seq κ,
    measure.sum_apply _ hs]
  simp_rw [comap_apply' _ hg _ s]
#align probability_theory.kernel.sum_comap_seq ProbabilityTheory.kernel.sum_comap_seq

instance IsMarkovKernel.comap (κ : kernel α β) [IsMarkovKernel κ] (hg : Measurable g) :
    IsMarkovKernel (comap κ g hg) :=
  ⟨fun a => ⟨by rw [comap_apply' κ hg a Set.univ, measure_univ]⟩⟩
#align probability_theory.kernel.is_markov_kernel.comap ProbabilityTheory.kernel.IsMarkovKernel.comap

instance IsFiniteKernel.comap (κ : kernel α β) [IsFiniteKernel κ] (hg : Measurable g) :
    IsFiniteKernel (comap κ g hg) :=
  by
  refine' ⟨⟨is_finite_kernel.bound κ, is_finite_kernel.bound_lt_top κ, fun a => _⟩⟩
  rw [comap_apply' κ hg a Set.univ]
  exact measure_le_bound κ _ _
#align probability_theory.kernel.is_finite_kernel.comap ProbabilityTheory.kernel.IsFiniteKernel.comap

instance IsSFiniteKernel.comap (κ : kernel α β) [IsSFiniteKernel κ] (hg : Measurable g) :
    IsSFiniteKernel (comap κ g hg) :=
  ⟨⟨fun n => comap (seq κ n) g hg, inferInstance, (sum_comap_seq κ hg).symm⟩⟩
#align probability_theory.kernel.is_s_finite_kernel.comap ProbabilityTheory.kernel.IsSFiniteKernel.comap

end MapComap

open ProbabilityTheory

section FstSnd

/-- Define a `kernel (γ × α) β` from a `kernel α β` by taking the comap of the projection. -/
def prodMkLeft (κ : kernel α β) (γ : Type _) [MeasurableSpace γ] : kernel (γ × α) β :=
  comap κ Prod.snd measurable_snd
#align probability_theory.kernel.prod_mk_left ProbabilityTheory.kernel.prodMkLeft

variable {γ : Type _} {mγ : MeasurableSpace γ} {f : β → γ} {g : γ → α}

include mγ

theorem prodMkLeft_apply (κ : kernel α β) (ca : γ × α) : prodMkLeft κ γ ca = κ ca.snd :=
  rfl
#align probability_theory.kernel.prod_mk_left_apply ProbabilityTheory.kernel.prodMkLeft_apply

theorem prodMkLeft_apply' (κ : kernel α β) (ca : γ × α) (s : Set β) :
    prodMkLeft κ γ ca s = κ ca.snd s :=
  rfl
#align probability_theory.kernel.prod_mk_left_apply' ProbabilityTheory.kernel.prodMkLeft_apply'

theorem lintegral_prodMkLeft (κ : kernel α β) (ca : γ × α) (g : β → ℝ≥0∞) :
    (∫⁻ b, g b ∂prodMkLeft κ γ ca) = ∫⁻ b, g b ∂κ ca.snd :=
  rfl
#align probability_theory.kernel.lintegral_prod_mk_left ProbabilityTheory.kernel.lintegral_prodMkLeft

instance IsMarkovKernel.prodMkLeft (κ : kernel α β) [IsMarkovKernel κ] :
    IsMarkovKernel (prodMkLeft κ γ) := by
  rw [prod_mk_left]
  infer_instance
#align probability_theory.kernel.is_markov_kernel.prod_mk_left ProbabilityTheory.kernel.IsMarkovKernel.prodMkLeft

instance IsFiniteKernel.prodMkLeft (κ : kernel α β) [IsFiniteKernel κ] :
    IsFiniteKernel (prodMkLeft κ γ) := by
  rw [prod_mk_left]
  infer_instance
#align probability_theory.kernel.is_finite_kernel.prod_mk_left ProbabilityTheory.kernel.IsFiniteKernel.prodMkLeft

instance IsSFiniteKernel.prodMkLeft (κ : kernel α β) [IsSFiniteKernel κ] :
    IsSFiniteKernel (prodMkLeft κ γ) := by
  rw [prod_mk_left]
  infer_instance
#align probability_theory.kernel.is_s_finite_kernel.prod_mk_left ProbabilityTheory.kernel.IsSFiniteKernel.prodMkLeft

/-- Define a `kernel (β × α) γ` from a `kernel (α × β) γ` by taking the comap of `prod.swap`. -/
def swapLeft (κ : kernel (α × β) γ) : kernel (β × α) γ :=
  comap κ Prod.swap measurable_swap
#align probability_theory.kernel.swap_left ProbabilityTheory.kernel.swapLeft

theorem swapLeft_apply (κ : kernel (α × β) γ) (a : β × α) : swapLeft κ a = κ a.symm :=
  rfl
#align probability_theory.kernel.swap_left_apply ProbabilityTheory.kernel.swapLeft_apply

theorem swapLeft_apply' (κ : kernel (α × β) γ) (a : β × α) (s : Set γ) :
    swapLeft κ a s = κ a.symm s :=
  rfl
#align probability_theory.kernel.swap_left_apply' ProbabilityTheory.kernel.swapLeft_apply'

theorem lintegral_swapLeft (κ : kernel (α × β) γ) (a : β × α) (g : γ → ℝ≥0∞) :
    (∫⁻ c, g c ∂swapLeft κ a) = ∫⁻ c, g c ∂κ a.symm := by
  rw [swap_left, lintegral_comap _ measurable_swap a]
#align probability_theory.kernel.lintegral_swap_left ProbabilityTheory.kernel.lintegral_swapLeft

instance IsMarkovKernel.swapLeft (κ : kernel (α × β) γ) [IsMarkovKernel κ] :
    IsMarkovKernel (swapLeft κ) := by
  rw [swap_left]
  infer_instance
#align probability_theory.kernel.is_markov_kernel.swap_left ProbabilityTheory.kernel.IsMarkovKernel.swapLeft

instance IsFiniteKernel.swapLeft (κ : kernel (α × β) γ) [IsFiniteKernel κ] :
    IsFiniteKernel (swapLeft κ) := by
  rw [swap_left]
  infer_instance
#align probability_theory.kernel.is_finite_kernel.swap_left ProbabilityTheory.kernel.IsFiniteKernel.swapLeft

instance IsSFiniteKernel.swapLeft (κ : kernel (α × β) γ) [IsSFiniteKernel κ] :
    IsSFiniteKernel (swapLeft κ) := by
  rw [swap_left]
  infer_instance
#align probability_theory.kernel.is_s_finite_kernel.swap_left ProbabilityTheory.kernel.IsSFiniteKernel.swapLeft

/-- Define a `kernel α (γ × β)` from a `kernel α (β × γ)` by taking the map of `prod.swap`. -/
noncomputable def swapRight (κ : kernel α (β × γ)) : kernel α (γ × β) :=
  map κ Prod.swap measurable_swap
#align probability_theory.kernel.swap_right ProbabilityTheory.kernel.swapRight

theorem swapRight_apply (κ : kernel α (β × γ)) (a : α) : swapRight κ a = (κ a).map Prod.swap :=
  rfl
#align probability_theory.kernel.swap_right_apply ProbabilityTheory.kernel.swapRight_apply

theorem swapRight_apply' (κ : kernel α (β × γ)) (a : α) {s : Set (γ × β)} (hs : MeasurableSet s) :
    swapRight κ a s = κ a { p | p.symm ∈ s } :=
  by
  rw [swap_right_apply, measure.map_apply measurable_swap hs]
  rfl
#align probability_theory.kernel.swap_right_apply' ProbabilityTheory.kernel.swapRight_apply'

theorem lintegral_swapRight (κ : kernel α (β × γ)) (a : α) {g : γ × β → ℝ≥0∞} (hg : Measurable g) :
    (∫⁻ c, g c ∂swapRight κ a) = ∫⁻ bc : β × γ, g bc.symm ∂κ a := by
  rw [swap_right, lintegral_map _ measurable_swap a hg]
#align probability_theory.kernel.lintegral_swap_right ProbabilityTheory.kernel.lintegral_swapRight

instance IsMarkovKernel.swapRight (κ : kernel α (β × γ)) [IsMarkovKernel κ] :
    IsMarkovKernel (swapRight κ) := by
  rw [swap_right]
  infer_instance
#align probability_theory.kernel.is_markov_kernel.swap_right ProbabilityTheory.kernel.IsMarkovKernel.swapRight

instance IsFiniteKernel.swapRight (κ : kernel α (β × γ)) [IsFiniteKernel κ] :
    IsFiniteKernel (swapRight κ) := by
  rw [swap_right]
  infer_instance
#align probability_theory.kernel.is_finite_kernel.swap_right ProbabilityTheory.kernel.IsFiniteKernel.swapRight

instance IsSFiniteKernel.swapRight (κ : kernel α (β × γ)) [IsSFiniteKernel κ] :
    IsSFiniteKernel (swapRight κ) := by
  rw [swap_right]
  infer_instance
#align probability_theory.kernel.is_s_finite_kernel.swap_right ProbabilityTheory.kernel.IsSFiniteKernel.swapRight

/-- Define a `kernel α β` from a `kernel α (β × γ)` by taking the map of the first projection. -/
noncomputable def fst (κ : kernel α (β × γ)) : kernel α β :=
  map κ Prod.fst measurable_fst
#align probability_theory.kernel.fst ProbabilityTheory.kernel.fst

theorem fst_apply (κ : kernel α (β × γ)) (a : α) : fst κ a = (κ a).map Prod.fst :=
  rfl
#align probability_theory.kernel.fst_apply ProbabilityTheory.kernel.fst_apply

theorem fst_apply' (κ : kernel α (β × γ)) (a : α) {s : Set β} (hs : MeasurableSet s) :
    fst κ a s = κ a { p | p.1 ∈ s } :=
  by
  rw [fst_apply, measure.map_apply measurable_fst hs]
  rfl
#align probability_theory.kernel.fst_apply' ProbabilityTheory.kernel.fst_apply'

theorem lintegral_fst (κ : kernel α (β × γ)) (a : α) {g : β → ℝ≥0∞} (hg : Measurable g) :
    (∫⁻ c, g c ∂fst κ a) = ∫⁻ bc : β × γ, g bc.fst ∂κ a := by
  rw [fst, lintegral_map _ measurable_fst a hg]
#align probability_theory.kernel.lintegral_fst ProbabilityTheory.kernel.lintegral_fst

instance IsMarkovKernel.fst (κ : kernel α (β × γ)) [IsMarkovKernel κ] : IsMarkovKernel (fst κ) :=
  by
  rw [fst]
  infer_instance
#align probability_theory.kernel.is_markov_kernel.fst ProbabilityTheory.kernel.IsMarkovKernel.fst

instance IsFiniteKernel.fst (κ : kernel α (β × γ)) [IsFiniteKernel κ] : IsFiniteKernel (fst κ) :=
  by
  rw [fst]
  infer_instance
#align probability_theory.kernel.is_finite_kernel.fst ProbabilityTheory.kernel.IsFiniteKernel.fst

instance IsSFiniteKernel.fst (κ : kernel α (β × γ)) [IsSFiniteKernel κ] : IsSFiniteKernel (fst κ) :=
  by
  rw [fst]
  infer_instance
#align probability_theory.kernel.is_s_finite_kernel.fst ProbabilityTheory.kernel.IsSFiniteKernel.fst

/-- Define a `kernel α γ` from a `kernel α (β × γ)` by taking the map of the second projection. -/
noncomputable def snd (κ : kernel α (β × γ)) : kernel α γ :=
  map κ Prod.snd measurable_snd
#align probability_theory.kernel.snd ProbabilityTheory.kernel.snd

theorem snd_apply (κ : kernel α (β × γ)) (a : α) : snd κ a = (κ a).map Prod.snd :=
  rfl
#align probability_theory.kernel.snd_apply ProbabilityTheory.kernel.snd_apply

theorem snd_apply' (κ : kernel α (β × γ)) (a : α) {s : Set γ} (hs : MeasurableSet s) :
    snd κ a s = κ a { p | p.2 ∈ s } :=
  by
  rw [snd_apply, measure.map_apply measurable_snd hs]
  rfl
#align probability_theory.kernel.snd_apply' ProbabilityTheory.kernel.snd_apply'

theorem lintegral_snd (κ : kernel α (β × γ)) (a : α) {g : γ → ℝ≥0∞} (hg : Measurable g) :
    (∫⁻ c, g c ∂snd κ a) = ∫⁻ bc : β × γ, g bc.snd ∂κ a := by
  rw [snd, lintegral_map _ measurable_snd a hg]
#align probability_theory.kernel.lintegral_snd ProbabilityTheory.kernel.lintegral_snd

instance IsMarkovKernel.snd (κ : kernel α (β × γ)) [IsMarkovKernel κ] : IsMarkovKernel (snd κ) :=
  by
  rw [snd]
  infer_instance
#align probability_theory.kernel.is_markov_kernel.snd ProbabilityTheory.kernel.IsMarkovKernel.snd

instance IsFiniteKernel.snd (κ : kernel α (β × γ)) [IsFiniteKernel κ] : IsFiniteKernel (snd κ) :=
  by
  rw [snd]
  infer_instance
#align probability_theory.kernel.is_finite_kernel.snd ProbabilityTheory.kernel.IsFiniteKernel.snd

instance IsSFiniteKernel.snd (κ : kernel α (β × γ)) [IsSFiniteKernel κ] : IsSFiniteKernel (snd κ) :=
  by
  rw [snd]
  infer_instance
#align probability_theory.kernel.is_s_finite_kernel.snd ProbabilityTheory.kernel.IsSFiniteKernel.snd

end FstSnd

section Comp

/-! ### Composition of two kernels -/

variable {γ : Type _} {mγ : MeasurableSpace γ} {f : β → γ} {g : γ → α}

include mγ

/-- Composition of two s-finite kernels. -/
noncomputable def comp (η : kernel β γ) [IsSFiniteKernel η] (κ : kernel α β) [IsSFiniteKernel κ] :
    kernel α γ :=
  snd (κ ⊗ₖ prodMkLeft η α)
#align probability_theory.kernel.comp ProbabilityTheory.kernel.comp

-- mathport name: kernel.comp
scoped[ProbabilityTheory] infixl:100 " ∘ₖ " => ProbabilityTheory.kernel.comp

theorem comp_apply (η : kernel β γ) [IsSFiniteKernel η] (κ : kernel α β) [IsSFiniteKernel κ] (a : α)
    {s : Set γ} (hs : MeasurableSet s) : (η ∘ₖ κ) a s = ∫⁻ b, η b s ∂κ a :=
  by
  rw [comp, snd_apply' _ _ hs, comp_prod_apply]
  swap; · exact measurable_snd hs
  simp only [Set.mem_setOf_eq, Set.setOf_mem_eq, prod_mk_left_apply' _ _ s]
#align probability_theory.kernel.comp_apply ProbabilityTheory.kernel.comp_apply

theorem lintegral_comp (η : kernel β γ) [IsSFiniteKernel η] (κ : kernel α β) [IsSFiniteKernel κ]
    (a : α) {g : γ → ℝ≥0∞} (hg : Measurable g) :
    (∫⁻ c, g c ∂(η ∘ₖ κ) a) = ∫⁻ b, ∫⁻ c, g c ∂η b ∂κ a :=
  by
  rw [comp, lintegral_snd _ _ hg]
  change
    (∫⁻ bc, (fun a b => g b) bc.fst bc.snd ∂(κ ⊗ₖ prod_mk_left η α) a) = ∫⁻ b, ∫⁻ c, g c ∂η b ∂κ a
  exact lintegral_comp_prod _ _ _ (hg.comp measurable_snd)
#align probability_theory.kernel.lintegral_comp ProbabilityTheory.kernel.lintegral_comp

instance IsMarkovKernel.comp (η : kernel β γ) [IsMarkovKernel η] (κ : kernel α β)
    [IsMarkovKernel κ] : IsMarkovKernel (η ∘ₖ κ) :=
  by
  rw [comp]
  infer_instance
#align probability_theory.kernel.is_markov_kernel.comp ProbabilityTheory.kernel.IsMarkovKernel.comp

instance IsFiniteKernel.comp (η : kernel β γ) [IsFiniteKernel η] (κ : kernel α β)
    [IsFiniteKernel κ] : IsFiniteKernel (η ∘ₖ κ) :=
  by
  rw [comp]
  infer_instance
#align probability_theory.kernel.is_finite_kernel.comp ProbabilityTheory.kernel.IsFiniteKernel.comp

instance IsSFiniteKernel.comp (η : kernel β γ) [IsSFiniteKernel η] (κ : kernel α β)
    [IsSFiniteKernel κ] : IsSFiniteKernel (η ∘ₖ κ) :=
  by
  rw [comp]
  infer_instance
#align probability_theory.kernel.is_s_finite_kernel.comp ProbabilityTheory.kernel.IsSFiniteKernel.comp

/-- Composition of kernels is associative. -/
theorem comp_assoc {δ : Type _} {mδ : MeasurableSpace δ} (ξ : kernel γ δ) [IsSFiniteKernel ξ]
    (η : kernel β γ) [IsSFiniteKernel η] (κ : kernel α β) [IsSFiniteKernel κ] :
    ξ ∘ₖ η ∘ₖ κ = ξ ∘ₖ (η ∘ₖ κ) :=
  by
  refine' ext_fun fun a f hf => _
  simp_rw [lintegral_comp _ _ _ hf, lintegral_comp _ _ _ (measurable_lintegral' ξ hf)]
#align probability_theory.kernel.comp_assoc ProbabilityTheory.kernel.comp_assoc

theorem deterministic_comp_eq_map (hf : Measurable f) (κ : kernel α β) [IsSFiniteKernel κ] :
    deterministic hf ∘ₖ κ = map κ f hf :=
  by
  ext (a s hs) : 2
  simp_rw [map_apply' _ _ _ hs, comp_apply _ _ _ hs, deterministic_apply' hf _ hs,
    lintegral_indicator_const_comp hf hs, one_mul]
#align probability_theory.kernel.deterministic_comp_eq_map ProbabilityTheory.kernel.deterministic_comp_eq_map

theorem comp_deterministic_eq_comap (κ : kernel α β) [IsSFiniteKernel κ] (hg : Measurable g) :
    κ ∘ₖ deterministic hg = comap κ g hg :=
  by
  ext (a s hs) : 2
  simp_rw [comap_apply' _ _ _ s, comp_apply _ _ _ hs, deterministic_apply hg a,
    lintegral_dirac' _ (kernel.measurable_coe κ hs)]
#align probability_theory.kernel.comp_deterministic_eq_comap ProbabilityTheory.kernel.comp_deterministic_eq_comap

end Comp

section Prod

/-! ### Product of two kernels -/

variable {γ : Type _} {mγ : MeasurableSpace γ}

include mγ

/-- Product of two s-finite kernels. -/
noncomputable def prod (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel α γ) [IsSFiniteKernel η] :
    kernel α (β × γ) :=
  κ ⊗ₖ swapLeft (prodMkLeft η β)
#align probability_theory.kernel.prod ProbabilityTheory.kernel.prod

theorem prod_apply (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel α γ) [IsSFiniteKernel η] (a : α)
    {s : Set (β × γ)} (hs : MeasurableSet s) :
    (prod κ η) a s = ∫⁻ b : β, (η a) { c : γ | (b, c) ∈ s } ∂κ a := by
  simp_rw [Prod, comp_prod_apply _ _ _ hs, swap_left_apply _ _, prod_mk_left_apply,
    Prod.swap_prod_mk]
#align probability_theory.kernel.prod_apply ProbabilityTheory.kernel.prod_apply

theorem lintegral_prod (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel α γ) [IsSFiniteKernel η]
    (a : α) {g : β × γ → ℝ≥0∞} (hg : Measurable g) :
    (∫⁻ c, g c ∂(prod κ η) a) = ∫⁻ b, ∫⁻ c, g (b, c) ∂η a ∂κ a := by
  simp_rw [Prod, lintegral_comp_prod _ _ _ hg, swap_left_apply, prod_mk_left_apply,
    Prod.swap_prod_mk]
#align probability_theory.kernel.lintegral_prod ProbabilityTheory.kernel.lintegral_prod

instance IsMarkovKernel.prod (κ : kernel α β) [IsMarkovKernel κ] (η : kernel α γ)
    [IsMarkovKernel η] : IsMarkovKernel (prod κ η) :=
  by
  rw [Prod]
  infer_instance
#align probability_theory.kernel.is_markov_kernel.prod ProbabilityTheory.kernel.IsMarkovKernel.prod

instance IsFiniteKernel.prod (κ : kernel α β) [IsFiniteKernel κ] (η : kernel α γ)
    [IsFiniteKernel η] : IsFiniteKernel (prod κ η) :=
  by
  rw [Prod]
  infer_instance
#align probability_theory.kernel.is_finite_kernel.prod ProbabilityTheory.kernel.IsFiniteKernel.prod

instance IsSFiniteKernel.prod (κ : kernel α β) [IsSFiniteKernel κ] (η : kernel α γ)
    [IsSFiniteKernel η] : IsSFiniteKernel (prod κ η) :=
  by
  rw [Prod]
  infer_instance
#align probability_theory.kernel.is_s_finite_kernel.prod ProbabilityTheory.kernel.IsSFiniteKernel.prod

end Prod

end Kernel

end ProbabilityTheory


/-
Copyright (c) 2022 Jesse Reimann. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jesse Reimann, Kalle Kytölä

! This file was ported from Lean 3 source module measure_theory.integral.riesz_markov_kakutani
! leanprover-community/mathlib commit b2ff9a3d7a15fd5b0f060b135421d6a89a999c2f
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Topology.ContinuousFunction.Bounded
import Mathbin.Topology.Sets.Compacts

/-!
#  Riesz–Markov–Kakutani representation theorem

This file will prove different versions of the Riesz-Markov-Kakutani representation theorem.
The theorem is first proven for compact spaces, from which the statements about linear functionals
on bounded continuous functions or compactly supported functions on locally compact spaces follow.

To make use of the existing API, the measure is constructed from a content `λ` on the
compact subsets of the space X, rather than the usual construction of open sets in the literature.

## References

* [Walter Rudin, Real and Complex Analysis.][Rud87]

-/

noncomputable section

open BoundedContinuousFunction NNReal ENNReal

open Set Function TopologicalSpace

variable {X : Type _} [TopologicalSpace X]

variable (Λ : (X →ᵇ ℝ≥0) →ₗ[ℝ≥0] ℝ≥0)

/-! ### Construction of the content: -/

/-- Given a positive linear functional Λ on X, for `K ⊆ X` compact define
`λ(K) = inf {Λf | 1≤f on K}`. When X is a compact Hausdorff space, this will be shown to be a
content, and will be shown to agree with the Riesz measure on the compact subsets `K ⊆ X`. -/
def rieszContentAux : Compacts X → ℝ≥0 := fun K =>
  infₛ (Λ '' { f : X →ᵇ ℝ≥0 | ∀ x ∈ K, (1 : ℝ≥0) ≤ f x })
#align riesz_content_aux rieszContentAux

section RieszMonotone

/-- For any compact subset `K ⊆ X`, there exist some bounded continuous nonnegative
functions f on X such that `f ≥ 1` on K. -/
theorem riesz_content_aux_image_nonempty (K : Compacts X) :
    (Λ '' { f : X →ᵇ ℝ≥0 | ∀ x ∈ K, (1 : ℝ≥0) ≤ f x }).Nonempty :=
  by
  rw [nonempty_image_iff]
  use (1 : X →ᵇ ℝ≥0)
  intro x x_in_K
  simp only [BoundedContinuousFunction.coe_one, Pi.one_apply]
#align riesz_content_aux_image_nonempty riesz_content_aux_image_nonempty

/-- Riesz content λ (associated with a positive linear functional Λ) is
monotone: if `K₁ ⊆ K₂` are compact subsets in X, then `λ(K₁) ≤ λ(K₂)`. -/
theorem rieszContentAux_mono {K₁ K₂ : Compacts X} (h : K₁ ≤ K₂) :
    rieszContentAux Λ K₁ ≤ rieszContentAux Λ K₂ :=
  cinfₛ_le_cinfₛ (OrderBot.bddBelow _) (riesz_content_aux_image_nonempty Λ K₂)
    (image_subset Λ (setOf_subset_setOf.mpr fun f f_hyp x x_in_K₁ => f_hyp x (h x_in_K₁)))
#align riesz_content_aux_mono rieszContentAux_mono

end RieszMonotone

section RieszSubadditive

/-- Any bounded continuous nonnegative f such that `f ≥ 1` on K gives an upper bound on the
content of K; namely `λ(K) ≤ Λ f`. -/
theorem rieszContentAux_le {K : Compacts X} {f : X →ᵇ ℝ≥0} (h : ∀ x ∈ K, (1 : ℝ≥0) ≤ f x) :
    rieszContentAux Λ K ≤ Λ f :=
  cinfₛ_le (OrderBot.bddBelow _) ⟨f, ⟨h, rfl⟩⟩
#align riesz_content_aux_le rieszContentAux_le

/-- The Riesz content can be approximated arbitrarily well by evaluating the positive linear
functional on test functions: for any `ε > 0`, there exists a bounded continuous nonnegative
function f on X such that `f ≥ 1` on K and such that `λ(K) ≤ Λ f < λ(K) + ε`. -/
theorem exists_lt_rieszContentAux_add_pos (K : Compacts X) {ε : ℝ≥0} (εpos : 0 < ε) :
    ∃ f : X →ᵇ ℝ≥0, (∀ x ∈ K, (1 : ℝ≥0) ≤ f x) ∧ Λ f < rieszContentAux Λ K + ε :=
  by
  --choose a test function `f` s.t. `Λf = α < λ(K) + ε`
  obtain ⟨α, ⟨⟨f, f_hyp⟩, α_hyp⟩⟩ :=
    exists_lt_of_cinfₛ_lt (riesz_content_aux_image_nonempty Λ K)
      (lt_add_of_pos_right (rieszContentAux Λ K) εpos)
  refine' ⟨f, f_hyp.left, _⟩
  rw [f_hyp.right]
  exact α_hyp
#align exists_lt_riesz_content_aux_add_pos exists_lt_rieszContentAux_add_pos

/-- The Riesz content λ associated to a given positive linear functional Λ is
finitely subadditive: `λ(K₁ ∪ K₂) ≤ λ(K₁) + λ(K₂)` for any compact subsets `K₁, K₂ ⊆ X`. -/
theorem rieszContentAux_sup_le (K1 K2 : Compacts X) :
    rieszContentAux Λ (K1 ⊔ K2) ≤ rieszContentAux Λ K1 + rieszContentAux Λ K2 :=
  by
  apply NNReal.le_of_forall_pos_le_add
  intro ε εpos
  --get test functions s.t. `λ(Ki) ≤ Λfi ≤ λ(Ki) + ε/2, i=1,2`
  obtain ⟨f1, f_test_function_K1⟩ := exists_lt_rieszContentAux_add_pos Λ K1 (half_pos εpos)
  obtain ⟨f2, f_test_function_K2⟩ := exists_lt_rieszContentAux_add_pos Λ K2 (half_pos εpos)
  --let `f := f1 + f2` test function for the content of `K`
  have f_test_function_union : ∀ x ∈ K1 ⊔ K2, (1 : ℝ≥0) ≤ (f1 + f2) x :=
    by
    rintro x (x_in_K1 | x_in_K2)
    · exact le_add_right (f_test_function_K1.left x x_in_K1)
    · exact le_add_left (f_test_function_K2.left x x_in_K2)
  --use that `Λf` is an upper bound for `λ(K1⊔K2)`
  apply (rieszContentAux_le Λ f_test_function_union).trans (le_of_lt _)
  rw [map_add]
  --use that `Λfi` are lower bounds for `λ(Ki) + ε/2`
  apply lt_of_lt_of_le (add_lt_add f_test_function_K1.right f_test_function_K2.right) (le_of_eq _)
  rw [add_assoc, add_comm (ε / 2), add_assoc, add_halves ε, add_assoc]
#align riesz_content_aux_sup_le rieszContentAux_sup_le

end RieszSubadditive


inductive Cat :=
| var (n : Nat) : Cat
deriving DecidableEq

mutual

inductive Obj :  Cat → Type where
| var (C : Cat) (n : Nat) : Obj C
| varApp {C D : Cat} (v : Nat) (X : Obj C) : Obj D
| rAdjApp {C D : Cat} (F : Func C D) (X : Obj D) : Obj C

inductive Func : Cat → Cat → Type where
| id (C : Cat) : Func C C
| compVar {C D E : Cat} (F : Func C D) (v : Nat) : Func C E
| compRAdj {C D E : Cat} (F : Func C D) (G : Func E D) : Func C E

end

@[instance] axiom Func.DecidableEq : {C D : Cat} → DecidableEq (Func C D)

@[instance] axiom Sigma.DecidableEq {α : Type} (β : α → Type) : DecidableEq (Sigma β)

noncomputable def Func.compAux {D E : Cat} (G : Func D E) : (C : Cat) → (F : Func C D) → Func C E :=
@Func.recOn (λ _ _ => Unit) (λ D E G => (C : Cat) → (F : Func C D) → Func C E)
  D E G
  (λ _ _ => ())
  (λ _ _ _ => ())
  (λ _ _ _ _ => ())
  (λ _ _ F => F)
  (λ D v ih C F => compVar (ih _ F) v)
  (λ F G ih _ C H => compRAdj (ih _ H) G)

noncomputable def Func.comp {C D E : Cat} (F : Func C D) (G : Func D E) : Func C E :=
Func.compAux G _ F

noncomputable def Func.app {C D : Cat} (F : Func C D) : (X : Obj C) → Obj D :=
@Func.recOn (λ _ _ => Unit) (λ C D _ => Obj C → Obj D)
  C D F
  (λ _ _ => ())
  (λ _ _ _ => ())
  (λ _ _ _ _ => ())
  (λ _ X => X)
  (λ F v ih X => Obj.varApp v (ih X))
  (λ F G ih _ X => Obj.rAdjApp G (ih X))

@[simp] theorem Func.idApp {C : Cat} (X : Obj C) : (Func.id C).app X = X := rfl

@[simp] theorem Func.compVarApp {C D E : Cat} (F : Func C D) (v : Nat) (X : Obj C) :
  (@Func.compVar C D E F v).app X = Obj.varApp v (F.app X) := rfl

@[simp] theorem Func.compRAdjApp {C D E : Cat} (F : Func C D) (G : Func E D) (X : Obj C) :
  (Func.compRAdj F G).app X = Obj.rAdjApp G (F.app X) := rfl

def Func.var (C D : Cat) (v : Nat) : Func C D :=
Func.compVar (Func.id _) v

def Func.rAdj {C D : Cat} (F : Func D C) : Func C D :=
Func.compRAdj (Func.id _) F

theorem rAdjAppEq {C D : Cat} (F : Func C D) (X : Obj D) :
  F.rAdj.app X = Obj.rAdjApp F X := rfl

-- noncomputable def Obj.cases : {C : Cat} →
--   (X Y : Obj C) → Σ D : Cat, Obj D × Obj D
-- | C, X, Obj.rAdjApp F Y => cases (F.app X) Y
-- | C, X, Obj.var _ v => ⟨C, X, Obj.var _ v⟩
-- | _, X, Obj.varApp v Y => ⟨_, X, Obj.varApp v Y⟩

noncomputable def Obj.cases' {C : Cat}
  (Y : Obj C) : (X : Obj C) → Σ D : Cat, Obj D × Obj D :=
@Obj.recOn (λ C X => Obj C → Σ D : Cat, Obj D × Obj D)
  (λ _ _ _ => Unit)
  C Y
  (λ C v X => ⟨C, X, Obj.var C v⟩)
  (λ v Y ih X => ⟨_, X, Obj.varApp v Y⟩)
  (λ F Y _ ih X => ih (F.app X))
  (λ _ => ())
  (λ _ _ _ => ())
  (λ _ _ _ _ => ())

noncomputable def Obj.cases {C : Cat}
  (X Y : Obj C) : Σ D : Cat, Obj D × Obj D :=
Obj.cases' Y X

inductive Hom : {C : Cat} → (X Y : Obj C) → Type
| mapVar {C D : Cat} {X Y : Obj C} (v : Nat) (f : Hom X Y) :
  Hom ((Func.var C D v).app X) ((Func.var C D v).app Y)
| varCases {C : Cat} (X Y : Obj C) (v : Nat) :
  Hom (X.cases Y).2.1 (X.cases Y).2.2
| counit {C D : Cat} (F : Func C D) (X : Obj D) :
  Hom (F.app (F.rAdj.app X)) X
| restrict {C D : Cat} (F : Func C D) {X : Obj C} {Y : Obj D}
  (f : Hom (F.app X) Y) : Hom X (Obj.rAdjApp F Y)
| comp {C : Cat} {X Y Z : Obj C}
  (f : Hom X Y) (g : Hom Y Z) : Hom X Z
| id {C : Cat} (X : Obj C) : Hom X X

noncomputable def Hom.ofCases : {C : Cat} → (X Y : Obj C) →
  (f : Hom (X.cases Y).2.1 (X.cases Y).2.2) → Hom X Y
| C, X, Obj.rAdjApp F Y, f =>
  Hom.restrict _ (ofCases (F.app X) Y f)
| C, X, Obj.varApp v Y, f => f
| C, X, Obj.var _ _,  f => f

noncomputable def Hom.var {C : Cat} (X Y : Obj C) (v : Nat) : Hom X Y :=
Hom.ofCases _ _ (Hom.varCases _ _ v)

/-
Rewriting rules
  F(f) ∘ F(g) => F (f ∘ g)
  restrict F (counit X) => id (F.rAdj.app X)
  restrict F (F.map f ∘ counit Y) => f
  f ∘ restrict F g = restrict f (F.map f ∘ g)
  F.map (restrict F f) ∘ counit _ = f
  F.map (g ∘ restrict F f) ∘ counit _ = F.map g ∘ f
-/

mutual

inductive Hom₁Aux : {C : Cat} → (X Y : Obj C) → Type
| mapVar {C D : Cat} {X Y : Obj C} (v : Nat) (f : Hom₁ X Y) :
  Hom₁Aux (Obj.varApp v X) (Obj.varApp v Y : Obj D)
| varCases {C : Cat} (X Y : Obj C) (v : Nat) :
  Hom₁Aux (X.cases Y).2.1 (X.cases Y).2.2
| counit {C D : Cat} (F : Func C D) (X : Obj D) :
  Hom₁Aux (F.app (F.rAdj.app X)) X
| restrict {C D : Cat} (F : Func C D) {X : Obj C} {Y : Obj D}
  (f : Hom₁ (F.app X) Y) : Hom₁Aux X (Obj.rAdjApp F Y)

inductive Hom₁ : {C : Cat} → (X Y : Obj C) → Type
| comp {C : Cat} {X Y Z : Obj C} (f : Hom₁ X Y) (g : Hom₁Aux Y Z) : Hom₁ X Z
| id {C : Cat} (X : Obj C) : Hom₁ X X

end

def Hom₁AuxToHom₁ {C : Cat} {X Y : Obj C} (f : Hom₁Aux X Y) : Hom₁ X Y :=
Hom₁.comp (Hom₁.id _) f

def Func.map : {C D : Cat} → {X Y : Obj C} → (F : Func C D) →
  (f : Hom₁ X Y) → Hom₁ (F.app X) (F.app Y)
| _, _, _, _, Func.id _, f => f
| _, _, _, _, Func.compVar F v, f => Hom₁AuxToHom₁ (Hom₁Aux.mapVar v (map F f))
| _, _, _, _, Func.compRAdj F G, f =>
  Hom₁AuxToHom₁ (Hom₁Aux.restrict _
    (by
    simp
  ))

mutual

def Hom₁AuxComp : {C : Cat} → {X Y Y' Z : Obj C} → Hom₁Aux X Y → Hom₁Aux Y' Z →
  Y = Y' → Hom₁ X Z
| _, _, _, _, _, Hom₁Aux.mapVar v f, Hom₁Aux.mapVar w g, h => by
  cases h
  exact Hom₁AuxToHom₁ (Hom₁Aux.mapVar v (Hom₁Comp f g rfl))
| _, _, _, _, _, f, Hom₁Aux.restrict F g, h => by
  cases h
  exact Hom₁Aux.restrict F (Hom₁Comp (Hom₁AuxToHom₁ f) g _)
| _, _, _, _, _, _, _, _ => sorry

def Hom₁Comp : {C : Cat} → {X Y Y' Z : Obj C} → Hom₁ X Y → Hom₁ Y' Z →
  Y = Y' → Hom₁ X Z
| _, _, _, _, _, _, _, _ => sorry

end


/-
Copyright (c) 2015, 2017 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Robert Y. Lewis, Johannes Hölzl, Mario Carneiro, Sébastien Gouëzel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.real.ennreal
import Mathlib.data.finset.intervals
import Mathlib.topology.uniform_space.uniform_embedding
import Mathlib.topology.uniform_space.pi
import Mathlib.topology.uniform_space.uniform_convergence
import Mathlib.PostPort

universes u v u_1 l u_2

namespace Mathlib

/-!
# Extended metric spaces

This file is devoted to the definition and study of `emetric_spaces`, i.e., metric
spaces in which the distance is allowed to take the value ∞. This extended distance is
called `edist`, and takes values in `ennreal`.

Many definitions and theorems expected on emetric spaces are already introduced on uniform spaces and
topological spaces. For example:
  open and closed sets, compactness, completeness, continuity and uniform continuity

The class `emetric_space` therefore extends `uniform_space` (and `topological_space`).
-/

/-- Characterizing uniformities associated to a (generalized) distance function `D`
in terms of the elements of the uniformity. -/
theorem uniformity_dist_of_mem_uniformity {α : Type u} {β : Type v} [linear_order β] {U : filter (α × α)} (z : β) (D : α → α → β) (H : ∀ (s : set (α × α)), s ∈ U ↔ ∃ (ε : β), ∃ (H : ε > z), ∀ {a b : α}, D a b < ε → (a, b) ∈ s) : U =
  infi
    fun (ε : β) => infi fun (H : ε > z) => filter.principal (set_of fun (p : α × α) => D (prod.fst p) (prod.snd p) < ε) := sorry

class has_edist (α : Type u_1)
where
  edist : α → α → ennreal

/-- Creating a uniform space from an extended distance. -/
def uniform_space_of_edist {α : Type u} (edist : α → α → ennreal) (edist_self : ∀ (x : α), edist x x = 0) (edist_comm : ∀ (x y : α), edist x y = edist y x) (edist_triangle : ∀ (x y z : α), edist x z ≤ edist x y + edist y z) : uniform_space α :=
  uniform_space.of_core
    (uniform_space.core.mk
      (infi
        fun (ε : ennreal) =>
          infi fun (H : ε > 0) => filter.principal (set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < ε))
      sorry sorry sorry)

-- the uniform structure is embedded in the emetric space structure

-- to avoid instance diamond issues. See Note [forgetful inheritance].

/-- Extended metric spaces, with an extended distance `edist` possibly taking the
value ∞

Each emetric space induces a canonical `uniform_space` and hence a canonical `topological_space`.
This is enforced in the type class definition, by extending the `uniform_space` structure. When
instantiating an `emetric_space` structure, the uniformity fields are not necessary, they will be
filled in by default. There is a default value for the uniformity, that can be substituted
in cases of interest, for instance when instantiating an `emetric_space` structure
on a product.

Continuity of `edist` is proved in `topology.instances.ennreal`
-/
class emetric_space (α : Type u)
extends has_edist α, emetric_space.to_uniform_space._default #2 #1 #0 α _to_has_edist = id (uniform_space_of_edist edist #0 α _to_has_edist), uniform_space #2, uniform_space α
where
  edist_self : ∀ (x : α), edist x x = 0
  eq_of_edist_eq_zero : ∀ {x y : α}, edist x y = 0 → x = y
  edist_comm : ∀ (x y : α), edist x y = edist y x
  edist_triangle : ∀ (x y z : α), edist x z ≤ edist x y + edist y z
  to_uniform_space : uniform_space α
  uniformity_edist : autoParam
  (uniformity α =
    infi
      fun (ε : ennreal) =>
        infi fun (H : ε > 0) => filter.principal (set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < ε))
  (Lean.Syntax.ident Lean.SourceInfo.none (String.toSubstring "Mathlib.control_laws_tac")
    (Lean.Name.mkStr (Lean.Name.mkStr Lean.Name.anonymous "Mathlib") "control_laws_tac") [])

/- emetric spaces are less common than metric spaces. Therefore, we work in a dedicated
namespace, while notions associated to metric spaces are mostly in the root namespace. -/

protected instance emetric_space.to_uniform_space' {α : Type u} [emetric_space α] : uniform_space α :=
  emetric_space.to_uniform_space

/-- Characterize the equality of points by the vanishing of their extended distance -/
@[simp] theorem edist_eq_zero {α : Type u} [emetric_space α] {x : α} {y : α} : edist x y = 0 ↔ x = y :=
  { mp := eq_of_edist_eq_zero, mpr := fun (this : x = y) => this ▸ edist_self x }

@[simp] theorem zero_eq_edist {α : Type u} [emetric_space α] {x : α} {y : α} : 0 = edist x y ↔ x = y :=
  { mp := fun (h : 0 = edist x y) => eq_of_edist_eq_zero (Eq.symm h),
    mpr := fun (this : x = y) => this ▸ Eq.symm (edist_self x) }

theorem edist_le_zero {α : Type u} [emetric_space α] {x : α} {y : α} : edist x y ≤ 0 ↔ x = y :=
  iff.trans nonpos_iff_eq_zero edist_eq_zero

/-- Triangle inequality for the extended distance -/
theorem edist_triangle_left {α : Type u} [emetric_space α] (x : α) (y : α) (z : α) : edist x y ≤ edist z x + edist z y :=
  eq.mpr (id (Eq._oldrec (Eq.refl (edist x y ≤ edist z x + edist z y)) (edist_comm z x))) (edist_triangle x z y)

theorem edist_triangle_right {α : Type u} [emetric_space α] (x : α) (y : α) (z : α) : edist x y ≤ edist x z + edist y z :=
  eq.mpr (id (Eq._oldrec (Eq.refl (edist x y ≤ edist x z + edist y z)) (edist_comm y z))) (edist_triangle x z y)

theorem edist_triangle4 {α : Type u} [emetric_space α] (x : α) (y : α) (z : α) (t : α) : edist x t ≤ edist x y + edist y z + edist z t :=
  le_trans (edist_triangle x z t) (add_le_add_right (edist_triangle x y z) (edist z t))

/-- The triangle (polygon) inequality for sequences of points; `finset.Ico` version. -/
theorem edist_le_Ico_sum_edist {α : Type u} [emetric_space α] (f : ℕ → α) {m : ℕ} {n : ℕ} (h : m ≤ n) : edist (f m) (f n) ≤ finset.sum (finset.Ico m n) fun (i : ℕ) => edist (f i) (f (i + 1)) := sorry

/-- The triangle (polygon) inequality for sequences of points; `finset.range` version. -/
theorem edist_le_range_sum_edist {α : Type u} [emetric_space α] (f : ℕ → α) (n : ℕ) : edist (f 0) (f n) ≤ finset.sum (finset.range n) fun (i : ℕ) => edist (f i) (f (i + 1)) :=
  finset.Ico.zero_bot n ▸ edist_le_Ico_sum_edist f (nat.zero_le n)

/-- A version of `edist_le_Ico_sum_edist` with each intermediate distance replaced
with an upper estimate. -/
theorem edist_le_Ico_sum_of_edist_le {α : Type u} [emetric_space α] {f : ℕ → α} {m : ℕ} {n : ℕ} (hmn : m ≤ n) {d : ℕ → ennreal} (hd : ∀ {k : ℕ}, m ≤ k → k < n → edist (f k) (f (k + 1)) ≤ d k) : edist (f m) (f n) ≤ finset.sum (finset.Ico m n) fun (i : ℕ) => d i := sorry

/-- A version of `edist_le_range_sum_edist` with each intermediate distance replaced
with an upper estimate. -/
theorem edist_le_range_sum_of_edist_le {α : Type u} [emetric_space α] {f : ℕ → α} (n : ℕ) {d : ℕ → ennreal} (hd : ∀ {k : ℕ}, k < n → edist (f k) (f (k + 1)) ≤ d k) : edist (f 0) (f n) ≤ finset.sum (finset.range n) fun (i : ℕ) => d i :=
  finset.Ico.zero_bot n ▸ edist_le_Ico_sum_of_edist_le (zero_le n) fun (_x : ℕ) (_x_1 : 0 ≤ _x) => hd

/-- Two points coincide if their distance is `< ε` for all positive ε -/
theorem eq_of_forall_edist_le {α : Type u} [emetric_space α] {x : α} {y : α} (h : ∀ (ε : ennreal), ε > 0 → edist x y ≤ ε) : x = y :=
  eq_of_edist_eq_zero (eq_of_le_of_forall_le_of_dense bot_le h)

/-- Reformulation of the uniform structure in terms of the extended distance -/
theorem uniformity_edist {α : Type u} [emetric_space α] : uniformity α =
  infi
    fun (ε : ennreal) =>
      infi fun (H : ε > 0) => filter.principal (set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < ε) :=
  emetric_space.uniformity_edist

theorem uniformity_basis_edist {α : Type u} [emetric_space α] : filter.has_basis (uniformity α) (fun (ε : ennreal) => 0 < ε)
  fun (ε : ennreal) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < ε := sorry

/-- Characterization of the elements of the uniformity in terms of the extended distance -/
theorem mem_uniformity_edist {α : Type u} [emetric_space α] {s : set (α × α)} : s ∈ uniformity α ↔ ∃ (ε : ennreal), ∃ (H : ε > 0), ∀ {a b : α}, edist a b < ε → (a, b) ∈ s :=
  filter.has_basis.mem_uniformity_iff uniformity_basis_edist

/-- Given `f : β → ennreal`, if `f` sends `{i | p i}` to a set of positive numbers
accumulating to zero, then `f i`-neighborhoods of the diagonal form a basis of `𝓤 α`.

For specific bases see `uniformity_basis_edist`, `uniformity_basis_edist'`,
`uniformity_basis_edist_nnreal`, and `uniformity_basis_edist_inv_nat`. -/
protected theorem emetric.mk_uniformity_basis {α : Type u} [emetric_space α] {β : Type u_1} {p : β → Prop} {f : β → ennreal} (hf₀ : ∀ (x : β), p x → 0 < f x) (hf : ∀ (ε : ennreal), 0 < ε → ∃ (x : β), ∃ (hx : p x), f x ≤ ε) : filter.has_basis (uniformity α) p fun (x : β) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < f x := sorry

/-- Given `f : β → ennreal`, if `f` sends `{i | p i}` to a set of positive numbers
accumulating to zero, then closed `f i`-neighborhoods of the diagonal form a basis of `𝓤 α`.

For specific bases see `uniformity_basis_edist_le` and `uniformity_basis_edist_le'`. -/
protected theorem emetric.mk_uniformity_basis_le {α : Type u} [emetric_space α] {β : Type u_1} {p : β → Prop} {f : β → ennreal} (hf₀ : ∀ (x : β), p x → 0 < f x) (hf : ∀ (ε : ennreal), 0 < ε → ∃ (x : β), ∃ (hx : p x), f x ≤ ε) : filter.has_basis (uniformity α) p fun (x : β) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) ≤ f x := sorry

theorem uniformity_basis_edist_le {α : Type u} [emetric_space α] : filter.has_basis (uniformity α) (fun (ε : ennreal) => 0 < ε)
  fun (ε : ennreal) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) ≤ ε :=
  emetric.mk_uniformity_basis_le (fun (_x : ennreal) => id)
    fun (ε : ennreal) (ε₀ : 0 < ε) => Exists.intro ε (Exists.intro ε₀ (le_refl ε))

theorem uniformity_basis_edist' {α : Type u} [emetric_space α] (ε' : ennreal) (hε' : 0 < ε') : filter.has_basis (uniformity α) (fun (ε : ennreal) => ε ∈ set.Ioo 0 ε')
  fun (ε : ennreal) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < ε := sorry

theorem uniformity_basis_edist_le' {α : Type u} [emetric_space α] (ε' : ennreal) (hε' : 0 < ε') : filter.has_basis (uniformity α) (fun (ε : ennreal) => ε ∈ set.Ioo 0 ε')
  fun (ε : ennreal) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) ≤ ε := sorry

theorem uniformity_basis_edist_nnreal {α : Type u} [emetric_space α] : filter.has_basis (uniformity α) (fun (ε : nnreal) => 0 < ε)
  fun (ε : nnreal) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < ↑ε := sorry

theorem uniformity_basis_edist_inv_nat {α : Type u} [emetric_space α] : filter.has_basis (uniformity α) (fun (_x : ℕ) => True)
  fun (n : ℕ) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < (↑n⁻¹) := sorry

/-- Fixed size neighborhoods of the diagonal belong to the uniform structure -/
theorem edist_mem_uniformity {α : Type u} [emetric_space α] {ε : ennreal} (ε0 : 0 < ε) : (set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < ε) ∈ uniformity α :=
  iff.mpr mem_uniformity_edist (Exists.intro ε (Exists.intro ε0 fun (a b : α) => id))

namespace emetric

theorem uniformity_has_countable_basis {α : Type u} [emetric_space α] : filter.is_countably_generated (uniformity α) :=
  filter.is_countably_generated_of_seq
    (Exists.intro (fun (i : ℕ) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < (↑i⁻¹))
      (filter.has_basis.eq_infi uniformity_basis_edist_inv_nat))

/-- ε-δ characterization of uniform continuity on a set for emetric spaces -/
theorem uniform_continuous_on_iff {α : Type u} {β : Type v} [emetric_space α] [emetric_space β] {f : α → β} {s : set α} : uniform_continuous_on f s ↔
  ∀ (ε : ennreal) (H : ε > 0),
    ∃ (δ : ennreal), ∃ (H : δ > 0), ∀ {a b : α}, a ∈ s → b ∈ s → edist a b < δ → edist (f a) (f b) < ε :=
  filter.has_basis.uniform_continuous_on_iff uniformity_basis_edist uniformity_basis_edist

/-- ε-δ characterization of uniform continuity on emetric spaces -/
theorem uniform_continuous_iff {α : Type u} {β : Type v} [emetric_space α] [emetric_space β] {f : α → β} : uniform_continuous f ↔
  ∀ (ε : ennreal) (H : ε > 0), ∃ (δ : ennreal), ∃ (H : δ > 0), ∀ {a b : α}, edist a b < δ → edist (f a) (f b) < ε :=
  filter.has_basis.uniform_continuous_iff uniformity_basis_edist uniformity_basis_edist

/-- ε-δ characterization of uniform embeddings on emetric spaces -/
theorem uniform_embedding_iff {α : Type u} {β : Type v} [emetric_space α] [emetric_space β] {f : α → β} : uniform_embedding f ↔
  function.injective f ∧
    uniform_continuous f ∧
      ∀ (δ : ennreal) (H : δ > 0), ∃ (ε : ennreal), ∃ (H : ε > 0), ∀ {a b : α}, edist (f a) (f b) < ε → edist a b < δ := sorry

/-- A map between emetric spaces is a uniform embedding if and only if the edistance between `f x`
and `f y` is controlled in terms of the distance between `x` and `y` and conversely. -/
theorem uniform_embedding_iff' {α : Type u} {β : Type v} [emetric_space α] [emetric_space β] {f : α → β} : uniform_embedding f ↔
  (∀ (ε : ennreal) (H : ε > 0), ∃ (δ : ennreal), ∃ (H : δ > 0), ∀ {a b : α}, edist a b < δ → edist (f a) (f b) < ε) ∧
    ∀ (δ : ennreal) (H : δ > 0), ∃ (ε : ennreal), ∃ (H : ε > 0), ∀ {a b : α}, edist (f a) (f b) < ε → edist a b < δ := sorry

/-- ε-δ characterization of Cauchy sequences on emetric spaces -/
protected theorem cauchy_iff {α : Type u} [emetric_space α] {f : filter α} : cauchy f ↔ f ≠ ⊥ ∧ ∀ (ε : ennreal) (H : ε > 0), ∃ (t : set α), ∃ (H : t ∈ f), ∀ (x y : α), x ∈ t → y ∈ t → edist x y < ε :=
  filter.has_basis.cauchy_iff uniformity_basis_edist

/-- A very useful criterion to show that a space is complete is to show that all sequences
which satisfy a bound of the form `edist (u n) (u m) < B N` for all `n m ≥ N` are
converging. This is often applied for `B N = 2^{-N}`, i.e., with a very fast convergence to
`0`, which makes it possible to use arguments of converging series, while this is impossible
to do in general for arbitrary Cauchy sequences. -/
theorem complete_of_convergent_controlled_sequences {α : Type u} [emetric_space α] (B : ℕ → ennreal) (hB : ∀ (n : ℕ), 0 < B n) (H : ∀ (u : ℕ → α),
  (∀ (N n m : ℕ), N ≤ n → N ≤ m → edist (u n) (u m) < B N) → ∃ (x : α), filter.tendsto u filter.at_top (nhds x)) : complete_space α :=
  uniform_space.complete_of_convergent_controlled_sequences uniformity_has_countable_basis
    (fun (n : ℕ) => set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < B n)
    (fun (n : ℕ) => edist_mem_uniformity (hB n)) H

/-- A sequentially complete emetric space is complete. -/
theorem complete_of_cauchy_seq_tendsto {α : Type u} [emetric_space α] : (∀ (u : ℕ → α), cauchy_seq u → ∃ (a : α), filter.tendsto u filter.at_top (nhds a)) → complete_space α :=
  uniform_space.complete_of_cauchy_seq_tendsto uniformity_has_countable_basis

/-- Expressing locally uniform convergence on a set using `edist`. -/
theorem tendsto_locally_uniformly_on_iff {α : Type u} {β : Type v} [emetric_space α] {ι : Type u_1} [topological_space β] {F : ι → β → α} {f : β → α} {p : filter ι} {s : set β} : tendsto_locally_uniformly_on F f p s ↔
  ∀ (ε : ennreal) (H : ε > 0) (x : β) (H : x ∈ s),
    ∃ (t : set β),
      ∃ (H : t ∈ nhds_within x s), filter.eventually (fun (n : ι) => ∀ (y : β), y ∈ t → edist (f y) (F n y) < ε) p := sorry

/-- Expressing uniform convergence on a set using `edist`. -/
theorem tendsto_uniformly_on_iff {α : Type u} {β : Type v} [emetric_space α] {ι : Type u_1} {F : ι → β → α} {f : β → α} {p : filter ι} {s : set β} : tendsto_uniformly_on F f p s ↔
  ∀ (ε : ennreal), ε > 0 → filter.eventually (fun (n : ι) => ∀ (x : β), x ∈ s → edist (f x) (F n x) < ε) p := sorry

/-- Expressing locally uniform convergence using `edist`. -/
theorem tendsto_locally_uniformly_iff {α : Type u} {β : Type v} [emetric_space α] {ι : Type u_1} [topological_space β] {F : ι → β → α} {f : β → α} {p : filter ι} : tendsto_locally_uniformly F f p ↔
  ∀ (ε : ennreal) (H : ε > 0) (x : β),
    ∃ (t : set β), ∃ (H : t ∈ nhds x), filter.eventually (fun (n : ι) => ∀ (y : β), y ∈ t → edist (f y) (F n y) < ε) p := sorry

/-- Expressing uniform convergence using `edist`. -/
theorem tendsto_uniformly_iff {α : Type u} {β : Type v} [emetric_space α] {ι : Type u_1} {F : ι → β → α} {f : β → α} {p : filter ι} : tendsto_uniformly F f p ↔
  ∀ (ε : ennreal), ε > 0 → filter.eventually (fun (n : ι) => ∀ (x : β), edist (f x) (F n x) < ε) p := sorry

end emetric

/-- An emetric space is separated -/
protected instance to_separated {α : Type u} [emetric_space α] : separated_space α :=
  iff.mpr separated_def
    fun (x y : α) (h : ∀ (r : set (α × α)), r ∈ uniformity α → (x, y) ∈ r) =>
      eq_of_forall_edist_le
        fun (ε : ennreal) (ε0 : ε > 0) =>
          le_of_lt (h (set_of fun (p : α × α) => edist (prod.fst p) (prod.snd p) < ε) (edist_mem_uniformity ε0))

/-- Auxiliary function to replace the uniformity on an emetric space with
a uniformity which is equal to the original one, but maybe not defeq.
This is useful if one wants to construct an emetric space with a
specified uniformity. See Note [forgetful inheritance] explaining why having definitionally
the right uniformity is often important.
-/
def emetric_space.replace_uniformity {α : Type u_1} [U : uniform_space α] (m : emetric_space α) (H : uniformity α = uniformity α) : emetric_space α :=
  emetric_space.mk edist_self eq_of_edist_eq_zero edist_comm edist_triangle U

/-- The extended metric induced by an injective function taking values in an emetric space. -/
def emetric_space.induced {α : Type u_1} {β : Type u_2} (f : α → β) (hf : function.injective f) (m : emetric_space β) : emetric_space α :=
  emetric_space.mk sorry sorry sorry sorry (uniform_space.comap f emetric_space.to_uniform_space)

/-- Emetric space instance on subsets of emetric spaces -/
protected instance subtype.emetric_space {α : Type u_1} {p : α → Prop} [t : emetric_space α] : emetric_space (Subtype p) :=
  emetric_space.induced coe sorry t

/-- The extended distance on a subset of an emetric space is the restriction of
the original distance, by definition -/
theorem subtype.edist_eq {α : Type u} [emetric_space α] {p : α → Prop} (x : Subtype p) (y : Subtype p) : edist x y = edist ↑x ↑y :=
  rfl

/-- The product of two emetric spaces, with the max distance, is an extended
metric spaces. We make sure that the uniform structure thus constructed is the one
corresponding to the product of uniform spaces, to avoid diamond problems. -/
protected instance prod.emetric_space_max {α : Type u} {β : Type v} [emetric_space α] [emetric_space β] : emetric_space (α × β) :=
  emetric_space.mk sorry sorry sorry sorry prod.uniform_space

theorem prod.edist_eq {α : Type u} {β : Type v} [emetric_space α] [emetric_space β] (x : α × β) (y : α × β) : edist x y = max (edist (prod.fst x) (prod.fst y)) (edist (prod.snd x) (prod.snd y)) :=
  rfl

/-- The product of a finite number of emetric spaces, with the max distance, is still
an emetric space.
This construction would also work for infinite products, but it would not give rise
to the product topology. Hence, we only formalize it in the good situation of finitely many
spaces. -/
protected instance emetric_space_pi {β : Type v} {π : β → Type u_1} [fintype β] [(b : β) → emetric_space (π b)] : emetric_space ((b : β) → π b) :=
  emetric_space.mk sorry sorry sorry sorry (Pi.uniform_space fun (b : β) => π b)

theorem edist_pi_def {β : Type v} {π : β → Type u_1} [fintype β] [(b : β) → emetric_space (π b)] (f : (b : β) → π b) (g : (b : β) → π b) : edist f g = finset.sup finset.univ fun (b : β) => edist (f b) (g b) :=
  rfl

@[simp] theorem edist_pi_const {α : Type u} {β : Type v} [emetric_space α] [fintype β] [Nonempty β] (a : α) (b : α) : (edist (fun (x : β) => a) fun (_x : β) => b) = edist a b :=
  finset.sup_const finset.univ_nonempty (edist a b)

namespace emetric

/-- `emetric.ball x ε` is the set of all points `y` with `edist y x < ε` -/
def ball {α : Type u} [emetric_space α] (x : α) (ε : ennreal) : set α :=
  set_of fun (y : α) => edist y x < ε

@[simp] theorem mem_ball {α : Type u} [emetric_space α] {x : α} {y : α} {ε : ennreal} : y ∈ ball x ε ↔ edist y x < ε :=
  iff.rfl

theorem mem_ball' {α : Type u} [emetric_space α] {x : α} {y : α} {ε : ennreal} : y ∈ ball x ε ↔ edist x y < ε :=
  eq.mpr (id (Eq._oldrec (Eq.refl (y ∈ ball x ε ↔ edist x y < ε)) (edist_comm x y))) (iff.refl (y ∈ ball x ε))

/-- `emetric.closed_ball x ε` is the set of all points `y` with `edist y x ≤ ε` -/
def closed_ball {α : Type u} [emetric_space α] (x : α) (ε : ennreal) : set α :=
  set_of fun (y : α) => edist y x ≤ ε

@[simp] theorem mem_closed_ball {α : Type u} [emetric_space α] {x : α} {y : α} {ε : ennreal} : y ∈ closed_ball x ε ↔ edist y x ≤ ε :=
  iff.rfl

theorem ball_subset_closed_ball {α : Type u} [emetric_space α] {x : α} {ε : ennreal} : ball x ε ⊆ closed_ball x ε :=
  fun (y : α) =>
    eq.mpr (id (imp_congr_eq (propext mem_ball) (propext mem_closed_ball))) fun (h : edist y x < ε) => le_of_lt h

theorem pos_of_mem_ball {α : Type u} [emetric_space α] {x : α} {y : α} {ε : ennreal} (hy : y ∈ ball x ε) : 0 < ε :=
  lt_of_le_of_lt (zero_le (edist y x)) hy

theorem mem_ball_self {α : Type u} [emetric_space α] {x : α} {ε : ennreal} (h : 0 < ε) : x ∈ ball x ε :=
  (fun (this : edist x x < ε) => this) (eq.mpr (id (Eq._oldrec (Eq.refl (edist x x < ε)) (edist_self x))) h)

theorem mem_closed_ball_self {α : Type u} [emetric_space α] {x : α} {ε : ennreal} : x ∈ closed_ball x ε :=
  (fun (this : edist x x ≤ ε) => this) (eq.mpr (id (Eq._oldrec (Eq.refl (edist x x ≤ ε)) (edist_self x))) bot_le)

theorem mem_ball_comm {α : Type u} [emetric_space α] {x : α} {y : α} {ε : ennreal} : x ∈ ball y ε ↔ y ∈ ball x ε := sorry

theorem ball_subset_ball {α : Type u} [emetric_space α] {x : α} {ε₁ : ennreal} {ε₂ : ennreal} (h : ε₁ ≤ ε₂) : ball x ε₁ ⊆ ball x ε₂ :=
  fun (y : α) (yx : edist y x < ε₁) => lt_of_lt_of_le yx h

theorem closed_ball_subset_closed_ball {α : Type u} [emetric_space α] {x : α} {ε₁ : ennreal} {ε₂ : ennreal} (h : ε₁ ≤ ε₂) : closed_ball x ε₁ ⊆ closed_ball x ε₂ :=
  fun (y : α) (yx : edist y x ≤ ε₁) => le_trans yx h

theorem ball_disjoint {α : Type u} [emetric_space α] {x : α} {y : α} {ε₁ : ennreal} {ε₂ : ennreal} (h : ε₁ + ε₂ ≤ edist x y) : ball x ε₁ ∩ ball y ε₂ = ∅ := sorry

theorem ball_subset {α : Type u} [emetric_space α] {x : α} {y : α} {ε₁ : ennreal} {ε₂ : ennreal} (h : edist x y + ε₁ ≤ ε₂) (h' : edist x y < ⊤) : ball x ε₁ ⊆ ball y ε₂ := sorry

theorem exists_ball_subset_ball {α : Type u} [emetric_space α] {x : α} {y : α} {ε : ennreal} (h : y ∈ ball x ε) : ∃ (ε' : ennreal), ∃ (H : ε' > 0), ball y ε' ⊆ ball x ε := sorry

theorem ball_eq_empty_iff {α : Type u} [emetric_space α] {x : α} {ε : ennreal} : ball x ε = ∅ ↔ ε = 0 := sorry

/-- Relation “two points are at a finite edistance” is an equivalence relation. -/
def edist_lt_top_setoid {α : Type u} [emetric_space α] : setoid α :=
  setoid.mk (fun (x y : α) => edist x y < ⊤) sorry

@[simp] theorem ball_zero {α : Type u} [emetric_space α] {x : α} : ball x 0 = ∅ :=
  eq.mpr (id (Eq._oldrec (Eq.refl (ball x 0 = ∅)) (propext ball_eq_empty_iff))) (Eq.refl 0)

theorem nhds_basis_eball {α : Type u} [emetric_space α] {x : α} : filter.has_basis (nhds x) (fun (ε : ennreal) => 0 < ε) (ball x) :=
  nhds_basis_uniformity uniformity_basis_edist

theorem nhds_basis_closed_eball {α : Type u} [emetric_space α] {x : α} : filter.has_basis (nhds x) (fun (ε : ennreal) => 0 < ε) (closed_ball x) :=
  nhds_basis_uniformity uniformity_basis_edist_le

theorem nhds_eq {α : Type u} [emetric_space α] {x : α} : nhds x = infi fun (ε : ennreal) => infi fun (H : ε > 0) => filter.principal (ball x ε) :=
  filter.has_basis.eq_binfi nhds_basis_eball

theorem mem_nhds_iff {α : Type u} [emetric_space α] {x : α} {s : set α} : s ∈ nhds x ↔ ∃ (ε : ennreal), ∃ (H : ε > 0), ball x ε ⊆ s :=
  filter.has_basis.mem_iff nhds_basis_eball

theorem is_open_iff {α : Type u} [emetric_space α] {s : set α} : is_open s ↔ ∀ (x : α) (H : x ∈ s), ∃ (ε : ennreal), ∃ (H : ε > 0), ball x ε ⊆ s := sorry

theorem is_open_ball {α : Type u} [emetric_space α] {x : α} {ε : ennreal} : is_open (ball x ε) :=
  iff.mpr is_open_iff fun (y : α) => exists_ball_subset_ball

theorem is_closed_ball_top {α : Type u} [emetric_space α] {x : α} : is_closed (ball x ⊤) := sorry

theorem ball_mem_nhds {α : Type u} [emetric_space α] (x : α) {ε : ennreal} (ε0 : 0 < ε) : ball x ε ∈ nhds x :=
  mem_nhds_sets is_open_ball (mem_ball_self ε0)

theorem ball_prod_same {α : Type u} {β : Type v} [emetric_space α] [emetric_space β] (x : α) (y : β) (r : ennreal) : set.prod (ball x r) (ball y r) = ball (x, y) r :=
  set.ext fun (z : α × β) => iff.symm max_lt_iff

theorem closed_ball_prod_same {α : Type u} {β : Type v} [emetric_space α] [emetric_space β] (x : α) (y : β) (r : ennreal) : set.prod (closed_ball x r) (closed_ball y r) = closed_ball (x, y) r :=
  set.ext fun (z : α × β) => iff.symm max_le_iff

/-- ε-characterization of the closure in emetric spaces -/
theorem mem_closure_iff {α : Type u} [emetric_space α] {x : α} {s : set α} : x ∈ closure s ↔ ∀ (ε : ennreal) (H : ε > 0), ∃ (y : α), ∃ (H : y ∈ s), edist x y < ε := sorry

theorem tendsto_nhds {α : Type u} {β : Type v} [emetric_space α] {f : filter β} {u : β → α} {a : α} : filter.tendsto u f (nhds a) ↔ ∀ (ε : ennreal), ε > 0 → filter.eventually (fun (x : β) => edist (u x) a < ε) f :=
  filter.has_basis.tendsto_right_iff nhds_basis_eball

theorem tendsto_at_top {α : Type u} {β : Type v} [emetric_space α] [Nonempty β] [semilattice_sup β] {u : β → α} {a : α} : filter.tendsto u filter.at_top (nhds a) ↔ ∀ (ε : ennreal), ε > 0 → ∃ (N : β), ∀ (n : β), n ≥ N → edist (u n) a < ε := sorry

/-- In an emetric space, Cauchy sequences are characterized by the fact that, eventually,
the edistance between its elements is arbitrarily small -/
theorem cauchy_seq_iff {α : Type u} {β : Type v} [emetric_space α] [Nonempty β] [semilattice_sup β] {u : β → α} : cauchy_seq u ↔ ∀ (ε : ennreal), ε > 0 → ∃ (N : β), ∀ (m n : β), m ≥ N → n ≥ N → edist (u m) (u n) < ε :=
  filter.has_basis.cauchy_seq_iff uniformity_basis_edist

/-- A variation around the emetric characterization of Cauchy sequences -/
theorem cauchy_seq_iff' {α : Type u} {β : Type v} [emetric_space α] [Nonempty β] [semilattice_sup β] {u : β → α} : cauchy_seq u ↔ ∀ (ε : ennreal), ε > 0 → ∃ (N : β), ∀ (n : β), n ≥ N → edist (u n) (u N) < ε :=
  filter.has_basis.cauchy_seq_iff' uniformity_basis_edist

/-- A variation of the emetric characterization of Cauchy sequences that deals with
`ℝ≥0` upper bounds. -/
theorem cauchy_seq_iff_nnreal {α : Type u} {β : Type v} [emetric_space α] [Nonempty β] [semilattice_sup β] {u : β → α} : cauchy_seq u ↔ ∀ (ε : nnreal), 0 < ε → ∃ (N : β), ∀ (n : β), N ≤ n → edist (u n) (u N) < ↑ε :=
  filter.has_basis.cauchy_seq_iff' uniformity_basis_edist_nnreal

theorem totally_bounded_iff {α : Type u} [emetric_space α] {s : set α} : totally_bounded s ↔
  ∀ (ε : ennreal) (H : ε > 0),
    ∃ (t : set α), set.finite t ∧ s ⊆ set.Union fun (y : α) => set.Union fun (H : y ∈ t) => ball y ε := sorry

theorem totally_bounded_iff' {α : Type u} [emetric_space α] {s : set α} : totally_bounded s ↔
  ∀ (ε : ennreal) (H : ε > 0),
    ∃ (t : set α), ∃ (H : t ⊆ s), set.finite t ∧ s ⊆ set.Union fun (y : α) => set.Union fun (H : y ∈ t) => ball y ε := sorry

/-- A compact set in an emetric space is separable, i.e., it is the closure of a countable set -/
theorem countable_closure_of_compact {α : Type u} [emetric_space α] {s : set α} (hs : is_compact s) : ∃ (t : set α), ∃ (H : t ⊆ s), set.countable t ∧ s = closure t := sorry

--    assume e, finite_cover_balls_of_compact hs,

protected instance topological_space.first_countable_topology (α : Type u) [emetric_space α] : topological_space.first_countable_topology α :=
  uniform_space.first_countable_topology uniformity_has_countable_basis

/-- A separable emetric space is second countable: one obtains a countable basis by taking
the balls centered at points in a dense subset, and with rational radii. We do not register
this as an instance, as there is already an instance going in the other direction
from second countable spaces to separable spaces, and we want to avoid loops. -/
theorem second_countable_of_separable (α : Type u) [emetric_space α] [topological_space.separable_space α] : topological_space.second_countable_topology α :=
  uniform_space.second_countable_of_separable uniformity_has_countable_basis

/-- The diameter of a set in an emetric space, named `emetric.diam` -/
def diam {α : Type u} [emetric_space α] (s : set α) : ennreal :=
  supr fun (x : α) => supr fun (H : x ∈ s) => supr fun (y : α) => supr fun (H : y ∈ s) => edist x y

theorem diam_le_iff_forall_edist_le {α : Type u} [emetric_space α] {s : set α} {d : ennreal} : diam s ≤ d ↔ ∀ (x : α), x ∈ s → ∀ (y : α), y ∈ s → edist x y ≤ d := sorry

/-- If two points belong to some set, their edistance is bounded by the diameter of the set -/
theorem edist_le_diam_of_mem {α : Type u} [emetric_space α] {x : α} {y : α} {s : set α} (hx : x ∈ s) (hy : y ∈ s) : edist x y ≤ diam s :=
  iff.mp diam_le_iff_forall_edist_le (le_refl (diam s)) x hx y hy

/-- If the distance between any two points in a set is bounded by some constant, this constant
bounds the diameter. -/
theorem diam_le_of_forall_edist_le {α : Type u} [emetric_space α] {s : set α} {d : ennreal} (h : ∀ (x : α), x ∈ s → ∀ (y : α), y ∈ s → edist x y ≤ d) : diam s ≤ d :=
  iff.mpr diam_le_iff_forall_edist_le h

/-- The diameter of a subsingleton vanishes. -/
theorem diam_subsingleton {α : Type u} [emetric_space α] {s : set α} (hs : set.subsingleton s) : diam s = 0 :=
  iff.mp nonpos_iff_eq_zero
    (diam_le_of_forall_edist_le
      fun (x : α) (hx : x ∈ s) (y : α) (hy : y ∈ s) => Eq.symm (hs hx hy) ▸ edist_self y ▸ le_refl (edist y y))

/-- The diameter of the empty set vanishes -/
@[simp] theorem diam_empty {α : Type u} [emetric_space α] : diam ∅ = 0 :=
  diam_subsingleton set.subsingleton_empty

/-- The diameter of a singleton vanishes -/
@[simp] theorem diam_singleton {α : Type u} [emetric_space α] {x : α} : diam (singleton x) = 0 :=
  diam_subsingleton set.subsingleton_singleton

theorem diam_eq_zero_iff {α : Type u} [emetric_space α] {s : set α} : diam s = 0 ↔ set.subsingleton s := sorry

theorem diam_pos_iff {α : Type u} [emetric_space α] {s : set α} : 0 < diam s ↔ ∃ (x : α), ∃ (H : x ∈ s), ∃ (y : α), ∃ (H : y ∈ s), x ≠ y := sorry

theorem diam_insert {α : Type u} [emetric_space α] {x : α} {s : set α} : diam (insert x s) = max (supr fun (y : α) => supr fun (H : y ∈ s) => edist x y) (diam s) := sorry

theorem diam_pair {α : Type u} [emetric_space α] {x : α} {y : α} : diam (insert x (singleton y)) = edist x y := sorry

theorem diam_triple {α : Type u} [emetric_space α] {x : α} {y : α} {z : α} : diam (insert x (insert y (singleton z))) = max (max (edist x y) (edist x z)) (edist y z) := sorry

/-- The diameter is monotonous with respect to inclusion -/
theorem diam_mono {α : Type u} [emetric_space α] {s : set α} {t : set α} (h : s ⊆ t) : diam s ≤ diam t :=
  diam_le_of_forall_edist_le fun (x : α) (hx : x ∈ s) (y : α) (hy : y ∈ s) => edist_le_diam_of_mem (h hx) (h hy)

/-- The diameter of a union is controlled by the diameter of the sets, and the edistance
between two points in the sets. -/
theorem diam_union {α : Type u} [emetric_space α] {x : α} {y : α} {s : set α} {t : set α} (xs : x ∈ s) (yt : y ∈ t) : diam (s ∪ t) ≤ diam s + edist x y + diam t := sorry

theorem diam_union' {α : Type u} [emetric_space α] {s : set α} {t : set α} (h : set.nonempty (s ∩ t)) : diam (s ∪ t) ≤ diam s + diam t := sorry

theorem diam_closed_ball {α : Type u} [emetric_space α] {x : α} {r : ennreal} : diam (closed_ball x r) ≤ bit0 1 * r := sorry

theorem diam_ball {α : Type u} [emetric_space α] {x : α} {r : ennreal} : diam (ball x r) ≤ bit0 1 * r :=
  le_trans (diam_mono ball_subset_closed_ball) diam_closed_ball