/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro, Alexander Bentkamp
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.linear_algebra.linear_independent
import Mathlib.linear_algebra.projection
import Mathlib.data.fintype.card
import Mathlib.PostPort

universes u_1 u_3 u_5 u_2 u_6 u_7 u_4 u u_8 u_9 u_10 u_11

namespace Mathlib

/-!

# Bases

This file defines bases in a module or vector space.

It is inspired by Isabelle/HOL's linear algebra, and hence indirectly by HOL Light.

## Main definitions

All definitions are given for families of vectors, i.e. `v : ι → M` where `M` is the module or
vector space and `ι : Type*` is an arbitrary indexing type.

* `is_basis R v` states that the vector family `v` is a basis, i.e. it is linearly independent and
  spans the entire space.

* `is_basis.repr hv x` is the basis version of `linear_independent.repr hv x`. It returns the
  linear combination representing `x : M` on a basis `v` of `M` (using classical choice).
  The argument `hv` must be a proof that `is_basis R v`. `is_basis.repr hv` is given as a linear
  map as well.

* `is_basis.constr hv f` constructs a linear map `M₁ →ₗ[R] M₂` given the values `f : ι → M₂` at the
  basis `v : ι → M₁`, given `hv : is_basis R v`.

## Main statements

* `is_basis.ext` states that two linear maps are equal if they coincide on a basis.

* `exists_is_basis` states that every vector space has a basis.

## Implementation notes

We use families instead of sets because it allows us to say that two identical vectors are linearly
dependent. For bases, this is useful as well because we can easily derive ordered bases by using an
ordered index type `ι`.

## Tags

basis, bases

-/

/-- A family of vectors is a basis if it is linearly independent and all vectors are in the span. -/
def is_basis {ι : Type u_1} (R : Type u_3) {M : Type u_5} (v : ι → M) [ring R] [add_comm_group M]
    [module R M] :=
  linear_independent R v ∧ submodule.span R (set.range v) = ⊤

theorem is_basis.mem_span {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) (x : M) :
    x ∈ submodule.span R (set.range v) :=
  iff.mp submodule.eq_top_iff' (and.right hv)

