/-
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
-/
import data.matrix.basis
import linear_algebra.basis
import linear_algebra.pi

/-!
# The standard basis

This file defines the standard basis `pi.basis (s : ∀ j, basis (ι j) R (M j))`,
which is the `Σ j, ι j`-indexed basis of Π j, M j`. The basis vectors are given by
`pi.basis s ⟨j, i⟩ j' = linear_map.std_basis R M j' (s j) i = if j = j' then s i else 0`.

The standard basis on `R^η`, i.e. `η → R` is called `pi.basis_fun`.

To give a concrete example, `linear_map.std_basis R (λ (i : fin 3), R) i 1`
gives the `i`th unit basis vector in `R³`, and `pi.basis_fun R (fin 3)` proves
this is a basis over `fin 3 → R`.

## Main definitions

 - `linear_map.std_basis R M`: if `x` is a basis vector of `M i`, then
   `linear_map.std_basis R M i x` is the `i`th standard basis vector of `Π i, M i`.
 - `pi.basis s`: given a basis `s i` for each `M i`, the standard basis on `Π i, M i`
 - `pi.basis_fun R η`: the standard basis on `R^η`, i.e. `η → R`, given by
   `pi.basis_fun R η i j = if i = j then 1 else 0`.
 - `matrix.std_basis R n m`: the standard basis on `matrix n m R`, given by
   `matrix.std_basis R n m (i, j) i' j' = if (i, j) = (i', j') then 1 else 0`.

-/

open function submodule
open_locale big_operators
open_locale big_operators

namespace linear_map

variables (R : Type*) {ι : Type*} [semiring R] (φ : ι → Type*)
  [Π i, add_comm_monoid (φ i)] [Π i, module R (φ i)] [decidable_eq ι]

/-- The standard basis of the product of `φ`. -/
def std_basis : Π (i : ι), φ i →ₗ[R] (Πi, φ i) := single

lemma std_basis_apply (i : ι) (b : φ i) : std_basis R φ i b = update 0 i b :=
rfl

lemma coe_std_basis (i : ι) : ⇑(std_basis R φ i) = pi.single i :=
funext $ std_basis_apply R φ i

@[simp] lemma std_basis_same (i : ι) (b : φ i) : std_basis R φ i b i = b :=
by rw [std_basis_apply, update_same]

lemma std_basis_ne (i j : ι) (h : j ≠ i) (b : φ i) : std_basis R φ i b j = 0 :=
by rw [std_basis_apply, update_noteq h]; refl

lemma std_basis_eq_pi_diag (i : ι) : std_basis R φ i = pi (diag i) :=
begin
  ext x j,
  convert (update_apply 0 x i j _).symm,
  refl,
end

lemma ker_std_basis (i : ι) : ker (std_basis R φ i) = ⊥ :=
ker_eq_bot_of_injective $ assume f g hfg,
  have std_basis R φ i f i = std_basis R φ i g i := hfg ▸ rfl,
  by simpa only [std_basis_same]

lemma proj_comp_std_basis (i j : ι) : (proj i).comp (std_basis R φ j) = diag j i :=
by rw [std_basis_eq_pi_diag, proj_pi]

lemma proj_std_basis_same (i : ι) : (proj i).comp (std_basis R φ i) = id :=
by ext b; simp

lemma proj_std_basis_ne (i j : ι) (h : i ≠ j) : (proj i).comp (std_basis R φ j) = 0 :=
by ext b; simp [std_basis_ne R φ _ _ h]

lemma supr_range_std_basis_le_infi_ker_proj (I J : set ι) (h : disjoint I J) :
  (⨆i∈I, range (std_basis R φ i)) ≤ (⨅i∈J, ker (proj i)) :=
begin
  refine (supr_le $ λ i, supr_le $ λ hi, range_le_iff_comap.2 _),
  simp only [(ker_comp _ _).symm, eq_top_iff, set_like.le_def, mem_ker, comap_infi, mem_infi],
  rintro b - j hj,
  rw [proj_std_basis_ne R φ j i, zero_apply],
  rintro rfl,
  exact h ⟨hi, hj⟩
end

lemma infi_ker_proj_le_supr_range_std_basis {I : finset ι} {J : set ι} (hu : set.univ ⊆ ↑I ∪ J) :
  (⨅ i∈J, ker (proj i)) ≤ (⨆i∈I, range (std_basis R φ i)) :=
set_like.le_def.2
begin
  assume b hb,
  simp only [mem_infi, mem_ker, proj_apply] at hb,
  rw ← show ∑ i in I, std_basis R φ i (b i) = b,
  { ext i,
    rw [finset.sum_apply, ← std_basis_same R φ i (b i)],
    refine finset.sum_eq_single i (assume j hjI ne, std_basis_ne _ _ _ _ ne.symm _) _,
    assume hiI,
    rw [std_basis_same],
    exact hb _ ((hu trivial).resolve_left hiI) },
  exact sum_mem _ (assume i hiI, mem_supr_of_mem i $ mem_supr_of_mem hiI $
    (std_basis R φ i).mem_range_self (b i))
end

lemma supr_range_std_basis_eq_infi_ker_proj {I J : set ι}
  (hd : disjoint I J) (hu : set.univ ⊆ I ∪ J) (hI : set.finite I) :
  (⨆i∈I, range (std_basis R φ i)) = (⨅i∈J, ker (proj i)) :=
begin
  refine le_antisymm (supr_range_std_basis_le_infi_ker_proj _ _ _ _ hd) _,
  have : set.univ ⊆ ↑hI.to_finset ∪ J, { rwa [hI.coe_to_finset] },
  refine le_trans (infi_ker_proj_le_supr_range_std_basis R φ this) (supr_le_supr $ assume i, _),
  rw [set.finite.mem_to_finset],
  exact le_rfl
end

lemma supr_range_std_basis [fintype ι] : (⨆i:ι, range (std_basis R φ i)) = ⊤ :=
have (set.univ : set ι) ⊆ ↑(finset.univ : finset ι) ∪ ∅ := by rw [finset.coe_univ, set.union_empty],
begin
  apply top_unique,
  convert (infi_ker_proj_le_supr_range_std_basis R φ this),
  exact infi_emptyset.symm,
  exact (funext $ λi, (@supr_pos _ _ _ (λh, range (std_basis R φ i)) $ finset.mem_univ i).symm)
end

lemma disjoint_std_basis_std_basis (I J : set ι) (h : disjoint I J) :
  disjoint (⨆i∈I, range (std_basis R φ i)) (⨆i∈J, range (std_basis R φ i)) :=
begin
  refine disjoint.mono
    (supr_range_std_basis_le_infi_ker_proj _ _ _ _ $ disjoint_compl_right)
    (supr_range_std_basis_le_infi_ker_proj _ _ _ _ $ disjoint_compl_right) _,
  simp only [disjoint, set_like.le_def, mem_infi, mem_inf, mem_ker, mem_bot, proj_apply,
    funext_iff],
  rintros b ⟨hI, hJ⟩ i,
  classical,
  by_cases hiI : i ∈ I,
  { by_cases hiJ : i ∈ J,
    { exact (h ⟨hiI, hiJ⟩).elim },
    { exact hJ i hiJ } },
  { exact hI i hiI }
end

lemma std_basis_eq_single {a : R} :
  (λ (i : ι), (std_basis R (λ _ : ι, R) i) a) = λ (i : ι), (finsupp.single i a) :=
begin
  ext i j,
  rw [std_basis_apply, finsupp.single_apply],
  split_ifs,
  { rw [h, function.update_same] },
  { rw [function.update_noteq (ne.symm h)], refl },
end

end linear_map

namespace pi
open linear_map
open set

variables {R : Type*}

section module
variables {η : Type*} {ιs : η → Type*} {Ms : η → Type*}

lemma linear_independent_std_basis [ring R] [∀i, add_comm_group (Ms i)] [∀i, module R (Ms i)]
  [decidable_eq η] (v : Πj, ιs j → (Ms j)) (hs : ∀i, linear_independent R (v i)) :
  linear_independent R (λ (ji : Σ j, ιs j), std_basis R Ms ji.1 (v ji.1 ji.2)) :=
begin
  have hs' : ∀j : η, linear_independent R (λ i : ιs j, std_basis R Ms j (v j i)),
  { intro j,
    exact (hs j).map' _ (ker_std_basis _ _ _) },
  apply linear_independent_Union_finite hs',
  { assume j J _ hiJ,
    simp [(set.Union.equations._eqn_1 _).symm, submodule.span_image, submodule.span_Union],
    have h₀ : ∀ j, span R (range (λ (i : ιs j), std_basis R Ms j (v j i)))
        ≤ range (std_basis R Ms j),
    { intro j,
      rw [span_le, linear_map.range_coe],
      apply range_comp_subset_range },
    have h₁ : span R (range (λ (i : ιs j), std_basis R Ms j (v j i)))
        ≤ ⨆ i ∈ {j}, range (std_basis R Ms i),
    { rw @supr_singleton _ _ _ (λ i, linear_map.range (std_basis R (λ (j : η), Ms j) i)),
      apply h₀ },
    have h₂ : (⨆ j ∈ J, span R (range (λ (i : ιs j), std_basis R Ms j (v j i)))) ≤
               ⨆ j ∈ J, range (std_basis R (λ (j : η), Ms j) j) :=
      supr_le_supr (λ i, supr_le_supr (λ H, h₀ i)),
    have h₃ : disjoint (λ (i : η), i ∈ {j}) J,
    { convert set.disjoint_singleton_left.2 hiJ using 0 },
    exact (disjoint_std_basis_std_basis _ _ _ _ h₃).mono h₁ h₂ }
end

variables [semiring R] [∀i, add_comm_monoid (Ms i)] [∀i, module R (Ms i)]

variable [fintype η]

section

open linear_equiv

/-- `pi.basis (s : ∀ j, basis (ιs j) R (Ms j))` is the `Σ j, ιs j`-indexed basis on `Π j, Ms j`
given by `s j` on each component. -/
protected noncomputable def basis (s : ∀ j, basis (ιs j) R (Ms j)) :
  basis (Σ j, ιs j) R (Π j, Ms j) :=
-- The `add_comm_monoid (Π j, Ms j)` instance was hard to find.
-- Defining this in tactic mode seems to shake up instance search enough that it works by itself.
by { refine basis.of_repr (_ ≪≫ₗ (finsupp.sigma_finsupp_lequiv_pi_finsupp R).symm),
     exact linear_equiv.Pi_congr_right (λ j, (s j).repr) }

@[simp] lemma basis_repr_std_basis [decidable_eq η] (s : ∀ j, basis (ιs j) R (Ms j)) (j i) :
  (pi.basis s).repr (std_basis R _ j (s j i)) = finsupp.single ⟨j, i⟩ 1 :=
begin
  ext ⟨j', i'⟩,
  by_cases hj : j = j',
  { subst hj,
    simp only [pi.basis, linear_equiv.trans_apply, basis.repr_self, std_basis_same,
        linear_equiv.Pi_congr_right_apply, finsupp.sigma_finsupp_lequiv_pi_finsupp_symm_apply],
    symmetry,
    exact basis.finsupp.single_apply_left
      (λ i i' (h : (⟨j, i⟩ : Σ j, ιs j) = ⟨j, i'⟩), eq_of_heq (sigma.mk.inj h).2) _ _ _ },
  simp only [pi.basis, linear_equiv.trans_apply, finsupp.sigma_finsupp_lequiv_pi_finsupp_symm_apply,
      linear_equiv.Pi_congr_right_apply],
  dsimp,
  rw [std_basis_ne _ _ _ _ (ne.symm hj), linear_equiv.map_zero, finsupp.zero_apply,
      finsupp.single_eq_of_ne],
  rintros ⟨⟩,
  contradiction
