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

! This file was ported from Lean 3 source module analysis.calculus.uniform_limits_deriv
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Calculus.MeanValue
import Mathbin.Analysis.NormedSpace.IsROrC
import Mathbin.Order.Filter.Curry

/-!
# Swapping limits and derivatives via uniform convergence

The purpose of this file is to prove that the derivative of the pointwise limit of a sequence of
functions is the pointwise limit of the functions' derivatives when the derivatives converge
_uniformly_. The formal statement appears as `has_fderiv_at_of_tendsto_locally_uniformly_at`.

## Main statements

* `uniform_cauchy_seq_on_filter_of_fderiv`: If
    1. `f : ℕ → E → G` is a sequence of functions which have derivatives
       `f' : ℕ → E → (E →L[𝕜] G)` on a neighborhood of `x`,
    2. the functions `f` converge at `x`, and
    3. the derivatives `f'` form a Cauchy sequence uniformly on a neighborhood of `x`,
  then the `f` form a Cauchy sequence _uniformly_ on a neighborhood of `x`
* `has_fderiv_at_of_tendsto_uniformly_on_filter` : Suppose (1), (2), and (3) above are true. Let
  `g` (resp. `g'`) be the limiting function of the `f` (resp. `g'`). Then `f'` is the derivative of
  `g` on a neighborhood of `x`
* `has_fderiv_at_of_tendsto_uniformly_on`: An often-easier-to-use version of the above theorem when
  *all* the derivatives exist and functions converge on a common open set and the derivatives
  converge uniformly there.

Each of the above statements also has variations that support `deriv` instead of `fderiv`.

## Implementation notes

