/-
Copyright (c) 2018 Sean Leather. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sean Leather, Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.list.alist
import Mathlib.data.finset.basic
import Mathlib.data.pfun
import Mathlib.PostPort

universes u v l u_1 w

namespace Mathlib

/-!
# Finite maps over `multiset`
-/

/-! ### multisets of sigma types-/

namespace multiset

/-- Multiset of keys of an association multiset. -/
def keys {α : Type u} {β : α → Type v} (s : multiset (sigma β)) : multiset α :=
  map sigma.fst s

@[simp] theorem coe_keys {α : Type u} {β : α → Type v} {l : List (sigma β)} : keys ↑l = ↑(list.keys l) :=
  rfl

/-- `nodupkeys s` means that `s` has no duplicate keys. -/
def nodupkeys {α : Type u} {β : α → Type v} (s : multiset (sigma β)) :=
  quot.lift_on s list.nodupkeys sorry

@[simp] theorem coe_nodupkeys {α : Type u} {β : α → Type v} {l : List (sigma β)} : nodupkeys ↑l ↔ list.nodupkeys l :=
  iff.rfl

end multiset

/-! ### finmap -/

/-- `finmap β` is the type of finite maps over a multiset. It is effectively
  a quotient of `alist β` by permutation of the underlying list. -/
structure finmap {α : Type u} (β : α → Type v)
where
  entries : multiset (sigma β)
  nodupkeys : multiset.nodupkeys entries

/-- The quotient map from `alist` to `finmap`. -/
def alist.to_finmap {α : Type u} {β : α → Type v} (s : alist β) : finmap β :=
  finmap.mk (↑(alist.entries s)) (alist.nodupkeys s)

theorem alist.to_finmap_eq {α : Type u} {β : α → Type v} {s₁ : alist β} {s₂ : alist β} : alist.to_finmap s₁ = alist.to_finmap s₂ ↔ alist.entries s₁ ~ alist.entries s₂ := sorry

@[simp] theorem alist.to_finmap_entries {α : Type u} {β : α → Type v} (s : alist β) : finmap.entries (alist.to_finmap s) = ↑(alist.entries s) :=
  rfl

/-- Given `l : list (sigma β)`, create a term of type `finmap β` by removing
entries with duplicate keys. -/
def list.to_finmap {α : Type u} {β : α → Type v} [DecidableEq α] (s : List (sigma β)) : finmap β :=
  alist.to_finmap (list.to_alist s)

namespace finmap

/-! ### lifting from alist -/

/-- Lift a permutation-respecting function on `alist` to `finmap`. -/
def lift_on {α : Type u} {β : α → Type v} {γ : Type u_1} (s : finmap β) (f : alist β → γ) (H : ∀ (a b : alist β), alist.entries a ~ alist.entries b → f a = f b) : γ :=
  roption.get
    (quotient.lift_on (entries s)
      (fun (l : List (sigma β)) => roption.mk (list.nodupkeys l) fun (nd : list.nodupkeys l) => f (alist.mk l nd)) sorry)
    sorry

@[simp] theorem lift_on_to_finmap {α : Type u} {β : α → Type v} {γ : Type u_1} (s : alist β) (f : alist β → γ) (H : ∀ (a b : alist β), alist.entries a ~ alist.entries b → f a = f b) : lift_on (alist.to_finmap s) f H = f s :=
  alist.cases_on s
    fun (s_entries : List (sigma β)) (s_nodupkeys : list.nodupkeys s_entries) =>
      Eq.refl (lift_on (alist.to_finmap (alist.mk s_entries s_nodupkeys)) f H)

/-- Lift a permutation-respecting function on 2 `alist`s to 2 `finmap`s. -/
def lift_on₂ {α : Type u} {β : α → Type v} {γ : Type u_1} (s₁ : finmap β) (s₂ : finmap β) (f : alist β → alist β → γ) (H : ∀ (a₁ b₁ a₂ b₂ : alist β), alist.entries a₁ ~ alist.entries a₂ → alist.entries b₁ ~ alist.entries b₂ → f a₁ b₁ = f a₂ b₂) : γ :=
  lift_on s₁ (fun (l₁ : alist β) => lift_on s₂ (f l₁) sorry) sorry

@[simp] theorem lift_on₂_to_finmap {α : Type u} {β : α → Type v} {γ : Type u_1} (s₁ : alist β) (s₂ : alist β) (f : alist β → alist β → γ) (H : ∀ (a₁ b₁ a₂ b₂ : alist β), alist.entries a₁ ~ alist.entries a₂ → alist.entries b₁ ~ alist.entries b₂ → f a₁ b₁ = f a₂ b₂) : lift_on₂ (alist.to_finmap s₁) (alist.to_finmap s₂) f H = f s₁ s₂ := sorry

/-! ### induction -/

theorem induction_on {α : Type u} {β : α → Type v} {C : finmap β → Prop} (s : finmap β) (H : ∀ (a : alist β), C (alist.to_finmap a)) : C s := sorry

theorem induction_on₂ {α : Type u} {β : α → Type v} {C : finmap β → finmap β → Prop} (s₁ : finmap β) (s₂ : finmap β) (H : ∀ (a₁ a₂ : alist β), C (alist.to_finmap a₁) (alist.to_finmap a₂)) : C s₁ s₂ :=
  induction_on s₁ fun (l₁ : alist β) => induction_on s₂ fun (l₂ : alist β) => H l₁ l₂

theorem induction_on₃ {α : Type u} {β : α → Type v} {C : finmap β → finmap β → finmap β → Prop} (s₁ : finmap β) (s₂ : finmap β) (s₃ : finmap β) (H : ∀ (a₁ a₂ a₃ : alist β), C (alist.to_finmap a₁) (alist.to_finmap a₂) (alist.to_finmap a₃)) : C s₁ s₂ s₃ :=
  induction_on₂ s₁ s₂ fun (l₁ l₂ : alist β) => induction_on s₃ fun (l₃ : alist β) => H l₁ l₂ l₃

/-! ### extensionality -/

theorem ext {α : Type u} {β : α → Type v} {s : finmap β} {t : finmap β} : entries s = entries t → s = t := sorry

@[simp] theorem ext_iff {α : Type u} {β : α → Type v} {s : finmap β} {t : finmap β} : entries s = entries t ↔ s = t :=
  { mp := ext, mpr := congr_arg fun {s : finmap β} => entries s }

/-! ### mem -/

/-- The predicate `a ∈ s` means that `s` has a value associated to the key `a`. -/
protected instance has_mem {α : Type u} {β : α → Type v} : has_mem α (finmap β) :=
  has_mem.mk fun (a : α) (s : finmap β) => a ∈ multiset.keys (entries s)

theorem mem_def {α : Type u} {β : α → Type v} {a : α} {s : finmap β} : a ∈ s ↔ a ∈ multiset.keys (entries s) :=
  iff.rfl

@[simp] theorem mem_to_finmap {α : Type u} {β : α → Type v} {a : α} {s : alist β} : a ∈ alist.to_finmap s ↔ a ∈ s :=
  iff.rfl

/-! ### keys -/

/-- The set of keys of a finite map. -/
def keys {α : Type u} {β : α → Type v} (s : finmap β) : finset α :=
  finset.mk (multiset.keys (entries s)) sorry

@[simp] theorem keys_val {α : Type u} {β : α → Type v} (s : alist β) : finset.val (keys (alist.to_finmap s)) = ↑(alist.keys s) :=
  rfl

@[simp] theorem keys_ext {α : Type u} {β : α → Type v} {s₁ : alist β} {s₂ : alist β} : keys (alist.to_finmap s₁) = keys (alist.to_finmap s₂) ↔ alist.keys s₁ ~ alist.keys s₂ := sorry

theorem mem_keys {α : Type u} {β : α → Type v} {a : α} {s : finmap β} : a ∈ keys s ↔ a ∈ s :=
  induction_on s fun (s : alist β) => alist.mem_keys

/-! ### empty -/

/-- The empty map. -/
protected instance has_emptyc {α : Type u} {β : α → Type v} : has_emptyc (finmap β) :=
  has_emptyc.mk (mk 0 list.nodupkeys_nil)

protected instance inhabited {α : Type u} {β : α → Type v} : Inhabited (finmap β) :=
  { default := ∅ }

@[simp] theorem empty_to_finmap {α : Type u} {β : α → Type v} : alist.to_finmap ∅ = ∅ :=
  rfl

@[simp] theorem to_finmap_nil {α : Type u} {β : α → Type v} [DecidableEq α] : list.to_finmap [] = ∅ :=
  rfl

theorem not_mem_empty {α : Type u} {β : α → Type v} {a : α} : ¬a ∈ ∅ :=
  multiset.not_mem_zero a

@[simp] theorem keys_empty {α : Type u} {β : α → Type v} : keys ∅ = ∅ :=
  rfl

/-! ### singleton -/

/-- The singleton map. -/
def singleton {α : Type u} {β : α → Type v} (a : α) (b : β a) : finmap β :=
  alist.to_finmap (alist.singleton a b)

@[simp] theorem keys_singleton {α : Type u} {β : α → Type v} (a : α) (b : β a) : keys (singleton a b) = singleton a :=
  rfl

@[simp] theorem mem_singleton {α : Type u} {β : α → Type v} (x : α) (y : α) (b : β y) : x ∈ singleton y b ↔ x = y := sorry

protected instance has_decidable_eq {α : Type u} {β : α → Type v} [DecidableEq α] [(a : α) → DecidableEq (β a)] : DecidableEq (finmap β) :=
  sorry

/-! ### lookup -/

/-- Look up the value associated to a key in a map. -/
def lookup {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : finmap β) : Option (β a) :=
  lift_on s (alist.lookup a) sorry

@[simp] theorem lookup_to_finmap {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : alist β) : lookup a (alist.to_finmap s) = alist.lookup a s :=
  rfl

@[simp] theorem lookup_list_to_finmap {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : List (sigma β)) : lookup a (list.to_finmap s) = list.lookup a s := sorry

@[simp] theorem lookup_empty {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) : lookup a ∅ = none :=
  rfl

