import .fin_seq

inductive prim_rec: nat -> Type
| zero: prim_rec 0
| succ: prim_rec 1
| proj: forall {n}, fin n -> prim_rec n
| comp: forall {k m}, prim_rec k -> (fin k -> prim_rec m) -> prim_rec m
| prec: forall {k}, prim_rec k -> prim_rec (k + 2) -> prim_rec (k + 1)

namespace prim_rec

def eval : forall {k}, prim_rec k -> (fin k -> nat) -> nat
| 0 prim_rec.zero _arg := 0
| 1 prim_rec.succ arg := arg 0 + 1
| m (prim_rec.proj idx) arg := arg idx
| m (@prim_rec.comp k .(m) (f: prim_rec k) g) arg
  := eval f (fun i, eval (g i) arg)
| .(_) (@prim_rec.prec k f g) arg :=
  let h :=
  fun (v: nat) (arg: fin k -> nat),
  @nat.rec (fun _, nat) (eval f arg)
    (fun (v': nat) (prev: nat), eval g (fin_seq.push prev (fin_seq.push v' arg))) v in
  let ⟨x, y⟩ := fin_seq.pop arg in
  h x y

def sum_of_fin: forall {n}, fin_seq n nat -> nat
| 0 _ := 0
| (n' + 1) ls := sum_of_fin (fun i, ls (fin.succ i)) + ls 0

def size_of: forall {k}, prim_rec k -> nat
| 0 prim_rec.zero := 1
| 1 prim_rec.succ := 1
| m (prim_rec.proj idx) := 1
| m (@prim_rec.comp k .(m) (f: prim_rec k) g)
  := size_of f + sum_of_fin (λi, size_of (g i)) + 1
| .(_) (@prim_rec.prec k f g) :=
  size_of f + size_of g + 1

@[simp] lemma zero.is_zero: eval zero fin_seq.empty = 0 :=
begin
  reflexivity,
end

lemma zero.size: size_of zero = 1 := rfl

def const: nat -> prim_rec 0
| 0 := zero
| (k + 1) := comp succ ({const k}: fin_seq _ _)

@[simp] def const.is_const: forall x, eval (const x) fin_seq.empty = x :=
begin
  intro x,
  induction x with x' ih,
  { reflexivity },
  {
    simp [eval, const],
    rw ih,
  },
end

lemma const.size: forall x, size_of (const x) = x * 2 + 1 :=
begin
  intro x,
  induction x with x' ih,
  reflexivity,
  simp [size_of, const],
  change 1 + sum_of_fin ({size_of (const x')}) + 1 = x'.succ * 2 + 1,
  rw ih,
  simp [sum_of_fin],
  rw nat.zero_add,
  rw nat.add_comm 1 _,
  rw nat.succ_mul x',
end

@[simp] lemma succ.is_succ: forall x, eval prim_rec.succ ({x}: fin_seq _ _) = x + 1 :=
fun x,
calc
  eval prim_rec.succ ({x}: fin_seq _ _) = x + 1 : by unfold eval; reflexivity

lemma succ.size: size_of succ = 1 := rfl

lemma uncurry_1: forall v: nat, fin_seq.pop {v} = ⟨v, fin_seq.empty⟩ :=
  fin_seq.pop_1

lemma curry_at_0: forall k v (rest: fin k -> nat), fin_seq.push v rest 0 = v :=
begin
  apply fin_seq.push_at_0
end

lemma curry_at_succ: forall k v (rest: fin k -> nat) (i: fin k), fin_seq.push v rest (fin.succ i) = rest i
  :=
begin
  apply fin_seq.push_at_succ,
end

def id: prim_rec 1 := proj 0

@[simp] lemma id.is_id : forall x, eval id ({x}: fin_seq 1 nat) = x :=
begin
  intro x,
  reflexivity,
end

lemma id.size: size_of id = 1 := rfl

def pred: prim_rec 1 := prec zero (proj 1)

@[simp] lemma pred.is_pred: forall x, eval pred ({x}: fin_seq _ _) = x - 1 :=
begin
  intro x,
  cases x with x',
  { reflexivity },
  { reflexivity },
end

lemma pred.size: size_of pred = 3 := rfl

def is_zero: prim_rec 1 := prec (const 1) (comp zero (fun _, proj 0))

@[simp] lemma is_zero.is_is_zero: forall x, eval is_zero ({x}: fin_seq 1 nat) = if x = 0 then 1 else 0 :=
begin
  intro x,
  cases x with x',
  { reflexivity },
  { reflexivity },
end

@[simp] lemma is_zero.is_is_zero_bulk: forall (xs: fin_seq 1 nat), eval is_zero xs = if xs 0 = 0 then 1 else 0 :=
begin
  intro xs,
  rw <- fin_seq.length_1_of_singleton xs,
  rw is_zero.is_is_zero,
  simp,
end

lemma is_zero.size: size_of is_zero = 6 := rfl

-- S(x) + y = S(x + y)
def add: prim_rec 2 := prec id (comp succ ({proj 0}: fin_seq _ _))
-- sub_rev(x, y) = y - x
def sub_rev: prim_rec 2 := prec id (comp pred ({proj 0}: fin_seq _ _))
def sub: prim_rec 2 := comp sub_rev (fin_seq.push (proj 1) {proj 0})
def mul: prim_rec 2 := prec (comp zero (fun _, succ)) (comp add (fin_seq.push (proj 0) {proj 2}))

-- logic
def le: prim_rec 2 := comp is_zero ({sub}: fin_seq _ _)
def ge: prim_rec 2 := comp is_zero ({sub_rev}: fin_seq _ _)
-- and(x, y) = if x != 0 then y else 0
def and: prim_rec 2 := prec (comp zero (fun _, proj 0)) (proj 2)
-- or(x, y) = if x != 0 then x else y
def or: prim_rec 2 := prec id (comp succ ({proj 1}: fin_seq _ _))
def not: prim_rec 1 := is_zero
-- cond(x, y, z) = if x != 0 then y else z
def cond: prim_rec 3 := prec (proj 1) (proj 2)
-- def eq: prim_rec 2 := comp and (fin_seq.push le {ge})
def eq: prim_rec 2 := comp is_zero ({comp add (fin_seq.push sub {sub_rev})}: fin_seq _ _)

@[simp] lemma add.is_add: forall x y, eval add (fin_seq.push x {y}) = x + y :=
begin
  intro x,
  induction x with x' ih,
  {
    intro y,
    rw nat.add_comm,
    rw nat.add_zero,
    simp [add, eval],
    reflexivity,
  },
  {
    intro y,
    change add.eval (fin_seq.push x' {y}) + 1 = x'.succ + y,
    rw ih,
    rw nat.add_comm x'.succ y,
    rw nat.add_succ y x',
    rw nat.add_comm y x',
  },
end

@[simp] lemma add.is_add_bulk: forall xy, eval add xy = xy 0 + xy 1 :=
begin
  intro xy,
  rw <- fin_seq.length_2_of_insert_of_singleton xy,
  apply add.is_add,
end

lemma add.size: size_of add = 5 := rfl

@[simp] lemma sub_rev.is_sub_rev: forall x y, eval sub_rev (fin_seq.push x {y}) = y - x :=
begin
  intro x,
  induction x with x' ih,
  {
    intro y,
    simp [sub_rev, eval],
    reflexivity,
  },
  {
    intro y,
    change pred.eval ({sub_rev.eval (fin_seq.push x' {y})}: fin_seq _ _) = y - x'.succ,
    simp,
    rw ih,
    reflexivity,
  },
end

lemma sub_rev.size: size_of sub_rev = 7 := rfl

@[simp] lemma sub_rev.is_sub_rev_bulk: forall xy, eval sub_rev xy = xy 1 - xy 0 :=
begin
  intro xy,
  rw <- fin_seq.length_2_of_insert_of_singleton xy,
  apply sub_rev.is_sub_rev,
end

@[simp] lemma sub.is_sub: forall x y, eval sub (fin_seq.push x {y}) = x - y :=
begin
  simp only [eval, sub],
  simp [sub_rev.is_sub_rev_bulk, eval],
  intros x y,
  reflexivity,
end

lemma sub.size: size_of sub = 10 := rfl

@[simp] lemma mul.is_mul: forall x y, eval mul (fin_seq.push x {y}) = x * y :=
begin
  intro x,
  induction x with x' ih; intro y,
  {
    change 0 = 0 * y,
    rw nat.zero_mul,
  },
  {
    simp only [mul, eval],
    change add.eval (fin_seq.push (mul.eval (fin_seq.push x' {y})) ({y}: fin_seq _ _)) = x'.succ * y,
    rw ih,
    rw add.is_add,
    rw nat.succ_mul,
  },
end

lemma mul.size: size_of sub = 10 := rfl

@[simp] lemma le.is_le: forall x y, eval le (fin_seq.push x {y}) = if x <= y then 1 else 0 :=
begin
  simp [le, eval],
  intros x y,
  apply if_congr,
  rw @nat.sub_eq_zero_iff_le x y,
  simp,
end

lemma le.size: size_of le = 17 := rfl

@[simp] lemma ge.is_ge: forall x y, eval ge (fin_seq.push x {y}) = if x >= y then 1 else 0 :=
begin
  simp [ge, eval],
  intros x y,
  apply if_congr,
  change y - x = 0 <-> x >= y,
  rw nat.sub_eq_zero_iff_le,
  simp,
end

lemma ge.size: size_of ge = 14 := rfl

@[simp] lemma and.is_and: forall x y, eval and (fin_seq.push x {y}) = if x ≠ 0 then y else 0 :=
begin
  intros x y,
  cases x with x'; reflexivity,
end

lemma and.size: size_of and = 4 := rfl

@[simp] lemma or.is_or: forall x y, eval or (fin_seq.push x {y}) = if x ≠ 0 then x else y :=
begin
  intros x y,
  cases x with x'; reflexivity,
end

lemma or.size: size_of or = 5 := rfl

@[simp] lemma not.is_not: forall x, eval not ({x}: fin_seq _ _) = if x ≠ 0 then 0 else 1 :=
begin
  intros x,
  cases x with x'; reflexivity,
end

lemma not.size: size_of not = 6 := rfl

@[simp] lemma cond.is_cond: forall (x y z: nat), eval cond (fin_seq.push x (fin_seq.push y ({z}: fin_seq 1 nat))) = if x ≠ 0 then y else z :=
begin
  intros x y z,
  cases x with x'; reflexivity,
end

lemma cond.size: size_of cond = 3 := rfl

@[simp] lemma eq.is_eq: forall x y, eval eq (fin_seq.push x {y}) = if x = y then 1 else 0 :=
begin
  intros x y,
  simp [eval, eq],
  apply if_congr,
  change (x - y) + eval sub_rev (fin_seq.push x {y}) = 0 <-> x = y,
  rw sub_rev.is_sub_rev,
  change (x - y) + (y - x) = 0 <-> x = y,
  split,
  {
    intro h,
    have := nat.eq_zero_of_add_eq_zero h,
    cases this with h1 h2,
    apply nat.le_antisymm,
    apply nat.le_of_sub_eq_zero h1,
    apply nat.le_of_sub_eq_zero h2,
  },
  {
    intro h,
    cases h,
    rw nat.sub_self,
  },
  simp,
end

lemma eq.size: size_of eq = 30 := rfl

end prim_rec


/-
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.big_operators.pi
! 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.Card
import Mathbin.Algebra.Group.Prod
import Mathbin.Algebra.BigOperators.Basic
import Mathbin.Algebra.Ring.Pi

/-!
# Big operators for Pi Types

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

This file contains theorems relevant to big operators in binary and arbitrary product
of monoids and groups
-/

open BigOperators

namespace Pi

/- warning: pi.list_prod_apply -> Pi.list_prod_apply is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} [_inst_1 : forall (a : α), Monoid.{u2} (β a)] (a : α) (l : List.{max u1 u2} (forall (a : α), β a)), Eq.{succ u2} (β a) (List.prod.{max u1 u2} (forall (a : α), β a) (Pi.instMul.{u1, u2} α (fun (a : α) => β a) (fun (i : α) => MulOneClass.toHasMul.{u2} (β i) (Monoid.toMulOneClass.{u2} (β i) (_inst_1 i)))) (Pi.instOne.{u1, u2} α (fun (a : α) => β a) (fun (i : α) => MulOneClass.toHasOne.{u2} (β i) (Monoid.toMulOneClass.{u2} (β i) (_inst_1 i)))) l a) (List.prod.{u2} (β a) (MulOneClass.toHasMul.{u2} (β a) (Monoid.toMulOneClass.{u2} (β a) (_inst_1 a))) (MulOneClass.toHasOne.{u2} (β a) (Monoid.toMulOneClass.{u2} (β a) (_inst_1 a))) (List.map.{max u1 u2, u2} (forall (a : α), β a) (β a) (fun (f : forall (a : α), β a) => f a) l))
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} [_inst_1 : forall (a : α), Monoid.{u1} (β a)] (a : α) (l : List.{max u2 u1} (forall (a : α), β a)), Eq.{succ u1} (β a) (List.prod.{max u2 u1} (forall (a : α), β a) (Pi.instMul.{u2, u1} α (fun (a : α) => β a) (fun (i : α) => MulOneClass.toMul.{u1} (β i) (Monoid.toMulOneClass.{u1} (β i) (_inst_1 i)))) (Pi.instOne.{u2, u1} α (fun (a : α) => β a) (fun (i : α) => Monoid.toOne.{u1} (β i) (_inst_1 i))) l a) (List.prod.{u1} (β a) (MulOneClass.toMul.{u1} (β a) (Monoid.toMulOneClass.{u1} (β a) (_inst_1 a))) (Monoid.toOne.{u1} (β a) (_inst_1 a)) (List.map.{max u2 u1, u1} (forall (a : α), β a) (β a) (fun (f : forall (a : α), β a) => f a) l))
Case conversion may be inaccurate. Consider using '#align pi.list_prod_apply Pi.list_prod_applyₓ'. -/
@[to_additive]
theorem list_prod_apply {α : Type _} {β : α → Type _} [∀ a, Monoid (β a)] (a : α)
    (l : List (∀ a, β a)) : l.Prod a = (l.map fun f : ∀ a, β a => f a).Prod :=
  (evalMonoidHom β a).map_list_prod _
#align pi.list_prod_apply Pi.list_prod_apply
#align pi.list_sum_apply Pi.list_sum_apply

/- warning: pi.multiset_prod_apply -> Pi.multiset_prod_apply is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} [_inst_1 : forall (a : α), CommMonoid.{u2} (β a)] (a : α) (s : Multiset.{max u1 u2} (forall (a : α), β a)), Eq.{succ u2} (β a) (Multiset.prod.{max u1 u2} (forall (a : α), β a) (Pi.commMonoid.{u1, u2} α (fun (a : α) => β a) (fun (i : α) => _inst_1 i)) s a) (Multiset.prod.{u2} (β a) (_inst_1 a) (Multiset.map.{max u1 u2, u2} (forall (a : α), β a) (β a) (fun (f : forall (a : α), β a) => f a) s))
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} [_inst_1 : forall (a : α), CommMonoid.{u1} (β a)] (a : α) (s : Multiset.{max u2 u1} (forall (a : α), β a)), Eq.{succ u1} (β a) (Multiset.prod.{max u2 u1} (forall (a : α), β a) (Pi.commMonoid.{u2, u1} α (fun (a : α) => β a) (fun (i : α) => _inst_1 i)) s a) (Multiset.prod.{u1} (β a) (_inst_1 a) (Multiset.map.{max u2 u1, u1} (forall (a : α), β a) (β a) (fun (f : forall (a : α), β a) => f a) s))
Case conversion may be inaccurate. Consider using '#align pi.multiset_prod_apply Pi.multiset_prod_applyₓ'. -/
@[to_additive]
theorem multiset_prod_apply {α : Type _} {β : α → Type _} [∀ a, CommMonoid (β a)] (a : α)
    (s : Multiset (∀ a, β a)) : s.Prod a = (s.map fun f : ∀ a, β a => f a).Prod :=
  (evalMonoidHom β a).map_multiset_prod _
#align pi.multiset_prod_apply Pi.multiset_prod_apply
#align pi.multiset_sum_apply Pi.multiset_sum_apply

end Pi

/- warning: finset.prod_apply -> Finset.prod_apply is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {γ : Type.{u3}} [_inst_1 : forall (a : α), CommMonoid.{u2} (β a)] (a : α) (s : Finset.{u3} γ) (g : γ -> (forall (a : α), β a)), Eq.{succ u2} (β a) (Finset.prod.{max u1 u2, u3} (forall (a : α), β a) γ (Pi.commMonoid.{u1, u2} α (fun (a : α) => β a) (fun (i : α) => _inst_1 i)) s (fun (c : γ) => g c) a) (Finset.prod.{u2, u3} (β a) γ (_inst_1 a) s (fun (c : γ) => g c a))
but is expected to have type
  forall {α : Type.{u3}} {β : α -> Type.{u2}} {γ : Type.{u1}} [_inst_1 : forall (a : α), CommMonoid.{u2} (β a)] (a : α) (s : Finset.{u1} γ) (g : γ -> (forall (a : α), β a)), Eq.{succ u2} (β a) (Finset.prod.{max u3 u2, u1} (forall (a : α), β a) γ (Pi.commMonoid.{u3, u2} α (fun (a : α) => β a) (fun (i : α) => _inst_1 i)) s (fun (c : γ) => g c) a) (Finset.prod.{u2, u1} (β a) γ (_inst_1 a) s (fun (c : γ) => g c a))
Case conversion may be inaccurate. Consider using '#align finset.prod_apply Finset.prod_applyₓ'. -/
@[simp, to_additive]
theorem Finset.prod_apply {α : Type _} {β : α → Type _} {γ} [∀ a, CommMonoid (β a)] (a : α)
    (s : Finset γ) (g : γ → ∀ a, β a) : (∏ c in s, g c) a = ∏ c in s, g c a :=
  (Pi.evalMonoidHom β a).map_prod _ _
#align finset.prod_apply Finset.prod_apply
#align finset.sum_apply Finset.sum_apply

/- warning: finset.prod_fn -> Finset.prod_fn is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {γ : Type.{u3}} [_inst_1 : forall (a : α), CommMonoid.{u2} (β a)] (s : Finset.{u3} γ) (g : γ -> (forall (a : α), β a)), Eq.{succ (max u1 u2)} (forall (a : α), β a) (Finset.prod.{max u1 u2, u3} (forall (a : α), β a) γ (Pi.commMonoid.{u1, u2} α (fun (a : α) => β a) (fun (i : α) => _inst_1 i)) s (fun (c : γ) => g c)) (fun (a : α) => Finset.prod.{u2, u3} (β a) γ (_inst_1 a) s (fun (c : γ) => g c a))
but is expected to have type
  forall {α : Type.{u3}} {β : α -> Type.{u2}} {γ : Type.{u1}} [_inst_1 : forall (a : α), CommMonoid.{u2} (β a)] (s : Finset.{u1} γ) (g : γ -> (forall (a : α), β a)), Eq.{max (succ u3) (succ u2)} (forall (a : α), β a) (Finset.prod.{max u3 u2, u1} (forall (a : α), β a) γ (Pi.commMonoid.{u3, u2} α (fun (a : α) => β a) (fun (i : α) => _inst_1 i)) s (fun (c : γ) => g c)) (fun (a : α) => Finset.prod.{u2, u1} (β a) γ (_inst_1 a) s (fun (c : γ) => g c a))
Case conversion may be inaccurate. Consider using '#align finset.prod_fn Finset.prod_fnₓ'. -/
/-- An 'unapplied' analogue of `finset.prod_apply`. -/
@[to_additive "An 'unapplied' analogue of `finset.sum_apply`."]
theorem Finset.prod_fn {α : Type _} {β : α → Type _} {γ} [∀ a, CommMonoid (β a)] (s : Finset γ)
    (g : γ → ∀ a, β a) : (∏ c in s, g c) = fun a => ∏ c in s, g c a :=
  funext fun a => Finset.prod_apply _ _ _
#align finset.prod_fn Finset.prod_fn
#align finset.sum_fn Finset.sum_fn

/- warning: fintype.prod_apply -> Fintype.prod_apply is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {γ : Type.{u3}} [_inst_1 : Fintype.{u3} γ] [_inst_2 : forall (a : α), CommMonoid.{u2} (β a)] (a : α) (g : γ -> (forall (a : α), β a)), Eq.{succ u2} (β a) (Finset.prod.{max u1 u2, u3} (forall (a : α), β a) γ (Pi.commMonoid.{u1, u2} α (fun (a : α) => β a) (fun (i : α) => _inst_2 i)) (Finset.univ.{u3} γ _inst_1) (fun (c : γ) => g c) a) (Finset.prod.{u2, u3} (β a) γ (_inst_2 a) (Finset.univ.{u3} γ _inst_1) (fun (c : γ) => g c a))
but is expected to have type
  forall {α : Type.{u3}} {β : α -> Type.{u2}} {γ : Type.{u1}} [_inst_1 : Fintype.{u1} γ] [_inst_2 : forall (a : α), CommMonoid.{u2} (β a)] (a : α) (g : γ -> (forall (a : α), β a)), Eq.{succ u2} (β a) (Finset.prod.{max u3 u2, u1} (forall (a : α), β a) γ (Pi.commMonoid.{u3, u2} α (fun (a : α) => β a) (fun (i : α) => _inst_2 i)) (Finset.univ.{u1} γ _inst_1) (fun (c : γ) => g c) a) (Finset.prod.{u2, u1} (β a) γ (_inst_2 a) (Finset.univ.{u1} γ _inst_1) (fun (c : γ) => g c a))
Case conversion may be inaccurate. Consider using '#align fintype.prod_apply Fintype.prod_applyₓ'. -/
@[simp, to_additive]
theorem Fintype.prod_apply {α : Type _} {β : α → Type _} {γ : Type _} [Fintype γ]
    [∀ a, CommMonoid (β a)] (a : α) (g : γ → ∀ a, β a) : (∏ c, g c) a = ∏ c, g c a :=
  Finset.prod_apply a Finset.univ g
#align fintype.prod_apply Fintype.prod_apply
#align fintype.sum_apply Fintype.sum_apply

/- warning: prod_mk_prod -> prod_mk_prod is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : CommMonoid.{u1} α] [_inst_2 : CommMonoid.{u2} β] (s : Finset.{u3} γ) (f : γ -> α) (g : γ -> β), Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (Prod.mk.{u1, u2} α β (Finset.prod.{u1, u3} α γ _inst_1 s (fun (x : γ) => f x)) (Finset.prod.{u2, u3} β γ _inst_2 s (fun (x : γ) => g x))) (Finset.prod.{max u1 u2, u3} (Prod.{u1, u2} α β) γ (Prod.commMonoid.{u1, u2} α β _inst_1 _inst_2) s (fun (x : γ) => Prod.mk.{u1, u2} α β (f x) (g x)))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} {γ : Type.{u1}} [_inst_1 : CommMonoid.{u3} α] [_inst_2 : CommMonoid.{u2} β] (s : Finset.{u1} γ) (f : γ -> α) (g : γ -> β), Eq.{max (succ u3) (succ u2)} (Prod.{u3, u2} α β) (Prod.mk.{u3, u2} α β (Finset.prod.{u3, u1} α γ _inst_1 s (fun (x : γ) => f x)) (Finset.prod.{u2, u1} β γ _inst_2 s (fun (x : γ) => g x))) (Finset.prod.{max u2 u3, u1} (Prod.{u3, u2} α β) γ (Prod.instCommMonoidProd.{u3, u2} α β _inst_1 _inst_2) s (fun (x : γ) => Prod.mk.{u3, u2} α β (f x) (g x)))
Case conversion may be inaccurate. Consider using '#align prod_mk_prod prod_mk_prodₓ'. -/
@[to_additive prod_mk_sum]
theorem prod_mk_prod {α β γ : Type _} [CommMonoid α] [CommMonoid β] (s : Finset γ) (f : γ → α)
    (g : γ → β) : (∏ x in s, f x, ∏ x in s, g x) = ∏ x in s, (f x, g x) :=
  haveI := Classical.decEq γ
  Finset.induction_on s rfl (by simp (config := { contextual := true }) [Prod.ext_iff])
#align prod_mk_prod prod_mk_prod
#align prod_mk_sum prod_mk_sum

section Single

variable {I : Type _} [DecidableEq I] {Z : I → Type _}

variable [∀ i, AddCommMonoid (Z i)]

/- warning: finset.univ_sum_single -> Finset.univ_sum_single is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} I] {Z : I -> Type.{u2}} [_inst_2 : forall (i : I), AddCommMonoid.{u2} (Z i)] [_inst_3 : Fintype.{u1} I] (f : forall (i : I), Z i), Eq.{succ (max u1 u2)} (forall (i : I), Z i) (Finset.sum.{max u1 u2, u1} (forall (i : I), Z i) I (Pi.addCommMonoid.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => _inst_2 i)) (Finset.univ.{u1} I _inst_3) (fun (i : I) => Pi.single.{u1, u2} I (fun (i : I) => Z i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddZeroClass.toHasZero.{u2} (Z i) (AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) i (f i))) f
but is expected to have type
  forall {I : Type.{u2}} [_inst_1 : DecidableEq.{succ u2} I] {Z : I -> Type.{u1}} [_inst_2 : forall (i : I), AddCommMonoid.{u1} (Z i)] [_inst_3 : Fintype.{u2} I] (f : forall (i : I), Z i), Eq.{max (succ u2) (succ u1)} (forall (i : I), Z i) (Finset.sum.{max u1 u2, u2} (forall (i : I), Z i) I (Pi.addCommMonoid.{u2, u1} I (fun (i : I) => Z i) (fun (i : I) => _inst_2 i)) (Finset.univ.{u2} I _inst_3) (fun (i : I) => Pi.single.{u2, u1} I (fun (i : I) => Z i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddMonoid.toZero.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))) i (f i))) f
Case conversion may be inaccurate. Consider using '#align finset.univ_sum_single Finset.univ_sum_singleₓ'. -/
-- As we only defined `single` into `add_monoid`, we only prove the `finset.sum` version here.
theorem Finset.univ_sum_single [Fintype I] (f : ∀ i, Z i) : (∑ i, Pi.single i (f i)) = f :=
  by
  ext a
  simp
#align finset.univ_sum_single Finset.univ_sum_single

/- warning: add_monoid_hom.functions_ext -> AddMonoidHom.functions_ext is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} I] {Z : I -> Type.{u2}} [_inst_2 : forall (i : I), AddCommMonoid.{u2} (Z i)] [_inst_3 : Finite.{succ u1} I] (G : Type.{u3}) [_inst_4 : AddCommMonoid.{u3} G] (g : AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) G (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} G (AddCommMonoid.toAddMonoid.{u3} G _inst_4))) (h : AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) G (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} G (AddCommMonoid.toAddMonoid.{u3} G _inst_4))), (forall (i : I) (x : Z i), Eq.{succ u3} G (coeFn.{max (succ u3) (succ (max u1 u2)), max (succ (max u1 u2)) (succ u3)} (AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) G (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} G (AddCommMonoid.toAddMonoid.{u3} G _inst_4))) (fun (_x : AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) G (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} G (AddCommMonoid.toAddMonoid.{u3} G _inst_4))) => (forall (i : I), Z i) -> G) (AddMonoidHom.hasCoeToFun.{max u1 u2, u3} (forall (i : I), Z i) G (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} G (AddCommMonoid.toAddMonoid.{u3} G _inst_4))) g (Pi.single.{u1, u2} I (fun (i : I) => Z i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddZeroClass.toHasZero.{u2} (Z i) (AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) i x)) (coeFn.{max (succ u3) (succ (max u1 u2)), max (succ (max u1 u2)) (succ u3)} (AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) G (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} G (AddCommMonoid.toAddMonoid.{u3} G _inst_4))) (fun (_x : AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) G (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} G (AddCommMonoid.toAddMonoid.{u3} G _inst_4))) => (forall (i : I), Z i) -> G) (AddMonoidHom.hasCoeToFun.{max u1 u2, u3} (forall (i : I), Z i) G (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} G (AddCommMonoid.toAddMonoid.{u3} G _inst_4))) h (Pi.single.{u1, u2} I (fun (i : I) => Z i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddZeroClass.toHasZero.{u2} (Z i) (AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) i x))) -> (Eq.{max (succ u3) (succ (max u1 u2))} (AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) G (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} G (AddCommMonoid.toAddMonoid.{u3} G _inst_4))) g h)
but is expected to have type
  forall {I : Type.{u3}} [_inst_1 : DecidableEq.{succ u3} I] {Z : I -> Type.{u1}} [_inst_2 : forall (i : I), AddCommMonoid.{u1} (Z i)] [_inst_3 : Finite.{succ u3} I] (G : Type.{u2}) [_inst_4 : AddCommMonoid.{u2} G] (g : AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) (h : AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))), (forall (i : I) (x : Z i), Eq.{succ u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : forall (i : I), Z i) => G) (Pi.single.{u3, u1} I (fun (i : I) => Z i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddMonoid.toZero.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))) i x)) (FunLike.coe.{max (max (succ u3) (succ u1)) (succ u2), max (succ u3) (succ u1), succ u2} (AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) (forall (i : I), Z i) (fun (_x : forall (i : I), Z i) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : forall (i : I), Z i) => G) _x) (AddHomClass.toFunLike.{max (max u3 u1) u2, max u3 u1, u2} (AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) (forall (i : I), Z i) G (AddZeroClass.toAdd.{max u3 u1} (forall (i : I), Z i) (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))))) (AddZeroClass.toAdd.{u2} G (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) (AddMonoidHomClass.toAddHomClass.{max (max u3 u1) u2, max u3 u1, u2} (AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4)) (AddMonoidHom.addMonoidHomClass.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))))) g (Pi.single.{u3, u1} I (fun (i : I) => Z i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddMonoid.toZero.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))) i x)) (FunLike.coe.{max (max (succ u3) (succ u1)) (succ u2), max (succ u3) (succ u1), succ u2} (AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) (forall (i : I), Z i) (fun (_x : forall (i : I), Z i) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : forall (i : I), Z i) => G) _x) (AddHomClass.toFunLike.{max (max u3 u1) u2, max u3 u1, u2} (AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) (forall (i : I), Z i) G (AddZeroClass.toAdd.{max u3 u1} (forall (i : I), Z i) (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))))) (AddZeroClass.toAdd.{u2} G (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) (AddMonoidHomClass.toAddHomClass.{max (max u3 u1) u2, max u3 u1, u2} (AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4)) (AddMonoidHom.addMonoidHomClass.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))))) h (Pi.single.{u3, u1} I (fun (i : I) => Z i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddMonoid.toZero.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))) i x))) -> (Eq.{max (max (succ u3) (succ u1)) (succ u2)} (AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) G (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} G (AddCommMonoid.toAddMonoid.{u2} G _inst_4))) g h)
