/-
Copyright (c) 2017 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro

! This file was ported from Lean 3 source module data.fintype.big_operators
! leanprover-community/mathlib commit 327c3c0d9232d80e250dc8f65e7835b82b266ea5
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Fintype.Option
import Mathbin.Data.Fintype.Powerset
import Mathbin.Data.Fintype.Sigma
import Mathbin.Data.Fintype.Sum
import Mathbin.Data.Fintype.Vector
import Mathbin.Algebra.BigOperators.Ring
import Mathbin.Algebra.BigOperators.Option

/-!
Results about "big operations" over a `fintype`, and consequent
results about cardinalities of certain types.

## Implementation note

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.
This content had previously been in `data.fintype.basic`, but was moved here to avoid
requiring `algebra.big_operators` (and hence many other imports) as a
dependency of `fintype`.

However many of the results here really belong in `algebra.big_operators.basic`
and should be moved at some point.
-/

universe u v

variable {α : Type _} {β : Type _} {γ : Type _}

open BigOperators

namespace Fintype

/- warning: fintype.prod_bool -> Fintype.prod_bool is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] (f : Bool -> α), Eq.{succ u1} α (Finset.prod.{u1, 0} α Bool _inst_1 (Finset.univ.{0} Bool Bool.fintype) (fun (b : Bool) => f b)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) (f Bool.true) (f Bool.false))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] (f : Bool -> α), Eq.{succ u1} α (Finset.prod.{u1, 0} α Bool _inst_1 (Finset.univ.{0} Bool Bool.fintype) (fun (b : Bool) => f b)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) (f Bool.true) (f Bool.false))
Case conversion may be inaccurate. Consider using '#align fintype.prod_bool Fintype.prod_boolₓ'. -/
@[to_additive]
theorem prod_bool [CommMonoid α] (f : Bool → α) : (∏ b, f b) = f true * f false := by simp
#align fintype.prod_bool Fintype.prod_bool
#align fintype.sum_bool Fintype.sum_bool

#print Fintype.card_eq_sum_ones /-
theorem card_eq_sum_ones {α} [Fintype α] : Fintype.card α = ∑ a : α, 1 :=
  Finset.card_eq_sum_ones _
#align fintype.card_eq_sum_ones Fintype.card_eq_sum_ones
-/

section

open Finset

variable {ι : Type _} [DecidableEq ι] [Fintype ι]

/- warning: fintype.prod_extend_by_one -> Fintype.prod_extend_by_one is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {ι : Type.{u2}} [_inst_1 : DecidableEq.{succ u2} ι] [_inst_2 : Fintype.{u2} ι] [_inst_3 : CommMonoid.{u1} α] (s : Finset.{u2} ι) (f : ι -> α), Eq.{succ u1} α (Finset.prod.{u1, u2} α ι _inst_3 (Finset.univ.{u2} ι _inst_2) (fun (i : ι) => ite.{succ u1} α (Membership.Mem.{u2, u2} ι (Finset.{u2} ι) (Finset.hasMem.{u2} ι) i s) (Finset.decidableMem.{u2} ι (fun (a : ι) (b : ι) => _inst_1 a b) i s) (f i) (OfNat.ofNat.{u1} α 1 (OfNat.mk.{u1} α 1 (One.one.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_3)))))))) (Finset.prod.{u1, u2} α ι _inst_3 s (fun (i : ι) => f i))
but is expected to have type
  forall {α : Type.{u2}} {ι : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} ι] [_inst_2 : Fintype.{u1} ι] [_inst_3 : CommMonoid.{u2} α] (s : Finset.{u1} ι) (f : ι -> α), Eq.{succ u2} α (Finset.prod.{u2, u1} α ι _inst_3 (Finset.univ.{u1} ι _inst_2) (fun (i : ι) => ite.{succ u2} α (Membership.mem.{u1, u1} ι (Finset.{u1} ι) (Finset.instMembershipFinset.{u1} ι) i s) (Finset.decidableMem.{u1} ι (fun (a : ι) (b : ι) => _inst_1 a b) i s) (f i) (OfNat.ofNat.{u2} α 1 (One.toOfNat1.{u2} α (Monoid.toOne.{u2} α (CommMonoid.toMonoid.{u2} α _inst_3)))))) (Finset.prod.{u2, u1} α ι _inst_3 s (fun (i : ι) => f i))
Case conversion may be inaccurate. Consider using '#align fintype.prod_extend_by_one Fintype.prod_extend_by_oneₓ'. -/
@[to_additive]
theorem prod_extend_by_one [CommMonoid α] (s : Finset ι) (f : ι → α) :
    (∏ i, if i ∈ s then f i else 1) = ∏ i in s, f i := by
  rw [← prod_filter, filter_mem_eq_inter, univ_inter]
#align fintype.prod_extend_by_one Fintype.prod_extend_by_one
#align fintype.sum_extend_by_zero Fintype.sum_extend_by_zero

end

section

variable {M : Type _} [Fintype α] [CommMonoid M]

/- warning: fintype.prod_eq_one -> Fintype.prod_eq_one is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {M : Type.{u2}} [_inst_1 : Fintype.{u1} α] [_inst_2 : CommMonoid.{u2} M] (f : α -> M), (forall (a : α), Eq.{succ u2} M (f a) (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))))))) -> (Eq.{succ u2} M (Finset.prod.{u2, u1} M α _inst_2 (Finset.univ.{u1} α _inst_1) (fun (a : α) => f a)) (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)))))))
but is expected to have type
  forall {α : Type.{u1}} {M : Type.{u2}} [_inst_1 : Fintype.{u1} α] [_inst_2 : CommMonoid.{u2} M] (f : α -> M), (forall (a : α), Eq.{succ u2} M (f a) (OfNat.ofNat.{u2} M 1 (One.toOfNat1.{u2} M (Monoid.toOne.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2))))) -> (Eq.{succ u2} M (Finset.prod.{u2, u1} M α _inst_2 (Finset.univ.{u1} α _inst_1) (fun (a : α) => f a)) (OfNat.ofNat.{u2} M 1 (One.toOfNat1.{u2} M (Monoid.toOne.{u2} M (CommMonoid.toMonoid.{u2} M _inst_2)))))
Case conversion may be inaccurate. Consider using '#align fintype.prod_eq_one Fintype.prod_eq_oneₓ'. -/
@[to_additive]
theorem prod_eq_one (f : α → M) (h : ∀ a, f a = 1) : (∏ a, f a) = 1 :=
  Finset.prod_eq_one fun a ha => h a
#align fintype.prod_eq_one Fintype.prod_eq_one
#align fintype.sum_eq_zero Fintype.sum_eq_zero

#print Fintype.prod_congr /-
@[to_additive]
theorem prod_congr (f g : α → M) (h : ∀ a, f a = g a) : (∏ a, f a) = ∏ a, g a :=
  Finset.prod_congr rfl fun a ha => h a
#align fintype.prod_congr Fintype.prod_congr
#align fintype.sum_congr Fintype.sum_congr
-/

/- warning: fintype.prod_eq_single -> Fintype.prod_eq_single is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {M : Type.{u2}} [_inst_1 : Fintype.{u1} α] [_inst_2 : CommMonoid.{u2} M] {f : α -> M} (a : α), (forall (x : α), (Ne.{succ u1} α x a) -> (Eq.{succ u2} M (f x) (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)))))))) -> (Eq.{succ u2} M (Finset.prod.{u2, u1} M α _inst_2 (Finset.univ.{u1} α _inst_1) (fun (x : α) => f x)) (f a))
but is expected to have type
  forall {α : Type.{u2}} {M : Type.{u1}} [_inst_1 : Fintype.{u2} α] [_inst_2 : CommMonoid.{u1} M] {f : α -> M} (a : α), (forall (x : α), (Ne.{succ u2} α x a) -> (Eq.{succ u1} M (f x) (OfNat.ofNat.{u1} M 1 (One.toOfNat1.{u1} M (Monoid.toOne.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2)))))) -> (Eq.{succ u1} M (Finset.prod.{u1, u2} M α _inst_2 (Finset.univ.{u2} α _inst_1) (fun (x : α) => f x)) (f a))
Case conversion may be inaccurate. Consider using '#align fintype.prod_eq_single Fintype.prod_eq_singleₓ'. -/
/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (x «expr ≠ » a) -/
@[to_additive]
theorem prod_eq_single {f : α → M} (a : α) (h : ∀ (x) (_ : x ≠ a), f x = 1) : (∏ x, f x) = f a :=
  Finset.prod_eq_single a (fun x _ hx => h x hx) fun ha => (ha (Finset.mem_univ a)).elim
#align fintype.prod_eq_single Fintype.prod_eq_single
#align fintype.sum_eq_single Fintype.sum_eq_single

/- warning: fintype.prod_eq_mul -> Fintype.prod_eq_mul is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {M : Type.{u2}} [_inst_1 : Fintype.{u1} α] [_inst_2 : CommMonoid.{u2} M] {f : α -> M} (a : α) (b : α), (Ne.{succ u1} α a b) -> (forall (x : α), (And (Ne.{succ u1} α x a) (Ne.{succ u1} α x b)) -> (Eq.{succ u2} M (f x) (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)))))))) -> (Eq.{succ u2} M (Finset.prod.{u2, u1} M α _inst_2 (Finset.univ.{u1} α _inst_1) (fun (x : α) => f x)) (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)))) (f a) (f b)))
but is expected to have type
  forall {α : Type.{u2}} {M : Type.{u1}} [_inst_1 : Fintype.{u2} α] [_inst_2 : CommMonoid.{u1} M] {f : α -> M} (a : α) (b : α), (Ne.{succ u2} α a b) -> (forall (x : α), (And (Ne.{succ u2} α x a) (Ne.{succ u2} α x b)) -> (Eq.{succ u1} M (f x) (OfNat.ofNat.{u1} M 1 (One.toOfNat1.{u1} M (Monoid.toOne.{u1} M (CommMonoid.toMonoid.{u1} M _inst_2)))))) -> (Eq.{succ u1} M (Finset.prod.{u1, u2} M α _inst_2 (Finset.univ.{u2} α _inst_1) (fun (x : α) => f x)) (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)))) (f a) (f b)))
Case conversion may be inaccurate. Consider using '#align fintype.prod_eq_mul Fintype.prod_eq_mulₓ'. -/
@[to_additive]
theorem prod_eq_mul {f : α → M} (a b : α) (h₁ : a ≠ b) (h₂ : ∀ x, x ≠ a ∧ x ≠ b → f x = 1) :
    (∏ x, f x) = f a * f b := by
  apply Finset.prod_eq_mul a b h₁ fun x _ hx => h₂ x hx <;>
    exact fun hc => (hc (Finset.mem_univ _)).elim
#align fintype.prod_eq_mul Fintype.prod_eq_mul
#align fintype.sum_eq_add Fintype.sum_eq_add

#print Fintype.eq_of_subsingleton_of_prod_eq /-
/-- If a product of a `finset` of a subsingleton type has a given
value, so do the terms in that product. -/
@[to_additive
      "If a sum of a `finset` of a subsingleton type has a given\nvalue, so do the terms in that sum."]
theorem eq_of_subsingleton_of_prod_eq {ι : Type _} [Subsingleton ι] {s : Finset ι} {f : ι → M}
    {b : M} (h : (∏ i in s, f i) = b) : ∀ i ∈ s, f i = b :=
  Finset.eq_of_card_le_one_of_prod_eq (Finset.card_le_one_of_subsingleton s) h
#align fintype.eq_of_subsingleton_of_prod_eq Fintype.eq_of_subsingleton_of_prod_eq
#align fintype.eq_of_subsingleton_of_sum_eq Fintype.eq_of_subsingleton_of_sum_eq
-/

end

end Fintype

open Finset

section

variable {M : Type _} [Fintype α] [CommMonoid M]

/- warning: fintype.prod_option -> Fintype.prod_option is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {M : Type.{u2}} [_inst_1 : Fintype.{u1} α] [_inst_2 : CommMonoid.{u2} M] (f : (Option.{u1} α) -> M), Eq.{succ u2} M (Finset.prod.{u2, u1} M (Option.{u1} α) _inst_2 (Finset.univ.{u1} (Option.{u1} α) (Option.fintype.{u1} α _inst_1)) (fun (i : Option.{u1} α) => f i)) (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)))) (f (Option.none.{u1} α)) (Finset.prod.{u2, u1} M α _inst_2 (Finset.univ.{u1} α _inst_1) (fun (i : α) => f (Option.some.{u1} α i))))
but is expected to have type
  forall {α : Type.{u2}} {M : Type.{u1}} [_inst_1 : Fintype.{u2} α] [_inst_2 : CommMonoid.{u1} M] (f : (Option.{u2} α) -> M), Eq.{succ u1} M (Finset.prod.{u1, u2} M (Option.{u2} α) _inst_2 (Finset.univ.{u2} (Option.{u2} α) (instFintypeOption.{u2} α _inst_1)) (fun (i : Option.{u2} α) => f i)) (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)))) (f (Option.none.{u2} α)) (Finset.prod.{u1, u2} M α _inst_2 (Finset.univ.{u2} α _inst_1) (fun (i : α) => f (Option.some.{u2} α i))))
Case conversion may be inaccurate. Consider using '#align fintype.prod_option Fintype.prod_optionₓ'. -/
@[simp, to_additive]
theorem Fintype.prod_option (f : Option α → M) : (∏ i, f i) = f none * ∏ i, f (some i) :=
  Finset.prod_insertNone f univ
