/-
Copyright (c) 2022 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel

! This file was ported from Lean 3 source module probability.strong_law
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Probability.IdentDistrib
import Mathbin.MeasureTheory.Integral.IntervalIntegral
import Mathbin.Analysis.SpecificLimits.FloorPow
import Mathbin.Analysis.PSeries
import Mathbin.Analysis.Asymptotics.SpecificAsymptotics

/-!
# The strong law of large numbers

We prove the strong law of large numbers, in `probability_theory.strong_law_ae`:
If `X n` is a sequence of independent identically distributed integrable real-valued random
variables, then `∑ i in range n, X i / n` converges almost surely to `𝔼[X 0]`.
We give here the strong version, due to Etemadi, that only requires pairwise independence.

This file also contains the Lᵖ version of the strong law of large numbers provided by
`probability_theory.strong_law_Lp` which shows `∑ i in range n, X i / n` converges in Lᵖ to
`𝔼[X 0]` provided `X n` is independent identically distributed and is Lᵖ.

## Implementation

We follow the proof by Etemadi
[Etemadi, *An elementary proof of the strong law of large numbers*][etemadi_strong_law],
which goes as follows.

It suffices to prove the result for nonnegative `X`, as one can prove the general result by
splitting a general `X` into its positive part and negative part.
Consider `Xₙ` a sequence of nonnegative integrable identically distributed pairwise independent
random variables. Let `Yₙ` be the truncation of `Xₙ` up to `n`. We claim that
* Almost surely, `Xₙ = Yₙ` for all but finitely many indices. Indeed, `∑ ℙ (Xₙ ≠ Yₙ)` is bounded by
  `1 + 𝔼[X]` (see `sum_prob_mem_Ioc_le` and `tsum_prob_mem_Ioi_lt_top`).
* Let `c > 1`. Along the sequence `n = c ^ k`, then `(∑_{i=0}^{n-1} Yᵢ - 𝔼[Yᵢ])/n` converges almost
  surely to `0`. This follows from a variance control, as
```
  ∑_k ℙ (|∑_{i=0}^{c^k - 1} Yᵢ - 𝔼[Yᵢ]| > c^k ε)
    ≤ ∑_k (c^k ε)^{-2} ∑_{i=0}^{c^k - 1} Var[Yᵢ]    (by Markov inequality)
    ≤ ∑_i (C/i^2) Var[Yᵢ]                           (as ∑_{c^k > i} 1/(c^k)^2 ≤ C/i^2)
    ≤ ∑_i (C/i^2) 𝔼[Yᵢ^2]
    ≤ 2C 𝔼[X^2]                                     (see `sum_variance_truncation_le`)
```
* As `𝔼[Yᵢ]` converges to `𝔼[X]`, it follows from the two previous items and Cesaro that, along
  the sequence `n = c^k`, one has `(∑_{i=0}^{n-1} Xᵢ) / n → 𝔼[X]` almost surely.
* To generalize it to all indices, we use the fact that `∑_{i=0}^{n-1} Xᵢ` is nondecreasing and
  that, if `c` is close enough to `1`, the gap between `c^k` and `c^(k+1)` is small.
-/

noncomputable section

open MeasureTheory Filter Finset Asymptotics

open Set (indicator)

open Topology BigOperators MeasureTheory ProbabilityTheory ENNReal NNReal

namespace ProbabilityTheory

/-! ### Prerequisites on truncations -/

section Truncation

variable {α : Type _}

/-- Truncating a real-valued function to the interval `(-A, A]`. -/
def truncation (f : α → ℝ) (A : ℝ) :=
  indicator (Set.Ioc (-A) A) id ∘ f
#align probability_theory.truncation ProbabilityTheory.truncation

variable {m : MeasurableSpace α} {μ : Measure α} {f : α → ℝ}

theorem MeasureTheory.AeStronglyMeasurable.truncation (hf : AeStronglyMeasurable f μ) {A : ℝ} :
    AeStronglyMeasurable (truncation f A) μ :=
  by
  apply ae_strongly_measurable.comp_ae_measurable _ hf.ae_measurable
  exact (strongly_measurable_id.indicator measurableSet_Ioc).AeStronglyMeasurable
#align measure_theory.ae_strongly_measurable.truncation MeasureTheory.AeStronglyMeasurable.truncation

theorem abs_truncation_le_bound (f : α → ℝ) (A : ℝ) (x : α) : |truncation f A x| ≤ |A| :=
  by
  simp only [truncation, Set.indicator, Set.mem_Icc, id.def, Function.comp_apply]
  split_ifs
  · exact abs_le_abs h.2 (neg_le.2 h.1.le)
  · simp [abs_nonneg]
#align probability_theory.abs_truncation_le_bound ProbabilityTheory.abs_truncation_le_bound

@[simp]
theorem truncation_zero (f : α → ℝ) : truncation f 0 = 0 := by simp [truncation]
#align probability_theory.truncation_zero ProbabilityTheory.truncation_zero

theorem abs_truncation_le_abs_self (f : α → ℝ) (A : ℝ) (x : α) : |truncation f A x| ≤ |f x| :=
  by
  simp only [truncation, indicator, Set.mem_Icc, id.def, Function.comp_apply]
  split_ifs
  · exact le_rfl
  · simp [abs_nonneg]
#align probability_theory.abs_truncation_le_abs_self ProbabilityTheory.abs_truncation_le_abs_self

theorem truncation_eq_self {f : α → ℝ} {A : ℝ} {x : α} (h : |f x| < A) : truncation f A x = f x :=
  by
  simp only [truncation, indicator, Set.mem_Icc, id.def, Function.comp_apply, ite_eq_left_iff]
  intro H
  apply H.elim
  simp [(abs_lt.1 h).1, (abs_lt.1 h).2.le]
#align probability_theory.truncation_eq_self ProbabilityTheory.truncation_eq_self

