/-
Copyright (c) 2018 Reid Barton. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Reid Barton
-/
import tactic.tidy
import topology.continuous_function.basic
import topology.homeomorph
import topology.subset_properties
import topology.maps

/-!
# The compact-open topology

In this file, we define the compact-open topology on the set of continuous maps between two
topological spaces.

## Main definitions

* `compact_open` is the compact-open topology on `C(α, β)`. It is declared as an instance.
* `continuous_map.coev` is the coevaluation map `β → C(α, β × α)`. It is always continuous.
* `continuous_map.curry` is the currying map `C(α × β, γ) → C(α, C(β, γ))`. This map always exists
  and it is continuous as long as `α × β` is locally compact.
* `continuous_map.uncurry` is the uncurrying map `C(α, C(β, γ)) → C(α × β, γ)`. For this map to
  exist, we need `β` to be locally compact. If `α` is also locally compact, then this map is
  continuous.
* `homeomorph.curry` combines the currying and uncurrying operations into a homeomorphism
  `C(α × β, γ) ≃ₜ C(α, C(β, γ))`. This homeomorphism exists if `α` and `β` are locally compact.

## Tags

compact-open, curry, function space
-/

open set
open_locale topological_space

namespace continuous_map

section compact_open
variables {α : Type*} {β : Type*} {γ : Type*}
variables [topological_space α] [topological_space β] [topological_space γ]

/-- A generating set for the compact-open topology (when `s` is compact and `u` is open). -/
def compact_open.gen (s : set α) (u : set β) : set C(α,β) := {f | f '' s ⊆ u}

@[simp] lemma gen_empty (u : set β) : compact_open.gen (∅ : set α) u = set.univ :=
set.ext (λ f, iff_true_intro ((congr_arg (⊆ u) (image_empty f)).mpr u.empty_subset))

@[simp] lemma gen_univ (s : set α) : compact_open.gen s (set.univ : set β) = set.univ :=
set.ext (λ f, iff_true_intro (f '' s).subset_univ)

@[simp] lemma gen_inter (s : set α) (u v : set β) :
  compact_open.gen s (u ∩ v) = compact_open.gen s u ∩ compact_open.gen s v :=
set.ext (λ f, subset_inter_iff)

@[simp] lemma gen_union (s t : set α) (u : set β) :
  compact_open.gen (s ∪ t) u = compact_open.gen s u ∩ compact_open.gen t u :=
set.ext (λ f, (iff_of_eq (congr_arg (⊆ u) (image_union f s t))).trans union_subset_iff)

-- The compact-open topology on the space of continuous maps α → β.
instance compact_open : topological_space C(α, β) :=
topological_space.generate_from
  {m | ∃ (s : set α) (hs : is_compact s) (u : set β) (hu : is_open u), m = compact_open.gen s u}

protected lemma is_open_gen {s : set α} (hs : is_compact s) {u : set β} (hu : is_open u) :
  is_open (compact_open.gen s u) :=
topological_space.generate_open.basic _ (by dsimp [mem_set_of_eq]; tauto)

section functorial

variables (g : C(β, γ))

private lemma preimage_gen {s : set α} (hs : is_compact s) {u : set γ} (hu : is_open u) :
  continuous_map.comp g ⁻¹' (compact_open.gen s u) = compact_open.gen s (g ⁻¹' u) :=
begin
  ext ⟨f, _⟩,
  change g ∘ f '' s ⊆ u ↔ f '' s ⊆ g ⁻¹' u,
  rw [image_comp, image_subset_iff]
end

/-- C(α, -) is a functor. -/
lemma continuous_comp : continuous (continuous_map.comp g : C(α, β) → C(α, γ)) :=
continuous_generated_from $ assume m ⟨s, hs, u, hu, hm⟩,
  by rw [hm, preimage_gen g hs hu]; exact continuous_map.is_open_gen hs (hu.preimage g.2)

end functorial

section ev

variables {α β}

/-- The evaluation map `C(α, β) × α → β` is continuous if `α` is locally compact.

