/-
Copyright (c) 2022 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics
import data.real.basic -- imports the real numbers
import solutions.section02reals.sheet5 -- import a bunch of previous stuff

/-

# Harder questions

Here are some harder questions. Don't feel like you have
to do them. We've seen enough techniques to be able to do
all of these, but the truth is that we've seen a ton of stuff
in this course already, so probably you're not on top of all of
it yet, and furthermore we have not seen
some techniques which will enable you to cut corners. If you
want to become a real Lean expert then see how many of these
you can do. I will go through them all in a solutions video,
so if you like you can try some of them and then watch me
solving them.

Good luck!
-/

/-- If `a(n)` tends to `t` then `37 * a(n)` tends to `37 * t`-/
theorem tends_to_thirtyseven_mul (a : ℕ → ℝ) (t : ℝ) (h : tends_to a t) :
  tends_to (λ n, 37 * a n) (37 * t) :=
begin
  intros ε hε,
  obtain ⟨X, hX⟩ := h (ε/37) (by linarith),
  use X,
  intros n hn,
  specialize hX n hn,
  simp,
  rw [← mul_sub, abs_mul, abs_of_nonneg];
  linarith,
end

/-- If `a(n)` tends to `t` and `c` is a positive constant then
`c * a(n)` tends to `c * t`. -/
theorem tends_to_pos_const_mul {a : ℕ → ℝ} {t : ℝ} (h : tends_to a t)
  {c : ℝ} (hc : 0 < c) : tends_to (λ n, c * a n) (c * t) :=
