import tactic
import data.setoid.basic
import data.real.basic
import data.set
import topology.metric_space.basic

namespace apartness

@[class]
structure setoid_ppapart (α : Type) extends setoid α :=
(ar : α → α → Prop)
(ar_not_r : ∀ x y, ar x y → ¬ setoid.r x y)
(ar_symm : ∀ x y, ar x y → ar y x)

variables (α : Type) [setoid_ppapart α] (S : set α)

def logical_complement : set α :=
{x | ∀ y ∈ S, ¬ setoid.r x y}

def complement : set α :=
{x | ∀ y ∈ S, setoid_ppapart.ar x y}

end apartness

namespace point_set_apartness

@[class]
structure point_set_apartness_space (α : Type) extends apartness.setoid_ppapart α :=
(apart : α → set α → Prop)
(ar_to_apart : ∀ x y, apartness.setoid_ppapart.ar x y → apart x {y})
(apart_to_nin : ∀ x A, apart x A → x ∉ A)
(apart_union_iff_apart_and_apart : ∀ x A B, apart x (A ∪ B) ↔ apart x A ∧ apart x B)
(apart_conj_to_apart : ∀ x A B, apart x A ∧ {x' | apart x' A} ⊆ apartness.complement α B → apart x B)
(apart_to_or_apart : ∀ x A, apart x A → ∀ y, apartness.setoid_ppapart.ar x y ∨ apart y A)

def real_ar : ℝ → ℝ → Prop :=
λ x y, x ≠ y

@[simp]
lemma rar_neq (x y : ℝ) : real_ar x y ↔ x ≠ y :=
begin
  split; intro h; exact h,
end

def real_apart : ℝ → set ℝ → Prop :=
λ x A, x ∉ A

@[simp]
lemma rap_nin (x : ℝ) (A : set ℝ) : real_apart x A ↔ x ∉ A :=
begin split; intro h; exact h end

@[instance]
lemma real_ppapart : point_set_apartness_space ℝ := { r := λ x y, x = y,
  iseqv := eq_equivalence,
  ar := λ x y, real_ar x y,
  ar_not_r := λ _ _ _, by finish,
  ar_symm := λ _ _ _, by finish,
  apart := real_apart,
  ar_to_apart := λ _ _ _ _, by finish,
  apart_to_nin := λ _ _ p, p,
  apart_union_iff_apart_and_apart := by finish,
  apart_conj_to_apart := λ x A B l l', l.1 (false.rec (x ∈ A) ((l.2 l.1 x l') rfl)),
  apart_to_or_apart := by finish, }

@[simp]
lemma ne_cotransitive (X : Type) : ∀ x z : X, x ≠ z → (∀ y : X, x ≠ y ∨ y ≠ z) :=
by finish

def apart (X : Type) [w : point_set_apartness_space X] :=
w.apart

def apart_to_nin (X : Type) [w : point_set_apartness_space X] :=
w.apart_to_nin

def set_diff (X : Type) [w : point_set_apartness_space X] : set X → set X → set X :=
λ A S, A ∩ {x | (apart X x S)}

def real_set_diff : set ℝ → set ℝ → set ℝ :=
λ A S, A ∩ {x | real_apart x S}

def near (X : Type) [w : point_set_apartness_space X] :=
λ x A, ∀ S, (apart X x S → ∃ y, y ∈ set_diff X A S)

def real_near :=
λ x A, ∀ S, (real_apart x S → ∃ y, y ∈ real_set_diff A S)

end point_set_apartness


/-
Copyright (c) 2020 Aaron Anderson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Aaron Anderson.
-/

import data.finsupp
import data.pnat.factors
import order.conditionally_complete_lattice
import order.lattice
import tactic.pi_instances

/-
# Factor Finsupps

## Notations

## Implementation Notes
A new notion of factorisation, similar to pnat.factor_multiset, but a finsupp.
Most of the sorries will be solved by proving some facts about lattice isomorphisms.

## References

-/

set_option old_structure_cmd true

open_locale classical
open_locale big_operators

noncomputable theory

def pnat.coprime (m n : ℕ+) : Prop := m.gcd n = 1

def mult (n : ℕ+) (p : nat.primes) : ℕ := (pnat.factor_multiset n).count p

def prime_finsupp : Type := nat.primes →₀ ℕ

@[instance]
instance prime_finsupp.has_coe_to_fun : has_coe_to_fun prime_finsupp := { ..finsupp.has_coe_to_fun}

def prime_finsupp.prod_apply_pow {α : Type} [comm_monoid α] (x : prime_finsupp) (f : ℕ+ → α) : α :=
x.prod (λ p : nat.primes, λ k : ℕ, f (p ^ k))

def prime_finsupp.prod_coe_pow (x : prime_finsupp) := x.prod_apply_pow id

lemma prime_finsupp.prod_pow_eq (x : prime_finsupp) :
  x.prod_coe_pow= x.prod (λ p : nat.primes, λ k : ℕ, (p ^ k)) := rfl

lemma prime_finsupp.prod_pow_eq_prod_to_multiset (x : prime_finsupp) :
  x.prod_coe_pow= prime_multiset.prod x.to_multiset :=
begin
  rw prime_finsupp.prod_pow_eq, rw prime_multiset.prod,
  sorry
end

def factor_finsupp (n : ℕ+) : prime_finsupp := n.factor_multiset.to_finsupp

@[simp]
lemma inf_zero_iff {m n : ℕ} : m ⊓ n = 0 ↔ m = 0 ∨ n = 0 :=
begin
  split, swap, intro h, cases h; rw h; simp,
  unfold has_inf.inf, unfold semilattice_inf.inf, unfold lattice.inf, unfold min,
  intro hmin, by_cases m = 0, left, apply h,
  rw if_neg _ at hmin, right, apply hmin, contrapose hmin,
  simp only [not_lt, not_le] at hmin, rw if_pos hmin, apply h
end

lemma nat.zero_eq_bot : (0 : ℕ) = ⊥ := rfl

variable {α : Type}

instance finsupp.has_inf :
  has_inf (α →₀ ℕ) :=
begin
  refine ⟨_⟩, intros v1 v2,
  refine ⟨v1.support ∩ v2.support, (λ (a : α), (v1 a ⊓ v2 a)), _⟩,
  intro a, simp only [finsupp.mem_support_iff, ne.def, finset.mem_inter],
  symmetry, rw not_iff_comm, push_neg, rw inf_zero_iff,
end

@[simp]
lemma finsupp.inf_apply {a : α} {f g : α →₀ ℕ} : (f ⊓ g) a = f a ⊓ g a := rfl

@[simp]
lemma finsupp.support_inf {f g : α →₀ ℕ} : (f ⊓ g).support = f.support ⊓ g.support := rfl

instance finsupp.has_sup :
  has_sup (α →₀ ℕ) :=
begin
  refine ⟨_⟩, intros v1 v2,
  refine ⟨v1.support ∪ v2.support, (λ (a : α), (v1 a ⊔ v2 a)), _⟩,
  intro a, simp only [finsupp.mem_support_iff, ne.def, finset.mem_inter],
  symmetry, rw not_iff_comm, simp only [finsupp.mem_support_iff, ne.def, finset.mem_union],
  push_neg, repeat {rw nat.zero_eq_bot}, rw sup_eq_bot_iff,
end

@[simp]
lemma finsupp.sup_apply {a : α} {f g : α →₀ ℕ} : (f ⊔ g) a = f a ⊔ g a := rfl

@[simp]
lemma finsupp.support_sup {f g : α →₀ ℕ} : (f ⊔ g).support = f.support ⊔ g.support := rfl

-- finsupp.to_multiset_strict_mono

lemma nat.bot_eq_zero : (⊥ : ℕ) = 0 := rfl

@[instance]
instance finsupp.lattice : lattice (α →₀ ℕ) :=
begin
  refine lattice.mk has_sup.sup has_le.le has_lt.lt _ _ _ _ _ _ _ has_inf.inf _ _ _,
  exact (finsupp.preorder).le_refl,
  exact (finsupp.preorder).le_trans,
  { simp only [auto_param_eq], intros a b, apply lt_iff_le_not_le,
  },
  exact (finsupp.partial_order).le_antisymm,
  intros, rw finsupp.le_iff, intros, simp,
  intros, rw finsupp.le_iff, intros, simp,
  { intros, rw finsupp.le_iff at *,
    intros, rw finsupp.sup_apply, apply sup_le,
    { by_cases s ∈ a.support, apply a_1 s h,
      simp only [finsupp.mem_support_iff, classical.not_not] at h, rw h, simp },
    { by_cases s ∈ b.support, apply a_2 s h,
      simp only [finsupp.mem_support_iff, classical.not_not] at h, rw h, simp }
    },
  intros, rw finsupp.le_iff, intros, simp,
  intros, rw finsupp.le_iff, intros, simp,
  { intros, rw finsupp.le_iff at *, intros,
    rw finsupp.inf_apply, apply le_inf, apply a_1 s H, apply a_2 s H }
end

@[instance]
instance prime_finsupp.lattice : lattice prime_finsupp :=
{ ..finsupp.lattice}

@[instance]
instance finsupp.semilattice_inf_bot : semilattice_inf_bot (α →₀ ℕ) :=
{ bot := 0,
  bot_le := by { intro a, simp [finsupp.le_iff] },
..finsupp.lattice}

@[instance]
instance prime_finsupp.semilattice_inf_bot : semilattice_inf_bot prime_finsupp :=
{ ..finsupp.semilattice_inf_bot}

@[instance]
instance prime_finsupp.add_comm_monoid : add_comm_monoid prime_finsupp :=
{ ..finsupp.add_comm_monoid}

@[simp]
lemma factor_finsupp_to_multiset_eq_factor_multiset {n : ℕ+} :
  (factor_finsupp n).to_multiset = n.factor_multiset :=
by { unfold factor_finsupp, simp }

lemma finsupp.of_multiset_strict_mono : strict_mono (@finsupp.of_multiset α) :=
begin
  unfold strict_mono, intros, rw lt_iff_le_and_ne at *, split,
  { rw finsupp.le_iff, intros s hs, repeat {rw finsupp.of_multiset_apply},
    rw multiset.le_iff_count at a_1, apply a_1.left },
  { have h := a_1.right, contrapose h, simp at *,
    apply finsupp.equiv_multiset.symm.injective h }
end

section basic_number_theory_definitions

lemma dvd_iff_le_factor_finsupps {m n : ℕ+} :
  m ∣ n ↔ factor_finsupp m ≤ factor_finsupp n := sorry

@[simp]
lemma factor_finsupp_mul {m n : ℕ+} :
  factor_finsupp (m * n) = factor_finsupp m + factor_finsupp n :=
begin
  apply finsupp.equiv_multiset.injective,
  change finsupp.to_multiset (factor_finsupp (m * n)) =
    finsupp.to_multiset (factor_finsupp m + factor_finsupp n),
  simp [finsupp.to_multiset_add, factor_finsupp, pnat.factor_multiset_mul]
end

lemma factor_finsupp_gcd_eq_inf_factor_finsupps {m n : ℕ+} :
  factor_finsupp (m.gcd n) = has_inf.inf (factor_finsupp m) (factor_finsupp n) :=
begin
  sorry
end

lemma coprime_iff_disjoint_supports {m n : ℕ+} :
  m.coprime n ↔ disjoint (factor_finsupp m).support (factor_finsupp n).support :=
begin
  sorry
end

lemma coprime_iff_disjoint_factor_finsupps {m n : ℕ+} :
  m.coprime n ↔ disjoint (factor_finsupp m) (factor_finsupp n) :=
begin
  sorry
end

end basic_number_theory_definitions

@[instance]
def prime_finsupp_coe_pnat : has_coe (nat.primes →₀ ℕ) (ℕ+ →₀ ℕ) :=
{ coe := finsupp.map_domain coe }

-- Just wraps to_multiset in the prime_multiset type for the next lemma to typecheck
def finsupp.to_prime_multiset (f : nat.primes →₀ ℕ) : prime_multiset := f.to_multiset

lemma coe_pnat_commute_to_multiset {f : nat.primes →₀ ℕ} :
(↑f : ℕ+ →₀ ℕ).to_multiset =  prime_multiset.to_pnat_multiset f.to_prime_multiset :=
begin
  unfold prime_multiset.to_pnat_multiset, unfold finsupp.to_prime_multiset,
  rw finsupp.to_multiset_map, refl
end

lemma prod_pow_factor_finsupp (n : ℕ+) :
  (factor_finsupp n).prod_coe_pow = n :=
begin
  rw prime_finsupp.prod_pow_eq_prod_to_multiset,
  rw factor_finsupp_to_multiset_eq_factor_multiset,
  rw pnat.prod_factor_multiset
end

lemma factor_finsupp_prod_pow (f : prime_finsupp) :
factor_finsupp (f.prod_coe_pow) = f :=
begin
  unfold factor_finsupp, conv_rhs {rw ← f.to_multiset_to_finsupp},
  rw ← prime_multiset.factor_multiset_prod f.to_multiset,
  rw prime_finsupp.prod_pow_eq_prod_to_multiset
end

lemma factor_finsupp_inj : function.injective factor_finsupp :=
begin
  unfold function.injective, intros a b h,
  rw [← prod_pow_factor_finsupp a, ← prod_pow_factor_finsupp b, h]
end

section prime_powers

@[simp]
lemma factor_finsupp_one : factor_finsupp 1 = 0 :=
begin
  apply finsupp.equiv_multiset.injective,
  change finsupp.to_multiset (factor_finsupp 1) = finsupp.to_multiset 0,
  rw [finsupp.to_multiset_zero, factor_finsupp_to_multiset_eq_factor_multiset,
    pnat.factor_multiset_one]
 end

variables {p : nat.primes} {n : ℕ+} {k : ℕ}

@[simp]
lemma factor_finsupp_prime : factor_finsupp ↑p = finsupp.single p 1 :=
begin
  apply finsupp.equiv_multiset.injective,
  change finsupp.to_multiset (factor_finsupp ↑p) = finsupp.to_multiset (finsupp.single p 1),
  rw [finsupp.to_multiset_single, factor_finsupp_to_multiset_eq_factor_multiset,
    pnat.factor_multiset_of_prime, prime_multiset.of_prime],
  simp
end

@[simp]
lemma factor_finsupp_pow : factor_finsupp (n ^ k) = k • factor_finsupp n :=
begin
  induction k, simp,
  rw [pow_succ, nat.succ_eq_add_one, add_smul, ← k_ih, mul_comm], simp
