section
  parameters {α : Type} {r : α → α → Type}
  parameter transr : ∀ {x y z}, r x y → r y z → r x z

  variables {a b c d e : α}

  theorem t1 (h₁ : r a b) (h₂ : r b c) (h₃ : r c d) : r a d :=
    transr (transr h₁ h₂) h₃

  theorem t2 (h₁ : r a b) (h₂ : r b c) (h₃ : r c d) (h₄ : r d e) : r a e :=
    transr h₁ (t1 h₂ h₃ h₄)

  #check t1
  #check t2
end

#check t1
#check t2


import GMLInit.Data.Index.Basic
import GMLInit.Data.Index.Append

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

def bind : {xs : List α} → (i : Index xs) × (Index (f i.val)) → Index (xs.bind f)
| _::_, ⟨head, j⟩ => append_inl j
| _::_, ⟨tail i, j⟩ => append_inr (bind ⟨i, j⟩)

def unbind : {xs : List α} → (k : Index (xs.bind f)) → (i : Index xs) × (Index (f i.val))
| _::_, k =>
  match unappend k with
  | .inl j => ⟨head, j⟩
  | .inr k => ⟨tail (unbind k).fst, (unbind k).snd⟩

theorem unbind_bind {xs : List α} (i : Index xs) (j : Index (f i.val)) : unbind f (bind f ⟨i, j⟩) = ⟨i, j⟩ := by
  induction i with
  | head => rw [bind, unbind, unappend_append]
  | tail i ih =>
    unfold bind unbind
    rw [unappend_append]
    clean
    rw [ih]

theorem bind_unbind {xs : List α} (k : Index (xs.bind f)) : bind f (unbind f k) = k := by
  induction xs with
  | nil => contradiction
  | cons x xs ih =>
    rw [unbind]
    split
    next h => rw [bind, append_inl, ←h, append_unappend]
    next h => rw [bind, append_inr, ih, ←h, append_unappend]

theorem bind_eq_iff_eq_unbind {xs} (i : (i : Index xs) × Index (f i.val)) (j : Index (xs.bind f)) : bind f i = j ↔ i = unbind f j := by
  constr
  · intro h; rw [←h, unbind_bind]
  · intro h; rw [h, bind_unbind]

theorem unbind_eq_iff_eq_bind {xs} (i : Index (xs.bind f)) (j : (i : Index xs) × Index (f i.val)) : unbind f i = j ↔ i = bind f j := by
  constr
  · intro h; rw [←h, bind_unbind]
  · intro h; rw [h, unbind_bind]

def bindEquiv (xs : List α) : Equiv ((i : Index xs) × Index (f i.val)) (Index (xs.bind f)) where
  fwd := bind f
  rev := unbind f
  spec := by
    intros
    constr
    · intro | rfl => exact unbind_bind ..
    · intro | rfl => exact bind_unbind ..

theorem val_bind {xs : List α} (i : (i : Index xs) × Index (f i.val)) : (bind f i).val = i.snd.val := by
  induction xs with
  | nil => cases i; contradiction
  | cons x xs ih =>
    match i with
    | ⟨head, j⟩ => rw [bind, val_append_inl]
    | ⟨tail i, j⟩ => rw [bind, val_append_inr, ih]

theorem val_unbind {xs : List α} (i : Index (xs.bind f)) : (unbind f i).snd.val = i.val := by
  rw [←bind_unbind f i, val_bind, unbind_bind]

end Index


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

A formalization of properties of random variables.
-/

import .probability_theory tactic.tidy
import order.filter
import measure_theory.lebesgue_measure measure_theory.borel_space
import topology.basic
open set measurable_space measure_theory ennreal lattice filter
set_option pp.implicit true

universe u

variables {s α : Type u} [probability_space s] (p : probability_measure s) (a : set s)

local attribute [instance] classical.prop_decidable

section random_variable

-- def is_random_variable (X : s → ℝ) := measurable X

-- lemma is_random_variable_leq (X : s → ℝ) :
-- is_random_variable(X) ↔ ∀ x:ℝ, is_measurable({ω : s | X ω ≤ x}) :=
-- begin
-- fsplit,
-- {
--   intros h x,
--   have g : {ω : s | X ω ≤ x} = X⁻¹'({z:ℝ| z ≤ x}), by refl,
--   rw g,
--   apply h,
--   refine measurable_le measurable_id measurable_const,
-- },
-- {
--   intros h,
--   unfold is_random_variable, unfold measurable,
--   -- rw borel_eq_generate_Iio,
--   unfold measurable_space.map,
--   intros b g, dsimp at *,
--   unfold set.preimage,

--   have h₀: ∀ ω :s , X ω ∈ b → X ω ≤ Sup b, {
--     -- intros ω h, apply le_Sup h,

--   }

-- }
-- end

noncomputable def indicator (a : set s) :=(λ x : s, if x ∈ a then (1:ℝ) else 0)

lemma indicator_in (a  : set s) (x : s)
(h : x ∈ a) : indicator a x = 1  := by simp [indicator,h]

lemma indicator_nin (a  : set s) (x : s)
(h : x ∉ a) : indicator a x = 0 := by simp [indicator,h]

