/-
Copyright (c) 2021 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import analysis.box_integral.box.basic
import analysis.specific_limits

/-!
# Induction on subboxes

In this file we prove the following induction principle for `box_integral.box`, see
`box_integral.box.subbox_induction_on`. Let `p` be a predicate on `box_integral.box ι`, let `I` be a
box. Suppose that the following two properties hold true.

* Consider a smaller box `J ≤ I`. The hyperplanes passing through the center of `J` split it into
  `2 ^ n` boxes. If `p` holds true on each of these boxes, then it is true on `J`.
* For each `z` in the closed box `I.Icc` there exists a neighborhood `U` of `z` within `I.Icc` such
  that for every box `J ≤ I` such that `z ∈ J.Icc ⊆ U`, if `J` is homothetic to `I` with a
  coefficient of the form `1 / 2 ^ m`, then `p` is true on `J`.

Then `p I` is true.

## Tags

rectangular box, induction
-/

open set finset function filter metric
open_locale classical topological_space filter ennreal
noncomputable theory

namespace box_integral

namespace box

variables {ι : Type*} {I J : box ι}

/-- For a box `I`, the hyperplanes passing through its center split `I` into `2 ^ card ι` boxes.
`box_integral.box.split_center_box I s` is one of these boxes. See also
`box_integral.partition.split_center` for the corresponding `box_integral.partition`. -/
def split_center_box (I : box ι) (s : set ι) : box ι :=
{ lower := s.piecewise (λ i, (I.lower i + I.upper i) / 2) I.lower,
  upper := s.piecewise I.upper (λ i, (I.lower i + I.upper i) / 2),
  lower_lt_upper := λ i, by { dunfold set.piecewise, split_ifs;
    simp only [left_lt_add_div_two, add_div_two_lt_right, I.lower_lt_upper] } }

lemma mem_split_center_box {s : set ι} {y : ι → ℝ} :
  y ∈ I.split_center_box s ↔ y ∈ I ∧ ∀ i, (I.lower i + I.upper i) / 2 < y i ↔ i ∈ s :=
begin
  simp only [split_center_box, mem_def, ← forall_and_distrib],
  refine forall_congr (λ i, _),
  dunfold set.piecewise,
  split_ifs with hs; simp only [hs, iff_true, iff_false, not_lt],
  exacts [⟨λ H, ⟨⟨(left_lt_add_div_two.2 (I.lower_lt_upper i)).trans H.1, H.2⟩, H.1⟩,
    λ H, ⟨H.2, H.1.2⟩⟩,
    ⟨λ H, ⟨⟨H.1, H.2.trans (add_div_two_lt_right.2 (I.lower_lt_upper i)).le⟩, H.2⟩,
      λ H, ⟨H.1.1, H.2⟩⟩]
end

lemma split_center_box_le (I : box ι) (s : set ι) : I.split_center_box s ≤ I :=
λ x hx, (mem_split_center_box.1 hx).1

lemma disjoint_split_center_box (I : box ι) {s t : set ι} (h : s ≠ t) :
  disjoint (I.split_center_box s : set (ι → ℝ)) (I.split_center_box t) :=
begin
  rintro y ⟨hs, ht⟩, apply h,
  ext i,
  rw [mem_coe, mem_split_center_box] at hs ht,
  rw [← hs.2, ← ht.2]
end

lemma injective_split_center_box (I : box ι) : injective I.split_center_box :=
λ s t H, by_contra $ λ Hne, (I.disjoint_split_center_box Hne).ne (nonempty_coe _).ne_empty (H ▸ rfl)

@[simp] lemma exists_mem_split_center_box {I : box ι} {x : ι → ℝ} :
  (∃ s, x ∈ I.split_center_box s) ↔ x ∈ I :=
⟨λ ⟨s, hs⟩, I.split_center_box_le s hs,
  λ hx, ⟨{i | (I.lower i + I.upper i) / 2 < x i}, mem_split_center_box.2 ⟨hx, λ i, iff.rfl⟩⟩⟩

/-- `box_integral.box.split_center_box` bundled as a `function.embedding`. -/
@[simps] def split_center_box_emb (I : box ι) : set ι ↪ box ι :=
⟨split_center_box I, injective_split_center_box I⟩

@[simp]

@[simp] lemma upper_sub_lower_split_center_box (I : box ι) (s : set ι) (i : ι) :
  (I.split_center_box s).upper i - (I.split_center_box s).lower i = (I.upper i - I.lower i) / 2 :=
by by_cases hs : i ∈ s; field_simp [split_center_box, hs, mul_two, two_mul]

/-- Let `p` be a predicate on `box ι`, let `I` be a box. Suppose that the following two properties
hold true.

* `H_ind` : Consider a smaller box `J ≤ I`. The hyperplanes passing through the center of `J` split
  it into `2 ^ n` boxes. If `p` holds true on each of these boxes, then it true on `J`.

* `H_nhds` : For each `z` in the closed box `I.Icc` there exists a neighborhood `U` of `z` within
  `I.Icc` such that for every box `J ≤ I` such that `z ∈ J.Icc ⊆ U`, if `J` is homothetic to `I`
  with a coefficient of the form `1 / 2 ^ m`, then `p` is true on `J`.

Then `p I` is true. See also `box_integral.box.subbox_induction_on` for a version using
`box_integral.prepartition.split_center` instead of `box_integral.box.split_center_box`.

The proof still works if we assume `H_ind` only for subboxes `J ≤ I` that are homothetic to `I` with
a coefficient of the form `2⁻ᵐ` but we do not need this generalization yet. -/
@[elab_as_eliminator]
lemma subbox_induction_on' {p : box ι → Prop} (I : box ι)
  (H_ind : ∀ J ≤ I, (∀ s, p (split_center_box J s)) → p J)
  (H_nhds : ∀ z ∈ I.Icc, ∃ (U ∈ 𝓝[I.Icc] z), ∀ (J ≤ I) (m : ℕ), z ∈ J.Icc → J.Icc ⊆ U →
    (∀ i, J.upper i - J.lower i = (I.upper i - I.lower i) / 2 ^ m) → p J) :
  p I :=
