/-
Copyright (c) 2020 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker
-/
import analysis.calculus.mean_value

/-!
# L'Hôpital's rule for 0/0 indeterminate forms

In this file, we prove several forms of "L'Hopital's rule" for computing 0/0
indeterminate forms. The proof of `has_deriv_at.lhopital_zero_right_on_Ioo`
is based on the one given in the corresponding
[Wikibooks](https://en.wikibooks.org/wiki/Calculus/L%27H%C3%B4pital%27s_Rule)
chapter, and all other statements are derived from this one by composing by
carefully chosen functions.

Note that the filter `f'/g'` tends to isn't required to be one of `𝓝 a`,
`at_top` or `at_bot`. In fact, we give a slightly stronger statement by
allowing it to be any filter on `ℝ`.

Each statement is available in a `has_deriv_at` form and a `deriv` form, which
is denoted by each statement being in either the `has_deriv_at` or the `deriv`
namespace.
-/

open filter set
open_locale filter topological_space

variables {a b : ℝ} (hab : a < b) {l : filter ℝ} {f f' g g' : ℝ → ℝ}

/-!
## Interval-based versions

We start by proving statements where all conditions (derivability, `g' ≠ 0`) have
to be satisfied on an explicitely-provided interval.
-/

namespace has_deriv_at

include hab

theorem lhopital_zero_right_on_Ioo
  (hff' : ∀ x ∈ Ioo a b, has_deriv_at f (f' x) x) (hgg' : ∀ x ∈ Ioo a b, has_deriv_at g (g' x) x)
  (hg' : ∀ x ∈ Ioo a b, g' x ≠ 0)
  (hfa : tendsto f (𝓝[Ioi a] a) (𝓝 0)) (hga : tendsto g (𝓝[Ioi a] a) (𝓝 0))
  (hdiv : tendsto (λ x, (f' x) / (g' x)) (𝓝[Ioi a] a) l) :
  tendsto (λ x, (f x) / (g x)) (𝓝[Ioi a] a) l :=
