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

! This file was ported from Lean 3 source module measure_theory.function.convergence_in_measure
! leanprover-community/mathlib commit 57ac39bd365c2f80589a700f9fbb664d3a1a30c2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.SpecialFunctions.Pow
import Mathbin.MeasureTheory.Function.Egorov
import Mathbin.MeasureTheory.Function.LpSpace

/-!
# Convergence in measure

We define convergence in measure which is one of the many notions of convergence in probability.
A sequence of functions `f` is said to converge in measure to some function `g`
if for all `ε > 0`, the measure of the set `{x | ε ≤ dist (f i x) (g x)}` tends to 0 as `i`
converges along some given filter `l`.

Convergence in measure is most notably used in the formulation of the weak law of large numbers
and is also useful in theorems such as the Vitali convergence theorem. This file provides some
basic lemmas for working with convergence in measure and establishes some relations between
convergence in measure and other notions of convergence.

## Main definitions

* `measure_theory.tendsto_in_measure (μ : measure α) (f : ι → α → E) (g : α → E)`: `f` converges
  in `μ`-measure to `g`.

## Main results

* `measure_theory.tendsto_in_measure_of_tendsto_ae`: convergence almost everywhere in a finite
  measure space implies convergence in measure.
* `measure_theory.tendsto_in_measure.exists_seq_tendsto_ae`: if `f` is a sequence of functions
  which converges in measure to `g`, then `f` has a subsequence which convergence almost
  everywhere to `g`.
* `measure_theory.tendsto_in_measure_of_tendsto_snorm`: convergence in Lp implies convergence
  in measure.
-/

open TopologicalSpace Filter

open NNReal ENNReal MeasureTheory Topology

namespace MeasureTheory

variable {α ι E : Type _} {m : MeasurableSpace α} {μ : Measure α}

/-- A sequence of functions `f` is said to converge in measure to some function `g` if for all
`ε > 0`, the measure of the set `{x | ε ≤ dist (f i x) (g x)}` tends to 0 as `i` converges along
some given filter `l`. -/
def TendstoInMeasure [Dist E] {m : MeasurableSpace α} (μ : Measure α) (f : ι → α → E) (l : Filter ι)
    (g : α → E) : Prop :=
  ∀ (ε) (hε : 0 < ε), Tendsto (fun i => μ { x | ε ≤ dist (f i x) (g x) }) l (𝓝 0)
#align measure_theory.tendsto_in_measure MeasureTheory.TendstoInMeasure

theorem tendstoInMeasure_iff_norm [SeminormedAddCommGroup E] {l : Filter ι} {f : ι → α → E}
    {g : α → E} :
    TendstoInMeasure μ f l g ↔
      ∀ (ε) (hε : 0 < ε), Tendsto (fun i => μ { x | ε ≤ ‖f i x - g x‖ }) l (𝓝 0) :=
  by simp_rw [tendsto_in_measure, dist_eq_norm]
#align measure_theory.tendsto_in_measure_iff_norm MeasureTheory.tendstoInMeasure_iff_norm

namespace TendstoInMeasure

variable [Dist E] {l : Filter ι} {f f' : ι → α → E} {g g' : α → E}