end

@[simp] lemma basis_apply [decidable_eq η] (s : ∀ j, basis (ιs j) R (Ms j)) (ji) :
  pi.basis s ji = std_basis R _ ji.1 (s ji.1 ji.2) :=
basis.apply_eq_iff.mpr (by simp)

@[simp] lemma basis_repr (s : ∀ j, basis (ιs j) R (Ms j)) (x) (ji) :
  (pi.basis s).repr x ji = (s ji.1).repr (x ji.1) ji.2 :=
rfl

end

section
variables (R η)

/-- The basis on `η → R` where the `i`th basis vector is `function.update 0 i 1`. -/
noncomputable def basis_fun : basis η R (Π (j : η), R) :=
basis.of_equiv_fun (linear_equiv.refl _ _)

@[simp] lemma basis_fun_apply [decidable_eq η] (i) :
  basis_fun R η i = std_basis R (λ (i : η), R) i 1 :=
by { simp only [basis_fun, basis.coe_of_equiv_fun, linear_equiv.refl_symm,
                linear_equiv.refl_apply, std_basis_apply],
     congr /- Get rid of a `decidable_eq` mismatch. -/ }

@[simp] lemma basis_fun_repr (x : η → R) (i : η) :
  (pi.basis_fun R η).repr x i = x i :=
by simp [basis_fun]

end

end module

end pi

namespace matrix

variables (R : Type*) (n : Type*) (m : Type*) [fintype m] [fintype n] [semiring R]

/-- The standard basis of `matrix n m R`. -/
noncomputable def std_basis : basis (n × m) R (matrix n m R) :=
basis.reindex (pi.basis (λ (i : n), pi.basis_fun R m)) (equiv.sigma_equiv_prod _ _)

variables {n m}

lemma std_basis_eq_std_basis_matrix (i : n) (j : m) [decidable_eq n] [decidable_eq m] :
  std_basis R n m (i, j) = std_basis_matrix i j (1 : R) :=
begin
  ext a b,
  by_cases hi : i = a; by_cases hj : j = b,
  { simp [std_basis, hi, hj] },
  { simp [std_basis, hi, hj, ne.symm hj, linear_map.std_basis_ne] },
  { simp [std_basis, hi, hj, ne.symm hi, linear_map.std_basis_ne] },
  { simp [std_basis, hi, hj, ne.symm hj, ne.symm hi, linear_map.std_basis_ne] }
end

end matrix


/-
Copyright (c) 2021 Oliver Nash. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Oliver Nash, Eric Wieser

! This file was ported from Lean 3 source module topology.instances.matrix
! leanprover-community/mathlib commit 9a59dcb7a2d06bf55da57b9030169219980660cd
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Topology.Algebra.InfiniteSum.Basic
import Mathbin.Topology.Algebra.Ring.Basic
import Mathbin.Topology.Algebra.Star
import Mathbin.LinearAlgebra.Matrix.NonsingularInverse
import Mathbin.LinearAlgebra.Matrix.Trace

/-!
# Topological properties of matrices

This file is a place to collect topological results about matrices.

## Main definitions:

* `matrix.topological_ring`: square matrices form a topological ring

## Main results

* Continuity:
  * `continuous.matrix_det`: the determinant is continuous over a topological ring.
  * `continuous.matrix_adjugate`: the adjugate is continuous over a topological ring.
* Infinite sums
  * `matrix.transpose_tsum`: transpose commutes with infinite sums
  * `matrix.diagonal_tsum`: diagonal commutes with infinite sums
  * `matrix.block_diagonal_tsum`: block diagonal commutes with infinite sums
  * `matrix.block_diagonal'_tsum`: non-uniform block diagonal commutes with infinite sums
-/

open Matrix

open Matrix

variable {X α l m n p S R : Type _} {m' n' : l → Type _}

instance [TopologicalSpace R] : TopologicalSpace (Matrix m n R) :=
  Pi.topologicalSpace

instance [TopologicalSpace R] [T2Space R] : T2Space (Matrix m n R) :=
  Pi.t2Space

/-! ### Lemmas about continuity of operations -/

section Continuity

variable [TopologicalSpace X] [TopologicalSpace R]

instance [SMul α R] [ContinuousConstSMul α R] : ContinuousConstSMul α (Matrix m n R) :=
  Pi.continuousConstSMul

instance [TopologicalSpace α] [SMul α R] [ContinuousSMul α R] : ContinuousSMul α (Matrix m n R) :=
  Pi.continuousSMul

instance [Add R] [ContinuousAdd R] : ContinuousAdd (Matrix m n R) :=
  Pi.continuousAdd

instance [Neg R] [ContinuousNeg R] : ContinuousNeg (Matrix m n R) :=
  Pi.continuousNeg

instance [AddGroup R] [TopologicalAddGroup R] : TopologicalAddGroup (Matrix m n R) :=
  Pi.topologicalAddGroup

/-- To show a function into matrices is continuous it suffices to show the coefficients of the
resulting matrix are continuous -/
@[continuity]
theorem continuous_matrix [TopologicalSpace α] {f : α → Matrix m n R}
    (h : ∀ i j, Continuous fun a => f a i j) : Continuous f :=
  continuous_pi fun _ => continuous_pi fun j => h _ _
#align continuous_matrix continuous_matrix

theorem Continuous.matrix_elem {A : X → Matrix m n R} (hA : Continuous A) (i : m) (j : n) :
    Continuous fun x => A x i j :=
  (continuous_apply_apply i j).comp hA
#align continuous.matrix_elem Continuous.matrix_elem

@[continuity]
theorem Continuous.matrix_map [TopologicalSpace S] {A : X → Matrix m n S} {f : S → R}
    (hA : Continuous A) (hf : Continuous f) : Continuous fun x => (A x).map f :=
  continuous_matrix fun i j => hf.comp <| hA.matrix_elem _ _
#align continuous.matrix_map Continuous.matrix_map

@[continuity]
theorem Continuous.matrix_transpose {A : X → Matrix m n R} (hA : Continuous A) :
    Continuous fun x => (A x)ᵀ :=
  continuous_matrix fun i j => hA.matrix_elem j i
#align continuous.matrix_transpose Continuous.matrix_transpose

theorem Continuous.matrix_conjTranspose [Star R] [ContinuousStar R] {A : X → Matrix m n R}
    (hA : Continuous A) : Continuous fun x => (A x)ᴴ :=
  hA.matrix_transpose.matrix_map continuous_star
#align continuous.matrix_conj_transpose Continuous.matrix_conjTranspose

instance [Star R] [ContinuousStar R] : ContinuousStar (Matrix m m R) :=
  ⟨continuous_id.matrix_conjTranspose⟩

@[continuity]
theorem Continuous.matrix_col {A : X → n → R} (hA : Continuous A) : Continuous fun x => col (A x) :=
  continuous_matrix fun i j => (continuous_apply _).comp hA
#align continuous.matrix_col Continuous.matrix_col

@[continuity]
theorem Continuous.matrix_row {A : X → n → R} (hA : Continuous A) : Continuous fun x => row (A x) :=
  continuous_matrix fun i j => (continuous_apply _).comp hA
#align continuous.matrix_row Continuous.matrix_row

@[continuity]
theorem Continuous.matrix_diagonal [Zero R] [DecidableEq n] {A : X → n → R} (hA : Continuous A) :
    Continuous fun x => diagonal (A x) :=
  continuous_matrix fun i j => ((continuous_apply i).comp hA).if_const _ continuous_zero
#align continuous.matrix_diagonal Continuous.matrix_diagonal

@[continuity]
theorem Continuous.matrix_dotProduct [Fintype n] [Mul R] [AddCommMonoid R] [ContinuousAdd R]
    [ContinuousMul R] {A : X → n → R} {B : X → n → R} (hA : Continuous A) (hB : Continuous B) :
    Continuous fun x => dotProduct (A x) (B x) :=
  continuous_finset_sum _ fun i _ =>
    ((continuous_apply i).comp hA).mul ((continuous_apply i).comp hB)
#align continuous.matrix_dot_product Continuous.matrix_dotProduct

/-- For square matrices the usual `continuous_mul` can be used. -/
@[continuity]
theorem Continuous.matrix_mul [Fintype n] [Mul R] [AddCommMonoid R] [ContinuousAdd R]
    [ContinuousMul R] {A : X → Matrix m n R} {B : X → Matrix n p R} (hA : Continuous A)
    (hB : Continuous B) : Continuous fun x => (A x).mul (B x) :=
  continuous_matrix fun i j =>
    continuous_finset_sum _ fun k _ => (hA.matrix_elem _ _).mul (hB.matrix_elem _ _)