begin
  have sub : ∀ x ∈ Ioo a b, Ioo a x ⊆ Ioo a b := λ x hx, Ioo_subset_Ioo (le_refl a) (le_of_lt hx.2),
  have hg : ∀ x ∈ (Ioo a b), g x ≠ 0,
  { intros x hx h,
    have : tendsto g (𝓝[Iio x] x) (𝓝 0),
    { rw [← h, ← nhds_within_Ioo_eq_nhds_within_Iio hx.1],
      exact ((hgg' x hx).continuous_at.continuous_within_at.mono $ sub x hx).tendsto },
    obtain ⟨y, hyx, hy⟩ : ∃ c ∈ Ioo a x, g' c = 0,
      from exists_has_deriv_at_eq_zero' hx.1 hga this (λ y hy, hgg' y $ sub x hx hy),
    exact hg' y (sub x hx hyx) hy },
  have : ∀ x ∈ Ioo a b, ∃ c ∈ Ioo a x, (f x) * (g' c) = (g x) * (f' c),
  { intros x hx,
    rw [← sub_zero (f x), ← sub_zero (g x)],
    exact exists_ratio_has_deriv_at_eq_ratio_slope' g g' hx.1 f f'
      (λ y hy, hgg' y $ sub x hx hy) (λ y hy, hff' y $ sub x hx hy) hga hfa
      (tendsto_nhds_within_of_tendsto_nhds (hgg' x hx).continuous_at.tendsto)
      (tendsto_nhds_within_of_tendsto_nhds (hff' x hx).continuous_at.tendsto) },
  choose! c hc using this,
  have : ∀ x ∈ Ioo a b, ((λ x', (f' x') / (g' x')) ∘ c) x = f x / g x,
  { intros x hx,
    rcases hc x hx with ⟨h₁, h₂⟩,
    field_simp [hg x hx, hg' (c x) ((sub x hx) h₁)],
    simp only [h₂],
    rwa mul_comm },
  have cmp : ∀ x ∈ Ioo a b, a < c x ∧ c x < x,
    from λ x hx, (hc x hx).1,
  rw ← nhds_within_Ioo_eq_nhds_within_Ioi hab,
  apply tendsto_nhds_within_congr this,
  simp only,
  apply hdiv.comp,
  refine tendsto_nhds_within_of_tendsto_nhds_of_eventually_within _
    (tendsto_of_tendsto_of_tendsto_of_le_of_le' tendsto_const_nhds
      (tendsto_nhds_within_of_tendsto_nhds tendsto_id) _ _) _,
  all_goals
  { apply eventually_nhds_with_of_forall,
    intros x hx,
    have := cmp x hx,
    try {simp},
    linarith [this] }
end

theorem lhopital_zero_right_on_Ico
  (hff' : ∀ x ∈ Ioo a b, has_deriv_at f (f' x) x) (hgg' : ∀ x ∈ Ioo a b, has_deriv_at g (g' x) x)
  (hcf : continuous_on f (Ico a b)) (hcg : continuous_on g (Ico a b))
  (hg' : ∀ x ∈ Ioo a b, g' x ≠ 0)
  (hfa : f a = 0) (hga : g a = 0)
  (hdiv : tendsto (λ x, (f' x) / (g' x)) (nhds_within a (Ioi a)) l) :
  tendsto (λ x, (f x) / (g x)) (nhds_within a (Ioi a)) l :=
begin
  refine lhopital_zero_right_on_Ioo hab hff' hgg' hg' _ _ hdiv,
  { rw [← hfa, ← nhds_within_Ioo_eq_nhds_within_Ioi hab],
    exact ((hcf a $ left_mem_Ico.mpr hab).mono Ioo_subset_Ico_self).tendsto },
  { rw [← hga, ← nhds_within_Ioo_eq_nhds_within_Ioi hab],
    exact ((hcg a $ left_mem_Ico.mpr hab).mono Ioo_subset_Ico_self).tendsto },
end

theorem lhopital_zero_left_on_Ioo
  (hff' : ∀ x ∈ Ioo a b, has_deriv_at f (f' x) x) (hgg' : ∀ x ∈ Ioo a b, has_deriv_at g (g' x) x)
  (hg' : ∀ x ∈ Ioo a b, g' x ≠ 0)
  (hfb : tendsto f (nhds_within b (Iio b)) (𝓝 0)) (hgb : tendsto g (nhds_within b (Iio b)) (𝓝 0))
  (hdiv : tendsto (λ x, (f' x) / (g' x)) (nhds_within b (Iio b)) l) :
  tendsto (λ x, (f x) / (g x)) (nhds_within b (Iio b)) l :=
begin
  -- Here, we essentially compose by `has_neg.neg`. The following is mostly technical details.
  have hdnf : ∀ x ∈ -Ioo a b, has_deriv_at (f ∘ has_neg.neg) (f' (-x) * (-1)) x,
    from λ x hx, comp x (hff' (-x) hx) (has_deriv_at_neg x),
  have hdng : ∀ x ∈ -Ioo a b, has_deriv_at (g ∘ has_neg.neg) (g' (-x) * (-1)) x,
    from λ x hx, comp x (hgg' (-x) hx) (has_deriv_at_neg x),
  rw preimage_neg_Ioo at hdnf,
  rw preimage_neg_Ioo at hdng,
  have := lhopital_zero_right_on_Ioo (neg_lt_neg hab) hdnf hdng
    (by { intros x hx h,
          apply hg' _ (by {rw ← preimage_neg_Ioo at hx, exact hx}),
          rwa [mul_comm, ← neg_eq_neg_one_mul, neg_eq_zero] at h })
    (hfb.comp tendsto_neg_nhds_within_Ioi_neg)
    (hgb.comp tendsto_neg_nhds_within_Ioi_neg)
    (by { simp only [neg_div_neg_eq, mul_one, mul_neg_eq_neg_mul_symm],
          exact (tendsto_congr $ λ x, rfl).mp (hdiv.comp tendsto_neg_nhds_within_Ioi_neg) }),
  have := this.comp tendsto_neg_nhds_within_Iio,
  unfold function.comp at this,
  simpa only [neg_neg]
end

theorem lhopital_zero_left_on_Ioc
  (hff' : ∀ x ∈ Ioo a b, has_deriv_at f (f' x) x) (hgg' : ∀ x ∈ Ioo a b, has_deriv_at g (g' x) x)
  (hcf : continuous_on f (Ioc a b)) (hcg : continuous_on g (Ioc a b))
  (hg' : ∀ x ∈ Ioo a b, g' x ≠ 0)
  (hfb : f b = 0) (hgb : g b = 0)
  (hdiv : tendsto (λ x, (f' x) / (g' x)) (nhds_within b (Iio b)) l) :
  tendsto (λ x, (f x) / (g x)) (nhds_within b (Iio b)) l :=
begin
  refine lhopital_zero_left_on_Ioo hab hff' hgg' hg' _ _ hdiv,
  { rw [← hfb, ← nhds_within_Ioo_eq_nhds_within_Iio hab],
    exact ((hcf b $ right_mem_Ioc.mpr hab).mono Ioo_subset_Ioc_self).tendsto },
  { rw [← hgb, ← nhds_within_Ioo_eq_nhds_within_Iio hab],
    exact ((hcg b $ right_mem_Ioc.mpr hab).mono Ioo_subset_Ioc_self).tendsto },
end

omit hab

theorem lhopital_zero_at_top_on_Ioi
  (hff' : ∀ x ∈ Ioi a, has_deriv_at f (f' x) x) (hgg' : ∀ x ∈ Ioi a, has_deriv_at g (g' x) x)
  (hg' : ∀ x ∈ Ioi a, g' x ≠ 0)
  (hftop : tendsto f at_top (𝓝 0)) (hgtop : tendsto g at_top (𝓝 0))
  (hdiv : tendsto (λ x, (f' x) / (g' x)) at_top l) :
  tendsto (λ x, (f x) / (g x)) at_top l :=
begin
  obtain ⟨ a', haa', ha'⟩ : ∃ a', a < a' ∧ 0 < a' :=
    ⟨1 + max a 0, ⟨lt_of_le_of_lt (le_max_left a 0) (lt_one_add _),
                   lt_of_le_of_lt (le_max_right a 0) (lt_one_add _)⟩⟩,
  have fact1 : ∀ (x:ℝ), x ∈ Ioo 0 a'⁻¹ → x ≠ 0 := λ _ hx, (ne_of_lt hx.1).symm,
  have fact2 : ∀ x ∈ Ioo 0 a'⁻¹, a < x⁻¹,
    from λ _ hx, lt_trans haa' ((lt_inv ha' hx.1).mpr hx.2),
  have hdnf : ∀ x ∈ Ioo 0 a'⁻¹, has_deriv_at (f ∘ has_inv.inv) (f' (x⁻¹) * (-(x^2)⁻¹)) x,
    from λ x hx, comp x (hff' (x⁻¹) $ fact2 x hx) (has_deriv_at_inv $ fact1 x hx),
  have hdng : ∀ x ∈ Ioo 0 a'⁻¹, has_deriv_at (g ∘ has_inv.inv) (g' (x⁻¹) * (-(x^2)⁻¹)) x,
    from λ x hx, comp x (hgg' (x⁻¹) $ fact2 x hx) (has_deriv_at_inv $ fact1 x hx),
  have := lhopital_zero_right_on_Ioo (inv_pos.mpr ha') hdnf hdng
    (by { intros x hx,
          refine mul_ne_zero _ (neg_ne_zero.mpr $ inv_ne_zero $ pow_ne_zero _ $ fact1 x hx),
          exact hg' _ (fact2 x hx) })
    (hftop.comp tendsto_inv_zero_at_top)
    (hgtop.comp tendsto_inv_zero_at_top)
    (by { refine (tendsto_congr' _).mp (hdiv.comp tendsto_inv_zero_at_top),
          rw eventually_eq_iff_exists_mem,
          use [Ioi 0, self_mem_nhds_within],
          intros x hx,
          unfold function.comp,
          erw mul_div_mul_right,
          refine neg_ne_zero.mpr (inv_ne_zero $ pow_ne_zero _ $ ne_of_gt hx) }),
  have := this.comp tendsto_inv_at_top_zero',
  unfold function.comp at this,
  simpa only [inv_inv'],
end

theorem lhopital_zero_at_bot_on_Iio
  (hff' : ∀ x ∈ Iio a, has_deriv_at f (f' x) x) (hgg' : ∀ x ∈ Iio a, has_deriv_at g (g' x) x)
  (hg' : ∀ x ∈ Iio a, g' x ≠ 0)
  (hfbot : tendsto f at_bot (𝓝 0)) (hgbot : tendsto g at_bot (𝓝 0))
  (hdiv : tendsto (λ x, (f' x) / (g' x)) at_bot l) :
  tendsto (λ x, (f x) / (g x)) at_bot l :=
begin
  -- Here, we essentially compose by `has_neg.neg`. The following is mostly technical details.
  have hdnf : ∀ x ∈ -Iio a, has_deriv_at (f ∘ has_neg.neg) (f' (-x) * (-1)) x,
    from λ x hx, comp x (hff' (-x) hx) (has_deriv_at_neg x),
  have hdng : ∀ x ∈ -Iio a, has_deriv_at (g ∘ has_neg.neg) (g' (-x) * (-1)) x,
    from λ x hx, comp x (hgg' (-x) hx) (has_deriv_at_neg x),
  rw preimage_neg_Iio at hdnf,
  rw preimage_neg_Iio at hdng,
  have := lhopital_zero_at_top_on_Ioi hdnf hdng
    (by { intros x hx h,
          apply hg' _ (by {rw ← preimage_neg_Iio at hx, exact hx}),
          rwa [mul_comm, ← neg_eq_neg_one_mul, neg_eq_zero] at h })
    (hfbot.comp tendsto_neg_at_top_at_bot)
    (hgbot.comp tendsto_neg_at_top_at_bot)
    (by { simp only [mul_one, mul_neg_eq_neg_mul_symm, neg_div_neg_eq],
          exact (tendsto_congr $ λ x, rfl).mp (hdiv.comp tendsto_neg_at_top_at_bot) }),
  have := this.comp tendsto_neg_at_bot_at_top,
  unfold function.comp at this,
  simpa only [neg_neg],
end

end has_deriv_at

namespace deriv

include hab

theorem lhopital_zero_right_on_Ioo
  (hdf : differentiable_on ℝ f (Ioo a b)) (hg' : ∀ x ∈ Ioo a b, deriv g x ≠ 0)
  (hfa : tendsto f (𝓝[Ioi a] a) (𝓝 0)) (hga : tendsto g (𝓝[Ioi a] a) (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) (𝓝[Ioi a] a) l) :
  tendsto (λ x, (f x) / (g x)) (𝓝[Ioi a] a) l :=
begin
  have hdf : ∀ x ∈ Ioo a b, differentiable_at ℝ f x,
    from λ x hx, (hdf x hx).differentiable_at (Ioo_mem_nhds hx.1 hx.2),
  have hdg : ∀ x ∈ Ioo a b, differentiable_at ℝ g x,
    from λ x hx, classical.by_contradiction (λ h, hg' x hx (deriv_zero_of_not_differentiable_at h)),
  exact has_deriv_at.lhopital_zero_right_on_Ioo hab (λ x hx, (hdf x hx).has_deriv_at)
    (λ x hx, (hdg x hx).has_deriv_at) hg' hfa hga hdiv
end

theorem lhopital_zero_right_on_Ico
  (hdf : differentiable_on ℝ f (Ioo a b))
  (hcf : continuous_on f (Ico a b)) (hcg : continuous_on g (Ico a b))
  (hg' : ∀ x ∈ (Ioo a b), (deriv g) x ≠ 0)
  (hfa : f a = 0) (hga : g a = 0)
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) (nhds_within a (Ioi a)) l) :
  tendsto (λ x, (f x) / (g x)) (nhds_within a (Ioi a)) l :=
begin
  refine lhopital_zero_right_on_Ioo hab hdf hg' _ _ hdiv,
  { rw [← hfa, ← nhds_within_Ioo_eq_nhds_within_Ioi hab],
    exact ((hcf a $ left_mem_Ico.mpr hab).mono Ioo_subset_Ico_self).tendsto },
  { rw [← hga, ← nhds_within_Ioo_eq_nhds_within_Ioi hab],
    exact ((hcg a $ left_mem_Ico.mpr hab).mono Ioo_subset_Ico_self).tendsto },
end

theorem lhopital_zero_left_on_Ioo
  (hdf : differentiable_on ℝ f (Ioo a b))
  (hg' : ∀ x ∈ (Ioo a b), (deriv g) x ≠ 0)
  (hfb : tendsto f (nhds_within b (Iio b)) (𝓝 0)) (hgb : tendsto g (nhds_within b (Iio b)) (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) (nhds_within b (Iio b)) l) :
  tendsto (λ x, (f x) / (g x)) (nhds_within b (Iio b)) l :=
begin
  have hdf : ∀ x ∈ Ioo a b, differentiable_at ℝ f x,
    from λ x hx, (hdf x hx).differentiable_at (Ioo_mem_nhds hx.1 hx.2),
  have hdg : ∀ x ∈ Ioo a b, differentiable_at ℝ g x,
    from λ x hx, classical.by_contradiction (λ h, hg' x hx (deriv_zero_of_not_differentiable_at h)),
  exact has_deriv_at.lhopital_zero_left_on_Ioo hab (λ x hx, (hdf x hx).has_deriv_at)
    (λ x hx, (hdg x hx).has_deriv_at) hg' hfb hgb hdiv
end

omit hab

theorem lhopital_zero_at_top_on_Ioi
  (hdf : differentiable_on ℝ f (Ioi a))
  (hg' : ∀ x ∈ (Ioi a), (deriv g) x ≠ 0)
  (hftop : tendsto f at_top (𝓝 0)) (hgtop : tendsto g at_top (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) at_top l) :
  tendsto (λ x, (f x) / (g x)) at_top l :=
begin
  have hdf : ∀ x ∈ Ioi a, differentiable_at ℝ f x,
    from λ x hx, (hdf x hx).differentiable_at (Ioi_mem_nhds hx),
  have hdg : ∀ x ∈ Ioi a, differentiable_at ℝ g x,
    from λ x hx, classical.by_contradiction (λ h, hg' x hx (deriv_zero_of_not_differentiable_at h)),
  exact has_deriv_at.lhopital_zero_at_top_on_Ioi (λ x hx, (hdf x hx).has_deriv_at)
    (λ x hx, (hdg x hx).has_deriv_at) hg' hftop hgtop hdiv,
end

theorem lhopital_zero_at_bot_on_Iio
  (hdf : differentiable_on ℝ f (Iio a))
  (hg' : ∀ x ∈ (Iio a), (deriv g) x ≠ 0)
  (hfbot : tendsto f at_bot (𝓝 0)) (hgbot : tendsto g at_bot (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) at_bot l) :
  tendsto (λ x, (f x) / (g x)) at_bot l :=
begin
  have hdf : ∀ x ∈ Iio a, differentiable_at ℝ f x,
    from λ x hx, (hdf x hx).differentiable_at (Iio_mem_nhds hx),
  have hdg : ∀ x ∈ Iio a, differentiable_at ℝ g x,
    from λ x hx, classical.by_contradiction (λ h, hg' x hx (deriv_zero_of_not_differentiable_at h)),
  exact has_deriv_at.lhopital_zero_at_bot_on_Iio (λ x hx, (hdf x hx).has_deriv_at)
    (λ x hx, (hdg x hx).has_deriv_at) hg' hfbot hgbot hdiv,
end

end deriv

/-!
## Generic versions

The following statements no longer any explicit interval, as they only require
conditions holding eventually.
-/

namespace has_deriv_at

/-- L'Hôpital's rule for approaching a real from the right, `has_deriv_at` version -/
theorem lhopital_zero_nhds_right
  (hff' : ∀ᶠ x in 𝓝[Ioi a] a, has_deriv_at f (f' x) x)
  (hgg' : ∀ᶠ x in 𝓝[Ioi a] a, has_deriv_at g (g' x) x)
  (hg' : ∀ᶠ x in 𝓝[Ioi a] a, g' x ≠ 0)
  (hfa : tendsto f (𝓝[Ioi a] a) (𝓝 0)) (hga : tendsto g (𝓝[Ioi a] a) (𝓝 0))
  (hdiv : tendsto (λ x, (f' x) / (g' x)) (𝓝[Ioi a] a) l) :
  tendsto (λ x, (f x) / (g x)) (𝓝[Ioi a] a) l :=
begin
  rw eventually_iff_exists_mem at *,
  rcases hff' with ⟨s₁, hs₁, hff'⟩,
  rcases hgg' with ⟨s₂, hs₂, hgg'⟩,
  rcases hg' with ⟨s₃, hs₃, hg'⟩,
  let s := s₁ ∩ s₂ ∩ s₃,
  have hs : s ∈ 𝓝[Ioi a] a := inter_mem_sets (inter_mem_sets hs₁ hs₂) hs₃,
  rw mem_nhds_within_Ioi_iff_exists_Ioo_subset at hs,
  rcases hs with ⟨u, hau, hu⟩,
  refine lhopital_zero_right_on_Ioo hau _ _ _ hfa hga hdiv;
  intros x hx;
  apply_assumption;
  exact (hu hx).1.1 <|> exact (hu hx).1.2 <|> exact (hu hx).2
end

/-- L'Hôpital's rule for approaching a real from the left, `has_deriv_at` version -/
theorem lhopital_zero_nhds_left
  (hff' : ∀ᶠ x in 𝓝[Iio a] a, has_deriv_at f (f' x) x)
  (hgg' : ∀ᶠ x in 𝓝[Iio a] a, has_deriv_at g (g' x) x)
  (hg' : ∀ᶠ x in 𝓝[Iio a] a, g' x ≠ 0)
  (hfa : tendsto f (𝓝[Iio a] a) (𝓝 0)) (hga : tendsto g (𝓝[Iio a] a) (𝓝 0))
  (hdiv : tendsto (λ x, (f' x) / (g' x)) (𝓝[Iio a] a) l) :
  tendsto (λ x, (f x) / (g x)) (𝓝[Iio a] a) l :=
begin
  rw eventually_iff_exists_mem at *,
  rcases hff' with ⟨s₁, hs₁, hff'⟩,
  rcases hgg' with ⟨s₂, hs₂, hgg'⟩,
  rcases hg' with ⟨s₃, hs₃, hg'⟩,
  let s := s₁ ∩ s₂ ∩ s₃,
  have hs : s ∈ 𝓝[Iio a] a := inter_mem_sets (inter_mem_sets hs₁ hs₂) hs₃,
  rw mem_nhds_within_Iio_iff_exists_Ioo_subset at hs,
  rcases hs with ⟨l, hal, hl⟩,
  refine lhopital_zero_left_on_Ioo hal _ _ _ hfa hga hdiv;
  intros x hx;
  apply_assumption;
  exact (hl hx).1.1 <|> exact (hl hx).1.2 <|> exact (hl hx).2
end

/-- L'Hôpital's rule for approaching a real, `has_deriv_at` version. This
  does not require anything about the situation at `a` -/
theorem lhopital_zero_nhds'
  (hff' : ∀ᶠ x in 𝓝[univ \ {a}] a, has_deriv_at f (f' x) x)
  (hgg' : ∀ᶠ x in 𝓝[univ \ {a}] a, has_deriv_at g (g' x) x)
  (hg' : ∀ᶠ x in 𝓝[univ \ {a}] a, g' x ≠ 0)
  (hfa : tendsto f (𝓝[univ \ {a}] a) (𝓝 0)) (hga : tendsto g (𝓝[univ \ {a}] a) (𝓝 0))
  (hdiv : tendsto (λ x, (f' x) / (g' x)) (𝓝[univ \ {a}] a) l) :
  tendsto (λ x, (f x) / (g x)) (𝓝[univ \ {a}] a) l :=
begin
  have : univ \ {a} = Iio a ∪ Ioi a,
  { ext, rw [mem_diff_singleton, eq_true_intro $ mem_univ x, true_and, ne_iff_lt_or_gt], refl },
  simp only [this, nhds_within_union, tendsto_sup, eventually_sup] at *,
  exact ⟨lhopital_zero_nhds_left hff'.1 hgg'.1 hg'.1 hfa.1 hga.1 hdiv.1,
          lhopital_zero_nhds_right hff'.2 hgg'.2 hg'.2 hfa.2 hga.2 hdiv.2⟩
end

/-- L'Hôpital's rule for approaching a real, `has_deriv_at` version -/
theorem lhopital_zero_nhds
  (hff' : ∀ᶠ x in 𝓝 a, has_deriv_at f (f' x) x)
  (hgg' : ∀ᶠ x in 𝓝 a, has_deriv_at g (g' x) x)
  (hg' : ∀ᶠ x in 𝓝 a, g' x ≠ 0)
  (hfa : tendsto f (𝓝 a) (𝓝 0)) (hga : tendsto g (𝓝 a) (𝓝 0))
  (hdiv : tendsto (λ x, f' x / g' x) (𝓝 a) l) :
  tendsto (λ x, f x / g x) (𝓝[univ \ {a}] a) l :=
begin
  apply @lhopital_zero_nhds' _ _ _ f' _ g';
  apply eventually_nhds_within_of_eventually_nhds <|> apply tendsto_nhds_within_of_tendsto_nhds;
  assumption
end

/-- L'Hôpital's rule for approaching +∞, `has_deriv_at` version -/
theorem lhopital_zero_at_top
  (hff' : ∀ᶠ x in at_top, has_deriv_at f (f' x) x)
  (hgg' : ∀ᶠ x in at_top, has_deriv_at g (g' x) x)
  (hg' : ∀ᶠ x in at_top, g' x ≠ 0)
  (hftop : tendsto f at_top (𝓝 0)) (hgtop : tendsto g at_top (𝓝 0))
  (hdiv : tendsto (λ x, (f' x) / (g' x)) at_top l) :
  tendsto (λ x, (f x) / (g x)) at_top l :=
begin
  rw eventually_iff_exists_mem at *,
  rcases hff' with ⟨s₁, hs₁, hff'⟩,
  rcases hgg' with ⟨s₂, hs₂, hgg'⟩,
  rcases hg' with ⟨s₃, hs₃, hg'⟩,
  let s := s₁ ∩ s₂ ∩ s₃,
  have hs : s ∈ at_top := inter_mem_sets (inter_mem_sets hs₁ hs₂) hs₃,
  rw mem_at_top_sets at hs,
  rcases hs with ⟨l, hl⟩,
  have hl' : Ioi l ⊆ s := λ x hx, hl x (le_of_lt hx),
  refine lhopital_zero_at_top_on_Ioi _ _ (λ x hx, hg' x $ (hl' hx).2) hftop hgtop hdiv;
  intros x hx;
  apply_assumption;
  exact (hl' hx).1.1 <|> exact (hl' hx).1.2
end

/-- L'Hôpital's rule for approaching -∞, `has_deriv_at` version -/
theorem lhopital_zero_at_bot
  (hff' : ∀ᶠ x in at_bot, has_deriv_at f (f' x) x)
  (hgg' : ∀ᶠ x in at_bot, has_deriv_at g (g' x) x)
  (hg' : ∀ᶠ x in at_bot, g' x ≠ 0)
  (hfbot : tendsto f at_bot (𝓝 0)) (hgbot : tendsto g at_bot (𝓝 0))
  (hdiv : tendsto (λ x, (f' x) / (g' x)) at_bot l) :
  tendsto (λ x, (f x) / (g x)) at_bot l :=
begin
  rw eventually_iff_exists_mem at *,
  rcases hff' with ⟨s₁, hs₁, hff'⟩,
  rcases hgg' with ⟨s₂, hs₂, hgg'⟩,
  rcases hg' with ⟨s₃, hs₃, hg'⟩,
  let s := s₁ ∩ s₂ ∩ s₃,
  have hs : s ∈ at_bot := inter_mem_sets (inter_mem_sets hs₁ hs₂) hs₃,
  rw mem_at_bot_sets at hs,
  rcases hs with ⟨l, hl⟩,
  have hl' : Iio l ⊆ s := λ x hx, hl x (le_of_lt hx),
  refine lhopital_zero_at_bot_on_Iio _ _ (λ x hx, hg' x $ (hl' hx).2) hfbot hgbot hdiv;
  intros x hx;
  apply_assumption;
  exact (hl' hx).1.1 <|> exact (hl' hx).1.2
end

end has_deriv_at

namespace deriv

/-- L'Hôpital's rule for approaching a real from the right, `deriv` version -/
theorem lhopital_zero_nhds_right
  (hdf : ∀ᶠ x in 𝓝[Ioi a] a, differentiable_at ℝ f x)
  (hg' : ∀ᶠ x in 𝓝[Ioi a] a, deriv g x ≠ 0)
  (hfa : tendsto f (𝓝[Ioi a] a) (𝓝 0)) (hga : tendsto g (𝓝[Ioi a] a) (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) (𝓝[Ioi a] a) l) :
  tendsto (λ x, (f x) / (g x)) (𝓝[Ioi a] a) l :=
begin
  have hdg : ∀ᶠ x in 𝓝[Ioi a] a, differentiable_at ℝ g x,
    from hg'.mp (eventually_of_forall $
      λ _ hg', classical.by_contradiction (λ h, hg' (deriv_zero_of_not_differentiable_at h))),
  have hdf' : ∀ᶠ x in 𝓝[Ioi a] a, has_deriv_at f (deriv f x) x,
    from hdf.mp (eventually_of_forall $ λ _, differentiable_at.has_deriv_at),
  have hdg' : ∀ᶠ x in 𝓝[Ioi a] a, has_deriv_at g (deriv g x) x,
    from hdg.mp (eventually_of_forall $ λ _, differentiable_at.has_deriv_at),
  exact has_deriv_at.lhopital_zero_nhds_right hdf' hdg' hg' hfa hga hdiv
end

/-- L'Hôpital's rule for approaching a real from the left, `deriv` version -/
theorem lhopital_zero_nhds_left
  (hdf : ∀ᶠ x in 𝓝[Iio a] a, differentiable_at ℝ f x)
  (hg' : ∀ᶠ x in 𝓝[Iio a] a, deriv g x ≠ 0)
  (hfa : tendsto f (𝓝[Iio a] a) (𝓝 0)) (hga : tendsto g (𝓝[Iio a] a) (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) (𝓝[Iio a] a) l) :
  tendsto (λ x, (f x) / (g x)) (𝓝[Iio a] a) l :=
begin
  have hdg : ∀ᶠ x in 𝓝[Iio a] a, differentiable_at ℝ g x,
    from hg'.mp (eventually_of_forall $
      λ _ hg', classical.by_contradiction (λ h, hg' (deriv_zero_of_not_differentiable_at h))),
  have hdf' : ∀ᶠ x in 𝓝[Iio a] a, has_deriv_at f (deriv f x) x,
    from hdf.mp (eventually_of_forall $ λ _, differentiable_at.has_deriv_at),
  have hdg' : ∀ᶠ x in 𝓝[Iio a] a, has_deriv_at g (deriv g x) x,
    from hdg.mp (eventually_of_forall $ λ _, differentiable_at.has_deriv_at),
  exact has_deriv_at.lhopital_zero_nhds_left hdf' hdg' hg' hfa hga hdiv
end

/-- L'Hôpital's rule for approaching a real, `deriv` version. This
  does not require anything about the situation at `a` -/
theorem lhopital_zero_nhds'
  (hdf : ∀ᶠ x in 𝓝[univ \ {a}] a, differentiable_at ℝ f x)
  (hg' : ∀ᶠ x in 𝓝[univ \ {a}] a, deriv g x ≠ 0)
  (hfa : tendsto f (𝓝[univ \ {a}] a) (𝓝 0)) (hga : tendsto g (𝓝[univ \ {a}] a) (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) (𝓝[univ \ {a}] a) l) :
  tendsto (λ x, (f x) / (g x)) (𝓝[univ \ {a}] a) l :=
begin
  have : univ \ {a} = Iio a ∪ Ioi a,
  { ext, rw [mem_diff_singleton, eq_true_intro $ mem_univ x, true_and, ne_iff_lt_or_gt], refl },
  simp only [this, nhds_within_union, tendsto_sup, eventually_sup] at *,
  exact ⟨lhopital_zero_nhds_left hdf.1 hg'.1 hfa.1 hga.1 hdiv.1,
          lhopital_zero_nhds_right hdf.2 hg'.2 hfa.2 hga.2 hdiv.2⟩,
end

/-- L'Hôpital's rule for approaching a real, `deriv` version -/
theorem lhopital_zero_nhds
  (hdf : ∀ᶠ x in 𝓝 a, differentiable_at ℝ f x)
  (hg' : ∀ᶠ x in 𝓝 a, deriv g x ≠ 0)
  (hfa : tendsto f (𝓝 a) (𝓝 0)) (hga : tendsto g (𝓝 a) (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) (𝓝 a) l) :
  tendsto (λ x, (f x) / (g x)) (𝓝[univ \ {a}] a) l :=
begin
  apply lhopital_zero_nhds';
  apply eventually_nhds_within_of_eventually_nhds <|> apply tendsto_nhds_within_of_tendsto_nhds;
  assumption
end

/-- L'Hôpital's rule for approaching +∞, `deriv` version -/
theorem lhopital_zero_at_top
  (hdf : ∀ᶠ (x : ℝ) in at_top, differentiable_at ℝ f x)
  (hg' : ∀ᶠ (x : ℝ) in at_top, deriv g x ≠ 0)
  (hftop : tendsto f at_top (𝓝 0)) (hgtop : tendsto g at_top (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) at_top l) :
  tendsto (λ x, (f x) / (g x)) at_top l :=
begin
  have hdg : ∀ᶠ x in at_top, differentiable_at ℝ g x,
    from hg'.mp (eventually_of_forall $
      λ _ hg', classical.by_contradiction (λ h, hg' (deriv_zero_of_not_differentiable_at h))),
  have hdf' : ∀ᶠ x in at_top, has_deriv_at f (deriv f x) x,
    from hdf.mp (eventually_of_forall $ λ _, differentiable_at.has_deriv_at),
  have hdg' : ∀ᶠ x in at_top, has_deriv_at g (deriv g x) x,
    from hdg.mp (eventually_of_forall $ λ _, differentiable_at.has_deriv_at),
  exact has_deriv_at.lhopital_zero_at_top hdf' hdg' hg' hftop hgtop hdiv
end

/-- L'Hôpital's rule for approaching -∞, `deriv` version -/
theorem lhopital_zero_at_bot
  (hdf : ∀ᶠ (x : ℝ) in at_bot, differentiable_at ℝ f x)
  (hg' : ∀ᶠ (x : ℝ) in at_bot, deriv g x ≠ 0)
  (hfbot : tendsto f at_bot (𝓝 0)) (hgbot : tendsto g at_bot (𝓝 0))
  (hdiv : tendsto (λ x, ((deriv f) x) / ((deriv g) x)) at_bot l) :
  tendsto (λ x, (f x) / (g x)) at_bot l :=
begin
  have hdg : ∀ᶠ x in at_bot, differentiable_at ℝ g x,
    from hg'.mp (eventually_of_forall $
      λ _ hg', classical.by_contradiction (λ h, hg' (deriv_zero_of_not_differentiable_at h))),
  have hdf' : ∀ᶠ x in at_bot, has_deriv_at f (deriv f x) x,
    from hdf.mp (eventually_of_forall $ λ _, differentiable_at.has_deriv_at),
  have hdg' : ∀ᶠ x in at_bot, has_deriv_at g (deriv g x) x,
    from hdg.mp (eventually_of_forall $ λ _, differentiable_at.has_deriv_at),
  exact has_deriv_at.lhopital_zero_at_bot hdf' hdg' hg' hfbot hgbot hdiv
end

end deriv


import GMLInit.Data.Index.Basic

namespace Index
variable {α β} (f : α → β)

@[inline]
def map'impl {xs : List α} (i : Index xs) : Index (xs.map f) :=
  Index.ofFin ⟨i.toNat, xs.length_map f ▸ i.toNat_lt_length⟩

@[implemented_by map'impl]
def map : {xs : List α} → Index xs → Index (xs.map f)
| _, Index.head => Index.head
| _, Index.tail i => Index.tail (map i)

@[inline]
def unmap'impl {xs : List α} (i : Index (xs.map f)) : Index xs :=
  Index.ofFin ⟨i.toNat, xs.length_map f ▸ i.toNat_lt_length⟩

@[implemented_by unmap'impl]
def unmap : {xs : List α} → Index (xs.map f) → Index xs
| _::_, Index.head => Index.head
| _::_, Index.tail i => Index.tail (unmap i)

theorem unmap_map {xs : List α} (i : Index xs) : (i.map f).unmap f = i := by
  induction i with
  | head => rfl
  | tail i ih => exact congrArg tail ih

theorem map_unmap {xs : List α} (i : Index (xs.map f)) : (i.unmap f).map f = i := by
  induction xs with
  | nil => contradiction
  | cons x xs ih =>
    match i with
    | head => rfl
    | tail i => exact congrArg tail (ih i)

theorem map_eq_iff_eq_unmap {xs : List α} (i : Index xs) (j : Index (xs.map f)) : i.map f = j ↔ i = j.unmap f := by
  constr
  · intro h; rw [←h, unmap_map]
  · intro h; rw [h, map_unmap]

theorem unmap_eq_iff_eq_map {xs : List α} (i : Index (xs.map f)) (j : Index xs) : i.unmap f = j ↔ i = j.map f := by
  constr
  · intro h; rw [←h, map_unmap]
  · intro h; rw [h, unmap_map]

def mapEquiv (xs : List α) : Equiv (Index xs) (Index (xs.map f)) where
  fwd := map f
  rev := unmap f
  spec := by
    intros
    constr
    · intro | rfl => exact unmap_map ..
    · intro | rfl => exact map_unmap ..

theorem val_map {xs : List α} (i : Index xs) : (i.map f).val = f i.val := by
  induction i with
  | head => rfl
  | tail _ ih => exact ih

theorem val_unmap {xs : List α} (i : Index (xs.map f)) : i.val = f (i.unmap f).val := by
  rw [←map_unmap f i, val_map, unmap_map]

end Index


/-
Copyright (c) 2022 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel
-/
import category_theory.generator
import category_theory.preadditive.yoneda

/-!
# Separators in preadditive categories

This file contains characterizations of separating sets and objects that are valid in all
preadditive categories.

-/

universes v u

open category_theory opposite

namespace category_theory
variables {C : Type u} [category.{v} C] [preadditive C]

lemma preadditive.is_separating_iff (𝒢 : set C) :
  is_separating 𝒢 ↔ ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ (G ∈ 𝒢) (h : G ⟶ X), h ≫ f = 0) → f = 0 :=
⟨λ h𝒢 X Y f hf, h𝒢 _ _ (by simpa only [limits.comp_zero] using hf),
 λ h𝒢 X Y f g hfg, sub_eq_zero.1 $ h𝒢 _
  (by simpa only [preadditive.comp_sub, sub_eq_zero] using hfg)⟩

lemma preadditive.is_coseparating_iff (𝒢 : set C) :
  is_coseparating 𝒢 ↔ ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ (G ∈ 𝒢) (h : Y ⟶ G), f ≫ h = 0) → f = 0 :=
⟨λ h𝒢 X Y f hf, h𝒢 _ _ (by simpa only [limits.zero_comp] using hf),
 λ h𝒢 X Y f g hfg, sub_eq_zero.1 $ h𝒢 _
  (by simpa only [preadditive.sub_comp, sub_eq_zero] using hfg)⟩

lemma preadditive.is_separator_iff (G : C) :
  is_separator G ↔ ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : G ⟶ X, h ≫ f = 0) → f = 0 :=
⟨λ hG X Y f hf, hG.def _ _ (by simpa only [limits.comp_zero] using hf),
 λ hG, (is_separator_def _).2 $ λ X Y f g hfg, sub_eq_zero.1 $ hG _
  (by simpa only [preadditive.comp_sub, sub_eq_zero] using hfg)⟩

lemma preadditive.is_coseparator_iff (G : C) :
  is_coseparator G ↔ ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : Y ⟶ G, f ≫ h = 0) → f = 0 :=
⟨λ hG X Y f hf, hG.def _ _ (by simpa only [limits.zero_comp] using hf),
 λ hG, (is_coseparator_def _).2 $ λ X Y f g hfg, sub_eq_zero.1 $ hG _
  (by simpa only [preadditive.sub_comp, sub_eq_zero] using hfg)⟩

lemma is_separator_iff_faithful_preadditive_coyoneda_obj (G : C) :
  is_separator G ↔ faithful (preadditive_coyoneda_obj (op G)) :=
begin
  rw [is_separator_iff_faithful_preadditive_coyoneda, preadditive_coyoneda_obj_2],
  exact ⟨λ h, by exactI faithful.of_comp _ (forget₂ _ AddCommGroup.{v}),
         λ h, by exactI faithful.comp _ _⟩
end

lemma is_coseparator_iff_faithful_preadditive_yoneda (G : C) :
  is_coseparator G ↔ faithful (preadditive_yoneda.obj G) :=
begin
  rw [is_coseparator_iff_faithful_yoneda_obj, ← whiskering_preadditive_yoneda, functor.comp_obj,
    whiskering_right_obj_obj],
  exact ⟨λ h, by exactI faithful.of_comp _ (forget AddCommGroup), λ h, by exactI faithful.comp _ _⟩
end

lemma is_coseparator_iff_faithful_preadditive_yoneda_obj (G : C) :
  is_coseparator G ↔ faithful (preadditive_yoneda_obj G) :=
begin
  rw [is_coseparator_iff_faithful_preadditive_yoneda, preadditive_yoneda_obj_2],
  exact ⟨λ h, by exactI faithful.of_comp _ (forget₂ _ AddCommGroup.{v}),
         λ h, by exactI faithful.comp _ _⟩
end

end category_theory


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Johannes Hölzl, Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.set_theory.ordinal_arithmetic
import Mathlib.tactic.omega.default
import Mathlib.PostPort

universes u_1 u v w u_2

namespace Mathlib

/-!
# Cardinals and ordinals

Relationships between cardinals and ordinals, properties of cardinals that are proved
using ordinals.

## Main definitions and results

* The `aleph'` function gives the cardinals listed by their ordinal
  index, and is the inverse of `aleph_idx`.
  `aleph' n = n`, `aleph' ω = ω`, `aleph' (ω + 1) = ℵ₁`, etc. It is an order isomorphism
  between ordinals and cardinals.
* The `aleph` function gives the infinite cardinals listed by their
  ordinal index. `aleph 0 = ω`, `aleph 1 = succ ω` is the first
  uncountable cardinal, and so on.

* `mul_eq_max` and `add_eq_max` state that the product (resp. sum) of two infinite cardinals
  is just their maximum. Several variations around this fact are also given.
* `mk_list_eq_mk` : when `α` is infinite, `α` and `list α` have the same cardinality.
* simp lemmas for inequalities between `bit0 a` and `bit1 b` are registered, making simp
  able to prove inequalities about numeral cardinals.
-/

namespace cardinal

theorem ord_is_limit {c : cardinal} (co : omega ≤ c) : ordinal.is_limit (ord c) := sorry

/-- The `aleph'` index function, which gives the ordinal index of a cardinal.
  (The `aleph'` part is because unlike `aleph` this counts also the
  finite stages. So `aleph_idx n = n`, `aleph_idx ω = ω`,
  `aleph_idx ℵ₁ = ω + 1` and so on.)
  In this definition, we register additionally that this function is an initial segment,
  i.e., it is order preserving and its range is an initial segment of the ordinals.
  For the basic function version, see `aleph_idx`.
  For an upgraded version stating that the range is everything, see `aleph_idx.rel_iso`. -/
def aleph_idx.initial_seg : initial_seg Less Less :=
  rel_embedding.collapse (order_embedding.lt_embedding ord.order_embedding)

/-- The `aleph'` index function, which gives the ordinal index of a cardinal.
  (The `aleph'` part is because unlike `aleph` this counts also the
  finite stages. So `aleph_idx n = n`, `aleph_idx ω = ω`,
  `aleph_idx ℵ₁ = ω + 1` and so on.)
  For an upgraded version stating that the range is everything, see `aleph_idx.rel_iso`. -/
def aleph_idx : cardinal → ordinal :=
  ⇑sorry

@[simp] theorem aleph_idx.initial_seg_coe : ⇑aleph_idx.initial_seg = aleph_idx :=
  rfl

@[simp] theorem aleph_idx_lt {a : cardinal} {b : cardinal} : aleph_idx a < aleph_idx b ↔ a < b :=
  rel_embedding.map_rel_iff (initial_seg.to_rel_embedding aleph_idx.initial_seg)

@[simp] theorem aleph_idx_le {a : cardinal} {b : cardinal} : aleph_idx a ≤ aleph_idx b ↔ a ≤ b := sorry

theorem aleph_idx.init {a : cardinal} {b : ordinal} : b < aleph_idx a → ∃ (c : cardinal), aleph_idx c = b :=
  initial_seg.init aleph_idx.initial_seg a b

/-- The `aleph'` index function, which gives the ordinal index of a cardinal.
  (The `aleph'` part is because unlike `aleph` this counts also the
  finite stages. So `aleph_idx n = n`, `aleph_idx ω = ω`,
  `aleph_idx ℵ₁ = ω + 1` and so on.)
  In this version, we register additionally that this function is an order isomorphism
  between cardinals and ordinals.
  For the basic function version, see `aleph_idx`. -/
def aleph_idx.rel_iso : Less ≃r Less :=
  rel_iso.of_surjective ↑aleph_idx.initial_seg sorry

@[simp] theorem aleph_idx.rel_iso_coe : ⇑aleph_idx.rel_iso = aleph_idx :=
  rfl

@[simp] theorem type_cardinal : ordinal.type Less = ordinal.univ :=
  eq.mpr (id (Eq._oldrec (Eq.refl (ordinal.type Less = ordinal.univ)) ordinal.univ_id))
    (quotient.sound (Nonempty.intro aleph_idx.rel_iso))

@[simp] theorem mk_cardinal : mk cardinal = univ := sorry

/-- The `aleph'` function gives the cardinals listed by their ordinal
  index, and is the inverse of `aleph_idx`.
  `aleph' n = n`, `aleph' ω = ω`, `aleph' (ω + 1) = ℵ₁`, etc.
  In this version, we register additionally that this function is an order isomorphism
  between ordinals and cardinals.
  For the basic function version, see `aleph'`. -/
def aleph'.rel_iso : Less ≃r Less :=
  rel_iso.symm aleph_idx.rel_iso

/-- The `aleph'` function gives the cardinals listed by their ordinal
  index, and is the inverse of `aleph_idx`.
  `aleph' n = n`, `aleph' ω = ω`, `aleph' (ω + 1) = ℵ₁`, etc. -/
def aleph' : ordinal → cardinal :=
  ⇑sorry

@[simp] theorem aleph'.rel_iso_coe : ⇑aleph'.rel_iso = aleph' :=
  rfl

@[simp] theorem aleph'_lt {o₁ : ordinal} {o₂ : ordinal} : aleph' o₁ < aleph' o₂ ↔ o₁ < o₂ :=
  rel_iso.map_rel_iff aleph'.rel_iso

@[simp] theorem aleph'_le {o₁ : ordinal} {o₂ : ordinal} : aleph' o₁ ≤ aleph' o₂ ↔ o₁ ≤ o₂ :=
  iff.mpr le_iff_le_iff_lt_iff_lt aleph'_lt

@[simp] theorem aleph'_aleph_idx (c : cardinal) : aleph' (aleph_idx c) = c :=
  equiv.symm_apply_apply (rel_iso.to_equiv aleph_idx.rel_iso) c

@[simp] theorem aleph_idx_aleph' (o : ordinal) : aleph_idx (aleph' o) = o :=
  equiv.apply_symm_apply (rel_iso.to_equiv aleph_idx.rel_iso) o