See also `continuous_map.continuous_eval` -/
lemma continuous_eval' [locally_compact_space α] : continuous (λ p : C(α, β) × α, p.1 p.2) :=
continuous_iff_continuous_at.mpr $ assume ⟨f, x⟩ n hn,
  let ⟨v, vn, vo, fxv⟩ := mem_nhds_iff.mp hn in
  have v ∈ 𝓝 (f x), from is_open.mem_nhds vo fxv,
  let ⟨s, hs, sv, sc⟩ :=
    locally_compact_space.local_compact_nhds x (f ⁻¹' v)
      (f.continuous.tendsto x this) in
  let ⟨u, us, uo, xu⟩ := mem_nhds_iff.mp hs in
  show (λ p : C(α, β) × α, p.1 p.2) ⁻¹' n ∈ 𝓝 (f, x), from
  let w := compact_open.gen s v ×ˢ u in
  have w ⊆ (λ p : C(α, β) × α, p.1 p.2) ⁻¹' n, from assume ⟨f', x'⟩ ⟨hf', hx'⟩, calc
    f' x' ∈ f' '' s  : mem_image_of_mem f' (us hx')
    ...       ⊆ v            : hf'
    ...       ⊆ n            : vn,
  have is_open w, from (continuous_map.is_open_gen sc vo).prod uo,
  have (f, x) ∈ w, from ⟨image_subset_iff.mpr sv, xu⟩,
  mem_nhds_iff.mpr ⟨w, by assumption, by assumption, by assumption⟩

/-- See also `continuous_map.continuous_eval_const` -/
lemma continuous_eval_const' [locally_compact_space α] (a : α) : continuous (λ f : C(α, β), f a) :=
continuous_eval'.comp (continuous_id.prod_mk continuous_const)

/-- See also `continuous_map.continuous_coe` -/
lemma continuous_coe' [locally_compact_space α] : @continuous (C(α, β)) (α → β) _ _ coe_fn :=
continuous_pi continuous_eval_const'

instance [t2_space β] : t2_space C(α, β) :=
⟨ begin
    intros f₁ f₂ h,
    obtain ⟨x, hx⟩ := not_forall.mp (mt (fun_like.ext f₁ f₂) h),
    obtain ⟨u, v, hu, hv, hxu, hxv, huv⟩ := t2_separation hx,
    refine ⟨compact_open.gen {x} u, compact_open.gen {x} v, continuous_map.is_open_gen
      is_compact_singleton hu, continuous_map.is_open_gen is_compact_singleton hv, _, _, _⟩,
    { rwa [compact_open.gen, mem_set_of_eq, image_singleton, singleton_subset_iff] },
    { rwa [compact_open.gen, mem_set_of_eq, image_singleton, singleton_subset_iff] },
    { rw [←continuous_map.gen_inter, huv],
      refine subset_empty_iff.mp (λ f, _),
      rw [compact_open.gen, mem_set_of_eq, image_singleton, singleton_subset_iff],
      exact id },
  end ⟩

end ev

section Inf_induced

lemma compact_open_le_induced (s : set α) :
  (continuous_map.compact_open : topological_space C(α, β))
  ≤ topological_space.induced (continuous_map.restrict s) continuous_map.compact_open :=
begin
  simp only [induced_generate_from_eq, continuous_map.compact_open],
  apply generate_from_mono,
  rintros b ⟨a, ⟨c, hc, u, hu, rfl⟩, rfl⟩,
  refine ⟨coe '' c, hc.image continuous_subtype_coe, u, hu, _⟩,
  ext f,
  simp only [compact_open.gen, mem_set_of_eq, mem_preimage, continuous_map.coe_restrict],
  rw image_comp f (coe : s → α),
end

/-- The compact-open topology on `C(α, β)` is equal to the infimum of the compact-open topologies
on `C(s, β)` for `s` a compact subset of `α`.  The key point of the proof is that the union of the
compact subsets of `α` is equal to the union of compact subsets of the compact subsets of `α`. -/
lemma compact_open_eq_Inf_induced :
  (continuous_map.compact_open : topological_space C(α, β))
  = ⨅ (s : set α) (hs : is_compact s),
    topological_space.induced (continuous_map.restrict s) continuous_map.compact_open :=
begin
  refine le_antisymm _ _,
  { refine le_infi₂ _,
    exact λ s hs, compact_open_le_induced s },
  simp only [← generate_from_Union, induced_generate_from_eq, continuous_map.compact_open],
  apply generate_from_mono,
  rintros _ ⟨s, hs, u, hu, rfl⟩,
  rw mem_Union₂,
  refine ⟨s, hs, _, ⟨univ, is_compact_iff_is_compact_univ.mp hs, u, hu, rfl⟩, _⟩,
  ext f,
  simp only [compact_open.gen, mem_set_of_eq, mem_preimage, continuous_map.coe_restrict],
  rw image_comp f (coe : s → α),
  simp
end

/-- For any subset `s` of `α`, the restriction of continuous functions to `s` is continuous as a
function from `C(α, β)` to `C(s, β)` with their respective compact-open topologies. -/
lemma continuous_restrict (s : set α) : continuous (λ F : C(α, β), F.restrict s) :=
by { rw continuous_iff_le_induced, exact compact_open_le_induced s }

lemma nhds_compact_open_eq_Inf_nhds_induced (f : C(α, β)) :
  𝓝 f = ⨅ s (hs : is_compact s), (𝓝 (f.restrict s)).comap (continuous_map.restrict s) :=
by { rw [compact_open_eq_Inf_induced], simp [nhds_infi, nhds_induced] }

lemma tendsto_compact_open_restrict {ι : Type*} {l : filter ι} {F : ι → C(α, β)} {f : C(α, β)}
  (hFf : filter.tendsto F l (𝓝 f)) (s : set α) :
  filter.tendsto (λ i, (F i).restrict s) l (𝓝 (f.restrict s)) :=
(continuous_restrict s).continuous_at.tendsto.comp hFf

lemma tendsto_compact_open_iff_forall {ι : Type*} {l : filter ι} (F : ι → C(α, β)) (f : C(α, β)) :
  filter.tendsto F l (𝓝 f)
  ↔ ∀ s (hs : is_compact s), filter.tendsto (λ i, (F i).restrict s) l (𝓝 (f.restrict s)) :=
by { rw [compact_open_eq_Inf_induced], simp [nhds_infi, nhds_induced, filter.tendsto_comap_iff] }

/-- A family `F` of functions in `C(α, β)` converges in the compact-open topology, if and only if
it converges in the compact-open topology on each compact subset of `α`. -/
lemma exists_tendsto_compact_open_iff_forall [locally_compact_space α] [t2_space α] [t2_space β]
  {ι : Type*} {l : filter ι} [filter.ne_bot l] (F : ι → C(α, β)) :
  (∃ f, filter.tendsto F l (𝓝 f))
  ↔ ∀ (s : set α) (hs : is_compact s), ∃ f, filter.tendsto (λ i, (F i).restrict s) l (𝓝 f) :=
begin
  split,
  { rintros ⟨f, hf⟩ s hs,
    exact ⟨f.restrict s, tendsto_compact_open_restrict hf s⟩ },
  { intros h,
    choose f hf using h,
    -- By uniqueness of limits in a `t2_space`, since `λ i, F i x` tends to both `f s₁ hs₁ x` and
    -- `f s₂ hs₂ x`, we have `f s₁ hs₁ x = f s₂ hs₂ x`
    have h : ∀ s₁ (hs₁ : is_compact s₁) s₂ (hs₂ : is_compact s₂) (x : α) (hxs₁ : x ∈ s₁)
      (hxs₂ : x ∈ s₂), f s₁ hs₁ ⟨x, hxs₁⟩ = f s₂ hs₂ ⟨x, hxs₂⟩,
    { rintros s₁ hs₁ s₂ hs₂ x hxs₁ hxs₂,
      haveI := is_compact_iff_compact_space.mp hs₁,
      haveI := is_compact_iff_compact_space.mp hs₂,
      have h₁ := (continuous_eval_const' (⟨x, hxs₁⟩ : s₁)).continuous_at.tendsto.comp (hf s₁ hs₁),
      have h₂ := (continuous_eval_const' (⟨x, hxs₂⟩ : s₂)).continuous_at.tendsto.comp (hf s₂ hs₂),
      exact tendsto_nhds_unique h₁ h₂ },
    -- So glue the `f s hs` together and prove that this glued function `f₀` is a limit on each
    -- compact set `s`
    have hs : ∀ x : α, ∃ s (hs : is_compact s), s ∈ 𝓝 x,
    { intros x,
      obtain ⟨s, hs, hs'⟩ := exists_compact_mem_nhds x,
      exact ⟨s, hs, hs'⟩ },
    refine ⟨lift_cover' _ _ h hs, _⟩,
    rw tendsto_compact_open_iff_forall,
    intros s hs,
    rw lift_cover_restrict',
    exact hf s hs }
end

end Inf_induced

section coev

variables (α β)

/-- The coevaluation map `β → C(α, β × α)` sending a point `x : β` to the continuous function
on `α` sending `y` to `(x, y)`. -/
def coev (b : β) : C(α, β × α) := ⟨prod.mk b, continuous_const.prod_mk continuous_id⟩

variables {α β}
lemma image_coev {y : β} (s : set α) : (coev α β y) '' s = ({y} : set β) ×ˢ s := by tidy

-- The coevaluation map β → C(α, β × α) is continuous (always).
lemma continuous_coev : continuous (coev α β) :=
continuous_generated_from $ begin
  rintros _ ⟨s, sc, u, uo, rfl⟩,
  rw is_open_iff_forall_mem_open,
  intros y hy,
  change (coev α β y) '' s ⊆ u at hy,
  rw image_coev s at hy,
  rcases generalized_tube_lemma is_compact_singleton sc uo hy
    with ⟨v, w, vo, wo, yv, sw, vwu⟩,
  refine ⟨v, _, vo, singleton_subset_iff.mp yv⟩,
  intros y' hy',
  change (coev α β y') '' s ⊆ u,
  rw image_coev s,
  exact subset.trans (prod_mono (singleton_subset_iff.mpr hy') sw) vwu
end

end coev

section curry

/-- Auxiliary definition, see `continuous_map.curry` and `homeomorph.curry`. -/
def curry' (f : C(α × β, γ)) (a : α) : C(β, γ) := ⟨function.curry f a⟩

/-- If a map `α × β → γ` is continuous, then its curried form `α → C(β, γ)` is continuous. -/
lemma continuous_curry' (f : C(α × β, γ)) : continuous (curry' f) :=
have hf : curry' f = continuous_map.comp f ∘ coev _ _, by { ext, refl },
hf ▸ continuous.comp (continuous_comp f) continuous_coev

/-- To show continuity of a map `α → C(β, γ)`, it suffices to show that its uncurried form
    `α × β → γ` is continuous. -/
lemma continuous_of_continuous_uncurry (f : α → C(β, γ))
  (h : continuous (function.uncurry (λ x y, f x y))) : continuous f :=
by { convert continuous_curry' ⟨_, h⟩, ext, refl }

/-- The curried form of a continuous map `α × β → γ` as a continuous map `α → C(β, γ)`.
    If `a × β` is locally compact, this is continuous. If `α` and `β` are both locally
    compact, then this is a homeomorphism, see `homeomorph.curry`. -/
def curry (f : C(α × β, γ)) : C(α, C(β, γ)) :=
⟨_, continuous_curry' f⟩

/-- The currying process is a continuous map between function spaces. -/
lemma continuous_curry [locally_compact_space (α × β)] :
  continuous (curry : C(α × β, γ) → C(α, C(β, γ))) :=
begin
  apply continuous_of_continuous_uncurry,
  apply continuous_of_continuous_uncurry,
  rw ←homeomorph.comp_continuous_iff' (homeomorph.prod_assoc _ _ _).symm,
  convert continuous_eval';
  tidy
end

@[simp]
lemma curry_apply (f : C(α × β, γ)) (a : α) (b : β) : f.curry a b = f (a, b) := rfl

/-- The uncurried form of a continuous map `α → C(β, γ)` is a continuous map `α × β → γ`. -/
lemma continuous_uncurry_of_continuous [locally_compact_space β] (f : C(α, C(β, γ))) :
  continuous (function.uncurry (λ x y, f x y)) :=
continuous_eval'.comp $ f.continuous.prod_map continuous_id

/-- The uncurried form of a continuous map `α → C(β, γ)` as a continuous map `α × β → γ` (if `β` is
    locally compact). If `α` is also locally compact, then this is a homeomorphism between the two
    function spaces, see `homeomorph.curry`. -/
def uncurry [locally_compact_space β] (f : C(α, C(β, γ))) : C(α × β, γ) :=
⟨_, continuous_uncurry_of_continuous f⟩

/-- The uncurrying process is a continuous map between function spaces. -/
lemma continuous_uncurry [locally_compact_space α] [locally_compact_space β] :
  continuous (uncurry : C(α, C(β, γ)) → C(α × β, γ)) :=
begin
  apply continuous_of_continuous_uncurry,
  rw ←homeomorph.comp_continuous_iff' (homeomorph.prod_assoc _ _ _),
  apply continuous.comp continuous_eval' (continuous.prod_map continuous_eval' continuous_id);
  apply_instance
end

/-- The family of constant maps: `β → C(α, β)` as a continuous map. -/
def const' : C(β, C(α, β)) := curry ⟨prod.fst, continuous_fst⟩

@[simp] lemma coe_const' : (const' : β → C(α, β)) = const α := rfl

lemma continuous_const' : continuous (const α : β → C(α, β)) := const'.continuous

end curry

end compact_open

end continuous_map

open continuous_map

namespace homeomorph
variables {α : Type*} {β : Type*} {γ : Type*}
variables [topological_space α] [topological_space β] [topological_space γ]

/-- Currying as a homeomorphism between the function spaces `C(α × β, γ)` and `C(α, C(β, γ))`. -/
def curry [locally_compact_space α] [locally_compact_space β] : C(α × β, γ) ≃ₜ C(α, C(β, γ)) :=
⟨⟨curry, uncurry, by tidy, by tidy⟩, continuous_curry, continuous_uncurry⟩

/-- If `α` has a single element, then `β` is homeomorphic to `C(α, β)`. -/
def continuous_map_of_unique [unique α] : β ≃ₜ C(α, β) :=
{ to_fun := const α,
  inv_fun := λ f, f default,
  left_inv := λ a, rfl,
  right_inv := λ f, by { ext, rw unique.eq_default a, refl },
  continuous_to_fun := continuous_const',
  continuous_inv_fun := continuous_eval'.comp (continuous_id.prod_mk continuous_const) }

@[simp]

@[simp] lemma continuous_map_of_unique_symm_apply [unique α] (f : C(α, β)) :
  continuous_map_of_unique.symm f = f default :=
rfl

end homeomorph

section quotient_map

variables {X₀ X Y Z : Type*} [topological_space X₀] [topological_space X]
  [topological_space Y] [topological_space Z] [locally_compact_space Y] {f : X₀ → X}

lemma quotient_map.continuous_lift_prod_left (hf : quotient_map f) {g : X × Y → Z}
  (hg : continuous (λ p : X₀ × Y, g (f p.1, p.2))) : continuous g :=
begin
  let Gf : C(X₀, C(Y, Z)) := continuous_map.curry ⟨_, hg⟩,
  have h : ∀ x : X, continuous (λ y, g (x, y)),
  { intros x,
    obtain ⟨x₀, rfl⟩ := hf.surjective x,
    exact (Gf x₀).continuous },
  let G : X → C(Y, Z) := λ x, ⟨_, h x⟩,
  have : continuous G,
  { rw hf.continuous_iff,
    exact Gf.continuous },
  convert continuous_map.continuous_uncurry_of_continuous ⟨G, this⟩,
  ext x,
  cases x,
  refl,
end

lemma quotient_map.continuous_lift_prod_right (hf : quotient_map f) {g : Y × X → Z}
  (hg : continuous (λ p : Y × X₀, g (p.1, f p.2))) : continuous g :=
begin
  have : continuous (λ p : X₀ × Y, g ((prod.swap p).1, f (prod.swap p).2)),
  { exact hg.comp continuous_swap },
  have : continuous (λ p : X₀ × Y, (g ∘ prod.swap) (f p.1, p.2)) := this,
  convert (hf.continuous_lift_prod_left this).comp continuous_swap,
  ext x,
  simp,
end

end quotient_map


/-
Copyright (c) 2019 Kevin Kappelmann. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Kappelmann
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.seq.seq
import Mathlib.algebra.field
import Mathlib.PostPort

universes u_1 l u_2

namespace Mathlib

/-!
# Basic Definitions/Theorems for Continued Fractions

## Summary

We define generalised, simple, and regular continued fractions and functions to evaluate their
convergents. We follow the naming conventions from Wikipedia and [wall2018analytic], Chapter 1.

## Main definitions

1. Generalised continued fractions (gcfs)
2. Simple continued fractions (scfs)
3. (Regular) continued fractions ((r)cfs)
4. Computation of convergents using the recurrence relation in `convergents`.
5. Computation of convergents by directly evaluating the fraction described by the gcf in
`convergents'`.

## Implementation notes

1. The most commonly used kind of continued fractions in the literature are regular continued
fractions. We hence just call them `continued_fractions` in the library.
2. We use sequences from `data.seq` to encode potentially infinite sequences.

## References

- <https://en.wikipedia.org/wiki/Generalized_continued_fraction>
- [Wall, H.S., *Analytic Theory of Continued Fractions*][wall2018analytic]

## Tags

numerics, number theory, approximations, fractions
-/

-- Fix a carrier `α`.

/-- We collect a partial numerator `aᵢ` and partial denominator `bᵢ` in a pair `⟨aᵢ,bᵢ⟩`. -/
structure generalized_continued_fraction.pair (α : Type u_1) where
  a : α
  b : α

/- Interlude: define some expected coercions and instances. -/

namespace generalized_continued_fraction.pair

/-- Make a `gcf.pair` printable. -/
protected instance has_repr {α : Type u_1} [has_repr α] : has_repr (pair α) := sorry

/-- Maps a function `f` on both components of a given pair. -/
def map {α : Type u_1} {β : Type u_2} (f : α → β) (gp : pair α) : pair β := mk (f (a gp)) (f (b gp))

/-! Interlude: define some expected coercions. -/

/- Fix another type `β` which we will convert to. -/

/-- Coerce a pair by elementwise coercion. -/
protected instance has_coe_to_generalized_continued_fraction_pair {α : Type u_1} {β : Type u_2}
    [has_coe α β] : has_coe (pair α) (pair β) :=
  has_coe.mk (map coe)

@[simp] theorem coe_to_generalized_continued_fraction_pair {α : Type u_1} {β : Type u_2}
    [has_coe α β] {a : α} {b : α} : ↑(mk a b) = mk ↑a ↑b :=
  rfl

end generalized_continued_fraction.pair

/--
A *generalised continued fraction* (gcf) is a potentially infinite expression of the form

                                a₀
                h + ---------------------------
                                  a₁
                      b₀ + --------------------
                                    a₂
                            b₁ + --------------
                                        a₃
                                  b₂ + --------
                                      b₃ + ...

where `h` is called the *head term* or *integer part*, the `aᵢ` are called the
*partial numerators* and the `bᵢ` the *partial denominators* of the gcf.
We store the sequence of partial numerators and denominators in a sequence of
generalized_continued_fraction.pairs `s`.
For convenience, one often writes `[h; (a₀, b₀), (a₁, b₁), (a₂, b₂),...]`.
-/
structure generalized_continued_fraction (α : Type u_1) where
  h : α
  s : seq (generalized_continued_fraction.pair α)

namespace generalized_continued_fraction

/-- Constructs a generalized continued fraction without fractional part. -/
def of_integer {α : Type u_1} (a : α) : generalized_continued_fraction α := mk a seq.nil

protected instance inhabited {α : Type u_1} [Inhabited α] :
    Inhabited (generalized_continued_fraction α) :=
  { default := of_integer Inhabited.default }

/-- Returns the sequence of partial numerators `aᵢ` of `g`. -/
/-- Returns the sequence of partial denominators `bᵢ` of `g`. -/
def partial_numerators {α : Type u_1} (g : generalized_continued_fraction α) : seq α :=
  seq.map pair.a (s g)

def partial_denominators {α : Type u_1} (g : generalized_continued_fraction α) : seq α :=
  seq.map pair.b (s g)

/-- A gcf terminated at position `n` if its sequence terminates at position `n`. -/
def terminated_at {α : Type u_1} (g : generalized_continued_fraction α) (n : ℕ) :=
  seq.terminated_at (s g) n

/-- It is decidable whether a gcf terminated at a given position. -/
protected instance terminated_at_decidable {α : Type u_1} (g : generalized_continued_fraction α)
    (n : ℕ) : Decidable (terminated_at g n) :=
  eq.mpr sorry (seq.terminated_at_decidable (s g) n)

/-- A gcf terminates if its sequence terminates. -/
def terminates {α : Type u_1} (g : generalized_continued_fraction α) := seq.terminates (s g)

/-! Interlude: define some expected coercions. -/

/- Fix another type `β` which we will convert to. -/

/-- Coerce a gcf by elementwise coercion. -/
protected instance has_coe_to_generalized_continued_fraction {α : Type u_1} {β : Type u_2}
    [has_coe α β] : has_coe (generalized_continued_fraction α) (generalized_continued_fraction β) :=
  has_coe.mk fun (g : generalized_continued_fraction α) => mk (↑(h g)) (seq.map coe (s g))

@[simp] theorem coe_to_generalized_continued_fraction {α : Type u_1} {β : Type u_2} [has_coe α β]
    {g : generalized_continued_fraction α} : ↑g = mk (↑(h g)) (seq.map coe (s g)) :=
  rfl

end generalized_continued_fraction

/--
A generalized continued fraction is a *simple continued fraction* if all partial numerators are
equal to one.

                                1
                h + ---------------------------
                                  1
                      b₀ + --------------------
                                    1
                            b₁ + --------------
                                        1
                                  b₂ + --------
                                      b₃ + ...

-/
def generalized_continued_fraction.is_simple_continued_fraction {α : Type u_1}
    (g : generalized_continued_fraction α) [HasOne α] :=
  ∀ (n : ℕ) (aₙ : α),
    seq.nth (generalized_continued_fraction.partial_numerators g) n = some aₙ → aₙ = 1

/--
A *simple continued fraction* (scf) is a generalized continued fraction (gcf) whose partial
numerators are equal to one.

                                1
                h + ---------------------------
                                  1
                      b₀ + --------------------
                                    1
                            b₁ + --------------
                                        1
                                  b₂ + --------
                                      b₃ + ...

For convenience, one often writes `[h; b₀, b₁, b₂,...]`.
It is encoded as the subtype of gcfs that satisfy
`generalized_continued_fraction.is_simple_continued_fraction`.
 -/
def simple_continued_fraction (α : Type u_1) [HasOne α] :=
  Subtype
    fun (g : generalized_continued_fraction α) =>
      generalized_continued_fraction.is_simple_continued_fraction g

/- Interlude: define some expected coercions. -/

namespace simple_continued_fraction

/-- Constructs a simple continued fraction without fractional part. -/
def of_integer {α : Type u_1} [HasOne α] (a : α) : simple_continued_fraction α :=
  { val := generalized_continued_fraction.of_integer a, property := sorry }

protected instance inhabited {α : Type u_1} [HasOne α] : Inhabited (simple_continued_fraction α) :=
  { default := of_integer 1 }

/-- Lift a scf to a gcf using the inclusion map. -/
protected instance has_coe_to_generalized_continued_fraction {α : Type u_1} [HasOne α] :
    has_coe (simple_continued_fraction α) (generalized_continued_fraction α) :=
  eq.mpr sorry Mathlib.coe_subtype

theorem coe_to_generalized_continued_fraction {α : Type u_1} [HasOne α]
    {s : simple_continued_fraction α} : ↑s = subtype.val s :=
  rfl

end simple_continued_fraction

/--
A simple continued fraction is a *(regular) continued fraction* ((r)cf) if all partial denominators
`bᵢ` are positive, i.e. `0 < bᵢ`.
-/
def simple_continued_fraction.is_regular_continued_fraction {α : Type u_1} [HasOne α] [HasZero α]
    [HasLess α] (s : simple_continued_fraction α) :=
  ∀ (n : ℕ) (bₙ : α),
    seq.nth (generalized_continued_fraction.partial_denominators ↑s) n = some bₙ → 0 < bₙ

/--
A *(regular) continued fraction* ((r)cf) is a simple continued fraction (scf) whose partial
denominators are all positive. It is the subtype of scfs that satisfy
`simple_continued_fraction.is_regular_continued_fraction`.
 -/
def continued_fraction (α : Type u_1) [HasOne α] [HasZero α] [HasLess α] :=
  Subtype
    fun (s : simple_continued_fraction α) =>
      simple_continued_fraction.is_regular_continued_fraction s

/- Interlude: define some expected coercions. -/

namespace continued_fraction

/-- Constructs a continued fraction without fractional part. -/
def of_integer {α : Type u_1} [HasOne α] [HasZero α] [HasLess α] (a : α) : continued_fraction α :=
  { val := simple_continued_fraction.of_integer a, property := sorry }

protected instance inhabited {α : Type u_1} [HasOne α] [HasZero α] [HasLess α] :
    Inhabited (continued_fraction α) :=
  { default := of_integer 0 }

/-- Lift a cf to a scf using the inclusion map. -/
protected instance has_coe_to_simple_continued_fraction {α : Type u_1} [HasOne α] [HasZero α]
    [HasLess α] : has_coe (continued_fraction α) (simple_continued_fraction α) :=
  eq.mpr sorry Mathlib.coe_subtype

theorem coe_to_simple_continued_fraction {α : Type u_1} [HasOne α] [HasZero α] [HasLess α]
    {c : continued_fraction α} : ↑c = subtype.val c :=
  rfl

/-- Lift a cf to a scf using the inclusion map. -/
protected instance has_coe_to_generalized_continued_fraction {α : Type u_1} [HasOne α] [HasZero α]
    [HasLess α] : has_coe (continued_fraction α) (generalized_continued_fraction α) :=
  has_coe.mk fun (c : continued_fraction α) => ↑↑c

theorem coe_to_generalized_continued_fraction {α : Type u_1} [HasOne α] [HasZero α] [HasLess α]
    {c : continued_fraction α} : ↑c = ↑(subtype.val c) :=
  rfl

end continued_fraction

/-
We now define how to compute the convergents of a gcf. There are two standard ways to do this:
directly evaluating the (infinite) fraction described by the gcf or using a recurrence relation.
For (r)cfs, these computations are equivalent as shown in
`algebra.continued_fractions.convergents_equiv`.
-/

namespace generalized_continued_fraction

-- Fix a division ring for the computations.

/-
We start with the definition of the recurrence relation. Given a gcf `g`, for all `n ≥ 1`, we define
- `A₋₁ = 1,  A₀ = h,  Aₙ = bₙ₋₁ * Aₙ₋₁ + aₙ₋₁ * Aₙ₋₂`, and
- `B₋₁ = 0,  B₀ = 1,  Bₙ = bₙ₋₁ * Bₙ₋₁ + aₙ₋₁ * Bₙ₋₂`.

`Aₙ, `Bₙ` are called the *nth continuants*, Aₙ the *nth numerator*, and `Bₙ` the
*nth denominator* of `g`. The *nth convergent* of `g` is given by `Aₙ / Bₙ`.
-/

/--
Returns the next numerator `Aₙ = bₙ₋₁ * Aₙ₋₁ + aₙ₋₁ * Aₙ₋₂`, where `predA` is `Aₙ₋₁`,
`ppredA` is `Aₙ₋₂`, `a` is `aₙ₋₁`, and `b` is `bₙ₋₁`.
-/
def next_numerator {K : Type u_2} [division_ring K] (a : K) (b : K) (ppredA : K) (predA : K) : K :=
  b * predA + a * ppredA

/--
Returns the next denominator `Bₙ = bₙ₋₁ * Bₙ₋₁ + aₙ₋₁ * Bₙ₋₂``, where `predB` is `Bₙ₋₁` and
`ppredB` is `Bₙ₋₂`, `a` is `aₙ₋₁`, and `b` is `bₙ₋₁`.
-/
def next_denominator {K : Type u_2} [division_ring K] (aₙ : K) (bₙ : K) (ppredB : K) (predB : K) :
    K :=
  bₙ * predB + aₙ * ppredB

/--
Returns the next continuants `⟨Aₙ, Bₙ⟩` using `next_numerator` and `next_denominator`, where `pred`
is `⟨Aₙ₋₁, Bₙ₋₁⟩`, `ppred` is `⟨Aₙ₋₂, Bₙ₋₂⟩`, `a` is `aₙ₋₁`, and `b` is `bₙ₋₁`.
-/
def next_continuants {K : Type u_2} [division_ring K] (a : K) (b : K) (ppred : pair K)
    (pred : pair K) : pair K :=
  pair.mk (next_numerator a b (pair.a ppred) (pair.a pred))
    (next_denominator a b (pair.b ppred) (pair.b pred))

/-- Returns the continuants `⟨Aₙ₋₁, Bₙ₋₁⟩` of `g`. -/
def continuants_aux {K : Type u_2} [division_ring K] (g : generalized_continued_fraction K) :
    stream (pair K) :=
  sorry

/-- Returns the continuants `⟨Aₙ, Bₙ⟩` of `g`. -/
def continuants {K : Type u_2} [division_ring K] (g : generalized_continued_fraction K) :
    stream (pair K) :=
  stream.tail (continuants_aux g)

/-- Returns the numerators `Aₙ` of `g`. -/
def numerators {K : Type u_2} [division_ring K] (g : generalized_continued_fraction K) : stream K :=
  stream.map pair.a (continuants g)

/-- Returns the denominators `Bₙ` of `g`. -/
def denominators {K : Type u_2} [division_ring K] (g : generalized_continued_fraction K) :
    stream K :=
  stream.map pair.b (continuants g)

/-- Returns the convergents `Aₙ / Bₙ` of `g`, where `Aₙ, Bₙ` are the nth continuants of `g`. -/
def convergents {K : Type u_2} [division_ring K] (g : generalized_continued_fraction K) :
    stream K :=
  fun (n : ℕ) => numerators g n / denominators g n

/--
Returns the approximation of the fraction described by the given sequence up to a given position n.
For example, `convergents'_aux [(1, 2), (3, 4), (5, 6)] 2 = 1 / (2 + 3 / 4)` and
`convergents'_aux [(1, 2), (3, 4), (5, 6)] 0 = 0`.
-/
def convergents'_aux {K : Type u_2} [division_ring K] : seq (pair K) → ℕ → K := sorry

/--
Returns the convergents of `g` by evaluating the fraction described by `g` up to a given
position `n`. For example, `convergents' [9; (1, 2), (3, 4), (5, 6)] 2 = 9 + 1 / (2 + 3 / 4)` and
`convergents' [9; (1, 2), (3, 4), (5, 6)] 0 = 9`
-/
def convergents' {K : Type u_2} [division_ring K] (g : generalized_continued_fraction K) (n : ℕ) :
    K :=
  h g + convergents'_aux (s g) n

end generalized_continued_fraction

-- Now, some basic, general theorems

namespace generalized_continued_fraction

/-- Two gcfs `g` and `g'` are equal if and only if their components are equal. -/
protected theorem ext_iff {α : Type u_1} {g : generalized_continued_fraction α}
    {g' : generalized_continued_fraction α} : g = g' ↔ h g = h g' ∧ s g = s g' :=
  sorry

protected theorem ext {α : Type u_1} {g : generalized_continued_fraction α}
    {g' : generalized_continued_fraction α} (hyp : h g = h g' ∧ s g = s g') : g = g' :=
  iff.elim_right generalized_continued_fraction.ext_iff hyp

end Mathlib


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

! This file was ported from Lean 3 source module category_theory.bicategory.coherence_tactic
! leanprover-community/mathlib commit 3d7987cda72abc473c7cdbbb075170e9ac620042
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.Bicategory.Coherence

/-!
# A `coherence` tactic for bicategories, and `⊗≫` (composition up to associators)

We provide a `coherence` tactic,
which proves that any two 2-morphisms (with the same source and target)
in a bicategory which are built out of associators and unitors
are equal.

We also provide `f ⊗≫ g`, the `bicategorical_comp` operation,
which automatically inserts associators and unitors as needed
to make the target of `f` match the source of `g`.

This file mainly deals with the type class setup for the coherence tactic. The actual front end
tactic is given in `category_theory/monooidal/coherence.lean` at the same time as the coherence
tactic for monoidal categories.
-/

noncomputable section

universe w v u

open CategoryTheory

open CategoryTheory.FreeBicategory

open Bicategory

variable {B : Type u} [Bicategory.{w, v} B] {a b c d e : B}

namespace CategoryTheory.Bicategory

/-- A typeclass carrying a choice of lift of a 1-morphism from `B` to `free_bicategory B`. -/
class LiftHom {a b : B} (f : a ⟶ b) where
  lift : of.obj a ⟶ of.obj b
#align category_theory.bicategory.lift_hom CategoryTheory.Bicategory.LiftHom

instance liftHomId : LiftHom (𝟙 a) where lift := 𝟙 (of.obj a)
#align category_theory.bicategory.lift_hom_id CategoryTheory.Bicategory.liftHomId

instance liftHomComp (f : a ⟶ b) (g : b ⟶ c) [LiftHom f] [LiftHom g] : LiftHom (f ≫ g)
    where lift := LiftHom.lift f ≫ LiftHom.lift g
#align category_theory.bicategory.lift_hom_comp CategoryTheory.Bicategory.liftHomComp

instance (priority := 100) liftHomOf (f : a ⟶ b) : LiftHom f where lift := of.map f
#align category_theory.bicategory.lift_hom_of CategoryTheory.Bicategory.liftHomOf

/-- A typeclass carrying a choice of lift of a 2-morphism from `B` to `free_bicategory B`. -/
class LiftHom₂ {f g : a ⟶ b} [LiftHom f] [LiftHom g] (η : f ⟶ g) where
  lift : LiftHom.lift f ⟶ LiftHom.lift g
#align category_theory.bicategory.lift_hom₂ CategoryTheory.Bicategory.LiftHom₂

instance liftHom₂Id (f : a ⟶ b) [LiftHom f] : LiftHom₂ (𝟙 f) where lift := 𝟙 _
#align category_theory.bicategory.lift_hom₂_id CategoryTheory.Bicategory.liftHom₂Id

instance liftHom₂LeftUnitorHom (f : a ⟶ b) [LiftHom f] : LiftHom₂ (λ_ f).Hom
    where lift := (λ_ (LiftHom.lift f)).Hom
#align category_theory.bicategory.lift_hom₂_left_unitor_hom CategoryTheory.Bicategory.liftHom₂LeftUnitorHom

instance liftHom₂LeftUnitorInv (f : a ⟶ b) [LiftHom f] : LiftHom₂ (λ_ f).inv
    where lift := (λ_ (LiftHom.lift f)).inv
#align category_theory.bicategory.lift_hom₂_left_unitor_inv CategoryTheory.Bicategory.liftHom₂LeftUnitorInv

instance liftHom₂RightUnitorHom (f : a ⟶ b) [LiftHom f] : LiftHom₂ (ρ_ f).Hom
    where lift := (ρ_ (LiftHom.lift f)).Hom
#align category_theory.bicategory.lift_hom₂_right_unitor_hom CategoryTheory.Bicategory.liftHom₂RightUnitorHom

instance liftHom₂RightUnitorInv (f : a ⟶ b) [LiftHom f] : LiftHom₂ (ρ_ f).inv
    where lift := (ρ_ (LiftHom.lift f)).inv
#align category_theory.bicategory.lift_hom₂_right_unitor_inv CategoryTheory.Bicategory.liftHom₂RightUnitorInv

instance liftHom₂AssociatorHom (f : a ⟶ b) (g : b ⟶ c) (h : c ⟶ d) [LiftHom f] [LiftHom g]
    [LiftHom h] : LiftHom₂ (α_ f g h).Hom
    where lift := (α_ (LiftHom.lift f) (LiftHom.lift g) (LiftHom.lift h)).Hom
#align category_theory.bicategory.lift_hom₂_associator_hom CategoryTheory.Bicategory.liftHom₂AssociatorHom

instance liftHom₂AssociatorInv (f : a ⟶ b) (g : b ⟶ c) (h : c ⟶ d) [LiftHom f] [LiftHom g]
    [LiftHom h] : LiftHom₂ (α_ f g h).inv
    where lift := (α_ (LiftHom.lift f) (LiftHom.lift g) (LiftHom.lift h)).inv
#align category_theory.bicategory.lift_hom₂_associator_inv CategoryTheory.Bicategory.liftHom₂AssociatorInv

instance liftHom₂Comp {f g h : a ⟶ b} [LiftHom f] [LiftHom g] [LiftHom h] (η : f ⟶ g) (θ : g ⟶ h)
    [LiftHom₂ η] [LiftHom₂ θ] : LiftHom₂ (η ≫ θ) where lift := LiftHom₂.lift η ≫ LiftHom₂.lift θ
#align category_theory.bicategory.lift_hom₂_comp CategoryTheory.Bicategory.liftHom₂Comp

instance liftHom₂WhiskerLeft (f : a ⟶ b) [LiftHom f] {g h : b ⟶ c} (η : g ⟶ h) [LiftHom g]
    [LiftHom h] [LiftHom₂ η] : LiftHom₂ (f ◁ η) where lift := LiftHom.lift f ◁ LiftHom₂.lift η
#align category_theory.bicategory.lift_hom₂_whisker_left CategoryTheory.Bicategory.liftHom₂WhiskerLeft

instance liftHom₂WhiskerRight {f g : a ⟶ b} (η : f ⟶ g) [LiftHom f] [LiftHom g] [LiftHom₂ η]
    {h : b ⟶ c} [LiftHom h] : LiftHom₂ (η ▷ h) where lift := LiftHom₂.lift η ▷ LiftHom.lift h
#align category_theory.bicategory.lift_hom₂_whisker_right CategoryTheory.Bicategory.liftHom₂WhiskerRight

/- ./././Mathport/Syntax/Translate/Command.lean:388:30: infer kinds are unsupported in Lean 4: #[`Hom] [] -/
/-- A typeclass carrying a choice of bicategorical structural isomorphism between two objects.
Used by the `⊗≫` bicategorical composition operator, and the `coherence` tactic.
-/
class BicategoricalCoherence (f g : a ⟶ b) [LiftHom f] [LiftHom g] where
  Hom : f ⟶ g
  [IsIso : IsIso hom]
#align category_theory.bicategory.bicategorical_coherence CategoryTheory.Bicategory.BicategoricalCoherence

attribute [instance] bicategorical_coherence.is_iso

namespace BicategoricalCoherence

@[simps]
instance refl (f : a ⟶ b) [LiftHom f] : BicategoricalCoherence f f :=
  ⟨𝟙 _⟩
#align category_theory.bicategory.bicategorical_coherence.refl CategoryTheory.Bicategory.BicategoricalCoherence.refl

@[simps]
instance whiskerLeft (f : a ⟶ b) (g h : b ⟶ c) [LiftHom f] [LiftHom g] [LiftHom h]
    [BicategoricalCoherence g h] : BicategoricalCoherence (f ≫ g) (f ≫ h) :=
  ⟨f ◁ BicategoricalCoherence.hom g h⟩
#align category_theory.bicategory.bicategorical_coherence.whisker_left CategoryTheory.Bicategory.BicategoricalCoherence.whiskerLeft

@[simps]
instance whiskerRight (f g : a ⟶ b) (h : b ⟶ c) [LiftHom f] [LiftHom g] [LiftHom h]
    [BicategoricalCoherence f g] : BicategoricalCoherence (f ≫ h) (g ≫ h) :=
  ⟨BicategoricalCoherence.hom f g ▷ h⟩
#align category_theory.bicategory.bicategorical_coherence.whisker_right CategoryTheory.Bicategory.BicategoricalCoherence.whiskerRight

@[simps]
instance tensorRight (f : a ⟶ b) (g : b ⟶ b) [LiftHom f] [LiftHom g]
    [BicategoricalCoherence (𝟙 b) g] : BicategoricalCoherence f (f ≫ g) :=
  ⟨(ρ_ f).inv ≫ f ◁ BicategoricalCoherence.hom (𝟙 b) g⟩
#align category_theory.bicategory.bicategorical_coherence.tensor_right CategoryTheory.Bicategory.BicategoricalCoherence.tensorRight

@[simps]
instance tensorRight' (f : a ⟶ b) (g : b ⟶ b) [LiftHom f] [LiftHom g]
    [BicategoricalCoherence g (𝟙 b)] : BicategoricalCoherence (f ≫ g) f :=
  ⟨f ◁ BicategoricalCoherence.hom g (𝟙 b) ≫ (ρ_ f).Hom⟩
#align category_theory.bicategory.bicategorical_coherence.tensor_right' CategoryTheory.Bicategory.BicategoricalCoherence.tensorRight'

@[simps]
instance left (f g : a ⟶ b) [LiftHom f] [LiftHom g] [BicategoricalCoherence f g] :
    BicategoricalCoherence (𝟙 a ≫ f) g :=
  ⟨(λ_ f).Hom ≫ BicategoricalCoherence.hom f g⟩
#align category_theory.bicategory.bicategorical_coherence.left CategoryTheory.Bicategory.BicategoricalCoherence.left

@[simps]
instance left' (f g : a ⟶ b) [LiftHom f] [LiftHom g] [BicategoricalCoherence f g] :
    BicategoricalCoherence f (𝟙 a ≫ g) :=
  ⟨BicategoricalCoherence.hom f g ≫ (λ_ g).inv⟩
#align category_theory.bicategory.bicategorical_coherence.left' CategoryTheory.Bicategory.BicategoricalCoherence.left'

@[simps]
instance right (f g : a ⟶ b) [LiftHom f] [LiftHom g] [BicategoricalCoherence f g] :
    BicategoricalCoherence (f ≫ 𝟙 b) g :=
  ⟨(ρ_ f).Hom ≫ BicategoricalCoherence.hom f g⟩
#align category_theory.bicategory.bicategorical_coherence.right CategoryTheory.Bicategory.BicategoricalCoherence.right

@[simps]
instance right' (f g : a ⟶ b) [LiftHom f] [LiftHom g] [BicategoricalCoherence f g] :
    BicategoricalCoherence f (g ≫ 𝟙 b) :=
  ⟨BicategoricalCoherence.hom f g ≫ (ρ_ g).inv⟩
#align category_theory.bicategory.bicategorical_coherence.right' CategoryTheory.Bicategory.BicategoricalCoherence.right'

@[simps]
instance assoc (f : a ⟶ b) (g : b ⟶ c) (h : c ⟶ d) (i : a ⟶ d) [LiftHom f] [LiftHom g] [LiftHom h]
    [LiftHom i] [BicategoricalCoherence (f ≫ g ≫ h) i] : BicategoricalCoherence ((f ≫ g) ≫ h) i :=
  ⟨(α_ f g h).Hom ≫ BicategoricalCoherence.hom (f ≫ g ≫ h) i⟩
#align category_theory.bicategory.bicategorical_coherence.assoc CategoryTheory.Bicategory.BicategoricalCoherence.assoc

@[simps]
instance assoc' (f : a ⟶ b) (g : b ⟶ c) (h : c ⟶ d) (i : a ⟶ d) [LiftHom f] [LiftHom g] [LiftHom h]
    [LiftHom i] [BicategoricalCoherence i (f ≫ g ≫ h)] : BicategoricalCoherence i ((f ≫ g) ≫ h) :=
  ⟨BicategoricalCoherence.hom i (f ≫ g ≫ h) ≫ (α_ f g h).inv⟩
#align category_theory.bicategory.bicategorical_coherence.assoc' CategoryTheory.Bicategory.BicategoricalCoherence.assoc'

end BicategoricalCoherence

/-- Construct an isomorphism between two objects in a bicategorical category
out of unitors and associators. -/
def bicategoricalIso (f g : a ⟶ b) [LiftHom f] [LiftHom g] [BicategoricalCoherence f g] : f ≅ g :=
  asIso (BicategoricalCoherence.hom f g)
#align category_theory.bicategory.bicategorical_iso CategoryTheory.Bicategory.bicategoricalIso

/-- Compose two morphisms in a bicategorical category,
inserting unitors and associators between as necessary. -/
def bicategoricalComp {f g h i : a ⟶ b} [LiftHom g] [LiftHom h] [BicategoricalCoherence g h]
    (η : f ⟶ g) (θ : h ⟶ i) : f ⟶ i :=
  η ≫ BicategoricalCoherence.hom g h ≫ θ
#align category_theory.bicategory.bicategorical_comp CategoryTheory.Bicategory.bicategoricalComp

-- mathport name: bicategorical_comp
scoped[Bicategory] infixr:80 " ⊗≫ " => CategoryTheory.Bicategory.bicategoricalComp

-- type as \ot \gg
/-- Compose two isomorphisms in a bicategorical category,
inserting unitors and associators between as necessary. -/
def bicategoricalIsoComp {f g h i : a ⟶ b} [LiftHom g] [LiftHom h] [BicategoricalCoherence g h]
    (η : f ≅ g) (θ : h ≅ i) : f ≅ i :=
  η ≪≫ asIso (BicategoricalCoherence.hom g h) ≪≫ θ
#align category_theory.bicategory.bicategorical_iso_comp CategoryTheory.Bicategory.bicategoricalIsoComp

-- mathport name: bicategorical_iso_comp
scoped[Bicategory] infixr:80 " ≪⊗≫ " => CategoryTheory.Bicategory.bicategoricalIsoComp

-- type as \ot \gg
example {f' : a ⟶ d} {f : a ⟶ b} {g : b ⟶ c} {h : c ⟶ d} {h' : a ⟶ d} (η : f' ⟶ f ≫ g ≫ h)
    (θ : (f ≫ g) ≫ h ⟶ h') : f' ⟶ h' :=
  η ⊗≫ θ

-- To automatically insert unitors/associators at the beginning or end,
-- you can use `η ⊗≫ 𝟙 _`
example {f' : a ⟶ d} {f : a ⟶ b} {g : b ⟶ c} {h : c ⟶ d} (η : f' ⟶ (f ≫ g) ≫ h) : f' ⟶ f ≫ g ≫ h :=
  η ⊗≫ 𝟙 _

@[simp]
theorem bicategoricalComp_refl {f g h : a ⟶ b} (η : f ⟶ g) (θ : g ⟶ h) : η ⊗≫ θ = η ≫ θ :=
  by
  dsimp [bicategorical_comp]
  simp
#align category_theory.bicategory.bicategorical_comp_refl CategoryTheory.Bicategory.bicategoricalComp_refl

end CategoryTheory.Bicategory

open CategoryTheory.Bicategory

namespace Tactic

/- ./././Mathport/Syntax/Translate/Tactic/Mathlib/Core.lean:38:34: unsupported: setup_tactic_parser -/
/- ./././Mathport/Syntax/Translate/Expr.lean:330:4: warning: unsupported (TODO): `[tacs] -/
-- failed to format: unknown constant 'term.pseudo.antiquot'
/-- Coherence tactic for bicategories. -/ unsafe
  def
    bicategorical_coherence
    : tactic Unit
    :=
      focus1
        do
          let o ← get_options
            set_options <| o `class.instance_max_depth 128
            try sorry
            let q( $ ( lhs ) = $ ( rhs ) ) ← target
            to_expr
                `
                  `(
                    ( FreeBicategory.lift ( Prefunctor.id _ ) ) . zipWith
                        ( LiftHom₂.lift $ ( lhs ) )
                      =
                      ( FreeBicategory.lift ( Prefunctor.id _ ) ) . zipWith
                        ( LiftHom₂.lift $ ( rhs ) )
                    )
              >>=
              tactic.change
            congr
#align tactic.bicategorical_coherence tactic.bicategorical_coherence

namespace Bicategory

/- ./././Mathport/Syntax/Translate/Expr.lean:330:4: warning: unsupported (TODO): `[tacs] -/
/-- Simp lemmas for rewriting a 2-morphism into a normal form. -/
unsafe def whisker_simps : tactic Unit :=
  sorry
#align tactic.bicategory.whisker_simps tactic.bicategory.whisker_simps

namespace Coherence

-- We have unused typeclass arguments here.
-- They are intentional, to ensure that `simp only [assoc_lift_hom₂]` only left associates
-- bicategorical structural morphisms.
/-- Auxiliary simp lemma for the `coherence` tactic:
this move brackets to the left in order to expose a maximal prefix
built out of unitors and associators.
-/
@[nolint unused_arguments]
theorem assoc_liftHom₂ {f g h i : a ⟶ b} [LiftHom f] [LiftHom g] [LiftHom h] (η : f ⟶ g) (θ : g ⟶ h)
    (ι : h ⟶ i) [LiftHom₂ η] [LiftHom₂ θ] : η ≫ θ ≫ ι = (η ≫ θ) ≫ ι :=
  (Category.assoc _ _ _).symm
#align tactic.bicategory.coherence.assoc_lift_hom₂ Tactic.Bicategory.Coherence.assoc_liftHom₂

end Coherence

end Bicategory

end Tactic


/- -----------------------------------------------------------------------
Facts about (fin n).
----------------------------------------------------------------------- -/

import .c0_misc

namespace qp
namespace stdaux

/-! #brief Helper for showing two fin's are heq.
-/
theorem fin.heq
    : ∀ {N₁ N₂ : ℕ}
        (ωN : N₁ = N₂)
        {n₁ : fin N₁} {n₂ : fin N₂}
        (ωn : n₁^.val = n₂^.val)
      , n₁ == n₂
| N .(N) (eq.refl .(N)) (fin.mk n ω₁) (fin.mk .(n) ω₂) (eq.refl .(n)) := heq.refl _

/-! #brief The value held within a casted fin.
-/
theorem fin.cast_val
    : ∀ {N₁ N₂ : ℕ} (ωN : N₁ = N₂)
        (n : fin N₁)
      , (cast (congr_arg fin ωN) n)^.val = n^.val
| N .(N) (eq.refl .(N)) n := rfl

/-! #brief Alternate constructor for fin.
-/
definition fin_of
    {N : ℕ} (n : ℕ)
    : fin (nat.succ (N + n))
:= { val := n
   , is_lt := by calc n   < nat.succ n       : nat.less_than_or_equal.refl _
                      ... ≤ nat.succ (N + n) : nat.succ_le_succ (nat.le_add_left n N)
   }

/-! #brief An esoteric but sometimes handy constructor.
-/
definition fin.intro_zero {N : ℕ} (ωN : ¬ 0 = N)
    : fin N
:= { val := 0
   , is_lt := begin
                cases N with N,
                { exact false.elim (ωN rfl) },
                { exact (fin_of 0)^.is_lt}
              end
   }

/-! #brief Adding a constant to a fin.
-/
definition fin.add {N : ℕ} (n : fin N) (m : ℕ)
    : fin (N + m)
:= { val := n^.val + m
   , is_lt := nat.add_lt_add_right n^.is_lt m
   }

/-! #brief Zero is in fin (nat.succ n).
-/
definition fin.zero {n : ℕ}
    : fin (nat.succ n)
:= fin_of 0

/-! #brief One is in fin (nat.succ (nat.succ n)).
-/
definition fin.one {n : ℕ}
    : fin (nat.succ (nat.succ n))
:= fin_of 1

/-! #brief fin 0 is uninhabited.
-/
definition {ℓ} fin.zero_elim {A : Sort ℓ}
    : fin 0 → A
| (fin.mk n ω) := false.rec A begin cases ω end

/-! #brief fin 1 is uniquely inhabited.
-/
definition fin.zero_uniq
    : ∀ (n : fin 1)
      , n = fin.zero
| (fin.mk 0 ω) := rfl
| (fin.mk (nat.succ n) ω) := begin cases ω, cases a end

/-! #brief Enumerate a finite function.
-/
definition {ℓ} fin.enum {A : Type ℓ}
    : ∀ {N : ℕ} (f : fin N → A)
      , list A
| 0 f := []
| (nat.succ N) f:= f (fin_of 0) :: @fin.enum N (λ n, f { val := nat.succ n^.val, is_lt := nat.succ_le_succ n^.is_lt })

/-! #brief Length of an enum.
-/
theorem {ℓ} list.length_enum {A : Type ℓ}
    : ∀ {N : ℕ} (f : fin N → A)
      , list.length (fin.enum f) = N
| 0 f := rfl
| (nat.succ N) f := congr_arg nat.succ (@list.length_enum N _)

/-! #brief Action of casting on a fin.
-/
theorem fin.cast
    : ∀ {N₁ N₂ : ℕ}
        {ω : N₁ = N₂}
        {n : fin N₁}
      , cast (congr_arg fin ω) n = { val := n^.val, is_lt := cast (by subst ω) n^.is_lt }
| N .(N) (eq.refl .(N)) n := fin.eq_of_veq rfl

/-! #brief Get the nth element out of a list.
-/
definition {ℓ} list.get {A : Type ℓ}
    : ∀ (aa : list A) (n : fin (list.length aa))
      , A
| [] n := fin.zero_elim n
| (a :: aa) (fin.mk 0 ω) := a
| (a :: aa) (fin.mk (nat.succ n) ω) := list.get aa { val := n, is_lt := nat.lt_of_succ_lt_succ ω }

/-! #brief Action of list.get on fin.enum.
-/
theorem {ℓ} list.get_enum {A : Type ℓ}
    : ∀ {N : ℕ} {f : fin N → A}
        {n : fin (list.length (fin.enum f))}
      , list.get (fin.enum f) n = f (cast (congr_arg fin (list.length_enum f)) n)
| 0 f n := fin.zero_elim n
| (nat.succ N) f (fin.mk 0 ω)
:= begin
     apply congr_arg f,
     apply eq_of_heq,
     refine heq.trans _ (heq.symm (cast_heq _ _)),
     apply fin.heq (eq.symm (list.length_enum f)),
     trivial
   end
| (nat.succ N) f (fin.mk (nat.succ n) ω)
:= begin
     apply eq.trans (@list.get_enum N _ _),
     apply congr_arg f,
     apply eq_of_heq,
     refine heq.trans _ (heq.symm (cast_heq _ _)),
     apply fin.heq (eq.symm (list.length_enum f)),
     apply congr_arg nat.succ,
     apply fin.cast_val
   end

/-! #brief Action of list.get on fin.enum.
-/
theorem {ℓ} list.get_enum' {A : Type ℓ}
    {N : ℕ} {f : fin N → A}
    : list.get (fin.enum f) == f
:= begin
     refine hfunext (congr_arg fin (list.length_enum f)) _,
     intro n,
     apply list.get_enum
   end

@[simp] theorem {ℓ} list.get.simp {A : Type ℓ}
    (a : A) (aa : list A)
    (n : ℕ) (ωn : nat.succ n < list.length (a :: aa))
    : list.get (a :: aa) { val := nat.succ n, is_lt := ωn }
       = list.get aa { val := n, is_lt := nat.lt_of_succ_lt_succ ωn }
:= rfl

/-! #brief A handy lemma about lengths of lists.
-/
lemma {ℓ} list.length.grow_left {A : Type ℓ}
    {aa₁ aa₂ : list A}
    {n : ℕ}
    (ωn : n < list.length aa₁)
    : n < list.length (aa₁ ++ aa₂)
:= by calc n   < list.length aa₁                   : ωn
           ... ≤ list.length aa₁ + list.length aa₂ : nat.le_add_right _ _
           ... = list.length (aa₁ ++ aa₂)          : by rw list.length_append

/-! #brief A handy lemma about lengths of lists.
-/
lemma {ℓ} list.length.grow_right {A : Type ℓ}
    {aa₁ aa₂ : list A}
    {n : ℕ}
    (ωn : n < list.length aa₂)
    : n + list.length aa₁ < list.length (aa₁ ++ aa₂)
:= by calc n + list.length aa₁
               = list.length aa₁ + n               : by rw nat.add_comm
           ... < list.length aa₁ + list.length aa₂ : nat.add_lt_add_left ωn _
           ... = list.length (aa₁ ++ aa₂)          : by rw list.length_append

/-! #brief Action of list.get on list.append.
-/
theorem {ℓ} list.get_append_left {A : Type ℓ}
    : ∀ {aa₁ aa₂ : list A}
        {n : ℕ} {ωn : n < list.length aa₁}
      , list.get (aa₁ ++ aa₂) (fin.mk n (list.length.grow_left ωn))
         = list.get aa₁ (fin.mk n ωn)
| [] aa₂ n ωn := fin.zero_elim (fin.mk n ωn)
| (a :: aa) aa₂ 0 ω0 := rfl
| (a :: aa) aa₂ (nat.succ n) ωn
:= begin
     refine eq.trans (list.get.simp _ _ _ _) _,
     refine eq.trans _ (eq.symm (list.get.simp _ _ _ _)),
     apply list.get_append_left
   end

/-! #brief Action of list.get on list.append.
-/
theorem {ℓ} list.get_append_right {A : Type ℓ}
    : ∀ {aa₁ aa₂ : list A}
        {n : ℕ} {ωn : n < list.length aa₂}
      , list.get (aa₁ ++ aa₂) (fin.mk (n + list.length aa₁) (list.length.grow_right ωn))
         = list.get aa₂ (fin.mk n ωn)
| [] aa₂ n ωn := rfl
| (a₁ :: aa₁) [] n ωn := fin.zero_elim (fin.mk n ωn)
| (a₁ :: aa₁) (a₂ :: aa₂) 0 ω0
:= begin
     refine eq.trans (list.get.simp _ _ _ _) _,
     apply list.get_append_right
   end
| (a₁ :: aa₁) (a₂ :: aa₂) (nat.succ n) ωn
:= begin
     refine eq.trans (list.get.simp _ _ _ _) _,
     refine eq.trans _ (eq.symm (list.get.simp _ _ _ _)),
     apply list.get_append_right
   end

/-! #brief Action of list.get on list.map.
-/
theorem {ℓ₁ ℓ₂} list.get_map {A : Type ℓ₁} {B : Type ℓ₂}
    {f : A → B}
    : ∀ {aa : list A}
        {n : ℕ}
        {ωn : n < list.length aa}
        (ω : list.length aa = list.length (list.map f aa))
      , f (list.get aa { val := n, is_lt := ωn })
         = list.get (list.map f aa) { val := n, is_lt := cast begin rw ω end ωn }
| [] n ωn ω := by cases ωn
| (a :: aa) 0 ω0 ω := rfl
| (a :: aa) (nat.succ n) ωn ω
:= begin
     dsimp [list.map],
     simp,
     apply list.get_map,
     apply nat.succ.inj,
     exact ω
   end

/-! #brief Action of list.get on list.repeat.
-/
theorem {ℓ} list.get_repeat {A : Type ℓ}
    {a : A}
    : ∀ {N : ℕ} {n : fin (list.length (list.repeat a N))}
      , a = list.get (list.repeat a N) n
:= sorry

end stdaux
end qp


/-
Copyright (c) 2021 Yaël Dillies, Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies, Bhavik Mehta
-/
import analysis.convex.topology
import combinatorics.simplicial_complex.extreme
import order.filter.archimedean

/-!
# Definitions and lemmas about individual simplices

These are phrased in terms of finite sets of points, and the assumption of affine independence
(ie non-degeneracy) is added to theorems.
-/

open_locale big_operators classical
open set

variables {E : Type*} [normed_group E] [normed_space ℝ E] {x : E} {X Y : finset E}
-- variables {m : ℕ}
-- local notation `E'` := fin m → ℝ

/--
The combinatorial frontier of a simplex as a subspace.
-/
def combi_frontier (X : finset E) :
  set E :=
⋃ Y ⊂ X, convex_hull ↑Y

lemma mem_combi_frontier_iff :
  x ∈ combi_frontier X ↔ ∃ Y, Y ⊂ X ∧ x ∈ convex_hull (Y : set E) :=
by simp [combi_frontier]

lemma combi_frontier_singleton :
  combi_frontier ({x} : finset E) = ∅ :=
begin
  apply set.eq_empty_of_subset_empty,
  rintro y hy,
  rw mem_combi_frontier_iff at hy,
  obtain ⟨X, hX, hyX⟩ := hy,
  rw finset.eq_empty_of_ssubset_singleton hX at hyX,
  simp at hyX,
  exact hyX,
end

lemma combi_frontier_eq :
  combi_frontier X =
    {x : E | ∃ (w : E → ℝ) (hw₀ : ∀ y ∈ X, 0 ≤ w y) (hw₁ : ∑ y in X, w y = 1)
        (hw₂ : ∃ y ∈ X, w y = 0), X.center_mass w id = x} :=
begin
  ext x,
  simp_rw [combi_frontier, set.mem_Union, set.mem_set_of_eq],
  split,
  { simp only [and_imp, exists_prop, exists_imp_distrib],
    intros Y YX hx,
    rw [finset.convex_hull_eq, set.mem_set_of_eq] at hx,
    rcases hx with ⟨w, hw₀, hw₁, hx⟩,
    rcases finset.exists_of_ssubset YX with ⟨y, hyX, hyY⟩,
    let w' := λ z, if z ∈ Y then w z else 0,
    have hw'₁ : X.sum w' = 1,
    { rwa [←finset.sum_subset YX.1, finset.sum_extend_by_zero],
      simp only [ite_eq_right_iff],
      tauto },
    refine ⟨w', _, hw'₁, ⟨_, ‹y ∈ X›, _⟩, _⟩,
    { intros y hy,
      change 0 ≤ ite (y ∈ Y) (w y) 0,
      split_ifs,
      { apply hw₀ y ‹_› },
      { refl } },
    { apply if_neg ‹y ∉ Y› },
    rw ← finset.center_mass_subset id YX.1,
    { rw [finset.center_mass_eq_of_sum_1],
      { rw finset.center_mass_eq_of_sum_1 _ _ hw₁ at hx,
        rw ← hx,
        apply finset.sum_congr rfl,
        intros x hx,
        change ite _ _ _ • _ = _,
        rw if_pos hx },
      rwa finset.sum_extend_by_zero },
    intros i _ hi,
    apply if_neg hi },
  { simp only [and_imp, exists_prop, exists_imp_distrib],
    intros w hw₁ hw₂ y hy₁ hy₂ hy₃,
    refine ⟨X.erase y, finset.erase_ssubset hy₁, _⟩,
    rw [finset.convex_hull_eq, set.mem_set_of_eq],
    refine ⟨w, λ z hz, hw₁ z (X.erase_subset _ hz), _, _⟩,
    rw finset.sum_erase _ hy₂,
    apply hw₂,
    rwa finset.center_mass_subset _ (X.erase_subset _),
    intros i hi₁ hi₂,
    simp only [hi₁, and_true, not_not, finset.mem_erase] at hi₂,
    subst hi₂,
    apply hy₂ }
end

/--
The interior of a simplex as a subspace. Note this is *not* the same thing as the topological
interior of the underlying space.
-/
def combi_interior (X : finset E) :
  set E :=
convex_hull ↑X \ combi_frontier X

lemma combi_interior_singleton :
  combi_interior ({x} : finset E) = {x} :=
begin
  unfold combi_interior,
  rw combi_frontier_singleton,
  simp,
end

lemma combi_interior_subset_positive_weighings :
  combi_interior X ⊆
    {x : E | ∃ (w : E → ℝ) (hw₀ : ∀ y ∈ X, 0 < w y) (hw₁ : ∑ y in X, w y = 1),
      X.center_mass w id = x} :=
begin
  rw [combi_interior, finset.convex_hull_eq, combi_frontier_eq],
  rintro x,
  simp only [not_exists, and_imp, not_and, mem_set_of_eq, mem_diff, exists_imp_distrib],
  rintro w hw₁ hw₂ hw₃ q,
  refine ⟨w, λ y hy, _, hw₂, hw₃⟩,
  exact lt_of_le_of_ne (hw₁ _ hy) (ne.symm (λ t, q w hw₁ hw₂ y hy t hw₃))
end

lemma combi_interior_eq (hX : affine_independent ℝ (λ p, p : (X : set E) → E)) :
  combi_interior X =
    {x : E | ∃ (w : E → ℝ) (hw₀ : ∀ y ∈ X, 0 < w y) (hw₁ : ∑ y in X, w y = 1),
      X.center_mass w id = x} :=
begin
  apply subset.antisymm combi_interior_subset_positive_weighings,
  rintro x,
  rw [combi_interior, finset.convex_hull_eq, combi_frontier_eq],
  simp only [not_exists, and_imp, not_and, mem_set_of_eq, mem_diff, exists_imp_distrib],
  intros w hw₁ hw₂ hw₃,
  refine ⟨⟨w, λ y hy, le_of_lt (hw₁ y hy), hw₂, hw₃⟩, _⟩,
  intros w' hw'₁ hw'₂ y hy₁ hy₂ hw'₃,
  rw ← hw₃ at hw'₃,
  rw (unique_combination hX w' w hw'₂ hw₂ hw'₃) y hy₁ at hy₂,
  exact ne_of_gt (hw₁ y hy₁) hy₂
end

lemma centroid_mem_combi_interior (hX : affine_independent ℝ (λ p, p : (X : set E) → E))
  (hXnonempty : X.nonempty) :
  X.centroid ℝ id ∈ combi_interior X :=
begin
  rw finset.centroid_def,
  have hXweights := X.sum_centroid_weights_eq_one_of_nonempty ℝ hXnonempty,
  rw affine_combination_eq_center_mass hXweights,
  rw combi_interior_eq hX,
  refine ⟨_, _, hXweights, rfl⟩,
  intros y hy,
  simpa [finset.card_pos] using hXnonempty,
end

lemma nonempty_combi_interior_of_nonempty (hX : affine_independent ℝ (λ p, p : (X : set E) → E))
  (hXnonempty : X.nonempty) :
  (combi_interior X).nonempty :=
⟨X.centroid ℝ id, centroid_mem_combi_interior hX hXnonempty⟩

lemma combi_interior_subset_convex_hull :
  combi_interior X ⊆ convex_hull ↑X :=
diff_subset _ _

lemma combi_interior.inj (hX : affine_independent ℝ (λ p, p : (X : set E) → E))
  (hY : affine_independent ℝ (λ p, p : (Y : set E) → E)) (h : combi_interior X = combi_interior Y) :
  X = Y := sorry

lemma is_closed_convex_hull :
  is_closed (convex_hull (X : set E)) :=
X.finite_to_set.is_closed_convex_hull

lemma is_closed_combi_frontier :
  is_closed (combi_frontier X) :=
begin
  apply is_closed_bUnion,
  { suffices : set.finite {Y | Y ⊆ X},
    { exact this.subset (λ i h, h.1) },
    convert X.powerset.finite_to_set using 1,
    ext,
    simp },
  { intros i hi,
    apply is_closed_convex_hull }
end

lemma subset_closure_combi_interior (hX : affine_independent ℝ (λ p, p : (X : set E) → E)) :
  (X : set E) ⊆ closure (combi_interior X) :=
begin
  rintro x (hx : x ∈ X),
  apply sequential_closure_subset_closure,
  have hXnonempty : X.nonempty := ⟨x, hx⟩,
  have centroid_weights : ∑ (i : E) in X, finset.centroid_weights ℝ X i = 1,
  { apply finset.sum_centroid_weights_eq_one_of_nonempty ℝ _ hXnonempty },
  refine ⟨_, _, _⟩,
  { intro n,
    apply ((n:ℝ)+2)⁻¹ • X.centroid ℝ id + (1-((n:ℝ)+2)⁻¹) • x },
  { intro n,
    rw finset.centroid_def,
    rw affine_combination_eq_center_mass _,
    { rw ←id.def x,
      rw ←finset.center_mass_ite_eq _ _ id hx,
      rw finset.center_mass_segment,
      { rw combi_interior_eq hX,
        refine ⟨_, _, _, rfl⟩,
        { simp only [mul_boole, finset.centroid_weights_apply],
          intros y hy,
          apply add_pos_of_pos_of_nonneg,
          { apply mul_pos,
            { rw inv_pos,
              norm_cast,
              simp, },
            { rw inv_pos,
              norm_cast,
              rwa finset.card_pos } },
          { split_ifs,
            { rw sub_nonneg,
              apply inv_le_one,
              norm_cast,
              apply nat.succ_pos },
            { refl } } },
        rw [finset.sum_add_distrib, ←finset.mul_sum, centroid_weights, ←finset.mul_sum,
          finset.sum_boole, finset.filter_eq],
        simp [hx] },
      { apply centroid_weights },
      { simp [finset.sum_boole, finset.filter_eq, hx] },
      { simp only [add_sub_cancel'_right] } },
    apply finset.sum_centroid_weights_eq_one_of_nonempty ℝ _ hXnonempty },
  { rw tendsto_iff_norm_tendsto_zero,
    convert_to filter.tendsto (λ (e:ℕ), ((e:ℝ)+2)⁻¹ * ∥X.centroid ℝ id - x∥) filter.at_top _,
    { ext n,
      rw [add_sub_assoc, sub_smul, sub_right_comm, one_smul, sub_self, zero_sub, ←smul_neg,
        ←smul_add, norm_smul_of_nonneg, ←sub_eq_add_neg],
      rw inv_nonneg,
      norm_cast,
      apply nat.zero_le },
    suffices : filter.tendsto (λ (e : ℕ), ((↑(e + 2):ℝ))⁻¹) filter.at_top (nhds 0),
    { simpa using this.mul_const _ },
    refine tendsto_inv_at_top_zero.comp _,
    rw tendsto_coe_nat_at_top_iff,
    apply filter.tendsto_add_at_top_nat }
end

lemma convex_combi_interior (hX : affine_independent ℝ (λ p, p : (X : set E) → E)) :
  convex (combi_interior X) :=
begin
  rw convex_iff_forall_pos,
  intros x y hx hy t₁ t₂ ht₁ ht₂ h,
  rw combi_interior_eq hX at hx hy ⊢,
  rcases hx with ⟨h₁, h₂, h₃, rfl⟩,
  rcases hy with ⟨h₄, h₅, h₆, rfl⟩,
  refine ⟨λ x, t₁ * h₁ x + t₂ * h₄ x, λ x hx, _, _, _⟩,
  { exact add_pos (mul_pos ht₁ (h₂ x hx)) (mul_pos ht₂ (h₅ x hx)) },
  { rw [finset.sum_add_distrib, ←finset.mul_sum, ←finset.mul_sum, h₃, h₆],
    simp [h] },
  rw finset.center_mass_segment _ _ _ _ h₃ h₆ _ _ h,
end

-- Affine indep is necessary, since if not combi_interior can be empty
lemma closure_combi_interior_eq_convex_hull (hX : affine_independent ℝ (λ p, p : (X : set E) → E)) :
  closure (combi_interior X) = convex_hull (X : set E) :=
begin
  apply set.subset.antisymm,
  { rw is_closed.closure_subset_iff is_closed_convex_hull,
    apply combi_interior_subset_convex_hull },
  refine convex_hull_min (subset_closure_combi_interior hX) _,
  apply convex.closure,
  apply convex_combi_interior hX,
end

lemma combi_frontier_subset_convex_hull :
  combi_frontier X ⊆ convex_hull ↑X :=
bUnion_subset (λ Y hY, convex_hull_mono hY.1)

lemma convex_hull_eq_interior_union_combi_frontier :
  convex_hull ↑X = combi_interior X ∪ combi_frontier X :=
(sdiff_union_of_subset combi_frontier_subset_convex_hull).symm

lemma convex_hull_subset_convex_hull_of_combi_interior_subset_combi_interior
  (hX : affine_independent ℝ (λ p, p : (X : set E) → E))
  (hY : affine_independent ℝ (λ p, p : (Y : set E) → E)) :
  combi_interior X ⊆ combi_interior Y → convex_hull (X : set E) ⊆ convex_hull (Y : set E) :=
begin
  rw ← closure_combi_interior_eq_convex_hull hX,
  rw ← closure_combi_interior_eq_convex_hull hY,
  intro h,
  apply closure_mono h,
end

lemma simplex_combi_interiors_cover :
  convex_hull ↑X = ⋃ (Y ⊆ X), combi_interior Y :=
begin
  apply subset.antisymm _ _,
  { apply X.strong_induction_on,
    rintro s ih x hx,
    by_cases x ∈ combi_frontier s,
    { rw [combi_frontier] at h,
      simp only [exists_prop, set.mem_Union] at h,
      obtain ⟨t, st, ht⟩ := h,
      specialize ih _ st ht,
      simp only [exists_prop, set.mem_Union] at ⊢ ih,
      obtain ⟨Z, Zt, hZ⟩ := ih,
      exact ⟨_, subset.trans Zt st.1, hZ⟩ },
    { exact subset_bUnion_of_mem (λ _ t, t) ⟨hx, h⟩ } },
  { exact bUnion_subset (λ Y hY, subset.trans (diff_subset _ _) (convex_hull_mono hY)) },
end

/- combi_interior X is the topological interior iff X is of dimension m -/
lemma interiors_agree_of_full_dimensional [finite_dimensional ℝ E]
  (hX : affine_independent ℝ (λ p, p : (X : set E) → E))
  (hXcard : X.card = finite_dimensional.finrank ℝ E + 1) :
  combi_interior X = interior (convex_hull ↑X) :=
begin
  --rw ← closure_combi_interior_eq_convex_hull,
  unfold combi_interior,
  sorry
end

lemma frontiers_agree_of_full_dimensional [finite_dimensional ℝ E]
  (hXcard : X.card = finite_dimensional.finrank ℝ E + 1) :
  combi_frontier X = frontier (convex_hull ↑X) :=
begin
  ext x,
  split,
  {
    unfold combi_frontier,
    simp_rw set.mem_Union,
    rintro ⟨Y, hYX, hx⟩,
    split,
    { exact subset_closure (convex_hull_mono hYX.1 hx) },
    {
      rintro h,
      sorry,
      --have :=  finset.convex_hull_eq,
     }
  },
  {
    rintro ⟨h, g⟩,
    sorry
  }
end


def numChars (s : String) : Nat :=
  go s.iter
where
  go (i : String.Iterator) : Nat :=
    if h : i.hasNext then
      go i.next + 1
    else
      0

#eval numChars "aαc"

example : numChars "aαc" = 3 := by
  rfl'

def numChars2 (s : String) : Nat :=
  go s.iter
where
  go (i : String.Iterator) : Nat :=
    match h : i.hasNext with
    | true  => go i.next + 1
    | false => 0

example : numChars2 "aαc" = 3 := by
  rfl'


import ..fglib
import ..basic
import .vector
import .square_matrix

namespace FG

inductive block_diagonal : Type
| empty : block_diagonal
| block {n : ℕ} : square_matrix n → block_diagonal → block_diagonal

namespace block_diagonal

@[simp] def length : block_diagonal → ℕ
| empty := 0
| (block a b) := a.length + b.length

def to_func : block_diagonal → matrix_func
| empty _ _ := 0
| (block a b) i j := if i < a.length then
    if j < a.length then a i j else 0
  else
    if j < a.length then 0 else
      b.to_func (i - a.length) (j - a.length)

@[simp] def to_matrix (A : block_diagonal) :
  square_matrix (length A - 1)
| ⟨i, _⟩ ⟨j, _⟩ := A.to_func i j

@[simp] def to_matrix_n (A : block_diagonal) (n : ℕ) :
  square_matrix n
| ⟨i, _⟩ ⟨j, _⟩ := A.to_func i j

/- Have to reduce over block_diagonal instead of block_diagonal since the length is unknown-/
def reduce {α : Type} (f : α → block_diagonal → α)
  : α → block_diagonal → α
| x empty       := x
| x (block a b) := reduce (f x (block a b)) b

@[simp] def det (A : block_diagonal) : ℂ :=
  A.to_matrix.det

/- A block matrix's determinant is the product of determinants of all the blocks -/
@[simp] lemma block_det (A : block_diagonal) :
  A.det = A.reduce (λ(x B), match B with
    | empty       := x
    | (block a b) := x * a.det
  end) 1 :=
sorry

@[simp] def is_invertible (A : block_diagonal) : Prop :=
  A.to_matrix.is_invertible

/- A block matrix is invertible if and only if all the blocks are invertible -/
@[simp] lemma block_invertible (A : block_diagonal) :
  A.is_invertible ↔ A.reduce (λ(x B), match B with
    | empty       := x
    | (block a b) := x ∧ a.is_invertible
  end) true :=
begin
  rw iff_eq_eq,
  calc A.is_invertible = A.to_matrix.is_invertible
      : by refl
    ... = (A.to_matrix.det ≠ 0)
      : by rw square_matrix.det_ne_zero_iff
    ... = (A.det ≠ 0)
      : by refl
    ... = (A.reduce (λ(x B), match B with
        | empty       := x
        | (block a b) := x * a.det
      end) 1 ≠ 0)
      : by rw block_det
    ... = A.reduce (λ(x B), match B with
        | empty       := x
        | (block a b) := x ∧ a.is_invertible
      end) true
      : by sorry
end

end block_diagonal

end FG


/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import algebra.big_operators.order

/-!
# Cauchy sequences

A basic theory of Cauchy sequences, used in the construction of the reals and p-adic numbers. Where
applicable, lemmas that will be reused in other contexts have been stated in extra generality.

There are other "versions" of Cauchyness in the library, in particular Cauchy filters in topology.
This is a concrete implementation that is useful for simplicity and computability reasons.

## Important definitions

* `is_absolute_value`: a type class stating that `f : β → α` satisfies the axioms of an abs val
* `is_cau_seq`: a predicate that says `f : ℕ → β` is Cauchy.
* `cau_seq`: the type of Cauchy sequences valued in type `β` with respect to an absolute value
  function `abv`.

## Tags

sequence, cauchy, abs val, absolute value
-/

open_locale big_operators

/-- A function `f` is an absolute value if it is nonnegative, zero only at 0, additive, and
multiplicative. -/
class is_absolute_value {α} [linear_ordered_field α]
  {β} [ring β] (f : β → α) : Prop :=
(abv_nonneg [] : ∀ x, 0 ≤ f x)
(abv_eq_zero [] : ∀ {x}, f x = 0 ↔ x = 0)
(abv_add [] : ∀ x y, f (x + y) ≤ f x + f y)
(abv_mul [] : ∀ x y, f (x * y) = f x * f y)

namespace is_absolute_value
variables {α : Type*} [linear_ordered_field α]
  {β : Type*} [ring β] (abv : β → α) [is_absolute_value abv]

theorem abv_zero : abv 0 = 0 := (abv_eq_zero abv).2 rfl

theorem abv_one [nontrivial β] : abv 1 = 1 :=
(mul_right_inj' $ mt (abv_eq_zero abv).1 one_ne_zero).1 $
by rw [← abv_mul abv, mul_one, mul_one]

theorem abv_pos {a : β} : 0 < abv a ↔ a ≠ 0 :=
by rw [lt_iff_le_and_ne, ne, eq_comm]; simp [abv_eq_zero abv, abv_nonneg abv]

theorem abv_neg (a : β) : abv (-a) = abv a :=
by rw [← mul_self_inj_of_nonneg (abv_nonneg abv _) (abv_nonneg abv _),
  ← abv_mul abv, ← abv_mul abv]; simp

theorem abv_sub (a b : β) : abv (a - b) = abv (b - a) :=
by rw [← neg_sub, abv_neg abv]

/-- `abv` as a `monoid_with_zero_hom`. -/
def abv_hom [nontrivial β] : monoid_with_zero_hom β α :=
⟨abv, abv_zero abv, abv_one abv, abv_mul abv⟩

theorem abv_inv
  {β : Type*} [field β] (abv : β → α) [is_absolute_value abv]
  (a : β) : abv a⁻¹ = (abv a)⁻¹ :=
(abv_hom abv).map_inv' a

theorem abv_div
  {β : Type*} [field β] (abv : β → α) [is_absolute_value abv]
  (a b : β) : abv (a / b) = abv a / abv b :=
(abv_hom abv).map_div a b

lemma abv_sub_le (a b c : β) : abv (a - c) ≤ abv (a - b) + abv (b - c) :=
by simpa [sub_eq_add_neg, add_assoc] using abv_add abv (a - b) (b - c)

lemma sub_abv_le_abv_sub (a b : β) : abv a - abv b ≤ abv (a - b) :=
sub_le_iff_le_add.2 $ by simpa using abv_add abv (a - b) b

lemma abs_abv_sub_le_abv_sub (a b : β) :
  abs (abv a - abv b) ≤ abv (a - b) :=
abs_sub_le_iff.2 ⟨sub_abv_le_abv_sub abv _ _,
  by rw abv_sub abv; apply sub_abv_le_abv_sub abv⟩

lemma abv_pow [nontrivial β] (abv : β → α) [is_absolute_value abv]
  (a : β) (n : ℕ) : abv (a ^ n) = abv a ^ n :=
(abv_hom abv).to_monoid_hom.map_pow a n

end is_absolute_value

instance abs_is_absolute_value {α} [linear_ordered_field α] :
  is_absolute_value (abs : α → α) :=
{ abv_nonneg  := abs_nonneg,
  abv_eq_zero := λ _, abs_eq_zero,
  abv_add     := abs_add,
  abv_mul     := abs_mul }

open is_absolute_value

theorem exists_forall_ge_and {α} [linear_order α] {P Q : α → Prop} :
  (∃ i, ∀ j ≥ i, P j) → (∃ i, ∀ j ≥ i, Q j) →
  ∃ i, ∀ j ≥ i, P j ∧ Q j
| ⟨a, h₁⟩ ⟨b, h₂⟩ := let ⟨c, ac, bc⟩ := exists_ge_of_linear a b in
  ⟨c, λ j hj, ⟨h₁ _ (le_trans ac hj), h₂ _ (le_trans bc hj)⟩⟩

section
variables {α : Type*} [linear_ordered_field α]
  {β : Type*} [ring β] (abv : β → α) [is_absolute_value abv]

theorem rat_add_continuous_lemma
  {ε : α} (ε0 : 0 < ε) : ∃ δ > 0, ∀ {a₁ a₂ b₁ b₂ : β},
  abv (a₁ - b₁) < δ → abv (a₂ - b₂) < δ → abv (a₁ + a₂ - (b₁ + b₂)) < ε :=
⟨ε / 2, half_pos ε0, λ a₁ a₂ b₁ b₂ h₁ h₂,
  by simpa [add_halves, sub_eq_add_neg, add_comm, add_left_comm, add_assoc]
    using lt_of_le_of_lt (abv_add abv _ _) (add_lt_add h₁ h₂)⟩

theorem rat_mul_continuous_lemma
  {ε K₁ K₂ : α} (ε0 : 0 < ε) :
  ∃ δ > 0, ∀ {a₁ a₂ b₁ b₂ : β}, abv a₁ < K₁ → abv b₂ < K₂ →
  abv (a₁ - b₁) < δ → abv (a₂ - b₂) < δ → abv (a₁ * a₂ - b₁ * b₂) < ε :=
begin
  have K0 : (0 : α) < max 1 (max K₁ K₂) := lt_of_lt_of_le zero_lt_one (le_max_left _ _),
  have εK := div_pos (half_pos ε0) K0,
  refine ⟨_, εK, λ a₁ a₂ b₁ b₂ ha₁ hb₂ h₁ h₂, _⟩,
  replace ha₁ := lt_of_lt_of_le ha₁ (le_trans (le_max_left _ K₂) (le_max_right 1 _)),
  replace hb₂ := lt_of_lt_of_le hb₂ (le_trans (le_max_right K₁ _) (le_max_right 1 _)),
  have := add_lt_add
    (mul_lt_mul' (le_of_lt h₁) hb₂ (abv_nonneg abv _) εK)
    (mul_lt_mul' (le_of_lt h₂) ha₁ (abv_nonneg abv _) εK),
  rw [← abv_mul abv, mul_comm, div_mul_cancel _ (ne_of_gt K0), ← abv_mul abv, add_halves] at this,
  simpa [mul_add, add_mul, sub_eq_add_neg, add_comm, add_left_comm]
    using lt_of_le_of_lt (abv_add abv _ _) this
end

theorem rat_inv_continuous_lemma
  {β : Type*} [field β] (abv : β → α) [is_absolute_value abv]
  {ε K : α} (ε0 : 0 < ε) (K0 : 0 < K) :
  ∃ δ > 0, ∀ {a b : β}, K ≤ abv a → K ≤ abv b →
  abv (a - b) < δ → abv (a⁻¹ - b⁻¹) < ε :=
begin
  have KK := mul_pos K0 K0,
  have εK := mul_pos ε0 KK,
  refine ⟨_, εK, λ a b ha hb h, _⟩,
  have a0 := lt_of_lt_of_le K0 ha,
  have b0 := lt_of_lt_of_le K0 hb,
  rw [inv_sub_inv ((abv_pos abv).1 a0) ((abv_pos abv).1 b0),
      abv_div abv, abv_mul abv, mul_comm, abv_sub abv,
      ← mul_div_cancel ε (ne_of_gt KK)],
  exact div_lt_div h
    (mul_le_mul hb ha (le_of_lt K0) (abv_nonneg abv _))
    (le_of_lt $ mul_pos ε0 KK) KK
end
end

/-- A sequence is Cauchy if the distance between its entries tends to zero. -/
def is_cau_seq {α : Type*} [linear_ordered_field α]
  {β : Type*} [ring β] (abv : β → α) (f : ℕ → β) : Prop :=
∀ ε > 0, ∃ i, ∀ j ≥ i, abv (f j - f i) < ε

namespace is_cau_seq
variables {α : Type*} [linear_ordered_field α]
  {β : Type*} [ring β] {abv : β → α} [is_absolute_value abv] {f : ℕ → β}

@[nolint ge_or_gt] -- see Note [nolint_ge]
theorem cauchy₂ (hf : is_cau_seq abv f) {ε : α} (ε0 : 0 < ε) :
  ∃ i, ∀ j k ≥ i, abv (f j - f k) < ε :=
begin
  refine (hf _ (half_pos ε0)).imp (λ i hi j k ij ik, _),
  rw ← add_halves ε,
  refine lt_of_le_of_lt (abv_sub_le abv _ _ _) (add_lt_add (hi _ ij) _),
  rw abv_sub abv, exact hi _ ik
end

theorem cauchy₃ (hf : is_cau_seq abv f) {ε : α} (ε0 : 0 < ε) :
  ∃ i, ∀ j ≥ i, ∀ k ≥ j, abv (f k - f j) < ε :=
let ⟨i, H⟩ := hf.cauchy₂ ε0 in ⟨i, λ j ij k jk, H _ _ (le_trans ij jk) ij⟩

end is_cau_seq

/-- `cau_seq β abv` is the type of `β`-valued Cauchy sequences, with respect to the absolute value
function `abv`. -/
def cau_seq {α : Type*} [linear_ordered_field α]
  (β : Type*) [ring β] (abv : β → α) : Type* :=
{f : ℕ → β // is_cau_seq abv f}

namespace cau_seq
variables {α : Type*} [linear_ordered_field α]

section ring
variables {β : Type*} [ring β] {abv : β → α}

instance : has_coe_to_fun (cau_seq β abv) := ⟨_, subtype.val⟩

@[simp] theorem mk_to_fun (f) (hf : is_cau_seq abv f) :
  @coe_fn (cau_seq β abv) _ ⟨f, hf⟩ = f := rfl

theorem ext {f g : cau_seq β abv} (h : ∀ i, f i = g i) : f = g :=
subtype.eq (funext h)

theorem is_cau (f : cau_seq β abv) : is_cau_seq abv f := f.2

theorem cauchy (f : cau_seq β abv) :
  ∀ {ε}, 0 < ε → ∃ i, ∀ j ≥ i, abv (f j - f i) < ε := f.2

/-- Given a Cauchy sequence `f`, create a Cauchy sequence from a sequence `g` with
the same values as `f`. -/
def of_eq (f : cau_seq β abv) (g : ℕ → β) (e : ∀ i, f i = g i) : cau_seq β abv :=
⟨g, λ ε, by rw [show g = f, from (funext e).symm]; exact f.cauchy⟩

variable [is_absolute_value abv]

@[nolint ge_or_gt] -- see Note [nolint_ge]
theorem cauchy₂ (f : cau_seq β abv) {ε} : 0 < ε →
  ∃ i, ∀ j k ≥ i, abv (f j - f k) < ε := f.2.cauchy₂

theorem cauchy₃ (f : cau_seq β abv) {ε} : 0 < ε →
  ∃ i, ∀ j ≥ i, ∀ k ≥ j, abv (f k - f j) < ε := f.2.cauchy₃

theorem bounded (f : cau_seq β abv) : ∃ r, ∀ i, abv (f i) < r :=
begin
  cases f.cauchy zero_lt_one with i h,
  let R := ∑ j in finset.range (i+1), abv (f j),
  have : ∀ j ≤ i, abv (f j) ≤ R,
  { intros j ij, change (λ j, abv (f j)) j ≤ R,
    apply finset.single_le_sum,
    { intros, apply abv_nonneg abv },
    { rwa [finset.mem_range, nat.lt_succ_iff] } },
  refine ⟨R + 1, λ j, _⟩,
  cases lt_or_le j i with ij ij,
  { exact lt_of_le_of_lt (this _ (le_of_lt ij)) (lt_add_one _) },
  { have := lt_of_le_of_lt (abv_add abv _ _)
      (add_lt_add_of_le_of_lt (this _ (le_refl _)) (h _ ij)),
    rw [add_sub, add_comm] at this, simpa }
end

theorem bounded' (f : cau_seq β abv) (x : α) : ∃ r > x, ∀ i, abv (f i) < r :=
let ⟨r, h⟩ := f.bounded in
⟨max r (x+1), lt_of_lt_of_le (lt_add_one _) (le_max_right _ _),
  λ i, lt_of_lt_of_le (h i) (le_max_left _ _)⟩

instance : has_add (cau_seq β abv) :=
⟨λ f g, ⟨λ i, (f i + g i : β), λ ε ε0,
  let ⟨δ, δ0, Hδ⟩ := rat_add_continuous_lemma abv ε0,
      ⟨i, H⟩ := exists_forall_ge_and (f.cauchy₃ δ0) (g.cauchy₃ δ0) in
  ⟨i, λ j ij, let ⟨H₁, H₂⟩ := H _ (le_refl _) in Hδ (H₁ _ ij) (H₂ _ ij)⟩⟩⟩

@[simp] theorem add_apply (f g : cau_seq β abv) (i : ℕ) : (f + g) i = f i + g i := rfl

variable (abv)

/-- The constant Cauchy sequence. -/
def const (x : β) : cau_seq β abv :=
⟨λ i, x, λ ε ε0, ⟨0, λ j ij, by simpa [abv_zero abv] using ε0⟩⟩

variable {abv}

local notation `const` := const abv

@[simp] theorem const_apply (x : β) (i : ℕ) : (const x : ℕ → β) i = x := rfl

theorem const_inj {x y : β} : (const x : cau_seq β abv) = const y ↔ x = y :=
⟨λ h, congr_arg (λ f:cau_seq β abv, (f:ℕ→β) 0) h, congr_arg _⟩

instance : has_zero (cau_seq β abv) := ⟨const 0⟩
instance : has_one (cau_seq β abv) := ⟨const 1⟩
instance : inhabited (cau_seq β abv) := ⟨0⟩

@[simp] theorem zero_apply (i) : (0 : cau_seq β abv) i = 0 := rfl
@[simp] theorem one_apply (i) : (1 : cau_seq β abv) i = 1 := rfl
@[simp] theorem const_zero : const 0 = 0 := rfl

theorem const_add (x y : β) : const (x + y) = const x + const y :=
ext $ λ i, rfl

instance : has_mul (cau_seq β abv) :=
⟨λ f g, ⟨λ i, (f i * g i : β), λ ε ε0,
  let ⟨F, F0, hF⟩ := f.bounded' 0, ⟨G, G0, hG⟩ := g.bounded' 0,
      ⟨δ, δ0, Hδ⟩ := rat_mul_continuous_lemma abv ε0,
      ⟨i, H⟩ := exists_forall_ge_and (f.cauchy₃ δ0) (g.cauchy₃ δ0) in
  ⟨i, λ j ij, let ⟨H₁, H₂⟩ := H _ (le_refl _) in
    Hδ (hF j) (hG i) (H₁ _ ij) (H₂ _ ij)⟩⟩⟩

@[simp] theorem mul_apply (f g : cau_seq β abv) (i : ℕ) : (f * g) i = f i * g i := rfl

theorem const_mul (x y : β) : const (x * y) = const x * const y :=
ext $ λ i, rfl

instance : has_neg (cau_seq β abv) :=
⟨λ f, of_eq (const (-1) * f) (λ x, -f x) (λ i, by simp)⟩

@[simp] theorem neg_apply (f : cau_seq β abv) (i) : (-f) i = -f i := rfl

theorem const_neg (x : β) : const (-x) = -const x :=
ext $ λ i, rfl

instance : has_sub (cau_seq β abv) :=
⟨λ f g, of_eq (f + -g) (λ x, f x - g x) (λ i, by simp [sub_eq_add_neg])⟩

@[simp] theorem sub_apply (f g : cau_seq β abv) (i : ℕ) : (f - g) i = f i - g i := rfl

theorem const_sub (x y : β) : const (x - y) = const x - const y :=
ext $ λ i, rfl

instance : ring (cau_seq β abv) :=
by refine_struct
     { neg := has_neg.neg,
       add := (+),
       zero := (0 : cau_seq β abv),
       mul := (*),
       one := 1,
       sub := has_sub.sub,
       npow := @npow_rec _ ⟨1⟩ ⟨(*)⟩,
       nsmul := @nsmul_rec _ ⟨0⟩ ⟨(+)⟩,
       gsmul := @gsmul_rec _ ⟨0⟩ ⟨(+)⟩ ⟨has_neg.neg⟩ };
intros; try { refl }; apply ext;
simp [mul_add, mul_assoc, add_mul, add_comm, add_left_comm, sub_eq_add_neg]

instance {β : Type*} [comm_ring β] {abv : β → α} [is_absolute_value abv] :
  comm_ring (cau_seq β abv) :=
{ mul_comm := by intros; apply ext; simp [mul_left_comm, mul_comm],
  ..cau_seq.ring }

/-- `lim_zero f` holds when `f` approaches 0. -/
def lim_zero {abv : β → α} (f : cau_seq β abv) : Prop := ∀ ε > 0, ∃ i, ∀ j ≥ i, abv (f j) < ε

theorem add_lim_zero {f g : cau_seq β abv}
  (hf : lim_zero f) (hg : lim_zero g) : lim_zero (f + g)
| ε ε0 := (exists_forall_ge_and
    (hf _ $ half_pos ε0) (hg _ $ half_pos ε0)).imp $
  λ i H j ij, let ⟨H₁, H₂⟩ := H _ ij in
    by simpa [add_halves ε] using lt_of_le_of_lt (abv_add abv _ _) (add_lt_add H₁ H₂)

theorem mul_lim_zero_right (f : cau_seq β abv) {g}
  (hg : lim_zero g) : lim_zero (f * g)
| ε ε0 := let ⟨F, F0, hF⟩ := f.bounded' 0 in
  (hg _ $ div_pos ε0 F0).imp $ λ i H j ij,
  by have := mul_lt_mul' (le_of_lt $ hF j) (H _ ij) (abv_nonneg abv _) F0;
     rwa [mul_comm F, div_mul_cancel _ (ne_of_gt F0), ← abv_mul abv] at this

theorem mul_lim_zero_left {f} (g : cau_seq β abv)
  (hg : lim_zero f) : lim_zero (f * g)
| ε ε0 := let ⟨G, G0, hG⟩ := g.bounded' 0 in
  (hg _ $ div_pos ε0 G0).imp $ λ i H j ij,
  by have := mul_lt_mul'' (H _ ij) (hG j) (abv_nonneg abv _) (abv_nonneg abv _);
     rwa [div_mul_cancel _ (ne_of_gt G0), ← abv_mul abv] at this

theorem neg_lim_zero {f : cau_seq β abv} (hf : lim_zero f) : lim_zero (-f) :=
by rw ← neg_one_mul; exact mul_lim_zero_right _ hf

theorem sub_lim_zero {f g : cau_seq β abv}
  (hf : lim_zero f) (hg : lim_zero g) : lim_zero (f - g) :=
by simpa only [sub_eq_add_neg] using add_lim_zero hf (neg_lim_zero hg)

theorem lim_zero_sub_rev {f g : cau_seq β abv} (hfg : lim_zero (f - g)) : lim_zero (g - f) :=
by simpa using neg_lim_zero hfg

theorem zero_lim_zero : lim_zero (0 : cau_seq β abv)
| ε ε0 := ⟨0, λ j ij, by simpa [abv_zero abv] using ε0⟩

theorem const_lim_zero {x : β} : lim_zero (const x) ↔ x = 0 :=
⟨λ H, (abv_eq_zero abv).1 $
  eq_of_le_of_forall_le_of_dense (abv_nonneg abv _) $
  λ ε ε0, let ⟨i, hi⟩ := H _ ε0 in le_of_lt $ hi _ (le_refl _),
λ e, e.symm ▸ zero_lim_zero⟩

instance equiv : setoid (cau_seq β abv) :=
⟨λ f g, lim_zero (f - g),
⟨λ f, by simp [zero_lim_zero],
 λ f g h, by simpa using neg_lim_zero h,
 λ f g h fg gh, by simpa [sub_eq_add_neg, add_assoc] using add_lim_zero fg gh⟩⟩

lemma add_equiv_add {f1 f2 g1 g2 : cau_seq β abv} (hf : f1 ≈ f2) (hg : g1 ≈ g2) :
  f1 + g1 ≈ f2 + g2 :=
begin
  change lim_zero ((f1 + g1) - _),
  convert add_lim_zero hf hg using 1,
  simp only [sub_eq_add_neg, add_assoc],
  rw add_comm (-f2), simp only [add_assoc],
  congr' 2, simp
end

lemma neg_equiv_neg {f g : cau_seq β abv} (hf : f ≈ g) : -f ≈ -g :=
begin
  have hf : lim_zero _ := neg_lim_zero hf,
  show lim_zero (-f - -g),
  convert hf using 1, simp
end

theorem equiv_def₃ {f g : cau_seq β abv} (h : f ≈ g) {ε : α} (ε0 : 0 < ε) :
  ∃ i, ∀ j ≥ i, ∀ k ≥ j, abv (f k - g j) < ε :=
(exists_forall_ge_and (h _ $ half_pos ε0) (f.cauchy₃ $ half_pos ε0)).imp $
λ i H j ij k jk, let ⟨h₁, h₂⟩ := H _ ij in
by have := lt_of_le_of_lt (abv_add abv (f j - g j) _) (add_lt_add h₁ (h₂ _ jk));
   rwa [sub_add_sub_cancel', add_halves] at this

theorem lim_zero_congr {f g : cau_seq β abv} (h : f ≈ g) : lim_zero f ↔ lim_zero g :=
⟨λ l, by simpa using add_lim_zero (setoid.symm h) l,
 λ l, by simpa using add_lim_zero h l⟩

theorem abv_pos_of_not_lim_zero {f : cau_seq β abv} (hf : ¬ lim_zero f) :
  ∃ K > 0, ∃ i, ∀ j ≥ i, K ≤ abv (f j) :=
begin
  haveI := classical.prop_decidable,
  by_contra nk,
  refine hf (λ ε ε0, _),
  simp [not_forall] at nk,
  cases f.cauchy₃ (half_pos ε0) with i hi,
  rcases nk _ (half_pos ε0) i with ⟨j, ij, hj⟩,
  refine ⟨j, λ k jk, _⟩,
  have := lt_of_le_of_lt (abv_add abv _ _) (add_lt_add (hi j ij k jk) hj),
  rwa [sub_add_cancel, add_halves] at this
end

theorem of_near (f : ℕ → β) (g : cau_seq β abv)
  (h : ∀ ε > 0, ∃ i, ∀ j ≥ i, abv (f j - g j) < ε) : is_cau_seq abv f
| ε ε0 :=
  let ⟨i, hi⟩ := exists_forall_ge_and
    (h _ (half_pos $ half_pos ε0)) (g.cauchy₃ $ half_pos ε0) in
  ⟨i, λ j ij, begin
    cases hi _ (le_refl _) with h₁ h₂, rw abv_sub abv at h₁,
    have := lt_of_le_of_lt (abv_add abv _ _) (add_lt_add (hi _ ij).1 h₁),
    have := lt_of_le_of_lt (abv_add abv _ _) (add_lt_add this (h₂ _ ij)),
    rwa [add_halves, add_halves, add_right_comm,
         sub_add_sub_cancel, sub_add_sub_cancel] at this
  end⟩

lemma not_lim_zero_of_not_congr_zero {f : cau_seq _ abv} (hf : ¬ f ≈ 0) : ¬ lim_zero f :=
assume : lim_zero f,
have lim_zero (f - 0), by simpa,
hf this

lemma mul_equiv_zero  (g : cau_seq _ abv) {f : cau_seq _ abv} (hf : f ≈ 0) : g * f ≈ 0 :=
have lim_zero (f - 0), from hf,
have lim_zero (g*f), from mul_lim_zero_right _ $ by simpa,
show lim_zero (g*f - 0), by simpa

lemma mul_not_equiv_zero {f g : cau_seq _ abv} (hf : ¬ f ≈ 0) (hg : ¬ g ≈ 0) : ¬ (f * g) ≈ 0 :=
assume : lim_zero (f*g - 0),
have hlz : lim_zero (f*g), by simpa,
have hf' : ¬ lim_zero f, by simpa using (show ¬ lim_zero (f - 0), from hf),
have hg' : ¬ lim_zero g, by simpa using (show ¬ lim_zero (g - 0), from hg),
begin
  rcases abv_pos_of_not_lim_zero hf' with ⟨a1, ha1, N1, hN1⟩,
  rcases abv_pos_of_not_lim_zero hg' with ⟨a2, ha2, N2, hN2⟩,
  have : 0 < a1 * a2, from mul_pos ha1 ha2,
  cases hlz _ this with N hN,
  let i := max N (max N1 N2),
  have hN' := hN i (le_max_left _ _),
  have hN1' := hN1 i (le_trans (le_max_left _ _) (le_max_right _ _)),
  have hN1' := hN2 i (le_trans (le_max_right _ _) (le_max_right _ _)),
  apply not_le_of_lt hN',
  change _ ≤ abv (_ * _),
  rw is_absolute_value.abv_mul abv,
  apply mul_le_mul; try { assumption },
    { apply le_of_lt ha2 },
    { apply is_absolute_value.abv_nonneg abv }
end

theorem const_equiv {x y : β} : const x ≈ const y ↔ x = y :=
show lim_zero _ ↔ _, by rw [← const_sub, const_lim_zero, sub_eq_zero]

end ring

section comm_ring
variables {β : Type*} [comm_ring β] {abv : β → α} [is_absolute_value abv]

lemma mul_equiv_zero' (g : cau_seq _ abv) {f : cau_seq _ abv} (hf : f ≈ 0) : f * g ≈ 0 :=
by rw mul_comm; apply mul_equiv_zero _ hf

end comm_ring

section integral_domain
variables {β : Type*} [integral_domain β] (abv : β → α) [is_absolute_value abv]

lemma one_not_equiv_zero : ¬ (const abv 1) ≈ (const abv 0) :=
assume h,
have ∀ ε > 0, ∃ i, ∀ k, i ≤ k → abv (1 - 0) < ε, from h,
have h1 : abv 1 ≤ 0, from le_of_not_gt $
  assume h2 : 0 < abv 1,
  exists.elim (this _ h2) $ λ i hi,
    lt_irrefl (abv 1) $ by simpa using hi _ (le_refl _),
have h2 : 0 ≤ abv 1, from is_absolute_value.abv_nonneg _ _,
have abv 1 = 0, from le_antisymm h1 h2,
have (1 : β) = 0, from (is_absolute_value.abv_eq_zero abv).1 this,
absurd this one_ne_zero

end integral_domain

section field
variables {β : Type*} [field β] {abv : β → α} [is_absolute_value abv]

theorem inv_aux {f : cau_seq β abv} (hf : ¬ lim_zero f) :
  ∀ ε > 0, ∃ i, ∀ j ≥ i, abv ((f j)⁻¹ - (f i)⁻¹) < ε | ε ε0 :=
let ⟨K, K0, HK⟩ := abv_pos_of_not_lim_zero hf,
    ⟨δ, δ0, Hδ⟩ := rat_inv_continuous_lemma abv ε0 K0,
    ⟨i, H⟩ := exists_forall_ge_and HK (f.cauchy₃ δ0) in
⟨i, λ j ij, let ⟨iK, H'⟩ := H _ (le_refl _) in Hδ (H _ ij).1 iK (H' _ ij)⟩

/-- Given a Cauchy sequence `f` with nonzero limit, create a Cauchy sequence with values equal to
the inverses of the values of `f`. -/
def inv (f : cau_seq β abv) (hf : ¬ lim_zero f) : cau_seq β abv := ⟨_, inv_aux hf⟩

@[simp] theorem inv_apply {f : cau_seq β abv} (hf i) : inv f hf i = (f i)⁻¹ := rfl

theorem inv_mul_cancel {f : cau_seq β abv} (hf) : inv f hf * f ≈ 1 :=
λ ε ε0, let ⟨K, K0, i, H⟩ := abv_pos_of_not_lim_zero hf in
⟨i, λ j ij,
  by simpa [(abv_pos abv).1 (lt_of_lt_of_le K0 (H _ ij)),
    abv_zero abv] using ε0⟩

theorem const_inv {x : β} (hx : x ≠ 0) :
  const abv (x⁻¹) = inv (const abv x) (by rwa const_lim_zero) :=
ext (assume n, by simp[inv_apply, const_apply])

end field

section abs
local notation `const` := const abs

/-- The entries of a positive Cauchy sequence eventually have a positive lower bound. -/
def pos (f : cau_seq α abs) : Prop := ∃ K > 0, ∃ i, ∀ j ≥ i, K ≤ f j

theorem not_lim_zero_of_pos {f : cau_seq α abs} : pos f → ¬ lim_zero f
| ⟨F, F0, hF⟩ H :=
  let ⟨i, h⟩ := exists_forall_ge_and hF (H _ F0),
      ⟨h₁, h₂⟩ := h _ (le_refl _) in
  not_lt_of_le h₁ (abs_lt.1 h₂).2

theorem const_pos {x : α} : pos (const x) ↔ 0 < x :=
⟨λ ⟨K, K0, i, h⟩, lt_of_lt_of_le K0 (h _ (le_refl _)),
 λ h, ⟨x, h, 0, λ j _, le_refl _⟩⟩

theorem add_pos {f g : cau_seq α abs} : pos f → pos g → pos (f + g)
| ⟨F, F0, hF⟩ ⟨G, G0, hG⟩ :=
  let ⟨i, h⟩ := exists_forall_ge_and hF hG in
  ⟨_, _root_.add_pos F0 G0, i,
    λ j ij, let ⟨h₁, h₂⟩ := h _ ij in add_le_add h₁ h₂⟩

theorem pos_add_lim_zero {f g : cau_seq α abs} : pos f → lim_zero g → pos (f + g)
| ⟨F, F0, hF⟩ H :=
  let ⟨i, h⟩ := exists_forall_ge_and hF (H _ (half_pos F0)) in
  ⟨_, half_pos F0, i, λ j ij, begin
    cases h j ij with h₁ h₂,
    have := add_le_add h₁ (le_of_lt (abs_lt.1 h₂).1),
    rwa [← sub_eq_add_neg, sub_self_div_two] at this
  end⟩

protected theorem mul_pos {f g : cau_seq α abs} : pos f → pos g → pos (f * g)
| ⟨F, F0, hF⟩ ⟨G, G0, hG⟩ :=
  let ⟨i, h⟩ := exists_forall_ge_and hF hG in
  ⟨_, _root_.mul_pos F0 G0, i,
    λ j ij, let ⟨h₁, h₂⟩ := h _ ij in
    mul_le_mul h₁ h₂ (le_of_lt G0) (le_trans (le_of_lt F0) h₁)⟩

theorem trichotomy (f : cau_seq α abs) : pos f ∨ lim_zero f ∨ pos (-f) :=
begin
  cases classical.em (lim_zero f); simp *,
  rcases abv_pos_of_not_lim_zero h with ⟨K, K0, hK⟩,
  rcases exists_forall_ge_and hK (f.cauchy₃ K0) with ⟨i, hi⟩,
  refine (le_total 0 (f i)).imp _ _;
    refine (λ h, ⟨K, K0, i, λ j ij, _⟩);
    have := (hi _ ij).1;
    cases hi _ (le_refl _) with h₁ h₂,
  { rwa abs_of_nonneg at this,
    rw abs_of_nonneg h at h₁,
    exact (le_add_iff_nonneg_right _).1
      (le_trans h₁ $ neg_le_sub_iff_le_add'.1 $
        le_of_lt (abs_lt.1 $ h₂ _ ij).1) },
  { rwa abs_of_nonpos at this,
    rw abs_of_nonpos h at h₁,
    rw [← sub_le_sub_iff_right, zero_sub],
    exact le_trans (le_of_lt (abs_lt.1 $ h₂ _ ij).2) h₁ }
end

instance : has_lt (cau_seq α abs) := ⟨λ f g, pos (g - f)⟩
instance : has_le (cau_seq α abs) := ⟨λ f g, f < g ∨ f ≈ g⟩

theorem lt_of_lt_of_eq {f g h : cau_seq α abs}
  (fg : f < g) (gh : g ≈ h) : f < h :=
show pos (h - f),
by simpa [sub_eq_add_neg, add_comm, add_left_comm] using pos_add_lim_zero fg (neg_lim_zero gh)

theorem lt_of_eq_of_lt {f g h : cau_seq α abs}
  (fg : f ≈ g) (gh : g < h) : f < h :=
by have := pos_add_lim_zero gh (neg_lim_zero fg);
   rwa [← sub_eq_add_neg, sub_sub_sub_cancel_right] at this

theorem lt_trans {f g h : cau_seq α abs} (fg : f < g) (gh : g < h) : f < h :=
show pos (h - f),
by simpa [sub_eq_add_neg, add_comm, add_left_comm] using add_pos fg gh

theorem lt_irrefl {f : cau_seq α abs} : ¬ f < f
| h := not_lim_zero_of_pos h (by simp [zero_lim_zero])

lemma le_of_eq_of_le {f g h : cau_seq α abs}
  (hfg : f ≈ g) (hgh : g ≤ h) : f ≤ h :=
hgh.elim (or.inl ∘ cau_seq.lt_of_eq_of_lt hfg)
  (or.inr ∘ setoid.trans hfg)

lemma le_of_le_of_eq {f g h : cau_seq α abs}
  (hfg : f ≤ g) (hgh : g ≈ h) : f ≤ h :=
hfg.elim (λ h, or.inl (cau_seq.lt_of_lt_of_eq h hgh))
  (λ h, or.inr (setoid.trans h hgh))

instance : preorder (cau_seq α abs) :=
{ lt := (<),
  le := λ f g, f < g ∨ f ≈ g,
  le_refl := λ f, or.inr (setoid.refl _),
  le_trans := λ f g h fg, match fg with
    | or.inl fg, or.inl gh := or.inl $ lt_trans fg gh
    | or.inl fg, or.inr gh := or.inl $ lt_of_lt_of_eq fg gh
    | or.inr fg, or.inl gh := or.inl $ lt_of_eq_of_lt fg gh
    | or.inr fg, or.inr gh := or.inr $ setoid.trans fg gh
    end,
  lt_iff_le_not_le := λ f g,
    ⟨λ h, ⟨or.inl h,
      not_or (mt (lt_trans h) lt_irrefl) (not_lim_zero_of_pos h)⟩,
    λ ⟨h₁, h₂⟩, h₁.resolve_right
      (mt (λ h, or.inr (setoid.symm h)) h₂)⟩ }

theorem le_antisymm {f g : cau_seq α abs} (fg : f ≤ g) (gf : g ≤ f) : f ≈ g :=
fg.resolve_left (not_lt_of_le gf)

theorem lt_total (f g : cau_seq α abs) : f < g ∨ f ≈ g ∨ g < f :=
(trichotomy (g - f)).imp_right
  (λ h, h.imp (λ h, setoid.symm h) (λ h, by rwa neg_sub at h))

theorem le_total (f g : cau_seq α abs) : f ≤ g ∨ g ≤ f :=
(or.assoc.2 (lt_total f g)).imp_right or.inl

theorem const_lt {x y : α} : const x < const y ↔ x < y :=
show pos _ ↔ _, by rw [← const_sub, const_pos, sub_pos]

theorem const_le {x y : α} : const x ≤ const y ↔ x ≤ y :=
by rw le_iff_lt_or_eq; exact or_congr const_lt const_equiv

lemma le_of_exists {f g : cau_seq α abs}
  (h : ∃ i, ∀ j ≥ i, f j ≤ g j) : f ≤ g :=
let ⟨i, hi⟩ := h in
(or.assoc.2 (cau_seq.lt_total f g)).elim
  id
  (λ hgf, false.elim (let ⟨K, hK0, j, hKj⟩ := hgf in
    not_lt_of_ge (hi (max i j) (le_max_left _ _))
      (sub_pos.1 (lt_of_lt_of_le hK0 (hKj _ (le_max_right _ _))))))

theorem exists_gt (f : cau_seq α abs) : ∃ a : α, f < const a :=
let ⟨K, H⟩ := f.bounded in
⟨K + 1, 1, zero_lt_one, 0, λ i _, begin
  rw [sub_apply, const_apply, le_sub_iff_add_le', add_le_add_iff_right],
  exact le_of_lt (abs_lt.1 (H _)).2
end⟩

theorem exists_lt (f : cau_seq α abs) : ∃ a : α, const a < f :=
let ⟨a, h⟩ := (-f).exists_gt in ⟨-a, show pos _,
  by rwa [const_neg, sub_neg_eq_add, add_comm, ← sub_neg_eq_add]⟩

end abs

end cau_seq


example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
  intros,
  transitivity a,
  symmetry,
  assumption,
  assumption
end


import data.real.nnreal

import analysis.special_functions.pow

theorem exo (f: nnreal -> nnreal):
  (exists x y, ! (f x = f y))
  -> (forall x y, (f x) * (f y) * (f (x+y)) = f x + f y - f (x + y) )
  -> (exists a, a > 1 /\ forall x, f x = (a^x - 1) / (a^x + 1))
:= sorry


/-
Copyright (c) 2020 Dan Stanescu. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Dan Stanescu.
-/

import tactic

/-!
# Knights and knaves puzzles
Two puzzles from "Knights and Knaves" by Raymond Smullyan.
For an online description of these puzzles, see: http://mesosyn.com/mental1-6.html
More information on R. Smullyan: https://en.wikipedia.org/wiki/Raymond_Smullyan

There is an island where all inhabitants are either knights or knaves.
Knights always tell the truth. Knaves always lie.
Joe and Bob are two inhabitants of this island.
-/

inductive islander
| knight
| knave

notation `y` := islander.knight
notation `n` := islander.knave

structure Q := (Joe Bob : islander)

/--
In this first puzzle, only Joe makes a statement.
He affirms that both protagonist (i.e. both Joe and Bob) are knaves.
Question: what kind of islanders are they in fact?
-/

def Q1 := Q

namespace Q1

variables (q : Q1)

-- Joe's statement
def S1 := q.Joe = n ∧ q.Bob = n
-- Stating that Joe can be a knight or a knave
def H := (q.Joe = y ∧ q.S1) ∨ (q.Joe = n ∧ ¬ q.S1)

lemma answer : q.H → q.Joe = n ∧ q.Bob = y :=
begin
   rcases q with ⟨_|_,_|_⟩;
   { simp [H, S1], },
   done
end

end Q1

/--
In the second puzzle, both Joe and Bob make a statement.
Joe states that he's a knave if and only if Bob is a knave.
Bob only states that they are different kinds.
Question: again, what kind of islanders are our protagonists?
-/

def Q2 := Q

namespace Q2

variables (q : Q2)

-- Joe's statement
def S1 := q.Joe = n ↔ q.Bob = n
-- Bob's statement
def S2 := q.Joe ≠ q.Bob
-- Stating that either one can be a knight or a knave
def H1 := (q.Joe = y ∧ q.S1) ∨ (q.Joe = n ∧ ¬ q.S1)
def H2 := (q.Bob = y ∧ q.S2) ∨ (q.Bob = n ∧ ¬ q.S2)
-- Again two forms here:
def H := q.H1 ∧ q.H2

lemma answer : q.H → q.Joe = n ∧ q.Bob = y :=
begin
   rcases q with ⟨_|_,_|_⟩;
   { simp [H, H1, S1, H2, S2], },
   done
end

end Q2


import tuto_lib

/-
This file continues the elementary study of limits of sequences.
It can be skipped if the previous file was too easy, it won't introduce
any new tactic or trick.

Remember useful lemmas:

abs_le (x y : ℝ) : |x| ≤ y ↔ -y ≤ x ∧ x ≤ y

abs_add (x y : ℝ) : |x + y| ≤ |x| + |y|

abs_sub (x y : ℝ) : |x - y| = |y - x|

ge_max_iff (p q r) : r ≥ max p q  ↔ r ≥ p ∧ r ≥ q

le_max_left p q : p ≤ max p q

le_max_right p q : q ≤ max p q

and the definition:

def seq_limit (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

You can also use a property proved in the previous file:

unique_limit : seq_limit u l → seq_limit u l' → l = l'

def extraction (φ : ℕ → ℕ) := ∀ n m, n < m → φ n < φ m
-/

variable {φ : ℕ → ℕ}

example : extraction φ → ∀ n, n ≤ φ n :=
begin
  intros h n,
  induction n with n hn,
  { exact nat.zero_le _ },
  { specialize h n (n+1) (by linarith),
    exact nat.succ_le_of_lt (by linarith [h]) },
end

/-
The next lemma is proved by an easy induction, but we haven't seen induction
in this tutorial. If you did the natural number game then you can delete
the proof below and try to reconstruct it.
-/
/-- An extraction is greater than id -/
lemma id_le_extraction' : extraction φ → ∀ n, n ≤ φ n :=
begin
  intros hyp n,
  induction n with n hn,
  { exact nat.zero_le _ },
  { exact nat.succ_le_of_lt (by linarith [hyp n (n+1) (by linarith)]) },
end

/-- Extractions take arbitrarily large values for arbitrarily large
inputs. -/
-- 0039
lemma extraction_ge : extraction φ → ∀ N N', ∃ n ≥ N', φ n ≥ N :=
begin
  intros h N N',
  use max N N',
  split,
  { exact le_sup_right },
  { transitivity max N N',
    { exact id_le_extraction' h (max N N') },
    { exact le_sup_left } }
end

/-- A real number `a` is a cluster point of a sequence `u`
if `u` has a subsequence converging to `a`.

def cluster_point (u : ℕ → ℝ) (a : ℝ) :=
∃ φ, extraction φ ∧ seq_limit (u ∘ φ) a
-/

variables {u : ℕ → ℝ} {a l : ℝ}

/-
In the exercise, we use `∃ n ≥ N, ...` which is the abbreviation of
`∃ n, n ≥ N ∧ ...`.
Lean can read this abbreviation, but displays it as the confusing:
`∃ (n : ℕ) (H : n ≥ N)`
One gets used to it. Alternatively, one can get rid of it using the lemma
  exists_prop {p q : Prop} : (∃ (h : p), q) ↔ p ∧ q
-/

/-- If `a` is a cluster point of `u` then there are values of
`u` arbitrarily close to `a` for arbitrarily large input. -/
-- 0040
lemma near_cluster (h : cluster_point u a) :
  ∀ ε > 0, ∀ N, ∃ n ≥ N, |u n - a| ≤ ε :=
begin
  intros ε ε_pos N,
  rcases h with ⟨φ, h, hu⟩,
  cases hu ε ε_pos with N' hN',
  rcases extraction_ge h N N' with ⟨n, h, hn⟩,
  exact ⟨φ n, hn, hN' n h⟩
end

/-
The above exercice can be done in five lines.
Hint: you can use the anonymous constructor syntax when proving
existential statements.
-/

--lemma extraction_ge : extraction φ → ∀ N N', ∃ n ≥ N', φ n ≥ N :=
/-- If `u` tends to `l` then its subsequences tend to `l`. -/
-- 0041
lemma subseq_tendsto_of_tendsto' (h : seq_limit u l) (hφ : extraction φ) :
seq_limit (u ∘ φ) l :=
begin
  intros ε ε_pos,
  cases h ε ε_pos with N hN,
  use N,
  intros n hn,
  apply hN,
  calc
    N ≤ n   : hn
  ... ≤ φ n : id_le_extraction hφ n
end

#find _ → (_ : ℝ) = _

/-- If `u` tends to `l` all its cluster points are equal to `l`. -/
-- 0042
lemma cluster_limit (hl : seq_limit u l) (ha : cluster_point u a) : a = l :=
begin
  -- I will leave this proof unpolished and document my thought process. This
  -- should be a simple question of applying the lemmas above. First, I will
  -- unfold the definitions because I always struggle to remember them.
  unfold seq_limit at hl,
  unfold cluster_point at ha,
  -- There is no reason why `ha` shouldn't be cased (except if you want to make
  -- the proof small).
  rcases ha with ⟨φ, hφ, hu⟩,
  -- I just realized I don't know how to an `ℝ` equality. To the mathlib docs!
  -- ...okay, the quick skim through the docs wasn't fruitful, neither was the
  -- `#find` above.
  --
  -- And I cheated, I took a glimpse at the solution and it uses a lemma
  -- used in the previous tutorial... Darn, I should have thought!
  apply unique_limit hu,
  -- Okay, now the problem got interesting!
  unfold seq_limit at hu,
  -- `seq_limit` in goal that means... `intros` time!
  intros ε ε_pos,
  cases hl ε ε_pos with N hN,
  use φ N,
  intros n hn,
  -- Okay, everything from the last comment to here was pretty straight
  -- forward. Now, it looks like the trick from the last lemma will suffice.
  apply hN,
  -- Firstly, `ge` sucks.
  unfold ge at *,
  calc
    N ≤ φ N : id_le_extraction hφ N
  ... ≤ n   : hn
  ... ≤ φ n : id_le_extraction hφ n
  -- After writing `calc`, it was just a question of adding `by
  -- library_search` at each step and accepting the solutions, because I am
  -- lazy.
end

/-- Cauchy_sequence sequence -/
def cauchy_sequence (u : ℕ → ℝ) := ∀ ε > 0, ∃ N, ∀ p q, p ≥ N → q ≥ N → |u p - u q| ≤ ε

-- 0043
example : (∃ l, seq_limit u l) → cauchy_sequence u :=
begin
  rintros ⟨l, hl⟩ ε ε_pos,
  unfold seq_limit at hl,
  cases hl (ε/2) (by linarith) with N hN,
  use N,
  intros p q hp hq,
  have := hN p hp,
  have := hN q hq,
  calc
  |u p - u q| ≤ |(u p - l) + (l - u q)| : by ring
  ...         ≤ |u p - l| + |l - u q|   : by apply abs_add
  ...         ≤ |u p - l| + |u q - l|   : by rw abs_sub (u q) l
  ...         ≤ ε                       : by linarith,
end

/-
In the next exercise, you can reuse
 near_cluster : cluster_point u a → ∀ ε > 0, ∀ N, ∃ n ≥ N, |u n - a| ≤ ε
-/
-- 0044
example (hu : cauchy_sequence u) (hl : cluster_point u l) : seq_limit u l :=
begin
  intros ε ε_pos,
  unfold cluster_point at hl,
  cases hu (ε/2) (by linarith) with N hN,
  rcases near_cluster hl (ε/2) (by linarith) N with ⟨N', hNN', hN'⟩,
  use N,
  intros n hn,
  have := hN n N' hn hNN',
  calc
  |u n - l| ≤ |(u n - u N') + (u N' - l)| : by ring
  ...       ≤ |u n - u N'| + |u N' - l|   : by apply abs_add
  ...       ≤ ε                           : by linarith,
end