end

@[simp]
lemma prime_finsupp.smul_eq_smul {b : ℕ} :
  (k • finsupp.single p b : prime_finsupp) = (k • finsupp.single p b : nat.primes →₀ ℕ) :=
begin
  rw nat.smul_def,
  induction k, simp, rw [succ_nsmul, nat.succ_eq_add_one, k_ih, add_smul, add_comm], simp
end

@[simp]
lemma nat.smul_one : k • 1 = k := by { rw nat.smul_def, simp }

@[simp]
lemma factor_finsupp_pow_prime : factor_finsupp (p ^ k) = finsupp.single p k := by simp

end prime_powers

section coprime_part

variables (p : nat.primes) (n : ℕ+)

/--
The greatest divisor n coprime to prime p
-/
def coprime_part : ℕ+ :=
prime_finsupp.prod_coe_pow ((factor_finsupp n).erase p)

variables {p} {n}

@[simp]
lemma factor_finsupp_coprime_part_eq_erase_factor_finsupp :
  factor_finsupp (coprime_part p n) = (factor_finsupp n).erase p :=
by { rw coprime_part, apply factor_finsupp_prod_pow _, }

variable (p)
lemma factor_finsupp_coprime_part_add_single_eq_self :
  factor_finsupp (coprime_part p n) + finsupp.single p ((factor_finsupp n) p) = factor_finsupp n :=
by { simp [finsupp.erase_add_single] }

variables {p} (n)
lemma pow_mult_coprime_part_eq_self : (coprime_part p n) * p ^ ((factor_finsupp n) p) = n :=
begin
  apply factor_finsupp_inj, rw factor_finsupp_mul,
  rw factor_finsupp_pow_prime,
  rw factor_finsupp_coprime_part_add_single_eq_self
end

variable {n}

lemma prime_dvd_iff_mem_support_factor_finsupp : ↑p ∣ n ↔ p ∈ (factor_finsupp n).support :=
begin
  rw dvd_iff_le_factor_finsupps, rw finsupp.le_iff, simp [finsupp.mem_support_single],
  cases (factor_finsupp n) p, simp, omega
end

lemma prime_dvd_iff_factor_finsupp_pos : ↑p ∣ n ↔ 0 < factor_finsupp n p :=
by { rw prime_dvd_iff_mem_support_factor_finsupp, simp [nat.pos_iff_ne_zero] }

lemma not_dvd_coprime_part : ¬ (↑p ∣ (coprime_part p n)) :=
begin
  rw [dvd_iff_le_factor_finsupps, finsupp.le_iff], push_neg, existsi p,
  rw [factor_finsupp_prime, factor_finsupp_coprime_part_eq_erase_factor_finsupp],
  simp,
end

lemma coprime_pow_coprime_part {k : ℕ} (pos : 0 < k): ((p : ℕ+) ^ k).coprime (coprime_part p n) :=
begin
  rw coprime_iff_disjoint_supports,
  rw [factor_finsupp_pow, factor_finsupp_prime, factor_finsupp_coprime_part_eq_erase_factor_finsupp],
  simp only [prime_finsupp.smul_eq_smul, finsupp.support_erase, nat.smul_one, finsupp.smul_single],
  rw finsupp.support_single_ne_zero, simp, omega
end

lemma coprime_of_prime_not_dvd (h : ¬ ↑p ∣ n) : n.coprime p :=
begin
  rw coprime_iff_disjoint_supports,
  rw prime_dvd_iff_factor_finsupp_pos at h,
  rw factor_finsupp_prime, rw finsupp.support_single_ne_zero, swap, omega,
  rw finset.disjoint_singleton, simp only [finsupp.mem_support_iff, classical.not_not],
  simp only [nat.pos_iff_ne_zero, classical.not_not] at h, apply h
end

lemma dvd_coprime_part_of_coprime_dvd {m : ℕ+} (hmn : has_dvd.dvd m n) (hmp : ¬ ↑p ∣ m) :
  m ∣ (coprime_part p n) :=
begin
  rw prime_dvd_iff_mem_support_factor_finsupp at hmp,
  rw dvd_iff_le_factor_finsupps at *, rw finsupp.le_iff at *, intro q,
  intro h, simp only [factor_finsupp_coprime_part_eq_erase_factor_finsupp],
  rw finsupp.erase_ne, apply hmn q h, intro qp, rw qp at h, apply hmp h
end

@[simp]
lemma coprime_part_prime_mul_eq_coprime_part : coprime_part p (p * n) = coprime_part p n :=
by { apply factor_finsupp_inj, simp }

def two_prime : nat.primes := ⟨2, nat.prime_two⟩

variable (n)
def odd_part : ℕ+ := coprime_part two_prime n
variable {n}

lemma dvd_odd_part_of_odd_dvd {m : ℕ+} (hmn : has_dvd.dvd m n) (hmp : ¬ 2 ∣ m) :
  m ∣ (odd_part n) :=
begin
  apply dvd_coprime_part_of_coprime_dvd hmn, apply hmp
end

lemma coprime_pow_odd_part {k : ℕ} (pos : 0 < k) : ((2 : ℕ+) ^ k).coprime (odd_part n) :=
begin
  have h : pnat.coprime (↑two_prime ^ k) (coprime_part two_prime n) := coprime_pow_coprime_part pos,
  apply h
end

variable (n)
lemma pow_mult_odd_part_eq_self : (odd_part n) * 2 ^ (factor_finsupp n two_prime) = n :=
begin
  rw odd_part,
  --conv_rhs {rw ← prod_pow_factor_finsupp n},
  change coprime_part two_prime n * ↑two_prime ^ (factor_finsupp n) two_prime = n,
  rw pow_mult_coprime_part_eq_self n,
end
variable {n}

@[simp]
lemma odd_part_two_mul_eq_odd_part : odd_part (2 * n) = odd_part n :=
coprime_part_prime_mul_eq_coprime_part

end coprime_part

section multiplicative

def is_multiplicative {α : Type} [comm_monoid α] (f : ℕ+ → α): Prop :=
f(1) = 1 ∧ ∀ m n : ℕ+, nat.coprime m n → f(m * n) = f(m) * f(n)

lemma multiplicative_from_prime_pow {α : Type} [comm_monoid α] {f : ℕ+ → α} (h : is_multiplicative f) :
∀ n : ℕ+, f(n) = (factor_finsupp n).prod_apply_pow f :=
begin
  sorry
end

end multiplicative


import dimensional_analysis.basic
import linear_algebra.dimension
import data.matrix.rank
import data.fin.vec_notation
import set_theory.cardinal.basic

variables {α : Type*} {n : ℕ} [fintype α]

noncomputable def number_of_dimensionless_parameters [decidable_eq α] (M : matrix (fin n) α ℚ) := n - M.rank
open dimension

lemma cardinal.to_nat_eq_iff {c : cardinal} {n : ℕ} (hn : n ≠ 0) : cardinal.to_nat c = n ↔ c = n :=
⟨λ h, (cardinal.cast_to_nat_of_lt_aleph_0 (lt_of_not_ge (hn ∘ h.symm.trans ∘
  cardinal.to_nat_apply_of_aleph_0_le))).symm.trans (congr_arg coe h),
  λ h, (congr_arg cardinal.to_nat h).trans (cardinal.to_nat_cast n)⟩

example : cardinal.to_nat ( 3 : cardinal) = 3 :=
begin
  rw cardinal.to_nat_eq_iff,
  all_goals {finish},