theorem truncation_eq_of_nonneg {f : α → ℝ} {A : ℝ} (h : ∀ x, 0 ≤ f x) :
    truncation f A = indicator (Set.Ioc 0 A) id ∘ f :=
  by
  ext x
  rcases(h x).lt_or_eq with (hx | hx)
  · simp only [truncation, indicator, hx, Set.mem_Ioc, id.def, Function.comp_apply, true_and_iff]
    by_cases h'x : f x ≤ A
    · have : -A < f x := by linarith [h x]
      simp only [this, true_and_iff]
    · simp only [h'x, and_false_iff]
  · simp only [truncation, indicator, hx, id.def, Function.comp_apply, if_t_t]
#align probability_theory.truncation_eq_of_nonneg ProbabilityTheory.truncation_eq_of_nonneg

theorem truncation_nonneg {f : α → ℝ} (A : ℝ) {x : α} (h : 0 ≤ f x) : 0 ≤ truncation f A x :=
  Set.indicator_apply_nonneg fun _ => h
#align probability_theory.truncation_nonneg ProbabilityTheory.truncation_nonneg

theorem MeasureTheory.AeStronglyMeasurable.memℒpTruncation [IsFiniteMeasure μ]
    (hf : AeStronglyMeasurable f μ) {A : ℝ} {p : ℝ≥0∞} : Memℒp (truncation f A) p μ :=
  Memℒp.ofBound hf.truncation (|A|) (eventually_of_forall fun x => abs_truncation_le_bound _ _ _)
#align measure_theory.ae_strongly_measurable.mem_ℒp_truncation MeasureTheory.AeStronglyMeasurable.memℒpTruncation

theorem MeasureTheory.AeStronglyMeasurable.integrableTruncation [IsFiniteMeasure μ]
    (hf : AeStronglyMeasurable f μ) {A : ℝ} : Integrable (truncation f A) μ :=
  by
  rw [← mem_ℒp_one_iff_integrable]
  exact hf.mem_ℒp_truncation
#align measure_theory.ae_strongly_measurable.integrable_truncation MeasureTheory.AeStronglyMeasurable.integrableTruncation

theorem moment_truncation_eq_intervalIntegral (hf : AeStronglyMeasurable f μ) {A : ℝ} (hA : 0 ≤ A)
    {n : ℕ} (hn : n ≠ 0) : (∫ x, truncation f A x ^ n ∂μ) = ∫ y in -A..A, y ^ n ∂Measure.map f μ :=
  by
  have M : MeasurableSet (Set.Ioc (-A) A) := measurableSet_Ioc
  change (∫ x, (fun z => indicator (Set.Ioc (-A) A) id z ^ n) (f x) ∂μ) = _
  rw [← integral_map hf.ae_measurable, intervalIntegral.integral_of_le, ← integral_indicator M]
  · simp only [indicator, zero_pow' _ hn, id.def, ite_pow]
  · linarith
  · exact ((measurable_id.indicator M).pow_const n).AeStronglyMeasurable
#align probability_theory.moment_truncation_eq_interval_integral ProbabilityTheory.moment_truncation_eq_intervalIntegral

theorem moment_truncation_eq_intervalIntegral_of_nonneg (hf : AeStronglyMeasurable f μ) {A : ℝ}
    {n : ℕ} (hn : n ≠ 0) (h'f : 0 ≤ f) :
    (∫ x, truncation f A x ^ n ∂μ) = ∫ y in 0 ..A, y ^ n ∂Measure.map f μ :=
  by
  have M : MeasurableSet (Set.Ioc 0 A) := measurableSet_Ioc
  have M' : MeasurableSet (Set.Ioc A 0) := measurableSet_Ioc
  rw [truncation_eq_of_nonneg h'f]
  change (∫ x, (fun z => indicator (Set.Ioc 0 A) id z ^ n) (f x) ∂μ) = _
  rcases le_or_lt 0 A with (hA | hA)
  · rw [← integral_map hf.ae_measurable, intervalIntegral.integral_of_le hA, ← integral_indicator M]
    · simp only [indicator, zero_pow' _ hn, id.def, ite_pow]
    · exact ((measurable_id.indicator M).pow_const n).AeStronglyMeasurable
  · rw [← integral_map hf.ae_measurable, intervalIntegral.integral_of_ge hA.le, ←
      integral_indicator M']
    · simp only [Set.Ioc_eq_empty_of_le hA.le, zero_pow' _ hn, Set.indicator_empty, integral_zero,
        zero_eq_neg]
      apply integral_eq_zero_of_ae
      have : ∀ᵐ x ∂measure.map f μ, (0 : ℝ) ≤ x :=
        (ae_map_iff hf.ae_measurable measurableSet_Ici).2 (eventually_of_forall h'f)
      filter_upwards [this]with x hx
      simp only [indicator, Set.mem_Ioc, Pi.zero_apply, ite_eq_right_iff, and_imp]
      intro h'x h''x
      have : x = 0 := by linarith
      simp [this, zero_pow' _ hn]
    · exact ((measurable_id.indicator M).pow_const n).AeStronglyMeasurable
#align probability_theory.moment_truncation_eq_interval_integral_of_nonneg ProbabilityTheory.moment_truncation_eq_intervalIntegral_of_nonneg

theorem integral_truncation_eq_intervalIntegral (hf : AeStronglyMeasurable f μ) {A : ℝ}
    (hA : 0 ≤ A) : (∫ x, truncation f A x ∂μ) = ∫ y in -A..A, y ∂Measure.map f μ := by
  simpa using moment_truncation_eq_interval_integral hf hA one_ne_zero
#align probability_theory.integral_truncation_eq_interval_integral ProbabilityTheory.integral_truncation_eq_intervalIntegral

theorem integral_truncation_eq_intervalIntegral_of_nonneg (hf : AeStronglyMeasurable f μ) {A : ℝ}
    (h'f : 0 ≤ f) : (∫ x, truncation f A x ∂μ) = ∫ y in 0 ..A, y ∂Measure.map f μ := by
  simpa using moment_truncation_eq_interval_integral_of_nonneg hf one_ne_zero h'f
#align probability_theory.integral_truncation_eq_interval_integral_of_nonneg ProbabilityTheory.integral_truncation_eq_intervalIntegral_of_nonneg

theorem integral_truncation_le_integral_of_nonneg (hf : Integrable f μ) (h'f : 0 ≤ f) {A : ℝ} :
    (∫ x, truncation f A x ∂μ) ≤ ∫ x, f x ∂μ :=
  by
  apply
    integral_mono_of_nonneg (eventually_of_forall fun x => _) hf (eventually_of_forall fun x => _)
  · exact truncation_nonneg _ (h'f x)
  ·
    calc
      truncation f A x ≤ |truncation f A x| := le_abs_self _
      _ ≤ |f x| := (abs_truncation_le_abs_self _ _ _)
      _ = f x := abs_of_nonneg (h'f x)

#align probability_theory.integral_truncation_le_integral_of_nonneg ProbabilityTheory.integral_truncation_le_integral_of_nonneg

/-- If a function is integrable, then the integral of its truncated versions converges to the
integral of the whole function. -/
theorem tendsto_integral_truncation {f : α → ℝ} (hf : Integrable f μ) :
    Tendsto (fun A => ∫ x, truncation f A x ∂μ) atTop (𝓝 (∫ x, f x ∂μ)) :=
  by
  refine' tendsto_integral_filter_of_dominated_convergence (fun x => abs (f x)) _ _ _ _
  · exact eventually_of_forall fun A => hf.ae_strongly_measurable.truncation
  · apply eventually_of_forall fun A => _
    apply eventually_of_forall fun x => _
    rw [Real.norm_eq_abs]
    exact abs_truncation_le_abs_self _ _ _
  · apply hf.abs
  · apply eventually_of_forall fun x => _
    apply tendsto_const_nhds.congr' _
    filter_upwards [Ioi_mem_at_top (abs (f x))]with A hA
    exact (truncation_eq_self hA).symm
#align probability_theory.tendsto_integral_truncation ProbabilityTheory.tendsto_integral_truncation

theorem IdentDistrib.truncation {β : Type _} [MeasurableSpace β] {ν : Measure β} {f : α → ℝ}
    {g : β → ℝ} (h : IdentDistrib f g μ ν) {A : ℝ} :
    IdentDistrib (truncation f A) (truncation g A) μ ν :=
  h.comp (measurable_id.indicator measurableSet_Ioc)
#align probability_theory.ident_distrib.truncation ProbabilityTheory.IdentDistrib.truncation

end Truncation

section StrongLawAe

variable {Ω : Type _} [MeasureSpace Ω] [IsProbabilityMeasure (ℙ : Measure Ω)]

section MomentEstimates

theorem sum_prob_mem_Ioc_le {X : Ω → ℝ} (hint : Integrable X) (hnonneg : 0 ≤ X) {K : ℕ} {N : ℕ}
    (hKN : K ≤ N) :
    (∑ j in range K, ℙ { ω | X ω ∈ Set.Ioc (j : ℝ) N }) ≤ ENNReal.ofReal (𝔼[X] + 1) :=
  by
  let ρ : Measure ℝ := measure.map X ℙ
  haveI : is_probability_measure ρ := is_probability_measure_map hint.ae_measurable
  have A : (∑ j in range K, ∫ x in j..N, (1 : ℝ) ∂ρ) ≤ 𝔼[X] + 1 :=
    calc
      (∑ j in range K, ∫ x in j..N, (1 : ℝ) ∂ρ) =
          ∑ j in range K, ∑ i in Ico j N, ∫ x in i..(i + 1 : ℕ), (1 : ℝ) ∂ρ :=
        by
        apply sum_congr rfl fun j hj => _
        rw [intervalIntegral.sum_integral_adjacent_intervals_Ico ((mem_range.1 hj).le.trans hKN)]
        intro k hk
        exact continuous_const.interval_integrable _ _
      _ = ∑ i in range N, ∑ j in range (min (i + 1) K), ∫ x in i..(i + 1 : ℕ), (1 : ℝ) ∂ρ :=
        by
        simp_rw [sum_sigma']
        refine'
          sum_bij' (fun (p : Σi : ℕ, ℕ) hp => (⟨p.2, p.1⟩ : Σi : ℕ, ℕ)) _ (fun a ha => rfl)
            (fun (p : Σi : ℕ, ℕ) hp => (⟨p.2, p.1⟩ : Σi : ℕ, ℕ)) _ _ _
        · rintro ⟨i, j⟩ hij
          simp only [mem_sigma, mem_range, mem_Ico] at hij
          simp only [hij, Nat.lt_succ_iff.2 hij.2.1, mem_sigma, mem_range, lt_min_iff, and_self_iff]
        · rintro ⟨i, j⟩ hij
          simp only [mem_sigma, mem_range, lt_min_iff] at hij
          simp only [hij, Nat.lt_succ_iff.1 hij.2.1, mem_sigma, mem_range, mem_Ico, and_self_iff]
        · rintro ⟨i, j⟩ hij
          rfl
        · rintro ⟨i, j⟩ hij
          rfl
      _ ≤ ∑ i in range N, (i + 1) * ∫ x in i..(i + 1 : ℕ), (1 : ℝ) ∂ρ :=
        by
        apply sum_le_sum fun i hi => _
        simp only [Nat.cast_add, Nat.cast_one, sum_const, card_range, nsmul_eq_mul, Nat.cast_min]
        refine' mul_le_mul_of_nonneg_right (min_le_left _ _) _
        apply intervalIntegral.integral_nonneg
        · simp only [le_add_iff_nonneg_right, zero_le_one]
        · simp only [zero_le_one, imp_true_iff]
      _ ≤ ∑ i in range N, ∫ x in i..(i + 1 : ℕ), x + 1 ∂ρ :=
        by
        apply sum_le_sum fun i hi => _
        have I : (i : ℝ) ≤ (i + 1 : ℕ) := by
          simp only [Nat.cast_add, Nat.cast_one, le_add_iff_nonneg_right, zero_le_one]
        simp_rw [intervalIntegral.integral_of_le I, ← integral_mul_left]
        apply set_integral_mono_on
        · exact continuous_const.integrable_on_Ioc
        · exact (continuous_id.add continuous_const).integrableOnIoc
        · exact measurableSet_Ioc
        · intro x hx
          simp only [Nat.cast_add, Nat.cast_one, Set.mem_Ioc] at hx
          simp [hx.1.le]
      _ = ∫ x in 0 ..N, x + 1 ∂ρ :=
        by
        rw [intervalIntegral.sum_integral_adjacent_intervals fun k hk => _]
        · norm_cast
        · exact (continuous_id.add continuous_const).IntervalIntegrable _ _
      _ = (∫ x in 0 ..N, x ∂ρ) + ∫ x in 0 ..N, 1 ∂ρ :=
        by
        rw [intervalIntegral.integral_add]
        · exact continuous_id.interval_integrable _ _
        · exact continuous_const.interval_integrable _ _
      _ = 𝔼[truncation X N] + ∫ x in 0 ..N, 1 ∂ρ := by
        rw [integral_truncation_eq_interval_integral_of_nonneg hint.1 hnonneg]
      _ ≤ 𝔼[X] + ∫ x in 0 ..N, 1 ∂ρ :=
        (add_le_add_right (integral_truncation_le_integral_of_nonneg hint hnonneg) _)
      _ ≤ 𝔼[X] + 1 := by
        refine' add_le_add le_rfl _
        rw [intervalIntegral.integral_of_le (Nat.cast_nonneg _)]
        simp only [integral_const, measure.restrict_apply', measurableSet_Ioc, Set.univ_inter,
          Algebra.id.smul_eq_mul, mul_one]
        rw [← ENNReal.one_toReal]
        exact ENNReal.toReal_mono ENNReal.one_ne_top prob_le_one

  have B : ∀ a b, ℙ { ω | X ω ∈ Set.Ioc a b } = ENNReal.ofReal (∫ x in Set.Ioc a b, (1 : ℝ) ∂ρ) :=
    by
    intro a b
    rw [of_real_set_integral_one ρ _,
      measure.map_apply_of_ae_measurable hint.ae_measurable measurableSet_Ioc]
    rfl
  calc
    (∑ j in range K, ℙ { ω | X ω ∈ Set.Ioc (j : ℝ) N }) =
        ∑ j in range K, ENNReal.ofReal (∫ x in Set.Ioc (j : ℝ) N, (1 : ℝ) ∂ρ) :=
      by simp_rw [B]
    _ = ENNReal.ofReal (∑ j in range K, ∫ x in Set.Ioc (j : ℝ) N, (1 : ℝ) ∂ρ) :=
      by
      rw [ENNReal.ofReal_sum_of_nonneg]
      simp only [integral_const, Algebra.id.smul_eq_mul, mul_one, ENNReal.toReal_nonneg,
        imp_true_iff]
    _ = ENNReal.ofReal (∑ j in range K, ∫ x in (j : ℝ)..N, (1 : ℝ) ∂ρ) :=
      by
      congr 1
      refine' sum_congr rfl fun j hj => _
      rw [intervalIntegral.integral_of_le (Nat.cast_le.2 ((mem_range.1 hj).le.trans hKN))]
    _ ≤ ENNReal.ofReal (𝔼[X] + 1) := ENNReal.ofReal_le_ofReal A

#align probability_theory.sum_prob_mem_Ioc_le ProbabilityTheory.sum_prob_mem_Ioc_le

theorem tsum_prob_mem_Ioi_lt_top {X : Ω → ℝ} (hint : Integrable X) (hnonneg : 0 ≤ X) :
    (∑' j : ℕ, ℙ { ω | X ω ∈ Set.Ioi (j : ℝ) }) < ∞ :=
  by
  suffices : ∀ K : ℕ, (∑ j in range K, ℙ { ω | X ω ∈ Set.Ioi (j : ℝ) }) ≤ ENNReal.ofReal (𝔼[X] + 1)
  exact
    (le_of_tendsto_of_tendsto (ENNReal.tendsto_nat_tsum _) tendsto_const_nhds
          (eventually_of_forall this)).trans_lt
      ENNReal.ofReal_lt_top
  intro K
  have A :
    tendsto (fun N : ℕ => ∑ j in range K, ℙ { ω | X ω ∈ Set.Ioc (j : ℝ) N }) at_top
      (𝓝 (∑ j in range K, ℙ { ω | X ω ∈ Set.Ioi (j : ℝ) })) :=
    by
    refine' tendsto_finset_sum _ fun i hi => _
    have : { ω | X ω ∈ Set.Ioi (i : ℝ) } = ⋃ N : ℕ, { ω | X ω ∈ Set.Ioc (i : ℝ) N } :=
      by
      apply Set.Subset.antisymm _ _
      · intro ω hω
        obtain ⟨N, hN⟩ : ∃ N : ℕ, X ω ≤ N := exists_nat_ge (X ω)
        exact Set.mem_unionᵢ.2 ⟨N, hω, hN⟩
      ·
        simp (config := { contextual := true }) only [Set.mem_Ioc, Set.mem_Ioi,
          Set.unionᵢ_subset_iff, Set.setOf_subset_setOf, imp_true_iff]
    rw [this]
    apply tendsto_measure_Union
    intro m n hmn x hx
    exact ⟨hx.1, hx.2.trans (Nat.cast_le.2 hmn)⟩
  apply le_of_tendsto_of_tendsto A tendsto_const_nhds
  filter_upwards [Ici_mem_at_top K]with N hN
  exact sum_prob_mem_Ioc_le hint hnonneg hN
#align probability_theory.tsum_prob_mem_Ioi_lt_top ProbabilityTheory.tsum_prob_mem_Ioi_lt_top

theorem sum_variance_truncation_le {X : Ω → ℝ} (hint : Integrable X) (hnonneg : 0 ≤ X) (K : ℕ) :
    (∑ j in range K, ((j : ℝ) ^ 2)⁻¹ * 𝔼[truncation X j ^ 2]) ≤ 2 * 𝔼[X] :=
  by
  set Y := fun n : ℕ => truncation X n
  let ρ : Measure ℝ := measure.map X ℙ
  have Y2 : ∀ n, 𝔼[Y n ^ 2] = ∫ x in 0 ..n, x ^ 2 ∂ρ :=
    by
    intro n
    change 𝔼[fun x => Y n x ^ 2] = _
    rw [moment_truncation_eq_interval_integral_of_nonneg hint.1 two_ne_zero hnonneg]
  calc
    (∑ j in range K, ((j : ℝ) ^ 2)⁻¹ * 𝔼[Y j ^ 2]) =
        ∑ j in range K, ((j : ℝ) ^ 2)⁻¹ * ∫ x in 0 ..j, x ^ 2 ∂ρ :=
      by simp_rw [Y2]
    _ = ∑ j in range K, ((j : ℝ) ^ 2)⁻¹ * ∑ k in range j, ∫ x in k..(k + 1 : ℕ), x ^ 2 ∂ρ :=
      by
      congr 1 with j
      congr 1
      rw [intervalIntegral.sum_integral_adjacent_intervals]
      · norm_cast
      intro k hk
      exact (continuous_id.pow _).IntervalIntegrable _ _
    _ = ∑ k in range K, (∑ j in Ioo k K, ((j : ℝ) ^ 2)⁻¹) * ∫ x in k..(k + 1 : ℕ), x ^ 2 ∂ρ :=
      by
      simp_rw [mul_sum, sum_mul, sum_sigma']
      refine'
        sum_bij' (fun (p : Σi : ℕ, ℕ) hp => (⟨p.2, p.1⟩ : Σi : ℕ, ℕ)) _ (fun a ha => rfl)
          (fun (p : Σi : ℕ, ℕ) hp => (⟨p.2, p.1⟩ : Σi : ℕ, ℕ)) _ _ _
      · rintro ⟨i, j⟩ hij
        simp only [mem_sigma, mem_range, mem_filter] at hij
        simp [hij, mem_sigma, mem_range, and_self_iff, hij.2.trans hij.1]
      · rintro ⟨i, j⟩ hij
        simp only [mem_sigma, mem_range, mem_Ioo] at hij
        simp only [hij, mem_sigma, mem_range, and_self_iff]
      · rintro ⟨i, j⟩ hij
        rfl
      · rintro ⟨i, j⟩ hij
        rfl
    _ ≤ ∑ k in range K, 2 / (k + 1) * ∫ x in k..(k + 1 : ℕ), x ^ 2 ∂ρ :=
      by
      apply sum_le_sum fun k hk => _
      refine' mul_le_mul_of_nonneg_right (sum_Ioo_inv_sq_le _ _) _
      refine' intervalIntegral.integral_nonneg_of_forall _ fun u => sq_nonneg _
      simp only [Nat.cast_add, Nat.cast_one, le_add_iff_nonneg_right, zero_le_one]
    _ ≤ ∑ k in range K, ∫ x in k..(k + 1 : ℕ), 2 * x ∂ρ :=
      by
      apply sum_le_sum fun k hk => _
      have Ik : (k : ℝ) ≤ (k + 1 : ℕ) := by simp
      rw [← intervalIntegral.integral_const_mul, intervalIntegral.integral_of_le Ik,
        intervalIntegral.integral_of_le Ik]
      refine' set_integral_mono_on _ _ measurableSet_Ioc fun x hx => _
      · apply Continuous.integrableOnIoc
        exact continuous_const.mul (continuous_pow 2)
      · apply Continuous.integrableOnIoc
        exact continuous_const.mul continuous_id'
      ·
        calc
          2 / (↑k + 1) * x ^ 2 = x / (k + 1) * (2 * x) := by ring
          _ ≤ 1 * (2 * x) :=
            (mul_le_mul_of_nonneg_right
              (by
                apply_mod_cast (div_le_one _).2 hx.2
                simp only [Nat.cast_add, Nat.cast_one]
                linarith only [show (0 : ℝ) ≤ k from Nat.cast_nonneg k])
              (mul_nonneg zero_le_two ((Nat.cast_nonneg k).trans hx.1.le)))
          _ = 2 * x := by rw [one_mul]

    _ = 2 * ∫ x in (0 : ℝ)..K, x ∂ρ :=
      by
      rw [intervalIntegral.sum_integral_adjacent_intervals fun k hk => _]
      swap; · exact (continuous_const.mul continuous_id').IntervalIntegrable _ _
      rw [intervalIntegral.integral_const_mul]
      norm_cast
    _ ≤ 2 * 𝔼[X] :=
      mul_le_mul_of_nonneg_left
        (by
          rw [← integral_truncation_eq_interval_integral_of_nonneg hint.1 hnonneg]
          exact integral_truncation_le_integral_of_nonneg hint hnonneg)
        zero_le_two

#align probability_theory.sum_variance_truncation_le ProbabilityTheory.sum_variance_truncation_le

end MomentEstimates

section StrongLawNonneg

/- This paragraph proves the strong law of large numbers (almost sure version, assuming only
pairwise independence) for nonnegative random variables, following Etemadi's proof. -/
variable (X : ℕ → Ω → ℝ) (hint : Integrable (X 0))
  (hindep : Pairwise fun i j => IndepFunCat (X i) (X j)) (hident : ∀ i, IdentDistrib (X i) (X 0))
  (hnonneg : ∀ i ω, 0 ≤ X i ω)

include X hint hindep hident hnonneg

/- The truncation of `Xᵢ` up to `i` satisfies the strong law of large numbers (with respect to
the truncated expectation) along the sequence `c^n`, for any `c > 1`, up to a given `ε > 0`.
This follows from a variance control. -/
theorem strong_law_aux1 {c : ℝ} (c_one : 1 < c) {ε : ℝ} (εpos : 0 < ε) :
    ∀ᵐ ω,
      ∀ᶠ n : ℕ in atTop,
        |(∑ i in range ⌊c ^ n⌋₊, truncation (X i) i ω) -
              𝔼[∑ i in range ⌊c ^ n⌋₊, truncation (X i) i]| <
          ε * ⌊c ^ n⌋₊ :=
  by
  /- Let `S n = ∑ i in range n, Y i` where `Y i = truncation (X i) i`. We should show that
    `|S k - 𝔼[S k]| / k ≤ ε` along the sequence of powers of `c`. For this, we apply Borel-Cantelli:
    it suffices to show that the converse probabilites are summable. From Chebyshev inequality, this
    will follow from a variance control `∑' Var[S (c^i)] / (c^i)^2 < ∞`. This is checked in `I2` using
    pairwise independence to expand the variance of the sum as the sum of the variances, and then
    a straightforward but tedious computation (essentially boiling down to the fact that the sum of
    `1/(c ^ i)^2` beyong a threshold `j` is comparable to `1/j^2`).
    Note that we have written `c^i` in the above proof sketch, but rigorously one should put integer
    parts everywhere, making things more painful. We write `u i = ⌊c^i⌋₊` for brevity. -/
  have c_pos : 0 < c := zero_lt_one.trans c_one
  let ρ : Measure ℝ := measure.map (X 0) ℙ
  have hX : ∀ i, ae_strongly_measurable (X i) ℙ := fun i =>
    (hident i).symm.aeStronglyMeasurableSnd hint.1
  have A : ∀ i, strongly_measurable (indicator (Set.Ioc (-i : ℝ) i) id) := fun i =>
    strongly_measurable_id.indicator measurableSet_Ioc
  set Y := fun n : ℕ => truncation (X n) n with hY
  set S := fun n => ∑ i in range n, Y i with hS
  let u : ℕ → ℕ := fun n => ⌊c ^ n⌋₊
  have u_mono : Monotone u := fun i j hij => Nat.floor_mono (pow_le_pow c_one.le hij)
  have I1 : ∀ K, (∑ j in range K, ((j : ℝ) ^ 2)⁻¹ * Var[Y j]) ≤ 2 * 𝔼[X 0] :=
    by
    intro K
    calc
      (∑ j in range K, ((j : ℝ) ^ 2)⁻¹ * Var[Y j]) ≤
          ∑ j in range K, ((j : ℝ) ^ 2)⁻¹ * 𝔼[truncation (X 0) j ^ 2] :=
        by
        apply sum_le_sum fun j hj => _
        refine' mul_le_mul_of_nonneg_left _ (inv_nonneg.2 (sq_nonneg _))
        rw [(hident j).truncation.variance_eq]
        exact variance_le_expectation_sq (hX 0).truncation
      _ ≤ 2 * 𝔼[X 0] := sum_variance_truncation_le hint (hnonneg 0) K

  let C := c ^ 5 * (c - 1)⁻¹ ^ 3 * (2 * 𝔼[X 0])
  have I2 : ∀ N, (∑ i in range N, ((u i : ℝ) ^ 2)⁻¹ * Var[S (u i)]) ≤ C :=
    by
    intro N
    calc
      (∑ i in range N, ((u i : ℝ) ^ 2)⁻¹ * Var[S (u i)]) =
          ∑ i in range N, ((u i : ℝ) ^ 2)⁻¹ * ∑ j in range (u i), Var[Y j] :=
        by
        congr 1 with i
        congr 1
        rw [hS, indep_fun.variance_sum]
        · intro j hj
          exact (hident j).aeStronglyMeasurableFst.memℒpTruncation
        · intro k hk l hl hkl
          exact (hindep hkl).comp (A k).Measurable (A l).Measurable
      _ =
          ∑ j in range (u (N - 1)),
            (∑ i in (range N).filterₓ fun i => j < u i, ((u i : ℝ) ^ 2)⁻¹) * Var[Y j] :=
        by
        simp_rw [mul_sum, sum_mul, sum_sigma']
        refine'
          sum_bij' (fun (p : Σi : ℕ, ℕ) hp => (⟨p.2, p.1⟩ : Σi : ℕ, ℕ)) _ (fun a ha => rfl)
            (fun (p : Σi : ℕ, ℕ) hp => (⟨p.2, p.1⟩ : Σi : ℕ, ℕ)) _ _ _
        · rintro ⟨i, j⟩ hij
          simp only [mem_sigma, mem_range] at hij
          simp only [hij.1, hij.2, mem_sigma, mem_range, mem_filter, and_true_iff]
          exact hij.2.trans_le (u_mono (Nat.le_pred_of_lt hij.1))
        · rintro ⟨i, j⟩ hij
          simp only [mem_sigma, mem_range, mem_filter] at hij
          simp only [hij.2.1, hij.2.2, mem_sigma, mem_range, and_self_iff]
        · rintro ⟨i, j⟩ hij
          rfl
        · rintro ⟨i, j⟩ hij
          rfl
      _ ≤ ∑ j in range (u (N - 1)), c ^ 5 * (c - 1)⁻¹ ^ 3 / j ^ 2 * Var[Y j] :=
        by
        apply sum_le_sum fun j hj => _
        rcases@eq_zero_or_pos _ _ j with (rfl | hj)
        · simp only [Y, Nat.cast_zero, zero_pow', Ne.def, bit0_eq_zero, Nat.one_ne_zero,
            not_false_iff, div_zero, MulZeroClass.zero_mul]
          simp only [Nat.cast_zero, truncation_zero, variance_zero, MulZeroClass.mul_zero]
        apply mul_le_mul_of_nonneg_right _ (variance_nonneg _ _)
        convert sum_div_nat_floor_pow_sq_le_div_sq N (Nat.cast_pos.2 hj) c_one
        · simp only [Nat.cast_lt]
        · simp only [one_div]
      _ = c ^ 5 * (c - 1)⁻¹ ^ 3 * ∑ j in range (u (N - 1)), ((j : ℝ) ^ 2)⁻¹ * Var[Y j] :=
        by
        simp_rw [mul_sum, div_eq_mul_inv]
        ring_nf
      _ ≤ c ^ 5 * (c - 1)⁻¹ ^ 3 * (2 * 𝔼[X 0]) :=
        by
        apply mul_le_mul_of_nonneg_left (I1 _)
        apply mul_nonneg (pow_nonneg c_pos.le _)
        exact pow_nonneg (inv_nonneg.2 (sub_nonneg.2 c_one.le)) _

  have I3 :
    ∀ N,
      (∑ i in range N, ℙ { ω | (u i * ε : ℝ) ≤ |S (u i) ω - 𝔼[S (u i)]| }) ≤
        ENNReal.ofReal (ε⁻¹ ^ 2 * C) :=
    by
    intro N
    calc
      (∑ i in range N, ℙ { ω | (u i * ε : ℝ) ≤ |S (u i) ω - 𝔼[S (u i)]| }) ≤
          ∑ i in range N, ENNReal.ofReal (Var[S (u i)] / (u i * ε) ^ 2) :=
        by
        refine' sum_le_sum fun i hi => _
        apply meas_ge_le_variance_div_sq
        · exact mem_ℒp_finset_sum' _ fun j hj => (hident j).aeStronglyMeasurableFst.memℒpTruncation
        · apply mul_pos (Nat.cast_pos.2 _) εpos
          refine' zero_lt_one.trans_le _
          apply Nat.le_floor
          rw [Nat.cast_one]
          apply one_le_pow_of_one_le c_one.le
      _ = ENNReal.ofReal (∑ i in range N, Var[S (u i)] / (u i * ε) ^ 2) :=
        by
        rw [ENNReal.ofReal_sum_of_nonneg fun i hi => _]
        exact div_nonneg (variance_nonneg _ _) (sq_nonneg _)
      _ ≤ ENNReal.ofReal (ε⁻¹ ^ 2 * C) :=
        by
        apply ENNReal.ofReal_le_ofReal
        simp_rw [div_eq_inv_mul, ← inv_pow, mul_inv, mul_comm _ ε⁻¹, mul_pow, mul_assoc, ← mul_sum]
        refine' mul_le_mul_of_nonneg_left _ (sq_nonneg _)
        simp_rw [inv_pow]
        exact I2 N

  have I4 : (∑' i, ℙ { ω | (u i * ε : ℝ) ≤ |S (u i) ω - 𝔼[S (u i)]| }) < ∞ :=
    (le_of_tendsto_of_tendsto' (ENNReal.tendsto_nat_tsum _) tendsto_const_nhds I3).trans_lt
      ENNReal.ofReal_lt_top
  filter_upwards [ae_eventually_not_mem I4.ne]with ω hω
  simp_rw [not_le, mul_comm, S, sum_apply] at hω
  exact hω
#align probability_theory.strong_law_aux1 ProbabilityTheory.strong_law_aux1

/- The truncation of `Xᵢ` up to `i` satisfies the strong law of large numbers
(with respect to the truncated expectation) along the sequence
`c^n`, for any `c > 1`. This follows from `strong_law_aux1` by varying `ε`. -/
theorem strong_law_aux2 {c : ℝ} (c_one : 1 < c) :
    ∀ᵐ ω,
      (fun n : ℕ =>
          (∑ i in range ⌊c ^ n⌋₊, truncation (X i) i ω) -
            𝔼[∑ i in range ⌊c ^ n⌋₊, truncation (X i) i]) =o[atTop]
        fun n : ℕ => (⌊c ^ n⌋₊ : ℝ) :=
  by
  obtain ⟨v, -, v_pos, v_lim⟩ :
    ∃ v : ℕ → ℝ, StrictAnti v ∧ (∀ n : ℕ, 0 < v n) ∧ tendsto v at_top (𝓝 0) :=
    exists_seq_strictAnti_tendsto (0 : ℝ)
  have := fun i => strong_law_aux1 X hint hindep hident hnonneg c_one (v_pos i)
  filter_upwards [ae_all_iff.2 this]with ω hω
  apply Asymptotics.isOCat_iff.2 fun ε εpos => _
  obtain ⟨i, hi⟩ : ∃ i, v i < ε := ((tendsto_order.1 v_lim).2 ε εpos).exists
  filter_upwards [hω i]with n hn
  simp only [Real.norm_eq_abs, LatticeOrderedCommGroup.abs_abs, Nat.abs_cast]
  exact hn.le.trans (mul_le_mul_of_nonneg_right hi.le (Nat.cast_nonneg _))
#align probability_theory.strong_law_aux2 ProbabilityTheory.strong_law_aux2

omit hindep hnonneg

/-- The expectation of the truncated version of `Xᵢ` behaves asymptotically like the whole
expectation. This follows from convergence and Cesaro averaging. -/
theorem strong_law_aux3 :
    (fun n => 𝔼[∑ i in range n, truncation (X i) i] - n * 𝔼[X 0]) =o[atTop] (coe : ℕ → ℝ) :=
  by
  have A : tendsto (fun i => 𝔼[truncation (X i) i]) at_top (𝓝 𝔼[X 0]) :=
    by
    convert(tendsto_integral_truncation hint).comp tendsto_nat_cast_atTop_atTop
    ext i
    exact (hident i).truncation.integral_eq
  convert Asymptotics.isOCat_sum_range_of_tendsto_zero (tendsto_sub_nhds_zero_iff.2 A)
  ext1 n
  simp only [sum_sub_distrib, sum_const, card_range, nsmul_eq_mul, sum_apply, sub_left_inj]
  rw [integral_finset_sum _ fun i hi => _]
  exact ((hident i).symm.integrableSnd hint).1.integrableTruncation
#align probability_theory.strong_law_aux3 ProbabilityTheory.strong_law_aux3

include hindep hnonneg

/- The truncation of `Xᵢ` up to `i` satisfies the strong law of large numbers
(with respect to the original expectation) along the sequence
`c^n`, for any `c > 1`. This follows from the version from the truncated expectation, and the
fact that the truncated and the original expectations have the same asymptotic behavior. -/
theorem strong_law_aux4 {c : ℝ} (c_one : 1 < c) :
    ∀ᵐ ω,
      (fun n : ℕ => (∑ i in range ⌊c ^ n⌋₊, truncation (X i) i ω) - ⌊c ^ n⌋₊ * 𝔼[X 0]) =o[atTop]
        fun n : ℕ => (⌊c ^ n⌋₊ : ℝ) :=
  by
  filter_upwards [strong_law_aux2 X hint hindep hident hnonneg c_one]with ω hω
  have A : tendsto (fun n : ℕ => ⌊c ^ n⌋₊) at_top at_top :=
    tendsto_nat_floor_at_top.comp (tendsto_pow_atTop_atTop_of_one_lt c_one)
  convert hω.add ((strong_law_aux3 X hint hident).comp_tendsto A)
  ext1 n
  simp
#align probability_theory.strong_law_aux4 ProbabilityTheory.strong_law_aux4

omit hindep

/-- The truncated and non-truncated versions of `Xᵢ` have the same asymptotic behavior, as they
almost surely coincide at all but finitely many steps. This follows from a probability computation
and Borel-Cantelli. -/
theorem strong_law_aux5 :
    ∀ᵐ ω,
      (fun n : ℕ => (∑ i in range n, truncation (X i) i ω) - ∑ i in range n, X i ω) =o[atTop]
        fun n : ℕ => (n : ℝ) :=
  by
  have A : (∑' j : ℕ, ℙ { ω | X j ω ∈ Set.Ioi (j : ℝ) }) < ∞ :=
    by
    convert tsum_prob_mem_Ioi_lt_top hint (hnonneg 0)
    ext1 j
    exact (hident j).measure_mem_eq measurableSet_Ioi
  have B : ∀ᵐ ω, tendsto (fun n : ℕ => truncation (X n) n ω - X n ω) at_top (𝓝 0) :=
    by
    filter_upwards [ae_eventually_not_mem A.ne]with ω hω
    apply tendsto_const_nhds.congr' _
    filter_upwards [hω, Ioi_mem_at_top 0]with n hn npos
    simp only [truncation, indicator, Set.mem_Ioc, id.def, Function.comp_apply]
    split_ifs
    · exact (sub_self _).symm
    · have : -(n : ℝ) < X n ω := by
        apply lt_of_lt_of_le _ (hnonneg n ω)
        simpa only [Right.neg_neg_iff, Nat.cast_pos] using npos
      simp only [this, true_and_iff, not_le] at h
      exact (hn h).elim
  filter_upwards [B]with ω hω
  convert is_o_sum_range_of_tendsto_zero hω
  ext n
  rw [sum_sub_distrib]
#align probability_theory.strong_law_aux5 ProbabilityTheory.strong_law_aux5

include hindep

/- `Xᵢ` satisfies the strong law of large numbers along the sequence
`c^n`, for any `c > 1`. This follows from the version for the truncated `Xᵢ`, and the fact that
`Xᵢ` and its truncated version have the same asymptotic behavior. -/
theorem strong_law_aux6 {c : ℝ} (c_one : 1 < c) :
    ∀ᵐ ω, Tendsto (fun n : ℕ => (∑ i in range ⌊c ^ n⌋₊, X i ω) / ⌊c ^ n⌋₊) atTop (𝓝 𝔼[X 0]) :=
  by
  have H : ∀ n : ℕ, (0 : ℝ) < ⌊c ^ n⌋₊ := by
    intro n
    refine' zero_lt_one.trans_le _
    simp only [Nat.one_le_cast, Nat.one_le_floor_iff, one_le_pow_of_one_le c_one.le n]
  filter_upwards [strong_law_aux4 X hint hindep hident hnonneg c_one,
    strong_law_aux5 X hint hident hnonneg]with ω hω h'ω
  rw [← tendsto_sub_nhds_zero_iff, ← Asymptotics.isOCat_one_iff ℝ]
  have L :
    (fun n : ℕ => (∑ i in range ⌊c ^ n⌋₊, X i ω) - ⌊c ^ n⌋₊ * 𝔼[X 0]) =o[at_top] fun n =>
      (⌊c ^ n⌋₊ : ℝ) :=
    by
    have A : tendsto (fun n : ℕ => ⌊c ^ n⌋₊) at_top at_top :=
      tendsto_nat_floor_at_top.comp (tendsto_pow_atTop_atTop_of_one_lt c_one)
    convert hω.sub (h'ω.comp_tendsto A)
    ext1 n
    simp only [sub_sub_sub_cancel_left]
  convert L.mul_is_O (is_O_refl (fun n : ℕ => (⌊c ^ n⌋₊ : ℝ)⁻¹) at_top) <;>
    · ext1 n
      field_simp [(H n).ne']
#align probability_theory.strong_law_aux6 ProbabilityTheory.strong_law_aux6

/-- `Xᵢ` satisfies the strong law of large numbers along all integers. This follows from the
corresponding fact along the sequences `c^n`, and the fact that any integer can be sandwiched
between `c^n` and `c^(n+1)` with comparably small error if `c` is close enough to `1`
(which is formalized in `tendsto_div_of_monotone_of_tendsto_div_floor_pow`). -/
theorem strong_law_aux7 :
    ∀ᵐ ω, Tendsto (fun n : ℕ => (∑ i in range n, X i ω) / n) atTop (𝓝 𝔼[X 0]) :=
  by
  obtain ⟨c, -, cone, clim⟩ :
    ∃ c : ℕ → ℝ, StrictAnti c ∧ (∀ n : ℕ, 1 < c n) ∧ tendsto c at_top (𝓝 1) :=
    exists_seq_strictAnti_tendsto (1 : ℝ)
  have :
    ∀ k,
      ∀ᵐ ω,
        tendsto (fun n : ℕ => (∑ i in range ⌊c k ^ n⌋₊, X i ω) / ⌊c k ^ n⌋₊) at_top (𝓝 𝔼[X 0]) :=
    fun k => strong_law_aux6 X hint hindep hident hnonneg (cone k)
  filter_upwards [ae_all_iff.2 this]with ω hω
  apply tendsto_div_of_monotone_of_tendsto_div_floor_pow _ _ _ c cone clim _
  · intro m n hmn
    exact sum_le_sum_of_subset_of_nonneg (range_mono hmn) fun i hi h'i => hnonneg i ω
  · exact hω
#align probability_theory.strong_law_aux7 ProbabilityTheory.strong_law_aux7

end StrongLawNonneg

/-- *Strong law of large numbers*, almost sure version: if `X n` is a sequence of independent
identically distributed integrable real-valued random variables, then `∑ i in range n, X i / n`
converges almost surely to `𝔼[X 0]`. We give here the strong version, due to Etemadi, that only
requires pairwise independence. -/
theorem strong_law_ae (X : ℕ → Ω → ℝ) (hint : Integrable (X 0))
    (hindep : Pairwise fun i j => IndepFunCat (X i) (X j))
    (hident : ∀ i, IdentDistrib (X i) (X 0)) :
    ∀ᵐ ω, Tendsto (fun n : ℕ => (∑ i in range n, X i ω) / n) atTop (𝓝 𝔼[X 0]) :=
  by
  let pos : ℝ → ℝ := fun x => max x 0
  let neg : ℝ → ℝ := fun x => max (-x) 0
  have posm : Measurable Pos := measurable_id'.max measurable_const
  have negm : Measurable neg := measurable_id'.neg.max measurable_const
  have A :
    ∀ᵐ ω, tendsto (fun n : ℕ => (∑ i in range n, (Pos ∘ X i) ω) / n) at_top (𝓝 𝔼[Pos ∘ X 0]) :=
    strong_law_aux7 _ hint.pos_part (fun i j hij => (hindep hij).comp posm posm)
      (fun i => (hident i).comp posm) fun i ω => le_max_right _ _
  have B :
    ∀ᵐ ω, tendsto (fun n : ℕ => (∑ i in range n, (neg ∘ X i) ω) / n) at_top (𝓝 𝔼[neg ∘ X 0]) :=
    strong_law_aux7 _ hint.neg_part (fun i j hij => (hindep hij).comp negm negm)
      (fun i => (hident i).comp negm) fun i ω => le_max_right _ _
  filter_upwards [A, B]with ω hωpos hωneg
  convert hωpos.sub hωneg
  · simp only [← sub_div, ← sum_sub_distrib, max_zero_sub_max_neg_zero_eq_self]
  · simp only [← integral_sub hint.pos_part hint.neg_part, max_zero_sub_max_neg_zero_eq_self]
#align probability_theory.strong_law_ae ProbabilityTheory.strong_law_ae

end StrongLawAe

section StrongLawLp

variable {Ω : Type _} [MeasureSpace Ω] [IsProbabilityMeasure (ℙ : Measure Ω)]

/-- *Strong law of large numbers*, Lᵖ version: if `X n` is a sequence of independent
identically distributed real-valued random variables in Lᵖ, then `∑ i in range n, X i / n`
converges in Lᵖ to `𝔼[X 0]`. -/
theorem strong_law_Lp {p : ℝ≥0∞} (hp : 1 ≤ p) (hp' : p ≠ ∞) (X : ℕ → Ω → ℝ) (hℒp : Memℒp (X 0) p)
    (hindep : Pairwise fun i j => IndepFunCat (X i) (X j))
    (hident : ∀ i, IdentDistrib (X i) (X 0)) :
    Tendsto (fun n => snorm (fun ω => (∑ i in range n, X i ω) / n - 𝔼[X 0]) p ℙ) atTop (𝓝 0) :=
  by
  have hmeas : ∀ i, ae_strongly_measurable (X i) ℙ := fun i =>
    (hident i).aeStronglyMeasurable_iff.2 hℒp.1
  have hint : integrable (X 0) ℙ := hℒp.integrable hp
  have havg : ∀ n, ae_strongly_measurable (fun ω => (∑ i in range n, X i ω) / n) ℙ :=
    by
    intro n
    simp_rw [div_eq_mul_inv]
    exact ae_strongly_measurable.mul_const (ae_strongly_measurable_sum _ fun i _ => hmeas i) _
  refine'
    tendsto_Lp_of_tendsto_in_measure _ hp hp' havg (mem_ℒp_const _) _
      (tendsto_in_measure_of_tendsto_ae havg (strong_law_ae _ hint hindep hident))
  rw [(_ : (fun n ω => (∑ i in range n, X i ω) / ↑n) = fun n => (∑ i in range n, X i) / ↑n)]
  ·
    exact
      (uniform_integrable_average hp <|
            mem_ℒp.uniform_integrable_of_ident_distrib hp hp' hℒp hident).2.1
  · ext (n ω)
    simp only [Pi.coe_nat, Pi.div_apply, sum_apply]
#align probability_theory.strong_law_Lp ProbabilityTheory.strong_law_Lp

end StrongLawLp

end ProbabilityTheory


example (p q : ℕ → Prop) : (∃ x, p x) → ∃ x, p x ∨ q x :=
begin
  intro h,
  cases h with x hpx,
  constructor, left, exact hpx
end


/-
Copyright (c) 2015 Joey Teng. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Joey Teng.
-/
import tactic -- imports all the Lean tactics
import combinatorics.simple_graph.connectivity

-- Graph Theory

universe u

namespace simple_graph
variables {V : Type u} (G : simple_graph V)

/-!
## Suppliments / Refinement to Connectivity

-/
namespace walk
variables {G} [decidable_eq V]

lemma support_drop_until_in {v w : V} (p : G.walk v w) (u : V) (h : u ∈ p.support): u ∈ (p.drop_until u h).support :=
begin
  finish,
end

lemma support_bypass_subset' {u v : V} (p : G.walk u v) : p.support ⊆
 p.bypass.support :=
begin
  induction p,
  { simp!, },
  { simp! only,
    split_ifs,
    {
      have h : p_p.bypass.support ⊆ p_p.support,
      { exact support_bypass_subset p_p, },
      by_cases hcases : p_u ∈ p_p.support,
      {
        intros v' h'',
        cases h'',
        { finish, },
        {
          sorry,
        },
      },
      { tauto, },
    },
    { rw support_cons,
      apply list.cons_subset_cons,
      assumption, }, },
end

end walk

/-!
## Connectivity

Utilities about `walk`, `trail` and `path`.
-/

def subwalk_nil_from_vertex : Π {u : V}, G.walk u u
| u := (walk.nil : G.walk u u)

theorem is_trail_nil {v : V} (p : G.walk v v): p = walk.nil → p.is_trail :=
begin
  intro hp,
  rw hp,
  simp,
end

/-!
## Reachable

Based on `simple_graph.walk`
-/

/-- Exists an walk in `G` from `v` to `w` --/
def reachable (v w : V) : Prop :=
  ∃ p : G.walk v w, true

theorem reachable_def {v w : V} :
  G.reachable v w ↔ ∃ ⦃p : G.walk v w⦄, true :=
begin
  refl,
end

theorem reachable_if_walk {v w : V} (p : G.walk v w):
  G.reachable v w :=
begin
  use p,
end

/-- `v` is always reachable to itself --/
@[simp]
theorem reachable_self {v : V} : G.reachable v v :=
begin
  use walk.nil,
end

@[simp]
theorem reachable_trans {u v w : V} :
  G.reachable u v → G.reachable v w → G.reachable u w :=
begin
  intros h1 h2,
  cases h1 with p1 hp1,
  cases h2 with p2 hp2,
  have p := p1.append p2,
  use p,
end

@[simp]
theorem reachable_symm {u v : V} :
  G.reachable u v ↔ G.reachable v u :=
begin
  split;
  intro h;
  cases h with p _;
  use p.reverse,
end

@[simp]
theorem reachable_adj {v w : V} : G.adj v w → G.reachable v w :=
begin
  intro h,
  have p : G.walk v w,
  { constructor,
    assumption,
    exact walk.nil, },
  use p,
end

theorem reachable_if_passing {u v w : V} (p : G.walk u w) (hp : v ∈ p.support) [decidable_eq V]:
  G.reachable v w :=
begin
  rw reachable_def,
  fconstructor,
  {
    let p' : G.walk u w := p.to_path,
    have hp' : v ∈ p'.support,
    {
      apply walk.support_bypass_subset',
      exact hp,
    },
    have hp'' : p'.is_path,
    { exact walk.bypass_is_path p, },
    let q : G.walk v w := p'.drop_until v hp',
    use q,
  },
  { triv },
end

theorem reachable_if_support {u v w x : V} (p : G.walk u x) (h1 : v ∈ p.support) (h2 : w ∈ p.support) [decidable_eq V]:
  G.reachable v w :=
begin
  have h' : G.reachable v x,
  { apply reachable_if_passing G p h1, },
  have h'' : G.reachable w x,
  { apply reachable_if_passing G p h2, },
  have h''' : G.reachable x w,
  {
    rw G.reachable_symm at h'',
    exact h'',
  },

  -- Unsure how to use "reachable_trans", thus copy over
  cases h' with p1 hp1,
  cases h''' with p2 hp2,
  have p := p1.append p2,
  use p,
end

/-### Extend to path-/

theorem reachable_path {v w : V} [decidable_eq V]:
  G.reachable v w ↔ ∃ ⦃p : G.walk v w⦄, p.is_path :=
begin
  split,
  { simp,
    intro h,
    rw reachable at h,
    cases h with p _,
    let p' : G.walk v w := p.bypass,
    use p',
    exact walk.bypass_is_path p,
  },
  { simp,
    intros x hx,
    use x,
  },
end

/-### Extend to trail-/

theorem reachable_trail {v w : V} [decidable_eq V]:
  G.reachable v w ↔ ∃ ⦃p : G.walk v w⦄, p.is_trail :=
begin
  split,
  { rw reachable_path,
    intro h,
    cases h with p hp,
    use p,
    let p' := hp.to_trail,
    exact p',
  },
  { simp,
    intros x hx,
    use x,
  },
end

/-!## Connected-/

/-- All vertices are reachable to each other --/
def is_connected : Prop :=
  ∀ ⦃v w : V⦄, G.reachable v w

/-- Complete Graph is connected.
  Note that empty graph may not, unless `V` is empty. --/
theorem complete_graph_is_connected : G = ⊤ → G.is_connected :=
begin
  intros hG v w,
  by_cases eq : v = w,
  { rw eq,
    exact G.reachable_self, },
  { rw ← ne at eq,
    have p : G.walk v w,
    {
       have h : G.adj v w,
      { finish, },
      { exact walk.cons h walk.nil },
    },
    rw reachable,
    use p,
  },
end

/-!## Eulerian Walks-/
section Eulerian
variables [decidable_eq V]

def is_euler_circuit {v : V} (p : G.walk v v) : Prop :=
  p.is_circuit ∧ (∀ ⦃e : sym2 V⦄, e ∈ G.edge_set → (e ∈ p.edges)) ∧ ∀ ⦃u : V⦄, u ∈ p.support

def is_eulerian [decidable_eq V] : Prop :=
  ∃ {v : V}, ∃ {p : G.walk v v}, G.is_euler_circuit p

theorem eulerian_is_connected :
  G.is_eulerian → G.is_connected :=
begin
  rw is_eulerian,
  intro h,
  cases h with u hu,
  cases hu with p hp,
  rw is_euler_circuit at hp,
  obtain ⟨hp, he, hV⟩ := hp,
  rw is_connected,
  intros v w,
  have hv : v ∈ p.support,
  { tauto, },
  have hw : w ∈ p.support,
  { tauto, },
  exact G.reachable_if_support p hv hw,
end

theorem eulerian_all_even_degree [decidable_rel G.adj] [fintype V] :
  G.is_eulerian → ∀ (v : V), even (G.degree v):=
begin
  intro h,
  cases h with u hu,
  cases hu with p hp,
  obtain ⟨hp, he, hV⟩ := hp,

  intro v,
  have hv : v ∈ p.support,
  { apply hV, },
  let pe := p.edges,
  let deg := G.degree v,
  have hdeg : even deg,
  {
    -- Either
    -- - v == u, thus list's first sym2 has a v, and last sym2 has a v
    -- - v != u, thus
      -- for each (sym2 V), i-th element is (_, v) → i+1-th element is (v, __)
      -- hence (sym2 V) comes in pairs
      -- thus degree is even.
    sorry,
  },
  exact hdeg,
end

/-Euler's Theorem-/
theorem is_eulerian_iff_all_even_degree [decidable_rel G.adj] [fintype V] :
  is_connected G ∧ ∀ (v : V), even (G.degree v) ↔ G.is_eulerian :=
begin
  sorry
end

/-!### Example: Seven Bridges of Königsberg

  A modified version of "Seven Bridges of Königsberg", which now contains a Euler path.

  Two duplicated edges (bridges) are removed from the original graph.
-/
section Konigsberg

/- Four sectors -/
@[derive decidable_eq]
inductive sector_
| v1 : sector_
| v2 : sector_
| v3 : sector_
| v4 : sector_

/-
  v1
  | \
  v2 - v4
  | /
  v3
-/
def bridge_ : sector_ → sector_ → Prop
| sector_.v1 sector_.v3 := false
| sector_.v3 sector_.v1 := false
| _ _ := true

/-
  A Euler Graph

    v1
   / \
  v2  v4
   \ /
    v3
-/
def bridge_2 : sector_ → sector_ → Prop
| sector_.v1 sector_.v3 := false
| sector_.v3 sector_.v1 := false
| sector_.v2 sector_.v4 := false
| sector_.v4 sector_.v2 := false
| _ _ := true

def graph_ : simple_graph sector_ := simple_graph.from_rel bridge_
def graph_2 : simple_graph sector_ := simple_graph.from_rel bridge_2

example : sector_.v1 ≠ sector_.v4 :=
begin
  exact dec_trivial
end

example {G : simple_graph sector_} [decidable_rel G.adj] [fintype sector_]:
   G = graph_2 → G.is_eulerian :=
begin
  intro h,
  -- construct such a circuit
  let v1 := sector_.v1,
  let v2 := sector_.v2,
  let v3 := sector_.v3,
  let v4 := sector_.v4,
  rw is_eulerian,
  use v4,
  let p' : G.walk v4 v4 := walk.nil,
  have e14 : G.adj v1 v4,
  {
    rw h,
    fconstructor;
    tauto,
  },
  have e21 : G.adj v2 v1,
  {
    rw h,
    fconstructor;
    tauto,
  }, -- same as e14
  have e32 : G.adj v3 v2,
  {
    rw h,
    fconstructor;
    tauto,
  },
  have e43 : G.adj v4 v3,
  {
    rw h,
    fconstructor;
    tauto,
  },
  let p : G.walk v4 v4 := walk.cons e43 (walk.cons e32 (walk.cons e21 (walk.cons e14 p'))),
  -- END of circuit construction
  use p,
  rw is_euler_circuit,
  split,
  {
    fconstructor,
    {
      fconstructor,
      exact dec_trivial,
    },
    { exact dec_trivial, },
  },
  split,
  -- prove edges
  {
    intro e,
    intro he,
    let _set := G.edge_set,
    rw ← mem_edge_set at *,
    have h_set : _set = G.edge_set,
    { refl, },
    have hset : graph_2.edge_set = {⟦(v1, v4)⟧, ⟦(v2, v1)⟧, ⟦(v3, v2)⟧, ⟦(v4, v3)⟧},
    {
      sorry,
    },
    rw ← h at hset,
    rw hset at h_set,
    norm_num,
    by_cases hc : e = ⟦(v4, v3)⟧,
    { left, exact hc, },
    {
      by_cases hc' : e = ⟦(v3, v2)⟧,
      { right, left, exact hc', },
      {
        by_cases hc'' : e = ⟦(v2, v1)⟧,
        { right, right, left, exact hc'', },
        {
          by_cases hc''' : e = ⟦(v1, v4)⟧,
          { right, right, right, exact hc''', },
          { finish, },
        },
      },
    },
  },
  -- proving vertices
  {
    intro u,
    norm_num,
    cases u,
    {
      right, right, right, left,
      refl,
    },
    {
      right, right, left,
      refl,
    },
    {
      right, left,
      refl,
    },
    {
      left,
      refl,
    },
  },
end

end Konigsberg

end Eulerian

end simple_graph


/-
Copyright (c) 2021 Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta
-/

import category_theory.limits.shapes.terminal
import category_theory.limits.shapes.binary_products
import category_theory.epi_mono

/-!
# Strict initial objects

This file sets up the basic theory of strict initial objects: initial objects where every morphism
to it is an isomorphism. This generalises a property of the empty set in the category of sets:
namely that the only function to the empty set is from itself.

We say `C` has strict initial objects if every initial object is strict, ie given any morphism
`f : A ⟶ I` where `I` is initial, then `f` is an isomorphism.
Strictly speaking, this says that *any* initial object must be strict, rather than that strict
initial objects exist, which turns out to be a more useful notion to formalise.

If the binary product of `X` with a strict initial object exists, it is also initial.

To show a category `C` with an initial object has strict initial objects, the most convenient way
is to show any morphism to the (chosen) initial object is an isomorphism and use
`has_strict_initial_objects_of_initial_is_strict`.

The dual notion (strict terminal objects) occurs much less frequently in practice so is ignored.

## TODO

* Construct examples of this: `Type*`, `Top`, `Groupoid`, simplicial types, posets.
* Construct the bottom element of the subobject lattice given strict initials.
* Show cartesian closed categories have strict initials

## References
* https://ncatlab.org/nlab/show/strict+initial+object
-/

universes v u

namespace category_theory
namespace limits
open category

variables (C : Type u) [category.{v} C]

section strict_initial

/--
We say `C` has strict initial objects if every initial object is strict, ie given any morphism
`f : A ⟶ I` where `I` is initial, then `f` is an isomorphism.

Strictly speaking, this says that *any* initial object must be strict, rather than that strict
initial objects exist.
-/
class has_strict_initial_objects : Prop :=
(out : ∀ {I A : C} (f : A ⟶ I), is_initial I → is_iso f)

variables {C}

section
variables [has_strict_initial_objects C] {I : C}

lemma is_initial.is_iso_to (hI : is_initial I) {A : C} (f : A ⟶ I) :
  is_iso f :=
has_strict_initial_objects.out f hI

lemma is_initial.strict_hom_ext (hI : is_initial I) {A : C} (f g : A ⟶ I) :
  f = g :=
begin
  haveI := hI.is_iso_to f,
  haveI := hI.is_iso_to g,
  exact eq_of_inv_eq_inv (hI.hom_ext (inv f) (inv g)),
end

lemma is_initial.subsingleton_to (hI : is_initial I) {A : C} :
  subsingleton (A ⟶ I) :=
⟨hI.strict_hom_ext⟩

@[priority 100] instance initial_mono_of_strict_initial_objects : initial_mono_class C :=
{ is_initial_mono_from := λ I A hI,
  { right_cancellation := λ B g h i, hI.strict_hom_ext _ _ } }

/-- If `I` is initial, then `X ⨯ I` is isomorphic to it. -/
@[simps hom]
noncomputable def mul_is_initial (X : C) [has_binary_product X I] (hI : is_initial I) :
  X ⨯ I ≅ I :=
@@as_iso _ prod.snd (hI.is_iso_to _)

@[simp] lemma mul_is_initial_inv (X : C) [has_binary_product X I] (hI : is_initial I) :
  (mul_is_initial X hI).inv = hI.to _ :=
hI.hom_ext _ _

/-- If `I` is initial, then `I ⨯ X` is isomorphic to it. -/
@[simps hom]
noncomputable def is_initial_mul (X : C) [has_binary_product I X] (hI : is_initial I) :
  I ⨯ X ≅ I :=
@@as_iso _ prod.fst (hI.is_iso_to _)

@[simp] lemma is_initial_mul_inv (X : C) [has_binary_product I X] (hI : is_initial I) :
  (is_initial_mul X hI).inv = hI.to _ :=
hI.hom_ext _ _

variable [has_initial C]

instance initial_is_iso_to {A : C} (f : A ⟶ ⊥_ C) : is_iso f :=
initial_is_initial.is_iso_to _

@[ext] lemma initial.hom_ext {A : C} (f g : A ⟶ ⊥_ C) : f = g :=
initial_is_initial.strict_hom_ext _ _

lemma initial.subsingleton_to {A : C} : subsingleton (A ⟶ ⊥_ C) :=
initial_is_initial.subsingleton_to

/--
The product of `X` with an initial object in a category with strict initial objects is itself
initial.
This is the generalisation of the fact that `X × empty ≃ empty` for types (or `n * 0 = 0`).
-/
@[simps hom]
noncomputable def mul_initial (X : C) [has_binary_product X ⊥_ C] :
  X ⨯ ⊥_ C ≅ ⊥_ C :=
mul_is_initial _ initial_is_initial

@[simp] lemma mul_initial_inv (X : C) [has_binary_product X ⊥_ C] :
  (mul_initial X).inv = initial.to _ :=
subsingleton.elim _ _

/--
The product of `X` with an initial object in a category with strict initial objects is itself
initial.
This is the generalisation of the fact that `empty × X ≃ empty` for types (or `0 * n = 0`).
-/
@[simps hom]
noncomputable def initial_mul (X : C) [has_binary_product (⊥_ C) X] :
  ⊥_ C ⨯ X ≅ ⊥_ C :=
is_initial_mul _ initial_is_initial

@[simp] lemma initial_mul_inv (X : C) [has_binary_product (⊥_ C) X] :
  (initial_mul X).inv = initial.to _ :=
subsingleton.elim _ _
end

/-- If `C` has an initial object such that every morphism *to* it is an isomorphism, then `C`
has strict initial objects. -/
lemma has_strict_initial_objects_of_initial_is_strict [has_initial C]
  (h : ∀ A (f : A ⟶ ⊥_ C), is_iso f) :
  has_strict_initial_objects C :=
{ out := λ I A f hI,
  begin
    haveI := h A (f ≫ hI.to _),
    exact ⟨⟨hI.to _ ≫ inv (f ≫ hI.to ⊥_ C), by rw [←assoc, is_iso.hom_inv_id], hI.hom_ext _ _⟩⟩,
  end }

end strict_initial

section strict_terminal

/--
We say `C` has strict terminal objects if every terminal object is strict, ie given any morphism
`f : I ⟶ A` where `I` is terminal, then `f` is an isomorphism.

Strictly speaking, this says that *any* terminal object must be strict, rather than that strict
terminal objects exist.
-/
class has_strict_terminal_objects : Prop :=
(out : ∀ {I A : C} (f : I ⟶ A), is_terminal I → is_iso f)

variables {C}

section
variables [has_strict_terminal_objects C] {I : C}

lemma is_terminal.is_iso_from (hI : is_terminal I) {A : C} (f : I ⟶ A) :
  is_iso f :=
has_strict_terminal_objects.out f hI

lemma is_terminal.strict_hom_ext (hI : is_terminal I) {A : C} (f g : I ⟶ A) :
  f = g :=
begin
  haveI := hI.is_iso_from f,
  haveI := hI.is_iso_from g,
  exact eq_of_inv_eq_inv (hI.hom_ext (inv f) (inv g)),
end

lemma is_terminal.subsingleton_to (hI : is_terminal I) {A : C} :
  subsingleton (I ⟶ A) :=
⟨hI.strict_hom_ext⟩

variables {J : Type v} [small_category J]

/-- If all but one object in a diagram is strict terminal, the the limit is isomorphic to the
said object via `limit.π`. -/
lemma limit_π_is_iso_of_is_strict_terminal (F : J ⥤ C) [has_limit F] (i : J)
  (H : ∀ j ≠ i, is_terminal (F.obj j)) [subsingleton (i ⟶ i)] :
  is_iso (limit.π F i) :=
begin
  classical,
  refine ⟨⟨limit.lift _ ⟨_,⟨_,_⟩⟩,_,_⟩⟩,
  { exact λ j, dite (j = i) (λ h, eq_to_hom (by { cases h, refl })) (λ h, (H _ h).from _) },
  { intros j k f,
    split_ifs,
    { cases h, cases h_1, have : f = 𝟙 _ := subsingleton.elim _ _, subst this, simpa },
    { cases h, erw category.comp_id,
      haveI : is_iso (F.map f) := (H _ h_1).is_iso_from _,
      rw ← is_iso.comp_inv_eq,
      apply (H _ h_1).hom_ext },
    { cases h_1, apply (H _ h).hom_ext },
    { apply (H _ h).hom_ext } },
  { ext,
    rw [assoc, limit.lift_π],
    dsimp only,
    split_ifs,
    { cases h, rw [id_comp, eq_to_hom_refl], exact comp_id _ },
    { apply (H _ h).hom_ext } },
  { rw limit.lift_π, simpa }
end

variable [has_terminal C]

instance terminal_is_iso_from {A : C} (f :  ⊤_ C ⟶ A) : is_iso f :=
terminal_is_terminal.is_iso_from _

@[ext] lemma terminal.hom_ext {A : C} (f g : ⊤_ C ⟶ A) : f = g :=
terminal_is_terminal.strict_hom_ext _ _

lemma terminal.subsingleton_to {A : C} : subsingleton (⊤_ C ⟶ A) :=
terminal_is_terminal.subsingleton_to

end

/-- If `C` has an object such that every morphism *from* it is an isomorphism, then `C`
has strict terminal objects. -/
lemma has_strict_terminal_objects_of_terminal_is_strict (I : C) (h : ∀ A (f : I ⟶ A), is_iso f) :
  has_strict_terminal_objects C :=
{ out := λ I' A f hI',
  begin
    haveI := h A (hI'.from _ ≫ f),
    exact ⟨⟨inv (hI'.from I ≫ f) ≫ hI'.from I,
      hI'.hom_ext _ _, by rw [assoc, is_iso.inv_hom_id]⟩⟩,
  end }

end strict_terminal
end limits
end category_theory


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau, Mario Carneiro, Johan Commelin, Amelia Livingston, Anne Baanen

! This file was ported from Lean 3 source module ring_theory.localization.ideal
! leanprover-community/mathlib commit e7f0ddbf65bd7181a85edb74b64bdc35ba4bdc74
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.RingTheory.Ideal.QuotientOperations
import Mathbin.RingTheory.Localization.Basic

/-!
# Ideals in localizations of commutative rings

## Implementation notes

See `src/ring_theory/localization/basic.lean` for a design overview.

## Tags
localization, ring localization, commutative ring localization, characteristic predicate,
commutative ring, field of fractions
-/

namespace IsLocalization

section CommSemiring

variable {R : Type _} [CommSemiring R] (M : Submonoid R) (S : Type _) [CommSemiring S]

variable [Algebra R S] [IsLocalization M S]

include M

/-- Explicit characterization of the ideal given by `ideal.map (algebra_map R S) I`.
In practice, this ideal differs only in that the carrier set is defined explicitly.
This definition is only meant to be used in proving `mem_map_algebra_map_iff`,
and any proof that needs to refer to the explicit carrier set should use that theorem. -/
private def map_ideal (I : Ideal R) : Ideal S
    where
  carrier := { z : S | ∃ x : I × M, z * algebraMap R S x.2 = algebraMap R S x.1 }
  zero_mem' := ⟨⟨0, 1⟩, by simp⟩
  add_mem' := by
    rintro a b ⟨a', ha⟩ ⟨b', hb⟩
    use ⟨a'.2 * b'.1 + b'.2 * a'.1, I.add_mem (I.mul_mem_left _ b'.1.2) (I.mul_mem_left _ a'.1.2)⟩
    use a'.2 * b'.2
    simp only [RingHom.map_add, Submodule.coe_mk, Submonoid.coe_mul, RingHom.map_mul]
    rw [add_mul, ← mul_assoc a, ha, mul_comm (algebraMap R S a'.2) (algebraMap R S b'.2), ←
      mul_assoc b, hb]
    ring
  smul_mem' := by
    rintro c x ⟨x', hx⟩
    obtain ⟨c', hc⟩ := IsLocalization.surj M c
    use ⟨c'.1 * x'.1, I.mul_mem_left c'.1 x'.1.2⟩
    use c'.2 * x'.2
    simp only [← hx, ← hc, smul_eq_mul, Submodule.coe_mk, Submonoid.coe_mul, RingHom.map_mul]
    ring
#align is_localization.map_ideal is_localization.map_ideal

theorem mem_map_algebraMap_iff {I : Ideal R} {z} :
    z ∈ Ideal.map (algebraMap R S) I ↔ ∃ x : I × M, z * algebraMap R S x.2 = algebraMap R S x.1 :=
  by
  constructor
  · change _ → z ∈ map_ideal M S I
    refine' fun h => Ideal.mem_infₛ.1 h fun z hz => _
    obtain ⟨y, hy⟩ := hz
    use ⟨⟨⟨y, hy.left⟩, 1⟩, by simp [hy.right]⟩
  · rintro ⟨⟨a, s⟩, h⟩
    rw [← Ideal.unit_mul_mem_iff_mem _ (map_units S s), mul_comm]
    exact h.symm ▸ Ideal.mem_map_of_mem _ a.2
#align is_localization.mem_map_algebra_map_iff IsLocalization.mem_map_algebraMap_iff

theorem map_comap (J : Ideal S) : Ideal.map (algebraMap R S) (Ideal.comap (algebraMap R S) J) = J :=
  le_antisymm (Ideal.map_le_iff_le_comap.2 le_rfl) fun x hJ =>
    by
    obtain ⟨r, s, hx⟩ := mk'_surjective M x
    rw [← hx] at hJ⊢
    exact
      Ideal.mul_mem_right _ _
        (Ideal.mem_map_of_mem _
          (show (algebraMap R S) r ∈ J from
            mk'_spec S r s ▸ J.mul_mem_right ((algebraMap R S) s) hJ))
#align is_localization.map_comap IsLocalization.map_comap

theorem comap_map_of_isPrime_disjoint (I : Ideal R) (hI : I.IsPrime) (hM : Disjoint (M : Set R) I) :
    Ideal.comap (algebraMap R S) (Ideal.map (algebraMap R S) I) = I :=
  by
  refine' le_antisymm (fun a ha => _) Ideal.le_comap_map
  obtain ⟨⟨b, s⟩, h⟩ := (mem_map_algebra_map_iff M S).1 (Ideal.mem_comap.1 ha)
  replace h : algebraMap R S (s * a) = algebraMap R S b := by
    simpa only [← map_mul, mul_comm] using h
  obtain ⟨c, hc⟩ := (eq_iff_exists M S).1 h
  have : ↑c * ↑s * a ∈ I := by
    rw [mul_assoc, hc]
    exact I.mul_mem_left c b.2
  exact (hI.mem_or_mem this).resolve_left fun hsc => hM.le_bot ⟨(c * s).2, hsc⟩
#align is_localization.comap_map_of_is_prime_disjoint IsLocalization.comap_map_of_isPrime_disjoint

/-- If `S` is the localization of `R` at a submonoid, the ordering of ideals of `S` is
embedded in the ordering of ideals of `R`. -/
def orderEmbedding : Ideal S ↪o Ideal R
    where
  toFun J := Ideal.comap (algebraMap R S) J
  inj' := Function.LeftInverse.injective (map_comap M S)
  map_rel_iff' J₁ J₂ :=
    ⟨fun hJ => (map_comap M S) J₁ ▸ (map_comap M S) J₂ ▸ Ideal.map_mono hJ, Ideal.comap_mono⟩
#align is_localization.order_embedding IsLocalization.orderEmbedding

/-- If `R` is a ring, then prime ideals in the localization at `M`
correspond to prime ideals in the original ring `R` that are disjoint from `M`.
This lemma gives the particular case for an ideal and its comap,
see `le_rel_iso_of_prime` for the more general relation isomorphism -/
theorem isPrime_iff_isPrime_disjoint (J : Ideal S) :
    J.IsPrime ↔
      (Ideal.comap (algebraMap R S) J).IsPrime ∧
        Disjoint (M : Set R) ↑(Ideal.comap (algebraMap R S) J) :=
  by
  constructor
  · refine' fun h =>
      ⟨⟨_, _⟩,
        set.disjoint_left.mpr fun m hm1 hm2 =>
          h.ne_top (Ideal.eq_top_of_isUnit_mem _ hm2 (map_units S ⟨m, hm1⟩))⟩
    · refine' fun hJ => h.ne_top _
      rw [eq_top_iff, ← (OrderEmbedding M S).le_iff_le]
      exact le_of_eq hJ.symm
    · intro x y hxy
      rw [Ideal.mem_comap, RingHom.map_mul] at hxy
      exact h.mem_or_mem hxy
  · refine' fun h => ⟨fun hJ => h.left.ne_top (eq_top_iff.2 _), _⟩
    · rwa [eq_top_iff, ← (OrderEmbedding M S).le_iff_le] at hJ
    · intro x y hxy
      obtain ⟨a, s, ha⟩ := mk'_surjective M x
      obtain ⟨b, t, hb⟩ := mk'_surjective M y
      have : mk' S (a * b) (s * t) ∈ J := by rwa [mk'_mul, ha, hb]
      rw [mk'_mem_iff, ← Ideal.mem_comap] at this
      replace this := h.left.mem_or_mem this
      rw [Ideal.mem_comap, Ideal.mem_comap] at this
      rwa [← ha, ← hb, mk'_mem_iff, mk'_mem_iff]
#align is_localization.is_prime_iff_is_prime_disjoint IsLocalization.isPrime_iff_isPrime_disjoint

/-- If `R` is a ring, then prime ideals in the localization at `M`
correspond to prime ideals in the original ring `R` that are disjoint from `M`.
This lemma gives the particular case for an ideal and its map,
see `le_rel_iso_of_prime` for the more general relation isomorphism, and the reverse implication -/
theorem isPrime_of_isPrime_disjoint (I : Ideal R) (hp : I.IsPrime) (hd : Disjoint (M : Set R) ↑I) :
    (Ideal.map (algebraMap R S) I).IsPrime :=
  by
  rw [is_prime_iff_is_prime_disjoint M S, comap_map_of_is_prime_disjoint M S I hp hd]
  exact ⟨hp, hd⟩
#align is_localization.is_prime_of_is_prime_disjoint IsLocalization.isPrime_of_isPrime_disjoint

/-- If `R` is a ring, then prime ideals in the localization at `M`
correspond to prime ideals in the original ring `R` that are disjoint from `M` -/
def orderIsoOfPrime :
    { p : Ideal S // p.IsPrime } ≃o { p : Ideal R // p.IsPrime ∧ Disjoint (M : Set R) ↑p }
    where
  toFun p := ⟨Ideal.comap (algebraMap R S) p.1, (isPrime_iff_isPrime_disjoint M S p.1).1 p.2⟩
  invFun p := ⟨Ideal.map (algebraMap R S) p.1, isPrime_of_isPrime_disjoint M S p.1 p.2.1 p.2.2⟩
  left_inv J := Subtype.eq (map_comap M S J)
  right_inv I := Subtype.eq (comap_map_of_isPrime_disjoint M S I.1 I.2.1 I.2.2)
  map_rel_iff' I I' :=
    ⟨fun h =>
      show I.val ≤ I'.val from map_comap M S I.val ▸ map_comap M S I'.val ▸ Ideal.map_mono h,
      fun h x hx => h hx⟩
#align is_localization.order_iso_of_prime IsLocalization.orderIsoOfPrime

end CommSemiring

section CommRing

variable {R : Type _} [CommRing R] (M : Submonoid R) (S : Type _) [CommRing S]

variable [Algebra R S] [IsLocalization M S]

include M

/-- `quotient_map` applied to maximal ideals of a localization is `surjective`.
  The quotient by a maximal ideal is a field, so inverses to elements already exist,
  and the localization necessarily maps the equivalence class of the inverse in the localization -/
theorem surjective_quotientMap_of_maximal_of_localization {I : Ideal S} [I.IsPrime] {J : Ideal R}
    {H : J ≤ I.comap (algebraMap R S)} (hI : (I.comap (algebraMap R S)).IsMaximal) :
    Function.Surjective (I.quotientMap (algebraMap R S) H) :=
  by
  intro s
  obtain ⟨s, rfl⟩ := Ideal.Quotient.mk_surjective s
  obtain ⟨r, ⟨m, hm⟩, rfl⟩ := mk'_surjective M s
  by_cases hM : (Ideal.Quotient.mk (I.comap (algebraMap R S))) m = 0
  · have : I = ⊤ := by
      rw [Ideal.eq_top_iff_one]
      rw [Ideal.Quotient.eq_zero_iff_mem, Ideal.mem_comap] at hM
      convert I.mul_mem_right (mk' S (1 : R) ⟨m, hm⟩) hM
      rw [← mk'_eq_mul_mk'_one, mk'_self]
    exact ⟨0, eq_comm.1 (by simp [Ideal.Quotient.eq_zero_iff_mem, this])⟩
  · rw [Ideal.Quotient.maximal_ideal_iff_isField_quotient] at hI
    obtain ⟨n, hn⟩ := hI.3 hM
    obtain ⟨rn, rfl⟩ := Ideal.Quotient.mk_surjective n
    refine' ⟨(Ideal.Quotient.mk J) (r * rn), _⟩
    -- The rest of the proof is essentially just algebraic manipulations to prove the equality
    rw [← RingHom.map_mul] at hn
    replace hn := congr_arg (Ideal.quotientMap I (algebraMap R S) le_rfl) hn
    simp only [RingHom.map_one, Ideal.quotientMap_mk, RingHom.map_mul] at hn
    rw [Ideal.quotientMap_mk, ← sub_eq_zero, ← RingHom.map_sub, Ideal.Quotient.eq_zero_iff_mem, ←
      Ideal.Quotient.eq_zero_iff_mem, RingHom.map_sub, sub_eq_zero, mk'_eq_mul_mk'_one]
    simp only [mul_eq_mul_left_iff, RingHom.map_mul]
    exact
      Or.inl
        (mul_left_cancel₀
          (fun hn =>
            hM
              (Ideal.Quotient.eq_zero_iff_mem.2
                (Ideal.mem_comap.2 (Ideal.Quotient.eq_zero_iff_mem.1 hn))))
          (trans hn (by rw [← RingHom.map_mul, ← mk'_eq_mul_mk'_one, mk'_self, RingHom.map_one])))
#align is_localization.surjective_quotient_map_of_maximal_of_localization IsLocalization.surjective_quotientMap_of_maximal_of_localization

open nonZeroDivisors

theorem bot_lt_comap_prime [IsDomain R] (hM : M ≤ R⁰) (p : Ideal S) [hpp : p.IsPrime]
    (hp0 : p ≠ ⊥) : ⊥ < Ideal.comap (algebraMap R S) p :=
  by
  haveI : IsDomain S := is_domain_of_le_non_zero_divisors _ hM
  convert(order_iso_of_prime M S).lt_iff_lt.mpr
      (show (⟨⊥, Ideal.bot_prime⟩ : { p : Ideal S // p.IsPrime }) < ⟨p, hpp⟩ from hp0.bot_lt)
  exact (Ideal.comap_bot_of_injective (algebraMap R S) (IsLocalization.injective _ hM)).symm
#align is_localization.bot_lt_comap_prime IsLocalization.bot_lt_comap_prime

end CommRing

end IsLocalization