Case conversion may be inaccurate. Consider using '#align add_monoid_hom.functions_ext AddMonoidHom.functions_extₓ'. -/
theorem AddMonoidHom.functions_ext [Finite I] (G : Type _) [AddCommMonoid G] (g h : (∀ i, Z i) →+ G)
    (H : ∀ i x, g (Pi.single i x) = h (Pi.single i x)) : g = h :=
  by
  cases nonempty_fintype I
  ext k
  rw [← Finset.univ_sum_single k, g.map_sum, h.map_sum]
  simp only [H]
#align add_monoid_hom.functions_ext AddMonoidHom.functions_ext

/- warning: add_monoid_hom.functions_ext' -> AddMonoidHom.functions_ext' is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} I] {Z : I -> Type.{u2}} [_inst_2 : forall (i : I), AddCommMonoid.{u2} (Z i)] [_inst_3 : Finite.{succ u1} I] (M : Type.{u3}) [_inst_4 : AddCommMonoid.{u3} M] (g : AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) M (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} M (AddCommMonoid.toAddMonoid.{u3} M _inst_4))) (h : AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) M (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} M (AddCommMonoid.toAddMonoid.{u3} M _inst_4))), (forall (i : I), Eq.{max (succ u3) (succ u2)} (AddMonoidHom.{u2, u3} (Z i) M (AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i))) (AddMonoid.toAddZeroClass.{u3} M (AddCommMonoid.toAddMonoid.{u3} M _inst_4))) (AddMonoidHom.comp.{u2, max u1 u2, u3} (Z i) (forall (i : I), Z i) M (AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i))) (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} M (AddCommMonoid.toAddMonoid.{u3} M _inst_4)) g (AddMonoidHom.single.{u1, u2} I Z (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i))) i)) (AddMonoidHom.comp.{u2, max u1 u2, u3} (Z i) (forall (i : I), Z i) M (AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i))) (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} M (AddCommMonoid.toAddMonoid.{u3} M _inst_4)) h (AddMonoidHom.single.{u1, u2} I Z (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i))) i))) -> (Eq.{max (succ u3) (succ (max u1 u2))} (AddMonoidHom.{max u1 u2, u3} (forall (i : I), Z i) M (Pi.addZeroClass.{u1, u2} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u2} (Z i) (AddCommMonoid.toAddMonoid.{u2} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u3} M (AddCommMonoid.toAddMonoid.{u3} M _inst_4))) g h)
but is expected to have type
  forall {I : Type.{u3}} [_inst_1 : DecidableEq.{succ u3} I] {Z : I -> Type.{u1}} [_inst_2 : forall (i : I), AddCommMonoid.{u1} (Z i)] [_inst_3 : Finite.{succ u3} I] (M : Type.{u2}) [_inst_4 : AddCommMonoid.{u2} M] (g : AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) M (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} M (AddCommMonoid.toAddMonoid.{u2} M _inst_4))) (h : AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) M (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} M (AddCommMonoid.toAddMonoid.{u2} M _inst_4))), (forall (i : I), Eq.{max (succ u1) (succ u2)} (AddMonoidHom.{u1, u2} (Z i) M (AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))) (AddMonoid.toAddZeroClass.{u2} M (AddCommMonoid.toAddMonoid.{u2} M _inst_4))) (AddMonoidHom.comp.{u1, max u3 u1, u2} (Z i) (forall (i : I), Z i) M (AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))) (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} M (AddCommMonoid.toAddMonoid.{u2} M _inst_4)) g (AddMonoidHom.single.{u3, u1} I Z (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))) i)) (AddMonoidHom.comp.{u1, max u3 u1, u2} (Z i) (forall (i : I), Z i) M (AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))) (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} M (AddCommMonoid.toAddMonoid.{u2} M _inst_4)) h (AddMonoidHom.single.{u3, u1} I Z (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i))) i))) -> (Eq.{max (max (succ u3) (succ u1)) (succ u2)} (AddMonoidHom.{max u3 u1, u2} (forall (i : I), Z i) M (Pi.addZeroClass.{u3, u1} I (fun (i : I) => Z i) (fun (i : I) => AddMonoid.toAddZeroClass.{u1} (Z i) (AddCommMonoid.toAddMonoid.{u1} (Z i) (_inst_2 i)))) (AddMonoid.toAddZeroClass.{u2} M (AddCommMonoid.toAddMonoid.{u2} M _inst_4))) g h)
