/-
Copyright (c) 2018 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Floris van Doorn, Gabriel Ebner, Yury Kudryashov
-/
import data.nat.enat
import order.conditionally_complete_lattice

/-!
# Conditionally complete linear order structure on `ℕ`

In this file we

* define a `conditionally_complete_linear_order_bot` structure on `ℕ`;
* define a `complete_linear_order` structure on `enat`;
* prove a few lemmas about `supr`/`infi`/`set.Union`/`set.Inter` and natural numbers.
-/

open set

namespace nat

open_locale classical

noncomputable instance : has_Inf ℕ :=
⟨λs, if h : ∃n, n ∈ s then @nat.find (λn, n ∈ s) _ h else 0⟩

noncomputable instance : has_Sup ℕ :=
⟨λs, if h : ∃n, ∀a∈s, a ≤ n then @nat.find (λn, ∀a∈s, a ≤ n) _ h else 0⟩

lemma Inf_def {s : set ℕ} (h : s.nonempty) : Inf s = @nat.find (λn, n ∈ s) _ h :=
dif_pos _

lemma Sup_def {s : set ℕ} (h : ∃n, ∀a∈s, a ≤ n) :
  Sup s = @nat.find (λn, ∀a∈s, a ≤ n) _ h :=
dif_pos _

@[simp] lemma Inf_eq_zero {s : set ℕ} : Inf s = 0 ↔ 0 ∈ s ∨ s = ∅ :=
begin
  cases eq_empty_or_nonempty s,
  { subst h, simp only [or_true, eq_self_iff_true, iff_true, Inf, has_Inf.Inf,
      mem_empty_eq, exists_false, dif_neg, not_false_iff] },
  { have := ne_empty_iff_nonempty.mpr h,
    simp only [this, or_false, nat.Inf_def, h, nat.find_eq_zero] }
end

@[simp] lemma Inf_empty : Inf ∅ = 0 :=
by { rw Inf_eq_zero, right, refl }

lemma Inf_mem {s : set ℕ} (h : s.nonempty) : Inf s ∈ s :=
by { rw [nat.Inf_def h], exact nat.find_spec h }

lemma not_mem_of_lt_Inf {s : set ℕ} {m : ℕ} (hm : m < Inf s) : m ∉ s :=
begin
  cases eq_empty_or_nonempty s,
  { subst h, apply not_mem_empty },
  { rw [nat.Inf_def h] at hm, exact nat.find_min h hm }
end

