/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Jeremy Avigad, Yury Kudryashov, Patrick Massot
-/
import order.filter.bases
import data.finset.preimage

/-!
# `at_top` and `at_bot` filters on preorded sets, monoids and groups.

In this file we define the filters

* `at_top`: corresponds to `n → +∞`;
* `at_bot`: corresponds to `n → -∞`.

Then we prove many lemmas like “if `f → +∞`, then `f ± c → +∞`”.
-/

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

open set
open_locale classical filter big_operators

namespace filter
/-- `at_top` is the filter representing the limit `→ ∞` on an ordered set.
  It is generated by the collection of up-sets `{b | a ≤ b}`.
  (The preorder need not have a top element for this to be well defined,
  and indeed is trivial when a top element exists.) -/
def at_top [preorder α] : filter α := ⨅ a, 𝓟 (Ici a)

/-- `at_bot` is the filter representing the limit `→ -∞` on an ordered set.
  It is generated by the collection of down-sets `{b | b ≤ a}`.
  (The preorder need not have a bottom element for this to be well defined,
  and indeed is trivial when a bottom element exists.) -/
def at_bot [preorder α] : filter α := ⨅ a, 𝓟 (Iic a)

lemma mem_at_top [preorder α] (a : α) : {b : α | a ≤ b} ∈ @at_top α _ :=
mem_infi_sets a $ subset.refl _

lemma Ioi_mem_at_top [preorder α] [no_top_order α] (x : α) : Ioi x ∈ (at_top : filter α) :=
let ⟨z, hz⟩ := no_top x in mem_sets_of_superset (mem_at_top z) $ λ y h,  lt_of_lt_of_le hz h

lemma mem_at_bot [preorder α] (a : α) : {b : α | b ≤ a} ∈ @at_bot α _ :=
mem_infi_sets a $ subset.refl _

lemma Iio_mem_at_bot [preorder α] [no_bot_order α] (x : α) : Iio x ∈ (at_bot : filter α) :=
let ⟨z, hz⟩ := no_bot x in mem_sets_of_superset (mem_at_bot z) $ λ y h, lt_of_le_of_lt h hz

lemma at_top_basis [nonempty α] [semilattice_sup α] :
  (@at_top α _).has_basis (λ _, true) Ici :=
has_basis_infi_principal (directed_of_sup $ λ a b, Ici_subset_Ici.2)

lemma at_top_basis' [semilattice_sup α] (a : α) :
  (@at_top α _).has_basis (λ x, a ≤ x) Ici :=
⟨λ t, (@at_top_basis α ⟨a⟩ _).mem_iff.trans
  ⟨λ ⟨x, _, hx⟩, ⟨x ⊔ a, le_sup_right, λ y hy, hx (le_trans le_sup_left hy)⟩,
    λ ⟨x, _, hx⟩, ⟨x, trivial, hx⟩⟩⟩

lemma at_bot_basis [nonempty α] [semilattice_inf α] :
  (@at_bot α _).has_basis (λ _, true) Iic :=
@at_top_basis (order_dual α) _ _

lemma at_bot_basis' [semilattice_inf α] (a : α) :
  (@at_bot α _).has_basis (λ x, x ≤ a) Iic :=
@at_top_basis' (order_dual α) _ _

@[instance]
lemma at_top_ne_bot [nonempty α] [semilattice_sup α] : ne_bot (at_top : filter α) :=
at_top_basis.ne_bot_iff.2 $ λ a _, nonempty_Ici

@[instance]
lemma at_bot_ne_bot [nonempty α] [semilattice_inf α] : ne_bot (at_bot : filter α) :=
@at_top_ne_bot (order_dual α) _ _

@[simp]
lemma mem_at_top_sets [nonempty α] [semilattice_sup α] {s : set α} :
  s ∈ (at_top : filter α) ↔ ∃a:α, ∀b≥a, b ∈ s :=
at_top_basis.mem_iff.trans $ exists_congr $ λ _, exists_const _

@[simp]
lemma mem_at_bot_sets [nonempty α] [semilattice_inf α] {s : set α} :
  s ∈ (at_bot : filter α) ↔ ∃a:α, ∀b≤a, b ∈ s :=
@mem_at_top_sets (order_dual α) _ _ _

@[simp]
lemma eventually_at_top [semilattice_sup α] [nonempty α] {p : α → Prop} :
  (∀ᶠ x in at_top, p x) ↔ (∃ a, ∀ b ≥ a, p b) :=
mem_at_top_sets

@[simp]
lemma eventually_at_bot [semilattice_inf α] [nonempty α] {p : α → Prop} :
  (∀ᶠ x in at_bot, p x) ↔ (∃ a, ∀ b ≤ a, p b) :=
mem_at_bot_sets

lemma eventually_ge_at_top [preorder α] (a : α) : ∀ᶠ x in at_top, a ≤ x := mem_at_top a

lemma eventually_le_at_bot [preorder α] (a : α) : ∀ᶠ x in at_bot, x ≤ a := mem_at_bot a

lemma eventually_gt_at_top [preorder α] [no_top_order α] (a : α) :
  ∀ᶠ x in at_top, a < x :=
Ioi_mem_at_top a

lemma eventually_lt_at_bot [preorder α] [no_bot_order α] (a : α) :
  ∀ᶠ x in at_bot, x < a :=
Iio_mem_at_bot a

lemma at_top_basis_Ioi [nonempty α] [semilattice_sup α] [no_top_order α] :
  (@at_top α _).has_basis (λ _, true) Ioi :=
at_top_basis.to_has_basis (λ a ha, ⟨a, ha, Ioi_subset_Ici_self⟩) $
  λ a ha, (no_top a).imp $ λ b hb, ⟨ha, Ici_subset_Ioi.2 hb⟩

lemma at_top_countable_basis [nonempty α] [semilattice_sup α] [encodable α] :
  has_countable_basis (at_top : filter α) (λ _, true) Ici :=
{ countable := countable_encodable _,
  .. at_top_basis }

lemma at_bot_countable_basis [nonempty α] [semilattice_inf α] [encodable α] :
  has_countable_basis (at_bot : filter α) (λ _, true) Iic :=
{ countable := countable_encodable _,
  .. at_bot_basis }

lemma is_countably_generated_at_top [nonempty α] [semilattice_sup α] [encodable α] :
  (at_top : filter $ α).is_countably_generated :=
at_top_countable_basis.is_countably_generated

lemma is_countably_generated_at_bot [nonempty α] [semilattice_inf α] [encodable α] :
  (at_bot : filter $ α).is_countably_generated :=
at_bot_countable_basis.is_countably_generated

lemma order_top.at_top_eq (α) [order_top α] : (at_top : filter α) = pure ⊤ :=
le_antisymm (le_pure_iff.2 $ (eventually_ge_at_top ⊤).mono $ λ b, top_unique)
  (le_infi $ λ b, le_principal_iff.2 le_top)

@[nontriviality]
lemma subsingleton.at_top_eq (α) [subsingleton α] [preorder α] : (at_top : filter α) = ⊤ :=
begin
  refine top_unique (λ s hs x, _),
  letI : unique α := ⟨⟨x⟩, λ y, subsingleton.elim y x⟩,
  rw [at_top, infi_unique, unique.default_eq x, mem_principal_sets] at hs,
  exact hs left_mem_Ici
end

@[nontriviality]
lemma subsingleton.at_bot_eq (α) [subsingleton α] [preorder α] : (at_bot : filter α) = ⊤ :=
subsingleton.at_top_eq (order_dual α)

lemma tendsto_at_top_pure [order_top α] (f : α → β) :
  tendsto f at_top (pure $ f ⊤) :=
(order_top.at_top_eq α).symm ▸ tendsto_pure_pure _ _

lemma tendsto_at_bot_pure [order_bot α] (f : α → β) :
  tendsto f at_bot (pure $ f ⊥) :=
@tendsto_at_top_pure (order_dual α) _ _ _

lemma eventually.exists_forall_of_at_top [semilattice_sup α] [nonempty α] {p : α → Prop}
  (h : ∀ᶠ x in at_top, p x) : ∃ a, ∀ b ≥ a, p b :=
eventually_at_top.mp h

lemma eventually.exists_forall_of_at_bot [semilattice_inf α] [nonempty α] {p : α → Prop}
  (h : ∀ᶠ x in at_bot, p x) : ∃ a, ∀ b ≤ a, p b :=
eventually_at_bot.mp h

lemma frequently_at_top [semilattice_sup α] [nonempty α] {p : α → Prop} :
  (∃ᶠ x in at_top, p x) ↔ (∀ a, ∃ b ≥ a, p b) :=
by simp [at_top_basis.frequently_iff]

lemma frequently_at_bot [semilattice_inf α] [nonempty α] {p : α → Prop} :
  (∃ᶠ x in at_bot, p x) ↔ (∀ a, ∃ b ≤ a, p b) :=
@frequently_at_top (order_dual α) _ _ _

lemma frequently_at_top' [semilattice_sup α] [nonempty α] [no_top_order α] {p : α → Prop} :
  (∃ᶠ x in at_top, p x) ↔ (∀ a, ∃ b > a, p b) :=
by simp [at_top_basis_Ioi.frequently_iff]

lemma frequently_at_bot' [semilattice_inf α] [nonempty α] [no_bot_order α] {p : α → Prop} :
  (∃ᶠ x in at_bot, p x) ↔ (∀ a, ∃ b < a, p b) :=
@frequently_at_top' (order_dual α) _ _ _ _

lemma frequently.forall_exists_of_at_top [semilattice_sup α] [nonempty α] {p : α → Prop}
  (h : ∃ᶠ x in at_top, p x) : ∀ a, ∃ b ≥ a, p b :=
frequently_at_top.mp h

lemma frequently.forall_exists_of_at_bot [semilattice_inf α] [nonempty α] {p : α → Prop}
  (h : ∃ᶠ x in at_bot, p x) : ∀ a, ∃ b ≤ a, p b :=
frequently_at_bot.mp h

lemma map_at_top_eq [nonempty α] [semilattice_sup α] {f : α → β} :
  at_top.map f = (⨅a, 𝓟 $ f '' {a' | a ≤ a'}) :=
(at_top_basis.map _).eq_infi

lemma map_at_bot_eq [nonempty α] [semilattice_inf α] {f : α → β} :
  at_bot.map f = (⨅a, 𝓟 $ f '' {a' | a' ≤ a}) :=
@map_at_top_eq (order_dual α) _ _ _ _

lemma tendsto_at_top [preorder β] {m : α → β} {f : filter α} :
  tendsto m f at_top ↔ (∀b, ∀ᶠ a in f, b ≤ m a) :=
by simp only [at_top, tendsto_infi, tendsto_principal, mem_Ici]

lemma tendsto_at_bot [preorder β] {m : α → β} {f : filter α} :
  tendsto m f at_bot ↔ (∀b, ∀ᶠ a in f, m a ≤ b) :=
@tendsto_at_top α (order_dual β) _ m f

lemma tendsto_at_top_mono' [preorder β] (l : filter α) ⦃f₁ f₂ : α → β⦄ (h : f₁ ≤ᶠ[l] f₂) :
  tendsto f₁ l at_top → tendsto f₂ l at_top :=
assume h₁, tendsto_at_top.2 $ λ b, mp_sets (tendsto_at_top.1 h₁ b)
  (monotone_mem_sets (λ a ha ha₁, le_trans ha₁ ha) h)

lemma tendsto_at_bot_mono' [preorder β] (l : filter α) ⦃f₁ f₂ : α → β⦄ (h : f₁ ≤ᶠ[l] f₂) :
  tendsto f₂ l at_bot → tendsto f₁ l at_bot :=
@tendsto_at_top_mono' _ (order_dual β) _ _ _ _ h

lemma tendsto_at_top_mono [preorder β] {l : filter α} {f g : α → β} (h : ∀ n, f n ≤ g n) :
  tendsto f l at_top → tendsto g l at_top :=
tendsto_at_top_mono' l $ eventually_of_forall h

lemma tendsto_at_bot_mono [preorder β] {l : filter α} {f g : α → β} (h : ∀ n, f n ≤ g n) :
  tendsto g l at_bot → tendsto f l at_bot :=
@tendsto_at_top_mono _ (order_dual β) _ _ _ _ h

/-!
### Sequences
-/

lemma inf_map_at_top_ne_bot_iff [semilattice_sup α] [nonempty α] {F : filter β} {u : α → β} :
  ne_bot (F ⊓ (map u at_top)) ↔ ∀ U ∈ F, ∀ N, ∃ n ≥ N, u n ∈ U :=
by simp_rw [inf_ne_bot_iff_frequently_left, frequently_map, frequently_at_top]; refl

lemma inf_map_at_bot_ne_bot_iff [semilattice_inf α] [nonempty α] {F : filter β} {u : α → β} :
  ne_bot (F ⊓ (map u at_bot)) ↔ ∀ U ∈ F, ∀ N, ∃ n ≤ N, u n ∈ U :=
@inf_map_at_top_ne_bot_iff (order_dual α) _ _ _ _ _

lemma extraction_of_frequently_at_top' {P : ℕ → Prop} (h : ∀ N, ∃ n > N, P n) :
  ∃ φ : ℕ → ℕ, strict_mono φ ∧ ∀ n, P (φ n) :=
begin
  choose u hu using h,
  cases forall_and_distrib.mp hu with hu hu',
  exact ⟨u ∘ (nat.rec 0 (λ n v, u v)), strict_mono.nat (λ n, hu _), λ n, hu' _⟩,
end

lemma extraction_of_frequently_at_top {P : ℕ → Prop} (h : ∃ᶠ n in at_top, P n) :
  ∃ φ : ℕ → ℕ, strict_mono φ ∧ ∀ n, P (φ n) :=
begin
  rw frequently_at_top' at h,
  exact extraction_of_frequently_at_top' h,
end