Case conversion may be inaccurate. Consider using '#align add_monoid_hom.functions_ext' AddMonoidHom.functions_ext'ₓ'. -/
/-- This is used as the ext lemma instead of `add_monoid_hom.functions_ext` for reasons explained in
note [partially-applied ext lemmas]. -/
@[ext]
theorem AddMonoidHom.functions_ext' [Finite I] (M : Type _) [AddCommMonoid M]
    (g h : (∀ i, Z i) →+ M)
    (H : ∀ i, g.comp (AddMonoidHom.single Z i) = h.comp (AddMonoidHom.single Z i)) : g = h :=
  have := fun i => AddMonoidHom.congr_fun (H i)
  -- elab without an expected type
      g.functions_ext
    M h this
#align add_monoid_hom.functions_ext' AddMonoidHom.functions_ext'

end Single

section RingHom

open Pi

variable {I : Type _} [DecidableEq I] {f : I → Type _}

variable [∀ i, NonAssocSemiring (f i)]

/- warning: ring_hom.functions_ext -> RingHom.functions_ext is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} I] {f : I -> Type.{u2}} [_inst_2 : forall (i : I), NonAssocSemiring.{u2} (f i)] [_inst_3 : Finite.{succ u1} I] (G : Type.{u3}) [_inst_4 : NonAssocSemiring.{u3} G] (g : RingHom.{max u1 u2, u3} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (h : RingHom.{max u1 u2, u3} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4), (forall (i : I) (x : f i), Eq.{succ u3} G (coeFn.{max (succ (max u1 u2)) (succ u3), max (succ (max u1 u2)) (succ u3)} (RingHom.{max u1 u2, u3} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (fun (_x : RingHom.{max u1 u2, u3} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) => (forall (i : I), f i) -> G) (RingHom.hasCoeToFun.{max u1 u2, u3} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) g (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) (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} (f i) (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} (f i) (_inst_2 i)))) i x)) (coeFn.{max (succ (max u1 u2)) (succ u3), max (succ (max u1 u2)) (succ u3)} (RingHom.{max u1 u2, u3} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (fun (_x : RingHom.{max u1 u2, u3} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) => (forall (i : I), f i) -> G) (RingHom.hasCoeToFun.{max u1 u2, u3} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) h (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) (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} (f i) (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} (f i) (_inst_2 i)))) i x))) -> (Eq.{max (succ (max u1 u2)) (succ u3)} (RingHom.{max u1 u2, u3} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u1, u2} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) g h)
but is expected to have type
  forall {I : Type.{u3}} [_inst_1 : DecidableEq.{succ u3} I] {f : I -> Type.{u1}} [_inst_2 : forall (i : I), NonAssocSemiring.{u1} (f i)] [_inst_3 : Finite.{succ u3} I] (G : Type.{u2}) [_inst_4 : NonAssocSemiring.{u2} G] (g : RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (h : RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4), (forall (i : I) (x : f i), Eq.{succ u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : forall (i : I), f i) => G) (Pi.single.{u3, u1} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulZeroOneClass.toZero.{u1} (f i) (NonAssocSemiring.toMulZeroOneClass.{u1} (f i) (_inst_2 i))) i x)) (FunLike.coe.{max (max (succ u3) (succ u1)) (succ u2), max (succ u3) (succ u1), succ u2} (RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (forall (i : I), f i) (fun (_x : forall (i : I), f i) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : forall (i : I), f i) => G) _x) (MulHomClass.toFunLike.{max (max u3 u1) u2, max u3 u1, u2} (RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (forall (i : I), f i) G (NonUnitalNonAssocSemiring.toMul.{max u3 u1} (forall (i : I), f i) (NonAssocSemiring.toNonUnitalNonAssocSemiring.{max u3 u1} (forall (i : I), f i) (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)))) (NonUnitalNonAssocSemiring.toMul.{u2} G (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} G _inst_4)) (NonUnitalRingHomClass.toMulHomClass.{max (max u3 u1) u2, max u3 u1, u2} (RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (forall (i : I), f i) G (NonAssocSemiring.toNonUnitalNonAssocSemiring.{max u3 u1} (forall (i : I), f i) (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} G _inst_4) (RingHomClass.toNonUnitalRingHomClass.{max (max u3 u1) u2, max u3 u1, u2} (RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4 (RingHom.instRingHomClassRingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4)))) g (Pi.single.{u3, u1} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulZeroOneClass.toZero.{u1} (f i) (NonAssocSemiring.toMulZeroOneClass.{u1} (f i) (_inst_2 i))) i x)) (FunLike.coe.{max (max (succ u3) (succ u1)) (succ u2), max (succ u3) (succ u1), succ u2} (RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (forall (i : I), f i) (fun (_x : forall (i : I), f i) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : forall (i : I), f i) => G) _x) (MulHomClass.toFunLike.{max (max u3 u1) u2, max u3 u1, u2} (RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (forall (i : I), f i) G (NonUnitalNonAssocSemiring.toMul.{max u3 u1} (forall (i : I), f i) (NonAssocSemiring.toNonUnitalNonAssocSemiring.{max u3 u1} (forall (i : I), f i) (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)))) (NonUnitalNonAssocSemiring.toMul.{u2} G (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} G _inst_4)) (NonUnitalRingHomClass.toMulHomClass.{max (max u3 u1) u2, max u3 u1, u2} (RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (forall (i : I), f i) G (NonAssocSemiring.toNonUnitalNonAssocSemiring.{max u3 u1} (forall (i : I), f i) (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i))) (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} G _inst_4) (RingHomClass.toNonUnitalRingHomClass.{max (max u3 u1) u2, max u3 u1, u2} (RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4 (RingHom.instRingHomClassRingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4)))) h (Pi.single.{u3, u1} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => MulZeroOneClass.toZero.{u1} (f i) (NonAssocSemiring.toMulZeroOneClass.{u1} (f i) (_inst_2 i))) i x))) -> (Eq.{max (max (succ u3) (succ u1)) (succ u2)} (RingHom.{max u3 u1, u2} (forall (i : I), f i) G (Pi.nonAssocSemiring.{u3, u1} I (fun (i : I) => f i) (fun (i : I) => _inst_2 i)) _inst_4) g h)