begin
  intros ε hε,
  obtain ⟨X, hX⟩ := h (ε/c) (div_pos hε hc),
  use X,
  intros n hn,
  specialize hX n hn,
  simp,
  rw [← mul_sub, abs_mul, abs_of_pos hc],
  exact (lt_div_iff' hc).mp hX,
end

/-- If `a(n)` tends to `t` and `c` is a negative constant then
`c * a(n)` tends to `c * t`. -/
theorem tends_to_neg_const_mul {a : ℕ → ℝ} {t : ℝ} (h : tends_to a t)
  {c : ℝ} (hc : c < 0) : tends_to (λ n, c * a n) (c * t) :=
begin
  have hc' : 0 < -c := neg_pos.mpr hc,
  intros ε hε,
  obtain ⟨X, hX⟩ := h (ε/(-c)) (div_pos hε hc'),
  use X,
  intros n hn,
  specialize hX n hn,
  simp,
  rw [← mul_sub, abs_mul, abs_of_neg hc],
  exact (lt_div_iff' hc').mp hX,
end

/-- If `a(n)` tends to `t` and `c` is a constant then `c * a(n)` tends
to `c * t`. -/
theorem tends_to_const_mul {a : ℕ → ℝ} {t : ℝ} (c : ℝ) (h : tends_to a t) :
  tends_to (λ n, c * a n) (c * t) :=
begin
  obtain (hc | rfl | hc) := lt_trichotomy 0 c,
  { exact tends_to_pos_const_mul h hc },
  { simpa using tends_to_const 0 },
  { exact tends_to_neg_const_mul h hc }
end

/-- If `a(n)` tends to `t` and `c` is a constant then `a(n) * c` tends
to `t * c`. -/
theorem tends_to_mul_const {a : ℕ → ℝ} {t : ℝ} (c : ℝ) (h : tends_to a t) :
  tends_to (λ n, a n * c) (t * c) :=
by simpa [mul_comm] using tends_to_const_mul c h

-- another proof of this result, showcasing some tactics
-- which I've not covered yet.
theorem tends_to_neg' {a : ℕ → ℝ} {t : ℝ} (ha : tends_to a t) :
  tends_to (λ n, - a n) (-t) :=
begin
  simpa using tends_to_const_mul (-1) ha,
end

/-- If `a(n)-b(n)` tends to `t` and `b(n)` tends to `u` then
`a(n)` tends to `t + u`. -/
theorem tends_to_of_tends_to_sub {a b : ℕ → ℝ} {t u : ℝ}
  (h1 : tends_to (λ n, a n - b n) t) (h2 : tends_to b u) :
  tends_to a (t+u) :=
begin
  simpa using tends_to_add h1 h2,
end

/-- If `a(n)` tends to `t` then `a(n)-t` tends to `0`. -/
theorem tends_to_sub_lim_iff {a : ℕ → ℝ} {t : ℝ} :
  tends_to a t ↔ tends_to (λ n, a n - t) 0 :=
begin
  split,
  { intro h,
    simpa using tends_to_sub h (tends_to_const t) },
  { intro h,
    simpa using tends_to_add h (tends_to_const t) },
end

/-- If `a(n)` and `b(n)` both tend to zero, then their product tends
to zero. -/
theorem tends_to_zero_mul_tends_to_zero
  {a b : ℕ → ℝ} (ha : tends_to a 0) (hb : tends_to b 0) :
  tends_to (λ n, a n * b n) 0 :=
begin
  intros ε hε,
  obtain ⟨X, hX⟩ := ha ε hε,
  obtain ⟨Y, hY⟩ := hb 1 zero_lt_one,
  use max X Y,
  intros n hn,
  specialize hX n (le_of_max_le_left hn),
  specialize hY n (le_of_max_le_right hn),
  simpa [abs_mul] using mul_lt_mul'' hX hY _ _;
  exact abs_nonneg _,
end

/-- If `a(n)` tends to `t` and `b(n)` tends to `u` then
`a(n)*b(n)` tends to `t*u`. -/
theorem tends_to_mul (a b : ℕ → ℝ) (t u : ℝ) (ha : tends_to a t)
  (hb : tends_to b u) : tends_to (λ n, a n * b n) (t * u) :=
begin
  -- suffices a(n)b(n)-tu -> 0
  rw tends_to_sub_lim_iff at *,
  -- rewrite as (a(n)-t)*(b(n)-u)+t(b(n)-u)+(a(n)-t)u ->0
  have h : ∀ n, a n * b n - t * u =
    (a n - t) * (b n - u) + t * (b n - u) + (a n - t) * u,
  { intro n, ring },
  simp [h],
  rw (show (0 : ℝ) = 0 + (t * 0) + (0 * u), by simp),
  refine tends_to_add (tends_to_add _ _) _,
  { exact tends_to_zero_mul_tends_to_zero ha hb },
  { exact tends_to_const_mul t hb },
  { exact tends_to_mul_const u ha },
end

-- something we never used!
/-- A sequence has at most one limit. -/
theorem tends_to_unique (a : ℕ → ℝ) (s t : ℝ)
  (hs : tends_to a s) (ht : tends_to a t) : s = t :=
begin
  by_contra h,
  wlog h2 : s < t,
  { exact ne.lt_or_lt h },
  set ε := t - s with hε,
  have hε : 0 < ε := sub_pos.mpr h2,
  obtain ⟨X, hX⟩ := hs (ε/2) (by linarith),
  obtain ⟨Y, hY⟩ := ht (ε/2) (by linarith),
  specialize hX (max X Y) (le_max_left X Y),
  specialize hY (max X Y) (le_max_right X Y),
  rw abs_lt at hX hY,
  linarith,
end

-- bonus solution: another proof of tends_to_unique inspired
-- by a comment on YT!

-- If |r|<ε for all ε>0 then r=0
lemma eq_zero_of_abs_lt_eps {r : ℝ} (h : ∀ ε > 0, |r| < ε) : r = 0 :=
begin
  -- Proof by contradiction. Say r ≠ 0.
  by_contra h2,
  -- Then let ε be |r|, and we must have ε > 0.
  specialize h (|r|) (abs_pos.mpr h2),
  -- Deduce |r|<|r|, a contradiction.
  linarith,
end

-- Second proof
theorem tends_to_unique' (a : ℕ → ℝ) (s t : ℝ)
  (hs : tends_to a s) (ht : tends_to a t) : s = t :=
begin
  -- We know a - a tends to s - t because of `tends_to_sub`
  have h := tends_to_sub hs ht,
  -- We want to prove s = t; by previous result suffices to
  -- show |t - s|<ε for all ε>0
  suffices : ∀ ε > 0, |t - s| < ε,
  { linarith [eq_zero_of_abs_lt_eps this] },
  -- Let ε be positive.
  intros ε hε,
  -- There exists X such that for n>=X, |a(n) - a(n) - (s - t)| < ε
  obtain ⟨X, hX⟩ := h ε hε,
  specialize hX X (by refl),
  -- and the simplifier can now reduce that to the goal |t-s|<ε.
  simpa using hX,
end


/-
Copyright (c) 2021 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel
-/
import algebra.category.Module.epi_mono
import algebra.category.Module.kernels
import category_theory.subobject.well_powered
import category_theory.subobject.limits

/-!
# Subobjects in the category of `R`-modules

We construct an explicit order isomorphism between the categorical subobjects of an `R`-module `M`
and its submodules. This immediately implies that the category of `R`-modules is well-powered.

-/

open category_theory
open category_theory.subobject
open category_theory.limits

open_locale Module

universes v u

namespace Module
variables {R : Type u} [ring R] (M : Module.{v} R)

/-- The categorical subobjects of a module `M` are in one-to-one correspondence with its
    submodules.-/
noncomputable def subobject_Module : subobject M ≃o submodule R M := order_iso.symm
({ inv_fun := λ S, S.arrow.range,
  to_fun := λ N, subobject.mk ↾N.subtype,
  right_inv := λ S, eq.symm
  begin
    fapply eq_mk_of_comm,
    { apply linear_equiv.to_Module_iso'_left,
      apply linear_equiv.of_bijective (linear_map.cod_restrict S.arrow.range S.arrow _),
      split,
      { simpa only [← linear_map.ker_eq_bot, linear_map.ker_cod_restrict]
          using ker_eq_bot_of_mono _ },
      { rw [← linear_map.range_eq_top, linear_map.range_cod_restrict,
          submodule.comap_subtype_self] },
      { exact linear_map.mem_range_self _ } },
    { apply linear_map.ext,
      intros x,
      refl }
  end,
  left_inv := λ N,
  begin
    convert congr_arg linear_map.range (underlying_iso_arrow ↾N.subtype) using 1,
    { have : (underlying_iso ↾N.subtype).inv = (underlying_iso ↾N.subtype).symm.to_linear_equiv,
      { apply linear_map.ext,
        intros x,
        refl },
      rw [this, comp_def, linear_equiv.range_comp] },
    { exact (submodule.range_subtype _).symm }
  end,
  map_rel_iff' := λ S T,
  begin
    refine ⟨λ h, _, λ h, mk_le_mk_of_comm ↟(submodule.of_le h) (by { ext, refl })⟩,
    convert linear_map.range_comp_le_range (of_mk_le_mk _ _ h) ↾T.subtype,
    { simpa only [←comp_def, of_mk_le_mk_comp] using (submodule.range_subtype _).symm },
    { exact (submodule.range_subtype _).symm }
  end })

instance well_powered_Module : well_powered (Module.{v} R) :=
⟨λ M, ⟨⟨_, ⟨(subobject_Module M).to_equiv⟩⟩⟩⟩

local attribute [instance] has_kernels_Module

/-- Bundle an element `m : M` such that `f m = 0` as a term of `kernel_subobject f`. -/
noncomputable def to_kernel_subobject {M N : Module R} {f : M ⟶ N} :
  linear_map.ker f →ₗ[R] kernel_subobject f :=
(kernel_subobject_iso f ≪≫ Module.kernel_iso_ker f).inv

@[simp] lemma to_kernel_subobject_arrow {M N : Module R} {f : M ⟶ N} (x : linear_map.ker f) :
  (kernel_subobject f).arrow (to_kernel_subobject x) = x.1 :=
by simp [to_kernel_subobject]

/--
An extensionality lemma showing that two elements of a cokernel by an image
are equal if they differ by an element of the image.

The application is for homology:
two elements in homology are equal if they differ by a boundary.
-/
@[ext]
lemma cokernel_π_image_subobject_ext {L M N : Module.{v} R}
  (f : L ⟶ M) [has_image f] (g : (image_subobject f : Module.{v} R) ⟶ N) [has_cokernel g]
  {x y : N} (l : L) (w : x = y + g (factor_thru_image_subobject f l)) :
  cokernel.π g x = cokernel.π g y :=
by { subst w, simp, }

end Module


def half : Nat → Nat
| 0     => 0
| 1     => 0
| (x+2) => half x + 1

theorem half0 : half 0 = 0 :=
rfl

theorem half1 : half 1 = 0 :=
rfl

theorem half_succ_succ (a : Nat) : half (a + 2) = half a + 1 :=
rfl

example : half 5 = 2 :=
rfl

example : half 8 = 4 :=
rfl


namespace hidden

variables {α : Type*} (r : α → α → Prop)

definition reflexive : Prop := ∀ (a : α), r a a
definition symmetric : Prop := ∀ ⦃a b : α⦄, r a b → r b a
definition transitive : Prop := ∀ ⦃a b c : α⦄, r a b → r b c → r a c
definition euclidean : Prop := ∀ ⦃a b c : α⦄, r a b → r a c → r b c

variable {r}

theorem th1 (reflr : reflexive r) (euclr : euclidean r) : symmetric r :=
assume a b : α,
assume : r a b,
show r b a, from euclr this (reflr _)

theorem th2 (symmr : symmetric r) (euclr : euclidean r) : transitive r :=
assume a b c : α,
assume (rab : r a b) (rbc : r b c),
show r a c, from euclr (symmr rab) rbc

theorem th3 (reflr : reflexive r) (euclr : euclidean r) : transitive r :=
th2 (th1 reflr euclr) euclr

end hidden


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

! This file was ported from Lean 3 source module measure_theory.measure.portmanteau
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.MeasureTheory.Measure.ProbabilityMeasure
import Mathbin.MeasureTheory.Measure.Lebesgue

/-!
# Characterizations of weak convergence of finite measures and probability measures

This file will provide portmanteau characterizations of the weak convergence of finite measures
and of probability measures, i.e., the standard characterizations of convergence in distribution.

## Main definitions

This file does not introduce substantial new definitions: the topologies of weak convergence on
the types of finite measures and probability measures are already defined in their corresponding
files.

## Main results

The main result will be the portmanteau theorem providing various characterizations of the
weak convergence of measures. The separate implications are:
 * `measure_theory.finite_measure.limsup_measure_closed_le_of_tendsto` proves that weak convergence
   implies a limsup-condition for closed sets.
 * `measure_theory.limsup_measure_closed_le_iff_liminf_measure_open_ge` proves for probability
   measures the equivalence of the limsup condition for closed sets and the liminf condition for
   open sets.
 * `measure_theory.tendsto_measure_of_null_frontier` proves that the liminf condition for open
   sets (which is equivalent to the limsup condition for closed sets) implies the convergence of
   probabilities of sets whose boundary carries no mass under the limit measure.
 * `measure_theory.probability_measure.tendsto_measure_of_null_frontier_of_tendsto` is a
   combination of earlier implications, which shows that weak convergence of probability measures
   implies the convergence of probabilities of sets whose boundary carries no mass under the
   limit measure.

TODO:
 * Prove the rest of the implications.

## Implementation notes

Many of the characterizations of weak convergence hold for finite measures and are proven in that
generality and then specialized to probability measures. Some implications hold with slightly
weaker assumptions than usually stated. The full portmanteau theorem, however, is most convenient
for probability measures on metrizable spaces with their Borel sigmas.

Some specific considerations on the assumptions in the different implications:
 * `measure_theory.finite_measure.limsup_measure_closed_le_of_tendsto` assumes
   `pseudo_emetric_space`. The only reason is to have bounded continuous pointwise approximations
   to the indicator function of a closed set. Clearly for example metrizability or
   pseudo-emetrizability would be sufficient assumptions. The typeclass assumptions should be later
   adjusted in a way that takes into account use cases, but the proof will presumably remain
   essentially the same.
 * Where formulations are currently only provided for probability measures, one can obtain the
   finite measure formulations using the characterization of convergence of finite measures by
   their total masses and their probability-normalized versions, i.e., by
   `measure_theory.finite_measure.tendsto_normalize_iff_tendsto`.

## References

* [Billingsley, *Convergence of probability measures*][billingsley1999]

## Tags

weak convergence of measures, convergence in distribution, convergence in law, finite measure,
probability measure

-/

noncomputable section

open MeasureTheory

open Set

open Filter

open BoundedContinuousFunction

open Topology ENNReal NNReal BoundedContinuousFunction

namespace MeasureTheory

section LimsupClosedLeAndLeLiminfOpen

/-! ### Portmanteau: limsup condition for closed sets iff liminf condition for open sets

In this section we prove that for a sequence of Borel probability measures on a topological space
and its candidate limit measure, the following two conditions are equivalent:
  (C) For any closed set `F` in `Ω` the limsup of the measures of `F` is at most the limit
      measure of `F`.
  (O) For any open set `G` in `Ω` the liminf of the measures of `G` is at least the limit
      measure of `G`.
Either of these will later be shown to be equivalent to the weak convergence of the sequence
of measures.
-/

variable {Ω : Type _} [MeasurableSpace Ω]

theorem le_measure_compl_liminf_of_limsup_measure_le {ι : Type _} {L : Filter ι} {μ : Measure Ω}
    {μs : ι → Measure Ω} [IsProbabilityMeasure μ] [∀ i, IsProbabilityMeasure (μs i)] {E : Set Ω}
    (E_mble : MeasurableSet E) (h : (L.limsup fun i => μs i E) ≤ μ E) :
    μ (Eᶜ) ≤ L.liminf fun i => μs i (Eᶜ) :=
  by
  by_cases L_bot : L = ⊥
  ·
    simp only [L_bot, le_top,
      show liminf (fun i => μs i (Eᶜ)) ⊥ = ⊤ by simp only [liminf, Filter.map_bot, Liminf_bot]]
  have : L.ne_bot := { ne' := L_bot }
  have meas_Ec : μ (Eᶜ) = 1 - μ E := by
    simpa only [measure_univ] using measure_compl E_mble (measure_lt_top μ E).Ne
  have meas_i_Ec : ∀ i, μs i (Eᶜ) = 1 - μs i E :=
    by
    intro i
    simpa only [measure_univ] using measure_compl E_mble (measure_lt_top (μs i) E).Ne
  simp_rw [meas_Ec, meas_i_Ec]
  have obs :
    (L.liminf fun i : ι => 1 - μs i E) = L.liminf ((fun x => 1 - x) ∘ fun i : ι => μs i E) := by rfl
  rw [obs]
  simp_rw [←
    antitone_const_tsub.map_limsup_of_continuous_at (fun i => μs i E)
      (ENNReal.continuous_sub_left ENNReal.one_ne_top).ContinuousAt]
  exact antitone_const_tsub h
#align measure_theory.le_measure_compl_liminf_of_limsup_measure_le MeasureTheory.le_measure_compl_liminf_of_limsup_measure_le

theorem le_measure_liminf_of_limsup_measure_compl_le {ι : Type _} {L : Filter ι} {μ : Measure Ω}
    {μs : ι → Measure Ω} [IsProbabilityMeasure μ] [∀ i, IsProbabilityMeasure (μs i)] {E : Set Ω}
    (E_mble : MeasurableSet E) (h : (L.limsup fun i => μs i (Eᶜ)) ≤ μ (Eᶜ)) :
    μ E ≤ L.liminf fun i => μs i E :=
  compl_compl E ▸ le_measure_compl_liminf_of_limsup_measure_le (MeasurableSet.compl E_mble) h
#align measure_theory.le_measure_liminf_of_limsup_measure_compl_le MeasureTheory.le_measure_liminf_of_limsup_measure_compl_le

theorem limsup_measure_compl_le_of_le_liminf_measure {ι : Type _} {L : Filter ι} {μ : Measure Ω}
    {μs : ι → Measure Ω} [IsProbabilityMeasure μ] [∀ i, IsProbabilityMeasure (μs i)] {E : Set Ω}
    (E_mble : MeasurableSet E) (h : μ E ≤ L.liminf fun i => μs i E) :
    (L.limsup fun i => μs i (Eᶜ)) ≤ μ (Eᶜ) :=
  by
  by_cases L_bot : L = ⊥
  ·
    simp only [L_bot, bot_le,
      show limsup (fun i => μs i (Eᶜ)) ⊥ = ⊥ by simp only [limsup, Filter.map_bot, Limsup_bot]]
  have : L.ne_bot := { ne' := L_bot }
  have meas_Ec : μ (Eᶜ) = 1 - μ E := by
    simpa only [measure_univ] using measure_compl E_mble (measure_lt_top μ E).Ne
  have meas_i_Ec : ∀ i, μs i (Eᶜ) = 1 - μs i E :=
    by
    intro i
    simpa only [measure_univ] using measure_compl E_mble (measure_lt_top (μs i) E).Ne
  simp_rw [meas_Ec, meas_i_Ec]
  have obs :
    (L.limsup fun i : ι => 1 - μs i E) = L.limsup ((fun x => 1 - x) ∘ fun i : ι => μs i E) := by rfl
  rw [obs]
  simp_rw [←
    antitone_const_tsub.map_liminf_of_continuous_at (fun i => μs i E)
      (ENNReal.continuous_sub_left ENNReal.one_ne_top).ContinuousAt]
  exact antitone_const_tsub h
#align measure_theory.limsup_measure_compl_le_of_le_liminf_measure MeasureTheory.limsup_measure_compl_le_of_le_liminf_measure

theorem limsup_measure_le_of_le_liminf_measure_compl {ι : Type _} {L : Filter ι} {μ : Measure Ω}
    {μs : ι → Measure Ω} [IsProbabilityMeasure μ] [∀ i, IsProbabilityMeasure (μs i)] {E : Set Ω}
    (E_mble : MeasurableSet E) (h : μ (Eᶜ) ≤ L.liminf fun i => μs i (Eᶜ)) :
    (L.limsup fun i => μs i E) ≤ μ E :=
  compl_compl E ▸ limsup_measure_compl_le_of_le_liminf_measure (MeasurableSet.compl E_mble) h
#align measure_theory.limsup_measure_le_of_le_liminf_measure_compl MeasureTheory.limsup_measure_le_of_le_liminf_measure_compl

variable [TopologicalSpace Ω] [OpensMeasurableSpace Ω]

/-- One pair of implications of the portmanteau theorem:
For a sequence of Borel probability measures, the following two are equivalent:

(C) The limsup of the measures of any closed set is at most the measure of the closed set
under a candidate limit measure.

(O) The liminf of the measures of any open set is at least the measure of the open set
under a candidate limit measure.
-/
theorem limsup_measure_closed_le_iff_liminf_measure_open_ge {ι : Type _} {L : Filter ι}
    {μ : Measure Ω} {μs : ι → Measure Ω} [IsProbabilityMeasure μ]
    [∀ i, IsProbabilityMeasure (μs i)] :
    (∀ F, IsClosed F → (L.limsup fun i => μs i F) ≤ μ F) ↔
      ∀ G, IsOpen G → μ G ≤ L.liminf fun i => μs i G :=
  by
  constructor
  · intro h G G_open
    exact
      le_measure_liminf_of_limsup_measure_compl_le G_open.measurable_set
        (h (Gᶜ) (is_closed_compl_iff.mpr G_open))
  · intro h F F_closed
    exact
      limsup_measure_le_of_le_liminf_measure_compl F_closed.measurable_set
        (h (Fᶜ) (is_open_compl_iff.mpr F_closed))
#align measure_theory.limsup_measure_closed_le_iff_liminf_measure_open_ge MeasureTheory.limsup_measure_closed_le_iff_liminf_measure_open_ge

end LimsupClosedLeAndLeLiminfOpen

-- section
section TendstoOfNullFrontier

/-! ### Portmanteau: limit of measures of Borel sets whose boundary carries no mass in the limit

In this section we prove that for a sequence of Borel probability measures on a topological space
and its candidate limit measure, either of the following equivalent conditions:
  (C) For any closed set `F` in `Ω` the limsup of the measures of `F` is at most the limit
      measure of `F`
  (O) For any open set `G` in `Ω` the liminf of the measures of `G` is at least the limit
      measure of `G`
implies that
  (B) For any Borel set `E` in `Ω` whose boundary `∂E` carries no mass under the candidate limit
      measure, we have that the limit of measures of `E` is the measure of `E` under the
      candidate limit measure.
-/

variable {Ω : Type _} [MeasurableSpace Ω]

theorem tendsto_measure_of_le_liminf_measure_of_limsup_measure_le {ι : Type _} {L : Filter ι}
    {μ : Measure Ω} {μs : ι → Measure Ω} {E₀ E E₁ : Set Ω} (E₀_subset : E₀ ⊆ E) (subset_E₁ : E ⊆ E₁)
    (nulldiff : μ (E₁ \ E₀) = 0) (h_E₀ : μ E₀ ≤ L.liminf fun i => μs i E₀)
    (h_E₁ : (L.limsup fun i => μs i E₁) ≤ μ E₁) : L.Tendsto (fun i => μs i E) (𝓝 (μ E)) :=
  by
  apply tendsto_of_le_liminf_of_limsup_le
  · have E₀_ae_eq_E : E₀ =ᵐ[μ] E :=
      eventually_le.antisymm E₀_subset.eventually_le
        (subset_E₁.eventually_le.trans (ae_le_set.mpr nulldiff))
    calc
      μ E = μ E₀ := measure_congr E₀_ae_eq_E.symm
      _ ≤ L.liminf fun i => μs i E₀ := h_E₀
      _ ≤ L.liminf fun i => μs i E := _

    · refine' liminf_le_liminf (eventually_of_forall fun _ => measure_mono E₀_subset) _
      infer_param
  · have E_ae_eq_E₁ : E =ᵐ[μ] E₁ :=
      eventually_le.antisymm subset_E₁.eventually_le
        ((ae_le_set.mpr nulldiff).trans E₀_subset.eventually_le)
    calc
      (L.limsup fun i => μs i E) ≤ L.limsup fun i => μs i E₁ := _
      _ ≤ μ E₁ := h_E₁
      _ = μ E := measure_congr E_ae_eq_E₁.symm

    · refine' limsup_le_limsup (eventually_of_forall fun _ => measure_mono subset_E₁) _
      infer_param
#align measure_theory.tendsto_measure_of_le_liminf_measure_of_limsup_measure_le MeasureTheory.tendsto_measure_of_le_liminf_measure_of_limsup_measure_le

variable [TopologicalSpace Ω] [OpensMeasurableSpace Ω]

/-- One implication of the portmanteau theorem:
For a sequence of Borel probability measures, if the liminf of the measures of any open set is at
least the measure of the open set under a candidate limit measure, then for any set whose
boundary carries no probability mass under the candidate limit measure, then its measures under the
sequence converge to its measure under the candidate limit measure.
-/
theorem tendsto_measure_of_null_frontier {ι : Type _} {L : Filter ι} {μ : Measure Ω}
    {μs : ι → Measure Ω} [IsProbabilityMeasure μ] [∀ i, IsProbabilityMeasure (μs i)]
    (h_opens : ∀ G, IsOpen G → μ G ≤ L.liminf fun i => μs i G) {E : Set Ω}
    (E_nullbdry : μ (frontier E) = 0) : L.Tendsto (fun i => μs i E) (𝓝 (μ E)) :=
  haveI h_closeds : ∀ F, IsClosed F → (L.limsup fun i => μs i F) ≤ μ F :=
    limsup_measure_closed_le_iff_liminf_measure_open_ge.mpr h_opens
  tendsto_measure_of_le_liminf_measure_of_limsup_measure_le interior_subset subset_closure
    E_nullbdry (h_opens _ isOpen_interior) (h_closeds _ isClosed_closure)
#align measure_theory.tendsto_measure_of_null_frontier MeasureTheory.tendsto_measure_of_null_frontier

end TendstoOfNullFrontier

--section
section ConvergenceImpliesLimsupClosedLe

/-! ### Portmanteau implication: weak convergence implies a limsup condition for closed sets

In this section we prove, under the assumption that the underlying topological space `Ω` is
pseudo-emetrizable, that the weak convergence of measures on `measure_theory.finite_measure Ω`
implies that for any closed set `F` in `Ω` the limsup of the measures of `F` is at most the
limit measure of `F`. This is one implication of the portmanteau theorem characterizing weak
convergence of measures.

Combining with an earlier implication we also get that weak convergence implies that for any Borel
set `E` in `Ω` whose boundary `∂E` carries no mass under the limit measure, the limit of measures
of `E` is the measure of `E` under the limit measure.
-/

variable {Ω : Type _} [MeasurableSpace Ω]

/-- If bounded continuous functions tend to the indicator of a measurable set and are
uniformly bounded, then their integrals against a finite measure tend to the measure of the set.
This formulation assumes:
 * the functions tend to a limit along a countably generated filter;
 * the limit is in the almost everywhere sense;
 * boundedness holds almost everywhere.
-/
theorem measure_of_cont_bdd_of_tendsto_filter_indicator {ι : Type _} {L : Filter ι}
    [L.IsCountablyGenerated] [TopologicalSpace Ω] [OpensMeasurableSpace Ω] (μ : Measure Ω)
    [IsFiniteMeasure μ] {c : ℝ≥0} {E : Set Ω} (E_mble : MeasurableSet E) (fs : ι → Ω →ᵇ ℝ≥0)
    (fs_bdd : ∀ᶠ i in L, ∀ᵐ ω : Ω ∂μ, fs i ω ≤ c)
    (fs_lim :
      ∀ᵐ ω : Ω ∂μ,
        Tendsto (fun i : ι => (coeFn : (Ω →ᵇ ℝ≥0) → Ω → ℝ≥0) (fs i) ω) L
          (𝓝 (indicator E (fun x => (1 : ℝ≥0)) ω))) :
    Tendsto (fun n => lintegral μ fun ω => fs n ω) L (𝓝 (μ E)) :=
  by
  convert finite_measure.tendsto_lintegral_nn_filter_of_le_const μ fs_bdd fs_lim
  have aux : ∀ ω, indicator E (fun ω => (1 : ℝ≥0∞)) ω = ↑(indicator E (fun ω => (1 : ℝ≥0)) ω) :=
    fun ω => by simp only [ENNReal.coe_indicator, ENNReal.coe_one]
  simp_rw [← aux, lintegral_indicator _ E_mble]
  simp only [lintegral_one, measure.restrict_apply, MeasurableSet.univ, univ_inter]
#align measure_theory.measure_of_cont_bdd_of_tendsto_filter_indicator MeasureTheory.measure_of_cont_bdd_of_tendsto_filter_indicator

/-- If a sequence of bounded continuous functions tends to the indicator of a measurable set and
the functions are uniformly bounded, then their integrals against a finite measure tend to the
measure of the set.

A similar result with more general assumptions is
`measure_theory.measure_of_cont_bdd_of_tendsto_filter_indicator`.
-/
theorem measure_of_cont_bdd_of_tendsto_indicator [TopologicalSpace Ω] [OpensMeasurableSpace Ω]
    (μ : Measure Ω) [IsFiniteMeasure μ] {c : ℝ≥0} {E : Set Ω} (E_mble : MeasurableSet E)
    (fs : ℕ → Ω →ᵇ ℝ≥0) (fs_bdd : ∀ n ω, fs n ω ≤ c)
    (fs_lim :
      Tendsto (fun n : ℕ => (coeFn : (Ω →ᵇ ℝ≥0) → Ω → ℝ≥0) (fs n)) atTop
        (𝓝 (indicator E fun x => (1 : ℝ≥0)))) :
    Tendsto (fun n => lintegral μ fun ω => fs n ω) atTop (𝓝 (μ E)) :=
  by
  have fs_lim' :
    ∀ ω, tendsto (fun n : ℕ => (fs n ω : ℝ≥0)) at_top (𝓝 (indicator E (fun x => (1 : ℝ≥0)) ω)) :=
    by
    rw [tendsto_pi_nhds] at fs_lim
    exact fun ω => fs_lim ω
  apply
    measure_of_cont_bdd_of_tendsto_filter_indicator μ E_mble fs
      (eventually_of_forall fun n => eventually_of_forall (fs_bdd n)) (eventually_of_forall fs_lim')
#align measure_theory.measure_of_cont_bdd_of_tendsto_indicator MeasureTheory.measure_of_cont_bdd_of_tendsto_indicator

/-- The integrals of thickened indicators of a closed set against a finite measure tend to the
measure of the closed set if the thickening radii tend to zero.
-/
theorem tendsto_lintegral_thickenedIndicator_of_isClosed {Ω : Type _} [MeasurableSpace Ω]
    [PseudoEMetricSpace Ω] [OpensMeasurableSpace Ω] (μ : Measure Ω) [IsFiniteMeasure μ] {F : Set Ω}
    (F_closed : IsClosed F) {δs : ℕ → ℝ} (δs_pos : ∀ n, 0 < δs n)
    (δs_lim : Tendsto δs atTop (𝓝 0)) :
    Tendsto (fun n => lintegral μ fun ω => (thickenedIndicator (δs_pos n) F ω : ℝ≥0∞)) atTop
      (𝓝 (μ F)) :=
  by
  apply
    measure_of_cont_bdd_of_tendsto_indicator μ F_closed.measurable_set
      (fun n => thickenedIndicator (δs_pos n) F) fun n ω => thickenedIndicator_le_one (δs_pos n) F ω
  have key := thickenedIndicator_tendsto_indicator_closure δs_pos δs_lim F
  rwa [F_closed.closure_eq] at key
#align measure_theory.tendsto_lintegral_thickened_indicator_of_is_closed MeasureTheory.tendsto_lintegral_thickenedIndicator_of_isClosed

/-- One implication of the portmanteau theorem:
Weak convergence of finite measures implies that the limsup of the measures of any closed set is
at most the measure of the closed set under the limit measure.
-/
theorem FiniteMeasure.limsup_measure_closed_le_of_tendsto {Ω ι : Type _} {L : Filter ι}
    [MeasurableSpace Ω] [PseudoEMetricSpace Ω] [OpensMeasurableSpace Ω] {μ : FiniteMeasure Ω}
    {μs : ι → FiniteMeasure Ω} (μs_lim : Tendsto μs L (𝓝 μ)) {F : Set Ω} (F_closed : IsClosed F) :
    (L.limsup fun i => (μs i : Measure Ω) F) ≤ (μ : Measure Ω) F :=
  by
  by_cases L = ⊥
  · simp only [h, limsup, Filter.map_bot, Limsup_bot, ENNReal.bot_eq_zero, zero_le]
  apply ENNReal.le_of_forall_pos_le_add
  intro ε ε_pos μ_F_finite
  set δs := fun n : ℕ => (1 : ℝ) / (n + 1) with def_δs
  have δs_pos : ∀ n, 0 < δs n := fun n => Nat.one_div_pos_of_nat
  have δs_lim : tendsto δs at_top (𝓝 0) := tendsto_one_div_add_atTop_nhds_0_nat
  have key₁ :=
    tendsto_lintegral_thickened_indicator_of_is_closed (μ : Measure Ω) F_closed δs_pos δs_lim
  have room₁ : (μ : Measure Ω) F < (μ : Measure Ω) F + ε / 2 := by
    apply
      ENNReal.lt_add_right (measure_lt_top (μ : Measure Ω) F).Ne
        (ennreal.div_pos_iff.mpr ⟨(ennreal.coe_pos.mpr ε_pos).Ne.symm, ENNReal.two_ne_top⟩).Ne.symm
  rcases eventually_at_top.mp (eventually_lt_of_tendsto_lt room₁ key₁) with ⟨M, hM⟩
  have key₂ :=
    finite_measure.tendsto_iff_forall_lintegral_tendsto.mp μs_lim (thickenedIndicator (δs_pos M) F)
  have room₂ :
    (lintegral (μ : Measure Ω) fun a => thickenedIndicator (δs_pos M) F a) <
      (lintegral (μ : Measure Ω) fun a => thickenedIndicator (δs_pos M) F a) + ε / 2 :=
    by
    apply
      ENNReal.lt_add_right (lintegral_lt_top_of_bounded_continuous_to_nnreal (μ : Measure Ω) _).Ne
        (ennreal.div_pos_iff.mpr ⟨(ennreal.coe_pos.mpr ε_pos).Ne.symm, ENNReal.two_ne_top⟩).Ne.symm
  have ev_near := eventually.mono (eventually_lt_of_tendsto_lt room₂ key₂) fun n => le_of_lt
  have aux := fun n =>
    le_trans
      (measure_le_lintegral_thickenedIndicator (μs n : Measure Ω) F_closed.measurable_set
        (δs_pos M))
  have ev_near' := eventually.mono ev_near aux
  apply (Filter.limsup_le_limsup ev_near').trans
  have : ne_bot L := ⟨h⟩
  rw [limsup_const]
  apply le_trans (add_le_add (hM M rfl.le).le (le_refl (ε / 2 : ℝ≥0∞)))
  simp only [add_assoc, ENNReal.add_halves, le_refl]
#align measure_theory.finite_measure.limsup_measure_closed_le_of_tendsto MeasureTheory.FiniteMeasure.limsup_measure_closed_le_of_tendsto

/-- One implication of the portmanteau theorem:
Weak convergence of probability measures implies that the limsup of the measures of any closed
set is at most the measure of the closed set under the limit probability measure.
-/
theorem ProbabilityMeasure.limsup_measure_closed_le_of_tendsto {Ω ι : Type _} {L : Filter ι}
    [MeasurableSpace Ω] [PseudoEMetricSpace Ω] [OpensMeasurableSpace Ω] {μ : ProbabilityMeasure Ω}
    {μs : ι → ProbabilityMeasure Ω} (μs_lim : Tendsto μs L (𝓝 μ)) {F : Set Ω}
    (F_closed : IsClosed F) : (L.limsup fun i => (μs i : Measure Ω) F) ≤ (μ : Measure Ω) F := by
  apply
    finite_measure.limsup_measure_closed_le_of_tendsto
      ((probability_measure.tendsto_nhds_iff_to_finite_measures_tendsto_nhds L).mp μs_lim) F_closed
#align measure_theory.probability_measure.limsup_measure_closed_le_of_tendsto MeasureTheory.ProbabilityMeasure.limsup_measure_closed_le_of_tendsto

/-- One implication of the portmanteau theorem:
Weak convergence of probability measures implies that the liminf of the measures of any open set
is at least the measure of the open set under the limit probability measure.
-/
theorem ProbabilityMeasure.le_liminf_measure_open_of_tendsto {Ω ι : Type _} {L : Filter ι}
    [MeasurableSpace Ω] [PseudoEMetricSpace Ω] [OpensMeasurableSpace Ω] {μ : ProbabilityMeasure Ω}
    {μs : ι → ProbabilityMeasure Ω} (μs_lim : Tendsto μs L (𝓝 μ)) {G : Set Ω} (G_open : IsOpen G) :
    (μ : Measure Ω) G ≤ L.liminf fun i => (μs i : Measure Ω) G :=
  haveI h_closeds :
    ∀ F, IsClosed F → (L.limsup fun i => (μs i : Measure Ω) F) ≤ (μ : Measure Ω) F :=
    fun F F_closed => probability_measure.limsup_measure_closed_le_of_tendsto μs_lim F_closed
  le_measure_liminf_of_limsup_measure_compl_le G_open.measurable_set
    (h_closeds _ (is_closed_compl_iff.mpr G_open))
#align measure_theory.probability_measure.le_liminf_measure_open_of_tendsto MeasureTheory.ProbabilityMeasure.le_liminf_measure_open_of_tendsto

theorem ProbabilityMeasure.tendsto_measure_of_null_frontier_of_tendsto' {Ω ι : Type _}
    {L : Filter ι} [MeasurableSpace Ω] [PseudoEMetricSpace Ω] [OpensMeasurableSpace Ω]
    {μ : ProbabilityMeasure Ω} {μs : ι → ProbabilityMeasure Ω} (μs_lim : Tendsto μs L (𝓝 μ))
    {E : Set Ω} (E_nullbdry : (μ : Measure Ω) (frontier E) = 0) :
    Tendsto (fun i => (μs i : Measure Ω) E) L (𝓝 ((μ : Measure Ω) E)) :=
  haveI h_opens : ∀ G, IsOpen G → (μ : Measure Ω) G ≤ L.liminf fun i => (μs i : Measure Ω) G :=
    fun G G_open => probability_measure.le_liminf_measure_open_of_tendsto μs_lim G_open
  tendsto_measure_of_null_frontier h_opens E_nullbdry
#align measure_theory.probability_measure.tendsto_measure_of_null_frontier_of_tendsto' MeasureTheory.ProbabilityMeasure.tendsto_measure_of_null_frontier_of_tendsto'

/-- One implication of the portmanteau theorem:
Weak convergence of probability measures implies that if the boundary of a Borel set
carries no probability mass under the limit measure, then the limit of the measures of the set
equals the measure of the set under the limit probability measure.

A version with coercions to ordinary `ℝ≥0∞`-valued measures is
`measure_theory.probability_measure.tendsto_measure_of_null_frontier_of_tendsto'`.
-/
theorem ProbabilityMeasure.tendsto_measure_of_null_frontier_of_tendsto {Ω ι : Type _} {L : Filter ι}
    [MeasurableSpace Ω] [PseudoEMetricSpace Ω] [OpensMeasurableSpace Ω] {μ : ProbabilityMeasure Ω}
    {μs : ι → ProbabilityMeasure Ω} (μs_lim : Tendsto μs L (𝓝 μ)) {E : Set Ω}
    (E_nullbdry : μ (frontier E) = 0) : Tendsto (fun i => μs i E) L (𝓝 (μ E)) :=
  by
  have E_nullbdry' : (μ : Measure Ω) (frontier E) = 0 := by
    rw [← probability_measure.ennreal_coe_fn_eq_coe_fn_to_measure, E_nullbdry, ENNReal.coe_zero]
  have key := probability_measure.tendsto_measure_of_null_frontier_of_tendsto' μs_lim E_nullbdry'
  exact (ENNReal.tendsto_toNNReal (measure_ne_top (↑μ) E)).comp key
#align measure_theory.probability_measure.tendsto_measure_of_null_frontier_of_tendsto MeasureTheory.ProbabilityMeasure.tendsto_measure_of_null_frontier_of_tendsto

end ConvergenceImpliesLimsupClosedLe

--section
section LimitBorelImpliesLimsupClosedLe

/-! ### Portmanteau implication: limit condition for Borel sets implies limsup for closed sets

TODO: The proof of the implication is not yet here. Add it.
-/

variable {Ω : Type _} [PseudoEMetricSpace Ω] [MeasurableSpace Ω] [OpensMeasurableSpace Ω]

theorem exists_null_frontier_thickening (μ : Measure Ω) [SigmaFinite μ] (s : Set Ω) {a b : ℝ}
    (hab : a < b) : ∃ r ∈ Ioo a b, μ (frontier (Metric.thickening r s)) = 0 :=
  by
  have mbles : ∀ r : ℝ, MeasurableSet (frontier (Metric.thickening r s)) := fun r =>
    isClosed_frontier.MeasurableSet
  have disjs := Metric.frontier_thickening_disjoint s
  have key := @measure.countable_meas_pos_of_disjoint_Union Ω _ _ μ _ _ mbles disjs
  have aux := @measure_diff_null ℝ _ volume (Ioo a b) _ (Set.Countable.measure_zero key volume)
  have len_pos : 0 < ENNReal.ofReal (b - a) := by simp only [hab, ENNReal.ofReal_pos, sub_pos]
  rw [← Real.volume_Ioo, ← aux] at len_pos
  rcases nonempty_of_measure_ne_zero len_pos.ne.symm with ⟨r, ⟨r_in_Ioo, hr⟩⟩
  refine' ⟨r, r_in_Ioo, _⟩
  simpa only [mem_set_of_eq, not_lt, le_zero_iff] using hr
#align measure_theory.exists_null_frontier_thickening MeasureTheory.exists_null_frontier_thickening

theorem exists_null_frontiers_thickening (μ : Measure Ω) [SigmaFinite μ] (s : Set Ω) :
    ∃ rs : ℕ → ℝ,
      Tendsto rs atTop (𝓝 0) ∧ ∀ n, 0 < rs n ∧ μ (frontier (Metric.thickening (rs n) s)) = 0 :=
  by
  rcases exists_seq_strictAnti_tendsto (0 : ℝ) with ⟨Rs, ⟨rubbish, ⟨Rs_pos, Rs_lim⟩⟩⟩
  have obs := fun n : ℕ => exists_null_frontier_thickening μ s (Rs_pos n)
  refine' ⟨fun n : ℕ => (obs n).some, ⟨_, _⟩⟩
  ·
    exact
      tendsto_of_tendsto_of_tendsto_of_le_of_le tendsto_const_nhds Rs_lim
        (fun n => (obs n).choose_spec.some.1.le) fun n => (obs n).choose_spec.some.2.le
  · exact fun n => ⟨(obs n).choose_spec.some.1, (obs n).choose_spec.choose_spec⟩
#align measure_theory.exists_null_frontiers_thickening MeasureTheory.exists_null_frontiers_thickening

end LimitBorelImpliesLimsupClosedLe

--section
end MeasureTheory

--namespace


/-
Copyright (c) 2020 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov
-/
import topology.algebra.monoid
import algebra.group.pi
import topology.homeomorph

/-!
# Topological group with zero

In this file we define `has_continuous_inv₀` to be a mixin typeclass a type with `has_inv` and
`has_zero` (e.g., a `group_with_zero`) such that `λ x, x⁻¹` is continuous at all nonzero points. Any
normed (semi)field has this property. Currently the only example of `has_continuous_inv₀` in
`mathlib` which is not a normed field is the type `nnnreal` (a.k.a. `ℝ≥0`) of nonnegative real
numbers.

Then we prove lemmas about continuity of `x ↦ x⁻¹` and `f / g` providing dot-style `*.inv'` and
`*.div` operations on `filter.tendsto`, `continuous_at`, `continuous_within_at`, `continuous_on`,
and `continuous`. As a special case, we provide `*.div_const` operations that require only
`group_with_zero` and `has_continuous_mul` instances.

All lemmas about `(⁻¹)` use `inv'` in their names because lemmas without `'` are used for
`topological_group`s. We also use `'` in the typeclass name `has_continuous_inv₀` for the sake of
consistency of notation.

On a `group_with_zero` with continuous multiplication, we also define left and right multiplication
as homeomorphisms.
-/

open_locale topological_space filter
open filter function

/-!
### A group with zero with continuous multiplication

If `G₀` is a group with zero with continuous `(*)`, then `(/y)` is continuous for any `y`. In this
section we prove lemmas that immediately follow from this fact providing `*.div_const` dot-style
operations on `filter.tendsto`, `continuous_at`, `continuous_within_at`, `continuous_on`, and
`continuous`.
-/

variables {α β G₀ : Type*}

section div_const

variables [group_with_zero G₀] [topological_space G₀] [has_continuous_mul G₀]
  {f : α → G₀} {s : set α} {l : filter α}

lemma filter.tendsto.div_const {x y : G₀} (hf : tendsto f l (𝓝 x)) :
  tendsto (λa, f a / y) l (𝓝 (x / y)) :=
by simpa only [div_eq_mul_inv] using hf.mul tendsto_const_nhds

variables [topological_space α]

lemma continuous_at.div_const {a : α} (hf : continuous_at f a) {y : G₀} :
  continuous_at (λ x, f x / y) a :=
by simpa only [div_eq_mul_inv] using hf.mul continuous_at_const

lemma continuous_within_at.div_const {a} (hf : continuous_within_at f s a) {y : G₀} :
  continuous_within_at (λ x, f x / y) s a :=
hf.div_const

lemma continuous_on.div_const (hf : continuous_on f s) {y : G₀} : continuous_on (λ x, f x / y) s :=
by simpa only [div_eq_mul_inv] using hf.mul continuous_on_const

@[continuity] lemma continuous.div_const (hf : continuous f) {y : G₀} :
  continuous (λ x, f x / y) :=
by simpa only [div_eq_mul_inv] using hf.mul continuous_const

end div_const

/-- A type with `0` and `has_inv` such that `λ x, x⁻¹` is continuous at all nonzero points. Any
normed (semi)field has this property. -/
class has_continuous_inv₀ (G₀ : Type*) [has_zero G₀] [has_inv G₀] [topological_space G₀] : Prop :=
(continuous_at_inv₀ : ∀ ⦃x : G₀⦄, x ≠ 0 → continuous_at has_inv.inv x)

export has_continuous_inv₀ (continuous_at_inv₀)

section inv₀

variables [has_zero G₀] [has_inv G₀] [topological_space G₀] [has_continuous_inv₀ G₀]
  {l : filter α} {f : α → G₀} {s : set α} {a : α}

/-!
### Continuity of `λ x, x⁻¹` at a non-zero point

We define `topological_group_with_zero` to be a `group_with_zero` such that the operation `x ↦ x⁻¹`
is continuous at all nonzero points. In this section we prove dot-style `*.inv'` lemmas for
`filter.tendsto`, `continuous_at`, `continuous_within_at`, `continuous_on`, and `continuous`.
-/

lemma tendsto_inv₀ {x : G₀}  (hx : x ≠ 0) : tendsto has_inv.inv (𝓝 x) (𝓝 x⁻¹) :=
continuous_at_inv₀ hx

lemma continuous_on_inv₀ : continuous_on (has_inv.inv : G₀ → G₀) {0}ᶜ :=
λ x hx, (continuous_at_inv₀ hx).continuous_within_at

/-- If a function converges to a nonzero value, its inverse converges to the inverse of this value.
We use the name `tendsto.inv₀` as `tendsto.inv` is already used in multiplicative topological
groups. -/
lemma filter.tendsto.inv₀ {a : G₀} (hf : tendsto f l (𝓝 a))
  (ha : a ≠ 0) :
  tendsto (λ x, (f x)⁻¹) l (𝓝 a⁻¹) :=
(tendsto_inv₀ ha).comp hf

variables [topological_space α]

lemma continuous_within_at.inv₀ (hf : continuous_within_at f s a) (ha : f a ≠ 0) :
  continuous_within_at (λ x, (f x)⁻¹) s a :=
hf.inv₀ ha

lemma continuous_at.inv₀ (hf : continuous_at f a) (ha : f a ≠ 0) :
  continuous_at (λ x, (f x)⁻¹) a :=
hf.inv₀ ha

@[continuity] lemma continuous.inv₀ (hf : continuous f) (h0 : ∀ x, f x ≠ 0) :
  continuous (λ x, (f x)⁻¹) :=
continuous_iff_continuous_at.2 $ λ x, (hf.tendsto x).inv₀ (h0 x)

lemma continuous_on.inv₀ (hf : continuous_on f s) (h0 : ∀ x ∈ s, f x ≠ 0) :
  continuous_on (λ x, (f x)⁻¹) s :=
λ x hx, (hf x hx).inv₀ (h0 x hx)

end inv₀

/-!
### Continuity of division

If `G₀` is a `group_with_zero` with `x ↦ x⁻¹` continuous at all nonzero points and `(*)`, then
division `(/)` is continuous at any point where the denominator is continuous.
-/

section div

variables [group_with_zero G₀] [topological_space G₀] [has_continuous_inv₀ G₀]
  [has_continuous_mul G₀] {f g : α → G₀}

lemma filter.tendsto.div {l : filter α} {a b : G₀} (hf : tendsto f l (𝓝 a))
  (hg : tendsto g l (𝓝 b)) (hy : b ≠ 0) :
  tendsto (f / g) l (𝓝 (a / b)) :=
by simpa only [div_eq_mul_inv] using hf.mul (hg.inv₀ hy)

variables [topological_space α] [topological_space β] {s : set α} {a : α}

lemma continuous_within_at.div (hf : continuous_within_at f s a) (hg : continuous_within_at g s a)
  (h₀ : g a ≠ 0) :
  continuous_within_at (f / g) s a :=
hf.div hg h₀

lemma continuous_on.div (hf : continuous_on f s) (hg : continuous_on g s) (h₀ : ∀ x ∈ s, g x ≠ 0) :
  continuous_on (f / g) s :=
λ x hx, (hf x hx).div (hg x hx) (h₀ x hx)

/-- Continuity at a point of the result of dividing two functions continuous at that point, where
the denominator is nonzero. -/
lemma continuous_at.div (hf : continuous_at f a) (hg : continuous_at g a) (h₀ : g a ≠ 0) :
  continuous_at (f / g) a :=
hf.div hg h₀

@[continuity] lemma continuous.div (hf : continuous f) (hg : continuous g) (h₀ : ∀ x, g x ≠ 0) :
  continuous (f / g) :=
by simpa only [div_eq_mul_inv] using hf.mul (hg.inv₀ h₀)

lemma continuous_on_div : continuous_on (λ p : G₀ × G₀, p.1 / p.2) {p | p.2 ≠ 0} :=
continuous_on_fst.div continuous_on_snd $ λ _, id

/-- The function `f x / g x` is discontinuous when `g x = 0`.
However, under appropriate conditions, `h x (f x / g x)` is still continuous.
The condition is that if `g a = 0` then `h x y` must tend to `h a 0` when `x` tends to `a`,
with no information about `y`. This is represented by the `⊤` filter.
Note: `filter.tendsto_prod_top_iff` characterizes this convergence in uniform spaces.
See also `filter.prod_top` and `filter.mem_prod_top`. -/
lemma continuous_at.comp_div_cases {f g : α → G₀} (h : α → G₀ → β)
  (hf : continuous_at f a) (hg : continuous_at g a)
  (hh : g a ≠ 0 → continuous_at ↿h (a, f a / g a))
  (h2h : g a = 0 → tendsto ↿h (𝓝 a ×ᶠ ⊤) (𝓝 (h a 0))) :
  continuous_at (λ x, h x (f x / g x)) a :=
begin
  show continuous_at (↿h ∘ (λ x, (x, f x / g x))) a,
  by_cases hga : g a = 0,
  { rw [continuous_at], simp_rw [comp_app, hga, div_zero],
    exact (h2h hga).comp (continuous_at_id.prod_mk tendsto_top) },
  { exact continuous_at.comp (hh hga) (continuous_at_id.prod (hf.div hg hga)) }
end

/-- `h x (f x / g x)` is continuous under certain conditions, even if the denominator is sometimes
  `0`. See docstring of `continuous_at.comp_div_cases`. -/
lemma continuous.comp_div_cases {f g : α → G₀} (h : α → G₀ → β)
  (hf : continuous f) (hg : continuous g)
  (hh : ∀ a, g a ≠ 0 → continuous_at ↿h (a, f a / g a))
  (h2h : ∀ a, g a = 0 → tendsto ↿h (𝓝 a ×ᶠ ⊤) (𝓝 (h a 0))) :
  continuous (λ x, h x (f x / g x)) :=
continuous_iff_continuous_at.mpr $
  λ a, hf.continuous_at.comp_div_cases _ hg.continuous_at (hh a) (h2h a)

end div

/-! ### Left and right multiplication as homeomorphisms -/

namespace homeomorph

variables [topological_space α] [group_with_zero α] [has_continuous_mul α]

/-- Left multiplication by a nonzero element in a `group_with_zero` with continuous multiplication
is a homeomorphism of the underlying type. -/
protected def mul_left₀ (c : α) (hc : c ≠ 0) : α ≃ₜ α :=
{ continuous_to_fun := continuous_mul_left _,
  continuous_inv_fun := continuous_mul_left _,
  .. equiv.mul_left₀ c hc }

/-- Right multiplication by a nonzero element in a `group_with_zero` with continuous multiplication
is a homeomorphism of the underlying type. -/
protected def mul_right₀ (c : α) (hc : c ≠ 0) : α ≃ₜ α :=
{ continuous_to_fun := continuous_mul_right _,
  continuous_inv_fun := continuous_mul_right _,
  .. equiv.mul_right₀ c hc }

@[simp] lemma coe_mul_left₀ (c : α) (hc : c ≠ 0) : ⇑(homeomorph.mul_left₀ c hc) = (*) c := rfl

@[simp] lemma mul_left₀_symm_apply (c : α) (hc : c ≠ 0) :
  ((homeomorph.mul_left₀ c hc).symm : α → α) = (*) c⁻¹ := rfl

@[simp] lemma coe_mul_right₀ (c : α) (hc : c ≠ 0) :
  ⇑(homeomorph.mul_right₀ c hc) = λ x, x * c := rfl

@[simp] lemma mul_right₀_symm_apply (c : α) (hc : c ≠ 0) :
  ((homeomorph.mul_right₀ c hc).symm : α → α) = λ x, x * c⁻¹ := rfl

end homeomorph

section zpow

variables [group_with_zero G₀] [topological_space G₀] [has_continuous_inv₀ G₀]
  [has_continuous_mul G₀]

lemma continuous_at_zpow₀ (x : G₀) (m : ℤ) (h : x ≠ 0 ∨ 0 ≤ m) : continuous_at (λ x, x ^ m) x :=
begin
  cases m,
  { simpa only [zpow_of_nat] using continuous_at_pow x m },
  { simp only [zpow_neg_succ_of_nat],
    have hx : x ≠ 0, from h.resolve_right (int.neg_succ_of_nat_lt_zero m).not_le,
    exact (continuous_at_pow x (m + 1)).inv₀ (pow_ne_zero _ hx) }
end

lemma continuous_on_zpow₀ (m : ℤ) : continuous_on (λ x : G₀, x ^ m) {0}ᶜ :=
λ x hx, (continuous_at_zpow₀ _ _ (or.inl hx)).continuous_within_at

lemma filter.tendsto.zpow₀ {f : α → G₀} {l : filter α} {a : G₀} (hf : tendsto f l (𝓝 a)) (m : ℤ)
  (h : a ≠ 0 ∨ 0 ≤ m) :
  tendsto (λ x, (f x) ^ m) l (𝓝 (a ^ m)) :=
(continuous_at_zpow₀ _ m h).tendsto.comp hf

variables {X : Type*} [topological_space X] {a : X} {s : set X} {f : X → G₀}

lemma continuous_at.zpow₀ (hf : continuous_at f a) (m : ℤ) (h : f a ≠ 0 ∨ 0 ≤ m) :
  continuous_at (λ x, (f x) ^ m) a :=
hf.zpow₀ m h

lemma continuous_within_at.zpow₀ (hf : continuous_within_at f s a) (m : ℤ) (h : f a ≠ 0 ∨ 0 ≤ m) :
  continuous_within_at (λ x, f x ^ m) s a :=
hf.zpow₀ m h

lemma continuous_on.zpow₀ (hf : continuous_on f s) (m : ℤ) (h : ∀ a ∈ s, f a ≠ 0 ∨ 0 ≤ m) :
  continuous_on (λ x, f x ^ m) s :=
λ a ha, (hf a ha).zpow₀ m (h a ha)

@[continuity] lemma continuous.zpow₀ (hf : continuous f) (m : ℤ) (h0 : ∀ a, f a ≠ 0 ∨ 0 ≤ m) :
  continuous (λ x, (f x) ^ m) :=
continuous_iff_continuous_at.2 $ λ x, (hf.tendsto x).zpow₀ m (h0 x)

end zpow


/-
Copyright (c) 2022 Alexander Bentkamp. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alexander Bentkamp
-/
import analysis.inner_product_space.spectrum
import linear_algebra.matrix.hermitian

/-! # Spectral theory of hermitian matrices

This file proves the spectral theorem for matrices. The proof of the spectral theorem is based on
the spectral theorem for linear maps (`diagonalization_basis_apply_self_apply`).

## Tags

spectral theorem, diagonalization theorem

-/

namespace matrix

variables {𝕜 : Type*} [is_R_or_C 𝕜] [decidable_eq 𝕜] {n : Type*} [fintype n] [decidable_eq n]
variables {A : matrix n n 𝕜}

open_locale matrix

namespace is_hermitian

variables (hA : A.is_hermitian)

/-- The eigenvalues of a hermitian matrix, indexed by `fin (fintype.card n)` where `n` is the index
type of the matrix. -/
noncomputable def eigenvalues₀ : fin (fintype.card n) → ℝ :=
@inner_product_space.is_self_adjoint.eigenvalues 𝕜 _ _ (pi_Lp 2 (λ (_ : n), 𝕜)) _ A.to_lin'
  (is_hermitian_iff_is_self_adjoint.1 hA) _ (fintype.card n) finrank_euclidean_space

/-- The eigenvalues of a hermitian matrix, reusing the index `n` of the matrix entries. -/
noncomputable def eigenvalues : n → ℝ :=
λ i, hA.eigenvalues₀ $ (fintype.equiv_of_card_eq (fintype.card_fin _)).symm i

/-- A choice of an orthonormal basis of eigenvectors of a hermitian matrix. -/
noncomputable def eigenvector_basis : basis n 𝕜 (n → 𝕜) :=
(@inner_product_space.is_self_adjoint.eigenvector_basis 𝕜 _ _
    (pi_Lp 2 (λ (_ : n), 𝕜)) _ A.to_lin' (is_hermitian_iff_is_self_adjoint.1 hA) _
    (fintype.card n) finrank_euclidean_space).reindex
  (fintype.equiv_of_card_eq (fintype.card_fin _))

/-- A matrix whose columns are an orthonormal basis of eigenvectors of a hermitian matrix. -/
noncomputable def eigenvector_matrix : matrix n n 𝕜 :=
(pi.basis_fun 𝕜 n).to_matrix (eigenvector_basis hA)

/-- The inverse of `eigenvector_matrix` -/
noncomputable def eigenvector_matrix_inv : matrix n n 𝕜 :=
(eigenvector_basis hA).to_matrix (pi.basis_fun 𝕜 n)

lemma eigenvector_matrix_mul_inv :
  hA.eigenvector_matrix ⬝ hA.eigenvector_matrix_inv = 1 :=
by apply basis.to_matrix_mul_to_matrix_flip

/-- *Diagonalization theorem*, *spectral theorem* for matrices; A hermitian matrix can be
diagonalized by a change of basis.

For the spectral theorem on linear maps, see `diagonalization_basis_apply_self_apply`. -/
theorem spectral_theorem :
  hA.eigenvector_matrix_inv ⬝ A =
    diagonal (coe ∘ hA.eigenvalues) ⬝ hA.eigenvector_matrix_inv :=
begin
  rw [eigenvector_matrix_inv, basis_to_matrix_basis_fun_mul],
  ext i j,
  convert @inner_product_space.is_self_adjoint.diagonalization_basis_apply_self_apply 𝕜 _ _
    (pi_Lp 2 (λ (_ : n), 𝕜)) _ A.to_lin' (is_hermitian_iff_is_self_adjoint.1 hA) _ (fintype.card n)
    finrank_euclidean_space (euclidean_space.single j 1)
    ((fintype.equiv_of_card_eq (fintype.card_fin _)).symm i),
  { rw [eigenvector_basis, inner_product_space.is_self_adjoint.diagonalization_basis,
      to_lin'_apply],
    simp only [basis.to_matrix, basis.coe_to_orthonormal_basis_repr, basis.equiv_fun_apply],
    simp_rw [basis.reindex_repr, euclidean_space.single, pi_Lp.equiv_symm_apply', mul_vec_single,
      mul_one],
    refl },
  { simp only [diagonal_mul, (∘), eigenvalues, eigenvector_basis,
      inner_product_space.is_self_adjoint.diagonalization_basis],
    rw [basis.to_matrix_apply, basis.coe_to_orthonormal_basis_repr, basis.reindex_repr,
      basis.equiv_fun_apply, pi.basis_fun_apply, eigenvalues₀, linear_map.coe_std_basis,
      euclidean_space.single, pi_Lp.equiv_symm_apply'] }
end

end is_hermitian

end matrix


import data.real.basic
import tactic.ring

structure complex :=
(re : ℝ)
(im : ℝ)

notation `ℂ` := complex

-- constructor applied to projections
theorem complex.eta (z : ℂ) : complex.mk (complex.re z) (complex.im z) = z :=
begin
  cases z with x y,
  dsimp,
  refl,
end

-- two complex numbers are equal if their real and imaginary parts are equal
@[extensionality] theorem complex.ext (z1 z2 : ℂ) (hre : z1.re = z2.re) (him : z1.im = z2.im) : z1 = z2 :=
begin
  cases z1 with x y,
  cases z2 with a b,
  -- two ways to sort out the mess that hre and him
  -- have become
  dsimp at hre,
  change y = b at him,
  rw hre,
  rw him,
end

namespace complex

def zero : ℂ := ⟨0, 0⟩
instance : has_zero ℂ := ⟨complex.zero⟩
@[simp] lemma zero_re : (0 : ℂ).re = 0 := rfl
@[simp] lemma zero_im : (0 : ℂ).im = 0 := rfl

def add (z w : ℂ) : ℂ := ⟨z.re + w.re, z.im + w.im⟩
instance : has_add ℂ := ⟨complex.add⟩
@[simp] lemma add_re (a b : ℂ) : (a + b).re = a.re + b.re := rfl
@[simp] lemma add_im (a b : ℂ) : (a + b).im = a.im + b.im := rfl

def neg (z : ℂ) : ℂ := ⟨-z.re, -z.im⟩
instance : has_neg ℂ := ⟨complex.neg⟩
@[simp] lemma neg_re (a : ℂ) : (-a).re = -a.re := rfl
@[simp] lemma neg_im (a : ℂ) : (-a).im = -a.im := rfl

def one : ℂ := ⟨1, 0⟩
instance : has_one ℂ := ⟨complex.one⟩
@[simp] lemma one_re : (1 : ℂ).re = 1 := rfl
@[simp] lemma one_im : (1 : ℂ).im = 0 := rfl

def mul (z w : ℂ) : ℂ := ⟨z.re * w.re - z.im * w.im, z.re * w.im + z.im * w.re⟩
instance : has_mul ℂ := ⟨complex.mul⟩
@[simp] lemma mul_re (a b : ℂ) : (a * b).re = a.re * b.re - a.im * b.im := rfl
@[simp] lemma mul_im (a b : ℂ) : (a * b).im = a.re * b.im + a.im * b.re := rfl

instance : comm_ring ℂ :=
begin
  refine { zero := 0, add := (+), neg := has_neg.neg,
  one := 1, mul := (*), ..};
  intros;
  ext;
  simp;
  ring
end

end complex -- end of namespace


import .finset.to_set

open nat classical

variable {A : Type}

noncomputable instance type_decidable_eq (A : Type) : decidable_eq A :=
λ a b, prop_decidable (a = b)

namespace set

attribute [class]
definition finite (s : set A) : Prop := ∃ (s' : finset A), s = finset.to_set s'

attribute [instance]
definition finite_finset (s : finset A) : finite (finset.to_set s) :=
exists.intro s rfl

noncomputable instance dec_finite (s : set A) : decidable (finite s) := prop_decidable (finite s)

noncomputable definition to_finset (s : set A) : finset A :=
if fins : finite s then some fins else finset.empty

theorem to_finset_of_not_finite {s : set A} (nfins : ¬ finite s) : to_finset s = finset.empty :=
dif_neg nfins

theorem to_set_to_finset (s : set A) [fins : finite s] : finset.to_set (to_finset s) = s :=
have to_finset s = some fins, from dif_pos fins,
by rw this; exact eq.symm (some_spec fins)

theorem mem_to_finset_eq (a : A) (s : set A) [finite s] :
  (finset.mem a (to_finset s)) = (a ∈ s) :=
have (finset.mem a (to_finset s)) = (a ∈ (finset.to_set (to_finset s))), from rfl,
begin rw this, rw [to_set_to_finset] end

theorem to_set_to_finset_of_not_finite {s : set A} (nfins : ¬ finite s) :
  finset.to_set (to_finset s) = ∅ :=
have to_finset s = finset.empty, from to_finset_of_not_finite nfins,
begin rw this, reflexivity end

theorem to_finset_to_set (s : finset A) : to_finset (finset.to_set s) = s :=
begin rw finset.eq_eq_to_set_eq, rw to_set_to_finset end

theorem to_finset_eq_of_to_set_eq {s : set A} {t : finset A} (H : finset.to_set t = s) :
  to_finset s = t :=
finset.eq_of_to_set_eq_to_set (begin rw -H, rw to_finset_to_set end)

theorem finite_of_to_set_to_finset_eq {s : set A} (H : finset.to_set (to_finset s) = s) :
  finite s :=
by rewrite -H; apply finite_finset

attribute [instance]
theorem finite_empty : finite (∅ : set A) :=
by rewrite [-finset.to_set_empty]; apply finite_finset

theorem to_finset_empty : to_finset (∅ : set A) = finset.empty :=
to_finset_eq_of_to_set_eq finset.to_set_empty

theorem to_finset_eq_empty_of_eq_empty {s : set A} [fins : finite s] (H : s = ∅) :
  to_finset s = finset.empty := by rewrite [H, to_finset_empty]

theorem eq_empty_of_to_finset_eq_empty {s : set A} [fins : finite s]
    (H : to_finset s = finset.empty) :
s = ∅ := by rewrite [-finset.to_set_empty, -H, to_set_to_finset]

theorem to_finset_eq_empty (s : set A) [fins : finite s] :
  (to_finset s = finset.empty) ↔ (s = ∅) :=
iff.intro eq_empty_of_to_finset_eq_empty to_finset_eq_empty_of_eq_empty

attribute [instance]
theorem finite_insert (a : A) (s : set A) [finite s] : finite (set.insert a s) :=
exists.intro (finset.insert a (to_finset s))
(by rw [-(to_set_to_finset s), finset.to_set_insert, to_finset_to_set])

theorem to_finset_insert (a : A) (s : set A) [finite s] :
  to_finset (insert a s) = finset.insert a (to_finset s) :=
begin apply to_finset_eq_of_to_set_eq,
rw [-finset.to_set_insert, to_set_to_finset], reflexivity end

attribute [instance]
theorem finite_union (s t : set A) [finite s] [finite t] :
  finite (s ∪ t) :=
exists.intro (to_finset s ∪ to_finset t)
(by rewrite [finset.to_set_union]; repeat {rw to_set_to_finset})

theorem to_finset_union (s t : set A) [finite s] [finite t] :
  to_finset (s ∪ t) = (to_finset s ∪ to_finset t) :=
by apply to_finset_eq_of_to_set_eq; rw [finset.to_set_union]; repeat {rw to_set_to_finset}

attribute [instance]
theorem finite_inter (s t : set A) [finite s] [finite t] :
  finite (s ∩ t) :=
exists.intro (to_finset s ∩ to_finset t)
(by rw [finset.to_set_inter]; repeat {rw to_set_to_finset})

theorem to_finset_inter (s t : set A) [finite s] [finite t] :
  to_finset (s ∩ t) = (to_finset s ∩ to_finset t) :=
by apply to_finset_eq_of_to_set_eq; rw [finset.to_set_inter]; repeat {rw to_set_to_finset}

noncomputable instance dec_prop (p : Prop) : decidable p := prop_decidable p

attribute [instance]
theorem finite_sep (s : set A) (p : A → Prop) [finite s] :
  finite {x ∈ s | p x}  :=
exists.intro (finset.sep p (to_finset s))
(by rw [finset.to_set_sep]; rw to_set_to_finset; reflexivity)

theorem to_finset_sep (s : set A) (p : A → Prop) [finite s] :
  to_finset {x ∈ s | p x} = finset.sep p (to_finset s) :=
begin apply to_finset_eq_of_to_set_eq; rewrite [finset.to_set_sep, to_set_to_finset], reflexivity end

attribute [instance]
theorem finite_image {B : Type} (f : A → B) (s : set A) [finite s] :
  finite (image f s) :=
exists.intro (finset.image f (to_finset s))
(begin rw [finset.to_set_image]; repeat {rw to_set_to_finset} end)

theorem to_finset_image {B : Type}  (f : A → B) (s : set A)
    [fins : finite s] :
  to_finset (image f s) = (finset.image f (to_finset s)) :=
by apply to_finset_eq_of_to_set_eq; rewrite [finset.to_set_image, to_set_to_finset]

attribute [instance]
theorem finite_diff (s t : set A) [finite s] : finite (s \ t) :=
finite_sep _ _

theorem to_finset_diff (s t : set A) [finite s] [finite t] :
        to_finset (s \ t) = (to_finset s \ to_finset t) :=
by apply to_finset_eq_of_to_set_eq; rw [finset.to_set_diff]; repeat {rw to_set_to_finset}

theorem finite_subset {s t : set A} [finite t] (ssubt : s ⊆ t) : finite s :=
by rewrite (eq_sep_of_subset ssubt); apply finite_sep

theorem to_finset_subset_to_finset_eq (s t : set A) [finite s] [finite t] :
  (to_finset s ⊆ to_finset t) = (s ⊆ t) :=
by rw [finset.subset_eq_to_set_subset]; repeat {rw to_set_to_finset}

theorem finite_of_finite_insert {s : set A} {a : A} (finias : finite (insert a s)) : finite s :=
finite_subset (subset_insert a s)

attribute [instance]
theorem finite_upto (n : ℕ) : finite {i | i < n} :=
by rewrite [-finset.to_set_upto n]; apply finite_finset

theorem to_finset_upto (n : ℕ) : to_finset {i | i < n} = finset.upto n :=
by apply (to_finset_eq_of_to_set_eq (finset.to_set_upto _))

theorem finite_of_surj_on {B : Type} {f : A → B} {s : set A} [finite s] {t : set B}
                          (H : surj_on f s t) :
        finite t :=
finite_subset H

section

-- classical inverse

open classical
variables {X Y : Type}

noncomputable definition inv_fun (f : X → Y) (a : set X) (dflt : X) (y : Y) : X :=
if H : ∃ x, x ∈ a ∧ f x = y then some H else dflt

theorem inv_fun_pos {f : X → Y} {a : set X} {dflt : X} {y : Y}
  (H : ∃ x, x ∈ a ∧ f x = y) : (inv_fun f a dflt y ∈ a) ∧ (f (inv_fun f a dflt y) = y) :=
have H1 : inv_fun f a dflt y = some H, from dif_pos H,
let ⟨x,ina⟩ := some_spec H in
⟨by rw H1; assumption,by rw H1; assumption⟩

theorem inv_fun_neg {f : X → Y} {a : set X} {dflt : X} {y : Y}
  (H : ¬ ∃ x, x ∈ a ∧ f x = y) : inv_fun f a dflt y = dflt :=
dif_neg H

variables {f : X → Y} {a : set X} {b : set Y}

theorem maps_to_inv_fun {dflt : X} (dflta : dflt ∈ a) :
  maps_to (inv_fun f a dflt) b a :=
let f' := inv_fun f a dflt in
take y,
assume yb : y ∈ b,
show f' y ∈ a, from
  by_cases
    (assume H : ∃ x, x ∈ a ∧ f x = y,
      and.left (inv_fun_pos H))
    (assume H : ¬ ∃x, x ∈ a ∧ f x = y,
begin dsimp, rw (inv_fun_neg H), assumption end)

theorem left_inv_on_inv_fun_of_inj_on (dflt : X) (H : inj_on f a) :
  left_inv_on (inv_fun f a dflt) f a :=
let f' := inv_fun f a dflt in
take x,
assume xa : x ∈ a,
have H1 : ∃ x', x' ∈ a ∧ f x' = f x, from ⟨x,xa,rfl⟩,
have H2 : f' (f x) ∈ a ∧ f (f' (f x)) = f x, from inv_fun_pos H1,
show f' (f x) = x, from H (and.left H2) xa (and.right H2)

theorem surj_on_inv_fun_of_inj_on (dflt : X) (mapsto : maps_to f a b) (H : inj_on f a) :
  surj_on (inv_fun f a dflt) b a :=
surj_on_of_right_inv_on mapsto (left_inv_on_inv_fun_of_inj_on dflt H)

end

theorem finite_of_inj_on {B : Type} {f : A → B} {s : set A} {t : set B} [finite t]
                         (mapsto : maps_to f s t) (injf : inj_on f s) :
        finite s :=
if H : s = ∅ then
  begin rewrite H; apply _, apply finite_empty end
else
  let ⟨dflt,xs⟩ := exists_mem_of_ne_empty H in
  let finv := inv_fun f s dflt in
  have surj_on finv t s, from surj_on_inv_fun_of_inj_on dflt mapsto injf,
  finite_of_surj_on this

-- theorem finite_of_bij_on {B : Type} {f : A → B} {s : set A} {t : set B} [finite s]
--                          (bijf : bij_on f s t) :
--         finite t :=
-- finite_of_surj_on (surj_on_of_bij_on bijf)

-- theorem finite_of_bij_on' {B : Type} {f : A → B} {s : set A} {t : set B} [finite t]
--                          (bijf : bij_on f s t) :
--         finite s :=
-- finite_of_inj_on (maps_to_of_bij_on bijf) (inj_on_of_bij_on bijf)

-- theorem finite_iff_finite_of_bij_on {B : Type} {f : A → B} {s : set A} {t : set B}
--                                     (bijf : bij_on f s t) :
--         finite s ↔ finite t :=
-- iff.intro (assume fs, finite_of_bij_on bijf) (assume ft, finite_of_bij_on' bijf)

-- theorem finite_powerset (s : set A) [finite s] : finite 𝒫 s :=
-- have H : 𝒫 s = finset.to_set ' (finset.to_set (#finset 𝒫 (to_finset s))),
--   from ext (take t, iff.intro
--     (suppose t ∈ 𝒫 s,
--       have t ⊆ s, from this,
--       have finite t, from finite_subset this,
--       have (#finset to_finset t ∈ 𝒫 (to_finset s)),
--         by rewrite [finset.mem_powerset_iff_subset, to_finset_subset_to_finset_eq]; apply `t ⊆ s`,
--       have to_finset t ∈ (finset.to_set (finset.powerset (to_finset s))), from this,
--       mem_image this (by rewrite to_set_to_finset))
--     (assume H',
--       obtain t' [(tmem : (#finset t' ∈ 𝒫 (to_finset s))) (teq : finset.to_set t' = t)],
--         from H',
--       show t ⊆ s,
--       begin
--         rewrite [-teq, finset.mem_powerset_iff_subset at tmem, -to_set_to_finset s],
--         rewrite -finset.subset_eq_to_set_subset, assumption
--      end)),
-- by rewrite H; apply finite_image

/- induction for finite sets -/

attribute [recursor 6]
theorem induction_finite {P : set A → Prop}
    (H1 : P ∅) (H2 : ∀ ⦃a : A⦄, ∀ {s : set A} [finite s], a ∉ s → P s → P (insert a s)) :
  ∀ (s : set A) [finite s], P s :=
(λ s fins,
have ∀ s' : finset A, P (finset.to_set s'), from
  λ s', @finset.induction_on _ _ (λ x, P (finset.to_set x)) s'
  (by rw finset.to_set_empty; exact H1)
  (begin
    intros a s' anin ih,
    rw [-finset.to_set_insert], apply H2,
    {rw [-finset.mem_eq_mem_to_set], exact anin},
    {exact ih}
   end),
begin rw [-to_set_to_finset s], exact (this (to_finset s)) end)

theorem induction_on_finite {P : set A → Prop} (s : set A) [finite s]
    (H1 : P ∅) (H2 : ∀ ⦃a : A⦄, ∀ {s : set A} [finite s], a ∉ s → P s → P (insert a s)) :
  P s :=
induction_finite H1 H2 s

end set


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Benjamin Davidson
-/
import analysis.special_functions.trigonometric.basic
import topology.algebra.order.proj_Icc

/-!
# Inverse trigonometric functions.

See also `analysis.special_functions.trigonometric.arctan` for the inverse tan function.
(This is delayed as it is easier to set up after developing complex trigonometric functions.)

Basic inequalities on trigonometric functions.
-/

noncomputable theory
open_locale classical topological_space filter
open set filter

open_locale real

namespace real

/-- Inverse of the `sin` function, returns values in the range `-π / 2 ≤ arcsin x ≤ π / 2`.
It defaults to `-π / 2` on `(-∞, -1)` and to `π / 2` to `(1, ∞)`. -/
@[pp_nodot] noncomputable def arcsin : ℝ → ℝ :=
coe ∘ Icc_extend (neg_le_self zero_le_one) sin_order_iso.symm

lemma arcsin_mem_Icc (x : ℝ) : arcsin x ∈ Icc (-(π / 2)) (π / 2) := subtype.coe_prop _

@[simp] lemma range_arcsin : range arcsin = Icc (-(π / 2)) (π / 2) :=
by { rw [arcsin, range_comp coe], simp [Icc] }

lemma arcsin_le_pi_div_two (x : ℝ) : arcsin x ≤ π / 2 := (arcsin_mem_Icc x).2

lemma neg_pi_div_two_le_arcsin (x : ℝ) : -(π / 2) ≤ arcsin x := (arcsin_mem_Icc x).1

lemma arcsin_proj_Icc (x : ℝ) :
  arcsin (proj_Icc (-1) 1 (neg_le_self zero_le_one) x) = arcsin x :=
by rw [arcsin, function.comp_app, Icc_extend_coe, function.comp_app, Icc_extend]

lemma sin_arcsin' {x : ℝ} (hx : x ∈ Icc (-1 : ℝ) 1) : sin (arcsin x) = x :=
by simpa [arcsin, Icc_extend_of_mem _ _ hx, -order_iso.apply_symm_apply]
  using subtype.ext_iff.1 (sin_order_iso.apply_symm_apply ⟨x, hx⟩)

lemma sin_arcsin {x : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) : sin (arcsin x) = x :=
sin_arcsin' ⟨hx₁, hx₂⟩

lemma arcsin_sin' {x : ℝ} (hx : x ∈ Icc (-(π / 2)) (π / 2)) : arcsin (sin x) = x :=
inj_on_sin (arcsin_mem_Icc _) hx $ by rw [sin_arcsin (neg_one_le_sin _) (sin_le_one _)]

lemma arcsin_sin {x : ℝ} (hx₁ : -(π / 2) ≤ x) (hx₂ : x ≤ π / 2) : arcsin (sin x) = x :=
arcsin_sin' ⟨hx₁, hx₂⟩

lemma strict_mono_on_arcsin : strict_mono_on arcsin (Icc (-1) 1) :=
(subtype.strict_mono_coe _).comp_strict_mono_on $
  sin_order_iso.symm.strict_mono.strict_mono_on_Icc_extend _

lemma monotone_arcsin : monotone arcsin :=
(subtype.mono_coe _).comp $ sin_order_iso.symm.monotone.Icc_extend _

lemma inj_on_arcsin : inj_on arcsin (Icc (-1) 1) := strict_mono_on_arcsin.inj_on

lemma arcsin_inj {x y : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) (hy₁ : -1 ≤ y) (hy₂ : y ≤ 1) :
  arcsin x = arcsin y ↔ x = y :=
inj_on_arcsin.eq_iff ⟨hx₁, hx₂⟩ ⟨hy₁, hy₂⟩

@[continuity]
lemma continuous_arcsin : continuous arcsin :=
continuous_subtype_coe.comp sin_order_iso.symm.continuous.Icc_extend'

lemma continuous_at_arcsin {x : ℝ} : continuous_at arcsin x :=
continuous_arcsin.continuous_at

lemma arcsin_eq_of_sin_eq {x y : ℝ} (h₁ : sin x = y) (h₂ : x ∈ Icc (-(π / 2)) (π / 2)) :
  arcsin y = x :=
begin
  subst y,
  exact inj_on_sin (arcsin_mem_Icc _) h₂ (sin_arcsin' (sin_mem_Icc x))
end

@[simp] lemma arcsin_zero : arcsin 0 = 0 :=
arcsin_eq_of_sin_eq sin_zero ⟨neg_nonpos.2 pi_div_two_pos.le, pi_div_two_pos.le⟩

@[simp] lemma arcsin_one : arcsin 1 = π / 2 :=
arcsin_eq_of_sin_eq sin_pi_div_two $ right_mem_Icc.2 (neg_le_self pi_div_two_pos.le)

lemma arcsin_of_one_le {x : ℝ} (hx : 1 ≤ x) : arcsin x = π / 2 :=
by rw [← arcsin_proj_Icc, proj_Icc_of_right_le _ hx, subtype.coe_mk, arcsin_one]

lemma arcsin_neg_one : arcsin (-1) = -(π / 2) :=
arcsin_eq_of_sin_eq (by rw [sin_neg, sin_pi_div_two]) $
  left_mem_Icc.2 (neg_le_self pi_div_two_pos.le)

lemma arcsin_of_le_neg_one {x : ℝ} (hx : x ≤ -1) : arcsin x = -(π / 2) :=
by rw [← arcsin_proj_Icc, proj_Icc_of_le_left _ hx, subtype.coe_mk, arcsin_neg_one]

@[simp] lemma arcsin_neg (x : ℝ) : arcsin (-x) = -arcsin x :=
begin
  cases le_total x (-1) with hx₁ hx₁,
  { rw [arcsin_of_le_neg_one hx₁, neg_neg, arcsin_of_one_le (le_neg.2 hx₁)] },
  cases le_total 1 x with hx₂ hx₂,
  { rw [arcsin_of_one_le hx₂, arcsin_of_le_neg_one (neg_le_neg hx₂)] },
  refine arcsin_eq_of_sin_eq _ _,
  { rw [sin_neg, sin_arcsin hx₁ hx₂] },
  { exact ⟨neg_le_neg (arcsin_le_pi_div_two _), neg_le.2 (neg_pi_div_two_le_arcsin _)⟩ }
end

lemma arcsin_le_iff_le_sin {x y : ℝ} (hx : x ∈ Icc (-1 : ℝ) 1) (hy : y ∈ Icc (-(π / 2)) (π / 2)) :
  arcsin x ≤ y ↔ x ≤ sin y :=
by rw [← arcsin_sin' hy, strict_mono_on_arcsin.le_iff_le hx (sin_mem_Icc _), arcsin_sin' hy]

lemma arcsin_le_iff_le_sin' {x y : ℝ} (hy : y ∈ Ico (-(π / 2)) (π / 2)) :
  arcsin x ≤ y ↔ x ≤ sin y :=
begin
  cases le_total x (-1) with hx₁ hx₁,
  { simp [arcsin_of_le_neg_one hx₁, hy.1, hx₁.trans (neg_one_le_sin _)] },
  cases lt_or_le 1 x with hx₂ hx₂,
  { simp [arcsin_of_one_le hx₂.le, hy.2.not_le, (sin_le_one y).trans_lt hx₂] },
  exact arcsin_le_iff_le_sin ⟨hx₁, hx₂⟩ (mem_Icc_of_Ico hy)
end

lemma le_arcsin_iff_sin_le {x y : ℝ} (hx : x ∈ Icc (-(π / 2)) (π / 2)) (hy : y ∈ Icc (-1 : ℝ) 1) :
  x ≤ arcsin y ↔ sin x ≤ y :=
by rw [← neg_le_neg_iff, ← arcsin_neg,
  arcsin_le_iff_le_sin ⟨neg_le_neg hy.2, neg_le.2 hy.1⟩ ⟨neg_le_neg hx.2, neg_le.2 hx.1⟩,
  sin_neg, neg_le_neg_iff]

lemma le_arcsin_iff_sin_le' {x y : ℝ} (hx : x ∈ Ioc (-(π / 2)) (π / 2)) :
  x ≤ arcsin y ↔ sin x ≤ y :=
by rw [← neg_le_neg_iff, ← arcsin_neg, arcsin_le_iff_le_sin' ⟨neg_le_neg hx.2, neg_lt.2 hx.1⟩,
  sin_neg, neg_le_neg_iff]

lemma arcsin_lt_iff_lt_sin {x y : ℝ} (hx : x ∈ Icc (-1 : ℝ) 1) (hy : y ∈ Icc (-(π / 2)) (π / 2)) :
  arcsin x < y ↔ x < sin y :=
not_le.symm.trans $ (not_congr $ le_arcsin_iff_sin_le hy hx).trans not_le

lemma arcsin_lt_iff_lt_sin' {x y : ℝ} (hy : y ∈ Ioc (-(π / 2)) (π / 2)) :
  arcsin x < y ↔ x < sin y :=
not_le.symm.trans $ (not_congr $ le_arcsin_iff_sin_le' hy).trans not_le

lemma lt_arcsin_iff_sin_lt {x y : ℝ} (hx : x ∈ Icc (-(π / 2)) (π / 2)) (hy : y ∈ Icc (-1 : ℝ) 1) :
  x < arcsin y ↔ sin x < y :=
not_le.symm.trans $ (not_congr $ arcsin_le_iff_le_sin hy hx).trans not_le

lemma lt_arcsin_iff_sin_lt' {x y : ℝ} (hx : x ∈ Ico (-(π / 2)) (π / 2)) :
  x < arcsin y ↔ sin x < y :=
not_le.symm.trans $ (not_congr $ arcsin_le_iff_le_sin' hx).trans not_le

lemma arcsin_eq_iff_eq_sin {x y : ℝ} (hy : y ∈ Ioo (-(π / 2)) (π / 2)) :
  arcsin x = y ↔ x = sin y :=
by simp only [le_antisymm_iff, arcsin_le_iff_le_sin' (mem_Ico_of_Ioo hy),
  le_arcsin_iff_sin_le' (mem_Ioc_of_Ioo hy)]

@[simp] lemma arcsin_nonneg {x : ℝ} : 0 ≤ arcsin x ↔ 0 ≤ x :=
(le_arcsin_iff_sin_le' ⟨neg_lt_zero.2 pi_div_two_pos, pi_div_two_pos.le⟩).trans $ by rw [sin_zero]

@[simp] lemma arcsin_nonpos {x : ℝ} : arcsin x ≤ 0 ↔ x ≤ 0 :=
neg_nonneg.symm.trans $ arcsin_neg x ▸ arcsin_nonneg.trans neg_nonneg

@[simp] lemma arcsin_eq_zero_iff {x : ℝ} : arcsin x = 0 ↔ x = 0 :=
by simp [le_antisymm_iff]

@[simp] lemma zero_eq_arcsin_iff {x} : 0 = arcsin x ↔ x = 0 :=
eq_comm.trans arcsin_eq_zero_iff

@[simp] lemma arcsin_pos {x : ℝ} : 0 < arcsin x ↔ 0 < x :=
lt_iff_lt_of_le_iff_le arcsin_nonpos

@[simp] lemma arcsin_lt_zero {x : ℝ} : arcsin x < 0 ↔ x < 0 :=
lt_iff_lt_of_le_iff_le arcsin_nonneg

@[simp] lemma arcsin_lt_pi_div_two {x : ℝ} : arcsin x < π / 2 ↔ x < 1 :=
(arcsin_lt_iff_lt_sin' (right_mem_Ioc.2 $ neg_lt_self pi_div_two_pos)).trans $
  by rw sin_pi_div_two

@[simp] lemma neg_pi_div_two_lt_arcsin {x : ℝ} : -(π / 2) < arcsin x ↔ -1 < x :=
(lt_arcsin_iff_sin_lt' $ left_mem_Ico.2 $ neg_lt_self pi_div_two_pos).trans $
  by rw [sin_neg, sin_pi_div_two]

@[simp] lemma arcsin_eq_pi_div_two {x : ℝ} : arcsin x = π / 2 ↔ 1 ≤ x :=
⟨λ h, not_lt.1 $ λ h', (arcsin_lt_pi_div_two.2 h').ne h, arcsin_of_one_le⟩

@[simp] lemma pi_div_two_eq_arcsin {x} : π / 2 = arcsin x ↔ 1 ≤ x :=
eq_comm.trans arcsin_eq_pi_div_two

@[simp] lemma pi_div_two_le_arcsin {x} : π / 2 ≤ arcsin x ↔ 1 ≤ x :=
(arcsin_le_pi_div_two x).le_iff_eq.trans pi_div_two_eq_arcsin

@[simp] lemma arcsin_eq_neg_pi_div_two {x : ℝ} : arcsin x = -(π / 2) ↔ x ≤ -1 :=
⟨λ h, not_lt.1 $ λ h', (neg_pi_div_two_lt_arcsin.2 h').ne' h, arcsin_of_le_neg_one⟩

@[simp] lemma neg_pi_div_two_eq_arcsin {x} : -(π / 2) = arcsin x ↔ x ≤ -1 :=
eq_comm.trans arcsin_eq_neg_pi_div_two

@[simp] lemma arcsin_le_neg_pi_div_two {x} : arcsin x ≤ -(π / 2) ↔ x ≤ -1 :=
(neg_pi_div_two_le_arcsin x).le_iff_eq.trans arcsin_eq_neg_pi_div_two

@[simp]

lemma maps_to_sin_Ioo : maps_to sin (Ioo (-(π / 2)) (π / 2)) (Ioo (-1) 1) :=
λ x h, by rwa [mem_Ioo, ← arcsin_lt_pi_div_two, ← neg_pi_div_two_lt_arcsin,
  arcsin_sin h.1.le h.2.le]

/-- `real.sin` as a `local_homeomorph` between `(-π / 2, π / 2)` and `(-1, 1)`. -/
@[simp] def sin_local_homeomorph : local_homeomorph ℝ ℝ :=
{ to_fun := sin,
  inv_fun := arcsin,
  source := Ioo (-(π / 2)) (π / 2),
  target := Ioo (-1) 1,
  map_source' := maps_to_sin_Ioo,
  map_target' := λ y hy, ⟨neg_pi_div_two_lt_arcsin.2 hy.1, arcsin_lt_pi_div_two.2 hy.2⟩,
  left_inv' := λ x hx, arcsin_sin hx.1.le hx.2.le,
  right_inv' := λ y hy, sin_arcsin hy.1.le hy.2.le,
  open_source := is_open_Ioo,
  open_target := is_open_Ioo,
  continuous_to_fun := continuous_sin.continuous_on,
  continuous_inv_fun := continuous_arcsin.continuous_on }

lemma cos_arcsin_nonneg (x : ℝ) : 0 ≤ cos (arcsin x) :=
cos_nonneg_of_mem_Icc ⟨neg_pi_div_two_le_arcsin _, arcsin_le_pi_div_two _⟩

lemma cos_arcsin {x : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) : cos (arcsin x) = sqrt (1 - x ^ 2) :=
have sin (arcsin x) ^ 2 + cos (arcsin x) ^ 2 = 1 := sin_sq_add_cos_sq (arcsin x),
begin
  rw [← eq_sub_iff_add_eq', ← sqrt_inj (sq_nonneg _) (sub_nonneg.2 (sin_sq_le_one (arcsin x))),
    sq, sqrt_mul_self (cos_arcsin_nonneg _)] at this,
  rw [this, sin_arcsin hx₁ hx₂],
end

/-- Inverse of the `cos` function, returns values in the range `0 ≤ arccos x` and `arccos x ≤ π`.
  If the argument is not between `-1` and `1` it defaults to `π / 2` -/
@[pp_nodot] noncomputable def arccos (x : ℝ) : ℝ :=
π / 2 - arcsin x

lemma arccos_eq_pi_div_two_sub_arcsin (x : ℝ) : arccos x = π / 2 - arcsin x := rfl

lemma arcsin_eq_pi_div_two_sub_arccos (x : ℝ) : arcsin x = π / 2 - arccos x :=
by simp [arccos]

lemma arccos_le_pi (x : ℝ) : arccos x ≤ π :=
by unfold arccos; linarith [neg_pi_div_two_le_arcsin x]

lemma arccos_nonneg (x : ℝ) : 0 ≤ arccos x :=
by unfold arccos; linarith [arcsin_le_pi_div_two x]

lemma cos_arccos {x : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) : cos (arccos x) = x :=
by rw [arccos, cos_pi_div_two_sub, sin_arcsin hx₁ hx₂]

lemma arccos_cos {x : ℝ} (hx₁ : 0 ≤ x) (hx₂ : x ≤ π) : arccos (cos x) = x :=
by rw [arccos, ← sin_pi_div_two_sub, arcsin_sin]; simp [sub_eq_add_neg]; linarith

lemma strict_anti_on_arccos : strict_anti_on arccos (Icc (-1) 1) :=
λ x hx y hy h, sub_lt_sub_left (strict_mono_on_arcsin hx hy h) _

lemma arccos_inj_on : inj_on arccos (Icc (-1) 1) := strict_anti_on_arccos.inj_on

lemma arccos_inj {x y : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) (hy₁ : -1 ≤ y) (hy₂ : y ≤ 1) :
  arccos x = arccos y ↔ x = y :=
arccos_inj_on.eq_iff ⟨hx₁, hx₂⟩ ⟨hy₁, hy₂⟩

@[simp] lemma arccos_zero : arccos 0 = π / 2 := by simp [arccos]

@[simp] lemma arccos_one : arccos 1 = 0 := by simp [arccos]

@[simp] lemma arccos_neg_one : arccos (-1) = π := by simp [arccos, add_halves]

@[simp] lemma arccos_eq_zero {x} : arccos x = 0 ↔ 1 ≤ x :=
by simp [arccos, sub_eq_zero]

@[simp] lemma arccos_eq_pi_div_two {x} : arccos x = π / 2 ↔ x = 0 :=
by simp [arccos]

@[simp] lemma arccos_eq_pi {x} : arccos x = π ↔ x ≤ -1 :=
by rw [arccos, sub_eq_iff_eq_add, ← sub_eq_iff_eq_add', div_two_sub_self, neg_pi_div_two_eq_arcsin]

lemma arccos_neg (x : ℝ) : arccos (-x) = π - arccos x :=
by rw [← add_halves π, arccos, arcsin_neg, arccos, add_sub_assoc, sub_sub_self, sub_neg_eq_add]

lemma sin_arccos {x : ℝ} (hx₁ : -1 ≤ x) (hx₂ : x ≤ 1) : sin (arccos x) = sqrt (1 - x ^ 2) :=
by rw [arccos_eq_pi_div_two_sub_arcsin, sin_pi_div_two_sub, cos_arcsin hx₁ hx₂]

@[simp] lemma arccos_le_pi_div_two {x} : arccos x ≤ π / 2 ↔ 0 ≤ x := by simp [arccos]

@[simp] lemma arccos_le_pi_div_four {x} : arccos x ≤ π / 4 ↔ sqrt 2 / 2 ≤ x :=
by { rw [arccos, ← pi_div_four_le_arcsin], split; { intro, linarith } }

@[continuity]
lemma continuous_arccos : continuous arccos := continuous_const.sub continuous_arcsin

end real


-- use mathlib stuff
import tactic.find
import logic.basic

section notes
  universe u
  variables (α: Type) (p q: α → Prop) (r: α → α → Prop)
  variables (a b c d e: ℤ)

  example: (∀ x: α, p x ∧ q x) → ∀ y: α, p y :=
    assume h: ∀ x: α, p x ∧ q x,
    assume y: α,
    show p y, from (h y).left

  variable refl_r : ∀ x, r x x
  variable symm_r : ∀ {x y}, r x y → r y x
  variable trans_r : ∀ {x y z}, r x y → r y z → r x z

  -- double transitivity + one symmetry
  example (a b c d : α) (hab : r a b) (hcb : r c b) (hcd : r c d) : r a d :=
    trans_r (trans_r hab (symm_r hcb)) hcd

  example (a b c d: α) (hab: a=b) (hcb: c=b) (hcd: c=d): a=d :=
    eq.trans (eq.trans hab (eq.symm hcb)) hcd

  -- cool!
  example: 2+5 = 7 := rfl

  example (α: Type u) (a b: α) (p: α → Prop) (h1: a = b) (h2: p a): p b :=
    h1 ▸ h2

  example: a+0 = a := add_zero a
  example: a*(b+c) = a*b + a*c := mul_add a b c

  example (x y: ℤ): (x+y)*(x+y) = x*x + y*x + x*y + y*y :=
    have h1: (x+y)*(x+y) = (x+y)*x + (x+y)*y, from mul_add (x+y) x y,
    have h2: (x+y)*(x+y) = x*x + y*x + (x*y + y*y),
    from (add_mul x y x) ▸ (add_mul x y y) ▸ h1,
    h2.trans (add_assoc (x*x + y*x) (x*y) (y*y)).symm

  theorem T (h1: a=b) (h2: b=c+1) (h3: c=d) (h4: e=1+d): a=e :=
    calc
      a   = b   : by rw h1
      ... = c+1 : by rw h2
      ... = d+1 : by rw h3
      ... = 1+d : by rw add_comm
      ... = e   : by rw h4

  theorem T' (h1: a=b) (h2: b=c+1) (h3: c=d) (h4: e=1+d): a=e :=
    -- black magic
    by simp [h1, h2, h3, h4]

  example (x y: ℤ): (x+y)*(x+y) = x*x + y*y + x*y + y*x :=
    by simp [mul_add, add_mul]

  example (x y z: ℤ) (hxy: x < y) (hyz: y < z):
    ∃ w, x < w ∧ w < z :=
  exists.intro y (and.intro hxy hyz)

  #check @exists.intro

  variable g : ℕ → ℕ → ℕ
  variable hg : g 0 0 = 0

  theorem gex1 : ∃ x, g x x = x := ⟨0, hg⟩
  theorem gex2 : ∃ x, g x 0 = x := ⟨0, hg⟩
  theorem gex3 : ∃ x, g 0 0 = x := ⟨0, hg⟩
  theorem gex4 : ∃ x, g x x = 0 := ⟨0, hg⟩

  set_option pp.implicit true  -- display implicit arguments
  #print gex1
  #print gex2
  #print gex3
  #print gex4

  example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
  match h with ⟨w, hw⟩ :=
    ⟨w, hw.right, hw.left⟩
  end

  def is_even (a: ℕ) := ∃ b, a = 2*b

  theorem even_plus_even {a b: ℕ}
    (h1: is_even a) (h2: is_even b): is_even (a+b) :=
  match h1, h2 with
    ⟨w1, hw1⟩, ⟨w2, hw2⟩ := ⟨w1 + w2, by rw [hw1, hw2, mul_add]⟩
  end

  -- requires LEM
  example (h: ¬ ∀ x, ¬ p x): ∃ x, p x :=
  classical.by_contradiction
    (assume h1: ¬ ∃ x, p x,
      have h2: ∀ x, ¬ p x, from
        assume x: α,
        assume h3: p x,
        -- ‹› <> ⟨⟩: thanks, Leo
        have h4: ∃ x, p  x, from ⟨x, ‹p x›⟩,
        show false, from h1 h4,
      show false, from h h2)
end notes

section ex1
  variables (α : Type) (p q : α → Prop)

  example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
    iff.intro
      (assume h: ∀ x, p x ∧ q x,
        and.intro
          (assume x, show p x, from (h x).left)
          (assume x, show q x, from (h x).right))
      (assume h: (∀ x, p x) ∧ (∀ x, q x),
      assume x,
      show p x ∧ q x, from
        and.intro (h.left x) (h.right x))

  example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
    assume h : (∀ x, p x → q x),
    assume h' : (∀ x, p x),
    (assume x,
      have p x → q x, from h x,
      show q x, from this (h' x))

  example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
    assume h : (∀ x, p x) ∨ (∀ x, q x),
    assume x,
    or.elim h
      (assume : (∀ x, p x), or.inl (this x))
      (assume : (∀ x, q x), or.inr (this x))
end ex1

section ex2
  variables (α : Type) (p q : α → Prop)
  variable r : Prop

  example : α → ((∀ x : α, r) ↔ r) :=
    assume x: α,
    iff.intro
      (assume h: (∀ x: α, r), h x)
      (assume h: r, assume _: α, h)

  example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
    iff.intro
      (assume h: ∀ x, p x ∨ r,
      classical.by_contradiction
        (assume : ¬((∀ x, p x) ∨ r),
        have h1: ¬(∀ x, p x) ∧ ¬r, from not_or_distrib.mp this,
        have ¬∀ x, p x, from h1.left,
        have ¬r, from h1.right,
        have ∃ x, ¬p x, from classical.not_forall.mp ‹¬∀ x, p x›,
        match this with ⟨x, hpx⟩ :=
          show false, from or.elim (h x)
            (assume : p x, ‹¬p x› ‹p x›)
            (assume : r, ‹¬r› ‹r›)
        end))
      (assume h: (∀ x, p x) ∨ r,
      show (∀ x, p x ∨ r), from or.elim h
        (assume hpx: ∀ x, p x, assume x, or.inl (hpx x))
        (assume hr: r, assume x, or.inr hr))

  example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
    iff.intro
      (assume h: ∀ x, r → p x,
      assume hr: r,
      assume x,
      show p x, from (h x) hr)
      (assume h: r → ∀ x, p x,
      assume x,
      assume hr: r,
      show p x, from (h hr) x)
end ex2

section ex3
  variables (men : Type) (barber : men)
  variable  (shaves : men → men → Prop)

  example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) :
    false :=
  have h: shaves barber barber ↔ ¬ shaves barber barber, from h barber,
  show false, from (iff_not_self _).mp h
end ex3

namespace ex4
  def divides (m n : ℕ) : Prop := ∃ k, m * k = n

  instance : has_dvd nat := ⟨divides⟩

  def even (n : ℕ) : Prop := 2 ∣ n

  section
    variables m n : ℕ

    #check m ∣ n
    #check m^n
    #check even (m^n +3)
  end

  def prime (n : ℕ) : Prop := ∀ m, (m ≠ 1 ∧ m ≠ n) →
    ¬ (m ∣ n)

  def infinitely_many_primes : Prop := ∀ p, (prime p) →
    (∃ p₂, p < p₂ ∧ prime p₂)

  def Fermat_prime (n : ℕ) : Prop := prime n ∧ (∃ k: ℕ, k ≠ 0 ∧ 2^k + 1 = n)

  def infinitely_many_Fermat_primes : Prop := ∀ p, (Fermat_prime p) →
    (∃ p₂, p < p₂ ∧ Fermat_prime p₂)

  def goldbach_conjecture : Prop := ∀ n, (2 ∣ n ∧ n > 2) →
    -- TODO prove
    (∃ p₁ p₂, prime p₁ ∧ prime p₂ ∧ n = p₁ + p₂)

  def Goldbach's_weak_conjecture : Prop := ∀ n, (¬ (2 ∣ n) ∧ n > 5) →
    (∃ p₁ p₂ p₃, prime p₁ ∧ prime p₂ ∧ prime p₃ ∧ n = p₁ + p₂ + p₃)

  def Fermat's_last_theorem : Prop := ¬ (∃ (a b c n: ℕ),
    a > 0 ∧ b > 0 ∧ c > 0 ∧ n > 2 ∧ (a^n + b^n = c^n))
end ex4

section ex5
  variables (α : Type) (p q : α → Prop)
  -- assume that at α has at least one element
  variable a : α
  variable r : Prop

  -- exists elimination
  example : (∃ x : α, r) → r :=
    assume h: ∃ x: α, r,
    match h with ⟨_, hw⟩ :=
      hw
    end

  -- exists introduction
  example : r → (∃ x : α, r) :=
    assume hr: r,
    -- using assumption about non-emptiness of α here
    exists.intro a hr

  example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
  iff.intro
    (assume h: (∃ x, p x ∧ r),
      match h with ⟨x, hpr⟩ :=
        ⟨⟨x, hpr.left⟩, hpr.right⟩
      end)
    (assume h: (∃ x, p x) ∧ r,
      match h.left with ⟨x, hp⟩ :=
        ⟨x, hp, h.right⟩
      end)

  example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
    iff.intro
      (assume h: ∃ x, p x ∨ q x,
        match h with ⟨x, hpq⟩ :=
          or.elim hpq
            (assume hp: p x,
            or.inl ⟨x, hp⟩)
            (assume hq: q x,
            or.inr ⟨x, hq⟩)
        end)
      (assume h: (∃ x, p x) ∨ (∃ x, q x),
        or.elim h
          (assume hxp: ∃ x, p x,
          match hxp with ⟨x, hp⟩ :=
            ⟨x, or.inl hp⟩
          end)
          (assume hxq: ∃ x, q x,
          match hxq with ⟨x, hq⟩ :=
            ⟨x, or.inr hq⟩
          end))

  -- classical reasoning generally needed to show that something exists
  -- but not needed to show "doesn't exist", i.e. ¬(∃ x, blah x)
  -- i guess that's because "doesn't exist" doesn't require constructing an example
  theorem dne {a: Prop} (h: ¬¬a): a :=
    classical.by_cases
      (assume ha: a, ha)
      (assume hna: ¬a, absurd hna h)

  example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
    iff.intro
      (assume h: ∀ x, p x,
      assume : ∃ x, ¬p x,
      match this with ⟨x, hnp⟩ :=
        show false, from hnp (h x)
      end)
      (assume h: ¬(∃ x, ¬p x),
      -- forall_not_of_not_exists proven below
      have h1: ∀ x, ¬¬p x, from forall_not_of_not_exists h,
      show ∀ x, p x, from assume x,
      show p x, from dne (h1 x))

  example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
    iff.intro
      (assume h: ∃ x, p x,
      match h with ⟨x, hp⟩ :=
        assume hno: ∀ x, ¬p x,
        show false, from hno x hp
      end)
      (assume h: ¬(∀ x, ¬p x),
      classical.by_contradiction
        (assume : ¬(∃ x, p x),
        have ∀ x, ¬p x, from forall_not_of_not_exists this,
        show false, from h this))

  example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) :=
    iff.intro
      (assume h: ¬∃ x, p x,
      assume x, assume hp: p x,
      have ∃ x, p x, from ⟨x, hp⟩,
      h this)
      (assume h: ∀ x, ¬p x,
      assume : ∃ x, p x,
      match this with ⟨x, hp⟩ :=
        h x hp
      end)

  example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) :=
    iff.intro
      (assume h: ¬∀ x, p x,
      classical.by_contradiction
        (assume : ¬(∃ x, ¬p x),
        have ∀ x, ¬¬p x, from forall_not_of_not_exists this,
        have ∀ x, p x, from assume x, dne (this x),
        show false, from h this))
      (assume h: ∃ x, ¬p x,
      match h with ⟨x, _⟩ :=
        assume hno: ∀ x, p x,
        have p x, from hno x,
        ‹¬p x› this
      end)

  example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
    iff.intro
      (assume h: ∀ x, p x → r,
      assume : ∃ x, p x,
      match this with ⟨x, hp⟩ :=
        h x hp
      end)
      (assume h: (∃ x, p x) → r,
      assume x,
      assume : p x,
      h ⟨x, this⟩)

  -- assume decidability of propositions from now on
  local attribute [instance] classical.prop_decidable

  example : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
    iff.intro
      (assume ⟨x, hpr⟩,
      assume : ∀ x, p x,
      have p x, from this x,
      show r, from hpr this)
      (assume h: (∀ x, p x) → r,
      classical.by_contradiction
        (assume : ¬(∃ x, p x → r),
        have ∀ x, ¬(p x → r), from forall_not_of_not_exists this,
        have ∀ x, p x ∧ ¬r, from assume x, not_imp.mp (this x),
        have (∀ x, p x) ∧ ¬r, from and.intro
          (assume x, (this x).left)
          -- need non-emptiness of α
          (this a).right,
        show false, from this.right (h this.left)))

  example : (∃ x, r → p x) ↔ (r → ∃ x, p x) :=
    iff.intro
      (assume h: ∃ x, r → p x,
      match h with ⟨x, hrp⟩ :=
        assume hr: r,
        ⟨x, hrp hr⟩
      end)
      (assume h: r → ∃ x, p x,
      classical.by_contradiction
        (assume : ¬(∃ x, r → p x),
        have ∀ x, ¬(r → p x), from forall_not_of_not_exists this,
        have h1: ∀ x, r ∧ ¬p x, from assume x, not_imp.mp (this x),
        -- need non-emptiness of α
        have ∃ x, p x, from h (h1 a).left,
        have h2: ¬(∀ x, ¬p x), from not_forall_not.mpr this,
        have h3: ∀ x, ¬p x, from assume x, (h1 x).right,
        show false, from h2 h3))
end ex5

section ex6
  variables (real : Type) [ordered_ring real]
  variables (log exp : real → real)
  variable  log_exp_eq : ∀ x, log (exp x) = x
  variable  exp_log_eq : ∀ {x}, x > 0 → exp (log x) = x
  variable  exp_pos    : ∀ x, exp x > 0
  variable  exp_add    : ∀ x y, exp (x + y) = exp x * exp y

  -- this ensures the assumptions are available in tactic proofs
  include log_exp_eq exp_log_eq exp_pos exp_add

  example (x y z : real) :
    exp (x + y + z) = exp x * exp y * exp z :=
  by rw [exp_add, exp_add]

  example (y : real) (h : y > 0)  : exp (log y) = y :=
  exp_log_eq h

  theorem log_mul {x y : real} (hx : x > 0) (hy : y > 0) :
    log (x * y) = log x + log y :=
  calc
    log (x * y) = log (exp (log x) * y)           : by rw exp_log_eq hx
    ...         = log (exp (log x) * exp (log y)) : by rw exp_log_eq hy
    ...         = log (exp (log x + log y))       : by rw exp_add
    ...         = log x + log y                   : by rw log_exp_eq
end ex6

section ex7
  #check sub_self

  example (x : ℤ) : x * 0 = 0 :=
  calc
    x * 0 = x * (x - x)   : by rw sub_self
    ...   = x * x - x * x : by rw mul_sub
    ...   = 0             : by rw sub_self
end ex7