theorem is_basis.comp {ι : Type u_1} {ι' : Type u_2} {R : Type u_3} {M : Type u_5} {v : ι → M}
    [ring R] [add_comm_group M] [module R M] (hv : is_basis R v) (f : ι' → ι)
    (hf : function.bijective f) : is_basis R (v ∘ f) :=
  sorry

theorem is_basis.injective {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] [nontrivial R] (hv : is_basis R v) : function.injective v :=
  fun (x y : ι) (h : v x = v y) => linear_independent.injective (and.left hv) h

theorem is_basis.range {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) :
    is_basis R fun (x : ↥(set.range v)) => ↑x :=
  sorry

/-- Given a basis, any vector can be written as a linear combination of the basis vectors. They are
given by this linear map. This is one direction of `module_equiv_finsupp`. -/
def is_basis.repr {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) : linear_map R M (ι →₀ R) :=
  linear_map.comp (linear_independent.repr sorry)
    (linear_map.cod_restrict (submodule.span R (set.range v)) linear_map.id (is_basis.mem_span hv))

theorem is_basis.total_repr {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) (x : M) :
    coe_fn (finsupp.total ι M R v) (coe_fn (is_basis.repr hv) x) = x :=
  linear_independent.total_repr (and.left hv) { val := x, property := is_basis.mem_span hv x }

theorem is_basis.total_comp_repr {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) :
    linear_map.comp (finsupp.total ι M R v) (is_basis.repr hv) = linear_map.id :=
  linear_map.ext (is_basis.total_repr hv)

theorem is_basis.ext {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6} {v : ι → M}
    [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M']
    {f : linear_map R M M'} {g : linear_map R M M'} (hv : is_basis R v)
    (h : ∀ (i : ι), coe_fn f (v i) = coe_fn g (v i)) : f = g :=
  linear_map.ext_on_range (and.right hv) h

theorem is_basis.repr_ker {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) : linear_map.ker (is_basis.repr hv) = ⊥ :=
  iff.mpr linear_map.ker_eq_bot (function.left_inverse.injective (is_basis.total_repr hv))

theorem is_basis.repr_range {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) :
    linear_map.range (is_basis.repr hv) = finsupp.supported R R set.univ :=
  sorry

theorem is_basis.repr_total {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) (x : ι →₀ R)
    (hx : x ∈ finsupp.supported R R set.univ) :
    coe_fn (is_basis.repr hv) (coe_fn (finsupp.total ι M R v) x) = x :=
  sorry

theorem is_basis.repr_eq_single {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) {i : ι} :
    coe_fn (is_basis.repr hv) (v i) = finsupp.single i 1 :=
  sorry

@[simp] theorem is_basis.repr_self_apply {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M}
    [ring R] [add_comm_group M] [module R M] (hv : is_basis R v) (i : ι) (j : ι) :
    coe_fn (coe_fn (is_basis.repr hv) (v i)) j = ite (i = j) 1 0 :=
  sorry

theorem is_basis.repr_eq_iff {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) {f : linear_map R M (ι →₀ R)} :
    is_basis.repr hv = f ↔ ∀ (i : ι), coe_fn f (v i) = finsupp.single i 1 :=
  sorry

theorem is_basis.repr_apply_eq {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) {f : M → ι → R}
    (hadd : ∀ (x y : M), f (x + y) = f x + f y) (hsmul : ∀ (c : R) (x : M), f (c • x) = c • f x)
    (f_eq : ∀ (i : ι), f (v i) = ⇑(finsupp.single i 1)) (x : M) (i : ι) :
    coe_fn (coe_fn (is_basis.repr hv) x) i = f x i :=
  sorry

theorem is_basis.range_repr_self {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) (i : ι) :
    coe_fn (is_basis.repr (is_basis.range hv)) (v i) =
        finsupp.single { val := v i, property := set.mem_range_self i } 1 :=
  sorry

@[simp] theorem is_basis.range_repr {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M}
    [ring R] [add_comm_group M] [module R M] {x : M} (hv : is_basis R v) (i : ι) :
    coe_fn (coe_fn (is_basis.repr (is_basis.range hv)) x)
          { val := v i, property := set.mem_range_self i } =
        coe_fn (coe_fn (is_basis.repr hv) x) i :=
  sorry

/-- Construct a linear map given the value at the basis. -/
def is_basis.constr {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6} {v : ι → M}
    [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M'] (hv : is_basis R v)
    (f : ι → M') : linear_map R M M' :=
  linear_map.comp (finsupp.total M' M' R id)
    (linear_map.comp (finsupp.lmap_domain R R f) (is_basis.repr hv))

theorem is_basis.constr_apply {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6}
    {v : ι → M} [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M']
    (hv : is_basis R v) (f : ι → M') (x : M) :
    coe_fn (is_basis.constr hv f) x =
        finsupp.sum (coe_fn (is_basis.repr hv) x) fun (b : ι) (a : R) => a • f b :=
  sorry

@[simp] theorem constr_basis {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6}
    {v : ι → M} [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M']
    {f : ι → M'} {i : ι} (hv : is_basis R v) : coe_fn (is_basis.constr hv f) (v i) = f i :=
  sorry

theorem constr_eq {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6} {v : ι → M} [ring R]
    [add_comm_group M] [add_comm_group M'] [module R M] [module R M'] {g : ι → M'}
    {f : linear_map R M M'} (hv : is_basis R v) (h : ∀ (i : ι), g i = coe_fn f (v i)) :
    is_basis.constr hv g = f :=
  is_basis.ext hv fun (i : ι) => Eq.trans (constr_basis hv) (h i)

theorem constr_self {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6} {v : ι → M}
    [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M'] (hv : is_basis R v)
    (f : linear_map R M M') : (is_basis.constr hv fun (i : ι) => coe_fn f (v i)) = f :=
  constr_eq hv fun (x : ι) => rfl

theorem constr_zero {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6} {v : ι → M}
    [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M'] (hv : is_basis R v) :
    (is_basis.constr hv fun (i : ι) => 0) = 0 :=
  constr_eq hv fun (x : ι) => rfl

theorem constr_add {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6} {v : ι → M} [ring R]
    [add_comm_group M] [add_comm_group M'] [module R M] [module R M'] {g : ι → M'} {f : ι → M'}
    (hv : is_basis R v) :
    (is_basis.constr hv fun (i : ι) => f i + g i) = is_basis.constr hv f + is_basis.constr hv g :=
  sorry

theorem constr_neg {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6} {v : ι → M} [ring R]
    [add_comm_group M] [add_comm_group M'] [module R M] [module R M'] {f : ι → M'}
    (hv : is_basis R v) : (is_basis.constr hv fun (i : ι) => -f i) = -is_basis.constr hv f :=
  sorry

theorem constr_sub {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6} {v : ι → M} [ring R]
    [add_comm_group M] [add_comm_group M'] [module R M] [module R M'] (hv : is_basis R v)
    {g : ι → M'} {f : ι → M'} (hs : is_basis R v) :
    (is_basis.constr hv fun (i : ι) => f i - g i) = is_basis.constr hs f - is_basis.constr hs g :=
  sorry

-- this only works on functions if `R` is a commutative ring

theorem constr_smul {ι : Type u_1} {R : Type u_2} {M : Type u_3} [comm_ring R] [add_comm_group M]
    [module R M] {v : ι → R} {f : ι → M} {a : R} (hv : is_basis R v) :
    (is_basis.constr hv fun (b : ι) => a • f b) = a • is_basis.constr hv f :=
  sorry

theorem constr_range {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6} {v : ι → M}
    [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M'] [Nonempty ι]
    (hv : is_basis R v) {f : ι → M'} :
    linear_map.range (is_basis.constr hv f) = submodule.span R (set.range f) :=
  sorry

/-- Canonical equivalence between a module and the linear combinations of basis vectors. -/
def module_equiv_finsupp {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hv : is_basis R v) : linear_equiv R M (ι →₀ R) :=
  linear_equiv.symm
    (linear_equiv.trans (linear_independent.total_equiv sorry)
      (linear_equiv.of_top (submodule.span R (set.range v)) sorry))

@[simp] theorem module_equiv_finsupp_apply_basis {ι : Type u_1} {R : Type u_3} {M : Type u_5}
    {v : ι → M} [ring R] [add_comm_group M] [module R M] (hv : is_basis R v) (i : ι) :
    coe_fn (module_equiv_finsupp hv) (v i) = finsupp.single i 1 :=
  sorry

/-- Isomorphism between the two modules, given two modules `M` and `M'` with respective bases
`v` and `v'` and a bijection between the indexing sets of the two bases. -/
def linear_equiv_of_is_basis {ι : Type u_1} {ι' : Type u_2} {R : Type u_3} {M : Type u_5}
    {M' : Type u_6} [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M']
    {v : ι → M} {v' : ι' → M'} (hv : is_basis R v) (hv' : is_basis R v') (e : ι ≃ ι') :
    linear_equiv R M M' :=
  linear_equiv.mk (linear_map.to_fun (is_basis.constr hv (v' ∘ ⇑e))) sorry sorry
    ⇑(is_basis.constr hv' (v ∘ ⇑(equiv.symm e))) sorry sorry

/-- Isomorphism between the two modules, given two modules `M` and `M'` with respective bases
`v` and `v'` and a bijection between the two bases. -/
def linear_equiv_of_is_basis' {ι : Type u_1} {ι' : Type u_2} {R : Type u_3} {M : Type u_5}
    {M' : Type u_6} [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M']
    {v : ι → M} {v' : ι' → M'} (f : M → M') (g : M' → M) (hv : is_basis R v) (hv' : is_basis R v')
    (hf : ∀ (i : ι), f (v i) ∈ set.range v') (hg : ∀ (i : ι'), g (v' i) ∈ set.range v)
    (hgf : ∀ (i : ι), g (f (v i)) = v i) (hfg : ∀ (i : ι'), f (g (v' i)) = v' i) :
    linear_equiv R M M' :=
  linear_equiv.mk (linear_map.to_fun (is_basis.constr hv (f ∘ v))) sorry sorry
    ⇑(is_basis.constr hv' (g ∘ v')) sorry sorry

@[simp] theorem linear_equiv_of_is_basis_comp {ι : Type u_1} {ι' : Type u_2} {R : Type u_3}
    {M : Type u_5} {M' : Type u_6} {M'' : Type u_7} [ring R] [add_comm_group M] [add_comm_group M']
    [add_comm_group M''] [module R M] [module R M'] [module R M''] {ι'' : Type u_4} {v : ι → M}
    {v' : ι' → M'} {v'' : ι'' → M''} (hv : is_basis R v) (hv' : is_basis R v')
    (hv'' : is_basis R v'') (e : ι ≃ ι') (f : ι' ≃ ι'') :
    linear_equiv.trans (linear_equiv_of_is_basis hv hv' e) (linear_equiv_of_is_basis hv' hv'' f) =
        linear_equiv_of_is_basis hv hv'' (equiv.trans e f) :=
  sorry

@[simp] theorem linear_equiv_of_is_basis_refl {ι : Type u_1} {R : Type u_3} {M : Type u_5}
    {v : ι → M} [ring R] [add_comm_group M] [module R M] (hv : is_basis R v) :
    linear_equiv_of_is_basis hv hv (equiv.refl ι) = linear_equiv.refl R M :=
  sorry

theorem linear_equiv_of_is_basis_trans_symm {ι : Type u_1} {ι' : Type u_2} {R : Type u_3}
    {M : Type u_5} {M' : Type u_6} {v : ι → M} [ring R] [add_comm_group M] [add_comm_group M']
    [module R M] [module R M'] (hv : is_basis R v) (e : ι ≃ ι') {v' : ι' → M'}
    (hv' : is_basis R v') :
    linear_equiv.trans (linear_equiv_of_is_basis hv hv' e)
          (linear_equiv_of_is_basis hv' hv (equiv.symm e)) =
        linear_equiv.refl R M :=
  sorry

theorem linear_equiv_of_is_basis_symm_trans {ι : Type u_1} {ι' : Type u_2} {R : Type u_3}
    {M : Type u_5} {M' : Type u_6} {v : ι → M} [ring R] [add_comm_group M] [add_comm_group M']
    [module R M] [module R M'] (hv : is_basis R v) (e : ι ≃ ι') {v' : ι' → M'}
    (hv' : is_basis R v') :
    linear_equiv.trans (linear_equiv_of_is_basis hv' hv (equiv.symm e))
          (linear_equiv_of_is_basis hv hv' e) =
        linear_equiv.refl R M' :=
  sorry

theorem is_basis_inl_union_inr {ι : Type u_1} {ι' : Type u_2} {R : Type u_3} {M : Type u_5}
    {M' : Type u_6} [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M']
    {v : ι → M} {v' : ι' → M'} (hv : is_basis R v) (hv' : is_basis R v') :
    is_basis R (sum.elim (⇑(linear_map.inl R M M') ∘ v) (⇑(linear_map.inr R M M') ∘ v')) :=
  sorry

theorem is_basis_singleton_one {ι : Type u_1} (R : Type u_2) [unique ι] [ring R] :
    is_basis R fun (_x : ι) => 1 :=
  sorry

protected theorem linear_equiv.is_basis {ι : Type u_1} {R : Type u_3} {M : Type u_5} {M' : Type u_6}
    {v : ι → M} [ring R] [add_comm_group M] [add_comm_group M'] [module R M] [module R M']
    (hs : is_basis R v) (f : linear_equiv R M M') : is_basis R (⇑f ∘ v) :=
  sorry

theorem is_basis_span {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] (hs : linear_independent R v) :
    is_basis R
        fun (i : ι) => { val := v i, property := submodule.subset_span (set.mem_range_self i) } :=
  sorry

theorem is_basis_empty {ι : Type u_1} {R : Type u_3} {M : Type u_5} [ring R] [add_comm_group M]
    [module R M] (h_empty : ¬Nonempty ι) (h : ∀ (x : M), x = 0) : is_basis R fun (x : ι) => 0 :=
  sorry

theorem is_basis_empty_bot {ι : Type u_1} {R : Type u_3} {M : Type u_5} [ring R] [add_comm_group M]
    [module R M] (h_empty : ¬Nonempty ι) : is_basis R fun (_x : ι) => 0 :=
  is_basis_empty h_empty
    fun (x : ↥⊥) => iff.mpr subtype.ext_iff_val (iff.mp (submodule.mem_bot R) (subtype.mem x))

/-- A module over `R` with a finite basis is linearly equivalent to functions from its basis to `R`.
-/
def is_basis.equiv_fun {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] [fintype ι] (h : is_basis R v) : linear_equiv R M (ι → R) :=
  linear_equiv.trans (module_equiv_finsupp h)
    (linear_equiv.mk finsupp.to_fun sorry sorry (equiv.inv_fun finsupp.equiv_fun_on_fintype) sorry
      sorry)

/-- A module over a finite ring that admits a finite basis is finite. -/
def module.fintype_of_fintype {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] [fintype ι] (h : is_basis R v) [fintype R] : fintype M :=
  fintype.of_equiv (ι → R) (equiv.symm (linear_equiv.to_equiv (is_basis.equiv_fun h)))

theorem module.card_fintype {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] [fintype ι] (h : is_basis R v) [fintype R] [fintype M] :
    fintype.card M = fintype.card R ^ fintype.card ι :=
  Eq.trans (fintype.card_congr (linear_equiv.to_equiv (is_basis.equiv_fun h))) fintype.card_fun

/-- Given a basis `v` indexed by `ι`, the canonical linear equivalence between `ι → R` and `M` maps
a function `x : ι → R` to the linear combination `∑_i x i • v i`. -/
@[simp] theorem is_basis.equiv_fun_symm_apply {ι : Type u_1} {R : Type u_3} {M : Type u_5}
    {v : ι → M} [ring R] [add_comm_group M] [module R M] [fintype ι] (h : is_basis R v)
    (x : ι → R) :
    coe_fn (linear_equiv.symm (is_basis.equiv_fun h)) x =
        finset.sum finset.univ fun (i : ι) => x i • v i :=
  sorry

theorem is_basis.equiv_fun_apply {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] [fintype ι] (h : is_basis R v) (u : M) :
    coe_fn (is_basis.equiv_fun h) u = ⇑(coe_fn (is_basis.repr h) u) :=
  rfl

theorem is_basis.equiv_fun_total {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M} [ring R]
    [add_comm_group M] [module R M] [fintype ι] (h : is_basis R v) (u : M) :
    (finset.sum finset.univ fun (i : ι) => coe_fn (is_basis.equiv_fun h) u i • v i) = u :=
  sorry

@[simp] theorem is_basis.equiv_fun_self {ι : Type u_1} {R : Type u_3} {M : Type u_5} {v : ι → M}
    [ring R] [add_comm_group M] [module R M] [fintype ι] (h : is_basis R v) (i : ι) (j : ι) :
    coe_fn (is_basis.equiv_fun h) (v i) j = ite (i = j) 1 0 :=
  sorry

@[simp] theorem is_basis.constr_apply_fintype {ι : Type u_1} {R : Type u_3} {M : Type u_5}
    {M' : Type u_6} {v : ι → M} [ring R] [add_comm_group M] [add_comm_group M'] [module R M]
    [module R M'] [fintype ι] (h : is_basis R v) (f : ι → M') (x : M) :
    coe_fn (is_basis.constr h f) x =
        finset.sum finset.univ fun (i : ι) => coe_fn (is_basis.equiv_fun h) x i • f i :=
  sorry

theorem exists_subset_is_basis {K : Type u_4} {V : Type u} [field K] [add_comm_group V]
    [vector_space K V] {s : set V} (hs : linear_independent K fun (x : ↥s) => ↑x) :
    ∃ (b : set V), s ⊆ b ∧ is_basis K coe :=
  sorry

theorem exists_sum_is_basis {ι : Type u_1} {K : Type u_4} {V : Type u} [field K] [add_comm_group V]
    [vector_space K V] {v : ι → V} (hs : linear_independent K v) :
    ∃ (ι' : Type u), ∃ (v' : ι' → V), is_basis K (sum.elim v v') :=
  sorry

theorem exists_is_basis (K : Type u_4) (V : Type u) [field K] [add_comm_group V]
    [vector_space K V] : ∃ (b : set V), is_basis K fun (i : ↥b) => ↑i :=
  sorry

theorem linear_map.exists_left_inverse_of_injective {K : Type u_4} {V : Type u} {V' : Type u_8}
    [field K] [add_comm_group V] [add_comm_group V'] [vector_space K V] [vector_space K V']
    (f : linear_map K V V') (hf_inj : linear_map.ker f = ⊥) :
    ∃ (g : linear_map K V' V), linear_map.comp g f = linear_map.id :=
  sorry

theorem submodule.exists_is_compl {K : Type u_4} {V : Type u} [field K] [add_comm_group V]
    [vector_space K V] (p : submodule K V) : ∃ (q : submodule K V), is_compl p q :=
  sorry

theorem linear_map.exists_right_inverse_of_surjective {K : Type u_4} {V : Type u} {V' : Type u_8}
    [field K] [add_comm_group V] [add_comm_group V'] [vector_space K V] [vector_space K V']
    (f : linear_map K V V') (hf_surj : linear_map.range f = ⊤) :
    ∃ (g : linear_map K V' V), linear_map.comp f g = linear_map.id :=
  sorry

theorem quotient_prod_linear_equiv {K : Type u_4} {V : Type u} [field K] [add_comm_group V]
    [vector_space K V] (p : submodule K V) :
    Nonempty (linear_equiv K (submodule.quotient p × ↥p) V) :=
  sorry

theorem vector_space.card_fintype (K : Type u_4) (V : Type u) [field K] [add_comm_group V]
    [vector_space K V] [fintype K] [fintype V] : ∃ (n : ℕ), fintype.card V = fintype.card K ^ n :=
  exists.elim (exists_is_basis K V)
    fun (b : set V) (hb : is_basis K fun (i : ↥b) => ↑i) =>
      Exists.intro (fintype.card ↥b) (module.card_fintype hb)

namespace pi

theorem linear_independent_std_basis {R : Type u_3} {η : Type u_9} {ιs : η → Type u_10}
    {Ms : η → Type u_11} [ring R] [(i : η) → add_comm_group (Ms i)] [(i : η) → module R (Ms i)]
    [DecidableEq η] (v : (j : η) → ιs j → Ms j) (hs : ∀ (i : η), linear_independent R (v i)) :
    linear_independent R
        fun (ji : sigma fun (j : η) => ιs j) =>
          coe_fn (linear_map.std_basis R Ms (sigma.fst ji)) (v (sigma.fst ji) (sigma.snd ji)) :=
  sorry

theorem is_basis_std_basis {R : Type u_3} {η : Type u_9} {ιs : η → Type u_10} {Ms : η → Type u_11}
    [ring R] [(i : η) → add_comm_group (Ms i)] [(i : η) → module R (Ms i)] [fintype η]
    [DecidableEq η] (s : (j : η) → ιs j → Ms j) (hs : ∀ (j : η), is_basis R (s j)) :
    is_basis R
        fun (ji : sigma fun (j : η) => ιs j) =>
          coe_fn (linear_map.std_basis R Ms (sigma.fst ji)) (s (sigma.fst ji) (sigma.snd ji)) :=
  sorry

theorem is_basis_fun₀ (R : Type u_3) (η : Type u_9) [ring R] [fintype η] [DecidableEq η] :
    is_basis R
        fun (ji : sigma fun (j : η) => Unit) =>
          coe_fn (linear_map.std_basis R (fun (i : η) => R) (sigma.fst ji)) 1 :=
  is_basis_std_basis (fun (_x : η) (_x : Unit) => 1) fun (i : η) => is_basis_singleton_one R

theorem is_basis_fun (R : Type u_3) (η : Type u_9) [ring R] [fintype η] [DecidableEq η] :
    is_basis R fun (i : η) => coe_fn (linear_map.std_basis R (fun (i : η) => R) i) 1 :=
  sorry

@[simp] theorem is_basis_fun_repr (R : Type u_3) (η : Type u_9) [ring R] [fintype η] [DecidableEq η]
    (x : η → R) (i : η) : coe_fn (coe_fn (is_basis.repr (is_basis_fun R η)) x) i = x i :=
  sorry

end Mathlib


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

import tactic.rcases

instance {α} : has_inter (set α) := ⟨λ s t, {a | a ∈ s ∧ a ∈ t}⟩

universe u
variables {α β γ : Type u}

example (x : α × β × γ) : true :=
begin
  rcases x with ⟨a, b, c⟩,
  { guard_hyp a : α,
    guard_hyp b : β,
    guard_hyp c : γ,
    trivial }
end

example (x : α × β × γ) : true :=
begin
  rcases x with ⟨a, ⟨-, c⟩⟩,
  { guard_hyp a : α,
    success_if_fail { guard_hyp x_snd_fst : β },
    guard_hyp c : γ,
    trivial }
end

example (x : (α × β) × γ) : true :=
begin
  rcases x with ⟨⟨a:α, b⟩, c⟩,
  { guard_hyp a : α,
    guard_hyp b : β,
    guard_hyp c : γ,
    trivial }
end

example : inhabited α × option β ⊕ γ → true :=
begin
  rintro (⟨⟨a⟩, _ | b⟩ | c),
  { guard_hyp a : α, trivial },
  { guard_hyp a : α, guard_hyp b : β, trivial },
  { guard_hyp c : γ, trivial }
end

example : cond ff ℕ ℤ → cond tt ℤ ℕ → (ℕ ⊕ unit) → true :=
begin
  rintro (x y : ℤ) (z | u),
  { guard_hyp x : ℤ, guard_hyp y : ℤ, guard_hyp z : ℕ, trivial },
  { guard_hyp x : ℤ, guard_hyp y : ℤ, guard_hyp u : unit, trivial }
end

example (x y : ℕ) (h : x = y) : true :=
begin
  rcases x with _|⟨⟩|z,
  { guard_hyp h : nat.zero = y, trivial },
  { guard_hyp h : nat.succ nat.zero = y, trivial },
  { guard_hyp z : ℕ,
    guard_hyp h : z.succ.succ = y, trivial },
end

-- from equiv.sum_empty
example (s : α ⊕ empty) : true :=
begin
  rcases s with _ | ⟨⟨⟩⟩,
  { guard_hyp s : α, trivial }
end

example : true :=
begin
  obtain ⟨n : ℕ, h : n = n, -⟩ : ∃ n : ℕ, n = n ∧ true,
  { existsi 0, simp },
  guard_hyp n : ℕ,
  guard_hyp h : n = n,
  success_if_fail {assumption},
  trivial
end

example : true :=
begin
  obtain : ∃ n : ℕ, n = n ∧ true,
  { existsi 0, simp },
  trivial
end

example : true :=
begin
  obtain (h : true) | ⟨⟨⟩⟩ : true ∨ false,
  { left, trivial },
  guard_hyp h : true,
  trivial
end

example : true :=
begin
  obtain h | ⟨⟨⟩⟩ : true ∨ false := or.inl trivial,
  guard_hyp h : true,
  trivial
end

example : true :=
begin
  obtain ⟨h, h2⟩ := and.intro trivial trivial,
  guard_hyp h : true,
  guard_hyp h2 : true,
  trivial
end

example : true :=
begin
  success_if_fail {obtain ⟨h, h2⟩},
  trivial
end

example (x y : α × β) : true :=
begin
  rcases ⟨x, y⟩ with ⟨⟨a, b⟩, c, d⟩,
  { guard_hyp a : α,
    guard_hyp b : β,
    guard_hyp c : α,
    guard_hyp d : β,
    trivial }
end

example (x y : α ⊕ β) : true :=
begin
  obtain ⟨a|b, c|d⟩ := ⟨x, y⟩,
  { guard_hyp a : α, guard_hyp c : α, trivial },
  { guard_hyp a : α, guard_hyp d : β, trivial },
  { guard_hyp b : β, guard_hyp c : α, trivial },
  { guard_hyp b : β, guard_hyp d : β, trivial },
end

example {i j : ℕ} : (Σ' x, i ≤ x ∧ x ≤ j) → i ≤ j :=
begin
  intro h,
  rcases h' : h with ⟨x,h₀,h₁⟩,
  guard_hyp h' : h = ⟨x,h₀,h₁⟩,
  apply le_trans h₀ h₁,
end

protected def set.foo {α β} (s : set α) (t : set β) : set (α × β) := ∅

example {α} (V : set α) (w : true → ∃ p, p ∈ (V.foo V) ∩ (V.foo V)) : true :=
begin
  obtain ⟨a, h⟩ : ∃ p, p ∈ (V.foo V) ∩ (V.foo V) := w trivial,
  trivial,
end

example (n : ℕ) : true :=
begin
  obtain one_lt_n | n_le_one : 1 < n + 1 ∨ n + 1 ≤ 1 := nat.lt_or_ge 1 (n + 1),
  trivial, trivial,
end

example (n : ℕ) : true :=
begin
  obtain one_lt_n | (n_le_one : n + 1 ≤ 1) := nat.lt_or_ge 1 (n + 1),
  trivial, trivial,
end

example (h : ∃ x : ℕ, x = x ∧ 1 = 1) : true :=
begin
  rcases h with ⟨-, _⟩,
  (do lc ← tactic.local_context, guard lc.empty),
  trivial
end

example (h : ∃ x : ℕ, x = x ∧ 1 = 1) : true :=
begin
  rcases h with ⟨-, _, h⟩,
  (do lc ← tactic.local_context, guard (lc.length = 1)),
  guard_hyp h : 1 = 1,
  trivial
end

example (h : true ∨ true ∨ true) : true :=
begin
  rcases h with -|-|-,
  iterate 3 {
    (do lc ← tactic.local_context, guard lc.empty),
    trivial },
end

example : bool → false → true
| ff := by rintro ⟨⟩
| tt := by rintro ⟨⟩

example : true :=
begin
  obtain h : true,
  { trivial },
  exact h
end

example {a b} (h : a ∧ b) : a ∧ b :=
begin
  rcases h with t,
  exact t
end

structure baz {α : Type*} (f : α → α) : Prop := [inst : nonempty α] (h : f ∘ f = id)
example {α} (f : α → α) (h : baz f) : true := by { rcases h with ⟨_⟩; trivial }
example {α} (f : α → α) (h : baz f) : true := by { rcases h with @⟨_, _⟩; trivial }

inductive test : nat → Prop
| a (n) : test (2 + n)
| b {n} : n > 5 → test (n * n)

example {n} (h : test n) : n = n :=
begin
  have : true,
  { rcases h with a | b,
    { guard_hyp a : nat, trivial },
    { guard_hyp b : ‹nat› > 5, trivial } },
  { rcases h with a | @⟨n, b⟩,
    { guard_hyp a : nat, trivial },
    { guard_hyp b : n > 5, trivial } },
end

open tactic
meta def test_rcases_hint (s : string) (num_goals : ℕ) (depth := 5) : tactic unit :=
do change `(true),
  h ← get_local `h,
  pat ← rcases_hint ```(h) depth,
  p ← pp pat,
  guard (p.to_string = s) <|> fail format!"got '{p.to_string}', expected: '{s}'",
  gs ← get_goals,
  guard (gs.length = num_goals) <|> fail format!"there are {gs.length} goals remaining",
  all_goals triv $> ()

example {α} (h : ∃ x : α, x = x) := by test_rcases_hint "⟨h_w, ⟨⟩⟩" 1
example (h : true ∨ true ∨ true) := by test_rcases_hint "⟨⟨⟩⟩ | ⟨⟨⟩⟩ | ⟨⟨⟩⟩" 3
example (h : ℕ) := by test_rcases_hint "_ | _ | h" 3 2
example {p} (h : (p ∧ p) ∨ (p ∧ p)) :=
by test_rcases_hint "⟨h_left, h_right⟩ | ⟨h_left, h_right⟩" 2
example {p} (h : (p ∧ p) ∨ (p ∧ (p ∨ p))) :=
by test_rcases_hint "⟨h_left, h_right⟩ | ⟨h_left, h_right | h_right⟩" 3
example {p} (h : p ∧ (p ∨ p)) :=
by test_rcases_hint "⟨h_left, h_right | h_right⟩" 2
example (h : 0 < 2) := by test_rcases_hint "_ | ⟨_, _ | ⟨_, ⟨⟩⟩⟩" 1
example (h : 3 < 2) := by test_rcases_hint "_ | ⟨_, _ | ⟨_, ⟨⟩⟩⟩" 0
example (h : 3 < 0) := by test_rcases_hint "⟨⟩" 0
example (h : false) := by test_rcases_hint "⟨⟩" 0
example (h : true) := by test_rcases_hint "⟨⟩" 1
example {α} (h : list α) := by test_rcases_hint "_ | ⟨h_hd, _ | ⟨h_tl_hd, h_tl_tl⟩⟩" 3 2
example {α} (h : (α ⊕ α) × α) := by test_rcases_hint "⟨h_fst | h_fst, h_snd⟩" 2 2

inductive foo (α : Type) : ℕ → Type
| zero : foo 0
| one (m) : α → foo m

example {α} (h : foo α 0) : true := by test_rcases_hint "_ | ⟨_, h_ᾰ⟩" 2
example {α} (h : foo α 1) : true := by test_rcases_hint "_ | ⟨_, h_ᾰ⟩" 1
example {α n} (h : foo α n) : true := by test_rcases_hint "_ | h_ᾰ" 2 1

example {α} (V : set α) (h : ∃ p, p ∈ (V.foo V) ∩ (V.foo V)) :=
by test_rcases_hint "⟨⟨h_w_fst, h_w_snd⟩, ⟨⟩⟩" 0

section rsuffices

/-- These next few are duplicated from `rcases/obtain` tests, with the goal order swapped. -/

example : true :=
begin
  rsuffices ⟨n : ℕ, h : n = n, -⟩ : ∃ n : ℕ, n = n ∧ true,
  { guard_hyp n : ℕ,
    guard_hyp h : n = n,
    success_if_fail {assumption},
    trivial },
  { existsi 0, simp },
end

example : true :=
begin
  rsuffices : ∃ n : ℕ, n = n ∧ true,
  { trivial },
  { existsi 0, simp },
end

example : true :=
begin
  rsuffices (h : true) | ⟨⟨⟩⟩ : true ∨ false,
  { guard_hyp h : true,
    trivial },
  { left, trivial },
end

example : true :=
begin
  success_if_fail {rsuffices ⟨h, h2⟩},
  trivial
end

example (x y : α × β) : true :=
begin
  rsuffices ⟨⟨a, b⟩, c, d⟩ : (α × β) × (α × β),
  { guard_hyp a : α,
    guard_hyp b : β,
    guard_hyp c : α,
    guard_hyp d : β,
    trivial },
  { exact ⟨x, y⟩ }
end

-- This test demonstrates why `swap` is not used in the implementation of `rsuffices`:
-- it would make the _second_ goal the one requiring ⟨x, y⟩, not the last one.
example (x y : α ⊕ β) : true :=
begin
  rsuffices ⟨a|b, c|d⟩ : (α ⊕ β) × (α ⊕ β),
  { guard_hyp a : α, guard_hyp c : α, trivial },
  { guard_hyp a : α, guard_hyp d : β, trivial },
  { guard_hyp b : β, guard_hyp c : α, trivial },
  { guard_hyp b : β, guard_hyp d : β, trivial },
  exact ⟨x, y⟩,
end

example {α} (V : set α) (w : true → ∃ p, p ∈ (V.foo V) ∩ (V.foo V)) : true :=
begin
  rsuffices ⟨a, h⟩ : ∃ p, p ∈ (V.foo V) ∩ (V.foo V),
  { trivial },
  { exact w trivial },
end

-- Now some tests that ensure that things stay in the correct order.

-- This test demonstrates why `focus1` is required in the definition of `rsuffices`; otherwise
-- the `∃ ...` goal would get put _after_ the `true` goal.
example : nonempty ℕ ∧ true :=
begin
  split,
  rsuffices ⟨n : ℕ, hn⟩ : ∃ n, _,
  { exact ⟨n⟩ },
  { exact true },
  { exact ⟨0, trivial⟩ },
  { trivial },
end

section instances

example (h : Π {α}, inhabited α) : inhabited (α ⊕ β) :=
begin
  rsufficesI (ha | hb) : inhabited α ⊕ inhabited β,
  { exact ⟨sum.inl default⟩ },
  { exact ⟨sum.inr default⟩ },
  { exact sum.inl h }
end

include β
-- this test demonstrates that the `resetI` also applies onto the goal.
example (h : Π {α}, inhabited α) : inhabited α :=
begin
  have : inhabited β := h,
  rsufficesI t : β,
  { exact h },
  { exact default }
end

example (h : Π {α}, inhabited α) : β :=
begin
  rsufficesI ht : inhabited β,
  { guard_hyp ht : inhabited β,
    exact default },
  { exact h }
end

end instances

end rsuffices


import S5.syntax.basic

open prf

-- Identity implication
theorem idd { p : form } { Γ : ctx } : Γ ⊢ₛ₅ p → p := by
  apply mp
  case hpq =>
    apply mp
    case hpq => exact @pl2 Γ p (p → p) p
    case hp => exact @pl1 Γ p (p → p)
  case hp => exact @pl1 Γ p p

-- Deduction metatheorem
theorem deduction { Γ : ctx } { p q : form } : ((Γ ∪ p) ⊢ₛ₅ q) → (Γ ⊢ₛ₅ p → q) := by
  intros h
  cases h
  {
    rename_i h₀;
    cases h₀;
    { exact idd }
    {
      rename_i h₁;
      exact mp pl1 (ax h₁)
    }
  }
  { exact mp pl1 pl1 }
  { exact mp pl1 pl2 }
  { exact mp pl1 pl3 }
  {
    sorry
  }
  { exact mp pl1 k }
  { exact mp pl1 t }
  { exact mp pl1 s4 }
  { exact mp pl1 s5 }
  {
    rename_i h₀;
    exact mp pl1 (nec h₀)
  }

-- Structural rules
theorem sub_weak { Γ Δ : ctx } { p : form } : (Δ ⊢ₛ₅ p) → (Δ ⊆ Γ) → (Γ ⊢ₛ₅ p) := by
  intros h₀ h₁
  induction h₀
  {
    rename_i Δ p h₂;
    apply ax;
    sorry
    -- apply ax;
  }
  { exact pl1 }
  { exact pl2 }
  { exact pl3 }
  { sorry }
  { exact k }
  { exact t }
  { exact s4 }
  { exact s5 }
  {
    rename_i h₂ h₃;
    exact nec h₂;
  }

theorem weak { Γ : ctx } { p q : form } :(Γ ⊢ₛ₅ p) → ((Γ ∪ q) ⊢ₛ₅ p) := by
  intros h
  induction h
  { sorry }
  { exact pl1 }
  { exact pl2 }
  { exact pl3 }
  { sorry }
  { exact k }
  { exact t }
  { exact s4 }
  { exact s5 }
  {
    rename_i h₀ h₁;
    exact nec h₀;
  }

theorem contr { Γ : ctx } { p q : form } : (((Γ ∪ p) ∪ p) ⊢ₛ₅ q) → ((Γ ∪ p) ⊢ₛ₅ q) := by
  intros h
  cases h
  {
    apply ax;
    rename_i h₀;
    sorry
  }
  { exact pl1 }
  { exact pl2 }
  { exact pl3 }
  { sorry }
  { exact k }
  { exact t }
  { exact s4 }
  { exact s5 }
  {
    rename_i h₀;
    exact nec h₀;
  }

theorem exg { Γ : ctx } { p q r : form } : (((Γ ∪ p) ∪ q) ⊢ₛ₅ r) → (((Γ ∪ q) ∪ p) ⊢ₛ₅ r) := sorry

theorem subctx_ax { Γ Δ : ctx } { p : form } : (Δ ⊆ Γ) → (Δ ⊢ₛ₅ p) → (Γ ⊢ₛ₅ p) := by
  intros s h
  induction h
  { sorry }
  { exact pl1 }
  { exact pl2 }
  { exact pl3 }
  { sorry }
  { exact k }
  { exact t }
  { exact s4 }
  { exact s5 }
  {
    rename_i h₀ h₁;
    exact nec h₀;
  }

-- Right-hand side basic rules of inference
theorem pr { Γ : ctx } { p : form } : (Γ ∪ p) ⊢ₛ₅ p :=
  ax $ by constructor

theorem pr1 { Γ : ctx } { p q : form } : ((Γ ∪ p) ∪ q) ⊢ₛ₅ p :=
  ax $ by (repeat constructor)

theorem pr2 { Γ : ctx } { p q : form } : ((Γ ∪ p) ∪ q) ⊢ₛ₅ q :=
  ax $ by constructor

theorem by_mp1 { Γ : ctx } { p q : form } : ((Γ ∪ p) ∪ p → q) ⊢ₛ₅ q := mp pr2 pr1
theorem by_mp2 { Γ : ctx } { p q : form } : ((Γ ∪ p → q) ∪ p) ⊢ₛ₅ q := mp pr1 pr2

theorem cut { Γ : ctx } { p q r : form } : (Γ ⊢ₛ₅ p → q) → (Γ ⊢ₛ₅ q → r) → (Γ ⊢ₛ₅ p → r) := by
  intro hpq hqr
  apply mp (mp pl2 (mp pl1 hqr)) hpq

theorem conv_deduction { Γ : ctx } { p q : form } : (Γ ⊢ₛ₅ p → q) → ((Γ ∪ p) ⊢ₛ₅ q) := by
  intro hpq
  apply mp (weak hpq) pr

-- Left-hand side basic rules of inference
theorem mp_in_ctx_left { Γ : ctx } { p q r : form } : (((Γ ∪ p) ∪ q) ⊢ₛ₅ r) → (((Γ ∪ p) ∪ p → q) ⊢ₛ₅ r) := sorry
theorem mp_in_ctx_right { Γ : ctx } { p q r : form } : (((Γ ∪ p) ∪ p → q) ⊢ₛ₅ r) → (((Γ ∪ p) ∪ q) ⊢ₛ₅ r) := sorry

-- Basic lemmas
theorem contrap { Γ : ctx } { p q : form } : Γ ⊢ₛ₅ ((¬q) → (¬p)) → (p → q) :=
  deduction (deduction (mp (mp pl3 pr1) (mp pl1 pr2) ))

theorem not_impl { Γ : ctx } { p q : form } : Γ ⊢ₛ₅ (p → q) → ((¬q) → (¬p)) := sorry

theorem dne { Γ : ctx } { p : form } : Γ ⊢ₛ₅ (¬¬p) → p :=
  have h : Γ ⊢ₛ₅ (¬¬p) → ((¬p) → (¬p)) := mp pl1 idd
  mp (mp pl2 (cut pl1 pl3)) h

theorem dni { Γ : ctx } { p : form } : Γ ⊢ₛ₅ p → (¬¬p) := mp contrap dne

theorem lem { Γ : ctx } { p : form } : Γ ⊢ₛ₅ (p ∨ (¬p)) := mp dni dni

theorem not_impl_to_and { Γ : ctx } { p q : form } : Γ ⊢ₛ₅ (¬(p → q)) → (p ∨ (¬q)) := by
  sorry

theorem and_not_to_not_impl { Γ : ctx } { p q : form } : Γ ⊢ₛ₅ (p ∧ (¬q)) → (¬(p → q)) := by
  sorry
  -- repeat (apply deduction)
  -- apply mp
  -- {
  --   apply pr1
  -- }
  -- { apply cut;
  --   { apply pr2 }
  --   { apply dni }
  -- }

theorem box_contrap { p q : form } : ⊢ₛ₅ (□(p → q)) → (□((¬q) → (¬p))) :=
  mp k (prf.nec not_impl)

theorem diamond_k { p q : form } : ⊢ₛ₅ (□(p → q)) → ((⋄p) → (⋄q)) :=
  deduction $ mp not_impl (mp k (mp (weak box_contrap) pr))

theorem box_dne { p : form } : ⊢ₛ₅ (□(¬¬p)) → (□p) := mp k (nec dne)
theorem box_dni { p : form } : ⊢ₛ₅ (□p) → (□(¬¬p)) := mp k (nec dni)

theorem not_box_dni { p : form } : ⊢ₛ₅ (¬□p) → (¬□(¬¬p)) := mp not_impl box_dne
theorem not_box_dne { p : form } : ⊢ₛ₅ (¬□(¬¬p)) → (¬□p) := mp not_impl box_dni

theorem diamond_dne { p : form } : ⊢ₛ₅ (⋄(¬¬p)) → (⋄p) := not_box_dne
theorem diamond_dni { p : form } : ⊢ₛ₅ (⋄p) → (⋄(¬¬p)) := not_box_dni

theorem contrap_b { p : form } : ⊢ₛ₅ (⋄(□p)) → p := sorry

-- Notable introduction rules
theorem negintro { Γ : ctx } { p q : form } : (Γ ⊢ₛ₅ p → q) → (Γ ⊢ₛ₅ p → (¬q)) → (Γ ⊢ₛ₅ (¬p)) := sorry
-- theorem ex_falso { Γ : ctx } { p : form } : (Γ ⊢ₛ₅ ⊥) → (Γ ⊢ₛ₅ p) := sorry
theorem ex_falso_and { Γ : ctx } { p q : form } : Γ ⊢ₛ₅ (¬p) → (p → q) := sorry
theorem ex_falso_pos { Γ : ctx } { p q : form } : Γ ⊢ₛ₅ p → ((¬p) → q) := sorry
theorem contr_conseq { Γ : ctx } { p r : form } : Γ ⊢ₛ₅ (p → r) → (((¬p) → r) → r) := sorry
theorem impl_weak { Γ : ctx } { p q r : form } (h : ((Γ ∪ r) ⊢ₛ₅ p) → (Γ ⊢ₛ₅ p)) :
  ((Γ ⊢ₛ₅ p) → (Γ ⊢ₛ₅ q)) → (((Γ ∪ r) ⊢ₛ₅ p) → ((Γ ∪ r) ⊢ₛ₅ q)) := sorry

theorem and_intro { Γ : ctx } { p q : form } : (Γ ⊢ₛ₅ p) → (Γ ⊢ₛ₅ q) → (Γ ⊢ₛ₅ (p ∧ q)) := sorry
theorem and_elim_left { Γ : ctx } { p q : form } : ((Γ ∪ (p ∧ q)) ⊢ₛ₅ p) := sorry
theorem and_elim_right { Γ : ctx } { p q : form } : ((Γ ∪ (p ∧ q)) ⊢ₛ₅ q) := sorry

theorem or_intro_left { Γ : ctx } { p q r : form } : (Γ ⊢ₛ₅ p) → (Γ ⊢ₛ₅ (p ∨ q)) := sorry
theorem or_intro_right { Γ : ctx } { p q r : form } : (Γ ⊢ₛ₅ q) → (Γ ⊢ₛ₅ (p ∨ q)) := sorry
theorem or_elim { Γ : ctx } { p q r : form } : (Γ ⊢ₛ₅ (p ∨ q)) → (Γ ⊢ₛ₅ p → r) → (Γ ⊢ₛ₅ q → r) → (Γ ⊢ₛ₅ r) := sorry
theorem detach_pos { Γ : ctx } { p q : form } : ((Γ ∪ p) ⊢ₛ₅ q) → ((Γ ∪ ¬p) ⊢ₛ₅ q) → (Γ ⊢ₛ₅ q) := sorry
theorem detach_neg { Γ : ctx } { p q : form } : ((Γ ∪ ¬p) ⊢ₛ₅ q) → ((Γ ∪ p) ⊢ₛ₅ q) → (Γ ⊢ₛ₅ q) := sorry


/-
Copyright (c) 2020 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Bhavik Mehta
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.limits.limits
import Mathlib.category_theory.limits.functor_category
import Mathlib.PostPort

universes v u

namespace Mathlib

/-!
# Limit properties relating to the (co)yoneda embedding.

We calculate the colimit of `Y ↦ (X ⟶ Y)`, which is just `punit`.
(This is used in characterising cofinal functors.)

We also show the (co)yoneda embeddings preserve limits and jointly reflect them.
-/

namespace category_theory

namespace coyoneda

/--
The colimit cocone over `coyoneda.obj X`, with cocone point `punit`.
-/
@[simp] theorem colimit_cocone_ι_app {C : Type v} [small_category C] (X : Cᵒᵖ) (X_1 : C) :
    ∀ (ᾰ : functor.obj (functor.obj coyoneda X) X_1),
        nat_trans.app (limits.cocone.ι (colimit_cocone X)) X_1 ᾰ =
          id
            (fun (ᾰ : functor.obj (functor.obj coyoneda X) X_1) =>
              id (fun (X : Cᵒᵖ) (X_1 : C) (ᾰ : opposite.unop X ⟶ X_1) => PUnit.unit) X X_1 ᾰ)
            ᾰ :=
  fun (ᾰ : functor.obj (functor.obj coyoneda X) X_1) =>
    Eq.refl (nat_trans.app (limits.cocone.ι (colimit_cocone X)) X_1 ᾰ)

/--
The proposed colimit cocone over `coyoneda.obj X` is a colimit cocone.
-/
def colimit_cocone_is_colimit {C : Type v} [small_category C] (X : Cᵒᵖ) :
    limits.is_colimit (colimit_cocone X) :=
  limits.is_colimit.mk
    fun (s : limits.cocone (functor.obj coyoneda X)) (x : limits.cocone.X (colimit_cocone X)) =>
      nat_trans.app (limits.cocone.ι s) (opposite.unop X) 𝟙

protected instance obj.category_theory.limits.has_colimit {C : Type v} [small_category C]
    (X : Cᵒᵖ) : limits.has_colimit (functor.obj coyoneda X) :=
  limits.has_colimit.mk (limits.colimit_cocone.mk (colimit_cocone X) (colimit_cocone_is_colimit X))

/--
The colimit of `coyoneda.obj X` is isomorphic to `punit`.
-/
def colimit_coyoneda_iso {C : Type v} [small_category C] (X : Cᵒᵖ) :
    limits.colimit (functor.obj coyoneda X) ≅ PUnit :=
  limits.colimit.iso_colimit_cocone
    (limits.colimit_cocone.mk (colimit_cocone X) (colimit_cocone_is_colimit X))

end coyoneda

/-- The yoneda embedding `yoneda.obj X : Cᵒᵖ ⥤ Type v` for `X : C` preserves limits. -/
protected instance yoneda_preserves_limits {C : Type u} [category C] (X : C) :
    limits.preserves_limits (functor.obj yoneda X) :=
  limits.preserves_limits.mk
    fun (J : Type v) (𝒥 : small_category J) =>
      limits.preserves_limits_of_shape.mk
        fun (K : J ⥤ (Cᵒᵖ)) =>
          limits.preserves_limit.mk
            fun (c : limits.cone K) (t : limits.is_limit c) =>
              limits.is_limit.mk
                fun (s : limits.cone (K ⋙ functor.obj yoneda X)) (x : limits.cone.X s) =>
                  has_hom.hom.unop
                    (limits.is_limit.lift t
                      (limits.cone.mk (opposite.op X)
                        (nat_trans.mk
                          fun (j : J) => has_hom.hom.op (nat_trans.app (limits.cone.π s) j x))))

/-- The coyoneda embedding `coyoneda.obj X : C ⥤ Type v` for `X : Cᵒᵖ` preserves limits. -/
protected instance coyoneda_preserves_limits {C : Type u} [category C] (X : Cᵒᵖ) :
    limits.preserves_limits (functor.obj coyoneda X) :=
  limits.preserves_limits.mk
    fun (J : Type v) (𝒥 : small_category J) =>
      limits.preserves_limits_of_shape.mk
        fun (K : J ⥤ C) =>
          limits.preserves_limit.mk
            fun (c : limits.cone K) (t : limits.is_limit c) =>
              limits.is_limit.mk
                fun (s : limits.cone (K ⋙ functor.obj coyoneda X)) (x : limits.cone.X s) =>
                  limits.is_limit.lift t
                    (limits.cone.mk (opposite.unop X)
                      (nat_trans.mk fun (j : J) => nat_trans.app (limits.cone.π s) j x))

/-- The yoneda embeddings jointly reflect limits. -/
def yoneda_jointly_reflects_limits {C : Type u} [category C] (J : Type v) [small_category J]
    (K : J ⥤ (Cᵒᵖ)) (c : limits.cone K)
    (t : (X : C) → limits.is_limit (functor.map_cone (functor.obj yoneda X) c)) :
    limits.is_limit c :=
  let s' :
    (s : limits.cone K) → limits.cone (K ⋙ functor.obj yoneda (opposite.unop (limits.cone.X s))) :=
    fun (s : limits.cone K) =>
      limits.cone.mk PUnit
        (nat_trans.mk
          fun (j : J) (_x : functor.obj (functor.obj (functor.const J) PUnit) j) =>
            has_hom.hom.unop (nat_trans.app (limits.cone.π s) j));
  limits.is_limit.mk
    fun (s : limits.cone K) =>
      has_hom.hom.op (limits.is_limit.lift (t (opposite.unop (limits.cone.X s))) (s' s) PUnit.unit)

/-- The coyoneda embeddings jointly reflect limits. -/
def coyoneda_jointly_reflects_limits {C : Type u} [category C] (J : Type v) [small_category J]
    (K : J ⥤ C) (c : limits.cone K)
    (t : (X : Cᵒᵖ) → limits.is_limit (functor.map_cone (functor.obj coyoneda X) c)) :
    limits.is_limit c :=
  let s' :
    (s : limits.cone K) → limits.cone (K ⋙ functor.obj coyoneda (opposite.op (limits.cone.X s))) :=
    fun (s : limits.cone K) =>
      limits.cone.mk PUnit
        (nat_trans.mk
          fun (j : J) (_x : functor.obj (functor.obj (functor.const J) PUnit) j) =>
            nat_trans.app (limits.cone.π s) j);
  limits.is_limit.mk
    fun (s : limits.cone K) =>
      limits.is_limit.lift (t (opposite.op (limits.cone.X s))) (s' s) PUnit.unit

end Mathlib


import tactic

open tactic expr

------------------------------------------------------------------------
-- § Definición de la táctica assumption                              --
------------------------------------------------------------------------

-- 1ª versión
-- ==========

meta def test (hyp tgt : expr) : tactic bool :=
do hyp_tp ← infer_type hyp,
   return (hyp_tp = tgt)

meta def map_over_lc (tgt : expr) : list expr → tactic unit
| []     := fail "nada del contexto coincide con el objetivo"
| (h::t) := do is_match ← test h tgt,
               if is_match then exact h
                           else map_over_lc t

meta def assump : tactic unit :=
do tgt ← target,
   ctx ← local_context,
   map_over_lc tgt ctx

example
  (A B C : Prop)
  (hA : A)
  (hB : B)
  (hC : C)
  : C :=
by assump

example
  (A B C : Prop)
  (hA : A)
  (hB : B)
  : C :=
by assump

-- Comentario: Al colocar el cursor sobre assump se obtiene
--    nada del contexto coincide con el objetivo
--    state:
--    A B C : Prop,
--    hA : A,
--    hB : B
--    ⊢ C

-- 2ª versión
-- ==========

meta def test2 (hyp tgt : expr) : tactic unit :=
do hyp_tp ← infer_type hyp,
   guard (hyp_tp = tgt)

meta def map_over_lc2 (tgt : expr) : list expr → tactic unit
| []     := fail "nada del contexto coincide con el objetivo"
| (h::t) := (do test2 h tgt, exact h) <|> map_over_lc2 t

meta def assump2 : tactic unit :=
do tgt ← target,
   ctx ← local_context,
   map_over_lc2 tgt ctx

example
  (A B C : Prop)
  (hA : A)
  (hB : B)
  (hC : C)
  : C :=
by assump2

example
  (A B C : Prop)
  (hA : A)
  (hB : B)
  : C :=
by assump2

-- Comentario: Al colocar el cursor sobre assump se obtiene
--    nada del contexto coincide con el objetivo
--    state:
--    A B C : Prop,
--    hA : A,
--    hB : B
--    ⊢ C

-- 3ª versión
-- ==========

meta def test_and_exact (hyp tgt : expr) : tactic unit :=
do hyp_tp ← infer_type hyp,
   guard (hyp_tp = tgt),
   exact hyp

meta def map_over_lc3 (tgt : expr) : list expr → tactic unit
| []     := fail "nada del contexto coincide con el objetivo"
| (h::t) := test_and_exact h tgt <|> map_over_lc3 t

meta def assump3 : tactic unit :=
do tgt ← target,
   ctx ← local_context,
   map_over_lc3 tgt ctx

example
  (A B C : Prop)
  (hA : A)
  (hB : B)
  (hC : C)
  : C :=
by assump3

example
  (A B C : Prop)
  (hA : A)
  (hB : B)
  : C :=
by assump3

-- Comentario: Al colocar el cursor sobre assump se obtiene
--    nada del contexto coincide con el objetivo
--    state:
--    A B C : Prop,
--    hA : A,
--    hB : B
--    ⊢ C

-- 4ª versión
-- ==========

meta def assump4 : tactic unit :=
do tgt ← target,
   ctx ← local_context,
   ctx.mfirst (λ e, test_and_exact e tgt)

example
  (A B C : Prop)
  (hA : A)
  (hB : B)
  (hC : C)
  : C :=
by assump4

example
  (A B C : Prop)
  (hA : A)
  (hB : B)
  : C :=
by assump4

-- Comentario: Al colocar el cursor sobre assump se obtiene
--    failed
--    state:
--    A B C : Prop,
--    hA : A,
--    hB : B
--    ⊢ C

example (n : ℕ) (hx : n + 0 = 5) : n = 5 :=
by assump4

-- Comentario: Al colocar el cursor sobre assump se obtiene
--    failed
--    state:
--    n : ℕ,
--    hx : n + 0 = 5
--    ⊢ n = 5

-- 5ª versión
-- ==========

meta def test_and_exact_def (hyp tgt : expr) : tactic unit :=
do hyp_tp ← infer_type hyp,
   is_def_eq hyp_tp tgt,
   exact hyp

meta def assump5 : tactic unit :=
do tgt ← target,
   ctx ← local_context,
   ctx.mfirst (λ e, test_and_exact_def e tgt)

example (n : ℕ) (hx : n + 0 = 5) : n = 5 :=
by assump5

-- Comentario: Al colocar el cursor sobre assump se obtiene éxito

-- 6ª versión
-- ==========

meta def assump6 : tactic unit :=
do tgt ← target,
   ctx ← local_context,
   ctx.mfirst (λ e, exact e)

example (n : ℕ) (hx : n + 0 = 6) : n = 6 :=
by assump6

-- Comentario: Al colocar el cursor sobre assump se obtiene éxito

-- 7ª versión
-- ==========

meta def assump7 : tactic unit :=
local_context >>= list.mfirst exact

example (n : ℕ) (hx : n + 0 = 7) : n = 7 :=
by assump7

-- Comentario: Al colocar el cursor sobre assump se obtiene éxito

----------------------------------------------------------------------
-- §§ Definición de la táctica add_refl                             --
----------------------------------------------------------------------

-- 1ª versión
-- ==========

meta def add_single_refl (e : expr) : tactic unit :=
do tp ← infer_type e,
   guard (tp = `(ℕ)),
   pf ← mk_app `eq.refl [e],
   nm ← get_unused_name,
   note nm none pf,
   skip

meta def add_refl : tactic unit :=
do ctx ← local_context,
   ctx.mmap' (λ e, add_single_refl e)

example (a b c : ℕ) (ha : a = b) : true :=
by do add_refl

-- Comentario: Al colocar el cursor sobre do se obtiene
--    failed
--    state:
--    a b c : ℕ,
--    ha : a = b,
--    _x : a = a,
--    _x_1 : b = b,
--    _x_2 : c = c
--    ⊢ true

-- 2ª versión
-- ==========

meta def add_refl2 : tactic unit :=
do ctx ← local_context,
   ctx.mmap' (λ e, try (add_single_refl e))

example (a b c : ℕ) (ha : a = b) : true :=
by do add_refl2

-- Comentario: Al colocar el cursor sobre do se obtiene
--    tactic failed, there are unsolved goals
--    state:
--    a b c : ℕ,
--    ha : a = b,
--    _x : a = a,
--    _x_1 : b = b,
--    _x_2 : c = c
--    ⊢ true

-- 3ª versión
-- ==========

meta def add_single_refl3 (e : expr) : tactic unit :=
do tp ← infer_type e,
   guard (tp = `(ℕ)),
   pf ← mk_app `eq.refl [e],
   nm ← get_unused_name e.local_pp_name,
   note nm none pf,
   skip

meta def add_refl3 : tactic unit :=
do ctx ← local_context,
   ctx.mmap' (λ e, try (add_single_refl3 e))

example (a b c : ℕ) (ha : a = b) : true :=
by do add_refl3

-- Comentario: Al colocar el cursor sobre do se obtiene
--    tactic failed, there are unsolved goals
--    state:
--    a b c : ℕ,
--    ha : a = b,
--    a_1 : a = a,
--    b_1 : b = b,
--    c_1 : c = c
--    ⊢ true

-- 4ª versión
-- ==========

meta def add_single_refl4 (e : expr) : tactic unit :=
do tp ← infer_type e,
   guard (tp = `(ℕ)),
   pf ← to_expr ``(not_lt_of_ge (nat.zero_le %%e)),
   nm ← get_unused_name e.local_pp_name,
   note nm none pf,
   skip

meta def add_refl4 : tactic unit :=
do ctx ← local_context,
   ctx.mmap' (λ e, try (add_single_refl4 e))

example (a b c : ℕ) (ha : a = b) : true :=
by do add_refl4

-- Comentario: Al colocar el cursor sobre do se obtiene
--    tactic failed, there are unsolved goals
--    state:
--    a b c : ℕ,
--    ha : a = b,
--    a_1 : ¬a < 0,
--    b_1 : ¬b < 0,
--    c_1 : ¬c < 0
--    ⊢ true

------------------------------------------------------------------------
-- § Referencia                                                       --
------------------------------------------------------------------------

-- Basado en el vídeo "Metaprogramming in Lean tutorial: video 5" de Rob
-- Lewis que se encuentra en https://youtu.be/-RQQxFVZnn4


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.analysis.convex.basic
import Mathlib.analysis.normed_space.bounded_linear_maps
import Mathlib.analysis.specific_limits
import Mathlib.PostPort

universes u_1 u_2 u_3 u_4

namespace Mathlib

/-!
# Tangent cone

In this file, we define two predicates `unique_diff_within_at 𝕜 s x` and `unique_diff_on 𝕜 s`
ensuring that, if a function has two derivatives, then they have to coincide. As a direct
definition of this fact (quantifying on all target types and all functions) would depend on
universes, we use a more intrinsic definition: if all the possible tangent directions to the set
`s` at the point `x` span a dense subset of the whole subset, it is easy to check that the
derivative has to be unique.

Therefore, we introduce the set of all tangent directions, named `tangent_cone_at`,
and express `unique_diff_within_at` and `unique_diff_on` in terms of it.
One should however think of this definition as an implementation detail: the only reason to
introduce the predicates `unique_diff_within_at` and `unique_diff_on` is to ensure the uniqueness
of the derivative. This is why their names reflect their uses, and not how they are defined.

## Implementation details

Note that this file is imported by `fderiv.lean`. Hence, derivatives are not defined yet. The
property of uniqueness of the derivative is therefore proved in `fderiv.lean`, but based on the
properties of the tangent cone we prove here.
-/

/-- The set of all tangent directions to the set `s` at the point `x`. -/
def tangent_cone_at (𝕜 : Type u_1) [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] (s : set E) (x : E) : set E :=
  set_of
    fun (y : E) =>
      ∃ (c : ℕ → 𝕜),
        ∃ (d : ℕ → E),
          filter.eventually (fun (n : ℕ) => x + d n ∈ s) filter.at_top ∧
            filter.tendsto (fun (n : ℕ) => norm (c n)) filter.at_top filter.at_top ∧
              filter.tendsto (fun (n : ℕ) => c n • d n) filter.at_top (nhds y)

/-- A property ensuring that the tangent cone to `s` at `x` spans a dense subset of the whole space.
The main role of this property is to ensure that the differential within `s` at `x` is unique,
hence this name. The uniqueness it asserts is proved in `unique_diff_within_at.eq` in `fderiv.lean`.
To avoid pathologies in dimension 0, we also require that `x` belongs to the closure of `s` (which
is automatic when `E` is not `0`-dimensional).
 -/
def unique_diff_within_at (𝕜 : Type u_1) [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] (s : set E) (x : E) :=
  dense ↑(submodule.span 𝕜 (tangent_cone_at 𝕜 s x)) ∧ x ∈ closure s

/-- A property ensuring that the tangent cone to `s` at any of its points spans a dense subset of
the whole space.  The main role of this property is to ensure that the differential along `s` is
unique, hence this name. The uniqueness it asserts is proved in `unique_diff_on.eq` in
`fderiv.lean`. -/
def unique_diff_on (𝕜 : Type u_1) [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] (s : set E) :=
  ∀ (x : E), x ∈ s → unique_diff_within_at 𝕜 s x

/- This section is devoted to the properties of the tangent cone. -/

theorem tangent_cone_univ {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} : tangent_cone_at 𝕜 set.univ x = set.univ := sorry

theorem tangent_cone_mono {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (h : s ⊆ t) : tangent_cone_at 𝕜 s x ⊆ tangent_cone_at 𝕜 t x := sorry

/-- Auxiliary lemma ensuring that, under the assumptions defining the tangent cone,
the sequence `d` tends to 0 at infinity. -/
theorem tangent_cone_at.lim_zero {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {y : E} {α : Type u_3} (l : filter α) {c : α → 𝕜} {d : α → E} (hc : filter.tendsto (fun (n : α) => norm (c n)) l filter.at_top) (hd : filter.tendsto (fun (n : α) => c n • d n) l (nhds y)) : filter.tendsto d l (nhds 0) := sorry

theorem tangent_cone_mono_nhds {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (h : nhds_within x s ≤ nhds_within x t) : tangent_cone_at 𝕜 s x ⊆ tangent_cone_at 𝕜 t x := sorry

/-- Tangent cone of `s` at `x` depends only on `𝓝[s] x`. -/
theorem tangent_cone_congr {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (h : nhds_within x s = nhds_within x t) : tangent_cone_at 𝕜 s x = tangent_cone_at 𝕜 t x :=
  set.subset.antisymm (tangent_cone_mono_nhds (le_of_eq h)) (tangent_cone_mono_nhds (le_of_eq (Eq.symm h)))

/-- Intersecting with a neighborhood of the point does not change the tangent cone. -/
theorem tangent_cone_inter_nhds {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (ht : t ∈ nhds x) : tangent_cone_at 𝕜 (s ∩ t) x = tangent_cone_at 𝕜 s x :=
  tangent_cone_congr (Eq.symm (nhds_within_restrict' s ht))

/-- The tangent cone of a product contains the tangent cone of its left factor. -/
theorem subset_tangent_cone_prod_left {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] {x : E} {s : set E} {t : set F} {y : F} (ht : y ∈ closure t) : ⇑(linear_map.inl 𝕜 E F) '' tangent_cone_at 𝕜 s x ⊆ tangent_cone_at 𝕜 (set.prod s t) (x, y) := sorry

/-- The tangent cone of a product contains the tangent cone of its right factor. -/
theorem subset_tangent_cone_prod_right {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] {x : E} {s : set E} {t : set F} {y : F} (hs : x ∈ closure s) : ⇑(linear_map.inr 𝕜 E F) '' tangent_cone_at 𝕜 t y ⊆ tangent_cone_at 𝕜 (set.prod s t) (x, y) := sorry

/-- If a subset of a real vector space contains a segment, then the direction of this
segment belongs to the tangent cone at its endpoints. -/
theorem mem_tangent_cone_of_segment_subset {G : Type u_4} [normed_group G] [normed_space ℝ G] {s : set G} {x : G} {y : G} (h : segment x y ⊆ s) : y - x ∈ tangent_cone_at ℝ s x := sorry

/-!
### Properties of `unique_diff_within_at` and `unique_diff_on`

This section is devoted to properties of the predicates `unique_diff_within_at` and `unique_diff_on`. -/

theorem unique_diff_on.unique_diff_within_at {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {s : set E} {x : E} (hs : unique_diff_on 𝕜 s) (h : x ∈ s) : unique_diff_within_at 𝕜 s x :=
  hs x h

theorem unique_diff_within_at_univ {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} : unique_diff_within_at 𝕜 set.univ x := sorry

theorem unique_diff_on_univ {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] : unique_diff_on 𝕜 set.univ :=
  fun (x : E) (hx : x ∈ set.univ) => unique_diff_within_at_univ

theorem unique_diff_on_empty {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] : unique_diff_on 𝕜 ∅ :=
  fun (x : E) (hx : x ∈ ∅) => false.elim hx

theorem unique_diff_within_at.mono_nhds {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (h : unique_diff_within_at 𝕜 s x) (st : nhds_within x s ≤ nhds_within x t) : unique_diff_within_at 𝕜 t x := sorry

theorem unique_diff_within_at.mono {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (h : unique_diff_within_at 𝕜 s x) (st : s ⊆ t) : unique_diff_within_at 𝕜 t x :=
  unique_diff_within_at.mono_nhds h (nhds_within_mono x st)

theorem unique_diff_within_at_congr {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (st : nhds_within x s = nhds_within x t) : unique_diff_within_at 𝕜 s x ↔ unique_diff_within_at 𝕜 t x :=
  { mp := fun (h : unique_diff_within_at 𝕜 s x) => unique_diff_within_at.mono_nhds h (le_of_eq st),
    mpr := fun (h : unique_diff_within_at 𝕜 t x) => unique_diff_within_at.mono_nhds h (le_of_eq (Eq.symm st)) }

theorem unique_diff_within_at_inter {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (ht : t ∈ nhds x) : unique_diff_within_at 𝕜 (s ∩ t) x ↔ unique_diff_within_at 𝕜 s x :=
  unique_diff_within_at_congr (Eq.symm (nhds_within_restrict' s ht))

theorem unique_diff_within_at.inter {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (hs : unique_diff_within_at 𝕜 s x) (ht : t ∈ nhds x) : unique_diff_within_at 𝕜 (s ∩ t) x :=
  iff.mpr (unique_diff_within_at_inter ht) hs

theorem unique_diff_within_at_inter' {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (ht : t ∈ nhds_within x s) : unique_diff_within_at 𝕜 (s ∩ t) x ↔ unique_diff_within_at 𝕜 s x :=
  unique_diff_within_at_congr (Eq.symm (nhds_within_restrict'' s ht))

theorem unique_diff_within_at.inter' {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} {t : set E} (hs : unique_diff_within_at 𝕜 s x) (ht : t ∈ nhds_within x s) : unique_diff_within_at 𝕜 (s ∩ t) x :=
  iff.mpr (unique_diff_within_at_inter' ht) hs

theorem unique_diff_within_at_of_mem_nhds {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} (h : s ∈ nhds x) : unique_diff_within_at 𝕜 s x := sorry

theorem is_open.unique_diff_within_at {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {x : E} {s : set E} (hs : is_open s) (xs : x ∈ s) : unique_diff_within_at 𝕜 s x :=
  unique_diff_within_at_of_mem_nhds (mem_nhds_sets hs xs)

theorem unique_diff_on.inter {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {s : set E} {t : set E} (hs : unique_diff_on 𝕜 s) (ht : is_open t) : unique_diff_on 𝕜 (s ∩ t) :=
  fun (x : E) (hx : x ∈ s ∩ t) => unique_diff_within_at.inter (hs x (and.left hx)) (mem_nhds_sets ht (and.right hx))

theorem is_open.unique_diff_on {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {s : set E} (hs : is_open s) : unique_diff_on 𝕜 s :=
  fun (x : E) (hx : x ∈ s) => is_open.unique_diff_within_at hs hx

/-- The product of two sets of unique differentiability at points `x` and `y` has unique
differentiability at `(x, y)`. -/
theorem unique_diff_within_at.prod {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] {x : E} {s : set E} {t : set F} {y : F} (hs : unique_diff_within_at 𝕜 s x) (ht : unique_diff_within_at 𝕜 t y) : unique_diff_within_at 𝕜 (set.prod s t) (x, y) := sorry

/-- The product of two sets of unique differentiability is a set of unique differentiability. -/
theorem unique_diff_on.prod {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] {s : set E} {t : set F} (hs : unique_diff_on 𝕜 s) (ht : unique_diff_on 𝕜 t) : unique_diff_on 𝕜 (set.prod s t) := sorry

/-- In a real vector space, a convex set with nonempty interior is a set of unique
differentiability. -/
theorem unique_diff_on_convex {G : Type u_4} [normed_group G] [normed_space ℝ G] {s : set G} (conv : convex s) (hs : set.nonempty (interior s)) : unique_diff_on ℝ s := sorry

theorem unique_diff_on_Ici (a : ℝ) : unique_diff_on ℝ (set.Ici a) := sorry

theorem unique_diff_on_Iic (a : ℝ) : unique_diff_on ℝ (set.Iic a) := sorry

theorem unique_diff_on_Ioi (a : ℝ) : unique_diff_on ℝ (set.Ioi a) :=
  is_open.unique_diff_on is_open_Ioi

theorem unique_diff_on_Iio (a : ℝ) : unique_diff_on ℝ (set.Iio a) :=
  is_open.unique_diff_on is_open_Iio

theorem unique_diff_on_Icc {a : ℝ} {b : ℝ} (hab : a < b) : unique_diff_on ℝ (set.Icc a b) := sorry

theorem unique_diff_on_Ico (a : ℝ) (b : ℝ) : unique_diff_on ℝ (set.Ico a b) := sorry

theorem unique_diff_on_Ioc (a : ℝ) (b : ℝ) : unique_diff_on ℝ (set.Ioc a b) := sorry

theorem unique_diff_on_Ioo (a : ℝ) (b : ℝ) : unique_diff_on ℝ (set.Ioo a b) :=
  is_open.unique_diff_on is_open_Ioo

/-- The real interval `[0, 1]` is a set of unique differentiability. -/
theorem unique_diff_on_Icc_zero_one : unique_diff_on ℝ (set.Icc 0 1) :=
  unique_diff_on_Icc zero_lt_one


variables {p q : Prop} (hp : p) (hq : q)

include hp hq

example : p ∧ q ∧ p :=
begin
  apply and.intro hp,
  exact and.intro hq hp
end

omit hp hq