Case conversion may be inaccurate. Consider using '#align ring_hom.functions_ext RingHom.functions_extₓ'. -/
@[ext]
theorem RingHom.functions_ext [Finite I] (G : Type _) [NonAssocSemiring G] (g h : (∀ i, f i) →+* G)
    (H : ∀ (i : I) (x : f i), g (single i x) = h (single i x)) : g = h :=
  RingHom.coe_addMonoidHom_injective <|
    @AddMonoidHom.functions_ext I _ f _ _ G _ (g : (∀ i, f i) →+ G) h H
#align ring_hom.functions_ext RingHom.functions_ext

end RingHom

namespace Prod

variable {α β γ : Type _} [CommMonoid α] [CommMonoid β] {s : Finset γ} {f : γ → α × β}

/- warning: prod.fst_prod -> Prod.fst_prod is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : CommMonoid.{u1} α] [_inst_2 : CommMonoid.{u2} β] {s : Finset.{u3} γ} {f : γ -> (Prod.{u1, u2} α β)}, Eq.{succ u1} α (Prod.fst.{u1, u2} α β (Finset.prod.{max u1 u2, u3} (Prod.{u1, u2} α β) γ (Prod.commMonoid.{u1, u2} α β _inst_1 _inst_2) s (fun (c : γ) => f c))) (Finset.prod.{u1, u3} α γ _inst_1 s (fun (c : γ) => Prod.fst.{u1, u2} α β (f c)))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} {γ : Type.{u1}} [_inst_1 : CommMonoid.{u3} α] [_inst_2 : CommMonoid.{u2} β] {s : Finset.{u1} γ} {f : γ -> (Prod.{u3, u2} α β)}, Eq.{succ u3} α (Prod.fst.{u3, u2} α β (Finset.prod.{max u3 u2, u1} (Prod.{u3, u2} α β) γ (Prod.instCommMonoidProd.{u3, u2} α β _inst_1 _inst_2) s (fun (c : γ) => f c))) (Finset.prod.{u3, u1} α γ _inst_1 s (fun (c : γ) => Prod.fst.{u3, u2} α β (f c)))
Case conversion may be inaccurate. Consider using '#align prod.fst_prod Prod.fst_prodₓ'. -/
@[to_additive]
theorem fst_prod : (∏ c in s, f c).1 = ∏ c in s, (f c).1 :=
  (MonoidHom.fst α β).map_prod f s
#align prod.fst_prod Prod.fst_prod
#align prod.fst_sum Prod.fst_sum

/- warning: prod.snd_prod -> Prod.snd_prod is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : CommMonoid.{u1} α] [_inst_2 : CommMonoid.{u2} β] {s : Finset.{u3} γ} {f : γ -> (Prod.{u1, u2} α β)}, Eq.{succ u2} β (Prod.snd.{u1, u2} α β (Finset.prod.{max u1 u2, u3} (Prod.{u1, u2} α β) γ (Prod.commMonoid.{u1, u2} α β _inst_1 _inst_2) s (fun (c : γ) => f c))) (Finset.prod.{u2, u3} β γ _inst_2 s (fun (c : γ) => Prod.snd.{u1, u2} α β (f c)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u3}} {γ : Type.{u1}} [_inst_1 : CommMonoid.{u2} α] [_inst_2 : CommMonoid.{u3} β] {s : Finset.{u1} γ} {f : γ -> (Prod.{u2, u3} α β)}, Eq.{succ u3} β (Prod.snd.{u2, u3} α β (Finset.prod.{max u2 u3, u1} (Prod.{u2, u3} α β) γ (Prod.instCommMonoidProd.{u2, u3} α β _inst_1 _inst_2) s (fun (c : γ) => f c))) (Finset.prod.{u3, u1} β γ _inst_2 s (fun (c : γ) => Prod.snd.{u2, u3} α β (f c)))
Case conversion may be inaccurate. Consider using '#align prod.snd_prod Prod.snd_prodₓ'. -/
@[to_additive]
theorem snd_prod : (∏ c in s, f c).2 = ∏ c in s, (f c).2 :=
  (MonoidHom.snd α β).map_prod f s
#align prod.snd_prod Prod.snd_prod
#align prod.snd_sum Prod.snd_sum

end Prod


import topology.metric_space.basic
import category_theory.concrete_category

universe u

open category_theory

def Metric_space := bundled metric_space.{u}

variables {X Y Z : Metric_space.{u}}
instance has_dist_bundled : metric_space X.1 := X.2

/- Definition of continuity on metric spaces -/
def is_continuous_at (f : X.1 → Y.1) (a : X.1) := ∀ ε > 0, ∃ δ > 0, ∀ x : X.1, dist x a < δ → dist (f x) (f a) < ε
def is_continuous (f : X.1 → Y.1) := ∀ a : X.1, is_continuous_at f a

/- Defining continuous functions as morphisms of metric spaces -/
structure metric_morph (X Y : Metric_space) :=
(to_fun : X.1 → Y.1)
(is_contin : is_continuous to_fun)

/- Identity metric morphism -/
def id_metric_morph (X : Metric_space) : metric_morph X X :=
{ to_fun := λ x : X.1, x,
  is_contin := λ a ε hε,
  ⟨ε, hε, λ x, by simp⟩
}

/- The composition of two continuous functions is continuous -/
lemma comp_continuous {f : X.1 → Y.1} {g : Y.1 → Z.1} (h₁ : is_continuous f) (h₂ : is_continuous g) :
is_continuous (g ∘ f) := λ a ε hε,
  let ⟨δ₁, hg₁, hg₂⟩ := h₂ (f a) ε hε in
  let ⟨δ₂, hf₁, hf₂⟩ := h₁ a δ₁ hg₁ in
  ⟨δ₂, hf₁, λ x hx, hg₂ (f x) (hf₂ x hx)⟩

/- Two metric_morph are the same if their underlying functiosn are the same -/
@[ext] lemma ext {f g : metric_morph X Y} (h : ∀ x : X.1, f.to_fun x = g.to_fun x) : f = g :=
by cases f; cases g; congr; exact funext h

lemma id_comp {f : metric_morph X Y} : f.to_fun ∘ (id_metric_morph X).to_fun = f.to_fun :=
by ext; rw [function.comp_app, show (id_metric_morph X).to_fun x = x, by refl]

lemma comp_id {f : metric_morph X Y} : (id_metric_morph Y).to_fun ∘ f.to_fun = f.to_fun :=
by ext; rw [function.comp_app, show (id_metric_morph Y).to_fun (f.to_fun x) = f.to_fun x, by refl]

/- The category of metric spaces -/
instance category_metric_space : category (Metric_space) :=
{ hom := metric_morph,
  id := id_metric_morph,
  comp := λ X Y Z f g, by {split, from comp_continuous f.is_contin g.is_contin},
  id_comp' := λ X Y f, by ext; simp [id_comp],
  comp_id' := λ X Y f, by ext; simp [comp_id],
  assoc' := λ W X Y Z f g h, by simp
}

/- Definition of isometry on metric spaces -/
def is_isometry (f : X.1 → Y.1) := ∀ x y : X.1, dist x y = dist (f x) (f y)

/- Defining isometries as morphisms of metric spaces -/
structure metric_morph2 (X Y : Metric_space) :=
(to_fun : X.1 → Y.1)
(is_iso : is_isometry to_fun)

/- Identity metric morphism -/
def id_metric_morph2 (X : Metric_space) : metric_morph2 X X :=
{ to_fun := λ x : X.1, x,
  is_iso := λ x y, rfl
}

/- The composition of two isometries is continuous -/
lemma comp_isometry {f : X.1 → Y.1} {g : Y.1 → Z.1} (h₁ : is_isometry f) (h₂ : is_isometry g) :
is_isometry (g ∘ f) := λ x y, by simp [h₁ x y, h₂ (f x) (f y)]

/- Two metric_morph2 are the same if their underlying functiosn are the same -/
@[ext] lemma ext2 {f g : metric_morph2 X Y} (h : ∀ x : X.1, f.to_fun x = g.to_fun x) : f = g :=
by cases f; cases g; congr; exact funext h

lemma id_comp2 {f : metric_morph2 X Y} : f.to_fun ∘ (id_metric_morph2 X).to_fun = f.to_fun :=
by ext; rw [function.comp_app, show (id_metric_morph2 X).to_fun x = x, by refl]

lemma comp_id2 {f : metric_morph2 X Y} : (id_metric_morph2 Y).to_fun ∘ f.to_fun = f.to_fun :=
by ext; rw [function.comp_app, show (id_metric_morph2 Y).to_fun (f.to_fun x) = f.to_fun x, by refl]

/- The category of metric spaces with isometries -/
instance category_metric_space2 : category (Metric_space) :=
{ hom := metric_morph2,
  id := id_metric_morph2,
  comp := λ X Y Z f g, by {split, from comp_isometry f.is_iso g.is_iso},
  id_comp' := λ X Y f, by ext; simp [id_comp2],
  comp_id' := λ X Y f, by ext; simp [comp_id2],
  assoc' := λ W X Y Z f g h, by simp
}


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

! This file was ported from Lean 3 source module field_theory.cardinality
! leanprover-community/mathlib commit 13e18cfa070ea337ea960176414f5ae3a1534aae
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Field.Ulift
import Mathbin.Data.MvPolynomial.Cardinal
import Mathbin.Data.Nat.Factorization.PrimePow
import Mathbin.Data.Rat.Denumerable
import Mathbin.FieldTheory.Finite.GaloisField
import Mathbin.Logic.Equiv.TransferInstance
import Mathbin.RingTheory.Localization.Cardinality
import Mathbin.SetTheory.Cardinal.Divisibility

/-!
# Cardinality of Fields

In this file we show all the possible cardinalities of fields. All infinite cardinals can harbour
a field structure, and so can all types with prime power cardinalities, and this is sharp.

## Main statements

* `fintype.nonempty_field_iff`: A `fintype` can be given a field structure iff its cardinality is a
  prime power.
* `infinite.nonempty_field` : Any infinite type can be endowed a field structure.
* `field.nonempty_iff` : There is a field structure on type iff its cardinality is a prime power.

-/