lemma extraction_of_eventually_at_top {P : ℕ → Prop} (h : ∀ᶠ n in at_top, P n) :
  ∃ φ : ℕ → ℕ, strict_mono φ ∧ ∀ n, P (φ n) :=
extraction_of_frequently_at_top h.frequently

lemma exists_le_of_tendsto_at_top [semilattice_sup α] [preorder β] {u : α → β}
  (h : tendsto u at_top at_top) (a : α) (b : β) : ∃ a' ≥ a, b ≤ u a' :=
begin
  have : ∀ᶠ x in at_top, a ≤ x ∧ b ≤ u x :=
    (eventually_ge_at_top a).and (h.eventually $ eventually_ge_at_top b),
  haveI : nonempty α := ⟨a⟩,
  rcases this.exists with ⟨a', ha, hb⟩,
  exact ⟨a', ha, hb⟩
end

@[nolint ge_or_gt] -- see Note [nolint_ge]
lemma exists_le_of_tendsto_at_bot [semilattice_sup α] [preorder β] {u : α → β}
  (h : tendsto u at_top at_bot) : ∀ a b, ∃ a' ≥ a, u a' ≤ b :=
@exists_le_of_tendsto_at_top _ (order_dual β) _ _ _ h

lemma exists_lt_of_tendsto_at_top [semilattice_sup α] [preorder β] [no_top_order β]
  {u : α → β} (h : tendsto u at_top at_top) (a : α) (b : β) : ∃ a' ≥ a, b < u a' :=
begin
  cases no_top b with b' hb',
  rcases exists_le_of_tendsto_at_top h a b' with ⟨a', ha', ha''⟩,
  exact ⟨a', ha', lt_of_lt_of_le hb' ha''⟩
end

@[nolint ge_or_gt] -- see Note [nolint_ge]
lemma exists_lt_of_tendsto_at_bot [semilattice_sup α] [preorder β] [no_bot_order β]
  {u : α → β} (h : tendsto u at_top at_bot) : ∀ a b, ∃ a' ≥ a, u a' < b :=
@exists_lt_of_tendsto_at_top _ (order_dual β) _ _ _ _ h

/--
If `u` is a sequence which is unbounded above,
then after any point, it reaches a value strictly greater than all previous values.
-/
lemma high_scores [linear_order β] [no_top_order β] {u : ℕ → β}
  (hu : tendsto u at_top at_top) : ∀ N, ∃ n ≥ N, ∀ k < n, u k < u n :=
begin
  intros N,
  let A := finset.image u (finset.range $ N+1), -- A = {u 0, ..., u N}
  have Ane : A.nonempty,
    from ⟨u 0, finset.mem_image_of_mem _ (finset.mem_range.mpr $ nat.zero_lt_succ _)⟩,
  let M := finset.max' A Ane,
  have ex : ∃ n ≥ N, M < u n,
    from exists_lt_of_tendsto_at_top hu _ _,
  obtain ⟨n, hnN, hnM, hn_min⟩ : ∃ n, N ≤ n ∧ M < u n ∧ ∀ k, N ≤ k → k < n → u k ≤ M,
  { use nat.find ex,
    rw ← and_assoc,
    split,
    { simpa using nat.find_spec ex },
    { intros k hk hk',
      simpa [hk] using nat.find_min ex hk' } },
  use [n, hnN],
  intros k hk,
  by_cases H : k ≤ N,
  { have : u k ∈ A,
      from finset.mem_image_of_mem _ (finset.mem_range.mpr $ nat.lt_succ_of_le H),
    have : u k ≤ M,
      from finset.le_max' A (u k) this,
    exact lt_of_le_of_lt this hnM },
  { push_neg at H,
    calc u k ≤ M   : hn_min k (le_of_lt H) hk
         ... < u n : hnM },
end

/--
If `u` is a sequence which is unbounded below,
then after any point, it reaches a value strictly smaller than all previous values.
-/
@[nolint ge_or_gt] -- see Note [nolint_ge]
lemma low_scores [linear_order β] [no_bot_order β] {u : ℕ → β}
  (hu : tendsto u at_top at_bot) : ∀ N, ∃ n ≥ N, ∀ k < n, u n < u k :=
@high_scores (order_dual β) _ _ _ hu

/--
If `u` is a sequence which is unbounded above,
then it `frequently` reaches a value strictly greater than all previous values.
-/
lemma frequently_high_scores [linear_order β] [no_top_order β] {u : ℕ → β}
  (hu : tendsto u at_top at_top) : ∃ᶠ n in at_top, ∀ k < n, u k < u n :=
by simpa [frequently_at_top] using high_scores hu

/--
If `u` is a sequence which is unbounded below,
then it `frequently` reaches a value strictly smaller than all previous values.
-/
lemma frequently_low_scores [linear_order β] [no_bot_order β] {u : ℕ → β}
  (hu : tendsto u at_top at_bot) : ∃ᶠ n in at_top, ∀ k < n, u n < u k :=
@frequently_high_scores (order_dual β) _ _ _ hu

lemma strict_mono_subseq_of_tendsto_at_top
  {β : Type*} [linear_order β] [no_top_order β]
  {u : ℕ → β} (hu : tendsto u at_top at_top) :
  ∃ φ : ℕ → ℕ, strict_mono φ ∧ strict_mono (u ∘ φ) :=
let ⟨φ, h, h'⟩ := extraction_of_frequently_at_top (frequently_high_scores hu) in
⟨φ, h, λ n m hnm, h' m _ (h hnm)⟩

lemma strict_mono_subseq_of_id_le {u : ℕ → ℕ} (hu : ∀ n, n ≤ u n) :
  ∃ φ : ℕ → ℕ, strict_mono φ ∧ strict_mono (u ∘ φ) :=
strict_mono_subseq_of_tendsto_at_top (tendsto_at_top_mono hu tendsto_id)

lemma strict_mono_tendsto_at_top {φ : ℕ → ℕ} (h : strict_mono φ) :
  tendsto φ at_top at_top :=
tendsto_at_top_mono h.id_le tendsto_id

section ordered_add_comm_monoid

variables [ordered_add_comm_monoid β] {l : filter α} {f g : α → β}

lemma tendsto_at_top_add_nonneg_left' (hf : ∀ᶠ x in l, 0 ≤ f x) (hg : tendsto g l at_top) :
  tendsto (λ x, f x + g x) l at_top :=
tendsto_at_top_mono' l (hf.mono (λ x, le_add_of_nonneg_left)) hg

lemma tendsto_at_bot_add_nonpos_left' (hf : ∀ᶠ x in l, f x ≤ 0) (hg : tendsto g l at_bot) :
  tendsto (λ x, f x + g x) l at_bot :=
@tendsto_at_top_add_nonneg_left' _ (order_dual β) _ _ _ _ hf hg

lemma tendsto_at_top_add_nonneg_left (hf : ∀ x, 0 ≤ f x) (hg : tendsto g l at_top) :
  tendsto (λ x, f x + g x) l at_top :=
tendsto_at_top_add_nonneg_left' (eventually_of_forall hf) hg

lemma tendsto_at_bot_add_nonpos_left (hf : ∀ x, f x ≤ 0) (hg : tendsto g l at_bot) :
  tendsto (λ x, f x + g x) l at_bot :=
@tendsto_at_top_add_nonneg_left _ (order_dual β) _ _ _ _ hf hg

lemma tendsto_at_top_add_nonneg_right' (hf : tendsto f l at_top) (hg : ∀ᶠ x in l, 0 ≤ g x) :
  tendsto (λ x, f x + g x) l at_top :=
tendsto_at_top_mono' l (monotone_mem_sets (λ x, le_add_of_nonneg_right) hg) hf

lemma tendsto_at_bot_add_nonpos_right' (hf : tendsto f l at_bot) (hg : ∀ᶠ x in l, g x ≤ 0) :
  tendsto (λ x, f x + g x) l at_bot :=
@tendsto_at_top_add_nonneg_right' _ (order_dual β) _ _ _ _ hf hg

lemma tendsto_at_top_add_nonneg_right (hf : tendsto f l at_top) (hg : ∀ x, 0 ≤ g x) :
  tendsto (λ x, f x + g x) l at_top :=
tendsto_at_top_add_nonneg_right' hf (eventually_of_forall hg)

lemma tendsto_at_bot_add_nonpos_right (hf : tendsto f l at_bot) (hg : ∀ x, g x ≤ 0) :
  tendsto (λ x, f x + g x) l at_bot :=
@tendsto_at_top_add_nonneg_right _ (order_dual β) _ _ _ _ hf hg

lemma tendsto_at_top_add (hf : tendsto f l at_top) (hg : tendsto g l at_top) :
  tendsto (λ x, f x + g x) l at_top :=
tendsto_at_top_add_nonneg_left' (tendsto_at_top.mp hf 0) hg

lemma tendsto_at_bot_add (hf : tendsto f l at_bot) (hg : tendsto g l at_bot) :
  tendsto (λ x, f x + g x) l at_bot :=
@tendsto_at_top_add _ (order_dual β) _ _ _ _ hf hg

lemma tendsto.nsmul_at_top (hf : tendsto f l at_top) {n : ℕ} (hn : 0 < n) :
  tendsto (λ x, n • f x) l at_top :=
tendsto_at_top.2 $ λ y, (tendsto_at_top.1 hf y).mp $ (tendsto_at_top.1 hf 0).mono $ λ x h₀ hy,
calc y ≤ f x : hy
... = 1 • f x : (one_nsmul _).symm
... ≤  n • f x : nsmul_le_nsmul h₀ hn

lemma tendsto.nsmul_at_bot (hf : tendsto f l at_bot) {n : ℕ} (hn : 0 < n) :
  tendsto (λ x, n • f x) l at_bot :=
@tendsto.nsmul_at_top α (order_dual β) _ l f hf n hn

lemma tendsto_bit0_at_top : tendsto bit0 (at_top : filter β) at_top :=
tendsto_at_top_add tendsto_id tendsto_id

lemma tendsto_bit0_at_bot : tendsto bit0 (at_bot : filter β) at_bot :=
tendsto_at_bot_add tendsto_id tendsto_id

end ordered_add_comm_monoid

section ordered_cancel_add_comm_monoid

variables [ordered_cancel_add_comm_monoid β] {l : filter α} {f g : α → β}

lemma tendsto_at_top_of_add_const_left (C : β) (hf : tendsto (λ x, C + f x) l at_top) :
  tendsto f l at_top :=
tendsto_at_top.2 $ assume b, (tendsto_at_top.1 hf (C + b)).mono (λ x, le_of_add_le_add_left)

lemma tendsto_at_bot_of_add_const_left (C : β) (hf : tendsto (λ x, C + f x) l at_bot) :
  tendsto f l at_bot :=
@tendsto_at_top_of_add_const_left _ (order_dual β) _ _ _ C hf

lemma tendsto_at_top_of_add_const_right (C : β) (hf : tendsto (λ x, f x + C) l at_top) :
  tendsto f l at_top :=
tendsto_at_top.2 $ assume b, (tendsto_at_top.1 hf (b + C)).mono (λ x, le_of_add_le_add_right)

lemma tendsto_at_bot_of_add_const_right (C : β) (hf : tendsto (λ x, f x + C) l at_bot) :
  tendsto f l at_bot :=
@tendsto_at_top_of_add_const_right _ (order_dual β) _ _ _ C hf

lemma tendsto_at_top_of_add_bdd_above_left' (C) (hC : ∀ᶠ x in l, f x ≤ C)
  (h : tendsto (λ x, f x + g x) l at_top) :
  tendsto g l at_top :=
tendsto_at_top_of_add_const_left C
  (tendsto_at_top_mono' l (hC.mono (λ x hx, add_le_add_right hx (g x))) h)

lemma tendsto_at_bot_of_add_bdd_below_left' (C) (hC : ∀ᶠ x in l, C ≤ f x)
  (h : tendsto (λ x, f x + g x) l at_bot) :
  tendsto g l at_bot :=
@tendsto_at_top_of_add_bdd_above_left' _ (order_dual β) _ _ _ _ C hC h

lemma tendsto_at_top_of_add_bdd_above_left (C) (hC : ∀ x, f x ≤ C) :
  tendsto (λ x, f x + g x) l at_top → tendsto g l at_top :=
tendsto_at_top_of_add_bdd_above_left' C (univ_mem_sets' hC)

lemma tendsto_at_bot_of_add_bdd_below_left (C) (hC : ∀ x, C ≤ f x) :
  tendsto (λ x, f x + g x) l at_bot → tendsto g l at_bot :=
@tendsto_at_top_of_add_bdd_above_left _ (order_dual β) _ _ _ _ C hC

lemma tendsto_at_top_of_add_bdd_above_right' (C) (hC : ∀ᶠ x in l, g x ≤ C)
  (h : tendsto (λ x, f x + g x) l at_top) :
  tendsto f l at_top :=
tendsto_at_top_of_add_const_right C
  (tendsto_at_top_mono' l (hC.mono (λ x hx, add_le_add_left hx (f x))) h)

lemma tendsto_at_bot_of_add_bdd_below_right' (C) (hC : ∀ᶠ x in l, C ≤ g x)
  (h : tendsto (λ x, f x + g x) l at_bot) :
  tendsto f l at_bot :=
@tendsto_at_top_of_add_bdd_above_right' _ (order_dual β) _ _ _ _ C hC h

lemma tendsto_at_top_of_add_bdd_above_right (C) (hC : ∀ x, g x ≤ C) :
  tendsto (λ x, f x + g x) l at_top → tendsto f l at_top :=
tendsto_at_top_of_add_bdd_above_right' C (univ_mem_sets' hC)

lemma tendsto_at_bot_of_add_bdd_below_right (C) (hC : ∀ x, C ≤ g x) :
  tendsto (λ x, f x + g x) l at_bot → tendsto f l at_bot :=
@tendsto_at_top_of_add_bdd_above_right _ (order_dual β) _ _ _ _ C hC

end ordered_cancel_add_comm_monoid

section ordered_group

variables [ordered_add_comm_group β] (l : filter α) {f g : α → β}

lemma tendsto_at_top_add_left_of_le' (C : β) (hf : ∀ᶠ x in l, C ≤ f x) (hg : tendsto g l at_top) :
  tendsto (λ x, f x + g x) l at_top :=
@tendsto_at_top_of_add_bdd_above_left' _ _ _ l (λ x, -(f x)) (λ x, f x + g x) (-C)
  (by simpa) (by simpa)

lemma tendsto_at_bot_add_left_of_ge' (C : β) (hf : ∀ᶠ x in l, f x ≤ C) (hg : tendsto g l at_bot) :
  tendsto (λ x, f x + g x) l at_bot :=
@tendsto_at_top_add_left_of_le' _ (order_dual β) _ _ _ _ C hf hg

lemma tendsto_at_top_add_left_of_le (C : β) (hf : ∀ x, C ≤ f x) (hg : tendsto g l at_top) :
  tendsto (λ x, f x + g x) l at_top :=
tendsto_at_top_add_left_of_le' l C (univ_mem_sets' hf) hg

lemma tendsto_at_bot_add_left_of_ge (C : β) (hf : ∀ x, f x ≤ C) (hg : tendsto g l at_bot) :
  tendsto (λ x, f x + g x) l at_bot :=
@tendsto_at_top_add_left_of_le _ (order_dual β) _ _ _ _ C hf hg

lemma tendsto_at_top_add_right_of_le' (C : β) (hf : tendsto f l at_top) (hg : ∀ᶠ x in l, C ≤ g x) :
  tendsto (λ x, f x + g x) l at_top :=
@tendsto_at_top_of_add_bdd_above_right' _ _ _ l (λ x, f x + g x) (λ x, -(g x)) (-C)
  (by simp [hg]) (by simp [hf])

lemma tendsto_at_bot_add_right_of_ge' (C : β) (hf : tendsto f l at_bot) (hg : ∀ᶠ x in l, g x ≤ C) :
  tendsto (λ x, f x + g x) l at_bot :=
@tendsto_at_top_add_right_of_le' _ (order_dual β) _ _ _ _ C hf hg

lemma tendsto_at_top_add_right_of_le (C : β) (hf : tendsto f l at_top) (hg : ∀ x, C ≤ g x) :
  tendsto (λ x, f x + g x) l at_top :=
tendsto_at_top_add_right_of_le' l C hf (univ_mem_sets' hg)