begin
  by_contra hpI,
  -- First we use `H_ind` to construct a decreasing sequence of boxes such that `∀ m, ¬p (J m)`.
  replace H_ind := λ J hJ, not_imp_not.2 (H_ind J hJ),
  simp only [exists_imp_distrib, not_forall] at H_ind,
  choose! s hs using H_ind,
  set J : ℕ → box ι := λ m, (λ J, split_center_box J (s J))^[m] I,
  have J_succ : ∀ m, J (m + 1) = split_center_box (J m) (s $ J m) := λ m, iterate_succ_apply' _ _ _,
  -- Now we prove some properties of `J`
  have hJmono : antitone J,
    from antitone_nat_of_succ_le (λ n, by simpa [J_succ] using split_center_box_le _ _),
  have hJle : ∀ m, J m ≤ I, from λ m, hJmono (zero_le m),
  have hJp : ∀ m, ¬p (J m),
    from λ m, nat.rec_on m hpI (λ m, by simpa only [J_succ] using hs (J m) (hJle m)),
  have hJsub : ∀ m i, (J m).upper i - (J m).lower i = (I.upper i - I.lower i) / 2 ^ m,
  { intros m i, induction m with m ihm, { simp [J] },
    simp only [pow_succ', J_succ, upper_sub_lower_split_center_box, ihm, div_div_eq_div_mul] },
  have h0 : J 0 = I, from rfl,
  -- Now we clear unneeded assumptions
  clear_value J, clear hpI hs J_succ s,
  -- Let `z` be the unique common point of all `(J m).Icc`. Then `H_nhds` proves `p (J m)` for
  -- sufficiently large `m`. This contradicts `hJp`.
  set z : ι → ℝ := ⨆ m, (J m).lower,
  have hzJ : ∀ m, z ∈ (J m).Icc,
    from mem_Inter.1 (csupr_mem_Inter_Icc_of_antitone_Icc
      ((@box.Icc ι).monotone.comp_antitone hJmono) (λ m, (J m).lower_le_upper)),
  have hJl_mem : ∀ m, (J m).lower ∈ I.Icc, from λ m, le_iff_Icc.1 (hJle m) (J m).lower_mem_Icc,
  have hJu_mem : ∀ m, (J m).upper ∈ I.Icc, from λ m, le_iff_Icc.1 (hJle m) (J m).upper_mem_Icc,
  have hJlz : tendsto (λ m, (J m).lower) at_top (𝓝 z),
    from tendsto_at_top_csupr (antitone_lower.comp hJmono)
      ⟨I.upper, λ x ⟨m, hm⟩, hm ▸ (hJl_mem m).2⟩,
  have hJuz : tendsto (λ m, (J m).upper) at_top (𝓝 z),
  { suffices : tendsto (λ m, (J m).upper - (J m).lower) at_top (𝓝 0), by simpa using hJlz.add this,
    refine tendsto_pi_nhds.2 (λ i, _),
    simpa [hJsub] using tendsto_const_nhds.div_at_top
      (tendsto_pow_at_top_at_top_of_one_lt (@one_lt_two ℝ _ _)) },
  replace hJlz : tendsto (λ m, (J m).lower) at_top (𝓝[Icc I.lower I.upper] z),
    from tendsto_nhds_within_of_tendsto_nhds_of_eventually_within _ hJlz
      (eventually_of_forall hJl_mem),
  replace hJuz : tendsto (λ m, (J m).upper) at_top (𝓝[Icc I.lower I.upper] z),
    from tendsto_nhds_within_of_tendsto_nhds_of_eventually_within _ hJuz
      (eventually_of_forall hJu_mem),
  rcases H_nhds z (h0 ▸ hzJ 0) with ⟨U, hUz, hU⟩,
  rcases (tendsto_lift'.1 (hJlz.Icc hJuz) U hUz).exists with ⟨m, hUm⟩,
  exact hJp m (hU (J m) (hJle m) m (hzJ m) hUm (hJsub m))
end

end box

end box_integral


/-
Copyright (c) 2022 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johanes Hölzl, Patrick Massot, Yury Kudryashov, Kevin Wilson, Heather Macbeth

! This file was ported from Lean 3 source module order.filter.prod
! leanprover-community/mathlib commit d6fad0e5bf2d6f48da9175d25c3dc5706b3834ce
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Order.Filter.Basic

/-!
# Product and coproduct filters

In this file we define `Filter.prod f g` (notation: `f ×ᶠ g`) and `Filter.coprod f g`. The product
of two filters is the largest filter `l` such that `Filter.Tendsto Prod.fst l f` and `Filter.Tendsto
Prod.snd l g`.

## Implementation details

The product filter cannot be defined using the monad structure on filters. For example:

```lean
F := do {x ← seq, y ← top, return (x, y)}
G := do {y ← top, x ← seq, return (x, y)}
```
hence:
```lean
s ∈ F  ↔  ∃ n, [n..∞] × univ ⊆ s
s ∈ G  ↔  ∀ i:ℕ, ∃ n, [n..∞] × {i} ⊆ s
```
Now `⋃ i, [i..∞] × {i}` is in `G` but not in `F`.
As product filter we want to have `F` as result.

## Notations

* `f ×ᶠ g` : `filter.prod f g`, localized in `filter`.

-/

open Set

open Filter

namespace Filter

variable {α β γ δ : Type _} {ι : Sort _}

section Prod

variable {s : Set α} {t : Set β} {f : Filter α} {g : Filter β}

/-- Product of filters. This is the filter generated by cartesian products
of elements of the component filters. -/
protected def prod (f : Filter α) (g : Filter β) : Filter (α × β) :=
  f.comap Prod.fst ⊓ g.comap Prod.snd
#align filter.prod Filter.prod

@[inherit_doc]
scoped infixl:60 " ×ᶠ " => Filter.prod

theorem prod_mem_prod {s : Set α} {t : Set β} {f : Filter α} {g : Filter β} (hs : s ∈ f)
    (ht : t ∈ g) : s ×ˢ t ∈ f ×ᶠ g :=
  inter_mem_inf (preimage_mem_comap hs) (preimage_mem_comap ht)
#align filter.prod_mem_prod Filter.prod_mem_prod

theorem mem_prod_iff {s : Set (α × β)} {f : Filter α} {g : Filter β} :
    s ∈ f ×ᶠ g ↔ ∃ t₁ ∈ f, ∃ t₂ ∈ g, t₁ ×ˢ t₂ ⊆ s := by
  simp only [Filter.prod]
  constructor
  · rintro ⟨t₁, ⟨s₁, hs₁, hts₁⟩, t₂, ⟨s₂, hs₂, hts₂⟩, rfl⟩
    exact ⟨s₁, hs₁, s₂, hs₂, fun p ⟨h, h'⟩ => ⟨hts₁ h, hts₂ h'⟩⟩
  · rintro ⟨t₁, ht₁, t₂, ht₂, h⟩
    exact mem_inf_of_inter (preimage_mem_comap ht₁) (preimage_mem_comap ht₂) h
#align filter.mem_prod_iff Filter.mem_prod_iff

@[simp]
theorem prod_mem_prod_iff {s : Set α} {t : Set β} {f : Filter α} {g : Filter β} [f.NeBot]
    [g.NeBot] : s ×ˢ t ∈ f ×ᶠ g ↔ s ∈ f ∧ t ∈ g :=
  ⟨fun h =>
    let ⟨_s', hs', _t', ht', H⟩ := mem_prod_iff.1 h
    (prod_subset_prod_iff.1 H).elim
      (fun ⟨hs's, ht't⟩ => ⟨mem_of_superset hs' hs's, mem_of_superset ht' ht't⟩) fun h =>
      h.elim (fun hs'e => absurd hs'e (nonempty_of_mem hs').ne_empty) fun ht'e =>
        absurd ht'e (nonempty_of_mem ht').ne_empty,
    fun h => prod_mem_prod h.1 h.2⟩
#align filter.prod_mem_prod_iff Filter.prod_mem_prod_iff

theorem mem_prod_principal {f : Filter α} {s : Set (α × β)} {t : Set β} :
    s ∈ f ×ᶠ 𝓟 t ↔ { a | ∀ b ∈ t, (a, b) ∈ s } ∈ f := by
  rw [← @exists_mem_subset_iff _ f, mem_prod_iff]
  refine' exists_congr fun u => Iff.rfl.and ⟨_, fun h => ⟨t, mem_principal_self t, _⟩⟩
  · rintro ⟨v, v_in, hv⟩ a a_in b b_in
    exact hv (mk_mem_prod a_in <| v_in b_in)
  · rintro ⟨x, y⟩ ⟨hx, hy⟩
    exact h hx y hy
#align filter.mem_prod_principal Filter.mem_prod_principal

theorem mem_prod_top {f : Filter α} {s : Set (α × β)} :
    s ∈ f ×ᶠ (⊤ : Filter β) ↔ { a | ∀ b, (a, b) ∈ s } ∈ f := by
  rw [← principal_univ, mem_prod_principal]
  simp only [mem_univ, forall_true_left]
#align filter.mem_prod_top Filter.mem_prod_top

theorem eventually_prod_principal_iff {p : α × β → Prop} {s : Set β} :
    (∀ᶠ x : α × β in f ×ᶠ 𝓟 s, p x) ↔ ∀ᶠ x : α in f, ∀ y : β, y ∈ s → p (x, y) := by
  rw [eventually_iff, eventually_iff, mem_prod_principal]
  simp only [mem_setOf_eq]
#align filter.eventually_prod_principal_iff Filter.eventually_prod_principal_iff

theorem comap_prod (f : α → β × γ) (b : Filter β) (c : Filter γ) :
    comap f (b ×ᶠ c) = comap (Prod.fst ∘ f) b ⊓ comap (Prod.snd ∘ f) c := by
  erw [comap_inf, Filter.comap_comap, Filter.comap_comap]
#align filter.comap_prod Filter.comap_prod

theorem prod_top {f : Filter α} : f ×ᶠ (⊤ : Filter β) = f.comap Prod.fst := by
  rw [Filter.prod, comap_top, inf_top_eq]
#align filter.prod_top Filter.prod_top

theorem sup_prod (f₁ f₂ : Filter α) (g : Filter β) : (f₁ ⊔ f₂) ×ᶠ g = (f₁ ×ᶠ g) ⊔ (f₂ ×ᶠ g) := by
  rw [Filter.prod, comap_sup, inf_sup_right, ← Filter.prod, ← Filter.prod]
#align filter.sup_prod Filter.sup_prod

theorem prod_sup (f : Filter α) (g₁ g₂ : Filter β) : f ×ᶠ g₁ ⊔ g₂ = (f ×ᶠ g₁) ⊔ (f ×ᶠ g₂) := by
  rw [Filter.prod, comap_sup, inf_sup_left, ← Filter.prod, ← Filter.prod]
#align filter.prod_sup Filter.prod_sup

theorem eventually_prod_iff {p : α × β → Prop} {f : Filter α} {g : Filter β} :
    (∀ᶠ x in f ×ᶠ g, p x) ↔
      ∃ pa : α → Prop, (∀ᶠ x in f, pa x) ∧ ∃ pb : β → Prop, (∀ᶠ y in g, pb y) ∧
        ∀ {x}, pa x → ∀ {y}, pb y → p (x, y) :=
  by simpa only [Set.prod_subset_iff] using @mem_prod_iff α β p f g
#align filter.eventually_prod_iff Filter.eventually_prod_iff

theorem tendsto_fst {f : Filter α} {g : Filter β} : Tendsto Prod.fst (f ×ᶠ g) f :=
  tendsto_inf_left tendsto_comap
#align filter.tendsto_fst Filter.tendsto_fst

theorem tendsto_snd {f : Filter α} {g : Filter β} : Tendsto Prod.snd (f ×ᶠ g) g :=
  tendsto_inf_right tendsto_comap
#align filter.tendsto_snd Filter.tendsto_snd

theorem Tendsto.prod_mk {f : Filter α} {g : Filter β} {h : Filter γ} {m₁ : α → β} {m₂ : α → γ}
    (h₁ : Tendsto m₁ f g) (h₂ : Tendsto m₂ f h) : Tendsto (fun x => (m₁ x, m₂ x)) f (g ×ᶠ h) :=
  tendsto_inf.2 ⟨tendsto_comap_iff.2 h₁, tendsto_comap_iff.2 h₂⟩
#align filter.tendsto.prod_mk Filter.Tendsto.prod_mk

theorem tendsto_prod_swap {α1 α2 : Type _} {a1 : Filter α1} {a2 : Filter α2} :
    Tendsto (Prod.swap : α1 × α2 → α2 × α1) (a1 ×ᶠ a2) (a2 ×ᶠ a1) :=
  tendsto_snd.prod_mk tendsto_fst
#align filter.tendsto_prod_swap Filter.tendsto_prod_swap

theorem Eventually.prod_inl {la : Filter α} {p : α → Prop} (h : ∀ᶠ x in la, p x) (lb : Filter β) :
    ∀ᶠ x in la ×ᶠ lb, p (x : α × β).1 :=
  tendsto_fst.eventually h
#align filter.eventually.prod_inl Filter.Eventually.prod_inl

theorem Eventually.prod_inr {lb : Filter β} {p : β → Prop} (h : ∀ᶠ x in lb, p x) (la : Filter α) :
    ∀ᶠ x in la ×ᶠ lb, p (x : α × β).2 :=
  tendsto_snd.eventually h
#align filter.eventually.prod_inr Filter.Eventually.prod_inr

theorem Eventually.prod_mk {la : Filter α} {pa : α → Prop} (ha : ∀ᶠ x in la, pa x) {lb : Filter β}
    {pb : β → Prop} (hb : ∀ᶠ y in lb, pb y) : ∀ᶠ p in la ×ᶠ lb, pa (p : α × β).1 ∧ pb p.2 :=
  (ha.prod_inl lb).and (hb.prod_inr la)
#align filter.eventually.prod_mk Filter.Eventually.prod_mk

theorem EventuallyEq.prod_map {δ} {la : Filter α} {fa ga : α → γ} (ha : fa =ᶠ[la] ga)
    {lb : Filter β} {fb gb : β → δ} (hb : fb =ᶠ[lb] gb) :
    Prod.map fa fb =ᶠ[la ×ᶠ lb] Prod.map ga gb :=
  (Eventually.prod_mk ha hb).mono fun _ h => Prod.ext h.1 h.2
#align filter.eventually_eq.prod_map Filter.EventuallyEq.prod_map

theorem EventuallyLE.prod_map {δ} [LE γ] [LE δ] {la : Filter α} {fa ga : α → γ} (ha : fa ≤ᶠ[la] ga)
    {lb : Filter β} {fb gb : β → δ} (hb : fb ≤ᶠ[lb] gb) :
    Prod.map fa fb ≤ᶠ[la ×ᶠ lb] Prod.map ga gb :=
  Eventually.prod_mk ha hb
#align filter.eventually_le.prod_map Filter.EventuallyLE.prod_map

theorem Eventually.curry {la : Filter α} {lb : Filter β} {p : α × β → Prop}
    (h : ∀ᶠ x in la ×ᶠ lb, p x) : ∀ᶠ x in la, ∀ᶠ y in lb, p (x, y) := by
  rcases eventually_prod_iff.1 h with ⟨pa, ha, pb, hb, h⟩
  exact ha.mono fun a ha => hb.mono fun b hb => h ha hb
#align filter.eventually.curry Filter.Eventually.curry

/-- A fact that is eventually true about all pairs `l ×ᶠ l` is eventually true about
all diagonal pairs `(i, i)` -/
theorem Eventually.diag_of_prod {f : Filter α} {p : α × α → Prop} (h : ∀ᶠ i in f ×ᶠ f, p i) :
    ∀ᶠ i in f, p (i, i) := by
  obtain ⟨t, ht, s, hs, hst⟩ := eventually_prod_iff.1 h
  apply (ht.and hs).mono fun x hx => hst hx.1 hx.2
#align filter.eventually.diag_of_prod Filter.Eventually.diag_of_prod

theorem Eventually.diag_of_prod_left {f : Filter α} {g : Filter γ} {p : (α × α) × γ → Prop} :
    (∀ᶠ x in (f ×ᶠ f) ×ᶠ g, p x) → ∀ᶠ x : α × γ in f ×ᶠ g, p ((x.1, x.1), x.2) := by
  intro h
  obtain ⟨t, ht, s, hs, hst⟩ := eventually_prod_iff.1 h
  refine' (ht.diag_of_prod.prod_mk hs).mono fun x hx => by simp only [hst hx.1 hx.2, Prod.mk.eta]
#align filter.eventually.diag_of_prod_left Filter.Eventually.diag_of_prod_left

theorem Eventually.diag_of_prod_right {f : Filter α} {g : Filter γ} {p : α × γ × γ → Prop} :
    (∀ᶠ x in f ×ᶠ (g ×ᶠ g), p x) → ∀ᶠ x : α × γ in f ×ᶠ g, p (x.1, x.2, x.2) := by
  intro h
  obtain ⟨t, ht, s, hs, hst⟩ := eventually_prod_iff.1 h
  refine' (ht.prod_mk hs.diag_of_prod).mono fun x hx => by simp only [hst hx.1 hx.2, Prod.mk.eta]
#align filter.eventually.diag_of_prod_right Filter.Eventually.diag_of_prod_right

theorem tendsto_diag : Tendsto (fun i => (i, i)) f (f ×ᶠ f) :=
  tendsto_iff_eventually.mpr fun _ hpr => hpr.diag_of_prod
#align filter.tendsto_diag Filter.tendsto_diag

theorem prod_infᵢ_left [Nonempty ι] {f : ι → Filter α} {g : Filter β} :
    (⨅ i, f i) ×ᶠ g = ⨅ i, f i ×ᶠ g := by
  rw [Filter.prod, comap_infᵢ, infᵢ_inf]
  simp only [Filter.prod, eq_self_iff_true]
#align filter.prod_infi_left Filter.prod_infᵢ_left

theorem prod_infᵢ_right [Nonempty ι] {f : Filter α} {g : ι → Filter β} :
    (f ×ᶠ ⨅ i, g i) = ⨅ i, f ×ᶠ g i := by
  rw [Filter.prod, comap_infᵢ, inf_infᵢ]
  simp only [Filter.prod, eq_self_iff_true]
#align filter.prod_infi_right Filter.prod_infᵢ_right

@[mono]
theorem prod_mono {f₁ f₂ : Filter α} {g₁ g₂ : Filter β} (hf : f₁ ≤ f₂) (hg : g₁ ≤ g₂) :
    f₁ ×ᶠ g₁ ≤ f₂ ×ᶠ g₂ :=
  inf_le_inf (comap_mono hf) (comap_mono hg)
#align filter.prod_mono Filter.prod_mono

theorem prod_mono_left (g : Filter β) {f₁ f₂ : Filter α} (hf : f₁ ≤ f₂) : f₁ ×ᶠ g ≤ f₂ ×ᶠ g :=
  Filter.prod_mono hf rfl.le
#align filter.prod_mono_left Filter.prod_mono_left

theorem prod_mono_right (f : Filter α) {g₁ g₂ : Filter β} (hf : g₁ ≤ g₂) : f ×ᶠ g₁ ≤ f ×ᶠ g₂ :=
  Filter.prod_mono rfl.le hf
#align filter.prod_mono_right Filter.prod_mono_right

theorem prod_comap_comap_eq.{u, v, w, x} {α₁ : Type u} {α₂ : Type v} {β₁ : Type w} {β₂ : Type x}
    {f₁ : Filter α₁} {f₂ : Filter α₂} {m₁ : β₁ → α₁} {m₂ : β₂ → α₂} :
    comap m₁ f₁ ×ᶠ comap m₂ f₂ = comap (fun p : β₁ × β₂ => (m₁ p.1, m₂ p.2)) (f₁ ×ᶠ f₂) := by
  simp only [Filter.prod, comap_comap, comap_inf, (· ∘ ·)]
#align filter.prod_comap_comap_eq Filter.prod_comap_comap_eq

theorem prod_comm : f ×ᶠ g = map (fun p : β × α => (p.2, p.1)) (g ×ᶠ f) := by
  rw [prod_comm', ← map_swap_eq_comap_swap]
  rfl
#align filter.prod_comm Filter.prod_comm

theorem mem_prod_iff_left {s : Set (α × β)} {f : Filter α} {g : Filter β} :
    s ∈ f ×ᶠ g ↔ ∃ t ∈ f, ∀ᶠ y in g, ∀ x ∈ t, (x, y) ∈ s := by
  simp only [mem_prod_iff, prod_subset_iff]
  refine exists_congr fun _ => Iff.rfl.and <| Iff.trans ?_ exists_mem_subset_iff
  exact exists_congr fun _ => Iff.rfl.and forall₂_swap

theorem mem_prod_iff_right {s : Set (α × β)} {f : Filter α} {g : Filter β} :
    s ∈ f ×ᶠ g ↔ ∃ t ∈ g, ∀ᶠ x in f, ∀ y ∈ t, (x, y) ∈ s := by
  rw [prod_comm, mem_map, mem_prod_iff_left]; rfl

@[simp]
theorem map_fst_prod (f : Filter α) (g : Filter β) [NeBot g] : map Prod.fst (f ×ᶠ g) = f := by
  ext s
  simp only [mem_map, mem_prod_iff_left, mem_preimage, eventually_const, ← subset_def,
    exists_mem_subset_iff]
#align filter.map_fst_prod Filter.map_fst_prod

@[simp]
theorem map_snd_prod (f : Filter α) (g : Filter β) [NeBot f] : map Prod.snd (f ×ᶠ g) = g := by
  rw [prod_comm, map_map]; apply map_fst_prod
#align filter.map_snd_prod Filter.map_snd_prod

@[simp]
theorem prod_le_prod {f₁ f₂ : Filter α} {g₁ g₂ : Filter β} [NeBot f₁] [NeBot g₁] :
    f₁ ×ᶠ g₁ ≤ f₂ ×ᶠ g₂ ↔ f₁ ≤ f₂ ∧ g₁ ≤ g₂ :=
  ⟨fun h =>
    ⟨map_fst_prod f₁ g₁ ▸ tendsto_fst.mono_left h, map_snd_prod f₁ g₁ ▸ tendsto_snd.mono_left h⟩,
    fun h => prod_mono h.1 h.2⟩
#align filter.prod_le_prod Filter.prod_le_prod

@[simp]
theorem prod_inj {f₁ f₂ : Filter α} {g₁ g₂ : Filter β} [NeBot f₁] [NeBot g₁] :
    f₁ ×ᶠ g₁ = f₂ ×ᶠ g₂ ↔ f₁ = f₂ ∧ g₁ = g₂ := by
  refine' ⟨fun h => _, fun h => h.1 ▸ h.2 ▸ rfl⟩
  have hle : f₁ ≤ f₂ ∧ g₁ ≤ g₂ := prod_le_prod.1 h.le
  haveI := neBot_of_le hle.1; haveI := neBot_of_le hle.2
  exact ⟨hle.1.antisymm <| (prod_le_prod.1 h.ge).1, hle.2.antisymm <| (prod_le_prod.1 h.ge).2⟩
#align filter.prod_inj Filter.prod_inj

theorem eventually_swap_iff {p : α × β → Prop} :
    (∀ᶠ x : α × β in f ×ᶠ g, p x) ↔ ∀ᶠ y : β × α in g ×ᶠ f, p y.swap := by
  rw [prod_comm]; rfl

#align filter.eventually_swap_iff Filter.eventually_swap_iff

theorem prod_assoc (f : Filter α) (g : Filter β) (h : Filter γ) :
    map (Equiv.prodAssoc α β γ) ((f ×ᶠ g) ×ᶠ h) = f ×ᶠ (g ×ᶠ h) := by
  simp_rw [← comap_equiv_symm, Filter.prod, comap_inf, comap_comap, inf_assoc, (· ∘ ·),
    Equiv.prodAssoc_symm_apply]
#align filter.prod_assoc Filter.prod_assoc

theorem prod_assoc_symm (f : Filter α) (g : Filter β) (h : Filter γ) :
    map (Equiv.prodAssoc α β γ).symm (f ×ᶠ (g ×ᶠ h)) = (f ×ᶠ g) ×ᶠ h := by
  simp_rw [map_equiv_symm, Filter.prod, comap_inf, comap_comap, inf_assoc, Function.comp,
    Equiv.prodAssoc_apply]
#align filter.prod_assoc_symm Filter.prod_assoc_symm

theorem tendsto_prodAssoc {f : Filter α} {g : Filter β} {h : Filter γ} :
    Tendsto (Equiv.prodAssoc α β γ) ((f ×ᶠ g) ×ᶠ h) (f ×ᶠ (g ×ᶠ h)) :=
  (prod_assoc f g h).le
#align filter.tendsto_prod_assoc Filter.tendsto_prodAssoc

theorem tendsto_prodAssoc_symm {f : Filter α} {g : Filter β} {h : Filter γ} :
    Tendsto (Equiv.prodAssoc α β γ).symm (f ×ᶠ (g ×ᶠ h)) ((f ×ᶠ g) ×ᶠ h) :=
  (prod_assoc_symm f g h).le
#align filter.tendsto_prod_assoc_symm Filter.tendsto_prodAssoc_symm

/-- A useful lemma when dealing with uniformities. -/
theorem map_swap4_prod {f : Filter α} {g : Filter β} {h : Filter γ} {k : Filter δ} :
    map (fun p : (α × β) × γ × δ => ((p.1.1, p.2.1), (p.1.2, p.2.2))) ((f ×ᶠ g) ×ᶠ (h ×ᶠ k)) =
      (f ×ᶠ h) ×ᶠ (g ×ᶠ k) :=
  by simp_rw [map_swap4_eq_comap, Filter.prod, comap_inf, comap_comap]; ac_rfl
#align filter.map_swap4_prod Filter.map_swap4_prod

theorem tendsto_swap4_prod {f : Filter α} {g : Filter β} {h : Filter γ} {k : Filter δ} :
    Tendsto (fun p : (α × β) × γ × δ => ((p.1.1, p.2.1), (p.1.2, p.2.2))) ((f ×ᶠ g) ×ᶠ (h ×ᶠ k))
      ((f ×ᶠ h) ×ᶠ (g ×ᶠ k)) :=
  map_swap4_prod.le
#align filter.tendsto_swap4_prod Filter.tendsto_swap4_prod

theorem prod_map_map_eq.{u, v, w, x} {α₁ : Type u} {α₂ : Type v} {β₁ : Type w} {β₂ : Type x}
    {f₁ : Filter α₁} {f₂ : Filter α₂} {m₁ : α₁ → β₁} {m₂ : α₂ → β₂} :
    map m₁ f₁ ×ᶠ map m₂ f₂ = map (fun p : α₁ × α₂ => (m₁ p.1, m₂ p.2)) (f₁ ×ᶠ f₂) :=
  le_antisymm
    (fun s hs =>
      let ⟨s₁, hs₁, s₂, hs₂, h⟩ := mem_prod_iff.mp hs
      mem_of_superset (prod_mem_prod (image_mem_map hs₁) (image_mem_map hs₂)) <|
        by rwa [prod_image_image_eq, image_subset_iff])
    ((tendsto_map.comp tendsto_fst).prod_mk (tendsto_map.comp tendsto_snd))
#align filter.prod_map_map_eq Filter.prod_map_map_eq

theorem prod_map_map_eq' {α₁ : Type _} {α₂ : Type _} {β₁ : Type _} {β₂ : Type _} (f : α₁ → α₂)
    (g : β₁ → β₂) (F : Filter α₁) (G : Filter β₁) :
    map f F ×ᶠ map g G = map (Prod.map f g) (F ×ᶠ G) :=
  prod_map_map_eq
#align filter.prod_map_map_eq' Filter.prod_map_map_eq'

theorem prod_map_left (f : α → β) (F : Filter α) (G : Filter γ) :
    map f F ×ᶠ G = map (Prod.map f id) (F ×ᶠ G) := by
  rw [← prod_map_map_eq', map_id]

theorem prod_map_right (f : β → γ) (F : Filter α) (G : Filter β) :
    F ×ᶠ map f G = map (Prod.map id f) (F ×ᶠ G) := by
  rw [← prod_map_map_eq', map_id]

theorem le_prod_map_fst_snd {f : Filter (α × β)} : f ≤ map Prod.fst f ×ᶠ map Prod.snd f :=
  le_inf le_comap_map le_comap_map
#align filter.le_prod_map_fst_snd Filter.le_prod_map_fst_snd

theorem Tendsto.prod_map {δ : Type _} {f : α → γ} {g : β → δ} {a : Filter α} {b : Filter β}
    {c : Filter γ} {d : Filter δ} (hf : Tendsto f a c) (hg : Tendsto g b d) :
    Tendsto (Prod.map f g) (a ×ᶠ b) (c ×ᶠ d) := by
  erw [Tendsto, ← prod_map_map_eq]
  exact Filter.prod_mono hf hg
#align filter.tendsto.prod_map Filter.Tendsto.prod_map

protected theorem map_prod (m : α × β → γ) (f : Filter α) (g : Filter β) :
    map m (f ×ᶠ g) = (f.map fun a b => m (a, b)).seq g := by
  simp [Filter.ext_iff, mem_prod_iff, mem_map_seq_iff]
  intro s
  constructor
  exact fun ⟨t, ht, s, hs, h⟩ => ⟨s, hs, t, ht, fun x hx y hy => @h ⟨x, y⟩ ⟨hx, hy⟩⟩
  exact fun ⟨s, hs, t, ht, h⟩ => ⟨t, ht, s, hs, fun ⟨x, y⟩ ⟨hx, hy⟩ => h x hx y hy⟩
#align filter.map_prod Filter.map_prod

theorem prod_eq {f : Filter α} {g : Filter β} : f ×ᶠ g = (f.map Prod.mk).seq g := by
  have h := f.map_prod id g
  rwa [map_id] at h
#align filter.prod_eq Filter.prod_eq

theorem prod_inf_prod {f₁ f₂ : Filter α} {g₁ g₂ : Filter β} :
    (f₁ ×ᶠ g₁) ⊓ (f₂ ×ᶠ g₂) = (f₁ ⊓ f₂) ×ᶠ (g₁ ⊓ g₂) := by
  simp only [Filter.prod, comap_inf, inf_comm, inf_assoc, inf_left_comm]
#align filter.prod_inf_prod Filter.prod_inf_prod

theorem inf_prod {f₁ f₂ : Filter α} {g : Filter β} : (f₁ ⊓ f₂) ×ᶠ g = (f₁ ×ᶠ g) ⊓ (f₂ ×ᶠ g) := by
  rw [prod_inf_prod, inf_idem]

theorem prod_inf {f : Filter α} {g₁ g₂ : Filter β} : f ×ᶠ (g₁ ⊓ g₂) = (f ×ᶠ g₁) ⊓ (f ×ᶠ g₂) := by
  rw [prod_inf_prod, inf_idem]

@[simp]
theorem prod_principal_principal {s : Set α} {t : Set β} : 𝓟 s ×ᶠ 𝓟 t = 𝓟 (s ×ˢ t) := by
  simp only [Filter.prod, comap_principal, principal_eq_iff_eq, comap_principal, inf_principal]; rfl
#align filter.prod_principal_principal Filter.prod_principal_principal

@[simp]
theorem pure_prod {a : α} {f : Filter β} : pure a ×ᶠ f = map (Prod.mk a) f := by
  rw [prod_eq, map_pure, pure_seq_eq_map]
#align filter.pure_prod Filter.pure_prod

theorem map_pure_prod (f : α → β → γ) (a : α) (B : Filter β) :
    map (Function.uncurry f) (pure a ×ᶠ B) = map (f a) B := by
  rw [Filter.pure_prod]; rfl
#align filter.map_pure_prod Filter.map_pure_prod

@[simp]
theorem prod_pure {f : Filter α} {b : β} : f ×ᶠ pure b = map (fun a => (a, b)) f := by
  rw [prod_eq, seq_pure, map_map]; rfl
#align filter.prod_pure Filter.prod_pure

theorem prod_pure_pure {a : α} {b : β} : pure a ×ᶠ pure b = pure (a, b) := by simp
#align filter.prod_pure_pure Filter.prod_pure_pure

theorem prod_eq_bot {f : Filter α} {g : Filter β} : f ×ᶠ g = ⊥ ↔ f = ⊥ ∨ g = ⊥ := by
  simp_rw [← empty_mem_iff_bot, mem_prod_iff, subset_empty_iff, prod_eq_empty_iff, ← exists_prop,
    Subtype.exists', exists_or, exists_const, Subtype.exists, exists_prop, exists_eq_right]
#align filter.prod_eq_bot Filter.prod_eq_bot

@[simp] theorem prod_bot {f : Filter α} : f ×ᶠ (⊥ : Filter β) = ⊥ := prod_eq_bot.2 <| Or.inr rfl
#align filter.prod_bot Filter.prod_bot

@[simp] theorem bot_prod {g : Filter β} : (⊥ : Filter α) ×ᶠ g = ⊥ := prod_eq_bot.2 <| Or.inl rfl
#align filter.bot_prod Filter.bot_prod

theorem prod_neBot {f : Filter α} {g : Filter β} : NeBot (f ×ᶠ g) ↔ NeBot f ∧ NeBot g := by
  simp only [neBot_iff, Ne, prod_eq_bot, not_or]
#align filter.prod_ne_bot Filter.prod_neBot

theorem NeBot.prod {f : Filter α} {g : Filter β} (hf : NeBot f) (hg : NeBot g) : NeBot (f ×ᶠ g) :=
  prod_neBot.2 ⟨hf, hg⟩
#align filter.ne_bot.prod Filter.NeBot.prod

instance prod_neBot' {f : Filter α} {g : Filter β} [hf : NeBot f] [hg : NeBot g] :
    NeBot (f ×ᶠ g) :=
  hf.prod hg
#align filter.prod_ne_bot' Filter.prod_neBot'

theorem tendsto_prod_iff {f : α × β → γ} {x : Filter α} {y : Filter β} {z : Filter γ} :
    Tendsto f (x ×ᶠ y) z ↔ ∀ W ∈ z, ∃ U ∈ x, ∃ V ∈ y, ∀ x y, x ∈ U → y ∈ V → f (x, y) ∈ W :=
  by simp only [tendsto_def, mem_prod_iff, prod_sub_preimage_iff, exists_prop, iff_self_iff]
#align filter.tendsto_prod_iff Filter.tendsto_prod_iff

theorem tendsto_prod_iff' {f : Filter α} {g : Filter β} {g' : Filter γ} {s : α → β × γ} :
    Tendsto s f (g ×ᶠ g') ↔ Tendsto (fun n => (s n).1) f g ∧ Tendsto (fun n => (s n).2) f g' := by
  unfold Filter.prod
  simp only [tendsto_inf, tendsto_comap_iff, (· ∘ ·)]
#align filter.tendsto_prod_iff' Filter.tendsto_prod_iff'

end Prod

/-! ### Coproducts of filters -/

section Coprod

variable {f : Filter α} {g : Filter β}

/-- Coproduct of filters. -/
protected def coprod (f : Filter α) (g : Filter β) : Filter (α × β) :=
  f.comap Prod.fst ⊔ g.comap Prod.snd
#align filter.coprod Filter.coprod

theorem mem_coprod_iff {s : Set (α × β)} {f : Filter α} {g : Filter β} :
    s ∈ f.coprod g ↔ (∃ t₁ ∈ f, Prod.fst ⁻¹' t₁ ⊆ s) ∧ ∃ t₂ ∈ g, Prod.snd ⁻¹' t₂ ⊆ s := by
  simp [Filter.coprod]
#align filter.mem_coprod_iff Filter.mem_coprod_iff

@[simp]
theorem bot_coprod (l : Filter β) : (⊥ : Filter α).coprod l = comap Prod.snd l := by
  simp [Filter.coprod]
#align filter.bot_coprod Filter.bot_coprod

@[simp]
theorem coprod_bot (l : Filter α) : l.coprod (⊥ : Filter β) = comap Prod.fst l := by
  simp [Filter.coprod]
#align filter.coprod_bot Filter.coprod_bot

theorem bot_coprod_bot : (⊥ : Filter α).coprod (⊥ : Filter β) = ⊥ := by simp
#align filter.bot_coprod_bot Filter.bot_coprod_bot

theorem compl_mem_coprod {s : Set (α × β)} {la : Filter α} {lb : Filter β} :
    sᶜ ∈ la.coprod lb ↔ (Prod.fst '' s)ᶜ ∈ la ∧ (Prod.snd '' s)ᶜ ∈ lb := by
  simp only [Filter.coprod, mem_sup, compl_mem_comap]
#align filter.compl_mem_coprod Filter.compl_mem_coprod

@[mono]
theorem coprod_mono {f₁ f₂ : Filter α} {g₁ g₂ : Filter β} (hf : f₁ ≤ f₂) (hg : g₁ ≤ g₂) :
    f₁.coprod g₁ ≤ f₂.coprod g₂ :=
  sup_le_sup (comap_mono hf) (comap_mono hg)
#align filter.coprod_mono Filter.coprod_mono

theorem coprod_neBot_iff : (f.coprod g).NeBot ↔ f.NeBot ∧ Nonempty β ∨ Nonempty α ∧ g.NeBot := by
  simp [Filter.coprod]
#align filter.coprod_ne_bot_iff Filter.coprod_neBot_iff

@[instance]
theorem coprod_neBot_left [NeBot f] [Nonempty β] : (f.coprod g).NeBot :=
  coprod_neBot_iff.2 (Or.inl ⟨‹_›, ‹_›⟩)
#align filter.coprod_ne_bot_left Filter.coprod_neBot_left

@[instance]
theorem coprod_neBot_right [NeBot g] [Nonempty α] : (f.coprod g).NeBot :=
  coprod_neBot_iff.2 (Or.inr ⟨‹_›, ‹_›⟩)
#align filter.coprod_ne_bot_right Filter.coprod_neBot_right

theorem principal_coprod_principal (s : Set α) (t : Set β) : (𝓟 s).coprod (𝓟 t) = 𝓟 ((sᶜ ×ˢ tᶜ)ᶜ) :=
  by
  rw [Filter.coprod, comap_principal, comap_principal, sup_principal, Set.prod_eq, compl_inter,
    preimage_compl, preimage_compl, compl_compl, compl_compl]
#align filter.principal_coprod_principal Filter.principal_coprod_principal

-- this inequality can be strict; see `map_const_principal_coprod_map_id_principal` and
-- `map_prod_map_const_id_principal_coprod_principal` below.
theorem map_prod_map_coprod_le.{u, v, w, x} {α₁ : Type u} {α₂ : Type v} {β₁ : Type w} {β₂ : Type x}
    {f₁ : Filter α₁} {f₂ : Filter α₂} {m₁ : α₁ → β₁} {m₂ : α₂ → β₂} :
    map (Prod.map m₁ m₂) (f₁.coprod f₂) ≤ (map m₁ f₁).coprod (map m₂ f₂) := by
  intro s
  simp only [mem_map, mem_coprod_iff]
  rintro ⟨⟨u₁, hu₁, h₁⟩, u₂, hu₂, h₂⟩
  refine' ⟨⟨m₁ ⁻¹' u₁, hu₁, fun _ hx => h₁ _⟩, ⟨m₂ ⁻¹' u₂, hu₂, fun _ hx => h₂ _⟩⟩ <;> convert hx
#align filter.map_prod_map_coprod_le Filter.map_prod_map_coprod_le

/-- Characterization of the coproduct of the `filter.map`s of two principal filters `𝓟 {a}` and
`𝓟 {i}`, the first under the constant function `λ a, b` and the second under the identity function.
Together with the next lemma, `map_prod_map_const_id_principal_coprod_principal`, this provides an
example showing that the inequality in the lemma `map_prod_map_coprod_le` can be strict. -/
theorem map_const_principal_coprod_map_id_principal {α β ι : Type _} (a : α) (b : β) (i : ι) :
    (map (fun _ => b) (𝓟 {a})).coprod (map id (𝓟 {i})) =
      𝓟 ((({b} : Set β) ×ˢ univ) ∪ (univ ×ˢ ({i} : Set ι))) := by
  simp only [map_principal, Filter.coprod, comap_principal, sup_principal, image_singleton,
    image_id, prod_univ, univ_prod, id]
#align filter.map_const_principal_coprod_map_id_principal Filter.map_const_principal_coprod_map_id_principal

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- Characterization of the `filter.map` of the coproduct of two principal filters `𝓟 {a}` and
`𝓟 {i}`, under the `prod.map` of two functions, respectively the constant function `λ a, b` and the
identity function.  Together with the previous lemma,
`map_const_principal_coprod_map_id_principal`, this provides an example showing that the inequality
in the lemma `map_prod_map_coprod_le` can be strict. -/
theorem map_prod_map_const_id_principal_coprod_principal {α β ι : Type _} (a : α) (b : β) (i : ι) :
    map (Prod.map (fun _ : α => b) id) ((𝓟 {a}).coprod (𝓟 {i})) =
      𝓟 (({b} : Set β) ×ˢ (univ : Set ι)) := by
  rw [principal_coprod_principal, map_principal]
  congr
  ext ⟨b', i'⟩
  constructor
  · rintro ⟨⟨a'', i''⟩, _, h₂, h₃⟩
    simp
  · rintro ⟨h₁, _⟩
    use (a, i')
    simpa using h₁.symm
#align filter.map_prod_map_const_id_principal_coprod_principal Filter.map_prod_map_const_id_principal_coprod_principal

theorem Tendsto.prod_map_coprod {δ : Type _} {f : α → γ} {g : β → δ} {a : Filter α} {b : Filter β}
    {c : Filter γ} {d : Filter δ} (hf : Tendsto f a c) (hg : Tendsto g b d) :
    Tendsto (Prod.map f g) (a.coprod b) (c.coprod d) :=
  map_prod_map_coprod_le.trans (coprod_mono hf hg)
#align filter.tendsto.prod_map_coprod Filter.Tendsto.prod_map_coprod

end Coprod

end Filter


-- Las_subsucesiones_tienen_el_mismo_limite_que_la_sucesion.lean
-- Las subsucesiones tienen el mismo límite que la sucesión
-- José A. Alonso Jiménez
-- Sevilla, 1 de septiembre de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Para extraer una subsucesión se aplica una función de extracción que
-- conserva el orden; por ejemplo, la subsucesión
--    uₒ, u₂, u₄, u₆, ...
-- se ha obtenido con la función de extracción φ tal que φ(n) = 2*n.
--
-- En Lean, se puede definir que φ es una función de extracción por
--    def extraccion (φ : ℕ → ℕ) :=
--      ∀ n m, n < m → φ n < φ m
-- que v es una subsucesión de u por
--    def subsucesion (v u : ℕ → ℝ) :=
--      ∃ φ, extraccion φ ∧ v = u ∘ φ
-- y que a es un límite de u por
--    def limite (u : ℕ → ℝ) (a : ℝ) :=
--      ∀ ε > 0, ∃ N, ∀ k ≥ N, |u k - a| < ε
--
-- Demostrar que las subsucesiones de una sucesión convergente tienen el
-- mismo límite que la sucesión.
-- ---------------------------------------------------------------------

import data.real.basic
open nat

variables {u v : ℕ → ℝ}
variable  {a : ℝ}
variable  {φ : ℕ → ℕ}

def extraccion (φ : ℕ → ℕ):=
  ∀ n m, n < m → φ n < φ m

def subsucesion (v u : ℕ → ℝ) :=
  ∃ φ, extraccion φ ∧ v = u ∘ φ

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

def limite (u : ℕ → ℝ) (a : ℝ) :=
  ∀ ε > 0, ∃ N, ∀ k ≥ N, |u k - a| < ε

-- En la demostración se usará el siguiente lema.
lemma aux
  (h : extraccion φ)
  : ∀ n, n ≤ φ n :=
begin
  intro n,
  induction n with m HI,
  { exact nat.zero_le (φ 0), },
  { apply nat.succ_le_of_lt,
    calc m ≤ φ m        : HI
       ... < φ (succ m) : h m (m+1) (lt_add_one m), },
end

-- 1ª demostración
example
  (hv : subsucesion v u)
  (ha : limite u a)
  : limite v a :=
begin
  unfold limite,
  intros ε hε,
  unfold limite at ha,
  cases ha ε hε with N hN,
  use N,
  intros n hn,
  unfold subsucesion at hv,
  rcases hv with ⟨φ, hφ1, hφ2⟩,
  rw hφ2,
  apply hN,
  apply le_trans hn,
  exact aux hφ1 n,
end

-- 2ª demostración
example
  (hv : subsucesion v u)
  (ha : limite u a)
  : limite v a :=
begin
  intros ε hε,
  cases ha ε hε with N hN,
  use N,
  intros n hn,
  rcases hv with ⟨φ, hφ1, hφ2⟩,
  rw hφ2,
  apply hN,
  exact le_trans hn (aux hφ1 n),
end

-- 3ª demostración
example
  (hv : subsucesion v u)
  (ha : limite u a)
  : limite v a :=
begin
  intros ε hε,
  cases ha ε hε with N hN,
  use N,
  intros n hn,
  rcases hv with ⟨φ, hφ1, hφ2⟩,
  rw hφ2,
  exact hN (φ n) (le_trans hn (aux hφ1 n)),
end

-- 4ª demostración
example
  (hv : subsucesion v u)
  (ha : limite u a)
  : limite v a :=
begin
  intros ε hε,
  cases ha ε hε with N hN,
  rcases hv with ⟨φ, hφ1, hφ2⟩,
  rw hφ2,
  use N,
  exact λ n hn, hN (φ n) (le_trans hn (aux hφ1 n)),
end

-- 5ª demostración
example
  (hv : subsucesion v u)
  (ha : limite u a)
  : limite v a :=
begin
  intros ε hε,
  cases ha ε hε with N hN,
  rcases hv with ⟨φ, hφ1, hφ2⟩,
  rw hφ2,
  exact ⟨N, λ n hn, hN (φ n) (le_trans hn (aux hφ1 n))⟩,
end


import data.nat.parity
import tactic.ring
open nat

def sum' : ℕ → ℕ
  | 0 := 0
  | (n+1) := n + 1 + sum' n

lemma trinum_doubled :
  ∀ n : ℕ, n * n + n = 2 * sum' n :=
begin
  intro n,
  induction n with n ih,
  {
    simp,
    unfold sum',
  },
  {
    dunfold sum',
    simp [succ_eq_add_one, add_mul, mul_add, ←ih],
    ring_nf,
  }
end

lemma trinum :
  ∀ n : ℕ, n * (n + 1) / 2 = sum' n :=
begin
  intro n,
  have h : even (n * (n + 1)) := by apply even_mul_succ_self,
  rw nat.div_eq_iff_eq_mul_left,
  rw [mul_add, mul_one, trinum_doubled, mul_comm],
  simp,
  exact even_iff_two_dvd.mp h,
end


import data.complex.basic
import .modular_group
import tactic.linarith
import .determinants
import group_theory.group_action

open complex

def upper_half_space := {z : ℂ | z.im > 0}
local notation `ℍ` := upper_half_space
local notation `Mat` := integral_matrices_with_determinant

instance upper_half_space.to_complex : has_coe ℍ ℂ := ⟨λ z, z.val⟩

instance nat.to_fintype : has_coe ℕ Type := ⟨λ n, fin n⟩

section

open matrix

noncomputable def mat2_complex (M : matrix (fin 2) (fin 2) ℝ) (z : ℂ) : ℂ :=
(M 0 0 * z + M 0 1) / (M 1 0 * z + M 1 1)

instance : is_monoid_action mat2_complex :=
{ one := λ z, by simp [mat2_complex, of_real_zero],
  mul := λ A B z, by { simp [mat2_complex], sorry } }

variables {M : matrix (fin 2) (fin 2) ℝ} {z : ℂ}

-- theorem mat2_complex.preserve_ℍ (hM : det M > 0) (hz : z ∈ ℍ) :

end

noncomputable def «Möbius_transform» (a b c d : ℝ) (z : ℂ) : ℂ :=
(↑a * z + b) / (c * z + d)

theorem preserve_ℍ.aux {a b c d : ℝ} (det : a * d - b * c > 0) {z : ℂ} (hz : z ∈ ℍ) :
  ↑c * z + ↑d ≠ 0 :=
begin
  intro H,
  have H1 : c = 0 ∨ z.im = 0, by simpa using congr_arg complex.im H,
  cases H1,
  { simp [H1, of_real_zero] at H,
    simp [H, H1] at det,
    linarith },
  change z.im > 0 at hz,
  linarith,
end

lemma preserve_ℍ {a b c d : ℝ} (det : a * d - b * c > 0) (z : ℂ) (h : z.im > 0) :
(«Möbius_transform» a b c d z).im > 0 :=
calc _ = (a * d - b * c) * z.im * (norm_sq (↑c * z + ↑d))⁻¹ :
  by simp [«Möbius_transform», div_eq_mul_inv, -add_comm]; ring
   ... > 0 : mul_pos (mul_pos det h) (inv_pos (norm_sq_pos.2 (preserve_ℍ.aux det h)))

noncomputable def M_trans {m : ℤ} (hm : 0 < m) (A : Mat m) (z : ℍ) : ℍ :=
begin
  refine ⟨«Möbius_transform» A.a A.b A.c A.d z, preserve_ℍ _ _ z.2⟩,
  show 0 < (A.a * A.d - A.b * A.c : ℝ),
  rwa [← int.cast_mul, ← int.cast_mul, ← int.cast_sub, ← int.cast_zero, int.cast_lt, A.det],
end

theorem SL2Z_H.aux {a b c d : ℤ} (h : a * d - b * c = 1) : (a : ℝ) * d - b * c > 0 :=
by convert zero_lt_one; simpa using congr_arg (coe : ℤ → ℝ) h

noncomputable def SL2Z_H : SL2Z → ℍ → ℍ :=
λ M z, ⟨«Möbius_transform» M.a M.b M.c M.d z, preserve_ℍ (SL2Z_H.aux M.det) z z.property⟩

@[simp] lemma SL2Z_H_val (A z) : (SL2Z_H A z).val = ((A.a:ℝ) * z.1 + (A.b:ℝ)) / ((A.c:ℝ) * z.1 + (A.d:ℝ)) := rfl

noncomputable instance : is_group_action SL2Z_H :=
{ mul := λ ⟨a1, b1, c1, d1, H1⟩ ⟨a2, b2, c2, d2, H2⟩ ⟨z, hz⟩,
    begin
      apply subtype.eq,
      simp only [SL2Z_H_val, SL2Z_mul_a, SL2Z_mul_b, SL2Z_mul_c, SL2Z_mul_d,
        of_real_add, of_real_mul, int.cast_add, int.cast_mul],
      have H3 := preserve_ℍ.aux (SL2Z_H.aux H2) hz,
      have H4 := preserve_ℍ.aux (SL2Z_H.aux H1)
        (preserve_ℍ (SL2Z_H.aux H2) z hz),
      simp only [«Möbius_transform»] at H3 H4,
      rw ← mul_div_mul_right _ H4 H3,
      conv { to_rhs,
        rw [add_mul, add_mul, mul_assoc, mul_assoc],
        rw [div_mul_cancel _ H3] },
      congr' 1; simp only [add_mul, mul_add, mul_assoc, add_left_comm, add_assoc]
    end,
  one := λ ⟨z, hz⟩, subtype.eq $ by simp [of_real_zero] }


import ..src.linear_combination

set_option trace.app_builder true

-- Simple Cases with ℤ and two or less equations

example (x y : ℤ) (h1 : 3*x + 2*y = 10):
  3*x + 2*y = 10 :=
by linear_combination [h1] [1]

example (x y : ℤ) (h1 : x + 2 = -3) (h2 : y = 10) :
  2*x + 4 = -6 :=
by linear_combination [h1] [2]

example (x y : ℤ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y = -2*y + 1 :=
by linear_combination [h1, h2] [1, -2]

example (x y : ℤ) (h1 : x + 2 = -3) (h2 : y = 10) :
  2*x + 4 - y = -16 :=
by linear_combination [h1, h2] [2, -1]

example (x y : ℤ) (h1 : x + 2 = -3) (h2 : y = 10) :
  -y + 2*x + 4 = -16 :=
by linear_combination [h2, h1] [-1, 2]

example (x y : ℤ) (h1 : 3*x + 2*y = 10) (h2 : 2*x + 5*y = 3) :
  11*y = -11 :=
by linear_combination [h1, h2] [-2, 3]

example (x y : ℤ) (h1 : 3*x + 2*y = 10) (h2 : 2*x + 5*y = 3) :
  -11*y = 11 :=
by linear_combination [h1, h2] [2, -3]

example (x y : ℤ) (h1 : 3*x + 2*y = 10) (h2 : 2*x + 5*y = 3) :
  -11*y = 11 + 1 - 1 :=
by linear_combination [h1, h2] [2, -3]

example (x y : ℤ) (h1 : 10 = 3*x + 2*y) (h2 : 3 = 2*x + 5*y) :
  11 + 1 - 1 = -11*y :=
by linear_combination [h1, h2] [2, -3]

-- More complicated cases with two equations

example (x y : ℚ) (h1 : 3*x + 2*y = 10) (h2 : 2*x + 5*y = 3) :
  -11*y + 1 = 11 + 1 :=
by linear_combination [h1, h2] [2, -3]

example (a b : ℝ) (ha : 2*a = 4) (hab : 2*b = a - b) :
  b = 2 / 3 :=
by linear_combination [ha, hab] [1/6, 1/3]

example (a b : ℝ) (ha : 2*a = 4) (hab : 2*b = a - b) (hignore : 3 = a + b) :
  b = 2 / 3 :=
by linear_combination [ha, hab, hignore] [1/6, 1/3, 0]

-- Cases with more than 2 equations

example (x y z : ℝ) (ha : x + 2*y - z = 4) (hb : 2*x + y + z = -2)
    (hc : x + 2*y + z = 2) :
  -3*x - 3*y - 4*z = 2 :=
by linear_combination [ha, hb, hc] [1, -1, -2]

example (x y z : ℝ) (ha : x + 2*y - z = 4) (hb : 2*x + y + z = -2)
    (hc : x + 2*y + z = 2) :
  6*x = -10 :=
by linear_combination [ha, hb, hc] [1, 4, -3]

example (x y z : ℝ) (ha : x + 2*y - z = 4) (hb : 2*x + y + z = -2)
    (hc : x + 2*y + z = 2) :
  10 = 6*-x :=
by linear_combination [ha, hb, hc] [1, 4, -3]

example (w x y z : ℝ) (h1 : x + 2.1*y + 2*z = 2) (h2 : x + 8*z + 5*w = -6.5)
    (h3 : x + y + 5*z + 5*w = 3) :
  x + 2.2*y + 2*z - 5*w = -8.5 :=
by linear_combination [h1, h2, h3] [2, 1, -2]

example (a b c d : ℚ) (h1 : a = 4) (h2 : 3 = b) (h3 : c*3 = d) (h4 : -d = a) :
  2*a - 3 + 9*c + 3*d = 8 - b + 3*d - 3*a :=
by linear_combination [h1, h2, h3, h4] [2, -1, 3, -3]

example (a b c d : ℚ) (h1 : a = 4) (h2 : 3 = b) (h3 : c*3 = d) (h4 : -d = a) :
  6 - 3*c + 3*a + 3*d = 2*b - d + 12 - 3*a :=
by linear_combination [h2, h3, h1, h4] [2, -1, 3, -3]

-- Cases that should fail

-- This should fail because there are no hypotheses given
example (x y : ℤ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y = -2*y + 1 :=
by linear_combination [] []

-- This should fail because h1 does not have a corresponding coefficient
example (x y : ℤ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y + 2*x = 1 :=
by linear_combination [h1] []

-- This should fail because h2 does not have a corresponding coefficient
example (x y : ℤ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y + 2*x = 1 :=
by linear_combination [h1, h2] [1]

-- This should fail because the first coefficient does not have a corresponding
--   equality
example (x y : ℤ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y + 2*x = 1 :=
by linear_combination [] [1]

-- This should fail because the second coefficient does not have a corresponding
--   equality
example (x y : ℤ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y + 2*x = 1 :=
by linear_combination [h1] [1, 0]

-- This should fail because the second coefficient has a different type than
--   the equations it is being combined with.  This was a design choice for the
--   sake of simplicity, but the tactic could potentially be modified to allow
--   this behavior.
example (x y : ℤ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y + 2*x = 1 :=
by linear_combination [h1, h2] [1, (0 : ℝ)]

-- This should fail because the second coefficient has a different type than
--   the equations it is being combined with.  This was a design choice for the
--   sake of simplicity, but the tactic could potentially be modified to allow
--   this behavior.
example (x y : ℤ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y + 2*x = 1 :=
by linear_combination [h1, h2] [1, (0 : ℕ)]

-- This should fail because the coefficients are incorrect.  They should instead
--   be -2 and 3, respectively.
example (x y : ℤ) (h1 : 3*x + 2*y = 10) (h2 : 2*x + 5*y = 3) :
  11*y = -11 :=
by linear_combination [h1, h2] [2, -3]

-- This should fail because the hypothesis and their coefficients are given in
--   the wrong order.  Switching h1 and h2 along with 2 and -1 would fix this
--   issue.  The tactic could potentially be modified to adapt to this
--   behavior and not depend on the order of the lists.
example (x y : ℤ) (h1 : x + 2 = -3) (h2 : y = 10) :
  - y + 2*x + 4 = -16 :=
by linear_combination [h1, h2] [2, -1]

-- This fails because the linear_combination tactic requires the equations
--   and coefficients to use a type that fulfills the add_group condition,
--   and ℕ does not.
example (a b : ℕ) (h1 : a = 3) :
  a = 3 :=
by linear_combination [h1] [(1 : ℕ)]


/-
Copyright (c) 2022 Julian Kuelshammer. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Julian Kuelshammer
-/

import data.real.basic

/-! Defining the plane ℝ²

This file defines the plane and equips it with the usual addition and scalar multiplication.

Some tactics that would be useful in the remainder:

cases : splits a structure into its component, e.g. if v is a pair of real numbers, cases v will split it
        into its first component v_x and its second component v_y.
simp : The simplifyer. Can be very good in simplifying things it is trained on. The challenge is to train it
       correctly. (Part of tidy.)
ext : The standard tactic to prove more complicated things are equal by splitting it into components. For
      example, it is already trained to prove that two functions f and g are equal if and only if they agree
      whenever applied to elements, i.e. f x = g x for all x, or that two subsets U and V of a set X are equal
      if and only if they have the same elements. (Part of tidy.)
ring : Let's you solve standard equations in rings.
-/

structure plane : Type :=
(x : ℝ) (y : ℝ)

namespace plane

/- We want to use the standard notation ℝ² for the real plane. -/
notation `ℝ²` := plane

/- We want to be able to use the ext tactic to prove when two vectors are equal by comparing their components. -/
@[ext] theorem ext {v w : ℝ²}
  (hx : v.x = w.x) (hy : v.y = w.y) : v = w :=
begin
  sorry
end

/- Now let's define the addition on ℝ² like we are used to. This is done so we can write v + w for vectors v
  and w in ℝ² without having to specify how they are actually defined. -/
instance has_add_plane : has_add ℝ² := ⟨λ v w, ⟨v.x + w.x, v.y + w.y⟩⟩

/- As I said, the simplifyer has to be trained to know things - even if they are trivial. -/
@[simp] lemma add_x (v w : ℝ²) : (v + w).x = v.x + w.x := sorry
@[simp] lemma add_y (v w : ℝ²) : (v + w).y = v.y + w.y := sorry

lemma add_assoc (u v w : ℝ²) : u + v + w = u + (v + w) :=
begin
  sorry
end

lemma add_comm (v w : ℝ²) : v + w = w + v :=
begin
  sorry
end

/- We want to give at least one example of a vector in ℝ², the zero vector. -/
def zero_vector : ℝ² := ⟨0,0⟩

/- In mathematics, it is common to write 0_V for the zero vector of a vector space V, sometimes one even
  omits the subscript V. We want to be able to use the same abuse of notation in Lean. (With the difference
  that it should always be clear to Lean what specific 0 we are talking about.)-/
instance : has_zero ℝ² := ⟨zero_vector⟩

/- Again some self-evident lemmas for the simplifyer. -/
@[simp] lemma zero_x : (0 : ℝ²).x = 0 := sorry
@[simp] lemma zero_y : (0 : ℝ²).y = 0 := sorry

@[simp] lemma add_zero (v : ℝ²) : v + 0 = v :=
begin
  sorry
end

@[simp] lemma zero_add (v : ℝ²) : 0 + v = v :=
begin
  sorry
end

/- Let's define the negation of a vector. -/
instance : has_neg ℝ² := ⟨λ v, ⟨-v.x, -v.y⟩⟩

@[simp] lemma neg_x (v : ℝ²) : (-v).x = -v.x := sorry
@[simp] lemma neg_y (v : ℝ²) : (-v).y = -v.y := sorry

@[simp] lemma add_neg_self (v : ℝ²) : v + -v = 0 :=
begin
  sorry
end

@[simp] lemma neg_add_self (v : ℝ²) : -v + v = 0 :=
begin
  sorry
end

/- We also want to define subtraction and scalar multiplication. -/
instance : has_sub ℝ² := ⟨λ v w, v + (-w)⟩

instance : has_scalar ℝ ℝ² := ⟨λ a v, ⟨a * v.x, a * v.y⟩⟩

@[simp] lemma smul_x (a : ℝ) (v : ℝ²) : (a • v).x = a * v.x := sorry
@[simp] lemma smul_y (a : ℝ) (v : ℝ²) : (a • v).y = a * v.y := sorry

lemma smul_assoc (a b : ℝ) (v : ℝ²) : (a * b) • v = a • (b • v) :=
begin
  sorry
end

@[simp] lemma one_smul (v : ℝ²) : (1 : ℝ) • v = v :=
begin
  sorry
end

@[simp] lemma smul_add (a : ℝ) (v w : ℝ²) : a • (v + w) = a • v + a • w :=
begin
  sorry
end

@[simp] lemma add_smul (a b : ℝ) (v : ℝ²) : (a + b) • v = a • v + b • v :=
begin
  sorry
end

end plane


open Classical

variable (p q r s : Prop)

example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
  λ h : p → r ∨ s =>
    Or.elim (em r)
      (
        λ hr : r =>
          Or.inl
            (
              λ hp : p => hr
            )
      )
      (
        λ hnr : ¬r =>
          Or.inr
            (
              λ hp : p =>
                Or.elim (h hp)
                  (λ hr : r => absurd hr hnr)
                  (λ hs : s => hs)
            )
      )

example : ¬(p ∧ q) → ¬p ∨ ¬q :=
  λ h : ¬(p ∧ q) =>
    Or.elim (em p)
      (
        λ hp : p =>
          Or.inr
            (λ hq : q => h ⟨hp, hq⟩)
      )
      Or.inl

example : ¬(p → q) → p ∧ ¬q :=
  λ h : ¬(p → q) =>
    ⟨
      Or.elim (em p)
        (λ hp : p => hp)
        (
          λ hnp : ¬p =>
            False.elim (h (λ hp : p => absurd hp hnp))
        )
    , λ hq : q =>
        show False
        from h (λ hp : p => hq)
    ⟩

example : (p → q) → (¬p ∨ q) :=
  λ h : p → q =>
    Or.elim (em p)
      (
        λ hp : p =>
          Or.inr (h hp)
      )
      Or.inl

example : (¬q → ¬p) → (p → q) :=
  λ h : ¬q → ¬p =>
    λ hp : p =>
      Or.elim (em q)
        (λ hq : q => hq)
        (λ hnq : ¬q => absurd hp (h hnq))

example : p ∨ ¬p := em p

example : (((p → q) → p) → p) :=
  λ h : (p → q) → p =>
    Or.elim (em p)
      (
        λ hp : p =>
          hp
      )
      (
        λ hnp : ¬p =>
          h (λ hp : p => absurd hp hnp)
      )


import topology.instances.real data.pfun2 order.lattice ...basic
open topological_space set

noncomputable theory
universes u v w

variables {α : Type u} {β : Type v} {γ : Type w} {n : ℕ}

/- continuity of a partial function -/
namespace pfun
def is_continuous [topological_space α] [topological_space β] (f : α →. β) :=
continuous (f.as_subtype)

def is_continuous_id [topological_space α] : is_continuous (@pfun.id α) := omitted
end pfun

open pfun
-- instance [t : topological_space α] : topological_space (vector α n) :=
-- ⨆(l : fin n), induced (λ x, vector.nth x l) t

structure Top :=
  (carrier : Type u)
  (struct : topological_space carrier)

namespace Top

instance : has_coe_to_sort Top := ⟨Type*, Top.carrier⟩
attribute [instance] Top.struct

def restrict (X : Top) (s : set X) : Top := ⟨subtype s, by apply_instance⟩

end Top

structure euclidean_space :=
(carrier : Type u)
(dim : ℕ)
(equiv : carrier ≃ vector ℝ dim)

namespace euclidean_space
instance : has_coe_to_sort euclidean_space := ⟨Type*, euclidean_space.carrier⟩

instance to_topological_space (E : euclidean_space) :
  topological_space E :=
induced (@euclidean_space.equiv E) (by apply_instance)

def to_Top (E : euclidean_space) : Top :=
⟨E, by apply_instance⟩

instance : has_coe euclidean_space Top :=
⟨to_Top⟩

def real_euclidean_space (n : ℕ) : euclidean_space :=
⟨ℝ, 1, vector.vector_one_equiv.symm⟩

def standard_euclidean_space (n : ℕ) : euclidean_space :=
⟨vector ℝ n, n, equiv.refl _⟩

notation `ℝ^` := standard_euclidean_space
end euclidean_space

variables {k k' : ℕ∞} {E : euclidean_space.{u}} {E' : euclidean_space.{v}}
def is_smooth_at (k : ℕ∞) (f : E →. E') (x : E) : Prop := sorry
def is_smooth (k : ℕ∞) (f : E →. E') : Prop := sorry --∀x, is_smooth_at k f x

/- the empty map is smooth -/
lemma is_smooth_empty (k : ℕ∞) (E E' : euclidean_space) : is_smooth k (pfun.empty E E') := omitted

/- every smooth map is continuous -/
lemma is_continuous_of_is_smooth {f : E →. E'} (hf : is_smooth k f) : f.is_continuous :=
omitted

lemma is_smooth_of_le {f : E →. E'} (hf : is_smooth k f) (hk : k' ≤ k) : is_smooth k' f :=
omitted

/- a partial homeomorphism -/
structure phomeo (X Y : Top) extends X ≃. Y :=
(continuous_to_fun  : to_fun.is_continuous)
(continuous_inv_fun : inv_fun.is_continuous)

infixr ` ≃ₜ. `:25 := phomeo

namespace phomeo
variables {X : Top} {Y : Top} {Z : Top}
def restrict_phomeo (p : set X) : X.restrict p ≃ₜ. X :=
⟨pequiv.subtype_pequiv p, omitted, omitted⟩

protected def rfl : X ≃ₜ. X := ⟨pequiv.rfl, is_continuous_id, is_continuous_id⟩
protected def refl (X) : X ≃ₜ. X := phomeo.rfl
def symm (f : X ≃ₜ. Y) : Y ≃ₜ. X := ⟨f.to_pequiv.symm, f.continuous_inv_fun, f.continuous_to_fun⟩
def trans (f : X ≃ₜ. Y) (g : Y ≃ₜ. Z) : X ≃ₜ. Z := ⟨f.to_pequiv.trans g.to_pequiv, omitted, omitted⟩

end phomeo