@[simp] theorem aleph'_zero : aleph' 0 = 0 := sorry

@[simp] theorem aleph'_succ {o : ordinal} : aleph' (ordinal.succ o) = succ (aleph' o) := sorry

@[simp] theorem aleph'_nat (n : ℕ) : aleph' ↑n = ↑n := sorry

theorem aleph'_le_of_limit {o : ordinal} (l : ordinal.is_limit o) {c : cardinal} : aleph' o ≤ c ↔ ∀ (o' : ordinal), o' < o → aleph' o' ≤ c := sorry

@[simp] theorem aleph'_omega : aleph' ordinal.omega = omega := sorry

/-- `aleph'` and `aleph_idx` form an equivalence between `ordinal` and `cardinal` -/
@[simp] def aleph'_equiv : ordinal ≃ cardinal :=
  equiv.mk aleph' aleph_idx aleph_idx_aleph' aleph'_aleph_idx

/-- The `aleph` function gives the infinite cardinals listed by their
  ordinal index. `aleph 0 = ω`, `aleph 1 = succ ω` is the first
  uncountable cardinal, and so on. -/
def aleph (o : ordinal) : cardinal :=
  aleph' (ordinal.omega + o)

@[simp] theorem aleph_lt {o₁ : ordinal} {o₂ : ordinal} : aleph o₁ < aleph o₂ ↔ o₁ < o₂ :=
  iff.trans aleph'_lt (ordinal.add_lt_add_iff_left ordinal.omega)

@[simp] theorem aleph_le {o₁ : ordinal} {o₂ : ordinal} : aleph o₁ ≤ aleph o₂ ↔ o₁ ≤ o₂ :=
  iff.mpr le_iff_le_iff_lt_iff_lt aleph_lt

@[simp] theorem aleph_succ {o : ordinal} : aleph (ordinal.succ o) = succ (aleph o) := sorry

@[simp] theorem aleph_zero : aleph 0 = omega := sorry

theorem omega_le_aleph' {o : ordinal} : omega ≤ aleph' o ↔ ordinal.omega ≤ o :=
  eq.mpr (id (Eq._oldrec (Eq.refl (omega ≤ aleph' o ↔ ordinal.omega ≤ o)) (Eq.symm aleph'_omega)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (aleph' ordinal.omega ≤ aleph' o ↔ ordinal.omega ≤ o)) (propext aleph'_le)))
      (iff.refl (ordinal.omega ≤ o)))

theorem omega_le_aleph (o : ordinal) : omega ≤ aleph o :=
  eq.mpr (id (Eq._oldrec (Eq.refl (omega ≤ aleph o)) (aleph.equations._eqn_1 o)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (omega ≤ aleph' (ordinal.omega + o))) (propext omega_le_aleph')))
      (ordinal.le_add_right ordinal.omega o))

theorem ord_aleph_is_limit (o : ordinal) : ordinal.is_limit (ord (aleph o)) :=
  ord_is_limit (omega_le_aleph o)

theorem exists_aleph {c : cardinal} : omega ≤ c ↔ ∃ (o : ordinal), c = aleph o := sorry

theorem aleph'_is_normal : ordinal.is_normal (ord ∘ aleph') := sorry

theorem aleph_is_normal : ordinal.is_normal (ord ∘ aleph) :=
  ordinal.is_normal.trans aleph'_is_normal (ordinal.add_is_normal ordinal.omega)

/-! ### Properties of `mul` -/

/-- If `α` is an infinite type, then `α × α` and `α` have the same cardinality. -/
theorem mul_eq_self {c : cardinal} (h : omega ≤ c) : c * c = c := sorry

/-- If `α` and `β` are infinite types, then the cardinality of `α × β` is the maximum
of the cardinalities of `α` and `β`. -/
theorem mul_eq_max {a : cardinal} {b : cardinal} (ha : omega ≤ a) (hb : omega ≤ b) : a * b = max a b := sorry

theorem mul_lt_of_lt {a : cardinal} {b : cardinal} {c : cardinal} (hc : omega ≤ c) (h1 : a < c) (h2 : b < c) : a * b < c := sorry

theorem mul_le_max_of_omega_le_left {a : cardinal} {b : cardinal} (h : omega ≤ a) : a * b ≤ max a b := sorry

theorem mul_eq_max_of_omega_le_left {a : cardinal} {b : cardinal} (h : omega ≤ a) (h' : b ≠ 0) : a * b = max a b := sorry

theorem mul_eq_left {a : cardinal} {b : cardinal} (ha : omega ≤ a) (hb : b ≤ a) (hb' : b ≠ 0) : a * b = a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a * b = a)) (mul_eq_max_of_omega_le_left ha hb')))
    (eq.mpr (id (Eq._oldrec (Eq.refl (max a b = a)) (max_eq_left hb))) (Eq.refl a))

theorem mul_eq_right {a : cardinal} {b : cardinal} (hb : omega ≤ b) (ha : a ≤ b) (ha' : a ≠ 0) : a * b = b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a * b = b)) (mul_comm a b)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (b * a = b)) (mul_eq_left hb ha ha'))) (Eq.refl b))