lemma tendsto_at_bot_add_right_of_ge (C : β) (hf : tendsto f l at_bot) (hg : ∀ x, g x ≤ C) :
  tendsto (λ x, f x + g x) l at_bot :=
@tendsto_at_top_add_right_of_le _ (order_dual β) _ _ _ _ C hf hg

lemma tendsto_at_top_add_const_left (C : β) (hf : tendsto f l at_top) :
  tendsto (λ x, C + f x) l at_top :=
tendsto_at_top_add_left_of_le' l C (univ_mem_sets' $ λ _, le_refl C) hf

lemma tendsto_at_bot_add_const_left (C : β) (hf : tendsto f l at_bot) :
  tendsto (λ x, C + f x) l at_bot :=
@tendsto_at_top_add_const_left _ (order_dual β) _ _ _ C hf

lemma tendsto_at_top_add_const_right (C : β) (hf : tendsto f l at_top) :
  tendsto (λ x, f x + C) l at_top :=
tendsto_at_top_add_right_of_le' l C hf (univ_mem_sets' $ λ _, le_refl C)

lemma tendsto_at_bot_add_const_right (C : β) (hf : tendsto f l at_bot) :
  tendsto (λ x, f x + C) l at_bot :=
@tendsto_at_top_add_const_right _ (order_dual β) _ _ _ C hf

lemma tendsto_neg_at_top_at_bot : tendsto (has_neg.neg : β → β) at_top at_bot :=
begin
  simp only [tendsto_at_bot, neg_le],
  exact λ b, eventually_ge_at_top _
end

lemma tendsto_neg_at_bot_at_top : tendsto (has_neg.neg : β → β) at_bot at_top :=
@tendsto_neg_at_top_at_bot (order_dual β) _

end ordered_group

section ordered_semiring

variables [ordered_semiring α] {l : filter β} {f g : β → α}

lemma tendsto_bit1_at_top : tendsto bit1 (at_top : filter α) at_top :=
tendsto_at_top_add_nonneg_right tendsto_bit0_at_top (λ _, zero_le_one)

lemma tendsto.at_top_mul_at_top (hf : tendsto f l at_top) (hg : tendsto g l at_top) :
  tendsto (λ x, f x * g x) l at_top :=
begin
  refine tendsto_at_top_mono' _ _ hg,
  filter_upwards [hg.eventually (eventually_ge_at_top 0), hf.eventually (eventually_ge_at_top 1)],
  exact λ x, le_mul_of_one_le_left
end

lemma tendsto_mul_self_at_top : tendsto (λ x : α, x * x) at_top at_top :=
tendsto_id.at_top_mul_at_top tendsto_id

/-- The monomial function `x^n` tends to `+∞` at `+∞` for any positive natural `n`.
A version for positive real powers exists as `tendsto_rpow_at_top`. -/
lemma tendsto_pow_at_top {n : ℕ} (hn : 1 ≤ n) : tendsto (λ x : α, x ^ n) at_top at_top :=
begin
  refine tendsto_at_top_mono' _ ((eventually_ge_at_top 1).mono $ λ x hx, _) tendsto_id,
  simpa only [pow_one] using pow_le_pow hx hn
end

end ordered_semiring

lemma zero_pow_eventually_eq [monoid_with_zero α] :
  (λ n : ℕ, (0 : α) ^ n) =ᶠ[at_top] (λ n, 0) :=
eventually_at_top.2 ⟨1, λ n hn, zero_pow (zero_lt_one.trans_le hn)⟩

section ordered_ring

variables [ordered_ring α] {l : filter β} {f g : β → α}

lemma tendsto.at_top_mul_at_bot (hf : tendsto f l at_top) (hg : tendsto g l at_bot) :
  tendsto (λ x, f x * g x) l at_bot :=
have _ := (hf.at_top_mul_at_top $ tendsto_neg_at_bot_at_top.comp hg),
by simpa only [(∘), neg_mul_eq_mul_neg, neg_neg] using tendsto_neg_at_top_at_bot.comp this

lemma tendsto.at_bot_mul_at_top (hf : tendsto f l at_bot) (hg : tendsto g l at_top) :
  tendsto (λ x, f x * g x) l at_bot :=
have tendsto (λ x, (-f x) * g x) l at_top :=
  ( (tendsto_neg_at_bot_at_top.comp hf).at_top_mul_at_top hg),
by simpa only [(∘), neg_mul_eq_neg_mul, neg_neg] using tendsto_neg_at_top_at_bot.comp this

lemma tendsto.at_bot_mul_at_bot (hf : tendsto f l at_bot) (hg : tendsto g l at_bot) :
  tendsto (λ x, f x * g x) l at_top :=
have tendsto (λ x, (-f x) * (-g x)) l at_top :=
  (tendsto_neg_at_bot_at_top.comp hf).at_top_mul_at_top (tendsto_neg_at_bot_at_top.comp hg),
by simpa only [neg_mul_neg] using this

end ordered_ring

section linear_ordered_add_comm_group

variables [linear_ordered_add_comm_group α]

/-- $\lim_{x\to+\infty}|x|=+\infty$ -/
lemma tendsto_abs_at_top_at_top : tendsto (abs : α → α) at_top at_top :=
tendsto_at_top_mono le_abs_self tendsto_id

/-- $\lim_{x\to-\infty}|x|=+\infty$ -/
lemma tendsto_abs_at_bot_at_top : tendsto (abs : α → α) at_bot at_top :=
tendsto_at_top_mono neg_le_abs_self tendsto_neg_at_bot_at_top

end linear_ordered_add_comm_group

section linear_ordered_semiring

variables [linear_ordered_semiring α] {l : filter β} {f : β → α}

lemma tendsto.at_top_of_const_mul {c : α} (hc : 0 < c) (hf : tendsto (λ x, c * f x) l at_top) :
  tendsto f l at_top :=
tendsto_at_top.2 $ λ b, (tendsto_at_top.1 hf (c * b)).mono $ λ x hx, le_of_mul_le_mul_left hx hc

lemma tendsto.at_top_of_mul_const {c : α} (hc : 0 < c) (hf : tendsto (λ x, f x * c) l at_top) :
  tendsto f l at_top :=
tendsto_at_top.2 $ λ b, (tendsto_at_top.1 hf (b * c)).mono $ λ x hx, le_of_mul_le_mul_right hx hc

end linear_ordered_semiring

lemma nonneg_of_eventually_pow_nonneg [linear_ordered_ring α] {a : α}
  (h : ∀ᶠ n in at_top, 0 ≤ a ^ (n : ℕ)) : 0 ≤ a :=
let ⟨n, hn⟩ := (tendsto_bit1_at_top.eventually h).exists in pow_bit1_nonneg_iff.1 hn

section linear_ordered_field

variables [linear_ordered_field α] {l : filter β} {f : β → α} {r : α}

/-- If a function tends to infinity along a filter, then this function multiplied by a positive
constant (on the left) also tends to infinity. For a version working in `ℕ` or `ℤ`, use
`filter.tendsto.const_mul_at_top'` instead. -/
lemma tendsto.const_mul_at_top (hr : 0 < r) (hf : tendsto f l at_top) :
  tendsto (λx, r * f x) l at_top :=
tendsto.at_top_of_const_mul (inv_pos.2 hr) $ by simpa only [inv_mul_cancel_left' hr.ne']

/-- If a function tends to infinity along a filter, then this function multiplied by a positive
constant (on the right) also tends to infinity. For a version working in `ℕ` or `ℤ`, use
`filter.tendsto.at_top_mul_const'` instead. -/
lemma tendsto.at_top_mul_const (hr : 0 < r) (hf : tendsto f l at_top) :
  tendsto (λx, f x * r) l at_top :=
by simpa only [mul_comm] using hf.const_mul_at_top hr

/-- If a function tends to infinity along a filter, then this function divided by a positive
constant also tends to infinity. -/
lemma tendsto.at_top_div_const (hr : 0 < r) (hf : tendsto f l at_top) :
  tendsto (λx, f x / r) l at_top :=
by simpa only [div_eq_mul_inv] using hf.at_top_mul_const (inv_pos.2 hr)

/-- If a function tends to infinity along a filter, then this function multiplied by a negative
constant (on the left) tends to negative infinity. -/
lemma tendsto.neg_const_mul_at_top (hr : r < 0) (hf : tendsto f l at_top) :
  tendsto (λ x, r * f x) l at_bot :=
by simpa only [(∘), neg_mul_eq_neg_mul, neg_neg]
  using tendsto_neg_at_top_at_bot.comp (hf.const_mul_at_top (neg_pos.2 hr))

/-- If a function tends to infinity along a filter, then this function multiplied by a negative
constant (on the right) tends to negative infinity. -/
lemma tendsto.at_top_mul_neg_const (hr : r < 0) (hf : tendsto f l at_top) :
  tendsto (λ x, f x * r) l at_bot :=
by simpa only [mul_comm] using hf.neg_const_mul_at_top hr

/-- If a function tends to negative infinity along a filter, then this function multiplied by
a positive constant (on the left) also tends to negative infinity. -/
lemma tendsto.const_mul_at_bot (hr : 0 < r) (hf : tendsto f l at_bot) :
  tendsto (λx, r * f x) l at_bot :=
by simpa only [(∘), neg_mul_eq_mul_neg, neg_neg]
  using tendsto_neg_at_top_at_bot.comp ((tendsto_neg_at_bot_at_top.comp hf).const_mul_at_top hr)

/-- If a function tends to negative infinity along a filter, then this function multiplied by
a positive constant (on the right) also tends to negative infinity. -/
lemma tendsto.at_bot_mul_const (hr : 0 < r) (hf : tendsto f l at_bot) :
  tendsto (λx, f x * r) l at_bot :=
by simpa only [mul_comm] using hf.const_mul_at_bot hr

/-- If a function tends to negative infinity along a filter, then this function divided by
a positive constant also tends to negative infinity. -/
lemma tendsto.at_bot_div_const (hr : 0 < r) (hf : tendsto f l at_bot) :
  tendsto (λx, f x / r) l at_bot :=
by simpa only [div_eq_mul_inv] using hf.at_bot_mul_const (inv_pos.2 hr)

/-- If a function tends to negative infinity along a filter, then this function multiplied by
a negative constant (on the left) tends to positive infinity. -/
lemma tendsto.neg_const_mul_at_bot (hr : r < 0) (hf : tendsto f l at_bot) :
  tendsto (λ x, r * f x) l at_top :=
by simpa only [(∘), neg_mul_eq_neg_mul, neg_neg]
  using tendsto_neg_at_bot_at_top.comp (hf.const_mul_at_bot (neg_pos.2 hr))

/-- If a function tends to negative infinity along a filter, then this function multiplied by
a negative constant (on the right) tends to positive infinity. -/
lemma tendsto.at_bot_mul_neg_const (hr : r < 0) (hf : tendsto f l at_bot) :
  tendsto (λ x, f x * r) l at_top :=
by simpa only [mul_comm] using hf.neg_const_mul_at_bot hr

lemma tendsto_const_mul_pow_at_top {c : α} {n : ℕ}
  (hn : 1 ≤ n) (hc : 0 < c) : tendsto (λ x, c * x^n) at_top at_top :=
tendsto.const_mul_at_top hc (tendsto_pow_at_top hn)

lemma tendsto_neg_const_mul_pow_at_top {c : α} {n : ℕ}
  (hn : 1 ≤ n) (hc : c < 0) : tendsto (λ x, c * x^n) at_top at_bot :=
tendsto.neg_const_mul_at_top hc (tendsto_pow_at_top hn)

