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

! This file was ported from Lean 3 source module algebra.group.pi
! leanprover-community/mathlib commit c3291da49cfa65f0d43b094750541c0731edc932
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Logic.Pairwise
import Mathbin.Algebra.Hom.GroupInstances
import Mathbin.Data.Pi.Algebra
import Mathbin.Data.Set.Function
import Mathbin.Tactic.PiInstances

/-!
# Pi instances for groups and monoids

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

This file defines instances for group, monoid, semigroup and related structures on Pi types.
-/

universe u v w

variable {ι α : Type _}

variable {I : Type u}

-- The indexing type
variable {f : I → Type v}

-- The family of types already equipped with instances
variable (x y : ∀ i, f i) (i : I)

/- warning: set.preimage_one -> Set.preimage_one is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : One.{u2} β] (s : Set.{u2} β) [_inst_2 : Decidable (Membership.Mem.{u2, u2} β (Set.{u2} β) (Set.hasMem.{u2} β) (OfNat.ofNat.{u2} β 1 (OfNat.mk.{u2} β 1 (One.one.{u2} β _inst_1))) s)], Eq.{succ u1} (Set.{u1} α) (Set.preimage.{u1, u2} α β (OfNat.ofNat.{max u1 u2} (α -> β) 1 (OfNat.mk.{max u1 u2} (α -> β) 1 (One.one.{max u1 u2} (α -> β) (Pi.instOne.{u1, u2} α (fun (ᾰ : α) => β) (fun (i : α) => _inst_1))))) s) (ite.{succ u1} (Set.{u1} α) (Membership.Mem.{u2, u2} β (Set.{u2} β) (Set.hasMem.{u2} β) (OfNat.ofNat.{u2} β 1 (OfNat.mk.{u2} β 1 (One.one.{u2} β _inst_1))) s) _inst_2 (Set.univ.{u1} α) (EmptyCollection.emptyCollection.{u1} (Set.{u1} α) (Set.hasEmptyc.{u1} α)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : One.{u1} β] (s : Set.{u1} β) [_inst_2 : Decidable (Membership.mem.{u1, u1} β (Set.{u1} β) (Set.instMembershipSet.{u1} β) (OfNat.ofNat.{u1} β 1 (One.toOfNat1.{u1} β _inst_1)) s)], Eq.{succ u2} (Set.{u2} α) (Set.preimage.{u2, u1} α β (OfNat.ofNat.{max u2 u1} (α -> β) 1 (One.toOfNat1.{max u2 u1} (α -> β) (Pi.instOne.{u2, u1} α (fun (a._@.Mathlib.Algebra.Group.Pi._hyg.90 : α) => β) (fun (i : α) => _inst_1)))) s) (ite.{succ u2} (Set.{u2} α) (Membership.mem.{u1, u1} β (Set.{u1} β) (Set.instMembershipSet.{u1} β) (OfNat.ofNat.{u1} β 1 (One.toOfNat1.{u1} β _inst_1)) s) _inst_2 (Set.univ.{u2} α) (EmptyCollection.emptyCollection.{u2} (Set.{u2} α) (Set.instEmptyCollectionSet.{u2} α)))
Case conversion may be inaccurate. Consider using '#align set.preimage_one Set.preimage_oneₓ'. -/
@[to_additive]
theorem Set.preimage_one {α β : Type _} [One β] (s : Set β) [Decidable ((1 : β) ∈ s)] :
    (1 : α → β) ⁻¹' s = if (1 : β) ∈ s then Set.univ else ∅ :=
  Set.preimage_const 1 s
#align set.preimage_one Set.preimage_one
#align set.preimage_zero Set.preimage_zero

namespace Pi

#print Pi.semigroup /-
@[to_additive]
instance semigroup [∀ i, Semigroup <| f i] : Semigroup (∀ i : I, f i) := by
  refine_struct { mul := (· * ·).. } <;> pi_instance_derive_field
#align pi.semigroup Pi.semigroup
#align pi.add_semigroup Pi.addSemigroup
-/

#print Pi.semigroupWithZero /-
instance semigroupWithZero [∀ i, SemigroupWithZero <| f i] : SemigroupWithZero (∀ i : I, f i) := by
  refine_struct
      { zero := (0 : ∀ i, f i)
        mul := (· * ·).. } <;>
    pi_instance_derive_field
#align pi.semigroup_with_zero Pi.semigroupWithZero
-/

#print Pi.commSemigroup /-
@[to_additive]
instance commSemigroup [∀ i, CommSemigroup <| f i] : CommSemigroup (∀ i : I, f i) := by
  refine_struct { mul := (· * ·).. } <;> pi_instance_derive_field
#align pi.comm_semigroup Pi.commSemigroup
#align pi.add_comm_semigroup Pi.addCommSemigroup
-/

#print Pi.mulOneClass /-
@[to_additive]
instance mulOneClass [∀ i, MulOneClass <| f i] : MulOneClass (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·).. } <;>
    pi_instance_derive_field
#align pi.mul_one_class Pi.mulOneClass
#align pi.add_zero_class Pi.addZeroClass
-/

#print Pi.monoid /-
@[to_additive]
instance monoid [∀ i, Monoid <| f i] : Monoid (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        npow := fun n x i => x i ^ n } <;>
    pi_instance_derive_field
#align pi.monoid Pi.monoid
#align pi.add_monoid Pi.addMonoid
-/

#print Pi.commMonoid /-
@[to_additive]
instance commMonoid [∀ i, CommMonoid <| f i] : CommMonoid (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        npow := Monoid.npow } <;>
    pi_instance_derive_field
#align pi.comm_monoid Pi.commMonoid
#align pi.add_comm_monoid Pi.addCommMonoid
-/

@[to_additive Pi.subNegMonoid]
instance [∀ i, DivInvMonoid <| f i] : DivInvMonoid (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        inv := Inv.inv
        div := Div.div
        npow := Monoid.npow
        zpow := fun z x i => x i ^ z } <;>
    pi_instance_derive_field

@[to_additive]
instance [∀ i, InvolutiveInv <| f i] : InvolutiveInv (∀ i, f i) := by
  refine_struct { inv := Inv.inv } <;> pi_instance_derive_field

@[to_additive Pi.subtractionMonoid]
instance [∀ i, DivisionMonoid <| f i] : DivisionMonoid (∀ i, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        inv := Inv.inv
        div := Div.div
        npow := Monoid.npow
        zpow := fun z x i => x i ^ z } <;>
    pi_instance_derive_field

@[to_additive Pi.subtractionCommMonoid]
instance [∀ i, DivisionCommMonoid <| f i] : DivisionCommMonoid (∀ i, f i) :=
  { Pi.divisionMonoid, Pi.commSemigroup with }

#print Pi.group /-
@[to_additive]
instance group [∀ i, Group <| f i] : Group (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        inv := Inv.inv
        div := Div.div
        npow := Monoid.npow
        zpow := DivInvMonoid.zpow } <;>
    pi_instance_derive_field
#align pi.group Pi.group
#align pi.add_group Pi.addGroup
-/

#print Pi.commGroup /-
@[to_additive]
instance commGroup [∀ i, CommGroup <| f i] : CommGroup (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        inv := Inv.inv
        div := Div.div
        npow := Monoid.npow
        zpow := DivInvMonoid.zpow } <;>
    pi_instance_derive_field
#align pi.comm_group Pi.commGroup
#align pi.add_comm_group Pi.addCommGroup
-/

#print Pi.leftCancelSemigroup /-
@[to_additive AddLeftCancelSemigroup]
instance leftCancelSemigroup [∀ i, LeftCancelSemigroup <| f i] :
    LeftCancelSemigroup (∀ i : I, f i) := by
  refine_struct { mul := (· * ·) } <;> pi_instance_derive_field
#align pi.left_cancel_semigroup Pi.leftCancelSemigroup
#align pi.add_left_cancel_semigroup Pi.addLeftCancelSemigroup
-/

#print Pi.rightCancelSemigroup /-
@[to_additive AddRightCancelSemigroup]
instance rightCancelSemigroup [∀ i, RightCancelSemigroup <| f i] :
    RightCancelSemigroup (∀ i : I, f i) := by
  refine_struct { mul := (· * ·) } <;> pi_instance_derive_field
#align pi.right_cancel_semigroup Pi.rightCancelSemigroup
#align pi.add_right_cancel_semigroup Pi.addRightCancelSemigroup
-/

#print Pi.leftCancelMonoid /-
@[to_additive AddLeftCancelMonoid]
instance leftCancelMonoid [∀ i, LeftCancelMonoid <| f i] : LeftCancelMonoid (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        npow := Monoid.npow } <;>
    pi_instance_derive_field
#align pi.left_cancel_monoid Pi.leftCancelMonoid
#align pi.add_left_cancel_monoid Pi.addLeftCancelMonoid
-/

#print Pi.rightCancelMonoid /-
@[to_additive AddRightCancelMonoid]
instance rightCancelMonoid [∀ i, RightCancelMonoid <| f i] : RightCancelMonoid (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        npow := Monoid.npow.. } <;>
    pi_instance_derive_field
#align pi.right_cancel_monoid Pi.rightCancelMonoid
#align pi.add_right_cancel_monoid Pi.addRightCancelMonoid
-/

#print Pi.cancelMonoid /-
@[to_additive AddCancelMonoid]
instance cancelMonoid [∀ i, CancelMonoid <| f i] : CancelMonoid (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        npow := Monoid.npow } <;>
    pi_instance_derive_field
#align pi.cancel_monoid Pi.cancelMonoid
#align pi.add_cancel_monoid Pi.addCancelMonoid
-/

#print Pi.cancelCommMonoid /-
@[to_additive AddCancelCommMonoid]
instance cancelCommMonoid [∀ i, CancelCommMonoid <| f i] : CancelCommMonoid (∀ i : I, f i) := by
  refine_struct
      { one := (1 : ∀ i, f i)
        mul := (· * ·)
        npow := Monoid.npow } <;>
    pi_instance_derive_field
#align pi.cancel_comm_monoid Pi.cancelCommMonoid
#align pi.add_cancel_comm_monoid Pi.addCancelCommMonoid
-/

#print Pi.mulZeroClass /-
instance mulZeroClass [∀ i, MulZeroClass <| f i] : MulZeroClass (∀ i : I, f i) := by
  refine_struct
      { zero := (0 : ∀ i, f i)
        mul := (· * ·).. } <;>
    pi_instance_derive_field
#align pi.mul_zero_class Pi.mulZeroClass
-/

#print Pi.mulZeroOneClass /-
instance mulZeroOneClass [∀ i, MulZeroOneClass <| f i] : MulZeroOneClass (∀ i : I, f i) := by
  refine_struct
      { zero := (0 : ∀ i, f i)
        one := (1 : ∀ i, f i)
        mul := (· * ·).. } <;>
    pi_instance_derive_field
#align pi.mul_zero_one_class Pi.mulZeroOneClass
-/

#print Pi.monoidWithZero /-
instance monoidWithZero [∀ i, MonoidWithZero <| f i] : MonoidWithZero (∀ i : I, f i) := by
  refine_struct
      { zero := (0 : ∀ i, f i)
        one := (1 : ∀ i, f i)
        mul := (· * ·)
        npow := Monoid.npow } <;>
    pi_instance_derive_field
#align pi.monoid_with_zero Pi.monoidWithZero
-/

#print Pi.commMonoidWithZero /-
instance commMonoidWithZero [∀ i, CommMonoidWithZero <| f i] : CommMonoidWithZero (∀ i : I, f i) :=
  by
  refine_struct
      { zero := (0 : ∀ i, f i)
        one := (1 : ∀ i, f i)
        mul := (· * ·)
        npow := Monoid.npow } <;>
    pi_instance_derive_field
#align pi.comm_monoid_with_zero Pi.commMonoidWithZero
-/

end Pi

namespace MulHom

/- warning: mul_hom.coe_mul -> MulHom.coe_mul is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {N : Type.{u2}} {mM : Mul.{u1} M} {mN : CommSemigroup.{u2} N} (f : MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) (g : MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))), Eq.{max (succ u1) (succ u2)} (M -> N) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (M -> N) (M -> N) (M -> N) (instHMul.{max u1 u2} (M -> N) (Pi.instMul.{u1, u2} M (fun (ᾰ : M) => N) (fun (i : M) => Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN)))) (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) (fun (_x : MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) => M -> N) (MulHom.hasCoeToFun.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) f) (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) (fun (_x : MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) => M -> N) (MulHom.hasCoeToFun.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) g)) (fun (x : M) => HMul.hMul.{u2, u2, u2} N N N (instHMul.{u2} N (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) (fun (_x : MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) => M -> N) (MulHom.hasCoeToFun.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) f x) (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) (fun (_x : MulHom.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) => M -> N) (MulHom.hasCoeToFun.{u1, u2} M N mM (Semigroup.toHasMul.{u2} N (CommSemigroup.toSemigroup.{u2} N mN))) g x))
but is expected to have type
  forall {M : Type.{u2}} {N : Type.{u1}} {mM : Mul.{u2} M} {mN : CommSemigroup.{u1} N} (f : MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))) (g : MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))), Eq.{max (succ u2) (succ u1)} (forall (ᾰ : M), (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) ᾰ) (HMul.hMul.{max u2 u1, max u2 u1, max u2 u1} (forall (ᾰ : M), (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) ᾰ) (forall (ᾰ : M), (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) ᾰ) (forall (ᾰ : M), (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) ᾰ) (instHMul.{max u2 u1} (forall (ᾰ : M), (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) ᾰ) (Pi.instMul.{u2, u1} M (fun (ᾰ : M) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) ᾰ) (fun (i : M) => Semigroup.toMul.{u1} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) i) (CommSemigroup.toSemigroup.{u1} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) i) mN)))) (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))) M (fun (_x : M) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) _x) (MulHomClass.toFunLike.{max u2 u1, u2, u1} (MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))) M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN)) (MulHom.mulHomClass.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN)))) f) (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))) M (fun (_x : M) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) _x) (MulHomClass.toFunLike.{max u2 u1, u2, u1} (MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))) M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN)) (MulHom.mulHomClass.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN)))) g)) (fun (x : M) => HMul.hMul.{u1, u1, u1} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) (instHMul.{u1} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) (Semigroup.toMul.{u1} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) (CommSemigroup.toSemigroup.{u1} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) x) mN))) (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))) M (fun (_x : M) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) _x) (MulHomClass.toFunLike.{max u2 u1, u2, u1} (MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))) M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN)) (MulHom.mulHomClass.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN)))) f x) (FunLike.coe.{max (succ u2) (succ u1), succ u2, succ u1} (MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))) M (fun (_x : M) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : M) => N) _x) (MulHomClass.toFunLike.{max u2 u1, u2, u1} (MulHom.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN))) M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN)) (MulHom.mulHomClass.{u2, u1} M N mM (Semigroup.toMul.{u1} N (CommSemigroup.toSemigroup.{u1} N mN)))) g x))