theorem le_mul_left {a : cardinal} {b : cardinal} (h : b ≠ 0) : a ≤ b * a := sorry

theorem le_mul_right {a : cardinal} {b : cardinal} (h : b ≠ 0) : a ≤ a * b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ≤ a * b)) (mul_comm a b))) (le_mul_left h)

theorem mul_eq_left_iff {a : cardinal} {b : cardinal} : a * b = a ↔ max omega b ≤ a ∧ b ≠ 0 ∨ b = 1 ∨ a = 0 := sorry

/-! ### Properties of `add` -/

/-- If `α` is an infinite type, then `α ⊕ α` and `α` have the same cardinality. -/
theorem add_eq_self {c : cardinal} (h : omega ≤ c) : c + c = c := sorry

/-- If `α` is an infinite type, then the cardinality of `α ⊕ β` is the maximum
of the cardinalities of `α` and `β`. -/
theorem add_eq_max {a : cardinal} {b : cardinal} (ha : omega ≤ a) : a + b = max a b :=
  le_antisymm (add_eq_self (le_trans ha (le_max_left a b)) ▸ add_le_add (le_max_left a b) (le_max_right a b))
    (max_le (self_le_add_right a b) (self_le_add_left b a))

theorem add_lt_of_lt {a : cardinal} {b : cardinal} {c : cardinal} (hc : omega ≤ c) (h1 : a < c) (h2 : b < c) : a + b < c := sorry

