/-
Copyright (c) 2021 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.independence
! leanprover-community/mathlib commit 70fd9563a21e7b963887c9360bd29b2393e6225a
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.MeasureTheory.Constructions.Pi

/-!
# Independence of sets of sets and measure spaces (σ-algebras)

* A family of sets of sets `π : ι → set (set Ω)` is independent with respect to a measure `μ` if for
  any finite set of indices `s = {i_1, ..., i_n}`, for any sets `f i_1 ∈ π i_1, ..., f i_n ∈ π i_n`,
  `μ (⋂ i in s, f i) = ∏ i in s, μ (f i) `. It will be used for families of π-systems.
* A family of measurable space structures (i.e. of σ-algebras) is independent with respect to a
  measure `μ` (typically defined on a finer σ-algebra) if the family of sets of measurable sets they
  define is independent. I.e., `m : ι → measurable_space Ω` is independent with respect to a
  measure `μ` if for any finite set of indices `s = {i_1, ..., i_n}`, for any sets
  `f i_1 ∈ m i_1, ..., f i_n ∈ m i_n`, then `μ (⋂ i in s, f i) = ∏ i in s, μ (f i)`.
* Independence of sets (or events in probabilistic parlance) is defined as independence of the
  measurable space structures they generate: a set `s` generates the measurable space structure with
  measurable sets `∅, s, sᶜ, univ`.
* Independence of functions (or random variables) is also defined as independence of the measurable
  space structures they generate: a function `f` for which we have a measurable space `m` on the
  codomain generates `measurable_space.comap f m`.

## Main statements

* `Indep_sets.Indep`: if π-systems are independent as sets of sets, then the
  measurable space structures they generate are independent.
* `indep_sets.indep`: variant with two π-systems.
* `measure_zero_or_one_of_measurable_set_limsup_at_top`: Kolmogorov's 0-1 law. Any set which is
  measurable with respect to the tail σ-algebra `limsup s at_top` of an independent sequence of
  σ-algebras `s` has probability 0 or 1.

## Implementation notes

We provide one main definition of independence:
* `Indep_sets`: independence of a family of sets of sets `pi : ι → set (set Ω)`.
Three other independence notions are defined using `Indep_sets`:
* `Indep`: independence of a family of measurable space structures `m : ι → measurable_space Ω`,
* `Indep_set`: independence of a family of sets `s : ι → set Ω`,
* `Indep_fun`: independence of a family of functions. For measurable spaces
  `m : Π (i : ι), measurable_space (β i)`, we consider functions `f : Π (i : ι), Ω → β i`.

Additionally, we provide four corresponding statements for two measurable space structures (resp.
sets of sets, sets, functions) instead of a family. These properties are denoted by the same names
as for a family, but without a capital letter, for example `indep_fun` is the version of `Indep_fun`
for two functions.

The definition of independence for `Indep_sets` uses finite sets (`finset`). An alternative and
equivalent way of defining independence would have been to use countable sets.
TODO: prove that equivalence.

Most of the definitions and lemma in this file list all variables instead of using the `variables`
keyword at the beginning of a section, for example
`lemma indep.symm {Ω} {m₁ m₂ : measurable_space Ω} [measurable_space Ω] {μ : measure Ω} ...` .
This is intentional, to be able to control the order of the `measurable_space` variables. Indeed
when defining `μ` in the example above, the measurable space used is the last one defined, here
`[measurable_space Ω]`, and not `m₁` or `m₂`.

## References

* Williams, David. Probability with martingales. Cambridge university press, 1991.
Part A, Chapter 4.
-/

open MeasureTheory MeasurableSpace

open BigOperators MeasureTheory ENNReal

namespace ProbabilityTheory

variable {Ω ι : Type _}

section Definitions

/-- A family of sets of sets `π : ι → set (set Ω)` is independent with respect to a measure `μ` if
for any finite set of indices `s = {i_1, ..., i_n}`, for any sets
`f i_1 ∈ π i_1, ..., f i_n ∈ π i_n`, then `μ (⋂ i in s, f i) = ∏ i in s, μ (f i) `.
It will be used for families of pi_systems. -/
def IndepSets [MeasurableSpace Ω] (π : ι → Set (Set Ω))
    (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume) : Prop :=
  ∀ (s : Finset ι) {f : ι → Set Ω} (H : ∀ i, i ∈ s → f i ∈ π i),
    μ (⋂ i ∈ s, f i) = ∏ i in s, μ (f i)
#align probability_theory.Indep_sets ProbabilityTheory.IndepSets

/-- Two sets of sets `s₁, s₂` are independent with respect to a measure `μ` if for any sets
`t₁ ∈ p₁, t₂ ∈ s₂`, then `μ (t₁ ∩ t₂) = μ (t₁) * μ (t₂)` -/
def IndepSetsCat [MeasurableSpace Ω] (s1 s2 : Set (Set Ω))
    (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume) : Prop :=
  ∀ t1 t2 : Set Ω, t1 ∈ s1 → t2 ∈ s2 → μ (t1 ∩ t2) = μ t1 * μ t2
#align probability_theory.indep_sets ProbabilityTheory.IndepSetsCat

/-- A family of measurable space structures (i.e. of σ-algebras) is independent with respect to a
measure `μ` (typically defined on a finer σ-algebra) if the family of sets of measurable sets they
define is independent. `m : ι → measurable_space Ω` is independent with respect to measure `μ` if
for any finite set of indices `s = {i_1, ..., i_n}`, for any sets
`f i_1 ∈ m i_1, ..., f i_n ∈ m i_n`, then `μ (⋂ i in s, f i) = ∏ i in s, μ (f i) `. -/
def Indep (m : ι → MeasurableSpace Ω) [MeasurableSpace Ω]
    (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume) : Prop :=
  IndepSets (fun x => { s | measurable_set[m x] s }) μ
#align probability_theory.Indep ProbabilityTheory.Indep

/-- Two measurable space structures (or σ-algebras) `m₁, m₂` are independent with respect to a
measure `μ` (defined on a third σ-algebra) if for any sets `t₁ ∈ m₁, t₂ ∈ m₂`,
`μ (t₁ ∩ t₂) = μ (t₁) * μ (t₂)` -/
def IndepCat (m₁ m₂ : MeasurableSpace Ω) [MeasurableSpace Ω]
    (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume) : Prop :=
  IndepSetsCat { s | measurable_set[m₁] s } { s | measurable_set[m₂] s } μ
#align probability_theory.indep ProbabilityTheory.IndepCat

/-- A family of sets is independent if the family of measurable space structures they generate is
independent. For a set `s`, the generated measurable space has measurable sets `∅, s, sᶜ, univ`. -/
def IndepSet [MeasurableSpace Ω] (s : ι → Set Ω)
    (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume) : Prop :=
  Indep (fun i => generateFrom {s i}) μ
#align probability_theory.Indep_set ProbabilityTheory.IndepSet

/-- Two sets are independent if the two measurable space structures they generate are independent.
For a set `s`, the generated measurable space structure has measurable sets `∅, s, sᶜ, univ`. -/
def IndepSetCat [MeasurableSpace Ω] (s t : Set Ω)
    (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume) : Prop :=
  IndepCat (generateFrom {s}) (generateFrom {t}) μ
#align probability_theory.indep_set ProbabilityTheory.IndepSetCat

/-- A family of functions defined on the same space `Ω` and taking values in possibly different
spaces, each with a measurable space structure, is independent if the family of measurable space
structures they generate on `Ω` is independent. For a function `g` with codomain having measurable
space structure `m`, the generated measurable space structure is `measurable_space.comap g m`. -/
def IndepFun [MeasurableSpace Ω] {β : ι → Type _} (m : ∀ x : ι, MeasurableSpace (β x))
    (f : ∀ x : ι, Ω → β x) (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume) : Prop :=
  Indep (fun x => MeasurableSpace.comap (f x) (m x)) μ
#align probability_theory.Indep_fun ProbabilityTheory.IndepFun

/-- Two functions are independent if the two measurable space structures they generate are
independent. For a function `f` with codomain having measurable space structure `m`, the generated
measurable space structure is `measurable_space.comap f m`. -/
def IndepFunCat {β γ} [MeasurableSpace Ω] [mβ : MeasurableSpace β] [mγ : MeasurableSpace γ]
    (f : Ω → β) (g : Ω → γ) (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume) : Prop :=
  IndepCat (MeasurableSpace.comap f mβ) (MeasurableSpace.comap g mγ) μ
#align probability_theory.indep_fun ProbabilityTheory.IndepFunCat

end Definitions

section Indep

@[symm]
theorem IndepSetsCat.symm {s₁ s₂ : Set (Set Ω)} [MeasurableSpace Ω] {μ : Measure Ω}
    (h : IndepSetsCat s₁ s₂ μ) : IndepSetsCat s₂ s₁ μ :=
  by
  intro t1 t2 ht1 ht2
  rw [Set.inter_comm, mul_comm]
  exact h t2 t1 ht2 ht1
#align probability_theory.indep_sets.symm ProbabilityTheory.IndepSetsCat.symm

@[symm]
theorem IndepCat.symm {m₁ m₂ : MeasurableSpace Ω} [MeasurableSpace Ω] {μ : Measure Ω}
    (h : IndepCat m₁ m₂ μ) : IndepCat m₂ m₁ μ :=
  IndepSetsCat.symm h
#align probability_theory.indep.symm ProbabilityTheory.IndepCat.symm

