/-
Copyright (c) 2015 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Metric spaces.

Authors: Jeremy Avigad, Robert Y. Lewis
-/
import data.real.complete data.pnat ..topology.continuous ..topology.limit data.set
open nat real eq.ops classical set prod set.filter topology interval

structure metric_space [class] (M : Type) : Type :=
  (dist : M → M → ℝ)
  (dist_self : ∀ x : M, dist x x = 0)
  (eq_of_dist_eq_zero : ∀ {x y : M}, dist x y = 0 → x = y)
  (dist_comm : ∀ x y : M, dist x y = dist y x)
  (dist_triangle : ∀ x y z : M, dist x z ≤ dist x y + dist y z)

namespace analysis

section metric_space_M
variables {M : Type} [metric_space M]

definition dist (x y : M) : ℝ := metric_space.dist x y

proposition dist_self (x : M) : dist x x = 0 := metric_space.dist_self x

proposition eq_of_dist_eq_zero {x y : M} (H : dist x y = 0) : x = y :=
metric_space.eq_of_dist_eq_zero H

proposition dist_comm (x y : M) : dist x y = dist y x := metric_space.dist_comm x y

proposition dist_eq_zero_iff (x y : M) : dist x y = 0 ↔ x = y :=
iff.intro eq_of_dist_eq_zero (suppose x = y, this ▸ !dist_self)

proposition dist_triangle (x y z : M) : dist x z ≤ dist x y + dist y z :=
metric_space.dist_triangle x y z

proposition dist_nonneg (x y : M) : 0 ≤ dist x y :=
have dist x y + dist y x ≥ 0, by rewrite -(dist_self x); apply dist_triangle,
have 2 * dist x y ≥ 0,
  by krewrite [-real.one_add_one, right_distrib, +one_mul, dist_comm at {2}]; apply this,
nonneg_of_mul_nonneg_left this two_pos

proposition dist_pos_of_ne {x y : M} (H : x ≠ y) : dist x y > 0 :=
lt_of_le_of_ne !dist_nonneg (suppose 0 = dist x y, H (iff.mp !dist_eq_zero_iff this⁻¹))

proposition ne_of_dist_pos {x y : M} (H : dist x y > 0) : x ≠ y :=
suppose x = y,
have H1 : dist x x > 0, by rewrite this at {2}; exact H,
by rewrite dist_self at H1; apply not_lt_self _ H1

proposition eq_of_forall_dist_le {x y : M} (H : ∀ ε, ε > 0 → dist x y ≤ ε) : x = y :=
eq_of_dist_eq_zero (eq_zero_of_nonneg_of_forall_le !dist_nonneg H)

/- instantiate metric space as a topology -/

definition open_ball (x : M) (ε : ℝ) := {y | dist y x < ε}

theorem open_ball_eq_empty_of_nonpos (x : M) {ε : ℝ} (Hε : ε ≤ 0) : open_ball x ε = ∅ :=
  begin
    apply eq_empty_of_forall_not_mem,
    intro y Hlt,
    apply not_lt_of_ge (dist_nonneg y x),
    apply lt_of_lt_of_le Hlt Hε
  end

theorem pos_of_mem_open_ball {x : M} {ε : ℝ} {u : M} (Hu : u ∈ open_ball x ε) : ε > 0 :=
  begin
    apply lt_of_not_ge,
    intro Hge,
    note Hop := open_ball_eq_empty_of_nonpos x Hge,
    rewrite Hop at Hu,
    apply not_mem_empty _ Hu
  end

theorem mem_open_ball (x : M) {ε : ℝ} (H : ε > 0) : x ∈ open_ball x ε :=
  show dist x x < ε, by rewrite dist_self; assumption

definition closed_ball (x : M) (ε : ℝ) := {y | dist y x ≤ ε}

theorem closed_ball_eq_compl (x : M) (ε : ℝ) : closed_ball x ε = - {y | dist y x > ε} :=
ext (take y, iff.intro
  (suppose dist y x ≤ ε, not_lt_of_ge this)
  (suppose ¬ dist y x > ε, le_of_not_gt this))

variable (M)

definition open_sets_basis : set (set M) := { s | ∃ x, ∃ ε, s = open_ball x ε }

definition metric_topology [instance] : topology M := topology.generated_by (open_sets_basis M)

variable {M}

theorem open_ball_mem_open_sets_basis (x : M) (ε : ℝ) : open_ball x ε ∈ open_sets_basis M :=
  exists.intro x (exists.intro ε rfl)

theorem Open_open_ball (x : M) (ε : ℝ) : Open (open_ball x ε) :=
  by apply generators_mem_topology_generated_by; apply open_ball_mem_open_sets_basis

theorem closed_closed_ball (x : M) {ε : ℝ} (H : ε > 0) : closed (closed_ball x ε) :=
Open_of_forall_exists_Open_nbhd
  (take y, suppose ¬ dist y x ≤ ε,
    have dist y x > ε, from lt_of_not_ge this,
    let B := open_ball y (dist y x - ε) in
    have y ∈ B, from mem_open_ball y (sub_pos_of_lt this),
    have B ⊆ - closed_ball x ε, from
      take y',
      assume Hy'y : dist y' y < dist y x - ε,
      assume Hy'x : dist y' x ≤ ε,
      show false, from not_lt_self (dist y x)
        (calc
          dist y x ≤ dist y y' + dist y' x   : dist_triangle
            ... < dist y x - ε + dist y' x   : by rewrite dist_comm; apply add_lt_add_right Hy'y
            ... ≤ dist y x - ε + ε           : add_le_add_left Hy'x
            ... = dist y x                   : by rewrite [sub_add_cancel]),
    exists.intro B (and.intro (Open_open_ball _ _) (and.intro `y ∈ B` this)))

proposition open_ball_subset_open_ball_of_le (x : M) {r₁ r₂ : ℝ} (H : r₁ ≤ r₂) :
  open_ball x r₁ ⊆ open_ball x r₂ :=
take y, assume ymem, lt_of_lt_of_le ymem H

theorem exists_open_ball_subset_of_Open_of_mem {U : set M} (HU : Open U) {x : M} (Hx : x ∈ U) :
        ∃ (r : ℝ), r > 0 ∧ open_ball x r ⊆ U :=