theorem eq_of_add_eq_of_omega_le {a : cardinal} {b : cardinal} {c : cardinal} (h : a + b = c) (ha : a < c) (hc : omega ≤ c) : b = c := sorry

theorem add_eq_left {a : cardinal} {b : cardinal} (ha : omega ≤ a) (hb : b ≤ a) : a + b = a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a + b = a)) (add_eq_max ha)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (max a b = a)) (max_eq_left hb))) (Eq.refl a))

theorem add_eq_right {a : cardinal} {b : cardinal} (hb : omega ≤ b) (ha : a ≤ b) : a + b = b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a + b = b)) (add_comm a b)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (b + a = b)) (add_eq_left hb ha))) (Eq.refl b))

theorem add_eq_left_iff {a : cardinal} {b : cardinal} : a + b = a ↔ max omega b ≤ a ∨ b = 0 := sorry

theorem add_eq_right_iff {a : cardinal} {b : cardinal} : a + b = b ↔ max omega a ≤ b ∨ a = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a + b = b ↔ max omega a ≤ b ∨ a = 0)) (add_comm a b)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (b + a = b ↔ max omega a ≤ b ∨ a = 0)) (propext add_eq_left_iff)))
      (iff.refl (max omega a ≤ b ∨ a = 0)))

theorem add_one_eq {a : cardinal} (ha : omega ≤ a) : a + 1 = a :=
  (fun (this : 1 ≤ a) => add_eq_left ha this) (le_trans (le_of_lt one_lt_omega) ha)

