import data.vector .list .string

variables {α : Type} {k m n : nat}

namespace vector

def dot_prod [ring α] : ∀ {k}, vector α k → vector α k → α
| 0 v w     := 0
| (k+1) v w := v.head * w.head + dot_prod v.tail w.tail
infix `⬝` := dot_prod

def pad_length [has_repr α] : ∀ {n}, vector α n → nat
| 0 x := 0
| (n+1) x := max (has_repr.repr x.head).length x.tail.pad_length

def singleton (a : α) : vector α 1 := ⟨[a],rfl⟩

def add [has_add α] : ∀ {k : nat}, vector α k → vector α k → vector α k
| 0 x y := nil
| (k+1) x y :=
  let x' : vector α k := x.tail in
  let y' : vector α k := y.tail in
  cons (x.head + y.head) (add x' y')

instance has_add [has_add α] : has_add (vector α k) := ⟨add⟩

def sub [has_sub α] : ∀ {k : nat}, vector α k → vector α k → vector α k
| 0 x y := nil
| (k+1) x y :=
  let x' : vector α k := x.tail in
  let y' : vector α k := y.tail in
  cons (x.head - y.head) (sub x' y')

instance has_sub [has_sub α] : has_sub (vector α k) := ⟨sub⟩

def halve {k} (x : vector α (2^(k+1))) :
  vector α (2^k) × vector α (2^k) :=
( ⟨x.val.take (2^k),
  begin
    rw [list.length_take, x.property, min_eq_left],
    rw [nat.pow_succ, nat.mul_succ], apply nat.le_add_left
  end⟩,
  ⟨x.val.drop (2^k),
  begin
    rw [list.length_drop, x.property, nat.pow_succ,
      nat.mul_succ, nat.add_sub_cancel], simp
  end⟩ )

def double {k} :
  (vector α (2^k) × vector α (2^k)) → vector α (2^(k+1))
| ⟨x,y⟩ :=
  ⟨x.val ++ y.val,
   begin
     rw [list.length_append, x.property, y.property,
       nat.pow_succ, nat.mul_succ], simp
   end⟩

lemma double_halve {k} (x : vector α (2^(k+1))) :
  double (halve x) = x :=
begin
  apply vector.eq, simp [double, halve, to_list],
  apply list.append_take_drop
end

end vector

def row_to_string [has_repr α] (l) : ∀ {n}, vector α n → string
| 0 x     := " |"
| (n+1) x :=
  " | " ++ (has_repr.repr x.head).pad l ++ row_to_string x.tail

 #exit

def split (m n) (x : vector α (m+n)) : (vector α m × vector α n) :=
( ⟨(x.val.split m).fst, list.length_fst_split _ n _ x.property⟩,
  ⟨(x.val.split m).snd, list.length_snd_split _ n _ x.property⟩ )

lemma append_split (m n) (x : vector α (m+n)) :
  append (split m n x).fst (split m n x).snd = x :=
begin
  simp [split, append], apply vector.eq,
  simp [to_list], apply list.append_split
end


/-
This file defines the I-adic topology on a commutative ring R with ideal I.

The ring is wrapped in `adic_ring I := R`, which then receive all relevant type classes.
The end-product is `instance : topological_ring (adic_ring I)`.
-/

import tactic.ring
import data.pnat
import ring_theory.ideal_operations
import analysis.topology.topological_groups

open filter set

variables {R : Type*} [comm_ring R]

namespace filter
-- This will be the filter `nhds 0` in our adic-ring
-- The first mathematical key fact is this is indeed a filter
def of_ideal (I : ideal R): filter R :=
{ sets := {s : set R | ∃ n : ℕ, (I^n).carrier ⊆ s},
  univ_sets := ⟨0, by simp⟩,
  sets_of_superset := assume s t ⟨n, hn⟩ st, ⟨n, subset.trans hn st⟩,
  inter_sets := assume s t ⟨n, hn⟩ ⟨m, hm⟩,
    have (I ^ (n + m)).carrier ⊆ (I^n).carrier ∩ (I^m).carrier,
    by rw pow_add ; exact ideal.mul_le_inf,
    ⟨n + m, subset.trans this (inter_subset_inter hn hm)⟩ }

lemma mem_of_ideal_sets {I : ideal R} (s : set R) :
  s ∈ (filter.of_ideal I).sets ↔ ∃ n : ℕ, (I^n).carrier ⊆ s := iff.rfl

lemma mem_of_ideal_sets' {I : ideal R} (s : set R) :
  s ∈ (filter.of_ideal I).sets ↔ ∃ n > 0, (I^n).carrier ⊆ s :=
begin
  split,
  { rintros ⟨n, H⟩,
    cases n with n H,
    { rw univ_subset_iff.1 H,
      use [1, nat.one_pos],
      simp },
    { use [n+1, nat.add_pos_right n nat.one_pos, H] } },
  { rintros ⟨n, npos, H⟩,
    use [n, H] },
end

-- Next lemma is currently unused, but relates to standard mathlib definition style
lemma of_ideal_eq_infi (I : ideal R) :
  filter.of_ideal I = ⨅ n : ℕ, principal (I^n : ideal R) :=
begin
  apply filter_eq,
  rw infi_sets_eq,
  { ext U,
    simp [mem_of_ideal_sets, mem_Union, mem_principal_sets],
    exact iff.refl _ },
  { rintros n m,
    have : (I ^ (n + m)).carrier ⊆ (I^n).carrier ∩ (I^m).carrier,
    by rw pow_add ; exact ideal.mul_le_inf,
    cases (subset_inter_iff.1 this),
    use n+m,
    split ; intros U U_sub ; rw mem_principal_sets at * ;
    exact subset.trans (by assumption) U_sub },
  exact ⟨1⟩
end
end filter

