/-
Copyright (c) 2017 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Stephen Morgan, Scott Morrison

! This file was ported from Lean 3 source module category_theory.opposites
! leanprover-community/mathlib commit 34ee86e6a59d911a8e4f89b68793ee7577ae79c7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.Equivalence

/-!
# Opposite categories

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

We provide a category instance on `Cᵒᵖ`.
The morphisms `X ⟶ Y` are defined to be the morphisms `unop Y ⟶ unop X` in `C`.

Here `Cᵒᵖ` is an irreducible typeclass synonym for `C`
(it is the same one used in the algebra library).

We also provide various mechanisms for constructing opposite morphisms, functors,
and natural transformations.

Unfortunately, because we do not have a definitional equality `op (op X) = X`,
there are quite a few variations that are needed in practice.
-/

universe v₁ v₂ u₁ u₂

-- morphism levels before object levels. See note [category_theory universes].
open Opposite

variable {C : Type u₁}

section Quiver

variable [Quiver.{v₁} C]

#print Quiver.Hom.op_inj /-
theorem Quiver.Hom.op_inj {X Y : C} :
    Function.Injective (Quiver.Hom.op : (X ⟶ Y) → (op Y ⟶ op X)) := fun _ _ H =>
  congr_arg Quiver.Hom.unop H
#align quiver.hom.op_inj Quiver.Hom.op_inj
-/

#print Quiver.Hom.unop_inj /-
theorem Quiver.Hom.unop_inj {X Y : Cᵒᵖ} :
    Function.Injective (Quiver.Hom.unop : (X ⟶ Y) → (unop Y ⟶ unop X)) := fun _ _ H =>
  congr_arg Quiver.Hom.op H
#align quiver.hom.unop_inj Quiver.Hom.unop_inj
-/

#print Quiver.Hom.unop_op /-
@[simp]
theorem Quiver.Hom.unop_op {X Y : C} (f : X ⟶ Y) : f.op.unop = f :=
  rfl
#align quiver.hom.unop_op Quiver.Hom.unop_op
-/

#print Quiver.Hom.op_unop /-
@[simp]
theorem Quiver.Hom.op_unop {X Y : Cᵒᵖ} (f : X ⟶ Y) : f.unop.op = f :=
  rfl
#align quiver.hom.op_unop Quiver.Hom.op_unop
-/

end Quiver

namespace CategoryTheory

variable [Category.{v₁} C]

#print CategoryTheory.Category.opposite /-
/-- The opposite category.

See <https://stacks.math.columbia.edu/tag/001M>.
-/
instance Category.opposite : Category.{v₁} Cᵒᵖ
    where
  comp _ _ _ f g := (g.unop ≫ f.unop).op
  id X := (𝟙 (unop X)).op
#align category_theory.category.opposite CategoryTheory.Category.opposite
-/

#print CategoryTheory.op_comp /-
@[simp]
theorem op_comp {X Y Z : C} {f : X ⟶ Y} {g : Y ⟶ Z} : (f ≫ g).op = g.op ≫ f.op :=
  rfl
#align category_theory.op_comp CategoryTheory.op_comp
-/

#print CategoryTheory.op_id /-
@[simp]
theorem op_id {X : C} : (𝟙 X).op = 𝟙 (op X) :=
  rfl
#align category_theory.op_id CategoryTheory.op_id
-/

#print CategoryTheory.unop_comp /-
@[simp]
theorem unop_comp {X Y Z : Cᵒᵖ} {f : X ⟶ Y} {g : Y ⟶ Z} : (f ≫ g).unop = g.unop ≫ f.unop :=
  rfl
#align category_theory.unop_comp CategoryTheory.unop_comp
-/

#print CategoryTheory.unop_id /-
@[simp]
theorem unop_id {X : Cᵒᵖ} : (𝟙 X).unop = 𝟙 (unop X) :=
  rfl
#align category_theory.unop_id CategoryTheory.unop_id
-/

#print CategoryTheory.unop_id_op /-
@[simp]
theorem unop_id_op {X : C} : (𝟙 (op X)).unop = 𝟙 X :=
  rfl
#align category_theory.unop_id_op CategoryTheory.unop_id_op
-/

#print CategoryTheory.op_id_unop /-
@[simp]
theorem op_id_unop {X : Cᵒᵖ} : (𝟙 (unop X)).op = 𝟙 X :=
  rfl
#align category_theory.op_id_unop CategoryTheory.op_id_unop
-/

section

variable (C)

#print CategoryTheory.opOp /-
/-- The functor from the double-opposite of a category to the underlying category. -/
@[simps]
def opOp : Cᵒᵖᵒᵖ ⥤ C where
  obj X := unop (unop X)
  map X Y f := f.unop.unop
#align category_theory.op_op CategoryTheory.opOp
-/

#print CategoryTheory.unopUnop /-
/-- The functor from a category to its double-opposite.  -/
@[simps]
def unopUnop : C ⥤ Cᵒᵖᵒᵖ where
  obj X := op (op X)
  map X Y f := f.op.op
#align category_theory.unop_unop CategoryTheory.unopUnop
-/

/- warning: category_theory.op_op_equivalence -> CategoryTheory.opOpEquivalence is a dubious translation:
lean 3 declaration is
  forall (C : Type.{u2}) [_inst_1 : CategoryTheory.Category.{u1, u2} C], CategoryTheory.Equivalence.{u1, u1, u2, u2} (Opposite.{succ u2} (Opposite.{succ u2} C)) (CategoryTheory.Category.opposite.{u1, u2} (Opposite.{succ u2} C) (CategoryTheory.Category.opposite.{u1, u2} C _inst_1)) C _inst_1
but is expected to have type
  forall (C : Type.{u2}) [_inst_1 : CategoryTheory.Category.{u1, u2} C], CategoryTheory.Equivalence.{u1, u1, u2, u2} (Opposite.{succ u2} (Opposite.{succ u2} C)) C (CategoryTheory.Category.opposite.{u1, u2} (Opposite.{succ u2} C) (CategoryTheory.Category.opposite.{u1, u2} C _inst_1)) _inst_1
Case conversion may be inaccurate. Consider using '#align category_theory.op_op_equivalence CategoryTheory.opOpEquivalenceₓ'. -/
/-- The double opposite category is equivalent to the original. -/
@[simps]
def opOpEquivalence : Cᵒᵖᵒᵖ ≌ C where
  Functor := opOp C
  inverse := unopUnop C
  unitIso := Iso.refl (𝟭 Cᵒᵖᵒᵖ)
  counitIso := Iso.refl (unopUnop C ⋙ opOp C)
#align category_theory.op_op_equivalence CategoryTheory.opOpEquivalence

end

#print CategoryTheory.isIso_op /-
/-- If `f` is an isomorphism, so is `f.op` -/
instance isIso_op {X Y : C} (f : X ⟶ Y) [IsIso f] : IsIso f.op :=
  ⟨⟨(inv f).op, ⟨Quiver.Hom.unop_inj (by tidy), Quiver.Hom.unop_inj (by tidy)⟩⟩⟩
#align category_theory.is_iso_op CategoryTheory.isIso_op
-/

#print CategoryTheory.isIso_of_op /-
/-- If `f.op` is an isomorphism `f` must be too.
(This cannot be an instance as it would immediately loop!)
-/
theorem isIso_of_op {X Y : C} (f : X ⟶ Y) [IsIso f.op] : IsIso f :=
  ⟨⟨(inv f.op).unop, ⟨Quiver.Hom.op_inj (by simp), Quiver.Hom.op_inj (by simp)⟩⟩⟩
#align category_theory.is_iso_of_op CategoryTheory.isIso_of_op
-/

#print CategoryTheory.isIso_op_iff /-
theorem isIso_op_iff {X Y : C} (f : X ⟶ Y) : IsIso f.op ↔ IsIso f :=
  ⟨fun hf => is_iso_of_op _, fun hf => inferInstance⟩
#align category_theory.is_iso_op_iff CategoryTheory.isIso_op_iff
-/

#print CategoryTheory.isIso_unop_iff /-
theorem isIso_unop_iff {X Y : Cᵒᵖ} (f : X ⟶ Y) : IsIso f.unop ↔ IsIso f := by
  rw [← is_iso_op_iff f.unop, Quiver.Hom.op_unop]
#align category_theory.is_iso_unop_iff CategoryTheory.isIso_unop_iff
-/

#print CategoryTheory.isIso_unop /-
instance isIso_unop {X Y : Cᵒᵖ} (f : X ⟶ Y) [IsIso f] : IsIso f.unop :=
  (isIso_unop_iff _).2 inferInstance
#align category_theory.is_iso_unop CategoryTheory.isIso_unop
-/

#print CategoryTheory.op_inv /-
@[simp]
theorem op_inv {X Y : C} (f : X ⟶ Y) [IsIso f] : (inv f).op = inv f.op :=
  by
  ext
  rw [← op_comp, is_iso.inv_hom_id, op_id]
#align category_theory.op_inv CategoryTheory.op_inv
-/

#print CategoryTheory.unop_inv /-
@[simp]
theorem unop_inv {X Y : Cᵒᵖ} (f : X ⟶ Y) [IsIso f] : (inv f).unop = inv f.unop :=
  by
  ext
  rw [← unop_comp, is_iso.inv_hom_id, unop_id]
#align category_theory.unop_inv CategoryTheory.unop_inv
-/

namespace Functor

section

variable {D : Type u₂} [Category.{v₂} D]

variable {C D}

#print CategoryTheory.Functor.op /-
/-- The opposite of a functor, i.e. considering a functor `F : C ⥤ D` as a functor `Cᵒᵖ ⥤ Dᵒᵖ`.
In informal mathematics no distinction is made between these.
-/
@[simps]
protected def op (F : C ⥤ D) : Cᵒᵖ ⥤ Dᵒᵖ
    where
  obj X := op (F.obj (unop X))
  map X Y f := (F.map f.unop).op
#align category_theory.functor.op CategoryTheory.Functor.op
-/

#print CategoryTheory.Functor.unop /-
/-- Given a functor `F : Cᵒᵖ ⥤ Dᵒᵖ` we can take the "unopposite" functor `F : C ⥤ D`.
In informal mathematics no distinction is made between these.
-/
@[simps]
protected def unop (F : Cᵒᵖ ⥤ Dᵒᵖ) : C ⥤ D
    where
  obj X := unop (F.obj (op X))
  map X Y f := (F.map f.op).unop
#align category_theory.functor.unop CategoryTheory.Functor.unop
-/

#print CategoryTheory.Functor.opUnopIso /-
/-- The isomorphism between `F.op.unop` and `F`. -/
@[simps]
def opUnopIso (F : C ⥤ D) : F.op.unop ≅ F :=
  NatIso.ofComponents (fun X => Iso.refl _) (by tidy)
#align category_theory.functor.op_unop_iso CategoryTheory.Functor.opUnopIso
-/

#print CategoryTheory.Functor.unopOpIso /-
/-- The isomorphism between `F.unop.op` and `F`. -/
@[simps]
def unopOpIso (F : Cᵒᵖ ⥤ Dᵒᵖ) : F.unop.op ≅ F :=
  NatIso.ofComponents (fun X => Iso.refl _) (by tidy)
#align category_theory.functor.unop_op_iso CategoryTheory.Functor.unopOpIso
-/

variable (C D)

