/-
Copyright (c) 2020 Rémy Degenne. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Rémy Degenne.
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.measure_theory.ess_sup
import Mathlib.measure_theory.l1_space
import Mathlib.analysis.mean_inequalities
import Mathlib.PostPort

universes u_1 u_3 u_2 u_4

namespace Mathlib

/-!
# ℒp space and Lp space

This file describes properties of almost everywhere measurable functions with finite seminorm,
denoted by `snorm f p μ` and defined for `p:ennreal` as `0` if `p=0`, `(∫ ∥f a∥^p ∂μ) ^ (1/p)` for
`0 < p < ∞` and `ess_sup ∥f∥ μ` for `p=∞`.

The Prop-valued `mem_ℒp f p μ` states that a function `f : α → E` has finite seminorm.
The space `Lp α E p μ` is the subtype of elements of `α →ₘ[μ] E` (see ae_eq_fun) such that
`snorm f p μ` is finite. For `1 ≤ p`, `snorm` defines a norm and Lp is a metric space.

TODO: prove that Lp is complete.

## Main definitions

* `snorm' f p μ` : `(∫ ∥f a∥^p ∂μ) ^ (1/p)` for `f : α → F` and `p : ℝ`, where `α` is a  measurable
  space and `F` is a normed group.
* `snorm_ess_sup f μ` : seminorm in `ℒ∞`, equal to the essential supremum `ess_sup ∥f∥ μ`.
* `snorm f p μ` : for `p : ennreal`, seminorm in `ℒp`, equal to `0` for `p=0`, to `snorm' f p μ`
  for `0 < p < ∞` and to `snorm_ess_sup f μ` for `p = ∞`.

* `mem_ℒp f p μ` : property that the function `f` is almost everywhere measurable and has finite
  p-seminorm for measure `μ` (`snorm f p μ < ∞`)
* `Lp E p μ` : elements of `α →ₘ[μ] E` (see ae_eq_fun) such that `snorm f p μ` is finite. Defined
  as an `add_subgroup` of `α →ₘ[μ] E`.

-/

namespace measure_theory

/-- `(∫ ∥f a∥^p ∂μ) ^ (1/p)`, which is a seminorm on the space of measurable functions for which
this quantity is finite -/
def snorm' {α : Type u_1} {F : Type u_3} [measurable_space α] [normed_group F] (f : α → F) (p : ℝ) (μ : measure α) : ennreal :=
  (lintegral μ fun (a : α) => ↑(nnnorm (f a)) ^ p) ^ (1 / p)

/-- seminorm for `ℒ∞`, equal to the essential supremum of `∥f∥`. -/
def snorm_ess_sup {α : Type u_1} {F : Type u_3} [measurable_space α] [normed_group F] (f : α → F) (μ : measure α) : ennreal :=
  ess_sup (fun (x : α) => ↑(nnnorm (f x))) μ

/-- `ℒp` seminorm, equal to `0` for `p=0`, to `(∫ ∥f a∥^p ∂μ) ^ (1/p)` for `0 < p < ∞` and to
`ess_sup ∥f∥ μ` for `p = ∞`. -/
def snorm {α : Type u_1} {F : Type u_3} [measurable_space α] [normed_group F] (f : α → F) (q : ennreal) (μ : measure α) : ennreal :=
  ite (q = 0) 0 (ite (q = ⊤) (snorm_ess_sup f μ) (snorm' f (ennreal.to_real q) μ))

theorem snorm_eq_snorm' {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {q : ennreal} (hq_ne_zero : q ≠ 0) (hq_ne_top : q ≠ ⊤) {f : α → F} : snorm f q μ = snorm' f (ennreal.to_real q) μ := sorry

@[simp] theorem snorm_exponent_top {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {f : α → F} : snorm f ⊤ μ = snorm_ess_sup f μ := sorry

/-- The property that `f:α→E` is ae_measurable and `(∫ ∥f a∥^p ∂μ)^(1/p)` is finite -/
def mem_ℒp {α : Type u_1} {E : Type u_2} [measurable_space α] [measurable_space E] [normed_group E] (f : α → E) (p : ennreal) (μ : measure α) :=
  ae_measurable f ∧ snorm f p μ < ⊤

theorem lintegral_rpow_nnnorm_eq_rpow_snorm' {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} {f : α → F} (hp0_lt : 0 < p) : (lintegral μ fun (a : α) => ↑(nnnorm (f a)) ^ p) = snorm' f p μ ^ p := sorry

theorem mem_ℒp_one_iff_integrable {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {f : α → E} : mem_ℒp f 1 μ ↔ integrable f := sorry

theorem mem_ℒp.snorm_lt_top {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} {f : α → E} (hfp : mem_ℒp f q μ) : snorm f q μ < ⊤ :=
  and.right hfp

theorem mem_ℒp.snorm_ne_top {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} {f : α → E} (hfp : mem_ℒp f q μ) : snorm f q μ ≠ ⊤ :=
  ne_of_lt (and.right hfp)

theorem lintegral_rpow_nnnorm_lt_top_of_snorm'_lt_top {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} {f : α → F} (hp0_lt : 0 < p) (hfp : snorm' f p μ < ⊤) : (lintegral μ fun (a : α) => ↑(nnnorm (f a)) ^ p) < ⊤ := sorry

@[simp] theorem snorm'_exponent_zero {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {f : α → F} : snorm' f 0 μ = 1 := sorry

@[simp] theorem snorm_exponent_zero {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {f : α → F} : snorm f 0 μ = 0 := sorry

theorem mem_ℒp_zero_iff_ae_measurable {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {f : α → E} : mem_ℒp f 0 μ ↔ ae_measurable f := sorry

@[simp] theorem snorm'_zero {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} (hp0_lt : 0 < p) : snorm' 0 p μ = 0 := sorry

@[simp] theorem snorm'_zero' {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} (hp0_ne : p ≠ 0) (hμ : μ ≠ 0) : snorm' 0 p μ = 0 := sorry

@[simp] theorem snorm_ess_sup_zero {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] : snorm_ess_sup 0 μ = 0 := sorry

@[simp] theorem snorm_zero {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {q : ennreal} : snorm 0 q μ = 0 := sorry

theorem zero_mem_ℒp {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} : mem_ℒp 0 q μ :=
  { left := measurable.ae_measurable measurable_zero,
    right := eq.mpr (id (Eq._oldrec (Eq.refl (snorm 0 q μ < ⊤)) snorm_zero)) ennreal.coe_lt_top }

theorem snorm'_measure_zero_of_pos {α : Type u_1} {F : Type u_3} [measurable_space α] [normed_group F] {p : ℝ} {f : α → F} (hp_pos : 0 < p) : snorm' f p 0 = 0 := sorry

theorem snorm'_measure_zero_of_exponent_zero {α : Type u_1} {F : Type u_3} [measurable_space α] [normed_group F] {f : α → F} : snorm' f 0 0 = 1 := sorry

theorem snorm'_measure_zero_of_neg {α : Type u_1} {F : Type u_3} [measurable_space α] [normed_group F] {p : ℝ} {f : α → F} (hp_neg : p < 0) : snorm' f p 0 = ⊤ := sorry

@[simp] theorem snorm_ess_sup_measure_zero {α : Type u_1} {F : Type u_3} [measurable_space α] [normed_group F] {f : α → F} : snorm_ess_sup f 0 = 0 := sorry

@[simp] theorem snorm_measure_zero {α : Type u_1} {F : Type u_3} [measurable_space α] [normed_group F] {q : ennreal} {f : α → F} : snorm f q 0 = 0 := sorry

theorem snorm'_const {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} (c : F) (hp_pos : 0 < p) : snorm' (fun (x : α) => c) p μ = ↑(nnnorm c) * coe_fn μ set.univ ^ (1 / p) := sorry

theorem snorm'_const' {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} [finite_measure μ] (c : F) (hc_ne_zero : c ≠ 0) (hp_ne_zero : p ≠ 0) : snorm' (fun (x : α) => c) p μ = ↑(nnnorm c) * coe_fn μ set.univ ^ (1 / p) := sorry

theorem snorm_ess_sup_const {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] (c : F) (hμ : μ ≠ 0) : snorm_ess_sup (fun (x : α) => c) μ = ↑(nnnorm c) := sorry

theorem snorm'_const_of_probability_measure {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} (c : F) (hp_pos : 0 < p) [probability_measure μ] : snorm' (fun (x : α) => c) p μ = ↑(nnnorm c) := sorry

theorem snorm_const {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {q : ennreal} (c : F) (h0 : q ≠ 0) (hμ : μ ≠ 0) : snorm (fun (x : α) => c) q μ = ↑(nnnorm c) * coe_fn μ set.univ ^ (1 / ennreal.to_real q) := sorry

theorem snorm_const' {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {q : ennreal} (c : F) (h0 : q ≠ 0) (h_top : q ≠ ⊤) : snorm (fun (x : α) => c) q μ = ↑(nnnorm c) * coe_fn μ set.univ ^ (1 / ennreal.to_real q) := sorry

theorem mem_ℒp_const {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} (c : E) [finite_measure μ] : mem_ℒp (fun (a : α) => c) q μ := sorry

theorem snorm'_congr_ae {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} {f : α → F} {g : α → F} (hfg : filter.eventually_eq (measure.ae μ) f g) : snorm' f p μ = snorm' g p μ := sorry

theorem snorm_ess_sup_congr_ae {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {f : α → F} {g : α → F} (hfg : filter.eventually_eq (measure.ae μ) f g) : snorm_ess_sup f μ = snorm_ess_sup g μ := sorry

theorem snorm_congr_ae {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {q : ennreal} {f : α → F} {g : α → F} (hfg : filter.eventually_eq (measure.ae μ) f g) : snorm f q μ = snorm g q μ := sorry

theorem mem_ℒp.ae_eq {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} {f : α → E} {g : α → E} (hfg : filter.eventually_eq (measure.ae μ) f g) (hf_Lp : mem_ℒp f q μ) : mem_ℒp g q μ := sorry

theorem mem_ℒp_congr_ae {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} {f : α → E} {g : α → E} (hfg : filter.eventually_eq (measure.ae μ) f g) : mem_ℒp f q μ ↔ mem_ℒp g q μ :=
  { mp := fun (h : mem_ℒp f q μ) => mem_ℒp.ae_eq hfg h,
    mpr := fun (h : mem_ℒp g q μ) => mem_ℒp.ae_eq (filter.eventually_eq.symm hfg) h }

theorem snorm'_eq_zero_of_ae_zero {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} {f : α → F} (hp0_lt : 0 < p) (hf_zero : filter.eventually_eq (measure.ae μ) f 0) : snorm' f p μ = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (snorm' f p μ = 0)) (snorm'_congr_ae hf_zero)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (snorm' 0 p μ = 0)) (snorm'_zero hp0_lt))) (Eq.refl 0))

theorem snorm'_eq_zero_of_ae_zero' {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} (hp0_ne : p ≠ 0) (hμ : μ ≠ 0) {f : α → F} (hf_zero : filter.eventually_eq (measure.ae μ) f 0) : snorm' f p μ = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (snorm' f p μ = 0)) (snorm'_congr_ae hf_zero)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (snorm' 0 p μ = 0)) (snorm'_zero' hp0_ne hμ))) (Eq.refl 0))

theorem ae_eq_zero_of_snorm'_eq_zero {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {p : ℝ} [opens_measurable_space E] {f : α → E} (hp0 : 0 ≤ p) (hf : ae_measurable f) (h : snorm' f p μ = 0) : filter.eventually_eq (measure.ae μ) f 0 := sorry

theorem snorm'_eq_zero_iff {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {p : ℝ} [opens_measurable_space E] (hp0_lt : 0 < p) {f : α → E} (hf : ae_measurable f) : snorm' f p μ = 0 ↔ filter.eventually_eq (measure.ae μ) f 0 :=
  { mp := ae_eq_zero_of_snorm'_eq_zero (le_of_lt hp0_lt) hf, mpr := snorm'_eq_zero_of_ae_zero hp0_lt }

theorem coe_nnnorm_ae_le_snorm_ess_sup {α : Type u_1} {F : Type u_3} [measurable_space α] [normed_group F] (f : α → F) (μ : measure α) : filter.eventually (fun (x : α) => ↑(nnnorm (f x)) ≤ snorm_ess_sup f μ) (measure.ae μ) :=
  ennreal.ae_le_ess_sup fun (x : α) => ↑(nnnorm (f x))