Our technique for proving the main result is the famous "`ε / 3` proof." In words, you can find it
explained, for instance, at [this StackExchange post](https://math.stackexchange.com/questions/214218/uniform-convergence-of-derivatives-tao-14-2-7).
The subtlety is that we want to prove that the difference quotients of the `g` converge to the `g'`.
That is, we want to prove something like:

```
∀ ε > 0, ∃ δ > 0, ∀ y ∈ B_δ(x), |y - x|⁻¹ * |(g y - g x) - g' x (y - x)| < ε.
```

To do so, we will need to introduce a pair of quantifers

```lean
∀ ε > 0, ∃ N, ∀ n ≥ N, ∃ δ > 0, ∀ y ∈ B_δ(x), |y - x|⁻¹ * |(g y - g x) - g' x (y - x)| < ε.
```

So how do we write this in terms of filters? Well, the initial definition of the derivative is

```lean
tendsto (|y - x|⁻¹ * |(g y - g x) - g' x (y - x)|) (𝓝 x) (𝓝 0)
```

There are two ways we might introduce `n`. We could do:

```lean
∀ᶠ (n : ℕ) in at_top, tendsto (|y - x|⁻¹ * |(g y - g x) - g' x (y - x)|) (𝓝 x) (𝓝 0)
```

but this is equivalent to the quantifier order `∃ N, ∀ n ≥ N, ∀ ε > 0, ∃ δ > 0, ∀ y ∈ B_δ(x)`,
which _implies_ our desired `∀ ∃ ∀ ∃ ∀` but is _not_ equivalent to it. On the other hand, we might
try

```lean
tendsto (|y - x|⁻¹ * |(g y - g x) - g' x (y - x)|) (at_top ×ᶠ 𝓝 x) (𝓝 0)
```

but this is equivalent to the quantifer order `∀ ε > 0, ∃ N, ∃ δ > 0, ∀ n ≥ N, ∀ y ∈ B_δ(x)`, which
again _implies_ our desired `∀ ∃ ∀ ∃ ∀` but is not equivalent to it.

So to get the quantifier order we want, we need to introduce a new filter construction, which we
call a "curried filter"

```lean
tendsto (|y - x|⁻¹ * |(g y - g x) - g' x (y - x)|) (at_top.curry (𝓝 x)) (𝓝 0)
```

Then the above implications are `filter.tendsto.curry` and
`filter.tendsto.mono_left filter.curry_le_prod`. We will use both of these deductions as part of
our proof.

We note that if you loosen the assumptions of the main theorem then the proof becomes quite a bit
easier. In particular, if you assume there is a common neighborhood `s` where all of the three
assumptions of `has_fderiv_at_of_tendsto_uniformly_on_filter` hold and that the `f'` are
continuous, then you can avoid the mean value theorem and much of the work around curried filters.

## Tags

uniform convergence, limits of derivatives
-/

open Filter

open uniformity Filter Topology

section LimitsOfDerivatives

variable {ι : Type _} {l : Filter ι} {E : Type _} [NormedAddCommGroup E] {𝕜 : Type _} [IsROrC 𝕜]
  [NormedSpace 𝕜 E] {G : Type _} [NormedAddCommGroup G] [NormedSpace 𝕜 G] {f : ι → E → G}
  {g : E → G} {f' : ι → E → E →L[𝕜] G} {g' : E → E →L[𝕜] G} {x : E}

/-- If a sequence of functions real or complex functions are eventually differentiable on a
neighborhood of `x`, they are Cauchy _at_ `x`, and their derivatives
are a uniform Cauchy sequence in a neighborhood of `x`, then the functions form a uniform Cauchy
sequence in a neighborhood of `x`. -/
theorem uniformCauchySeqOnFilter_of_fderiv (hf' : UniformCauchySeqOnFilter f' l (𝓝 x))
    (hf : ∀ᶠ n : ι × E in l ×ᶠ 𝓝 x, HasFderivAt (f n.1) (f' n.1 n.2) n.2)
    (hfg : Cauchy (map (fun n => f n x) l)) : UniformCauchySeqOnFilter f l (𝓝 x) :=
  by
  let : NormedSpace ℝ E
  exact NormedSpace.restrictScalars ℝ 𝕜 _
  rw [SeminormedAddGroup.uniformCauchySeqOnFilter_iff_tendstoUniformlyOnFilter_zero] at hf'⊢
  suffices
    TendstoUniformlyOnFilter (fun (n : ι × ι) (z : E) => f n.1 z - f n.2 z - (f n.1 x - f n.2 x)) 0
        (l ×ᶠ l) (𝓝 x) ∧
      TendstoUniformlyOnFilter (fun (n : ι × ι) (z : E) => f n.1 x - f n.2 x) 0 (l ×ᶠ l) (𝓝 x)
    by
    have := this.1.add this.2
    rw [add_zero] at this
    exact this.congr (by simp)
  constructor
  · -- This inequality follows from the mean value theorem. To apply it, we will need to shrink our
    -- neighborhood to small enough ball
    rw [Metric.tendstoUniformlyOnFilter_iff] at hf'⊢
    intro ε hε
    have := (tendsto_swap4_prod.eventually (hf.prod_mk hf)).diag_of_prod_right
    obtain ⟨a, b, c, d, e⟩ := eventually_prod_iff.1 ((hf' ε hε).And this)
    obtain ⟨R, hR, hR'⟩ := metric.nhds_basis_ball.eventually_iff.mp d
    let r := min 1 R
    have hr : 0 < r := by simp [hR]
    have hr' : ∀ ⦃y : E⦄, y ∈ Metric.ball x r → c y := fun y hy =>
      hR' (lt_of_lt_of_le (metric.mem_ball.mp hy) (min_le_right _ _))
    have hxy : ∀ y : E, y ∈ Metric.ball x r → ‖y - x‖ < 1 :=
      by
      intro y hy
      rw [Metric.mem_ball, dist_eq_norm] at hy
      exact lt_of_lt_of_le hy (min_le_left _ _)
    have hxyε : ∀ y : E, y ∈ Metric.ball x r → ε * ‖y - x‖ < ε :=
      by
      intro y hy
      exact (mul_lt_iff_lt_one_right hε.lt).mpr (hxy y hy)
    -- With a small ball in hand, apply the mean value theorem
    refine'
      eventually_prod_iff.mpr
        ⟨_, b, fun e : E => Metric.ball x r e,
          eventually_mem_set.mpr (metric.nhds_basis_ball.mem_of_mem hr), fun n hn y hy => _⟩
    simp only [Pi.zero_apply, dist_zero_left] at e⊢
    refine' lt_of_le_of_lt _ (hxyε y hy)
    exact
      Convex.norm_image_sub_le_of_norm_has_fderiv_within_le
        (fun y hy => ((e hn (hr' hy)).2.1.sub (e hn (hr' hy)).2.2).HasFderivWithinAt)
        (fun y hy => (e hn (hr' hy)).1.le) (convex_ball x r) (Metric.mem_ball_self hr) hy
  · -- This is just `hfg` run through `eventually_prod_iff`
    refine' metric.tendsto_uniformly_on_filter_iff.mpr fun ε hε => _
    obtain ⟨t, ht, ht'⟩ := (metric.cauchy_iff.mp hfg).2 ε hε
    exact
      eventually_prod_iff.mpr
        ⟨fun n : ι × ι => f n.1 x ∈ t ∧ f n.2 x ∈ t,
          eventually_prod_iff.mpr ⟨_, ht, _, ht, fun n hn n' hn' => ⟨hn, hn'⟩⟩, fun y => True, by
          simp, fun n hn y hy => by simpa [norm_sub_rev, dist_eq_norm] using ht' _ hn.1 _ hn.2⟩
#align uniform_cauchy_seq_on_filter_of_fderiv uniformCauchySeqOnFilter_of_fderiv

/-- A variant of the second fundamental theorem of calculus (FTC-2): If a sequence of functions
between real or complex normed spaces are differentiable on a ball centered at `x`, they
form a Cauchy sequence _at_ `x`, and their derivatives are Cauchy uniformly on the ball, then the
functions form a uniform Cauchy sequence on the ball.

NOTE: The fact that we work on a ball is typically all that is necessary to work with power series
and Dirichlet series (our primary use case). However, this can be generalized by replacing the ball
with any connected, bounded, open set and replacing uniform convergence with local uniform
convergence. See `cauchy_map_of_uniform_cauchy_seq_on_fderiv`.
-/
theorem uniformCauchySeqOn_ball_of_fderiv {r : ℝ} (hf' : UniformCauchySeqOn f' l (Metric.ball x r))
    (hf : ∀ n : ι, ∀ y : E, y ∈ Metric.ball x r → HasFderivAt (f n) (f' n y) y)
    (hfg : Cauchy (map (fun n => f n x) l)) : UniformCauchySeqOn f l (Metric.ball x r) :=
  by
  let : NormedSpace ℝ E
  exact NormedSpace.restrictScalars ℝ 𝕜 _
  have : ne_bot l := (cauchy_map_iff.1 hfg).1
  rcases le_or_lt r 0 with (hr | hr)
  ·
    simp only [Metric.ball_eq_empty.2 hr, UniformCauchySeqOn, Set.mem_empty_iff_false,
      IsEmpty.forall_iff, eventually_const, imp_true_iff]
  rw [SeminormedAddGroup.uniformCauchySeqOn_iff_tendstoUniformlyOn_zero] at hf'⊢
  suffices
    TendstoUniformlyOn (fun (n : ι × ι) (z : E) => f n.1 z - f n.2 z - (f n.1 x - f n.2 x)) 0
        (l ×ᶠ l) (Metric.ball x r) ∧
      TendstoUniformlyOn (fun (n : ι × ι) (z : E) => f n.1 x - f n.2 x) 0 (l ×ᶠ l) (Metric.ball x r)
    by
    have := this.1.add this.2
    rw [add_zero] at this
    refine' this.congr _
    apply eventually_of_forall
    intro n z hz
    simp
  constructor
  · -- This inequality follows from the mean value theorem
    rw [Metric.tendstoUniformlyOn_iff] at hf'⊢
    intro ε hε
    obtain ⟨q, hqpos, hq⟩ : ∃ q : ℝ, 0 < q ∧ q * r < ε :=
      by
      simp_rw [mul_comm]
      exact exists_pos_mul_lt hε.lt r
    apply (hf' q hqpos.gt).mono
    intro n hn y hy
    simp_rw [dist_eq_norm, Pi.zero_apply, zero_sub, norm_neg] at hn⊢
    have mvt :=
      Convex.norm_image_sub_le_of_norm_has_fderiv_within_le
        (fun z hz => ((hf n.1 z hz).sub (hf n.2 z hz)).HasFderivWithinAt) (fun z hz => (hn z hz).le)
        (convex_ball x r) (Metric.mem_ball_self hr) hy
    refine' lt_of_le_of_lt mvt _
    have : q * ‖y - x‖ < q * r :=
      mul_lt_mul' rfl.le (by simpa only [dist_eq_norm] using metric.mem_ball.mp hy) (norm_nonneg _)
        hqpos
    exact this.trans hq
  · -- This is just `hfg` run through `eventually_prod_iff`
    refine' metric.tendsto_uniformly_on_iff.mpr fun ε hε => _
    obtain ⟨t, ht, ht'⟩ := (metric.cauchy_iff.mp hfg).2 ε hε
    rw [eventually_prod_iff]
    refine' ⟨fun n => f n x ∈ t, ht, fun n => f n x ∈ t, ht, _⟩
    intro n hn n' hn' z hz
    rw [dist_eq_norm, Pi.zero_apply, zero_sub, norm_neg, ← dist_eq_norm]
    exact ht' _ hn _ hn'
#align uniform_cauchy_seq_on_ball_of_fderiv uniformCauchySeqOn_ball_of_fderiv

/-- If a sequence of functions between real or complex normed spaces are differentiable on a
preconnected open set, they form a Cauchy sequence _at_ `x`, and their derivatives are Cauchy
uniformly on the set, then the functions form a Cauchy sequence at any point in the set. -/
theorem cauchy_map_of_uniformCauchySeqOn_fderiv {s : Set E} (hs : IsOpen s) (h's : IsPreconnected s)
    (hf' : UniformCauchySeqOn f' l s) (hf : ∀ n : ι, ∀ y : E, y ∈ s → HasFderivAt (f n) (f' n y) y)
    {x₀ x : E} (hx₀ : x₀ ∈ s) (hx : x ∈ s) (hfg : Cauchy (map (fun n => f n x₀) l)) :
    Cauchy (map (fun n => f n x) l) :=
  by
  have : ne_bot l := (cauchy_map_iff.1 hfg).1
  let t := { y | y ∈ s ∧ Cauchy (map (fun n => f n y) l) }
  suffices H : s ⊆ t
  exact (H hx).2
  have A : ∀ x ε, x ∈ t → Metric.ball x ε ⊆ s → Metric.ball x ε ⊆ t := fun x ε xt hx y hy =>
    ⟨hx hy,
      (uniformCauchySeqOn_ball_of_fderiv (hf'.mono hx) (fun n y hy => hf n y (hx hy))
            xt.2).cauchy_map
        hy⟩
  have open_t : IsOpen t := by
    rw [Metric.isOpen_iff]
    intro x hx
    rcases Metric.isOpen_iff.1 hs x hx.1 with ⟨ε, εpos, hε⟩
    exact ⟨ε, εpos, A x ε hx hε⟩
  have st_nonempty : (s ∩ t).Nonempty := ⟨x₀, hx₀, ⟨hx₀, hfg⟩⟩
  suffices H : closure t ∩ s ⊆ t
  exact h's.subset_of_closure_inter_subset open_t st_nonempty H
  rintro x ⟨xt, xs⟩
  obtain ⟨ε, εpos, hε⟩ : ∃ (ε : ℝ)(H : ε > 0), Metric.ball x ε ⊆ s
  exact Metric.isOpen_iff.1 hs x xs
  obtain ⟨y, yt, hxy⟩ : ∃ (y : E)(yt : y ∈ t), dist x y < ε / 2
  exact Metric.mem_closure_iff.1 xt _ (half_pos εpos)
  have B : Metric.ball y (ε / 2) ⊆ Metric.ball x ε :=
    by
    apply Metric.ball_subset_ball'
    rw [dist_comm]
    linarith
  exact A y (ε / 2) yt (B.trans hε) (Metric.mem_ball.2 hxy)
#align cauchy_map_of_uniform_cauchy_seq_on_fderiv cauchy_map_of_uniformCauchySeqOn_fderiv

/-- If `f_n → g` pointwise and the derivatives `(f_n)' → h` _uniformly_ converge, then
in fact for a fixed `y`, the difference quotients `‖z - y‖⁻¹ • (f_n z - f_n y)` converge
_uniformly_ to `‖z - y‖⁻¹ • (g z - g y)` -/
theorem difference_quotients_converge_uniformly (hf' : TendstoUniformlyOnFilter f' g' l (𝓝 x))
    (hf : ∀ᶠ n : ι × E in l ×ᶠ 𝓝 x, HasFderivAt (f n.1) (f' n.1 n.2) n.2)
    (hfg : ∀ᶠ y : E in 𝓝 x, Tendsto (fun n => f n y) l (𝓝 (g y))) :
    TendstoUniformlyOnFilter (fun n : ι => fun y : E => (‖y - x‖⁻¹ : 𝕜) • (f n y - f n x))
      (fun y : E => (‖y - x‖⁻¹ : 𝕜) • (g y - g x)) l (𝓝 x) :=
  by
  let : NormedSpace ℝ E
  exact NormedSpace.restrictScalars ℝ 𝕜 _
  rcases eq_or_ne l ⊥ with (hl | hl)
  · simp only [hl, TendstoUniformlyOnFilter, bot_prod, eventually_bot, imp_true_iff]
  haveI : ne_bot l := ⟨hl⟩
  refine'
    UniformCauchySeqOnFilter.tendstoUniformlyOnFilter_of_tendsto _
      ((hfg.and (eventually_const.mpr hfg.self_of_nhds)).mono fun y hy =>
        (hy.1.sub hy.2).const_smul _)
  rw [SeminormedAddGroup.uniformCauchySeqOnFilter_iff_tendstoUniformlyOnFilter_zero]
  rw [Metric.tendstoUniformlyOnFilter_iff]
  have hfg' := hf'.uniform_cauchy_seq_on_filter
  rw [SeminormedAddGroup.uniformCauchySeqOnFilter_iff_tendstoUniformlyOnFilter_zero] at hfg'
  rw [Metric.tendstoUniformlyOnFilter_iff] at hfg'
  intro ε hε
  obtain ⟨q, hqpos, hqε⟩ := exists_pos_rat_lt hε
  specialize hfg' (q : ℝ) (by simp [hqpos])
  have := (tendsto_swap4_prod.eventually (hf.prod_mk hf)).diag_of_prod_right
  obtain ⟨a, b, c, d, e⟩ := eventually_prod_iff.1 (hfg'.and this)
  obtain ⟨r, hr, hr'⟩ := metric.nhds_basis_ball.eventually_iff.mp d
  rw [eventually_prod_iff]
  refine'
    ⟨_, b, fun e : E => Metric.ball x r e,
      eventually_mem_set.mpr (metric.nhds_basis_ball.mem_of_mem hr), fun n hn y hy => _⟩
  simp only [Pi.zero_apply, dist_zero_left]
  rw [← smul_sub, norm_smul, norm_inv, IsROrC.norm_coe_norm]
  refine' lt_of_le_of_lt _ hqε
  by_cases hyz' : x = y
  · simp [hyz', hqpos.le]
  have hyz : 0 < ‖y - x‖ := by
    rw [norm_pos_iff]
    intro hy'
    exact hyz' (eq_of_sub_eq_zero hy').symm
  rw [inv_mul_le_iff hyz, mul_comm, sub_sub_sub_comm]
  simp only [Pi.zero_apply, dist_zero_left] at e
  refine'
    Convex.norm_image_sub_le_of_norm_has_fderiv_within_le
      (fun y hy => ((e hn (hr' hy)).2.1.sub (e hn (hr' hy)).2.2).HasFderivWithinAt)
      (fun y hy => (e hn (hr' hy)).1.le) (convex_ball x r) (Metric.mem_ball_self hr) hy
#align difference_quotients_converge_uniformly difference_quotients_converge_uniformly

/-- `(d/dx) lim_{n → ∞} f n x = lim_{n → ∞} f' n x` when the `f' n` converge
_uniformly_ to their limit at `x`.

In words the assumptions mean the following:
  * `hf'`: The `f'` converge "uniformly at" `x` to `g'`. This does not mean that the `f' n` even
    converge away from `x`!
  * `hf`: For all `(y, n)` with `y` sufficiently close to `x` and `n` sufficiently large, `f' n` is
    the derivative of `f n`
  * `hfg`: The `f n` converge pointwise to `g` on a neighborhood of `x` -/
theorem hasFderivAt_of_tendstoUniformlyOnFilter [NeBot l]
    (hf' : TendstoUniformlyOnFilter f' g' l (𝓝 x))
    (hf : ∀ᶠ n : ι × E in l ×ᶠ 𝓝 x, HasFderivAt (f n.1) (f' n.1 n.2) n.2)
    (hfg : ∀ᶠ y in 𝓝 x, Tendsto (fun n => f n y) l (𝓝 (g y))) : HasFderivAt g (g' x) x :=
  by
  -- The proof strategy follows several steps:
  --   1. The quantifiers in the definition of the derivative are
  --      `∀ ε > 0, ∃δ > 0, ∀y ∈ B_δ(x)`. We will introduce a quantifier in the middle:
  --      `∀ ε > 0, ∃N, ∀n ≥ N, ∃δ > 0, ∀y ∈ B_δ(x)` which will allow us to introduce the `f(') n`
  --   2. The order of the quantifiers `hfg` are opposite to what we need. We will be able to swap
  --      the quantifiers using the uniform convergence assumption
  rw [hasFderivAt_iff_tendsto]
  -- Introduce extra quantifier via curried filters
  suffices
    tendsto (fun y : ι × E => ‖y.2 - x‖⁻¹ * ‖g y.2 - g x - (g' x) (y.2 - x)‖) (l.curry (𝓝 x)) (𝓝 0)
    by
    rw [Metric.tendsto_nhds] at this⊢
    intro ε hε
    specialize this ε hε
    rw [eventually_curry_iff] at this
    simp only at this
    exact (eventually_const.mp this).mono (by simp only [imp_self, forall_const])
  -- With the new quantifier in hand, we can perform the famous `ε/3` proof. Specifically,
  -- we will break up the limit (the difference functions minus the derivative go to 0) into 3:
  --   * The difference functions of the `f n` converge *uniformly* to the difference functions
  --     of the `g n`
  --   * The `f' n` are the derivatives of the `f n`
  --   * The `f' n` converge to `g'` at `x`
  conv =>
    congr
    ext
    rw [← norm_norm, ← norm_inv, ← @IsROrC.norm_of_real 𝕜 _ _, IsROrC.of_real_inv, ← norm_smul]
  rw [← tendsto_zero_iff_norm_tendsto_zero]
  have :
    (fun a : ι × E => (‖a.2 - x‖⁻¹ : 𝕜) • (g a.2 - g x - (g' x) (a.2 - x))) =
      ((fun a : ι × E => (‖a.2 - x‖⁻¹ : 𝕜) • (g a.2 - g x - (f a.1 a.2 - f a.1 x))) +
          fun a : ι × E =>
          (‖a.2 - x‖⁻¹ : 𝕜) • (f a.1 a.2 - f a.1 x - ((f' a.1 x) a.2 - (f' a.1 x) x))) +
        fun a : ι × E => (‖a.2 - x‖⁻¹ : 𝕜) • (f' a.1 x - g' x) (a.2 - x) :=
    by
    ext
    simp only [Pi.add_apply]
    rw [← smul_add, ← smul_add]
    congr
    simp only [map_sub, sub_add_sub_cancel, ContinuousLinearMap.coe_sub', Pi.sub_apply]
  simp_rw [this]
  have : 𝓝 (0 : G) = 𝓝 (0 + 0 + 0)
  simp only [add_zero]
  rw [this]
  refine' tendsto.add (tendsto.add _ _) _
  simp only
  · have := difference_quotients_converge_uniformly hf' hf hfg
    rw [Metric.tendstoUniformlyOnFilter_iff] at this
    rw [Metric.tendsto_nhds]
    intro ε hε
    apply ((this ε hε).filter_mono curry_le_prod).mono
    intro n hn
    rw [dist_eq_norm] at hn⊢
    rw [← smul_sub] at hn
    rwa [sub_zero]
  · -- (Almost) the definition of the derivatives
    rw [Metric.tendsto_nhds]
    intro ε hε
    rw [eventually_curry_iff]
    refine' hf.curry.mono fun n hn => _
    have := hn.self_of_nhds
    rw [hasFderivAt_iff_tendsto, Metric.tendsto_nhds] at this
    refine' (this ε hε).mono fun y hy => _
    rw [dist_eq_norm] at hy⊢
    simp only [sub_zero, map_sub, norm_mul, norm_inv, norm_norm] at hy⊢
    rw [norm_smul, norm_inv, IsROrC.norm_coe_norm]
    exact hy
  · -- hfg' after specializing to `x` and applying the definition of the operator norm
    refine' tendsto.mono_left _ curry_le_prod
    have h1 : tendsto (fun n : ι × E => g' n.2 - f' n.1 n.2) (l ×ᶠ 𝓝 x) (𝓝 0) :=
      by
      rw [Metric.tendstoUniformlyOnFilter_iff] at hf'
      exact metric.tendsto_nhds.mpr fun ε hε => by simpa using hf' ε hε
    have h2 : tendsto (fun n : ι => g' x - f' n x) l (𝓝 0) :=
      by
      rw [Metric.tendsto_nhds] at h1⊢
      exact fun ε hε => (h1 ε hε).curry.mono fun n hn => hn.self_of_nhds
    have := tendsto_fst.comp (h2.prod_map tendsto_id)
    refine' squeeze_zero_norm _ (tendsto_zero_iff_norm_tendsto_zero.mp this)
    intro n
    simp_rw [norm_smul, norm_inv, IsROrC.norm_coe_norm]
    by_cases hx : x = n.2
    · simp [hx]
    have hnx : 0 < ‖n.2 - x‖ := by
      rw [norm_pos_iff]
      intro hx'
      exact hx (eq_of_sub_eq_zero hx').symm
    rw [inv_mul_le_iff hnx, mul_comm]
    simp only [Function.comp_apply, Prod_map]
    rw [norm_sub_rev]
    exact (f' n.1 x - g' x).le_op_norm (n.2 - x)
#align has_fderiv_at_of_tendsto_uniformly_on_filter hasFderivAt_of_tendstoUniformlyOnFilter

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem hasFderivAt_of_tendstoLocallyUniformlyOn [NeBot l] {s : Set E} (hs : IsOpen s)
    (hf' : TendstoLocallyUniformlyOn f' g' l s) (hf : ∀ n, ∀ x ∈ s, HasFderivAt (f n) (f' n x) x)
    (hfg : ∀ x ∈ s, Tendsto (fun n => f n x) l (𝓝 (g x))) (hx : x ∈ s) : HasFderivAt g (g' x) x :=
  by
  have h1 : s ∈ 𝓝 x := hs.mem_nhds hx
  have h3 : Set.univ ×ˢ s ∈ l ×ᶠ 𝓝 x := by simp only [h1, prod_mem_prod_iff, univ_mem, and_self_iff]
  have h4 : ∀ᶠ n : ι × E in l ×ᶠ 𝓝 x, HasFderivAt (f n.1) (f' n.1 n.2) n.2 :=
    eventually_of_mem h3 fun ⟨n, z⟩ ⟨hn, hz⟩ => hf n z hz
  refine' hasFderivAt_of_tendstoUniformlyOnFilter _ h4 (eventually_of_mem h1 hfg)
  simpa [IsOpen.nhdsWithin_eq hs hx] using tendsto_locally_uniformly_on_iff_filter.mp hf' x hx
#align has_fderiv_at_of_tendsto_locally_uniformly_on hasFderivAt_of_tendstoLocallyUniformlyOn

/-- A slight variant of `has_fderiv_at_of_tendsto_locally_uniformly_on` with the assumption stated
in terms of `differentiable_on` rather than `has_fderiv_at`. This makes a few proofs nicer in
complex analysis where holomorphicity is assumed but the derivative is not known a priori. -/
theorem hasFderivAt_of_tendsto_locally_uniformly_on' [NeBot l] {s : Set E} (hs : IsOpen s)
    (hf' : TendstoLocallyUniformlyOn (fderiv 𝕜 ∘ f) g' l s) (hf : ∀ n, DifferentiableOn 𝕜 (f n) s)
    (hfg : ∀ x ∈ s, Tendsto (fun n => f n x) l (𝓝 (g x))) (hx : x ∈ s) : HasFderivAt g (g' x) x :=
  by
  refine' hasFderivAt_of_tendstoLocallyUniformlyOn hs hf' (fun n z hz => _) hfg hx
  exact ((hf n z hz).DifferentiableAt (hs.mem_nhds hz)).HasFderivAt
#align has_fderiv_at_of_tendsto_locally_uniformly_on' hasFderivAt_of_tendsto_locally_uniformly_on'

/-- `(d/dx) lim_{n → ∞} f n x = lim_{n → ∞} f' n x` when the `f' n` converge
_uniformly_ to their limit on an open set containing `x`. -/
theorem hasFderivAt_of_tendstoUniformlyOn [NeBot l] {s : Set E} (hs : IsOpen s)
    (hf' : TendstoUniformlyOn f' g' l s)
    (hf : ∀ n : ι, ∀ x : E, x ∈ s → HasFderivAt (f n) (f' n x) x)
    (hfg : ∀ x : E, x ∈ s → Tendsto (fun n => f n x) l (𝓝 (g x))) :
    ∀ x : E, x ∈ s → HasFderivAt g (g' x) x := fun x =>
  hasFderivAt_of_tendstoLocallyUniformlyOn hs hf'.TendstoLocallyUniformlyOn hf hfg
#align has_fderiv_at_of_tendsto_uniformly_on hasFderivAt_of_tendstoUniformlyOn

/-- `(d/dx) lim_{n → ∞} f n x = lim_{n → ∞} f' n x` when the `f' n` converge
_uniformly_ to their limit. -/
theorem hasFderivAt_of_tendstoUniformly [NeBot l] (hf' : TendstoUniformly f' g' l)
    (hf : ∀ n : ι, ∀ x : E, HasFderivAt (f n) (f' n x) x)
    (hfg : ∀ x : E, Tendsto (fun n => f n x) l (𝓝 (g x))) : ∀ x : E, HasFderivAt g (g' x) x :=
  by
  intro x
  have hf : ∀ n : ι, ∀ x : E, x ∈ Set.univ → HasFderivAt (f n) (f' n x) x := by simp [hf]
  have hfg : ∀ x : E, x ∈ Set.univ → tendsto (fun n => f n x) l (𝓝 (g x)) := by simp [hfg]
  have hf' : TendstoUniformlyOn f' g' l Set.univ := by rwa [tendstoUniformlyOn_univ]
  refine' hasFderivAt_of_tendstoUniformlyOn isOpen_univ hf' hf hfg x (Set.mem_univ x)
#align has_fderiv_at_of_tendsto_uniformly hasFderivAt_of_tendstoUniformly

end LimitsOfDerivatives

section deriv

/-! ### `deriv` versions of above theorems

In this section, we provide `deriv` equivalents of the `fderiv` lemmas in the previous section.
The protected function `promote_deriv` provides the translation between derivatives and Fréchet
derivatives
-/

variable {ι : Type _} {l : Filter ι} {𝕜 : Type _} [IsROrC 𝕜] {G : Type _} [NormedAddCommGroup G]
  [NormedSpace 𝕜 G] {f : ι → 𝕜 → G} {g : 𝕜 → G} {f' : ι → 𝕜 → G} {g' : 𝕜 → G} {x : 𝕜}

/-- If our derivatives converge uniformly, then the Fréchet derivatives converge uniformly -/
theorem UniformCauchySeqOnFilter.one_smulRight {l' : Filter 𝕜}
    (hf' : UniformCauchySeqOnFilter f' l l') :
    UniformCauchySeqOnFilter (fun n => fun z => (1 : 𝕜 →L[𝕜] 𝕜).smul_right (f' n z)) l l' :=
  by
  -- The tricky part of this proof is that operator norms are written in terms of `≤` whereas
  -- metrics are written in terms of `<`. So we need to shrink `ε` utilizing the archimedean
  -- property of `ℝ`
  rw [SeminormedAddGroup.uniformCauchySeqOnFilter_iff_tendstoUniformlyOnFilter_zero,
    Metric.tendstoUniformlyOnFilter_iff] at hf'⊢
  intro ε hε
  obtain ⟨q, hq, hq'⟩ := exists_between hε.lt
  apply (hf' q hq).mono
  intro n hn
  refine' lt_of_le_of_lt _ hq'
  simp only [dist_eq_norm, Pi.zero_apply, zero_sub, norm_neg] at hn⊢
  refine' ContinuousLinearMap.op_norm_le_bound _ hq.le _
  intro z
  simp only [ContinuousLinearMap.coe_sub', Pi.sub_apply, ContinuousLinearMap.smulRight_apply,
    ContinuousLinearMap.one_apply]
  rw [← smul_sub, norm_smul, mul_comm]
  exact mul_le_mul hn.le rfl.le (norm_nonneg _) hq.le
#align uniform_cauchy_seq_on_filter.one_smul_right UniformCauchySeqOnFilter.one_smulRight

theorem uniformCauchySeqOnFilter_of_deriv (hf' : UniformCauchySeqOnFilter f' l (𝓝 x))
    (hf : ∀ᶠ n : ι × 𝕜 in l ×ᶠ 𝓝 x, HasDerivAt (f n.1) (f' n.1 n.2) n.2)
    (hfg : Cauchy (map (fun n => f n x) l)) : UniformCauchySeqOnFilter f l (𝓝 x) :=
  by
  simp_rw [hasDerivAt_iff_hasFderivAt] at hf
  exact uniformCauchySeqOnFilter_of_fderiv hf'.one_smul_right hf hfg
#align uniform_cauchy_seq_on_filter_of_deriv uniformCauchySeqOnFilter_of_deriv

theorem uniformCauchySeqOn_ball_of_deriv {r : ℝ} (hf' : UniformCauchySeqOn f' l (Metric.ball x r))
    (hf : ∀ n : ι, ∀ y : 𝕜, y ∈ Metric.ball x r → HasDerivAt (f n) (f' n y) y)
    (hfg : Cauchy (map (fun n => f n x) l)) : UniformCauchySeqOn f l (Metric.ball x r) :=
  by
  simp_rw [hasDerivAt_iff_hasFderivAt] at hf
  rw [uniformCauchySeqOn_iff_uniformCauchySeqOnFilter] at hf'
  have hf' :
    UniformCauchySeqOn (fun n => fun z => (1 : 𝕜 →L[𝕜] 𝕜).smul_right (f' n z)) l
      (Metric.ball x r) :=
    by
    rw [uniformCauchySeqOn_iff_uniformCauchySeqOnFilter]
    exact hf'.one_smul_right
  exact uniformCauchySeqOn_ball_of_fderiv hf' hf hfg
#align uniform_cauchy_seq_on_ball_of_deriv uniformCauchySeqOn_ball_of_deriv

theorem hasDerivAt_of_tendstoUniformlyOnFilter [NeBot l]
    (hf' : TendstoUniformlyOnFilter f' g' l (𝓝 x))
    (hf : ∀ᶠ n : ι × 𝕜 in l ×ᶠ 𝓝 x, HasDerivAt (f n.1) (f' n.1 n.2) n.2)
    (hfg : ∀ᶠ y in 𝓝 x, Tendsto (fun n => f n y) l (𝓝 (g y))) : HasDerivAt g (g' x) x :=
  by
  -- The first part of the proof rewrites `hf` and the goal to be functions so that Lean
  -- can recognize them when we apply `has_fderiv_at_of_tendsto_uniformly_on_filter`
  let F' n z := (1 : 𝕜 →L[𝕜] 𝕜).smul_right (f' n z)
  let G' z := (1 : 𝕜 →L[𝕜] 𝕜).smul_right (g' z)
  simp_rw [hasDerivAt_iff_hasFderivAt] at hf⊢
  -- Now we need to rewrite hf' in terms of continuous_linear_maps. The tricky part is that
  -- operator norms are written in terms of `≤` whereas metrics are written in terms of `<`. So we
  -- need to shrink `ε` utilizing the archimedean property of `ℝ`
  have hf' : TendstoUniformlyOnFilter F' G' l (𝓝 x) :=
    by
    rw [Metric.tendstoUniformlyOnFilter_iff] at hf'⊢
    intro ε hε
    obtain ⟨q, hq, hq'⟩ := exists_between hε.lt
    apply (hf' q hq).mono
    intro n hn
    refine' lt_of_le_of_lt _ hq'
    simp only [F', G', dist_eq_norm] at hn⊢
    refine' ContinuousLinearMap.op_norm_le_bound _ hq.le _
    intro z
    simp only [ContinuousLinearMap.coe_sub', Pi.sub_apply, ContinuousLinearMap.smulRight_apply,
      ContinuousLinearMap.one_apply]
    rw [← smul_sub, norm_smul, mul_comm]
    exact mul_le_mul hn.le rfl.le (norm_nonneg _) hq.le
  exact hasFderivAt_of_tendstoUniformlyOnFilter hf' hf hfg
#align has_deriv_at_of_tendsto_uniformly_on_filter hasDerivAt_of_tendstoUniformlyOnFilter

theorem hasDerivAt_of_tendstoLocallyUniformlyOn [NeBot l] {s : Set 𝕜} (hs : IsOpen s)
    (hf' : TendstoLocallyUniformlyOn f' g' l s)
    (hf : ∀ᶠ n in l, ∀ x ∈ s, HasDerivAt (f n) (f' n x) x)
    (hfg : ∀ x ∈ s, Tendsto (fun n => f n x) l (𝓝 (g x))) (hx : x ∈ s) : HasDerivAt g (g' x) x :=
  by
  have h1 : s ∈ 𝓝 x := hs.mem_nhds hx
  have h2 : ∀ᶠ n : ι × 𝕜 in l ×ᶠ 𝓝 x, HasDerivAt (f n.1) (f' n.1 n.2) n.2 :=
    eventually_prod_iff.2 ⟨_, hf, fun x => x ∈ s, h1, fun n => id⟩
  refine' hasDerivAt_of_tendstoUniformlyOnFilter _ h2 (eventually_of_mem h1 hfg)
  simpa [IsOpen.nhdsWithin_eq hs hx] using tendsto_locally_uniformly_on_iff_filter.mp hf' x hx
#align has_deriv_at_of_tendsto_locally_uniformly_on hasDerivAt_of_tendstoLocallyUniformlyOn

/-- A slight variant of `has_deriv_at_of_tendsto_locally_uniformly_on` with the assumption stated in
terms of `differentiable_on` rather than `has_deriv_at`. This makes a few proofs nicer in complex
analysis where holomorphicity is assumed but the derivative is not known a priori. -/
theorem hasDerivAt_of_tendsto_locally_uniformly_on' [NeBot l] {s : Set 𝕜} (hs : IsOpen s)
    (hf' : TendstoLocallyUniformlyOn (deriv ∘ f) g' l s)
    (hf : ∀ᶠ n in l, DifferentiableOn 𝕜 (f n) s)
    (hfg : ∀ x ∈ s, Tendsto (fun n => f n x) l (𝓝 (g x))) (hx : x ∈ s) : HasDerivAt g (g' x) x :=
  by
  refine' hasDerivAt_of_tendstoLocallyUniformlyOn hs hf' _ hfg hx
  filter_upwards [hf]with n h z hz using((h z hz).DifferentiableAt (hs.mem_nhds hz)).HasDerivAt
#align has_deriv_at_of_tendsto_locally_uniformly_on' hasDerivAt_of_tendsto_locally_uniformly_on'

theorem hasDerivAt_of_tendstoUniformlyOn [NeBot l] {s : Set 𝕜} (hs : IsOpen s)
    (hf' : TendstoUniformlyOn f' g' l s)
    (hf : ∀ᶠ n in l, ∀ x : 𝕜, x ∈ s → HasDerivAt (f n) (f' n x) x)
    (hfg : ∀ x : 𝕜, x ∈ s → Tendsto (fun n => f n x) l (𝓝 (g x))) :
    ∀ x : 𝕜, x ∈ s → HasDerivAt g (g' x) x := fun x =>
  hasDerivAt_of_tendstoLocallyUniformlyOn hs hf'.TendstoLocallyUniformlyOn hf hfg
#align has_deriv_at_of_tendsto_uniformly_on hasDerivAt_of_tendstoUniformlyOn

theorem hasDerivAt_of_tendstoUniformly [NeBot l] (hf' : TendstoUniformly f' g' l)
    (hf : ∀ᶠ n in l, ∀ x : 𝕜, HasDerivAt (f n) (f' n x) x)
    (hfg : ∀ x : 𝕜, Tendsto (fun n => f n x) l (𝓝 (g x))) : ∀ x : 𝕜, HasDerivAt g (g' x) x :=
  by
  intro x
  have hf : ∀ᶠ n in l, ∀ x : 𝕜, x ∈ Set.univ → HasDerivAt (f n) (f' n x) x := by
    filter_upwards [hf]with n h x hx using h x
  have hfg : ∀ x : 𝕜, x ∈ Set.univ → tendsto (fun n => f n x) l (𝓝 (g x)) := by simp [hfg]
  have hf' : TendstoUniformlyOn f' g' l Set.univ := by rwa [tendstoUniformlyOn_univ]
  exact hasDerivAt_of_tendstoUniformlyOn isOpen_univ hf' hf hfg x (Set.mem_univ x)
#align has_deriv_at_of_tendsto_uniformly hasDerivAt_of_tendstoUniformly

end deriv


-- Imagen_de_la_interseccion_general.lean
-- Imagen de la intersección general
-- José A. Alonso Jiménez
-- Sevilla, 24 de junio de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que
--    f '' (⋂ i, A i) ⊆ ⋂ i, f '' A i
-- ----------------------------------------------------------------------

import data.set.basic
import tactic

open set

variables {α : Type*} {β : Type*} {I : Type*}
variable  f : α → β
variables A : ℕ → set α

-- 1ª demostración
-- ===============

example : f '' (⋂ i, A i) ⊆ ⋂ i, f '' A i :=
begin
  intros y h,
  apply mem_Inter_of_mem,
  intro i,
  cases h with x hx,
  cases hx with xIA fxy,
  rw ← fxy,
  apply mem_image_of_mem,
  exact mem_Inter.mp xIA i,
end

-- 2ª demostración
-- ===============

example : f '' (⋂ i, A i) ⊆ ⋂ i, f '' A i :=
begin
  intros y h,
  apply mem_Inter_of_mem,
  intro i,
  rcases h with ⟨x, xIA, rfl⟩,
  exact mem_image_of_mem f (mem_Inter.mp xIA i),
end

-- 3ª demostración
-- ===============

example : f '' (⋂ i, A i) ⊆ ⋂ i, f '' A i :=
begin
  intro y,
  simp,
  intros x xIA fxy i,
  use [x, xIA i, fxy],
end

-- 4ª demostración
-- ===============

example : f '' (⋂ i, A i) ⊆ ⋂ i, f '' A i :=
by tidy


/-
Copyright (c) 2018 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel

! This file was ported from Lean 3 source module order.conditionally_complete_lattice.finset
! leanprover-community/mathlib commit 2445c98ae4b87eabebdde552593519b9b6dc350c
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Order.ConditionallyCompleteLattice.Basic
import Mathlib.Data.Set.Finite

/-!
# Conditionally complete lattices and finite sets.

-/

open Set

variable {α β γ : Type _}

section ConditionallyCompleteLattice

variable [ConditionallyCompleteLattice α] {s t : Set α} {a b : α}

theorem Finset.Nonempty.sup'_eq_cSup_image {s : Finset β} (hs : s.Nonempty) (f : β → α) :
    s.sup' hs f = supₛ (f '' s) :=
  eq_of_forall_ge_iff fun a => by
    simp [csupₛ_le_iff (s.finite_toSet.image f).bddAbove (hs.to_set.image f)]
#align finset.nonempty.sup'_eq_cSup_image Finset.Nonempty.sup'_eq_cSup_image

theorem Finset.Nonempty.sup'_id_eq_cSup {s : Finset α} (hs : s.Nonempty) : s.sup' hs id = supₛ s :=
  by rw [hs.sup'_eq_cSup_image, Set.image_id]
#align finset.nonempty.sup'_id_eq_cSup Finset.Nonempty.sup'_id_eq_cSup

end ConditionallyCompleteLattice

section ConditionallyCompleteLinearOrder

variable [ConditionallyCompleteLinearOrder α] {s t : Set α} {a b : α}

theorem Finset.Nonempty.cSup_eq_max' {s : Finset α} (h : s.Nonempty) : supₛ ↑s = s.max' h :=
  eq_of_forall_ge_iff fun _ => (csupₛ_le_iff s.bddAbove h.to_set).trans (s.max'_le_iff h).symm
#align finset.nonempty.cSup_eq_max' Finset.Nonempty.cSup_eq_max'

theorem Finset.Nonempty.cInf_eq_min' {s : Finset α} (h : s.Nonempty) : infₛ ↑s = s.min' h :=
  @Finset.Nonempty.cSup_eq_max' αᵒᵈ _ s h
#align finset.nonempty.cInf_eq_min' Finset.Nonempty.cInf_eq_min'

theorem Finset.Nonempty.cSup_mem {s : Finset α} (h : s.Nonempty) : supₛ (s : Set α) ∈ s := by
  rw [h.cSup_eq_max']
  exact s.max'_mem _
#align finset.nonempty.cSup_mem Finset.Nonempty.cSup_mem

theorem Finset.Nonempty.cInf_mem {s : Finset α} (h : s.Nonempty) : infₛ (s : Set α) ∈ s :=
  @Finset.Nonempty.cSup_mem αᵒᵈ _ _ h
#align finset.nonempty.cInf_mem Finset.Nonempty.cInf_mem

theorem Set.Nonempty.cSup_mem (h : s.Nonempty) (hs : s.Finite) : supₛ s ∈ s := by
  lift s to Finset α using hs
  exact Finset.Nonempty.cSup_mem h
#align set.nonempty.cSup_mem Set.Nonempty.cSup_mem

theorem Set.Nonempty.cInf_mem (h : s.Nonempty) (hs : s.Finite) : infₛ s ∈ s :=
  @Set.Nonempty.cSup_mem αᵒᵈ _ _ h hs
#align set.nonempty.cInf_mem Set.Nonempty.cInf_mem

theorem Set.Finite.cSup_lt_iff (hs : s.Finite) (h : s.Nonempty) : supₛ s < a ↔ ∀ x ∈ s, x < a :=
  ⟨fun h _ hx => (le_csupₛ hs.bddAbove hx).trans_lt h, fun H => H _ <| h.cSup_mem hs⟩
#align set.finite.cSup_lt_iff Set.Finite.cSup_lt_iff

theorem Set.Finite.lt_cInf_iff (hs : s.Finite) (h : s.Nonempty) : a < infₛ s ↔ ∀ x ∈ s, a < x :=
  @Set.Finite.cSup_lt_iff αᵒᵈ _ _ _ hs h
#align set.finite.lt_cInf_iff Set.Finite.lt_cInf_iff

end ConditionallyCompleteLinearOrder

/-!
### Relation between `Sup` / `Inf` and `Finset.sup'` / `Finset.inf'`

Like the `Sup` of a `ConditionallyCompleteLattice`, `Finset.sup'` also requires the set to be
non-empty. As a result, we can translate between the two.
-/

namespace Finset

theorem sup'_eq_csupₛ_image [ConditionallyCompleteLattice β] (s : Finset α) (H) (f : α → β) :
    s.sup' H f = supₛ (f '' s) := by
  apply le_antisymm
  · refine' Finset.sup'_le _ _ fun a ha => _
    refine' le_csupₛ ⟨s.sup' H f, _⟩ ⟨a, ha, rfl⟩
    rintro i ⟨j, hj, rfl⟩
    exact Finset.le_sup' _ hj
  · apply csupₛ_le ((coe_nonempty.mpr H).image _)
    rintro _ ⟨a, ha, rfl⟩
    exact Finset.le_sup' _ ha
#align finset.sup'_eq_cSup_image Finset.sup'_eq_csupₛ_image

theorem inf'_eq_cinfₛ_image [ConditionallyCompleteLattice β] (s : Finset α) (H) (f : α → β) :
    s.inf' H f = infₛ (f '' s) :=
  @sup'_eq_csupₛ_image _ βᵒᵈ _ _ H _
#align finset.inf'_eq_cInf_image Finset.inf'_eq_cinfₛ_image

theorem sup'_id_eq_csupₛ [ConditionallyCompleteLattice α] (s : Finset α) (H) :
    s.sup' H id = supₛ s := by rw [sup'_eq_csupₛ_image s H, Set.image_id]
#align finset.sup'_id_eq_cSup Finset.sup'_id_eq_csupₛ

theorem inf'_id_eq_cinfₛ [ConditionallyCompleteLattice α] (s : Finset α) (H) :
    s.inf' H id = infₛ s :=
  @sup'_id_eq_csupₛ αᵒᵈ _ _ H
#align finset.inf'_id_eq_cInf Finset.inf'_id_eq_cinfₛ

end Finset


theorem add_one_eq_succ (d : mynat) : d + 1 = succ d :=
begin
symmetry,
rw succ_eq_add_one,
refl,
end


import interior_world.definition -- hide

namespace topological_space -- hide

variables (X : Type) [topological_space X]
--variables [topological_space X]

def is_closed {X : Type} [topological_space X] := λ (C : set X), @is_open X _ (compl C)

class T0_space (X : Type) [topological_space X]: Prop :=
(t0 : ∀ (x y : X) (h : y ≠ x) , ∃ (U : set X) (hU : is_open U), ((x ∈ U) ∧ (y ∉ U)) ∨ ((x ∉ U) ∧ (y ∈ U)))

class T1_space (X : Type) [topological_space X]: Prop :=
(t1 : ∀ (x y : X) (h : y ≠ x), ∃ (U : set X) (hU : is_open U), (x ∈ U) ∧ (y ∉ U))

class T1_space' (X : Type) [topological_space X]: Prop :=
(t1 : ∀ (x y : X) (h : y ≠ x), ∃ (U V: set X) (hU : is_open U) (hV : is_open V), (x ∈ U) ∧ (y ∉ U) ∧ (x ∉ V) ∧ (y ∈ V))

class T2_space (X : Type) [topological_space X]: Prop :=
(t2 : ∀ (x y : X) (h : y ≠ x), ∃ (U V: set X) (hU : is_open U) (hV : is_open V) (hUV : U ∩ V = ∅), (x ∈ U) ∧ (y ∈ V))

class T3_space extends T0_space X: Prop :=
(regular : ∀ (x : X) (F : set X) (hF : is_closed F) (hxF: x ∉ F),
  ∃ (U V : set X) (hU : is_open U) (hV : is_open V) (hUV : U ∩ V = ∅), (x ∈ U) ∧ (F ⊆ V))

theorem subset.antisymm {a b : set X} (h₁ : a ⊆ b) (h₂ : b ⊆ a) : a = b :=
set.ext $ λ x, ⟨@h₁ _, @h₂ _⟩

end topological_space -- hide


namespace hidden

/-
bᵢ is Elementary braid, it is a crossing of two ropes i_th rope and (i+1)_th rope:
  bᵢ is counterclockwise crossing (if look from up (begining) to down (ending of braid))
  bᵢ⁻¹ is clockwise crossing

Properties of braid group:
G1. Associativity: ∀ a b c, a ∘ (b ∘ c) = (a ∘ b) ∘ c
G2. Identity: ∃! e, ∀ a, a ∘ e = e ∘ a = e
G3: Inverse: ∀ a, ∃! x, a ∘ x = x ∘ a = e
1. Trivial: bᵢ⁻¹ bᵢ = bᵢ bᵢ⁻¹ = e
2. Distant commutativity: if |i-j| > 2 then bᵢ bⱼ = bⱼ bᵢ
3. Relation of Artin: bᵢ bᵢ₊₁ bᵢ = bᵢ₊₁ bᵢ bᵢ₊₁

Example:
b₃⁻¹ (b₂ b₃ b₂) b₃⁻¹ =
[by Artin relation]
(b₃⁻¹ b₃) b₂ (b₃ b₃⁻¹) =
[by Trivial]
e b₂ e =
[by id: λ x, e x = x]
b₂
-/

constant β : Type
constant b : ℤ → (β → β)
constant e : β → β
constant zero : β

namespace braid

-- we have assoc from assoc of functions β → β
axiom id (x : β) : e x = x
axiom b_zero : b 0 = e
axiom trivial (i : ℤ) (x : β) : b (-i) (b i x) = e x
axiom dist_comm (i j : ℤ) (x : β) :
  abs (i - j) >= 2 → b i (b j x) = b j (b i x)

attribute [simp] id trivial
variable x : β

example : (b (-2)) (b 2 x) = e x :=
begin

end

end braid

end hidden


import tactic
import data.real.basic
import data.pnat.basic

local notation `|` x `|` := abs x

def is_limit (a : ℕ+ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, | a n - l | < ε

def is_convergent (a : ℕ+ → ℝ) : Prop :=
∃ l : ℝ, is_limit a l

/-
1. Consider the following properties of a sequence of real numbers (a n ) n≥0 :
(i) a_n → a, or
(ii) “a_n eventually equals a” – i.e. ∃N ∈ N such that ∀n ≥ N, a n = a, or
(iii) “(a_n ) is bounded” – i.e. ∃ R ∈ R such that |a n | < R ∀n ∈ N.
For each statement (a-e) below, which of (i-iii) is it equivalent to? Proof?
(a) ∃ N ∈ N such that ∀n ≥ N, ∀ε > 0, |a n − a| < ε.
(b) ∀ε > 0 there are only finitely many n ∈ N for which |a n − a| ≥ ε.
(c) ∀N ∈ N, ∃ ε > 0 such that n ≥ N ⇒ |a n − a| < ε.
(d) ∃ ε > 0 such that ∀N ∈ N, |a n − a| < ε ∀n ≥ N .
(e) ∀R > 0 ∃ N ∈ N such that n ≥ N ⇒ a n ∈ (a − 1/R , a + 1/R ).
-/

namespace sheet4

section Q1

def Q1i (an : ℕ+ → ℝ) (a : ℝ) := is_limit an a
def Q1ii (an : ℕ+ → ℝ) (a : ℝ) := ∃ N : ℕ+, ∀ n, N ≤ n → an n = a
def Q1iii (an : ℕ+ → ℝ) := ∃ R : ℝ, ∀ n : ℕ+, |an n| < R

-- instructions: change thing on RHS of ↔ to `Q1ii an a` or `Q1iii an`
-- as appropriate, then prove
theorem Q1a (an : ℕ+ → ℝ) (a : ℝ) :
  (∃ N : ℕ+, ∀ n, N ≤ n → ∀ ε, 0 < ε → |an n - a| < ε) ↔ Q1i an a :=
begin
  sorry
end

theorem Q1b (an : ℕ+ → ℝ) (a : ℝ) :
  (∀ ε, 0 < ε → {n : ℕ+ | ε ≤ |an n - a|}.finite) ↔ Q1i an a :=
begin
  sorry
end

theorem Q1c (an : ℕ+ → ℝ) (a : ℝ) :
  (∀ N : ℕ+, ∃ ε > 0, ∀ n ≥ N, |an n - a| < ε) ↔ Q1i an a :=
begin
  sorry
end

theorem Q1d (an : ℕ+ → ℝ) (a : ℝ) :
  (∃ ε > 0, ∀ N : ℕ+, ∀ n ≥ N, |an n - a| < ε) ↔ Q1i an a :=
begin
  sorry
end

open set

theorem Q1e (an : ℕ+ → ℝ) (a : ℝ) :
  (∀R > 0, ∃ N : ℕ+, ∀ n ≥ N, an n ∈ Ioo (a - 1/R) (a + 1/R)) ↔ Q1i an a :=
begin
  sorry
end

end Q1

section Q3
/-
3. Suppose that a n ≤ b n ≤ c n ∀n and that a n → a and c n → a. Prove that b n → a.
-/

theorem Q3sandwich (a b c : ℕ+ → ℝ) (l : ℝ) (hab : ∀ n, a n ≤ b n)
  (hbc : ∀ n, b n ≤ c n) (ha : is_limit a l) (hc : is_limit c l) :
  is_limit b l :=
begin
  sorry
end

end Q3

/-
4. Suppose that aₙ → 0 and (bₙ) is bounded. Prove that aₙ bₙ → 0.
-/

/-- A sequence (aₙ) is bounded if there's some R such that |aₙ|≤ R for all n. -/
def is_bounded (a : ℕ+ → ℝ) := ∃ R : ℝ, ∀ n, |a n| ≤ R

section Q4

theorem Q4 (a b : ℕ+ → ℝ) (l : ℝ) (ha : is_limit a 0) (hb : is_bounded b) :
  is_limit (λ n, a n * b n) 0 :=
begin
  sorry
end

end Q4

section Q5
-- let's do it properly

theorem is_bounded_of_is_convergent {a : ℕ+ → ℝ} (ha : is_convergent a) :
  is_bounded a :=
begin
  sorry
end

theorem limit_add_const {a : ℕ+ → ℝ} (l t : ℝ) (hal : is_limit a l) :
  is_limit (λ n, t + a n) (t + l) :=
begin
  sorry
end

theorem limit_mul_const {a : ℕ+ → ℝ} {l : ℝ} (hal : is_limit a l) (t : ℝ) :
  is_limit (λ n, t * a n) (t * l) :=
begin
  sorry
end

theorem limit_add {a b : ℕ+ → ℝ} {l m : ℝ} (hal : is_limit a l) (hbm : is_limit b m) :
  is_limit (λ n, a n + b n) (l + m) :=
begin
  sorry
end

theorem limit_zero_mul_limit_zero {a b : ℕ+ → ℝ} (hal : is_limit a 0)
  (hbm : is_limit b 0) : is_limit (λ n, a n * b n) 0 :=
begin
  sorry
end

theorem mul_limit {a b : ℕ+ → ℝ} {l m : ℝ} (hal : is_limit a l)
  (hb : is_limit b m) : is_limit (λ n, a n * b n) (l * m) :=
begin
  sorry
end

theorem inv_limit {a : ℕ+ → ℝ} {l : ℝ} (hl : l ≠ 0) (hal : is_limit a l) :
  is_limit (λ n, (a n)⁻¹) l⁻¹ :=
begin
  sorry
end

theorem div_limit {a b : ℕ+ → ℝ} {l m : ℝ} (hl : m ≠ 0) (hal : is_limit a l)
  (hb : is_limit b m) : is_limit (λ n, a n / b n) (l / m) :=
begin
  sorry
end

end Q5

section Q6

/-
6. Given functions f n : (0, 1) → R and f : (0, 1) → R, suppose we make the following
Definition: f n converges to f (or f n → f ) if and only if ∀x ∈ (0, 1), f n (x) → f (x).

Consider the examples fₙ(x) = n for x ≤ 1/n and 0 for x > 1/n. Do they converge
to some function f : (0, 1) → ℝ ? Prove your answer. Compare with the sequence
of real numbers aₙ := ∫₀¹ fₙ .
-/

open set

def fn_is_limit (fn : ℕ+ → ℝ → ℝ) (f : ℝ → ℝ) :=
  ∀ x : ℝ, x ∈ Ioo (0 : ℝ) 1 → is_limit (λ n, fn n x) (f x)

-- fill in
def flim : ℝ → ℝ := sorry

noncomputable def fn (n : ℕ+) (x : ℝ) : ℝ := if x ≤ 1 / n then n else 0

theorem Q6yes : fn_is_limit fn flim :=
begin
  sorry
end

-- can we do the integral?
end Q6

end sheet4


-- Include all your imports for the exercises above this line

-- Use #check below to see if you've gotten the import and namespace figured out correctly.

/-
 # Exercise 1: Importing something you've already done
 We (mostly) proved a handy theorem last time about all natural numbers being either even or odd. Import that file (demo1.lean) and #check if you can cite that lemma.
-/

/-
  # Exercise 2: Searching mathlib by name
  Here are a few lemmas by name. Import the appropriate file above, and #check to see if you got it right.

  * lemma frequently_measure_inter_ne_zero (an important result in basic ergodic theory (a part of dynamics))
  * theorem halting_problem (The theorem that says the halting problem is not computable)
  * def ideal (the definition of an ideal in a semiring)
  * theorem is_galois (a theorem that says a field is Galois iff it's separable and normal)
  * structure partition_of_unity (defines the notion of a ``partition of unity`` on a topological space)
-/

/-
  # Exercise 3: Searching through mathlib
  Here are a few descriptions of lemmas. Try to find them, import the file, and #check your answer.

  * A lemma that says the set of neighbors of a vertex v in a subgraph G' is a subset of the vertices of the original graph G
  * A lemma that says that -1 is a square mod p if and only if p is not congruent to 3 mod 4 (this is a useful lemma to prove quadratic reciprocity)
  * A theorem that says that a linear map f has eigenvalue μ if and only if μ is a root of the minimal polynomial of f
  * A lemma that says that the tail of a list l ++ [a] is the same as the tail of l ++ [a] (i.e. tail (l ++ [a]) = tail(l) ++ [a] )
  * The Whitney embedding theorem.
  * The definition of the ``yoneda functor`` in category theory
  * A theorem that says a group G is ``nilpotent`` if and only if it has a finite ascending series
-/

/-
  # Exercise 4: Look up all your favorite mathematical objects
  Spend some time getting familiar with the layout of mathlib. Look for your favorite mathematical objects, and see if they're defined already! (If they aren't, maybe you can do it!)
-/


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel, Johannes Hölzl, Yury G. Kudryashov, Patrick Massot
-/
import algebra.geom_sum
import order.filter.archimedean
import order.iterate
import topology.instances.ennreal

/-!
# A collection of specific limit computations

This file, by design, is independent of `normed_space` in the import hierarchy.  It contains
important specific limit computations in metric spaces, in ordered rings/fields, and in specific
instances of these such as `ℝ`, `ℝ≥0` and `ℝ≥0∞`.
-/

noncomputable theory
open classical set function filter finset metric

open_locale classical topological_space nat big_operators uniformity nnreal ennreal

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

lemma tendsto_inverse_at_top_nhds_0_nat : tendsto (λ n : ℕ, (n : ℝ)⁻¹) at_top (𝓝 0) :=
tendsto_inv_at_top_zero.comp tendsto_coe_nat_at_top_at_top

lemma tendsto_const_div_at_top_nhds_0_nat (C : ℝ) : tendsto (λ n : ℕ, C / n) at_top (𝓝 0) :=
by simpa only [mul_zero] using tendsto_const_nhds.mul tendsto_inverse_at_top_nhds_0_nat

lemma nnreal.tendsto_inverse_at_top_nhds_0_nat : tendsto (λ n : ℕ, (n : ℝ≥0)⁻¹) at_top (𝓝 0) :=
by { rw ← nnreal.tendsto_coe, convert tendsto_inverse_at_top_nhds_0_nat, simp }

lemma nnreal.tendsto_const_div_at_top_nhds_0_nat (C : ℝ≥0) :
  tendsto (λ n : ℕ, C / n) at_top (𝓝 0) :=
by simpa using tendsto_const_nhds.mul nnreal.tendsto_inverse_at_top_nhds_0_nat

lemma tendsto_one_div_add_at_top_nhds_0_nat :
  tendsto (λ n : ℕ, 1 / ((n : ℝ) + 1)) at_top (𝓝 0) :=
suffices tendsto (λ n : ℕ, 1 / (↑(n + 1) : ℝ)) at_top (𝓝 0), by simpa,
(tendsto_add_at_top_iff_nat 1).2 (tendsto_const_div_at_top_nhds_0_nat 1)

/-! ### Powers -/

lemma tendsto_add_one_pow_at_top_at_top_of_pos [linear_ordered_semiring α] [archimedean α] {r : α}
  (h : 0 < r) :
  tendsto (λ n:ℕ, (r + 1)^n) at_top at_top :=
tendsto_at_top_at_top_of_monotone' (λ n m, pow_le_pow (le_add_of_nonneg_left (le_of_lt h))) $
  not_bdd_above_iff.2 $ λ x, set.exists_range_iff.2 $ add_one_pow_unbounded_of_pos _ h

lemma tendsto_pow_at_top_at_top_of_one_lt [linear_ordered_ring α] [archimedean α]
  {r : α} (h : 1 < r) :
  tendsto (λn:ℕ, r ^ n) at_top at_top :=
sub_add_cancel r 1 ▸ tendsto_add_one_pow_at_top_at_top_of_pos (sub_pos.2 h)

lemma nat.tendsto_pow_at_top_at_top_of_one_lt {m : ℕ} (h : 1 < m) :
  tendsto (λn:ℕ, m ^ n) at_top at_top :=
tsub_add_cancel_of_le (le_of_lt h) ▸
  tendsto_add_one_pow_at_top_at_top_of_pos (tsub_pos_of_lt h)

lemma tendsto_pow_at_top_nhds_0_of_lt_1 {𝕜 : Type*} [linear_ordered_field 𝕜] [archimedean 𝕜]
  [topological_space 𝕜] [order_topology 𝕜] {r : 𝕜} (h₁ : 0 ≤ r) (h₂ : r < 1) :
  tendsto (λn:ℕ, r^n) at_top (𝓝 0) :=
h₁.eq_or_lt.elim
  (assume : 0 = r,
    (tendsto_add_at_top_iff_nat 1).mp $ by simp [pow_succ, ← this, tendsto_const_nhds])
  (assume : 0 < r,
    have tendsto (λn, (r⁻¹ ^ n)⁻¹) at_top (𝓝 0),
      from tendsto_inv_at_top_zero.comp
        (tendsto_pow_at_top_at_top_of_one_lt $ one_lt_inv this h₂),
    this.congr (λ n, by simp))

lemma tendsto_pow_at_top_nhds_within_0_of_lt_1 {𝕜 : Type*} [linear_ordered_field 𝕜] [archimedean 𝕜]
  [topological_space 𝕜] [order_topology 𝕜] {r : 𝕜} (h₁ : 0 < r) (h₂ : r < 1) :
  tendsto (λn:ℕ, r^n) at_top (𝓝[>] 0) :=
tendsto_inf.2 ⟨tendsto_pow_at_top_nhds_0_of_lt_1 h₁.le h₂,
  tendsto_principal.2 $ eventually_of_forall $ λ n, pow_pos h₁ _⟩

lemma uniformity_basis_dist_pow_of_lt_1 {α : Type*} [pseudo_metric_space α]
  {r : ℝ} (h₀ : 0 < r) (h₁ : r < 1) :
  (𝓤 α).has_basis (λ k : ℕ, true) (λ k, {p : α × α | dist p.1 p.2 < r ^ k}) :=
metric.mk_uniformity_basis (λ i _, pow_pos h₀ _) $ λ ε ε0,
  (exists_pow_lt_of_lt_one ε0 h₁).imp $ λ k hk, ⟨trivial, hk.le⟩

lemma geom_lt {u : ℕ → ℝ} {c : ℝ} (hc : 0 ≤ c) {n : ℕ} (hn : 0 < n)
  (h : ∀ k < n, c * u k < u (k + 1)) :
  c ^ n * u 0 < u n :=
begin
  refine (monotone_mul_left_of_nonneg hc).seq_pos_lt_seq_of_le_of_lt hn _ _ h,
  { simp },
  { simp [pow_succ, mul_assoc, le_refl] }
end

lemma geom_le {u : ℕ → ℝ} {c : ℝ} (hc : 0 ≤ c) (n : ℕ) (h : ∀ k < n, c * u k ≤ u (k + 1)) :
  c ^ n * u 0 ≤ u n :=
by refine (monotone_mul_left_of_nonneg hc).seq_le_seq n _ _ h; simp [pow_succ, mul_assoc, le_refl]

lemma lt_geom {u : ℕ → ℝ} {c : ℝ} (hc : 0 ≤ c) {n : ℕ} (hn : 0 < n)
  (h : ∀ k < n, u (k + 1) < c * u k) :
  u n < c ^ n * u 0 :=
begin
  refine (monotone_mul_left_of_nonneg hc).seq_pos_lt_seq_of_lt_of_le hn _ h _,
  { simp },
  { simp [pow_succ, mul_assoc, le_refl] }
end

lemma le_geom {u : ℕ → ℝ} {c : ℝ} (hc : 0 ≤ c) (n : ℕ) (h : ∀ k < n, u (k + 1) ≤ c * u k) :
  u n ≤ (c ^ n) * u 0 :=
by refine (monotone_mul_left_of_nonneg hc).seq_le_seq n _ h _; simp [pow_succ, mul_assoc, le_refl]

/-- If a sequence `v` of real numbers satisfies `k * v n ≤ v (n+1)` with `1 < k`,
then it goes to +∞. -/
lemma tendsto_at_top_of_geom_le {v : ℕ → ℝ} {c : ℝ} (h₀ : 0 < v 0) (hc : 1 < c)
  (hu : ∀ n, c * v n ≤ v (n + 1)) : tendsto v at_top at_top :=
tendsto_at_top_mono (λ n, geom_le (zero_le_one.trans hc.le) n (λ k hk, hu k)) $
  (tendsto_pow_at_top_at_top_of_one_lt hc).at_top_mul_const h₀

lemma nnreal.tendsto_pow_at_top_nhds_0_of_lt_1 {r : ℝ≥0} (hr : r < 1) :
  tendsto (λ n:ℕ, r^n) at_top (𝓝 0) :=
nnreal.tendsto_coe.1 $ by simp only [nnreal.coe_pow, nnreal.coe_zero,
  tendsto_pow_at_top_nhds_0_of_lt_1 r.coe_nonneg hr]

lemma ennreal.tendsto_pow_at_top_nhds_0_of_lt_1 {r : ℝ≥0∞} (hr : r < 1) :
  tendsto (λ n:ℕ, r^n) at_top (𝓝 0) :=
begin
  rcases ennreal.lt_iff_exists_coe.1 hr with ⟨r, rfl, hr'⟩,
  rw [← ennreal.coe_zero],
  norm_cast at *,
  apply nnreal.tendsto_pow_at_top_nhds_0_of_lt_1 hr
end

/-! ### Geometric series-/
section geometric

lemma has_sum_geometric_of_lt_1 {r : ℝ} (h₁ : 0 ≤ r) (h₂ : r < 1) :
  has_sum (λn:ℕ, r ^ n) (1 - r)⁻¹ :=
have r ≠ 1, from ne_of_lt h₂,
have tendsto (λn, (r ^ n - 1) * (r - 1)⁻¹) at_top (𝓝 ((0 - 1) * (r - 1)⁻¹)),
  from ((tendsto_pow_at_top_nhds_0_of_lt_1 h₁ h₂).sub tendsto_const_nhds).mul tendsto_const_nhds,
have (λ n, (∑ i in range n, r ^ i)) = (λ n, geom_sum r n) := rfl,
(has_sum_iff_tendsto_nat_of_nonneg (pow_nonneg h₁) _).mpr $
  by simp [neg_inv, geom_sum_eq, div_eq_mul_inv, *] at *

lemma summable_geometric_of_lt_1 {r : ℝ} (h₁ : 0 ≤ r) (h₂ : r < 1) : summable (λn:ℕ, r ^ n) :=
⟨_, has_sum_geometric_of_lt_1 h₁ h₂⟩

lemma tsum_geometric_of_lt_1 {r : ℝ} (h₁ : 0 ≤ r) (h₂ : r < 1) : ∑'n:ℕ, r ^ n = (1 - r)⁻¹ :=
(has_sum_geometric_of_lt_1 h₁ h₂).tsum_eq

lemma has_sum_geometric_two : has_sum (λn:ℕ, ((1:ℝ)/2) ^ n) 2 :=
by convert has_sum_geometric_of_lt_1 _ _; norm_num

lemma summable_geometric_two : summable (λn:ℕ, ((1:ℝ)/2) ^ n) :=
⟨_, has_sum_geometric_two⟩

lemma summable_geometric_two_encode {ι : Type*} [encodable ι] :
  summable (λ (i : ι), (1/2 : ℝ)^(encodable.encode i)) :=
summable_geometric_two.comp_injective encodable.encode_injective

lemma tsum_geometric_two : ∑'n:ℕ, ((1:ℝ)/2) ^ n = 2 :=
has_sum_geometric_two.tsum_eq

lemma sum_geometric_two_le (n : ℕ) : ∑ (i : ℕ) in range n, (1 / (2 : ℝ)) ^ i ≤ 2 :=
begin
  have : ∀ i, 0 ≤ (1 / (2 : ℝ)) ^ i,
  { intro i, apply pow_nonneg, norm_num },
  convert sum_le_tsum (range n) (λ i _, this i) summable_geometric_two,
  exact tsum_geometric_two.symm
end

lemma tsum_geometric_inv_two : ∑' n : ℕ, (2 : ℝ)⁻¹ ^ n = 2 :=
(inv_eq_one_div (2 : ℝ)).symm ▸ tsum_geometric_two

/-- The sum of `2⁻¹ ^ i` for `n ≤ i` equals `2 * 2⁻¹ ^ n`. -/
lemma tsum_geometric_inv_two_ge (n : ℕ) :
  ∑' i, ite (n ≤ i) ((2 : ℝ)⁻¹ ^ i) 0 = 2 * 2⁻¹ ^ n :=
begin
  have A : summable (λ (i : ℕ), ite (n ≤ i) ((2⁻¹ : ℝ) ^ i) 0),
  { apply summable_of_nonneg_of_le _ _ summable_geometric_two;
    { intro i, by_cases hi : n ≤ i; simp [hi] } },
  have B : (finset.range n).sum (λ (i : ℕ), ite (n ≤ i) ((2⁻¹ : ℝ)^i) 0) = 0 :=
    finset.sum_eq_zero (λ i hi, ite_eq_right_iff.2 $ λ h,
      (lt_irrefl _ ((finset.mem_range.1 hi).trans_le h)).elim),
  simp only [← sum_add_tsum_nat_add n A, B, if_true, zero_add, zero_le',
    le_add_iff_nonneg_left, pow_add, tsum_mul_right, tsum_geometric_inv_two],
end

lemma has_sum_geometric_two' (a : ℝ) : has_sum (λn:ℕ, (a / 2) / 2 ^ n) a :=
begin
  convert has_sum.mul_left (a / 2) (has_sum_geometric_of_lt_1
    (le_of_lt one_half_pos) one_half_lt_one),
  { funext n, simp, refl, },
  { norm_num }
end

lemma summable_geometric_two' (a : ℝ) : summable (λ n:ℕ, (a / 2) / 2 ^ n) :=
⟨a, has_sum_geometric_two' a⟩

lemma tsum_geometric_two' (a : ℝ) : ∑' n:ℕ, (a / 2) / 2^n = a :=
(has_sum_geometric_two' a).tsum_eq

/-- **Sum of a Geometric Series** -/
lemma nnreal.has_sum_geometric {r : ℝ≥0} (hr : r < 1) :
  has_sum (λ n : ℕ, r ^ n) (1 - r)⁻¹ :=
begin
  apply nnreal.has_sum_coe.1,
  push_cast,
  rw [nnreal.coe_sub (le_of_lt hr)],
  exact has_sum_geometric_of_lt_1 r.coe_nonneg hr
end

lemma nnreal.summable_geometric {r : ℝ≥0} (hr : r < 1) : summable (λn:ℕ, r ^ n) :=
⟨_, nnreal.has_sum_geometric hr⟩

lemma tsum_geometric_nnreal {r : ℝ≥0} (hr : r < 1) : ∑'n:ℕ, r ^ n = (1 - r)⁻¹ :=
(nnreal.has_sum_geometric hr).tsum_eq

/-- The series `pow r` converges to `(1-r)⁻¹`. For `r < 1` the RHS is a finite number,
and for `1 ≤ r` the RHS equals `∞`. -/
@[simp] lemma ennreal.tsum_geometric (r : ℝ≥0∞) : ∑'n:ℕ, r ^ n = (1 - r)⁻¹ :=
begin
  cases lt_or_le r 1 with hr hr,
  { rcases ennreal.lt_iff_exists_coe.1 hr with ⟨r, rfl, hr'⟩,
    norm_cast at *,
    convert ennreal.tsum_coe_eq (nnreal.has_sum_geometric hr),
    rw [ennreal.coe_inv $ ne_of_gt $ tsub_pos_iff_lt.2 hr] },
  { rw [tsub_eq_zero_iff_le.mpr hr, ennreal.inv_zero, ennreal.tsum_eq_supr_nat, supr_eq_top],
    refine λ a ha, (ennreal.exists_nat_gt (lt_top_iff_ne_top.1 ha)).imp
      (λ n hn, lt_of_lt_of_le hn _),
    calc (n:ℝ≥0∞) = ∑ i in range n, 1     : by rw [sum_const, nsmul_one, card_range]
              ... ≤ ∑ i in range n, r ^ i : sum_le_sum (λ k _, one_le_pow_of_one_le' hr k) }
end

end geometric

/-!
### Sequences with geometrically decaying distance in metric spaces

In this paragraph, we discuss sequences in metric spaces or emetric spaces for which the distance
between two consecutive terms decays geometrically. We show that such sequences are Cauchy
sequences, and bound their distances to the limit. We also discuss series with geometrically
decaying terms.
-/
section edist_le_geometric

variables [pseudo_emetric_space α] (r C : ℝ≥0∞) (hr : r < 1) (hC : C ≠ ⊤) {f : ℕ → α}
  (hu : ∀n, edist (f n) (f (n+1)) ≤ C * r^n)

include hr hC hu

/-- If `edist (f n) (f (n+1))` is bounded by `C * r^n`, `C ≠ ∞`, `r < 1`,
then `f` is a Cauchy sequence.-/
lemma cauchy_seq_of_edist_le_geometric : cauchy_seq f :=
begin
  refine cauchy_seq_of_edist_le_of_tsum_ne_top _ hu _,
  rw [ennreal.tsum_mul_left, ennreal.tsum_geometric],
  refine ennreal.mul_ne_top hC (ennreal.inv_ne_top.2 _),
  exact (tsub_pos_iff_lt.2 hr).ne'
end

omit hr hC

/-- If `edist (f n) (f (n+1))` is bounded by `C * r^n`, then the distance from
`f n` to the limit of `f` is bounded above by `C * r^n / (1 - r)`. -/
lemma edist_le_of_edist_le_geometric_of_tendsto {a : α} (ha : tendsto f at_top (𝓝 a)) (n : ℕ) :
  edist (f n) a ≤ (C * r^n) / (1 - r) :=
begin
  convert edist_le_tsum_of_edist_le_of_tendsto _ hu ha _,
  simp only [pow_add, ennreal.tsum_mul_left, ennreal.tsum_geometric, div_eq_mul_inv, mul_assoc]
end

/-- If `edist (f n) (f (n+1))` is bounded by `C * r^n`, then the distance from
`f 0` to the limit of `f` is bounded above by `C / (1 - r)`. -/
lemma edist_le_of_edist_le_geometric_of_tendsto₀ {a : α} (ha : tendsto f at_top (𝓝 a)) :
  edist (f 0) a ≤ C / (1 - r) :=
by simpa only [pow_zero, mul_one] using edist_le_of_edist_le_geometric_of_tendsto r C hu ha 0

end edist_le_geometric

section edist_le_geometric_two

variables [pseudo_emetric_space α] (C : ℝ≥0∞) (hC : C ≠ ⊤) {f : ℕ → α}
  (hu : ∀n, edist (f n) (f (n+1)) ≤ C / 2^n) {a : α} (ha : tendsto f at_top (𝓝 a))

include hC hu

/-- If `edist (f n) (f (n+1))` is bounded by `C * 2^-n`, then `f` is a Cauchy sequence.-/
lemma cauchy_seq_of_edist_le_geometric_two : cauchy_seq f :=
begin
  simp only [div_eq_mul_inv, ennreal.inv_pow] at hu,
  refine cauchy_seq_of_edist_le_geometric 2⁻¹ C _ hC hu,
  simp [ennreal.one_lt_two]
end

omit hC
include ha

/-- If `edist (f n) (f (n+1))` is bounded by `C * 2^-n`, then the distance from
`f n` to the limit of `f` is bounded above by `2 * C * 2^-n`. -/
lemma edist_le_of_edist_le_geometric_two_of_tendsto (n : ℕ) :
  edist (f n) a ≤ 2 * C / 2^n :=
begin
  simp only [div_eq_mul_inv, ennreal.inv_pow] at *,
  rw [mul_assoc, mul_comm],
  convert edist_le_of_edist_le_geometric_of_tendsto 2⁻¹ C hu ha n,
  rw [ennreal.one_sub_inv_two, inv_inv]
end

/-- If `edist (f n) (f (n+1))` is bounded by `C * 2^-n`, then the distance from
`f 0` to the limit of `f` is bounded above by `2 * C`. -/
lemma edist_le_of_edist_le_geometric_two_of_tendsto₀: edist (f 0) a ≤ 2 * C :=
by simpa only [pow_zero, div_eq_mul_inv, ennreal.inv_one, mul_one]
  using edist_le_of_edist_le_geometric_two_of_tendsto C hu ha 0

end edist_le_geometric_two

section le_geometric

variables [pseudo_metric_space α] {r C : ℝ} (hr : r < 1) {f : ℕ → α}
  (hu : ∀n, dist (f n) (f (n+1)) ≤ C * r^n)

include hr hu

lemma aux_has_sum_of_le_geometric : has_sum (λ n : ℕ, C * r^n) (C / (1 - r)) :=
begin
  rcases sign_cases_of_C_mul_pow_nonneg (λ n, dist_nonneg.trans (hu n)) with rfl | ⟨C₀, r₀⟩,
  { simp [has_sum_zero] },
  { refine has_sum.mul_left C _,
    simpa using has_sum_geometric_of_lt_1 r₀ hr }
end

variables (r C)

/-- If `dist (f n) (f (n+1))` is bounded by `C * r^n`, `r < 1`, then `f` is a Cauchy sequence.
Note that this lemma does not assume `0 ≤ C` or `0 ≤ r`. -/
lemma cauchy_seq_of_le_geometric : cauchy_seq f :=
cauchy_seq_of_dist_le_of_summable _ hu ⟨_, aux_has_sum_of_le_geometric hr hu⟩

/-- If `dist (f n) (f (n+1))` is bounded by `C * r^n`, `r < 1`, then the distance from
`f n` to the limit of `f` is bounded above by `C * r^n / (1 - r)`. -/
lemma dist_le_of_le_geometric_of_tendsto₀ {a : α} (ha : tendsto f at_top (𝓝 a)) :
  dist (f 0) a ≤ C / (1 - r) :=
(aux_has_sum_of_le_geometric hr hu).tsum_eq ▸
  dist_le_tsum_of_dist_le_of_tendsto₀ _ hu ⟨_, aux_has_sum_of_le_geometric hr hu⟩ ha

/-- If `dist (f n) (f (n+1))` is bounded by `C * r^n`, `r < 1`, then the distance from
`f 0` to the limit of `f` is bounded above by `C / (1 - r)`. -/
lemma dist_le_of_le_geometric_of_tendsto {a : α} (ha : tendsto f at_top (𝓝 a)) (n : ℕ) :
  dist (f n) a ≤ (C * r^n) / (1 - r) :=
begin
  have := aux_has_sum_of_le_geometric hr hu,
  convert dist_le_tsum_of_dist_le_of_tendsto _ hu ⟨_, this⟩ ha n,
  simp only [pow_add, mul_left_comm C, mul_div_right_comm],
  rw [mul_comm],
  exact (this.mul_left _).tsum_eq.symm
end

omit hr hu

variable (hu₂ : ∀ n, dist (f n) (f (n+1)) ≤ (C / 2) / 2^n)

/-- If `dist (f n) (f (n+1))` is bounded by `(C / 2) / 2^n`, then `f` is a Cauchy sequence. -/
lemma cauchy_seq_of_le_geometric_two : cauchy_seq f :=
cauchy_seq_of_dist_le_of_summable _ hu₂ $ ⟨_, has_sum_geometric_two' C⟩

/-- If `dist (f n) (f (n+1))` is bounded by `(C / 2) / 2^n`, then the distance from
`f 0` to the limit of `f` is bounded above by `C`. -/
lemma dist_le_of_le_geometric_two_of_tendsto₀ {a : α} (ha : tendsto f at_top (𝓝 a)) :
  dist (f 0) a ≤ C :=
(tsum_geometric_two' C) ▸ dist_le_tsum_of_dist_le_of_tendsto₀ _ hu₂ (summable_geometric_two' C) ha

include hu₂

/-- If `dist (f n) (f (n+1))` is bounded by `(C / 2) / 2^n`, then the distance from
`f n` to the limit of `f` is bounded above by `C / 2^n`. -/
lemma dist_le_of_le_geometric_two_of_tendsto {a : α} (ha : tendsto f at_top (𝓝 a)) (n : ℕ) :
  dist (f n) a ≤ C / 2^n :=
begin
  convert dist_le_tsum_of_dist_le_of_tendsto _ hu₂ (summable_geometric_two' C) ha n,
  simp only [add_comm n, pow_add, ← div_div_eq_div_mul],
  symmetry,
  exact ((has_sum_geometric_two' C).div_const _).tsum_eq
end

end le_geometric

/-! ### Summability tests based on comparison with geometric series -/

/-- A series whose terms are bounded by the terms of a converging geometric series converges. -/
lemma summable_one_div_pow_of_le {m : ℝ} {f : ℕ → ℕ} (hm : 1 < m) (fi : ∀ i, i ≤ f i) :
  summable (λ i, 1 / m ^ f i) :=
begin
  refine summable_of_nonneg_of_le
    (λ a, one_div_nonneg.mpr (pow_nonneg (zero_le_one.trans hm.le) _)) (λ a, _)
    (summable_geometric_of_lt_1 (one_div_nonneg.mpr (zero_le_one.trans hm.le))
      ((one_div_lt (zero_lt_one.trans hm) zero_lt_one).mpr (one_div_one.le.trans_lt hm))),
  rw [div_pow, one_pow],
  refine (one_div_le_one_div _ _).mpr (pow_le_pow hm.le (fi a));
  exact pow_pos (zero_lt_one.trans hm) _
end

/-! ### Positive sequences with small sums on encodable types -/

/-- For any positive `ε`, define on an encodable type a positive sequence with sum less than `ε` -/
def pos_sum_of_encodable {ε : ℝ} (hε : 0 < ε)
  (ι) [encodable ι] : {ε' : ι → ℝ // (∀ i, 0 < ε' i) ∧ ∃ c, has_sum ε' c ∧ c ≤ ε} :=
begin
  let f := λ n, (ε / 2) / 2 ^ n,
  have hf : has_sum f ε := has_sum_geometric_two' _,
  have f0 : ∀ n, 0 < f n := λ n, div_pos (half_pos hε) (pow_pos zero_lt_two _),
  refine ⟨f ∘ encodable.encode, λ i, f0 _, _⟩,
  rcases hf.summable.comp_injective (@encodable.encode_injective ι _) with ⟨c, hg⟩,
  refine ⟨c, hg, has_sum_le_inj _ (@encodable.encode_injective ι _) _ _ hg hf⟩,
  { assume i _, exact le_of_lt (f0 _) },
  { assume n, exact le_rfl }
end

lemma set.countable.exists_pos_has_sum_le {ι : Type*} {s : set ι} (hs : s.countable)
  {ε : ℝ} (hε : 0 < ε) :
  ∃ ε' : ι → ℝ, (∀ i, 0 < ε' i) ∧ ∃ c, has_sum (λ i : s, ε' i) c ∧ c ≤ ε :=
begin
  haveI := hs.to_encodable,
  rcases pos_sum_of_encodable hε s with ⟨f, hf0, ⟨c, hfc, hcε⟩⟩,
  refine ⟨λ i, if h : i ∈ s then f ⟨i, h⟩ else 1, λ i, _, ⟨c, _, hcε⟩⟩,
  { split_ifs, exacts [hf0 _, zero_lt_one] },
  { simpa only [subtype.coe_prop, dif_pos, subtype.coe_eta] }
end

lemma set.countable.exists_pos_forall_sum_le {ι : Type*} {s : set ι} (hs : s.countable)
  {ε : ℝ} (hε : 0 < ε) :
  ∃ ε' : ι → ℝ, (∀ i, 0 < ε' i) ∧ ∀ t : finset ι, ↑t ⊆ s → ∑ i in t, ε' i ≤ ε :=
begin
  rcases hs.exists_pos_has_sum_le hε with ⟨ε', hpos, c, hε'c, hcε⟩,
  refine ⟨ε', hpos, λ t ht, _⟩,
  rw [← sum_subtype_of_mem _ ht],
  refine (sum_le_has_sum _ _ hε'c).trans hcε,
  exact λ _ _, (hpos _).le
end

namespace nnreal

theorem exists_pos_sum_of_encodable {ε : ℝ≥0} (hε : ε ≠ 0) (ι) [encodable ι] :
  ∃ ε' : ι → ℝ≥0, (∀ i, 0 < ε' i) ∧ ∃c, has_sum ε' c ∧ c < ε :=
let ⟨a, a0, aε⟩ := exists_between (pos_iff_ne_zero.2 hε) in
let ⟨ε', hε', c, hc, hcε⟩ := pos_sum_of_encodable a0 ι in
⟨ λi, ⟨ε' i, le_of_lt $ hε' i⟩, assume i, nnreal.coe_lt_coe.1 $ hε' i,
  ⟨c, has_sum_le (assume i, le_of_lt $ hε' i) has_sum_zero hc ⟩, nnreal.has_sum_coe.1 hc,
   lt_of_le_of_lt (nnreal.coe_le_coe.1 hcε) aε ⟩

end nnreal

namespace ennreal

theorem exists_pos_sum_of_encodable {ε : ℝ≥0∞} (hε : ε ≠ 0) (ι) [encodable ι] :
  ∃ ε' : ι → ℝ≥0, (∀ i, 0 < ε' i) ∧ ∑' i, (ε' i : ℝ≥0∞) < ε :=
begin
  rcases exists_between (pos_iff_ne_zero.2 hε) with ⟨r, h0r, hrε⟩,
  rcases lt_iff_exists_coe.1 hrε with ⟨x, rfl, hx⟩,
  rcases nnreal.exists_pos_sum_of_encodable (coe_pos.1 h0r).ne' ι with ⟨ε', hp, c, hc, hcr⟩,
  exact ⟨ε', hp, (ennreal.tsum_coe_eq hc).symm ▸ lt_trans (coe_lt_coe.2 hcr) hrε⟩
end

theorem exists_pos_sum_of_encodable' {ε : ℝ≥0∞} (hε : ε ≠ 0) (ι) [encodable ι] :
  ∃ ε' : ι → ℝ≥0∞, (∀ i, 0 < ε' i) ∧ (∑' i, ε' i) < ε :=
let ⟨δ, δpos, hδ⟩ := exists_pos_sum_of_encodable hε ι in
  ⟨λ i, δ i, λ i, ennreal.coe_pos.2 (δpos i), hδ⟩

theorem exists_pos_tsum_mul_lt_of_encodable {ε : ℝ≥0∞} (hε : ε ≠ 0) {ι} [encodable ι]
  (w : ι → ℝ≥0∞) (hw : ∀ i, w i ≠ ∞) :
  ∃ δ : ι → ℝ≥0, (∀ i, 0 < δ i) ∧ ∑' i, (w i * δ i : ℝ≥0∞) < ε :=
begin
  lift w to ι → ℝ≥0 using hw,
  rcases exists_pos_sum_of_encodable hε ι with ⟨δ', Hpos, Hsum⟩,
  have : ∀ i, 0 < max 1 (w i), from λ i, zero_lt_one.trans_le (le_max_left _ _),
  refine ⟨λ i, δ' i / max 1 (w i), λ i, nnreal.div_pos (Hpos _) (this i), _⟩,
  refine lt_of_le_of_lt (ennreal.tsum_le_tsum $ λ i, _) Hsum,
  rw [coe_div (this i).ne'],
  refine mul_le_of_le_div' (ennreal.mul_le_mul le_rfl $ ennreal.inv_le_inv.2 _),
  exact coe_le_coe.2 (le_max_right _ _)
end

end ennreal

/-!
### Factorial
-/

lemma factorial_tendsto_at_top : tendsto nat.factorial at_top at_top :=
tendsto_at_top_at_top_of_monotone nat.monotone_factorial (λ n, ⟨n, n.self_le_factorial⟩)

lemma tendsto_factorial_div_pow_self_at_top : tendsto (λ n, n! / n^n : ℕ → ℝ) at_top (𝓝 0) :=
tendsto_of_tendsto_of_tendsto_of_le_of_le'
  tendsto_const_nhds
  (tendsto_const_div_at_top_nhds_0_nat 1)
  (eventually_of_forall $ λ n, div_nonneg (by exact_mod_cast n.factorial_pos.le)
    (pow_nonneg (by exact_mod_cast n.zero_le) _))
  begin
    refine (eventually_gt_at_top 0).mono (λ n hn, _),
    rcases nat.exists_eq_succ_of_ne_zero hn.ne.symm with ⟨k, rfl⟩,
    rw [← prod_range_add_one_eq_factorial, pow_eq_prod_const, div_eq_mul_inv, ← inv_eq_one_div,
      prod_nat_cast, nat.cast_succ, ← prod_inv_distrib', ← prod_mul_distrib,
      finset.prod_range_succ'],
    simp only [prod_range_succ', one_mul, nat.cast_add, zero_add, nat.cast_one],
    refine mul_le_of_le_one_left (inv_nonneg.mpr $ by exact_mod_cast hn.le) (prod_le_one _ _);
      intros x hx; rw finset.mem_range at hx,
    { refine mul_nonneg _ (inv_nonneg.mpr _); norm_cast; linarith },
    { refine (div_le_one $ by exact_mod_cast hn).mpr _, norm_cast, linarith }
  end

/-!
### Ceil and floor
-/

section

variables {R : Type*} [topological_space R] [linear_ordered_field R] [order_topology R]
[floor_ring R]

lemma tendsto_nat_floor_mul_div_at_top {a : R} (ha : 0 ≤ a) :
  tendsto (λ x, (⌊a * x⌋₊ : R) / x) at_top (𝓝 a) :=
begin
  have A : tendsto (λ (x : R), a - x⁻¹) at_top (𝓝 (a - 0)) :=
    tendsto_const_nhds.sub tendsto_inv_at_top_zero,
  rw sub_zero at A,
  apply tendsto_of_tendsto_of_tendsto_of_le_of_le' A tendsto_const_nhds,
  { refine eventually_at_top.2 ⟨1, λ x hx, _⟩,
    simp only [le_div_iff (zero_lt_one.trans_le hx), sub_mul,
      inv_mul_cancel (zero_lt_one.trans_le hx).ne'],
    have := nat.lt_floor_add_one (a * x),
    linarith },
  { refine eventually_at_top.2 ⟨1, λ x hx, _⟩,
    rw div_le_iff (zero_lt_one.trans_le hx),
    simp [nat.floor_le (mul_nonneg ha (zero_le_one.trans hx))] }
end

lemma tendsto_nat_ceil_mul_div_at_top {a : R} (ha : 0 ≤ a) :
  tendsto (λ x, (⌈a * x⌉₊ : R) / x) at_top (𝓝 a) :=
begin
  have A : tendsto (λ (x : R), a + x⁻¹) at_top (𝓝 (a + 0)) :=
    tendsto_const_nhds.add tendsto_inv_at_top_zero,
  rw add_zero at A,
  apply tendsto_of_tendsto_of_tendsto_of_le_of_le' tendsto_const_nhds A,
  { refine eventually_at_top.2 ⟨1, λ x hx, _⟩,
    rw le_div_iff (zero_lt_one.trans_le hx),
    exact nat.le_ceil _ },
  { refine eventually_at_top.2 ⟨1, λ x hx, _⟩,
    simp [div_le_iff (zero_lt_one.trans_le hx), inv_mul_cancel (zero_lt_one.trans_le hx).ne',
      (nat.ceil_lt_add_one ((mul_nonneg ha (zero_le_one.trans hx)))).le, add_mul] }
end

end


--Exercise 3.7.1
-- Author : Jagadish Bapanapally

variables p q r : Prop

-- commutativity of ∧ and ∨
example : p ∨ q ↔ q ∨ p :=
iff.intro
(assume h : p ∨ q,
  or.elim h (λ hp, or.inr hp) (λ hq, or.inl hq))
(assume h : q ∨ p,
  or.elim h (λ hp, or.inr hp) (λ hq, or.inl hq))

example : p ∨ q ↔ q ∨ p :=
iff.intro
(assume h : p ∨ q,
  or.elim h (λ hp, or.inr hp) (λ hq, or.inl hq))
(assume h : q ∨ p,
  or.elim h (λ hp, or.inr hp) (λ hq, or.inl hq))

-- associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
iff.intro
(assume hpqr : (p ∧ q) ∧ r,
  have hpq : p ∧ q, from and.left hpqr,
  have hp : p, from and.left hpq,
  have hq : q, from and.right hpq,
  have hr : r, from and.right hpqr,
  show p ∧ (q ∧ r), from and.intro hp (and.intro hq hr))
(assume hpqr1 : p ∧ (q ∧ r),
  have hqr : q ∧ r, from and.right hpqr1,
  have hp : p, from and.left hpqr1,
  have hq : q, from and.left hqr,
  have hr : r, from and.right hqr,
  show (p ∧ q) ∧ r, from (and.intro (and.intro hp hq) hr))

example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
iff.intro
(assume hpqr : (p ∨ q) ∨ r,
   or.elim hpqr
      (assume hpq : p ∨ q,
          or.elim hpq
           (assume hp : p,
              show p ∨ (q ∨ r), from or.inl hp)
            (assume hq : q,
              have hqr : q ∨ r, from or.inl hq,
              show p ∨ (q ∨ r), from or.inr hqr))
      (assume hr : r,
        have hqr : q ∨ r, from or.inr hr,
        show p ∨ (q ∨ r), from or.inr hqr)
)
(assume hpqr1 : p ∨ (q ∨ r),
   or.elim hpqr1
      (assume hp : p,
        have hpq : p ∨ q, from or.inl hp,
        show (p ∨ q) ∨ r, from or.inl hpq)
      (assume hqr : q ∨ r,
        or.elim hqr
          (assume hq : q,
            have hpq : p ∨ q, from or.inr hq,
            show (p ∨ q) ∨ r, from or.inl hpq)
          (assume hr : r,
            show (p ∨ q) ∨ r, from or.inr hr))
)

-- distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
iff.intro
(assume hpqr: p ∧ (q ∨ r),
  have hp : p, from hpqr.left,
  or.elim (hpqr.right)
    (assume hq : q,
      have hpq : p ∧ q, from and.intro hp hq,
      show (p ∧ q) ∨ (p ∧ r), from or.inl hpq
    )
    (assume hr : r,
      have hpr : p ∧ r, from and.intro hp hr,
      show (p ∧ q) ∨ (p ∧ r), from or.inr hpr
    )
)
(assume hpqr1: (p ∧ q) ∨ (p ∧ r),
  or.elim hpqr1
    (assume hpq: p ∧ q,
      have hp : p, from hpq.left,
      have hq : q, from hpq.right,
      show p ∧ (q ∨ r), from ⟨hp, or.inl hq⟩ -- ⟨
    )
    (assume hpr : p ∧ r,
       have hp : p, from hpr.left,
       have hr : r, from hpr.right,
       show p ∧ (q ∨ r), from ⟨hp, or.inr hr⟩))

--- new
example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
begin
apply iff.intro,
intro pqr,
cases pqr,
split,
left,
exact pqr,
left,
exact pqr,
split,
cases pqr,
right,
exact pqr_left,
cases pqr,
right,
exact pqr_right,
intro pqr,
cases pqr,
cases pqr_left,
cases pqr_right,
left,
exact pqr_left,
left,
exact pqr_left,
cases pqr_right,
left,
exact pqr_right,
right,
split,
exact pqr_left,
exact pqr_right,
end

-- other properties
example : (p → (q → r)) ↔ (p ∧ q → r) :=
begin
apply iff.intro,
{
  intro hpqr,
  intro hpq,
  apply hpqr, from hpq.left, from hpq.right
},
{
 intro hpqr,
 intro hp,
 intro hq,
 apply hpqr, from (and.intro hp hq)
},
end

example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) :=
begin
apply iff.intro,
{
  intro hpqr,
  split,
   {
     intro hp,
     apply hpqr, left, exact hp
   },
   {
     intro hq,
     apply hpqr, right, exact hq
   },
},
{
 intro hprqr,
 intro hpq,
 cases hprqr with hpr hqr,
        cases hpq with hp hq,
 {
   apply hpr, exact hp,
 },
 {
   apply hqr, exact hq,
 },
},
end

example : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
begin
apply iff.intro,
{
  intro hpq,
  split,
  {
    intro hp,
    apply hpq, left, exact hp,
  },
  {
    intro hq,
    apply hpq, right, exact hq,
  },
},
{
  intro hpq,
  cases hpq with hnp hnq,
   {
     intro hpq1,
     cases hpq1 with hp1 hq1,
     {
       apply hnp, exact hp1,
     },
     apply hnq, exact hq1,
   },
},
end

example : ¬p ∨ ¬q → ¬(p ∧ q) :=
begin
intro hnpq,
cases hnpq with hnp hnq,
{

  intro hnpq1,
  apply hnp, exact hnpq1.left,
},
intro hnpq2,
apply hnq, exact hnpq2.right,
end

example : ¬(p ∧ ¬p) :=
begin
intro hnp,
cases hnp,
apply hnp_right, exact hnp_left,
end

example : p ∧ ¬q → ¬(p → q) :=
begin
intro hpq,
intro hinpq,
have hp : p, from and.left hpq,
cases hpq,
apply hpq_right, apply hinpq, apply hp,
end

example : ¬p → (p → q) :=
begin
  intro hnp,
  intro hnp1,
  contradiction,
end

example : (¬p ∨ q) → (p → q) :=
begin
  intro hpq,
  intro hpq1,
  cases hpq,
  {
    contradiction
  },
  exact hpq,
end

example : p ∨ false ↔ p :=
begin
  apply iff.intro,
  {
    intro hpf,
    cases hpf,
    exact hpf,
    contradiction,
  },
  {
    intro hp,
    show p ∨ false, from or.inl hp,
  },
end

example : p ∧ false ↔ false :=
begin
apply iff.intro,
intro hpf,
have hp : p, from hpf.left,
show false, from hpf.right,
intro hpf1,
cases hpf1,
end

example : (p → q) → (¬q → ¬p) :=
begin
intro hpq,
intro hnq,
intro hnp,
have hq := hpq hnp,
contradiction,
end

--Exercise 3.7.2

open classical

variables s : Prop

example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
begin
intro prs,
apply or.elim (em p),
intro hp,
have hors := prs hp,
cases hors,
left,
intro hp1,
exact hors,
right,
intro hp1,
exact hors,
intro nhp,
left,
intro hp10,
contradiction,
end

example : ¬(p ∧ q) → ¬p ∨ ¬q :=
begin
  intro npq,
  cases em p,
  cases em q,
  have hnq:= (and.intro h h_1),
  contradiction,
  right,
  exact h_1,
  left,
  exact h,
end

example : ¬(p → q) → p ∧ ¬q :=
begin
intro pq,
cases em p,
cases em q,
have pimpq : p → q,
intro hp,
exact h_1,
contradiction,
split,
exact h,
exact h_1,
exfalso,
apply pq,
contradiction,
end

example : (p → q) → (¬p ∨ q) :=
begin
intro pq,
cases em p,
right,
show q, from pq h,
left,
exact h,
end

example : (¬q → ¬p) → (p → q) :=
begin
  intro qp,
  cases em p,
  cases em q,
  intro hp,
  exact h_1,
  intro hp2,
  have nhp:= qp h_1,
  contradiction,
  intro hp1,
  contradiction,
end

example : p ∨ ¬p :=
begin
  cases em p,
  left,
  exact h,
  right,
  exact h,
end

example : (((p → q) → p) → p) :=
begin
intro pq,
cases em p,
exact h,
have hp : p, apply pq,
intro hp,
contradiction,
exact hp,
end

--Exercise 3.7.3

example : ¬(p ↔ ¬p) :=
begin
intro hp,
cases hp,
apply hp_mp,
apply hp_mpr,
intro hp,
have hnp := hp_mp hp,
contradiction,
apply hp_mpr,
intro hp,
have hnp:= hp_mp hp,
contradiction,
end


import Lean

universe u

structure Magma where
  α   : Type u
  mul : α → α → α

instance : CoeSort Magma (Type u) where
  coe s := s.α

abbrev mul {M : Magma} (a b : M) : M :=
  M.mul a b

set_option pp.all true

infixl:70 (priority := high) "*" => mul

structure Semigroup extends Magma where
  mul_assoc (a b c : α) : a * b * c = a * (b * c)

instance : CoeSort Semigroup (Type u) where
  coe s := s.α

structure CommSemigroup extends Semigroup where
  mul_comm (a b : α) : a * b = b * a

structure Monoid extends Semigroup where
  one : α
  one_mul (a : α) : one * a = a
  mul_one (a : α) : a * one = a

instance : CoeSort Monoid (Type u) where
  coe s := s.α

structure CommMonoid extends Monoid where
  mul_comm (a b : α) : a * b = b * a

instance : Coe CommMonoid CommSemigroup where
  coe s := {
      α   := s.α
      mul := s.mul
      mul_assoc := s.mul_assoc
      mul_comm  := s.mul_comm
    }

structure Group extends Monoid where
  inv : α → α
  mul_left_inv (a : α) : (inv a) * a = one

instance : CoeSort Group (Type u) where
  coe s := s.α

abbrev inv {G : Group} (a : G) : G :=
  G.inv a

postfix:max "⁻¹" => inv

instance (G : Group) : OfNat (coeSort G.toMagma) (nat_lit 1) where
  ofNat := G.one

instance (G : Group) : OfNat (G.toMagma.α) (nat_lit 1) where
  ofNat := G.one

structure CommGroup extends Group where
  mul_comm (a b : α) : a * b = b * a

instance : CoeSort CommGroup (Type u) where
  coe s := s.α

theorem inv_mul_cancel_left {G : Group} (a b : G) : a⁻¹ * (a * b) = b := by
  rw [← G.mul_assoc, G.mul_left_inv, G.one_mul]

theorem toMonoidOneEq {G : Group} : G.toMonoid.one = 1 :=
  rfl

theorem inv_eq_of_mul_eq_one {G : Group} {a b : G} (h : a * b = 1) : a⁻¹ = b := by
  rw [← G.mul_one a⁻¹, toMonoidOneEq, ←h, ← G.mul_assoc, G.mul_left_inv, G.one_mul]

theorem inv_inv {G : Group} (a : G) : (a⁻¹)⁻¹ = a :=
  inv_eq_of_mul_eq_one (G.mul_left_inv a)

theorem mul_right_inv {G : Group} (a : G) : a * a⁻¹ = 1 := by
  have : a⁻¹⁻¹ * a⁻¹ = 1 := by rw [G.mul_left_inv]; rfl
  rw [inv_inv] at this
  assumption

unif_hint (G : Group) where
  |- G.toMonoid.one =?= 1

theorem mul_inv_rev {G : Group} (a b : G) : (a * b)⁻¹ = b⁻¹ * a⁻¹ := by
  apply inv_eq_of_mul_eq_one
  rw [G.mul_assoc, ← G.mul_assoc b, mul_right_inv, G.one_mul, mul_right_inv]

theorem mul_inv {G : CommGroup} (a b : G) : (a * b)⁻¹ = a⁻¹ * b⁻¹ := by
  rw [mul_inv_rev, G.mul_comm]


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau

! This file was ported from Lean 3 source module data.int.sqrt
! leanprover-community/mathlib commit ba2245edf0c8bb155f1569fd9b9492a9b384cde6
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Nat.Sqrt

/-!
# Square root of integers

This file defines the square root function on integers. `Int.sqrt z` is the greatest integer `r`
such that `r * r ≤ z`. If `z ≤ 0`, then `Int.sqrt z = 0`.
-/

namespace Int

/-- `sqrt z` is the square root of an integer `z`. If `z` is positive, it returns the largest
integer `r` such that `r * r ≤ n`. If it is negative, it returns `0`. For example, `sqrt (-1) = 0`,
`sqrt 1 = 1`, `sqrt 2 = 1` -/
-- @[pp_nodot] porting note: unknown attribute
def sqrt (z : ℤ) : ℤ :=
  Nat.sqrt <| Int.toNat z
#align int.sqrt Int.sqrt

theorem sqrt_eq (n : ℤ) : sqrt (n * n) = n.natAbs := by
  rw [sqrt, ← natAbs_mul_self, toNat_coe_nat, Nat.sqrt_eq]
#align int.sqrt_eq Int.sqrt_eq

theorem exists_mul_self (x : ℤ) : (∃ n, n * n = x) ↔ sqrt x * sqrt x = x :=
  ⟨fun ⟨n, hn⟩ => by rw [← hn, sqrt_eq, ← Int.ofNat_mul, natAbs_mul_self], fun h => ⟨sqrt x, h⟩⟩
#align int.exists_mul_self Int.exists_mul_self

theorem sqrt_nonneg (n : ℤ) : 0 ≤ sqrt n :=
  coe_nat_nonneg _
#align int.sqrt_nonneg Int.sqrt_nonneg

end Int


import prelim.embed
import matroid.rankfun matroid.dual .projection .matroid_in

open_locale classical
noncomputable theory

universes u v w

open matroid set

namespace matroid_in

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

/-- the contraction of C in M. Implemented as a projection -/
def contract (M : matroid_in α) (C : set α) : matroid_in α :=
⟨ M.E \ C,
  M.carrier.project C,
  by {convert M.carrier.projected_set_union_rank_zero C M.support,
      rw [diff_eq, compl_compl_inter_right], }⟩

/-- the deletion of D in M. Implemented as a loopification -/
def delete (M : matroid_in α) (D : set α) : matroid_in α :=
⟨ M.E \ D,
  M.carrier.loopify D,
  by {convert M.carrier.loopified_set_union_rank_zero D M.support, set_solver,}⟩

/-- the restriction of M to R (i.e. the deletion of Rᶜ)-/
def restrict (M : matroid_in α) (R : set α) : matroid_in α := M.delete Rᶜ

notation M / C := matroid_in.contract M C
notation M \ D := matroid_in.delete M D
notation M ∣ R := matroid_in.restrict M R

lemma restr_eq_del_compl (M : matroid_in α) (R : set α) :
  M ∣ R = M \ Rᶜ :=
rfl

@[simp, msimp] lemma con_rank (M : matroid_in α) (C X: set α) :
  (M / C).r X = M.r (X ∪ C) - M.r C :=
M.carrier.project_r _ _

@[simp, msimp] lemma con_E (M : matroid_in α) (C: set α) :
  (M / C).E = M.E \ C :=
rfl

@[simp, msimp] lemma del_rank (M : matroid_in α) (D X: set α) :
  (M \ D).r X = M.r (X \ D) :=
rfl

@[simp, msimp] lemma restr_r (M : matroid_in α) (R X : set α) :
  (M ∣ R).r X = M.r (X ∩ R) :=
by rw [restr_eq_del_compl, del_rank, diff_compl]

@[simp, msimp] lemma del_E (M : matroid_in α) (D: set α) :
  (M \ D).E = M.E \ D :=
rfl

@[simp, msimp] lemma restr_E (M : matroid_in α) (R : set α) :
  (M ∣ R).E = M.E ∩ R :=
by rw [restr_eq_del_compl, del_E, diff_compl]

lemma contr_restr_E (M : matroid_in α){C R : set α} (hR : R ⊆ M.E \ C) :
  (M / C ∣ R).E = R :=
by rwa [restr_E, con_E, ←subset_iff_inter_eq_right]

@[simp, msimp] lemma con_empty (M : matroid_in α) :
  (M / ∅) = M :=
by {ext, simp, intros X hX, simp [←r_carrier_eq_r _ ∅]}

@[simp, msimp] lemma del_empty (M : matroid_in α) :
  (M \ ∅) = M :=
by {ext, simp, intros X hX, simp [←r_carrier_eq_r _ ∅]}

@[simp,msimp] lemma del_del (M : matroid_in α) (D D' : set α) :
  (M \ D \ D') = M \ (D ∪ D') :=
by {ext, simp [diff_eq, ←inter_assoc], intros X hX, simp [diff_eq, ←inter_assoc, inter_right_comm ],  }

lemma del_del_comm (M : matroid_in α) (D D' : set α) :
  M \ D \ D' = M \ D' \ D :=
by rw [del_del, union_comm, ←del_del]

lemma del_eq_del_inter_E (M : matroid_in α) (D : set α) :
  M \ D = M \ (M.E ∩ D) :=
begin
  ext,
  {  simp only [diff_eq] with msimp, rw [compl_inter, inter_distrib_left], simp,  },
  simp only [diff_eq] with msimp, intros X hX,
  rw [subset_inter_iff, subset_iff_disjoint_compl, subset_iff_inter_eq_left] at hX,
  rw [compl_inter, inter_distrib_left], simp [hX],
end

lemma con_eq_con_inter_E (M : matroid_in α) (C : set α) :
  M / C = M / (M.E ∩ C) :=
begin
  ext,
  {  simp only [diff_eq] with msimp, rw [compl_inter, inter_distrib_left], simp,  },
  intros X hX,
  simp only with msimp at *,
  have : M.E ∩ X = X := by {rw inter_comm, exact subset_iff_inter_eq_left.mp (subset.trans hX (inter_subset_left _ _))},
  rw [r_eq_inter_r M C, r_eq_inter_r M (X ∪ C), inter_distrib_left, this],
end

lemma con_del_E (M : matroid_in α) (C D : set α) :
  (M / C \ D).E = M.E \ (C ∪ D) :=
by {simp only [diff_eq] with msimp, simp [←inter_assoc]}

lemma con_del_eq_del_con (M : matroid_in α) (C D : set α) (h : C ∩ D = ∅) :
  M / C \ D = M \ D / C :=
begin
  ext, simp [diff_eq, inter_right_comm _ Cᶜ],
  intros X hX,
  simp only [diff_eq, matroid_in.con_rank, matroid_in.del_rank],
  convert rfl; set_solver,
end

/- turns contract/delete to delete/contract when C ∩ D ≠ ∅ -/
lemma con_del_eq_del_con' (M : matroid_in α) (C D : set α) :
  M / C \ D = M \ (D \ C) / C :=
begin
  rw [del_eq_del_inter_E, con_del_eq_del_con, del_eq_del_inter_E M (D \ C)],
  congr' 2, simp [diff_eq, ←inter_assoc, inter_right_comm],
  ext, simp, tauto,
end

/- turns delete/contract to contract/delete when C ∩ D ≠ ∅ -/
lemma del_con_eq_con_del' (M : matroid_in α) (C D : set α) :
  M \ D / C = M / (C \ D) \ D :=
begin
  rw [con_eq_con_inter_E, ←con_del_eq_del_con, con_eq_con_inter_E M (C \ D)],
  congr' 2, simp [diff_eq, ←inter_assoc, inter_right_comm],
  {ext, simp, tauto},
end

lemma dual_con_eq_del_dual (M : matroid_in α) (A : set α) (hA : A ⊆ M.E) :
  of_mat ((M / A).as_mat.dual) = (of_mat M.as_mat.dual) \ A :=
begin
  ext, simp,
  refine (λ X hX, _),
  simp only [diff_eq, matroid.dual_r] with msimp coe_up,
  rw [(λ p q r z, by ring : ∀ p q r z : ℤ, p + (q-z) - (r-z) = p + q - r),
  inter_comm _ Aᶜ, ←inter_assoc],
  convert rfl; {ext, simp, tauto},
end

lemma dual_con (M : matroid_in α) (A : set α) :
  (M / A).dual = (M.dual) \ A :=
begin
  rw [con_eq_con_inter_E, del_eq_del_inter_E],  simp only with msimp,
  set A' := M.E ∩ A with hA',
  ext, simp,
  refine (λ X hX, _),
  simp only [diff_eq, matroid.dual_r] with msimp coe_up,
  rw [(λ p q r z, by ring : ∀ p q r z : ℤ, p + (q-z) - (r-z) = p + q - r),
  inter_comm _ A'ᶜ, ←inter_assoc],
  convert rfl; { ext, simp, tauto},
end

lemma dual_del (M : matroid_in α) (A : set α) :
  (M \ A).dual = M.dual / A :=
by rw [←M.dual_dual, ←dual_con, matroid_in.dual_dual, matroid_in.dual_dual]

lemma con_con (M : matroid_in α) (C C' : set α) :
  (M / C / C') = M / (C ∪ C') :=
by {rw [←M.dual_dual], repeat {rw ←dual_del}, rw del_del}

lemma con_con_comm (M : matroid_in α) (C C' : set α) :
  M / C / C' = M / C' / C:=
by rw [con_con, union_comm, ← con_con]

lemma con_del_con_del (M : matroid_in α) (C C' D D': set α) (h : (C ∪ C') ∩ (D ∪ D') = ∅ ) :
  M / C \ D / C' \ D' = M / (C ∪ C') \ (D ∪ D') :=
begin
  repeat {rw con_del_eq_del_con}, rwa [con_con, del_del],
  all_goals
  {apply disjoint_of_subsets _ _ h,
    repeat {apply subset_union_left},
    repeat {apply subset_union_right},},
end

lemma dual_con_del (M : matroid_in α){C D : set α} (hi : C ∩ D = ∅) :
  (M / C \ D).dual = M.dual / D \ C :=
by {rw [dual_del _ D, dual_con _ C, con_del_eq_del_con], rwa inter_comm,}

lemma dual_con_del' (M : matroid_in α){C D : set α} (hi : C ∩ D = ∅) :
  (M / C \ D).dual = M.dual \ C / D :=
by {rw [dual_con_del _ hi, con_del_eq_del_con],  rwa inter_comm,}

lemma con_rank_ground (M : matroid_in α) (C : set α) :
  (M / C).r (M / C).E = M.r M.E - M.r C :=
begin
  simp only [diff_eq] with msimp,
  rw r_eq_inter_r, convert rfl,
  rw [inter_distrib_left, ←inter_assoc, inter_self, ←inter_distrib_left], simp,
end

lemma indep_del_iff_indep_loopify {M : matroid_in α} {D X : set α} :
  (M \ D).is_indep X ↔ (M.carrier.loopify D).is_indep X :=
by rw [indep_iff_carrier, delete]

lemma indep_del_iff {M : matroid_in α} {D X : set α} :
  (M \ D).is_indep X ↔ X ∩ D = ∅ ∧ M.is_indep X :=
by {rw [indep_del_iff_indep_loopify, indep_loopify_iff, indep_iff_r, matroid.indep_iff_r, r_carrier_eq_r], tauto,  }

lemma delete_coindep_rank_ground (M : matroid_in α){D : set α} (hD : M.dual.is_indep D) :
  (M \ D).r (M \ D).E = M.r M.E :=
by {simp only [inter_assoc, inter_self, diff_eq] with msimp, exact (coindep_iff_r.mp hD).2,}

lemma coindep_contract_iff {M : matroid_in α} {C X : set α} :
  (M / C).dual.is_indep X ↔ X ∩ C = ∅ ∧ M.dual.is_indep X :=
by rw [dual_con, indep_del_iff]

/-- structure consisting of an ordered pair of disjoint sets contained in M.E . Possibly should
be abstracted away from matroids completely -/
@[ext] structure cd_pair (M : matroid_in α) :=
(C : set α)
(D : set α)
(disj : C ∩ D = ∅)
(C_ss_E : C ⊆ M.E)
(D_ss_E : D ⊆ M.E)

instance cd_pair.fintype (M : matroid_in α) : fintype (cd_pair M) :=
begin
  letI : fintype (set α × set α) := by apply_instance,
  set emb : M.cd_pair → (set α × set α) := λ e, ⟨e.C, e.D⟩ with hemb,
  refine fintype.of_injective emb (λ p₁ p₂ h, _),
  rw hemb at h, dsimp only at h,
  ext : 1,
  tidy,
end

def cd_pair.trivial (M : matroid_in α) : cd_pair M :=
⟨∅, ∅, by rw inter_self, empty_subset _, empty_subset _⟩

def cd_pair.switch {M : matroid_in α} (p : cd_pair M ) : cd_pair M  :=
⟨p.D,p.C, by {simpa [inter_comm] using p.disj}, p.D_ss_E, p.C_ss_E ⟩

def cd_pair.of_eq_E {M M': matroid_in α} (p : cd_pair M) (hE : M'.E = M.E) :
  cd_pair M' :=
⟨p.C,p.D, p.disj, by simpa [hE] using p.C_ss_E, by simpa [hE] using p.D_ss_E⟩

lemma cd_pair.rank {M : matroid_in α} (p : cd_pair M) (X : set α) (hX : X ⊆ (M / p.C \ p.D).E) :
  (M / p.C \ p.D).r X = M.r (X ∪ p.C) - M.r p.C :=
begin
  simp only [diff_eq] with msimp at *, congr',
  exact subset_iff_inter_eq_left.mp (subset.trans hX (inter_subset_right _ _)),
end

@[simp, msimp] lemma cd_pair.rank_subtype {M : matroid_in α} (p : cd_pair M) (X : set (M / p.C \ p.D).E) :
  (M / p.C \ p.D).r X = M.r (X ∪ p.C) - M.r p.C :=
by {rw cd_pair.rank, rintro x hx, obtain ⟨⟨x,h⟩,-,rfl⟩ := (mem_image _ _ _).mp hx, exact h, }

@[simp, msimp] lemma cd_pair.E {M : matroid_in α} (p : cd_pair M) :
  (M / p.C \ p.D).E = M.E \ (p.C ∪ p.D) :=
by simp only [diff_diff] with msimp

def cd_pair.of_restr (M : matroid_in α){R : set α} (hR : R ⊆ M.E) :
  cd_pair M :=
{ C := ∅,
  D := M.E \ R,
  C_ss_E := empty_subset _,
  D_ss_E := diff_subset _ _,
  disj := empty_inter _}

def cd_pair.of_con_restr (M : matroid_in α){C R : set α} (hC : C ⊆ M.E) (hR : R ⊆ M.E \ C) :
  cd_pair M :=
{ C := C,
  D := M.E \ (C ∪ R),
  C_ss_E := hC,
  D_ss_E := diff_subset _ _,
  disj := by {ext, simp, tauto}}

/-- maps a cd_pair M to the corresponding pair in M.as_mat-/
def cd_pair.to_as_mat {M : matroid_in α} (p : cd_pair M) : cd_pair (M.as_mat : matroid_in M.E) :=
{ C := (coe ⁻¹' p.C),
  D := (coe ⁻¹' p.D),
  disj := by {unfold_coes, rw [←preimage_inter, p.disj, preimage_empty]},
  C_ss_E := λ x hx, by {simp at *},
  D_ss_E := λ x hx, by {simp at *}}

/-- maps a cd_pair (M.as_mat : matroid_in M.E) to the corresponding pair in M-/
def cd_pair.from_as_mat {M : matroid_in α} (p : cd_pair (M.as_mat : matroid_in M.E)) : cd_pair M :=
{ C := coe '' p.C,
  D := coe '' p.D,
  disj := by {unfold_coes, rw [image_inter, p.disj, image_empty], exact subtype.coe_injective,  },
  C_ss_E := by simp,
  D_ss_E := by simp }

/-- the image of a cd_pair under an injection  -/
def cd_pair.image {M : matroid α} {M' : matroid β} (p : cd_pair (M : matroid_in α)) (f : α ↪ β) :
  cd_pair (M' : matroid_in β) :=
{ C := (f.to_fun '' p.C),
  D := (f.to_fun '' p.D),
  disj := by {rw [(image_inter f.inj'), p.disj, image_empty], },
  C_ss_E := λ x hx, by simp,
  D_ss_E := λ x hx, by simp}

end matroid_in


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Kenny Lau

! This file was ported from Lean 3 source module data.int.range
! leanprover-community/mathlib commit f2f413b9d4be3a02840d0663dace76e8fe3da053
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.List.Range
import Mathbin.Data.Int.Order.Basic

/-!
# Intervals in ℤ

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

This file defines integer ranges. `range m n` is the set of integers greater than `m` and strictly
less than `n`.

## Note

This could be unified with `data.list.intervals`. See the TODOs there.
-/

namespace Int

#print Int.range /-
/-- List enumerating `[m, n)`. This is the ℤ variant of `list.Ico`. -/
def range (m n : ℤ) : List ℤ :=
  (List.range (toNat (n - m))).map fun r => m + r
#align int.range Int.range
-/

#print Int.mem_range_iff /-
theorem mem_range_iff {m n r : ℤ} : r ∈ range m n ↔ m ≤ r ∧ r < n :=
  ⟨fun H =>
    let ⟨s, h1, h2⟩ := List.mem_map.1 H
    h2 ▸
      ⟨le_add_of_nonneg_right (ofNat_zero_le s),
        add_lt_of_lt_sub_left <|
          match n - m, h1 with
          | (k : ℕ), h1 => by rwa [List.mem_range, to_nat_coe_nat, ← coe_nat_lt] at h1⟩,
    fun ⟨h1, h2⟩ =>
    List.mem_map.2
      ⟨toNat (r - m),
        List.mem_range.2 <| by
          rw [← coe_nat_lt, to_nat_of_nonneg (sub_nonneg_of_le h1),
              to_nat_of_nonneg (sub_nonneg_of_le (le_of_lt (lt_of_le_of_lt h1 h2)))] <;>
            exact sub_lt_sub_right h2 _,
        show m + _ = _ by rw [to_nat_of_nonneg (sub_nonneg_of_le h1), add_sub_cancel'_right]⟩⟩
#align int.mem_range_iff Int.mem_range_iff
-/

#print Int.decidableLELT /-
instance decidableLELT (P : Int → Prop) [DecidablePred P] (m n : ℤ) :
    Decidable (∀ r, m ≤ r → r < n → P r) :=
  decidable_of_iff (∀ r ∈ range m n, P r) <| by simp only [mem_range_iff, and_imp]
#align int.decidable_le_lt Int.decidableLELT
-/

#print Int.decidableLELE /-
instance decidableLELE (P : Int → Prop) [DecidablePred P] (m n : ℤ) :
    Decidable (∀ r, m ≤ r → r ≤ n → P r) :=
  decidable_of_iff (∀ r ∈ range m (n + 1), P r) <| by
    simp only [mem_range_iff, and_imp, lt_add_one_iff]
#align int.decidable_le_le Int.decidableLELE
-/

#print Int.decidableLTLT /-
instance decidableLTLT (P : Int → Prop) [DecidablePred P] (m n : ℤ) :
    Decidable (∀ r, m < r → r < n → P r) :=
  Int.decidableLELT P _ _
#align int.decidable_lt_lt Int.decidableLTLT
-/

#print Int.decidableLTLE /-
instance decidableLTLE (P : Int → Prop) [DecidablePred P] (m n : ℤ) :
    Decidable (∀ r, m < r → r ≤ n → P r) :=
  Int.decidableLELE P _ _
#align int.decidable_lt_le Int.decidableLTLE
-/

end Int