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

! This file was ported from Lean 3 source module dynamics.ergodic.ergodic
! leanprover-community/mathlib commit 809e920edfa343283cea507aedff916ea0f1bd88
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Dynamics.Ergodic.MeasurePreserving

/-!
# Ergodic maps and measures

Let `f : α → α` be measure preserving with respect to a measure `μ`. We say `f` is ergodic with
respect to `μ` (or `μ` is ergodic with respect to `f`) if the only measurable sets `s` such that
`f⁻¹' s = s` are either almost empty or full.

In this file we define ergodic maps / measures together with quasi-ergodic maps / measures and
provide some basic API. Quasi-ergodicity is a weaker condition than ergodicity for which the measure
preserving condition is relaxed to quasi measure preserving.

# Main definitions:

 * `pre_ergodic`: the ergodicity condition without the measure preserving condition. This exists
   to share code between the `ergodic` and `quasi_ergodic` definitions.
 * `ergodic`: the definition of ergodic maps / measures.
 * `quasi_ergodic`: the definition of quasi ergodic maps / measures.
 * `ergodic.quasi_ergodic`: an ergodic map / measure is quasi ergodic.
 * `quasi_ergodic.ae_empty_or_univ'`: when the map is quasi measure preserving, one may relax the
   strict invariance condition to almost invariance in the ergodicity condition.

-/

open Set Function Filter MeasureTheory MeasureTheory.Measure

open ENNReal

variable {α : Type _} {m : MeasurableSpace α} (f : α → α) {s : Set α}

include m

/-- A map `f : α → α` is said to be pre-ergodic with respect to a measure `μ` if any measurable
strictly invariant set is either almost empty or full. -/
structure PreErgodic (μ : Measure α := by exact MeasureTheory.MeasureSpace.volume) : Prop where
  ae_empty_or_univ : ∀ ⦃s⦄, MeasurableSet s → f ⁻¹' s = s → s =ᵐ[μ] (∅ : Set α) ∨ s =ᵐ[μ] univ
#align pre_ergodic PreErgodic

/-- A map `f : α → α` is said to be ergodic with respect to a measure `μ` if it is measure
preserving and pre-ergodic. -/
@[nolint has_nonempty_instance]
structure Ergodic (μ : Measure α := by exact MeasureTheory.MeasureSpace.volume) extends
  MeasurePreserving f μ μ, PreErgodic f μ : Prop
#align ergodic Ergodic

/-- A map `f : α → α` is said to be quasi ergodic with respect to a measure `μ` if it is quasi
measure preserving and pre-ergodic. -/
@[nolint has_nonempty_instance]
structure QuasiErgodic (μ : Measure α := by exact MeasureTheory.MeasureSpace.volume) extends
  QuasiMeasurePreserving f μ μ, PreErgodic f μ : Prop
#align quasi_ergodic QuasiErgodic

variable {f} {μ : Measure α}

namespace PreErgodic

theorem measure_self_or_compl_eq_zero (hf : PreErgodic f μ) (hs : MeasurableSet s)
    (hs' : f ⁻¹' s = s) : μ s = 0 ∨ μ (sᶜ) = 0 := by simpa using hf.ae_empty_or_univ hs hs'
#align pre_ergodic.measure_self_or_compl_eq_zero PreErgodic.measure_self_or_compl_eq_zero

/-- On a probability space, the (pre)ergodicity condition is a zero one law. -/
theorem prob_eq_zero_or_one [IsProbabilityMeasure μ] (hf : PreErgodic f μ) (hs : MeasurableSet s)
    (hs' : f ⁻¹' s = s) : μ s = 0 ∨ μ s = 1 := by
  simpa [hs] using hf.measure_self_or_compl_eq_zero hs hs'
#align pre_ergodic.prob_eq_zero_or_one PreErgodic.prob_eq_zero_or_one

theorem ofIterate (n : ℕ) (hf : PreErgodic (f^[n]) μ) : PreErgodic f μ :=
  ⟨fun s hs hs' => hf.ae_empty_or_univ hs <| IsFixedPt.preimage_iterate hs' n⟩
#align pre_ergodic.of_iterate PreErgodic.ofIterate

end PreErgodic

namespace MeasureTheory.MeasurePreserving