theorem snorm_ess_sup_eq_zero_iff {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {f : α → F} : snorm_ess_sup f μ = 0 ↔ filter.eventually_eq (measure.ae μ) f 0 := sorry

theorem snorm_eq_zero_iff {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} [opens_measurable_space E] {f : α → E} (hf : ae_measurable f) (h0 : q ≠ 0) : snorm f q μ = 0 ↔ filter.eventually_eq (measure.ae μ) f 0 := sorry

@[simp] theorem snorm'_neg {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} {f : α → F} : snorm' (-f) p μ = snorm' f p μ := sorry

@[simp] theorem snorm_neg {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {q : ennreal} {f : α → F} : snorm (-f) q μ = snorm f q μ := sorry

theorem mem_ℒp.neg {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} [borel_space E] {f : α → E} (hf : mem_ℒp f q μ) : mem_ℒp (-f) q μ := sorry

theorem snorm'_le_snorm'_mul_rpow_measure_univ {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] {p : ℝ} {q : ℝ} (hp0_lt : 0 < p) (hpq : p ≤ q) {f : α → E} (hf : ae_measurable f) : snorm' f p μ ≤ snorm' f q μ * coe_fn μ set.univ ^ (1 / p - 1 / q) := sorry

theorem snorm'_le_snorm_ess_sup_mul_rpow_measure_univ {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} (hp_pos : 0 < p) {f : α → F} : snorm' f p μ ≤ snorm_ess_sup f μ * coe_fn μ set.univ ^ (1 / p) := sorry

theorem snorm'_le_snorm'_of_exponent_le {α : Type u_1} {E : Type u_2} [measurable_space α] [measurable_space E] [normed_group E] [borel_space E] {p : ℝ} {q : ℝ} (hp0_lt : 0 < p) (hpq : p ≤ q) (μ : measure α) [probability_measure μ] {f : α → E} (hf : ae_measurable f) : snorm' f p μ ≤ snorm' f q μ := sorry

theorem snorm'_le_snorm_ess_sup {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} (hp_pos : 0 < p) {f : α → F} [probability_measure μ] : snorm' f p μ ≤ snorm_ess_sup f μ := sorry

theorem snorm_le_snorm_of_exponent_le {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] {p : ennreal} {q : ennreal} (hpq : p ≤ q) [probability_measure μ] {f : α → E} (hf : ae_measurable f) : snorm f p μ ≤ snorm f q μ := sorry

theorem snorm'_lt_top_of_snorm'_lt_top_of_exponent_le {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] {p : ℝ} {q : ℝ} [finite_measure μ] {f : α → E} (hf : ae_measurable f) (hfq_lt_top : snorm' f q μ < ⊤) (hp_nonneg : 0 ≤ p) (hpq : p ≤ q) : snorm' f p μ < ⊤ := sorry

theorem mem_ℒp.mem_ℒp_of_exponent_le {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] {p : ennreal} {q : ennreal} [finite_measure μ] {f : α → E} (hfq : mem_ℒp f q μ) (hpq : p ≤ q) : mem_ℒp f p μ := sorry

theorem mem_ℒp.integrable {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} [borel_space E] (hq1 : 1 ≤ q) {f : α → E} [finite_measure μ] (hfq : mem_ℒp f q μ) : integrable f :=
  iff.mp mem_ℒp_one_iff_integrable (mem_ℒp.mem_ℒp_of_exponent_le hfq hq1)

theorem snorm'_add_le {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {p : ℝ} [borel_space E] {f : α → E} {g : α → E} (hf : ae_measurable f) (hg : ae_measurable g) (hp1 : 1 ≤ p) : snorm' (f + g) p μ ≤ snorm' f p μ + snorm' g p μ := sorry

theorem snorm_ess_sup_add_le {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {f : α → F} {g : α → F} : snorm_ess_sup (f + g) μ ≤ snorm_ess_sup f μ + snorm_ess_sup g μ := sorry

theorem snorm_add_le {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} [borel_space E] {f : α → E} {g : α → E} (hf : ae_measurable f) (hg : ae_measurable g) (hq1 : 1 ≤ q) : snorm (f + g) q μ ≤ snorm f q μ + snorm g q μ := sorry

theorem snorm_add_lt_top_of_one_le {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} [borel_space E] {f : α → E} {g : α → E} (hf : mem_ℒp f q μ) (hg : mem_ℒp g q μ) (hq1 : 1 ≤ q) : snorm (f + g) q μ < ⊤ :=
  lt_of_le_of_lt (snorm_add_le (and.left hf) (and.left hg) hq1)
    (iff.mpr ennreal.add_lt_top { left := and.right hf, right := and.right hg })

theorem snorm'_add_lt_top_of_le_one {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {p : ℝ} [borel_space E] {f : α → E} {g : α → E} (hf : ae_measurable f) (hg : ae_measurable g) (hf_snorm : snorm' f p μ < ⊤) (hg_snorm : snorm' g p μ < ⊤) (hp_pos : 0 < p) (hp1 : p ≤ 1) : snorm' (f + g) p μ < ⊤ := sorry

theorem snorm_add_lt_top {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} [borel_space E] {f : α → E} {g : α → E} (hf : mem_ℒp f q μ) (hg : mem_ℒp g q μ) : snorm (f + g) q μ < ⊤ := sorry

theorem mem_ℒp.add {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} [borel_space E] [topological_space.second_countable_topology E] {f : α → E} {g : α → E} (hf : mem_ℒp f q μ) (hg : mem_ℒp g q μ) : mem_ℒp (f + g) q μ :=
  { left := ae_measurable.add (and.left hf) (and.left hg), right := snorm_add_lt_top hf hg }

theorem mem_ℒp.sub {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} [borel_space E] [topological_space.second_countable_topology E] {f : α → E} {g : α → E} (hf : mem_ℒp f q μ) (hg : mem_ℒp g q μ) : mem_ℒp (f - g) q μ :=
  eq.mpr (id (Eq._oldrec (Eq.refl (mem_ℒp (f - g) q μ)) (sub_eq_add_neg f g))) (mem_ℒp.add hf (mem_ℒp.neg hg))

theorem snorm'_const_smul {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {p : ℝ} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 F] {f : α → F} (c : 𝕜) (hp0_lt : 0 < p) : snorm' (c • f) p μ = ↑(nnnorm c) * snorm' f p μ := sorry

theorem snorm_ess_sup_const_smul {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 F] {f : α → F} (c : 𝕜) : snorm_ess_sup (c • f) μ = ↑(nnnorm c) * snorm_ess_sup f μ := sorry

theorem snorm_const_smul {α : Type u_1} {F : Type u_3} [measurable_space α] {μ : measure α} [normed_group F] {q : ennreal} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 F] {f : α → F} (c : 𝕜) : snorm (c • f) q μ = ↑(nnnorm c) * snorm f q μ := sorry

theorem mem_ℒp.const_smul {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {q : ennreal} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 E] [borel_space E] {f : α → E} (hf : mem_ℒp f q μ) (c : 𝕜) : mem_ℒp (c • f) q μ :=
  { left := ae_measurable.const_smul (and.left hf) c,
    right := lt_of_le_of_lt (le_of_eq (snorm_const_smul c)) (ennreal.mul_lt_top ennreal.coe_lt_top (and.right hf)) }

theorem snorm'_smul_le_mul_snorm' {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {p : ℝ} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 E] [opens_measurable_space E] [measurable_space 𝕜] [opens_measurable_space 𝕜] {q : ℝ} {r : ℝ} {f : α → E} (hf : ae_measurable f) {φ : α → 𝕜} (hφ : ae_measurable φ) (hp0_lt : 0 < p) (hpq : p < q) (hpqr : 1 / p = 1 / q + 1 / r) : snorm' (φ • f) p μ ≤ snorm' φ q μ * snorm' f r μ := sorry

/-! ### Lp space

The space of equivalence classes of measurable functions for which `snorm f p μ < ⊤`.
-/

@[simp] theorem snorm_ae_eq_fun {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {p : ennreal} {f : α → E} (hf : ae_measurable f) : snorm (⇑(ae_eq_fun.mk f hf)) p μ = snorm f p μ :=
  snorm_congr_ae (ae_eq_fun.coe_fn_mk f hf)

theorem mem_ℒp.snorm_mk_lt_top {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] {p : ennreal} {f : α → E} (hfp : mem_ℒp f p μ) : snorm (⇑(ae_eq_fun.mk f (and.left hfp))) p μ < ⊤ := sorry

/-- Lp space -/
def Lp {α : Type u_1} (E : Type u_2) [measurable_space α] [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] (p : ennreal) (μ : measure α) : add_subgroup (ae_eq_fun α E μ) :=
  add_subgroup.mk (set_of fun (f : ae_eq_fun α E μ) => snorm (⇑f) p μ < ⊤) sorry sorry sorry

/-- make an element of Lp from a function verifying `mem_ℒp` -/
def mem_ℒp.to_Lp {α : Type u_1} {E : Type u_2} [measurable_space α] [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] (f : α → E) {p : ennreal} {μ : measure α} (h_mem_ℒp : mem_ℒp f p μ) : ↥(Lp E p μ) :=
  { val := ae_eq_fun.mk f sorry, property := mem_ℒp.snorm_mk_lt_top h_mem_ℒp }

theorem mem_ℒp.coe_fn_to_Lp {α : Type u_1} {E : Type u_2} [measurable_space α] [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {μ : measure α} {p : ennreal} {f : α → E} (hf : mem_ℒp f p μ) : filter.eventually_eq (measure.ae μ) (⇑(mem_ℒp.to_Lp f hf)) f :=
  ae_eq_fun.coe_fn_mk f (mem_ℒp.to_Lp._proof_1 f hf)

namespace Lp

theorem mem_Lp_iff_snorm_lt_top {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {f : ae_eq_fun α E μ} : f ∈ Lp E p μ ↔ snorm (⇑f) p μ < ⊤ :=
  iff.refl (f ∈ Lp E p μ)

theorem antimono {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] [finite_measure μ] {p : ennreal} {q : ennreal} (hpq : p ≤ q) : Lp E q μ ≤ Lp E p μ :=
  fun (f : ae_eq_fun α E μ) (hf : f ∈ Lp E q μ) =>
    and.right (mem_ℒp.mem_ℒp_of_exponent_le { left := ae_eq_fun.ae_measurable f, right := hf } hpq)

theorem coe_fn_mk {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {f : ae_eq_fun α E μ} (hf : snorm (⇑f) p μ < ⊤) : filter.eventually_eq (measure.ae μ) ⇑{ val := f, property := hf } ⇑f := sorry

theorem snorm_lt_top {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} (f : ↥(Lp E p μ)) : snorm (⇑f) p μ < ⊤ :=
  subtype.prop f

theorem snorm_ne_top {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} (f : ↥(Lp E p μ)) : snorm (⇑f) p μ ≠ ⊤ :=
  has_lt.lt.ne (snorm_lt_top f)

theorem measurable {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} (f : ↥(Lp E p μ)) : measurable ⇑f :=
  ae_eq_fun.measurable (subtype.val f)

theorem ae_measurable {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} (f : ↥(Lp E p μ)) : ae_measurable ⇑f :=
  ae_eq_fun.ae_measurable (subtype.val f)

theorem mem_ℒp {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} (f : ↥(Lp E p μ)) : mem_ℒp (⇑f) p μ :=
  { left := ae_measurable f, right := subtype.prop f }

theorem coe_fn_zero {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} : filter.eventually_eq (measure.ae μ) (⇑0) 0 :=
  ae_eq_fun.coe_fn_zero

theorem coe_fn_neg {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {f : ↥(Lp E p μ)} : filter.eventually_eq (measure.ae μ) (⇑(-f)) (-⇑f) :=
  ae_eq_fun.coe_fn_neg ↑f

theorem coe_fn_add {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {f : ↥(Lp E p μ)} {g : ↥(Lp E p μ)} : filter.eventually_eq (measure.ae μ) (⇑(f + g)) (⇑f + ⇑g) :=
  ae_eq_fun.coe_fn_add (subtype.val f) (subtype.val g)

theorem coe_fn_sub {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {f : ↥(Lp E p μ)} {g : ↥(Lp E p μ)} : filter.eventually_eq (measure.ae μ) (⇑(f - g)) (⇑f - ⇑g) :=
  ae_eq_fun.coe_fn_sub ↑f ↑g

theorem mem_Lp_const {E : Type u_2} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} (α : Type u_1) [measurable_space α] (μ : measure α) (c : E) [finite_measure μ] : ae_eq_fun.const α c ∈ Lp E p μ :=
  mem_ℒp.snorm_mk_lt_top (mem_ℒp_const c)

protected instance has_norm {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} : has_norm ↥(Lp E p μ) :=
  has_norm.mk fun (f : ↥(Lp E p μ)) => ennreal.to_real (snorm (⇑f) p μ)

theorem norm_def {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} (f : ↥(Lp E p μ)) : norm f = ennreal.to_real (snorm (⇑f) p μ) :=
  rfl

@[simp] theorem norm_zero {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} : norm 0 = 0 := sorry

theorem norm_eq_zero_iff {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {f : ↥(Lp E p μ)} (hp : 0 < p) : norm f = 0 ↔ f = 0 := sorry

@[simp] theorem norm_neg {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {f : ↥(Lp E p μ)} : norm (-f) = norm f := sorry

protected instance normed_group {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} [hp : fact (1 ≤ p)] : normed_group ↥(Lp E p μ) :=
  normed_group.of_core ↥(Lp E p μ) sorry

theorem mem_Lp_const_smul {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 E] (c : 𝕜) (f : ↥(Lp E p μ)) : c • ↑f ∈ Lp E p μ := sorry

protected instance has_scalar {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 E] : has_scalar 𝕜 ↥(Lp E p μ) :=
  has_scalar.mk fun (c : 𝕜) (f : ↥(Lp E p μ)) => { val := c • ↑f, property := mem_Lp_const_smul c f }

theorem coe_fn_smul {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 E] {f : ↥(Lp E p μ)} {c : 𝕜} : filter.eventually_eq (measure.ae μ) (⇑(c • f)) (c • ⇑f) :=
  ae_eq_fun.coe_fn_smul c ↑f

protected instance semimodule {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 E] : semimodule 𝕜 ↥(Lp E p μ) :=
  semimodule.mk sorry sorry

theorem norm_const_smul {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 E] (c : 𝕜) (f : ↥(Lp E p μ)) : norm (c • f) = norm c * norm f := sorry

protected instance normed_space {α : Type u_1} {E : Type u_2} [measurable_space α] {μ : measure α} [measurable_space E] [normed_group E] [borel_space E] [topological_space.second_countable_topology E] {p : ennreal} {𝕜 : Type u_4} [normed_field 𝕜] [normed_space 𝕜 E] [fact (1 ≤ p)] : normed_space 𝕜 ↥(Lp E p μ) :=
  normed_space.mk sorry


/-
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 data.rat.order
import data.int.char_zero
import algebra.field.opposite

/-!
# Casts for Rational Numbers

## Summary

We define the canonical injection from ℚ into an arbitrary division ring and prove various
casting lemmas showing the well-behavedness of this injection.

## Notations

- `/.` is infix notation for `rat.mk`.

## Tags

rat, rationals, field, ℚ, numerator, denominator, num, denom, cast, coercion, casting
-/

namespace rat
variable {α : Type*}
open_locale rat

section with_div_ring
variable [division_ring α]

/-- Construct the canonical injection from `ℚ` into an arbitrary
  division ring. If the field has positive characteristic `p`,
  we define `1 / p = 1 / 0 = 0` for consistency with our
  division by zero convention. -/
-- see Note [coercion into rings]
@[priority 900] instance cast_coe : has_coe_t ℚ α := ⟨λ r, r.1 / r.2⟩

theorem cast_def (r : ℚ) : (r : α) = r.num / r.denom := rfl

@[simp] theorem cast_of_int (n : ℤ) : (of_int n : α) = n :=
show (n / (1:ℕ) : α) = n, by rw [nat.cast_one, div_one]

@[simp, norm_cast] theorem cast_coe_int (n : ℤ) : ((n : ℚ) : α) = n :=
by rw [coe_int_eq_of_int, cast_of_int]

@[simp, norm_cast] theorem cast_coe_nat (n : ℕ) : ((n : ℚ) : α) = n := cast_coe_int n

@[simp, norm_cast] theorem cast_zero : ((0 : ℚ) : α) = 0 :=
(cast_of_int _).trans int.cast_zero

@[simp, norm_cast] theorem cast_one : ((1 : ℚ) : α) = 1 :=
(cast_of_int _).trans int.cast_one

theorem cast_commute (r : ℚ) (a : α) : commute ↑r a :=
(r.1.cast_commute a).div_left (r.2.cast_commute a)

theorem cast_comm (r : ℚ) (a : α) : (r : α) * a = a * r :=
(cast_commute r a).eq

theorem commute_cast (a : α) (r : ℚ) : commute a r :=
(r.cast_commute a).symm

@[norm_cast] theorem cast_mk_of_ne_zero (a b : ℤ)
  (b0 : (b:α) ≠ 0) : (a /. b : α) = a / b :=
begin
  have b0' : b ≠ 0, { refine mt _ b0, simp {contextual := tt} },
  cases e : a /. b with n d h c,
  have d0 : (d:α) ≠ 0,
  { intro d0,
    have dd := denom_dvd a b,
    cases (show (d:ℤ) ∣ b, by rwa e at dd) with k ke,
    have : (b:α) = (d:α) * (k:α), {rw [ke, int.cast_mul], refl},
    rw [d0, zero_mul] at this, contradiction },
  rw [num_denom'] at e,
  have := congr_arg (coe : ℤ → α) ((mk_eq b0' $ ne_of_gt $ int.coe_nat_pos.2 h).1 e),
  rw [int.cast_mul, int.cast_mul, int.cast_coe_nat] at this,
  symmetry, change (a / b : α) = n / d,
  rw [div_eq_mul_inv, eq_div_iff_mul_eq d0, mul_assoc, (d.commute_cast _).eq,
      ← mul_assoc, this, mul_assoc, mul_inv_cancel b0, mul_one]
end

@[norm_cast] theorem cast_add_of_ne_zero : ∀ {m n : ℚ},
  (m.denom : α) ≠ 0 → (n.denom : α) ≠ 0 → ((m + n : ℚ) : α) = m + n
| ⟨n₁, d₁, h₁, c₁⟩ ⟨n₂, d₂, h₂, c₂⟩ := λ (d₁0 : (d₁:α) ≠ 0) (d₂0 : (d₂:α) ≠ 0), begin
  have d₁0' : (d₁:ℤ) ≠ 0 := int.coe_nat_ne_zero.2 (λ e, by rw e at d₁0; exact d₁0 rfl),
  have d₂0' : (d₂:ℤ) ≠ 0 := int.coe_nat_ne_zero.2 (λ e, by rw e at d₂0; exact d₂0 rfl),
  rw [num_denom', num_denom', add_def d₁0' d₂0'],
  suffices : (n₁ * (d₂ * (d₂⁻¹ * d₁⁻¹)) +
    n₂ * (d₁ * d₂⁻¹) * d₁⁻¹ : α) = n₁ * d₁⁻¹ + n₂ * d₂⁻¹,
  { rw [cast_mk_of_ne_zero, cast_mk_of_ne_zero, cast_mk_of_ne_zero],
    { simpa [division_def, left_distrib, right_distrib, mul_inv_rev₀, d₁0, d₂0, mul_assoc] },
    all_goals {simp [d₁0, d₂0]} },
  rw [← mul_assoc (d₂:α), mul_inv_cancel d₂0, one_mul,
      (nat.cast_commute _ _).eq], simp [d₁0, mul_assoc]
end

@[simp, norm_cast] theorem cast_neg : ∀ n, ((-n : ℚ) : α) = -n
| ⟨n, d, h, c⟩ := show (↑-n / d : α) = -(n / d),
  by rw [div_eq_mul_inv, div_eq_mul_inv, int.cast_neg, neg_mul_eq_neg_mul]

@[norm_cast] theorem cast_sub_of_ne_zero {m n : ℚ}
  (m0 : (m.denom : α) ≠ 0) (n0 : (n.denom : α) ≠ 0) : ((m - n : ℚ) : α) = m - n :=
have ((-n).denom : α) ≠ 0, by cases n; exact n0,
by simp [sub_eq_add_neg, (cast_add_of_ne_zero m0 this)]

@[norm_cast] theorem cast_mul_of_ne_zero : ∀ {m n : ℚ},
  (m.denom : α) ≠ 0 → (n.denom : α) ≠ 0 → ((m * n : ℚ) : α) = m * n
| ⟨n₁, d₁, h₁, c₁⟩ ⟨n₂, d₂, h₂, c₂⟩ := λ (d₁0 : (d₁:α) ≠ 0) (d₂0 : (d₂:α) ≠ 0), begin
  have d₁0' : (d₁:ℤ) ≠ 0 := int.coe_nat_ne_zero.2 (λ e, by rw e at d₁0; exact d₁0 rfl),
  have d₂0' : (d₂:ℤ) ≠ 0 := int.coe_nat_ne_zero.2 (λ e, by rw e at d₂0; exact d₂0 rfl),
  rw [num_denom', num_denom', mul_def d₁0' d₂0'],
  suffices : (n₁ * ((n₂ * d₂⁻¹) * d₁⁻¹) : α) = n₁ * (d₁⁻¹ * (n₂ * d₂⁻¹)),
  { rw [cast_mk_of_ne_zero, cast_mk_of_ne_zero, cast_mk_of_ne_zero],
    { simpa [division_def, mul_inv_rev₀, d₁0, d₂0, mul_assoc] },
    all_goals {simp [d₁0, d₂0]} },
  rw [(d₁.commute_cast (_:α)).inv_right₀.eq]
end

@[simp] theorem cast_inv_nat (n : ℕ) : ((n⁻¹ : ℚ) : α) = n⁻¹ :=
begin
  cases n, { simp },
  simp_rw [coe_nat_eq_mk, inv_def, mk, mk_nat, dif_neg n.succ_ne_zero, mk_pnat],
  simp [cast_def]
end

@[simp] theorem cast_inv_int (n : ℤ) : ((n⁻¹ : ℚ) : α) = n⁻¹ :=
begin
  cases n,
  { exact cast_inv_nat _ },
  { simp only [int.cast_neg_succ_of_nat, ← nat.cast_succ, cast_neg, inv_neg, cast_inv_nat] }
end

@[norm_cast] theorem cast_inv_of_ne_zero : ∀ {n : ℚ},
  (n.num : α) ≠ 0 → (n.denom : α) ≠ 0 → ((n⁻¹ : ℚ) : α) = n⁻¹
| ⟨n, d, h, c⟩ := λ (n0 : (n:α) ≠ 0) (d0 : (d:α) ≠ 0), begin
  have n0' : (n:ℤ) ≠ 0 := λ e, by rw e at n0; exact n0 rfl,
  have d0' : (d:ℤ) ≠ 0 := int.coe_nat_ne_zero.2 (λ e, by rw e at d0; exact d0 rfl),
  rw [num_denom', inv_def],
  rw [cast_mk_of_ne_zero, cast_mk_of_ne_zero, inv_div];
  simp [n0, d0]
end

@[norm_cast] theorem cast_div_of_ne_zero {m n : ℚ} (md : (m.denom : α) ≠ 0)
  (nn : (n.num : α) ≠ 0) (nd : (n.denom : α) ≠ 0) : ((m / n : ℚ) : α) = m / n :=
have (n⁻¹.denom : ℤ) ∣ n.num,
by conv in n⁻¹.denom { rw [←(@num_denom n), inv_def] };
   apply denom_dvd,
have (n⁻¹.denom : α) = 0 → (n.num : α) = 0, from
λ h, let ⟨k, e⟩ := this in
  by have := congr_arg (coe : ℤ → α) e;
     rwa [int.cast_mul, int.cast_coe_nat, h, zero_mul] at this,
by rw [division_def, cast_mul_of_ne_zero md (mt this nn), cast_inv_of_ne_zero nn nd, division_def]

@[simp, norm_cast] theorem cast_inj [char_zero α] : ∀ {m n : ℚ}, (m : α) = n ↔ m = n
| ⟨n₁, d₁, h₁, c₁⟩ ⟨n₂, d₂, h₂, c₂⟩ := begin
  refine ⟨λ h, _, congr_arg _⟩,
  have d₁0 : d₁ ≠ 0 := ne_of_gt h₁,
  have d₂0 : d₂ ≠ 0 := ne_of_gt h₂,
  have d₁a : (d₁:α) ≠ 0 := nat.cast_ne_zero.2 d₁0,
  have d₂a : (d₂:α) ≠ 0 := nat.cast_ne_zero.2 d₂0,
  rw [num_denom', num_denom'] at h ⊢,
  rw [cast_mk_of_ne_zero, cast_mk_of_ne_zero] at h; simp [d₁0, d₂0] at h ⊢,
  rwa [eq_div_iff_mul_eq d₂a, division_def, mul_assoc, (d₁.cast_commute (d₂:α)).inv_left₀.eq,
    ← mul_assoc, ← division_def, eq_comm, eq_div_iff_mul_eq d₁a, eq_comm,
    ← int.cast_coe_nat, ← int.cast_mul, ← int.cast_coe_nat, ← int.cast_mul,
    int.cast_inj, ← mk_eq (int.coe_nat_ne_zero.2 d₁0) (int.coe_nat_ne_zero.2 d₂0)] at h
end

theorem cast_injective [char_zero α] : function.injective (coe : ℚ → α)
| m n := cast_inj.1

@[simp] theorem cast_eq_zero [char_zero α] {n : ℚ} : (n : α) = 0 ↔ n = 0 :=
by rw [← cast_zero, cast_inj]

theorem cast_ne_zero [char_zero α] {n : ℚ} : (n : α) ≠ 0 ↔ n ≠ 0 :=
not_congr cast_eq_zero

@[simp, norm_cast] theorem cast_add [char_zero α] (m n) :
  ((m + n : ℚ) : α) = m + n :=
cast_add_of_ne_zero (nat.cast_ne_zero.2 $ ne_of_gt m.pos) (nat.cast_ne_zero.2 $ ne_of_gt n.pos)

@[simp, norm_cast] theorem cast_sub [char_zero α] (m n) :
  ((m - n : ℚ) : α) = m - n :=
cast_sub_of_ne_zero (nat.cast_ne_zero.2 $ ne_of_gt m.pos) (nat.cast_ne_zero.2 $ ne_of_gt n.pos)

@[simp, norm_cast] theorem cast_mul [char_zero α] (m n) :
  ((m * n : ℚ) : α) = m * n :=
cast_mul_of_ne_zero (nat.cast_ne_zero.2 $ ne_of_gt m.pos) (nat.cast_ne_zero.2 $ ne_of_gt n.pos)

@[simp, norm_cast] theorem cast_bit0 [char_zero α] (n : ℚ) :
  ((bit0 n : ℚ) : α) = bit0 n :=
cast_add _ _

@[simp, norm_cast] theorem cast_bit1 [char_zero α] (n : ℚ) :
  ((bit1 n : ℚ) : α) = bit1 n :=
by rw [bit1, cast_add, cast_one, cast_bit0]; refl

variable (α)

/-- Coercion `ℚ → α` as a `ring_hom`. -/
def cast_hom [char_zero α] : ℚ →+* α := ⟨coe, cast_one, cast_mul, cast_zero, cast_add⟩

variable {α}

@[simp] lemma coe_cast_hom [char_zero α] : ⇑(cast_hom α) = coe := rfl

@[simp, norm_cast] theorem cast_inv [char_zero α] (n) : ((n⁻¹ : ℚ) : α) = n⁻¹ :=
(cast_hom α).map_inv _

@[simp, norm_cast] theorem cast_div [char_zero α] (m n) :
  ((m / n : ℚ) : α) = m / n :=
(cast_hom α).map_div _ _

@[norm_cast] theorem cast_mk [char_zero α] (a b : ℤ) : ((a /. b) : α) = a / b :=
by simp only [mk_eq_div, cast_div, cast_coe_int]

@[simp, norm_cast] theorem cast_pow [char_zero α] (q) (k : ℕ) :
  ((q ^ k : ℚ) : α) = q ^ k :=
(cast_hom α).map_pow q k

end with_div_ring

@[simp, norm_cast] theorem cast_nonneg [linear_ordered_field α] : ∀ {n : ℚ}, 0 ≤ (n : α) ↔ 0 ≤ n
| ⟨n, d, h, c⟩ :=
  by { rw [num_denom', cast_mk, mk_eq_div, div_nonneg_iff, div_nonneg_iff], norm_cast }

@[simp, norm_cast] theorem cast_le [linear_ordered_field α] {m n : ℚ} : (m : α) ≤ n ↔ m ≤ n :=
by rw [← sub_nonneg, ← cast_sub, cast_nonneg, sub_nonneg]

@[simp, norm_cast] theorem cast_lt [linear_ordered_field α] {m n : ℚ} : (m : α) < n ↔ m < n :=
by simpa [-cast_le] using not_congr (@cast_le α _ n m)

@[simp] theorem cast_nonpos [linear_ordered_field α] {n : ℚ} : (n : α) ≤ 0 ↔ n ≤ 0 :=
by rw [← cast_zero, cast_le]

@[simp] theorem cast_pos [linear_ordered_field α] {n : ℚ} : (0 : α) < n ↔ 0 < n :=
by rw [← cast_zero, cast_lt]

@[simp] theorem cast_lt_zero [linear_ordered_field α] {n : ℚ} : (n : α) < 0 ↔ n < 0 :=
by rw [← cast_zero, cast_lt]

@[simp, norm_cast] theorem cast_id : ∀ n : ℚ, ↑n = n
| ⟨n, d, h, c⟩ := by rw [num_denom', cast_mk, mk_eq_div]

@[simp] lemma cast_hom_rat : cast_hom ℚ = ring_hom.id ℚ :=
ring_hom.ext cast_id

@[simp, norm_cast] theorem cast_min [linear_ordered_field α] {a b : ℚ} :
  (↑(min a b) : α) = min a b :=
by by_cases a ≤ b; simp [h, min_def]

@[simp, norm_cast] theorem cast_max [linear_ordered_field α] {a b : ℚ} :
  (↑(max a b) : α) = max a b :=
by by_cases b ≤ a; simp [h, max_def]

@[simp, norm_cast] theorem cast_abs [linear_ordered_field α] {q : ℚ} :
  ((|q| : ℚ) : α) = |q| :=
by simp [abs_eq_max_neg]

end rat

open rat ring_hom

lemma ring_hom.eq_rat_cast {k} [division_ring k] (f : ℚ →+* k) (r : ℚ) : f r = r :=
calc f r = f (r.1 / r.2) : by rw [← int.cast_coe_nat, ← mk_eq_div, num_denom]
     ... = f r.1 / f r.2 : f.map_div _ _
     ... = r.1 / r.2     : by rw [map_nat_cast, map_int_cast]

-- This seems to be true for a `[char_p k]` too because `k'` must have the same characteristic
-- but the proof would be much longer
lemma ring_hom.map_rat_cast {k k'} [division_ring k] [char_zero k] [division_ring k']
  (f : k →+* k') (r : ℚ) :
  f r = r :=
(f.comp (cast_hom k)).eq_rat_cast r

lemma ring_hom.ext_rat {R : Type*} [semiring R] (f g : ℚ →+* R) : f = g :=
begin
  ext r,
  refine rat.num_denom_cases_on' r _,
  intros a b b0,
  let φ : ℤ →+* R := f.comp (int.cast_ring_hom ℚ),
  let ψ : ℤ →+* R := g.comp (int.cast_ring_hom ℚ),
  rw [rat.mk_eq_div, int.cast_coe_nat],
  have b0' : (b:ℚ) ≠ 0 := nat.cast_ne_zero.2 b0,
  have : ∀ n : ℤ, f n = g n := λ n, show φ n = ψ n, by rw [φ.ext_int ψ],
  calc f (a * b⁻¹)
      = f a * f b⁻¹ * (g (b:ℤ) * g b⁻¹) :
        by rw [int.cast_coe_nat, ← g.map_mul, mul_inv_cancel b0', g.map_one, mul_one, f.map_mul]
  ... = g a * f b⁻¹ * (f (b:ℤ) * g b⁻¹) : by rw [this a, ← this b]
  ... = g (a * b⁻¹) :
        by rw [int.cast_coe_nat, mul_assoc, ← mul_assoc (f b⁻¹),
              ← f.map_mul, inv_mul_cancel b0', f.map_one, one_mul, g.map_mul]
end

instance rat.subsingleton_ring_hom {R : Type*} [semiring R] : subsingleton (ℚ →+* R) :=
⟨ring_hom.ext_rat⟩

namespace monoid_with_zero_hom

variables {M : Type*} [group_with_zero M]

/-- If `f` and `g` agree on the integers then they are equal `φ`.

See note [partially-applied ext lemmas] for why `comp` is used here. -/
@[ext]
theorem ext_rat {f g : ℚ →*₀ M}
  (same_on_int : f.comp (int.cast_ring_hom ℚ).to_monoid_with_zero_hom =
    g.comp (int.cast_ring_hom ℚ).to_monoid_with_zero_hom) : f = g :=
begin
  have same_on_int' : ∀ k : ℤ, f k = g k := congr_fun same_on_int,
  ext x,
  rw [← @rat.num_denom x, rat.mk_eq_div, f.map_div, g.map_div,
    same_on_int' x.num, same_on_int' x.denom],
end

/-- Positive integer values of a morphism `φ` and its value on `-1` completely determine `φ`. -/
theorem ext_rat_on_pnat {f g : ℚ →*₀ M}
  (same_on_neg_one : f (-1) = g (-1)) (same_on_pnat : ∀ n : ℕ, 0 < n → f n = g n) : f = g :=
ext_rat $ ext_int' (by simpa) ‹_›

end monoid_with_zero_hom

namespace mul_opposite

variables {α : Type*} [division_ring α]

@[simp, norm_cast] lemma op_rat_cast (r : ℚ) : op (r : α) = (↑r : αᵐᵒᵖ) :=
by rw [cast_def, div_eq_mul_inv, op_mul, op_inv, op_nat_cast, op_int_cast,
    (commute.cast_int_right _ r.num).eq, cast_def, div_eq_mul_inv]

@[simp, norm_cast] lemma unop_rat_cast (r : ℚ) : unop (r : αᵐᵒᵖ) = r :=
by rw [cast_def, div_eq_mul_inv, unop_mul, unop_inv, unop_nat_cast, unop_int_cast,
    (commute.cast_int_right _ r.num).eq, cast_def, div_eq_mul_inv]

end mul_opposite


import ..lovelib

/-!

# LoVe Demo 1: Definitions and Statements

We introduce the basics of Lean and proof assistants, without trying to carry
out actual proofs yet. We focus on specifying objects and statements of their
intended properties. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## A View of Lean

Lean is a proof assistant developed principally by Leonardo de Moura
at Microsoft Research. Note: we are using Lean _3_ in this class. You might
find information online about Lean _4_, which is in a pre-release stage.

In a first approximation:

    Lean = functional programming + logic

In today's lecture, we cover inductive types, recursive functions, and lemma
statements.

If you are not familiar with typed functional programming (e.g., Haskell, ML,
OCaml, Scala), we recommend that you study a tutorial, such as the first
chapters of the online tutorial __Learn You a Haskell for Great Good!__:

    http://learnyouahaskell.com/chapters

Make sure to at least reach the section titled "Lambdas".

Some keywords we'll talk about over the next few days/weeks:

* Dependent types
* Martin-Lof type theory
* Calculus of constructions

These all refer to the type theory of Lean, or equivalently, it's logic.
(Why are these the same? Stay tuned!)
This is what Lean adds over many other strongly typed functional languages.

## Types and Terms

Similar to simply typed λ-calculus or typed functional programming languages
(ML, OCaml, Haskell).

Types `σ`, `τ`, `υ`:

* type variables `α`;
* basic types `T`;
* complex types `T σ1 … σN`.

-/

#check ℕ
#check ℤ

#check empty
#check unit
#check bool

/-!
Some type constructors `T` are written infix, e.g., `→` (function type).

The function arrow is right-associative:
`σ₁ → σ₂ → σ₃ → τ` = `σ₁ → (σ₂ → (σ₃ → τ))`.

-/

#check ℕ → ℤ
#check ℤ → ℕ
#check bool → ℕ → ℤ
#check (bool → ℕ) → ℤ
#check ℕ → (bool → ℕ) → ℤ

#check prod ℕ bool
#check prod

/-!

Polymorphic types are also possible. In Lean, the type variables must be bound
using `∀`, e.g., `∀α, α → α`.

You'll also see the capital Pi `Π` used in place of `∀`.

-/

#check ∀ (α : Type), α → α

/-!
Terms `t`, `u`:

* constants `c`;
* variables `x`;
* applications `t u`;
* λ-expressions `λx, t`.

__Currying__: functions can be

* fully applied (e.g., `f x y z` if `f` is ternary);
* partially applied (e.g., `f x y`, `f x`);
* left unapplied (e.g., `f`).

Application is left-associative: `f x y z` = `((f x) y) z`. -/

#check λx : ℕ, x
#check λf : ℕ → ℕ, λg : ℕ → ℕ, λh : ℕ → ℕ, λx : ℕ, h (g (f x))
#check λ(f g h : ℕ → ℕ) (x : ℕ), h (g (f x))

constants a b : ℤ
constant f : ℤ → ℤ
constant g : ℤ → ℤ → ℤ

#check λx : ℤ, g (f (g a x)) (g x b)
#check λx, g (f (g a x)) (g x b)

#check λx, x

constant trool : Type
constants trool.true trool.false trool.maybe : trool

/-! ### Type Checking and Type Inference

Type checking and type inference are decidable problems, but this property is
quickly lost if features such as overloading or subtyping are added.

Type judgment: `C ⊢ t : σ`, meaning `t` has type `σ` in local context `C`.

Typing rules:

    —————————— Cst   if c is declared with type σ
    C ⊢ c : σ

    —————————— Var   if x : σ occurs in C
    C ⊢ x : σ

    C ⊢ t : σ → τ    C ⊢ u : σ
    ——————————————————————————— App
    C ⊢ t u : τ

    C, x : σ ⊢ t : τ
    ———————————————————————— Lam
    C ⊢ (λx : σ, t) : σ → τ

### Type Inhabitation

Given a type `σ`, the __type inhabitation__ problem consists of finding a term
of that type.

Recursive procedure:

1. If `σ` is of the form `τ → υ`, a candidate inhabitant is an anonymous
   function of the form `λx, _`.

2. Alternatively, you can use any constant or variable `x : τ₁ → ⋯ → τN → σ` to
   build the term `x _ … _`. -/

constants α β γ : Type

def some_fun_of_type : (α → β → γ) → ((β → α) → β) → α → γ :=
λf g a, f a (g (λb, a))

/-! ## Type Definitions

An __inductive type__ (also called __inductive datatype__,
__algebraic datatype__, or just __datatype__) is a type that consists of all the
values that can be built using a finite number of applications of its
__constructors__, and only those.

### Natural Numbers -/

namespace my_nat

/-! Definition of type `nat` (= `ℕ`) of natural numbers, using Peano-style unary
notation: -/

inductive nat : Type
| zero : nat
| succ : nat → nat

#check nat
#check nat.zero
#check nat.succ

end my_nat

#print nat
#print ℕ

/-! ### Arithmetic Expressions -/

inductive aexp : Type
| num : ℤ → aexp
| var : string → aexp
| add : aexp → aexp → aexp
| sub : aexp → aexp → aexp
| mul : aexp → aexp → aexp
| div : aexp → aexp → aexp

/-! ### Lists -/

namespace my_list

inductive list (α : Type) : Type
| nil  : list
| cons : α → list → list

#check list.nil
#check list.cons

end my_list

#print list

/-! ## Function Definitions

The syntax for defining a function operating on an inductive type is very
compact: We define a single function and use __pattern matching__ to extract the
arguments to the constructors. -/

def add : ℕ → ℕ → ℕ
| m nat.zero     := m
| m (nat.succ n) := nat.succ (add m n)

#eval add 2 7
#reduce add 2 7

def mul : ℕ → ℕ → ℕ
| _ nat.zero     := nat.zero
| m (nat.succ n) := add m (mul m n)

#eval mul 2 7

#print mul
#print mul._main

def power : ℕ → ℕ → ℕ
| _ nat.zero     := 1
| m (nat.succ n) := m * power m n

#eval power 2 5

def power₂ (m : ℕ) : ℕ → ℕ
| nat.zero     := 1
| (nat.succ n) := m * power₂ n

#eval power₂ 2 5

def iter (α : Type) (z : α) (f : α → α) : ℕ → α
| nat.zero     := z
| (nat.succ n) := f (iter n)

#check iter

def power₃ (m n : ℕ) : ℕ :=
iter ℕ 1 (λl, m * l) n

#eval power₃ 2 5

def append (α : Type) : list α → list α → list α
| list.nil         ys := ys
| (list.cons x xs) ys := list.cons x (append xs ys)

#check append
#eval append _ [3, 1] [4, 1, 5]

/-! Aliases:

    `[]`          := `nil`
    `x :: xs`     := `cons x xs`
    `[x₁, …, xN]` := `x₁ :: … :: xN :: []` -/

def append₂ {α : Type} : list α → list α → list α
| list.nil         ys := ys
| (list.cons x xs) ys := list.cons x (append₂ xs ys)

#check append₂
#eval append₂ [3, 1] [4, 1, 5]

#check @append₂
#eval @append₂ _ [3, 1] [4, 1, 5]

def append₃ {α : Type} : list α → list α → list α
| []        ys := ys
| (x :: xs) ys := x :: append₃ xs ys

def reverse {α : Type} : list α → list α
| []        := []
| (x :: xs) := reverse xs ++ [x]

def eval (env : string → ℤ) : aexp → ℤ
| (aexp.num i)     := i
| (aexp.var x)     := env x
| (aexp.add e₁ e₂) := eval e₁ + eval e₂
| (aexp.sub e₁ e₂) := eval e₁ - eval e₂
| (aexp.mul e₁ e₂) := eval e₁ * eval e₂
| (aexp.div e₁ e₂) := eval e₁ / eval e₂

#eval eval (λs, 7) (aexp.div (aexp.var "x") (aexp.num 0))

/-! Lean only accepts the function definitions for which it can prove
termination. In particular, it accepts __structurally recursive__ functions,
which peel off exactly one constructor at a time.

## Lemma Statements

Notice the similarity with `def` commands. -/

namespace sorry_lemmas

lemma add_comm (m n : ℕ) :
  add m n = add n m :=
sorry

lemma add_assoc (l m n : ℕ) :
  add (add l m) n = add l (add m n) :=
sorry

lemma mul_comm (m n : ℕ) :
  mul m n = mul n m :=
sorry

lemma mul_assoc (l m n : ℕ) :
  mul (mul l m) n = mul l (mul m n) :=
sorry

lemma mul_add (l m n : ℕ) :
  mul l (add m n) = add (mul l m) (mul l n) :=
sorry

lemma reverse_reverse {α : Type} (xs : list α) :
  reverse (reverse xs) = xs :=
sorry

/-! Axioms are like lemmas but without proofs (`:= …`). Constant declarations
are like definitions but without bodies (`:= …`). -/

constants a b : ℤ

axiom a_less_b :
  a < b

end sorry_lemmas

end LoVe


/- Copyright 2019 (c) Hans-Dieter Hiep. All rights reserved. Released under MIT license as described in the file LICENSE. -/

import data.finmap data.bool data.vector data.list data.multiset
import data.finsupp

open nat option finset list

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

/- Indices of a list -/
@[derive decidable_eq]
inductive pointer: list α → Type u
| here (x : α) (xs : list α): pointer (x :: xs)
| tail {xs : list α} (y : α): pointer xs → pointer (y :: xs)

/- List membership with concrete witness (position) -/
@[derive decidable_eq]
inductive list_at: α → list α → Type u
| here (x : α) (xs : list α): list_at x (x :: xs)
| tail {x : α} {l : list α} (y : α): list_at x l → list_at x (y :: l)

lemma list_at_mem {x : α} {l : list α} : list_at x l → x ∈ l :=
begin
    intro H,
    induction l,
    cases H,
    cases H,
    constructor, refl,
    have: x ∈ l_tl,
        apply l_ih, assumption,
    right, assumption
end

/- A FIFO queue is a list of elements. Adding an element appends it to the back. Removing an element takes it from the front. -/
@[derive decidable_eq]
structure queue (α : Type u) := (l : list α)

@[reducible]
def queue.add (q : queue α) (x : α) : queue α := ⟨q.l ++ [x]⟩
@[reducible]
def queue.empty : queue α → bool
| ⟨[]⟩ := tt
| ⟨(x :: l)⟩ := ff
def queue.full : queue α → Prop := λq, q ≠ ⟨[]⟩
@[reducible]
def queue.remove : Π q : queue α, queue.full q → α × queue α
| ⟨[]⟩ H := begin exfalso, apply H, simp end
| ⟨(x :: l)⟩ _ := ⟨x, ⟨l⟩⟩
@[reducible]
def queue.first (q : queue α) (H : queue.full q) : α :=
  (queue.remove q H).fst
@[reducible]
def queue.unshift (q : queue α) (H : queue.full q) : queue α :=
  (queue.remove q H).snd
@[reducible]
def queue.poll : queue α → option (α × queue α)
| ⟨[]⟩ := none
| ⟨(x :: l)⟩ := some ⟨x, ⟨l⟩⟩
instance queue.has_zero : has_zero (queue α) := ⟨⟨[]⟩⟩

/- A function with finite support can be updated. This either adds a new value, or overwrites the value previoulsy mapped. -/
namespace finsupp
variables [decidable_eq α] [decidable_eq β] [has_zero β]

def update (f : α →₀ β) (a : α) (b : β) : α →₀ β :=
  ⟨if b = 0 then f.support.erase a else f.support ∪ {a},
   (λa', if a = a' then b else f a'), λa',
    begin
      by_cases H : (a = a'); by_cases G : (b = 0); simp [G,H],
      { split, {intro, cases a_1, assumption}, {intro,
          have : ¬a' = a, intro, apply H,
            apply eq.symm, assumption,
          exact ⟨this, a_1⟩ } },
      { split, {intro, cases a_1, exfalso,
          apply H, apply eq.symm, assumption, assumption},
        { intro, right, assumption } }
    end⟩

@[simp]
theorem update.to_fun (f : α →₀ β) (a : α) (b : β) :
  (update f a b).to_fun = (λa', if a = a' then b else f a') := rfl

@[simp]
theorem update.app_new_eq (f : α →₀ β) (a : α) (b : β) :
  (update f a b) a = b :=
begin
  simp [coe_fn], unfold has_coe_to_fun.coe, simp
end

theorem update.app_old_eq (f : α →₀ β) (a : α) (b : β)
    (c : α) (H : a ≠ c) :
  (update f a b) c = f c :=
begin
  simp [coe_fn], unfold has_coe_to_fun.coe, simp [H, coe_fn],
  unfold has_coe_to_fun.coe
end

end finsupp

/- Elimination and matching with equality (thanks to Rob Lewis) -/
def option.elim {β : Sort v} (t : option α)
    (f : t = none → β) (g : Π(a : α), t = some a → β) : β :=
  match t, rfl : (∀ b, t = b → β) with
  | none, h := f h
  | (some a), h := g a h
  end

/- Lift list of options -/
lemma head_lift_nil {a : α} :
  head (lift (@nil α)) ≠ some a :=
begin
  intro,
  simp [lift, has_lift.lift, default, inhabited.default] at a_1,
  assumption
end
lemma tail_lift_some {hd a : α} {tl : list α} :
  head (lift (list.cons hd tl)) = some a → hd = a :=
begin
  intro,
  simp [lift, has_lift.lift, coe] at a_1,
  simp [lift_t, has_lift_t.lift, coe_t, has_coe_t.coe] at a_1,
  assumption
end

/- Decomposition of finite set: a singleton and remainder set, such they are disjoint. A decomposition can be coerced to their union set. -/
inductive decomp_finset [decidable_eq α]
  (rem: finset α) (elem: α) : Type
| mk: elem ∉ rem → decomp_finset
instance decomp_finset.coe_finset [decidable_eq α]
  (rem: finset α) (elem: α) : has_coe (decomp_finset rem elem) (finset α) := ⟨λ_, rem ∪ {elem}⟩

notation Γ `;;` x := decomp_finset Γ x

/- Existence of a proof of a proposition implies that proposition. -/
lemma exists_proof_prop (P Q : Prop) : (∃x : P, Q) → P :=
begin
  intro, cases a, assumption
end


import tactic.lift
import data.set.basic
import data.int.basic

/-! Some tests of the `lift` tactic. -/

example (n m k x z u : ℤ) (hn : 0 < n) (hk : 0 ≤ k + n) (hu : 0 ≤ u)
  (h : k + n = 2 + x) (f : false) :
  k + n = m + x :=
begin
  lift n to ℕ using le_of_lt hn,
    guard_target (k + ↑n = m + x), guard_hyp hn : (0 : ℤ) < ↑n,
  lift m to ℕ,
    guard_target (k + ↑n = ↑m + x), tactic.swap, guard_target (0 ≤ m), tactic.swap,
    tactic.num_goals >>= λ n, guard (n = 2),
  lift (k + n) to ℕ using hk with l hl,
    guard_hyp l : ℕ, guard_hyp hl : ↑l = k + ↑n, guard_target (↑l = ↑m + x),
    tactic.success_if_fail (tactic.get_local `hk),
  lift x to ℕ with y hy,
    guard_hyp y : ℕ, guard_hyp hy : ↑y = x, guard_target (↑l = ↑m + x),
  lift z to ℕ with w,
    guard_hyp w : ℕ, tactic.success_if_fail (tactic.get_local `z),
  lift u to ℕ using hu with u rfl hu,
    guard_hyp hu : (0 : ℤ) ≤ ↑u,

  all_goals { exfalso, assumption },
end

-- test lift of functions
example (α : Type*) (f : α → ℤ) (hf : ∀ a, 0 ≤ f a) (hf' : ∀ a, f a < 1) (a : α) : 0 ≤ 2 * f a :=
begin
  lift f to α → ℕ using hf,
    guard_target ((0:ℤ) ≤ 2 * (λ i : α, (f i : ℤ)) a),
    guard_hyp hf' : ∀ a, ((λ i : α, (f i:ℤ)) a) < 1,
  exact int.coe_nat_nonneg _
end

-- fail gracefully when the lifted variable is a local definition
example : let n : ℤ := 3 in n = n :=
begin
  intro n,
  success_if_fail_with_msg { lift n to ℕ }
    ("Cannot substitute variable n, it is a local definition. " ++
    "If you really want to do this, use `clear_value` first."),
  refl
end

instance can_lift_unit : can_lift unit unit :=
⟨id, λ x, true, λ x _, ⟨x, rfl⟩⟩

/- test whether new instances of `can_lift` are added as simp lemmas -/
run_cmd do l ← can_lift_attr.get_cache, guard (`can_lift_unit ∈ l)

/- test error messages -/
example (n : ℤ) (hn : 0 < n) : true :=
begin
  success_if_fail_with_msg {lift n to ℕ using hn} "lift tactic failed.
invalid type ascription, term has type\n  0 < n\nbut is expected to have type\n  0 ≤ n",
  success_if_fail_with_msg {lift (n : option ℤ) to ℕ}
    "Failed to find a lift from option ℤ to ℕ. Provide an instance of\n  can_lift (option ℤ) ℕ",
  trivial
end

example (n : ℤ) : ℕ :=
begin
  success_if_fail_with_msg {lift n to ℕ}
    "lift tactic failed. Tactic is only applicable when the target is a proposition.",
  exact 0
end

instance can_lift_subtype (R : Type*) (P : R → Prop) : can_lift R {x // P x} :=
{ coe := coe,
  cond := λ x, P x,
  prf := λ x hx, ⟨⟨x, hx⟩, rfl⟩ }

instance can_lift_set (R : Type*) (s : set R) : can_lift R s :=
{ coe := coe,
  cond := λ x, x ∈ s,
  prf := λ x hx, ⟨⟨x, hx⟩, rfl⟩ }

example {R : Type*} {P : R → Prop} (x : R) (hx : P x) : true :=
by { lift x to {x // P x} using hx with y, trivial }

/-! Test that `lift` elaborates `s` as a type, not as a set. -/
example {R : Type*} {s : set R} (x : R) (hx : x ∈ s) : true :=
by { lift x to s using hx with y, trivial }

example (n : ℤ) (hn : 0 ≤ n) : true :=
by { lift n to ℕ, trivial, exact hn }

example (n : ℤ) (hn : 0 ≤ n) : true :=
by { lift n to ℕ using hn, trivial }

example (n : ℤ) (hn : n ≥ 0) : true :=
by { lift n to ℕ using ge.le _, trivial, guard_target (n ≥ 0), exact hn }

example (n : ℤ) (hn : 0 ≤ 1 * n) : true :=
begin
  lift n to ℕ using by { simpa [int.one_mul] using hn } with k,
  -- the above braces are optional, but it would be bad style to remove them (see next example)
  guard_hyp hn : 0 ≤ 1 * ((k : ℕ) : ℤ),
  trivial
end

example (n : ℤ) (hn : 0 ≤ n ↔ true) : true :=
begin
  lift n to ℕ using by { simp [hn] } with k, -- the braces are not optional here
  trivial
end


/-
Copyright (c) 2021 Alena Gusakov, Bhavik Mehta, Kyle Miller. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alena Gusakov, Bhavik Mehta, Kyle Miller
-/
import data.fintype.basic
import data.set.finite

/-!
# Hall's Marriage Theorem for finite index types

This module proves the basic form of Hall's theorem.
In constrast to the theorem described in `combinatorics.hall.basic`, this
version requires that the indexed family `t : ι → finset α` have `ι` be a `fintype`.
The `combinatorics.hall.basic` module applies a compactness argument to this version
to remove the `fintype` constraint on `ι`.

The modules are split like this since the generalized statement
depends on the topology and category theory libraries, but the finite
case in this module has few dependencies.

A description of this formalization is in [Gusakov2021].

## Main statements

* `finset.all_card_le_bUnion_card_iff_exists_injective'` is Hall's theorem with
  a finite index set.  This is elsewhere generalized to
  `finset.all_card_le_bUnion_card_iff_exists_injective`.

## Tags

Hall's Marriage Theorem, indexed families
-/

open finset

universes u v

namespace hall_marriage_theorem

variables {ι : Type u} {α : Type v} [fintype ι] {t : ι → finset α} [decidable_eq α]

lemma hall_cond_of_erase {x : ι} (a : α)
  (ha : ∀ (s : finset ι), s.nonempty → s ≠ univ → s.card < (s.bUnion t).card)
  (s' : finset {x' : ι | x' ≠ x}) :
  s'.card ≤ (s'.bUnion (λ x', (t x').erase a)).card :=
begin
  haveI := classical.dec_eq ι,
  specialize ha (s'.image coe),
  rw [nonempty.image_iff, finset.card_image_of_injective s' subtype.coe_injective] at ha,
  by_cases he : s'.nonempty,
  { have ha' : s'.card < (s'.bUnion (λ x, t x)).card,
    { convert ha he (λ h, by simpa [←h] using mem_univ x) using 2,
      ext x,
      simp only [mem_image, mem_bUnion, exists_prop, set_coe.exists,
                 exists_and_distrib_right, exists_eq_right, subtype.coe_mk], },
    rw ←erase_bUnion,
    by_cases hb : a ∈ s'.bUnion (λ x, t x),
    { rw card_erase_of_mem hb,
      exact nat.le_pred_of_lt ha' },
    { rw erase_eq_of_not_mem hb,
      exact nat.le_of_lt ha' }, },
  { rw [nonempty_iff_ne_empty, not_not] at he,
    subst s',
    simp },
end

/--
First case of the inductive step: assuming that
`∀ (s : finset ι), s.nonempty → s ≠ univ → s.card < (s.bUnion t).card`
and that the statement of **Hall's Marriage Theorem** is true for all
`ι'` of cardinality ≤ `n`, then it is true for `ι` of cardinality `n + 1`.
-/
lemma hall_hard_inductive_step_A {n : ℕ} (hn : fintype.card ι = n + 1)
  (ht : ∀ (s : finset ι), s.card ≤ (s.bUnion t).card)
  (ih : ∀ {ι' : Type u} [fintype ι'] (t' : ι' → finset α),
        by exactI fintype.card ι' ≤ n →
                  (∀ (s' : finset ι'), s'.card ≤ (s'.bUnion t').card) →
                  ∃ (f : ι' → α), function.injective f ∧ ∀ x, f x ∈ t' x)
  (ha : ∀ (s : finset ι), s.nonempty → s ≠ univ → s.card < (s.bUnion t).card) :
  ∃ (f : ι → α), function.injective f ∧ ∀ x, f x ∈ t x :=
begin
  haveI : nonempty ι := fintype.card_pos_iff.mp (hn.symm ▸ nat.succ_pos _),
  haveI := classical.dec_eq ι,
  /- Choose an arbitrary element `x : ι` and `y : t x`. -/
  let x := classical.arbitrary ι,
  have tx_ne : (t x).nonempty,
  { rw ←finset.card_pos,
    calc 0 < 1 : nat.one_pos
       ... ≤ (finset.bUnion {x} t).card : ht {x}
       ... = (t x).card : by rw finset.singleton_bUnion, },
  choose y hy using tx_ne,
  /- Restrict to everything except `x` and `y`. -/
  let ι' := {x' : ι | x' ≠ x},
  let t' : ι' → finset α := λ x', (t x').erase y,
  have card_ι' : fintype.card ι' = n :=
    calc fintype.card ι' = fintype.card ι - 1 : set.card_ne_eq _
                     ... = n : by { rw [hn, nat.add_succ_sub_one, add_zero], },
  rcases ih t' card_ι'.le (hall_cond_of_erase y ha) with ⟨f', hfinj, hfr⟩,
  /- Extend the resulting function. -/
  refine ⟨λ z, if h : z = x then y else f' ⟨z, h⟩, _, _⟩,
  { rintro z₁ z₂,
    have key : ∀ {x}, y ≠ f' x,
    { intros x h,
      simpa [←h] using hfr x, },
    by_cases h₁ : z₁ = x; by_cases h₂ : z₂ = x; simp [h₁, h₂, hfinj.eq_iff, key, key.symm], },
  { intro z,
    split_ifs with hz,
    { rwa hz },
    { specialize hfr ⟨z, hz⟩,
      rw mem_erase at hfr,
      exact hfr.2, }, },
end

lemma hall_cond_of_restrict {ι : Type u} {t : ι → finset α} {s : finset ι}
  (ht : ∀ (s : finset ι), s.card ≤ (s.bUnion t).card)
  (s' : finset (s : set ι)) :
  s'.card ≤ (s'.bUnion (λ a', t a')).card :=
begin
  classical,
  rw ← card_image_of_injective s' subtype.coe_injective,
  convert ht (s'.image coe) using 1,
  apply congr_arg,
  ext y,
  simp,
end

lemma hall_cond_of_compl {ι : Type u} {t : ι → finset α} {s : finset ι}
  (hus : s.card = (s.bUnion t).card)
  (ht : ∀ (s : finset ι), s.card ≤ (s.bUnion t).card)
  (s' : finset (sᶜ : set ι)) :
  s'.card ≤ (s'.bUnion (λ x', t x' \ s.bUnion t)).card :=
begin
  haveI := classical.dec_eq ι,
  have disj : disjoint s (s'.image coe),
  { simp only [disjoint_left, not_exists, mem_image, exists_prop, set_coe.exists,
               exists_and_distrib_right, exists_eq_right, subtype.coe_mk],
    intros x hx hc h,
    exact absurd hx hc, },
  have : s'.card = (s ∪ s'.image coe).card - s.card,
  { simp [disj, card_image_of_injective _ subtype.coe_injective], },
  rw [this, hus],
  refine (tsub_le_tsub_right (ht _) _).trans _,
  rw ← card_sdiff,
  { refine (card_le_of_subset _).trans le_rfl,
    intros t,
    simp only [mem_bUnion, mem_sdiff, not_exists, mem_image, and_imp, mem_union,
               exists_and_distrib_right, exists_imp_distrib],
    rintro x (hx | ⟨x', hx', rfl⟩) rat hs,
    { exact (hs x hx rat).elim },
    { exact ⟨⟨x', hx', rat⟩, hs⟩, } },
  { apply bUnion_subset_bUnion_of_subset_left,
    apply subset_union_left }
end

/--
Second case of the inductive step: assuming that
`∃ (s : finset ι), s ≠ univ → s.card = (s.bUnion t).card`
and that the statement of **Hall's Marriage Theorem** is true for all
`ι'` of cardinality ≤ `n`, then it is true for `ι` of cardinality `n + 1`.
-/
lemma hall_hard_inductive_step_B {n : ℕ} (hn : fintype.card ι = n + 1)
  (ht : ∀ (s : finset ι), s.card ≤ (s.bUnion t).card)
  (ih : ∀ {ι' : Type u} [fintype ι'] (t' : ι' → finset α),
        by exactI fintype.card ι' ≤ n →
                  (∀ (s' : finset ι'), s'.card ≤ (s'.bUnion t').card) →
                  ∃ (f : ι' → α), function.injective f ∧ ∀ x, f x ∈ t' x)
  (s : finset ι)
  (hs : s.nonempty)
  (hns : s ≠ univ)
  (hus : s.card = (s.bUnion t).card) :
  ∃ (f : ι → α), function.injective f ∧ ∀ x, f x ∈ t x :=
begin
  haveI := classical.dec_eq ι,
  /- Restrict to `s` -/
  let t' : s → finset α := λ x', t x',
  rw nat.add_one at hn,
  have card_ι'_le : fintype.card s ≤ n,
  { apply nat.le_of_lt_succ,
    calc fintype.card s = s.card : fintype.card_coe _
                    ... < fintype.card ι : (card_lt_iff_ne_univ _).mpr hns
                    ... = n.succ : hn },
  rcases ih t' card_ι'_le (hall_cond_of_restrict ht) with ⟨f', hf', hsf'⟩,
  /- Restrict to `sᶜ` in the domain and `(s.bUnion t)ᶜ` in the codomain. -/
  set ι'' := (s : set ι)ᶜ with ι''_def,
  let t'' : ι'' → finset α := λ a'', t a'' \ s.bUnion t,
  have card_ι''_le : fintype.card ι'' ≤ n,
  { simp_rw [← nat.lt_succ_iff, ← hn, ι'', ← finset.coe_compl, coe_sort_coe],
    rwa [fintype.card_coe, card_compl_lt_iff_nonempty] },
  rcases ih t'' card_ι''_le (hall_cond_of_compl hus ht) with ⟨f'', hf'', hsf''⟩,
  /- Put them together -/
  have f'_mem_bUnion : ∀ {x'} (hx' : x' ∈ s), f' ⟨x', hx'⟩ ∈ s.bUnion t,
  { intros x' hx',
    rw mem_bUnion,
    exact ⟨x', hx', hsf' _⟩, },
  have f''_not_mem_bUnion : ∀ {x''} (hx'' : ¬ x'' ∈ s), ¬ f'' ⟨x'', hx''⟩ ∈ s.bUnion t,
  { intros x'' hx'',
    have h := hsf'' ⟨x'', hx''⟩,
    rw mem_sdiff at h,
    exact h.2, },
  have im_disj : ∀ (x' x'' : ι) (hx' : x' ∈ s) (hx'' : ¬x'' ∈ s), f' ⟨x', hx'⟩ ≠ f'' ⟨x'', hx''⟩,
  { intros _ _ hx' hx'' h,
    apply f''_not_mem_bUnion hx'',
    rw ←h,
    apply f'_mem_bUnion, },
  refine ⟨λ x, if h : x ∈ s then f' ⟨x, h⟩ else f'' ⟨x, h⟩, _, _⟩,
  { exact hf'.dite _ hf'' im_disj },
  { intro x,
    split_ifs with h,
    { exact hsf' ⟨x, h⟩ },
    { exact sdiff_subset _ _ (hsf'' ⟨x, h⟩) } }
end

/--
Here we combine the two inductive steps into a full strong induction proof,
completing the proof the harder direction of **Hall's Marriage Theorem**.
-/
theorem hall_hard_inductive
  (ht : ∀ (s : finset ι), s.card ≤ (s.bUnion t).card) :
  ∃ (f : ι → α), function.injective f ∧ ∀ x, f x ∈ t x :=
begin
  unfreezingI
  { induction hn : fintype.card ι using nat.strong_induction_on with n ih generalizing ι },
  rcases n with _|_,
  { rw fintype.card_eq_zero_iff at hn,
    exactI ⟨is_empty_elim, is_empty_elim, is_empty_elim⟩, },
  { have ih' : ∀ (ι' : Type u) [fintype ι'] (t' : ι' → finset α),
                 by exactI fintype.card ι' ≤ n →
                    (∀ (s' : finset ι'), s'.card ≤ (s'.bUnion t').card) →
                    ∃ (f : ι' → α), function.injective f ∧ ∀ x, f x ∈ t' x,
    { introsI ι' _ _ hι' ht',
      exact ih _ (nat.lt_succ_of_le hι') ht' rfl, },
    by_cases h : ∀ (s : finset ι), s.nonempty → s ≠ univ → s.card < (s.bUnion t).card,
    { exact hall_hard_inductive_step_A hn ht ih' h, },
    { push_neg at h,
      rcases h with ⟨s, sne, snu, sle⟩,
      exact hall_hard_inductive_step_B hn ht ih' s sne snu (nat.le_antisymm (ht _) sle), } },
end

end hall_marriage_theorem

/--
This is the version of **Hall's Marriage Theorem** in terms of indexed
families of finite sets `t : ι → finset α` with `ι` a `fintype`.
It states that there is a set of distinct representatives if and only
if every union of `k` of the sets has at least `k` elements.

See `finset.all_card_le_bUnion_card_iff_exists_injective` for a version
where the `fintype ι` constraint is removed.
-/
theorem finset.all_card_le_bUnion_card_iff_exists_injective'
  {ι α : Type*} [fintype ι] [decidable_eq α] (t : ι → finset α) :
  (∀ (s : finset ι), s.card ≤ (s.bUnion t).card) ↔
    (∃ (f : ι → α), function.injective f ∧ ∀ x, f x ∈ t x) :=
begin
  split,
  { exact hall_marriage_theorem.hall_hard_inductive },
  { rintro ⟨f, hf₁, hf₂⟩ s,
    rw ←card_image_of_injective s hf₁,
    apply card_le_of_subset,
    intro _,
    rw [mem_image, mem_bUnion],
    rintros ⟨x, hx, rfl⟩,
    exact ⟨x, hx, hf₂ x⟩, },
end


/-
Copyright (c) 2020 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/
import logic.basic

/-!
# Extra facts about `pprod`

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

open function
variables {α β γ δ : Sort*}

namespace pprod

@[simp] lemma mk.eta {p : pprod α β} : pprod.mk p.1 p.2 = p :=
pprod.cases_on p (λ a b, rfl)

@[simp] theorem «forall» {p : pprod α β → Prop} : (∀ x, p x) ↔ (∀ a b, p ⟨a, b⟩) :=
⟨assume h a b, h ⟨a, b⟩, assume h ⟨a, b⟩, h a b⟩

@[simp] theorem «exists» {p : pprod α β → Prop} : (∃ x, p x) ↔ (∃ a b, p ⟨a, b⟩) :=
⟨assume ⟨⟨a, b⟩, h⟩, ⟨a, b, h⟩, assume ⟨a, b, h⟩, ⟨⟨a, b⟩, h⟩⟩

theorem forall' {p : α → β → Prop} : (∀ x : pprod α β, p x.1 x.2) ↔ ∀ a b, p a b :=
pprod.forall

theorem exists' {p : α → β → Prop} : (∃ x : pprod α β, p x.1 x.2) ↔ ∃ a b, p a b :=
pprod.exists

end pprod

lemma function.injective.pprod_map {f : α → β} {g : γ → δ} (hf : injective f) (hg : injective g) :
  injective (λ x, ⟨f x.1, g x.2⟩ : pprod α γ → pprod β δ) :=
λ ⟨x₁, x₂⟩ ⟨y₁, y₂⟩ h,
have A : _ := congr_arg pprod.fst h,
have B : _ := congr_arg pprod.snd h,
congr_arg2 pprod.mk (hf A) (hg B)


import lib.lib09

open m154

/-
Le bouquet final

Cette feuille utilise tout le cours de MDD 154 pour démontrer de nombreux
théorèmes parmi les plus subtils de Math 101 : la caractérisation
séquentielle des bornes supérieures, l'équivalence entre continuité
séquentielle et continuité, le fait que toute fonction continue sur un segment
est majorée et atteint son maximum, et enfin le théorème des valeurs
intermédiaires.

Il est inutile d'aborder cette feuille sans être à l'aise avec les feuilles
précédentes. Réviser les feuilles précédentes et poser des questions est toujours
une bonne idée.

On commence par un échauffement, une variante du

  lim_le (hu : limite_suite u x) (ineg : ∀ n, u n ≤ y) : x ≤ y

de la feuille 11, avec l'autre inégalité et seulement à partir d'un certain rang.

On pourra utiliser les lemmes habituels :

  `abs_inferieur_ssi (x y : ℝ) : |x| ≤ y ↔ -y ≤ x ∧ x ≤ y`

  `superieur_max_ssi (p q r) : r ≥ max p q  ↔ r ≥ p ∧ r ≥ q`

  `inferieur_max_gauche p q : p ≤ max p q`

  `inferieur_max_droite p q : q ≤ max p q`

ainsi que le lemme

  `inferieur_si_inferieur_plus_eps : (∀ ε > 0, y ≤ x + ε) →  y ≤ x`

démontré dans la feuille 11
-/

-- Si u tend vers x et y ≤ u_n pour n assez grand n alors y ≤ x.
lemma le_lim {x y : ℝ} {u : ℕ → ℝ} (hu : limite_suite u x)
  (ineg : ∃ N, ∀ n ≥ N, y ≤ u n) : y ≤ x :=
begin
  sorry
end

/-
Le premier objectif sérieux de cette feuille est de démontrer la caractérisation
séquentielle des bornes supérieures. Pour cela, et pour les objectifs suivants,
nous aurons besoin de pas mal de choses des feuilles précédentes, et de
quelques extras.

Nous avons démontré dans la feuille 8 qu'une suite constante converge vers
sa valeur :

  `lim_constante (x : ℝ) : limite_suite (fct n ↦ x) x`

et le théorème des gendarmes :

  `gendarmes (lim_u : limite_suite u l) (lim_w : limite_suite w l)`
    `(hu : ∀ n, u n ≤ v n) (hw : ∀ n, v n ≤ w n)  : limite_suite v l`

Dans la feuille 11, nous avons introduit les deux définitions :

  Le réel x est un majorant de l'ensemble de réels A :
  `def majorant (A : set ℝ) (x : ℝ) := ∀ a ∈ A, a ≤ x`

  Le réel x est une  borne supérieure de l'ensemble de réels A :
  `def borne_sup (A : set ℝ) (x : ℝ) := majorant A x ∧ ∀ y, majorant A y → x ≤ y`

et montré que si un réel x est borne supérieure d'un ensemble de réels A alors,
pour tout y, si y < x alors il existe a dans A strictement plus grand que y.

  `lt_sup (hx : borne_sup A x) : ∀ y, y < x → ∃ a ∈ A, y < a`

Dans les exercices suivants, on pourra aussi utiliser les lemmes

  `inv_succ_pos : ∀ n : ℕ, 1/(n + 1 : ℝ) > 0`

  `limite_inv_succ :  ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, 1/(n + 1 : ℝ) ≤ ε`

et les conséquences faciles suivantes de limite_inv_succ :

  `limite_si_inferieur_un_sur (h : ∀ n, |u n - x| ≤ 1/(n+1)) : limite_suite u x`

  `lim_plus_un_sur (x : ℝ) : limite_suite (fct n ↦ x + 1/(n+1)) x`

  `lim_moins_un_sur (x : ℝ) : limite_suite (fct n ↦ x - 1/(n+1)) x`

La structure de la démonstration est offerte. Les accolades servent à
délimiter les objectifs intermédiaires, tout en faisant provisoirement
disparaître l'affichage des objectifs en attente. Il ne faut surtout pas
les supprimer, sous peine de se perdre irrémédiablement.

Enfin on notera l'apparition dans la structure de démonstration ci-dessous
de la commande `Par ... on choisit ... tel que`. Cette commande est décrite
dans la dernière section du chapitre 3 du cours, intitulée
« Utilisation d'un ∀x, ∃y et axiome du choix »
(qu'il convient donc de relire ou de lire), mais n'était pas encore apparue
dans les exercices.
-/

-- Un réel x est borne supérieure d'un ensemble de réels A ssi il est
-- majorant de A et il existe une suite d'éléments de A qui tend vers x.
lemma borne_sup_ssi (A : set ℝ) (x : ℝ) :
(borne_sup A x) ↔ (majorant A x ∧ ∃ u : ℕ → ℝ, limite_suite u x ∧ ∀ n, u n ∈ A) :=
begin
  Montrons que (borne_sup A x) → (majorant A x ∧ ∃ u : ℕ → ℝ, limite_suite u x ∧ ∀ n, u n ∈ A),
  { Supposons h : borne_sup A x,
    Montrons que majorant A x,
    {
  sorry
    },
    Montrons que ∃ (u : ℕ → ℝ), limite_suite u x ∧ ∀ n, u n ∈ A,
    { Fait F1 : ∀ n : ℕ, ∃ a ∈ A, x - 1/(n+1) < a,
      {
  sorry
        },
      Par F1 on choisit u tel que (hu : ∀ n, u n ∈ A) (hu' : ∀ (n : ℕ), x - 1 / (n + 1 : ℝ) < u n),
  sorry
           } },
  {
  sorry
      },
end

/-
Les autres exercices de cette feuille utiliseront la définition de la
continuité d'une fonction de ℝ dans ℝ en un point de ℝ.
-/

/-- La fonction `f` est continue en `x₀`. -/
def continue_en (f : ℝ → ℝ) (x₀ : ℝ) : Prop :=
∀ ε > 0, ∃ δ > 0, ∀ x, |x - x₀| ≤ δ → |f x - f x₀| ≤ ε

-- Dans la suite, f désignera une fonction de ℝ dans ℝ, x₀ un réel et u
-- une suite de réels
variables {f : ℝ → ℝ} {x₀ : ℝ} {u : ℕ → ℝ}

/-
On commence par un échauffement concernant définition de la continuité.
-/

/-- Une fonction continue en x₀ est séquentiellement continue en x₀ -/
lemma seq_continue_si_continue (hf : continue_en f x₀)
  (hu : limite_suite u x₀) : limite_suite (f ∘ u) (f x₀) :=
begin
  sorry
end

/-
La réciproque de du lemme précédent est vraie aussi :
La continuité séquentielle en x₀ implique la continuité en x₀.
Mais la démonstration est moins courte. On pourra s'inspirer de
la démonstration de borne_sup_ssi (et du poly de math 101 !).
-/
example :
  (∀ u : ℕ → ℝ, limite_suite u x₀ → limite_suite (f ∘ u) (f x₀)) →
  continue_en f x₀ :=
begin
  sorry
end

/-
Dans la suite, étant donnés deux réels,
on utilise la notation habituelle [a, b] pour désigner le segment [a, b].
Attention, si par malheur b < a alors ce segment est vide. C'est un peu
une question de convention, mais notre définition d'un segment assure le
lemme suivant (dont l'utilisation est rarement nécessaire vu qu'il ne fait que
déplier une définition).
-/

lemma dans_segment {a b x : ℝ}  : x ∈ [a, b] ↔ a ≤ x ∧ x ≤ b :=
iff.rfl -- cette ligne signifie : « C'est équivalent par définition. »

/-
Nous avons vu dans la feuille 9 les définitions et le lemme :

Une extraction est une fonction strictement croissante de ℕ dans ℕ :

  `def extraction (φ : ℕ → ℕ) := ∀ n m, n < m → φ n < φ m`

Un réel a est valeur d'adhérence d'une suite u s'il
existe une suite extraite de u qui tend vers a.

  `def valeur_adherence (u : ℕ → ℝ) (a : ℝ) := ∃ φ, extraction φ ∧ limite_suite (u ∘ φ) a`

Toute extraction est supérieure à l'identité :

  `extraction_superieur_id : extraction φ → ∀ n, n ≤ φ n`

Et dans la feuille 11 la définition et le lemme :

La suite u tend vers +∞.

  `def limite_infinie_suite (u : ℕ → ℝ) := ∀ A, ∃ N, ∀ n ≥ N, u n ≥ A`

Si u tend vers +∞ alors u ne tend vers aucune limite finie

  `limite_infinie_pas_finie : limite_infinie_suite u → ∀ l, ¬ limite_suite u l`
-/

-- Dans la suite, φ sera une fonction de ℕ dans ℕ
variables {φ : ℕ → ℕ}

/-- Si `u` tend vers `+∞` alors toutes ses suites extraites tendent vers `+∞`. -/
lemma limite_infinie_extraction_si_limite
  (h : limite_infinie_suite u) (hφ : extraction φ) :
limite_infinie_suite (u ∘ φ) :=
begin
  sorry
end

lemma limite_infinie_gendarme {u v : ℕ → ℝ} (hu : limite_infinie_suite u)
(huv : ∀ n, u n ≤ v n) : limite_infinie_suite v :=
begin
  sorry
end

/-
Dans la suite, on pourra aussi utiliser le théorème de Bolzano-Weirstrass :

Toute suite à valeur dans un segment [a, b] admet une valeur d'adhérence
dans [a, b].

  `bolzano_weierstrass (h : ∀ n, u n ∈ [a, b]) : ∃ c ∈ [a, b], valeur_adherence u c`

Ce théorème se démontre à partir du théorème de la borne supérieure, mais il
faut construire une suite par récurrence, ce que nous n'avons pas appris à
faire dans Lean.

On pourra utiliser aussi le lemme suivant, qui lui est évident

  `limite_suite_id : limite_infinie_suite (fct n ↦ n)`
-/

-- Toute fonction continue sur un segment y est majorée.
lemma majoree_segment {f : ℝ → ℝ} {a b : ℝ} (hf : ∀ x ∈ [a, b], continue_en f x) :
∃ M, ∀ x ∈ [a, b], f x ≤ M :=
begin
  sorry
end

/-
Pour l'exercice suivant, on pourra utiliser le lemme

  `abs_neg x : |-x| = |x|`
-/

-- Si f est continue en x₀ alors -f est aussi continue en x₀
lemma continue_opposee {f : ℝ → ℝ} {x₀ : ℝ} (h : continue_en f x₀) :
  continue_en (fct x ↦ -f x) x₀ :=
begin
  sorry
end

-- Toute fonction continue sur un segment y est minorée.
lemma minoree_segment {f : ℝ → ℝ} {a b : ℝ} (hf : ∀ x ∈ [a, b], continue_en f x) :
∃ m, ∀ x ∈ [a, b], m ≤ f x :=
begin
  Fait clef : ∃ M, ∀ x ∈ [a, b], -f x ≤ M,
  {
  sorry
  },
  sorry
end

/-
Nous avons démontré dans la feuille 8 qu'une suite a au plus une limite :

 `unicite_limite : limite_suite u l → limite_suite u l' → l = l'`

et démontré dans la feuille 9 que,si u tend vers l alors toutes ses suites
extraites tendent vers l :

  `limite_extraction_si_limite (h : limite_suite u l) (hφ : extraction φ) :`
    `limite_suite (u ∘ φ) l`

On admet une version du théorème de la borne supérieure (qu'on ne peut pas
démontrer sans construire les nombres réels ou admettre un autre théorème
aussi fort) :

`sup_segment {a b : ℝ} {A : set ℝ} (hnonvide : ∃ x, x ∈ A) (h : A ⊆ [a, b]) :`
  `∃ x ∈ [a, b], borne_sup A x`

Dans l'exercice suivant, il peut être utile de démontrer une inclusion
entre ensembles A et B de nombres réels.
Par définition, `A ⊆ B` signifie : `∀ x, x ∈ A → x ∈ B`.
On peut donc commencer la démonstration de A ⊆ B par « Soit (x ∈ A) »
qui fait apparaître « x : ℝ » et « x_mem : x ∈ A  » dans le contexte,
puis démontrer x ∈ B.

On remarquera aussi l'utilisation de la notation
  {x | P x}
qui désigne l'ensemble des x vérifiant le prédicat P.
Par exemple, la définition du segment [a, b] est :
`[a, b] = { x | a ≤ x ∧ x ≤ b }`

Ainsi l'énoncé `y ∈ { x | P x}` signifie `P y`, par définition.

-/

/-- Toute fonction continue sur un segment non vide y admet un maximum. -/
example {a b : ℝ} (hab : a ≤ b) (hf : ∀ x ∈ [a, b], continue_en f x) :
∃ x₀ ∈ [a, b], ∀ x ∈ [a, b], f x ≤ f x₀ :=
begin
  Par minoree_segment appliqué à hf on obtient m
     tel que hm : ∀ (x : ℝ), x ∈ [a, b] → m ≤ f x,
  Par majoree_segment appliqué à hf on obtient M
     tel que hM : ∀ (x : ℝ), x ∈ [a, b] → f x ≤ M,
  Posons A := {y | ∃ x ∈ [a, b], y = f x}, -- A est l'image de [a, b] par f
  Fait etape1 : ∃ y₀ ∈ [m, M], borne_sup A y₀,
  {
  sorry
  },
  Par etape1 on obtient y₀ tel que (y_dans : y₀ ∈ [m, M]) (y_sup : borne_sup A y₀),
  On réécrit via borne_sup_ssi dans y_sup,
  Par y_sup on obtient (y_maj : majorant A y₀)
                       (u : ℕ → ℝ) (lim_u : limite_suite u y₀) (u_dans : ∀ n, u n ∈ A),
  Par u_dans on choisit (v : ℕ → ℝ) tel que
    (v_dans : ∀ n, v n ∈ [a, b]) (hufv : ∀ n, u n = f (v n)),
  Fait hu_eq_comp : u = f ∘ v,
    On conclut par funext appliqué à hufv,
  sorry
end

-- Malheureusement `On combine` ne fait pas tout seul le lemme suivant
lemma stupide {a b x : ℝ} (h : x ∈ [a, b]) (h' : x ≠ b) : x < b :=
begin
  On conclut par lt_of_le_of_ne appliqué à [h.right, h'],
end
/-
Et maintenant le boss final...

On pourra utiliser le lemme `not_and {P Q : Prop} : (¬(P ∧ Q)) ↔ (P → ¬Q)`
plutôt que d'utiliser directement la commande `On pousse la négation` qui utilise
une autre reformulation de `¬(P ∧ Q)`.
-/

/-- Le théorème des valeurs intermédiaires -/
example (f : ℝ → ℝ) (hf : ∀ x, continue_en f x) (h₀ : f 0 < 0) (h₁ : f 1 > 0) :
∃ x₀ ∈ [0, 1], f x₀ = 0 :=
begin
  Posons A := { x | x ∈ [0, 1] ∧ f x < 0},
  Fait ex_x₀ : ∃ x₀ ∈ [0, 1], borne_sup A x₀,
  {
  sorry
  },
  Par ex_x₀ on obtient x₀ tel que x₀_dans x₀_sup,
  Montrons que x₀ convient,
    On conclut par x₀_dans,
  Fait fx₀_neg : f x₀ ≤ 0,
  {
  sorry
  },
  Fait x₀_1: x₀ < 1,
  {
  sorry
  },
  Fait fx₀_pos : f x₀ ≥ 0,
  { Fait dans' : ∃ N : ℕ, ∀ n ≥ N, x₀ + 1/(n+1) ∈ [0, 1],
    { Fait dans'' : ∃ N : ℕ, ∀ n≥ N, 1/(n+1 : ℝ) ≤ 1-x₀,
      {
  sorry
      },
  sorry
    },
    Fait pas_dans : ∀ n : ℕ, x₀ + 1/(n+1) ∉ A,
    -- Par définition, x ∉ A signifie « non (x ∈ A) ».
    {
  sorry
    },
    On reformule pas_dans en ∀ n : ℕ, ¬(x₀ + 1 / (n + 1 : ℝ) ∈ [0, 1] ∧ f (x₀ + 1 / (n + 1 : ℝ)) < 0),
  sorry
  },
  On combine [fx₀_pos, fx₀_neg],
end