#align fintype.prod_option Fintype.prod_option
#align fintype.sum_option Fintype.sum_option

end

open Finset

/- warning: fintype.card_sigma -> Fintype.card_sigma is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (β : α -> Type.{u2}) [_inst_1 : Fintype.{u1} α] [_inst_2 : forall (a : α), Fintype.{u2} (β a)], Eq.{1} Nat (Fintype.card.{max u1 u2} (Sigma.{u1, u2} α β) (Sigma.fintype.{u1, u2} α β _inst_1 (fun (a : α) => _inst_2 a))) (Finset.sum.{0, u1} Nat α Nat.addCommMonoid (Finset.univ.{u1} α _inst_1) (fun (a : α) => Fintype.card.{u2} (β a) (_inst_2 a)))
but is expected to have type
  forall {α : Type.{u2}} (β : α -> Type.{u1}) [_inst_1 : Fintype.{u2} α] [_inst_2 : forall (a : α), Fintype.{u1} (β a)], Eq.{1} Nat (Fintype.card.{max u1 u2} (Sigma.{u2, u1} α β) (instFintypeSigma.{u2, u1} α β _inst_1 (fun (a : α) => _inst_2 a))) (Finset.sum.{0, u2} Nat α Nat.addCommMonoid (Finset.univ.{u2} α _inst_1) (fun (a : α) => Fintype.card.{u1} (β a) (_inst_2 a)))
Case conversion may be inaccurate. Consider using '#align fintype.card_sigma Fintype.card_sigmaₓ'. -/
@[simp]
theorem Fintype.card_sigma {α : Type _} (β : α → Type _) [Fintype α] [∀ a, Fintype (β a)] :
    Fintype.card (Sigma β) = ∑ a, Fintype.card (β a) :=
  card_sigma _ _
#align fintype.card_sigma Fintype.card_sigma

/- warning: finset.card_pi -> Finset.card_pi is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Type.{u2}} (s : Finset.{u1} α) (t : forall (a : α), Finset.{u2} (δ a)), Eq.{1} Nat (Finset.card.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) a s) -> (δ a)) (Finset.pi.{u1, u2} α (fun (a : α) => δ a) (fun (a : α) (b : α) => _inst_1 a b) s t)) (Finset.prod.{0, u1} Nat α Nat.commMonoid s (fun (a : α) => Finset.card.{u2} (δ a) (t a)))
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : DecidableEq.{succ u2} α] {δ : α -> Type.{u1}} (s : Finset.{u2} α) (t : forall (a : α), Finset.{u1} (δ a)), Eq.{1} Nat (Finset.card.{max u2 u1} (forall (a : α), (Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) a s) -> (δ a)) (Finset.pi.{u1, u2} α (fun (a : α) => δ a) (fun (a : α) (b : α) => _inst_1 a b) s t)) (Finset.prod.{0, u2} Nat α Nat.commMonoid s (fun (a : α) => Finset.card.{u1} (δ a) (t a)))
Case conversion may be inaccurate. Consider using '#align finset.card_pi Finset.card_piₓ'. -/
@[simp]
theorem Finset.card_pi [DecidableEq α] {δ : α → Type _} (s : Finset α) (t : ∀ a, Finset (δ a)) :
    (s.pi t).card = ∏ a in s, card (t a) :=
  Multiset.card_pi _ _
#align finset.card_pi Finset.card_pi

/- warning: fintype.card_pi_finset -> Fintype.card_piFinset is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] [_inst_2 : Fintype.{u1} α] {δ : α -> Type.{u2}} (t : forall (a : α), Finset.{u2} (δ a)), Eq.{1} Nat (Finset.card.{max u1 u2} (forall (a : α), δ a) (Fintype.piFinset.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) _inst_2 (fun (a : α) => δ a) t)) (Finset.prod.{0, u1} Nat α Nat.commMonoid (Finset.univ.{u1} α _inst_2) (fun (a : α) => Finset.card.{u2} (δ a) (t a)))
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : DecidableEq.{succ u2} α] [_inst_2 : Fintype.{u2} α] {δ : α -> Type.{u1}} (t : forall (a : α), Finset.{u1} (δ a)), Eq.{1} Nat (Finset.card.{max u2 u1} (forall (a : α), δ a) (Fintype.piFinset.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) _inst_2 (fun (a : α) => δ a) t)) (Finset.prod.{0, u2} Nat α Nat.commMonoid (Finset.univ.{u2} α _inst_2) (fun (a : α) => Finset.card.{u1} (δ a) (t a)))
Case conversion may be inaccurate. Consider using '#align fintype.card_pi_finset Fintype.card_piFinsetₓ'. -/
@[simp]
theorem Fintype.card_piFinset [DecidableEq α] [Fintype α] {δ : α → Type _} (t : ∀ a, Finset (δ a)) :
    (Fintype.piFinset t).card = ∏ a, card (t a) := by simp [Fintype.piFinset, card_map]
#align fintype.card_pi_finset Fintype.card_piFinset

#print Fintype.card_pi /-
@[simp]
theorem Fintype.card_pi {β : α → Type _} [DecidableEq α] [Fintype α] [f : ∀ a, Fintype (β a)] :
    Fintype.card (∀ a, β a) = ∏ a, Fintype.card (β a) :=
  Fintype.card_piFinset _
#align fintype.card_pi Fintype.card_pi
-/

/- warning: fintype.card_fun -> Fintype.card_fun is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : DecidableEq.{succ u1} α] [_inst_2 : Fintype.{u1} α] [_inst_3 : Fintype.{u2} β], Eq.{1} Nat (Fintype.card.{max u1 u2} (α -> β) (Pi.fintype.{u1, u2} α (fun (ᾰ : α) => β) (fun (a : α) (b : α) => _inst_1 a b) _inst_2 (fun (a : α) => _inst_3))) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) (Fintype.card.{u2} β _inst_3) (Fintype.card.{u1} α _inst_2))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : DecidableEq.{succ u2} α] [_inst_2 : Fintype.{u2} α] [_inst_3 : Fintype.{u1} β], Eq.{1} Nat (Fintype.card.{max u2 u1} (α -> β) (Pi.fintype.{u2, u1} α (fun (ᾰ : α) => β) (fun (a : α) (b : α) => _inst_1 a b) _inst_2 (fun (a : α) => _inst_3))) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) (Fintype.card.{u1} β _inst_3) (Fintype.card.{u2} α _inst_2))
Case conversion may be inaccurate. Consider using '#align fintype.card_fun Fintype.card_funₓ'. -/
-- FIXME ouch, this should be in the main file.
@[simp]
theorem Fintype.card_fun [DecidableEq α] [Fintype α] [Fintype β] :
    Fintype.card (α → β) = Fintype.card β ^ Fintype.card α := by
  rw [Fintype.card_pi, Finset.prod_const] <;> rfl
#align fintype.card_fun Fintype.card_fun

#print card_vector /-
@[simp]
theorem card_vector [Fintype α] (n : ℕ) : Fintype.card (Vector α n) = Fintype.card α ^ n := by
  rw [Fintype.ofEquiv_card] <;> simp
#align card_vector card_vector
-/

/- warning: finset.prod_attach_univ -> Finset.prod_attach_univ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Fintype.{u1} α] [_inst_2 : CommMonoid.{u2} β] (f : (Subtype.{succ u1} α (fun (a : α) => Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) a (Finset.univ.{u1} α _inst_1))) -> β), Eq.{succ u2} β (Finset.prod.{u2, u1} β (Subtype.{succ u1} α (fun (x : α) => Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x (Finset.univ.{u1} α _inst_1))) _inst_2 (Finset.attach.{u1} α (Finset.univ.{u1} α _inst_1)) (fun (x : Subtype.{succ u1} α (fun (x : α) => Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x (Finset.univ.{u1} α _inst_1))) => f x)) (Finset.prod.{u2, u1} β α _inst_2 (Finset.univ.{u1} α _inst_1) (fun (x : α) => f (Subtype.mk.{succ u1} α (fun (a : α) => Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) a (Finset.univ.{u1} α _inst_1)) x (Finset.mem_univ.{u1} α _inst_1 x))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Fintype.{u2} α] [_inst_2 : CommMonoid.{u1} β] (f : (Subtype.{succ u2} α (fun (a : α) => Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) a (Finset.univ.{u2} α _inst_1))) -> β), Eq.{succ u1} β (Finset.prod.{u1, u2} β (Subtype.{succ u2} α (fun (x : α) => Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) x (Finset.univ.{u2} α _inst_1))) _inst_2 (Finset.attach.{u2} α (Finset.univ.{u2} α _inst_1)) (fun (x : Subtype.{succ u2} α (fun (x : α) => Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) x (Finset.univ.{u2} α _inst_1))) => f x)) (Finset.prod.{u1, u2} β α _inst_2 (Finset.univ.{u2} α _inst_1) (fun (x : α) => f (Subtype.mk.{succ u2} α (fun (a : α) => Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) a (Finset.univ.{u2} α _inst_1)) x (Finset.mem_univ.{u2} α _inst_1 x))))
Case conversion may be inaccurate. Consider using '#align finset.prod_attach_univ Finset.prod_attach_univₓ'. -/
@[simp, to_additive]
theorem Finset.prod_attach_univ [Fintype α] [CommMonoid β] (f : { a : α // a ∈ @univ α _ } → β) :
    (∏ x in univ.attach, f x) = ∏ x, f ⟨x, mem_univ _⟩ :=
  Fintype.prod_equiv (Equiv.subtypeUnivEquiv fun x => mem_univ _) _ _ fun x => by simp
#align finset.prod_attach_univ Finset.prod_attach_univ
#align finset.sum_attach_univ Finset.sum_attach_univ

/- warning: finset.prod_univ_pi -> Finset.prod_univ_pi is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : DecidableEq.{succ u1} α] [_inst_2 : Fintype.{u1} α] [_inst_3 : CommMonoid.{u2} β] {δ : α -> Type.{u3}} {t : forall (a : α), Finset.{u3} (δ a)} (f : (forall (a : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) a (Finset.univ.{u1} α _inst_2)) -> (δ a)) -> β), Eq.{succ u2} β (Finset.prod.{u2, max u1 u3} β (forall (a : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) a (Finset.univ.{u1} α _inst_2)) -> (δ a)) _inst_3 (Finset.pi.{u1, u3} α (fun (a : α) => δ a) (fun (a : α) (b : α) => _inst_1 a b) (Finset.univ.{u1} α _inst_2) t) (fun (x : forall (a : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) a (Finset.univ.{u1} α _inst_2)) -> (δ a)) => f x)) (Finset.prod.{u2, max u1 u3} β (forall (a : α), δ a) _inst_3 (Fintype.piFinset.{u1, u3} α (fun (a : α) (b : α) => _inst_1 a b) _inst_2 (fun (a : α) => δ a) t) (fun (x : forall (a : α), δ a) => f (fun (a : α) (_x : Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) a (Finset.univ.{u1} α _inst_2)) => x a)))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} [_inst_1 : DecidableEq.{succ u3} α] [_inst_2 : Fintype.{u3} α] [_inst_3 : CommMonoid.{u2} β] {δ : α -> Type.{u1}} {t : forall (a : α), Finset.{u1} (δ a)} (f : (forall (a : α), (Membership.mem.{u3, u3} α (Finset.{u3} α) (Finset.instMembershipFinset.{u3} α) a (Finset.univ.{u3} α _inst_2)) -> (δ a)) -> β), Eq.{succ u2} β (Finset.prod.{u2, max u3 u1} β (forall (a : α), (Membership.mem.{u3, u3} α (Finset.{u3} α) (Finset.instMembershipFinset.{u3} α) a (Finset.univ.{u3} α _inst_2)) -> (δ a)) _inst_3 (Finset.pi.{u1, u3} α (fun (a : α) => δ a) (fun (a : α) (b : α) => _inst_1 a b) (Finset.univ.{u3} α _inst_2) t) (fun (x : forall (a : α), (Membership.mem.{u3, u3} α (Finset.{u3} α) (Finset.instMembershipFinset.{u3} α) a (Finset.univ.{u3} α _inst_2)) -> (δ a)) => f x)) (Finset.prod.{u2, max u1 u3} β (forall (a : α), δ a) _inst_3 (Fintype.piFinset.{u3, u1} α (fun (a : α) (b : α) => _inst_1 a b) _inst_2 (fun (a : α) => δ a) t) (fun (x : forall (a : α), δ a) => f (fun (a : α) (_x : Membership.mem.{u3, u3} α (Finset.{u3} α) (Finset.instMembershipFinset.{u3} α) a (Finset.univ.{u3} α _inst_2)) => x a)))
Case conversion may be inaccurate. Consider using '#align finset.prod_univ_pi Finset.prod_univ_piₓ'. -/
/-- Taking a product over `univ.pi t` is the same as taking the product over `fintype.pi_finset t`.
  `univ.pi t` and `fintype.pi_finset t` are essentially the same `finset`, but differ
  in the type of their element, `univ.pi t` is a `finset (Π a ∈ univ, t a)` and
  `fintype.pi_finset t` is a `finset (Π a, t a)`. -/
@[to_additive
      "Taking a sum over `univ.pi t` is the same as taking the sum over\n  `fintype.pi_finset t`. `univ.pi t` and `fintype.pi_finset t` are essentially the same `finset`,\n  but differ in the type of their element, `univ.pi t` is a `finset (Π a ∈ univ, t a)` and\n  `fintype.pi_finset t` is a `finset (Π a, t a)`."]