-- mathport name: «expr‖ ‖»
local notation "‖" x "‖" => Fintype.card x

open Cardinal nonZeroDivisors

universe u

/-- A finite field has prime power cardinality. -/
theorem Fintype.isPrimePow_card_of_field {α} [Fintype α] [Field α] : IsPrimePow ‖α‖ :=
  by
  cases' CharP.exists α with p _
  haveI hp := Fact.mk (CharP.char_is_prime α p)
  let b := IsNoetherian.finsetBasis (ZMod p) α
  rw [Module.card_fintype b, ZMod.card, isPrimePow_pow_iff]
  · exact hp.1.IsPrimePow
  rw [← FiniteDimensional.finrank_eq_card_basis b]
  exact finite_dimensional.finrank_pos.ne'
#align fintype.is_prime_pow_card_of_field Fintype.isPrimePow_card_of_field

/-- A `fintype` can be given a field structure iff its cardinality is a prime power. -/
theorem Fintype.nonempty_field_iff {α} [Fintype α] : Nonempty (Field α) ↔ IsPrimePow ‖α‖ :=
  by
  refine' ⟨fun ⟨h⟩ => Fintype.isPrimePow_card_of_field, _⟩
  rintro ⟨p, n, hp, hn, hα⟩
  haveI := Fact.mk hp.nat_prime
  exact ⟨(Fintype.equivOfCardEq ((GaloisField.card p n hn.ne').trans hα)).symm.Field⟩
#align fintype.nonempty_field_iff Fintype.nonempty_field_iff

theorem Fintype.not_isField_of_card_not_prime_pow {α} [Fintype α] [Ring α] :
    ¬IsPrimePow ‖α‖ → ¬IsField α :=
  mt fun h => Fintype.nonempty_field_iff.mp ⟨h.toField⟩
#align fintype.not_is_field_of_card_not_prime_pow Fintype.not_isField_of_card_not_prime_pow

/-- Any infinite type can be endowed a field structure. -/
theorem Infinite.nonempty_field {α : Type u} [Infinite α] : Nonempty (Field α) :=
  by
  letI K := FractionRing (MvPolynomial α <| ULift.{u} ℚ)
  suffices (#α) = (#K) by
    obtain ⟨e⟩ := Cardinal.eq.1 this
    exact ⟨e.field⟩
  rw [← IsLocalization.card (MvPolynomial α <| ULift.{u} ℚ)⁰ K le_rfl]
  apply le_antisymm
  · refine'
      ⟨⟨fun a => MvPolynomial.monomial (Finsupp.single a 1) (1 : ULift.{u} ℚ), fun x y h => _⟩⟩
    simpa [MvPolynomial.monomial_eq_monomial_iff, Finsupp.single_eq_single_iff] using h
  · simp
#align infinite.nonempty_field Infinite.nonempty_field

/-- There is a field structure on type if and only if its cardinality is a prime power. -/
theorem Field.nonempty_iff {α : Type u} : Nonempty (Field α) ↔ IsPrimePow (#α) :=
  by
  rw [Cardinal.isPrimePow_iff]
  cases' fintypeOrInfinite α with h h
  ·
    simpa only [Cardinal.mk_fintype, Nat.cast_inj, exists_eq_left',
      (Cardinal.nat_lt_aleph0 _).not_le, false_or_iff] using Fintype.nonempty_field_iff
  · simpa only [← Cardinal.infinite_iff, h, true_or_iff, iff_true_iff] using Infinite.nonempty_field
#align field.nonempty_iff Field.nonempty_iff


/-
Copyright (c) 2020 Thomas Browning, Patrick Lutz. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Thomas Browning, Patrick Lutz
-/

import field_theory.normal
import field_theory.primitive_element
import field_theory.fixed
import ring_theory.power_basis
import group_theory.group_action.fixing_subgroup

/-!
# Galois Extensions

In this file we define Galois extensions as extensions which are both separable and normal.

## Main definitions

- `is_galois F E` where `E` is an extension of `F`
- `fixed_field H` where `H : subgroup (E ≃ₐ[F] E)`
- `fixing_subgroup K` where `K : intermediate_field F E`
- `galois_correspondence` where `E/F` is finite dimensional and Galois

## Main results

- `fixing_subgroup_of_fixed_field` : If `E/F` is finite dimensional (but not necessarily Galois)
  then `fixing_subgroup (fixed_field H) = H`
- `fixed_field_of_fixing_subgroup`: If `E/F` is finite dimensional and Galois
  then `fixed_field (fixing_subgroup K) = K`
Together, these two result prove the Galois correspondence

- `is_galois.tfae` : Equivalent characterizations of a Galois extension of finite degree
-/

open_locale polynomial

open finite_dimensional alg_equiv

section

variables (F : Type*) [field F] (E : Type*) [field E] [algebra F E]

/-- A field extension E/F is galois if it is both separable and normal. Note that in mathlib
a separable extension of fields is by definition algebraic. -/
class is_galois : Prop :=
[to_is_separable : is_separable F E]
[to_normal : normal F E]

variables {F E}

theorem is_galois_iff : is_galois F E ↔ is_separable F E ∧ normal F E :=
⟨λ h, ⟨h.1, h.2⟩, λ h, { to_is_separable := h.1, to_normal := h.2 }⟩

attribute [instance, priority 100] -- see Note [lower instance priority]
is_galois.to_is_separable is_galois.to_normal

variables (F E)

namespace is_galois

instance self : is_galois F F :=
⟨⟩

variables (F) {E}

lemma integral [is_galois F E] (x : E) : is_integral F x := normal.is_integral' x

lemma separable [is_galois F E] (x : E) : (minpoly F x).separable := is_separable.separable F x

lemma splits [is_galois F E] (x : E) : (minpoly F x).splits (algebra_map F E) := normal.splits' x

variables (F E)

instance of_fixed_field (G : Type*) [group G] [fintype G] [mul_semiring_action G E] :
  is_galois (fixed_points.subfield G E) E :=
⟨⟩

lemma intermediate_field.adjoin_simple.card_aut_eq_finrank
  [finite_dimensional F E] {α : E} (hα : is_integral F α)
  (h_sep : (minpoly F α).separable)
  (h_splits : (minpoly F α).splits (algebra_map F F⟮α⟯)) :
  fintype.card (F⟮α⟯ ≃ₐ[F] F⟮α⟯) = finrank F F⟮α⟯ :=
begin
  letI : fintype (F⟮α⟯ →ₐ[F] F⟮α⟯) := intermediate_field.fintype_of_alg_hom_adjoin_integral F hα,
  rw intermediate_field.adjoin.finrank hα,
  rw ← intermediate_field.card_alg_hom_adjoin_integral F hα h_sep h_splits,
  exact fintype.card_congr (alg_equiv_equiv_alg_hom F F⟮α⟯)
end

lemma card_aut_eq_finrank [finite_dimensional F E] [is_galois F E] :
  fintype.card (E ≃ₐ[F] E) = finrank F E :=
begin
  cases field.exists_primitive_element F E with α hα,
  let iso : F⟮α⟯ ≃ₐ[F] E :=
  { to_fun := λ e, e.val,
    inv_fun := λ e, ⟨e, by { rw hα, exact intermediate_field.mem_top }⟩,
    left_inv := λ _, by { ext, refl },
    right_inv := λ _, rfl,
    map_mul' := λ _ _, rfl,
    map_add' := λ _ _, rfl,
    commutes' := λ _, rfl },
  have H : is_integral F α := is_galois.integral F α,
  have h_sep : (minpoly F α).separable := is_galois.separable F α,
  have h_splits : (minpoly F α).splits (algebra_map F E) := is_galois.splits F α,
  replace h_splits : polynomial.splits (algebra_map F F⟮α⟯) (minpoly F α),
  { have p : iso.symm.to_alg_hom.to_ring_hom.comp (algebra_map F E) = (algebra_map F ↥F⟮α⟯),
    { ext, simp, },
    simpa [p] using polynomial.splits_comp_of_splits
      (algebra_map F E) iso.symm.to_alg_hom.to_ring_hom h_splits, },
  rw ← linear_equiv.finrank_eq iso.to_linear_equiv,
  rw ← intermediate_field.adjoin_simple.card_aut_eq_finrank F E H h_sep h_splits,
  apply fintype.card_congr,
  apply equiv.mk (λ ϕ, iso.trans (trans ϕ iso.symm)) (λ ϕ, iso.symm.trans (trans ϕ iso)),
  { intro ϕ, ext1, simp only [trans_apply, apply_symm_apply] },
  { intro ϕ, ext1, simp only [trans_apply, symm_apply_apply] },
end

end is_galois

end

section is_galois_tower

variables (F K E : Type*) [field F] [field K] [field E] {E' : Type*} [field E'] [algebra F E']
variables [algebra F K] [algebra F E] [algebra K E] [is_scalar_tower F K E]

lemma is_galois.tower_top_of_is_galois [is_galois F E] : is_galois K E :=
{ to_is_separable := is_separable_tower_top_of_is_separable F K E,
  to_normal := normal.tower_top_of_normal F K E }

variables {F E}

@[priority 100] -- see Note [lower instance priority]
instance is_galois.tower_top_intermediate_field (K : intermediate_field F E) [h : is_galois F E] :
  is_galois K E := is_galois.tower_top_of_is_galois F K E

lemma is_galois_iff_is_galois_bot : is_galois (⊥ : intermediate_field F E) E ↔ is_galois F E :=
begin
  split,
  { introI h,
    exact is_galois.tower_top_of_is_galois (⊥ : intermediate_field F E) F E },
  { introI h, apply_instance },
end

lemma is_galois.of_alg_equiv [h : is_galois F E] (f : E ≃ₐ[F] E') : is_galois F E' :=
{ to_is_separable := is_separable.of_alg_hom F E f.symm, to_normal := normal.of_alg_equiv f }

lemma alg_equiv.transfer_galois (f : E ≃ₐ[F] E') : is_galois F E ↔ is_galois F E' :=
⟨λ h, by exactI is_galois.of_alg_equiv f, λ h, by exactI is_galois.of_alg_equiv f.symm⟩

lemma is_galois_iff_is_galois_top : is_galois F (⊤ : intermediate_field F E) ↔ is_galois F E :=
(intermediate_field.top_equiv : (⊤ : intermediate_field F E) ≃ₐ[F] E).transfer_galois

instance is_galois_bot : is_galois F (⊥ : intermediate_field F E) :=
(intermediate_field.bot_equiv F E).transfer_galois.mpr (is_galois.self F)

end is_galois_tower

section galois_correspondence

variables {F : Type*} [field F] {E : Type*} [field E] [algebra F E]
variables (H : subgroup (E ≃ₐ[F] E)) (K : intermediate_field F E)

/-- The intermediate field of fixed points fixed by a monoid action that commutes with the
`F`-action on `E`. -/
def fixed_points.intermediate_field (M : Type*) [monoid M] [mul_semiring_action M E]
  [smul_comm_class M F E] : intermediate_field F E :=
{ carrier := mul_action.fixed_points M E,
  algebra_map_mem' := λ a g, by rw [algebra.algebra_map_eq_smul_one, smul_comm, smul_one],
  ..fixed_points.subfield M E }

namespace intermediate_field

/-- The intermediate_field fixed by a subgroup -/
def fixed_field : intermediate_field F E :=
fixed_points.intermediate_field H

lemma finrank_fixed_field_eq_card [finite_dimensional F E] [decidable_pred (∈ H)] :
  finrank (fixed_field H) E = fintype.card H :=
fixed_points.finrank_eq_card H E

/-- The subgroup fixing an intermediate_field -/
def fixing_subgroup : subgroup (E ≃ₐ[F] E) :=
fixing_subgroup (E ≃ₐ[F] E) (K : set E)

lemma le_iff_le : K ≤ fixed_field H ↔ H ≤ fixing_subgroup K :=
⟨λ h g hg x, h (subtype.mem x) ⟨g, hg⟩, λ h x hx g, h (subtype.mem g) ⟨x, hx⟩⟩

/-- The fixing_subgroup of `K : intermediate_field F E` is isomorphic to `E ≃ₐ[K] E` -/
def fixing_subgroup_equiv : fixing_subgroup K ≃* (E ≃ₐ[K] E) :=
{ to_fun := λ ϕ, { commutes' := ϕ.mem, ..alg_equiv.to_ring_equiv ↑ϕ },
  inv_fun := λ ϕ, ⟨ϕ.restrict_scalars _, ϕ.commutes⟩,
  left_inv := λ _, by { ext, refl },
  right_inv := λ _, by { ext, refl },
  map_mul' := λ _ _, by { ext, refl } }

theorem fixing_subgroup_fixed_field [finite_dimensional F E] :
  fixing_subgroup (fixed_field H) = H :=
begin
  have H_le : H ≤ (fixing_subgroup (fixed_field H)) := (le_iff_le _ _).mp le_rfl,
  classical,
  suffices : fintype.card H = fintype.card (fixing_subgroup (fixed_field H)),
  { exact set_like.coe_injective
      (set.eq_of_inclusion_surjective ((fintype.bijective_iff_injective_and_card
        (set.inclusion H_le)).mpr ⟨set.inclusion_injective H_le, this⟩).2).symm },
  apply fintype.card_congr,
  refine (fixed_points.to_alg_hom_equiv H E).trans _,
  refine (alg_equiv_equiv_alg_hom (fixed_field H) E).symm.trans _,
  exact (fixing_subgroup_equiv (fixed_field H)).to_equiv.symm
end

instance fixed_field.algebra : algebra K (fixed_field (fixing_subgroup K)) :=
{ smul := λ x y, ⟨x*y, λ ϕ, by rw [smul_mul', (show ϕ • ↑x = ↑x, by exact subtype.mem ϕ x),
    (show ϕ • ↑y = ↑y, by exact subtype.mem y ϕ)]⟩,
  to_fun := λ x, ⟨x, λ ϕ, subtype.mem ϕ x⟩,
  map_zero' := rfl,
  map_add' := λ _ _, rfl,
  map_one' := rfl,
  map_mul' := λ _ _, rfl,
  commutes' := λ _ _, mul_comm _ _,
  smul_def' := λ _ _, rfl }

instance fixed_field.is_scalar_tower : is_scalar_tower K (fixed_field (fixing_subgroup K)) E :=
⟨λ _ _ _, mul_assoc _ _ _⟩

end intermediate_field

namespace is_galois
theorem fixed_field_fixing_subgroup [finite_dimensional F E] [h : is_galois F E] :
  intermediate_field.fixed_field (intermediate_field.fixing_subgroup K) = K :=
begin
  have K_le : K ≤ intermediate_field.fixed_field (intermediate_field.fixing_subgroup K) :=
    (intermediate_field.le_iff_le _ _).mpr le_rfl,
  suffices : finrank K E =
    finrank (intermediate_field.fixed_field (intermediate_field.fixing_subgroup K)) E,
  { exact (intermediate_field.eq_of_le_of_finrank_eq' K_le this).symm },
  classical,
  rw [intermediate_field.finrank_fixed_field_eq_card,
    fintype.card_congr (intermediate_field.fixing_subgroup_equiv K).to_equiv],
  exact (card_aut_eq_finrank K E).symm,
end

lemma card_fixing_subgroup_eq_finrank [decidable_pred (∈ intermediate_field.fixing_subgroup K)]
  [finite_dimensional F E] [is_galois F E] :
  fintype.card (intermediate_field.fixing_subgroup K) = finrank K E :=
by conv { to_rhs, rw [←fixed_field_fixing_subgroup K,
  intermediate_field.finrank_fixed_field_eq_card] }

/-- The Galois correspondence from intermediate fields to subgroups -/
def intermediate_field_equiv_subgroup [finite_dimensional F E] [is_galois F E] :
  intermediate_field F E ≃o order_dual (subgroup (E ≃ₐ[F] E)) :=
{ to_fun := intermediate_field.fixing_subgroup,
  inv_fun := intermediate_field.fixed_field,
  left_inv := λ K, fixed_field_fixing_subgroup K,
  right_inv := λ H, intermediate_field.fixing_subgroup_fixed_field H,
  map_rel_iff' := λ K L, by { rw [←fixed_field_fixing_subgroup L, intermediate_field.le_iff_le,
                                  fixed_field_fixing_subgroup L, ←order_dual.dual_le], refl } }

/-- The Galois correspondence as a galois_insertion -/
def galois_insertion_intermediate_field_subgroup [finite_dimensional F E] :
  galois_insertion (order_dual.to_dual ∘
      (intermediate_field.fixing_subgroup : intermediate_field F E → subgroup (E ≃ₐ[F] E)))
    ((intermediate_field.fixed_field : subgroup (E ≃ₐ[F] E) → intermediate_field F E) ∘
      order_dual.to_dual) :=
{ choice := λ K _, intermediate_field.fixing_subgroup K,
  gc := λ K H, (intermediate_field.le_iff_le H K).symm,
  le_l_u := λ H, le_of_eq (intermediate_field.fixing_subgroup_fixed_field H).symm,
  choice_eq := λ K _, rfl }

/-- The Galois correspondence as a galois_coinsertion -/
def galois_coinsertion_intermediate_field_subgroup [finite_dimensional F E] [is_galois F E] :
  galois_coinsertion (order_dual.to_dual ∘
      (intermediate_field.fixing_subgroup : intermediate_field F E → subgroup (E ≃ₐ[F] E)))
    ((intermediate_field.fixed_field : subgroup (E ≃ₐ[F] E) → intermediate_field F E) ∘
      order_dual.to_dual) :=
{ choice := λ H _, intermediate_field.fixed_field H,
  gc := λ K H, (intermediate_field.le_iff_le H K).symm,
  u_l_le := λ K, le_of_eq (fixed_field_fixing_subgroup K),
  choice_eq := λ H _, rfl }

end is_galois

end galois_correspondence

section galois_equivalent_definitions

variables (F : Type*) [field F] (E : Type*) [field E] [algebra F E]

namespace is_galois

lemma is_separable_splitting_field [finite_dimensional F E] [is_galois F E] :
  ∃ p : F[X], p.separable ∧ p.is_splitting_field F E :=
begin
  cases field.exists_primitive_element F E with α h1,
  use [minpoly F α, separable F α, is_galois.splits F α],
  rw [eq_top_iff, ←intermediate_field.top_to_subalgebra, ←h1],
  rw intermediate_field.adjoin_simple_to_subalgebra_of_integral F α (integral F α),
  apply algebra.adjoin_mono,
  rw [set.singleton_subset_iff, finset.mem_coe, multiset.mem_to_finset, polynomial.mem_roots],
  { dsimp only [polynomial.is_root],
    rw [polynomial.eval_map, ←polynomial.aeval_def],
    exact minpoly.aeval _ _ },
  { exact polynomial.map_ne_zero (minpoly.ne_zero (integral F α)) }
end

lemma of_fixed_field_eq_bot [finite_dimensional F E]
  (h : intermediate_field.fixed_field (⊤ : subgroup (E ≃ₐ[F] E)) = ⊥) : is_galois F E :=
begin
  rw [←is_galois_iff_is_galois_bot, ←h],
  classical,
  exact is_galois.of_fixed_field E (⊤ : subgroup (E ≃ₐ[F] E)),
end

lemma of_card_aut_eq_finrank [finite_dimensional F E]
  (h : fintype.card (E ≃ₐ[F] E) = finrank F E) : is_galois F E :=
begin
  apply of_fixed_field_eq_bot,
  have p : 0 < finrank (intermediate_field.fixed_field (⊤ : subgroup (E ≃ₐ[F] E))) E := finrank_pos,
  classical,
  rw [←intermediate_field.finrank_eq_one_iff, ←mul_left_inj' (ne_of_lt p).symm, finrank_mul_finrank,
      ←h, one_mul, intermediate_field.finrank_fixed_field_eq_card],
  apply fintype.card_congr,
  exact { to_fun := λ g, ⟨g, subgroup.mem_top g⟩, inv_fun := coe,
          left_inv := λ g, rfl, right_inv := λ _, by { ext, refl } },
end

variables {F} {E} {p : F[X]}

lemma of_separable_splitting_field_aux [hFE : finite_dimensional F E]
  [sp : p.is_splitting_field F E] (hp : p.separable) (K : intermediate_field F E) {x : E}
  (hx : x ∈ (p.map (algebra_map F E)).roots) :
  fintype.card ((↑K⟮x⟯ : intermediate_field F E) →ₐ[F] E) =
    fintype.card (K →ₐ[F] E) * finrank K K⟮x⟯ :=
begin
  have h : is_integral K x := is_integral_of_is_scalar_tower x
    (is_integral_of_noetherian (is_noetherian.iff_fg.2 hFE) x),
  have h1 : p ≠ 0 := λ hp, by rwa [hp, polynomial.map_zero, polynomial.roots_zero] at hx,
  have h2 : (minpoly K x) ∣ p.map (algebra_map F K),
  { apply minpoly.dvd,
    rw [polynomial.aeval_def, polynomial.eval₂_map, ←polynomial.eval_map],
    exact (polynomial.mem_roots (polynomial.map_ne_zero h1)).mp hx },
  let key_equiv : ((↑K⟮x⟯ : intermediate_field F E) →ₐ[F] E) ≃ Σ (f : K →ₐ[F] E),
    @alg_hom K K⟮x⟯ E _ _ _ _ (ring_hom.to_algebra f) :=
  equiv.trans (alg_equiv.arrow_congr (intermediate_field.lift2_alg_equiv K⟮x⟯) (alg_equiv.refl))
    alg_hom_equiv_sigma,
  haveI : Π (f : K →ₐ[F] E), fintype (@alg_hom K K⟮x⟯ E _ _ _ _ (ring_hom.to_algebra f)) := λ f, by
  { apply fintype.of_injective (sigma.mk f) (λ _ _ H, eq_of_heq ((sigma.mk.inj H).2)),
    exact fintype.of_equiv _ key_equiv },
  rw [fintype.card_congr key_equiv, fintype.card_sigma, intermediate_field.adjoin.finrank h],
  apply finset.sum_const_nat,
  intros f hf,
  rw ← @intermediate_field.card_alg_hom_adjoin_integral K _ E _ _ x E _ (ring_hom.to_algebra f) h,
  { apply fintype.card_congr, refl },
  { exact polynomial.separable.of_dvd ((polynomial.separable_map (algebra_map F K)).mpr hp) h2 },
  { refine polynomial.splits_of_splits_of_dvd _ (polynomial.map_ne_zero h1) _ h2,
    rw [polynomial.splits_map_iff, ←is_scalar_tower.algebra_map_eq],
    exact sp.splits },
end

lemma of_separable_splitting_field [sp : p.is_splitting_field F E] (hp : p.separable) :
  is_galois F E :=
begin
  haveI hFE : finite_dimensional F E := polynomial.is_splitting_field.finite_dimensional E p,
  letI := classical.dec_eq E,
  let s := (p.map (algebra_map F E)).roots.to_finset,
  have adjoin_root : intermediate_field.adjoin F ↑s = ⊤,
  { apply intermediate_field.to_subalgebra_injective,
    rw [intermediate_field.top_to_subalgebra, ←top_le_iff, ←sp.adjoin_roots],
    apply intermediate_field.algebra_adjoin_le_adjoin, },
  let P : intermediate_field F E → Prop := λ K, fintype.card (K →ₐ[F] E) = finrank F K,
  suffices : P (intermediate_field.adjoin F ↑s),
  { rw adjoin_root at this,
    apply of_card_aut_eq_finrank,
    rw ← eq.trans this (linear_equiv.finrank_eq intermediate_field.top_equiv.to_linear_equiv),
    exact fintype.card_congr (equiv.trans (alg_equiv_equiv_alg_hom F E)
      (alg_equiv.arrow_congr intermediate_field.top_equiv.symm alg_equiv.refl)) },
  apply intermediate_field.induction_on_adjoin_finset s P,
  { have key := intermediate_field.card_alg_hom_adjoin_integral F
      (show is_integral F (0 : E), by exact is_integral_zero),
    rw [minpoly.zero, polynomial.nat_degree_X] at key,
    specialize key polynomial.separable_X (polynomial.splits_X (algebra_map F E)),
    rw [←@subalgebra.finrank_bot F E _ _ _, ←intermediate_field.bot_to_subalgebra] at key,
    refine eq.trans _ key,
    apply fintype.card_congr,
    rw intermediate_field.adjoin_zero },
  intros K x hx hK,
  simp only [P] at *,
  rw [of_separable_splitting_field_aux hp K (multiset.mem_to_finset.mp hx),
    hK, finrank_mul_finrank],
  symmetry,
  exact linear_equiv.finrank_eq (alg_equiv.to_linear_equiv (intermediate_field.lift2_alg_equiv _))
end

/--Equivalent characterizations of a Galois extension of finite degree-/
theorem tfae [finite_dimensional F E] :
  tfae [is_galois F E,
    intermediate_field.fixed_field (⊤ : subgroup (E ≃ₐ[F] E)) = ⊥,
    fintype.card (E ≃ₐ[F] E) = finrank F E,
    ∃ p : F[X], p.separable ∧ p.is_splitting_field F E] :=
begin
  tfae_have : 1 → 2,
  { exact λ h, order_iso.map_bot (@intermediate_field_equiv_subgroup F _ E _ _ _ h).symm },
  tfae_have : 1 → 3,
  { introI _, exact card_aut_eq_finrank F E },
  tfae_have : 1 → 4,
  { introI _, exact is_separable_splitting_field F E },
  tfae_have : 2 → 1,
  { exact of_fixed_field_eq_bot F E },
  tfae_have : 3 → 1,
  { exact of_card_aut_eq_finrank F E },
  tfae_have : 4 → 1,
  { rintros ⟨h, hp1, _⟩, exactI of_separable_splitting_field hp1 },
  tfae_finish,
end

end is_galois

end galois_equivalent_definitions


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem mathd_numbertheory_254 :
  (239 + 174 + 83) % 10 = 6 :=
begin
  norm_num,
end


/-
Copyright (c) 2019 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin, Floris van Doorn

! This file was ported from Lean 3 source module data.set.pointwise.finite
! leanprover-community/mathlib commit c941bb9426d62e266612b6d99e6c9fc93e7a1d07
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Set.Finite
import Mathbin.Data.Set.Pointwise.Smul

/-!
# Finiteness lemmas for pointwise operations on sets

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

open Pointwise

variable {F α β γ : Type _}

namespace Set

section One

variable [One α]

#print Set.finite_one /-
@[simp, to_additive]
theorem finite_one : (1 : Set α).Finite :=
  finite_singleton _
#align set.finite_one Set.finite_one
#align set.finite_zero Set.finite_zero
-/

end One

section InvolutiveInv

variable [InvolutiveInv α] {s : Set α}

/- warning: set.finite.inv -> Set.Finite.inv is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : InvolutiveInv.{u1} α] {s : Set.{u1} α}, (Set.Finite.{u1} α s) -> (Set.Finite.{u1} α (Inv.inv.{u1} (Set.{u1} α) (Set.inv.{u1} α (InvolutiveInv.toHasInv.{u1} α _inst_1)) s))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : InvolutiveInv.{u1} α] {s : Set.{u1} α}, (Set.Finite.{u1} α s) -> (Set.Finite.{u1} α (Inv.inv.{u1} (Set.{u1} α) (Set.inv.{u1} α (InvolutiveInv.toInv.{u1} α _inst_1)) s))
Case conversion may be inaccurate. Consider using '#align set.finite.inv Set.Finite.invₓ'. -/
@[to_additive]
theorem Finite.inv (hs : s.Finite) : s⁻¹.Finite :=
  hs.Preimage <| inv_injective.InjOn _
#align set.finite.inv Set.Finite.inv
#align set.finite.neg Set.Finite.neg

end InvolutiveInv

section Mul

variable [Mul α] {s t : Set α}

#print Set.Finite.mul /-
@[to_additive]
theorem Finite.mul : s.Finite → t.Finite → (s * t).Finite :=
  Finite.image2 _
#align set.finite.mul Set.Finite.mul
#align set.finite.add Set.Finite.add
-/

#print Set.fintypeMul /-
/-- Multiplication preserves finiteness. -/
@[to_additive "Addition preserves finiteness."]
def fintypeMul [DecidableEq α] (s t : Set α) [Fintype s] [Fintype t] : Fintype (s * t : Set α) :=
  Set.fintypeImage2 _ _ _
#align set.fintype_mul Set.fintypeMul
#align set.fintype_add Set.fintypeAdd
-/

end Mul

section Monoid

variable [Monoid α] {s t : Set α}

/- warning: set.decidable_mem_mul -> Set.decidableMemMul is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {s : Set.{u1} α} {t : Set.{u1} α} [_inst_2 : Fintype.{u1} α] [_inst_3 : DecidableEq.{succ u1} α] [_inst_4 : DecidablePred.{succ u1} α (fun (_x : α) => Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) _x s)] [_inst_5 : DecidablePred.{succ u1} α (fun (_x : α) => Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) _x t)], DecidablePred.{succ u1} α (fun (_x : α) => Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) _x (HMul.hMul.{u1, u1, u1} (Set.{u1} α) (Set.{u1} α) (Set.{u1} α) (instHMul.{u1} (Set.{u1} α) (Set.mul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)))) s t))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {s : Set.{u1} α} {t : Set.{u1} α} [_inst_2 : Fintype.{u1} α] [_inst_3 : DecidableEq.{succ u1} α] [_inst_4 : DecidablePred.{succ u1} α (fun (_x : α) => Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) _x s)] [_inst_5 : DecidablePred.{succ u1} α (fun (_x : α) => Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) _x t)], DecidablePred.{succ u1} α (fun (_x : α) => Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) _x (HMul.hMul.{u1, u1, u1} (Set.{u1} α) (Set.{u1} α) (Set.{u1} α) (instHMul.{u1} (Set.{u1} α) (Set.mul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)))) s t))
Case conversion may be inaccurate. Consider using '#align set.decidable_mem_mul Set.decidableMemMulₓ'. -/
@[to_additive]
instance decidableMemMul [Fintype α] [DecidableEq α] [DecidablePred (· ∈ s)]
    [DecidablePred (· ∈ t)] : DecidablePred (· ∈ s * t) := fun _ => decidable_of_iff _ mem_mul.symm
