import .util

namespace posets
    open function
    open classical (em prop_decidable)
    open classical (renaming some → unexists) (renaming some_spec → unexists_prop)
    local attribute [instance] prop_decidable

    open util

    -- comparability:
    inductive comp {T: Sort _} [hT: partial_order T] (x y: T): Prop
    | le (hle: x ≤ y): comp
    | ge (hge: y ≤ x): comp

    infix ` <=> `:50 := comp

    @[refl] theorem comp.refl {T: Sort _} [hT: partial_order T] (x: T):
        x <=> x := begin
            apply comp.le,
            refl,
        end

    @[symm] theorem comp.symm {T: Sort _} [hT: partial_order T] {x y: T}:
        x <=> y →
        y <=> x := begin
            intro hcomp,
            induction hcomp,
            case comp.le {
                apply comp.ge,
                exact hcomp,
            },
            case comp.ge {
                apply comp.le,
                exact hcomp,
            },
        end

    theorem le_of_not_lt {T: Sort _} [hT: partial_order T] {x y: T} (hcomp: x <=> y):
        ¬ x < y →
        y ≤ x := begin
            intro hnlt,
            cases hcomp,
            case comp.le {
                rw [le_iff_lt_or_eq] at hcomp,
                cases hcomp,
                case or.inl {
                    contradiction,
                },
                case or.inr {
                    rw [hcomp],
                },
            },
            case comp.ge {
                exact hcomp,
            },
        end

    theorem lt_of_not_le {T: Sort _} [hT: partial_order T] {x y: T} (hcomp: x <=> y):
        ¬ x ≤ y →
        y < x := begin
            intro hnlt,
            cases hcomp,
            case comp.le {
                contradiction,
            },
            case comp.ge {
                by_contra hnlt,
                have hyx: comp y x := comp.le hcomp,
                have hle := le_of_not_lt hyx hnlt,
                contradiction,
            },
        end

    -- connectedness:
    inductive con {T: Sort _} [hT: partial_order T]: T → T → Prop
    | le {x y: T} (hle: x ≤ y): con x y
    | ge {x y: T} (hge: y ≤ x): con x y
    | trans {x y z: T} (hxy: con x y) (hyz: con y z): con x z

    attribute [trans] con.trans

    infix ` ~ ` := con

    @[refl] theorem con.refl {T: Sort _} [hT: partial_order T] (x: T):
        x ~ x := begin
            intros,
            apply con.le,
            refl,
        end

    @[symm] theorem con.symm {T: Sort _} [hT: partial_order T] {x y: T}:
        x ~ y →
        y ~ x := begin
            intro hcon,
            induction hcon,
            case con.le {
                apply con.ge,
                assumption,
            },
            case con.ge {
                apply con.le,
                assumption,
            },
            case con.trans {
                exact con.trans hcon_ih_hyz hcon_ih_hxy,
            },
        end

    theorem comp.con {T: Sort _} [hT: partial_order T] {x y: T} (hcomp: x <=> y):
        x ~ y := begin
            induction hcomp,
            case comp.le {
                apply con.le,
                exact hcomp,
            },
            case comp.ge {
                apply con.ge,
                exact hcomp,
            },
        end

    -- order with bottom:
    class bot_order (T: Sort _) extends partial_order T :=
        (bot: T)
        (bot_le: ∀ x: T, bot ≤ x)

    @[reducible, inline] def bot {T: Sort _} [hT: bot_order T] := hT.bot

    @[reducible, inline] def bot_le {T: Sort _} [hT: bot_order T] := hT.bot_le

    instance bot_order.inhabited {T: Sort _} [hT: bot_order T]:
        inhabited T := (| bot |)

    theorem bot_uniq {T: Sort _} [hT: bot_order T]:
        ∀ bot': T,
        (∀ x: T, bot' ≤ x) →
        bot' = bot := begin
            intros bot' bot_le',
            apply le_antisymm,
            apply bot_le',
            apply bot_le,
        end

    theorem bot_ne_elim {T: Sort _} [hT: bot_order T] {x: T}:
        x ≠ bot →
        ∃ y: T,
        ¬ x ≤ y := begin
            intro hne,
            apply not_forall_not_elim,
            intro hnlt,
            apply hne,
            apply bot_uniq,
            intro y,
            have hy := hnlt y,
            simp at hy,
            exact hy,
        end

    theorem con.all_of_bot {T: Sort _} [hT: bot_order T] {x y: T}:
        x ~ y := begin
            have hx: x ~ bot, by {
                apply con.ge,
                apply bot_le,
            },
            have hy: bot ~ y, by {
                apply con.le,
                apply bot_le,
            },
            exact con.trans hx hy,
        end

    -- trivial order:
    @[reducible] def trivially_ordered (T: Sort _) [hT: partial_order T]: Prop :=
        ∀ {x y: T},
        x ≤ y → x = y

    @[simp] theorem trivially_ordered.elim {T: Sort _} [hT: partial_order T] (htriv: trivially_ordered T):
        ∀ {x y: T},
        x ≤ y ↔ x = y := begin
            intros,
            apply iff.intro,
            {
                intro h,
                exact htriv h,
            },
            {
                intro h,
                rw [h],
            },
        end

    @[simp] theorem trivially_ordered.of_bot {T: Sort _} [hT: bot_order T] (htriv: trivially_ordered T):
        ∀ {x: T},
        x = bot := begin
            intros,
            symmetry,
            apply htriv,
            apply bot_le,
        end

    -- not marked as instance to avoid automatically trivially ordering everything
    def trivial_ordering (T: Sort _):
        partial_order T := {
            le := eq,
            le_refl := begin
                intros,
                refl,
            end,
            le_trans := begin
                intros x y z hxy hyz,
                rw [hxy, ←hyz],
            end,
            le_antisymm := begin
                intros,
                assumption,
            end,
        }

    theorem trivial_ordering.is_triv (T: Sort _):
        @trivially_ordered T (trivial_ordering T) := begin
            intros x y,
            intro hxy,
            cases hxy,
            refl,
        end

    -- min:
    noncomputable def or_else {T: Sort _} [hT: bot_order T] (x y: T):
        T := if x = bot then y else x

    infix ` ?? `:60 := or_else

    theorem or_else.le_refl {T: Sort _} [hT: bot_order T] {x y: T}:
        x ≤ x ?? y := begin
            rw [or_else],
            cases em (x = bot),
            case or.inl {
                rw [if_pos h, h],
                apply bot_le,
            },
            case or.inr {
                rw [if_neg h],
            },
        end

    theorem or_else.le_of_le {T: Sort _} [hT: bot_order T] {x y: T}:
        x ≤ y →
        x ≤ y ?? x := begin
            intro hle,
            rw [or_else],
            cases em (y = bot),
            case or.inl {
                rw [if_pos h],
            },
            case or.inr {
                rw [if_neg h],
                exact hle,
            },
        end

    -- max:
    noncomputable def and_then {T: Sort _} [hT: bot_order T] (x y: T):
        T := if x = bot then x else y

    infix ` >> `:60 := and_then

    theorem and_then.le_refl {T: Sort _} [hT: bot_order T] {x y: T}:
        y >> x ≤ x := begin
            rw [and_then],
            cases em (y = bot),
            case or.inl {
                rw [if_pos h, h],
                apply bot_le,
            },
            case or.inr {
                rw [if_neg h],
            },
        end

    theorem and_then.le_of_le {T: Sort _} [hT: bot_order T] {x y: T}:
        x ≤ y →
        y >> x ≤ y := begin
            intro hle,
            rw [and_then],
            cases em (y = bot),
            case or.inl {
                rw [if_pos h],
            },
            case or.inr {
                rw [if_neg h],
                exact hle,
            },
        end

    -- function classes:
    class increasing {T: Sort _} [hT: partial_order T] (f: T → T): Prop :=
        (elim:
            ∀ {x: T},
            x ≤ f x)

    class decreasing {T: Sort _} [hT: partial_order T] (f: T → T): Prop :=
        (elim:
            ∀ {x: T},
            f x ≤ x)

    class monotone {T T': Sort _} [hT: partial_order T] [hT': partial_order T'] (f: T → T'): Prop :=
        (elim:
            ∀ {x y: T},
            x ≤ y →
            f x ≤ f y)

    class antitone {T T': Sort _} [hT: partial_order T] [hT': partial_order T'] (f: T → T'): Prop :=
        (elim:
            ∀ {x y: T},
            f x ≤ f y →
            x ≤ y)

    -- examples:
    instance id.increasing {T: Sort _} [hT: partial_order T]:
        increasing (@id T) := begin
            split,
            intro x,
            simp,
        end

    instance id.decreasing {T: Sort _} [hT: partial_order T]:
        decreasing (@id T) := begin
            split,
            intro x,
            simp,
        end

    instance id.monotone {T: Sort _} [hT: partial_order T]:
        monotone (@id T) := begin
            split,
            intros x y hxy,
            simp,
            exact hxy,
        end

    instance id.antitone {T: Sort _} [hT: partial_order T]:
        antitone (@id T) := begin
            split,
            intros x y hid,
            simp at hid,
            exact hid,
        end

    instance nat.succ.monotone:
        monotone nat.succ := begin
            split,
            intros,
            apply nat.succ_le_succ,
            assumption,
        end

    -- monotonicity:
    theorem monotone.of_comparable {T T': Sort _} [hT: partial_order T] [hT': partial_order T'] {f: T → T'} [hf: monotone f]:
        ∀ {x y: T},
        x <=> y →
        f x <=> f y := begin
            intros x y hcomp,
            induction hcomp,
            case comp.le {
                apply comp.le,
                apply hf.elim,
                exact hcomp,
            },
            case comp.ge {
                apply comp.ge,
                apply hf.elim,
                exact hcomp,
            },
        end

    theorem monotone.of_con {T T': Sort _} [hT: partial_order T] [hT': partial_order T'] {f: T → T'} [hf: monotone f]:
        ∀ {x y: T},
        x ~ y →
        f x ~ f y := begin
            intros x y hcon,
            induction hcon,
            case con.le {
                apply con.le,
                apply hf.elim,
                assumption,
            },
            case con.ge {
                apply con.ge,
                apply hf.elim,
                assumption,
            },
            case con.trans {
                exact con.trans hcon_ih_hxy hcon_ih_hyz,
            },
        end

    @[simp] theorem monotone.bot_to_bot_of_sur {T T': Sort _} [hT: bot_order T] [hT': bot_order T'] (f: T → T') [hfm: monotone f] [hfs: surjective f]:
        f bot = bot := begin
            apply bot_uniq,
            intro x,
            have hfsx := hfs x,
            apply exists.elim hfsx,
            intros y hy,
            rw [←hy],
            apply hfm.elim,
            apply bot_le,
        end

    def monotone.cod_bot_of_sur {T T': Sort _} [hT: bot_order T] [hT': partial_order T'] (f: T → T') [hfm: monotone f] [hfs: surjective f]:
        bot_order T' := begin
            split,
            show T', from f bot,
            intro x,
            have hfsx := hfs x,
            apply exists.elim hfsx,
            intros y hy,
            rw [←hy],
            apply hfm.elim,
            apply bot_le,
        end

    instance monotone.of_composition {T T' T'': Sort _} [hT: partial_order T] [hT': partial_order T'] [hT'': partial_order T''] (g: T → T') [hg: monotone g] (f: T' → T'') [hf: monotone f]:
        monotone (f ∘ g) := begin
            split,
            intros x y hxy,
            simp,
            apply hf.elim,
            apply hg.elim,
            exact hxy,
        end

    -- galois connections:
    structure galois_connection (A B: Sort _) [hA: partial_order A] [hB: partial_order B] :=
        (F: A → B)
        [hF: monotone F]
        (G: B → A)
        [hG: monotone G]
        (elim:
            ∀ {a: A} {b: B},
            F a ≤ b ↔
            a ≤ G b)

    def galois_connection.closure {A B: Sort _} [hA: partial_order A] [hB: partial_order B] (gc: galois_connection A B):
        A → A := gc.G ∘ gc.F

    instance galois_connection.closure.monotone {A B: Sort _} [hA: partial_order A] [hB: partial_order B] (gc: galois_connection A B):
        monotone gc.closure := by apply @monotone.of_composition A B A hA hB hA gc.F gc.hF gc.G gc.hG

    def galois_connection.kernel {A B: Sort _} [hA: partial_order A] [hB: partial_order B] (gc: galois_connection A B):
        B → B := gc.F ∘ gc.G

    instance galois_connection.kernel.monotone {A B: Sort _} [hA: partial_order A] [hB: partial_order B] (gc: galois_connection A B):
        monotone gc.kernel := by apply @monotone.of_composition B A B hB hA hB gc.G gc.hG gc.F gc.hF

    -- trivial or well-founded:
    def has_bot (T: Sort _) [hT: partial_order T]: Prop :=
        ∃ bot: T,
        ∀ x: T,
        bot ≤ x

    noncomputable def has_bot.bot_order {T: Sort _} [hT: partial_order T] (hbot: has_bot T):
        bot_order T := {
            bot := unexists hbot,
            bot_le := unexists_prop hbot,
            ..hT,
        }

    inductive triv_or_bot (T: Sort _) [hT: partial_order T]: Prop
    | triv (htriv: trivially_ordered T): triv_or_bot
    | bot (hbot: has_bot T): triv_or_bot

    -- lower bounds:
    @[reducible] def lower_bound {T: Sort _} [ht: partial_order T] (lb: T) (s: set T): Prop :=
        ∀ x: T,
        x ∈ s →
        lb ≤ x

    infix ≤ := lower_bound

    class bounded_below {T: Sort _} [hT: partial_order T] (s: set T) :=
        (elim:
            ∃ lb: T,
            lb ≤ s)

    instance bounded_below.of_bot {T: Sort _} [hT: bot_order T] (s: set T):
        bounded_below s := begin
            split,
            apply exists.intro bot,
            intros x hx,
            apply bot_le,
        end

    noncomputable def bounded_below.bot_of_bounded_univ {T: Sort _} [hT: partial_order T] [hs: bounded_below {x: T | true}]:
        bot_order T := begin
            split,
            show T, from unexists hs.elim,
            intros,
            apply unexists_prop hs.elim,
            split,
        end

    class glb_prop (T: Sort _) extends partial_order T :=
        (has_glb:
            ∀ s: set T,
            ∀ [hs: bounded_below s],
            ∃ glb: T,
            glb ≤ s ∧
            ∀ x: T,
            x ≤ s →
            x ≤ glb)

    noncomputable def inf {T: Sort _} [hT: glb_prop T] (s: set T) [hs: bounded_below s]: T :=
        unexists (glb_prop.has_glb s)

    theorem inf_le {T: Sort _} [hT: glb_prop T] {s: set T} [hs: bounded_below s]:
        inf s ≤ s :=
            (unexists_prop (glb_prop.has_glb s)).1

    theorem inf_glb {T: Sort _} [hT: glb_prop T] {s: set T} [hs: bounded_below s]:
        ∀ x: T,
        x ≤ s →
        x ≤ inf s :=
            (unexists_prop (glb_prop.has_glb s)).2

    -- order inversion:
    def invert_order (T: Sort _) [hT: partial_order T]:
        partial_order T := {
            le := λ x y: T, y ≤ x,
            le_refl := begin
                intros,
                apply hT.le_refl,
            end,
            le_trans := begin
                intros,
                apply hT.le_trans;
                assumption,
            end,
            le_antisymm := begin
                intros,
                apply hT.le_antisymm;
                assumption,
            end,
        }

    class monotone.decreasing {T T': Sort _} [hT: partial_order T] [hT': partial_order T'] (f: T → T'): Prop :=
        (elim:
            ∀ {x y: T},
            x ≤ y →
            f y ≤ f x)
end posets


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

The `even` and `odd` predicates on the natural numbers.
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.nat.modeq
import Mathlib.PostPort

universes u_1

namespace Mathlib

namespace nat

@[simp] theorem mod_two_ne_one {n : ℕ} : ¬n % bit0 1 = 1 ↔ n % bit0 1 = 0 := sorry

@[simp] theorem mod_two_ne_zero {n : ℕ} : ¬n % bit0 1 = 0 ↔ n % bit0 1 = 1 := sorry

theorem even_iff {n : ℕ} : even n ↔ n % bit0 1 = 0 := sorry

theorem odd_iff {n : ℕ} : odd n ↔ n % bit0 1 = 1 := sorry

theorem not_even_iff {n : ℕ} : ¬even n ↔ n % bit0 1 = 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (¬even n ↔ n % bit0 1 = 1)) (propext even_iff)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (¬n % bit0 1 = 0 ↔ n % bit0 1 = 1)) (propext mod_two_ne_zero)))
      (iff.refl (n % bit0 1 = 1)))

