/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl
-/
import algebra.geom_sum
import order.filter.archimedean
import order.iterate
import topology.instances.ennreal
import tactic.ring_exp
import analysis.asymptotics.asymptotics

/-!
# A collection of specific limit computations
-/

noncomputable theory
open classical set function filter finset metric asymptotics

open_locale classical topological_space nat big_operators uniformity nnreal ennreal

variables {α : Type*} {β : Type*} {ι : Type*}

lemma tendsto_norm_at_top_at_top : tendsto (norm : ℝ → ℝ) at_top at_top :=
tendsto_abs_at_top_at_top

lemma summable_of_absolute_convergence_real {f : ℕ → ℝ} :
  (∃r, tendsto (λn, (∑ i in range n, abs (f i))) at_top (𝓝 r)) → summable f
| ⟨r, hr⟩ :=
  begin
    refine summable_of_summable_norm ⟨r, (has_sum_iff_tendsto_nat_of_nonneg _ _).2 _⟩,
    exact assume i, norm_nonneg _,
    simpa only using hr
  end

lemma tendsto_inverse_at_top_nhds_0_nat : tendsto (λ n : ℕ, (n : ℝ)⁻¹) at_top (𝓝 0) :=
tendsto_inv_at_top_zero.comp tendsto_coe_nat_at_top_at_top

lemma tendsto_const_div_at_top_nhds_0_nat (C : ℝ) : tendsto (λ n : ℕ, C / n) at_top (𝓝 0) :=
by simpa only [mul_zero] using tendsto_const_nhds.mul tendsto_inverse_at_top_nhds_0_nat

lemma nnreal.tendsto_inverse_at_top_nhds_0_nat : tendsto (λ n : ℕ, (n : ℝ≥0)⁻¹) at_top (𝓝 0) :=
by { rw ← nnreal.tendsto_coe, convert tendsto_inverse_at_top_nhds_0_nat, simp }

lemma nnreal.tendsto_const_div_at_top_nhds_0_nat (C : ℝ≥0) :
  tendsto (λ n : ℕ, C / n) at_top (𝓝 0) :=
by simpa using tendsto_const_nhds.mul nnreal.tendsto_inverse_at_top_nhds_0_nat

lemma tendsto_one_div_add_at_top_nhds_0_nat :
  tendsto (λ n : ℕ, 1 / ((n : ℝ) + 1)) at_top (𝓝 0) :=
suffices tendsto (λ n : ℕ, 1 / (↑(n + 1) : ℝ)) at_top (𝓝 0), by simpa,
(tendsto_add_at_top_iff_nat 1).2 (tendsto_const_div_at_top_nhds_0_nat 1)

/-! ### Powers -/

lemma tendsto_add_one_pow_at_top_at_top_of_pos [linear_ordered_semiring α] [archimedean α] {r : α}
  (h : 0 < r) :
  tendsto (λ n:ℕ, (r + 1)^n) at_top at_top :=
tendsto_at_top_at_top_of_monotone' (λ n m, pow_le_pow (le_add_of_nonneg_left (le_of_lt h))) $
  not_bdd_above_iff.2 $ λ x, set.exists_range_iff.2 $ add_one_pow_unbounded_of_pos _ h

lemma tendsto_pow_at_top_at_top_of_one_lt [linear_ordered_ring α] [archimedean α]
  {r : α} (h : 1 < r) :
  tendsto (λn:ℕ, r ^ n) at_top at_top :=
sub_add_cancel r 1 ▸ tendsto_add_one_pow_at_top_at_top_of_pos (sub_pos.2 h)

lemma nat.tendsto_pow_at_top_at_top_of_one_lt {m : ℕ} (h : 1 < m) :
  tendsto (λn:ℕ, m ^ n) at_top at_top :=
nat.sub_add_cancel (le_of_lt h) ▸
  tendsto_add_one_pow_at_top_at_top_of_pos (nat.sub_pos_of_lt h)

lemma tendsto_norm_zero' {𝕜 : Type*} [normed_group 𝕜] :
  tendsto (norm : 𝕜 → ℝ) (𝓝[{x | x ≠ 0}] 0) (𝓝[set.Ioi 0] 0) :=
tendsto_norm_zero.inf $ tendsto_principal_principal.2 $ λ x hx, norm_pos_iff.2 hx

lemma normed_field.tendsto_norm_inverse_nhds_within_0_at_top {𝕜 : Type*} [normed_field 𝕜] :
  tendsto (λ x:𝕜, ∥x⁻¹∥) (𝓝[{x | x ≠ 0}] 0) at_top :=