theorem Finset.prod_univ_pi [DecidableEq α] [Fintype α] [CommMonoid β] {δ : α → Type _}
    {t : ∀ a : α, Finset (δ a)} (f : (∀ a : α, a ∈ (univ : Finset α) → δ a) → β) :
    (∏ x in univ.pi t, f x) = ∏ x in Fintype.piFinset t, f fun a _ => x a :=
  prod_bij (fun x _ a => x a (mem_univ _)) (by simp) (by simp)
    (by simp (config := { contextual := true }) [Function.funext_iff]) fun x hx =>
    ⟨fun a _ => x a, by simp_all⟩
#align finset.prod_univ_pi Finset.prod_univ_pi
#align finset.sum_univ_pi Finset.sum_univ_pi

/- warning: finset.prod_univ_sum -> Finset.prod_univ_sum is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u_1}} {β : Type.{u_2}} [_inst_1 : DecidableEq.{succ u_1} α] [_inst_2 : Fintype.{u_1} α] [_inst_3 : CommSemiring.{u_2} β] {δ : α -> Type.{u_1}} [_inst_4 : forall (a : α), DecidableEq.{succ u_1} (δ a)] {t : forall (a : α), Finset.{u_1} (δ a)} {f : forall (a : α), (δ a) -> β}, Eq.{succ u_2} β (Finset.prod.{u_2, u_1} β α (CommSemiring.toCommMonoid.{u_2} β _inst_3) (Finset.univ.{u_1} α _inst_2) (fun (a : α) => Finset.sum.{u_2, u_1} β (δ a) (NonUnitalNonAssocSemiring.toAddCommMonoid.{u_2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u_2} β (Semiring.toNonAssocSemiring.{u_2} β (CommSemiring.toSemiring.{u_2} β _inst_3)))) (t a) (fun (b : δ a) => f a b))) (Finset.sum.{u_2, u_1} β (forall (a : α), δ a) (NonUnitalNonAssocSemiring.toAddCommMonoid.{u_2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u_2} β (Semiring.toNonAssocSemiring.{u_2} β (CommSemiring.toSemiring.{u_2} β _inst_3)))) (Fintype.piFinset.{u_1, u_1} α (fun (a : α) (b : α) => _inst_1 a b) _inst_2 (fun (a : α) => δ a) t) (fun (p : forall (a : α), δ a) => Finset.prod.{u_2, u_1} β α (CommSemiring.toCommMonoid.{u_2} β _inst_3) (Finset.univ.{u_1} α _inst_2) (fun (x : α) => f x (p x))))
but is expected to have type
  forall {α : Type.{u_2}} {β : Type.{u_3}} [_inst_1 : DecidableEq.{succ u_2} α] [_inst_2 : Fintype.{u_2} α] [_inst_3 : CommSemiring.{u_3} β] {δ : α -> Type.{u_1}} [_inst_4 : forall (a : α), DecidableEq.{succ u_1} (δ a)] {t : forall (a : α), Finset.{u_1} (δ a)} {f : forall (a : α), (δ a) -> β}, Eq.{succ u_3} β (Finset.prod.{u_3, u_2} β α (CommSemiring.toCommMonoid.{u_3} β _inst_3) (Finset.univ.{u_2} α _inst_2) (fun (a : α) => Finset.sum.{u_3, u_1} β (δ a) (NonUnitalNonAssocSemiring.toAddCommMonoid.{u_3} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u_3} β (Semiring.toNonAssocSemiring.{u_3} β (CommSemiring.toSemiring.{u_3} β _inst_3)))) (t a) (fun (b : δ a) => f a b))) (Finset.sum.{u_3, max u_1 u_2} β (forall (a : α), δ a) (NonUnitalNonAssocSemiring.toAddCommMonoid.{u_3} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u_3} β (Semiring.toNonAssocSemiring.{u_3} β (CommSemiring.toSemiring.{u_3} β _inst_3)))) (Fintype.piFinset.{u_2, u_1} α (fun (a : α) (b : α) => _inst_1 a b) _inst_2 (fun (a : α) => δ a) t) (fun (p : forall (a : α), δ a) => Finset.prod.{u_3, u_2} β α (CommSemiring.toCommMonoid.{u_3} β _inst_3) (Finset.univ.{u_2} α _inst_2) (fun (x : α) => f x (p x))))
Case conversion may be inaccurate. Consider using '#align finset.prod_univ_sum Finset.prod_univ_sumₓ'. -/
/-- The product over `univ` of a sum can be written as a sum over the product of sets,
  `fintype.pi_finset`. `finset.prod_sum` is an alternative statement when the product is not
  over `univ` -/
theorem Finset.prod_univ_sum [DecidableEq α] [Fintype α] [CommSemiring β] {δ : α → Type u_1}
    [∀ a : α, DecidableEq (δ a)] {t : ∀ a : α, Finset (δ a)} {f : ∀ a : α, δ a → β} :
    (∏ a, ∑ b in t a, f a b) = ∑ p in Fintype.piFinset t, ∏ x, f x (p x) := by
  simp only [Finset.prod_attach_univ, prod_sum, Finset.sum_univ_pi]
#align finset.prod_univ_sum Finset.prod_univ_sum

/- warning: fintype.sum_pow_mul_eq_add_pow -> Fintype.sum_pow_mul_eq_add_pow is a dubious translation:
lean 3 declaration is
  forall (α : Type.{u1}) [_inst_1 : Fintype.{u1} α] {R : Type.{u2}} [_inst_2 : CommSemiring.{u2} R] (a : R) (b : R), Eq.{succ u2} R (Finset.sum.{u2, u1} R (Finset.{u1} α) (NonUnitalNonAssocSemiring.toAddCommMonoid.{u2} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} R (Semiring.toNonAssocSemiring.{u2} R (CommSemiring.toSemiring.{u2} R _inst_2)))) (Finset.univ.{u1} (Finset.{u1} α) (Finset.fintype.{u1} α _inst_1)) (fun (s : Finset.{u1} α) => HMul.hMul.{u2, u2, u2} R R R (instHMul.{u2} R (Distrib.toHasMul.{u2} R (NonUnitalNonAssocSemiring.toDistrib.{u2} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} R (Semiring.toNonAssocSemiring.{u2} R (CommSemiring.toSemiring.{u2} R _inst_2)))))) (HPow.hPow.{u2, 0, u2} R Nat R (instHPow.{u2, 0} R Nat (Monoid.Pow.{u2} R (MonoidWithZero.toMonoid.{u2} R (Semiring.toMonoidWithZero.{u2} R (CommSemiring.toSemiring.{u2} R _inst_2))))) a (Finset.card.{u1} α s)) (HPow.hPow.{u2, 0, u2} R Nat R (instHPow.{u2, 0} R Nat (Monoid.Pow.{u2} R (MonoidWithZero.toMonoid.{u2} R (Semiring.toMonoidWithZero.{u2} R (CommSemiring.toSemiring.{u2} R _inst_2))))) b (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) (Fintype.card.{u1} α _inst_1) (Finset.card.{u1} α s))))) (HPow.hPow.{u2, 0, u2} R Nat R (instHPow.{u2, 0} R Nat (Monoid.Pow.{u2} R (MonoidWithZero.toMonoid.{u2} R (Semiring.toMonoidWithZero.{u2} R (CommSemiring.toSemiring.{u2} R _inst_2))))) (HAdd.hAdd.{u2, u2, u2} R R R (instHAdd.{u2} R (Distrib.toHasAdd.{u2} R (NonUnitalNonAssocSemiring.toDistrib.{u2} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} R (Semiring.toNonAssocSemiring.{u2} R (CommSemiring.toSemiring.{u2} R _inst_2)))))) a b) (Fintype.card.{u1} α _inst_1))
but is expected to have type
  forall (α : Type.{u2}) [_inst_1 : Fintype.{u2} α] {R : Type.{u1}} [_inst_2 : CommSemiring.{u1} R] (a : R) (b : R), Eq.{succ u1} R (Finset.sum.{u1, u2} R (Finset.{u2} α) (NonUnitalNonAssocSemiring.toAddCommMonoid.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_2)))) (Finset.univ.{u2} (Finset.{u2} α) (Finset.fintype.{u2} α _inst_1)) (fun (s : Finset.{u2} α) => HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_2))))) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R (CommSemiring.toSemiring.{u1} R _inst_2))))) a (Finset.card.{u2} α s)) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R (CommSemiring.toSemiring.{u1} R _inst_2))))) b (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) (Fintype.card.{u2} α _inst_1) (Finset.card.{u2} α s))))) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R (CommSemiring.toSemiring.{u1} R _inst_2))))) (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toAdd.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_2)))))) a b) (Fintype.card.{u2} α _inst_1))
Case conversion may be inaccurate. Consider using '#align fintype.sum_pow_mul_eq_add_pow Fintype.sum_pow_mul_eq_add_powₓ'. -/
/-- Summing `a^s.card * b^(n-s.card)` over all finite subsets `s` of a fintype of cardinality `n`
gives `(a + b)^n`. The "good" proof involves expanding along all coordinates using the fact that
`x^n` is multilinear, but multilinear maps are only available now over rings, so we give instead
a proof reducing to the usual binomial theorem to have a result over semirings. -/
theorem Fintype.sum_pow_mul_eq_add_pow (α : Type _) [Fintype α] {R : Type _} [CommSemiring R]
    (a b : R) :
    (∑ s : Finset α, a ^ s.card * b ^ (Fintype.card α - s.card)) = (a + b) ^ Fintype.card α :=
  Finset.sum_pow_mul_eq_add_pow _ _ _
#align fintype.sum_pow_mul_eq_add_pow Fintype.sum_pow_mul_eq_add_pow

/- warning: function.bijective.prod_comp -> Function.Bijective.prod_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Fintype.{u1} α] [_inst_2 : Fintype.{u2} β] [_inst_3 : CommMonoid.{u3} γ] {f : α -> β}, (Function.Bijective.{succ u1, succ u2} α β f) -> (forall (g : β -> γ), Eq.{succ u3} γ (Finset.prod.{u3, u1} γ α _inst_3 (Finset.univ.{u1} α _inst_1) (fun (i : α) => g (f i))) (Finset.prod.{u3, u2} γ β _inst_3 (Finset.univ.{u2} β _inst_2) (fun (i : β) => g i)))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} {γ : Type.{u1}} [_inst_1 : Fintype.{u3} α] [_inst_2 : Fintype.{u2} β] [_inst_3 : CommMonoid.{u1} γ] {f : α -> β}, (Function.Bijective.{succ u3, succ u2} α β f) -> (forall (g : β -> γ), Eq.{succ u1} γ (Finset.prod.{u1, u3} γ α _inst_3 (Finset.univ.{u3} α _inst_1) (fun (i : α) => g (f i))) (Finset.prod.{u1, u2} γ β _inst_3 (Finset.univ.{u2} β _inst_2) (fun (i : β) => g i)))
Case conversion may be inaccurate. Consider using '#align function.bijective.prod_comp Function.Bijective.prod_compₓ'. -/
@[to_additive]
theorem Function.Bijective.prod_comp [Fintype α] [Fintype β] [CommMonoid γ] {f : α → β}
    (hf : Function.Bijective f) (g : β → γ) : (∏ i, g (f i)) = ∏ i, g i :=
  Fintype.prod_bijective f hf _ _ fun x => rfl
#align function.bijective.prod_comp Function.Bijective.prod_comp
#align function.bijective.sum_comp Function.Bijective.sum_comp

/- warning: equiv.prod_comp -> Equiv.prod_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Fintype.{u1} α] [_inst_2 : Fintype.{u2} β] [_inst_3 : CommMonoid.{u3} γ] (e : Equiv.{succ u1, succ u2} α β) (f : β -> γ), Eq.{succ u3} γ (Finset.prod.{u3, u1} γ α _inst_3 (Finset.univ.{u1} α _inst_1) (fun (i : α) => f (coeFn.{max 1 (max (succ u1) (succ u2)) (succ u2) (succ u1), max (succ u1) (succ u2)} (Equiv.{succ u1, succ u2} α β) (fun (_x : Equiv.{succ u1, succ u2} α β) => α -> β) (Equiv.hasCoeToFun.{succ u1, succ u2} α β) e i))) (Finset.prod.{u3, u2} γ β _inst_3 (Finset.univ.{u2} β _inst_2) (fun (i : β) => f i))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} {γ : Type.{u1}} [_inst_1 : Fintype.{u3} α] [_inst_2 : Fintype.{u2} β] [_inst_3 : CommMonoid.{u1} γ] (e : Equiv.{succ u3, succ u2} α β) (f : β -> γ), Eq.{succ u1} γ (Finset.prod.{u1, u3} γ α _inst_3 (Finset.univ.{u3} α _inst_1) (fun (i : α) => f (FunLike.coe.{max (succ u3) (succ u2), succ u3, succ u2} (Equiv.{succ u3, succ u2} α β) α (fun (_x : α) => (fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : α) => β) _x) (Equiv.instFunLikeEquiv.{succ u3, succ u2} α β) e i))) (Finset.prod.{u1, u2} γ β _inst_3 (Finset.univ.{u2} β _inst_2) (fun (i : β) => f i))
Case conversion may be inaccurate. Consider using '#align equiv.prod_comp Equiv.prod_compₓ'. -/
@[to_additive]
theorem Equiv.prod_comp [Fintype α] [Fintype β] [CommMonoid γ] (e : α ≃ β) (f : β → γ) :
    (∏ i, f (e i)) = ∏ i, f i :=
  e.Bijective.prod_comp f