theorem not_odd_iff {n : ℕ} : ¬odd n ↔ n % bit0 1 = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (¬odd n ↔ n % bit0 1 = 0)) (propext odd_iff)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (¬n % bit0 1 = 1 ↔ n % bit0 1 = 0)) (propext mod_two_ne_one)))
      (iff.refl (n % bit0 1 = 0)))

theorem even_iff_not_odd {n : ℕ} : even n ↔ ¬odd n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (even n ↔ ¬odd n)) (propext not_odd_iff)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (even n ↔ n % bit0 1 = 0)) (propext even_iff)))
      (iff.refl (n % bit0 1 = 0)))

@[simp] theorem odd_iff_not_even {n : ℕ} : odd n ↔ ¬even n :=
  eq.mpr (id (Eq._oldrec (Eq.refl (odd n ↔ ¬even n)) (propext not_even_iff)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (odd n ↔ n % bit0 1 = 1)) (propext odd_iff)))
      (iff.refl (n % bit0 1 = 1)))

theorem even_or_odd (n : ℕ) : even n ∨ odd n :=
  or.imp_right (iff.mpr odd_iff_not_even) (em (even n))

theorem even_or_odd' (n : ℕ) : ∃ (k : ℕ), n = bit0 1 * k ∨ n = bit0 1 * k + 1 := sorry

theorem even_xor_odd (n : ℕ) : xor (even n) (odd n) :=
  or.dcases_on (even_or_odd n)
    (fun (h : even n) => Or.inl { left := h, right := iff.mp even_iff_not_odd h })
    fun (h : odd n) => Or.inr { left := h, right := iff.mp odd_iff_not_even h }

theorem even_xor_odd' (n : ℕ) : ∃ (k : ℕ), xor (n = bit0 1 * k) (n = bit0 1 * k + 1) := sorry

theorem odd_gt_zero {n : ℕ} (h : odd n) : 0 < n :=
  Exists.dcases_on h
    fun (k : ℕ) (hk : n = bit0 1 * k + 1) => eq.mpr (id (Eq._oldrec (Eq.refl (0 < n)) hk)) succ_pos'

protected instance even.decidable_pred : decidable_pred even :=
  fun (n : ℕ) => decidable_of_decidable_of_iff (nat.decidable_eq (n % bit0 1) 0) sorry

protected instance decidable_pred_odd : decidable_pred odd :=
  fun (n : ℕ) => decidable_of_decidable_of_iff not.decidable sorry

@[simp] theorem even_zero : even 0 := Exists.intro 0 (of_as_true trivial)

@[simp] theorem not_even_one : ¬even 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (¬even 1)) (propext even_iff))) one_ne_zero

@[simp] theorem even_bit0 (n : ℕ) : even (bit0 n) :=
  Exists.intro n
    (eq.mpr (id (Eq._oldrec (Eq.refl (bit0 n = bit0 1 * n)) (bit0.equations._eqn_1 n)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (n + n = bit0 1 * n)) (two_mul n))) (Eq.refl (n + n))))

theorem even_add {m : ℕ} {n : ℕ} : even (m + n) ↔ (even m ↔ even n) := sorry

theorem even.add {m : ℕ} {n : ℕ} (hm : even m) (hn : even n) : even (m + n) := sorry

@[simp] theorem not_even_bit1 (n : ℕ) : ¬even (bit1 n) := sorry

theorem two_not_dvd_two_mul_add_one (a : ℕ) : ¬bit0 1 ∣ bit0 1 * a + 1 := sorry

theorem two_not_dvd_two_mul_sub_one {a : ℕ} (w : 0 < a) : ¬bit0 1 ∣ bit0 1 * a - 1 := sorry

theorem even_sub {m : ℕ} {n : ℕ} (h : n ≤ m) : even (m - n) ↔ (even m ↔ even n) := sorry

theorem even.sub {m : ℕ} {n : ℕ} (hm : even m) (hn : even n) : even (m - n) := sorry

theorem even_succ {n : ℕ} : even (Nat.succ n) ↔ ¬even n := sorry

theorem even_mul {m : ℕ} {n : ℕ} : even (m * n) ↔ even m ∨ even n := sorry

/-- If `m` and `n` are natural numbers, then the natural number `m^n` is even
if and only if `m` is even and `n` is positive. -/
theorem even_pow {m : ℕ} {n : ℕ} : even (m ^ n) ↔ even m ∧ n ≠ 0 := sorry

theorem even_div {a : ℕ} {b : ℕ} : even (a / b) ↔ a % (bit0 1 * b) / b = 0 := sorry

theorem neg_one_pow_eq_one_iff_even {α : Type u_1} [ring α] {n : ℕ} (h1 : -1 ≠ 1) :
    (-1) ^ n = 1 ↔ even n :=
  sorry

@[simp] theorem neg_one_pow_two {α : Type u_1} [ring α] : (-1) ^ bit0 1 = 1 := sorry

theorem neg_one_pow_of_even {α : Type u_1} [ring α] {n : ℕ} : even n → (-1) ^ n = 1 := sorry

theorem neg_one_pow_of_odd {α : Type u_1} [ring α] {n : ℕ} : odd n → (-1) ^ n = -1 := sorry

end Mathlib


import analysis.calculus.mean_value
import analysis.special_functions.exp
import analysis.special_functions.exp_deriv
import data.nat.factorial.basic
import data.nat.choose.basic
import basic
import exp
import coeff
open polynomial

open set filter

noncomputable theory

@[simp]
def double_factorial : ℕ → ℕ
| 0 := 1
| 1 := 1
| (k+2) := (k+2) * double_factorial k

notation n `‼`:10000 := double_factorial n -- this is \!! not two !'s
localized "notation (name := nat.factorial) n `!`:10000 := nat.factorial n" in nat

lemma double_factorial_def (n : ℕ) : double_factorial (n + 2) = (n + 2) * double_factorial n := rfl

lemma double_factorial_def' (n : ℕ) : (n+1) ‼ = (n+1) * (n-1)‼ := by { cases n; refl }

lemma succ_mul_choose_succ_eq (n k : ℕ) : (k+1) * (nat.choose n (k+1)) = (nat.choose n k) * (n - k) :=
begin
  cases n,
    { simp },
    { have := nat.choose_mul_succ_eq n k,
      have := nat.succ_mul_choose_eq n k,
      repeat {rw nat.succ_eq_add_one at *},
      linarith },
end

lemma neg_one_half_pow (n : ℕ) : (-1 : ℝ) ^ ((n + 2) / 2) = (-1) * (-1) ^ (n / 2) :=
begin
  rw [(by linarith : n+2 = n + 2 * 1), nat.add_mul_div_left, pow_add];
  simp
end

@[simp] -- a_{n, k}
def hermite_explicit (n k : ℕ) : ℝ :=
if (even (n-k)) then (-1)^((n-k)/2) * (n-k-1)‼ * nat.choose n k
else 0

lemma hermite_explicit_def (n k : ℕ) : hermite_explicit n k =
if (even (n-k)) then (-1)^((n-k)/2) * (n-k-1)‼ * nat.choose n k else 0 := rfl

@[simp] -- alt definition of a_{n+k, k}
def hermite_explicit' (n k : ℕ) : ℝ :=
if (even n) then (-1)^(n/2) * (n-1)‼ * nat.choose (n+k) k
else 0

lemma hermite_explicit_def' (n k : ℕ) : hermite_explicit' n k =
if (even n) then (-1)^(n/2) * (n-1)‼ * nat.choose (n+k) k else 0 := rfl

-- lemma hermite_explicit_eq_explicit' (n k : ℕ) :
--   (if (even n) then (-1 : ℝ)^(n/2) * (n-1)‼ * nat.choose (n+k) k
--   else 0) = (if (even n) then (-1 : ℝ)^(n/2) * (n-1)‼ * nat.choose (n+k) k
--   else 0) := rfl

lemma hermite_explicit_eq_explicit' (n k : ℕ) :
  hermite_explicit (n+k) k = hermite_explicit' n k :=