end linear_ordered_field

open_locale filter

lemma tendsto_at_top' [nonempty α] [semilattice_sup α] {f : α → β} {l : filter β} :
  tendsto f at_top l ↔ (∀s ∈ l, ∃a, ∀b≥a, f b ∈ s) :=
by simp only [tendsto_def, mem_at_top_sets]; refl

lemma tendsto_at_bot' [nonempty α] [semilattice_inf α] {f : α → β} {l : filter β} :
  tendsto f at_bot l ↔ (∀s ∈ l, ∃a, ∀b≤a, f b ∈ s) :=
@tendsto_at_top' (order_dual α) _ _ _ _ _

theorem tendsto_at_top_principal [nonempty β] [semilattice_sup β] {f : β → α} {s : set α} :
  tendsto f at_top (𝓟 s) ↔ ∃N, ∀n≥N, f n ∈ s :=
by rw [tendsto_iff_comap, comap_principal, le_principal_iff, mem_at_top_sets]; refl

theorem tendsto_at_bot_principal [nonempty β] [semilattice_inf β] {f : β → α} {s : set α} :
  tendsto f at_bot (𝓟 s) ↔ ∃N, ∀n≤N, f n ∈ s :=
@tendsto_at_top_principal _ (order_dual β) _ _ _ _

/-- A function `f` grows to `+∞` independent of an order-preserving embedding `e`. -/
lemma tendsto_at_top_at_top [nonempty α] [semilattice_sup α] [preorder β] {f : α → β} :
  tendsto f at_top at_top ↔ ∀ b : β, ∃ i : α, ∀ a : α, i ≤ a → b ≤ f a :=
iff.trans tendsto_infi $ forall_congr $ assume b, tendsto_at_top_principal

lemma tendsto_at_top_at_bot [nonempty α] [semilattice_sup α] [preorder β] {f : α → β} :
  tendsto f at_top at_bot ↔ ∀ (b : β), ∃ (i : α), ∀ (a : α), i ≤ a → f a ≤ b :=
@tendsto_at_top_at_top α (order_dual β) _ _ _ f

lemma tendsto_at_bot_at_top [nonempty α] [semilattice_inf α] [preorder β] {f : α → β} :
  tendsto f at_bot at_top ↔ ∀ (b : β), ∃ (i : α), ∀ (a : α), a ≤ i → b ≤ f a :=
@tendsto_at_top_at_top (order_dual α) β _ _ _ f

lemma tendsto_at_bot_at_bot [nonempty α] [semilattice_inf α] [preorder β] {f : α → β} :
  tendsto f at_bot at_bot ↔ ∀ (b : β), ∃ (i : α), ∀ (a : α), a ≤ i → f a ≤ b :=
@tendsto_at_top_at_top (order_dual α) (order_dual β) _ _ _ f

lemma tendsto_at_top_at_top_of_monotone [preorder α] [preorder β] {f : α → β} (hf : monotone f)
  (h : ∀ b, ∃ a, b ≤ f a) :
  tendsto f at_top at_top :=