protected theorem eq_of_add_eq_add_left {a : cardinal} {b : cardinal} {c : cardinal} (h : a + b = a + c) (ha : a < omega) : b = c := sorry

protected theorem eq_of_add_eq_add_right {a : cardinal} {b : cardinal} {c : cardinal} (h : a + b = c + b) (hb : b < omega) : a = c := sorry

/-! ### Properties about power -/

theorem pow_le {κ : cardinal} {μ : cardinal} (H1 : omega ≤ κ) (H2 : μ < omega) : κ ^ μ ≤ κ := sorry

theorem power_self_eq {c : cardinal} (h : omega ≤ c) : c ^ c = bit0 1 ^ c := sorry

theorem power_nat_le {c : cardinal} {n : ℕ} (h : omega ≤ c) : c ^ ↑n ≤ c :=
  pow_le h (nat_lt_omega n)

theorem powerlt_omega {c : cardinal} (h : omega ≤ c) : c ^< omega = c := sorry

theorem powerlt_omega_le (c : cardinal) : c ^< omega ≤ max c omega := sorry

/-! ### Computing cardinality of various types -/

theorem mk_list_eq_mk {α : Type u} (H1 : omega ≤ mk α) : mk (List α) = mk α := sorry

theorem mk_finset_eq_mk {α : Type u} (h : omega ≤ mk α) : mk (finset α) = mk α :=
  Eq.symm
    (le_antisymm (mk_le_of_injective fun (x y : α) => iff.mp finset.singleton_inj)
      (trans_rel_left LessEq (mk_le_of_surjective list.to_finset_surjective) (mk_list_eq_mk h)))