#print CategoryTheory.Functor.opHom /-
/-- Taking the opposite of a functor is functorial.
-/
@[simps]
def opHom : (C ⥤ D)ᵒᵖ ⥤ Cᵒᵖ ⥤ Dᵒᵖ where
  obj F := (unop F).op
  map F G α :=
    { app := fun X => (α.unop.app (unop X)).op
      naturality' := fun X Y f => Quiver.Hom.unop_inj (α.unop.naturality f.unop).symm }
#align category_theory.functor.op_hom CategoryTheory.Functor.opHom
-/

#print CategoryTheory.Functor.opInv /-
/-- Take the "unopposite" of a functor is functorial.
-/
@[simps]
def opInv : (Cᵒᵖ ⥤ Dᵒᵖ) ⥤ (C ⥤ D)ᵒᵖ where
  obj F := op F.unop
  map F G α :=
    Quiver.Hom.op
      { app := fun X => (α.app (op X)).unop
        naturality' := fun X Y f => Quiver.Hom.op_inj <| (α.naturality f.op).symm }
#align category_theory.functor.op_inv CategoryTheory.Functor.opInv
-/

variable {C D}

#print CategoryTheory.Functor.leftOp /-
/--
Another variant of the opposite of functor, turning a functor `C ⥤ Dᵒᵖ` into a functor `Cᵒᵖ ⥤ D`.
In informal mathematics no distinction is made.
-/
@[simps]
protected def leftOp (F : C ⥤ Dᵒᵖ) : Cᵒᵖ ⥤ D
    where
  obj X := unop (F.obj (unop X))
  map X Y f := (F.map f.unop).unop
#align category_theory.functor.left_op CategoryTheory.Functor.leftOp
-/

#print CategoryTheory.Functor.rightOp /-
/--
Another variant of the opposite of functor, turning a functor `Cᵒᵖ ⥤ D` into a functor `C ⥤ Dᵒᵖ`.
In informal mathematics no distinction is made.
-/
@[simps]
protected def rightOp (F : Cᵒᵖ ⥤ D) : C ⥤ Dᵒᵖ
    where
  obj X := op (F.obj (op X))
  map X Y f := (F.map f.op).op
#align category_theory.functor.right_op CategoryTheory.Functor.rightOp
-/

instance {F : C ⥤ D} [Full F] : Full F.op where preimage X Y f := (F.preimage f.unop).op

instance {F : C ⥤ D} [Faithful F] : Faithful F.op
    where map_injective' X Y f g h :=
    Quiver.Hom.unop_inj <| by simpa using map_injective F (Quiver.Hom.op_inj h)

#print CategoryTheory.Functor.rightOp_faithful /-
/-- If F is faithful then the right_op of F is also faithful. -/
instance rightOp_faithful {F : Cᵒᵖ ⥤ D} [Faithful F] : Faithful F.rightOp
    where map_injective' X Y f g h := Quiver.Hom.op_inj (map_injective F (Quiver.Hom.op_inj h))
#align category_theory.functor.right_op_faithful CategoryTheory.Functor.rightOp_faithful
-/

#print CategoryTheory.Functor.leftOp_faithful /-
/-- If F is faithful then the left_op of F is also faithful. -/
instance leftOp_faithful {F : C ⥤ Dᵒᵖ} [Faithful F] : Faithful F.leftOp
    where map_injective' X Y f g h := Quiver.Hom.unop_inj (map_injective F (Quiver.Hom.unop_inj h))
#align category_theory.functor.left_op_faithful CategoryTheory.Functor.leftOp_faithful
-/

#print CategoryTheory.Functor.leftOpRightOpIso /-
/-- The isomorphism between `F.left_op.right_op` and `F`. -/
@[simps]
def leftOpRightOpIso (F : C ⥤ Dᵒᵖ) : F.leftOp.rightOp ≅ F :=
  NatIso.ofComponents (fun X => Iso.refl _) (by tidy)
#align category_theory.functor.left_op_right_op_iso CategoryTheory.Functor.leftOpRightOpIso
-/

#print CategoryTheory.Functor.rightOpLeftOpIso /-
/-- The isomorphism between `F.right_op.left_op` and `F`. -/
@[simps]
def rightOpLeftOpIso (F : Cᵒᵖ ⥤ D) : F.rightOp.leftOp ≅ F :=
  NatIso.ofComponents (fun X => Iso.refl _) (by tidy)
#align category_theory.functor.right_op_left_op_iso CategoryTheory.Functor.rightOpLeftOpIso
-/

#print CategoryTheory.Functor.rightOp_leftOp_eq /-
/-- Whenever possible, it is advisable to use the isomorphism `right_op_left_op_iso`
instead of this equality of functors. -/
theorem rightOp_leftOp_eq (F : Cᵒᵖ ⥤ D) : F.rightOp.leftOp = F :=
  by
  cases F
  rfl
#align category_theory.functor.right_op_left_op_eq CategoryTheory.Functor.rightOp_leftOp_eq
-/

end

end Functor

namespace NatTrans

variable {D : Type u₂} [Category.{v₂} D]

section

variable {F G : C ⥤ D}

#print CategoryTheory.NatTrans.op /-
/-- The opposite of a natural transformation. -/
@[simps]
protected def op (α : F ⟶ G) : G.op ⟶ F.op
    where
  app X := (α.app (unop X)).op
  naturality' X Y f := Quiver.Hom.unop_inj (by simp)
#align category_theory.nat_trans.op CategoryTheory.NatTrans.op
-/

#print CategoryTheory.NatTrans.op_id /-
@[simp]
theorem op_id (F : C ⥤ D) : NatTrans.op (𝟙 F) = 𝟙 F.op :=
  rfl
#align category_theory.nat_trans.op_id CategoryTheory.NatTrans.op_id
-/

#print CategoryTheory.NatTrans.unop /-
/-- The "unopposite" of a natural transformation. -/
@[simps]
protected def unop {F G : Cᵒᵖ ⥤ Dᵒᵖ} (α : F ⟶ G) : G.unop ⟶ F.unop
    where
  app X := (α.app (op X)).unop
  naturality' X Y f := Quiver.Hom.op_inj (by simp)
#align category_theory.nat_trans.unop CategoryTheory.NatTrans.unop
-/

#print CategoryTheory.NatTrans.unop_id /-
@[simp]
theorem unop_id (F : Cᵒᵖ ⥤ Dᵒᵖ) : NatTrans.unop (𝟙 F) = 𝟙 F.unop :=
  rfl
#align category_theory.nat_trans.unop_id CategoryTheory.NatTrans.unop_id
-/

#print CategoryTheory.NatTrans.removeOp /-
/-- Given a natural transformation `α : F.op ⟶ G.op`,
we can take the "unopposite" of each component obtaining a natural transformation `G ⟶ F`.
-/
@[simps]
protected def removeOp (α : F.op ⟶ G.op) : G ⟶ F
    where
  app X := (α.app (op X)).unop
  naturality' X Y f :=
    Quiver.Hom.op_inj <| by simpa only [functor.op_map] using (α.naturality f.op).symm
#align category_theory.nat_trans.remove_op CategoryTheory.NatTrans.removeOp
-/

#print CategoryTheory.NatTrans.removeOp_id /-
@[simp]
theorem removeOp_id (F : C ⥤ D) : NatTrans.removeOp (𝟙 F.op) = 𝟙 F :=
  rfl
#align category_theory.nat_trans.remove_op_id CategoryTheory.NatTrans.removeOp_id
-/

#print CategoryTheory.NatTrans.removeUnop /-
/-- Given a natural transformation `α : F.unop ⟶ G.unop`, we can take the opposite of each
component obtaining a natural transformation `G ⟶ F`. -/
@[simps]
protected def removeUnop {F G : Cᵒᵖ ⥤ Dᵒᵖ} (α : F.unop ⟶ G.unop) : G ⟶ F
    where
  app X := (α.app (unop X)).op
  naturality' X Y f :=
    Quiver.Hom.unop_inj <| by simpa only [functor.unop_map] using (α.naturality f.unop).symm
#align category_theory.nat_trans.remove_unop CategoryTheory.NatTrans.removeUnop
-/

#print CategoryTheory.NatTrans.removeUnop_id /-
@[simp]
theorem removeUnop_id (F : Cᵒᵖ ⥤ Dᵒᵖ) : NatTrans.removeUnop (𝟙 F.unop) = 𝟙 F :=
  rfl
#align category_theory.nat_trans.remove_unop_id CategoryTheory.NatTrans.removeUnop_id
-/

end

section

variable {F G H : C ⥤ Dᵒᵖ}

#print CategoryTheory.NatTrans.leftOp /-
/-- Given a natural transformation `α : F ⟶ G`, for `F G : C ⥤ Dᵒᵖ`,
taking `unop` of each component gives a natural transformation `G.left_op ⟶ F.left_op`.
-/
@[simps]
protected def leftOp (α : F ⟶ G) : G.leftOp ⟶ F.leftOp
    where
  app X := (α.app (unop X)).unop
  naturality' X Y f := Quiver.Hom.op_inj (by simp)
#align category_theory.nat_trans.left_op CategoryTheory.NatTrans.leftOp
-/

#print CategoryTheory.NatTrans.leftOp_id /-
@[simp]
theorem leftOp_id : (𝟙 F : F ⟶ F).leftOp = 𝟙 F.leftOp :=
  rfl
#align category_theory.nat_trans.left_op_id CategoryTheory.NatTrans.leftOp_id
-/

#print CategoryTheory.NatTrans.leftOp_comp /-
@[simp]
theorem leftOp_comp (α : F ⟶ G) (β : G ⟶ H) : (α ≫ β).leftOp = β.leftOp ≫ α.leftOp :=
  rfl
#align category_theory.nat_trans.left_op_comp CategoryTheory.NatTrans.leftOp_comp
-/

#print CategoryTheory.NatTrans.removeLeftOp /-
/-- Given a natural transformation `α : F.left_op ⟶ G.left_op`, for `F G : C ⥤ Dᵒᵖ`,
taking `op` of each component gives a natural transformation `G ⟶ F`.
-/
@[simps]
protected def removeLeftOp (α : F.leftOp ⟶ G.leftOp) : G ⟶ F
    where
  app X := (α.app (op X)).op
  naturality' X Y f :=
    Quiver.Hom.unop_inj <| by simpa only [functor.left_op_map] using (α.naturality f.op).symm
#align category_theory.nat_trans.remove_left_op CategoryTheory.NatTrans.removeLeftOp
-/

#print CategoryTheory.NatTrans.removeLeftOp_id /-
@[simp]
theorem removeLeftOp_id : NatTrans.removeLeftOp (𝟙 F.leftOp) = 𝟙 F :=
  rfl
#align category_theory.nat_trans.remove_left_op_id CategoryTheory.NatTrans.removeLeftOp_id
-/

end

section

variable {F G H : Cᵒᵖ ⥤ D}

#print CategoryTheory.NatTrans.rightOp /-
/-- Given a natural transformation `α : F ⟶ G`, for `F G : Cᵒᵖ ⥤ D`,
taking `op` of each component gives a natural transformation `G.right_op ⟶ F.right_op`.
-/
@[simps]
protected def rightOp (α : F ⟶ G) : G.rightOp ⟶ F.rightOp
    where
  app X := (α.app _).op
  naturality' X Y f := Quiver.Hom.unop_inj (by simp)
#align category_theory.nat_trans.right_op CategoryTheory.NatTrans.rightOp
-/

#print CategoryTheory.NatTrans.rightOp_id /-
@[simp]
theorem rightOp_id : (𝟙 F : F ⟶ F).rightOp = 𝟙 F.rightOp :=
  rfl
#align category_theory.nat_trans.right_op_id CategoryTheory.NatTrans.rightOp_id
-/

#print CategoryTheory.NatTrans.rightOp_comp /-
@[simp]
theorem rightOp_comp (α : F ⟶ G) (β : G ⟶ H) : (α ≫ β).rightOp = β.rightOp ≫ α.rightOp :=
  rfl
#align category_theory.nat_trans.right_op_comp CategoryTheory.NatTrans.rightOp_comp
-/

#print CategoryTheory.NatTrans.removeRightOp /-
/-- Given a natural transformation `α : F.right_op ⟶ G.right_op`, for `F G : Cᵒᵖ ⥤ D`,
taking `unop` of each component gives a natural transformation `G ⟶ F`.
-/
@[simps]
protected def removeRightOp (α : F.rightOp ⟶ G.rightOp) : G ⟶ F
    where
  app X := (α.app X.unop).unop
  naturality' X Y f :=
    Quiver.Hom.op_inj <| by simpa only [functor.right_op_map] using (α.naturality f.unop).symm
#align category_theory.nat_trans.remove_right_op CategoryTheory.NatTrans.removeRightOp
-/

#print CategoryTheory.NatTrans.removeRightOp_id /-
@[simp]
theorem removeRightOp_id : NatTrans.removeRightOp (𝟙 F.rightOp) = 𝟙 F :=
  rfl
#align category_theory.nat_trans.remove_right_op_id CategoryTheory.NatTrans.removeRightOp_id
-/

end

end NatTrans

namespace Iso

variable {X Y : C}

#print CategoryTheory.Iso.op /-
/-- The opposite isomorphism.
-/
@[simps]
protected def op (α : X ≅ Y) : op Y ≅ op X
    where
  Hom := α.Hom.op
  inv := α.inv.op
  hom_inv_id' := Quiver.Hom.unop_inj α.inv_hom_id
  inv_hom_id' := Quiver.Hom.unop_inj α.hom_inv_id
#align category_theory.iso.op CategoryTheory.Iso.op
-/

#print CategoryTheory.Iso.unop /-
/-- The isomorphism obtained from an isomorphism in the opposite category. -/
@[simps]
def unop {X Y : Cᵒᵖ} (f : X ≅ Y) : Y.unop ≅ X.unop
    where
  Hom := f.Hom.unop
  inv := f.inv.unop
  hom_inv_id' := by simp only [← unop_comp, f.inv_hom_id, unop_id]
  inv_hom_id' := by simp only [← unop_comp, f.hom_inv_id, unop_id]
#align category_theory.iso.unop CategoryTheory.Iso.unop
-/

#print CategoryTheory.Iso.unop_op /-
@[simp]
theorem unop_op {X Y : Cᵒᵖ} (f : X ≅ Y) : f.unop.op = f := by ext <;> rfl
#align category_theory.iso.unop_op CategoryTheory.Iso.unop_op
-/

#print CategoryTheory.Iso.op_unop /-
@[simp]
theorem op_unop {X Y : C} (f : X ≅ Y) : f.op.unop = f := by ext <;> rfl
#align category_theory.iso.op_unop CategoryTheory.Iso.op_unop
-/

end Iso

namespace NatIso

variable {D : Type u₂} [Category.{v₂} D]

variable {F G : C ⥤ D}

#print CategoryTheory.NatIso.op /-
/-- The natural isomorphism between opposite functors `G.op ≅ F.op` induced by a natural
isomorphism between the original functors `F ≅ G`. -/
@[simps]
protected def op (α : F ≅ G) : G.op ≅ F.op
    where
  Hom := NatTrans.op α.Hom
  inv := NatTrans.op α.inv
  hom_inv_id' := by ext; dsimp; rw [← op_comp]; rw [α.inv_hom_id_app]; rfl
  inv_hom_id' := by ext; dsimp; rw [← op_comp]; rw [α.hom_inv_id_app]; rfl
#align category_theory.nat_iso.op CategoryTheory.NatIso.op
-/

#print CategoryTheory.NatIso.removeOp /-
/-- The natural isomorphism between functors `G ≅ F` induced by a natural isomorphism
between the opposite functors `F.op ≅ G.op`. -/
@[simps]
protected def removeOp (α : F.op ≅ G.op) : G ≅ F
    where
  Hom := NatTrans.removeOp α.Hom
  inv := NatTrans.removeOp α.inv
  hom_inv_id' := by ext; dsimp; rw [← unop_comp]; rw [α.inv_hom_id_app]; rfl
  inv_hom_id' := by ext; dsimp; rw [← unop_comp]; rw [α.hom_inv_id_app]; rfl
#align category_theory.nat_iso.remove_op CategoryTheory.NatIso.removeOp
-/

#print CategoryTheory.NatIso.unop /-
/-- The natural isomorphism between functors `G.unop ≅ F.unop` induced by a natural isomorphism
between the original functors `F ≅ G`. -/
@[simps]
protected def unop {F G : Cᵒᵖ ⥤ Dᵒᵖ} (α : F ≅ G) : G.unop ≅ F.unop
    where
  Hom := NatTrans.unop α.Hom
  inv := NatTrans.unop α.inv
  hom_inv_id' := by ext; dsimp; rw [← unop_comp]; rw [α.inv_hom_id_app]; rfl
  inv_hom_id' := by ext; dsimp; rw [← unop_comp]; rw [α.hom_inv_id_app]; rfl
#align category_theory.nat_iso.unop CategoryTheory.NatIso.unop
-/

end NatIso

namespace Equivalence

variable {D : Type u₂} [Category.{v₂} D]

/- warning: category_theory.equivalence.op -> CategoryTheory.Equivalence.op is a dubious translation:
lean 3 declaration is
  forall {C : Type.{u3}} [_inst_1 : CategoryTheory.Category.{u1, u3} C] {D : Type.{u4}} [_inst_2 : CategoryTheory.Category.{u2, u4} D], (CategoryTheory.Equivalence.{u1, u2, u3, u4} C _inst_1 D _inst_2) -> (CategoryTheory.Equivalence.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2))
but is expected to have type
  forall {C : Type.{u3}} [_inst_1 : CategoryTheory.Category.{u1, u3} C] {D : Type.{u4}} [_inst_2 : CategoryTheory.Category.{u2, u4} D], (CategoryTheory.Equivalence.{u1, u2, u3, u4} C D _inst_1 _inst_2) -> (CategoryTheory.Equivalence.{u1, u2, u3, u4} (Opposite.{succ u3} C) (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2))
Case conversion may be inaccurate. Consider using '#align category_theory.equivalence.op CategoryTheory.Equivalence.opₓ'. -/
/-- An equivalence between categories gives an equivalence between the opposite categories.
-/
@[simps]
def op (e : C ≌ D) : Cᵒᵖ ≌ Dᵒᵖ where
  Functor := e.Functor.op
  inverse := e.inverse.op
  unitIso := (NatIso.op e.unitIso).symm
  counitIso := (NatIso.op e.counitIso).symm
  functor_unitIso_comp' X := by
    apply Quiver.Hom.unop_inj
    dsimp
    simp
#align category_theory.equivalence.op CategoryTheory.Equivalence.op

/- warning: category_theory.equivalence.unop -> CategoryTheory.Equivalence.unop is a dubious translation:
lean 3 declaration is
  forall {C : Type.{u3}} [_inst_1 : CategoryTheory.Category.{u1, u3} C] {D : Type.{u4}} [_inst_2 : CategoryTheory.Category.{u2, u4} D], (CategoryTheory.Equivalence.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2)) -> (CategoryTheory.Equivalence.{u1, u2, u3, u4} C _inst_1 D _inst_2)