(tendsto_inv_zero_at_top.comp tendsto_norm_zero').congr $ λ x, (normed_field.norm_inv x).symm

lemma tendsto_pow_at_top_nhds_0_of_lt_1 {𝕜 : Type*} [linear_ordered_field 𝕜] [archimedean 𝕜]
  [topological_space 𝕜] [order_topology 𝕜] {r : 𝕜} (h₁ : 0 ≤ r) (h₂ : r < 1) :
  tendsto (λn:ℕ, r^n) at_top (𝓝 0) :=
h₁.eq_or_lt.elim
  (assume : 0 = r,
    (tendsto_add_at_top_iff_nat 1).mp $ by simp [pow_succ, ← this, tendsto_const_nhds])
  (assume : 0 < r,
    have tendsto (λn, (r⁻¹ ^ n)⁻¹) at_top (𝓝 0),
      from tendsto_inv_at_top_zero.comp
        (tendsto_pow_at_top_at_top_of_one_lt $ one_lt_inv this h₂),
    this.congr (λ n, by simp))

lemma tendsto_pow_at_top_nhds_within_0_of_lt_1 {𝕜 : Type*} [linear_ordered_field 𝕜] [archimedean 𝕜]
  [topological_space 𝕜] [order_topology 𝕜] {r : 𝕜} (h₁ : 0 < r) (h₂ : r < 1) :
  tendsto (λn:ℕ, r^n) at_top (𝓝[Ioi 0] 0) :=
tendsto_inf.2 ⟨tendsto_pow_at_top_nhds_0_of_lt_1 h₁.le h₂,
  tendsto_principal.2 $ eventually_of_forall $ λ n, pow_pos h₁ _⟩

lemma is_o_pow_pow_of_lt_left {r₁ r₂ : ℝ} (h₁ : 0 ≤ r₁) (h₂ : r₁ < r₂) :
  is_o (λ n : ℕ, r₁ ^ n) (λ n, r₂ ^ n) at_top :=
have H : 0 < r₂ := h₁.trans_lt h₂,
is_o_of_tendsto (λ n hn, false.elim $ H.ne' $ pow_eq_zero hn) $
  (tendsto_pow_at_top_nhds_0_of_lt_1 (div_nonneg h₁ (h₁.trans h₂.le)) ((div_lt_one H).2 h₂)).congr
    (λ n, div_pow _ _ _)

lemma is_O_pow_pow_of_le_left {r₁ r₂ : ℝ} (h₁ : 0 ≤ r₁) (h₂ : r₁ ≤ r₂) :
  is_O (λ n : ℕ, r₁ ^ n) (λ n, r₂ ^ n) at_top :=
h₂.eq_or_lt.elim (λ h, h ▸ is_O_refl _ _) (λ h, (is_o_pow_pow_of_lt_left h₁ h).is_O)

lemma is_o_pow_pow_of_abs_lt_left {r₁ r₂ : ℝ} (h : abs r₁ < abs r₂) :
  is_o (λ n : ℕ, r₁ ^ n) (λ n, r₂ ^ n) at_top :=
begin
  refine (is_o.of_norm_left _).of_norm_right,
  exact (is_o_pow_pow_of_lt_left (abs_nonneg r₁) h).congr (pow_abs r₁) (pow_abs r₂)
end

/-- 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. -/
lemma tfae_exists_lt_is_o_pow (f : ℕ → ℝ) (R : ℝ) :
  tfae [∃ a ∈ Ioo (-R) R, is_o f (pow a) at_top,
    ∃ a ∈ Ioo 0 R, is_o f (pow a) at_top,
    ∃ a ∈ Ioo (-R) R, is_O f (pow a) at_top,
    ∃ a ∈ Ioo 0 R, is_O f (pow a) at_top,
    ∃ (a < R) C (h₀ : 0 < C ∨ 0 < R), ∀ n, abs (f n) ≤ C * a ^ n,
    ∃ (a ∈ Ioo 0 R) (C > 0), ∀ n, abs (f n) ≤ C * a ^ n,
    ∃ a < R, ∀ᶠ n in at_top, abs (f n) ≤ a ^ n,
    ∃ a ∈ Ioo 0 R, ∀ᶠ n in at_top, abs (f n) ≤ a ^ n] :=
begin
  have A : Ico 0 R ⊆ Ioo (-R) R,
    from λ 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, from λ ⟨a, ha, H⟩, ⟨a, ha, H.is_O⟩,
  tfae_have : 2 → 1, from λ ⟨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 (is_o_pow_pow_of_abs_lt_left (hab.trans_le (le_abs_self b)))⟩ },
  tfae_have : 2 → 4, from λ ⟨a, ha, H⟩, ⟨a, ha, H.is_O⟩,
  tfae_have : 4 → 3, from λ ⟨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₀, λ 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, from λ ⟨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 (λ 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] at h₀,
      exact ⟨0, ⟨neg_lt_zero.2 h₀, h₀⟩, is_O_zero _ _⟩ },
    exact ⟨a, A ⟨ha₀, ha⟩,
      is_O_of_le' _ (λ 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 (λ 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, from λ ⟨a, ha, H⟩, ⟨a, ha.2, H⟩,
  tfae_have : 7 → 3,
  { rintro ⟨a, ha, H⟩,
    have : 0 ≤ a, from nonneg_of_eventually_pow_nonneg (H.mono $ λ 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
end

lemma uniformity_basis_dist_pow_of_lt_1 {α : Type*} [metric_space α]
  {r : ℝ} (h₀ : 0 < r) (h₁ : r < 1) :
  (𝓤 α).has_basis (λ k : ℕ, true) (λ k, {p : α × α | dist p.1 p.2 < r ^ k}) :=
metric.mk_uniformity_basis (λ i _, pow_pos h₀ _) $ λ ε ε0,
  (exists_pow_lt_of_lt_one ε0 h₁).imp $ λ k hk, ⟨trivial, hk.le⟩

lemma geom_lt {u : ℕ → ℝ} {c : ℝ} (hc : 0 ≤ c) {n : ℕ} (hn : 0 < n)
  (h : ∀ k < n, c * u k < u (k + 1)) :
  c ^ n * u 0 < u n :=
begin
  refine (monotone_mul_left_of_nonneg hc).seq_pos_lt_seq_of_le_of_lt hn _ _ h,
  { simp },
  { simp [pow_succ, mul_assoc, le_refl] }
end

lemma geom_le {u : ℕ → ℝ} {c : ℝ} (hc : 0 ≤ c) (n : ℕ) (h : ∀ k < n, c * u k ≤ u (k + 1)) :
  c ^ n * u 0 ≤ u n :=
by refine (monotone_mul_left_of_nonneg hc).seq_le_seq n _ _ h; simp [pow_succ, mul_assoc, le_refl]

lemma lt_geom {u : ℕ → ℝ} {c : ℝ} (hc : 0 ≤ c) {n : ℕ} (hn : 0 < n)
  (h : ∀ k < n, u (k + 1) < c * u k) :
  u n < c ^ n * u 0 :=
begin
  refine (monotone_mul_left_of_nonneg hc).seq_pos_lt_seq_of_lt_of_le hn _ h _,
  { simp },
  { simp [pow_succ, mul_assoc, le_refl] }
end

lemma le_geom {u : ℕ → ℝ} {c : ℝ} (hc : 0 ≤ c) (n : ℕ) (h : ∀ k < n, u (k + 1) ≤ c * u k) :
  u n ≤ (c ^ n) * u 0 :=
by refine (monotone_mul_left_of_nonneg hc).seq_le_seq n _ h _; simp [pow_succ, mul_assoc, le_refl]

/-- For any natural `k` and a real `r > 1` we have `n ^ k = o(r ^ n)` as `n → ∞`. -/
lemma is_o_pow_const_const_pow_of_one_lt {R : Type*} [normed_ring R] (k : ℕ) {r : ℝ} (hr : 1 < r) :
  is_o (λ n, n ^ k : ℕ → R) (λ n, r ^ n) at_top :=
begin
  have : tendsto (λ x : ℝ, x ^ k) (𝓝[Ioi 1] 1) (𝓝 1),
    from ((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_nhds_within).exists,
  have h0 : 0 ≤ r' := zero_le_one.trans h1.le,
  suffices : is_O _ (λ n : ℕ, (r' ^ k) ^ n) at_top,
    from this.trans_is_o (is_o_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∥,
    from (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
end

/-- For a real `r > 1` we have `n = o(r ^ n)` as `n → ∞`. -/
lemma is_o_coe_const_pow_of_one_lt {R : Type*} [normed_ring R] {r : ℝ} (hr : 1 < r) :
  is_o (coe : ℕ → R) (λ n, r ^ n) at_top :=
by simpa only [pow_one] using is_o_pow_const_const_pow_of_one_lt 1 hr

/-- If `∥r₁∥ < r₂`, then for any naturak `k` we have `n ^ k r₁ ^ n = o (r₂ ^ n)` as `n → ∞`. -/
lemma is_o_pow_const_mul_const_pow_const_pow_of_norm_lt {R : Type*} [normed_ring R] (k : ℕ)
  {r₁ : R} {r₂ : ℝ} (h : ∥r₁∥ < r₂) :
  is_o (λ n, n ^ k * r₁ ^ n : ℕ → R) (λ n, r₂ ^ n) at_top :=
begin
  by_cases h0 : r₁ = 0,
  { refine (is_o_zero _ _).congr' (mem_at_top_sets.2 $ ⟨1, λ 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 : is_o (λ n, n ^ k : ℕ → R) (λ n, (r₂ / ∥r₁∥) ^ n) at_top,
    from is_o_pow_const_const_pow_of_one_lt k ((one_lt_div h0).2 h),
  suffices : is_O (λ n, r₁ ^ n) (λ n, ∥r₁∥ ^ n) at_top,
    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₁)
end

lemma tendsto_pow_const_div_const_pow_of_one_lt (k : ℕ) {r : ℝ} (hr : 1 < r) :
  tendsto (λ n, n ^ k / r ^ n : ℕ → ℝ) at_top (𝓝 0) :=
(is_o_pow_const_const_pow_of_one_lt k hr).tendsto_0

/-- If `|r| < 1`, then `n ^ k r ^ n` tends to zero for any natural `k`. -/
lemma tendsto_pow_const_mul_const_pow_of_abs_lt_one (k : ℕ) {r : ℝ} (hr : abs r < 1) :
  tendsto (λ n, n ^ k * r ^ n : ℕ → ℝ) at_top (𝓝 0) :=
begin
  by_cases h0 : r = 0,
  { exact tendsto_const_nhds.congr'
      (mem_at_top_sets.2 ⟨1, λ n hn, by simp [zero_lt_one.trans_le hn, h0]⟩) },
  have hr' : 1 < (abs r)⁻¹, from 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'
end

/-- If a sequence `v` of real numbers satisfies `k * v n ≤ v (n+1)` with `1 < k`,
then it goes to +∞. -/
lemma tendsto_at_top_of_geom_le {v : ℕ → ℝ} {c : ℝ} (h₀ : 0 < v 0) (hc : 1 < c)
  (hu : ∀ n, c * v n ≤ v (n + 1)) : tendsto v at_top at_top :=
tendsto_at_top_mono (λ n, geom_le (zero_le_one.trans hc.le) n (λ k hk, hu k)) $
  (tendsto_pow_at_top_at_top_of_one_lt hc).at_top_mul_const h₀

lemma nnreal.tendsto_pow_at_top_nhds_0_of_lt_1 {r : ℝ≥0} (hr : r < 1) :
  tendsto (λ n:ℕ, r^n) at_top (𝓝 0) :=
nnreal.tendsto_coe.1 $ by simp only [nnreal.coe_pow, nnreal.coe_zero,
  tendsto_pow_at_top_nhds_0_of_lt_1 r.coe_nonneg hr]

lemma ennreal.tendsto_pow_at_top_nhds_0_of_lt_1 {r : ℝ≥0∞} (hr : r < 1) :
  tendsto (λ n:ℕ, r^n) at_top (𝓝 0) :=
begin
  rcases ennreal.lt_iff_exists_coe.1 hr with ⟨r, rfl, hr'⟩,
  rw [← ennreal.coe_zero],
  norm_cast at *,
  apply nnreal.tendsto_pow_at_top_nhds_0_of_lt_1 hr
end

/-- In a normed ring, the powers of an element x with `∥x∥ < 1` tend to zero. -/
lemma tendsto_pow_at_top_nhds_0_of_norm_lt_1 {R : Type*} [normed_ring R] {x : R}
  (h : ∥x∥ < 1) : tendsto (λ (n : ℕ), x ^ n) at_top (𝓝 0) :=
begin
  apply squeeze_zero_norm' (eventually_norm_pow_le x),
  exact tendsto_pow_at_top_nhds_0_of_lt_1 (norm_nonneg _) h,
end

lemma tendsto_pow_at_top_nhds_0_of_abs_lt_1 {r : ℝ} (h : abs r < 1) :
  tendsto (λn:ℕ, r^n) at_top (𝓝 0) :=
tendsto_pow_at_top_nhds_0_of_norm_lt_1 h

/-! ### Geometric series-/
section geometric

lemma has_sum_geometric_of_lt_1 {r : ℝ} (h₁ : 0 ≤ r) (h₂ : r < 1) :
  has_sum (λn:ℕ, r ^ n) (1 - r)⁻¹ :=
have r ≠ 1, from ne_of_lt h₂,
have tendsto (λn, (r ^ n - 1) * (r - 1)⁻¹) at_top (𝓝 ((0 - 1) * (r - 1)⁻¹)),
  from ((tendsto_pow_at_top_nhds_0_of_lt_1 h₁ h₂).sub tendsto_const_nhds).mul tendsto_const_nhds,
have (λ n, (∑ i in range n, r ^ i)) = (λ n, geom_sum r n) := rfl,
(has_sum_iff_tendsto_nat_of_nonneg (pow_nonneg h₁) _).mpr $
  by simp [neg_inv, geom_sum_eq, div_eq_mul_inv, *] at *

lemma summable_geometric_of_lt_1 {r : ℝ} (h₁ : 0 ≤ r) (h₂ : r < 1) : summable (λn:ℕ, r ^ n) :=
⟨_, has_sum_geometric_of_lt_1 h₁ h₂⟩

lemma tsum_geometric_of_lt_1 {r : ℝ} (h₁ : 0 ≤ r) (h₂ : r < 1) : ∑'n:ℕ, r ^ n = (1 - r)⁻¹ :=
(has_sum_geometric_of_lt_1 h₁ h₂).tsum_eq

lemma has_sum_geometric_two : has_sum (λn:ℕ, ((1:ℝ)/2) ^ n) 2 :=
by convert has_sum_geometric_of_lt_1 _ _; norm_num

lemma summable_geometric_two : summable (λn:ℕ, ((1:ℝ)/2) ^ n) :=
⟨_, has_sum_geometric_two⟩

lemma tsum_geometric_two : ∑'n:ℕ, ((1:ℝ)/2) ^ n = 2 :=
has_sum_geometric_two.tsum_eq

lemma sum_geometric_two_le (n : ℕ) : ∑ (i : ℕ) in range n, (1 / (2 : ℝ)) ^ i ≤ 2 :=
begin
  have : ∀ i, 0 ≤ (1 / (2 : ℝ)) ^ i,
  { intro i, apply pow_nonneg, norm_num },
  convert sum_le_tsum (range n) (λ i _, this i) summable_geometric_two,
  exact tsum_geometric_two.symm
end

lemma has_sum_geometric_two' (a : ℝ) : has_sum (λn:ℕ, (a / 2) / 2 ^ n) a :=
begin
  convert has_sum.mul_left (a / 2) (has_sum_geometric_of_lt_1
    (le_of_lt one_half_pos) one_half_lt_one),
  { funext n, simp, refl, },
  { norm_num }
end

lemma summable_geometric_two' (a : ℝ) : summable (λ n:ℕ, (a / 2) / 2 ^ n) :=
⟨a, has_sum_geometric_two' a⟩

lemma tsum_geometric_two' (a : ℝ) : ∑' n:ℕ, (a / 2) / 2^n = a :=
(has_sum_geometric_two' a).tsum_eq

lemma nnreal.has_sum_geometric {r : ℝ≥0} (hr : r < 1) :
  has_sum (λ n : ℕ, r ^ n) (1 - r)⁻¹ :=
begin
  apply nnreal.has_sum_coe.1,
  push_cast,
  rw [nnreal.coe_sub (le_of_lt hr)],
  exact has_sum_geometric_of_lt_1 r.coe_nonneg hr
end

lemma nnreal.summable_geometric {r : ℝ≥0} (hr : r < 1) : summable (λn:ℕ, r ^ n) :=
⟨_, nnreal.has_sum_geometric hr⟩

lemma tsum_geometric_nnreal {r : ℝ≥0} (hr : r < 1) : ∑'n:ℕ, r ^ n = (1 - r)⁻¹ :=
(nnreal.has_sum_geometric hr).tsum_eq

/-- The series `pow r` converges to `(1-r)⁻¹`. For `r < 1` the RHS is a finite number,
and for `1 ≤ r` the RHS equals `∞`. -/
lemma ennreal.tsum_geometric (r : ℝ≥0∞) : ∑'n:ℕ, r ^ n = (1 - r)⁻¹ :=
begin
  cases lt_or_le r 1 with hr hr,
  { rcases ennreal.lt_iff_exists_coe.1 hr with ⟨r, rfl, hr'⟩,
    norm_cast at *,
    convert ennreal.tsum_coe_eq (nnreal.has_sum_geometric hr),
    rw [ennreal.coe_inv $ ne_of_gt $ nnreal.sub_pos.2 hr] },
  { rw [ennreal.sub_eq_zero_of_le hr, ennreal.inv_zero, ennreal.tsum_eq_supr_nat, supr_eq_top],
    refine λ a ha, (ennreal.exists_nat_gt (lt_top_iff_ne_top.1 ha)).imp
      (λ n hn, lt_of_lt_of_le hn _),
    have : ∀ k:ℕ, 1 ≤ r^k, by simpa using canonically_ordered_semiring.pow_le_pow_of_le_left hr,
    calc (n:ℝ≥0∞) = (∑ i in range n, 1) : by rw [sum_const, nsmul_one, card_range]
    ... ≤ ∑ i in range n, r ^ i : sum_le_sum (λ k _, this k) }
end

variables {K : Type*} [normed_field K] {ξ : K}

lemma has_sum_geometric_of_norm_lt_1 (h : ∥ξ∥ < 1) : has_sum (λn:ℕ, ξ ^ n) (1 - ξ)⁻¹ :=
begin
  have xi_ne_one : ξ ≠ 1, by { contrapose! h, simp [h] },
  have A : tendsto (λn, (ξ ^ n - 1) * (ξ - 1)⁻¹) at_top (𝓝 ((0 - 1) * (ξ - 1)⁻¹)),
    from ((tendsto_pow_at_top_nhds_0_of_norm_lt_1 h).sub tendsto_const_nhds).mul tendsto_const_nhds,
  have B : (λ n, (∑ i in range n, ξ ^ i)) = (λ n, geom_sum ξ n) := rfl,
  rw [has_sum_iff_tendsto_nat_of_summable_norm, B],
  { simpa [geom_sum_eq, xi_ne_one, neg_inv, div_eq_mul_inv] using A },
  { simp [normed_field.norm_pow, summable_geometric_of_lt_1 (norm_nonneg _) h] }
end

lemma summable_geometric_of_norm_lt_1 (h : ∥ξ∥ < 1) : summable (λn:ℕ, ξ ^ n) :=
⟨_, has_sum_geometric_of_norm_lt_1 h⟩

lemma tsum_geometric_of_norm_lt_1 (h : ∥ξ∥ < 1) : ∑'n:ℕ, ξ ^ n = (1 - ξ)⁻¹ :=
(has_sum_geometric_of_norm_lt_1 h).tsum_eq

lemma has_sum_geometric_of_abs_lt_1 {r : ℝ} (h : abs r < 1) : has_sum (λn:ℕ, r ^ n) (1 - r)⁻¹ :=
has_sum_geometric_of_norm_lt_1 h

lemma summable_geometric_of_abs_lt_1 {r : ℝ} (h : abs r < 1) : summable (λn:ℕ, r ^ n) :=
summable_geometric_of_norm_lt_1 h

lemma tsum_geometric_of_abs_lt_1 {r : ℝ} (h : abs r < 1) : ∑'n:ℕ, r ^ n = (1 - r)⁻¹ :=
tsum_geometric_of_norm_lt_1 h

/-- A geometric series in a normed field is summable iff the norm of the common ratio is less than
one. -/
@[simp] lemma summable_geometric_iff_norm_lt_1 : summable (λ n : ℕ, ξ ^ n) ↔ ∥ξ∥ < 1 :=
begin
  refine ⟨λ 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 [normed_field.norm_pow, dist_zero_right] at hk,
  rw [← one_pow k] at hk,
  exact lt_of_pow_lt_pow _ zero_le_one hk
end

end geometric

section mul_geometric

lemma summable_norm_pow_mul_geometric_of_norm_lt_1 {R : Type*} [normed_ring R]
  (k : ℕ) {r : R} (hr : ∥r∥ < 1) : summable (λ n : ℕ, ∥(n ^ k * r ^ n : R)∥) :=
begin
  rcases exists_between hr with ⟨r', hrr', h⟩,
  exact summable_of_is_O_nat _ (summable_geometric_of_lt_1 ((norm_nonneg _).trans hrr'.le) h)
    (is_o_pow_const_mul_const_pow_const_pow_of_norm_lt _ hrr').is_O.norm_left
end

lemma summable_pow_mul_geometric_of_norm_lt_1 {R : Type*} [normed_ring R] [complete_space R]
  (k : ℕ) {r : R} (hr : ∥r∥ < 1) : summable (λ n, n ^ k * r ^ n : ℕ → R) :=
summable_of_summable_norm $ summable_norm_pow_mul_geometric_of_norm_lt_1 _ hr

/-- If `∥r∥ < 1`, then `∑' n : ℕ, n * r ^ n = r / (1 - r) ^ 2`, `has_sum` version. -/
lemma has_sum_coe_mul_geometric_of_norm_lt_1 {𝕜 : Type*} [normed_field 𝕜] [complete_space 𝕜]
  {r : 𝕜} (hr : ∥r∥ < 1) : has_sum (λ n, n * r ^ n : ℕ → 𝕜) (r / (1 - r) ^ 2) :=
begin
  have A : summable (λ n, n * r ^ n : ℕ → 𝕜),
    by simpa using summable_pow_mul_geometric_of_norm_lt_1 1 hr,
  have B : has_sum (pow r : ℕ → 𝕜) (1 - r)⁻¹, from has_sum_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 { congr, exact 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_eq_div_mul]
end

/-- If `∥r∥ < 1`, then `∑' n : ℕ, n * r ^ n = r / (1 - r) ^ 2`. -/
lemma tsum_coe_mul_geometric_of_norm_lt_1 {𝕜 : Type*} [normed_field 𝕜] [complete_space 𝕜]
  {r : 𝕜} (hr : ∥r∥ < 1) :
  (∑' n : ℕ, n * r ^ n : 𝕜) = (r / (1 - r) ^ 2) :=
(has_sum_coe_mul_geometric_of_norm_lt_1 hr).tsum_eq

end mul_geometric

/-!
### Sequences with geometrically decaying distance in metric spaces

In this paragraph, we discuss sequences in metric spaces or emetric spaces for which the distance
between two consecutive terms decays geometrically. We show that such sequences are Cauchy
sequences, and bound their distances to the limit. We also discuss series with geometrically
decaying terms.
-/
section edist_le_geometric

variables [emetric_space α] (r C : ℝ≥0∞) (hr : r < 1) (hC : C ≠ ⊤) {f : ℕ → α}
  (hu : ∀n, edist (f n) (f (n+1)) ≤ C * r^n)

include hr hC hu

/-- If `edist (f n) (f (n+1))` is bounded by `C * r^n`, `C ≠ ∞`, `r < 1`,
then `f` is a Cauchy sequence.-/
lemma cauchy_seq_of_edist_le_geometric : cauchy_seq f :=
begin
  refine cauchy_seq_of_edist_le_of_tsum_ne_top _ hu _,
  rw [ennreal.tsum_mul_left, ennreal.tsum_geometric],
  refine ennreal.mul_ne_top hC (ennreal.inv_ne_top.2 _),
  exact ne_of_gt (ennreal.zero_lt_sub_iff_lt.2 hr)
end

omit hr hC

/-- If `edist (f n) (f (n+1))` is bounded by `C * r^n`, then the distance from
`f n` to the limit of `f` is bounded above by `C * r^n / (1 - r)`. -/
lemma edist_le_of_edist_le_geometric_of_tendsto {a : α} (ha : tendsto f at_top (𝓝 a)) (n : ℕ) :
  edist (f n) a ≤ (C * r^n) / (1 - r) :=
begin
  convert edist_le_tsum_of_edist_le_of_tendsto _ hu ha _,
  simp only [pow_add, ennreal.tsum_mul_left, ennreal.tsum_geometric, div_eq_mul_inv, mul_assoc]
end

/-- If `edist (f n) (f (n+1))` is bounded by `C * r^n`, then the distance from
`f 0` to the limit of `f` is bounded above by `C / (1 - r)`. -/
lemma edist_le_of_edist_le_geometric_of_tendsto₀ {a : α} (ha : tendsto f at_top (𝓝 a)) :
  edist (f 0) a ≤ C / (1 - r) :=
by simpa only [pow_zero, mul_one] using edist_le_of_edist_le_geometric_of_tendsto r C hu ha 0

end edist_le_geometric

section edist_le_geometric_two

variables [emetric_space α] (C : ℝ≥0∞) (hC : C ≠ ⊤) {f : ℕ → α}
  (hu : ∀n, edist (f n) (f (n+1)) ≤ C / 2^n) {a : α} (ha : tendsto f at_top (𝓝 a))

include hC hu

/-- If `edist (f n) (f (n+1))` is bounded by `C * 2^-n`, then `f` is a Cauchy sequence.-/
lemma cauchy_seq_of_edist_le_geometric_two : cauchy_seq f :=
begin
  simp only [div_eq_mul_inv, ennreal.inv_pow] at hu,
  refine cauchy_seq_of_edist_le_geometric 2⁻¹ C _ hC hu,
  simp [ennreal.one_lt_two]
end

omit hC
include ha

/-- If `edist (f n) (f (n+1))` is bounded by `C * 2^-n`, then the distance from
`f n` to the limit of `f` is bounded above by `2 * C * 2^-n`. -/
lemma edist_le_of_edist_le_geometric_two_of_tendsto (n : ℕ) :
  edist (f n) a ≤ 2 * C / 2^n :=
begin
  simp only [div_eq_mul_inv, ennreal.inv_pow] at *,
  rw [mul_assoc, mul_comm],
  convert edist_le_of_edist_le_geometric_of_tendsto 2⁻¹ C hu ha n,
  rw [ennreal.one_sub_inv_two, ennreal.inv_inv]
end

/-- If `edist (f n) (f (n+1))` is bounded by `C * 2^-n`, then the distance from
`f 0` to the limit of `f` is bounded above by `2 * C`. -/
lemma edist_le_of_edist_le_geometric_two_of_tendsto₀: edist (f 0) a ≤ 2 * C :=
by simpa only [pow_zero, div_eq_mul_inv, ennreal.inv_one, mul_one]
  using edist_le_of_edist_le_geometric_two_of_tendsto C hu ha 0

end edist_le_geometric_two

section le_geometric

variables [metric_space α] {r C : ℝ} (hr : r < 1) {f : ℕ → α}
  (hu : ∀n, dist (f n) (f (n+1)) ≤ C * r^n)

include hr hu

lemma aux_has_sum_of_le_geometric : has_sum (λ n : ℕ, C * r^n) (C / (1 - r)) :=
begin
  rcases sign_cases_of_C_mul_pow_nonneg (λ n, dist_nonneg.trans (hu n)) with rfl | ⟨C₀, r₀⟩,
  { simp [has_sum_zero] },
  { refine has_sum.mul_left C _,
    simpa using has_sum_geometric_of_lt_1 r₀ hr }
end

variables (r C)

/-- If `dist (f n) (f (n+1))` is bounded by `C * r^n`, `r < 1`, then `f` is a Cauchy sequence.
Note that this lemma does not assume `0 ≤ C` or `0 ≤ r`. -/
lemma cauchy_seq_of_le_geometric : cauchy_seq f :=
cauchy_seq_of_dist_le_of_summable _ hu ⟨_, aux_has_sum_of_le_geometric hr hu⟩

/-- If `dist (f n) (f (n+1))` is bounded by `C * r^n`, `r < 1`, then the distance from
`f n` to the limit of `f` is bounded above by `C * r^n / (1 - r)`. -/
lemma dist_le_of_le_geometric_of_tendsto₀ {a : α} (ha : tendsto f at_top (𝓝 a)) :
  dist (f 0) a ≤ C / (1 - r) :=
(aux_has_sum_of_le_geometric hr hu).tsum_eq ▸
  dist_le_tsum_of_dist_le_of_tendsto₀ _ hu ⟨_, aux_has_sum_of_le_geometric hr hu⟩ ha

/-- If `dist (f n) (f (n+1))` is bounded by `C * r^n`, `r < 1`, then the distance from
`f 0` to the limit of `f` is bounded above by `C / (1 - r)`. -/
lemma dist_le_of_le_geometric_of_tendsto {a : α} (ha : tendsto f at_top (𝓝 a)) (n : ℕ) :
  dist (f n) a ≤ (C * r^n) / (1 - r) :=
begin
  have := aux_has_sum_of_le_geometric hr hu,
  convert dist_le_tsum_of_dist_le_of_tendsto _ hu ⟨_, this⟩ ha n,
  simp only [pow_add, mul_left_comm C, mul_div_right_comm],
  rw [mul_comm],
  exact (this.mul_left _).tsum_eq.symm
end

omit hr hu

variable (hu₂ : ∀ n, dist (f n) (f (n+1)) ≤ (C / 2) / 2^n)

/-- If `dist (f n) (f (n+1))` is bounded by `(C / 2) / 2^n`, then `f` is a Cauchy sequence. -/
lemma cauchy_seq_of_le_geometric_two : cauchy_seq f :=
cauchy_seq_of_dist_le_of_summable _ hu₂ $ ⟨_, has_sum_geometric_two' C⟩

/-- If `dist (f n) (f (n+1))` is bounded by `(C / 2) / 2^n`, then the distance from
`f 0` to the limit of `f` is bounded above by `C`. -/
lemma dist_le_of_le_geometric_two_of_tendsto₀ {a : α} (ha : tendsto f at_top (𝓝 a)) :
  dist (f 0) a ≤ C :=
(tsum_geometric_two' C) ▸ dist_le_tsum_of_dist_le_of_tendsto₀ _ hu₂ (summable_geometric_two' C) ha

include hu₂

/-- If `dist (f n) (f (n+1))` is bounded by `(C / 2) / 2^n`, then the distance from
`f n` to the limit of `f` is bounded above by `C / 2^n`. -/
lemma dist_le_of_le_geometric_two_of_tendsto {a : α} (ha : tendsto f at_top (𝓝 a)) (n : ℕ) :
  dist (f n) a ≤ C / 2^n :=
begin
  convert dist_le_tsum_of_dist_le_of_tendsto _ hu₂ (summable_geometric_two' C) ha n,
  simp only [add_comm n, pow_add, ← div_div_eq_div_mul],
  symmetry,
  exact ((has_sum_geometric_two' C).div_const _).tsum_eq
end

end le_geometric

section summable_le_geometric

variables [normed_group α] {r C : ℝ} {f : ℕ → α}

lemma 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 :=
begin
  rw [sum_range_succ, dist_eq_norm, ← norm_neg, neg_sub, add_sub_cancel'],
  exact hf n,
end

/-- 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`. -/
lemma cauchy_seq_finset_of_geometric_bound (hr : r < 1) (hf : ∀n, ∥f n∥ ≤ C * r^n) :
  cauchy_seq (λ s : finset (ℕ), ∑ x in s, f x) :=
cauchy_seq_finset_of_norm_bounded _
  (aux_has_sum_of_le_geometric hr (dist_partial_sum_le_of_le_geometric hf)).summable hf

/-- 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`. -/
lemma norm_sub_le_of_geometric_bound_of_has_sum (hr : r < 1) (hf : ∀n, ∥f n∥ ≤ C * r^n)
  {a : α} (ha : has_sum f a) (n : ℕ) :
  ∥(∑ x in finset.range n, f x) - a∥ ≤ (C * r ^ n) / (1 - r) :=
begin
  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
end

end summable_le_geometric

section normed_ring_geometric
variables {R : Type*} [normed_ring R] [complete_space R]

open normed_space

/-- A geometric series in a complete normed ring is summable.
Proved above (same name, different namespace) for not-necessarily-complete normed fields. -/
lemma normed_ring.summable_geometric_of_norm_lt_1
  (x : R) (h : ∥x∥ < 1) : summable (λ (n:ℕ), x ^ n) :=
begin
  have h1 : summable (λ (n:ℕ), ∥x∥ ^ n) := summable_geometric_of_lt_1 (norm_nonneg _) h,
  refine summable_of_norm_bounded_eventually _ h1 _,
  rw nat.cofinite_eq_at_top,
  exact eventually_norm_pow_le x,
end

/-- 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`. -/
lemma normed_ring.tsum_geometric_of_norm_lt_1
  (x : R) (h : ∥x∥ < 1) : ∥∑' n:ℕ, x ^ n∥ ≤ ∥(1:R)∥ - 1 + (1 - ∥x∥)⁻¹ :=
begin
  rw tsum_eq_zero_add (normed_ring.summable_geometric_of_norm_lt_1 x h),
  simp only [pow_zero],
  refine le_trans (norm_add_le _ _) _,
  have : ∥∑' b : ℕ, (λ n, x ^ (n + 1)) b∥ ≤ (1 - ∥x∥)⁻¹ - 1,
  { refine tsum_of_norm_bounded _ (λ b, norm_pow_le' _ (nat.succ_pos b)),
    convert (has_sum_nat_add_iff' 1).mpr (has_sum_geometric_of_lt_1 (norm_nonneg x) h),
    simp },
  linarith
end

lemma geom_series_mul_neg (x : R) (h : ∥x∥ < 1) :
  (∑' i:ℕ, x ^ i) * (1 - x) = 1 :=
begin
  have := ((normed_ring.summable_geometric_of_norm_lt_1 x h).has_sum.mul_right (1 - x)),
  refine tendsto_nhds_unique this.tendsto_sum_nat _,
  have : tendsto (λ (n : ℕ), 1 - x ^ n) at_top (𝓝 1),
  { simpa using tendsto_const_nhds.sub (tendsto_pow_at_top_nhds_0_of_norm_lt_1 h) },
  convert ← this,
  ext n,
  rw [←geom_sum_mul_neg, geom_sum_def, finset.sum_mul],
end

lemma mul_neg_geom_series (x : R) (h : ∥x∥ < 1) :
  (1 - x) * ∑' i:ℕ, x ^ i = 1 :=
begin
  have := (normed_ring.summable_geometric_of_norm_lt_1 x h).has_sum.mul_left (1 - x),
  refine tendsto_nhds_unique this.tendsto_sum_nat _,
  have : tendsto (λ (n : ℕ), 1 - x ^ n) at_top (nhds 1),
  { simpa using tendsto_const_nhds.sub
      (tendsto_pow_at_top_nhds_0_of_norm_lt_1 h) },
  convert ← this,
  ext n,
  rw [←mul_neg_geom_sum, geom_sum_def, finset.mul_sum]
end

end normed_ring_geometric

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

lemma summable_of_ratio_norm_eventually_le {α : Type*} [semi_normed_group α] [complete_space α]
  {f : ℕ → α} {r : ℝ} (hr₁ : r < 1)
  (h : ∀ᶠ n in at_top, ∥f (n+1)∥ ≤ r * ∥f n∥) : summable f :=
begin
  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 (λ n, ∥f N∥ * r^n)
      (summable.mul_left _ $ summable_geometric_of_lt_1 hr₀ hr₁) (λ n, _),
    conv_rhs {rw [mul_comm, ← zero_add N]},
    refine le_geom hr₀ n (λ i _, _),
    convert hN (i + N) (N.le_add_left i) using 3,
    ac_refl },
  { push_neg at hr₀,
    refine summable_of_norm_bounded_eventually 0 summable_zero _,
    rw nat.cofinite_eq_at_top,
    filter_upwards [h],
    intros n hn,
    by_contra h,
    push_neg at h,
    exact not_lt.mpr (norm_nonneg _) (lt_of_le_of_lt hn $ mul_neg_of_neg_of_pos hr₀ h) }
end

lemma summable_of_ratio_test_tendsto_lt_one {α : Type*} [normed_group α] [complete_space α]
  {f : ℕ → α} {l : ℝ} (hl₁ : l < 1) (hf : ∀ᶠ n in at_top, f n ≠ 0)
  (h : tendsto (λ n, ∥f (n+1)∥/∥f n∥) at_top (𝓝 l)) : summable f :=
begin
  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],
  intros n h₀ h₁,
  rwa ← div_le_iff (norm_pos_iff.mpr h₁)
end

lemma not_summable_of_ratio_norm_eventually_ge {α : Type*} [semi_normed_group α]
  {f : ℕ → α} {r : ℝ} (hr : 1 < r) (hf : ∃ᶠ n in at_top, ∥f n∥ ≠ 0)
  (h : ∀ᶠ n in at_top, r * ∥f n∥ ≤ ∥f (n+1)∥) : ¬ summable f :=
begin
  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_at_top_zero
    (λ h', not_tendsto_at_top_of_tendsto_nhds (tendsto_norm_zero.comp h') _),
  convert tendsto_at_top_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_refl }
end

lemma not_summable_of_ratio_test_tendsto_gt_one {α : Type*} [semi_normed_group α]
  {f : ℕ → α} {l : ℝ} (hl : 1 < l)
  (h : tendsto (λ n, ∥f (n+1)∥/∥f n∥) at_top (𝓝 l)) : ¬ summable f :=
begin
  have key : ∀ᶠ n in at_top, ∥f n∥ ≠ 0,
  { filter_upwards [eventually_ge_of_tendsto_gt hl h],
    intros n 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],
  intros n h₀ h₁,
  rwa ← le_div_iff (lt_of_le_of_ne (norm_nonneg _) h₁.symm)
end

/-! ### Positive sequences with small sums on encodable types -/

/-- For any positive `ε`, define on an encodable type a positive sequence with sum less than `ε` -/
def pos_sum_of_encodable {ε : ℝ} (hε : 0 < ε)
  (ι) [encodable ι] : {ε' : ι → ℝ // (∀ i, 0 < ε' i) ∧ ∃ c, has_sum ε' c ∧ c ≤ ε} :=
begin
  let f := λ n, (ε / 2) / 2 ^ n,
  have hf : has_sum f ε := has_sum_geometric_two' _,
  have f0 : ∀ n, 0 < f n := λ n, div_pos (half_pos hε) (pow_pos zero_lt_two _),
  refine ⟨f ∘ encodable.encode, λ i, f0 _, _⟩,
  rcases hf.summable.comp_injective (@encodable.encode_injective ι _) with ⟨c, hg⟩,
  refine ⟨c, hg, has_sum_le_inj _ (@encodable.encode_injective ι _) _ _ hg hf⟩,
  { assume i _, exact le_of_lt (f0 _) },
  { assume n, exact le_refl _ }
end

namespace nnreal

theorem exists_pos_sum_of_encodable {ε : ℝ≥0} (hε : 0 < ε) (ι) [encodable ι] :
  ∃ ε' : ι → ℝ≥0, (∀ i, 0 < ε' i) ∧ ∃c, has_sum ε' c ∧ c < ε :=
let ⟨a, a0, aε⟩ := exists_between hε in
let ⟨ε', hε', c, hc, hcε⟩ := pos_sum_of_encodable a0 ι in
⟨ λi, ⟨ε' i, le_of_lt $ hε' i⟩, assume i, nnreal.coe_lt_coe.2 $ hε' i,
  ⟨c, has_sum_le (assume i, le_of_lt $ hε' i) has_sum_zero hc ⟩, nnreal.has_sum_coe.1 hc,
   lt_of_le_of_lt (nnreal.coe_le_coe.1 hcε) aε ⟩

end nnreal

namespace ennreal

theorem exists_pos_sum_of_encodable {ε : ℝ≥0∞} (hε : 0 < ε) (ι) [encodable ι] :
  ∃ ε' : ι → ℝ≥0, (∀ i, 0 < ε' i) ∧ ∑' i, (ε' i : ℝ≥0∞) < ε :=
begin
  rcases exists_between hε with ⟨r, h0r, hrε⟩,
  rcases lt_iff_exists_coe.1 hrε with ⟨x, rfl, hx⟩,
  rcases nnreal.exists_pos_sum_of_encodable (coe_lt_coe.1 h0r) ι with ⟨ε', hp, c, hc, hcr⟩,
  exact ⟨ε', hp, (ennreal.tsum_coe_eq hc).symm ▸ lt_trans (coe_lt_coe.2 hcr) hrε⟩
end

end ennreal

/-!
### Factorial
-/

lemma factorial_tendsto_at_top : tendsto nat.factorial at_top at_top :=
tendsto_at_top_at_top_of_monotone nat.monotone_factorial (λ n, ⟨n, n.self_le_factorial⟩)

lemma tendsto_factorial_div_pow_self_at_top : tendsto (λ n, n! / n^n : ℕ → ℝ) at_top (𝓝 0) :=
tendsto_of_tendsto_of_tendsto_of_le_of_le'
  tendsto_const_nhds
  (tendsto_const_div_at_top_nhds_0_nat 1)
  (eventually_of_forall $ λ n, div_nonneg (by exact_mod_cast n.factorial_pos.le)
    (pow_nonneg (by exact_mod_cast n.zero_le) _))
  begin
    refine (eventually_gt_at_top 0).mono (λ n hn, _),
    rcases nat.exists_eq_succ_of_ne_zero hn.ne.symm with ⟨k, rfl⟩,
    rw [← prod_range_add_one_eq_factorial, pow_eq_prod_const, div_eq_mul_inv, ← inv_eq_one_div,
      prod_nat_cast, nat.cast_succ, ← prod_inv_distrib', ← prod_mul_distrib,
      finset.prod_range_succ'],
    simp only [prod_range_succ', one_mul, nat.cast_add, zero_add, nat.cast_one],
    refine mul_le_of_le_one_left (inv_nonneg.mpr $ by exact_mod_cast hn.le) (prod_le_one _ _);
      intros x hx; rw finset.mem_range at hx,
    { refine mul_nonneg _ (inv_nonneg.mpr _); norm_cast; linarith },
    { refine (div_le_one $ by exact_mod_cast hn).mpr _, norm_cast, linarith }
  end


/-
Copyright (c) 2021 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
import data.finset.lattice
import data.set.pairwise
import order.hom.basic

/-!
# The monotone sequence of partial supremums of a sequence

We define `partial_sups : (ℕ → α) → ℕ →o α` inductively. For `f : ℕ → α`, `partial_sups f` is
the sequence `f 0 `, `f 0 ⊔ f 1`, `f 0 ⊔ f 1 ⊔ f 2`, ... The point of this definition is that
* it doesn't need a `⨆`, as opposed to `⨆ (i ≤ n), f i`.
* it doesn't need a `⊥`, as opposed to `(finset.range (n + 1)).sup f`.
* it avoids needing to prove that `finset.range (n + 1)` is nonempty to use `finset.sup'`.

Equivalence with those definitions is shown by `partial_sups_eq_bsupr`, `partial_sups_eq_sup_range`,
`partial_sups_eq_sup'_range` and respectively.

## Notes

One might dispute whether this sequence should start at `f 0` or `⊥`. We choose the former because :
* Starting at `⊥` requires... having a bottom element.
* `λ f n, (finset.range n).sup f` is already effectively the sequence starting at `⊥`.
* If we started at `⊥` we wouldn't have the Galois insertion. See `partial_sups.gi`.

## TODO

One could generalize `partial_sups` to any locally finite bot preorder domain, in place of `ℕ`.
Necessary for the TODO in the module docstring of `order.disjointed`.
-/

variables {α : Type*}

section semilattice_sup
variables [semilattice_sup α]

/-- The monotone sequence whose value at `n` is the supremum of the `f m` where `m ≤ n`. -/
def partial_sups (f : ℕ → α) : ℕ →o α :=
⟨@nat.rec (λ _, α) (f 0) (λ (n : ℕ) (a : α), a ⊔ f (n + 1)),
  monotone_nat_of_le_succ (λ n, le_sup_left)⟩

@[simp] lemma partial_sups_zero (f : ℕ → α) : partial_sups f 0 = f 0 := rfl
@[simp] lemma partial_sups_succ (f : ℕ → α) (n : ℕ) :
  partial_sups f (n + 1) = partial_sups f n ⊔ f (n + 1) := rfl

lemma le_partial_sups_of_le (f : ℕ → α) {m n : ℕ} (h : m ≤ n) :
  f m ≤ partial_sups f n :=
begin
  induction n with n ih,
  { cases h, exact le_rfl, },
  { cases h with h h,
    { exact le_sup_right, },
    { exact (ih h).trans le_sup_left, } },
end

lemma le_partial_sups (f : ℕ → α) :
  f ≤ partial_sups f :=
λ n, le_partial_sups_of_le f le_rfl

lemma partial_sups_le (f : ℕ → α) (n : ℕ)
  (a : α) (w : ∀ m, m ≤ n → f m ≤ a) : partial_sups f n ≤ a :=
begin
  induction n with n ih,
  { apply w 0 le_rfl, },
  { exact sup_le (ih (λ m p, w m (nat.le_succ_of_le p))) (w (n + 1) le_rfl) }
end

lemma monotone.partial_sups_eq {f : ℕ → α} (hf : monotone f) :
  (partial_sups f : ℕ → α) = f :=
begin
  ext n,
  induction n with n ih,
  { refl },
  { rw [partial_sups_succ, ih, sup_eq_right.2 (hf (nat.le_succ _))] }
end

lemma partial_sups_mono : monotone (partial_sups : (ℕ → α) → ℕ →o α) :=
begin
  rintro f g h n,
  induction n with n ih,
  { exact h 0 },
  { exact sup_le_sup ih (h _) }
end

/-- `partial_sups` forms a Galois insertion with the coercion from monotone functions to functions.
-/
def partial_sups.gi : galois_insertion (partial_sups : (ℕ → α) → ℕ →o α) coe_fn :=
{ choice := λ f h, ⟨f, begin
    convert (partial_sups f).monotone,
    exact (le_partial_sups f).antisymm h,
  end⟩,
  gc := λ f g, begin
    refine ⟨(le_partial_sups f).trans, λ h, _⟩,
    convert partial_sups_mono h,
    exact order_hom.ext _ _ g.monotone.partial_sups_eq.symm,
  end,
  le_l_u := λ f, le_partial_sups f,
  choice_eq := λ f h, order_hom.ext _ _ ((le_partial_sups f).antisymm h) }

lemma partial_sups_eq_sup'_range (f : ℕ → α) (n : ℕ) :
  partial_sups f n = (finset.range (n + 1)).sup' ⟨n, finset.self_mem_range_succ n⟩ f :=
begin
  induction n with n ih,
  { simp },
  { dsimp [partial_sups] at ih ⊢,
    simp_rw @finset.range_succ n.succ,
    rw [ih, finset.sup'_insert, sup_comm] }
end

end semilattice_sup

lemma partial_sups_eq_sup_range [semilattice_sup α] [order_bot α] (f : ℕ → α) (n : ℕ) :
  partial_sups f n = (finset.range (n + 1)).sup f :=
begin
  induction n with n ih,
  { simp },
  { dsimp [partial_sups] at ih ⊢,
    rw [finset.range_succ, finset.sup_insert, sup_comm, ih] }
end

/- Note this lemma requires a distributive lattice, so is not useful (or true) in situations such as
submodules. -/
lemma partial_sups_disjoint_of_disjoint [distrib_lattice α] [order_bot α]
  (f : ℕ → α) (h : pairwise (disjoint on f)) {m n : ℕ} (hmn : m < n) :
  disjoint (partial_sups f m) (f n) :=
begin
  induction m with m ih,
  { exact h 0 n hmn.ne, },
  { rw [partial_sups_succ, disjoint_sup_left],
    exact ⟨ih (nat.lt_of_succ_lt hmn), h (m + 1) n hmn.ne⟩ }
end

section complete_lattice
variables [complete_lattice α]

lemma partial_sups_eq_bsupr (f : ℕ → α) (n : ℕ) :
  partial_sups f n = ⨆ (i ≤ n), f i :=
begin
  rw [partial_sups_eq_sup_range, finset.sup_eq_supr],
  congr,
  ext a,
  exact supr_congr_Prop (by rw [finset.mem_range, nat.lt_succ_iff]) (λ _, rfl),
end

@[simp] lemma supr_partial_sups_eq (f : ℕ → α) :
  (⨆ n, partial_sups f n) = ⨆ n, f n :=
begin
  refine (supr_le $ λ n, _).antisymm (supr_le_supr $ le_partial_sups f),
  rw partial_sups_eq_bsupr,
  exact bsupr_le_supr _ _,
end

lemma supr_le_supr_of_partial_sups_le_partial_sups {f g : ℕ → α}
  (h : partial_sups f ≤ partial_sups g) :
  (⨆ n, f n) ≤ ⨆ n, g n :=
begin
  rw [←supr_partial_sups_eq f, ←supr_partial_sups_eq g],
  exact supr_le_supr h,
end

lemma supr_eq_supr_of_partial_sups_eq_partial_sups {f g : ℕ → α}
  (h : partial_sups f = partial_sups g) :
  (⨆ n, f n) = ⨆ n, g n :=
by simp_rw [←supr_partial_sups_eq f, ←supr_partial_sups_eq g, h]

end complete_lattice


/-
Copyright (c) 2021 Aaron Anderson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Aaron Anderson
-/
import set_theory.cardinal

/-!
# Finite Cardinality Functions

## Main Definitions

* `nat.card α` is the cardinality of `α` as a natural number.
  If `α` is infinite, `nat.card α = 0`.
* `enat.card α` is the cardinality of `α` as an extended natural number.
  If `α` is infinite, `enat.card α = ⊤`.

-/

open cardinal
noncomputable theory

variable {α : Type*}

namespace nat

/-- `nat.card α` is the cardinality of `α` as a natural number.
  If `α` is infinite, `nat.card α = 0`. -/
def card (α : Type*) : ℕ := (mk α).to_nat

@[simp]
lemma card_eq_fintype_card [fintype α] : card α = fintype.card α := mk_to_nat_eq_card

@[simp]
lemma card_eq_zero_of_infinite [infinite α] : card α = 0 := mk_to_nat_of_infinite

end nat

namespace enat

/-- `enat.card α` is the cardinality of `α` as an extended natural number.
  If `α` is infinite, `enat.card α = ⊤`. -/
def card (α : Type*) : enat := (mk α).to_enat

@[simp]
lemma card_eq_coe_fintype_card [fintype α] : card α = fintype.card α := mk_to_enat_eq_coe_card

@[simp]
lemma card_eq_top_of_infinite [infinite α] : card α = ⊤ := mk_to_enat_of_infinite

end enat


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

import tactic -- imports all the Lean tactics

/-

# More on functions

Another question on the Imperial introduction to proof problem sheet on functions
is "If `f : X → Y` and `g : Y → Z` and `g ∘ f` is injective, is it true that `g` is injective?"
This is not true. A counterexample could be made by letting `X` and `Z` have one element,
and letting `Y` have two elements; `f` and `g` are then not hard to write down. Let's
see how to do this in Lean by making inductive types `X`, `Y` and `Z` and functions
`f` and `g` which give an explicit counterexample.

-/

-- Let X be {a}
inductive X : Type
| a : X

-- in fact the term of type X is called `X.a`.

-- Let Y be {b,c}
inductive Y : Type
| b : Y
| c : Y

inductive Z : Type
| d : Z

-- Define f by f(X.a)=Y.b
def f : X → Y
| X.a := Y.b

-- define g by g(Y.b)=g(Y.c)=Z.d
def g : Y → Z
| Y.b := Z.d
| Y.c := Z.d

-- examples of how these things work
example (z : Z) : z = Z.d :=
begin
  cases z,
  refl,
end

example : Y.b ≠ Y.c :=
begin
  intro h, -- x ≠ y is definitionally equal to (x = y) → false
  cases h, -- no cases when they're equal!
end

open function

lemma gf_injective : injective (g ∘ f) :=
begin
  rintros x₁ x₂ h,
  cases x₁,
  cases x₂,
  refl,
end

-- This is a question on the IUM (Imperial introduction to proof course) function problem sheet
example : ¬ (∀ X Y Z : Type, ∀ (f : X → Y) (g : Y → Z), injective (g ∘ f) → injective g) :=
begin
  intro h,
  specialize h X Y Z f g gf_injective,
  specialize h,
  have hg : g (Y.b) = g (Y.c),
    refl,
  specialize h hg,
  cases h,
end

-- This is another one
example : ¬ (∀ X Y Z : Type, ∀ (f : X → Y) (g : Y → Z), surjective (g ∘ f) → surjective f) :=
begin
  intro h,
  specialize h X Y Z f g _,
  { rintro ⟨⟩,
    use X.a,
    refl },
  { rcases h Y.c with ⟨⟨⟩, ⟨⟩⟩ },
end


/-
Copyright (c) 2022 Violeta Hernández Palacios. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Violeta Hernández Palacios

! This file was ported from Lean 3 source module computability.ackermann
! leanprover-community/mathlib commit 9b2660e1b25419042c8da10bf411aa3c67f14383
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Computability.Primrec
import Mathbin.Tactic.Linarith.Default

/-!
# Ackermann function

In this file, we define the two-argument Ackermann function `ack`. Despite having a recursive
definition, we show that this isn't a primitive recursive function.

## Main results

- `exists_lt_ack_of_primrec`: any primitive recursive function is pointwise bounded above by `ack m`
  for some `m`.
- `not_primrec₂_ack`: the two-argument Ackermann function is not primitive recursive.

## Proof approach

We very broadly adapt the proof idea from
https://www.planetmath.org/ackermannfunctionisnotprimitiverecursive. Namely, we prove that for any
primitive recursive `f : ℕ → ℕ`, there exists `m` such that `f n < ack m n` for all `n`. This then
implies that `λ n, ack n n` can't be primitive recursive, and so neither can `ack`. We aren't able
to use the same bounds as in that proof though, since our approach of using pairing functions
differs from their approach of using multivariate functions.

The important bounds we show during the main inductive proof (`exists_lt_ack_of_primrec`) are the
following. Assuming `∀ n, f n < ack a n` and `∀ n, g n < ack b n`, we have:

- `∀ n, nat.mkpair (f n) (g n) < ack (max a b + 3) n`.
- `∀ n, g (f n) < ack (max a b + 2) n`.
- `∀ n, nat.elim (f n.unpair.1) (λ (y IH : ℕ), g (nat.mkpair n.unpair.1 (nat.mkpair y IH)))
  n.unpair.2 < ack (max a b + 9) n`.

The last one is evidently the hardest. Using `nat.unpair_add_le`, we reduce it to the more
manageable

- `∀ m n, elim (f m) (λ (y IH : ℕ), g (nat.mkpair m (nat.mkpair y IH))) n <
  ack (max a b + 9) (m + n)`.

We then prove this by induction on `n`. Our proof crucially depends on `ack_mkpair_lt`, which is
applied twice, giving us a constant of `4 + 4`. The rest of the proof consists of simpler bounds
which bump up our constant to `9`.
-/

open Nat

/-- The two-argument Ackermann function, defined so that

- `ack 0 n = n + 1`
- `ack (m + 1) 0 = ack m 1`
- `ack (m + 1) (n + 1) = ack m (ack (m + 1) n)`.

This is of interest as both a fast-growing function, and as an example of a recursive function that
isn't primitive recursive. -/
def ack : ℕ → ℕ → ℕ
  | 0, n => n + 1
  | m + 1, 0 => ack m 1
  | m + 1, n + 1 => ack m (ack (m + 1) n)
#align ack ack

@[simp]
theorem ack_zero (n : ℕ) : ack 0 n = n + 1 := by rw [ack]
#align ack_zero ack_zero

@[simp]
theorem ack_succ_zero (m : ℕ) : ack (m + 1) 0 = ack m 1 := by rw [ack]
#align ack_succ_zero ack_succ_zero

@[simp]
theorem ack_succ_succ (m n : ℕ) : ack (m + 1) (n + 1) = ack m (ack (m + 1) n) := by rw [ack]
#align ack_succ_succ ack_succ_succ

@[simp]
theorem ack_one (n : ℕ) : ack 1 n = n + 2 :=
  by
  induction' n with n IH
  · simp
  · simp [IH]
#align ack_one ack_one

@[simp]
theorem ack_two (n : ℕ) : ack 2 n = 2 * n + 3 :=
  by
  induction' n with n IH
  · simp
  · simp [IH, mul_succ]
#align ack_two ack_two

private theorem ack_three_aux (n : ℕ) : (ack 3 n : ℤ) = 2 ^ (n + 3) - 3 :=
  by
  induction' n with n IH
  · simp
    norm_num
  · simp [IH, pow_succ]
    rw [mul_sub, sub_add]
    norm_num
#align ack_three_aux ack_three_aux

@[simp]
theorem ack_three (n : ℕ) : ack 3 n = 2 ^ (n + 3) - 3 :=
  by
  zify
  rw [cast_sub]
  · exact_mod_cast ack_three_aux n
  · have H : 3 ≤ 2 ^ 3 := by norm_num
    exact H.trans (pow_mono one_le_two <| le_add_left le_rfl)
#align ack_three ack_three

theorem ack_pos : ∀ m n, 0 < ack m n
  | 0, n => by simp
  | m + 1, 0 => by
    rw [ack_succ_zero]
    apply ack_pos
  | m + 1, n + 1 => by
    rw [ack_succ_succ]
    apply ack_pos
#align ack_pos ack_pos

theorem one_lt_ack_succ_left : ∀ m n, 1 < ack (m + 1) n
  | 0, n => by simp
  | m + 1, 0 => by
    rw [ack_succ_zero]
    apply one_lt_ack_succ_left
  | m + 1, n + 1 => by
    rw [ack_succ_succ]
    apply one_lt_ack_succ_left
#align one_lt_ack_succ_left one_lt_ack_succ_left

theorem one_lt_ack_succ_right : ∀ m n, 1 < ack m (n + 1)
  | 0, n => by simp
  | m + 1, n => by
    rw [ack_succ_succ]
    cases exists_eq_succ_of_ne_zero (ack_pos (m + 1) n).ne'
    rw [h]
    apply one_lt_ack_succ_right
#align one_lt_ack_succ_right one_lt_ack_succ_right

theorem ack_strictMono_right : ∀ m, StrictMono (ack m)
  | 0, n₁, n₂, h => by simpa using h
  | m + 1, 0, n + 1, h => by
    rw [ack_succ_zero, ack_succ_succ]
    exact ack_strictMono_right _ (one_lt_ack_succ_left m n)
  | m + 1, n₁ + 1, n₂ + 1, h => by
    rw [ack_succ_succ, ack_succ_succ]
    apply ack_strictMono_right _ (ack_strictMono_right _ _)
    rwa [add_lt_add_iff_right] at h
#align ack_strict_mono_right ack_strictMono_right

theorem ack_mono_right (m : ℕ) : Monotone (ack m) :=
  (ack_strictMono_right m).Monotone
#align ack_mono_right ack_mono_right

theorem ack_injective_right (m : ℕ) : Function.Injective (ack m) :=
  (ack_strictMono_right m).Injective
#align ack_injective_right ack_injective_right

@[simp]
theorem ack_lt_iff_right {m n₁ n₂ : ℕ} : ack m n₁ < ack m n₂ ↔ n₁ < n₂ :=
  (ack_strictMono_right m).lt_iff_lt
#align ack_lt_iff_right ack_lt_iff_right

@[simp]
theorem ack_le_iff_right {m n₁ n₂ : ℕ} : ack m n₁ ≤ ack m n₂ ↔ n₁ ≤ n₂ :=
  (ack_strictMono_right m).le_iff_le
#align ack_le_iff_right ack_le_iff_right

@[simp]
theorem ack_inj_right {m n₁ n₂ : ℕ} : ack m n₁ = ack m n₂ ↔ n₁ = n₂ :=
  (ack_injective_right m).eq_iff
#align ack_inj_right ack_inj_right

theorem max_ack_right (m n₁ n₂ : ℕ) : ack m (max n₁ n₂) = max (ack m n₁) (ack m n₂) :=
  (ack_mono_right m).map_max
#align max_ack_right max_ack_right

theorem add_lt_ack : ∀ m n, m + n < ack m n
  | 0, n => by simp
  | m + 1, 0 => by simpa using add_lt_ack m 1
  | m + 1, n + 1 =>
    calc
      m + 1 + n + 1 ≤ m + (m + n + 2) := by linarith
      _ < ack m (m + n + 2) := (add_lt_ack _ _)
      _ ≤ ack m (ack (m + 1) n) :=
        (ack_mono_right m <| le_of_eq_of_le (by ring_nf) <| succ_le_of_lt <| add_lt_ack (m + 1) n)
      _ = ack (m + 1) (n + 1) := (ack_succ_succ m n).symm

#align add_lt_ack add_lt_ack

theorem add_add_one_le_ack (m n : ℕ) : m + n + 1 ≤ ack m n :=
  succ_le_of_lt (add_lt_ack m n)
#align add_add_one_le_ack add_add_one_le_ack

theorem lt_ack_left (m n : ℕ) : m < ack m n :=
  (self_le_add_right m n).trans_lt <| add_lt_ack m n
#align lt_ack_left lt_ack_left

theorem lt_ack_right (m n : ℕ) : n < ack m n :=
  (self_le_add_left n m).trans_lt <| add_lt_ack m n
#align lt_ack_right lt_ack_right

-- we reorder the arguments to appease the equation compiler
private theorem ack_strict_mono_left' : ∀ {m₁ m₂} (n), m₁ < m₂ → ack m₁ n < ack m₂ n
  | m, 0, n => fun h => (Nat.not_lt_zero m h).elim
  | 0, m + 1, 0 => fun h => by simpa using one_lt_ack_succ_right m 0
  | 0, m + 1, n + 1 => fun h => by
    rw [ack_zero, ack_succ_succ]
    apply lt_of_le_of_lt (le_trans _ <| add_le_add_left (add_add_one_le_ack _ _) m) (add_lt_ack _ _)
    linarith
  | m₁ + 1, m₂ + 1, 0 => fun h => by
    simpa using ack_strict_mono_left' 1 ((add_lt_add_iff_right 1).1 h)
  | m₁ + 1, m₂ + 1, n + 1 => fun h =>
    by
    rw [ack_succ_succ, ack_succ_succ]
    exact
      (ack_strict_mono_left' _ <| (add_lt_add_iff_right 1).1 h).trans
        (ack_strictMono_right _ <| ack_strict_mono_left' n h)
#align ack_strict_mono_left' ack_strict_mono_left'

theorem ack_strictMono_left (n : ℕ) : StrictMono fun m => ack m n := fun m₁ m₂ =>
  ack_strict_mono_left' n
#align ack_strict_mono_left ack_strictMono_left

theorem ack_mono_left (n : ℕ) : Monotone fun m => ack m n :=
  (ack_strictMono_left n).Monotone
#align ack_mono_left ack_mono_left

theorem ack_injective_left (n : ℕ) : Function.Injective fun m => ack m n :=
  (ack_strictMono_left n).Injective
#align ack_injective_left ack_injective_left

@[simp]
theorem ack_lt_iff_left {m₁ m₂ n : ℕ} : ack m₁ n < ack m₂ n ↔ m₁ < m₂ :=
  (ack_strictMono_left n).lt_iff_lt
#align ack_lt_iff_left ack_lt_iff_left

@[simp]
theorem ack_le_iff_left {m₁ m₂ n : ℕ} : ack m₁ n ≤ ack m₂ n ↔ m₁ ≤ m₂ :=
  (ack_strictMono_left n).le_iff_le
#align ack_le_iff_left ack_le_iff_left

@[simp]
theorem ack_inj_left {m₁ m₂ n : ℕ} : ack m₁ n = ack m₂ n ↔ m₁ = m₂ :=
  (ack_injective_left n).eq_iff
#align ack_inj_left ack_inj_left

theorem max_ack_left (m₁ m₂ n : ℕ) : ack (max m₁ m₂) n = max (ack m₁ n) (ack m₂ n) :=
  (ack_mono_left n).map_max
#align max_ack_left max_ack_left

theorem ack_le_ack {m₁ m₂ n₁ n₂ : ℕ} (hm : m₁ ≤ m₂) (hn : n₁ ≤ n₂) : ack m₁ n₁ ≤ ack m₂ n₂ :=
  (ack_mono_left n₁ hm).trans <| ack_mono_right m₂ hn
#align ack_le_ack ack_le_ack

theorem ack_succ_right_le_ack_succ_left (m n : ℕ) : ack m (n + 1) ≤ ack (m + 1) n :=
  by
  cases n
  · simp
  · rw [ack_succ_succ, succ_eq_add_one]
    apply ack_mono_right m (le_trans _ <| add_add_one_le_ack _ n)
    linarith
#align ack_succ_right_le_ack_succ_left ack_succ_right_le_ack_succ_left

-- All the inequalities from this point onwards are specific to the main proof.
private theorem sq_le_two_pow_add_one_minus_three (n : ℕ) : n ^ 2 ≤ 2 ^ (n + 1) - 3 :=
  by
  induction' n with k hk
  · norm_num
  · cases k
    · norm_num
    · rw [succ_eq_add_one, add_sq, pow_succ 2, two_mul (2 ^ _), add_tsub_assoc_of_le,
        add_comm (2 ^ _), add_assoc]
      · apply add_le_add hk
        norm_num
        apply succ_le_of_lt
        rw [pow_succ, mul_lt_mul_left (zero_lt_two' ℕ)]
        apply lt_two_pow
      · rw [pow_succ, pow_succ]
        linarith [one_le_pow k 2 zero_lt_two]
#align sq_le_two_pow_add_one_minus_three sq_le_two_pow_add_one_minus_three

theorem ack_add_one_sq_lt_ack_add_three : ∀ m n, (ack m n + 1) ^ 2 ≤ ack (m + 3) n
  | 0, n => by simpa using sq_le_two_pow_add_one_minus_three (n + 2)
  | m + 1, 0 => by
    rw [ack_succ_zero, ack_succ_zero]
    apply ack_add_one_sq_lt_ack_add_three
  | m + 1, n + 1 => by
    rw [ack_succ_succ, ack_succ_succ]
    apply (ack_add_one_sq_lt_ack_add_three _ _).trans (ack_mono_right _ <| ack_mono_left _ _)
    linarith
#align ack_add_one_sq_lt_ack_add_three ack_add_one_sq_lt_ack_add_three

theorem ack_ack_lt_ack_max_add_two (m n k : ℕ) : ack m (ack n k) < ack (max m n + 2) k :=
  calc
    ack m (ack n k) ≤ ack (max m n) (ack n k) := ack_mono_left _ (le_max_left _ _)
    _ < ack (max m n) (ack (max m n + 1) k) :=
      (ack_strictMono_right _ <| ack_strictMono_left k <| lt_succ_of_le <| le_max_right m n)
    _ = ack (max m n + 1) (k + 1) := (ack_succ_succ _ _).symm
    _ ≤ ack (max m n + 2) k := ack_succ_right_le_ack_succ_left _ _

#align ack_ack_lt_ack_max_add_two ack_ack_lt_ack_max_add_two

theorem ack_add_one_sq_lt_ack_add_four (m n : ℕ) : ack m ((n + 1) ^ 2) < ack (m + 4) n :=
  calc
    ack m ((n + 1) ^ 2) < ack m ((ack m n + 1) ^ 2) :=
      ack_strictMono_right m <| pow_lt_pow_of_lt_left (succ_lt_succ <| lt_ack_right m n) zero_lt_two
    _ ≤ ack m (ack (m + 3) n) := (ack_mono_right m <| ack_add_one_sq_lt_ack_add_three m n)
    _ ≤ ack (m + 2) (ack (m + 3) n) := (ack_mono_left _ <| by linarith)
    _ = ack (m + 3) (n + 1) := (ack_succ_succ _ n).symm
    _ ≤ ack (m + 4) n := ack_succ_right_le_ack_succ_left _ n

#align ack_add_one_sq_lt_ack_add_four ack_add_one_sq_lt_ack_add_four

theorem ack_pair_lt (m n k : ℕ) : ack m (pair n k) < ack (m + 4) (max n k) :=
  (ack_strictMono_right m <| pair_lt_max_add_one_sq n k).trans <| ack_add_one_sq_lt_ack_add_four _ _
#align ack_mkpair_lt ack_pair_lt

/-- If `f` is primitive recursive, there exists `m` such that `f n < ack m n` for all `n`. -/
theorem exists_lt_ack_of_nat_primrec {f : ℕ → ℕ} (hf : Nat.Primrec f) : ∃ m, ∀ n, f n < ack m n :=
  by
  induction' hf with f g hf hg IHf IHg f g hf hg IHf IHg f g hf hg IHf IHg
  -- Zero function:
  · exact ⟨0, ack_pos 0⟩
  -- Successor function:
  · refine' ⟨1, fun n => _⟩
    rw [succ_eq_one_add]
    apply add_lt_ack
  -- Left projection:
  · refine' ⟨0, fun n => _⟩
    rw [ack_zero, lt_succ_iff]
    exact unpair_left_le n
  -- Right projection:
  · refine' ⟨0, fun n => _⟩
    rw [ack_zero, lt_succ_iff]
    exact unpair_right_le n
  all_goals cases' IHf with a ha; cases' IHg with b hb
  -- Pairing:
  · refine'
      ⟨max a b + 3, fun n =>
        (mkpair_lt_max_add_one_sq _ _).trans_le <|
          (Nat.pow_le_pow_of_le_left (add_le_add_right _ _) 2).trans <|
            ack_add_one_sq_lt_ack_add_three _ _⟩
    rw [max_ack_left]
    exact max_le_max (ha n).le (hb n).le
  -- Composition:
  ·
    exact
      ⟨max a b + 2, fun n =>
        (ha _).trans <| (ack_strictMono_right a <| hb n).trans <| ack_ack_lt_ack_max_add_two a b n⟩
  -- Primitive recursion operator:
  · -- We prove this simpler inequality first.
    have :
      ∀ {m n},
        elim (f m) (fun y IH => g <| mkpair m <| mkpair y IH) n < ack (max a b + 9) (m + n) :=
      by
      intro m n
      -- We induct on n.
      induction' n with n IH
      -- The base case is easy.
      · apply (ha m).trans (ack_strictMono_left m <| (le_max_left a b).trans_lt _)
        linarith
      · -- We get rid of the first `mkpair`.
        rw [elim_succ]
        apply (hb _).trans ((ack_pair_lt _ _ _).trans_le _)
        -- If m is the maximum, we get a very weak inequality.
        cases' lt_or_le _ m with h₁ h₁
        · rw [max_eq_left h₁.le]
          exact ack_le_ack (add_le_add (le_max_right a b) <| by norm_num) (self_le_add_right m _)
        rw [max_eq_right h₁]
        -- We get rid of the second `mkpair`.
        apply (ack_pair_lt _ _ _).le.trans
        -- If n is the maximum, we get a very weak inequality.
        cases' lt_or_le _ n with h₂ h₂
        · rw [max_eq_left h₂.le, add_assoc]
          exact
            ack_le_ack (add_le_add (le_max_right a b) <| by norm_num)
              ((le_succ n).trans <| self_le_add_left _ _)
        rw [max_eq_right h₂]
        -- We now use the inductive hypothesis, and some simple algebraic manipulation.
        apply (ack_strictMono_right _ IH).le.trans
        rw [add_succ m, add_succ _ 8, ack_succ_succ (_ + 8), add_assoc]
        exact ack_mono_left _ (add_le_add (le_max_right a b) le_rfl)
    -- The proof is now simple.
    exact ⟨max a b + 9, fun n => this.trans_le <| ack_mono_right _ <| unpair_add_le n⟩
#align exists_lt_ack_of_nat_primrec exists_lt_ack_of_nat_primrec

theorem not_nat_primrec_ack_self : ¬Nat.Primrec fun n => ack n n := fun h =>
  by
  cases' exists_lt_ack_of_nat_primrec h with m hm
  exact (hm m).False
#align not_nat_primrec_ack_self not_nat_primrec_ack_self

theorem not_primrec_ack_self : ¬Primrec fun n => ack n n :=
  by
  rw [Primrec.nat_iff]
  exact not_nat_primrec_ack_self
#align not_primrec_ack_self not_primrec_ack_self

/-- The Ackermann function is not primitive recursive. -/
theorem not_primrec₂_ack : ¬Primrec₂ ack := fun h =>
  not_primrec_ack_self <| h.comp Primrec.id Primrec.id
#align not_primrec₂_ack not_primrec₂_ack


-- vim: ts=2 sw=0 sts=-1 et ai tw=70

import .fib
import .nat_sub
import .fact
import .extra_pow
import ..sequence

import .le

namespace hidden

open mynat

-- A bit of basic series/sum theory, not using any lists.

-- some ambitious things:
-- define polynomials
-- prove that the sums of a k-th degree polynomial are a k+1th degree
-- polynomial

open sequence

namespace mynat

variables a b c d m n k: mynat
variables term f g : sequence mynat

@[simp] theorem sum_zero: sum term 0 = 0 := rfl

@[simp]
theorem sum_succ: sum term (succ n) = sum term n + term n := rfl

@[simp]
theorem sum_one: sum term 1 = term 0 := add_comm _ _

@[simp] theorem prod_zero: product term 0 = 1 := rfl

@[simp]
theorem prod_succ:
product term (succ n) = product term n * term n := rfl

@[simp]
theorem prod_one: product term 1 = term 0 := mul_comm _ _

theorem constant_sum: ∀ n : mynat, sum ↑(1 :mynat) n = n
| zero := rfl
| (succ n) := by rw [sum_succ, constant_sum, coe_triv, add_one_succ]

theorem mul_sum: ∀ n, sum (↑m * f) n = m * (sum f n)
| zero := rfl
| (succ n) := by rw [sum_succ, sum_succ, mul_add, mul_sum, coe_mul]

theorem sum_distr: ∀ n, sum (f + g) n = (sum f n) + (sum g n)
| zero     := rfl
| (succ n) := by conv {
  rw [sum_succ, sum_succ, sum_succ, sum_distr, addition],
  to_rhs,
  rw [←add_assoc, add_assoc (sum f n), add_comm (f n),
      ←add_assoc (sum f n), add_assoc],
}

theorem sum_cancel: (∀ n, sum f n = sum g n) ↔ ∀ n, f n = g n :=
begin
  split, {
    assume h,
    intro n,
    have hsn := h (succ n),
    repeat {rw sum_succ at hsn},
    rw h n at hsn,
    from add_cancel hsn,
  }, {
    assume h n,
    induction n with n hn,
      refl,
    rw [sum_succ, sum_succ, hn, h n],
  },
end

theorem apply_sum: (∀ n, f n = g n) → sum f k = sum g k :=
begin
  assume h,
  induction k with k hk,
    refl,
  rw [sum_succ, sum_succ, h k, hk],
end

private def two : 2 = succ (succ 0) := rfl

-- phrased in a way that avoids rationals and subtraction :)
theorem triangular_numbers: ∀ n,
sum (λ k, succ k) n * 2 = n * (n + 1)
| zero     := rfl
| (succ n) := by conv {
  rw [sum_succ, add_mul, triangular_numbers, two],
  simp,
  rw add_comm n (n + n * n),
  to_rhs,
  rw add_assoc,
}

-- this one's here because it's so nice to state,
-- abeit ugly to prove
theorem cube_sum:
sum (λ k, (succ k) ^ (3: mynat)) n =
sum (λ k, succ k) n ^ (2: mynat) :=
begin
  have cancel:
    ∀ a b c: mynat,
      a = b → c + a = c + b, {
    intros a b c,
    assume hab,
    rw hab,
  },
  induction n with n hn, {
    refl,
  }, {
    rw sum_succ,
    rw sum_succ,
    rw hn,
    have hrw: (2: mynat) = succ (succ 0) := rfl,
    conv {
      congr,
      rw hrw,
      rw pow_succ,
      rw pow_succ,
      rw pow_zero,
      rw mul_one,
      skip,
      rw hrw,
      rw pow_succ,
      rw pow_succ,
      rw pow_zero,
      rw mul_one,
      rw mul_add,
      rw add_mul,
      rw add_mul,
    }, clear hrw,
    repeat {rw add_assoc},
    apply cancel,
    have hrw: ∀ a: mynat, a + a = a * 2 := (λ a, rfl),
    conv {
      to_rhs,
      rw ←add_assoc,
      congr,
      rw mul_comm,
      rw ←add_mul,
      rw hrw,
      rw triangular_numbers,
    },
    rw add_one_succ,
    conv in (succ n * succ n) {
      rw ←one_mul (succ n * succ n),
    },
    rw mul_assoc,
    rw ←add_mul,
    refl,
  },
end

theorem geometric_progression: ∀ n,
sum (λ k, a * (succ b) ^ k) n * b + a = a * (succ b) ^ n
| zero     := by simp
| (succ n) :=
begin
  conv {
    rw [sum_succ, add_mul, add_assoc, add_comm _ a,
        ←add_assoc, geometric_progression, mul_assoc, ←mul_add],
    to_lhs,
    congr, skip, congr,
    rw ←mul_one (succ b ^ n),
  },
  rw [←mul_add, add_comm, add_one_succ, mul_comm _ (succ b)],
  simp,
end

private theorem two_one : (2 : mynat) = 1 + 1 := rfl

theorem fibonacci_sum: ∀ n, (sum fib (n+1)) + 1 = (fib (n + 2))
| zero     := rfl
| (succ n) := by conv {
  congr,
  rw [succ_add, sum_succ, add_assoc, add_comm (fib (n+1)), ←add_assoc,
      fibonacci_sum, add_comm],
  skip,
  rw [succ_add, two_one, ←add_assoc n, @add_one_succ n,
      succ_add, fib_succsucc, ←add_one_succ, add_assoc n, ←two_one],
}

-- TODO: This is not the right file for this.
-- binomial coefficients, defined via Pascal's triangle,
-- so's to avoid subtraction, or worse, division!
def binom: mynat → mynat → mynat
| _        0        := 1
| 0        (succ _) := 0
| (succ n) (succ k) := binom n k + binom n (succ k)

@[simp]
theorem binom_zero: binom n 0 = 1 :=
begin
  cases n,
  refl,
  refl,
end

@[simp] theorem binom_zero_succ: binom 0 (succ n) = 0 := rfl

@[simp]
theorem binom_succ_succ:
binom (succ n) (succ k) = binom n k + binom n (succ k) := rfl

@[simp]
theorem binom_one:
binom n 1 = n :=
begin
  induction n with n_n n_ih, {
    refl,
  }, {
    have: 1 = succ 0 := rfl,
    rw [this, binom_succ_succ, ←this, n_ih],
    simp,
    rw ←add_one_succ,
    rw add_comm,
  },
end

@[simp]
theorem binom_overflow:
binom n (n + succ k) = 0 :=
begin
  revert k,
  induction n with n_n n_ih, {
    intro k,
    refl,
  }, {
    intro k,
    rw [add_succ, binom_succ_succ],
    conv {
      congr, congr,
      rw [succ_add, ←add_succ, n_ih],
    },
    rw succ_add,
    repeat {rw ←add_succ},
    rw n_ih,
    refl,
  },
end

@[simp]
theorem binom_succ_kill:
binom n (succ n) = 0 :=
begin
  have: 1 = succ 0 := rfl,
  rw [←add_one_succ, this, binom_overflow],
end

@[simp]
theorem binom_dupl:
binom n n = 1 :=
begin
  induction n with n_n n_ih, {
    refl,
  }, {
    rw [binom_succ_succ, n_ih, binom_succ_kill],
    refl,
  },
end

@[simp]
theorem binom_one_symm:
binom (succ n) n = succ n :=
begin
  induction n with n_n n_ih, {
    refl,
  }, {
    rw [binom_succ_succ, n_ih, binom_succ_succ],
    simp,
  }
end

theorem binom_multiplication:
binom (n + k) k * n = binom (n + k) (succ k) * (succ k) :=
begin
  induction h: n + k with m hm generalizing n k, {
    rw add_integral h,
    simp,
  }, {
    cases k, {
      simp,
      simp at h,
      assumption,
    }, {
      cases n, {
        simp at h,
        rw h,
        simp,
      }, {
        have hm_1 := hm (succ n) k _,
        have hm_2 := hm n (succ k) _,
        conv {
          congr,
          rw [binom_succ_succ, add_mul, hm_1, ←mul_add],
          congr, skip,
          simp,
          skip,
          rw [binom_succ_succ, add_mul, ←hm_2, ←mul_add],
          congr, skip,
          simp,
        }, {
          simp at h,
          simp [h],
        }, {
          simp at h,
          simp [h],
        },
      },
    },
  },
end

theorem binom_formula:
binom (n + k) k * fact k * fact n = fact (n + k) :=
begin
  induction k, {
    simp,
  }, {
    rw [add_succ, binom_succ_succ, fact_succ, mul_assoc, add_mul],
    conv {
      to_lhs,
      congr,
      rw [mul_assoc, mul_comm (succ k_n),
          ←mul_assoc, ←mul_assoc, k_ih],
      skip,
      rw [mul_comm _ (succ k_n), ←mul_assoc, ←mul_assoc,
          ←binom_multiplication, mul_assoc, mul_assoc,
          mul_comm n, ←mul_assoc, ←mul_assoc, k_ih],
    },
    rw [←mul_add, succ_add, fact_succ, add_comm k_n],
  },
end

theorem binom_symm:
binom (a + b) a = binom (a + b) b :=
begin
  apply mul_cancel, {
    have hfafb: fact a * fact b ≠ 0, {
      assume h,
      from fact_nzero (mul_integral fact_nzero h),
    },
    from hfafb,
  }, {
    rw [mul_comm, ←mul_assoc, add_comm, binom_formula,
        mul_comm, mul_comm (fact a), ←mul_assoc, add_comm,
        binom_formula],
  },
end

theorem sum_tail:
sum f (succ n) = sum (λ k, f (succ k)) n + f 0 :=
begin
  induction n, {
    rw sum_succ,
    simp,
  }, {
    rw [sum_succ, n_ih, add_assoc, add_comm (f 0), ←add_assoc,
        ←sum_succ],
  },
end

private theorem sum_distr':
sum (λ k, f k + g k) n = (sum f n) + (sum g n) :=
sum_distr f g n

-- nicer proof from binomial theorem later
example:
-- chad partial function notation
sum (binom n) (succ n) = 2 ^ n :=
begin
  induction n, {
    refl,
  }, {
    rw [sum_succ, sum_tail],
    -- gosh this is a pain
    have: ∀ k,
      (λ k, binom (succ n_n) (succ k)) k
      = (λ k, binom n_n k + binom n_n (succ k)) k, {
      simp,
    },
    rw [(sum_cancel _ _).mpr this, sum_distr',
    -- clumsy
        binom_succ_succ, add_comm (binom n_n n_n)],
    repeat {rw ←add_assoc},
    rw add_comm,
    repeat {rw ←add_assoc},
    rw [add_comm (binom n_n n_n), ←sum_succ, n_ih,
        binom_succ_kill, add_zero],
    conv in (binom (succ n_n) 0) {
      rw [binom_zero, ←binom_zero n_n],
    },
    rw add_assoc,
    rw ←sum_tail,
    rw n_ih,
    rw pow_succ,
    rw mul_comm,
    refl,
  },
end

private theorem restricted_mpr {m : mynat} {f g : sequence mynat}
(h : ∀ n, n < m → f n = g n) : ∀ n, n ≤ m → sum f n = sum g n
| zero := λ _, rfl
| (succ n) := (assume hnm, by rw [sum_succ, sum_succ,
  restricted_mpr n (@le_cancel_strong n m 1 hnm),
  h n (lt_iff_succ_le.mpr hnm)])

-- this might be necessary/useful when working with -, since it lets
-- you basically assume k < n in order to rewrite the terms
theorem sum_cancel_restricted: (∀ n, n ≤ m → sum f n = sum g n) ↔
(∀ n, n < m → f n = g n) := ⟨assume h n hnm,
have hsn : _ := h (succ n) (lt_iff_succ_le.mp hnm),
by {rw [sum_succ, sum_succ, h n (lt_impl_le hnm)] at hsn,
    from add_cancel hsn},
restricted_mpr⟩

-- β-reduction, basically
private theorem lambda_subs: (λ k, f k) k = f k := rfl
private theorem add_two: ∀ k, k + 2 = succ (succ k) := (λ k, rfl)

private theorem add_dupl: ∀ k: mynat, k + k = 2 * k :=
begin
  intro k,
  rw mul_comm,
  refl,
end

-- the famous diagonal fibonacci sums in Pascal's triangle
theorem binom_fib_sum:
sum (λ k, binom (n + k) (2 * k)) (succ n)
  = fib (2 * n + 1) ∧
sum (λ k, binom (n + succ k) (2 * k + 1)) (succ n)
  = fib (2 * n + 2) :=
begin
  revert n,
  apply induction_conjunction, {
    from and.intro rfl rfl,
  }, {
    intro n,
    split, {
      assume odd_sum even_sum,
      rw [sum_succ, sum_tail],
      have hrw:
          ∀ k,
            (λ k, binom (succ n + succ k) (2 * succ k)) k
            = (λ k, binom (n + succ k) (2 * k + 1)) k
              + (λ k, binom (n + succ k) (2 * succ k)) k, {
        intro k,
        repeat {rw lambda_subs},
        rw [mul_succ, add_comm 2, add_two, add_one_succ],
        simp,
      },
      rw [(sum_cancel _ _).mpr hrw, sum_distr', add_comm],
      clear hrw,
      repeat {rw ←add_assoc},
      have hrw:
          binom (succ n + succ n) (2 * succ n)
          = binom (n + succ n) (2 * n + 1), {
        rw [mul_succ, add_comm 2, add_two, add_one_succ],
        simp [add_dupl],
        rw [add_comm, add_two],
        from binom_dupl _,
      },
      conv {
        to_lhs, congr, congr,
        rw [add_comm, hrw, ←sum_succ, even_sum],
      }, clear hrw,
      have hrw:
          binom (succ n + 0) (2 * 0)
          = binom (n + 0) (2 * 0), {
        simp,
      },
      conv {
        to_lhs,
        rw add_assoc,
        congr, skip,
        rw hrw,
        rw [←sum_tail n (λ k, binom (n + k) (2 * k)),
            odd_sum],
      }, clear hrw,
      conv {
        congr,
        rw add_comm, congr,
        rw add_one_succ,
        skip,
        rw add_two,
        skip,
        rw [mul_succ, add_comm 2, add_one_succ,
            add_two, fib_succsucc],
      },
    }, {
      assume odd_sum_ even_sum odd_sum, clear odd_sum_,
      have hrw:
        ∀ k,
          (λ k, binom (succ n + succ k) (2 * k + 1)) k
          = (λ k, binom (succ n + k) (2 * k)) k
            + (λ k, binom (n + succ k) (2 * k + 1)) k, {
        intro k,
        simp,
      },
      rw (sum_cancel _ _).mpr hrw, clear hrw,
      rw [sum_distr', odd_sum, sum_succ, even_sum],
      have hrw:
        2 * succ n + 1 = succ (succ (succ (n + n))), {
        rw [mul_succ, add_comm 2, add_one_succ, add_two, mul_comm],
        refl,
      },
      conv {
        congr, congr, skip, congr, skip,
        rw lambda_subs,
        congr,
        simp, skip,
        rw hrw,
      }, clear hrw,
      rw [binom_succ_kill, add_zero],
      conv {
        congr, congr,
        rw [mul_succ, add_comm 2, add_one_succ, add_two],
        skip,
        rw add_two,
        skip,
        rw [mul_succ, add_two, add_comm, add_two, fib_succsucc],
      },
      rw add_comm,
    },
  },
end

private theorem mul_sum':
sum (λ k, a * f k) n = a * sum f n := mul_sum _ _ _

theorem binomial_theorem:
(a + b) ^ n = sum (λ k, binom n k * a ^ k * b ^ (n - k)) (succ n) :=
begin
  -- ok what's the one line way to do these because
  -- this is ridic
  have cancel_f:
    ∀ n k: mynat, ∀ f: mynat → mynat,
      n = k → f n = f k, {
    intros n k f,
    assume h,
    rw h,
  },
  induction n with n hn, {
    refl,
  }, {
    rw [pow_succ, hn, add_mul, ←mul_sum', ←mul_sum'],
    have hrw:
      ∀ k,
        (λ k,
          a * (binom n k * a ^ k * b ^ (n - k))
        ) k =
        (λ k,
          binom n k * a ^ succ k * b ^ (n - k)
        ) k, {
      intro k,
      repeat {rw lambda_subs},
      conv {
        to_lhs,
        rw [←mul_assoc, mul_comm _ (a ^ k)],
        congr,
        rw [←mul_assoc, ←pow_succ, mul_comm],
      },
    },
    rw (sum_cancel _ _).mpr hrw, clear hrw,
    have hrw:
      -- ??? why is it being weird about "a"
      ∀ a k, k < succ n →
        (λ k,
          b * (binom n k * a ^ k * b ^ (n - k))
        ) k =
        (λ k,
          binom n k * a ^ k * b ^ (succ n - k)
        ) k, {
      intros a k,
      assume hksn,
      repeat {rw lambda_subs},
      conv {
        to_lhs,
        rw ←mul_assoc,
        congr,
        rw mul_comm,
      },
      repeat {rw mul_assoc},
      apply cancel_f,
      apply cancel_f,
      rw ←pow_succ,
      apply cancel_f,
      cases sub_succ_converse hksn with m hm,
      have := sub_succ_cancel hm,
      rw [this, hm],
    },
    rw (sum_cancel_restricted _ _ _).mpr
       (hrw a) (succ n) le_refl, clear hrw,
    have hrw:
      ∀ a k, k < succ n →
        (λ k,
          binom n k * a ^ succ k * b ^ (n - k)
        ) k =
        (λ k,
          binom n k * a ^ succ k * b ^ (succ n - succ k)
        ) k, {
      intros a k,
      assume hksn,
      repeat {rw lambda_subs},
      rw sub_succ_succ,
    },
    rw [(sum_cancel_restricted _ _ _).mpr
          (hrw a) (succ n) le_refl,
        sum_succ, sum_tail, sum_succ, sum_tail], clear hrw,
    -- convs rearranging
    conv {
      congr,
      congr,
      rw add_comm,
      skip,
      rw add_comm,
      skip,
      rw [add_assoc, add_comm],
      congr,
      rw add_comm,
    },
    conv {
      to_lhs,
      rw ←add_assoc,
      congr,
      rw add_assoc,
      congr,
      skip,
      rw add_comm,
    },
    repeat {rw add_assoc},
    -- convs actually rewriting
    conv {
      congr,
      congr,
      rw [lambda_subs, binom_dupl, one_mul,
          sub_self_eq_zero, pow_zero, mul_one],
      skip,
      congr,
      rw [lambda_subs, binom_zero, one_mul, pow_zero,
          one_mul, sub_zero],
      skip,
      skip,
      congr,
      rw [binom_dupl, sub_self_eq_zero, pow_zero, one_mul, mul_one],
      skip,
      rw [binom_zero, pow_zero, sub_zero, one_mul, one_mul],
    },
    apply cancel_f,
    apply cancel_f,
    rw ←sum_distr',
    apply (sum_cancel _ _).mpr,
    intro k,
    repeat {rw lambda_subs},
    rw [sub_succ_succ, binom_succ_succ, ←add_mul, ←add_mul],
  },
end

-- Lovely corollary
theorem binom_sum:
sum (binom n) (succ n) = 2 ^ n :=
begin
  have hbithm := @binomial_theorem 1 1 n,
  rw ←two_one at hbithm,
  rw hbithm,
  apply apply_sum,
  assume n,
  rw [one_pow, one_pow, mul_one, mul_one],
end

theorem sum_reverse:
sum f n = sum (λ k, f (n - succ k)) n :=
begin
  have cancel_f:
    ∀ n k: mynat, ∀ f: mynat → mynat,
      n = k → f n = f k, {
    intros n k f,
    assume h,
    rw h,
  },
  revert n f,
  -- easy way to access the n - 2 case of IH
  apply duo_induction
    (λ n, ∀ f, sum f n =
      sum (λ k, f (n - succ k)) n), {
    intro f,
    refl,
  }, {
    intro f,
    refl,
  }, {
    intro n,
    assume h_ih _,
    intro f,
    rw [add_two, sum_succ, sum_tail, h_ih,
        sum_succ, sum_tail, sub_succ_succ,
        sub_zero, sub_self_eq_zero],
    conv {
      congr,
      rw [add_assoc, add_comm],
      skip,
      rw [add_assoc, add_comm],
      congr,
      rw add_comm,
    },
    apply cancel_f,
    -- help lean with type inference a bit
    have h_aesthetic := @le_refl n,
    revert h_aesthetic,
    apply (sum_cancel_restricted _ _ _).mpr,
    intro m,
    assume hmn,
    apply cancel_f,
    rw [sub_succ_succ, sub_succ_succ],
    cases sub_succ_converse hmn with d hd,
    symmetry,
    rw [sub_succ_rearrange, sub_succ, hd, succ_sub_one],
    from sub_succ_rearrange.mp hd,
  },
end

-- TODO: general theorem about sum f (n * m) as a double sum?
theorem sum_split:
sum f (2 * n) = sum f n + sum (λ k, f (n + k)) n :=
begin
  induction n with n hn, {
    refl,
  }, {
    rw mul_succ,
    rw add_comm 2,
    have: sum f (2 * n + 2) = sum f (2 * n) + f (2 * n) + f (2 * n + 1) := rfl,
    rw this, clear this,
    rw hn,
    have: f (2 * n) + f (2 * n + 1) = f (n + n) + f (n + succ n), {
      repeat {rw mul_comm 2},
      refl,
    },
    rw add_assoc,
    rw this,
    rw add_assoc,
    conv {
      congr, congr, skip,
      rw ←add_assoc,
      rw ←sum_succ,
      rw ←sum_succ,
      rw sum_tail,
      rw add_comm,
    },
    rw ←add_assoc,
    rw add_zero,
    rw ←sum_succ,
    have: ∀ k, f (n + succ k) = f(succ n + k), {
      simp,
    },
    rw (sum_cancel _ _).mpr this,
  },
end

theorem prod_tail:
product f (succ n) = product (λ k, f (succ k)) n * f 0 :=
begin
  induction n with n hn, {
    refl,
  }, {
    rw prod_succ,
    rw hn,
    rw mul_assoc,
    rw mul_comm (f 0),
    rw ←mul_assoc,
    rw ←prod_succ,
  },
end

theorem prod_congr:
(∀ k, f k = g k) → (∀ n, product f n = product g n) :=
begin
  assume heq,
  intro n,
  induction n with n hn, {
    refl,
  }, {
    repeat {rw prod_succ},
    rw hn,
    rw heq n,
  },
end

theorem prod_split:
product f (2 * n) = product f n * product (λ k, f (n + k)) n :=
begin
  induction n with n hn, {
    refl,
  }, {
    rw mul_succ,
    rw add_comm 2,
    have: product f (2 * n + 2) = product f (2 * n) * f (2 * n) * f (2 * n + 1) := rfl,
    rw this, clear this,
    rw hn,
    have: f (2 * n) * f (2 * n + 1) = f (n + n) * f (n + succ n), {
      repeat {rw mul_comm 2},
      refl,
    },
    rw mul_assoc,
    rw this,
    rw mul_assoc,
    conv {
      congr, congr, skip,
      rw ←mul_assoc,
      rw ←prod_succ,
      rw ←prod_succ,
      rw prod_tail,
      rw mul_comm,
    },
    rw ←mul_assoc,
    rw add_zero,
    rw ←prod_succ,
    have: ∀ k, f (n + succ k) = f(succ n + k), {
      simp,
    },
    rw prod_congr _ _ this,
  },
end

-- this has some things in common with both the binomial theorem
-- and the sum of a GP. Particularly, it's more or less a consequence
-- of the sum of a GP with common ratio in ℚ.
-- However it admits this (superficially) inductionless proof.
-- Note also that this subtraction is not what it may seem
theorem difference_of_powers:
a ^ succ n - b ^ succ n
  = (a - b) * sum (λ k, a ^ k * b ^ (n - k)) (succ n) :=
begin
  rw [sub_mul, ←mul_sum', ←mul_sum', sum_succ,
      sum_tail, sub_self_eq_zero, pow_zero, mul_one,
      pow_zero, one_mul, sub_zero],
  have hrw:
    ∀ a k, k < n →
      (λ k,
        b * (a ^ succ k * b ^ (n - succ k))
      ) k =
      (λ k,
        a * (a ^ k * b ^ (n - k))
      ) k, {
    intros a k,
    assume hkn,
    repeat {rw lambda_subs},
    rw [←mul_assoc, mul_comm b, mul_assoc,
        ←pow_succ, pow_succ a, mul_assoc],
    suffices h: n - k = succ (n - succ k), {
      rw h,
    }, {
      rw [sub_succ_rearrange, succ_add, ←add_succ],
      symmetry,
      from sub_add_condition.mpr (lt_iff_succ_le.mp hkn),
    },
  },
  rw (sum_cancel_restricted _ _ _).mpr (hrw a) _ le_refl,
  clear hrw,
  rw [sub_distr, add_comm, add_sub],
  repeat {rw pow_succ},
end

private lemma am_gm_base:
4 * (a * b) ≤ (a + b) ^ (2: mynat) :=
begin
  wlog_le a b, {
    rw mul_comm a b,
    rw add_comm,
    assumption,
  }, {
    cases hle with d hd,
    subst hd,
    have: (a + d + a) ^ (2: mynat) = (a + d + a) * (a + d + a) := rfl,
    rw this, clear this,
    have: (4: mynat) = 1 + 1 + 1 + 1 := rfl,
    rw this,
    simp,
    existsi d * d,
    ac_refl,
  },
end

theorem am_gm_2:
2 * (a * b) ≤ a * a + b * b :=
begin
  apply @le_cancel _ _ (2 * (a * b)),
  rw ←mul_add,
  have: a * a + b * b + 2 * (a * b) = (a + b) ^ (2: mynat), {
    have: (2: mynat) = 1 + 1 := rfl,
    repeat {rw this},
    simp,
    ac_refl,
  },
  rw this, clear this,
  have: 2 * (a * b + a * b) = 4 * (a * b), {
    have: (4: mynat) = 1 + 1 + 1 + 1 := rfl,
    rw this, clear this,
    have: (2: mynat) = 1 + 1 := rfl,
    rw this,
    simp,
    ac_refl,
  },
  rw this,
  from am_gm_base _ _,
end

private lemma weak_binomial:
a ^ succ n + b ^ succ n ≤ (a + b) ^ succ n :=
begin
  induction n with n hn, {
    refl,
  }, {
    repeat {rw pow_succ _ (succ n)},
    transitivity (a + b) * (a ^ succ n + b ^ succ n), {
      existsi b * a ^ succ n + a * b ^ succ n,
      simp,
      ac_refl,
    }, {
      from le_mul _ hn,
    },
  },
end

private lemma am_gm_double:
(∀ f, n ^ n * product f n ≤ sum f n ^ n)
→ (∀ f, (2 * n) ^ (2 * n) * product f (2 * n) ≤ sum f (2 * n) ^ (2 * n)) :=
begin
  assume h_ih,
  intro f,
  rw mul_pow,
  rw prod_split,
  rw sum_split,
  have: n ^ (2 * n) = n ^ n * n ^ n, {
    rw mul_comm,
    from pow_add _ _ _,
  },
  rw this, clear this,
  have:
    2 ^ (2 * n) * (n ^ n * n ^ n) * (product f n * product (λ (k : mynat), f (n + k)) n)
    = 2 ^ (2 * n) * ((n ^ n) * product f n) * (n ^ n * product (λ (k : mynat), f (n + k)) n), {
    ac_refl,
  },
  rw this, clear this,
  transitivity 2 ^ (2 * n) * sum f n ^ n * sum (λ (k : mynat), f (n + k)) n ^ n, {
    conv {
      congr,
      rw mul_assoc,
      skip,
      rw mul_assoc,
    },
    apply le_mul,
    apply le_mul_comb, {
      apply h_ih,
    }, {
      apply h_ih,
    },
  }, {
    rw ←pow_mul,
    rw ←mul_pow,
    rw ←mul_pow,
    rw ←pow_mul,
    apply pow_monotone_nonstrict,
    rw mul_assoc,
    apply am_gm_base,
  },
end

private lemma am_gm_pow2:
((2: mynat) ^ n) ^ ((2: mynat) ^ n) * product f (2 ^ n)
≤ sum f ((2: mynat) ^ n) ^ ((2: mynat) ^ n) :=
begin
  induction n with n hn generalizing f, {
    simp,
  }, {
    apply am_gm_double _ hn,
  },
end

theorem cantors_weak_thm:
n ≤ 2 ^ n :=
begin
  apply lt_impl_le,
  rw lt_iff_succ_le,
  induction n with n hn, {
    from le_refl,
  }, {
    cases hn with d hd,
    existsi 2 * d + n,
    rw pow_succ,
    rw hd,
    repeat {rw ←add_one_succ <|> rw mul_add},
    have: (2: mynat) * 1 = 2 := rfl, rw this, clear this,
    have: n + 1 + 1 = n + 2 := rfl, rw this, clear this,
    have: 2 * n = n + n, {rw mul_comm, refl,}, rw this, clear this,
    ac_refl,
  },
end

private lemma am_gm_descent_lemma:
(∀ f, succ n ^ succ n * product f (succ n) ≤ sum f (succ n) ^ succ n)
→ (∀ f, n ^ n * product f n ≤ sum f n ^ n) :=
begin
  assume h_ih,
  intro f,
  sorry,
end

private theorem am_gm_le:
(∀ f, n ^ n * product f n ≤ (sum f n) ^ n) →
m ≤ n → (∀ f, m ^ m * product f m ≤ (sum f m) ^ m) :=
begin
  assume h hmn,
  intro f,
  cases hmn with d hd,
  induction d with k hk generalizing n m f, {
    simp at hd,
    subst hd,
    apply h,
  }, {
    apply hk (m + k) m,
    apply am_gm_descent_lemma,
    intro f,
    subst hd,
    apply h,
    refl,
  },
end

theorem am_gm:
n ^ n * product f n ≤ (sum f n) ^ n :=
begin
  apply am_gm_le _ _ _ (cantors_weak_thm n),
  apply am_gm_pow2,
end

theorem cauchy_schwarz:
(sum (λ k, f k * g k) n) ^ (2: mynat)
 ≤ sum (λ k, f k * f k) n * sum (λ k, g k * g k) n :=
begin
  sorry,
end

-- TODO: some sort of cross-over episode with bijections

end mynat

end hidden


import tactic
open nat

variables {m n d : ℤ}
#check one_mul

example : 1 ∣ n :=
begin
  use n,
  rw one_mul,
end

example (hn : n > 0) (hd : d > 0): d ∣ n → d ≤ n :=
begin
  intro hdn,
  rcases hdn with ⟨k, rfl⟩,
  nth_rewrite_lhs 0 ← mul_one d,
  apply mul_le_mul,
  exact le_refl d,
  {
    rcases lt_trichotomy k 0 with hk | rfl | hk;
    nlinarith,
  },
  exact zero_le_one,
  exact le_of_lt hd,
end


import incidence_world.level03 --hide
open IncidencePlane --hide

/-
Remember that a couple of levels ago we proved that a line could help
us deciding that two points were different. Now we will see that
a point can help us deciding that two lines are different.
-/

variables {Ω : Type} [IncidencePlane Ω] --hide
variables {P : Ω} {r s : Line Ω} --hide

/- Lemma :
If two lines `r` and `s` do not share a point, then they are not equal.
-/
lemma ne_of_not_share_point  (hPr : P ∈ r)
(hPs : P ∉ s): r ≠ s:=
begin
  intro H,
  rw H at hPr,
  exact hPs hPr,

end