#exit

import Structure.Generic.Axioms
import Structure.Generic.DerivedFunctors

import mathlib4_experiments.Data.Equiv.Basic

open GeneralizedRelation

set_option autoBoundImplicitLocal false

universes u v w

namespace GeneralizedProperty

  def ConstProp (α : Sort u) {V : Universe} {β : V} (c : β) : GeneralizedProperty α V := λ _ => β

  namespace ConstProp

    variable (α : Sort u) {V : Universe} {β : V} (c : β)

    instance hasInst : HasInst (ConstProp α c) := ⟨λ _ => c⟩

  end ConstProp

end GeneralizedProperty

namespace GeneralizedRelation

  def ConstRel (α : Sort u) {V : Universe} {β : V} (c : β) : GeneralizedRelation α V := λ _ _ => β

  namespace ConstRel

    variable (α : Sort u) {V : Universe} {β : V} (c : β)

    instance hasRefl  : HasRefl  (ConstRel α c) := ⟨λ _ => c⟩

    variable [HasInternalFunctors V] [HasAffineFunOp V]

    instance hasSymm  : HasSymm  (ConstRel α c) := ⟨HasLinearFunOp.idFun β⟩
    instance hasTrans : HasTrans (ConstRel α c) := ⟨HasAffineFunOp.constFun β (HasLinearFunOp.idFun β)⟩

    instance isPreorder    : IsPreorder    (ConstRel α c) := ⟨⟩
    instance isEquivalence : IsEquivalence (ConstRel α c) := ⟨⟩

    @[simp] theorem symmEq  {a₁ a₂    : α} : (hasSymm  α c).symm'  (a := a₁) (b := a₂)           c   = c :=
    HasLinearFunOp.idFun.eff β c
    @[simp] theorem transEq {a₁ a₂ a₃ : α} : (hasTrans α c).trans' (a := a₁) (b := a₂) (c := a₃) c c = c :=
    let h₁ := congrArg HasInternalFunctors.funCoe (HasAffineFunOp.constFun.eff β (HasLinearFunOp.idFun β) c);
    Eq.trans (congrFun h₁ c) (HasLinearFunOp.idFun.eff β c)

  end ConstRel

end GeneralizedRelation

open GeneralizedRelation

-- TODO: Should we use actual abstract equivalence here?

structure Iff' {U : Universe} [HasInternalFunctors U] (α β : U) where
(mp  : α ⟶ β)
(mpr : β ⟶ α)

infix:20 " ⟷' " => Iff'