#align continuous.matrix_mul Continuous.matrix_mul

instance [Fintype n] [Mul R] [AddCommMonoid R] [ContinuousAdd R] [ContinuousMul R] :
    ContinuousMul (Matrix n n R) :=
  ⟨continuous_fst.matrix_mul continuous_snd⟩

instance [Fintype n] [NonUnitalNonAssocSemiring R] [TopologicalSemiring R] :
    TopologicalSemiring (Matrix n n R) where

instance [Fintype n] [NonUnitalNonAssocRing R] [TopologicalRing R] : TopologicalRing (Matrix n n R)
    where

@[continuity]
theorem Continuous.matrix_vecMulVec [Mul R] [ContinuousMul R] {A : X → m → R} {B : X → n → R}
    (hA : Continuous A) (hB : Continuous B) : Continuous fun x => vecMulVec (A x) (B x) :=
  continuous_matrix fun i j => ((continuous_apply _).comp hA).mul ((continuous_apply _).comp hB)
#align continuous.matrix_vec_mul_vec Continuous.matrix_vecMulVec

@[continuity]
theorem Continuous.matrix_mulVec [NonUnitalNonAssocSemiring R] [ContinuousAdd R] [ContinuousMul R]
    [Fintype n] {A : X → Matrix m n R} {B : X → n → R} (hA : Continuous A) (hB : Continuous B) :
    Continuous fun x => (A x).mulVec (B x) :=
  continuous_pi fun i => ((continuous_apply i).comp hA).matrix_dotProduct hB
#align continuous.matrix_mul_vec Continuous.matrix_mulVec

@[continuity]
theorem Continuous.matrix_vecMul [NonUnitalNonAssocSemiring R] [ContinuousAdd R] [ContinuousMul R]
    [Fintype m] {A : X → m → R} {B : X → Matrix m n R} (hA : Continuous A) (hB : Continuous B) :
    Continuous fun x => vecMul (A x) (B x) :=
  continuous_pi fun i => hA.matrix_dotProduct <| continuous_pi fun j => hB.matrix_elem _ _
#align continuous.matrix_vec_mul Continuous.matrix_vecMul

@[continuity]
theorem Continuous.matrix_submatrix {A : X → Matrix l n R} (hA : Continuous A) (e₁ : m → l)
    (e₂ : p → n) : Continuous fun x => (A x).submatrix e₁ e₂ :=
  continuous_matrix fun i j => hA.matrix_elem _ _
#align continuous.matrix_submatrix Continuous.matrix_submatrix

@[continuity]
theorem Continuous.matrix_reindex {A : X → Matrix l n R} (hA : Continuous A) (e₁ : l ≃ m)
    (e₂ : n ≃ p) : Continuous fun x => reindex e₁ e₂ (A x) :=
  hA.matrix_submatrix _ _
#align continuous.matrix_reindex Continuous.matrix_reindex

@[continuity]
theorem Continuous.matrix_diag {A : X → Matrix n n R} (hA : Continuous A) :
    Continuous fun x => Matrix.diag (A x) :=
  continuous_pi fun _ => hA.matrix_elem _ _
#align continuous.matrix_diag Continuous.matrix_diag

-- note this doesn't elaborate well from the above
theorem continuous_matrix_diag : Continuous (Matrix.diag : Matrix n n R → n → R) :=
  show Continuous fun x : Matrix n n R => Matrix.diag x from continuous_id.matrix_diag
#align continuous_matrix_diag continuous_matrix_diag

@[continuity]
theorem Continuous.matrix_trace [Fintype n] [AddCommMonoid R] [ContinuousAdd R]
    {A : X → Matrix n n R} (hA : Continuous A) : Continuous fun x => trace (A x) :=
  continuous_finset_sum _ fun i hi => hA.matrix_elem _ _
#align continuous.matrix_trace Continuous.matrix_trace

@[continuity]
theorem Continuous.matrix_det [Fintype n] [DecidableEq n] [CommRing R] [TopologicalRing R]
    {A : X → Matrix n n R} (hA : Continuous A) : Continuous fun x => (A x).det :=
  by
  simp_rw [Matrix.det_apply]
  refine' continuous_finset_sum _ fun l _ => Continuous.const_smul _ _
  refine' continuous_finset_prod _ fun l _ => hA.matrix_elem _ _
#align continuous.matrix_det Continuous.matrix_det

@[continuity]
theorem Continuous.matrix_updateColumn [DecidableEq n] (i : n) {A : X → Matrix m n R}
    {B : X → m → R} (hA : Continuous A) (hB : Continuous B) :
    Continuous fun x => (A x).updateColumn i (B x) :=
  continuous_matrix fun j k =>
    (continuous_apply k).comp <|
      ((continuous_apply _).comp hA).update i ((continuous_apply _).comp hB)
#align continuous.matrix_update_column Continuous.matrix_updateColumn

@[continuity]
theorem Continuous.matrix_updateRow [DecidableEq m] (i : m) {A : X → Matrix m n R} {B : X → n → R}
    (hA : Continuous A) (hB : Continuous B) : Continuous fun x => (A x).updateRow i (B x) :=
  hA.update i hB
#align continuous.matrix_update_row Continuous.matrix_updateRow

@[continuity]
theorem Continuous.matrix_cramer [Fintype n] [DecidableEq n] [CommRing R] [TopologicalRing R]
    {A : X → Matrix n n R} {B : X → n → R} (hA : Continuous A) (hB : Continuous B) :
    Continuous fun x => (A x).cramer (B x) :=
  continuous_pi fun i => (hA.matrix_updateColumn _ hB).matrix_det
#align continuous.matrix_cramer Continuous.matrix_cramer

@[continuity]
theorem Continuous.matrix_adjugate [Fintype n] [DecidableEq n] [CommRing R] [TopologicalRing R]
    {A : X → Matrix n n R} (hA : Continuous A) : Continuous fun x => (A x).adjugate :=
  continuous_matrix fun j k =>
    (hA.matrix_transpose.matrix_updateColumn k continuous_const).matrix_det
#align continuous.matrix_adjugate Continuous.matrix_adjugate

/-- When `ring.inverse` is continuous at the determinant (such as in a `normed_ring`, or a
`topological_field`), so is `matrix.has_inv`. -/
theorem continuousAt_matrix_inv [Fintype n] [DecidableEq n] [CommRing R] [TopologicalRing R]
    (A : Matrix n n R) (h : ContinuousAt Ring.inverse A.det) : ContinuousAt Inv.inv A :=
  (h.comp continuous_id.matrix_det.ContinuousAt).smul continuous_id.matrix_adjugate.ContinuousAt
#align continuous_at_matrix_inv continuousAt_matrix_inv

-- lemmas about functions in `data/matrix/block.lean`
section BlockMatrices

@[continuity]
theorem Continuous.matrix_fromBlocks {A : X → Matrix n l R} {B : X → Matrix n m R}
    {C : X → Matrix p l R} {D : X → Matrix p m R} (hA : Continuous A) (hB : Continuous B)
    (hC : Continuous C) (hD : Continuous D) :
    Continuous fun x => Matrix.fromBlocks (A x) (B x) (C x) (D x) :=
  continuous_matrix fun i j => by
    cases i <;> cases j <;> refine' Continuous.matrix_elem _ i j <;> assumption
#align continuous.matrix_from_blocks Continuous.matrix_fromBlocks

@[continuity]
theorem Continuous.matrix_blockDiagonal [Zero R] [DecidableEq p] {A : X → p → Matrix m n R}
    (hA : Continuous A) : Continuous fun x => blockDiagonal (A x) :=
  continuous_matrix fun ⟨i₁, i₂⟩ ⟨j₁, j₂⟩ =>
    (((continuous_apply i₂).comp hA).matrix_elem i₁ j₁).if_const _ continuous_zero
#align continuous.matrix_block_diagonal Continuous.matrix_blockDiagonal

@[continuity]
theorem Continuous.matrix_blockDiag {A : X → Matrix (m × p) (n × p) R} (hA : Continuous A) :
    Continuous fun x => blockDiag (A x) :=
  continuous_pi fun i => continuous_matrix fun j k => hA.matrix_elem _ _
#align continuous.matrix_block_diag Continuous.matrix_blockDiag