#align equiv.prod_comp Equiv.prod_comp
#align equiv.sum_comp Equiv.sum_comp

/- warning: equiv.prod_comp' -> Equiv.prod_comp' is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Fintype.{u1} α] [_inst_2 : Fintype.{u2} β] [_inst_3 : CommMonoid.{u3} γ] (e : Equiv.{succ u1, succ u2} α β) (f : α -> γ) (g : β -> γ), (forall (i : α), Eq.{succ u3} γ (f i) (g (coeFn.{max 1 (max (succ u1) (succ u2)) (succ u2) (succ u1), max (succ u1) (succ u2)} (Equiv.{succ u1, succ u2} α β) (fun (_x : Equiv.{succ u1, succ u2} α β) => α -> β) (Equiv.hasCoeToFun.{succ u1, succ u2} α β) e i))) -> (Eq.{succ u3} γ (Finset.prod.{u3, u1} γ α _inst_3 (Finset.univ.{u1} α _inst_1) (fun (i : α) => f i)) (Finset.prod.{u3, u2} γ β _inst_3 (Finset.univ.{u2} β _inst_2) (fun (i : β) => g i)))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} {γ : Type.{u1}} [_inst_1 : Fintype.{u3} α] [_inst_2 : Fintype.{u2} β] [_inst_3 : CommMonoid.{u1} γ] (e : Equiv.{succ u3, succ u2} α β) (f : α -> γ) (g : β -> γ), (forall (i : α), Eq.{succ u1} γ (f i) (g (FunLike.coe.{max (succ u3) (succ u2), succ u3, succ u2} (Equiv.{succ u3, succ u2} α β) α (fun (_x : α) => (fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : α) => β) _x) (Equiv.instFunLikeEquiv.{succ u3, succ u2} α β) e i))) -> (Eq.{succ u1} γ (Finset.prod.{u1, u3} γ α _inst_3 (Finset.univ.{u3} α _inst_1) (fun (i : α) => f i)) (Finset.prod.{u1, u2} γ β _inst_3 (Finset.univ.{u2} β _inst_2) (fun (i : β) => g i)))
Case conversion may be inaccurate. Consider using '#align equiv.prod_comp' Equiv.prod_comp'ₓ'. -/
@[to_additive]
theorem Equiv.prod_comp' [Fintype α] [Fintype β] [CommMonoid γ] (e : α ≃ β) (f : α → γ) (g : β → γ)
    (h : ∀ i, f i = g (e i)) : (∏ i, f i) = ∏ i, g i :=
  (show f = g ∘ e from funext h).symm ▸ e.prod_comp _
#align equiv.prod_comp' Equiv.prod_comp'
#align equiv.sum_comp' Equiv.sum_comp'

#print Fin.prod_univ_eq_prod_range /-
/-- It is equivalent to compute the product of a function over `fin n` or `finset.range n`. -/
@[to_additive "It is equivalent to sum a function over `fin n` or `finset.range n`."]
theorem Fin.prod_univ_eq_prod_range [CommMonoid α] (f : ℕ → α) (n : ℕ) :
    (∏ i : Fin n, f i) = ∏ i in range n, f i :=
  calc
    (∏ i : Fin n, f i) = ∏ i : { x // x ∈ range n }, f i :=
      (Fin.equivSubtype.trans (Equiv.subtypeEquivRight (by simp))).prod_comp' _ _ (by simp)
    _ = ∏ i in range n, f i := by rw [← attach_eq_univ, prod_attach]

#align fin.prod_univ_eq_prod_range Fin.prod_univ_eq_prod_range
#align fin.sum_univ_eq_sum_range Fin.sum_univ_eq_sum_range
-/

/- warning: finset.prod_fin_eq_prod_range -> Finset.prod_fin_eq_prod_range is a dubious translation:
lean 3 declaration is
  forall {β : Type.{u1}} [_inst_1 : CommMonoid.{u1} β] {n : Nat} (c : (Fin n) -> β), Eq.{succ u1} β (Finset.prod.{u1, 0} β (Fin n) _inst_1 (Finset.univ.{0} (Fin n) (Fin.fintype n)) (fun (i : Fin n) => c i)) (Finset.prod.{u1, 0} β Nat _inst_1 (Finset.range n) (fun (i : Nat) => dite.{succ u1} β (LT.lt.{0} Nat Nat.hasLt i n) (Nat.decidableLt i n) (fun (h : LT.lt.{0} Nat Nat.hasLt i n) => c (Fin.mk n i h)) (fun (h : Not (LT.lt.{0} Nat Nat.hasLt i n)) => OfNat.ofNat.{u1} β 1 (OfNat.mk.{u1} β 1 (One.one.{u1} β (MulOneClass.toHasOne.{u1} β (Monoid.toMulOneClass.{u1} β (CommMonoid.toMonoid.{u1} β _inst_1))))))))
but is expected to have type
  forall {β : Type.{u1}} [_inst_1 : CommMonoid.{u1} β] {n : Nat} (c : (Fin n) -> β), Eq.{succ u1} β (Finset.prod.{u1, 0} β (Fin n) _inst_1 (Finset.univ.{0} (Fin n) (Fin.fintype n)) (fun (i : Fin n) => c i)) (Finset.prod.{u1, 0} β Nat _inst_1 (Finset.range n) (fun (i : Nat) => dite.{succ u1} β (LT.lt.{0} Nat instLTNat i n) (Nat.decLt i n) (fun (h : LT.lt.{0} Nat instLTNat i n) => c (Fin.mk n i h)) (fun (h : Not (LT.lt.{0} Nat instLTNat i n)) => OfNat.ofNat.{u1} β 1 (One.toOfNat1.{u1} β (Monoid.toOne.{u1} β (CommMonoid.toMonoid.{u1} β _inst_1))))))
Case conversion may be inaccurate. Consider using '#align finset.prod_fin_eq_prod_range Finset.prod_fin_eq_prod_rangeₓ'. -/
@[to_additive]
theorem Finset.prod_fin_eq_prod_range [CommMonoid β] {n : ℕ} (c : Fin n → β) :
    (∏ i, c i) = ∏ i in Finset.range n, if h : i < n then c ⟨i, h⟩ else 1 :=
  by
  rw [← Fin.prod_univ_eq_prod_range, Finset.prod_congr rfl]
  rintro ⟨i, hi⟩ _
  simp only [[anonymous], hi, dif_pos]
#align finset.prod_fin_eq_prod_range Finset.prod_fin_eq_prod_range
#align finset.sum_fin_eq_sum_range Finset.sum_fin_eq_sum_range

#print Finset.prod_toFinset_eq_subtype /-
@[to_additive]
theorem Finset.prod_toFinset_eq_subtype {M : Type _} [CommMonoid M] [Fintype α] (p : α → Prop)
    [DecidablePred p] (f : α → M) : (∏ a in { x | p x }.toFinset, f a) = ∏ a : Subtype p, f a :=
  by
  rw [← Finset.prod_subtype]
  simp
#align finset.prod_to_finset_eq_subtype Finset.prod_toFinset_eq_subtype
#align finset.sum_to_finset_eq_subtype Finset.sum_toFinset_eq_subtype
-/

/- warning: finset.prod_fiberwise -> Finset.prod_fiberwise is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : DecidableEq.{succ u2} β] [_inst_2 : Fintype.{u2} β] [_inst_3 : CommMonoid.{u3} γ] (s : Finset.{u1} α) (f : α -> β) (g : α -> γ), Eq.{succ u3} γ (Finset.prod.{u3, u2} γ β _inst_3 (Finset.univ.{u2} β _inst_2) (fun (b : β) => Finset.prod.{u3, u1} γ α _inst_3 (Finset.filter.{u1} α (fun (a : α) => Eq.{succ u2} β (f a) b) (fun (a : α) => _inst_1 (f a) b) s) (fun (a : α) => g a))) (Finset.prod.{u3, u1} γ α _inst_3 s (fun (a : α) => g a))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u3}} {γ : Type.{u2}} [_inst_1 : DecidableEq.{succ u3} β] [_inst_2 : Fintype.{u3} β] [_inst_3 : CommMonoid.{u2} γ] (s : Finset.{u1} α) (f : α -> β) (g : α -> γ), Eq.{succ u2} γ (Finset.prod.{u2, u3} γ β _inst_3 (Finset.univ.{u3} β _inst_2) (fun (b : β) => Finset.prod.{u2, u1} γ α _inst_3 (Finset.filter.{u1} α (fun (a : α) => Eq.{succ u3} β (f a) b) (fun (a : α) => _inst_1 (f a) b) s) (fun (a : α) => g a))) (Finset.prod.{u2, u1} γ α _inst_3 s (fun (a : α) => g a))
Case conversion may be inaccurate. Consider using '#align finset.prod_fiberwise Finset.prod_fiberwiseₓ'. -/
@[to_additive]
theorem Finset.prod_fiberwise [DecidableEq β] [Fintype β] [CommMonoid γ] (s : Finset α) (f : α → β)
    (g : α → γ) : (∏ b : β, ∏ a in s.filterₓ fun a => f a = b, g a) = ∏ a in s, g a :=
  Finset.prod_fiberwise_of_maps_to (fun x _ => mem_univ _) _
#align finset.prod_fiberwise Finset.prod_fiberwise
#align finset.sum_fiberwise Finset.sum_fiberwise