by simp [hermite_explicit, hermite_explicit', nat.add_sub_cancel]

lemma hermite_explicit_upper (n k : ℕ) : hermite_explicit n (n + k + 1) = 0 :=
begin
  simp only [hermite_explicit],
  rw nat.choose_eq_zero_of_lt (by linarith : n < n + k + 1),
  simp
end

lemma hermite_explicit_upper' (n k : ℕ) (h : n < k) : hermite_explicit n k = 0 :=
begin
  obtain ⟨k', rfl⟩ := nat.exists_eq_add_of_lt h,
  apply hermite_explicit_upper,
end

lemma hermite_explicit'_recur (n k : ℕ) : hermite_explicit' (n + 2) (k + 1) =
hermite_explicit' (n + 2) k - (k + 2) * hermite_explicit' n (k + 2) :=
begin
  -- Simplify `even (n+2)` to `even n`
  simp only [hermite_explicit', nat.even_add, even_two, iff_true],
  split_ifs,
  { -- Some algebra rearrangement.
    rw [nat.add_succ_sub_one, mul_comm (↑k + _),
        (by linarith : n + 2 + (k + 1) = (n + 1) + (k + 1) + 1),
        (by linarith : n + 2 + k = (n + 1) + (k + 1)),
        (by linarith : n + (k + 2) = (n + 1) + (k + 1))],
    -- Factor out the (-1)'s.
    rw [neg_one_half_pow, sub_eq_add_neg],
    nth_rewrite 4 neg_eq_neg_one_mul,
    simp only [mul_assoc, ← mul_add],
    congr' 2,
    -- Factor out the factorials.
    norm_cast,
    rw [double_factorial_def', mul_comm (n + 1)],
    simp only [mul_assoc, ← mul_add],
    congr' 1,
    -- Factor out the binomial coefficients.
    rw [nat.choose, mul_add],
    have key : (k + 2) * _ = _ := succ_mul_choose_succ_eq (n+1 + (k+1)) (k+1),
    rw nat.add_sub_cancel at key,
    linarith },
  { -- The odd case (0 = 0 - 0)
    simp },
end

lemma hermite_explicit_recur_zero (n : ℕ) : hermite_explicit (n + 1) 0 = - hermite_explicit n 1 :=
begin
  cases n,
  { simp },
  { simp only [hermite_explicit, tsub_zero, nat.succ_sub_succ_eq_sub, eq_self_iff_true],
    rw (by ring : n.succ + 1 = n + 2),
    simp only [nat.even_add, even_two, iff_true],
    split_ifs,
  { rw [neg_one_half_pow, double_factorial_def', nat.choose_zero_right, nat.choose_one_right],
    simp [mul_one, mul_assoc, mul_comm, neg_mul],
    ring },
  { simp }}
end

lemma hermite_explicit_recur (n k : ℕ) : hermite_explicit (n + 1) (k + 1) =
hermite_explicit n k - (k + 2) * hermite_explicit n (k + 2):=
begin
  cases le_or_gt k n with h,
  { obtain ⟨n', rfl⟩ := nat.exists_eq_add_of_le h,
    rw [add_comm k n', add_assoc],
    repeat {rw hermite_explicit_eq_explicit'},
    cases n',
      { rw hermite_explicit_upper' (0 + k) (k + 2) (by linarith),
        simp [hermite_explicit'] },
      { cases n',
        { rw hermite_explicit_upper' (1 + k) (k + 2) (by linarith),
          simp [hermite_explicit'] },
        repeat {rw hermite_explicit_def},
        repeat {rw nat.add_sub_cancel},
        repeat {rw (by {simp [← nat.add_one, ← nat.sub_sub]} : n'.succ.succ + k - (k + 2) = n')},
        rw (by { repeat {rw ← nat.add_one}, ring} : n'.succ.succ + k = n' + (k + 2)),
        repeat {rw ← hermite_explicit_def'},
        {apply hermite_explicit'_recur }}},
  { repeat {rw hermite_explicit_upper'};
    linarith }
end

lemma hermite_explicit'_recur_zero (n : ℕ) : hermite_explicit' (n + 2) 0 = -hermite_explicit' n 1 :=
begin
    -- Simplify `even (n+2)` to `even n`
  simp only [hermite_explicit', nat.even_add, even_two, iff_true],
  split_ifs,
  { rw neg_one_half_pow,
    nth_rewrite 2 neg_eq_neg_one_mul,
    rw [(by ring : n + 2 - 1 = n + 1), double_factorial_def'],
    simp only [mul_assoc],
    congr' 2,
    rw [nat.choose, nat.choose_zero_right,
        nat.choose_zero_right, nat.choose_one_right],
    norm_cast,
    simp [mul_one, add_comm, mul_comm] },
  { simp }
end

lemma hermite_explicit'_zero (k : ℕ) : hermite_explicit' 0 k = 1 := by simp

lemma hermite_explicit'_one (k : ℕ) : hermite_explicit' 1 k = 0 := by simp

lemma hermite_coeff_eq_explicit (n k : ℕ) :
coeff (hermite n) k = if (even (n-k)) then (-1 : ℝ)^((n-k)/2) * (n-k-1)‼ * nat.choose n k else 0 :=
begin
  induction n with n ih generalizing k,
  { cases k,
    { simp },
    { rw (by ring : k.succ = 0 + k + 1),
      rw [← hermite_explicit_def, hermite_explicit_upper, hermite_coeff_upper] }},
  { cases k,
    { rw [← hermite_explicit_def, hermite_explicit_recur_zero, hermite_coeff_recur_zero,
          hermite_explicit_def, ih 1] },
    { rw [← hermite_explicit_def, hermite_explicit_recur, hermite_coeff_recur],
      repeat { rw hermite_explicit_def },
      rw [ih k, ih (k+2)] }}
end

lemma hermite_coeff_eq_explicit' (n k : ℕ) :
coeff (hermite (n+k)) k = if (even n) then (-1)^(n/2) * (n-1)‼ * nat.choose (n+k) k else 0 :=
by rw [← hermite_explicit_def', ← hermite_explicit_eq_explicit', hermite_coeff_eq_explicit, ← hermite_explicit_def]


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

/-!
# Uniform convergence

A sequence of functions `Fₙ` (with values in a metric space) converges uniformly on a set `s` to a
function `f` if, for all `ε > 0`, for all large enough `n`, one has for all `y ∈ s` the inequality
`dist (f y, Fₙ y) < ε`. Under uniform convergence, many properties of the `Fₙ` pass to the limit,
most notably continuity. We prove this in the file, defining the notion of uniform convergence
in the more general setting of uniform spaces, and with respect to an arbitrary indexing set
endowed with a filter (instead of just `ℕ` with `at_top`).

## Main results

Let `α` be a topological space, `β` a uniform space, `Fₙ` and `f` be functions from `α`to `β`
(where the index `n` belongs to an indexing type `ι` endowed with a filter `p`).

* `tendsto_uniformly_on F f p s`: the fact that `Fₙ` converges uniformly to `f` on `s`. This means
  that, for any entourage `u` of the diagonal, for large enough `n` (with respect to `p`), one has
  `(f y, Fₙ y) ∈ u` for all `y ∈ s`.
* `tendsto_uniformly F f p`: same notion with `s = univ`.
* `tendsto_uniformly_on.continuous_on`: a uniform limit on a set of functions which are continuous
  on this set is itself continuous on this set.
* `tendsto_uniformly.continuous`: a uniform limit of continuous functions is continuous.
* `tendsto_uniformly_on.tendsto_comp`: If `Fₙ` tends uniformly to `f` on a set `s`, and `gₙ` tends
  to `x` within `s`, then `Fₙ gₙ` tends to `f x` if `f` is continuous at `x` within `s`.
* `tendsto_uniformly.tendsto_comp`: If `Fₙ` tends uniformly to `f`, and `gₙ` tends to `x`, then
  `Fₙ gₙ` tends to `f x`.

We also define notions where the convergence is locally uniform, called
`tendsto_locally_uniformly_on F f p s` and `tendsto_locally_uniformly F f p`. The previous theorems
all have corresponding versions under locally uniform convergence.

Finally, we introduce the notion of a uniform Cauchy sequence, which is to uniform
convergence what a Cauchy sequence is to the usual notion of convergence.

## Implementation notes

Most results hold under weaker assumptions of locally uniform approximation. In a first section,
we prove the results under these weaker assumptions. Then, we derive the results on uniform
convergence from them.

## Tags

Uniform limit, uniform convergence, tends uniformly to
 -/

noncomputable theory
open_locale topological_space classical uniformity filter

open set filter

universes u v w
variables {α β γ ι : Type*} [uniform_space β]
variables {F : ι → α → β} {f : α → β} {s s' : set α} {x : α} {p : filter ι} {g : ι → α}

/-!
### Different notions of uniform convergence

We define uniform convergence and locally uniform convergence, on a set or in the whole space.
-/

/-- A sequence of functions `Fₙ` converges uniformly on a set `s` to a limiting function `f` with
respect to the filter `p` if, for any entourage of the diagonal `u`, one has `p`-eventually
`(f x, Fₙ x) ∈ u` for all `x ∈ s`. -/
def tendsto_uniformly_on (F : ι → α → β) (f : α → β) (p : filter ι) (s : set α) :=
∀ u ∈ 𝓤 β, ∀ᶠ n in p, ∀ x ∈ s, (f x, F n x) ∈ u

/--
A sequence of functions `Fₙ` converges uniformly on a set `s` to a limiting function `f` w.r.t.
filter `p` iff the function `(n, x) ↦ (f x, Fₙ x)` converges along `p ×ᶠ 𝓟 s` to the uniformity.
In other words: one knows nothing about the behavior of `x` in this limit besides it being in `s`.
-/
lemma tendsto_uniformly_on_iff_tendsto {F : ι → α → β} {f : α → β} {p : filter ι} {s : set α} :
  tendsto_uniformly_on F f p s ↔ tendsto (λ q : ι × α, (f q.2, F q.1 q.2)) (p ×ᶠ 𝓟 s) (𝓤 β) :=
forall₂_congr $ λ u u_in, by simp [mem_map, filter.eventually, mem_prod_principal]

/-- A sequence of functions `Fₙ` converges uniformly to a limiting function `f` with respect to a
filter `p` if, for any entourage of the diagonal `u`, one has `p`-eventually
`(f x, Fₙ x) ∈ u` for all `x`. -/
def tendsto_uniformly (F : ι → α → β) (f : α → β) (p : filter ι) :=
∀ u ∈ 𝓤 β, ∀ᶠ n in p, ∀ x, (f x, F n x) ∈ u

lemma tendsto_uniformly_on_iff_tendsto_uniformly_comp_coe :
  tendsto_uniformly_on F f p s ↔ tendsto_uniformly (λ i (x : s), F i x) (f ∘ coe) p :=
forall₂_congr $ λ V hV, by simp

/--
A sequence of functions `Fₙ` converges uniformly to a limiting function `f` w.r.t.
filter `p` iff the function `(n, x) ↦ (f x, Fₙ x)` converges along `p ×ᶠ ⊤` to the uniformity.
In other words: one knows nothing about the behavior of `x` in this limit.
-/
lemma tendsto_uniformly_iff_tendsto {F : ι → α → β} {f : α → β} {p : filter ι} :
  tendsto_uniformly F f p ↔ tendsto (λ q : ι × α, (f q.2, F q.1 q.2)) (p ×ᶠ ⊤) (𝓤 β) :=
forall₂_congr $ λ u u_in, by simp [mem_map, filter.eventually, mem_prod_top]

/-- Uniform converence implies pointwise convergence. -/
lemma tendsto_uniformly.tendsto_at (h : tendsto_uniformly F f p) (x : α) :
  tendsto (λ n, F n x) p $ 𝓝 (f x) :=
uniform.tendsto_nhds_right.mpr $ λ u hu, mem_map.mpr $ by { filter_upwards [h u hu], tauto, }

lemma tendsto_uniformly_on_univ :
  tendsto_uniformly_on F f p univ ↔ tendsto_uniformly F f p :=
by simp [tendsto_uniformly_on, tendsto_uniformly]

lemma tendsto_uniformly_on.mono {s' : set α}
  (h : tendsto_uniformly_on F f p s) (h' : s' ⊆ s) : tendsto_uniformly_on F f p s' :=
λ u hu, (h u hu).mono (λ n hn x hx, hn x (h' hx))

lemma tendsto_uniformly_on.congr {F' : ι → α → β}
  (hf : tendsto_uniformly_on F f p s) (hff' : ∀ᶠ n in p, set.eq_on (F n) (F' n) s) :
  tendsto_uniformly_on F' f p s :=
begin
  refine (λ u hu, ((hf u hu).and hff').mono (λ n h x hx, _)),
  rw ← h.right hx,
  exact h.left x hx,
end

protected lemma tendsto_uniformly.tendsto_uniformly_on
  (h : tendsto_uniformly F f p) : tendsto_uniformly_on F f p s :=
(tendsto_uniformly_on_univ.2 h).mono (subset_univ s)

/-- Composing on the right by a function preserves uniform convergence on a set -/
lemma tendsto_uniformly_on.comp (h : tendsto_uniformly_on F f p s) (g : γ → α) :
  tendsto_uniformly_on (λ n, F n ∘ g) (f ∘ g) p (g ⁻¹' s) :=
λ u hu, (h u hu).mono (λ i hi, λ a, hi (g a))

/-- Composing on the right by a function preserves uniform convergence -/
lemma tendsto_uniformly.comp (h : tendsto_uniformly F f p) (g : γ → α) :
  tendsto_uniformly (λ n, F n ∘ g) (f ∘ g) p :=
λ u hu, (h u hu).mono (λ i hi, λ a, hi (g a))

/-- Composing on the left by a uniformly continuous function preserves
  uniform convergence on a set -/
lemma uniform_continuous.comp_tendsto_uniformly_on [uniform_space γ] {g : β → γ}
  (hg : uniform_continuous g) (h : tendsto_uniformly_on F f p s) :
  tendsto_uniformly_on (λ i, g ∘ (F i)) (g ∘ f) p s :=
λ u hu, h _ (hg hu)

/-- Composing on the left by a uniformly continuous function preserves uniform convergence -/
lemma uniform_continuous.comp_tendsto_uniformly [uniform_space γ] {g : β → γ}
  (hg : uniform_continuous g) (h : tendsto_uniformly F f p) :
  tendsto_uniformly (λ i, g ∘ (F i)) (g ∘ f) p :=
λ u hu, h _ (hg hu)

lemma tendsto_uniformly_on.prod_map {ι' α' β' : Type*} [uniform_space β']
  {F' : ι' → α' → β'} {f' : α' → β'} {p' : filter ι'} {s' : set α'}
  (h : tendsto_uniformly_on F f p s) (h' : tendsto_uniformly_on F' f' p' s') :
  tendsto_uniformly_on (λ (i : ι × ι'), prod.map (F i.1) (F' i.2))
    (prod.map f f') (p.prod p') (s ×ˢ s') :=
begin
  intros u hu,
  rw [uniformity_prod_eq_prod, mem_map, mem_prod_iff] at hu,
  obtain ⟨v, hv, w, hw, hvw⟩ := hu,
  exact mem_prod_iff.mpr ⟨_, h v hv, _, h' w hw,
    λ i hi a ha, hvw (show (_, _) ∈ v ×ˢ w, from ⟨hi.1 a.1 ha.1, hi.2 a.2 ha.2⟩)⟩,
end

lemma tendsto_uniformly.prod_map {ι' α' β' : Type*} [uniform_space β'] {F' : ι' → α' → β'}
  {f' : α' → β'} {p' : filter ι'} (h : tendsto_uniformly F f p) (h' : tendsto_uniformly F' f' p') :
  tendsto_uniformly (λ (i : ι × ι'), prod.map (F i.1) (F' i.2)) (prod.map f f') (p.prod p') :=
begin
  rw [←tendsto_uniformly_on_univ, ←univ_prod_univ] at *,
  exact h.prod_map h',
end

lemma tendsto_uniformly_on.prod {ι' β' : Type*} [uniform_space β'] {F' : ι' → α → β'} {f' : α → β'}
  {p' : filter ι'} (h : tendsto_uniformly_on F f p s) (h' : tendsto_uniformly_on F' f' p' s) :
  tendsto_uniformly_on (λ (i : ι × ι') a, (F i.1 a, F' i.2 a)) (λ a, (f a, f' a)) (p.prod p') s :=
(congr_arg _ s.inter_self).mp ((h.prod_map h').comp (λ a, (a, a)))

lemma tendsto_uniformly.prod {ι' β' : Type*} [uniform_space β'] {F' : ι' → α → β'} {f' : α → β'}
  {p' : filter ι'} (h : tendsto_uniformly F f p) (h' : tendsto_uniformly F' f' p') :
  tendsto_uniformly (λ (i : ι × ι') a, (F i.1 a, F' i.2 a)) (λ a, (f a, f' a)) (p.prod p') :=
(h.prod_map h').comp (λ a, (a, a))

/-- Uniform convergence on a set `s` to a constant function is equivalent to convergence in
`p ×ᶠ 𝓟 s`. -/
lemma tendsto_prod_principal_iff {c : β} :
  tendsto ↿F (p ×ᶠ 𝓟 s) (𝓝 c) ↔ tendsto_uniformly_on F (λ _, c) p s :=
begin
  unfold tendsto,
  simp_rw [nhds_eq_comap_uniformity, map_le_iff_le_comap.symm, map_map, le_def, mem_map,
    mem_prod_principal],
  simpa,
end

/-- Uniform convergence to a constant function is equivalent to convergence in `p ×ᶠ ⊤`. -/
lemma tendsto_prod_top_iff {c : β} : tendsto ↿F (p ×ᶠ ⊤) (𝓝 c) ↔ tendsto_uniformly F (λ _, c) p :=
by rw [←principal_univ, ←tendsto_uniformly_on_univ, ←tendsto_prod_principal_iff]

/-- Uniform convergence on the empty set is vacuously true -/
lemma tendsto_uniformly_on_empty :
  tendsto_uniformly_on F f p ∅ :=
λ u hu, by simp

/-- Uniform convergence on a singleton is equivalent to regular convergence -/
lemma tendsto_uniformly_on_singleton_iff_tendsto :
  tendsto_uniformly_on F f p {x} ↔ tendsto (λ n : ι, F n x) p (𝓝 (f x)) :=
by simp_rw [uniform.tendsto_nhds_right, tendsto_uniformly_on, mem_singleton_iff, forall_eq,
  tendsto_def, preimage, filter.eventually]

/-- If a sequence `g` converges to some `b`, then the sequence of constant functions
`λ n, λ a, g n` converges to the constant function `λ a, b` on any set `s` -/
lemma filter.tendsto.tendsto_uniformly_on_const
  {g : ι → β} {b : β} (hg : tendsto g p (𝓝 b)) (s : set α) :
  tendsto_uniformly_on (λ n : ι, λ a : α, g n) (λ a : α, b) p s :=
λ u hu, hg.eventually
  (eventually_of_mem (mem_nhds_left b hu) (λ x hx y hy, hx) : ∀ᶠ x in 𝓝 b, ∀ y ∈ s, (b, x) ∈ u)

lemma uniform_continuous_on.tendsto_uniformly [uniform_space α] [uniform_space γ]
  {x : α} {U : set α} (hU : U ∈ 𝓝 x)
  {F : α → β → γ} (hF : uniform_continuous_on ↿F (U ×ˢ (univ : set β))) :
  tendsto_uniformly F (F x) (𝓝 x) :=
begin
  let φ := (λ q : α × β, ((x, q.2), q)),
  rw [tendsto_uniformly_iff_tendsto,
      show (λ q : α × β, (F x q.2, F q.1 q.2)) = prod.map ↿F ↿F ∘ φ, by { ext ; simpa }],
  apply hF.comp (tendsto_inf.mpr ⟨_, _⟩),
  { rw [uniformity_prod, tendsto_inf, tendsto_comap_iff, tendsto_comap_iff,
      show (λp : (α × β) × α × β, (p.1.1, p.2.1)) ∘ φ = (λa, (x, a)) ∘ prod.fst, by { ext, simp },
      show (λp : (α × β) × α × β, (p.1.2, p.2.2)) ∘ φ = (λb, (b, b)) ∘ prod.snd, by { ext, simp }],
    exact ⟨tendsto_left_nhds_uniformity.comp tendsto_fst,
           (tendsto_diag_uniformity id ⊤).comp tendsto_top⟩ },
  { rw tendsto_principal,
    apply mem_of_superset (prod_mem_prod hU (mem_top.mpr rfl)) (λ q h, _),
    simp [h.1, mem_of_mem_nhds hU] }
end

lemma uniform_continuous₂.tendsto_uniformly [uniform_space α] [uniform_space γ]
  {f : α → β → γ} (h : uniform_continuous₂ f) {x : α} : tendsto_uniformly f (f x) (𝓝 x) :=
uniform_continuous_on.tendsto_uniformly univ_mem $
  by rwa [univ_prod_univ, uniform_continuous_on_univ]

/-- A sequence is uniformly Cauchy if eventually all of its pairwise differences are
uniformly bounded -/
def uniform_cauchy_seq_on
  (F : ι → α → β) (p : filter ι) (s : set α) : Prop :=
  ∀ u : set (β × β), u ∈ 𝓤 β → ∀ᶠ (m : ι × ι) in (p ×ᶠ p),
    ∀ (x : α), x ∈ s → (F m.fst x, F m.snd x) ∈ u

/-- A sequence that converges uniformly is also uniformly Cauchy -/
lemma tendsto_uniformly_on.uniform_cauchy_seq_on (hF : tendsto_uniformly_on F f p s) :
  uniform_cauchy_seq_on F p s :=
begin
  intros u hu,
  rcases comp_symm_of_uniformity hu with ⟨t, ht, htsymm, htmem⟩,
  apply ((hF t ht).prod_mk (hF t ht)).mono,
  intros n h x hx,
  cases h with hl hr,
  specialize hl x hx,
  specialize hr x hx,
  exact set.mem_of_mem_of_subset (prod_mk_mem_comp_rel (htsymm hl) hr) htmem,
end

/-- A uniformly Cauchy sequence converges uniformly to its limit -/
lemma uniform_cauchy_seq_on.tendsto_uniformly_on_of_tendsto [ne_bot p]
  (hF : uniform_cauchy_seq_on F p s) (hF' : ∀ x : α, x ∈ s → tendsto (λ n, F n x) p (nhds (f x))) :
  tendsto_uniformly_on F f p s :=
begin
  -- Proof idea: |f_n(x) - f(x)| ≤ |f_n(x) - f_m(x)| + |f_m(x) - f(x)|. We choose `n`
  -- so that |f_n(x) - f_m(x)| is uniformly small across `s` whenever `m ≥ n`. Then for
  -- a fixed `x`, we choose `m` sufficiently large such that |f_m(x) - f(x)| is small.
  intros u hu,
  rcases comp_symm_of_uniformity hu with ⟨t, ht, htsymm, htmem⟩,

  -- Choose n
  apply (hF t ht).curry.mono,

  -- Work with a specific x
  intros n hn x hx,
  refine set.mem_of_mem_of_subset (mem_comp_rel.mpr _) htmem,

  -- Choose m
  specialize hF' x hx,
  rw uniform.tendsto_nhds_right at hF',
  rcases (hn.and (hF'.eventually (eventually_mem_set.mpr ht))).exists with ⟨m, hm, hm'⟩,

  -- Finish the proof
  exact ⟨F m x, ⟨hm', htsymm (hm x hx)⟩⟩,
end

lemma uniform_cauchy_seq_on.mono {s' : set α} (hf : uniform_cauchy_seq_on F p s) (hss' : s' ⊆ s) :
  uniform_cauchy_seq_on F p s' :=
λ u hu, (hf u hu).mono (λ x hx y hy, hx y (hss' hy))

/-- Composing on the right by a function preserves uniform Cauchy sequences -/
lemma uniform_cauchy_seq_on.comp {γ : Type*} (hf : uniform_cauchy_seq_on F p s) (g : γ → α) :
  uniform_cauchy_seq_on (λ n, F n ∘ g) p (g ⁻¹' s) :=
λ u hu, (hf u hu).mono (λ x hx y hy, hx (g y) hy)

/-- Composing on the left by a uniformly continuous function preserves
uniform Cauchy sequences -/
lemma uniform_continuous.comp_uniform_cauchy_seq_on [uniform_space γ] {g : β → γ}
  (hg : uniform_continuous g) (hf : uniform_cauchy_seq_on F p s) :
  uniform_cauchy_seq_on (λ n, g ∘ (F n)) p s :=
λ u hu, hf _ (hg hu)

lemma uniform_cauchy_seq_on.prod_map {ι' α' β' : Type*} [uniform_space β']
  {F' : ι' → α' → β'} {p' : filter ι'} {s' : set α'}
  (h : uniform_cauchy_seq_on F p s) (h' : uniform_cauchy_seq_on F' p' s') :
  uniform_cauchy_seq_on (λ (i : ι × ι'), prod.map (F i.1) (F' i.2))
    (p.prod p') (s ×ˢ s') :=
begin
  intros u hu,
  rw [uniformity_prod_eq_prod, mem_map, mem_prod_iff] at hu,
  obtain ⟨v, hv, w, hw, hvw⟩ := hu,
  simp_rw [mem_prod, prod_map, and_imp, prod.forall],
  rw [← set.image_subset_iff] at hvw,
  apply (tendsto_swap4_prod.eventually ((h v hv).prod_mk (h' w hw))).mono,
  intros x hx a b ha hb,
  refine hvw ⟨_, mk_mem_prod (hx.1 a ha) (hx.2 b hb), rfl⟩,
end

lemma uniform_cauchy_seq_on.prod {ι' β' : Type*} [uniform_space β'] {F' : ι' → α → β'}
  {p' : filter ι'}
  (h : uniform_cauchy_seq_on F p s) (h' : uniform_cauchy_seq_on F' p' s) :
  uniform_cauchy_seq_on (λ (i : ι × ι') a, (F i.fst a, F' i.snd a)) (p ×ᶠ p') s :=
(congr_arg _ s.inter_self).mp ((h.prod_map h').comp (λ a, (a, a)))

lemma uniform_cauchy_seq_on.prod' {β' : Type*} [uniform_space β'] {F' : ι → α → β'}
  (h : uniform_cauchy_seq_on F p s) (h' : uniform_cauchy_seq_on F' p s) :
  uniform_cauchy_seq_on (λ (i : ι) a, (F i a, F' i a)) p s :=
begin
  intros u hu,
  have hh : tendsto (λ x : ι, (x, x)) p (p ×ᶠ p), { exact tendsto_diag, },
  exact (hh.prod_map hh).eventually ((h.prod h') u hu),
end

section seq_tendsto

lemma tendsto_uniformly_on_of_seq_tendsto_uniformly_on {l : filter ι} [l.is_countably_generated]
  (h : ∀ u : ℕ → ι, tendsto u at_top l → tendsto_uniformly_on (λ n, F (u n)) f at_top s) :
  tendsto_uniformly_on F f l s :=
begin
  rw [tendsto_uniformly_on_iff_tendsto, tendsto_iff_seq_tendsto],
  intros u hu,
  rw tendsto_prod_iff' at hu,
  specialize h (λ n, (u n).fst) hu.1,
  rw tendsto_uniformly_on_iff_tendsto at h,
  have : ((λ (q : ι × α), (f q.snd, F q.fst q.snd)) ∘ u)
    = (λ (q : ℕ × α), (f q.snd, F ((λ (n : ℕ), (u n).fst) q.fst) q.snd)) ∘ (λ n, (n, (u n).snd)),
  { ext1 n, simp, },
  rw this,
  refine tendsto.comp h _,
  rw tendsto_prod_iff',
  exact ⟨tendsto_id, hu.2⟩,
end

lemma tendsto_uniformly_on.seq_tendsto_uniformly_on {l : filter ι}
  (h : tendsto_uniformly_on F f l s) (u : ℕ → ι) (hu : tendsto u at_top l) :
  tendsto_uniformly_on (λ n, F (u n)) f at_top s :=
begin
  rw tendsto_uniformly_on_iff_tendsto at h ⊢,
  have : (λ (q : ℕ × α), (f q.snd, F (u q.fst) q.snd))
    = (λ (q : ι × α), (f q.snd, F q.fst q.snd)) ∘ (λ p : ℕ × α, (u p.fst, p.snd)),
  { ext1 x, simp, },
  rw this,
  refine h.comp _,
  rw tendsto_prod_iff',
  exact ⟨hu.comp tendsto_fst, tendsto_snd⟩,
end

lemma tendsto_uniformly_on_iff_seq_tendsto_uniformly_on {l : filter ι} [l.is_countably_generated] :
  tendsto_uniformly_on F f l s
    ↔ ∀ u : ℕ → ι, tendsto u at_top l → tendsto_uniformly_on (λ n, F (u n)) f at_top s :=
⟨tendsto_uniformly_on.seq_tendsto_uniformly_on, tendsto_uniformly_on_of_seq_tendsto_uniformly_on⟩

lemma tendsto_uniformly_iff_seq_tendsto_uniformly {l : filter ι} [l.is_countably_generated] :
  tendsto_uniformly F f l
    ↔ ∀ u : ℕ → ι, tendsto u at_top l → tendsto_uniformly (λ n, F (u n)) f at_top :=
begin
  simp_rw ← tendsto_uniformly_on_univ,
  exact tendsto_uniformly_on_iff_seq_tendsto_uniformly_on,
end

end seq_tendsto

variable [topological_space α]

/-- A sequence of functions `Fₙ` converges locally uniformly on a set `s` to a limiting function
`f` with respect to a filter `p` if, for any entourage of the diagonal `u`, for any `x ∈ s`, one
has `p`-eventually `(f y, Fₙ y) ∈ u` for all `y` in a neighborhood of `x` in `s`. -/
def tendsto_locally_uniformly_on (F : ι → α → β) (f : α → β) (p : filter ι) (s : set α) :=
  ∀ u ∈ 𝓤 β, ∀ x ∈ s, ∃ t ∈ 𝓝[s] x, ∀ᶠ n in p, ∀ y ∈ t, (f y, F n y) ∈ u

/-- A sequence of functions `Fₙ` converges locally uniformly to a limiting function `f` with respect
to a filter `p` if, for any entourage of the diagonal `u`, for any `x`, one has `p`-eventually
`(f y, Fₙ y) ∈ u` for all `y` in a neighborhood of `x`. -/
def tendsto_locally_uniformly (F : ι → α → β) (f : α → β) (p : filter ι) :=
  ∀ u ∈ 𝓤 β, ∀ (x : α), ∃ t ∈ 𝓝 x, ∀ᶠ n in p, ∀ y ∈ t, (f y, F n y) ∈ u

lemma tendsto_locally_uniformly_on_iff_tendsto_locally_uniformly_comp_coe :
  tendsto_locally_uniformly_on F f p s ↔
  tendsto_locally_uniformly (λ i (x : s), F i x) (f ∘ coe) p :=
begin
  refine forall₂_congr (λ V hV, _),
  simp only [exists_prop, function.comp_app, set_coe.forall, subtype.coe_mk],
  refine forall₂_congr (λ x hx, ⟨_, _⟩),
  { rintro ⟨t, ht₁, ht₂⟩,
    obtain ⟨u, hu₁, hu₂⟩ := mem_nhds_within_iff_exists_mem_nhds_inter.mp ht₁,
    exact ⟨coe⁻¹' u,
           (mem_nhds_subtype _ _ _).mpr ⟨u, hu₁, rfl.subset⟩,
           ht₂.mono (λ i hi y hy₁ hy₂, hi y (hu₂ ⟨hy₂, hy₁⟩))⟩, },
  { rintro ⟨t, ht₁, ht₂⟩,
    obtain ⟨u, hu₁, hu₂⟩ := (mem_nhds_subtype _ _ _).mp ht₁,
    exact ⟨u ∩ s,
           mem_nhds_within_iff_exists_mem_nhds_inter.mpr ⟨u, hu₁, rfl.subset⟩,
           ht₂.mono (λ i hi y hy, hi y hy.2 (hu₂ (by simp [hy.1])))⟩, },
end

lemma tendsto_locally_uniformly_iff_forall_tendsto :
  tendsto_locally_uniformly F f p ↔
  ∀ x, tendsto (λ (y : ι × α), (f y.2, F y.1 y.2)) (p ×ᶠ (𝓝 x)) (𝓤 β) :=
begin
  simp only [tendsto_locally_uniformly, filter.forall_in_swap, tendsto_def, mem_prod_iff,
    set.prod_subset_iff],
  refine forall₃_congr (λ x u hu, ⟨_, _⟩),
  { rintros ⟨n, hn, hp⟩,
    exact ⟨_, hp, n, hn, λ i hi a ha, hi a ha⟩, },
  { rintros ⟨I, hI, n, hn, hu⟩,
    exact ⟨n, hn, by filter_upwards [hI] using hu⟩, },
end

protected lemma tendsto_uniformly_on.tendsto_locally_uniformly_on
  (h : tendsto_uniformly_on F f p s) : tendsto_locally_uniformly_on F f p s :=
λ u hu x hx, ⟨s, self_mem_nhds_within, h u hu⟩

protected lemma tendsto_uniformly.tendsto_locally_uniformly
  (h : tendsto_uniformly F f p) : tendsto_locally_uniformly F f p :=
λ u hu x, ⟨univ, univ_mem, by simpa using h u hu⟩

lemma tendsto_locally_uniformly_on.mono (h : tendsto_locally_uniformly_on F f p s) (h' : s' ⊆ s) :
  tendsto_locally_uniformly_on F f p s' :=
begin
  assume u hu x hx,
  rcases h u hu x (h' hx) with ⟨t, ht, H⟩,
  exact ⟨t, nhds_within_mono x h' ht, H.mono (λ n, id)⟩
end

lemma tendsto_locally_uniformly_on_univ :
  tendsto_locally_uniformly_on F f p univ ↔ tendsto_locally_uniformly F f p :=
by simp [tendsto_locally_uniformly_on, tendsto_locally_uniformly, nhds_within_univ]

protected lemma tendsto_locally_uniformly.tendsto_locally_uniformly_on
  (h : tendsto_locally_uniformly F f p) : tendsto_locally_uniformly_on F f p s :=
(tendsto_locally_uniformly_on_univ.mpr h).mono (subset_univ _)

/-- On a compact space, locally uniform convergence is just uniform convergence. -/
lemma tendsto_locally_uniformly_iff_tendsto_uniformly_of_compact_space [compact_space α] :
  tendsto_locally_uniformly F f p ↔ tendsto_uniformly F f p :=
begin
  refine ⟨λ h V hV, _, tendsto_uniformly.tendsto_locally_uniformly⟩,
  choose U hU using h V hV,
  obtain ⟨t, ht⟩ := compact_univ.elim_nhds_subcover' (λ k hk, U k) (λ k hk, (hU k).1),
  replace hU := λ (x : t), (hU x).2,
  rw ← eventually_all at hU,
  refine hU.mono (λ i hi x, _),
  specialize ht (mem_univ x),
  simp only [exists_prop, mem_Union, set_coe.exists, exists_and_distrib_right,subtype.coe_mk] at ht,
  obtain ⟨y, ⟨hy₁, hy₂⟩, hy₃⟩ := ht,
  exact hi ⟨⟨y, hy₁⟩, hy₂⟩ x hy₃,
end

/-- For a compact set `s`, locally uniform convergence on `s` is just uniform convergence on `s`. -/
lemma tendsto_locally_uniformly_on_iff_tendsto_uniformly_on_of_compact (hs : is_compact s) :
  tendsto_locally_uniformly_on F f p s ↔ tendsto_uniformly_on F f p s :=
begin
  haveI : compact_space s := is_compact_iff_compact_space.mp hs,
  refine ⟨λ h, _, tendsto_uniformly_on.tendsto_locally_uniformly_on⟩,
  rwa [tendsto_locally_uniformly_on_iff_tendsto_locally_uniformly_comp_coe,
    tendsto_locally_uniformly_iff_tendsto_uniformly_of_compact_space,
    ← tendsto_uniformly_on_iff_tendsto_uniformly_comp_coe] at h,
end

lemma tendsto_locally_uniformly_on.comp [topological_space γ] {t : set γ}
  (h : tendsto_locally_uniformly_on F f p s)
  (g : γ → α) (hg : maps_to g t s) (cg : continuous_on g t) :
  tendsto_locally_uniformly_on (λ n, (F n) ∘ g) (f ∘ g) p t :=
begin
  assume u hu x hx,
  rcases h u hu (g x) (hg hx) with ⟨a, ha, H⟩,
  have : g⁻¹' a ∈ 𝓝[t] x :=
    ((cg x hx).preimage_mem_nhds_within' (nhds_within_mono (g x) hg.image_subset ha)),
  exact ⟨g ⁻¹' a, this, H.mono (λ n hn y hy, hn _ hy)⟩
end

lemma tendsto_locally_uniformly.comp [topological_space γ]
  (h : tendsto_locally_uniformly F f p) (g : γ → α) (cg : continuous g) :
  tendsto_locally_uniformly (λ n, (F n) ∘ g) (f ∘ g) p :=
begin
  rw ← tendsto_locally_uniformly_on_univ at h ⊢,
  rw continuous_iff_continuous_on_univ at cg,
  exact h.comp _ (maps_to_univ _ _) cg
end

/-!
### Uniform approximation

In this section, we give lemmas ensuring that a function is continuous if it can be approximated
uniformly by continuous functions. We give various versions, within a set or the whole space, at
a single point or at all points, with locally uniform approximation or uniform approximation. All
the statements are derived from a statement about locally uniform approximation within a set at
a point, called `continuous_within_at_of_locally_uniform_approx_of_continuous_within_at`. -/

/-- A function which can be locally uniformly approximated by functions which are continuous
within a set at a point is continuous within this set at this point. -/
lemma continuous_within_at_of_locally_uniform_approx_of_continuous_within_at
  (hx : x ∈ s) (L : ∀ u ∈ 𝓤 β, ∃ (t ∈ 𝓝[s] x) (F : α → β), continuous_within_at F s x ∧
    ∀ y ∈ t, (f y, F y) ∈ u) : continuous_within_at f s x :=
begin
  apply uniform.continuous_within_at_iff'_left.2 (λ u₀ hu₀, _),
  obtain ⟨u₁, h₁, u₁₀⟩ : ∃ (u : set (β × β)) (H : u ∈ 𝓤 β), comp_rel u u ⊆ u₀ :=
    comp_mem_uniformity_sets hu₀,
  obtain ⟨u₂, h₂, hsymm, u₂₁⟩ : ∃ (u : set (β × β)) (H : u ∈ 𝓤 β),
    (∀{a b}, (a, b) ∈ u → (b, a) ∈ u) ∧ comp_rel u u ⊆ u₁ := comp_symm_of_uniformity h₁,
  rcases L u₂ h₂ with ⟨t, tx, F, hFc, hF⟩,
  have A : ∀ᶠ y in 𝓝[s] x, (f y, F y) ∈ u₂ := eventually.mono tx hF,
  have B : ∀ᶠ y in 𝓝[s] x, (F y, F x) ∈ u₂ :=
    uniform.continuous_within_at_iff'_left.1 hFc h₂,
  have C : ∀ᶠ y in 𝓝[s] x, (f y, F x) ∈ u₁ :=
    (A.and B).mono (λ y hy, u₂₁ (prod_mk_mem_comp_rel hy.1 hy.2)),
  have : (F x, f x) ∈ u₁ :=
    u₂₁ (prod_mk_mem_comp_rel (refl_mem_uniformity h₂) (hsymm (A.self_of_nhds_within hx))),
  exact C.mono (λ y hy, u₁₀ (prod_mk_mem_comp_rel hy this))
end

/-- A function which can be locally uniformly approximated by functions which are continuous at
a point is continuous at this point. -/
lemma continuous_at_of_locally_uniform_approx_of_continuous_at
  (L : ∀ u ∈ 𝓤 β, ∃ (t ∈ 𝓝 x) F, continuous_at F x ∧ ∀ y ∈ t, (f y, F y) ∈ u) :
  continuous_at f x :=
begin
  rw ← continuous_within_at_univ,
  apply continuous_within_at_of_locally_uniform_approx_of_continuous_within_at (mem_univ _) _,
  simpa only [exists_prop, nhds_within_univ, continuous_within_at_univ] using L
end

/-- A function which can be locally uniformly approximated by functions which are continuous
on a set is continuous on this set. -/
lemma continuous_on_of_locally_uniform_approx_of_continuous_within_at
  (L : ∀ (x ∈ s) (u ∈ 𝓤 β), ∃ (t ∈ 𝓝[s] x) F,
    continuous_within_at F s x ∧ ∀ y ∈ t, (f y, F y) ∈ u) : continuous_on f s :=
λ x hx, continuous_within_at_of_locally_uniform_approx_of_continuous_within_at hx (L x hx)

/-- A function which can be uniformly approximated by functions which are continuous on a set
is continuous on this set. -/
lemma continuous_on_of_uniform_approx_of_continuous_on
  (L : ∀ u ∈ 𝓤 β, ∃ F, continuous_on F s ∧ ∀ y ∈ s, (f y, F y) ∈ u) : continuous_on f s :=
continuous_on_of_locally_uniform_approx_of_continuous_within_at $
  λ x hx u hu, ⟨s, self_mem_nhds_within, (L u hu).imp $
    λ F hF, ⟨hF.1.continuous_within_at hx, hF.2⟩⟩

/-- A function which can be locally uniformly approximated by continuous functions is continuous. -/
lemma continuous_of_locally_uniform_approx_of_continuous_at
  (L : ∀ (x : α), ∀ u ∈ 𝓤 β, ∃ t ∈ 𝓝 x, ∃ F, continuous_at F x ∧ ∀ y ∈ t, (f y, F y) ∈ u) :
  continuous f :=
continuous_iff_continuous_at.2 $ λ x, continuous_at_of_locally_uniform_approx_of_continuous_at (L x)

/-- A function which can be uniformly approximated by continuous functions is continuous. -/
lemma continuous_of_uniform_approx_of_continuous
  (L : ∀ u ∈ 𝓤 β, ∃ F, continuous F ∧ ∀ y, (f y, F y) ∈ u) : continuous f :=
continuous_iff_continuous_on_univ.mpr $ continuous_on_of_uniform_approx_of_continuous_on $
  by simpa [continuous_iff_continuous_on_univ] using L

/-!
### Uniform limits

From the previous statements on uniform approximation, we deduce continuity results for uniform
limits.
-/

/-- A locally uniform limit on a set of functions which are continuous on this set is itself
continuous on this set. -/
protected lemma tendsto_locally_uniformly_on.continuous_on
  (h : tendsto_locally_uniformly_on F f p s) (hc : ∀ᶠ n in p, continuous_on (F n) s) [ne_bot p] :
  continuous_on f s :=
begin
  apply continuous_on_of_locally_uniform_approx_of_continuous_within_at (λ x hx u hu, _),
  rcases h u hu x hx with ⟨t, ht, H⟩,
  rcases (hc.and H).exists with ⟨n, hFc, hF⟩,
  exact ⟨t, ht, ⟨F n, hFc.continuous_within_at hx, hF⟩⟩
end

/-- A uniform limit on a set of functions which are continuous on this set is itself continuous
on this set. -/
protected lemma tendsto_uniformly_on.continuous_on (h : tendsto_uniformly_on F f p s)
  (hc : ∀ᶠ n in p, continuous_on (F n) s) [ne_bot p] : continuous_on f s :=
h.tendsto_locally_uniformly_on.continuous_on hc

/-- A locally uniform limit of continuous functions is continuous. -/
protected lemma tendsto_locally_uniformly.continuous (h : tendsto_locally_uniformly F f p)
  (hc : ∀ᶠ n in p, continuous (F n)) [ne_bot p] : continuous f :=
continuous_iff_continuous_on_univ.mpr $ h.tendsto_locally_uniformly_on.continuous_on $
  hc.mono $ λ n hn, hn.continuous_on

/-- A uniform limit of continuous functions is continuous. -/
protected lemma tendsto_uniformly.continuous (h : tendsto_uniformly F f p)
  (hc : ∀ᶠ n in p, continuous (F n)) [ne_bot p] : continuous f :=
h.tendsto_locally_uniformly.continuous hc

/-!
### Composing limits under uniform convergence

In general, if `Fₙ` converges pointwise to a function `f`, and `gₙ` tends to `x`, it is not true
that `Fₙ gₙ` tends to `f x`. It is true however if the convergence of `Fₙ` to `f` is uniform. In
this paragraph, we prove variations around this statement.
-/

/-- If `Fₙ` converges locally uniformly on a neighborhood of `x` within a set `s` to a function `f`
which is continuous at `x` within `s `, and `gₙ` tends to `x` within `s`, then `Fₙ (gₙ)` tends
to `f x`. -/
lemma tendsto_comp_of_locally_uniform_limit_within
  (h : continuous_within_at f s x) (hg : tendsto g p (𝓝[s] x))
  (hunif : ∀ u ∈ 𝓤 β, ∃ t ∈ 𝓝[s] x, ∀ᶠ n in p, ∀ y ∈ t, (f y, F n y) ∈ u) :
  tendsto (λ n, F n (g n)) p (𝓝 (f x)) :=
begin
  apply uniform.tendsto_nhds_right.2 (λ u₀ hu₀, _),
  obtain ⟨u₁, h₁, u₁₀⟩ : ∃ (u : set (β × β)) (H : u ∈ 𝓤 β), comp_rel u u ⊆ u₀ :=
    comp_mem_uniformity_sets hu₀,
  rcases hunif u₁ h₁ with ⟨s, sx, hs⟩,
  have A : ∀ᶠ n in p, g n ∈ s := hg sx,
  have B : ∀ᶠ n in p, (f x, f (g n)) ∈ u₁ := hg (uniform.continuous_within_at_iff'_right.1 h h₁),
  refine ((hs.and A).and B).mono (λ y hy, _),
  rcases hy with ⟨⟨H1, H2⟩, H3⟩,
  exact u₁₀ (prod_mk_mem_comp_rel H3 (H1 _ H2))
end

/-- If `Fₙ` converges locally uniformly on a neighborhood of `x` to a function `f` which is
continuous at `x`, and `gₙ` tends to `x`, then `Fₙ (gₙ)` tends to `f x`. -/
lemma tendsto_comp_of_locally_uniform_limit (h : continuous_at f x) (hg : tendsto g p (𝓝 x))
  (hunif : ∀ u ∈ 𝓤 β, ∃ t ∈ 𝓝 x, ∀ᶠ n in p, ∀ y ∈ t, (f y, F n y) ∈ u) :
  tendsto (λ n, F n (g n)) p (𝓝 (f x)) :=
begin
  rw ← continuous_within_at_univ at h,
  rw ← nhds_within_univ at hunif hg,
  exact tendsto_comp_of_locally_uniform_limit_within h hg hunif
end

/-- If `Fₙ` tends locally uniformly to `f` on a set `s`, and `gₙ` tends to `x` within `s`, then
`Fₙ gₙ` tends to `f x` if `f` is continuous at `x` within `s` and `x ∈ s`. -/
lemma tendsto_locally_uniformly_on.tendsto_comp (h : tendsto_locally_uniformly_on F f p s)
  (hf : continuous_within_at f s x) (hx : x ∈ s) (hg : tendsto g p (𝓝[s] x)) :
  tendsto (λ n, F n (g n)) p (𝓝 (f x)) :=
tendsto_comp_of_locally_uniform_limit_within hf hg (λ u hu, h u hu x hx)

/-- If `Fₙ` tends uniformly to `f` on a set `s`, and `gₙ` tends to `x` within `s`, then `Fₙ gₙ`
tends to `f x` if `f` is continuous at `x` within `s`. -/
lemma tendsto_uniformly_on.tendsto_comp (h : tendsto_uniformly_on F f p s)
  (hf : continuous_within_at f s x) (hg : tendsto g p (𝓝[s] x)) :
  tendsto (λ n, F n (g n)) p (𝓝 (f x)) :=
tendsto_comp_of_locally_uniform_limit_within hf hg (λ u hu, ⟨s, self_mem_nhds_within, h u hu⟩)

/-- If `Fₙ` tends locally uniformly to `f`, and `gₙ` tends to `x`, then `Fₙ gₙ` tends to `f x`. -/
lemma tendsto_locally_uniformly.tendsto_comp (h : tendsto_locally_uniformly F f p)
  (hf : continuous_at f x) (hg : tendsto g p (𝓝 x)) : tendsto (λ n, F n (g n)) p (𝓝 (f x)) :=
tendsto_comp_of_locally_uniform_limit hf hg (λ u hu, h u hu x)

/-- If `Fₙ` tends uniformly to `f`, and `gₙ` tends to `x`, then `Fₙ gₙ` tends to `f x`. -/
lemma tendsto_uniformly.tendsto_comp (h : tendsto_uniformly F f p)
  (hf : continuous_at f x) (hg : tendsto g p (𝓝 x)) : tendsto (λ n, F n (g n)) p (𝓝 (f x)) :=
h.tendsto_locally_uniformly.tendsto_comp hf hg


/-
Copyright (c) 2019 Johannes Hölzl, Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Zhouhang Zhou
-/
import measure_theory.integral.lebesgue
import order.filter.germ
import topology.continuous_function.algebra

/-!

# Almost everywhere equal functions

Two measurable functions are treated as identical if they are almost everywhere equal. We form the
set of equivalence classes under the relation of being almost everywhere equal, which is sometimes
known as the `L⁰` space.

See `l1_space.lean` for `L¹` space.

## Notation

* `α →ₘ[μ] β` is the type of `L⁰` space, where `α` and `β` are measurable spaces and `μ`
  is a measure on `α`. `f : α →ₘ β` is a "function" in `L⁰`. In comments, `[f]` is also used
  to denote an `L⁰` function.

  `ₘ` can be typed as `\_m`. Sometimes it is shown as a box if font is missing.

## Main statements

* The linear structure of `L⁰` :
    Addition and scalar multiplication are defined on `L⁰` in the natural way, i.e.,
    `[f] + [g] := [f + g]`, `c • [f] := [c • f]`. So defined, `α →ₘ β` inherits the linear structure
    of `β`. For example, if `β` is a module, then `α →ₘ β` is a module over the same ring.

    See `mk_add_mk`,  `neg_mk`,     `mk_sub_mk`,  `smul_mk`,
        `add_to_fun`, `neg_to_fun`, `sub_to_fun`, `smul_to_fun`

* The order structure of `L⁰` :
    `≤` can be defined in a similar way: `[f] ≤ [g]` if `f a ≤ g a` for almost all `a` in domain.
    And `α →ₘ β` inherits the preorder and partial order of `β`.

    TODO: Define `sup` and `inf` on `L⁰` so that it forms a lattice. It seems that `β` must be a
    linear order, since otherwise `f ⊔ g` may not be a measurable function.

## Implementation notes

* `f.to_fun`     : To find a representative of `f : α →ₘ β`, use `f.to_fun`.
                 For each operation `op` in `L⁰`, there is a lemma called `op_to_fun`,
                 characterizing, say, `(f op g).to_fun`.
* `ae_eq_fun.mk` : To constructs an `L⁰` function `α →ₘ β` from a measurable function `f : α → β`,
                 use `ae_eq_fun.mk`
* `comp`         : Use `comp g f` to get `[g ∘ f]` from `g : β → γ` and `[f] : α →ₘ γ`
* `comp₂`        : Use `comp₂ g f₁ f₂ to get `[λa, g (f₁ a) (f₂ a)]`.
                 For example, `[f + g]` is `comp₂ (+)`

## Tags

function space, almost everywhere equal, `L⁰`, ae_eq_fun

-/

noncomputable theory
open_locale classical ennreal

open set filter topological_space ennreal emetric measure_theory function
variables {α β γ δ : Type*} [measurable_space α] {μ ν : measure α}

namespace measure_theory

section measurable_space
variables [measurable_space β]

variable (β)

/-- The equivalence relation of being almost everywhere equal -/
def measure.ae_eq_setoid (μ : measure α) : setoid { f : α → β // ae_measurable f μ } :=
⟨λf g, (f : α → β) =ᵐ[μ] g, λ f, ae_eq_refl f, λ f g, ae_eq_symm, λ f g h, ae_eq_trans⟩

variable (α)

/-- The space of equivalence classes of measurable functions, where two measurable functions are
    equivalent if they agree almost everywhere, i.e., they differ on a set of measure `0`.  -/
def ae_eq_fun (μ : measure α) : Type* := quotient (μ.ae_eq_setoid β)

variables {α β}

notation α ` →ₘ[`:25 μ `] ` β := ae_eq_fun α β μ

end measurable_space

namespace ae_eq_fun
variables [measurable_space β] [measurable_space γ] [measurable_space δ]

/-- Construct the equivalence class `[f]` of an almost everywhere measurable function `f`, based
    on the equivalence relation of being almost everywhere equal. -/
def mk (f : α → β) (hf : ae_measurable f μ) : α →ₘ[μ] β := quotient.mk' ⟨f, hf⟩

/-- A measurable representative of an `ae_eq_fun` [f] -/
instance : has_coe_to_fun (α →ₘ[μ] β) (λ _, α → β) :=
⟨λf, ae_measurable.mk _ (quotient.out' f : {f : α → β // ae_measurable f μ}).2⟩

protected lemma measurable (f : α →ₘ[μ] β) : measurable f :=
ae_measurable.measurable_mk _

protected lemma ae_measurable (f : α →ₘ[μ] β) : ae_measurable f μ :=
f.measurable.ae_measurable

@[simp] lemma quot_mk_eq_mk (f : α → β) (hf) :
  (quot.mk (@setoid.r _ $ μ.ae_eq_setoid β) ⟨f, hf⟩ : α →ₘ[μ] β) = mk f hf :=
rfl

@[simp] lemma mk_eq_mk {f g : α → β} {hf hg} :
  (mk f hf : α →ₘ[μ] β) = mk g hg ↔ f =ᵐ[μ] g :=
quotient.eq'

@[simp] lemma mk_coe_fn (f : α →ₘ[μ] β) : mk f f.ae_measurable = f :=
begin
  conv_rhs { rw ← quotient.out_eq' f },
  set g : {f : α → β // ae_measurable f μ} := quotient.out' f with hg,
  have : g = ⟨g.1, g.2⟩ := subtype.eq rfl,
  rw [this, ← mk, mk_eq_mk],
  exact (ae_measurable.ae_eq_mk _).symm,
end

@[ext] lemma ext {f g : α →ₘ[μ] β} (h : f =ᵐ[μ] g) : f = g :=
by rwa [← f.mk_coe_fn, ← g.mk_coe_fn, mk_eq_mk]

lemma ext_iff {f g : α →ₘ[μ] β} : f = g ↔ f =ᵐ[μ] g :=
⟨λ h, by rw h, λ h, ext h⟩

lemma coe_fn_mk (f : α → β) (hf) : (mk f hf : α →ₘ[μ] β) =ᵐ[μ] f :=
begin
  apply (ae_measurable.ae_eq_mk _).symm.trans,
  exact @quotient.mk_out' _ (μ.ae_eq_setoid β) (⟨f, hf⟩ : {f // ae_measurable f μ})
end

@[elab_as_eliminator]

@[elab_as_eliminator]
lemma induction_on₂ {α' β' : Type*} [measurable_space α'] [measurable_space β'] {μ' : measure α'}
  (f : α →ₘ[μ] β) (f' : α' →ₘ[μ'] β') {p : (α →ₘ[μ] β) → (α' →ₘ[μ'] β') → Prop}
  (H : ∀ f hf f' hf', p (mk f hf) (mk f' hf')) :
  p f f' :=
induction_on f $ λ f hf, induction_on f' $ H f hf

@[elab_as_eliminator]
lemma induction_on₃ {α' β' : Type*} [measurable_space α'] [measurable_space β'] {μ' : measure α'}
  {α'' β'' : Type*} [measurable_space α''] [measurable_space β''] {μ'' : measure α''}
  (f : α →ₘ[μ] β) (f' : α' →ₘ[μ'] β') (f'' : α'' →ₘ[μ''] β'')
  {p : (α →ₘ[μ] β) → (α' →ₘ[μ'] β') → (α'' →ₘ[μ''] β'') → Prop}
  (H : ∀ f hf f' hf' f'' hf'', p (mk f hf) (mk f' hf') (mk f'' hf'')) :
  p f f' f'' :=
induction_on f $ λ f hf, induction_on₂ f' f'' $ H f hf

/-- Given a measurable function `g : β → γ`, and an almost everywhere equal function `[f] : α →ₘ β`,
    return the equivalence class of `g ∘ f`, i.e., the almost everywhere equal function
    `[g ∘ f] : α →ₘ γ`. -/
def comp (g : β → γ) (hg : measurable g) (f : α →ₘ[μ] β) : α →ₘ[μ] γ :=
quotient.lift_on' f (λ f, mk (g ∘ (f : α → β)) (hg.comp_ae_measurable f.2)) $
  λ f f' H, mk_eq_mk.2 $ H.fun_comp g

@[simp] lemma comp_mk (g : β → γ) (hg : measurable g)
  (f : α → β) (hf) :
  comp g hg (mk f hf : α →ₘ[μ] β) = mk (g ∘ f) (hg.comp_ae_measurable hf) :=
rfl

lemma comp_eq_mk (g : β → γ) (hg : measurable g) (f : α →ₘ[μ] β) :
  comp g hg f = mk (g ∘ f) (hg.comp_ae_measurable f.ae_measurable) :=
by rw [← comp_mk g hg f f.ae_measurable, mk_coe_fn]

lemma coe_fn_comp (g : β → γ) (hg : measurable g) (f : α →ₘ[μ] β) :
  comp g hg f =ᵐ[μ] g ∘ f :=
by { rw [comp_eq_mk], apply coe_fn_mk }

/-- The class of `x ↦ (f x, g x)`. -/
def pair (f : α →ₘ[μ] β) (g : α →ₘ[μ] γ) : α →ₘ[μ] β × γ :=
quotient.lift_on₂' f g (λ f g, mk (λ x, (f.1 x, g.1 x)) (f.2.prod_mk g.2)) $
  λ f g f' g' Hf Hg, mk_eq_mk.2 $ Hf.prod_mk Hg

@[simp] lemma pair_mk_mk (f : α → β) (hf) (g : α → γ) (hg) :
  (mk f hf : α →ₘ[μ] β).pair (mk g hg) = mk (λ x, (f x, g x)) (hf.prod_mk hg) :=
rfl

lemma pair_eq_mk (f : α →ₘ[μ] β) (g : α →ₘ[μ] γ) :
  f.pair g = mk (λ x, (f x, g x)) (f.ae_measurable.prod_mk g.ae_measurable) :=
by simp only [← pair_mk_mk, mk_coe_fn]

lemma coe_fn_pair (f : α →ₘ[μ] β) (g : α →ₘ[μ] γ) :
  f.pair g =ᵐ[μ] (λ x, (f x, g x)) :=
by { rw pair_eq_mk, apply coe_fn_mk }

/-- Given a measurable function `g : β → γ → δ`, and almost everywhere equal functions
    `[f₁] : α →ₘ β` and `[f₂] : α →ₘ γ`, return the equivalence class of the function
    `λa, g (f₁ a) (f₂ a)`, i.e., the almost everywhere equal function
    `[λa, g (f₁ a) (f₂ a)] : α →ₘ γ` -/
def comp₂ {γ δ : Type*} [measurable_space γ] [measurable_space δ] (g : β → γ → δ)
  (hg : measurable (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) : α →ₘ[μ] δ :=
comp _ hg (f₁.pair f₂)

@[simp] lemma comp₂_mk_mk {γ δ : Type*} [measurable_space γ] [measurable_space δ]
  (g : β → γ → δ) (hg : measurable (uncurry g)) (f₁ : α → β) (f₂ : α → γ) (hf₁ hf₂) :
  comp₂ g hg (mk f₁ hf₁ : α →ₘ[μ] β) (mk f₂ hf₂) =
    mk (λa, g (f₁ a) (f₂ a)) (hg.comp_ae_measurable (hf₁.prod_mk hf₂)) :=
rfl

lemma comp₂_eq_pair {γ δ : Type*} [measurable_space γ] [measurable_space δ]
  (g : β → γ → δ) (hg : measurable (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) :
  comp₂ g hg f₁ f₂ = comp _ hg (f₁.pair f₂) :=
rfl

lemma comp₂_eq_mk {γ δ : Type*} [measurable_space γ] [measurable_space δ]
  (g : β → γ → δ) (hg : measurable (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) :
  comp₂ g hg f₁ f₂ = mk (λ a, g (f₁ a) (f₂ a))
    (hg.comp_ae_measurable (f₁.ae_measurable.prod_mk f₂.ae_measurable)) :=
by rw [comp₂_eq_pair, pair_eq_mk, comp_mk]; refl

lemma coe_fn_comp₂ {γ δ : Type*} [measurable_space γ] [measurable_space δ]
  (g : β → γ → δ) (hg : measurable (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) :
  comp₂ g hg f₁ f₂ =ᵐ[μ] λ a, g (f₁ a) (f₂ a) :=
by { rw comp₂_eq_mk, apply coe_fn_mk }

/-- Interpret `f : α →ₘ[μ] β` as a germ at `μ.ae` forgetting that `f` is almost everywhere
    measurable. -/
def to_germ (f : α →ₘ[μ] β) : germ μ.ae β :=
quotient.lift_on' f (λ f, ((f : α → β) : germ μ.ae β)) $ λ f g H, germ.coe_eq.2 H

@[simp] lemma mk_to_germ (f : α → β) (hf) : (mk f hf : α →ₘ[μ] β).to_germ = f := rfl

lemma to_germ_eq (f : α →ₘ[μ] β) : f.to_germ = (f : α → β) :=
by rw [← mk_to_germ, mk_coe_fn]

lemma to_germ_injective : injective (to_germ : (α →ₘ[μ] β) → germ μ.ae β) :=
λ f g H, ext $ germ.coe_eq.1 $ by rwa [← to_germ_eq, ← to_germ_eq]

lemma comp_to_germ (g : β → γ) (hg : measurable g) (f : α →ₘ[μ] β) :
  (comp g hg f).to_germ = f.to_germ.map g :=
induction_on f $ λ f hf, by simp

lemma comp₂_to_germ (g : β → γ → δ) (hg : measurable (uncurry g))
  (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) :
  (comp₂ g hg f₁ f₂).to_germ = f₁.to_germ.map₂ g f₂.to_germ :=
induction_on₂ f₁ f₂ $ λ f₁ hf₁ f₂ hf₂, by simp

/-- Given a predicate `p` and an equivalence class `[f]`, return true if `p` holds of `f a`
    for almost all `a` -/
def lift_pred (p : β → Prop) (f : α →ₘ[μ] β) : Prop := f.to_germ.lift_pred p

/-- Given a relation `r` and equivalence class `[f]` and `[g]`, return true if `r` holds of
    `(f a, g a)` for almost all `a` -/
def lift_rel (r : β → γ → Prop) (f : α →ₘ[μ] β) (g : α →ₘ[μ] γ) : Prop :=
f.to_germ.lift_rel r g.to_germ

lemma lift_rel_mk_mk {r : β → γ → Prop} {f : α → β} {g : α → γ} {hf hg} :
  lift_rel r (mk f hf : α →ₘ[μ] β) (mk g hg) ↔ ∀ᵐ a ∂μ, r (f a) (g a) :=
iff.rfl

lemma lift_rel_iff_coe_fn {r : β → γ → Prop} {f : α →ₘ[μ] β} {g : α →ₘ[μ] γ} :
  lift_rel r f g ↔ ∀ᵐ a ∂μ, r (f a) (g a) :=
by rw [← lift_rel_mk_mk, mk_coe_fn, mk_coe_fn]

section order

instance [preorder β] : preorder (α →ₘ[μ] β) := preorder.lift to_germ

@[simp] lemma mk_le_mk [preorder β] {f g : α → β} (hf hg) :
  (mk f hf : α →ₘ[μ] β) ≤ mk g hg ↔ f ≤ᵐ[μ] g :=
iff.rfl

@[simp, norm_cast] lemma coe_fn_le [preorder β] {f g : α →ₘ[μ] β} :
  (f : α → β) ≤ᵐ[μ] g ↔ f ≤ g :=
lift_rel_iff_coe_fn.symm

instance [partial_order β] : partial_order (α →ₘ[μ] β) :=
partial_order.lift to_germ to_germ_injective

/- TODO: Prove `L⁰` space is a lattice if β is linear order.
         What if β is only a lattice? -/

-- instance [linear_order β] : semilattice_sup (α →ₘ β) :=
-- { sup := comp₂ (⊔) (_),
--    .. ae_eq_fun.partial_order }

end order

variable (α)
/-- The equivalence class of a constant function: `[λa:α, b]`, based on the equivalence relation of
    being almost everywhere equal -/
def const (b : β) : α →ₘ[μ] β := mk (λa:α, b) ae_measurable_const

lemma coe_fn_const (b : β) : (const α b : α →ₘ[μ] β) =ᵐ[μ] function.const α b :=
coe_fn_mk _ _

variable {α}

instance [inhabited β] : inhabited (α →ₘ[μ] β) := ⟨const α (default β)⟩

@[to_additive] instance [has_one β] : has_one (α →ₘ[μ] β) := ⟨const α 1⟩
@[to_additive] lemma one_def [has_one β] :
  (1 : α →ₘ[μ] β) = mk (λa:α, 1) ae_measurable_const := rfl
@[to_additive] lemma coe_fn_one [has_one β] : ⇑(1 : α →ₘ[μ] β) =ᵐ[μ] 1 := coe_fn_const _ _
@[simp, to_additive] lemma one_to_germ [has_one β] : (1 : α →ₘ[μ] β).to_germ = 1 := rfl

section monoid
variables
  [topological_space γ] [second_countable_topology γ] [borel_space γ]
  [monoid γ] [has_continuous_mul γ]

@[to_additive]
instance : has_mul (α →ₘ[μ] γ) := ⟨comp₂ (*) measurable_mul⟩

@[simp, to_additive] lemma mk_mul_mk (f g : α → γ) (hf hg) :
  (mk f hf : α →ₘ[μ] γ) * (mk g hg) = mk (f * g) (hf.mul hg) :=
rfl

@[to_additive] lemma coe_fn_mul (f g : α →ₘ[μ] γ) : ⇑(f * g) =ᵐ[μ] f * g := coe_fn_comp₂ _ _ _ _

@[simp, to_additive] lemma mul_to_germ (f g : α →ₘ[μ] γ) :
  (f * g).to_germ = f.to_germ * g.to_germ :=
comp₂_to_germ _ _ _ _

@[to_additive]
instance : monoid (α →ₘ[μ] γ) :=
to_germ_injective.monoid to_germ one_to_germ mul_to_germ

end monoid

@[to_additive]
instance comm_monoid [topological_space γ] [second_countable_topology γ] [borel_space γ]
  [comm_monoid γ] [has_continuous_mul γ] : comm_monoid (α →ₘ[μ] γ) :=
to_germ_injective.comm_monoid to_germ one_to_germ mul_to_germ

section group

variables [topological_space γ] [borel_space γ] [group γ] [topological_group γ]

@[to_additive] instance : has_inv (α →ₘ[μ] γ) := ⟨comp has_inv.inv measurable_inv⟩

@[simp, to_additive] lemma inv_mk (f : α → γ) (hf) : (mk f hf : α →ₘ[μ] γ)⁻¹ = mk f⁻¹ hf.inv := rfl

@[to_additive] lemma coe_fn_inv (f : α →ₘ[μ] γ) : ⇑(f⁻¹) =ᵐ[μ] f⁻¹ := coe_fn_comp _ _ _

@[to_additive] lemma inv_to_germ (f : α →ₘ[μ] γ) : (f⁻¹).to_germ = f.to_germ⁻¹ := comp_to_germ _ _ _

variables [second_countable_topology γ]

@[to_additive] instance : has_div (α →ₘ[μ] γ) := ⟨comp₂ has_div.div measurable_div⟩

@[simp, to_additive] lemma mk_div (f g : α → γ) (hf hg) :
  mk (f / g) (ae_measurable.div hf hg) = (mk f hf : α →ₘ[μ] γ) / (mk g hg) :=
rfl

@[to_additive] lemma coe_fn_div (f g : α →ₘ[μ] γ) : ⇑(f / g) =ᵐ[μ] f / g := coe_fn_comp₂ _ _ _ _

@[to_additive] lemma div_to_germ (f g : α →ₘ[μ] γ) : (f / g).to_germ = f.to_germ / g.to_germ :=
comp₂_to_germ _ _ _ _

@[to_additive]
instance : group (α →ₘ[μ] γ) :=
to_germ_injective.group _ one_to_germ mul_to_germ inv_to_germ div_to_germ

end group

@[to_additive]
instance [topological_space γ] [borel_space γ] [comm_group γ] [topological_group γ]
  [second_countable_topology γ] : comm_group (α →ₘ[μ] γ) :=
{ .. ae_eq_fun.group, .. ae_eq_fun.comm_monoid }

section module

variables {𝕜 : Type*} [semiring 𝕜] [topological_space 𝕜] [measurable_space 𝕜]
  [opens_measurable_space 𝕜]
variables [topological_space γ] [borel_space γ] [add_comm_monoid γ] [module 𝕜 γ]
  [has_continuous_smul 𝕜 γ]

instance : has_scalar 𝕜 (α →ₘ[μ] γ) :=
⟨λ c f, comp ((•) c) (measurable_id.const_smul c) f⟩

@[simp] lemma smul_mk (c : 𝕜) (f : α → γ) (hf) :
  c • (mk f hf : α →ₘ[μ] γ) = mk (c • f) (hf.const_smul _) :=
rfl

lemma coe_fn_smul (c : 𝕜) (f : α →ₘ[μ] γ) : ⇑(c • f) =ᵐ[μ] c • f := coe_fn_comp _ _ _

lemma smul_to_germ (c : 𝕜) (f : α →ₘ[μ] γ) : (c • f).to_germ = c • f.to_germ :=
comp_to_germ _ _ _

variables [second_countable_topology γ] [has_continuous_add γ]

instance : module 𝕜 (α →ₘ[μ] γ) :=
to_germ_injective.module 𝕜 ⟨@to_germ α γ _ μ _, zero_to_germ, add_to_germ⟩ smul_to_germ

end module

open ennreal

/-- For `f : α → ℝ≥0∞`, define `∫ [f]` to be `∫ f` -/
def lintegral (f : α →ₘ[μ] ℝ≥0∞) : ℝ≥0∞ :=
quotient.lift_on' f (λf, ∫⁻ a, (f : α → ℝ≥0∞) a ∂μ) (assume f g, lintegral_congr_ae)

@[simp] lemma lintegral_mk (f : α → ℝ≥0∞) (hf) :
  (mk f hf : α →ₘ[μ] ℝ≥0∞).lintegral = ∫⁻ a, f a ∂μ := rfl

lemma lintegral_coe_fn (f : α →ₘ[μ] ℝ≥0∞) : ∫⁻ a, f a ∂μ = f.lintegral :=
by rw [← lintegral_mk, mk_coe_fn]

@[simp] lemma lintegral_zero : lintegral (0 : α →ₘ[μ] ℝ≥0∞) = 0 := lintegral_zero

@[simp] lemma lintegral_eq_zero_iff {f : α →ₘ[μ] ℝ≥0∞} : lintegral f = 0 ↔ f = 0 :=
induction_on f $ λ f hf, (lintegral_eq_zero_iff' hf).trans mk_eq_mk.symm

lemma lintegral_add (f g : α →ₘ[μ] ℝ≥0∞) : lintegral (f + g) = lintegral f + lintegral g :=
induction_on₂ f g $ λ f hf g hg, by simp [lintegral_add' hf hg]

lemma lintegral_mono {f g : α →ₘ[μ] ℝ≥0∞} : f ≤ g → lintegral f ≤ lintegral g :=
induction_on₂ f g $ λ f hf g hg hfg, lintegral_mono_ae hfg

section pos_part

variables [topological_space γ] [linear_order γ] [order_closed_topology γ]
  [second_countable_topology γ] [has_zero γ] [opens_measurable_space γ]

/-- Positive part of an `ae_eq_fun`. -/
def pos_part (f : α →ₘ[μ] γ) : α →ₘ[μ] γ :=
comp (λ x, max x 0) (measurable_id.max measurable_const) f

@[simp] lemma pos_part_mk (f : α → γ) (hf) :
  pos_part (mk f hf : α →ₘ[μ] γ) = mk (λ x, max (f x) 0) (hf.max ae_measurable_const) :=
rfl

lemma coe_fn_pos_part (f : α →ₘ[μ] γ) : ⇑(pos_part f) =ᵐ[μ] (λ a, max (f a) 0) :=
coe_fn_comp _ _ _

end pos_part

end ae_eq_fun

end measure_theory

namespace continuous_map

open measure_theory

variables [topological_space α] [borel_space α] (μ)
variables [topological_space β] [measurable_space β] [borel_space β]

/-- The equivalence class of `μ`-almost-everywhere measurable functions associated to a continuous
map. -/
def to_ae_eq_fun (f : C(α, β)) : α →ₘ[μ] β :=
ae_eq_fun.mk f f.continuous.measurable.ae_measurable

lemma coe_fn_to_ae_eq_fun (f : C(α, β)) : f.to_ae_eq_fun μ =ᵐ[μ] f :=
ae_eq_fun.coe_fn_mk f _

variables [group β] [topological_group β] [second_countable_topology β]

/-- The `mul_hom` from the group of continuous maps from `α` to `β` to the group of equivalence
classes of `μ`-almost-everywhere measurable functions. -/
@[to_additive "The `add_hom` from the group of continuous maps from `α` to `β` to the group of
equivalence classes of `μ`-almost-everywhere measurable functions."]
def to_ae_eq_fun_mul_hom : C(α, β) →* α →ₘ[μ] β :=
{ to_fun := continuous_map.to_ae_eq_fun μ,
  map_one' := rfl,
  map_mul' := λ f g, ae_eq_fun.mk_mul_mk f g f.continuous.measurable.ae_measurable
    g.continuous.measurable.ae_measurable }

variables {𝕜 : Type*} [semiring 𝕜] [topological_space 𝕜] [measurable_space 𝕜]
  [opens_measurable_space 𝕜]
variables [topological_space γ] [measurable_space γ] [borel_space γ] [add_comm_group γ]
  [module 𝕜 γ] [topological_add_group γ] [has_continuous_smul 𝕜 γ]
  [second_countable_topology γ]

/-- The linear map from the group of continuous maps from `α` to `β` to the group of equivalence
classes of `μ`-almost-everywhere measurable functions. -/
def to_ae_eq_fun_linear_map : C(α, γ) →ₗ[𝕜] α →ₘ[μ] γ :=
{ map_smul' := λ c f, ae_eq_fun.smul_mk c f f.continuous.measurable.ae_measurable,
  .. to_ae_eq_fun_add_hom μ }

end continuous_map


/-
Copyright (c) 2019 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.rat.order
import Mathlib.data.int.sqrt
import Mathlib.PostPort

namespace Mathlib

/-!
# Square root on rational numbers

This file defines the square root function on rational numbers, `rat.sqrt` and proves several theorems about it.

-/

namespace rat

/-- Square root function on rational numbers, defined by taking the (integer) square root of the
numerator and the square root (on natural numbers) of the denominator. -/
def sqrt (q : ℚ) : ℚ := mk (int.sqrt (num q)) ↑(nat.sqrt (denom q))

theorem sqrt_eq (q : ℚ) : sqrt (q * q) = abs q := sorry

theorem exists_mul_self (x : ℚ) : (∃ (q : ℚ), q * q = x) ↔ sqrt x * sqrt x = x := sorry

theorem sqrt_nonneg (q : ℚ) : 0 ≤ sqrt q := sorry

end Mathlib