theorem lookup_is_some {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {s : finmap β} : ↥(option.is_some (lookup a s)) ↔ a ∈ s :=
  induction_on s fun (s : alist β) => alist.lookup_is_some

theorem lookup_eq_none {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {s : finmap β} : lookup a s = none ↔ ¬a ∈ s :=
  induction_on s fun (s : alist β) => alist.lookup_eq_none

@[simp] theorem lookup_singleton_eq {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {b : β a} : lookup a (singleton a b) = some b := sorry

protected instance has_mem.mem.decidable {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : finmap β) : Decidable (a ∈ s) :=
  decidable_of_iff ↥(option.is_some (lookup a s)) sorry

theorem mem_iff {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {s : finmap β} : a ∈ s ↔ ∃ (b : β a), lookup a s = some b :=
  induction_on s
    fun (s : alist β) =>
      iff.trans list.mem_keys (exists_congr fun (b : β a) => iff.symm (list.mem_lookup_iff (alist.nodupkeys s)))

theorem mem_of_lookup_eq_some {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {b : β a} {s : finmap β} (h : lookup a s = some b) : a ∈ s :=
  iff.mpr mem_iff (Exists.intro b h)

theorem ext_lookup {α : Type u} {β : α → Type v} [DecidableEq α] {s₁ : finmap β} {s₂ : finmap β} : (∀ (x : α), lookup x s₁ = lookup x s₂) → s₁ = s₂ := sorry

/-! ### replace -/

/-- Replace a key with a given value in a finite map.
  If the key is not present it does nothing. -/
def replace {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (b : β a) (s : finmap β) : finmap β :=
  lift_on s (fun (t : alist β) => alist.to_finmap (alist.replace a b t)) sorry

@[simp] theorem replace_to_finmap {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (b : β a) (s : alist β) : replace a b (alist.to_finmap s) = alist.to_finmap (alist.replace a b s) := sorry

@[simp] theorem keys_replace {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (b : β a) (s : finmap β) : keys (replace a b s) = keys s := sorry

@[simp] theorem mem_replace {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {a' : α} {b : β a} {s : finmap β} : a' ∈ replace a b s ↔ a' ∈ s := sorry

/-! ### foldl -/

/-- Fold a commutative function over the key-value pairs in the map -/
def foldl {α : Type u} {β : α → Type v} {δ : Type w} (f : δ → (a : α) → β a → δ) (H : ∀ (d : δ) (a₁ : α) (b₁ : β a₁) (a₂ : α) (b₂ : β a₂), f (f d a₁ b₁) a₂ b₂ = f (f d a₂ b₂) a₁ b₁) (d : δ) (m : finmap β) : δ :=
  multiset.foldl (fun (d : δ) (s : sigma β) => f d (sigma.fst s) (sigma.snd s)) sorry d (entries m)

/-- `any f s` returns `tt` iff there exists a value `v` in `s` such that `f v = tt`. -/
def any {α : Type u} {β : α → Type v} (f : (x : α) → β x → Bool) (s : finmap β) : Bool :=
  foldl (fun (x : Bool) (y : α) (z : β y) => to_bool (↥x ∨ ↥(f y z))) sorry false s

/-- `all f s` returns `tt` iff `f v = tt` for all values `v` in `s`. -/
def all {α : Type u} {β : α → Type v} (f : (x : α) → β x → Bool) (s : finmap β) : Bool :=
  foldl (fun (x : Bool) (y : α) (z : β y) => to_bool (↥x ∧ ↥(f y z))) sorry false s

/-! ### erase -/

/-- Erase a key from the map. If the key is not present it does nothing. -/
def erase {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : finmap β) : finmap β :=
  lift_on s (fun (t : alist β) => alist.to_finmap (alist.erase a t)) sorry

@[simp] theorem erase_to_finmap {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : alist β) : erase a (alist.to_finmap s) = alist.to_finmap (alist.erase a s) := sorry

@[simp] theorem keys_erase_to_finset {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : alist β) : keys (alist.to_finmap (alist.erase a s)) = finset.erase (keys (alist.to_finmap s)) a := sorry

@[simp] theorem keys_erase {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : finmap β) : keys (erase a s) = finset.erase (keys s) a := sorry

@[simp] theorem mem_erase {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {a' : α} {s : finmap β} : a' ∈ erase a s ↔ a' ≠ a ∧ a' ∈ s := sorry

theorem not_mem_erase_self {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {s : finmap β} : ¬a ∈ erase a s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (¬a ∈ erase a s)) (propext mem_erase)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (¬(a ≠ a ∧ a ∈ s))) (propext not_and_distrib)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (¬a ≠ a ∨ ¬a ∈ s)) (propext not_not))) (Or.inl (Eq.refl a))))

@[simp] theorem lookup_erase {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : finmap β) : lookup a (erase a s) = none :=
  induction_on s (alist.lookup_erase a)

@[simp] theorem lookup_erase_ne {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {a' : α} {s : finmap β} (h : a ≠ a') : lookup a (erase a' s) = lookup a s :=
  induction_on s fun (s : alist β) => alist.lookup_erase_ne h

theorem erase_erase {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {a' : α} {s : finmap β} : erase a (erase a' s) = erase a' (erase a s) := sorry

/-! ### sdiff -/

/-- `sdiff s s'` consists of all key-value pairs from `s` and `s'` where the keys are in `s` or
`s'` but not both. -/
def sdiff {α : Type u} {β : α → Type v} [DecidableEq α] (s : finmap β) (s' : finmap β) : finmap β :=
  foldl (fun (s : finmap β) (x : α) (_x : β x) => erase x s) sorry s s'

protected instance has_sdiff {α : Type u} {β : α → Type v} [DecidableEq α] : has_sdiff (finmap β) :=
  has_sdiff.mk sdiff

/-! ### insert -/

/-- Insert a key-value pair into a finite map, replacing any existing pair with
  the same key. -/
def insert {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (b : β a) (s : finmap β) : finmap β :=
  lift_on s (fun (t : alist β) => alist.to_finmap (alist.insert a b t)) sorry

@[simp] theorem insert_to_finmap {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (b : β a) (s : alist β) : insert a b (alist.to_finmap s) = alist.to_finmap (alist.insert a b s) := sorry

theorem insert_entries_of_neg {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {b : β a} {s : finmap β} : ¬a ∈ s → entries (insert a b s) = sigma.mk a b ::ₘ entries s := sorry

@[simp] theorem mem_insert {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {a' : α} {b' : β a'} {s : finmap β} : a ∈ insert a' b' s ↔ a = a' ∨ a ∈ s :=
  induction_on s alist.mem_insert

@[simp] theorem lookup_insert {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {b : β a} (s : finmap β) : lookup a (insert a b s) = some b := sorry

@[simp] theorem lookup_insert_of_ne {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {a' : α} {b : β a} (s : finmap β) (h : a' ≠ a) : lookup a' (insert a b s) = lookup a' s := sorry

@[simp] theorem insert_insert {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {b : β a} {b' : β a} (s : finmap β) : insert a b' (insert a b s) = insert a b' s := sorry

theorem insert_insert_of_ne {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {a' : α} {b : β a} {b' : β a'} (s : finmap β) (h : a ≠ a') : insert a' b' (insert a b s) = insert a b (insert a' b' s) := sorry

theorem to_finmap_cons {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (b : β a) (xs : List (sigma β)) : list.to_finmap (sigma.mk a b :: xs) = insert a b (list.to_finmap xs) :=
  rfl

theorem mem_list_to_finmap {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (xs : List (sigma β)) : a ∈ list.to_finmap xs ↔ ∃ (b : β a), sigma.mk a b ∈ xs := sorry

@[simp] theorem insert_singleton_eq {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {b : β a} {b' : β a} : insert a b (singleton a b') = singleton a b := sorry

/-! ### extract -/

/-- Erase a key from the map, and return the corresponding value, if found. -/
def extract {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : finmap β) : Option (β a) × finmap β :=
  lift_on s (fun (t : alist β) => prod.map id alist.to_finmap (alist.extract a t)) sorry

@[simp] theorem extract_eq_lookup_erase {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (s : finmap β) : extract a s = (lookup a s, erase a s) := sorry

/-! ### union -/

/-- `s₁ ∪ s₂` is the key-based union of two finite maps. It is left-biased: if
there exists an `a ∈ s₁`, `lookup a (s₁ ∪ s₂) = lookup a s₁`. -/
def union {α : Type u} {β : α → Type v} [DecidableEq α] (s₁ : finmap β) (s₂ : finmap β) : finmap β :=
  lift_on₂ s₁ s₂ (fun (s₁ s₂ : alist β) => alist.to_finmap (s₁ ∪ s₂)) sorry

protected instance has_union {α : Type u} {β : α → Type v} [DecidableEq α] : has_union (finmap β) :=
  has_union.mk union

@[simp] theorem mem_union {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {s₁ : finmap β} {s₂ : finmap β} : a ∈ s₁ ∪ s₂ ↔ a ∈ s₁ ∨ a ∈ s₂ :=
  induction_on₂ s₁ s₂ fun (_x _x_1 : alist β) => alist.mem_union

@[simp] theorem union_to_finmap {α : Type u} {β : α → Type v} [DecidableEq α] (s₁ : alist β) (s₂ : alist β) : alist.to_finmap s₁ ∪ alist.to_finmap s₂ = alist.to_finmap (s₁ ∪ s₂) := sorry

theorem keys_union {α : Type u} {β : α → Type v} [DecidableEq α] {s₁ : finmap β} {s₂ : finmap β} : keys (s₁ ∪ s₂) = keys s₁ ∪ keys s₂ := sorry

@[simp] theorem lookup_union_left {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {s₁ : finmap β} {s₂ : finmap β} : a ∈ s₁ → lookup a (s₁ ∪ s₂) = lookup a s₁ :=
  induction_on₂ s₁ s₂ fun (s₁ s₂ : alist β) => alist.lookup_union_left

@[simp] theorem lookup_union_right {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {s₁ : finmap β} {s₂ : finmap β} : ¬a ∈ s₁ → lookup a (s₁ ∪ s₂) = lookup a s₂ :=
  induction_on₂ s₁ s₂ fun (s₁ s₂ : alist β) => alist.lookup_union_right

theorem lookup_union_left_of_not_in {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {s₁ : finmap β} {s₂ : finmap β} (h : ¬a ∈ s₂) : lookup a (s₁ ∪ s₂) = lookup a s₁ := sorry

@[simp] theorem mem_lookup_union {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {b : β a} {s₁ : finmap β} {s₂ : finmap β} : b ∈ lookup a (s₁ ∪ s₂) ↔ b ∈ lookup a s₁ ∨ ¬a ∈ s₁ ∧ b ∈ lookup a s₂ :=
  induction_on₂ s₁ s₂ fun (s₁ s₂ : alist β) => alist.mem_lookup_union

theorem mem_lookup_union_middle {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {b : β a} {s₁ : finmap β} {s₂ : finmap β} {s₃ : finmap β} : b ∈ lookup a (s₁ ∪ s₃) → ¬a ∈ s₂ → b ∈ lookup a (s₁ ∪ s₂ ∪ s₃) :=
  induction_on₃ s₁ s₂ s₃ fun (s₁ s₂ s₃ : alist β) => alist.mem_lookup_union_middle

theorem insert_union {α : Type u} {β : α → Type v} [DecidableEq α] {a : α} {b : β a} {s₁ : finmap β} {s₂ : finmap β} : insert a b (s₁ ∪ s₂) = insert a b s₁ ∪ s₂ := sorry

theorem union_assoc {α : Type u} {β : α → Type v} [DecidableEq α] {s₁ : finmap β} {s₂ : finmap β} {s₃ : finmap β} : s₁ ∪ s₂ ∪ s₃ = s₁ ∪ (s₂ ∪ s₃) := sorry

@[simp] theorem empty_union {α : Type u} {β : α → Type v} [DecidableEq α] {s₁ : finmap β} : ∅ ∪ s₁ = s₁ := sorry

@[simp] theorem union_empty {α : Type u} {β : α → Type v} [DecidableEq α] {s₁ : finmap β} : s₁ ∪ ∅ = s₁ := sorry

theorem erase_union_singleton {α : Type u} {β : α → Type v} [DecidableEq α] (a : α) (b : β a) (s : finmap β) (h : lookup a s = some b) : erase a s ∪ singleton a b = s := sorry

/-! ### disjoint -/

/-- `disjoint s₁ s₂` holds if `s₁` and `s₂` have no keys in common. -/
def disjoint {α : Type u} {β : α → Type v} (s₁ : finmap β) (s₂ : finmap β) :=
  ∀ (x : α), x ∈ s₁ → ¬x ∈ s₂

theorem disjoint_empty {α : Type u} {β : α → Type v} (x : finmap β) : disjoint ∅ x :=
  fun (x_1 : α) (H : x_1 ∈ ∅) (ᾰ : x_1 ∈ x) => false.dcases_on (fun (H : x_1 ∈ ∅) => False) H

theorem disjoint.symm {α : Type u} {β : α → Type v} (x : finmap β) (y : finmap β) (h : disjoint x y) : disjoint y x :=
  fun (p : α) (hy : p ∈ y) (hx : p ∈ x) => h p hx hy

theorem disjoint.symm_iff {α : Type u} {β : α → Type v} (x : finmap β) (y : finmap β) : disjoint x y ↔ disjoint y x :=
  { mp := disjoint.symm x y, mpr := disjoint.symm y x }

protected instance disjoint.decidable_rel {α : Type u} {β : α → Type v} [DecidableEq α] : DecidableRel disjoint :=
  fun (x y : finmap β) => id multiset.decidable_dforall_multiset

theorem disjoint_union_left {α : Type u} {β : α → Type v} [DecidableEq α] (x : finmap β) (y : finmap β) (z : finmap β) : disjoint (x ∪ y) z ↔ disjoint x z ∧ disjoint y z := sorry

theorem disjoint_union_right {α : Type u} {β : α → Type v} [DecidableEq α] (x : finmap β) (y : finmap β) (z : finmap β) : disjoint x (y ∪ z) ↔ disjoint x y ∧ disjoint x z := sorry

theorem union_comm_of_disjoint {α : Type u} {β : α → Type v} [DecidableEq α] {s₁ : finmap β} {s₂ : finmap β} : disjoint s₁ s₂ → s₁ ∪ s₂ = s₂ ∪ s₁ := sorry

theorem union_cancel {α : Type u} {β : α → Type v} [DecidableEq α] {s₁ : finmap β} {s₂ : finmap β} {s₃ : finmap β} (h : disjoint s₁ s₃) (h' : disjoint s₂ s₃) : s₁ ∪ s₃ = s₂ ∪ s₃ ↔ s₁ = s₂ := sorry


universe u
axiom f {α : Sort u} (a : α) : α
axiom f_eq {α : Sort u} (a : α) : f a = a

example (a : Nat) : f id a = a := by
  simp only [f_eq]
  trace_state
  rfl


import polytime.size
import stack_rec
import misc

open tree function polysize (size)
open_locale tree

namespace tree

section

variables {α : Type} {β : Type} [tencodable α] [tencodable β]
  {base : α → β} {pre₁ pre₂ : tree unit → tree unit → α → α}
  {post : β → β → tree unit → tree unit → α → β}

@[elab_as_eliminator]
theorem stack_step_induction
  (P : tree unit → α → iterator_stack α β → Prop)
  (hb : ∀ t arg, P t arg (sum.inl (t, arg, none)))
  (hb' : ∀ ⦃l r arg res⦄, res = stack_rec base pre₁ pre₂ post l (pre₁ l r arg) → P l (pre₁ l r arg) (sum.inr res)
           → P (l △ r) arg (sum.inl (l △ r, arg, some res)))
  (hbr : ∀ arg, P tree.nil arg (sum.inr (base arg)))
  (hi₁ : ∀ ⦃l r arg e⦄, P l (pre₁ l r arg) e → P (l △ r) arg e)
  (hi₂ : ∀ ⦃l r arg e⦄, P r (pre₂ l r arg) e → P (l △ r) arg e)
  (hp : ∀ ⦃l r arg res₁ res₂⦄,
    res₁ = stack_rec base pre₁ pre₂ post l (pre₁ l r arg) →
    res₂ = stack_rec base pre₁ pre₂ post r (pre₂ l r arg) →
    P l (pre₁ l r arg) (sum.inr res₁) →
    P r (pre₂ l r arg) (sum.inr res₂) →
    P (l △ r) arg (sum.inr $ post res₁ res₂ l r arg))
  :
  ∀ (x : tree unit) (arg : α) (n : ℕ)
    (e : iterator_stack α β) (he : e ∈ ((stack_step base pre₁ pre₂ post)^[n] [sum.inl (x, arg, none)])),
    P x arg e :=
begin
  -- For the inductive hypothesis, we add an excess `xs`
  suffices : ∀ (xs : list (iterator_stack α β)) (x : tree unit) (arg : α) (n : ℕ) (hn : n ≤ x.time_steps)
    (e : iterator_stack α β) (he : e ∈ ((stack_step base pre₁ pre₂ post)^[n] (sum.inl (x, arg, none) :: xs))),
    P x arg e ∨ e ∈ xs,
  { intros x arg n e he, refine (this [] x arg (min n x.time_steps) (min_le_right _ _) e _).resolve_right not_false, rwa stack_step_iterate_min, },
  intros,
  induction x using tree.unit_rec_on with l r ih₁ ih₂ generalizing arg xs n e,
  -- In the case `n=0`, no iteration happens, so the only
  -- nontrivial possibility is `e = sum.inl (t, arg, none)` (the base case)
  all_goals { cases n, { refine or.imp_left _ he, rintro rfl, exact hb _ arg, }, },
  { obtain rfl : n = 0 := by simpa [nat.succ_eq_add_one] using hn,
    refine or.imp_left _ he, rintro rfl, exact hbr arg, },
  -- In the inductive step, suppose we take `n+1` steps; we first show that `P` is true of the previous results (used several times later)
  have P_l_res : P l (pre₁ l r arg) (sum.inr $ stack_rec base pre₁ pre₂ post l (pre₁ l r arg)),
  { refine (ih₁ _ [] _ _ rfl.le _).resolve_right not_false, simp, },
  have P_r_res : P r (pre₂ l r arg) (sum.inr $ stack_rec base pre₁ pre₂ post r (pre₂ l r arg)),
  { refine (ih₂ _ [] _ _ rfl.le _).resolve_right not_false, simp, },
  -- Now we consider intermediate stages `0 ≤ n ≤ l.time_steps`
  by_cases H : n ≤ l.time_steps,
  { clear ih₂, rcases ih₁ (pre₁ l r arg) _ n e H he with (ih|rfl|ih),
    { left, exact hi₁ ih, }, { left, exact hb _ _, }, { right, exact ih, } },
  -- Now we consider the cases `n = l.time_steps + 1` i.e. `c=1` letting `c = n - l.time_steps`
  rcases lt_iff_exists_add.mp (lt_of_not_le H) with ⟨c, hc, rfl⟩, clear H,
  rw [iterate_succ_apply, add_comm l.time_steps, iterate_add_apply, stack_step, stack_step_iterate] at he,
  cases c, { cases not_lt_zero' hc, }, clear hc,
  cases c, { refine or.imp_left _ he, rintro rfl, exact hb' rfl P_l_res, },
  -- Next, we consider the cases `l.time_steps + 2 ≤ n ≤ l.time_steps + 2 + r.time_steps`
  by_cases H : c ≤ r.time_steps,
  { rcases ih₂ (pre₂ l r arg) _ c e H he with (ih|rfl|ih),
    { left, exact hi₂ ih, }, { left, exact hb' rfl P_l_res, }, { right, exact ih, }, },
  -- Finally, we consider the last step `n = l.time_steps + 2 + r.time_steps + 1`
  obtain rfl : c = 1 + r.time_steps,
  { simp [time_steps_node, nat.succ_eq_add_one] at hn,
    linarith only [H, hn], },
  simp [iterate_add] at he,
  refine he.imp_left _, rintro rfl,
  exact hp rfl rfl P_l_res P_r_res,
end

-- First we show the length is bounded
-- Note this is inefficient because it does branching in many cases where we do not need to branch
lemma stack_step_len_le (ls : list (iterator_stack α β)) :
  (stack_step base pre₁ pre₂ post ls).length ≤ ls.length + 1 :=
by rcases ls with (_|⟨(⟨(_|⟨⟨⟩, l, r⟩), arg, (_|_)⟩|res), (_|⟨(⟨_,_,(_|_)⟩|_), _⟩)⟩); simp [add_assoc]

lemma stack_step_iter_len_le (ls : list (iterator_stack α β)) (n : ℕ) :
  ((stack_step base pre₁ pre₂ post)^[n] ls).length ≤ ls.length + n :=
by { induction n with n ih, { refl, }, rw [iterate_succ_apply'], exact (stack_step_len_le _).trans (nat.succ_le_succ ih), }

lemma stack_step_iter_len_le' (x : tree unit) (arg : α) (n : ℕ) :
  ((stack_step base pre₁ pre₂ post)^[n] [sum.inl (x, arg, none)]).length ≤ x.time_steps + 1 :=
by { rw [← stack_step_iterate_min, add_comm x.time_steps], refine (stack_step_iter_len_le _ _).trans _, simp, }

variables [polysize α] [polysize β] (bb bpr₁ bpr₂ bpo : polynomial ℕ)
  (hbb : ∀ x, size (base x) ≤ bb.eval (size x))
  (hbpr₁ : ∀ x y a, size (pre₁ x y a) ≤ (size a) + bpr₁.eval (size (x △ y)))
  (hbpr₂ : ∀ x y a, size (pre₂ x y a) ≤ (size a) + bpr₂.eval (size (x △ y)))
  (hbpo : ∀ ih₁ ih₂ x y a, size (post ih₁ ih₂ x y a) ≤ (size ih₁) + (size ih₂) + bpo.eval (size (x △ y, a)))

def max_arg_size (x₀ : tree unit) (arg₀ : α) : ℕ :=
size arg₀ + x₀.height * ((bpr₁.eval x₀.num_nodes) + (bpr₂.eval x₀.num_nodes))

def max_res_size (x₀ : tree unit) (arg₀ : α) : ℕ :=
x₀.num_leaves * bb.eval (max_arg_size bpr₁ bpr₂ x₀ arg₀) + x₀.num_nodes * bpo.eval (x₀.num_nodes + (max_arg_size bpr₁ bpr₂ x₀ arg₀))

def bdd_size (x₀ : tree unit) (arg₀ : α) : iterator_stack α β → Prop
| (sum.inl (x, arg, res)) := size x ≤ size x₀ ∧
      size arg ≤ max_arg_size bpr₁ bpr₂ x₀ arg₀ ∧
      size res ≤ max_res_size bb bpr₁ bpr₂ bpo x₀ arg₀
| (sum.inr res) := size res ≤ max_res_size bb bpr₁ bpr₂ bpo x₀ arg₀

variables {bb bpr₁ bpr₂ bpo}

lemma max_res_size_node (l r : tree unit) (arg : α) :
  max_res_size bb bpr₁ bpr₂ bpo (l △ r) arg =
  (l.num_leaves * bb.eval (max_arg_size bpr₁ bpr₂ (l △ r) arg) + l.num_nodes * bpo.eval ((l △ r).num_nodes + (max_arg_size bpr₁ bpr₂ (l △ r) arg))) +
  (r.num_leaves * bb.eval (max_arg_size bpr₁ bpr₂ (l △ r) arg) + r.num_nodes * bpo.eval ((l △ r).num_nodes + (max_arg_size bpr₁ bpr₂ (l △ r) arg))) +
  bpo.eval ((l △ r).num_nodes + (max_arg_size bpr₁ bpr₂ (l △ r) arg)) :=
by { simp [max_res_size], ring, }

lemma max_arg_size_le₁ (hbpr₁ : ∀ x y a, size (pre₁ x y a) ≤ (size a) + bpr₁.eval (size (x △ y))) (l r : tree unit) (arg : α) :
  max_arg_size bpr₁ bpr₂ l (pre₁ l r arg) ≤ max_arg_size bpr₁ bpr₂ (l △ r) arg :=
begin
  simp only [max_arg_size, tree.height, add_comm (max l.height r.height), add_mul, one_mul, ← add_assoc],
  mono*,
  exacts [(hbpr₁ _ _ _).trans le_self_add, le_max_left _ _,
    (l △ r).left_num_nodes_le, (l △ r).left_num_nodes_le, zero_le', zero_le'],
end

lemma max_arg_size_le₂ (hbpr₂ : ∀ x y a, size (pre₂ x y a) ≤ (size a) + bpr₂.eval (size (x △ y))) (l r : tree unit) (arg : α) :
  max_arg_size bpr₁ bpr₂ r (pre₂ l r arg) ≤ max_arg_size bpr₁ bpr₂ (l △ r) arg :=
begin
  simp only [max_arg_size, tree.height, add_comm (max l.height r.height), add_mul, one_mul, ← add_assoc],
  mono*,
  exacts [(hbpr₂ _ _ _).trans (add_le_add_right le_self_add _),
    le_max_right _ _, (l △ r).right_num_nodes_le, (l △ r).right_num_nodes_le, zero_le', zero_le'],
end

lemma max_res_size_le₁ (hbpr₁ : ∀ x y a, size (pre₁ x y a) ≤ (size a) + bpr₁.eval (size (x △ y))) (l r : tree unit) (arg : α) :
  max_res_size bb bpr₁ bpr₂ bpo l (pre₁ l r arg) ≤ max_res_size bb bpr₁ bpr₂ bpo (l △ r) arg :=
let h : max_arg_size bpr₁ bpr₂ l (pre₁ l r arg) ≤ max_arg_size bpr₁ bpr₂ (l △ r) arg := max_arg_size_le₁ hbpr₁ _ _ _ in
(add_le_add (mul_le_mul' le_self_add (bb.eval_mono h)) (mul_le_mul' (l △ r).left_num_nodes_le (bpo.eval_mono $ add_le_add (l △ r).left_num_nodes_le h)))

lemma max_res_size_le₂ (hbpr₂ : ∀ x y a, size (pre₂ x y a) ≤ (size a) + bpr₂.eval (size (x △ y))) (l r : tree unit) (arg : α) :
  max_res_size bb bpr₁ bpr₂ bpo r (pre₂ l r arg) ≤ max_res_size bb bpr₁ bpr₂ bpo (l △ r) arg :=
let h : max_arg_size bpr₁ bpr₂ r (pre₂ l r arg) ≤ max_arg_size bpr₁ bpr₂ (l △ r) arg := max_arg_size_le₂ hbpr₂ _ _ _ in
add_le_add (mul_le_mul' le_add_self (bb.eval_mono h)) (mul_le_mul' (l △ r).right_num_nodes_le (bpo.eval_mono $ add_le_add (l △ r).right_num_nodes_le h))

include hbpr₁ hbpr₂

lemma add_max_res_size_le (l r : tree unit) (arg : α) :
  max_res_size bb bpr₁ bpr₂ bpo l (pre₁ l r arg) + max_res_size bb bpr₁ bpr₂ bpo r (pre₂ l r arg) + bpo.eval (size (l △ r, arg)) ≤
    max_res_size bb bpr₁ bpr₂ bpo (l △ r) arg :=
begin
  have h₁ : max_arg_size bpr₁ bpr₂ l (pre₁ l r arg) ≤ max_arg_size bpr₁ bpr₂ (l △ r) arg := max_arg_size_le₁ hbpr₁ _ _ _,
  have h₂ : max_arg_size bpr₁ bpr₂ r (pre₂ l r arg) ≤ max_arg_size bpr₁ bpr₂ (l △ r) arg := max_arg_size_le₂ hbpr₂ _ _ _,
  conv_rhs { rw max_res_size_node, }, simp only [max_res_size, polysize.prod_size],
  mono*,
  exacts [zero_le', (l △ r).left_num_nodes_le, zero_le', zero_le', (l △ r).right_num_nodes_le, zero_le', le_self_add],
end

include hbb hbpo

theorem stack_step_iter_le (x : tree unit) (arg : α) (n : ℕ)
  (e : iterator_stack α β) (he : e ∈ ((stack_step base pre₁ pre₂ post)^[n] [sum.inl (x, arg, none)])) :
  bdd_size bb bpr₁ bpr₂ bpo x arg e :=
begin
  refine stack_step_induction _ _ _ _ _ _ _ _ _ _ _ he; clear he e x arg n,
  { intros t arg, refine ⟨rfl.le, le_self_add, zero_le'⟩, },
  { intros l r arg res _ h, refine ⟨rfl.le, le_self_add, trans h (max_res_size_le₁ hbpr₁ _ _ _)⟩, },
  { intros arg, refine (hbb _).trans (le_add_right _), simpa using bb.eval_mono le_self_add, },
  { intros l r arg e he,
    rcases e with (⟨x, arg', res⟩|res),
    { exact ⟨he.1.trans (l △ r).left_num_nodes_le, he.2.1.trans (max_arg_size_le₁ hbpr₁ _ _ _), he.2.2.trans (max_res_size_le₁ hbpr₁ _ _ _)⟩, },
    exact trans he (max_res_size_le₁ hbpr₁ _ _ _), },
  { intros l r arg e he,
    rcases e with (⟨x, arg', res⟩|res),
    { exact ⟨he.1.trans (l △ r).right_num_nodes_le, he.2.1.trans (max_arg_size_le₂ hbpr₂ _ _ _), he.2.2.trans (max_res_size_le₂ hbpr₂ _ _ _)⟩, },
    exact trans he (max_res_size_le₂ hbpr₂ _ _ _), },
  { intros l r arg res₁ res₂ _ _ ih₁ ih₂,
    rw [bdd_size] at ih₁ ih₂ ⊢,
    refine (hbpo _ _ _ _ _).trans _,
    refine trans _ (add_max_res_size_le hbpr₁ hbpr₂ _ _ _),
    mono*, },
end

end

section

variables {α β γ : Type} [tencodable α] [tencodable β] [tencodable γ]
  [polysize α] [polysize β] [polysize γ]
  {st : γ → tree unit} {arg : γ → α}
  {base : γ → α → β} {pre₁ pre₂ : γ → tree unit → tree unit → α → α}
  {post : γ → β → β → tree unit → tree unit → α → β}
  (hst : polysize_fun st) (harg : polysize_fun arg)
  (hb : polysize_fun base)
  (hpr₁ : polysize_safe (λ (usf : γ × tree unit × tree unit) (a : α), pre₁ usf.1 usf.2.1 usf.2.2 a))
  (hpr₂ : polysize_safe (λ (usf : γ × tree unit × tree unit) (sf : α), pre₂ usf.1 usf.2.1 usf.2.2 sf))
  (hpo : polysize_safe (λ (usf : γ × tree unit × tree unit × α) (sf : β × β), post usf.1 sf.1 sf.2 usf.2.1 usf.2.2.1 usf.2.2.2))

@[reducible] noncomputable def _root_.polynomial.peval {α : Type*} [semiring α] (p : polynomial α) (x : α) : polynomial α :=
p.comp ((polynomial.C x) + polynomial.X)

noncomputable def max_arg_size_poly : polynomial ℕ :=
harg.poly + hst.poly * ((hpr₁.poly.comp (polynomial.X + hst.poly)) + (hpr₂.poly.comp (polynomial.X + hst.poly)))

noncomputable def max_res_size_poly : polynomial ℕ :=
(hst.poly + 1) * hb.poly.comp (polynomial.X + max_arg_size_poly hst harg hpr₁ hpr₂) +
  hst.poly * hpo.poly.comp (polynomial.X + hst.poly + max_arg_size_poly hst harg hpr₁ hpr₂)

noncomputable def bdd_size_poly : polynomial ℕ :=
hst.poly + (max_arg_size_poly hst harg hpr₁ hpr₂) + (max_res_size_poly hst harg hb hpr₁ hpr₂ hpo)

lemma arg_poly_le (x : γ) :
  max_arg_size (hpr₁.poly.peval (size x)) (hpr₂.poly.peval (size x)) (st x) (arg x) ≤
   (max_arg_size_poly hst harg hpr₁ hpr₂).eval (size x) :=
begin
  have := hst.spec x, have := harg.spec x,
  simp only [max_arg_size_poly, max_arg_size, polynomial.eval_add, polynomial.eval_X,
    polynomial.eval_mul, polysize.prod_size, polysize_tree_unit, polynomial.eval_comp, polynomial.eval_C],
  mono*,
  exacts [(st x).height_le_num_nodes.trans (hst.spec x), zero_le', zero_le'],
end

lemma res_poly_le (x : γ) :
  max_res_size (hb.poly.peval (size x)) (hpr₁.poly.peval (size x)) (hpr₂.poly.peval (size x)) (hpo.poly.peval (size x)) (st x) (arg x) ≤
    (max_res_size_poly hst harg hb hpr₁ hpr₂ hpo).eval (size x) :=
begin
  have := hst.spec, have := arg_poly_le hst harg hpr₁ hpr₂ x,
  simp only [max_res_size, max_res_size_poly, polynomial.peval, ←add_assoc, eq_nat_cast,
    polynomial.eval_comp, polynomial.eval_add, polynomial.eval_nat_cast, nat.cast_id, polynomial.eval_X,
    polynomial.eval_mul, polynomial.eval_one, (st x).num_leaves_eq_num_nodes_succ],
  mono*; exact zero_le',
end

lemma bdd_size_poly_le (x : γ) (e : iterator_stack α β)
  (he : bdd_size (hb.poly.peval (size x)) (hpr₁.poly.peval (size x)) (hpr₂.poly.peval (size x))
    (hpo.poly.peval (size x)) (st x) (arg x) e) : size e ≤ (bdd_size_poly hst harg hb hpr₁ hpr₂ hpo).eval (size x) :=
begin
  rcases e with (⟨t, a, res⟩|res),
  { conv_lhs { simp [← add_assoc], },
    simp [bdd_size, bdd_size_poly] at ⊢ he,
    exact add_le_add (add_le_add (he.1.trans $ hst.spec x)
      (he.2.1.trans $ arg_poly_le _ _ _ _ x))
      (he.2.2.trans $ res_poly_le _ _ _ _ _ _ x), },
  { simp [bdd_size, bdd_size_poly] at he ⊢,
    exact le_add_left (he.trans $ res_poly_le _ _ _ _ _ _ x), }
end

include hst harg hb hpr₁ hpr₂ hpo

theorem stack_step_iter_le' : ∃ (p : polynomial ℕ),
  ∀ (x : γ) (n : ℕ)
  (e : iterator_stack α β) (he : e ∈ ((stack_step (base x) (pre₁ x) (pre₂ x) (post x))^[n] [sum.inl (st x, arg x, none)])),
  size e ≤ p.eval (size x) :=
begin
  use (bdd_size_poly hst harg hb hpr₁ hpr₂ hpo),
  intros x n e he,
  apply bdd_size_poly_le, apply stack_step_iter_le _ _ _ _ _ _ n e he,
  { intro a, simpa using hb.spec (x, a), },
  { intros l r a, refine (hpr₁.spec (x, l, r) a).trans _, simp [add_assoc], mono*, exact nat.le_succ _, },
  { intros l r a, refine (hpr₂.spec (x, l, r) a).trans _, simp [add_assoc], mono*, exact nat.le_succ _, },
  { intros ih₁ ih₂ l r a, refine (hpo.spec (x, l, r, a) (ih₁, ih₂)).trans _, simp [add_assoc], mono*, exact le_add_self, }
end

theorem stack_step_polysize : ∃ (p : polynomial ℕ), ∀ (x : γ) (n : ℕ),
  size ((stack_step (base x) (pre₁ x) (pre₂ x) (post x))^[n] [sum.inl (st x, arg x, none)]) ≤
    p.eval (size x) :=
begin
  cases stack_step_iter_le' hst harg hb hpr₁ hpr₂ hpo with p hp,
  use (5 * hst.poly + 2) * (p + 1),
  intros x n,
  simp only [polynomial.eval_add, polynomial.eval_mul, polynomial.eval_one],
  apply list.size_le_mul_of_le,
  { refine (stack_step_iter_len_le' _ _ _).trans _, simpa [tree.time_steps, add_assoc] using hst.spec x, },
  { exact hp x n, },
end

end

end tree


import number_theory.bernoulli

--OUTPUT 1
theorem bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
begin
  induction n with d hd,
  {
    simp,
  },
  {
    have h1 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h2 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h3 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h4 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h5 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h6 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h7 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h8 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h9 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h10 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h11 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h12 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h13 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h14 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h15 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h16 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h17 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h18 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h19 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h20 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h21 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h22 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h23 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h24 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h25 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h26 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h27 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h28 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h29 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h30 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h31 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h32 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h33 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have h34 : (d + 1) * (1 + x)^d - (d + 1) * x^d = (d + 1) * (1 + x)^d - (d + 1) * x^d, from rfl,
    have
end --Needs more than 2000 tokens!

/- FEW SHOT PROMPTS TO CODEX(START)
/--`theorem`
Power Set is Closed under Intersection
Let $S$ be a set.

Let $\powerset S$ be the power set of $S$.

Then:
:$\forall A, B \in \powerset S: A \cap B \in \powerset S$
`proof`
Let $A, B \in \powerset S$.

Then by the definition of power set, $A \subseteq S$ and $B \subseteq S$.

From Intersection is Subset we have that $A \cap B \subseteq A$.

It follows from Subset Relation is Transitive that $A \cap B \subseteq S$.

Thus $A \cap B \in \powerset S$ and closure is proved.
{{qed}}
-/
theorem power_set_intersection_closed {α : Type*} (S : set α) : ∀ A B ∈ 𝒫 S, (A ∩ B) ∈ 𝒫 S :=
begin
  assume (A : set α) (hA : A ∈ 𝒫 S) (B : set α) (hB : B ∈ 𝒫 S),
  have h1 : (A ⊆ S) ∧ (B ⊆ S), from by {split,apply set.subset_of_mem_powerset,exact hA,apply set.subset_of_mem_powerset,exact hB},
  have h2 : (A ∩ B) ⊆ A, from by apply set.inter_subset_left,
  have h3 : (A ∩ B) ⊆ S, from by {apply set.subset.trans h2 h1.left},
  show (A ∩ B) ∈  𝒫 S, from by {apply set.mem_powerset h3},
end

/--`theorem`
Square of Sum
 :$\forall x, y \in \R: \paren {x + y}^2 = x^2 + 2 x y + y^2$
`proof`
Follows from the distribution of multiplication over addition:

{{begin-eqn}}
{{eqn | l = \left({x + y}\right)^2
      | r = \left({x + y}\right) \cdot \left({x + y}\right)
}}
{{eqn | r = x \cdot \left({x + y}\right) + y \cdot \left({x + y}\right)
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x \cdot x + x \cdot y + y \cdot x + y \cdot y
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x^2 + 2xy + y^2
      | c =
}}
{{end-eqn}}
{{qed}}
-/
theorem square_of_sum (x y : ℝ) : (x + y)^2 = (x^2 + 2*x*y + y^2) :=
begin
  calc (x + y)^2 = (x+y)*(x+y) : by rw sq
  ... = x*(x+y) + y*(x+y) : by rw add_mul
  ... = x*x + x*y + y*x + y*y : by {rw [mul_comm x (x+y),mul_comm y (x+y)], rw [add_mul,add_mul], ring}
  ... = x^2 + 2*x*y + y^2 : by {repeat {rw ← sq}, rw mul_comm y x, ring}
end

/--`theorem`
Identity of Group is Unique
Let $\struct {G, \circ}$ be a group. Then there is a unique identity element $e \in G$.
`proof`
From Group has Latin Square Property, there exists a unique $x \in G$ such that:
:$a x = b$

and there exists a unique $y \in G$ such that:
:$y a = b$

Setting $b = a$, this becomes:

There exists a unique $x \in G$ such that:
:$a x = a$

and there exists a unique $y \in G$ such that:
:$y a = a$

These $x$ and $y$ are both $e$, by definition of identity element.
{{qed}}
-/
theorem group_identity_unique {G : Type*} [group G] : ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a :=
begin
  have h1 : ∀ a b : G, ∃! x : G, a * x = b, from by {
    assume a b : G, use a⁻¹ * b, obviously, },
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from by {
    assume a b : G, use b * a⁻¹, obviously, },

  have h3 : ∀ a : G, ∃! x : G, a * x = a, from
    assume a : G, h1 a a,
  have h4 : ∀ a : G, ∃! y : G, y * a = a, from
    assume a : G, h2 a a,

  have h5 : ∀ a : G, classical.some (h3 a).exists = (1 : G), from assume a :G,
    exists_unique.unique (h3 a) (classical.some_spec (exists_unique.exists (h3 a)))
    (mul_one a),
  have h6 : ∀ a : G, classical.some (h4 a).exists = (1 : G), from assume a : G,
    exists_unique.unique (h4 a) (classical.some_spec (exists_unique.exists (h4 a))) (one_mul a),

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by {
    use (1 : G),
    have h7 : ∀ e : G, (∀ a : G, e * a = a ∧ a * e = a) → e = 1, from by {
      assume (e : G) (hident : ∀ a : G, e * a = a ∧ a * e = a),
      have h8 : ∀ a : G, e = classical.some (h3 a).exists, from assume (a : G),
        exists_unique.unique (h3 a) (hident a).right
        (classical.some_spec (exists_unique.exists (h3 a))),
      have h9 : ∀ a : G, e = classical.some (h4 a).exists, from assume (a : G),
        exists_unique.unique (h4 a) (hident a).left
        (classical.some_spec (exists_unique.exists (h4 a))),
      show e = (1 : G), from eq.trans (h9 e) (h6 _),
    },
    exact ⟨by obviously, h7⟩,
  }
end

/--`theorem`
Bernoulli polynomial evaluation
Given a natural number $n$ and a rational $x$, let $B_n (x)$ denote the $n$-th Bernoulli polynomial evaluated at $x$. Then, $$B_n (1 + x) = B_n (x) + n x^{n - 1}$$
`proof`
We apply strong induction on $n$. So, for all $m < d$, we have $B_{m} (1 + x) = B_{m} (x) + m x^{m - 1}$ and we want to show that $$B_{d} (1 + x) = B_{d} (x) + d x^{d - 1}$$
Multiplying both sides by $d + 1$, and using the fact that, for all $l \in \mathbb{N}$, $\sum_{k = 0}^{l} {l + 1 \choose k} B_k = (l + 1) X^l$ (where $B_k$ is the $k$-th Bernoulli number), we get that
$$ (d + 1) (1 + x)^d - (d + 1) x^d = \sum_{l = 0}^{d} {d + 1 \choose l} l x^{l - 1} $$
The conclusion then follows easily.

QED

-/
theorem  bernoulli_eval_one_add (n : ℕ) (x : ℚ) :
  (polynomial.bernoulli n).eval (1 + x) = (polynomial.bernoulli n).eval x + n * x^(n - 1) :=
FEW SHOT PROMPTS TO CODEX(END)-/


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

! This file was ported from Lean 3 source module data.nat.choose.sum
! leanprover-community/mathlib commit 3e32bc908f617039c74c06ea9a897e30c30803c2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Nat.Choose.Basic
import Mathbin.Tactic.Linarith.Default
import Mathbin.Algebra.BigOperators.Ring
import Mathbin.Algebra.BigOperators.Intervals
import Mathbin.Algebra.BigOperators.Order
import Mathbin.Algebra.BigOperators.NatAntidiagonal

/-!
# Sums of binomial coefficients

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

This file includes variants of the binomial theorem and other results on sums of binomial
coefficients. Theorems whose proofs depend on such sums may also go in this file for import
reasons.

-/

open Nat

open Finset

open BigOperators

variable {R : Type _}

namespace Commute

variable [Semiring R] {x y : R} (h : Commute x y) (n : ℕ)

include h

/- warning: commute.add_pow -> Commute.add_pow is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {x : R} {y : R}, (Commute.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1)))) x y) -> (forall (n : Nat), Eq.{succ u1} R (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toHasAdd.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) x y) n) (Finset.sum.{u1, 0} R Nat (NonUnitalNonAssocSemiring.toAddCommMonoid.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))) (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (fun (m : Nat) => HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) x m) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) y (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n m))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat R (HasLiftT.mk.{1, succ u1} Nat R (CoeTCₓ.coe.{1, succ u1} Nat R (Nat.castCoe.{u1} R (AddMonoidWithOne.toNatCast.{u1} R (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} R (NonAssocSemiring.toAddCommMonoidWithOne.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))))) (Nat.choose n m)))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {x : R} {y : R}, (Commute.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))) x y) -> (forall (n : Nat), Eq.{succ u1} R (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toAdd.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) x y) n) (Finset.sum.{u1, 0} R Nat (NonUnitalNonAssocSemiring.toAddCommMonoid.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))) (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (m : Nat) => HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1)))) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1)))) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) x m) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) y (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n m))) (Nat.cast.{u1} R (Semiring.toNatCast.{u1} R _inst_1) (Nat.choose n m)))))
Case conversion may be inaccurate. Consider using '#align commute.add_pow Commute.add_powₓ'. -/
/-- A version of the **binomial theorem** for commuting elements in noncommutative semirings. -/
theorem add_pow : (x + y) ^ n = ∑ m in range (n + 1), x ^ m * y ^ (n - m) * choose n m :=
  by
  let t : ℕ → ℕ → R := fun n m => x ^ m * y ^ (n - m) * choose n m
  change (x + y) ^ n = ∑ m in range (n + 1), t n m
  have h_first : ∀ n, t n 0 = y ^ n := fun n =>
    by
    dsimp [t]
    rw [choose_zero_right, pow_zero, Nat.cast_one, mul_one, one_mul]
  have h_last : ∀ n, t n n.succ = 0 := fun n =>
    by
    dsimp [t]
    rw [choose_succ_self, Nat.cast_zero, MulZeroClass.mul_zero]
  have h_middle :
    ∀ n i : ℕ, i ∈ range n.succ → (t n.succ ∘ Nat.succ) i = x * t n i + y * t n i.succ :=
    by
    intro n i h_mem
    have h_le : i ≤ n := Nat.le_of_lt_succ (mem_range.mp h_mem)
    dsimp [t]
    rw [choose_succ_succ, Nat.cast_add, mul_add]
    congr 1
    · rw [pow_succ x, succ_sub_succ, mul_assoc, mul_assoc, mul_assoc]
    · rw [← mul_assoc y, ← mul_assoc y, (h.symm.pow_right i.succ).Eq]
      by_cases h_eq : i = n
      · rw [h_eq, choose_succ_self, Nat.cast_zero, MulZeroClass.mul_zero, MulZeroClass.mul_zero]
      · rw [succ_sub (lt_of_le_of_ne h_le h_eq)]
        rw [pow_succ y, mul_assoc, mul_assoc, mul_assoc, mul_assoc]
  induction' n with n ih
  · rw [pow_zero, sum_range_succ, range_zero, sum_empty, zero_add]
    dsimp [t]
    rw [pow_zero, pow_zero, choose_self, Nat.cast_one, mul_one, mul_one]
  · rw [sum_range_succ', h_first]
    rw [sum_congr rfl (h_middle n), sum_add_distrib, add_assoc]
    rw [pow_succ (x + y), ih, add_mul, mul_sum, mul_sum]
    congr 1
    rw [sum_range_succ', sum_range_succ, h_first, h_last, MulZeroClass.mul_zero, add_zero, pow_succ]
#align commute.add_pow Commute.add_pow

/- warning: commute.add_pow' -> Commute.add_pow' is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {x : R} {y : R}, (Commute.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1)))) x y) -> (forall (n : Nat), Eq.{succ u1} R (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toHasAdd.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) x y) n) (Finset.sum.{u1, 0} R (Prod.{0, 0} Nat Nat) (NonUnitalNonAssocSemiring.toAddCommMonoid.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))) (Finset.Nat.antidiagonal n) (fun (m : Prod.{0, 0} Nat Nat) => SMul.smul.{0, u1} Nat R (AddMonoid.SMul.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} R (NonAssocSemiring.toAddCommMonoidWithOne.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) (Nat.choose n (Prod.fst.{0, 0} Nat Nat m)) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) x (Prod.fst.{0, 0} Nat Nat m)) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) y (Prod.snd.{0, 0} Nat Nat m))))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : Semiring.{u1} R] {x : R} {y : R}, (Commute.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))) x y) -> (forall (n : Nat), Eq.{succ u1} R (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toAdd.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))))) x y) n) (Finset.sum.{u1, 0} R (Prod.{0, 0} Nat Nat) (NonUnitalNonAssocSemiring.toAddCommMonoid.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1))) (Finset.Nat.antidiagonal n) (fun (m : Prod.{0, 0} Nat Nat) => HSMul.hSMul.{0, u1, u1} Nat R R (instHSMul.{0, u1} Nat R (AddMonoid.SMul.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} R (NonAssocSemiring.toAddCommMonoidWithOne.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1)))))) (Nat.choose n (Prod.fst.{0, 0} Nat Nat m)) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R _inst_1)))) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) x (Prod.fst.{0, 0} Nat Nat m)) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R _inst_1)))) y (Prod.snd.{0, 0} Nat Nat m))))))
Case conversion may be inaccurate. Consider using '#align commute.add_pow' Commute.add_pow'ₓ'. -/
/-- A version of `commute.add_pow` that avoids ℕ-subtraction by summing over the antidiagonal and
also with the binomial coefficient applied via scalar action of ℕ. -/
theorem add_pow' :
    (x + y) ^ n = ∑ m in Nat.antidiagonal n, choose n m.fst • (x ^ m.fst * y ^ m.snd) := by
  simp_rw [Finset.Nat.sum_antidiagonal_eq_sum_range_succ fun m p => choose n m • (x ^ m * y ^ p),
    _root_.nsmul_eq_mul, cast_comm, h.add_pow]
