/-
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
import measure_theory.function.strongly_measurable

/-!

# Almost everywhere equal functions

We build a space of equivalence classes of functions, where two 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.
To use this space as a basis for the `L^p` spaces and for the Bochner integral, we consider
equivalence classes of strongly measurable functions (or, equivalently, of almost everywhere
strongly measurable functions.)

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

## Notation

* `α →ₘ[μ] β` is the type of `L⁰` space, where `α` is a measurable space, `β` is a topological
  space, 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 the coercion `(f : α → β)`, which
                 is implemented as `f.to_fun`.
                 For each operation `op` in `L⁰`, there is a lemma called `coe_fn_op`,
                 characterizing, say, `(f op g : α → β)`.
* `ae_eq_fun.mk` : To constructs an `L⁰` function `α →ₘ β` from an almost everywhere strongly
                 measurable function `f : α → β`, use `ae_eq_fun.mk`
* `comp`         : Use `comp g f` to get `[g ∘ f]` from `g : β → γ` and `[f] : α →ₘ γ` when `g` is
                 continuous. Use `comp_measurable` if `g` is only measurable (this requires the
                 target space to be second countable).
* `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 topological_space

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

namespace measure_theory

section measurable_space
variables [topological_space β]

variable (β)

/-- The equivalence relation of being almost everywhere equal for almost everywhere strongly
measurable functions. -/
def measure.ae_eq_setoid (μ : measure α) : setoid { f : α → β // ae_strongly_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 almost everywhere strongly measurable functions, where two
    strongly 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 [topological_space β] [topological_space γ] [topological_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 {β : Type*} [topological_space β]
  (f : α → β) (hf : ae_strongly_measurable f μ) : α →ₘ[μ] β := quotient.mk' ⟨f, hf⟩

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

protected lemma strongly_measurable (f : α →ₘ[μ] β) : strongly_measurable f :=
ae_strongly_measurable.strongly_measurable_mk _

protected lemma ae_strongly_measurable (f : α →ₘ[μ] β) : ae_strongly_measurable f μ :=
f.strongly_measurable.ae_strongly_measurable

protected lemma measurable [metrizable_space β] [measurable_space β] [borel_space β]
  (f : α →ₘ[μ] β) : measurable f :=
ae_strongly_measurable.measurable_mk _

protected lemma ae_measurable [metrizable_space β] [measurable_space β] [borel_space β]
  (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_strongly_measurable = f :=
begin
  conv_rhs { rw ← quotient.out_eq' f },
  set g : {f : α → β // ae_strongly_measurable f μ} := quotient.out' f with hg,
  have : g = ⟨g.1, g.2⟩ := subtype.eq rfl,
  rw [this, ← mk, mk_eq_mk],
  exact (ae_strongly_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_strongly_measurable.ae_eq_mk _).symm.trans,
  exact @quotient.mk_out' _ (μ.ae_eq_setoid β) (⟨f, hf⟩ : {f // ae_strongly_measurable f μ})
end

@[elab_as_eliminator]

@[elab_as_eliminator]
lemma induction_on₂ {α' β' : Type*} [measurable_space α'] [topological_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 α'] [topological_space β']
  {μ' : measure α'}
  {α'' β'' : Type*} [measurable_space α''] [topological_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 continuous 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 : continuous g) (f : α →ₘ[μ] β) : α →ₘ[μ] γ :=
quotient.lift_on' f (λ f, mk (g ∘ (f : α → β)) (hg.comp_ae_strongly_measurable f.2)) $
  λ f f' H, mk_eq_mk.2 $ H.fun_comp g

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

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

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

section comp_measurable

variables [measurable_space β] [metrizable_space β] [borel_space β]
  [measurable_space γ] [metrizable_space γ] [opens_measurable_space γ] [second_countable_topology γ]

/-- 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] : α →ₘ γ`. This requires that `γ` has a second countable topology. -/
def comp_measurable
  (g : β → γ) (hg : measurable g) (f : α →ₘ[μ] β) : α →ₘ[μ] γ :=