but is expected to have type
  forall {C : Type.{u3}} [_inst_1 : CategoryTheory.Category.{u1, u3} C] {D : Type.{u4}} [_inst_2 : CategoryTheory.Category.{u2, u4} D], (CategoryTheory.Equivalence.{u1, u2, u3, u4} (Opposite.{succ u3} C) (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2)) -> (CategoryTheory.Equivalence.{u1, u2, u3, u4} C D _inst_1 _inst_2)
Case conversion may be inaccurate. Consider using '#align category_theory.equivalence.unop CategoryTheory.Equivalence.unopₓ'. -/
/-- An equivalence between opposite categories gives an equivalence between the original categories.
-/
@[simps]
def unop (e : Cᵒᵖ ≌ Dᵒᵖ) : C ≌ D where
  Functor := e.Functor.unop
  inverse := e.inverse.unop
  unitIso := (NatIso.unop e.unitIso).symm
  counitIso := (NatIso.unop e.counitIso).symm
  functor_unitIso_comp' X := by
    apply Quiver.Hom.op_inj
    dsimp
    simp
#align category_theory.equivalence.unop CategoryTheory.Equivalence.unop

end Equivalence

#print CategoryTheory.opEquiv /-
/-- The equivalence between arrows of the form `A ⟶ B` and `B.unop ⟶ A.unop`. Useful for building
adjunctions.
Note that this (definitionally) gives variants
```
def op_equiv' (A : C) (B : Cᵒᵖ) : (opposite.op A ⟶ B) ≃ (B.unop ⟶ A) :=
op_equiv _ _

def op_equiv'' (A : Cᵒᵖ) (B : C) : (A ⟶ opposite.op B) ≃ (B ⟶ A.unop) :=
op_equiv _ _

def op_equiv''' (A B : C) : (opposite.op A ⟶ opposite.op B) ≃ (B ⟶ A) :=
op_equiv _ _
```
-/
@[simps]
def opEquiv (A B : Cᵒᵖ) : (A ⟶ B) ≃ (B.unop ⟶ A.unop)
    where
  toFun f := f.unop
  invFun g := g.op
  left_inv _ := rfl
  right_inv _ := rfl
#align category_theory.op_equiv CategoryTheory.opEquiv
-/

#print CategoryTheory.subsingleton_of_unop /-
instance subsingleton_of_unop (A B : Cᵒᵖ) [Subsingleton (unop B ⟶ unop A)] : Subsingleton (A ⟶ B) :=
  (opEquiv A B).Subsingleton
#align category_theory.subsingleton_of_unop CategoryTheory.subsingleton_of_unop
-/

#print CategoryTheory.decidableEqOfUnop /-
instance decidableEqOfUnop (A B : Cᵒᵖ) [DecidableEq (unop B ⟶ unop A)] : DecidableEq (A ⟶ B) :=
  (opEquiv A B).DecidableEq
#align category_theory.decidable_eq_of_unop CategoryTheory.decidableEqOfUnop
-/

#print CategoryTheory.isoOpEquiv /-
/-- The equivalence between isomorphisms of the form `A ≅ B` and `B.unop ≅ A.unop`.

Note this is definitionally the same as the other three variants:
* `(opposite.op A ≅ B) ≃ (B.unop ≅ A)`
* `(A ≅ opposite.op B) ≃ (B ≅ A.unop)`
* `(opposite.op A ≅ opposite.op B) ≃ (B ≅ A)`
-/
@[simps]
def isoOpEquiv (A B : Cᵒᵖ) : (A ≅ B) ≃ (B.unop ≅ A.unop)
    where
  toFun f := f.unop
  invFun g := g.op
  left_inv _ := by
    ext
    rfl
  right_inv _ := by
    ext
    rfl
#align category_theory.iso_op_equiv CategoryTheory.isoOpEquiv
-/

namespace Functor

variable (C)

variable (D : Type u₂) [Category.{v₂} D]

/- warning: category_theory.functor.op_unop_equiv -> CategoryTheory.Functor.opUnopEquiv is a dubious translation:
lean 3 declaration is
  forall (C : Type.{u3}) [_inst_1 : CategoryTheory.Category.{u1, u3} C] (D : Type.{u4}) [_inst_2 : CategoryTheory.Category.{u2, u4} D], CategoryTheory.Equivalence.{max u3 u2, max u3 u2, max u1 u2 u3 u4, max u1 u2 u3 u4} (Opposite.{succ (max u1 u2 u3 u4)} (CategoryTheory.Functor.{u1, u2, u3, u4} C _inst_1 D _inst_2)) (CategoryTheory.Category.opposite.{max u3 u2, max u1 u2 u3 u4} (CategoryTheory.Functor.{u1, u2, u3, u4} C _inst_1 D _inst_2) (CategoryTheory.Functor.category.{u1, u2, u3, u4} C _inst_1 D _inst_2)) (CategoryTheory.Functor.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2)) (CategoryTheory.Functor.category.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2))
but is expected to have type
  forall (C : Type.{u3}) [_inst_1 : CategoryTheory.Category.{u1, u3} C] (D : Type.{u4}) [_inst_2 : CategoryTheory.Category.{u2, u4} D], CategoryTheory.Equivalence.{max u3 u2, max u3 u2, max (max (max u4 u3) u2) u1, max (max (max u4 u3) u2) u1} (Opposite.{succ (max (max (max u4 u3) u2) u1)} (CategoryTheory.Functor.{u1, u2, u3, u4} C _inst_1 D _inst_2)) (CategoryTheory.Functor.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2)) (CategoryTheory.Category.opposite.{max u3 u2, max (max (max u3 u4) u1) u2} (CategoryTheory.Functor.{u1, u2, u3, u4} C _inst_1 D _inst_2) (CategoryTheory.Functor.category.{u1, u2, u3, u4} C _inst_1 D _inst_2)) (CategoryTheory.Functor.category.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2))
Case conversion may be inaccurate. Consider using '#align category_theory.functor.op_unop_equiv CategoryTheory.Functor.opUnopEquivₓ'. -/
/-- The equivalence of functor categories induced by `op` and `unop`.
-/
@[simps]
def opUnopEquiv : (C ⥤ D)ᵒᵖ ≌ Cᵒᵖ ⥤ Dᵒᵖ
    where
  Functor := opHom _ _
  inverse := opInv _ _
  unitIso :=
    NatIso.ofComponents (fun F => F.unop.opUnopIso.op)
      (by
        intro F G f
        dsimp [op_unop_iso]
        rw [show f = f.unop.op by simp, ← op_comp, ← op_comp]
        congr 1
        tidy)
  counitIso := NatIso.ofComponents (fun F => F.unopOpIso) (by tidy)
#align category_theory.functor.op_unop_equiv CategoryTheory.Functor.opUnopEquiv

/- warning: category_theory.functor.left_op_right_op_equiv -> CategoryTheory.Functor.leftOpRightOpEquiv is a dubious translation:
lean 3 declaration is
  forall (C : Type.{u3}) [_inst_1 : CategoryTheory.Category.{u1, u3} C] (D : Type.{u4}) [_inst_2 : CategoryTheory.Category.{u2, u4} D], CategoryTheory.Equivalence.{max u3 u2, max u3 u2, max u1 u2 u3 u4, max u1 u2 u3 u4} (Opposite.{succ (max u1 u2 u3 u4)} (CategoryTheory.Functor.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) D _inst_2)) (CategoryTheory.Category.opposite.{max u3 u2, max u1 u2 u3 u4} (CategoryTheory.Functor.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) D _inst_2) (CategoryTheory.Functor.category.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) D _inst_2)) (CategoryTheory.Functor.{u1, u2, u3, u4} C _inst_1 (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2)) (CategoryTheory.Functor.category.{u1, u2, u3, u4} C _inst_1 (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2))
but is expected to have type
  forall (C : Type.{u3}) [_inst_1 : CategoryTheory.Category.{u1, u3} C] (D : Type.{u4}) [_inst_2 : CategoryTheory.Category.{u2, u4} D], CategoryTheory.Equivalence.{max u3 u2, max u3 u2, max (max (max u4 u3) u2) u1, max (max (max u4 u3) u2) u1} (Opposite.{succ (max (max (max u4 u3) u2) u1)} (CategoryTheory.Functor.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) D _inst_2)) (CategoryTheory.Functor.{u1, u2, u3, u4} C _inst_1 (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2)) (CategoryTheory.Category.opposite.{max u3 u2, max (max (max u3 u4) u1) u2} (CategoryTheory.Functor.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) D _inst_2) (CategoryTheory.Functor.category.{u1, u2, u3, u4} (Opposite.{succ u3} C) (CategoryTheory.Category.opposite.{u1, u3} C _inst_1) D _inst_2)) (CategoryTheory.Functor.category.{u1, u2, u3, u4} C _inst_1 (Opposite.{succ u4} D) (CategoryTheory.Category.opposite.{u2, u4} D _inst_2))
Case conversion may be inaccurate. Consider using '#align category_theory.functor.left_op_right_op_equiv CategoryTheory.Functor.leftOpRightOpEquivₓ'. -/
/-- The equivalence of functor categories induced by `left_op` and `right_op`.
-/
@[simps]
def leftOpRightOpEquiv : (Cᵒᵖ ⥤ D)ᵒᵖ ≌ C ⥤ Dᵒᵖ
    where
  Functor :=
    { obj := fun F => F.unop.rightOp
      map := fun F G η => η.unop.rightOp }
  inverse :=
    { obj := fun F => op F.leftOp
      map := fun F G η => η.leftOp.op }
  unitIso :=
    NatIso.ofComponents (fun F => F.unop.rightOpLeftOpIso.op)
      (by
        intro F G η
        dsimp
        rw [show η = η.unop.op by simp, ← op_comp, ← op_comp]
        congr 1
        tidy)
  counitIso := NatIso.ofComponents (fun F => F.leftOpRightOpIso) (by tidy)
#align category_theory.functor.left_op_right_op_equiv CategoryTheory.Functor.leftOpRightOpEquiv

end Functor

end CategoryTheory


import .finsum_more prelim.single data.nat.parity data.zmod.basic data.zmod.parity

open_locale classical big_operators
noncomputable theory

universes u v w

variables {α : Type*} {β : Type*}

open set

def fincard (s : set α) : ℕ := ∑ᶠ x in s, 1

def fincard_t (α : Type*) := fincard (set.univ : set α)

lemma fincard_def (s : set α) :
  fincard s = ∑ᶠ x in s, 1 :=
rfl

lemma fincard_t_def (α : Type*) :
  fincard_t α = fincard (set.univ : set α) :=
rfl

lemma fincard_t_eq_sum_ones (α : Type*) :
  fincard_t α = ∑ᶠ (x : α), 1 :=
by rw [fincard_t, fincard_def, finsum_eq_finsum_in_univ]

lemma fincard_eq_finset_card' (s : set α) (hs : s.finite) :
  fincard s = finset.card (hs.to_finset) :=