Case conversion may be inaccurate. Consider using '#align mul_hom.coe_mul MulHom.coe_mulₓ'. -/
@[to_additive]
theorem coe_mul {M N} {mM : Mul M} {mN : CommSemigroup N} (f g : M →ₙ* N) :
    (f * g : M → N) = fun x => f x * g x :=
  rfl
#align mul_hom.coe_mul MulHom.coe_mul
#align add_hom.coe_add AddHom.coe_add

end MulHom

section MulHom

#print Pi.mulHom /-
/-- A family of mul_hom `f a : γ →ₙ* β a` defines a mul_hom `pi.mul_hom f : γ →ₙ* Π a, β a`
given by `pi.mul_hom f x b = f b x`. -/
@[to_additive
      "A family of add_hom `f a : γ → β a` defines a add_hom `pi.add_hom\nf : γ → Π a, β a` given by `pi.add_hom f x b = f b x`.",
  simps]
def Pi.mulHom {γ : Type w} [∀ i, Mul (f i)] [Mul γ] (g : ∀ i, γ →ₙ* f i) : γ →ₙ* ∀ i, f i
    where
  toFun x i := g i x
  map_mul' x y := funext fun i => (g i).map_mul x y
#align pi.mul_hom Pi.mulHom
#align pi.add_hom Pi.addHom
-/

#print Pi.mulHom_injective /-
@[to_additive]
theorem Pi.mulHom_injective {γ : Type w} [Nonempty I] [∀ i, Mul (f i)] [Mul γ] (g : ∀ i, γ →ₙ* f i)
    (hg : ∀ i, Function.Injective (g i)) : Function.Injective (Pi.mulHom g) := fun x y h =>
  let ⟨i⟩ := ‹Nonempty I›
  hg i ((Function.funext_iff.mp h : _) i)
#align pi.mul_hom_injective Pi.mulHom_injective
#align pi.add_hom_injective Pi.addHom_injective
-/

#print Pi.monoidHom /-
/-- A family of monoid homomorphisms `f a : γ →* β a` defines a monoid homomorphism
`pi.monoid_mul_hom f : γ →* Π a, β a` given by `pi.monoid_mul_hom f x b = f b x`. -/
@[to_additive
      "A family of additive monoid homomorphisms `f a : γ →+ β a` defines a monoid\nhomomorphism `pi.add_monoid_hom f : γ →+ Π a, β a` given by `pi.add_monoid_hom f x b\n= f b x`.",
  simps]
def Pi.monoidHom {γ : Type w} [∀ i, MulOneClass (f i)] [MulOneClass γ] (g : ∀ i, γ →* f i) :
    γ →* ∀ i, f i :=
  {
    Pi.mulHom fun i => (g i).toMulHom with
    toFun := fun x i => g i x
    map_one' := funext fun i => (g i).map_one }
#align pi.monoid_hom Pi.monoidHom
#align pi.add_monoid_hom Pi.addMonoidHom
-/

/- warning: pi.monoid_hom_injective -> Pi.monoidHom_injective is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} {γ : Type.{u3}} [_inst_1 : Nonempty.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)] [_inst_3 : MulOneClass.{u3} γ] (g : forall (i : I), MonoidHom.{u3, u2} γ (f i) _inst_3 (_inst_2 i)), (forall (i : I), Function.Injective.{succ u3, succ u2} γ (f i) (coeFn.{max (succ u2) (succ u3), max (succ u3) (succ u2)} (MonoidHom.{u3, u2} γ (f i) _inst_3 (_inst_2 i)) (fun (_x : MonoidHom.{u3, u2} γ (f i) _inst_3 (_inst_2 i)) => γ -> (f i)) (MonoidHom.hasCoeToFun.{u3, u2} γ (f i) _inst_3 (_inst_2 i)) (g i))) -> (Function.Injective.{succ u3, max (succ u1) (succ u2)} γ (forall (i : I), (fun (i : I) => f i) i) (coeFn.{max (succ (max u1 u2)) (succ u3), max (succ u3) (succ (max u1 u2))} (MonoidHom.{u3, max u1 u2} γ (forall (i : I), (fun (i : I) => f i) i) _inst_3 (Pi.mulOneClass.{u1, u2} I (fun (i : I) => (fun (i : I) => f i) i) (fun (i : I) => (fun (i : I) => _inst_2 i) i))) (fun (_x : MonoidHom.{u3, max u1 u2} γ (forall (i : I), (fun (i : I) => f i) i) _inst_3 (Pi.mulOneClass.{u1, u2} I (fun (i : I) => (fun (i : I) => f i) i) (fun (i : I) => (fun (i : I) => _inst_2 i) i))) => γ -> (forall (i : I), (fun (i : I) => f i) i)) (MonoidHom.hasCoeToFun.{u3, max u1 u2} γ (forall (i : I), (fun (i : I) => f i) i) _inst_3 (Pi.mulOneClass.{u1, u2} I (fun (i : I) => (fun (i : I) => f i) i) (fun (i : I) => (fun (i : I) => _inst_2 i) i))) (Pi.monoidHom.{u1, u2, u3} I (fun (i : I) => f i) γ (fun (i : I) => _inst_2 i) _inst_3 g)))
but is expected to have type
  forall {I : Type.{u1}} {f : I -> Type.{u2}} {γ : Type.{u3}} [_inst_1 : Nonempty.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)] [_inst_3 : MulOneClass.{u3} γ] (g : forall (i : I), MonoidHom.{u3, u2} γ (f i) _inst_3 (_inst_2 i)), (forall (i : I), Function.Injective.{succ u3, succ u2} γ (f i) (FunLike.coe.{max (succ u2) (succ u3), succ u3, succ u2} (MonoidHom.{u3, u2} γ (f i) _inst_3 (_inst_2 i)) γ (fun (_x : γ) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : γ) => f i) _x) (MulHomClass.toFunLike.{max u2 u3, u3, u2} (MonoidHom.{u3, u2} γ (f i) _inst_3 (_inst_2 i)) γ (f i) (MulOneClass.toMul.{u3} γ _inst_3) (MulOneClass.toMul.{u2} (f i) (_inst_2 i)) (MonoidHomClass.toMulHomClass.{max u2 u3, u3, u2} (MonoidHom.{u3, u2} γ (f i) _inst_3 (_inst_2 i)) γ (f i) _inst_3 (_inst_2 i) (MonoidHom.monoidHomClass.{u3, u2} γ (f i) _inst_3 (_inst_2 i)))) (g i))) -> (Function.Injective.{succ u3, max (succ u1) (succ u2)} γ (forall (i : I), f i) (FunLike.coe.{max (max (succ u1) (succ u2)) (succ u3), succ u3, max (succ u1) (succ u2)} (MonoidHom.{u3, max u1 u2} γ (forall (i : I), f i) _inst_3 (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) γ (fun (_x : γ) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : γ) => forall (i : I), f i) _x) (MulHomClass.toFunLike.{max (max u1 u2) u3, u3, max u1 u2} (MonoidHom.{u3, max u1 u2} γ (forall (i : I), f i) _inst_3 (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) γ (forall (i : I), f i) (MulOneClass.toMul.{u3} γ _inst_3) (MulOneClass.toMul.{max u1 u2} (forall (i : I), f i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) (MonoidHomClass.toMulHomClass.{max (max u1 u2) u3, u3, max u1 u2} (MonoidHom.{u3, max u1 u2} γ (forall (i : I), f i) _inst_3 (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) γ (forall (i : I), f i) _inst_3 (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) (MonoidHom.monoidHomClass.{u3, max u1 u2} γ (forall (i : I), f i) _inst_3 (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))))) (Pi.monoidHom.{u1, u2, u3} I (fun (i : I) => f i) γ (fun (i : I) => _inst_2 i) _inst_3 g)))
Case conversion may be inaccurate. Consider using '#align pi.monoid_hom_injective Pi.monoidHom_injectiveₓ'. -/
@[to_additive]
theorem Pi.monoidHom_injective {γ : Type w} [Nonempty I] [∀ i, MulOneClass (f i)] [MulOneClass γ]
    (g : ∀ i, γ →* f i) (hg : ∀ i, Function.Injective (g i)) :
    Function.Injective (Pi.monoidHom g) :=
  Pi.mulHom_injective (fun i => (g i).toMulHom) hg
#align pi.monoid_hom_injective Pi.monoidHom_injective
#align pi.add_monoid_hom_injective Pi.addMonoidHom_injective

variable (f) [∀ i, Mul (f i)]

#print Pi.evalMulHom /-
/-- Evaluation of functions into an indexed collection of semigroups at a point is a semigroup
homomorphism.
This is `function.eval i` as a `mul_hom`. -/
@[to_additive
      "Evaluation of functions into an indexed collection of additive semigroups at a\npoint is an additive semigroup homomorphism.\nThis is `function.eval i` as an `add_hom`.",
  simps]
def Pi.evalMulHom (i : I) : (∀ i, f i) →ₙ* f i
    where
  toFun g := g i
  map_mul' x y := Pi.mul_apply _ _ i
#align pi.eval_mul_hom Pi.evalMulHom
#align pi.eval_add_hom Pi.evalAddHom
-/

#print Pi.constMulHom /-
/-- `function.const` as a `mul_hom`. -/
@[to_additive "`function.const` as an `add_hom`.", simps]
def Pi.constMulHom (α β : Type _) [Mul β] : β →ₙ* α → β
    where
  toFun := Function.const α
  map_mul' _ _ := rfl
#align pi.const_mul_hom Pi.constMulHom
#align pi.const_add_hom Pi.constAddHom
-/

/- warning: mul_hom.coe_fn -> MulHom.coeFn is a dubious translation:
lean 3 declaration is
  forall (α : Type.{u1}) (β : Type.{u2}) [_inst_2 : Mul.{u1} α] [_inst_3 : CommSemigroup.{u2} β], MulHom.{max u2 u1, max u1 u2} (MulHom.{u1, u2} α β _inst_2 (Semigroup.toHasMul.{u2} β (CommSemigroup.toSemigroup.{u2} β _inst_3))) (α -> β) (MulHom.hasMul.{u1, u2} α β _inst_2 _inst_3) (Pi.instMul.{u1, u2} α (fun (ᾰ : α) => β) (fun (i : α) => Semigroup.toHasMul.{u2} β (CommSemigroup.toSemigroup.{u2} β _inst_3)))
but is expected to have type
  forall (α : Type.{u1}) (β : Type.{u2}) [_inst_2 : Mul.{u1} α] [_inst_3 : CommSemigroup.{u2} β], MulHom.{max u2 u1, max u1 u2} (MulHom.{u1, u2} α β _inst_2 (Semigroup.toMul.{u2} β (CommSemigroup.toSemigroup.{u2} β _inst_3))) (α -> β) (MulHom.instMulMulHomToMulToSemigroup.{u1, u2} α β _inst_2 _inst_3) (Pi.instMul.{u1, u2} α (fun (ᾰ : α) => β) (fun (i : α) => Semigroup.toMul.{u2} β (CommSemigroup.toSemigroup.{u2} β _inst_3)))
Case conversion may be inaccurate. Consider using '#align mul_hom.coe_fn MulHom.coeFnₓ'. -/
/-- Coercion of a `mul_hom` into a function is itself a `mul_hom`.
See also `mul_hom.eval`. -/
@[to_additive
      "Coercion of an `add_hom` into a function is itself a `add_hom`.\nSee also `add_hom.eval`. ",
  simps]
def MulHom.coeFn (α β : Type _) [Mul α] [CommSemigroup β] : (α →ₙ* β) →ₙ* α → β
    where
  toFun g := g
  map_mul' x y := rfl
#align mul_hom.coe_fn MulHom.coeFn
#align add_hom.coe_fn AddHom.coeFn

#print MulHom.compLeft /-
/-- Semigroup homomorphism between the function spaces `I → α` and `I → β`, induced by a semigroup
homomorphism `f` between `α` and `β`. -/
@[to_additive
      "Additive semigroup homomorphism between the function spaces `I → α` and `I → β`,\ninduced by an additive semigroup homomorphism `f` between `α` and `β`",
  simps]
protected def MulHom.compLeft {α β : Type _} [Mul α] [Mul β] (f : α →ₙ* β) (I : Type _) :
    (I → α) →ₙ* I → β where
  toFun h := f ∘ h
  map_mul' _ _ := by ext <;> simp
#align mul_hom.comp_left MulHom.compLeft
#align add_hom.comp_left AddHom.compLeft
-/

end MulHom

section MonoidHom

variable (f) [∀ i, MulOneClass (f i)]

#print Pi.evalMonoidHom /-
/-- Evaluation of functions into an indexed collection of monoids at a point is a monoid
homomorphism.
This is `function.eval i` as a `monoid_hom`. -/
@[to_additive
      "Evaluation of functions into an indexed collection of additive monoids at a\npoint is an additive monoid homomorphism.\nThis is `function.eval i` as an `add_monoid_hom`.",
  simps]
def Pi.evalMonoidHom (i : I) : (∀ i, f i) →* f i
    where
  toFun g := g i
  map_one' := Pi.one_apply i
  map_mul' x y := Pi.mul_apply _ _ i
#align pi.eval_monoid_hom Pi.evalMonoidHom
#align pi.eval_add_monoid_hom Pi.evalAddMonoidHom
-/

#print Pi.constMonoidHom /-
/-- `function.const` as a `monoid_hom`. -/
@[to_additive "`function.const` as an `add_monoid_hom`.", simps]
def Pi.constMonoidHom (α β : Type _) [MulOneClass β] : β →* α → β
    where
  toFun := Function.const α
  map_one' := rfl
  map_mul' _ _ := rfl
#align pi.const_monoid_hom Pi.constMonoidHom
#align pi.const_add_monoid_hom Pi.constAddMonoidHom
-/

#print MonoidHom.coeFn /-
/-- Coercion of a `monoid_hom` into a function is itself a `monoid_hom`.

See also `monoid_hom.eval`. -/
@[to_additive
      "Coercion of an `add_monoid_hom` into a function is itself a `add_monoid_hom`.\n\nSee also `add_monoid_hom.eval`. ",
  simps]
def MonoidHom.coeFn (α β : Type _) [MulOneClass α] [CommMonoid β] : (α →* β) →* α → β
    where
  toFun g := g
  map_one' := rfl
  map_mul' x y := rfl
#align monoid_hom.coe_fn MonoidHom.coeFn
#align add_monoid_hom.coe_fn AddMonoidHom.coeFn
-/

#print MonoidHom.compLeft /-
/-- Monoid homomorphism between the function spaces `I → α` and `I → β`, induced by a monoid
homomorphism `f` between `α` and `β`. -/
@[to_additive
      "Additive monoid homomorphism between the function spaces `I → α` and `I → β`,\ninduced by an additive monoid homomorphism `f` between `α` and `β`",
  simps]
protected def MonoidHom.compLeft {α β : Type _} [MulOneClass α] [MulOneClass β] (f : α →* β)
    (I : Type _) : (I → α) →* I → β where
  toFun h := f ∘ h
  map_one' := by ext <;> simp
  map_mul' _ _ := by ext <;> simp
#align monoid_hom.comp_left MonoidHom.compLeft
#align add_monoid_hom.comp_left AddMonoidHom.compLeft
-/

end MonoidHom

section Single

variable [DecidableEq I]

open Pi

variable (f)

#print OneHom.single /-
/-- The one-preserving homomorphism including a single value
into a dependent family of values, as functions supported at a point.

This is the `one_hom` version of `pi.mul_single`. -/
@[to_additive ZeroHom.single
      "The zero-preserving homomorphism including a single value\ninto a dependent family of values, as functions supported at a point.\n\nThis is the `zero_hom` version of `pi.single`."]
def OneHom.single [∀ i, One <| f i] (i : I) : OneHom (f i) (∀ i, f i)
    where
  toFun := mulSingle i
  map_one' := mulSingle_one i
#align one_hom.single OneHom.single
#align zero_hom.single ZeroHom.single
-/

#print OneHom.single_apply /-
@[simp, to_additive]
theorem OneHom.single_apply [∀ i, One <| f i] (i : I) (x : f i) :
    OneHom.single f i x = mulSingle i x :=
  rfl
#align one_hom.single_apply OneHom.single_apply
#align zero_hom.single_apply ZeroHom.single_apply
-/

#print MonoidHom.single /-
/-- The monoid homomorphism including a single monoid into a dependent family of additive monoids,
as functions supported at a point.

This is the `monoid_hom` version of `pi.mul_single`. -/
@[to_additive
      "The additive monoid homomorphism including a single additive\nmonoid into a dependent family of additive monoids, as functions supported at a point.\n\nThis is the `add_monoid_hom` version of `pi.single`."]
def MonoidHom.single [∀ i, MulOneClass <| f i] (i : I) : f i →* ∀ i, f i :=
  { OneHom.single f i with map_mul' := mulSingle_op₂ (fun _ => (· * ·)) (fun _ => one_mul _) _ }
#align monoid_hom.single MonoidHom.single
#align add_monoid_hom.single AddMonoidHom.single
-/

/- warning: monoid_hom.single_apply -> MonoidHom.single_apply is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} (f : I -> Type.{u2}) [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)] (i : I) (x : f i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (coeFn.{max (succ (max u1 u2)) (succ u2), max (succ u2) (succ (max u1 u2))} (MonoidHom.{u2, max u1 u2} (f i) (forall (i : I), f i) ((fun (i : I) => _inst_2 i) i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => (fun (i : I) => _inst_2 i) i))) (fun (_x : MonoidHom.{u2, max u1 u2} (f i) (forall (i : I), f i) ((fun (i : I) => _inst_2 i) i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => (fun (i : I) => _inst_2 i) i))) => (f i) -> (forall (i : I), f i)) (MonoidHom.hasCoeToFun.{u2, max u1 u2} (f i) (forall (i : I), f i) ((fun (i : I) => _inst_2 i) i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => (fun (i : I) => _inst_2 i) i))) (MonoidHom.single.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_2 i) i) x) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (_inst_2 i)) i x)
but is expected to have type
  forall {I : Type.{u1}} (f : I -> Type.{u2}) [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)] (i : I) (x : f i), Eq.{max (succ u1) (succ u2)} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : f i) => forall (i : I), f i) x) (FunLike.coe.{max (succ u1) (succ u2), succ u2, max (succ u1) (succ u2)} (MonoidHom.{u2, max u1 u2} (f i) (forall (i : I), f i) (_inst_2 i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) (f i) (fun (_x : f i) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : f i) => forall (i : I), f i) _x) (MulHomClass.toFunLike.{max u1 u2, u2, max u1 u2} (MonoidHom.{u2, max u1 u2} (f i) (forall (i : I), f i) (_inst_2 i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) (f i) (forall (i : I), f i) (MulOneClass.toMul.{u2} (f i) (_inst_2 i)) (MulOneClass.toMul.{max u1 u2} (forall (i : I), f i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) (MonoidHomClass.toMulHomClass.{max u1 u2, u2, max u1 u2} (MonoidHom.{u2, max u1 u2} (f i) (forall (i : I), f i) (_inst_2 i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) (f i) (forall (i : I), f i) (_inst_2 i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) (MonoidHom.monoidHomClass.{u2, max u1 u2} (f i) (forall (i : I), f i) (_inst_2 i) (Pi.mulOneClass.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))))) (MonoidHom.single.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_2 i) i) x) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toOne.{u2} (f i) (_inst_2 i)) i x)