#align commute.add_pow' Commute.add_pow'

end Commute

/- warning: add_pow -> add_pow is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : CommSemiring.{u1} R] (x : R) (y : R) (n : Nat), Eq.{succ u1} R (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1))))) (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toHasAdd.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1)))))) x y) n) (Finset.sum.{u1, 0} R Nat (NonUnitalNonAssocSemiring.toAddCommMonoid.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1)))) (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (fun (m : Nat) => HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1)))))) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1)))))) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1))))) x m) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1))))) y (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n m))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat R (HasLiftT.mk.{1, succ u1} Nat R (CoeTCₓ.coe.{1, succ u1} Nat R (Nat.castCoe.{u1} R (AddMonoidWithOne.toNatCast.{u1} R (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} R (NonAssocSemiring.toAddCommMonoidWithOne.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1)))))))) (Nat.choose n m))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : CommSemiring.{u1} R] (x : R) (y : R) (n : Nat), Eq.{succ u1} R (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1))))) (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (Distrib.toAdd.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1)))))) x y) n) (Finset.sum.{u1, 0} R Nat (NonUnitalNonAssocSemiring.toAddCommMonoid.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1)))) (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (m : Nat) => HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1))))) (HMul.hMul.{u1, u1, u1} R R R (instHMul.{u1} R (NonUnitalNonAssocSemiring.toMul.{u1} R (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} R (Semiring.toNonAssocSemiring.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1))))) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1))))) x m) (HPow.hPow.{u1, 0, u1} R Nat R (instHPow.{u1, 0} R Nat (Monoid.Pow.{u1} R (MonoidWithZero.toMonoid.{u1} R (Semiring.toMonoidWithZero.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1))))) y (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n m))) (Nat.cast.{u1} R (Semiring.toNatCast.{u1} R (CommSemiring.toSemiring.{u1} R _inst_1)) (Nat.choose n m))))
Case conversion may be inaccurate. Consider using '#align add_pow add_powₓ'. -/
/-- The **binomial theorem** -/
theorem add_pow [CommSemiring R] (x y : R) (n : ℕ) :
    (x + y) ^ n = ∑ m in range (n + 1), x ^ m * y ^ (n - m) * choose n m :=
  (Commute.all x y).add_pow n