theorem mk_bounded_set_le_of_omega_le (α : Type u) (c : cardinal) (hα : omega ≤ mk α) : mk (Subtype fun (t : set α) => mk ↥t ≤ c) ≤ mk α ^ c := sorry

theorem mk_bounded_set_le (α : Type u) (c : cardinal) : mk (Subtype fun (t : set α) => mk ↥t ≤ c) ≤ max (mk α) omega ^ c := sorry

theorem mk_bounded_subset_le {α : Type u} (s : set α) (c : cardinal) : mk (Subtype fun (t : set α) => t ⊆ s ∧ mk ↥t ≤ c) ≤ max (mk ↥s) omega ^ c := sorry

/-! ### Properties of `compl` -/

theorem mk_compl_of_omega_le {α : Type u_1} (s : set α) (h : omega ≤ mk α) (h2 : mk ↥s < mk α) : mk ↥(sᶜ) = mk α :=
  eq_of_add_eq_of_omega_le (mk_sum_compl s) h2 h

theorem mk_compl_finset_of_omega_le {α : Type u_1} (s : finset α) (h : omega ≤ mk α) : mk ↥(↑sᶜ) = mk α :=
  mk_compl_of_omega_le (↑s) h (lt_of_lt_of_le (finset_card_lt_omega s) h)