Case conversion may be inaccurate. Consider using '#align monoid_hom.single_apply MonoidHom.single_applyₓ'. -/
@[simp, to_additive]
theorem MonoidHom.single_apply [∀ i, MulOneClass <| f i] (i : I) (x : f i) :
    MonoidHom.single f i x = mulSingle i x :=
  rfl
#align monoid_hom.single_apply MonoidHom.single_apply
#align add_monoid_hom.single_apply AddMonoidHom.single_apply

/- warning: mul_hom.single -> MulHom.single is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} (f : I -> Type.{u2}) [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulZeroClass.{u2} (f i)] (i : I), MulHom.{u2, max u1 u2} (f i) (forall (i : I), f i) (MulZeroClass.toHasMul.{u2} (f i) (_inst_2 i)) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulZeroClass.toHasMul.{u2} (f i) (_inst_2 i)))
but is expected to have type
  forall {I : Type.{u1}} (f : I -> Type.{u2}) [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulZeroClass.{u2} (f i)] (i : I), MulHom.{u2, max u1 u2} (f i) (forall (i : I), f i) (MulZeroClass.toMul.{u2} (f i) (_inst_2 i)) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulZeroClass.toMul.{u2} (f i) (_inst_2 i)))
Case conversion may be inaccurate. Consider using '#align mul_hom.single MulHom.singleₓ'. -/
/-- The multiplicative homomorphism including a single `mul_zero_class`
into a dependent family of `mul_zero_class`es, as functions supported at a point.

This is the `mul_hom` version of `pi.single`. -/
@[simps]
def MulHom.single [∀ i, MulZeroClass <| f i] (i : I) : f i →ₙ* ∀ i, f i
    where
  toFun := single i
  map_mul' := Pi.single_op₂ (fun _ => (· * ·)) (fun _ => MulZeroClass.zero_mul _) _
#align mul_hom.single MulHom.single

variable {f}

/- warning: pi.mul_single_mul -> Pi.mulSingle_mul is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)] (i : I) (x : f i) (y : f i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (_inst_2 i)) i (HMul.hMul.{u2, u2, u2} (f i) (f i) (f i) (instHMul.{u2} (f i) (MulOneClass.toHasMul.{u2} (f i) (_inst_2 i))) x y)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (forall (i : I), f i) (forall (i : I), f i) (forall (i : I), f i) (instHMul.{max u1 u2} (forall (i : I), f i) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulOneClass.toHasMul.{u2} (f i) (_inst_2 i)))) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (_inst_2 i)) i x) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (_inst_2 i)) i y))
but is expected to have type
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)] (i : I) (x : f i) (y : f i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toOne.{u2} (f i) (_inst_2 i)) i (HMul.hMul.{u2, u2, u2} (f i) (f i) (f i) (instHMul.{u2} (f i) (MulOneClass.toMul.{u2} (f i) (_inst_2 i))) x y)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (forall (i : I), f i) (forall (i : I), f i) (forall (i : I), f i) (instHMul.{max u1 u2} (forall (i : I), f i) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulOneClass.toMul.{u2} (f i) (_inst_2 i)))) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toOne.{u2} (f i) (_inst_2 i)) i x) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toOne.{u2} (f i) (_inst_2 i)) i y))
Case conversion may be inaccurate. Consider using '#align pi.mul_single_mul Pi.mulSingle_mulₓ'. -/
@[to_additive]
theorem Pi.mulSingle_mul [∀ i, MulOneClass <| f i] (i : I) (x y : f i) :
    mulSingle i (x * y) = mulSingle i x * mulSingle i y :=
  (MonoidHom.single f i).map_mul x y
#align pi.mul_single_mul Pi.mulSingle_mul
#align pi.single_add Pi.single_add

/- warning: pi.mul_single_inv -> Pi.mulSingle_inv is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), Group.{u2} (f i)] (i : I) (x : f i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (Monoid.toMulOneClass.{u2} (f i) (DivInvMonoid.toMonoid.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) i (Inv.inv.{u2} (f i) (DivInvMonoid.toHasInv.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))) x)) (Inv.inv.{max u1 u2} (forall (i : I), f i) (Pi.instInv.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => DivInvMonoid.toHasInv.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i)))) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (Monoid.toMulOneClass.{u2} (f i) (DivInvMonoid.toMonoid.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) i x))
but is expected to have type
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), Group.{u2} (f i)] (i : I) (x : f i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => InvOneClass.toOne.{u2} (f i) (DivInvOneMonoid.toInvOneClass.{u2} (f i) (DivisionMonoid.toDivInvOneMonoid.{u2} (f i) (Group.toDivisionMonoid.{u2} (f i) (_inst_2 i))))) i (Inv.inv.{u2} (f i) (InvOneClass.toInv.{u2} (f i) (DivInvOneMonoid.toInvOneClass.{u2} (f i) (DivisionMonoid.toDivInvOneMonoid.{u2} (f i) (Group.toDivisionMonoid.{u2} (f i) (_inst_2 i))))) x)) (Inv.inv.{max u2 u1} (forall (i : I), f i) (Pi.instInv.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => InvOneClass.toInv.{u2} (f i) (DivInvOneMonoid.toInvOneClass.{u2} (f i) (DivisionMonoid.toDivInvOneMonoid.{u2} (f i) (Group.toDivisionMonoid.{u2} (f i) (_inst_2 i)))))) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => InvOneClass.toOne.{u2} (f i) (DivInvOneMonoid.toInvOneClass.{u2} (f i) (DivisionMonoid.toDivInvOneMonoid.{u2} (f i) (Group.toDivisionMonoid.{u2} (f i) (_inst_2 i))))) i x))
Case conversion may be inaccurate. Consider using '#align pi.mul_single_inv Pi.mulSingle_invₓ'. -/
@[to_additive]
theorem Pi.mulSingle_inv [∀ i, Group <| f i] (i : I) (x : f i) :
    mulSingle i x⁻¹ = (mulSingle i x)⁻¹ :=
  (MonoidHom.single f i).map_inv x
#align pi.mul_single_inv Pi.mulSingle_inv
#align pi.single_neg Pi.single_neg

/- warning: pi.single_div -> Pi.single_div is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), Group.{u2} (f i)] (i : I) (x : f i) (y : f i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (Monoid.toMulOneClass.{u2} (f i) (DivInvMonoid.toMonoid.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) i (HDiv.hDiv.{u2, u2, u2} (f i) (f i) (f i) (instHDiv.{u2} (f i) (DivInvMonoid.toHasDiv.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i)))) x y)) (HDiv.hDiv.{max u1 u2, max u1 u2, max u1 u2} (forall (i : I), f i) (forall (i : I), f i) (forall (i : I), f i) (instHDiv.{max u1 u2} (forall (i : I), f i) (Pi.instDiv.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => DivInvMonoid.toHasDiv.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (Monoid.toMulOneClass.{u2} (f i) (DivInvMonoid.toMonoid.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) i x) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (Monoid.toMulOneClass.{u2} (f i) (DivInvMonoid.toMonoid.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) i y))
but is expected to have type
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), Group.{u2} (f i)] (i : I) (x : f i) (y : f i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => InvOneClass.toOne.{u2} (f i) (DivInvOneMonoid.toInvOneClass.{u2} (f i) (DivisionMonoid.toDivInvOneMonoid.{u2} (f i) (Group.toDivisionMonoid.{u2} (f i) (_inst_2 i))))) i (HDiv.hDiv.{u2, u2, u2} (f i) (f i) (f i) (instHDiv.{u2} (f i) (DivInvMonoid.toDiv.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i)))) x y)) (HDiv.hDiv.{max u1 u2, max u1 u2, max u1 u2} (forall (i : I), f i) (forall (i : I), f i) (forall (i : I), f i) (instHDiv.{max u1 u2} (forall (i : I), f i) (Pi.instDiv.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => DivInvMonoid.toDiv.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => InvOneClass.toOne.{u2} (f i) (DivInvOneMonoid.toInvOneClass.{u2} (f i) (DivisionMonoid.toDivInvOneMonoid.{u2} (f i) (Group.toDivisionMonoid.{u2} (f i) (_inst_2 i))))) i x) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => InvOneClass.toOne.{u2} (f i) (DivInvOneMonoid.toInvOneClass.{u2} (f i) (DivisionMonoid.toDivInvOneMonoid.{u2} (f i) (Group.toDivisionMonoid.{u2} (f i) (_inst_2 i))))) i y))
Case conversion may be inaccurate. Consider using '#align pi.single_div Pi.single_divₓ'. -/
@[to_additive]
theorem Pi.single_div [∀ i, Group <| f i] (i : I) (x y : f i) :
    mulSingle i (x / y) = mulSingle i x / mulSingle i y :=
  (MonoidHom.single f i).map_div x y