section Morphisms

  variable {α : Sort u} {U : Universe} [HasInternalFunctors U] [he : HasFunctorialEquivalences U]
           [HasLinearFunOp he.equivUniverse] {R : GeneralizedRelation α U}

  instance : HasFunctorialArrows U := HasFunctorialEquivalences.toHasFunctorialArrows U

  open IsCompositionRelation

  namespace IsCompositionRelation

    variable [HasTrans R] [IsCompositionRelation R]

    def comp_congrArg {a b c : α} {f₁ f₂ : R a b} {g₁ g₂ : R b c} : f₁ ≃ f₂ ⟶ g₁ ≃ g₂ ⟶ g₁ • f₁ ≃ g₂ • f₂ :=
    he.equivCongr ⊙ HasFunctorialEquivalences.equiv_congrArg U HasTrans.trans

    def comp_congrArg_left  {a b c : α} {f : R a b} {g₁ g₂ : R b c} : g₁ ≃ g₂ ⟶ g₁ • f ≃ g₂ • f :=
    HasFunctorialEquivalences.equiv_congrArg U (HasTrans.trans ⟮f⟯)

    def comp_congrArg_right {a b c : α} {f₁ f₂ : R a b} {g : R b c} : f₁ ≃ f₂ ⟶ g • f₁ ≃ g • f₂ :=
    HasLinearFunOp.swapFun comp_congrArg (HasRefl.refl g)

    def comp_subst  {a b c : α} {f₁ f₂ : R a b} {g₁ g₂ : R b c} {e : R a c} : f₁ ≃ f₂ ⟶ g₁ ≃ g₂ ⟶ g₂ • f₂ ≃ e ⟶ g₁ • f₁ ≃ e :=
    HasLinearFunOp.compFun₂ comp_congrArg HasTrans.trans
    def comp_subst' {a b c : α} {f₁ f₂ : R a b} {g₁ g₂ : R b c} {e : R a c} : f₁ ≃ f₂ ⟶ g₁ ≃ g₂ ⟶ e ≃ g₁ • f₁ ⟶ e ≃ g₂ • f₂ :=
    HasLinearFunOp.compFun₂ comp_congrArg HasTrans.revTrans

    def comp_subst_left   {a b c : α} (f : R a b) {g₁ g₂ : R b c} {e : R a c} : g₁ ≃ g₂ ⟶ g₂ • f ≃ e ⟶ g₁ • f ≃ e :=
    HasTrans.trans    ⊙ comp_congrArg_left
    def comp_subst_left'  {a b c : α} (f : R a b) {g₁ g₂ : R b c} {e : R a c} : g₁ ≃ g₂ ⟶ e ≃ g₁ • f ⟶ e ≃ g₂ • f :=
    HasTrans.revTrans ⊙ comp_congrArg_left

    def comp_subst_right  {a b c : α} {f₁ f₂ : R a b} (g : R b c) {e : R a c} : f₁ ≃ f₂ ⟶ g • f₂ ≃ e ⟶ g • f₁ ≃ e :=
    HasTrans.trans    ⊙ comp_congrArg_right
    def comp_subst_right' {a b c : α} {f₁ f₂ : R a b} (g : R b c) {e : R a c} : f₁ ≃ f₂ ⟶ e ≃ g • f₁ ⟶ e ≃ g • f₂ :=
    HasTrans.revTrans ⊙ comp_congrArg_right

    def applyAssocLR_left  {a b c d : α} {f : R a b} {g : R b c} {h : R c d} {e : R a d} : (h • g) • f ≃ e ⟶ h • (g • f) ≃ e :=
    HasTrans.trans ⟮assocRL f g h⟯
    def applyAssocRL_left  {a b c d : α} {f : R a b} {g : R b c} {h : R c d} {e : R a d} : h • (g • f) ≃ e ⟶ (h • g) • f ≃ e :=
    HasTrans.trans ⟮assocLR f g h⟯
    def applyAssocLR_right {a b c d : α} {f : R a b} {g : R b c} {h : R c d} {e : R a d} : e ≃ (h • g) • f ⟶ e ≃ h • (g • f) :=
    HasTrans.revTrans'' (assocLR f g h)
    def applyAssocRL_right {a b c d : α} {f : R a b} {g : R b c} {h : R c d} {e : R a d} : e ≃ h • (g • f) ⟶ e ≃ (h • g) • f :=
    HasTrans.revTrans'' (assocRL f g h)

    def applyAssocLR {a β₁ β₂ γ₁ γ₂ d : α} {f₁ : R a β₁} {f₂ : R a β₂} {g₁ : R β₁ γ₁} {g₂ : R β₂ γ₂} {h₁ : R γ₁ d} {h₂ : R γ₂ d} :
      (h₁ • g₁) • f₁ ≃ (h₂ • g₂) • f₂ ⟶ h₁ • (g₁ • f₁) ≃ h₂ • (g₂ • f₂) :=
    applyAssocLR_right ⊙ applyAssocLR_left
    def applyAssocRL {a β₁ β₂ γ₁ γ₂ d : α} {f₁ : R a β₁} {f₂ : R a β₂} {g₁ : R β₁ γ₁} {g₂ : R β₂ γ₂} {h₁ : R γ₁ d} {h₂ : R γ₂ d} :
      h₁ • (g₁ • f₁) ≃ h₂ • (g₂ • f₂) ⟶ (h₁ • g₁) • f₁ ≃ (h₂ • g₂) • f₂ :=
    applyAssocRL_right ⊙ applyAssocRL_left

  end IsCompositionRelation

  namespace IsMorphismRelation

    variable [IsPreorder R] [IsMorphismRelation R]

    def leftCancelId  {a b : α} (f : R a b) {e : R b b} : e ≃ ident R b ⟶ e • f ≃ f :=
    HasLinearFunOp.swapFun (comp_subst_left  f) (leftId  f)
    def rightCancelId {a b : α} (f : R a b) {e : R a a} : e ≃ ident R a ⟶ f • e ≃ f :=
    HasLinearFunOp.swapFun (comp_subst_right f) (rightId f)

  end IsMorphismRelation

  open IsMorphismRelation

  namespace IsIsomorphismRelation

    variable [IsEquivalence R] [IsIsomorphismRelation R]

    def inv_congrArg {a b : α} {f₁ f₂ : R a b} : f₁ ≃ f₂ ⟶ f₁⁻¹ ≃ f₂⁻¹ :=
    HasFunctorialEquivalences.equiv_congrArg U HasSymm.symm

    def inv_subst  {a b : α} {f₁ f₂ : R a b} {e : R b a} : f₁ ≃ f₂ ⟶ f₂⁻¹ ≃ e ⟶ f₁⁻¹ ≃ e :=
    HasTrans.trans    ⊙ inv_congrArg
    def inv_subst' {a b : α} {f₁ f₂ : R a b} {e : R b a} : f₁ ≃ f₂ ⟶ e ≃ f₁⁻¹ ⟶ e ≃ f₂⁻¹ :=
    HasTrans.revTrans ⊙ inv_congrArg

    def leftCancel'     {a b c : α} (f : R a b) (g : R b c) : (g⁻¹ • g) • f ≃ f := (leftCancelId  f) (leftInv  g)
    def leftCancel      {a b c : α} (f : R a b) (g : R b c) : g⁻¹ • (g • f) ≃ f := applyAssocLR_left ⟮leftCancel'     f g⟯
    def leftCancelInv'  {a b c : α} (f : R a b) (g : R c b) : (g • g⁻¹) • f ≃ f := (leftCancelId  f) (rightInv g)
    def leftCancelInv   {a b c : α} (f : R a b) (g : R c b) : g • (g⁻¹ • f) ≃ f := applyAssocLR_left ⟮leftCancelInv'  f g⟯
    def rightCancel'    {a b c : α} (f : R a b) (g : R c a) : f • (g • g⁻¹) ≃ f := (rightCancelId f) (rightInv g)
    def rightCancel     {a b c : α} (f : R a b) (g : R c a) : (f • g) • g⁻¹ ≃ f := applyAssocRL_left ⟮rightCancel'    f g⟯
    def rightCancelInv' {a b c : α} (f : R a b) (g : R a c) : f • (g⁻¹ • g) ≃ f := (rightCancelId f) (leftInv  g)
    def rightCancelInv  {a b c : α} (f : R a b) (g : R a c) : (f • g⁻¹) • g ≃ f := applyAssocRL_left ⟮rightCancelInv' f g⟯

    def leftMulInv  {a b c : α} (f₁ : R a b) (f₂ : R a c) (g : R b c) : g • f₁ ≃ f₂ ⟷' f₁ ≃ g⁻¹ • f₂ :=
    ⟨HasLinearFunOp.swapFun (comp_subst_right' g⁻¹) (HasSymm.symm' (leftCancel f₁ g)),
     HasLinearFunOp.swapFun (comp_subst_right  g)   (leftCancelInv f₂ g)⟩
    def leftMulInv' {a b c : α} (f₁ : R a b) (f₂ : R a c) (g : R c b) : g⁻¹ • f₁ ≃ f₂ ⟷' f₁ ≃ g • f₂ :=
    ⟨HasLinearFunOp.swapFun (comp_subst_right' g)   (HasSymm.symm' (leftCancelInv f₁ g)),
     HasLinearFunOp.swapFun (comp_subst_right  g⁻¹) (leftCancel f₂ g)⟩

    def leftMul {a b c : α} (f₁ f₂ : R a b) (g : R b c) : g • f₁ ≃ g • f₂ ⟷' f₁ ≃ f₂ :=
    ⟨HasTrans.revTrans'' (leftCancel f₂ g) ⊙ (leftMulInv f₁ (g • f₂) g).mp, comp_congrArg_right⟩

    def rightMulInv  {a b c : α} (f₁ : R a c) (f₂ : R b c) (g : R b a) : f₁ • g ≃ f₂ ⟷' f₁ ≃ f₂ • g⁻¹ :=
    ⟨HasLinearFunOp.swapFun (comp_subst_left' g⁻¹) (HasSymm.symm' (rightCancel f₁ g)),
     HasLinearFunOp.swapFun (comp_subst_left  g)   (rightCancelInv f₂ g)⟩
    def rightMulInv' {a b c : α} (f₁ : R a c) (f₂ : R b c) (g : R a b) : f₁ • g⁻¹ ≃ f₂ ⟷' f₁ ≃ f₂ • g :=
    ⟨HasLinearFunOp.swapFun (comp_subst_left' g)   (HasSymm.symm' (rightCancelInv f₁ g)),
     HasLinearFunOp.swapFun (comp_subst_left  g⁻¹) (rightCancel f₂ g)⟩

    def rightMul {a b c : α} (f₁ f₂ : R a b) (g : R c a) : f₁ • g ≃ f₂ • g ⟷' f₁ ≃ f₂ :=
    ⟨HasTrans.revTrans'' (rightCancel f₂ g) ⊙ (rightMulInv f₁ (f₂ • g) g).mp, comp_congrArg_left⟩

    def eqInvIffInvEq {a b : α} (f : R a b) (g : R b a) : f ≃ g⁻¹ ⟷' f⁻¹ ≃ g :=
    ⟨HasLinearFunOp.swapFun inv_subst  (invInv g),
     HasLinearFunOp.swapFun inv_subst' (HasSymm.symm' (invInv f))⟩

    def eqIffEqInv {a b : α} (f₁ f₂ : R a b) : f₁⁻¹ ≃ f₂⁻¹ ⟷' f₁ ≃ f₂ :=
    ⟨HasTrans.revTrans'' (invInv f₂) ⊙ (eqInvIffInvEq f₁ f₂⁻¹).mpr, inv_congrArg⟩

    def leftRightMul {a b c d : α} (f₁ : R a b) (f₂ : R a c) (g₁ : R b d) (g₂ : R c d) :
      g₂⁻¹ • g₁ ≃ f₂ • f₁⁻¹ ⟷' g₁ • f₁ ≃ g₂ • f₂ :=
    ⟨(leftMulInv' (g₁ • f₁) f₂ g₂).mp ⊙ applyAssocLR_left ⊙ (rightMulInv (g₂⁻¹ • g₁) f₂ f₁).mpr,
     (leftMulInv' g₁ (f₂ • f₁⁻¹) g₂).mpr ⊙ applyAssocLR_right ⊙ (rightMulInv g₁ (g₂ • f₂) f₁).mp⟩

    def swapInv  {a b c d : α} (f₁ : R a b) (f₂ : R c d) (g₁ : R d b) (g₂ : R c a) :
      g₁⁻¹ • f₁ ≃ f₂ • g₂⁻¹ ⟶ f₁⁻¹ • g₁ ≃ g₂ • f₂⁻¹ :=
    (leftRightMul f₂ g₂ g₁ f₁).mpr ⊙ HasSymm.symm ⊙ (leftRightMul g₂ f₂ f₁ g₁).mp

    def swapInv' {a b c d : α} (f₁ : R a b) (f₂ : R c d) (g₁ : R d b) (g₂ : R c a) :
      f₂ • g₂⁻¹ ≃ g₁⁻¹ • f₁ ⟶ g₂ • f₂⁻¹ ≃ f₁⁻¹ • g₁ :=
    HasSymm.symm ⊙ swapInv f₁ f₂ g₁ g₂ ⊙ HasSymm.symm

  end IsIsomorphismRelation

  open IsIsomorphismRelation

end Morphisms

section Functors

  variable {α : Sort u}

  namespace idFun

    variable {U : Universe} [HasInstanceArrows U] [HasExternalFunctors U U]
             [HasIdFun U] (R : GeneralizedRelation α U)

    instance isReflFunctor  [HasRefl  R] : IsReflFunctor  R R (HasIdFun.idFun' _) :=
    ⟨λ a   => HasRefl.refl (ident R a)⟩

    variable [HasInternalFunctors U]

    instance isSymmFunctor  [HasSymm  R] : IsSymmFunctor  R R (HasIdFun.idFun' _) :=
    ⟨λ f   => HasRefl.refl f⁻¹⟩

    instance isTransFunctor [HasTrans R] : IsTransFunctor R R (HasIdFun.idFun' _) :=
    ⟨λ f g => HasRefl.refl (g • f)⟩

    instance isPreorderFunctor    [IsPreorder    R] : IsPreorderFunctor    R R (HasIdFun.idFun' _) := ⟨⟩
    instance isEquivalenceFunctor [IsEquivalence R] : IsEquivalenceFunctor R R (HasIdFun.idFun' _) := ⟨⟩

  end idFun

  namespace constFun

    variable {U V : Universe} [HasInstanceArrows V] [HasExternalFunctors U V]
             [HasConstFun U V] (R : GeneralizedRelation α U) {β : V} (c : β)

    def idArrow : c ⇝ c := ident (HasInstanceArrows.Arrow β) c

    instance isReflFunctor  [HasRefl  R] : IsReflFunctor  R (ConstRel α c) (HasConstFun.constFun' _ c) :=
    ⟨λ _   => idArrow c⟩

    variable [HasInternalFunctors U] [HasInternalFunctors V] [HasAffineFunOp V]

    instance isSymmFunctor  [HasSymm  R] : IsSymmFunctor  R (ConstRel α c) (HasConstFun.constFun' _ c) :=
    ⟨λ _   => Eq.ndrec (motive := λ b : β => ⌈c ⇝ b⌉) (idArrow c) (Eq.symm (ConstRel.symmEq  α c))⟩

    instance isTransFunctor [HasTrans R] : IsTransFunctor R (ConstRel α c) (HasConstFun.constFun' _ c) :=
    ⟨λ _ _ => Eq.ndrec (motive := λ b : β => ⌈c ⇝ b⌉) (idArrow c) (Eq.symm (ConstRel.transEq α c))⟩

    instance isPreorderFunctor    [IsPreorder    R] : IsPreorderFunctor    R (ConstRel α c) (HasConstFun.constFun' _ c) := ⟨⟩
    instance isEquivalenceFunctor [IsEquivalence R] : IsEquivalenceFunctor R (ConstRel α c) (HasConstFun.constFun' _ c) := ⟨⟩

  end constFun

  namespace compFun

    variable {U V W : Universe} [hV : HasInstanceArrows V] [hW : HasInstanceArrows W]
             [HasExternalFunctors U V] [HasExternalFunctors V W] [HasExternalFunctors U W]
             [HasExternalFunctors hV.arrowUniverse hW.arrowUniverse] [HasArrowCongrArg V W]
             [HasCompFun U V W]
             {R : GeneralizedRelation α U} {S : GeneralizedRelation α V} {T : GeneralizedRelation α W}
             (F : BaseFunctor R S) (G : BaseFunctor S T)

    instance isReflFunctor  [HasRefl  R] [HasRefl  S] [HasRefl  T] [hF : IsReflFunctor  R S F] [hG : IsReflFunctor  S T G] :
      IsReflFunctor  R T (G ⊙' F) :=
    ⟨λ a   => let e₁ : G (F (ident R a)) ⇝ G (ident S a) := HasArrowCongrArg.arrowCongrArg G (hF.respectsRefl a);
              let e₂ : G (ident S a) ⇝ ident T a         := hG.respectsRefl a;
              HasTrans.trans' e₁ e₂⟩

    variable [HasInternalFunctors U] [HasInternalFunctors V] [HasInternalFunctors W]

    instance isSymmFunctor  [HasSymm  R] [HasSymm  S] [HasSymm  T] [hF : IsSymmFunctor  R S F] [hG : IsSymmFunctor  S T G] :
      IsSymmFunctor  R T (G ⊙' F) :=
    ⟨λ f   => let e₁ : G (F f⁻¹) ⇝ G (F f)⁻¹   := HasArrowCongrArg.arrowCongrArg G (hF.respectsSymm f);
              let e₂ : G (F f)⁻¹ ⇝ (G (F f))⁻¹ := hG.respectsSymm (F f);
              HasTrans.trans' e₁ e₂⟩

    instance isTransFunctor [HasTrans R] [HasTrans S] [HasTrans T] [hF : IsTransFunctor R S F] [hG : IsTransFunctor S T G] :
      IsTransFunctor R T (G ⊙' F) :=
    ⟨λ f g => let e₁ : G (F (g • f)) ⇝ G (F g • F f)     := HasArrowCongrArg.arrowCongrArg G (hF.respectsTrans f g);
              let e₂ : G (F g • F f) ⇝ G (F g) • G (F f) := hG.respectsTrans (F f) (F g);
              HasTrans.trans' e₁ e₂⟩

    instance isPreorderFunctor    [IsPreorder    R] [IsPreorder    S] [IsPreorder    T] [IsPreorderFunctor    R S F] [IsPreorderFunctor    S T G] :
      IsPreorderFunctor    R T (G ⊙' F) := ⟨⟩
    instance isEquivalenceFunctor [IsEquivalence R] [IsEquivalence S] [IsEquivalence T] [IsEquivalenceFunctor R S F] [IsEquivalenceFunctor S T G] :
      IsEquivalenceFunctor R T (G ⊙' F) := ⟨⟩

  end compFun

end Functors

section NaturalTransformations

  variable {α : Sort u} {V W : Universe} [HasInternalFunctors W] [hW : HasFunctorialEquivalences W] [HasExternalFunctors V W]
           {β : Sort w} (R : GeneralizedRelation α V) (S : GeneralizedRelation β W)

  instance : HasFunctorialArrows W := hW.toHasFunctorialArrows W

  namespace IsNaturalTransformation

    def refl  [IsPreorder    S] [h : IsMorphismRelation    S] {mF       : α → β}
              (F : ∀ {a b}, R a b ⟶' S (mF a) (mF b)) :
      IsNatural R S F F (λ a => ident S (mF a)) :=
    ⟨λ f => HasTrans.trans' (h.rightId (F f)) (HasSymm.symm' (h.leftId (F f)))⟩

    variable [HasLinearFunOp hW.equivUniverse]

    def symm  [IsEquivalence S] [h : IsIsomorphismRelation S] {mF mG    : α → β}
              (F : ∀ {a b}, R a b ⟶' S (mF a) (mF b)) (G : ∀ {a b}, R a b ⟶' S (mG a) (mG b))
              (n : ∀ a, S (mF a) (mG a)) [hn : IsNatural R S F G n] :
      IsNatural R S G F (λ a => (n a)⁻¹) :=
    ⟨λ {a b} f => HasSymm.symm' ((IsIsomorphismRelation.leftRightMul (n a) (F f) (G f) (n b)).mpr (hn.nat f))⟩

    def trans [HasTrans      S] [h : IsCompositionRelation S] {mF mG mH : α → β}
              (F : ∀ {a b}, R a b ⟶' S (mF a) (mF b)) (G : ∀ {a b}, R a b ⟶' S (mG a) (mG b)) (H : ∀ {a b}, R a b ⟶' S (mH a) (mH b))
              (nFG : ∀ a, S (mF a) (mG a))   (nGH : ∀ a, S (mG a) (mH a))
              [hnFG : IsNatural R S F G nFG] [hnGH : IsNatural R S G H nGH] :
      IsNatural R S F H (λ a => nGH a • nFG a) :=
    ⟨λ {a b} f => let e₁ := IsCompositionRelation.applyAssocLR_left ⟮IsCompositionRelation.comp_congrArg_left  (f := nFG a) (hnGH.nat f)⟯;
                  let e₂ := IsCompositionRelation.applyAssocRL      ⟮IsCompositionRelation.comp_congrArg_right (g := nGH b) (hnFG.nat f)⟯;
                  HasTrans.trans' e₁ e₂⟩

  end IsNaturalTransformation

end NaturalTransformations


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Floris van Doorn
-/
import category_theory.limits.shapes.finite_products
import category_theory.discrete_category

/-!
# Limits in `C` give colimits in `Cᵒᵖ`.

We also give special cases for (co)products,
but not yet for pullbacks / pushouts or for (co)equalizers.

-/

universes v₁ v₂ u₁ u₂

noncomputable theory

open category_theory
open category_theory.functor
open opposite

namespace category_theory.limits

variables {C : Type u₁} [category.{v₁} C]
variables {J : Type u₂} [category.{v₂} J]

/-- Turn a colimit for `F : J ⥤ C` into a limit for `F.op : Jᵒᵖ ⥤ Cᵒᵖ`. -/
@[simps] def is_limit_cocone_op (F : J ⥤ C) {c : cocone F} (hc : is_colimit c) :
  is_limit c.op :=
{ lift := λ s, (hc.desc s.unop).op,
  fac' := λ s j, quiver.hom.unop_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.unop_inj (hc.hom_ext (λ j, quiver.hom.op_inj _)),
    simpa only [quiver.hom.unop_op, is_colimit.fac] using w (op j)
  end }

/-- Turn a limit for `F : J ⥤ C` into a colimit for `F.op : Jᵒᵖ ⥤ Cᵒᵖ`. -/
@[simps] def is_colimit_cone_op (F : J ⥤ C) {c : cone F} (hc : is_limit c) :
  is_colimit c.op :=
{ desc := λ s, (hc.lift s.unop).op,
  fac' := λ s j, quiver.hom.unop_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.unop_inj (hc.hom_ext (λ j, quiver.hom.op_inj _)),
    simpa only [quiver.hom.unop_op, is_limit.fac] using w (op j)
  end }

/-- Turn a colimit for `F : J ⥤ Cᵒᵖ` into a limit for `F.left_op : Jᵒᵖ ⥤ C`. -/
@[simps] def is_limit_cone_left_op_of_cocone (F : J ⥤ Cᵒᵖ) {c : cocone F} (hc : is_colimit c) :
  is_limit (cone_left_op_of_cocone c) :=
{ lift := λ s, (hc.desc (cocone_of_cone_left_op s)).unop,
  fac' :=  λ s j, quiver.hom.op_inj $ by simpa only [cone_left_op_of_cocone_π_app, op_comp,
    quiver.hom.op_unop, is_colimit.fac, cocone_of_cone_left_op_ι_app],
  uniq' := λ s m w,
  begin
    refine quiver.hom.op_inj (hc.hom_ext (λ j, quiver.hom.unop_inj _)),
    simpa only [quiver.hom.op_unop, is_colimit.fac, cocone_of_cone_left_op_ι_app] using w (op j)
  end }

/-- Turn a limit of `F : J ⥤ Cᵒᵖ` into a colimit of `F.left_op : Jᵒᵖ ⥤ C`. -/
@[simps] def is_colimit_cocone_left_op_of_cone (F : J ⥤ Cᵒᵖ) {c : cone F} (hc : is_limit c) :
  is_colimit (cocone_left_op_of_cone c) :=
{ desc := λ s, (hc.lift (cone_of_cocone_left_op s)).unop,
  fac' := λ s j, quiver.hom.op_inj $ by simpa only [cocone_left_op_of_cone_ι_app, op_comp,
    quiver.hom.op_unop, is_limit.fac, cone_of_cocone_left_op_π_app],
  uniq' := λ s m w,
  begin
    refine quiver.hom.op_inj (hc.hom_ext (λ j, quiver.hom.unop_inj _)),
    simpa only [quiver.hom.op_unop, is_limit.fac, cone_of_cocone_left_op_π_app] using w (op j)
  end }

/-- Turn a colimit for `F : Jᵒᵖ ⥤ C` into a limit for `F.right_op : J ⥤ Cᵒᵖ`. -/
@[simps] def is_limit_cone_right_op_of_cocone (F : Jᵒᵖ ⥤ C) {c : cocone F} (hc : is_colimit c) :
  is_limit (cone_right_op_of_cocone c) :=
{ lift := λ s, (hc.desc (cocone_of_cone_right_op s)).op,
  fac' := λ s j, quiver.hom.unop_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.unop_inj (hc.hom_ext (λ j, quiver.hom.op_inj _)),
    simpa only [quiver.hom.unop_op, is_colimit.fac] using w (unop j)
  end }

/-- Turn a limit for `F : Jᵒᵖ ⥤ C` into a colimit for `F.right_op : J ⥤ Cᵒᵖ`. -/
@[simps] def is_colimit_cocone_right_op_of_cone (F : Jᵒᵖ ⥤ C) {c : cone F} (hc : is_limit c) :
  is_colimit (cocone_right_op_of_cone c) :=
{ desc := λ s, (hc.lift (cone_of_cocone_right_op s)).op,
  fac' := λ s j, quiver.hom.unop_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.unop_inj (hc.hom_ext (λ j, quiver.hom.op_inj _)),
    simpa only [quiver.hom.unop_op, is_limit.fac] using w (unop j)
  end }

/-- Turn a colimit for `F : Jᵒᵖ ⥤ Cᵒᵖ` into a limit for `F.unop : J ⥤ C`. -/
@[simps] def is_limit_cone_unop_of_cocone (F : Jᵒᵖ ⥤ Cᵒᵖ) {c : cocone F} (hc : is_colimit c) :
  is_limit (cone_unop_of_cocone c) :=
{ lift := λ s, (hc.desc (cocone_of_cone_unop s)).unop,
  fac' := λ s j, quiver.hom.op_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.op_inj (hc.hom_ext (λ j, quiver.hom.unop_inj _)),
    simpa only [quiver.hom.op_unop, is_colimit.fac] using w (unop j)
  end }

/-- Turn a limit of `F : Jᵒᵖ ⥤ Cᵒᵖ` into a colimit of `F.unop : J ⥤ C`. -/
@[simps] def is_colimit_cocone_unop_of_cone (F : Jᵒᵖ ⥤ Cᵒᵖ) {c : cone F} (hc : is_limit c) :
  is_colimit (cocone_unop_of_cone c) :=
{ desc := λ s, (hc.lift (cone_of_cocone_unop s)).unop,
  fac' := λ s j, quiver.hom.op_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.op_inj (hc.hom_ext (λ j, quiver.hom.unop_inj _)),
    simpa only [quiver.hom.op_unop, is_limit.fac] using w (unop j)
  end }

/-- Turn a colimit for `F.op : Jᵒᵖ ⥤ Cᵒᵖ` into a limit for `F : J ⥤ C`. -/
@[simps] def is_limit_cocone_unop (F : J ⥤ C) {c : cocone F.op} (hc : is_colimit c) :
  is_limit c.unop :=
{ lift := λ s, (hc.desc s.op).unop,
  fac' := λ s j, quiver.hom.op_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.op_inj (hc.hom_ext (λ j, quiver.hom.unop_inj _)),
    simpa only [quiver.hom.op_unop, is_colimit.fac] using w (unop j)
  end }

/-- Turn a limit for `F.op : Jᵒᵖ ⥤ Cᵒᵖ` into a colimit for `F : J ⥤ C`. -/
@[simps] def is_colimit_cone_unop (F : J ⥤ C) {c : cone F.op} (hc : is_limit c) :
  is_colimit c.unop :=
{ desc := λ s, (hc.lift s.op).unop,
  fac' := λ s j, quiver.hom.op_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.op_inj (hc.hom_ext (λ j, quiver.hom.unop_inj _)),
    simpa only [quiver.hom.op_unop, is_limit.fac] using w (unop j)
  end }

/-- Turn a colimit for `F.left_op : Jᵒᵖ ⥤ C` into a limit for `F : J ⥤ Cᵒᵖ`. -/
@[simps] def is_limit_cone_of_cocone_left_op (F : J ⥤ Cᵒᵖ) {c : cocone F.left_op}
  (hc : is_colimit c) : is_limit (cone_of_cocone_left_op c) :=
{ lift := λ s, (hc.desc (cocone_left_op_of_cone s)).op,
  fac' := λ s j, quiver.hom.unop_inj $ by simpa only [cone_of_cocone_left_op_π_app, unop_comp,
    quiver.hom.unop_op, is_colimit.fac, cocone_left_op_of_cone_ι_app],
  uniq' := λ s m w,
  begin
    refine quiver.hom.unop_inj (hc.hom_ext (λ j, quiver.hom.op_inj _)),
    simpa only [quiver.hom.unop_op, is_colimit.fac, cone_of_cocone_left_op_π_app] using w (unop j)
  end }

/-- Turn a limit of `F.left_op : Jᵒᵖ ⥤ C` into a colimit of `F : J ⥤ Cᵒᵖ`. -/
@[simps] def is_colimit_cocone_of_cone_left_op (F : J ⥤ Cᵒᵖ) {c : cone (F.left_op)}
  (hc : is_limit c) : is_colimit (cocone_of_cone_left_op c) :=
{ desc := λ s, (hc.lift (cone_left_op_of_cocone s)).op,
  fac' := λ s j, quiver.hom.unop_inj $ by simpa only [cocone_of_cone_left_op_ι_app, unop_comp,
    quiver.hom.unop_op, is_limit.fac, cone_left_op_of_cocone_π_app],
  uniq' := λ s m w,
  begin
    refine quiver.hom.unop_inj (hc.hom_ext (λ j, quiver.hom.op_inj _)),
    simpa only [quiver.hom.unop_op, is_limit.fac, cocone_of_cone_left_op_ι_app] using w (unop j)
  end }

/-- Turn a colimit for `F.right_op : J ⥤ Cᵒᵖ` into a limit for `F : Jᵒᵖ ⥤ C`. -/
@[simps] def is_limit_cone_of_cocone_right_op (F : Jᵒᵖ ⥤ C) {c : cocone F.right_op}
  (hc : is_colimit c) : is_limit (cone_of_cocone_right_op c) :=
{ lift := λ s, (hc.desc (cocone_right_op_of_cone s)).unop,
  fac' := λ s j, quiver.hom.op_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.op_inj (hc.hom_ext (λ j, quiver.hom.unop_inj _)),
    simpa only [quiver.hom.op_unop, is_colimit.fac] using w (op j)
  end }

/-- Turn a limit for `F.right_op : J ⥤ Cᵒᵖ` into a limit for `F : Jᵒᵖ ⥤ C`. -/
@[simps] def is_colimit_cocone_of_cone_right_op (F : Jᵒᵖ ⥤ C) {c : cone F.right_op}
  (hc : is_limit c) : is_colimit (cocone_of_cone_right_op c) :=
{ desc := λ s, (hc.lift (cone_right_op_of_cocone s)).unop,
  fac' := λ s j, quiver.hom.op_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.op_inj (hc.hom_ext (λ j, quiver.hom.unop_inj _)),
    simpa only [quiver.hom.op_unop, is_limit.fac] using w (op j)
  end }

/-- Turn a colimit for `F.unop : J ⥤ C` into a limit for `F : Jᵒᵖ ⥤ Cᵒᵖ`. -/
@[simps] def is_limit_cone_of_cocone_unop (F : Jᵒᵖ ⥤ Cᵒᵖ) {c : cocone F.unop} (hc : is_colimit c) :
  is_limit (cone_of_cocone_unop c) :=
{ lift := λ s, (hc.desc (cocone_unop_of_cone s)).op,
  fac' := λ s j, quiver.hom.unop_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.unop_inj (hc.hom_ext (λ j, quiver.hom.op_inj _)),
    simpa only [quiver.hom.unop_op, is_colimit.fac] using w (op j)
  end }

/-- Turn a limit for `F.unop : J ⥤ C` into a colimit for `F : Jᵒᵖ ⥤ Cᵒᵖ`. -/
@[simps] def is_colimit_cone_of_cocone_unop (F : Jᵒᵖ ⥤ Cᵒᵖ) {c : cone F.unop} (hc : is_limit c) :
  is_colimit (cocone_of_cone_unop c) :=
{ desc := λ s, (hc.lift (cone_unop_of_cocone s)).op,
  fac' := λ s j, quiver.hom.unop_inj (by simpa),
  uniq' := λ s m w,
  begin
    refine quiver.hom.unop_inj (hc.hom_ext (λ j, quiver.hom.op_inj _)),
    simpa only [quiver.hom.unop_op, is_limit.fac] using w (op j)
  end }

/--
If `F.left_op : Jᵒᵖ ⥤ C` has a colimit, we can construct a limit for `F : J ⥤ Cᵒᵖ`.
-/
lemma has_limit_of_has_colimit_left_op (F : J ⥤ Cᵒᵖ) [has_colimit F.left_op] : has_limit F :=
has_limit.mk
{ cone := cone_of_cocone_left_op (colimit.cocone F.left_op),
  is_limit := is_limit_cone_of_cocone_left_op _ (colimit.is_colimit _) }

/--
If `C` has colimits of shape `Jᵒᵖ`, we can construct limits in `Cᵒᵖ` of shape `J`.
-/
lemma has_limits_of_shape_op_of_has_colimits_of_shape [has_colimits_of_shape Jᵒᵖ C] :
  has_limits_of_shape J Cᵒᵖ :=
{ has_limit := λ F, has_limit_of_has_colimit_left_op F }

local attribute [instance] has_limits_of_shape_op_of_has_colimits_of_shape

/--
If `C` has colimits, we can construct limits for `Cᵒᵖ`.
-/
lemma has_limits_op_of_has_colimits [has_colimits C] : has_limits Cᵒᵖ := ⟨infer_instance⟩

/--
If `F.left_op : Jᵒᵖ ⥤ C` has a limit, we can construct a colimit for `F : J ⥤ Cᵒᵖ`.
-/
lemma has_colimit_of_has_limit_left_op (F : J ⥤ Cᵒᵖ) [has_limit F.left_op] : has_colimit F :=
has_colimit.mk
{ cocone := cocone_of_cone_left_op (limit.cone F.left_op),
  is_colimit := is_colimit_cocone_of_cone_left_op _ (limit.is_limit _) }

/--
If `C` has colimits of shape `Jᵒᵖ`, we can construct limits in `Cᵒᵖ` of shape `J`.
-/
lemma has_colimits_of_shape_op_of_has_limits_of_shape [has_limits_of_shape Jᵒᵖ C] :
  has_colimits_of_shape J Cᵒᵖ :=
{ has_colimit := λ F, has_colimit_of_has_limit_left_op F }

local attribute [instance] has_colimits_of_shape_op_of_has_limits_of_shape

/--
If `C` has limits, we can construct colimits for `Cᵒᵖ`.
-/
lemma has_colimits_op_of_has_limits [has_limits C] : has_colimits Cᵒᵖ := ⟨infer_instance⟩

variables (X : Type v₁)
/--
If `C` has products indexed by `X`, then `Cᵒᵖ` has coproducts indexed by `X`.
-/
lemma has_coproducts_opposite [has_products_of_shape X C] :
  has_coproducts_of_shape X Cᵒᵖ :=
begin
  haveI : has_limits_of_shape (discrete X)ᵒᵖ C :=
    has_limits_of_shape_of_equivalence (discrete.opposite X).symm,
  apply_instance
end

/--
If `C` has coproducts indexed by `X`, then `Cᵒᵖ` has products indexed by `X`.
-/
lemma has_products_opposite [has_coproducts_of_shape X C] :
  has_products_of_shape X Cᵒᵖ :=
begin
  haveI : has_colimits_of_shape (discrete X)ᵒᵖ C :=
    has_colimits_of_shape_of_equivalence (discrete.opposite X).symm,
  apply_instance
end

lemma has_finite_coproducts_opposite [has_finite_products C] :
  has_finite_coproducts Cᵒᵖ :=
{ out := λ J 𝒟 𝒥, begin
    resetI,
    haveI : has_limits_of_shape (discrete J)ᵒᵖ C :=
      has_limits_of_shape_of_equivalence (discrete.opposite J).symm,
    apply_instance,
  end }

lemma has_finite_products_opposite [has_finite_coproducts C] :
  has_finite_products Cᵒᵖ :=
{ out := λ J 𝒟 𝒥, begin
    resetI,
    haveI : has_colimits_of_shape (discrete J)ᵒᵖ C :=
      has_colimits_of_shape_of_equivalence (discrete.opposite J).symm,
    apply_instance,
  end }

lemma has_equalizers_opposite [has_coequalizers C] : has_equalizers Cᵒᵖ :=
begin
  haveI : has_colimits_of_shape walking_parallel_pair.{v₁}ᵒᵖ C :=
    has_colimits_of_shape_of_equivalence walking_parallel_pair_op_equiv.{v₁},
  apply_instance
end

lemma has_coequalizers_opposite [has_equalizers C] : has_coequalizers Cᵒᵖ :=
begin
  haveI : has_limits_of_shape walking_parallel_pair.{v₁}ᵒᵖ C :=
    has_limits_of_shape_of_equivalence walking_parallel_pair_op_equiv.{v₁},
  apply_instance
end

lemma has_finite_colimits_opposite [has_finite_limits C] :
  has_finite_colimits Cᵒᵖ :=
{ out := λ J 𝒟 𝒥, by { resetI, apply_instance, }, }

lemma has_finite_limits_opposite [has_finite_colimits C] :
  has_finite_limits Cᵒᵖ :=
{ out := λ J 𝒟 𝒥, by { resetI, apply_instance, }, }

end category_theory.limits


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Kenny Lau
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.module.pi
import Mathlib.algebra.big_operators.basic
import Mathlib.data.set.finite
import Mathlib.group_theory.submonoid.basic
import Mathlib.PostPort

universes u v l v₁ v₂ w u_1 u_2 u_3 u_4 u₁ x

namespace Mathlib

/-!
# Dependent functions with finite support

For a non-dependent version see `data/finsupp.lean`.
-/

namespace dfinsupp

structure pre (ι : Type u) (β : ι → Type v) [(i : ι) → HasZero (β i)]
where
  to_fun : (i : ι) → β i
  pre_support : multiset ι
  zero : ∀ (i : ι), i ∈ pre_support ∨ to_fun i = 0

protected instance inhabited_pre (ι : Type u) (β : ι → Type v) [(i : ι) → HasZero (β i)] : Inhabited (pre ι β) :=
  { default := pre.mk (fun (i : ι) => 0) ∅ sorry }

protected instance pre.setoid (ι : Type u) (β : ι → Type v) [(i : ι) → HasZero (β i)] : setoid (pre ι β) :=
  setoid.mk (fun (x y : pre ι β) => ∀ (i : ι), pre.to_fun x i = pre.to_fun y i) sorry

end dfinsupp

/-- A dependent function `Π i, β i` with finite support. -/
def dfinsupp {ι : Type u} (β : ι → Type v) [(i : ι) → HasZero (β i)] :=
  quotient sorry

infixl:25 " →ₚ " => Mathlib.dfinsupp

namespace dfinsupp

protected instance has_coe_to_fun {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] : has_coe_to_fun (dfinsupp fun (i : ι) => β i) :=
  has_coe_to_fun.mk (fun (_x : dfinsupp fun (i : ι) => β i) => (i : ι) → β i)
    fun (f : dfinsupp fun (i : ι) => β i) => quotient.lift_on f pre.to_fun sorry

protected instance has_zero {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] : HasZero (dfinsupp fun (i : ι) => β i) :=
  { zero := quotient.mk (pre.mk (fun (i : ι) => 0) ∅ sorry) }

protected instance inhabited {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] : Inhabited (dfinsupp fun (i : ι) => β i) :=
  { default := 0 }

@[simp] theorem zero_apply {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] (i : ι) : coe_fn 0 i = 0 :=
  rfl

theorem ext {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {f : dfinsupp fun (i : ι) => β i} {g : dfinsupp fun (i : ι) => β i} (H : ∀ (i : ι), coe_fn f i = coe_fn g i) : f = g := sorry

/-- The composition of `f : β₁ → β₂` and `g : Π₀ i, β₁ i` is
  `map_range f hf g : Π₀ i, β₂ i`, well defined when `f 0 = 0`. -/
def map_range {ι : Type u} {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] (f : (i : ι) → β₁ i → β₂ i) (hf : ∀ (i : ι), f i 0 = 0) (g : dfinsupp fun (i : ι) => β₁ i) : dfinsupp fun (i : ι) => β₂ i :=
  quotient.lift_on g
    (fun (x : pre ι fun (i : ι) => β₁ i) =>
      quotient.mk (pre.mk (fun (i : ι) => f i (pre.to_fun x i)) (pre.pre_support x) sorry))
    sorry

@[simp] theorem map_range_apply {ι : Type u} {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] (f : (i : ι) → β₁ i → β₂ i) (hf : ∀ (i : ι), f i 0 = 0) (g : dfinsupp fun (i : ι) => β₁ i) (i : ι) : coe_fn (map_range f hf g) i = f i (coe_fn g i) :=
  quotient.induction_on g fun (x : pre ι fun (i : ι) => β₁ i) => rfl

/-- Let `f i` be a binary operation `β₁ i → β₂ i → β i` such that `f i 0 0 = 0`.
Then `zip_with f hf` is a binary operation `Π₀ i, β₁ i → Π₀ i, β₂ i → Π₀ i, β i`. -/
def zip_with {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] (f : (i : ι) → β₁ i → β₂ i → β i) (hf : ∀ (i : ι), f i 0 0 = 0) (g₁ : dfinsupp fun (i : ι) => β₁ i) (g₂ : dfinsupp fun (i : ι) => β₂ i) : dfinsupp fun (i : ι) => β i :=
  quotient.lift_on₂ g₁ g₂
    (fun (x : pre ι fun (i : ι) => β₁ i) (y : pre ι fun (i : ι) => β₂ i) =>
      quotient.mk
        (pre.mk (fun (i : ι) => f i (pre.to_fun x i) (pre.to_fun y i)) (pre.pre_support x + pre.pre_support y) sorry))
    sorry

@[simp] theorem zip_with_apply {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] (f : (i : ι) → β₁ i → β₂ i → β i) (hf : ∀ (i : ι), f i 0 0 = 0) (g₁ : dfinsupp fun (i : ι) => β₁ i) (g₂ : dfinsupp fun (i : ι) => β₂ i) (i : ι) : coe_fn (zip_with f hf g₁ g₂) i = f i (coe_fn g₁ i) (coe_fn g₂ i) :=
  quotient.induction_on₂ g₁ g₂ fun (_x : pre ι fun (i : ι) => β₁ i) (_x_1 : pre ι fun (i : ι) => β₂ i) => rfl

protected instance has_add {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)] : Add (dfinsupp fun (i : ι) => β i) :=
  { add := zip_with (fun (_x : ι) => Add.add) sorry }

@[simp] theorem add_apply {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)] (g₁ : dfinsupp fun (i : ι) => β i) (g₂ : dfinsupp fun (i : ι) => β i) (i : ι) : coe_fn (g₁ + g₂) i = coe_fn g₁ i + coe_fn g₂ i :=
  zip_with_apply (fun (_x : ι) => Add.add) has_add._proof_1 g₁ g₂ i

protected instance add_monoid {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)] : add_monoid (dfinsupp fun (i : ι) => β i) :=
  add_monoid.mk Add.add sorry 0 sorry sorry

protected instance is_add_monoid_hom {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)] {i : ι} : is_add_monoid_hom fun (g : dfinsupp fun (i : ι) => β i) => coe_fn g i :=
  is_add_monoid_hom.mk (zero_apply i)

protected instance has_neg {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)] : Neg (dfinsupp fun (i : ι) => β i) :=
  { neg := fun (f : dfinsupp fun (i : ι) => β i) => map_range (fun (_x : ι) => Neg.neg) sorry f }

protected instance add_comm_monoid {ι : Type u} {β : ι → Type v} [(i : ι) → add_comm_monoid (β i)] : add_comm_monoid (dfinsupp fun (i : ι) => β i) :=
  add_comm_monoid.mk add_monoid.add sorry add_monoid.zero sorry sorry sorry

@[simp] theorem neg_apply {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)] (g : dfinsupp fun (i : ι) => β i) (i : ι) : coe_fn (-g) i = -coe_fn g i :=
  map_range_apply (fun (_x : ι) => Neg.neg) has_neg._proof_1 g i

protected instance add_group {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)] : add_group (dfinsupp fun (i : ι) => β i) :=
  add_group.mk add_monoid.add sorry add_monoid.zero sorry sorry Neg.neg
    (sub_neg_monoid.sub._default add_monoid.add sorry add_monoid.zero sorry sorry Neg.neg) sorry

@[simp] theorem sub_apply {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)] (g₁ : dfinsupp fun (i : ι) => β i) (g₂ : dfinsupp fun (i : ι) => β i) (i : ι) : coe_fn (g₁ - g₂) i = coe_fn g₁ i - coe_fn g₂ i := sorry

protected instance add_comm_group {ι : Type u} {β : ι → Type v} [(i : ι) → add_comm_group (β i)] : add_comm_group (dfinsupp fun (i : ι) => β i) :=
  add_comm_group.mk add_group.add sorry add_group.zero sorry sorry add_group.neg add_group.sub sorry sorry

/-- Dependent functions with finite support inherit a semiring action from an action on each
coordinate. -/
protected instance has_scalar {ι : Type u} {β : ι → Type v} {γ : Type w} [semiring γ] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] : has_scalar γ (dfinsupp fun (i : ι) => β i) :=
  has_scalar.mk fun (c : γ) (v : dfinsupp fun (i : ι) => β i) => map_range (fun (_x : ι) => has_scalar.smul c) sorry v

@[simp] theorem smul_apply {ι : Type u} {β : ι → Type v} {γ : Type w} [semiring γ] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] (b : γ) (v : dfinsupp fun (i : ι) => β i) (i : ι) : coe_fn (b • v) i = b • coe_fn v i :=
  map_range_apply (fun (_x : ι) => has_scalar.smul b) (has_scalar._proof_1 b) v i

/-- Dependent functions with finite support inherit a semimodule structure from such a structure on
each coordinate. -/
protected instance semimodule {ι : Type u} {β : ι → Type v} {γ : Type w} [semiring γ] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] : semimodule γ (dfinsupp fun (i : ι) => β i) :=
  semimodule.mk sorry sorry

/-- `filter p f` is the function which is `f i` if `p i` is true and 0 otherwise. -/
def filter {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] (p : ι → Prop) [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) : dfinsupp fun (i : ι) => β i :=
  quotient.lift_on f
    (fun (x : pre ι fun (i : ι) => β i) =>
      quotient.mk (pre.mk (fun (i : ι) => ite (p i) (pre.to_fun x i) 0) (pre.pre_support x) sorry))
    sorry

@[simp] theorem filter_apply {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] (p : ι → Prop) [decidable_pred p] (i : ι) (f : dfinsupp fun (i : ι) => β i) : coe_fn (filter p f) i = ite (p i) (coe_fn f i) 0 :=
  quotient.induction_on f fun (x : pre ι fun (i : ι) => β i) => rfl

theorem filter_apply_pos {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {p : ι → Prop} [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) {i : ι} (h : p i) : coe_fn (filter p f) i = coe_fn f i := sorry

theorem filter_apply_neg {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {p : ι → Prop} [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) {i : ι} (h : ¬p i) : coe_fn (filter p f) i = 0 := sorry

theorem filter_pos_add_filter_neg {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)] (f : dfinsupp fun (i : ι) => β i) (p : ι → Prop) [decidable_pred p] : filter p f + filter (fun (i : ι) => ¬p i) f = f := sorry

/-- `subtype_domain p f` is the restriction of the finitely supported function
  `f` to the subtype `p`. -/
def subtype_domain {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] (p : ι → Prop) [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) : dfinsupp fun (i : Subtype p) => β ↑i :=
  quotient.lift_on f
    (fun (x : pre ι fun (i : ι) => β i) =>
      quotient.mk
        (pre.mk (fun (i : Subtype p) => pre.to_fun x ↑i)
          (multiset.map
            (fun (j : Subtype fun (x_1 : ι) => x_1 ∈ multiset.filter p (pre.pre_support x)) =>
              { val := ↑j, property := sorry })
            (multiset.attach (multiset.filter p (pre.pre_support x))))
          sorry))
    sorry

@[simp] theorem subtype_domain_zero {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {p : ι → Prop} [decidable_pred p] : subtype_domain p 0 = 0 :=
  rfl

@[simp] theorem subtype_domain_apply {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] {p : ι → Prop} [decidable_pred p] {i : Subtype p} {v : dfinsupp fun (i : ι) => β i} : coe_fn (subtype_domain p v) i = coe_fn v ↑i :=
  quotient.induction_on v fun (x : pre ι fun (i : ι) => β i) => rfl

@[simp] theorem subtype_domain_add {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)] {p : ι → Prop} [decidable_pred p] {v : dfinsupp fun (i : ι) => β i} {v' : dfinsupp fun (i : ι) => β i} : subtype_domain p (v + v') = subtype_domain p v + subtype_domain p v' := sorry

protected instance subtype_domain.is_add_monoid_hom {ι : Type u} {β : ι → Type v} [(i : ι) → add_monoid (β i)] {p : ι → Prop} [decidable_pred p] : is_add_monoid_hom (subtype_domain p) :=
  is_add_monoid_hom.mk subtype_domain_zero

@[simp] theorem subtype_domain_neg {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)] {p : ι → Prop} [decidable_pred p] {v : dfinsupp fun (i : ι) => β i} : subtype_domain p (-v) = -subtype_domain p v := sorry

@[simp] theorem subtype_domain_sub {ι : Type u} {β : ι → Type v} [(i : ι) → add_group (β i)] {p : ι → Prop} [decidable_pred p] {v : dfinsupp fun (i : ι) => β i} {v' : dfinsupp fun (i : ι) => β i} : subtype_domain p (v - v') = subtype_domain p v - subtype_domain p v' := sorry

theorem finite_supp {ι : Type u} {β : ι → Type v} [(i : ι) → HasZero (β i)] (f : dfinsupp fun (i : ι) => β i) : set.finite (set_of fun (i : ι) => coe_fn f i ≠ 0) := sorry

/-- Create an element of `Π₀ i, β i` from a finset `s` and a function `x`
defined on this `finset`. -/
def mk {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] (s : finset ι) (x : (i : ↥↑s) → β ↑i) : dfinsupp fun (i : ι) => β i :=
  quotient.mk
    (pre.mk (fun (i : ι) => dite (i ∈ s) (fun (H : i ∈ s) => x { val := i, property := H }) fun (H : ¬i ∈ s) => 0)
      (finset.val s) sorry)

@[simp] theorem mk_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {s : finset ι} {x : (i : ↥↑s) → β ↑i} {i : ι} : coe_fn (mk s x) i = dite (i ∈ s) (fun (H : i ∈ s) => x { val := i, property := H }) fun (H : ¬i ∈ s) => 0 :=
  rfl

theorem mk_injective {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] (s : finset ι) : function.injective (mk s) := sorry

/-- The function `single i b : Π₀ i, β i` sends `i` to `b`
and all other points to `0`. -/
def single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] (i : ι) (b : β i) : dfinsupp fun (i : ι) => β i :=
  mk (singleton i) fun (j : ↥↑(singleton i)) => eq.rec_on sorry b

@[simp] theorem single_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {i : ι} {i' : ι} {b : β i} : coe_fn (single i b) i' = dite (i = i') (fun (h : i = i') => eq.rec_on h b) fun (h : ¬i = i') => 0 := sorry

@[simp] theorem single_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {i : ι} : single i 0 = 0 := sorry

@[simp] theorem single_eq_same {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {i : ι} {b : β i} : coe_fn (single i b) i = b := sorry

theorem single_eq_of_ne {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {i : ι} {i' : ι} {b : β i} (h : i ≠ i') : coe_fn (single i b) i' = 0 := sorry

theorem single_injective {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {i : ι} : function.injective (single i) := sorry

/-- Like `finsupp.single_eq_single_iff`, but with a `heq` due to dependent types -/
theorem single_eq_single_iff {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] (i : ι) (j : ι) (xi : β i) (xj : β j) : single i xi = single j xj ↔ i = j ∧ xi == xj ∨ xi = 0 ∧ xj = 0 := sorry

/-- Redefine `f i` to be `0`. -/
def erase {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] (i : ι) (f : dfinsupp fun (i : ι) => β i) : dfinsupp fun (i : ι) => β i :=
  quotient.lift_on f
    (fun (x : pre ι fun (i : ι) => β i) =>
      quotient.mk (pre.mk (fun (j : ι) => ite (j = i) 0 (pre.to_fun x j)) (pre.pre_support x) sorry))
    sorry

@[simp] theorem erase_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {i : ι} {j : ι} {f : dfinsupp fun (i : ι) => β i} : coe_fn (erase i f) j = ite (j = i) 0 (coe_fn f j) :=
  quotient.induction_on f fun (x : pre ι fun (i : ι) => β i) => rfl

@[simp] theorem erase_same {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {i : ι} {f : dfinsupp fun (i : ι) => β i} : coe_fn (erase i f) i = 0 := sorry

theorem erase_ne {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {i : ι} {i' : ι} {f : dfinsupp fun (i : ι) => β i} (h : i' ≠ i) : coe_fn (erase i f) i' = coe_fn f i' := sorry

@[simp] theorem single_add {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] {i : ι} {b₁ : β i} {b₂ : β i} : single i (b₁ + b₂) = single i b₁ + single i b₂ := sorry

/-- `dfinsupp.single` as an `add_monoid_hom`. -/
@[simp] theorem single_add_hom_apply {ι : Type u} (β : ι → Type v) [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] (i : ι) (b : β i) : coe_fn (single_add_hom β i) b = single i b :=
  Eq.refl (coe_fn (single_add_hom β i) b)

theorem single_add_erase {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] {i : ι} {f : dfinsupp fun (i : ι) => β i} : single i (coe_fn f i) + erase i f = f := sorry

theorem erase_add_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] {i : ι} {f : dfinsupp fun (i : ι) => β i} : erase i f + single i (coe_fn f i) = f := sorry

protected theorem induction {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] {p : (dfinsupp fun (i : ι) => β i) → Prop} (f : dfinsupp fun (i : ι) => β i) (h0 : p 0) (ha : ∀ (i : ι) (b : β i) (f : dfinsupp fun (i : ι) => β i), coe_fn f i = 0 → b ≠ 0 → p f → p (single i b + f)) : p f := sorry

theorem induction₂ {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] {p : (dfinsupp fun (i : ι) => β i) → Prop} (f : dfinsupp fun (i : ι) => β i) (h0 : p 0) (ha : ∀ (i : ι) (b : β i) (f : dfinsupp fun (i : ι) => β i), coe_fn f i = 0 → b ≠ 0 → p f → p (f + single i b)) : p f := sorry

@[simp] theorem add_closure_Union_range_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] : add_submonoid.closure (set.Union fun (i : ι) => set.range (single i)) = ⊤ := sorry

/-- If two additive homomorphisms from `Π₀ i, β i` are equal on each `single a b`, then
they are equal. -/
theorem add_hom_ext {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] {γ : Type w} [add_monoid γ] {f : (dfinsupp fun (i : ι) => β i) →+ γ} {g : (dfinsupp fun (i : ι) => β i) →+ γ} (H : ∀ (i : ι) (y : β i), coe_fn f (single i y) = coe_fn g (single i y)) : f = g := sorry

/-- If two additive homomorphisms from `Π₀ i, β i` are equal on each `single a b`, then
they are equal.

See note [partially-applied ext lemmas]. -/
theorem add_hom_ext' {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] {γ : Type w} [add_monoid γ] {f : (dfinsupp fun (i : ι) => β i) →+ γ} {g : (dfinsupp fun (i : ι) => β i) →+ γ} (H : ∀ (x : ι), add_monoid_hom.comp f (single_add_hom β x) = add_monoid_hom.comp g (single_add_hom β x)) : f = g :=
  add_hom_ext fun (x : ι) => add_monoid_hom.congr_fun (H x)

@[simp] theorem mk_add {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] {s : finset ι} {x : (i : ↥↑s) → β ↑i} {y : (i : ↥↑s) → β ↑i} : mk s (x + y) = mk s x + mk s y := sorry

@[simp] theorem mk_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] {s : finset ι} : mk s 0 = 0 := sorry

@[simp] theorem mk_neg {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_group (β i)] {s : finset ι} {x : (i : ↥↑s) → β (subtype.val i)} : mk s (-x) = -mk s x := sorry

@[simp] theorem mk_sub {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_group (β i)] {s : finset ι} {x : (i : ↥↑s) → β (subtype.val i)} {y : (i : ↥↑s) → β (subtype.val i)} : mk s (x - y) = mk s x - mk s y := sorry

protected instance mk.is_add_group_hom {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_group (β i)] {s : finset ι} : is_add_group_hom (mk s) :=
  is_add_group_hom.mk

@[simp] theorem mk_smul {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] (γ : Type w) [semiring γ] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] {s : finset ι} {c : γ} (x : (i : ↥↑s) → β (subtype.val i)) : mk s (c • x) = c • mk s x := sorry

@[simp] theorem single_smul {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] (γ : Type w) [semiring γ] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] {i : ι} {c : γ} {x : β i} : single i (c • x) = c • single i x := sorry

/-- Set `{i | f x ≠ 0}` as a `finset`. -/
def support {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (f : dfinsupp fun (i : ι) => β i) : finset ι :=
  quotient.lift_on f
    (fun (x : pre ι fun (i : ι) => β i) =>
      finset.filter (fun (i : ι) => pre.to_fun x i ≠ 0) (multiset.to_finset (pre.pre_support x)))
    sorry

@[simp] theorem support_mk_subset {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {s : finset ι} {x : (i : ↥↑s) → β (subtype.val i)} : support (mk s x) ⊆ s :=
  fun (i : ι) (H : i ∈ support (mk s x)) => iff.mp multiset.mem_to_finset (and.left (iff.mp finset.mem_filter H))

@[simp] theorem mem_support_to_fun {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (f : dfinsupp fun (i : ι) => β i) (i : ι) : i ∈ support f ↔ coe_fn f i ≠ 0 := sorry

theorem eq_mk_support {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (f : dfinsupp fun (i : ι) => β i) : f = mk (support f) fun (i : ↥↑(support f)) => coe_fn f ↑i := sorry

@[simp] theorem support_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] : support 0 = ∅ :=
  rfl

theorem mem_support_iff {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (f : dfinsupp fun (i : ι) => β i) (i : ι) : i ∈ support f ↔ coe_fn f i ≠ 0 :=
  mem_support_to_fun f

@[simp] theorem support_eq_empty {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {f : dfinsupp fun (i : ι) => β i} : support f = ∅ ↔ f = 0 := sorry

protected instance decidable_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] : decidable_pred (Eq 0) :=
  fun (f : dfinsupp fun (i : ι) => β i) => decidable_of_iff (support f = ∅) sorry

theorem support_subset_iff {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {s : set ι} {f : dfinsupp fun (i : ι) => β i} : ↑(support f) ⊆ s ↔ ∀ (i : ι), ¬i ∈ s → coe_fn f i = 0 := sorry

theorem support_single_ne_zero {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {i : ι} {b : β i} (hb : b ≠ 0) : support (single i b) = singleton i := sorry

theorem support_single_subset {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {i : ι} {b : β i} : support (single i b) ⊆ singleton i :=
  support_mk_subset

theorem map_range_def {ι : Type u} [dec : DecidableEq ι] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)] {f : (i : ι) → β₁ i → β₂ i} {hf : ∀ (i : ι), f i 0 = 0} {g : dfinsupp fun (i : ι) => β₁ i} : map_range f hf g = mk (support g) fun (i : ↥↑(support g)) => f (subtype.val i) (coe_fn g (subtype.val i)) := sorry

@[simp] theorem map_range_single {ι : Type u} [dec : DecidableEq ι] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] {f : (i : ι) → β₁ i → β₂ i} {hf : ∀ (i : ι), f i 0 = 0} {i : ι} {b : β₁ i} : map_range f hf (single i b) = single i (f i b) := sorry

theorem support_map_range {ι : Type u} [dec : DecidableEq ι] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → (x : β₂ i) → Decidable (x ≠ 0)] {f : (i : ι) → β₁ i → β₂ i} {hf : ∀ (i : ι), f i 0 = 0} {g : dfinsupp fun (i : ι) => β₁ i} : support (map_range f hf g) ⊆ support g := sorry

theorem zip_with_def {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → (x : β₂ i) → Decidable (x ≠ 0)] {f : (i : ι) → β₁ i → β₂ i → β i} {hf : ∀ (i : ι), f i 0 0 = 0} {g₁ : dfinsupp fun (i : ι) => β₁ i} {g₂ : dfinsupp fun (i : ι) => β₂ i} : zip_with f hf g₁ g₂ =
  mk (support g₁ ∪ support g₂)
    fun (i : ↥↑(support g₁ ∪ support g₂)) => f (subtype.val i) (coe_fn g₁ (subtype.val i)) (coe_fn g₂ (subtype.val i)) := sorry

theorem support_zip_with {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → (x : β₂ i) → Decidable (x ≠ 0)] {f : (i : ι) → β₁ i → β₂ i → β i} {hf : ∀ (i : ι), f i 0 0 = 0} {g₁ : dfinsupp fun (i : ι) => β₁ i} {g₂ : dfinsupp fun (i : ι) => β₂ i} : support (zip_with f hf g₁ g₂) ⊆ support g₁ ∪ support g₂ := sorry

theorem erase_def {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (i : ι) (f : dfinsupp fun (i : ι) => β i) : erase i f = mk (finset.erase (support f) i) fun (j : ↥↑(finset.erase (support f) i)) => coe_fn f (subtype.val j) := sorry

@[simp] theorem support_erase {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (i : ι) (f : dfinsupp fun (i : ι) => β i) : support (erase i f) = finset.erase (support f) i := sorry

theorem filter_def {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {p : ι → Prop} [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) : filter p f = mk (finset.filter p (support f)) fun (i : ↥↑(finset.filter p (support f))) => coe_fn f (subtype.val i) := sorry

@[simp] theorem support_filter {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {p : ι → Prop} [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) : support (filter p f) = finset.filter p (support f) := sorry

theorem subtype_domain_def {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {p : ι → Prop} [decidable_pred p] (f : dfinsupp fun (i : ι) => β i) : subtype_domain p f = mk (finset.subtype p (support f)) fun (i : ↥↑(finset.subtype p (support f))) => coe_fn f ↑i := sorry

@[simp] theorem support_subtype_domain {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {p : ι → Prop} [decidable_pred p] {f : dfinsupp fun (i : ι) => β i} : support (subtype_domain p f) = finset.subtype p (support f) := sorry

theorem support_add {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {g₁ : dfinsupp fun (i : ι) => β i} {g₂ : dfinsupp fun (i : ι) => β i} : support (g₁ + g₂) ⊆ support g₁ ∪ support g₂ :=
  support_zip_with

@[simp] theorem support_neg {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_group (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {f : dfinsupp fun (i : ι) => β i} : support (-f) = support f := sorry

theorem support_smul {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [semiring γ] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → semimodule γ (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] (b : γ) (v : dfinsupp fun (i : ι) => β i) : support (b • v) ⊆ support v :=
  support_map_range

protected instance decidable_eq {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → HasZero (β i)] [(i : ι) → DecidableEq (β i)] : DecidableEq (dfinsupp fun (i : ι) => β i) :=
  fun (f g : dfinsupp fun (i : ι) => β i) =>
    decidable_of_iff (support f = support g ∧ ∀ (i : ι), i ∈ support f → coe_fn f i = coe_fn g i) sorry

-- [to_additive sum] for dfinsupp.prod doesn't work, the equation lemmas are not generated

/-- `sum f g` is the sum of `g i (f i)` over the support of `f`. -/
def sum {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] (f : dfinsupp fun (i : ι) => β i) (g : (i : ι) → β i → γ) : γ :=
  finset.sum (support f) fun (i : ι) => g i (coe_fn f i)

/-- `prod f g` is the product of `g i (f i)` over the support of `f`. -/
def prod {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [comm_monoid γ] (f : dfinsupp fun (i : ι) => β i) (g : (i : ι) → β i → γ) : γ :=
  finset.prod (support f) fun (i : ι) => g i (coe_fn f i)

theorem prod_map_range_index {ι : Type u} [dec : DecidableEq ι] {γ : Type w} {β₁ : ι → Type v₁} {β₂ : ι → Type v₂} [(i : ι) → HasZero (β₁ i)] [(i : ι) → HasZero (β₂ i)] [(i : ι) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → (x : β₂ i) → Decidable (x ≠ 0)] [comm_monoid γ] {f : (i : ι) → β₁ i → β₂ i} {hf : ∀ (i : ι), f i 0 = 0} {g : dfinsupp fun (i : ι) => β₁ i} {h : (i : ι) → β₂ i → γ} (h0 : ∀ (i : ι), h i 0 = 1) : prod (map_range f hf g) h = prod g fun (i : ι) (b : β₁ i) => h i (f i b) := sorry

theorem sum_zero_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] {h : (i : ι) → β i → γ} : sum 0 h = 0 :=
  rfl

theorem sum_single_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] {i : ι} {b : β i} {h : (i : ι) → β i → γ} (h_zero : h i 0 = 0) : sum (single i b) h = h i b := sorry

theorem sum_neg_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_group (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] {g : dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ} (h0 : ∀ (i : ι), h i 0 = 0) : sum (-g) h = sum g fun (i : ι) (b : β i) => h i (-b) :=
  sum_map_range_index h0

theorem sum_comm {γ : Type w} {ι₁ : Type u_1} {ι₂ : Type u_2} {β₁ : ι₁ → Type u_3} {β₂ : ι₂ → Type u_4} [DecidableEq ι₁] [DecidableEq ι₂] [(i : ι₁) → HasZero (β₁ i)] [(i : ι₂) → HasZero (β₂ i)] [(i : ι₁) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι₂) → (x : β₂ i) → Decidable (x ≠ 0)] [add_comm_monoid γ] (f₁ : dfinsupp fun (i : ι₁) => β₁ i) (f₂ : dfinsupp fun (i : ι₂) => β₂ i) (h : (i : ι₁) → β₁ i → (i : ι₂) → β₂ i → γ) : (sum f₁ fun (i₁ : ι₁) (x₁ : β₁ i₁) => sum f₂ fun (i₂ : ι₂) (x₂ : β₂ i₂) => h i₁ x₁ i₂ x₂) =
  sum f₂ fun (i₂ : ι₂) (x₂ : β₂ i₂) => sum f₁ fun (i₁ : ι₁) (x₁ : β₁ i₁) => h i₁ x₁ i₂ x₂ :=
  finset.sum_comm

@[simp] theorem sum_apply {ι : Type u} {β : ι → Type v} {ι₁ : Type u₁} [DecidableEq ι₁] {β₁ : ι₁ → Type v₁} [(i₁ : ι₁) → HasZero (β₁ i₁)] [(i : ι₁) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → add_comm_monoid (β i)] {f : dfinsupp fun (i₁ : ι₁) => β₁ i₁} {g : (i₁ : ι₁) → β₁ i₁ → dfinsupp fun (i : ι) => β i} {i₂ : ι} : coe_fn (sum f g) i₂ = sum f fun (i₁ : ι₁) (b : β₁ i₁) => coe_fn (g i₁ b) i₂ :=
  Eq.symm (finset.sum_hom (support f) fun (f : dfinsupp fun (i : ι) => β i) => coe_fn f i₂)

theorem support_sum {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {ι₁ : Type u₁} [DecidableEq ι₁] {β₁ : ι₁ → Type v₁} [(i₁ : ι₁) → HasZero (β₁ i₁)] [(i : ι₁) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {f : dfinsupp fun (i₁ : ι₁) => β₁ i₁} {g : (i₁ : ι₁) → β₁ i₁ → dfinsupp fun (i : ι) => β i} : support (sum f g) ⊆ finset.bUnion (support f) fun (i : ι₁) => support (g i (coe_fn f i)) := sorry

@[simp] theorem prod_one {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [comm_monoid γ] {f : dfinsupp fun (i : ι) => β i} : (prod f fun (i : ι) (b : β i) => 1) = 1 :=
  finset.prod_const_one

@[simp] theorem sum_add {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] {f : dfinsupp fun (i : ι) => β i} {h₁ : (i : ι) → β i → γ} {h₂ : (i : ι) → β i → γ} : (sum f fun (i : ι) (b : β i) => h₁ i b + h₂ i b) = sum f h₁ + sum f h₂ :=
  finset.sum_add_distrib

@[simp] theorem sum_neg {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_group γ] {f : dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ} : (sum f fun (i : ι) (b : β i) => -h i b) = -sum f h :=
  finset.sum_hom (support f) Neg.neg

theorem prod_add_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [comm_monoid γ] {f : dfinsupp fun (i : ι) => β i} {g : dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ} (h_zero : ∀ (i : ι), h i 0 = 1) (h_add : ∀ (i : ι) (b₁ b₂ : β i), h i (b₁ + b₂) = h i b₁ * h i b₂) : prod (f + g) h = prod f h * prod g h := sorry

/--
When summing over an `add_monoid_hom`, the decidability assumption is not needed, and the result is
also an `add_monoid_hom`.
-/
def sum_add_hom {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_monoid (β i)] [add_comm_monoid γ] (φ : (i : ι) → β i →+ γ) : (dfinsupp fun (i : ι) => β i) →+ γ :=
  add_monoid_hom.mk
    (fun (f : dfinsupp fun (i : ι) => β i) =>
      quotient.lift_on f
        (fun (x : pre ι fun (i : ι) => β i) =>
          finset.sum (multiset.to_finset (pre.pre_support x)) fun (i : ι) => coe_fn (φ i) (pre.to_fun x i))
        sorry)
    sorry sorry

@[simp] theorem sum_add_hom_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_monoid (β i)] [add_comm_monoid γ] (φ : (i : ι) → β i →+ γ) (i : ι) (x : β i) : coe_fn (sum_add_hom φ) (single i x) = coe_fn (φ i) x := sorry

@[simp] theorem sum_add_hom_comp_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_comm_monoid (β i)] [add_comm_monoid γ] (f : (i : ι) → β i →+ γ) (i : ι) : add_monoid_hom.comp (sum_add_hom f) (single_add_hom β i) = f i :=
  add_monoid_hom.ext fun (x : β i) => sum_add_hom_single f i x

/-- While we didn't need decidable instances to define it, we do to reduce it to a sum -/
theorem sum_add_hom_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] (φ : (i : ι) → β i →+ γ) (f : dfinsupp fun (i : ι) => β i) : coe_fn (sum_add_hom φ) f = sum f fun (x : ι) => ⇑(φ x) := sorry

/-- The `dfinsupp` version of `finsupp.lift_add_hom`,-/
@[simp] theorem lift_add_hom_symm_apply {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_monoid (β i)] [add_comm_monoid γ] (F : (dfinsupp fun (i : ι) => β i) →+ γ) (i : ι) : coe_fn (add_equiv.symm lift_add_hom) F i = add_monoid_hom.comp F (single_add_hom β i) :=
  Eq.refl (coe_fn (add_equiv.symm lift_add_hom) F i)

/-- The `dfinsupp` version of `finsupp.lift_add_hom_single_add_hom`,-/
@[simp] theorem lift_add_hom_single_add_hom {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_comm_monoid (β i)] : coe_fn lift_add_hom (single_add_hom β) = add_monoid_hom.id (dfinsupp fun (i : ι) => β i) :=
  iff.mpr (equiv.apply_eq_iff_eq_symm_apply (add_equiv.to_equiv lift_add_hom)) rfl

/-- The `dfinsupp` version of `finsupp.lift_add_hom_apply_single`,-/
theorem lift_add_hom_apply_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_comm_monoid (β i)] [add_comm_monoid γ] (f : (i : ι) → β i →+ γ) (i : ι) (x : β i) : coe_fn (coe_fn lift_add_hom f) (single i x) = coe_fn (f i) x := sorry

/-- The `dfinsupp` version of `finsupp.lift_add_hom_comp_single`,-/
theorem lift_add_hom_comp_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_comm_monoid (β i)] [add_comm_monoid γ] (f : (i : ι) → β i →+ γ) (i : ι) : add_monoid_hom.comp (coe_fn lift_add_hom f) (single_add_hom β i) = f i := sorry

/-- The `dfinsupp` version of `finsupp.comp_lift_add_hom`,-/
theorem comp_lift_add_hom {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} {δ : Type u_1} [(i : ι) → add_comm_monoid (β i)] [add_comm_monoid γ] [add_comm_monoid δ] (g : γ →+ δ) (f : (i : ι) → β i →+ γ) : add_monoid_hom.comp g (coe_fn lift_add_hom f) = coe_fn lift_add_hom fun (a : ι) => add_monoid_hom.comp g (f a) := sorry

theorem sum_sub_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → add_comm_group (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_group γ] {f : dfinsupp fun (i : ι) => β i} {g : dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ} (h_sub : ∀ (i : ι) (b₁ b₂ : β i), h i (b₁ - b₂) = h i b₁ - h i b₂) : sum (f - g) h = sum f h - sum g h := sorry

theorem sum_finset_sum_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} {α : Type x} [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] {s : finset α} {g : α → dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ} (h_zero : ∀ (i : ι), h i 0 = 0) (h_add : ∀ (i : ι) (b₁ b₂ : β i), h i (b₁ + b₂) = h i b₁ + h i b₂) : (finset.sum s fun (i : α) => sum (g i) h) = sum (finset.sum s fun (i : α) => g i) h := sorry

theorem sum_sum_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} {ι₁ : Type u₁} [DecidableEq ι₁] {β₁ : ι₁ → Type v₁} [(i₁ : ι₁) → HasZero (β₁ i₁)] [(i : ι₁) → (x : β₁ i) → Decidable (x ≠ 0)] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] {f : dfinsupp fun (i₁ : ι₁) => β₁ i₁} {g : (i₁ : ι₁) → β₁ i₁ → dfinsupp fun (i : ι) => β i} {h : (i : ι) → β i → γ} (h_zero : ∀ (i : ι), h i 0 = 0) (h_add : ∀ (i : ι) (b₁ b₂ : β i), h i (b₁ + b₂) = h i b₁ + h i b₂) : sum (sum f g) h = sum f fun (i : ι₁) (b : β₁ i) => sum (g i b) h :=
  Eq.symm (sum_finset_sum_index h_zero h_add)

@[simp] theorem sum_single {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] [(i : ι) → add_comm_monoid (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] {f : dfinsupp fun (i : ι) => β i} : sum f single = f := sorry

theorem sum_subtype_domain_index {ι : Type u} {β : ι → Type v} [dec : DecidableEq ι] {γ : Type w} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [add_comm_monoid γ] {v : dfinsupp fun (i : ι) => β i} {p : ι → Prop} [decidable_pred p] {h : (i : ι) → β i → γ} (hp : ∀ (x : ι), x ∈ support v → p x) : (sum (subtype_domain p v) fun (i : Subtype p) (b : β ↑i) => h (↑i) b) = sum v h := sorry

theorem subtype_domain_sum {ι : Type u} {β : ι → Type v} {γ : Type w} [(i : ι) → add_comm_monoid (β i)] {s : finset γ} {h : γ → dfinsupp fun (i : ι) => β i} {p : ι → Prop} [decidable_pred p] : subtype_domain p (finset.sum s fun (c : γ) => h c) = finset.sum s fun (c : γ) => subtype_domain p (h c) :=
  Eq.symm (finset.sum_hom s (subtype_domain p))

theorem subtype_domain_finsupp_sum {ι : Type u} {β : ι → Type v} {γ : Type w} {δ : γ → Type x} [DecidableEq γ] [(c : γ) → HasZero (δ c)] [(c : γ) → (x : δ c) → Decidable (x ≠ 0)] [(i : ι) → add_comm_monoid (β i)] {p : ι → Prop} [decidable_pred p] {s : dfinsupp fun (c : γ) => δ c} {h : (c : γ) → δ c → dfinsupp fun (i : ι) => β i} : subtype_domain p (sum s h) = sum s fun (c : γ) (d : δ c) => subtype_domain p (h c d) :=
  subtype_domain_sum

end dfinsupp

/-! ### Product and sum lemmas for bundled morphisms -/

namespace monoid_hom

@[simp] theorem map_dfinsupp_prod {ι : Type u} {β : ι → Type v} [DecidableEq ι] {R : Type u_1} {S : Type u_2} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [comm_monoid R] [comm_monoid S] (h : R →* S) (f : dfinsupp fun (i : ι) => β i) (g : (i : ι) → β i → R) : coe_fn h (dfinsupp.prod f g) = dfinsupp.prod f fun (a : ι) (b : β a) => coe_fn h (g a b) :=
  map_prod h (fun (i : ι) => g i (coe_fn f i)) (dfinsupp.support f)

theorem coe_dfinsupp_prod {ι : Type u} {β : ι → Type v} [DecidableEq ι] {R : Type u_1} {S : Type u_2} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [monoid R] [comm_monoid S] (f : dfinsupp fun (i : ι) => β i) (g : (i : ι) → β i → R →* S) : ⇑(dfinsupp.prod f g) = dfinsupp.prod f fun (a : ι) (b : β a) => ⇑(g a b) :=
  coe_prod (fun (i : ι) => g i (coe_fn f i)) (dfinsupp.support f)

@[simp] theorem dfinsupp_prod_apply {ι : Type u} {β : ι → Type v} [DecidableEq ι] {R : Type u_1} {S : Type u_2} [(i : ι) → HasZero (β i)] [(i : ι) → (x : β i) → Decidable (x ≠ 0)] [monoid R] [comm_monoid S] (f : dfinsupp fun (i : ι) => β i) (g : (i : ι) → β i → R →* S) (r : R) : coe_fn (dfinsupp.prod f g) r = dfinsupp.prod f fun (a : ι) (b : β a) => coe_fn (g a b) r :=
  finset_prod_apply (fun (i : ι) => g i (coe_fn f i)) (dfinsupp.support f) r


/-
Copyright (c) 2020 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn
-/
import tactic.lint
import tactic.ext

/-!
# Congruence and related tactics

This file contains the tactic `congr'`, which is an extension of `congr`, and various tactics
using `congr'` internally.

`congr'` has some advantages over `congr`:
* It turns `↔` to equalities, before trying another congr lemma
* You can write `congr' n` to give the maximal depth of recursive applications. This is useful if
  `congr` breaks down the goal to aggressively, and the resulting goals are false.
* You can write `congr' with ...` to do `congr', ext ...` in a single tactic.

Other tactics in this file:
* `rcongr`: repeatedly apply `congr'` and `ext.`
* `convert`: like `exact`, but produces an equality goal if the type doesn't match.
* `convert_to`: changes the goal, if you prove an equality between the old goal and the new goal.
* `ac_change`: like `convert_to`, but uses `ac_refl` to discharge the goals.
-/

open tactic
setup_tactic_parser

namespace tactic

/-- Apply the constant `iff_of_eq` to the goal. -/
meta def apply_iff_congr_core : tactic unit :=
applyc ``iff_of_eq

/-- The main part of the body for the loop in `congr'`. This will try to replace a goal `f x = f y`
 with `x = y`. Also has support for `==` and `↔`. -/
meta def congr_core' : tactic unit :=
do tgt ← target,
   apply_eq_congr_core tgt
   <|> apply_heq_congr_core
   <|> apply_iff_congr_core
   <|> fail "congr tactic failed"

/-- The main function in `convert_to`. Changes the goal to `r` and a proof obligation that the goal
  is equal to `r`. -/
meta def convert_to_core (r : pexpr) : tactic unit :=
do tgt ← target,
   h   ← to_expr ``(_ : %%tgt = %%r),
   rewrite_target h,
   swap

/-- Attempts to prove the goal by proof irrelevance, but avoids unifying universe metavariables
to do so. -/
meta def by_proof_irrel : tactic unit :=
do tgt ← target,
  @expr.const tt n [level.zero] ← pure tgt.get_app_fn,
  if n = ``eq then `[apply proof_irrel] else
  if n = ``heq then `[apply proof_irrel_heq] else
  failed

/--
Same as the `congr` tactic, but takes an optional argument which gives
the depth of recursive applications.
* This is useful when `congr` is too aggressive in breaking down the goal.
* For example, given `⊢ f (g (x + y)) = f (g (y + x))`, `congr'` produces the goals `⊢ x = y`
  and `⊢ y = x`, while `congr' 2` produces the intended `⊢ x + y = y + x`.
* If, at any point, a subgoal matches a hypothesis then the subgoal will be closed.
-/
meta def congr' : option ℕ → tactic unit
| o := focus1 $
  assumption <|> reflexivity transparency.none <|> by_proof_irrel <|>
  (guard (o ≠ some 0) >> congr_core' >>
    all_goals' (try (congr' (nat.pred <$> o)))) <|>
  reflexivity

namespace interactive

/--
Same as the `congr` tactic, but takes an optional argument which gives
the depth of recursive applications.
* This is useful when `congr` is too aggressive in breaking down the goal.
* For example, given `⊢ f (g (x + y)) = f (g (y + x))`, `congr'` produces the goals `⊢ x = y`
  and `⊢ y = x`, while `congr' 2` produces the intended `⊢ x + y = y + x`.
* If, at any point, a subgoal matches a hypothesis then the subgoal will be closed.
* You can use `congr' with p (: n)?` to call `ext p (: n)?` to all subgoals generated by `congr'`.
  For example, if the goal is `⊢ f '' s = g '' s` then `congr' with x` generates the goal
  `x : α ⊢ f x = g x`.
-/
meta def congr' (n : parse (with_desc "n" small_nat)?) :
  parse (tk "with" *> prod.mk <$> rintro_patt_parse_hi* <*> (tk ":" *> small_nat)?)? →
  tactic unit
| none         := tactic.congr' n
| (some ⟨p, m⟩) := focus1 (tactic.congr' n >> all_goals' (tactic.ext p.join m $> ()))

/--
Repeatedly and apply `congr'` and `ext`, using the given patterns as arguments for `ext`.

There are two ways this tactic stops:
* `congr'` fails (makes no progress), after having already applied `ext`.
* `congr'` canceled out the last usage of `ext`. In this case, the state is reverted to before
  the `congr'` was applied.

For example, when the goal is
```lean
⊢ (λ x, f x + 3) '' s = (λ x, g x + 3) '' s
```
then `rcongr x` produces the goal
```lean
x : α ⊢ f x = g x
```
This gives the same result as `congr', ext x, congr'`.

In contrast, `congr'` would produce
```lean
⊢ (λ x, f x + 3) = (λ x, g x + 3)
```
and `congr' with x` (or `congr', ext x`) would produce
```lean
x : α ⊢ f x + 3 = g x + 3
```
-/
meta def rcongr : parse (list.join <$> rintro_patt_parse_hi*) → tactic unit
| ps := do
  t ← target,
  qs ← try_core (tactic.ext ps none),
  some () ← try_core (tactic.congr' none >>
    (done <|> do s ← target, guard $ ¬ s =ₐ t)) | skip,
  done <|> rcongr (qs.get_or_else ps)

add_tactic_doc
{ name       := "congr'",
  category   := doc_category.tactic,
  decl_names := [`tactic.interactive.congr', `tactic.interactive.congr, `tactic.interactive.rcongr],
  tags       := ["congruence"],
  inherit_description_from := `tactic.interactive.congr' }

/--
The `exact e` and `refine e` tactics require a term `e` whose type is
definitionally equal to the goal. `convert e` is similar to `refine e`,
but the type of `e` is not required to exactly match the
goal. Instead, new goals are created for differences between the type
of `e` and the goal. For example, in the proof state

```lean
n : ℕ,
e : prime (2 * n + 1)
⊢ prime (n + n + 1)
```

the tactic `convert e` will change the goal to

```lean
⊢ n + n = 2 * n
```

In this example, the new goal can be solved using `ring`.

The `convert` tactic applies congruence lemmas eagerly before reducing,
therefore it can fail in cases where `exact` succeeds:
```lean
def p (n : ℕ) := true
example (h : p 0) : p 1 := by exact h -- succeeds
example (h : p 0) : p 1 := by convert h -- fails, with leftover goal `1 = 0`
```

If `x y : t`, and an instance `subsingleton t` is in scope, then any goals of the form
`x = y` are solved automatically.

The syntax `convert ← e` will reverse the direction of the new goals
(producing `⊢ 2 * n = n + n` in this example).

Internally, `convert e` works by creating a new goal asserting that
the goal equals the type of `e`, then simplifying it using
`congr'`. The syntax `convert e using n` can be used to control the
depth of matching (like `congr' n`). In the example, `convert e using
1` would produce a new goal `⊢ n + n + 1 = 2 * n + 1`.
-/
meta def convert (sym : parse (with_desc "←" (tk "<-")?)) (r : parse texpr)
  (n : parse (tk "using" *> small_nat)?) : tactic unit :=
do tgt ← target,
  u ← infer_type tgt,
  r ← i_to_expr ``(%%r : (_ : %%u)),
  src ← infer_type r,
  src ← simp_lemmas.mk.dsimplify [] src {fail_if_unchanged := ff},
  v ← to_expr (if sym.is_some then ``(%%src = %%tgt) else ``(%%tgt = %%src)) tt ff >>= mk_meta_var,
  (if sym.is_some then mk_eq_mp v r else mk_eq_mpr v r) >>= tactic.exact,
  gs ← get_goals,
  set_goals [v],
  try (tactic.congr' n),
  gs' ← get_goals,
  set_goals $ gs' ++ gs

add_tactic_doc
{ name       := "convert",
  category   := doc_category.tactic,
  decl_names := [`tactic.interactive.convert],
  tags       := ["congruence"] }

/--
`convert_to g using n` attempts to change the current goal to `g`, but unlike `change`,
it will generate equality proof obligations using `congr' n` to resolve discrepancies.
`convert_to g` defaults to using `congr' 1`.

`convert_to` is similar to `convert`, but `convert_to` takes a type (the desired subgoal) while
`convert` takes a proof term.
That is, `convert_to g using n` is equivalent to `convert (_ : g) using n`.
-/
meta def convert_to (r : parse texpr) (n : parse (tk "using" *> small_nat)?) : tactic unit :=
match n with
  | none     := convert_to_core r >> `[congr' 1]
  | (some 0) := convert_to_core r
  | (some o) := convert_to_core r >> tactic.congr' o
end

/--
`ac_change g using n` is `convert_to g using n` followed by `ac_refl`. It is useful for
rearranging/reassociating e.g. sums:
```lean
example (a b c d e f g N : ℕ) : (a + b) + (c + d) + (e + f) + g ≤ N :=
begin
  ac_change a + d + e + f + c + g + b ≤ _,
-- ⊢ a + d + e + f + c + g + b ≤ N
end
```

##  Related tactic: `move_add`
In the case in which the expression to be changed is a sum of terms, tactic
`tactive.interactive.move_add` can also be useful. -/
meta def ac_change (r : parse texpr) (n : parse (tk "using" *> small_nat)?) : tactic unit :=
convert_to r n; try ac_refl

add_tactic_doc
{ name       := "convert_to",
  category   := doc_category.tactic,
  decl_names := [`tactic.interactive.convert_to, `tactic.interactive.ac_change],
  tags       := ["congruence"],
  inherit_description_from := `tactic.interactive.convert_to }

end interactive

end tactic


import Smt

theorem cong (p q : Bool) (f : Bool → Bool) : p == q → f p == f q := by
  smt
  cases p <;> cases q <;> cases f true <;> cases f false <;> simp_all


import for_mathlib.category_theory.localization.predicate

noncomputable theory

namespace category_theory

open localization category

namespace adjunction

variables {C₁ C₂ D₁ D₂ : Type*} [category C₁] [category C₂] [category D₁] [category D₂]
  {G : C₁ ⥤ C₂} {F : C₂ ⥤ C₁} (adj : G ⊣ F)
  (L₁ : C₁ ⥤ D₁) (W₁ : morphism_property C₁) [L₁.is_localization W₁]
  (L₂ : C₂ ⥤ D₂) (W₂ : morphism_property C₂) [L₂.is_localization W₂]
  (G' : D₁ ⥤ D₂) (F' : D₂ ⥤ D₁)

include adj
/-- Should be redone using Comm_sq instead of `lifting`. -/
def localization [lifting L₁ W₁ (G ⋙ L₂) G'] [lifting L₂ W₂ (F ⋙ L₁) F'] :
  G' ⊣ F' := adjunction.mk_of_unit_counit
begin
  let α₁ := lifting.iso L₂ W₂ (F ⋙ L₁) F',
  let α₂ := lifting.iso L₁ W₁ (G ⋙ L₂) G',
  let e₁ : (G ⋙ L₂) ⋙ F' ≅ (G ⋙ F) ⋙ L₁ := iso_whisker_left G α₁,
  let e₂ : (F ⋙ L₁) ⋙ G' ≅ (F ⋙ G) ⋙ L₂ := iso_whisker_left F α₂,
  letI : lifting L₁ W₁ ((G ⋙ F) ⋙ L₁) (G' ⋙ F') :=
    lifting.of_isos L₁ W₁ e₁ (iso.refl (G' ⋙ F')),
  letI : lifting L₂ W₂ ((F ⋙ G) ⋙ L₂) (F' ⋙ G') :=
    lifting.of_isos L₂ W₂ e₂ (iso.refl (F' ⋙ G')),
  let ε := lift_nat_trans L₁ W₁ L₁ ((G ⋙ F) ⋙ L₁) (𝟭 D₁) (G' ⋙ F')
    ((functor.left_unitor L₁).inv ≫ nat_trans.hcomp adj.unit (𝟙 L₁)),
  let η := lift_nat_trans L₂ W₂ ((F ⋙ G) ⋙ L₂) L₂ (F' ⋙ G') (𝟭 D₂)
      (nat_trans.hcomp adj.counit (𝟙 L₂) ≫ (functor.left_unitor L₂).hom),
  have hε : ∀ (X₁ : C₁), ε.app (L₁.obj X₁) = L₁.map (adj.unit.app X₁) ≫
    α₁.inv.app (G.obj X₁) ≫ F'.map (α₂.inv.app X₁),
  { intro X₁,
    rw lift_nat_trans_app,
    dsimp,
    simp only [id_comp, comp_id], },
  have hη : ∀ (X₂ : C₂), η.app (L₂.obj X₂) = G'.map (α₁.hom.app X₂) ≫
    α₂.hom.app (F.obj X₂) ≫ L₂.map (adj.counit.app X₂),
  { intro X₂,
    rw lift_nat_trans_app,
    dsimp,
    simp only [id_comp, comp_id, assoc], },
  exact
  { unit := ε,
    counit := η,
    left_triangle' := nat_trans_ext L₁ W₁ _ _ (λ X₁, begin
      have eq := congr_app adj.left_triangle X₁,
      dsimp at ⊢ eq,
      erw [id_comp, hε, G'.map_comp, G'.map_comp, assoc, assoc, η.naturality, hη, assoc, assoc],
      slice_lhs 2 3 { erw [← G'.map_comp, α₁.inv_hom_id_app, G'.map_id], },
      erw [assoc, assoc, id_comp, α₂.hom.naturality_assoc, ← L₂.map_comp_assoc, eq,
        L₂.map_id, id_comp, α₂.hom_inv_id_app],
      refl,
    end),
    right_triangle' := nat_trans_ext L₂ W₂ _ _ (λ X₂, begin
      have eq := congr_app adj.right_triangle X₂,
      dsimp at ⊢ eq,
      erw [id_comp, hη, F'.map_comp, F'.map_comp, ← ε.naturality_assoc, hε, assoc, assoc],
      slice_lhs 4 5 { erw [← F'.map_comp, α₂.inv_hom_id_app, F'.map_id], },
      erw [id_comp, ← α₁.inv.naturality, ← L₁.map_comp_assoc, eq,
        L₁.map_id, id_comp, α₁.hom_inv_id_app],
      refl,
    end) },
end

@[simp]
lemma localization_unit_app [lifting L₁ W₁ (G ⋙ L₂) G'] [lifting L₂ W₂ (F ⋙ L₁) F'] (X₁ : C₁) :
  (adj.localization L₁ W₁ L₂ W₂ G' F').unit.app (L₁.obj X₁) =
    L₁.map (adj.unit.app X₁) ≫ (lifting.iso L₂ W₂ (F ⋙ L₁) F').inv.app (G.obj X₁) ≫
    F'.map ((lifting.iso L₁ W₁ (G ⋙ L₂) G').inv.app X₁) :=
begin
  dsimp only [localization, adjunction.mk_of_unit_counit],
  rw lift_nat_trans_app,
  dsimp,
  simp only [id_comp, comp_id],
end

@[simp]
lemma localization_counit_app [lifting L₁ W₁ (G ⋙ L₂) G'] [lifting L₂ W₂ (F ⋙ L₁) F'] (X₂ : C₂) :
  (adj.localization L₁ W₁ L₂ W₂ G' F').counit.app (L₂.obj X₂) =
    G'.map (((lifting.iso L₂ W₂ (F ⋙ L₁) F')).hom.app X₂) ≫
      (lifting.iso L₁ W₁ (G ⋙ L₂) G').hom.app (F.obj X₂) ≫ L₂.map (adj.counit.app X₂) :=
begin
  dsimp only [localization, adjunction.mk_of_unit_counit],
  rw lift_nat_trans_app,
  dsimp,
  simp only [id_comp, comp_id, assoc],
end

end adjunction

end category_theory