protected theorem congr' (h_left : ∀ᶠ i in l, f i =ᵐ[μ] f' i) (h_right : g =ᵐ[μ] g')
    (h_tendsto : TendstoInMeasure μ f l g) : TendstoInMeasure μ f' l g' :=
  by
  intro ε hε
  suffices
    (fun i => μ { x | ε ≤ dist (f' i x) (g' x) }) =ᶠ[l] fun i => μ { x | ε ≤ dist (f i x) (g x) }
    by
    rw [tendsto_congr' this]
    exact h_tendsto ε hε
  filter_upwards [h_left]with i h_ae_eq
  refine' measure_congr _
  filter_upwards [h_ae_eq, h_right]with x hxf hxg
  rw [eq_iff_iff]
  change ε ≤ dist (f' i x) (g' x) ↔ ε ≤ dist (f i x) (g x)
  rw [hxg, hxf]
#align measure_theory.tendsto_in_measure.congr' MeasureTheory.TendstoInMeasure.congr'

protected theorem congr (h_left : ∀ i, f i =ᵐ[μ] f' i) (h_right : g =ᵐ[μ] g')
    (h_tendsto : TendstoInMeasure μ f l g) : TendstoInMeasure μ f' l g' :=
  TendstoInMeasure.congr' (eventually_of_forall h_left) h_right h_tendsto
#align measure_theory.tendsto_in_measure.congr MeasureTheory.TendstoInMeasure.congr

theorem congrLeft (h : ∀ i, f i =ᵐ[μ] f' i) (h_tendsto : TendstoInMeasure μ f l g) :
    TendstoInMeasure μ f' l g :=
  h_tendsto.congr h EventuallyEq.rfl
#align measure_theory.tendsto_in_measure.congr_left MeasureTheory.TendstoInMeasure.congrLeft

theorem congrRight (h : g =ᵐ[μ] g') (h_tendsto : TendstoInMeasure μ f l g) :
    TendstoInMeasure μ f l g' :=
  h_tendsto.congr (fun i => EventuallyEq.rfl) h
#align measure_theory.tendsto_in_measure.congr_right MeasureTheory.TendstoInMeasure.congrRight

end TendstoInMeasure

section ExistsSeqTendstoAe

variable [MetricSpace E]

variable {f : ℕ → α → E} {g : α → E}

/-- Auxiliary lemma for `tendsto_in_measure_of_tendsto_ae`. -/
theorem tendstoInMeasureOfTendstoAeOfStronglyMeasurable [IsFiniteMeasure μ]
    (hf : ∀ n, StronglyMeasurable (f n)) (hg : StronglyMeasurable g)
    (hfg : ∀ᵐ x ∂μ, Tendsto (fun n => f n x) atTop (𝓝 (g x))) : TendstoInMeasure μ f atTop g :=
  by
  refine' fun ε hε => ennreal.tendsto_at_top_zero.mpr fun δ hδ => _
  by_cases hδi : δ = ∞
  · simp only [hδi, imp_true_iff, le_top, exists_const]
  lift δ to ℝ≥0 using hδi
  rw [gt_iff_lt, ENNReal.coe_pos, ← NNReal.coe_pos] at hδ
  obtain ⟨t, htm, ht, hunif⟩ := tendsto_uniformly_on_of_ae_tendsto' hf hg hfg hδ
  rw [ENNReal.ofReal_coe_nnreal] at ht
  rw [Metric.tendstoUniformlyOn_iff] at hunif
  obtain ⟨N, hN⟩ := eventually_at_top.1 (hunif ε hε)
  refine' ⟨N, fun n hn => _⟩
  suffices : { x : α | ε ≤ dist (f n x) (g x) } ⊆ t; exact (measure_mono this).trans ht
  rw [← Set.compl_subset_compl]
  intro x hx
  rw [Set.mem_compl_iff, Set.nmem_setOf_iff, dist_comm, not_le]
  exact hN n hn x hx
#align measure_theory.tendsto_in_measure_of_tendsto_ae_of_strongly_measurable MeasureTheory.tendstoInMeasureOfTendstoAeOfStronglyMeasurable

/-- Convergence a.e. implies convergence in measure in a finite measure space. -/
theorem tendstoInMeasureOfTendstoAe [IsFiniteMeasure μ] (hf : ∀ n, AeStronglyMeasurable (f n) μ)
    (hfg : ∀ᵐ x ∂μ, Tendsto (fun n => f n x) atTop (𝓝 (g x))) : TendstoInMeasure μ f atTop g :=
  by
  have hg : ae_strongly_measurable g μ := aeStronglyMeasurableOfTendstoAe _ hf hfg
  refine' tendsto_in_measure.congr (fun i => (hf i).ae_eq_mk.symm) hg.ae_eq_mk.symm _
  refine'
    tendsto_in_measure_of_tendsto_ae_of_strongly_measurable (fun i => (hf i).stronglyMeasurable_mk)
      hg.strongly_measurable_mk _
  have hf_eq_ae : ∀ᵐ x ∂μ, ∀ n, (hf n).mk (f n) x = f n x :=
    ae_all_iff.mpr fun n => (hf n).ae_eq_mk.symm
  filter_upwards [hf_eq_ae, hg.ae_eq_mk, hfg]with x hxf hxg hxfg
  rw [← hxg, funext fun n => hxf n]
  exact hxfg
#align measure_theory.tendsto_in_measure_of_tendsto_ae MeasureTheory.tendstoInMeasureOfTendstoAe

namespace ExistsSeqTendstoAe

theorem exists_nat_measure_lt_two_inv (hfg : TendstoInMeasure μ f atTop g) (n : ℕ) :
    ∃ N, ∀ m ≥ N, μ { x | 2⁻¹ ^ n ≤ dist (f m x) (g x) } ≤ 2⁻¹ ^ n :=
  by
  specialize hfg (2⁻¹ ^ n) (by simp only [zero_lt_bit0, pow_pos, zero_lt_one, inv_pos])
  rw [ENNReal.tendsto_atTop_zero] at hfg
  exact hfg (2⁻¹ ^ n) (pos_iff_ne_zero.mpr fun h_zero => by simpa using pow_eq_zero h_zero)
#align measure_theory.exists_seq_tendsto_ae.exists_nat_measure_lt_two_inv MeasureTheory.ExistsSeqTendstoAe.exists_nat_measure_lt_two_inv

/-- Given a sequence of functions `f` which converges in measure to `g`,
`seq_tendsto_ae_seq_aux` is a sequence such that
`∀ m ≥ seq_tendsto_ae_seq_aux n, μ {x | 2⁻¹ ^ n ≤ dist (f m x) (g x)} ≤ 2⁻¹ ^ n`. -/
noncomputable def seqTendstoAeSeqAux (hfg : TendstoInMeasure μ f atTop g) (n : ℕ) :=
  Classical.choose (exists_nat_measure_lt_two_inv hfg n)
#align measure_theory.exists_seq_tendsto_ae.seq_tendsto_ae_seq_aux MeasureTheory.ExistsSeqTendstoAe.seqTendstoAeSeqAux

/-- Transformation of `seq_tendsto_ae_seq_aux` to makes sure it is strictly monotone. -/
noncomputable def seqTendstoAeSeq (hfg : TendstoInMeasure μ f atTop g) : ℕ → ℕ
  | 0 => seqTendstoAeSeqAux hfg 0
  | n + 1 => max (seqTendstoAeSeqAux hfg (n + 1)) (seq_tendsto_ae_seq n + 1)
#align measure_theory.exists_seq_tendsto_ae.seq_tendsto_ae_seq MeasureTheory.ExistsSeqTendstoAe.seqTendstoAeSeq

theorem seqTendstoAeSeq_succ (hfg : TendstoInMeasure μ f atTop g) {n : ℕ} :
    seqTendstoAeSeq hfg (n + 1) =
      max (seqTendstoAeSeqAux hfg (n + 1)) (seqTendstoAeSeq hfg n + 1) :=
  by rw [seq_tendsto_ae_seq]
#align measure_theory.exists_seq_tendsto_ae.seq_tendsto_ae_seq_succ MeasureTheory.ExistsSeqTendstoAe.seqTendstoAeSeq_succ

theorem seqTendstoAeSeq_spec (hfg : TendstoInMeasure μ f atTop g) (n k : ℕ)
    (hn : seqTendstoAeSeq hfg n ≤ k) : μ { x | 2⁻¹ ^ n ≤ dist (f k x) (g x) } ≤ 2⁻¹ ^ n :=
  by
  cases n
  · exact Classical.choose_spec (exists_nat_measure_lt_two_inv hfg 0) k hn
  ·
    exact
      Classical.choose_spec (exists_nat_measure_lt_two_inv hfg _) _ (le_trans (le_max_left _ _) hn)
#align measure_theory.exists_seq_tendsto_ae.seq_tendsto_ae_seq_spec MeasureTheory.ExistsSeqTendstoAe.seqTendstoAeSeq_spec

theorem seqTendstoAeSeq_strictMono (hfg : TendstoInMeasure μ f atTop g) :
    StrictMono (seqTendstoAeSeq hfg) :=
  by
  refine' strictMono_nat_of_lt_succ fun n => _
  rw [seq_tendsto_ae_seq_succ]
  exact lt_of_lt_of_le (lt_add_one <| seq_tendsto_ae_seq hfg n) (le_max_right _ _)
#align measure_theory.exists_seq_tendsto_ae.seq_tendsto_ae_seq_strict_mono MeasureTheory.ExistsSeqTendstoAe.seqTendstoAeSeq_strictMono

end ExistsSeqTendstoAe

/-- If `f` is a sequence of functions which converges in measure to `g`, then there exists a
subsequence of `f` which converges a.e. to `g`. -/
theorem TendstoInMeasure.exists_seq_tendsto_ae (hfg : TendstoInMeasure μ f atTop g) :
    ∃ ns : ℕ → ℕ, StrictMono ns ∧ ∀ᵐ x ∂μ, Tendsto (fun i => f (ns i) x) atTop (𝓝 (g x)) :=
  by
  /- Since `f` tends to `g` in measure, it has a subsequence `k ↦ f (ns k)` such that
    `μ {|f (ns k) - g| ≥ 2⁻ᵏ} ≤ 2⁻ᵏ` for all `k`. Defining
    `s := ⋂ k, ⋃ i ≥ k, {|f (ns k) - g| ≥ 2⁻ᵏ}`, we see that `μ s = 0` by the
    first Borel-Cantelli lemma.

    On the other hand, as `s` is precisely the set for which `f (ns k)`
    doesn't converge to `g`, `f (ns k)` converges almost everywhere to `g` as required. -/
  have h_lt_ε_real : ∀ (ε : ℝ) (hε : 0 < ε), ∃ k : ℕ, 2 * 2⁻¹ ^ k < ε :=
    by
    intro ε hε
    obtain ⟨k, h_k⟩ : ∃ k : ℕ, 2⁻¹ ^ k < ε := exists_pow_lt_of_lt_one hε (by norm_num)
    refine' ⟨k + 1, (le_of_eq _).trans_lt h_k⟩
    rw [pow_add]
    ring
  set ns := exists_seq_tendsto_ae.seq_tendsto_ae_seq hfg
  use ns
  let S k := { x | 2⁻¹ ^ k ≤ dist (f (ns k) x) (g x) }
  have hμS_le : ∀ k, μ (S k) ≤ 2⁻¹ ^ k := fun k =>
    exists_seq_tendsto_ae.seq_tendsto_ae_seq_spec hfg k (ns k) le_rfl
  set s := filter.at_top.limsup S with hs
  have hμs : μ s = 0 :=
    by
    refine' measure_limsup_eq_zero (ne_of_lt <| lt_of_le_of_lt (ENNReal.tsum_le_tsum hμS_le) _)
    simp only [ENNReal.tsum_geometric, ENNReal.one_sub_inv_two, inv_inv]
    decide
  have h_tendsto : ∀ x ∈ sᶜ, tendsto (fun i => f (ns i) x) at_top (𝓝 (g x)) :=
    by
    refine' fun x hx => metric.tendsto_at_top.mpr fun ε hε => _
    rw [hs, limsup_eq_infi_supr_of_nat] at hx
    simp only [Set.supᵢ_eq_unionᵢ, Set.infᵢ_eq_interᵢ, Set.compl_interᵢ, Set.compl_unionᵢ,
      Set.mem_unionᵢ, Set.mem_interᵢ, Set.mem_compl_iff, Set.mem_setOf_eq, not_le] at hx
    obtain ⟨N, hNx⟩ := hx
    obtain ⟨k, hk_lt_ε⟩ := h_lt_ε_real ε hε
    refine' ⟨max N (k - 1), fun n hn_ge => lt_of_le_of_lt _ hk_lt_ε⟩
    specialize hNx n ((le_max_left _ _).trans hn_ge)
    have h_inv_n_le_k : (2 : ℝ)⁻¹ ^ n ≤ 2 * 2⁻¹ ^ k :=
      by
      rw [mul_comm, ← inv_mul_le_iff' (zero_lt_two' ℝ)]
      conv_lhs =>
        congr
        rw [← pow_one (2 : ℝ)⁻¹]
      rw [← pow_add, add_comm]
      exact
        pow_le_pow_of_le_one (one_div (2 : ℝ) ▸ one_half_pos.le) (inv_le_one one_le_two)
          ((le_tsub_add.trans (add_le_add_right (le_max_right _ _) 1)).trans
            (add_le_add_right hn_ge 1))
    exact le_trans hNx.le h_inv_n_le_k
  rw [ae_iff]
  refine'
    ⟨exists_seq_tendsto_ae.seq_tendsto_ae_seq_strict_mono hfg, measure_mono_null (fun x => _) hμs⟩
  rw [Set.mem_setOf_eq, ← @Classical.not_not (x ∈ s), not_imp_not]
  exact h_tendsto x
#align measure_theory.tendsto_in_measure.exists_seq_tendsto_ae MeasureTheory.TendstoInMeasure.exists_seq_tendsto_ae

theorem TendstoInMeasure.exists_seq_tendstoInMeasure_atTop {u : Filter ι} [NeBot u]
    [IsCountablyGenerated u] {f : ι → α → E} {g : α → E} (hfg : TendstoInMeasure μ f u g) :
    ∃ ns : ℕ → ι, TendstoInMeasure μ (fun n => f (ns n)) atTop g :=
  by
  obtain ⟨ns, h_tendsto_ns⟩ : ∃ ns : ℕ → ι, tendsto ns at_top u := exists_seq_tendsto u
  exact ⟨ns, fun ε hε => (hfg ε hε).comp h_tendsto_ns⟩
#align measure_theory.tendsto_in_measure.exists_seq_tendsto_in_measure_at_top MeasureTheory.TendstoInMeasure.exists_seq_tendstoInMeasure_atTop

theorem TendstoInMeasure.exists_seq_tendsto_ae' {u : Filter ι} [NeBot u] [IsCountablyGenerated u]
    {f : ι → α → E} {g : α → E} (hfg : TendstoInMeasure μ f u g) :
    ∃ ns : ℕ → ι, ∀ᵐ x ∂μ, Tendsto (fun i => f (ns i) x) atTop (𝓝 (g x)) :=
  by
  obtain ⟨ms, hms⟩ := hfg.exists_seq_tendsto_in_measure_at_top
  obtain ⟨ns, -, hns⟩ := hms.exists_seq_tendsto_ae
  exact ⟨ms ∘ ns, hns⟩
#align measure_theory.tendsto_in_measure.exists_seq_tendsto_ae' MeasureTheory.TendstoInMeasure.exists_seq_tendsto_ae'

end ExistsSeqTendstoAe

section AeMeasurableOf

variable [MeasurableSpace E] [NormedAddCommGroup E] [BorelSpace E]

theorem TendstoInMeasure.aeMeasurable {u : Filter ι} [NeBot u] [IsCountablyGenerated u]
    {f : ι → α → E} {g : α → E} (hf : ∀ n, AeMeasurable (f n) μ)
    (h_tendsto : TendstoInMeasure μ f u g) : AeMeasurable g μ :=
  by
  obtain ⟨ns, hns⟩ := h_tendsto.exists_seq_tendsto_ae'
  exact aeMeasurableOfTendstoMetrizableAe at_top (fun n => hf (ns n)) hns
#align measure_theory.tendsto_in_measure.ae_measurable MeasureTheory.TendstoInMeasure.aeMeasurable

end AeMeasurableOf

section TendstoInMeasureOf

variable [NormedAddCommGroup E] {p : ℝ≥0∞}

variable {f : ι → α → E} {g : α → E}

/-- This lemma is superceded by `measure_theory.tendsto_in_measure_of_tendsto_snorm` where we
allow `p = ∞` and only require `ae_strongly_measurable`. -/
theorem tendstoInMeasureOfTendstoSnormOfStronglyMeasurable (hp_ne_zero : p ≠ 0) (hp_ne_top : p ≠ ∞)
    (hf : ∀ n, StronglyMeasurable (f n)) (hg : StronglyMeasurable g) {l : Filter ι}
    (hfg : Tendsto (fun n => snorm (f n - g) p μ) l (𝓝 0)) : TendstoInMeasure μ f l g :=
  by
  intro ε hε
  replace hfg :=
    ENNReal.Tendsto.const_mul (tendsto.ennrpow_const p.to_real hfg)
      (Or.inr <| @ENNReal.ofReal_ne_top (1 / ε ^ p.to_real))
  simp only [MulZeroClass.mul_zero,
    ENNReal.zero_rpow_of_pos (ENNReal.toReal_pos hp_ne_zero hp_ne_top)] at hfg
  rw [ENNReal.tendsto_nhds_zero] at hfg⊢
  intro δ hδ
  refine' (hfg δ hδ).mono fun n hn => _
  refine' le_trans _ hn
  rw [ENNReal.ofReal_div_of_pos (Real.rpow_pos_of_pos hε _), ENNReal.ofReal_one, mul_comm,
    mul_one_div, ENNReal.le_div_iff_mul_le _ (Or.inl ENNReal.ofReal_ne_top), mul_comm]
  · convert mul_meas_ge_le_pow_snorm' μ hp_ne_zero hp_ne_top ((hf n).sub hg).AeStronglyMeasurable
        (ENNReal.ofReal ε)
    · exact (ENNReal.ofReal_rpow_of_pos hε).symm
    · ext x
      rw [dist_eq_norm, ← ENNReal.ofReal_le_ofReal_iff (norm_nonneg _), ofReal_norm_eq_coe_nnnorm]
      exact Iff.rfl
  · rw [Ne, ENNReal.ofReal_eq_zero, not_le]
    exact Or.inl (Real.rpow_pos_of_pos hε _)
#align measure_theory.tendsto_in_measure_of_tendsto_snorm_of_strongly_measurable MeasureTheory.tendstoInMeasureOfTendstoSnormOfStronglyMeasurable

/-- This lemma is superceded by `measure_theory.tendsto_in_measure_of_tendsto_snorm` where we
allow `p = ∞`. -/
theorem tendstoInMeasureOfTendstoSnormOfNeTop (hp_ne_zero : p ≠ 0) (hp_ne_top : p ≠ ∞)
    (hf : ∀ n, AeStronglyMeasurable (f n) μ) (hg : AeStronglyMeasurable g μ) {l : Filter ι}
    (hfg : Tendsto (fun n => snorm (f n - g) p μ) l (𝓝 0)) : TendstoInMeasure μ f l g :=
  by
  refine' tendsto_in_measure.congr (fun i => (hf i).ae_eq_mk.symm) hg.ae_eq_mk.symm _
  refine'
    tendsto_in_measure_of_tendsto_snorm_of_strongly_measurable hp_ne_zero hp_ne_top
      (fun i => (hf i).stronglyMeasurable_mk) hg.strongly_measurable_mk _
  have : (fun n => snorm ((hf n).mk (f n) - hg.mk g) p μ) = fun n => snorm (f n - g) p μ :=
    by
    ext1 n
    refine' snorm_congr_ae (eventually_eq.sub (hf n).ae_eq_mk.symm hg.ae_eq_mk.symm)
  rw [this]
  exact hfg
#align measure_theory.tendsto_in_measure_of_tendsto_snorm_of_ne_top MeasureTheory.tendstoInMeasureOfTendstoSnormOfNeTop

/-- See also `measure_theory.tendsto_in_measure_of_tendsto_snorm` which work for general
Lp-convergence for all `p ≠ 0`. -/
theorem tendstoInMeasureOfTendstoSnormTop {E} [NormedAddCommGroup E] {f : ι → α → E} {g : α → E}
    {l : Filter ι} (hfg : Tendsto (fun n => snorm (f n - g) ∞ μ) l (𝓝 0)) :
    TendstoInMeasure μ f l g := by
  intro δ hδ
  simp only [snorm_exponent_top, snorm_ess_sup] at hfg
  rw [ENNReal.tendsto_nhds_zero] at hfg⊢
  intro ε hε
  specialize
    hfg (ENNReal.ofReal δ / 2)
      (ENNReal.div_pos_iff.2 ⟨(ENNReal.ofReal_pos.2 hδ).Ne.symm, ENNReal.two_ne_top⟩)
  refine' hfg.mono fun n hn => _
  simp only [true_and_iff, gt_iff_lt, ge_iff_le, zero_tsub, zero_le, zero_add, Set.mem_Icc,
    Pi.sub_apply] at *
  have : essSup (fun x : α => (‖f n x - g x‖₊ : ℝ≥0∞)) μ < ENNReal.ofReal δ :=
    lt_of_le_of_lt hn
      (ENNReal.half_lt_self (ENNReal.ofReal_pos.2 hδ).Ne.symm ennreal.of_real_lt_top.ne)
  refine' ((le_of_eq _).trans (ae_lt_of_essSup_lt this).le).trans hε.le
  congr with x
  simp only [ENNReal.ofReal_le_iff_le_toReal ennreal.coe_lt_top.ne, ENNReal.coe_toReal, not_lt,
    coe_nnnorm, Set.mem_setOf_eq, Set.mem_compl_iff]
  rw [← dist_eq_norm (f n x) (g x)]
  rfl
#align measure_theory.tendsto_in_measure_of_tendsto_snorm_top MeasureTheory.tendstoInMeasureOfTendstoSnormTop

/-- Convergence in Lp implies convergence in measure. -/
theorem tendstoInMeasureOfTendstoSnorm {l : Filter ι} (hp_ne_zero : p ≠ 0)
    (hf : ∀ n, AeStronglyMeasurable (f n) μ) (hg : AeStronglyMeasurable g μ)
    (hfg : Tendsto (fun n => snorm (f n - g) p μ) l (𝓝 0)) : TendstoInMeasure μ f l g :=
  by
  by_cases hp_ne_top : p = ∞
  · subst hp_ne_top
    exact tendsto_in_measure_of_tendsto_snorm_top hfg
  · exact tendsto_in_measure_of_tendsto_snorm_of_ne_top hp_ne_zero hp_ne_top hf hg hfg
#align measure_theory.tendsto_in_measure_of_tendsto_snorm MeasureTheory.tendstoInMeasureOfTendstoSnorm

/-- Convergence in Lp implies convergence in measure. -/
theorem tendstoInMeasureOfTendstoLp [hp : Fact (1 ≤ p)] {f : ι → lp E p μ} {g : lp E p μ}
    {l : Filter ι} (hfg : Tendsto f l (𝓝 g)) : TendstoInMeasure μ (fun n => f n) l g :=
  tendstoInMeasureOfTendstoSnorm (zero_lt_one.trans_le hp.elim).Ne.symm
    (fun n => lp.aeStronglyMeasurable _) (lp.aeStronglyMeasurable _)
    ((lp.tendsto_lp_iff_tendsto_ℒp' _ _).mp hfg)
#align measure_theory.tendsto_in_measure_of_tendsto_Lp MeasureTheory.tendstoInMeasureOfTendstoLp

end TendstoInMeasureOf

end MeasureTheory


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
-/

import data.int.basic data.nat.modeq

namespace int

def modeq (n a b : ℤ) := a % n = b % n

notation a ` ≡ `:50 b ` [ZMOD `:50 n `]`:0 := modeq n a b

namespace modeq
variables {n m a b c d : ℤ}

@[refl] protected theorem refl (a : ℤ) : a ≡ a [ZMOD n] := @rfl _ _

@[symm] protected theorem symm : a ≡ b [ZMOD n] → b ≡ a [ZMOD n] := eq.symm

@[trans] protected theorem trans : a ≡ b [ZMOD n] → b ≡ c [ZMOD n] → a ≡ c [ZMOD n] := eq.trans

lemma coe_nat_modeq_iff {a b n : ℕ} : a ≡ b [ZMOD n] ↔ a ≡ b [MOD n] :=
by unfold modeq nat.modeq; rw ← int.coe_nat_eq_coe_nat_iff; simp [int.coe_nat_mod]

instance : decidable (a ≡ b [ZMOD n]) := by unfold modeq; apply_instance

theorem modeq_iff_dvd : a ≡ b [ZMOD n] ↔ (n:ℤ) ∣ b - a :=
by rw [modeq, eq_comm];
   simp [int.mod_eq_mod_iff_mod_sub_eq_zero, int.dvd_iff_mod_eq_zero]

theorem modeq_of_dvd_of_modeq (d : m ∣ n) (h : a ≡ b [ZMOD n]) : a ≡ b [ZMOD m] :=
modeq_iff_dvd.2 $ dvd_trans d (modeq_iff_dvd.1 h)

theorem modeq_mul_left' (hc : 0 ≤ c) (h : a ≡ b [ZMOD n]) : c * a ≡ c * b [ZMOD (c * n)] :=
or.cases_on (lt_or_eq_of_le hc) (λ hc,
  by unfold modeq;
  simp [mul_mod_mul_of_pos _ _ hc, (show _ = _, from h)] )
(λ hc, by simp [hc.symm])

theorem modeq_mul_right' (hc : 0 ≤ c) (h : a ≡ b [ZMOD n]) : a * c ≡ b * c [ZMOD (n * c)] :=
by rw [mul_comm a, mul_comm b, mul_comm n]; exact modeq_mul_left' hc h

theorem modeq_add (h₁ : a ≡ b [ZMOD n]) (h₂ : c ≡ d [ZMOD n]) : a + c ≡ b + d [ZMOD n] :=
modeq_iff_dvd.2 $ by simpa using dvd_add (modeq_iff_dvd.1 h₁) (modeq_iff_dvd.1 h₂)

theorem modeq_add_cancel_left (h₁ : a ≡ b [ZMOD n]) (h₂ : a + c ≡ b + d [ZMOD n]) : c ≡ d [ZMOD n] :=
have (n:ℤ) ∣ a + (-a + (d + -c)),
by simpa using dvd_sub (modeq_iff_dvd.1 h₂) (modeq_iff_dvd.1 h₁),
modeq_iff_dvd.2 $ by rwa add_neg_cancel_left at this

theorem modeq_add_cancel_right (h₁ : c ≡ d [ZMOD n]) (h₂ : a + c ≡ b + d [ZMOD n]) : a ≡ b [ZMOD n] :=
by rw [add_comm a, add_comm b] at h₂; exact modeq_add_cancel_left h₁ h₂

theorem mod_modeq (a n) : a % n ≡ a [ZMOD n] := int.mod_mod _ _

theorem modeq_neg (h : a ≡ b [ZMOD n]) : -a ≡ -b [ZMOD n] :=
modeq_add_cancel_left h (by simp)

theorem modeq_sub (h₁ : a ≡ b [ZMOD n]) (h₂ : c ≡ d [ZMOD n]) : a - c ≡ b - d [ZMOD n] :=
by rw [sub_eq_add_neg, sub_eq_add_neg]; exact modeq_add h₁ (modeq_neg h₂)

theorem modeq_mul_left (c : ℤ) (h : a ≡ b [ZMOD n]) : c * a ≡ c * b [ZMOD n] :=
or.cases_on (le_total 0 c)
(λ hc, modeq_of_dvd_of_modeq (dvd_mul_left _ _) (modeq_mul_left' hc h))
(λ hc, by rw [← neg_neg c, ← neg_mul_eq_neg_mul, ← neg_mul_eq_neg_mul _ b];
    exact modeq_neg (modeq_of_dvd_of_modeq (dvd_mul_left _ _)
    (modeq_mul_left' (neg_nonneg.2 hc) h)))

theorem modeq_mul_right (c : ℤ) (h : a ≡ b [ZMOD n]) : a * c ≡ b * c [ZMOD n] :=
by rw [mul_comm a, mul_comm b]; exact modeq_mul_left c h

theorem modeq_mul (h₁ : a ≡ b [ZMOD n]) (h₂ : c ≡ d [ZMOD n]) : a * c ≡ b * d [ZMOD n] :=
(modeq_mul_left _ h₂).trans (modeq_mul_right _ h₁)

theorem modeq_of_modeq_mul_left (m : ℤ) (h : a ≡ b [ZMOD m * n]) : a ≡ b [ZMOD n] :=
by rw [modeq_iff_dvd] at *; exact dvd.trans (dvd_mul_left n m) h

theorem modeq_of_modeq_mul_right (m : ℤ) : a ≡ b [ZMOD n * m] → a ≡ b [ZMOD n] :=
mul_comm m n ▸ modeq_of_modeq_mul_left _

lemma modeq_and_modeq_iff_modeq_mul {a b m n : ℤ} (hmn : nat.coprime m.nat_abs n.nat_abs) :
  a ≡ b [ZMOD m] ∧ a ≡ b [ZMOD n] ↔ (a ≡ b [ZMOD m * n]) :=
⟨λ h, begin
    rw [int.modeq.modeq_iff_dvd, int.modeq.modeq_iff_dvd] at h,
    rw [int.modeq.modeq_iff_dvd, ← int.nat_abs_dvd, ← int.dvd_nat_abs,
      int.coe_nat_dvd, int.nat_abs_mul],
    refine hmn.mul_dvd_of_dvd_of_dvd _ _;
    rw [← int.coe_nat_dvd, int.nat_abs_dvd, int.dvd_nat_abs]; tauto
  end,
λ h, ⟨int.modeq.modeq_of_modeq_mul_right _ h, int.modeq.modeq_of_modeq_mul_left _ h⟩⟩

lemma gcd_a_modeq (a b : ℕ) : (a : ℤ) * nat.gcd_a a b ≡ nat.gcd a b [ZMOD b] :=
by rw [← add_zero ((a : ℤ) * _), nat.gcd_eq_gcd_ab];
  exact int.modeq.modeq_add rfl (int.modeq.modeq_zero_iff.2 (dvd_mul_right _ _)).symm

theorem modeq_add_fac {a b n : ℤ} (c : ℤ) (ha : a ≡ b [ZMOD n]) : a + n*c ≡ b [ZMOD n] :=
calc a + n*c ≡ b + n*c [ZMOD n] : int.modeq.modeq_add ha (int.modeq.refl _)
         ... ≡ b + 0 [ZMOD n] : int.modeq.modeq_add (int.modeq.refl _) (int.modeq.modeq_zero_iff.2 (dvd_mul_right _ _))
         ... ≡ b [ZMOD n] : by simp

open nat
lemma mod_coprime {a b : ℕ} (hab : coprime a b) : ∃ y : ℤ, a * y ≡ 1 [ZMOD b] :=
⟨ gcd_a a b,
  have hgcd : nat.gcd a b = 1, from coprime.gcd_eq_one hab,
  calc
   ↑a * gcd_a a b ≡ ↑a*gcd_a a b + ↑b*gcd_b a b [ZMOD ↑b] : int.modeq.symm $ modeq_add_fac _ $ int.modeq.refl _
              ... ≡ 1 [ZMOD ↑b] : by rw [←gcd_eq_gcd_ab, hgcd]; reflexivity ⟩

lemma exists_unique_equiv (a : ℤ) {b : ℤ} (hb : b > 0) : ∃ z : ℤ, 0 ≤ z ∧ z < b ∧ z ≡ a [ZMOD b] :=
⟨ a % b, int.mod_nonneg _ (ne_of_gt hb),
  have a % b < abs b, from int.mod_lt _ (ne_of_gt hb),
  by rwa abs_of_pos hb at this,
  by simp [int.modeq] ⟩

lemma exists_unique_equiv_nat (a : ℤ) {b : ℤ} (hb : b > 0) : ∃ z : ℕ, ↑z < b ∧ ↑z ≡ a [ZMOD b] :=
let ⟨z, hz1, hz2, hz3⟩ := exists_unique_equiv a hb in
⟨z.nat_abs, by split; rw [←int.of_nat_eq_coe, int.of_nat_nat_abs_eq_of_nonneg hz1]; assumption⟩

end modeq

@[simp] lemma mod_mul_right_mod (a b c : ℤ) : a % (b * c) % b = a % b :=
int.modeq.modeq_of_modeq_mul_right _ (int.modeq.mod_modeq _ _)

@[simp] lemma mod_mul_left_mod (a b c : ℤ) : a % (b * c) % c = a % c :=
int.modeq.modeq_of_modeq_mul_left _ (int.modeq.mod_modeq _ _)

end int


import algebra.group_power
import set_theory.cardinal
import ring_theory.ideals
import data.finsupp
import group_theory.quotient_group
import tactic.tidy
import for_mathlib.linear_ordered_comm_group

local attribute [instance, priority 0] classical.prop_decidable
noncomputable theory

universes u₁ u₂ u₃ -- v is used for valuations

namespace valuation

class is_valuation {R : Type u₁} [comm_ring R] {Γ : Type u₂} [linear_ordered_comm_group Γ]
  (v : R → with_zero Γ) : Prop :=
(map_zero : v 0 = 0)
(map_one  : v 1 = 1)
(map_mul  : ∀ x y, v (x * y) = v x * v y)
(map_add  : ∀ x y, v (x + y) ≤ v x ∨ v (x + y) ≤ v y)

end valuation

def valuation (R : Type u₁) [comm_ring R] (Γ : Type u₂) [linear_ordered_comm_group Γ] :=
{ v : R → with_zero Γ // valuation.is_valuation v }

namespace valuation

instance (R : Type u₁) [comm_ring R] (Γ : Type u₂) [linear_ordered_comm_group Γ] :
has_coe_to_fun (valuation R Γ) := { F := λ _, R → with_zero Γ, coe := subtype.val}

variables {R : Type u₁} [comm_ring R]
variables {Γ : Type u₂} [linear_ordered_comm_group Γ]
variables (v : valuation R Γ) {x y z : R}

instance : is_valuation v := v.property

@[simp] lemma map_zero : v 0 = 0 := v.property.map_zero
@[simp] lemma map_one  : v 1 = 1 := v.property.map_one
@[simp] lemma map_mul  : ∀ x y, v (x * y) = v x * v y := v.property.map_mul
@[simp] lemma map_add  : ∀ x y, v (x + y) ≤ v x ∨ v (x + y) ≤ v y := v.property.map_add

theorem map_unit (h : x * y = 1) : option.is_some (v x) :=
begin
  have h1 := v.map_mul x y,
  rw [h, map_one v] at h1,
  cases (v x),
  { exfalso,
    exact option.no_confusion h1 },
  { constructor }
end

theorem map_neg_one : v (-1) = 1 :=
begin
  have h1 : (-1 : R) * (-1) = 1 := by simp,
  have h2 := map_unit v h1,
  have h3 := map_mul v (-1) (-1),
  rw [option.is_some_iff_exists] at h2,
  cases h2 with x h,
  change v (-1) = some x at h,
  show v (-1) = 1,
  rw h at h3 ⊢,
  congr,
  rw [h1, map_one v] at h3,
  replace h3 := eq.symm (option.some.inj h3),
  have h4 : x^2 = 1 := by simpa [pow_two] using h3,
  exact linear_ordered_comm_group.eq_one_of_pow_eq_one h4
end

@[simp] theorem eq_zero_iff_le_zero {r : R} : v r = 0 ↔ v r ≤ v 0 :=
by split; intro h; simpa using h

section

variables {Γ₁ : Type u₂} [linear_ordered_comm_group Γ₁]
variables {Γ₂ : Type u₃} [linear_ordered_comm_group Γ₂]
variables {v₁ : R → with_zero Γ₁} {v₂ : R → with_zero Γ₂}
variables {ψ : Γ₁ → Γ₂}
variables (H12 : ∀ r, with_zero.map ψ (v₁ r) = v₂ r)
variables (Hle : ∀ g h : Γ₁, g ≤ h ↔ ψ g ≤ ψ h)
include H12 Hle

theorem le_of_le (r s : R) : v₁ r ≤ v₁ s ↔ v₂ r ≤ v₂ s :=
begin
  rw ←H12 r, rw ←H12 s,
  cases hr : (v₁ r) with g; cases hs : (v₁ s) with h; try {simp},
  { intro oops, exact option.no_confusion oops },
  { exact Hle g h }
end

theorem valuation_of_valuation [is_group_hom ψ]
  (Hiψ : function.injective ψ)
  (H : is_valuation v₂) :
is_valuation v₁ :=
{ map_zero := begin
    show v₁ 0 = 0,
    have H0 : v₂ 0 = 0 := H.map_zero,
    rw ←H12 0 at H0,
    change with_zero.map ψ (v₁ 0) = 0 at H0,
    cases h : v₁ 0, refl,
    exfalso,
    rw h at H0,
    revert H0,
    exact dec_trivial
  end,
map_one := begin
    show v₁ 1 = 1,
    have H0 : v₂ 1 = 1 := H.map_one,
    rw ←H12 1 at H0,
    cases h : v₁ 1,
    { change v₁ 1 = 0 at h, rw h at H0,
      simpa only [with_zero.map_zero] using H0 },
    { rw h at H0,
      change some (ψ val) = some 1 at H0,
      congr,
      apply Hiψ,
      rw [option.some_inj.1 H0, is_group_hom.one ψ] }
  end,
map_mul := begin
    intros r s,
    apply with_zero.map_inj Hiψ,
    rw [H12 (r * s), H.map_mul, ←H12 r, ←H12 s],
    symmetry,
    exact with_zero.map_mul _ _ _,
  end,
map_add := begin
    intros r s,
    cases is_valuation.map_add v₂ r s,
    { left,
      rw [←H12 r, ←H12 (r+s)] at h,
      rwa with_zero.map_le Hle },
    { right,
      rw [←H12 s, ←H12 (r+s)] at h,
      rwa with_zero.map_le Hle }
  end }

end

def map {S : Type u₃} [comm_ring S] (f : S → R) [is_ring_hom f] : valuation S Γ :=
{ val := v ∘ f,
  property :=
  { map_zero := by simp [is_ring_hom.map_zero f],
    map_one  := by simp [is_ring_hom.map_one f],
    map_mul  := by simp [is_ring_hom.map_mul f],
    map_add  := by simp [is_ring_hom.map_add f] } }

section trivial
variables (S : ideal R) [prime : ideal.is_prime S]
include prime

def trivial : valuation R Γ :=
{ val := λ x, if x ∈ S then 0 else 1,
  property :=
  { map_zero := if_pos S.zero_mem,
    map_one  := if_neg (assume h, prime.1 (S.eq_top_iff_one.2 h)),
    map_mul  := λ x y, begin
        split_ifs with hxy hx hy; try {simp}; exfalso,
        { cases ideal.is_prime.mem_or_mem prime hxy with h' h',
          { exact hx h' },
          { exact h h' } },
        { have H : x * y ∈ S, exact S.mul_mem_right h,
          exact hxy H },
        { have H : x * y ∈ S, exact S.mul_mem_right h,
          exact hxy H },
        { have H : x * y ∈ S, exact S.mul_mem_left h_1,
          exact hxy H }
      end,
    map_add  := λ x y, begin
        split_ifs with hxy hx hy; try {simp};
        try {left; exact le_refl _};
        try {right}; try {exact le_refl _},
        { have hxy' : x + y ∈ S := S.add_mem h h_1,
          exfalso, exact hxy hxy' }
      end } }

@[simp] lemma trivial_val : (trivial S).val = (λ x, if x ∈ S then 0 else 1 : R → (with_zero Γ)) := rfl

end trivial

section supp
open with_zero
include v
def supp : ideal R :=
{ carrier := {x | v x = 0},
  zero := map_zero v,
  add  := λ x y hx hy, or.cases_on (map_add v x y)
    (λ hxy, le_antisymm (hx ▸ hxy) zero_le)
    (λ hxy, le_antisymm (hy ▸ hxy) zero_le),
  smul  := λ c x hx, calc v (c * x)
                        = v c * v x : map_mul v c x
                    ... = v c * 0 : congr_arg _ hx
                    ... = 0 : mul_zero _ }
omit v
instance : ideal.is_prime (supp v) :=
⟨λ h, have h1 : (1:R) ∈ supp v, by rw h; trivial,
    have h2 : v 1 = 0 := h1,
    by rw [map_one v] at h2; exact option.no_confusion h2,
 λ x y hxy, begin
    dsimp [supp] at hxy ⊢,
    change v (x * y) = 0 at hxy,
    rw [map_mul v x y] at hxy,
    exact eq_zero_or_eq_zero_of_mul_eq_zero _ _ hxy
  end⟩

/-
definition extension_to_integral_domain {α : Type} [linear_ordered_comm_group α]
  {R : Type} [comm_ring R] (f : R → with_zero α) [H : is_valuation f] :
  (comm_ring.quotient R (supp f)) → with_zero α := sorry
-/

end supp

variable (v)

definition value_group := group.closure {a : Γ | ∃ r : R, v r = some a}

definition value_group_v (v : R → with_zero Γ) [is_valuation v] :=
group.closure ({a : Γ | ∃ r : R, v r = some a})

instance : group (value_group v) :=
@subtype.group _ _ (value_group v) (group.closure.is_subgroup {a : Γ | ∃ r : R, v r = some a})

instance valutaion.group_v (v : R → with_zero Γ) [is_valuation v] : group (value_group_v v) :=
  @subtype.group _ _ (value_group_v v) (group.closure.is_subgroup {a : Γ | ∃ r : R, v r = some a})

end valuation

namespace valuation
open quotient_group

variables {R : Type u₁} [comm_ring R] [decidable_eq R]

structure minimal_valuation.parametrized_subgroup (Γ₂ : Type u₂) [linear_ordered_comm_group Γ₂] :=
(Γ : Type u₁)
[grp : comm_group Γ]
(inc : Γ → Γ₂)
[hom : is_group_hom inc]
(inj : function.injective inc)

local attribute [instance] parametrized_subgroup.grp
local attribute [instance] parametrized_subgroup.hom

variables  {Γ₂ : Type u₂} [linear_ordered_comm_group Γ₂]
variables (v₂ : valuation R  Γ₂)

set_option class.instance_max_depth 41
include R v₂
def minimal_value_group : minimal_valuation.parametrized_subgroup Γ₂ :=
begin
  let FG : Type u₁ := multiplicative (R →₀ ℤ), -- free ab group on R
  let φ₀ : R → Γ₂ := λ r, option.get_or_else (v₂ r) 1,
  let φ : FG → Γ₂ := λ f, finsupp.prod f (λ r n,(φ₀ r) ^ n),
  haveI : is_group_hom φ :=
    ⟨λ a b, finsupp.prod_add_index (λ a, rfl) (λ a b₁ b₂, gpow_add (φ₀ a) b₁ b₂)⟩,

  exact
  { Γ     :=  quotient (is_group_hom.ker φ),
    inc   :=  lift (is_group_hom.ker φ) φ (λ _,(is_group_hom.mem_ker φ).1),
    hom   := quotient_group.is_group_hom_quotient_lift _ _ _,
    inj   := injective_ker_lift φ }
end

namespace minimal_value_group

def mk (r : R) : (minimal_value_group v₂).Γ :=
begin
  let FG : Type u₁ := multiplicative (R →₀ ℤ), -- free ab group on R
  let φ₀ : R → Γ₂ := λ r, option.get_or_else (v₂ r) 1,
  let φ : FG → Γ₂ := λ f, finsupp.prod f (λ r n,(φ₀ r) ^ n),
  haveI : is_group_hom φ :=
    ⟨λ a b, finsupp.prod_add_index (λ a, rfl) (λ a b₁ b₂, gpow_add (φ₀ a) b₁ b₂)⟩,

  exact quotient_group.mk (finsupp.single r (1 : ℤ))
end

lemma mk_some {r : R} {g : Γ₂} (h : v₂ r = some g) :
  v₂ r = some ((minimal_value_group v₂).inc (mk v₂ r)) :=
begin
  rw h,
  congr' 1,
  dsimp[minimal_value_group, minimal_value_group.mk],
  rw finsupp.prod_single_index ; finish
end

instance : linear_ordered_comm_group (minimal_value_group v₂).Γ :=
begin
  cases minimal_value_group v₂ with Γ₁ _ ψ _ inj,

  letI Γ₁linord : linear_order Γ₁ :=
  { le := λ g h, ψ g ≤ ψ h,
    le_refl := λ _, le_refl _,
    le_trans := λ _ _ _ hab hbc, le_trans hab hbc,
    le_antisymm := λ g h Hgh Hhg, inj $ le_antisymm Hgh Hhg,
    le_total := λ g h, le_total _ _ },
  exact ⟨λ a b H c,
    begin
      change ψ (c * a) ≤ ψ (c * b),
      rw [is_group_hom.mul ψ c b, is_group_hom.mul ψ c a],
      exact linear_ordered_comm_group.mul_le_mul_left H _,
    end⟩
end

end minimal_value_group

definition minimal_valuation.val (r : R) : with_zero ((minimal_value_group v₂).Γ) :=
match v₂ r with
| some _ := some (minimal_value_group.mk v₂ r)
| 0 := 0
end

namespace minimal_valuation

@[simp] lemma zero {r} (h : v₂ r = 0) : val v₂ r = 0 :=
by simp [val, h]

lemma some {r} {g} (h : v₂ r = some g) : val v₂ r = some (minimal_value_group.mk v₂ r) :=
by simp [val, h]

lemma map (r : R) :
with_zero.map (minimal_value_group v₂).inc (val v₂ r) = v₂ r :=
begin
  destruct (v₂ r),
  { intro h, change v₂ r = 0 at h,
    simp [zero v₂ h, h], },
  { intros g h,
    rw [minimal_value_group.mk_some v₂ h, some v₂ h, with_zero.map_some] },
end

end minimal_valuation

def minimal_valuation : valuation R (minimal_value_group v₂).Γ :=
{ val := minimal_valuation.val v₂,
  property := let Γ₁ := minimal_value_group v₂ in
    valuation_of_valuation (minimal_valuation.map v₂) (λ g h, iff.refl _) Γ₁.inj (v₂.property) }

def is_equiv {R : Type u₁} [comm_ring R]
{Γ₁ : Type u₂} [linear_ordered_comm_group Γ₁]
{Γ₂ : Type u₃} [linear_ordered_comm_group Γ₂]
(v₁ : valuation R Γ₁) (v₂ : valuation R Γ₂) : Prop :=
∀ r s, v₁ r ≤ v₁ s ↔ v₂ r ≤ v₂ s

lemma minimal_valuation_equiv :
is_equiv (v₂.minimal_valuation : valuation R (minimal_value_group v₂).Γ) v₂ :=
le_of_le (minimal_valuation.map v₂) (λ g h, iff.refl _)

end valuation

/- quotes from mathlib (mostly Mario) (all 2018)

Jul03

class is_valuation {α : Type} [linear_ordered_comm_group α]
  {R : Type} [comm_ring R] (f : R → option α) : Prop :=
(map_zero : f 0 = 0)
(map_one  : f 1 = 1)
(map_mul  : ∀ x y, f (x * y) = f x * f y)
(map_add  : ∀ x y, f (x + y) ≤ f x ∨ f (x + y) ≤ f y)

namespace is_valuation

...

structure valuation (R : Type) [comm_ring R] (α : Type) [Hα : linear_ordered_comm_group α] :=
(f : R → option α)
(Hf : is_valuation f)

...

**All Jul03**

MC: What's wrong, again, with defining Spv as the collection of all valuation relations?
KB: All proofs need an actual valuation
MC: You can define your own version of quot.lift and quot.mk that take valuations
MC: valuation functions that is
[quot.lift is the statement that if I have a function on valuations which is constant
on equiv classes then I can produce a function on Spv]
MC: You only use the relations as inhabitants of the type so that the universe isn't pushed up,
    but all the work uses functions
MC: You will need to prove the computation rule, so it won't be definitional, but otherwise it
    should work smoothly if your API is solid
MC: No equivalence class needed either
MC: quot.mk takes a valuation function and produces an element of Spv
MC: quot.lift takes a function defined on valuation functions and produces a function defined on Spv
KB: So what about proofs which go "Spv(R) is compact. Proof: take an element of Spv(R), call it v or
    f or whatever, and now manipulate f in the following way..."
MC: That's quot.lift
MC: Actually you will want quot.ind as well
["any subset of the quotient type containing the image of quot.mk is everything"]
or equivalently quot.exists_rep
[lemma exists_rep {α : Sort u} {r : α → α → Prop} (q : quot r) : ∃ a : α, (quot.mk r a) = q :=
]
MC: that is, for every element of Spv there is a valuation function that quot.mk's to it
MC: Note it's not actually a function producing valuation functions, it's an exists
MC: if you prove analogues of those theorems for your type, then you have constructed the
    quotient up to isomorphism
MC: This all has a category theoretic interpretation as a coequalizer, and all constructions
    are natural in that category
MC: As opposed to, say, quot.out, which picks an element from an equivalence class
MC: Although in your case if I understand correctly you also have a canonical way to define quot.out
    satisfying some other universal property to do with the ordered group
    where the valuation and ring have to share the same universe.
    You can prove that the universe need not be the same as part of the universal properties
    i.e. Spv.mk takes as input a valuation function  (v : valuation R A) where {R : Type u} and
    {A : Type v} (so it isn't just instantiating the exists)
KB: "If you want to be polymorphic" -- I just want to do maths. I have no idea if I want to be polymorphic.
     If I just want to define a perfectoid space, do I want to be polymorphic?
MC : In lean, you should usually be polymorphic
     at least in contravariant positions (i.e. the inputs should be maximally polymorphic, the output should
      be minimally polymorphic)
     This is why we don't have nat : Type u
     The general rule is to keep types out of classes if at all possible. Lean behaves better when the
     types are given as "alpha" rather than "the type inside v", particularly if you start manipulating
     the functions (adding them, say).
     It is the same things that make the difference between bundled vs unbundled groups. When
     working "internally", i.e. calculations using the monoid structure, it is better for the type
     to be exposed as a variable
-/


import Mathlib.Tactic.Abel

variable {α : Type _} {a b : α}

example [AddCommMonoid α] : a + (b + a) = a + a + b := by abel
example [AddCommGroup α] : (a + b) - ((b + a) + a) = -a := by abel
example [AddCommGroup α] (x : α) : x - 0 = x := by abel
example [AddCommMonoid α] : (3 : ℕ) • a = a + (2 : ℕ) • a := by abel
example [AddCommGroup α] : (3 : ℤ) • a = a + (2 : ℤ) • a := by abel
example [AddCommGroup α] (a b : α) : a-2•b = a -2•b := by abel

example [AddCommMonoid α] (a b : α) : a + (b + a) = a + a + b := by abel1
example [AddCommGroup α] (a b : α) : (a + b) - ((b + a) + a) = -a := by abel1
example [AddCommGroup α] (x : α) : x - 0 = x := by abel1
example [AddCommMonoid α] (a : α) : (3 : ℕ) • a = a + (2 : ℕ) • a := by abel1
example [AddCommGroup α] (a : α) : (3 : ℤ) • a = a + (2 : ℤ) • a := by abel1
example [AddCommGroup α] (a b : α) : a - 2•b = a - 2•b := by abel1
example [AddCommGroup α] (a : α) : 0 + a = a := by abel1
example [AddCommGroup α] (n : ℕ) (a : α) : n • a = n • a := by abel1
example [AddCommGroup α] (n : ℕ) (a : α) : 0 + n • a = n • a := by abel1

example [AddCommMonoid α] (a b c d e : α) :
  a + (b + (a + (c + (a + (d + (a + e)))))) = e + d + c + b + 4 • a := by abel1
example [AddCommGroup α] (a b c d e : α) :
  a + (b + (a + (c + (a + (d + (a + e)))))) = e + d + c + b + 4 • a := by abel1
example [AddCommGroup α] (a b c d : α) :
  a + b + (c + d - a) = b + c + d := by abel1
example [AddCommGroup α] (a b c : α) :
  a + b + c + (c - a - a) = (-1)•a + b + 2•c := by abel1

-- Make sure we fail on some non-equalities.
example [AddCommMonoid α] (a b c d e : α) :
    a + (b + (a + (c + (a + (d + (a + e)))))) = e + d + c + b + 3 • a ∨ True := by
  fail_if_success
    left; abel1
  right; trivial
example [AddCommGroup α] (a b c d e : α) :
    a + (b + (a + (c + (a + (d + (a + e)))))) = e + d + c + b + 3 • a ∨ True := by
  fail_if_success
    left; abel1
  right; trivial
example [AddCommGroup α] (a b c d : α) :
    a + b + (c + d - a) = b + c - d ∨ True := by
  fail_if_success
    left; abel1
  right; trivial
example [AddCommGroup α] (a b c : α) :
    a + b + c + (c - a - a) = (-1)•a + b + c ∨ True := by
  fail_if_success
    left; abel1
  right; trivial

-- `abel!` should see through terms that are definitionally equal,
def id' (x : α) := x
example [AddCommGroup α] (a b : α) : a + b - b - id' a = 0 := by
  fail_if_success
    abel1
  abel1!

-- https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/Interaction.20of.20abel.20with.20casting/near/319895001
example [AddCommGroup α] : True := by
  have : ∀ (p q r s : α), s + p - q = s - r - (q - r - p) := by
    intro p q r s
    abel
  trivial

-- https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/Interaction.20of.20abel.20with.20casting/near/319897374
example [AddCommGroup α] (x y z : α) : y = x + z - (x - y + z) := by
  have : True := trivial
  abel


/-
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_139
  (s : ℝ → ℝ → ℝ)
  (h₀ : ∀ x≠0, ∀y≠0, s x y = (1/y - 1/x) / (x-y)) :
  s 3 11 = 1/33 :=
begin
  norm_num [h₀],
end


/-
Copyright (c) 2019 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 Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.local_homeomorph
import Mathlib.PostPort

universes u_2 u_3 u_4 l u_5 u_6 u_1

namespace Mathlib

/-!
# Fiber bundles

A topological fiber bundle with fiber `F` over a base `B` is a space projecting on `B` for which the
fibers are all homeomorphic to `F`, such that the local situation around each point is a direct
product. We define a predicate `is_topological_fiber_bundle F p` saying that `p : Z → B` is a
topological fiber bundle with fiber `F`.

It is in general nontrivial to construct a fiber bundle. A way is to start from the knowledge of
how changes of local trivializations act on the fiber. From this, one can construct the total space
of the bundle and its topology by a suitable gluing construction. The main content of this file is
an implementation of this construction: starting from an object of type
`topological_fiber_bundle_core` registering the trivialization changes, one gets the corresponding
fiber bundle and projection.

## Main definitions

* `bundle_trivialization F p` : structure extending local homeomorphisms, defining a local
                  trivialization of a topological space `Z` with projection `p` and fiber `F`.
* `is_topological_fiber_bundle F p` : Prop saying that the map `p` between topological spaces is a
                  fiber bundle with fiber `F`.

* `topological_fiber_bundle_core ι B F` : structure registering how changes of coordinates act
  on the fiber `F` above open subsets of `B`, where local trivializations are indexed by `ι`.

Let `Z : topological_fiber_bundle_core ι B F`. Then we define

* `Z.total_space` : the total space of `Z`, defined as a `Type` as `Σ (b : B), F`, but with a
  twisted topology coming from the fiber bundle structure
* `Z.proj`        : projection from `Z.total_space` to `B`. It is continuous.
* `Z.fiber x`     : the fiber above `x`, homeomorphic to `F` (and defeq to `F` as a type).
* `Z.local_triv i`: for `i : ι`, a local homeomorphism from `Z.total_space` to `B × F`, that
  realizes a trivialization above the set `Z.base_set i`, which is an open set in `B`.

## Implementation notes

A topological fiber bundle with fiber `F` over a base `B` is a family of spaces isomorphic to `F`,
indexed by `B`, which is locally trivial in the following sense: there is a covering of `B` by open
sets such that, on each such open set `s`, the bundle is isomorphic to `s × F`.

To construct a fiber bundle formally, the main data is what happens when one changes trivializations
from `s × F` to `s' × F` on `s ∩ s'`: one should get a family of homeomorphisms of `F`, depending
continuously on the base point, satisfying basic compatibility conditions (cocycle property).
Useful classes of bundles can then be specified by requiring that these homeomorphisms of `F`
belong to some subgroup, preserving some structure (the "structure group of the bundle"): then
these structures are inherited by the fibers of the bundle.

Given such trivialization change data (encoded below in a structure called
`topological_fiber_bundle_core`), one can construct the fiber bundle. The intrinsic canonical
mathematical construction is the following.
The fiber above `x` is the disjoint union of `F` over all trivializations, modulo the gluing
identifications: one gets a fiber which is isomorphic to `F`, but non-canonically
(each choice of one of the trivializations around `x` gives such an isomorphism). Given a
trivialization over a set `s`, one gets an isomorphism between `s × F` and `proj^{-1} s`, by using
the identification corresponding to this trivialization. One chooses the topology on the bundle that
makes all of these into homeomorphisms.

For the practical implementation, it turns out to be more convenient to avoid completely the
gluing and quotienting construction above, and to declare above each `x` that the fiber is `F`,
but thinking that it corresponds to the `F` coming from the choice of one trivialization around `x`.
This has several practical advantages:
* without any work, one gets a topological space structure on the fiber. And if `F` has more
structure it is inherited for free by the fiber.
* In the case of the tangent bundle of manifolds, this implies that on vector spaces the derivative
(from `F` to `F`) and the manifold derivative (from `tangent_space I x` to `tangent_space I' (f x)`)
are equal.

A drawback is that some silly constructions will typecheck: in the case of the tangent bundle, one
can add two vectors in different tangent spaces (as they both are elements of `F` from the point of
view of Lean). To solve this, one could mark the tangent space as irreducible, but then one would
lose the identification of the tangent space to `F` with `F`. There is however a big advantage of
this situation: even if Lean can not check that two basepoints are defeq, it will accept the fact
that the tangent spaces are the same. For instance, if two maps `f` and `g` are locally inverse to
each other, one can express that the composition of their derivatives is the identity of
`tangent_space I x`. One could fear issues as this composition goes from `tangent_space I x` to
`tangent_space I (g (f x))` (which should be the same, but should not be obvious to Lean
as it does not know that `g (f x) = x`). As these types are the same to Lean (equal to `F`), there
are in fact no dependent type difficulties here!

For this construction of a fiber bundle from a `topological_fiber_bundle_core`, we should thus
choose for each `x` one specific trivialization around it. We include this choice in the definition
of the `topological_fiber_bundle_core`, as it makes some constructions more
functorial and it is a nice way to say that the trivializations cover the whole space `B`.

With this definition, the type of the fiber bundle space constructed from the core data is just
`Σ (b : B), F `, but the topology is not the product one, in general.

We also take the indexing type (indexing all the trivializations) as a parameter to the fiber bundle
core: it could always be taken as a subtype of all the maps from open subsets of `B` to continuous
maps of `F`, but in practice it will sometimes be something else. For instance, on a manifold, one
will use the set of charts as a good parameterization for the trivializations of the tangent bundle.
Or for the pullback of a `topological_fiber_bundle_core`, the indexing type will be the same as
for the initial bundle.

## Tags
Fiber bundle, topological bundle, vector bundle, local trivialization, structure group
-/

/--
A structure extending local homeomorphisms, defining a local trivialization of a projection
`proj : Z → B` with fiber `F`, as a local homeomorphism between `Z` and `B × F` defined between two
sets of the form `proj ⁻¹' base_set` and `base_set × F`, acting trivially on the first coordinate.
-/
structure bundle_trivialization {B : Type u_2} (F : Type u_3) {Z : Type u_4} [topological_space B] [topological_space Z] [topological_space F] (proj : Z → B)
extends local_homeomorph Z (B × F)
where
  base_set : set B
  open_base_set : is_open base_set
  source_eq : local_equiv.source (local_homeomorph.to_local_equiv _to_local_homeomorph) = proj ⁻¹' base_set
  target_eq : local_equiv.target (local_homeomorph.to_local_equiv _to_local_homeomorph) = set.prod base_set set.univ
  proj_to_fun : ∀ (p : Z),
  p ∈ local_equiv.source (local_homeomorph.to_local_equiv _to_local_homeomorph) →
    prod.fst (local_equiv.to_fun (local_homeomorph.to_local_equiv _to_local_homeomorph) p) = proj p

protected instance bundle_trivialization.has_coe_to_fun {B : Type u_2} (F : Type u_3) {Z : Type u_4} [topological_space B] [topological_space Z] [topological_space F] (proj : Z → B) : has_coe_to_fun (bundle_trivialization F proj) :=
  has_coe_to_fun.mk (fun (e : bundle_trivialization F proj) => Z → B × F)
    fun (e : bundle_trivialization F proj) =>
      local_equiv.to_fun (local_homeomorph.to_local_equiv (bundle_trivialization.to_local_homeomorph e))

@[simp] theorem bundle_trivialization.coe_coe {B : Type u_2} (F : Type u_3) {Z : Type u_4} [topological_space B] [topological_space Z] [topological_space F] (proj : Z → B) (e : bundle_trivialization F proj) (x : Z) : coe_fn (bundle_trivialization.to_local_homeomorph e) x = coe_fn e x :=
  rfl

@[simp] theorem bundle_trivialization.coe_mk {B : Type u_2} (F : Type u_3) {Z : Type u_4} [topological_space B] [topological_space Z] [topological_space F] (proj : Z → B) (e : local_homeomorph Z (B × F)) (i : set B) (j : is_open i) (k : local_equiv.source (local_homeomorph.to_local_equiv e) = proj ⁻¹' i) (l : local_equiv.target (local_homeomorph.to_local_equiv e) = set.prod i set.univ) (m : ∀ (p : Z),
  p ∈ local_equiv.source (local_homeomorph.to_local_equiv e) →
    prod.fst (local_equiv.to_fun (local_homeomorph.to_local_equiv e) p) = proj p) (x : Z) : coe_fn (bundle_trivialization.mk e i j k l m) x = coe_fn e x :=
  rfl

/-- A topological fiber bundle with fiber F over a base B is a space projecting on B for which the
fibers are all homeomorphic to F, such that the local situation around each point is a direct
product. -/
def is_topological_fiber_bundle {B : Type u_2} (F : Type u_3) {Z : Type u_4} [topological_space B] [topological_space Z] [topological_space F] (proj : Z → B) :=
  ∀ (x : Z),
    ∃ (e : bundle_trivialization F proj),
      x ∈ local_equiv.source (local_homeomorph.to_local_equiv (bundle_trivialization.to_local_homeomorph e))

@[simp] theorem bundle_trivialization.coe_fst {B : Type u_2} {F : Type u_3} {Z : Type u_4} [topological_space B] [topological_space Z] [topological_space F] {proj : Z → B} (e : bundle_trivialization F proj) {x : Z} (ex : x ∈ local_equiv.source (local_homeomorph.to_local_equiv (bundle_trivialization.to_local_homeomorph e))) : prod.fst (coe_fn e x) = proj x :=
  bundle_trivialization.proj_to_fun e x ex

/-- In the domain of a bundle trivialization, the projection is continuous-/
theorem bundle_trivialization.continuous_at_proj {B : Type u_2} {F : Type u_3} {Z : Type u_4} [topological_space B] [topological_space Z] [topological_space F] {proj : Z → B} (e : bundle_trivialization F proj) {x : Z} (ex : x ∈ local_equiv.source (local_homeomorph.to_local_equiv (bundle_trivialization.to_local_homeomorph e))) : continuous_at proj x := sorry

/-- The projection from a topological fiber bundle to its base is continuous. -/
theorem is_topological_fiber_bundle.continuous_proj {B : Type u_2} {F : Type u_3} {Z : Type u_4} [topological_space B] [topological_space Z] [topological_space F] {proj : Z → B} (h : is_topological_fiber_bundle F proj) : continuous proj := sorry

/-- The projection from a topological fiber bundle to its base is an open map. -/
theorem is_topological_fiber_bundle.is_open_map_proj {B : Type u_2} {F : Type u_3} {Z : Type u_4} [topological_space B] [topological_space Z] [topological_space F] {proj : Z → B} (h : is_topological_fiber_bundle F proj) : is_open_map proj := sorry

/-- The first projection in a product is a topological fiber bundle. -/
theorem is_topological_fiber_bundle_fst {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] : is_topological_fiber_bundle F prod.fst := sorry

/-- The second projection in a product is a topological fiber bundle. -/
theorem is_topological_fiber_bundle_snd {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] : is_topological_fiber_bundle F prod.snd := sorry

/-- Core data defining a locally trivial topological bundle with fiber `F` over a topological
space `B`. Note that "bundle" is used in its mathematical sense. This is the (computer science)
bundled version, i.e., all the relevant data is contained in the following structure. A family of
local trivializations is indexed by a type ι, on open subsets `base_set i` for each `i : ι`.
Trivialization changes from `i` to `j` are given by continuous maps `coord_change i j` from
`base_set i ∩ base_set j` to the set of homeomorphisms of `F`, but we express them as maps
`B → F → F` and require continuity on `(base_set i ∩ base_set j) × F` to avoid the topology on the
space of continuous maps on `F`. -/
structure topological_fiber_bundle_core (ι : Type u_4) (B : Type u_5) [topological_space B] (F : Type u_6) [topological_space F]
where
  base_set : ι → set B
  is_open_base_set : ∀ (i : ι), is_open (base_set i)
  index_at : B → ι
  mem_base_set_at : ∀ (x : B), x ∈ base_set (index_at x)
  coord_change : ι → ι → B → F → F
  coord_change_self : ∀ (i : ι) (x : B), x ∈ base_set i → ∀ (v : F), coord_change i i x v = v
  coord_change_continuous : ∀ (i j : ι),
  continuous_on (fun (p : B × F) => coord_change i j (prod.fst p) (prod.snd p))
    (set.prod (base_set i ∩ base_set j) set.univ)
  coord_change_comp : ∀ (i j k : ι) (x : B),
  x ∈ base_set i ∩ base_set j ∩ base_set k → ∀ (v : F), coord_change j k x (coord_change i j x v) = coord_change i k x v

namespace topological_fiber_bundle_core

/-- The index set of a topological fiber bundle core, as a convenience function for dot notation -/
def index {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) :=
  ι

/-- The base space of a topological fiber bundle core, as a convenience function for dot notation -/
def base {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) :=
  B

/-- The fiber of a topological fiber bundle core, as a convenience function for dot notation and
typeclass inference -/
def fiber {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (x : B) :=
  F

protected instance topological_space_fiber {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (x : B) : topological_space (fiber Z x) :=
  id _inst_2

/-- Total space of a topological bundle created from core. It is equal to `Σ (x : B), F` as a type,
but the fiber above `x` is registered as `Z.fiber x` to make sure that it is possible to register
additional type classes on these fibers. -/
def total_space {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) :=
  sigma fun (x : B) => fiber Z x

/-- The projection from the total space of a topological fiber bundle core, on its base. -/
@[simp] def proj {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) : total_space Z → B :=
  fun (p : total_space Z) => sigma.fst p

/-- Local homeomorphism version of the trivialization change. -/
def triv_change {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (j : ι) : local_homeomorph (B × F) (B × F) :=
  local_homeomorph.mk
    (local_equiv.mk (fun (p : B × F) => (prod.fst p, coord_change Z i j (prod.fst p) (prod.snd p)))
      (fun (p : B × F) => (prod.fst p, coord_change Z j i (prod.fst p) (prod.snd p)))
      (set.prod (base_set Z i ∩ base_set Z j) set.univ) (set.prod (base_set Z i ∩ base_set Z j) set.univ) sorry sorry
      sorry sorry)
    sorry sorry sorry sorry

@[simp] theorem mem_triv_change_source {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (j : ι) (p : B × F) : p ∈ local_equiv.source (local_homeomorph.to_local_equiv (triv_change Z i j)) ↔ prod.fst p ∈ base_set Z i ∩ base_set Z j := sorry

/-- Associate to a trivialization index `i : ι` the corresponding trivialization, i.e., a bijection
between `proj ⁻¹ (base_set i)` and `base_set i × F`. As the fiber above `x` is `F` but read in the
chart with index `index_at x`, the trivialization in the fiber above x is by definition the
coordinate change from i to `index_at x`, so it depends on `x`.
The local trivialization will ultimately be a local homeomorphism. For now, we only introduce the
local equiv version, denoted with a prime. In further developments, avoid this auxiliary version,
and use Z.local_triv instead.
-/
def local_triv' {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) : local_equiv (total_space Z) (B × F) :=
  local_equiv.mk
    (fun (p : total_space Z) => (sigma.fst p, coord_change Z (index_at Z (sigma.fst p)) i (sigma.fst p) (sigma.snd p)))
    (fun (p : B × F) => sigma.mk (prod.fst p) (coord_change Z i (index_at Z (prod.fst p)) (prod.fst p) (prod.snd p)))
    (proj Z ⁻¹' base_set Z i) (set.prod (base_set Z i) set.univ) sorry sorry sorry sorry

@[simp] theorem mem_local_triv'_source {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (p : total_space Z) : p ∈ local_equiv.source (local_triv' Z i) ↔ sigma.fst p ∈ base_set Z i :=
  iff.refl (p ∈ local_equiv.source (local_triv' Z i))

@[simp] theorem mem_local_triv'_target {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (p : B × F) : p ∈ local_equiv.target (local_triv' Z i) ↔ prod.fst p ∈ base_set Z i := sorry

@[simp] theorem local_triv'_apply {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (p : total_space Z) : coe_fn (local_triv' Z i) p = (sigma.fst p, coord_change Z (index_at Z (sigma.fst p)) i (sigma.fst p) (sigma.snd p)) :=
  rfl

@[simp] theorem local_triv'_symm_apply {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (p : B × F) : coe_fn (local_equiv.symm (local_triv' Z i)) p =
  sigma.mk (prod.fst p) (coord_change Z i (index_at Z (prod.fst p)) (prod.fst p) (prod.snd p)) :=
  rfl

/-- The composition of two local trivializations is the trivialization change Z.triv_change i j. -/
theorem local_triv'_trans {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (j : ι) : local_equiv.trans (local_equiv.symm (local_triv' Z i)) (local_triv' Z j) ≈
  local_homeomorph.to_local_equiv (triv_change Z i j) := sorry

/-- Topological structure on the total space of a topological bundle created from core, designed so
that all the local trivialization are continuous. -/
protected instance to_topological_space {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) : topological_space (total_space Z) :=
  topological_space.generate_from
    (set.Union
      fun (i : ι) =>
        set.Union
          fun (s : set (B × F)) =>
            set.Union
              fun (s_open : is_open s) => singleton (local_equiv.source (local_triv' Z i) ∩ ⇑(local_triv' Z i) ⁻¹' s))

theorem open_source' {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) : is_open (local_equiv.source (local_triv' Z i)) := sorry

theorem open_target' {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) : is_open (local_equiv.target (local_triv' Z i)) :=
  is_open.prod (is_open_base_set Z i) is_open_univ

/-- Local trivialization of a topological bundle created from core, as a local homeomorphism. -/
def local_triv {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) : local_homeomorph (total_space Z) (B × F) :=
  local_homeomorph.mk
    (local_equiv.mk (local_equiv.to_fun (local_triv' Z i)) (local_equiv.inv_fun (local_triv' Z i))
      (local_equiv.source (local_triv' Z i)) (local_equiv.target (local_triv' Z i)) sorry sorry sorry sorry)
    (open_source' Z i) (open_target' Z i) sorry sorry

/- We will now state again the basic properties of the local trivializations, but without primes,
i.e., for the local homeomorphism instead of the local equiv. -/

@[simp] theorem mem_local_triv_source {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (p : total_space Z) : p ∈ local_equiv.source (local_homeomorph.to_local_equiv (local_triv Z i)) ↔ sigma.fst p ∈ base_set Z i :=
  iff.refl (p ∈ local_equiv.source (local_homeomorph.to_local_equiv (local_triv Z i)))

@[simp] theorem mem_local_triv_target {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (p : B × F) : p ∈ local_equiv.target (local_homeomorph.to_local_equiv (local_triv Z i)) ↔ prod.fst p ∈ base_set Z i := sorry

@[simp] theorem local_triv_apply {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (p : total_space Z) : coe_fn (local_triv Z i) p = (sigma.fst p, coord_change Z (index_at Z (sigma.fst p)) i (sigma.fst p) (sigma.snd p)) :=
  rfl

@[simp] theorem local_triv_symm_fst {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (p : B × F) : coe_fn (local_homeomorph.symm (local_triv Z i)) p =
  sigma.mk (prod.fst p) (coord_change Z i (index_at Z (prod.fst p)) (prod.fst p) (prod.snd p)) :=
  rfl

/-- The composition of two local trivializations is the trivialization change Z.triv_change i j. -/
theorem local_triv_trans {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) (j : ι) : local_homeomorph.trans (local_homeomorph.symm (local_triv Z i)) (local_triv Z j) ≈ triv_change Z i j :=
  local_triv'_trans Z i j

/-- Extended version of the local trivialization of a fiber bundle constructed from core,
registering additionally in its type that it is a local bundle trivialization. -/
def local_triv_ext {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (i : ι) : bundle_trivialization F (proj Z) :=
  bundle_trivialization.mk
    (local_homeomorph.mk (local_homeomorph.to_local_equiv (local_triv Z i)) sorry sorry sorry sorry) (base_set Z i)
    (is_open_base_set Z i) sorry sorry sorry

/-- A topological fiber bundle constructed from core is indeed a topological fiber bundle. -/
theorem is_topological_fiber_bundle {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) : is_topological_fiber_bundle F (proj Z) := sorry

/-- The projection on the base of a topological bundle created from core is continuous -/
theorem continuous_proj {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) : continuous (proj Z) :=
  is_topological_fiber_bundle.continuous_proj (is_topological_fiber_bundle Z)

/-- The projection on the base of a topological bundle created from core is an open map -/
theorem is_open_map_proj {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) : is_open_map (proj Z) :=
  is_topological_fiber_bundle.is_open_map_proj (is_topological_fiber_bundle Z)

/-- Preferred local trivialization of a fiber bundle constructed from core, at a given point, as
a local homeomorphism -/
def local_triv_at {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (p : total_space Z) : local_homeomorph (total_space Z) (B × F) :=
  local_triv Z (index_at Z (proj Z p))

@[simp] theorem mem_local_triv_at_source {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (p : total_space Z) : p ∈ local_equiv.source (local_homeomorph.to_local_equiv (local_triv_at Z p)) := sorry

@[simp] theorem local_triv_at_fst {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (p : total_space Z) (q : total_space Z) : prod.fst (coe_fn (local_triv_at Z p) q) = sigma.fst q :=
  rfl

@[simp] theorem local_triv_at_symm_fst {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (p : total_space Z) (q : B × F) : sigma.fst (coe_fn (local_homeomorph.symm (local_triv_at Z p)) q) = prod.fst q :=
  rfl

/-- Preferred local trivialization of a fiber bundle constructed from core, at a given point, as
a bundle trivialization -/
def local_triv_at_ext {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (p : total_space Z) : bundle_trivialization F (proj Z) :=
  local_triv_ext Z (index_at Z (proj Z p))

@[simp] theorem local_triv_at_ext_to_local_homeomorph {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (p : total_space Z) : bundle_trivialization.to_local_homeomorph (local_triv_at_ext Z p) = local_triv_at Z p :=
  rfl

/-- If an element of `F` is invariant under all coordinate changes, then one can define a
corresponding section of the fiber bundle, which is continuous. This applies in particular to the
zero section of a vector bundle. Another example (not yet defined) would be the identity
section of the endomorphism bundle of a vector bundle. -/
theorem continuous_const_section {ι : Type u_1} {B : Type u_2} {F : Type u_3} [topological_space B] [topological_space F] (Z : topological_fiber_bundle_core ι B F) (v : F) (h : ∀ (i j : ι) (x : B), x ∈ base_set Z i ∩ base_set Z j → coord_change Z i j x v = v) : continuous ((fun (this : B → total_space Z) => this) fun (x : B) => sigma.mk x v) := sorry


import metric_spaces.definitions

/-
A metric space (X, d) is a non-empty set X alongside a function
d : X ⨯ X → ℝ satisfying
- ∀ x, y, ∈ X, d(x, y) ≥ 0 with equality iff x = y.
- ∀ x, y, ∈ X, d(x, y) = d(y, x)
- ∀ x, y, z ∈ X, d(x, z) ≤ d(x, y) + d(y, z).
We call d a metric of the metric space (X, d).

class metric_space (α : Type u) extends has_dist α : Type u :=
(dist_self : ∀ x : α, dist x x = 0)
(eq_of_dist_eq_zero : ∀ {x y : α}, dist x y = 0 → x = y)
(dist_comm : ∀ x y : α, dist x y = dist y x)
(dist_triangle : ∀ x y z : α, dist x z ≤ dist x y + dist y z)
[... Some stuff that doesn't matter ...]
-/

/-
In basic we will prove some simple results regarding metric spaces
including theorem about
- continuous functions
- bounded metric spaces
- open balls and open sets
- closed sets
- closure and interior
- convergence of sequences
-/

noncomputable theory
local attribute [instance] classical.prop_decidable

open set definitions

variables {α : Type*}
variables {X : Type*} [metric_space X]

/- The metric of any two elements of a metric space is non-negative -/
theorem metric_nonneg : ∀ x y : X, 0 ≤ dist x y := λ x y,
begin
  suffices : 0 ≤ dist y x + dist x y - dist y y,
    rw [dist_self, dist_comm, sub_zero] at this,
    linarith,
  linarith [dist_triangle y x y]
end

namespace continuity

variables {Y : Type*} [metric_space Y]
variables {Z : Type*} [metric_space Z]
variables {f : X → Y} {g : Y → Z}

/- The composition of two continuous functions is continuous -/
theorem comp_continuous (h₁ : is_continuous f) (h₂ : is_continuous g) :
is_continuous (g ∘ f) := λ a ε hε,
  let ⟨δ₁, hg₁, hg₂⟩ := h₂ (f a) ε hε in
  let ⟨δ₂, hf₁, hf₂⟩ := h₁ a δ₁ hg₁ in
  ⟨δ₂, hf₁, λ x hx, hg₂ (f x) (hf₂ x hx)⟩

/- The product of two metric spaces is also a metric space
(This messes with the product space defined in mathlib, hence setting the priority to 0) -/
@[priority 0] instance : metric_space (X × Y) :=
{ dist := λ ⟨x₀, y₀⟩ ⟨x₁, y₁⟩, dist x₀ x₁ + dist y₀ y₁,
  dist_self := λ ⟨x, y⟩, show dist x x + dist y y = 0, by simp,
  eq_of_dist_eq_zero :=
    begin
      rintros ⟨x₀, y₀⟩ ⟨x₁, y₁⟩,
      show dist x₀ x₁ + dist y₀ y₁ = 0 → (x₀, y₀) = (x₁, y₁), intro h,
      suffices : dist x₀ x₁ = 0 ∧ dist y₀ y₁ = 0,
        rwa [eq_of_dist_eq_zero this.left, eq_of_dist_eq_zero this.right],
      split,
      all_goals { linarith [metric_nonneg x₀ x₁, metric_nonneg y₀ y₁, h] }
    end,
  dist_comm := λ ⟨x₀, y₀⟩ ⟨x₁, y₁⟩,
    show dist x₀ x₁ + dist y₀ y₁ = dist x₁ x₀ + dist y₁ y₀, by simp [dist_comm],
  dist_triangle := λ ⟨x₀, y₀⟩ ⟨x₁, y₁⟩ ⟨x₂, y₂⟩,
    show dist x₀ x₂ + dist y₀ y₂ ≤ dist x₀ x₁ + dist y₀ y₁ + (dist x₁ x₂ + dist y₁ y₂),
    by linarith [dist_triangle x₀ x₁ x₂, dist_triangle y₀ y₁ y₂]
}

/- This is based on the metric λ ⟨x₀, y₀⟩ ⟨x₁, y₁⟩, dist x₀ x₁ + dist y₀ y₁
/- Given two functions f g : X → ℝ, if both are continuous, then so is λ x : X, (f x, g x) -/
theorem prod_continuous' (f g : X → ℝ) (h₁ : is_continuous f) (h₂ : is_continuous g) :
is_continuous (λ x : X, (f x, g x)) := λ x₀ ε hε,
  let ⟨δ₁, hδ₁, hf⟩ := h₁ x₀ (ε / 2) (half_pos hε) in
  let ⟨δ₂, hδ₂, hg⟩ := h₂ x₀ (ε / 2) (half_pos hε) in
  show ∃ δ > 0, ∀ (x : X), dist x x₀ < δ → dist (f x) (f x₀) + dist (g x) (g x₀) < ε,
  from ⟨min δ₁ δ₂, by simp; from ⟨hδ₁, hδ₂⟩, λ x hx,
begin
  suffices : dist (f x) (f x₀) < ε / 2 ∧ dist (g x) (g x₀) < ε / 2,
    linarith [this.left, this.right],
  split,
    all_goals {apply hf x <|> apply hg x, apply lt_of_lt_of_le hx},
    from inf_le_left, from inf_le_right
end
⟩
-/

variables {X' : Type*} [metric_space X']
variables {Y' : Type*} [metric_space Y']

/- A generalisation of the above using the metric λ x y : X × X' max (dist x.1 y.1) (dist x.2 y.2) -/
theorem prod_continuous (f : X → Y) (g : X' → Y') (h₁ : is_continuous f) (h₂ : is_continuous g) :
is_continuous (λ x : X × X', (f x.1, g x.2)) := λ x₀ ε hε,
  let ⟨δ₁, hδ₁, hf⟩ := h₁ x₀.1 ε hε in
  let ⟨δ₂, hδ₂, hg⟩ := h₂ x₀.2 ε hε in
  show ∃ δ > 0, ∀ (x : X × X'), dist x x₀ < δ → max (dist (f x.1) (f x₀.1)) (dist (g x.2) (g x₀.2)) < ε,
  from ⟨min δ₁ δ₂, by simp; from ⟨hδ₁, hδ₂⟩, λ x hx,
begin
  suffices : dist (f x.1) (f x₀.1) < ε ∧ dist (g x.2) (g x₀.2) < ε,
    simp [this.left, this.right],
  split,
    { apply hf x.1, simp at hx, apply lt_of_le_of_lt _ hx.left,
    show dist x.1 x₀.1 ≤ max (dist x.1 x₀.1) (dist x.2 x₀.2),
    rw le_max_iff, left, apply le_refl  },
    { apply hg x.2, simp at hx, apply lt_of_le_of_lt _ hx.right,
    show dist x.2 x₀.2 ≤ max (dist x.1 x₀.1) (dist x.2 x₀.2),
    rw le_max_iff, right, apply le_refl }
end
⟩

/- TODO: now that we have the product of two metric spaces is also a metric space,
we can show that (+) : ℝ × ℝ → ℝ is a continuous function and this the composition of
this and two continous functions f, g is also continuous, i.e. f + g is continuous.
Similar process for f × g.
-/

/- Defining the diagonal map Δ : X → X × X as Δ(x) := (x, x) -/
def diagonal_map (X : Type*) [metric_space X] : X → X × X := λ x : X, (x, x)

/- The diagonal map is continuous -/
lemma diagonal_map_is_continuous : is_continuous (diagonal_map X) := λ x₀ ε hε,
  ⟨ε, hε, λ x hx, show max (dist x x₀) (dist x x₀) < ε, by simp [hx]⟩

lemma comp_map_prod (f : X → Y) (g : X → Y') :
(λ x : X, (f x, g x)) = (λ x : X × X, (f x.1, g x.2)) ∘ (diagonal_map X) :=
by ext; all_goals {simp, refl}

/- Using the diagonal map, we can show given continuous functions f g, x → (f x, g x) is continous-/
theorem map_prod_continous (f : X → Y) (g : X → Y')
  (h₁ : is_continuous f) (h₂ : is_continuous g) :
  is_continuous (λ x : X, (f x, g x)) :=
begin
  rw comp_map_prod, refine comp_continuous diagonal_map_is_continuous _,
  apply prod_continuous, repeat {assumption}
end

end continuity

namespace bounded

/- If S is bounded then ∀ s₀ ∈ S, ∃ K : ℝ, ∀ s ∈ S, dist s₀ s ≤ K -/
lemma bounded_all {S : set X} (h₀ : is_bounded S) :
∀ s₀ ∈ S, ∃ K : ℝ, ∀ s ∈ S, dist s₀ s ≤ K := λ s₀ hs₀,
or.elim h₀
  (λ h₀, by simp [h₀, hs₀])
  (λ h₀, let ⟨x₀, hx₀, K, hK⟩ := h₀ in
  ⟨K + K, λ s hs,
    by linarith [dist_triangle s₀ x₀ s, hK s₀ hs₀,
    (show dist x₀ s ≤ K, by rw dist_comm; from hK s hs)]⟩)

/- Reverse of the above -/
lemma all_bounded {S : set X} (h₀ : ∀ s₀ ∈ S, ∃ K : ℝ, ∀ s ∈ S, dist s₀ s ≤ K) :
is_bounded S :=
or.elim (eq_empty_or_nonempty S)
  (λ hs, or.inl $ hs)
  (λ hs, or.inr $ let ⟨s', hs'⟩ := hs in
  ⟨s', hs', let ⟨K, hK⟩ := h₀ s' hs' in ⟨K, by simp [dist_comm]; from hK⟩⟩)

/- If S is bounded if and only if ∀ s₀ ∈ S, ∃ K : ℝ, ∀ s ∈ S, dist s₀ s ≤ K -/
theorem bounded_iff_all {S : set X} :
is_bounded S ↔ ∀ s₀ ∈ S, ∃ K : ℝ, ∀ s ∈ S, dist s₀ s ≤ K :=
iff.intro (λ h, bounded_all h) (λ h, all_bounded h)

/- The union of two bounded subsets is also bounded -/
lemma bounded_union_two (S T : set X) (hs : is_bounded S) (ht : is_bounded T) :
is_bounded $ S ∪ T :=
or.elim hs (λ hs, by simp [hs, ht]) (λ ⟨x₀, hx₀, K₀, hK₀⟩,
  or.elim ht (λ ht, by simp [hs, ht]) (λ ⟨x₁, hx₁, K₁, hK₁⟩, or.inr
    ⟨x₀, by simp [hx₀], ⟨max K₀ (K₁ + dist x₀ x₁), λ x hx,
      begin
        cases hx, simp [hK₀, hx],
        rw [le_max_iff], right,  conv_rhs {rw dist_comm},
        apply le_trans (dist_triangle x x₁ x₀), simp [hK₁ x hx]
      end ⟩⟩
    )
  )

/- The union of finitely many bounded subsets is also bounded -/
theorem bounded_union (S : ℕ → set X) (h₀ : ∀ n : ℕ, is_bounded $ S n) :
∀ n : ℕ, is_bounded $ finset.sup (finset.range n) S
| 0       := by left; simp; refl
| (n + 1) := by simpa [finset.range_succ] using bounded_union_two _ _ (h₀ _) (bounded_union n)

/- TODO : Change the above to something like
theorem bounded_union {s : set β} (f : β → set X) {hs : finite s}
  (h₀ : ∀ i ∈ s, is_bounded $ f i) :
  is_bounded $ ⋃ i ∈ s, f i :=
-/

end bounded

namespace open_closed

/- Given an open ball, there exists an open ball (with positive radius) centered anywhere within the ball-/
theorem subset_open_ball (x₀ : X) (r : ℝ) :
∀ y ∈ open_ball x₀ r, ∃ r' > 0, open_ball y r' ⊆ open_ball x₀ r := λ y hy,
  ⟨r - dist x₀ y, by rw mem_set_of_eq at hy; linarith [hy], λ x hx,
    by apply lt_of_le_of_lt (dist_triangle x₀ y x); rw [mem_set_of_eq] at hx; linarith [hx]
  ⟩

/- An open ball is open -/
theorem open_ball_is_open (x₀ : X) (r : ℝ) (h : 0 < r) :
  is_open' $ open_ball x₀ r := subset_open_ball x₀ r

/- An open ball has non-positive radius then its empty -/
lemma nonpos_empty {x₀ : X} {r : ℝ} (h₁ : r ≤ 0) :
open_ball x₀ r = ∅ :=
begin
  ext, split,
    { intro hx, rw mem_set_of_eq at hx,
      exfalso, apply (not_le.mpr hx), apply le_trans h₁,
      from metric_nonneg x₀ x },
    { intro hx, exfalso, from hx }
end

/- An open ball either has positive radius or its empty -/
lemma pos_or_empty (x₀ : X) (r : ℝ) : open_ball x₀ r = ∅ ∨ 0 < r :=
or_iff_not_imp_left.2 (by rw ← not_le; exact mt nonpos_empty)

/- An empty set is open -/
theorem empty_is_open : is_open' (∅ : set X) := λ s hs, by exfalso; from hs

variables {Y : Type*} [metric_space Y]
variables {f : X → Y}

/- f : X → Y is continuous iff f⁻¹ U is open in X whenever U is open -/
lemma contin_to_preimg_open (U : set Y) (h₀ : is_open' U) (h₁ : is_continuous f) :
is_open' $ preimage f U := λ x hx,
  let ⟨ε, hε₁, hε₂⟩ := h₀ (f x) hx in
  let ⟨δ, hδ₁, hδ₂⟩ := h₁ x ε hε₁ in
  ⟨δ, hδ₁, λ x' hx',
    by apply hε₂; rw [mem_set_of_eq, dist_comm]; apply hδ₂ x'; rw dist_comm; assumption⟩

lemma preimg_open_to_contin :
  (∀ (U : set Y) (h₀ : is_open' U), is_open' $ preimage f U) → is_continuous f :=
λ h x ε hε,
  let U := open_ball (f x) ε in have hinU : f x ∈ U := by simp; from hε,
  let ⟨δ, hδ₀, hδ₁⟩ := h U (open_ball_is_open (f x) ε hε) x hinU in
  ⟨δ, hδ₀, λ y hy,
    begin
      suffices : y ∈ f⁻¹' U, simp at this, rw dist_comm, assumption,
      apply hδ₁, rw [mem_set_of_eq, dist_comm], assumption
    end
  ⟩

theorem contin_iff_preimg_open :
  (∀ (U : set Y) (h₀ : is_open' U), is_open' $ preimage f U) ↔ is_continuous f :=
iff.intro
  (preimg_open_to_contin)
  (λ hcontin U hopen, contin_to_preimg_open U hopen hcontin)

/- A continuous function is continuous when restricted to a subspace -/
theorem restricted_contin_of_contin (h : is_continuous f) (U : set X) :
is_continuous $ restrict f U :=
begin
  refine preimg_open_to_contin (λ V hV s hs, _),
  rcases hV (f s) hs with ⟨ε, hε₀, hε₁⟩,
  rcases h s ε hε₀ with ⟨δ, hδ₀, hδ₁⟩,
  refine ⟨δ, hδ₀, λ x hx, hε₁ $ _⟩,
  rw [mem_set_of_eq, dist_comm],
  refine hδ₁ x _, rw dist_comm, exact hx
end

/- An similar lemma for subsets -/
theorem subset_contin_of_contin {S T : set X} (hsub : S ⊆ T)
(f : T → Y) (g : S → Y) (hf : is_continuous f) (hg : g = λ s, f ⟨s.1, hsub s.2⟩) :
is_continuous g :=
begin
  refine preimg_open_to_contin (λ V hV s hs, _),
  rcases hV (g s) hs with ⟨ε, hε₀, hε₁⟩,
  rcases hf ⟨s.1, hsub s.2⟩ ε hε₀ with ⟨δ, hδ₀, hδ₁⟩,
  refine ⟨δ, hδ₀, λ x hx, hε₁ $ _⟩,
  rw [mem_set_of_eq, dist_comm, hg],
  refine hδ₁ ⟨x.1, hsub x.2⟩ _, rw dist_comm, exact hx
end

/- The intersect of finitely many open sets is open -/
lemma inter_open_is_open  {U₀ U₁ : set X}
(h₀ : is_open' U₀) (h₁ : is_open' U₁) : is_open' (U₀ ∩ U₁) := λ s ⟨hs₀, hs₁⟩,
  let ⟨ε₀, hε₀, hε₀'⟩ := h₀ s hs₀ in
  let ⟨ε₁, hε₁, hε₁'⟩ := h₁ s hs₁ in
  let ε := min ε₀ ε₁ in
  ⟨ε, by simp [hε₀, hε₁],
    begin
      rw subset_inter_iff, split,
        { refine subset.trans _ hε₀',
        simp, intros _ h _, assumption },
        refine subset.trans _ hε₁', simp
    end
  ⟩

lemma inter_finite_open_is_open {I : set α} {U : α → set X} (hI : finite I) :
(∀ i ∈ I, is_open' $ U i) → (is_open' $ ⋂ i ∈ I, U i) :=
finite.induction_on hI (λ x, by simp; from (λ s _,
  ⟨1, ⟨by norm_num, subset_univ (open_ball s 1)⟩⟩)) $ λ i S hi hS hopen hopen',
  begin
    rw bInter_insert,
    apply inter_open_is_open,
      { apply hopen', from mem_insert i S },
      apply hopen, intros j hj,
      apply hopen', apply mem_union_right, assumption
  end

/- The union of open sets is open-/
lemma union_open_is_open {U₀ U₁ : set X}
(h₀ : is_open' U₀) (h₁ : is_open' U₁) : is_open' (U₀ ∪ U₁) := λ s hs,
or.elim hs
  (λ hs', let ⟨ε, hε, hε'⟩ := h₀ s hs' in
    ⟨ε, hε, subset.trans hε' (subset_union_left U₀ U₁)⟩)
  (λ hs', let ⟨ε, hε, hε'⟩ := h₁ s hs' in
    ⟨ε, hε, subset.trans hε' (subset_union_right U₀ U₁)⟩)

theorem Union_open_is_open {α} {U : α → set X}
(h : ∀ i, is_open' $ U i) : is_open' $ ⋃ i, U i := λ x hx,
  let ⟨i, hi⟩ := mem_Union.mp hx in
  let ⟨ε, hε, hε'⟩ := h i x hi in
  ⟨ε, hε,
  begin
    refine subset.trans hε' _,
    intros y hy, rw mem_Union,
    from ⟨i, hy⟩
  end
  ⟩

/- The union of finitely many closed sets is closed -/
theorem union_closed_is_closed {U V : set X} (hU : is_closed' U) (hV : is_closed' V) :
is_closed' (U ∪ V) :=
begin
  unfold is_closed' at *,
  rw compl_union, exact inter_open_is_open hU hV
end

theorem union_finite_closed_is_closed {I : set α} {U : α → set X} (hI : finite I)
(h : ∀ i ∈ I, is_closed' $ U i) : (is_closed' $ ⋃ i ∈ I, U i) :=
begin
  unfold is_closed' at *,
  rw compl_bUnion, from inter_finite_open_is_open hI h
end

/- The empty set is closed-/
theorem empty_is_closed : is_closed' (∅ : set X) :=
λ x hx, ⟨1, zero_lt_one, by simp⟩

/- The intersect of closed sets is closed-/
theorem Inter_closed_is_closed {α} {U : α → set X}
  (h : ∀ i, is_closed' $ U i) : is_closed' $ ⋂ i, U i :=
begin
  unfold is_closed' at *,
  rw compl_Inter, from Union_open_is_open h
end

namespace closure'

/- The closure of a closed set is itself -/
theorem closure_self {S : set X} (h : is_closed' S) : (closure' S) = S :=
begin
  ext, unfold closure', simp, split,
    { intro hx, apply hx; finish },
    intros hx T hT₀ hT₁, apply hT₀, assumption
end

/- The closure of a set is also closed -/
lemma closure_closed (S : set X) : is_closed' $ closure' S :=
Inter_closed_is_closed $ λ T, Inter_closed_is_closed $ λ h₁, Inter_closed_is_closed $ λ h₂, h₂

/- The closure of a closure is itself -/
theorem closure_closure' (S : set X) : closure' (closure' S) = closure' S :=
closure_self $ closure_closed S

/- The closure of a set S is S ∪ {limit points of S} -/

-- A set is smaller than its closure
lemma subset_closure' {S : set X} : S ⊆ closure' S :=
subset_Inter $ λ _, subset_Inter $ λ h, subset_Inter $ λ _, h

-- The limit points of a set is smaller than its closure
lemma limit_points_sub_closure' {S : set X} :
limit_points S ⊆ closure' S := λ x hx,
begin
  simp, intros T hS hT,
  apply classical.by_contradiction, intro hnT,
  rcases hT x hnT with ⟨ε, hε₀, hε₁⟩,
  rcases hx ε hε₀ with ⟨y, hy₀, hy₁, hy₂⟩,
  have : y ∈ Tᶜ, apply hε₁, assumption,
  have : y ∈ T, apply hS, assumption,
  contradiction
end

lemma with_limit_points_sub_closure {S : set X} :
S ∪ limit_points S ⊆ closure' S := union_subset_iff.mpr $
  ⟨subset_closure', limit_points_sub_closure'⟩

lemma closure_sub_sub_closed {S T : set X}
(hT : is_closed' T) (hST : S ⊆ T) : closure' S ⊆ T :=
begin
  intros x hx, rw mem_Inter at hx,
  have := mem_Inter.1 (hx T) hST,
  refine mem_Inter.1 this hT
end

/- Sequences within a set and it's limit points that converges converges to itself-/
lemma limit_in_with_limit_points {a : ℕ → X} {l} {S : set X}
(h : ∀ n, a n ∈ S ∪ limit_points S) (h₁ : a ⇒ l) :
l ∈ S ∪ limit_points S :=
begin
  cases classical.em (∃ n, l = a n) with heq hneq,
  cases heq with n hn, cases h n with hS hlS,
  left, exact ((symm hn) ▸ hS), right, exact ((symm hn) ▸ hlS),
  push_neg at hneq, cases classical.em (l ∈ S) with _ hlS,
  { left, assumption },
  { right, intros ε hε,
    cases h₁ (ε / 2) (half_pos hε) with n hn, cases h n with hS' hlS',
    exact ⟨a n, hS', hneq n, lt_trans (hn n (le_refl n)) (half_lt_self hε)⟩,
    rcases hlS' (ε / 2) (half_pos hε) with ⟨y, hy₀, hy₁, hy₂⟩,
    refine ⟨y, hy₀, λ heq, hlS $ _, lt_of_le_of_lt (dist_triangle l (a n) y) _⟩,
    { rw heq, exact hy₀ },
    { rw mem_set_of_eq at hy₂, linarith [hy₂, hn n (le_refl n)] }
  }
end

/- By constructing a sequence that converges outside of the limit points with show
by contradiction that a set with its limit points is closed -/
theorem with_limit_points_is_closed (S : set X) :
is_closed' (S ∪ limit_points S) :=
begin
  intros s hs,
  apply classical.by_contradiction,
  intro h, push_neg at h,
  have : ∀ n : ℕ, ∃ x ∈ open_ball s (1 / (n + 1)), x ∈ (S ∪ limit_points S),
  { intro n, apply classical.by_contradiction,
    push_neg, intro hn,
    exact h (1 / (n + 1)) (nat.one_div_pos_of_nat) hn },
  simp only [classical.skolem] at this,
  rcases this with ⟨a, ha₀, ha₁⟩,
  have : a ⇒ s,
  { intros ε hε,
    cases exists_nat_gt (1 / ε) with N hN,
    refine ⟨N, λ n hn, _⟩,
    have := ha₀ n, rw mem_set_of_eq at this,
    refine lt_trans this _,
    rw (one_div_lt _ hε),
    exact lt_of_lt_of_le hN (by norm_cast; exact le_trans hn (nat.le_succ n)),
    norm_cast, exact nat.succ_pos n },
  exact hs (limit_in_with_limit_points ha₁ this)
end

lemma closure_sub_with_limit_points {S : set X} :
closure' S ⊆ S ∪ limit_points S :=
closure_sub_sub_closed (with_limit_points_is_closed S)
  (subset_union_left S (limit_points S))

theorem with_limit_points_is_closure (S : set X) :
closure' S = S ∪ limit_points S := subset.antisymm_iff.mpr $
  ⟨closure_sub_with_limit_points, with_limit_points_sub_closure⟩

/- If S ⊆ T, the the limit points of S ⊆ limit points of T -/
theorem limit_points_le {S T : set X} (h : S ⊆ T) :
limit_points S ⊆ limit_points T := λ x hx ε hε,
  let ⟨δ, hδ₀, hδ₁⟩ := hx ε hε in ⟨δ,  by apply h; from hδ₀, hδ₁⟩

/- The closure of a smaller set is smaller than closure -/
theorem closure_mono' {S T : set X} (h : S ⊆ T) : closure' S ⊆ closure' T :=
begin
  iterate 2 {rw with_limit_points_is_closure},
  suffices : limit_points S ⊆ limit_points T,
    rw union_subset_iff,
    split, refine subset.trans h _, simp,
      refine subset.trans this _, simp,
  from limit_points_le h
end

theorem monotone_closure' : monotone $ @closure' X _ := λ _ _, closure_mono'

/- Closure is the smallest closed subset -/
theorem closure_is_min {S T : set X}
(h₀ : is_closed' T) (h₁ : S ⊆ T) : closure' S ⊆ T := λ x hx,
begin
  apply mem_Inter.mp hx T, simp only [mem_range],
  refine ⟨h₁, by finish⟩
end

end closure'

namespace interior'

/- An alternative definition of interiror -/
attribute [reducible]
def interior'' (S : set X) := {x : X | x ∈ S ∧ ∃ ε > 0, open_ball x ε ⊆ S}
notation S`⁰` := interior'' S

/- The interior is open -/
lemma interior'_is_open (S : set X) : is_open' $ interior' S :=
Union_open_is_open $ λ _, Union_open_is_open $ λ _, Union_open_is_open $ λ h, h

lemma interior''_is_open (S : set X) : is_open' $ S⁰ := λ x hx,
let ⟨hS, ε, hε₀, hε₁⟩ := hx in
⟨ε, hε₀, λ y hy,
  ⟨by apply hε₁; assumption,
  let ⟨δ, hδ₀, hδ₁⟩ := (open_ball_is_open x ε hε₀) y hy in
  ⟨δ, hδ₀, subset.trans hδ₁ hε₁⟩⟩ ⟩

/- The interior is smaller than the set -/
lemma interior'_subset (S : set X) : interior' S ⊆ S := λ x hx,
let ⟨T, T', hT, hT'⟩ := mem_Union.mp hx in
begin
  simp at hT, cases hT with hT₀ hT₁,
  suffices : (⋃ (h₀ : is_open' T), T) ⊆ T,
  { apply hT₀, apply this, rw hT₁, assumption  },
  { intros y hy, rw mem_Union at hy, cases hy with _ h, assumption  }
end

lemma interior''_subset (S : set X) : S⁰ ⊆ S := λ x hx, hx.1

@[simp] theorem eq_interior (S : set X) : interior' S = (S⁰) :=
begin
  rw subset.antisymm_iff,
  refine ⟨λ x hx, _, λ x hx, _⟩,
  { refine ⟨interior'_subset S hx, _⟩,
    rcases interior'_is_open S x hx with ⟨ε, hε₀, hε₁⟩,
    exact ⟨ε, hε₀, subset.trans hε₁ (interior'_subset S)⟩
  },
  { rw mem_Union, refine ⟨S⁰, _⟩,
    rw mem_Union, refine ⟨interior''_subset S, _⟩,
    rw mem_Union, exact ⟨interior''_is_open S, hx⟩ }
end

/- The interior of an open set is itself -/
theorem interior'_self {S : set X} (h : is_open' S) :
interior' S = S :=
begin
  rw subset.antisymm_iff,
  refine ⟨interior'_subset S, λ _ hx, _⟩,
  rw mem_Union, refine ⟨S, _⟩,
  rw mem_Union, refine ⟨subset.refl S, _⟩,
  rw mem_Union, refine ⟨h, hx⟩
end

theorem interior''_self {S : set X} (h : is_open' S) :
S⁰ = S := by rw ←eq_interior; exact interior'_self h

/- The interior of the interior is itself -/
@[simp] theorem interior'_interior {S : set X} :
interior' (interior' S) = interior' S:= interior'_self $ interior'_is_open S

/- If S ⊆ T, then S⁰ ⊆ T⁰ -/
theorem interior''_mono {S T : set X} (h : S ⊆ T) :
S⁰ ⊆ (T⁰) := λ x ⟨hx₀, ε, hε₀, hε₁⟩, ⟨h hx₀, ε, hε₀, subset.trans hε₁ h⟩

theorem monotone_interior'' : monotone $ @interior'' X _ := @interior''_mono X _

end interior'

namespace boundary

open closure' interior'

/- Alternative definition of boundary -/
@[simp] theorem boundary' {S : set X} : boundary S = closure' S \ (S⁰) :=
by rwa ←eq_interior S

end boundary

end open_closed

namespace convergence

variables {Y : Type*} [metric_space Y]
variables {s : ℕ → X}

lemma dist_zero {x₀ x₁ : X}
(h : ∀ (ε : ℝ) (hε : 0 < ε), dist x₀ x₁ < ε ) : x₀ = x₁ :=
eq_of_dist_eq_zero $ classical.by_contradiction $ λ hne,
begin
  cases lt_or_gt_of_ne hne with hlt hgt,
  rw ←not_le at hlt, from hlt (metric_nonneg x₀ x₁),
  linarith [h _ hgt]
end

/- Limits are unique -/
theorem limit_unique (x₀ x₁ : X) (h₀ : s ⇒ x₀) (h₁ : s ⇒ x₁) :
x₀ = x₁ := dist_zero $ λ ε hε,
  let ⟨N₀, hN₀⟩ := h₀ (ε / 2) (half_pos hε) in
  let ⟨N₁, hN₁⟩ := h₁ (ε / 2) (half_pos hε) in
  let N := max N₀ N₁ in
  lt_of_le_of_lt (dist_triangle x₀ (s N) x₁) $
by linarith [hN₀ N (le_max_left _ _),
  show dist (s N) x₁ < ε / 2, by rw dist_comm; from hN₁ N (le_max_right _ _)]

/- Constants converges to themselves -/
theorem const_converge (x : X) : (λ n, x) ⇒ x :=
  λ ε hε, ⟨1, λ n hn, by rw dist_self; exact hε⟩

/- Sequential continuity -/
theorem seq_contin {x : X} {f : X → Y} (hf : is_continuous f)
(hs : s ⇒ x) : (λ n, f (s n)) ⇒ f x := λ ε hε,
begin
  rcases hf x ε hε with ⟨δ, hδ₀, hδ₁⟩,
  rcases hs δ hδ₀ with ⟨N, hN⟩,
  refine ⟨N, λ n hn, _⟩,
  simp only [],
  rw dist_comm, refine hδ₁ _ _,
  rw dist_comm, refine hN _ hn
end

theorem seq_contin' {x : X} {f : X → Y}
  (hf : ∀ s : ℕ → X, s ⇒ x → (λ n, f (s n)) ⇒ f x) :
  is_continuous_at f x :=
begin
  unfold is_continuous_at,
  by_contra h, push_neg at h,
  rcases h with ⟨ε, hε, h⟩,
  choose s hs using λ n : ℕ, h (1 / (n + 1) : ℝ) nat.one_div_pos_of_nat,
  have : s ⇒ x,
    { intros δ hδ,
      rcases exists_nat_gt (1 / δ) with ⟨N, hN'⟩,
      refine ⟨N, λ n hn, _⟩,
      rw dist_comm,
      apply lt_trans (hs n).1 ((one_div_lt _ hδ).2 _),
      exact nat.cast_add_one_pos n,
      apply lt_trans hN', norm_cast,
      exact nat.lt_succ_iff.mpr hn },
  specialize hf s this,
  suffices : ¬ ((λ (n : ℕ), f (s n)) ⇒ f x), contradiction,
  unfold converge_to, push_neg,
  refine ⟨ε, hε, λ N, ⟨N, le_refl _, _⟩⟩,
  rw dist_comm, exact (hs N).2
end

end convergence