#align add_pow add_pow

namespace Nat

#print Nat.sum_range_choose /-
/-- The sum of entries in a row of Pascal's triangle -/
theorem sum_range_choose (n : ℕ) : (∑ m in range (n + 1), choose n m) = 2 ^ n := by
  simpa using (add_pow 1 1 n).symm
#align nat.sum_range_choose Nat.sum_range_choose
-/

#print Nat.sum_range_choose_halfway /-
theorem sum_range_choose_halfway (m : Nat) : (∑ i in range (m + 1), choose (2 * m + 1) i) = 4 ^ m :=
  have :
    (∑ i in range (m + 1), choose (2 * m + 1) (2 * m + 1 - i)) =
      ∑ i in range (m + 1), choose (2 * m + 1) i :=
    sum_congr rfl fun i hi => choose_symm <| by linarith [mem_range.1 hi]
  mul_right_injective₀ two_ne_zero <|
    calc
      (2 * ∑ i in range (m + 1), choose (2 * m + 1) i) =
          (∑ i in range (m + 1), choose (2 * m + 1) i) +
            ∑ i in range (m + 1), choose (2 * m + 1) (2 * m + 1 - i) :=
        by rw [two_mul, this]
      _ =
          (∑ i in range (m + 1), choose (2 * m + 1) i) +
            ∑ i in Ico (m + 1) (2 * m + 2), choose (2 * m + 1) i :=
        by
        rw [range_eq_Ico, sum_Ico_reflect]
        · congr
          have A : m + 1 ≤ 2 * m + 1 := by linarith
          rw [add_comm, add_tsub_assoc_of_le A, ← add_comm]
          congr
          rw [tsub_eq_iff_eq_add_of_le A]
          ring
        · linarith
      _ = ∑ i in range (2 * m + 2), choose (2 * m + 1) i := (sum_range_add_sum_Ico _ (by linarith))
      _ = 2 ^ (2 * m + 1) := (sum_range_choose (2 * m + 1))
      _ = 2 * 4 ^ m := by
        rw [pow_succ, pow_mul]
        rfl