theorem mk_compl_eq_mk_compl_infinite {α : Type u_1} {s : set α} {t : set α} (h : omega ≤ mk α) (hs : mk ↥s < mk α) (ht : mk ↥t < mk α) : mk ↥(sᶜ) = mk ↥(tᶜ) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (mk ↥(sᶜ) = mk ↥(tᶜ))) (mk_compl_of_omega_le s h hs)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (mk α = mk ↥(tᶜ))) (mk_compl_of_omega_le t h ht))) (Eq.refl (mk α)))

theorem mk_compl_eq_mk_compl_finite_lift {α : Type u} {β : Type v} {s : set α} {t : set β} (hα : mk α < omega) (h1 : lift (mk α) = lift (mk β)) (h2 : lift (mk ↥s) = lift (mk ↥t)) : lift (mk ↥(sᶜ)) = lift (mk ↥(tᶜ)) := sorry

theorem mk_compl_eq_mk_compl_finite {α : Type u} {β : Type u} {s : set α} {t : set β} (hα : mk α < omega) (h1 : mk α = mk β) (h : mk ↥s = mk ↥t) : mk ↥(sᶜ) = mk ↥(tᶜ) := sorry

theorem mk_compl_eq_mk_compl_finite_same {α : Type u_1} {s : set α} {t : set α} (hα : mk α < omega) (h : mk ↥s = mk ↥t) : mk ↥(sᶜ) = mk ↥(tᶜ) :=
  mk_compl_eq_mk_compl_finite hα rfl h

/-! ### Extending an injection to an equiv -/

theorem extend_function {α : Type u_1} {β : Type u_2} {s : set α} (f : ↥s ↪ β) (h : Nonempty (↥(sᶜ) ≃ ↥(set.range ⇑fᶜ))) : ∃ (g : α ≃ β), ∀ (x : ↥s), coe_fn g ↑x = coe_fn f x := sorry

theorem extend_function_finite {α : Type u_1} {β : Type u_2} {s : set α} (f : ↥s ↪ β) (hs : mk α < omega) (h : Nonempty (α ≃ β)) : ∃ (g : α ≃ β), ∀ (x : ↥s), coe_fn g ↑x = coe_fn f x := sorry

theorem extend_function_of_lt {α : Type u_1} {β : Type u_2} {s : set α} (f : ↥s ↪ β) (hs : mk ↥s < mk α) (h : Nonempty (α ≃ β)) : ∃ (g : α ≃ β), ∀ (x : ↥s), coe_fn g ↑x = coe_fn f x := sorry

/-!
This section proves inequalities for `bit0` and `bit1`, enabling `simp` to solve inequalities
for numeral cardinals. The complexity of the resulting algorithm is not good, as in some cases
`simp` reduces an inequality to a disjunction of two situations, depending on whether a cardinal
is finite or infinite. Since the evaluation of the branches is not lazy, this is bad. It is good
enough for practical situations, though.

For specific numbers, these inequalities could also be deduced from the corresponding
inequalities of natural numbers using `norm_cast`:
```
example : (37 : cardinal) < 42 :=
by { norm_cast, norm_num }
```
-/

@[simp] theorem bit0_ne_zero (a : cardinal) : ¬bit0 a = 0 ↔ ¬a = 0 := sorry

@[simp] theorem bit1_ne_zero (a : cardinal) : ¬bit1 a = 0 := sorry

@[simp] theorem zero_lt_bit0 (a : cardinal) : 0 < bit0 a ↔ 0 < a := sorry

@[simp] theorem zero_lt_bit1 (a : cardinal) : 0 < bit1 a :=
  lt_of_lt_of_le zero_lt_one (self_le_add_left 1 (bit0 a))

@[simp] theorem one_le_bit0 (a : cardinal) : 1 ≤ bit0 a ↔ 0 < a :=
  { mp := fun (h : 1 ≤ bit0 a) => iff.mp (zero_lt_bit0 a) (lt_of_lt_of_le zero_lt_one h),
    mpr := fun (h : 0 < a) => le_trans (iff.mpr one_le_iff_pos h) (self_le_add_left a a) }

@[simp] theorem one_le_bit1 (a : cardinal) : 1 ≤ bit1 a :=
  self_le_add_left 1 (bit0 a)

theorem bit0_eq_self {c : cardinal} (h : omega ≤ c) : bit0 c = c :=
  add_eq_self h

@[simp] theorem bit0_lt_omega {c : cardinal} : bit0 c < omega ↔ c < omega := sorry

@[simp] theorem omega_le_bit0 {c : cardinal} : omega ≤ bit0 c ↔ omega ≤ c := sorry

@[simp] theorem bit1_eq_self_iff {c : cardinal} : bit1 c = c ↔ omega ≤ c := sorry

@[simp] theorem bit1_lt_omega {c : cardinal} : bit1 c < omega ↔ c < omega := sorry

@[simp] theorem omega_le_bit1 {c : cardinal} : omega ≤ bit1 c ↔ omega ≤ c := sorry

@[simp] theorem bit0_le_bit0 {a : cardinal} {b : cardinal} : bit0 a ≤ bit0 b ↔ a ≤ b := sorry

@[simp] theorem bit0_le_bit1 {a : cardinal} {b : cardinal} : bit0 a ≤ bit1 b ↔ a ≤ b := sorry

@[simp] theorem bit1_le_bit1 {a : cardinal} {b : cardinal} : bit1 a ≤ bit1 b ↔ a ≤ b :=
  { mp := fun (h : bit1 a ≤ bit1 b) => iff.mp bit0_le_bit1 (le_trans (self_le_add_right (bit0 a) 1) h),
    mpr :=
      fun (h : a ≤ b) => le_trans (add_le_add_right (add_le_add_left h a) 1) (add_le_add_right (add_le_add_right h b) 1) }

@[simp] theorem bit1_le_bit0 {a : cardinal} {b : cardinal} : bit1 a ≤ bit0 b ↔ a < b ∨ a ≤ b ∧ omega ≤ a := sorry

@[simp] theorem bit0_lt_bit0 {a : cardinal} {b : cardinal} : bit0 a < bit0 b ↔ a < b := sorry

@[simp] theorem bit1_lt_bit0 {a : cardinal} {b : cardinal} : bit1 a < bit0 b ↔ a < b := sorry

@[simp] theorem bit1_lt_bit1 {a : cardinal} {b : cardinal} : bit1 a < bit1 b ↔ a < b := sorry

@[simp] theorem bit0_lt_bit1 {a : cardinal} {b : cardinal} : bit0 a < bit1 b ↔ a < b ∨ a ≤ b ∧ a < omega := sorry

-- This strategy works generally to prove inequalities between numerals in `cardinality`.

theorem one_lt_two : 1 < bit0 1 := sorry

@[simp] theorem one_lt_bit0 {a : cardinal} : 1 < bit0 a ↔ 0 < a := sorry

@[simp] theorem one_lt_bit1 (a : cardinal) : 1 < bit1 a ↔ 0 < a := sorry

@[simp] theorem one_le_one : 1 ≤ 1 :=
  le_refl 1