tendsto_infi.2 $ λ b, tendsto_principal.2 $ let ⟨a, ha⟩ := h b in
mem_sets_of_superset (mem_at_top a) $ λ a' ha', le_trans ha (hf ha')

lemma tendsto_at_bot_at_bot_of_monotone [preorder α] [preorder β] {f : α → β} (hf : monotone f)
  (h : ∀ b, ∃ a, f a ≤ b) :
  tendsto f at_bot at_bot :=
tendsto_infi.2 $ λ b, tendsto_principal.2 $ let ⟨a, ha⟩ := h b in
mem_sets_of_superset (mem_at_bot a) $ λ a' ha', le_trans (hf ha') ha

lemma tendsto_at_top_at_top_iff_of_monotone [nonempty α] [semilattice_sup α] [preorder β]
  {f : α → β} (hf : monotone f) :
  tendsto f at_top at_top ↔ ∀ b : β, ∃ a : α, b ≤ f a :=
tendsto_at_top_at_top.trans $ forall_congr $ λ b, exists_congr $ λ a,
  ⟨λ h, h a (le_refl a), λ h a' ha', le_trans h $ hf ha'⟩

lemma tendsto_at_bot_at_bot_iff_of_monotone [nonempty α] [semilattice_inf α] [preorder β]
  {f : α → β} (hf : monotone f) :
  tendsto f at_bot at_bot ↔ ∀ b : β, ∃ a : α, f a ≤ b :=
tendsto_at_bot_at_bot.trans $ forall_congr $ λ b, exists_congr $ λ a,
  ⟨λ h, h a (le_refl a), λ h a' ha', le_trans (hf ha') h⟩

alias tendsto_at_top_at_top_of_monotone ← monotone.tendsto_at_top_at_top
alias tendsto_at_bot_at_bot_of_monotone ← monotone.tendsto_at_bot_at_bot
alias tendsto_at_top_at_top_iff_of_monotone ← monotone.tendsto_at_top_at_top_iff
alias tendsto_at_bot_at_bot_iff_of_monotone ← monotone.tendsto_at_bot_at_bot_iff

lemma tendsto_at_top_embedding [preorder β] [preorder γ]
  {f : α → β} {e : β → γ} {l : filter α}
  (hm : ∀b₁ b₂, e b₁ ≤ e b₂ ↔ b₁ ≤ b₂) (hu : ∀c, ∃b, c ≤ e b) :
  tendsto (e ∘ f) l at_top ↔ tendsto f l at_top :=
begin
  refine ⟨_, (tendsto_at_top_at_top_of_monotone (λ b₁ b₂, (hm b₁ b₂).2) hu).comp⟩,
  rw [tendsto_at_top, tendsto_at_top],
  exact λ hc b, (hc (e b)).mono (λ a, (hm b (f a)).1)
end

/-- A function `f` goes to `-∞` independent of an order-preserving embedding `e`. -/
lemma tendsto_at_bot_embedding [preorder β] [preorder γ]
  {f : α → β} {e : β → γ} {l : filter α}
  (hm : ∀b₁ b₂, e b₁ ≤ e b₂ ↔ b₁ ≤ b₂) (hu : ∀c, ∃b, e b ≤ c) :
  tendsto (e ∘ f) l at_bot ↔ tendsto f l at_bot :=
@tendsto_at_top_embedding α (order_dual β) (order_dual γ) _ _ f e l (function.swap hm) hu

lemma tendsto_finset_range : tendsto finset.range at_top at_top :=
finset.range_mono.tendsto_at_top_at_top finset.exists_nat_subset_range

lemma at_top_finset_eq_infi : (at_top : filter $ finset α) = ⨅ x : α, 𝓟 (Ici {x}) :=
begin
  refine le_antisymm (le_infi (λ i, le_principal_iff.2 $ mem_at_top {i})) _,
  refine le_infi (λ s, le_principal_iff.2 $ mem_infi_iff.2 _),
  refine ⟨↑s, s.finite_to_set, _, λ i, mem_principal_self _, _⟩,
  simp only [subset_def, mem_Inter, set_coe.forall, mem_Ici, finset.le_iff_subset,
    finset.mem_singleton, finset.subset_iff, forall_eq], dsimp,
  exact λ t, id
end

/-- If `f` is a monotone sequence of `finset`s and each `x` belongs to one of `f n`, then
`tendsto f at_top at_top`. -/
lemma tendsto_at_top_finset_of_monotone [preorder β]
  {f : β → finset α} (h : monotone f) (h' : ∀ x : α, ∃ n, x ∈ f n) :
  tendsto f at_top at_top :=
begin
  simp only [at_top_finset_eq_infi, tendsto_infi, tendsto_principal],
  intro a,
  rcases h' a with ⟨b, hb⟩,
  exact eventually.mono (mem_at_top b)
    (λ b' hb', le_trans (finset.singleton_subset_iff.2 hb) (h hb')),
end

alias tendsto_at_top_finset_of_monotone ← monotone.tendsto_at_top_finset

lemma tendsto_finset_image_at_top_at_top {i : β → γ} {j : γ → β} (h : function.left_inverse j i) :
  tendsto (finset.image j) at_top at_top :=
(finset.image_mono j).tendsto_at_top_finset $ assume a,
  ⟨{i a}, by simp only [finset.image_singleton, h a, finset.mem_singleton]⟩

lemma tendsto_finset_preimage_at_top_at_top {f : α → β} (hf : function.injective f) :
  tendsto (λ s : finset β, s.preimage f (hf.inj_on _)) at_top at_top :=
(finset.monotone_preimage hf).tendsto_at_top_finset $
  λ x, ⟨{f x}, finset.mem_preimage.2 $ finset.mem_singleton_self _⟩

lemma prod_at_top_at_top_eq {β₁ β₂ : Type*} [semilattice_sup β₁] [semilattice_sup β₂] :
  (at_top : filter β₁) ×ᶠ (at_top : filter β₂) = (at_top : filter (β₁ × β₂)) :=
begin
  by_cases ne : nonempty β₁ ∧ nonempty β₂,
  { cases ne,
    resetI,
    simp [at_top, prod_infi_left, prod_infi_right, infi_prod],
    exact infi_comm },
  { rw not_and_distrib at ne,
    cases ne;
    { have : ¬ (nonempty (β₁ × β₂)), by simp [ne],
      rw [at_top.filter_eq_bot_of_not_nonempty ne, at_top.filter_eq_bot_of_not_nonempty this],
      simp only [bot_prod, prod_bot] } }
end

lemma prod_at_bot_at_bot_eq {β₁ β₂ : Type*} [semilattice_inf β₁] [semilattice_inf β₂] :
  (at_bot : filter β₁) ×ᶠ (at_bot : filter β₂) = (at_bot : filter (β₁ × β₂)) :=
@prod_at_top_at_top_eq (order_dual β₁) (order_dual β₂) _ _

lemma prod_map_at_top_eq {α₁ α₂ β₁ β₂ : Type*} [semilattice_sup β₁] [semilattice_sup β₂]
  (u₁ : β₁ → α₁) (u₂ : β₂ → α₂) :
  (map u₁ at_top) ×ᶠ (map u₂ at_top) = map (prod.map u₁ u₂) at_top :=
by rw [prod_map_map_eq, prod_at_top_at_top_eq, prod.map_def]

lemma prod_map_at_bot_eq {α₁ α₂ β₁ β₂ : Type*} [semilattice_inf β₁] [semilattice_inf β₂]
  (u₁ : β₁ → α₁) (u₂ : β₂ → α₂) :
  (map u₁ at_bot) ×ᶠ (map u₂ at_bot) = map (prod.map u₁ u₂) at_bot :=
@prod_map_at_top_eq _ _ (order_dual β₁) (order_dual β₂) _ _ _ _

/-- A function `f` maps upwards closed sets (at_top sets) to upwards closed sets when it is a
Galois insertion. The Galois "insertion" and "connection" is weakened to only require it to be an
insertion and a connetion above `b'`. -/
lemma map_at_top_eq_of_gc [semilattice_sup α] [semilattice_sup β] {f : α → β} (g : β → α) (b' : β)
  (hf : monotone f) (gc : ∀a, ∀b≥b', f a ≤ b ↔ a ≤ g b) (hgi : ∀b≥b', b ≤ f (g b)) :
  map f at_top = at_top :=
begin
  refine le_antisymm
    (hf.tendsto_at_top_at_top $ λ b, ⟨g (b ⊔ b'), le_sup_left.trans $ hgi _ le_sup_right⟩) _,
  rw [@map_at_top_eq _ _ ⟨g b'⟩],
  refine le_infi (λ a, infi_le_of_le (f a ⊔ b') $ principal_mono.2 $ λ b hb, _),
  rw [mem_Ici, sup_le_iff] at hb,
  exact ⟨g b, (gc _ _ hb.2).1 hb.1, le_antisymm ((gc _ _ hb.2).2 (le_refl _)) (hgi _ hb.2)⟩
end

lemma map_at_bot_eq_of_gc [semilattice_inf α] [semilattice_inf β] {f : α → β} (g : β → α) (b' : β)
  (hf : monotone f) (gc : ∀a, ∀b≤b', b ≤ f a ↔ g b ≤ a) (hgi : ∀b≤b', f (g b) ≤ b) :
  map f at_bot = at_bot :=
@map_at_top_eq_of_gc (order_dual α) (order_dual β) _ _ _ _ _ hf.order_dual gc hgi

lemma map_coe_at_top_of_Ici_subset [semilattice_sup α] {a : α} {s : set α} (h : Ici a ⊆ s) :
  map (coe : s → α) at_top = at_top :=
begin
  have : directed (≥) (λ x : s, 𝓟 (Ici x)),
  { intros x y,
    use ⟨x ⊔ y ⊔ a, h le_sup_right⟩,
    simp only [ge_iff_le, principal_mono, Ici_subset_Ici, ← subtype.coe_le_coe, subtype.coe_mk],
    exact ⟨le_sup_left.trans le_sup_left, le_sup_right.trans le_sup_left⟩ },
  haveI : nonempty s := ⟨⟨a, h le_rfl⟩⟩,
  simp only [le_antisymm_iff, at_top, le_infi_iff, le_principal_iff, mem_map, mem_set_of_eq,
    map_infi_eq this, map_principal],
  split,
  { intro x,
    refine mem_sets_of_superset (mem_infi_sets ⟨x ⊔ a, h le_sup_right⟩ (mem_principal_self _)) _,
    rintro _ ⟨y, hy, rfl⟩,
    exact le_trans le_sup_left (subtype.coe_le_coe.2 hy) },
  { intro x,
    filter_upwards [mem_at_top (↑x ⊔ a)],
    intros b hb,
    exact ⟨⟨b, h $ le_sup_right.trans hb⟩, subtype.coe_le_coe.1 (le_sup_left.trans hb), rfl⟩ }
end

/-- The image of the filter `at_top` on `Ici a` under the coercion equals `at_top`. -/
@[simp] lemma map_coe_Ici_at_top [semilattice_sup α] (a : α) :
  map (coe : Ici a → α) at_top = at_top :=
map_coe_at_top_of_Ici_subset (subset.refl _)

/-- The image of the filter `at_top` on `Ioi a` under the coercion equals `at_top`. -/
@[simp] lemma map_coe_Ioi_at_top [semilattice_sup α] [no_top_order α] (a : α) :
  map (coe : Ioi a → α) at_top = at_top :=
begin
  rcases no_top a with ⟨b, hb⟩,
  exact map_coe_at_top_of_Ici_subset (Ici_subset_Ioi.2 hb)
end

/-- The `at_top` filter for an open interval `Ioi a` comes from the `at_top` filter in the ambient
order. -/
lemma at_top_Ioi_eq [semilattice_sup α] (a : α) :
  at_top = comap (coe : Ioi a → α) at_top :=
begin
  nontriviality,
  rcases nontrivial_iff_nonempty.1 ‹_› with ⟨b, hb⟩,
  rw [← map_coe_at_top_of_Ici_subset (Ici_subset_Ioi.2 hb), comap_map subtype.coe_injective]
end

/-- The `at_top` filter for an open interval `Ici a` comes from the `at_top` filter in the ambient
order. -/
lemma at_top_Ici_eq [semilattice_sup α] (a : α) :
  at_top = comap (coe : Ici a → α) at_top :=
by rw [← map_coe_Ici_at_top a, comap_map subtype.coe_injective]

/-- The `at_bot` filter for an open interval `Iio a` comes from the `at_bot` filter in the ambient
order. -/
@[simp] lemma map_coe_Iio_at_bot [semilattice_inf α] [no_bot_order α] (a : α) :
  map (coe : Iio a → α) at_bot = at_bot :=
@map_coe_Ioi_at_top (order_dual α) _ _ _

/-- The `at_bot` filter for an open interval `Iio a` comes from the `at_bot` filter in the ambient
order. -/
lemma at_bot_Iio_eq [semilattice_inf α] (a : α) :
  at_bot = comap (coe : Iio a → α) at_bot :=
@at_top_Ioi_eq (order_dual α) _ _

/-- The `at_bot` filter for an open interval `Iic a` comes from the `at_bot` filter in the ambient
order. -/
@[simp] lemma map_coe_Iic_at_bot [semilattice_inf α] (a : α) :
  map (coe : Iic a → α) at_bot = at_bot :=
@map_coe_Ici_at_top (order_dual α) _ _

/-- The `at_bot` filter for an open interval `Iic a` comes from the `at_bot` filter in the ambient
order. -/
lemma at_bot_Iic_eq [semilattice_inf α] (a : α) :
  at_bot = comap (coe : Iic a → α) at_bot :=
@at_top_Ici_eq (order_dual α) _ _

lemma tendsto_Ioi_at_top [semilattice_sup α] {a : α} {f : β → Ioi a}
  {l : filter β} :
  tendsto f l at_top ↔ tendsto (λ x, (f x : α)) l at_top :=
by rw [at_top_Ioi_eq, tendsto_comap_iff]

lemma tendsto_Iio_at_bot [semilattice_inf α] {a : α} {f : β → Iio a}
  {l : filter β} :
  tendsto f l at_bot ↔ tendsto (λ x, (f x : α)) l at_bot :=
by rw [at_bot_Iio_eq, tendsto_comap_iff]

lemma tendsto_Ici_at_top [semilattice_sup α] {a : α} {f : β → Ici a} {l : filter β} :
  tendsto f l at_top ↔ tendsto (λ x, (f x : α)) l at_top :=
by rw [at_top_Ici_eq, tendsto_comap_iff]

lemma tendsto_Iic_at_bot [semilattice_inf α] {a : α} {f : β → Iic a} {l : filter β} :
  tendsto f l at_bot ↔ tendsto (λ x, (f x : α)) l at_bot :=
by rw [at_bot_Iic_eq, tendsto_comap_iff]

@[simp] lemma tendsto_comp_coe_Ioi_at_top [semilattice_sup α] [no_top_order α] {a : α}
  {f : α → β} {l : filter β} :
  tendsto (λ x : Ioi a, f x) at_top l ↔ tendsto f at_top l :=
by rw [← map_coe_Ioi_at_top a, tendsto_map'_iff]

@[simp] lemma tendsto_comp_coe_Ici_at_top [semilattice_sup α] {a : α}
  {f : α → β} {l : filter β} :
  tendsto (λ x : Ici a, f x) at_top l ↔ tendsto f at_top l :=
by rw [← map_coe_Ici_at_top a, tendsto_map'_iff]

@[simp] lemma tendsto_comp_coe_Iio_at_bot [semilattice_inf α] [no_bot_order α] {a : α}
  {f : α → β} {l : filter β} :
  tendsto (λ x : Iio a, f x) at_bot l ↔ tendsto f at_bot l :=
by rw [← map_coe_Iio_at_bot a, tendsto_map'_iff]

@[simp] lemma tendsto_comp_coe_Iic_at_bot [semilattice_inf α] {a : α}
  {f : α → β} {l : filter β} :
  tendsto (λ x : Iic a, f x) at_bot l ↔ tendsto f at_bot l :=
by rw [← map_coe_Iic_at_bot a, tendsto_map'_iff]

lemma map_add_at_top_eq_nat (k : ℕ) : map (λa, a + k) at_top = at_top :=
map_at_top_eq_of_gc (λa, a - k) k
  (assume a b h, add_le_add_right h k)
  (assume a b h, (nat.le_sub_right_iff_add_le h).symm)
  (assume a h, by rw [nat.sub_add_cancel h])

lemma map_sub_at_top_eq_nat (k : ℕ) : map (λa, a - k) at_top = at_top :=
map_at_top_eq_of_gc (λa, a + k) 0
  (assume a b h, nat.sub_le_sub_right h _)
  (assume a b _, nat.sub_le_right_iff_le_add)
  (assume b _, by rw [nat.add_sub_cancel])

lemma tendsto_add_at_top_nat (k : ℕ) : tendsto (λa, a + k) at_top at_top :=
le_of_eq (map_add_at_top_eq_nat k)

lemma tendsto_sub_at_top_nat (k : ℕ) : tendsto (λa, a - k) at_top at_top :=
le_of_eq (map_sub_at_top_eq_nat k)

lemma tendsto_add_at_top_iff_nat {f : ℕ → α} {l : filter α} (k : ℕ) :
  tendsto (λn, f (n + k)) at_top l ↔ tendsto f at_top l :=
show tendsto (f ∘ (λn, n + k)) at_top l ↔ tendsto f at_top l,
  by rw [← tendsto_map'_iff, map_add_at_top_eq_nat]

lemma map_div_at_top_eq_nat (k : ℕ) (hk : 0 < k) : map (λa, a / k) at_top = at_top :=
map_at_top_eq_of_gc (λb, b * k + (k - 1)) 1
  (assume a b h, nat.div_le_div_right h)
  (assume a b _,
    calc a / k ≤ b ↔ a / k < b + 1 : by rw [← nat.succ_eq_add_one, nat.lt_succ_iff]
      ... ↔ a < (b + 1) * k : nat.div_lt_iff_lt_mul _ _ hk
      ... ↔ _ :
      begin
        cases k,
        exact (lt_irrefl _ hk).elim,
        simp [mul_add, add_mul, nat.succ_add, nat.lt_succ_iff]
      end)
  (assume b _,
    calc b = (b * k) / k : by rw [nat.mul_div_cancel b hk]
      ... ≤ (b * k + (k - 1)) / k : nat.div_le_div_right $ nat.le_add_right _ _)

/-- If `u` is a monotone function with linear ordered codomain and the range of `u` is not bounded
above, then `tendsto u at_top at_top`. -/
lemma tendsto_at_top_at_top_of_monotone' [preorder ι] [linear_order α]
  {u : ι → α} (h : monotone u) (H : ¬bdd_above (range u)) :
  tendsto u at_top at_top :=
begin
  apply h.tendsto_at_top_at_top,
  intro b,
  rcases not_bdd_above_iff.1 H b with ⟨_, ⟨N, rfl⟩, hN⟩,
  exact ⟨N, le_of_lt hN⟩,
end

/-- If `u` is a monotone function with linear ordered codomain and the range of `u` is not bounded
below, then `tendsto u at_bot at_bot`. -/
lemma tendsto_at_bot_at_bot_of_monotone' [preorder ι] [linear_order α]
  {u : ι → α} (h : monotone u) (H : ¬bdd_below (range u)) :
  tendsto u at_bot at_bot :=
@tendsto_at_top_at_top_of_monotone' (order_dual ι) (order_dual α) _ _ _ h.order_dual H

lemma unbounded_of_tendsto_at_top [nonempty α] [semilattice_sup α] [preorder β] [no_top_order β]
  {f : α → β} (h : tendsto f at_top at_top) :
  ¬ bdd_above (range f) :=
begin
  rintros ⟨M, hM⟩,
  cases mem_at_top_sets.mp (h $ Ioi_mem_at_top M) with a ha,
  apply lt_irrefl M,
  calc
  M < f a : ha a (le_refl _)
  ... ≤ M : hM (set.mem_range_self a)
end

lemma unbounded_of_tendsto_at_bot [nonempty α] [semilattice_sup α] [preorder β] [no_bot_order β]
  {f : α → β} (h : tendsto f at_top at_bot) :
  ¬ bdd_below (range f) :=
@unbounded_of_tendsto_at_top _ (order_dual β) _ _ _ _ _ h

lemma unbounded_of_tendsto_at_top' [nonempty α] [semilattice_inf α] [preorder β] [no_top_order β]
  {f : α → β} (h : tendsto f at_bot at_top) :
  ¬ bdd_above (range f) :=
@unbounded_of_tendsto_at_top (order_dual α) _ _ _ _ _ _ h

lemma unbounded_of_tendsto_at_bot' [nonempty α] [semilattice_inf α] [preorder β] [no_bot_order β]
  {f : α → β} (h : tendsto f at_bot at_bot) :
  ¬ bdd_below (range f) :=
@unbounded_of_tendsto_at_top (order_dual α) (order_dual β) _ _ _ _ _ h

/-- If a monotone function `u : ι → α` tends to `at_top` along *some* non-trivial filter `l`, then
it tends to `at_top` along `at_top`. -/
lemma tendsto_at_top_of_monotone_of_filter [preorder ι] [preorder α] {l : filter ι}
  {u : ι → α} (h : monotone u) [ne_bot l] (hu : tendsto u l at_top) :
  tendsto u at_top at_top :=
h.tendsto_at_top_at_top $ λ b, (hu.eventually (mem_at_top b)).exists

/-- If a monotone function `u : ι → α` tends to `at_bot` along *some* non-trivial filter `l`, then
it tends to `at_bot` along `at_bot`. -/
lemma tendsto_at_bot_of_monotone_of_filter [preorder ι] [preorder α] {l : filter ι}
  {u : ι → α} (h : monotone u) [ne_bot l] (hu : tendsto u l at_bot) :
  tendsto u at_bot at_bot :=
@tendsto_at_top_of_monotone_of_filter (order_dual ι) (order_dual α) _ _ _ _ h.order_dual _ hu

lemma tendsto_at_top_of_monotone_of_subseq [preorder ι] [preorder α] {u : ι → α}
  {φ : ι' → ι} (h : monotone u) {l : filter ι'} [ne_bot l]
  (H : tendsto (u ∘ φ) l at_top) :
  tendsto u at_top at_top :=
tendsto_at_top_of_monotone_of_filter h (tendsto_map' H)

lemma tendsto_at_bot_of_monotone_of_subseq [preorder ι] [preorder α] {u : ι → α}
  {φ : ι' → ι} (h : monotone u) {l : filter ι'} [ne_bot l]
  (H : tendsto (u ∘ φ) l at_bot) :
  tendsto u at_bot at_bot :=
tendsto_at_bot_of_monotone_of_filter h (tendsto_map' H)

/-- Let `f` and `g` be two maps to the same commutative monoid. This lemma gives a sufficient
condition for comparison of the filter `at_top.map (λ s, ∏ b in s, f b)` with
`at_top.map (λ s, ∏ b in s, g b)`. This is useful to compare the set of limit points of
`Π b in s, f b` as `s → at_top` with the similar set for `g`. -/
@[to_additive]
lemma map_at_top_finset_prod_le_of_prod_eq [comm_monoid α] {f : β → α} {g : γ → α}
  (h_eq : ∀u:finset γ, ∃v:finset β, ∀v', v ⊆ v' → ∃u', u ⊆ u' ∧ ∏ x in u', g x = ∏ b in v', f b) :
  at_top.map (λs:finset β, ∏ b in s, f b) ≤ at_top.map (λs:finset γ, ∏ x in s, g x) :=
by rw [map_at_top_eq, map_at_top_eq];
from (le_infi $ assume b, let ⟨v, hv⟩ := h_eq b in infi_le_of_le v $
  by simp [set.image_subset_iff]; exact hv)

lemma has_antimono_basis.tendsto [semilattice_sup ι] [nonempty ι] {l : filter α}
  {p : ι → Prop} {s : ι → set α} (hl : l.has_antimono_basis p s) {φ : ι → α}
  (h : ∀ i : ι, φ i ∈ s i) : tendsto φ at_top l  :=
(at_top_basis.tendsto_iff hl.to_has_basis).2 $ assume i hi,
  ⟨i, trivial, λ j hij, hl.decreasing hi (hl.mono hij hi) hij (h j)⟩

namespace is_countably_generated

/-- An abstract version of continuity of sequentially continuous functions on metric spaces:
if a filter `k` is countably generated then `tendsto f k l` iff for every sequence `u`
converging to `k`, `f ∘ u` tends to `l`. -/
lemma tendsto_iff_seq_tendsto {f : α → β} {k : filter α} {l : filter β}
  (hcb : k.is_countably_generated) :
  tendsto f k l ↔ (∀ x : ℕ → α, tendsto x at_top k → tendsto (f ∘ x) at_top l) :=
suffices (∀ x : ℕ → α, tendsto x at_top k → tendsto (f ∘ x) at_top l) → tendsto f k l,
  from ⟨by intros; apply tendsto.comp; assumption, by assumption⟩,
begin
  rcases hcb.exists_antimono_basis with ⟨g, gbasis, gmon, -⟩,
  contrapose,
  simp only [not_forall, gbasis.tendsto_left_iff, exists_const, not_exists, not_imp],
  rintro ⟨B, hBl, hfBk⟩,
  choose x h using hfBk,
  use x, split,
  { exact (at_top_basis.tendsto_iff gbasis).2
      (λ i _, ⟨i, trivial, λ j hj, gmon trivial trivial hj (h j).1⟩) },
  { simp only [tendsto_at_top', (∘), not_forall, not_exists],
    use [B, hBl],
    intro i, use [i, (le_refl _)],
    apply (h i).right },
end

lemma tendsto_of_seq_tendsto {f : α → β} {k : filter α} {l : filter β}
  (hcb : k.is_countably_generated) :
  (∀ x : ℕ → α, tendsto x at_top k → tendsto (f ∘ x) at_top l) → tendsto f k l :=
hcb.tendsto_iff_seq_tendsto.2

lemma subseq_tendsto {f : filter α} (hf : is_countably_generated f)
  {u : ℕ → α}
  (hx : ne_bot (f ⊓ map u at_top)) :
  ∃ (θ : ℕ → ℕ), (strict_mono θ) ∧ (tendsto (u ∘ θ) at_top f) :=
begin
  rcases hf.exists_antimono_basis with ⟨B, h⟩,
  have : ∀ N, ∃ n ≥ N, u n ∈ B N,
    from λ N, filter.inf_map_at_top_ne_bot_iff.mp hx _ (h.to_has_basis.mem_of_mem trivial) N,
  choose φ hφ using this,
  cases forall_and_distrib.mp hφ with φ_ge φ_in,
  have lim_uφ : tendsto (u ∘ φ) at_top f,
    from h.tendsto φ_in,
  have lim_φ : tendsto φ at_top at_top,
    from (tendsto_at_top_mono φ_ge tendsto_id),
  obtain ⟨ψ, hψ, hψφ⟩ : ∃ ψ : ℕ → ℕ, strict_mono ψ ∧ strict_mono (φ ∘ ψ),
    from strict_mono_subseq_of_tendsto_at_top lim_φ,
  exact ⟨φ ∘ ψ, hψφ, lim_uφ.comp $ strict_mono_tendsto_at_top hψ⟩,
end

end is_countably_generated

end filter

open filter finset

section

variables {R : Type*} [linear_ordered_semiring R]

lemma exists_lt_mul_self (a : R) : ∃ x ≥ 0, a < x * x :=
let ⟨x, hxa, hx0⟩ :=((tendsto_mul_self_at_top.eventually (eventually_gt_at_top a)).and
  (eventually_ge_at_top 0)).exists
in ⟨x, hx0, hxa⟩

lemma exists_le_mul_self (a : R) : ∃ x ≥ 0, a ≤ x * x :=
let ⟨x, hx0, hxa⟩ := exists_lt_mul_self a in ⟨x, hx0, hxa.le⟩

end

namespace order_iso

variables [preorder α] [preorder β]

@[simp] lemma comap_at_top (e : α ≃o β) : comap e at_top = at_top :=
by simp [at_top, ← e.surjective.infi_comp]

@[simp] lemma comap_at_bot (e : α ≃o β) : comap e at_bot = at_bot :=
e.dual.comap_at_top

@[simp] lemma map_at_top (e : α ≃o β) : map ⇑e at_top = at_top :=
by rw [← e.comap_at_top, map_comap_of_surjective e.surjective]

@[simp] lemma map_at_bot (e : α ≃o β) : map ⇑e at_bot = at_bot :=
e.dual.map_at_top

lemma tendsto_at_top (e : α ≃o β) : tendsto e at_top at_top :=
e.map_at_top.le

lemma tendsto_at_bot (e : α ≃o β) : tendsto e at_bot at_bot :=
e.map_at_bot.le

@[simp] lemma tendsto_at_top_iff {l : filter γ} {f : γ → α} (e : α ≃o β) :
  tendsto (λ x, e (f x)) l at_top ↔ tendsto f l at_top :=
by rw [← e.comap_at_top, tendsto_comap_iff]

@[simp] lemma tendsto_at_bot_iff {l : filter γ} {f : γ → α} (e : α ≃o β) :
  tendsto (λ x, e (f x)) l at_bot ↔ tendsto f l at_bot :=
e.dual.tendsto_at_top_iff

end order_iso

/-- Let `g : γ → β` be an injective function and `f : β → α` be a function from the codomain of `g`
to a commutative monoid. Suppose that `f x = 1` outside of the range of `g`. Then the filters
`at_top.map (λ s, ∏ i in s, f (g i))` and `at_top.map (λ s, ∏ i in s, f i)` coincide.

The additive version of this lemma is used to prove the equality `∑' x, f (g x) = ∑' y, f y` under
the same assumptions.-/
@[to_additive]
lemma function.injective.map_at_top_finset_prod_eq [comm_monoid α] {g : γ → β}
  (hg : function.injective g) {f : β → α} (hf : ∀ x ∉ set.range g, f x = 1) :
  map (λ s, ∏ i in s, f (g i)) at_top = map (λ s, ∏ i in s, f i) at_top :=
begin
  apply le_antisymm; refine map_at_top_finset_prod_le_of_prod_eq (λ s, _),
  { refine ⟨s.preimage g (hg.inj_on _), λ t ht, _⟩,
    refine ⟨t.image g ∪ s, finset.subset_union_right _ _, _⟩,
    rw [← finset.prod_image (hg.inj_on _)],
    refine (prod_subset (subset_union_left _ _) _).symm,
    simp only [finset.mem_union, finset.mem_image],
    refine λ y hy hyt, hf y (mt _ hyt),
    rintros ⟨x, rfl⟩,
    exact ⟨x, ht (finset.mem_preimage.2 $ hy.resolve_left hyt), rfl⟩ },
  { refine ⟨s.image g, λ t ht, _⟩,
    simp only [← prod_preimage _ _ (hg.inj_on _) _ (λ x _, hf x)],
    exact ⟨_, (image_subset_iff_subset_preimage _).1 ht, rfl⟩ }
end

/-- Let `g : γ → β` be an injective function and `f : β → α` be a function from the codomain of `g`
to an additive commutative monoid. Suppose that `f x = 0` outside of the range of `g`. Then the
filters `at_top.map (λ s, ∑ i in s, f (g i))` and `at_top.map (λ s, ∑ i in s, f i)` coincide.

This lemma is used to prove the equality `∑' x, f (g x) = ∑' y, f y` under
the same assumptions.-/
add_decl_doc function.injective.map_at_top_finset_sum_eq


/-
Following code are mostly from Logic and Proof:
https://leanprover.github.io/logic_and_proof/functions_in_lean.html

by Jeremy Avigad, Robert Y. Lewis, and Floris van Doorn
-/
universes u v
variables {W X Y Z α β: Sort*}

/-
def comp (f : Y → Z) (g: X → Y) : X → Z :=
λx, f(g x)

infixr ` ∘ ` := comp

def id (x: X) : X :=
x
-/
-- funext is function extensionality (what?)
example (f g : X → Y) (h: ∀ x, f x = g x) : f = g:=
funext h

-- rfl is reflexivity
lemma left_id (f : X → Y) : id ∘ f = f :=
rfl

lemma right_id (f: X → Y) : f ∘ id = f :=
rfl

theorem comp.assoc (f: Z → W) (g: Y → Z) (h: X → Y) :
(f ∘ g) ∘ h = f ∘ (g ∘ h) := rfl

theorem comp.left_id (f: X → Y) : id ∘ f = f := rfl

theorem comp.right_id (f: X → Y) :f ∘ id = f := rfl

-- The double '{}', ⦃ ⦄ is super important
-- If we write ∀ x₁ : X, ∀ x₂ : X, injective_cmp is not provable
def injective {X Y} (f : X → Y) : Prop :=
  ∀ ⦃ x₁ x₂ ⦄, f x₁ = f x₂ → x₁ = x₂

/-- A function `f : α → β` is called injective if `f x = f y` implies `x = y`. -/
--@[reducible] def injective (f : α → β) : Prop := ∀ ⦃a₁ a₂⦄, f a₁ = f a₂ → a₁ = a₂

def injective' (f : X → Y) : Prop :=
  ∀ ⦃ x₁ x₂ ⦄, f x₁ = f x₂ → x₁ = x₂

-- We can not write x₁ ∈ X, use x₁ : X instead
def injective2 (f: X → Y) : Prop :=
 ∀ ⦃ x₁ x₂ ⦄, x₁ ≠ x₂ → f x₁ ≠ f x₂

def injective3 (f: X → Y) : Prop :=
 ∀ ⦃ a a' ⦄, a ≠ a' → f a ≠ f a'

def injective4 (f: X → Y) : Prop :=
 ∀ ⦃ a a' ⦄, f a = f a' → a = a'

-- We can eliminate writing :Y, :X
def surjective (f: X → Y) : Prop :=
 ∀ y, ∃ x, f x = y

def bijective (f: X → Y) := injective f ∧ surjective f

/--
 What is @id mean?
 @ is for preventing to fill implicit argument
 and forcing to fill statement.
 ... what?

 If we get rid of @ from "@id", lean raises type
 error anyway.
--/

/-
Let's prove identity function is injective.

The syntax of injective proposition is defined with
def injective (f: X → Y) : Prop
So "injective (@id X)" means,
given function id with domain X, it is injective.
-/
theorem injective_id : injective (@id X) :=
assume x₁ x₂,
assume H : id x₁ = id x₂,
show x₁ = x₂, from H -- prove this from def of id

theorem injective_id3 : injective3 (@id X) :=
assume a a',
assume H : a ≠ a',  -- contraposition can not be proved automatically
show id a ≠ id a', from H

theorem injective_id4 : injective4 (@id X) :=
assume a a',
assume H : id a = id a',
show a = a', from H

-- prove identity function is subjective
theorem surjective_id : surjective (@id X) :=
assume y,
show ∃ x, id x = y, from exists.intro y rfl

-- prove identy function is bijective
theorem bijective_id : bijective(@id X) :=
and.intro injective_id surjective_id

-- prove compisition of injective functions
-- is injective

-- Why can not prove this?  Isn't this a tautology?
-- lemma injective_g   {g : Y → Z}
--                  (Hg: injective g) : injective (g) :=
--assume y₁ y₂,
--assume h: g y₁ = g y₂,
--show y₁ = y₂, from h Hg

-- theorem: if g and f are both injective, g ∘ f is injective
theorem injective_comp {g : Y → Z} {f : X → Y}
    (Hg : injective g) (Hf : injective f) : injective (g ∘ f) :=
assume x₁ x₂ : X,
--assume y₁ y₂ : Y, -- This ': Y' is necessary for the following
--assume  h2: g y₁ = g y₂,
--have y₁ = y₂, from h2 Hg,
assume h1 : (g ∘ f) x₁ = (g ∘ f) x₂,
-- If f x₁ = f x₂ → x₁ = x₂ from Hf
-- If g x₂ = g x₂ → x₁ = x₂ from Hg
-- If (g ∘ f) x₁ = (g ∘ f) x₂ → x₁ = x₂
-- How to prove the following prop?
--  If (g ∘ f) x₁ = (g ∘ f) x₂ → f x₁ = f ×2
-- Since g x₁ = g x₂ → x₁ = x₂ by Hg
-- ALso f ×₁ = f x₂ → x₁ = x₂ by Hf
have f x₁ = f x₂, from Hg h1,
show x₁ = x₂, from Hf this

example (q r : Prop) : q ∧ r → q :=
assume h: q ∧ r,
have r, from and.right h,
show q, from and.left h

#check Type

variable U : Type
variable P : U → Prop
variable Q : Prop
-- introduce a new variable y and prove Q
-- under the assumption that P y holds.
example (h1 : ∃ x, P x) (h2: ∀ x, P x → Q) : Q :=
exists.elim h1
  (assume (y : U) (h: P y),
    have h3 : P y → Q, from h2 y,
    show Q, from h3 h)

example (h1: ∃ x, P x) (h2: ∀ x, P x → Q) : Q :=
exists.elim h1 (assume y h, h2 y h)

example (h1: ∃ x, P x) (h2: ∀ x, P x → Q) : Q :=
exists.elim h1 $
assume y h, h2 y h

--def surjective (f: X → Y) : Prop :=
-- ∀ y, ∃ x, f x = y
-- What is "$"?
--   -> It is a syntax suger of '( some proof )'
theorem surjective_comp {g : Y → Z} {f: X → Y}
  (hg: surjective g) (hf: surjective f) :
  surjective (g ∘ f) :=
  assume z,
  exists.elim (hg z) $
  assume y (hy : g y = z),
  exists.elim (hf y) $
  assume x (hx : f x = y),
  have g (f x) = z, from eq.subst (eq.symm hx) hy,
  show ∃ x, g (f x) = z, from exists.intro x this


/-
Copyright (c) 2020 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov, Patrick Massot
-/
import data.set.intervals.proj_Icc
import topology.algebra.order.basic

/-!
# Projection onto a closed interval

In this file we prove that the projection `set.proj_Icc f a b h` is a quotient map, and use it
to show that `Icc_extend h f` is continuous if and only if `f` is continuous.
-/

open set filter
open_locale filter topological_space

variables {α β γ : Type*} [linear_order α] [topological_space γ] {a b c : α} {h : a ≤ b}

lemma filter.tendsto.Icc_extend (f : γ → Icc a b → β) {z : γ} {l : filter α} {l' : filter β}
  (hf : tendsto ↿f (𝓝 z ×ᶠ l.map (proj_Icc a b h)) l') :
  tendsto ↿(Icc_extend h ∘ f) (𝓝 z ×ᶠ l) l' :=
show tendsto (↿f ∘ prod.map id (proj_Icc a b h)) (𝓝 z ×ᶠ l) l', from
hf.comp $ tendsto_id.prod_map tendsto_map

variables [topological_space α] [order_topology α] [topological_space β]

@[continuity]
lemma continuous_proj_Icc : continuous (proj_Icc a b h) :=
continuous_subtype_mk _ $ continuous_const.max $ continuous_const.min continuous_id

lemma quotient_map_proj_Icc : quotient_map (proj_Icc a b h) :=
quotient_map_iff.2 ⟨proj_Icc_surjective h, λ s,
  ⟨λ hs, hs.preimage continuous_proj_Icc,
   λ hs, ⟨_, hs, by { ext, simp }⟩⟩⟩

@[simp] lemma continuous_Icc_extend_iff {f : Icc a b → β} :
  continuous (Icc_extend h f) ↔ continuous f :=
quotient_map_proj_Icc.continuous_iff.symm

/-- See Note [continuity lemma statement]. -/
lemma continuous.Icc_extend {f : γ → Icc a b → β} {g : γ → α}
  (hf : continuous ↿f) (hg : continuous g) : continuous (λ a, Icc_extend h (f a) (g a)) :=
hf.comp $ continuous_id.prod_mk $ continuous_proj_Icc.comp hg

/-- A useful special case of `continuous.Icc_extend`. -/
@[continuity]
lemma continuous.Icc_extend' {f : Icc a b → β} (hf : continuous f) : continuous (Icc_extend h f) :=
hf.comp continuous_proj_Icc

lemma continuous_at.Icc_extend {x : γ} (f : γ → Icc a b → β) {g : γ → α}
  (hf : continuous_at ↿f (x, proj_Icc a b h (g x))) (hg : continuous_at g x) :
  continuous_at (λ a, Icc_extend h (f a) (g a)) x :=
show continuous_at (↿f ∘ λ x, (x, proj_Icc a b h (g x))) x, from
continuous_at.comp hf $ continuous_at_id.prod $ continuous_proj_Icc.continuous_at.comp hg


/-  Math40001 : Introduction to university mathematics.

Problem Sheet 3, October 2019.

This is a Lean file. It can be read with the Lean theorem prover.

You can work on this file online at
https://tinyurl.com/Lean-M40001-Example-Sheet-3

or you can install Lean and its maths library following the
instructions at
https://github.com/leanprover-community/mathlib#installation

There are advantages to installing Lean on your own computer
(for example it's faster), but it's more hassle than
just using it online.

In the below, delete "sorry" and replace it with some
tactics which prove the result.

-/

import data.real.basic -- the real numbers

/- Question 1.

  Say $X$, $Y$ and $Z$ are sets, and $f:X\to Y$ and $g:Y\to Z$ are functions. In lectures we proved that if
$f$ and $g$ are injective, then $g\circ f$ is also injective, and we will prove on Monday that if $f$ and $g
$ are surjective, then $g\circ f$ is surjective. But what about the other way?
  \begin{enumerate}
  \item If $g\circ f$ is injective, then is $f$ injective? Give a proof or a counterexample.
  \item If $g\circ f$ is injective, then is $g$ injective? Give a proof or a counterexample.
  \item If $g\circ f$ is surjective, then is $f$ surjective? Give a proof or a counterexample.
  \item If $g\circ f$ is surjective, then is $g$ surjective? Give a proof or a counterexample.
  \end{enumerate}
-/

open function

-- in Q1 you would be best off defining the counterexample explicitly before you embark upon the
-- disproofs of the false statements

-- put ¬ in front of the ∀ and put everything in brackets if you want to disprove it
lemma question_one_a_true : ∀ (X Y Z : Type) (f : X → Y) (g : Y → Z), injective (g ∘ f) → injective f :=
begin
  sorry
end

-- put ¬ in front of the ∀ and put everything in brackets if you want to disprove it
lemma question_one_b_true : ∀ (X Y Z : Type) (f : X → Y) (g : Y → Z), injective (g ∘ f) → injective g :=
begin
  sorry
end

-- put ¬ in front of the ∀ and put everything in brackets if you want to disprove it
lemma question_one_c_true : ∀ (X Y Z : Type) (f : X → Y) (g : Y → Z), surjective (g ∘ f) → surjective f :=
begin
  sorry
end

-- put ¬ in front of the ∀ and put everything in brackets if you want to disprove it
lemma question_one_d_true : ∀ (X Y Z : Type) (f : X → Y) (g : Y → Z), surjective (g ∘ f) → surjective g :=
begin
  sorry
end

/-
% Q2
\item For each of the following functions, decide whether or not thprop_decidableey are injective, surjective, bijective.
Proofs required!

  \begin{enumerate}
    \item $f:\R\to\R$, $f(x)=1/x$ if $x\not=0$ and $f(0)=0$.
\item  $f : \R\to\R$, $f(x)=2x+1$.
\item  $f:\Z\to\Z$, $f(x)=2x+1$.
\item  $f:\R\to\R$ defined by $f(x)=3-x$ if the Riemann hypothesis is true, and $f(x)=2+x$ if not. [NB the \
href{https://en.wikipedia.org/wiki/Riemann_hypothesis}{Riemann Hypothesis} is a hard unsolved problem in mat
hematics; nobody currently knows if it is true or false.]
\item $f:\Z\to\Z$, $f(n)=n^3-2n^2+2n-1$.
\end{enumerate}

-/

-- this line just says "we're mathematicians so every proposition is either true or false"
local attribute [instance, priority 10] classical.prop_decidable

-- this line says "a function might not be defined by an algorithm"
noncomputable theory

-- definition of the functions in Q2."λ x," is the way computer scientists say "x ↦"

def fa : ℝ → ℝ := λ x, 1 / x -- Lean defines 1/0 to be 0

def fb : ℝ → ℝ := λ x, 2 * x + 1

def fc : ℤ → ℤ := λ x, 2 * x + 1

constant Riemann_Hypothesis : Prop -- doesn't matter what it says

def fd : ℝ → ℝ := λ x, if Riemann_Hypothesis then 3 - x else 2 + x

def fe : ℤ → ℤ := λ n, n ^ 3 - 2 * n ^ 2 + 2 * n - 1

-- now write your own questions, below are some examples (that may or may not be possible to prove)
lemma Q2a1 : injective fa := sorry
lemma Q2a2 : ¬ (surjective fa) := sorry
lemma Q2c3 : bijective fc := sorry

/-
Question 3 is "why does this not make sense" so it can't be formalised.
-/

/-
  % Q4
\item  Prove the claim I will make in lecture on Monday, saying that if $f:X\to Y$ is a function, and $g:Y\t
o X$ is a two-sided inverse of~$f$, then~$f$ is a two-sided inverse for~$g$. Deduce that if~$X$ and~$Y$ are
sets, and there exists a bijection from~$X$ to~$Y$, then there exists a bijection from~$Y$ to~$X$.
-/

lemma Q4a (X Y : Type) (f : X → Y) (g : Y → X) (h2sided : (∀ x : X, g (f x) = x) ∧ (∀ y : Y, f (g y) = y)) :
(∀ y : Y, f (g y) = y) ∧ (∀ x : X, g (f x) = x) := sorry

-- you will need this result to do the second part. Ignore the proof, I'm using term mode just to
-- make it quicker. Note that this crazy-looking proof is an indication that there are other
-- ways of using Lean apart from tactic mode.

lemma exists_bijection_iff_has_two_sided_inverse (X Y : Type) :
(∃ f : X → Y, bijective f) ↔ (∃ (f : X → Y), ∃ (g : Y → X), (∀ x : X, g (f x) = x) ∧ (∀ y : Y, f (g y) = y)) :=
⟨λ ⟨f, hf⟩, ⟨f, bijective_iff_has_inverse.1 hf⟩,
 λ ⟨f, g, hgf, hfg⟩, ⟨f, bijective_iff_has_inverse.2 ⟨g, hgf, hfg⟩⟩⟩

lemma Q4b (X Y : Type) : (∃ f : X → Y, bijective f) ↔ (∃ g : Y → X, bijective g) := sorry

/-
  % Q5
  \item Let~$Z$ be a set. If $f:X\to Z$ and $g:Y\to Z$ are injective functions, let's say that $f$ \emph{is
friends with} $g$ if there is a bijection $h:X\to Y$ such that $f=g\circ h$. Prove that $f$ is friends with
$g$ if and only if the image of~$f$ equals the image of~$g$. NB: by the \emph{image} of $f:X\to Z$ I mean th
e subset of~$Z$ consisting of things ``hit'' by $f$, in other words the set $\{z\in Z\,:\,\exists x\in X, f(
x)=z\}$. Some people call this the ``range'' of $f$, although other people use ``range'' to mean the same th
ing as ``codomain'' :-|

-/

def friends {X Y Z : Type} (f : X → Z) (g : Y → Z) (hf : injective f) (hg : injective g) :=
  ∃ h : X → Y, bijective h ∧ f = g ∘ h

lemma Q5 (X Y Z : Type) (f : X → Z) (g : Y → Z) (hf : injective f) (hg : injective g) :
friends f g hf hg ↔ set.range f = set.range g := sorry


/-In this tutorial, we aim to introduce using logic in Lean. We will firstly look at
defining logical quantifiers.#check

The symbol '∧', which can be written by \and means and. Therefore, for logical
statements A and B, A ∧ B means A and B.

Furthermore, the symbol '∨' means or, which can be typed out using \or .

When using Lean, we also need to use tactics. These are commands that Lean interprets
which help us to progress towards closing the goal.

Firstly, if we have a hypothesis which is identical to our goal, say h, then we can
use the 'exact h,' tactic to close our goal.

Secondly, the 'intro' tactic can be used whenever we have an '↔' if implication
in our goal. For example, if our goal is of the form
⊢ A → B
then by typing 'intro h,' we introduce a new hypothesis h:A with our goal
changing to ⊢ B.

The 'left,' and 'right,' tactics can be used when we have a goal containing an
∨ symbol. 'left,' will change a goal of the form ⊢A ∨ B to ⊢ A.
On the other hand, by typing 'right,' a goal of the form ⊢ A ∨ B will change
to ⊢ B.#check

Furthermore, the 'split' tactic can be used when our goal contains an '∧' symbol.
'split' will change a goal of the form ⊢ A ∧ B to two separate goals, ⊢ A and ⊢ B
which we prove separately. We can also use 'split,' whenever we have an '↔' iff
statement in the goal.

We may also have an instance where we want to consider the cases when A is true
and false separately. This can be done by deploying the 'by_cases' tactic. By writing
something similar to 'by_cases h:A,' we will introduce two identical goals, one with
a hypothesis h:A and the other with the hypothesis h:¬A.#check

Finally, if one of our hypotheses contain an '∧' or a '∨' statement, we can use the
cases tactic. If our hypothesis is of the form h: A ∧ B, by typing
'cases h with h₁ h₂,'
we will transform the hypothesis h into two simpler hypotheses h₁:A and h₂:B
which are more easily handled.
Furthermore, if we have a hypothesis of the form h: A ∨ B, and one goal, then by typing
'cases h with h₁ h₂,'
we will change our hypothesis h into two separate hypotheses h₁ : A and h₂ : B
which are each placed separately in two different goals. This means that we consider
when A and B are true separately.

We will now provide a few examples of logic in Lean. The statement we wish to prove
will follow after the word 'example' and our proof goes between the 'begin' and 'end'
commands. For any incomplete exercise, remove the sorry to begin the proof.
-/

example (A : Prop) (h:A) : A:=
begin
exact h,
end

example (A B : Prop) (h : B) : A ∨ B :=
begin
right,
exact h,
end

example (A B : Prop) (h₁: A) (h₂: B) : A ∧ B :=
begin
split,
exact h₁,
exact h₂,
end

example (A : Prop) : A ∨ ¬ A :=
begin
by_cases h : A,
left,
exact h,
right,
exact h,
end

example (A B : Prop) (h: A ∧ ¬ B) : ¬ B ∧ A :=
begin
cases h with h₁ h₂,
split,
exact h₂,
exact h₁,
end

example (A B C : Prop) (h : (A ∧ B) ∨ C) : B ∨ C :=
begin
cases h with h₁ h₂,
cases h₁ with h₂ h₃,
left,
exact h₃,
right,
exact h₂,
end

example (A B : Prop) : A ∧ B → A ∨ B :=
begin
intro h,
cases h with h₁ h₂,
left,
exact h₁,
end

example (A B : Prop): (A ∨ ¬ A) ∧ (B ∨ ¬B) :=
begin
split,
by_cases h:A,
left,
exact h,
right,
exact h,
by_cases h:B,
left,
exact h,
right,
exact h,
end

theorem lawofcomm (A B : Prop) : A∧B ↔ B∧A:=
begin
split, --We consider the separate implications ⊢ A ∧ B → B ∧ A and ⊢B ∧ A → A ∧ B
intro h₁, --This lets us consider the hypothesis h₁: A ∧ B
cases h₁ with h₂ h₃, -- h₁:A ∧ B changes to h₂:A and h₃:B
split,--Instead of showing B ∧ A we show B and A separately
exact h₃, --Our goal, ⊢B is the same as our hypothesis h₃
exact h₂, --Our goal, ⊢A is the same as our hypothesis h₂
intro h₁, --Due to the symmetric nature of the proof, we just repeat what we've written above
cases h₁ with h₂ h₃,
split,
exact h₃,
exact h₂,
end

--Now it's time for you to have a go. Delete the sorry and continue the proofs.

example (A B : Prop) : A ∨ B ∨ ¬ A ∨ B :=
begin
sorry,
end

theorem lawofassoc (A B C : Prop) : A∧(B∧C) ↔ (A∧B)∧C:=
begin
sorry,
end

theorem lawofdist (A B C : Prop) : A∧(B∨C) ↔ (A∧B)∨(A∧C):=
begin
sorry,
end

theorem de_morgan1 (A B : Prop) : ¬ (A∨B) ↔ (¬A) ∧ (¬B) :=
begin
sorry,
end

theorem de_morgan2 (A B : Prop) : ¬(A∧B) ↔ (¬A)∨(¬B):=
begin
sorry,
end


/-
Copyright (c) 2015 William Peterson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: William Peterson, Jeremy Avigad

Extended gcd, Bezout's theorem, chinese remainder theorem.
-/
import data.nat.div data.int .primes

/- Bezout's theorem -/

section Bezout

open nat int
open eq.ops well_founded decidable prod

private definition pair_nat.lt : ℕ × ℕ → ℕ × ℕ → Prop := measure pr₂
private definition pair_nat.lt.wf : well_founded pair_nat.lt := intro_k (measure.wf pr₂) 20
local attribute pair_nat.lt.wf [instance]
local infixl `≺`:50 := pair_nat.lt

private definition gcd.lt.dec (x y₁ : ℕ) : (succ y₁, x % succ y₁) ≺ (x, succ y₁) :=
!nat.mod_lt (succ_pos y₁)

private definition egcd_rec_f (z : ℤ) : ℤ → ℤ → ℤ × ℤ := λ s t, (t, s - t * z)

definition egcd.F : Π (p₁ : ℕ × ℕ), (Π p₂ : ℕ × ℕ, p₂ ≺ p₁ → ℤ × ℤ) → ℤ × ℤ
| (x, y) := nat.cases_on y
              (λ f, (1, 0) )
              (λ y₁ (f : Π p₂, p₂ ≺ (x, succ y₁) → ℤ × ℤ),
                let bz := f (succ y₁, x % succ y₁) !gcd.lt.dec in
                prod.cases_on bz (egcd_rec_f (x / succ y₁)))

definition egcd (x y : ℕ) := fix egcd.F (pair x y)

theorem egcd_zero (x : ℕ) : egcd x 0 = (1, 0) :=
well_founded.fix_eq egcd.F (x, 0)

theorem egcd_succ (x y : ℕ) :
  egcd x (succ y) = prod.cases_on (egcd (succ y) (x % succ y)) (egcd_rec_f (x / succ y)) :=
well_founded.fix_eq egcd.F (x, succ y)

theorem egcd_of_pos (x : ℕ) {y : ℕ} (ypos : y > 0) :
  let erec := egcd y (x % y), u := pr₁ erec, v := pr₂ erec in
    egcd x y = (v, u - v * (x / y)) :=
obtain (y' : nat) (yeq : y = succ y'), from exists_eq_succ_of_pos ypos,
begin
  rewrite [yeq, egcd_succ, -prod.eta (egcd _ _)],
  esimp, unfold egcd_rec_f,
  rewrite [of_nat_div]
end

theorem egcd_prop (x y : ℕ) : (pr₁ (egcd x y)) * x + (pr₂ (egcd x y)) * y = gcd x y :=
gcd.induction x y
  (take m, by krewrite [egcd_zero, mul_zero, one_mul])
  (take m n,
    assume npos : 0 < n,
    assume IH,
    begin
      note H := egcd_of_pos m npos, esimp at H,
      rewrite H,
      esimp,
      rewrite [gcd_rec, -IH],
      rewrite [add.comm],
      rewrite [-of_nat_mod],
      rewrite [int.mod_def],
      rewrite [+mul_sub_right_distrib],
      rewrite [+mul_sub_left_distrib, *left_distrib],
      rewrite [*sub_eq_add_neg, {pr₂ (egcd n (m % n)) * of_nat m + - _}add.comm],
      rewrite [-add.assoc, mul.assoc]
    end)

theorem Bezout_aux (x y : ℕ) : ∃ a b : ℤ, a * x + b * y = gcd x y :=
exists.intro _ (exists.intro _ (egcd_prop x y))

theorem Bezout (x y : ℤ) : ∃ a b : ℤ, a * x + b * y = gcd x y :=
obtain a' b' (H : a' * nat_abs x + b' * nat_abs y = gcd x y), from !Bezout_aux,
begin
  existsi (a' * sign x),
  existsi (b' * sign y),
  rewrite [*mul.assoc, -*abs_eq_sign_mul, -*of_nat_nat_abs],
  apply H
end
end Bezout

/-
A sample application of Bezout's theorem, namely, an alternative proof that irreducible
implies prime (dvd_or_dvd_of_prime_of_dvd_mul).
-/

namespace nat
open int

example {p x y : ℕ} (pp : prime p) (H : p ∣ x * y) : p ∣ x ∨ p ∣ y :=
decidable.by_cases
  (suppose p ∣ x, or.inl this)
  (suppose ¬ p ∣ x,
    have cpx : coprime p x, from coprime_of_prime_of_not_dvd pp this,
    obtain (a b : ℤ) (Hab : a * p + b * x = gcd p x), from Bezout_aux p x,
    have a * p * y + b * x * y = y,
      by krewrite [-right_distrib, Hab, ↑coprime at cpx, cpx, int.one_mul],
    have p ∣ y,
      begin
        apply dvd_of_of_nat_dvd_of_nat,
        rewrite [-this],
        apply @dvd_add,
          {apply dvd_mul_of_dvd_left,
            apply dvd_mul_of_dvd_right,
            apply dvd.refl},
          {rewrite mul.assoc,
            apply dvd_mul_of_dvd_right,
            apply of_nat_dvd_of_nat_of_dvd H}
      end,
    or.inr this)

end nat


-- ---------------------------------------------------------------------
-- Ejercicio 1. Importar la librería de las tácticas.
-- ----------------------------------------------------------------------

import tactic

-- ---------------------------------------------------------------------
-- Ejercicio 2. Declarar R como una variable de tipo de los anillos
-- conmutativos.
-- ----------------------------------------------------------------------

variables (R : Type*) [comm_ring R]

-- ---------------------------------------------------------------------
-- Ejercicio 3. Declarar a, b, c y d como variables sobre R.
-- ----------------------------------------------------------------------

variables a b c d : R

-- ---------------------------------------------------------------------
-- Ejercicio 4. Demostrar que
--     (c * b) * a = b * (a * c)
-- ----------------------------------------------------------------------

example : (c * b) * a = b * (a * c) :=
by ring

-- ---------------------------------------------------------------------
-- Ejercicio 5. Demostrar que
--     (a + b) * (a + b) = a * a + 2 * (a * b) + b * b
-- ----------------------------------------------------------------------

example : (a + b) * (a + b) = a * a + 2 * (a * b) + b * b :=
by ring

-- ---------------------------------------------------------------------
-- Ejercicio 6. Demostrar que
--     (a + b) * (a - b) = a^2 - b^2
-- ----------------------------------------------------------------------

example : (a + b) * (a - b) = a^2 - b^2 :=
by ring

-- ---------------------------------------------------------------------
-- Ejercicio 7. Demostrar que si
--    c = d * a + b
--    b = a * d
-- entonces
--    c = 2 * a * d
-- ----------------------------------------------------------------------

example
  (h1 : c = d * a + b)
  (h2 : b = a * d)
  : c = 2 * a * d :=
begin
  rw [h1, h2],
  ring
end


/-
Copyright (c) 2021 Alena Gusakov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alena Gusakov
-/
import combinatorics.simple_graph.basic
import data.set.finite
/-!
# Strongly regular graphs

## Main definitions

* `G.is_SRG_of n k l m` (see `is_simple_graph.is_SRG_of`) is a structure for a `simple_graph`
  satisfying the following conditions:
  * The cardinality of the vertex set is `n`
  * `G` is a regular graph with degree `k`
  * The number of common neighbors between any two adjacent vertices in `G` is `l`
  * The number of common neighbors between any two nonadjacent vertices in `G` is `m`

## TODO
- Prove that the complement of a strongly regular graph is strongly regular with parameters
  `is_SRG_of n (n - k - 1) (n - 2 - 2k + m) (v - 2k + l)`
- Prove that the parameters of a strongly regular graph
  obey the relation `(n - k - 1) * m = k * (k - l - 1)`
- Prove that if `I` is the identity matrix and `J` is the all-one matrix,
  then the adj matrix `A` of SRG obeys relation `A^2 = kI + lA + m(J - I - A)`
-/

universes u

namespace simple_graph
variables {V : Type u}
variables (G : simple_graph V) [decidable_rel G.adj]

variables [fintype V] [decidable_eq V]

/--
A graph is strongly regular with parameters `n k l m` if
 * its vertex set has cardinality `n`
 * it is regular with degree `k`
 * every pair of adjacent vertices has `l` common neighbors
 * every pair of nonadjacent vertices has `m` common neighbors
-/
structure is_SRG_of (n k l m : ℕ) : Prop :=
(card : fintype.card V = n)
(regular : G.is_regular_of_degree k)
(adj_common : ∀ (v w : V), G.adj v w → fintype.card (G.common_neighbors v w) = l)
(nadj_common : ∀ (v w : V), ¬ G.adj v w ∧ v ≠ w → fintype.card (G.common_neighbors v w) = m)

open finset

/-- Complete graphs are strongly regular. Note that the parameter `m` can take any value
  for complete graphs, since there are no distinct pairs of nonadjacent vertices. -/
lemma complete_strongly_regular (m : ℕ) :
  (complete_graph V).is_SRG_of (fintype.card V) (fintype.card V - 1) (fintype.card V - 2) m :=
{ card := rfl,
  regular := complete_graph_degree,
  adj_common := λ v w (h : v ≠ w),
    begin
      simp only [fintype.card_of_finset, mem_common_neighbors, complete_graph, ne.def, filter_not,
        ←not_or_distrib, filter_eq, filter_or, card_univ_diff, mem_univ, if_pos, ←insert_eq],
      rw [card_insert_of_not_mem, card_singleton],
      simpa,
    end,
  nadj_common := λ v w (h : ¬(v ≠ w) ∧ _), (h.1 h.2).elim }

end simple_graph


import Mynat.Mul

namespace mynat

def mypow (m n : mynat) : mynat :=
  match n with
  | 0 => 1
  | succ n' => mymul (mypow m n') m

instance : Pow mynat mynat where
  pow := mypow

theorem pow_zero (a : mynat) : a ^ (0 : mynat) = 1 := rfl
theorem pow_succ (a b : mynat) : a ^ (succ b) = a ^ b * a := rfl

theorem zero_pow_zero : (0 : mynat) ^ (0 : mynat) = 1 := by
  rw [pow_zero]

theorem zero_pow_succ (m : mynat) : (0 : mynat) ^ (succ m) = 0 := by
  rw [pow_succ]
  rw [mul_zero]

theorem pow_one (a : mynat) : a ^ (1 : mynat) = a := by
  rw [one_eq_succ_zero]
  rw [pow_succ]
  rw [pow_zero]
  rw [one_mul]

theorem one_pow (m : mynat) : (1 : mynat) ^ m = 1 := by
  cases m
  case zero =>
    rw [mynat_zero_eq_zero]
    rw [pow_zero]
  case succ m' =>
    rw [pow_succ]
    rw [one_pow m']
    rw [one_mul]

theorem pow_add (a m n : mynat) : a ^ (m + n) = a ^ m * a ^ n := by
  cases n
  case zero =>
    rw [mynat_zero_eq_zero]
    rw [add_zero]
    rw [pow_zero]
    rw [mul_one]
  case succ n' =>
    rw [add_succ]
    rw [pow_succ]
    rw [pow_succ]
    rw [← mul_assoc (a ^ m) (a ^ n') a]
    rw [pow_add a m n']

theorem mul_pow (a b n : mynat) : (a * b) ^ n = a ^ n * b ^ n := by
  cases n
  case zero =>
    rw [mynat_zero_eq_zero]
    repeat {rw [pow_zero]}
    rfl
  case succ n' =>
    rw [pow_succ]
    rw [pow_succ]
    rw [pow_succ]
    rw [mul_pow a b n']
    rw [mul_assoc (a ^ n') a (b ^ n' * b)]
    rw [← mul_assoc a (b ^ n') b]
    rw [mul_comm a (b ^ n')]
    rw [mul_assoc (b ^ n') a b]
    rw [mul_assoc (a ^ n') (b ^ n') (a * b)]

theorem pow_pow (a m n : mynat) : (a ^ m) ^ n = a ^ (m * n) := by
  cases n
  case zero =>
    rw [mynat_zero_eq_zero]
    rw [pow_zero]
    rw [mul_zero]
    rw [pow_zero]
  case succ n' =>
    rw [pow_succ]
    rw [pow_pow a m n']
    rw [← pow_add]
    rw [mul_succ]

theorem add_squared (a b : mynat) :
  (a + b) ^ (2 : mynat) = a ^ (2 : mynat) + b ^ (2 : mynat) + 2 * a * b := by
  have h2 : (2 : mynat) = succ 1 := rfl
  rw [h2]
  rw [one_eq_succ_zero]
  rw [pow_succ]
  rw [pow_succ]
  rw [pow_succ]
  rw [pow_succ]
  rw [pow_succ]
  rw [pow_succ]
  rw [pow_zero]
  rw [one_mul]
  rw [pow_zero]
  rw [pow_zero]
  rw [one_mul]
  rw [one_mul]
  rw [succ_mul]
  rw [succ_mul]
  rw [zero_mul]
  rw [zero_add]
  rw [add_mul]
  rw [mul_add]
  rw [mul_add]
  rw [add_mul]
  rw [add_assoc]
  rw [add_assoc]
  rw [← add_assoc (b * b) (a * b) (a * b)]
  rw [add_comm (b * b) (a * b)]
  rw [add_assoc]
  rw [add_comm (b * b) (a * b)]
  rw [mul_comm a b]

end mynat