#align set.decidable_mem_mul Set.decidableMemMul
#align set.decidable_mem_add Set.decidableMemAdd

/- warning: set.decidable_mem_pow -> Set.decidableMemPow is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {s : Set.{u1} α} [_inst_2 : Fintype.{u1} α] [_inst_3 : DecidableEq.{succ u1} α] [_inst_4 : DecidablePred.{succ u1} α (fun (_x : α) => Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) _x s)] (n : Nat), DecidablePred.{succ u1} α (fun (_x : α) => Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) _x (HPow.hPow.{u1, 0, u1} (Set.{u1} α) Nat (Set.{u1} α) (instHPow.{u1, 0} (Set.{u1} α) Nat (Set.NPow.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)) (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)))) s n))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {s : Set.{u1} α} [_inst_2 : Fintype.{u1} α] [_inst_3 : DecidableEq.{succ u1} α] [_inst_4 : DecidablePred.{succ u1} α (fun (_x : α) => Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) _x s)] (n : Nat), DecidablePred.{succ u1} α (fun (_x : α) => Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) _x (HPow.hPow.{u1, 0, u1} (Set.{u1} α) Nat (Set.{u1} α) (instHPow.{u1, 0} (Set.{u1} α) Nat (Set.NPow.{u1} α (Monoid.toOne.{u1} α _inst_1) (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)))) s n))
Case conversion may be inaccurate. Consider using '#align set.decidable_mem_pow Set.decidableMemPowₓ'. -/
@[to_additive]
instance decidableMemPow [Fintype α] [DecidableEq α] [DecidablePred (· ∈ s)] (n : ℕ) :
    DecidablePred (· ∈ s ^ n) := by
  induction' n with n ih
  · simp_rw [pow_zero, mem_one]
    infer_instance
  · letI := ih
    rw [pow_succ]
    infer_instance