protected lemma Inf_le {s : set ℕ} {m : ℕ} (hm : m ∈ s) : Inf s ≤ m :=
by { rw [nat.Inf_def ⟨m, hm⟩], exact nat.find_min' ⟨m, hm⟩ hm }

lemma nonempty_of_pos_Inf {s : set ℕ} (h : 0 < Inf s) : s.nonempty :=
begin
  by_contradiction contra, rw set.not_nonempty_iff_eq_empty at contra,
  have h' : Inf s ≠ 0, { exact ne_of_gt h, }, apply h',
  rw nat.Inf_eq_zero, right, assumption,
end

lemma nonempty_of_Inf_eq_succ {s : set ℕ} {k : ℕ} (h : Inf s = k + 1) : s.nonempty :=
nonempty_of_pos_Inf (h.symm ▸ (succ_pos k) : Inf s > 0)

lemma eq_Ici_of_nonempty_of_upward_closed {s : set ℕ} (hs : s.nonempty)
  (hs' : ∀ (k₁ k₂ : ℕ), k₁ ≤ k₂ → k₁ ∈ s → k₂ ∈ s) : s = Ici (Inf s) :=
ext (λ n, ⟨λ H, nat.Inf_le H, λ H, hs' (Inf s) n H (Inf_mem hs)⟩)

lemma Inf_upward_closed_eq_succ_iff {s : set ℕ}
  (hs : ∀ (k₁ k₂ : ℕ), k₁ ≤ k₂ → k₁ ∈ s → k₂ ∈ s) (k : ℕ) :
  Inf s = k + 1 ↔ k + 1 ∈ s ∧ k ∉ s :=
begin
  split,
  { intro H,
    rw [eq_Ici_of_nonempty_of_upward_closed (nonempty_of_Inf_eq_succ H) hs, H, mem_Ici, mem_Ici],
    exact ⟨le_refl _, k.not_succ_le_self⟩, },
  { rintro ⟨H, H'⟩,
    rw [Inf_def (⟨_, H⟩ : s.nonempty), find_eq_iff],
    exact ⟨H, λ n hnk hns, H' $ hs n k (lt_succ_iff.mp hnk) hns⟩, },
end

/-- This instance is necessary, otherwise the lattice operations would be derived via
conditionally_complete_linear_order_bot and marked as noncomputable. -/
instance : lattice ℕ := lattice_of_linear_order

noncomputable instance : conditionally_complete_linear_order_bot ℕ :=
{ Sup := Sup, Inf := Inf,
  le_cSup    := assume s a hb ha, by rw [Sup_def hb]; revert a ha; exact @nat.find_spec _ _ hb,
  cSup_le    := assume s a hs ha, by rw [Sup_def ⟨a, ha⟩]; exact nat.find_min' _ ha,
  le_cInf    := assume s a hs hb,
    by rw [Inf_def hs]; exact hb (@nat.find_spec (λn, n ∈ s) _ _),
  cInf_le    := assume s a hb ha, by rw [Inf_def ⟨a, ha⟩]; exact nat.find_min' _ ha,
  cSup_empty :=
  begin
    simp only [Sup_def, set.mem_empty_eq, forall_const, forall_prop_of_false, not_false_iff,
      exists_const],
    apply bot_unique (nat.find_min' _ _),
    trivial
  end,
  .. (infer_instance : order_bot ℕ), .. (lattice_of_linear_order : lattice ℕ),
  .. (infer_instance : linear_order ℕ) }

lemma Inf_add {n : ℕ} {p : ℕ → Prop} (hn : n ≤ Inf {m | p m}) :
  Inf {m | p (m + n)} + n = Inf {m | p m} :=
begin
  obtain h | ⟨m, hm⟩ := {m | p (m + n)}.eq_empty_or_nonempty,
  { rw [h, nat.Inf_empty, zero_add],
    obtain hnp | hnp := hn.eq_or_lt,
    { exact hnp },
    suffices hp : p (Inf {m | p m} - n + n),
    { exact (h.subset hp).elim },
    rw tsub_add_cancel_of_le hn,
    exact Inf_mem (nonempty_of_pos_Inf $ n.zero_le.trans_lt hnp) },
  { have hp : ∃ n, n ∈ {m | p m} := ⟨_, hm⟩,
    rw [nat.Inf_def ⟨m, hm⟩, nat.Inf_def hp],
    rw [nat.Inf_def hp] at hn,
    exact find_add hn }
end

section

variables {α : Type*} [complete_lattice α]

lemma supr_lt_succ (u : ℕ → α) (n : ℕ) : (⨆ k < n + 1, u k) = (⨆ k < n, u k) ⊔ u n :=
by simp [nat.lt_succ_iff_lt_or_eq, supr_or, supr_sup_eq]

lemma supr_lt_succ' (u : ℕ → α) (n : ℕ) : (⨆ k < n + 1, u k) = u 0 ⊔ (⨆ k < n, u (k + 1)) :=
by { rw ← sup_supr_nat_succ, simp }

lemma infi_lt_succ (u : ℕ → α) (n : ℕ) : (⨅ k < n + 1, u k) = (⨅ k < n, u k) ⊓ u n :=
@supr_lt_succ (order_dual α) _ _ _

lemma infi_lt_succ' (u : ℕ → α) (n : ℕ) : (⨅ k < n + 1, u k) = u 0 ⊓ (⨅ k < n, u (k + 1)) :=
@supr_lt_succ' (order_dual α) _ _ _

end

end nat

namespace set

variable {α : Type*}

lemma bUnion_lt_succ (u : ℕ → set α) (n : ℕ) : (⋃ k < n + 1, u k) = (⋃ k < n, u k) ∪ u n :=
nat.supr_lt_succ u n

lemma bUnion_lt_succ' (u : ℕ → set α) (n : ℕ) : (⋃ k < n + 1, u k) = u 0 ∪ (⋃ k < n, u (k + 1)) :=
nat.supr_lt_succ' u n

lemma bInter_lt_succ (u : ℕ → set α) (n : ℕ) : (⋂ k < n + 1, u k) = (⋂ k < n, u k) ∩ u n :=
nat.infi_lt_succ u n

lemma bInter_lt_succ' (u : ℕ → set α) (n : ℕ) : (⋂ k < n + 1, u k) = u 0 ∩ (⋂ k < n, u (k + 1)) :=
nat.infi_lt_succ' u n

end set

namespace enat
open_locale classical

noncomputable instance : complete_linear_order enat :=
{ .. enat.linear_order,
  .. with_top_order_iso.symm.to_galois_insertion.lift_complete_lattice }

end enat


-- begin header
import M40001.M40001_C2

namespace M40001
-- end header

universe u
variables {X V : Type u}

/- Theorem
Let $X$ be a set and let $R$ be an equivalence relation on $X$. Then any partition of $X$ can form a equivalence relation.
-/
theorem partition_equiv_relation -- I have defined rs to be: def rs (A : set (set(X))) (s t : X) := ∃ B ∈ A, s ∈ B ∧ t ∈ B
    (C : set (set(X))) (h : partition C) : equivalence (rs C) :=
begin
    split,
-- Proving reflexivity
    {intro x,
    cases h with ha hb,
    replace ha :
        ∃ (B : set X) (H : B ∈ C), x ∈ B ∧ ∀ (D : set X), D ∈ C → x ∈ D → B = D := ha x,
    rcases ha with ⟨ha, ⟨hb, ⟨hc, hd⟩⟩⟩,
    use ha, use hb,
    split,
    repeat {assumption}
    },
-- Proving symmtric
    {split,
        {rintros x y ⟨ha, ⟨hb, ⟨hc, hd⟩⟩⟩,
        use ha, use hb,
        split,
        repeat {assumption}
        },
-- Proving transitive
        {rintros x y z ⟨⟨ha, ⟨hb, ⟨hd, he⟩⟩⟩, ⟨hf, ⟨hg, ⟨hk, hl⟩⟩⟩⟩,
        use ha, use hb,
        cases h with hm hn,
        replace hm :
            ∃ (B : set X) (H : B ∈ C), y ∈ B ∧ ∀ (D : set X), D ∈ C → y ∈ D → B = D := hm y,
        rcases hm with ⟨ho, ⟨hp, ⟨hq, hr⟩⟩⟩,
        have : hf = ha,
            {suffices : hf = ho, {rw this, apply hr ha, repeat {assumption}},
            rwa hr hf, repeat {assumption},
            },
        split,
        {assumption},
        {rwa ←this},
        }
    }
end

lemma class_relate_lem_c
    (s t : X) (R : bin_rel X) (h : equivalence R) : R s t ↔ cls R t = cls R s :=
begin
    split,
    {from class_relate_lem_b s t R h},
    {intro ha,
    unfold cls at ha,
    have : t ∈ {x : X | R t x}, by {rwa set.mem_set_of_eq, from equiv_refl R h t},
    rwa [ha, set.mem_set_of_eq] at this
    }
end

variable {R : bin_rel X}
def Rf (g : X → V) (s t : X)  := g s = g t

theorem equiv_relation_equiv (f = λ x, cls R x) (h : equivalence R) : ∀ s t : X, R s t ↔ Rf f s t :=
begin
    intros s t,
    unfold Rf, rw H, simp,
    split,
    {intro ha,
    rwa [←class_relate_lem_c, equiv_symm R h t s],
    assumption
    },
    {intro ha,
    rwa [class_relate_lem_c s t R h, ha]
    }
end

end M40001


/-
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 topology.algebra.ordered.basic
import data.set.intervals.proj_Icc

/-!
# 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


/-
Copyright (c) 2021 Lu-Ming Zhang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Lu-Ming Zhang
-/
import linear_algebra.matrix.symmetric
import linear_algebra.matrix.orthogonal
import data.matrix.kronecker

/-!
# Diagonal matrices

This file contains the definition and basic results about diagonal matrices.

## Main results

- `matrix.is_diag`: a proposition that states a given square matrix `A` is diagonal.

## Tags

diag, diagonal, matrix
-/

namespace matrix

variables {α β R n m : Type*}

open function
open_locale matrix kronecker

/-- `A.is_diag` means square matrix `A` is a diagonal matrix. -/
def is_diag [has_zero α] (A : matrix n n α) : Prop := ∀ ⦃i j⦄, i ≠ j → A i j = 0

@[simp] lemma is_diag_diagonal [has_zero α] [decidable_eq n] (d : n → α) :
  (diagonal d).is_diag :=
λ i j, matrix.diagonal_apply_ne

/-- Diagonal matrices are generated by `matrix.diagonal`. -/
lemma is_diag.exists_diagonal [has_zero α] [decidable_eq n] {A : matrix n n α} (h : A.is_diag) :
  ∃ d, diagonal d = A :=
begin
  refine ⟨λ i, A i i, ext $ λ i j, _⟩,
  obtain rfl | hij := decidable.eq_or_ne i j,
  { rw diagonal_apply_eq },
  { rw [diagonal_apply_ne hij, h hij] },
end

/-- `matrix.is_diag.exists_diagonal` as an iff. -/
lemma is_diag_iff_exists_diagonal [has_zero α] [decidable_eq n] (A : matrix n n α) :
  A.is_diag ↔ (∃ d, diagonal d = A) :=
⟨is_diag.exists_diagonal, λ ⟨d, hd⟩, hd ▸ is_diag_diagonal d⟩

/-- Every matrix indexed by a subsingleton is diagonal. -/
lemma is_diag_of_subsingleton [has_zero α] [subsingleton n] (A : matrix n n α) : A.is_diag :=
λ i j h, (h $ subsingleton.elim i j).elim

/-- Every zero matrix is diagonal. -/
@[simp] lemma is_diag_zero [has_zero α] : (0 : matrix n n α).is_diag :=
λ i j h, rfl

/-- Every identity matrix is diagonal. -/
@[simp] lemma is_diag_one [decidable_eq n] [has_zero α] [has_one α] :
  (1 : matrix n n α).is_diag :=
λ i j, one_apply_ne

lemma is_diag.map [has_zero α] [has_zero β]
{A : matrix n n α} (ha : A.is_diag) {f : α → β} (hf : f 0 = 0) :
  (A.map f).is_diag :=
by { intros i j h, simp [ha h, hf] }

lemma is_diag.neg [add_group α] {A : matrix n n α} (ha : A.is_diag) :
  (-A).is_diag :=
by { intros i j h, simp [ha h] }

@[simp] lemma is_diag_neg_iff [add_group α] {A : matrix n n α} :
  (-A).is_diag ↔ A.is_diag :=
⟨ λ ha i j h, neg_eq_zero.1 (ha h), is_diag.neg ⟩

lemma is_diag.add
  [add_zero_class α] {A B : matrix n n α} (ha : A.is_diag) (hb : B.is_diag) :
  (A + B).is_diag :=
by { intros i j h, simp [ha h, hb h] }

lemma is_diag.sub [add_group α]
  {A B : matrix n n α} (ha : A.is_diag) (hb : B.is_diag) :
  (A - B).is_diag :=
by { intros i j h, simp [ha h, hb h] }

lemma is_diag.smul [monoid R] [add_monoid α] [distrib_mul_action R α]
  (k : R) {A : matrix n n α} (ha : A.is_diag) :
  (k • A).is_diag :=
by { intros i j h, simp [ha h] }

@[simp] lemma is_diag_smul_one (n) [semiring α] [decidable_eq n] (k : α) :
  (k • (1 : matrix n n α)).is_diag :=
is_diag_one.smul k

lemma is_diag.transpose [has_zero α] {A : matrix n n α} (ha : A.is_diag) : Aᵀ.is_diag :=
λ i j h, ha h.symm

@[simp] lemma is_diag_transpose_iff [has_zero α] {A : matrix n n α} :
  Aᵀ.is_diag ↔ A.is_diag :=
⟨ is_diag.transpose, is_diag.transpose ⟩

lemma is_diag.conj_transpose
  [semiring α] [star_ring α] {A : matrix n n α} (ha : A.is_diag) :
  Aᴴ.is_diag :=
ha.transpose.map (star_zero _)

@[simp] lemma is_diag_conj_transpose_iff [semiring α] [star_ring α] {A : matrix n n α} :
  Aᴴ.is_diag ↔ A.is_diag :=
⟨ λ ha, by {convert ha.conj_transpose, simp}, is_diag.conj_transpose ⟩

lemma is_diag.minor [has_zero α]
  {A : matrix n n α} (ha : A.is_diag) {f : m → n} (hf : injective f) :
  (A.minor f f).is_diag :=
λ i j h, ha (hf.ne h)

/-- `(A ⊗ B).is_diag` if both `A` and `B` are diagonal. -/
lemma is_diag.kronecker [mul_zero_class α]
  {A : matrix m m α} {B : matrix n n α} (hA : A.is_diag) (hB : B.is_diag) :
  (A ⊗ₖ B).is_diag :=
begin
  rintros ⟨a, b⟩ ⟨c, d⟩ h,
  simp only [prod.mk.inj_iff, ne.def, not_and_distrib] at h,
  cases h with hac hbd,
  { simp [hA hac] },
  { simp [hB hbd] },
end

/-- The block matrix `A.from_blocks 0 0 D` is diagonal if `A` and `D` are diagonal. -/
lemma is_diag.from_blocks [has_zero α]
  {A : matrix m m α} {D : matrix n n α}
  (ha : A.is_diag) (hd : D.is_diag) :
  (A.from_blocks 0 0 D).is_diag :=
begin
  rintros (i | i) (j | j) hij,
  { exact ha (ne_of_apply_ne _ hij) },
  { refl },
  { refl },
  { exact hd (ne_of_apply_ne _ hij) },
end

/-- This is the `iff` version of `matrix.is_diag.from_blocks`. -/
lemma is_diag_from_blocks_iff [has_zero α]
  {A : matrix m m α} {B : matrix m n α} {C : matrix n m α} {D : matrix n n α} :
  (A.from_blocks B C D).is_diag ↔ A.is_diag ∧ B = 0 ∧ C = 0 ∧ D.is_diag :=
begin
  split,
  { intros h,
    refine ⟨λ i j hij, _, ext $ λ i j, _, ext $ λ i j, _, λ i j hij, _⟩,
    { exact h (sum.inl_injective.ne hij), },
    { exact h sum.inl_ne_inr, },
    { exact h sum.inr_ne_inl, },
    { exact h (sum.inr_injective.ne hij), }, },
  { rintros ⟨ha, hb, hc, hd⟩,
    convert is_diag.from_blocks ha hd }
end

/-- A symmetric block matrix `A.from_blocks B C D` is diagonal
    if  `A` and `D` are diagonal and `B` is `0`. -/
lemma is_diag.from_blocks_of_is_symm [has_zero α]
  {A : matrix m m α} {C : matrix n m α} {D : matrix n n α}
  (h : (A.from_blocks 0 C D).is_symm) (ha : A.is_diag) (hd : D.is_diag) :
  (A.from_blocks 0 C D).is_diag :=
begin
  rw ←(is_symm_from_blocks_iff.1 h).2.1,
  exact ha.from_blocks hd,
end

lemma mul_transpose_self_is_diag_iff_has_orthogonal_rows
  [fintype n] [has_mul α] [add_comm_monoid α] {A : matrix m n α} :
  (A ⬝ Aᵀ).is_diag ↔ A.has_orthogonal_rows :=
iff.rfl

lemma transpose_mul_self_is_diag_iff_has_orthogonal_cols
  [fintype m] [has_mul α] [add_comm_monoid α] {A : matrix m n α} :
  (Aᵀ ⬝ A).is_diag ↔ A.has_orthogonal_cols :=
iff.rfl

end matrix


/-
Justin Cai, jc5pz
Here is the practice exam. It provides you
with a set of problems illustrative of the
material to be tested. This is a homework
assignment due this coming Thursday. We will
review the questions in class.

Copy this file your "work" directory. Work
on it there. Upload to Collab when done.

Note: An incorrect answer above or below
a correct answer can cause Lean to be unable
to process the correct answer. If you are not
able to complete a problem successfully please
comment out your incomplete answer so that we
can see your work but so that your incomplete
work does not cause problems for surrounding
problems and answers.
-/

/-
PART I: Functions. Functions are an essential
element of the language of predicate logic. In
this section, you show that you understand how
to define, use, and reason about functions in
Lean.
-/

/- 1.
Study each of the following definitions,
then answer the associated question about
the types involved in these definitions.
-/

-- Consider this function
def f (n : ℕ) (s : string) := s

/-
a. What is it's return type? Answer:
string
-/

/-
b. What is the type of (f 5)? Answer:
string
-/

/-
c. What is the value of (f 0 "yay")
"yay"
-/

/-
d. What is the type of this function?
ℕ → string
-/

/- 2.
Define three functions called square,
square', and square'', each of type
ℕ → ℕ. Each function must return the
square of the value to which it is
applied. Write the first function in
"C" style, the second using a tactic
script, and the third using a lambda
abstraction. Declare all argument and
return types explicitly. Answer below.
-/
def square (x: nat) : nat := x^2

def square': ℕ → ℕ :=
begin
    assume x,
    exact x*x,
end

def square'': ℕ → ℕ := λ x, x^2

/- 3.
Construct three proofs to test your
function definitions. The first must
use "lemma" to define a proof, called
square_3_9, of the proposition that
(square 3) equals 9. The second must
use "theorem" to define a proof, called
square'_4_16, of the proposition that
square' applied to 4 reduces to 16. The
third must prove that square'' 5 is
equal to 25. This third proof must not
use the equals sign, =, but must use
"eq" instead to state the proposition
to be proved. Hint on #3, sometimes
you need to use parentheses to express
how you want terms to be grouped. Answer
below.
-/

lemma square_3_9 : 9 = 9 :=
begin
    apply eq.refl(square 3)
end

theorem square'_4_16 : 16 = 16 :=
begin
    apply eq.refl(square' 4)
end

example: eq 25 25 :=
begin
    apply eq.refl(square'' 5)
end

/- 4.
Define a function called last_first.
It must take two string values, called
"first" and "last" (without quotes),
and return a string consisting of "last"
followed by a comma and a space followed
by "first".

For example (last_first "Orson" "Welles")
must return the string, "Welles, Orson".
Write a test case for your function to
prove that (last_first "Orson" "Welles")
is "Welles, Orson". Use "example", to
check the proof. Hint: The ++ operator
implements the string append function.
Answer below.
-/
def last_first (first: string) (last: string) := last ++ ", " ++ first
example: last_first "Orson" "Welles" = "Welles, Orson" :=
begin
    apply eq.refl
end

/- 5.

Complete the following definition of a
function, called apply3. It takes, as an
argument, a function, you might call it f,
of type ℕ → ℕ. It must return a function,
also of type ℕ to ℕ, that, when applied
to a value, n, returns the result of
applying the given function, f, to the
given value, n, three times. The function
returned must compute f(f(f(n))) when it
is applied to an argument, n.
-/

def apply3 :(ℕ → ℕ) → (ℕ → ℕ) :=
    λ f : ℕ → ℕ,
    λ n:ℕ ,
    f(f(f n))

/- 6.

The Lean libraries define a function,
string.length, that takes a string and
returns its length as a natural number.
Define a function, len2, that takes two
strings and returns the sum of their
lengths. You may use the ++ operator
but not the + operator in your answer.
Follow your function definition with a
test case in the form of a proof using
"example" showing that len2 applied to
"Orson" and "Welles" is 11. Remember:
you might need parenthesis in some
cases to group terms correctly into
larger terms.
-/
def len2 (first: string) (second: string) : ℕ :=
    (first ++ second).length
example: len2 "Orson" "Welles" = 11 :=
begin
    apply eq.refl
end

/- 7.
Use "example" to prove that there is a
function of the following type:

((ℕ → ℕ) → (ℕ → ℕ)) →
    ((ℕ → ℕ) → ℕ) →
        ((ℕ → ℕ) → ℕ)
-/
example: ((ℕ → ℕ) → (ℕ → ℕ)) →
    ((ℕ → ℕ ) → ℕ ) → ((ℕ→ℕ) → ℕ) :=
begin
    assume a b c,
    exact b c
end

/-
PART II: Functions, revisited. In
mathematics,  functions play a central
role. A function, f, in the mathematical
sense is a triple, f = { D, P, C }, where
D, a set, is the domain of definition of
f, C is the co-domain of f, and P is a
set of ordered pairs, each with a first
element from D and a second element from
C,. In addition, P has one additional
essential  property, the subject of one
of the following questions.
-/

/- 8.

What one additional property is essential to
the definition of what it means for a triple,
{ D, P, C } to be a function?

Name the property. Answer:
Single valued

Now explain precisely what it means:  "That there
are no two pairs, (x, y) and (x', y') such that..."

Fill in the blank, and use a logical ∧ in answering.
You might also want to use = or ≠.

Answer:
x=x' ∧ y ≠ y'
x can equal x' and y isn't equal to y'
-/

/- 9.

Give names to the following concepts:

The set of all values appearing as the first
element of any pair in P.

Answer: Domain

The set of all values appearing as the second
element of any pair in P.

Answer: Range

The property that the set of all values
appearing as the first element of P is the
same as the entire set, D.

Answer: Total

The property that the set of all
values appearing as the second element of
P is the same as the entire set, C.

Answer: Surjective

The property of being one-to-one and onto.

Answer: Bijective

-/

/- 10.

What does it mean for a function, f, to be
injective? Give you answer by completing the
following sentences with logical expressions.

"A function, f, is said to be injective if
it has no two pairs, (x, y) and (x', y'),
such that ..."

Answer: x ≠ x' ∧ y=y'

In other words, "If (x, y) and (x', y') are
related by f and x ≠ x' then ..."

Answer:
f(x) ≠ f(x')
-/

/- 11.

Suppose that S and T are types and that f
is defined to be a function, *in Lean*, of
type S → T. Which of the following properties,
if any, does f necessarily have?

- injective
- surjective
- bijective
- one-to-many
- one-to-one
- onto
- single-valued
- partial
- total

Answer:
Surjective, total
-/

/-
PART III: Logic and Proof.
-/

/- 12a.

Use axiom and/or axioms in Lean to express,
in formal logic, the following assumptions:

- T is a type
- t1 and t2 are values of type T
- t1 = t2

Answer immediately after this comment block.
If you need to introduce a name, use eqt1t2.
-/
axiom T : Type
axioms t1 t2 : T
axiom eqt1t2 : t1 = t2

/- 12b.

Use axiom or axioms to represent the
additional assumptions that

- P is a predicate expressing a property of objects of type T
- t1 has property P

If you need to use a name, use Pt1
-/
axiom P : T → Prop
axiom Pt1 : P t1

/- 12 c.

Now use "example" to assert, and then
prove, that t2 also has property P.
-/
example : P t2 := eq.subst eqt1t2 Pt1

/- 13 a.
Define eq_1_0 to be the proposition, 1 = 0.
-/
def eq_1_0 : Prop := 1 = 0

/- 13 b.
Define pf_eq_0_0 to be a proof of the
proposition that 0 = 0. Use the lemma
keyword.
-/
lemma pf_eq_0_0 : 0 = 0 := rfl

/- 13 c.
Write a function, w, that takes three
values, a, b, and c of type ℕ, and that
also takes proofs, cb : c = b, and
ba : b = a, and that returns a proof
that a = c.
-/
def w (a b c: ℕ ) (cb: c = b) (ba : b = a) : a = c :=
    eq.trans(eq.symm ba) (eq.symm cb)

/- 13d.

What is the type of this function?

Answer: ℕ → c = b → b = a → a = c

What is the form of this proposition?

Answer: ℕ → Prop → Prop → Prop

What's the form the proposition after the
comma?

Answer: ℕ → Prop

What is the premise of the proposition after
the comma?

Answer: For 3 Nats a b c given c = b and b = a, then a = b and b = c then a = c

-/

/- 14.
Complete the following proofs. Give each one
in the form indicated by a comment preceding
the statement of the conjecture to be proved.
When using tactic scripts, remember to write
begin/end pairs right away, so Lean knowns
you want to use a tactic script.
-/

-- lambda expression
example : ∀ (s : string), s = s :=
λ s: string,
eq.refl s

-- lambda expresion
example : ∀ (n : ℕ), ∀ (m : ℕ), true :=
λ n m: ℕ, true.intro

-- tactic script
example : ∀ (T : Type), ∀ (t : T), eq t t :=
begin
    assume T: Type,
    assume t: T,
    exact eq.refl t
end

-- tactic script
example :
    ∀ (T : Type),
    ∀ (P : T → Prop),
    ∀ t1 t2 : T,
    ∀ Pt1 : P t1,
    ∀ t2t1 : t2 = t1,
    P t2 :=
begin
    assume T P t1 t2 Pt1 t2t1,
    exact eq.subst (eq.symm(t2t1)) Pt1
end

/-
The following problems involve implications.
For example, false → P in an implication. To
prove an implication, just show that there is
(give) a function of the specified type.
-/

-- lambda expression
example : ∀ (P : Prop), false → P :=
λ P i, false.elim i

-- tactic script
example : ∀ (P : Prop), false → P :=
begin
    assume P i,
    exact false.elim i
end

-- lambda expression
example : ∀ (P Q : Prop), P ∧ Q → Q ∧ P :=
λ P Q,
    λ pq: P ∧ Q,
        and.intro pq.elim_right pq.elim_left

-- tactic script
example : ∀ (P Q : Prop), P ∧ Q → Q ∧ P :=
begin
    assume P Q,
    assume pq: P ∧ Q,
    exact and.intro pq.elim_right pq.elim_left
end

-- tactic script
example :
    ∀ T : Type,
    ∀ (t1 t2 t3 : T),
    t1 = t2 ∧ t2 = t3 → t1 = t3 :=
begin
    assume T t1 t2 t3,
    assume t1t2t3: t1 = t2 ∧ t2 = t3,
    exact eq.trans(t1t2t3.elim_left) (t1t2t3.elim_right)
end

/- 15.

Use Lean to model a world in which there
are Dogs, all Dogs are friendly, and Fido
is a Dog, with a proof that in this world,
Fido must be friendly, too.
-/
axiom Dog : Type
axiom Fido : Dog
axiom Friendly: Dog → Prop
axiom FriendlyDog : ∀ d: Dog, Friendly d
example : Friendly Fido :=
begin
    exact FriendlyDog Fido
end


/- LoVe Exercise 8: Operational Semantics -/

import .love08_operational_semantics_demo

namespace LoVe

/- Question 1: Program Equivalence -/

/- For this question, we introduce the notation of program equivalence
`p₁ ≈ p₂`. -/

def program_equiv (S₁ S₂ : program) : Prop :=
∀s t, (S₁, s) ⟹ t ↔ (S₂, s) ⟹ t

local infix ` ≈ ` := program_equiv

/- Program equivalence is a equivalence relation, i.e., it is reflexive,
symmetric, and transitive. -/

@[refl] lemma program_equiv.refl {S} :
  S ≈ S :=
assume s t,
show (S, s) ⟹ t ↔ (S, s) ⟹ t,
  by refl

@[symm] lemma program_equiv.symm {S₁ S₂}:
  S₁ ≈ S₂ → S₂ ≈ S₁ :=
assume h s t,
show (S₂, s) ⟹ t ↔ (S₁, s) ⟹ t,
  from iff.symm (h s t)

@[trans] lemma program_equiv.trans {S₁ S₂ S₃} (h₁₂ : S₁ ≈ S₂) (h₂₃ : S₂ ≈ S₃) :
  S₁ ≈ S₃ :=
assume s t,
show (S₁, s) ⟹ t ↔ (S₃, s) ⟹ t,
  from iff.trans (h₁₂ s t) (h₂₃ s t)

/- 1.1. Prove the following program equivalences. -/

lemma program_equiv.seq_skip_left {S} :
  skip ;; S ≈ S :=
sorry

lemma program_equiv.seq_skip_right {S} :
  S ;; skip ≈ S :=
sorry

lemma program_equiv.seq_congr {S₁ S₂ T₁ T₂} (hS : S₁ ≈ S₂) (hT : T₁ ≈ T₂) :
  S₁ ;; T₁ ≈ S₂ ;; T₂ :=
sorry

lemma program_equiv.ite_seq_while {b S} :
  ite b (S ;; while b S) skip ≈ while b S :=
sorry

/- 1.2. Prove one more equivalence. -/

lemma program_equiv.skip_assign_id {x} :
  assign x (λs, s x) ≈ skip :=
sorry

/- Question 2: Guarded Command Language (GCL) -/

/- In 1976, E. W. Dijkstra introduced the guarded command language, a
minimalistic imperative language with built-in nondeterminism. A grammar for one
of its variants is given below:

    S  ::=  x := e       -- assignment
         |  assert b     -- assertion
         |  S ; S        -- sequential composition
         |  S | ⋯ | S    -- nondeterministic choice
         |  loop S       -- nondeterministic iteration

Assignment and sequential composition are as in the WHILE language. The other
statements have the following semantics:

* `assert b` aborts if `b` evaluates to false; otherwise, the command is a
  no-op.

* `S | ⋯ | S` chooses **any** of the branches and executes it, ignoring the
  other branches.

* `loop S` executes `S` **any** number of times.

In Lean, GCL is captured by the following inductive type: -/

inductive gcl (σ : Type) : Type
| assign : string → (σ → ℕ) → gcl
| assert : (σ → Prop) → gcl
| seq    : gcl → gcl → gcl
| choice : list gcl → gcl
| loop   : gcl → gcl

infixr ` ;; `:90 := gcl.seq

namespace gcl

/- The parameter `σ` abstracts over the state type. It is necessary to work
around a bug in Lean.

The big-step semantics is defined as follows: -/

inductive big_step : (gcl state × state) → state → Prop
| assign {x a s} :
  big_step (assign x a, s) (s{x ↦ a s})
| assert {b : state → Prop} {s} (hcond : b s) :
  big_step (assert b, s) s
| seq {S T s t u} (h₁ : big_step (S, s) t) (h₂ : big_step (T, t) u) :
  big_step (S ;; T, s) u
| choice {Ss : list (gcl state)} {s t} (i : ℕ) (hless : i < list.length Ss)
    (hbody : big_step (list.nth_le Ss i hless, s) t) :
  big_step (choice Ss, s) t
| loop_base {S s} :
  big_step (loop S, s) s
| loop_step {S s u} (t) (hbody : big_step (S, s) t)
    (hrest : big_step (loop S, t) u) :
  big_step (loop S, s) u

/- Convenience syntax: -/

infix ` ~~> `:110 := big_step

/- 2.1. Prove the following inversion rules, as we did in the lecture for the
WHILE language. -/

@[simp] lemma big_step_assign_iff {x a s t} :
  (assign x a, s) ~~> t ↔ t = s{x ↦ a s} :=
sorry

@[simp] lemma big_step_assert {b s t} :
  (assert b, s) ~~> t ↔ t = s ∧ b s :=
sorry

@[simp] lemma big_step_seq_iff {S₁ S₂ s t} :
  (S₁ ;; S₂, s) ~~> t ↔ (∃u, (S₁, s) ~~> u ∧ (S₂, u) ~~> t) :=
sorry

lemma big_step_loop {S s u} :
  (loop S, s) ~~> u ↔ (s = u ∨ (∃t, (S, s) ~~> t ∧ (loop S, t) ~~> u)) :=
sorry

@[simp] lemma big_step_choice {Ss s t} :
  (choice Ss, s) ~~> t ↔
  (∃(i : ℕ) (hless : i < list.length Ss),
    (list.nth_le Ss i hless, s) ~~> t) :=
sorry

/- 2.2. Complete the translation below of a deterministic program to a GCL
program, by filling in the `sorry` placeholders below. -/

def of_program : program → gcl state
| program.skip          := assert (λ_, true)
| (program.assign x f)  :=
  sorry
| (program.seq S₁ S₂)   :=
  sorry
| (program.ite b S₁ S₂) :=
  choice [seq (assert b) (of_program S₁),
    seq (assert (λs, ¬ b s)) (of_program S₂)]
| (program.while b S)   :=
  seq (loop (seq (assert b) (of_program S))) (assert (λs, ¬ b s))

/- 2.3. In the definition of `of_program` above, `skip` is translated to
`assert (λ_, true)`. Looking at the big-step semantics of both constructs, we
can convince ourselves that it makes sense. Can you think of other correct ways
to define the `skip` case? -/

-- enter your answer here

end gcl

end LoVe


import data.nat.prime
import data.nat.totient
import data.list.basic
import data.nat.gcd
import .list

namespace nat

open finset

theorem totient_prime {p : ℕ} (hp : p.prime) : p.totient = p - 1 :=
begin
	unfold totient,
	have hcoprime : ∀ x ∈ (range p).erase 0, p.coprime x,
	{ intros x hx, rw prime.coprime_iff_not_dvd hp,
		apply not_dvd_of_pos_of_lt (nat.pos_of_ne_zero $ ne_of_mem_erase hx),
		apply mem_range.mp (mem_of_mem_erase hx) },
	have hunion : range p = {0} ∪ (range p).erase 0,
	{	rw [← insert_eq, insert_erase], simp, exact prime.pos hp },
	rw [hunion, filter_union, filter_false_of_mem], swap,
	{	intros x hx, simp at hx, simp [hx], intro h1, exact not_prime_one (h1 ▸ hp) },
	rw [filter_true_of_mem hcoprime, empty_union, card_erase_of_mem, card_range],
	{ rw pred_eq_sub_one },
	simp [prime.pos hp],
end

theorem factors_ne_nil {n : ℕ} (hn : 1 < n) : n.factors ≠ list.nil :=
begin
	induction n with n ih,
	{	exfalso, exact (not_lt_of_gt nat.zero_lt_one) hn },
	induction n with n ih,
	{ exfalso, exact (lt_irrefl 1) hn },
	rw factors_add_two,
	intro h, injection h,
end

theorem pow_count_factors_dvd (n k : ℕ) : k ^ list.count k n.factors ∣ n :=
begin
	by_cases h0 : n = 0, { simp [h0] },
	conv { congr, skip, rw ← prod_factors (nat.pos_of_ne_zero h0) },
	apply list.pow_count_dvd_prod,
end

theorem pow_gt_count_factors_not_dvd {n p : ℕ} (hn : 0 < n) (hp : p.prime) :
  ∀ k, list.count p n.factors < k → ¬ p ^ k ∣ n :=
begin
	have aux : ∀ l, (∀ x : ℕ, x ∈ l → x.prime) → ∀ p : ℕ, p.prime →
		∀ k, list.count p l < k → ¬ p ^ k ∣ l.prod,
	{	intros l hl p hp,
		induction l with hd tl ih,
		{ intros k hk hdvd,
			rw [list.count_nil] at hk,
			rw [list.prod_nil, nat.dvd_one] at hdvd,
			have := pow_lt_pow (prime.one_lt hp) hk,
			rw [hdvd, pow_zero] at this,
			exact (lt_irrefl 1) this },
		intros k hk hdvd,
		have hkpos : 0 < k := lt_of_le_of_lt (zero_le _) hk,
		rw [list.count_cons] at hk,	split_ifs at hk,
		{ rw [← h, list.prod_cons, ← succ_pred_eq_of_pos hkpos, pow_succ] at hdvd,
			cases hdvd with w hw,
			rw [mul_assoc, mul_eq_mul_left_iff] at hw,
			cases hw with h1 h2, swap, { exact (prime.ne_zero hp) h2 },
			apply ih (λ x hx, hl x (by simp [hx])) k.pred (lt_pred_iff.mpr hk),
			use [w, h1] },
		rw [list.prod_cons] at hdvd,
		apply ih (λ x hx, hl x (by simp [hx])) k hk,
		apply coprime.dvd_of_dvd_mul_left _ hdvd,
		rw [← pow_one hd], apply coprime.pow,
		rwa coprime_primes hp (hl hd (by simp)) },
	have prod := prod_factors hn,
	conv in (¬ _ ∣ n) { rw ← prod },
	apply aux n.factors (λ x, mem_factors) p hp,
end

theorem mem_factors_dvd {n : ℕ} : ∀ p ∈ n.factors, p ∣ n :=
λ p hp, decidable.by_cases
	(by { intro hn, exfalso, simp [hn, factors] at hp, assumption })
	(λ hn, (mem_factors_iff_dvd (nat.pos_of_ne_zero hn) (mem_factors hp)).mp hp)

lemma list_pos_prod_pos {l : list ℕ} (hpos : ∀ x ∈ l, 0 < x) : 0 < l.prod :=
begin
	induction l with hd tl ih, { simp },
	simp, apply mul_pos,
	{	apply hpos, simp },
	apply ih, intros x hx, apply hpos, simp, right, exact hx,
end

lemma list_pos_prod_ge_sublist_prod {s t : list ℕ} (hpos : ∀ x ∈ s, 0 < x) (h : t <+ s) :
	t.prod ≤ s.prod :=
begin
	induction s with hd tl ih generalizing t,
	{	simp [list.eq_nil_of_sublist_nil h] },
	induction t with hd' tl' ih',
	{	have := succ_le_of_lt (list_pos_prod_pos hpos), simpa },
	have htlpos : ∀ x ∈ tl, 0 < x := λ x hx, by { apply hpos x, simp, right, exact hx },
	by_cases heq : hd = hd',
	{	simp [heq], apply nat.mul_le_mul_of_nonneg_left, apply ih htlpos,
		rw heq at h, apply list.sublist_of_cons_sublist_cons, exact h },
	have : hd' :: tl' <+ tl := list.cons_sublist_of_cons_sublist_cons (ne.symm heq) h,
	have := ih htlpos this,
	apply le_trans this,
	rw [← nat.one_mul tl.prod, list.prod_cons],
	have := succ_le_of_lt (hpos hd (by simp)),
	apply nat.mul_le_mul_of_nonneg_right, assumption,
end

theorem div_factor_ne_zero (n : ℕ) : ∀ p ∈ n.factors, n / p ^ list.count p n.factors ≠ 0 :=
begin
	by_cases h0 : n = 0,
	{	intros p hmem, exfalso, simp [h0, factors] at hmem, exact hmem },
	have hpos : 0 < n := nat.pos_of_ne_zero h0,
	have hfactorspos : ∀ x ∈ n.factors, 0 < x := λ x hx, prime.pos (mem_factors hx),
	intros p hmem h0,
	rw nat.div_eq_zero_iff at h0, swap,
	{	apply nat.pos_of_ne_zero, apply pow_ne_zero,
		intro h, apply not_prime_zero, rw ← h, exact mem_factors hmem },
	have hleft := prod_factors hpos,
	have hright : p ^ list.count p n.factors = (list.repeat p $ list.count p n.factors).prod := by simp,
	conv at h0 {congr, rw ← hleft, skip, rw hright },
	apply not_lt_of_ge (list_pos_prod_ge_sublist_prod hfactorspos _) h0,
	rw ← list.le_count_iff_repeat_sublist,
end

end nat


-- Las_relaciones_definidas_por_particiones_son_simetricas.lean
-- Las relaciones definidas por particiones son simétricas
-- José A. Alonso Jiménez
-- Sevilla, 10 de octubre de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que la relación correspondiente a unaa partición es
-- simétrica.
-- ---------------------------------------------------------------------

import tactic

@[ext] structure particion (A : Type) :=
(Bloques    : set (set A))
(Hno_vacios : ∀ X ∈ Bloques, (X : set A).nonempty)
(Hrecubren  : ∀ a, ∃ X ∈ Bloques, a ∈ X)
(Hdisjuntos : ∀ X Y ∈ Bloques, (X ∩ Y : set A).nonempty → X = Y)

namespace particion

variable  {A : Type}
variables {X Y : set A}
variable  {P : particion A}

def relacion : (particion A) → (A → A → Prop) :=
  λ P a b, ∀ X ∈ Bloques P, a ∈ X → b ∈ X

-- Se usarán los siguientes lemas auxiliares

lemma iguales_si_comun
  (hX : X ∈ Bloques P)
  (hY : Y ∈ Bloques P)
  {a : A}
  (haX : a ∈ X)
  (haY : a ∈ Y)
  : X = Y :=
Hdisjuntos P X hX Y hY ⟨a, haX, haY⟩

lemma pertenece_si_pertenece
  (hX : X ∈ Bloques P)
  (hY : Y ∈ Bloques P)
  {a b : A}
  (haX : a ∈ X)
  (haY : a ∈ Y)
  (hbX : b ∈ X)
  : b ∈ Y :=
begin
  convert hbX,
  exact iguales_si_comun hY hX haY haX,
end

-- 1ª demostración
example
  (P : particion A)
  : symmetric (relacion P) :=
begin
  rw symmetric,
  intros a b hab,
  unfold relacion at *,
  intros X hX hbX,
  obtain ⟨Y, hY, haY⟩ := Hrecubren P a,
  specialize hab Y hY haY,
  exact pertenece_si_pertenece hY hX hab hbX haY,
end

-- 2ª demostración
example
  (P : particion A)
  : symmetric (relacion P) :=
begin
  intros a b hab,
  intros X hX hbX,
  obtain ⟨Y, hY, haY⟩ := Hrecubren P a,
  specialize hab Y hY haY,
  exact pertenece_si_pertenece hY hX hab hbX haY,
end

-- 3ª demostración
lemma simetrica
  (P : particion A)
  : symmetric (relacion P) :=
begin
  intros a b h X hX hbX,
  obtain ⟨Y, hY, haY⟩ := Hrecubren P a,
  specialize h Y hY haY,
  exact pertenece_si_pertenece hY hX h hbX haY,
end

end particion


/-
Copyright (c) 2019 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Yury Kudryashov
-/
import analysis.normed_space.basic
import topology.algebra.order.liminf_limsup
import topology.local_homeomorph

/-!
# Asymptotics

We introduce these relations:

* `is_O_with c f g l` : "f is big O of g along l with constant c";
* `is_O f g l` : "f is big O of g along l";
* `is_o f g l` : "f is little o of g along l".

Here `l` is any filter on the domain of `f` and `g`, which are assumed to be the same. The codomains
of `f` and `g` do not need to be the same; all that is needed that there is a norm associated with
these types, and it is the norm that is compared asymptotically.

The relation `is_O_with c` is introduced to factor out common algebraic arguments in the proofs of
similar properties of `is_O` and `is_o`. Usually proofs outside of this file should use `is_O`
instead.

Often the ranges of `f` and `g` will be the real numbers, in which case the norm is the absolute
value. In general, we have

  `is_O f g l ↔ is_O (λ x, ∥f x∥) (λ x, ∥g x∥) l`,

and similarly for `is_o`. But our setup allows us to use the notions e.g. with functions
to the integers, rationals, complex numbers, or any normed vector space without mentioning the
norm explicitly.

If `f` and `g` are functions to a normed field like the reals or complex numbers and `g` is always
nonzero, we have

  `is_o f g l ↔ tendsto (λ x, f x / (g x)) l (𝓝 0)`.

In fact, the right-to-left direction holds without the hypothesis on `g`, and in the other direction
it suffices to assume that `f` is zero wherever `g` is. (This generalization is useful in defining
the Fréchet derivative.)
-/

open filter set
open_locale topological_space big_operators classical filter nnreal

namespace asymptotics

variables {α : Type*} {β : Type*} {E : Type*} {F : Type*} {G : Type*}
  {E' : Type*} {F' : Type*} {G' : Type*} {R : Type*} {R' : Type*} {𝕜 : Type*} {𝕜' : Type*}

variables [has_norm E] [has_norm F] [has_norm G] [normed_group E'] [normed_group F']
  [normed_group G'] [normed_ring R] [normed_ring R'] [normed_field 𝕜] [normed_field 𝕜']
  {c c' : ℝ} {f : α → E} {g : α → F} {k : α → G} {f' : α → E'} {g' : α → F'} {k' : α → G'}
  {l l' : filter α}

section defs

/-! ### Definitions -/

/-- This version of the Landau notation `is_O_with C f g l` where `f` and `g` are two functions on
a type `α` and `l` is a filter on `α`, means that eventually for `l`, `∥f∥` is bounded by `C * ∥g∥`.
In other words, `∥f∥ / ∥g∥` is eventually bounded by `C`, modulo division by zero issues that are
avoided by this definition. Probably you want to use `is_O` instead of this relation. -/
@[irreducible]
def is_O_with (c : ℝ) (f : α → E) (g : α → F) (l : filter α) : Prop :=
∀ᶠ x in l, ∥ f x ∥ ≤ c * ∥ g x ∥

/-- Definition of `is_O_with`. We record it in a lemma as we will set `is_O_with` to be irreducible
at the end of this file. -/
lemma is_O_with_iff {c : ℝ} {f : α → E} {g : α → F} {l : filter α} :
  is_O_with c f g l ↔ ∀ᶠ x in l, ∥ f x ∥ ≤ c * ∥ g x ∥ := by rw is_O_with

alias is_O_with_iff ↔ asymptotics.is_O_with.bound asymptotics.is_O_with.of_bound

/-- The Landau notation `is_O f g l` where `f` and `g` are two functions on a type `α` and `l` is
a filter on `α`, means that eventually for `l`, `∥f∥` is bounded by a constant multiple of `∥g∥`.
In other words, `∥f∥ / ∥g∥` is eventually bounded, modulo division by zero issues that are avoided
by this definition. -/
@[irreducible]
def is_O (f : α → E) (g : α → F) (l : filter α) : Prop := ∃ c : ℝ, is_O_with c f g l

/-- Definition of `is_O` in terms of `is_O_with`. We record it in a lemma as we will set
`is_O` to be irreducible at the end of this file. -/
lemma is_O_iff_is_O_with {f : α → E} {g : α → F} {l : filter α} :
  is_O f g l ↔ ∃ c : ℝ, is_O_with c f g l := by rw is_O

/-- Definition of `is_O` in terms of filters. We record it in a lemma as we will set
`is_O` to be irreducible at the end of this file. -/
lemma is_O_iff {f : α → E} {g : α → F} {l : filter α} :
  is_O f g l ↔ ∃ c : ℝ, ∀ᶠ x in l, ∥ f x ∥ ≤ c * ∥ g x ∥ := by simp [is_O, is_O_with]

lemma is_O.of_bound (c : ℝ) {f : α → E} {g : α → F} {l : filter α}
  (h : ∀ᶠ x in l, ∥ f x ∥ ≤ c * ∥ g x ∥) : is_O f g l := is_O_iff.2 ⟨c, h⟩

lemma is_O.bound {f : α → E} {g : α → F} {l : filter α} :
  is_O f g l → ∃ c : ℝ, ∀ᶠ x in l, ∥ f x ∥ ≤ c * ∥ g x ∥ := is_O_iff.1

/-- The Landau notation `is_o f g l` where `f` and `g` are two functions on a type `α` and `l` is
a filter on `α`, means that eventually for `l`, `∥f∥` is bounded by an arbitrarily small constant
multiple of `∥g∥`. In other words, `∥f∥ / ∥g∥` tends to `0` along `l`, modulo division by zero
issues that are avoided by this definition. -/
@[irreducible]
def is_o (f : α → E) (g : α → F) (l : filter α) : Prop := ∀ ⦃c : ℝ⦄, 0 < c → is_O_with c f g l

/-- Definition of `is_o` in terms of `is_O_with`. We record it in a lemma as we will set
`is_o` to be irreducible at the end of this file. -/
lemma is_o_iff_forall_is_O_with {f : α → E} {g : α → F} {l : filter α} :
  is_o f g l ↔ ∀ ⦃c : ℝ⦄, 0 < c → is_O_with c f g l := by rw is_o

alias is_o_iff_forall_is_O_with ↔ asymptotics.is_o.forall_is_O_with asymptotics.is_o.of_is_O_with

/-- Definition of `is_o` in terms of filters. We record it in a lemma as we will set
`is_o` to be irreducible at the end of this file. -/
lemma is_o_iff {f : α → E} {g : α → F} {l : filter α} :
  is_o f g l ↔ ∀ ⦃c : ℝ⦄, 0 < c → ∀ᶠ x in l, ∥ f x ∥ ≤ c * ∥ g x ∥ :=
by simp only [is_o, is_O_with]

alias is_o_iff ↔ asymptotics.is_o.bound asymptotics.is_o.of_bound

lemma is_o.def {f : α → E} {g : α → F} {l : filter α} (h : is_o f g l) {c : ℝ} (hc : 0 < c) :
  ∀ᶠ x in l, ∥ f x ∥ ≤ c * ∥ g x ∥ :=
is_o_iff.1 h hc

lemma is_o.def' {f : α → E} {g : α → F} {l : filter α} (h : is_o f g l) {c : ℝ} (hc : 0 < c) :
  is_O_with c f g l :=
is_O_with_iff.2 $ is_o_iff.1 h hc

end defs

/-! ### Conversions -/

theorem is_O_with.is_O (h : is_O_with c f g l) : is_O f g l := by rw is_O; exact ⟨c, h⟩

theorem is_o.is_O_with (hgf : is_o f g l) : is_O_with 1 f g l := hgf.def' zero_lt_one

theorem is_o.is_O (hgf : is_o f g l) : is_O f g l := hgf.is_O_with.is_O

lemma is_O.is_O_with {f : α → E} {g : α → F} {l : filter α} :
  is_O f g l → ∃ c : ℝ, is_O_with c f g l := is_O_iff_is_O_with.1

theorem is_O_with.weaken (h : is_O_with c f g' l) (hc : c ≤ c') : is_O_with c' f g' l :=
is_O_with.of_bound $ mem_of_superset h.bound $ λ x hx,
calc ∥f x∥ ≤ c * ∥g' x∥ : hx
... ≤ _ : mul_le_mul_of_nonneg_right hc (norm_nonneg _)

theorem is_O_with.exists_pos (h : is_O_with c f g' l) :
  ∃ c' (H : 0 < c'), is_O_with c' f g' l :=
⟨max c 1, lt_of_lt_of_le zero_lt_one (le_max_right c 1), h.weaken $ le_max_left c 1⟩

theorem is_O.exists_pos (h : is_O f g' l) : ∃ c (H : 0 < c), is_O_with c f g' l :=
let ⟨c, hc⟩ := h.is_O_with in hc.exists_pos

theorem is_O_with.exists_nonneg (h : is_O_with c f g' l) :
  ∃ c' (H : 0 ≤ c'), is_O_with c' f g' l :=
let ⟨c, cpos, hc⟩ := h.exists_pos in ⟨c, le_of_lt cpos, hc⟩

theorem is_O.exists_nonneg (h : is_O f g' l) :
  ∃ c (H : 0 ≤ c), is_O_with c f g' l :=
let ⟨c, hc⟩ := h.is_O_with in hc.exists_nonneg

/-- `f = O(g)` if and only if `is_O_with c f g` for all sufficiently large `c`. -/
lemma is_O_iff_eventually_is_O_with : is_O f g' l ↔ ∀ᶠ c in at_top, is_O_with c f g' l :=
is_O_iff_is_O_with.trans
  ⟨λ ⟨c, hc⟩, mem_at_top_sets.2 ⟨c, λ c' hc', hc.weaken hc'⟩, λ h, h.exists⟩

/-- `f = O(g)` if and only if `∀ᶠ x in l, ∥f x∥ ≤ c * ∥g x∥` for all sufficiently large `c`. -/
lemma is_O_iff_eventually : is_O f g' l ↔ ∀ᶠ c in at_top, ∀ᶠ x in l, ∥f x∥ ≤ c * ∥g' x∥ :=
is_O_iff_eventually_is_O_with.trans $ by simp only [is_O_with]

/-! ### Subsingleton -/

@[nontriviality] lemma is_o_of_subsingleton [subsingleton E'] : is_o f' g' l :=
is_o.of_bound $ λ c hc, by simp [subsingleton.elim (f' _) 0, mul_nonneg hc.le]

@[nontriviality] lemma is_O_of_subsingleton [subsingleton E'] : is_O f' g' l :=
is_o_of_subsingleton.is_O

/-! ### Congruence -/

theorem is_O_with_congr {c₁ c₂} {f₁ f₂ : α → E} {g₁ g₂ : α → F} {l : filter α}
  (hc : c₁ = c₂) (hf : f₁ =ᶠ[l] f₂) (hg : g₁ =ᶠ[l] g₂) :
  is_O_with c₁ f₁ g₁ l ↔ is_O_with c₂ f₂ g₂ l :=
begin
  unfold is_O_with,
  subst c₂,
  apply filter.eventually_congr,
  filter_upwards [hf, hg] with _ e₁ e₂,
  rw [e₁, e₂],
end

theorem is_O_with.congr' {c₁ c₂} {f₁ f₂ : α → E} {g₁ g₂ : α → F} {l : filter α}
  (hc : c₁ = c₂) (hf : f₁ =ᶠ[l] f₂) (hg : g₁ =ᶠ[l] g₂) :
  is_O_with c₁ f₁ g₁ l → is_O_with c₂ f₂ g₂ l :=
(is_O_with_congr hc hf hg).mp

theorem is_O_with.congr {c₁ c₂} {f₁ f₂ : α → E} {g₁ g₂ : α → F} {l : filter α}
  (hc : c₁ = c₂) (hf : ∀ x, f₁ x = f₂ x) (hg : ∀ x, g₁ x = g₂ x) :
  is_O_with c₁ f₁ g₁ l → is_O_with c₂ f₂ g₂ l :=
λ h, h.congr' hc (univ_mem' hf) (univ_mem' hg)

theorem is_O_with.congr_left {f₁ f₂ : α → E} {l : filter α} (hf : ∀ x, f₁ x = f₂ x) :
  is_O_with c f₁ g l → is_O_with c f₂ g l :=
is_O_with.congr rfl hf (λ _, rfl)

theorem is_O_with.congr_right {g₁ g₂ : α → F} {l : filter α} (hg : ∀ x, g₁ x = g₂ x) :
  is_O_with c f g₁ l → is_O_with c f g₂ l :=
is_O_with.congr rfl (λ _, rfl) hg

theorem is_O_with.congr_const {c₁ c₂} {l : filter α} (hc : c₁ = c₂) :
  is_O_with c₁ f g l → is_O_with c₂ f g l :=
is_O_with.congr hc (λ _, rfl) (λ _, rfl)

theorem is_O_congr {f₁ f₂ : α → E} {g₁ g₂ : α → F} {l : filter α}
    (hf : f₁ =ᶠ[l] f₂) (hg : g₁ =ᶠ[l] g₂) :
  is_O f₁ g₁ l ↔ is_O f₂ g₂ l :=
by { unfold is_O, exact exists_congr (λ c, is_O_with_congr rfl hf hg) }

theorem is_O.congr' {f₁ f₂ : α → E} {g₁ g₂ : α → F} {l : filter α}
    (hf : f₁ =ᶠ[l] f₂) (hg : g₁ =ᶠ[l] g₂) :
  is_O f₁ g₁ l → is_O f₂ g₂ l :=
(is_O_congr hf hg).mp

theorem is_O.congr {f₁ f₂ : α → E} {g₁ g₂ : α → F} {l : filter α}
    (hf : ∀ x, f₁ x = f₂ x) (hg : ∀ x, g₁ x = g₂ x) :
  is_O f₁ g₁ l → is_O f₂ g₂ l :=
λ h, h.congr' (univ_mem' hf) (univ_mem' hg)

theorem is_O.congr_left {f₁ f₂ : α → E} {l : filter α} (hf : ∀ x, f₁ x = f₂ x) :
  is_O f₁ g l → is_O f₂ g l :=
is_O.congr hf (λ _, rfl)

theorem is_O.congr_right {g₁ g₂ : α → E} {l : filter α} (hg : ∀ x, g₁ x = g₂ x) :
  is_O f g₁ l → is_O f g₂ l :=
is_O.congr (λ _, rfl) hg

theorem is_o_congr {f₁ f₂ : α → E} {g₁ g₂ : α → F} {l : filter α}
    (hf : f₁ =ᶠ[l] f₂) (hg : g₁ =ᶠ[l] g₂) :
  is_o f₁ g₁ l ↔ is_o f₂ g₂ l :=
by { unfold is_o, exact ball_congr (λ c hc, is_O_with_congr (eq.refl c) hf hg) }

theorem is_o.congr' {f₁ f₂ : α → E} {g₁ g₂ : α → F} {l : filter α}
    (hf : f₁ =ᶠ[l] f₂) (hg : g₁ =ᶠ[l] g₂) :
  is_o f₁ g₁ l → is_o f₂ g₂ l :=
(is_o_congr hf hg).mp

theorem is_o.congr {f₁ f₂ : α → E} {g₁ g₂ : α → F} {l : filter α}
    (hf : ∀ x, f₁ x = f₂ x) (hg : ∀ x, g₁ x = g₂ x) :
  is_o f₁ g₁ l → is_o f₂ g₂ l :=
λ h, h.congr' (univ_mem' hf) (univ_mem' hg)

theorem is_o.congr_left {f₁ f₂ : α → E} {l : filter α} (hf : ∀ x, f₁ x = f₂ x) :
  is_o f₁ g l → is_o f₂ g l :=
is_o.congr hf (λ _, rfl)

theorem is_o.congr_right {g₁ g₂ : α → E} {l : filter α} (hg : ∀ x, g₁ x = g₂ x) :
  is_o f g₁ l → is_o f g₂ l :=
is_o.congr (λ _, rfl) hg

/-! ### Filter operations and transitivity -/

theorem is_O_with.comp_tendsto (hcfg : is_O_with c f g l)
  {k : β → α} {l' : filter β} (hk : tendsto k l' l):
  is_O_with c (f ∘ k) (g ∘ k) l' :=
is_O_with.of_bound $ hk hcfg.bound

theorem is_O.comp_tendsto (hfg : is_O f g l) {k : β → α} {l' : filter β} (hk : tendsto k l' l) :
  is_O (f ∘ k) (g ∘ k) l' :=
is_O_iff_is_O_with.2 $ hfg.is_O_with.imp (λ c h, h.comp_tendsto hk)

theorem is_o.comp_tendsto (hfg : is_o f g l) {k : β → α} {l' : filter β} (hk : tendsto k l' l) :
  is_o (f ∘ k) (g ∘ k) l' :=
is_o.of_is_O_with $ λ c cpos, (hfg.forall_is_O_with cpos).comp_tendsto hk

@[simp] theorem is_O_with_map {k : β → α} {l : filter β} :
  is_O_with c f g (map k l) ↔ is_O_with c (f ∘ k) (g ∘ k) l :=
by { unfold is_O_with, exact mem_map }

@[simp] theorem is_O_map {k : β → α} {l : filter β} :
  is_O f g (map k l) ↔ is_O (f ∘ k) (g ∘ k) l :=
by simp only [is_O, is_O_with_map]

@[simp] theorem is_o_map {k : β → α} {l : filter β} :
  is_o f g (map k l) ↔ is_o (f ∘ k) (g ∘ k) l :=
by simp only [is_o, is_O_with_map]

theorem is_O_with.mono (h : is_O_with c f g l') (hl : l ≤ l') : is_O_with c f g l :=
is_O_with.of_bound $ hl h.bound

theorem is_O.mono (h : is_O f g l') (hl : l ≤ l') : is_O f g l :=
is_O_iff_is_O_with.2 $ h.is_O_with.imp (λ c h, h.mono hl)

theorem is_o.mono (h : is_o f g l') (hl : l ≤ l') : is_o f g l :=
is_o.of_is_O_with $ λ c cpos, (h.forall_is_O_with cpos).mono hl

theorem is_O_with.trans (hfg : is_O_with c f g l) (hgk : is_O_with c' g k l) (hc : 0 ≤ c) :
  is_O_with (c * c') f k l :=
begin
  unfold is_O_with at *,
  filter_upwards [hfg, hgk] with x hx hx',
  calc ∥f x∥ ≤ c * ∥g x∥ : hx
  ... ≤ c * (c' * ∥k x∥) : mul_le_mul_of_nonneg_left hx' hc
  ... = c * c' * ∥k x∥ : (mul_assoc _ _ _).symm
end

theorem is_O.trans (hfg : is_O f g' l) (hgk : is_O g' k l) : is_O f k l :=
let ⟨c, cnonneg, hc⟩ := hfg.exists_nonneg, ⟨c', hc'⟩ := hgk.is_O_with in
(hc.trans hc' cnonneg).is_O

theorem is_o.trans_is_O_with (hfg : is_o f g l) (hgk : is_O_with c g k l) (hc : 0 < c) :
  is_o f k l :=
begin
  unfold is_o at *,
  intros c' c'pos,
  have : 0 < c' / c, from div_pos c'pos hc,
  exact ((hfg this).trans hgk (le_of_lt this)).congr_const (div_mul_cancel _ (ne_of_gt hc))
end

theorem is_o.trans_is_O (hfg : is_o f g l) (hgk : is_O g k' l) : is_o f k' l :=
let ⟨c, cpos, hc⟩ := hgk.exists_pos in hfg.trans_is_O_with hc cpos

theorem is_O_with.trans_is_o (hfg : is_O_with c f g l) (hgk : is_o g k l) (hc : 0 < c) :
  is_o f k l :=
begin
  unfold is_o at *,
  intros c' c'pos,
  have : 0 < c' / c, from div_pos c'pos hc,
  exact (hfg.trans (hgk this) (le_of_lt hc)).congr_const (mul_div_cancel' _ (ne_of_gt hc))
end

theorem is_O.trans_is_o (hfg : is_O f g' l) (hgk : is_o g' k l) : is_o f k l :=
let ⟨c, cpos, hc⟩ := hfg.exists_pos in hc.trans_is_o hgk cpos

theorem is_o.trans (hfg : is_o f g l) (hgk : is_o g k' l) : is_o f k' l :=
hfg.trans_is_O hgk.is_O

theorem is_o.trans' (hfg : is_o f g' l) (hgk : is_o g' k l) : is_o f k l :=
hfg.is_O.trans_is_o hgk

section

variable (l)

theorem is_O_with_of_le' (hfg : ∀ x, ∥f x∥ ≤ c * ∥g x∥) : is_O_with c f g l :=
is_O_with.of_bound $ univ_mem' hfg

theorem is_O_with_of_le (hfg : ∀ x, ∥f x∥ ≤ ∥g x∥) : is_O_with 1 f g l :=
is_O_with_of_le' l $ λ x, by { rw one_mul, exact hfg x }

theorem is_O_of_le' (hfg : ∀ x, ∥f x∥ ≤ c * ∥g x∥) : is_O f g l :=
(is_O_with_of_le' l hfg).is_O

theorem is_O_of_le (hfg : ∀ x, ∥f x∥ ≤ ∥g x∥) : is_O f g l :=
(is_O_with_of_le l hfg).is_O

end

theorem is_O_with_refl (f : α → E) (l : filter α) : is_O_with 1 f f l :=
is_O_with_of_le l $ λ _, le_rfl

theorem is_O_refl (f : α → E) (l : filter α) : is_O f f l := (is_O_with_refl f l).is_O

theorem is_O_with.trans_le (hfg : is_O_with c f g l) (hgk : ∀ x, ∥g x∥ ≤ ∥k x∥) (hc : 0 ≤ c) :
  is_O_with c f k l :=
(hfg.trans (is_O_with_of_le l hgk) hc).congr_const $ mul_one c

theorem is_O.trans_le (hfg : is_O f g' l) (hgk : ∀ x, ∥g' x∥ ≤ ∥k x∥) :
  is_O f k l :=
hfg.trans (is_O_of_le l hgk)

theorem is_o.trans_le (hfg : is_o f g l) (hgk : ∀ x, ∥g x∥ ≤ ∥k x∥) :
  is_o f k l :=
hfg.trans_is_O_with (is_O_with_of_le _ hgk) zero_lt_one

section bot

variables (c f g)

@[simp] theorem is_O_with_bot : is_O_with c f g ⊥ := is_O_with.of_bound $ trivial

@[simp] theorem is_O_bot : is_O f g ⊥ := (is_O_with_bot 1 f g).is_O

@[simp] theorem is_o_bot : is_o f g ⊥ := is_o.of_is_O_with $ λ c _, is_O_with_bot c f g

end bot

@[simp] theorem is_O_with_pure {x} : is_O_with c f g (pure x) ↔ ∥f x∥ ≤ c * ∥g x∥ := is_O_with_iff

theorem is_O_with.join (h : is_O_with c f g l) (h' : is_O_with c f g l') :
  is_O_with c f g (l ⊔ l') :=
is_O_with.of_bound $ mem_sup.2 ⟨h.bound, h'.bound⟩

theorem is_O_with.join' (h : is_O_with c f g' l) (h' : is_O_with c' f g' l') :
  is_O_with (max c c') f g' (l ⊔ l') :=
is_O_with.of_bound $
mem_sup.2 ⟨(h.weaken $ le_max_left c c').bound, (h'.weaken $ le_max_right c c').bound⟩

theorem is_O.join (h : is_O f g' l) (h' : is_O f g' l') : is_O f g' (l ⊔ l') :=
let ⟨c, hc⟩ := h.is_O_with, ⟨c', hc'⟩ := h'.is_O_with in (hc.join' hc').is_O

theorem is_o.join (h : is_o f g l) (h' : is_o f g l') :
  is_o f g (l ⊔ l') :=
is_o.of_is_O_with $
λ c cpos, (h.forall_is_O_with cpos).join (h'.forall_is_O_with cpos)

/-! ### Simplification : norm -/

@[simp] theorem is_O_with_norm_right : is_O_with c f (λ x, ∥g' x∥) l ↔ is_O_with c f g' l :=
by simp only [is_O_with, norm_norm]

alias is_O_with_norm_right ↔ asymptotics.is_O_with.of_norm_right asymptotics.is_O_with.norm_right

@[simp] theorem is_O_norm_right : is_O f (λ x, ∥g' x∥) l ↔ is_O f g' l :=
by { unfold is_O, exact exists_congr (λ _, is_O_with_norm_right) }

alias is_O_norm_right ↔ asymptotics.is_O.of_norm_right asymptotics.is_O.norm_right

@[simp] theorem is_o_norm_right : is_o f (λ x, ∥g' x∥) l ↔ is_o f g' l :=
by { unfold is_o, exact forall₂_congr (λ _ _, is_O_with_norm_right) }

alias is_o_norm_right ↔ asymptotics.is_o.of_norm_right asymptotics.is_o.norm_right

@[simp] theorem is_O_with_norm_left : is_O_with c (λ x, ∥f' x∥) g l ↔ is_O_with c f' g l :=
by simp only [is_O_with, norm_norm]

alias is_O_with_norm_left ↔ asymptotics.is_O_with.of_norm_left asymptotics.is_O_with.norm_left

@[simp] theorem is_O_norm_left : is_O (λ x, ∥f' x∥) g l ↔ is_O f' g l :=
by { unfold is_O, exact exists_congr (λ _, is_O_with_norm_left) }

alias is_O_norm_left ↔ asymptotics.is_O.of_norm_left asymptotics.is_O.norm_left

@[simp] theorem is_o_norm_left : is_o (λ x, ∥f' x∥) g l ↔ is_o f' g l :=
by { unfold is_o, exact forall₂_congr (λ _ _, is_O_with_norm_left) }

alias is_o_norm_left ↔ asymptotics.is_o.of_norm_left asymptotics.is_o.norm_left

theorem is_O_with_norm_norm :
  is_O_with c (λ x, ∥f' x∥) (λ x, ∥g' x∥) l ↔ is_O_with c f' g' l :=
is_O_with_norm_left.trans is_O_with_norm_right

alias is_O_with_norm_norm ↔ asymptotics.is_O_with.of_norm_norm asymptotics.is_O_with.norm_norm

theorem is_O_norm_norm :
  is_O (λ x, ∥f' x∥) (λ x, ∥g' x∥) l ↔ is_O f' g' l :=
is_O_norm_left.trans is_O_norm_right

alias is_O_norm_norm ↔ asymptotics.is_O.of_norm_norm asymptotics.is_O.norm_norm

theorem is_o_norm_norm :
  is_o (λ x, ∥f' x∥) (λ x, ∥g' x∥) l ↔ is_o f' g' l :=
is_o_norm_left.trans is_o_norm_right

alias is_o_norm_norm ↔ asymptotics.is_o.of_norm_norm asymptotics.is_o.norm_norm

/-! ### Simplification: negate -/

@[simp] theorem is_O_with_neg_right : is_O_with c f (λ x, -(g' x)) l ↔ is_O_with c f g' l :=
by simp only [is_O_with, norm_neg]

alias is_O_with_neg_right ↔ asymptotics.is_O_with.of_neg_right asymptotics.is_O_with.neg_right

@[simp] theorem is_O_neg_right : is_O f (λ x, -(g' x)) l ↔ is_O f g' l :=
by { unfold is_O, exact exists_congr (λ _, is_O_with_neg_right) }

alias is_O_neg_right ↔ asymptotics.is_O.of_neg_right asymptotics.is_O.neg_right

@[simp] theorem is_o_neg_right : is_o f (λ x, -(g' x)) l ↔ is_o f g' l :=
by { unfold is_o, exact forall₂_congr (λ _ _, is_O_with_neg_right) }

alias is_o_neg_right ↔ asymptotics.is_o.of_neg_right asymptotics.is_o.neg_right

@[simp] theorem is_O_with_neg_left : is_O_with c (λ x, -(f' x)) g l ↔ is_O_with c f' g l :=
by simp only [is_O_with, norm_neg]

alias is_O_with_neg_left ↔ asymptotics.is_O_with.of_neg_left asymptotics.is_O_with.neg_left

@[simp] theorem is_O_neg_left : is_O (λ x, -(f' x)) g l ↔ is_O f' g l :=
by { unfold is_O, exact exists_congr (λ _, is_O_with_neg_left) }

alias is_O_neg_left ↔ asymptotics.is_O.of_neg_left asymptotics.is_O.neg_left

@[simp] theorem is_o_neg_left : is_o (λ x, -(f' x)) g l ↔ is_o f' g l :=
by { unfold is_o, exact forall₂_congr (λ _ _, is_O_with_neg_left) }

alias is_o_neg_left ↔ asymptotics.is_o.of_neg_right asymptotics.is_o.neg_left

/-! ### Product of functions (right) -/

lemma is_O_with_fst_prod : is_O_with 1 f' (λ x, (f' x, g' x)) l :=
is_O_with_of_le l $ λ x, le_max_left _ _

lemma is_O_with_snd_prod : is_O_with 1 g' (λ x, (f' x, g' x)) l :=
is_O_with_of_le l $ λ x, le_max_right _ _

lemma is_O_fst_prod : is_O f' (λ x, (f' x, g' x)) l := is_O_with_fst_prod.is_O

lemma is_O_snd_prod : is_O g' (λ x, (f' x, g' x)) l := is_O_with_snd_prod.is_O

lemma is_O_fst_prod' {f' : α → E' × F'} : is_O (λ x, (f' x).1) f' l :=
by simpa [is_O, is_O_with] using is_O_fst_prod

lemma is_O_snd_prod' {f' : α → E' × F'} : is_O (λ x, (f' x).2) f' l :=
by simpa [is_O, is_O_with] using is_O_snd_prod

section

variables (f' k')

lemma is_O.prod_rightl (h : is_O f g' l) : is_O f (λx, (g' x, k' x)) l :=
let ⟨c, cnonneg, hc⟩ := h.exists_nonneg in (hc.prod_rightl k' cnonneg).is_O

lemma is_o.prod_rightl (h : is_o f g' l) : is_o f (λ x, (g' x, k' x)) l :=
is_o.of_is_O_with $
λ c cpos, (h.forall_is_O_with cpos).prod_rightl k' (le_of_lt cpos)

lemma is_O_with.prod_rightr (h : is_O_with c f g' l) (hc : 0 ≤ c) :
  is_O_with c f (λ x, (f' x, g' x)) l :=
(h.trans is_O_with_snd_prod hc).congr_const (mul_one c)

lemma is_O.prod_rightr (h : is_O f g' l) : is_O f (λx, (f' x, g' x)) l :=
let ⟨c, cnonneg, hc⟩ := h.exists_nonneg in (hc.prod_rightr f' cnonneg).is_O

lemma is_o.prod_rightr (h : is_o f g' l) : is_o f (λx, (f' x, g' x)) l :=
is_o.of_is_O_with $
λ c cpos, (h.forall_is_O_with cpos).prod_rightr f' (le_of_lt cpos)

end

lemma is_O_with.prod_left_same (hf : is_O_with c f' k' l) (hg : is_O_with c g' k' l) :
  is_O_with c (λ x, (f' x, g' x)) k' l :=
by rw is_O_with_iff at *; filter_upwards [hf, hg] with x using max_le

lemma is_O_with.prod_left (hf : is_O_with c f' k' l) (hg : is_O_with c' g' k' l) :
  is_O_with (max c c') (λ x, (f' x, g' x)) k' l :=
(hf.weaken $ le_max_left c c').prod_left_same (hg.weaken $ le_max_right c c')

lemma is_O_with.prod_left_fst (h : is_O_with c (λ x, (f' x, g' x)) k' l) :
  is_O_with c f' k' l :=
(is_O_with_fst_prod.trans h zero_le_one).congr_const $ one_mul c

lemma is_O_with.prod_left_snd (h : is_O_with c (λ x, (f' x, g' x)) k' l) :
  is_O_with c g' k' l :=
(is_O_with_snd_prod.trans h zero_le_one).congr_const $ one_mul c

lemma is_O_with_prod_left :
   is_O_with c (λ x, (f' x, g' x)) k' l ↔ is_O_with c f' k' l ∧ is_O_with c g' k' l :=
⟨λ h, ⟨h.prod_left_fst, h.prod_left_snd⟩, λ h, h.1.prod_left_same h.2⟩

lemma is_O.prod_left (hf : is_O f' k' l) (hg : is_O g' k' l) : is_O (λ x, (f' x, g' x)) k' l :=
let ⟨c, hf⟩ := hf.is_O_with, ⟨c', hg⟩ := hg.is_O_with in (hf.prod_left hg).is_O

lemma is_O.prod_left_fst (h : is_O (λ x, (f' x, g' x)) k' l) : is_O f' k' l :=
is_O_fst_prod.trans h

lemma is_O.prod_left_snd (h : is_O (λ x, (f' x, g' x)) k' l) : is_O g' k' l :=
is_O_snd_prod.trans h

@[simp] lemma is_O_prod_left :
  is_O (λ x, (f' x, g' x)) k' l ↔ is_O f' k' l ∧ is_O g' k' l :=
⟨λ h, ⟨h.prod_left_fst, h.prod_left_snd⟩, λ h, h.1.prod_left h.2⟩

lemma is_o.prod_left (hf : is_o f' k' l) (hg : is_o g' k' l) : is_o (λ x, (f' x, g' x)) k' l :=
is_o.of_is_O_with $
λ c hc, (hf.forall_is_O_with hc).prod_left_same (hg.forall_is_O_with hc)

lemma is_o.prod_left_fst (h : is_o (λ x, (f' x, g' x)) k' l) : is_o f' k' l :=
is_O_fst_prod.trans_is_o h

lemma is_o.prod_left_snd (h : is_o (λ x, (f' x, g' x)) k' l) : is_o g' k' l :=
is_O_snd_prod.trans_is_o h

@[simp] lemma is_o_prod_left :
  is_o (λ x, (f' x, g' x)) k' l ↔ is_o f' k' l ∧ is_o g' k' l :=
⟨λ h, ⟨h.prod_left_fst, h.prod_left_snd⟩, λ h, h.1.prod_left h.2⟩

lemma is_O_with.eq_zero_imp (h : is_O_with c f' g' l) : ∀ᶠ x in l, g' x = 0 → f' x = 0 :=
eventually.mono h.bound $ λ x hx hg, norm_le_zero_iff.1 $ by simpa [hg] using hx

lemma is_O.eq_zero_imp (h : is_O f' g' l) : ∀ᶠ x in l, g' x = 0 → f' x = 0 :=
let ⟨C, hC⟩ := h.is_O_with in hC.eq_zero_imp

/-! ### Addition and subtraction -/

section add_sub

variables {c₁ c₂ : ℝ} {f₁ f₂ : α → E'}

theorem is_O_with.add (h₁ : is_O_with c₁ f₁ g l) (h₂ : is_O_with c₂ f₂ g l) :
  is_O_with (c₁ + c₂) (λ x, f₁ x + f₂ x) g l :=
by rw is_O_with at *; filter_upwards [h₁, h₂] with x hx₁ hx₂ using
calc ∥f₁ x + f₂ x∥ ≤ c₁ * ∥g x∥ + c₂ * ∥g x∥ : norm_add_le_of_le hx₁ hx₂
               ... = (c₁ + c₂) * ∥g x∥       : (add_mul _ _ _).symm

theorem is_O.add (h₁ : is_O f₁ g l) (h₂ : is_O f₂ g l) : is_O (λ x, f₁ x + f₂ x) g l :=
let ⟨c₁, hc₁⟩ := h₁.is_O_with, ⟨c₂, hc₂⟩ := h₂.is_O_with in (hc₁.add hc₂).is_O

theorem is_o.add (h₁ : is_o f₁ g l) (h₂ : is_o f₂ g l) : is_o (λ x, f₁ x + f₂ x) g l :=
is_o.of_is_O_with $ λ c cpos, ((h₁.forall_is_O_with $ half_pos cpos).add
  (h₂.forall_is_O_with $ half_pos cpos)).congr_const (add_halves c)

theorem is_o.add_add {g₁ g₂ : α → F'} (h₁ : is_o f₁ g₁ l) (h₂ : is_o f₂ g₂ l) :
  is_o (λ x, f₁ x + f₂ x) (λ x, ∥g₁ x∥ + ∥g₂ x∥) l :=
by refine (h₁.trans_le $ λ x, _).add (h₂.trans_le _);
  simp [real.norm_eq_abs, abs_of_nonneg, add_nonneg]

theorem is_O.add_is_o (h₁ : is_O f₁ g l) (h₂ : is_o f₂ g l) : is_O (λ x, f₁ x + f₂ x) g l :=
h₁.add h₂.is_O

theorem is_o.add_is_O (h₁ : is_o f₁ g l) (h₂ : is_O f₂ g l) : is_O (λ x, f₁ x + f₂ x) g l :=
h₁.is_O.add h₂

theorem is_O_with.add_is_o (h₁ : is_O_with c₁ f₁ g l) (h₂ : is_o f₂ g l) (hc : c₁ < c₂) :
  is_O_with c₂ (λx, f₁ x + f₂ x) g l :=
(h₁.add (h₂.forall_is_O_with (sub_pos.2 hc))).congr_const (add_sub_cancel'_right _ _)

theorem is_o.add_is_O_with (h₁ : is_o f₁ g l) (h₂ : is_O_with c₁ f₂ g l) (hc : c₁ < c₂) :
  is_O_with c₂ (λx, f₁ x + f₂ x) g l :=
(h₂.add_is_o h₁ hc).congr_left $ λ _, add_comm _ _

theorem is_O_with.sub (h₁ : is_O_with c₁ f₁ g l) (h₂ : is_O_with c₂ f₂ g l) :
  is_O_with (c₁ + c₂) (λ x, f₁ x - f₂ x) g l :=
by simpa only [sub_eq_add_neg] using h₁.add h₂.neg_left

theorem is_O_with.sub_is_o (h₁ : is_O_with c₁ f₁ g l) (h₂ : is_o f₂ g l) (hc : c₁ < c₂) :
  is_O_with c₂ (λ x, f₁ x - f₂ x) g l :=
by simpa only [sub_eq_add_neg] using h₁.add_is_o h₂.neg_left hc

theorem is_O.sub (h₁ : is_O f₁ g l) (h₂ : is_O f₂ g l) : is_O (λ x, f₁ x - f₂ x) g l :=
by simpa only [sub_eq_add_neg] using h₁.add h₂.neg_left

theorem is_o.sub (h₁ : is_o f₁ g l) (h₂ : is_o f₂ g l) : is_o (λ x, f₁ x - f₂ x) g l :=
by simpa only [sub_eq_add_neg] using h₁.add h₂.neg_left

end add_sub

/-! ### Lemmas about `is_O (f₁ - f₂) g l` / `is_o (f₁ - f₂) g l` treated as a binary relation -/

section is_oO_as_rel

variables {f₁ f₂ f₃ : α → E'}

theorem is_O_with.symm (h : is_O_with c (λ x, f₁ x - f₂ x) g l) :
  is_O_with c (λ x, f₂ x - f₁ x) g l :=
h.neg_left.congr_left $ λ x, neg_sub _ _

theorem is_O_with_comm :
  is_O_with c (λ x, f₁ x - f₂ x) g l ↔ is_O_with c (λ x, f₂ x - f₁ x) g l :=
⟨is_O_with.symm, is_O_with.symm⟩

theorem is_O.symm (h : is_O (λ x, f₁ x - f₂ x) g l) : is_O (λ x, f₂ x - f₁ x) g l :=
h.neg_left.congr_left $ λ x, neg_sub _ _

theorem is_O_comm : is_O (λ x, f₁ x - f₂ x) g l ↔ is_O (λ x, f₂ x - f₁ x) g l :=
⟨is_O.symm, is_O.symm⟩

theorem is_o.symm (h : is_o (λ x, f₁ x - f₂ x) g l) : is_o (λ x, f₂ x - f₁ x) g l :=
by simpa only [neg_sub] using h.neg_left

theorem is_o_comm : is_o (λ x, f₁ x - f₂ x) g l ↔ is_o (λ x, f₂ x - f₁ x) g l :=
⟨is_o.symm, is_o.symm⟩

theorem is_O_with.triangle (h₁ : is_O_with c (λ x, f₁ x - f₂ x) g l)
  (h₂ : is_O_with c' (λ x, f₂ x - f₃ x) g l) :
  is_O_with (c + c') (λ x, f₁ x - f₃ x) g l :=
(h₁.add h₂).congr_left $ λ x, sub_add_sub_cancel _ _ _

theorem is_O.triangle (h₁ : is_O (λ x, f₁ x - f₂ x) g l) (h₂ : is_O (λ x, f₂ x - f₃ x) g l) :
  is_O (λ x, f₁ x - f₃ x) g l :=
(h₁.add h₂).congr_left $ λ x, sub_add_sub_cancel _ _ _

theorem is_o.triangle (h₁ : is_o (λ x, f₁ x - f₂ x) g l) (h₂ : is_o (λ x, f₂ x - f₃ x) g l) :
  is_o (λ x, f₁ x - f₃ x) g l :=
(h₁.add h₂).congr_left $ λ x, sub_add_sub_cancel _ _ _

theorem is_O.congr_of_sub (h : is_O (λ x, f₁ x - f₂ x) g l) :
  is_O f₁ g l ↔ is_O f₂ g l :=
⟨λ h', (h'.sub h).congr_left (λ x, sub_sub_cancel _ _),
 λ h', (h.add h').congr_left (λ x, sub_add_cancel _ _)⟩

theorem is_o.congr_of_sub (h : is_o (λ x, f₁ x - f₂ x) g l) :
  is_o f₁ g l ↔ is_o f₂ g l :=
⟨λ h', (h'.sub h).congr_left (λ x, sub_sub_cancel _ _),
 λ h', (h.add h').congr_left (λ x, sub_add_cancel _ _)⟩

end is_oO_as_rel

/-! ### Zero, one, and other constants -/

section zero_const

variables (g g' l)

theorem is_o_zero : is_o (λ x, (0 : E')) g' l :=
is_o.of_bound $ λ c hc, univ_mem' $ λ x,
by simpa using mul_nonneg hc.le (norm_nonneg $ g' x)

theorem is_O_with_zero (hc : 0 ≤ c) : is_O_with c (λ x, (0 : E')) g' l :=
is_O_with.of_bound $ univ_mem' $ λ x, by simpa using mul_nonneg hc (norm_nonneg $ g' x)

theorem is_O_with_zero' : is_O_with 0 (λ x, (0 : E')) g l :=
is_O_with.of_bound $ univ_mem' $ λ x, by simp

theorem is_O_zero : is_O (λ x, (0 : E')) g l :=
is_O_iff_is_O_with.2 ⟨0, is_O_with_zero' _ _⟩

theorem is_O_refl_left : is_O (λ x, f' x - f' x) g' l :=
(is_O_zero g' l).congr_left $ λ x, (sub_self _).symm

theorem is_o_refl_left : is_o (λ x, f' x - f' x) g' l :=
(is_o_zero g' l).congr_left $ λ x, (sub_self _).symm

variables {g g' l}

@[simp] theorem is_O_with_zero_right_iff :
  is_O_with c f' (λ x, (0 : F')) l ↔ ∀ᶠ x in l, f' x = 0 :=
by simp only [is_O_with, exists_prop, true_and, norm_zero, mul_zero, norm_le_zero_iff]

@[simp] theorem is_O_zero_right_iff : is_O f' (λ x, (0 : F')) l ↔ ∀ᶠ x in l, f' x = 0 :=
⟨λ h, let ⟨c, hc⟩ := h.is_O_with in is_O_with_zero_right_iff.1 hc,
  λ h, (is_O_with_zero_right_iff.2 h : is_O_with 1 _ _ _).is_O⟩

@[simp] theorem is_o_zero_right_iff :
  is_o f' (λ x, (0 : F')) l ↔ ∀ᶠ x in l, f' x = 0 :=
⟨λ h, is_O_zero_right_iff.1 h.is_O,
  λ h, is_o.of_is_O_with $ λ c hc, is_O_with_zero_right_iff.2 h⟩

theorem is_O_with_const_const (c : E) {c' : F'} (hc' : c' ≠ 0) (l : filter α) :
  is_O_with (∥c∥ / ∥c'∥) (λ x : α, c) (λ x, c') l :=
begin
  unfold is_O_with,
  apply univ_mem',
  intro x,
  rw [mem_set_of_eq, div_mul_cancel],
  rwa [ne.def, norm_eq_zero]
end

theorem is_O_const_const (c : E) {c' : F'} (hc' : c' ≠ 0) (l : filter α) :
  is_O (λ x : α, c) (λ x, c') l :=
(is_O_with_const_const c hc' l).is_O

@[simp] theorem is_O_const_const_iff {c : E'} {c' : F'} (l : filter α) [l.ne_bot] :
  is_O (λ x : α, c) (λ x, c') l ↔ (c' = 0 → c = 0) :=
begin
  rcases eq_or_ne c' 0 with rfl|hc',
  { simp },
  { simp [hc', is_O_const_const _ hc'] }
end

@[simp] lemma is_O_pure {x} : is_O f' g' (pure x) ↔ (g' x = 0 → f' x = 0) :=
calc is_O f' g' (pure x) ↔ is_O (λ y : α, f' x) (λ _, g' x) (pure x) : is_O_congr rfl rfl
                     ... ↔ g' x = 0 → f' x = 0                       : is_O_const_const_iff _

end zero_const

@[simp] lemma is_O_with_top : is_O_with c f g ⊤ ↔ ∀ x, ∥f x∥ ≤ c * ∥g x∥ := by rw is_O_with; refl

@[simp] lemma is_O_top : is_O f g ⊤ ↔ ∃ C, ∀ x, ∥f x∥ ≤ C * ∥g x∥ := by rw is_O_iff; refl

@[simp] lemma is_o_top : is_o f' g' ⊤ ↔ ∀ x, f' x = 0 :=
begin
  refine ⟨_, λ h, (is_o_zero g' ⊤).congr (λ x, (h x).symm) (λ x, rfl)⟩,
  simp only [is_o_iff, eventually_top],
  refine λ h x, norm_le_zero_iff.1 _,
  have : tendsto (λ c : ℝ, c * ∥g' x∥) (𝓝[>] 0) (𝓝 0) :=
    ((continuous_id.mul continuous_const).tendsto' _ _ (zero_mul _)).mono_left inf_le_left,
  exact le_of_tendsto_of_tendsto tendsto_const_nhds this
    (eventually_nhds_within_iff.2 $ eventually_of_forall $ λ c hc, h hc x)
end

@[simp] lemma is_O_with_principal {s : set α} :
  is_O_with c f g (𝓟 s) ↔ ∀ x ∈ s, ∥f x∥ ≤ c * ∥g x∥ :=
by rw is_O_with; refl

lemma is_O_principal {s : set α} :
  is_O f g (𝓟 s) ↔ ∃ c, ∀ x ∈ s, ∥f x∥ ≤ c * ∥g x∥ :=
by rw is_O_iff; refl

theorem is_O_with_const_one (c : E) (l : filter α) : is_O_with ∥c∥ (λ x : α, c) (λ x, (1 : 𝕜)) l :=
begin
  refine (is_O_with_const_const c _ l).congr_const _,
  { rw [norm_one, div_one] },
  { exact one_ne_zero }
end

theorem is_O_const_one (c : E) (l : filter α) : is_O (λ x : α, c) (λ x, (1 : 𝕜)) l :=
(is_O_with_const_one c l).is_O

section

variable (𝕜)

theorem is_o_const_iff_is_o_one {c : F'} (hc : c ≠ 0) :
  is_o f (λ x, c) l ↔ is_o f (λ x, (1:𝕜)) l :=
⟨λ h, h.trans_is_O $ is_O_const_one c l, λ h, h.trans_is_O $ is_O_const_const _ hc _⟩

end

theorem is_o_const_iff {c : F'} (hc : c ≠ 0) :
  is_o f' (λ x, c) l ↔ tendsto f' l (𝓝 0) :=
(is_o_const_iff_is_o_one ℝ hc).trans
begin
  clear hc c,
  simp only [is_o, is_O_with, norm_one, mul_one, metric.nhds_basis_closed_ball.tendsto_right_iff,
    metric.mem_closed_ball, dist_zero_right]
end

lemma is_o_id_const {c : F'} (hc : c ≠ 0) :
  is_o (λ (x : E'), x) (λ x, c) (𝓝 0) :=
(is_o_const_iff hc).mpr (continuous_id.tendsto 0)

theorem _root_.filter.is_bounded_under.is_O_const (h : is_bounded_under (≤) l (norm ∘ f))
  {c : F'} (hc : c ≠ 0) : is_O f (λ x, c) l :=
begin
  rcases h with ⟨C, hC⟩,
  refine (is_O.of_bound 1 _).trans (is_O_const_const C hc l),
  refine (eventually_map.1 hC).mono (λ x h, _),
  calc ∥f x∥ ≤ C : h
  ... ≤ abs C : le_abs_self C
  ... = 1 * ∥C∥ : (one_mul _).symm
end

theorem is_O_const_of_tendsto {y : E'} (h : tendsto f' l (𝓝 y)) {c : F'} (hc : c ≠ 0) :
  is_O f' (λ x, c) l :=
h.norm.is_bounded_under_le.is_O_const hc

section

variable (𝕜)

theorem is_o_one_iff : is_o f' (λ x, (1 : 𝕜)) l ↔ tendsto f' l (𝓝 0) :=
is_o_const_iff one_ne_zero

theorem is_O_one_of_tendsto {y : E'} (h : tendsto f' l (𝓝 y)) :
  is_O f' (λ x, (1:𝕜)) l :=
is_O_const_of_tendsto h one_ne_zero

theorem is_O.trans_tendsto_nhds (hfg : is_O f g' l) {y : F'} (hg : tendsto g' l (𝓝 y)) :
  is_O f (λ x, (1:𝕜)) l :=
hfg.trans $ is_O_one_of_tendsto 𝕜 hg

end

theorem is_O.trans_tendsto (hfg : is_O f' g' l) (hg : tendsto g' l (𝓝 0)) :
  tendsto f' l (𝓝 0) :=
(is_o_one_iff ℝ).1 $ hfg.trans_is_o $ (is_o_one_iff ℝ).2 hg

theorem is_o.trans_tendsto (hfg : is_o f' g' l) (hg : tendsto g' l (𝓝 0)) :
  tendsto f' l (𝓝 0) :=
hfg.is_O.trans_tendsto hg

/-! ### Multiplication by a constant -/

theorem is_O_with_const_mul_self (c : R) (f : α → R) (l : filter α) :
  is_O_with ∥c∥ (λ x, c * f x) f l :=
is_O_with_of_le' _ $ λ x, norm_mul_le _ _

theorem is_O_const_mul_self (c : R) (f : α → R) (l : filter α) :
  is_O (λ x, c * f x) f l :=
(is_O_with_const_mul_self c f l).is_O

theorem is_O_with.const_mul_left {f : α → R} (h : is_O_with c f g l) (c' : R) :
  is_O_with (∥c'∥ * c) (λ x, c' * f x) g l :=
(is_O_with_const_mul_self c' f l).trans h (norm_nonneg c')

theorem is_O.const_mul_left {f : α → R} (h : is_O f g l) (c' : R) :
  is_O (λ x, c' * f x) g l :=
let ⟨c, hc⟩ := h.is_O_with in (hc.const_mul_left c').is_O

theorem is_O_with_self_const_mul' (u : Rˣ) (f : α → R) (l : filter α) :
  is_O_with ∥(↑u⁻¹:R)∥ f (λ x, ↑u * f x) l :=
(is_O_with_const_mul_self ↑u⁻¹ _ l).congr_left $ λ x, u.inv_mul_cancel_left (f x)

theorem is_O_with_self_const_mul (c : 𝕜) (hc : c ≠ 0) (f : α → 𝕜) (l : filter α) :
  is_O_with ∥c∥⁻¹ f (λ x, c * f x) l :=
(is_O_with_self_const_mul' (units.mk0 c hc) f l).congr_const $
  norm_inv c

theorem is_O_self_const_mul' {c : R} (hc : is_unit c) (f : α → R) (l : filter α) :
  is_O f (λ x, c * f x) l :=
let ⟨u, hu⟩ := hc in hu ▸ (is_O_with_self_const_mul' u f l).is_O

theorem is_O_self_const_mul (c : 𝕜) (hc : c ≠ 0) (f : α → 𝕜) (l : filter α) :
  is_O f (λ x, c * f x) l :=
is_O_self_const_mul' (is_unit.mk0 c hc) f l

theorem is_O_const_mul_left_iff' {f : α → R} {c : R} (hc : is_unit c) :
  is_O (λ x, c * f x) g l ↔ is_O f g l :=
⟨(is_O_self_const_mul' hc f l).trans, λ h, h.const_mul_left c⟩

theorem is_O_const_mul_left_iff {f : α → 𝕜} {c : 𝕜} (hc : c ≠ 0) :
  is_O (λ x, c * f x) g l ↔ is_O f g l :=
is_O_const_mul_left_iff' $ is_unit.mk0 c hc

theorem is_o.const_mul_left {f : α → R} (h : is_o f g l) (c : R) :
  is_o (λ x, c * f x) g l :=
(is_O_const_mul_self c f l).trans_is_o h

theorem is_o_const_mul_left_iff' {f : α → R} {c : R} (hc : is_unit c) :
  is_o (λ x, c * f x) g l ↔ is_o f g l :=
⟨(is_O_self_const_mul' hc f l).trans_is_o, λ h, h.const_mul_left c⟩

theorem is_o_const_mul_left_iff {f : α → 𝕜} {c : 𝕜} (hc : c ≠ 0) :
  is_o (λ x, c * f x) g l ↔ is_o f g l :=
is_o_const_mul_left_iff' $ is_unit.mk0 c hc

theorem is_O_with.of_const_mul_right {g : α → R} {c : R} (hc' : 0 ≤ c')
  (h : is_O_with c' f (λ x, c * g x) l) :
  is_O_with (c' * ∥c∥) f g l :=
h.trans (is_O_with_const_mul_self c g l) hc'

theorem is_O.of_const_mul_right {g : α → R} {c : R}
  (h : is_O f (λ x, c * g x) l) :
  is_O f g l :=
let ⟨c, cnonneg, hc⟩ := h.exists_nonneg in (hc.of_const_mul_right cnonneg).is_O

theorem is_O_with.const_mul_right' {g : α → R} {u : Rˣ} {c' : ℝ} (hc' : 0 ≤ c')
  (h : is_O_with c' f g l) :
  is_O_with (c' * ∥(↑u⁻¹:R)∥) f (λ x, ↑u * g x) l :=
h.trans (is_O_with_self_const_mul' _ _ _) hc'

theorem is_O_with.const_mul_right {g : α → 𝕜} {c : 𝕜} (hc : c ≠ 0)
  {c' : ℝ} (hc' : 0 ≤ c') (h : is_O_with c' f g l) :
  is_O_with (c' * ∥c∥⁻¹) f (λ x, c * g x) l :=
h.trans (is_O_with_self_const_mul c hc g l) hc'

theorem is_O.const_mul_right' {g : α → R} {c : R} (hc : is_unit c) (h : is_O f g l) :
  is_O f (λ x, c * g x) l :=
h.trans (is_O_self_const_mul' hc g l)

theorem is_O.const_mul_right {g : α → 𝕜} {c : 𝕜} (hc : c ≠ 0) (h : is_O f g l) :
  is_O f (λ x, c * g x) l :=
h.const_mul_right' $ is_unit.mk0 c hc

theorem is_O_const_mul_right_iff' {g : α → R} {c : R} (hc : is_unit c) :
  is_O f (λ x, c * g x) l ↔ is_O f g l :=
⟨λ h, h.of_const_mul_right, λ h, h.const_mul_right' hc⟩

theorem is_O_const_mul_right_iff {g : α → 𝕜} {c : 𝕜} (hc : c ≠ 0) :
  is_O f (λ x, c * g x) l ↔ is_O f g l :=
is_O_const_mul_right_iff' $ is_unit.mk0 c hc

theorem is_o.of_const_mul_right {g : α → R} {c : R} (h : is_o f (λ x, c * g x) l) :
  is_o f g l :=
h.trans_is_O (is_O_const_mul_self c g l)

theorem is_o.const_mul_right' {g : α → R} {c : R} (hc : is_unit c) (h : is_o f g l) :
  is_o f (λ x, c * g x) l :=
h.trans_is_O (is_O_self_const_mul' hc g l)

theorem is_o.const_mul_right {g : α → 𝕜} {c : 𝕜} (hc : c ≠ 0) (h : is_o f g l) :
  is_o f (λ x, c * g x) l :=
h.const_mul_right' $ is_unit.mk0 c hc

theorem is_o_const_mul_right_iff' {g : α → R} {c : R} (hc : is_unit c) :
  is_o f (λ x, c * g x) l ↔ is_o f g l :=
⟨λ h, h.of_const_mul_right, λ h, h.const_mul_right' hc⟩

theorem is_o_const_mul_right_iff {g : α → 𝕜} {c : 𝕜} (hc : c ≠ 0) :
  is_o f (λ x, c * g x) l ↔ is_o f g l :=
is_o_const_mul_right_iff' $ is_unit.mk0 c hc

/-! ### Multiplication -/

theorem is_O_with.mul {f₁ f₂ : α → R} {g₁ g₂ : α → 𝕜} {c₁ c₂ : ℝ}
  (h₁ : is_O_with c₁ f₁ g₁ l) (h₂ : is_O_with c₂ f₂ g₂ l) :
  is_O_with (c₁ * c₂) (λ x, f₁ x * f₂ x) (λ x, g₁ x * g₂ x) l :=
begin
  unfold is_O_with at *,
  filter_upwards [h₁, h₂] with _ hx₁ hx₂,
  apply le_trans (norm_mul_le _ _),
  convert mul_le_mul hx₁ hx₂ (norm_nonneg _) (le_trans (norm_nonneg _) hx₁) using 1,
  rw [norm_mul, mul_mul_mul_comm]
end

theorem is_O.mul {f₁ f₂ : α → R} {g₁ g₂ : α → 𝕜}
  (h₁ : is_O f₁ g₁ l) (h₂ : is_O f₂ g₂ l) :
  is_O (λ x, f₁ x * f₂ x) (λ x, g₁ x * g₂ x) l :=
let ⟨c, hc⟩ := h₁.is_O_with, ⟨c', hc'⟩ := h₂.is_O_with in (hc.mul hc').is_O

theorem is_O.mul_is_o {f₁ f₂ : α → R} {g₁ g₂ : α → 𝕜}
  (h₁ : is_O f₁ g₁ l) (h₂ : is_o f₂ g₂ l) :
  is_o (λ x, f₁ x * f₂ x) (λ x, g₁ x * g₂ x) l :=
begin
  unfold is_o at *,
  intros c cpos,
  rcases h₁.exists_pos with ⟨c', c'pos, hc'⟩,
  exact (hc'.mul (h₂ (div_pos cpos c'pos))).congr_const (mul_div_cancel' _ (ne_of_gt c'pos))
end

theorem is_o.mul_is_O {f₁ f₂ : α → R} {g₁ g₂ : α → 𝕜}
  (h₁ : is_o f₁ g₁ l) (h₂ : is_O f₂ g₂ l) :
  is_o (λ x, f₁ x * f₂ x) (λ x, g₁ x * g₂ x) l :=
begin
  unfold is_o at *,
  intros c cpos,
  rcases h₂.exists_pos with ⟨c', c'pos, hc'⟩,
  exact ((h₁ (div_pos cpos c'pos)).mul hc').congr_const (div_mul_cancel _ (ne_of_gt c'pos))
end

theorem is_o.mul {f₁ f₂ : α → R} {g₁ g₂ : α → 𝕜} (h₁ : is_o f₁ g₁ l) (h₂ : is_o f₂ g₂ l) :
  is_o (λ x, f₁ x * f₂ x) (λ x, g₁ x * g₂ x) l :=
h₁.mul_is_O h₂.is_O

theorem is_O_with.pow' {f : α → R} {g : α → 𝕜} (h : is_O_with c f g l) :
  ∀ n : ℕ, is_O_with (nat.cases_on n ∥(1 : R)∥ (λ n, c ^ (n + 1))) (λ x, f x ^ n) (λ x, g x ^ n) l
| 0 := by simpa using is_O_with_const_const (1 : R) (@one_ne_zero 𝕜 _ _) l
| 1 := by simpa
| (n + 2) := by simpa [pow_succ] using h.mul (is_O_with.pow' (n + 1))

theorem is_O_with.pow [norm_one_class R] {f : α → R} {g : α → 𝕜} (h : is_O_with c f g l) :
  ∀ n : ℕ, is_O_with (c ^ n) (λ x, f x ^ n) (λ x, g x ^ n) l
| 0 := by simpa using h.pow' 0
| (n + 1) := h.pow' (n + 1)

theorem is_O.pow {f : α → R} {g : α → 𝕜} (h : is_O f g l) (n : ℕ) :
  is_O (λ x, f x ^ n) (λ x, g x ^ n) l :=
let ⟨C, hC⟩ := h.is_O_with in is_O_iff_is_O_with.2 ⟨_, hC.pow' n⟩

theorem is_o.pow {f : α → R} {g : α → 𝕜} (h : is_o f g l) {n : ℕ} (hn : 0 < n) :
  is_o (λ x, f x ^ n) (λ x, g x ^ n) l :=
begin
  cases n, exact hn.false.elim, clear hn,
  induction n with n ihn, { simpa only [pow_one] },
  convert h.mul ihn; simp [pow_succ]
end

/-! ### Inverse -/

theorem is_O_with.inv_rev {f : α → 𝕜} {g : α → 𝕜'} (h : is_O_with c f g l)
  (h₀ : ∀ᶠ x in l, f x ≠ 0) : is_O_with c (λ x, (g x)⁻¹) (λ x, (f x)⁻¹) l :=
begin
  refine is_O_with.of_bound (h.bound.mp (h₀.mono $ λ x h₀ hle, _)),
  cases le_or_lt c 0 with hc hc,
  { refine (h₀ $ norm_le_zero_iff.1 _).elim,
    exact hle.trans (mul_nonpos_of_nonpos_of_nonneg hc $ norm_nonneg _) },
  { replace hle := inv_le_inv_of_le (norm_pos_iff.2 h₀) hle,
    simpa only [norm_inv, mul_inv₀, ← div_eq_inv_mul, div_le_iff hc] using hle }
end

theorem is_O.inv_rev {f : α → 𝕜} {g : α → 𝕜'} (h : is_O f g l)
  (h₀ : ∀ᶠ x in l, f x ≠ 0) : is_O (λ x, (g x)⁻¹) (λ x, (f x)⁻¹) l :=
let ⟨c, hc⟩ := h.is_O_with in (hc.inv_rev h₀).is_O

theorem is_o.inv_rev {f : α → 𝕜} {g : α → 𝕜'} (h : is_o f g l)
  (h₀ : ∀ᶠ x in l, f x ≠ 0) : is_o (λ x, (g x)⁻¹) (λ x, (f x)⁻¹) l :=
is_o.of_is_O_with $ λ c hc, (h.def' hc).inv_rev h₀

/-! ### Scalar multiplication -/

section smul_const
variables [normed_space 𝕜 E']

theorem is_O_with.const_smul_left (h : is_O_with c f' g l) (c' : 𝕜) :
  is_O_with (∥c'∥ * c) (λ x, c' • f' x) g l :=
by refine ((h.norm_left.const_mul_left (∥c'∥)).congr _ _ (λ _, rfl)).of_norm_left;
    intros; simp only [norm_norm, norm_smul]

theorem is_O_const_smul_left_iff {c : 𝕜} (hc : c ≠ 0) :
  is_O (λ x, c • f' x) g l ↔ is_O f' g l :=
begin
  have cne0 : ∥c∥ ≠ 0, from mt norm_eq_zero.mp hc,
  rw [←is_O_norm_left], simp only [norm_smul],
  rw [is_O_const_mul_left_iff cne0, is_O_norm_left],
end

theorem is_o_const_smul_left (h : is_o f' g l) (c : 𝕜) :
  is_o (λ x, c • f' x) g l :=
begin
  refine ((h.norm_left.const_mul_left (∥c∥)).congr_left _).of_norm_left,
  exact λ x, (norm_smul _ _).symm
end

theorem is_o_const_smul_left_iff {c : 𝕜} (hc : c ≠ 0) :
  is_o (λ x, c • f' x) g l ↔ is_o f' g l :=
begin
  have cne0 : ∥c∥ ≠ 0, from mt norm_eq_zero.mp hc,
  rw [←is_o_norm_left], simp only [norm_smul],
  rw [is_o_const_mul_left_iff cne0, is_o_norm_left]
end

theorem is_O_const_smul_right {c : 𝕜} (hc : c ≠ 0) :
  is_O f (λ x, c • f' x) l ↔ is_O f f' l :=
begin
  have cne0 : ∥c∥ ≠ 0, from mt norm_eq_zero.mp hc,
  rw [←is_O_norm_right], simp only [norm_smul],
  rw [is_O_const_mul_right_iff cne0, is_O_norm_right]
end

theorem is_o_const_smul_right {c : 𝕜} (hc : c ≠ 0) :
  is_o f (λ x, c • f' x) l ↔ is_o f f' l :=
begin
  have cne0 : ∥c∥ ≠ 0, from mt norm_eq_zero.mp hc,
  rw [←is_o_norm_right], simp only [norm_smul],
  rw [is_o_const_mul_right_iff cne0, is_o_norm_right]
end

end smul_const

section smul

variables [normed_space 𝕜 E'] [normed_space 𝕜 F']

theorem is_O_with.smul {k₁ k₂ : α → 𝕜} (h₁ : is_O_with c k₁ k₂ l) (h₂ : is_O_with c' f' g' l) :
  is_O_with (c * c') (λ x, k₁ x • f' x) (λ x, k₂ x • g' x) l :=
by refine ((h₁.norm_norm.mul h₂.norm_norm).congr rfl _ _).of_norm_norm;
  by intros; simp only [norm_smul]

theorem is_O.smul {k₁ k₂ : α → 𝕜} (h₁ : is_O k₁ k₂ l) (h₂ : is_O f' g' l) :
  is_O (λ x, k₁ x • f' x) (λ x, k₂ x • g' x) l :=
by refine ((h₁.norm_norm.mul h₂.norm_norm).congr _ _).of_norm_norm;
  by intros; simp only [norm_smul]

theorem is_O.smul_is_o {k₁ k₂ : α → 𝕜} (h₁ : is_O k₁ k₂ l) (h₂ : is_o f' g' l) :
  is_o (λ x, k₁ x • f' x) (λ x, k₂ x • g' x) l :=
by refine ((h₁.norm_norm.mul_is_o h₂.norm_norm).congr _ _).of_norm_norm;
  by intros; simp only [norm_smul]

theorem is_o.smul_is_O {k₁ k₂ : α → 𝕜} (h₁ : is_o k₁ k₂ l) (h₂ : is_O f' g' l) :
  is_o (λ x, k₁ x • f' x) (λ x, k₂ x • g' x) l :=
by refine ((h₁.norm_norm.mul_is_O h₂.norm_norm).congr _ _).of_norm_norm;
  by intros; simp only [norm_smul]

theorem is_o.smul {k₁ k₂ : α → 𝕜} (h₁ : is_o k₁ k₂ l) (h₂ : is_o f' g' l) :
  is_o (λ x, k₁ x • f' x) (λ x, k₂ x • g' x) l :=
by refine ((h₁.norm_norm.mul h₂.norm_norm).congr _ _).of_norm_norm;
  by intros; simp only [norm_smul]

end smul

/-! ### Sum -/

section sum

variables {ι : Type*} {A : ι → α → E'} {C : ι → ℝ} {s : finset ι}

theorem is_O_with.sum (h : ∀ i ∈ s, is_O_with (C i) (A i) g l) :
  is_O_with (∑ i in s, C i) (λ x, ∑ i in s, A i x) g l :=
begin
  induction s using finset.induction_on with i s is IH,
  { simp only [is_O_with_zero', finset.sum_empty, forall_true_iff] },
  { simp only [is, finset.sum_insert, not_false_iff],
    exact (h _ (finset.mem_insert_self i s)).add (IH (λ j hj, h _ (finset.mem_insert_of_mem hj))) }
end

theorem is_O.sum (h : ∀ i ∈ s, is_O (A i) g l) :
  is_O (λ x, ∑ i in s, A i x) g l :=
begin
  induction s using finset.induction_on with i s is IH,
  { simp only [is_O_zero, finset.sum_empty, forall_true_iff] },
  { simp only [is, finset.sum_insert, not_false_iff],
    exact (h _ (finset.mem_insert_self i s)).add (IH (λ j hj, h _ (finset.mem_insert_of_mem hj))) }
end

theorem is_o.sum (h : ∀ i ∈ s, is_o (A i) g' l) :
  is_o (λ x, ∑ i in s, A i x) g' l :=
begin
  induction s using finset.induction_on with i s is IH,
  { simp only [is_o_zero, finset.sum_empty, forall_true_iff] },
  { simp only [is, finset.sum_insert, not_false_iff],
    exact (h _ (finset.mem_insert_self i s)).add (IH (λ j hj, h _ (finset.mem_insert_of_mem hj))) }
end

end sum

/-! ### Relation between `f = o(g)` and `f / g → 0` -/

theorem is_o.tendsto_div_nhds_zero {f g : α → 𝕜} {l : filter α} (h : is_o f g l) :
  tendsto (λ x, f x / (g x)) l (𝓝 0) :=
have eq₁ : is_o (λ x, f x / g x) (λ x, g x / g x) l,
  by simpa only [div_eq_mul_inv] using h.mul_is_O (is_O_refl _ _),
have eq₂ : is_O (λ x, g x / g x) (λ x, (1 : 𝕜)) l,
  from is_O_of_le _ (λ x, by simp [div_self_le_one]),
(is_o_one_iff 𝕜).mp (eq₁.trans_is_O eq₂)

theorem is_o.tendsto_inv_smul_nhds_zero [normed_space 𝕜 E'] {f : α → E'} {g : α → 𝕜} {l : filter α}
  (h : is_o f g l) : tendsto (λ x, (g x)⁻¹ • f x) l (𝓝 0) :=
by simpa only [div_eq_inv_mul, ← norm_inv, ← norm_smul,
  ← tendsto_zero_iff_norm_tendsto_zero] using h.norm_norm.tendsto_div_nhds_zero

theorem is_o_iff_tendsto' {f g : α → 𝕜} {l : filter α}
    (hgf : ∀ᶠ x in l, g x = 0 → f x = 0) :
  is_o f g l ↔ tendsto (λ x, f x / (g x)) l (𝓝 0) :=
iff.intro is_o.tendsto_div_nhds_zero $ λ h,
  (((is_o_one_iff _).mpr h).mul_is_O (is_O_refl g l)).congr'
    (hgf.mono $ λ x, div_mul_cancel_of_imp) (eventually_of_forall $ λ x, one_mul _)

theorem is_o_iff_tendsto {f g : α → 𝕜} {l : filter α}
    (hgf : ∀ x, g x = 0 → f x = 0) :
  is_o f g l ↔ tendsto (λ x, f x / (g x)) l (𝓝 0) :=
⟨λ h, h.tendsto_div_nhds_zero, (is_o_iff_tendsto' (eventually_of_forall hgf)).2⟩

alias is_o_iff_tendsto' ↔ _ asymptotics.is_o_of_tendsto'
alias is_o_iff_tendsto ↔ _ asymptotics.is_o_of_tendsto

lemma is_o_const_left_of_ne {c : E'} (hc : c ≠ 0) :
  is_o (λ x, c) g l ↔ tendsto (norm ∘ g) l at_top :=
begin
  split; intro h,
  { refine (at_top_basis' 1).tendsto_right_iff.2 (λ C hC, _),
    replace hC : 0 < C := zero_lt_one.trans_le hC,
    replace h : is_o (λ _, 1 : α → ℝ) g l := (is_O_const_const _ hc _).trans_is_o h,
    refine (h.def $ inv_pos.2 hC).mono (λ x hx, _),
    rwa [norm_one, ← div_eq_inv_mul, one_le_div hC] at hx },
  { suffices : is_o (λ _, 1 : α → ℝ) g l,
      from (is_O_const_const c (@one_ne_zero ℝ _ _) _).trans_is_o this,
    refine is_o_iff.2 (λ ε ε0, (tendsto_at_top.1 h ε⁻¹).mono (λ x hx, _)),
    rwa [norm_one, ← inv_inv ε, ← div_eq_inv_mul, one_le_div (inv_pos.2 ε0)] }
end

@[simp] lemma is_o_const_left {c : E'} :
  is_o (λ x, c) g' l ↔ c = 0 ∨ tendsto (norm ∘ g') l at_top :=
begin
  rcases eq_or_ne c 0 with rfl | hc,
  { simp only [is_o_zero, eq_self_iff_true, true_or] },
  { simp only [hc, false_or, is_o_const_left_of_ne hc] }
end

@[simp] theorem is_o_const_const_iff [ne_bot l] {d : E'} {c : F'} :
  is_o (λ x, d) (λ x, c) l ↔ d = 0 :=
have ¬tendsto (function.const α ∥c∥) l at_top,
  from not_tendsto_at_top_of_tendsto_nhds tendsto_const_nhds,
by simp [function.const, this]

@[simp] lemma is_o_pure {x} : is_o f' g' (pure x) ↔ f' x = 0 :=
calc is_o f' g' (pure x) ↔ is_o (λ y : α, f' x) (λ _, g' x) (pure x) : is_o_congr rfl rfl
                     ... ↔ f' x = 0                                  : is_o_const_const_iff

/-!
### Eventually (u / v) * v = u

If `u` and `v` are linked by an `is_O_with` relation, then we
eventually have `(u / v) * v = u`, even if `v` vanishes.
-/

section eventually_mul_div_cancel

variables {u v : α → 𝕜}

lemma is_O_with.eventually_mul_div_cancel (h : is_O_with c u v l) :
  (u / v) * v =ᶠ[l] u :=
eventually.mono h.bound (λ y hy, div_mul_cancel_of_imp $ λ hv, by simpa [hv] using hy)

/-- If `u = O(v)` along `l`, then `(u / v) * v = u` eventually at `l`. -/
lemma is_O.eventually_mul_div_cancel (h : is_O u v l) :  (u / v) * v =ᶠ[l] u :=
let ⟨c, hc⟩ := h.is_O_with in hc.eventually_mul_div_cancel

/-- If `u = o(v)` along `l`, then `(u / v) * v = u` eventually at `l`. -/
lemma is_o.eventually_mul_div_cancel (h : is_o u v l) : (u / v) * v =ᶠ[l] u :=
(h.forall_is_O_with zero_lt_one).eventually_mul_div_cancel

end eventually_mul_div_cancel

/-! ### Equivalent definitions of the form `∃ φ, u =ᶠ[l] φ * v` in a `normed_field`. -/

section exists_mul_eq

variables {u v : α → 𝕜}

/-- If `∥φ∥` is eventually bounded by `c`, and `u =ᶠ[l] φ * v`, then we have `is_O_with c u v l`.
    This does not require any assumptions on `c`, which is why we keep this version along with
    `is_O_with_iff_exists_eq_mul`. -/
lemma is_O_with_of_eq_mul (φ : α → 𝕜) (hφ : ∀ᶠ x in l, ∥φ x∥ ≤ c) (h : u =ᶠ[l] φ * v) :
  is_O_with c u v l :=
begin
  unfold is_O_with,
  refine h.symm.rw (λ x a, ∥a∥ ≤ c * ∥v x∥) (hφ.mono $ λ x hx, _),
  simp only [norm_mul, pi.mul_apply],
  exact mul_le_mul_of_nonneg_right hx (norm_nonneg _)
end

lemma is_O_with_iff_exists_eq_mul (hc : 0 ≤ c) :
  is_O_with c u v l ↔ ∃ (φ : α → 𝕜) (hφ : ∀ᶠ x in l, ∥φ x∥ ≤ c), u =ᶠ[l] φ * v :=
begin
  split,
  { intro h,
    use (λ x, u x / v x),
    refine ⟨eventually.mono h.bound (λ y hy, _), h.eventually_mul_div_cancel.symm⟩,
    simpa using div_le_of_nonneg_of_le_mul (norm_nonneg _) hc hy },
  { rintros ⟨φ, hφ, h⟩,
    exact is_O_with_of_eq_mul φ hφ h }
end

lemma is_O_with.exists_eq_mul (h : is_O_with c u v l) (hc : 0 ≤ c) :
  ∃ (φ : α → 𝕜) (hφ : ∀ᶠ x in l, ∥φ x∥ ≤ c), u =ᶠ[l] φ * v :=
(is_O_with_iff_exists_eq_mul hc).mp h

lemma is_O_iff_exists_eq_mul :
  is_O u v l ↔ ∃ (φ : α → 𝕜) (hφ : l.is_bounded_under (≤) (norm ∘ φ)), u =ᶠ[l] φ * v :=
begin
  split,
  { rintros h,
    rcases h.exists_nonneg with ⟨c, hnnc, hc⟩,
    rcases hc.exists_eq_mul hnnc with ⟨φ, hφ, huvφ⟩,
    exact ⟨φ, ⟨c, hφ⟩, huvφ⟩ },
  { rintros ⟨φ, ⟨c, hφ⟩, huvφ⟩,
    exact is_O_iff_is_O_with.2 ⟨c, is_O_with_of_eq_mul φ hφ huvφ⟩ }
end

alias is_O_iff_exists_eq_mul ↔ asymptotics.is_O.exists_eq_mul _

lemma is_o_iff_exists_eq_mul :
  is_o u v l ↔ ∃ (φ : α → 𝕜) (hφ : tendsto φ l (𝓝 0)), u =ᶠ[l] φ * v :=
begin
  split,
  { exact λ h, ⟨λ x, u x / v x, h.tendsto_div_nhds_zero, h.eventually_mul_div_cancel.symm⟩ },
  { unfold is_o, rintros ⟨φ, hφ, huvφ⟩ c hpos,
    rw normed_group.tendsto_nhds_zero at hφ,
    exact is_O_with_of_eq_mul _ ((hφ c hpos).mono $ λ x, le_of_lt)  huvφ }
end

alias is_o_iff_exists_eq_mul ↔ asymptotics.is_o.exists_eq_mul _

end exists_mul_eq

/-! ### Miscellanous lemmas -/

theorem div_is_bounded_under_of_is_O {α : Type*} {l : filter α}
  {f g : α → 𝕜} (h : is_O f g l) :
  is_bounded_under (≤) l (λ x, ∥f x / g x∥) :=
begin
  obtain ⟨c, hc⟩ := is_O_iff.mp h,
  refine ⟨max c 0, eventually_map.2 (filter.mem_of_superset hc (λ x hx, _))⟩,
  simp only [mem_set_of_eq, norm_div] at ⊢ hx,
  by_cases hgx : g x = 0,
  { rw [hgx, norm_zero, div_zero, le_max_iff],
    exact or.inr le_rfl },
  { exact le_max_iff.2 (or.inl ((div_le_iff (norm_pos_iff.2 hgx)).2 hx)) }
end

theorem is_O_iff_div_is_bounded_under {α : Type*} {l : filter α}
  {f g : α → 𝕜} (hgf : ∀ᶠ x in l, g x = 0 → f x = 0) :
  is_O f g l ↔ is_bounded_under (≤) l (λ x, ∥f x / g x∥) :=
begin
  refine ⟨div_is_bounded_under_of_is_O, λ h, _⟩,
  obtain ⟨c, hc⟩ := h,
  rw filter.eventually_iff at hgf hc,
  simp only [mem_set_of_eq, mem_map, norm_div] at hc,
  refine is_O_iff.2 ⟨c, filter.eventually_of_mem (inter_mem hgf hc) (λ x hx, _)⟩,
  by_cases hgx : g x = 0,
  { simp [hx.1 hgx, hgx] },
  { refine (div_le_iff (norm_pos_iff.2 hgx)).mp hx.2 },
end

theorem is_O_of_div_tendsto_nhds {α : Type*} {l : filter α}
  {f g : α → 𝕜} (hgf : ∀ᶠ x in l, g x = 0 → f x = 0)
  (c : 𝕜) (H : filter.tendsto (f / g) l (𝓝 c)) :
  is_O f g l :=
(is_O_iff_div_is_bounded_under hgf).2 $ is_bounded_under_of_tendsto H

lemma is_o.tendsto_zero_of_tendsto {α E 𝕜 : Type*} [normed_group E] [normed_field 𝕜] {u : α → E}
  {v : α → 𝕜} {l : filter α} {y : 𝕜} (huv : is_o u v l) (hv : tendsto v l (𝓝 y)) :
  tendsto u l (𝓝 0) :=
begin
  suffices h : is_o u (λ x, (1 : 𝕜)) l,
  { rwa is_o_one_iff at h },
  exact huv.trans_is_O (is_O_one_of_tendsto 𝕜 hv),
end

theorem is_o_pow_pow {m n : ℕ} (h : m < n) :
  is_o (λ(x : 𝕜), x^n) (λx, x^m) (𝓝 0) :=
begin
  let p := n - m,
  have nmp : n = m + p := (add_tsub_cancel_of_le (le_of_lt h)).symm,
  have : (λ(x : 𝕜), x^m) = (λx, x^m * 1), by simp only [mul_one],
  simp only [this, pow_add, nmp],
  refine is_O.mul_is_o (is_O_refl _ _) ((is_o_one_iff _).2 _),
  convert (continuous_pow p).tendsto (0 : 𝕜),
  exact (zero_pow (tsub_pos_of_lt h)).symm
end

theorem is_o_norm_pow_norm_pow {m n : ℕ} (h : m < n) :
  is_o (λ(x : E'), ∥x∥^n) (λx, ∥x∥^m) (𝓝 (0 : E')) :=
(is_o_pow_pow h).comp_tendsto tendsto_norm_zero

theorem is_o_pow_id {n : ℕ} (h : 1 < n) :
  is_o (λ(x : 𝕜), x^n) (λx, x) (𝓝 0) :=
by { convert is_o_pow_pow h, simp only [pow_one] }

theorem is_o_norm_pow_id {n : ℕ} (h : 1 < n) :
  is_o (λ(x : E'), ∥x∥^n) (λx, x) (𝓝 0) :=
by simpa only [pow_one, is_o_norm_right] using @is_o_norm_pow_norm_pow E' _ _ _ h

theorem is_O_with.right_le_sub_of_lt_1 {f₁ f₂ : α → E'} (h : is_O_with c f₁ f₂ l) (hc : c < 1) :
  is_O_with (1 / (1 - c)) f₂ (λx, f₂ x - f₁ x) l :=
is_O_with.of_bound $ mem_of_superset h.bound $ λ x hx,
begin
  simp only [mem_set_of_eq] at hx ⊢,
  rw [mul_comm, one_div, ← div_eq_mul_inv, le_div_iff, mul_sub, mul_one, mul_comm],
  { exact le_trans (sub_le_sub_left hx _) (norm_sub_norm_le _ _) },
  { exact sub_pos.2 hc }
end

theorem is_O_with.right_le_add_of_lt_1 {f₁ f₂ : α → E'} (h : is_O_with c f₁ f₂ l) (hc : c < 1) :
  is_O_with (1 / (1 - c)) f₂ (λx, f₁ x + f₂ x) l :=
(h.neg_right.right_le_sub_of_lt_1 hc).neg_right.of_neg_left.congr rfl (λ x, rfl)
  (λ x, by rw [neg_sub, sub_neg_eq_add])

theorem is_o.right_is_O_sub {f₁ f₂ : α → E'} (h : is_o f₁ f₂ l) :
  is_O f₂ (λx, f₂ x - f₁ x) l :=
((h.def' one_half_pos).right_le_sub_of_lt_1 one_half_lt_one).is_O

theorem is_o.right_is_O_add {f₁ f₂ : α → E'} (h : is_o f₁ f₂ l) :
  is_O f₂ (λx, f₁ x + f₂ x) l :=
((h.def' one_half_pos).right_le_add_of_lt_1 one_half_lt_one).is_O

/-- If `f x = O(g x)` along `cofinite`, then there exists a positive constant `C` such that
`∥f x∥ ≤ C * ∥g x∥` whenever `g x ≠ 0`. -/
theorem bound_of_is_O_cofinite (h : is_O f g' cofinite) :
  ∃ C > 0, ∀ ⦃x⦄, g' x ≠ 0 → ∥f x∥ ≤ C * ∥g' x∥ :=
begin
  rcases h.exists_pos with ⟨C, C₀, hC⟩,
  rw [is_O_with, eventually_cofinite] at hC,
  rcases (hC.to_finset.image (λ x, ∥f x∥ / ∥g' x∥)).exists_le with ⟨C', hC'⟩,
  have : ∀ x, C * ∥g' x∥ < ∥f x∥ → ∥f x∥ / ∥g' x∥ ≤ C', by simpa using hC',
  refine ⟨max C C', lt_max_iff.2 (or.inl C₀), λ x h₀, _⟩,
  rw [max_mul_of_nonneg _ _ (norm_nonneg _), le_max_iff, or_iff_not_imp_left, not_le],
  exact λ hx, (div_le_iff (norm_pos_iff.2 h₀)).1 (this _ hx)
end

theorem is_O_cofinite_iff (h : ∀ x, g' x = 0 → f' x = 0) :
  is_O f' g' cofinite ↔ ∃ C, ∀ x, ∥f' x∥ ≤ C * ∥g' x∥ :=
⟨λ h', let ⟨C, C₀, hC⟩ := bound_of_is_O_cofinite h' in
  ⟨C, λ x, if hx : g' x = 0 then by simp [h _ hx, hx] else hC hx⟩,
  λ h, (is_O_top.2 h).mono le_top⟩

theorem bound_of_is_O_nat_at_top {f : ℕ → E} {g' : ℕ → E'} (h : is_O f g' at_top) :
  ∃ C > 0, ∀ ⦃x⦄, g' x ≠ 0 → ∥f x∥ ≤ C * ∥g' x∥ :=
bound_of_is_O_cofinite $ by rwa nat.cofinite_eq_at_top

theorem is_O_nat_at_top_iff {f : ℕ → E'} {g : ℕ → F'} (h : ∀ x, g x = 0 → f x = 0) :
  is_O f g at_top ↔ ∃ C, ∀ x, ∥f x∥ ≤ C * ∥g x∥ :=
by rw [← nat.cofinite_eq_at_top, is_O_cofinite_iff h]

theorem is_O_one_nat_at_top_iff {f : ℕ → E'} :
  is_O f (λ n, 1 : ℕ → ℝ) at_top ↔ ∃ C, ∀ n, ∥f n∥ ≤ C :=
iff.trans (is_O_nat_at_top_iff (λ n h, (one_ne_zero h).elim)) $
  by simp only [norm_one, mul_one]

theorem is_O_with_pi {ι : Type*} [fintype ι] {E' : ι → Type*} [Π i, normed_group (E' i)]
  {f : α → Π i, E' i} {C : ℝ} (hC : 0 ≤ C) :
  is_O_with C f g' l ↔ ∀ i, is_O_with C (λ x, f x i) g' l :=
have ∀ x, 0 ≤ C * ∥g' x∥, from λ x, mul_nonneg hC (norm_nonneg _),
by simp only [is_O_with_iff, pi_norm_le_iff (this _), eventually_all]

@[simp] theorem is_O_pi {ι : Type*} [fintype ι] {E' : ι → Type*} [Π i, normed_group (E' i)]
  {f : α → Π i, E' i} :
  is_O f g' l ↔ ∀ i, is_O (λ x, f x i) g' l :=
begin
  simp only [is_O_iff_eventually_is_O_with, ← eventually_all],
  exact eventually_congr (eventually_at_top.2 ⟨0, λ c, is_O_with_pi⟩)
end

@[simp] theorem is_o_pi {ι : Type*} [fintype ι] {E' : ι → Type*} [Π i, normed_group (E' i)]
  {f : α → Π i, E' i} :
  is_o f g' l ↔ ∀ i, is_o (λ x, f x i) g' l :=
begin
  simp only [is_o, is_O_with_pi, le_of_lt] { contextual := tt },
  exact ⟨λ h i c hc, h hc i, λ h c hc i, h i hc⟩
end

end asymptotics

open asymptotics

lemma summable_of_is_O {ι E} [normed_group E] [complete_space E] {f : ι → E} {g : ι → ℝ}
  (hg : summable g) (h : is_O f g cofinite) : summable f :=
let ⟨C, hC⟩ := h.is_O_with in
summable_of_norm_bounded_eventually (λ x, C * ∥g x∥) (hg.abs.mul_left _) hC.bound

lemma summable_of_is_O_nat {E} [normed_group E] [complete_space E] {f : ℕ → E} {g : ℕ → ℝ}
  (hg : summable g) (h : is_O f g at_top) : summable f :=
summable_of_is_O hg $ nat.cofinite_eq_at_top.symm ▸ h

namespace local_homeomorph

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

variables {E : Type*} [has_norm E] {F : Type*} [has_norm F]

/-- Transfer `is_O_with` over a `local_homeomorph`. -/
lemma is_O_with_congr (e : local_homeomorph α β) {b : β} (hb : b ∈ e.target)
  {f : β → E} {g : β → F} {C : ℝ} :
  is_O_with C f g (𝓝 b) ↔ is_O_with C (f ∘ e) (g ∘ e) (𝓝 (e.symm b)) :=
⟨λ h, h.comp_tendsto $
  by { convert e.continuous_at (e.map_target hb), exact (e.right_inv hb).symm },
  λ h, (h.comp_tendsto (e.continuous_at_symm hb)).congr' rfl
    ((e.eventually_right_inverse hb).mono $ λ x hx, congr_arg f hx)
    ((e.eventually_right_inverse hb).mono $ λ x hx, congr_arg g hx)⟩

/-- Transfer `is_O` over a `local_homeomorph`. -/
lemma is_O_congr (e : local_homeomorph α β) {b : β} (hb : b ∈ e.target) {f : β → E} {g : β → F} :
  is_O f g (𝓝 b) ↔ is_O (f ∘ e) (g ∘ e) (𝓝 (e.symm b)) :=
by { unfold is_O, exact exists_congr (λ C, e.is_O_with_congr hb) }

/-- Transfer `is_o` over a `local_homeomorph`. -/
lemma is_o_congr (e : local_homeomorph α β) {b : β} (hb : b ∈ e.target) {f : β → E} {g : β → F} :
  is_o f g (𝓝 b) ↔ is_o (f ∘ e) (g ∘ e) (𝓝 (e.symm b)) :=
by { unfold is_o, exact forall₂_congr (λ c hc, e.is_O_with_congr hb) }

end local_homeomorph

namespace homeomorph

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

variables {E : Type*} [has_norm E] {F : Type*} [has_norm F]

open asymptotics

/-- Transfer `is_O_with` over a `homeomorph`. -/
lemma is_O_with_congr (e : α ≃ₜ β) {b : β} {f : β → E} {g : β → F} {C : ℝ} :
  is_O_with C f g (𝓝 b) ↔ is_O_with C (f ∘ e) (g ∘ e) (𝓝 (e.symm b)) :=
e.to_local_homeomorph.is_O_with_congr trivial

/-- Transfer `is_O` over a `homeomorph`. -/
lemma is_O_congr (e : α ≃ₜ β) {b : β} {f : β → E} {g : β → F} :
  is_O f g (𝓝 b) ↔ is_O (f ∘ e) (g ∘ e) (𝓝 (e.symm b)) :=
by { unfold is_O, exact exists_congr (λ C, e.is_O_with_congr) }

/-- Transfer `is_o` over a `homeomorph`. -/
lemma is_o_congr (e : α ≃ₜ β) {b : β} {f : β → E} {g : β → F} :
  is_o f g (𝓝 b) ↔ is_o (f ∘ e) (g ∘ e) (𝓝 (e.symm b)) :=
by { unfold is_o, exact forall₂_congr (λ c hc, e.is_O_with_congr) }

end homeomorph