begin
  convert (finset.card_eq_sum_ones (hs.to_finset)).symm,
  exact finsum_in_eq_finset_sum''' (1 : α → ℕ) hs,
end

lemma fincard_eq_finset_card (s : set α)[fintype α]:
  fincard s = s.to_finset.card :=
begin
  convert (finset.card_eq_sum_ones (s.to_finset)).symm,
  exact finsum_in_eq_finset_sum' (1 : α → ℕ) _,
end

lemma fincard_t_eq_fintype_card [fintype α]:
  fincard_t α = fintype.card α :=
by {rw [fincard_t_def, fincard_eq_finset_card, ← finset.card_univ, ← to_finset_univ], congr', }

lemma fincard_t_subtype_eq_fincard (s : set α) :
  fincard_t s = fincard s :=
by {rw [fincard_t_eq_sum_ones], exact finsum_subtype_eq_finsum_in (1) s}

@[simp] lemma support_const [has_zero β] {b : β} (hb : b ≠ 0) :
  function.support (λ x : α, b) = univ :=
by {ext, simp [hb], }

@[simp] lemma support_one :
  function.support (1 : α → ℕ) = univ :=
support_const (by simp : 1 ≠ 0)

lemma fincard_of_infinite {s : set α} (hs : s.infinite) :
  fincard s = 0 :=
by {rw [fincard, finsum_in_eq_zero_of_infinite], simpa using hs,}

lemma finite_of_fincard_pos {s : set α} (hs : 0 < fincard s) :
  s.finite :=
by_contra (λ hn, by {rw fincard_of_infinite hn at hs, exact lt_irrefl _ hs, })

@[simp] lemma fincard_empty :
  fincard (∅ : set α) = 0 :=
by simp [fincard]

lemma fincard_eq_zero_iff_empty {s : set α} (hs : s.finite) :
  fincard s = 0 ↔ s = ∅ :=
begin
  rw [fincard_def, finsum_in_eq_zero_iff _],
    swap, simpa using hs,
  exact ⟨λ h, by {ext, simp at *, tauto,}, λ h, by {rw h, simp,}⟩,
end

lemma nonempty_of_fincard_pos {s : set α} (hs : 0 < fincard s):
  s.nonempty :=
begin
  by_contra hn,
  rw [← ne_empty_iff_nonempty, not_not] at hn,
  rw [hn, fincard_empty] at hs,
  exact lt_irrefl _ hs,
end

lemma fincard_insert {s : set α} {e : α} (hes : e ∉ s) (hs : s.finite) :
  fincard (insert e s) = 1 + fincard s :=
finsum_in_insert _ hes hs

lemma fincard_remove {s : set α} { e : α} (hes : e ∈ s) (hs : s.finite) :
  fincard s = 1 + fincard (s \ {e}) :=
begin
  convert fincard_insert (nonmem_removal s e) (finite.diff hs _),
  rw [remove_insert, insert_eq_of_mem hes],
end

@[simp] lemma fincard_t_fin (n : ℕ) :
  fincard_t (fin n) = n :=
by simp [fincard_t_eq_fintype_card]

lemma fincard_t_eq_iff_equiv [fintype α] [fintype β]:
  fincard_t α = fincard_t β ↔ nonempty (α ≃ β) :=
by simp_rw [fincard_t_eq_fintype_card, fintype.card_eq]

lemma fincard_t_eq_iff_fin_equiv [fintype α] {n : ℕ} :
  fincard_t α = n ↔ nonempty (α ≃ fin n) :=
by {nth_rewrite 0 ← fincard_t_fin n, apply fincard_t_eq_iff_equiv}

lemma equiv_fin_fincard_t [fintype α]:
  nonempty (α ≃ fin (fincard_t α)) :=
fincard_t_eq_iff_fin_equiv.mp rfl

/-- chooses a bijection between α and fin (fincard α) -/
def choose_fin_bij [fintype α]:
  α ≃ fin (fincard_t α) :=
classical.choice equiv_fin_fincard_t

@[simp] lemma fincard_singleton (e : α) :
  fincard ({e} : set α) = 1 :=
by simp [fincard]

lemma fincard_modular {s t : set α} (hs : s.finite) (ht : t.finite) :
  fincard (s ∪ t) + fincard (s ∩ t) = fincard s + fincard t :=
by simp [fincard, finsum_in_union_inter hs ht]

lemma fincard_nonneg (s : set α) : 0 ≤ fincard s :=
nonneg_of_finsum_nonneg (λ i, by {split_ifs; simp})

lemma fincard_img_emb (f : α ↪ β) (s : set α) :
  fincard (f '' s) = fincard s :=
begin
  by_cases hs : s.finite,
  { rw [fincard_def, fincard_def, finsum_in_image' hs],
    exact (set.inj_on_of_injective f.inj' _)},
  rw [fincard_of_infinite, fincard_of_infinite], assumption,
  rw set.infinite_image_iff, assumption,
  exact (set.inj_on_of_injective f.inj' _),
end

lemma fincard_of_finite_eq_card {s : set α} (hs : s.finite) :
  fincard s = (hs.to_finset).card :=
begin
  rw [fincard_def, finset.card_eq_sum_ones, finsum_in_eq_finset_sum],  simp,
  exact set.finite.subset hs (inter_subset_left _ _),
end

theorem fincard_preimage_eq_sum' (f : α → β){s : set α} {t : set β}
(hs : s.finite) (ht : t.finite) :
fincard (s ∩ f⁻¹' t) = ∑ᶠ y in t, fincard {x ∈ s | f x = y} :=
begin
  simp_rw fincard,
  have := @finsum_in_bUnion α ℕ _ β (λ _, 1) t (λ y, {x ∈ s | f x = y}) ht _ _, rotate,
  { intro b, apply set.finite.subset hs, intros x hx, simp only [mem_sep_eq] at hx, exact hx.1},
  { rintro x hx y hy hxy,
    simp only [disjoint_left, and_imp, mem_sep_eq, not_and],
    rintros a ha rfl - rfl, exact hxy rfl},
  convert this, {ext, simp, tauto},
end

theorem fincard_preimage_eq_sum (f : α → β){t : set β}
(ht : t.finite) (ht' : (f ⁻¹' t).finite) :
fincard (f⁻¹' t) = ∑ᶠ y in t, fincard {x | f x = y} :=
begin
  have := fincard_preimage_eq_sum' f ht' ht, rw inter_self at this,
  rw [this, finsum_in_def, finsum_in_def], congr', ext,
  split_ifs, swap, refl,
  convert fincard_img_emb (function.embedding.refl α) _,
  ext, simp, rintro rfl, assumption,
end

@[simp] lemma nat.finsum_const_eq_mul_fincard_t (b : ℕ) :
  ∑ᶠ (i : α), b = b * (fincard_t α) :=
by rw [← mul_one b, ← mul_distrib_finsum, fincard_t_eq_sum_ones, mul_one]

@[simp] lemma nat.finsum_in_const_eq_mul_fincard (b : ℕ){s : set α} :
  ∑ᶠ i in s, b = b * (fincard s) :=
by rw [← mul_one b, ← mul_distrib_finsum_in, fincard_def, mul_one]

lemma sum_fincard_fiber_eq_fincard {s : set α} (f : α → β)
(hs : s.finite) :
∑ᶠ (b : β), fincard {a ∈ s | f a = b} = fincard s :=
begin
  set t := f '' s with ht,
  have hs' : s = s ∩ f ⁻¹' t,
  { rw [eq_comm, ← set.subset_iff_inter_eq_left, ht], exact subset_preimage_image f s,  },
  rw [eq_comm, hs', fincard_preimage_eq_sum' _ hs (finite.image _ hs), finsum_eq_finsum_in_subset],
  { convert rfl, ext, congr', rw ← hs', },
  intro x,
  rw [fincard_eq_zero_iff_empty, mem_image], swap,
  { apply set.finite.subset hs, intro y, rw mem_sep_eq, exact λ h, h.1 },
  intro hx, ext y,
  rw [mem_sep_eq, mem_empty_eq],
  exact ⟨λ h, hx ⟨y,h⟩, λ h, false.elim h⟩,
end

lemma fincard_union {s t : set α} (hs : s.finite) (ht : t.finite):
  (fincard (s ∪ t) : ℤ) = fincard s + fincard t - fincard (s ∩ t) :=
by linarith [fincard_modular hs ht]

lemma fincard_union_disj {s t : set α} (hs : s.finite) (ht : t.finite) (hst : disjoint s t) :
  fincard (s ∪ t) = fincard s + fincard t :=
by {zify, simp [fincard_union hs ht,  disjoint_iff_inter_eq_empty.mp hst]}

lemma fincard_diff {s t : set α} (hs : s.finite) (hst : t ⊆ s) :
  (fincard (s \ t) : ℤ) = fincard s - fincard t :=
begin
  unfold fincard,
  have := @finsum_in_sdiff _ _ _ (1 : α → ℕ) _ _ hs hst,
  change ∑ᶠ (i : α) in t, 1 + ∑ᶠ (i : α) in s \ t, 1 = ∑ᶠ (i : α) in s, 1 at this,
  simp [← this],
end

lemma fincard_symm_diff {s t : set α} (hs : s.finite) (ht : t.finite):
  (fincard (s.symm_diff t) : ℤ) = fincard s + fincard t - 2 * fincard (s ∩ t) :=
begin
  rw [
    symm_diff_alt,
    fincard_diff (hs.union ht) (subset.trans (inter_subset_left _ _) (subset_union_left _ _)),
    fincard_union hs ht],
  linarith,
end

lemma fincard_symm_diff_mod2 {s t : set α} (hs : s.finite) (ht : t.finite) :
  (fincard (s.symm_diff t) : zmod 2) = fincard s + fincard t :=
begin
  rw [← int.cast_coe_nat, fincard_symm_diff hs ht, int.cast_sub],
  convert sub_zero _, swap, simp,
  rw [int.cast_mul],
  convert zero_mul _,
end

lemma finite_set_induction (P : set α → Prop):
  (P ∅) → (∀ (s : set α) (e : α), e ∉ s → P s → P (insert e s)) → (∀ (s : set α), s.finite → P s) :=
begin
  suffices h' : ∀ n : ℕ, (P ∅) → (∀ (s : set α) (e : α), e ∉ s → P s → P (insert e s))
    → (∀ (s : set α), s.finite → fincard s = n → P s),
  { exact λ hbase ih s hs, h' (fincard s) hbase ih _ hs rfl},
  intros n hempt,
  induction n with n ih,
  { intros ih s hfin hsize, rwa (fincard_eq_zero_iff_empty hfin).mp hsize, },
  intros h' s hfin hcard,
  obtain ⟨e,he⟩ := nonempty_of_fincard_pos (by {rw hcard, simp} : 0 < fincard s),
  rw [fincard_remove he hfin, nat.one_add] at hcard,
  convert h' _ _ (nonmem_removal s e) (ih h' (s \ {e}) (finite.diff hfin _) (nat.succ.inj hcard)),
  rw [remove_insert, insert_eq_of_mem he],
end

section incl_excl

variables {M : Type*} [add_comm_group M]

def signed_convolution (f : set α → M) (s : set α) : M :=
  ∑ᶠ a in s.powerset, ((-1)^(fincard a)) •ℤ (f a)

lemma twice {f : set α → M} {s : set α} (hs : s.finite):
  signed_convolution (signed_convolution f) s = f s:=
begin

  unfold signed_convolution,
  have hs' : s.powerset.finite := set.finite.finite_subsets hs,
  have ha_fin : ∀ (a : set α), a ∈ s.powerset → a.powerset.finite,
    from λ a ha, hs'.subset (set.powerset_mono.mpr ha),
  convert @finsum_in_eq_of_eq _ _ _ _ _ _
    (λ (a : set α) ha, @gsmul_distrib_finsum_in _ _ _
      (λ x, ((-1)^(fincard x)) •ℤ (f x))
      (a.powerset)
      ((-1)^(fincard a))
      (set.finite.subset (ha_fin _ ha) (inter_subset_left _ _))),

  rw finsum_comm_dep, rotate,
  { apply hs'.subset, rintros x ⟨hx, hx'⟩, assumption},
  { exact λ a ha, set.finite.subset (ha_fin _ ha) (inter_subset_left _ _)},

  convert (finsum_ite_singleton_eq s).symm,
  funext,
  split_ifs with h,
  { convert (finsum_ite_singleton_eq y),
    { change _ = {y}, ext x, simp [h, subset.antisymm_iff]},
    simp only [←gsmul_mul,  ← pow_add, ← two_mul, nat.neg_one_pow_of_even ⟨_,rfl⟩, one_gsmul]},

  by_cases hsy : s ⊆ y,
  { convert finsum_in_empty,
    ext,
    simp only [mem_sep_eq, mem_empty_eq, not_and, mem_powerset_iff, iff_false],
    exact λ hxs hyx, h (subset.antisymm (subset.trans hyx hxs) hsy)},

  rw [subset_def] at hsy, push_neg at hsy,
  obtain ⟨e, hes, hey⟩ := hsy,

  apply finsum_in_involution (λ (x : set α), x.symm_diff {e}),
  { rintros a ⟨has, hya⟩,
    rw ←  add_gsmul,
    convert zero_gsmul _,
    apply nat.neg_one_pow_sum_eq_zero_of_sum_odd,
    rw [← zmod.eq_one_iff_odd, nat.cast_add,
      fincard_symm_diff_mod2 (hs.subset has) (set.finite_singleton _),
      add_comm, ← add_assoc, ← two_mul],
    convert zero_add _,
      convert zero_mul _,
    simp},
  { exact λ a ⟨has, hya⟩, ⟨subset.trans (symm_diff_subset_union _ _)
                                        (union_subset has (singleton_subset_iff.mpr hes)),
                           subset.trans (subset_diff_singleton hya hey)
                                        (diff_subset_symm_diff _ _ )⟩},
  { simp},
  refine λ a _ ha, false.elim _,
  simp only [symm_diff_eq_self_iff] at ha,
  exact singleton_ne_empty _ ha,
end

end incl_excl
/-

lemma finsum_incl_excl {s : set (set α)} (f : α → M) (hs : s.finite) :
  ∑ᶠ x in ⋃₀ s, f x = ∑ᶠ t in s.powerset, (∑ᶠ x in ⋂₀ t, group_sign (fincard t) (f x) ) :=
begin
  sorry,
end

lemma finsum_incl_excl' {s : set (set α)} {f : α → M}
(hf : (⋃₀ s ∩ function.support f).finite):
  ∑ᶠ x in ⋃₀ s, f x = ∑ᶠ t in s.powerset, (∑ᶠ x in ⋂₀ t, group_sign (fincard t) (f x) ) :=
begin
  set s' := (λ t, t ∩ function.support f) '' s with hs',
  have hfin : (⋃₀ s').finite,
  { convert hf, rw hs', ext x, simp, tauto},
  have hs' : s'.finite,
  { apply finite.subset (finite.finite_subsets hfin),
    exact λ t ht, mem_set_of_eq.mpr (λ x hx, mem_sUnion.mpr ⟨t,ht,hx⟩)},
  convert finsum_incl_excl f hs' using 1,
  { sorry},
  rw [finsum_in_inter_support,  finsum_in_inter_support _ (s'.powerset)],
-/


/-
Copyright (c) 2018 Ellen Arlt. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Ellen Arlt, Blair Shi, Sean Leather, Mario Carneiro, Johan Commelin

! This file was ported from Lean 3 source module data.matrix.block
! leanprover-community/mathlib commit 6ca1a09bc9aa75824bf97388c9e3b441fc4ccf3f
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Matrix.Basic

/-!
# Block Matrices

## Main definitions

* `matrix.from_blocks`: build a block matrix out of 4 blocks
* `matrix.to_blocks₁₁`, `matrix.to_blocks₁₂`, `matrix.to_blocks₂₁`, `matrix.to_blocks₂₂`:
  extract each of the four blocks from `matrix.from_blocks`.
* `matrix.block_diagonal`: block diagonal of equally sized blocks. On square blocks, this is a
  ring homomorphisms, `matrix.block_diagonal_ring_hom`.
* `matrix.block_diag`: extract the blocks from the diagonal of a block diagonal matrix.
* `matrix.block_diagonal'`: block diagonal of unequally sized blocks. On square blocks, this is a
  ring homomorphisms, `matrix.block_diagonal'_ring_hom`.
* `matrix.block_diag'`: extract the blocks from the diagonal of a block diagonal matrix.
-/

variable {l m n o p q : Type _} {m' n' p' : o → Type _}

variable {R : Type _} {S : Type _} {α : Type _} {β : Type _}

open Matrix

namespace Matrix

section BlockMatrices

/-- We can form a single large matrix by flattening smaller 'block' matrices of compatible
dimensions. -/
@[pp_nodot]
def fromBlocks (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α) (D : Matrix o m α) :
    Matrix (Sum n o) (Sum l m) α :=
  of <| Sum.elim (fun i => Sum.elim (A i) (B i)) fun i => Sum.elim (C i) (D i)
#align matrix.from_blocks Matrix.fromBlocks

@[simp]
theorem fromBlocks_apply₁₁ (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) (i : n) (j : l) : fromBlocks A B C D (Sum.inl i) (Sum.inl j) = A i j :=
  rfl
#align matrix.from_blocks_apply₁₁ Matrix.fromBlocks_apply₁₁

@[simp]
theorem fromBlocks_apply₁₂ (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) (i : n) (j : m) : fromBlocks A B C D (Sum.inl i) (Sum.inr j) = B i j :=
  rfl
#align matrix.from_blocks_apply₁₂ Matrix.fromBlocks_apply₁₂

@[simp]
theorem fromBlocks_apply₂₁ (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) (i : o) (j : l) : fromBlocks A B C D (Sum.inr i) (Sum.inl j) = C i j :=
  rfl
#align matrix.from_blocks_apply₂₁ Matrix.fromBlocks_apply₂₁

@[simp]
theorem fromBlocks_apply₂₂ (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) (i : o) (j : m) : fromBlocks A B C D (Sum.inr i) (Sum.inr j) = D i j :=
  rfl
#align matrix.from_blocks_apply₂₂ Matrix.fromBlocks_apply₂₂

/-- Given a matrix whose row and column indexes are sum types, we can extract the corresponding
"top left" submatrix. -/
def toBlocks₁₁ (M : Matrix (Sum n o) (Sum l m) α) : Matrix n l α :=
  of fun i j => M (Sum.inl i) (Sum.inl j)
#align matrix.to_blocks₁₁ Matrix.toBlocks₁₁

/-- Given a matrix whose row and column indexes are sum types, we can extract the corresponding
"top right" submatrix. -/
def toBlocks₁₂ (M : Matrix (Sum n o) (Sum l m) α) : Matrix n m α :=
  of fun i j => M (Sum.inl i) (Sum.inr j)
#align matrix.to_blocks₁₂ Matrix.toBlocks₁₂

/-- Given a matrix whose row and column indexes are sum types, we can extract the corresponding
"bottom left" submatrix. -/
def toBlocks₂₁ (M : Matrix (Sum n o) (Sum l m) α) : Matrix o l α :=
  of fun i j => M (Sum.inr i) (Sum.inl j)
#align matrix.to_blocks₂₁ Matrix.toBlocks₂₁

/-- Given a matrix whose row and column indexes are sum types, we can extract the corresponding
"bottom right" submatrix. -/
def toBlocks₂₂ (M : Matrix (Sum n o) (Sum l m) α) : Matrix o m α :=
  of fun i j => M (Sum.inr i) (Sum.inr j)
#align matrix.to_blocks₂₂ Matrix.toBlocks₂₂

theorem fromBlocks_to_blocks (M : Matrix (Sum n o) (Sum l m) α) :
    fromBlocks M.toBlocks₁₁ M.toBlocks₁₂ M.toBlocks₂₁ M.toBlocks₂₂ = M := by ext (i j);
  rcases i with ⟨⟩ <;> rcases j with ⟨⟩ <;> rfl
#align matrix.from_blocks_to_blocks Matrix.fromBlocks_to_blocks

@[simp]
theorem to_blocks_from_blocks₁₁ (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) : (fromBlocks A B C D).toBlocks₁₁ = A :=
  rfl
#align matrix.to_blocks_from_blocks₁₁ Matrix.to_blocks_from_blocks₁₁

@[simp]
theorem to_blocks_from_blocks₁₂ (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) : (fromBlocks A B C D).toBlocks₁₂ = B :=
  rfl
#align matrix.to_blocks_from_blocks₁₂ Matrix.to_blocks_from_blocks₁₂

@[simp]
theorem to_blocks_from_blocks₂₁ (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) : (fromBlocks A B C D).toBlocks₂₁ = C :=
  rfl
#align matrix.to_blocks_from_blocks₂₁ Matrix.to_blocks_from_blocks₂₁

@[simp]
theorem to_blocks_from_blocks₂₂ (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) : (fromBlocks A B C D).toBlocks₂₂ = D :=
  rfl
#align matrix.to_blocks_from_blocks₂₂ Matrix.to_blocks_from_blocks₂₂

theorem fromBlocks_map (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α) (D : Matrix o m α)
    (f : α → β) : (fromBlocks A B C D).map f = fromBlocks (A.map f) (B.map f) (C.map f) (D.map f) :=
  by ext (i j); rcases i with ⟨⟩ <;> rcases j with ⟨⟩ <;> simp [from_blocks]
#align matrix.from_blocks_map Matrix.fromBlocks_map

theorem fromBlocks_transpose (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) : (fromBlocks A B C D)ᵀ = fromBlocks Aᵀ Cᵀ Bᵀ Dᵀ := by ext (i j);
  rcases i with ⟨⟩ <;> rcases j with ⟨⟩ <;> simp [from_blocks]
#align matrix.from_blocks_transpose Matrix.fromBlocks_transpose

theorem fromBlocks_conjTranspose [Star α] (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) : (fromBlocks A B C D)ᴴ = fromBlocks Aᴴ Cᴴ Bᴴ Dᴴ := by
  simp only [conj_transpose, from_blocks_transpose, from_blocks_map]
#align matrix.from_blocks_conj_transpose Matrix.fromBlocks_conjTranspose

@[simp]
theorem fromBlocks_submatrix_sum_swap_left (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) (f : p → Sum l m) :
    (fromBlocks A B C D).submatrix Sum.swap f = (fromBlocks C D A B).submatrix id f :=
  by
  ext (i j)
  cases i <;> dsimp <;> cases f j <;> rfl
#align matrix.from_blocks_submatrix_sum_swap_left Matrix.fromBlocks_submatrix_sum_swap_left

@[simp]
theorem fromBlocks_submatrix_sum_swap_right (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) (f : p → Sum n o) :
    (fromBlocks A B C D).submatrix f Sum.swap = (fromBlocks B A D C).submatrix f id :=
  by
  ext (i j)
  cases j <;> dsimp <;> cases f i <;> rfl
#align matrix.from_blocks_submatrix_sum_swap_right Matrix.fromBlocks_submatrix_sum_swap_right

theorem fromBlocks_submatrix_sum_swap_sum_swap {l m n o α : Type _} (A : Matrix n l α)
    (B : Matrix n m α) (C : Matrix o l α) (D : Matrix o m α) :
    (fromBlocks A B C D).submatrix Sum.swap Sum.swap = fromBlocks D C B A := by simp
#align matrix.from_blocks_submatrix_sum_swap_sum_swap Matrix.fromBlocks_submatrix_sum_swap_sum_swap

/-- A 2x2 block matrix is block diagonal if the blocks outside of the diagonal vanish -/
def IsTwoBlockDiagonal [Zero α] (A : Matrix (Sum n o) (Sum l m) α) : Prop :=
  toBlocks₁₂ A = 0 ∧ toBlocks₂₁ A = 0
#align matrix.is_two_block_diagonal Matrix.IsTwoBlockDiagonal

/-- Let `p` pick out certain rows and `q` pick out certain columns of a matrix `M`. Then
  `to_block M p q` is the corresponding block matrix. -/
def toBlock (M : Matrix m n α) (p : m → Prop) (q : n → Prop) : Matrix { a // p a } { a // q a } α :=
  M.submatrix coe coe
#align matrix.to_block Matrix.toBlock

@[simp]
theorem toBlock_apply (M : Matrix m n α) (p : m → Prop) (q : n → Prop) (i : { a // p a })
    (j : { a // q a }) : toBlock M p q i j = M ↑i ↑j :=
  rfl
#align matrix.to_block_apply Matrix.toBlock_apply

/-- Let `p` pick out certain rows and columns of a square matrix `M`. Then
  `to_square_block_prop M p` is the corresponding block matrix. -/
def toSquareBlockProp (M : Matrix m m α) (p : m → Prop) : Matrix { a // p a } { a // p a } α :=
  toBlock M _ _
#align matrix.to_square_block_prop Matrix.toSquareBlockProp

theorem toSquareBlockProp_def (M : Matrix m m α) (p : m → Prop) :
    toSquareBlockProp M p = fun i j => M ↑i ↑j :=
  rfl
#align matrix.to_square_block_prop_def Matrix.toSquareBlockProp_def

/-- Let `b` map rows and columns of a square matrix `M` to blocks. Then
  `to_square_block M b k` is the block `k` matrix. -/
def toSquareBlock (M : Matrix m m α) (b : m → β) (k : β) :
    Matrix { a // b a = k } { a // b a = k } α :=
  toSquareBlockProp M _
#align matrix.to_square_block Matrix.toSquareBlock

theorem toSquareBlock_def (M : Matrix m m α) (b : m → β) (k : β) :
    toSquareBlock M b k = fun i j => M ↑i ↑j :=
  rfl
#align matrix.to_square_block_def Matrix.toSquareBlock_def

theorem fromBlocks_smul [SMul R α] (x : R) (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) : x • fromBlocks A B C D = fromBlocks (x • A) (x • B) (x • C) (x • D) := by
  ext (i j); rcases i with ⟨⟩ <;> rcases j with ⟨⟩ <;> simp [from_blocks]
#align matrix.from_blocks_smul Matrix.fromBlocks_smul

theorem fromBlocks_neg [Neg R] (A : Matrix n l R) (B : Matrix n m R) (C : Matrix o l R)
    (D : Matrix o m R) : -fromBlocks A B C D = fromBlocks (-A) (-B) (-C) (-D) := by ext (i j);
  cases i <;> cases j <;> simp [from_blocks]
#align matrix.from_blocks_neg Matrix.fromBlocks_neg

theorem fromBlocks_add [Add α] (A : Matrix n l α) (B : Matrix n m α) (C : Matrix o l α)
    (D : Matrix o m α) (A' : Matrix n l α) (B' : Matrix n m α) (C' : Matrix o l α)
    (D' : Matrix o m α) :
    fromBlocks A B C D + fromBlocks A' B' C' D' = fromBlocks (A + A') (B + B') (C + C') (D + D') :=
  by ext (i j); rcases i with ⟨⟩ <;> rcases j with ⟨⟩ <;> rfl
#align matrix.from_blocks_add Matrix.fromBlocks_add

theorem fromBlocks_multiply [Fintype l] [Fintype m] [NonUnitalNonAssocSemiring α] (A : Matrix n l α)
    (B : Matrix n m α) (C : Matrix o l α) (D : Matrix o m α) (A' : Matrix l p α) (B' : Matrix l q α)
    (C' : Matrix m p α) (D' : Matrix m q α) :
    fromBlocks A B C D ⬝ fromBlocks A' B' C' D' =
      fromBlocks (A ⬝ A' + B ⬝ C') (A ⬝ B' + B ⬝ D') (C ⬝ A' + D ⬝ C') (C ⬝ B' + D ⬝ D') :=
  by ext (i j);
  rcases i with ⟨⟩ <;> rcases j with ⟨⟩ <;>
    simp only [from_blocks, mul_apply, Fintype.sum_sum_type, Sum.elim_inl, Sum.elim_inr,
      Pi.add_apply, of_apply]
#align matrix.from_blocks_multiply Matrix.fromBlocks_multiply

theorem fromBlocks_mulVec [Fintype l] [Fintype m] [NonUnitalNonAssocSemiring α] (A : Matrix n l α)
    (B : Matrix n m α) (C : Matrix o l α) (D : Matrix o m α) (x : Sum l m → α) :
    mulVec (fromBlocks A B C D) x =
      Sum.elim (mulVec A (x ∘ Sum.inl) + mulVec B (x ∘ Sum.inr))
        (mulVec C (x ∘ Sum.inl) + mulVec D (x ∘ Sum.inr)) :=
  by
  ext i
  cases i <;> simp [mul_vec, dot_product]
#align matrix.from_blocks_mul_vec Matrix.fromBlocks_mulVec

theorem vecMul_fromBlocks [Fintype n] [Fintype o] [NonUnitalNonAssocSemiring α] (A : Matrix n l α)
    (B : Matrix n m α) (C : Matrix o l α) (D : Matrix o m α) (x : Sum n o → α) :
    vecMul x (fromBlocks A B C D) =
      Sum.elim (vecMul (x ∘ Sum.inl) A + vecMul (x ∘ Sum.inr) C)
        (vecMul (x ∘ Sum.inl) B + vecMul (x ∘ Sum.inr) D) :=
  by
  ext i
  cases i <;> simp [vec_mul, dot_product]
#align matrix.vec_mul_from_blocks Matrix.vecMul_fromBlocks

variable [DecidableEq l] [DecidableEq m]

section Zero

variable [Zero α]

theorem toBlock_diagonal_self (d : m → α) (p : m → Prop) :
    Matrix.toBlock (diagonal d) p p = diagonal fun i : Subtype p => d ↑i :=
  by
  ext (i j)
  by_cases i = j
  · simp [h]
  · simp [One.one, h, fun h' => h <| Subtype.ext h']
#align matrix.to_block_diagonal_self Matrix.toBlock_diagonal_self

theorem toBlock_diagonal_disjoint (d : m → α) {p q : m → Prop} (hpq : Disjoint p q) :
    Matrix.toBlock (diagonal d) p q = 0 :=
  by
  ext (⟨i, hi⟩⟨j, hj⟩)
  have : i ≠ j := fun heq => hpq.le_bot i ⟨hi, HEq.symm ▸ hj⟩
  simp [diagonal_apply_ne d this]
#align matrix.to_block_diagonal_disjoint Matrix.toBlock_diagonal_disjoint

@[simp]
theorem fromBlocks_diagonal (d₁ : l → α) (d₂ : m → α) :
    fromBlocks (diagonal d₁) 0 0 (diagonal d₂) = diagonal (Sum.elim d₁ d₂) := by ext (i j);
  rcases i with ⟨⟩ <;> rcases j with ⟨⟩ <;> simp [diagonal]
#align matrix.from_blocks_diagonal Matrix.fromBlocks_diagonal

end Zero

section HasZeroHasOne

variable [Zero α] [One α]

@[simp]
theorem fromBlocks_one : fromBlocks (1 : Matrix l l α) 0 0 (1 : Matrix m m α) = 1 :=
  by
  ext (i j)
  rcases i with ⟨⟩ <;> rcases j with ⟨⟩ <;> simp [one_apply]
#align matrix.from_blocks_one Matrix.fromBlocks_one

@[simp]
theorem toBlock_one_self (p : m → Prop) : Matrix.toBlock (1 : Matrix m m α) p p = 1 :=
  toBlock_diagonal_self _ p
#align matrix.to_block_one_self Matrix.toBlock_one_self

theorem toBlock_one_disjoint {p q : m → Prop} (hpq : Disjoint p q) :
    Matrix.toBlock (1 : Matrix m m α) p q = 0 :=
  toBlock_diagonal_disjoint _ hpq
#align matrix.to_block_one_disjoint Matrix.toBlock_one_disjoint

end HasZeroHasOne

end BlockMatrices

section BlockDiagonal

variable [DecidableEq o]

section Zero

variable [Zero α] [Zero β]

/- warning: matrix.block_diagonal -> Matrix.blockDiagonal is a dubious translation:
lean 3 declaration is
  forall {m : Type.{u1}} {n : Type.{u2}} {o : Type.{u3}} {α : Type.{u4}} [_inst_1 : DecidableEq.{succ u3} o] [_inst_2 : Zero.{u4} α], (o -> (Matrix.{u1, u2, u4} m n α)) -> (Matrix.{max u1 u3, max u2 u3, u4} (Prod.{u1, u3} m o) (Prod.{u2, u3} n o) α)
but is expected to have type
  forall {m : Type.{u2}} {n : Type.{u3}} {o : Type.{u4}} {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u4} o] [_inst_2 : Zero.{u1} α], (o -> (Matrix.{u2, u3, u1} m n α)) -> (Matrix.{max u2 u4, max u3 u4, u1} (Prod.{u2, u4} m o) (Prod.{u3, u4} n o) α)
Case conversion may be inaccurate. Consider using '#align matrix.block_diagonal Matrix.blockDiagonalₓ'. -/
/-- `matrix.block_diagonal M` turns a homogenously-indexed collection of matrices
`M : o → matrix m n α'` into a `m × o`-by-`n × o` block matrix which has the entries of `M` along
the diagonal and zero elsewhere.

See also `matrix.block_diagonal'` if the matrices may not have the same size everywhere.
-/
def blockDiagonal (M : o → Matrix m n α) : Matrix (m × o) (n × o) α
  | ⟨i, k⟩, ⟨j, k'⟩ => if k = k' then M k i j else 0
#align matrix.block_diagonal Matrix.blockDiagonal

theorem blockDiagonal_apply (M : o → Matrix m n α) (ik jk) :
    blockDiagonal M ik jk = if ik.2 = jk.2 then M ik.2 ik.1 jk.1 else 0 :=
  by
  cases ik
  cases jk
  rfl
#align matrix.block_diagonal_apply Matrix.blockDiagonal_apply

@[simp]
theorem blockDiagonal_apply_eq (M : o → Matrix m n α) (i j k) :
    blockDiagonal M (i, k) (j, k) = M k i j :=
  if_pos rfl
#align matrix.block_diagonal_apply_eq Matrix.blockDiagonal_apply_eq

theorem blockDiagonal_apply_ne (M : o → Matrix m n α) (i j) {k k'} (h : k ≠ k') :
    blockDiagonal M (i, k) (j, k') = 0 :=
  if_neg h
#align matrix.block_diagonal_apply_ne Matrix.blockDiagonal_apply_ne

theorem blockDiagonal_map (M : o → Matrix m n α) (f : α → β) (hf : f 0 = 0) :
    (blockDiagonal M).map f = blockDiagonal fun k => (M k).map f :=
  by
  ext
  simp only [map_apply, block_diagonal_apply, eq_comm]
  rw [apply_ite f, hf]
#align matrix.block_diagonal_map Matrix.blockDiagonal_map

@[simp]
theorem blockDiagonal_transpose (M : o → Matrix m n α) :
    (blockDiagonal M)ᵀ = blockDiagonal fun k => (M k)ᵀ :=
  by
  ext
  simp only [transpose_apply, block_diagonal_apply, eq_comm]
  split_ifs with h
  · rw [h]
  · rfl
#align matrix.block_diagonal_transpose Matrix.blockDiagonal_transpose

@[simp]
theorem blockDiagonal_conjTranspose {α : Type _} [AddMonoid α] [StarAddMonoid α]
    (M : o → Matrix m n α) : (blockDiagonal M)ᴴ = blockDiagonal fun k => (M k)ᴴ :=
  by
  simp only [conj_transpose, block_diagonal_transpose]
  rw [block_diagonal_map _ star (star_zero α)]
#align matrix.block_diagonal_conj_transpose Matrix.blockDiagonal_conjTranspose

@[simp]
theorem blockDiagonal_zero : blockDiagonal (0 : o → Matrix m n α) = 0 :=
  by
  ext
  simp [block_diagonal_apply]
#align matrix.block_diagonal_zero Matrix.blockDiagonal_zero

@[simp]
theorem blockDiagonal_diagonal [DecidableEq m] (d : o → m → α) :
    (blockDiagonal fun k => diagonal (d k)) = diagonal fun ik => d ik.2 ik.1 :=
  by
  ext (⟨i, k⟩⟨j, k'⟩)
  simp only [block_diagonal_apply, diagonal, Prod.mk.inj_iff, ← ite_and]
  congr 1
  rw [and_comm']
#align matrix.block_diagonal_diagonal Matrix.blockDiagonal_diagonal

@[simp]
theorem blockDiagonal_one [DecidableEq m] [One α] : blockDiagonal (1 : o → Matrix m m α) = 1 :=
  show (blockDiagonal fun _ : o => diagonal fun _ : m => (1 : α)) = diagonal fun _ => 1 by
    rw [block_diagonal_diagonal]
#align matrix.block_diagonal_one Matrix.blockDiagonal_one

end Zero

@[simp]
theorem blockDiagonal_add [AddZeroClass α] (M N : o → Matrix m n α) :
    blockDiagonal (M + N) = blockDiagonal M + blockDiagonal N :=
  by
  ext
  simp only [block_diagonal_apply, Pi.add_apply]
  split_ifs <;> simp
#align matrix.block_diagonal_add Matrix.blockDiagonal_add

section

variable (o m n α)

/-- `matrix.block_diagonal` as an `add_monoid_hom`. -/
@[simps]
def blockDiagonalAddMonoidHom [AddZeroClass α] : (o → Matrix m n α) →+ Matrix (m × o) (n × o) α
    where
  toFun := blockDiagonal
  map_zero' := blockDiagonal_zero
  map_add' := blockDiagonal_add
#align matrix.block_diagonal_add_monoid_hom Matrix.blockDiagonalAddMonoidHom

end

@[simp]
theorem blockDiagonal_neg [AddGroup α] (M : o → Matrix m n α) :
    blockDiagonal (-M) = -blockDiagonal M :=
  map_neg (blockDiagonalAddMonoidHom m n o α) M
#align matrix.block_diagonal_neg Matrix.blockDiagonal_neg

@[simp]
theorem blockDiagonal_sub [AddGroup α] (M N : o → Matrix m n α) :
    blockDiagonal (M - N) = blockDiagonal M - blockDiagonal N :=
  map_sub (blockDiagonalAddMonoidHom m n o α) M N
#align matrix.block_diagonal_sub Matrix.blockDiagonal_sub

@[simp]
theorem blockDiagonal_mul [Fintype n] [Fintype o] [NonUnitalNonAssocSemiring α]
    (M : o → Matrix m n α) (N : o → Matrix n p α) :
    (blockDiagonal fun k => M k ⬝ N k) = blockDiagonal M ⬝ blockDiagonal N :=
  by
  ext (⟨i, k⟩⟨j, k'⟩)
  simp only [block_diagonal_apply, mul_apply, ← Finset.univ_product_univ, Finset.sum_product]
  split_ifs with h <;> simp [h]
#align matrix.block_diagonal_mul Matrix.blockDiagonal_mul

section

variable (α m o)

/-- `matrix.block_diagonal` as a `ring_hom`. -/
@[simps]
def blockDiagonalRingHom [DecidableEq m] [Fintype o] [Fintype m] [NonAssocSemiring α] :
    (o → Matrix m m α) →+* Matrix (m × o) (m × o) α :=
  {
    blockDiagonalAddMonoidHom m m o α with
    toFun := blockDiagonal
    map_one' := blockDiagonal_one
    map_mul' := blockDiagonal_mul }
#align matrix.block_diagonal_ring_hom Matrix.blockDiagonalRingHom

end

@[simp]
theorem blockDiagonal_pow [DecidableEq m] [Fintype o] [Fintype m] [Semiring α]
    (M : o → Matrix m m α) (n : ℕ) : blockDiagonal (M ^ n) = blockDiagonal M ^ n :=
  map_pow (blockDiagonalRingHom m o α) M n
#align matrix.block_diagonal_pow Matrix.blockDiagonal_pow

@[simp]
theorem blockDiagonal_smul {R : Type _} [Monoid R] [AddMonoid α] [DistribMulAction R α] (x : R)
    (M : o → Matrix m n α) : blockDiagonal (x • M) = x • blockDiagonal M :=
  by
  ext
  simp only [block_diagonal_apply, Pi.smul_apply]
  split_ifs <;> simp
#align matrix.block_diagonal_smul Matrix.blockDiagonal_smul

end BlockDiagonal

section BlockDiag

/- warning: matrix.block_diag -> Matrix.blockDiag is a dubious translation:
lean 3 declaration is
  forall {m : Type.{u1}} {n : Type.{u2}} {o : Type.{u3}} {α : Type.{u4}}, (Matrix.{max u1 u3, max u2 u3, u4} (Prod.{u1, u3} m o) (Prod.{u2, u3} n o) α) -> o -> (Matrix.{u1, u2, u4} m n α)
but is expected to have type
  forall {m : Type.{u2}} {n : Type.{u3}} {o : Type.{u4}} {α : Type.{u1}}, (Matrix.{max u2 u4, max u3 u4, u1} (Prod.{u2, u4} m o) (Prod.{u3, u4} n o) α) -> o -> (Matrix.{u2, u3, u1} m n α)
Case conversion may be inaccurate. Consider using '#align matrix.block_diag Matrix.blockDiagₓ'. -/
/-- Extract a block from the diagonal of a block diagonal matrix.

This is the block form of `matrix.diag`, and the left-inverse of `matrix.block_diagonal`. -/
def blockDiag (M : Matrix (m × o) (n × o) α) (k : o) : Matrix m n α
  | i, j => M (i, k) (j, k)
#align matrix.block_diag Matrix.blockDiag

theorem blockDiag_map (M : Matrix (m × o) (n × o) α) (f : α → β) :
    blockDiag (M.map f) = fun k => (blockDiag M k).map f :=
  rfl
#align matrix.block_diag_map Matrix.blockDiag_map

@[simp]
theorem blockDiag_transpose (M : Matrix (m × o) (n × o) α) (k : o) :
    blockDiag Mᵀ k = (blockDiag M k)ᵀ :=
  ext fun i j => rfl
#align matrix.block_diag_transpose Matrix.blockDiag_transpose

@[simp]
theorem blockDiag_conjTranspose {α : Type _} [AddMonoid α] [StarAddMonoid α]
    (M : Matrix (m × o) (n × o) α) (k : o) : blockDiag Mᴴ k = (blockDiag M k)ᴴ :=
  ext fun i j => rfl
#align matrix.block_diag_conj_transpose Matrix.blockDiag_conjTranspose

section Zero

variable [Zero α] [Zero β]

@[simp]
theorem blockDiag_zero : blockDiag (0 : Matrix (m × o) (n × o) α) = 0 :=
  rfl
#align matrix.block_diag_zero Matrix.blockDiag_zero

@[simp]
theorem blockDiag_diagonal [DecidableEq o] [DecidableEq m] (d : m × o → α) (k : o) :
    blockDiag (diagonal d) k = diagonal fun i => d (i, k) :=
  ext fun i j => by
    obtain rfl | hij := Decidable.eq_or_ne i j
    · rw [block_diag, diagonal_apply_eq, diagonal_apply_eq]
    · rw [block_diag, diagonal_apply_ne _ hij, diagonal_apply_ne _ (mt _ hij)]
      exact prod.fst_eq_iff.mpr
#align matrix.block_diag_diagonal Matrix.blockDiag_diagonal

@[simp]
theorem blockDiag_blockDiagonal [DecidableEq o] (M : o → Matrix m n α) :
    blockDiag (blockDiagonal M) = M :=
  funext fun k => ext fun i j => blockDiagonal_apply_eq _ _ _ _
#align matrix.block_diag_block_diagonal Matrix.blockDiag_blockDiagonal

@[simp]
theorem blockDiag_one [DecidableEq o] [DecidableEq m] [One α] :
    blockDiag (1 : Matrix (m × o) (m × o) α) = 1 :=
  funext <| blockDiag_diagonal _
#align matrix.block_diag_one Matrix.blockDiag_one

end Zero

@[simp]
theorem blockDiag_add [AddZeroClass α] (M N : Matrix (m × o) (n × o) α) :
    blockDiag (M + N) = blockDiag M + blockDiag N :=
  rfl
#align matrix.block_diag_add Matrix.blockDiag_add

section

variable (o m n α)

/-- `matrix.block_diag` as an `add_monoid_hom`. -/
@[simps]
def blockDiagAddMonoidHom [AddZeroClass α] : Matrix (m × o) (n × o) α →+ o → Matrix m n α
    where
  toFun := blockDiag
  map_zero' := blockDiag_zero
  map_add' := blockDiag_add
#align matrix.block_diag_add_monoid_hom Matrix.blockDiagAddMonoidHom

end

@[simp]
theorem blockDiag_neg [AddGroup α] (M : Matrix (m × o) (n × o) α) : blockDiag (-M) = -blockDiag M :=
  map_neg (blockDiagAddMonoidHom m n o α) M
#align matrix.block_diag_neg Matrix.blockDiag_neg

@[simp]
theorem blockDiag_sub [AddGroup α] (M N : Matrix (m × o) (n × o) α) :
    blockDiag (M - N) = blockDiag M - blockDiag N :=
  map_sub (blockDiagAddMonoidHom m n o α) M N
#align matrix.block_diag_sub Matrix.blockDiag_sub

@[simp]
theorem blockDiag_smul {R : Type _} [Monoid R] [AddMonoid α] [DistribMulAction R α] (x : R)
    (M : Matrix (m × o) (n × o) α) : blockDiag (x • M) = x • blockDiag M :=
  rfl
#align matrix.block_diag_smul Matrix.blockDiag_smul

end BlockDiag

section BlockDiagonal'

variable [DecidableEq o]

section Zero

variable [Zero α] [Zero β]

/- warning: matrix.block_diagonal' -> Matrix.blockDiagonal' is a dubious translation:
lean 3 declaration is
  forall {o : Type.{u1}} {m' : o -> Type.{u2}} {n' : o -> Type.{u3}} {α : Type.{u4}} [_inst_1 : DecidableEq.{succ u1} o] [_inst_2 : Zero.{u4} α], (forall (i : o), Matrix.{u2, u3, u4} (m' i) (n' i) α) -> (Matrix.{max u1 u2, max u1 u3, u4} (Sigma.{u1, u2} o (fun (i : o) => m' i)) (Sigma.{u1, u3} o (fun (i : o) => n' i)) α)
but is expected to have type
  forall {o : Type.{u2}} {m' : o -> Type.{u3}} {n' : o -> Type.{u4}} {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u2} o] [_inst_2 : Zero.{u1} α], (forall (i : o), Matrix.{u3, u4, u1} (m' i) (n' i) α) -> (Matrix.{max u2 u3, max u2 u4, u1} (Sigma.{u2, u3} o (fun (i : o) => m' i)) (Sigma.{u2, u4} o (fun (i : o) => n' i)) α)
Case conversion may be inaccurate. Consider using '#align matrix.block_diagonal' Matrix.blockDiagonal'ₓ'. -/
/-- `matrix.block_diagonal' M` turns `M : Π i, matrix (m i) (n i) α` into a
`Σ i, m i`-by-`Σ i, n i` block matrix which has the entries of `M` along the diagonal
and zero elsewhere.

This is the dependently-typed version of `matrix.block_diagonal`. -/
def blockDiagonal' (M : ∀ i, Matrix (m' i) (n' i) α) : Matrix (Σi, m' i) (Σi, n' i) α
  | ⟨k, i⟩, ⟨k', j⟩ => if h : k = k' then M k i (cast (congr_arg n' h.symm) j) else 0
#align matrix.block_diagonal' Matrix.blockDiagonal'

theorem blockDiagonal'_eq_blockDiagonal (M : o → Matrix m n α) {k k'} (i j) :
    blockDiagonal M (i, k) (j, k') = blockDiagonal' M ⟨k, i⟩ ⟨k', j⟩ :=
  rfl
#align matrix.block_diagonal'_eq_block_diagonal Matrix.blockDiagonal'_eq_blockDiagonal

theorem blockDiagonal'_submatrix_eq_blockDiagonal (M : o → Matrix m n α) :
    (blockDiagonal' M).submatrix (Prod.toSigma ∘ Prod.swap) (Prod.toSigma ∘ Prod.swap) =
      blockDiagonal M :=
  Matrix.ext fun ⟨k, i⟩ ⟨k', j⟩ => rfl
#align matrix.block_diagonal'_submatrix_eq_block_diagonal Matrix.blockDiagonal'_submatrix_eq_blockDiagonal

theorem blockDiagonal'_apply (M : ∀ i, Matrix (m' i) (n' i) α) (ik jk) :
    blockDiagonal' M ik jk =
      if h : ik.1 = jk.1 then M ik.1 ik.2 (cast (congr_arg n' h.symm) jk.2) else 0 :=
  by
  cases ik
  cases jk
  rfl
#align matrix.block_diagonal'_apply Matrix.blockDiagonal'_apply

@[simp]
theorem blockDiagonal'_apply_eq (M : ∀ i, Matrix (m' i) (n' i) α) (k i j) :
    blockDiagonal' M ⟨k, i⟩ ⟨k, j⟩ = M k i j :=
  dif_pos rfl
#align matrix.block_diagonal'_apply_eq Matrix.blockDiagonal'_apply_eq

theorem blockDiagonal'_apply_ne (M : ∀ i, Matrix (m' i) (n' i) α) {k k'} (i j) (h : k ≠ k') :
    blockDiagonal' M ⟨k, i⟩ ⟨k', j⟩ = 0 :=
  dif_neg h
#align matrix.block_diagonal'_apply_ne Matrix.blockDiagonal'_apply_ne

theorem blockDiagonal'_map (M : ∀ i, Matrix (m' i) (n' i) α) (f : α → β) (hf : f 0 = 0) :
    (blockDiagonal' M).map f = blockDiagonal' fun k => (M k).map f :=
  by
  ext
  simp only [map_apply, block_diagonal'_apply, eq_comm]
  rw [apply_dite f, hf]
#align matrix.block_diagonal'_map Matrix.blockDiagonal'_map

@[simp]
theorem blockDiagonal'_transpose (M : ∀ i, Matrix (m' i) (n' i) α) :
    (blockDiagonal' M)ᵀ = blockDiagonal' fun k => (M k)ᵀ :=
  by
  ext (⟨ii, ix⟩⟨ji, jx⟩)
  simp only [transpose_apply, block_diagonal'_apply]
  split_ifs <;> cc
#align matrix.block_diagonal'_transpose Matrix.blockDiagonal'_transpose

@[simp]
theorem blockDiagonal'_conjTranspose {α} [AddMonoid α] [StarAddMonoid α]
    (M : ∀ i, Matrix (m' i) (n' i) α) : (blockDiagonal' M)ᴴ = blockDiagonal' fun k => (M k)ᴴ :=
  by
  simp only [conj_transpose, block_diagonal'_transpose]
  exact block_diagonal'_map _ star (star_zero α)
#align matrix.block_diagonal'_conj_transpose Matrix.blockDiagonal'_conjTranspose

@[simp]
theorem blockDiagonal'_zero : blockDiagonal' (0 : ∀ i, Matrix (m' i) (n' i) α) = 0 :=
  by
  ext
  simp [block_diagonal'_apply]
#align matrix.block_diagonal'_zero Matrix.blockDiagonal'_zero

@[simp]
theorem blockDiagonal'_diagonal [∀ i, DecidableEq (m' i)] (d : ∀ i, m' i → α) :
    (blockDiagonal' fun k => diagonal (d k)) = diagonal fun ik => d ik.1 ik.2 :=
  by
  ext (⟨i, k⟩⟨j, k'⟩)
  simp only [block_diagonal'_apply, diagonal]
  obtain rfl | hij := Decidable.eq_or_ne i j
  · simp
  · simp [hij]
#align matrix.block_diagonal'_diagonal Matrix.blockDiagonal'_diagonal

@[simp]
theorem blockDiagonal'_one [∀ i, DecidableEq (m' i)] [One α] :
    blockDiagonal' (1 : ∀ i, Matrix (m' i) (m' i) α) = 1 :=
  show (blockDiagonal' fun i : o => diagonal fun _ : m' i => (1 : α)) = diagonal fun _ => 1 by
    rw [block_diagonal'_diagonal]
#align matrix.block_diagonal'_one Matrix.blockDiagonal'_one

end Zero

@[simp]
theorem blockDiagonal'_add [AddZeroClass α] (M N : ∀ i, Matrix (m' i) (n' i) α) :
    blockDiagonal' (M + N) = blockDiagonal' M + blockDiagonal' N :=
  by
  ext
  simp only [block_diagonal'_apply, Pi.add_apply]
  split_ifs <;> simp
#align matrix.block_diagonal'_add Matrix.blockDiagonal'_add

section

variable (m' n' α)

/-- `matrix.block_diagonal'` as an `add_monoid_hom`. -/
@[simps]
def blockDiagonal'AddMonoidHom [AddZeroClass α] :
    (∀ i, Matrix (m' i) (n' i) α) →+ Matrix (Σi, m' i) (Σi, n' i) α
    where
  toFun := blockDiagonal'
  map_zero' := blockDiagonal'_zero
  map_add' := blockDiagonal'_add
#align matrix.block_diagonal'_add_monoid_hom Matrix.blockDiagonal'AddMonoidHom

end

@[simp]
theorem blockDiagonal'_neg [AddGroup α] (M : ∀ i, Matrix (m' i) (n' i) α) :
    blockDiagonal' (-M) = -blockDiagonal' M :=
  map_neg (blockDiagonal'AddMonoidHom m' n' α) M
#align matrix.block_diagonal'_neg Matrix.blockDiagonal'_neg

@[simp]
theorem blockDiagonal'_sub [AddGroup α] (M N : ∀ i, Matrix (m' i) (n' i) α) :
    blockDiagonal' (M - N) = blockDiagonal' M - blockDiagonal' N :=
  map_sub (blockDiagonal'AddMonoidHom m' n' α) M N
#align matrix.block_diagonal'_sub Matrix.blockDiagonal'_sub

@[simp]
theorem blockDiagonal'_mul [NonUnitalNonAssocSemiring α] [∀ i, Fintype (n' i)] [Fintype o]
    (M : ∀ i, Matrix (m' i) (n' i) α) (N : ∀ i, Matrix (n' i) (p' i) α) :
    (blockDiagonal' fun k => M k ⬝ N k) = blockDiagonal' M ⬝ blockDiagonal' N :=
  by
  ext (⟨k, i⟩⟨k', j⟩)
  simp only [block_diagonal'_apply, mul_apply, ← Finset.univ_sigma_univ, Finset.sum_sigma]
  rw [Fintype.sum_eq_single k]
  · split_ifs <;> simp
  · intro j' hj'
    exact Finset.sum_eq_zero fun _ _ => by rw [dif_neg hj'.symm, MulZeroClass.zero_mul]
#align matrix.block_diagonal'_mul Matrix.blockDiagonal'_mul

section

variable (α m')

/-- `matrix.block_diagonal'` as a `ring_hom`. -/
@[simps]
def blockDiagonal'RingHom [∀ i, DecidableEq (m' i)] [Fintype o] [∀ i, Fintype (m' i)]
    [NonAssocSemiring α] : (∀ i, Matrix (m' i) (m' i) α) →+* Matrix (Σi, m' i) (Σi, m' i) α :=
  {
    blockDiagonal'AddMonoidHom m' m'
      α with
    toFun := blockDiagonal'
    map_one' := blockDiagonal'_one
    map_mul' := blockDiagonal'_mul }
#align matrix.block_diagonal'_ring_hom Matrix.blockDiagonal'RingHom

end

@[simp]
theorem blockDiagonal'_pow [∀ i, DecidableEq (m' i)] [Fintype o] [∀ i, Fintype (m' i)] [Semiring α]
    (M : ∀ i, Matrix (m' i) (m' i) α) (n : ℕ) : blockDiagonal' (M ^ n) = blockDiagonal' M ^ n :=
  map_pow (blockDiagonal'RingHom m' α) M n
#align matrix.block_diagonal'_pow Matrix.blockDiagonal'_pow

@[simp]
theorem blockDiagonal'_smul {R : Type _} [Semiring R] [AddCommMonoid α] [Module R α] (x : R)
    (M : ∀ i, Matrix (m' i) (n' i) α) : blockDiagonal' (x • M) = x • blockDiagonal' M :=
  by
  ext
  simp only [block_diagonal'_apply, Pi.smul_apply]
  split_ifs <;> simp
#align matrix.block_diagonal'_smul Matrix.blockDiagonal'_smul

end BlockDiagonal'

section BlockDiag'

/- warning: matrix.block_diag' -> Matrix.blockDiag' is a dubious translation:
lean 3 declaration is
  forall {o : Type.{u1}} {m' : o -> Type.{u2}} {n' : o -> Type.{u3}} {α : Type.{u4}}, (Matrix.{max u1 u2, max u1 u3, u4} (Sigma.{u1, u2} o (fun (i : o) => m' i)) (Sigma.{u1, u3} o (fun (i : o) => n' i)) α) -> (forall (k : o), Matrix.{u2, u3, u4} (m' k) (n' k) α)
but is expected to have type
  forall {o : Type.{u2}} {m' : o -> Type.{u3}} {n' : o -> Type.{u4}} {α : Type.{u1}}, (Matrix.{max u2 u3, max u2 u4, u1} (Sigma.{u2, u3} o (fun (i : o) => m' i)) (Sigma.{u2, u4} o (fun (i : o) => n' i)) α) -> (forall (k : o), Matrix.{u3, u4, u1} (m' k) (n' k) α)
Case conversion may be inaccurate. Consider using '#align matrix.block_diag' Matrix.blockDiag'ₓ'. -/
/-- Extract a block from the diagonal of a block diagonal matrix.

This is the block form of `matrix.diag`, and the left-inverse of `matrix.block_diagonal'`. -/
def blockDiag' (M : Matrix (Σi, m' i) (Σi, n' i) α) (k : o) : Matrix (m' k) (n' k) α
  | i, j => M ⟨k, i⟩ ⟨k, j⟩
#align matrix.block_diag' Matrix.blockDiag'

theorem blockDiag'_map (M : Matrix (Σi, m' i) (Σi, n' i) α) (f : α → β) :
    blockDiag' (M.map f) = fun k => (blockDiag' M k).map f :=
  rfl
#align matrix.block_diag'_map Matrix.blockDiag'_map

@[simp]
theorem blockDiag'_transpose (M : Matrix (Σi, m' i) (Σi, n' i) α) (k : o) :
    blockDiag' Mᵀ k = (blockDiag' M k)ᵀ :=
  ext fun i j => rfl
#align matrix.block_diag'_transpose Matrix.blockDiag'_transpose

@[simp]
theorem blockDiag'_conjTranspose {α : Type _} [AddMonoid α] [StarAddMonoid α]
    (M : Matrix (Σi, m' i) (Σi, n' i) α) (k : o) : blockDiag' Mᴴ k = (blockDiag' M k)ᴴ :=
  ext fun i j => rfl
#align matrix.block_diag'_conj_transpose Matrix.blockDiag'_conjTranspose

section Zero

variable [Zero α] [Zero β]

@[simp]
theorem blockDiag'_zero : blockDiag' (0 : Matrix (Σi, m' i) (Σi, n' i) α) = 0 :=
  rfl
#align matrix.block_diag'_zero Matrix.blockDiag'_zero

@[simp]
theorem blockDiag'_diagonal [DecidableEq o] [∀ i, DecidableEq (m' i)] (d : (Σi, m' i) → α) (k : o) :
    blockDiag' (diagonal d) k = diagonal fun i => d ⟨k, i⟩ :=
  ext fun i j => by
    obtain rfl | hij := Decidable.eq_or_ne i j
    · rw [block_diag', diagonal_apply_eq, diagonal_apply_eq]
    · rw [block_diag', diagonal_apply_ne _ hij, diagonal_apply_ne _ (mt (fun h => _) hij)]
      cases h
      rfl
#align matrix.block_diag'_diagonal Matrix.blockDiag'_diagonal

@[simp]
theorem blockDiag'_blockDiagonal' [DecidableEq o] (M : ∀ i, Matrix (m' i) (n' i) α) :
    blockDiag' (blockDiagonal' M) = M :=
  funext fun k => ext fun i j => blockDiagonal'_apply_eq _ _ _ _
#align matrix.block_diag'_block_diagonal' Matrix.blockDiag'_blockDiagonal'

@[simp]
theorem blockDiag'_one [DecidableEq o] [∀ i, DecidableEq (m' i)] [One α] :
    blockDiag' (1 : Matrix (Σi, m' i) (Σi, m' i) α) = 1 :=
  funext <| blockDiag'_diagonal _
#align matrix.block_diag'_one Matrix.blockDiag'_one

end Zero

@[simp]
theorem blockDiag'_add [AddZeroClass α] (M N : Matrix (Σi, m' i) (Σi, n' i) α) :
    blockDiag' (M + N) = blockDiag' M + blockDiag' N :=
  rfl
#align matrix.block_diag'_add Matrix.blockDiag'_add

section

variable (m' n' α)

/-- `matrix.block_diag'` as an `add_monoid_hom`. -/
@[simps]
def blockDiag'AddMonoidHom [AddZeroClass α] :
    Matrix (Σi, m' i) (Σi, n' i) α →+ ∀ i, Matrix (m' i) (n' i) α
    where
  toFun := blockDiag'
  map_zero' := blockDiag'_zero
  map_add' := blockDiag'_add
#align matrix.block_diag'_add_monoid_hom Matrix.blockDiag'AddMonoidHom

end

@[simp]
theorem blockDiag'_neg [AddGroup α] (M : Matrix (Σi, m' i) (Σi, n' i) α) :
    blockDiag' (-M) = -blockDiag' M :=
  map_neg (blockDiag'AddMonoidHom m' n' α) M
#align matrix.block_diag'_neg Matrix.blockDiag'_neg

@[simp]
theorem blockDiag'_sub [AddGroup α] (M N : Matrix (Σi, m' i) (Σi, n' i) α) :
    blockDiag' (M - N) = blockDiag' M - blockDiag' N :=
  map_sub (blockDiag'AddMonoidHom m' n' α) M N
#align matrix.block_diag'_sub Matrix.blockDiag'_sub

@[simp]
theorem blockDiag'_smul {R : Type _} [Monoid R] [AddMonoid α] [DistribMulAction R α] (x : R)
    (M : Matrix (Σi, m' i) (Σi, n' i) α) : blockDiag' (x • M) = x • blockDiag' M :=
  rfl
#align matrix.block_diag'_smul Matrix.blockDiag'_smul

end BlockDiag'

section

variable [CommRing R]

theorem toBlock_mul_eq_mul {m n k : Type _} [Fintype n] (p : m → Prop) (q : k → Prop)
    (A : Matrix m n R) (B : Matrix n k R) : (A ⬝ B).toBlock p q = A.toBlock p ⊤ ⬝ B.toBlock ⊤ q :=
  by
  ext (i k)
  simp only [to_block_apply, mul_apply]
  rw [Finset.sum_subtype]
  simp [Top.top, CompleteLattice.top, BoundedOrder.top]
#align matrix.to_block_mul_eq_mul Matrix.toBlock_mul_eq_mul

theorem toBlock_mul_eq_add {m n k : Type _} [Fintype n] (p : m → Prop) (q : n → Prop)
    [DecidablePred q] (r : k → Prop) (A : Matrix m n R) (B : Matrix n k R) :
    (A ⬝ B).toBlock p r =
      A.toBlock p q ⬝ B.toBlock q r + (A.toBlock p fun i => ¬q i) ⬝ B.toBlock (fun i => ¬q i) r :=
  by
  classical
    ext (i k)
    simp only [to_block_apply, mul_apply, Pi.add_apply]
    convert(Fintype.sum_subtype_add_sum_subtype q fun x => A (↑i) x * B x ↑k).symm
#align matrix.to_block_mul_eq_add Matrix.toBlock_mul_eq_add

end

end Matrix


import tactic.ring
import tactic.ring
import data.polynomial
import algebra.category.CommRing.basic
open category_theory
open functor
open CommRing
open polynomial

universe u
variable {R : Type u}

structure Gm (R : Type u) [comm_ring R] :=   --- Object ?
(val : R)
(inv : R)
(val_inv : val * inv = 1)
(inv_val : inv * val = 1)
namespace Gm
variables [comm_ring R]
------    My universe
instance : has_coe (Gm R) R := ⟨val⟩    ---- ici c'est la valeur de l'unité dans R instance ???
--- Exemple of Gm element !
def Nil_lemma_for_fun___D(a : R) : (a * a = (0 : R)) → Gm R :=
    begin
        --- My favorite commutative lemma : η^2 =0 → (1+η) × (1-η) = 1 so 1+η ∈ Gm(R) :D
        ---                               :
        intro h,
        let val_inv : (1+a)* (1-a) = 1, from
            calc
                (1+a) * (1-a) = 1*1  - a*a        : by rw [eq.symm (mul_self_sub_mul_self_eq (1) (a))]
                ...           = 1*1  - 0          : by exact congr_arg (λ l, 1*1 - l)  (h)
                ...           = 1                 : by simp,
        let inv_val : (1-a)* (1+a)= 1, from
            calc
                (1-a) * (1+a) = (1+a) * (1-a)    : by rw mul_comm
                ...          = 1                 : val_inv,
        exact ⟨1+a,1-a, val_inv,inv_val⟩
    end

protected def  linv (x : Gm R) : (Gm R) := ⟨x.inv,x.val,x.inv_val,x.val_inv⟩
--- Perhaps i can drop inv_val cause commutativity (all Ring are commutative :D)
/-- Gm herite de la structure de la multiplication de R -/
protected def mul (u₁ u₂ : Gm R) : Gm R :=
⟨u₁.val * u₂.val,
u₂.inv * u₁.inv,
have u₁.val * (u₂.val * u₂.inv) * u₁.inv = 1, by
begin
    let a  := u₁.val,
    let b  := u₂.val,
    let b' := u₂.inv,
    let a' := u₁.inv,
    show  a * (b * b') * a' = 1, from
        calc
            a * ( b * b' ) * a' =  (a * 1) * a'     : by exact congr_arg (λ l, a * l  * a')  (u₂.val_inv)
            ...                 =   a * a'          : by rw mul_one a
            ...                 =   1               : by rw u₁.val_inv,
end,
by simpa only [mul_assoc],
have u₂.inv * (u₁.inv *u₁.val) * u₂.val  = 1, by
begin
    let b'  := u₁.val,
    let a'  := u₂.val,
    let a   := u₂.inv,
    let b   := u₁.inv,
    show  a * (b * b') * a' = 1, from
        calc
            a * ( b * b' ) * a' =  (a * 1) * a'     : by exact congr_arg (λ l, a * l  * a')  (u₁.inv_val)
            ...                 =   a * a'          : by rw mul_one a
            ...                 =   1               : by rw u₂.inv_val,
end,
by simpa only [mul_assoc],  -- what is simpa  = Sympathique ???
⟩
instance : has_mul (Gm R) := ⟨Gm.mul⟩
instance : has_one (Gm R) := ⟨⟨1, 1, mul_one 1, one_mul 1⟩⟩
instance : has_inv (Gm R) := ⟨Gm.linv⟩
end Gm


namespace prop_16

variables A B : Prop

theorem prop_16 : ((¬ A → B) ∧ (¬ A → ¬ B)) → A :=
assume h1: (¬ A → B) ∧ (¬ A → ¬ B),
have h2: ¬ A → B, from and.left h1,
have h3: ¬ A → ¬ B, from and.right h1,
show A, from (classical.by_contradiction
    (assume h4: ¬ A,
     have h5: B, from h2 h4,
     have h6: ¬ B, from h3 h4,
     show false, from h6 h5))

-- end namespace
end prop_16


import linear_algebra.finite_dimensional
import analysis.normed_space.inner_product
import data.complex.basic

/--
Hilbert space
-/
class complex_hilbert_space (V : Type) extends inner_product_space ℂ V :=
(hilbert_ness: 1=1)

variables {ℋ : Type} [complex_hilbert_space ℋ]

#check ∥(0 : ℋ)∥

/--
The Trace of a linear map on a complex Hilbert space.
-/
noncomputable def Tr := linear_map.trace ℂ ℋ

/--
A quantum state is a linear map from a Hilbert space to itself --
a.k.a., a linear operator -- with trace one and an additional axiom.
-/
class quantum_state (ρ : ℋ →ₗ[ℂ] ℋ) :=
(trace_one : Tr ρ = 1)
(axiom_2 : 1=1)

variables {ρ σ : ℋ →ₗ[ℂ] ℋ} [quantum_state ρ] [quantum_state σ]

example : Tr ρ = 1 :=
begin
    exact quantum_state.trace_one,
end