quotient.lift_on' f (λ f', mk (g ∘ (f' : α → β))
  (hg.comp_ae_measurable f'.2.ae_measurable).ae_strongly_measurable) $
  λ f f' H, mk_eq_mk.2 $ H.fun_comp g

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

lemma comp_measurable_eq_mk (g : β → γ) (hg : measurable g) (f : α →ₘ[μ] β) :
  comp_measurable g hg f =
    mk (g ∘ f) (hg.comp_ae_measurable f.ae_measurable).ae_strongly_measurable :=
by rw [← comp_measurable_mk g hg f f.ae_strongly_measurable, mk_coe_fn]

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

end comp_measurable

/-- 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_strongly_measurable.prod_mk g.ae_strongly_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 continuous 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₂ (g : β → γ → δ)
  (hg : continuous (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) : α →ₘ[μ] δ :=
comp _ hg (f₁.pair f₂)

@[simp] lemma comp₂_mk_mk
  (g : β → γ → δ) (hg : continuous (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_strongly_measurable (hf₁.prod_mk hf₂)) :=
rfl

lemma comp₂_eq_pair
  (g : β → γ → δ) (hg : continuous (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) :
  comp₂ g hg f₁ f₂ = comp _ hg (f₁.pair f₂) :=
rfl

lemma comp₂_eq_mk
  (g : β → γ → δ) (hg : continuous (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) :
  comp₂ g hg f₁ f₂ = mk (λ a, g (f₁ a) (f₂ a)) (hg.comp_ae_strongly_measurable
    (f₁.ae_strongly_measurable.prod_mk f₂.ae_strongly_measurable)) :=
by rw [comp₂_eq_pair, pair_eq_mk, comp_mk]; refl

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

section

variables [measurable_space β] [metrizable_space β] [borel_space β] [second_countable_topology β]
  [measurable_space γ] [metrizable_space γ] [borel_space γ] [second_countable_topology γ]
  [measurable_space δ] [metrizable_space δ] [opens_measurable_space δ] [second_countable_topology δ]

/-- 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)] : α →ₘ γ`. This requires `δ` to have second-countable topology. -/
def comp₂_measurable (g : β → γ → δ)
  (hg : measurable (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) : α →ₘ[μ] δ :=
comp_measurable _ hg (f₁.pair f₂)

@[simp] lemma comp₂_measurable_mk_mk
  (g : β → γ → δ) (hg : measurable (uncurry g)) (f₁ : α → β) (f₂ : α → γ) (hf₁ hf₂) :
  comp₂_measurable g hg (mk f₁ hf₁ : α →ₘ[μ] β) (mk f₂ hf₂) =
  mk (λ a, g (f₁ a) (f₂ a))
    (hg.comp_ae_measurable (hf₁.ae_measurable.prod_mk hf₂.ae_measurable)).ae_strongly_measurable :=
rfl

lemma comp₂_measurable_eq_pair
  (g : β → γ → δ) (hg : measurable (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) :
  comp₂_measurable g hg f₁ f₂ = comp_measurable _ hg (f₁.pair f₂) :=
rfl

lemma comp₂_measurable_eq_mk
  (g : β → γ → δ) (hg : measurable (uncurry g)) (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) :
  comp₂_measurable g hg f₁ f₂ = mk (λ a, g (f₁ a) (f₂ a)) (hg.comp_ae_measurable
    (f₁.ae_measurable.prod_mk f₂.ae_measurable)).ae_strongly_measurable :=
by rw [comp₂_measurable_eq_pair, pair_eq_mk, comp_measurable_mk]; refl

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

end

/-- Interpret `f : α →ₘ[μ] β` as a germ at `μ.ae` forgetting that `f` is almost everywhere
    strongly 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 : continuous g) (f : α →ₘ[μ] β) :
  (comp g hg f).to_germ = f.to_germ.map g :=
induction_on f $ λ f hf, by simp

lemma comp_measurable_to_germ [measurable_space β] [borel_space β] [metrizable_space β]
  [metrizable_space γ] [second_countable_topology γ] [measurable_space γ] [opens_measurable_space γ]
  (g : β → γ) (hg : measurable g) (f : α →ₘ[μ] β) :
  (comp_measurable g hg f).to_germ = f.to_germ.map g :=
induction_on f $ λ f hf, by simp

lemma comp₂_to_germ (g : β → γ → δ) (hg : continuous (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

lemma comp₂_measurable_to_germ
  [metrizable_space β] [second_countable_topology β] [measurable_space β] [borel_space β]
  [metrizable_space γ] [second_countable_topology γ] [measurable_space γ] [borel_space γ]
  [metrizable_space δ] [second_countable_topology δ] [measurable_space δ] [opens_measurable_space δ]
  (g : β → γ → δ) (hg : measurable (uncurry g))
  (f₁ : α →ₘ[μ] β) (f₂ : α →ₘ[μ] γ) :
  (comp₂_measurable 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

section lattice

section sup
variables [semilattice_sup β] [measurable_space β] [second_countable_topology β]
  [metrizable_space β] [borel_space β] [has_measurable_sup₂ β]

instance : has_sup (α →ₘ[μ] β) :=
{ sup := λ f g, ae_eq_fun.comp₂_measurable (⊔) measurable_sup f g }

lemma coe_fn_sup (f g : α →ₘ[μ] β) : ⇑(f ⊔ g) =ᵐ[μ] λ x, f x ⊔ g x :=
coe_fn_comp₂_measurable _ _ _ _

protected lemma le_sup_left (f g : α →ₘ[μ] β) : f ≤ f ⊔ g :=
by { rw ← coe_fn_le, filter_upwards [coe_fn_sup f g] with _ ha, rw ha, exact le_sup_left, }

protected lemma le_sup_right (f g : α →ₘ[μ] β) : g ≤ f ⊔ g :=
by { rw ← coe_fn_le, filter_upwards [coe_fn_sup f g] with _ ha, rw ha, exact le_sup_right, }

protected lemma sup_le (f g f' : α →ₘ[μ] β) (hf : f ≤ f') (hg : g ≤ f') : f ⊔ g ≤ f' :=
begin
  rw ← coe_fn_le at hf hg ⊢,
  filter_upwards [hf, hg, coe_fn_sup f g] with _ haf hag ha_sup,
  rw ha_sup,
  exact sup_le haf hag,
end

end sup

section inf
variables [semilattice_inf β] [measurable_space β] [second_countable_topology β]
  [metrizable_space β] [borel_space β] [has_measurable_inf₂ β]

instance : has_inf (α →ₘ[μ] β) :=
{ inf := λ f g, ae_eq_fun.comp₂_measurable (⊓) measurable_inf f g }

lemma coe_fn_inf (f g : α →ₘ[μ] β) : ⇑(f ⊓ g) =ᵐ[μ] λ x, f x ⊓ g x :=
coe_fn_comp₂_measurable _ _ _ _

protected lemma inf_le_left (f g : α →ₘ[μ] β) : f ⊓ g ≤ f :=
by { rw ← coe_fn_le, filter_upwards [coe_fn_inf f g] with _ ha, rw ha, exact inf_le_left, }

protected lemma inf_le_right (f g : α →ₘ[μ] β) : f ⊓ g ≤ g :=
by { rw ← coe_fn_le, filter_upwards [coe_fn_inf f g] with _ ha, rw ha, exact inf_le_right, }

protected lemma le_inf (f' f g : α →ₘ[μ] β) (hf : f' ≤ f) (hg : f' ≤ g) : f' ≤ f ⊓ g :=
begin
  rw ← coe_fn_le at hf hg ⊢,
  filter_upwards [hf, hg, coe_fn_inf f g] with _ haf hag ha_inf,
  rw ha_inf,
  exact le_inf haf hag,
end

end inf

instance [lattice β] [measurable_space β] [second_countable_topology β]
  [metrizable_space β] [borel_space β]
  [has_measurable_sup₂ β] [has_measurable_inf₂ β] : lattice (α →ₘ[μ] β) :=
{ sup           := has_sup.sup,
  le_sup_left   := ae_eq_fun.le_sup_left,
  le_sup_right  := ae_eq_fun.le_sup_right,
  sup_le        := ae_eq_fun.sup_le,
  inf           := has_inf.inf,
  inf_le_left   := ae_eq_fun.inf_le_left,
  inf_le_right  := ae_eq_fun.inf_le_right,
  le_inf        := ae_eq_fun.le_inf,
  ..ae_eq_fun.partial_order}

end lattice

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_strongly_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_strongly_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

-- Note we set up the scalar actions before the `monoid` structures in case we want to
-- try to override the `nsmul` or `zsmul` fields in future.
section has_scalar

variables {𝕜 𝕜' : Type*}
variables [has_scalar 𝕜 γ] [has_continuous_const_smul 𝕜 γ]
variables [has_scalar 𝕜' γ] [has_continuous_const_smul 𝕜' γ]

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

@[simp] lemma smul_mk (c : 𝕜) (f : α → γ) (hf : ae_strongly_measurable f μ) :
  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 _ _ _

instance [smul_comm_class 𝕜 𝕜' γ] : smul_comm_class 𝕜 𝕜' (α →ₘ[μ] γ) :=
⟨λ a b f, induction_on f $ λ f hf, by simp_rw [smul_mk, smul_comm]⟩

instance [has_scalar 𝕜 𝕜'] [is_scalar_tower 𝕜 𝕜' γ] : is_scalar_tower 𝕜 𝕜' (α →ₘ[μ] γ) :=
⟨λ a b f, induction_on f $ λ f hf, by simp_rw [smul_mk, smul_assoc]⟩

instance [has_scalar 𝕜ᵐᵒᵖ γ] [is_central_scalar 𝕜 γ] : is_central_scalar 𝕜 (α →ₘ[μ] γ) :=
⟨λ a f, induction_on f $ λ f hf, by simp_rw [smul_mk, op_smul_eq_smul]⟩

end has_scalar

section has_mul
variables [has_mul γ] [has_continuous_mul γ]

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

@[simp, to_additive] lemma mk_mul_mk (f g : α → γ) (hf : ae_strongly_measurable f μ)
  (hg : ae_strongly_measurable g μ) :
  (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 _ _ _ _

end has_mul

instance [add_monoid γ] [has_continuous_add γ] : add_monoid (α →ₘ[μ] γ) :=
to_germ_injective.add_monoid to_germ zero_to_germ add_to_germ (λ _ _, smul_to_germ _ _)

instance [add_comm_monoid γ] [has_continuous_add γ] : add_comm_monoid (α →ₘ[μ] γ) :=
to_germ_injective.add_comm_monoid to_germ zero_to_germ add_to_germ (λ _ _, smul_to_germ _ _)

section monoid
variables [monoid γ] [has_continuous_mul γ]

instance : has_pow (α →ₘ[μ] γ) ℕ := ⟨λ f n, comp _ (continuous_pow n) f⟩

@[simp] lemma mk_pow (f : α → γ) (hf) (n : ℕ) :
  (mk f hf : α →ₘ[μ] γ) ^ n =
    mk (f ^ n) ((_root_.continuous_pow n).comp_ae_strongly_measurable hf) :=
rfl

lemma coe_fn_pow (f : α →ₘ[μ] γ) (n : ℕ) : ⇑(f ^ n) =ᵐ[μ] f ^ n :=
coe_fn_comp _ _ _

@[simp] lemma pow_to_germ (f : α →ₘ[μ] γ) (n : ℕ) :
  (f ^ n).to_germ = f.to_germ ^ n :=
comp_to_germ _ _ _

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

/-- `ae_eq_fun.to_germ` as a `monoid_hom`. -/
@[to_additive "`ae_eq_fun.to_germ` as an `add_monoid_hom`.", simps]
def to_germ_monoid_hom : (α →ₘ[μ] γ) →* μ.ae.germ γ :=
{ to_fun := to_germ,
  map_one' := one_to_germ,
  map_mul' := mul_to_germ }

end monoid

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

section group
variables [group γ] [topological_group γ]

section inv

@[to_additive] instance : has_inv (α →ₘ[μ] γ) := ⟨comp has_inv.inv continuous_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 _ _ _

end inv

section div

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

@[simp, to_additive] lemma mk_div (f g : α → γ)
  (hf : ae_strongly_measurable f μ) (hg : ae_strongly_measurable g μ) :
  mk (f / g) (hf.div 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 _ _ _ _

end div

section zpow

instance has_int_pow : has_pow (α →ₘ[μ] γ) ℤ :=
⟨λ f n, comp _ (continuous_zpow n) f⟩

@[simp] lemma mk_zpow (f : α → γ) (hf) (n : ℤ) :
  (mk f hf : α →ₘ[μ] γ) ^ n = mk (f ^ n) ((continuous_zpow n).comp_ae_strongly_measurable hf) :=
rfl

lemma coe_fn_zpow (f : α →ₘ[μ] γ) (n : ℤ) : ⇑(f ^ n) =ᵐ[μ] f ^ n :=
coe_fn_comp _ _ _

@[simp] lemma zpow_to_germ (f : α →ₘ[μ] γ) (n : ℤ) :
  (f ^ n).to_germ = f.to_germ ^ n :=
comp_to_germ _ _ _

end zpow

end group

instance [add_group γ] [topological_add_group γ] :
  add_group (α →ₘ[μ] γ) :=
to_germ_injective.add_group to_germ zero_to_germ add_to_germ neg_to_germ sub_to_germ
  (λ _ _, smul_to_germ _ _) (λ _ _, smul_to_germ _ _)

instance [add_comm_group γ] [topological_add_group γ] :
  add_comm_group (α →ₘ[μ] γ) :=
to_germ_injective.add_comm_group to_germ zero_to_germ add_to_germ neg_to_germ sub_to_germ
  (λ _ _, smul_to_germ _ _) (λ _ _, smul_to_germ _ _)

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

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

section module

variables {𝕜 : Type*}

instance [monoid 𝕜] [mul_action 𝕜 γ] [has_continuous_const_smul 𝕜 γ] :
  mul_action 𝕜 (α →ₘ[μ] γ) :=
to_germ_injective.mul_action to_germ smul_to_germ

instance [monoid 𝕜] [add_monoid γ] [has_continuous_add γ]
  [distrib_mul_action 𝕜 γ] [has_continuous_const_smul 𝕜 γ] :
  distrib_mul_action 𝕜 (α →ₘ[μ] γ) :=
to_germ_injective.distrib_mul_action (to_germ_add_monoid_hom : (α →ₘ[μ] γ) →+ _)
  (λ c : 𝕜, smul_to_germ c)

instance [semiring 𝕜] [add_comm_monoid γ] [has_continuous_add γ] [module 𝕜 γ]
  [has_continuous_const_smul 𝕜 γ] :
  module 𝕜 (α →ₘ[μ] γ) :=
to_germ_injective.module 𝕜 (to_germ_add_monoid_hom : (α →ₘ[μ] γ) →+ _) 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.ae_measurable).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.ae_measurable hg.ae_measurable]

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 [linear_order γ] [order_closed_topology γ] [has_zero γ]

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

@[simp] lemma pos_part_mk (f : α → γ) (hf) :
  pos_part (mk f hf : α →ₘ[μ] γ) =
    mk (λ x, max (f x) 0) ((continuous_id.max continuous_const).comp_ae_strongly_measurable hf) :=
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 β] [second_countable_topology_either α β] [metrizable_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.ae_strongly_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 β]

/-- 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.continuous.ae_strongly_measurable g.continuous.ae_strongly_measurable }

variables {𝕜 : Type*} [semiring 𝕜]
variables [topological_space γ] [metrizable_space γ] [add_comm_group γ]
  [module 𝕜 γ] [topological_add_group γ] [has_continuous_const_smul 𝕜 γ]
  [second_countable_topology_either α γ]

/-- 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.ae_strongly_measurable,
  .. to_ae_eq_fun_add_hom μ }

end continuous_map


import ModelChecking.Mathlib

open Classical

-- Definition 2.1
structure TransSys where
  S : Type _
  Act : Type _
  trans : S → Act → S → Prop
  I : Set S
  AP : Type _
  L : S → Set AP

notation s " -[" t ", " α "]→ " s' => TransSys.trans t s α s'

namespace TransSys

variable (t : TransSys)

-- Page 20
def finite : Prop := sorry

-- Definition 2.3
def post₁ (s : t.S) (α : t.Act) : Set t.S := { s' | s -[t, α]→ s' }

-- Definition 2.3
def post₂ (s : t.S) : Set t.S := { s' | ∃ α, s' ∈ t.post₁ s α }

-- Definition 2.3
def post₃ (c : Set t.S) (α : t.Act) : Set t.S := { s' | ∃ s ∈ c, s' ∈ t.post₁ s α }

-- Definition 2.3
def post₄ (c : Set t.S) : Set t.S := { s' | ∃ s ∈ c, s' ∈ t.post₂ s }

-- Definition 2.3
def pre₁ (s : t.S) (α : t.Act) : Set t.S := { s' | s' -[t, α]→ s }

-- Definition 2.3
def pre₂ (s : t.S) : Set t.S := { s' | ∃ α, s' ∈ t.pre₁ s α }

-- Definition 2.3
def pre₃ (c : Set t.S) (α : t.Act) : Set t.S := { s' | ∃ s ∈ c, s' ∈ t.pre₁ s α }

-- Definition 2.3
def pre₄ (c : Set t.S) : Set t.S := { s' | ∃ s ∈ c, s' ∈ t.pre₂ s }

-- Definition 2.4
def terminal (s : t.S) : Prop := t.post₂ s = ∅

-- Definition 2.5.1
structure actionDeterministic : Prop where
  init : t.I.subsingleton
  post : ∀ s α, (t.post₁ s α).subsingleton

-- Definition 2.5.2
structure apDeterministic : Prop where
  init : t.I.subsingleton
  post : ∀ s aps, (t.post₂ s ∩ { s' | t.L s' = aps }).subsingleton

-- Definition 2.6
structure ExecFragment where
  sₒ : t.S
  tail : Nat → Option (t.Act × t.S)
  tailSeq : ∀ i, tail i = none → tail (i + 1) = none
  wfHeadTrans : ∀ i₁ α₁ s₁, tail i₁ = some (α₁, s₁) → (sₒ -[t, α₁]→ s₁)
  wfTailTrans : ∀ i α₁ α₂ s₁ s₂, tail i = some (α₁, s₁) → tail (i + 1) = some (α₂, s₂) → (s₁ -[t, α₂]→ s₂)

namespace ExecFragment

-- An execution fragment consisting of only one state.
def singleton (s : t.S) : ExecFragment t := {
  sₒ := s,
  tail := λ _ => none,
  tailSeq := λ _ _ => rfl,
  wfHeadTrans := λ _ _ _ h => by simp at h,
  wfTailTrans := λ _ _ _ _ _ h => by simp at h
}

variable {t}

-- Definition 2.6
def finite (f : ExecFragment t) : Prop := ∃ i, f.tail i = none

theorem singleton_finite (t) (s : t.S) : (singleton t s).finite := by
  simp only [singleton, finite]
  exact ⟨0, True.intro⟩

-- Definition 2.6
def infinite (f : ExecFragment t) : Prop := ∀ i, f.tail i ≠ none

theorem finite_or_infinite (f : ExecFragment t) : f.finite ∨ f.infinite := by
  sorry

theorem finite_has_end (f : ExecFragment t) (h : f.finite) :
  ∃ i, f.tail i = none ∧ ∀ j, j < i → f.tail j ≠ none :=
  sorry

-- Definition 2.6
noncomputable def length (f : ExecFragment t) (h : f.finite) : Nat := (finite_has_end f h).choose

theorem pred_of_positive_length_ne_none (f : ExecFragment t) (hf : f.finite) (hl : 0 < f.length hf) :
  f.tail (f.length hf - 1) ≠ none := by
    simp only [length] at *
    have hc := (finite_has_end f hf).choose_spec.right (finite_has_end f hf).choose -- hl
    sorry
    /-let x := (Option.ne_none_iff_exists (o := f.tail n)).mp (by
      simp only [length] at hm
      have hc := Exists.choose_spec (finite_has_end f h)
      rw [hm] at hc
      exact hc.right n (Nat.lt_succ_self n)
    )-/

-- TODO: Remove this.
lemma Nat.zero_lt_succ (n : Nat) : 0 < Nat.succ n := sorry

-- Gets the last state in a finite execution.
noncomputable def last (f : ExecFragment t) (h : f.finite) : t.S :=
  match hm:f.length h with
  | 0 => f.sₒ
  | n + 1 =>
    have hl : 0 < length f h := by simp [hm, Nat.zero_lt_succ]
    (Option.ne_none_iff_exists.mp $ pred_of_positive_length_ne_none f h hl).choose.snd

theorem singleton_last (t) (s : t.S) : (singleton t s).last (singleton_finite t s) = s := by
  simp only [last]
  sorry
  /-
  cases (singleton t s).length (singleton_finite t s)
  case zero => simp [singleton]
  case succ => sorry
  -/

-- Definition 2.7
inductive maximal (f : ExecFragment t) : Prop
  | finite (h : f.finite) : t.terminal (f.last h) → maximal f
  | infinite : f.infinite → maximal f

-- Definition 2.7
def initial (f : ExecFragment t) : Prop := f.sₒ ∈ t.I

end ExecFragment

-- Definition 2.9
structure Execution extends ExecFragment t where
  initial : toExecFragment.initial
  maximal : toExecFragment.maximal

-- Definition 2.10
def reachable (s : t.S) : Prop :=
  ∃ (f : ExecFragment t) (h : f.finite), f.initial ∧ (f.last h = s)

-- Definition 2.10
def reach : Set t.S := { s | t.reachable s }

variable {S₁ S₂ Act AP₁ AP₂ : Type _}

-- Definition 2.42
def syncProd (t₁ t₂ : TransSys) : TransSys := {
  S := t₁.S × t₂.S,
  Act := t₁.Act × t₂.Act,
  trans := λ s₁ α s₂ => (s₁.1 -[t₁, α.1]→ s₂.1) ∧ (s₁.2 -[t₂, α.2]→ s₂.2),
  I := { i | i.1 ∈ t₁.I ∧ i.2 ∈ t₂.I },
  AP := t₁.AP ⊕ t₂.AP,
  L := λ (s₁, s₂) => ((t₁.L s₁).image Sum.inl) ∪ ((t₂.L s₂).image Sum.inr)
}

-- Definition 2.42
notation t₁ " ⊗ " t₂ => TransSys.syncProd t₁ t₂

end TransSys


/-
Copyright (c) 2020 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
-/
import ring_theory.prime
import ring_theory.polynomial.content
/-!
# Eisenstein's criterion

A proof of a slight generalisation of Eisenstein's criterion for the irreducibility of
a polynomial over an integral domain.
-/

open polynomial ideal.quotient
variables {R : Type*} [comm_ring R]

namespace polynomial
open_locale polynomial
namespace eisenstein_criterion_aux
/- Section for auxiliary lemmas used in the proof of `irreducible_of_eisenstein_criterion`-/

lemma map_eq_C_mul_X_pow_of_forall_coeff_mem {f : R[X]} {P : ideal R}
  (hfP : ∀ (n : ℕ), ↑n < f.degree → f.coeff n ∈ P) :
  map (mk P) f = C ((mk P) f.leading_coeff) * X ^ f.nat_degree :=
polynomial.ext (λ n, begin
  by_cases hf0 : f = 0, { simp [hf0], },
  rcases lt_trichotomy ↑n (degree f) with h | h | h,
  { erw [coeff_map, eq_zero_iff_mem.2 (hfP n h), coeff_C_mul, coeff_X_pow, if_neg, mul_zero],
    rintro rfl, exact not_lt_of_ge degree_le_nat_degree h },
  { have : nat_degree f = n, from nat_degree_eq_of_degree_eq_some h.symm,
    rw [coeff_C_mul, coeff_X_pow, if_pos this.symm, mul_one, leading_coeff, this, coeff_map] },
  { rw [coeff_eq_zero_of_degree_lt, coeff_eq_zero_of_degree_lt],
    { refine lt_of_le_of_lt (degree_C_mul_X_pow_le _ _) _,
      rwa ← degree_eq_nat_degree hf0 },
    { exact lt_of_le_of_lt (degree_map_le _ _) h } }
end)

lemma le_nat_degree_of_map_eq_mul_X_pow {n : ℕ}
  {P : ideal R} (hP : P.is_prime) {q : R[X]} {c : polynomial (R ⧸ P)}
  (hq : map (mk P) q = c * X ^ n) (hc0 : c.degree = 0) : n ≤ q.nat_degree :=
with_bot.coe_le_coe.1
  (calc ↑n = degree (q.map (mk P)) :
      by rw [hq, degree_mul, hc0, zero_add, degree_pow, degree_X, nsmul_one, nat.cast_with_bot]
      ... ≤ degree q : degree_map_le _ _
      ... ≤ nat_degree q : degree_le_nat_degree)

lemma eval_zero_mem_ideal_of_eq_mul_X_pow {n : ℕ} {P : ideal R}
  {q : R[X]} {c : polynomial (R ⧸ P)}
  (hq : map (mk P) q = c * X ^ n) (hn0 : 0 < n) : eval 0 q ∈ P :=
by rw [← coeff_zero_eq_eval_zero, ← eq_zero_iff_mem, ← coeff_map,
    coeff_zero_eq_eval_zero, hq, eval_mul, eval_pow, eval_X, zero_pow hn0, mul_zero]

lemma is_unit_of_nat_degree_eq_zero_of_forall_dvd_is_unit {p q : R[X]}
  (hu : ∀ (x : R), C x ∣ p * q → is_unit x) (hpm : p.nat_degree = 0) :
  is_unit p :=
begin
  rw [eq_C_of_degree_le_zero (nat_degree_eq_zero_iff_degree_le_zero.1 hpm), is_unit_C],
  refine hu _ _,
  rw [← eq_C_of_degree_le_zero (nat_degree_eq_zero_iff_degree_le_zero.1 hpm)],
  exact dvd_mul_right _ _
end

end eisenstein_criterion_aux

open eisenstein_criterion_aux

variables [is_domain R]

/-- If `f` is a non constant polynomial with coefficients in `R`, and `P` is a prime ideal in `R`,
then if every coefficient in `R` except the leading coefficient is in `P`, and
the trailing coefficient is not in `P^2` and no non units in `R` divide `f`, then `f` is
irreducible. -/
theorem irreducible_of_eisenstein_criterion {f : R[X]} {P : ideal R} (hP : P.is_prime)
  (hfl : f.leading_coeff ∉ P)
  (hfP : ∀ n : ℕ, ↑n < degree f → f.coeff n ∈ P)
  (hfd0 : 0 < degree f) (h0 : f.coeff 0 ∉ P^2)
  (hu : f.is_primitive) : irreducible f :=
have hf0 : f ≠ 0, from λ _, by simp only [*, not_true, submodule.zero_mem, coeff_zero] at *,
have hf : f.map (mk P) = C (mk P (leading_coeff f)) * X ^ nat_degree f,
  from map_eq_C_mul_X_pow_of_forall_coeff_mem hfP,
have hfd0 : 0 < f.nat_degree, from with_bot.coe_lt_coe.1
  (lt_of_lt_of_le hfd0 degree_le_nat_degree),
⟨mt degree_eq_zero_of_is_unit (λ h, by simp only [*, lt_irrefl] at *),
begin
  rintros p q rfl,
  rw [map_mul] at hf,
  rcases mul_eq_mul_prime_pow (show prime (X : polynomial (R ⧸ P)),
    from monic_X.prime_of_degree_eq_one degree_X) hf with
      ⟨m, n, b, c, hmnd, hbc, hp, hq⟩,
  have hmn : 0 < m → 0 < n → false,
  { assume hm0 hn0,
    refine h0 _,
    rw [coeff_zero_eq_eval_zero, eval_mul, sq],
    exact ideal.mul_mem_mul
      (eval_zero_mem_ideal_of_eq_mul_X_pow hp hm0)
      (eval_zero_mem_ideal_of_eq_mul_X_pow hq hn0) },
  have hpql0 : (mk P) (p * q).leading_coeff ≠ 0,
  { rwa [ne.def, eq_zero_iff_mem] },
  have hp0 : p ≠ 0, from λ h, by simp only [*, zero_mul, eq_self_iff_true, not_true, ne.def] at *,
  have hq0 : q ≠ 0, from λ h, by simp only [*, eq_self_iff_true, not_true, ne.def, mul_zero] at *,
  have hbc0 : degree b = 0 ∧ degree c = 0,
  { apply_fun degree at hbc,
    rwa [degree_C hpql0, degree_mul, eq_comm, nat.with_bot.add_eq_zero_iff] at hbc },
  have hmp : m ≤ nat_degree p,
    from le_nat_degree_of_map_eq_mul_X_pow hP hp hbc0.1,
  have hnq : n ≤ nat_degree q,
    from le_nat_degree_of_map_eq_mul_X_pow hP hq hbc0.2,
  have hpmqn : p.nat_degree = m ∧ q.nat_degree = n,
  { rw [nat_degree_mul hp0 hq0] at hmnd,
    clear_except hmnd hmp hnq,
    contrapose hmnd,
    apply ne_of_lt,
    rw not_and_distrib at hmnd,
    cases hmnd,
    { exact add_lt_add_of_lt_of_le (lt_of_le_of_ne hmp (ne.symm hmnd)) hnq },
    { exact add_lt_add_of_le_of_lt hmp (lt_of_le_of_ne hnq (ne.symm hmnd)) } },
  obtain rfl | rfl : m = 0 ∨ n = 0,
  { rwa [pos_iff_ne_zero, pos_iff_ne_zero, imp_false, not_not,
      ← or_iff_not_imp_left] at hmn },
  { exact or.inl (is_unit_of_nat_degree_eq_zero_of_forall_dvd_is_unit hu hpmqn.1) },
  { exact or.inr (is_unit_of_nat_degree_eq_zero_of_forall_dvd_is_unit
      (by simpa only [mul_comm] using hu) hpmqn.2) }
end⟩

end polynomial


/-
Copyright (c) 2021 Heather Macbeth. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Heather Macbeth, David Loeffler
-/
import analysis.special_functions.complex.circle
import topology.instances.add_circle
import analysis.inner_product_space.l2_space
import measure_theory.function.continuous_map_dense
import measure_theory.function.l2_space
import measure_theory.group.integration
import measure_theory.integral.periodic
import topology.continuous_function.stone_weierstrass

/-!

# Fourier analysis on the additive circle

This file contains basic results on Fourier series for functions on the additive circle
`add_circle T = ℝ / ℤ • T`.

## Main definitions

* `haar_add_circle`, Haar measure on `add_circle T`, normalized to have total measure `1`. (Note
  that this is not the same normalisation as the standard measure defined in `integral.periodic`,
  so we do not declare it as a `measure_space` instance, to avoid confusion.)
* for `n : ℤ`, `fourier n` is the monomial `λ x, exp (2 π i n x / T)`, bundled as a continuous map
  from `add_circle T` to `ℂ`.
* `fourier_basis` is the Hilbert basis of `Lp ℂ 2 haar_add_circle` given by the images of the
  monomials `fourier n`.
* `fourier_coeff f n`, for `f : add_circle T → E` (with `E` a complete normed `ℂ`-vector space), is
  the `n`-th Fourier coefficient of `f`, defined as an integral over `add_circle T`. The lemma
  `fourier_coeff_eq_interval_integral` expresses this as an integral over `[a, a + T]` for any real
  `a`.
* `fourier_coeff_on`, for `f : ℝ → E` and `a < b` reals, is the `n`-th Fourier
  coefficient of the unique periodic function of period `b - a` which agrees with `f` on `(a, b]`.
  The lemma `fourier_coeff_on_eq_integral` expresses this as an integral over `[a, b]`.

## Main statements

The theorem `span_fourier_closure_eq_top` states that the span of the monomials `fourier n` is
dense in `C(add_circle T, ℂ)`, i.e. that its `submodule.topological_closure` is `⊤`.  This follows
from the Stone-Weierstrass theorem after checking that the span is a subalgebra, is closed under
conjugation, and separates points.

Using this and general theory on approximation of Lᵖ functions by continuous functions, we deduce
(`span_fourier_Lp_closure_eq_top`) that for any `1 ≤ p < ∞`, the span of the Fourier monomials is
dense in the Lᵖ space of `add_circle T`. For `p = 2` we show (`orthonormal_fourier`) that the
monomials are also orthonormal, so they form a Hilbert basis for L², which is named as
`fourier_basis`; in particular, for `L²` functions `f`, the Fourier series of `f` converges to `f`
in the `L²` topology (`has_sum_fourier_series_L2`). Parseval's identity, `tsum_sq_fourier_coeff`, is
a direct consequence.

For continuous maps `f : add_circle T → ℂ`, the theorem
`continuous_map.has_sum_fourier_series_of_summable` states that if the sequence of Fourier
coefficients of `f` is summable, then the Fourier series `∑ (i:ℤ), f.fourier_coeff i * fourier i`
converges to `f` in the uniform-convergence topology of `C(add_circle T, ℂ)`.
-/

noncomputable theory
open_locale ennreal complex_conjugate real
open topological_space continuous_map measure_theory measure_theory.measure algebra submodule set

variables {T : ℝ}

namespace add_circle

/-! ### Map from `add_circle` to `circle` -/

lemma scaled_exp_map_periodic :
  function.periodic (λ x, exp_map_circle (2 * π / T * x)) T :=
begin
  -- The case T = 0 is not interesting, but it is true, so we prove it to save hypotheses
  rcases eq_or_ne T 0 with rfl | hT,
  { intro x, simp },
  { intro x, simp_rw mul_add, rw [div_mul_cancel _ hT, periodic_exp_map_circle] }
end

/-- The canonical map `λ x, exp (2 π i x / T)` from `ℝ / ℤ • T` to the unit circle in `ℂ`.
If `T = 0` we understand this as the constant function 1. -/
def to_circle : add_circle T → circle := (@scaled_exp_map_periodic T).lift

lemma to_circle_add (x : add_circle T) (y : add_circle T) :
  to_circle (x + y) = to_circle x * to_circle y :=
begin
  induction x using quotient_add_group.induction_on',
  induction y using quotient_add_group.induction_on',
  simp_rw [←quotient_add_group.coe_add, to_circle, function.periodic.lift_coe,
    mul_add, exp_map_circle_add],
end

lemma continuous_to_circle : continuous (@to_circle T) :=
continuous_coinduced_dom.mpr (exp_map_circle.continuous.comp $ continuous_const.mul continuous_id')

lemma injective_to_circle (hT : T ≠ 0) : function.injective (@to_circle T) :=
begin
  intros a b h,
  induction a using quotient_add_group.induction_on',
  induction b using quotient_add_group.induction_on',
  simp_rw [to_circle, function.periodic.lift_coe] at h,
  obtain ⟨m, hm⟩ := exp_map_circle_eq_exp_map_circle.mp h.symm,
  simp_rw [quotient_add_group.eq, add_subgroup.mem_zmultiples_iff, zsmul_eq_mul],
  use m,
  field_simp [real.two_pi_pos.ne'] at hm,
  rw ← mul_right_inj' real.two_pi_pos.ne',
  linarith
end

/-! ### Measure on `add_circle T`

In this file we use the Haar measure on `add_circle T` normalised to have total measure 1 (which is
**not** the same as the standard measure defined in `topology.instances.add_circle`). -/

variables [hT : fact (0 < T)]
include hT

/-- Haar measure on the additive circle, normalised to have total measure 1. -/
@[derive is_add_haar_measure]
def haar_add_circle : measure (add_circle T) := add_haar_measure ⊤

instance : is_probability_measure (@haar_add_circle T _) :=
  is_probability_measure.mk add_haar_measure_self

lemma volume_eq_smul_haar_add_circle :
  (volume : measure (add_circle T)) = ennreal.of_real T • haar_add_circle := rfl

end add_circle

open add_circle

section monomials

/-- The family of exponential monomials `λ x, exp (2 π i n x / T)`, parametrized by `n : ℤ` and
considered as bundled continuous maps from `ℝ / ℤ • T` to `ℂ`. -/
def fourier (n : ℤ) : C(add_circle T, ℂ) :=
{ to_fun := λ x, to_circle (n • x),
  continuous_to_fun := continuous_induced_dom.comp $ continuous_to_circle.comp $ continuous_zsmul _}

@[simp] lemma fourier_apply {n : ℤ} {x : add_circle T} : fourier n x = to_circle (n • x) := rfl

@[simp] lemma fourier_coe_apply {n : ℤ} {x : ℝ} :
  fourier n (x : add_circle T) = complex.exp (2 * π * complex.I * n * x / T) :=
begin
  rw [fourier_apply, ←quotient_add_group.coe_zsmul, to_circle, function.periodic.lift_coe,
    exp_map_circle_apply, complex.of_real_mul, complex.of_real_div, complex.of_real_mul,
    zsmul_eq_mul, complex.of_real_mul, complex.of_real_int_cast, complex.of_real_bit0,
    complex.of_real_one],
  congr' 1, ring,
end

@[simp] lemma fourier_zero {x : add_circle T} : fourier 0 x = 1 :=
begin
  induction x using quotient_add_group.induction_on',
  simp only [fourier_coe_apply, algebra_map.coe_zero, mul_zero, zero_mul,
    zero_div, complex.exp_zero],
end

@[simp] lemma fourier_eval_zero (n : ℤ) : fourier n (0 : add_circle T) = 1 :=
by rw [←quotient_add_group.coe_zero, fourier_coe_apply, complex.of_real_zero,
  mul_zero, zero_div, complex.exp_zero]

@[simp] lemma fourier_one {x : add_circle T} : fourier 1 x = to_circle x :=
by rw [fourier_apply, one_zsmul]

@[simp] lemma fourier_neg {n : ℤ} {x : add_circle T} : fourier (-n) x = conj (fourier n x) :=
begin
  induction x using quotient_add_group.induction_on',
  simp_rw [fourier_apply, to_circle, ←quotient_add_group.coe_zsmul,
    function.periodic.lift_coe, ←coe_inv_circle_eq_conj, ←exp_map_circle_neg, neg_smul, mul_neg],
end

@[simp] lemma fourier_add {m n : ℤ} {x : add_circle T} :
  fourier (m + n) x = fourier m x * fourier n x :=
by simp_rw [fourier_apply, add_zsmul, to_circle_add, coe_mul_unit_sphere]

lemma fourier_norm [fact (0 < T)] (n : ℤ) : ‖@fourier T n‖ = 1 :=
begin
  rw continuous_map.norm_eq_supr_norm,
  have : ∀ (x : add_circle T), ‖fourier n x‖ = 1 := λ x, abs_coe_circle _,
  simp_rw this,
  exact @csupr_const _ _ _ has_zero.nonempty _,
end

/-- For `n ≠ 0`, a translation by `T / 2 / n` negates the function `fourier n`. -/
lemma fourier_add_half_inv_index {n : ℤ} (hn : n ≠ 0) (hT : 0 < T) (x : add_circle T) :
  fourier n (x + ((T / 2 / n) : ℝ)) = - fourier n x :=
begin
  rw [fourier_apply, zsmul_add, ←quotient_add_group.coe_zsmul, to_circle_add, coe_mul_unit_sphere],
  have : (n : ℂ) ≠ 0 := by simpa using hn,
  have : ((@to_circle T ((n • (T / 2 / n)) : ℝ)) : ℂ) = -1,
  { rw [zsmul_eq_mul, to_circle, function.periodic.lift_coe, exp_map_circle_apply],
    replace hT := complex.of_real_ne_zero.mpr hT.ne',
    convert complex.exp_pi_mul_I using 3,
    field_simp, ring, },
  rw this, simp,
end

/-- The subalgebra of `C(add_circle T, ℂ)` generated by `fourier n` for `n ∈ ℤ` . -/
def fourier_subalgebra : subalgebra ℂ C(add_circle T, ℂ) := algebra.adjoin ℂ (range fourier)

/-- The subalgebra of `C(add_circle T, ℂ)` generated by `fourier n` for `n ∈ ℤ` is in fact the
linear span of these functions. -/
lemma fourier_subalgebra_coe : (@fourier_subalgebra T).to_submodule = span ℂ (range fourier) :=
begin
  apply adjoin_eq_span_of_subset,
  refine subset.trans _ submodule.subset_span,
  intros x hx,
  apply submonoid.closure_induction hx (λ _, id) ⟨0, _⟩,
  { rintros _ _ ⟨m, rfl⟩ ⟨n, rfl⟩,
    refine ⟨m + n, _⟩,
    ext1 z,
    exact fourier_add },
  { ext1 z, exact fourier_zero }
end

/-- The subalgebra of `C(add_circle T, ℂ)` generated by `fourier n` for `n ∈ ℤ` is invariant under
complex conjugation. -/
lemma fourier_subalgebra_conj_invariant :
  conj_invariant_subalgebra ((@fourier_subalgebra T).restrict_scalars ℝ) :=
begin
  apply subalgebra_conj_invariant,
  rintros _ ⟨n, rfl⟩,
  exact ⟨-n, ext (λ _, fourier_neg)⟩
end

variables [hT : fact (0 < T)]
include hT

/-- The subalgebra of `C(add_circle T, ℂ)` generated by `fourier n` for `n ∈ ℤ`
separates points. -/
lemma fourier_subalgebra_separates_points : (@fourier_subalgebra T).separates_points :=
begin
  intros x y hxy,
  refine ⟨_, ⟨fourier 1, subset_adjoin ⟨1, rfl⟩, rfl⟩, _⟩,
  dsimp only, rw [fourier_one, fourier_one],
  contrapose! hxy,
  rw subtype.coe_inj at hxy,
  exact injective_to_circle hT.elim.ne' hxy,
end

/-- The subalgebra of `C(add_circle T, ℂ)` generated by `fourier n` for `n ∈ ℤ` is dense. -/
lemma fourier_subalgebra_closure_eq_top : (@fourier_subalgebra T).topological_closure = ⊤ :=
continuous_map.subalgebra_is_R_or_C_topological_closure_eq_top_of_separates_points
  fourier_subalgebra fourier_subalgebra_separates_points fourier_subalgebra_conj_invariant

/-- The linear span of the monomials `fourier n` is dense in `C(add_circle T, ℂ)`. -/
lemma span_fourier_closure_eq_top : (span ℂ (range $ @fourier T)).topological_closure = ⊤ :=
begin
  rw ← fourier_subalgebra_coe,
  exact congr_arg subalgebra.to_submodule fourier_subalgebra_closure_eq_top,
end

/-- The family of monomials `fourier n`, parametrized by `n : ℤ` and considered as
elements of the `Lp` space of functions `add_circle T → ℂ`. -/
abbreviation fourier_Lp (p : ℝ≥0∞) [fact (1 ≤ p)] (n : ℤ) : Lp ℂ p (@haar_add_circle T hT) :=
to_Lp p haar_add_circle ℂ (fourier n)

lemma coe_fn_fourier_Lp (p : ℝ≥0∞) [fact (1 ≤ p)] (n : ℤ) :
  (@fourier_Lp T hT p _ n) =ᵐ[haar_add_circle] fourier n := coe_fn_to_Lp haar_add_circle (fourier n)

/-- For each `1 ≤ p < ∞`, the linear span of the monomials `fourier n` is dense in
`Lp ℂ p haar_circle`. -/
lemma span_fourier_Lp_closure_eq_top {p : ℝ≥0∞} [fact (1 ≤ p)] (hp : p ≠ ∞) :
  (span ℂ (range (@fourier_Lp T _ p _))).topological_closure = ⊤ :=
begin
  convert (continuous_map.to_Lp_dense_range ℂ hp (@haar_add_circle T hT) ℂ
    ).topological_closure_map_submodule (span_fourier_closure_eq_top),
  rw [map_span, range_comp],
  simp only [continuous_linear_map.coe_coe],
end

/-- The monomials `fourier n` are an orthonormal set with respect to normalised Haar measure. -/
lemma orthonormal_fourier : orthonormal ℂ (@fourier_Lp T _ 2 _) :=
begin
  rw orthonormal_iff_ite,
  intros i j,
  rw continuous_map.inner_to_Lp (@haar_add_circle T hT) (fourier i) (fourier j),
  simp_rw [←fourier_neg, ←fourier_add],
  split_ifs,
  { simp_rw [h, neg_add_self],
    have : ⇑(@fourier T 0) = (λ x, 1 : (add_circle T) → ℂ),
    { ext1, exact fourier_zero },
    rw [this, integral_const, measure_univ, ennreal.one_to_real, complex.real_smul,
      complex.of_real_one, mul_one] },
  have hij : -i + j ≠ 0,
  { rw add_comm,
    exact sub_ne_zero.mpr (ne.symm h) },
  convert integral_eq_zero_of_add_right_eq_neg (fourier_add_half_inv_index hij hT.elim),
  exact is_add_left_invariant.is_add_right_invariant
end

end monomials

section scope_hT -- everything from here on needs `0 < T`
variables [hT : fact (0 < T)]
include hT

section fourier_coeff
variables {E : Type} [normed_add_comm_group E] [normed_space ℂ E] [complete_space E]

/-- The `n`-th Fourier coefficient of a function `add_circle T → E`, for `E` a complete normed
`ℂ`-vector space, defined as the integral over `add_circle T` of `fourier (-n) t • f t`. -/
def fourier_coeff (f : add_circle T → E) (n : ℤ) : E :=
∫ (t : add_circle T), fourier (-n) t • f t ∂ haar_add_circle

/-- The Fourier coefficients of a function on `add_circle T` can be computed as an integral
over `[a, a + T]`, for any real `a`. -/
lemma fourier_coeff_eq_interval_integral (f : add_circle T → E) (n : ℤ) (a : ℝ) :
  fourier_coeff f n = (1 / T) • ∫ x in a .. a + T, @fourier T (-n) x • f x :=
begin
  have : ∀ (x : ℝ), @fourier T (-n) x • f x = (λ (z : add_circle T), @fourier T (-n) z • f z) x,
  { intro x, refl, },
  simp_rw this,
  rw [fourier_coeff, add_circle.interval_integral_preimage T a,
    volume_eq_smul_haar_add_circle, integral_smul_measure, ennreal.to_real_of_real hT.out.le,
    ←smul_assoc, smul_eq_mul, one_div_mul_cancel hT.out.ne', one_smul],
end

lemma fourier_coeff.const_smul (f : add_circle T → E) (c : ℂ) (n : ℤ) :
  fourier_coeff (c • f) n = c • fourier_coeff f n :=
by simp_rw [fourier_coeff, pi.smul_apply, ←smul_assoc, smul_eq_mul, mul_comm, ←smul_eq_mul,
  smul_assoc, integral_smul]

lemma fourier_coeff.const_mul (f : add_circle T → ℂ) (c : ℂ) (n : ℤ) :
  fourier_coeff (λ x, c * f x) n = c * fourier_coeff f n :=
fourier_coeff.const_smul f c n

omit hT

/-- For a function on `ℝ`, the Fourier coefficients of `f` on `[a, b]` are defined as the
Fourier coefficients of the unique periodic function agreeing with `f` on `Ioc a b`. -/
def fourier_coeff_on {a b : ℝ} (hab : a < b) (f : ℝ → E) (n : ℤ) : E :=
begin
  haveI := fact.mk (by linarith : 0 < b - a),
  exact fourier_coeff (add_circle.lift_Ioc (b - a) a f) n
end

lemma fourier_coeff_on_eq_integral {a b : ℝ} (f : ℝ → E) (n : ℤ) (hab : a < b) :
  fourier_coeff_on hab f n =
  (1 / (b - a)) • ∫ x in a ..b, fourier (-n) (x : add_circle (b - a)) • f x :=
begin
  rw [fourier_coeff_on, fourier_coeff_eq_interval_integral _ _ a],
  congr' 1,
  rw [add_sub, add_sub_cancel'],
  simp_rw interval_integral.integral_of_le hab.le,
  refine set_integral_congr measurable_set_Ioc (λ x hx, _),
  dsimp only,
  rwa [lift_Ioc_coe_apply],
  rwa [add_sub, add_sub_cancel'],
end

lemma fourier_coeff_on.const_smul {a b : ℝ} (f : ℝ → E) (c : ℂ) (n : ℤ) (hab : a < b) :
  fourier_coeff_on hab (c • f) n = c • fourier_coeff_on hab f n :=
by apply fourier_coeff.const_smul

lemma fourier_coeff_on.const_mul {a b : ℝ} (f : ℝ → ℂ) (c : ℂ) (n : ℤ) (hab : a < b) :
  fourier_coeff_on hab (λ x, c * f x) n = c * fourier_coeff_on hab f n :=
fourier_coeff_on.const_smul _ _ _ _

include hT

lemma fourier_coeff_lift_Ioc_eq {a : ℝ} (f : ℝ → ℂ) (n : ℤ) :
  fourier_coeff (add_circle.lift_Ioc T a f) n =
  fourier_coeff_on (lt_add_of_pos_right a hT.out) f n :=
begin
  rw [fourier_coeff_on_eq_integral, fourier_coeff_eq_interval_integral, add_sub_cancel' a T],
  congr' 1,
  refine interval_integral.integral_congr_ae (ae_of_all _ (λ x hx, _)),
  rw lift_Ioc_coe_apply,
  rwa uIoc_of_le (lt_add_of_pos_right a hT.out).le at hx,
end

lemma fourier_coeff_lift_Ico_eq {a : ℝ} (f : ℝ → ℂ) (n : ℤ) :
  fourier_coeff (add_circle.lift_Ico T a f) n =
  fourier_coeff_on (lt_add_of_pos_right a hT.out) f n :=
begin
  rw [fourier_coeff_on_eq_integral, fourier_coeff_eq_interval_integral _ _ a, add_sub_cancel' a T],
  congr' 1,
  simp_rw [interval_integral.integral_of_le (lt_add_of_pos_right a hT.out).le,
    integral_Ioc_eq_integral_Ioo],
  refine set_integral_congr measurable_set_Ioo (λ x hx, _),
  dsimp only,
  rw lift_Ico_coe_apply (Ioo_subset_Ico_self hx),
end

end fourier_coeff

section fourier_L2

/-- We define `fourier_basis` to be a `ℤ`-indexed Hilbert basis for `Lp ℂ 2 haar_add_circle`,
which by definition is an isometric isomorphism from `Lp ℂ 2 haar_add_circle` to `ℓ²(ℤ, ℂ)`. -/
def fourier_basis : hilbert_basis ℤ ℂ (Lp ℂ 2 $ @haar_add_circle T hT) :=
hilbert_basis.mk orthonormal_fourier (span_fourier_Lp_closure_eq_top (by norm_num)).ge

/-- The elements of the Hilbert basis `fourier_basis` are the functions `fourier_Lp 2`, i.e. the
monomials `fourier n` on the circle considered as elements of `L²`. -/
@[simp] lemma coe_fourier_basis : ⇑(@fourier_basis _ hT) = fourier_Lp 2 := hilbert_basis.coe_mk _ _

/-- Under the isometric isomorphism `fourier_basis` from `Lp ℂ 2 haar_circle` to `ℓ²(ℤ, ℂ)`, the
`i`-th coefficient is `fourier_coeff f i`, i.e., the integral over `add_circle T` of
`λ t, fourier (-i) t * f t` with respect to the Haar measure of total mass 1. -/
lemma fourier_basis_repr (f : Lp ℂ 2 $ @haar_add_circle T hT) (i : ℤ) :
  fourier_basis.repr f i = fourier_coeff f i :=
begin
  transitivity ∫ (t : add_circle T),
    conj (((@fourier_Lp T hT 2 _ i) : add_circle T → ℂ) t) * f t ∂ haar_add_circle,
  { simp [fourier_basis.repr_apply_apply f i, measure_theory.L2.inner_def] },
  { apply integral_congr_ae,
    filter_upwards [coe_fn_fourier_Lp 2 i] with _ ht,
    rw [ht, ←fourier_neg, smul_eq_mul], }
end

/-- The Fourier series of an `L2` function `f` sums to `f`, in the `L²` space of `add_circle T`. -/
lemma has_sum_fourier_series_L2 (f : Lp ℂ 2 $ @haar_add_circle T hT) :
  has_sum (λ i, fourier_coeff f i • fourier_Lp 2 i) f :=
by { simp_rw ←fourier_basis_repr, simpa using hilbert_basis.has_sum_repr fourier_basis f }

/-- **Parseval's identity**: for an `L²` function `f` on `add_circle T`, the sum of the squared
norms of the Fourier coefficients equals the `L²` norm of `f`. -/
lemma tsum_sq_fourier_coeff (f : Lp ℂ 2 $ @haar_add_circle T hT) :
  ∑' i : ℤ, ‖fourier_coeff f i‖ ^ 2 = ∫ (t : add_circle T), ‖f t‖ ^ 2 ∂ haar_add_circle :=
begin
  simp_rw ←fourier_basis_repr,
  have H₁ : ‖fourier_basis.repr f‖ ^ 2 = ∑' i, ‖fourier_basis.repr f i‖ ^ 2,
  { exact_mod_cast lp.norm_rpow_eq_tsum _ (fourier_basis.repr f),
    norm_num },
  have H₂ : ‖fourier_basis.repr f‖ ^ 2 = ‖f‖ ^ 2 := by simp,
  have H₃ := congr_arg is_R_or_C.re (@L2.inner_def (add_circle T) ℂ ℂ _ _ _ _ _ f f),
  rw ← integral_re at H₃,
  { simp only [← norm_sq_eq_inner] at H₃,
    rw [← H₁, H₂, H₃], },
  { exact L2.integrable_inner f f },
end

end fourier_L2

section convergence

variables (f : C(add_circle T, ℂ))

lemma fourier_coeff_to_Lp (n : ℤ) :
  fourier_coeff (to_Lp 2 haar_add_circle ℂ f) n = fourier_coeff f n :=
integral_congr_ae (filter.eventually_eq.mul
  (filter.eventually_of_forall (by tauto))
  (continuous_map.coe_fn_to_ae_eq_fun haar_add_circle f))

variables {f}

/-- If the sequence of Fourier coefficients of `f` is summable, then the Fourier series converges
uniformly to `f`. -/
lemma has_sum_fourier_series_of_summable (h : summable (fourier_coeff f)) :
  has_sum (λ i, fourier_coeff f i • fourier i) f :=
begin
  have sum_L2 := has_sum_fourier_series_L2 (to_Lp 2 haar_add_circle ℂ f),
  simp_rw fourier_coeff_to_Lp at sum_L2,
  refine continuous_map.has_sum_of_has_sum_Lp (summable_of_summable_norm _) sum_L2,
  simp_rw [norm_smul, fourier_norm, mul_one, summable_norm_iff],
  exact h,
end

/-- If the sequence of Fourier coefficients of `f` is summable, then the Fourier series of `f`
converges everywhere pointwise to `f`. -/
lemma has_pointwise_sum_fourier_series_of_summable
  (h : summable (fourier_coeff f)) (x : add_circle T) :
  has_sum (λ i, fourier_coeff f i • fourier i x) (f x) :=
(continuous_map.eval_clm ℂ x).has_sum (has_sum_fourier_series_of_summable h)

end convergence

end scope_hT

section deriv

open complex interval_integral
open_locale interval

variables (T)

lemma has_deriv_at_fourier (n : ℤ) (x : ℝ) : has_deriv_at (λ y:ℝ, fourier n (y : add_circle T))
  (2 * π * I * n / T * fourier n (x : add_circle T)) x :=
begin
  simp_rw [fourier_coe_apply],
  refine (_ : has_deriv_at (λ y, exp (2 * π * I * n * y / T)) _ _).comp_of_real,
  rw (λ α β, by ring : ∀ (α β : ℂ), α * exp β = exp β * α),
  refine (has_deriv_at_exp _).comp x _,
  convert has_deriv_at_mul_const (2 * ↑π * I * ↑n / T),
  ext1 y, ring,
end

lemma has_deriv_at_fourier_neg (n : ℤ) (x : ℝ) :
  has_deriv_at (λ y:ℝ, fourier (-n) (y : add_circle T))
  (-2 * π * I * n / T * fourier (-n) (x : add_circle T)) x :=
by simpa using has_deriv_at_fourier T (-n) x

variables {T}

lemma has_antideriv_at_fourier_neg (hT : fact (0 < T)) {n : ℤ} (hn : n ≠ 0) (x : ℝ) :
  has_deriv_at (λ (y : ℝ), (T : ℂ) / (-2 * π * I * n) * fourier (-n) (y : add_circle T))
  (fourier (-n) (x : add_circle T)) x :=
begin
  convert (has_deriv_at_fourier_neg T n x).div_const (-2 * π * I * n / T) using 1,
  { ext1 y, rw div_div_eq_mul_div, ring, },
  { rw mul_div_cancel_left,
    simp only [ne.def, div_eq_zero_iff, neg_eq_zero, mul_eq_zero, bit0_eq_zero, one_ne_zero,
      of_real_eq_zero, false_or, int.cast_eq_zero, not_or_distrib],
    exact ⟨⟨⟨real.pi_ne_zero, I_ne_zero⟩, hn⟩, hT.out.ne'⟩ },
end

/-- Express Fourier coefficients of `f` on an interval in terms of those of its derivative. -/
lemma fourier_coeff_on_of_has_deriv_at {a b : ℝ} (hab : a < b) {f f' : ℝ → ℂ} {n : ℤ}
  (hn : n ≠ 0) (hf : ∀ x, x ∈ [a, b] → has_deriv_at f (f' x) x)
  (hf' : interval_integrable f' volume a b) :
  fourier_coeff_on hab f n =
  1 / (-2 * π * I * n) * (fourier (-n) (a : add_circle (b - a)) * (f b - f a)
    - (b - a) * fourier_coeff_on hab f' n) :=
begin
  rw ←of_real_sub,
  have hT : fact (0 < b - a) := ⟨by linarith⟩,
  simp_rw [fourier_coeff_on_eq_integral, smul_eq_mul, real_smul, of_real_div, of_real_one],
  conv { for (fourier _ _ * _) [1, 2, 3] { rw mul_comm } },
  rw integral_mul_deriv_eq_deriv_mul hf (λ x hx, has_antideriv_at_fourier_neg hT hn x) hf'
    (((map_continuous (fourier (-n))).comp (add_circle.continuous_mk' _)).interval_integrable _ _),
  dsimp only,
  have : ∀ (u v w : ℂ), u * ( (b - a : ℝ) / v * w) = (b - a : ℝ) / v * (u * w) := by {intros, ring},
  conv in (interval_integral _ _ _ _) { congr, funext, rw this, },
  rw (by ring : ((b - a : ℝ) : ℂ) / ((-2) * π * I * n)
    = ((b - a : ℝ) : ℂ) * (1 / ((-2) * π * I * n))),
  have s2 : (b : add_circle (b - a)) = (a : add_circle (b - a)),
  { simpa using coe_add_period (b - a) a, },
  rw [s2, integral_const_mul, ←sub_mul, mul_sub, mul_sub],
  congr' 1,
  { conv_lhs {rw [mul_comm, mul_div, mul_one]},
    rw [div_eq_iff (of_real_ne_zero.mpr hT.out.ne')],
    ring, },
  { ring, },
end

end deriv


/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Floris van Doorn

The (classical) real numbers ℝ. This is a direct construction
from Cauchy sequences.
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.order.conditionally_complete_lattice
import Mathlib.data.real.cau_seq_completion
import Mathlib.algebra.archimedean
import Mathlib.algebra.star.basic
import Mathlib.PostPort

namespace Mathlib

/-- The type `ℝ` of real numbers constructed as equivalence classes of Cauchy sequences of rational
numbers. -/
def real := cau_seq.completion.Cauchy

notation:1024 "ℝ" => Mathlib.real

namespace real

def comm_ring_aux : comm_ring ℝ := cau_seq.completion.Cauchy.comm_ring

protected instance comm_ring : comm_ring ℝ :=
  comm_ring.mk comm_ring.add comm_ring.add_assoc comm_ring.zero comm_ring.zero_add
    comm_ring.add_zero comm_ring.neg comm_ring.sub comm_ring.add_left_neg comm_ring.add_comm
    comm_ring.mul comm_ring.mul_assoc comm_ring.one comm_ring.one_mul comm_ring.mul_one
    comm_ring.left_distrib comm_ring.right_distrib comm_ring.mul_comm

/- Extra instances to short-circuit type class resolution -/

protected instance ring : ring ℝ := comm_ring.to_ring ℝ

protected instance comm_semiring : comm_semiring ℝ := comm_ring.to_comm_semiring

protected instance semiring : semiring ℝ := ring.to_semiring

protected instance add_comm_group : add_comm_group ℝ := ring.to_add_comm_group ℝ

protected instance add_group : add_group ℝ := add_comm_group.to_add_group ℝ

protected instance add_comm_monoid : add_comm_monoid ℝ := add_comm_group.to_add_comm_monoid ℝ

protected instance add_monoid : add_monoid ℝ := sub_neg_monoid.to_add_monoid ℝ

protected instance add_left_cancel_semigroup : add_left_cancel_semigroup ℝ :=
  add_left_cancel_monoid.to_add_left_cancel_semigroup ℝ

protected instance add_right_cancel_semigroup : add_right_cancel_semigroup ℝ :=
  add_right_cancel_monoid.to_add_right_cancel_semigroup ℝ

protected instance add_comm_semigroup : add_comm_semigroup ℝ :=
  add_comm_monoid.to_add_comm_semigroup ℝ

protected instance add_semigroup : add_semigroup ℝ := add_monoid.to_add_semigroup ℝ

protected instance comm_monoid : comm_monoid ℝ := comm_semiring.to_comm_monoid ℝ

protected instance monoid : monoid ℝ := ring.to_monoid ℝ

protected instance comm_semigroup : comm_semigroup ℝ := comm_ring.to_comm_semigroup ℝ

protected instance semigroup : semigroup ℝ := monoid.to_semigroup ℝ

protected instance inhabited : Inhabited ℝ := { default := 0 }

/-- The real numbers are a *-ring, with the trivial *-structure. -/
protected instance star_ring : star_ring ℝ := star_ring_of_comm

/-- Coercion `ℚ` → `ℝ` as a `ring_hom`. Note that this
is `cau_seq.completion.of_rat`, not `rat.cast`. -/
def of_rat : ℚ →+* ℝ := ring_hom.mk cau_seq.completion.of_rat sorry sorry sorry sorry

/-- Make a real number from a Cauchy sequence of rationals (by taking the equivalence class). -/
def mk (x : cau_seq ℚ abs) : ℝ := cau_seq.completion.mk x

theorem of_rat_sub (x : ℚ) (y : ℚ) : coe_fn of_rat (x - y) = coe_fn of_rat x - coe_fn of_rat y :=
  congr_arg mk (cau_seq.const_sub x y)

protected instance has_lt : HasLess ℝ :=
  { Less := fun (x y : ℝ) => quotient.lift_on₂ x y Less sorry }

@[simp] theorem mk_lt {f : cau_seq ℚ abs} {g : cau_seq ℚ abs} : mk f < mk g ↔ f < g := iff.rfl

theorem mk_eq {f : cau_seq ℚ abs} {g : cau_seq ℚ abs} : mk f = mk g ↔ f ≈ g :=
  cau_seq.completion.mk_eq

theorem quotient_mk_eq_mk (f : cau_seq ℚ abs) : quotient.mk f = mk f := rfl

theorem mk_eq_mk {f : cau_seq ℚ abs} : cau_seq.completion.mk f = mk f := rfl

@[simp] theorem mk_pos {f : cau_seq ℚ abs} : 0 < mk f ↔ cau_seq.pos f :=
  iff_of_eq (congr_arg cau_seq.pos (sub_zero f))

protected def le (x : ℝ) (y : ℝ) := x < y ∨ x = y

protected instance has_le : HasLessEq ℝ := { LessEq := real.le }

@[simp] theorem mk_le {f : cau_seq ℚ abs} {g : cau_seq ℚ abs} : mk f ≤ mk g ↔ f ≤ g :=
  or_congr iff.rfl quotient.eq

theorem add_lt_add_iff_left {a : ℝ} {b : ℝ} (c : ℝ) : c + a < c + b ↔ a < b := sorry

protected instance partial_order : partial_order ℝ := partial_order.mk LessEq Less sorry sorry sorry

protected instance preorder : preorder ℝ := partial_order.to_preorder ℝ

theorem of_rat_lt {x : ℚ} {y : ℚ} : coe_fn of_rat x < coe_fn of_rat y ↔ x < y := cau_seq.const_lt

protected theorem zero_lt_one : 0 < 1 := iff.mpr of_rat_lt zero_lt_one

protected theorem mul_pos {a : ℝ} {b : ℝ} : 0 < a → 0 < b → 0 < a * b := sorry

protected instance ordered_ring : ordered_ring ℝ :=
  ordered_ring.mk comm_ring.add comm_ring.add_assoc comm_ring.zero comm_ring.zero_add
    comm_ring.add_zero comm_ring.neg comm_ring.sub comm_ring.add_left_neg comm_ring.add_comm
    comm_ring.mul comm_ring.mul_assoc comm_ring.one comm_ring.one_mul comm_ring.mul_one
    comm_ring.left_distrib comm_ring.right_distrib partial_order.le partial_order.lt
    partial_order.le_refl partial_order.le_trans partial_order.le_antisymm sorry sorry real.mul_pos

protected instance ordered_semiring : ordered_semiring ℝ := ordered_ring.to_ordered_semiring

protected instance ordered_add_comm_group : ordered_add_comm_group ℝ :=
  ordered_ring.to_ordered_add_comm_group ℝ

protected instance ordered_cancel_add_comm_monoid : ordered_cancel_add_comm_monoid ℝ :=
  ordered_semiring.to_ordered_cancel_add_comm_monoid ℝ

protected instance ordered_add_comm_monoid : ordered_add_comm_monoid ℝ :=
  ordered_cancel_add_comm_monoid.to_ordered_add_comm_monoid

protected instance has_one : HasOne ℝ := monoid.to_has_one ℝ

protected instance has_zero : HasZero ℝ := mul_zero_class.to_has_zero ℝ

protected instance has_mul : Mul ℝ := distrib.to_has_mul ℝ

protected instance has_add : Add ℝ := distrib.to_has_add ℝ

protected instance has_sub : Sub ℝ := sub_neg_monoid.to_has_sub ℝ

protected instance nontrivial : nontrivial ℝ :=
  nontrivial.mk (Exists.intro 0 (Exists.intro 1 (ne_of_lt real.zero_lt_one)))

protected instance linear_order : linear_order ℝ :=
  linear_order.mk partial_order.le partial_order.lt partial_order.le_refl partial_order.le_trans
    partial_order.le_antisymm sorry (fun (a b : ℝ) => classical.prop_decidable (a ≤ b))
    Mathlib.decidable_eq_of_decidable_le Mathlib.decidable_lt_of_decidable_le

protected instance linear_ordered_comm_ring : linear_ordered_comm_ring ℝ :=
  linear_ordered_comm_ring.mk ordered_ring.add ordered_ring.add_assoc ordered_ring.zero
    ordered_ring.zero_add ordered_ring.add_zero ordered_ring.neg ordered_ring.sub
    ordered_ring.add_left_neg ordered_ring.add_comm ordered_ring.mul ordered_ring.mul_assoc
    ordered_ring.one ordered_ring.one_mul ordered_ring.mul_one ordered_ring.left_distrib
    ordered_ring.right_distrib ordered_ring.le ordered_ring.lt ordered_ring.le_refl
    ordered_ring.le_trans ordered_ring.le_antisymm ordered_ring.add_le_add_left
    ordered_ring.zero_le_one ordered_ring.mul_pos linear_order.le_total linear_order.decidable_le
    linear_order.decidable_eq linear_order.decidable_lt nontrivial.exists_pair_ne comm_ring.mul_comm

/- Extra instances to short-circuit type class resolution -/

protected instance linear_ordered_ring : linear_ordered_ring ℝ :=
  linear_ordered_comm_ring.to_linear_ordered_ring ℝ

protected instance linear_ordered_semiring : linear_ordered_semiring ℝ :=
  linear_ordered_comm_ring.to_linear_ordered_semiring

protected instance domain : domain ℝ :=
  domain.mk comm_ring.add comm_ring.add_assoc comm_ring.zero comm_ring.zero_add comm_ring.add_zero
    comm_ring.neg comm_ring.sub comm_ring.add_left_neg comm_ring.add_comm comm_ring.mul
    comm_ring.mul_assoc comm_ring.one comm_ring.one_mul comm_ring.mul_one comm_ring.left_distrib
    comm_ring.right_distrib nontrivial.exists_pair_ne sorry

protected instance linear_ordered_field : linear_ordered_field ℝ := sorry

/- Extra instances to short-circuit type class resolution -/

protected instance linear_ordered_add_comm_group : linear_ordered_add_comm_group ℝ :=
  linear_ordered_ring.to_linear_ordered_add_comm_group

protected instance field : field ℝ := linear_ordered_field.to_field ℝ

protected instance division_ring : division_ring ℝ := field.to_division_ring

protected instance integral_domain : integral_domain ℝ := field.to_integral_domain

protected instance distrib_lattice : distrib_lattice ℝ := Mathlib.distrib_lattice_of_linear_order

protected instance lattice : lattice ℝ := Mathlib.lattice_of_linear_order

protected instance semilattice_inf : semilattice_inf ℝ := lattice.to_semilattice_inf ℝ

protected instance semilattice_sup : semilattice_sup ℝ := lattice.to_semilattice_sup ℝ

protected instance has_inf : has_inf ℝ := semilattice_inf.to_has_inf ℝ

protected instance has_sup : has_sup ℝ := semilattice_sup.to_has_sup ℝ

protected instance decidable_lt (a : ℝ) (b : ℝ) : Decidable (a < b) := has_lt.lt.decidable a b

protected instance decidable_le (a : ℝ) (b : ℝ) : Decidable (a ≤ b) := has_le.le.decidable a b

protected instance decidable_eq (a : ℝ) (b : ℝ) : Decidable (a = b) := quotient.decidable_eq a b

@[simp] theorem of_rat_eq_cast (x : ℚ) : coe_fn of_rat x = ↑x := ring_hom.eq_rat_cast of_rat

theorem le_mk_of_forall_le {x : ℝ} {f : cau_seq ℚ abs} :
    (∃ (i : ℕ), ∀ (j : ℕ), j ≥ i → x ≤ ↑(coe_fn f j)) → x ≤ mk f :=
  sorry

theorem mk_le_of_forall_le {f : cau_seq ℚ abs} {x : ℝ} :
    (∃ (i : ℕ), ∀ (j : ℕ), j ≥ i → ↑(coe_fn f j) ≤ x) → mk f ≤ x :=
  sorry

theorem mk_near_of_forall_near {f : cau_seq ℚ abs} {x : ℝ} {ε : ℝ}
    (H : ∃ (i : ℕ), ∀ (j : ℕ), j ≥ i → abs (↑(coe_fn f j) - x) ≤ ε) : abs (mk f - x) ≤ ε :=
  sorry

protected instance archimedean : archimedean ℝ :=
  iff.mpr archimedean_iff_rat_le
    fun (x : ℝ) => quotient.induction_on x fun (f : cau_seq ℚ abs) => sorry

/- mark `real` irreducible in order to prevent `auto_cases` unfolding reals,
since users rarely want to consider real numbers as Cauchy sequences.
Marking `comm_ring_aux` `irreducible` is done to ensure that there are no problems
with non definitionally equal instances, caused by making `real` irreducible-/

protected instance floor_ring : floor_ring ℝ := archimedean.floor_ring ℝ

theorem is_cau_seq_iff_lift {f : ℕ → ℚ} : is_cau_seq abs f ↔ is_cau_seq abs fun (i : ℕ) => ↑(f i) :=
  sorry

theorem of_near (f : ℕ → ℚ) (x : ℝ)
    (h : ∀ (ε : ℝ), ε > 0 → ∃ (i : ℕ), ∀ (j : ℕ), j ≥ i → abs (↑(f j) - x) < ε) :
    ∃ (h' : is_cau_seq abs f), mk { val := f, property := h' } = x :=
  sorry

theorem exists_floor (x : ℝ) : ∃ (ub : ℤ), ↑ub ≤ x ∧ ∀ (z : ℤ), ↑z ≤ x → z ≤ ub := sorry

theorem exists_sup (S : set ℝ) :
    (∃ (x : ℝ), x ∈ S) →
        (∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x) →
          ∃ (x : ℝ), ∀ (y : ℝ), x ≤ y ↔ ∀ (z : ℝ), z ∈ S → z ≤ y :=
  sorry

protected instance has_Sup : has_Sup ℝ :=
  has_Sup.mk
    fun (S : set ℝ) =>
      dite ((∃ (x : ℝ), x ∈ S) ∧ ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x)
        (fun (h : (∃ (x : ℝ), x ∈ S) ∧ ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x) => classical.some sorry)
        fun (h : ¬((∃ (x : ℝ), x ∈ S) ∧ ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x)) => 0

theorem Sup_def (S : set ℝ) :
    Sup S =
        dite ((∃ (x : ℝ), x ∈ S) ∧ ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x)
          (fun (h : (∃ (x : ℝ), x ∈ S) ∧ ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x) =>
            classical.some (exists_sup S (and.left h) (and.right h)))
          fun (h : ¬((∃ (x : ℝ), x ∈ S) ∧ ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x)) => 0 :=
  rfl

theorem Sup_le (S : set ℝ) (h₁ : ∃ (x : ℝ), x ∈ S) (h₂ : ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x)
    {y : ℝ} : Sup S ≤ y ↔ ∀ (z : ℝ), z ∈ S → z ≤ y :=
  sorry

-- this proof times out without this

theorem lt_Sup (S : set ℝ) (h₁ : ∃ (x : ℝ), x ∈ S) (h₂ : ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x)
    {y : ℝ} : y < Sup S ↔ ∃ (z : ℝ), ∃ (H : z ∈ S), y < z :=
  sorry

theorem le_Sup (S : set ℝ) (h₂ : ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → y ≤ x) {x : ℝ} (xS : x ∈ S) :
    x ≤ Sup S :=
  iff.mp (Sup_le S (Exists.intro x xS) h₂) (le_refl (Sup S)) x xS

theorem Sup_le_ub (S : set ℝ) (h₁ : ∃ (x : ℝ), x ∈ S) {ub : ℝ} (h₂ : ∀ (y : ℝ), y ∈ S → y ≤ ub) :
    Sup S ≤ ub :=
  iff.mpr (Sup_le S h₁ (Exists.intro ub h₂)) h₂

protected theorem is_lub_Sup {s : set ℝ} {a : ℝ} {b : ℝ} (ha : a ∈ s) (hb : b ∈ upper_bounds s) :
    is_lub s (Sup s) :=
  { left := fun (x : ℝ) (xs : x ∈ s) => le_Sup s (Exists.intro b hb) xs,
    right := fun (u : ℝ) (h : u ∈ upper_bounds s) => Sup_le_ub s (Exists.intro a ha) h }

protected instance has_Inf : has_Inf ℝ :=
  has_Inf.mk fun (S : set ℝ) => -Sup (set_of fun (x : ℝ) => -x ∈ S)

theorem Inf_def (S : set ℝ) : Inf S = -Sup (set_of fun (x : ℝ) => -x ∈ S) := rfl

theorem le_Inf (S : set ℝ) (h₁ : ∃ (x : ℝ), x ∈ S) (h₂ : ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → x ≤ y)
    {y : ℝ} : y ≤ Inf S ↔ ∀ (z : ℝ), z ∈ S → y ≤ z :=
  sorry

-- this proof times out without this

theorem Inf_lt (S : set ℝ) (h₁ : ∃ (x : ℝ), x ∈ S) (h₂ : ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → x ≤ y)
    {y : ℝ} : Inf S < y ↔ ∃ (z : ℝ), ∃ (H : z ∈ S), z < y :=
  sorry

theorem Inf_le (S : set ℝ) (h₂ : ∃ (x : ℝ), ∀ (y : ℝ), y ∈ S → x ≤ y) {x : ℝ} (xS : x ∈ S) :
    Inf S ≤ x :=
  iff.mp (le_Inf S (Exists.intro x xS) h₂) (le_refl (Inf S)) x xS

theorem lb_le_Inf (S : set ℝ) (h₁ : ∃ (x : ℝ), x ∈ S) {lb : ℝ} (h₂ : ∀ (y : ℝ), y ∈ S → lb ≤ y) :
    lb ≤ Inf S :=
  iff.mpr (le_Inf S h₁ (Exists.intro lb h₂)) h₂

protected instance conditionally_complete_linear_order : conditionally_complete_linear_order ℝ :=
  conditionally_complete_linear_order.mk lattice.sup linear_order.le linear_order.lt
    linear_order.le_refl linear_order.le_trans linear_order.le_antisymm lattice.le_sup_left
    lattice.le_sup_right lattice.sup_le lattice.inf lattice.inf_le_left lattice.inf_le_right
    lattice.le_inf Sup Inf sorry sorry sorry sorry linear_order.le_total (classical.dec_rel LessEq)
    linear_order.decidable_eq linear_order.decidable_lt

theorem Sup_empty : Sup ∅ = 0 := sorry

theorem Sup_of_not_bdd_above {s : set ℝ} (hs : ¬bdd_above s) : Sup s = 0 :=
  dif_neg fun (h : (∃ (x : ℝ), x ∈ s) ∧ ∃ (x : ℝ), ∀ (y : ℝ), y ∈ s → y ≤ x) => hs (and.right h)

theorem Sup_univ : Sup set.univ = 0 := sorry

theorem Inf_empty : Inf ∅ = 0 := sorry

theorem Inf_of_not_bdd_below {s : set ℝ} (hs : ¬bdd_below s) : Inf s = 0 := sorry

theorem cau_seq_converges (f : cau_seq ℝ abs) : ∃ (x : ℝ), f ≈ cau_seq.const abs x := sorry

protected instance abs.cau_seq.is_complete : cau_seq.is_complete ℝ abs :=
  cau_seq.is_complete.mk cau_seq_converges

end Mathlib