end
example : number_of_dimensionless_parameters (matrix.of (![dimension.length system1, dimension.time system1, dimension.velocity system1])) = 1 :=
begin
  simp [number_of_dimensionless_parameters, matrix.rank],
  rw [finite_dimensional.finrank],
  have h : cardinal.to_nat (module.rank ℚ ((matrix.to_lin' (matrix.of ![length system1, time system1, velocity system1])).range)) = 2,
  { rw [cardinal.to_nat_eq_iff],

    },

end
example : matrix.rank !![(1 : ℚ),0;0,1;1,-1] = 2 :=
begin
  simp [matrix.rank],
  rw [finite_dimensional.finrank, cardinal.to_nat_eq_iff, module.rank],

end


variables (men : Type) (barber : men)
variable  (shaves : men → men → Prop)

example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : false := begin
intros,
have hsbb := (h barber),
have no := λ sbb, hsbb.mp sbb sbb,
have yes := hsbb.mpr no,
contradiction
end


/-
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.adjoin
import field_theory.is_alg_closed.basic
import field_theory.separable
import ring_theory.integral_domain

/-!
# Primitive Element Theorem

In this file we prove the primitive element theorem.

## Main results

- `exists_primitive_element`: a finite separable extension `E / F` has a primitive element, i.e.
  there is an `α : E` such that `F⟮α⟯ = (⊤ : subalgebra F E)`.

## Implementation notes

In declaration names, `primitive_element` abbreviates `adjoin_simple_eq_top`:
it stands for the statement `F⟮α⟯ = (⊤ : subalgebra F E)`. We did not add an extra
declaration `is_primitive_element F α := F⟮α⟯ = (⊤ : subalgebra F E)` because this
requires more unfolding without much obvious benefit.

## Tags

primitive element, separable field extension, separable extension, intermediate field, adjoin,
exists_adjoin_simple_eq_top

-/

noncomputable theory
open_locale classical polynomial

open finite_dimensional polynomial intermediate_field

namespace field

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

/-! ### Primitive element theorem for finite fields -/

/-- **Primitive element theorem** assuming E is finite. -/
lemma exists_primitive_element_of_finite_top [finite E] : ∃ α : E, F⟮α⟯ = ⊤ :=
begin
  obtain ⟨α, hα⟩ := is_cyclic.exists_generator (units E),
  use α,
  apply eq_top_iff.mpr,
  rintros x -,
  by_cases hx : x = 0,
  { rw hx,
    exact F⟮α.val⟯.zero_mem },
  { obtain ⟨n, hn⟩ := set.mem_range.mp (hα (units.mk0 x hx)),
    rw (show x = α^n, by { norm_cast, rw [hn, units.coe_mk0] }),
    exact zpow_mem (mem_adjoin_simple_self F ↑α) n, },
end

/-- Primitive element theorem for finite dimensional extension of a finite field. -/
theorem exists_primitive_element_of_finite_bot [finite F] [finite_dimensional F E] :
  ∃ α : E, F⟮α⟯ = ⊤ :=
begin
  haveI : finite E := finite_of_finite F E,
  exact exists_primitive_element_of_finite_top F E
end

end primitive_element_finite

/-! ### Primitive element theorem for infinite fields -/

section primitive_element_inf

variables {F : Type*} [field F] [infinite F] {E : Type*} [field E] (ϕ : F →+* E) (α β : E)

lemma primitive_element_inf_aux_exists_c (f g : F[X]) :
  ∃ c : F, ∀ (α' ∈ (f.map ϕ).roots) (β' ∈ (g.map ϕ).roots), -(α' - α)/(β' - β) ≠ ϕ c :=
begin
  let sf := (f.map ϕ).roots,
  let sg := (g.map ϕ).roots,
  let s := (sf.bind (λ α', sg.map (λ β', -(α' - α) / (β' - β)))).to_finset,
  let s' := s.preimage ϕ (λ x hx y hy h, ϕ.injective h),
  obtain ⟨c, hc⟩ := infinite.exists_not_mem_finset s',
  simp_rw [finset.mem_preimage, multiset.mem_to_finset, multiset.mem_bind, multiset.mem_map] at hc,
  push_neg at hc,
  exact ⟨c, hc⟩,
end

variables (F) [algebra F E]

-- This is the heart of the proof of the primitive element theorem. It shows that if `F` is
-- infinite and `α` and `β` are separable over `F` then `F⟮α, β⟯` is generated by a single element.
lemma primitive_element_inf_aux [is_separable F E] :
  ∃ γ : E, F⟮α, β⟯ = F⟮γ⟯ :=
begin
  have hα := is_separable.is_integral F α,
  have hβ := is_separable.is_integral F β,
  let f := minpoly F α,
  let g := minpoly F β,
  let ιFE := algebra_map F E,
  let ιEE' := algebra_map E (splitting_field (g.map ιFE)),
  obtain ⟨c, hc⟩ := primitive_element_inf_aux_exists_c (ιEE'.comp ιFE) (ιEE' α) (ιEE' β) f g,
  let γ := α + c • β,
  suffices β_in_Fγ : β ∈ F⟮γ⟯,
  { use γ,
    apply le_antisymm,
    { rw adjoin_le_iff,
      have α_in_Fγ : α ∈ F⟮γ⟯,
      { rw ← add_sub_cancel α (c • β),
        exact F⟮γ⟯.sub_mem (mem_adjoin_simple_self F γ) (F⟮γ⟯.to_subalgebra.smul_mem β_in_Fγ c) },
      exact λ x hx, by cases hx; cases hx; cases hx; assumption },
    { rw [adjoin_simple_le_iff],
      have α_in_Fαβ : α ∈ F⟮α, β⟯ := subset_adjoin F {α, β} (set.mem_insert α {β}),
      have β_in_Fαβ : β ∈ F⟮α, β⟯ := subset_adjoin F {α, β} (set.mem_insert_of_mem α rfl),
      exact F⟮α,β⟯.add_mem α_in_Fαβ (F⟮α, β⟯.smul_mem β_in_Fαβ) } },
  let p := euclidean_domain.gcd ((f.map (algebra_map F F⟮γ⟯)).comp
    (C (adjoin_simple.gen F γ) - (C ↑c * X))) (g.map (algebra_map F F⟮γ⟯)),
  let h := euclidean_domain.gcd ((f.map ιFE).comp (C γ - (C (ιFE c) * X))) (g.map ιFE),
  have map_g_ne_zero : g.map ιFE ≠ 0 := map_ne_zero (minpoly.ne_zero hβ),
  have h_ne_zero : h ≠ 0 :=  mt euclidean_domain.gcd_eq_zero_iff.mp
    (not_and.mpr (λ _, map_g_ne_zero)),
  suffices p_linear : p.map (algebra_map F⟮γ⟯ E) = (C h.leading_coeff) * (X - C β),
  { have finale : β = algebra_map F⟮γ⟯ E (-p.coeff 0 / p.coeff 1),
    { rw [map_div₀, ring_hom.map_neg, ←coeff_map, ←coeff_map, p_linear],
      simp [mul_sub, coeff_C, mul_div_cancel_left β (mt leading_coeff_eq_zero.mp h_ne_zero)] },
    rw finale,
    exact subtype.mem (-p.coeff 0 / p.coeff 1) },
  have h_sep : h.separable := separable_gcd_right _ (is_separable.separable F β).map,
  have h_root : h.eval β = 0,
  { apply eval_gcd_eq_zero,
    { rw [eval_comp, eval_sub, eval_mul, eval_C, eval_C, eval_X, eval_map, ←aeval_def,
          ←algebra.smul_def, add_sub_cancel, minpoly.aeval] },
    { rw [eval_map, ←aeval_def, minpoly.aeval] } },
  have h_splits : splits ιEE' h := splits_of_splits_gcd_right ιEE' map_g_ne_zero
    (splitting_field.splits _),
  have h_roots : ∀ x ∈ (h.map ιEE').roots, x = ιEE' β,
  { intros x hx,
    rw mem_roots_map h_ne_zero at hx,
    specialize hc ((ιEE' γ) - (ιEE' (ιFE c)) * x) (begin
      have f_root := root_left_of_root_gcd hx,
      rw [eval₂_comp, eval₂_sub, eval₂_mul,eval₂_C, eval₂_C, eval₂_X, eval₂_map] at f_root,
      exact (mem_roots_map (minpoly.ne_zero hα)).mpr f_root,
    end),
    specialize hc x (begin
      rw [mem_roots_map (minpoly.ne_zero hβ), ←eval₂_map],
      exact root_right_of_root_gcd hx,
    end),
    by_contradiction a,
    apply hc,
    apply (div_eq_iff (sub_ne_zero.mpr a)).mpr,
    simp only [algebra.smul_def, ring_hom.map_add, ring_hom.map_mul, ring_hom.comp_apply],
    ring },
  rw ← eq_X_sub_C_of_separable_of_root_eq h_sep h_root h_splits h_roots,
  transitivity euclidean_domain.gcd (_ : E[X]) (_ : E[X]),
  { dsimp only [p],
    convert (gcd_map (algebra_map F⟮γ⟯ E)).symm },
  { simpa [map_comp, polynomial.map_map, ←is_scalar_tower.algebra_map_eq, h] },
end

end primitive_element_inf

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

section separable_assumption

variable [is_separable F E]

/-- Primitive element theorem: a finite separable field extension `E` of `F` has a
  primitive element, i.e. there is an `α ∈ E` such that `F⟮α⟯ = (⊤ : subalgebra F E)`.-/
theorem exists_primitive_element : ∃ α : E, F⟮α⟯ = ⊤ :=
begin
  rcases is_empty_or_nonempty (fintype F) with F_inf|⟨⟨F_finite⟩⟩,
  { let P : intermediate_field F E → Prop := λ K, ∃ α : E, F⟮α⟯ = K,
    have base : P ⊥ := ⟨0, adjoin_zero⟩,
    have ih : ∀ (K : intermediate_field F E) (x : E), P K → P (K⟮x⟯.restrict_scalars F),
    { intros K β hK,
      cases hK with α hK,
      rw [←hK, adjoin_simple_adjoin_simple],
      haveI : infinite F := is_empty_fintype.mp F_inf,
      cases primitive_element_inf_aux F α β with γ hγ,
      exact ⟨γ, hγ.symm⟩ },
    exact induction_on_adjoin P base ih ⊤ },
  { exactI exists_primitive_element_of_finite_bot F E }
end

/-- Alternative phrasing of primitive element theorem:
a finite separable field extension has a basis `1, α, α^2, ..., α^n`.

See also `exists_primitive_element`. -/
noncomputable def power_basis_of_finite_of_separable : power_basis F E :=
let α := (exists_primitive_element F E).some,
    pb := (adjoin.power_basis (is_separable.is_integral F α)) in
have e : F⟮α⟯ = ⊤ := (exists_primitive_element F E).some_spec,
pb.map ((intermediate_field.equiv_of_eq e).trans intermediate_field.top_equiv)

end separable_assumption

end field

@[simp] lemma alg_hom.card (F E K : Type*) [field F] [field E] [field K] [is_alg_closed K]
  [algebra F E] [finite_dimensional F E] [is_separable F E] [algebra F K] :
  fintype.card (E →ₐ[F] K) = finrank F E :=
begin
  convert (alg_hom.card_of_power_basis (field.power_basis_of_finite_of_separable F E)
    (is_separable.separable _ _) (is_alg_closed.splits_codomain _)).trans
    (power_basis.finrank _).symm,
  apply_instance,
end


import measure_theory.integral.interval_integral

noncomputable theory

def subs (n : ℕ) (t : ℝ) := t/n

lemma thing1 (n : ℕ) : continuous_on (subs n) (set.interval 0 n) :=
begin
  apply continuous_on.div,
  { apply continuous_on_id},
  { apply continuous_on_const},
  { intros x hx,
    have h₁ := set.nonempty_interval,

    sorry},
end

example (n : ℕ) (x : ℝ) : ∫ (t : ℝ) in 0..n, (1 - t/n)^n * t^(x - 1) = n^x * ∫ (s : ℝ) in 0..1, (1 - s)^x * s^(x - 1) :=
begin
  refine interval_integral.integral_comp_mul_deriv'' (λ t, t/n) _ _,
  sorry
end


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Johan Commelin, Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.polynomial.eval
import Mathlib.PostPort

universes u_1 u_2 u w v x

namespace Mathlib

/-!
# Multivariate polynomials

This file defines polynomial rings over a base ring (or even semiring),
with variables from a general type `σ` (which could be infinite).

## Important definitions

Let `R` be a commutative ring (or a semiring) and let `σ` be an arbitrary
type. This file creates the type `mv_polynomial σ R`, which mathematicians
might denote $R[X_i : i \in σ]$. It is the type of multivariate
(a.k.a. multivariable) polynomials, with variables
corresponding to the terms in `σ`, and coefficients in `R`.

### Notation

In the definitions below, we use the following notation:

+ `σ : Type*` (indexing the variables)

+ `R : Type*` `[comm_semiring R]` (the coefficients)

+ `s : σ →₀ ℕ`, a function from `σ` to `ℕ` which is zero away from a finite set.
This will give rise to a monomial in `mv_polynomial σ R` which mathematicians might call `X^s`

+ `a : R`

+ `i : σ`, with corresponding monomial `X i`, often denoted `X_i` by mathematicians

+ `p : mv_polynomial σ R`

### Definitions

* `mv_polynomial σ R` : the type of polynomials with variables of type `σ` and coefficients
  in the commutative semiring `R`

* `monomial s a` : the monomial which mathematically would be denoted `a * X^s`

* `C a` : the constant polynomial with value `a`

* `X i` : the degree one monomial corresponding to i; mathematically this might be denoted `Xᵢ`.

* `coeff s p` : the coefficient of `s` in `p`.

* `eval₂ (f : R → S₁) (g : σ → S₁) p` : given a semiring homomorphism from `R` to another
  semiring `S₁`, and a map `σ → S₁`, evaluates `p` at this valuation, returning a term of type `S₁`.
  Note that `eval₂` can be made using `eval` and `map` (see below), and it has been suggested
  that sticking to `eval` and `map` might make the code less brittle.

* `eval (g : σ → R) p` : given a map `σ → R`, evaluates `p` at this valuation,
  returning a term of type `R`

* `map (f : R → S₁) p` : returns the multivariate polynomial obtained from `p` by the change of
  coefficient semiring corresponding to `f`

## Implementation notes

Recall that if `Y` has a zero, then `X →₀ Y` is the type of functions from `X` to `Y` with finite
support, i.e. such that only finitely many elements of `X` get sent to non-zero terms in `Y`.
The definition of `mv_polynomial σ R` is `(σ →₀ ℕ) →₀ R` ; here `σ →₀ ℕ` denotes the space of all
monomials in the variables, and the function to `R` sends a monomial to its coefficient in
the polynomial being represented.

## Tags

polynomial, multivariate polynomial, multivariable polynomial

-/

/-- Multivariate polynomial, where `σ` is the index set of the variables and
  `R` is the coefficient ring -/
def mv_polynomial (σ : Type u_1) (R : Type u_2) [comm_semiring R] := add_monoid_algebra R (σ →₀ ℕ)

namespace mv_polynomial

protected instance decidable_eq_mv_polynomial {R : Type u} {σ : Type u_1} [comm_semiring R]
    [DecidableEq σ] [DecidableEq R] : DecidableEq (mv_polynomial σ R) :=
  finsupp.finsupp.decidable_eq

protected instance comm_semiring {R : Type u} {σ : Type u_1} [comm_semiring R] :
    comm_semiring (mv_polynomial σ R) :=
  add_monoid_algebra.comm_semiring

protected instance inhabited {R : Type u} {σ : Type u_1} [comm_semiring R] :
    Inhabited (mv_polynomial σ R) :=
  { default := 0 }

protected instance has_scalar {R : Type u} {σ : Type u_1} [comm_semiring R] :
    has_scalar R (mv_polynomial σ R) :=
  add_monoid_algebra.has_scalar

protected instance semimodule {R : Type u} {σ : Type u_1} [comm_semiring R] :
    semimodule R (mv_polynomial σ R) :=
  add_monoid_algebra.semimodule

protected instance algebra {R : Type u} {σ : Type u_1} [comm_semiring R] :
    algebra R (mv_polynomial σ R) :=
  add_monoid_algebra.algebra

/-- The coercion turning an `mv_polynomial` into the function which reports the coefficient
of a given monomial. -/
def coeff_coe_to_fun {R : Type u} {σ : Type u_1} [comm_semiring R] :
    has_coe_to_fun (mv_polynomial σ R) :=
  finsupp.has_coe_to_fun

/-- `monomial s a` is the monomial with coefficient `a` and exponents given by `s`  -/
def monomial {R : Type u} {σ : Type u_1} [comm_semiring R] (s : σ →₀ ℕ) (a : R) :
    mv_polynomial σ R :=
  finsupp.single s a

theorem single_eq_monomial {R : Type u} {σ : Type u_1} [comm_semiring R] (s : σ →₀ ℕ) (a : R) :
    finsupp.single s a = monomial s a :=
  rfl

/-- `C a` is the constant polynomial with value `a` -/
def C {R : Type u} {σ : Type u_1} [comm_semiring R] : R →+* mv_polynomial σ R :=
  ring_hom.mk (monomial 0) sorry sorry sorry sorry

theorem algebra_map_eq (R : Type u) (σ : Type u_1) [comm_semiring R] :
    algebra_map R (mv_polynomial σ R) = C :=
  rfl

/-- `X n` is the degree `1` monomial $X_n$. -/
def X {R : Type u} {σ : Type u_1} [comm_semiring R] (n : σ) : mv_polynomial σ R :=
  monomial (finsupp.single n 1) 1

@[simp] theorem C_0 {R : Type u} {σ : Type u_1} [comm_semiring R] : coe_fn C 0 = 0 := sorry

@[simp] theorem C_1 {R : Type u} {σ : Type u_1} [comm_semiring R] : coe_fn C 1 = 1 := rfl

theorem C_mul_monomial {R : Type u} {σ : Type u_1} {a : R} {a' : R} {s : σ →₀ ℕ} [comm_semiring R] :
    coe_fn C a * monomial s a' = monomial s (a * a') :=
  sorry

@[simp] theorem C_add {R : Type u} {σ : Type u_1} {a : R} {a' : R} [comm_semiring R] :
    coe_fn C (a + a') = coe_fn C a + coe_fn C a' :=
  finsupp.single_add

@[simp] theorem C_mul {R : Type u} {σ : Type u_1} {a : R} {a' : R} [comm_semiring R] :
    coe_fn C (a * a') = coe_fn C a * coe_fn C a' :=
  Eq.symm C_mul_monomial

@[simp] theorem C_pow {R : Type u} {σ : Type u_1} [comm_semiring R] (a : R) (n : ℕ) :
    coe_fn C (a ^ n) = coe_fn C a ^ n :=
  sorry

theorem C_injective (σ : Type u_1) (R : Type u_2) [comm_semiring R] : function.injective ⇑C :=
  finsupp.single_injective 0

theorem C_surjective {R : Type u_1} [comm_semiring R] (σ : Type u_2) (hσ : ¬Nonempty σ) :
    function.surjective ⇑C :=
  sorry

theorem C_surjective_fin_0 {R : Type u_1} [comm_ring R] : function.surjective ⇑C := sorry

@[simp] theorem C_inj {σ : Type u_1} (R : Type u_2) [comm_semiring R] (r : R) (s : R) :
    coe_fn C r = coe_fn C s ↔ r = s :=
  function.injective.eq_iff (C_injective σ R)

protected instance infinite_of_infinite (σ : Type u_1) (R : Type u_2) [comm_semiring R]
    [infinite R] : infinite (mv_polynomial σ R) :=
  infinite.of_injective (⇑C) (C_injective σ R)

protected instance infinite_of_nonempty (σ : Type u_1) (R : Type u_2) [Nonempty σ] [comm_semiring R]
    [nontrivial R] : infinite (mv_polynomial σ R) :=
  sorry

theorem C_eq_coe_nat {R : Type u} {σ : Type u_1} [comm_semiring R] (n : ℕ) : coe_fn C ↑n = ↑n :=
  sorry

theorem C_mul' {R : Type u} {σ : Type u_1} {a : R} [comm_semiring R] {p : mv_polynomial σ R} :
    coe_fn C a * p = a • p :=
  sorry

theorem smul_eq_C_mul {R : Type u} {σ : Type u_1} [comm_semiring R] (p : mv_polynomial σ R)
    (a : R) : a • p = coe_fn C a * p :=
  Eq.symm C_mul'

theorem X_pow_eq_single {R : Type u} {σ : Type u_1} {e : ℕ} {n : σ} [comm_semiring R] :
    X n ^ e = monomial (finsupp.single n e) 1 :=
  sorry

theorem monomial_add_single {R : Type u} {σ : Type u_1} {a : R} {e : ℕ} {n : σ} {s : σ →₀ ℕ}
    [comm_semiring R] : monomial (s + finsupp.single n e) a = monomial s a * X n ^ e :=
  sorry

theorem monomial_single_add {R : Type u} {σ : Type u_1} {a : R} {e : ℕ} {n : σ} {s : σ →₀ ℕ}
    [comm_semiring R] : monomial (finsupp.single n e + s) a = X n ^ e * monomial s a :=
  sorry

theorem single_eq_C_mul_X {R : Type u} {σ : Type u_1} [comm_semiring R] {s : σ} {a : R} {n : ℕ} :
    monomial (finsupp.single s n) a = coe_fn C a * X s ^ n :=
  sorry

@[simp] theorem monomial_add {R : Type u} {σ : Type u_1} [comm_semiring R] {s : σ →₀ ℕ} {a : R}
    {b : R} : monomial s a + monomial s b = monomial s (a + b) :=
  sorry

@[simp] theorem monomial_mul {R : Type u} {σ : Type u_1} [comm_semiring R] {s : σ →₀ ℕ}
    {s' : σ →₀ ℕ} {a : R} {b : R} : monomial s a * monomial s' b = monomial (s + s') (a * b) :=
  sorry

@[simp] theorem monomial_zero {R : Type u} {σ : Type u_1} [comm_semiring R] {s : σ →₀ ℕ} :
    monomial s 0 = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (monomial s 0 = 0)) (monomial.equations._eqn_1 s 0)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (finsupp.single s 0 = 0)) finsupp.single_zero)) (Eq.refl 0))

@[simp] theorem sum_monomial {R : Type u} {σ : Type u_1} [comm_semiring R] {A : Type u_2}
    [add_comm_monoid A] {u : σ →₀ ℕ} {r : R} {b : (σ →₀ ℕ) → R → A} (w : b u 0 = 0) :
    finsupp.sum (monomial u r) b = b u r :=
  finsupp.sum_single_index w

theorem monomial_eq {R : Type u} {σ : Type u_1} {a : R} {s : σ →₀ ℕ} [comm_semiring R] :
    monomial s a = coe_fn C a * finsupp.prod s fun (n : σ) (e : ℕ) => X n ^ e :=
  sorry

theorem induction_on {R : Type u} {σ : Type u_1} [comm_semiring R] {M : mv_polynomial σ R → Prop}
    (p : mv_polynomial σ R) (h_C : ∀ (a : R), M (coe_fn C a))
    (h_add : ∀ (p q : mv_polynomial σ R), M p → M q → M (p + q))
    (h_X : ∀ (p : mv_polynomial σ R) (n : σ), M p → M (p * X n)) : M p :=
  sorry

theorem induction_on' {R : Type u} {σ : Type u_1} [comm_semiring R] {P : mv_polynomial σ R → Prop}
    (p : mv_polynomial σ R) (h1 : ∀ (u : σ →₀ ℕ) (a : R), P (monomial u a))
    (h2 : ∀ (p q : mv_polynomial σ R), P p → P q → P (p + q)) : P p :=
  sorry

theorem ring_hom_ext {R : Type u} {σ : Type u_1} [comm_semiring R] {A : Type u_2} [semiring A]
    {f : mv_polynomial σ R →+* A} {g : mv_polynomial σ R →+* A}
    (hC : ∀ (r : R), coe_fn f (coe_fn C r) = coe_fn g (coe_fn C r))
    (hX : ∀ (i : σ), coe_fn f (X i) = coe_fn g (X i)) : f = g :=
  add_monoid_algebra.ring_hom_ext' (ring_hom.ext fun (x : R) => hC x)
    (finsupp.mul_hom_ext' fun (x : σ) => monoid_hom.ext_mnat (hX x))

theorem hom_eq_hom {R : Type u} {S₂ : Type w} {σ : Type u_1} [comm_semiring R] [semiring S₂]
    (f : mv_polynomial σ R →+* S₂) (g : mv_polynomial σ R →+* S₂)
    (hC : ∀ (a : R), coe_fn f (coe_fn C a) = coe_fn g (coe_fn C a))
    (hX : ∀ (n : σ), coe_fn f (X n) = coe_fn g (X n)) (p : mv_polynomial σ R) :
    coe_fn f p = coe_fn g p :=
  ring_hom.congr_fun (ring_hom_ext hC hX) p

theorem is_id {R : Type u} {σ : Type u_1} [comm_semiring R]
    (f : mv_polynomial σ R →+* mv_polynomial σ R)
    (hC : ∀ (a : R), coe_fn f (coe_fn C a) = coe_fn C a) (hX : ∀ (n : σ), coe_fn f (X n) = X n)
    (p : mv_polynomial σ R) : coe_fn f p = p :=
  hom_eq_hom f (ring_hom.id (mv_polynomial σ R)) hC hX p

theorem alg_hom_ext {R : Type u} {σ : Type u_1} [comm_semiring R] {A : Type u_2} [comm_semiring A]
    [algebra R A] {f : alg_hom R (mv_polynomial σ R) A} {g : alg_hom R (mv_polynomial σ R) A}
    (hf : ∀ (i : σ), coe_fn f (X i) = coe_fn g (X i)) : f = g :=
  add_monoid_algebra.alg_hom_ext' (finsupp.mul_hom_ext' fun (x : σ) => monoid_hom.ext_mnat (hf x))

@[simp] theorem alg_hom_C {R : Type u} {σ : Type u_1} [comm_semiring R]
    (f : alg_hom R (mv_polynomial σ R) (mv_polynomial σ R)) (r : R) :
    coe_fn f (coe_fn C r) = coe_fn C r :=
  alg_hom.commutes f r

-- While setting up `coeff`, we make `mv_polynomial` reducible so we can treat it as a function.

/-- The coefficient of the monomial `m` in the multi-variable polynomial `p`. -/
def coeff {R : Type u} {σ : Type u_1} [comm_semiring R] (m : σ →₀ ℕ) (p : mv_polynomial σ R) : R :=
  coe_fn p m

theorem ext {R : Type u} {σ : Type u_1} [comm_semiring R] (p : mv_polynomial σ R)
    (q : mv_polynomial σ R) : (∀ (m : σ →₀ ℕ), coeff m p = coeff m q) → p = q :=
  finsupp.ext

theorem ext_iff {R : Type u} {σ : Type u_1} [comm_semiring R] (p : mv_polynomial σ R)
    (q : mv_polynomial σ R) : p = q ↔ ∀ (m : σ →₀ ℕ), coeff m p = coeff m q :=
  { mp :=
      fun (h : p = q) (m : σ →₀ ℕ) =>
        eq.mpr (id (Eq._oldrec (Eq.refl (coeff m p = coeff m q)) h)) (Eq.refl (coeff m q)),
    mpr := ext p q }

@[simp] theorem coeff_add {R : Type u} {σ : Type u_1} [comm_semiring R] (m : σ →₀ ℕ)
    (p : mv_polynomial σ R) (q : mv_polynomial σ R) : coeff m (p + q) = coeff m p + coeff m q :=
  finsupp.add_apply

@[simp] theorem coeff_zero {R : Type u} {σ : Type u_1} [comm_semiring R] (m : σ →₀ ℕ) :
    coeff m 0 = 0 :=
  rfl

@[simp] theorem coeff_zero_X {R : Type u} {σ : Type u_1} [comm_semiring R] (i : σ) :
    coeff 0 (X i) = 0 :=
  sorry

protected instance coeff.is_add_monoid_hom {R : Type u} {σ : Type u_1} [comm_semiring R]
    (m : σ →₀ ℕ) : is_add_monoid_hom (coeff m) :=
  is_add_monoid_hom.mk (coeff_zero m)

theorem coeff_sum {R : Type u} {σ : Type u_1} [comm_semiring R] {X : Type u_2} (s : finset X)
    (f : X → mv_polynomial σ R) (m : σ →₀ ℕ) :
    coeff m (finset.sum s fun (x : X) => f x) = finset.sum s fun (x : X) => coeff m (f x) :=
  Eq.symm (finset.sum_hom s (coeff m))

theorem monic_monomial_eq {R : Type u} {σ : Type u_1} [comm_semiring R] (m : σ →₀ ℕ) :
    monomial m 1 = finsupp.prod m fun (n : σ) (e : ℕ) => X n ^ e :=
  sorry

@[simp] theorem coeff_monomial {R : Type u} {σ : Type u_1} [comm_semiring R] (m : σ →₀ ℕ)
    (n : σ →₀ ℕ) (a : R) : coeff m (monomial n a) = ite (n = m) a 0 :=
  sorry

@[simp] theorem coeff_C {R : Type u} {σ : Type u_1} [comm_semiring R] (m : σ →₀ ℕ) (a : R) :
    coeff m (coe_fn C a) = ite (0 = m) a 0 :=
  sorry

theorem coeff_X_pow {R : Type u} {σ : Type u_1} [comm_semiring R] (i : σ) (m : σ →₀ ℕ) (k : ℕ) :
    coeff m (X i ^ k) = ite (finsupp.single i k = m) 1 0 :=
  sorry

theorem coeff_X' {R : Type u} {σ : Type u_1} [comm_semiring R] (i : σ) (m : σ →₀ ℕ) :
    coeff m (X i) = ite (finsupp.single i 1 = m) 1 0 :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (coeff m (X i) = ite (finsupp.single i 1 = m) 1 0))
        (Eq.symm (coeff_X_pow i m 1))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (coeff m (X i) = coeff m (X i ^ 1))) (pow_one (X i))))
      (Eq.refl (coeff m (X i))))