#align set.decidable_mem_pow Set.decidableMemPow
#align set.decidable_mem_nsmul Set.decidableMemNSMul

end Monoid

section SMul

variable [SMul α β] {s : Set α} {t : Set β}

/- warning: set.finite.smul -> Set.Finite.smul is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : SMul.{u1, u2} α β] {s : Set.{u1} α} {t : Set.{u2} β}, (Set.Finite.{u1} α s) -> (Set.Finite.{u2} β t) -> (Set.Finite.{u2} β (SMul.smul.{u1, u2} (Set.{u1} α) (Set.{u2} β) (Set.smul.{u1, u2} α β _inst_1) s t))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : SMul.{u2, u1} α β] {s : Set.{u2} α} {t : Set.{u1} β}, (Set.Finite.{u2} α s) -> (Set.Finite.{u1} β t) -> (Set.Finite.{u1} β (HSMul.hSMul.{u2, u1, u1} (Set.{u2} α) (Set.{u1} β) (Set.{u1} β) (instHSMul.{u2, u1} (Set.{u2} α) (Set.{u1} β) (Set.smul.{u2, u1} α β _inst_1)) s t))
Case conversion may be inaccurate. Consider using '#align set.finite.smul Set.Finite.smulₓ'. -/
@[to_additive]
theorem Finite.smul : s.Finite → t.Finite → (s • t).Finite :=
  Finite.image2 _
#align set.finite.smul Set.Finite.smul
#align set.finite.vadd Set.Finite.vadd

end SMul

section HasSmulSet

variable [SMul α β] {s : Set β} {a : α}

#print Set.Finite.smul_set /-
@[to_additive]
theorem Finite.smul_set : s.Finite → (a • s).Finite :=
  Finite.image _
#align set.finite.smul_set Set.Finite.smul_set
#align set.finite.vadd_set Set.Finite.vadd_set
-/

@[to_additive]
theorem Infinite.of_smul_set : (a • s).Infinite → s.Infinite :=
  Infinite.of_image _
#align set.infinite.of_smul_set Set.Infinite.of_smul_set
#align set.infinite.of_vadd_set Set.Infinite.of_vadd_set

end HasSmulSet

section Vsub

variable [VSub α β] {s t : Set β}

include α

#print Set.Finite.vsub /-
theorem Finite.vsub (hs : s.Finite) (ht : t.Finite) : Set.Finite (s -ᵥ t) :=
  hs.image2 _ ht
#align set.finite.vsub Set.Finite.vsub
-/

end Vsub

section Cancel

variable [Mul α] [IsLeftCancelMul α] [IsRightCancelMul α] {s t : Set α}

@[to_additive]
theorem infinite_mul : (s * t).Infinite ↔ s.Infinite ∧ t.Nonempty ∨ t.Infinite ∧ s.Nonempty :=
  infinite_image2 (fun _ _ => (mul_left_injective _).InjOn _) fun _ _ =>
    (mul_right_injective _).InjOn _
#align set.infinite_mul Set.infinite_mul
#align set.infinite_add Set.infinite_add

end Cancel

section Group

variable [Group α] [MulAction α β] {a : α} {s : Set β}

@[simp, to_additive]
theorem finite_smul_set : (a • s).Finite ↔ s.Finite :=
  finite_image_iff <| (MulAction.injective _).InjOn _
#align set.finite_smul_set Set.finite_smul_set
#align set.finite_vadd_set Set.finite_vadd_set

@[simp, to_additive]
theorem infinite_smul_set : (a • s).Infinite ↔ s.Infinite :=
  infinite_image_iff <| (MulAction.injective _).InjOn _
#align set.infinite_smul_set Set.infinite_smul_set
#align set.infinite_vadd_set Set.infinite_vadd_set

alias finite_smul_set ↔ finite.of_smul_set _
#align set.finite.of_smul_set Set.Finite.of_smul_set

alias infinite_smul_set ↔ _ infinite.smul_set
#align set.infinite.smul_set Set.Infinite.smul_set

attribute [to_additive] finite.of_smul_set infinite.smul_set

end Group

end Set

open Set

namespace Group

variable {G : Type _} [Group G] [Fintype G] (S : Set G)