#align nat.sum_range_choose_halfway Nat.sum_range_choose_halfway
-/

#print Nat.choose_middle_le_pow /-
theorem choose_middle_le_pow (n : ℕ) : choose (2 * n + 1) n ≤ 4 ^ n :=
  by
  have t : choose (2 * n + 1) n ≤ ∑ i in range (n + 1), choose (2 * n + 1) i :=
    single_le_sum (fun x _ => by linarith) (self_mem_range_succ n)
  simpa [sum_range_choose_halfway n] using t
#align nat.choose_middle_le_pow Nat.choose_middle_le_pow
-/

#print Nat.four_pow_le_two_mul_add_one_mul_central_binom /-
theorem four_pow_le_two_mul_add_one_mul_central_binom (n : ℕ) :
    4 ^ n ≤ (2 * n + 1) * choose (2 * n) n :=
  calc
    4 ^ n = (1 + 1) ^ (2 * n) := by norm_num [pow_mul]
    _ = ∑ m in range (2 * n + 1), choose (2 * n) m := by simp [add_pow]
    _ ≤ ∑ m in range (2 * n + 1), choose (2 * n) (2 * n / 2) :=
      (sum_le_sum fun i hi => choose_le_middle i (2 * n))
    _ = (2 * n + 1) * choose (2 * n) n := by simp