@[simp] theorem coeff_X {R : Type u} {σ : Type u_1} [comm_semiring R] (i : σ) :
    coeff (finsupp.single i 1) (X i) = 1 :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (coeff (finsupp.single i 1) (X i) = 1))
        (coeff_X' i (finsupp.single i 1))))
    (eq.mpr
      (id
        (Eq._oldrec (Eq.refl (ite (finsupp.single i 1 = finsupp.single i 1) 1 0 = 1)) (if_pos rfl)))
      (Eq.refl 1))

@[simp] theorem coeff_C_mul {R : Type u} {σ : Type u_1} [comm_semiring R] (m : σ →₀ ℕ) (a : R)
    (p : mv_polynomial σ R) : coeff m (coe_fn C a * p) = a * coeff m p :=
  sorry

theorem coeff_mul {R : Type u} {σ : Type u_1} [comm_semiring R] (p : mv_polynomial σ R)
    (q : mv_polynomial σ R) (n : σ →₀ ℕ) :
    coeff n (p * q) =
        finset.sum (finsupp.support (finsupp.antidiagonal n))
          fun (x : (σ →₀ ℕ) × (σ →₀ ℕ)) => coeff (prod.fst x) p * coeff (prod.snd x) q :=
  sorry

@[simp] theorem coeff_mul_X {R : Type u} {σ : Type u_1} [comm_semiring R] (m : σ →₀ ℕ) (s : σ)
    (p : mv_polynomial σ R) : coeff (m + finsupp.single s 1) (p * X s) = coeff m p :=
  sorry

theorem coeff_mul_X' {R : Type u} {σ : Type u_1} [comm_semiring R] (m : σ →₀ ℕ) (s : σ)
    (p : mv_polynomial σ R) :
    coeff m (p * X s) = ite (s ∈ finsupp.support m) (coeff (m - finsupp.single s 1) p) 0 :=
  sorry

theorem eq_zero_iff {R : Type u} {σ : Type u_1} [comm_semiring R] {p : mv_polynomial σ R} :
    p = 0 ↔ ∀ (d : σ →₀ ℕ), coeff d p = 0 :=
  sorry

theorem ne_zero_iff {R : Type u} {σ : Type u_1} [comm_semiring R] {p : mv_polynomial σ R} :
    p ≠ 0 ↔ ∃ (d : σ →₀ ℕ), coeff d p ≠ 0 :=
  sorry

theorem exists_coeff_ne_zero {R : Type u} {σ : Type u_1} [comm_semiring R] {p : mv_polynomial σ R}
    (h : p ≠ 0) : ∃ (d : σ →₀ ℕ), coeff d p ≠ 0 :=
  iff.mp ne_zero_iff h

theorem C_dvd_iff_dvd_coeff {R : Type u} {σ : Type u_1} [comm_semiring R] (r : R)
    (φ : mv_polynomial σ R) : coe_fn C r ∣ φ ↔ ∀ (i : σ →₀ ℕ), r ∣ coeff i φ :=
  sorry

/--
`constant_coeff p` returns the constant term of the polynomial `p`, defined as `coeff 0 p`.
This is a ring homomorphism.
-/
def constant_coeff {R : Type u} {σ : Type u_1} [comm_semiring R] : mv_polynomial σ R →+* R :=
  ring_hom.mk (coeff 0) sorry sorry sorry sorry

theorem constant_coeff_eq {R : Type u} {σ : Type u_1} [comm_semiring R] :
    ⇑constant_coeff = coeff 0 :=
  rfl

@[simp] theorem constant_coeff_C {R : Type u} {σ : Type u_1} [comm_semiring R] (r : R) :
    coe_fn constant_coeff (coe_fn C r) = r :=
  sorry

@[simp] theorem constant_coeff_X {R : Type u} {σ : Type u_1} [comm_semiring R] (i : σ) :
    coe_fn constant_coeff (X i) = 0 :=
  sorry

theorem constant_coeff_monomial {R : Type u} {σ : Type u_1} [comm_semiring R] (d : σ →₀ ℕ) (r : R) :
    coe_fn constant_coeff (monomial d r) = ite (d = 0) r 0 :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (coe_fn constant_coeff (monomial d r) = ite (d = 0) r 0))
        constant_coeff_eq))
    (eq.mpr
      (id (Eq._oldrec (Eq.refl (coeff 0 (monomial d r) = ite (d = 0) r 0)) (coeff_monomial 0 d r)))
      (Eq.refl (ite (d = 0) r 0)))

@[simp] theorem constant_coeff_comp_C (R : Type u) (σ : Type u_1) [comm_semiring R] :
    ring_hom.comp constant_coeff C = ring_hom.id R :=
  ring_hom.ext fun (x : R) => constant_coeff_C x

@[simp] theorem constant_coeff_comp_algebra_map (R : Type u) (σ : Type u_1) [comm_semiring R] :
    ring_hom.comp constant_coeff (algebra_map R (mv_polynomial σ R)) = ring_hom.id R :=
  constant_coeff_comp_C R σ

@[simp] theorem support_sum_monomial_coeff {R : Type u} {σ : Type u_1} [comm_semiring R]
    (p : mv_polynomial σ R) :
    (finset.sum (finsupp.support p) fun (v : σ →₀ ℕ) => monomial v (coeff v p)) = p :=
  finsupp.sum_single p

theorem as_sum {R : Type u} {σ : Type u_1} [comm_semiring R] (p : mv_polynomial σ R) :
    p = finset.sum (finsupp.support p) fun (v : σ →₀ ℕ) => monomial v (coeff v p) :=
  Eq.symm (support_sum_monomial_coeff p)