begin
  induction HU with s sbasis s t sbasis tbasis ihs iht S Sbasis ihS,
    {cases sbasis with x' aux, cases aux with ε seq,
      have x ∈ open_ball x' ε, by rewrite -seq; exact Hx,
      have εpos : ε > 0, from pos_of_mem_open_ball this,
      have ε - dist x x' > 0, from sub_pos_of_lt `x ∈ open_ball x' ε`,
      existsi (ε - dist x x'), split, exact this, rewrite seq,
      show open_ball x (ε - dist x x') ⊆ open_ball x' ε, from
        take y, suppose dist y x < ε - dist x x',
        calc
          dist y x' ≤ dist y x + dist x x'      : dist_triangle
                ... < ε - dist x x' + dist x x' : add_lt_add_right this
                ... = ε                         : sub_add_cancel},
    {existsi 1, split, exact zero_lt_one, exact subset_univ _},
    {cases ihs (and.left Hx) with rs aux, cases aux with rspos ballrs_sub,
      cases iht (and.right Hx) with rt aux, cases aux with rtpos ballrt_sub,
      let rmin := min rs rt,
      existsi rmin, split, exact lt_min rspos rtpos,
      have open_ball x rmin ⊆ s,
        from subset.trans (open_ball_subset_open_ball_of_le x !min_le_left) ballrs_sub,
      have open_ball x rmin ⊆ t,
        from subset.trans (open_ball_subset_open_ball_of_le x !min_le_right) ballrt_sub,
      show open_ball x (min rs rt) ⊆ s ∩ t,
        by apply subset_inter; repeat assumption},
  cases Hx with s aux, cases aux with sS xs,
  cases (ihS sS xs) with r aux, cases aux with rpos ballr_sub,
  existsi r, split, exact rpos,
  show open_ball x r ⊆ ⋃₀ S, from subset.trans ballr_sub (subset_sUnion_of_mem sS)
end

/- limits in metric spaces -/

proposition eventually_nhds_intro {P : M → Prop} {ε : ℝ} (εpos : ε > 0) {x : M}
    (H : ∀ x', dist x' x < ε → P x') :
  eventually P (nhds x) :=
topology.eventually_nhds_intro (Open_open_ball x ε) (mem_open_ball x εpos) H

proposition eventually_nhds_dest {P : M → Prop} {x : M} (H : eventually P (nhds x)) :
  ∃ ε, ε > 0 ∧ ∀ x', dist x' x < ε → P x' :=
obtain s [(Os : Open s) [(xs : x ∈ s) (Hs : ∀₀ x' ∈ s, P x')]],
  from topology.eventually_nhds_dest H,
obtain ε [(εpos : ε > 0) (Hε : open_ball x ε ⊆ s)],
  from exists_open_ball_subset_of_Open_of_mem Os xs,
exists.intro ε (and.intro εpos
  (take x', suppose dist x' x < ε,
    have x' ∈ s, from Hε this,
    show P x', from Hs this))

proposition eventually_nhds_iff (P : M → Prop) (x : M) :
  eventually P (nhds x) ↔ (∃ ε, ε > 0 ∧ ∀ x', dist x' x < ε → P x') :=
iff.intro eventually_nhds_dest
  (assume H, obtain ε [εpos Hε], from H, eventually_nhds_intro εpos Hε)

proposition eventually_dist_lt_nhds (x : M) {ε : ℝ} (εpos : ε > 0) :
   eventually (λ x', dist x' x < ε) (nhds x) :=
eventually_nhds_intro εpos (λ x' H, H)

proposition eventually_at_within_intro {P : M → Prop} {ε : ℝ} (εpos : ε > 0) {x : M} {s : set M}
  (H : ∀₀ x' ∈ s, dist x' x < ε → x' ≠ x → P x') :
 eventually P [at x within s] :=
topology.eventually_at_within_intro (Open_open_ball x ε) (mem_open_ball x εpos)
  (λ x' x'mem x'ne x's, H x's x'mem x'ne)

proposition eventually_at_within_dest {P : M → Prop} {x : M} {s : set M}
    (H : eventually P [at x within s]) :
  ∃ ε, ε > 0 ∧ ∀₀ x' ∈ s, dist x' x < ε → x' ≠ x → P x' :=
obtain t [(Ot : Open t) [(xt : x ∈ t) (Ht : ∀₀ x' ∈ t, x' ≠ x → x' ∈ s → P x')]],
  from topology.eventually_at_within_dest H,
obtain ε [(εpos : ε > 0) (Hε : open_ball x ε ⊆ t)],
  from exists_open_ball_subset_of_Open_of_mem Ot xt,
exists.intro ε (and.intro εpos
  (take x', assume x's distx'x x'nex,
    have x' ∈ t, from Hε distx'x,
    show P x', from Ht this x'nex x's))

proposition eventually_at_within_iff (P : M → Prop) (x : M) (s : set M) :
  eventually P [at x within s] ↔  ∃ ε, ε > 0 ∧ ∀₀ x' ∈ s, dist x' x < ε → x' ≠ x → P x' :=
iff.intro eventually_at_within_dest
  (λ H, obtain ε [εpos Hε], from H, eventually_at_within_intro εpos Hε)

proposition eventually_at_intro {P : M → Prop} {ε : ℝ} (εpos : ε > 0) {x : M}
    (H : ∀ x', dist x' x < ε → x' ≠ x → P x') :
 eventually P [at x] :=
topology.eventually_at_intro (Open_open_ball x ε) (mem_open_ball x εpos)
  (λ x' x'mem x'ne, H x' x'mem x'ne)

proposition eventually_at_dest {P : M → Prop} {x : M} (H : eventually P [at x]) :
  ∃ ε, ε > 0 ∧ ∀ ⦃x'⦄, dist x' x < ε → x' ≠ x → P x' :=
obtain ε [εpos Hε], from eventually_at_within_dest H,
exists.intro ε (and.intro εpos (λ x', Hε x' (mem_univ x')))

proposition eventually_at_iff (P : M → Prop) (x : M) :
  eventually P [at x] ↔  ∃ ε, ε > 0 ∧ ∀ ⦃x'⦄, dist x' x < ε → x' ≠ x → P x' :=
iff.intro eventually_at_dest (λ H, obtain ε [εpos Hε], from H, eventually_at_intro εpos Hε)

end metric_space_M

namespace metric_space
variables {M : Type} [metric_space M]

section approaches
  variables {X : Type} {F : filter X} {f : X → M} {y : M}

  proposition approaches_intro (H : ∀ ε, ε > 0 → eventually (λ x, dist (f x) y < ε) F) :
    (f ⟶ y) F :=
  tendsto_intro
    (take P, assume eventuallyP,
      obtain ε [(εpos : ε > 0) (Hε : ∀ x', dist x' y < ε → P x')],
        from eventually_nhds_dest eventuallyP,
      show eventually (λ x, P (f x)) F,
        from eventually_mono (H ε εpos) (λ x Hx, Hε (f x) Hx))

  proposition approaches_dest (H : (f ⟶ y) F) {ε : ℝ} (εpos : ε > 0) :
    eventually (λ x, dist (f x) y < ε) F :=
  tendsto_dest H (eventually_dist_lt_nhds y εpos)

  variables (F f y)

  proposition approaches_iff : (f ⟶ y) F ↔ (∀ ε, ε > 0 → eventually (λ x, dist (f x) y < ε) F) :=
  iff.intro approaches_dest approaches_intro

end approaches

-- here we full unwrap two particular kinds of convergence3
-- TODO: put these in metric space namespace? (will have similar in normed_space

proposition approaches_at_infty_intro {f : ℕ → M} {y : M}
    (H : ∀ ε, ε > 0 → ∃ N, ∀ n, n ≥ N → dist (f n) y < ε) :
  f ⟶ y [at ∞] :=
approaches_intro (λ ε εpos, obtain N HN, from H ε εpos,
  eventually_at_infty_intro HN)

proposition approaches_at_infty_dest {f : ℕ → M} {y : M}
    (H : f ⟶ y [at ∞]) ⦃ε : ℝ⦄ (εpos : ε > 0) :
  ∃ N, ∀ ⦃n⦄, n ≥ N → dist (f n) y < ε :=
have eventually (λ x, dist (f x) y < ε) [at ∞], from approaches_dest H εpos,
eventually_at_infty_dest this

proposition approaches_at_infty_iff (f : ℕ → M) (y : M) :
  f ⟶ y [at ∞] ↔ (∀ ε, ε > 0 → ∃ N, ∀ ⦃n⦄, n ≥ N → dist (f n) y < ε) :=
iff.intro approaches_at_infty_dest approaches_at_infty_intro

section metric_space_N
variables {N : Type} [metric_space N]

proposition approaches_at_dest {f : M → N} {y : N} {x : M}
    (H : f ⟶ y [at x]) ⦃ε : ℝ⦄ (εpos : ε > 0) :
  ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, dist x' x < δ → x' ≠ x → dist (f x') y < ε :=
have eventually (λ x, dist (f x) y < ε) [at x],
  from approaches_dest H εpos,
eventually_at_dest this

proposition approaches_at_intro {f : M → N} {y : N} {x : M}
    (H : ∀ ε, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, dist x' x < δ → x' ≠ x → dist (f x') y < ε) :
  f ⟶ y [at x] :=
approaches_intro (λ ε εpos,
  obtain δ [δpos Hδ], from H ε εpos,
  eventually_at_intro δpos Hδ)

proposition approaches_at_iff (f : M → N) (y : N) (x : M) : f ⟶ y [at x] ↔
    (∀ ⦃ε⦄, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, dist x' x < δ → x' ≠ x → dist (f x') y < ε) :=
iff.intro approaches_at_dest approaches_at_intro

end metric_space_N
end metric_space -- close namespace

section metric_space_M
variables {M : Type} [metric_space M]
-- TODO: remove this. It is only here temporarily, because it is used in normed_space (JA)
-- It is used in the definition of a complete metric space below, but I think it doesn't
-- have to be a class (RL)
abbreviation converges_seq [class] (X : ℕ → M) : Prop := ∃ y, X ⟶ y [at ∞]

-- TODO: refactor
-- the same, with ≤ in place of <; easier to prove, harder to use
definition approaches_at_infty_intro' {X : ℕ → M} {y : M}
    (H : ∀ ⦃ε : ℝ⦄, ε > 0 → ∃ N : ℕ, ∀ {n}, n ≥ N → dist (X n) y ≤ ε) :
  (X ⟶ y) [at ∞] :=
metric_space.approaches_at_infty_intro
take ε, assume epos : ε > 0,
  have e2pos : ε / 2 > 0, from  div_pos_of_pos_of_pos `ε > 0` two_pos,
  obtain N HN, from H e2pos,
  exists.intro N
    (take n, suppose n ≥ N,
      calc
        dist (X n) y ≤ ε / 2 : HN _ `n ≥ N`
                 ... < ε     : div_two_lt_of_pos epos)

-- TODO: prove more generally
proposition approaches_at_infty_unique {X : ℕ → M} {y₁ y₂ : M}
  (H₁ : X ⟶ y₁ [at ∞]) (H₂ : X ⟶ y₂ [at ∞]) : y₁ = y₂ :=
eq_of_forall_dist_le
  (take ε, suppose ε > 0,
    have e2pos : ε / 2 > 0, from  div_pos_of_pos_of_pos `ε > 0` two_pos,
    obtain N₁ (HN₁ : ∀ {n}, n ≥ N₁ → dist (X n) y₁ < ε / 2),
      from metric_space.approaches_at_infty_dest H₁ e2pos,
    obtain N₂ (HN₂ : ∀ {n}, n ≥ N₂ → dist (X n) y₂ < ε / 2),
      from metric_space.approaches_at_infty_dest H₂ e2pos,
    let N := max N₁ N₂ in
    have dN₁ : dist (X N) y₁ < ε / 2, from HN₁ !le_max_left,
    have dN₂ : dist (X N) y₂ < ε / 2, from HN₂ !le_max_right,
    have dist y₁ y₂ < ε, from calc
      dist y₁ y₂ ≤ dist y₁ (X N) + dist (X N) y₂ : dist_triangle
             ... = dist (X N) y₁ + dist (X N) y₂ : dist_comm
             ... < ε / 2 + ε / 2                 : add_lt_add dN₁ dN₂
             ... = ε                             : add_halves,
    show dist y₁ y₂ ≤ ε, from le_of_lt this)

/- TODO: revise

definition converges_seq [class] (X : ℕ → M) : Prop := ∃ y, X ⟶ y in ℕ

noncomputable definition limit_seq (X : ℕ → M) [H : converges_seq X] : M := some H

proposition converges_to_limit_seq (X : ℕ → M) [H : converges_seq X] :
  (X ⟶ limit_seq X in ℕ) :=
some_spec H

proposition eq_limit_of_converges_to_seq {X : ℕ → M} {y : M} (H : X ⟶ y in ℕ) :
  y = @limit_seq M _ X (exists.intro y H) :=
converges_to_seq_unique H (@converges_to_limit_seq M _ X (exists.intro y H))

proposition converges_to_seq_offset_left {X : ℕ → M} {y : M} (k : ℕ) (H : X ⟶ y in ℕ) :
  (λ n, X (k + n)) ⟶ y in ℕ :=
have aux : (λ n, X (k + n)) = (λ n, X (n + k)), from funext (take n, by rewrite add.comm),
by rewrite aux; exact converges_to_seq_offset k H

proposition converges_to_seq_of_converges_to_seq_offset_left
    {X : ℕ → M} {y : M} {k : ℕ} (H : (λ n, X (k + n)) ⟶ y in ℕ) :
  X ⟶ y in ℕ :=
have aux : (λ n, X (k + n)) = (λ n, X (n + k)), from funext (take n, by rewrite add.comm),
by rewrite aux at H; exact converges_to_seq_of_converges_to_seq_offset H
-/

proposition bounded_of_converges_seq {X : ℕ → M} {x : M} (H : X ⟶ x [at ∞]) :
            ∃ K : ℝ, ∀ n : ℕ, dist (X n) x ≤ K :=
  have eventually (λ n, dist (X n) x < 1) [at ∞],
    from metric_space.approaches_dest H zero_lt_one,
  obtain N (HN : ∀ n, n ≥ N → dist (X n) x < 1),
    from eventually_at_infty_dest this,
  let K := max 1 (Max i ∈ '(-∞, N), dist (X i) x) in
  exists.intro K
    (take n,
      if Hn : n < N then
        show dist (X n) x ≤ K,
          from le.trans (le_Max _ Hn) !le_max_right
      else
        show dist (X n) x ≤ K,
          from le.trans (le_of_lt (HN n (le_of_not_gt Hn))) !le_max_left)

proposition bounded_of_converges {A : Type} {X : A → M} {x : M} {F} (H : (X ⟶ x) F) :
            ∃ K : ℝ, eventually (λ n, dist (X n) x ≤ K) F :=
  begin
    note H' := metric_space.approaches_dest H zero_lt_one,
    existsi 1,
    apply eventually_mono H',
    intro x' Hx',
    apply le_of_lt Hx'
  end

/-proposition converges_to_seq_of_converges_to_seq_offset_succ
    {X : ℕ → M} {y : M} (H : (λ n, X (succ n)) ⟶ y [at ∞]) :
  X ⟶ y [at ∞] :=
@converges_to_seq_of_converges_to_seq_offset M _ X y 1 H-/
/-
proposition converges_to_seq_offset_iff (X : ℕ → M) (y : M) (k : ℕ) :
  ((λ n, X (n + k)) ⟶ y in ℕ) ↔ (X ⟶ y in ℕ) :=
iff.intro converges_to_seq_of_converges_to_seq_offset !converges_to_seq_offset

proposition converges_to_seq_offset_left_iff (X : ℕ → M) (y : M) (k : ℕ) :
  ((λ n, X (k + n)) ⟶ y in ℕ) ↔ (X ⟶ y in ℕ) :=
iff.intro converges_to_seq_of_converges_to_seq_offset_left !converges_to_seq_offset_left

proposition converges_to_seq_offset_succ_iff (X : ℕ → M) (y : M) :
  ((λ n, X (succ n)) ⟶ y in ℕ) ↔ (X ⟶ y in ℕ) :=
iff.intro converges_to_seq_of_converges_to_seq_offset_succ !converges_to_seq_offset_succ
-/

/- cauchy sequences -/

definition cauchy (X : ℕ → M) : Prop :=
∀ ε : ℝ, ε > 0 → ∃ N, ∀ m n, m ≥ N → n ≥ N → dist (X m) (X n) < ε

proposition cauchy_of_converges_seq {X : ℕ → M} (H : ∃ y, X ⟶ y [at ∞]) : cauchy X :=
take ε, suppose ε > 0,
  obtain y (Hy : X ⟶ y [at ∞]), from H,
  have e2pos : ε / 2 > 0, from div_pos_of_pos_of_pos `ε > 0` two_pos,
  have eventually (λ n, dist (X n) y < ε / 2) [at ∞], from metric_space.approaches_dest Hy e2pos,
  obtain N (HN : ∀ {n}, n ≥ N → dist (X n) y < ε / 2), from eventually_at_infty_dest this,
    exists.intro N
      (take m n, suppose m ≥ N, suppose n ≥ N,
        have dN₁ : dist (X m) y < ε / 2, from HN `m ≥ N`,
        have dN₂ : dist (X n) y < ε / 2, from HN `n ≥ N`,
        show dist (X m) (X n) < ε, from calc
          dist (X m) (X n) ≤ dist (X m) y + dist y (X n) : dist_triangle
                       ... = dist (X m) y + dist (X n) y : dist_comm
                       ... < ε / 2 + ε / 2               : add_lt_add dN₁ dN₂
                       ... = ε                           : add_halves)

end metric_space_M

/- convergence of a function at a point -/

section metric_space_M_N
variables {M N : Type} [metric_space M] [metric_space N]

-- TODO: refactor
section
open pnat rat

theorem cnv_real_of_cnv_nat {X : ℕ → M} {c : M} (H : ∀ n : ℕ, dist (X n) c < 1 / (real.of_nat n + 1)) :
        ∀ ε : ℝ, ε > 0 → ∃ N : ℕ, ∀ n : ℕ, n ≥ N → dist (X n) c < ε :=
  begin
    intros ε Hε,
    cases ex_rat_pos_lower_bound_of_pos Hε with q Hq,
    cases Hq with Hq1 Hq2,
    cases pnat_bound Hq1 with p Hp,
    existsi pnat.nat_of_pnat p,
    intros n Hn,
    apply lt_of_lt_of_le,
    apply H,
    apply le.trans,
    rotate 1,
    apply Hq2,
    have Hrat : of_rat (inv p) ≤ of_rat q, from of_rat_le_of_rat_of_le Hp,
    apply le.trans,
    rotate 1,
    exact Hrat,
    change 1 / (of_nat n + 1) ≤ of_rat ((1 : ℚ) / (rat_of_pnat p)),
    rewrite [of_rat_divide, of_rat_one],
    eapply one_div_le_one_div_of_le,
    krewrite -of_rat_zero,
    apply of_rat_lt_of_rat_of_lt,
    apply rat_of_pnat_is_pos,
    change of_nat (nat_of_pnat p) ≤ n + 1,
    krewrite [-real.of_nat_add],
    apply real.of_nat_le_of_nat_of_le,
    apply le_add_of_le_right,
    assumption
  end
end

-- TODO : refactor
theorem converges_to_at_of_all_conv_seqs {f : M → N} (c : M) (l : N)
  (Hseq : ∀ X : ℕ → M, (eventually (λ n, X n ≠ c) [at ∞] ∧ (X ⟶ c [at ∞])) → ((λ n : ℕ, f (X n)) ⟶ l [at ∞]))
  : f ⟶ l [at c] :=
  begin
    eapply by_contradiction,
    intro Hnot,
    cases exists_not_of_not_forall (λ H, Hnot (metric_space.approaches_at_intro H)) with ε Hε,
    cases and_not_of_not_implies Hε with H1 H2,
    note H3' := forall_not_of_not_exists H2,
    have H3 : ∀ δ, δ > 0 → (∃ x', dist x' c < δ ∧ x' ≠ c ∧ dist (f x') l ≥ ε), begin
      intro δ Hδ,
      cases exists_not_of_not_forall (or.resolve_left (not_or_not_of_not_and' (H3' δ)) (not_not_intro Hδ))
        with x' Hx',
      existsi x',
      rewrite [2 not_implies_iff_and_not at Hx', ge_iff_not_lt],
      exact Hx'
    end,
    let S := λ (n : ℕ) (x : M), 0 < dist x c ∧ dist x c < 1 / (of_nat n + 1) ∧ dist (f x) l ≥ ε,
    have HS : ∀ n : ℕ, ∃ m : M, S n m, begin
      intro k,
      have Hpos : 0 < of_nat k + 1, from of_nat_succ_pos k,
      cases H3 (1 / (k + 1)) (one_div_pos_of_pos Hpos) with x' Hx',
      cases Hx' with Hne Hx',
      cases Hx' with Hdistl Hdistg,
      existsi x',
      esimp,
      split,
      apply dist_pos_of_ne,
      assumption,
      split,
      repeat assumption
    end,
    let X := λ n, some (HS n),
    have H4 : (eventually (λ n, X n ≠ c) [at ∞]) ∧ (X ⟶ c [at ∞]), begin
      split,
      {fapply @eventually_at_infty_intro,
      exact 0,
      intro n Hn,
      note Hspec := some_spec (HS n),
      esimp, esimp at Hspec,
      cases Hspec,
      apply ne_of_dist_pos,
      assumption},
      {intro,
      apply metric_space.approaches_at_infty_intro,
      apply cnv_real_of_cnv_nat,
      intro m,
      note Hspec := some_spec (HS m),
      esimp, esimp at Hspec,
      cases Hspec with Hspec1 Hspec2,
      cases Hspec2,
      assumption}
    end,
    have H5 : (λ n, f (X n)) ⟶ l [at ∞], from Hseq X H4,
    note H6 := metric_space.approaches_at_infty_dest H5 H1,
    cases H6 with Q HQ,
    note HQ' := HQ !le.refl,
    esimp at HQ',
    apply absurd HQ',
    apply not_lt_of_ge,
    note H7 := some_spec (HS Q),
    esimp at H7,
    cases H7 with H71 H72,
    cases H72,
    assumption
  end

end metric_space_M_N

namespace metric_space

section continuity
variables {M N : Type} [Hm : metric_space M] [Hn : metric_space N]
include Hm Hn
open topology set

-- the ε - δ definition of continuity is equivalent to the topological definition

theorem continuous_at_within_intro {f : M → N} {x : M} {s : set M}
        (H : ∀ ⦃ε⦄, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, x' ∈ s → dist x' x < δ → dist (f x') (f x) < ε) :
  continuous_at_on f x s :=
  begin
    intro U Uopen HfU,
    cases exists_open_ball_subset_of_Open_of_mem Uopen HfU with r Hr,
    cases Hr with Hr HUr,
    cases H Hr with δ Hδ,
    cases Hδ with Hδ Hx'δ,
    existsi open_ball x δ,
    split,
    apply Open_open_ball,
    split,
    apply mem_open_ball,
    exact Hδ,
    intro y Hy,
    apply mem_preimage,
    apply HUr,
    apply Hx'δ,
    apply and.right Hy,
    apply and.left Hy
  end

theorem continuous_at_on_dest {f : M → N} {x : M} {s : set M} (Hfx : continuous_at_on f x s) :
         ∀ ⦃ε⦄, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, x' ∈ s → dist x' x < δ → dist (f x') (f x) < ε :=
  begin
    intros ε Hε,
    cases @Hfx (open_ball (f x) ε) !Open_open_ball (mem_open_ball _ Hε) with V HV,
    cases HV with HV HVx,
    cases HVx with HVx HVf,
    cases exists_open_ball_subset_of_Open_of_mem HV HVx with δ Hδ,
    cases Hδ with Hδ Hδx,
    existsi δ,
    split,
    exact Hδ,
    intro x' Hx's Hx',
    apply HVf,
    apply and.intro,
    apply Hδx,
    exact Hx',
    exact Hx's
  end

theorem continuous_at_intro {f : M → N} {x : M}
        (H : ∀ ⦃ε⦄, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, dist x' x < δ → dist (f x') (f x) < ε) :
        continuous_at f x :=
  continuous_at_of_continuous_at_on_univ
    (continuous_at_within_intro
      (take ε, suppose ε > 0,
      obtain δ (Hδ : δ > 0 ∧ ∀ ⦃x'⦄, dist x' x < δ → dist (f x') (f x) < ε), from H this,
      exists.intro δ (and.intro
      (show δ > 0, from and.left Hδ)
      (take x' H' Hx', and.right Hδ _ Hx'))))

theorem continuous_at_dest {f : M → N} {x : M} (Hfx : continuous_at f x) :
        ∀ ⦃ε⦄, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, dist x' x < δ → dist (f x') (f x) < ε :=
  begin
    intro ε Hε,
    cases continuous_at_on_dest (continuous_at_on_univ_of_continuous_at Hfx) Hε with δ Hδ,
    existsi δ,
    split,
    exact and.left Hδ,
    intro x' Hx',
    apply and.right Hδ,
    apply mem_univ,
    apply Hx'
  end

theorem continuous_on_intro {f : M → N} {s : set M}
        (H : ∀ x ⦃ε⦄, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, x' ∈ s → dist x' x < δ → dist (f x') (f x) < ε) :
        continuous_on f s :=
  continuous_on_of_forall_continuous_at_on (λ x, continuous_at_within_intro (H x))

theorem continuous_on_dest {f : M → N} {s : set M} (H : continuous_on f s) {x : M} (Hxs : x ∈ s) :
        ∀ ⦃ε⦄, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, x' ∈ s → dist x' x < δ → dist (f x') (f x) < ε :=
  continuous_at_on_dest (continuous_at_on_of_continuous_on H Hxs)

theorem continuous_intro {f : M → N}
        (H : ∀ x ⦃ε⦄, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, dist x' x < δ → dist (f x') (f x) < ε) :
        continuous f :=
  continuous_of_forall_continuous_at (λ x, continuous_at_intro (H x))

theorem continuous_dest {f : M → N} (H : continuous f) (x : M) :
         ∀ ⦃ε⦄, ε > 0 → ∃ δ, δ > 0 ∧ ∀ ⦃x'⦄, dist x' x < δ → dist (f x') (f x) < ε :=
  continuous_at_dest (forall_continuous_at_of_continuous H x)

theorem continuous_at_of_converges_to_at {f : M → N} {x : M} (Hf : f ⟶ f x [at x]) :
  continuous_at f x :=
continuous_at_intro
(take ε, suppose ε > 0,
obtain δ Hδ, from approaches_at_dest Hf this,
exists.intro δ (and.intro
  (and.left Hδ)
  (take x', suppose dist x' x < δ,
   if Heq : x' = x then
     by rewrite [-Heq, dist_self]; assumption
   else
     (suffices dist x' x < δ, from and.right Hδ x' this Heq,
      this))))

theorem converges_to_at_of_continuous_at {f : M → N} {x : M} (Hf : continuous_at f x) :
  f ⟶ f x [at x] :=
approaches_at_intro
  (take ε, suppose ε > 0,
    obtain δ [δpos Hδ], from continuous_at_dest Hf this,
    exists.intro δ (and.intro δpos (λ x' Hx' xnex', Hδ x' Hx')))

theorem converges_seq_comp_of_converges_seq_of_cts (X : ℕ → M) [HX : converges_seq X] {f : M → N}
        (Hf : continuous f) :
        converges_seq (λ n, f (X n)) :=
  begin
    cases HX with xlim Hxlim,
    existsi f xlim,
    apply approaches_at_infty_intro,
    intros ε Hε,
    let Hcont := (continuous_at_dest (forall_continuous_at_of_continuous Hf xlim)) Hε,
    cases Hcont with δ Hδ,
    cases approaches_at_infty_dest Hxlim (and.left Hδ) with B HB,
    existsi B,
    intro n Hn,
    apply and.right Hδ,
    apply HB Hn
  end

end continuity

end metric_space

end analysis

/- complete metric spaces -/

structure complete_metric_space [class] (M : Type) extends metricM : metric_space M : Type :=
(complete : ∀ X, @analysis.cauchy M metricM X → @analysis.converges_seq M metricM X)

namespace analysis

proposition complete (M : Type) [cmM : complete_metric_space M] {X : ℕ → M} (H : cauchy X) :
  converges_seq X :=
complete_metric_space.complete X H

end analysis

/- the reals form a metric space -/

noncomputable definition metric_space_real [instance] : metric_space ℝ :=
⦃ metric_space,
  dist               := λ x y, abs (x - y),
  dist_self          := λ x, abstract by rewrite [sub_self, abs_zero] end,
  eq_of_dist_eq_zero := λ x y, eq_of_abs_sub_eq_zero,
  dist_comm          := abs_sub,
  dist_triangle      := abs_sub_le
⦄


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

Modular equality relation.
-/
import data.int.gcd algebra.ordered_ring

namespace nat

/-- Modular equality. `modeq n a b`, or `a ≡ b [MOD n]`, means
  that `a - b` is a multiple of `n`. -/
def modeq (n a b : ℕ) := a % n = b % n

notation a ` ≡ `:50 b ` [MOD `:50 n `]`:0 := modeq n a b

namespace modeq
variables {n m a b c d : ℕ}

@[refl] protected theorem refl (a : ℕ) : a ≡ a [MOD n] := @rfl _ _

@[symm] protected theorem symm : a ≡ b [MOD n] → b ≡ a [MOD n] := eq.symm

@[trans] protected theorem trans : a ≡ b [MOD n] → b ≡ c [MOD n] → a ≡ c [MOD n] := eq.trans

instance : decidable (a ≡ b [MOD n]) := by unfold modeq; apply_instance

theorem modeq_zero_iff : a ≡ 0 [MOD n] ↔ n ∣ a :=
by rw [modeq, zero_mod, dvd_iff_mod_eq_zero]

theorem modeq_iff_dvd : a ≡ b [MOD n] ↔ (n:ℤ) ∣ b - a :=
by rw [modeq, eq_comm, ← int.coe_nat_inj'];
   simp [int.mod_eq_mod_iff_mod_sub_eq_zero, int.dvd_iff_mod_eq_zero]

theorem modeq_of_dvd : (n:ℤ) ∣ b - a → a ≡ b [MOD n] := modeq_iff_dvd.2
theorem dvd_of_modeq : a ≡ b [MOD n] → (n:ℤ) ∣ b - a := modeq_iff_dvd.1

theorem mod_modeq (a n) : a % n ≡ a [MOD n] := nat.mod_mod _ _

theorem modeq_of_dvd_of_modeq (d : m ∣ n) (h : a ≡ b [MOD n]) : a ≡ b [MOD m] :=
modeq_of_dvd $ dvd_trans (int.coe_nat_dvd.2 d) (dvd_of_modeq h)

theorem modeq_mul_left' (c : ℕ) (h : a ≡ b [MOD n]) : c * a ≡ c * b [MOD (c * n)] :=
by unfold modeq at *; rw [mul_mod_mul_left, mul_mod_mul_left, h]

theorem modeq_mul_left (c : ℕ) (h : a ≡ b [MOD n]) : c * a ≡ c * b [MOD n] :=
modeq_of_dvd_of_modeq (dvd_mul_left _ _) $ modeq_mul_left' _ h

theorem modeq_mul_right' (c : ℕ) (h : a ≡ b [MOD n]) : a * c ≡ b * c [MOD (n * c)] :=
by rw [mul_comm a, mul_comm b, mul_comm n]; exact modeq_mul_left' c h

theorem modeq_mul_right (c : ℕ) (h : a ≡ b [MOD n]) : a * c ≡ b * c [MOD n] :=
by rw [mul_comm a, mul_comm b]; exact modeq_mul_left c h

theorem modeq_mul (h₁ : a ≡ b [MOD n]) (h₂ : c ≡ d [MOD n]) : a * c ≡ b * d [MOD n] :=
(modeq_mul_left _ h₂).trans (modeq_mul_right _ h₁)

theorem modeq_add (h₁ : a ≡ b [MOD n]) (h₂ : c ≡ d [MOD n]) : a + c ≡ b + d [MOD n] :=
modeq_of_dvd $ by simpa using dvd_add (dvd_of_modeq h₁) (dvd_of_modeq h₂)

theorem modeq_add_cancel_left (h₁ : a ≡ b [MOD n]) (h₂ : a + c ≡ b + d [MOD n]) : c ≡ d [MOD n] :=
have (n:ℤ) ∣ a + (-a + (d + -c)),
by simpa using _root_.dvd_sub (dvd_of_modeq h₂) (dvd_of_modeq h₁),
modeq_of_dvd $ by rwa add_neg_cancel_left at this

theorem modeq_add_cancel_right (h₁ : c ≡ d [MOD n]) (h₂ : a + c ≡ b + d [MOD n]) : a ≡ b [MOD n] :=
by rw [add_comm a, add_comm b] at h₂; exact modeq_add_cancel_left h₁ h₂

theorem modeq_of_modeq_mul_left (m : ℕ) (h : a ≡ b [MOD m * n]) : a ≡ b [MOD n] :=
by rw [modeq_iff_dvd] at *; exact dvd.trans (dvd_mul_left (n : ℤ) (m : ℤ)) h

theorem modeq_of_modeq_mul_right (m : ℕ) : a ≡ b [MOD n * m] → a ≡ b [MOD n] :=
mul_comm m n ▸ modeq_of_modeq_mul_left _

def chinese_remainder (co : coprime n m) (a b : ℕ) : {k // k ≡ a [MOD n] ∧ k ≡ b [MOD m]} :=
⟨let (c, d) := xgcd n m in int.to_nat ((b * c * n + a * d * m) % (n * m)), begin
  rw xgcd_val, dsimp [chinese_remainder._match_1],
  rw [modeq_iff_dvd, modeq_iff_dvd],
  rw [int.to_nat_of_nonneg], swap,
  { by_cases h₁ : n = 0, {simp [coprime, h₁] at co, substs m n, simp},
    by_cases h₂ : m = 0, {simp [coprime, h₂] at co, substs m n, simp},
    exact int.mod_nonneg _
      (mul_ne_zero (int.coe_nat_ne_zero.2 h₁) (int.coe_nat_ne_zero.2 h₂)) },
  have := gcd_eq_gcd_ab n m, simp [co.gcd_eq_one, mul_comm] at this,
  rw [int.mod_def, ← sub_add, ← sub_add]; split,
  { refine dvd_add _ (dvd_trans (dvd_mul_right _ _) (dvd_mul_right _ _)),
    rw [add_comm, ← sub_sub], refine _root_.dvd_sub _ (dvd_mul_left _ _),
    have := congr_arg ((*) ↑a) this,
    exact ⟨_, by rwa [mul_add, ← mul_assoc, ← mul_assoc, mul_one, mul_comm,
        ← sub_eq_iff_eq_add] at this⟩ },
  { refine dvd_add _ (dvd_trans (dvd_mul_left _ _) (dvd_mul_right _ _)),
    rw [← sub_sub], refine _root_.dvd_sub _ (dvd_mul_left _ _),
    have := congr_arg ((*) ↑b) this,
    exact ⟨_, by rwa [mul_add, ← mul_assoc, ← mul_assoc, mul_one, mul_comm _ ↑m,
        ← sub_eq_iff_eq_add'] at this⟩ }
end⟩

lemma modeq_and_modeq_iff_modeq_mul {a b m n : ℕ} (hmn : coprime m n) :
  a ≡ b [MOD m] ∧ a ≡ b [MOD n] ↔ (a ≡ b [MOD m * n]) :=
⟨λ h, begin
    rw [nat.modeq.modeq_iff_dvd, nat.modeq.modeq_iff_dvd, ← int.dvd_nat_abs,
      int.coe_nat_dvd, ← int.dvd_nat_abs, int.coe_nat_dvd] at h,
    rw [nat.modeq.modeq_iff_dvd, ← int.dvd_nat_abs, int.coe_nat_dvd],
    exact hmn.mul_dvd_of_dvd_of_dvd h.1 h.2
  end,
λ h, ⟨nat.modeq.modeq_of_modeq_mul_right _ h, nat.modeq.modeq_of_modeq_mul_left _ h⟩⟩

lemma coprime_of_mul_modeq_one (b : ℕ) {a n : ℕ} (h : a * b ≡ 1 [MOD n]) : coprime a n :=
nat.coprime_of_dvd' (λ k ⟨ka, hka⟩ ⟨kb, hkb⟩, int.coe_nat_dvd.1 begin
  rw [hka, hkb, modeq_iff_dvd] at h,
  cases h with z hz,
  rw [sub_eq_iff_eq_add] at hz,
  rw [hz, int.coe_nat_mul, mul_assoc, mul_assoc, int.coe_nat_mul, ← mul_add],
  exact dvd_mul_right _ _,
end)

end modeq

@[simp] lemma mod_mul_right_mod (a b c : ℕ) : a % (b * c) % b = a % b :=
modeq.modeq_of_modeq_mul_right _ (modeq.mod_modeq _ _)

@[simp] lemma mod_mul_left_mod (a b c : ℕ) : a % (b * c) % c = a % c :=
modeq.modeq_of_modeq_mul_left _ (modeq.mod_modeq _ _)

lemma odd_mul_odd {n m : ℕ} (hn1 : n % 2 = 1) (hm1 : m % 2 = 1) : (n * m) % 2 = 1 :=
show (n * m) % 2 = (1 * 1) % 2, from nat.modeq.modeq_mul hn1 hm1

lemma odd_mul_odd_div_two {m n : ℕ} (hm1 : m % 2 = 1) (hn1 : n % 2 = 1) :
  (m * n) / 2 = m * (n / 2) + m / 2 :=
have hm0 : 0 < m := nat.pos_of_ne_zero (λ h, by simp * at *),
have hn0 : 0 < n := nat.pos_of_ne_zero (λ h, by simp * at *),
(nat.mul_left_inj (show 0 < 2, from dec_trivial)).1 $
by rw [mul_add, two_mul_odd_div_two hm1, mul_left_comm, two_mul_odd_div_two hn1,
  two_mul_odd_div_two (nat.odd_mul_odd hm1 hn1), nat.mul_sub_left_distrib, mul_one,
  ← nat.add_sub_assoc hm0, nat.sub_add_cancel (le_mul_of_ge_one_right' (nat.zero_le _) hn0)]

lemma odd_of_mod_four_eq_one {n : ℕ} (h : n % 4 = 1) : n % 2 = 1 :=
@modeq.modeq_of_modeq_mul_left 2 n 1 2 h

lemma odd_of_mod_four_eq_three {n : ℕ} (h : n % 4 = 3) : n % 2 = 1 :=
@modeq.modeq_of_modeq_mul_left 2 n 3 2 h

end nat

namespace list
variable {α : Type*}

lemma nth_rotate : ∀ {l : list α} {n m : ℕ} (hml : m < l.length),
  (l.rotate n).nth m = l.nth ((m + n) % l.length)
| []     n     m hml := (nat.not_lt_zero _ hml).elim
| l      0     m hml := by simp [nat.mod_eq_of_lt hml]
| (a::l) (n+1) m hml :=
have h₃ : m < list.length (l ++ [a]), by simpa using hml,
(lt_or_eq_of_le (nat.le_of_lt_succ $ nat.mod_lt (m + n)
  (lt_of_le_of_lt (nat.zero_le _) hml))).elim
(λ hml',
  have h₁ : (m + (n + 1)) % ((a :: l : list α).length) =
      (m + n) % ((a :: l : list α).length) + 1,
    from calc (m + (n + 1)) % (l.length + 1) =
      ((m + n) % (l.length + 1) + 1) % (l.length + 1) :
      add_assoc m n 1 ▸ nat.modeq.modeq_add (nat.mod_mod _ _).symm rfl
    ... = (m + n) % (l.length + 1) + 1 : nat.mod_eq_of_lt (nat.succ_lt_succ hml'),
  have h₂ : (m + n) % (l ++ [a]).length < l.length, by simpa [nat.add_one] using hml',
  by rw [list.rotate_cons_succ, nth_rotate h₃, list.nth_append h₂, h₁, list.nth]; simp)
(λ hml',
  have h₁ : (m + (n + 1)) % (l.length + 1) = 0,
    from calc (m + (n + 1)) % (l.length + 1) = (l.length + 1) % (l.length + 1) :
      add_assoc m n 1 ▸ nat.modeq.modeq_add
        (hml'.trans (nat.mod_eq_of_lt (nat.lt_succ_self _)).symm) rfl
    ... = 0 : by simp,
  have h₂ : l.length < (l ++ [a]).length, by simp [nat.lt_succ_self],
  by rw [list.length, list.rotate_cons_succ, nth_rotate h₃, list.length_append,
    list.length_cons, list.length, zero_add, hml', h₁, list.nth_concat_length]; refl)

lemma rotate_eq_self_iff_eq_repeat [hα : nonempty α] : ∀ {l : list α},
  (∀ n, l.rotate n = l) ↔ ∃ a, l = list.repeat a l.length
| []     := ⟨λ h, nonempty.elim hα (λ a, ⟨a, by simp⟩), by simp⟩
| (a::l) :=
⟨λ h, ⟨a, list.ext_le (by simp) $ λ n hn h₁,
  begin
    rw [← option.some_inj, ← list.nth_le_nth],
    conv {to_lhs, rw ← h ((list.length (a :: l)) - n)},
    rw [nth_rotate hn, nat.add_sub_cancel' (le_of_lt hn),
      nat.mod_self, nth_le_repeat _ hn], refl
  end⟩,
  λ ⟨a, ha⟩ n, ha.symm ▸ list.ext_le (by simp)
    (λ m hm h,
      have hm' : (m + n) % (list.repeat a (list.length (a :: l))).length < list.length (a :: l),
        by rw list.length_repeat; exact nat.mod_lt _ (nat.succ_pos _),
      by rw [nth_le_repeat, ← option.some_inj, ← list.nth_le_nth, nth_rotate h, list.nth_le_nth,
        nth_le_repeat]; simp * at *)⟩

end list


/-
Copyright (c) 2020 Alena Gusakov, Jalex Stark. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Alena Gusakov, Jalex Stark.
-/

import .basic

noncomputable theory
open_locale classical

universes u
variables {V : Type u}
namespace simple_graph

-- define map from graph to subgraph, use `is_subgraph` property from graph_induction.lean

def is_subgraph (H : simple_graph V) (G : simple_graph V) : Prop :=
∀ u v, H.adj u v → G.adj u v

variables (G : simple_graph V)
include G

@[refl] lemma is_subgraph_self : G.is_subgraph G := by tidy

lemma empty_is_subgraph : empty.is_subgraph G := by tidy

def subgraph := {H : simple_graph V // H.is_subgraph G}

namespace subgraph

-- CR-soon jstark: bundle these together
variables {G} (H : simple_graph V) (hG : H.is_subgraph G)
include hG

def edge_inclusion : H.E → G.E :=
begin
  intro e,
  set u := e.some,
  have hu := e.some_spec,
  set v := e.other hu,
  have hv := e.other_mem hu,
  suffices : G.adj u v, { apply G.edge_of_adj this },
  apply hG, apply e.other_adj,
end

-- properties of `inclusion`: it's an injective homomorphism

end subgraph

def induced_subgraph (G : simple_graph V) (S : set V) : simple_graph S :=
{
  adj := λ a b, G.adj a b,
  sym := λ a b h, G.sym h,
  loopless := λ x h, G.loopless x h
}

variables (s : set V) (S : simple_graph s)

-- two ideas:
    -- `induced_subgraph.to_supergraph`
    -- `simple_graph.to_induced_subgraph`

/-def induced_subgraph.to_supergraph {S : simple_graph s} {G : simple_graph V} : S.E → G.E
    | -/

def path_inclusion.to_supergraph {V : Type u} : T.path → S.path
    | []     := ∅
    | (h::t) := {h} ∪ list.to_set t

end simple_graph