#align pi.single_div Pi.single_div
#align pi.single_sub Pi.single_sub

/- warning: pi.single_mul -> Pi.single_mul is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulZeroClass.{u2} (f i)] (i : I) (x : f i) (y : f i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (Pi.single.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulZeroClass.toHasZero.{u2} (f i) (_inst_2 i)) i (HMul.hMul.{u2, u2, u2} (f i) (f i) (f i) (instHMul.{u2} (f i) (MulZeroClass.toHasMul.{u2} (f i) (_inst_2 i))) x y)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (forall (i : I), f i) (forall (i : I), f i) (forall (i : I), f i) (instHMul.{max u1 u2} (forall (i : I), f i) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulZeroClass.toHasMul.{u2} (f i) (_inst_2 i)))) (Pi.single.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulZeroClass.toHasZero.{u2} (f i) (_inst_2 i)) i x) (Pi.single.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulZeroClass.toHasZero.{u2} (f i) (_inst_2 i)) i y))
but is expected to have type
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulZeroClass.{u2} (f i)] (i : I) (x : f i) (y : f i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (Pi.single.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulZeroClass.toZero.{u2} (f i) (_inst_2 i)) i (HMul.hMul.{u2, u2, u2} (f i) (f i) (f i) (instHMul.{u2} (f i) (MulZeroClass.toMul.{u2} (f i) (_inst_2 i))) x y)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (forall (i : I), f i) (forall (i : I), f i) (forall (i : I), f i) (instHMul.{max u1 u2} (forall (i : I), f i) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulZeroClass.toMul.{u2} (f i) (_inst_2 i)))) (Pi.single.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulZeroClass.toZero.{u2} (f i) (_inst_2 i)) i x) (Pi.single.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulZeroClass.toZero.{u2} (f i) (_inst_2 i)) i y))
Case conversion may be inaccurate. Consider using '#align pi.single_mul Pi.single_mulₓ'. -/
theorem Pi.single_mul [∀ i, MulZeroClass <| f i] (i : I) (x y : f i) :
    single i (x * y) = single i x * single i y :=
  (MulHom.single f i).map_mul x y
#align pi.single_mul Pi.single_mul

/- warning: pi.mul_single_commute -> Pi.mulSingle_commute is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)], Pairwise.{u1} I (fun (i : I) (j : I) => forall (x : f i) (y : f j), Commute.{max u1 u2} (forall (i : I), f i) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulOneClass.toHasMul.{u2} (f i) (_inst_2 i))) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (_inst_2 i)) i x) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (_inst_2 i)) j y))
but is expected to have type
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)], Pairwise.{u1} I (fun (i : I) (j : I) => forall (x : f i) (y : f j), Commute.{max u2 u1} (forall (i : I), f i) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulOneClass.toMul.{u2} (f i) (_inst_2 i))) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toOne.{u2} (f i) (_inst_2 i)) i x) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toOne.{u2} (f i) (_inst_2 i)) j y))
Case conversion may be inaccurate. Consider using '#align pi.mul_single_commute Pi.mulSingle_commuteₓ'. -/
/-- The injection into a pi group at different indices commutes.

For injections of commuting elements at the same index, see `commute.map` -/
@[to_additive
      "The injection into an additive pi group at different indices commutes.\n\nFor injections of commuting elements at the same index, see `add_commute.map`"]
theorem Pi.mulSingle_commute [∀ i, MulOneClass <| f i] :
    Pairwise fun i j => ∀ (x : f i) (y : f j), Commute (mulSingle i x) (mulSingle j y) :=
  by
  intro i j hij x y; ext k
  by_cases h1 : i = k;
  · subst h1
    simp [hij]
  by_cases h2 : j = k;
  · subst h2
    simp [hij]
  simp [h1, h2]
#align pi.mul_single_commute Pi.mulSingle_commute
#align pi.single_commute Pi.single_commute

/- warning: pi.mul_single_apply_commute -> Pi.mulSingle_apply_commute is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)] (x : forall (i : I), f i) (i : I) (j : I), Commute.{max u1 u2} (forall (i : I), f i) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulOneClass.toHasMul.{u2} (f i) (_inst_2 i))) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (_inst_2 i)) i (x i)) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (_inst_2 i)) j (x j))
but is expected to have type
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), MulOneClass.{u2} (f i)] (x : forall (i : I), f i) (i : I) (j : I), Commute.{max u2 u1} (forall (i : I), f i) (Pi.instMul.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => MulOneClass.toMul.{u2} (f i) (_inst_2 i))) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toOne.{u2} (f i) (_inst_2 i)) i (x i)) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toOne.{u2} (f i) (_inst_2 i)) j (x j))
Case conversion may be inaccurate. Consider using '#align pi.mul_single_apply_commute Pi.mulSingle_apply_commuteₓ'. -/
/-- The injection into a pi group with the same values commutes. -/
@[to_additive "The injection into an additive pi group with the same values commutes."]
theorem Pi.mulSingle_apply_commute [∀ i, MulOneClass <| f i] (x : ∀ i, f i) (i j : I) :
    Commute (mulSingle i (x i)) (mulSingle j (x j)) :=
  by
  obtain rfl | hij := Decidable.eq_or_ne i j
  · rfl
  · exact Pi.mulSingle_commute hij _ _
#align pi.mul_single_apply_commute Pi.mulSingle_apply_commute
#align pi.single_apply_commute Pi.single_apply_commute

/- warning: pi.update_eq_div_mul_single -> Pi.update_eq_div_mul_mulSingle is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} (i : I) [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), Group.{u2} (f i)] (g : forall (i : I), f i) (x : f i), Eq.{max (succ u1) (succ u2)} (forall (a : I), f a) (Function.update.{succ u1, succ u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) g i x) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (forall (a : I), f a) (forall (a : I), f a) (forall (a : I), f a) (instHMul.{max u1 u2} (forall (a : I), f a) (Pi.instMul.{u1, u2} I (fun (a : I) => f a) (fun (i : I) => MulOneClass.toHasMul.{u2} (f i) (Monoid.toMulOneClass.{u2} (f i) (DivInvMonoid.toMonoid.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))))) (HDiv.hDiv.{max u1 u2, max u1 u2, max u1 u2} (forall (a : I), f a) (forall (a : I), f a) (forall (a : I), f a) (instHDiv.{max u1 u2} (forall (a : I), f a) (Pi.instDiv.{u1, u2} I (fun (a : I) => f a) (fun (i : I) => DivInvMonoid.toHasDiv.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) g (Pi.mulSingle.{u1, u2} I (fun (a : I) => f a) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (Monoid.toMulOneClass.{u2} (f i) (DivInvMonoid.toMonoid.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) i (g i))) (Pi.mulSingle.{u1, u2} I (fun (a : I) => f a) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} (f i) (Monoid.toMulOneClass.{u2} (f i) (DivInvMonoid.toMonoid.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) i x))
but is expected to have type
  forall {I : Type.{u1}} {f : I -> Type.{u2}} (i : I) [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), Group.{u2} (f i)] (g : forall (i : I), f i) (x : f i), Eq.{max (succ u1) (succ u2)} (forall (a : I), f a) (Function.update.{succ u1, succ u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) g i x) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (forall (a : I), f a) (forall (a : I), f a) (forall (a : I), f a) (instHMul.{max u1 u2} (forall (a : I), f a) (Pi.instMul.{u1, u2} I (fun (a : I) => f a) (fun (i : I) => MulOneClass.toMul.{u2} (f i) (Monoid.toMulOneClass.{u2} (f i) (DivInvMonoid.toMonoid.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))))) (HDiv.hDiv.{max u1 u2, max u1 u2, max u1 u2} (forall (a : I), f a) (forall (a : I), f a) (forall (a : I), f a) (instHDiv.{max u1 u2} (forall (a : I), f a) (Pi.instDiv.{u1, u2} I (fun (a : I) => f a) (fun (i : I) => DivInvMonoid.toDiv.{u2} (f i) (Group.toDivInvMonoid.{u2} (f i) (_inst_2 i))))) g (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => InvOneClass.toOne.{u2} (f i) (DivInvOneMonoid.toInvOneClass.{u2} (f i) (DivisionMonoid.toDivInvOneMonoid.{u2} (f i) (Group.toDivisionMonoid.{u2} (f i) (_inst_2 i))))) i (g i))) (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => InvOneClass.toOne.{u2} (f i) (DivInvOneMonoid.toInvOneClass.{u2} (f i) (DivisionMonoid.toDivInvOneMonoid.{u2} (f i) (Group.toDivisionMonoid.{u2} (f i) (_inst_2 i))))) i x))
Case conversion may be inaccurate. Consider using '#align pi.update_eq_div_mul_single Pi.update_eq_div_mul_mulSingleₓ'. -/
@[to_additive update_eq_sub_add_single]
theorem Pi.update_eq_div_mul_mulSingle [∀ i, Group <| f i] (g : ∀ i : I, f i) (x : f i) :
    Function.update g i x = g / mulSingle i (g i) * mulSingle i x :=
  by
  ext j
  rcases eq_or_ne i j with (rfl | h)
  · simp
  · simp [Function.update_noteq h.symm, h]
#align pi.update_eq_div_mul_single Pi.update_eq_div_mul_mulSingle

/- warning: pi.mul_single_mul_mul_single_eq_mul_single_mul_mul_single -> Pi.mulSingle_mul_mulSingle_eq_mulSingle_mul_mulSingle is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} I] {M : Type.{u2}} [_inst_2 : CommMonoid.{u2} M] {k : I} {l : I} {m : I} {n : I} {u : M} {v : M}, (Ne.{succ u2} M u (OfNat.ofNat.{u2} M 1 (OfNat.mk.{u2} M 1 (One.one.{u2} M (MulOneClass.toHasOne.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))))))) -> (Ne.{succ u2} M v (OfNat.ofNat.{u2} M 1 (OfNat.mk.{u2} M 1 (One.one.{u2} M (MulOneClass.toHasOne.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))))))) -> (Iff (Eq.{succ (max u1 u2)} (I -> M) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (I -> M) (I -> M) (I -> M) (instHMul.{max u1 u2} (I -> M) (Pi.instMul.{u1, u2} I (fun (i : I) => M) (fun (i : I) => MulOneClass.toHasMul.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))))) (Pi.mulSingle.{u1, u2} I (fun {k : I} => M) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))) k u) (Pi.mulSingle.{u1, u2} I (fun (i : I) => M) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))) l v)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (I -> M) (I -> M) (I -> M) (instHMul.{max u1 u2} (I -> M) (Pi.instMul.{u1, u2} I (fun (i : I) => M) (fun (i : I) => MulOneClass.toHasMul.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))))) (Pi.mulSingle.{u1, u2} I (fun (i : I) => M) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))) m u) (Pi.mulSingle.{u1, u2} I (fun (i : I) => M) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulOneClass.toHasOne.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))) n v))) (Or (And (Eq.{succ u1} I k m) (Eq.{succ u1} I l n)) (Or (And (Eq.{succ u2} M u v) (And (Eq.{succ u1} I k n) (Eq.{succ u1} I l m))) (And (Eq.{succ u2} M (HMul.hMul.{u2, u2, u2} M M M (instHMul.{u2} M (MulOneClass.toHasMul.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2)))) u v) (OfNat.ofNat.{u2} M 1 (OfNat.mk.{u2} M 1 (One.one.{u2} M (MulOneClass.toHasOne.{u2} M (Monoid.toMulOneClass.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))))))) (And (Eq.{succ u1} I k l) (Eq.{succ u1} I m n))))))
but is expected to have type
  forall {I : Type.{u2}} [_inst_1 : DecidableEq.{succ u2} I] {M : Type.{u1}} [_inst_2 : CommMonoid.{u1} M] {k : I} {l : I} {m : I} {n : I} {u : M} {v : M}, (Ne.{succ u1} M u (OfNat.ofNat.{u1} M 1 (One.toOfNat1.{u1} M (Monoid.toOne.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2))))) -> (Ne.{succ u1} M v (OfNat.ofNat.{u1} M 1 (One.toOfNat1.{u1} M (Monoid.toOne.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2))))) -> (Iff (Eq.{succ (max u2 u1)} (I -> M) (HMul.hMul.{max u2 u1, max u2 u1, max u2 u1} (I -> M) (I -> M) (I -> M) (instHMul.{max u2 u1} (I -> M) (Pi.instMul.{u2, u1} I (fun (i : I) => M) (fun (i : I) => MulOneClass.toMul.{u1} M (Monoid.toMulOneClass.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2))))) (Pi.mulSingle.{u2, u1} I (fun (k : I) => M) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => Monoid.toOne.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2)) k u) (Pi.mulSingle.{u2, u1} I (fun (i : I) => M) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => Monoid.toOne.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2)) l v)) (HMul.hMul.{max u2 u1, max u2 u1, max u2 u1} (I -> M) (I -> M) (I -> M) (instHMul.{max u2 u1} (I -> M) (Pi.instMul.{u2, u1} I (fun (i : I) => M) (fun (i : I) => MulOneClass.toMul.{u1} M (Monoid.toMulOneClass.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2))))) (Pi.mulSingle.{u2, u1} I (fun (i : I) => M) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => Monoid.toOne.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2)) m u) (Pi.mulSingle.{u2, u1} I (fun (i : I) => M) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => Monoid.toOne.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2)) n v))) (Or (And (Eq.{succ u2} I k m) (Eq.{succ u2} I l n)) (Or (And (Eq.{succ u1} M u v) (And (Eq.{succ u2} I k n) (Eq.{succ u2} I l m))) (And (Eq.{succ u1} M (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (MulOneClass.toMul.{u1} M (Monoid.toMulOneClass.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2)))) u v) (OfNat.ofNat.{u1} M 1 (One.toOfNat1.{u1} M (Monoid.toOne.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2))))) (And (Eq.{succ u2} I k l) (Eq.{succ u2} I m n))))))