/-- Evaluate a polynomial `p` given a valuation `g` of all the variables
  and a ring hom `f` from the scalar ring to the target -/
def eval₂ {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    (f : R →+* S₁) (g : σ → S₁) (p : mv_polynomial σ R) : S₁ :=
  finsupp.sum p
    fun (s : σ →₀ ℕ) (a : R) => coe_fn f a * finsupp.prod s fun (n : σ) (e : ℕ) => g n ^ e

theorem eval₂_eq {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    (g : R →+* S₁) (x : σ → S₁) (f : mv_polynomial σ R) :
    eval₂ g x f =
        finset.sum (finsupp.support f)
          fun (d : σ →₀ ℕ) =>
            coe_fn g (coeff d f) *
              finset.prod (finsupp.support d) fun (i : σ) => x i ^ coe_fn d i :=
  rfl

theorem eval₂_eq' {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    [fintype σ] (g : R →+* S₁) (x : σ → S₁) (f : mv_polynomial σ R) :
    eval₂ g x f =
        finset.sum (finsupp.support f)
          fun (d : σ →₀ ℕ) =>
            coe_fn g (coeff d f) * finset.prod finset.univ fun (i : σ) => x i ^ coe_fn d i :=
  sorry

@[simp] theorem eval₂_zero {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) : eval₂ f g 0 = 0 :=
  finsupp.sum_zero_index

@[simp] theorem eval₂_add {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    {p : mv_polynomial σ R} {q : mv_polynomial σ R} [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) :
    eval₂ f g (p + q) = eval₂ f g p + eval₂ f g q :=
  sorry

@[simp] theorem eval₂_monomial {R : Type u} {S₁ : Type v} {σ : Type u_1} {a : R} {s : σ →₀ ℕ}
    [comm_semiring R] [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) :
    eval₂ f g (monomial s a) = coe_fn f a * finsupp.prod s fun (n : σ) (e : ℕ) => g n ^ e :=
  sorry

@[simp] theorem eval₂_C {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (a : R) : eval₂ f g (coe_fn C a) = coe_fn f a :=
  sorry

@[simp] theorem eval₂_one {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) : eval₂ f g 1 = 1 :=
  Eq.trans (eval₂_C f g 1) (is_semiring_hom.map_one ⇑f)

@[simp] theorem eval₂_X {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (n : σ) : eval₂ f g (X n) = g n :=
  sorry

theorem eval₂_mul_monomial {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    {p : mv_polynomial σ R} [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) {s : σ →₀ ℕ} {a : R} :
    eval₂ f g (p * monomial s a) =
        eval₂ f g p * coe_fn f a * finsupp.prod s fun (n : σ) (e : ℕ) => g n ^ e :=
  sorry

@[simp] theorem eval₂_mul {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    {q : mv_polynomial σ R} [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) {p : mv_polynomial σ R} :
    eval₂ f g (p * q) = eval₂ f g p * eval₂ f g q :=
  sorry

@[simp] theorem eval₂_pow {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) {p : mv_polynomial σ R} {n : ℕ} :
    eval₂ f g (p ^ n) = eval₂ f g p ^ n :=
  sorry

protected instance eval₂.is_semiring_hom {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) : is_semiring_hom (eval₂ f g) :=
  is_semiring_hom.mk (eval₂_zero f g) (eval₂_one f g)
    (fun (p q : mv_polynomial σ R) => eval₂_add f g) fun (p q : mv_polynomial σ R) => eval₂_mul f g

/-- `mv_polynomial.eval₂` as a `ring_hom`. -/
def eval₂_hom {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    (f : R →+* S₁) (g : σ → S₁) : mv_polynomial σ R →+* S₁ :=
  ring_hom.of (eval₂ f g)

@[simp] theorem coe_eval₂_hom {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) : ⇑(eval₂_hom f g) = eval₂ f g :=
  rfl

theorem eval₂_hom_congr {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] {f₁ : R →+* S₁} {f₂ : R →+* S₁} {g₁ : σ → S₁} {g₂ : σ → S₁}
    {p₁ : mv_polynomial σ R} {p₂ : mv_polynomial σ R} :
    f₁ = f₂ → g₁ = g₂ → p₁ = p₂ → coe_fn (eval₂_hom f₁ g₁) p₁ = coe_fn (eval₂_hom f₂ g₂) p₂ :=
  fun (ᾰ : f₁ = f₂) (ᾰ_1 : g₁ = g₂) (ᾰ_2 : p₁ = p₂) =>
    Eq._oldrec (Eq._oldrec (Eq._oldrec (Eq.refl (coe_fn (eval₂_hom f₁ g₁) p₁)) ᾰ_2) ᾰ_1) ᾰ

@[simp] theorem eval₂_hom_C {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (r : R) :
    coe_fn (eval₂_hom f g) (coe_fn C r) = coe_fn f r :=
  eval₂_C f g r

@[simp] theorem eval₂_hom_X' {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (i : σ) : coe_fn (eval₂_hom f g) (X i) = g i :=
  eval₂_X f g i

@[simp] theorem comp_eval₂_hom {R : Type u} {S₁ : Type v} {S₂ : Type w} {σ : Type u_1}
    [comm_semiring R] [comm_semiring S₁] [comm_semiring S₂] (f : R →+* S₁) (g : σ → S₁)
    (φ : S₁ →+* S₂) :
    ring_hom.comp φ (eval₂_hom f g) = eval₂_hom (ring_hom.comp φ f) fun (i : σ) => coe_fn φ (g i) :=
  sorry

theorem map_eval₂_hom {R : Type u} {S₁ : Type v} {S₂ : Type w} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] [comm_semiring S₂] (f : R →+* S₁) (g : σ → S₁) (φ : S₁ →+* S₂)
    (p : mv_polynomial σ R) :
    coe_fn φ (coe_fn (eval₂_hom f g) p) =
        coe_fn (eval₂_hom (ring_hom.comp φ f) fun (i : σ) => coe_fn φ (g i)) p :=
  sorry

theorem eval₂_hom_monomial {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (d : σ →₀ ℕ) (r : R) :
    coe_fn (eval₂_hom f g) (monomial d r) =
        coe_fn f r * finsupp.prod d fun (i : σ) (k : ℕ) => g i ^ k :=
  sorry

theorem eval₂_comp_left {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] {S₂ : Type u_2} [comm_semiring S₂] (k : S₁ →+* S₂) (f : R →+* S₁)
    (g : σ → S₁) (p : mv_polynomial σ R) :
    coe_fn k (eval₂ f g p) = eval₂ (ring_hom.comp k f) (⇑k ∘ g) p :=
  sorry

@[simp] theorem eval₂_eta {R : Type u} {σ : Type u_1} [comm_semiring R] (p : mv_polynomial σ R) :
    eval₂ C X p = p :=
  sorry

theorem eval₂_congr {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    {p : mv_polynomial σ R} [comm_semiring S₁] (f : R →+* S₁) (g₁ : σ → S₁) (g₂ : σ → S₁)
    (h : ∀ {i : σ} {c : σ →₀ ℕ}, i ∈ finsupp.support c → coeff c p ≠ 0 → g₁ i = g₂ i) :
    eval₂ f g₁ p = eval₂ f g₂ p :=
  sorry

@[simp] theorem eval₂_prod {R : Type u} {S₁ : Type v} {S₂ : Type w} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (s : finset S₂) (p : S₂ → mv_polynomial σ R) :
    eval₂ f g (finset.prod s fun (x : S₂) => p x) = finset.prod s fun (x : S₂) => eval₂ f g (p x) :=
  Eq.symm (finset.prod_hom s (eval₂ f g))

@[simp] theorem eval₂_sum {R : Type u} {S₁ : Type v} {S₂ : Type w} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (s : finset S₂) (p : S₂ → mv_polynomial σ R) :
    eval₂ f g (finset.sum s fun (x : S₂) => p x) = finset.sum s fun (x : S₂) => eval₂ f g (p x) :=
  Eq.symm (finset.sum_hom s (eval₂ f g))

theorem eval₂_assoc {R : Type u} {S₁ : Type v} {S₂ : Type w} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (q : S₂ → mv_polynomial σ R)
    (p : mv_polynomial S₂ R) :
    eval₂ f (fun (t : S₂) => eval₂ f g (q t)) p = eval₂ f g (eval₂ C q p) :=
  sorry

/-- Evaluate a polynomial `p` given a valuation `f` of all the variables -/
def eval {R : Type u} {σ : Type u_1} [comm_semiring R] (f : σ → R) : mv_polynomial σ R →+* R :=
  eval₂_hom (ring_hom.id R) f

theorem eval_eq {R : Type u} {σ : Type u_1} [comm_semiring R] (x : σ → R) (f : mv_polynomial σ R) :
    coe_fn (eval x) f =
        finset.sum (finsupp.support f)
          fun (d : σ →₀ ℕ) =>
            coeff d f * finset.prod (finsupp.support d) fun (i : σ) => x i ^ coe_fn d i :=
  rfl

theorem eval_eq' {R : Type u} {σ : Type u_1} [comm_semiring R] [fintype σ] (x : σ → R)
    (f : mv_polynomial σ R) :
    coe_fn (eval x) f =
        finset.sum (finsupp.support f)
          fun (d : σ →₀ ℕ) => coeff d f * finset.prod finset.univ fun (i : σ) => x i ^ coe_fn d i :=
  eval₂_eq' (ring_hom.id R) x f

theorem eval_monomial {R : Type u} {σ : Type u_1} {a : R} {s : σ →₀ ℕ} [comm_semiring R]
    {f : σ → R} :
    coe_fn (eval f) (monomial s a) = a * finsupp.prod s fun (n : σ) (e : ℕ) => f n ^ e :=
  eval₂_monomial (ring_hom.id R) fun (n : σ) => f n

@[simp] theorem eval_C {R : Type u} {σ : Type u_1} [comm_semiring R] {f : σ → R} (a : R) :
    coe_fn (eval f) (coe_fn C a) = a :=
  eval₂_C (ring_hom.id R) fun (n : σ) => f n

@[simp] theorem eval_X {R : Type u} {σ : Type u_1} [comm_semiring R] {f : σ → R} (n : σ) :
    coe_fn (eval f) (X n) = f n :=
  eval₂_X (ring_hom.id R) fun (n : σ) => f n

@[simp] theorem smul_eval {R : Type u} {σ : Type u_1} [comm_semiring R] (x : σ → R)
    (p : mv_polynomial σ R) (s : R) : coe_fn (eval x) (s • p) = s * coe_fn (eval x) p :=
  sorry

theorem eval_sum {R : Type u} {σ : Type u_1} [comm_semiring R] {ι : Type u_2} (s : finset ι)
    (f : ι → mv_polynomial σ R) (g : σ → R) :
    coe_fn (eval g) (finset.sum s fun (i : ι) => f i) =
        finset.sum s fun (i : ι) => coe_fn (eval g) (f i) :=
  ring_hom.map_sum (eval g) (fun (i : ι) => f i) s

theorem eval_prod {R : Type u} {σ : Type u_1} [comm_semiring R] {ι : Type u_2} (s : finset ι)
    (f : ι → mv_polynomial σ R) (g : σ → R) :
    coe_fn (eval g) (finset.prod s fun (i : ι) => f i) =
        finset.prod s fun (i : ι) => coe_fn (eval g) (f i) :=
  ring_hom.map_prod (eval g) (fun (i : ι) => f i) s

theorem eval_assoc {R : Type u} {σ : Type u_1} [comm_semiring R] {τ : Type u_2}
    (f : σ → mv_polynomial τ R) (g : τ → R) (p : mv_polynomial σ R) :
    coe_fn (eval (⇑(eval g) ∘ f)) p = coe_fn (eval g) (eval₂ C f p) :=
  sorry

/-- `map f p` maps a polynomial `p` across a ring hom `f` -/
def map {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    (f : R →+* S₁) : mv_polynomial σ R →+* mv_polynomial σ S₁ :=
  eval₂_hom (ring_hom.comp C f) X

@[simp] theorem map_monomial {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (s : σ →₀ ℕ) (a : R) :
    coe_fn (map f) (monomial s a) = monomial s (coe_fn f a) :=
  Eq.trans (eval₂_monomial (ring_hom.comp C f) fun (n : σ) => X n) (Eq.symm monomial_eq)

@[simp] theorem map_C {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    (f : R →+* S₁) (a : R) : coe_fn (map f) (coe_fn C a) = coe_fn C (coe_fn f a) :=
  map_monomial f 0

@[simp] theorem map_X {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    (f : R →+* S₁) (n : σ) : coe_fn (map f) (X n) = X n :=
  eval₂_X (ring_hom.comp C f) fun (n : σ) => X n

theorem map_id {R : Type u} {σ : Type u_1} [comm_semiring R] (p : mv_polynomial σ R) :
    coe_fn (map (ring_hom.id R)) p = p :=
  eval₂_eta

theorem map_map {R : Type u} {S₁ : Type v} {S₂ : Type w} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) [comm_semiring S₂] (g : S₁ →+* S₂) (p : mv_polynomial σ R) :
    coe_fn (map g) (coe_fn (map f) p) = coe_fn (map (ring_hom.comp g f)) p :=
  sorry

theorem eval₂_eq_eval_map {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (p : mv_polynomial σ R) :
    eval₂ f g p = coe_fn (eval g) (coe_fn (map f) p) :=
  sorry

theorem eval₂_comp_right {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] {S₂ : Type u_2} [comm_semiring S₂] (k : S₁ →+* S₂) (f : R →+* S₁)
    (g : σ → S₁) (p : mv_polynomial σ R) :
    coe_fn k (eval₂ f g p) = eval₂ k (⇑k ∘ g) (coe_fn (map f) p) :=
  sorry

theorem map_eval₂ {R : Type u} {S₁ : Type v} {S₂ : Type w} {S₃ : Type x} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : S₂ → mv_polynomial S₃ R) (p : mv_polynomial S₂ R) :
    coe_fn (map f) (eval₂ C g p) = eval₂ C (⇑(map f) ∘ g) (coe_fn (map f) p) :=
  sorry

theorem coeff_map {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    (f : R →+* S₁) (p : mv_polynomial σ R) (m : σ →₀ ℕ) :
    coeff m (coe_fn (map f) p) = coe_fn f (coeff m p) :=
  sorry

theorem map_injective {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    (f : R →+* S₁) (hf : function.injective ⇑f) : function.injective ⇑(map f) :=
  sorry

@[simp] theorem eval_map {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : σ → S₁) (p : mv_polynomial σ R) :
    coe_fn (eval g) (coe_fn (map f) p) = eval₂ f g p :=
  sorry

@[simp] theorem eval₂_map {R : Type u} {S₁ : Type v} {S₂ : Type w} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] [comm_semiring S₂] (f : R →+* S₁) (g : σ → S₂) (φ : S₁ →+* S₂)
    (p : mv_polynomial σ R) : eval₂ φ g (coe_fn (map f) p) = eval₂ (ring_hom.comp φ f) g p :=
  sorry

@[simp] theorem eval₂_hom_map_hom {R : Type u} {S₁ : Type v} {S₂ : Type w} {σ : Type u_1}
    [comm_semiring R] [comm_semiring S₁] [comm_semiring S₂] (f : R →+* S₁) (g : σ → S₂)
    (φ : S₁ →+* S₂) (p : mv_polynomial σ R) :
    coe_fn (eval₂_hom φ g) (coe_fn (map f) p) = coe_fn (eval₂_hom (ring_hom.comp φ f) g) p :=
  eval₂_map f g φ p

@[simp] theorem constant_coeff_map {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (φ : mv_polynomial σ R) :
    coe_fn constant_coeff (coe_fn (map f) φ) = coe_fn f (coe_fn constant_coeff φ) :=
  coeff_map f φ 0

theorem constant_coeff_comp_map {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) :
    ring_hom.comp constant_coeff (map f) = ring_hom.comp f constant_coeff :=
  sorry

theorem support_map_subset {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (p : mv_polynomial σ R) :
    finsupp.support (coe_fn (map f) p) ⊆ finsupp.support p :=
  sorry

theorem support_map_of_injective {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (p : mv_polynomial σ R) {f : R →+* S₁} (hf : function.injective ⇑f) :
    finsupp.support (coe_fn (map f) p) = finsupp.support p :=
  sorry

theorem C_dvd_iff_map_hom_eq_zero {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (q : R →+* S₁) (r : R) (hr : ∀ (r' : R), coe_fn q r' = 0 ↔ r ∣ r')
    (φ : mv_polynomial σ R) : coe_fn C r ∣ φ ↔ coe_fn (map q) φ = 0 :=
  sorry

theorem map_map_range_eq_iff {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] (f : R →+* S₁) (g : S₁ → R) (hg : g 0 = 0) (φ : mv_polynomial σ S₁) :
    coe_fn (map f) (finsupp.map_range g hg φ) = φ ↔
        ∀ (d : σ →₀ ℕ), coe_fn f (g (coeff d φ)) = coeff d φ :=
  sorry

/-! ### The algebra of multivariate polynomials -/

/-- A map `σ → S₁` where `S₁` is an algebra over `R` generates an `R`-algebra homomorphism
from multivariate polynomials over `σ` to `S₁`. -/
def aeval {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] (f : σ → S₁)
    [comm_semiring S₁] [algebra R S₁] : alg_hom R (mv_polynomial σ R) S₁ :=
  alg_hom.mk (ring_hom.to_fun (eval₂_hom (algebra_map R S₁) f)) sorry sorry sorry sorry sorry

theorem aeval_def {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] (f : σ → S₁)
    [comm_semiring S₁] [algebra R S₁] (p : mv_polynomial σ R) :
    coe_fn (aeval f) p = eval₂ (algebra_map R S₁) f p :=
  rfl

theorem aeval_eq_eval₂_hom {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] (f : σ → S₁)
    [comm_semiring S₁] [algebra R S₁] (p : mv_polynomial σ R) :
    coe_fn (aeval f) p = coe_fn (eval₂_hom (algebra_map R S₁) f) p :=
  rfl

@[simp] theorem aeval_X {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] (f : σ → S₁)
    [comm_semiring S₁] [algebra R S₁] (s : σ) : coe_fn (aeval f) (X s) = f s :=
  eval₂_X (algebra_map R S₁) (fun (n : σ) => f n) s

@[simp] theorem aeval_C {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] (f : σ → S₁)
    [comm_semiring S₁] [algebra R S₁] (r : R) :
    coe_fn (aeval f) (coe_fn C r) = coe_fn (algebra_map R S₁) r :=
  eval₂_C (algebra_map R S₁) (fun (n : σ) => f n) r

theorem aeval_unique {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] [comm_semiring S₁]
    [algebra R S₁] (φ : alg_hom R (mv_polynomial σ R) S₁) : φ = aeval (⇑φ ∘ X) :=
  sorry

theorem comp_aeval {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R] (f : σ → S₁)
    [comm_semiring S₁] [algebra R S₁] {B : Type u_2} [comm_semiring B] [algebra R B]
    (φ : alg_hom R S₁ B) : alg_hom.comp φ (aeval f) = aeval fun (i : σ) => coe_fn φ (f i) :=
  sorry

@[simp] theorem map_aeval {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] [algebra R S₁] {B : Type u_2} [comm_semiring B] (g : σ → S₁) (φ : S₁ →+* B)
    (p : mv_polynomial σ R) :
    coe_fn φ (coe_fn (aeval g) p) =
        coe_fn (eval₂_hom (ring_hom.comp φ (algebra_map R S₁)) fun (i : σ) => coe_fn φ (g i)) p :=
  sorry

@[simp] theorem eval₂_hom_zero {R : Type u} {S₂ : Type w} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₂] (f : R →+* S₂) (p : mv_polynomial σ R) :
    coe_fn (eval₂_hom f 0) p = coe_fn f (coe_fn constant_coeff p) :=
  sorry

@[simp] theorem eval₂_hom_zero' {R : Type u} {S₂ : Type w} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₂] (f : R →+* S₂) (p : mv_polynomial σ R) :
    coe_fn (eval₂_hom f fun (_x : σ) => 0) p = coe_fn f (coe_fn constant_coeff p) :=
  eval₂_hom_zero f p

@[simp] theorem aeval_zero {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] [algebra R S₁] (p : mv_polynomial σ R) :
    coe_fn (aeval 0) p = coe_fn (algebra_map R S₁) (coe_fn constant_coeff p) :=
  eval₂_hom_zero (algebra_map R S₁) p

@[simp] theorem aeval_zero' {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] [algebra R S₁] (p : mv_polynomial σ R) :
    coe_fn (aeval fun (_x : σ) => 0) p = coe_fn (algebra_map R S₁) (coe_fn constant_coeff p) :=
  aeval_zero p

theorem aeval_monomial {R : Type u} {S₁ : Type v} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₁] [algebra R S₁] (g : σ → S₁) (d : σ →₀ ℕ) (r : R) :
    coe_fn (aeval g) (monomial d r) =
        coe_fn (algebra_map R S₁) r * finsupp.prod d fun (i : σ) (k : ℕ) => g i ^ k :=
  eval₂_hom_monomial (algebra_map R S₁) (fun (n : σ) => g n) d r

theorem eval₂_hom_eq_zero {R : Type u} {S₂ : Type w} {σ : Type u_1} [comm_semiring R]
    [comm_semiring S₂] (f : R →+* S₂) (g : σ → S₂) (φ : mv_polynomial σ R)
    (h : ∀ (d : σ →₀ ℕ), coeff d φ ≠ 0 → ∃ (i : σ), ∃ (H : i ∈ finsupp.support d), g i = 0) :
    coe_fn (eval₂_hom f g) φ = 0 :=
  sorry

theorem aeval_eq_zero {R : Type u} {S₂ : Type w} {σ : Type u_1} [comm_semiring R] [comm_semiring S₂]
    [algebra R S₂] (f : σ → S₂) (φ : mv_polynomial σ R)
    (h : ∀ (d : σ →₀ ℕ), coeff d φ ≠ 0 → ∃ (i : σ), ∃ (H : i ∈ finsupp.support d), f i = 0) :
    coe_fn (aeval f) φ = 0 :=
  eval₂_hom_eq_zero (algebra_map R S₂) (fun (n : σ) => f n) φ h

end Mathlib


/-
Copyright (c) 2020 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau
-/

import algebra.algebra.basic
import linear_algebra.prod

/-!
# Trivial Square-Zero Extension

Given a module `M` over a ring `R`, the trivial square-zero extension of `M` over `R` is defined
to be the `R`-algebra `R ⊕ M` with multiplication given by
`(r₁ + m₁) * (r₂ + m₂) = r₁ r₂ + r₁ m₂ + r₂ m₁`.

It is a square-zero extension because `M^2 = 0`.

## Main definitions

* `triv_sq_zero_ext.inl`, `triv_sq_zero_ext.inr`: the canonical inclusions into
  `triv_sq_zero_ext R M`.
* `triv_sq_zero_ext.fst`, `triv_sq_zero_ext.snd`: the canonical projections from
  `triv_sq_zero_ext R M`.
* `triv_sq_zero_ext.algebra`: the associated `R`-algebra structure.
* `triv_sq_zero_ext.lift`: the universal property of the trivial square-zero extension; algebra
  morphisms `triv_sq_zero_ext R M →ₐ[R] A` are uniquely defined by linear maps `M →ₗ[R] A` for
  which the product of any two elements in the range is zero.

-/

universes u v w

/--
"Trivial Square-Zero Extension".

Given a module `M` over a ring `R`, the trivial square-zero extension of `M` over `R` is defined
to be the `R`-algebra `R × M` with multiplication given by
`(r₁ + m₁) * (r₂ + m₂) = r₁ r₂ + r₁ m₂ + r₂ m₁`.

It is a square-zero extension because `M^2 = 0`.
-/
def triv_sq_zero_ext (R : Type u) (M : Type v) :=
R × M

local notation `tsze` := triv_sq_zero_ext

namespace triv_sq_zero_ext

section basic
variables {R : Type u} {M : Type v}

/-- The canonical inclusion `R → triv_sq_zero_ext R M`. -/
def inl [has_zero M] (r : R) : tsze R M :=
(r, 0)

/-- The canonical inclusion `M → triv_sq_zero_ext R M`. -/
def inr [has_zero R] (m : M) : tsze R M :=
(0, m)

/-- The canonical projection `triv_sq_zero_ext R M → R`. -/
def fst (x : tsze R M) : R :=
x.1

/-- The canonical projection `triv_sq_zero_ext R M → M`. -/
def snd (x : tsze R M) : M :=
x.2

@[ext] lemma ext {x y : tsze R M} (h1 : x.fst = y.fst) (h2 : x.snd = y.snd) : x = y :=
prod.ext h1 h2

section
variables (M)
@[simp] lemma fst_inl [has_zero M] (r : R) : (inl r : tsze R M).fst = r := rfl
@[simp] lemma snd_inl [has_zero M] (r : R) : (inl r : tsze R M).snd = 0 := rfl
end

section
variables (R)
@[simp] lemma fst_inr [has_zero R] (m : M) : (inr m : tsze R M).fst = 0 := rfl
@[simp]

lemma inl_injective [has_zero M] : function.injective (inl : R → tsze R M) :=
function.left_inverse.injective $ fst_inl _

lemma inr_injective [has_zero R] : function.injective (inr : M → tsze R M) :=
function.left_inverse.injective $ snd_inr _

end basic

/-! ### Structures inherited from `prod`

Additive operators and scalar multiplication operate elementwise. -/

section additive

variables {T : Type*} {S : Type*} {R : Type u} {M : Type v}

instance [inhabited R] [inhabited M] : inhabited (tsze R M) :=
prod.inhabited

instance [has_zero R] [has_zero M] : has_zero (tsze R M) :=
prod.has_zero

instance [has_add R] [has_add M] : has_add (tsze R M) :=
prod.has_add

instance [has_neg R] [has_neg M] : has_neg (tsze R M) :=
prod.has_neg

instance [add_semigroup R] [add_semigroup M] : add_semigroup (tsze R M) :=
prod.add_semigroup

instance [add_zero_class R] [add_zero_class M] : add_zero_class (tsze R M) :=
prod.add_zero_class

instance [add_monoid R] [add_monoid M] : add_monoid (tsze R M) :=
prod.add_monoid

instance [add_group R] [add_group M] : add_group (tsze R M) :=
prod.add_group

instance [add_comm_semigroup R] [add_comm_semigroup M] : add_comm_semigroup (tsze R M) :=
prod.add_comm_semigroup

instance [add_comm_monoid R] [add_comm_monoid M] : add_comm_monoid (tsze R M) :=
prod.add_comm_monoid

instance [add_comm_group R] [add_comm_group M] : add_comm_group (tsze R M) :=
prod.add_comm_group

instance [has_scalar S R] [has_scalar S M] : has_scalar S (tsze R M) :=
prod.has_scalar

instance [has_scalar T R] [has_scalar T M] [has_scalar S R] [has_scalar S M] [has_scalar T S]
  [is_scalar_tower T S R] [is_scalar_tower T S M] : is_scalar_tower T S (tsze R M) :=
prod.is_scalar_tower

instance [has_scalar T R] [has_scalar T M] [has_scalar S R] [has_scalar S M]
  [smul_comm_class T S R] [smul_comm_class T S M] : smul_comm_class T S (tsze R M) :=
prod.smul_comm_class

instance [has_scalar S R] [has_scalar S M] [has_scalar Sᵐᵒᵖ R] [has_scalar Sᵐᵒᵖ M]
  [is_central_scalar S R] [is_central_scalar S M] : is_central_scalar S (tsze R M) :=
prod.is_central_scalar

instance [monoid S] [mul_action S R] [mul_action S M] : mul_action S (tsze R M) :=
prod.mul_action

instance [monoid S] [add_monoid R] [add_monoid M]
  [distrib_mul_action S R] [distrib_mul_action S M] : distrib_mul_action S (tsze R M) :=
prod.distrib_mul_action

instance [semiring S] [add_comm_monoid R] [add_comm_monoid M]
  [module S R] [module S M] : module S (tsze R M) :=
prod.module

@[simp] lemma fst_zero [has_zero R] [has_zero M] : (0 : tsze R M).fst = 0 := rfl
@[simp] lemma snd_zero [has_zero R] [has_zero M] : (0 : tsze R M).snd = 0 := rfl

@[simp] lemma fst_add [has_add R] [has_add M] (x₁ x₂ : tsze R M) :
  (x₁ + x₂).fst = x₁.fst + x₂.fst := rfl
@[simp] lemma snd_add [has_add R] [has_add M] (x₁ x₂ : tsze R M) :
  (x₁ + x₂).snd = x₁.snd + x₂.snd := rfl

@[simp] lemma fst_neg [has_neg R] [has_neg M] (x : tsze R M) : (-x).fst = -x.fst := rfl
@[simp] lemma snd_neg [has_neg R] [has_neg M] (x : tsze R M) : (-x).snd = -x.snd := rfl

@[simp] lemma fst_smul [has_scalar S R] [has_scalar S M] (s : S) (x : tsze R M) :
  (s • x).fst = s • x.fst := rfl
@[simp] lemma snd_smul [has_scalar S R] [has_scalar S M] (s : S) (x : tsze R M) :
  (s • x).snd = s • x.snd := rfl

section
variables (M)

@[simp] lemma inl_zero [has_zero R] [has_zero M] : (inl 0 : tsze R M) = 0 := rfl

@[simp] lemma inl_add [has_add R] [add_zero_class M] (r₁ r₂ : R) :
  (inl (r₁ + r₂) : tsze R M) = inl r₁ + inl r₂ :=
ext rfl (add_zero 0).symm

@[simp] lemma inl_neg [has_neg R] [add_group M] (r : R) :
  (inl (-r) : tsze R M) = -inl r :=
ext rfl neg_zero.symm

@[simp] lemma inl_smul [monoid S] [add_monoid M] [has_scalar S R] [distrib_mul_action S M]
  (s : S) (r : R) : (inl (s • r) : tsze R M) = s • inl r :=
ext rfl (smul_zero s).symm

end

section
variables (R)

@[simp] lemma inr_zero [has_zero R] [has_zero M] : (inr 0 : tsze R M) = 0 := rfl

@[simp] lemma inr_add [add_zero_class R] [add_zero_class M] (m₁ m₂ : M) :
  (inr (m₁ + m₂) : tsze R M) = inr m₁ + inr m₂ :=
ext (add_zero 0).symm rfl

@[simp] lemma inr_neg [add_group R] [has_neg M] (m : M) :
  (inr (-m) : tsze R M) = -inr m :=
ext neg_zero.symm rfl

@[simp] lemma inr_smul [has_zero R] [has_zero S] [smul_with_zero S R] [has_scalar S M]
  (r : S) (m : M) : (inr (r • m) : tsze R M) = r • inr m :=
ext (smul_zero' _ _).symm rfl

end

lemma inl_fst_add_inr_snd_eq [add_zero_class R] [add_zero_class M] (x : tsze R M) :
  inl x.fst + inr x.snd = x :=
ext (add_zero x.1) (zero_add x.2)

/-- To show a property hold on all `triv_sq_zero_ext R M` it suffices to show it holds
on terms of the form `inl r + inr m`.

This can be used as `induction x using triv_sq_zero_ext.ind`. -/
lemma ind {R M} [add_zero_class R] [add_zero_class M] {P : triv_sq_zero_ext R M → Prop}
  (h : ∀ r m, P (inl r + inr m)) (x) : P x :=
inl_fst_add_inr_snd_eq x ▸ h x.1 x.2

/-- This cannot be marked `@[ext]` as it ends up being used instead of `linear_map.prod_ext` when
working with `R × M`. -/
lemma linear_map_ext {N} [semiring S] [add_comm_monoid R] [add_comm_monoid M] [add_comm_monoid N]
  [module S R] [module S M] [module S N] ⦃f g : tsze R M →ₗ[S] N⦄
  (hl : ∀ r, f (inl r) = g (inl r)) (hr : ∀ m, f (inr m) = g (inr m)) :
  f = g :=
linear_map.prod_ext (linear_map.ext hl) (linear_map.ext hr)

variables (R M)

/-- The canonical `R`-linear inclusion `M → triv_sq_zero_ext R M`. -/
@[simps apply]
def inr_hom [semiring R] [add_comm_monoid M] [module R M] : M →ₗ[R] tsze R M :=
{ to_fun := inr, ..linear_map.inr _ _ _ }

/-- The canonical `R`-linear projection `triv_sq_zero_ext R M → M`. -/
@[simps apply]
def snd_hom [semiring R] [add_comm_monoid M] [module R M] : tsze R M →ₗ[R] M :=
{ to_fun := snd, ..linear_map.snd _ _ _ }

end additive

/-! ### Multiplicative structure -/

section mul
variables {R : Type u} {M : Type v}

instance [has_one R] [has_zero M] : has_one (tsze R M) :=
⟨(1, 0)⟩

instance [has_mul R] [has_add M] [has_scalar R M] : has_mul (tsze R M) :=
⟨λ x y, (x.1 * y.1, x.1 • y.2 + y.1 • x.2)⟩

@[simp] lemma fst_one [has_one R] [has_zero M] : (1 : tsze R M).fst = 1 := rfl
@[simp] lemma snd_one [has_one R] [has_zero M] : (1 : tsze R M).snd = 0 := rfl

@[simp] lemma fst_mul [has_mul R] [has_add M] [has_scalar R M] (x₁ x₂ : tsze R M) :
  (x₁ * x₂).fst = x₁.fst * x₂.fst := rfl
@[simp] lemma snd_mul [has_mul R] [has_add M] [has_scalar R M] (x₁ x₂ : tsze R M) :
  (x₁ * x₂).snd = x₁.fst • x₂.snd + x₂.fst • x₁.snd := rfl

section
variables (M)

@[simp] lemma inl_one [has_one R] [has_zero M] : (inl 1 : tsze R M) = 1 := rfl

@[simp] lemma inl_mul [monoid R] [add_monoid M] [distrib_mul_action R M] (r₁ r₂ : R) :
  (inl (r₁ * r₂) : tsze R M) = inl r₁ * inl r₂ :=
ext rfl $ show (0 : M) = r₁ • 0 + r₂ • 0, by rw [smul_zero, zero_add, smul_zero]

lemma inl_mul_inl [monoid R] [add_monoid M] [distrib_mul_action R M] (r₁ r₂ : R) :
  (inl r₁ * inl r₂ : tsze R M) = inl (r₁ * r₂) :=
(inl_mul M r₁ r₂).symm

end

section
variables (R)

@[simp] lemma inr_mul_inr [semiring R] [add_comm_monoid M] [module R M] (m₁ m₂ : M) :
  (inr m₁ * inr m₂ : tsze R M) = 0 :=
ext (mul_zero _) $ show (0 : R) • m₂ + (0 : R) • m₁ = 0, by rw [zero_smul, zero_add, zero_smul]

end

lemma inl_mul_inr [semiring R] [add_comm_monoid M] [module R M] (r : R) (m : M) :
  (inl r * inr m : tsze R M) = inr (r • m) :=
ext (mul_zero r) $ show r • m + (0 : R) • 0 = r • m, by rw [smul_zero, add_zero]

lemma inr_mul_inl [semiring R] [add_comm_monoid M] [module R M] (r : R) (m : M) :
  (inr m * inl r : tsze R M) = inr (r • m) :=
ext (zero_mul r) $ show (0 : R) • 0 + r • m = r • m, by rw [smul_zero, zero_add]

instance [monoid R] [add_monoid M] [distrib_mul_action R M] : mul_one_class (tsze R M) :=
{ one_mul := λ x, ext (one_mul x.1) $ show (1 : R) • x.2 + x.1 • 0 = x.2,
    by rw [one_smul, smul_zero, add_zero],
  mul_one := λ x, ext (mul_one x.1) $ show (x.1 • 0 : M) + (1 : R) • x.2 = x.2,
    by rw [smul_zero, zero_add, one_smul],
  .. triv_sq_zero_ext.has_one,
  .. triv_sq_zero_ext.has_mul }

instance [semiring R] [add_comm_monoid M] [module R M] : non_assoc_semiring (tsze R M) :=
{ zero_mul := λ x, ext (zero_mul x.1) $ show (0 : R) • x.2 + x.1 • 0 = 0,
    by rw [zero_smul, zero_add, smul_zero],
  mul_zero := λ x, ext (mul_zero x.1) $ show (x.1 • 0 : M) + (0 : R) • x.2 = 0,
    by rw [smul_zero, zero_add, zero_smul],
  left_distrib := λ x₁ x₂ x₃, ext (mul_add x₁.1 x₂.1 x₃.1) $
    show x₁.1 • (x₂.2 + x₃.2) + (x₂.1 + x₃.1) • x₁.2 =
      x₁.1 • x₂.2 + x₂.1 • x₁.2 + (x₁.1 • x₃.2 + x₃.1 • x₁.2),
    by simp_rw [smul_add, add_smul, add_add_add_comm],
  right_distrib := λ x₁ x₂ x₃, ext (add_mul x₁.1 x₂.1 x₃.1) $
    show (x₁.1 + x₂.1) • x₃.2 + x₃.1 • (x₁.2 + x₂.2) =
      x₁.1 • x₃.2 + x₃.1 • x₁.2 + (x₂.1 • x₃.2 + x₃.1 • x₂.2),
    by simp_rw [add_smul, smul_add, add_add_add_comm],
  .. triv_sq_zero_ext.mul_one_class,
  .. triv_sq_zero_ext.add_comm_monoid }

instance [comm_monoid R] [add_monoid M] [distrib_mul_action R M] : monoid (tsze R M) :=
{ mul_assoc := λ x y z, ext (mul_assoc x.1 y.1 z.1) $
    show (x.1 * y.1) • z.2 + z.1 • (x.1 • y.2 + y.1 • x.2) =
      x.1 • (y.1 • z.2 + z.1 • y.2) + (y.1 * z.1) • x.2,
    by simp_rw [smul_add, ← mul_smul, add_assoc, mul_comm],
  .. triv_sq_zero_ext.mul_one_class }

instance [comm_monoid R] [add_comm_monoid M] [distrib_mul_action R M] : comm_monoid (tsze R M) :=
{ mul_comm := λ x₁ x₂, ext (mul_comm x₁.1 x₂.1) $
    show x₁.1 • x₂.2 + x₂.1 • x₁.2 = x₂.1 • x₁.2 + x₁.1 • x₂.2, from add_comm _ _,
  .. triv_sq_zero_ext.monoid }

instance [comm_semiring R] [add_comm_monoid M] [module R M] : comm_semiring (tsze R M) :=
{ .. triv_sq_zero_ext.comm_monoid,
  .. triv_sq_zero_ext.non_assoc_semiring }

variables (R M)

/-- The canonical inclusion of rings `R → triv_sq_zero_ext R M`. -/
@[simps apply]
def inl_hom [semiring R] [add_comm_monoid M] [module R M] : R →+* tsze R M :=
{ to_fun := inl,
  map_one' := inl_one M,
  map_mul' := inl_mul M,
  map_zero' := inl_zero M,
  map_add' := inl_add M }

end mul

section algebra
variables (S : Type*) (R : Type u) (M : Type v)
variables [comm_semiring S] [comm_semiring R] [add_comm_monoid M]
variables [algebra S R] [module S M] [module R M] [is_scalar_tower S R M]

instance algebra'  : algebra S (tsze R M) :=
{ commutes' := λ r x, mul_comm _ _,
  smul_def' := λ r x, ext (algebra.smul_def _ _) $
    show r • x.2 = algebra_map S R r • x.2 + x.1 • 0, by rw [smul_zero, add_zero, algebra_map_smul],
  .. (triv_sq_zero_ext.inl_hom R M).comp (algebra_map S R) }

-- shortcut instance for the common case
instance : algebra R (tsze R M) := triv_sq_zero_ext.algebra' _ _ _

lemma algebra_map_eq_inl : ⇑(algebra_map R (tsze R M)) = inl := rfl
lemma algebra_map_eq_inl_hom : algebra_map R (tsze R M) = inl_hom R M := rfl
lemma algebra_map_eq_inl' (s : S) : algebra_map S (tsze R M) s = inl (algebra_map S R s) := rfl

/-- The canonical `R`-algebra projection `triv_sq_zero_ext R M → R`. -/
@[simps]
def fst_hom : tsze R M →ₐ[R] R :=
{ to_fun := fst,
  map_one' := fst_one,
  map_mul' := fst_mul,
  map_zero' := fst_zero,
  map_add' := fst_add,
  commutes' := fst_inl M }

variables {R S M}

lemma alg_hom_ext {A} [semiring A] [algebra R A] ⦃f g : tsze R M →ₐ[R] A⦄
  (h : ∀ m, f (inr m) = g (inr m)) :
  f = g :=
alg_hom.to_linear_map_injective $ linear_map_ext (λ r, (f.commutes _).trans (g.commutes _).symm) h

@[ext]
lemma alg_hom_ext' {A} [semiring A] [algebra R A] ⦃f g : tsze R M →ₐ[R] A⦄
  (h : f.to_linear_map.comp (inr_hom R M) = g.to_linear_map.comp (inr_hom R M)) :
  f = g :=
alg_hom_ext $ linear_map.congr_fun h

variables {A : Type*} [semiring A] [algebra R A]

/-- There is an alg_hom from the trivial square zero extension to any `R`-algebra with a submodule
whose products are all zero.

See `triv_sq_zero_ext.lift` for this as an equiv. -/
def lift_aux (f : M →ₗ[R] A) (hf : ∀ x y, f x * f y = 0) : tsze R M →ₐ[R] A :=
alg_hom.of_linear_map
  ((algebra.linear_map _ _).comp (fst_hom R M).to_linear_map + f.comp (snd_hom R M))
  (show algebra_map R _ 1 + f (0 : M) = 1, by rw [map_zero, map_one, add_zero])
  (triv_sq_zero_ext.ind $ λ r₁ m₁, triv_sq_zero_ext.ind $ λ r₂ m₂, begin
    dsimp,
    simp only [add_zero, zero_add, add_mul, mul_add, smul_mul_smul, hf, smul_zero],
    rw [←ring_hom.map_mul, linear_map.map_add, ←algebra.commutes _ (f _), ←algebra.smul_def,
        ←algebra.smul_def, add_right_comm, add_assoc, linear_map.map_smul, linear_map.map_smul],
  end)

@[simp] lemma lift_aux_apply_inr (f : M →ₗ[R] A) (hf : ∀ x y, f x * f y = 0) (m : M) :
  lift_aux f hf (inr m) = f m :=
show algebra_map R A 0 + f m = f m, by rw [ring_hom.map_zero, zero_add]

@[simp] lemma lift_aux_comp_inr_hom (f : M →ₗ[R] A) (hf : ∀ x y, f x * f y = 0) :
  (lift_aux f hf).to_linear_map.comp (inr_hom R M) = f :=
linear_map.ext $ lift_aux_apply_inr f hf

/- When applied to `inr` itself, `lift_aux` is the identity. -/
@[simp]
lemma lift_aux_inr_hom : lift_aux (inr_hom R M) (inr_mul_inr R) = alg_hom.id R (tsze R M) :=
alg_hom_ext' $ lift_aux_comp_inr_hom _ _

/-- A universal property of the trivial square-zero extension, providing a unique
`triv_sq_zero_ext R M →ₐ[R] A` for every linear map `M →ₗ[R] A` whose range has no non-zero
products.

This isomorphism is named to match the very similar `complex.lift`. -/
@[simps]
def lift : {f : M →ₗ[R] A // ∀ x y, f x * f y = 0} ≃ (tsze R M →ₐ[R] A) :=
{ to_fun := λ f, lift_aux f f.prop,
  inv_fun := λ F, ⟨F.to_linear_map.comp (inr_hom R M), λ x y,
    (F.map_mul _ _).symm.trans $ (F.congr_arg $ inr_mul_inr _ _ _).trans F.map_zero⟩,
  left_inv := λ f, subtype.ext $ lift_aux_comp_inr_hom _ _,
  right_inv := λ F, alg_hom_ext' $ lift_aux_comp_inr_hom _ _, }

end algebra

end triv_sq_zero_ext


/-
Copyright (c) 2019 Seul Baek. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Seul Baek
-/
import data.nat.order.basic

/-!
# Lists as Functions

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

Definitions for using lists as finite representations of finitely-supported functions with domain
ℕ.

These include pointwise operations on lists, as well as get and set operations.

## Notations

An index notation is introduced in this file for setting a particular element of a list. With `as`
as a list `m` as an index, and `a` as a new element, the notation is `as {m ↦ a}`.

So, for example
`[1, 3, 5] {1 ↦ 9}` would result in `[1, 9, 5]`

This notation is in the locale `list.func`.
-/

open list

universes u v w
variables {α : Type u} {β : Type v} {γ : Type w}

namespace list
namespace func

variables {a : α}
variables {as as1 as2 as3 : list α}

/-- Elementwise negation of a list -/
def neg [has_neg α] (as : list α) := as.map (λ a, -a)

variables [inhabited α] [inhabited β]

/--
Update element of a list by index. If the index is out of range, extend the list with default
elements
-/
@[simp] def set (a : α) : list α → ℕ → list α
| (_::as) 0     := a::as
| []      0     := [a]
| (h::as) (k+1) := h::(set as k)
| []      (k+1) := default::(set ([] : list α) k)

localized "notation (name := list.func.set)
  as ` {` m ` ↦ ` a `}` := list.func.set a as m" in list.func

/-- Get element of a list by index. If the index is out of range, return the default element -/
@[simp] def get : ℕ → list α → α
| _ []          := default
| 0 (a::as)     := a
| (n+1) (a::as) := get n as

/--
Pointwise equality of lists. If lists are different lengths, compare with the default
element.
-/
def equiv (as1 as2 : list α) : Prop :=
∀ (m : nat), get m as1 = get m as2

/-- Pointwise operations on lists. If lists are different lengths, use the default element. -/
@[simp] def pointwise (f : α → β → γ) : list α → list β → list γ
| []      []      := []
| []      (b::bs) := map (f default) (b::bs)
| (a::as) []      := map (λ x, f x default) (a::as)
| (a::as) (b::bs) := (f a b)::(pointwise as bs)

/-- Pointwise addition on lists. If lists are different lengths, use zero. -/
def add {α : Type u} [has_zero α] [has_add α] : list α → list α → list α :=
@pointwise α α α ⟨0⟩ ⟨0⟩ (+)

/-- Pointwise subtraction on lists. If lists are different lengths, use zero. -/
def sub {α : Type u} [has_zero α] [has_sub α] : list α → list α → list α :=
@pointwise α α α ⟨0⟩ ⟨0⟩ (@has_sub.sub α _)

/- set -/

lemma length_set : ∀ {m : ℕ} {as : list α},
  (as {m ↦ a}).length = max as.length (m+1)
| 0 []          := rfl
| 0 (a::as)     := by {rw max_eq_left, refl, simp [nat.le_add_right]}
| (m+1) []      := by simp only [set, nat.zero_max, length, @length_set m]
| (m+1) (a::as) := by simp only [set, nat.max_succ_succ, length, @length_set m]

@[simp] lemma get_nil {k : ℕ} : (get k [] : α) = default :=
by {cases k; refl}

lemma get_eq_default_of_le :
  ∀ (k : ℕ) {as : list α}, as.length ≤ k → get k as = default
| 0     []      h1 := rfl
| 0     (a::as) h1 := by cases h1
| (k+1) []      h1 := rfl
| (k+1) (a::as) h1 :=
  begin
    apply get_eq_default_of_le k,
    rw ← nat.succ_le_succ_iff, apply h1,
  end

@[simp] lemma get_set {a : α} :
  ∀ {k : ℕ} {as : list α}, get k (as {k ↦ a}) = a
| 0 as     := by {cases as; refl, }
| (k+1) as := by {cases as; simp [get_set]}

lemma eq_get_of_mem {a : α} :
  ∀ {as : list α}, a ∈ as → ∃ n : nat, ∀ d : α, a = (get n as)
| [] h := by cases h
| (b::as) h :=
  begin
    rw mem_cons_iff at h, cases h,
    { existsi 0, intro d, apply h },
    { cases eq_get_of_mem h with n h2,
      existsi (n+1), apply h2 }
  end

lemma mem_get_of_le :
  ∀ {n : ℕ} {as : list α}, n < as.length → get n as ∈ as
| _ [] h1 := by cases h1
| 0 (a::as) _ := or.inl rfl
| (n+1) (a::as) h1 :=
  begin
    apply or.inr, unfold get,
    apply mem_get_of_le,
    apply nat.lt_of_succ_lt_succ h1,
  end

lemma mem_get_of_ne_zero :
  ∀ {n : ℕ} {as : list α},
  get n as ≠ default → get n as ∈ as
| _ [] h1 := begin exfalso, apply h1, rw get_nil end
| 0 (a::as) h1 := or.inl rfl
| (n+1) (a::as) h1 :=
  begin
    unfold get,
    apply (or.inr (mem_get_of_ne_zero _)),
    apply h1
  end

lemma get_set_eq_of_ne {a : α} :
  ∀ {as : list α} (k : ℕ) (m : ℕ),
  m ≠ k → get m (as {k ↦ a}) = get m as
| as 0 m h1 :=
  by { cases m, contradiction, cases as;
       simp only [set, get, get_nil] }
| as (k+1) m h1 :=
  begin
    cases as; cases m,
    simp only [set, get],
    { have h3 : get m (nil {k ↦ a}) = default,
      { rw [get_set_eq_of_ne k m, get_nil],
        intro hc, apply h1, simp [hc] },
      apply h3 },
    simp only [set, get],
    { apply get_set_eq_of_ne k m,
      intro hc, apply h1, simp [hc], }
  end

lemma get_map {f : α → β} : ∀ {n : ℕ} {as : list α}, n < as.length →
  get n (as.map f) = f (get n as)
| _ [] h := by cases h
| 0 (a::as) h := rfl
| (n+1) (a::as) h1 :=
  begin
    have h2 : n < length as,
    { rw [← nat.succ_le_iff, ← nat.lt_succ_iff],
      apply h1 },
    apply get_map h2,
  end

lemma get_map' {f : α → β} {n : ℕ} {as : list α} :
  f default = default →
  get n (as.map f) = f (get n as) :=
begin
  intro h1, by_cases h2 : n < as.length,
  { apply get_map h2, },
  { rw not_lt at h2,
    rw [get_eq_default_of_le _ h2, get_eq_default_of_le, h1],
    rw [length_map], apply h2 }
end

lemma forall_val_of_forall_mem {as : list α} {p : α → Prop} :
  p default → (∀ x ∈ as, p x) → (∀ n, p (get n as)) :=
begin
  intros h1 h2 n,
  by_cases h3 : n < as.length,
  { apply h2 _ (mem_get_of_le h3) },
  { rw not_lt at h3,
    rw get_eq_default_of_le _ h3, apply h1 }
end

/- equiv -/

lemma equiv_refl : equiv as as := λ k, rfl

lemma equiv_symm : equiv as1 as2 → equiv as2 as1 :=
λ h1 k, (h1 k).symm

lemma equiv_trans :
  equiv as1 as2 → equiv as2 as3 → equiv as1 as3 :=
λ h1 h2 k, eq.trans (h1 k) (h2 k)

lemma equiv_of_eq : as1 = as2 → equiv as1 as2 :=
begin intro h1, rw h1, apply equiv_refl end

lemma eq_of_equiv : ∀ {as1 as2 : list α}, as1.length = as2.length →
  equiv as1 as2 → as1 = as2
| []     [] h1 h2 := rfl
| (_::_) [] h1 h2 := by cases h1
| [] (_::_) h1 h2 := by cases h1
| (a1::as1) (a2::as2) h1 h2 :=
  begin
    congr,
    { apply h2 0 },
    have h3 : as1.length = as2.length,
    { simpa [add_left_inj, add_comm, length] using h1 },
    apply eq_of_equiv h3,
    intro m, apply h2 (m+1)
  end

end func
-- We want to drop the `inhabited` instances for a moment,
-- so we close and open the namespace

namespace func

/- neg -/

@[simp] lemma get_neg [add_group α] {k : ℕ} {as : list α} :
  @get α ⟨0⟩ k (neg as) = -(@get α ⟨0⟩ k as) :=
by {unfold neg, rw (@get_map' α α ⟨0⟩), apply neg_zero}

@[simp] lemma length_neg [has_neg α] (as : list α) : (neg as).length = as.length :=
by simp only [neg, length_map]

variables [inhabited α] [inhabited β]

/- pointwise -/

lemma nil_pointwise {f : α → β → γ} : ∀ bs : list β, pointwise f [] bs = bs.map (f default)
| []      := rfl
| (b::bs) :=
  by simp only [nil_pointwise bs, pointwise,
     eq_self_iff_true, and_self, map]

lemma pointwise_nil {f : α → β → γ} :
  ∀ as : list α, pointwise f as [] = as.map (λ a, f a default)
| []      := rfl
| (a::as) :=
  by simp only [pointwise_nil as, pointwise,
     eq_self_iff_true, and_self, list.map]

lemma get_pointwise [inhabited γ] {f : α → β → γ} (h1 : f default default = default) :
  ∀ (k : nat) (as : list α) (bs : list β),
  get k (pointwise f as bs) = f (get k as) (get k bs)
| k [] [] := by simp only [h1, get_nil, pointwise, get]
| 0 [] (b::bs) :=
  by simp only [get_pointwise, get_nil,
      pointwise, get, nat.nat_zero_eq_zero, map]
| (k+1) [] (b::bs) :=
  by { have : get k (map (f default) bs) = f default (get k bs),
       { simpa [nil_pointwise, get_nil] using (get_pointwise k [] bs) },
       simpa [get, get_nil, pointwise, map] }
| 0 (a::as) [] :=
  by simp only [get_pointwise, get_nil,
     pointwise, get, nat.nat_zero_eq_zero, map]
| (k+1) (a::as) [] :=
  by simpa [get, get_nil, pointwise, map, pointwise_nil, get_nil]
     using get_pointwise k as []
| 0 (a::as) (b::bs) := by simp only [pointwise, get]
| (k+1) (a::as) (b::bs) :=
  by simp only [pointwise, get, get_pointwise k]

lemma length_pointwise {f : α → β → γ} :
  ∀ {as : list α} {bs : list β},
  (pointwise f as bs).length = max as.length bs.length
| []      []      := rfl
| []      (b::bs) :=
  by simp only [pointwise, length, length_map,
     max_eq_right (nat.zero_le (length bs + 1))]
| (a::as) []      :=
  by simp only [pointwise, length, length_map,
     max_eq_left (nat.zero_le (length as + 1))]
| (a::as) (b::bs) :=
  by simp only [pointwise, length,
     nat.max_succ_succ, @length_pointwise as bs]

end func

namespace func
/- add -/

@[simp] lemma get_add {α : Type u} [add_monoid α] {k : ℕ} {xs ys : list α} :
  @get α ⟨0⟩ k (add xs ys) = ( @get α ⟨0⟩ k xs + @get α ⟨0⟩ k ys) :=
by {apply get_pointwise, apply zero_add}

@[simp] lemma length_add {α : Type u}
  [has_zero α] [has_add α] {xs ys : list α} :
  (add xs ys).length = max xs.length ys.length :=
@length_pointwise α α α ⟨0⟩ ⟨0⟩ _ _ _

@[simp]

@[simp] lemma add_nil {α : Type u} [add_monoid α]
  (as : list α) : add as [] = as :=
begin
  rw [add, @pointwise_nil α α α ⟨0⟩ ⟨0⟩],
  apply eq.trans _ (map_id as),
  congr' with x,
  rw [add_zero, id]
end

lemma map_add_map {α : Type u} [add_monoid α] (f g : α → α) {as : list α} :
  add (as.map f) (as.map g) = as.map (λ x, f x + g x) :=
begin
  apply @eq_of_equiv _ (⟨0⟩ : inhabited α),
  { rw [length_map, length_add, max_eq_left, length_map],
    apply le_of_eq,
    rw [length_map, length_map] },
  intros m,
  rw [get_add],
  by_cases h : m < length as,
  { repeat {rw [@get_map α α ⟨0⟩ ⟨0⟩ _ _ _ h]} },
  rw not_lt at h,
  repeat {rw [get_eq_default_of_le m]};
  try {rw length_map, apply h},
  apply zero_add
end

/- sub -/

@[simp] lemma get_sub {α : Type u}
  [add_group α] {k : ℕ} {xs ys : list α} :
  @get α ⟨0⟩ k (sub xs ys) = (@get α ⟨0⟩ k xs - @get α ⟨0⟩ k ys) :=
by {apply get_pointwise, apply sub_zero}

@[simp] lemma length_sub [has_zero α] [has_sub α] {xs ys : list α} :
  (sub xs ys).length = max xs.length ys.length :=
@length_pointwise α α α ⟨0⟩ ⟨0⟩ _ _ _

@[simp] lemma nil_sub {α : Type*} [add_group α]
  (as : list α) : sub [] as = neg as :=
begin
  rw [sub, nil_pointwise],
  congr' with x,
  rw [zero_sub]
end

@[simp] lemma sub_nil {α : Type*} [add_group α]
  (as : list α) : sub as [] = as :=
begin
  rw [sub, pointwise_nil],
  apply eq.trans _ (map_id as),
  congr' with x,
  rw [sub_zero, id]
end

end func
end list