/- TODO: Shorten these proofs. -/
lemma indicator_map_1 (a : set s)
(b : set ℝ)
(h₁ : (0:ℝ) ∈ b)
(h₂ : (1:ℝ) ∈ b)
: (indicator a ⁻¹' b) = univ :=
begin
ext1, dsimp at *, by_cases (x ∈ a),
{
  split,
  intros h₁, rw indicator_in a x h at h₁, apply mem_univ,
  intros h₂, rw indicator_in a x h, assumption,
},
{
  split,
  intros g₁, rw indicator_nin a x h at g₁, apply mem_univ,
  intros g₂, rw indicator_nin a x h, assumption,
}
end

lemma indicator_map_2 (a : set s)
(b : set ℝ)
(h₁ : (0:ℝ) ∉ b)
(h₂ : (1:ℝ) ∈ b)
: (indicator a ⁻¹' b) = a :=
begin
  ext1, dsimp at *, fsplit, by_cases(x ∈ a),
  {
    intros k, assumption,
  },
  {
    intros k, rw indicator_nin a x h at k, exfalso, apply h₁ k,
  },
  {
    intros k, rw indicator_in a x k, assumption,
  },
end

lemma indicator_map_3 (a : set s)
(b : set ℝ)
(h₁ : (0:ℝ) ∈ b)
(h₂ : (1:ℝ) ∉ b)
: (indicator a ⁻¹' b) = -a :=
begin
  tidy,
  rw indicator_in a x a_2 at a_1,
  apply h₂ a_1,
  rw indicator_nin a x a_1, assumption,
end

lemma indicator_map_4 (a : set s)
(b : set ℝ)
(h₁ : (0:ℝ) ∉ b)
(h₂ : (1:ℝ) ∉ b)
: (indicator a ⁻¹' b) = ∅ :=
begin
  ext1, dsimp at *, by_cases (x ∈ a),
  {
    split,
    intros h₁, rw indicator_in a x h at h₁, apply h₂ h₁,
    intros h₂, exfalso, exact h₂,
  },
  {
    split,
    intros g₁, rw indicator_nin a x h at g₁, apply h₁ g₁,
    intros g₂, exfalso, exact g₂,
  }
end

lemma is_random_variable_indicator {a : set s} (g : is_measurable a) : measurable(indicator a) :=
begin
  intros b h,
  unfold measurable_space.map, dsimp,
  by_cases g₁: ((0:ℝ) ∈ b),
  {
    by_cases g₂: ((1:ℝ) ∈ b),
    rw indicator_map_1 a b g₁ g₂, apply is_measurable.univ,
    rw indicator_map_3 a b g₁ g₂, apply is_measurable.compl g,
  },
  {
    by_cases g₂: ((1:ℝ) ∈ b),
    rw indicator_map_2 a b g₁ g₂, assumption,
    rw indicator_map_4 a b g₁ g₂, apply is_measurable.empty,
  }
end

lemma is_random_variable_add (X Y : s → ℝ) (h₁ : measurable X) (h₂ : measurable Y) :
measurable( X + Y ) :=
begin
  apply measurable_add, repeat{assumption},
end

lemma is_random_variable_mul (X Y : s → ℝ) (h₁ : measurable X) (h₂ : measurable Y) :
measurable( X * Y ) :=
begin
  apply measure_theory.measurable_mul, repeat{assumption},
end

-- theorem is_random_variable_lim' (Z : ℕ → (s → α))
-- (h : Π i:ℕ, is_random_variable (Z i))
--  (Z' : s → ℝ)
--  (h : ∀ ω : s, tendsto (λ i:ℕ, Z i ω) at_top (nhds(Z' ω))):
-- (is_random_variable Z')
-- := sorry

-- theorem is_random_variable_lim (Z : ℕ → (s → ℝ))
-- (h : Π i:ℕ, is_random_variable (Z i))
--  (Z' : s → ℝ)
--  (h : ∀ ω : s, tendsto (λ i:ℕ, Z i ω) at_top (nhds(Z' ω))):
-- (is_random_variable Z')
-- :=
-- begin
--   intros b g,
--   unfold measurable_space.map, dsimp,
--   unfold set.preimage,
--   unfold tendsto at h,
--   sorry
-- end

end random_variable

section distribution_function
-- variables (X : s → ℝ) [measurable X]

def distribution_function {X : s → ℝ} (hX : measurable X) :=
(λ x:ℝ, prob p({ω : s | X(ω) ≤ x}))

end distribution_function


/-
Copyright (c) 2020 Aaron Anderson, Jalex Stark, Kyle Miller. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Aaron Anderson, Jalex Stark, Kyle Miller, Alena Gusakov, Hunter Monroe
-/
import data.rel
import data.set.finite
import data.sym.sym2

/-!
# Simple graphs

This module defines simple graphs on a vertex type `V` as an
irreflexive symmetric relation.

There is a basic API for locally finite graphs and for graphs with
finitely many vertices.

## Main definitions

* `simple_graph` is a structure for symmetric, irreflexive relations

* `simple_graph.neighbor_set` is the `set` of vertices adjacent to a given vertex

* `simple_graph.common_neighbors` is the intersection of the neighbor sets of two given vertices

* `simple_graph.neighbor_finset` is the `finset` of vertices adjacent to a given vertex,
   if `neighbor_set` is finite

* `simple_graph.incidence_set` is the `set` of edges containing a given vertex

* `simple_graph.incidence_finset` is the `finset` of edges containing a given vertex,
   if `incidence_set` is finite

* `simple_graph.dart` is an ordered pair of adjacent vertices, thought of as being an
  orientated edge.

* `simple_graph.hom`, `simple_graph.embedding`, and `simple_graph.iso` for graph
  homomorphisms, graph embeddings, and
  graph isomorphisms. Note that a graph embedding is a stronger notion than an
  injective graph homomorphism, since its image is an induced subgraph.

* `boolean_algebra` instance: Under the subgraph relation, `simple_graph` forms a `boolean_algebra`.
  In other words, this is the lattice of spanning subgraphs of the complete graph.

## Notations

* `→g`, `↪g`, and `≃g` for graph homomorphisms, graph embeddings, and graph isomorphisms,
  respectively.

## Implementation notes

* A locally finite graph is one with instances `Π v, fintype (G.neighbor_set v)`.

* Given instances `decidable_rel G.adj` and `fintype V`, then the graph
  is locally finite, too.

* Morphisms of graphs are abbreviations for `rel_hom`, `rel_embedding`, and `rel_iso`.
  To make use of pre-existing simp lemmas, definitions involving morphisms are
  abbreviations as well.

## Naming Conventions

* If the vertex type of a graph is finite, we refer to its cardinality as `card_verts`.

## Todo

* Upgrade `simple_graph.boolean_algebra` to a `complete_boolean_algebra`.

* This is the simplest notion of an unoriented graph.  This should
  eventually fit into a more complete combinatorics hierarchy which
  includes multigraphs and directed graphs.  We begin with simple graphs
  in order to start learning what the combinatorics hierarchy should
  look like.
-/
open finset
universes u v w

/--
A simple graph is an irreflexive symmetric relation `adj` on a vertex type `V`.
The relation describes which pairs of vertices are adjacent.
There is exactly one edge for every pair of adjacent vertices;
see `simple_graph.edge_set` for the corresponding edge set.
-/
@[ext]
structure simple_graph (V : Type u) :=
(adj : V → V → Prop)
(symm : symmetric adj . obviously)
(loopless : irreflexive adj . obviously)

/--
Construct the simple graph induced by the given relation. It
symmetrizes the relation and makes it irreflexive.
-/
def simple_graph.from_rel {V : Type u} (r : V → V → Prop) : simple_graph V :=
{ adj := λ a b, (a ≠ b) ∧ (r a b ∨ r b a),
  symm := λ a b ⟨hn, hr⟩, ⟨hn.symm, hr.symm⟩,
  loopless := λ a ⟨hn, _⟩, hn rfl }

noncomputable instance {V : Type u} [fintype V] : fintype (simple_graph V) :=
by { classical, exact fintype.of_injective simple_graph.adj simple_graph.ext }

@[simp]
lemma simple_graph.from_rel_adj {V : Type u} (r : V → V → Prop) (v w : V) :
  (simple_graph.from_rel r).adj v w ↔ v ≠ w ∧ (r v w ∨ r w v) :=
iff.rfl

/-- The complete graph on a type `V` is the simple graph with all pairs of distinct vertices
adjacent. In `mathlib`, this is usually referred to as `⊤`. -/
def complete_graph (V : Type u) : simple_graph V := { adj := ne }

/-- The graph with no edges on a given vertex type `V`. `mathlib` prefers the notation `⊥`. -/
def empty_graph (V : Type u) : simple_graph V := { adj := λ i j, false }

/--
Two vertices are adjacent in the complete bipartite graph on two vertex types
if and only if they are not from the same side.
Bipartite graphs in general may be regarded as being subgraphs of one of these.

TODO also introduce complete multi-partite graphs, where the vertex type is a sigma type of an
indexed family of vertex types
-/
@[simps]
def complete_bipartite_graph (V W : Type*) : simple_graph (V ⊕ W) :=
{ adj := λ v w, (v.is_left ∧ w.is_right) ∨ (v.is_right ∧ w.is_left),
  symm := begin
    intros v w,
    cases v; cases w; simp,
  end,
  loopless := begin
    intro v,
    cases v; simp,
  end }

namespace simple_graph

variables {V : Type u} {W : Type v} {X : Type w} (G : simple_graph V) (G' : simple_graph W)
  {a b c u v w : V} {e : sym2 V}

@[simp] lemma irrefl {v : V} : ¬G.adj v v := G.loopless v

lemma adj_comm (u v : V) : G.adj u v ↔ G.adj v u := ⟨λ x, G.symm x, λ x, G.symm x⟩

@[symm] lemma adj_symm (h : G.adj u v) : G.adj v u := G.symm h

lemma adj.symm {G : simple_graph V} {u v : V} (h : G.adj u v) : G.adj v u := G.symm h

lemma ne_of_adj (h : G.adj a b) : a ≠ b := by { rintro rfl, exact G.irrefl h }

protected lemma adj.ne {G : simple_graph V} {a b : V} (h : G.adj a b) : a ≠ b := G.ne_of_adj h

protected lemma adj.ne' {G : simple_graph V} {a b : V} (h : G.adj a b) : b ≠ a := h.ne.symm

lemma ne_of_adj_of_not_adj {v w x : V} (h : G.adj v x) (hn : ¬ G.adj w x) : v ≠ w :=
λ h', hn (h' ▸ h)

section order

/-- The relation that one `simple_graph` is a subgraph of another.
Note that this should be spelled `≤`. -/
def is_subgraph (x y : simple_graph V) : Prop := ∀ ⦃v w : V⦄, x.adj v w → y.adj v w

instance : has_le (simple_graph V) := ⟨is_subgraph⟩

@[simp] lemma is_subgraph_eq_le : (is_subgraph : simple_graph V → simple_graph V → Prop) = (≤) :=
rfl

/-- The supremum of two graphs `x ⊔ y` has edges where either `x` or `y` have edges. -/
instance : has_sup (simple_graph V) := ⟨λ x y,
  { adj := x.adj ⊔ y.adj,
    symm := λ v w h, by rwa [pi.sup_apply, pi.sup_apply, x.adj_comm, y.adj_comm] }⟩

@[simp] lemma sup_adj (x y : simple_graph V) (v w : V) : (x ⊔ y).adj v w ↔ x.adj v w ∨ y.adj v w :=
iff.rfl

/-- The infimum of two graphs `x ⊓ y` has edges where both `x` and `y` have edges. -/
instance : has_inf (simple_graph V) := ⟨λ x y,
  { adj := x.adj ⊓ y.adj,
    symm := λ v w h, by rwa [pi.inf_apply, pi.inf_apply, x.adj_comm, y.adj_comm] }⟩

@[simp] lemma inf_adj (x y : simple_graph V) (v w : V) : (x ⊓ y).adj v w ↔ x.adj v w ∧ y.adj v w :=
iff.rfl

/--
We define `Gᶜ` to be the `simple_graph V` such that no two adjacent vertices in `G`
are adjacent in the complement, and every nonadjacent pair of vertices is adjacent
(still ensuring that vertices are not adjacent to themselves).
-/
instance : has_compl (simple_graph V) := ⟨λ G,
  { adj := λ v w, v ≠ w ∧ ¬G.adj v w,
    symm := λ v w ⟨hne, _⟩, ⟨hne.symm, by rwa adj_comm⟩,
    loopless := λ v ⟨hne, _⟩, (hne rfl).elim }⟩

@[simp] lemma compl_adj (G : simple_graph V) (v w : V) : Gᶜ.adj v w ↔ v ≠ w ∧ ¬G.adj v w := iff.rfl

/-- The difference of two graphs `x \ y` has the edges of `x` with the edges of `y` removed. -/
instance : has_sdiff (simple_graph V) := ⟨λ x y,
  { adj := x.adj \ y.adj,
    symm := λ v w h, by change x.adj w v ∧ ¬ y.adj w v; rwa [x.adj_comm, y.adj_comm] }⟩

@[simp] lemma sdiff_adj (x y : simple_graph V) (v w : V) :
  (x \ y).adj v w ↔ (x.adj v w ∧ ¬ y.adj v w) := iff.rfl

instance : boolean_algebra (simple_graph V) :=
{ le := (≤),
  sup := (⊔),
  inf := (⊓),
  compl := has_compl.compl,
  sdiff := (\),
  top := complete_graph V,
  bot := empty_graph V,
  le_top := λ x v w h, x.ne_of_adj h,
  bot_le := λ x v w h, h.elim,
  sup_le := λ x y z hxy hyz v w h, h.cases_on (λ h, hxy h) (λ h, hyz h),
  sdiff_eq := λ x y, by { ext v w, refine ⟨λ h, ⟨h.1, ⟨_, h.2⟩⟩, λ h, ⟨h.1, h.2.2⟩⟩,
                          rintro rfl, exact x.irrefl h.1 },
  sup_inf_sdiff := λ a b, by { ext v w, refine ⟨λ h, _, λ h', _⟩,
                               obtain ⟨ha, _⟩|⟨ha, _⟩ := h; exact ha,
                               by_cases b.adj v w; exact or.inl ⟨h', h⟩ <|> exact or.inr ⟨h', h⟩ },
  inf_inf_sdiff := λ a b, by { ext v w, exact ⟨λ ⟨⟨_, hb⟩,⟨_, hb'⟩⟩, hb' hb, λ h, h.elim⟩ },
  le_sup_left := λ x y v w h, or.inl h,
  le_sup_right := λ x y v w h, or.inr h,
  le_inf := λ x y z hxy hyz v w h, ⟨hxy h, hyz h⟩,
  le_sup_inf := λ a b c v w h, or.dcases_on h.2 or.inl $
    or.dcases_on h.1 (λ h _, or.inl h) $ λ hb hc, or.inr ⟨hb, hc⟩,
  inf_compl_le_bot := λ a v w h, false.elim $ h.2.2 h.1,
  top_le_sup_compl := λ a v w ne, by { by_cases a.adj v w, exact or.inl h, exact or.inr ⟨ne, h⟩ },
  inf_le_left := λ x y v w h, h.1,
  inf_le_right := λ x y v w h, h.2,
  .. partial_order.lift adj ext }

@[simp] lemma top_adj (v w : V) : (⊤ : simple_graph V).adj v w ↔ v ≠ w := iff.rfl

@[simp] lemma bot_adj (v w : V) : (⊥ : simple_graph V).adj v w ↔ false := iff.rfl

@[simp] lemma complete_graph_eq_top (V : Type u) : complete_graph V = ⊤ := rfl

@[simp] lemma empty_graph_eq_bot (V : Type u) : empty_graph V = ⊥ := rfl

instance (V : Type u) : inhabited (simple_graph V) := ⟨⊤⟩

section decidable

variables (V) (H : simple_graph V) [decidable_rel G.adj] [decidable_rel H.adj]

instance bot.adj_decidable   : decidable_rel (⊥ : simple_graph V).adj := λ v w, decidable.false

instance sup.adj_decidable   : decidable_rel (G ⊔ H).adj := λ v w, or.decidable

instance inf.adj_decidable   : decidable_rel (G ⊓ H).adj := λ v w, and.decidable

instance sdiff.adj_decidable : decidable_rel (G \ H).adj := λ v w, and.decidable

variable [decidable_eq V]

instance top.adj_decidable   : decidable_rel (⊤ : simple_graph V).adj :=  λ v w, not.decidable

instance compl.adj_decidable : decidable_rel Gᶜ.adj := λ v w, and.decidable

end decidable

end order

/-- `G.support` is the set of vertices that form edges in `G`. -/
def support : set V := rel.dom G.adj

lemma mem_support {v : V} : v ∈ G.support ↔ ∃ w, G.adj v w := iff.rfl

lemma support_mono {G G' : simple_graph V} (h : G ≤ G') : G.support ⊆ G'.support :=
rel.dom_mono h

/-- `G.neighbor_set v` is the set of vertices adjacent to `v` in `G`. -/
def neighbor_set (v : V) : set V := set_of (G.adj v)

instance neighbor_set.mem_decidable (v : V) [decidable_rel G.adj] :
  decidable_pred (∈ G.neighbor_set v) := by { unfold neighbor_set, apply_instance }

/--
The edges of G consist of the unordered pairs of vertices related by
`G.adj`.

The way `edge_set` is defined is such that `mem_edge_set` is proved by `refl`.
(That is, `⟦(v, w)⟧ ∈ G.edge_set` is definitionally equal to `G.adj v w`.)
-/
def edge_set : set (sym2 V) := sym2.from_rel G.symm

@[simp] lemma mem_edge_set : ⟦(v, w)⟧ ∈ G.edge_set ↔ G.adj v w := iff.rfl

/--
Two vertices are adjacent iff there is an edge between them. The
condition `v ≠ w` ensures they are different endpoints of the edge,
which is necessary since when `v = w` the existential
`∃ (e ∈ G.edge_set), v ∈ e ∧ w ∈ e` is satisfied by every edge
incident to `v`.
-/
lemma adj_iff_exists_edge {v w : V} :
  G.adj v w ↔ v ≠ w ∧ ∃ (e ∈ G.edge_set), v ∈ e ∧ w ∈ e :=
begin
  refine ⟨λ _, ⟨G.ne_of_adj ‹_›, ⟦(v,w)⟧, _⟩, _⟩,
  { simpa },
  { rintro ⟨hne, e, he, hv⟩,
    rw sym2.mem_and_mem_iff hne at hv,
    subst e,
    rwa mem_edge_set at he }
end

lemma adj_iff_exists_edge_coe : G.adj a b ↔ ∃ (e : G.edge_set), ↑e = ⟦(a, b)⟧ :=
by simp only [mem_edge_set, exists_prop, set_coe.exists, exists_eq_right, subtype.coe_mk]

lemma edge_other_ne {e : sym2 V} (he : e ∈ G.edge_set) {v : V} (h : v ∈ e) : h.other ≠ v :=
begin
  erw [← sym2.other_spec h, sym2.eq_swap] at he,
  exact G.ne_of_adj he,
end

instance decidable_mem_edge_set [decidable_rel G.adj] :
  decidable_pred (∈ G.edge_set) := sym2.from_rel.decidable_pred _

instance edges_fintype [decidable_eq V] [fintype V] [decidable_rel G.adj] :
  fintype G.edge_set := subtype.fintype _

/-! ## Darts -/

/-- A `dart` is an oriented edge, implemented as an ordered pair of adjacent vertices. -/
@[ext, derive decidable_eq]
structure dart extends V × V :=
(is_adj : G.adj fst snd)

section darts
variables {G}

/-- The first vertex for the dart. -/
abbreviation dart.fst (d : G.dart) : V := d.fst

/-- The second vertex for the dart. -/
abbreviation dart.snd (d : G.dart) : V := d.snd

instance dart.fintype [fintype V] [decidable_rel G.adj] : fintype G.dart :=
fintype.of_equiv (Σ v, G.neighbor_set v)
{ to_fun := λ s, ⟨(s.fst, s.snd), s.snd.property⟩,
  inv_fun := λ d, ⟨d.fst, d.snd, d.is_adj⟩,
  left_inv := λ s, by ext; simp,
  right_inv := λ d, by ext; simp }

/-- The edge associated to the dart. -/
def dart.edge (d : G.dart) : sym2 V := ⟦d.to_prod⟧

@[simp] lemma dart.edge_mk {p : V × V} (h : G.adj p.1 p.2) :
  (dart.mk p h).edge = ⟦p⟧ := rfl

@[simp] lemma dart.edge_mem (d : G.dart) : d.edge ∈ G.edge_set :=
d.is_adj

/-- The dart with reversed orientation from a given dart. -/
@[simps] def dart.symm (d : G.dart) : G.dart :=
⟨d.to_prod.swap, G.symm d.is_adj⟩

@[simp] lemma dart.symm_mk {p : V × V} (h : G.adj p.1 p.2) :
  (dart.mk p h).symm = dart.mk p.swap h.symm := rfl

@[simp] lemma dart.edge_symm (d : G.dart) : d.symm.edge = d.edge :=
sym2.mk_prod_swap_eq

@[simp] lemma dart.edge_comp_symm : dart.edge ∘ dart.symm = (dart.edge : G.dart → sym2 V) :=
funext dart.edge_symm

@[simp] lemma dart.symm_symm (d : G.dart) : d.symm.symm = d :=
dart.ext _ _ $ prod.swap_swap _

@[simp] lemma dart.symm_involutive : function.involutive (dart.symm : G.dart → G.dart) :=
dart.symm_symm

lemma dart.symm_ne (d : G.dart) : d.symm ≠ d :=
ne_of_apply_ne (prod.snd ∘ dart.to_prod) d.is_adj.ne

lemma dart_edge_eq_iff : Π (d₁ d₂ : G.dart),
  d₁.edge = d₂.edge ↔ d₁ = d₂ ∨ d₁ = d₂.symm :=
by { rintros ⟨p, hp⟩ ⟨q, hq⟩, simp [sym2.mk_eq_mk_iff] }

lemma dart_edge_eq_mk_iff : Π {d : G.dart} {p : V × V},
  d.edge = ⟦p⟧ ↔ d.to_prod = p ∨ d.to_prod = p.swap :=
by { rintro ⟨p, h⟩, apply sym2.mk_eq_mk_iff }

lemma dart_edge_eq_mk_iff' : Π {d : G.dart} {u v : V},
  d.edge = ⟦(u, v)⟧ ↔ d.fst = u ∧ d.snd = v ∨ d.fst = v ∧ d.snd = u :=
by { rintro ⟨⟨a, b⟩, h⟩ u v, rw dart_edge_eq_mk_iff, simp }

variables (G)

/-- Two darts are said to be adjacent if they could be consecutive
darts in a walk -- that is, the first dart's second vertex is equal to
the second dart's first vertex. -/
def dart_adj (d d' : G.dart) : Prop := d.snd = d'.fst

/-- For a given vertex `v`, this is the bijective map from the neighbor set at `v`
to the darts `d` with `d.fst = v`. --/
@[simps] def dart_of_neighbor_set (v : V) (w : G.neighbor_set v) : G.dart :=
⟨(v, w), w.property⟩

lemma dart_of_neighbor_set_injective (v : V) : function.injective (G.dart_of_neighbor_set v) :=
λ e₁ e₂ h, subtype.ext $ by { injection h with h', convert congr_arg prod.snd h' }

instance dart.inhabited [inhabited V] [inhabited (G.neighbor_set default)] :
  inhabited G.dart := ⟨G.dart_of_neighbor_set default default⟩

end darts

/-! ### Incidence set -/

/-- Set of edges incident to a given vertex, aka incidence set. -/
def incidence_set (v : V) : set (sym2 V) := {e ∈ G.edge_set | v ∈ e}

lemma incidence_set_subset (v : V) : G.incidence_set v ⊆ G.edge_set := λ _ h, h.1

lemma mk_mem_incidence_set_iff : ⟦(b, c)⟧ ∈ G.incidence_set a ↔ G.adj b c ∧ (a = b ∨ a = c) :=
and_congr_right' sym2.mem_iff

lemma mk_mem_incidence_set_left_iff : ⟦(a, b)⟧ ∈ G.incidence_set a ↔ G.adj a b :=
and_iff_left $ sym2.mem_mk_left _ _

lemma mk_mem_incidence_set_right_iff : ⟦(a, b)⟧ ∈ G.incidence_set b ↔ G.adj a b :=
and_iff_left $ sym2.mem_mk_right _ _

lemma edge_mem_incidence_set_iff {e : G.edge_set} : ↑e ∈ G.incidence_set a ↔ a ∈ (e : sym2 V) :=
and_iff_right e.2

lemma incidence_set_inter_incidence_set_subset (h : a ≠ b) :
  G.incidence_set a ∩ G.incidence_set b ⊆ {⟦(a, b)⟧} :=
λ e he, (sym2.mem_and_mem_iff h).1 ⟨he.1.2, he.2.2⟩

lemma incidence_set_inter_incidence_set_of_adj (h : G.adj a b) :
  G.incidence_set a ∩ G.incidence_set b = {⟦(a, b)⟧} :=
begin
  refine (G.incidence_set_inter_incidence_set_subset $ h.ne).antisymm _,
  rintro _ (rfl : _ = ⟦(a, b)⟧),
  exact ⟨G.mk_mem_incidence_set_left_iff.2 h, G.mk_mem_incidence_set_right_iff.2 h⟩,
end

lemma adj_of_mem_incidence_set (h : a ≠ b) (ha : e ∈ G.incidence_set a)
  (hb : e ∈ G.incidence_set b) :
  G.adj a b :=
by rwa [←mk_mem_incidence_set_left_iff,
  ←set.mem_singleton_iff.1 $ G.incidence_set_inter_incidence_set_subset h ⟨ha, hb⟩]

lemma incidence_set_inter_incidence_set_of_not_adj (h : ¬ G.adj a b) (hn : a ≠ b) :
  G.incidence_set a ∩ G.incidence_set b = ∅ :=
begin
  simp_rw [set.eq_empty_iff_forall_not_mem, set.mem_inter_eq, not_and],
  intros u ha hb,
  exact h (G.adj_of_mem_incidence_set hn ha hb),
end

instance decidable_mem_incidence_set [decidable_eq V] [decidable_rel G.adj] (v : V) :
  decidable_pred (∈ G.incidence_set v) := λ e, and.decidable

/--
The `edge_set` of the graph as a `finset`.
-/
def edge_finset [decidable_eq V] [fintype V] [decidable_rel G.adj] : finset (sym2 V) :=
set.to_finset G.edge_set

@[simp] lemma mem_edge_finset [decidable_eq V] [fintype V] [decidable_rel G.adj] (e : sym2 V) :
  e ∈ G.edge_finset ↔ e ∈ G.edge_set :=
set.mem_to_finset

@[simp] lemma edge_set_univ_card [decidable_eq V] [fintype V] [decidable_rel G.adj] :
  (univ : finset G.edge_set).card = G.edge_finset.card :=
fintype.card_of_subtype G.edge_finset (mem_edge_finset _)

@[simp] lemma mem_neighbor_set (v w : V) : w ∈ G.neighbor_set v ↔ G.adj v w :=
iff.rfl

@[simp] lemma mem_neighbor_set' (v w : V) : G.neighbor_set v w ↔ G.adj v w :=
iff.rfl

@[simp] lemma mem_incidence_set (v w : V) : ⟦(v, w)⟧ ∈ G.incidence_set v ↔ G.adj v w :=
by simp [incidence_set]

lemma mem_incidence_iff_neighbor {v w : V} : ⟦(v, w)⟧ ∈ G.incidence_set v ↔ w ∈ G.neighbor_set v :=
by simp only [mem_incidence_set, mem_neighbor_set]

lemma adj_incidence_set_inter {v : V} {e : sym2 V} (he : e ∈ G.edge_set) (h : v ∈ e) :
  G.incidence_set v ∩ G.incidence_set h.other = {e} :=
begin
  ext e',
  simp only [incidence_set, set.mem_sep_eq, set.mem_inter_eq, set.mem_singleton_iff],
  refine ⟨λ h', _, _⟩,
  { rw ←sym2.other_spec h,
    exact (sym2.mem_and_mem_iff (edge_other_ne G he h).symm).mp ⟨h'.1.2, h'.2.2⟩ },
  { rintro rfl,
    exact ⟨⟨he, h⟩, he, sym2.other_mem _⟩ }
end

lemma compl_neighbor_set_disjoint (G : simple_graph V) (v : V) :
  disjoint (G.neighbor_set v) (Gᶜ.neighbor_set v) :=
begin
  rw set.disjoint_iff,
  rintro w ⟨h, h'⟩,
  rw [mem_neighbor_set, compl_adj] at h',
  exact h'.2 h,
end

lemma neighbor_set_union_compl_neighbor_set_eq (G : simple_graph V) (v : V) :
  G.neighbor_set v ∪ Gᶜ.neighbor_set v = {v}ᶜ :=
begin
  ext w,
  have h := @ne_of_adj _ G,
  simp_rw [set.mem_union, mem_neighbor_set, compl_adj, set.mem_compl_eq, set.mem_singleton_iff],
  tauto,
end

-- TODO find out why TC inference has `h` failing a defeq check for `to_finset`
lemma card_neighbor_set_union_compl_neighbor_set [fintype V] (G : simple_graph V)
  (v : V) [h : fintype (G.neighbor_set v ∪ Gᶜ.neighbor_set v : set V)] :
  (@set.to_finset _ (G.neighbor_set v ∪ Gᶜ.neighbor_set v) h).card = fintype.card V - 1 :=
begin
  classical,
  simp_rw [neighbor_set_union_compl_neighbor_set_eq, set.to_finset_compl, finset.card_compl,
    set.to_finset_card, set.card_singleton],
end

lemma neighbor_set_compl (G : simple_graph V) (v : V) :
  Gᶜ.neighbor_set v = (G.neighbor_set v)ᶜ \ {v} :=
by { ext w, simp [and_comm, eq_comm] }

/--
The set of common neighbors between two vertices `v` and `w` in a graph `G` is the
intersection of the neighbor sets of `v` and `w`.
-/
def common_neighbors (v w : V) : set V := G.neighbor_set v ∩ G.neighbor_set w

lemma common_neighbors_eq (v w : V) :
  G.common_neighbors v w = G.neighbor_set v ∩ G.neighbor_set w := rfl

lemma mem_common_neighbors {u v w : V} : u ∈ G.common_neighbors v w ↔ G.adj v u ∧ G.adj w u :=
iff.rfl

lemma common_neighbors_symm (v w : V) : G.common_neighbors v w = G.common_neighbors w v :=
set.inter_comm _ _

lemma not_mem_common_neighbors_left (v w : V) : v ∉ G.common_neighbors v w :=
λ h, ne_of_adj G h.1 rfl

lemma not_mem_common_neighbors_right (v w : V) : w ∉ G.common_neighbors v w :=
λ h, ne_of_adj G h.2 rfl

lemma common_neighbors_subset_neighbor_set_left (v w : V) :
  G.common_neighbors v w ⊆ G.neighbor_set v :=
set.inter_subset_left _ _

lemma common_neighbors_subset_neighbor_set_right (v w : V) :
  G.common_neighbors v w ⊆ G.neighbor_set w :=
set.inter_subset_right _ _

instance decidable_mem_common_neighbors [decidable_rel G.adj] (v w : V) :
  decidable_pred (∈ G.common_neighbors v w) :=
λ a, and.decidable

lemma common_neighbors_top_eq {v w : V} :
  (⊤ : simple_graph V).common_neighbors v w = set.univ \ {v, w} :=
by { ext u, simp [common_neighbors, eq_comm, not_or_distrib.symm] }

section incidence
variable [decidable_eq V]

/--
Given an edge incident to a particular vertex, get the other vertex on the edge.
-/
def other_vertex_of_incident {v : V} {e : sym2 V} (h : e ∈ G.incidence_set v) : V := h.2.other'

lemma edge_other_incident_set {v : V} {e : sym2 V} (h : e ∈ G.incidence_set v) :
  e ∈ G.incidence_set (G.other_vertex_of_incident h) :=
by { use h.1, simp [other_vertex_of_incident, sym2.other_mem'] }

lemma incidence_other_prop {v : V} {e : sym2 V} (h : e ∈ G.incidence_set v) :
  G.other_vertex_of_incident h ∈ G.neighbor_set v :=
by { cases h with he hv, rwa [←sym2.other_spec' hv, mem_edge_set] at he }

@[simp]
lemma incidence_other_neighbor_edge {v w : V} (h : w ∈ G.neighbor_set v) :
  G.other_vertex_of_incident (G.mem_incidence_iff_neighbor.mpr h) = w :=
sym2.congr_right.mp (sym2.other_spec' (G.mem_incidence_iff_neighbor.mpr h).right)

/--
There is an equivalence between the set of edges incident to a given
vertex and the set of vertices adjacent to the vertex.
-/
@[simps] def incidence_set_equiv_neighbor_set (v : V) : G.incidence_set v ≃ G.neighbor_set v :=
{ to_fun := λ e, ⟨G.other_vertex_of_incident e.2, G.incidence_other_prop e.2⟩,
  inv_fun := λ w, ⟨⟦(v, w.1)⟧, G.mem_incidence_iff_neighbor.mpr w.2⟩,
  left_inv := λ x, by simp [other_vertex_of_incident],
  right_inv := λ ⟨w, hw⟩, by simp }

end incidence

/-! ## Edge deletion -/

/-- Given a set of vertex pairs, remove all of the corresponding edges from the edge set.
It is fine to delete edges outside the edge set. -/
def delete_edges (s : set (sym2 V)) : simple_graph V :=
{ adj := G.adj \ sym2.to_rel s,
  symm := λ a b, by simp [adj_comm, sym2.eq_swap] }

@[simp] lemma delete_edges_adj (s : set (sym2 V)) (v w : V) :
  (G.delete_edges s).adj v w ↔ G.adj v w ∧ ¬ ⟦(v, w)⟧ ∈ s := iff.rfl

lemma sdiff_eq_delete_edges (G G' : simple_graph V) :
  G \ G' = G.delete_edges G'.edge_set :=
by { ext, simp }

lemma compl_eq_delete_edges :
  Gᶜ = (⊤ : simple_graph V).delete_edges G.edge_set :=
by { ext, simp }

@[simp] lemma delete_edges_delete_edges (s s' : set (sym2 V)) :
  (G.delete_edges s).delete_edges s' = G.delete_edges (s ∪ s') :=
by { ext, simp [and_assoc, not_or_distrib] }

@[simp] lemma delete_edges_empty_eq : G.delete_edges ∅ = G :=
by { ext, simp }

@[simp] lemma delete_edges_univ_eq : G.delete_edges set.univ = ⊥ :=
by { ext, simp }

lemma delete_edges_le (s : set (sym2 V)) : G.delete_edges s ≤ G :=
by { intro, simp { contextual := tt } }

lemma delete_edges_le_of_le {s s' : set (sym2 V)} (h : s ⊆ s') :
  G.delete_edges s' ≤ G.delete_edges s :=
λ v w, begin
  simp only [delete_edges_adj, and_imp, true_and] { contextual := tt },
  exact λ ha hn hs, hn (h hs),
end

lemma delete_edges_eq_inter_edge_set (s : set (sym2 V)) :
  G.delete_edges s = G.delete_edges (s ∩ G.edge_set) :=
by { ext, simp [imp_false] { contextual := tt } }

section finite_at

/-!
## Finiteness at a vertex

This section contains definitions and lemmas concerning vertices that
have finitely many adjacent vertices.  We denote this condition by
`fintype (G.neighbor_set v)`.

We define `G.neighbor_finset v` to be the `finset` version of `G.neighbor_set v`.
Use `neighbor_finset_eq_filter` to rewrite this definition as a `filter`.
-/

variables (v) [fintype (G.neighbor_set v)]
/--
`G.neighbors v` is the `finset` version of `G.adj v` in case `G` is
locally finite at `v`.
-/
def neighbor_finset : finset V := (G.neighbor_set v).to_finset

lemma neighbor_finset_def : G.neighbor_finset v = (G.neighbor_set v).to_finset := rfl

@[simp] lemma mem_neighbor_finset (w : V) :
  w ∈ G.neighbor_finset v ↔ G.adj v w :=
set.mem_to_finset

/--
`G.degree v` is the number of vertices adjacent to `v`.
-/
def degree : ℕ := (G.neighbor_finset v).card

@[simp]
lemma card_neighbor_set_eq_degree : fintype.card (G.neighbor_set v) = G.degree v :=
(set.to_finset_card _).symm

lemma degree_pos_iff_exists_adj : 0 < G.degree v ↔ ∃ w, G.adj v w :=
by simp only [degree, card_pos, finset.nonempty, mem_neighbor_finset]

lemma degree_compl [fintype (Gᶜ.neighbor_set v)] [fintype V] :
  Gᶜ.degree v = fintype.card V - 1 - G.degree v :=
begin
  classical,
  rw [← card_neighbor_set_union_compl_neighbor_set G v, set.to_finset_union],
  simp [card_disjoint_union (set.to_finset_disjoint_iff.mpr (compl_neighbor_set_disjoint G v))],
end

instance incidence_set_fintype [decidable_eq V] : fintype (G.incidence_set v) :=
fintype.of_equiv (G.neighbor_set v) (G.incidence_set_equiv_neighbor_set v).symm

/--
This is the `finset` version of `incidence_set`.
-/
def incidence_finset [decidable_eq V] : finset (sym2 V) := (G.incidence_set v).to_finset

@[simp]
lemma card_incidence_set_eq_degree [decidable_eq V] :
  fintype.card (G.incidence_set v) = G.degree v :=
by { rw fintype.card_congr (G.incidence_set_equiv_neighbor_set v), simp }

@[simp]
lemma card_incidence_finset_eq_degree [decidable_eq V] :
  (G.incidence_finset v).card = G.degree v :=
by { rw ← G.card_incidence_set_eq_degree, apply set.to_finset_card }

@[simp]
lemma mem_incidence_finset [decidable_eq V] (e : sym2 V) :
  e ∈ G.incidence_finset v ↔ e ∈ G.incidence_set v :=
set.mem_to_finset

end finite_at

section locally_finite

/--
A graph is locally finite if every vertex has a finite neighbor set.
-/
@[reducible]
def locally_finite := Π (v : V), fintype (G.neighbor_set v)

variable [locally_finite G]

/--
A locally finite simple graph is regular of degree `d` if every vertex has degree `d`.
-/
def is_regular_of_degree (d : ℕ) : Prop := ∀ (v : V), G.degree v = d

variables {G}

lemma is_regular_of_degree.degree_eq {d : ℕ} (h : G.is_regular_of_degree d) (v : V) :
  G.degree v = d := h v

lemma is_regular_of_degree.compl [fintype V] [decidable_eq V]
  {G : simple_graph V} [decidable_rel G.adj]
  {k : ℕ} (h : G.is_regular_of_degree k) :
  Gᶜ.is_regular_of_degree (fintype.card V - 1 - k) :=
by { intro v, rw [degree_compl, h v] }

end locally_finite

section finite

variable [fintype V]

instance neighbor_set_fintype [decidable_rel G.adj] (v : V) : fintype (G.neighbor_set v) :=
@subtype.fintype _ _ (by { simp_rw mem_neighbor_set, apply_instance }) _

lemma neighbor_finset_eq_filter {v : V} [decidable_rel G.adj] :
  G.neighbor_finset v = finset.univ.filter (G.adj v) :=
by { ext, simp }

lemma neighbor_finset_compl [decidable_eq V] [decidable_rel G.adj] (v : V) :
  Gᶜ.neighbor_finset v = (G.neighbor_finset v)ᶜ \ {v} :=
by simp only [neighbor_finset, neighbor_set_compl, set.to_finset_sdiff, set.to_finset_compl,
    set.to_finset_singleton]

@[simp]
lemma complete_graph_degree [decidable_eq V] (v : V) :
  (⊤ : simple_graph V).degree v = fintype.card V - 1 :=
by erw [degree, neighbor_finset_eq_filter, filter_ne, card_erase_of_mem (mem_univ v), card_univ]

lemma bot_degree (v : V) : (⊥ : simple_graph V).degree v = 0 :=
begin
  erw [degree, neighbor_finset_eq_filter, filter_false],
  exact finset.card_empty,
end

lemma is_regular_of_degree.top [decidable_eq V] :
  (⊤ : simple_graph V).is_regular_of_degree (fintype.card V - 1) :=
by { intro v, simp }

/--
The minimum degree of all vertices (and `0` if there are no vertices).
The key properties of this are given in `exists_minimal_degree_vertex`, `min_degree_le_degree`
and `le_min_degree_of_forall_le_degree`.
-/
def min_degree [decidable_rel G.adj] : ℕ :=
option.get_or_else (univ.image (λ v, G.degree v)).min 0

/--
There exists a vertex of minimal degree. Note the assumption of being nonempty is necessary, as
the lemma implies there exists a vertex.
-/
lemma exists_minimal_degree_vertex [decidable_rel G.adj] [nonempty V] :
  ∃ v, G.min_degree = G.degree v :=
begin
  obtain ⟨t, ht : _ = _⟩ := min_of_nonempty (univ_nonempty.image (λ v, G.degree v)),
  obtain ⟨v, _, rfl⟩ := mem_image.mp (mem_of_min ht),
  refine ⟨v, by simp [min_degree, ht]⟩,
end

/-- The minimum degree in the graph is at most the degree of any particular vertex. -/
lemma min_degree_le_degree [decidable_rel G.adj] (v : V) : G.min_degree ≤ G.degree v :=
begin
  obtain ⟨t, ht⟩ := finset.min_of_mem (mem_image_of_mem (λ v, G.degree v) (mem_univ v)),
  have := finset.min_le_of_mem (mem_image_of_mem _ (mem_univ v)) ht,
  rw option.mem_def at ht,
  rwa [min_degree, ht, option.get_or_else_some],
end

/--
In a nonempty graph, if `k` is at most the degree of every vertex, it is at most the minimum
degree. Note the assumption that the graph is nonempty is necessary as long as `G.min_degree` is
defined to be a natural.
-/
lemma le_min_degree_of_forall_le_degree [decidable_rel G.adj] [nonempty V] (k : ℕ)
  (h : ∀ v, k ≤ G.degree v) :
  k ≤ G.min_degree :=
by { rcases G.exists_minimal_degree_vertex with ⟨v, hv⟩, rw hv, apply h }

/--
The maximum degree of all vertices (and `0` if there are no vertices).
The key properties of this are given in `exists_maximal_degree_vertex`, `degree_le_max_degree`
and `max_degree_le_of_forall_degree_le`.
-/
def max_degree [decidable_rel G.adj] : ℕ :=
option.get_or_else (univ.image (λ v, G.degree v)).max 0

/--
There exists a vertex of maximal degree. Note the assumption of being nonempty is necessary, as
the lemma implies there exists a vertex.
-/
lemma exists_maximal_degree_vertex [decidable_rel G.adj] [nonempty V] :
  ∃ v, G.max_degree = G.degree v :=
begin
  obtain ⟨t, ht⟩ := max_of_nonempty (univ_nonempty.image (λ v, G.degree v)),
  have ht₂ := mem_of_max ht,
  simp only [mem_image, mem_univ, exists_prop_of_true] at ht₂,
  rcases ht₂ with ⟨v, rfl⟩,
  rw option.mem_def at ht,
  refine ⟨v, _⟩,
  rw [max_degree, ht],
  refl
end

/-- The maximum degree in the graph is at least the degree of any particular vertex. -/
lemma degree_le_max_degree [decidable_rel G.adj] (v : V) : G.degree v ≤ G.max_degree :=
begin
  obtain ⟨t, ht : _ = _⟩ := finset.max_of_mem (mem_image_of_mem (λ v, G.degree v) (mem_univ v)),
  have := finset.le_max_of_mem (mem_image_of_mem _ (mem_univ v)) ht,
  rwa [max_degree, ht, option.get_or_else_some],
end

/--
In a graph, if `k` is at least the degree of every vertex, then it is at least the maximum
degree.
-/
lemma max_degree_le_of_forall_degree_le [decidable_rel G.adj] (k : ℕ)
  (h : ∀ v, G.degree v ≤ k) :
  G.max_degree ≤ k :=
begin
  by_cases hV : (univ : finset V).nonempty,
  { haveI : nonempty V := univ_nonempty_iff.mp hV,
    obtain ⟨v, hv⟩ := G.exists_maximal_degree_vertex,
    rw hv,
    apply h },
  { rw not_nonempty_iff_eq_empty at hV,
    rw [max_degree, hV, image_empty],
    exact zero_le k },
end

lemma degree_lt_card_verts [decidable_rel G.adj] (v : V) : G.degree v < fintype.card V :=
begin
  classical,
  apply finset.card_lt_card,
  rw finset.ssubset_iff,
  exact ⟨v, by simp, finset.subset_univ _⟩,
end

/--
The maximum degree of a nonempty graph is less than the number of vertices. Note that the assumption
that `V` is nonempty is necessary, as otherwise this would assert the existence of a
natural number less than zero.
-/
lemma max_degree_lt_card_verts [decidable_rel G.adj] [nonempty V] : G.max_degree < fintype.card V :=
begin
  cases G.exists_maximal_degree_vertex with v hv,
  rw hv,
  apply G.degree_lt_card_verts v,
end

lemma card_common_neighbors_le_degree_left [decidable_rel G.adj] (v w : V) :
  fintype.card (G.common_neighbors v w) ≤ G.degree v :=
begin
  rw [←card_neighbor_set_eq_degree],
  exact set.card_le_of_subset (set.inter_subset_left _ _),
end

lemma card_common_neighbors_lt_card_verts [decidable_rel G.adj] (v w : V) :
  fintype.card (G.common_neighbors v w) < fintype.card V :=
nat.lt_of_le_of_lt (G.card_common_neighbors_le_degree_left _ _) (G.degree_lt_card_verts v)

/--
If the condition `G.adj v w` fails, then `card_common_neighbors_le_degree` is
the best we can do in general.
-/
lemma adj.card_common_neighbors_lt_degree {G : simple_graph V} [decidable_rel G.adj]
  {v w : V} (h : G.adj v w) :
  fintype.card (G.common_neighbors v w) < G.degree v :=
begin
  classical,
  erw [←set.to_finset_card],
  apply finset.card_lt_card,
  rw finset.ssubset_iff,
  use w,
  split,
  { rw set.mem_to_finset,
    apply not_mem_common_neighbors_right },
  { rw finset.insert_subset,
    split,
    { simpa, },
    { rw [neighbor_finset, ← set.subset_iff_to_finset_subset],
      exact G.common_neighbors_subset_neighbor_set_left _ _ } }
end

lemma card_common_neighbors_top [decidable_eq V] {v w : V} (h : v ≠ w) :
  fintype.card ((⊤ : simple_graph V).common_neighbors v w) = fintype.card V - 2 :=
begin
  simp only [common_neighbors_top_eq, ← set.to_finset_card, set.to_finset_sdiff],
  rw finset.card_sdiff,
  { simp [finset.card_univ, h], },
  { simp only [←set.subset_iff_to_finset_subset, set.subset_univ] },
end

end finite

section maps

/--
A graph homomorphism is a map on vertex sets that respects adjacency relations.

The notation `G →g G'` represents the type of graph homomorphisms.
-/
abbreviation hom := rel_hom G.adj G'.adj

/--
A graph embedding is an embedding `f` such that for vertices `v w : V`,
`G.adj f(v) f(w) ↔ G.adj v w `. Its image is an induced subgraph of G'.

The notation `G ↪g G'` represents the type of graph embeddings.
-/
abbreviation embedding := rel_embedding G.adj G'.adj

/--
A graph isomorphism is an bijective map on vertex sets that respects adjacency relations.

The notation `G ≃g G'` represents the type of graph isomorphisms.
-/
abbreviation iso := rel_iso G.adj G'.adj

infix ` →g ` : 50 := hom
infix ` ↪g ` : 50 := embedding
infix ` ≃g ` : 50 := iso

namespace hom
variables {G G'} (f : G →g G')

/-- The identity homomorphism from a graph to itself. -/
abbreviation id : G →g G := rel_hom.id _

lemma map_adj {v w : V} (h : G.adj v w) : G'.adj (f v) (f w) := f.map_rel' h

lemma map_mem_edge_set {e : sym2 V} (h : e ∈ G.edge_set) : e.map f ∈ G'.edge_set :=
quotient.ind (λ e h, sym2.from_rel_prop.mpr (f.map_rel' h)) e h

lemma apply_mem_neighbor_set {v w : V} (h : w ∈ G.neighbor_set v) : f w ∈ G'.neighbor_set (f v) :=
map_adj f h

/-- The map between edge sets induced by a homomorphism.
The underlying map on edges is given by `sym2.map`. -/
@[simps] def map_edge_set (e : G.edge_set) : G'.edge_set :=
⟨sym2.map f e, f.map_mem_edge_set e.property⟩

/-- The map between neighbor sets induced by a homomorphism. -/
@[simps] def map_neighbor_set (v : V) (w : G.neighbor_set v) : G'.neighbor_set (f v) :=
⟨f w, f.apply_mem_neighbor_set w.property⟩

/-- The induced map for spanning subgraphs, which is the identity on vertices. -/
def map_spanning_subgraphs {G G' : simple_graph V} (h : G ≤ G') : G →g G' :=
{ to_fun := λ x, x,
  map_rel' := h }

lemma map_edge_set.injective (hinj : function.injective f) : function.injective f.map_edge_set :=
begin
  rintros ⟨e₁, h₁⟩ ⟨e₂, h₂⟩,
  dsimp [hom.map_edge_set],
  repeat { rw subtype.mk_eq_mk },
  apply sym2.map.injective hinj,
end

variable {G'' : simple_graph X}

/-- Composition of graph homomorphisms. -/
abbreviation comp (f' : G' →g G'') (f : G →g G') : G →g G'' := f'.comp f

@[simp] lemma coe_comp (f' : G' →g G'') (f : G →g G') : ⇑(f'.comp f) = f' ∘ f := rfl

end hom

namespace embedding
variables {G G'} (f : G ↪g G')

/-- The identity embedding from a graph to itself. -/
abbreviation refl : G ↪g G := rel_embedding.refl _

/-- An embedding of graphs gives rise to a homomorphism of graphs. -/
abbreviation to_hom : G →g G' := f.to_rel_hom

lemma map_adj_iff {v w : V} : G'.adj (f v) (f w) ↔ G.adj v w := f.map_rel_iff

lemma map_mem_edge_set_iff {e : sym2 V} : e.map f ∈ G'.edge_set ↔ e ∈ G.edge_set :=
quotient.ind (λ ⟨v, w⟩, f.map_adj_iff) e

lemma apply_mem_neighbor_set_iff {v w : V} : f w ∈ G'.neighbor_set (f v) ↔ w ∈ G.neighbor_set v :=
map_adj_iff f

/-- A graph embedding induces an embedding of edge sets. -/
@[simps] def map_edge_set : G.edge_set ↪ G'.edge_set :=
{ to_fun := hom.map_edge_set f,
  inj' := hom.map_edge_set.injective f f.inj' }

/-- A graph embedding induces an embedding of neighbor sets. -/
@[simps] def map_neighbor_set (v : V) : G.neighbor_set v ↪ G'.neighbor_set (f v) :=
{ to_fun := λ w, ⟨f w, f.apply_mem_neighbor_set_iff.mpr w.2⟩,
  inj' := begin
    rintros ⟨w₁, h₁⟩ ⟨w₂, h₂⟩ h,
    rw subtype.mk_eq_mk at h ⊢,
    exact f.inj' h,
  end }

/-- Embeddings of types induce embeddings of complete graphs on those types. -/
def complete_graph.of_embedding {α β : Type*} (f : α ↪ β) : complete_graph α ↪g complete_graph β :=
{ to_fun := f,
  inj' := f.inj',
  map_rel_iff' := by simp }

variables {G'' : simple_graph X}

/-- Composition of graph embeddings. -/
abbreviation comp (f' : G' ↪g G'') (f : G ↪g G') : G ↪g G'' := f.trans f'

@[simp] lemma coe_comp (f' : G' ↪g G'') (f : G ↪g G') : ⇑(f'.comp f) = f' ∘ f := rfl

end embedding

namespace iso
variables {G G'} (f : G ≃g G')

/-- The identity isomorphism of a graph with itself. -/
abbreviation refl : G ≃g G := rel_iso.refl _

/-- An isomorphism of graphs gives rise to an embedding of graphs. -/
abbreviation to_embedding : G ↪g G' := f.to_rel_embedding

/-- An isomorphism of graphs gives rise to a homomorphism of graphs. -/
abbreviation to_hom : G →g G' := f.to_embedding.to_hom

/-- The inverse of a graph isomorphism. --/
abbreviation symm : G' ≃g G := f.symm

lemma map_adj_iff {v w : V} : G'.adj (f v) (f w) ↔ G.adj v w := f.map_rel_iff

lemma map_mem_edge_set_iff {e : sym2 V} : e.map f ∈ G'.edge_set ↔ e ∈ G.edge_set :=
quotient.ind (λ ⟨v, w⟩, f.map_adj_iff) e

lemma apply_mem_neighbor_set_iff {v w : V} : f w ∈ G'.neighbor_set (f v) ↔ w ∈ G.neighbor_set v :=
map_adj_iff f

/-- An isomorphism of graphs induces an equivalence of edge sets. -/
@[simps] def map_edge_set : G.edge_set ≃ G'.edge_set :=
{ to_fun := hom.map_edge_set f,
  inv_fun := hom.map_edge_set f.symm,
  left_inv := begin
    rintro ⟨e, h⟩,
    simp only [hom.map_edge_set, sym2.map_map, rel_iso.coe_coe_fn,
      rel_embedding.coe_coe_fn, subtype.mk_eq_mk, subtype.coe_mk, coe_coe],
    apply congr_fun,
    convert sym2.map_id,
    exact funext (λ _, rel_iso.symm_apply_apply _ _),
  end,
  right_inv := begin
    rintro ⟨e, h⟩,
    simp only [hom.map_edge_set, sym2.map_map, rel_iso.coe_coe_fn,
      rel_embedding.coe_coe_fn, subtype.mk_eq_mk, subtype.coe_mk, coe_coe],
    apply congr_fun,
    convert sym2.map_id,
    exact funext (λ _, rel_iso.apply_symm_apply _ _),
  end }

/-- A graph isomorphism induces an equivalence of neighbor sets. -/
@[simps] def map_neighbor_set (v : V) : G.neighbor_set v ≃ G'.neighbor_set (f v) :=
{ to_fun := λ w, ⟨f w, f.apply_mem_neighbor_set_iff.mpr w.2⟩,
  inv_fun := λ w, ⟨f.symm w, begin
    convert f.symm.apply_mem_neighbor_set_iff.mpr w.2,
    simp only [rel_iso.symm_apply_apply],
  end⟩,
  left_inv := λ w, by simp,
  right_inv := λ w, by simp }

lemma card_eq_of_iso [fintype V] [fintype W] (f : G ≃g G') : fintype.card V = fintype.card W :=
by convert (fintype.of_equiv_card f.to_equiv).symm

variables {G'' : simple_graph X}

/-- Composition of graph isomorphisms. -/
abbreviation comp (f' : G' ≃g G'') (f : G ≃g G') : G ≃g G'' := f.trans f'

@[simp] lemma coe_comp (f' : G' ≃g G'') (f : G ≃g G') : ⇑(f'.comp f) = f' ∘ f := rfl

end iso

end maps

end simple_graph


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

-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.padics.padic_norm
import Mathlib.analysis.normed_space.basic
import Mathlib.PostPort

namespace Mathlib

/-!
# p-adic numbers

This file defines the p-adic numbers (rationals) `ℚ_p` as the completion of `ℚ` with respect to the
p-adic norm. We show that the p-adic norm on ℚ extends to `ℚ_p`, that `ℚ` is embedded in `ℚ_p`, and that
`ℚ_p` is Cauchy complete.

## Important definitions

* `padic` : the type of p-adic numbers
* `padic_norm_e` : the rational valued p-adic norm on `ℚ_p`

## Notation

We introduce the notation `ℚ_[p]` for the p-adic numbers.

## Implementation notes

Much, but not all, of this file assumes that `p` is prime. This assumption is inferred automatically
by taking `[fact (prime p)]` as a type class argument.

We use the same concrete Cauchy sequence construction that is used to construct ℝ. `ℚ_p` inherits a
field structure from this construction. The extension of the norm on ℚ to `ℚ_p` is *not* analogous to
extending the absolute value to ℝ, and hence the proof that `ℚ_p` is complete is different from the
proof that ℝ is complete.

A small special-purpose simplification tactic, `padic_index_simp`, is used to manipulate sequence
indices in the proof that the norm extends.

`padic_norm_e` is the rational-valued p-adic norm on `ℚ_p`. To instantiate `ℚ_p` as a normed field,
we must cast this into a ℝ-valued norm. The `ℝ`-valued norm, using notation `∥ ∥` from normed spaces,
is the canonical representation of this norm.

`simp` prefers `padic_norm` to `padic_norm_e` when possible.
Since `padic_norm_e` and `∥ ∥` have different types, `simp` does not rewrite one to the other.

Coercions from `ℚ` to `ℚ_p` are set up to work with the `norm_cast` tactic.

## References

* [F. Q. Gouêva, *p-adic numbers*][gouvea1997]
* [R. Y. Lewis, *A formal proof of Hensel's lemma over the p-adic integers*][lewis2019]
* <https://en.wikipedia.org/wiki/P-adic_number>

## Tags

p-adic, p adic, padic, norm, valuation, cauchy, completion, p-adic completion
-/

/-- The type of Cauchy sequences of rationals with respect to the p-adic norm. -/
def padic_seq (p : ℕ) := cau_seq ℚ (padic_norm p)

namespace padic_seq

/-- The p-adic norm of the entries of a nonzero Cauchy sequence of rationals is eventually
constant. -/
theorem stationary {p : ℕ} [fact (nat.prime p)] {f : cau_seq ℚ (padic_norm p)} (hf : ¬f ≈ 0) :
    ∃ (N : ℕ), ∀ (m n : ℕ), N ≤ m → N ≤ n → padic_norm p (coe_fn f n) = padic_norm p (coe_fn f m) :=
  sorry

/-- For all n ≥ stationary_point f hf, the p-adic norm of f n is the same. -/
def stationary_point {p : ℕ} [fact (nat.prime p)] {f : padic_seq p} (hf : ¬f ≈ 0) : ℕ :=
  classical.some (stationary hf)

theorem stationary_point_spec {p : ℕ} [fact (nat.prime p)] {f : padic_seq p} (hf : ¬f ≈ 0) {m : ℕ}
    {n : ℕ} :
    stationary_point hf ≤ m →
        stationary_point hf ≤ n → padic_norm p (coe_fn f n) = padic_norm p (coe_fn f m) :=
  classical.some_spec (stationary hf)

/-- Since the norm of the entries of a Cauchy sequence is eventually stationary, we can lift the norm
to sequences. -/
def norm {p : ℕ} [fact (nat.prime p)] (f : padic_seq p) : ℚ :=
  dite (f ≈ 0) (fun (hf : f ≈ 0) => 0)
    fun (hf : ¬f ≈ 0) => padic_norm p (coe_fn f (stationary_point hf))

theorem norm_zero_iff {p : ℕ} [fact (nat.prime p)] (f : padic_seq p) : norm f = 0 ↔ f ≈ 0 := sorry

theorem equiv_zero_of_val_eq_of_equiv_zero {p : ℕ} [fact (nat.prime p)] {f : padic_seq p}
    {g : padic_seq p} (h : ∀ (k : ℕ), padic_norm p (coe_fn f k) = padic_norm p (coe_fn g k))
    (hf : f ≈ 0) : g ≈ 0 :=
  sorry

theorem norm_nonzero_of_not_equiv_zero {p : ℕ} [fact (nat.prime p)] {f : padic_seq p}
    (hf : ¬f ≈ 0) : norm f ≠ 0 :=
  hf ∘ iff.mp (norm_zero_iff f)

theorem norm_eq_norm_app_of_nonzero {p : ℕ} [fact (nat.prime p)] {f : padic_seq p} (hf : ¬f ≈ 0) :
    ∃ (k : ℚ), norm f = padic_norm p k ∧ k ≠ 0 :=
  sorry

theorem not_lim_zero_const_of_nonzero {p : ℕ} [fact (nat.prime p)] {q : ℚ} (hq : q ≠ 0) :
    ¬cau_seq.lim_zero (cau_seq.const (padic_norm p) q) :=
  fun (h' : cau_seq.lim_zero (cau_seq.const (padic_norm p) q)) =>
    hq (iff.mp cau_seq.const_lim_zero h')

theorem not_equiv_zero_const_of_nonzero {p : ℕ} [fact (nat.prime p)] {q : ℚ} (hq : q ≠ 0) :
    ¬cau_seq.const (padic_norm p) q ≈ 0 :=
  sorry

theorem norm_nonneg {p : ℕ} [fact (nat.prime p)] (f : padic_seq p) : 0 ≤ norm f := sorry

/-- An auxiliary lemma for manipulating sequence indices. -/
theorem lift_index_left_left {p : ℕ} [fact (nat.prime p)] {f : padic_seq p} (hf : ¬f ≈ 0) (v2 : ℕ)
    (v3 : ℕ) :
    padic_norm p (coe_fn f (stationary_point hf)) =
        padic_norm p (coe_fn f (max (stationary_point hf) (max v2 v3))) :=
  stationary_point_spec hf (le_max_left (stationary_point hf) (max v2 v3))
    (le_refl (stationary_point hf))

/-- An auxiliary lemma for manipulating sequence indices. -/
theorem lift_index_left {p : ℕ} [fact (nat.prime p)] {f : padic_seq p} (hf : ¬f ≈ 0) (v1 : ℕ)
    (v3 : ℕ) :
    padic_norm p (coe_fn f (stationary_point hf)) =
        padic_norm p (coe_fn f (max v1 (max (stationary_point hf) v3))) :=
  stationary_point_spec hf
    (le_trans (le_max_left (stationary_point hf) v3)
      (le_max_right v1 (max (stationary_point hf) v3)))
    (le_refl (stationary_point hf))

/-- An auxiliary lemma for manipulating sequence indices. -/
theorem lift_index_right {p : ℕ} [fact (nat.prime p)] {f : padic_seq p} (hf : ¬f ≈ 0) (v1 : ℕ)
    (v2 : ℕ) :
    padic_norm p (coe_fn f (stationary_point hf)) =
        padic_norm p (coe_fn f (max v1 (max v2 (stationary_point hf)))) :=
  stationary_point_spec hf
    (le_trans (le_max_right v2 (stationary_point hf))
      (le_max_right v1 (max v2 (stationary_point hf))))
    (le_refl (stationary_point hf))

/-! ### Valuation on `padic_seq` -/

/--
The `p`-adic valuation on `ℚ` lifts to `padic_seq p`.
`valuation f` is defined to be the valuation of the (`ℚ`-valued) stationary point of `f`.
-/
def valuation {p : ℕ} [fact (nat.prime p)] (f : padic_seq p) : ℤ :=
  dite (f ≈ 0) (fun (hf : f ≈ 0) => 0)
    fun (hf : ¬f ≈ 0) => padic_val_rat p (coe_fn f (stationary_point hf))

theorem norm_eq_pow_val {p : ℕ} [fact (nat.prime p)] {f : padic_seq p} (hf : ¬f ≈ 0) :
    norm f = ↑p ^ (-valuation f) :=
  sorry

theorem val_eq_iff_norm_eq {p : ℕ} [fact (nat.prime p)] {f : padic_seq p} {g : padic_seq p}
    (hf : ¬f ≈ 0) (hg : ¬g ≈ 0) : valuation f = valuation g ↔ norm f = norm g :=
  sorry

end padic_seq

/--
  This is a special-purpose tactic that lifts padic_norm (f (stationary_point f)) to
  padic_norm (f (max _ _ _)).
-/
namespace padic_seq

theorem norm_mul {p : ℕ} [hp : fact (nat.prime p)] (f : padic_seq p) (g : padic_seq p) :
    norm (f * g) = norm f * norm g :=
  sorry

theorem eq_zero_iff_equiv_zero {p : ℕ} [hp : fact (nat.prime p)] (f : padic_seq p) :
    cau_seq.completion.mk f = 0 ↔ f ≈ 0 :=
  cau_seq.completion.mk_eq

theorem ne_zero_iff_nequiv_zero {p : ℕ} [hp : fact (nat.prime p)] (f : padic_seq p) :
    cau_seq.completion.mk f ≠ 0 ↔ ¬f ≈ 0 :=
  iff.mpr not_iff_not (eq_zero_iff_equiv_zero f)

theorem norm_const {p : ℕ} [hp : fact (nat.prime p)] (q : ℚ) :
    norm (cau_seq.const (padic_norm p) q) = padic_norm p q :=
  sorry

theorem norm_values_discrete {p : ℕ} [hp : fact (nat.prime p)] (a : padic_seq p) (ha : ¬a ≈ 0) :
    ∃ (z : ℤ), norm a = ↑p ^ (-z) :=
  sorry

theorem norm_one {p : ℕ} [hp : fact (nat.prime p)] : norm 1 = 1 := sorry

theorem norm_equiv {p : ℕ} [hp : fact (nat.prime p)] {f : padic_seq p} {g : padic_seq p}
    (hfg : f ≈ g) : norm f = norm g :=
  sorry

theorem norm_nonarchimedean {p : ℕ} [hp : fact (nat.prime p)] (f : padic_seq p) (g : padic_seq p) :
    norm (f + g) ≤ max (norm f) (norm g) :=
  sorry

theorem norm_eq {p : ℕ} [hp : fact (nat.prime p)] {f : padic_seq p} {g : padic_seq p}
    (h : ∀ (k : ℕ), padic_norm p (coe_fn f k) = padic_norm p (coe_fn g k)) : norm f = norm g :=
  sorry

theorem norm_neg {p : ℕ} [hp : fact (nat.prime p)] (a : padic_seq p) : norm (-a) = norm a := sorry

theorem norm_eq_of_add_equiv_zero {p : ℕ} [hp : fact (nat.prime p)] {f : padic_seq p}
    {g : padic_seq p} (h : f + g ≈ 0) : norm f = norm g :=
  sorry

theorem add_eq_max_of_ne {p : ℕ} [hp : fact (nat.prime p)] {f : padic_seq p} {g : padic_seq p}
    (hfgne : norm f ≠ norm g) : norm (f + g) = max (norm f) (norm g) :=
  sorry

end padic_seq

/-- The p-adic numbers `Q_[p]` are the Cauchy completion of `ℚ` with respect to the p-adic norm. -/
def padic (p : ℕ) [fact (nat.prime p)] := cau_seq.completion.Cauchy

namespace padic

/-- The discrete field structure on `ℚ_p` is inherited from the Cauchy completion construction. -/
protected instance field {p : ℕ} [fact (nat.prime p)] : field (padic p) := cau_seq.completion.field

protected instance inhabited {p : ℕ} [fact (nat.prime p)] : Inhabited (padic p) := { default := 0 }

-- short circuits

protected instance has_zero {p : ℕ} [fact (nat.prime p)] : HasZero (padic p) :=
  mul_zero_class.to_has_zero (padic p)

protected instance has_one {p : ℕ} [fact (nat.prime p)] : HasOne (padic p) :=
  monoid.to_has_one (padic p)

protected instance has_add {p : ℕ} [fact (nat.prime p)] : Add (padic p) :=
  distrib.to_has_add (padic p)

protected instance has_mul {p : ℕ} [fact (nat.prime p)] : Mul (padic p) :=
  distrib.to_has_mul (padic p)

protected instance has_sub {p : ℕ} [fact (nat.prime p)] : Sub (padic p) :=
  sub_neg_monoid.to_has_sub (padic p)

protected instance has_neg {p : ℕ} [fact (nat.prime p)] : Neg (padic p) :=
  sub_neg_monoid.to_has_neg (padic p)

protected instance has_div {p : ℕ} [fact (nat.prime p)] : Div (padic p) :=
  div_inv_monoid.to_has_div (padic p)

protected instance add_comm_group {p : ℕ} [fact (nat.prime p)] : add_comm_group (padic p) :=
  ring.to_add_comm_group (padic p)

protected instance comm_ring {p : ℕ} [fact (nat.prime p)] : comm_ring (padic p) :=
  euclidean_domain.to_comm_ring (padic p)

/-- Builds the equivalence class of a Cauchy sequence of rationals. -/
def mk {p : ℕ} [fact (nat.prime p)] : padic_seq p → padic p := quotient.mk

theorem mk_eq (p : ℕ) [fact (nat.prime p)] {f : padic_seq p} {g : padic_seq p} :
    mk f = mk g ↔ f ≈ g :=
  quotient.eq

/-- Embeds the rational numbers in the p-adic numbers. -/
def of_rat (p : ℕ) [fact (nat.prime p)] : ℚ → padic p := cau_seq.completion.of_rat

@[simp] theorem of_rat_add (p : ℕ) [fact (nat.prime p)] (x : ℚ) (y : ℚ) :
    of_rat p (x + y) = of_rat p x + of_rat p y :=
  cau_seq.completion.of_rat_add

@[simp] theorem of_rat_neg (p : ℕ) [fact (nat.prime p)] (x : ℚ) : of_rat p (-x) = -of_rat p x :=
  cau_seq.completion.of_rat_neg

@[simp] theorem of_rat_mul (p : ℕ) [fact (nat.prime p)] (x : ℚ) (y : ℚ) :
    of_rat p (x * y) = of_rat p x * of_rat p y :=
  cau_seq.completion.of_rat_mul

@[simp] theorem of_rat_sub (p : ℕ) [fact (nat.prime p)] (x : ℚ) (y : ℚ) :
    of_rat p (x - y) = of_rat p x - of_rat p y :=
  cau_seq.completion.of_rat_sub

@[simp] theorem of_rat_div (p : ℕ) [fact (nat.prime p)] (x : ℚ) (y : ℚ) :
    of_rat p (x / y) = of_rat p x / of_rat p y :=
  cau_seq.completion.of_rat_div

@[simp] theorem of_rat_one (p : ℕ) [fact (nat.prime p)] : of_rat p 1 = 1 := rfl

@[simp] theorem of_rat_zero (p : ℕ) [fact (nat.prime p)] : of_rat p 0 = 0 := rfl

theorem cast_eq_of_rat_of_nat (p : ℕ) [fact (nat.prime p)] (n : ℕ) : ↑n = of_rat p ↑n := sorry

theorem cast_eq_of_rat_of_int (p : ℕ) [fact (nat.prime p)] (n : ℤ) : ↑n = of_rat p ↑n := sorry

theorem cast_eq_of_rat (p : ℕ) [fact (nat.prime p)] (q : ℚ) : ↑q = of_rat p q := sorry

theorem coe_add (p : ℕ) [fact (nat.prime p)] {x : ℚ} {y : ℚ} : ↑(x + y) = ↑x + ↑y := sorry

theorem coe_neg (p : ℕ) [fact (nat.prime p)] {x : ℚ} : ↑(-x) = -↑x := sorry

theorem coe_mul (p : ℕ) [fact (nat.prime p)] {x : ℚ} {y : ℚ} : ↑(x * y) = ↑x * ↑y := sorry

theorem coe_sub (p : ℕ) [fact (nat.prime p)] {x : ℚ} {y : ℚ} : ↑(x - y) = ↑x - ↑y := sorry

theorem coe_div (p : ℕ) [fact (nat.prime p)] {x : ℚ} {y : ℚ} : ↑(x / y) = ↑x / ↑y := sorry

theorem coe_one (p : ℕ) [fact (nat.prime p)] : ↑1 = 1 := rfl

theorem coe_zero (p : ℕ) [fact (nat.prime p)] : ↑0 = 0 := rfl

theorem const_equiv (p : ℕ) [fact (nat.prime p)] {q : ℚ} {r : ℚ} :
    cau_seq.const (padic_norm p) q ≈ cau_seq.const (padic_norm p) r ↔ q = r :=
  sorry

theorem of_rat_eq (p : ℕ) [fact (nat.prime p)] {q : ℚ} {r : ℚ} : of_rat p q = of_rat p r ↔ q = r :=
  { mp := iff.mp (const_equiv p) ∘ iff.mp quotient.eq,
    mpr :=
      fun (h : q = r) =>
        eq.mpr (id (Eq._oldrec (Eq.refl (of_rat p q = of_rat p r)) h)) (Eq.refl (of_rat p r)) }

theorem coe_inj (p : ℕ) [fact (nat.prime p)] {q : ℚ} {r : ℚ} : ↑q = ↑r ↔ q = r := sorry

protected instance char_zero (p : ℕ) [fact (nat.prime p)] : char_zero (padic p) :=
  char_zero.mk
    fun (m n : ℕ) =>
      eq.mpr (id (Eq._oldrec (Eq.refl (↑m = ↑n → m = n)) (Eq.symm (rat.cast_coe_nat m))))
        (eq.mpr
          (id
            (imp_congr_eq
              (Eq.trans
                (Eq.trans (congr_arg (Eq ↑↑m) (Eq.symm (rat.cast_coe_nat n))) (propext (coe_inj p)))
                (propext nat.cast_inj))
              (Eq.refl (m = n))))
          id)

end padic

/-- The rational-valued p-adic norm on `ℚ_p` is lifted from the norm on Cauchy sequences. The
canonical form of this function is the normed space instance, with notation `∥ ∥`. -/
def padic_norm_e {p : ℕ} [hp : fact (nat.prime p)] : padic p → ℚ :=
  quotient.lift padic_seq.norm padic_seq.norm_equiv

namespace padic_norm_e

theorem defn {p : ℕ} [fact (nat.prime p)] (f : padic_seq p) {ε : ℚ} (hε : 0 < ε) :
    ∃ (N : ℕ), ∀ (i : ℕ), i ≥ N → padic_norm_e (quotient.mk f - ↑(coe_fn f i)) < ε :=
  sorry

protected theorem nonneg {p : ℕ} [fact (nat.prime p)] (q : padic p) : 0 ≤ padic_norm_e q :=
  quotient.induction_on q padic_seq.norm_nonneg

theorem zero_def {p : ℕ} [fact (nat.prime p)] : 0 = quotient.mk 0 := rfl

theorem zero_iff {p : ℕ} [fact (nat.prime p)] (q : padic p) : padic_norm_e q = 0 ↔ q = 0 := sorry

@[simp] protected theorem zero {p : ℕ} [fact (nat.prime p)] : padic_norm_e 0 = 0 :=
  iff.mpr (zero_iff 0) rfl

/-- Theorems about `padic_norm_e` are named with a `'` so the names do not conflict with the
equivalent theorems about `norm` (`∥ ∥`). -/
@[simp] protected theorem one' {p : ℕ} [fact (nat.prime p)] : padic_norm_e 1 = 1 :=
  padic_seq.norm_one

@[simp] protected theorem neg {p : ℕ} [fact (nat.prime p)] (q : padic p) :
    padic_norm_e (-q) = padic_norm_e q :=
  quotient.induction_on q padic_seq.norm_neg

/-- Theorems about `padic_norm_e` are named with a `'` so the names do not conflict with the
equivalent theorems about `norm` (`∥ ∥`). -/
theorem nonarchimedean' {p : ℕ} [fact (nat.prime p)] (q : padic p) (r : padic p) :
    padic_norm_e (q + r) ≤ max (padic_norm_e q) (padic_norm_e r) :=
  quotient.induction_on₂ q r padic_seq.norm_nonarchimedean

/-- Theorems about `padic_norm_e` are named with a `'` so the names do not conflict with the
equivalent theorems about `norm` (`∥ ∥`). -/
theorem add_eq_max_of_ne' {p : ℕ} [fact (nat.prime p)] {q : padic p} {r : padic p} :
    padic_norm_e q ≠ padic_norm_e r →
        padic_norm_e (q + r) = max (padic_norm_e q) (padic_norm_e r) :=
  quotient.induction_on₂ q r fun (_x _x_1 : cau_seq ℚ (padic_norm p)) => padic_seq.add_eq_max_of_ne

theorem triangle_ineq {p : ℕ} [fact (nat.prime p)] (x : padic p) (y : padic p) (z : padic p) :
    padic_norm_e (x - z) ≤ padic_norm_e (x - y) + padic_norm_e (y - z) :=
  sorry

protected theorem add {p : ℕ} [fact (nat.prime p)] (q : padic p) (r : padic p) :
    padic_norm_e (q + r) ≤ padic_norm_e q + padic_norm_e r :=
  le_trans (nonarchimedean' q r)
    (max_le_add_of_nonneg (padic_norm_e.nonneg q) (padic_norm_e.nonneg r))

protected theorem mul' {p : ℕ} [fact (nat.prime p)] (q : padic p) (r : padic p) :
    padic_norm_e (q * r) = padic_norm_e q * padic_norm_e r :=
  quotient.induction_on₂ q r padic_seq.norm_mul

protected instance is_absolute_value {p : ℕ} [fact (nat.prime p)] :
    is_absolute_value padic_norm_e :=
  is_absolute_value.mk padic_norm_e.nonneg zero_iff padic_norm_e.add padic_norm_e.mul'

@[simp] theorem eq_padic_norm' {p : ℕ} [fact (nat.prime p)] (q : ℚ) :
    padic_norm_e (padic.of_rat p q) = padic_norm p q :=
  padic_seq.norm_const q

protected theorem image' {p : ℕ} [fact (nat.prime p)] {q : padic p} :
    q ≠ 0 → ∃ (n : ℤ), padic_norm_e q = ↑p ^ (-n) :=
  quotient.induction_on q
    fun (f : cau_seq ℚ (padic_norm p)) (hf : quotient.mk f ≠ 0) =>
      (fun (this : ¬f ≈ 0) => padic_seq.norm_values_discrete f this)
        (iff.mp (padic_seq.ne_zero_iff_nequiv_zero f) hf)

theorem sub_rev {p : ℕ} [fact (nat.prime p)] (q : padic p) (r : padic p) :
    padic_norm_e (q - r) = padic_norm_e (r - q) :=
  sorry

end padic_norm_e

namespace padic

theorem rat_dense' {p : ℕ} [fact (nat.prime p)] (q : padic p) {ε : ℚ} (hε : 0 < ε) :
    ∃ (r : ℚ), padic_norm_e (q - ↑r) < ε :=
  sorry

/-- `lim_seq f`, for `f` a Cauchy sequence of `p`-adic numbers,
is a sequence of rationals with the same limit point as `f`. -/
def lim_seq {p : ℕ} [fact (nat.prime p)] (f : cau_seq (padic p) padic_norm_e) : ℕ → ℚ :=
  fun (n : ℕ) => classical.some sorry

theorem exi_rat_seq_conv {p : ℕ} [fact (nat.prime p)] (f : cau_seq (padic p) padic_norm_e) {ε : ℚ}
    (hε : 0 < ε) : ∃ (N : ℕ), ∀ (i : ℕ), i ≥ N → padic_norm_e (coe_fn f i - ↑(lim_seq f i)) < ε :=
  sorry

theorem exi_rat_seq_conv_cauchy {p : ℕ} [fact (nat.prime p)] (f : cau_seq (padic p) padic_norm_e) :
    is_cau_seq (padic_norm p) (lim_seq f) :=
  sorry

theorem complete' {p : ℕ} [fact (nat.prime p)] (f : cau_seq (padic p) padic_norm_e) :
    ∃ (q : padic p),
        ∀ (ε : ℚ), ε > 0 → ∃ (N : ℕ), ∀ (i : ℕ), i ≥ N → padic_norm_e (q - coe_fn f i) < ε :=
  sorry

protected instance has_dist (p : ℕ) [fact (nat.prime p)] : has_dist (padic p) :=
  has_dist.mk fun (x y : padic p) => ↑(padic_norm_e (x - y))

protected instance metric_space (p : ℕ) [fact (nat.prime p)] : metric_space (padic p) :=
  metric_space.mk sorry sorry sorry sorry
    (fun (x y : padic p) => ennreal.of_real ((fun (x y : padic p) => ↑(padic_norm_e (x - y))) x y))
    (uniform_space_of_dist (fun (x y : padic p) => ↑(padic_norm_e (x - y))) sorry sorry sorry)

protected instance has_norm (p : ℕ) [fact (nat.prime p)] : has_norm (padic p) :=
  has_norm.mk fun (x : padic p) => ↑(padic_norm_e x)

protected instance normed_field (p : ℕ) [fact (nat.prime p)] : normed_field (padic p) :=
  normed_field.mk sorry sorry

protected instance is_absolute_value (p : ℕ) [fact (nat.prime p)] :
    is_absolute_value fun (a : padic p) => norm a :=
  sorry

theorem rat_dense {p : ℕ} {hp : fact (nat.prime p)} (q : padic p) {ε : ℝ} (hε : 0 < ε) :
    ∃ (r : ℚ), norm (q - ↑r) < ε :=
  sorry

end padic

namespace padic_norm_e

@[simp] protected theorem mul {p : ℕ} [hp : fact (nat.prime p)] (q : padic p) (r : padic p) :
    norm (q * r) = norm q * norm r :=
  sorry

protected theorem is_norm {p : ℕ} [hp : fact (nat.prime p)] (q : padic p) :
    ↑(padic_norm_e q) = norm q :=
  rfl

theorem nonarchimedean {p : ℕ} [hp : fact (nat.prime p)] (q : padic p) (r : padic p) :
    norm (q + r) ≤ max (norm q) (norm r) :=
  sorry

theorem add_eq_max_of_ne {p : ℕ} [hp : fact (nat.prime p)] {q : padic p} {r : padic p}
    (h : norm q ≠ norm r) : norm (q + r) = max (norm q) (norm r) :=
  sorry

@[simp] theorem eq_padic_norm {p : ℕ} [hp : fact (nat.prime p)] (q : ℚ) :
    norm ↑q = ↑(padic_norm p q) :=
  sorry

protected instance padic.nondiscrete_normed_field {p : ℕ} [hp : fact (nat.prime p)] :
    nondiscrete_normed_field (padic p) :=
  nondiscrete_normed_field.mk sorry

@[simp] theorem norm_p {p : ℕ} [hp : fact (nat.prime p)] : norm ↑p = (↑p⁻¹) := sorry

theorem norm_p_lt_one {p : ℕ} [hp : fact (nat.prime p)] : norm ↑p < 1 := sorry

@[simp] theorem norm_p_pow {p : ℕ} [hp : fact (nat.prime p)] (n : ℤ) : norm (↑p ^ n) = ↑p ^ (-n) :=
  sorry

protected theorem image {p : ℕ} [hp : fact (nat.prime p)] {q : padic p} :
    q ≠ 0 → ∃ (n : ℤ), norm q = ↑(↑p ^ (-n)) :=
  sorry

protected theorem is_rat {p : ℕ} [hp : fact (nat.prime p)] (q : padic p) :
    ∃ (q' : ℚ), norm q = ↑q' :=
  sorry

/--`rat_norm q`, for a `p`-adic number `q` is the `p`-adic norm of `q`, as rational number.

The lemma `padic_norm_e.eq_rat_norm` asserts `∥q∥ = rat_norm q`. -/
def rat_norm {p : ℕ} [hp : fact (nat.prime p)] (q : padic p) : ℚ :=
  classical.some (padic_norm_e.is_rat q)

theorem eq_rat_norm {p : ℕ} [hp : fact (nat.prime p)] (q : padic p) : norm q = ↑(rat_norm q) :=
  classical.some_spec (padic_norm_e.is_rat q)

theorem norm_rat_le_one {p : ℕ} [hp : fact (nat.prime p)] {q : ℚ} (hq : ¬p ∣ rat.denom q) :
    norm ↑q ≤ 1 :=
  sorry

theorem norm_int_le_one {p : ℕ} [hp : fact (nat.prime p)] (z : ℤ) : norm ↑z ≤ 1 := sorry

theorem norm_int_lt_one_iff_dvd {p : ℕ} [hp : fact (nat.prime p)] (k : ℤ) : norm ↑k < 1 ↔ ↑p ∣ k :=
  sorry

theorem norm_int_le_pow_iff_dvd {p : ℕ} [hp : fact (nat.prime p)] (k : ℤ) (n : ℕ) :
    norm ↑k ≤ ↑p ^ (-↑n) ↔ ↑(p ^ n) ∣ k :=
  sorry

theorem eq_of_norm_add_lt_right {p : ℕ} {hp : fact (nat.prime p)} {z1 : padic p} {z2 : padic p}
    (h : norm (z1 + z2) < norm z2) : norm z1 = norm z2 :=
  sorry

theorem eq_of_norm_add_lt_left {p : ℕ} {hp : fact (nat.prime p)} {z1 : padic p} {z2 : padic p}
    (h : norm (z1 + z2) < norm z1) : norm z1 = norm z2 :=
  sorry

end padic_norm_e

namespace padic

protected instance complete {p : ℕ} [fact (nat.prime p)] : cau_seq.is_complete (padic p) norm :=
  cau_seq.is_complete.mk sorry

theorem padic_norm_e_lim_le {p : ℕ} [fact (nat.prime p)] {f : cau_seq (padic p) norm} {a : ℝ}
    (ha : 0 < a) (hf : ∀ (i : ℕ), norm (coe_fn f i) ≤ a) : norm (cau_seq.lim f) ≤ a :=
  sorry

/-!
### Valuation on `ℚ_[p]`
-/

/--
`padic.valuation` lifts the p-adic valuation on rationals to `ℚ_[p]`.
-/
def valuation {p : ℕ} [fact (nat.prime p)] : padic p → ℤ := quotient.lift padic_seq.valuation sorry

@[simp] theorem valuation_zero {p : ℕ} [fact (nat.prime p)] : valuation 0 = 0 :=
  dif_pos (iff.mpr (const_equiv p) rfl)

@[simp] theorem valuation_one {p : ℕ} [fact (nat.prime p)] : valuation 1 = 0 := sorry

theorem norm_eq_pow_val {p : ℕ} [fact (nat.prime p)] {x : padic p} :
    x ≠ 0 → norm x = ↑p ^ (-valuation x) :=
  sorry

@[simp] theorem valuation_p {p : ℕ} [fact (nat.prime p)] : valuation ↑p = 1 := sorry

end Mathlib


import data.fin.basic
import data.nat.basic
import data.nat.dist
import data.multiset.basic
import .dists

/-- Bounded sum, unknown N. --/
variables {n m ub : ℕ} {α β : Type*} [decidable_eq α] [decidable_eq β] (f : α → β)

def nf (a : fin (n + 1)) := nat_of_fin a

@[simp] def multiset.sat_add (a : fin (n + 1)) (b : fin (n + 1)) : (fin (n + 1))
  := fin.clamp ((nf a) + (nf b)) n

lemma multiset.sat_add_comm (a : fin (n + 1)) (b : fin (n + 1)) :
  multiset.sat_add a b = multiset.sat_add b a :=
begin
  simp only [multiset.sat_add],
  have h : nf a + nf b = nf b + nf a
    := by { rw add_comm },
  rw h
end

/-
lemma fin.clamp_le (v n : ℕ) (h : v ≤ n) : fin.clamp v n = v :=
begin

end
-/

lemma multiset.sat_add_left_comm (a b c: fin (n + 1)) :
  multiset.sat_add a (multiset.sat_add b c) = multiset.sat_add b (multiset.sat_add a c) :=
begin
  simp only [multiset.sat_add],
  cases classical.em (nf a + nf b + nf c ≤ nf n),
  have ha : nf a + nf b ≤ nf n := nat.le_of_add_le_left _,
  have hc : nf c ≤ nf n := nat.le_of_add_le_right _,

  /-
  have hb : nf b ≤ nf n := nat.le_of_add_le_left _ _ ,
  -/
end

@[reducible] def multiset.sat_sum (m : multiset (fin (n + 1))) (ub : ℕ) (h : n ≤ ub): (fin (ub + 1))
  := multiset.foldr


theorem ex1 (h₁ : ¬ p) (h₂ : q → p) : ¬ q := by
  intro h
  apply h₁
  apply h₂
  apply h


/-
Copyright (c) 2020 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov

! This file was ported from Lean 3 source module order.filter.interval
! leanprover-community/mathlib commit a11f9106a169dd302a285019e5165f8ab32ff433
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Set.Intervals.OrdConnected
import Mathbin.Order.Filter.SmallSets
import Mathbin.Order.Filter.AtTopBot

/-!
# Convergence of intervals

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

If both `a` and `b` tend to some filter `l₁`, sometimes this implies that `Ixx a b` tends to
`l₂.small_sets`, i.e., for any `s ∈ l₂` eventually `Ixx a b` becomes a subset of `s`.  Here and
below `Ixx` is one of `Icc`, `Ico`, `Ioc`, and `Ioo`. We define `filter.tendsto_Ixx_class Ixx l₁ l₂`
to be a typeclass representing this property.

The instances provide the best `l₂` for a given `l₁`. In many cases `l₁ = l₂` but sometimes we can
drop an endpoint from an interval: e.g., we prove `tendsto_Ixx_class Ico (𝓟 $ Iic a) (𝓟 $ Iio a)`,
i.e., if `u₁ n` and `u₂ n` belong eventually to `Iic a`, then the interval `Ico (u₁ n) (u₂ n)` is
eventually included in `Iio a`.

The next table shows “output” filters `l₂` for different values of `Ixx` and `l₁`. The instances
that need topology are defined in `topology/algebra/ordered`.

| Input filter |  `Ixx = Icc`  |  `Ixx = Ico`  |  `Ixx = Ioc`  |  `Ixx = Ioo`  |
| -----------: | :-----------: | :-----------: | :-----------: | :-----------: |
|     `at_top` |    `at_top`   |    `at_top`   |    `at_top`   |    `at_top`   |
|     `at_bot` |    `at_bot`   |    `at_bot`   |    `at_bot`   |    `at_bot`   |
|     `pure a` |    `pure a`   |      `⊥`      |      `⊥`      |      `⊥`      |
|  `𝓟 (Iic a)` |  `𝓟 (Iic a)`  |  `𝓟 (Iio a)`  |  `𝓟 (Iic a)`  |  `𝓟 (Iio a)`  |
|  `𝓟 (Ici a)` |  `𝓟 (Ici a)`  |  `𝓟 (Ici a)`  |  `𝓟 (Ioi a)`  |  `𝓟 (Ioi a)`  |
|  `𝓟 (Ioi a)` |  `𝓟 (Ioi a)`  |  `𝓟 (Ioi a)`  |  `𝓟 (Ioi a)`  |  `𝓟 (Ioi a)`  |
|  `𝓟 (Iio a)` |  `𝓟 (Iio a)`  |  `𝓟 (Iio a)`  |  `𝓟 (Iio a)`  |  `𝓟 (Iio a)`  |
|        `𝓝 a` |     `𝓝 a`     |     `𝓝 a`     |     `𝓝 a`     |     `𝓝 a`     |
| `𝓝[Iic a] b` |  `𝓝[Iic a] b` |  `𝓝[Iio a] b` |  `𝓝[Iic a] b` |  `𝓝[Iio a] b` |
| `𝓝[Ici a] b` |  `𝓝[Ici a] b` |  `𝓝[Ici a] b` |  `𝓝[Ioi a] b` |  `𝓝[Ioi a] b` |
| `𝓝[Ioi a] b` |  `𝓝[Ioi a] b` |  `𝓝[Ioi a] b` |  `𝓝[Ioi a] b` |  `𝓝[Ioi a] b` |
| `𝓝[Iio a] b` |  `𝓝[Iio a] b` |  `𝓝[Iio a] b` |  `𝓝[Iio a] b` |  `𝓝[Iio a] b` |

-/

variable {α β : Type _}

open Classical Filter Interval

open Set Function

namespace Filter

section Preorder

variable [Preorder α]

/- warning: filter.tendsto_Ixx_class -> Filter.TendstoIxxClass is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Preorder.{u1} α], (α -> α -> (Set.{u1} α)) -> (Filter.{u1} α) -> (outParam.{succ u1} (Filter.{u1} α)) -> Prop
but is expected to have type
  forall {α : Type.{u1}}, (α -> α -> (Set.{u1} α)) -> (Filter.{u1} α) -> (outParam.{succ u1} (Filter.{u1} α)) -> Prop
Case conversion may be inaccurate. Consider using '#align filter.tendsto_Ixx_class Filter.TendstoIxxClassₓ'. -/
/-- A pair of filters `l₁`, `l₂` has `tendsto_Ixx_class Ixx` property if `Ixx a b` tends to
`l₂.small_sets` as `a` and `b` tend to `l₁`. In all instances `Ixx` is one of `Icc`, `Ico`, `Ioc`,
or `Ioo`. The instances provide the best `l₂` for a given `l₁`. In many cases `l₁ = l₂` but
sometimes we can drop an endpoint from an interval: e.g., we prove `tendsto_Ixx_class Ico (𝓟 $ Iic
a) (𝓟 $ Iio a)`, i.e., if `u₁ n` and `u₂ n` belong eventually to `Iic a`, then the interval `Ico (u₁
n) (u₂ n)` is eventually included in `Iio a`.

We mark `l₂` as an `out_param` so that Lean can automatically find an appropriate `l₂` based on
`Ixx` and `l₁`. This way, e.g., `tendsto.Ico h₁ h₂` works without specifying explicitly `l₂`. -/
class TendstoIxxClass (Ixx : α → α → Set α) (l₁ : Filter α) (l₂ : outParam <| Filter α) : Prop where
  tendsto_Ixx : Tendsto (fun p : α × α => Ixx p.1 p.2) (l₁ ×ᶠ l₁) l₂.smallSets
#align filter.tendsto_Ixx_class Filter.TendstoIxxClass

/- warning: filter.tendsto.Icc -> Filter.Tendsto.Icc is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Preorder.{u1} α] {l₁ : Filter.{u1} α} {l₂ : Filter.{u1} α} [_inst_2 : Filter.TendstoIxxClass.{u1} α _inst_1 (Set.Icc.{u1} α _inst_1) l₁ l₂] {lb : Filter.{u2} β} {u₁ : β -> α} {u₂ : β -> α}, (Filter.Tendsto.{u2, u1} β α u₁ lb l₁) -> (Filter.Tendsto.{u2, u1} β α u₂ lb l₁) -> (Filter.Tendsto.{u2, u1} β (Set.{u1} α) (fun (x : β) => Set.Icc.{u1} α _inst_1 (u₁ x) (u₂ x)) lb (Filter.smallSets.{u1} α l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Preorder.{u2} α] {l₁ : Filter.{u2} α} {l₂ : Filter.{u2} α} [_inst_2 : Filter.TendstoIxxClass.{u2} α (Set.Icc.{u2} α _inst_1) l₁ l₂] {lb : Filter.{u1} β} {u₁ : β -> α} {u₂ : β -> α}, (Filter.Tendsto.{u1, u2} β α u₁ lb l₁) -> (Filter.Tendsto.{u1, u2} β α u₂ lb l₁) -> (Filter.Tendsto.{u1, u2} β (Set.{u2} α) (fun (x : β) => Set.Icc.{u2} α _inst_1 (u₁ x) (u₂ x)) lb (Filter.smallSets.{u2} α l₂))
Case conversion may be inaccurate. Consider using '#align filter.tendsto.Icc Filter.Tendsto.Iccₓ'. -/
theorem Tendsto.Icc {l₁ l₂ : Filter α} [TendstoIxxClass Icc l₁ l₂] {lb : Filter β} {u₁ u₂ : β → α}
    (h₁ : Tendsto u₁ lb l₁) (h₂ : Tendsto u₂ lb l₁) :
    Tendsto (fun x => Icc (u₁ x) (u₂ x)) lb l₂.smallSets :=
  TendstoIxxClass.tendsto_Ixx.comp <| h₁.prod_mk h₂
#align filter.tendsto.Icc Filter.Tendsto.Icc

/- warning: filter.tendsto.Ioc -> Filter.Tendsto.Ioc is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Preorder.{u1} α] {l₁ : Filter.{u1} α} {l₂ : Filter.{u1} α} [_inst_2 : Filter.TendstoIxxClass.{u1} α _inst_1 (Set.Ioc.{u1} α _inst_1) l₁ l₂] {lb : Filter.{u2} β} {u₁ : β -> α} {u₂ : β -> α}, (Filter.Tendsto.{u2, u1} β α u₁ lb l₁) -> (Filter.Tendsto.{u2, u1} β α u₂ lb l₁) -> (Filter.Tendsto.{u2, u1} β (Set.{u1} α) (fun (x : β) => Set.Ioc.{u1} α _inst_1 (u₁ x) (u₂ x)) lb (Filter.smallSets.{u1} α l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Preorder.{u2} α] {l₁ : Filter.{u2} α} {l₂ : Filter.{u2} α} [_inst_2 : Filter.TendstoIxxClass.{u2} α (Set.Ioc.{u2} α _inst_1) l₁ l₂] {lb : Filter.{u1} β} {u₁ : β -> α} {u₂ : β -> α}, (Filter.Tendsto.{u1, u2} β α u₁ lb l₁) -> (Filter.Tendsto.{u1, u2} β α u₂ lb l₁) -> (Filter.Tendsto.{u1, u2} β (Set.{u2} α) (fun (x : β) => Set.Ioc.{u2} α _inst_1 (u₁ x) (u₂ x)) lb (Filter.smallSets.{u2} α l₂))
Case conversion may be inaccurate. Consider using '#align filter.tendsto.Ioc Filter.Tendsto.Iocₓ'. -/
theorem Tendsto.Ioc {l₁ l₂ : Filter α} [TendstoIxxClass Ioc l₁ l₂] {lb : Filter β} {u₁ u₂ : β → α}
    (h₁ : Tendsto u₁ lb l₁) (h₂ : Tendsto u₂ lb l₁) :
    Tendsto (fun x => Ioc (u₁ x) (u₂ x)) lb l₂.smallSets :=
  TendstoIxxClass.tendsto_Ixx.comp <| h₁.prod_mk h₂
#align filter.tendsto.Ioc Filter.Tendsto.Ioc

/- warning: filter.tendsto.Ico -> Filter.Tendsto.Ico is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Preorder.{u1} α] {l₁ : Filter.{u1} α} {l₂ : Filter.{u1} α} [_inst_2 : Filter.TendstoIxxClass.{u1} α _inst_1 (Set.Ico.{u1} α _inst_1) l₁ l₂] {lb : Filter.{u2} β} {u₁ : β -> α} {u₂ : β -> α}, (Filter.Tendsto.{u2, u1} β α u₁ lb l₁) -> (Filter.Tendsto.{u2, u1} β α u₂ lb l₁) -> (Filter.Tendsto.{u2, u1} β (Set.{u1} α) (fun (x : β) => Set.Ico.{u1} α _inst_1 (u₁ x) (u₂ x)) lb (Filter.smallSets.{u1} α l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Preorder.{u2} α] {l₁ : Filter.{u2} α} {l₂ : Filter.{u2} α} [_inst_2 : Filter.TendstoIxxClass.{u2} α (Set.Ico.{u2} α _inst_1) l₁ l₂] {lb : Filter.{u1} β} {u₁ : β -> α} {u₂ : β -> α}, (Filter.Tendsto.{u1, u2} β α u₁ lb l₁) -> (Filter.Tendsto.{u1, u2} β α u₂ lb l₁) -> (Filter.Tendsto.{u1, u2} β (Set.{u2} α) (fun (x : β) => Set.Ico.{u2} α _inst_1 (u₁ x) (u₂ x)) lb (Filter.smallSets.{u2} α l₂))
Case conversion may be inaccurate. Consider using '#align filter.tendsto.Ico Filter.Tendsto.Icoₓ'. -/
theorem Tendsto.Ico {l₁ l₂ : Filter α} [TendstoIxxClass Ico l₁ l₂] {lb : Filter β} {u₁ u₂ : β → α}
    (h₁ : Tendsto u₁ lb l₁) (h₂ : Tendsto u₂ lb l₁) :
    Tendsto (fun x => Ico (u₁ x) (u₂ x)) lb l₂.smallSets :=
  TendstoIxxClass.tendsto_Ixx.comp <| h₁.prod_mk h₂
#align filter.tendsto.Ico Filter.Tendsto.Ico

/- warning: filter.tendsto.Ioo -> Filter.Tendsto.Ioo is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Preorder.{u1} α] {l₁ : Filter.{u1} α} {l₂ : Filter.{u1} α} [_inst_2 : Filter.TendstoIxxClass.{u1} α _inst_1 (Set.Ioo.{u1} α _inst_1) l₁ l₂] {lb : Filter.{u2} β} {u₁ : β -> α} {u₂ : β -> α}, (Filter.Tendsto.{u2, u1} β α u₁ lb l₁) -> (Filter.Tendsto.{u2, u1} β α u₂ lb l₁) -> (Filter.Tendsto.{u2, u1} β (Set.{u1} α) (fun (x : β) => Set.Ioo.{u1} α _inst_1 (u₁ x) (u₂ x)) lb (Filter.smallSets.{u1} α l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Preorder.{u2} α] {l₁ : Filter.{u2} α} {l₂ : Filter.{u2} α} [_inst_2 : Filter.TendstoIxxClass.{u2} α (Set.Ioo.{u2} α _inst_1) l₁ l₂] {lb : Filter.{u1} β} {u₁ : β -> α} {u₂ : β -> α}, (Filter.Tendsto.{u1, u2} β α u₁ lb l₁) -> (Filter.Tendsto.{u1, u2} β α u₂ lb l₁) -> (Filter.Tendsto.{u1, u2} β (Set.{u2} α) (fun (x : β) => Set.Ioo.{u2} α _inst_1 (u₁ x) (u₂ x)) lb (Filter.smallSets.{u2} α l₂))
Case conversion may be inaccurate. Consider using '#align filter.tendsto.Ioo Filter.Tendsto.Iooₓ'. -/
theorem Tendsto.Ioo {l₁ l₂ : Filter α} [TendstoIxxClass Ioo l₁ l₂] {lb : Filter β} {u₁ u₂ : β → α}
    (h₁ : Tendsto u₁ lb l₁) (h₂ : Tendsto u₂ lb l₁) :
    Tendsto (fun x => Ioo (u₁ x) (u₂ x)) lb l₂.smallSets :=
  TendstoIxxClass.tendsto_Ixx.comp <| h₁.prod_mk h₂
#align filter.tendsto.Ioo Filter.Tendsto.Ioo

/- warning: filter.tendsto_Ixx_class_principal -> Filter.tendstoIxxClass_principal is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Preorder.{u1} α] {s : Set.{u1} α} {t : Set.{u1} α} {Ixx : α -> α -> (Set.{u1} α)}, Iff (Filter.TendstoIxxClass.{u1} α _inst_1 Ixx (Filter.principal.{u1} α s) (Filter.principal.{u1} α t)) (forall (x : α), (Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) x s) -> (forall (y : α), (Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) y s) -> (HasSubset.Subset.{u1} (Set.{u1} α) (Set.hasSubset.{u1} α) (Ixx x y) t)))
but is expected to have type
  forall {α : Type.{u1}} {_inst_1 : Set.{u1} α} {s : Set.{u1} α} {t : α -> α -> (Set.{u1} α)}, Iff (Filter.TendstoIxxClass.{u1} α t (Filter.principal.{u1} α _inst_1) (Filter.principal.{u1} α s)) (forall (x : α), (Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) x _inst_1) -> (forall (y : α), (Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) y _inst_1) -> (HasSubset.Subset.{u1} (Set.{u1} α) (Set.instHasSubsetSet.{u1} α) (t x y) s)))
Case conversion may be inaccurate. Consider using '#align filter.tendsto_Ixx_class_principal Filter.tendstoIxxClass_principalₓ'. -/
/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (x y «expr ∈ » s) -/
theorem tendstoIxxClass_principal {s t : Set α} {Ixx : α → α → Set α} :
    TendstoIxxClass Ixx (𝓟 s) (𝓟 t) ↔ ∀ (x) (_ : x ∈ s) (y) (_ : y ∈ s), Ixx x y ⊆ t :=
  Iff.trans ⟨fun h => h.1, fun h => ⟨h⟩⟩ <| by
    simp only [small_sets_principal, prod_principal_principal, tendsto_principal_principal,
      forall_prod_set, mem_powerset_iff, mem_principal]
#align filter.tendsto_Ixx_class_principal Filter.tendstoIxxClass_principal

/- warning: filter.tendsto_Ixx_class_inf -> Filter.tendstoIxxClass_inf is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Preorder.{u1} α] {l₁ : Filter.{u1} α} {l₁' : Filter.{u1} α} {l₂ : Filter.{u1} α} {l₂' : Filter.{u1} α} {Ixx : α -> α -> (Set.{u1} α)} [h : Filter.TendstoIxxClass.{u1} α _inst_1 Ixx l₁ l₂] [h' : Filter.TendstoIxxClass.{u1} α _inst_1 Ixx l₁' l₂'], Filter.TendstoIxxClass.{u1} α _inst_1 Ixx (Inf.inf.{u1} (Filter.{u1} α) (Filter.hasInf.{u1} α) l₁ l₁') (Inf.inf.{u1} (outParam.{succ u1} (Filter.{u1} α)) (Filter.hasInf.{u1} α) l₂ l₂')
but is expected to have type
  forall {α : Type.{u1}} {_inst_1 : Filter.{u1} α} {l₁ : Filter.{u1} α} {l₁' : Filter.{u1} α} {l₂ : Filter.{u1} α} {l₂' : α -> α -> (Set.{u1} α)} [Ixx : Filter.TendstoIxxClass.{u1} α l₂' _inst_1 l₁'] [h : Filter.TendstoIxxClass.{u1} α l₂' l₁ l₂], Filter.TendstoIxxClass.{u1} α l₂' (Inf.inf.{u1} (Filter.{u1} α) (Filter.instInfFilter.{u1} α) _inst_1 l₁) (Inf.inf.{u1} (Filter.{u1} α) (Filter.instInfFilter.{u1} α) l₁' l₂)
Case conversion may be inaccurate. Consider using '#align filter.tendsto_Ixx_class_inf Filter.tendstoIxxClass_infₓ'. -/
theorem tendstoIxxClass_inf {l₁ l₁' l₂ l₂' : Filter α} {Ixx} [h : TendstoIxxClass Ixx l₁ l₂]
    [h' : TendstoIxxClass Ixx l₁' l₂'] : TendstoIxxClass Ixx (l₁ ⊓ l₁') (l₂ ⊓ l₂') :=
  ⟨by simpa only [prod_inf_prod, small_sets_inf] using h.1.inf h'.1⟩
#align filter.tendsto_Ixx_class_inf Filter.tendstoIxxClass_inf

/- warning: filter.tendsto_Ixx_class_of_subset -> Filter.tendstoIxxClass_of_subset is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Preorder.{u1} α] {l₁ : Filter.{u1} α} {l₂ : Filter.{u1} α} {Ixx : α -> α -> (Set.{u1} α)} {Ixx' : α -> α -> (Set.{u1} α)}, (forall (a : α) (b : α), HasSubset.Subset.{u1} (Set.{u1} α) (Set.hasSubset.{u1} α) (Ixx a b) (Ixx' a b)) -> (forall [h' : Filter.TendstoIxxClass.{u1} α _inst_1 Ixx' l₁ l₂], Filter.TendstoIxxClass.{u1} α _inst_1 Ixx l₁ l₂)
but is expected to have type
  forall {α : Type.{u1}} {_inst_1 : Filter.{u1} α} {l₁ : Filter.{u1} α} {l₂ : α -> α -> (Set.{u1} α)} {Ixx : α -> α -> (Set.{u1} α)}, (forall (ᾰ : α) (ᾰ_1 : α), HasSubset.Subset.{u1} (Set.{u1} α) (Set.instHasSubsetSet.{u1} α) (l₂ ᾰ ᾰ_1) (Ixx ᾰ ᾰ_1)) -> (forall [h : Filter.TendstoIxxClass.{u1} α Ixx _inst_1 l₁], Filter.TendstoIxxClass.{u1} α l₂ _inst_1 l₁)
Case conversion may be inaccurate. Consider using '#align filter.tendsto_Ixx_class_of_subset Filter.tendstoIxxClass_of_subsetₓ'. -/
theorem tendstoIxxClass_of_subset {l₁ l₂ : Filter α} {Ixx Ixx' : α → α → Set α}
    (h : ∀ a b, Ixx a b ⊆ Ixx' a b) [h' : TendstoIxxClass Ixx' l₁ l₂] : TendstoIxxClass Ixx l₁ l₂ :=
  ⟨h'.1.smallSets_mono <| eventually_of_forall <| Prod.forall.2 h⟩
#align filter.tendsto_Ixx_class_of_subset Filter.tendstoIxxClass_of_subset

/- warning: filter.has_basis.tendsto_Ixx_class -> Filter.HasBasis.tendstoIxxClass is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Preorder.{u1} α] {ι : Type.{u2}} {p : ι -> Prop} {s : ι -> (Set.{u1} α)} {l : Filter.{u1} α}, (Filter.HasBasis.{u1, succ u2} α ι l p s) -> (forall {Ixx : α -> α -> (Set.{u1} α)}, (forall (i : ι), (p i) -> (forall (x : α), (Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) x (s i)) -> (forall (y : α), (Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) y (s i)) -> (HasSubset.Subset.{u1} (Set.{u1} α) (Set.hasSubset.{u1} α) (Ixx x y) (s i))))) -> (Filter.TendstoIxxClass.{u1} α _inst_1 Ixx l l))
but is expected to have type
  forall {α : Type.{u1}} {_inst_1 : Type.{u2}} {ι : _inst_1 -> Prop} {p : _inst_1 -> (Set.{u1} α)} {s : Filter.{u1} α}, (Filter.HasBasis.{u1, succ u2} α _inst_1 s ι p) -> (forall {hl : α -> α -> (Set.{u1} α)}, (forall (ᾰ : _inst_1), (ι ᾰ) -> (forall (x : α), (Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) x (p ᾰ)) -> (forall (y : α), (Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) y (p ᾰ)) -> (HasSubset.Subset.{u1} (Set.{u1} α) (Set.instHasSubsetSet.{u1} α) (hl x y) (p ᾰ))))) -> (Filter.TendstoIxxClass.{u1} α hl s s))
Case conversion may be inaccurate. Consider using '#align filter.has_basis.tendsto_Ixx_class Filter.HasBasis.tendstoIxxClassₓ'. -/
theorem HasBasis.tendstoIxxClass {ι : Type _} {p : ι → Prop} {s} {l : Filter α}
    (hl : l.HasBasis p s) {Ixx : α → α → Set α}
    (H : ∀ i, p i → ∀ x ∈ s i, ∀ y ∈ s i, Ixx x y ⊆ s i) : TendstoIxxClass Ixx l l :=
  ⟨(hl.prod_self.tendsto_iffₓ hl.smallSets).2 fun i hi => ⟨i, hi, fun x hx => H i hi _ hx.1 _ hx.2⟩⟩
#align filter.has_basis.tendsto_Ixx_class Filter.HasBasis.tendstoIxxClass

#print Filter.tendsto_Icc_atTop_atTop /-
instance tendsto_Icc_atTop_atTop : TendstoIxxClass Icc (atTop : Filter α) atTop :=
  (hasBasis_infᵢ_principal_finite _).TendstoIxxClass fun s hs =>
    Set.OrdConnected.out <| ordConnected_binterᵢ fun i hi => ordConnected_Ici
#align filter.tendsto_Icc_at_top_at_top Filter.tendsto_Icc_atTop_atTop
-/

#print Filter.tendsto_Ico_atTop_atTop /-
instance tendsto_Ico_atTop_atTop : TendstoIxxClass Ico (atTop : Filter α) atTop :=
  tendstoIxxClass_of_subset fun _ _ => Ico_subset_Icc_self
#align filter.tendsto_Ico_at_top_at_top Filter.tendsto_Ico_atTop_atTop
-/

#print Filter.tendsto_Ioc_atTop_atTop /-
instance tendsto_Ioc_atTop_atTop : TendstoIxxClass Ioc (atTop : Filter α) atTop :=
  tendstoIxxClass_of_subset fun _ _ => Ioc_subset_Icc_self
#align filter.tendsto_Ioc_at_top_at_top Filter.tendsto_Ioc_atTop_atTop
-/

#print Filter.tendsto_Ioo_atTop_atTop /-
instance tendsto_Ioo_atTop_atTop : TendstoIxxClass Ioo (atTop : Filter α) atTop :=
  tendstoIxxClass_of_subset fun _ _ => Ioo_subset_Icc_self
#align filter.tendsto_Ioo_at_top_at_top Filter.tendsto_Ioo_atTop_atTop
-/

#print Filter.tendsto_Icc_atBot_atBot /-
instance tendsto_Icc_atBot_atBot : TendstoIxxClass Icc (atBot : Filter α) atBot :=
  (hasBasis_infᵢ_principal_finite _).TendstoIxxClass fun s hs =>
    Set.OrdConnected.out <| ordConnected_binterᵢ fun i hi => ordConnected_Iic
#align filter.tendsto_Icc_at_bot_at_bot Filter.tendsto_Icc_atBot_atBot
-/

#print Filter.tendsto_Ico_atBot_atBot /-
instance tendsto_Ico_atBot_atBot : TendstoIxxClass Ico (atBot : Filter α) atBot :=
  tendstoIxxClass_of_subset fun _ _ => Ico_subset_Icc_self
#align filter.tendsto_Ico_at_bot_at_bot Filter.tendsto_Ico_atBot_atBot
-/

#print Filter.tendsto_Ioc_atBot_atBot /-
instance tendsto_Ioc_atBot_atBot : TendstoIxxClass Ioc (atBot : Filter α) atBot :=
  tendstoIxxClass_of_subset fun _ _ => Ioc_subset_Icc_self
#align filter.tendsto_Ioc_at_bot_at_bot Filter.tendsto_Ioc_atBot_atBot
-/

#print Filter.tendsto_Ioo_atBot_atBot /-
instance tendsto_Ioo_atBot_atBot : TendstoIxxClass Ioo (atBot : Filter α) atBot :=
  tendstoIxxClass_of_subset fun _ _ => Ioo_subset_Icc_self
#align filter.tendsto_Ioo_at_bot_at_bot Filter.tendsto_Ioo_atBot_atBot
-/

#print Filter.OrdConnected.tendsto_Icc /-
instance OrdConnected.tendsto_Icc {s : Set α} [hs : OrdConnected s] :
    TendstoIxxClass Icc (𝓟 s) (𝓟 s) :=
  tendstoIxxClass_principal.2 hs.out
#align filter.ord_connected.tendsto_Icc Filter.OrdConnected.tendsto_Icc
-/

#print Filter.tendsto_Ico_Ici_Ici /-
instance tendsto_Ico_Ici_Ici {a : α} : TendstoIxxClass Ico (𝓟 (Ici a)) (𝓟 (Ici a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ico_subset_Icc_self
#align filter.tendsto_Ico_Ici_Ici Filter.tendsto_Ico_Ici_Ici
-/

#print Filter.tendsto_Ico_Ioi_Ioi /-
instance tendsto_Ico_Ioi_Ioi {a : α} : TendstoIxxClass Ico (𝓟 (Ioi a)) (𝓟 (Ioi a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ico_subset_Icc_self
#align filter.tendsto_Ico_Ioi_Ioi Filter.tendsto_Ico_Ioi_Ioi
-/

#print Filter.tendsto_Ico_Iic_Iio /-
instance tendsto_Ico_Iic_Iio {a : α} : TendstoIxxClass Ico (𝓟 (Iic a)) (𝓟 (Iio a)) :=
  tendstoIxxClass_principal.2 fun a ha b hb x hx => lt_of_lt_of_le hx.2 hb
#align filter.tendsto_Ico_Iic_Iio Filter.tendsto_Ico_Iic_Iio
-/

#print Filter.tendsto_Ico_Iio_Iio /-
instance tendsto_Ico_Iio_Iio {a : α} : TendstoIxxClass Ico (𝓟 (Iio a)) (𝓟 (Iio a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ico_subset_Icc_self
#align filter.tendsto_Ico_Iio_Iio Filter.tendsto_Ico_Iio_Iio
-/

#print Filter.tendsto_Ioc_Ici_Ioi /-
instance tendsto_Ioc_Ici_Ioi {a : α} : TendstoIxxClass Ioc (𝓟 (Ici a)) (𝓟 (Ioi a)) :=
  tendstoIxxClass_principal.2 fun x hx y hy t ht => lt_of_le_of_lt hx ht.1
#align filter.tendsto_Ioc_Ici_Ioi Filter.tendsto_Ioc_Ici_Ioi
-/

#print Filter.tendsto_Ioc_Iic_Iic /-
instance tendsto_Ioc_Iic_Iic {a : α} : TendstoIxxClass Ioc (𝓟 (Iic a)) (𝓟 (Iic a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ioc_subset_Icc_self
#align filter.tendsto_Ioc_Iic_Iic Filter.tendsto_Ioc_Iic_Iic
-/

#print Filter.tendsto_Ioc_Iio_Iio /-
instance tendsto_Ioc_Iio_Iio {a : α} : TendstoIxxClass Ioc (𝓟 (Iio a)) (𝓟 (Iio a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ioc_subset_Icc_self
#align filter.tendsto_Ioc_Iio_Iio Filter.tendsto_Ioc_Iio_Iio
-/

#print Filter.tendsto_Ioc_Ioi_Ioi /-
instance tendsto_Ioc_Ioi_Ioi {a : α} : TendstoIxxClass Ioc (𝓟 (Ioi a)) (𝓟 (Ioi a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ioc_subset_Icc_self
#align filter.tendsto_Ioc_Ioi_Ioi Filter.tendsto_Ioc_Ioi_Ioi
-/

#print Filter.tendsto_Ioo_Ici_Ioi /-
instance tendsto_Ioo_Ici_Ioi {a : α} : TendstoIxxClass Ioo (𝓟 (Ici a)) (𝓟 (Ioi a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ioo_subset_Ioc_self
#align filter.tendsto_Ioo_Ici_Ioi Filter.tendsto_Ioo_Ici_Ioi
-/

#print Filter.tendsto_Ioo_Iic_Iio /-
instance tendsto_Ioo_Iic_Iio {a : α} : TendstoIxxClass Ioo (𝓟 (Iic a)) (𝓟 (Iio a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ioo_subset_Ico_self
#align filter.tendsto_Ioo_Iic_Iio Filter.tendsto_Ioo_Iic_Iio
-/

#print Filter.tendsto_Ioo_Ioi_Ioi /-
instance tendsto_Ioo_Ioi_Ioi {a : α} : TendstoIxxClass Ioo (𝓟 (Ioi a)) (𝓟 (Ioi a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ioo_subset_Ioc_self
#align filter.tendsto_Ioo_Ioi_Ioi Filter.tendsto_Ioo_Ioi_Ioi
-/

#print Filter.tendsto_Ioo_Iio_Iio /-
instance tendsto_Ioo_Iio_Iio {a : α} : TendstoIxxClass Ioo (𝓟 (Iio a)) (𝓟 (Iio a)) :=
  tendstoIxxClass_of_subset fun _ _ => Ioo_subset_Ioc_self
#align filter.tendsto_Ioo_Iio_Iio Filter.tendsto_Ioo_Iio_Iio
-/

#print Filter.tendsto_Icc_Icc_Icc /-
instance tendsto_Icc_Icc_Icc {a b : α} : TendstoIxxClass Icc (𝓟 (Icc a b)) (𝓟 (Icc a b)) :=
  tendstoIxxClass_principal.mpr fun x hx y hy => Icc_subset_Icc hx.1 hy.2
#align filter.tendsto_Icc_Icc_Icc Filter.tendsto_Icc_Icc_Icc
-/

#print Filter.tendsto_Ioc_Icc_Icc /-
instance tendsto_Ioc_Icc_Icc {a b : α} : TendstoIxxClass Ioc (𝓟 (Icc a b)) (𝓟 (Icc a b)) :=
  tendstoIxxClass_of_subset fun _ _ => Ioc_subset_Icc_self
#align filter.tendsto_Ioc_Icc_Icc Filter.tendsto_Ioc_Icc_Icc
-/

end Preorder

section PartialOrder

variable [PartialOrder α]

#print Filter.tendsto_Icc_pure_pure /-
instance tendsto_Icc_pure_pure {a : α} : TendstoIxxClass Icc (pure a) (pure a : Filter α) :=
  by
  rw [← principal_singleton]
  exact tendsto_Ixx_class_principal.2 ord_connected_singleton.out
#align filter.tendsto_Icc_pure_pure Filter.tendsto_Icc_pure_pure
-/

#print Filter.tendsto_Ico_pure_bot /-
instance tendsto_Ico_pure_bot {a : α} : TendstoIxxClass Ico (pure a) ⊥ :=
  ⟨by simp⟩
#align filter.tendsto_Ico_pure_bot Filter.tendsto_Ico_pure_bot
-/

#print Filter.tendsto_Ioc_pure_bot /-
instance tendsto_Ioc_pure_bot {a : α} : TendstoIxxClass Ioc (pure a) ⊥ :=
  ⟨by simp⟩
#align filter.tendsto_Ioc_pure_bot Filter.tendsto_Ioc_pure_bot
-/

#print Filter.tendsto_Ioo_pure_bot /-
instance tendsto_Ioo_pure_bot {a : α} : TendstoIxxClass Ioo (pure a) ⊥ :=
  ⟨by simp⟩
#align filter.tendsto_Ioo_pure_bot Filter.tendsto_Ioo_pure_bot
-/

end PartialOrder

section LinearOrder

variable [LinearOrder α]

#print Filter.tendsto_Icc_uIcc_uIcc /-
instance tendsto_Icc_uIcc_uIcc {a b : α} : TendstoIxxClass Icc (𝓟 [a, b]) (𝓟 [a, b]) :=
  Filter.tendsto_Icc_Icc_Icc
#align filter.tendsto_Icc_uIcc_uIcc Filter.tendsto_Icc_uIcc_uIcc
-/

#print Filter.tendsto_Ioc_uIcc_uIcc /-
instance tendsto_Ioc_uIcc_uIcc {a b : α} : TendstoIxxClass Ioc (𝓟 [a, b]) (𝓟 [a, b]) :=
  Filter.tendsto_Ioc_Icc_Icc
#align filter.tendsto_Ioc_uIcc_uIcc Filter.tendsto_Ioc_uIcc_uIcc
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print Filter.tendsto_uIcc_of_Icc /-
instance tendsto_uIcc_of_Icc {l : Filter α} [TendstoIxxClass Icc l l] : TendstoIxxClass uIcc l l :=
  by
  refine' ⟨fun s hs => mem_map.2 <| mem_prod_self_iff.2 _⟩
  obtain ⟨t, htl, hts⟩ : ∃ t ∈ l, ∀ p ∈ (t : Set α) ×ˢ t, Icc (p : α × α).1 p.2 ∈ s
  exact mem_prod_self_iff.1 (mem_map.1 (tendsto_fst.Icc tendsto_snd hs))
  refine' ⟨t, htl, fun p hp => _⟩
  cases le_total p.1 p.2
  · rw [mem_preimage, uIcc_of_le h]
    exact hts p hp
  · rw [mem_preimage, uIcc_of_ge h]
    exact hts ⟨p.2, p.1⟩ ⟨hp.2, hp.1⟩
#align filter.tendsto_uIcc_of_Icc Filter.tendsto_uIcc_of_Icc
-/

/- warning: filter.tendsto.uIcc -> Filter.Tendsto.uIcc is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : LinearOrder.{u1} α] {l : Filter.{u1} α} [_inst_2 : Filter.TendstoIxxClass.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (LinearOrder.toLattice.{u1} α _inst_1)))) (Set.Icc.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (LinearOrder.toLattice.{u1} α _inst_1))))) l l] {f : β -> α} {g : β -> α} {lb : Filter.{u2} β}, (Filter.Tendsto.{u2, u1} β α f lb l) -> (Filter.Tendsto.{u2, u1} β α g lb l) -> (Filter.Tendsto.{u2, u1} β (Set.{u1} α) (fun (x : β) => Set.uIcc.{u1} α (LinearOrder.toLattice.{u1} α _inst_1) (f x) (g x)) lb (Filter.smallSets.{u1} α l))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : LinearOrder.{u2} α] {l : Filter.{u2} α} [_inst_2 : Filter.TendstoIxxClass.{u2} α (Set.Icc.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (DistribLattice.toLattice.{u2} α (instDistribLattice.{u2} α _inst_1)))))) l l] {f : β -> α} {g : β -> α} {lb : Filter.{u1} β}, (Filter.Tendsto.{u1, u2} β α f lb l) -> (Filter.Tendsto.{u1, u2} β α g lb l) -> (Filter.Tendsto.{u1, u2} β (Set.{u2} α) (fun (x : β) => Set.uIcc.{u2} α (DistribLattice.toLattice.{u2} α (instDistribLattice.{u2} α _inst_1)) (f x) (g x)) lb (Filter.smallSets.{u2} α l))
Case conversion may be inaccurate. Consider using '#align filter.tendsto.uIcc Filter.Tendsto.uIccₓ'. -/
theorem Tendsto.uIcc {l : Filter α} [TendstoIxxClass Icc l l] {f g : β → α} {lb : Filter β}
    (hf : Tendsto f lb l) (hg : Tendsto g lb l) : Tendsto (fun x => [f x, g x]) lb l.smallSets :=
  TendstoIxxClass.tendsto_Ixx.comp <| hf.prod_mk hg
#align filter.tendsto.uIcc Filter.Tendsto.uIcc

end LinearOrder

end Filter


set_option pp.analyze false

def p (x y : Nat) := x = y

example (x y : Nat) : p (x + y) (y + x + 0) := by
  conv =>
    whnf
    congr
    . rfl
    . whnf; rfl
  trace_state
  rw [Nat.add_comm]
  rfl

example (x y : Nat) : p (x + y) (y + x + 0) := by
  conv =>
    whnf
    rhs
    whnf
  trace_state
  rw [Nat.add_comm]
  rfl

example (x y : Nat) : p (x + y) (y + x + 0) := by
  conv =>
    whnf
    lhs
    whnf
  conv =>
    rhs
    whnf
  trace_state
  apply Nat.add_comm x y

example (x y : Nat) : p (x + y) (0 + y + x) := by
  conv =>
    whnf
    rhs
    rw [Nat.zero_add, Nat.add_comm]
    trace_state
    rfl
    done

axiom div_self (x : Nat) : x ≠ 0 → x / x = 1

example (h : x ≠ 0) : x / x + x = x.succ := by
  conv =>
    lhs
    arg 1
    rw [div_self]
    rfl
    tactic => assumption
    done
  show 1 + x = x.succ
  rw [Nat.succ_add, Nat.zero_add]

example (h1 : x ≠ 0) (h2 : y = x / x) : y = 1 := by
  conv at h2 =>
    rhs
    rw [div_self]
    rfl
    tactic => assumption
  assumption

example : id (fun x => 0 + x) = id := by
  conv =>
    lhs
    arg 1
    ext y
    rw [Nat.zero_add]

def f (x : Nat) :=
  if x > 0 then
    x + 1
  else
    x + 2

example (g : Nat → Nat) (h₁ : g x = x + 1) (h₂ : x > 0) : g x = f x := by
  conv =>
    rhs
    simp [f, h₂]
  exact h₁

example (h₁ : f x = x + 1) (h₂ : x > 0) : f x = f x := by
  conv =>
    rhs
    simp [f, h₂]
  exact h₁

example (x y : Nat) (f : Nat → Nat → Nat) (g : Nat → Nat) (h₁ : ∀ z, f z z = z) (h₂ : ∀ x y, f (g x) (g y) = y) : f (g (0 + y)) (f (g x) (g (x + 0))) = x := by
  conv in _ + 0 => apply Nat.add_zero
  trace_state
  conv in 0 + _ => apply Nat.zero_add
  trace_state
  simp [h₁, h₂]

example (x y : Nat) (f : Nat → Nat → Nat) (g : Nat → Nat)
        (h₁ : ∀ z, f z z = z) (h₂ : ∀ x y, f (g x) (g y) = y)
        (h₃ : f (g (0 + x)) (g x) = 0)
 : g x = 0 := by
  conv at h₃ in 0 + x => apply Nat.zero_add
  trace_state
  conv at h₃ => lhs; apply h₁
  trace_state
  assumption


/-
Copyright (c) 2022 Kyle Miller. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kyle Miller
-/
import data.fintype.card

/-!
# Lemmas about `finite` and `set`s

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

In this file we prove two lemmas about `finite` and `set`s.

## Tags

finiteness, finite sets
-/

open set

universes u v w x
variables {α : Type u} {β : Type v} {ι : Sort w}

lemma finite.set.finite_of_finite_image (s : set α)
  {f : α → β} (h : s.inj_on f) [finite (f '' s)] : finite s :=
finite.of_equiv _ (equiv.of_bijective _ h.bij_on_image.bijective).symm

lemma finite.of_injective_finite_range {f : ι → α}
  (hf : function.injective f) [finite (range f)] : finite ι :=
finite.of_injective (set.range_factorization f) (hf.cod_restrict _)