-- Here we check our I-adic neighborhood of zero filter has the required properties to
-- be (nhds 0) in a uniform additive group
def add_group_with_zero_nhd.of_ideal (I : ideal R) : add_group_with_zero_nhd R :=
{ Z := filter.of_ideal I,
  zero_Z := assume U ⟨n, H⟩, mem_pure $ H (I^n).zero_mem,
  sub_Z := begin
             rw tendsto_prod_self_iff,
             rintros U ⟨n, h⟩,
             use [(I^n).carrier, n],
             intros x x' x_in x'_in,
             exact h ((I^n).sub_mem x_in x'_in),
           end,
  ..‹comm_ring R›}

def adic_topology (I : ideal R) : topological_space R :=
  @add_group_with_zero_nhd.topological_space R (add_group_with_zero_nhd.of_ideal I)

def adic_ring (I : ideal R) := R

namespace adic_ring
variable {I : ideal R}

instance : comm_ring (adic_ring I) := by unfold adic_ring ; apply_instance
instance : topological_space (adic_ring I) := adic_topology I

lemma nhds_zero_eq (I : ideal R) : (nhds (0 : adic_ring I)).sets = {s : set R | ∃ n : ℕ, (I^n).carrier ⊆ s} :=
begin
  rw add_group_with_zero_nhd.nhds_eq,
  dsimp [adic_ring],
  ext s,
  simp [filter.mem_of_ideal_sets],
  finish,
end

lemma nhds_eq (I : ideal R) {s : set (adic_ring I)} {a : adic_ring I}:
  s ∈ (nhds a).sets ↔ ∃ n : ℕ, (λ b, b + a) '' (I^n).carrier ⊆ s :=
begin
  rw [add_group_with_zero_nhd.nhds_eq, mem_map, ←add_group_with_zero_nhd.nhds_zero_eq_Z, nhds_zero_eq],
  split ;
  { rintros ⟨n, h⟩,
    use n,
    rwa image_subset_iff at * }
end

-- This is the second mathematical key fact: multiplication is continuous in I-adic topology
lemma continuous_mul' : continuous (λ (p : adic_ring I × adic_ring I), p.fst * p.snd) :=
continuous_iff_tendsto.2 $ assume ⟨x₀, y₀⟩,
begin
  rw nhds_prod_eq,
  rw tendsto_prod_iff,
  simp [adic_ring.nhds_eq I] at *,
  rintros V n hV,
  let J := I^n,
  use [has_add.add x₀ '' J.carrier, n],
  use [has_add.add y₀ '' J.carrier, n],
  rintros x y ⟨a, a_in, x₀a⟩ ⟨b, b_in, y₀b⟩,
  apply hV,
  have key : (x₀*b + y₀*a + a*b) + x₀*y₀ = x*y, by rw [←x₀a, ←y₀b] ; ring,
  use x₀*b + y₀*a + a*b,
  exact
  ⟨J.add_mem
     (J.add_mem (J.mul_mem_left b_in) (J.mul_mem_left a_in))
     (J.mul_mem_left b_in),
   key⟩,
end

instance : topological_add_group (adic_ring I) :=  by apply add_group_with_zero_nhd.topological_add_group
instance : uniform_space (adic_ring I) := topological_add_group.to_uniform_space _
instance : uniform_add_group (adic_ring I) := topological_add_group_is_uniform
instance : topological_ring (adic_ring I) :=
{ continuous_add := continuous_add',
  continuous_mul := continuous_mul',
  continuous_neg := continuous_neg' }
end adic_ring


import automata.dfa
import regular.regex
import data.list.basic
import regular.list_lemmas

open DFA list

namespace pumping

variables {S : Type} {Q : Type} {L : set (list S)} [fintype S] [fintype Q] [decidable_eq Q]
variables {w : list S}

lemma dfa_word_split (d : DFA S Q) (st : Q) (w : list S):
    (fintype.card Q) ≤ length w →
    ∃ (x y z : list S) (t : Q), x ++ y ++ z = w ∧ (x ++ y).length ≤ (fintype.card Q) ∧ y ≠ [] ∧ go d st x = t ∧ go d t y = t :=
begin
    rintro hlen,
    have tmp2 : (finset.univ : finset Q).card < (finset.range (fintype.card Q + 1)).card, from by {
        simp only [hlen, finset.card_range],
        rw nat.lt_succ_iff,
        refl,
    },
    have tmp3 := finset.exists_ne_map_eq_of_card_lt_of_maps_to tmp2,
    specialize tmp3 (λ a _, finset.mem_univ (go d st (take a w))),
    rcases tmp3 with ⟨x, hx, y, hy, x_ne_y, go_xy_eq⟩,
    rw finset.mem_range at hx hy,
    replace hx := nat.le_of_lt_succ hx,
    replace hy := nat.le_of_lt_succ hy,

    wlog x_lt_y : x ≤ y,
    replace x_lt_y := nat.lt_of_le_and_ne x_lt_y x_ne_y,

    use [take x $ take y w, drop x $ take y w, drop y w, go d st (take x w)],
    simp only [true_and, take_append_drop, eq_self_iff_true],
    refine ⟨_, _, _, _⟩, {
        rwa [length_take, min_eq_left (le_trans hy hlen)],
    }, {
        exact drop_of_take_of_lt_ne_nil x_lt_y (le_trans hy hlen),
    }, {
        rw [take_take, min_eq_left_of_lt x_lt_y],
    }, {
        rw [← dfa_go_append', go_xy_eq],
        congr,
        exact take_append_drop_of_lt x_lt_y,
    }
end

lemma dfa_go_repeat {d : DFA S Q} {st : Q} {w: list S} {k : ℕ} :
    go d st w = st → go d st (repeat w k).join = st :=
begin
    intro go_base,
    induction k, {
        simp only [join, go_finish, repeat],
    }, {
        simp only [join, repeat_succ],
        rwa [dfa_go_append', go_base],
    }
end

lemma pumping_lemma :
    dfa_lang L →
        (∃ (n : ℕ), ∀ w, w ∈ L → n ≤ length w →
        (∃ (x y z : list S), x ++ y ++ z = w ∧ y ≠ [] ∧ (x ++ y).length ≤ n ∧
        ∀ (k : ℕ), x ++ (repeat y k).join ++ z ∈ L)) :=
begin
    rintro ⟨Q, _, _, dfa, rfl⟩,
    resetI,
    use fintype.card Q,
    rintro w w_dfa w_len,

    rcases dfa_word_split dfa dfa.start w w_len with ⟨x, y, z, t, xyz, xy_len, ynil, hx, hy⟩,

    refine ⟨x, y, z, xyz, ynil, xy_len, λ k, _⟩,
    simp only [lang_of_dfa, dfa_accepts_word, set.mem_set_of_eq] at w_dfa ⊢,
    rw ← xyz at w_dfa,
    rw [append_assoc, dfa_go_append', hx, dfa_go_append'] at w_dfa ⊢,
    rw dfa_go_repeat hy,
    rwa hy at w_dfa,
end

lemma pumping_lemma_negation {L : set (list S)} :
    (∀ n : ℕ, ∃ (w : list S), w ∈ L ∧ n ≤ length w ∧
     ∀ (x y z : list S), x ++ y ++ z = w → y ≠ [] → (x ++ y).length ≤ n →
     ∃ k : ℕ, x ++ (repeat y k).join ++ z ∉ L) → ¬dfa_lang L:=
begin
    contrapose,
    push_neg,
    refine pumping_lemma,
end

end pumping


import MyNat.Definition
import MyNat.Addition
import Mathlib.Tactic.Relation.Symm
namespace MyNat
open MyNat

axiom zero_ne_succ (a : MyNat) : 0 ≠ succ a

/-!

# Advanced Addition World

## Level 9: `succ_ne_zero`

In this level we will use a new tactic, the [symm tactic](../Tactics/symm.lean.md).

`symm` turns goals of the form `⊢ A = B` to `⊢ B = A`.
This tactic is extensible, meaning you can add new `@[symm]`
attributes to things to teach `symm` new tricks, like we
did with the `simp` tactic.  To teach it how to deal with
`≠` we write this:
-/

@[symm] def neqSymm {α : Type} (a b: α) : a ≠ b → b ≠ a := Ne.symm

/-!

Levels 9 to 13 introduce the last axiom of Peano, namely
that `0 ≠ succ a`. The proof of this is called `zero_ne_succ a`.

`zero_ne_succ (a : MyNat) : 0 ≠ succ a`

We can simply use the `symm` tactic to flip this goal into
`succ a ≠ 0` which then matches our `zero_ne_succ` axiom.

## Theorem : succ_ne_zero
Zero is not the successor of any natural number.
-/

theorem succ_ne_zero (a : MyNat) : succ a ≠ 0 := by
  symm
  apply (zero_ne_succ a)

/-!
Next up [Level 10](./Level10.lean.md)
-/


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Mario Carneiro

! This file was ported from Lean 3 source module data.nat.pairing
! leanprover-community/mathlib commit c3291da49cfa65f0d43b094750541c0731edc932
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Nat.Sqrt
import Mathbin.Data.Set.Lattice
import Mathbin.Algebra.Group.Prod
import Mathbin.Algebra.Order.Monoid.MinMax

/-!
#  Naturals pairing function

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

This file defines a pairing function for the naturals as follows:
```text
 0  1  4  9 16
 2  3  5 10 17
 6  7  8 11 18
12 13 14 15 19
20 21 22 23 24
```

It has the advantage of being monotone in both directions and sending `⟦0, n^2 - 1⟧` to
`⟦0, n - 1⟧²`.
-/

open Prod Decidable Function

namespace Nat

#print Nat.pair /-
/-- Pairing function for the natural numbers. -/
@[pp_nodot]
def pair (a b : ℕ) : ℕ :=
  if a < b then b * b + a else a * a + a + b
#align nat.mkpair Nat.pair
-/

#print Nat.unpair /-
/-- Unpairing function for the natural numbers. -/
@[pp_nodot]
def unpair (n : ℕ) : ℕ × ℕ :=
  let s := sqrt n
  if n - s * s < s then (n - s * s, s) else (s, n - s * s - s)
#align nat.unpair Nat.unpair
-/

#print Nat.pair_unpair /-
@[simp]
theorem pair_unpair (n : ℕ) : pair (unpair n).1 (unpair n).2 = n :=
  by
  dsimp only [unpair]; set s := sqrt n
  have sm : s * s + (n - s * s) = n := add_tsub_cancel_of_le (sqrt_le _)
  split_ifs
  · simp [mkpair, h, sm]
  · have hl : n - s * s - s ≤ s :=
      tsub_le_iff_left.mpr (tsub_le_iff_left.mpr <| by rw [← add_assoc] <;> apply sqrt_le_add)
    simp [mkpair, hl.not_lt, add_assoc, add_tsub_cancel_of_le (le_of_not_gt h), sm]
#align nat.mkpair_unpair Nat.pair_unpair
-/

#print Nat.pair_unpair' /-
theorem pair_unpair' {n a b} (H : unpair n = (a, b)) : pair a b = n := by
  simpa [H] using mkpair_unpair n
#align nat.mkpair_unpair' Nat.pair_unpair'
-/

#print Nat.unpair_pair /-
@[simp]
theorem unpair_pair (a b : ℕ) : unpair (pair a b) = (a, b) :=
  by
  dsimp only [mkpair]; split_ifs
  · show unpair (b * b + a) = (a, b)
    have be : sqrt (b * b + a) = b := sqrt_add_eq _ (le_trans (le_of_lt h) (Nat.le_add_left _ _))
    simp [unpair, be, add_tsub_cancel_right, h]
  · show unpair (a * a + a + b) = (a, b)
    have ae : sqrt (a * a + (a + b)) = a :=
      by
      rw [sqrt_add_eq]
      exact add_le_add_left (le_of_not_gt h) _
    simp [unpair, ae, Nat.not_lt_zero, add_assoc]
#align nat.unpair_mkpair Nat.unpair_pair
-/

#print Nat.pairEquiv /-
/-- An equivalence between `ℕ × ℕ` and `ℕ`. -/
@[simps (config := { fullyApplied := false })]
def pairEquiv : ℕ × ℕ ≃ ℕ :=
  ⟨uncurry pair, unpair, fun ⟨a, b⟩ => unpair_pair a b, pair_unpair⟩
#align nat.mkpair_equiv Nat.pairEquiv
-/

#print Nat.surjective_unpair /-
theorem surjective_unpair : Surjective unpair :=
  pairEquiv.symm.Surjective
#align nat.surjective_unpair Nat.surjective_unpair
-/

#print Nat.pair_eq_pair /-
@[simp]
theorem pair_eq_pair {a b c d : ℕ} : pair a b = pair c d ↔ a = c ∧ b = d :=
  pairEquiv.Injective.eq_iff.trans (@Prod.ext_iff ℕ ℕ (a, b) (c, d))
#align nat.mkpair_eq_mkpair Nat.pair_eq_pair
-/

#print Nat.unpair_lt /-
theorem unpair_lt {n : ℕ} (n1 : 1 ≤ n) : (unpair n).1 < n :=
  by
  let s := sqrt n
  simp [unpair]; change sqrt n with s
  by_cases h : n - s * s < s <;> simp [h]
  · exact lt_of_lt_of_le h (sqrt_le_self _)
  · simp at h
    have s0 : 0 < s := sqrt_pos.2 n1
    exact lt_of_le_of_lt h (tsub_lt_self n1 (mul_pos s0 s0))
#align nat.unpair_lt Nat.unpair_lt
-/

#print Nat.unpair_zero /-
@[simp]
theorem unpair_zero : unpair 0 = 0 := by
  rw [unpair]
  simp
#align nat.unpair_zero Nat.unpair_zero
-/

#print Nat.unpair_left_le /-
theorem unpair_left_le : ∀ n : ℕ, (unpair n).1 ≤ n
  | 0 => by simp
  | n + 1 => le_of_lt (unpair_lt (Nat.succ_pos _))
#align nat.unpair_left_le Nat.unpair_left_le
-/

#print Nat.left_le_pair /-
theorem left_le_pair (a b : ℕ) : a ≤ pair a b := by simpa using unpair_left_le (mkpair a b)
#align nat.left_le_mkpair Nat.left_le_pair
-/

#print Nat.right_le_pair /-
theorem right_le_pair (a b : ℕ) : b ≤ pair a b :=
  by
  by_cases h : a < b <;> simp [mkpair, h]
  exact le_trans (le_mul_self _) (Nat.le_add_right _ _)
#align nat.right_le_mkpair Nat.right_le_pair
-/

#print Nat.unpair_right_le /-
theorem unpair_right_le (n : ℕ) : (unpair n).2 ≤ n := by
  simpa using right_le_mkpair n.unpair.1 n.unpair.2
#align nat.unpair_right_le Nat.unpair_right_le
-/

#print Nat.pair_lt_pair_left /-
theorem pair_lt_pair_left {a₁ a₂} (b) (h : a₁ < a₂) : pair a₁ b < pair a₂ b :=
  by
  by_cases h₁ : a₁ < b <;> simp [mkpair, h₁, add_assoc]
  · by_cases h₂ : a₂ < b <;> simp [mkpair, h₂, h]
    simp at h₂
    apply add_lt_add_of_le_of_lt
    exact mul_self_le_mul_self h₂
    exact lt_add_right _ _ _ h
  · simp at h₁
    simp [not_lt_of_gt (lt_of_le_of_lt h₁ h)]
    apply add_lt_add
    exact mul_self_lt_mul_self h
    apply add_lt_add_right <;> assumption
#align nat.mkpair_lt_mkpair_left Nat.pair_lt_pair_left
-/

#print Nat.pair_lt_pair_right /-
theorem pair_lt_pair_right (a) {b₁ b₂} (h : b₁ < b₂) : pair a b₁ < pair a b₂ :=
  by
  by_cases h₁ : a < b₁ <;> simp [mkpair, h₁, add_assoc]
  · simp [mkpair, lt_trans h₁ h, h]
    exact mul_self_lt_mul_self h
  · by_cases h₂ : a < b₂ <;> simp [mkpair, h₂, h]
    simp at h₁
    rw [add_comm, add_comm _ a, add_assoc, add_lt_add_iff_left]
    rwa [add_comm, ← sqrt_lt, sqrt_add_eq]
    exact le_trans h₁ (Nat.le_add_left _ _)
#align nat.mkpair_lt_mkpair_right Nat.pair_lt_pair_right
-/

/- warning: nat.mkpair_lt_max_add_one_sq -> Nat.pair_lt_max_add_one_sq is a dubious translation:
lean 3 declaration is
  forall (m : Nat) (n : Nat), LT.lt.{0} Nat Nat.hasLt (Nat.pair m n) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (LinearOrder.max.{0} Nat Nat.linearOrder m n) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) (OfNat.ofNat.{0} Nat 2 (OfNat.mk.{0} Nat 2 (bit0.{0} Nat Nat.hasAdd (One.one.{0} Nat Nat.hasOne)))))
but is expected to have type
  forall (m : Nat) (n : Nat), LT.lt.{0} Nat instLTNat (Nat.pair m n) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (Max.max.{0} Nat Nat.instMaxNat m n) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2)))