theorem indepBotRight (m' : MeasurableSpace Ω) {m : MeasurableSpace Ω} {μ : Measure Ω}
    [IsProbabilityMeasure μ] : IndepCat m' ⊥ μ :=
  by
  intro s t hs ht
  rw [Set.mem_setOf_eq, MeasurableSpace.measurableSet_bot_iff] at ht
  cases ht
  · rw [ht, Set.inter_empty, measure_empty, MulZeroClass.mul_zero]
  · rw [ht, Set.inter_univ, measure_univ, mul_one]
#align probability_theory.indep_bot_right ProbabilityTheory.indepBotRight

theorem indepBotLeft (m' : MeasurableSpace Ω) {m : MeasurableSpace Ω} {μ : Measure Ω}
    [IsProbabilityMeasure μ] : IndepCat ⊥ m' μ :=
  (indepBotRight m').symm
#align probability_theory.indep_bot_left ProbabilityTheory.indepBotLeft

theorem indepSetEmptyRight {m : MeasurableSpace Ω} {μ : Measure Ω} [IsProbabilityMeasure μ]
    (s : Set Ω) : IndepSetCat s ∅ μ :=
  by
  simp only [indep_set, generate_from_singleton_empty]
  exact indep_bot_right _
#align probability_theory.indep_set_empty_right ProbabilityTheory.indepSetEmptyRight

theorem indepSetEmptyLeft {m : MeasurableSpace Ω} {μ : Measure Ω} [IsProbabilityMeasure μ]
    (s : Set Ω) : IndepSetCat ∅ s μ :=
  (indepSetEmptyRight s).symm
#align probability_theory.indep_set_empty_left ProbabilityTheory.indepSetEmptyLeft

theorem indepSetsOfIndepSetsOfLeLeft {s₁ s₂ s₃ : Set (Set Ω)} [MeasurableSpace Ω] {μ : Measure Ω}
    (h_indep : IndepSetsCat s₁ s₂ μ) (h31 : s₃ ⊆ s₁) : IndepSetsCat s₃ s₂ μ := fun t1 t2 ht1 ht2 =>
  h_indep t1 t2 (Set.mem_of_subset_of_mem h31 ht1) ht2
#align probability_theory.indep_sets_of_indep_sets_of_le_left ProbabilityTheory.indepSetsOfIndepSetsOfLeLeft

theorem indepSetsOfIndepSetsOfLeRight {s₁ s₂ s₃ : Set (Set Ω)} [MeasurableSpace Ω] {μ : Measure Ω}
    (h_indep : IndepSetsCat s₁ s₂ μ) (h32 : s₃ ⊆ s₂) : IndepSetsCat s₁ s₃ μ := fun t1 t2 ht1 ht2 =>
  h_indep t1 t2 ht1 (Set.mem_of_subset_of_mem h32 ht2)
#align probability_theory.indep_sets_of_indep_sets_of_le_right ProbabilityTheory.indepSetsOfIndepSetsOfLeRight

theorem indepOfIndepOfLeLeft {m₁ m₂ m₃ : MeasurableSpace Ω} [MeasurableSpace Ω] {μ : Measure Ω}
    (h_indep : IndepCat m₁ m₂ μ) (h31 : m₃ ≤ m₁) : IndepCat m₃ m₂ μ := fun t1 t2 ht1 ht2 =>
  h_indep t1 t2 (h31 _ ht1) ht2
#align probability_theory.indep_of_indep_of_le_left ProbabilityTheory.indepOfIndepOfLeLeft

theorem indepOfIndepOfLeRight {m₁ m₂ m₃ : MeasurableSpace Ω} [MeasurableSpace Ω] {μ : Measure Ω}
    (h_indep : IndepCat m₁ m₂ μ) (h32 : m₃ ≤ m₂) : IndepCat m₁ m₃ μ := fun t1 t2 ht1 ht2 =>
  h_indep t1 t2 ht1 (h32 _ ht2)
#align probability_theory.indep_of_indep_of_le_right ProbabilityTheory.indepOfIndepOfLeRight

theorem IndepSetsCat.union [MeasurableSpace Ω] {s₁ s₂ s' : Set (Set Ω)} {μ : Measure Ω}
    (h₁ : IndepSetsCat s₁ s' μ) (h₂ : IndepSetsCat s₂ s' μ) : IndepSetsCat (s₁ ∪ s₂) s' μ :=
  by
  intro t1 t2 ht1 ht2
  cases' (Set.mem_union _ _ _).mp ht1 with ht1₁ ht1₂
  · exact h₁ t1 t2 ht1₁ ht2
  · exact h₂ t1 t2 ht1₂ ht2
#align probability_theory.indep_sets.union ProbabilityTheory.IndepSetsCat.union

@[simp]
theorem IndepSetsCat.union_iff [MeasurableSpace Ω] {s₁ s₂ s' : Set (Set Ω)} {μ : Measure Ω} :
    IndepSetsCat (s₁ ∪ s₂) s' μ ↔ IndepSetsCat s₁ s' μ ∧ IndepSetsCat s₂ s' μ :=
  ⟨fun h =>
    ⟨indepSetsOfIndepSetsOfLeLeft h (Set.subset_union_left s₁ s₂),
      indepSetsOfIndepSetsOfLeLeft h (Set.subset_union_right s₁ s₂)⟩,
    fun h => IndepSetsCat.union h.left h.right⟩
#align probability_theory.indep_sets.union_iff ProbabilityTheory.IndepSetsCat.union_iff

/- warning: probability_theory.indep_sets.Union clashes with probability_theory.indep_sets.union -> ProbabilityTheory.IndepSetsCat.union
warning: probability_theory.indep_sets.Union -> ProbabilityTheory.IndepSetsCat.union is a dubious translation:
lean 3 declaration is
  forall {Ω : Type.{u_1}} {ι : Type.{u_2}} [_inst_1 : MeasurableSpace.{u_1} Ω] {s : ι -> (Set.{u_1} (Set.{u_1} Ω))} {s' : Set.{u_1} (Set.{u_1} Ω)} {μ : MeasureTheory.Measure.{u_1} Ω _inst_1}, (forall (n : ι), ProbabilityTheory.IndepSetsCat.{u_1} Ω _inst_1 (s n) s' μ) -> (ProbabilityTheory.IndepSetsCat.{u_1} Ω _inst_1 (Set.unionᵢ.{u_1, succ u_2} (Set.{u_1} Ω) ι (fun (n : ι) => s n)) s' μ)
but is expected to have type
  PUnit.{0}
Case conversion may be inaccurate. Consider using '#align probability_theory.indep_sets.Union ProbabilityTheory.IndepSetsCat.unionₓ'. -/
theorem IndepSetsCat.union [MeasurableSpace Ω] {s : ι → Set (Set Ω)} {s' : Set (Set Ω)}
    {μ : Measure Ω} (hyp : ∀ n, IndepSetsCat (s n) s' μ) : IndepSetsCat (⋃ n, s n) s' μ :=
  by
  intro t1 t2 ht1 ht2
  rw [Set.mem_unionᵢ] at ht1
  cases' ht1 with n ht1
  exact hyp n t1 t2 ht1 ht2
#align probability_theory.indep_sets.Union ProbabilityTheory.IndepSetsCat.union

theorem IndepSetsCat.bUnion [MeasurableSpace Ω] {s : ι → Set (Set Ω)} {s' : Set (Set Ω)}
    {μ : Measure Ω} {u : Set ι} (hyp : ∀ n ∈ u, IndepSetsCat (s n) s' μ) :
    IndepSetsCat (⋃ n ∈ u, s n) s' μ := by
  intro t1 t2 ht1 ht2
  simp_rw [Set.mem_unionᵢ] at ht1
  rcases ht1 with ⟨n, hpn, ht1⟩
  exact hyp n hpn t1 t2 ht1 ht2
#align probability_theory.indep_sets.bUnion ProbabilityTheory.IndepSetsCat.bUnion

theorem IndepSetsCat.inter [MeasurableSpace Ω] {s₁ s' : Set (Set Ω)} (s₂ : Set (Set Ω))
    {μ : Measure Ω} (h₁ : IndepSetsCat s₁ s' μ) : IndepSetsCat (s₁ ∩ s₂) s' μ :=
  fun t1 t2 ht1 ht2 => h₁ t1 t2 ((Set.mem_inter_iff _ _ _).mp ht1).left ht2
#align probability_theory.indep_sets.inter ProbabilityTheory.IndepSetsCat.inter

/- warning: probability_theory.indep_sets.Inter clashes with probability_theory.indep_sets.inter -> ProbabilityTheory.IndepSetsCat.inter
warning: probability_theory.indep_sets.Inter -> ProbabilityTheory.IndepSetsCat.inter is a dubious translation:
lean 3 declaration is
  forall {Ω : Type.{u_1}} {ι : Type.{u_2}} [_inst_1 : MeasurableSpace.{u_1} Ω] {s : ι -> (Set.{u_1} (Set.{u_1} Ω))} {s' : Set.{u_1} (Set.{u_1} Ω)} {μ : MeasureTheory.Measure.{u_1} Ω _inst_1}, (Exists.{succ u_2} ι (fun (n : ι) => ProbabilityTheory.IndepSetsCat.{u_1} Ω _inst_1 (s n) s' μ)) -> (ProbabilityTheory.IndepSetsCat.{u_1} Ω _inst_1 (Set.interᵢ.{u_1, succ u_2} (Set.{u_1} Ω) ι (fun (n : ι) => s n)) s' μ)
but is expected to have type
  PUnit.{0}
Case conversion may be inaccurate. Consider using '#align probability_theory.indep_sets.Inter ProbabilityTheory.IndepSetsCat.interₓ'. -/
theorem IndepSetsCat.inter [MeasurableSpace Ω] {s : ι → Set (Set Ω)} {s' : Set (Set Ω)}
    {μ : Measure Ω} (h : ∃ n, IndepSetsCat (s n) s' μ) : IndepSetsCat (⋂ n, s n) s' μ :=
  by
  intro t1 t2 ht1 ht2
  cases' h with n h
  exact h t1 t2 (set.mem_Inter.mp ht1 n) ht2
#align probability_theory.indep_sets.Inter ProbabilityTheory.IndepSetsCat.inter

theorem IndepSetsCat.bInter [MeasurableSpace Ω] {s : ι → Set (Set Ω)} {s' : Set (Set Ω)}
    {μ : Measure Ω} {u : Set ι} (h : ∃ n ∈ u, IndepSetsCat (s n) s' μ) :
    IndepSetsCat (⋂ n ∈ u, s n) s' μ := by
  intro t1 t2 ht1 ht2
  rcases h with ⟨n, hn, h⟩
  exact h t1 t2 (Set.binterᵢ_subset_of_mem hn ht1) ht2
#align probability_theory.indep_sets.bInter ProbabilityTheory.IndepSetsCat.bInter

theorem indepSetsCat_singleton_iff [MeasurableSpace Ω] {s t : Set Ω} {μ : Measure Ω} :
    IndepSetsCat {s} {t} μ ↔ μ (s ∩ t) = μ s * μ t :=
  ⟨fun h => h s t rfl rfl, fun h s1 t1 hs1 ht1 => by
    rwa [set.mem_singleton_iff.mp hs1, set.mem_singleton_iff.mp ht1]⟩
#align probability_theory.indep_sets_singleton_iff ProbabilityTheory.indepSetsCat_singleton_iff

end Indep

/-! ### Deducing `indep` from `Indep` -/

section FromIndepToIndep

theorem IndepSets.indepSets {s : ι → Set (Set Ω)} [MeasurableSpace Ω] {μ : Measure Ω}
    (h_indep : IndepSets s μ) {i j : ι} (hij : i ≠ j) : IndepSetsCat (s i) (s j) μ := by
  classical
    intro t₁ t₂ ht₁ ht₂
    have hf_m : ∀ x : ι, x ∈ {i, j} → ite (x = i) t₁ t₂ ∈ s x :=
      by
      intro x hx
      cases' finset.mem_insert.mp hx with hx hx
      · simp [hx, ht₁]
      · simp [finset.mem_singleton.mp hx, hij.symm, ht₂]
    have h1 : t₁ = ite (i = i) t₁ t₂ := by simp only [if_true, eq_self_iff_true]
    have h2 : t₂ = ite (j = i) t₁ t₂ := by simp only [hij.symm, if_false]
    have h_inter :
      (⋂ (t : ι) (H : t ∈ ({i, j} : Finset ι)), ite (t = i) t₁ t₂) =
        ite (i = i) t₁ t₂ ∩ ite (j = i) t₁ t₂ :=
      by simp only [Finset.set_binterᵢ_singleton, Finset.set_binterᵢ_insert]
    have h_prod :
      (∏ t : ι in ({i, j} : Finset ι), μ (ite (t = i) t₁ t₂)) =
        μ (ite (i = i) t₁ t₂) * μ (ite (j = i) t₁ t₂) :=
      by
      simp only [hij, Finset.prod_singleton, Finset.prod_insert, not_false_iff,
        Finset.mem_singleton]
    rw [h1]
    nth_rw 2 [h2]
    nth_rw 4 [h2]
    rw [← h_inter, ← h_prod, h_indep {i, j} hf_m]
#align probability_theory.Indep_sets.indep_sets ProbabilityTheory.IndepSets.indepSets

theorem Indep.indep {m : ι → MeasurableSpace Ω} [MeasurableSpace Ω] {μ : Measure Ω}
    (h_indep : Indep m μ) {i j : ι} (hij : i ≠ j) : IndepCat (m i) (m j) μ :=
  by
  change indep_sets ((fun x => measurable_set[m x]) i) ((fun x => measurable_set[m x]) j) μ
  exact Indep_sets.indep_sets h_indep hij
#align probability_theory.Indep.indep ProbabilityTheory.Indep.indep

theorem IndepFun.indepFun {m₀ : MeasurableSpace Ω} {μ : Measure Ω} {β : ι → Type _}
    {m : ∀ x, MeasurableSpace (β x)} {f : ∀ i, Ω → β i} (hf_Indep : IndepFun m f μ) {i j : ι}
    (hij : i ≠ j) : IndepFunCat (f i) (f j) μ :=
  hf_Indep.indep hij
#align probability_theory.Indep_fun.indep_fun ProbabilityTheory.IndepFun.indepFun

end FromIndepToIndep

/-!
## π-system lemma

Independence of measurable spaces is equivalent to independence of generating π-systems.
-/

section FromMeasurableSpacesToSetsOfSets

/-! ### Independence of measurable space structures implies independence of generating π-systems -/

theorem Indep.indepSets [MeasurableSpace Ω] {μ : Measure Ω} {m : ι → MeasurableSpace Ω}
    {s : ι → Set (Set Ω)} (hms : ∀ n, m n = generateFrom (s n)) (h_indep : Indep m μ) :
    IndepSets s μ := fun S f hfs =>
  h_indep S fun x hxS =>
    ((hms x).symm ▸ measurableSet_generateFrom (hfs x hxS) : measurable_set[m x] (f x))
#align probability_theory.Indep.Indep_sets ProbabilityTheory.Indep.indepSets

theorem IndepCat.indepSets [MeasurableSpace Ω] {μ : Measure Ω} {s1 s2 : Set (Set Ω)}
    (h_indep : IndepCat (generateFrom s1) (generateFrom s2) μ) : IndepSetsCat s1 s2 μ :=
  fun t1 t2 ht1 ht2 =>
  h_indep t1 t2 (measurableSet_generateFrom ht1) (measurableSet_generateFrom ht2)
#align probability_theory.indep.indep_sets ProbabilityTheory.IndepCat.indepSets

end FromMeasurableSpacesToSetsOfSets

section FromPiSystemsToMeasurableSpaces

/-! ### Independence of generating π-systems implies independence of measurable space structures -/

private theorem indep_sets.indep_aux {m2 : MeasurableSpace Ω} {m : MeasurableSpace Ω}
    {μ : Measure Ω} [IsProbabilityMeasure μ] {p1 p2 : Set (Set Ω)} (h2 : m2 ≤ m)
    (hp2 : IsPiSystem p2) (hpm2 : m2 = generateFrom p2) (hyp : IndepSetsCat p1 p2 μ) {t1 t2 : Set Ω}
    (ht1 : t1 ∈ p1) (ht2m : measurable_set[m2] t2) : μ (t1 ∩ t2) = μ t1 * μ t2 :=
  by
  let μ_inter := μ.restrict t1
  let ν := μ t1 • μ
  have h_univ : μ_inter Set.univ = ν Set.univ := by
    rw [measure.restrict_apply_univ, measure.smul_apply, smul_eq_mul, measure_univ, mul_one]
  haveI : is_finite_measure μ_inter := @restrict.is_finite_measure Ω _ t1 μ ⟨measure_lt_top μ t1⟩
  rw [Set.inter_comm, ← measure.restrict_apply (h2 t2 ht2m)]
  refine' ext_on_measurable_space_of_generate_finite m p2 (fun t ht => _) h2 hpm2 hp2 h_univ ht2m
  have ht2 : measurable_set[m] t := by
    refine' h2 _ _
    rw [hpm2]
    exact measurable_set_generate_from ht
  rw [measure.restrict_apply ht2, measure.smul_apply, Set.inter_comm]
  exact hyp t1 t ht1 ht
#align probability_theory.indep_sets.indep_aux probability_theory.indep_sets.indep_aux

theorem IndepSetsCat.indep {m1 m2 : MeasurableSpace Ω} {m : MeasurableSpace Ω} {μ : Measure Ω}
    [IsProbabilityMeasure μ] {p1 p2 : Set (Set Ω)} (h1 : m1 ≤ m) (h2 : m2 ≤ m) (hp1 : IsPiSystem p1)
    (hp2 : IsPiSystem p2) (hpm1 : m1 = generateFrom p1) (hpm2 : m2 = generateFrom p2)
    (hyp : IndepSetsCat p1 p2 μ) : IndepCat m1 m2 μ :=
  by
  intro t1 t2 ht1 ht2
  let μ_inter := μ.restrict t2
  let ν := μ t2 • μ
  have h_univ : μ_inter Set.univ = ν Set.univ := by
    rw [measure.restrict_apply_univ, measure.smul_apply, smul_eq_mul, measure_univ, mul_one]
  haveI : is_finite_measure μ_inter := @restrict.is_finite_measure Ω _ t2 μ ⟨measure_lt_top μ t2⟩
  rw [mul_comm, ← measure.restrict_apply (h1 t1 ht1)]
  refine' ext_on_measurable_space_of_generate_finite m p1 (fun t ht => _) h1 hpm1 hp1 h_univ ht1
  have ht1 : measurable_set[m] t := by
    refine' h1 _ _
    rw [hpm1]
    exact measurable_set_generate_from ht
  rw [measure.restrict_apply ht1, measure.smul_apply, smul_eq_mul, mul_comm]
  exact indep_sets.indep_aux h2 hp2 hpm2 hyp ht ht2
#align probability_theory.indep_sets.indep ProbabilityTheory.IndepSetsCat.indep

theorem IndepSetsCat.indep' {m : MeasurableSpace Ω} {μ : Measure Ω} [IsProbabilityMeasure μ]
    {p1 p2 : Set (Set Ω)} (hp1m : ∀ s ∈ p1, MeasurableSet s) (hp2m : ∀ s ∈ p2, MeasurableSet s)
    (hp1 : IsPiSystem p1) (hp2 : IsPiSystem p2) (hyp : IndepSetsCat p1 p2 μ) :
    IndepCat (generateFrom p1) (generateFrom p2) μ :=
  hyp.indep (generateFrom_le hp1m) (generateFrom_le hp2m) hp1 hp2 rfl rfl
#align probability_theory.indep_sets.indep' ProbabilityTheory.IndepSetsCat.indep'

variable {m0 : MeasurableSpace Ω} {μ : Measure Ω}

theorem indepSetsPiUnionInterOfDisjoint [IsProbabilityMeasure μ] {s : ι → Set (Set Ω)} {S T : Set ι}
    (h_indep : IndepSets s μ) (hST : Disjoint S T) :
    IndepSetsCat (piUnionᵢInter s S) (piUnionᵢInter s T) μ :=
  by
  rintro t1 t2 ⟨p1, hp1, f1, ht1_m, ht1_eq⟩ ⟨p2, hp2, f2, ht2_m, ht2_eq⟩
  classical
    let g i := ite (i ∈ p1) (f1 i) Set.univ ∩ ite (i ∈ p2) (f2 i) Set.univ
    have h_P_inter : μ (t1 ∩ t2) = ∏ n in p1 ∪ p2, μ (g n) :=
      by
      have hgm : ∀ i ∈ p1 ∪ p2, g i ∈ s i :=
        by
        intro i hi_mem_union
        rw [Finset.mem_union] at hi_mem_union
        cases' hi_mem_union with hi1 hi2
        · have hi2 : i ∉ p2 := fun hip2 => set.disjoint_left.mp hST (hp1 hi1) (hp2 hip2)
          simp_rw [g, if_pos hi1, if_neg hi2, Set.inter_univ]
          exact ht1_m i hi1
        · have hi1 : i ∉ p1 := fun hip1 => set.disjoint_right.mp hST (hp2 hi2) (hp1 hip1)
          simp_rw [g, if_neg hi1, if_pos hi2, Set.univ_inter]
          exact ht2_m i hi2
      have h_p1_inter_p2 :
        ((⋂ x ∈ p1, f1 x) ∩ ⋂ x ∈ p2, f2 x) =
          ⋂ i ∈ p1 ∪ p2, ite (i ∈ p1) (f1 i) Set.univ ∩ ite (i ∈ p2) (f2 i) Set.univ :=
        by
        ext1 x
        simp only [Set.mem_ite_univ_right, Set.mem_inter_iff, Set.mem_interᵢ, Finset.mem_union]
        exact
          ⟨fun h i _ => ⟨h.1 i, h.2 i⟩, fun h =>
            ⟨fun i hi => (h i (Or.inl hi)).1 hi, fun i hi => (h i (Or.inr hi)).2 hi⟩⟩
      rw [ht1_eq, ht2_eq, h_p1_inter_p2, ← h_indep _ hgm]
    have h_μg : ∀ n, μ (g n) = ite (n ∈ p1) (μ (f1 n)) 1 * ite (n ∈ p2) (μ (f2 n)) 1 :=
      by
      intro n
      simp_rw [g]
      split_ifs
      · exact absurd rfl (set.disjoint_iff_forall_ne.mp hST _ (hp1 h) _ (hp2 h_1))
      all_goals simp only [measure_univ, one_mul, mul_one, Set.inter_univ, Set.univ_inter]
    simp_rw [h_P_inter, h_μg, Finset.prod_mul_distrib,
      Finset.prod_ite_mem (p1 ∪ p2) p1 fun x => μ (f1 x), Finset.union_inter_cancel_left,
      Finset.prod_ite_mem (p1 ∪ p2) p2 fun x => μ (f2 x), Finset.union_inter_cancel_right, ht1_eq, ←
      h_indep p1 ht1_m, ht2_eq, ← h_indep p2 ht2_m]
#align probability_theory.indep_sets_pi_Union_Inter_of_disjoint ProbabilityTheory.indepSetsPiUnionInterOfDisjoint

theorem IndepSet.indepGenerateFromOfDisjoint [IsProbabilityMeasure μ] {s : ι → Set Ω}
    (hsm : ∀ n, MeasurableSet (s n)) (hs : IndepSet s μ) (S T : Set ι) (hST : Disjoint S T) :
    IndepCat (generateFrom { t | ∃ n ∈ S, s n = t }) (generateFrom { t | ∃ k ∈ T, s k = t }) μ :=
  by
  rw [← generateFrom_piUnionᵢInter_singleton_left, ← generateFrom_piUnionᵢInter_singleton_left]
  refine'
    indep_sets.indep'
      (fun t ht => generateFrom_piUnionᵢInter_le _ _ _ _ (measurable_set_generate_from ht))
      (fun t ht => generateFrom_piUnionᵢInter_le _ _ _ _ (measurable_set_generate_from ht)) _ _ _
  · exact fun k => generate_from_le fun t ht => (Set.mem_singleton_iff.1 ht).symm ▸ hsm k
  · exact fun k => generate_from_le fun t ht => (Set.mem_singleton_iff.1 ht).symm ▸ hsm k
  · exact isPiSystem_piUnionᵢInter _ (fun k => IsPiSystem.singleton _) _
  · exact isPiSystem_piUnionᵢInter _ (fun k => IsPiSystem.singleton _) _
  · classical exact indep_sets_pi_Union_Inter_of_disjoint (Indep.Indep_sets (fun n => rfl) hs) hST
#align probability_theory.Indep_set.indep_generate_from_of_disjoint ProbabilityTheory.IndepSet.indepGenerateFromOfDisjoint

theorem indepSuprOfDisjoint [IsProbabilityMeasure μ] {m : ι → MeasurableSpace Ω}
    (h_le : ∀ i, m i ≤ m0) (h_indep : Indep m μ) {S T : Set ι} (hST : Disjoint S T) :
    IndepCat (⨆ i ∈ S, m i) (⨆ i ∈ T, m i) μ :=
  by
  refine'
    indep_sets.indep (supᵢ₂_le fun i _ => h_le i) (supᵢ₂_le fun i _ => h_le i) _ _
      (generateFrom_piUnionᵢInter_measurableSet m S).symm
      (generateFrom_piUnionᵢInter_measurableSet m T).symm _
  · exact isPiSystem_piUnionᵢInter _ (fun n => @is_pi_system_measurable_set Ω (m n)) _
  · exact isPiSystem_piUnionᵢInter _ (fun n => @is_pi_system_measurable_set Ω (m n)) _
  · classical exact indep_sets_pi_Union_Inter_of_disjoint h_indep hST
#align probability_theory.indep_supr_of_disjoint ProbabilityTheory.indepSuprOfDisjoint

theorem indepSuprOfDirectedLe {Ω} {m : ι → MeasurableSpace Ω} {m' m0 : MeasurableSpace Ω}
    {μ : Measure Ω} [IsProbabilityMeasure μ] (h_indep : ∀ i, IndepCat (m i) m' μ)
    (h_le : ∀ i, m i ≤ m0) (h_le' : m' ≤ m0) (hm : Directed (· ≤ ·) m) : IndepCat (⨆ i, m i) m' μ :=
  by
  let p : ι → Set (Set Ω) := fun n => { t | measurable_set[m n] t }
  have hp : ∀ n, IsPiSystem (p n) := fun n => @is_pi_system_measurable_set Ω (m n)
  have h_gen_n : ∀ n, m n = generate_from (p n) := fun n =>
    (@generate_from_measurable_set Ω (m n)).symm
  have hp_supr_pi : IsPiSystem (⋃ n, p n) := isPiSystem_unionᵢ_of_directed_le p hp hm
  let p' := { t : Set Ω | measurable_set[m'] t }
  have hp'_pi : IsPiSystem p' := @is_pi_system_measurable_set Ω m'
  have h_gen' : m' = generate_from p' := (@generate_from_measurable_set Ω m').symm
  -- the π-systems defined are independent
  have h_pi_system_indep : indep_sets (⋃ n, p n) p' μ :=
    by
    refine' indep_sets.Union _
    simp_rw [h_gen_n, h_gen'] at h_indep
    exact fun n => (h_indep n).IndepSetsCat
  -- now go from π-systems to σ-algebras
  refine' indep_sets.indep (supᵢ_le h_le) h_le' hp_supr_pi hp'_pi _ h_gen' h_pi_system_indep
  exact (generate_from_Union_measurable_set _).symm
#align probability_theory.indep_supr_of_directed_le ProbabilityTheory.indepSuprOfDirectedLe

theorem IndepSet.indepGenerateFromLt [Preorder ι] [IsProbabilityMeasure μ] {s : ι → Set Ω}
    (hsm : ∀ n, MeasurableSet (s n)) (hs : IndepSet s μ) (i : ι) :
    IndepCat (generateFrom {s i}) (generateFrom { t | ∃ j < i, s j = t }) μ :=
  by
  convert hs.indep_generate_from_of_disjoint hsm {i} { j | j < i }
      (set.disjoint_singleton_left.mpr (lt_irrefl _))
  simp only [Set.mem_singleton_iff, exists_prop, exists_eq_left, Set.setOf_eq_eq_singleton']
#align probability_theory.Indep_set.indep_generate_from_lt ProbabilityTheory.IndepSet.indepGenerateFromLt

theorem IndepSet.indepGenerateFromLe [LinearOrder ι] [IsProbabilityMeasure μ] {s : ι → Set Ω}
    (hsm : ∀ n, MeasurableSet (s n)) (hs : IndepSet s μ) (i : ι) {k : ι} (hk : i < k) :
    IndepCat (generateFrom {s k}) (generateFrom { t | ∃ j ≤ i, s j = t }) μ :=
  by
  convert hs.indep_generate_from_of_disjoint hsm {k} { j | j ≤ i }
      (set.disjoint_singleton_left.mpr hk.not_le)
  simp only [Set.mem_singleton_iff, exists_prop, exists_eq_left, Set.setOf_eq_eq_singleton']
#align probability_theory.Indep_set.indep_generate_from_le ProbabilityTheory.IndepSet.indepGenerateFromLe

theorem IndepSet.indepGenerateFromLeNat [IsProbabilityMeasure μ] {s : ℕ → Set Ω}
    (hsm : ∀ n, MeasurableSet (s n)) (hs : IndepSet s μ) (n : ℕ) :
    IndepCat (generateFrom {s (n + 1)}) (generateFrom { t | ∃ k ≤ n, s k = t }) μ :=
  hs.indepGenerateFromLe hsm _ n.lt_succ_self
#align probability_theory.Indep_set.indep_generate_from_le_nat ProbabilityTheory.IndepSet.indepGenerateFromLeNat

theorem indepSuprOfMonotone [SemilatticeSup ι] {Ω} {m : ι → MeasurableSpace Ω}
    {m' m0 : MeasurableSpace Ω} {μ : Measure Ω} [IsProbabilityMeasure μ]
    (h_indep : ∀ i, IndepCat (m i) m' μ) (h_le : ∀ i, m i ≤ m0) (h_le' : m' ≤ m0)
    (hm : Monotone m) : IndepCat (⨆ i, m i) m' μ :=
  indepSuprOfDirectedLe h_indep h_le h_le' (Monotone.directed_le hm)
#align probability_theory.indep_supr_of_monotone ProbabilityTheory.indepSuprOfMonotone

theorem indepSuprOfAntitone [SemilatticeInf ι] {Ω} {m : ι → MeasurableSpace Ω}
    {m' m0 : MeasurableSpace Ω} {μ : Measure Ω} [IsProbabilityMeasure μ]
    (h_indep : ∀ i, IndepCat (m i) m' μ) (h_le : ∀ i, m i ≤ m0) (h_le' : m' ≤ m0)
    (hm : Antitone m) : IndepCat (⨆ i, m i) m' μ :=
  indepSuprOfDirectedLe h_indep h_le h_le' (directed_of_inf hm)
#align probability_theory.indep_supr_of_antitone ProbabilityTheory.indepSuprOfAntitone

theorem IndepSets.piUnionInterOfNotMem {π : ι → Set (Set Ω)} {a : ι} {S : Finset ι}
    (hp_ind : IndepSets π μ) (haS : a ∉ S) : IndepSetsCat (piUnionᵢInter π S) (π a) μ :=
  by
  rintro t1 t2 ⟨s, hs_mem, ft1, hft1_mem, ht1_eq⟩ ht2_mem_pia
  rw [Finset.coe_subset] at hs_mem
  classical
    let f n := ite (n = a) t2 (ite (n ∈ s) (ft1 n) Set.univ)
    have h_f_mem : ∀ n ∈ insert a s, f n ∈ π n :=
      by
      intro n hn_mem_insert
      simp_rw [f]
      cases' finset.mem_insert.mp hn_mem_insert with hn_mem hn_mem
      · simp [hn_mem, ht2_mem_pia]
      · have hn_ne_a : n ≠ a := by
          rintro rfl
          exact haS (hs_mem hn_mem)
        simp [hn_ne_a, hn_mem, hft1_mem n hn_mem]
    have h_f_mem_pi : ∀ n ∈ s, f n ∈ π n := fun x hxS => h_f_mem x (by simp [hxS])
    have h_t1 : t1 = ⋂ n ∈ s, f n :=
      by
      suffices h_forall : ∀ n ∈ s, f n = ft1 n
      · rw [ht1_eq]
        congr with (n x)
        congr with (hns y)
        simp only [(h_forall n hns).symm]
      intro n hnS
      have hn_ne_a : n ≠ a := by
        rintro rfl
        exact haS (hs_mem hnS)
      simp_rw [f, if_pos hnS, if_neg hn_ne_a]
    have h_μ_t1 : μ t1 = ∏ n in s, μ (f n) := by rw [h_t1, ← hp_ind s h_f_mem_pi]
    have h_t2 : t2 = f a := by
      simp_rw [f]
      simp
    have h_μ_inter : μ (t1 ∩ t2) = ∏ n in insert a s, μ (f n) :=
      by
      have h_t1_inter_t2 : t1 ∩ t2 = ⋂ n ∈ insert a s, f n := by
        rw [h_t1, h_t2, Finset.set_binterᵢ_insert, Set.inter_comm]
      rw [h_t1_inter_t2, ← hp_ind (insert a s) h_f_mem]
    have has : a ∉ s := fun has_mem => haS (hs_mem Membership)
    rw [h_μ_inter, Finset.prod_insert has, h_t2, mul_comm, h_μ_t1]
#align probability_theory.Indep_sets.pi_Union_Inter_of_not_mem ProbabilityTheory.IndepSets.piUnionInterOfNotMem

/-- The measurable space structures generated by independent pi-systems are independent. -/
theorem IndepSets.indep [IsProbabilityMeasure μ] (m : ι → MeasurableSpace Ω) (h_le : ∀ i, m i ≤ m0)
    (π : ι → Set (Set Ω)) (h_pi : ∀ n, IsPiSystem (π n))
    (h_generate : ∀ i, m i = generateFrom (π i)) (h_ind : IndepSets π μ) : Indep m μ := by
  classical
    refine' Finset.induction _ _
    ·
      simp only [measure_univ, imp_true_iff, Set.interᵢ_false, Set.interᵢ_univ, Finset.prod_empty,
        eq_self_iff_true]
    intro a S ha_notin_S h_rec f hf_m
    have hf_m_S : ∀ x ∈ S, measurable_set[m x] (f x) := fun x hx => hf_m x (by simp [hx])
    rw [Finset.set_binterᵢ_insert, Finset.prod_insert ha_notin_S, ← h_rec hf_m_S]
    let p := piUnionᵢInter π S
    set m_p := generate_from p with hS_eq_generate
    have h_indep : indep m_p (m a) μ :=
      by
      have hp : IsPiSystem p := isPiSystem_piUnionᵢInter π h_pi S
      have h_le' : ∀ i, generate_from (π i) ≤ m0 := fun i => (h_generate i).symm.trans_le (h_le i)
      have hm_p : m_p ≤ m0 := generateFrom_piUnionᵢInter_le π h_le' S
      exact
        indep_sets.indep hm_p (h_le a) hp (h_pi a) hS_eq_generate (h_generate a)
          (h_ind.pi_Union_Inter_of_not_mem ha_notin_S)
    refine' h_indep.symm (f a) (⋂ n ∈ S, f n) (hf_m a (Finset.mem_insert_self a S)) _
    have h_le_p : ∀ i ∈ S, m i ≤ m_p := by
      intro n hn
      rw [hS_eq_generate, h_generate n]
      exact le_generateFrom_piUnionᵢInter S hn
    have h_S_f : ∀ i ∈ S, measurable_set[m_p] (f i) := fun i hi => (h_le_p i hi) (f i) (hf_m_S i hi)
    exact S.measurable_set_bInter h_S_f
#align probability_theory.Indep_sets.Indep ProbabilityTheory.IndepSets.indep

end FromPiSystemsToMeasurableSpaces

section IndepSet

/-! ### Independence of measurable sets

We prove the following equivalences on `indep_set`, for measurable sets `s, t`.
* `indep_set s t μ ↔ μ (s ∩ t) = μ s * μ t`,
* `indep_set s t μ ↔ indep_sets {s} {t} μ`.
-/

variable {s t : Set Ω} (S T : Set (Set Ω))

theorem indepSetCat_iff_indepSetsCat_singleton {m0 : MeasurableSpace Ω} (hs_meas : MeasurableSet s)
    (ht_meas : MeasurableSet t) (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume)
    [IsProbabilityMeasure μ] : IndepSetCat s t μ ↔ IndepSetsCat {s} {t} μ :=
  ⟨IndepCat.indepSets, fun h =>
    IndepSetsCat.indep (generateFrom_le fun u hu => by rwa [set.mem_singleton_iff.mp hu])
      (generateFrom_le fun u hu => by rwa [set.mem_singleton_iff.mp hu]) (IsPiSystem.singleton s)
      (IsPiSystem.singleton t) rfl rfl h⟩
#align probability_theory.indep_set_iff_indep_sets_singleton ProbabilityTheory.indepSetCat_iff_indepSetsCat_singleton

theorem indepSetCat_iff_measure_inter_eq_mul {m0 : MeasurableSpace Ω} (hs_meas : MeasurableSet s)
    (ht_meas : MeasurableSet t) (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume)
    [IsProbabilityMeasure μ] : IndepSetCat s t μ ↔ μ (s ∩ t) = μ s * μ t :=
  (indepSetCat_iff_indepSetsCat_singleton hs_meas ht_meas μ).trans indepSetsCat_singleton_iff
#align probability_theory.indep_set_iff_measure_inter_eq_mul ProbabilityTheory.indepSetCat_iff_measure_inter_eq_mul

theorem IndepSetsCat.indepSetOfMem {m0 : MeasurableSpace Ω} (hs : s ∈ S) (ht : t ∈ T)
    (hs_meas : MeasurableSet s) (ht_meas : MeasurableSet t)
    (μ : Measure Ω := by exact MeasureTheory.MeasureSpace.volume) [IsProbabilityMeasure μ]
    (h_indep : IndepSetsCat S T μ) : IndepSetCat s t μ :=
  (indepSetCat_iff_measure_inter_eq_mul hs_meas ht_meas μ).mpr (h_indep s t hs ht)
#align probability_theory.indep_sets.indep_set_of_mem ProbabilityTheory.IndepSetsCat.indepSetOfMem

theorem IndepCat.indepSetOfMeasurableSet {m₁ m₂ m0 : MeasurableSpace Ω} {μ : Measure Ω}
    (h_indep : IndepCat m₁ m₂ μ) {s t : Set Ω} (hs : measurable_set[m₁] s)
    (ht : measurable_set[m₂] t) : IndepSetCat s t μ :=
  by
  refine' fun s' t' hs' ht' => h_indep s' t' _ _
  · refine' generate_from_induction (fun u => measurable_set[m₁] u) {s} _ _ _ _ hs'
    · simp only [hs, Set.mem_singleton_iff, Set.mem_setOf_eq, forall_eq]
    · exact @MeasurableSet.empty _ m₁
    · exact fun u hu => hu.compl
    · exact fun f hf => MeasurableSet.unionᵢ hf
  · refine' generate_from_induction (fun u => measurable_set[m₂] u) {t} _ _ _ _ ht'
    · simp only [ht, Set.mem_singleton_iff, Set.mem_setOf_eq, forall_eq]
    · exact @MeasurableSet.empty _ m₂
    · exact fun u hu => hu.compl
    · exact fun f hf => MeasurableSet.unionᵢ hf
#align probability_theory.indep.indep_set_of_measurable_set ProbabilityTheory.IndepCat.indepSetOfMeasurableSet

theorem indepCat_iff_forall_indepSetCat (m₁ m₂ : MeasurableSpace Ω) {m0 : MeasurableSpace Ω}
    (μ : Measure Ω) :
    IndepCat m₁ m₂ μ ↔ ∀ s t, measurable_set[m₁] s → measurable_set[m₂] t → IndepSetCat s t μ :=
  ⟨fun h => fun s t hs ht => h.indepSetOfMeasurableSet hs ht, fun h s t hs ht =>
    h s t hs ht s t (measurableSet_generateFrom (Set.mem_singleton s))
      (measurableSet_generateFrom (Set.mem_singleton t))⟩
#align probability_theory.indep_iff_forall_indep_set ProbabilityTheory.indepCat_iff_forall_indepSetCat

end IndepSet

section IndepFun

/-! ### Independence of random variables

-/

variable {β β' γ γ' : Type _} {mΩ : MeasurableSpace Ω} {μ : Measure Ω} {f : Ω → β} {g : Ω → β'}

theorem indepFunCat_iff_measure_inter_preimage_eq_mul {mβ : MeasurableSpace β}
    {mβ' : MeasurableSpace β'} :
    IndepFunCat f g μ ↔
      ∀ s t,
        MeasurableSet s → MeasurableSet t → μ (f ⁻¹' s ∩ g ⁻¹' t) = μ (f ⁻¹' s) * μ (g ⁻¹' t) :=
  by
  constructor <;> intro h
  · refine' fun s t hs ht => h (f ⁻¹' s) (g ⁻¹' t) ⟨s, hs, rfl⟩ ⟨t, ht, rfl⟩
  · rintro _ _ ⟨s, hs, rfl⟩ ⟨t, ht, rfl⟩
    exact h s t hs ht
#align probability_theory.indep_fun_iff_measure_inter_preimage_eq_mul ProbabilityTheory.indepFunCat_iff_measure_inter_preimage_eq_mul

theorem indepFun_iff_measure_inter_preimage_eq_mul {ι : Type _} {β : ι → Type _}
    (m : ∀ x, MeasurableSpace (β x)) (f : ∀ i, Ω → β i) :
    IndepFun m f μ ↔
      ∀ (S : Finset ι) {sets : ∀ i : ι, Set (β i)} (H : ∀ i, i ∈ S → measurable_set[m i] (sets i)),
        μ (⋂ i ∈ S, f i ⁻¹' sets i) = ∏ i in S, μ (f i ⁻¹' sets i) :=
  by
  refine' ⟨fun h S sets h_meas => h _ fun i hi_mem => ⟨sets i, h_meas i hi_mem, rfl⟩, _⟩
  intro h S setsΩ h_meas
  classical
    let setsβ : ∀ i : ι, Set (β i) := fun i =>
      dite (i ∈ S) (fun hi_mem => (h_meas i hi_mem).some) fun _ => Set.univ
    have h_measβ : ∀ i ∈ S, measurable_set[m i] (setsβ i) :=
      by
      intro i hi_mem
      simp_rw [setsβ, dif_pos hi_mem]
      exact (h_meas i hi_mem).choose_spec.1
    have h_preim : ∀ i ∈ S, setsΩ i = f i ⁻¹' setsβ i :=
      by
      intro i hi_mem
      simp_rw [setsβ, dif_pos hi_mem]
      exact (h_meas i hi_mem).choose_spec.2.symm
    have h_left_eq : μ (⋂ i ∈ S, setsΩ i) = μ (⋂ i ∈ S, f i ⁻¹' setsβ i) :=
      by
      congr with (i x)
      simp only [Set.mem_interᵢ]
      constructor <;> intro h hi_mem <;> specialize h hi_mem
      · rwa [h_preim i hi_mem] at h
      · rwa [h_preim i hi_mem]
    have h_right_eq : (∏ i in S, μ (setsΩ i)) = ∏ i in S, μ (f i ⁻¹' setsβ i) :=
      by
      refine' Finset.prod_congr rfl fun i hi_mem => _
      rw [h_preim i hi_mem]
    rw [h_left_eq, h_right_eq]
    exact h S h_measβ
#align probability_theory.Indep_fun_iff_measure_inter_preimage_eq_mul ProbabilityTheory.indepFun_iff_measure_inter_preimage_eq_mul

theorem indepFunCat_iff_indepSetCat_preimage {mβ : MeasurableSpace β} {mβ' : MeasurableSpace β'}
    [IsProbabilityMeasure μ] (hf : Measurable f) (hg : Measurable g) :
    IndepFunCat f g μ ↔
      ∀ s t, MeasurableSet s → MeasurableSet t → IndepSetCat (f ⁻¹' s) (g ⁻¹' t) μ :=
  by
  refine' indep_fun_iff_measure_inter_preimage_eq_mul.trans _
  constructor <;> intro h s t hs ht <;> specialize h s t hs ht
  · rwa [indep_set_iff_measure_inter_eq_mul (hf hs) (hg ht) μ]
  · rwa [← indep_set_iff_measure_inter_eq_mul (hf hs) (hg ht) μ]
#align probability_theory.indep_fun_iff_indep_set_preimage ProbabilityTheory.indepFunCat_iff_indepSetCat_preimage

@[symm]
theorem IndepFunCat.symm {mβ : MeasurableSpace β} {f g : Ω → β} (hfg : IndepFunCat f g μ) :
    IndepFunCat g f μ :=
  hfg.symm
#align probability_theory.indep_fun.symm ProbabilityTheory.IndepFunCat.symm

theorem IndepFunCat.aeEq {mβ : MeasurableSpace β} {f g f' g' : Ω → β} (hfg : IndepFunCat f g μ)
    (hf : f =ᵐ[μ] f') (hg : g =ᵐ[μ] g') : IndepFunCat f' g' μ :=
  by
  rintro _ _ ⟨A, hA, rfl⟩ ⟨B, hB, rfl⟩
  have h1 : f ⁻¹' A =ᵐ[μ] f' ⁻¹' A := hf.fun_comp A
  have h2 : g ⁻¹' B =ᵐ[μ] g' ⁻¹' B := hg.fun_comp B
  rw [← measure_congr h1, ← measure_congr h2, ← measure_congr (h1.inter h2)]
  exact hfg _ _ ⟨_, hA, rfl⟩ ⟨_, hB, rfl⟩
#align probability_theory.indep_fun.ae_eq ProbabilityTheory.IndepFunCat.aeEq

theorem IndepFunCat.comp {mβ : MeasurableSpace β} {mβ' : MeasurableSpace β'}
    {mγ : MeasurableSpace γ} {mγ' : MeasurableSpace γ'} {φ : β → γ} {ψ : β' → γ'}
    (hfg : IndepFunCat f g μ) (hφ : Measurable φ) (hψ : Measurable ψ) :
    IndepFunCat (φ ∘ f) (ψ ∘ g) μ :=
  by
  rintro _ _ ⟨A, hA, rfl⟩ ⟨B, hB, rfl⟩
  apply hfg
  · exact ⟨φ ⁻¹' A, hφ hA, set.preimage_comp.symm⟩
  · exact ⟨ψ ⁻¹' B, hψ hB, set.preimage_comp.symm⟩
#align probability_theory.indep_fun.comp ProbabilityTheory.IndepFunCat.comp

/-- If `f` is a family of mutually independent random variables (`Indep_fun m f μ`) and `S, T` are
two disjoint finite index sets, then the tuple formed by `f i` for `i ∈ S` is independent of the
tuple `(f i)_i` for `i ∈ T`. -/
theorem IndepFun.indepFunFinset [IsProbabilityMeasure μ] {ι : Type _} {β : ι → Type _}
    {m : ∀ i, MeasurableSpace (β i)} {f : ∀ i, Ω → β i} (S T : Finset ι) (hST : Disjoint S T)
    (hf_Indep : IndepFun m f μ) (hf_meas : ∀ i, Measurable (f i)) :
    IndepFunCat (fun a (i : S) => f i a) (fun a (i : T) => f i a) μ :=
  by
  -- We introduce π-systems, build from the π-system of boxes which generates `measurable_space.pi`.
  let πSβ :=
    Set.pi (Set.univ : Set S) ''
      Set.pi (Set.univ : Set S) fun i => { s : Set (β i) | measurable_set[m i] s }
  let πS := { s : Set Ω | ∃ t ∈ πSβ, (fun a (i : S) => f i a) ⁻¹' t = s }
  have hπS_pi : IsPiSystem πS := is_pi_system_pi.comap fun a i => f i a
  have hπS_gen : (measurable_space.pi.comap fun a (i : S) => f i a) = generate_from πS :=
    by
    rw [generate_from_pi.symm, comap_generate_from]
    · congr with s
      simp only [Set.mem_image, Set.mem_setOf_eq, exists_prop]
    · infer_instance
  let πTβ :=
    Set.pi (Set.univ : Set T) ''
      Set.pi (Set.univ : Set T) fun i => { s : Set (β i) | measurable_set[m i] s }
  let πT := { s : Set Ω | ∃ t ∈ πTβ, (fun a (i : T) => f i a) ⁻¹' t = s }
  have hπT_pi : IsPiSystem πT := is_pi_system_pi.comap fun a i => f i a
  have hπT_gen : (measurable_space.pi.comap fun a (i : T) => f i a) = generate_from πT :=
    by
    rw [generate_from_pi.symm, comap_generate_from]
    · congr with s
      simp only [Set.mem_image, Set.mem_setOf_eq, exists_prop]
    · infer_instance
  -- To prove independence, we prove independence of the generating π-systems.
  refine'
    indep_sets.indep (Measurable.comap_le (measurable_pi_iff.mpr fun i => hf_meas i))
      (Measurable.comap_le (measurable_pi_iff.mpr fun i => hf_meas i)) hπS_pi hπT_pi hπS_gen hπT_gen
      _
  rintro _ _ ⟨s, ⟨sets_s, hs1, hs2⟩, rfl⟩ ⟨t, ⟨sets_t, ht1, ht2⟩, rfl⟩
  simp only [Set.mem_univ_pi, Set.mem_setOf_eq] at hs1 ht1
  rw [← hs2, ← ht2]
  classical
    let sets_s' : ∀ i : ι, Set (β i) := fun i =>
      dite (i ∈ S) (fun hi => sets_s ⟨i, hi⟩) fun _ => Set.univ
    have h_sets_s'_eq : ∀ {i} (hi : i ∈ S), sets_s' i = sets_s ⟨i, hi⟩ :=
      by
      intro i hi
      simp_rw [sets_s', dif_pos hi]
    have h_sets_s'_univ : ∀ {i} (hi : i ∈ T), sets_s' i = Set.univ :=
      by
      intro i hi
      simp_rw [sets_s', dif_neg (finset.disjoint_right.mp hST hi)]
    let sets_t' : ∀ i : ι, Set (β i) := fun i =>
      dite (i ∈ T) (fun hi => sets_t ⟨i, hi⟩) fun _ => Set.univ
    have h_sets_t'_univ : ∀ {i} (hi : i ∈ S), sets_t' i = Set.univ :=
      by
      intro i hi
      simp_rw [sets_t', dif_neg (finset.disjoint_left.mp hST hi)]
    have h_meas_s' : ∀ i ∈ S, MeasurableSet (sets_s' i) :=
      by
      intro i hi
      rw [h_sets_s'_eq hi]
      exact hs1 _
    have h_meas_t' : ∀ i ∈ T, MeasurableSet (sets_t' i) :=
      by
      intro i hi
      simp_rw [sets_t', dif_pos hi]
      exact ht1 _
    have h_eq_inter_S :
      (fun (ω : Ω) (i : ↥S) => f (↑i) ω) ⁻¹' Set.pi Set.univ sets_s = ⋂ i ∈ S, f i ⁻¹' sets_s' i :=
      by
      ext1 x
      simp only [Set.mem_preimage, Set.mem_univ_pi, Set.mem_interᵢ]
      constructor <;> intro h
      · intro i hi
        rw [h_sets_s'_eq hi]
        exact h ⟨i, hi⟩
      · rintro ⟨i, hi⟩
        specialize h i hi
        rw [h_sets_s'_eq hi] at h
        exact h
    have h_eq_inter_T :
      (fun (ω : Ω) (i : ↥T) => f (↑i) ω) ⁻¹' Set.pi Set.univ sets_t = ⋂ i ∈ T, f i ⁻¹' sets_t' i :=
      by
      ext1 x
      simp only [Set.mem_preimage, Set.mem_univ_pi, Set.mem_interᵢ]
      constructor <;> intro h
      · intro i hi
        simp_rw [sets_t', dif_pos hi]
        exact h ⟨i, hi⟩
      · rintro ⟨i, hi⟩
        specialize h i hi
        simp_rw [sets_t', dif_pos hi] at h
        exact h
    rw [Indep_fun_iff_measure_inter_preimage_eq_mul] at hf_Indep
    rw [h_eq_inter_S, h_eq_inter_T, hf_Indep S h_meas_s', hf_Indep T h_meas_t']
    have h_Inter_inter :
      ((⋂ i ∈ S, f i ⁻¹' sets_s' i) ∩ ⋂ i ∈ T, f i ⁻¹' sets_t' i) =
        ⋂ i ∈ S ∪ T, f i ⁻¹' (sets_s' i ∩ sets_t' i) :=
      by
      ext1 x
      simp only [Set.mem_inter_iff, Set.mem_interᵢ, Set.mem_preimage, Finset.mem_union]
      constructor <;> intro h
      · intro i hi
        cases hi
        · rw [h_sets_t'_univ hi]
          exact ⟨h.1 i hi, Set.mem_univ _⟩
        · rw [h_sets_s'_univ hi]
          exact ⟨Set.mem_univ _, h.2 i hi⟩
      · exact ⟨fun i hi => (h i (Or.inl hi)).1, fun i hi => (h i (Or.inr hi)).2⟩
    rw [h_Inter_inter, hf_Indep (S ∪ T)]
    swap
    · intro i hi_mem
      rw [Finset.mem_union] at hi_mem
      cases hi_mem
      · rw [h_sets_t'_univ hi_mem, Set.inter_univ]
        exact h_meas_s' i hi_mem
      · rw [h_sets_s'_univ hi_mem, Set.univ_inter]
        exact h_meas_t' i hi_mem
    rw [Finset.prod_union hST]
    congr 1
    · refine' Finset.prod_congr rfl fun i hi => _
      rw [h_sets_t'_univ hi, Set.inter_univ]
    · refine' Finset.prod_congr rfl fun i hi => _
      rw [h_sets_s'_univ hi, Set.univ_inter]
#align probability_theory.Indep_fun.indep_fun_finset ProbabilityTheory.IndepFun.indepFunFinset

theorem IndepFun.indepFunProd [IsProbabilityMeasure μ] {ι : Type _} {β : ι → Type _}
    {m : ∀ i, MeasurableSpace (β i)} {f : ∀ i, Ω → β i} (hf_Indep : IndepFun m f μ)
    (hf_meas : ∀ i, Measurable (f i)) (i j k : ι) (hik : i ≠ k) (hjk : j ≠ k) :
    IndepFunCat (fun a => (f i a, f j a)) (f k) μ := by
  classical
    have h_right :
      f k =
        (fun p : ∀ j : ({k} : Finset ι), β j => p ⟨k, Finset.mem_singleton_self k⟩) ∘
          fun a (j : ({k} : Finset ι)) => f j a :=
      rfl
    have h_meas_right :
      Measurable fun p : ∀ j : ({k} : Finset ι), β j => p ⟨k, Finset.mem_singleton_self k⟩ :=
      measurable_pi_apply ⟨k, Finset.mem_singleton_self k⟩
    let s : Finset ι := {i, j}
    have h_left :
      (fun ω => (f i ω, f j ω)) =
        (fun p : ∀ l : s, β l =>
            (p ⟨i, Finset.mem_insert_self i _⟩,
              p ⟨j, Finset.mem_insert_of_mem (Finset.mem_singleton_self _)⟩)) ∘
          fun a (j : s) => f j a :=
      by
      ext1 a
      simp only [Prod.mk.inj_iff]
      constructor <;> rfl
    have h_meas_left :
      Measurable fun p : ∀ l : s, β l =>
        (p ⟨i, Finset.mem_insert_self i _⟩,
          p ⟨j, Finset.mem_insert_of_mem (Finset.mem_singleton_self _)⟩) :=
      Measurable.prod (measurable_pi_apply ⟨i, Finset.mem_insert_self i {j}⟩)
        (measurable_pi_apply ⟨j, Finset.mem_insert_of_mem (Finset.mem_singleton_self j)⟩)
    rw [h_left, h_right]
    refine' (hf_Indep.indep_fun_finset s {k} _ hf_meas).comp h_meas_left h_meas_right
    rw [Finset.disjoint_singleton_right]
    simp only [Finset.mem_insert, Finset.mem_singleton, not_or]
    exact ⟨hik.symm, hjk.symm⟩
#align probability_theory.Indep_fun.indep_fun_prod ProbabilityTheory.IndepFun.indepFunProd

@[to_additive]
theorem IndepFun.mul [IsProbabilityMeasure μ] {ι : Type _} {β : Type _} {m : MeasurableSpace β}
    [Mul β] [HasMeasurableMul₂ β] {f : ι → Ω → β} (hf_Indep : IndepFun (fun _ => m) f μ)
    (hf_meas : ∀ i, Measurable (f i)) (i j k : ι) (hik : i ≠ k) (hjk : j ≠ k) :
    IndepFunCat (f i * f j) (f k) μ :=
  by
  have : indep_fun (fun ω => (f i ω, f j ω)) (f k) μ :=
    hf_Indep.indep_fun_prod hf_meas i j k hik hjk
  change indep_fun ((fun p : β × β => p.fst * p.snd) ∘ fun ω => (f i ω, f j ω)) (id ∘ f k) μ
  exact indep_fun.comp this (measurable_fst.mul measurable_snd) measurable_id
#align probability_theory.Indep_fun.mul ProbabilityTheory.IndepFun.mul
#align probability_theory.Indep_fun.add ProbabilityTheory.IndepFun.add

@[to_additive]
theorem IndepFun.indepFunFinsetProdOfNotMem [IsProbabilityMeasure μ] {ι : Type _} {β : Type _}
    {m : MeasurableSpace β} [CommMonoid β] [HasMeasurableMul₂ β] {f : ι → Ω → β}
    (hf_Indep : IndepFun (fun _ => m) f μ) (hf_meas : ∀ i, Measurable (f i)) {s : Finset ι} {i : ι}
    (hi : i ∉ s) : IndepFunCat (∏ j in s, f j) (f i) μ := by
  classical
    have h_right :
      f i =
        (fun p : ∀ j : ({i} : Finset ι), β => p ⟨i, Finset.mem_singleton_self i⟩) ∘
          fun a (j : ({i} : Finset ι)) => f j a :=
      rfl
    have h_meas_right :
      Measurable fun p : ∀ j : ({i} : Finset ι), β => p ⟨i, Finset.mem_singleton_self i⟩ :=
      measurable_pi_apply ⟨i, Finset.mem_singleton_self i⟩
    have h_left : (∏ j in s, f j) = (fun p : ∀ j : s, β => ∏ j, p j) ∘ fun a (j : s) => f j a :=
      by
      ext1 a
      simp only [Function.comp_apply]
      have : (∏ j : ↥s, f (↑j) a) = (∏ j : ↥s, f ↑j) a := by rw [Finset.prod_apply]
      rw [this, Finset.prod_coe_sort]
    have h_meas_left : Measurable fun p : ∀ j : s, β => ∏ j, p j :=
      finset.univ.measurable_prod fun (j : ↥s) (H : j ∈ Finset.univ) => measurable_pi_apply j
    rw [h_left, h_right]
    exact
      (hf_Indep.indep_fun_finset s {i} (finset.disjoint_singleton_left.mpr hi).symm hf_meas).comp
        h_meas_left h_meas_right
#align probability_theory.Indep_fun.indep_fun_finset_prod_of_not_mem ProbabilityTheory.IndepFun.indepFunFinsetProdOfNotMem
#align probability_theory.Indep_fun.indep_fun_finset_sum_of_not_mem ProbabilityTheory.IndepFun.indep_fun_finset_sum_of_not_mem

@[to_additive]
theorem IndepFun.indepFunProdRangeSucc [IsProbabilityMeasure μ] {β : Type _} {m : MeasurableSpace β}
    [CommMonoid β] [HasMeasurableMul₂ β] {f : ℕ → Ω → β} (hf_Indep : IndepFun (fun _ => m) f μ)
    (hf_meas : ∀ i, Measurable (f i)) (n : ℕ) : IndepFunCat (∏ j in Finset.range n, f j) (f n) μ :=
  hf_Indep.indepFunFinsetProdOfNotMem hf_meas Finset.not_mem_range_self
#align probability_theory.Indep_fun.indep_fun_prod_range_succ ProbabilityTheory.IndepFun.indepFunProdRangeSucc
#align probability_theory.Indep_fun.indep_fun_sum_range_succ ProbabilityTheory.IndepFun.indep_fun_sum_range_succ

theorem IndepSet.indepFunIndicator [Zero β] [One β] {m : MeasurableSpace β} {s : ι → Set Ω}
    (hs : IndepSet s μ) : IndepFun (fun n => m) (fun n => (s n).indicator fun ω => 1) μ := by
  classical
    rw [Indep_fun_iff_measure_inter_preimage_eq_mul]
    rintro S π hπ
    simp_rw [Set.indicator_const_preimage_eq_union]
    refine' @hs S (fun i => ite (1 ∈ π i) (s i) ∅ ∪ ite ((0 : β) ∈ π i) (s iᶜ) ∅) fun i hi => _
    have hsi : measurable_set[generate_from {s i}] (s i) :=
      measurable_set_generate_from (Set.mem_singleton _)
    refine'
      MeasurableSet.union (MeasurableSet.ite' (fun _ => hsi) fun _ => _)
        (MeasurableSet.ite' (fun _ => hsi.compl) fun _ => _)
    · exact @MeasurableSet.empty _ (generate_from {s i})
    · exact @MeasurableSet.empty _ (generate_from {s i})
#align probability_theory.Indep_set.Indep_fun_indicator ProbabilityTheory.IndepSet.indepFunIndicator

end IndepFun

/-! ### Kolmogorov's 0-1 law

Let `s : ι → measurable_space Ω` be an independent sequence of sub-σ-algebras. Then any set which
is measurable with respect to the tail σ-algebra `limsup s at_top` has probability 0 or 1.
-/

section ZeroOneLaw

variable {m m0 : MeasurableSpace Ω} {μ : Measure Ω}

theorem measure_eq_zero_or_one_or_top_of_indepSetCat_self {t : Set Ω}
    (h_indep : IndepSetCat t t μ) : μ t = 0 ∨ μ t = 1 ∨ μ t = ∞ :=
  by
  specialize
    h_indep t t (measurable_set_generate_from (Set.mem_singleton t))
      (measurable_set_generate_from (Set.mem_singleton t))
  by_cases h0 : μ t = 0
  · exact Or.inl h0
  by_cases h_top : μ t = ∞
  · exact Or.inr (Or.inr h_top)
  rw [← one_mul (μ (t ∩ t)), Set.inter_self, ENNReal.mul_eq_mul_right h0 h_top] at h_indep
  exact Or.inr (Or.inl h_indep.symm)
#align probability_theory.measure_eq_zero_or_one_or_top_of_indep_set_self ProbabilityTheory.measure_eq_zero_or_one_or_top_of_indepSetCat_self

theorem measure_eq_zero_or_one_of_indepSetCat_self [IsFiniteMeasure μ] {t : Set Ω}
    (h_indep : IndepSetCat t t μ) : μ t = 0 ∨ μ t = 1 :=
  by
  have h_0_1_top := measure_eq_zero_or_one_or_top_of_indep_set_self h_indep
  simpa [measure_ne_top μ] using h_0_1_top
#align probability_theory.measure_eq_zero_or_one_of_indep_set_self ProbabilityTheory.measure_eq_zero_or_one_of_indepSetCat_self

variable [IsProbabilityMeasure μ] {s : ι → MeasurableSpace Ω}

open Filter

theorem indepBsuprCompl (h_le : ∀ n, s n ≤ m0) (h_indep : Indep s μ) (t : Set ι) :
    IndepCat (⨆ n ∈ t, s n) (⨆ n ∈ tᶜ, s n) μ :=
  indepSuprOfDisjoint h_le h_indep disjoint_compl_right
#align probability_theory.indep_bsupr_compl ProbabilityTheory.indepBsuprCompl

section Abstract

variable {α : Type _} {p : Set ι → Prop} {f : Filter ι} {ns : α → Set ι}

/-! We prove a version of Kolmogorov's 0-1 law for the σ-algebra `limsup s f` where `f` is a filter
for which we can define the following two functions:
* `p : set ι → Prop` such that for a set `t`, `p t → tᶜ ∈ f`,
* `ns : α → set ι` a directed sequence of sets which all verify `p` and such that
  `⋃ a, ns a = set.univ`.

For the example of `f = at_top`, we can take `p = bdd_above` and `ns : ι → set ι := λ i, set.Iic i`.
-/

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:69:18: unsupported non-interactive tactic filter.is_bounded_default -/
theorem indepBsuprLimsup (h_le : ∀ n, s n ≤ m0) (h_indep : Indep s μ) (hf : ∀ t, p t → tᶜ ∈ f)
    {t : Set ι} (ht : p t) : IndepCat (⨆ n ∈ t, s n) (limsup s f) μ :=
  by
  refine' indep_of_indep_of_le_right (indep_bsupr_compl h_le h_indep t) _
  refine'
    Limsup_le_of_le
      (by
        run_tac
          is_bounded_default)
      _
  simp only [Set.mem_compl_iff, eventually_map]
  exact eventually_of_mem (hf t ht) le_supᵢ₂
#align probability_theory.indep_bsupr_limsup ProbabilityTheory.indepBsuprLimsup

theorem indepSuprDirectedLimsup (h_le : ∀ n, s n ≤ m0) (h_indep : Indep s μ)
    (hf : ∀ t, p t → tᶜ ∈ f) (hns : Directed (· ≤ ·) ns) (hnsp : ∀ a, p (ns a)) :
    IndepCat (⨆ a, ⨆ n ∈ ns a, s n) (limsup s f) μ :=
  by
  refine' indep_supr_of_directed_le _ _ _ _
  · exact fun a => indep_bsupr_limsup h_le h_indep hf (hnsp a)
  · exact fun a => supᵢ₂_le fun n hn => h_le n
  · exact limsup_le_supr.trans (supᵢ_le h_le)
  · intro a b
    obtain ⟨c, hc⟩ := hns a b
    refine' ⟨c, _, _⟩ <;> refine' supᵢ_mono fun n => supᵢ_mono' fun hn => ⟨_, le_rfl⟩
    · exact hc.1 hn
    · exact hc.2 hn
#align probability_theory.indep_supr_directed_limsup ProbabilityTheory.indepSuprDirectedLimsup

theorem indepSuprLimsup (h_le : ∀ n, s n ≤ m0) (h_indep : Indep s μ) (hf : ∀ t, p t → tᶜ ∈ f)
    (hns : Directed (· ≤ ·) ns) (hnsp : ∀ a, p (ns a)) (hns_univ : ∀ n, ∃ a, n ∈ ns a) :
    IndepCat (⨆ n, s n) (limsup s f) μ :=
  by
  suffices (⨆ a, ⨆ n ∈ ns a, s n) = ⨆ n, s n by
    rw [← this]
    exact indep_supr_directed_limsup h_le h_indep hf hns hnsp
  rw [supᵢ_comm]
  refine' supᵢ_congr fun n => _
  have : (⨆ (i : α) (H : n ∈ ns i), s n) = ⨆ h : ∃ i, n ∈ ns i, s n := by rw [supᵢ_exists]
  haveI : Nonempty (∃ i : α, n ∈ ns i) := ⟨hns_univ n⟩
  rw [this, supᵢ_const]
#align probability_theory.indep_supr_limsup ProbabilityTheory.indepSuprLimsup

theorem indepLimsupSelf (h_le : ∀ n, s n ≤ m0) (h_indep : Indep s μ) (hf : ∀ t, p t → tᶜ ∈ f)
    (hns : Directed (· ≤ ·) ns) (hnsp : ∀ a, p (ns a)) (hns_univ : ∀ n, ∃ a, n ∈ ns a) :
    IndepCat (limsup s f) (limsup s f) μ :=
  indepOfIndepOfLeLeft (indepSuprLimsup h_le h_indep hf hns hnsp hns_univ) limsup_le_supᵢ
#align probability_theory.indep_limsup_self ProbabilityTheory.indepLimsupSelf

theorem measure_zero_or_one_of_measurableSet_limsup (h_le : ∀ n, s n ≤ m0) (h_indep : Indep s μ)
    (hf : ∀ t, p t → tᶜ ∈ f) (hns : Directed (· ≤ ·) ns) (hnsp : ∀ a, p (ns a))
    (hns_univ : ∀ n, ∃ a, n ∈ ns a) {t : Set Ω} (ht_tail : measurable_set[limsup s f] t) :
    μ t = 0 ∨ μ t = 1 :=
  measure_eq_zero_or_one_of_indepSetCat_self
    ((indepLimsupSelf h_le h_indep hf hns hnsp hns_univ).indepSetOfMeasurableSet ht_tail ht_tail)
#align probability_theory.measure_zero_or_one_of_measurable_set_limsup ProbabilityTheory.measure_zero_or_one_of_measurableSet_limsup

end Abstract

section AtTop

variable [SemilatticeSup ι] [NoMaxOrder ι] [Nonempty ι]

theorem indepLimsupAtTopSelf (h_le : ∀ n, s n ≤ m0) (h_indep : Indep s μ) :
    IndepCat (limsup s atTop) (limsup s atTop) μ :=
  by
  let ns : ι → Set ι := Set.Iic
  have hnsp : ∀ i, BddAbove (ns i) := fun i => bddAbove_Iic
  refine' indep_limsup_self h_le h_indep _ _ hnsp _
  · simp only [mem_at_top_sets, ge_iff_le, Set.mem_compl_iff, BddAbove, upperBounds, Set.Nonempty]
    rintro t ⟨a, ha⟩
    obtain ⟨b, hb⟩ : ∃ b, a < b := exists_gt a
    refine' ⟨b, fun c hc hct => _⟩
    suffices : ∀ i ∈ t, i < c
    exact lt_irrefl c (this c hct)
    exact fun i hi => (ha hi).trans_lt (hb.trans_le hc)
  · exact Monotone.directed_le fun i j hij k hki => le_trans hki hij
  · exact fun n => ⟨n, le_rfl⟩
#align probability_theory.indep_limsup_at_top_self ProbabilityTheory.indepLimsupAtTopSelf

/-- **Kolmogorov's 0-1 law** : any event in the tail σ-algebra of an independent sequence of
sub-σ-algebras has probability 0 or 1.
The tail σ-algebra `limsup s at_top` is the same as `⋂ n, ⋃ i ≥ n, s i`. -/
theorem measure_zero_or_one_of_measurableSet_limsup_atTop (h_le : ∀ n, s n ≤ m0)
    (h_indep : Indep s μ) {t : Set Ω} (ht_tail : measurable_set[limsup s atTop] t) :
    μ t = 0 ∨ μ t = 1 :=
  measure_eq_zero_or_one_of_indepSetCat_self
    ((indepLimsupAtTopSelf h_le h_indep).indepSetOfMeasurableSet ht_tail ht_tail)
#align probability_theory.measure_zero_or_one_of_measurable_set_limsup_at_top ProbabilityTheory.measure_zero_or_one_of_measurableSet_limsup_atTop

end AtTop

section AtBot

variable [SemilatticeInf ι] [NoMinOrder ι] [Nonempty ι]

theorem indepLimsupAtBotSelf (h_le : ∀ n, s n ≤ m0) (h_indep : Indep s μ) :
    IndepCat (limsup s atBot) (limsup s atBot) μ :=
  by
  let ns : ι → Set ι := Set.Ici
  have hnsp : ∀ i, BddBelow (ns i) := fun i => bddBelow_Ici
  refine' indep_limsup_self h_le h_indep _ _ hnsp _
  · simp only [mem_at_bot_sets, ge_iff_le, Set.mem_compl_iff, BddBelow, lowerBounds, Set.Nonempty]
    rintro t ⟨a, ha⟩
    obtain ⟨b, hb⟩ : ∃ b, b < a := exists_lt a
    refine' ⟨b, fun c hc hct => _⟩
    suffices : ∀ i ∈ t, c < i
    exact lt_irrefl c (this c hct)
    exact fun i hi => hc.trans_lt (hb.trans_le (ha hi))
  · exact directed_of_inf fun i j hij k hki => hij.trans hki
  · exact fun n => ⟨n, le_rfl⟩
#align probability_theory.indep_limsup_at_bot_self ProbabilityTheory.indepLimsupAtBotSelf

/-- **Kolmogorov's 0-1 law** : any event in the tail σ-algebra of an independent sequence of
sub-σ-algebras has probability 0 or 1. -/
theorem measure_zero_or_one_of_measurableSet_limsup_atBot (h_le : ∀ n, s n ≤ m0)
    (h_indep : Indep s μ) {t : Set Ω} (ht_tail : measurable_set[limsup s atBot] t) :
    μ t = 0 ∨ μ t = 1 :=
  measure_eq_zero_or_one_of_indepSetCat_self
    ((indepLimsupAtBotSelf h_le h_indep).indepSetOfMeasurableSet ht_tail ht_tail)
#align probability_theory.measure_zero_or_one_of_measurable_set_limsup_at_bot ProbabilityTheory.measure_zero_or_one_of_measurableSet_limsup_atBot

end AtBot

end ZeroOneLaw

end ProbabilityTheory


/-
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
-/
import analysis.special_functions.exp

/-!
# Real logarithm

In this file we define `real.log` to be the logarithm of a real number. As usual, we extend it from
its domain `(0, +∞)` to a globally defined function. We choose to do it so that `log 0 = 0` and
`log (-x) = log x`.

We prove some basic properties of this function and show that it is continuous.

## Tags

logarithm, continuity
-/

open set filter function
open_locale topological_space
noncomputable theory

namespace real

variables {x y : ℝ}

/-- The real logarithm function, equal to the inverse of the exponential for `x > 0`,
to `log |x|` for `x < 0`, and to `0` for `0`. We use this unconventional extension to
`(-∞, 0]` as it gives the formula `log (x * y) = log x + log y` for all nonzero `x` and `y`, and
the derivative of `log` is `1/x` away from `0`. -/
@[pp_nodot] noncomputable def log (x : ℝ) : ℝ :=
if hx : x = 0 then 0 else exp_order_iso.symm ⟨|x|, abs_pos.2 hx⟩

lemma log_of_ne_zero (hx : x ≠ 0) : log x = exp_order_iso.symm ⟨|x|, abs_pos.2 hx⟩ := dif_neg hx

lemma log_of_pos (hx : 0 < x) : log x = exp_order_iso.symm ⟨x, hx⟩ :=
by { rw [log_of_ne_zero hx.ne'], congr, exact abs_of_pos hx }

lemma exp_log_eq_abs (hx : x ≠ 0) : exp (log x) = |x| :=
by rw [log_of_ne_zero hx, ← coe_exp_order_iso_apply, order_iso.apply_symm_apply, subtype.coe_mk]

lemma exp_log (hx : 0 < x) : exp (log x) = x :=
by { rw exp_log_eq_abs hx.ne', exact abs_of_pos hx }

lemma exp_log_of_neg (hx : x < 0) : exp (log x) = -x :=
by { rw exp_log_eq_abs (ne_of_lt hx), exact abs_of_neg hx }

@[simp] lemma log_exp (x : ℝ) : log (exp x) = x :=
exp_injective $ exp_log (exp_pos x)

lemma surj_on_log : surj_on log (Ioi 0) univ :=
λ x _, ⟨exp x, exp_pos x, log_exp x⟩

lemma log_surjective : surjective log :=
λ x, ⟨exp x, log_exp x⟩

@[simp] lemma range_log : range log = univ :=
log_surjective.range_eq

@[simp] lemma log_zero : log 0 = 0 := dif_pos rfl

@[simp] lemma log_one : log 1 = 0 :=
exp_injective $ by rw [exp_log zero_lt_one, exp_zero]

@[simp] lemma log_abs (x : ℝ) : log (|x|) = log x :=
begin
  by_cases h : x = 0,
  { simp [h] },
  { rw [← exp_eq_exp, exp_log_eq_abs h, exp_log_eq_abs (abs_pos.2 h).ne', abs_abs] }
end

@[simp] lemma log_neg_eq_log (x : ℝ) : log (-x) = log x :=
by rw [← log_abs x, ← log_abs (-x), abs_neg]

lemma surj_on_log' : surj_on log (Iio 0) univ :=
λ x _, ⟨-exp x, neg_lt_zero.2 $ exp_pos x, by rw [log_neg_eq_log, log_exp]⟩

lemma log_mul (hx : x ≠ 0) (hy : y ≠ 0) : log (x * y) = log x + log y :=
exp_injective $
by rw [exp_log_eq_abs (mul_ne_zero hx hy), exp_add, exp_log_eq_abs hx, exp_log_eq_abs hy, abs_mul]

lemma log_div (hx : x ≠ 0) (hy : y ≠ 0) : log (x / y) = log x - log y :=
exp_injective $
by rw [exp_log_eq_abs (div_ne_zero hx hy), exp_sub, exp_log_eq_abs hx, exp_log_eq_abs hy, abs_div]

@[simp] lemma log_inv (x : ℝ) : log (x⁻¹) = -log x :=
begin
  by_cases hx : x = 0, { simp [hx] },
  rw [← exp_eq_exp, exp_log_eq_abs (inv_ne_zero hx), exp_neg, exp_log_eq_abs hx, abs_inv]
end

lemma log_le_log (h : 0 < x) (h₁ : 0 < y) : real.log x ≤ real.log y ↔ x ≤ y :=
by rw [← exp_le_exp, exp_log h, exp_log h₁]

lemma log_lt_log (hx : 0 < x) : x < y → log x < log y :=
by { intro h, rwa [← exp_lt_exp, exp_log hx, exp_log (lt_trans hx h)] }

lemma log_lt_log_iff (hx : 0 < x) (hy : 0 < y) : log x < log y ↔ x < y :=
by { rw [← exp_lt_exp, exp_log hx, exp_log hy] }

lemma log_le_iff_le_exp (hx : 0 < x) : log x ≤ y ↔ x ≤ exp y := by rw [←exp_le_exp, exp_log hx]

lemma log_lt_iff_lt_exp (hx : 0 < x) : log x < y ↔ x < exp y := by rw [←exp_lt_exp, exp_log hx]

lemma le_log_iff_exp_le (hy : 0 < y) : x ≤ log y ↔ exp x ≤ y := by rw [←exp_le_exp, exp_log hy]

lemma lt_log_iff_exp_lt (hy : 0 < y) : x < log y ↔ exp x < y := by rw [←exp_lt_exp, exp_log hy]

lemma log_pos_iff (hx : 0 < x) : 0 < log x ↔ 1 < x :=
by { rw ← log_one, exact log_lt_log_iff zero_lt_one hx }

lemma log_pos (hx : 1 < x) : 0 < log x :=
(log_pos_iff (lt_trans zero_lt_one hx)).2 hx

lemma log_neg_iff (h : 0 < x) : log x < 0 ↔ x < 1 :=
by { rw ← log_one, exact log_lt_log_iff h zero_lt_one }

lemma log_neg (h0 : 0 < x) (h1 : x < 1) : log x < 0 := (log_neg_iff h0).2 h1

lemma log_nonneg_iff (hx : 0 < x) : 0 ≤ log x ↔ 1 ≤ x :=
by rw [← not_lt, log_neg_iff hx, not_lt]

lemma log_nonneg (hx : 1 ≤ x) : 0 ≤ log x :=
(log_nonneg_iff (zero_lt_one.trans_le hx)).2 hx

lemma log_nonpos_iff (hx : 0 < x) : log x ≤ 0 ↔ x ≤ 1 :=
by rw [← not_lt, log_pos_iff hx, not_lt]

lemma log_nonpos_iff' (hx : 0 ≤ x) : log x ≤ 0 ↔ x ≤ 1 :=
begin
  rcases hx.eq_or_lt with (rfl|hx),
  { simp [le_refl, zero_le_one] },
  exact log_nonpos_iff hx
end

lemma log_nonpos (hx : 0 ≤ x) (h'x : x ≤ 1) : log x ≤ 0 :=
(log_nonpos_iff' hx).2 h'x

lemma strict_mono_on_log : strict_mono_on log (set.Ioi 0) :=
λ x hx y hy hxy, log_lt_log hx hxy

lemma strict_anti_on_log : strict_anti_on log (set.Iio 0) :=
begin
  rintros x (hx : x < 0) y (hy : y < 0) hxy,
  rw [← log_abs y, ← log_abs x],
  refine log_lt_log (abs_pos.2 hy.ne) _,
  rwa [abs_of_neg hy, abs_of_neg hx, neg_lt_neg_iff]
end

lemma log_inj_on_pos : set.inj_on log (set.Ioi 0) :=
strict_mono_on_log.inj_on

lemma eq_one_of_pos_of_log_eq_zero {x : ℝ} (h₁ : 0 < x) (h₂ : log x = 0) : x = 1 :=
log_inj_on_pos (set.mem_Ioi.2 h₁) (set.mem_Ioi.2 zero_lt_one) (h₂.trans real.log_one.symm)

lemma log_ne_zero_of_pos_of_ne_one {x : ℝ} (hx_pos : 0 < x) (hx : x ≠ 1) : log x ≠ 0 :=
mt (eq_one_of_pos_of_log_eq_zero hx_pos) hx

@[simp] lemma log_eq_zero {x : ℝ} : log x = 0 ↔ x = 0 ∨ x = 1 ∨ x = -1 :=
begin
  split,
  { intros h,
    rcases lt_trichotomy x 0 with x_lt_zero | rfl | x_gt_zero,
    { refine or.inr (or.inr (eq_neg_iff_eq_neg.mp _)),
      rw [←log_neg_eq_log x] at h,
      exact (eq_one_of_pos_of_log_eq_zero (neg_pos.mpr x_lt_zero) h).symm, },
    { exact or.inl rfl },
    { exact or.inr (or.inl (eq_one_of_pos_of_log_eq_zero x_gt_zero h)), }, },
  { rintro (rfl|rfl|rfl); simp only [log_one, log_zero, log_neg_eq_log], }
end

/-- The real logarithm function tends to `+∞` at `+∞`. -/
lemma tendsto_log_at_top : tendsto log at_top at_top :=
tendsto_comp_exp_at_top.1 $ by simpa only [log_exp] using tendsto_id

lemma tendsto_log_nhds_within_zero : tendsto log (𝓝[{0}ᶜ] 0) at_bot :=
begin
  rw [← (show _ = log, from funext log_abs)],
  refine tendsto.comp _ tendsto_abs_nhds_within_zero,
  simpa [← tendsto_comp_exp_at_bot] using tendsto_id
end

lemma continuous_on_log : continuous_on log {0}ᶜ :=
begin
  rw [continuous_on_iff_continuous_restrict, restrict],
  conv in (log _) { rw [log_of_ne_zero (show (x : ℝ) ≠ 0, from x.2)] },
  exact exp_order_iso.symm.continuous.comp (continuous_subtype_mk _ continuous_subtype_coe.norm)
end

@[continuity] lemma continuous_log : continuous (λ x : {x : ℝ // x ≠ 0}, log x) :=
continuous_on_iff_continuous_restrict.1 $ continuous_on_log.mono $ λ x hx, hx

@[continuity] lemma continuous_log' : continuous (λ x : {x : ℝ // 0 < x}, log x) :=
continuous_on_iff_continuous_restrict.1 $ continuous_on_log.mono $ λ x hx, ne_of_gt hx

lemma continuous_at_log (hx : x ≠ 0) : continuous_at log x :=
(continuous_on_log x hx).continuous_at $ is_open.mem_nhds is_open_compl_singleton hx

@[simp] lemma continuous_at_log_iff : continuous_at log x ↔ x ≠ 0 :=
begin
  refine ⟨_, continuous_at_log⟩,
  rintros h rfl,
  exact not_tendsto_nhds_of_tendsto_at_bot tendsto_log_nhds_within_zero _
    (h.tendsto.mono_left inf_le_left)
end

end real

section continuity

open real
variables {α : Type*}

lemma filter.tendsto.log {f : α → ℝ} {l : filter α} {x : ℝ} (h : tendsto f l (𝓝 x)) (hx : x ≠ 0) :
  tendsto (λ x, log (f x)) l (𝓝 (log x)) :=
(continuous_at_log hx).tendsto.comp h

variables [topological_space α] {f : α → ℝ} {s : set α} {a : α}

lemma continuous.log (hf : continuous f) (h₀ : ∀ x, f x ≠ 0) : continuous (λ x, log (f x)) :=
continuous_on_log.comp_continuous hf h₀

lemma continuous_at.log (hf : continuous_at f a) (h₀ : f a ≠ 0) :
  continuous_at (λ x, log (f x)) a :=
hf.log h₀

lemma continuous_within_at.log (hf : continuous_within_at f s a) (h₀ : f a ≠ 0) :
  continuous_within_at (λ x, log (f x)) s a :=
hf.log h₀

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

end continuity


-- Suma_por_diferencia.lean
-- Suma por diferencia.
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 26-agosto-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que si a y b son números reales, entonces
--    (a + b) * (a - b) = a^2 - b^2
-- ---------------------------------------------------------------------

import data.real.basic

variables a b c d : ℝ

-- 1ª demostración
example : (a + b) * (a - b) = a^2 - b^2 :=
calc
  (a + b) * (a - b)
      = a * (a - b) + b * (a - b)         : by rw add_mul
  ... = (a * a - a * b) + b * (a - b)     : by rw mul_sub
  ... = (a^2 - a * b) + b * (a - b)       : by rw ← pow_two
  ... = (a^2 - a * b) + (b * a - b * b)   : by rw mul_sub
  ... = (a^2 - a * b) + (b * a - b^2)     : by rw ← pow_two
  ... = (a^2 + -(a * b)) + (b * a - b^2)  : by ring
  ... = a^2 + (-(a * b) + (b * a - b^2))  : by rw add_assoc
  ... = a^2 + (-(a * b) + (b * a + -b^2)) : by ring
  ... = a^2 + ((-(a * b) + b * a) + -b^2) : by rw ← add_assoc
                                               (-(a * b)) (b * a) (-b^2)
  ... = a^2 + ((-(a * b) + a * b) + -b^2) : by rw mul_comm
  ... = a^2 + (0 + -b^2)                  : by rw neg_add_self (a * b)
  ... = (a^2 + 0) + -b^2                  : by rw ← add_assoc
  ... = a^2 + -b^2                        : by rw add_zero
  ... = a^2 - b^2                         : by linarith

-- 2ª demostración
example : (a + b) * (a - b) = a^2 - b^2 :=
calc
  (a + b) * (a - b)
      = a * (a - b) + b * (a - b)         : by ring
  ... = (a * a - a * b) + b * (a - b)     : by ring
  ... = (a^2 - a * b) + b * (a - b)       : by ring
  ... = (a^2 - a * b) + (b * a - b * b)   : by ring
  ... = (a^2 - a * b) + (b * a - b^2)     : by ring
  ... = (a^2 + -(a * b)) + (b * a - b^2)  : by ring
  ... = a^2 + (-(a * b) + (b * a - b^2))  : by ring
  ... = a^2 + (-(a * b) + (b * a + -b^2)) : by ring
  ... = a^2 + ((-(a * b) + b * a) + -b^2) : by ring
  ... = a^2 + ((-(a * b) + a * b) + -b^2) : by ring
  ... = a^2 + (0 + -b^2)                  : by ring
  ... = (a^2 + 0) + -b^2                  : by ring
  ... = a^2 + -b^2                        : by ring
  ... = a^2 - b^2                         : by ring

-- 3ª demostración
example : (a + b) * (a - b) = a^2 - b^2 :=
by ring


/-
This file is intended for Lean beginners. The goal is to demonstrate what it feels like to prove
things using Lean and mathlib. Complicated definitions and theory building are not covered.
-/

-- We want real numbers and their basic properties
import data.real.basic

-- We want to be able to define functions using the law of excluded middle
noncomputable theory
open_locale classical

/-
Our first goal is to define the set of upper bounds of a set of real numbers.
This is already defined in mathlib (in a more general context), but we repeat
it for the sake of exposition. Right-click "upper_bounds" below to get offered
to jump to mathlib's version
-/
#check upper_bounds

/-- The set of upper bounds of a set of real numbers ℝ -/
def up_bounds (A : set ℝ) := { x : ℝ | ∀ a ∈ A, a ≤ x}

/-- Predicate `is_max a A` means `a` is a maximum of `A` -/
def is_max (a : ℝ) (A : set ℝ) := a ∈ A ∧ a ∈ up_bounds A

/-
In the above definition, the symbol `∧` means "and". We also see the most
visible difference between set theoretic foundations and type theoretic ones
(used by almost all proof assistants). In set theory, everything is a set, and the
only relation you get from foundations are `=` and `∈`. In type theory, there is
a meta-theoretic relation of "typing": `a : ℝ` reads "`a` is a real number" or,
more precisely, "the type of `a` is `ℝ`". Here "meta-theoretic" means this is not a
statement you can prove or disprove inside the theory, it's a fact that is true or
not. Here we impose this fact, in other circumstances, it would be checked by the
Lean kernel.
By contrast, `a ∈ A` is a statement inside the theory. Here it's part of the
definition, in other circumstances it could be something proven inside Lean.
-/

/- For illustrative purposes, we now define an infix version of the above predicate.
It will allow us to write `a is_a_max_of A`, which is closer to a sentence.
-/
infix `is_a_max_of`:55 := is_max

/-
Let's prove something now! A set of real number has at most one maximum. Here
everything left of the final `:` is introducing the objects and assumption. The equality
`x = y` right of the colon is the conclusion.
-/
lemma unique_max (A : set ℝ) (x y : ℝ) (hx : x is_a_max_of A) (hy : y is_a_max_of A) : x = y :=
begin
  -- We first break our assumptions in their two constituent pieces.
  -- We are free to choose the name following `with`
  cases hx with x_in x_up,
  cases hy with y_in y_up,
  -- Assumption `x_up` means x isn't less than elements of A, let's apply this to y
  specialize x_up y,
  -- Assumption `x_up` now needs the information that `y` is indeed in `A`.
  specialize x_up y_in,
  -- Let's do this quicker with roles swapped
  specialize y_up x x_in,
  -- We explained to Lean the idea of this proof.
  -- Now we know `x ≤ y` and `y ≤ x`, and Lean shouldn't need more help.
  -- `linarith` proves equalities and inequalities that follow linearly from
  -- assumption we have.
  linarith,
end

/-
The above proof is too long, even if you remove comments. We don't really need the
unpacking steps at the beginning, we can access both parts of the assumption
`hx : x is_a_max_of A` using shortcuts `h.1` and `h.2`. We can also improve
readability without assistance from the tactic state display, clearly announcing
intermediate goals using `have`. This way we get to the following version of the
same proof.
-/

example (A : set ℝ) (x y : ℝ) (hx : x is_a_max_of A) (hy : y is_a_max_of A) : x = y :=
begin
  have : x ≤ y, from hy.2 x hx.1,
  have : y ≤ x, from hx.2 y hy.1,
  linarith,
end

/-
Notice how mathematics based on type theory treats the assumption
`∀ a ∈ A, a ≤ y` as a function turning an element `a` of `A` into the statement
`a ≤ y`. More precisely, this assumption is the abbreviation of
`∀ a : ℝ, a ∈ A → a ≤ y`. The expression `hy.2 x` appearing in the above proof
is then the statement `x ∈ A → x ≤ y`, which itself is a function turning a
statement `x ∈ A` into `x ≤ y` so that the full expression `hy.2 x hx.1` is
indeed a proof of `x ≤ y`.
One could argue a three line long proof of this lemma is still two lines too long.
This is debatable, but mathlib's style is to write very short proofs for trivial
lemmas. Those proofs are not easy to read but they are meant to indicate that the
proof is probably not worth reading.
In order to reach this stage, we need to know what linarith did for us. It invoked
the lemma `le_antisymm` which says: `x ≤ y → y ≤ x → x = y`. This arrow, which
is used both for function and implication, is right associative. So the statement is
`x ≤ y → (y ≤ x → x = y)` which reads: I will send a proof `p` of `x ≤ y` to a function
sending a proof `p'` of `y ≤ x` to a proof of `x = y`. Hence `le_antisymm p p'` is a
proof of `x = y`.
Using this we can get our one-line proof:
-/

example (A : set ℝ) (x y : ℝ) (hx : x is_a_max_of A) (hy : y is_a_max_of A) : x = y :=
le_antisymm (hy.2 x hx.1) (hx.2 y hy.1)

/-
Such a proof is called a proof term (or a "term mode" proof). Notice it has no `begin`
and `end`. It is directly the kind of low level proof that the Lean kernel is
consuming. Commands like `cases`, `specialize` or `linarith` are called tactics, they
help users constructing proof terms that could be very tedious to write directly.
The most efficient proof style combines tactics with proof terms like our previous
`have : x ≤ y, from hy.2 x hx.1` where `hy.2 x hx.1` is a proof term embeded inside
a tactic mode proof.
In the remaining of this file, we'll be characterizing infima of sets of real numbers
in term of sequences.
-/

/-- The set of lower bounds of a set of real numbers ℝ -/
def low_bounds (A : set ℝ) := { x : ℝ | ∀ a ∈ A, x ≤ a}

/-
We now define `a` is an infimum of `A`. Again there is already a more general version
in mathlib.
-/
def is_inf (x : ℝ) (A : set ℝ) := x is_a_max_of (low_bounds A)
infix `is_an_inf_of`:55 := is_inf

/-
We need to prove that any number which is greater than the infimum of A is greater
than some element of A.
-/

lemma inf_lt {A : set ℝ} {x : ℝ} (hx : x is_an_inf_of A) :
  ∀ y, x < y → ∃ a ∈ A, a < y :=
begin
  -- Let `y` be any real number.
  intro y,
  -- Let's prove the contrapositive
  contrapose,
  -- The symbol `¬` means negation. Let's ask Lean to rewrite the goal without negation,
  -- pushing negation through quantifiers and inequalities
  push_neg,
  -- Let's assume the premise, calling the assumption `h`
  intro h,
  -- `h` is exactly saying `y` is a lower bound of `A` so the second part of
  -- the infimum assumption `hx` applied to `y` and `h` is exactly what we want.
  exact hx.2 y h
end

/-
In the above proof, the sequence `contrapose, push_neg` is so common it can be
abbreviated to `contrapose!`. With these commands, we enter the gray zone between
proof checking and proof finding. Practical computer proof checking crucially needs
the computer to handle tedious proof steps. In the next proof, we'll start using
`linarith` a bit more seriously, going one step further into automation.
Our next real goal is to prove inequalities for limits of sequences. We extract the
following lemma: if `y ≤ x + ε` for all positive `ε` then `y ≤ x`.
-/

lemma le_of_le_add_eps {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  -- Let's prove the contrapositive, asking Lean to push negations right away.
  contrapose!,
  -- Assume `h : x < y`.
  intro h,
  -- We need to find `ε` such that `ε` is positive and `x + ε < y`.
  -- Let's use `(y-x)/2`
  use ((y-x)/2),
  -- we now have two properties to prove. Let's do both in turn, using `linarith`
  split,
  linarith,
  linarith,
end

/-
Note how `linarith` was used for both sub-goals at the end of the above proof.
We could have shortened that using the semi-colon combinator instead of comma,
writing `split ; linarith`.
Next we will study a compressed version of that proof:
-/

example {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  contrapose!,
  exact assume h, ⟨(y-x)/2, by linarith, by linarith⟩,
end

/-
The angle brackets `⟨` and `⟩` introduce compound data or proofs. A proof
of a `∃ z, P z` statemement is composed of a witness `z₀` and a proof `z` of
`P z₀`. The compound is denoted by `⟨z₀, h⟩`. In the example above, the predicate is
itself compound, it is a conjunction `P z ∧ Q z`. So the proof term should read
`⟨z₀, ⟨h₁, h₂⟩⟩` where `h₁` (resp. `h₂`) is a proof of `P z₀` (resp. `Q z₀`).
But these so-called "anonymous constructor" brackets are right-associative, so we can
get rid of the nested brackets.
The keyword `by` introduces tactic mode inside term mode, it is a shorter version
of the `begin`/`end` pair, which is more convenient for single tactic blocks.
In this example, `begin` enters tactic mode, `exact` leaves it, `by` re-enters it.
Going all the way to a proof term would make the proof much longer, because we
crucially use automation with `contrapose!` and `linarith`. We can still get a one-line
proof using curly braces to gather several tactic invocation, and the `by` abbreviation
instead of `begin`/`end`:
-/

example {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
by { contrapose!, exact assume h, ⟨(y-x)/2, by linarith, by linarith⟩ }

/-
One could argue that the above proof is a bit too terse, and we are relying too much
on linarith. Let's have more `linarith` calls for smaller steps. For the sake
of (tiny) variation, we will also assume the premise and argue by contradiction
instead of contraposing.
-/

example {x y : ℝ} : (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  intro h,
  -- Assume the conclusion is false, and call this assumption H.
  by_contradiction H,
  push_neg at H,
  -- Now let's compute.
  have key := calc
  -- Each line must end with a colon followed by a proof term
  -- We want to specialize our assumption `h` to `ε = (y-x)/2` but this is long to
  -- type, so let's put a hole `_` that Lean will fill in by comparing the
  -- statement we want to prove and our proof term with a hole. As usual,
  -- positivity of `(y-x)/2` is proved by `linarith`
    y   ≤ x + (y-x)/2 : h _ (by linarith)
    ... = x/2 + y/2   : by linarith
    ... < y           : by linarith,
  -- our key now says `y < y` (notice how the sequence `≤`, `=`, `<` was correctly
  -- merged into a `<`). Let `linarith` find the desired contradiction now.
  linarith,
  -- alternatively, we could have provided the proof term
  -- `exact lt_irrefl y key`
end

/-
Now we are ready for some analysis. Let's setup notation for absolute value
-/

local notation `|`x`|` := abs x

/-
And let's define convergence of sequences of real numbers (of course there is
a much more general definition in mathlib).
-/

/-- The sequence `u` tends to `l` -/
def limit (u : ℕ → ℝ) (l : ℝ) := ∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

/-
In the above definition, `u n` denotes the n-th term of the sequence. We can
add parentheses to get `u(n)` but we try to avoid parentheses because they pile up
very quickly
-/

-- If y ≤ u n for all n and u n goes to x then y ≤ x
lemma le_lim {x y : ℝ} {u : ℕ → ℝ} (hu : limit u x) (ineq : ∀ n, y ≤ u n) : y ≤ x :=
begin
  -- Let's apply our previous lemma
  apply le_of_le_add_eps,
  -- We need to prove y ≤ x + ε for all positive ε.
  -- Let ε be any positive real
  intros ε ε_pos,
  -- we now specialize our limit assumption to this `ε`, and immediately
  -- fix a `N` as promised by the definition.
  cases hu ε ε_pos with N HN,
  -- Now we only need to compute until reaching the conclusion
  calc
  y ≤ u N             : ineq N
  ... = x + (u N - x) : by linarith
    -- We'll need `add_le_add` which says `a ≤ b` and `c ≤ d` implies `a + c ≤ b + d`
    -- We need a lemma saying `z ≤ |z|`. Because we don't know the name of this lemma,
    -- let's use `library_search`. Because searching thourgh the library is slow,
    -- Lean will write what it found in the Lean message window when cursor is on
    -- that line, so that we can replace it by the lemma. We see `le_max_left` which
    -- says `a ≤ max a b`. Actually there is a more specific lemma `le_abs_self`
  ... ≤ x + |u N - x| : add_le_add (by linarith) (by library_search)
  ... ≤ x + ε         : add_le_add (by linarith) (HN N (by linarith)),
end

/-
The next lemma has been extracted from the main proof in order to discuss numbers.
In ordinary maths, we know that ℕ is *not* contained in `ℝ`, whatever the
construction of real numbers that we use. For instance a natural number is not
an equivalence class of Cauchy sequences. But it's very easy to
pretend otherwise. Formal maths requires slightly more care. In the statement below,
the "type ascription" `(n + 1 : ℝ)` forces Lean to convert the natural number
`n+1` into a real number.  The "inclusion" map will be displayed in tactic state
as `↑`. There are various lemmas asserting this map is compatible with addition and
monotone, but we don't want to bother writing their names. The `norm_cast`
tactic is designed to wisely apply those lemmas for us.
-/

lemma inv_succ_pos : ∀ n : ℕ, 1/(n+1 : ℝ) > 0 :=
begin
  -- Let `n` be any integer
  intro n,
  -- Since we don't know the name of the relevant lemma, asserting that the inverse of
  -- a positive number is positive, let's state that is suffices
  -- to prove that `n+1`, seen as a real number, is positive, and ask `library_search`
  suffices : (n + 1 : ℝ) > 0,
  { library_search },
  -- Now we want to reduce to a statement about natural numbers, not real numbers
  -- coming from natural numbers.
  norm_cast,
  -- and then get the usual help from `linarith`
  linarith,
end

/-
That was a pretty long proof for an obvious fact. And stating it as a lemma feels
stupid, so let's find a way to write it on one line in case we want to include it
in some other proof without stating a lemma. First the `library_search` call
above displays the name of the relevant lemma: `one_div_pos_of_pos`. We can also
replace the `linarith` call on the last line by `library_search` to learn the name
of the lemma `nat.succ_pos` asserting that the successor of a natural number is
positive. There is also a variant on `norm_cast` that combines it with `exact`.
The term mode analogue of `intro` is `λ`. We get down to:
-/

example : ∀ n : ℕ, 1/(n+1 : ℝ) > 0 :=
λ n, one_div_pos_of_pos (by exact_mod_cast nat.succ_pos n)

/-
The next proof uses mostly known things, so we will commment only new aspects.
-/

lemma limit_inv_succ : ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, 1/(n + 1 : ℝ) ≤ ε :=
begin
  intros ε ε_pos,
  suffices : ∃ N : ℕ, 1/ε ≤ N,
  { -- Because we didn't provide a name for the above statement, Lean called it `this`.
    -- Let's fix an `N` that works.
    cases this with N HN,
    use N,
    intros n Hn,
    -- Now we want to rewrite the goal using lemmas
    -- `div_le_iff' : 0 < b →  (a / b ≤ c ↔ a ≤ b * c)`
    -- `div_le_iff : 0 < b →  (a / b ≤ c ↔ a ≤ c * b)`
    -- the second one will be rewritten from right to left, as indicated by `←`.
    -- Lean will create a side goal for the required positivity assumption that
    -- we don't provide for `div_le_iff'`.
    rw [div_le_iff', ← div_le_iff ε_pos],
    -- We want to replace assumption `Hn` by its real counter-part so that
    -- linarith can find what it needs.
    replace Hn : (N : ℝ) ≤ n, exact_mod_cast Hn,
    linarith,
    -- we are still left with the positivity assumption, but already discussed
    -- how to prove it in the precedining lemma
    exact_mod_cast nat.succ_pos n },
  -- Now we need to prove that sufficient statement.
  -- We want to use that `ℝ` is archimedean. So we start typing
  -- `exact archimedean_` and hit Ctrl-space to see what completion Lean proposes
  -- the lemma `archimedean_iff_nat_le` sounds promising. We select the left to
  -- right implication using `.1`. This a generic lemma for fields equiped with
  -- a linear (ie total) order. We need to provide a proof that `ℝ` is indeed
  -- archimedean. This is done using the `apply_instance` tactic that will be
  -- covered elsewhere.
  exact archimedean_iff_nat_le.1 (by apply_instance) (1/ε),
end

/-
We can now put all pieces together, with almost no new things to explain.
-/

lemma inf_seq (A : set ℝ) (x : ℝ) :
  (x is_an_inf_of A) ↔ (x ∈ low_bounds A ∧ ∃ u : ℕ → ℝ, limit u x ∧ ∀ n, u n ∈ A ) :=
begin
  split,
  { intro h,
    split,
    { exact h.1 },
    -- On the next line, we don't need to tell Lean to treat `n+1` as a real number because
    -- we add `x` to it, so Lean knows there is only one way to make sense of this expression.
    have key : ∀ n : ℕ, ∃ a ∈ A, a < x + 1/(n+1),
    { intro n,
      -- we can use the lemma we proved above
      apply inf_lt h,
      -- and another one we proved!
      have : 0 < 1/(n+1 : ℝ), from inv_succ_pos n,
      linarith },
    -- Now we need to use axiom of (countable) choice
    choose u hu using key,
    use u,
    split,
    { intros ε ε_pos,
      -- again we use a lemma we proved, specializing it to our fixed `ε`, and fixing a `N`
      cases limit_inv_succ ε ε_pos with N H,
      use N,
      intros n hn,
      have : x ≤ u n, from h.1 _ (hu n).1,
      have := calc
        u n < x + 1/(n + 1) : (hu n).2
        ... ≤ x + ε         : add_le_add (le_refl x) (H n hn),
      rw abs_of_nonneg ; linarith },
    { intro n,
      exact (hu n).1 } },
  { intro h,
    -- Assumption `h` is made of nested compound statements. We can use the
    -- recursive version of `cases` to unpack it in one go.
    rcases h with ⟨x_min, u, lim, huA⟩,
    split,
    exact x_min,
    intros y y_mino,
    apply le_lim lim,
    intro n,
    exact y_mino (u n) (huA n) },
end


import hom.quotient data.setoid.partition

open setoid set

namespace mygroup

namespace lagrange

open mygroup.quotient mygroup.subgroup fincard function

variables {G : Type} [group G] {H : subgroup G}

def is_lcoset (H : subgroup G) (B : set G) := ∃ g : G, B = lcoset g H

def lcosets (H : subgroup G) := { B : set G // is_lcoset H B }

def lcoset_setoid (H : subgroup G) : setoid G :=
{ r := lcoset_rel H,
  iseqv := lcoset_iseqv H }

lemma lcoset_setoid_classes :
  (lcoset_setoid H).classes = { B | ∃ g : G, B = lcoset g H } :=
begin
  ext, split; rintro ⟨g, rfl⟩; refine ⟨g, _⟩, apply eq.symm,
  all_goals { show _ = { h | lcoset_rel H h g },
              ext, split; intro hx,
                { exact lcoset_digj (self_mem_coset x H) hx },
                { rw [mem_set_of_eq, lcoset_rel_def] at hx,
                  rw ← hx, exact self_mem_coset _ _ } }
end

/-- The left cosets of a subgroup `H` form a partition -/
def lcoset_partition (H : subgroup G) :
  is_partition { B | ∃ g : G, B = lcoset g H } :=
begin
  rw ← lcoset_setoid_classes,
  exact is_partition_classes (lcoset_setoid H)
end

/-- Let `H` be a subgroup of the finite group `G`, then the cardinality of `G`
equals the cardinality of `H` multiplied with the number of left cosets of `H` -/
theorem lagrange [fintype G] :
  fincard G = fincard H * fincard (lcosets H) :=
begin
  change fincard G =
    fincard H * fincard { B | ∃ g : G, B = lcoset g H },
  rw [card_eq_finsum_partition (lcoset_partition H),
    mul_comm, finsum_const_nat],
  rintros x ⟨g, rfl⟩,
  exact eq_card_of_lcoset.symm
end

def to_lcosets (N : normal G) : G /ₘ N → lcosets (N : subgroup G) :=
λ x, let f : G → lcosets (N : subgroup G) := λ g, ⟨g ⋆ N, ⟨g, rfl⟩⟩ in
  lift_on x f (λ a b h, by simpa [h])

lemma to_lcosets_mk {N : normal G} (g : G) :
  (to_lcosets N (g : G /ₘ N)).val = lcoset g N := rfl

lemma bijective_to_lcosets {N : normal G} : bijective (to_lcosets N) :=
begin
  split,
    { intros x y hxy,
      rcases exists_mk x with ⟨x, rfl⟩,
      rcases exists_mk y with ⟨y, rfl⟩,
      rw [mk_eq, ← to_lcosets_mk, hxy, to_lcosets_mk] },
    { rintro ⟨_, g, rfl⟩, exact ⟨g, subtype.eq (to_lcosets_mk g)⟩ }
end

noncomputable def to_lcosets_equiv (N : normal G) :
  G /ₘ N ≃ { B | ∃ g : G, B = lcoset g N } :=
equiv.of_bijective (to_lcosets N) bijective_to_lcosets

theorem card_quotient_eq_mul [fintype G] (N : normal G) :
  fincard G = fincard N * fincard (G /ₘ N) :=
begin
  rw @lagrange _ _ (N : subgroup G),
  congr' 1, exact of_equiv (to_lcosets_equiv N).symm
end

end lagrange

end mygroup


/-
Copyright (c) 2022 Jireh Loreaux. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jireh Loreaux
-/
import analysis.normed_space.star.basic
import analysis.normed_space.spectrum
import analysis.normed_space.star.exponential
import analysis.special_functions.exponential
import algebra.star.star_alg_hom

/-! # Spectral properties in C⋆-algebras
In this file, we establish various properties related to the spectrum of elements in C⋆-algebras.
-/

local postfix `⋆`:std.prec.max_plus := star

section

open_locale topology ennreal
open filter ennreal spectrum cstar_ring

section unitary_spectrum

variables
{𝕜 : Type*} [normed_field 𝕜]
{E : Type*} [normed_ring E] [star_ring E] [cstar_ring E]
[normed_algebra 𝕜 E] [complete_space E]

lemma unitary.spectrum_subset_circle (u : unitary E) :
  spectrum 𝕜 (u : E) ⊆ metric.sphere 0 1 :=
begin
  nontriviality E,
  refine λ k hk, mem_sphere_zero_iff_norm.mpr (le_antisymm _ _),
  { simpa only [cstar_ring.norm_coe_unitary u] using norm_le_norm_of_mem hk },
  { rw ←unitary.coe_to_units_apply u at hk,
    have hnk := ne_zero_of_mem_of_unit hk,
    rw [←inv_inv (unitary.to_units u), ←spectrum.map_inv, set.mem_inv] at hk,
    have : ‖k‖⁻¹ ≤ ‖↑((unitary.to_units u)⁻¹)‖, simpa only [norm_inv] using norm_le_norm_of_mem hk,
    simpa using inv_le_of_inv_le (norm_pos_iff.mpr hnk) this }
end

lemma spectrum.subset_circle_of_unitary {u : E} (h : u ∈ unitary E) :
  spectrum 𝕜 u ⊆ metric.sphere 0 1 :=
unitary.spectrum_subset_circle ⟨u, h⟩

end unitary_spectrum

section complex_scalars

open complex

variables {A : Type*}
[normed_ring A] [normed_algebra ℂ A] [complete_space A] [star_ring A] [cstar_ring A]

local notation `↑ₐ` := algebra_map ℂ A

lemma is_self_adjoint.spectral_radius_eq_nnnorm {a : A}
  (ha : is_self_adjoint a) :
  spectral_radius ℂ a = ‖a‖₊ :=
begin
  have hconst : tendsto (λ n : ℕ, (‖a‖₊ : ℝ≥0∞)) at_top _ := tendsto_const_nhds,
  refine tendsto_nhds_unique _ hconst,
  convert (spectrum.pow_nnnorm_pow_one_div_tendsto_nhds_spectral_radius (a : A)).comp
      (nat.tendsto_pow_at_top_at_top_of_one_lt one_lt_two),
  refine funext (λ n, _),
  rw [function.comp_app, ha.nnnorm_pow_two_pow, ennreal.coe_pow, ←rpow_nat_cast,
    ←rpow_mul],
  simp,
end

lemma is_star_normal.spectral_radius_eq_nnnorm (a : A) [is_star_normal a] :
  spectral_radius ℂ a = ‖a‖₊ :=
begin
  refine (ennreal.pow_strict_mono two_ne_zero).injective _,
  have heq : (λ n : ℕ, ((‖(a⋆ * a) ^ n‖₊ ^ (1 / n : ℝ)) : ℝ≥0∞))
    = (λ x, x ^ 2) ∘ (λ n : ℕ, ((‖a ^ n‖₊ ^ (1 / n : ℝ)) : ℝ≥0∞)),
  { funext,
    rw [function.comp_apply, ←rpow_nat_cast, ←rpow_mul, mul_comm, rpow_mul, rpow_nat_cast,
      ←coe_pow, sq, ←nnnorm_star_mul_self, commute.mul_pow (star_comm_self' a), star_pow], },
  have h₂ := ((ennreal.continuous_pow 2).tendsto (spectral_radius ℂ a)).comp
    (spectrum.pow_nnnorm_pow_one_div_tendsto_nhds_spectral_radius a),
  rw ←heq at h₂,
  convert tendsto_nhds_unique h₂ (pow_nnnorm_pow_one_div_tendsto_nhds_spectral_radius (a⋆ * a)),
  rw [(is_self_adjoint.star_mul_self a).spectral_radius_eq_nnnorm, sq, nnnorm_star_mul_self,
    coe_mul],
end

/-- Any element of the spectrum of a selfadjoint is real. -/
theorem is_self_adjoint.mem_spectrum_eq_re [star_module ℂ A] {a : A}
  (ha : is_self_adjoint a) {z : ℂ} (hz : z ∈ spectrum ℂ a) : z = z.re :=
begin
  have hu := exp_mem_unitary_of_mem_skew_adjoint ℂ (ha.smul_mem_skew_adjoint conj_I),
  let Iu := units.mk0 I I_ne_zero,
  have : exp ℂ (I • z) ∈ spectrum ℂ (exp ℂ (I • a)),
    by simpa only [units.smul_def, units.coe_mk0]
      using spectrum.exp_mem_exp (Iu • a) (smul_mem_smul_iff.mpr hz),
  exact complex.ext (of_real_re _)
    (by simpa only [←complex.exp_eq_exp_ℂ, mem_sphere_zero_iff_norm, norm_eq_abs, abs_exp,
      real.exp_eq_one_iff, smul_eq_mul, I_mul, neg_eq_zero]
      using spectrum.subset_circle_of_unitary hu this),
end

/-- Any element of the spectrum of a selfadjoint is real. -/
theorem self_adjoint.mem_spectrum_eq_re [star_module ℂ A]
  (a : self_adjoint A) {z : ℂ} (hz : z ∈ spectrum ℂ (a : A)) : z = z.re :=
a.prop.mem_spectrum_eq_re hz

/-- The spectrum of a selfadjoint is real -/
theorem is_self_adjoint.coe_re_map_spectrum [star_module ℂ A] {a : A}
  (ha : is_self_adjoint a) : spectrum ℂ a = (coe ∘ re '' (spectrum ℂ a) : set ℂ) :=
le_antisymm (λ z hz, ⟨z, hz, (ha.mem_spectrum_eq_re hz).symm⟩) (λ z, by
  { rintros ⟨z, hz, rfl⟩,
    simpa only [(ha.mem_spectrum_eq_re hz).symm, function.comp_app] using hz })

/-- The spectrum of a selfadjoint is real -/
theorem self_adjoint.coe_re_map_spectrum [star_module ℂ A] (a : self_adjoint A) :
  spectrum ℂ (a : A) = (coe ∘ re '' (spectrum ℂ (a : A)) : set ℂ) :=
a.property.coe_re_map_spectrum

end complex_scalars

namespace star_alg_hom

variables {F A B : Type*}
[normed_ring A] [normed_algebra ℂ A] [complete_space A] [star_ring A] [cstar_ring A]
[normed_ring B] [normed_algebra ℂ B] [complete_space B] [star_ring B] [cstar_ring B]
[hF : star_alg_hom_class F ℂ A B] (φ : F)
include hF

/-- A star algebra homomorphism of complex C⋆-algebras is norm contractive. -/
lemma nnnorm_apply_le (a : A) : ‖(φ a : B)‖₊ ≤ ‖a‖₊ :=
begin
  suffices : ∀ s : A, is_self_adjoint s → ‖φ s‖₊ ≤ ‖s‖₊,
  { exact nonneg_le_nonneg_of_sq_le_sq zero_le'
      (by simpa only [nnnorm_star_mul_self, map_star, map_mul]
      using this _ (is_self_adjoint.star_mul_self a)) },
  { intros s hs,
    simpa only [hs.spectral_radius_eq_nnnorm, (hs.star_hom_apply φ).spectral_radius_eq_nnnorm,
      coe_le_coe] using (show spectral_radius ℂ (φ s) ≤ spectral_radius ℂ s,
      from supr_le_supr_of_subset (alg_hom.spectrum_apply_subset φ s)) }
end

/-- A star algebra homomorphism of complex C⋆-algebras is norm contractive. -/
lemma norm_apply_le (a : A) : ‖(φ a : B)‖ ≤ ‖a‖ := nnnorm_apply_le φ a

/-- Star algebra homomorphisms between C⋆-algebras are continuous linear maps.
See note [lower instance priority] -/
@[priority 100]
noncomputable instance : continuous_linear_map_class F ℂ A B :=
{ map_continuous := λ φ, add_monoid_hom_class.continuous_of_bound φ 1
    (by simpa only [one_mul] using nnnorm_apply_le φ),
  .. alg_hom_class.linear_map_class }

end star_alg_hom

end

namespace weak_dual

open continuous_map complex
open_locale complex_star_module

variables {F A : Type*} [normed_ring A] [normed_algebra ℂ A] [complete_space A]
  [star_ring A] [cstar_ring A] [star_module ℂ A] [hF : alg_hom_class F ℂ A ℂ]

include hF

/-- This instance is provided instead of `star_alg_hom_class` to avoid type class inference loops.
See note [lower instance priority] -/
@[priority 100]
noncomputable instance : star_hom_class F A ℂ :=
{ coe := λ φ, φ,
  coe_injective' := fun_like.coe_injective',
  map_star := λ φ a,
  begin
    suffices hsa : ∀ s : self_adjoint A, (φ s)⋆ = φ s,
    { rw ←real_part_add_I_smul_imaginary_part a,
      simp only [map_add, map_smul, star_add, star_smul, hsa, self_adjoint.star_coe_eq] },
    { intros s,
      have := alg_hom.apply_mem_spectrum φ (s : A),
      rw self_adjoint.coe_re_map_spectrum s at this,
      rcases this with ⟨⟨_, _⟩, _, heq⟩,
      rw [←heq, is_R_or_C.star_def, is_R_or_C.conj_of_real] }
  end }

/-- This is not an instance to avoid type class inference loops. See
`weak_dual.complex.star_hom_class`. -/
noncomputable def _root_.alg_hom_class.star_alg_hom_class : star_alg_hom_class F ℂ A ℂ :=
{ coe := λ f, f,
  .. weak_dual.complex.star_hom_class,
  .. hF }

omit hF

namespace character_space

noncomputable instance : star_alg_hom_class (character_space ℂ A) ℂ A ℂ :=
{ coe := λ f, f,
  .. alg_hom_class.star_alg_hom_class }

end character_space

end weak_dual