#align nat.four_pow_le_two_mul_add_one_mul_central_binom Nat.four_pow_le_two_mul_add_one_mul_central_binom
-/

end Nat

/- warning: int.alternating_sum_range_choose -> Int.alternating_sum_range_choose is a dubious translation:
lean 3 declaration is
  forall {n : Nat}, Eq.{1} Int (Finset.sum.{0, 0} Int Nat Int.addCommMonoid (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (fun (m : Nat) => HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.hasMul) (HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.monoid)) (Neg.neg.{0} Int Int.hasNeg (OfNat.ofNat.{0} Int 1 (OfNat.mk.{0} Int 1 (One.one.{0} Int Int.hasOne)))) m) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Nat.choose n m)))) (ite.{1} Int (Eq.{1} Nat n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) (Nat.decidableEq n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) (OfNat.ofNat.{0} Int 1 (OfNat.mk.{0} Int 1 (One.one.{0} Int Int.hasOne))) (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero))))
but is expected to have type
  forall {n : Nat}, Eq.{1} Int (Finset.sum.{0, 0} Int Nat Int.instAddCommMonoidInt (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (m : Nat) => HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.instMulInt) (HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.instMonoidInt)) (Neg.neg.{0} Int Int.instNegInt (OfNat.ofNat.{0} Int 1 (instOfNatInt 1))) m) (Nat.cast.{0} Int instNatCastInt (Nat.choose n m)))) (ite.{1} Int (Eq.{1} Nat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) (instDecidableEqNat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) (OfNat.ofNat.{0} Int 1 (instOfNatInt 1)) (OfNat.ofNat.{0} Int 0 (instOfNatInt 0)))
Case conversion may be inaccurate. Consider using '#align int.alternating_sum_range_choose Int.alternating_sum_range_chooseₓ'. -/
theorem Int.alternating_sum_range_choose {n : ℕ} :
    (∑ m in range (n + 1), ((-1) ^ m * ↑(choose n m) : ℤ)) = if n = 0 then 1 else 0 :=
  by
  cases n; · simp
  have h := add_pow (-1 : ℤ) 1 n.succ
  simp only [one_pow, mul_one, add_left_neg] at h
  rw [← h, zero_pow (Nat.succ_pos n), if_neg (Nat.succ_ne_zero n)]
#align int.alternating_sum_range_choose Int.alternating_sum_range_choose

/- warning: int.alternating_sum_range_choose_of_ne -> Int.alternating_sum_range_choose_of_ne is a dubious translation:
lean 3 declaration is
  forall {n : Nat}, (Ne.{1} Nat n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) -> (Eq.{1} Int (Finset.sum.{0, 0} Int Nat Int.addCommMonoid (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (fun (m : Nat) => HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.hasMul) (HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.monoid)) (Neg.neg.{0} Int Int.hasNeg (OfNat.ofNat.{0} Int 1 (OfNat.mk.{0} Int 1 (One.one.{0} Int Int.hasOne)))) m) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Nat.choose n m)))) (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero))))
but is expected to have type
  forall {n : Nat}, (Ne.{1} Nat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) -> (Eq.{1} Int (Finset.sum.{0, 0} Int Nat Int.instAddCommMonoidInt (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (m : Nat) => HMul.hMul.{0, 0, 0} Int Int Int (instHMul.{0} Int Int.instMulInt) (HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.instMonoidInt)) (Neg.neg.{0} Int Int.instNegInt (OfNat.ofNat.{0} Int 1 (instOfNatInt 1))) m) (Nat.cast.{0} Int instNatCastInt (Nat.choose n m)))) (OfNat.ofNat.{0} Int 0 (instOfNatInt 0)))
Case conversion may be inaccurate. Consider using '#align int.alternating_sum_range_choose_of_ne Int.alternating_sum_range_choose_of_neₓ'. -/
theorem Int.alternating_sum_range_choose_of_ne {n : ℕ} (h0 : n ≠ 0) :
    (∑ m in range (n + 1), ((-1) ^ m * ↑(choose n m) : ℤ)) = 0 := by
  rw [Int.alternating_sum_range_choose, if_neg h0]
#align int.alternating_sum_range_choose_of_ne Int.alternating_sum_range_choose_of_ne

namespace Finset

/- warning: finset.sum_powerset_apply_card -> Finset.sum_powerset_apply_card is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : AddCommMonoid.{u1} α] (f : Nat -> α) {x : Finset.{u2} β}, Eq.{succ u1} α (Finset.sum.{u1, u2} α (Finset.{u2} β) _inst_1 (Finset.powerset.{u2} β x) (fun (m : Finset.{u2} β) => f (Finset.card.{u2} β m))) (Finset.sum.{u1, 0} α Nat _inst_1 (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (Finset.card.{u2} β x) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) (fun (m : Nat) => SMul.smul.{0, u1} Nat α (AddMonoid.SMul.{u1} α (AddCommMonoid.toAddMonoid.{u1} α _inst_1)) (Nat.choose (Finset.card.{u2} β x) m) (f m)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : AddCommMonoid.{u2} α] (f : Nat -> α) {x : Finset.{u1} β}, Eq.{succ u2} α (Finset.sum.{u2, u1} α (Finset.{u1} β) _inst_1 (Finset.powerset.{u1} β x) (fun (m : Finset.{u1} β) => f (Finset.card.{u1} β m))) (Finset.sum.{u2, 0} α Nat _inst_1 (Finset.range (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (Finset.card.{u1} β x) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) (fun (m : Nat) => HSMul.hSMul.{0, u2, u2} Nat α α (instHSMul.{0, u2} Nat α (AddMonoid.SMul.{u2} α (AddCommMonoid.toAddMonoid.{u2} α _inst_1))) (Nat.choose (Finset.card.{u1} β x) m) (f m)))
Case conversion may be inaccurate. Consider using '#align finset.sum_powerset_apply_card Finset.sum_powerset_apply_cardₓ'. -/
theorem sum_powerset_apply_card {α β : Type _} [AddCommMonoid α] (f : ℕ → α) {x : Finset β} :
    (∑ m in x.powerset, f m.card) = ∑ m in range (x.card + 1), x.card.choose m • f m :=
  by
  trans ∑ m in range (x.card + 1), ∑ j in x.powerset.filter fun z => z.card = m, f j.card
  · refine' (sum_fiberwise_of_maps_to _ _).symm
    intro y hy
    rw [mem_range, Nat.lt_succ_iff]
    rw [mem_powerset] at hy
    exact card_le_of_subset hy
  · refine' sum_congr rfl fun y hy => _
    rw [← card_powerset_len, ← sum_const]
    refine' sum_congr powerset_len_eq_filter.symm fun z hz => _
    rw [(mem_powerset_len.1 hz).2]
#align finset.sum_powerset_apply_card Finset.sum_powerset_apply_card

/- warning: finset.sum_powerset_neg_one_pow_card -> Finset.sum_powerset_neg_one_pow_card is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {x : Finset.{u1} α}, Eq.{1} Int (Finset.sum.{0, u1} Int (Finset.{u1} α) Int.addCommMonoid (Finset.powerset.{u1} α x) (fun (m : Finset.{u1} α) => HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.monoid)) (Neg.neg.{0} Int Int.hasNeg (OfNat.ofNat.{0} Int 1 (OfNat.mk.{0} Int 1 (One.one.{0} Int Int.hasOne)))) (Finset.card.{u1} α m))) (ite.{1} Int (Eq.{succ u1} (Finset.{u1} α) x (EmptyCollection.emptyCollection.{u1} (Finset.{u1} α) (Finset.hasEmptyc.{u1} α))) (Finset.decidableEq.{u1} α (fun (a : α) (b : α) => _inst_1 a b) x (EmptyCollection.emptyCollection.{u1} (Finset.{u1} α) (Finset.hasEmptyc.{u1} α))) (OfNat.ofNat.{0} Int 1 (OfNat.mk.{0} Int 1 (One.one.{0} Int Int.hasOne))) (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero))))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {x : Finset.{u1} α}, Eq.{1} Int (Finset.sum.{0, u1} Int (Finset.{u1} α) Int.instAddCommMonoidInt (Finset.powerset.{u1} α x) (fun (m : Finset.{u1} α) => HPow.hPow.{0, 0, 0} Int Nat Int Int.instHPowIntNat (Neg.neg.{0} Int Int.instNegInt (OfNat.ofNat.{0} Int 1 (instOfNatInt 1))) (Finset.card.{u1} α m))) (ite.{1} Int (Eq.{succ u1} (Finset.{u1} α) x (EmptyCollection.emptyCollection.{u1} (Finset.{u1} α) (Finset.instEmptyCollectionFinset.{u1} α))) (Finset.decidableEq.{u1} α (fun (a : α) (b : α) => _inst_1 a b) x (EmptyCollection.emptyCollection.{u1} (Finset.{u1} α) (Finset.instEmptyCollectionFinset.{u1} α))) (OfNat.ofNat.{0} Int 1 (instOfNatInt 1)) (OfNat.ofNat.{0} Int 0 (instOfNatInt 0)))
Case conversion may be inaccurate. Consider using '#align finset.sum_powerset_neg_one_pow_card Finset.sum_powerset_neg_one_pow_cardₓ'. -/
theorem sum_powerset_neg_one_pow_card {α : Type _} [DecidableEq α] {x : Finset α} :
    (∑ m in x.powerset, (-1 : ℤ) ^ m.card) = if x = ∅ then 1 else 0 :=
  by
  rw [sum_powerset_apply_card]
  simp only [nsmul_eq_mul', ← card_eq_zero, Int.alternating_sum_range_choose]
#align finset.sum_powerset_neg_one_pow_card Finset.sum_powerset_neg_one_pow_card

/- warning: finset.sum_powerset_neg_one_pow_card_of_nonempty -> Finset.sum_powerset_neg_one_pow_card_of_nonempty is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {x : Finset.{u1} α}, (Finset.Nonempty.{u1} α x) -> (Eq.{1} Int (Finset.sum.{0, u1} Int (Finset.{u1} α) Int.addCommMonoid (Finset.powerset.{u1} α x) (fun (m : Finset.{u1} α) => HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.monoid)) (Neg.neg.{0} Int Int.hasNeg (OfNat.ofNat.{0} Int 1 (OfNat.mk.{0} Int 1 (One.one.{0} Int Int.hasOne)))) (Finset.card.{u1} α m))) (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero))))
but is expected to have type
  forall {α : Type.{u1}} {x : Finset.{u1} α}, (Finset.Nonempty.{u1} α x) -> (Eq.{1} Int (Finset.sum.{0, u1} Int (Finset.{u1} α) Int.instAddCommMonoidInt (Finset.powerset.{u1} α x) (fun (m : Finset.{u1} α) => HPow.hPow.{0, 0, 0} Int Nat Int Int.instHPowIntNat (Neg.neg.{0} Int Int.instNegInt (OfNat.ofNat.{0} Int 1 (instOfNatInt 1))) (Finset.card.{u1} α m))) (OfNat.ofNat.{0} Int 0 (instOfNatInt 0)))
