/-
Copyright (c) 2020 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker, Sébastien Gouëzel, Yury G. Kudryashov, Dylan MacKenzie, Patrick Massot

! This file was ported from Lean 3 source module analysis.specific_limits.normed
! 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.Algebra.Order.Field.Basic
import Mathbin.Analysis.Asymptotics.Asymptotics
import Mathbin.Analysis.SpecificLimits.Basic

/-!
# A collection of specific limit computations

This file contains important specific limit computations in (semi-)normed groups/rings/spaces, as
as well as such computations in `ℝ` when the natural proof passes through a fact about normed
spaces.

-/

noncomputable section

open Classical Set Function Filter Finset Metric Asymptotics

open Classical Topology Nat BigOperators uniformity NNReal ENNReal

variable {α : Type _} {β : Type _} {ι : Type _}

theorem tendsto_norm_atTop_atTop : Tendsto (norm : ℝ → ℝ) atTop atTop :=
  tendsto_abs_atTop_atTop
#align tendsto_norm_at_top_at_top tendsto_norm_atTop_atTop

theorem summable_of_absolute_convergence_real {f : ℕ → ℝ} :
    (∃ r, Tendsto (fun n => ∑ i in range n, |f i|) atTop (𝓝 r)) → Summable f
  | ⟨r, hr⟩ =>
    by
    refine' summable_of_summable_norm ⟨r, (hasSum_iff_tendsto_nat_of_nonneg _ _).2 _⟩
    exact fun i => norm_nonneg _
    simpa only using hr
#align summable_of_absolute_convergence_real summable_of_absolute_convergence_real

/-! ### Powers -/

theorem tendsto_norm_zero' {𝕜 : Type _} [NormedAddCommGroup 𝕜] :
    Tendsto (norm : 𝕜 → ℝ) (𝓝[≠] 0) (𝓝[>] 0) :=
  tendsto_norm_zero.inf <| tendsto_principal_principal.2 fun x hx => norm_pos_iff.2 hx
#align tendsto_norm_zero' tendsto_norm_zero'

namespace NormedField