@[continuity]
theorem Continuous.matrix_blockDiagonal' [Zero R] [DecidableEq l]
    {A : X → ∀ i, Matrix (m' i) (n' i) R} (hA : Continuous A) :
    Continuous fun x => blockDiagonal' (A x) :=
  continuous_matrix fun ⟨i₁, i₂⟩ ⟨j₁, j₂⟩ =>
    by
    dsimp only [block_diagonal']
    split_ifs
    · subst h
      exact ((continuous_apply i₁).comp hA).matrix_elem i₂ j₂
    · exact continuous_const
#align continuous.matrix_block_diagonal' Continuous.matrix_blockDiagonal'

@[continuity]
theorem Continuous.matrix_blockDiag' {A : X → Matrix (Σi, m' i) (Σi, n' i) R} (hA : Continuous A) :
    Continuous fun x => blockDiag' (A x) :=
  continuous_pi fun i => continuous_matrix fun j k => hA.matrix_elem _ _
#align continuous.matrix_block_diag' Continuous.matrix_blockDiag'

end BlockMatrices

end Continuity

/-! ### Lemmas about infinite sums -/

section tsum

variable [Semiring α] [AddCommMonoid R] [TopologicalSpace R] [Module α R]

theorem HasSum.matrix_transpose {f : X → Matrix m n R} {a : Matrix m n R} (hf : HasSum f a) :
    HasSum (fun x => (f x)ᵀ) aᵀ :=
  (hf.map (Matrix.transposeAddEquiv m n R) continuous_id.matrix_transpose : _)
#align has_sum.matrix_transpose HasSum.matrix_transpose

theorem Summable.matrix_transpose {f : X → Matrix m n R} (hf : Summable f) :
    Summable fun x => (f x)ᵀ :=
  hf.HasSum.matrix_transpose.Summable
#align summable.matrix_transpose Summable.matrix_transpose

@[simp]
theorem summable_matrix_transpose {f : X → Matrix m n R} :
    (Summable fun x => (f x)ᵀ) ↔ Summable f :=
  (Summable.map_iff_of_equiv (Matrix.transposeAddEquiv m n R)
      (@continuous_id (Matrix m n R) _).matrix_transpose continuous_id.matrix_transpose :
    _)
#align summable_matrix_transpose summable_matrix_transpose

theorem Matrix.transpose_tsum [T2Space R] {f : X → Matrix m n R} : (∑' x, f x)ᵀ = ∑' x, (f x)ᵀ :=
  by
  by_cases hf : Summable f
  · exact hf.has_sum.matrix_transpose.tsum_eq.symm
  · have hft := summable_matrix_transpose.not.mpr hf
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft, transpose_zero]
#align matrix.transpose_tsum Matrix.transpose_tsum

theorem HasSum.matrix_conjTranspose [StarAddMonoid R] [ContinuousStar R] {f : X → Matrix m n R}
    {a : Matrix m n R} (hf : HasSum f a) : HasSum (fun x => (f x)ᴴ) aᴴ :=
  (hf.map (Matrix.conjTransposeAddEquiv m n R) continuous_id.matrix_conjTranspose : _)
#align has_sum.matrix_conj_transpose HasSum.matrix_conjTranspose

theorem Summable.matrix_conjTranspose [StarAddMonoid R] [ContinuousStar R] {f : X → Matrix m n R}
    (hf : Summable f) : Summable fun x => (f x)ᴴ :=
  hf.HasSum.matrix_conjTranspose.Summable
#align summable.matrix_conj_transpose Summable.matrix_conjTranspose

@[simp]
theorem summable_matrix_conjTranspose [StarAddMonoid R] [ContinuousStar R] {f : X → Matrix m n R} :
    (Summable fun x => (f x)ᴴ) ↔ Summable f :=
  (Summable.map_iff_of_equiv (Matrix.conjTransposeAddEquiv m n R)
      (@continuous_id (Matrix m n R) _).matrix_conjTranspose continuous_id.matrix_conjTranspose :
    _)
#align summable_matrix_conj_transpose summable_matrix_conjTranspose

theorem Matrix.conjTranspose_tsum [StarAddMonoid R] [ContinuousStar R] [T2Space R]
    {f : X → Matrix m n R} : (∑' x, f x)ᴴ = ∑' x, (f x)ᴴ :=
  by
  by_cases hf : Summable f
  · exact hf.has_sum.matrix_conj_transpose.tsum_eq.symm
  · have hft := summable_matrix_conj_transpose.not.mpr hf
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft, conj_transpose_zero]
#align matrix.conj_transpose_tsum Matrix.conjTranspose_tsum

theorem HasSum.matrix_diagonal [DecidableEq n] {f : X → n → R} {a : n → R} (hf : HasSum f a) :
    HasSum (fun x => diagonal (f x)) (diagonal a) :=
  (hf.map (diagonalAddMonoidHom n R) <| Continuous.matrix_diagonal <| continuous_id : _)
#align has_sum.matrix_diagonal HasSum.matrix_diagonal

theorem Summable.matrix_diagonal [DecidableEq n] {f : X → n → R} (hf : Summable f) :
    Summable fun x => diagonal (f x) :=
  hf.HasSum.matrix_diagonal.Summable
#align summable.matrix_diagonal Summable.matrix_diagonal

@[simp]
theorem summable_matrix_diagonal [DecidableEq n] {f : X → n → R} :
    (Summable fun x => diagonal (f x)) ↔ Summable f :=
  (Summable.map_iff_of_leftInverse (@Matrix.diagonalAddMonoidHom n R _ _)
      (Matrix.diagAddMonoidHom n R) (Continuous.matrix_diagonal continuous_id)
      continuous_matrix_diag fun A => diag_diagonal A :
    _)
#align summable_matrix_diagonal summable_matrix_diagonal

theorem Matrix.diagonal_tsum [DecidableEq n] [T2Space R] {f : X → n → R} :
    diagonal (∑' x, f x) = ∑' x, diagonal (f x) :=
  by
  by_cases hf : Summable f
  · exact hf.has_sum.matrix_diagonal.tsum_eq.symm
  · have hft := summable_matrix_diagonal.not.mpr hf
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft]
    exact diagonal_zero
#align matrix.diagonal_tsum Matrix.diagonal_tsum

theorem HasSum.matrix_diag {f : X → Matrix n n R} {a : Matrix n n R} (hf : HasSum f a) :
    HasSum (fun x => diag (f x)) (diag a) :=
  (hf.map (diagAddMonoidHom n R) continuous_matrix_diag : _)
#align has_sum.matrix_diag HasSum.matrix_diag

theorem Summable.matrix_diag {f : X → Matrix n n R} (hf : Summable f) :
    Summable fun x => diag (f x) :=
  hf.HasSum.matrix_diag.Summable
#align summable.matrix_diag Summable.matrix_diag

section BlockMatrices

theorem HasSum.matrix_blockDiagonal [DecidableEq p] {f : X → p → Matrix m n R}
    {a : p → Matrix m n R} (hf : HasSum f a) :
    HasSum (fun x => blockDiagonal (f x)) (blockDiagonal a) :=
  (hf.map (blockDiagonalAddMonoidHom m n p R) <| Continuous.matrix_blockDiagonal <| continuous_id :
    _)
#align has_sum.matrix_block_diagonal HasSum.matrix_blockDiagonal

theorem Summable.matrix_blockDiagonal [DecidableEq p] {f : X → p → Matrix m n R} (hf : Summable f) :
    Summable fun x => blockDiagonal (f x) :=
  hf.HasSum.matrix_blockDiagonal.Summable
#align summable.matrix_block_diagonal Summable.matrix_blockDiagonal

theorem summable_matrix_blockDiagonal [DecidableEq p] {f : X → p → Matrix m n R} :
    (Summable fun x => blockDiagonal (f x)) ↔ Summable f :=
  (Summable.map_iff_of_leftInverse (Matrix.blockDiagonalAddMonoidHom m n p R)
      (Matrix.blockDiagAddMonoidHom m n p R) (Continuous.matrix_blockDiagonal continuous_id)
      (Continuous.matrix_blockDiag continuous_id) fun A => blockDiag_blockDiagonal A :
    _)
#align summable_matrix_block_diagonal summable_matrix_blockDiagonal

theorem Matrix.blockDiagonal_tsum [DecidableEq p] [T2Space R] {f : X → p → Matrix m n R} :
    blockDiagonal (∑' x, f x) = ∑' x, blockDiagonal (f x) :=
  by
  by_cases hf : Summable f
  · exact hf.has_sum.matrix_block_diagonal.tsum_eq.symm
  · have hft := summable_matrix_block_diagonal.not.mpr hf
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft]
    exact block_diagonal_zero
#align matrix.block_diagonal_tsum Matrix.blockDiagonal_tsum

theorem HasSum.matrix_blockDiag {f : X → Matrix (m × p) (n × p) R} {a : Matrix (m × p) (n × p) R}
    (hf : HasSum f a) : HasSum (fun x => blockDiag (f x)) (blockDiag a) :=
  (hf.map (blockDiagAddMonoidHom m n p R) <| Continuous.matrix_blockDiag continuous_id : _)
#align has_sum.matrix_block_diag HasSum.matrix_blockDiag

theorem Summable.matrix_blockDiag {f : X → Matrix (m × p) (n × p) R} (hf : Summable f) :
    Summable fun x => blockDiag (f x) :=
  hf.HasSum.matrix_blockDiag.Summable
#align summable.matrix_block_diag Summable.matrix_blockDiag

theorem HasSum.matrix_blockDiagonal' [DecidableEq l] {f : X → ∀ i, Matrix (m' i) (n' i) R}
    {a : ∀ i, Matrix (m' i) (n' i) R} (hf : HasSum f a) :
    HasSum (fun x => blockDiagonal' (f x)) (blockDiagonal' a) :=
  (hf.map (blockDiagonal'AddMonoidHom m' n' R) <|
      Continuous.matrix_blockDiagonal' <| continuous_id :
    _)
#align has_sum.matrix_block_diagonal' HasSum.matrix_blockDiagonal'

theorem Summable.matrix_blockDiagonal' [DecidableEq l] {f : X → ∀ i, Matrix (m' i) (n' i) R}
    (hf : Summable f) : Summable fun x => blockDiagonal' (f x) :=
  hf.HasSum.matrix_blockDiagonal'.Summable
#align summable.matrix_block_diagonal' Summable.matrix_blockDiagonal'

theorem summable_matrix_blockDiagonal' [DecidableEq l] {f : X → ∀ i, Matrix (m' i) (n' i) R} :
    (Summable fun x => blockDiagonal' (f x)) ↔ Summable f :=
  (Summable.map_iff_of_leftInverse (Matrix.blockDiagonal'AddMonoidHom m' n' R)
      (Matrix.blockDiag'AddMonoidHom m' n' R) (Continuous.matrix_blockDiagonal' continuous_id)
      (Continuous.matrix_blockDiag' continuous_id) fun A => blockDiag'_blockDiagonal' A :
    _)
#align summable_matrix_block_diagonal' summable_matrix_blockDiagonal'

theorem Matrix.blockDiagonal'_tsum [DecidableEq l] [T2Space R]
    {f : X → ∀ i, Matrix (m' i) (n' i) R} :
    blockDiagonal' (∑' x, f x) = ∑' x, blockDiagonal' (f x) :=
  by
  by_cases hf : Summable f
  · exact hf.has_sum.matrix_block_diagonal'.tsum_eq.symm
  · have hft := summable_matrix_block_diagonal'.not.mpr hf
    rw [tsum_eq_zero_of_not_summable hf, tsum_eq_zero_of_not_summable hft]
    exact block_diagonal'_zero
#align matrix.block_diagonal'_tsum Matrix.blockDiagonal'_tsum

theorem HasSum.matrix_blockDiag' {f : X → Matrix (Σi, m' i) (Σi, n' i) R}
    {a : Matrix (Σi, m' i) (Σi, n' i) R} (hf : HasSum f a) :
    HasSum (fun x => blockDiag' (f x)) (blockDiag' a) :=
  (hf.map (blockDiag'AddMonoidHom m' n' R) <| Continuous.matrix_blockDiag' continuous_id : _)
#align has_sum.matrix_block_diag' HasSum.matrix_blockDiag'

theorem Summable.matrix_blockDiag' {f : X → Matrix (Σi, m' i) (Σi, n' i) R} (hf : Summable f) :
    Summable fun x => blockDiag' (f x) :=
  hf.HasSum.matrix_blockDiag'.Summable
#align summable.matrix_block_diag' Summable.matrix_blockDiag'

end BlockMatrices

end tsum


/-
Copyright (c) 2014 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura, Jeremy Avigad
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.logic

universes u v u₁ u₂ v₁ v₂

namespace Mathlib

@[simp] theorem prod.mk.eta {α : Type u} {β : Type v} {p : α × β} : (prod.fst p, prod.snd p) = p :=
  prod.cases_on p
    fun (p_fst : α) (p_snd : β) =>
      idRhs ((prod.fst (p_fst, p_snd), prod.snd (p_fst, p_snd)) = (prod.fst (p_fst, p_snd), prod.snd (p_fst, p_snd))) rfl

protected instance prod.inhabited {α : Type u} {β : Type v} [Inhabited α] [Inhabited β] : Inhabited (α × β) :=
  { default := (Inhabited.default, Inhabited.default) }

protected instance prod.decidable_eq {α : Type u} {β : Type v} [h₁ : DecidableEq α] [h₂ : DecidableEq β] : DecidableEq (α × β) :=
  sorry

protected instance prod.has_lt {α : Type u} {β : Type v} [HasLess α] [HasLess β] : HasLess (α × β) :=
  { Less := fun (s t : α × β) => prod.fst s < prod.fst t ∨ prod.fst s = prod.fst t ∧ prod.snd s < prod.snd t }

protected instance prod_has_decidable_lt {α : Type u} {β : Type v} [HasLess α] [HasLess β] [DecidableEq α] [DecidableEq β] [DecidableRel Less] [DecidableRel Less] (s : α × β) (t : α × β) : Decidable (s < t) :=
  or.decidable

theorem prod.lt_def {α : Type u} {β : Type v} [HasLess α] [HasLess β] (s : α × β) (t : α × β) : s < t = (prod.fst s < prod.fst t ∨ prod.fst s = prod.fst t ∧ prod.snd s < prod.snd t) :=
  rfl

def prod.map {α₁ : Type u₁} {α₂ : Type u₂} {β₁ : Type v₁} {β₂ : Type v₂} (f : α₁ → α₂) (g : β₁ → β₂) (x : α₁ × β₁) : α₂ × β₂ :=
  (f (prod.fst x), g (prod.snd x))


/-
Copyright (c) 2020 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
-/
import algebra.associated
import algebra.big_operators.basic
/-!
# Prime elements in rings

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.
This file contains lemmas about prime elements of commutative rings.
-/

section cancel_comm_monoid_with_zero

variables {R : Type*} [cancel_comm_monoid_with_zero R]
open finset

open_locale big_operators

/-- If `x * y = a * ∏ i in s, p i` where `p i` is always prime, then
  `x` and `y` can both be written as a divisor of `a` multiplied by
  a product over a subset of `s`  -/
lemma mul_eq_mul_prime_prod {α : Type*} [decidable_eq α] {x y a : R} {s : finset α}
  {p : α → R} (hp : ∀ i ∈ s, prime (p i)) (hx : x * y = a * ∏ i in s, p i) :
  ∃ (t u : finset α) (b c : R),
      t ∪ u = s
    ∧ disjoint t u
    ∧ a = b * c
    ∧ x = b * ∏ i in t, p i
    ∧ y = c * ∏ i in u, p i :=
begin
  induction s using finset.induction with i s his ih generalizing x y a,
  { exact ⟨∅, ∅, x, y, by simp [hx]⟩ },
  { rw [prod_insert his, ← mul_assoc] at hx,
    have hpi : prime (p i), { exact hp i (mem_insert_self _ _) },
    rcases ih (λ i hi, hp i (mem_insert_of_mem hi)) hx with
      ⟨t, u, b, c, htus, htu, hbc, rfl, rfl⟩,
    have hit : i ∉ t, from λ hit, his (htus ▸ mem_union_left _ hit),
    have hiu : i ∉ u, from λ hiu, his (htus ▸ mem_union_right _ hiu),
    obtain ⟨d, rfl⟩ | ⟨d, rfl⟩ : p i ∣ b ∨ p i ∣ c,
      from hpi.dvd_or_dvd ⟨a, by rw [← hbc, mul_comm]⟩,
    { rw [mul_assoc, mul_comm a, mul_right_inj' hpi.ne_zero] at hbc,
      exact ⟨insert i t, u, d, c, by rw [insert_union, htus],
        disjoint_insert_left.2 ⟨hiu, htu⟩,
          by simp [hbc, prod_insert hit, mul_assoc, mul_comm, mul_left_comm]⟩ },
    { rw [← mul_assoc, mul_right_comm b, mul_left_inj' hpi.ne_zero] at hbc,
      exact ⟨t, insert i u, b, d, by rw [union_insert, htus],
        disjoint_insert_right.2 ⟨hit, htu⟩,
          by simp [← hbc, prod_insert hiu, mul_assoc, mul_comm, mul_left_comm]⟩ } }
end

/-- If ` x * y = a * p ^ n` where `p` is prime, then `x` and `y` can both be written
  as the product of a power of `p` and a divisor of `a`. -/
lemma mul_eq_mul_prime_pow {x y a p : R} {n : ℕ} (hp : prime p) (hx : x * y = a * p ^ n) :
  ∃ (i j : ℕ) (b c : R),
    i + j = n
  ∧ a = b * c
  ∧ x = b * p ^ i
  ∧ y = c * p ^ j :=
begin
  rcases mul_eq_mul_prime_prod (λ _ _, hp)
    (show x * y = a * (range n).prod (λ _, p), by simpa) with
    ⟨t, u, b, c, htus, htu, rfl, rfl, rfl⟩,
  exact ⟨t.card, u.card, b, c, by rw [← card_disjoint_union htu, htus, card_range], by simp⟩,
end

end cancel_comm_monoid_with_zero

section comm_ring

variables {α : Type*} [comm_ring α]

lemma prime.neg {p : α} (hp : prime p) : prime (-p) :=
begin
  obtain ⟨h1, h2, h3⟩ := hp,
  exact ⟨neg_ne_zero.mpr h1, by rwa is_unit.neg_iff, by simpa [neg_dvd] using h3⟩
end

lemma prime.abs [linear_order α] {p : α} (hp : prime p) : prime (abs p) :=
begin
  obtain h|h := abs_choice p; rw h,
  { exact hp },
  { exact hp.neg }
end

end comm_ring


import Catlib4.Groundwork.Basic
import Catlib4.Notation
import Catlib4.Combinatorics

namespace CategoryTheory

section Category

structure CategoryStruct extends Quiver where
  id : ∀ a : α, a ⟶ a
  comp : ∀ {a b c : α}, (a ⟶ b) → (b ⟶ c) → (a ⟶ c)

instance : Coe CategoryStruct Quiver where
  coe C := {
    α := C.α
    hom := C.hom
  }

instance : CoeSort CategoryStruct (Type u) where
  coe C := C.α

instance (C : CategoryStruct) : HasIdentity C where
  identity := C.id

instance (C : CategoryStruct) : HasComp C where
  comp := C.comp

structure Category extends CategoryStruct where
  id_comp' {x y : α} : ∀ (f : x ⟶ y), 𝟙 x ≫ f = f
  comp_id' {x y : α} : ∀ (f : x ⟶ y), f ≫ 𝟙 y = f
  assoc' : ∀ {w x y z : α} (f : w ⟶ x) (g : x ⟶ y) (h : y ⟶ z),
    f ≫ (g ≫ h) = (f ≫ g) ≫ h

instance : Coe Category CategoryStruct where
  coe C := {
    α := C.α
    hom := C.hom
    id := C.id
    comp := C.comp
  }

instance : CoeSort Category (Type u) where
  coe C := C.α

@[simp]
theorem Category.id_comp (α : Category)
  : ∀ {x y : α} (f : x ⟶ y), 𝟙 x ≫ f = f := α.id_comp'

@[simp]
theorem Category.comp_id (α : Category)
  : ∀ {x y : α} (f : x ⟶ y), f ≫ 𝟙 y = f := α.comp_id'

@[simp]
theorem Category.assoc (α : Category)
  : ∀ {w x y z : α} (f : w ⟶ x) (g : x ⟶ y) (h : y ⟶ z),
    f ≫ (g ≫ h) = (f ≫ g) ≫ h := α.assoc'

structure Iso {C : Category} (x y : C) where
  hom : x ⟶ y
  inv : y ⟶ x
  hom_inv_id : hom ≫ inv = 𝟙 x
  inv_hom_id : inv ≫ hom = 𝟙 y

instance (C : Category) : HasIso C where
  iso := Iso

end Category

section Functor

structure Functor (C D : Category) extends Prefunctor C D where
  map_id' : ∀ (x : C), map (𝟙 x) = 𝟙 (obj x)
  map_comp' : ∀ {x y z : C} (f : x ⟶ y) (g : y ⟶ z), map (f ≫ g) = map f ≫ map g

infixr:26 "⥤" => Functor

section NaturalTransformations

structure NatTrans {α β : Category.{u,v}} (F G : α ⥤ β) : Type (max u v)
where
  map : ∀ (x : α), F.obj x ⟶ G.obj x
  naturality : ∀ {x y : α} (f : x ⟶ y), F.map f ≫ map y = map x ≫ G.map f

theorem NatTrans.ext {C D : Category} {F G : C ⥤ D} :
  (σ τ : NatTrans F G) → σ.map = τ.map → σ = τ
| ⟨ _, _ ⟩, ⟨ _, _ ⟩ => by simp_all

def NatTrans.vcomp
  {α β : Category}
  {F G H : α ⥤ β}
  (σ : NatTrans F G) (τ : NatTrans G H) : NatTrans F H
where
  map x := σ.map x ≫ τ.map x
  naturality f := by
    conv => lhs; rw [Category.assoc, σ.naturality f]
    conv => rhs; rw [← Category.assoc, ← τ.naturality f]
    rw [Category.assoc]

def NatTrans.id
  {α β : Category}
  (F : α ⥤ β) : NatTrans F F
where
  map x := 𝟙 (F.obj x)
  naturality f := by simp

end NaturalTransformations

section functor_category

def functor_category (C D : Category.{u,v}) : Category.{max u v, max u v + 1} where
  α := C ⥤ D
  hom F G := NatTrans F G
  id := NatTrans.id
  comp := NatTrans.vcomp
  id_comp' {F G} σ := by
    suffices p : NatTrans.vcomp (NatTrans.id F) σ = σ
    from p
    apply NatTrans.ext
    simp [NatTrans.vcomp, NatTrans.id]
  comp_id' {F G} σ := by
    suffices p : NatTrans.vcomp σ (NatTrans.id G) = σ
    from p
    apply NatTrans.ext
    simp [NatTrans.vcomp, NatTrans.id]
  assoc' {F₁ F₂ F₃ F₄} σ τ ρ := by
    suffices p
      : NatTrans.vcomp σ (NatTrans.vcomp τ ρ)
      = NatTrans.vcomp (NatTrans.vcomp σ τ) ρ
    from p
    apply NatTrans.ext
    simp [NatTrans.vcomp, NatTrans.id]

instance : CoeSort (functor_category.{u,v} C D) (Type (max u v)) where
  coe _ := C ⥤ D

instance : HasHom (C ⥤ D) := inferInstanceAs (HasHom (functor_category C D).α)
instance (C D : Category ): HasIso (C ⥤ D) := inferInstanceAs (HasIso (functor_category C D).α)

end functor_category

@[simp]
theorem Functor.map_id {C D : Category} (F : Functor C D) (x : C) : F.map (𝟙 x) = 𝟙 (F.obj x) := map_id' F x

@[simp]
theorem Functor.map_comp {C D : Category} (F : Functor C D)
  : ∀ {x y z : C} (f : x ⟶ y) (g : y ⟶ z), F.map (f ≫ g) = F.map f ≫ F.map g
  := map_comp' F

def Functor.constant (C : Category) {D : Category} (a : D) : C ⥤ D where
  obj _ := a
  map _ := 𝟙 a
  map_id' _ := rfl
  map_comp' _ _ := (D.id_comp _).symm

def Functor.comp' {B C D : Category} (F : Functor B C) (G : Functor C D) : Functor B D where
  obj := G.obj ∘ F.obj
  map := G.map ∘ F.map
  map_id' x := by simp
  map_comp' := by simp

instance {B C D : Category} : HasComp' (Functor B C) (Functor C D) (Functor B D) where
  comp' := Functor.comp'

@[simp]
theorem comp'_map {B C D : Category} (F : Functor B C) (G : Functor C D)
  {x y : B} (f : x ⟶ y) : (F ⋙ G).map f = G.map (F.map f) := rfl

section NaturalTransformations

def NatTrans.hcomp {B C D : Category} {F₁ G₁ : B ⥤ C} {F₂ G₂ : C ⥤ D}
  (σ : NatTrans F₁ G₁) (τ : NatTrans F₂ G₂) : NatTrans (F₁ ⋙ F₂) (G₁ ⋙ G₂)
where
  map x := τ.map (F₁.obj x) ≫ G₂.map (σ.map x)
  naturality f := by
    dsimp
    rw [Category.assoc D]
    rw [τ.naturality]
    rw [← Category.assoc D]
    rw [← G₂.map_comp]
    rw [σ.naturality]
    rw [G₂.map_comp]
    rw [Category.assoc D]

instance {B C D : Category} {F₁ G₁ : B ⥤ C} {F₂ G₂ : C ⥤ D}
  : HasHComp (NatTrans F₁ G₁) (NatTrans F₂ G₂) (NatTrans (F₁ ⋙ F₂) (G₁ ⋙ G₂)) where
  hcomp := NatTrans.hcomp

end NaturalTransformations

end Functor

end CategoryTheory


/-
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.data.equiv.basic
import Mathlib.PostPort

universes u_5 u_6 l u_1 u_2 u_3 u_4

namespace Mathlib

/-!
# Local equivalences

This files defines equivalences between subsets of given types.
An element `e` of `local_equiv α β` is made of two maps `e.to_fun` and `e.inv_fun` respectively
from α to β and from  β to α (just like equivs), which are inverse to each other on the subsets
`e.source` and `e.target` of respectively α and β.

They are designed in particular to define charts on manifolds.

The main functionality is `e.trans f`, which composes the two local equivalences by restricting
the source and target to the maximal set where the composition makes sense.

As for equivs, we register a coercion to functions and use it in our simp normal form: we write
`e x` and `e.symm y` instead of `e.to_fun x` and `e.inv_fun y`.

## Main definitions

`equiv.to_local_equiv`: associating a local equiv to an equiv, with source = target = univ
`local_equiv.symm`    : the inverse of a local equiv
`local_equiv.trans`   : the composition of two local equivs
`local_equiv.refl`    : the identity local equiv
`local_equiv.of_set`  : the identity on a set `s`
`eq_on_source`        : equivalence relation describing the "right" notion of equality for local
                        equivs (see below in implementation notes)

## Implementation notes

There are at least three possible implementations of local equivalences:
* equivs on subtypes
* pairs of functions taking values in `option α` and `option β`, equal to none where the local
equivalence is not defined
* pairs of functions defined everywhere, keeping the source and target as additional data

Each of these implementations has pros and cons.
* When dealing with subtypes, one still need to define additional API for composition and
restriction of domains. Checking that one always belongs to the right subtype makes things very
tedious, and leads quickly to DTT hell (as the subtype `u ∩ v` is not the "same" as `v ∩ u`, for
instance).
* With option-valued functions, the composition is very neat (it is just the usual composition, and
the domain is restricted automatically). These are implemented in `pequiv.lean`. For manifolds,
where one wants to discuss thoroughly the smoothness of the maps, this creates however a lot of
overhead as one would need to extend all classes of smoothness to option-valued maps.
* The local_equiv version as explained above is easier to use for manifolds. The drawback is that
there is extra useless data (the values of `to_fun` and `inv_fun` outside of `source` and `target`).
In particular, the equality notion between local equivs is not "the right one", i.e., coinciding
source and target and equality there. Moreover, there are no local equivs in this sense between
an empty type and a nonempty type. Since empty types are not that useful, and since one almost never
needs to talk about equal local equivs, this is not an issue in practice.
Still, we introduce an equivalence relation `eq_on_source` that captures this right notion of
equality, and show that many properties are invariant under this equivalence relation.
-/

-- register in the simpset `mfld_simps` several lemmas that are often useful when dealing

-- with manifolds

namespace tactic.interactive

/-- A very basic tactic to show that sets showing up in manifolds coincide or are included in
one another. -/
end tactic.interactive

/-- Local equivalence between subsets `source` and `target` of α and β respectively. The (global)
maps `to_fun : α → β` and `inv_fun : β → α` map `source` to `target` and conversely, and are inverse
to each other there. The values of `to_fun` outside of `source` and of `inv_fun` outside of `target`
are irrelevant. -/
structure local_equiv (α : Type u_5) (β : Type u_6)
where
  to_fun : α → β
  inv_fun : β → α
  source : set α
  target : set β
  map_source' : ∀ {x : α}, x ∈ source → to_fun x ∈ target
  map_target' : ∀ {x : β}, x ∈ target → inv_fun x ∈ source
  left_inv' : ∀ {x : α}, x ∈ source → inv_fun (to_fun x) = x
  right_inv' : ∀ {x : β}, x ∈ target → to_fun (inv_fun x) = x

/-- Associating a local_equiv to an equiv-/
def equiv.to_local_equiv {α : Type u_1} {β : Type u_2} (e : α ≃ β) : local_equiv α β :=
  local_equiv.mk (equiv.to_fun e) (equiv.inv_fun e) set.univ set.univ sorry sorry sorry sorry

namespace local_equiv

/-- The inverse of a local equiv -/
protected def symm {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : local_equiv β α :=
  mk (inv_fun e) (to_fun e) (target e) (source e) (map_target' e) (map_source' e) (right_inv' e) (left_inv' e)

protected instance has_coe_to_fun {α : Type u_1} {β : Type u_2} : has_coe_to_fun (local_equiv α β) :=
  has_coe_to_fun.mk (fun (x : local_equiv α β) => α → β) to_fun

@[simp] theorem coe_mk {α : Type u_1} {β : Type u_2} (f : α → β) (g : β → α) (s : set α) (t : set β) (ml : ∀ {x : α}, x ∈ s → f x ∈ t) (mr : ∀ {x : β}, x ∈ t → g x ∈ s) (il : ∀ {x : α}, x ∈ s → g (f x) = x) (ir : ∀ {x : β}, x ∈ t → f (g x) = x) : ⇑(mk f g s t ml mr il ir) = f :=
  rfl

@[simp] theorem coe_symm_mk {α : Type u_1} {β : Type u_2} (f : α → β) (g : β → α) (s : set α) (t : set β) (ml : ∀ {x : α}, x ∈ s → f x ∈ t) (mr : ∀ {x : β}, x ∈ t → g x ∈ s) (il : ∀ {x : α}, x ∈ s → g (f x) = x) (ir : ∀ {x : β}, x ∈ t → f (g x) = x) : ⇑(local_equiv.symm (mk f g s t ml mr il ir)) = g :=
  rfl

@[simp] theorem to_fun_as_coe {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : to_fun e = ⇑e :=
  rfl

@[simp] theorem inv_fun_as_coe {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : inv_fun e = ⇑(local_equiv.symm e) :=
  rfl

@[simp] theorem map_source {α : Type u_1} {β : Type u_2} (e : local_equiv α β) {x : α} (h : x ∈ source e) : coe_fn e x ∈ target e :=
  map_source' e h

protected theorem maps_to {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : set.maps_to (⇑e) (source e) (target e) :=
  fun (_x : α) => map_source e

@[simp] theorem map_target {α : Type u_1} {β : Type u_2} (e : local_equiv α β) {x : β} (h : x ∈ target e) : coe_fn (local_equiv.symm e) x ∈ source e :=
  map_target' e h

theorem symm_maps_to {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : set.maps_to (⇑(local_equiv.symm e)) (target e) (source e) :=
  local_equiv.maps_to (local_equiv.symm e)

@[simp] theorem left_inv {α : Type u_1} {β : Type u_2} (e : local_equiv α β) {x : α} (h : x ∈ source e) : coe_fn (local_equiv.symm e) (coe_fn e x) = x :=
  left_inv' e h

protected theorem left_inv_on {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : set.left_inv_on (⇑(local_equiv.symm e)) (⇑e) (source e) :=
  fun (_x : α) => left_inv e

@[simp] theorem right_inv {α : Type u_1} {β : Type u_2} (e : local_equiv α β) {x : β} (h : x ∈ target e) : coe_fn e (coe_fn (local_equiv.symm e) x) = x :=
  right_inv' e h

protected theorem right_inv_on {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : set.right_inv_on (⇑(local_equiv.symm e)) (⇑e) (target e) :=
  fun (_x : β) => right_inv e

/-- Associating to a local_equiv an equiv between the source and the target -/
protected def to_equiv {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : ↥(source e) ≃ ↥(target e) :=
  equiv.mk (fun (x : ↥(source e)) => { val := coe_fn e ↑x, property := sorry })
    (fun (y : ↥(target e)) => { val := coe_fn (local_equiv.symm e) ↑y, property := sorry }) sorry sorry

@[simp] theorem symm_source {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : source (local_equiv.symm e) = target e :=
  rfl

@[simp] theorem symm_target {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : target (local_equiv.symm e) = source e :=
  rfl

@[simp] theorem symm_symm {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : local_equiv.symm (local_equiv.symm e) = e := sorry

/-- A local equiv induces a bijection between its source and target -/
theorem bij_on_source {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : set.bij_on (⇑e) (source e) (target e) :=
  set.inv_on.bij_on { left := local_equiv.left_inv_on e, right := local_equiv.right_inv_on e } (local_equiv.maps_to e)
    (symm_maps_to e)

theorem image_eq_target_inter_inv_preimage {α : Type u_1} {β : Type u_2} (e : local_equiv α β) {s : set α} (h : s ⊆ source e) : ⇑e '' s = target e ∩ ⇑(local_equiv.symm e) ⁻¹' s := sorry

theorem image_inter_source_eq {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set α) : ⇑e '' (s ∩ source e) = target e ∩ ⇑(local_equiv.symm e) ⁻¹' (s ∩ source e) :=
  image_eq_target_inter_inv_preimage e (set.inter_subset_right s (source e))

theorem image_inter_source_eq' {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set α) : ⇑e '' (s ∩ source e) = target e ∩ ⇑(local_equiv.symm e) ⁻¹' s := sorry

theorem symm_image_eq_source_inter_preimage {α : Type u_1} {β : Type u_2} (e : local_equiv α β) {s : set β} (h : s ⊆ target e) : ⇑(local_equiv.symm e) '' s = source e ∩ ⇑e ⁻¹' s :=
  image_eq_target_inter_inv_preimage (local_equiv.symm e) h

theorem symm_image_inter_target_eq {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set β) : ⇑(local_equiv.symm e) '' (s ∩ target e) = source e ∩ ⇑e ⁻¹' (s ∩ target e) :=
  image_inter_source_eq (local_equiv.symm e) s

theorem symm_image_inter_target_eq' {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set β) : ⇑(local_equiv.symm e) '' (s ∩ target e) = source e ∩ ⇑e ⁻¹' s :=
  image_inter_source_eq' (local_equiv.symm e) s

theorem source_inter_preimage_inv_preimage {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set α) : source e ∩ ⇑e ⁻¹' (⇑(local_equiv.symm e) ⁻¹' s) = source e ∩ s := sorry

theorem target_inter_inv_preimage_preimage {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set β) : target e ∩ ⇑(local_equiv.symm e) ⁻¹' (⇑e ⁻¹' s) = target e ∩ s :=
  source_inter_preimage_inv_preimage (local_equiv.symm e) s

theorem image_source_eq_target {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : ⇑e '' source e = target e :=
  set.bij_on.image_eq (bij_on_source e)

theorem source_subset_preimage_target {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : source e ⊆ ⇑e ⁻¹' target e :=
  fun (x : α) (hx : x ∈ source e) => map_source e hx

theorem inv_image_target_eq_source {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : ⇑(local_equiv.symm e) '' target e = source e :=
  set.bij_on.image_eq (bij_on_source (local_equiv.symm e))

theorem target_subset_preimage_source {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : target e ⊆ ⇑(local_equiv.symm e) ⁻¹' source e :=
  fun (x : β) (hx : x ∈ target e) => map_target e hx

/-- Two local equivs that have the same `source`, same `to_fun` and same `inv_fun`, coincide. -/
protected theorem ext {α : Type u_1} {β : Type u_2} {e : local_equiv α β} {e' : local_equiv α β} (h : ∀ (x : α), coe_fn e x = coe_fn e' x) (hsymm : ∀ (x : β), coe_fn (local_equiv.symm e) x = coe_fn (local_equiv.symm e') x) (hs : source e = source e') : e = e' := sorry

/-- Restricting a local equivalence to e.source ∩ s -/
protected def restr {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set α) : local_equiv α β :=
  mk (⇑e) (⇑(local_equiv.symm e)) (source e ∩ s) (target e ∩ ⇑(local_equiv.symm e) ⁻¹' s) sorry sorry sorry sorry

@[simp] theorem restr_coe {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set α) : ⇑(local_equiv.restr e s) = ⇑e :=
  rfl

@[simp] theorem restr_coe_symm {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set α) : ⇑(local_equiv.symm (local_equiv.restr e s)) = ⇑(local_equiv.symm e) :=
  rfl

@[simp] theorem restr_source {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set α) : source (local_equiv.restr e s) = source e ∩ s :=
  rfl

@[simp] theorem restr_target {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set α) : target (local_equiv.restr e s) = target e ∩ ⇑(local_equiv.symm e) ⁻¹' s :=
  rfl

theorem restr_eq_of_source_subset {α : Type u_1} {β : Type u_2} {e : local_equiv α β} {s : set α} (h : source e ⊆ s) : local_equiv.restr e s = e := sorry

@[simp] theorem restr_univ {α : Type u_1} {β : Type u_2} {e : local_equiv α β} : local_equiv.restr e set.univ = e :=
  restr_eq_of_source_subset (set.subset_univ (source e))

/-- The identity local equiv -/
protected def refl (α : Type u_1) : local_equiv α α :=
  equiv.to_local_equiv (equiv.refl α)

@[simp] theorem refl_source {α : Type u_1} : source (local_equiv.refl α) = set.univ :=
  rfl

@[simp] theorem refl_target {α : Type u_1} : target (local_equiv.refl α) = set.univ :=
  rfl

@[simp] theorem refl_coe {α : Type u_1} : ⇑(local_equiv.refl α) = id :=
  rfl

@[simp] theorem refl_symm {α : Type u_1} : local_equiv.symm (local_equiv.refl α) = local_equiv.refl α :=
  rfl

@[simp] theorem refl_restr_source {α : Type u_1} (s : set α) : source (local_equiv.restr (local_equiv.refl α) s) = s := sorry

@[simp] theorem refl_restr_target {α : Type u_1} (s : set α) : target (local_equiv.restr (local_equiv.refl α) s) = s := sorry

/-- The identity local equiv on a set `s` -/
def of_set {α : Type u_1} (s : set α) : local_equiv α α :=
  mk id id s s sorry sorry sorry sorry

@[simp] theorem of_set_source {α : Type u_1} (s : set α) : source (of_set s) = s :=
  rfl

@[simp] theorem of_set_target {α : Type u_1} (s : set α) : target (of_set s) = s :=
  rfl

@[simp] theorem of_set_coe {α : Type u_1} (s : set α) : ⇑(of_set s) = id :=
  rfl

@[simp] theorem of_set_symm {α : Type u_1} (s : set α) : local_equiv.symm (of_set s) = of_set s :=
  rfl

/-- Composing two local equivs if the target of the first coincides with the source of the
second. -/
protected def trans' {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) (h : target e = source e') : local_equiv α γ :=
  mk (⇑e' ∘ ⇑e) (⇑(local_equiv.symm e) ∘ ⇑(local_equiv.symm e')) (source e) (target e') sorry sorry sorry sorry

/-- Composing two local equivs, by restricting to the maximal domain where their composition
is well defined. -/
protected def trans {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : local_equiv α γ :=
  local_equiv.trans' (local_equiv.symm (local_equiv.restr (local_equiv.symm e) (source e')))
    (local_equiv.restr e' (target e)) sorry

@[simp] theorem coe_trans {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : ⇑(local_equiv.trans e e') = ⇑e' ∘ ⇑e :=
  rfl

@[simp] theorem coe_trans_symm {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : ⇑(local_equiv.symm (local_equiv.trans e e')) = ⇑(local_equiv.symm e) ∘ ⇑(local_equiv.symm e') :=
  rfl

theorem trans_symm_eq_symm_trans_symm {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : local_equiv.symm (local_equiv.trans e e') = local_equiv.trans (local_equiv.symm e') (local_equiv.symm e) := sorry

@[simp] theorem trans_source {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : source (local_equiv.trans e e') = source e ∩ ⇑e ⁻¹' source e' :=
  rfl

theorem trans_source' {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : source (local_equiv.trans e e') = source e ∩ ⇑e ⁻¹' (target e ∩ source e') := sorry

theorem trans_source'' {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : source (local_equiv.trans e e') = ⇑(local_equiv.symm e) '' (target e ∩ source e') := sorry

theorem image_trans_source {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : ⇑e '' source (local_equiv.trans e e') = target e ∩ source e' :=
  image_source_eq_target (local_equiv.symm (local_equiv.restr (local_equiv.symm e) (source e')))

@[simp] theorem trans_target {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : target (local_equiv.trans e e') = target e' ∩ ⇑(local_equiv.symm e') ⁻¹' target e :=
  rfl

theorem trans_target' {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : target (local_equiv.trans e e') = target e' ∩ ⇑(local_equiv.symm e') ⁻¹' (source e' ∩ target e) :=
  trans_source' (local_equiv.symm e') (local_equiv.symm e)

theorem trans_target'' {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : target (local_equiv.trans e e') = ⇑e' '' (source e' ∩ target e) :=
  trans_source'' (local_equiv.symm e') (local_equiv.symm e)

theorem inv_image_trans_target {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) : ⇑(local_equiv.symm e') '' target (local_equiv.trans e e') = source e' ∩ target e :=
  image_trans_source (local_equiv.symm e') (local_equiv.symm e)

theorem trans_assoc {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} (e : local_equiv α β) (e' : local_equiv β γ) (e'' : local_equiv γ δ) : local_equiv.trans (local_equiv.trans e e') e'' = local_equiv.trans e (local_equiv.trans e' e'') := sorry

@[simp] theorem trans_refl {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : local_equiv.trans e (local_equiv.refl β) = e := sorry

@[simp] theorem refl_trans {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : local_equiv.trans (local_equiv.refl α) e = e := sorry

theorem trans_refl_restr {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set β) : local_equiv.trans e (local_equiv.restr (local_equiv.refl β) s) = local_equiv.restr e (⇑e ⁻¹' s) := sorry

theorem trans_refl_restr' {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (s : set β) : local_equiv.trans e (local_equiv.restr (local_equiv.refl β) s) = local_equiv.restr e (source e ∩ ⇑e ⁻¹' s) := sorry

theorem restr_trans {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : local_equiv α β) (e' : local_equiv β γ) (s : set α) : local_equiv.trans (local_equiv.restr e s) e' = local_equiv.restr (local_equiv.trans e e') s := sorry

/-- `eq_on_source e e'` means that `e` and `e'` have the same source, and coincide there. Then `e`
and `e'` should really be considered the same local equiv. -/
def eq_on_source {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (e' : local_equiv α β) :=
  source e = source e' ∧ set.eq_on (⇑e) (⇑e') (source e)

/-- `eq_on_source` is an equivalence relation -/
protected instance eq_on_source_setoid {α : Type u_1} {β : Type u_2} : setoid (local_equiv α β) :=
  setoid.mk eq_on_source sorry

theorem eq_on_source_refl {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : e ≈ e :=
  setoid.refl e

/-- Two equivalent local equivs have the same source -/
theorem eq_on_source.source_eq {α : Type u_1} {β : Type u_2} {e : local_equiv α β} {e' : local_equiv α β} (h : e ≈ e') : source e = source e' :=
  and.left h

/-- Two equivalent local equivs coincide on the source -/
theorem eq_on_source.eq_on {α : Type u_1} {β : Type u_2} {e : local_equiv α β} {e' : local_equiv α β} (h : e ≈ e') : set.eq_on (⇑e) (⇑e') (source e) :=
  and.right h

/-- Two equivalent local equivs have the same target -/
theorem eq_on_source.target_eq {α : Type u_1} {β : Type u_2} {e : local_equiv α β} {e' : local_equiv α β} (h : e ≈ e') : target e = target e' := sorry

/-- If two local equivs are equivalent, so are their inverses. -/
theorem eq_on_source.symm' {α : Type u_1} {β : Type u_2} {e : local_equiv α β} {e' : local_equiv α β} (h : e ≈ e') : local_equiv.symm e ≈ local_equiv.symm e' := sorry

/-- Two equivalent local equivs have coinciding inverses on the target -/
theorem eq_on_source.symm_eq_on {α : Type u_1} {β : Type u_2} {e : local_equiv α β} {e' : local_equiv α β} (h : e ≈ e') : set.eq_on (⇑(local_equiv.symm e)) (⇑(local_equiv.symm e')) (target e) :=
  eq_on_source.eq_on (eq_on_source.symm' h)

/-- Composition of local equivs respects equivalence -/
theorem eq_on_source.trans' {α : Type u_1} {β : Type u_2} {γ : Type u_3} {e : local_equiv α β} {e' : local_equiv α β} {f : local_equiv β γ} {f' : local_equiv β γ} (he : e ≈ e') (hf : f ≈ f') : local_equiv.trans e f ≈ local_equiv.trans e' f' := sorry

/-- Restriction of local equivs respects equivalence -/
theorem eq_on_source.restr {α : Type u_1} {β : Type u_2} {e : local_equiv α β} {e' : local_equiv α β} (he : e ≈ e') (s : set α) : local_equiv.restr e s ≈ local_equiv.restr e' s := sorry

/-- Preimages are respected by equivalence -/
theorem eq_on_source.source_inter_preimage_eq {α : Type u_1} {β : Type u_2} {e : local_equiv α β} {e' : local_equiv α β} (he : e ≈ e') (s : set β) : source e ∩ ⇑e ⁻¹' s = source e' ∩ ⇑e' ⁻¹' s := sorry

/-- Composition of a local equiv and its inverse is equivalent to the restriction of the identity
to the source -/
theorem trans_self_symm {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : local_equiv.trans e (local_equiv.symm e) ≈ of_set (source e) := sorry

/-- Composition of the inverse of a local equiv and this local equiv is equivalent to the
restriction of the identity to the target -/
theorem trans_symm_self {α : Type u_1} {β : Type u_2} (e : local_equiv α β) : local_equiv.trans (local_equiv.symm e) e ≈ of_set (target e) :=
  trans_self_symm (local_equiv.symm e)

/-- Two equivalent local equivs are equal when the source and target are univ -/
theorem eq_of_eq_on_source_univ {α : Type u_1} {β : Type u_2} (e : local_equiv α β) (e' : local_equiv α β) (h : e ≈ e') (s : source e = set.univ) (t : target e = set.univ) : e = e' := sorry

/-- The product of two local equivs, as a local equiv on the product. -/
def prod {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} (e : local_equiv α β) (e' : local_equiv γ δ) : local_equiv (α × γ) (β × δ) :=
  mk (fun (p : α × γ) => (coe_fn e (prod.fst p), coe_fn e' (prod.snd p)))
    (fun (p : β × δ) => (coe_fn (local_equiv.symm e) (prod.fst p), coe_fn (local_equiv.symm e') (prod.snd p)))
    (set.prod (source e) (source e')) (set.prod (target e) (target e')) sorry sorry sorry sorry

@[simp] theorem prod_source {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} (e : local_equiv α β) (e' : local_equiv γ δ) : source (prod e e') = set.prod (source e) (source e') :=
  rfl

@[simp] theorem prod_target {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} (e : local_equiv α β) (e' : local_equiv γ δ) : target (prod e e') = set.prod (target e) (target e') :=
  rfl

@[simp] theorem prod_coe {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} (e : local_equiv α β) (e' : local_equiv γ δ) : ⇑(prod e e') = fun (p : α × γ) => (coe_fn e (prod.fst p), coe_fn e' (prod.snd p)) :=
  rfl

theorem prod_coe_symm {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} (e : local_equiv α β) (e' : local_equiv γ δ) : ⇑(local_equiv.symm (prod e e')) =
  fun (p : β × δ) => (coe_fn (local_equiv.symm e) (prod.fst p), coe_fn (local_equiv.symm e') (prod.snd p)) :=
  rfl

@[simp] theorem prod_symm {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} (e : local_equiv α β) (e' : local_equiv γ δ) : local_equiv.symm (prod e e') = prod (local_equiv.symm e) (local_equiv.symm e') := sorry

@[simp] theorem prod_trans {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} {η : Type u_5} {ε : Type u_6} (e : local_equiv α β) (f : local_equiv β γ) (e' : local_equiv δ η) (f' : local_equiv η ε) : local_equiv.trans (prod e e') (prod f f') = prod (local_equiv.trans e f) (local_equiv.trans e' f') := sorry

end local_equiv

namespace set

-- All arguments are explicit to avoid missing information in the pretty printer output

/-- A bijection between two sets `s : set α` and `t : set β` provides a local equivalence
between `α` and `β`. -/
@[simp] theorem bij_on.to_local_equiv_inv_fun {α : Type u_1} {β : Type u_2} [Nonempty α] (f : α → β) (s : set α) (t : set β) (hf : bij_on f s t) (b : β) : local_equiv.inv_fun (bij_on.to_local_equiv f s t hf) b = function.inv_fun_on f s b :=
  Eq.refl (local_equiv.inv_fun (bij_on.to_local_equiv f s t hf) b)

/-- A map injective on a subset of its domain provides a local equivalence. -/
@[simp] def inj_on.to_local_equiv {α : Type u_1} {β : Type u_2} [Nonempty α] (f : α → β) (s : set α) (hf : inj_on f s) : local_equiv α β :=
  bij_on.to_local_equiv f s (f '' s) (inj_on.bij_on_image hf)

end set

namespace equiv

/- equivs give rise to local_equiv. We set up simp lemmas to reduce most properties of the local
equiv to that of the equiv. -/

@[simp] theorem to_local_equiv_coe {α : Type u_1} {β : Type u_2} (e : α ≃ β) : ⇑(to_local_equiv e) = ⇑e :=
  rfl

@[simp] theorem to_local_equiv_symm_coe {α : Type u_1} {β : Type u_2} (e : α ≃ β) : ⇑(local_equiv.symm (to_local_equiv e)) = ⇑(equiv.symm e) :=
  rfl

@[simp] theorem to_local_equiv_source {α : Type u_1} {β : Type u_2} (e : α ≃ β) : local_equiv.source (to_local_equiv e) = set.univ :=
  rfl

@[simp] theorem to_local_equiv_target {α : Type u_1} {β : Type u_2} (e : α ≃ β) : local_equiv.target (to_local_equiv e) = set.univ :=
  rfl

@[simp] theorem refl_to_local_equiv {α : Type u_1} : to_local_equiv (equiv.refl α) = local_equiv.refl α :=
  rfl

@[simp] theorem symm_to_local_equiv {α : Type u_1} {β : Type u_2} (e : α ≃ β) : to_local_equiv (equiv.symm e) = local_equiv.symm (to_local_equiv e) :=
  rfl

@[simp] theorem trans_to_local_equiv {α : Type u_1} {β : Type u_2} {γ : Type u_3} (e : α ≃ β) (e' : β ≃ γ) : to_local_equiv (equiv.trans e e') = local_equiv.trans (to_local_equiv e) (to_local_equiv e') := sorry