Case conversion may be inaccurate. Consider using '#align finset.sum_powerset_neg_one_pow_card_of_nonempty Finset.sum_powerset_neg_one_pow_card_of_nonemptyₓ'. -/
theorem sum_powerset_neg_one_pow_card_of_nonempty {α : Type _} {x : Finset α} (h0 : x.Nonempty) :
    (∑ m in x.powerset, (-1 : ℤ) ^ m.card) = 0 := by
  classical
    rw [sum_powerset_neg_one_pow_card, if_neg]
    rw [← Ne.def, ← nonempty_iff_ne_empty]
    apply h0
#align finset.sum_powerset_neg_one_pow_card_of_nonempty Finset.sum_powerset_neg_one_pow_card_of_nonempty

end Finset


import .Borel_Isomorphism

class standard_borel (α : Type*) [measurable_space α] : Prop :=
  (is_polishable : ∃ τ : topological_space α, polish_space α ∧ borel_space α)

class upgraded_standard_borel (α : Type*) extends measurable_space α,
  topological_space α, borel_space α, polish_space α

noncomputable
def upgrade_standard_borel (α : Type*) [hm : measurable_space α] [hs : standard_borel α] :
  upgraded_standard_borel α :=
begin
  choose τ hp hb using hs.is_polishable,
  letI := τ,
  apply upgraded_standard_borel.mk,
end

section clopenable

open measurable_space
variables {α : Type*}

set_option pp.implicit true
theorem eq_borel_of_polish_le_polish {s t : topological_space α}
  (hs : @polish_space _ s) (ht : @polish_space _ t) (h : s ≤ t) : @borel _ s = @borel _ t :=
begin
  dsimp[borel],
  apply le_antisymm; apply generate_from_le; intros X hX,  {
    have := @is_open.is_clopenable _ s _ X hX,
    rcases this with ⟨r,rs,hr⟩,
    rw ← @measure_theory.is_clopenable_iff_measurable_set _ t _ (@borel _ t) (by {
      exact @borel_space.mk α t (@borel α t) (@rfl (measurable_space α) (@borel α t)),
    }),
    refine ⟨r,_,hr⟩,
    exact le_trans rs h,
  },
  apply measurable_set_generate_from,
  apply h,
  exact hX,
end

lemma clopenable_same_borel [t : topological_space α] [ht : polish_space α]
  [m : measurable_space α] [hb : borel_space α]
  {X : set α} (hX : polish_space.is_clopenable X) : ∃ s : topological_space α,
  (@polish_space _ s) ∧ @is_closed _ s X ∧ @is_open _ s X ∧ @borel_space _ s _ :=
begin
  rcases hX with  ⟨s,st,hs1,hs2,hs3⟩,
  refine ⟨s,hs1,hs2,hs3,_⟩,
  constructor,
  rw hb.measurable_eq,
  symmetry,
  apply eq_borel_of_polish_le_polish; assumption,
end

end clopenable

section sb_instances

variables {α β: Type*}
variables [measurable_space α] [standard_borel α] [measurable_space β] [standard_borel β]

--set_option pp.implicit true
theorem measurable_set.standard_borel {s : set α} (hs : measurable_set s) : standard_borel s :=
begin
  letI := upgrade_standard_borel α,
  rw ← measure_theory.is_clopenable_iff_measurable_set at hs,
  rcases clopenable_same_borel hs with ⟨t,tpolish,sclosed,-,ht⟩,
  letI := t,
  constructor,
  use infer_instance,
  split, {
    apply sclosed.polish_space,
  },
  apply_instance,
end

instance prod_standard_borel : standard_borel (α × β) :=
begin
  letI := upgrade_standard_borel α,
  letI := upgrade_standard_borel β,
  constructor,
  use[infer_instance],
  sorry, --no polish_space.prod!
end

end sb_instances


import for_mathlib.AddCommGroup.explicit_products

open category_theory
open category_theory.limits

namespace AddCommGroup

universes u
variables {J : Type u} [small_category J] (F : J ⥤ AddCommGroup.{u})

open_locale classical
noncomputable theory

def explicit_cocone_point_kernel :
  add_subgroup (direct_sum J (λ i, F.obj i)) :=
add_subgroup.closure
{ x | ∃ (i j : J) (f : i ⟶ j) (t : F.obj i),
  x = direct_sum.of _ j (F.map f t) - direct_sum.of _ i t  }

def as_small_succ (i : as_small.{u} ℕ) : as_small ℕ :=
  as_small.up.obj (as_small.down.obj i + 1)

def to_as_small_succ (i : as_small.{u} ℕ) : i ⟶ as_small_succ i :=
as_small.up.map (hom_of_le $ nat.le_succ _)

lemma explicit_cocone_point_kernel_eq_of_as_small_nat
  (F : as_small.{u} ℕ ⥤ AddCommGroup.{u}) :
  explicit_cocone_point_kernel F =
  add_subgroup.closure { x | ∃ i (t : F.obj i), x =
    direct_sum.of (λ i, F.obj i) (as_small_succ i) (F.map (to_as_small_succ i) t) -
    direct_sum.of _ i t } :=