Case conversion may be inaccurate. Consider using '#align nat.mkpair_lt_max_add_one_sq Nat.pair_lt_max_add_one_sqₓ'. -/
theorem pair_lt_max_add_one_sq (m n : ℕ) : pair m n < (max m n + 1) ^ 2 :=
  by
  rw [mkpair, add_sq, mul_one, two_mul, sq, add_assoc, add_assoc]
  cases lt_or_le m n
  · rw [if_pos h, max_eq_right h.le, add_lt_add_iff_left, add_assoc]
    exact h.trans_le (self_le_add_right n _)
  · rw [if_neg h.not_lt, max_eq_left h, add_lt_add_iff_left, add_assoc, add_lt_add_iff_left]
    exact lt_succ_of_le h
#align nat.mkpair_lt_max_add_one_sq Nat.pair_lt_max_add_one_sq

/- warning: nat.max_sq_add_min_le_mkpair -> Nat.max_sq_add_min_le_pair is a dubious translation:
lean 3 declaration is
  forall (m : Nat) (n : Nat), LE.le.{0} Nat Nat.hasLe (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) (LinearOrder.max.{0} Nat Nat.linearOrder m n) (OfNat.ofNat.{0} Nat 2 (OfNat.mk.{0} Nat 2 (bit0.{0} Nat Nat.hasAdd (One.one.{0} Nat Nat.hasOne))))) (LinearOrder.min.{0} Nat Nat.linearOrder m n)) (Nat.pair m n)
but is expected to have type
  forall (m : Nat) (n : Nat), LE.le.{0} Nat instLENat (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) (Max.max.{0} Nat Nat.instMaxNat m n) (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2))) (Min.min.{0} Nat instMinNat m n)) (Nat.pair m n)
Case conversion may be inaccurate. Consider using '#align nat.max_sq_add_min_le_mkpair Nat.max_sq_add_min_le_pairₓ'. -/
theorem max_sq_add_min_le_pair (m n : ℕ) : max m n ^ 2 + min m n ≤ pair m n :=
  by
  rw [mkpair]
  cases lt_or_le m n
  · rw [if_pos h, max_eq_right h.le, min_eq_left h.le, sq]
  · rw [if_neg h.not_lt, max_eq_left h, min_eq_right h, sq, add_assoc, add_le_add_iff_left]
    exact le_add_self
#align nat.max_sq_add_min_le_mkpair Nat.max_sq_add_min_le_pair

#print Nat.add_le_pair /-
theorem add_le_pair (m n : ℕ) : m + n ≤ pair m n :=
  (max_sq_add_min_le_pair _ _).trans' <|
    by
    rw [sq, ← min_add_max, add_comm, add_le_add_iff_right]
    exact le_mul_self _
#align nat.add_le_mkpair Nat.add_le_pair
-/

#print Nat.unpair_add_le /-
theorem unpair_add_le (n : ℕ) : (unpair n).1 + (unpair n).2 ≤ n :=
  (add_le_pair _ _).trans_eq (pair_unpair _)
#align nat.unpair_add_le Nat.unpair_add_le
-/

end Nat

open Nat

section CompleteLattice

/- warning: supr_unpair -> supᵢ_unpair is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CompleteLattice.{u1} α] (f : Nat -> Nat -> α), Eq.{succ u1} α (supᵢ.{u1, 1} α (CompleteSemilatticeSup.toHasSup.{u1} α (CompleteLattice.toCompleteSemilatticeSup.{u1} α _inst_1)) Nat (fun (n : Nat) => f (Prod.fst.{0, 0} Nat Nat (Nat.unpair n)) (Prod.snd.{0, 0} Nat Nat (Nat.unpair n)))) (supᵢ.{u1, 1} α (CompleteSemilatticeSup.toHasSup.{u1} α (CompleteLattice.toCompleteSemilatticeSup.{u1} α _inst_1)) Nat (fun (i : Nat) => supᵢ.{u1, 1} α (CompleteSemilatticeSup.toHasSup.{u1} α (CompleteLattice.toCompleteSemilatticeSup.{u1} α _inst_1)) Nat (fun (j : Nat) => f i j)))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CompleteLattice.{u1} α] (f : Nat -> Nat -> α), Eq.{succ u1} α (supᵢ.{u1, 1} α (CompleteLattice.toSupSet.{u1} α _inst_1) Nat (fun (n : Nat) => f (Prod.fst.{0, 0} Nat Nat (Nat.unpair n)) (Prod.snd.{0, 0} Nat Nat (Nat.unpair n)))) (supᵢ.{u1, 1} α (CompleteLattice.toSupSet.{u1} α _inst_1) Nat (fun (i : Nat) => supᵢ.{u1, 1} α (CompleteLattice.toSupSet.{u1} α _inst_1) Nat (fun (j : Nat) => f i j)))
Case conversion may be inaccurate. Consider using '#align supr_unpair supᵢ_unpairₓ'. -/
/- ./././Mathport/Syntax/Translate/Expr.lean:107:6: warning: expanding binder group (i j) -/
theorem supᵢ_unpair {α} [CompleteLattice α] (f : ℕ → ℕ → α) :
    (⨆ n : ℕ, f n.unpair.1 n.unpair.2) = ⨆ (i : ℕ) (j : ℕ), f i j := by
  rw [← (supᵢ_prod : (⨆ i : ℕ × ℕ, f i.1 i.2) = _), ← nat.surjective_unpair.supr_comp]
#align supr_unpair supᵢ_unpair

/- warning: infi_unpair -> infᵢ_unpair is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CompleteLattice.{u1} α] (f : Nat -> Nat -> α), Eq.{succ u1} α (infᵢ.{u1, 1} α (CompleteSemilatticeInf.toHasInf.{u1} α (CompleteLattice.toCompleteSemilatticeInf.{u1} α _inst_1)) Nat (fun (n : Nat) => f (Prod.fst.{0, 0} Nat Nat (Nat.unpair n)) (Prod.snd.{0, 0} Nat Nat (Nat.unpair n)))) (infᵢ.{u1, 1} α (CompleteSemilatticeInf.toHasInf.{u1} α (CompleteLattice.toCompleteSemilatticeInf.{u1} α _inst_1)) Nat (fun (i : Nat) => infᵢ.{u1, 1} α (CompleteSemilatticeInf.toHasInf.{u1} α (CompleteLattice.toCompleteSemilatticeInf.{u1} α _inst_1)) Nat (fun (j : Nat) => f i j)))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CompleteLattice.{u1} α] (f : Nat -> Nat -> α), Eq.{succ u1} α (infᵢ.{u1, 1} α (CompleteLattice.toInfSet.{u1} α _inst_1) Nat (fun (n : Nat) => f (Prod.fst.{0, 0} Nat Nat (Nat.unpair n)) (Prod.snd.{0, 0} Nat Nat (Nat.unpair n)))) (infᵢ.{u1, 1} α (CompleteLattice.toInfSet.{u1} α _inst_1) Nat (fun (i : Nat) => infᵢ.{u1, 1} α (CompleteLattice.toInfSet.{u1} α _inst_1) Nat (fun (j : Nat) => f i j)))
Case conversion may be inaccurate. Consider using '#align infi_unpair infᵢ_unpairₓ'. -/
/- ./././Mathport/Syntax/Translate/Expr.lean:107:6: warning: expanding binder group (i j) -/
theorem infᵢ_unpair {α} [CompleteLattice α] (f : ℕ → ℕ → α) :
    (⨅ n : ℕ, f n.unpair.1 n.unpair.2) = ⨅ (i : ℕ) (j : ℕ), f i j :=
  supᵢ_unpair (show ℕ → ℕ → αᵒᵈ from f)