Case conversion may be inaccurate. Consider using '#align pi.mul_single_mul_mul_single_eq_mul_single_mul_mul_single Pi.mulSingle_mul_mulSingle_eq_mulSingle_mul_mulSingleₓ'. -/
@[to_additive Pi.single_add_single_eq_single_add_single]
theorem Pi.mulSingle_mul_mulSingle_eq_mulSingle_mul_mulSingle {M : Type _} [CommMonoid M]
    {k l m n : I} {u v : M} (hu : u ≠ 1) (hv : v ≠ 1) :
    mulSingle k u * mulSingle l v = mulSingle m u * mulSingle n v ↔
      k = m ∧ l = n ∨ u = v ∧ k = n ∧ l = m ∨ u * v = 1 ∧ k = l ∧ m = n :=
  by
  refine' ⟨fun h => _, _⟩
  · have hk := congr_fun h k
    have hl := congr_fun h l
    have hm := (congr_fun h m).symm
    have hn := (congr_fun h n).symm
    simp only [mul_apply, mul_single_apply, if_pos rfl] at hk hl hm hn
    rcases eq_or_ne k m with (rfl | hkm)
    · refine' Or.inl ⟨rfl, not_ne_iff.mp fun hln => (hv _).elim⟩
      rcases eq_or_ne k l with (rfl | hkl)
      · rwa [if_neg hln.symm, if_neg hln.symm, one_mul, one_mul] at hn
      · rwa [if_neg hkl.symm, if_neg hln, one_mul, one_mul] at hl
    · rcases eq_or_ne m n with (rfl | hmn)
      · rcases eq_or_ne k l with (rfl | hkl)
        · rw [if_neg hkm.symm, if_neg hkm.symm, one_mul, if_pos rfl] at hm
          exact Or.inr (Or.inr ⟨hm, rfl, rfl⟩)
        · simpa only [if_neg hkm, if_neg hkl, mul_one] using hk
      · rw [if_neg hkm.symm, if_neg hmn, one_mul, mul_one] at hm
        obtain rfl := (ite_ne_right_iff.mp (ne_of_eq_of_ne hm.symm hu)).1
        rw [if_neg hkm, if_neg hkm, one_mul, mul_one] at hk
        obtain rfl := (ite_ne_right_iff.mp (ne_of_eq_of_ne hk.symm hu)).1
        exact Or.inr (Or.inl ⟨hk.trans (if_pos rfl), rfl, rfl⟩)
  · rintro (⟨rfl, rfl⟩ | ⟨rfl, rfl, rfl⟩ | ⟨h, rfl, rfl⟩)
    · rfl
    · apply mul_comm
    · simp_rw [← Pi.mulSingle_mul, h, mul_single_one]
#align pi.mul_single_mul_mul_single_eq_mul_single_mul_mul_single Pi.mulSingle_mul_mulSingle_eq_mulSingle_mul_mulSingle
#align pi.single_add_single_eq_single_add_single Pi.single_add_single_eq_single_add_single

end Single

namespace Function

#print Function.update_one /-
@[simp, to_additive]
theorem update_one [∀ i, One (f i)] [DecidableEq I] (i : I) : update (1 : ∀ i, f i) i 1 = 1 :=
  update_eq_self i 1
#align function.update_one Function.update_one
#align function.update_zero Function.update_zero
-/

#print Function.update_mul /-
@[to_additive]
theorem update_mul [∀ i, Mul (f i)] [DecidableEq I] (f₁ f₂ : ∀ i, f i) (i : I) (x₁ : f i)
    (x₂ : f i) : update (f₁ * f₂) i (x₁ * x₂) = update f₁ i x₁ * update f₂ i x₂ :=
  funext fun j => (apply_update₂ (fun i => (· * ·)) f₁ f₂ i x₁ x₂ j).symm
#align function.update_mul Function.update_mul
#align function.update_add Function.update_add
-/

#print Function.update_inv /-
@[to_additive]
theorem update_inv [∀ i, Inv (f i)] [DecidableEq I] (f₁ : ∀ i, f i) (i : I) (x₁ : f i) :
    update f₁⁻¹ i x₁⁻¹ = (update f₁ i x₁)⁻¹ :=
  funext fun j => (apply_update (fun i => Inv.inv) f₁ i x₁ j).symm
#align function.update_inv Function.update_inv
#align function.update_neg Function.update_neg
-/

#print Function.update_div /-
@[to_additive]
theorem update_div [∀ i, Div (f i)] [DecidableEq I] (f₁ f₂ : ∀ i, f i) (i : I) (x₁ : f i)
    (x₂ : f i) : update (f₁ / f₂) i (x₁ / x₂) = update f₁ i x₁ / update f₂ i x₂ :=
  funext fun j => (apply_update₂ (fun i => (· / ·)) f₁ f₂ i x₁ x₂ j).symm
#align function.update_div Function.update_div
#align function.update_sub Function.update_sub
-/

variable [One α] [Nonempty ι] {a : α}

/- warning: function.const_eq_one -> Function.const_eq_one is a dubious translation:
lean 3 declaration is
  forall {ι : Type.{u1}} {α : Type.{u2}} [_inst_1 : One.{u2} α] [_inst_2 : Nonempty.{succ u1} ι] {a : α}, Iff (Eq.{max (succ u1) (succ u2)} (ι -> α) (Function.const.{succ u2, succ u1} α ι a) (OfNat.ofNat.{max u1 u2} (ι -> α) 1 (OfNat.mk.{max u1 u2} (ι -> α) 1 (One.one.{max u1 u2} (ι -> α) (Pi.instOne.{u1, u2} ι (fun (ᾰ : ι) => α) (fun (i : ι) => _inst_1)))))) (Eq.{succ u2} α a (OfNat.ofNat.{u2} α 1 (OfNat.mk.{u2} α 1 (One.one.{u2} α _inst_1))))
but is expected to have type
  forall {ι : Type.{u2}} {α : Type.{u1}} [_inst_1 : One.{u1} α] [_inst_2 : Nonempty.{succ u2} ι] {a : α}, Iff (Eq.{max (succ u2) (succ u1)} (ι -> α) (Function.const.{succ u1, succ u2} α ι a) (OfNat.ofNat.{max u2 u1} (ι -> α) 1 (One.toOfNat1.{max u2 u1} (ι -> α) (Pi.instOne.{u2, u1} ι (fun (a._@.Init.Prelude._hyg.54 : ι) => α) (fun (i : ι) => _inst_1))))) (Eq.{succ u1} α a (OfNat.ofNat.{u1} α 1 (One.toOfNat1.{u1} α _inst_1)))
Case conversion may be inaccurate. Consider using '#align function.const_eq_one Function.const_eq_oneₓ'. -/
@[simp, to_additive]
theorem const_eq_one : const ι a = 1 ↔ a = 1 :=
  @const_inj _ _ _ _ 1
#align function.const_eq_one Function.const_eq_one
#align function.const_eq_zero Function.const_eq_zero

/- warning: function.const_ne_one -> Function.const_ne_one is a dubious translation:
lean 3 declaration is
  forall {ι : Type.{u1}} {α : Type.{u2}} [_inst_1 : One.{u2} α] [_inst_2 : Nonempty.{succ u1} ι] {a : α}, Iff (Ne.{max (succ u1) (succ u2)} (ι -> α) (Function.const.{succ u2, succ u1} α ι a) (OfNat.ofNat.{max u1 u2} (ι -> α) 1 (OfNat.mk.{max u1 u2} (ι -> α) 1 (One.one.{max u1 u2} (ι -> α) (Pi.instOne.{u1, u2} ι (fun (ᾰ : ι) => α) (fun (i : ι) => _inst_1)))))) (Ne.{succ u2} α a (OfNat.ofNat.{u2} α 1 (OfNat.mk.{u2} α 1 (One.one.{u2} α _inst_1))))
but is expected to have type
  forall {ι : Type.{u2}} {α : Type.{u1}} [_inst_1 : One.{u1} α] [_inst_2 : Nonempty.{succ u2} ι] {a : α}, Iff (Ne.{max (succ u2) (succ u1)} (ι -> α) (Function.const.{succ u1, succ u2} α ι a) (OfNat.ofNat.{max u2 u1} (ι -> α) 1 (One.toOfNat1.{max u2 u1} (ι -> α) (Pi.instOne.{u2, u1} ι (fun (a._@.Init.Prelude._hyg.54 : ι) => α) (fun (i : ι) => _inst_1))))) (Ne.{succ u1} α a (OfNat.ofNat.{u1} α 1 (One.toOfNat1.{u1} α _inst_1)))
Case conversion may be inaccurate. Consider using '#align function.const_ne_one Function.const_ne_oneₓ'. -/
@[to_additive]
theorem const_ne_one : const ι a ≠ 1 ↔ a ≠ 1 :=
  const_eq_one.Not
#align function.const_ne_one Function.const_ne_one
#align function.const_ne_zero Function.const_ne_zero

end Function

section Piecewise

#print Set.piecewise_mul /-
@[to_additive]
theorem Set.piecewise_mul [∀ i, Mul (f i)] (s : Set I) [∀ i, Decidable (i ∈ s)]
    (f₁ f₂ g₁ g₂ : ∀ i, f i) :
    s.piecewise (f₁ * f₂) (g₁ * g₂) = s.piecewise f₁ g₁ * s.piecewise f₂ g₂ :=
  s.piecewise_op₂ _ _ _ _ fun _ => (· * ·)
#align set.piecewise_mul Set.piecewise_mul
#align set.piecewise_add Set.piecewise_add
-/

#print Set.piecewise_inv /-
@[to_additive]
theorem Set.piecewise_inv [∀ i, Inv (f i)] (s : Set I) [∀ i, Decidable (i ∈ s)] (f₁ g₁ : ∀ i, f i) :
    s.piecewise f₁⁻¹ g₁⁻¹ = (s.piecewise f₁ g₁)⁻¹ :=
  s.piecewise_op f₁ g₁ fun _ x => x⁻¹
#align set.piecewise_inv Set.piecewise_inv
#align set.piecewise_neg Set.piecewise_neg
-/

#print Set.piecewise_div /-
@[to_additive]
theorem Set.piecewise_div [∀ i, Div (f i)] (s : Set I) [∀ i, Decidable (i ∈ s)]
    (f₁ f₂ g₁ g₂ : ∀ i, f i) :
    s.piecewise (f₁ / f₂) (g₁ / g₂) = s.piecewise f₁ g₁ / s.piecewise f₂ g₂ :=
  s.piecewise_op₂ _ _ _ _ fun _ => (· / ·)
#align set.piecewise_div Set.piecewise_div
#align set.piecewise_sub Set.piecewise_sub
-/

end Piecewise

section Extend

variable {η : Type v} (R : Type w) (s : ι → η)

#print Function.ExtendByOne.hom /-
/-- `function.extend s f 1` as a bundled hom. -/
@[to_additive Function.ExtendByZero.hom "`function.extend s f 0` as a bundled hom.", simps]
noncomputable def Function.ExtendByOne.hom [MulOneClass R] : (ι → R) →* η → R
    where
  toFun f := Function.extend s f 1
  map_one' := Function.extend_one s
  map_mul' f g := by simpa using Function.extend_mul s f g 1 1
#align function.extend_by_one.hom Function.ExtendByOne.hom
#align function.extend_by_zero.hom Function.ExtendByZero.hom
-/

end Extend


/-
Copyright (c) 2022 Jireh Loreaux. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jireh Loreaux
-/

import algebra.hom.non_unital_alg
import algebra.star.prod
import algebra.algebra.prod

/-!
# Morphisms of star algebras

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

This file defines morphisms between `R`-algebras (unital or non-unital) `A` and `B` where both
`A` and `B` are equipped with a `star` operation. These morphisms, namely `star_alg_hom` and
`non_unital_star_alg_hom` are direct extensions of their non-`star`red counterparts with a field
`map_star` which guarantees they preserve the star operation. We keep the type classes as generic
as possible, in keeping with the definition of `non_unital_alg_hom` in the non-unital case. In this
file, we only assume `has_star` unless we want to talk about the zero map as a
`non_unital_star_alg_hom`, in which case we need `star_add_monoid`. Note that the scalar ring `R`
is not required to have a star operation, nor do we need `star_ring` or `star_module` structures on
`A` and `B`.

As with `non_unital_alg_hom`, in the non-unital case the multiplications are not assumed to be
associative or unital, or even to be compatible with the scalar actions. In a typical application,
the operations will satisfy compatibility conditions making them into algebras (albeit possibly
non-associative and/or non-unital) but such conditions are not required here for the definitions.

The primary impetus for defining these types is that they constitute the morphisms in the categories
of unital C⋆-algebras (with `star_alg_hom`s) and of C⋆-algebras (with `non_unital_star_alg_hom`s).

TODO: add `star_alg_equiv`.

## Main definitions

  * `non_unital_alg_hom`
  * `star_alg_hom`

## Tags

non-unital, algebra, morphism, star
-/

set_option old_structure_cmd true

/-! ### Non-unital star algebra homomorphisms -/

/-- A *non-unital ⋆-algebra homomorphism* is a non-unital algebra homomorphism between
non-unital `R`-algebras `A` and `B` equipped with a `star` operation, and this homomorphism is
also `star`-preserving. -/
structure non_unital_star_alg_hom (R A B : Type*) [monoid R]
  [non_unital_non_assoc_semiring A] [distrib_mul_action R A] [has_star A]
  [non_unital_non_assoc_semiring B] [distrib_mul_action R B] [has_star B]
  extends A →ₙₐ[R] B :=