begin
  apply le_antisymm,
  { erw add_subgroup.closure_le,
    rintros x ⟨⟨i⟩,⟨j⟩,f,t,rfl⟩,
    obtain ⟨k,rfl⟩ : ∃ k : ℕ, j = i + k,
    { have : i ≤ j := le_of_hom (as_small.down.map f),
      exact le_iff_exists_add.mp this },
    induction k with k hk,
    { have : f = 𝟙 _, ext, rw this,
      simp only [category_theory.functor.map_id, id_apply, set_like.mem_coe],
      erw sub_self,
      exact add_subgroup.zero_mem _, },
    { let f₁ : as_small.up.obj i ⟶ as_small.up.obj (i + k) := as_small.up.map
        (hom_of_le $ le_self_add),
      let f₂ : as_small.up.obj (i + k) ⟶ as_small.up.obj (i + (k + 1)) :=
        as_small.up.map (hom_of_le $ by nlinarith),
      have hf : f = f₁ ≫ f₂, by ext, rw hf, clear hf,
      specialize hk f₁,
      let t' := _, change t' ∈ _, let s := _, change s ∈ _ at hk,
      rw (show t' = (t' - s) + s, by simp),
      let A := add_subgroup.closure {x :
        direct_sum (as_small ℕ) (λ (i : as_small ℕ), ↥(F.obj i)) |
          ∃ (i : as_small ℕ) (t : ↥(F.obj i)), x =
            (direct_sum.of (λ (i : as_small ℕ), ↥(F.obj i)) (as_small_succ i))
              ((F.map (to_as_small_succ i)) t) -
            (direct_sum.of (λ (i : as_small ℕ), ↥(F.obj i)) i) t},
      change _ ∈ A,
      suffices : (t' - s) ∈ A, by exact A.add_mem this hk,
      dsimp [t', s], simp only [functor.map_comp, comp_apply, sub_sub_sub_cancel_right],
      apply add_subgroup.subset_closure,
      use as_small.up.obj (i + k),
      let tt : F.obj (as_small.up.obj (i + k)) := F.map f₁ t,
      use tt,
      congr } },
  { rw add_subgroup.closure_le,
    rintros x ⟨i,t,rfl⟩,
    apply add_subgroup.subset_closure,
    refine ⟨i,as_small_succ i, to_as_small_succ i, t, _⟩,
    congr }
end

def explicit_cocone_point : AddCommGroup.{u} :=
AddCommGroup.of
((direct_sum J (λ i, F.obj i)) ⧸ explicit_cocone_point_kernel F)

def explicit_cocone : cocone F :=
{ X := explicit_cocone_point F,
  ι :=
  { app := λ j, add_monoid_hom.comp (quotient_add_group.mk' _)
      (direct_sum.of _ j),
    naturality' := begin
      intros i j f, ext t,
      dsimp,
      simp only [comp_apply, add_monoid_hom.coe_comp, quotient_add_group.coe_mk',
        function.comp_app, id_apply],
      rw quotient_add_group.eq_iff_sub_mem,
      apply add_subgroup.subset_closure,
      dsimp, refine ⟨i, j, f, t, rfl⟩,
    end } }

def is_colimit_explicit_cocone : is_colimit (explicit_cocone F) :=
{ desc := λ S, quotient_add_group.lift _
    (direct_sum.to_add_monoid $ λ i, S.ι.app _)
    begin
      intros t ht,
      apply add_subgroup.closure_induction ht,
      { rintros x ⟨i,j,f,t,rfl⟩,
        simp only [map_sub, direct_sum.to_add_monoid_of, cocone.w_apply, sub_self] },
      { simp only [map_zero], },
      { intros x y hx hy, simp only [hx, hy, map_add, add_zero] },
      { intros x hx, simp only [hx, map_neg, neg_zero] },
    end,
  fac' := begin
    intros S j, ext t, dsimp [explicit_cocone],
    simp only [direct_sum.to_add_monoid_of, comp_apply, add_monoid_hom.coe_comp,
      quotient_add_group.coe_mk', quotient_add_group.lift_mk],
  end,
  uniq' := begin
    intros S m hm, ext j t,
    simp only [direct_sum.to_add_monoid_of, add_monoid_hom.coe_comp, quotient_add_group.coe_mk',
      function.comp_app, quotient_add_group.lift_mk],
    rw ← hm, refl,
  end }

end AddCommGroup


/-
Copyright (c) 2019 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Johan Commelin
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.ring_theory.integral_closure
import Mathlib.data.polynomial.field_division
import Mathlib.ring_theory.polynomial.gauss_lemma
import Mathlib.PostPort

universes u_1 u_2 u_3

namespace Mathlib

/-!
# Minimal polynomials

This file defines the minimal polynomial of an element `x` of an `A`-algebra `B`,
under the assumption that x is integral over `A`.

After stating the defining property we specialize to the setting of field extensions
and derive some well-known properties, amongst which the fact that minimal polynomials
are irreducible, and uniquely determined by their defining property.

-/

/-- Let `B` be an `A`-algebra, and `x` an element of `B` that is integral over `A`
so we have some term `hx : is_integral A x`.
The minimal polynomial `minpoly A x` of `x` is a monic polynomial of smallest degree
that has `x` as its root.
For instance, if `V` is a `K`-vector space for some field `K`, and `f : V →ₗ[K] V` then
the minimal polynomial of `f` is `minpoly f.is_integral`. -/
def minpoly (A : Type u_1) {B : Type u_2} [comm_ring A] [ring B] [algebra A B] (x : B) : polynomial A :=
  dite (is_integral A x)
    (fun (hx : is_integral A x) =>
      well_founded.min sorry (fun (p : polynomial A) => polynomial.monic p ∧ polynomial.eval₂ (algebra_map A B) x p = 0)
        hx)
    fun (hx : ¬is_integral A x) => 0

namespace minpoly

/--A minimal polynomial is monic.-/
theorem monic {A : Type u_1} {B : Type u_2} [comm_ring A] [ring B] [algebra A B] {x : B} (hx : is_integral A x) : polynomial.monic (minpoly A x) := sorry

/-- A minimal polynomial is nonzero. -/
theorem ne_zero {A : Type u_1} {B : Type u_2} [comm_ring A] [ring B] [algebra A B] {x : B} [nontrivial A] (hx : is_integral A x) : minpoly A x ≠ 0 :=
  polynomial.ne_zero_of_monic (monic hx)

theorem eq_zero {A : Type u_1} {B : Type u_2} [comm_ring A] [ring B] [algebra A B] {x : B} (hx : ¬is_integral A x) : minpoly A x = 0 :=
  dif_neg hx

/--An element is a root of its minimal polynomial.-/
@[simp] theorem aeval (A : Type u_1) {B : Type u_2} [comm_ring A] [ring B] [algebra A B] (x : B) : coe_fn (polynomial.aeval x) (minpoly A x) = 0 := sorry

theorem mem_range_of_degree_eq_one (A : Type u_1) {B : Type u_2} [comm_ring A] [ring B] [algebra A B] (x : B) (hx : polynomial.degree (minpoly A x) = 1) : x ∈ ring_hom.range (algebra_map A B) := sorry

/--The defining property of the minimal polynomial of an element x:
it is the monic polynomial with smallest degree that has x as its root.-/
theorem min (A : Type u_1) {B : Type u_2} [comm_ring A] [ring B] [algebra A B] (x : B) {p : polynomial A} (pmonic : polynomial.monic p) (hp : coe_fn (polynomial.aeval x) p = 0) : polynomial.degree (minpoly A x) ≤ polynomial.degree p := sorry

-- TODO(Commelin, Brasca): this is a duplicate

/-- If an element `x` is a root of a nonzero monic polynomial `p`,
then the degree of `p` is at least the degree of the minimal polynomial of `x`. -/
theorem degree_le_of_monic (A : Type u_1) {B : Type u_2} [comm_ring A] [ring B] [algebra A B] (x : B) {p : polynomial A} (hmonic : polynomial.monic p) (hp : coe_fn (polynomial.aeval x) p = 0) : polynomial.degree (minpoly A x) ≤ polynomial.degree p := sorry

/-- The degree of a minimal polynomial is positive. -/
theorem degree_pos {A : Type u_1} {B : Type u_2} [integral_domain A] [ring B] [algebra A B] [nontrivial B] {x : B} [nontrivial A] (hx : is_integral A x) : 0 < polynomial.degree (minpoly A x) := sorry

/-- If `B/A` is an injective ring extension, and `a` is an element of `A`,
then the minimal polynomial of `algebra_map A B a` is `X - C a`. -/
theorem eq_X_sub_C_of_algebra_map_inj {A : Type u_1} {B : Type u_2} [integral_domain A] [ring B] [algebra A B] [nontrivial B] [nontrivial A] (a : A) (hf : function.injective ⇑(algebra_map A B)) : minpoly A (coe_fn (algebra_map A B) a) = polynomial.X - coe_fn polynomial.C a := sorry

/-- A minimal polynomial is not a unit. -/
theorem not_is_unit (A : Type u_1) {B : Type u_2} [integral_domain A] [ring B] [algebra A B] [nontrivial B] (x : B) : ¬is_unit (minpoly A x) := sorry

/-- If `a` strictly divides the minimal polynomial of `x`, then `x` cannot be a root for `a`. -/
theorem aeval_ne_zero_of_dvd_not_unit_minpoly {A : Type u_1} {B : Type u_2} [integral_domain A] [domain B] [algebra A B] {x : B} {a : polynomial A} (hx : is_integral A x) (hamonic : polynomial.monic a) (hdvd : dvd_not_unit a (minpoly A x)) : coe_fn (polynomial.aeval x) a ≠ 0 := sorry

/--A minimal polynomial is irreducible.-/
theorem irreducible {A : Type u_1} {B : Type u_2} [integral_domain A] [domain B] [algebra A B] {x : B} (hx : is_integral A x) : irreducible (minpoly A x) := sorry

/-- If an element `x` is a root of a nonzero polynomial `p`,
then the degree of `p` is at least the degree of the minimal polynomial of `x`. -/
theorem degree_le_of_ne_zero (A : Type u_1) {B : Type u_2} [field A] [ring B] [algebra A B] (x : B) {p : polynomial A} (pnz : p ≠ 0) (hp : coe_fn (polynomial.aeval x) p = 0) : polynomial.degree (minpoly A x) ≤ polynomial.degree p := sorry

/-- The minimal polynomial of an element x is uniquely characterized by its defining property:
if there is another monic polynomial of minimal degree that has x as a root,
then this polynomial is equal to the minimal polynomial of x. -/
theorem unique (A : Type u_1) {B : Type u_2} [field A] [ring B] [algebra A B] (x : B) {p : polynomial A} (pmonic : polynomial.monic p) (hp : coe_fn (polynomial.aeval x) p = 0) (pmin : ∀ (q : polynomial A), polynomial.monic q → coe_fn (polynomial.aeval x) q = 0 → polynomial.degree p ≤ polynomial.degree q) : p = minpoly A x := sorry

/-- If an element x is a root of a polynomial p,
then the minimal polynomial of x divides p. -/
theorem dvd (A : Type u_1) {B : Type u_2} [field A] [ring B] [algebra A B] (x : B) {p : polynomial A} (hp : coe_fn (polynomial.aeval x) p = 0) : minpoly A x ∣ p := sorry

theorem dvd_map_of_is_scalar_tower (A : Type u_1) (K : Type u_2) {R : Type u_3} [comm_ring A] [field K] [comm_ring R] [algebra A K] [algebra A R] [algebra K R] [is_scalar_tower A K R] (x : R) : minpoly K x ∣ polynomial.map (algebra_map A K) (minpoly A x) := sorry

theorem unique' {A : Type u_1} {B : Type u_2} [field A] [ring B] [algebra A B] {x : B} [nontrivial B] {p : polynomial A} (hx : is_integral A x) (hp1 : irreducible p) (hp2 : coe_fn (polynomial.aeval x) p = 0) (hp3 : polynomial.monic p) : p = minpoly A x := sorry

/-- If `y` is the image of `x` in an extension, their minimal polynomials coincide.

We take `h : y = algebra_map L T x` as an argument because `rw h` typically fails
since `is_integral R y` depends on y.
-/
theorem eq_of_algebra_map_eq {K : Type u_1} {S : Type u_2} {T : Type u_3} [field K] [comm_ring S] [comm_ring T] [algebra K S] [algebra K T] [algebra S T] [is_scalar_tower K S T] (hST : function.injective ⇑(algebra_map S T)) {x : S} {y : T} (hx : is_integral K x) (h : y = coe_fn (algebra_map S T) x) : minpoly K x = minpoly K y := sorry

/-- For GCD domains, the minimal polynomial over the ring is the same as the minimal polynomial
over the fraction field. -/
theorem gcd_domain_eq_field_fractions {A : Type u_1} {K : Type u_2} {R : Type u_3} [integral_domain A] [gcd_monoid A] [field K] [integral_domain R] (f : fraction_map A K) [algebra (localization_map.codomain f) R] [algebra A R] [is_scalar_tower A (localization_map.codomain f) R] {x : R} (hx : is_integral A x) : minpoly (localization_map.codomain f) x = polynomial.map (localization_map.to_ring_hom f) (minpoly A x) := sorry

/-- The minimal polynomial over `ℤ` is the same as the minimal polynomial over `ℚ`. -/
--TODO use `gcd_domain_eq_field_fractions` directly when localizations are defined

-- in terms of algebras instead of `ring_hom`s

theorem over_int_eq_over_rat {A : Type u_1} [integral_domain A] {x : A} [hℚA : algebra ℚ A] (hx : is_integral ℤ x) : minpoly ℚ x = polynomial.map (int.cast_ring_hom ℚ) (minpoly ℤ x) := sorry

/-- For GCD domains, the minimal polynomial divides any primitive polynomial that has the integral
element as root. -/
theorem gcd_domain_dvd {A : Type u_1} {K : Type u_2} {R : Type u_3} [integral_domain A] [gcd_monoid A] [field K] [integral_domain R] (f : fraction_map A K) [algebra (localization_map.codomain f) R] [algebra A R] [is_scalar_tower A (localization_map.codomain f) R] {x : R} (hx : is_integral A x) {P : polynomial A} (hprim : polynomial.is_primitive P) (hroot : coe_fn (polynomial.aeval x) P = 0) : minpoly A x ∣ P := sorry

/-- The minimal polynomial over `ℤ` divides any primitive polynomial that has the integral element
as root. -/
-- TODO use `gcd_domain_dvd` directly when localizations are defined in terms of algebras

-- instead of `ring_hom`s

theorem integer_dvd {A : Type u_1} [integral_domain A] [algebra ℚ A] {x : A} (hx : is_integral ℤ x) {P : polynomial ℤ} (hprim : polynomial.is_primitive P) (hroot : coe_fn (polynomial.aeval x) P = 0) : minpoly ℤ x ∣ P := sorry

/-- If `B/K` is a nontrivial algebra over a field, and `x` is an element of `K`,
then the minimal polynomial of `algebra_map K B x` is `X - C x`. -/
theorem eq_X_sub_C {A : Type u_1} (B : Type u_2) [field A] [ring B] [algebra A B] [nontrivial B] (a : A) : minpoly A (coe_fn (algebra_map A B) a) = polynomial.X - coe_fn polynomial.C a :=
  eq_X_sub_C_of_algebra_map_inj a (ring_hom.injective (algebra_map A B))

theorem eq_X_sub_C' {A : Type u_1} [field A] (a : A) : minpoly A a = polynomial.X - coe_fn polynomial.C a :=
  eq_X_sub_C A a

/-- The minimal polynomial of `0` is `X`. -/
@[simp] theorem zero (A : Type u_1) (B : Type u_2) [field A] [ring B] [algebra A B] [nontrivial B] : minpoly A 0 = polynomial.X := sorry

/-- The minimal polynomial of `1` is `X - 1`. -/
@[simp] theorem one (A : Type u_1) (B : Type u_2) [field A] [ring B] [algebra A B] [nontrivial B] : minpoly A 1 = polynomial.X - 1 := sorry

/-- A minimal polynomial is prime. -/
theorem prime {A : Type u_1} {B : Type u_2} [field A] [domain B] [algebra A B] {x : B} (hx : is_integral A x) : prime (minpoly A x) := sorry

/-- If `L/K` is a field extension and an element `y` of `K` is a root of the minimal polynomial
of an element `x ∈ L`, then `y` maps to `x` under the field embedding. -/
theorem root {A : Type u_1} {B : Type u_2} [field A] [domain B] [algebra A B] {x : B} (hx : is_integral A x) {y : A} (h : polynomial.is_root (minpoly A x) y) : coe_fn (algebra_map A B) y = x := sorry

/--The constant coefficient of the minimal polynomial of `x` is `0` if and only if `x = 0`. -/
@[simp] theorem coeff_zero_eq_zero {A : Type u_1} {B : Type u_2} [field A] [domain B] [algebra A B] {x : B} (hx : is_integral A x) : polynomial.coeff (minpoly A x) 0 = 0 ↔ x = 0 := sorry

/--The minimal polynomial of a nonzero element has nonzero constant coefficient. -/
theorem coeff_zero_ne_zero {A : Type u_1} {B : Type u_2} [field A] [domain B] [algebra A B] {x : B} (hx : is_integral A x) (h : x ≠ 0) : polynomial.coeff (minpoly A x) 0 ≠ 0 := sorry