/- warning: group.card_pow_eq_card_pow_card_univ -> Group.card_pow_eq_card_pow_card_univ is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] [_inst_2 : Fintype.{u1} G] (S : Set.{u1} G) [_inst_3 : forall (k : Nat), DecidablePred.{succ u1} G (fun (_x : G) => Membership.Mem.{u1, u1} G (Set.{u1} G) (Set.hasMem.{u1} G) _x (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (MulOneClass.toHasOne.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S k))] (k : Nat), (LE.le.{0} Nat Nat.hasLe (Fintype.card.{u1} G _inst_2) k) -> (Eq.{1} Nat (Fintype.card.{u1} (coeSort.{succ u1, succ (succ u1)} (Set.{u1} G) Type.{u1} (Set.hasCoeToSort.{u1} G) (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (MulOneClass.toHasOne.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S k)) (Subtype.fintype.{u1} G (fun (x : G) => Membership.Mem.{u1, u1} G (Set.{u1} G) (Set.hasMem.{u1} G) x (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (MulOneClass.toHasOne.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S k)) (fun (a : G) => _inst_3 k a) _inst_2)) (Fintype.card.{u1} (coeSort.{succ u1, succ (succ u1)} (Set.{u1} G) Type.{u1} (Set.hasCoeToSort.{u1} G) (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (MulOneClass.toHasOne.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S (Fintype.card.{u1} G _inst_2))) (Subtype.fintype.{u1} G (fun (x : G) => Membership.Mem.{u1, u1} G (Set.{u1} G) (Set.hasMem.{u1} G) x (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (MulOneClass.toHasOne.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))) (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S (Fintype.card.{u1} G _inst_2))) (fun (a : G) => _inst_3 (Fintype.card.{u1} G _inst_2) a) _inst_2)))
but is expected to have type
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] [_inst_2 : Fintype.{u1} G] (S : Set.{u1} G) [_inst_3 : forall (k : Nat), DecidablePred.{succ u1} G (fun (_x : G) => Membership.mem.{u1, u1} G (Set.{u1} G) (Set.instMembershipSet.{u1} G) _x (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (InvOneClass.toOne.{u1} G (DivInvOneMonoid.toInvOneClass.{u1} G (DivisionMonoid.toDivInvOneMonoid.{u1} G (Group.toDivisionMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S k))] (k : Nat), (LE.le.{0} Nat instLENat (Fintype.card.{u1} G _inst_2) k) -> (Eq.{1} Nat (Fintype.card.{u1} (Set.Elem.{u1} G (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (InvOneClass.toOne.{u1} G (DivInvOneMonoid.toInvOneClass.{u1} G (DivisionMonoid.toDivInvOneMonoid.{u1} G (Group.toDivisionMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S k)) (Subtype.fintype.{u1} G (fun (x : G) => Membership.mem.{u1, u1} G (Set.{u1} G) (Set.instMembershipSet.{u1} G) x (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (InvOneClass.toOne.{u1} G (DivInvOneMonoid.toInvOneClass.{u1} G (DivisionMonoid.toDivInvOneMonoid.{u1} G (Group.toDivisionMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S k)) (fun (a : G) => _inst_3 k a) _inst_2)) (Fintype.card.{u1} (Set.Elem.{u1} G (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (InvOneClass.toOne.{u1} G (DivInvOneMonoid.toInvOneClass.{u1} G (DivisionMonoid.toDivInvOneMonoid.{u1} G (Group.toDivisionMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S (Fintype.card.{u1} G _inst_2))) (Subtype.fintype.{u1} G (fun (x : G) => Membership.mem.{u1, u1} G (Set.{u1} G) (Set.instMembershipSet.{u1} G) x (HPow.hPow.{u1, 0, u1} (Set.{u1} G) Nat (Set.{u1} G) (instHPow.{u1, 0} (Set.{u1} G) Nat (Set.NPow.{u1} G (InvOneClass.toOne.{u1} G (DivInvOneMonoid.toInvOneClass.{u1} G (DivisionMonoid.toDivInvOneMonoid.{u1} G (Group.toDivisionMonoid.{u1} G _inst_1)))) (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)))))) S (Fintype.card.{u1} G _inst_2))) (fun (a : G) => _inst_3 (Fintype.card.{u1} G _inst_2) a) _inst_2)))
Case conversion may be inaccurate. Consider using '#align group.card_pow_eq_card_pow_card_univ Group.card_pow_eq_card_pow_card_univₓ'. -/
@[to_additive]
theorem card_pow_eq_card_pow_card_univ [∀ k : ℕ, DecidablePred (· ∈ S ^ k)] :
    ∀ k, Fintype.card G ≤ k → Fintype.card ↥(S ^ k) = Fintype.card ↥(S ^ Fintype.card G) :=
  by
  have hG : 0 < Fintype.card G := fintype.card_pos_iff.mpr ⟨1⟩
  by_cases hS : S = ∅
  · refine' fun k hk => Fintype.card_congr _
    rw [hS, empty_pow (ne_of_gt (lt_of_lt_of_le hG hk)), empty_pow (ne_of_gt hG)]
  obtain ⟨a, ha⟩ := Set.nonempty_iff_ne_empty.2 hS
  classical!
  have key : ∀ (a) (s t : Set G), (∀ b : G, b ∈ s → a * b ∈ t) → Fintype.card s ≤ Fintype.card t :=
    by
    refine' fun a s t h => Fintype.card_le_of_injective (fun ⟨b, hb⟩ => ⟨a * b, h b hb⟩) _
    rintro ⟨b, hb⟩ ⟨c, hc⟩ hbc
    exact Subtype.ext (mul_left_cancel (subtype.ext_iff.mp hbc))
  have mono : Monotone (fun n => Fintype.card ↥(S ^ n) : ℕ → ℕ) :=
    monotone_nat_of_le_succ fun n => key a _ _ fun b hb => Set.mul_mem_mul ha hb
  convert card_pow_eq_card_pow_card_univ_aux mono (fun n => set_fintype_card_le_univ (S ^ n))
      fun n h => le_antisymm (mono (n + 1).le_succ) (key a⁻¹ _ _ _)
  · simp only [[anonymous], Fintype.card_ofFinset]
  replace h : {a} * S ^ n = S ^ (n + 1)
  · refine' Set.eq_of_subset_of_card_le _ (le_trans (ge_of_eq h) _)
    · exact mul_subset_mul (set.singleton_subset_iff.mpr ha) Set.Subset.rfl
    · convert key a (S ^ n) ({a} * S ^ n) fun b hb => Set.mul_mem_mul (Set.mem_singleton a) hb
  rw [pow_succ', ← h, mul_assoc, ← pow_succ', h]
  rintro _ ⟨b, c, hb, hc, rfl⟩
  rwa [set.mem_singleton_iff.mp hb, inv_mul_cancel_left]
#align group.card_pow_eq_card_pow_card_univ Group.card_pow_eq_card_pow_card_univ
#align add_group.card_nsmul_eq_card_nsmul_card_univ AddGroup.card_nsmul_eq_card_nsmul_card_univ

end Group


/-
Copyright (c) 2020 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import data.prod.basic
import data.subtype
import logic.function.basic
import logic.unique

/-!
# Nontrivial types

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

A type is *nontrivial* if it contains at least two elements. This is useful in particular for rings
(where it is equivalent to the fact that zero is different from one) and for vector spaces
(where it is equivalent to the fact that the dimension is positive).

We introduce a typeclass `nontrivial` formalizing this property.
-/

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

open_locale classical

/-- Predicate typeclass for expressing that a type is not reduced to a single element. In rings,
this is equivalent to `0 ≠ 1`. In vector spaces, this is equivalent to positive dimension. -/
class nontrivial (α : Type*) : Prop :=
(exists_pair_ne : ∃ (x y : α), x ≠ y)

lemma nontrivial_iff : nontrivial α ↔ ∃ (x y : α), x ≠ y :=
⟨λ h, h.exists_pair_ne, λ h, ⟨h⟩⟩

lemma exists_pair_ne (α : Type*) [nontrivial α] : ∃ (x y : α), x ≠ y :=
nontrivial.exists_pair_ne

-- See Note [decidable namespace]
protected lemma decidable.exists_ne [nontrivial α] [decidable_eq α] (x : α) : ∃ y, y ≠ x :=
begin
  rcases exists_pair_ne α with ⟨y, y', h⟩,
  by_cases hx : x = y,
  { rw ← hx at h,
    exact ⟨y', h.symm⟩ },
  { exact ⟨y, ne.symm hx⟩ }
end

lemma exists_ne [nontrivial α] (x : α) : ∃ y, y ≠ x :=
by classical; exact decidable.exists_ne x

-- `x` and `y` are explicit here, as they are often needed to guide typechecking of `h`.
lemma nontrivial_of_ne (x y : α) (h : x ≠ y) : nontrivial α :=
⟨⟨x, y, h⟩⟩

-- `x` and `y` are explicit here, as they are often needed to guide typechecking of `h`.
lemma nontrivial_of_lt [preorder α] (x y : α) (h : x < y) : nontrivial α :=
⟨⟨x, y, ne_of_lt h⟩⟩

lemma exists_pair_lt (α : Type*) [nontrivial α] [linear_order α] : ∃ (x y : α), x < y :=
begin
  rcases exists_pair_ne α with ⟨x, y, hxy⟩,
  cases lt_or_gt_of_ne hxy;
  exact ⟨_, _, h⟩
end

lemma nontrivial_iff_lt [linear_order α] : nontrivial α ↔ ∃ (x y : α), x < y :=
⟨λ h, @exists_pair_lt α h _, λ ⟨x, y, h⟩, nontrivial_of_lt x y h⟩

lemma nontrivial_iff_exists_ne (x : α) : nontrivial α ↔ ∃ y, y ≠ x :=
⟨λ h, @exists_ne α h x, λ ⟨y, hy⟩, nontrivial_of_ne _ _ hy⟩

lemma subtype.nontrivial_iff_exists_ne (p : α → Prop) (x : subtype p) :
  nontrivial (subtype p) ↔ ∃ (y : α) (hy : p y), y ≠ x :=
by simp only [nontrivial_iff_exists_ne x, subtype.exists, ne.def, subtype.ext_iff, subtype.coe_mk]

instance : nontrivial Prop := ⟨⟨true, false, true_ne_false⟩⟩

/--
See Note [lower instance priority]

Note that since this and `nonempty_of_inhabited` are the most "obvious" way to find a nonempty
instance if no direct instance can be found, we give this a higher priority than the usual `100`.
-/
@[priority 500]
instance nontrivial.to_nonempty [nontrivial α] : nonempty α :=
let ⟨x, _⟩ := exists_pair_ne α in ⟨x⟩

attribute [instance, priority 500] nonempty_of_inhabited

/-- An inhabited type is either nontrivial, or has a unique element. -/
noncomputable def nontrivial_psum_unique (α : Type*) [inhabited α] :
  psum (nontrivial α) (unique α) :=
if h : nontrivial α then psum.inl h else psum.inr
{ default := default,
  uniq := λ (x : α),
  begin
    change x = default,
    contrapose! h,
    use [x, default]
  end }

lemma subsingleton_iff : subsingleton α ↔ ∀ (x y : α), x = y :=
⟨by { introsI h, exact subsingleton.elim }, λ h, ⟨h⟩⟩

lemma not_nontrivial_iff_subsingleton : ¬(nontrivial α) ↔ subsingleton α :=
by { rw [nontrivial_iff, subsingleton_iff], push_neg, refl }

lemma not_nontrivial (α) [subsingleton α] : ¬nontrivial α :=
λ ⟨⟨x, y, h⟩⟩, h $ subsingleton.elim x y

lemma not_subsingleton (α) [h : nontrivial α] : ¬subsingleton α :=
let ⟨⟨x, y, hxy⟩⟩ := h in λ ⟨h'⟩, hxy $ h' x y

/-- A type is either a subsingleton or nontrivial. -/
lemma subsingleton_or_nontrivial (α : Type*) : subsingleton α ∨ nontrivial α :=
by { rw [← not_nontrivial_iff_subsingleton, or_comm], exact classical.em _ }

lemma false_of_nontrivial_of_subsingleton (α : Type*) [nontrivial α] [subsingleton α] : false :=
let ⟨x, y, h⟩ := exists_pair_ne α in h $ subsingleton.elim x y

instance option.nontrivial [nonempty α] : nontrivial (option α) :=
by { inhabit α, use [none, some default] }

/-- Pushforward a `nontrivial` instance along an injective function. -/
protected lemma function.injective.nontrivial [nontrivial α]
  {f : α → β} (hf : function.injective f) : nontrivial β :=
let ⟨x, y, h⟩ := exists_pair_ne α in ⟨⟨f x, f y, hf.ne h⟩⟩

/-- Pullback a `nontrivial` instance along a surjective function. -/
protected lemma function.surjective.nontrivial [nontrivial β]
  {f : α → β} (hf : function.surjective f) : nontrivial α :=
begin
  rcases exists_pair_ne β with ⟨x, y, h⟩,
  rcases hf x with ⟨x', hx'⟩,
  rcases hf y with ⟨y', hy'⟩,
  have : x' ≠ y', by { contrapose! h, rw [← hx', ← hy', h] },
  exact ⟨⟨x', y', this⟩⟩
end

/-- An injective function from a nontrivial type has an argument at
which it does not take a given value. -/
protected lemma function.injective.exists_ne [nontrivial α] {f : α → β}
  (hf : function.injective f) (y : β) : ∃ x, f x ≠ y :=
begin
  rcases exists_pair_ne α with ⟨x₁, x₂, hx⟩,
  by_cases h : f x₂ = y,
  { exact ⟨x₁, (hf.ne_iff' h).2 hx⟩ },
  { exact ⟨x₂, h⟩ }
end

instance nontrivial_prod_right [nonempty α] [nontrivial β] : nontrivial (α × β) :=
prod.snd_surjective.nontrivial

instance nontrivial_prod_left [nontrivial α] [nonempty β] : nontrivial (α × β) :=
prod.fst_surjective.nontrivial

namespace pi

variables {I : Type*} {f : I → Type*}

/-- A pi type is nontrivial if it's nonempty everywhere and nontrivial somewhere. -/
lemma nontrivial_at (i' : I) [inst : Π i, nonempty (f i)] [nontrivial (f i')] :
  nontrivial (Π i : I, f i) :=
by classical; exact
(function.update_injective (λ i, classical.choice (inst i)) i').nontrivial

/--
As a convenience, provide an instance automatically if `(f default)` is nontrivial.

If a different index has the non-trivial type, then use `haveI := nontrivial_at that_index`.
-/
instance nontrivial [inhabited I] [inst : Π i, nonempty (f i)] [nontrivial (f default)] :
  nontrivial (Π i : I, f i) := nontrivial_at default

end pi

instance function.nontrivial [h : nonempty α] [nontrivial β] : nontrivial (α → β) :=
h.elim $ λ a, pi.nontrivial_at a

mk_simp_attribute nontriviality "Simp lemmas for `nontriviality` tactic"

protected lemma subsingleton.le [preorder α] [subsingleton α] (x y : α) : x ≤ y :=
le_of_eq (subsingleton.elim x y)

attribute [nontriviality] eq_iff_true_of_subsingleton subsingleton.le

namespace bool

instance : nontrivial bool := ⟨⟨tt,ff, tt_eq_ff_eq_false⟩⟩

end bool