(map_star' : ∀ a : A, to_fun (star a) = star (to_fun a))

infixr ` →⋆ₙₐ `:25 := non_unital_star_alg_hom _
notation A ` →⋆ₙₐ[`:25 R `] ` B := non_unital_star_alg_hom R A B

/-- Reinterpret a non-unital star algebra homomorphism as a non-unital algebra homomorphism
by forgetting the interaction with the star operation. -/
add_decl_doc non_unital_star_alg_hom.to_non_unital_alg_hom

/-- `non_unital_star_alg_hom_class F R A B` asserts `F` is a type of bundled non-unital ⋆-algebra
homomorphisms from `A` to `B`. -/
class non_unital_star_alg_hom_class (F : Type*) (R : out_param Type*) (A : out_param Type*)
  (B : out_param Type*) [monoid R] [has_star A] [has_star B]
  [non_unital_non_assoc_semiring A] [non_unital_non_assoc_semiring B]
  [distrib_mul_action R A] [distrib_mul_action R B]
  extends non_unital_alg_hom_class F R A B, star_hom_class F A B

-- `R` becomes a metavariable but that's fine because it's an `out_param`
attribute [nolint dangerous_instance] non_unital_star_alg_hom_class.to_star_hom_class

namespace non_unital_star_alg_hom_class

variables {F R A B : Type*} [monoid R]
variables [non_unital_non_assoc_semiring A] [distrib_mul_action R A] [has_star A]
variables [non_unital_non_assoc_semiring B] [distrib_mul_action R B] [has_star B]

instance [non_unital_star_alg_hom_class F R A B] : has_coe_t F (A →⋆ₙₐ[R] B) :=
{ coe := λ f,
  { to_fun := f,
    map_star' := map_star f,
    .. (f : A →ₙₐ[R] B) }}

end non_unital_star_alg_hom_class

namespace non_unital_star_alg_hom

section basic

variables {R A B C D : Type*} [monoid R]
variables [non_unital_non_assoc_semiring A] [distrib_mul_action R A] [has_star A]
variables [non_unital_non_assoc_semiring B] [distrib_mul_action R B] [has_star B]
variables [non_unital_non_assoc_semiring C] [distrib_mul_action R C] [has_star C]
variables [non_unital_non_assoc_semiring D] [distrib_mul_action R D] [has_star D]

instance : non_unital_star_alg_hom_class (A →⋆ₙₐ[R] B) R A B :=
{ coe := to_fun,
  coe_injective' := by rintro ⟨f, _⟩ ⟨g, _⟩ ⟨h⟩; congr,
  map_smul := λ f, f.map_smul',
  map_add := λ f, f.map_add',
  map_zero := λ f, f.map_zero',
  map_mul := λ f, f.map_mul',
  map_star := λ f, f.map_star' }

/-- Helper instance for when there's too many metavariables to apply `fun_like.has_coe_to_fun`
directly. -/
instance : has_coe_to_fun (A →⋆ₙₐ[R] B) (λ _, A → B) := fun_like.has_coe_to_fun

initialize_simps_projections non_unital_star_alg_hom (to_fun → apply)

@[simp, protected]

@[simp] lemma coe_to_non_unital_alg_hom {f : A →⋆ₙₐ[R] B} :
  (f.to_non_unital_alg_hom : A → B) = f := rfl

@[ext] lemma ext {f g : A →⋆ₙₐ[R] B} (h : ∀ x, f x = g x) : f = g := fun_like.ext _ _ h

/-- Copy of a `non_unital_star_alg_hom` with a new `to_fun` equal to the old one. Useful
to fix definitional equalities. -/
protected def copy (f : A →⋆ₙₐ[R] B) (f' : A → B) (h : f' = f) : A →⋆ₙₐ[R] B :=
{ to_fun := f',
  map_smul' := h.symm ▸ map_smul f,
  map_zero' := h.symm ▸ map_zero f,
  map_add' := h.symm ▸ map_add f,
  map_mul' := h.symm ▸ map_mul f,
  map_star' := h.symm ▸ map_star f }

@[simp] lemma coe_copy (f : A →⋆ₙₐ[R] B) (f' : A → B) (h : f' = f) : ⇑(f.copy f' h) = f' := rfl
lemma copy_eq (f : A →⋆ₙₐ[R] B) (f' : A → B) (h : f' = f) : f.copy f' h = f := fun_like.ext' h

@[simp] lemma coe_mk (f : A → B) (h₁ h₂ h₃ h₄ h₅) :
  ((⟨f, h₁, h₂, h₃, h₄, h₅⟩ : A →⋆ₙₐ[R] B) : A → B) = f :=
rfl

@[simp] lemma mk_coe (f : A →⋆ₙₐ[R] B) (h₁ h₂ h₃ h₄ h₅) :
  (⟨f, h₁, h₂, h₃, h₄, h₅⟩ : A →⋆ₙₐ[R] B) = f :=
by { ext, refl, }

section
variables (R A)
/-- The identity as a non-unital ⋆-algebra homomorphism. -/
protected def id : A →⋆ₙₐ[R] A :=
{ map_star' := λ x, rfl, .. (1 : A →ₙₐ[R] A) }

@[simp] lemma coe_id : ⇑(non_unital_star_alg_hom.id R A) = id := rfl
end

/-- The composition of non-unital ⋆-algebra homomorphisms, as a non-unital ⋆-algebra
homomorphism. -/
def comp (f : B →⋆ₙₐ[R] C) (g : A →⋆ₙₐ[R] B) : A →⋆ₙₐ[R] C :=
{ map_star' := by simp only [map_star, non_unital_alg_hom.to_fun_eq_coe, eq_self_iff_true,
    non_unital_alg_hom.coe_comp, coe_to_non_unital_alg_hom, function.comp_app, forall_const],
  .. f.to_non_unital_alg_hom.comp g.to_non_unital_alg_hom }

@[simp] lemma coe_comp (f : B →⋆ₙₐ[R] C) (g : A →⋆ₙₐ[R] B) : ⇑(comp f g) = f ∘ g := rfl

@[simp] lemma comp_apply (f : B →⋆ₙₐ[R] C) (g : A →⋆ₙₐ[R] B) (a : A) : comp f g a = f (g a) := rfl

@[simp] lemma comp_assoc (f : C →⋆ₙₐ[R] D) (g : B →⋆ₙₐ[R] C) (h : A →⋆ₙₐ[R] B) :
  (f.comp g).comp h = f.comp (g.comp h) := rfl

@[simp] lemma id_comp (f : A →⋆ₙₐ[R] B) : (non_unital_star_alg_hom.id _ _).comp f = f :=
ext $ λ _, rfl

@[simp] lemma comp_id (f : A →⋆ₙₐ[R] B) : f.comp (non_unital_star_alg_hom.id _ _) = f :=
ext $ λ _, rfl

instance : monoid (A →⋆ₙₐ[R] A) :=
{ mul := comp,
  mul_assoc := comp_assoc,
  one := non_unital_star_alg_hom.id R A,
  one_mul := id_comp,
  mul_one := comp_id, }

@[simp] lemma coe_one : ((1 : A →⋆ₙₐ[R] A) : A → A) = id := rfl
lemma one_apply (a : A) : (1 : A →⋆ₙₐ[R] A) a = a := rfl

end basic

section zero
-- the `zero` requires extra type class assumptions because we need `star_zero`
variables {R A B C D : Type*} [monoid R]
variables [non_unital_non_assoc_semiring A] [distrib_mul_action R A] [star_add_monoid A]
variables [non_unital_non_assoc_semiring B] [distrib_mul_action R B] [star_add_monoid B]

instance : has_zero (A →⋆ₙₐ[R] B) :=
⟨{ map_star' := by simp, .. (0 : non_unital_alg_hom R A B) }⟩

instance : inhabited (A →⋆ₙₐ[R] B) := ⟨0⟩

instance : monoid_with_zero (A →⋆ₙₐ[R] A) :=
{ zero_mul := λ f, ext $ λ x, rfl,
  mul_zero := λ f, ext $ λ x, map_zero f,
  .. non_unital_star_alg_hom.monoid,
  .. non_unital_star_alg_hom.has_zero }

@[simp] lemma coe_zero : ((0 : A →⋆ₙₐ[R] B) : A → B) = 0 := rfl
lemma zero_apply (a : A) : (0 : A →⋆ₙₐ[R] B) a = 0 := rfl

end zero

end non_unital_star_alg_hom

/-! ### Unital star algebra homomorphisms -/

section unital

/-- A *⋆-algebra homomorphism* is an algebra homomorphism between `R`-algebras `A` and `B`
equipped with a `star` operation, and this homomorphism is also `star`-preserving. -/
structure star_alg_hom (R A B: Type*) [comm_semiring R] [semiring A] [algebra R A] [has_star A]
  [semiring B] [algebra R B] [has_star B] extends alg_hom R A B :=
(map_star' : ∀ x : A, to_fun (star x) = star (to_fun x))

infixr ` →⋆ₐ `:25 := star_alg_hom _
notation A ` →⋆ₐ[`:25 R `] ` B := star_alg_hom R A B

/-- Reinterpret a unital star algebra homomorphism as a unital algebra homomorphism
by forgetting the interaction with the star operation. -/
add_decl_doc star_alg_hom.to_alg_hom

/-- `star_alg_hom_class F R A B` states that `F` is a type of ⋆-algebra homomorphisms.

You should also extend this typeclass when you extend `star_alg_hom`. -/
class star_alg_hom_class (F : Type*) (R : out_param Type*) (A : out_param Type*)
  (B : out_param Type*) [comm_semiring R] [semiring A] [algebra R A] [has_star A]
  [semiring B] [algebra R B] [has_star B] extends alg_hom_class F R A B, star_hom_class F A B

-- `R` becomes a metavariable but that's fine because it's an `out_param`
attribute [nolint dangerous_instance] star_alg_hom_class.to_star_hom_class

namespace star_alg_hom_class

variables (F R A B : Type*) [comm_semiring R] [semiring A] [algebra R A] [has_star A]
variables [semiring B] [algebra R B] [has_star B] [hF : star_alg_hom_class F R A B]
include hF

@[priority 100] /- See note [lower instance priority] -/
instance to_non_unital_star_alg_hom_class : non_unital_star_alg_hom_class F R A B :=
{ map_smul := map_smul,
  .. star_alg_hom_class.to_alg_hom_class F R A B,
  .. star_alg_hom_class.to_star_hom_class F R A B, }

instance : has_coe_t F (A →⋆ₐ[R] B) :=
{ coe := λ f,
  { to_fun := f,
    map_star' := map_star f,
    ..(f : A →ₐ[R] B) } }

end star_alg_hom_class

namespace star_alg_hom

variables {F R A B C D : Type*} [comm_semiring R]
  [semiring A] [algebra R A] [has_star A]
  [semiring B] [algebra R B] [has_star B]
  [semiring C] [algebra R C] [has_star C]
  [semiring D] [algebra R D] [has_star D]

instance : star_alg_hom_class (A →⋆ₐ[R] B) R A B :=
{ coe :=  λ f, f.to_fun,
  coe_injective' := λ f g h,
  begin
    obtain ⟨_, _, _, _, _, _, _⟩ := f;
    obtain ⟨_, _, _, _, _, _, _⟩ := g;
    congr'
  end,
  map_mul := map_mul',
  map_one := map_one',
  map_add := map_add',
  map_zero := map_zero',
  commutes := commutes',
  map_star := map_star' }

/-- Helper instance for when there's too many metavariables to apply `fun_like.has_coe_to_fun`
directly. -/
instance : has_coe_to_fun (A →⋆ₐ[R] B) (λ _, A → B) := fun_like.has_coe_to_fun

@[simp, protected] lemma coe_coe {F : Type*} [star_alg_hom_class F R A B] (f : F) :
  ⇑(f : A →⋆ₐ[R] B) = f := rfl

initialize_simps_projections star_alg_hom (to_fun → apply)

@[simp] lemma coe_to_alg_hom {f : A →⋆ₐ[R] B} :
  (f.to_alg_hom : A → B) = f := rfl

@[ext] lemma ext {f g : A →⋆ₐ[R] B} (h : ∀ x, f x = g x) : f = g := fun_like.ext _ _ h

/-- Copy of a `star_alg_hom` with a new `to_fun` equal to the old one. Useful
to fix definitional equalities. -/
protected def copy (f : A →⋆ₐ[R] B) (f' : A → B) (h : f' = f) : A →⋆ₐ[R] B :=
{ to_fun := f',
  map_one' := h.symm ▸ map_one f ,
  map_mul' := h.symm ▸ map_mul f,
  map_zero' := h.symm ▸ map_zero f,
  map_add' := h.symm ▸ map_add f,
  commutes' := h.symm ▸ alg_hom_class.commutes f,
  map_star' := h.symm ▸ map_star f }

@[simp] lemma coe_copy (f : A →⋆ₐ[R] B) (f' : A → B) (h : f' = f) : ⇑(f.copy f' h) = f' := rfl
lemma copy_eq (f : A →⋆ₐ[R] B) (f' : A → B) (h : f' = f) : f.copy f' h = f := fun_like.ext' h

@[simp] lemma coe_mk (f : A → B) (h₁ h₂ h₃ h₄ h₅ h₆) :
  ((⟨f, h₁, h₂, h₃, h₄, h₅, h₆⟩ : A →⋆ₐ[R] B) : A → B) = f :=
rfl

@[simp] lemma mk_coe (f : A →⋆ₐ[R] B) (h₁ h₂ h₃ h₄ h₅ h₆) :
  (⟨f, h₁, h₂, h₃, h₄, h₅, h₆⟩ : A →⋆ₐ[R] B) = f :=
by { ext, refl, }

section
variables (R A)
/-- The identity as a `star_alg_hom`. -/
protected def id : A →⋆ₐ[R] A := { map_star' := λ x, rfl, .. alg_hom.id _ _ }
@[simp] lemma coe_id : ⇑(star_alg_hom.id R A) = id := rfl
end

instance : inhabited (A →⋆ₐ[R] A) := ⟨star_alg_hom.id R A⟩

/-- The composition of ⋆-algebra homomorphisms, as a ⋆-algebra homomorphism. -/
def comp (f : B →⋆ₐ[R] C) (g : A →⋆ₐ[R] B) : A →⋆ₐ[R] C :=
{ map_star' := by simp only [map_star, alg_hom.to_fun_eq_coe, alg_hom.coe_comp, coe_to_alg_hom,
    function.comp_app, eq_self_iff_true, forall_const],
  .. f.to_alg_hom.comp g.to_alg_hom }

@[simp] lemma coe_comp (f : B →⋆ₐ[R] C) (g : A →⋆ₐ[R] B) : ⇑(comp f g) = f ∘ g := rfl

@[simp] lemma comp_apply (f : B →⋆ₐ[R] C) (g : A →⋆ₐ[R] B) (a : A) : comp f g a = f (g a) := rfl

@[simp] lemma comp_assoc (f : C →⋆ₐ[R] D) (g : B →⋆ₐ[R] C) (h : A →⋆ₐ[R] B) :
  (f.comp g).comp h = f.comp (g.comp h) := rfl

@[simp] lemma id_comp (f : A →⋆ₐ[R] B) : (star_alg_hom.id _ _).comp f = f := ext $ λ _, rfl

@[simp] lemma comp_id (f : A →⋆ₐ[R] B) : f.comp (star_alg_hom.id _ _) = f := ext $ λ _, rfl

instance : monoid (A →⋆ₐ[R] A) :=
{ mul := comp,
  mul_assoc := comp_assoc,
  one := star_alg_hom.id R A,
  one_mul := id_comp,
  mul_one := comp_id }

/-- A unital morphism of ⋆-algebras is a `non_unital_star_alg_hom`. -/
def to_non_unital_star_alg_hom (f : A →⋆ₐ[R] B) : A →⋆ₙₐ[R] B :=
{ map_smul' := map_smul f, .. f, }

@[simp] lemma coe_to_non_unital_star_alg_hom (f : A →⋆ₐ[R] B) :
  (f.to_non_unital_star_alg_hom : A → B) = f :=
rfl

end star_alg_hom

end unital

/-! ### Operations on the product type

Note that this is copied from [`algebra/hom/non_unital_alg`](non_unital_alg). -/

namespace non_unital_star_alg_hom

section prod

variables (R A B C : Type*) [monoid R]
  [non_unital_non_assoc_semiring A] [distrib_mul_action R A] [has_star A]
  [non_unital_non_assoc_semiring B] [distrib_mul_action R B] [has_star B]
  [non_unital_non_assoc_semiring C] [distrib_mul_action R C] [has_star C]

/-- The first projection of a product is a non-unital ⋆-algebra homomoprhism. -/
@[simps]
def fst : A × B →⋆ₙₐ[R] A :=
{ map_star' := λ x, rfl, .. non_unital_alg_hom.fst R A B }

/-- The second projection of a product is a non-unital ⋆-algebra homomorphism. -/
@[simps]
def snd : A × B →⋆ₙₐ[R] B :=
{ map_star' := λ x, rfl, .. non_unital_alg_hom.snd R A B }

variables {R A B C}

/-- The `pi.prod` of two morphisms is a morphism. -/
@[simps] def prod (f : A →⋆ₙₐ[R] B) (g : A →⋆ₙₐ[R] C) : (A →⋆ₙₐ[R] B × C) :=
{ map_star' := λ x, by simp [map_star, prod.star_def],
  .. f.to_non_unital_alg_hom.prod g.to_non_unital_alg_hom }

lemma coe_prod (f : A →⋆ₙₐ[R] B) (g : A →⋆ₙₐ[R] C) : ⇑(f.prod g) = pi.prod f g := rfl

@[simp] theorem fst_prod (f : A →⋆ₙₐ[R] B) (g : A →⋆ₙₐ[R] C) :
  (fst R B C).comp (prod f g) = f := by ext; refl

@[simp] theorem snd_prod (f : A →⋆ₙₐ[R] B) (g : A →⋆ₙₐ[R] C) :
  (snd R B C).comp (prod f g) = g := by ext; refl

@[simp] theorem prod_fst_snd : prod (fst R A B) (snd R A B) = 1 :=
fun_like.coe_injective pi.prod_fst_snd

/-- Taking the product of two maps with the same domain is equivalent to taking the product of
their codomains. -/
@[simps] def prod_equiv : ((A →⋆ₙₐ[R] B) × (A →⋆ₙₐ[R] C)) ≃ (A →⋆ₙₐ[R] B × C) :=
{ to_fun := λ f, f.1.prod f.2,
  inv_fun := λ f, ((fst _ _ _).comp f, (snd _ _ _).comp f),
  left_inv := λ f, by ext; refl,
  right_inv := λ f, by ext; refl }

end prod

section inl_inr

variables (R A B C : Type*) [monoid R]
  [non_unital_non_assoc_semiring A] [distrib_mul_action R A] [star_add_monoid A]
  [non_unital_non_assoc_semiring B] [distrib_mul_action R B] [star_add_monoid B]
  [non_unital_non_assoc_semiring C] [distrib_mul_action R C] [star_add_monoid C]

/-- The left injection into a product is a non-unital algebra homomorphism. -/
def inl : A →⋆ₙₐ[R] A × B := prod 1 0

/-- The right injection into a product is a non-unital algebra homomorphism. -/
def inr : B →⋆ₙₐ[R] A × B := prod 0 1

variables {R A B}

@[simp] theorem coe_inl : (inl R A B : A → A × B) = λ x, (x, 0) := rfl
theorem inl_apply (x : A) : inl R A B x = (x, 0) := rfl

@[simp] theorem coe_inr : (inr R A B : B → A × B) = prod.mk 0 := rfl
theorem inr_apply (x : B) : inr R A B x = (0, x) := rfl

end inl_inr

end non_unital_star_alg_hom

namespace star_alg_hom

variables (R A B C : Type*) [comm_semiring R]
  [semiring A] [algebra R A] [has_star A]
  [semiring B] [algebra R B] [has_star B]
  [semiring C] [algebra R C] [has_star C]

/-- The first projection of a product is a ⋆-algebra homomoprhism. -/
@[simps]
def fst : A × B →⋆ₐ[R] A :=
{ map_star' := λ x, rfl, .. alg_hom.fst R A B }

/-- The second projection of a product is a ⋆-algebra homomorphism. -/
@[simps]
def snd : A × B →⋆ₐ[R] B :=
{ map_star' := λ x, rfl, .. alg_hom.snd R A B }

variables {R A B C}

/-- The `pi.prod` of two morphisms is a morphism. -/
@[simps] def prod (f : A →⋆ₐ[R] B) (g : A →⋆ₐ[R] C) : (A →⋆ₐ[R] B × C) :=
{ map_star' := λ x, by simp [prod.star_def, map_star],
 .. f.to_alg_hom.prod g.to_alg_hom }

lemma coe_prod (f : A →⋆ₐ[R] B) (g : A →⋆ₐ[R] C) : ⇑(f.prod g) = pi.prod f g := rfl

@[simp] theorem fst_prod (f : A →⋆ₐ[R] B) (g : A →⋆ₐ[R] C) :
  (fst R B C).comp (prod f g) = f := by ext; refl

@[simp] theorem snd_prod (f : A →⋆ₐ[R] B) (g : A →⋆ₐ[R] C) :
  (snd R B C).comp (prod f g) = g := by ext; refl

@[simp] theorem prod_fst_snd : prod (fst R A B) (snd R A B) = 1 :=
fun_like.coe_injective pi.prod_fst_snd

/-- Taking the product of two maps with the same domain is equivalent to taking the product of
their codomains. -/
@[simps] def prod_equiv : ((A →⋆ₐ[R] B) × (A →⋆ₐ[R] C)) ≃ (A →⋆ₐ[R] B × C) :=
{ to_fun := λ f, f.1.prod f.2,
  inv_fun := λ f, ((fst _ _ _).comp f, (snd _ _ _).comp f),
  left_inv := λ f, by ext; refl,
  right_inv := λ f, by ext; refl }

end star_alg_hom

/-! ### Star algebra equivalences -/

/-- A *⋆-algebra* equivalence is an equivalence preserving addition, multiplication, scalar
multiplication and the star operation, which allows for considering both unital and non-unital
equivalences with a single structure. Currently, `alg_equiv` requires unital algebras, which is
why this structure does not extend it. -/
structure star_alg_equiv (R A B : Type*) [has_add A] [has_mul A] [has_smul R A] [has_star A]
  [has_add B] [has_mul B] [has_smul R B] [has_star B] extends A ≃+* B :=
(map_star' : ∀ a : A, to_fun (star a) = star (to_fun a))
(map_smul' : ∀ (r : R) (a : A), to_fun (r • a) = r • to_fun a)

infixr ` ≃⋆ₐ `:25 := star_alg_equiv _
notation A ` ≃⋆ₐ[`:25 R `] ` B := star_alg_equiv R A B

/-- Reinterpret a star algebra equivalence as a `ring_equiv` by forgetting the interaction with
the star operation and scalar multiplication. -/
add_decl_doc star_alg_equiv.to_ring_equiv

/-- `star_alg_equiv_class F R A B` asserts `F` is a type of bundled ⋆-algebra equivalences between
`A` and `B`.

You should also extend this typeclass when you extend `star_alg_equiv`. -/
class star_alg_equiv_class (F : Type*) (R : out_param Type*) (A : out_param Type*)
  (B : out_param Type*) [has_add A] [has_mul A] [has_smul R A] [has_star A] [has_add B] [has_mul B]
  [has_smul R B] [has_star B] extends ring_equiv_class F A B :=
(map_star : ∀ (f : F) (a : A), f (star a) = star (f a))
(map_smul : ∀ (f : F) (r : R) (a : A), f (r • a) = r • f a)

-- `R` becomes a metavariable but that's fine because it's an `out_param`
attribute [nolint dangerous_instance] star_alg_equiv_class.to_ring_equiv_class

namespace star_alg_equiv_class

@[priority 50] -- See note [lower instance priority]
instance {F R A B : Type*} [has_add A] [has_mul A] [has_smul R A] [has_star A] [has_add B]
  [has_mul B] [has_smul R B] [has_star B] [hF : star_alg_equiv_class F R A B] :
  star_hom_class F A B :=
{ coe := λ f, f,
  coe_injective' := fun_like.coe_injective,
  .. hF }

-- `R` becomes a metavariable but that's fine because it's an `out_param`
attribute [nolint dangerous_instance] star_alg_equiv_class.star_hom_class

@[priority 50] -- See note [lower instance priority]
instance {F R A B : Type*} [has_add A] [has_mul A] [has_star A] [has_smul R A] [has_add B]
  [has_mul B] [has_smul R B] [has_star B] [hF : star_alg_equiv_class F R A B] :
  smul_hom_class F R A B :=
{ coe := λ f, f,
  coe_injective' := fun_like.coe_injective,
  .. hF }

-- `R` becomes a metavariable but that's fine because it's an `out_param`
attribute [nolint dangerous_instance] star_alg_equiv_class.smul_hom_class

@[priority 100] -- See note [lower instance priority]
instance {F R A B : Type*} [monoid R] [non_unital_non_assoc_semiring A] [distrib_mul_action R A]
  [has_star A] [non_unital_non_assoc_semiring B] [distrib_mul_action R B] [has_star B]
  [hF : star_alg_equiv_class F R A B] : non_unital_star_alg_hom_class F R A B :=
{ coe := λ f, f,
  coe_injective' := fun_like.coe_injective,
  map_zero := map_zero,
  .. hF }

@[priority 100] -- See note [lower instance priority]
instance (F R A B : Type*) [comm_semiring R] [semiring A] [algebra R A] [has_star A]
  [semiring B] [algebra R B] [has_star B] [hF : star_alg_equiv_class F R A B] :
  star_alg_hom_class F R A B :=
{ coe := λ f, f,
  coe_injective' := fun_like.coe_injective,
  map_one := map_one,
  map_zero := map_zero,
  commutes := λ f r, by simp only [algebra.algebra_map_eq_smul_one, map_smul, map_one],
  .. hF}

end star_alg_equiv_class

namespace star_alg_equiv

section basic

variables {F R A B C : Type*}
  [has_add A] [has_mul A] [has_smul R A] [has_star A]
  [has_add B] [has_mul B] [has_smul R B] [has_star B]
  [has_add C] [has_mul C] [has_smul R C] [has_star C]

instance : star_alg_equiv_class (A ≃⋆ₐ[R] B) R A B :=
{ coe := to_fun,
  inv := inv_fun,
  left_inv := left_inv,
  right_inv := right_inv,
  coe_injective' := λ f g h₁ h₂, by { cases f, cases g, congr' },
  map_mul := map_mul',
  map_add := map_add',
  map_star := map_star',
  map_smul := map_smul' }

/--  Helper instance for when there's too many metavariables to apply
`fun_like.has_coe_to_fun` directly. -/
instance : has_coe_to_fun (A ≃⋆ₐ[R] B) (λ _, A → B) := ⟨star_alg_equiv.to_fun⟩

@[ext]
lemma ext {f g : A ≃⋆ₐ[R] B} (h : ∀ a, f a = g a) : f = g := fun_like.ext f g h

lemma ext_iff {f g : A ≃⋆ₐ[R] B} : f = g ↔ ∀ a, f a = g a  := fun_like.ext_iff

/-- Star algebra equivalences are reflexive. -/
@[refl] def refl : A ≃⋆ₐ[R] A :=
{ map_smul' := λ r a, rfl, map_star' := λ a, rfl, ..ring_equiv.refl A }

instance : inhabited (A ≃⋆ₐ[R] A) := ⟨refl⟩

@[simp] lemma coe_refl : ⇑(refl : A ≃⋆ₐ[R] A) = id := rfl

/-- Star algebra equivalences are symmetric. -/
@[symm]
def symm (e : A ≃⋆ₐ[R] B) : B ≃⋆ₐ[R] A :=
{ map_star' := λ b, by simpa only [e.left_inv (star (e.inv_fun b)), e.right_inv b]
    using congr_arg e.inv_fun (e.map_star' (e.inv_fun b)).symm,
  map_smul' := λ r b, by simpa only [e.left_inv (r • e.inv_fun b), e.right_inv b]
    using congr_arg e.inv_fun (e.map_smul' r (e.inv_fun b)).symm,
  ..e.to_ring_equiv.symm, }

/-- See Note [custom simps projection] -/
def simps.symm_apply (e : A ≃⋆ₐ[R] B) : B → A := e.symm

initialize_simps_projections star_alg_equiv (to_fun → apply, inv_fun → simps.symm_apply)

@[simp] lemma inv_fun_eq_symm {e : A ≃⋆ₐ[R] B} : e.inv_fun = e.symm := rfl

@[simp] lemma symm_symm (e : A ≃⋆ₐ[R] B) : e.symm.symm = e :=
by { ext, refl, }

lemma symm_bijective : function.bijective (symm : (A ≃⋆ₐ[R] B) → (B ≃⋆ₐ[R] A)) :=
equiv.bijective ⟨symm, symm, symm_symm, symm_symm⟩

@[simp] lemma mk_coe' (e : A ≃⋆ₐ[R] B) (f h₁ h₂ h₃ h₄ h₅ h₆) :
  (⟨f, e, h₁, h₂, h₃, h₄, h₅, h₆⟩ : B ≃⋆ₐ[R] A) = e.symm :=
symm_bijective.injective $ ext $ λ x, rfl

@[simp] lemma symm_mk (f f') (h₁ h₂ h₃ h₄ h₅ h₆) :
  (⟨f, f', h₁, h₂, h₃, h₄, h₅, h₆⟩ : A ≃⋆ₐ[R] B).symm =
  { to_fun := f', inv_fun := f,
    ..(⟨f, f', h₁, h₂, h₃, h₄, h₅, h₆⟩ : A ≃⋆ₐ[R] B).symm } := rfl

@[simp] lemma refl_symm : (star_alg_equiv.refl : A ≃⋆ₐ[R] A).symm = star_alg_equiv.refl := rfl

-- should be a `simp` lemma, but causes a linter timeout
lemma to_ring_equiv_symm (f : A ≃⋆ₐ[R] B) : (f : A ≃+* B).symm = f.symm := rfl

@[simp] lemma symm_to_ring_equiv (e : A ≃⋆ₐ[R] B) : (e.symm : B ≃+* A) = (e : A ≃+* B).symm := rfl

/-- Star algebra equivalences are transitive. -/
@[trans]
def trans (e₁ : A ≃⋆ₐ[R] B) (e₂ : B ≃⋆ₐ[R] C) : A ≃⋆ₐ[R] C :=
{ map_smul' := λ r a, show e₂.to_fun (e₁.to_fun (r • a)) = r • e₂.to_fun (e₁.to_fun a),
    by rw [e₁.map_smul', e₂.map_smul'],
  map_star' := λ a, show e₂.to_fun (e₁.to_fun (star a)) = star (e₂.to_fun (e₁.to_fun a)),
    by rw [e₁.map_star', e₂.map_star'],
  ..(e₁.to_ring_equiv.trans e₂.to_ring_equiv), }

@[simp] lemma apply_symm_apply (e : A ≃⋆ₐ[R] B) : ∀ x, e (e.symm x) = x :=
  e.to_ring_equiv.apply_symm_apply

@[simp] lemma symm_apply_apply (e : A ≃⋆ₐ[R] B) : ∀ x, e.symm (e x) = x :=
  e.to_ring_equiv.symm_apply_apply

@[simp] lemma symm_trans_apply (e₁ : A ≃⋆ₐ[R] B) (e₂ : B ≃⋆ₐ[R] C) (x : C) :
  (e₁.trans e₂).symm x = e₁.symm (e₂.symm x) := rfl

@[simp] lemma coe_trans (e₁ : A ≃⋆ₐ[R] B) (e₂ : B ≃⋆ₐ[R] C) :
  ⇑(e₁.trans e₂) = e₂ ∘ e₁ := rfl

@[simp] lemma trans_apply (e₁ : A ≃⋆ₐ[R] B) (e₂ : B ≃⋆ₐ[R] C) (x : A) :
  (e₁.trans e₂) x = e₂ (e₁ x) := rfl

theorem left_inverse_symm (e : A ≃⋆ₐ[R] B) : function.left_inverse e.symm e := e.left_inv

theorem right_inverse_symm (e : A ≃⋆ₐ[R] B) : function.right_inverse e.symm e := e.right_inv

end basic

section bijective

variables {F G R A B : Type*} [monoid R]
variables [non_unital_non_assoc_semiring A] [distrib_mul_action R A] [has_star A]
variables [non_unital_non_assoc_semiring B] [distrib_mul_action R B] [has_star B]
variables [hF : non_unital_star_alg_hom_class F R A B] [non_unital_star_alg_hom_class G R B A]
include hF

/-- If a (unital or non-unital) star algebra morphism has an inverse, it is an isomorphism of
star algebras. -/
@[simps] def of_star_alg_hom (f : F) (g : G) (h₁ : ∀ x, g (f x) = x) (h₂ : ∀ x, f (g x) = x) :
  A ≃⋆ₐ[R] B :=
{ to_fun    := f,
  inv_fun   := g,
  left_inv  := h₁,
  right_inv := h₂,
  map_add' := map_add f,
  map_mul' := map_mul f,
  map_smul' := map_smul f,
  map_star' := map_star f }

/-- Promote a bijective star algebra homomorphism to a star algebra equivalence. -/
noncomputable def of_bijective (f : F) (hf : function.bijective f) : A ≃⋆ₐ[R] B :=
{ to_fun := f,
  map_star' := map_star f,
  map_smul' := map_smul f,
  .. ring_equiv.of_bijective f (hf : function.bijective (f : A → B)), }

@[simp] lemma coe_of_bijective {f : F} (hf : function.bijective f) :
  (star_alg_equiv.of_bijective f hf : A → B) = f := rfl

lemma of_bijective_apply {f : F} (hf : function.bijective f) (a : A) :
  (star_alg_equiv.of_bijective f hf) a = f a := rfl

end bijective

end star_alg_equiv


/-
Copyright (c) 2022 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies

! This file was ported from Lean 3 source module category_theory.category.PartialFun
! leanprover-community/mathlib commit 14b69e9f3c16630440a2cbd46f1ddad0d561dee7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.Category.Pointed
import Mathbin.Data.Pfun

/-!
# The category of types with partial functions

This defines `PartialFun`, the category of types equipped with partial functions.

This category is classically equivalent to the category of pointed types. The reason it doesn't hold
constructively stems from the difference between `part` and `option`. Both can model partial
functions, but the latter forces a decidable domain.

Precisely, `PartialFun_to_Pointed` turns a partial function `α →. β` into a function
`option α → option β` by sending to `none` the undefined values (and `none` to `none`). But being
defined is (generally) undecidable while being sent to `none` is decidable. So it can't be
constructive.

## References

* [nLab, *The category of sets and partial functions*]
  (https://ncatlab.org/nlab/show/partial+function)
-/

open CategoryTheory Option

universe u

variable {α β : Type _}

/-- The category of types equipped with partial functions. -/
def PartialFun : Type _ :=
  Type _
#align PartialFun PartialFun

namespace PartialFun

instance : CoeSort PartialFun (Type _) :=
  ⟨id⟩

/-- Turns a type into a `PartialFun`. -/
@[nolint has_nonempty_instance]
def of : Type _ → PartialFun :=
  id
#align PartialFun.of PartialFun.of

@[simp]
theorem coe_of (X : Type _) : ↥(of X) = X :=
  rfl
#align PartialFun.coe_of PartialFun.coe_of

instance : Inhabited PartialFun :=
  ⟨Type _⟩

instance largeCategory : LargeCategory.{u} PartialFun
    where
  Hom := PFun
  id := PFun.id
  comp X Y Z f g := g.comp f
  id_comp' := @PFun.comp_id
  comp_id' := @PFun.id_comp
  assoc' W X Y Z _ _ _ := (PFun.comp_assoc _ _ _).symm
#align PartialFun.large_category PartialFun.largeCategory

/-- Constructs a partial function isomorphism between types from an equivalence between them. -/
@[simps]
def Iso.mk {α β : PartialFun.{u}} (e : α ≃ β) : α ≅ β
    where
  Hom := e
  inv := e.symm
  hom_inv_id' := (PFun.coe_comp _ _).symm.trans <| congr_arg coe e.symm_comp_self
  inv_hom_id' := (PFun.coe_comp _ _).symm.trans <| congr_arg coe e.self_comp_symm
#align PartialFun.iso.mk PartialFun.Iso.mk

end PartialFun

/-- The forgetful functor from `Type` to `PartialFun` which forgets that the maps are total. -/
def typeToPartialFun : Type u ⥤ PartialFun
    where
  obj := id
  map := @PFun.lift
  map_comp' _ _ _ _ _ := PFun.coe_comp _ _
#align Type_to_PartialFun typeToPartialFun

instance : Faithful typeToPartialFun :=
  ⟨fun X Y => PFun.lift_injective⟩

/-- The functor which deletes the point of a pointed type. In return, this makes the maps partial.
This the computable part of the equivalence `PartialFun_equiv_Pointed`. -/
@[simps map]
def pointedToPartialFun : Pointed.{u} ⥤ PartialFun
    where
  obj X := { x : X // x ≠ X.point }
  map X Y f := PFun.toSubtype _ f.toFun ∘ Subtype.val
  map_id' X :=
    PFun.ext fun a b => PFun.mem_to_subtype_iff.trans (Subtype.coe_inj.trans Part.mem_some_iff.symm)
  map_comp' X Y Z f g :=
    PFun.ext fun a c =>
      by
      refine' (pfun.mem_to_subtype_iff.trans _).trans part.mem_bind_iff.symm
      simp_rw [PFun.mem_to_subtype_iff, Subtype.exists]
      refine'
        ⟨fun h =>
          ⟨f.to_fun a, fun ha =>
            c.2 <| h.trans ((congr_arg g.to_fun ha : g.to_fun _ = _).trans g.map_point), rfl, h⟩,
          _⟩
      rintro ⟨b, _, rfl : b = _, h⟩
      exact h
#align Pointed_to_PartialFun pointedToPartialFun

/-- The functor which maps undefined values to a new point. This makes the maps total and creates
pointed types. This the noncomputable part of the equivalence `PartialFun_equiv_Pointed`. It can't
be computable because `= option.none` is decidable while the domain of a general `part` isn't. -/
@[simps map]
noncomputable def partialFunToPointed : PartialFun ⥤ Pointed := by
  classical exact
      { obj := fun X => ⟨Option X, none⟩
        map := fun X Y f => ⟨Option.elim' none fun a => (f a).toOption, rfl⟩
        map_id' := fun X =>
          Pointed.Hom.ext _ _ <| funext fun o => Option.recOn o rfl fun a => Part.some_toOption _
        map_comp' := fun X Y Z f g =>
          Pointed.Hom.ext _ _ <|
            funext fun o => Option.recOn o rfl fun a => Part.bind_toOption _ _ }
#align PartialFun_to_Pointed partialFunToPointed

/-- The equivalence induced by `PartialFun_to_Pointed` and `Pointed_to_PartialFun`.
`part.equiv_option` made functorial. -/
@[simps]
noncomputable def partialFunEquivPointed : PartialFun.{u} ≌ Pointed := by
  classical exact
      equivalence.mk partialFunToPointed pointedToPartialFun
        (nat_iso.of_components
          (fun X =>
            PartialFun.Iso.mk
              { toFun := fun a => ⟨some a, some_ne_none a⟩
                invFun := fun a => get <| ne_none_iff_is_some.1 a.2
                left_inv := fun a => get_some _ _
                right_inv := fun a => by
                  simp only [Subtype.val_eq_coe, some_get, Subtype.coe_eta] })
          fun X Y f =>
          PFun.ext fun a b => by
            unfold_projs
            dsimp
            rw [Part.bind_some]
            refine' (part.mem_bind_iff.trans _).trans pfun.mem_to_subtype_iff.symm
            obtain ⟨b | b, hb⟩ := b
            · exact (hb rfl).elim
            dsimp
            simp_rw [Part.mem_some_iff, Subtype.mk_eq_mk, exists_prop, some_inj, exists_eq_right']
            refine' part.mem_to_option.symm.trans _
            exact eq_comm)
        (nat_iso.of_components
          (fun X =>
            Pointed.Iso.mk
              { toFun := Option.elim' X.point Subtype.val
                invFun := fun a => if h : a = X.point then none else some ⟨_, h⟩
                left_inv := fun a =>
                  Option.recOn a (dif_pos rfl) fun a =>
                    (dif_neg a.2).trans <| by
                      simp only [Option.elim', Subtype.val_eq_coe, Subtype.coe_eta]
                right_inv := fun a =>
                  by
                  change Option.elim' _ _ (dite _ _ _) = _
                  split_ifs
                  · rw [h]
                    rfl
                  · rfl }
              rfl)
          fun X Y f =>
          Pointed.Hom.ext _ _ <|
            funext fun a =>
              Option.recOn a f.map_point.symm fun a =>
                by
                unfold_projs
                dsimp
                change Option.elim' _ _ _ = _
                rw [Part.elim_toOption]
                split_ifs
                · rfl
                · exact Eq.symm (of_not_not h))
#align PartialFun_equiv_Pointed partialFunEquivPointed

/-- Forgetting that maps are total and making them total again by adding a point is the same as just
adding a point. -/
@[simps]
noncomputable def typeToPartialFunIsoPartialFunToPointed :
    typeToPartialFun ⋙ partialFunToPointed ≅ typeToPointed :=
  NatIso.ofComponents
    (fun X =>
      { Hom := ⟨id, rfl⟩
        inv := ⟨id, rfl⟩
        hom_inv_id' := rfl
        inv_hom_id' := rfl })
    fun X Y f =>
    Pointed.Hom.ext _ _ <|
      funext fun a => Option.recOn a rfl fun a => by convert Part.some_toOption _
#align Type_to_PartialFun_iso_PartialFun_to_Pointed typeToPartialFunIsoPartialFunToPointed