/- warning: fintype.prod_fiberwise -> Fintype.prod_fiberwise is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Fintype.{u1} α] [_inst_2 : DecidableEq.{succ u2} β] [_inst_3 : Fintype.{u2} β] [_inst_4 : CommMonoid.{u3} γ] (f : α -> β) (g : α -> γ), Eq.{succ u3} γ (Finset.prod.{u3, u2} γ β _inst_4 (Finset.univ.{u2} β _inst_3) (fun (b : β) => Finset.prod.{u3, u1} γ (Subtype.{succ u1} α (fun (a : α) => Eq.{succ u2} β (f a) b)) _inst_4 (Finset.univ.{u1} (Subtype.{succ u1} α (fun (a : α) => Eq.{succ u2} β (f a) b)) (Subtype.fintype.{u1} α (fun (a : α) => Eq.{succ u2} β (f a) b) (fun (a : α) => _inst_2 (f a) b) _inst_1)) (fun (a : Subtype.{succ u1} α (fun (a : α) => Eq.{succ u2} β (f a) b)) => g ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Subtype.{succ u1} α (fun (a : α) => Eq.{succ u2} β (f a) b)) α (HasLiftT.mk.{succ u1, succ u1} (Subtype.{succ u1} α (fun (a : α) => Eq.{succ u2} β (f a) b)) α (CoeTCₓ.coe.{succ u1, succ u1} (Subtype.{succ u1} α (fun (a : α) => Eq.{succ u2} β (f a) b)) α (coeBase.{succ u1, succ u1} (Subtype.{succ u1} α (fun (a : α) => Eq.{succ u2} β (f a) b)) α (coeSubtype.{succ u1} α (fun (a : α) => Eq.{succ u2} β (f a) b))))) a)))) (Finset.prod.{u3, u1} γ α _inst_4 (Finset.univ.{u1} α _inst_1) (fun (a : α) => g a))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} {γ : Type.{u1}} [_inst_1 : Fintype.{u3} α] [_inst_2 : DecidableEq.{succ u2} β] [_inst_3 : Fintype.{u2} β] [_inst_4 : CommMonoid.{u1} γ] (f : α -> β) (g : α -> γ), Eq.{succ u1} γ (Finset.prod.{u1, u2} γ β _inst_4 (Finset.univ.{u2} β _inst_3) (fun (b : β) => Finset.prod.{u1, u3} γ (Subtype.{succ u3} α (fun (a : α) => Eq.{succ u2} β (f a) b)) _inst_4 (Finset.univ.{u3} (Subtype.{succ u3} α (fun (a : α) => Eq.{succ u2} β (f a) b)) (Subtype.fintype.{u3} α (fun (a : α) => Eq.{succ u2} β (f a) b) (fun (a : α) => _inst_2 (f a) b) _inst_1)) (fun (a : Subtype.{succ u3} α (fun (a : α) => Eq.{succ u2} β (f a) b)) => g (Subtype.val.{succ u3} α (fun (a : α) => Eq.{succ u2} β (f a) b) a)))) (Finset.prod.{u1, u3} γ α _inst_4 (Finset.univ.{u3} α _inst_1) (fun (a : α) => g a))
Case conversion may be inaccurate. Consider using '#align fintype.prod_fiberwise Fintype.prod_fiberwiseₓ'. -/
@[to_additive]
theorem Fintype.prod_fiberwise [Fintype α] [DecidableEq β] [Fintype β] [CommMonoid γ] (f : α → β)
    (g : α → γ) : (∏ b : β, ∏ a : { a // f a = b }, g (a : α)) = ∏ a, g a :=
  by
  rw [← (Equiv.sigmaFiberEquiv f).prod_comp, ← univ_sigma_univ, prod_sigma]
  rfl
#align fintype.prod_fiberwise Fintype.prod_fiberwise
#align fintype.sum_fiberwise Fintype.sum_fiberwise

/- warning: fintype.prod_dite -> Fintype.prod_dite is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Fintype.{u1} α] {p : α -> Prop} [_inst_2 : DecidablePred.{succ u1} α p] [_inst_3 : CommMonoid.{u2} β] (f : forall (a : α), (p a) -> β) (g : forall (a : α), (Not (p a)) -> β), Eq.{succ u2} β (Finset.prod.{u2, u1} β α _inst_3 (Finset.univ.{u1} α _inst_1) (fun (a : α) => dite.{succ u2} β (p a) (_inst_2 a) (f a) (g a))) (HMul.hMul.{u2, u2, u2} β β β (instHMul.{u2} β (MulOneClass.toHasMul.{u2} β (Monoid.toMulOneClass.{u2} β (CommMonoid.toMonoid.{u2} β _inst_3)))) (Finset.prod.{u2, u1} β (Subtype.{succ u1} α (fun (a : α) => p a)) _inst_3 (Finset.univ.{u1} (Subtype.{succ u1} α (fun (a : α) => p a)) (Subtype.fintype.{u1} α (fun (a : α) => p a) (fun (a : α) => _inst_2 a) _inst_1)) (fun (a : Subtype.{succ u1} α (fun (a : α) => p a)) => f ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Subtype.{succ u1} α (fun (a : α) => p a)) α (HasLiftT.mk.{succ u1, succ u1} (Subtype.{succ u1} α (fun (a : α) => p a)) α (CoeTCₓ.coe.{succ u1, succ u1} (Subtype.{succ u1} α (fun (a : α) => p a)) α (coeBase.{succ u1, succ u1} (Subtype.{succ u1} α (fun (a : α) => p a)) α (coeSubtype.{succ u1} α (fun (a : α) => p a))))) a) (Subtype.property.{succ u1} α (fun (a : α) => p a) a))) (Finset.prod.{u2, u1} β (Subtype.{succ u1} α (fun (a : α) => Not (p a))) _inst_3 (Finset.univ.{u1} (Subtype.{succ u1} α (fun (a : α) => Not (p a))) (Subtype.fintype.{u1} α (fun (a : α) => Not (p a)) (fun (a : α) => Not.decidable (p a) (_inst_2 a)) _inst_1)) (fun (a : Subtype.{succ u1} α (fun (a : α) => Not (p a))) => g ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Subtype.{succ u1} α (fun (a : α) => Not (p a))) α (HasLiftT.mk.{succ u1, succ u1} (Subtype.{succ u1} α (fun (a : α) => Not (p a))) α (CoeTCₓ.coe.{succ u1, succ u1} (Subtype.{succ u1} α (fun (a : α) => Not (p a))) α (coeBase.{succ u1, succ u1} (Subtype.{succ u1} α (fun (a : α) => Not (p a))) α (coeSubtype.{succ u1} α (fun (a : α) => Not (p a)))))) a) (Subtype.property.{succ u1} α (fun (a : α) => Not (p a)) a))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Fintype.{u2} α] {p : α -> Prop} [_inst_2 : DecidablePred.{succ u2} α p] [_inst_3 : CommMonoid.{u1} β] (f : forall (a : α), (p a) -> β) (g : forall (a : α), (Not (p a)) -> β), Eq.{succ u1} β (Finset.prod.{u1, u2} β α _inst_3 (Finset.univ.{u2} α _inst_1) (fun (a : α) => dite.{succ u1} β (p a) (_inst_2 a) (f a) (g a))) (HMul.hMul.{u1, u1, u1} β β β (instHMul.{u1} β (MulOneClass.toMul.{u1} β (Monoid.toMulOneClass.{u1} β (CommMonoid.toMonoid.{u1} β _inst_3)))) (Finset.prod.{u1, u2} β (Subtype.{succ u2} α (fun (a : α) => p a)) _inst_3 (Finset.univ.{u2} (Subtype.{succ u2} α (fun (a : α) => p a)) (Subtype.fintype.{u2} α (fun (a : α) => p a) (fun (a : α) => _inst_2 a) _inst_1)) (fun (a : Subtype.{succ u2} α (fun (a : α) => p a)) => f (Subtype.val.{succ u2} α (fun (a : α) => p a) a) (Subtype.property.{succ u2} α (fun (a : α) => p a) a))) (Finset.prod.{u1, u2} β (Subtype.{succ u2} α (fun (a : α) => Not (p a))) _inst_3 (Finset.univ.{u2} (Subtype.{succ u2} α (fun (a : α) => Not (p a))) (Subtype.fintype.{u2} α (fun (a : α) => Not (p a)) (fun (a : α) => instDecidableNot (p a) (_inst_2 a)) _inst_1)) (fun (a : Subtype.{succ u2} α (fun (a : α) => Not (p a))) => g (Subtype.val.{succ u2} α (fun (a : α) => Not (p a)) a) (Subtype.property.{succ u2} α (fun (a : α) => Not (p a)) a))))
Case conversion may be inaccurate. Consider using '#align fintype.prod_dite Fintype.prod_diteₓ'. -/
theorem Fintype.prod_dite [Fintype α] {p : α → Prop} [DecidablePred p] [CommMonoid β]
    (f : ∀ (a : α) (ha : p a), β) (g : ∀ (a : α) (ha : ¬p a), β) :
    (∏ a, dite (p a) (f a) (g a)) = (∏ a : { a // p a }, f a a.2) * ∏ a : { a // ¬p a }, g a a.2 :=
  by
  simp only [prod_dite, attach_eq_univ]
  congr 1
  · convert(Equiv.subtypeEquivRight _).prod_comp fun x : { x // p x } => f x x.2
    simp
  · convert(Equiv.subtypeEquivRight _).prod_comp fun x : { x // ¬p x } => g x x.2
    simp
#align fintype.prod_dite Fintype.prod_dite

section

open Finset

variable {α₁ : Type _} {α₂ : Type _} {M : Type _} [Fintype α₁] [Fintype α₂] [CommMonoid M]

/- warning: fintype.prod_sum_elim -> Fintype.prod_sum_elim is a dubious translation:
lean 3 declaration is
  forall {α₁ : Type.{u1}} {α₂ : Type.{u2}} {M : Type.{u3}} [_inst_1 : Fintype.{u1} α₁] [_inst_2 : Fintype.{u2} α₂] [_inst_3 : CommMonoid.{u3} M] (f : α₁ -> M) (g : α₂ -> M), Eq.{succ u3} M (Finset.prod.{u3, max u1 u2} M (Sum.{u1, u2} α₁ α₂) _inst_3 (Finset.univ.{max u1 u2} (Sum.{u1, u2} α₁ α₂) (Sum.fintype.{u1, u2} α₁ α₂ _inst_1 _inst_2)) (fun (x : Sum.{u1, u2} α₁ α₂) => Sum.elim.{u1, u2, succ u3} α₁ α₂ M f g x)) (HMul.hMul.{u3, u3, u3} M M M (instHMul.{u3} M (MulOneClass.toHasMul.{u3} M (Monoid.toMulOneClass.{u3} M (CommMonoid.toMonoid.{u3} M _inst_3)))) (Finset.prod.{u3, u1} M α₁ _inst_3 (Finset.univ.{u1} α₁ _inst_1) (fun (a₁ : α₁) => f a₁)) (Finset.prod.{u3, u2} M α₂ _inst_3 (Finset.univ.{u2} α₂ _inst_2) (fun (a₂ : α₂) => g a₂)))
but is expected to have type
  forall {α₁ : Type.{u2}} {α₂ : Type.{u1}} {M : Type.{u3}} [_inst_1 : Fintype.{u2} α₁] [_inst_2 : Fintype.{u1} α₂] [_inst_3 : CommMonoid.{u3} M] (f : α₁ -> M) (g : α₂ -> M), Eq.{succ u3} M (Finset.prod.{u3, max u2 u1} M (Sum.{u2, u1} α₁ α₂) _inst_3 (Finset.univ.{max u2 u1} (Sum.{u2, u1} α₁ α₂) (instFintypeSum.{u2, u1} α₁ α₂ _inst_1 _inst_2)) (fun (x : Sum.{u2, u1} α₁ α₂) => Sum.elim.{u2, u1, succ u3} α₁ α₂ M f g x)) (HMul.hMul.{u3, u3, u3} M M M (instHMul.{u3} M (MulOneClass.toMul.{u3} M (Monoid.toMulOneClass.{u3} M (CommMonoid.toMonoid.{u3} M _inst_3)))) (Finset.prod.{u3, u2} M α₁ _inst_3 (Finset.univ.{u2} α₁ _inst_1) (fun (a₁ : α₁) => f a₁)) (Finset.prod.{u3, u1} M α₂ _inst_3 (Finset.univ.{u1} α₂ _inst_2) (fun (a₂ : α₂) => g a₂)))
Case conversion may be inaccurate. Consider using '#align fintype.prod_sum_elim Fintype.prod_sum_elimₓ'. -/
@[to_additive]
theorem Fintype.prod_sum_elim (f : α₁ → M) (g : α₂ → M) :
    (∏ x, Sum.elim f g x) = (∏ a₁, f a₁) * ∏ a₂, g a₂ :=
  prod_disj_sum _ _ _
#align fintype.prod_sum_elim Fintype.prod_sum_elim
#align fintype.sum_sum_elim Fintype.sum_sum_elim

/- warning: fintype.prod_sum_type -> Fintype.prod_sum_type is a dubious translation:
lean 3 declaration is
  forall {α₁ : Type.{u1}} {α₂ : Type.{u2}} {M : Type.{u3}} [_inst_1 : Fintype.{u1} α₁] [_inst_2 : Fintype.{u2} α₂] [_inst_3 : CommMonoid.{u3} M] (f : (Sum.{u1, u2} α₁ α₂) -> M), Eq.{succ u3} M (Finset.prod.{u3, max u1 u2} M (Sum.{u1, u2} α₁ α₂) _inst_3 (Finset.univ.{max u1 u2} (Sum.{u1, u2} α₁ α₂) (Sum.fintype.{u1, u2} α₁ α₂ _inst_1 _inst_2)) (fun (x : Sum.{u1, u2} α₁ α₂) => f x)) (HMul.hMul.{u3, u3, u3} M M M (instHMul.{u3} M (MulOneClass.toHasMul.{u3} M (Monoid.toMulOneClass.{u3} M (CommMonoid.toMonoid.{u3} M _inst_3)))) (Finset.prod.{u3, u1} M α₁ _inst_3 (Finset.univ.{u1} α₁ _inst_1) (fun (a₁ : α₁) => f (Sum.inl.{u1, u2} α₁ α₂ a₁))) (Finset.prod.{u3, u2} M α₂ _inst_3 (Finset.univ.{u2} α₂ _inst_2) (fun (a₂ : α₂) => f (Sum.inr.{u1, u2} α₁ α₂ a₂))))
but is expected to have type
  forall {α₁ : Type.{u3}} {α₂ : Type.{u2}} {M : Type.{u1}} [_inst_1 : Fintype.{u3} α₁] [_inst_2 : Fintype.{u2} α₂] [_inst_3 : CommMonoid.{u1} M] (f : (Sum.{u3, u2} α₁ α₂) -> M), Eq.{succ u1} M (Finset.prod.{u1, max u3 u2} M (Sum.{u3, u2} α₁ α₂) _inst_3 (Finset.univ.{max u3 u2} (Sum.{u3, u2} α₁ α₂) (instFintypeSum.{u3, u2} α₁ α₂ _inst_1 _inst_2)) (fun (x : Sum.{u3, u2} α₁ α₂) => f x)) (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (MulOneClass.toMul.{u1} M (Monoid.toMulOneClass.{u1} M (CommMonoid.toMonoid.{u1} M _inst_3)))) (Finset.prod.{u1, u3} M α₁ _inst_3 (Finset.univ.{u3} α₁ _inst_1) (fun (a₁ : α₁) => f (Sum.inl.{u3, u2} α₁ α₂ a₁))) (Finset.prod.{u1, u2} M α₂ _inst_3 (Finset.univ.{u2} α₂ _inst_2) (fun (a₂ : α₂) => f (Sum.inr.{u3, u2} α₁ α₂ a₂))))
Case conversion may be inaccurate. Consider using '#align fintype.prod_sum_type Fintype.prod_sum_typeₓ'. -/
@[simp, to_additive]
theorem Fintype.prod_sum_type (f : Sum α₁ α₂ → M) :
    (∏ x, f x) = (∏ a₁, f (Sum.inl a₁)) * ∏ a₂, f (Sum.inr a₂) :=
  prod_disj_sum _ _ _
#align fintype.prod_sum_type Fintype.prod_sum_type
#align fintype.sum_sum_type Fintype.sum_sum_type

end


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

! This file was ported from Lean 3 source module tactic.abel
! leanprover-community/mathlib commit ec322deb9ba5aad978f862669053069b7957c31d
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Tactic.NormNum

/-!
# The `abel` tactic

Evaluate expressions in the language of additive, commutative monoids and groups.

-/

namespace Tactic

namespace Abel

/-- The `context` for a call to `abel`.

Stores a few options for this call, and caches some common subexpressions
such as typeclass instances and `0 : α`.
-/
unsafe structure context where
  red : Transparency
  α : expr
  univ : level
  α0 : expr
  is_group : Bool
  inst : expr
#align tactic.abel.context tactic.abel.context

/-- Populate a `context` object for evaluating `e`, up to reducibility level `red`. -/
unsafe def mk_context (red : Transparency) (e : expr) : tactic context := do
  let α ← infer_type e
  let c ← mk_app `` AddCommMonoid [α] >>= mk_instance
  let cg ← try_core (mk_app `` AddCommGroup [α] >>= mk_instance)
  let u ← mk_meta_univ
  infer_type α >>= unify (expr.sort (level.succ u))
  let u ← get_univ_assignment u
  let α0 ← expr.of_nat α 0
  match cg with
    | some cg => return ⟨red, α, u, α0, tt, cg⟩
    | _ => return ⟨red, α, u, α0, ff, c⟩
#align tactic.abel.mk_context tactic.abel.mk_context

/-- Apply the function `n : ∀ {α} [inst : add_whatever α], _` to the
implicit parameters in the context, and the given list of arguments. -/
unsafe def context.app (c : context) (n : Name) (inst : expr) : List expr → expr :=
  (@expr.const true n [c.univ] c.α inst).mk_app
#align tactic.abel.context.app tactic.abel.context.app

/-- Apply the function `n : ∀ {α} [inst α], _` to the implicit parameters in the
context, and the given list of arguments.

Compared to `context.app`, this takes the name of the typeclass, rather than an
inferred typeclass instance.
-/
unsafe def context.mk_app (c : context) (n inst : Name) (l : List expr) : tactic expr := do
  let m ← mk_instance ((expr.const inst [c.univ] : expr) c.α)
  return <| c n m l
#align tactic.abel.context.mk_app tactic.abel.context.mk_app

/-- Add the letter "g" to the end of the name, e.g. turning `term` into `termg`.

This is used to choose between declarations taking `add_comm_monoid` and those
taking `add_comm_group` instances.
-/
unsafe def add_g : Name → Name
  | Name.mk_string s p => Name.mk_string (s ++ "g") p
  | n => n
#align tactic.abel.add_g tactic.abel.add_g

/-- Apply the function `n : ∀ {α} [add_comm_{monoid,group} α]` to the given
list of arguments.

Will use the `add_comm_{monoid,group}` instance that has been cached in the context.
-/
unsafe def context.iapp (c : context) (n : Name) : List expr → expr :=
  c.app (if c.is_group then add_g n else n) c.inst
#align tactic.abel.context.iapp tactic.abel.context.iapp

def term {α} [AddCommMonoid α] (n : ℕ) (x a : α) : α :=
  n • x + a
#align tactic.abel.term Tactic.Abel.term

def termg {α} [AddCommGroup α] (n : ℤ) (x a : α) : α :=
  n • x + a
#align tactic.abel.termg Tactic.Abel.termg

/-- Evaluate a term with coefficient `n`, atom `x` and successor terms `a`. -/
unsafe def context.mk_term (c : context) (n x a : expr) : expr :=
  c.iapp `` term [n, x, a]
#align tactic.abel.context.mk_term tactic.abel.context.mk_term

/-- Interpret an integer as a coefficient to a term. -/
unsafe def context.int_to_expr (c : context) (n : ℤ) : tactic expr :=
  expr.of_int (if c.is_group then q(ℤ) else q(ℕ)) n
#align tactic.abel.context.int_to_expr tactic.abel.context.int_to_expr

unsafe inductive normal_expr : Type
  | zero (e : expr) : normal_expr
  | nterm (e : expr) (n : expr × ℤ) (x : expr) (a : normal_expr) : normal_expr
#align tactic.abel.normal_expr tactic.abel.normal_expr

unsafe def normal_expr.e : normal_expr → expr
  | normal_expr.zero e => e
  | normal_expr.nterm e _ _ _ => e
#align tactic.abel.normal_expr.e tactic.abel.normal_expr.e

unsafe instance : Coe normal_expr expr :=
  ⟨normal_expr.e⟩

unsafe instance : CoeFun normal_expr fun _ => expr → expr :=
  ⟨fun e => ⇑(e : expr)⟩

unsafe def normal_expr.term' (c : context) (n : expr × ℤ) (x : expr) (a : normal_expr) :
    normal_expr :=
  normal_expr.nterm (c.mk_term n.1 x a) n x a
#align tactic.abel.normal_expr.term' tactic.abel.normal_expr.term'

unsafe def normal_expr.zero' (c : context) : normal_expr :=
  normal_expr.zero c.α0
#align tactic.abel.normal_expr.zero' tactic.abel.normal_expr.zero'

unsafe def normal_expr.to_list : normal_expr → List (ℤ × expr)
  | normal_expr.zero _ => []
  | normal_expr.nterm _ (_, n) x a => (n, x) :: a.toList
#align tactic.abel.normal_expr.to_list tactic.abel.normal_expr.to_list

open NormalExpr

unsafe def normal_expr.to_string (e : normal_expr) : String :=
  " + ".intercalate <| (to_list e).map fun ⟨n, e⟩ => toString n ++ " • (" ++ toString e ++ ")"
#align tactic.abel.normal_expr.to_string tactic.abel.normal_expr.to_string

unsafe def normal_expr.pp (e : normal_expr) : tactic format := do
  let l ←
    (to_list e).mapM fun ⟨n, e⟩ => do
        let pe ← pp e
        return (to_fmt n ++ " • (" ++ pe ++ ")")
  return <| format.join <| l ↑" + "
#align tactic.abel.normal_expr.pp tactic.abel.normal_expr.pp

unsafe instance : has_to_tactic_format normal_expr :=
  ⟨normal_expr.pp⟩

unsafe def normal_expr.refl_conv (e : normal_expr) : tactic (normal_expr × expr) := do
  let p ← mk_eq_refl e
  return (e, p)
#align tactic.abel.normal_expr.refl_conv tactic.abel.normal_expr.refl_conv

theorem const_add_term {α} [AddCommMonoid α] (k n x a a') (h : k + a = a') :
    k + @term α _ n x a = term n x a' := by simp [h.symm, term] <;> ac_rfl
#align tactic.abel.const_add_term Tactic.Abel.const_add_term

theorem const_add_termg {α} [AddCommGroup α] (k n x a a') (h : k + a = a') :
    k + @termg α _ n x a = termg n x a' := by simp [h.symm, termg] <;> ac_rfl
#align tactic.abel.const_add_termg Tactic.Abel.const_add_termg

theorem term_add_const {α} [AddCommMonoid α] (n x a k a') (h : a + k = a') :
    @term α _ n x a + k = term n x a' := by simp [h.symm, term, add_assoc]
#align tactic.abel.term_add_const Tactic.Abel.term_add_const

theorem term_add_constg {α} [AddCommGroup α] (n x a k a') (h : a + k = a') :
    @termg α _ n x a + k = termg n x a' := by simp [h.symm, termg, add_assoc]
#align tactic.abel.term_add_constg Tactic.Abel.term_add_constg

theorem term_add_term {α} [AddCommMonoid α] (n₁ x a₁ n₂ a₂ n' a') (h₁ : n₁ + n₂ = n')
    (h₂ : a₁ + a₂ = a') : @term α _ n₁ x a₁ + @term α _ n₂ x a₂ = term n' x a' := by
  simp [h₁.symm, h₂.symm, term, add_nsmul] <;> ac_rfl
#align tactic.abel.term_add_term Tactic.Abel.term_add_term

theorem term_add_termg {α} [AddCommGroup α] (n₁ x a₁ n₂ a₂ n' a') (h₁ : n₁ + n₂ = n')
    (h₂ : a₁ + a₂ = a') : @termg α _ n₁ x a₁ + @termg α _ n₂ x a₂ = termg n' x a' := by
  simp [h₁.symm, h₂.symm, termg, add_zsmul] <;> ac_rfl
#align tactic.abel.term_add_termg Tactic.Abel.term_add_termg

theorem zero_term {α} [AddCommMonoid α] (x a) : @term α _ 0 x a = a := by
  simp [term, zero_nsmul, one_nsmul]
#align tactic.abel.zero_term Tactic.Abel.zero_term

theorem zero_termg {α} [AddCommGroup α] (x a) : @termg α _ 0 x a = a := by simp [termg]
#align tactic.abel.zero_termg Tactic.Abel.zero_termg

unsafe def eval_add (c : context) : normal_expr → normal_expr → tactic (normal_expr × expr)
  | zero _, e₂ => do
    let p ← mk_app `` zero_add [e₂]
    return (e₂, p)
  | e₁, zero _ => do
    let p ← mk_app `` add_zero [e₁]
    return (e₁, p)
  | he₁@(nterm e₁ n₁ x₁ a₁), he₂@(nterm e₂ n₂ x₂ a₂) =>
    (do
        is_def_eq x₁ x₂ c
        let (n', h₁) ← mk_app `` Add.add [n₁.1, n₂.1] >>= norm_num.eval_field
        let (a', h₂) ← eval_add a₁ a₂
        let k := n₁.2 + n₂.2
        let p₁ := c.iapp `` term_add_term [n₁.1, x₁, a₁, n₂.1, a₂, n', a', h₁, h₂]
        if k = 0 then do
            let p ← mk_eq_trans p₁ (c `` zero_term [x₁, a'])
            return (a', p)
          else return (term' c (n', k) x₁ a', p₁)) <|>
      if expr.lex_lt x₁ x₂ then do
        let (a', h) ← eval_add a₁ he₂
        return (term' c n₁ x₁ a', c `` term_add_const [n₁.1, x₁, a₁, e₂, a', h])
      else do
        let (a', h) ← eval_add he₁ a₂
        return (term' c n₂ x₂ a', c `` const_add_term [e₁, n₂.1, x₂, a₂, a', h])
#align tactic.abel.eval_add tactic.abel.eval_add

theorem term_neg {α} [AddCommGroup α] (n x a n' a') (h₁ : -n = n') (h₂ : -a = a') :
    -@termg α _ n x a = termg n' x a' := by simp [h₂.symm, h₁.symm, termg] <;> ac_rfl
#align tactic.abel.term_neg Tactic.Abel.term_neg

unsafe def eval_neg (c : context) : normal_expr → tactic (normal_expr × expr)
  | zero e => do
    let p ← c.mk_app `` neg_zero `` NegZeroClass []
    return (zero' c, p)
  | nterm e n x a => do
    let (n', h₁) ← mk_app `` Neg.neg [n.1] >>= norm_num.eval_field
    let (a', h₂) ← eval_neg a
    return (term' c (n', -n.2) x a', c `` term_neg c [n.1, x, a, n', a', h₁, h₂])
#align tactic.abel.eval_neg tactic.abel.eval_neg

def natSmulInst {α} [AddCommMonoid α] : SMul ℕ α := by infer_instance
#align tactic.abel.nat_smul_inst Tactic.Abel.natSmulInst

def natSmulInstg {α} [AddCommGroup α] : SMul ℕ α := by infer_instance
#align tactic.abel.nat_smul_instg Tactic.Abel.natSmulInstg

def intSmulInstg {α} [AddCommGroup α] : SMul ℤ α := by infer_instance
#align tactic.abel.int_smul_instg Tactic.Abel.intSmulInstg

def smul {α} [AddCommMonoid α] (n : ℕ) (x : α) : α :=
  n • x
#align tactic.abel.smul Tactic.Abel.smul

def smulg {α} [AddCommGroup α] (n : ℤ) (x : α) : α :=
  n • x
#align tactic.abel.smulg Tactic.Abel.smulg

theorem zero_smul {α} [AddCommMonoid α] (c) : smul c (0 : α) = 0 := by simp [smul, nsmul_zero]
#align tactic.abel.zero_smul Tactic.Abel.zero_smul

theorem zero_smulg {α} [AddCommGroup α] (c) : smulg c (0 : α) = 0 := by simp [smulg, zsmul_zero]
#align tactic.abel.zero_smulg Tactic.Abel.zero_smulg

theorem term_smul {α} [AddCommMonoid α] (c n x a n' a') (h₁ : c * n = n') (h₂ : smul c a = a') :
    smul c (@term α _ n x a) = term n' x a' := by
  simp [h₂.symm, h₁.symm, term, smul, nsmul_add, mul_nsmul']
#align tactic.abel.term_smul Tactic.Abel.term_smul

theorem term_smulg {α} [AddCommGroup α] (c n x a n' a') (h₁ : c * n = n') (h₂ : smulg c a = a') :
    smulg c (@termg α _ n x a) = termg n' x a' := by
  simp [h₂.symm, h₁.symm, termg, smulg, zsmul_add, mul_zsmul]
#align tactic.abel.term_smulg Tactic.Abel.term_smulg

unsafe def eval_smul (c : context) (k : expr × ℤ) : normal_expr → tactic (normal_expr × expr)
  | zero _ => return (zero' c, c.iapp `` zero_smul [k.1])
  | nterm e n x a => do
    let (n', h₁) ← mk_app `` Mul.mul [k.1, n.1] >>= norm_num.eval_field
    let (a', h₂) ← eval_smul a
    return (term' c (n', k.2 * n.2) x a', c `` term_smul [k.1, n.1, x, a, n', a', h₁, h₂])
#align tactic.abel.eval_smul tactic.abel.eval_smul

theorem term_atom {α} [AddCommMonoid α] (x : α) : x = term 1 x 0 := by simp [term]
#align tactic.abel.term_atom Tactic.Abel.term_atom

theorem term_atomg {α} [AddCommGroup α] (x : α) : x = termg 1 x 0 := by simp [termg]
#align tactic.abel.term_atomg Tactic.Abel.term_atomg

unsafe def eval_atom (c : context) (e : expr) : tactic (normal_expr × expr) := do
  let n1 ← c.int_to_expr 1
  return (term' c (n1, 1) e (zero' c), c `` term_atom [e])
#align tactic.abel.eval_atom tactic.abel.eval_atom

theorem unfold_sub {α} [SubtractionMonoid α] (a b c : α) (h : a + -b = c) : a - b = c := by
  rw [sub_eq_add_neg, h]
#align tactic.abel.unfold_sub Tactic.Abel.unfold_sub

theorem unfold_smul {α} [AddCommMonoid α] (n) (x y : α) (h : smul n x = y) : n • x = y :=
  h
#align tactic.abel.unfold_smul Tactic.Abel.unfold_smul

theorem unfold_smulg {α} [AddCommGroup α] (n : ℕ) (x y : α) (h : smulg (Int.ofNat n) x = y) :
    (n : ℤ) • x = y :=
  h
#align tactic.abel.unfold_smulg Tactic.Abel.unfold_smulg

theorem unfold_zsmul {α} [AddCommGroup α] (n : ℤ) (x y : α) (h : smulg n x = y) : n • x = y :=
  h
#align tactic.abel.unfold_zsmul Tactic.Abel.unfold_zsmul

theorem subst_into_smul {α} [AddCommMonoid α] (l r tl tr t) (prl : l = tl) (prr : r = tr)
    (prt : @smul α _ tl tr = t) : smul l r = t := by simp [prl, prr, prt]
#align tactic.abel.subst_into_smul Tactic.Abel.subst_into_smul

theorem subst_into_smulg {α} [AddCommGroup α] (l r tl tr t) (prl : l = tl) (prr : r = tr)
    (prt : @smulg α _ tl tr = t) : smulg l r = t := by simp [prl, prr, prt]
#align tactic.abel.subst_into_smulg Tactic.Abel.subst_into_smulg

theorem subst_into_smul_upcast {α} [AddCommGroup α] (l r tl zl tr t) (prl₁ : l = tl)
    (prl₂ : ↑tl = zl) (prr : r = tr) (prt : @smulg α _ zl tr = t) : smul l r = t := by
  simp [← prt, prl₁, ← prl₂, prr, smul, smulg]
#align tactic.abel.subst_into_smul_upcast Tactic.Abel.subst_into_smul_upcast

/-- Normalize a term `orig` of the form `smul e₁ e₂` or `smulg e₁ e₂`.
  Normalized terms use `smul` for monoids and `smulg` for groups,
  so there are actually four cases to handle:
  * Using `smul` in a monoid just simplifies the pieces using `subst_into_smul`
  * Using `smulg` in a group just simplifies the pieces using `subst_into_smulg`
  * Using `smul a b` in a group requires converting `a` from a nat to an int and
    then simplifying `smulg ↑a b` using `subst_into_smul_upcast`
  * Using `smulg` in a monoid is impossible (or at least out of scope),
    because you need a group argument to write a `smulg` term -/
unsafe def eval_smul' (c : context) (eval : expr → tactic (normal_expr × expr)) (is_smulg : Bool)
    (orig e₁ e₂ : expr) : tactic (normal_expr × expr) := do
  let (e₁', p₁) ← norm_num.derive e₁ <|> refl_conv e₁
  match if is_smulg then e₁' else coe <$> e₁' with
    | some n => do
      let (e₂', p₂) ← eval e₂
      if c = is_smulg then do
          let (e', p) ← eval_smul c (e₁', n) e₂'
          return (e', c `` subst_into_smul [e₁, e₂, e₁', e₂', e', p₁, p₂, p])
        else do
          guardb c
          let ic ← mk_instance_cache q(ℤ)
          let nc ← mk_instance_cache q(ℕ)
          let (ic, zl) ← ic n
          let (_, _, _, p₁') ← norm_num.prove_nat_uncast ic nc zl
          let (e', p) ← eval_smul c (zl, n) e₂'
          return (e', c `` subst_into_smul_upcast c [e₁, e₂, e₁', zl, e₂', e', p₁, p₁', p₂, p])
    | none => eval_atom c orig
#align tactic.abel.eval_smul' tactic.abel.eval_smul'

-- failed to format: unknown constant 'term.pseudo.antiquot'
unsafe
  def
    eval
    ( c : context ) : expr → tactic ( normal_expr × expr )
    |
        q( $ ( e₁ ) + $ ( e₂ ) )
        =>
        do
          let ( e₁' , p₁ ) ← eval e₁
            let ( e₂' , p₂ ) ← eval e₂
            let ( e' , p' ) ← eval_add c e₁' e₂'
            let
              p
                ←
                c . mk_app
                  ` ` NormNum.subst_into_add ` ` Add [ e₁ , e₂ , e₁' , e₂' , e' , p₁ , p₂ , p' ]
            return ( e' , p )
      |
        q( $ ( e₁ ) - $ ( e₂ ) )
        =>
        do
          let e₂' ← mk_app ` ` Neg.neg [ e₂ ]
            let e ← mk_app ` ` Add.add [ e₁ , e₂' ]
            let ( e' , p ) ← eval e
            let p' ← c . mk_app ` ` unfold_sub ` ` SubtractionMonoid [ e₁ , e₂ , e' , p ]
            return ( e' , p' )
      |
        q( - $ ( e ) )
        =>
        do
          let ( e₁ , p₁ ) ← eval e
            let ( e₂ , p₂ ) ← eval_neg c e₁
            let p ← c . mk_app ` ` NormNum.subst_into_neg ` ` Neg [ e , e₁ , e₂ , p₁ , p₂ ]
            return ( e₂ , p )
      |
        q( AddMonoid.nsmul $ ( e₁ ) $ ( e₂ ) )
        =>
        do
          let n ← if c . is_group then mk_app ` ` Int.ofNat [ e₁ ] else return e₁
            let ( e' , p ) ← eval <| c . iapp ` ` smul [ n , e₂ ]
            return ( e' , c ` ` unfold_smul [ e₁ , e₂ , e' , p ] )
      |
        q( SubNegMonoid.zsmul $ ( e₁ ) $ ( e₂ ) )
        =>
        do
          guardb c
            let ( e' , p ) ← eval <| c . iapp ` ` smul [ e₁ , e₂ ]
            return ( e' , c ` ` unfold_zsmul c [ e₁ , e₂ , e' , p ] )
      |
        e @ q( @ SMul.smul Nat $ ( α ) $ ( inst ) $ ( e₁ ) $ ( e₂ ) )
        =>
        do
          let inst' := c . iapp ` ` nat_smul_inst [ ]
            condM
              ( succeeds ( is_def_eq inst inst' ) )
                ( eval_smul' c eval ff e e₁ e₂ )
                ( eval_atom c e )
      |
        e @ q( @ SMul.smul Int $ ( α ) $ ( inst ) $ ( e₁ ) $ ( e₂ ) )
        =>
        do
          let tt ← pure c . is_group | eval_atom c e
            let inst' := c . app ` ` int_smul_instg c . inst [ ]
            condM
              ( succeeds ( is_def_eq inst inst' ) )
                ( eval_smul' c eval tt e e₁ e₂ )
                ( eval_atom c e )
      | e @ q( smul $ ( e₁ ) $ ( e₂ ) ) => eval_smul' c eval false e e₁ e₂
      | e @ q( smulg $ ( e₁ ) $ ( e₂ ) ) => eval_smul' c eval true e e₁ e₂
      |
        e @ q( @ Zero.zero _ _ )
        =>
        condM
          ( succeeds ( is_def_eq e c . α0 ) )
            ( mk_eq_refl c . α0 >>= fun p => pure ( zero' c , p ) )
            ( eval_atom c e )
      | e => eval_atom c e
#align tactic.abel.eval tactic.abel.eval

unsafe def eval' (c : context) (e : expr) : tactic (expr × expr) := do
  let (e', p) ← eval c e
  return (e', p)
#align tactic.abel.eval' tactic.abel.eval'

inductive NormalizeMode
  | raw
  | term
  deriving has_reflect
#align tactic.abel.normalize_mode Tactic.Abel.NormalizeMode

instance : Inhabited NormalizeMode :=
  ⟨NormalizeMode.term⟩

unsafe def normalize (red : Transparency) (mode := NormalizeMode.term) (e : expr) :
    tactic (expr × expr) := do
  let pow_lemma ← simp_lemmas.mk.add_simp `` pow_one
  let lemmas :=
    match mode with
    | normalize_mode.term =>
      [`` term.equations._eqn_1, `` termg.equations._eqn_1, `` add_zero, `` one_nsmul, `` one_zsmul,
        `` zsmul_zero]
    | _ => []
  let lemmas ← lemmas.foldlM simp_lemmas.add_simp simp_lemmas.mk
  let (_, e', pr) ←
    ext_simplify_core () { } simp_lemmas.mk (fun _ => failed)
        (fun _ _ _ _ e => do
          let c ← mk_context red e
          let (new_e, pr) ←
            (match mode with
                | normalize_mode.raw => eval' c
                | normalize_mode.term =>
                  trans_conv (eval' c) fun e => do
                    let (e', prf, _) ← simplify lemmas [] e
                    return (e', prf))
                e
          guard ¬new_e == e
          return ((), new_e, some pr, ff))
        (fun _ _ _ _ _ => failed) `eq e
  return (e', pr)
#align tactic.abel.normalize tactic.abel.normalize

end Abel

namespace Interactive

open Tactic.Abel

/- ./././Mathport/Syntax/Translate/Tactic/Mathlib/Core.lean:38:34: unsupported: setup_tactic_parser -/
-- failed to format: unknown constant 'term.pseudo.antiquot'
/--
      Tactic for solving equations in the language of
      *additive*, commutative monoids and groups.
      This version of `abel` fails if the target is not an equality
      that is provable by the axioms of commutative monoids/groups.

      `abel1!` will use a more aggressive reducibility setting to identify atoms.
      This can prove goals that `abel` cannot, but is more expensive.
      -/
    unsafe
  def
    abel1
    ( red : parse ( tk "!" ) ? ) : tactic Unit
    :=
      do
        let q( $ ( e₁ ) = $ ( e₂ ) ) ← target
          let c ← mk_context ( if red . isSome then semireducible else reducible ) e₁
          let ( e₁' , p₁ ) ← eval c e₁
          let ( e₂' , p₂ ) ← eval c e₂
          is_def_eq e₁' e₂'
          let p ← mk_eq_symm p₂ >>= mk_eq_trans p₁
          tactic.exact p
#align tactic.interactive.abel1 tactic.interactive.abel1

unsafe def abel.mode : lean.parser Abel.NormalizeMode :=
  with_desc "(raw|term)?" do
    let mode ← ident ?
    match mode with
      | none => return abel.normalize_mode.term
      | some `term => return abel.normalize_mode.term
      | some `raw => return abel.normalize_mode.raw
      | _ => failed
#align tactic.interactive.abel.mode tactic.interactive.abel.mode

/-- Evaluate expressions in the language of *additive*, commutative monoids and groups.
It attempts to prove the goal outright if there is no `at`
specifier and the target is an equality, but if this
fails, it falls back to rewriting all monoid expressions into a normal form.
If there is an `at` specifier, it rewrites the given target into a normal form.

`abel!` will use a more aggressive reducibility setting to identify atoms.
This can prove goals that `abel` cannot, but is more expensive.
```lean
example {α : Type*} {a b : α} [add_comm_monoid α] : a + (b + a) = a + a + b := by abel
example {α : Type*} {a b : α} [add_comm_group α] : (a + b) - ((b + a) + a) = -a := by abel
example {α : Type*} {a b : α} [add_comm_group α] (hyp : a + a - a = b - b) : a = 0 :=
by { abel at hyp, exact hyp }
example {α : Type*} {a b : α} [add_comm_group α] : (a + b) - (id a + b) = 0 := by abel!
```
-/
unsafe def abel (red : parse (tk "!")?) (SOP : parse abel.mode) (loc : parse location) :
    tactic Unit :=
  (match loc with
    | Interactive.Loc.ns [none] => abel1 red
    | _ => failed) <|>
    do
    let ns ← loc.get_locals
    let red := if red.isSome then semireducible else reducible
    let tt ← tactic.replace_at (normalize red SOP) ns loc.include_goal |
      fail "abel failed to simplify"
    when loc <| try tactic.reflexivity
#align tactic.interactive.abel tactic.interactive.abel

add_tactic_doc
  { Name := "abel"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.abel]
    tags := ["arithmetic", "decision procedure"] }

end Interactive

end Tactic


/-
Copyright (c) 2021 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker

! This file was ported from Lean 3 source module analysis.special_functions.exponential
! leanprover-community/mathlib commit ccf84e0d918668460a34aa19d02fe2e0e2286da0
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.NormedSpace.Exponential
import Mathbin.Analysis.Calculus.FderivAnalytic
import Mathbin.Data.Complex.Exponential
import Mathbin.Topology.MetricSpace.CauSeqFilter

/-!
# Calculus results on exponential in a Banach algebra

In this file, we prove basic properties about the derivative of the exponential map `exp 𝕂`
in a Banach algebra `𝔸` over a field `𝕂`. We keep them separate from the main file
`analysis/normed_space/exponential` in order to minimize dependencies.

## Main results

We prove most result for an arbitrary field `𝕂`, and then specialize to `𝕂 = ℝ` or `𝕂 = ℂ`.

### General case

- `has_strict_fderiv_at_exp_zero_of_radius_pos` : `exp 𝕂` has strict Fréchet-derivative
  `1 : 𝔸 →L[𝕂] 𝔸` at zero, as long as it converges on a neighborhood of zero
  (see also `has_strict_deriv_at_exp_zero_of_radius_pos` for the case `𝔸 = 𝕂`)
- `has_strict_fderiv_at_exp_of_lt_radius` : if `𝕂` has characteristic zero and `𝔸` is commutative,
  then given a point `x` in the disk of convergence, `exp 𝕂` as strict Fréchet-derivative
  `exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸` at x (see also `has_strict_deriv_at_exp_of_lt_radius` for the case
  `𝔸 = 𝕂`)

### `𝕂 = ℝ` or `𝕂 = ℂ`

- `has_strict_fderiv_at_exp_zero` : `exp 𝕂` has strict Fréchet-derivative `1 : 𝔸 →L[𝕂] 𝔸` at zero
  (see also `has_strict_deriv_at_exp_zero` for the case `𝔸 = 𝕂`)
- `has_strict_fderiv_at_exp` : if `𝔸` is commutative, then given any point `x`, `exp 𝕂` as strict
  Fréchet-derivative `exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸` at x (see also `has_strict_deriv_at_exp` for the
  case `𝔸 = 𝕂`)

### Compatibilty with `real.exp` and `complex.exp`

- `complex.exp_eq_exp_ℂ` : `complex.exp = exp ℂ ℂ`
- `real.exp_eq_exp_ℝ` : `real.exp = exp ℝ ℝ`

-/

open Filter IsROrC ContinuousMultilinearMap NormedField Asymptotics

open Nat Topology BigOperators ENNReal

section AnyFieldAnyAlgebra

variable {𝕂 𝔸 : Type _} [NontriviallyNormedField 𝕂] [NormedRing 𝔸] [NormedAlgebra 𝕂 𝔸]
  [CompleteSpace 𝔸]

/-- The exponential in a Banach-algebra `𝔸` over a normed field `𝕂` has strict Fréchet-derivative
`1 : 𝔸 →L[𝕂] 𝔸` at zero, as long as it converges on a neighborhood of zero. -/
theorem hasStrictFderivAt_exp_zero_of_radius_pos (h : 0 < (expSeries 𝕂 𝔸).radius) :
    HasStrictFderivAt (exp 𝕂) (1 : 𝔸 →L[𝕂] 𝔸) 0 :=
  by
  convert(hasFpowerSeriesAtExpZeroOfRadiusPos h).HasStrictFderivAt
  ext x
  change x = expSeries 𝕂 𝔸 1 fun _ => x
  simp [expSeries_apply_eq]
#align has_strict_fderiv_at_exp_zero_of_radius_pos hasStrictFderivAt_exp_zero_of_radius_pos

/-- The exponential in a Banach-algebra `𝔸` over a normed field `𝕂` has Fréchet-derivative
`1 : 𝔸 →L[𝕂] 𝔸` at zero, as long as it converges on a neighborhood of zero. -/
theorem hasFderivAt_exp_zero_of_radius_pos (h : 0 < (expSeries 𝕂 𝔸).radius) :
    HasFderivAt (exp 𝕂) (1 : 𝔸 →L[𝕂] 𝔸) 0 :=
  (hasStrictFderivAt_exp_zero_of_radius_pos h).HasFderivAt
#align has_fderiv_at_exp_zero_of_radius_pos hasFderivAt_exp_zero_of_radius_pos

end AnyFieldAnyAlgebra

section AnyFieldCommAlgebra

variable {𝕂 𝔸 : Type _} [NontriviallyNormedField 𝕂] [NormedCommRing 𝔸] [NormedAlgebra 𝕂 𝔸]
  [CompleteSpace 𝔸]

/-- The exponential map in a commutative Banach-algebra `𝔸` over a normed field `𝕂` of
characteristic zero has Fréchet-derivative `exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸` at any point `x` in the
disk of convergence. -/
theorem hasFderivAt_exp_of_mem_ball [CharZero 𝕂] {x : 𝔸}
    (hx : x ∈ EMetric.ball (0 : 𝔸) (expSeries 𝕂 𝔸).radius) :
    HasFderivAt (exp 𝕂) (exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸) x :=
  by
  have hpos : 0 < (expSeries 𝕂 𝔸).radius := (zero_le _).trans_lt hx
  rw [hasFderivAt_iff_isOCat_nhds_zero]
  suffices
    (fun h => exp 𝕂 x * (exp 𝕂 (0 + h) - exp 𝕂 0 - ContinuousLinearMap.id 𝕂 𝔸 h)) =ᶠ[𝓝 0] fun h =>
      exp 𝕂 (x + h) - exp 𝕂 x - exp 𝕂 x • ContinuousLinearMap.id 𝕂 𝔸 h
    by
    refine' (is_o.const_mul_left _ _).congr' this (eventually_eq.refl _ _)
    rw [← hasFderivAt_iff_isOCat_nhds_zero]
    exact hasFderivAt_exp_zero_of_radius_pos hpos
  have : ∀ᶠ h in 𝓝 (0 : 𝔸), h ∈ EMetric.ball (0 : 𝔸) (expSeries 𝕂 𝔸).radius :=
    EMetric.ball_mem_nhds _ hpos
  filter_upwards [this]with _ hh
  rw [exp_add_of_mem_ball hx hh, exp_zero, zero_add, ContinuousLinearMap.id_apply, smul_eq_mul]
  ring
#align has_fderiv_at_exp_of_mem_ball hasFderivAt_exp_of_mem_ball

/-- The exponential map in a commutative Banach-algebra `𝔸` over a normed field `𝕂` of
characteristic zero has strict Fréchet-derivative `exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸` at any point `x` in
the disk of convergence. -/
theorem hasStrictFderivAt_exp_of_mem_ball [CharZero 𝕂] {x : 𝔸}
    (hx : x ∈ EMetric.ball (0 : 𝔸) (expSeries 𝕂 𝔸).radius) :
    HasStrictFderivAt (exp 𝕂) (exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸) x :=
  let ⟨p, hp⟩ := analyticAt_exp_of_mem_ball x hx
  hp.HasFderivAt.unique (hasFderivAt_exp_of_mem_ball hx) ▸ hp.HasStrictFderivAt
#align has_strict_fderiv_at_exp_of_mem_ball hasStrictFderivAt_exp_of_mem_ball

end AnyFieldCommAlgebra

section deriv

variable {𝕂 : Type _} [NontriviallyNormedField 𝕂] [CompleteSpace 𝕂]

/-- The exponential map in a complete normed field `𝕂` of characteristic zero has strict derivative
`exp 𝕂 x` at any point `x` in the disk of convergence. -/
theorem hasStrictDerivAt_exp_of_mem_ball [CharZero 𝕂] {x : 𝕂}
    (hx : x ∈ EMetric.ball (0 : 𝕂) (expSeries 𝕂 𝕂).radius) : HasStrictDerivAt (exp 𝕂) (exp 𝕂 x) x :=
  by simpa using (hasStrictFderivAt_exp_of_mem_ball hx).HasStrictDerivAt
#align has_strict_deriv_at_exp_of_mem_ball hasStrictDerivAt_exp_of_mem_ball

/-- The exponential map in a complete normed field `𝕂` of characteristic zero has derivative
`exp 𝕂 x` at any point `x` in the disk of convergence. -/
theorem hasDerivAt_exp_of_mem_ball [CharZero 𝕂] {x : 𝕂}
    (hx : x ∈ EMetric.ball (0 : 𝕂) (expSeries 𝕂 𝕂).radius) : HasDerivAt (exp 𝕂) (exp 𝕂 x) x :=
  (hasStrictDerivAt_exp_of_mem_ball hx).HasDerivAt
#align has_deriv_at_exp_of_mem_ball hasDerivAt_exp_of_mem_ball

/-- The exponential map in a complete normed field `𝕂` of characteristic zero has strict derivative
`1` at zero, as long as it converges on a neighborhood of zero. -/
theorem hasStrictDerivAt_exp_zero_of_radius_pos (h : 0 < (expSeries 𝕂 𝕂).radius) :
    HasStrictDerivAt (exp 𝕂) (1 : 𝕂) 0 :=
  (hasStrictFderivAt_exp_zero_of_radius_pos h).HasStrictDerivAt
#align has_strict_deriv_at_exp_zero_of_radius_pos hasStrictDerivAt_exp_zero_of_radius_pos

/-- The exponential map in a complete normed field `𝕂` of characteristic zero has derivative
`1` at zero, as long as it converges on a neighborhood of zero. -/
theorem hasDerivAt_exp_zero_of_radius_pos (h : 0 < (expSeries 𝕂 𝕂).radius) :
    HasDerivAt (exp 𝕂) (1 : 𝕂) 0 :=
  (hasStrictDerivAt_exp_zero_of_radius_pos h).HasDerivAt
#align has_deriv_at_exp_zero_of_radius_pos hasDerivAt_exp_zero_of_radius_pos

end deriv

section IsROrCAnyAlgebra

variable {𝕂 𝔸 : Type _} [IsROrC 𝕂] [NormedRing 𝔸] [NormedAlgebra 𝕂 𝔸] [CompleteSpace 𝔸]

/-- The exponential in a Banach-algebra `𝔸` over `𝕂 = ℝ` or `𝕂 = ℂ` has strict Fréchet-derivative
`1 : 𝔸 →L[𝕂] 𝔸` at zero. -/
theorem hasStrictFderivAt_exp_zero : HasStrictFderivAt (exp 𝕂) (1 : 𝔸 →L[𝕂] 𝔸) 0 :=
  hasStrictFderivAt_exp_zero_of_radius_pos (expSeries_radius_pos 𝕂 𝔸)
#align has_strict_fderiv_at_exp_zero hasStrictFderivAt_exp_zero

/-- The exponential in a Banach-algebra `𝔸` over `𝕂 = ℝ` or `𝕂 = ℂ` has Fréchet-derivative
`1 : 𝔸 →L[𝕂] 𝔸` at zero. -/
theorem hasFderivAt_exp_zero : HasFderivAt (exp 𝕂) (1 : 𝔸 →L[𝕂] 𝔸) 0 :=
  hasStrictFderivAt_exp_zero.HasFderivAt
#align has_fderiv_at_exp_zero hasFderivAt_exp_zero

end IsROrCAnyAlgebra

section IsROrCCommAlgebra

variable {𝕂 𝔸 : Type _} [IsROrC 𝕂] [NormedCommRing 𝔸] [NormedAlgebra 𝕂 𝔸] [CompleteSpace 𝔸]

/-- The exponential map in a commutative Banach-algebra `𝔸` over `𝕂 = ℝ` or `𝕂 = ℂ` has strict
Fréchet-derivative `exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸` at any point `x`. -/
theorem hasStrictFderivAt_exp {x : 𝔸} : HasStrictFderivAt (exp 𝕂) (exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸) x :=
  hasStrictFderivAt_exp_of_mem_ball ((expSeries_radius_eq_top 𝕂 𝔸).symm ▸ edist_lt_top _ _)
#align has_strict_fderiv_at_exp hasStrictFderivAt_exp

/-- The exponential map in a commutative Banach-algebra `𝔸` over `𝕂 = ℝ` or `𝕂 = ℂ` has
Fréchet-derivative `exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸` at any point `x`. -/
theorem hasFderivAt_exp {x : 𝔸} : HasFderivAt (exp 𝕂) (exp 𝕂 x • 1 : 𝔸 →L[𝕂] 𝔸) x :=
  hasStrictFderivAt_exp.HasFderivAt
#align has_fderiv_at_exp hasFderivAt_exp

end IsROrCCommAlgebra

section DerivROrC

variable {𝕂 : Type _} [IsROrC 𝕂]

/-- The exponential map in `𝕂 = ℝ` or `𝕂 = ℂ` has strict derivative `exp 𝕂 x` at any point
`x`. -/
theorem hasStrictDerivAt_exp {x : 𝕂} : HasStrictDerivAt (exp 𝕂) (exp 𝕂 x) x :=
  hasStrictDerivAt_exp_of_mem_ball ((expSeries_radius_eq_top 𝕂 𝕂).symm ▸ edist_lt_top _ _)
#align has_strict_deriv_at_exp hasStrictDerivAt_exp

/-- The exponential map in `𝕂 = ℝ` or `𝕂 = ℂ` has derivative `exp 𝕂 x` at any point `x`. -/
theorem hasDerivAt_exp {x : 𝕂} : HasDerivAt (exp 𝕂) (exp 𝕂 x) x :=
  hasStrictDerivAt_exp.HasDerivAt
#align has_deriv_at_exp hasDerivAt_exp

/-- The exponential map in `𝕂 = ℝ` or `𝕂 = ℂ` has strict derivative `1` at zero. -/
theorem hasStrictDerivAt_exp_zero : HasStrictDerivAt (exp 𝕂) (1 : 𝕂) 0 :=
  hasStrictDerivAt_exp_zero_of_radius_pos (expSeries_radius_pos 𝕂 𝕂)
#align has_strict_deriv_at_exp_zero hasStrictDerivAt_exp_zero

/-- The exponential map in `𝕂 = ℝ` or `𝕂 = ℂ` has derivative `1` at zero. -/
theorem hasDerivAt_exp_zero : HasDerivAt (exp 𝕂) (1 : 𝕂) 0 :=
  hasStrictDerivAt_exp_zero.HasDerivAt
#align has_deriv_at_exp_zero hasDerivAt_exp_zero

end DerivROrC

theorem Complex.exp_eq_exp_ℂ : Complex.exp = exp ℂ :=
  by
  refine' funext fun x => _
  rw [Complex.exp, exp_eq_tsum_div]
  exact
    tendsto_nhds_unique x.exp'.tendsto_limit (exp_series_div_summable ℝ x).HasSum.tendsto_sum_nat
#align complex.exp_eq_exp_ℂ Complex.exp_eq_exp_ℂ

theorem Real.exp_eq_exp_ℝ : Real.exp = exp ℝ :=
  by
  ext x
  exact_mod_cast congr_fun Complex.exp_eq_exp_ℂ x
#align real.exp_eq_exp_ℝ Real.exp_eq_exp_ℝ