#align infi_unpair infᵢ_unpair

end CompleteLattice

namespace Set

/- warning: set.Union_unpair_prod -> Set.unionᵢ_unpair_prod is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {s : Nat -> (Set.{u1} α)} {t : Nat -> (Set.{u2} β)}, Eq.{succ (max u1 u2)} (Set.{max u1 u2} (Prod.{u1, u2} α β)) (Set.unionᵢ.{max u1 u2, 1} (Prod.{u1, u2} α β) Nat (fun (n : Nat) => Set.prod.{u1, u2} α β (s (Prod.fst.{0, 0} Nat Nat (Nat.unpair n))) (t (Prod.snd.{0, 0} Nat Nat (Nat.unpair n))))) (Set.prod.{u1, u2} α β (Set.unionᵢ.{u1, 1} α Nat (fun (n : Nat) => s n)) (Set.unionᵢ.{u2, 1} β Nat (fun (n : Nat) => t n)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {s : Nat -> (Set.{u2} α)} {t : Nat -> (Set.{u1} β)}, Eq.{max (succ u2) (succ u1)} (Set.{max u1 u2} (Prod.{u2, u1} α β)) (Set.unionᵢ.{max u1 u2, 1} (Prod.{u2, u1} α β) Nat (fun (n : Nat) => Set.prod.{u2, u1} α β (s (Prod.fst.{0, 0} Nat Nat (Nat.unpair n))) (t (Prod.snd.{0, 0} Nat Nat (Nat.unpair n))))) (Set.prod.{u2, u1} α β (Set.unionᵢ.{u2, 1} α Nat (fun (n : Nat) => s n)) (Set.unionᵢ.{u1, 1} β Nat (fun (n : Nat) => t n)))
Case conversion may be inaccurate. Consider using '#align set.Union_unpair_prod Set.unionᵢ_unpair_prodₓ'. -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem unionᵢ_unpair_prod {α β} {s : ℕ → Set α} {t : ℕ → Set β} :
    (⋃ n : ℕ, s n.unpair.fst ×ˢ t n.unpair.snd) = (⋃ n, s n) ×ˢ ⋃ n, t n :=
  by
  rw [← Union_prod]
  convert surjective_unpair.Union_comp _
  rfl
#align set.Union_unpair_prod Set.unionᵢ_unpair_prod

/- ./././Mathport/Syntax/Translate/Expr.lean:107:6: warning: expanding binder group (i j) -/
#print Set.unionᵢ_unpair /-
theorem unionᵢ_unpair {α} (f : ℕ → ℕ → Set α) :
    (⋃ n : ℕ, f n.unpair.1 n.unpair.2) = ⋃ (i : ℕ) (j : ℕ), f i j :=
  supᵢ_unpair f
#align set.Union_unpair Set.unionᵢ_unpair
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:107:6: warning: expanding binder group (i j) -/
#print Set.interᵢ_unpair /-
theorem interᵢ_unpair {α} (f : ℕ → ℕ → Set α) :
    (⋂ n : ℕ, f n.unpair.1 n.unpair.2) = ⋂ (i : ℕ) (j : ℕ), f i j :=
  infᵢ_unpair f
#align set.Inter_unpair Set.interᵢ_unpair
-/

end Set


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

! This file was ported from Lean 3 source module probability.moments
! leanprover-community/mathlib commit 85453a2a14be8da64caf15ca50930cf4c6e5d8de
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Probability.Variance

/-!
# Moments and moment generating function

## Main definitions

* `probability_theory.moment X p μ`: `p`th moment of a real random variable `X` with respect to
  measure `μ`, `μ[X^p]`
* `probability_theory.central_moment X p μ`:`p`th central moment of `X` with respect to measure `μ`,
  `μ[(X - μ[X])^p]`
* `probability_theory.mgf X μ t`: moment generating function of `X` with respect to measure `μ`,
  `μ[exp(t*X)]`
* `probability_theory.cgf X μ t`: cumulant generating function, logarithm of the moment generating
  function

## Main results

* `probability_theory.indep_fun.mgf_add`: if two real random variables `X` and `Y` are independent
  and their mgf are defined at `t`, then `mgf (X + Y) μ t = mgf X μ t * mgf Y μ t`
* `probability_theory.indep_fun.cgf_add`: if two real random variables `X` and `Y` are independent
  and their mgf are defined at `t`, then `cgf (X + Y) μ t = cgf X μ t + cgf Y μ t`
* `probability_theory.measure_ge_le_exp_cgf` and `probability_theory.measure_le_le_exp_cgf`:
  Chernoff bound on the upper (resp. lower) tail of a random variable. For `t` nonnegative such that
  the cgf exists, `ℙ(ε ≤ X) ≤ exp(- t*ε + cgf X ℙ t)`. See also
  `probability_theory.measure_ge_le_exp_mul_mgf` and
  `probability_theory.measure_le_le_exp_mul_mgf` for versions of these results using `mgf` instead
  of `cgf`.

-/

open MeasureTheory Filter Finset Real

noncomputable section

open BigOperators MeasureTheory ProbabilityTheory ENNReal NNReal

namespace ProbabilityTheory

variable {Ω ι : Type _} {m : MeasurableSpace Ω} {X : Ω → ℝ} {p : ℕ} {μ : Measure Ω}

include m

/-- Moment of a real random variable, `μ[X ^ p]`. -/
def moment (X : Ω → ℝ) (p : ℕ) (μ : Measure Ω) : ℝ :=
  μ[X ^ p]
#align probability_theory.moment ProbabilityTheory.moment

/-- Central moment of a real random variable, `μ[(X - μ[X]) ^ p]`. -/
def centralMoment (X : Ω → ℝ) (p : ℕ) (μ : Measure Ω) : ℝ :=
  μ[(X - fun x => μ[X]) ^ p]
#align probability_theory.central_moment ProbabilityTheory.centralMoment

@[simp]
theorem moment_zero (hp : p ≠ 0) : moment 0 p μ = 0 := by
  simp only [moment, hp, zero_pow', Ne.def, not_false_iff, Pi.zero_apply, integral_const,
    Algebra.id.smul_eq_mul, MulZeroClass.mul_zero]
#align probability_theory.moment_zero ProbabilityTheory.moment_zero

@[simp]
theorem centralMoment_zero (hp : p ≠ 0) : centralMoment 0 p μ = 0 := by
  simp only [central_moment, hp, Pi.zero_apply, integral_const, Algebra.id.smul_eq_mul,
    MulZeroClass.mul_zero, zero_sub, Pi.pow_apply, Pi.neg_apply, neg_zero, zero_pow', Ne.def,
    not_false_iff]
#align probability_theory.central_moment_zero ProbabilityTheory.centralMoment_zero

theorem centralMoment_one' [IsFiniteMeasure μ] (h_int : Integrable X μ) :
    centralMoment X 1 μ = (1 - (μ Set.univ).toReal) * μ[X] :=
  by
  simp only [central_moment, Pi.sub_apply, pow_one]
  rw [integral_sub h_int (integrable_const _)]
  simp only [sub_mul, integral_const, Algebra.id.smul_eq_mul, one_mul]
#align probability_theory.central_moment_one' ProbabilityTheory.centralMoment_one'

@[simp]
theorem centralMoment_one [IsProbabilityMeasure μ] : centralMoment X 1 μ = 0 :=
  by
  by_cases h_int : integrable X μ
  · rw [central_moment_one' h_int]
    simp only [measure_univ, ENNReal.one_toReal, sub_self, MulZeroClass.zero_mul]
  · simp only [central_moment, Pi.sub_apply, pow_one]
    have : ¬integrable (fun x => X x - integral μ X) μ :=
      by
      refine' fun h_sub => h_int _
      have h_add : X = (fun x => X x - integral μ X) + fun x => integral μ X :=
        by
        ext1 x
        simp
      rw [h_add]
      exact h_sub.add (integrable_const _)
    rw [integral_undef this]
#align probability_theory.central_moment_one ProbabilityTheory.centralMoment_one

theorem centralMoment_two_eq_variance [IsFiniteMeasure μ] (hX : Memℒp X 2 μ) :
    centralMoment X 2 μ = variance X μ :=
  by
  rw [hX.variance_eq]
  rfl
#align probability_theory.central_moment_two_eq_variance ProbabilityTheory.centralMoment_two_eq_variance

section MomentGeneratingFunction

variable {t : ℝ}

/-- Moment generating function of a real random variable `X`: `λ t, μ[exp(t*X)]`. -/
def mgf (X : Ω → ℝ) (μ : Measure Ω) (t : ℝ) : ℝ :=
  μ[fun ω => exp (t * X ω)]
#align probability_theory.mgf ProbabilityTheory.mgf

/-- Cumulant generating function of a real random variable `X`: `λ t, log μ[exp(t*X)]`. -/
def cgf (X : Ω → ℝ) (μ : Measure Ω) (t : ℝ) : ℝ :=
  log (mgf X μ t)
#align probability_theory.cgf ProbabilityTheory.cgf

@[simp]
theorem mgf_zero_fun : mgf 0 μ t = (μ Set.univ).toReal := by
  simp only [mgf, Pi.zero_apply, MulZeroClass.mul_zero, exp_zero, integral_const,
    Algebra.id.smul_eq_mul, mul_one]
#align probability_theory.mgf_zero_fun ProbabilityTheory.mgf_zero_fun

@[simp]
theorem cgf_zero_fun : cgf 0 μ t = log (μ Set.univ).toReal := by simp only [cgf, mgf_zero_fun]
#align probability_theory.cgf_zero_fun ProbabilityTheory.cgf_zero_fun

@[simp]
theorem mgf_zero_measure : mgf X (0 : Measure Ω) t = 0 := by simp only [mgf, integral_zero_measure]
#align probability_theory.mgf_zero_measure ProbabilityTheory.mgf_zero_measure

@[simp]
theorem cgf_zero_measure : cgf X (0 : Measure Ω) t = 0 := by
  simp only [cgf, log_zero, mgf_zero_measure]
#align probability_theory.cgf_zero_measure ProbabilityTheory.cgf_zero_measure

@[simp]
theorem mgf_const' (c : ℝ) : mgf (fun _ => c) μ t = (μ Set.univ).toReal * exp (t * c) := by
  simp only [mgf, integral_const, Algebra.id.smul_eq_mul]
#align probability_theory.mgf_const' ProbabilityTheory.mgf_const'

@[simp]
theorem mgf_const (c : ℝ) [IsProbabilityMeasure μ] : mgf (fun _ => c) μ t = exp (t * c) := by
  simp only [mgf_const', measure_univ, ENNReal.one_toReal, one_mul]
#align probability_theory.mgf_const ProbabilityTheory.mgf_const

@[simp]
theorem cgf_const' [IsFiniteMeasure μ] (hμ : μ ≠ 0) (c : ℝ) :
    cgf (fun _ => c) μ t = log (μ Set.univ).toReal + t * c :=
  by
  simp only [cgf, mgf_const']
  rw [log_mul _ (exp_pos _).ne']
  · rw [log_exp _]
  · rw [Ne.def, ENNReal.toReal_eq_zero_iff, measure.measure_univ_eq_zero]
    simp only [hμ, measure_ne_top μ Set.univ, or_self_iff, not_false_iff]
#align probability_theory.cgf_const' ProbabilityTheory.cgf_const'

@[simp]
theorem cgf_const [IsProbabilityMeasure μ] (c : ℝ) : cgf (fun _ => c) μ t = t * c := by
  simp only [cgf, mgf_const, log_exp]
#align probability_theory.cgf_const ProbabilityTheory.cgf_const

@[simp]
theorem mgf_zero' : mgf X μ 0 = (μ Set.univ).toReal := by
  simp only [mgf, MulZeroClass.zero_mul, exp_zero, integral_const, Algebra.id.smul_eq_mul, mul_one]
#align probability_theory.mgf_zero' ProbabilityTheory.mgf_zero'

@[simp]
theorem mgf_zero [IsProbabilityMeasure μ] : mgf X μ 0 = 1 := by
  simp only [mgf_zero', measure_univ, ENNReal.one_toReal]
#align probability_theory.mgf_zero ProbabilityTheory.mgf_zero

@[simp]
theorem cgf_zero' : cgf X μ 0 = log (μ Set.univ).toReal := by simp only [cgf, mgf_zero']
#align probability_theory.cgf_zero' ProbabilityTheory.cgf_zero'

@[simp]
theorem cgf_zero [IsProbabilityMeasure μ] : cgf X μ 0 = 0 := by
  simp only [cgf_zero', measure_univ, ENNReal.one_toReal, log_one]
#align probability_theory.cgf_zero ProbabilityTheory.cgf_zero

theorem mgf_undef (hX : ¬Integrable (fun ω => exp (t * X ω)) μ) : mgf X μ t = 0 := by
  simp only [mgf, integral_undef hX]
#align probability_theory.mgf_undef ProbabilityTheory.mgf_undef

theorem cgf_undef (hX : ¬Integrable (fun ω => exp (t * X ω)) μ) : cgf X μ t = 0 := by
  simp only [cgf, mgf_undef hX, log_zero]
#align probability_theory.cgf_undef ProbabilityTheory.cgf_undef

theorem mgf_nonneg : 0 ≤ mgf X μ t :=
  by
  refine' integral_nonneg _
  intro ω
  simp only [Pi.zero_apply]
  exact (exp_pos _).le
#align probability_theory.mgf_nonneg ProbabilityTheory.mgf_nonneg

theorem mgf_pos' (hμ : μ ≠ 0) (h_int_X : Integrable (fun ω => exp (t * X ω)) μ) : 0 < mgf X μ t :=
  by
  simp_rw [mgf]
  have : (∫ x : Ω, exp (t * X x) ∂μ) = ∫ x : Ω in Set.univ, exp (t * X x) ∂μ := by
    simp only [measure.restrict_univ]
  rw [this, set_integral_pos_iff_support_of_nonneg_ae _ _]
  · have h_eq_univ : (Function.support fun x : Ω => exp (t * X x)) = Set.univ :=
      by
      ext1 x
      simp only [Function.mem_support, Set.mem_univ, iff_true_iff]
      exact (exp_pos _).ne'
    rw [h_eq_univ, Set.inter_univ _]
    refine' Ne.bot_lt _
    simp only [hμ, ENNReal.bot_eq_zero, Ne.def, measure.measure_univ_eq_zero, not_false_iff]
  · refine' eventually_of_forall fun x => _
    rw [Pi.zero_apply]
    exact (exp_pos _).le
  · rwa [integrable_on_univ]
#align probability_theory.mgf_pos' ProbabilityTheory.mgf_pos'

theorem mgf_pos [IsProbabilityMeasure μ] (h_int_X : Integrable (fun ω => exp (t * X ω)) μ) :
    0 < mgf X μ t :=
  mgf_pos' (IsProbabilityMeasure.ne_zero μ) h_int_X
#align probability_theory.mgf_pos ProbabilityTheory.mgf_pos

theorem mgf_neg : mgf (-X) μ t = mgf X μ (-t) := by simp_rw [mgf, Pi.neg_apply, mul_neg, neg_mul]
#align probability_theory.mgf_neg ProbabilityTheory.mgf_neg

theorem cgf_neg : cgf (-X) μ t = cgf X μ (-t) := by simp_rw [cgf, mgf_neg]
#align probability_theory.cgf_neg ProbabilityTheory.cgf_neg

/-- This is a trivial application of `indep_fun.comp` but it will come up frequently. -/
theorem IndepFunCat.expMul {X Y : Ω → ℝ} (h_indep : IndepFunCat X Y μ) (s t : ℝ) :
    IndepFunCat (fun ω => exp (s * X ω)) (fun ω => exp (t * Y ω)) μ :=
  by
  have h_meas : ∀ t, Measurable fun x => exp (t * x) := fun t => (measurable_id'.const_mul t).exp
  change indep_fun ((fun x => exp (s * x)) ∘ X) ((fun x => exp (t * x)) ∘ Y) μ
  exact indep_fun.comp h_indep (h_meas s) (h_meas t)
#align probability_theory.indep_fun.exp_mul ProbabilityTheory.IndepFunCat.expMul

theorem IndepFunCat.mgf_add {X Y : Ω → ℝ} (h_indep : IndepFunCat X Y μ)
    (hX : AeStronglyMeasurable (fun ω => exp (t * X ω)) μ)
    (hY : AeStronglyMeasurable (fun ω => exp (t * Y ω)) μ) :
    mgf (X + Y) μ t = mgf X μ t * mgf Y μ t :=
  by
  simp_rw [mgf, Pi.add_apply, mul_add, exp_add]
  exact (h_indep.exp_mul t t).integral_mul hX hY
#align probability_theory.indep_fun.mgf_add ProbabilityTheory.IndepFunCat.mgf_add

theorem IndepFunCat.mgf_add' {X Y : Ω → ℝ} (h_indep : IndepFunCat X Y μ)
    (hX : AeStronglyMeasurable X μ) (hY : AeStronglyMeasurable Y μ) :
    mgf (X + Y) μ t = mgf X μ t * mgf Y μ t :=
  by
  have A : Continuous fun x : ℝ => exp (t * x) := by continuity
  have h'X : ae_strongly_measurable (fun ω => exp (t * X ω)) μ :=
    A.ae_strongly_measurable.comp_ae_measurable hX.ae_measurable
  have h'Y : ae_strongly_measurable (fun ω => exp (t * Y ω)) μ :=
    A.ae_strongly_measurable.comp_ae_measurable hY.ae_measurable
  exact h_indep.mgf_add h'X h'Y
#align probability_theory.indep_fun.mgf_add' ProbabilityTheory.IndepFunCat.mgf_add'

theorem IndepFunCat.cgf_add {X Y : Ω → ℝ} (h_indep : IndepFunCat X Y μ)
    (h_int_X : Integrable (fun ω => exp (t * X ω)) μ)
    (h_int_Y : Integrable (fun ω => exp (t * Y ω)) μ) : cgf (X + Y) μ t = cgf X μ t + cgf Y μ t :=
  by
  by_cases hμ : μ = 0
  · simp [hμ]
  simp only [cgf, h_indep.mgf_add h_int_X.ae_strongly_measurable h_int_Y.ae_strongly_measurable]
  exact log_mul (mgf_pos' hμ h_int_X).ne' (mgf_pos' hμ h_int_Y).ne'
#align probability_theory.indep_fun.cgf_add ProbabilityTheory.IndepFunCat.cgf_add

theorem aeStronglyMeasurableExpMulAdd {X Y : Ω → ℝ}
    (h_int_X : AeStronglyMeasurable (fun ω => exp (t * X ω)) μ)
    (h_int_Y : AeStronglyMeasurable (fun ω => exp (t * Y ω)) μ) :
    AeStronglyMeasurable (fun ω => exp (t * (X + Y) ω)) μ :=
  by
  simp_rw [Pi.add_apply, mul_add, exp_add]
  exact ae_strongly_measurable.mul h_int_X h_int_Y
#align probability_theory.ae_strongly_measurable_exp_mul_add ProbabilityTheory.aeStronglyMeasurableExpMulAdd

theorem aeStronglyMeasurableExpMulSum {X : ι → Ω → ℝ} {s : Finset ι}
    (h_int : ∀ i ∈ s, AeStronglyMeasurable (fun ω => exp (t * X i ω)) μ) :
    AeStronglyMeasurable (fun ω => exp (t * (∑ i in s, X i) ω)) μ := by
  classical
    induction' s using Finset.induction_on with i s hi_notin_s h_rec h_int
    · simp only [Pi.zero_apply, sum_apply, sum_empty, MulZeroClass.mul_zero, exp_zero]
      exact ae_strongly_measurable_const
    · have : ∀ i : ι, i ∈ s → ae_strongly_measurable (fun ω : Ω => exp (t * X i ω)) μ := fun i hi =>
        h_int i (mem_insert_of_mem hi)
      specialize h_rec this
      rw [sum_insert hi_notin_s]
      apply ae_strongly_measurable_exp_mul_add (h_int i (mem_insert_self _ _)) h_rec
#align probability_theory.ae_strongly_measurable_exp_mul_sum ProbabilityTheory.aeStronglyMeasurableExpMulSum

theorem IndepFunCat.integrableExpMulAdd {X Y : Ω → ℝ} (h_indep : IndepFunCat X Y μ)
    (h_int_X : Integrable (fun ω => exp (t * X ω)) μ)
    (h_int_Y : Integrable (fun ω => exp (t * Y ω)) μ) :
    Integrable (fun ω => exp (t * (X + Y) ω)) μ :=
  by
  simp_rw [Pi.add_apply, mul_add, exp_add]
  exact (h_indep.exp_mul t t).integrableMul h_int_X h_int_Y
#align probability_theory.indep_fun.integrable_exp_mul_add ProbabilityTheory.IndepFunCat.integrableExpMulAdd

theorem IndepFun.integrableExpMulSum [IsProbabilityMeasure μ] {X : ι → Ω → ℝ}
    (h_indep : IndepFun (fun i => inferInstance) X μ) (h_meas : ∀ i, Measurable (X i))
    {s : Finset ι} (h_int : ∀ i ∈ s, Integrable (fun ω => exp (t * X i ω)) μ) :
    Integrable (fun ω => exp (t * (∑ i in s, X i) ω)) μ := by
  classical
    induction' s using Finset.induction_on with i s hi_notin_s h_rec h_int
    · simp only [Pi.zero_apply, sum_apply, sum_empty, MulZeroClass.mul_zero, exp_zero]
      exact integrable_const _
    · have : ∀ i : ι, i ∈ s → integrable (fun ω : Ω => exp (t * X i ω)) μ := fun i hi =>
        h_int i (mem_insert_of_mem hi)
      specialize h_rec this
      rw [sum_insert hi_notin_s]
      refine' indep_fun.integrable_exp_mul_add _ (h_int i (mem_insert_self _ _)) h_rec
      exact (h_indep.indep_fun_finset_sum_of_not_mem h_meas hi_notin_s).symm
#align probability_theory.Indep_fun.integrable_exp_mul_sum ProbabilityTheory.IndepFun.integrableExpMulSum

theorem IndepFun.mgf_sum [IsProbabilityMeasure μ] {X : ι → Ω → ℝ}
    (h_indep : IndepFun (fun i => inferInstance) X μ) (h_meas : ∀ i, Measurable (X i))
    (s : Finset ι) : mgf (∑ i in s, X i) μ t = ∏ i in s, mgf (X i) μ t := by
  classical
    induction' s using Finset.induction_on with i s hi_notin_s h_rec h_int
    · simp only [sum_empty, mgf_zero_fun, measure_univ, ENNReal.one_toReal, prod_empty]
    · have h_int' : ∀ i : ι, ae_strongly_measurable (fun ω : Ω => exp (t * X i ω)) μ := fun i =>
        ((h_meas i).const_mul t).exp.AeStronglyMeasurable
      rw [sum_insert hi_notin_s,
        indep_fun.mgf_add (h_indep.indep_fun_finset_sum_of_not_mem h_meas hi_notin_s).symm
          (h_int' i) (ae_strongly_measurable_exp_mul_sum fun i hi => h_int' i),
        h_rec, prod_insert hi_notin_s]
#align probability_theory.Indep_fun.mgf_sum ProbabilityTheory.IndepFun.mgf_sum

theorem IndepFun.cgf_sum [IsProbabilityMeasure μ] {X : ι → Ω → ℝ}
    (h_indep : IndepFun (fun i => inferInstance) X μ) (h_meas : ∀ i, Measurable (X i))
    {s : Finset ι} (h_int : ∀ i ∈ s, Integrable (fun ω => exp (t * X i ω)) μ) :
    cgf (∑ i in s, X i) μ t = ∑ i in s, cgf (X i) μ t :=
  by
  simp_rw [cgf]
  rw [← log_prod _ _ fun j hj => _]
  · rw [h_indep.mgf_sum h_meas]
  · exact (mgf_pos (h_int j hj)).ne'
#align probability_theory.Indep_fun.cgf_sum ProbabilityTheory.IndepFun.cgf_sum

/-- **Chernoff bound** on the upper tail of a real random variable. -/
theorem measure_ge_le_exp_mul_mgf [IsFiniteMeasure μ] (ε : ℝ) (ht : 0 ≤ t)
    (h_int : Integrable (fun ω => exp (t * X ω)) μ) :
    (μ { ω | ε ≤ X ω }).toReal ≤ exp (-t * ε) * mgf X μ t :=
  by
  cases' ht.eq_or_lt with ht_zero_eq ht_pos
  · rw [ht_zero_eq.symm]
    simp only [neg_zero, MulZeroClass.zero_mul, exp_zero, mgf_zero', one_mul]
    rw [ENNReal.toReal_le_toReal (measure_ne_top μ _) (measure_ne_top μ _)]
    exact measure_mono (Set.subset_univ _)
  calc
    (μ { ω | ε ≤ X ω }).toReal = (μ { ω | exp (t * ε) ≤ exp (t * X ω) }).toReal :=
      by
      congr with ω
      simp only [exp_le_exp, eq_iff_iff]
      exact
        ⟨fun h => mul_le_mul_of_nonneg_left h ht_pos.le, fun h => le_of_mul_le_mul_left h ht_pos⟩
    _ ≤ (exp (t * ε))⁻¹ * μ[fun ω => exp (t * X ω)] :=
      by
      have :
        exp (t * ε) * (μ { ω | exp (t * ε) ≤ exp (t * X ω) }).toReal ≤ μ[fun ω => exp (t * X ω)] :=
        mul_meas_ge_le_integral_of_nonneg (fun x => (exp_pos _).le) h_int _
      rwa [mul_comm (exp (t * ε))⁻¹, ← div_eq_mul_inv, le_div_iff' (exp_pos _)]
    _ = exp (-t * ε) * mgf X μ t := by
      rw [neg_mul, exp_neg]
      rfl

#align probability_theory.measure_ge_le_exp_mul_mgf ProbabilityTheory.measure_ge_le_exp_mul_mgf

/-- **Chernoff bound** on the lower tail of a real random variable. -/
theorem measure_le_le_exp_mul_mgf [IsFiniteMeasure μ] (ε : ℝ) (ht : t ≤ 0)
    (h_int : Integrable (fun ω => exp (t * X ω)) μ) :
    (μ { ω | X ω ≤ ε }).toReal ≤ exp (-t * ε) * mgf X μ t :=
  by
  rw [← neg_neg t, ← mgf_neg, neg_neg, ← neg_mul_neg (-t)]
  refine' Eq.trans_le _ (measure_ge_le_exp_mul_mgf (-ε) (neg_nonneg.mpr ht) _)
  · congr with ω
    simp only [Pi.neg_apply, neg_le_neg_iff]
  · simp_rw [Pi.neg_apply, neg_mul_neg]
    exact h_int
#align probability_theory.measure_le_le_exp_mul_mgf ProbabilityTheory.measure_le_le_exp_mul_mgf

/-- **Chernoff bound** on the upper tail of a real random variable. -/
theorem measure_ge_le_exp_cgf [IsFiniteMeasure μ] (ε : ℝ) (ht : 0 ≤ t)
    (h_int : Integrable (fun ω => exp (t * X ω)) μ) :
    (μ { ω | ε ≤ X ω }).toReal ≤ exp (-t * ε + cgf X μ t) :=
  by
  refine' (measure_ge_le_exp_mul_mgf ε ht h_int).trans _
  rw [exp_add]
  exact mul_le_mul le_rfl (le_exp_log _) mgf_nonneg (exp_pos _).le
#align probability_theory.measure_ge_le_exp_cgf ProbabilityTheory.measure_ge_le_exp_cgf

/-- **Chernoff bound** on the lower tail of a real random variable. -/
theorem measure_le_le_exp_cgf [IsFiniteMeasure μ] (ε : ℝ) (ht : t ≤ 0)
    (h_int : Integrable (fun ω => exp (t * X ω)) μ) :
    (μ { ω | X ω ≤ ε }).toReal ≤ exp (-t * ε + cgf X μ t) :=
  by
  refine' (measure_le_le_exp_mul_mgf ε ht h_int).trans _
  rw [exp_add]
  exact mul_le_mul le_rfl (le_exp_log _) mgf_nonneg (exp_pos _).le
#align probability_theory.measure_le_le_exp_cgf ProbabilityTheory.measure_le_le_exp_cgf

end MomentGeneratingFunction

end ProbabilityTheory


/-
Copyright (c) 2022 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker
-/
import analysis.inner_product_space.adjoint

/-!
# Positive operators

In this file we define positive operators in a Hilbert space. We follow Bourbaki's choice
of requiring self adjointness in the definition.

## Main definitions

* `is_positive` : a continuous linear map is positive if it is self adjoint and
  `∀ x, 0 ≤ re ⟪T x, x⟫`

## Main statements

* `continuous_linear_map.is_positive.conj_adjoint` : if `T : E →L[𝕜] E` is positive,
  then for any `S : E →L[𝕜] F`, `S ∘L T ∘L S†` is also positive.
* `continuous_linear_map.is_positive_iff_complex` : in a ***complex*** hilbert space,
  checking that `⟪T x, x⟫` is a nonnegative real number for all `x` suffices to prove that
  `T` is positive

## References

* [Bourbaki, *Topological Vector Spaces*][bourbaki1987]

## Tags

Positive operator
-/

open inner_product_space is_R_or_C continuous_linear_map
open_locale inner_product complex_conjugate

namespace continuous_linear_map

variables {𝕜 E F : Type*} [is_R_or_C 𝕜]
variables [normed_add_comm_group E] [normed_add_comm_group F]
variables [inner_product_space 𝕜 E] [inner_product_space 𝕜 F]
variables [complete_space E] [complete_space F]
local notation `⟪`x`, `y`⟫` := @inner 𝕜 _ _ x y

/-- A continuous linear endomorphism `T` of a Hilbert space is **positive** if it is self adjoint
  and `∀ x, 0 ≤ re ⟪T x, x⟫`. -/
def is_positive (T : E →L[𝕜] E) : Prop :=
  is_self_adjoint T ∧ ∀ x, 0 ≤ T.re_apply_inner_self x

lemma is_positive.is_self_adjoint {T : E →L[𝕜] E} (hT : is_positive T) :
  is_self_adjoint T :=
hT.1

lemma is_positive.inner_nonneg_left {T : E →L[𝕜] E} (hT : is_positive T) (x : E) :
  0 ≤ re ⟪T x, x⟫ :=
hT.2 x

lemma is_positive.inner_nonneg_right {T : E →L[𝕜] E} (hT : is_positive T) (x : E) :
  0 ≤ re ⟪x, T x⟫ :=
by rw inner_re_symm; exact hT.inner_nonneg_left x

lemma is_positive_zero : is_positive (0 : E →L[𝕜] E) :=
begin
  refine ⟨is_self_adjoint_zero _, λ x, _⟩,
  change 0 ≤ re ⟪_, _⟫,
  rw [zero_apply, inner_zero_left, zero_hom_class.map_zero]
end

lemma is_positive_one : is_positive (1 : E →L[𝕜] E) :=
⟨is_self_adjoint_one _, λ x, inner_self_nonneg⟩

lemma is_positive.add {T S : E →L[𝕜] E} (hT : T.is_positive)
  (hS : S.is_positive) : (T + S).is_positive :=
begin
  refine ⟨hT.is_self_adjoint.add hS.is_self_adjoint, λ x, _⟩,
  rw [re_apply_inner_self, add_apply, inner_add_left, map_add],
  exact add_nonneg (hT.inner_nonneg_left x) (hS.inner_nonneg_left x)
end

lemma is_positive.conj_adjoint {T : E →L[𝕜] E}
  (hT : T.is_positive) (S : E →L[𝕜] F) : (S ∘L T ∘L S†).is_positive :=
begin
  refine ⟨hT.is_self_adjoint.conj_adjoint S, λ x, _⟩,
  rw [re_apply_inner_self, comp_apply, ← adjoint_inner_right],
  exact hT.inner_nonneg_left _
end

lemma is_positive.adjoint_conj {T : E →L[𝕜] E}
  (hT : T.is_positive) (S : F →L[𝕜] E) : (S† ∘L T ∘L S).is_positive :=
begin
  convert hT.conj_adjoint (S†),
  rw adjoint_adjoint
end

lemma is_positive.conj_orthogonal_projection (U : submodule 𝕜 E) {T : E →L[𝕜] E}
  (hT : T.is_positive) [complete_space U] :
  (U.subtypeL ∘L orthogonal_projection U ∘L T ∘L U.subtypeL ∘L
    orthogonal_projection U).is_positive :=
begin
  have := hT.conj_adjoint (U.subtypeL ∘L orthogonal_projection U),
  rwa (orthogonal_projection_is_self_adjoint U).adjoint_eq at this
end

lemma is_positive.orthogonal_projection_comp {T : E →L[𝕜] E}
  (hT : T.is_positive) (U : submodule 𝕜 E) [complete_space U] :
  (orthogonal_projection U ∘L T ∘L U.subtypeL).is_positive :=
begin
  have := hT.conj_adjoint (orthogonal_projection U : E →L[𝕜] U),
  rwa [U.adjoint_orthogonal_projection] at this,
end

section complex

variables {E' : Type*} [normed_add_comm_group E'] [inner_product_space ℂ E'] [complete_space E']

lemma is_positive_iff_complex (T : E' →L[ℂ] E') :
  is_positive T ↔ ∀ x, (re ⟪T x, x⟫_ℂ : ℂ) = ⟪T x, x⟫_ℂ ∧ 0 ≤ re ⟪T x, x⟫_ℂ :=
begin
  simp_rw [is_positive, forall_and_distrib, is_self_adjoint_iff_is_symmetric,
    linear_map.is_symmetric_iff_inner_map_self_real, eq_conj_iff_re],
  refl
end

end complex

end continuous_linear_map


section pred_logic

variables X Y Z : Prop

/- *** FORALL and ARROW *** -/

-- → and ∀
def arrow_all_equiv   := (∀ (x : X), Y) ↔ (X → Y)

/-
To prove either (∀ (x : X), Y) or (X → Y), you first assume
that you're given an arbitrary but specific proof of X, and
in that context, you show that you can derive a proof (thus
deducing the truth) of Y. It's exactly the same reasoning in
each case. This is the *introduction* rule for ∀ and →.
-/

/-
In fact, in constructive logic, X → Y is simply a notation
*defined* as ∀ (x : X), Y. What each of these propositions
states in constructive logic is that "From *any* proof, x,
of X, we can derive a proof of Y." In fact, in Lean, these
propositions are not only equivalent but equal.
-/

#check X → Y          -- Lean confirms this is a proposition
#check ∀ (x : X), Y   -- Lean understands this to say X → Y!

/- OPTIONAL
As an aside, here's a proof that these propositions are
actually equal. This proof uses an inference rule, rfl, for
equality that we've not yet studied. Don't worry about the
"rfl" for now, but trust that we're giving a correct proof
of the equality of these two propositions in Lean
-/
theorem all_imp_equal : (∀ (x : X), Y) = (X → Y) := rfl

/-
The reason it's super-helpful to know these propositions
are equivalent is that it tells you that you can *use* a
proof of a ∀ proposition or of a → proposition in exactly
the same way. So let's turn to the *elimination* rules for
→ and ∀.
-/

def arrow_elim        := (X → Y)        → X   → Y
def all_elim          := (∀ (x : X), Y) → X   → Y

/-
The idea underlying these rules date to ancient times.
They both say "if from the truth or a proof of X you
can derive a proof or the truth of Y, and if you also
have a proof, or know the truth, of X, then you can (in
constructive logic) derive a proof of Y (or deduce the
truth of Y."

Here's an example. What we want to say in logic is
that if every ball is blue and b is some specific
ball then b is blue. The elimination rule for ∀ and
→ applies a generalization to a specific instance to
deduce that the generalized statement specialized to
a particular instance is true.

Note: In this example, Y is a proposition obtained by
plugging "x" into a one-argument predicate. So suppose
(∀ (x : X), Y) is read as "for any Ball x, x is blue."
Here X is "Ball;" x is an arbitrary but specific Ball;
and Y is read as "x is blue."

Now suppose that, in this context, you're given a
*particular* ball, (b : X). What the overall rules
says is that you now conclude that "b is blue."

The elimination rule works by *applying* a proof of
a universal generalization (showing that something
is true of *every* object of a particular kind) to
a *specific* object of that kind, to deduce that the
generalized statement is also true of that specific
object.

If every ball is blue, and if b is a ball, then b
must be blue. Another way to say it that makes a
bit more sense for the (X → Y) notation is that
"if being any ball, x, implies that x is blue, and
if b is some particular ball, then b is blue.
-/

/-
As an example, consider a predicate, (isBlue _), where you can fill
in the blank/argument with any Ball-type object. If b is a specific
Ball-type object, then (isBlue b) is a proposition, representing the
English-language claim that b is blue. Here's how we represent this
predicate in Lean.
-/

variable Ball : Type            -- Ball is a type of object
variable isBlue : Ball → Prop
/-
First we Ball to be the name of a type of object (like int or
bool). Then we define isBlue to be a construct (think function!)
that when given any object of type Ball as an argument yields a
proposition. To see how this works, suppose we have some specific
balls, b1 and b2.
-/
variables (b1 b2 : Ball)
/-
Now let's use isBlue to make some propositions!
-/
#check isBlue                               -- a predicate
#check isBlue b1                            -- a proposition about b1
#check isBlue b2                            -- a proposition about b2
#check (∀ (x : Ball), isBlue x)             -- generalization
variable all_balls_blue : (∀ (x : Ball), isBlue x)   -- proof of it
#check all_balls_blue b1                    -- proof b1 is blue
#check all_balls_blue b2                    -- proof b2 is blue

/-
Here's an English-language version.

Suppose b1 and b2 are objects of some type, Ball, and that isBlue
is one-place predicate taking any Ball, b, as an argument, and that
reduces to a proposition, denoted (isBlue b), that we understand as
asserting that the particular ball, b, is blue. Next (295), we take
all_balls_blue as a proof that all balls are blue. Finally (296 and
297), we see that we can can use this proof/truth by *applying* it
to any particular ball, b, to obtain a proof/truth that b is blue.

For any type S, given any X: (∀ s : S), T and any s : S, the ∀
and → elimination rule(s) say that you can derive a value/proof of
type T; moreover this operation is basically done by *applying* ,
viewed as a function from parameter value to proposition, to the
actual parameter, s (in Lean denoted as (X s)), to obtain a value
(proof) of (type) T. Modus ponens is like function application. In
constructive logic, a proof of the ∀ proposition *is* a function.
Here you begin to see how profound is that proofs in constructive
logic tell you not only that a proposition is true but why. Here a
proof of X → Y or of ∀ (x : X), Y, is a program that when given any
value/proof of X as an argument returns a value/proof of Y. If you
can produce a function that turns any proof of X into a proof of Y,
then you've shown that whenever X is true, so is Y; and that's just
what X → Y is meant to say (similarly for ∀ (x : X), Y).
-/

/-
Walk-away message: Applying a proof/truth of a universal
generalization to a specific object yields a proof of the
generalization *specialized* to that particular object. That
is in the higher-order predicate logic of Lean.
-/

/-
Finally, let's compare our elimination rule, in the higher-order
predicate logic of Lean, with its first-order logic counterpart.

There are two big differences, first, in first-order logic, you
have to present the rule outside of the logic: you can't write
rules like this, ∀ (X Y : Prop), X → Y → (X ∧ Y), in first-order
logic because in first order logic you can't quantify over types,
propositions, predicates, functions. Here we do just this with the
"∀ (X Y : Prop)." By contrast, in the higher-order logic of Lean,
we can represent the rules of first-order logic with no problem:
e.g., "∀ (X Y : Prop), X → Y → (X ∧ Y)."

Second, as we've discussed, using Lean's higher-order logic, you
can think of a proof of "∀ (X Y : Prop), X → Y → (X ∧ Y)" as a
function. Each variable bound by a ∀ and each implication premise
is an argument, with the type of the return value at the end of
the line. So, here, a proof of this proposition can be taken as
a function that takes two propositions, X and Y as arguments, then
a proof (value) of (type) X, then a proof (value) of type Y, and
that finally returns a proof (value) X ∧ Y. Whereas the proof of
∀ (X Y : Prop), X → Y → (X ∧ Y) is a function the returned proof
of (X ∧ Y) is a pair-like data structure. Proofs in constructive
logic are *computational*, and you can even compute with them, as
you do when you *apply* a proof of a certain kind to an argument
to obtain a resulting proof/value.
-/

/-
Quiz questions:

First-order logic. I know that every natural number is
beautiful (∀ n, NaturalNumber(n) → Beautiful(n) : true),
and I want to prove (7 is beautiful : true). Prove it.
Name the inference rule and identify the arguments you
give it to prove it.

Constructive logic. Suppose I have a proof, pf, that every
natural number is beautiful (∀ (n : ℕ), beautiful n), and I
need a proof that 7 is beautiful. How can I get the proof
I need? Answer in both English and with a Lean expression.

Formalize this story: All people are mortal, and Plato
is a person, therefore Plato is Mortal.
-/

/- Quick exercise. Give a proof of this (in English, and
give it a try in Lean as well.
-/

def arrow_trans       := (X → Y) → (Y → Z) → (X → Z)

end pred_logic


import linear_algebra.matrix.spectrum

namespace linear_map

variables {𝕜 : Type*} [is_R_or_C 𝕜] [decidable_eq 𝕜]
variables {E : Type*} [inner_product_space 𝕜 E]
variables [finite_dimensional 𝕜 E]
variables {n : ℕ} (hn : finite_dimensional.finrank 𝕜 E = n)
variables {T : E →ₗ[𝕜] E}

-- TODO: move analysis.inner_product_space.spectrum
-- TODO: can be used to prove version 2.
/-- *Diagonalization theorem*, *spectral theorem*; version 3: A self-adjoint operator `T` on a
finite-dimensional inner product space `E` acts diagonally on the identification of `E` with
Euclidean space induced by an orthonormal basis of eigenvectors of `T`. -/
lemma spectral_theorem' (v : E) (i : fin n)
  (xs : orthonormal_basis (fin n) 𝕜 E) (as : fin n → ℝ)
  (hxs : ∀ j, module.End.has_eigenvector T (as j) (xs j)) :
  xs.repr (T v) i = as i * xs.repr v i :=
begin
  suffices : ∀ w : euclidean_space 𝕜 (fin n),
    T (xs.repr.symm w) = xs.repr.symm (λ i, as i * w i),
  { simpa only [linear_isometry_equiv.symm_apply_apply, linear_isometry_equiv.apply_symm_apply]
      using congr_arg (λ (v : E), (xs.repr) v i) (this ((xs.repr) v)) },
  intros w,
  simp_rw [← orthonormal_basis.sum_repr_symm, linear_map.map_sum,
    linear_map.map_smul, λ j, module.End.mem_eigenspace_iff.mp (hxs j).1, smul_smul, mul_comm]
end

end linear_map


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro

! This file was ported from Lean 3 source module topology.instances.int
! leanprover-community/mathlib commit 70fd9563a21e7b963887c9360bd29b2393e6225a
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Int.Interval
import Mathlib.Topology.MetricSpace.Basic
import Mathlib.Order.Filter.Archimedean

/-!
# Topology on the integers

The structure of a metric space on `ℤ` is introduced in this file, induced from `ℝ`.
-/

noncomputable section

open Metric Set Filter

namespace Int

instance : Dist ℤ :=
  ⟨fun x y => dist (x : ℝ) y⟩

theorem dist_eq (x y : ℤ) : dist x y = |(x : ℝ) - y| := rfl
#align int.dist_eq Int.dist_eq

theorem dist_eq' (m n : ℤ) : dist m n = |m - n| := by rw [dist_eq]; norm_cast

@[norm_cast, simp]
theorem dist_cast_real (x y : ℤ) : dist (x : ℝ) y = dist x y :=
  rfl
#align int.dist_cast_real Int.dist_cast_real

theorem pairwise_one_le_dist : Pairwise fun m n : ℤ => 1 ≤ dist m n := by
  intro m n hne
  rw [dist_eq]; norm_cast; rwa [← zero_add (1 : ℤ), Int.add_one_le_iff, abs_pos, sub_ne_zero]
#align int.pairwise_one_le_dist Int.pairwise_one_le_dist

theorem uniformEmbedding_coe_real : UniformEmbedding ((↑) : ℤ → ℝ) :=
  uniformEmbedding_bot_of_pairwise_le_dist zero_lt_one pairwise_one_le_dist
#align int.uniform_embedding_coe_real Int.uniformEmbedding_coe_real

theorem closedEmbedding_coe_real : ClosedEmbedding ((↑) : ℤ → ℝ) :=
  closedEmbedding_of_pairwise_le_dist zero_lt_one pairwise_one_le_dist
#align int.closed_embedding_coe_real Int.closedEmbedding_coe_real

instance : MetricSpace ℤ := Int.uniformEmbedding_coe_real.comapMetricSpace _

theorem preimage_ball (x : ℤ) (r : ℝ) : (↑) ⁻¹' ball (x : ℝ) r = ball x r := rfl
#align int.preimage_ball Int.preimage_ball

theorem ball_eq_Ioo (x : ℤ) (r : ℝ) : ball x r = Ioo ⌊↑x - r⌋ ⌈↑x + r⌉ := by
  rw [← preimage_ball, Real.ball_eq_Ioo, preimage_Ioo]
#align int.ball_eq_Ioo Int.ball_eq_Ioo

theorem closedBall_eq_Icc (x : ℤ) (r : ℝ) : closedBall x r = Icc ⌈↑x - r⌉ ⌊↑x + r⌋ := by
  rw [← preimage_closedBall, Real.closedBall_eq_Icc, preimage_Icc]
#align int.closed_ball_eq_Icc Int.closedBall_eq_Icc

instance : ProperSpace ℤ :=
  ⟨fun x r => by
    rw [closedBall_eq_Icc]
    exact (Set.finite_Icc _ _).isCompact⟩

@[simp]
theorem cocompact_eq : cocompact ℤ = atBot ⊔ atTop := by
  simp_rw [← comap_dist_right_atTop_eq_cocompact (0 : ℤ), dist_eq', sub_zero,
    ← comap_abs_atTop, ← @Int.comap_cast_atTop ℝ, comap_comap]; rfl
#align int.cocompact_eq Int.cocompact_eq

@[simp]
theorem cofinite_eq : (cofinite : Filter ℤ) = atBot ⊔ atTop := by
  rw [← cocompact_eq_cofinite, cocompact_eq]
#align int.cofinite_eq Int.cofinite_eq

end Int


namespace Sec_4_6

  namespace exercise1
    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 w,
          show p w, from (h w).left)
        (assume w,
          show q w, from (h w).right))
      (assume h : (∀ x, p x) ∧ (∀ x, q x),
        assume w,
        ⟨(h.left w), (h.right w)⟩)

    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 w,
      have h₃ : p w, from h₂ w,
      show q w, from h₁ w h₃

    example : (∀ x, p x) ∨ (∀ x, q x) → (∀ x, p x ∨ q x) :=
      assume h: (∀ x, p x) ∨ (∀ x, q x),
      assume w,
        or.elim h
          (assume hl : ∀ x, p x,
            show p w ∨ q w, from or.intro_left _ (hl w))
          (assume hr : ∀ x, q x,
            show p w ∨ q w, from or.intro_right _ (hr w))

  end exercise1

end Sec_4_6