theorem tendsto_norm_inverse_nhdsWithin_0_atTop {𝕜 : Type _} [NormedField 𝕜] :
    Tendsto (fun x : 𝕜 => ‖x⁻¹‖) (𝓝[≠] 0) atTop :=
  (tendsto_inv_zero_atTop.comp tendsto_norm_zero').congr fun x => (norm_inv x).symm
#align normed_field.tendsto_norm_inverse_nhds_within_0_at_top NormedField.tendsto_norm_inverse_nhdsWithin_0_atTop

theorem tendsto_norm_zpow_nhdsWithin_0_atTop {𝕜 : Type _} [NormedField 𝕜] {m : ℤ} (hm : m < 0) :
    Tendsto (fun x : 𝕜 => ‖x ^ m‖) (𝓝[≠] 0) atTop :=
  by
  rcases neg_surjective m with ⟨m, rfl⟩
  rw [neg_lt_zero] at hm; lift m to ℕ using hm.le; rw [Int.coe_nat_pos] at hm
  simp only [norm_pow, zpow_neg, zpow_ofNat, ← inv_pow]
  exact (tendsto_pow_at_top hm.ne').comp NormedField.tendsto_norm_inverse_nhdsWithin_0_atTop
#align normed_field.tendsto_norm_zpow_nhds_within_0_at_top NormedField.tendsto_norm_zpow_nhdsWithin_0_atTop

/-- The (scalar) product of a sequence that tends to zero with a bounded one also tends to zero. -/
theorem tendsto_zero_smul_of_tendsto_zero_of_bounded {ι 𝕜 𝔸 : Type _} [NormedField 𝕜]
    [NormedAddCommGroup 𝔸] [NormedSpace 𝕜 𝔸] {l : Filter ι} {ε : ι → 𝕜} {f : ι → 𝔸}
    (hε : Tendsto ε l (𝓝 0)) (hf : Filter.IsBoundedUnder (· ≤ ·) l (norm ∘ f)) :
    Tendsto (ε • f) l (𝓝 0) := by
  rw [← is_o_one_iff 𝕜] at hε⊢
  simpa using is_o.smul_is_O hε (hf.is_O_const (one_ne_zero : (1 : 𝕜) ≠ 0))
#align normed_field.tendsto_zero_smul_of_tendsto_zero_of_bounded NormedField.tendsto_zero_smul_of_tendsto_zero_of_bounded

@[simp]
theorem continuousAt_zpow {𝕜 : Type _} [NontriviallyNormedField 𝕜] {m : ℤ} {x : 𝕜} :
    ContinuousAt (fun x => x ^ m) x ↔ x ≠ 0 ∨ 0 ≤ m :=
  by
  refine' ⟨_, continuousAt_zpow₀ _ _⟩
  contrapose!; rintro ⟨rfl, hm⟩ hc
  exact
    not_tendsto_atTop_of_tendsto_nhds (hc.tendsto.mono_left nhdsWithin_le_nhds).norm
      (tendsto_norm_zpow_nhds_within_0_at_top hm)
#align normed_field.continuous_at_zpow NormedField.continuousAt_zpow

@[simp]
theorem continuousAt_inv {𝕜 : Type _} [NontriviallyNormedField 𝕜] {x : 𝕜} :
    ContinuousAt Inv.inv x ↔ x ≠ 0 := by
  simpa [(zero_lt_one' ℤ).not_le] using @continuousAt_zpow _ _ (-1) x
#align normed_field.continuous_at_inv NormedField.continuousAt_inv

end NormedField

theorem isOCat_pow_pow_of_lt_left {r₁ r₂ : ℝ} (h₁ : 0 ≤ r₁) (h₂ : r₁ < r₂) :
    (fun n : ℕ => r₁ ^ n) =o[atTop] fun n => r₂ ^ n :=
  have H : 0 < r₂ := h₁.trans_lt h₂
  (isOCat_of_tendsto fun n hn => False.elim <| H.ne' <| pow_eq_zero hn) <|
    (tendsto_pow_atTop_nhds_0_of_lt_1 (div_nonneg h₁ (h₁.trans h₂.le)) ((div_lt_one H).2 h₂)).congr
      fun n => div_pow _ _ _
#align is_o_pow_pow_of_lt_left isOCat_pow_pow_of_lt_left

theorem isO_pow_pow_of_le_left {r₁ r₂ : ℝ} (h₁ : 0 ≤ r₁) (h₂ : r₁ ≤ r₂) :
    (fun n : ℕ => r₁ ^ n) =O[atTop] fun n => r₂ ^ n :=
  h₂.eq_or_lt.elim (fun h => h ▸ isO_refl _ _) fun h => (isOCat_pow_pow_of_lt_left h₁ h).IsO
#align is_O_pow_pow_of_le_left isO_pow_pow_of_le_left

theorem isOCat_pow_pow_of_abs_lt_left {r₁ r₂ : ℝ} (h : |r₁| < |r₂|) :
    (fun n : ℕ => r₁ ^ n) =o[atTop] fun n => r₂ ^ n :=
  by
  refine' (is_o.of_norm_left _).of_norm_right
  exact (isOCat_pow_pow_of_lt_left (abs_nonneg r₁) h).congr (pow_abs r₁) (pow_abs r₂)
#align is_o_pow_pow_of_abs_lt_left isOCat_pow_pow_of_abs_lt_left

/-- Various statements equivalent to the fact that `f n` grows exponentially slower than `R ^ n`.

* 0: $f n = o(a ^ n)$ for some $-R < a < R$;
* 1: $f n = o(a ^ n)$ for some $0 < a < R$;
* 2: $f n = O(a ^ n)$ for some $-R < a < R$;
* 3: $f n = O(a ^ n)$ for some $0 < a < R$;
* 4: there exist `a < R` and `C` such that one of `C` and `R` is positive and $|f n| ≤ Ca^n$
     for all `n`;
* 5: there exists `0 < a < R` and a positive `C` such that $|f n| ≤ Ca^n$ for all `n`;
* 6: there exists `a < R` such that $|f n| ≤ a ^ n$ for sufficiently large `n`;
* 7: there exists `0 < a < R` such that $|f n| ≤ a ^ n$ for sufficiently large `n`.

NB: For backwards compatibility, if you add more items to the list, please append them at the end of
the list. -/
theorem tFAE_exists_lt_isOCat_pow (f : ℕ → ℝ) (R : ℝ) :
    TFAE
      [∃ a ∈ Ioo (-R) R, f =o[atTop] pow a, ∃ a ∈ Ioo 0 R, f =o[atTop] pow a,
        ∃ a ∈ Ioo (-R) R, f =O[atTop] pow a, ∃ a ∈ Ioo 0 R, f =O[atTop] pow a,
        ∃ a < R, ∃ (C : _)(h₀ : 0 < C ∨ 0 < R), ∀ n, |f n| ≤ C * a ^ n,
        ∃ a ∈ Ioo 0 R, ∃ C > 0, ∀ n, |f n| ≤ C * a ^ n, ∃ a < R, ∀ᶠ n in atTop, |f n| ≤ a ^ n,
        ∃ a ∈ Ioo 0 R, ∀ᶠ n in atTop, |f n| ≤ a ^ n] :=
  by
  have A : Ico 0 R ⊆ Ioo (-R) R := fun x hx =>
    ⟨(neg_lt_zero.2 (hx.1.trans_lt hx.2)).trans_le hx.1, hx.2⟩
  have B : Ioo 0 R ⊆ Ioo (-R) R := subset.trans Ioo_subset_Ico_self A
  -- First we prove that 1-4 are equivalent using 2 → 3 → 4, 1 → 3, and 2 → 1
  tfae_have 1 → 3
  exact fun ⟨a, ha, H⟩ => ⟨a, ha, H.IsO⟩
  tfae_have 2 → 1
  exact fun ⟨a, ha, H⟩ => ⟨a, B ha, H⟩
  tfae_have 3 → 2
  · rintro ⟨a, ha, H⟩
    rcases exists_between (abs_lt.2 ha) with ⟨b, hab, hbR⟩
    exact
      ⟨b, ⟨(abs_nonneg a).trans_lt hab, hbR⟩,
        H.trans_is_o (isOCat_pow_pow_of_abs_lt_left (hab.trans_le (le_abs_self b)))⟩
  tfae_have 2 → 4
  exact fun ⟨a, ha, H⟩ => ⟨a, ha, H.IsO⟩
  tfae_have 4 → 3
  exact fun ⟨a, ha, H⟩ => ⟨a, B ha, H⟩
  -- Add 5 and 6 using 4 → 6 → 5 → 3
  tfae_have 4 → 6
  · rintro ⟨a, ha, H⟩
    rcases bound_of_is_O_nat_at_top H with ⟨C, hC₀, hC⟩
    refine' ⟨a, ha, C, hC₀, fun n => _⟩
    simpa only [Real.norm_eq_abs, abs_pow, abs_of_nonneg ha.1.le] using hC (pow_ne_zero n ha.1.ne')
  tfae_have 6 → 5
  exact fun ⟨a, ha, C, H₀, H⟩ => ⟨a, ha.2, C, Or.inl H₀, H⟩
  tfae_have 5 → 3
  · rintro ⟨a, ha, C, h₀, H⟩
    rcases sign_cases_of_C_mul_pow_nonneg fun n => (abs_nonneg _).trans (H n) with
      (rfl | ⟨hC₀, ha₀⟩)
    · obtain rfl : f = 0 := by
        ext n
        simpa using H n
      simp only [lt_irrefl, false_or_iff] at h₀
      exact ⟨0, ⟨neg_lt_zero.2 h₀, h₀⟩, is_O_zero _ _⟩
    exact
      ⟨a, A ⟨ha₀, ha⟩,
        is_O_of_le' _ fun n => (H n).trans <| mul_le_mul_of_nonneg_left (le_abs_self _) hC₀.le⟩
  -- Add 7 and 8 using 2 → 8 → 7 → 3
  tfae_have 2 → 8
  · rintro ⟨a, ha, H⟩
    refine' ⟨a, ha, (H.def zero_lt_one).mono fun n hn => _⟩
    rwa [Real.norm_eq_abs, Real.norm_eq_abs, one_mul, abs_pow, abs_of_pos ha.1] at hn
  tfae_have 8 → 7
  exact fun ⟨a, ha, H⟩ => ⟨a, ha.2, H⟩
  tfae_have 7 → 3
  · rintro ⟨a, ha, H⟩
    have : 0 ≤ a := nonneg_of_eventually_pow_nonneg (H.mono fun n => (abs_nonneg _).trans)
    refine' ⟨a, A ⟨this, ha⟩, is_O.of_bound 1 _⟩
    simpa only [Real.norm_eq_abs, one_mul, abs_pow, abs_of_nonneg this]
  tfae_finish
#align tfae_exists_lt_is_o_pow tFAE_exists_lt_isOCat_pow

/-- For any natural `k` and a real `r > 1` we have `n ^ k = o(r ^ n)` as `n → ∞`. -/
theorem isOCat_pow_const_const_pow_of_one_lt {R : Type _} [NormedRing R] (k : ℕ) {r : ℝ}
    (hr : 1 < r) : (fun n => n ^ k : ℕ → R) =o[atTop] fun n => r ^ n :=
  by
  have : tendsto (fun x : ℝ => x ^ k) (𝓝[>] 1) (𝓝 1) :=
    ((continuous_id.pow k).tendsto' (1 : ℝ) 1 (one_pow _)).mono_left inf_le_left
  obtain ⟨r' : ℝ, hr' : r' ^ k < r, h1 : 1 < r'⟩ :=
    ((this.eventually (gt_mem_nhds hr)).And self_mem_nhdsWithin).exists
  have h0 : 0 ≤ r' := zero_le_one.trans h1.le
  suffices : (fun n => n ^ k : ℕ → R) =O[at_top] fun n : ℕ => (r' ^ k) ^ n
  exact this.trans_is_o (isOCat_pow_pow_of_lt_left (pow_nonneg h0 _) hr')
  conv in (r' ^ _) ^ _ => rw [← pow_mul, mul_comm, pow_mul]
  suffices : ∀ n : ℕ, ‖(n : R)‖ ≤ (r' - 1)⁻¹ * ‖(1 : R)‖ * ‖r' ^ n‖
  exact (is_O_of_le' _ this).pow _
  intro n
  rw [mul_right_comm]
  refine' n.norm_cast_le.trans (mul_le_mul_of_nonneg_right _ (norm_nonneg _))
  simpa [div_eq_inv_mul, Real.norm_eq_abs, abs_of_nonneg h0] using n.cast_le_pow_div_sub h1
#align is_o_pow_const_const_pow_of_one_lt isOCat_pow_const_const_pow_of_one_lt

/-- For a real `r > 1` we have `n = o(r ^ n)` as `n → ∞`. -/
theorem isOCat_coe_const_pow_of_one_lt {R : Type _} [NormedRing R] {r : ℝ} (hr : 1 < r) :
    (coe : ℕ → R) =o[atTop] fun n => r ^ n := by
  simpa only [pow_one] using @isOCat_pow_const_const_pow_of_one_lt R _ 1 _ hr
#align is_o_coe_const_pow_of_one_lt isOCat_coe_const_pow_of_one_lt

/-- If `‖r₁‖ < r₂`, then for any naturak `k` we have `n ^ k r₁ ^ n = o (r₂ ^ n)` as `n → ∞`. -/
theorem isOCat_pow_const_mul_const_pow_const_pow_of_norm_lt {R : Type _} [NormedRing R] (k : ℕ)
    {r₁ : R} {r₂ : ℝ} (h : ‖r₁‖ < r₂) :
    (fun n => n ^ k * r₁ ^ n : ℕ → R) =o[atTop] fun n => r₂ ^ n :=
  by
  by_cases h0 : r₁ = 0
  · refine' (is_o_zero _ _).congr' (mem_at_top_sets.2 <| ⟨1, fun n hn => _⟩) eventually_eq.rfl
    simp [zero_pow (zero_lt_one.trans_le hn), h0]
  rw [← Ne.def, ← norm_pos_iff] at h0
  have A : (fun n => n ^ k : ℕ → R) =o[at_top] fun n => (r₂ / ‖r₁‖) ^ n :=
    isOCat_pow_const_const_pow_of_one_lt k ((one_lt_div h0).2 h)
  suffices (fun n => r₁ ^ n) =O[at_top] fun n => ‖r₁‖ ^ n by
    simpa [div_mul_cancel _ (pow_pos h0 _).ne'] using A.mul_is_O this
  exact is_O.of_bound 1 (by simpa using eventually_norm_pow_le r₁)
#align is_o_pow_const_mul_const_pow_const_pow_of_norm_lt isOCat_pow_const_mul_const_pow_const_pow_of_norm_lt

theorem tendsto_pow_const_div_const_pow_of_one_lt (k : ℕ) {r : ℝ} (hr : 1 < r) :
    Tendsto (fun n => n ^ k / r ^ n : ℕ → ℝ) atTop (𝓝 0) :=
  (isOCat_pow_const_const_pow_of_one_lt k hr).tendsto_div_nhds_zero
#align tendsto_pow_const_div_const_pow_of_one_lt tendsto_pow_const_div_const_pow_of_one_lt

/-- If `|r| < 1`, then `n ^ k r ^ n` tends to zero for any natural `k`. -/
theorem tendsto_pow_const_mul_const_pow_of_abs_lt_one (k : ℕ) {r : ℝ} (hr : |r| < 1) :
    Tendsto (fun n => n ^ k * r ^ n : ℕ → ℝ) atTop (𝓝 0) :=
  by
  by_cases h0 : r = 0
  ·
    exact
      tendsto_const_nhds.congr'
        (mem_at_top_sets.2 ⟨1, fun n hn => by simp [zero_lt_one.trans_le hn, h0]⟩)
  have hr' : 1 < (|r|)⁻¹ := one_lt_inv (abs_pos.2 h0) hr
  rw [tendsto_zero_iff_norm_tendsto_zero]
  simpa [div_eq_mul_inv] using tendsto_pow_const_div_const_pow_of_one_lt k hr'
#align tendsto_pow_const_mul_const_pow_of_abs_lt_one tendsto_pow_const_mul_const_pow_of_abs_lt_one

/-- If `0 ≤ r < 1`, then `n ^ k r ^ n` tends to zero for any natural `k`.
This is a specialized version of `tendsto_pow_const_mul_const_pow_of_abs_lt_one`, singled out
for ease of application. -/
theorem tendsto_pow_const_mul_const_pow_of_lt_one (k : ℕ) {r : ℝ} (hr : 0 ≤ r) (h'r : r < 1) :
    Tendsto (fun n => n ^ k * r ^ n : ℕ → ℝ) atTop (𝓝 0) :=
  tendsto_pow_const_mul_const_pow_of_abs_lt_one k (abs_lt.2 ⟨neg_one_lt_zero.trans_le hr, h'r⟩)
#align tendsto_pow_const_mul_const_pow_of_lt_one tendsto_pow_const_mul_const_pow_of_lt_one

/-- If `|r| < 1`, then `n * r ^ n` tends to zero. -/
theorem tendsto_self_mul_const_pow_of_abs_lt_one {r : ℝ} (hr : |r| < 1) :
    Tendsto (fun n => n * r ^ n : ℕ → ℝ) atTop (𝓝 0) := by
  simpa only [pow_one] using tendsto_pow_const_mul_const_pow_of_abs_lt_one 1 hr
#align tendsto_self_mul_const_pow_of_abs_lt_one tendsto_self_mul_const_pow_of_abs_lt_one

/-- If `0 ≤ r < 1`, then `n * r ^ n` tends to zero. This is a specialized version of
`tendsto_self_mul_const_pow_of_abs_lt_one`, singled out for ease of application. -/
theorem tendsto_self_mul_const_pow_of_lt_one {r : ℝ} (hr : 0 ≤ r) (h'r : r < 1) :
    Tendsto (fun n => n * r ^ n : ℕ → ℝ) atTop (𝓝 0) := by
  simpa only [pow_one] using tendsto_pow_const_mul_const_pow_of_lt_one 1 hr h'r
#align tendsto_self_mul_const_pow_of_lt_one tendsto_self_mul_const_pow_of_lt_one

/-- In a normed ring, the powers of an element x with `‖x‖ < 1` tend to zero. -/
theorem tendsto_pow_atTop_nhds_0_of_norm_lt_1 {R : Type _} [NormedRing R] {x : R} (h : ‖x‖ < 1) :
    Tendsto (fun n : ℕ => x ^ n) atTop (𝓝 0) :=
  by
  apply squeeze_zero_norm' (eventually_norm_pow_le x)
  exact tendsto_pow_atTop_nhds_0_of_lt_1 (norm_nonneg _) h
#align tendsto_pow_at_top_nhds_0_of_norm_lt_1 tendsto_pow_atTop_nhds_0_of_norm_lt_1

theorem tendsto_pow_atTop_nhds_0_of_abs_lt_1 {r : ℝ} (h : |r| < 1) :
    Tendsto (fun n : ℕ => r ^ n) atTop (𝓝 0) :=
  tendsto_pow_atTop_nhds_0_of_norm_lt_1 h
#align tendsto_pow_at_top_nhds_0_of_abs_lt_1 tendsto_pow_atTop_nhds_0_of_abs_lt_1

/-! ### Geometric series-/

section Geometric

variable {K : Type _} [NormedField K] {ξ : K}

theorem hasSum_geometric_of_norm_lt_1 (h : ‖ξ‖ < 1) : HasSum (fun n : ℕ => ξ ^ n) (1 - ξ)⁻¹ :=
  by
  have xi_ne_one : ξ ≠ 1 := by
    contrapose! h
    simp [h]
  have A : tendsto (fun n => (ξ ^ n - 1) * (ξ - 1)⁻¹) at_top (𝓝 ((0 - 1) * (ξ - 1)⁻¹)) :=
    ((tendsto_pow_atTop_nhds_0_of_norm_lt_1 h).sub tendsto_const_nhds).mul tendsto_const_nhds
  rw [hasSum_iff_tendsto_nat_of_summable_norm]
  · simpa [geom_sum_eq, xi_ne_one, neg_inv, div_eq_mul_inv] using A
  · simp [norm_pow, summable_geometric_of_lt_1 (norm_nonneg _) h]
#align has_sum_geometric_of_norm_lt_1 hasSum_geometric_of_norm_lt_1

theorem summable_geometric_of_norm_lt_1 (h : ‖ξ‖ < 1) : Summable fun n : ℕ => ξ ^ n :=
  ⟨_, hasSum_geometric_of_norm_lt_1 h⟩
#align summable_geometric_of_norm_lt_1 summable_geometric_of_norm_lt_1

theorem tsum_geometric_of_norm_lt_1 (h : ‖ξ‖ < 1) : (∑' n : ℕ, ξ ^ n) = (1 - ξ)⁻¹ :=
  (hasSum_geometric_of_norm_lt_1 h).tsum_eq
#align tsum_geometric_of_norm_lt_1 tsum_geometric_of_norm_lt_1

theorem hasSum_geometric_of_abs_lt_1 {r : ℝ} (h : |r| < 1) :
    HasSum (fun n : ℕ => r ^ n) (1 - r)⁻¹ :=
  hasSum_geometric_of_norm_lt_1 h
#align has_sum_geometric_of_abs_lt_1 hasSum_geometric_of_abs_lt_1

theorem summable_geometric_of_abs_lt_1 {r : ℝ} (h : |r| < 1) : Summable fun n : ℕ => r ^ n :=
  summable_geometric_of_norm_lt_1 h
#align summable_geometric_of_abs_lt_1 summable_geometric_of_abs_lt_1

theorem tsum_geometric_of_abs_lt_1 {r : ℝ} (h : |r| < 1) : (∑' n : ℕ, r ^ n) = (1 - r)⁻¹ :=
  tsum_geometric_of_norm_lt_1 h
#align tsum_geometric_of_abs_lt_1 tsum_geometric_of_abs_lt_1

/-- A geometric series in a normed field is summable iff the norm of the common ratio is less than
one. -/
@[simp]
theorem summable_geometric_iff_norm_lt_1 : (Summable fun n : ℕ => ξ ^ n) ↔ ‖ξ‖ < 1 :=
  by
  refine' ⟨fun h => _, summable_geometric_of_norm_lt_1⟩
  obtain ⟨k : ℕ, hk : dist (ξ ^ k) 0 < 1⟩ :=
    (h.tendsto_cofinite_zero.eventually (ball_mem_nhds _ zero_lt_one)).exists
  simp only [norm_pow, dist_zero_right] at hk
  rw [← one_pow k] at hk
  exact lt_of_pow_lt_pow _ zero_le_one hk
#align summable_geometric_iff_norm_lt_1 summable_geometric_iff_norm_lt_1

end Geometric

section MulGeometric

theorem summable_norm_pow_mul_geometric_of_norm_lt_1 {R : Type _} [NormedRing R] (k : ℕ) {r : R}
    (hr : ‖r‖ < 1) : Summable fun n : ℕ => ‖(n ^ k * r ^ n : R)‖ :=
  by
  rcases exists_between hr with ⟨r', hrr', h⟩
  exact
    summable_of_isO_nat (summable_geometric_of_lt_1 ((norm_nonneg _).trans hrr'.le) h)
      (isOCat_pow_const_mul_const_pow_const_pow_of_norm_lt _ hrr').IsO.norm_left
#align summable_norm_pow_mul_geometric_of_norm_lt_1 summable_norm_pow_mul_geometric_of_norm_lt_1

theorem summable_pow_mul_geometric_of_norm_lt_1 {R : Type _} [NormedRing R] [CompleteSpace R]
    (k : ℕ) {r : R} (hr : ‖r‖ < 1) : Summable (fun n => n ^ k * r ^ n : ℕ → R) :=
  summable_of_summable_norm <| summable_norm_pow_mul_geometric_of_norm_lt_1 _ hr
#align summable_pow_mul_geometric_of_norm_lt_1 summable_pow_mul_geometric_of_norm_lt_1

/-- If `‖r‖ < 1`, then `∑' n : ℕ, n * r ^ n = r / (1 - r) ^ 2`, `has_sum` version. -/
theorem hasSum_coe_mul_geometric_of_norm_lt_1 {𝕜 : Type _} [NormedField 𝕜] [CompleteSpace 𝕜] {r : 𝕜}
    (hr : ‖r‖ < 1) : HasSum (fun n => n * r ^ n : ℕ → 𝕜) (r / (1 - r) ^ 2) :=
  by
  have A : Summable (fun n => n * r ^ n : ℕ → 𝕜) := by
    simpa using summable_pow_mul_geometric_of_norm_lt_1 1 hr
  have B : HasSum (pow r : ℕ → 𝕜) (1 - r)⁻¹ := hasSum_geometric_of_norm_lt_1 hr
  refine' A.has_sum_iff.2 _
  have hr' : r ≠ 1 := by
    rintro rfl
    simpa [lt_irrefl] using hr
  set s : 𝕜 := ∑' n : ℕ, n * r ^ n
  calc
    s = (1 - r) * s / (1 - r) := (mul_div_cancel_left _ (sub_ne_zero.2 hr'.symm)).symm
    _ = (s - r * s) / (1 - r) := by rw [sub_mul, one_mul]
    _ = (((0 : ℕ) * r ^ 0 + ∑' n : ℕ, (n + 1 : ℕ) * r ^ (n + 1)) - r * s) / (1 - r) := by
      rw [← tsum_eq_zero_add A]
    _ = ((r * ∑' n : ℕ, (n + 1) * r ^ n) - r * s) / (1 - r) := by
      simp [pow_succ, mul_left_comm _ r, tsum_mul_left]
    _ = r / (1 - r) ^ 2 := by
      simp [add_mul, tsum_add A B.summable, mul_add, B.tsum_eq, ← div_eq_mul_inv, sq, div_div]

#align has_sum_coe_mul_geometric_of_norm_lt_1 hasSum_coe_mul_geometric_of_norm_lt_1

/-- If `‖r‖ < 1`, then `∑' n : ℕ, n * r ^ n = r / (1 - r) ^ 2`. -/
theorem tsum_coe_mul_geometric_of_norm_lt_1 {𝕜 : Type _} [NormedField 𝕜] [CompleteSpace 𝕜] {r : 𝕜}
    (hr : ‖r‖ < 1) : (∑' n : ℕ, n * r ^ n : 𝕜) = r / (1 - r) ^ 2 :=
  (hasSum_coe_mul_geometric_of_norm_lt_1 hr).tsum_eq
#align tsum_coe_mul_geometric_of_norm_lt_1 tsum_coe_mul_geometric_of_norm_lt_1

end MulGeometric

section SummableLeGeometric

variable [SeminormedAddCommGroup α] {r C : ℝ} {f : ℕ → α}

theorem SeminormedAddCommGroup.cauchySeq_of_le_geometric {C : ℝ} {r : ℝ} (hr : r < 1) {u : ℕ → α}
    (h : ∀ n, ‖u n - u (n + 1)‖ ≤ C * r ^ n) : CauchySeq u :=
  cauchySeq_of_le_geometric r C hr (by simpa [dist_eq_norm] using h)
#align seminormed_add_comm_group.cauchy_seq_of_le_geometric SeminormedAddCommGroup.cauchySeq_of_le_geometric

theorem dist_partial_sum_le_of_le_geometric (hf : ∀ n, ‖f n‖ ≤ C * r ^ n) (n : ℕ) :
    dist (∑ i in range n, f i) (∑ i in range (n + 1), f i) ≤ C * r ^ n :=
  by
  rw [sum_range_succ, dist_eq_norm, ← norm_neg, neg_sub, add_sub_cancel']
  exact hf n
#align dist_partial_sum_le_of_le_geometric dist_partial_sum_le_of_le_geometric

/-- If `‖f n‖ ≤ C * r ^ n` for all `n : ℕ` and some `r < 1`, then the partial sums of `f` form a
Cauchy sequence. This lemma does not assume `0 ≤ r` or `0 ≤ C`. -/
theorem cauchySeq_finset_of_geometric_bound (hr : r < 1) (hf : ∀ n, ‖f n‖ ≤ C * r ^ n) :
    CauchySeq fun s : Finset ℕ => ∑ x in s, f x :=
  cauchySeq_finset_of_norm_bounded _
    (aux_hasSum_of_le_geometric hr (dist_partial_sum_le_of_le_geometric hf)).Summable hf
#align cauchy_seq_finset_of_geometric_bound cauchySeq_finset_of_geometric_bound

/-- If `‖f n‖ ≤ C * r ^ n` for all `n : ℕ` and some `r < 1`, then the partial sums of `f` are within
distance `C * r ^ n / (1 - r)` of the sum of the series. This lemma does not assume `0 ≤ r` or
`0 ≤ C`. -/
theorem norm_sub_le_of_geometric_bound_of_hasSum (hr : r < 1) (hf : ∀ n, ‖f n‖ ≤ C * r ^ n) {a : α}
    (ha : HasSum f a) (n : ℕ) : ‖(∑ x in Finset.range n, f x) - a‖ ≤ C * r ^ n / (1 - r) :=
  by
  rw [← dist_eq_norm]
  apply dist_le_of_le_geometric_of_tendsto r C hr (dist_partial_sum_le_of_le_geometric hf)
  exact ha.tendsto_sum_nat
#align norm_sub_le_of_geometric_bound_of_has_sum norm_sub_le_of_geometric_bound_of_hasSum

@[simp]
theorem dist_partial_sum (u : ℕ → α) (n : ℕ) :
    dist (∑ k in range (n + 1), u k) (∑ k in range n, u k) = ‖u n‖ := by
  simp [dist_eq_norm, sum_range_succ]
#align dist_partial_sum dist_partial_sum

@[simp]
theorem dist_partial_sum' (u : ℕ → α) (n : ℕ) :
    dist (∑ k in range n, u k) (∑ k in range (n + 1), u k) = ‖u n‖ := by
  simp [dist_eq_norm', sum_range_succ]
#align dist_partial_sum' dist_partial_sum'

theorem cauchy_series_of_le_geometric {C : ℝ} {u : ℕ → α} {r : ℝ} (hr : r < 1)
    (h : ∀ n, ‖u n‖ ≤ C * r ^ n) : CauchySeq fun n => ∑ k in range n, u k :=
  cauchySeq_of_le_geometric r C hr (by simp [h])
#align cauchy_series_of_le_geometric cauchy_series_of_le_geometric

theorem NormedAddCommGroup.cauchy_series_of_le_geometric' {C : ℝ} {u : ℕ → α} {r : ℝ} (hr : r < 1)
    (h : ∀ n, ‖u n‖ ≤ C * r ^ n) : CauchySeq fun n => ∑ k in range (n + 1), u k :=
  (cauchy_series_of_le_geometric hr h).comp_tendsto <| tendsto_add_atTop_nat 1
#align normed_add_comm_group.cauchy_series_of_le_geometric' NormedAddCommGroup.cauchy_series_of_le_geometric'

theorem NormedAddCommGroup.cauchy_series_of_le_geometric'' {C : ℝ} {u : ℕ → α} {N : ℕ} {r : ℝ}
    (hr₀ : 0 < r) (hr₁ : r < 1) (h : ∀ n ≥ N, ‖u n‖ ≤ C * r ^ n) :
    CauchySeq fun n => ∑ k in range (n + 1), u k :=
  by
  set v : ℕ → α := fun n => if n < N then 0 else u n
  have hC : 0 ≤ C :=
    (zero_le_mul_right <| pow_pos hr₀ N).mp ((norm_nonneg _).trans <| h N <| le_refl N)
  have : ∀ n ≥ N, u n = v n := by
    intro n hn
    simp [v, hn, if_neg (not_lt.mpr hn)]
  refine'
    cauchySeq_sum_of_eventually_eq this (NormedAddCommGroup.cauchy_series_of_le_geometric' hr₁ _)
  · exact C
  intro n
  dsimp [v]
  split_ifs with H H
  · rw [norm_zero]
    exact mul_nonneg hC (pow_nonneg hr₀.le _)
  · push_neg  at H
    exact h _ H
#align normed_add_comm_group.cauchy_series_of_le_geometric'' NormedAddCommGroup.cauchy_series_of_le_geometric''

end SummableLeGeometric

section NormedRingGeometric

variable {R : Type _} [NormedRing R] [CompleteSpace R]

open NormedSpace

/-- A geometric series in a complete normed ring is summable.
Proved above (same name, different namespace) for not-necessarily-complete normed fields. -/
theorem NormedRing.summable_geometric_of_norm_lt_1 (x : R) (h : ‖x‖ < 1) :
    Summable fun n : ℕ => x ^ n :=
  by
  have h1 : Summable fun n : ℕ => ‖x‖ ^ n := summable_geometric_of_lt_1 (norm_nonneg _) h
  refine' summable_of_norm_bounded_eventually _ h1 _
  rw [Nat.cofinite_eq_atTop]
  exact eventually_norm_pow_le x
#align normed_ring.summable_geometric_of_norm_lt_1 NormedRing.summable_geometric_of_norm_lt_1

/-- Bound for the sum of a geometric series in a normed ring.  This formula does not assume that the
normed ring satisfies the axiom `‖1‖ = 1`. -/
theorem NormedRing.tsum_geometric_of_norm_lt_1 (x : R) (h : ‖x‖ < 1) :
    ‖∑' n : ℕ, x ^ n‖ ≤ ‖(1 : R)‖ - 1 + (1 - ‖x‖)⁻¹ :=
  by
  rw [tsum_eq_zero_add (NormedRing.summable_geometric_of_norm_lt_1 x h)]
  simp only [pow_zero]
  refine' le_trans (norm_add_le _ _) _
  have : ‖∑' b : ℕ, (fun n => x ^ (n + 1)) b‖ ≤ (1 - ‖x‖)⁻¹ - 1 :=
    by
    refine' tsum_of_norm_bounded _ fun b => norm_pow_le' _ (Nat.succ_pos b)
    convert(hasSum_nat_add_iff' 1).mpr (hasSum_geometric_of_lt_1 (norm_nonneg x) h)
    simp
  linarith
#align normed_ring.tsum_geometric_of_norm_lt_1 NormedRing.tsum_geometric_of_norm_lt_1

theorem geom_series_mul_neg (x : R) (h : ‖x‖ < 1) : (∑' i : ℕ, x ^ i) * (1 - x) = 1 :=
  by
  have := (NormedRing.summable_geometric_of_norm_lt_1 x h).HasSum.mul_right (1 - x)
  refine' tendsto_nhds_unique this.tendsto_sum_nat _
  have : tendsto (fun n : ℕ => 1 - x ^ n) at_top (𝓝 1) := by
    simpa using tendsto_const_nhds.sub (tendsto_pow_atTop_nhds_0_of_norm_lt_1 h)
  convert← this
  ext n
  rw [← geom_sum_mul_neg, Finset.sum_mul]
#align geom_series_mul_neg geom_series_mul_neg

theorem mul_neg_geom_series (x : R) (h : ‖x‖ < 1) : ((1 - x) * ∑' i : ℕ, x ^ i) = 1 :=
  by
  have := (NormedRing.summable_geometric_of_norm_lt_1 x h).HasSum.mul_left (1 - x)
  refine' tendsto_nhds_unique this.tendsto_sum_nat _
  have : tendsto (fun n : ℕ => 1 - x ^ n) at_top (nhds 1) := by
    simpa using tendsto_const_nhds.sub (tendsto_pow_atTop_nhds_0_of_norm_lt_1 h)
  convert← this
  ext n
  rw [← mul_neg_geom_sum, Finset.mul_sum]
#align mul_neg_geom_series mul_neg_geom_series

end NormedRingGeometric

/-! ### Summability tests based on comparison with geometric series -/

theorem summable_of_ratio_norm_eventually_le {α : Type _} [SeminormedAddCommGroup α]
    [CompleteSpace α] {f : ℕ → α} {r : ℝ} (hr₁ : r < 1)
    (h : ∀ᶠ n in atTop, ‖f (n + 1)‖ ≤ r * ‖f n‖) : Summable f :=
  by
  by_cases hr₀ : 0 ≤ r
  · rw [eventually_at_top] at h
    rcases h with ⟨N, hN⟩
    rw [← @summable_nat_add_iff α _ _ _ _ N]
    refine'
      summable_of_norm_bounded (fun n => ‖f N‖ * r ^ n)
        (Summable.mul_left _ <| summable_geometric_of_lt_1 hr₀ hr₁) fun n => _
    conv_rhs => rw [mul_comm, ← zero_add N]
    refine' le_geom hr₀ n fun i _ => _
    convert hN (i + N) (N.le_add_left i) using 3
    ac_rfl
  · push_neg  at hr₀
    refine' summable_of_norm_bounded_eventually 0 summable_zero _
    rw [Nat.cofinite_eq_atTop]
    filter_upwards [h]with _ hn
    by_contra' h
    exact not_lt.mpr (norm_nonneg _) (lt_of_le_of_lt hn <| mul_neg_of_neg_of_pos hr₀ h)
#align summable_of_ratio_norm_eventually_le summable_of_ratio_norm_eventually_le

theorem summable_of_ratio_test_tendsto_lt_one {α : Type _} [NormedAddCommGroup α] [CompleteSpace α]
    {f : ℕ → α} {l : ℝ} (hl₁ : l < 1) (hf : ∀ᶠ n in atTop, f n ≠ 0)
    (h : Tendsto (fun n => ‖f (n + 1)‖ / ‖f n‖) atTop (𝓝 l)) : Summable f :=
  by
  rcases exists_between hl₁ with ⟨r, hr₀, hr₁⟩
  refine' summable_of_ratio_norm_eventually_le hr₁ _
  filter_upwards [eventually_le_of_tendsto_lt hr₀ h, hf]with _ _ h₁
  rwa [← div_le_iff (norm_pos_iff.mpr h₁)]
#align summable_of_ratio_test_tendsto_lt_one summable_of_ratio_test_tendsto_lt_one

theorem not_summable_of_ratio_norm_eventually_ge {α : Type _} [SeminormedAddCommGroup α] {f : ℕ → α}
    {r : ℝ} (hr : 1 < r) (hf : ∃ᶠ n in atTop, ‖f n‖ ≠ 0)
    (h : ∀ᶠ n in atTop, r * ‖f n‖ ≤ ‖f (n + 1)‖) : ¬Summable f :=
  by
  rw [eventually_at_top] at h
  rcases h with ⟨N₀, hN₀⟩
  rw [frequently_at_top] at hf
  rcases hf N₀ with ⟨N, hNN₀ : N₀ ≤ N, hN⟩
  rw [← @summable_nat_add_iff α _ _ _ _ N]
  refine'
    mt Summable.tendsto_atTop_zero fun h' =>
      not_tendsto_atTop_of_tendsto_nhds (tendsto_norm_zero.comp h') _
  convert tendsto_atTop_of_geom_le _ hr _
  · refine' lt_of_le_of_ne (norm_nonneg _) _
    intro h''
    specialize hN₀ N hNN₀
    simp only [comp_app, zero_add] at h''
    exact hN h''.symm
  · intro i
    dsimp only [comp_app]
    convert hN₀ (i + N) (hNN₀.trans (N.le_add_left i)) using 3
    ac_rfl
#align not_summable_of_ratio_norm_eventually_ge not_summable_of_ratio_norm_eventually_ge

theorem not_summable_of_ratio_test_tendsto_gt_one {α : Type _} [SeminormedAddCommGroup α]
    {f : ℕ → α} {l : ℝ} (hl : 1 < l) (h : Tendsto (fun n => ‖f (n + 1)‖ / ‖f n‖) atTop (𝓝 l)) :
    ¬Summable f :=
  by
  have key : ∀ᶠ n in at_top, ‖f n‖ ≠ 0 :=
    by
    filter_upwards [eventually_ge_of_tendsto_gt hl h]with _ hn hc
    rw [hc, div_zero] at hn
    linarith
  rcases exists_between hl with ⟨r, hr₀, hr₁⟩
  refine' not_summable_of_ratio_norm_eventually_ge hr₀ key.frequently _
  filter_upwards [eventually_ge_of_tendsto_gt hr₁ h, key]with _ _ h₁
  rwa [← le_div_iff (lt_of_le_of_ne (norm_nonneg _) h₁.symm)]
#align not_summable_of_ratio_test_tendsto_gt_one not_summable_of_ratio_test_tendsto_gt_one

section

/-! ### Dirichlet and alternating series tests -/

variable {E : Type _} [NormedAddCommGroup E] [NormedSpace ℝ E]

variable {b : ℝ} {f : ℕ → ℝ} {z : ℕ → E}

/-- **Dirichlet's Test** for monotone sequences. -/
theorem Monotone.cauchySeq_series_mul_of_tendsto_zero_of_bounded (hfa : Monotone f)
    (hf0 : Tendsto f atTop (𝓝 0)) (hgb : ∀ n, ‖∑ i in range n, z i‖ ≤ b) :
    CauchySeq fun n => ∑ i in range (n + 1), f i • z i :=
  by
  simp_rw [Finset.sum_range_by_parts _ _ (Nat.succ _), sub_eq_add_neg, Nat.succ_sub_succ_eq_sub,
    tsub_zero]
  apply
    (NormedField.tendsto_zero_smul_of_tendsto_zero_of_bounded hf0
          ⟨b, eventually_map.mpr <| eventually_of_forall fun n => hgb <| n + 1⟩).CauchySeq.add
  refine' (cauchySeq_range_of_norm_bounded _ _ (fun n => _ : ∀ n, _ ≤ b * |f (n + 1) - f n|)).neg
  · simp_rw [abs_of_nonneg (sub_nonneg_of_le (hfa (Nat.le_succ _))), ← mul_sum]
    apply real.uniform_continuous_const_mul.comp_cauchy_seq
    simp_rw [sum_range_sub, sub_eq_add_neg]
    exact (tendsto.cauchy_seq hf0).AddConst
  · rw [norm_smul, mul_comm]
    exact mul_le_mul_of_nonneg_right (hgb _) (abs_nonneg _)
#align monotone.cauchy_seq_series_mul_of_tendsto_zero_of_bounded Monotone.cauchySeq_series_mul_of_tendsto_zero_of_bounded

/-- **Dirichlet's test** for antitone sequences. -/
theorem Antitone.cauchySeq_series_mul_of_tendsto_zero_of_bounded (hfa : Antitone f)
    (hf0 : Tendsto f atTop (𝓝 0)) (hzb : ∀ n, ‖∑ i in range n, z i‖ ≤ b) :
    CauchySeq fun n => ∑ i in range (n + 1), f i • z i :=
  by
  have hfa' : Monotone fun n => -f n := fun _ _ hab => neg_le_neg <| hfa hab
  have hf0' : tendsto (fun n => -f n) at_top (𝓝 0) :=
    by
    convert hf0.neg
    norm_num
  convert(hfa'.cauchy_seq_series_mul_of_tendsto_zero_of_bounded hf0' hzb).neg
  funext
  simp
#align antitone.cauchy_seq_series_mul_of_tendsto_zero_of_bounded Antitone.cauchySeq_series_mul_of_tendsto_zero_of_bounded

theorem norm_sum_neg_one_pow_le (n : ℕ) : ‖∑ i in range n, (-1 : ℝ) ^ i‖ ≤ 1 :=
  by
  rw [neg_one_geom_sum]
  split_ifs <;> norm_num
#align norm_sum_neg_one_pow_le norm_sum_neg_one_pow_le

/-- The **alternating series test** for monotone sequences.
See also `tendsto_alternating_series_of_monotone_tendsto_zero`. -/
theorem Monotone.cauchySeq_alternating_series_of_tendsto_zero (hfa : Monotone f)
    (hf0 : Tendsto f atTop (𝓝 0)) : CauchySeq fun n => ∑ i in range (n + 1), (-1) ^ i * f i :=
  by
  simp_rw [mul_comm]
  exact hfa.cauchy_seq_series_mul_of_tendsto_zero_of_bounded hf0 norm_sum_neg_one_pow_le
#align monotone.cauchy_seq_alternating_series_of_tendsto_zero Monotone.cauchySeq_alternating_series_of_tendsto_zero

/-- The **alternating series test** for monotone sequences. -/
theorem Monotone.tendsto_alternating_series_of_tendsto_zero (hfa : Monotone f)
    (hf0 : Tendsto f atTop (𝓝 0)) :
    ∃ l, Tendsto (fun n => ∑ i in range (n + 1), (-1) ^ i * f i) atTop (𝓝 l) :=
  cauchySeq_tendsto_of_complete <| hfa.cauchySeq_alternating_series_of_tendsto_zero hf0
#align monotone.tendsto_alternating_series_of_tendsto_zero Monotone.tendsto_alternating_series_of_tendsto_zero

/-- The **alternating series test** for antitone sequences.
See also `tendsto_alternating_series_of_antitone_tendsto_zero`. -/
theorem Antitone.cauchySeq_alternating_series_of_tendsto_zero (hfa : Antitone f)
    (hf0 : Tendsto f atTop (𝓝 0)) : CauchySeq fun n => ∑ i in range (n + 1), (-1) ^ i * f i :=
  by
  simp_rw [mul_comm]
  exact hfa.cauchy_seq_series_mul_of_tendsto_zero_of_bounded hf0 norm_sum_neg_one_pow_le
#align antitone.cauchy_seq_alternating_series_of_tendsto_zero Antitone.cauchySeq_alternating_series_of_tendsto_zero

/-- The **alternating series test** for antitone sequences. -/
theorem Antitone.tendsto_alternating_series_of_tendsto_zero (hfa : Antitone f)
    (hf0 : Tendsto f atTop (𝓝 0)) :
    ∃ l, Tendsto (fun n => ∑ i in range (n + 1), (-1) ^ i * f i) atTop (𝓝 l) :=
  cauchySeq_tendsto_of_complete <| hfa.cauchySeq_alternating_series_of_tendsto_zero hf0
#align antitone.tendsto_alternating_series_of_tendsto_zero Antitone.tendsto_alternating_series_of_tendsto_zero

end

/-!
### Factorial
-/

/-- The series `∑' n, x ^ n / n!` is summable of any `x : ℝ`. See also `exp_series_div_summable`
for a version that also works in `ℂ`, and `exp_series_summable'` for a version that works in
any normed algebra over `ℝ` or `ℂ`. -/
theorem Real.summable_pow_div_factorial (x : ℝ) : Summable (fun n => x ^ n / n ! : ℕ → ℝ) :=
  by
  -- We start with trivial extimates
  have A : (0 : ℝ) < ⌊‖x‖⌋₊ + 1 := zero_lt_one.trans_le (by simp)
  have B : ‖x‖ / (⌊‖x‖⌋₊ + 1) < 1 := (div_lt_one A).2 (Nat.lt_floor_add_one _)
  -- Then we apply the ratio test. The estimate works for `n ≥ ⌊‖x‖⌋₊`.
  suffices : ∀ n ≥ ⌊‖x‖⌋₊, ‖x ^ (n + 1) / (n + 1)!‖ ≤ ‖x‖ / (⌊‖x‖⌋₊ + 1) * ‖x ^ n / ↑n !‖
  exact summable_of_ratio_norm_eventually_le B (eventually_at_top.2 ⟨⌊‖x‖⌋₊, this⟩)
  -- Finally, we prove the upper estimate
  intro n hn
  calc
    ‖x ^ (n + 1) / (n + 1)!‖ = ‖x‖ / (n + 1) * ‖x ^ n / n !‖ := by
      rw [pow_succ, Nat.factorial_succ, Nat.cast_mul, ← div_mul_div_comm, norm_mul, norm_div,
        Real.norm_coe_nat, Nat.cast_succ]
    _ ≤ ‖x‖ / (⌊‖x‖⌋₊ + 1) * ‖x ^ n / n !‖ := by
      mono* with 0 ≤ ‖x ^ n / n !‖, 0 ≤ ‖x‖ <;> apply norm_nonneg

#align real.summable_pow_div_factorial Real.summable_pow_div_factorial

theorem Real.tendsto_pow_div_factorial_atTop (x : ℝ) :
    Tendsto (fun n => x ^ n / n ! : ℕ → ℝ) atTop (𝓝 0) :=
  (Real.summable_pow_div_factorial x).tendsto_atTop_zero
#align real.tendsto_pow_div_factorial_at_top Real.tendsto_pow_div_factorial_atTop


/-
Copyright (c) 2022 Oliver Nash. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Oliver Nash
-/
import measure_theory.covering.density_theorem

/-!
# Liminf, limsup, and doubling measures.

This file is a place to collect lemmas about liminf and limsup for subsets of a metric space
carrying a doubling measure.

## Main results:

 * `blimsup_cthickening_mul_ae_eq`: the limsup of the closed thickening of a sequence of subsets
   of a metric space is unchanged almost everywhere for a doubling measure if the sequence of
   distances is multiplied by a positive scale factor. This is a generalisation of a result of
   Cassels, appearing as Lemma 9 on page 217 of
   [J.W.S. Cassels, *Some metrical theorems in Diophantine approximation. I*](cassels1950).
 * `blimsup_thickening_mul_ae_eq`: a variant of `blimsup_cthickening_mul_ae_eq` for thickenings
   rather than closed thickenings.

-/

open set filter metric measure_theory topological_space
open_locale nnreal ennreal topology

variables {α : Type*} [metric_space α] [second_countable_topology α] [measurable_space α]
  [borel_space α]
variables (μ : measure α) [is_locally_finite_measure μ] [is_doubling_measure μ]

/-- This is really an auxiliary result en route to `blimsup_cthickening_ae_le_of_eventually_mul_le`
(which is itself an auxiliary result en route to `blimsup_cthickening_mul_ae_eq`).

NB: The `set : α` type ascription is present because of issue #16932 on GitHub. -/
lemma blimsup_cthickening_ae_le_of_eventually_mul_le_aux
  (p : ℕ → Prop) {s : ℕ → set α} (hs : ∀ i, is_closed (s i))
  {r₁ r₂ : ℕ → ℝ} (hr : tendsto r₁ at_top (𝓝[>] 0)) (hrp : 0 ≤ r₁)
  {M : ℝ} (hM : 0 < M) (hM' : M < 1) (hMr : ∀ᶠ i in at_top, M * r₁ i ≤ r₂ i) :
  (blimsup (λ i, cthickening (r₁ i) (s i)) at_top p : set α) ≤ᵐ[μ]
  (blimsup (λ i, cthickening (r₂ i) (s i)) at_top p : set α) :=
begin
  /- Sketch of proof:

  Assume that `p` is identically true for simplicity. Let `Y₁ i = cthickening (r₁ i) (s i)`, define
  `Y₂` similarly except using `r₂`, and let `(Z i) = ⋃_{j ≥ i} (Y₂ j)`. Our goal is equivalent to
  showing that `μ ((limsup Y₁) \ (Z i)) = 0` for all `i`.

  Assume for contradiction that `μ ((limsup Y₁) \ (Z i)) ≠ 0` for some `i` and let
  `W = (limsup Y₁) \ (Z i)`. Apply Lebesgue's density theorem to obtain a point `d` in `W` of
  density `1`. Since `d ∈ limsup Y₁`, there is a subsequence of `j ↦ Y₁ j`, indexed by
  `f 0 < f 1 < ...`, such that `d ∈ Y₁ (f j)` for all `j`. For each `j`, we may thus choose
  `w j ∈ s (f j)` such that `d ∈ B j`, where `B j = closed_ball (w j) (r₁ (f j))`. Note that
  since `d` has density one, `μ (W ∩ (B j)) / μ (B j) → 1`.

  We obtain our contradiction by showing that there exists `η < 1` such that
  `μ (W ∩ (B j)) / μ (B j) ≤ η` for sufficiently large `j`. In fact we claim that `η = 1 - C⁻¹`
  is such a value where `C` is the scaling constant of `M⁻¹` for the doubling measure `μ`.

  To prove the claim, let `b j = closed_ball (w j) (M * r₁ (f j))` and for given `j` consider the
  sets `b j` and `W ∩ (B j)`. These are both subsets of `B j` and are disjoint for large enough `j`
  since `M * r₁ j ≤ r₂ j` and thus `b j ⊆ Z i ⊆ Wᶜ`. We thus have:
  `μ (b j) + μ (W ∩ (B j)) ≤ μ (B j)`. Combining this with `μ (B j) ≤ C * μ (b j)` we obtain
  the required inequality. -/
  set Y₁ : ℕ → set α := λ i, cthickening (r₁ i) (s i),
  set Y₂ : ℕ → set α := λ i, cthickening (r₂ i) (s i),
  let Z : ℕ → set α := λ i, ⋃ j (h : p j ∧ i ≤ j), Y₂ j,
  suffices : ∀ i, μ (at_top.blimsup Y₁ p \ Z i) = 0,
  { rwa [ae_le_set, @blimsup_eq_infi_bsupr_of_nat _ _ _ Y₂, infi_eq_Inter, diff_Inter,
      measure_Union_null_iff], },
  intros,
  set W := at_top.blimsup Y₁ p \ Z i,
  by_contra contra,
  obtain ⟨d, hd, hd'⟩ : ∃ d, d ∈ W ∧ ∀ {ι : Type*} {l : filter ι} (w : ι → α) (δ : ι → ℝ),
    tendsto δ l (𝓝[>] 0) → (∀ᶠ j in l, d ∈ closed_ball (w j) (2 * δ j)) →
    tendsto (λ j, μ (W ∩ closed_ball (w j) (δ j)) / μ (closed_ball (w j) (δ j))) l (𝓝 1) :=
    measure.exists_mem_of_measure_ne_zero_of_ae contra
      (is_doubling_measure.ae_tendsto_measure_inter_div μ W 2),
  replace hd : d ∈ blimsup Y₁ at_top p := ((mem_diff _).mp hd).1,
  obtain ⟨f : ℕ → ℕ, hf⟩ := exists_forall_mem_of_has_basis_mem_blimsup' at_top_basis hd,
  simp only [forall_and_distrib] at hf,
  obtain ⟨hf₀ : ∀ j, d ∈ cthickening (r₁ (f j)) (s (f j)), hf₁, hf₂ : ∀ j, j ≤ f j⟩ := hf,
  have hf₃ : tendsto f at_top at_top :=
    tendsto_at_top_at_top.mpr (λ j, ⟨f j, λ i hi, (hf₂ j).trans (hi.trans $ hf₂ i)⟩),
  replace hr : tendsto (r₁ ∘ f) at_top (𝓝[>] 0) := hr.comp hf₃,
  replace hMr : ∀ᶠ j in at_top, M * r₁ (f j) ≤ r₂ (f j) := hf₃.eventually hMr,
  replace hf₀ : ∀ j, ∃ (w ∈ s (f j)), d ∈ closed_ball w (2 * r₁ (f j)),
  { intros j,
    specialize hrp (f j),
    rw pi.zero_apply at hrp,
    rcases eq_or_lt_of_le hrp with hr0 | hrp',
    { specialize hf₀ j,
      rw [← hr0, cthickening_zero, (hs (f j)).closure_eq] at hf₀,
      exact ⟨d, hf₀, by simp [← hr0]⟩, },
    { exact mem_Union₂.mp (cthickening_subset_Union_closed_ball_of_lt (s (f j)) (by positivity)
        (lt_two_mul_self hrp') (hf₀ j)), }, },
  choose w hw hw' using hf₀,
  let C := is_doubling_measure.scaling_constant_of μ M⁻¹,
  have hC : 0 < C :=
    lt_of_lt_of_le zero_lt_one (is_doubling_measure.one_le_scaling_constant_of μ M⁻¹),
  suffices : ∃ (η < (1 : ℝ≥0)), ∀ᶠ j in at_top,
    μ (W ∩ closed_ball (w j) (r₁ (f j))) / μ (closed_ball (w j) (r₁ (f j))) ≤ η,
  { obtain ⟨η, hη, hη'⟩ := this,
    replace hη' : 1 ≤ η := by simpa only [ennreal.one_le_coe_iff] using
      le_of_tendsto (hd' w (λ j, r₁ (f j)) hr $ eventually_of_forall hw') hη',
    exact (lt_self_iff_false _).mp (lt_of_lt_of_le hη hη'), },
  refine ⟨1 - C⁻¹, tsub_lt_self zero_lt_one (inv_pos.mpr hC), _⟩,
  replace hC : C ≠ 0 := ne_of_gt hC,
  let b : ℕ → set α := λ j, closed_ball (w j) (M * r₁ (f j)),
  let B : ℕ → set α := λ j, closed_ball (w j) (r₁ (f j)),
  have h₁ : ∀ j, b j ⊆ B j :=
    λ j, closed_ball_subset_closed_ball (mul_le_of_le_one_left (hrp (f j)) hM'.le),
  have h₂ : ∀ j, W ∩ B j ⊆ B j := λ j, inter_subset_right W (B j),
  have h₃ : ∀ᶠ j in at_top, disjoint (b j) (W ∩ B j),
  { apply hMr.mp,
    rw eventually_at_top,
    refine ⟨i, λ j hj hj', disjoint.inf_right (B j) $ disjoint.inf_right' (blimsup Y₁ at_top p) _⟩,
    change disjoint (b j) (Z i)ᶜ,
    rw disjoint_compl_right_iff_subset,
    refine (closed_ball_subset_cthickening (hw j) (M * r₁ (f j))).trans
      ((cthickening_mono hj' _).trans (λ a ha, _)),
    simp only [mem_Union, exists_prop],
    exact ⟨f j, ⟨hf₁ j, hj.le.trans (hf₂ j)⟩, ha⟩, },
  have h₄ : ∀ᶠ j in at_top, μ (B j) ≤ C * μ (b j) :=
    (hr.eventually (is_doubling_measure.eventually_measure_le_scaling_constant_mul'
      μ M hM)).mono (λ j hj, hj (w j)),
  refine (h₃.and h₄).mono (λ j hj₀, _),
  change μ (W ∩ B j) / μ (B j) ≤ ↑(1 - C⁻¹),
  rcases eq_or_ne (μ (B j)) ∞ with hB | hB, { simp [hB], },
  apply ennreal.div_le_of_le_mul,
  rw [with_top.coe_sub, ennreal.coe_one, ennreal.sub_mul (λ _ _, hB), one_mul],
  replace hB : ↑C⁻¹ * μ (B j) ≠ ∞,
  { refine ennreal.mul_ne_top _ hB,
    rwa [ennreal.coe_inv hC, ne.def, ennreal.inv_eq_top, ennreal.coe_eq_zero], },
  obtain ⟨hj₁ : disjoint (b j) (W ∩ B j), hj₂ : μ (B j) ≤ C * μ (b j)⟩ := hj₀,
  replace hj₂ : ↑C⁻¹ * μ (B j) ≤ μ (b j),
  { rw [ennreal.coe_inv hC, ← ennreal.div_eq_inv_mul],
    exact ennreal.div_le_of_le_mul' hj₂, },
  have hj₃ : ↑C⁻¹ * μ (B j) + μ (W ∩ B j) ≤ μ (B j),
  { refine le_trans (add_le_add_right hj₂ _) _,
    rw ← measure_union' hj₁ measurable_set_closed_ball,
    exact measure_mono (union_subset (h₁ j) (h₂ j)), },
  replace hj₃ := tsub_le_tsub_right hj₃ (↑C⁻¹ * μ (B j)),
  rwa ennreal.add_sub_cancel_left hB at hj₃,
end

/-- This is really an auxiliary result en route to `blimsup_cthickening_mul_ae_eq`.

NB: The `set : α` type ascription is present because of issue #16932 on GitHub. -/
lemma blimsup_cthickening_ae_le_of_eventually_mul_le
  (p : ℕ → Prop) {s : ℕ → set α} {M : ℝ} (hM : 0 < M)
  {r₁ r₂ : ℕ → ℝ} (hr : tendsto r₁ at_top (𝓝[>] 0)) (hMr : ∀ᶠ i in at_top, M * r₁ i ≤ r₂ i) :
  (blimsup (λ i, cthickening (r₁ i) (s i)) at_top p : set α) ≤ᵐ[μ]
  (blimsup (λ i, cthickening (r₂ i) (s i)) at_top p : set α) :=
begin
  let R₁ := λ i, max 0 (r₁ i),
  let R₂ := λ i, max 0 (r₂ i),
  have hRp : 0 ≤ R₁ := λ i, le_max_left 0 (r₁ i),
  replace hMr : ∀ᶠ i in at_top, M * R₁ i ≤ R₂ i,
  { refine hMr.mono (λ i hi, _),
    rw [mul_max_of_nonneg _ _ hM.le, mul_zero],
    exact max_le_max (le_refl 0) hi, },
  simp_rw [← cthickening_max_zero (r₁ _), ← cthickening_max_zero (r₂ _)],
  cases le_or_lt 1 M with hM' hM',
  { apply has_subset.subset.eventually_le,
    change _ ≤ _,
    refine mono_blimsup' (hMr.mono $ λ i hi hp, cthickening_mono _ (s i)),
    exact (le_mul_of_one_le_left (hRp i) hM').trans hi, },
  { simp only [← @cthickening_closure _ _ _ (s _)],
    have hs : ∀ i, is_closed (closure (s i)) := λ i, is_closed_closure,
    exact blimsup_cthickening_ae_le_of_eventually_mul_le_aux
      μ p hs (tendsto_nhds_max_right hr) hRp hM hM' hMr, },
end

/-- Given a sequence of subsets `sᵢ` of a metric space, together with a sequence of radii `rᵢ`
such that `rᵢ → 0`, the set of points which belong to infinitely many of the closed
`rᵢ`-thickenings of `sᵢ` is unchanged almost everywhere for a doubling measure if the `rᵢ` are all
scaled by a positive constant.

This lemma is a generalisation of Lemma 9 appearing on page 217 of
[J.W.S. Cassels, *Some metrical theorems in Diophantine approximation. I*](cassels1950).

See also `blimsup_thickening_mul_ae_eq`.

NB: The `set : α` type ascription is present because of issue #16932 on GitHub. -/
theorem blimsup_cthickening_mul_ae_eq
  (p : ℕ → Prop) (s : ℕ → set α) {M : ℝ} (hM : 0 < M) (r : ℕ → ℝ) (hr : tendsto r at_top (𝓝 0)) :
  (blimsup (λ i, cthickening (M * r i) (s i)) at_top p : set α) =ᵐ[μ]
  (blimsup (λ i, cthickening (r i) (s i)) at_top p : set α) :=
begin
  have : ∀ (p : ℕ → Prop) {r : ℕ → ℝ} (hr : tendsto r at_top (𝓝[>] 0)),
    (blimsup (λ i, cthickening (M * r i) (s i)) at_top p : set α) =ᵐ[μ]
    (blimsup (λ i, cthickening (r i) (s i)) at_top p : set α),
  { clear p hr r, intros p r hr,
    have hr' : tendsto (λ i, M * r i) at_top (𝓝[>] 0),
    { convert tendsto_nhds_within_Ioi.const_mul hM hr; simp only [mul_zero], },
    refine eventually_le_antisymm_iff.mpr ⟨_, _⟩,
    { exact blimsup_cthickening_ae_le_of_eventually_mul_le μ p (inv_pos.mpr hM) hr'
        (eventually_of_forall $ λ i, by rw inv_mul_cancel_left₀ hM.ne' (r i)), },
    { exact blimsup_cthickening_ae_le_of_eventually_mul_le μ p hM hr
        (eventually_of_forall $ λ i, le_refl _), }, },
  let r' : ℕ → ℝ := λ i, if 0 < r i then r i else 1/((i : ℝ) + 1),
  have hr' : tendsto r' at_top (𝓝[>] 0),
  { refine tendsto_nhds_within_iff.mpr ⟨tendsto.if' hr tendsto_one_div_add_at_top_nhds_0_nat,
      eventually_of_forall $ λ i, _⟩,
    by_cases hi : 0 < r i,
    { simp [hi, r'], },
    { simp only [hi, r', one_div, mem_Ioi, if_false, inv_pos], positivity, }, },
  have h₀ : ∀ i, (p i ∧ 0 < r i) → cthickening (r i) (s i) = cthickening (r' i) (s i),
  { rintros i ⟨-, hi⟩, congr, change r i = ite (0 < r i) (r i) _, simp [hi], },
  have h₁ : ∀ i, (p i ∧ 0 < r i) → cthickening (M * r i) (s i) = cthickening (M * r' i) (s i),
  { rintros i ⟨-, hi⟩, simp only [hi, mul_ite, if_true], },
  have h₂ : ∀ i, (p i ∧ r i ≤ 0) → cthickening (M * r i) (s i) = cthickening (r i) (s i),
  { rintros i ⟨-, hi⟩,
    have hi' : M * r i ≤ 0 := mul_nonpos_of_nonneg_of_nonpos hM.le hi,
    rw [cthickening_of_nonpos hi, cthickening_of_nonpos hi'], },
  have hp : p = λ i, (p i ∧ 0 < r i) ∨ (p i ∧ r i ≤ 0),
  { ext i, simp [← and_or_distrib_left, lt_or_le 0 (r i)], },
  rw [hp, blimsup_or_eq_sup, blimsup_or_eq_sup, sup_eq_union,
    blimsup_congr (eventually_of_forall h₀), blimsup_congr (eventually_of_forall h₁),
    blimsup_congr (eventually_of_forall h₂)],
  exact ae_eq_set_union (this (λ i, p i ∧ 0 < r i) hr') (ae_eq_refl _),
end

lemma blimsup_cthickening_ae_eq_blimsup_thickening
  {p : ℕ → Prop} {s : ℕ → set α} {r : ℕ → ℝ}
  (hr : tendsto r at_top (𝓝 0)) (hr' : ∀ᶠ i in at_top , p i → 0 < r i) :
  (blimsup (λ i, cthickening (r i) (s i)) at_top p : set α) =ᵐ[μ]
  (blimsup (λ i, thickening (r i) (s i)) at_top p : set α) :=
begin
  refine eventually_le_antisymm_iff.mpr ⟨_, has_subset.subset.eventually_le (_ : _ ≤ _)⟩,
  { rw eventually_le_congr (blimsup_cthickening_mul_ae_eq μ p s (@one_half_pos ℝ _) r hr).symm
      eventually_eq.rfl,
    apply has_subset.subset.eventually_le,
    change _ ≤ _,
    refine mono_blimsup' (hr'.mono $ λ i hi pi, cthickening_subset_thickening' (hi pi) _ (s i)),
    nlinarith [hi pi], },
  { exact mono_blimsup (λ i pi, thickening_subset_cthickening _ _), },
end

/-- An auxiliary result en route to `blimsup_thickening_mul_ae_eq`. -/
lemma blimsup_thickening_mul_ae_eq_aux
  (p : ℕ → Prop) (s : ℕ → set α) {M : ℝ} (hM : 0 < M)
  (r : ℕ → ℝ) (hr : tendsto r at_top (𝓝 0)) (hr' : ∀ᶠ i in at_top , p i → 0 < r i) :
  (blimsup (λ i, thickening (M * r i) (s i)) at_top p : set α) =ᵐ[μ]
  (blimsup (λ i, thickening (r i) (s i)) at_top p : set α) :=
begin
  have h₁ := blimsup_cthickening_ae_eq_blimsup_thickening μ hr hr',
  have h₂ := blimsup_cthickening_mul_ae_eq μ p s hM r hr,
  replace hr : tendsto (λ i, M * r i) at_top (𝓝 0), { convert hr.const_mul M, simp, },
  replace hr' : ∀ᶠ i in at_top , p i → 0 < M * r i := hr'.mono (λ i hi hip, mul_pos hM (hi hip)),
  have h₃ := blimsup_cthickening_ae_eq_blimsup_thickening μ hr hr',
  exact h₃.symm.trans (h₂.trans h₁),
end

/-- Given a sequence of subsets `sᵢ` of a metric space, together with a sequence of radii `rᵢ`
such that `rᵢ → 0`, the set of points which belong to infinitely many of the
`rᵢ`-thickenings of `sᵢ` is unchanged almost everywhere for a doubling measure if the `rᵢ` are all
scaled by a positive constant.

This lemma is a generalisation of Lemma 9 appearing on page 217 of
[J.W.S. Cassels, *Some metrical theorems in Diophantine approximation. I*](cassels1950).

See also `blimsup_cthickening_mul_ae_eq`.

NB: The `set : α` type ascription is present because of issue #16932 on GitHub. -/
theorem blimsup_thickening_mul_ae_eq
  (p : ℕ → Prop) (s : ℕ → set α) {M : ℝ} (hM : 0 < M) (r : ℕ → ℝ) (hr : tendsto r at_top (𝓝 0)) :
  (blimsup (λ i, thickening (M * r i) (s i)) at_top p : set α) =ᵐ[μ]
  (blimsup (λ i, thickening (r i) (s i)) at_top p : set α) :=
begin
  let q : ℕ → Prop := λ i, p i ∧ 0 < r i,
  have h₁ : blimsup (λ i, thickening (r i) (s i)) at_top p =
            blimsup (λ i, thickening (r i) (s i)) at_top q,
  { refine blimsup_congr' (eventually_of_forall $ λ i h, _),
    replace hi : 0 < r i, { contrapose! h, apply thickening_of_nonpos h, },
    simp only [hi, iff_self_and, implies_true_iff], },
  have h₂ : blimsup (λ i, thickening (M * r i) (s i)) at_top p =
            blimsup (λ i, thickening (M * r i) (s i)) at_top q,
  { refine blimsup_congr' (eventually_of_forall $ λ i h, _),
    replace h : 0 < r i, { rw ← zero_lt_mul_left hM, contrapose! h, apply thickening_of_nonpos h, },
    simp only [h, iff_self_and, implies_true_iff], },
  rw [h₁, h₂],
  exact blimsup_thickening_mul_ae_eq_aux μ q s hM r hr (eventually_of_forall (λ i hi, hi.2)),
end


/-
Copyright (c) 2021 Andrew Yang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Andrew Yang
-/
import topology.separation
import topology.continuous_function.basic

/-!
# Sober spaces

A quasi-sober space is a topological space where every
irreducible closed subset has a generic point.
A sober space is a quasi-sober space where every irreducible closed subset
has a *unique* generic point. This is if and only if the space is T0, and thus sober spaces can be
stated via `[quasi_sober α] [t0_space α]`.

## Main definition

* `specializes` : `specializes x y` (`x ⤳ y`) means that `x` specializes to `y`, i.e.
  `y` is in the closure of `x`.
* `specialization_preorder` : specialization gives a preorder on a topological space.
* `specialization_order` : specialization gives a partial order on a T0 space.
* `is_generic_point` : `x` is the generic point of `S` if `S` is the closure of `x`.
* `quasi_sober` : A space is quasi-sober if every irreducible closed subset has a generic point.

-/

variables {α β : Type*} [topological_space α] [topological_space β]

section specialize_order

/-- `x` specializes to `y` if `y` is in the closure of `x`. The notation used is `x ⤳ y`. -/
def specializes (x y : α) : Prop := y ∈ closure ({x} : set α)

infix ` ⤳ `:300 := specializes

lemma specializes_def (x y : α) : x ⤳ y ↔ y ∈ closure ({x} : set α) := iff.rfl

lemma specializes_iff_closure_subset {x y : α} :
  x ⤳ y ↔ closure ({y} : set α) ⊆ closure ({x} : set α) :=
is_closed_closure.mem_iff_closure_subset

lemma specializes_rfl {x : α} : x ⤳ x := subset_closure (set.mem_singleton x)

lemma specializes_refl (x : α) : x ⤳ x := specializes_rfl

lemma specializes.trans {x y z : α} : x ⤳ y → y ⤳ z → x ⤳ z :=
by { simp_rw specializes_iff_closure_subset, exact λ a b, b.trans a }

lemma specializes_iff_forall_closed {x y : α} :
  x ⤳ y ↔ ∀ (Z : set α) (h : is_closed Z), x ∈ Z → y ∈ Z :=
begin
  split,
  { intros h Z hZ,
    rw [hZ.mem_iff_closure_subset, hZ.mem_iff_closure_subset],
    exact (specializes_iff_closure_subset.mp h).trans },
  { intro h, exact h _ is_closed_closure (subset_closure $ set.mem_singleton x) }
end

lemma specializes_iff_forall_open {x y : α} :
  x ⤳ y ↔ ∀ (U : set α) (h : is_open U), y ∈ U → x ∈ U :=
begin
  rw specializes_iff_forall_closed,
  exact ⟨λ h U hU, not_imp_not.mp (h _ (is_closed_compl_iff.mpr hU)),
    λ h U hU, not_imp_not.mp (h _ (is_open_compl_iff.mpr hU))⟩,
end

lemma indistinguishable_iff_specializes_and (x y : α) :
  indistinguishable x y ↔ x ⤳ y ∧ y ⤳ x :=
(indistinguishable_iff_closure x y).trans (and_comm _ _)

lemma specializes_antisymm [t0_space α] (x y : α) : x ⤳ y → y ⤳ x → x = y :=
λ h₁ h₂, ((indistinguishable_iff_specializes_and _ _).mpr ⟨h₁, h₂⟩).eq

lemma specializes.map {x y : α} (h : x ⤳ y) {f : α → β} (hf : continuous f) : f x ⤳ f y :=
begin
  rw [specializes_def, ← set.image_singleton],
  exact image_closure_subset_closure_image hf ⟨_, h, rfl⟩,
end

lemma continuous_map.map_specialization {x y : α} (h : x ⤳ y) (f : C(α, β)) : f x ⤳ f y :=
h.map f.2

lemma specializes.eq [t1_space α] {x y : α} (h : x ⤳ y) : x = y :=
(set.mem_singleton_iff.mp
  ((specializes_iff_forall_closed.mp h) _ (t1_space.t1 _) (set.mem_singleton _))).symm

@[simp] lemma specializes_iff_eq [t1_space α] {x y : α} : x ⤳ y ↔ x = y :=
⟨specializes.eq, λ h, h ▸ specializes_refl _⟩

variable (α)

/-- Specialization forms a preorder on the topological space. -/
def specialization_preorder : preorder α :=
{ le := λ x y, y ⤳ x,
  le_refl := λ x, specializes_refl x,
  le_trans := λ _ _ _ h₁ h₂, specializes.trans h₂ h₁ }

local attribute [instance] specialization_preorder

/-- Specialization forms a partial order on a t0 topological space. -/
def specialization_order [t0_space α] : partial_order α :=
{ le_antisymm := λ _ _ h₁ h₂, specializes_antisymm _ _ h₂ h₁,
  .. specialization_preorder α }

variable {α}

lemma specialization_order.monotone_of_continuous (f : α → β) (hf : continuous f) : monotone f :=
λ x y h, specializes.map h hf

end specialize_order

section generic_point

/-- `x` is a generic point of `S` if `S` is the closure of `x`. -/
def is_generic_point (x : α) (S : set α) : Prop := closure ({x} : set α) = S

lemma is_generic_point_def {x : α} {S : set α} : is_generic_point x S ↔ closure ({x} : set α) = S :=
iff.rfl

lemma is_generic_point.def {x : α} {S : set α} (h : is_generic_point x S) :
  closure ({x} : set α) = S := h

lemma is_generic_point_closure {x : α} : is_generic_point x (closure ({x} : set α)) := refl _

variables {x : α} {S : set α} (h : is_generic_point x S)

include h

lemma is_generic_point.specializes {y : α} (h' : y ∈ S) :
  x ⤳ y := by rwa ← h.def at h'

lemma is_generic_point.mem : x ∈ S :=
h.def ▸ subset_closure (set.mem_singleton x)

lemma is_generic_point.is_closed : is_closed S :=
h.def ▸ is_closed_closure

lemma is_generic_point.is_irreducible : is_irreducible S :=
h.def ▸ is_irreducible_singleton.closure

lemma is_generic_point.eq [t0_space α] {y : α} (h' : is_generic_point y S) : x = y :=
specializes_antisymm _ _ (h.specializes h'.mem) (h'.specializes h.mem)

lemma is_generic_point.mem_open_set_iff
  {U : set α} (hU : is_open U) : x ∈ U ↔ (S ∩ U).nonempty :=
⟨λ h', ⟨x, h.mem, h'⟩,
    λ h', specializes_iff_forall_open.mp (h.specializes h'.some_spec.1) U hU h'.some_spec.2⟩

lemma is_generic_point.disjoint_iff
  {U : set α} (hU : is_open U) : disjoint S U ↔ x ∉ U :=
by rw [h.mem_open_set_iff hU, ← set.ne_empty_iff_nonempty, not_not, set.disjoint_iff_inter_eq_empty]

lemma is_generic_point.mem_closed_set_iff
  {Z : set α} (hZ : is_closed Z) : x ∈ Z ↔ S ⊆ Z :=
by rw [← is_generic_point_def.mp h, hZ.closure_subset_iff, set.singleton_subset_iff]

lemma is_generic_point.image {f : α → β} (hf : continuous f) :
  is_generic_point (f x) (closure (f '' S)) :=
begin
  rw [is_generic_point_def, ← is_generic_point_def.mp h],
  apply le_antisymm,
  { exact closure_mono
      (set.singleton_subset_iff.mpr ⟨_, subset_closure $ set.mem_singleton x, rfl⟩) },
  { convert is_closed_closure.closure_subset_iff.mpr (image_closure_subset_closure_image hf),
    rw set.image_singleton }
end

omit h

lemma is_generic_point_iff_forall_closed {x : α} {S : set α} (hS : is_closed S) (hxS : x ∈ S) :
  is_generic_point x S ↔ ∀ (Z : set α) (hZ : is_closed Z) (hxZ : x ∈ Z), S ⊆ Z :=
begin
  split,
  { intros h Z hZ hxZ, exact (h.mem_closed_set_iff hZ).mp hxZ },
  { intro h,
    apply le_antisymm,
    { rwa [set.le_eq_subset, hS.closure_subset_iff, set.singleton_subset_iff] },
    { exact h _ is_closed_closure (subset_closure $ set.mem_singleton x) } }
end

end generic_point

section sober

/-- A space is sober if every irreducible closed subset has a generic point. -/
@[mk_iff]
class quasi_sober (α : Type*) [topological_space α] : Prop :=
(sober : ∀ {S : set α} (hS₁ : is_irreducible S) (hS₂ : is_closed S), ∃ x, is_generic_point x S)

/-- A generic point of the closure of an irreducible space. -/
noncomputable
def is_irreducible.generic_point [quasi_sober α] {S : set α} (hS : is_irreducible S) : α :=
(quasi_sober.sober hS.closure is_closed_closure).some

lemma is_irreducible.generic_point_spec [quasi_sober α] {S : set α} (hS : is_irreducible S) :
  is_generic_point hS.generic_point (closure S) :=
(quasi_sober.sober hS.closure is_closed_closure).some_spec

@[simp] lemma is_irreducible.generic_point_closure_eq [quasi_sober α] {S : set α}
  (hS : is_irreducible S) : closure ({hS.generic_point} : set α) = closure S :=
hS.generic_point_spec

variable (α)

/-- A generic point of a sober irreducible space. -/
noncomputable
def generic_point [quasi_sober α] [irreducible_space α] : α :=
(irreducible_space.is_irreducible_univ α).generic_point

lemma generic_point_spec [quasi_sober α] [irreducible_space α] :
  is_generic_point (generic_point α) ⊤ :=
by simpa using (irreducible_space.is_irreducible_univ α).generic_point_spec

@[simp]
lemma generic_point_closure [quasi_sober α] [irreducible_space α] :
  closure ({generic_point α} : set α) = ⊤ := generic_point_spec α

variable {α}

lemma generic_point_specializes [quasi_sober α] [irreducible_space α] (x : α) :
  generic_point α ⤳ x := (is_irreducible.generic_point_spec _).specializes (by simp)

local attribute [instance, priority 10] specialization_order

/-- The closed irreducible subsets of a sober space bijects with the points of the space. -/
noncomputable
def irreducible_set_equiv_points [quasi_sober α] [t0_space α] :
  { s : set α | is_irreducible s ∧ is_closed s } ≃o α :=
{ to_fun := λ s, s.prop.1.generic_point,
  inv_fun := λ x, ⟨closure ({x} : set α), is_irreducible_singleton.closure, is_closed_closure⟩,
  left_inv := λ s,
    subtype.eq $ eq.trans (s.prop.1.generic_point_spec) $ closure_eq_iff_is_closed.mpr s.2.2,
  right_inv := λ x, is_irreducible_singleton.closure.generic_point_spec.eq
      (by { convert is_generic_point_closure using 1, rw closure_closure }),
  map_rel_iff' := λ s t, by { change _ ⤳ _ ↔ _, rw specializes_iff_closure_subset,
    simp [s.prop.2.closure_eq, t.prop.2.closure_eq, ← subtype.coe_le_coe] } }

lemma closed_embedding.quasi_sober {f : α → β} (hf : closed_embedding f) [quasi_sober β] :
  quasi_sober α :=
begin
  constructor,
  intros S hS hS',
  have hS'' := hS.image f hf.continuous.continuous_on,
  obtain ⟨x, hx⟩ := quasi_sober.sober hS'' (hf.is_closed_map _ hS'),
  obtain ⟨y, hy, rfl⟩ := hx.mem,
  use y,
  change _ = _ at hx,
  apply set.image_injective.mpr hf.inj,
  rw [← hx, ← hf.closure_image_eq, set.image_singleton]
end

lemma open_embedding.quasi_sober {f : α → β} (hf : open_embedding f) [quasi_sober β] :
  quasi_sober α :=
begin
  constructor,
  intros S hS hS',
  have hS'' := hS.image f hf.continuous.continuous_on,
  obtain ⟨x, hx⟩ := quasi_sober.sober hS''.closure is_closed_closure,
  obtain ⟨T, hT, rfl⟩ := hf.to_inducing.is_closed_iff.mp hS',
  rw set.image_preimage_eq_inter_range at hx hS'',
  have hxT : x ∈ T,
  { rw ← hT.closure_eq, exact closure_mono (set.inter_subset_left _ _) hx.mem },
  have hxU : x ∈ set.range f,
  { rw hx.mem_open_set_iff hf.open_range,
    refine set.nonempty.mono _ hS''.1,
    simpa using subset_closure },
  rcases hxU with ⟨y, rfl⟩,
  use y,
  change _ = _,
  rw [hf.to_embedding.closure_eq_preimage_closure_image,
    set.image_singleton, (show _ = _, from hx)],
  apply set.image_injective.mpr hf.inj,
  ext z,
  simp only [set.image_preimage_eq_inter_range, set.mem_inter_eq, and.congr_left_iff],
  exact λ hy, ⟨λ h, hT.closure_eq ▸ closure_mono (set.inter_subset_left _ _) h,
    λ h, subset_closure ⟨h, hy⟩⟩
end

/-- A space is quasi sober if it can be covered by open quasi sober subsets. -/
lemma quasi_sober_of_open_cover (S : set (set α)) (hS : ∀ s : S, is_open (s : set α))
  [hS' : ∀ s : S, quasi_sober s] (hS'' : ⋃₀ S = ⊤) : quasi_sober α :=
begin
  rw quasi_sober_iff,
  intros t h h',
  obtain ⟨x, hx⟩ := h.1,
  obtain ⟨U, hU, hU'⟩ : x ∈ ⋃₀S := by { rw hS'', trivial },
  haveI : quasi_sober U := hS' ⟨U, hU⟩,
  have H : is_preirreducible (coe ⁻¹' t : set U) :=
    h.2.preimage (hS ⟨U, hU⟩).open_embedding_subtype_coe,
  replace H : is_irreducible (coe ⁻¹' t : set U) := ⟨⟨⟨x, hU'⟩, by simpa using hx⟩, H⟩,
  use H.generic_point,
  have := continuous_subtype_coe.closure_preimage_subset _ H.generic_point_spec.mem,
  rw h'.closure_eq at this,
  apply le_antisymm,
  { apply h'.closure_subset_iff.mpr, simpa using this },
  rw [← set.image_singleton, ← closure_closure],
  have := closure_mono (image_closure_subset_closure_image (@continuous_subtype_coe α _ U)),
  refine set.subset.trans _ this,
  rw H.generic_point_spec.def,
  refine (subset_closure_inter_of_is_preirreducible_of_is_open
    h.2 (hS ⟨U, hU⟩) ⟨x, hx, hU'⟩).trans (closure_mono _),
  rw ← subtype.image_preimage_coe,
  exact set.image_subset _ subset_closure,
end

@[priority 100]
instance t2_space.quasi_sober [t2_space α] : quasi_sober α :=
begin
  constructor,
  rintro S h -,
  obtain ⟨x, rfl⟩ := (is_irreducible_iff_singleton S).mp h,
  exact ⟨x, (t1_space.t1 x).closure_eq⟩
end

end sober


import Lake
import Init
import Mathlib.Data.Real.Basic
import Mathlib.Data.Vector

#eval 1

/-!
# Indexed inductive type

We define types `Vec α n` for `α : Type u` and `n: ℕ` with terms of `Vec α n` n-tuples in `α`.

* `α` will be a parameter.
* `n` will be an index.
-/

inductive Vec (α : Type u) :
            ℕ → Type (u + 1) where
| nil : Vec α 0
| cons : {n : ℕ} →
  (head : α) → (tail : Vec α n) → Vec α (n + 1)

example : Vec ℕ 0 := Vec.nil

example : Vec ℕ 1 := Vec.cons 3 (Vec.nil)

#check List

def Vec.to_list {α : Type u} {n : ℕ} : Vec α n → List α
| Vec.nil => []
| Vec.cons head tail => head :: tail.to_list

/-!
Vectors, i.e., lists of a fixed length, can be defined in (at least) two ways. One way is as an indexed inductive type `Vec`, as we saw in lecture and is in the file `InductiveTypes.lean`.

A different definition is as a subtype `Vector` of lists consisting of those of a fixed length. This is the definition used in `mathlib` and is recalled below.

```lean
/-- `Vector α n` is the type of lists of length `n` with elements of type `α`. -/
def Vector (α : Type u) (n : ℕ) :=
  { l : List α // l.length = n }
```

In this lab, you will relate the two definitions by constructing functions that convert between the two definitions and prove that these functions are inverses of each other.
-/
universe u

/-- Convert a `Vector` to a `Vec` -/
def Vec.ofVector {α : Type u}: (n : ℕ) →  Vector α n → Vec α n
| 0, _ => Vec.nil
| k + 1, ⟨ head :: tail, h ⟩ => by
  have h1 : tail.length = k := by
    simp [List.length_cons] at h
    assumption
  apply Vec.cons head (Vec.ofVector k ⟨ tail, h1⟩ )

#print Vec.ofVector

/-- Convert a `Vec` to a `Vector`
def Vec.toVector {α : Type u}: (n : ℕ) →  Vec α n → Vector α n
| _, _ => sorry --/

def vector1 : Vector ℕ 1 := ⟨ [2] , rfl ⟩
def vector0 : Vector ℕ 0 := ⟨ [], rfl ⟩

#eval vector1

/-- Convert a `Vec` to a `Vector` -/
def Vec.toVector {α : Type u}: (n : ℕ) →  Vec α n → Vector α n
| 0, _ => ⟨ [], rfl ⟩
| k + 1, Vec.cons head tail => ⟨ head :: (Vec.toVector k tail).val, by
    simp [List.length]; apply (Vec.toVector k tail).property⟩
  -- by
  -- let vectorTail : Vector α k := Vec.toVector k tail
  -- let ⟨Ltail, h⟩ := vectorTail

  -- ------------------------
  -- --have lem1: Ltail = vectorTail.1 := by
  -- ------------------------
  -- have lem5 : (head :: Ltail).length = k + 1 := by
  --   simp [List.length, h]
  -- let vec3 : Vector α (k+1) := ⟨head :: Ltail, lem5⟩
  -- apply vec3

-- def Vec.toVector2 {α : Type u}: (n : ℕ) →  Vec α n → Vector α n
-- | 0, _ => ⟨ [], rfl ⟩
-- | k + 1, Vec.cons head tail => by
--   let vectorTail : Vector α k := Vec.toVector2 k tail
--   let ⟨Ltail, h⟩ := vectorTail
--   ------------------------
--   have lem1: Ltail = vectorTail.1 := by

--   ------------------------
--   have lem5 : (head :: Ltail).length = k + 1 := by
--     simp [List.length, h]
--   let vec3 : Vector α (k+1) := ⟨head :: Ltail, lem5⟩
--   apply vec3

-- #print Vec.toVector2

def V : Vec ℕ 3  := Vec.cons 1 (Vec.cons 3 (Vec.cons 2 Vec.nil))
#check (Vec.toVector 3 V).2

def vec : Vector ℕ 3 := ⟨[1,5,3],rfl⟩

#check vec
#check vec.2

/--  Vec.to_list {α : Type u} {n : ℕ} : Vec α n → List α
| Vec.nil => []
| Vec.cons head tail => head :: tail.to_list
-/
def p : Prop := (1 = 1) ∧ (2 = 2)
#check p
theorem A (h : p): 1 = 1 := by
  let ⟨ha, _⟩ := h
  apply ha

-- Mapping a `Vec` to a `Vector` and back gives the original `Vec` -/
theorem Vec.ofVector.toVector {α : Type u} (n : ℕ) (v : Vec α n) :
  Vec.ofVector n (Vec.toVector n v) = v :=
  match n with
  | 0 => by
    rw [Vec.ofVector]
    match v with
    | Vec.nil => rfl

  | k + 1 => by
    match v with
    | Vec.cons head tail =>
      simp [Vec.ofVector]
      apply Vec.ofVector.toVector k tail

#print Vec.ofVector.toVector
      -- let Lhead := (Vec.toVector (k + 1) (Vec.cons head tail)).head
      -- let Ltail := (Vec.toVector (k + 1) (Vec.cons head tail)).tail
      -- let h := (Vec.toVector (k + 1) (Vec.cons head tail)).2

      -- have lem1 : Lhead :: Ltail = head :: (Vec.toVector k tail).val := by
      --   simp [Vec.toVector]
      --   apply And.intro
      --   case left =>

/-- Mapping a `Vector` to a `Vec` and back gives the original `Vector` -/
theorem Vec.toVector.ofVector {α : Type u} (n : ℕ) (v : Vector α n) :
  Vec.toVector n (Vec.ofVector n v) = v :=
   match n with
  | 0 => by
    rw [Vec.toVector]
    match v with
    | Vector.nil => rfl

  | k + 1 => by
    match v with
    | ⟨ head :: tail, h⟩ =>
      simp[toVector]
      simp only [ofVector]

      -- have lem1 : head ::
      --   (toVector (k + 0 + 0)
      --   (ofVector (k + 0)
      --   { val := tail, property := (h1 : List.length tail = Nat.add k 0) })).val =
      --   head :: tail :=

#check Vec.ofVector
#print Vec.toVector.ofVector

-- /-- Mapping a `Vector` to a `Vec` and back gives the original `Vector` -/
-- theorem Vec.toVector.ofVector2 {α : Type u} (n : ℕ) (v : Vector α n) :
--   Vec.toVector n (Vec.ofVector n v) = v :=
--    match n with
--   | 0 => by
--     rw [Vec.toVector]
--     match v with
--     | Vector.nil => rfl

--   | k + 1 => by
--     match v with
--     | ⟨head :: tail, h⟩ =>
--       simp [Vec.toVector]

--       have lem1 : tail.length = k := by
--         simp [List.length_cons] at h
--         assumption

--       --apply Vec.toVector.ofVector k ⟨tail, lem1⟩

--       have lemt : (toVector k (Vec.ofVector k ⟨tail, lem1⟩)) = ⟨tail, lem1⟩ := by
--         apply Vec.toVector.ofVector k ⟨tail, lem1⟩

--       simp only [lemt]
--       -- head :: ↑(toVector (k + 0 + 0) (Vec.ofVector (k + 0) { val := tail, property := (_ : List.length tail = Nat.add k 0) }))

--       let vecTail : Vector α k := ⟨tail, lem1⟩

--       have lem2_val :
--       head :: (Vec.toVector k (Vec.ofVector k vecTail)).val =
--       head :: tail := by
--         simp
--         simp [Vec.toVector.ofVector, lem1]

--       simp [lem2_val]

--       have lem3 : (_ :
--         List.length
--             (head ::
--               ↑(toVector (Nat.add (k + 0) 0)
--                   (Vec.ofVector (k + 0) { val := tail, property := (_ : List.length tail = Nat.add k 0) }))) =
--           Nat.add (k + 0) 0 + 1

-- theorem Vec.toVector.ofVector2 {α : Type u} (n : ℕ) (v : Vector α n) :
--   Vec.toVector n (Vec.ofVector n v) = v := by
--     induction n
--     case zero =>
--       rw [Vec.toVector]
--       match v with
--       | Vector.nil => rfl
--     case succ =>
--       apply n_ih✝
--       simp [Vec.toVector]

      --apply Vec.toVector.ofVector k --⟨tail, lem1⟩

/-{
    val :=
      head ::
        ↑(toVector k
            (Vec.ofVector k { val := tail, property := (_ : List.length tail = k })),
    property :=
      (_ :
        List.length
            (head ::
              ↑(toVector k)
                  (Vec.ofVector k { val := tail, property := (_ : List.length tail = k) }))) =
          k + 1) } =
  { val := head :: tail, property := h }-/

/-(match
    toVector k (Vec.ofVector k { val := tail, property := (_ : List.length tail = k) }) with
  | { val := Ltail, property := h } =>
    { val := head :: Ltail, property := (_ : List.length Ltail= k) }) =
  { val := head :: tail, property := h }-/

#eval 1

#check Subtype
#check Subtype.mk -- (val : α) → p val → Subtype p
#check Subtype.property

universe u

inductive InfiniteTree (α : Type u) where
| leaf (label: α) : InfiniteTree α
| node : (ℕ → InfiniteTree α) → InfiniteTree α

inductive FiniteTree (α : Type u) where
| leaf (label: α) : FiniteTree α
| node : (List <| FiniteTree α)  → FiniteTree α

def And_implies_right (a b : Prop): a ∧ b → a := by
  intro hab
  apply And.left hab

/-def Distrubutive (a b c : Prop): a ∧ (b ∨ c) ↔ (a ∧ b) ∨ (a ∧ c) := by
  apply Iff.intro
  case mp =>
    intro h
    have ha : a := And.left h
    have hborc : b ∨ c := And.right h
    by_cases hc:c
    case inl =>
      have hac : a ∧ c := by apply And.intro ha hc
      apply Or.inr
      assumption
    case inr =>
        by_cases hb : b
        case inl =>
          have hab : a ∧ b := by apply And.intro ha hb
          apply Or.inl
          assumption
        case inr =>-/

-------------------------------------------------------------------------------
variable (Point : Type)
variable [pts_nonEmpty : Inhabited Point]

variable (Line : Type)
variable (lies_on : Point → Line → Prop)

def samePoint(p₁ p₂: Point) := p₁ = p₂

def some_point : Point := default

#check some_point

structure IncidenceGeometry where
  Line : Type
  Point : Type
  lies_on : Point → Line → Prop

def intersect {geom : IncidenceGeometry}(l₁ l₂ : geom.Line) : Prop :=
  ∃p : geom.Point, geom.lies_on p l₁ ∧ geom.lies_on p l₂

def intersect_on_pair {geom : IncidenceGeometry}(l₁ l₂ : geom.Line) : Prop :=
  ∃p₁ p₂ : geom.Point, geom.lies_on p₁ l₁ ∧ geom.lies_on p₁ l₂ ∧
            geom.lies_on p₂ l₁ ∧ geom.lies_on p₂ l₂ ∧ p₁ ≠ p₂

variable (congruence : {A : Type} → A → A →  Prop)

-- Congruence is equivalence relationship

axiom CongEquiv {A : Type} : IsEquiv A congruence

lemma CongRefl {A : Type} : ∀ a : A, congruence a a :=
  CongEquiv.refl

lemma CongSymm {A : Type} :
  ∀ a b : A, congruence a b → congruence b a :=
  CongEquiv.symm

lemma CongTrans {A : Type} :
  ∀ a b c : A,
    congruence a b → congruence b c → congruence a c :=
  CongEquiv.trans

axiom in_between : Point → Point → Point → Prop

structure Segment : Type :=
  p1 : Point
  p2 : Point

axiom distance (Apt Bpt : Point) : ℝ

-- Distance axioms
axiom dist_is_not_neg (A B : Point): distance A B ≥ 0
axiom dist_same_point (A : Point) : distance A A = 0
axiom dist_geq_0 (A B : Point) : A ≠ B ↔ distance A B >  0
axiom dist_is_symm (A B : Point) : distance A B = distance B A
axiom dist_tri_ineq (A B C : Point) :
distance A B + distance B C ≥ distance A C

-- Axioms when a point is in between other two
axiom dist_in_between (a b c : Point) (h : in_between a b c) :
distance a b + distance b c = distance a c

axiom between_refl_left (a b : Point) : in_between a a b
axiom between_refl_right (a b : Point) : in_between a b b

--Euclid postulates

--Postulate 1
--Between two points there is an unique line passing through them
axiom Line_of_two_points (A B : Point) (h : A ≠ B): Line --says you get a line from two points
axiom point_contain (A B : Point) (h : A ≠ B) : --says such a line contain both the points
  have l : Line := Line_of_two_points A B h
  lies_on A l ∧ lies_on B l

axiom line_unique (A B: Point) (h : A ≠ B) (l1 l2 : Line): --says such a line is unique
  (lies_on A l1 ∧ lies_on B l2) ∧ (lies_on A l2 ∧ lies_on B l2) → l1 = l2

axiom IsCoincide (l1 l2 : Line) : l1 = l2

--Postulate 2
--A line segment can be extended to get a line
axiom line_from_seg (seg : Segment) : Line --says a you get a line from a segment
axiom contain_end_points (seg : Segment) : --says the end points of the segment lies on the resulting line
  lies_on (seg.p1) (line_from_seg seg) ∧ lies_on (seg.p2) (line_from_seg seg)

axiom length_of_seg (seg : Segment) : ℝ
axiom Length_of_seg (seg : Segment) : length_of_seg seg = distance seg.p1 seg.p2

--Postulate 3
--A circle can be drawn from any centre and any radius
structure Circle : Type :=
  centre : Point
  radius : ℝ

axiom On_circle (A : Point) (C : Circle) :
distance C.centre A = C.radius

/-
def Equi_triangle (A B C : Point) : Prop :=
distance A B = distance A C ∧ distance A C = distance B C

theorem Elements_th1 (s : Segment) :
∃ C : Point , Equi_triangle (s.p1 s.p2 C) := by
  have c1 : Circle := Circle.mk s.p1 (length_of_seg s)
  have c2 : Circle := Circle.mk s.p2 (length_of_seg s)

-/

--- (c1 : Circle) : c1.centre = segAB.p1 ∧ c1.radius = (distance (segAB.p1 segAB.p2)) →

--Postulate 4
--All right angles are equal
structure Angle : Type :=
  p1 : Point
  Pivot : Point
  p2 : Point
-- check whether p1 and pivot are different points
axiom reflexAngle : Angle → Angle
axiom mAngle : Angle → ℝ

-- properties of Angle
axiom AngleSymm (a b c : Point) : Angle.mk a b c = Angle.mk c b a

axiom MakingIntPointAngle (a : Point) (A : Angle) : Prop

-- property of "introducing an interior point 'a' within an angle A"
def IntPointAngle (a : Point) (A : Angle) (_ : MakingIntPointAngle a A): Prop :=
  have A1 : Angle := Angle.mk a A.Pivot A.p1
  have A2 : Angle := Angle.mk a A.Pivot A.p2
  (mAngle (A1) < mAngle  A)
  ∧ (mAngle (A2) < mAngle A) -- strict inequality because of 120

#check IntPointAngle
-- properties of mAngle

axiom mAngle_non_neg (a b c : Point) : mAngle (Angle.mk a b c) ≥ 0
axiom ZeroAngle (a b c : Point) (hc : in_between a c b): mAngle (Angle.mk a b c) = 0
axiom mAngle_postive (a b c : Point) (hc : ¬ in_between a c b) (ha : ¬ in_between c a b):
  mAngle (Angle.mk a b c) > 0

axiom mReflexAngle (A : Angle) : mAngle (reflexAngle A) = 360 - mAngle A

-- Angle A as Sum of its constituents
axiom mAngleAdd (a : Point) (A : Angle) (hInt : MakingIntPointAngle a A)(h : IntPointAngle a A hInt) :
  have A1 : Angle := Angle.mk a A.Pivot A.p1
  have A2 : Angle := Angle.mk a A.Pivot A.p2
  mAngle A = mAngle A1 + mAngle A2

-- Given Angle A, if we introduce segment (a A.Pivot), then
-- the constiuents sum to the whole Angle A. We can obtain the
-- this as a theorem to MakingIntPointAngle, IntPointAngle, mAngleAdd.

-- Postulate 4 says all right angles are equal.
-- We are assigning it a value of 90
axiom isRightAngle (A : Angle): mAngle A = 90
-- TODO: straight line is two right angles
axiom StraightAngle (a b c : Point) (h : in_between a b c) : mAngle (Angle.mk a b c) = 180

-- Postulate 5
-- If a straight line falling on two straight lines makes the interior
-- angles on the same side of it taken together less than two right angles, then the
-- two straight lines, if produced indefinitely, meet on that side on which the sum of
-- angles is less than two right angles.

axiom IsParallel (l1 l2 : Line): Prop -- defn of parallel lines
axiom IsParrallel_isequiv : sorry

-- need to take care of the case when l1 = l2 for IsIntersect.
-- we probably don't need this

-- property of IsIntersect
axiom PointFromIntersect (l1 l2 : Line) (h : ¬ IsParallel l1 l2) :
  ∃ c : Point, lies_on c l1 ∧ lies_on c l2

-- Unique intersection point of two lines
theorem UniqueIntersectPoint (l1 l2 : Line) (h1 : l1 ≠ l2) (h2 : ¬ IsParallel l1 l2 h1) :
  lies_on (a : Point) l1 ∧ lies_on a l2
  →
  lies_on  (b : Point) l1 ∧ lies_on b l2
  →
  a = b
  := sorry

-/
-- Towards same-sided-ness
-- entire segment is on the line
/-def SegmentOnLine (A : Segment) (l : Line) : Prop :=
  lies_on A.p1 l ∧ lies_on A.p2 l-/

-- points on a segment
def PointOnSegment (a : Point) (A : Segment) : Prop :=
  in_between A.p1 a A.p2

axiom SegLineIntersect (A : Segment) (l : Line) (h : ¬ SegmentOnLine A l)
  : Prop
-- need to introduce property of SegLineIntersect
-- have not accounted for the case where segment "stands on" the line

-- Points on the same side of a line segment
axiom SameSidedPoints (a b : Point) (l : Line)
  (h1 : ¬ SegmentOnLine (Segment.mk a b) l)
  (h2 : ¬ SegLineIntersect (Segment.mk a b) l h1)
  : Prop

axiom IntersectingLines (A B C : Segment)
  (h1 : ¬ SegmentOnLine (Segment.mk A.p1 B.p1) (line_from_seg C))
  (h2 : ¬ SegLineIntersect (Segment.mk A.p1 B.p1) (line_from_seg C) h1)
  :
  SameSidedPoints (A.p1) (B.p1) (line_from_seg C) h1 h2
  →

  /-
  →
  (in_between A.p1 C.p1 A.p2) ∧ (in_between B.p1 C.p2 B.p2)
  -- what about C.p1 = A.p1? is this not taken care of by SegLineIntersect?
  → -/
--  (need to bring in "same-sided-ness")