variable {β : Type _} {m' : MeasurableSpace β} {μ' : Measure β} {s' : Set β} {g : α → β}

theorem preErgodicOfPreErgodicConjugate (hg : MeasurePreserving g μ μ') (hf : PreErgodic f μ)
    {f' : β → β} (h_comm : g ∘ f = f' ∘ g) : PreErgodic f' μ' :=
  ⟨by
    intro s hs₀ hs₁
    replace hs₁ : f ⁻¹' (g ⁻¹' s) = g ⁻¹' s; · rw [← preimage_comp, h_comm, preimage_comp, hs₁]
    cases' hf.ae_empty_or_univ (hg.measurable hs₀) hs₁ with hs₂ hs₂ <;> [left, right]
    · simpa only [ae_eq_empty, hg.measure_preimage hs₀] using hs₂
    · simpa only [ae_eq_univ, ← preimage_compl, hg.measure_preimage hs₀.compl] using hs₂⟩
#align measure_theory.measure_preserving.pre_ergodic_of_pre_ergodic_conjugate MeasureTheory.MeasurePreserving.preErgodicOfPreErgodicConjugate

theorem preErgodic_conjugate_iff {e : α ≃ᵐ β} (h : MeasurePreserving e μ μ') :
    PreErgodic (e ∘ f ∘ e.symm) μ' ↔ PreErgodic f μ :=
  by
  refine'
    ⟨fun hf => pre_ergodic_of_pre_ergodic_conjugate (h.symm e) hf _, fun hf =>
      pre_ergodic_of_pre_ergodic_conjugate h hf _⟩
  · change (e.symm ∘ e) ∘ f ∘ e.symm = f ∘ e.symm
    rw [MeasurableEquiv.symm_comp_self, comp.left_id]
  · change e ∘ f = e ∘ f ∘ e.symm ∘ e
    rw [MeasurableEquiv.symm_comp_self, comp.right_id]
#align measure_theory.measure_preserving.pre_ergodic_conjugate_iff MeasureTheory.MeasurePreserving.preErgodic_conjugate_iff

theorem ergodic_conjugate_iff {e : α ≃ᵐ β} (h : MeasurePreserving e μ μ') :
    Ergodic (e ∘ f ∘ e.symm) μ' ↔ Ergodic f μ :=
  by
  have : measure_preserving (e ∘ f ∘ e.symm) μ' μ' ↔ measure_preserving f μ μ := by
    rw [h.comp_left_iff, (measure_preserving.symm e h).comp_right_iff]
  replace h : PreErgodic (e ∘ f ∘ e.symm) μ' ↔ PreErgodic f μ := h.pre_ergodic_conjugate_iff
  exact
    ⟨fun hf => { this.mp hf.toMeasurePreserving, h.mp hf.toPreErgodic with }, fun hf =>
      { this.mpr hf.toMeasurePreserving, h.mpr hf.toPreErgodic with }⟩
#align measure_theory.measure_preserving.ergodic_conjugate_iff MeasureTheory.MeasurePreserving.ergodic_conjugate_iff

end MeasureTheory.MeasurePreserving

namespace QuasiErgodic

/-- For a quasi ergodic map, sets that are almost invariant (rather than strictly invariant) are
still either almost empty or full. -/
theorem ae_empty_or_univ' (hf : QuasiErgodic f μ) (hs : MeasurableSet s) (hs' : f ⁻¹' s =ᵐ[μ] s) :
    s =ᵐ[μ] (∅ : Set α) ∨ s =ᵐ[μ] univ :=
  by
  obtain ⟨t, h₀, h₁, h₂⟩ := hf.to_quasi_measure_preserving.exists_preimage_eq_of_preimage_ae hs hs'
  rcases hf.ae_empty_or_univ h₀ h₂ with (h₃ | h₃) <;> [left, right] <;> exact ae_eq_trans h₁.symm h₃
#align quasi_ergodic.ae_empty_or_univ' QuasiErgodic.ae_empty_or_univ'

end QuasiErgodic

namespace Ergodic

/-- An ergodic map is quasi ergodic. -/
theorem quasiErgodic (hf : Ergodic f μ) : QuasiErgodic f μ :=
  { hf.toPreErgodic, hf.toMeasurePreserving.QuasiMeasurePreserving with }
#align ergodic.quasi_ergodic Ergodic.quasiErgodic

/-- See also `ergodic.ae_empty_or_univ_of_preimage_ae_le`. -/
theorem ae_empty_or_univ_of_preimage_ae_le' (hf : Ergodic f μ) (hs : MeasurableSet s)
    (hs' : f ⁻¹' s ≤ᵐ[μ] s) (h_fin : μ s ≠ ∞) : s =ᵐ[μ] (∅ : Set α) ∨ s =ᵐ[μ] univ :=
  by
  refine' hf.quasi_ergodic.ae_empty_or_univ' hs _
  refine' ae_eq_of_ae_subset_of_measure_ge hs' (hf.measure_preimage hs).symm.le _ h_fin
  exact measurableSet_preimage hf.measurable hs
#align ergodic.ae_empty_or_univ_of_preimage_ae_le' Ergodic.ae_empty_or_univ_of_preimage_ae_le'

/-- See also `ergodic.ae_empty_or_univ_of_ae_le_preimage`. -/
theorem ae_empty_or_univ_of_ae_le_preimage' (hf : Ergodic f μ) (hs : MeasurableSet s)
    (hs' : s ≤ᵐ[μ] f ⁻¹' s) (h_fin : μ s ≠ ∞) : s =ᵐ[μ] (∅ : Set α) ∨ s =ᵐ[μ] univ :=
  by
  replace h_fin : μ (f ⁻¹' s) ≠ ∞; · rwa [hf.measure_preimage hs]
  refine' hf.quasi_ergodic.ae_empty_or_univ' hs _
  exact (ae_eq_of_ae_subset_of_measure_ge hs' (hf.measure_preimage hs).le hs h_fin).symm
#align ergodic.ae_empty_or_univ_of_ae_le_preimage' Ergodic.ae_empty_or_univ_of_ae_le_preimage'

/-- See also `ergodic.ae_empty_or_univ_of_image_ae_le`. -/
theorem ae_empty_or_univ_of_image_ae_le' (hf : Ergodic f μ) (hs : MeasurableSet s)
    (hs' : f '' s ≤ᵐ[μ] s) (h_fin : μ s ≠ ∞) : s =ᵐ[μ] (∅ : Set α) ∨ s =ᵐ[μ] univ :=
  by
  replace hs' : s ≤ᵐ[μ] f ⁻¹' s :=
    (HasSubset.Subset.eventuallyLE (subset_preimage_image f s)).trans
      (hf.quasi_measure_preserving.preimage_mono_ae hs')
  exact ae_empty_or_univ_of_ae_le_preimage' hf hs hs' h_fin
#align ergodic.ae_empty_or_univ_of_image_ae_le' Ergodic.ae_empty_or_univ_of_image_ae_le'

section IsFiniteMeasure

variable [IsFiniteMeasure μ]

theorem ae_empty_or_univ_of_preimage_ae_le (hf : Ergodic f μ) (hs : MeasurableSet s)
    (hs' : f ⁻¹' s ≤ᵐ[μ] s) : s =ᵐ[μ] (∅ : Set α) ∨ s =ᵐ[μ] univ :=
  ae_empty_or_univ_of_preimage_ae_le' hf hs hs' <| measure_ne_top μ s
#align ergodic.ae_empty_or_univ_of_preimage_ae_le Ergodic.ae_empty_or_univ_of_preimage_ae_le

theorem ae_empty_or_univ_of_ae_le_preimage (hf : Ergodic f μ) (hs : MeasurableSet s)
    (hs' : s ≤ᵐ[μ] f ⁻¹' s) : s =ᵐ[μ] (∅ : Set α) ∨ s =ᵐ[μ] univ :=
  ae_empty_or_univ_of_ae_le_preimage' hf hs hs' <| measure_ne_top μ s
#align ergodic.ae_empty_or_univ_of_ae_le_preimage Ergodic.ae_empty_or_univ_of_ae_le_preimage

theorem ae_empty_or_univ_of_image_ae_le (hf : Ergodic f μ) (hs : MeasurableSet s)
    (hs' : f '' s ≤ᵐ[μ] s) : s =ᵐ[μ] (∅ : Set α) ∨ s =ᵐ[μ] univ :=
  ae_empty_or_univ_of_image_ae_le' hf hs hs' <| measure_ne_top μ s
#align ergodic.ae_empty_or_univ_of_image_ae_le Ergodic.ae_empty_or_univ_of_image_ae_le

end IsFiniteMeasure

end Ergodic


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

! This file was ported from Lean 3 source module data.bool.basic
! leanprover-community/mathlib commit 448144f7ae193a8990cb7473c9e9a01990f64ac7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/

/-!
# booleans

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

This file proves various trivial lemmas about booleans and their
relation to decidable propositions.

## Notations

This file introduces the notation `!b` for `bnot b`, the boolean "not".

## Tags
bool, boolean, De Morgan
-/

-- mathport name: «expr! »
prefix:90 "!" => not

namespace Bool

-- TODO: duplicate of a lemma in core
theorem coeSort_true : coeSort.{1, 1} true = True :=
  Bool.coe_sort_true
#align bool.coe_sort_tt Bool.coeSort_true

-- TODO: duplicate of a lemma in core
theorem coeSort_false : coeSort.{1, 1} false = False :=
  Bool.coe_sort_false
#align bool.coe_sort_ff Bool.coeSort_false

#print Bool.decide_True /-
-- TODO: duplicate of a lemma in core
theorem decide_True {h} : @decide True h = true :=
  decide_True' h
#align bool.to_bool_true Bool.decide_True
-/

#print Bool.decide_False /-
-- TODO: duplicate of a lemma in core
theorem decide_False {h} : @decide False h = false :=
  decide_False' h
#align bool.to_bool_false Bool.decide_False
-/

#print Bool.decide_coe /-
@[simp]
theorem decide_coe (b : Bool) {h} : @decide b h = b :=
  show _ = decide b by congr.trans (by cases b <;> rfl)
#align bool.to_bool_coe Bool.decide_coe
-/

#print Bool.coe_decide /-
theorem coe_decide (p : Prop) [Decidable p] : decide p ↔ p :=
  Bool.decide_iff _
#align bool.coe_to_bool Bool.coe_decide
-/

#print Bool.of_decide_iff /-
@[simp]
theorem of_decide_iff {p : Prop} [Decidable p] : decide p ↔ p :=
  ⟨Bool.of_decide_true, Bool.decide_true⟩
#align bool.of_to_bool_iff Bool.of_decide_iff
-/

#print Bool.true_eq_decide_iff /-
@[simp]
theorem true_eq_decide_iff {p : Prop} [Decidable p] : true = decide p ↔ p :=
  eq_comm.trans of_decide_iff
#align bool.tt_eq_to_bool_iff Bool.true_eq_decide_iff
-/

#print Bool.false_eq_decide_iff /-
@[simp]
theorem false_eq_decide_iff {p : Prop} [Decidable p] : false = decide p ↔ ¬p :=
  eq_comm.trans (Bool.decide_false_iff _)
#align bool.ff_eq_to_bool_iff Bool.false_eq_decide_iff
-/

#print Bool.decide_not /-
@[simp]
theorem decide_not (p : Prop) [Decidable p] : (decide ¬p) = !decide p := by by_cases p <;> simp [*]
#align bool.to_bool_not Bool.decide_not
-/

#print Bool.decide_and /-
@[simp]
theorem decide_and (p q : Prop) [Decidable p] [Decidable q] : decide (p ∧ q) = (p && q) := by
  by_cases p <;> by_cases q <;> simp [*]
#align bool.to_bool_and Bool.decide_and
-/

#print Bool.decide_or /-
@[simp]
theorem decide_or (p q : Prop) [Decidable p] [Decidable q] : decide (p ∨ q) = (p || q) := by
  by_cases p <;> by_cases q <;> simp [*]
#align bool.to_bool_or Bool.decide_or
-/

#print decide_eq_decide /-
@[simp]
theorem decide_eq_decide {p q : Prop} [Decidable p] [Decidable q] : decide p = decide q ↔ (p ↔ q) :=
  ⟨fun h => (coe_decide p).symm.trans <| by simp [h], Bool.decide_congr⟩
#align bool.to_bool_eq decide_eq_decide
-/

#print Bool.not_false' /-
theorem not_false' : ¬false :=
  false_ne_true
#align bool.not_ff Bool.not_false'
-/

#print Bool.default_bool /-
@[simp]
theorem default_bool : default = false :=
  rfl
#align bool.default_bool Bool.default_bool
-/

#print Bool.dichotomy /-
theorem dichotomy (b : Bool) : b = false ∨ b = true := by cases b <;> simp
#align bool.dichotomy Bool.dichotomy
-/

#print Bool.forall_bool /-
@[simp]
theorem forall_bool {p : Bool → Prop} : (∀ b, p b) ↔ p false ∧ p true :=
  ⟨fun h => by simp [h], fun ⟨h₁, h₂⟩ b => by cases b <;> assumption⟩
#align bool.forall_bool Bool.forall_bool
-/

#print Bool.exists_bool /-
@[simp]
theorem exists_bool {p : Bool → Prop} : (∃ b, p b) ↔ p false ∨ p true :=
  ⟨fun ⟨b, h⟩ => by cases b <;> [exact Or.inl h, exact Or.inr h], fun h => by
    cases h <;> exact ⟨_, h⟩⟩
#align bool.exists_bool Bool.exists_bool
-/

#print Bool.decidableForallBool /-
/-- If `p b` is decidable for all `b : bool`, then `∀ b, p b` is decidable -/
instance decidableForallBool {p : Bool → Prop} [∀ b, Decidable (p b)] : Decidable (∀ b, p b) :=
  decidable_of_decidable_of_iff And.decidable forall_bool.symm
#align bool.decidable_forall_bool Bool.decidableForallBool
-/

#print Bool.decidableExistsBool /-
/-- If `p b` is decidable for all `b : bool`, then `∃ b, p b` is decidable -/
instance decidableExistsBool {p : Bool → Prop} [∀ b, Decidable (p b)] : Decidable (∃ b, p b) :=
  decidable_of_decidable_of_iff Or.decidable exists_bool.symm
#align bool.decidable_exists_bool Bool.decidableExistsBool
-/

#print Bool.cond_false /-
@[simp]
theorem cond_false {α} (t e : α) : cond false t e = e :=
  rfl
#align bool.cond_ff Bool.cond_false
-/

#print Bool.cond_true /-
@[simp]
theorem cond_true {α} (t e : α) : cond true t e = t :=
  rfl
#align bool.cond_tt Bool.cond_true
-/

#print Bool.cond_eq_ite /-
theorem cond_eq_ite {α} (b : Bool) (t e : α) : cond b t e = if b then t else e := by
  cases b <;> simp
#align bool.cond_eq_ite Bool.cond_eq_ite
-/

#print Bool.cond_decide /-
@[simp]
theorem cond_decide {α} (p : Prop) [Decidable p] (t e : α) :
    cond (decide p) t e = if p then t else e := by simp [cond_eq_ite]
#align bool.cond_to_bool Bool.cond_decide
-/

#print Bool.cond_not /-
@[simp]
theorem cond_not {α} (b : Bool) (t e : α) : cond (!b) t e = cond b e t := by cases b <;> rfl
#align bool.cond_bnot Bool.cond_not
-/

#print Bool.not_ne_id /-
theorem not_ne_id : not ≠ id := fun h => false_ne_true <| congr_fun h true
#align bool.bnot_ne_id Bool.not_ne_id
-/

#print Bool.coe_bool_iff /-
theorem coe_bool_iff : ∀ {a b : Bool}, (a ↔ b) ↔ a = b := by decide
#align bool.coe_bool_iff Bool.coe_bool_iff
-/

#print Bool.eq_true_of_ne_false /-
theorem eq_true_of_ne_false : ∀ {a : Bool}, a ≠ false → a = true := by decide
#align bool.eq_tt_of_ne_ff Bool.eq_true_of_ne_false
-/

#print Bool.eq_false_of_ne_true /-
theorem eq_false_of_ne_true : ∀ {a : Bool}, a ≠ true → a = false := by decide
#align bool.eq_ff_of_ne_tt Bool.eq_false_of_ne_true
-/

#print Bool.or_comm /-
theorem or_comm : ∀ a b, (a || b) = (b || a) := by decide
#align bool.bor_comm Bool.or_comm
-/

#print Bool.or_assoc /-
@[simp]
theorem or_assoc : ∀ a b c, (a || b || c) = (a || (b || c)) := by decide
#align bool.bor_assoc Bool.or_assoc
-/

#print Bool.or_left_comm /-
theorem or_left_comm : ∀ a b c, (a || (b || c)) = (b || (a || c)) := by decide
#align bool.bor_left_comm Bool.or_left_comm
-/

#print Bool.or_inl /-
theorem or_inl {a b : Bool} (H : a) : a || b := by simp [H]
#align bool.bor_inl Bool.or_inl
-/

#print Bool.or_inr /-
theorem or_inr {a b : Bool} (H : b) : a || b := by simp [H]
#align bool.bor_inr Bool.or_inr
-/

#print Bool.and_comm /-
theorem and_comm : ∀ a b, (a && b) = (b && a) := by decide
#align bool.band_comm Bool.and_comm
-/

#print Bool.and_assoc /-
@[simp]
theorem and_assoc : ∀ a b c, (a && b && c) = (a && (b && c)) := by decide
#align bool.band_assoc Bool.and_assoc
-/

#print Bool.and_left_comm /-
theorem and_left_comm : ∀ a b c, (a && (b && c)) = (b && (a && c)) := by decide
#align bool.band_left_comm Bool.and_left_comm
-/

#print Bool.and_elim_left /-
theorem and_elim_left : ∀ {a b : Bool}, a && b → a := by decide
#align bool.band_elim_left Bool.and_elim_left
-/

#print Bool.and_intro /-
theorem and_intro : ∀ {a b : Bool}, a → b → a && b := by decide
#align bool.band_intro Bool.and_intro
-/

#print Bool.and_elim_right /-
theorem and_elim_right : ∀ {a b : Bool}, a && b → b := by decide
#align bool.band_elim_right Bool.and_elim_right
-/

#print Bool.and_or_distrib_left /-
theorem and_or_distrib_left (a b c : Bool) : (a && (b || c)) = (a && b || a && c) := by
  cases a <;> simp
#align bool.band_bor_distrib_left Bool.and_or_distrib_left
-/

#print Bool.and_or_distrib_right /-
theorem and_or_distrib_right (a b c : Bool) : ((a || b) && c) = (a && c || b && c) := by
  cases c <;> simp
#align bool.band_bor_distrib_right Bool.and_or_distrib_right
-/

#print Bool.or_and_distrib_left /-
theorem or_and_distrib_left (a b c : Bool) : (a || b && c) = ((a || b) && (a || c)) := by
  cases a <;> simp
#align bool.bor_band_distrib_left Bool.or_and_distrib_left
-/

#print Bool.or_and_distrib_right /-
theorem or_and_distrib_right (a b c : Bool) : (a && b || c) = ((a || c) && (b || c)) := by
  cases c <;> simp
#align bool.bor_band_distrib_right Bool.or_and_distrib_right
-/

#print Bool.not_false /-
@[simp]
theorem not_false : !false = true :=
  rfl
#align bool.bnot_ff Bool.not_false
-/

#print Bool.not_true /-
@[simp]
theorem not_true : !true = false :=
  rfl
#align bool.bnot_tt Bool.not_true
-/

#print Bool.eq_not_iff /-
theorem eq_not_iff : ∀ {a b : Bool}, a = !b ↔ a ≠ b := by decide
#align bool.eq_bnot_iff Bool.eq_not_iff
-/

/- warning: bool.bnot_eq_iff -> Bool.not_eq_iff is a dubious translation:
lean 3 declaration is
  forall {a : Bool} {b : Bool}, Iff (Eq.{1} Bool (not a) b) (Ne.{1} Bool a b)
but is expected to have type
  forall {a : Bool} {b : Bool}, Iff (Eq.{1} Bool (not (Decidable.decide (Eq.{1} Bool a b) (instDecidableEqBool a b))) Bool.true) (Ne.{1} Bool a b)
Case conversion may be inaccurate. Consider using '#align bool.bnot_eq_iff Bool.not_eq_iffₓ'. -/
theorem not_eq_iff : ∀ {a b : Bool}, !a = b ↔ a ≠ b := by decide
#align bool.bnot_eq_iff Bool.not_eq_iff

#print Bool.not_eq_not /-
@[simp]
theorem not_eq_not : ∀ {a b : Bool}, ¬a = !b ↔ a = b := by decide
#align bool.not_eq_bnot Bool.not_eq_not
-/

#print Bool.not_not_eq /-
@[simp]
theorem not_not_eq : ∀ {a b : Bool}, ¬!a = b ↔ a = b := by decide
#align bool.bnot_not_eq Bool.not_not_eq
-/

#print Bool.ne_not /-
theorem ne_not {a b : Bool} : a ≠ !b ↔ a = b :=
  not_eq_not
#align bool.ne_bnot Bool.ne_not
-/

#print Bool.not_ne /-
theorem not_ne {a b : Bool} : !a ≠ b ↔ a = b :=
  not_not_eq
#align bool.bnot_ne Bool.not_ne
-/

#print Bool.not_ne_self /-
theorem not_ne_self : ∀ b : Bool, !b ≠ b := by decide
#align bool.bnot_ne_self Bool.not_ne_self
-/

#print Bool.self_ne_not /-
theorem self_ne_not : ∀ b : Bool, b ≠ !b := by decide
#align bool.self_ne_bnot Bool.self_ne_not
-/

#print Bool.eq_or_eq_not /-
theorem eq_or_eq_not : ∀ a b, a = b ∨ a = !b := by decide
#align bool.eq_or_eq_bnot Bool.eq_or_eq_not
-/

#print Bool.not_iff_not /-
@[simp]
theorem not_iff_not : ∀ {b : Bool}, !b ↔ ¬b := by decide
#align bool.bnot_iff_not Bool.not_iff_not
-/

/- warning: bool.eq_tt_of_bnot_eq_ff -> Bool.eq_true_of_not_eq_false' is a dubious translation:
lean 3 declaration is
  forall {a : Bool}, (Eq.{1} Bool (not a) Bool.false) -> (Eq.{1} Bool a Bool.true)
but is expected to have type
  forall {a : Bool}, (Eq.{1} Bool (not (Decidable.decide (Eq.{1} Bool a Bool.false) (instDecidableEqBool a Bool.false))) Bool.true) -> (Eq.{1} Bool a Bool.true)
Case conversion may be inaccurate. Consider using '#align bool.eq_tt_of_bnot_eq_ff Bool.eq_true_of_not_eq_false'ₓ'. -/
theorem eq_true_of_not_eq_false' : ∀ {a : Bool}, !a = false → a = true := by decide
#align bool.eq_tt_of_bnot_eq_ff Bool.eq_true_of_not_eq_false'

/- warning: bool.eq_ff_of_bnot_eq_tt -> Bool.eq_false_of_not_eq_true' is a dubious translation:
lean 3 declaration is
  forall {a : Bool}, (Eq.{1} Bool (not a) Bool.true) -> (Eq.{1} Bool a Bool.false)
but is expected to have type
  forall {a : Bool}, (Eq.{1} Bool (not (Decidable.decide (Eq.{1} Bool a Bool.true) (instDecidableEqBool a Bool.true))) Bool.true) -> (Eq.{1} Bool a Bool.false)
Case conversion may be inaccurate. Consider using '#align bool.eq_ff_of_bnot_eq_tt Bool.eq_false_of_not_eq_true'ₓ'. -/
theorem eq_false_of_not_eq_true' : ∀ {a : Bool}, !a = true → a = false := by decide
#align bool.eq_ff_of_bnot_eq_tt Bool.eq_false_of_not_eq_true'

#print Bool.and_not_self /-
@[simp]
theorem and_not_self : ∀ x, (x && !x) = false := by decide
#align bool.band_bnot_self Bool.and_not_self
-/

#print Bool.not_and_self /-
@[simp]
theorem not_and_self : ∀ x, (!x && x) = false := by decide
#align bool.bnot_band_self Bool.not_and_self
-/

#print Bool.or_not_self /-
@[simp]
theorem or_not_self : ∀ x, (x || !x) = true := by decide
#align bool.bor_bnot_self Bool.or_not_self
-/

#print Bool.not_or_self /-
@[simp]
theorem not_or_self : ∀ x, (!x || x) = true := by decide
#align bool.bnot_bor_self Bool.not_or_self
-/

#print Bool.xor_comm /-
theorem xor_comm : ∀ a b, xor a b = xor b a := by decide
#align bool.bxor_comm Bool.xor_comm
-/

#print Bool.xor_assoc /-
@[simp]
theorem xor_assoc : ∀ a b c, xor (xor a b) c = xor a (xor b c) := by decide
#align bool.bxor_assoc Bool.xor_assoc
-/

#print Bool.xor_left_comm /-
theorem xor_left_comm : ∀ a b c, xor a (xor b c) = xor b (xor a c) := by decide
#align bool.bxor_left_comm Bool.xor_left_comm
-/

#print Bool.xor_not_left /-
@[simp]
theorem xor_not_left : ∀ a, xor (!a) a = true := by decide
#align bool.bxor_bnot_left Bool.xor_not_left
-/

#print Bool.xor_not_right /-
@[simp]
theorem xor_not_right : ∀ a, xor a (!a) = true := by decide
#align bool.bxor_bnot_right Bool.xor_not_right
-/

#print Bool.xor_not_not /-
@[simp]
theorem xor_not_not : ∀ a b, xor (!a) (!b) = xor a b := by decide
#align bool.bxor_bnot_bnot Bool.xor_not_not
-/

#print Bool.xor_false_left /-
@[simp]
theorem xor_false_left : ∀ a, xor false a = a := by decide
#align bool.bxor_ff_left Bool.xor_false_left
-/

#print Bool.xor_false_right /-
@[simp]
theorem xor_false_right : ∀ a, xor a false = a := by decide
#align bool.bxor_ff_right Bool.xor_false_right
-/

#print Bool.and_xor_distrib_left /-
theorem and_xor_distrib_left (a b c : Bool) : (a && xor b c) = xor (a && b) (a && c) := by
  cases a <;> simp
#align bool.band_bxor_distrib_left Bool.and_xor_distrib_left
-/

#print Bool.and_xor_distrib_right /-
theorem and_xor_distrib_right (a b c : Bool) : (xor a b && c) = xor (a && c) (b && c) := by
  cases c <;> simp
#align bool.band_bxor_distrib_right Bool.and_xor_distrib_right
-/

#print Bool.xor_iff_ne /-
theorem xor_iff_ne : ∀ {x y : Bool}, xor x y = true ↔ x ≠ y := by decide
#align bool.bxor_iff_ne Bool.xor_iff_ne
-/

/-! ### De Morgan's laws for booleans-/

/- warning: bool.bnot_band -> Bool.not_and is a dubious translation:
lean 3 declaration is
  forall (a : Bool) (b : Bool), Eq.{1} Bool (not (and a b)) (or (not a) (not b))
but is expected to have type
  forall (a : Bool) (b : Bool), Eq.{1} Bool (not (Decidable.decide (Eq.{1} Bool (and a b) (or (not a) (not b))) (instDecidableEqBool (and a b) (or (not a) (not b))))) Bool.true
Case conversion may be inaccurate. Consider using '#align bool.bnot_band Bool.not_andₓ'. -/
@[simp]
theorem not_and : ∀ a b : Bool, !(a && b) = (!a || !b) := by decide
#align bool.bnot_band Bool.not_and

/- warning: bool.bnot_bor -> Bool.not_or is a dubious translation:
lean 3 declaration is
  forall (a : Bool) (b : Bool), Eq.{1} Bool (not (or a b)) (and (not a) (not b))
but is expected to have type
  forall (a : Bool) (b : Bool), Eq.{1} Bool (not (Decidable.decide (Eq.{1} Bool (or a b) (and (not a) (not b))) (instDecidableEqBool (or a b) (and (not a) (not b))))) Bool.true
Case conversion may be inaccurate. Consider using '#align bool.bnot_bor Bool.not_orₓ'. -/
@[simp]
theorem not_or : ∀ a b : Bool, !(a || b) = (!a && !b) := by decide
#align bool.bnot_bor Bool.not_or

/- warning: bool.bnot_inj -> Bool.not_inj is a dubious translation:
lean 3 declaration is
  forall {a : Bool} {b : Bool}, (Eq.{1} Bool (not a) (not b)) -> (Eq.{1} Bool a b)
but is expected to have type
  forall {a : Bool} {b : Bool}, (Eq.{1} Bool (not (Decidable.decide (Eq.{1} Bool a (not b)) (instDecidableEqBool a (not b)))) Bool.true) -> (Eq.{1} Bool a b)
Case conversion may be inaccurate. Consider using '#align bool.bnot_inj Bool.not_injₓ'. -/
theorem not_inj : ∀ {a b : Bool}, !a = !b → a = b := by decide
#align bool.bnot_inj Bool.not_inj

instance : LinearOrder Bool where
  le a b := a = false ∨ b = true
  le_refl := by decide
  le_trans := by decide
  le_antisymm := by decide
  le_total := by decide
  decidableLe := inferInstance
  DecidableEq := inferInstance
  max := or
  max_def := by
    funext x y
    revert x y
    exact by decide
  min := and
  min_def := by
    funext x y
    revert x y
    exact by decide

#print Bool.false_le /-
@[simp]
theorem false_le {x : Bool} : false ≤ x :=
  Or.intro_left _ rfl
#align bool.ff_le Bool.false_le
-/

#print Bool.le_true /-
@[simp]
theorem le_true {x : Bool} : x ≤ true :=
  Or.intro_right _ rfl
#align bool.le_tt Bool.le_true
-/

#print Bool.lt_iff /-
theorem lt_iff : ∀ {x y : Bool}, x < y ↔ x = false ∧ y = true := by decide
#align bool.lt_iff Bool.lt_iff
-/

#print Bool.false_lt_true /-
@[simp]
theorem false_lt_true : false < true :=
  lt_iff.2 ⟨rfl, rfl⟩
#align bool.ff_lt_tt Bool.false_lt_true
-/

#print Bool.le_iff_imp /-
theorem le_iff_imp : ∀ {x y : Bool}, x ≤ y ↔ x → y := by decide
#align bool.le_iff_imp Bool.le_iff_imp
-/

#print Bool.and_le_left /-
theorem and_le_left : ∀ x y : Bool, (x && y) ≤ x := by decide
#align bool.band_le_left Bool.and_le_left
-/

#print Bool.and_le_right /-
theorem and_le_right : ∀ x y : Bool, (x && y) ≤ y := by decide
#align bool.band_le_right Bool.and_le_right
-/

#print Bool.le_and /-
theorem le_and : ∀ {x y z : Bool}, x ≤ y → x ≤ z → x ≤ (y && z) := by decide
#align bool.le_band Bool.le_and
-/

#print Bool.left_le_or /-
theorem left_le_or : ∀ x y : Bool, x ≤ (x || y) := by decide
#align bool.left_le_bor Bool.left_le_or
-/

#print Bool.right_le_or /-
theorem right_le_or : ∀ x y : Bool, y ≤ (x || y) := by decide
#align bool.right_le_bor Bool.right_le_or
-/

#print Bool.or_le /-
theorem or_le : ∀ {x y z}, x ≤ z → y ≤ z → (x || y) ≤ z := by decide
#align bool.bor_le Bool.or_le
-/

#print Bool.toNat /-
/-- convert a `bool` to a `ℕ`, `false -> 0`, `true -> 1` -/
def toNat (b : Bool) : ℕ :=
  cond b 1 0
#align bool.to_nat Bool.toNat
-/

#print Bool.ofNat /-
/-- convert a `ℕ` to a `bool`, `0 -> false`, everything else -> `true` -/
def ofNat (n : ℕ) : Bool :=
  decide (n ≠ 0)
#align bool.of_nat Bool.ofNat
-/

#print Bool.ofNat_le_ofNat /-
theorem ofNat_le_ofNat {n m : ℕ} (h : n ≤ m) : ofNat n ≤ ofNat m :=
  by
  simp [of_nat] <;> cases Nat.decidableEq n 0 <;> cases Nat.decidableEq m 0 <;> simp only [to_bool]
  · subst m
    have h := le_antisymm h (Nat.zero_le _)
    contradiction
  · left
    rfl
#align bool.of_nat_le_of_nat Bool.ofNat_le_ofNat
-/

#print Bool.toNat_le_toNat /-
theorem toNat_le_toNat {b₀ b₁ : Bool} (h : b₀ ≤ b₁) : toNat b₀ ≤ toNat b₁ := by
  cases h <;> subst h <;> [cases b₁, cases b₀] <;> simp [to_nat, Nat.zero_le]
#align bool.to_nat_le_to_nat Bool.toNat_le_toNat
-/

#print Bool.ofNat_toNat /-
theorem ofNat_toNat (b : Bool) : ofNat (toNat b) = b := by
  cases b <;> simp only [of_nat, to_nat] <;> exact by decide
#align bool.of_nat_to_nat Bool.ofNat_toNat
-/

#print Bool.injective_iff /-
@[simp]
theorem injective_iff {α : Sort _} {f : Bool → α} : Function.Injective f ↔ f false ≠ f true :=
  ⟨fun Hinj Heq => false_ne_true (Hinj Heq), fun H x y hxy =>
    by
    cases x <;> cases y
    exacts[rfl, (H hxy).elim, (H hxy.symm).elim, rfl]⟩
#align bool.injective_iff Bool.injective_iff
-/

#print Bool.apply_apply_apply /-
/-- **Kaminski's Equation** -/
theorem apply_apply_apply (f : Bool → Bool) (x : Bool) : f (f (f x)) = f x := by
  cases x <;> cases h₁ : f tt <;> cases h₂ : f ff <;> simp only [h₁, h₂]
#align bool.apply_apply_apply Bool.apply_apply_apply
-/

end Bool


/-
Copyright (c) 2019 Lucas Allen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Lucas Allen

Tests for the `conv` tactic inside the inveractive `conv` monad
-/

import tactic.converter.interactive

example (a b c d : ℕ) (h₁ : b = c) (h₂ : a + c = a + d) : a + b = a + d :=
begin
conv {     --| a + b = a + d
  --Conv to the left hand side
  to_lhs,  -- | a + b
  --Zoom into the left hand side.
  conv {   -- | a + b
    congr, -- two goals: | a and | b
    skip,  -- | b
    rw h₁, -- | c
  },       -- | a + c
  --At this point, to get back to this position, without zoom, we would need to close
  --this conv block, and open a new one.
  rw h₂,   -- | a + d
},         -- goals accomplished
end

example : 1 + (5 * 8) - (3 * 14) + (4 * 99 - 45) - 350 = 1 :=
begin
  have h₁ : 5 * 8 = 40, from dec_trivial,
  have h₂ : 3 * 14 = 42, from dec_trivial,
  have h₃ : 4 * 99 - 45 = 351, from dec_trivial,
  conv {
    to_lhs,    -- conv to the left hand side
    conv {     -- zooming in to rewrite 4 * 99 - 45 to 351
      congr, congr, skip,
      rw h₃,
    },         -- returning to the left hand side, note we don't need to open a new conv block
    conv {     -- zooming in to rewrite 3 * 14 to 42
      congr, congr, congr, skip,
      rw h₂,
    },         -- returning to the left hand side
    conv {     -- zooming in to rewrite 5 * 8 to 40
      congr, congr, congr, congr, skip,
      rw h₁,
    },         -- returning to the left hand side
  },
end


/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import data.vector
import data.list.nodup
import data.list.of_fn
import control.applicative
/-!
# Additional theorems and definitions about the `vector` type

This file introduces the infix notation `::ᵥ` for `vector.cons`.
-/
universes u
variables {n : ℕ}

namespace vector
variables {α : Type*}

infixr `::ᵥ`:67  := vector.cons

attribute [simp] head_cons tail_cons

instance [inhabited α] : inhabited (vector α n) :=
⟨of_fn (λ _, default)⟩

theorem to_list_injective : function.injective (@to_list α n) :=
subtype.val_injective

/-- Two `v w : vector α n` are equal iff they are equal at every single index. -/
@[ext] theorem ext : ∀ {v w : vector α n}
  (h : ∀ m : fin n, vector.nth v m = vector.nth w m), v = w
| ⟨v, hv⟩ ⟨w, hw⟩ h := subtype.eq (list.ext_le (by rw [hv, hw])
  (λ m hm hn, h ⟨m, hv ▸ hm⟩))

/-- The empty `vector` is a `subsingleton`. -/
instance zero_subsingleton : subsingleton (vector α 0) :=
⟨λ _ _, vector.ext (λ m, fin.elim0 m)⟩

@[simp] theorem cons_val (a : α) : ∀ (v : vector α n), (a ::ᵥ v).val = a :: v.val
| ⟨_, _⟩ := rfl

@[simp] theorem cons_head (a : α) : ∀ (v : vector α n), (a ::ᵥ v).head = a
| ⟨_, _⟩ := rfl

@[simp] theorem cons_tail (a : α) : ∀ (v : vector α n), (a ::ᵥ v).tail = v
| ⟨_, _⟩ := rfl

@[simp] theorem to_list_of_fn : ∀ {n} (f : fin n → α), to_list (of_fn f) = list.of_fn f
| 0     f := rfl
| (n+1) f := by rw [of_fn, list.of_fn_succ, to_list_cons, to_list_of_fn]

@[simp] theorem mk_to_list :
  ∀ (v : vector α n) h, (⟨to_list v, h⟩ : vector α n) = v
| ⟨l, h₁⟩ h₂ := rfl

@[simp]
lemma length_coe (v : vector α n) :
  ((coe : { l : list α // l.length = n } → list α) v).length = n :=
v.2

@[simp] lemma to_list_map {β : Type*} (v : vector α n) (f : α → β) : (v.map f).to_list =
  v.to_list.map f := by cases v; refl

theorem nth_eq_nth_le : ∀ (v : vector α n) (i),
  nth v i = v.to_list.nth_le i.1 (by rw to_list_length; exact i.2)
| ⟨l, h⟩ i := rfl

@[simp]
lemma nth_repeat (a : α) (i : fin n) :
  (vector.repeat a n).nth i = a :=
by apply list.nth_le_repeat

@[simp] lemma nth_map {β : Type*} (v : vector α n) (f : α → β) (i : fin n) :
  (v.map f).nth i = f (v.nth i) :=
by simp [nth_eq_nth_le]

@[simp] theorem nth_of_fn {n} (f : fin n → α) (i) : nth (of_fn f) i = f i :=
by rw [nth_eq_nth_le, ← list.nth_le_of_fn f];
   congr; apply to_list_of_fn

@[simp] theorem of_fn_nth (v : vector α n) : of_fn (nth v) = v :=
begin
  rcases v with ⟨l, rfl⟩,
  apply to_list_injective,
  change nth ⟨l, eq.refl _⟩ with λ i, nth ⟨l, rfl⟩ i,
  simpa only [to_list_of_fn] using list.of_fn_nth_le _
end

/-- The natural equivalence between length-`n` vectors and functions from `fin n`. -/
def _root_.equiv.vector_equiv_fin (α : Type*) (n : ℕ) : vector α n ≃ (fin n → α) :=
⟨vector.nth, vector.of_fn, vector.of_fn_nth, λ f, funext $ vector.nth_of_fn f⟩

theorem nth_tail (x : vector α n) (i) :
  x.tail.nth i = x.nth ⟨i.1 + 1, lt_tsub_iff_right.mp i.2⟩ :=
by { rcases x with ⟨_|_, h⟩; refl, }

@[simp]
theorem nth_tail_succ : ∀ (v : vector α n.succ) (i : fin n),
  nth (tail v) i = nth v i.succ
| ⟨a::l, e⟩ ⟨i, h⟩ := by simp [nth_eq_nth_le]; refl

@[simp] theorem tail_val : ∀ (v : vector α n.succ), v.tail.val = v.val.tail
| ⟨a::l, e⟩ := rfl

/-- The `tail` of a `nil` vector is `nil`. -/
@[simp] lemma tail_nil : (@nil α).tail = nil := rfl

/-- The `tail` of a vector made up of one element is `nil`. -/
@[simp] lemma singleton_tail (v : vector α 1) : v.tail = vector.nil :=
by simp only [←cons_head_tail, eq_iff_true_of_subsingleton]

@[simp] theorem tail_of_fn {n : ℕ} (f : fin n.succ → α) :
  tail (of_fn f) = of_fn (λ i, f i.succ) :=
(of_fn_nth _).symm.trans $ by { congr, funext i, cases i, simp, }

/-- The list that makes up a `vector` made up of a single element,
retrieved via `to_list`, is equal to the list of that single element. -/
@[simp] lemma to_list_singleton (v : vector α 1) : v.to_list = [v.head] :=
begin
  rw ←v.cons_head_tail,
  simp only [to_list_cons, to_list_nil, cons_head, eq_self_iff_true,
             and_self, singleton_tail]
end

/-- Mapping under `id` does not change a vector. -/
@[simp] lemma map_id {n : ℕ} (v : vector α n) : vector.map id v = v :=
  vector.eq _ _ (by simp only [list.map_id, vector.to_list_map])

lemma mem_iff_nth {a : α} {v : vector α n} : a ∈ v.to_list ↔ ∃ i, v.nth i = a :=
by simp only [list.mem_iff_nth_le, fin.exists_iff, vector.nth_eq_nth_le];
  exact ⟨λ ⟨i, hi, h⟩, ⟨i, by rwa to_list_length at hi, h⟩,
    λ ⟨i, hi, h⟩, ⟨i, by rwa to_list_length, h⟩⟩

lemma nodup_iff_nth_inj {v : vector α n} : v.to_list.nodup ↔ function.injective v.nth :=
begin
  cases v with l hl,
  subst hl,
  simp only [list.nodup_iff_nth_le_inj],
  split,
  { intros h i j hij,
    cases i, cases j, ext, apply h, simpa },
  { intros h i j hi hj hij,
    have := @h ⟨i, hi⟩ ⟨j, hj⟩, simp [nth_eq_nth_le] at *, tauto }
end

@[simp] lemma nth_mem (i : fin n) (v : vector α n) : v.nth i ∈ v.to_list :=
by rw [nth_eq_nth_le]; exact list.nth_le_mem _ _ _

theorem head'_to_list : ∀ (v : vector α n.succ),
  (to_list v).head' = some (head v)
| ⟨a::l, e⟩ := rfl

/-- Reverse a vector. -/
def reverse (v : vector α n) : vector α n :=
⟨v.to_list.reverse, by simp⟩

/-- The `list` of a vector after a `reverse`, retrieved by `to_list` is equal
to the `list.reverse` after retrieving a vector's `to_list`. -/
lemma to_list_reverse {v : vector α n} : v.reverse.to_list = v.to_list.reverse := rfl

@[simp]
lemma reverse_reverse {v : vector α n} : v.reverse.reverse = v :=
by { cases v, simp [vector.reverse], }

@[simp] theorem nth_zero : ∀ (v : vector α n.succ), nth v 0 = head v
| ⟨a::l, e⟩ := rfl

@[simp] theorem head_of_fn
  {n : ℕ} (f : fin n.succ → α) : head (of_fn f) = f 0 :=
by rw [← nth_zero, nth_of_fn]

@[simp] theorem nth_cons_zero
  (a : α) (v : vector α n) : nth (a ::ᵥ v) 0 = a :=
by simp [nth_zero]

/-- Accessing the `nth` element of a vector made up
of one element `x : α` is `x` itself. -/
@[simp] lemma nth_cons_nil {ix : fin 1}
  (x : α) : nth (x ::ᵥ nil) ix = x :=
by convert nth_cons_zero x nil

@[simp] theorem nth_cons_succ
  (a : α) (v : vector α n) (i : fin n) : nth (a ::ᵥ v) i.succ = nth v i :=
by rw [← nth_tail_succ, tail_cons]

/-- The last element of a `vector`, given that the vector is at least one element. -/
def last (v : vector α (n + 1)) : α := v.nth (fin.last n)

/-- The last element of a `vector`, given that the vector is at least one element. -/
lemma last_def {v : vector α (n + 1)} : v.last = v.nth (fin.last n) := rfl

/-- The `last` element of a vector is the `head` of the `reverse` vector. -/
lemma reverse_nth_zero {v : vector α (n + 1)} : v.reverse.head = v.last :=
begin
  have : 0 = v.to_list.length - 1 - n,
  { simp only [nat.add_succ_sub_one, add_zero, to_list_length, tsub_self,
               list.length_reverse] },
  rw [←nth_zero, last_def, nth_eq_nth_le, nth_eq_nth_le],
  simp_rw [to_list_reverse, fin.val_eq_coe, fin.coe_last, fin.coe_zero, this],
  rw list.nth_le_reverse,
end

section scan

variables {β : Type*}
variables (f : β → α → β) (b : β)
variables (v : vector α n)

/--
Construct a `vector β (n + 1)` from a `vector α n` by scanning `f : β → α → β`
from the "left", that is, from 0 to `fin.last n`, using `b : β` as the starting value.
-/
def scanl : vector β (n + 1) :=
⟨list.scanl f b v.to_list, by rw [list.length_scanl, to_list_length]⟩

/-- Providing an empty vector to `scanl` gives the starting value `b : β`. -/
@[simp] lemma scanl_nil : scanl f b nil = b ::ᵥ nil := rfl

/--
The recursive step of `scanl` splits a vector `x ::ᵥ v : vector α (n + 1)`
into the provided starting value `b : β` and the recursed `scanl`
`f b x : β` as the starting value.

This lemma is the `cons` version of `scanl_nth`.
-/
@[simp] lemma scanl_cons (x : α) : scanl f b (x ::ᵥ v) = b ::ᵥ scanl f (f b x) v :=
by simpa only [scanl, to_list_cons]

/--
The underlying `list` of a `vector` after a `scanl` is the `list.scanl`
of the underlying `list` of the original `vector`.
-/
@[simp] lemma scanl_val : ∀ {v : vector α n}, (scanl f b v).val = list.scanl f b v.val
| ⟨l, hl⟩ := rfl

/--
The `to_list` of a `vector` after a `scanl` is the `list.scanl`
of the `to_list` of the original `vector`.
-/
@[simp] lemma to_list_scanl : (scanl f b v).to_list = list.scanl f b v.to_list := rfl

/--
The recursive step of `scanl` splits a vector made up of a single element
`x ::ᵥ nil : vector α 1` into a `vector` of the provided starting value `b : β`
and the mapped `f b x : β` as the last value.
-/
@[simp] lemma scanl_singleton (v : vector α 1) : scanl f b v = b ::ᵥ f b v.head ::ᵥ nil :=
begin
  rw [←cons_head_tail v],
  simp only [scanl_cons, scanl_nil, cons_head, singleton_tail]
end

/--
The first element of `scanl` of a vector `v : vector α n`,
retrieved via `head`, is the starting value `b : β`.
-/
@[simp] lemma scanl_head : (scanl f b v).head = b :=
begin
  cases n,
  { have : v = nil := by simp only [eq_iff_true_of_subsingleton],
    simp only [this, scanl_nil, cons_head] },
  { rw ←cons_head_tail v,
    simp only [←nth_zero, nth_eq_nth_le, to_list_scanl,
                to_list_cons, list.scanl, fin.val_zero', list.nth_le] }
end

/--
For an index `i : fin n`, the `nth` element of `scanl` of a
vector `v : vector α n` at `i.succ`, is equal to the application
function `f : β → α → β` of the `i.cast_succ` element of
`scanl f b v` and `nth v i`.

This lemma is the `nth` version of `scanl_cons`.
-/
@[simp] lemma scanl_nth (i : fin n) :
  (scanl f b v).nth i.succ = f ((scanl f b v).nth i.cast_succ) (v.nth i) :=
begin
  cases n,
  { exact fin_zero_elim i },
  induction n with n hn generalizing b,
  { have i0 : i = 0 := by simp only [eq_iff_true_of_subsingleton],
    simpa only [scanl_singleton, i0, nth_zero] },
  { rw [←cons_head_tail v, scanl_cons, nth_cons_succ],
    refine fin.cases _ _ i,
    { simp only [nth_zero, scanl_head, fin.cast_succ_zero, cons_head] },
    { intro i',
      simp only [hn, fin.cast_succ_fin_succ, nth_cons_succ] } }
end

end scan

/-- Monadic analog of `vector.of_fn`.
Given a monadic function on `fin n`, return a `vector α n` inside the monad. -/
def m_of_fn {m} [monad m] {α : Type u} : ∀ {n}, (fin n → m α) → m (vector α n)
| 0     f := pure nil
| (n+1) f := do a ← f 0, v ← m_of_fn (λi, f i.succ), pure (a ::ᵥ v)

theorem m_of_fn_pure {m} [monad m] [is_lawful_monad m] {α} :
  ∀ {n} (f : fin n → α), @m_of_fn m _ _ _ (λ i, pure (f i)) = pure (of_fn f)
| 0     f := rfl
| (n+1) f := by simp [m_of_fn, @m_of_fn_pure n, of_fn]

/-- Apply a monadic function to each component of a vector,
returning a vector inside the monad. -/
def mmap {m} [monad m] {α} {β : Type u} (f : α → m β) :
  ∀ {n}, vector α n → m (vector β n)
| 0 xs := pure nil
| (n+1) xs := do h' ← f xs.head, t' ← @mmap n xs.tail, pure (h' ::ᵥ t')

@[simp] theorem mmap_nil {m} [monad m] {α β} (f : α → m β) :
  mmap f nil = pure nil := rfl

@[simp] theorem mmap_cons {m} [monad m] {α β} (f : α → m β) (a) :
  ∀ {n} (v : vector α n), mmap f (a ::ᵥ v) =
  do h' ← f a, t' ← mmap f v, pure (h' ::ᵥ t')
| _ ⟨l, rfl⟩ := rfl

/-- Define `C v` by induction on `v : vector α n`.

This function has two arguments: `h_nil` handles the base case on `C nil`,
and `h_cons` defines the inductive step using `∀ x : α, C w → C (x ::ᵥ w)`. -/
@[elab_as_eliminator] def induction_on {C : Π {n : ℕ}, vector α n → Sort*}
  (v : vector α n)
  (h_nil : C nil)
  (h_cons : ∀ {n : ℕ} {x : α} {w : vector α n}, C w → C (x ::ᵥ w)) :
    C v :=
begin
  induction n with n ih generalizing v,
  { rcases v with ⟨_|⟨-,-⟩,-|-⟩,
    exact h_nil, },
  { rcases v with ⟨_|⟨a,v⟩,_⟩,
    cases v_property,
    apply @h_cons n _ ⟨v, (add_left_inj 1).mp v_property⟩,
    apply ih, }
end

variables {β γ : Type*}

/-- Define `C v w` by induction on a pair of vectors `v : vector α n` and `w : vector β n`. -/
@[elab_as_eliminator] def induction_on₂ {C : Π {n}, vector α n → vector β n → Sort*}
  (v : vector α n) (w : vector β n)
  (h_nil : C nil nil)
  (h_cons : ∀ {n a b} {x : vector α n} {y}, C x y → C (a ::ᵥ x) (b ::ᵥ y)) : C v w :=
begin
  induction n with n ih generalizing v w,
  { rcases v with ⟨_|⟨-,-⟩,-|-⟩, rcases w with ⟨_|⟨-,-⟩,-|-⟩,
    exact h_nil, },
  { rcases v with ⟨_|⟨a,v⟩,_⟩,
    cases v_property,
    rcases w with ⟨_|⟨b,w⟩,_⟩,
    cases w_property,
    apply @h_cons n _ _ ⟨v, (add_left_inj 1).mp v_property⟩ ⟨w, (add_left_inj 1).mp w_property⟩,
    apply ih, }
end

/-- Define `C u v w` by induction on a triplet of vectors
`u : vector α n`, `v : vector β n`, and `w : vector γ b`. -/
@[elab_as_eliminator] def induction_on₃ {C : Π {n}, vector α n → vector β n → vector γ n → Sort*}
  (u : vector α n) (v : vector β n) (w : vector γ n)
  (h_nil : C nil nil nil)
  (h_cons : ∀ {n a b c} {x : vector α n} {y z}, C x y z → C (a ::ᵥ x) (b ::ᵥ y) (c ::ᵥ z)) :
  C u v w :=
begin
  induction n with n ih generalizing u v w,
  { rcases u with ⟨_|⟨-,-⟩,-|-⟩, rcases v with ⟨_|⟨-,-⟩,-|-⟩, rcases w with ⟨_|⟨-,-⟩,-|-⟩,
    exact h_nil, },
  { rcases u with ⟨_|⟨a,u⟩,_⟩,
    cases u_property,
    rcases v with ⟨_|⟨b,v⟩,_⟩,
    cases v_property,
    rcases w with ⟨_|⟨c,w⟩,_⟩,
    cases w_property,
    apply @h_cons n _ _ _ ⟨u, (add_left_inj 1).mp u_property⟩ ⟨v, (add_left_inj 1).mp v_property⟩
      ⟨w, (add_left_inj 1).mp w_property⟩,
    apply ih, }
end

/-- Cast a vector to an array. -/
def to_array : vector α n → array n α
| ⟨xs, h⟩ := cast (by rw h) xs.to_array

section insert_nth
variable {a : α}

/-- `v.insert_nth a i` inserts `a` into the vector `v` at position `i`
(and shifting later components to the right). -/
def insert_nth (a : α) (i : fin (n+1)) (v : vector α n) : vector α (n+1) :=
⟨v.1.insert_nth i a,
  begin
    rw [list.length_insert_nth, v.2],
    rw [v.2, ← nat.succ_le_succ_iff],
    exact i.2
  end⟩

lemma insert_nth_val {i : fin (n+1)} {v : vector α n} :
  (v.insert_nth a i).val = v.val.insert_nth i.1 a :=
rfl

@[simp] lemma remove_nth_val {i : fin n} :
  ∀{v : vector α n}, (remove_nth i v).val = v.val.remove_nth i
| ⟨l, hl⟩ := rfl

lemma remove_nth_insert_nth {v : vector α n} {i : fin (n+1)} :
  remove_nth i (insert_nth a i v) = v :=
subtype.eq $ list.remove_nth_insert_nth i.1 v.1

lemma remove_nth_insert_nth' {v : vector α (n+1)} :
  ∀{i : fin (n+1)} {j : fin (n+2)},
    remove_nth (j.succ_above i) (insert_nth a j v) = insert_nth a (i.pred_above j) (remove_nth i v)
| ⟨i, hi⟩ ⟨j, hj⟩ :=
  begin
    dsimp [insert_nth, remove_nth, fin.succ_above, fin.pred_above],
    simp only [subtype.mk_eq_mk],
    split_ifs,
    { convert (list.insert_nth_remove_nth_of_ge i (j-1) _ _ _).symm,
      { convert (nat.succ_pred_eq_of_pos _).symm, exact lt_of_le_of_lt (zero_le _) h, },
      { apply remove_nth_val, },
      { convert hi, exact v.2, },
      { exact nat.le_pred_of_lt h, }, },
    { convert (list.insert_nth_remove_nth_of_le i j _ _ _).symm,
      { apply remove_nth_val, },
      { convert hi, exact v.2, },
      { simpa using h, }, }
  end

lemma insert_nth_comm (a b : α) (i j : fin (n+1)) (h : i ≤ j) :
  ∀(v : vector α n),
    (v.insert_nth a i).insert_nth b j.succ = (v.insert_nth b j).insert_nth a i.cast_succ
| ⟨l, hl⟩ :=
  begin
    refine subtype.eq _,
    simp only [insert_nth_val, fin.coe_succ, fin.cast_succ, fin.val_eq_coe, fin.coe_cast_add],
    apply list.insert_nth_comm,
    { assumption },
    { rw hl, exact nat.le_of_succ_le_succ j.2 }
  end

end insert_nth

section update_nth

/-- `update_nth v n a` replaces the `n`th element of `v` with `a` -/
def update_nth (v : vector α n) (i : fin n) (a : α) : vector α n :=
⟨v.1.update_nth i.1 a, by rw [list.update_nth_length, v.2]⟩

@[simp] lemma to_list_update_nth (v : vector α n) (i : fin n) (a : α) :
  (v.update_nth i a).to_list = v.to_list.update_nth i a :=
rfl

@[simp] lemma nth_update_nth_same (v : vector α n) (i : fin n) (a : α) :
  (v.update_nth i a).nth i = a :=
by cases v; cases i; simp [vector.update_nth, vector.nth_eq_nth_le]

lemma nth_update_nth_of_ne {v : vector α n} {i j : fin n} (h : i ≠ j) (a : α) :
  (v.update_nth i a).nth j = v.nth j :=
by cases v; cases i; cases j; simp [vector.update_nth, vector.nth_eq_nth_le,
  list.nth_le_update_nth_of_ne (fin.vne_of_ne h)]

lemma nth_update_nth_eq_if {v : vector α n} {i j : fin n} (a : α) :
  (v.update_nth i a).nth j = if i = j then a else v.nth j :=
by split_ifs; try {simp *}; try {rw nth_update_nth_of_ne}; assumption

@[to_additive]
lemma prod_update_nth [monoid α] (v : vector α n) (i : fin n) (a : α) :
  (v.update_nth i a).to_list.prod =
    (v.take i).to_list.prod * a * (v.drop (i + 1)).to_list.prod :=
begin
  refine (list.prod_update_nth v.to_list i a).trans _,
  have : ↑i < v.to_list.length := lt_of_lt_of_le i.2 (le_of_eq v.2.symm),
  simp * at *
end

@[to_additive]
lemma prod_update_nth' [comm_group α] (v : vector α n) (i : fin n) (a : α) :
  (v.update_nth i a).to_list.prod =
    v.to_list.prod * (v.nth i)⁻¹ * a :=
begin
  refine (list.prod_update_nth' v.to_list i a).trans _,
  have : ↑i < v.to_list.length := lt_of_lt_of_le i.2 (le_of_eq v.2.symm),
  simp [this, nth_eq_nth_le, mul_assoc],
end

end update_nth

end vector

namespace vector

section traverse

variables {F G : Type u → Type u}
variables [applicative F] [applicative G]

open applicative functor
open list (cons) nat

private def traverse_aux {α β : Type u} (f : α → F β) :
  Π (x : list α), F (vector β x.length)
| []      := pure vector.nil
| (x::xs) := vector.cons <$> f x <*> traverse_aux xs

/-- Apply an applicative function to each component of a vector. -/
protected def traverse {α β : Type u} (f : α → F β) : vector α n → F (vector β n)
| ⟨v, Hv⟩ := cast (by rw Hv) $ traverse_aux f v

section
variables {α β : Type u}

@[simp] protected lemma traverse_def
  (f : α → F β) (x : α) : ∀ (xs : vector α n),
  (x ::ᵥ xs).traverse f = cons <$> f x <*> xs.traverse f :=
by rintro ⟨xs, rfl⟩; refl

protected lemma id_traverse : ∀ (x : vector α n), x.traverse id.mk = x :=
begin
  rintro ⟨x, rfl⟩, dsimp [vector.traverse, cast],
  induction x with x xs IH, {refl},
  simp! [IH], refl
end

end

open function

variables [is_lawful_applicative F] [is_lawful_applicative G]
variables {α β γ : Type u}

-- We need to turn off the linter here as
-- the `is_lawful_traversable` instance below expects a particular signature.
@[nolint unused_arguments]
protected lemma comp_traverse (f : β → F γ) (g : α → G β) : ∀ (x : vector α n),
  vector.traverse (comp.mk ∘ functor.map f ∘ g) x =
  comp.mk (vector.traverse f <$> vector.traverse g x) :=
by rintro ⟨x, rfl⟩; dsimp [vector.traverse, cast];
   induction x with x xs; simp! [cast, *] with functor_norm;
   [refl, simp [(∘)]]

protected lemma traverse_eq_map_id {α β} (f : α → β) : ∀ (x : vector α n),
  x.traverse (id.mk ∘ f) = id.mk (map f x) :=
by rintro ⟨x, rfl⟩; simp!;
   induction x; simp! * with functor_norm; refl

variable (η : applicative_transformation F G)

protected lemma naturality {α β : Type*}
  (f : α → F β) : ∀ (x : vector α n),
  η (x.traverse f) = x.traverse (@η _ ∘ f) :=
by rintro ⟨x, rfl⟩; simp! [cast];
   induction x with x xs IH; simp! * with functor_norm

end traverse

instance : traversable.{u} (flip vector n) :=
{ traverse := @vector.traverse n,
  map := λ α β, @vector.map.{u u} α β n }

instance : is_lawful_traversable.{u} (flip vector n) :=
{ id_traverse := @vector.id_traverse n,
  comp_traverse := @vector.comp_traverse n,
  traverse_eq_map_id := @vector.traverse_eq_map_id n,
  naturality := @vector.naturality n,
  id_map := by intros; cases x; simp! [(<$>)],
  comp_map := by intros; cases x; simp! [(<$>)] }

end vector


/-
Copyright (c) 2020 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov
-/
import analysis.special_functions.pow

/-!
# Convergence of `p`-series

In this file we prove that the series `∑' k in ℕ, 1 / k ^ p` converges if and only if `p > 1`.
The proof is based on the
[Cauchy condensation test](https://en.wikipedia.org/wiki/Cauchy_condensation_test): `∑ k, f k`
converges if and only if so does `∑ k, 2 ^ k f (2 ^ k)`. We prove this test in
`nnreal.summable_condensed_iff` and `summable_condensed_iff_of_nonneg`, then use it to prove
`summable_one_div_rpow`. After this transformation, a `p`-series turns into a geometric series.

## TODO

It should be easy to generalize arguments to Schlömilch's generalization of the Cauchy condensation
test once we need it.

## Tags

p-series, Cauchy condensation test
-/

open filter
open_locale big_operators ennreal nnreal topological_space

/-!
### Cauchy condensation test

In this section we prove the Cauchy condensation test: for `f : ℕ → ℝ≥0` or `f : ℕ → ℝ`,
`∑ k, f k` converges if and only if so does `∑ k, 2 ^ k f (2 ^ k)`. Instead of giving a monolithic
proof, we split it into a series of lemmas with explicit estimates of partial sums of each series in
terms of the partial sums of the other series.
-/

namespace finset

variables {M : Type*} [ordered_add_comm_monoid M] {f : ℕ → M}

lemma le_sum_condensed' (hf : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) (n : ℕ) :
  (∑ k in Ico 1 (2 ^ n), f k) ≤ ∑ k in range n, (2 ^ k) • f (2 ^ k) :=
begin
  induction n with n ihn, { simp },
  suffices : (∑ k in Ico (2 ^ n) (2 ^ (n + 1)), f k) ≤ (2 ^ n) • f (2 ^ n),
  { rw [sum_range_succ, ← sum_Ico_consecutive],
    exact add_le_add ihn this,
    exacts [n.one_le_two_pow, nat.pow_le_pow_of_le_right zero_lt_two n.le_succ] },
  have : ∀ k ∈ Ico (2 ^ n) (2 ^ (n + 1)), f k ≤ f (2 ^ n) :=
    λ k hk, hf (pow_pos zero_lt_two _) (Ico.mem.mp hk).1,
  convert sum_le_sum this,
  simp [pow_succ, two_mul]
end

lemma le_sum_condensed (hf : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) (n : ℕ) :
  (∑ k in range (2 ^ n), f k) ≤ f 0 + ∑ k in range n, (2 ^ k) • f (2 ^ k) :=
begin
  convert add_le_add_left (le_sum_condensed' hf n) (f 0),
  rw [← sum_range_add_sum_Ico _ n.one_le_two_pow, sum_range_succ, sum_range_zero, zero_add]
end

lemma sum_condensed_le' (hf : ∀ ⦃m n⦄, 1 < m → m ≤ n → f n ≤ f m) (n : ℕ) :
  (∑ k in range n, (2 ^ k) • f (2 ^ (k + 1))) ≤ ∑ k in Ico 2 (2 ^ n + 1), f k :=
begin
  induction n with n ihn, { simp },
  suffices : (2 ^ n) • f (2 ^ (n + 1)) ≤ ∑ k in Ico (2 ^ n + 1) (2 ^ (n + 1) + 1), f k,
  { rw [sum_range_succ, ← sum_Ico_consecutive],
    exact add_le_add ihn this,
    exacts [add_le_add_right n.one_le_two_pow _,
      add_le_add_right (nat.pow_le_pow_of_le_right zero_lt_two n.le_succ) _] },
  have : ∀ k ∈ Ico (2 ^ n + 1) (2 ^ (n + 1) + 1), f (2 ^ (n + 1)) ≤ f k :=
    λ k hk, hf (n.one_le_two_pow.trans_lt $ (nat.lt_succ_of_le le_rfl).trans_le (Ico.mem.mp hk).1)
      (nat.le_of_lt_succ $ (Ico.mem.mp hk).2),
  convert sum_le_sum this,
  simp [pow_succ, two_mul]
end

lemma sum_condensed_le (hf : ∀ ⦃m n⦄, 1 < m → m ≤ n → f n ≤ f m) (n : ℕ) :
  (∑ k in range (n + 1), (2 ^ k) • f (2 ^ k)) ≤ f 1 + 2 • ∑ k in Ico 2 (2 ^ n + 1), f k :=
begin
  convert add_le_add_left (nsmul_le_nsmul_of_le_right (sum_condensed_le' hf n) 2) (f 1),
  simp [sum_range_succ', add_comm, pow_succ, mul_nsmul, sum_nsmul]
end

end finset

namespace ennreal

variable {f : ℕ → ℝ≥0∞}

lemma le_tsum_condensed (hf : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) :
  ∑' k, f k ≤ f 0 + ∑' k : ℕ, (2 ^ k) * f (2 ^ k) :=
begin
  rw [ennreal.tsum_eq_supr_nat' (nat.tendsto_pow_at_top_at_top_of_one_lt _root_.one_lt_two)],
  refine supr_le (λ n, (finset.le_sum_condensed hf n).trans (add_le_add_left _ _)),
  simp only [nsmul_eq_mul, nat.cast_pow, nat.cast_two],
  apply ennreal.sum_le_tsum
end

lemma tsum_condensed_le (hf : ∀ ⦃m n⦄, 1 < m → m ≤ n → f n ≤ f m) :
  ∑' k : ℕ, (2 ^ k * f (2 ^ k)) ≤ f 1 + 2 * ∑' k, f k :=
begin
  rw [ennreal.tsum_eq_supr_nat' (tendsto_at_top_mono nat.le_succ tendsto_id), two_mul, ← two_nsmul],
  refine supr_le (λ n, le_trans _ (add_le_add_left (nsmul_le_nsmul_of_le_right
    (ennreal.sum_le_tsum $ finset.Ico 2 (2^n + 1)) _) _)),
  simpa using finset.sum_condensed_le hf n
end

end ennreal

namespace nnreal

/-- Cauchy condensation test for a series of `nnreal` version. -/
lemma summable_condensed_iff {f : ℕ → ℝ≥0} (hf : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) :
  summable (λ k : ℕ, (2 ^ k) * f (2 ^ k)) ↔ summable f :=
begin
  simp only [← ennreal.tsum_coe_ne_top_iff_summable, ne.def, not_iff_not, ennreal.coe_mul,
    ennreal.coe_pow, ennreal.coe_two],
  split; intro h,
  { replace hf : ∀ m n, 1 < m → m ≤ n → (f n : ℝ≥0∞) ≤ f m :=
      λ m n hm hmn, ennreal.coe_le_coe.2 (hf (zero_lt_one.trans hm) hmn),
    simpa [h, ennreal.add_eq_top] using (ennreal.tsum_condensed_le hf) },
  { replace hf : ∀ m n, 0 < m → m ≤ n → (f n : ℝ≥0∞) ≤ f m :=
      λ m n hm hmn, ennreal.coe_le_coe.2 (hf hm hmn),
    simpa [h, ennreal.add_eq_top] using (ennreal.le_tsum_condensed hf) }
end

end nnreal

/-- Cauchy condensation test for series of nonnegative real numbers. -/
lemma summable_condensed_iff_of_nonneg {f : ℕ → ℝ} (h_nonneg : ∀ n, 0 ≤ f n)
  (h_mono : ∀ ⦃m n⦄, 0 < m → m ≤ n → f n ≤ f m) :
  summable (λ k : ℕ, (2 ^ k) * f (2 ^ k)) ↔ summable f :=
begin
  set g : ℕ → ℝ≥0 := λ n, ⟨f n, h_nonneg n⟩,
  have : f = λ n, g n := rfl,
  simp only [this],
  have : ∀ ⦃m n⦄, 0 < m → m ≤ n → g n ≤ g m := λ m n hm h, nnreal.coe_le_coe.2 (h_mono hm h),
  exact_mod_cast nnreal.summable_condensed_iff this
end

open real

/-!
### Convergence of the `p`-series

In this section we prove that for a real number `p`, the series `∑' n : ℕ, 1 / (n ^ p)` converges if
and only if `1 < p`. There are many different proofs of this fact. The proof in this file uses the
Cauchy condensation test we formalized above. This test implies that `∑ n, 1 / (n ^ p)` converges if
and only if `∑ n, 2 ^ n / ((2 ^ n) ^ p)` converges, and the latter series is a geometric series with
common ratio `2 ^ {1 - p}`. -/

/-- Test for congergence of the `p`-series: the real-valued series `∑' n : ℕ, (n ^ p)⁻¹` converges
if and only if `1 < p`. -/
@[simp] lemma real.summable_nat_rpow_inv {p : ℝ} : summable (λ n, (n ^ p)⁻¹ : ℕ → ℝ) ↔ 1 < p :=
begin
  cases le_or_lt 0 p with hp hp,
  /- Cauchy condensation test applies only to monotonically decreasing sequences, so we consider the
  cases `0 ≤ p` and `p < 0` separately. -/
  { rw ← summable_condensed_iff_of_nonneg,
    { simp_rw [nat.cast_pow, nat.cast_two, ← rpow_nat_cast, ← rpow_mul zero_lt_two.le, mul_comm _ p,
        rpow_mul zero_lt_two.le, rpow_nat_cast, ← inv_pow', ← mul_pow,
        summable_geometric_iff_norm_lt_1],
      nth_rewrite 0 [← rpow_one 2],
      rw [← division_def, ← rpow_sub zero_lt_two, norm_eq_abs,
        abs_of_pos (rpow_pos_of_pos zero_lt_two _), rpow_lt_one_iff zero_lt_two.le],
      norm_num },
    { intro n,
      exact inv_nonneg.2 (rpow_nonneg_of_nonneg n.cast_nonneg _) },
    { intros m n hm hmn,
      exact inv_le_inv_of_le (rpow_pos_of_pos (nat.cast_pos.2 hm) _)
         (rpow_le_rpow m.cast_nonneg (nat.cast_le.2 hmn) hp) } },
  /- If `p < 0`, then `1 / n ^ p` tends to infinity, thus the series diverges. -/
  { suffices : ¬summable (λ n, (n ^ p)⁻¹ : ℕ → ℝ),
    { have : ¬(1 < p) := λ hp₁, hp.not_le (zero_le_one.trans hp₁.le),
      simpa [this, -one_div] },
    { intro h,
      obtain ⟨k : ℕ, hk₁ : ((k ^ p)⁻¹ : ℝ) < 1, hk₀ : k ≠ 0⟩ :=
        ((h.tendsto_cofinite_zero.eventually (gt_mem_nhds zero_lt_one)).and
          (eventually_cofinite_ne 0)).exists,
      apply hk₀,
      rw [← pos_iff_ne_zero, ← @nat.cast_pos ℝ] at hk₀,
      simpa [inv_lt_one_iff_of_pos (rpow_pos_of_pos hk₀ _), one_lt_rpow_iff_of_pos hk₀, hp,
        hp.not_lt, hk₀] using hk₁ } }
end

/-- Test for congergence of the `p`-series: the real-valued series `∑' n : ℕ, 1 / n ^ p` converges
if and only if `1 < p`. -/
lemma real.summable_one_div_nat_rpow {p : ℝ} : summable (λ n, 1 / n ^ p : ℕ → ℝ) ↔ 1 < p :=
by simp

/-- Test for congergence of the `p`-series: the real-valued series `∑' n : ℕ, (n ^ p)⁻¹` converges
if and only if `1 < p`. -/
@[simp] lemma real.summable_nat_pow_inv {p : ℕ} : summable (λ n, (n ^ p)⁻¹ : ℕ → ℝ) ↔ 1 < p :=
by simp only [← rpow_nat_cast, real.summable_nat_rpow_inv, nat.one_lt_cast]

/-- Test for congergence of the `p`-series: the real-valued series `∑' n : ℕ, 1 / n ^ p` converges
if and only if `1 < p`. -/
lemma real.summable_one_div_nat_pow {p : ℕ} : summable (λ n, 1 / n ^ p : ℕ → ℝ) ↔ 1 < p :=
by simp

/-- Harmonic series is not unconditionally summable. -/
lemma real.not_summable_nat_cast_inv : ¬summable (λ n, n⁻¹ : ℕ → ℝ) :=
have ¬summable (λ n, (n^1)⁻¹ : ℕ → ℝ), from mt real.summable_nat_pow_inv.1 (lt_irrefl 1),
by simpa

/-- Harmonic series is not unconditionally summable. -/
lemma real.not_summable_one_div_nat_cast : ¬summable (λ n, 1 / n : ℕ → ℝ) :=
by simpa only [inv_eq_one_div] using real.not_summable_nat_cast_inv

/-- Harmonic series diverges. -/
lemma real.tendsto_sum_range_one_div_nat_succ_at_top :
  tendsto (λ n, ∑ i in finset.range n, (1 / (i + 1) : ℝ)) at_top at_top :=
begin
  rw ← not_summable_iff_tendsto_nat_at_top_of_nonneg,
  { exact_mod_cast mt (summable_nat_add_iff 1).1 real.not_summable_one_div_nat_cast },
  { exact λ i, div_nonneg zero_le_one i.cast_add_one_pos.le }
end

@[simp] lemma nnreal.summable_one_rpow_inv {p : ℝ} : summable (λ n, (n ^ p)⁻¹ : ℕ → ℝ≥0) ↔ 1 < p :=
by simp [← nnreal.summable_coe]

lemma nnreal.summable_one_div_rpow {p : ℝ} : summable (λ n, 1 / n ^ p : ℕ → ℝ≥0) ↔ 1 < p :=
by simp


import number_theory.bernoulli

--OUTPUT 1
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  induction n with d hd,
    norm_num,
    by_cases hd_eq_zero : d = 0,
      rw hd_eq_zero,
      norm_num,
    rw polynomial.pow_add,
    have h1 : (1 + x) ^ d = x ^ d + d * x ^ (d - 1), from by {
      rw [← polynomial.pow_add, polynomial.pow_one, mul_one, add_comm],
    },
    rw [← polynomial.pow_add, add_comm, polynomial.pow_one, mul_one, ← hd d hd_eq_zero, ← h1],
    rw polynomial.eval_add,
    rw polynomial.eval_sum,
    rw polynomial.eval_mul,
    rw polynomial.eval_C,
    rw polynomial.eval_pow,
    rw polynomial.eval_pow,
    rw polynomial.eval_C,
    norm_num,
    rw [add_assoc, ← mul_assoc, ← mul_assoc],
    rw ← C_mul_X,
    rw polynomial.eval_add,
    rw polynomial.eval_mul,
    rw polynomial.eval_C,
    rw polynomial.eval_X,
    rw polynomial.eval_mul,
    rw polynomial.eval_C,
    rw polynomial.eval_pow,
    rw polynomial.eval_X,
    rw polynomial.eval_C,
    rw polynomial.eval_pow,
    rw polynomial.eval_X,
    rw polynomial.eval_C,
    rw polynomial.eval_pow,
    rw polynomial.eval_X,
    rw polynomial.eval_C,
    rw polynomial.eval_add,
    rw polynomial.eval_mul,
    rw polynomial.eval_C,
    rw polynomial.eval_X,
    rw polynomial.eval_C,
    rw polynomial.eval_mul,
    rw polynomial.eval_C,
    rw polynomial.eval_pow,
    rw polynomial.eval_X,
    rw polynomial.eval_C,
    rw polynomial.eval_pow,
    rw polynomial.eval_X,
    rw polynomial.eval_C,
    rw polynomial.eval_pow,
    rw polynomial.eval_X,
    rw polynomial.eval_C,
    by ring,
end

--OUTPUT 2
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  apply nat.strong_induction_on n,
  assume (d : ℕ) (h : ∀ m : ℕ, m < d → (polynomial.bernoulli m).eval (1 + x) = (polynomial.bernoulli m).eval x + m * x^(m - 1)),
  have h1 : (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + d * x^(d - 1), from begin
    have h2 : (d + 1) * (1 + x)^d - (d + 1) * x^d = ∑ k in finset.range (d + 1), (d + 1) * (finset.range d).sum (λ i, ((d + 1 - i) * (1 + x)^(i - 1) * x^(d - i)) / (i : ℚ)), from begin
      rw finset.sum_mul_distrib,
      rw finset.sum_range_succ,
      rw finset.sum_range_succ,
      rw finset.sum_range_succ,
      rw [← mul_one ((d + 1) : ℚ), ← mul_one ((d + 1) : ℚ), ← mul_one ((d + 1) : ℚ)],
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum,
      rw finset.mul_sum
end --Needs more than 2000 tokens!

--OUTPUT 3
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  induction n with d hd,
  {
    show (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval x + 0 * x^(0 - 1),
    calc (polynomial.bernoulli 0).eval (1 + x) = 1 : by apply polynomial.bernoulli_zero_eval
    ... = (polynomial.bernoulli 0).eval x + 0 * x^(0 - 1) : by rw [polynomial.bernoulli_zero_eval, mul_zero, add_zero, pow_zero],
  },
  {
    have h1 : (d + 1) * (1 + x)^d = (d + 1) * x^d + (d + 1) * x^(d - 1), from by {
      rw mul_comm (d + 1) ((1 + x)^d), rw pow_add, rw [mul_comm (d + 1) (1 + x), mul_one],
    },
    have h2 : (d + 1) * x^(d - 1) = (d + 1) * x^(d - 1) + (d + 1) * x^(d - 1), from by {
      rw add_zero,
    },
    rw ← add_assoc,
    have h3 : (d + 1) * x^(d - 1) = ∑ (k : ℕ) in finset.range (d + 1), (d + 1) * (d choose k) * x^(k - 1), from by {
      rw ← polynomial.eval_sum,
      rw ← polynomial.eval_mul,
      rw ← polynomial.eval_pow,
      rw ← polynomial.eval_mul,
      rw polynomial.eval_C,
      rw polynomial.eval_X,
      rw polynomial.eval_C,
      rw polynomial.eval_X,
      rw mul_comm,
      rw mul_comm (d + 1),
      rw polynomial.eval_X,
      rw polynomial.eval_C,
      rw finset.sum_const_mul,
      rw mul_comm (d + 1),
      rw ← polynomial.eval_mul,
      rw ← polynomial.eval_sum,
      rw polynomial.eval_bernoulli,
      rw finset.sum_const_mul,
    },
    rw h3,
    have h4 : (polynomial.bernoulli d).eval (1 + x) = (d + 1) * (1 + x)^d + ∑ (k : ℕ) in finset.range d, (d + 1) * (d choose k) * x^(k - 1), from by {
      rw ← polynomial.eval_sum,
      rw ← polynomial.eval_mul,
      rw ← polynomial.eval_pow,
      rw ← polynomial.eval_mul,
      rw polynomial.eval_C,
      rw polynomial.eval_X,
      rw polynomial.eval_C,
      rw polynomial.eval_X,
      rw mul_comm,
      rw mul_comm (d + 1),
      rw polynomial.eval_X,
      rw polynomial.eval_C,
      rw finset.sum_const_mul,
      rw mul_comm (d + 1),
      rw ← polynomial.eval_mul,
      rw ← polynomial.eval_sum,
      rw polynomial.eval_bernoulli,
      rw finset.sum_const_mul,
    },
    have h5 : (polynomial.bernoulli d).eval (1 + x) = (d + 1) * x^d + ∑ (k : ℕ) in finset.range d, (d + 1) * (d choose k) * x^(k - 1), from by {
      rw h4,
      rw h1,
      rw add_assoc,
      rw h2,
    },
    have h6 : (polynomial.bernoulli d).eval x + d * x^(d - 1) = (d + 1) * x^d + ∑ (k : ℕ) in finset.range d, (d + 1) * (d choose k) * x^(k - 1), from by {
      rw ← polynomial.eval_sum,
      rw ← polynomial.eval_mul,
      rw ← polynomial.eval_pow,
      rw ← polynomial.eval_mul,
      rw polynomial.eval_C,
      rw polynomial.eval_X,
      rw polynomial.eval_C,
      rw polynomial.eval_X,
      rw mul_comm,
      rw mul_comm (d + 1),
      rw polynomial.eval_X,
      rw polynomial.eval_C,
      rw finset.sum_const_mul,
      rw mul_comm (d + 1),
      rw ← polynomial.eval_mul,
      rw ← polynomial.eval_sum,
      rw polynomial.eval_bernoulli,
      rw finset.sum_const_mul,
    },
    have h7 : (polynomial.bernoulli d).eval x + d * x^(d - 1) = (polynomial.bernoulli d).eval (1 + x), from
    by {rw h5, rw ← add_assoc, rw ← add_assoc, rw ← add_assoc, rw ← add_assoc, rw ← add_assoc, rw ← add_assoc, rw ← add_assoc, rw add_right_neg, rw add_zero, rw add_zero, rw add_zero, rw add_zero, rw add_zero},
    have h8 : (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + d * x^(d - 1), from by {rw ← h7, assumption},
    have h9 : ∀ (k : ℕ), k < d → (polynomial.bernoulli k).eval (1 + x) = (polynomial.bernoulli k).eval x + k * x^(k - 1), from assume (k : ℕ) (hk : k < d), by {rw ← hd k hk,},
    have h10 : (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + d * x^(d - 1), from by {
      rw ← h8,
      apply h9,
      assumption,
    },
    have h11 : (polynomial.bernoulli d).eval (1 + x) = (polynomial.bernoulli d).eval x + d * x^(d - 1), from by {
      rw ← h10,
      assumption,
    },
    show (polynomial.bernoulli (d + 1)).eval (1 + x) = (polynomial.bernoulli (d + 1)).eval x + (d + 1) * x^(d), from by {
      rw ← h11,
      rw ← pow_add,
      rw ← mul_comm,
      rw ← mul_comm,
      rw ← pow_add,
      rw ← mul_comm,
      rw ← mul_comm,
    },
  },
end

--OUTPUT 4
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  induction n with d hd,
  {
    unfold polynomial.bernoulli,
    rw [polynomial.eval_C],
    rw [zero_add],
    rw [one_pow],
    rw [one_mul],
    exact (eq.symm (polynomial.eval_C _ _)),
  },
  {
    have h1 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (polynomial.sum (λ (k : ℕ), (polynomial.C (d + 1) * polynomial.X ^ k) * (polynomial.C (k) * polynomial.X ^ (k - 1))) (d + 1)).eval x,
    {
      rw [polynomial.eval_sum],
      rw [polynomial.eval_add],
      rw [polynomial.eval_mul],
      rw [polynomial.eval_C],
      rw [polynomial.eval_mul],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],
      rw [polynomial.eval_C],
      rw [polynomial.eval_X],

end --Needs more than 2000 tokens!

--OUTPUT 5
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  induction n with d hd,
  { calc (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval 1 + 0 * x ^ (0 - 1) : by simp
    ... = (polynomial.bernoulli 0).eval 1 + 0 : by simp
    ... = 1 : by simp
    ... = (polynomial.bernoulli 0).eval x + 0 : by simp
    ... = (polynomial.bernoulli 0).eval x + 0 * x ^ (0 - 1) : by simp
    ... = (polynomial.bernoulli 0).eval x + d * x ^ (0 - 1) : by simp
    ... = (polynomial.bernoulli 0).eval x + d * x ^ (d - 1) : by simp, },
  { calc (polynomial.bernoulli (d + 1)).eval (1 + x) = (polynomial.bernoulli (d + 1)).eval 1 + (d + 1) * x ^ (d + 1 - 1) : by simp
    ... = (polynomial.bernoulli (d + 1)).eval 1 + (d + 1) * x ^ d : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + (d + 1) * x ^ d : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + d * x ^ d + x ^ d : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + d * x ^ d + (polynomial.bernoulli 1).eval x : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + d * x ^ d + x : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + (d * x ^ d + x) : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + (d * x ^ (d - 1) + x ^ (d - 1)) : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + (d + 1) * x ^ (d - 1) : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + (d + 1) * x ^ (d + 1 - 1) : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + (d + 1) * x ^ (d + 1 - 1) : by simp
    ... = (polynomial.bernoulli (d + 1)).eval x + (d + 1) * x ^ (d + 1 - 1) : by simp, }
end

--OUTPUT 6
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  induction n with d hd,
  {
    show (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval x + 0 * x^(0 - 1),
    {
      rw [polynomial.bernoulli,eval_zero,eval_zero,zero_mul],
      ring,
    }
  },
  {
    show (polynomial.bernoulli (d + 1)).eval (1 + x) = (polynomial.bernoulli (d + 1)).eval x + (d + 1) * x^((d + 1) - 1),
    {
      rw [polynomial.bernoulli,eval_add,eval_mul,eval_C,eval_X,eval_add,eval_mul,eval_C,eval_X],
      rw [eval_add,eval_mul,eval_C,eval_X,eval_add,eval_mul,eval_C,eval_X],
      have h1 : (d + 1) * (1 + x)^d = (d + 1) * (1 + x)^d + (d + 1) * x^d - (d + 1) * x^d,
      {
        ring,
      },
      rw [← h1,← hd,add_sub_cancel,sub_add_cancel],
      rw [mul_one,mul_one,add_comm,mul_comm,mul_comm],
      ring,
    }
  }
end

--OUTPUT 7
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  induction n with d hd,
  {
    have h1 : (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval x + 0 * x^(-1), from
      by {rw [(polynomial.bernoulli 0).eval_zero], rw [zero_mul], rw [nat.sub_zero], rw [(polynomial.bernoulli 0).eval_zero], },
    exact h1,
  },
  {
    have h1 : (polynomial.bernoulli 0).eval (1 + x) = (polynomial.bernoulli 0).eval x + 0 * x^(-1), from
      by {rw [(polynomial.bernoulli 0).eval_zero], rw [zero_mul], rw [nat.sub_zero], rw [(polynomial.bernoulli 0).eval_zero], },
    have h2 : (polynomial.bernoulli (d+1)).eval (1 + x) = (polynomial.bernoulli (d+1)).eval x + (d+1) * x^(d), from
    begin
      rw [(polynomial.bernoulli (d+1)).eval_add],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [one_add_one],
      rw [(polynomial.bernoulli (d+1)).eval_mul],
      rw [(polynomial.bernoulli (d+1)).eval_X],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [one_add_one],
      rw [(polynomial.bernoulli (d+1)).eval_X],
      rw [(polynomial.bernoulli d).eval_add],
      rw [hd],
      rw [(polynomial.bernoulli d).eval_zero],
      rw [mul_zero],
      rw [(polynomial.bernoulli d).eval_zero],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [add_mul],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [mul_add],
      rw [add_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      rw [one_mul],
      rw [mul_one],
      rw [mul_one],
      rw [mul_one],
      r
end --Needs more than 2000 tokens!

--OUTPUT 8
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  have h1 : ∀ n : ℕ, ∀ x : ℚ, (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1), from assume (n : ℕ) (x : ℚ),
    by {induction n with d hd, obviously, assume d : ℕ, assume hd : ∀ n : ℕ, ∀ x : ℚ, (polynomial.bernoulli n).eval (1+x) = (polynomial.bernoulli n).eval x + n*x^(n - 1),
      calc (polynomial.bernoulli (d + 1)).eval (1 + x)
           = (polynomial.bernoulli (d + 1)).eval (1 + x) : by refl
      ... = (polynomial.bernoulli (d + 1)).eval (1 + x) : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by rw polynomial.eval_of_degree_eq_zero
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)^d - (d + 1) * x^d : by refl
      ... = (d + 1) * (1 + x)
end --Needs more than 2000 tokens!

/- FEW SHOT PROMPTS TO CODEX(START)
/--`theorem`
Power Set is Closed under Intersection
Let $S$ be a set.

Let $\powerset S$ be the power set of $S$.

Then:
:$\forall A, B \in \powerset S: A \cap B \in \powerset S$
`proof`
Let $A, B \in \powerset S$.

Then by the definition of power set, $A \subseteq S$ and $B \subseteq S$.

From Intersection is Subset we have that $A \cap B \subseteq A$.

It follows from Subset Relation is Transitive that $A \cap B \subseteq S$.

Thus $A \cap B \in \powerset S$ and closure is proved.
{{qed}}
-/
theorem power_set_intersection_closed {α : Type*} (S : set α) : ∀ A B ∈ 𝒫 S, (A ∩ B) ∈ 𝒫 S :=
begin
  assume (A : set α) (hA : A ∈ 𝒫 S) (B : set α) (hB : B ∈ 𝒫 S),
  have h1 : (A ⊆ S) ∧ (B ⊆ S), from by {split,apply set.subset_of_mem_powerset,exact hA,apply set.subset_of_mem_powerset,exact hB},
  have h2 : (A ∩ B) ⊆ A, from by apply set.inter_subset_left,
  have h3 : (A ∩ B) ⊆ S, from by {apply set.subset.trans h2 h1.left},
  show (A ∩ B) ∈  𝒫 S, from by {apply set.mem_powerset h3},
end

/--`theorem`
Square of Sum
 :$\forall x, y \in \R: \paren {x + y}^2 = x^2 + 2 x y + y^2$
`proof`
Follows from the distribution of multiplication over addition:

{{begin-eqn}}
{{eqn | l = \left({x + y}\right)^2
      | r = \left({x + y}\right) \cdot \left({x + y}\right)
}}
{{eqn | r = x \cdot \left({x + y}\right) + y \cdot \left({x + y}\right)
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x \cdot x + x \cdot y + y \cdot x + y \cdot y
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x^2 + 2xy + y^2
      | c =
}}
{{end-eqn}}
{{qed}}
-/
theorem square_of_sum (x y : ℝ) : (x + y)^2 = (x^2 + 2*x*y + y^2) :=
begin
  calc (x + y)^2 = (x+y)*(x+y) : by rw sq
  ... = x*(x+y) + y*(x+y) : by rw add_mul
  ... = x*x + x*y + y*x + y*y : by {rw [mul_comm x (x+y),mul_comm y (x+y)], rw [add_mul,add_mul], ring}
  ... = x^2 + 2*x*y + y^2 : by {repeat {rw ← sq}, rw mul_comm y x, ring}
end

/--`theorem`
Identity of Group is Unique
Let $\struct {G, \circ}$ be a group. Then there is a unique identity element $e \in G$.
`proof`
From Group has Latin Square Property, there exists a unique $x \in G$ such that:
:$a x = b$

and there exists a unique $y \in G$ such that:
:$y a = b$

Setting $b = a$, this becomes:

There exists a unique $x \in G$ such that:
:$a x = a$

and there exists a unique $y \in G$ such that:
:$y a = a$

These $x$ and $y$ are both $e$, by definition of identity element.
{{qed}}
-/
theorem group_identity_unique {G : Type*} [group G] : ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a :=
begin
  have h1 : ∀ a b : G, ∃! x : G, a * x = b, from by {
    assume a b : G, use a⁻¹ * b, obviously, },
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from by {
    assume a b : G, use b * a⁻¹, obviously, },

  have h3 : ∀ a : G, ∃! x : G, a * x = a, from
    assume a : G, h1 a a,
  have h4 : ∀ a : G, ∃! y : G, y * a = a, from
    assume a : G, h2 a a,

  have h5 : ∀ a : G, classical.some (h3 a).exists = (1 : G), from assume a :G,
    exists_unique.unique (h3 a) (classical.some_spec (exists_unique.exists (h3 a)))
    (mul_one a),
  have h6 : ∀ a : G, classical.some (h4 a).exists = (1 : G), from assume a : G,
    exists_unique.unique (h4 a) (classical.some_spec (exists_unique.exists (h4 a))) (one_mul a),

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by {
    use (1 : G),
    have h7 : ∀ e : G, (∀ a : G, e * a = a ∧ a * e = a) → e = 1, from by {
      assume (e : G) (hident : ∀ a : G, e * a = a ∧ a * e = a),
      have h8 : ∀ a : G, e = classical.some (h3 a).exists, from assume (a : G),
        exists_unique.unique (h3 a) (hident a).right
        (classical.some_spec (exists_unique.exists (h3 a))),
      have h9 : ∀ a : G, e = classical.some (h4 a).exists, from assume (a : G),
        exists_unique.unique (h4 a) (hident a).left
        (classical.some_spec (exists_unique.exists (h4 a))),
      show e = (1 : G), from eq.trans (h9 e) (h6 _),
    },
    exact ⟨by obviously, h7⟩,
  }
end

/--`theorem`
Bernoulli polynomial evaluation
Given a natural number $n$ and a rational $x$, let $B_n (x)$ denote the $n$-th Bernoulli polynomial evaluated at $x$. Then, $$B_n (1 + x) = B_n (x) + n x^{n - 1}$$
`proof`
We apply strong induction on $n$. So, for all $m < d$, we have $B_{m} (1 + x) = B_{m} (x) + m x^{m - 1}$ and we want to show that $$B_{d} (1 + x) = B_{d} (x) + d x^{d - 1}$$
Multiplying both sides by $d + 1$, and using the fact that, for all $l \in \mathbb{N}$, $\sum_{k = 0}^{l} {l + 1 \choose k} B_k = (l + 1) X^l$ (where $B_k$ is the $k$-th Bernoulli number), we get that
$$ (d + 1) (1 + x)^d - (d + 1) x^d = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
The conclusion then follows easily.

QED

-/
theorem  bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
FEW SHOT PROMPTS TO CODEX(END)-/