/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Johannes Hölzl, Scott Morrison, Jens Wagemaker

! This file was ported from Lean 3 source module data.polynomial.algebra_map
! leanprover-community/mathlib commit e064a7bf82ad94c3c17b5128bbd860d1ec34874e
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Algebra.Pi
import Mathlib.RingTheory.Adjoin.Basic
import Mathlib.Data.Polynomial.Eval

/-!
# Theory of univariate polynomials

We show that `A[X]` is an R-algebra when `A` is an R-algebra.
We promote `eval₂` to an algebra hom in `aeval`.
-/

noncomputable section

open Finset

open BigOperators Polynomial

namespace Polynomial

universe u v w z

variable {R : Type u} {S : Type v} {T : Type w} {A : Type z} {A' B' : Type _} {a b : R} {n : ℕ}

variable [CommSemiring A'] [Semiring B']

section CommSemiring

variable [CommSemiring R] {p q r : R[X]}

variable [Semiring A] [Algebra R A]

/-- Note that this instance also provides `Algebra R R[X]`. -/
instance algebraOfAlgebra : Algebra R A[X]
    where
  smul_def' r p :=
    toFinsupp_injective <|
      by
      dsimp only [RingHom.toFun_eq_coe, RingHom.comp_apply]
      rw [toFinsupp_smul, toFinsupp_mul, toFinsupp_C]
      exact Algebra.smul_def' _ _
  commutes' r p :=
    toFinsupp_injective <|
      by
      dsimp only [RingHom.toFun_eq_coe, RingHom.comp_apply]
      simp_rw [toFinsupp_mul, toFinsupp_C]
      convert Algebra.commutes' r p.toFinsupp
  toRingHom := C.comp (algebraMap R A)
#align polynomial.algebra_of_algebra Polynomial.algebraOfAlgebra

theorem algebraMap_apply (r : R) : algebraMap R A[X] r = C (algebraMap R A r) :=
  rfl
#align polynomial.algebra_map_apply Polynomial.algebraMap_apply

@[simp]
theorem toFinsupp_algebraMap (r : R) : (algebraMap R A[X] r).toFinsupp = algebraMap R _ r :=
  show toFinsupp (C (algebraMap _ _ r)) = _
    by
    rw [toFinsupp_C]
    rfl
#align polynomial.to_finsupp_algebra_map Polynomial.toFinsupp_algebraMap

theorem ofFinsupp_algebraMap (r : R) : (⟨algebraMap R _ r⟩ : A[X]) = algebraMap R A[X] r :=
  toFinsupp_injective (toFinsupp_algebraMap _).symm
#align polynomial.of_finsupp_algebra_map Polynomial.ofFinsupp_algebraMap

/-- When we have `[CommSemiring R]`, the function `C` is the same as `algebraMap R R[X]`.

(But note that `C` is defined when `R` is not necessarily commutative, in which case
`algebraMap` is not available.)
-/
theorem C_eq_algebraMap (r : R) : C r = algebraMap R R[X] r :=
  rfl
set_option linter.uppercaseLean3 false in
#align polynomial.C_eq_algebra_map Polynomial.C_eq_algebraMap

-- porting note: removed `variable` because of redunant binder update annotation

/-- Extensionality lemma for algebra maps out of `A'[X]` over a smaller base ring than `A'`
-/
@[ext 1100]
theorem algHom_ext' [Algebra R A'] [Algebra R B'] {f g : A'[X] →ₐ[R] B'}
    (h₁ : f.comp (IsScalarTower.toAlgHom R A' A'[X]) = g.comp (IsScalarTower.toAlgHom R A' A'[X]))
    (h₂ : f X = g X) : f = g :=
  AlgHom.coe_ringHom_injective (Polynomial.ringHom_ext' (congr_arg AlgHom.toRingHom h₁) h₂)
#align polynomial.alg_hom_ext' Polynomial.algHom_ext'

variable (R)

/-- Algebra isomorphism between `R[X]` and `AddMonoidAlgebra R ℕ`. This is just an
implementation detail, but it can be useful to transfer results from `Finsupp` to polynomials. -/
@[simps!]
def toFinsuppIsoAlg : R[X] ≃ₐ[R] AddMonoidAlgebra R ℕ :=
  { toFinsuppIso R with
    commutes' := fun r => by
      dsimp
      exact toFinsupp_algebraMap _ }
#align polynomial.to_finsupp_iso_alg Polynomial.toFinsuppIsoAlg

variable {R}

instance subalgebraNontrivial [Nontrivial A] : Nontrivial (Subalgebra R A[X]) :=
  ⟨⟨⊥, ⊤, by
      rw [Ne.def, SetLike.ext_iff, not_forall]
      refine' ⟨X, _⟩
      simp only [Algebra.mem_bot, not_exists, Set.mem_range, iff_true_iff, Algebra.mem_top,
        algebraMap_apply, not_forall]
      intro x
      rw [ext_iff, not_forall]
      refine' ⟨1, _⟩
      simp [coeff_C]⟩⟩

@[simp]
theorem algHom_eval₂_algebraMap {R A B : Type _} [CommSemiring R] [Semiring A] [Semiring B]
    [Algebra R A] [Algebra R B] (p : R[X]) (f : A →ₐ[R] B) (a : A) :
    f (eval₂ (algebraMap R A) a p) = eval₂ (algebraMap R B) (f a) p := by
  simp only [eval₂_eq_sum, sum_def]
  simp only [f.map_sum, f.map_mul, f.map_pow, eq_intCast, map_intCast, AlgHom.commutes]
#align polynomial.alg_hom_eval₂_algebra_map Polynomial.algHom_eval₂_algebraMap

@[simp]
theorem eval₂_algebraMap_X {R A : Type _} [CommSemiring R] [Semiring A] [Algebra R A] (p : R[X])
    (f : R[X] →ₐ[R] A) : eval₂ (algebraMap R A) (f X) p = f p := by
  conv_rhs => rw [← Polynomial.sum_C_mul_X_pow_eq p]
  simp only [eval₂_eq_sum, sum_def]
  simp only [f.map_sum, f.map_mul, f.map_pow, eq_intCast, map_intCast]
  simp [Polynomial.C_eq_algebraMap]
set_option linter.uppercaseLean3 false in
#align polynomial.eval₂_algebra_map_X Polynomial.eval₂_algebraMap_X

-- these used to be about `algebraMap ℤ R`, but now the simp-normal form is `Int.castRingHom R`.
@[simp]
theorem ringHom_eval₂_cast_int_ringHom {R S : Type _} [Ring R] [Ring S] (p : ℤ[X]) (f : R →+* S)
    (r : R) : f (eval₂ (Int.castRingHom R) r p) = eval₂ (Int.castRingHom S) (f r) p :=
  algHom_eval₂_algebraMap p f.toIntAlgHom r
#align polynomial.ring_hom_eval₂_cast_int_ring_hom Polynomial.ringHom_eval₂_cast_int_ringHom

@[simp]
theorem eval₂_int_castRingHom_X {R : Type _} [Ring R] (p : ℤ[X]) (f : ℤ[X] →+* R) :
    eval₂ (Int.castRingHom R) (f X) p = f p :=
  eval₂_algebraMap_X p f.toIntAlgHom
set_option linter.uppercaseLean3 false in
#align polynomial.eval₂_int_cast_ring_hom_X Polynomial.eval₂_int_castRingHom_X

end CommSemiring

section aeval

variable [CommSemiring R] {p q : R[X]}

variable [Semiring A] [Algebra R A]

variable {B : Type _} [Semiring B] [Algebra R B]

variable (x : A)

/-- Given a valuation `x` of the variable in an `R`-algebra `A`, `aeval R A x` is
the unique `R`-algebra homomorphism from `R[X]` to `A` sending `X` to `x`.

This is a stronger variant of the linear map `Polynomial.leval`. -/
def aeval : R[X] →ₐ[R] A :=
  { eval₂RingHom' (algebraMap R A) x fun _a => Algebra.commutes _ _ with
    commutes' := fun _r => eval₂_C _ _ }
#align polynomial.aeval Polynomial.aeval

-- porting note: removed `variable` due to redundant binder annotation update

@[simp]
theorem adjoin_X : Algebra.adjoin R ({X} : Set R[X]) = ⊤ := by
  refine' top_unique fun p _hp => _
  set S := Algebra.adjoin R ({X} : Set R[X])
  rw [← sum_monomial_eq p]; simp only [← smul_X_eq_monomial, Sum]
  exact S.sum_mem fun n _hn => S.smul_mem (S.pow_mem (Algebra.subset_adjoin rfl) _) _
set_option linter.uppercaseLean3 false in
#align polynomial.adjoin_X Polynomial.adjoin_X

@[ext 1200]
theorem algHom_ext {f g : R[X] →ₐ[R] A} (h : f X = g X) : f = g :=
  AlgHom.ext_of_adjoin_eq_top adjoin_X fun _p hp => (Set.mem_singleton_iff.1 hp).symm ▸ h
#align polynomial.alg_hom_ext Polynomial.algHom_ext

theorem aeval_def (p : R[X]) : aeval x p = eval₂ (algebraMap R A) x p :=
  rfl
#align polynomial.aeval_def Polynomial.aeval_def

-- porting note: removed `@[simp]` because `simp` can prove this
theorem aeval_zero : aeval x (0 : R[X]) = 0 :=
  AlgHom.map_zero (aeval x)
#align polynomial.aeval_zero Polynomial.aeval_zero

@[simp]
theorem aeval_X : aeval x (X : R[X]) = x :=
  eval₂_X _ x
set_option linter.uppercaseLean3 false in
#align polynomial.aeval_X Polynomial.aeval_X

@[simp]
theorem aeval_C (r : R) : aeval x (C r) = algebraMap R A r :=
  eval₂_C _ x
set_option linter.uppercaseLean3 false in
#align polynomial.aeval_C Polynomial.aeval_C

@[simp]
theorem aeval_monomial {n : ℕ} {r : R} : aeval x (monomial n r) = algebraMap _ _ r * x ^ n :=
  eval₂_monomial _ _
#align polynomial.aeval_monomial Polynomial.aeval_monomial

-- porting note: removed `@[simp]` because `simp` can prove this
theorem aeval_X_pow {n : ℕ} : aeval x ((X : R[X]) ^ n) = x ^ n :=
  eval₂_X_pow _ _
set_option linter.uppercaseLean3 false in
#align polynomial.aeval_X_pow Polynomial.aeval_X_pow

-- porting note: removed `@[simp]` because `simp` can prove this
theorem aeval_add : aeval x (p + q) = aeval x p + aeval x q :=
  AlgHom.map_add _ _ _
#align polynomial.aeval_add Polynomial.aeval_add

-- porting note: removed `@[simp]` because `simp` can prove this

section deprecated
set_option linter.deprecated false

-- porting note: removed `@[simp]` because `simp` can prove this
@[deprecated]
theorem aeval_bit0 : aeval x (bit0 p) = bit0 (aeval x p) :=
  AlgHom.map_bit0 _ _
#align polynomial.aeval_bit0 Polynomial.aeval_bit0

-- porting note: removed `@[simp]` because `simp` can prove this
@[deprecated]
theorem aeval_bit1 : aeval x (bit1 p) = bit1 (aeval x p) :=
  AlgHom.map_bit1 _ _
#align polynomial.aeval_bit1 Polynomial.aeval_bit1

end deprecated

-- porting note: removed `@[simp]` because `simp` can prove this
theorem aeval_nat_cast (n : ℕ) : aeval x (n : R[X]) = n :=
  map_natCast _ _
#align polynomial.aeval_nat_cast Polynomial.aeval_nat_cast

theorem aeval_mul : aeval x (p * q) = aeval x p * aeval x q :=
  AlgHom.map_mul _ _ _
#align polynomial.aeval_mul Polynomial.aeval_mul

theorem aeval_comp {A : Type _} [CommSemiring A] [Algebra R A] (x : A) :
    aeval x (p.comp q) = aeval (aeval x q) p :=
  eval₂_comp (algebraMap R A)
#align polynomial.aeval_comp Polynomial.aeval_comp

theorem aeval_algHom (f : A →ₐ[R] B) (x : A) : aeval (f x) = f.comp (aeval x) :=
  algHom_ext <| by simp only [aeval_X, AlgHom.comp_apply]
#align polynomial.aeval_alg_hom Polynomial.aeval_algHom

@[simp]
theorem aeval_X_left : aeval (X : R[X]) = AlgHom.id R R[X] :=
  algHom_ext <| aeval_X X
set_option linter.uppercaseLean3 false in
#align polynomial.aeval_X_left Polynomial.aeval_X_left

theorem aeval_X_left_apply (p : R[X]) : aeval X p = p :=
  AlgHom.congr_fun (@aeval_X_left R _) p
set_option linter.uppercaseLean3 false in
#align polynomial.aeval_X_left_apply Polynomial.aeval_X_left_apply

theorem eval_unique (φ : R[X] →ₐ[R] A) (p) : φ p = eval₂ (algebraMap R A) (φ X) p := by
  rw [← aeval_def, aeval_algHom, aeval_X_left, AlgHom.comp_id]
#align polynomial.eval_unique Polynomial.eval_unique

theorem aeval_algHom_apply {F : Type _} [AlgHomClass F R A B] (f : F) (x : A) (p : R[X]) :
    aeval (f x) p = f (aeval x p) := by
  refine' Polynomial.induction_on p (by simp [AlgHomClass.commutes]) (fun p q hp hq => _)
    (by simp [AlgHomClass.commutes])
  rw [map_add, hp, hq, ← map_add, ← map_add]
#align polynomial.aeval_alg_hom_apply Polynomial.aeval_algHom_apply

theorem aeval_algEquiv (f : A ≃ₐ[R] B) (x : A) : aeval (f x) = (f : A →ₐ[R] B).comp (aeval x) :=
  aeval_algHom (f : A →ₐ[R] B) x
#align polynomial.aeval_alg_equiv Polynomial.aeval_algEquiv

theorem aeval_algebraMap_apply_eq_algebraMap_eval (x : R) (p : R[X]) :
    aeval (algebraMap R A x) p = algebraMap R A (p.eval x) :=
  aeval_algHom_apply (Algebra.ofId R A) x p
#align polynomial.aeval_algebra_map_apply_eq_algebra_map_eval Polynomial.aeval_algebraMap_apply_eq_algebraMap_eval

@[simp]
theorem coe_aeval_eq_eval (r : R) : (aeval r : R[X] → R) = eval r :=
  rfl
#align polynomial.coe_aeval_eq_eval Polynomial.coe_aeval_eq_eval

@[simp]
theorem coe_aeval_eq_evalRingHom (x : R) :
    ((aeval x : R[X] →ₐ[R] R) : R[X] →+* R) = evalRingHom x :=
  rfl
#align polynomial.coe_aeval_eq_eval_ring_hom Polynomial.coe_aeval_eq_evalRingHom

@[simp]
theorem aeval_fn_apply {X : Type _} (g : R[X]) (f : X → R) (x : X) :
    ((aeval f) g) x = aeval (f x) g :=
  (aeval_algHom_apply (Pi.evalAlgHom R (fun _ => R) x) f g).symm
#align polynomial.aeval_fn_apply Polynomial.aeval_fn_apply

@[norm_cast]
theorem aeval_subalgebra_coe (g : R[X]) {A : Type _} [Semiring A] [Algebra R A] (s : Subalgebra R A)
    (f : s) : (aeval f g : A) = aeval (f : A) g :=
  (aeval_algHom_apply s.val f g).symm
#align polynomial.aeval_subalgebra_coe Polynomial.aeval_subalgebra_coe

theorem coeff_zero_eq_aeval_zero (p : R[X]) : p.coeff 0 = aeval 0 p := by
  simp [coeff_zero_eq_eval_zero]
#align polynomial.coeff_zero_eq_aeval_zero Polynomial.coeff_zero_eq_aeval_zero

theorem coeff_zero_eq_aeval_zero' (p : R[X]) : algebraMap R A (p.coeff 0) = aeval (0 : A) p := by
  simp [aeval_def]
#align polynomial.coeff_zero_eq_aeval_zero' Polynomial.coeff_zero_eq_aeval_zero'

theorem map_aeval_eq_aeval_map {S T U : Type _} [CommSemiring S] [CommSemiring T] [Semiring U]
    [Algebra R S] [Algebra T U] {φ : R →+* T} {ψ : S →+* U}
    (h : (algebraMap T U).comp φ = ψ.comp (algebraMap R S)) (p : R[X]) (a : S) :
    ψ (aeval a p) = aeval (ψ a) (p.map φ) := by
  conv_rhs => rw [aeval_def, ← eval_map]
  rw [map_map, h, ← map_map, eval_map, eval₂_at_apply, aeval_def, eval_map]
#align polynomial.map_aeval_eq_aeval_map Polynomial.map_aeval_eq_aeval_map

theorem aeval_eq_zero_of_dvd_aeval_eq_zero [CommSemiring S] [CommSemiring T] [Algebra S T]
    {p q : S[X]} (h₁ : p ∣ q) {a : T} (h₂ : aeval a p = 0) : aeval a q = 0 := by
  rw [aeval_def, ← eval_map] at h₂⊢
  exact eval_eq_zero_of_dvd_of_eval_eq_zero (Polynomial.map_dvd (algebraMap S T) h₁) h₂
#align polynomial.aeval_eq_zero_of_dvd_aeval_eq_zero Polynomial.aeval_eq_zero_of_dvd_aeval_eq_zero

variable (R)

theorem _root_.Algebra.adjoin_singleton_eq_range_aeval (x : A) :
    Algebra.adjoin R {x} = (Polynomial.aeval x).range := by
  rw [← Algebra.map_top, ← adjoin_X, AlgHom.map_adjoin, Set.image_singleton, aeval_X]
#align algebra.adjoin_singleton_eq_range_aeval Algebra.adjoin_singleton_eq_range_aeval

variable {R}

section Semiring

variable [Semiring S] {f : R →+* S}

theorem aeval_eq_sum_range [Algebra R S] {p : R[X]} (x : S) :
    aeval x p = ∑ i in Finset.range (p.natDegree + 1), p.coeff i • x ^ i := by
  simp_rw [Algebra.smul_def]
  exact eval₂_eq_sum_range (algebraMap R S) x
#align polynomial.aeval_eq_sum_range Polynomial.aeval_eq_sum_range

theorem aeval_eq_sum_range' [Algebra R S] {p : R[X]} {n : ℕ} (hn : p.natDegree < n) (x : S) :
    aeval x p = ∑ i in Finset.range n, p.coeff i • x ^ i := by
  simp_rw [Algebra.smul_def]
  exact eval₂_eq_sum_range' (algebraMap R S) hn x
#align polynomial.aeval_eq_sum_range' Polynomial.aeval_eq_sum_range'

theorem isRoot_of_eval₂_map_eq_zero (hf : Function.Injective f) {r : R} :
    eval₂ f (f r) p = 0 → p.IsRoot r := by
  intro h
  apply hf
  rw [← eval₂_hom, h, f.map_zero]
#align polynomial.is_root_of_eval₂_map_eq_zero Polynomial.isRoot_of_eval₂_map_eq_zero

theorem isRoot_of_aeval_algebraMap_eq_zero [Algebra R S] {p : R[X]}
    (inj : Function.Injective (algebraMap R S)) {r : R} (hr : aeval (algebraMap R S r) p = 0) :
    p.IsRoot r :=
  isRoot_of_eval₂_map_eq_zero inj hr
#align polynomial.is_root_of_aeval_algebra_map_eq_zero Polynomial.isRoot_of_aeval_algebraMap_eq_zero

end Semiring

section CommSemiring

section aevalTower

variable [CommSemiring S] [Algebra S R] [Algebra S A'] [Algebra S B']

/-- Version of `aeval` for defining algebra homs out of `R[X]` over a smaller base ring
  than `R`. -/
def aevalTower (f : R →ₐ[S] A') (x : A') : R[X] →ₐ[S] A' :=
  { eval₂RingHom (↑f) x with commutes' := fun r => by simp [algebraMap_apply] }
#align polynomial.aeval_tower Polynomial.aevalTower

variable (g : R →ₐ[S] A') (y : A')

@[simp]
theorem aevalTower_X : aevalTower g y X = y :=
  eval₂_X _ _
set_option linter.uppercaseLean3 false in
#align polynomial.aeval_tower_X Polynomial.aevalTower_X

@[simp]
theorem aevalTower_C (x : R) : aevalTower g y (C x) = g x :=
  eval₂_C _ _
set_option linter.uppercaseLean3 false in
#align polynomial.aeval_tower_C Polynomial.aevalTower_C

@[simp]
theorem aevalTower_comp_C : (aevalTower g y : R[X] →+* A').comp C = g :=
  RingHom.ext <| aevalTower_C _ _
set_option linter.uppercaseLean3 false in
#align polynomial.aeval_tower_comp_C Polynomial.aevalTower_comp_C

@[simp]
theorem aevalTower_algebraMap (x : R) : aevalTower g y (algebraMap R R[X] x) = g x :=
  eval₂_C _ _
#align polynomial.aeval_tower_algebra_map Polynomial.aevalTower_algebraMap

@[simp]
theorem aevalTower_comp_algebraMap : (aevalTower g y : R[X] →+* A').comp (algebraMap R R[X]) = g :=
  aevalTower_comp_C _ _
#align polynomial.aeval_tower_comp_algebra_map Polynomial.aevalTower_comp_algebraMap

theorem aevalTower_toAlgHom (x : R) : aevalTower g y (IsScalarTower.toAlgHom S R R[X] x) = g x :=
  aevalTower_algebraMap _ _ _
#align polynomial.aeval_tower_to_alg_hom Polynomial.aevalTower_toAlgHom

@[simp]
theorem aevalTower_comp_toAlgHom : (aevalTower g y).comp (IsScalarTower.toAlgHom S R R[X]) = g :=
  AlgHom.coe_ringHom_injective <| aevalTower_comp_algebraMap _ _
#align polynomial.aeval_tower_comp_to_alg_hom Polynomial.aevalTower_comp_toAlgHom

@[simp]
theorem aevalTower_id : aevalTower (AlgHom.id S S) = aeval := by
  ext s p
  simp only [eval_X, aevalTower_X, coe_aeval_eq_eval]
#align polynomial.aeval_tower_id Polynomial.aevalTower_id

@[simp]
theorem aevalTower_ofId : aevalTower (Algebra.ofId S A') = aeval := by
  ext
  simp only [aeval_X, aevalTower_X]
#align polynomial.aeval_tower_of_id Polynomial.aevalTower_ofId

end aevalTower

end CommSemiring

section CommRing

variable [CommRing S] {f : R →+* S}

theorem dvd_term_of_dvd_eval_of_dvd_terms {z p : S} {f : S[X]} (i : ℕ) (dvd_eval : p ∣ f.eval z)
    (dvd_terms : ∀ (j) (_ : j ≠ i), p ∣ f.coeff j * z ^ j) : p ∣ f.coeff i * z ^ i := by
  by_cases hi : i ∈ f.support
  · rw [eval, eval₂_eq_sum, sum_def] at dvd_eval
    rw [← Finset.insert_erase hi, Finset.sum_insert (Finset.not_mem_erase _ _)] at dvd_eval
    refine' (dvd_add_left _).mp dvd_eval
    apply Finset.dvd_sum
    intro j hj
    exact dvd_terms j (Finset.ne_of_mem_erase hj)
  · convert dvd_zero p
    rw [not_mem_support_iff] at hi
    simp [hi]
#align polynomial.dvd_term_of_dvd_eval_of_dvd_terms Polynomial.dvd_term_of_dvd_eval_of_dvd_terms

theorem dvd_term_of_isRoot_of_dvd_terms {r p : S} {f : S[X]} (i : ℕ) (hr : f.IsRoot r)
    (h : ∀ (j) (_ : j ≠ i), p ∣ f.coeff j * r ^ j) : p ∣ f.coeff i * r ^ i :=
  dvd_term_of_dvd_eval_of_dvd_terms i (Eq.symm hr ▸ dvd_zero p) h
#align polynomial.dvd_term_of_is_root_of_dvd_terms Polynomial.dvd_term_of_isRoot_of_dvd_terms

end CommRing

end aeval

section Ring

variable [Ring R]

/-- The evaluation map is not generally multiplicative when the coefficient ring is noncommutative,
but nevertheless any polynomial of the form `p * (X - monomial 0 r)` is sent to zero
when evaluated at `r`.

This is the key step in our proof of the Cayley-Hamilton theorem.
-/
theorem eval_mul_X_sub_C {p : R[X]} (r : R) : (p * (X - C r)).eval r = 0 := by
  simp only [eval, eval₂_eq_sum, RingHom.id_apply]
  have bound :=
    calc
      (p * (X - C r)).natDegree ≤ p.natDegree + (X - C r).natDegree := natDegree_mul_le
      _ ≤ p.natDegree + 1 := (add_le_add_left (natDegree_X_sub_C_le _) _)
      _ < p.natDegree + 2 := lt_add_one _
  rw [sum_over_range' _ _ (p.natDegree + 2) bound]
  swap
  · simp
  rw [sum_range_succ']
  conv_lhs =>
    congr
    arg 2
    simp [coeff_mul_X_sub_C, sub_mul, mul_assoc, ← pow_succ]
  rw [sum_range_sub']
  simp [coeff_monomial]
set_option linter.uppercaseLean3 false in
#align polynomial.eval_mul_X_sub_C Polynomial.eval_mul_X_sub_C

theorem not_isUnit_X_sub_C [Nontrivial R] (r : R) : ¬IsUnit (X - C r) :=
  fun ⟨⟨_, g, _hfg, hgf⟩, rfl⟩ => zero_ne_one' R <| by erw [← eval_mul_X_sub_C, hgf, eval_one]
set_option linter.uppercaseLean3 false in
#align polynomial.not_is_unit_X_sub_C Polynomial.not_isUnit_X_sub_C

end Ring

-- porting note: workaround lean4#2074, this declaration works with
-- `set_option synthInstance.etaExperiment true`
attribute [-instance] Ring.toNonAssocRing

theorem aeval_endomorphism {M : Type _} [CommRing R] [AddCommGroup M] [Module R M] (f : M →ₗ[R] M)
    (v : M) (p : R[X]) : aeval f p v = p.sum fun n b => b • (f ^ n) v := by
  rw [aeval_def, eval₂_eq_sum]
  exact (LinearMap.applyₗ v).map_sum
#align polynomial.aeval_endomorphism Polynomial.aeval_endomorphism

end Polynomial


/-
Copyright (c) 2016 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad

! This file was ported from Lean 3 source module data.int.dvd.pow
! leanprover-community/mathlib commit c3291da49cfa65f0d43b094750541c0731edc932
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Int.Dvd.Basic
import Mathbin.Data.Nat.Pow

/-!
# Basic lemmas about the divisibility relation in `ℤ` involving powers.

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

open Nat

namespace Int

/- warning: int.sign_pow_bit1 -> Int.sign_pow_bit1 is a dubious translation:
lean 3 declaration is
  forall (k : Nat) (n : Int), Eq.{1} Int (HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.monoid)) (Int.sign n) (bit1.{0} Nat Nat.hasOne Nat.hasAdd k)) (Int.sign n)
but is expected to have type
  forall (k : Nat) (n : Int), Eq.{1} Int (HPow.hPow.{0, 0, 0} Int Nat Int Int.instHPowIntNat (Int.sign n) (bit1.{0} Nat (CanonicallyOrderedCommSemiring.toOne.{0} Nat Nat.canonicallyOrderedCommSemiring) instAddNat k)) (Int.sign n)
Case conversion may be inaccurate. Consider using '#align int.sign_pow_bit1 Int.sign_pow_bit1ₓ'. -/
@[simp]
theorem sign_pow_bit1 (k : ℕ) : ∀ n : ℤ, n.sign ^ bit1 k = n.sign
  | (n + 1 : ℕ) => one_pow (bit1 k)
  | 0 => zero_pow (Nat.zero_lt_bit1 k)
  | -[n+1] => (neg_pow_bit1 1 k).trans (congr_arg (fun x => -x) (one_pow (bit1 k)))
#align int.sign_pow_bit1 Int.sign_pow_bit1

/- warning: int.pow_dvd_of_le_of_pow_dvd -> Int.pow_dvd_of_le_of_pow_dvd is a dubious translation:
lean 3 declaration is
  forall {p : Nat} {m : Nat} {n : Nat} {k : Int}, (LE.le.{0} Nat Nat.hasLe m n) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) ((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))) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p n)) k) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) ((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))) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p m)) k)
but is expected to have type
  forall {p : Nat} {m : Nat} {n : Nat} {k : Int}, (LE.le.{0} Nat instLENat m n) -> (Dvd.dvd.{0} Int Int.instDvdInt (Nat.cast.{0} Int instNatCastInt (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p n)) k) -> (Dvd.dvd.{0} Int Int.instDvdInt (Nat.cast.{0} Int instNatCastInt (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p m)) k)
Case conversion may be inaccurate. Consider using '#align int.pow_dvd_of_le_of_pow_dvd Int.pow_dvd_of_le_of_pow_dvdₓ'. -/
theorem pow_dvd_of_le_of_pow_dvd {p m n : ℕ} {k : ℤ} (hmn : m ≤ n) (hdiv : ↑(p ^ n) ∣ k) :
    ↑(p ^ m) ∣ k := by
  induction k
  · apply Int.coe_nat_dvd.2
    apply pow_dvd_of_le_of_pow_dvd hmn
    apply Int.coe_nat_dvd.1 hdiv
  change -[k+1] with -(↑(k + 1) : ℤ)
  apply dvd_neg_of_dvd
  apply Int.coe_nat_dvd.2
  apply pow_dvd_of_le_of_pow_dvd hmn
  apply Int.coe_nat_dvd.1
  apply dvd_of_dvd_neg
  exact hdiv
#align int.pow_dvd_of_le_of_pow_dvd Int.pow_dvd_of_le_of_pow_dvd

/- warning: int.dvd_of_pow_dvd -> Int.dvd_of_pow_dvd is a dubious translation:
lean 3 declaration is
  forall {p : Nat} {k : Nat} {m : Int}, (LE.le.{0} Nat Nat.hasLe (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) k) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) ((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))) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p k)) m) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) ((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))) p) m)
but is expected to have type
  forall {p : Nat} {k : Nat} {m : Int}, (LE.le.{0} Nat instLENat (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) k) -> (Dvd.dvd.{0} Int Int.instDvdInt (Nat.cast.{0} Int instNatCastInt (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p k)) m) -> (Dvd.dvd.{0} Int Int.instDvdInt (Nat.cast.{0} Int instNatCastInt p) m)
Case conversion may be inaccurate. Consider using '#align int.dvd_of_pow_dvd Int.dvd_of_pow_dvdₓ'. -/
theorem dvd_of_pow_dvd {p k : ℕ} {m : ℤ} (hk : 1 ≤ k) (hpk : ↑(p ^ k) ∣ m) : ↑p ∣ m := by
  rw [← pow_one p] <;> exact pow_dvd_of_le_of_pow_dvd hk hpk
#align int.dvd_of_pow_dvd Int.dvd_of_pow_dvd

end Int


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

! This file was ported from Lean 3 source module data.list.chain
! leanprover-community/mathlib commit dd71334db81d0bd444af1ee339a29298bef40734
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.List.Pairwise
import Mathlib.Logic.Relation

/-!
# Relation chain

This file provides basic results about `List.Chain` (definition in `Data.List.Defs`).
A list `[a₂, ..., aₙ]` is a `Chain` starting at `a₁` with respect to the relation `r` if `r a₁ a₂`
and `r a₂ a₃` and ... and `r aₙ₋₁ aₙ`. We write it `Chain r a₁ [a₂, ..., aₙ]`.
A graph-specialized version is in development and will hopefully be added under `combinatorics.`
sometime soon.
-/

universe u v

open Nat

namespace List

variable {α : Type u} {β : Type v} {R r : α → α → Prop} {l l₁ l₂ : List α} {a b : α}

mk_iff_of_inductive_prop List.Chain List.chain_iff
#align list.chain_iff List.chain_iff

--Porting note: attribute in Lean3, but not in Lean4 Std so added here instead
attribute [simp] Chain.nil

#align list.chain.nil List.Chain.nil
#align list.chain.cons List.Chain.cons

theorem rel_of_chain_cons {a b : α} {l : List α} (p : Chain R a (b :: l)) : R a b :=
  (chain_cons.1 p).1
#align list.rel_of_chain_cons List.rel_of_chain_cons

theorem chain_of_chain_cons {a b : α} {l : List α} (p : Chain R a (b :: l)) : Chain R b l :=
  (chain_cons.1 p).2
#align list.chain_of_chain_cons List.chain_of_chain_cons

theorem Chain.imp' {R S : α → α → Prop} (HRS : ∀ ⦃a b⦄, R a b → S a b) {a b : α}
    (Hab : ∀ ⦃c⦄, R a c → S b c) {l : List α} (p : Chain R a l) : Chain S b l := by
  induction p generalizing b with
  | nil => constructor
  | cons r _ ih =>
    constructor
    · exact Hab r
    · exact ih (@HRS _)
#align list.chain.imp' List.Chain.imp'

theorem Chain.imp {R S : α → α → Prop} (H : ∀ a b, R a b → S a b) {a : α} {l : List α}
    (p : Chain R a l) : Chain S a l :=
  p.imp' H (H a)
#align list.chain.imp List.Chain.imp

theorem Chain.iff {S : α → α → Prop} (H : ∀ a b, R a b ↔ S a b) {a : α} {l : List α} :
    Chain R a l ↔ Chain S a l :=
  ⟨Chain.imp fun a b => (H a b).1, Chain.imp fun a b => (H a b).2⟩
#align list.chain.iff List.Chain.iff

theorem Chain.iff_mem {a : α} {l : List α} :
    Chain R a l ↔ Chain (fun x y => x ∈ a :: l ∧ y ∈ l ∧ R x y) a l :=
  ⟨fun p => by
    induction' p with _ a b l r _ IH <;> constructor <;>
      [exact ⟨mem_cons_self _ _, mem_cons_self _ _, r⟩,
      exact IH.imp fun a b ⟨am, bm, h⟩ => ⟨mem_cons_of_mem _ am, mem_cons_of_mem _ bm, h⟩],
    Chain.imp fun a b h => h.2.2⟩
#align list.chain.iff_mem List.Chain.iff_mem

theorem chain_singleton {a b : α} : Chain R a [b] ↔ R a b := by
  simp only [chain_cons, Chain.nil, and_true_iff]
#align list.chain_singleton List.chain_singleton

theorem chain_split {a b : α} {l₁ l₂ : List α} :
    Chain R a (l₁ ++ b :: l₂) ↔ Chain R a (l₁ ++ [b]) ∧ Chain R b l₂ := by
  induction' l₁ with x l₁ IH generalizing a <;>
    simp only [*, nil_append, cons_append, Chain.nil, chain_cons, and_true_iff, and_assoc]
#align list.chain_split List.chain_split

@[simp]
theorem chain_append_cons_cons {a b c : α} {l₁ l₂ : List α} :
    Chain R a (l₁ ++ b :: c :: l₂) ↔ Chain R a (l₁ ++ [b]) ∧ R b c ∧ Chain R c l₂ := by
  rw [chain_split, chain_cons]
#align list.chain_append_cons_cons List.chain_append_cons_cons

theorem chain_iff_forall₂ :
    ∀ {a : α} {l : List α}, Chain R a l ↔ l = [] ∨ Forall₂ R (a :: dropLast l) l
  | a, [] => by simp
  | a, b :: l => by
    by_cases h : l = [] <;>
    simp [@chain_iff_forall₂ b l, *]
#align list.chain_iff_forall₂ List.chain_iff_forall₂

theorem chain_append_singleton_iff_forall₂ : Chain R a (l ++ [b]) ↔ Forall₂ R (a :: l) (l ++ [b]) :=
  by simp [chain_iff_forall₂]
#align list.chain_append_singleton_iff_forall₂ List.chain_append_singleton_iff_forall₂

theorem chain_map (f : β → α) {b : β} {l : List β} :
    Chain R (f b) (map f l) ↔ Chain (fun a b : β => R (f a) (f b)) b l := by
  induction l generalizing b <;> simp only [map, Chain.nil, chain_cons, *]
#align list.chain_map List.chain_map

theorem chain_of_chain_map {S : β → β → Prop} (f : α → β) (H : ∀ a b : α, S (f a) (f b) → R a b)
    {a : α} {l : List α} (p : Chain S (f a) (map f l)) : Chain R a l :=
  ((chain_map f).1 p).imp H
#align list.chain_of_chain_map List.chain_of_chain_map

theorem chain_map_of_chain {S : β → β → Prop} (f : α → β) (H : ∀ a b : α, R a b → S (f a) (f b))
    {a : α} {l : List α} (p : Chain R a l) : Chain S (f a) (map f l) :=
  (chain_map f).2 <| p.imp H
#align list.chain_map_of_chain List.chain_map_of_chain

theorem chain_pmap_of_chain {S : β → β → Prop} {p : α → Prop} {f : ∀ a, p a → β}
    (H : ∀ a b ha hb, R a b → S (f a ha) (f b hb)) {a : α} {l : List α} (hl₁ : Chain R a l)
    (ha : p a) (hl₂ : ∀ a ∈ l, p a) : Chain S (f a ha) (List.pmap f l hl₂) := by
  induction' l with lh lt l_ih generalizing a
  · simp
  · simp [H _ _ _ _ (rel_of_chain_cons hl₁), l_ih (chain_of_chain_cons hl₁)]
#align list.chain_pmap_of_chain List.chain_pmap_of_chain

theorem chain_of_chain_pmap {S : β → β → Prop} {p : α → Prop} (f : ∀ a, p a → β) {l : List α}
    (hl₁ : ∀ a ∈ l, p a) {a : α} (ha : p a) (hl₂ : Chain S (f a ha) (List.pmap f l hl₁))
    (H : ∀ a b ha hb, S (f a ha) (f b hb) → R a b) : Chain R a l := by
  induction' l with lh lt l_ih generalizing a
  · simp
  · simp [H _ _ _ _ (rel_of_chain_cons hl₂), l_ih _ _ (chain_of_chain_cons hl₂)]
#align list.chain_of_chain_pmap List.chain_of_chain_pmap

protected theorem Pairwise.chain (p : Pairwise R (a :: l)) : Chain R a l := by
  cases' pairwise_cons.1 p with r p'; clear p
  induction' p' with b l r' _ IH generalizing a; · exact Chain.nil
  simp only [chain_cons, forall_mem_cons] at r
  exact chain_cons.2 ⟨r.1, IH r'⟩
#align list.pairwise.chain List.Pairwise.chain

protected theorem Chain.pairwise [IsTrans α R] :
    ∀ {a : α} {l : List α}, Chain R a l → Pairwise R (a :: l)
  | a, [], Chain.nil => pairwise_singleton _ _
  | a, _, @Chain.cons _ _ _ b l h hb =>
    hb.pairwise.cons
      (by
        simp only [mem_cons, forall_eq_or_imp, h, true_and_iff]
        exact fun c hc => _root_.trans h (rel_of_pairwise_cons hb.pairwise hc))
#align list.chain.pairwise List.Chain.pairwise

theorem chain_iff_pairwise [IsTrans α R] {a : α} {l : List α} : Chain R a l ↔ Pairwise R (a :: l) :=
  ⟨Chain.pairwise, Pairwise.chain⟩
#align list.chain_iff_pairwise List.chain_iff_pairwise

protected

protected theorem Chain.rel [IsTrans α R] (hl : l.Chain R a) (hb : b ∈ l) : R a b := by
  rw [chain_iff_pairwise] at hl
  exact rel_of_pairwise_cons hl hb
#align list.chain.rel List.Chain.rel

theorem chain_iff_get {R} : ∀ {a : α} {l : List α}, Chain R a l ↔
    (∀ h : 0 < length l, R a (get l ⟨0, h⟩)) ∧
      ∀ (i : ℕ) (h : i < l.length - 1),
        R (get l ⟨i, lt_of_lt_pred h⟩) (get l ⟨i+1, lt_pred_iff.mp h⟩)
  | a, [] => iff_of_true (by simp) ⟨fun h => by simp at h, fun _ h => by simp at h⟩
  | a, b :: t => by
    rw [chain_cons, @chain_iff_get _ _ t]
    constructor
    · rintro ⟨R, ⟨h0, h⟩⟩
      constructor
      · intro _
        exact R
      intro i w
      cases' i with i
      · apply h0
      . exact h i (lt_pred_iff.2 <| by simpa using w)
    rintro ⟨h0, h⟩; constructor
    · apply h0
      simp
    constructor
    · apply h 0
    intro i w
    exact h (i+1) (lt_pred_iff.mp w)

set_option linter.deprecated false in
@[deprecated chain_iff_get]
theorem chain_iff_nthLe {R} {a : α} {l : List α} : Chain R a l ↔
        (∀ h : 0 < length l, R a (nthLe l 0 h)) ∧
          ∀ (i) (h : i < length l - 1),
            R (nthLe l i (lt_of_lt_pred h)) (nthLe l (i + 1) (lt_pred_iff.mp h)) :=
  by rw [chain_iff_get]; simp [nthLe]
#align list.chain_iff_nth_le List.chain_iff_nthLe

theorem Chain'.imp {S : α → α → Prop} (H : ∀ a b, R a b → S a b) {l : List α} (p : Chain' R l) :
    Chain' S l := by cases l <;> [trivial, exact Chain.imp H p]
#align list.chain'.imp List.Chain'.imp

theorem Chain'.iff {S : α → α → Prop} (H : ∀ a b, R a b ↔ S a b) {l : List α} :
    Chain' R l ↔ Chain' S l :=
  ⟨Chain'.imp fun a b => (H a b).1, Chain'.imp fun a b => (H a b).2⟩
#align list.chain'.iff List.Chain'.iff

theorem Chain'.iff_mem : ∀ {l : List α}, Chain' R l ↔ Chain' (fun x y => x ∈ l ∧ y ∈ l ∧ R x y) l
  | [] => Iff.rfl
  | _ :: _ =>
    ⟨fun h => (Chain.iff_mem.1 h).imp fun _ _ ⟨h₁, h₂, h₃⟩ => ⟨h₁, mem_cons.2 (Or.inr h₂), h₃⟩,
      Chain'.imp fun _ _ h => h.2.2⟩
#align list.chain'.iff_mem List.Chain'.iff_mem

@[simp]
theorem chain'_nil : Chain' R [] :=
  trivial
#align list.chain'_nil List.chain'_nil

@[simp]
theorem chain'_singleton (a : α) : Chain' R [a] :=
  Chain.nil
#align list.chain'_singleton List.chain'_singleton

@[simp]
theorem chain'_cons {x y l} : Chain' R (x :: y :: l) ↔ R x y ∧ Chain' R (y :: l) :=
  chain_cons
#align list.chain'_cons List.chain'_cons

theorem chain'_isInfix : ∀ l : List α, Chain' (fun x y => [x, y] <:+: l) l
  | [] => chain'_nil
  | [a] => chain'_singleton _
  | a :: b :: l =>
    chain'_cons.2
      ⟨⟨[], l, by simp⟩, (chain'_isInfix (b :: l)).imp fun x y h => h.trans ⟨[a], [], by simp⟩⟩
#align list.chain'_is_infix List.chain'_isInfix

theorem chain'_split {a : α} :
    ∀ {l₁ l₂ : List α}, Chain' R (l₁ ++ a :: l₂) ↔ Chain' R (l₁ ++ [a]) ∧ Chain' R (a :: l₂)
  | [], _ => (and_iff_right (chain'_singleton a)).symm
  | _ :: _, _ => chain_split
#align list.chain'_split List.chain'_split

@[simp]
theorem chain'_append_cons_cons {b c : α} {l₁ l₂ : List α} :
    Chain' R (l₁ ++ b :: c :: l₂) ↔ Chain' R (l₁ ++ [b]) ∧ R b c ∧ Chain' R (c :: l₂) := by
  rw [chain'_split, chain'_cons]
#align list.chain'_append_cons_cons List.chain'_append_cons_cons

theorem chain'_map (f : β → α) {l : List β} :
    Chain' R (map f l) ↔ Chain' (fun a b : β => R (f a) (f b)) l := by
  cases l <;> [rfl, exact chain_map _]
#align list.chain'_map List.chain'_map

theorem chain'_of_chain'_map {S : β → β → Prop} (f : α → β) (H : ∀ a b : α, S (f a) (f b) → R a b)
    {l : List α} (p : Chain' S (map f l)) : Chain' R l :=
  ((chain'_map f).1 p).imp H
#align list.chain'_of_chain'_map List.chain'_of_chain'_map

theorem chain'_map_of_chain' {S : β → β → Prop} (f : α → β) (H : ∀ a b : α, R a b → S (f a) (f b))
    {l : List α} (p : Chain' R l) : Chain' S (map f l) :=
  (chain'_map f).2 <| p.imp H
#align list.chain'_map_of_chain' List.chain'_map_of_chain'

theorem Pairwise.chain' : ∀ {l : List α}, Pairwise R l → Chain' R l
  | [], _ => trivial
  | _ :: _, h => Pairwise.chain h
#align list.pairwise.chain' List.Pairwise.chain'

theorem chain'_iff_pairwise [IsTrans α R] : ∀ {l : List α}, Chain' R l ↔ Pairwise R l
  | [] => (iff_true_intro Pairwise.nil).symm
  | _ :: _ => chain_iff_pairwise
#align list.chain'_iff_pairwise List.chain'_iff_pairwise

protected theorem Chain'.sublist [IsTrans α R] (hl : l₂.Chain' R) (h : l₁ <+ l₂) : l₁.Chain' R := by
  rw [chain'_iff_pairwise] at hl⊢
  exact hl.sublist h
#align list.chain'.sublist List.Chain'.sublist

theorem Chain'.cons {x y l} (h₁ : R x y) (h₂ : Chain' R (y :: l)) : Chain' R (x :: y :: l) :=
  chain'_cons.2 ⟨h₁, h₂⟩
#align list.chain'.cons List.Chain'.cons

theorem Chain'.tail : ∀ {l} (_ : Chain' R l), Chain' R l.tail
  | [], _ => trivial
  | [_], _ => trivial
  | _ :: _ :: _, h => (chain'_cons.mp h).right
#align list.chain'.tail List.Chain'.tail

theorem Chain'.rel_head {x y l} (h : Chain' R (x :: y :: l)) : R x y :=
  rel_of_chain_cons h
#align list.chain'.rel_head List.Chain'.rel_head

theorem Chain'.rel_head? {x l} (h : Chain' R (x :: l)) ⦃y⦄ (hy : y ∈ head? l) : R x y := by
  rw [← cons_head?_tail hy] at h
  exact h.rel_head
#align list.chain'.rel_head' List.Chain'.rel_head?

theorem Chain'.cons' {x} : ∀ {l : List α}, Chain' R l → (∀ y ∈ l.head?, R x y) → Chain' R (x :: l)
  | [], _, _ => chain'_singleton x
  | _ :: _, hl, H => hl.cons <| H _ rfl
#align list.chain'.cons' List.Chain'.cons'

theorem chain'_cons' {x l} : Chain' R (x :: l) ↔ (∀ y ∈ head? l, R x y) ∧ Chain' R l :=
  ⟨fun h => ⟨h.rel_head?, h.tail⟩, fun ⟨h₁, h₂⟩ => h₂.cons' h₁⟩
#align list.chain'_cons' List.chain'_cons'

theorem chain'_append :
    ∀ {l₁ l₂ : List α},
      Chain' R (l₁ ++ l₂) ↔ Chain' R l₁ ∧ Chain' R l₂ ∧ ∀ x ∈ l₁.getLast?, ∀ y ∈ l₂.head?, R x y
  | [], l => by simp
  | [a], l => by simp [chain'_cons', and_comm]
  | a :: b :: l₁, l₂ => by
    rw [cons_append, cons_append, chain'_cons, chain'_cons, ← cons_append, chain'_append,
      and_assoc]
    simp
#align list.chain'_append List.chain'_append

theorem Chain'.append (h₁ : Chain' R l₁) (h₂ : Chain' R l₂)
    (h : ∀ x ∈ l₁.getLast?, ∀ y ∈ l₂.head?, R x y) : Chain' R (l₁ ++ l₂) :=
  chain'_append.2 ⟨h₁, h₂, h⟩
#align list.chain'.append List.Chain'.append

theorem Chain'.left_of_append (h : Chain' R (l₁ ++ l₂)) : Chain' R l₁ :=
  (chain'_append.1 h).1
#align list.chain'.left_of_append List.Chain'.left_of_append

theorem Chain'.right_of_append (h : Chain' R (l₁ ++ l₂)) : Chain' R l₂ :=
  (chain'_append.1 h).2.1
#align list.chain'.right_of_append List.Chain'.right_of_append

theorem Chain'.infix (h : Chain' R l) (h' : l₁ <:+: l) : Chain' R l₁ := by
  rcases h' with ⟨l₂, l₃, rfl⟩
  exact h.left_of_append.right_of_append
#align list.chain'.infix List.Chain'.infix

theorem Chain'.suffix (h : Chain' R l) (h' : l₁ <:+ l) : Chain' R l₁ :=
  h.infix h'.isInfix
#align list.chain'.suffix List.Chain'.suffix

theorem Chain'.prefix (h : Chain' R l) (h' : l₁ <+: l) : Chain' R l₁ :=
  h.infix h'.isInfix
#align list.chain'.prefix List.Chain'.prefix

theorem Chain'.drop (h : Chain' R l) (n : ℕ) : Chain' R (drop n l) :=
  h.suffix (drop_suffix _ _)
#align list.chain'.drop List.Chain'.drop

theorem Chain'.init (h : Chain' R l) : Chain' R l.dropLast :=
  h.prefix l.dropLast_prefix
#align list.chain'.init List.Chain'.init

theorem Chain'.take (h : Chain' R l) (n : ℕ) : Chain' R (take n l) :=
  h.prefix (take_prefix _ _)
#align list.chain'.take List.Chain'.take

theorem chain'_pair {x y} : Chain' R [x, y] ↔ R x y := by
  simp only [chain'_singleton, chain'_cons, and_true_iff]
#align list.chain'_pair List.chain'_pair

theorem Chain'.imp_head {x y} (h : ∀ {z}, R x z → R y z) {l} (hl : Chain' R (x :: l)) :
    Chain' R (y :: l) :=
  hl.tail.cons' fun _ hz => h <| hl.rel_head? hz
#align list.chain'.imp_head List.Chain'.imp_head

theorem chain'_reverse : ∀ {l}, Chain' R (reverse l) ↔ Chain' (flip R) l
  | [] => Iff.rfl
  | [a] => by simp only [chain'_singleton, reverse_singleton]
  | a :: b :: l => by
    rw [chain'_cons, reverse_cons, reverse_cons, append_assoc, cons_append, nil_append,
      chain'_split, ← reverse_cons, @chain'_reverse (b :: l), and_comm, chain'_pair, flip]
#align list.chain'_reverse List.chain'_reverse

theorem chain'_iff_get {R} : ∀ {l : List α}, Chain' R l ↔
    ∀ (i : ℕ) (h : i < length l - 1),
      R (get l ⟨i, lt_of_lt_pred h⟩) (get l ⟨i + 1, lt_pred_iff.mp h⟩)
  | [] => iff_of_true (by simp) (fun _ h => by simp at h)
  | [a] => iff_of_true (by simp) (fun _ h => by simp at h)
  | a :: b :: t => by
    rw [← and_forall_succ, chain'_cons, chain'_iff_get]
    simp
    dsimp [succ_sub_one]
    exact fun _ => ⟨fun h i hi => h i (Nat.lt_of_succ_lt_succ hi),
                    fun h i hi => h i (Nat.succ_lt_succ hi)⟩

set_option linter.deprecated false in
@[deprecated chain'_iff_get]
theorem chain'_iff_nthLe {R} {l : List α} : Chain' R l ↔
    ∀ (i) (h : i < length l - 1),
      R (nthLe l i (lt_of_lt_pred h)) (nthLe l (i + 1) (lt_pred_iff.mp h)) :=
  chain'_iff_get.trans <| by simp [nthLe]
#align list.chain'_iff_nth_le List.chain'_iff_nthLe

/-- If `l₁ l₂` and `l₃` are lists and `l₁ ++ l₂` and `l₂ ++ l₃` both satisfy
  `Chain' R`, then so does `l₁ ++ l₂ ++ l₃` provided `l₂ ≠ []` -/
theorem Chain'.append_overlap {l₁ l₂ l₃ : List α} (h₁ : Chain' R (l₁ ++ l₂))
    (h₂ : Chain' R (l₂ ++ l₃)) (hn : l₂ ≠ []) : Chain' R (l₁ ++ l₂ ++ l₃) :=
  h₁.append h₂.right_of_append <| by
    simpa only [getLast?_append_of_ne_nil _ hn] using (chain'_append.1 h₂).2.2
#align list.chain'.append_overlap List.Chain'.append_overlap

-- porting note: new
lemma chain'_join : ∀ {L : List (List α)}, [] ∉ L →
  (Chain' R L.join ↔ (∀ l ∈ L, Chain' R l) ∧
    L.Chain' (fun l₁ l₂ => ∀ᵉ (x ∈ l₁.getLast?) (y ∈ l₂.head?), R x y))
| [], _ => by simp
| [l], _ => by simp [join]
| (l₁ :: l₂ :: L), hL => by
    rw [mem_cons, not_or, ← Ne.def] at hL
    rw [join, chain'_append, chain'_join hL.2, forall_mem_cons, chain'_cons]
    rw [mem_cons, not_or, ← Ne.def] at hL
    simp only [forall_mem_cons, and_assoc, join, head?_append_of_ne_nil _ hL.2.1.symm]
    exact Iff.rfl.and (Iff.rfl.and $ Iff.rfl.and and_comm)

/-- If `a` and `b` are related by the reflexive transitive closure of `r`, then there is a `r`-chain
starting from `a` and ending on `b`.
The converse of `relationReflTransGen_of_exists_chain`.
-/
theorem exists_chain_of_relationReflTransGen (h : Relation.ReflTransGen r a b) :
    ∃ l, Chain r a l ∧ getLast (a :: l) (cons_ne_nil _ _) = b := by
  refine' Relation.ReflTransGen.head_induction_on h _ _
  · exact ⟨[], Chain.nil, rfl⟩
  · intro c d e _ ih
    obtain ⟨l, hl₁, hl₂⟩ := ih
    refine' ⟨d :: l, Chain.cons e hl₁, _⟩
    rwa [getLast_cons_cons]
#align list.exists_chain_of_relation_refl_trans_gen List.exists_chain_of_relationReflTransGen

/-- Given a chain from `a` to `b`, and a predicate true at `b`, if `r x y → p y → p x` then
the predicate is true everywhere in the chain and at `a`.
That is, we can propagate the predicate up the chain.
-/
theorem Chain.induction (p : α → Prop) (l : List α) (h : Chain r a l)
    (hb : getLast (a :: l) (cons_ne_nil _ _) = b) (carries : ∀ ⦃x y : α⦄, r x y → p y → p x)
    (final : p b) : ∀ i ∈ a :: l, p i := by
  induction' l with _ _ l_ih generalizing a
  · cases hb
    simpa using final
  · rw [chain_cons] at h
    simp only [mem_cons]
    rintro _ (rfl | H)
    apply carries h.1 (l_ih h.2 hb _ (mem_cons.2 (Or.inl rfl)))
    apply l_ih h.2 hb _ (mem_cons.2 H)
#align list.chain.induction List.Chain.induction

/-- Given a chain from `a` to `b`, and a predicate true at `b`, if `r x y → p y → p x` then
the predicate is true at `a`.
That is, we can propagate the predicate all the way up the chain.
-/
@[elab_as_elim]
theorem Chain.induction_head (p : α → Prop) (l : List α) (h : Chain r a l)
    (hb : getLast (a :: l) (cons_ne_nil _ _) = b) (carries : ∀ ⦃x y : α⦄, r x y → p y → p x)
    (final : p b) : p a :=
  (Chain.induction p l h hb carries final) _ (mem_cons_self _ _)
#align list.chain.induction_head List.Chain.induction_head

/--
If there is an `r`-chain starting from `a` and ending at `b`, then `a` and `b` are related by the
reflexive transitive closure of `r`. The converse of `exists_chain_of_relationReflTransGen`.
-/
theorem relationReflTransGen_of_exists_chain (l : List α) (hl₁ : Chain r a l)
    (hl₂ : getLast (a :: l) (cons_ne_nil _ _) = b) : Relation.ReflTransGen r a b :=
--Porting note: `p` behaves like an implicit argument to `Chain.induction_head` but it is explicit.
  Chain.induction_head l hl₁ hl₂ (fun _ _ => Relation.ReflTransGen.head)
    Relation.ReflTransGen.refl
#align list.relation_refl_trans_gen_of_exists_chain List.relationReflTransGen_of_exists_chain

end List


import data.real.basic
import linear_algebra.basic
import linear_algebra.linear_independent
import analysis.normed_space.basic
import analysis.inner_product_space.euclidean_dist
import data.mv_polynomial.basic
import data.polynomial.basic
import poly_questions
import topology.category.Profinite.default
import data.matrix.basic
import linear_algebra.matrix.to_linear_equiv

open_locale big_operators classical matrix

/--
The purpose of this document is to show:
  - For any set of `d` vectors `uᵢ : (fin d) → ℝ`, there exist `d` perturbations `ηᵢ : (fin d) → ℝ`
  such that the set of vectors `uᵢ + ηᵢ` are linearly independent.
  - For any set of `n` vectors `uᵢ : (fin d) → ℝ`, there exist `n` perturbations `ηᵢ : (fin d) → ℝ`
  such that any size `d` subset of `uᵢ + η_i` are linearly independent.
  - These perturbations can be made arbitrarily small.
-/
variables {n : ℕ}

def perm_graph (perm : equiv.perm (fin n)) : (fin n × fin n) → ℕ := λ ⟨i, j⟩, if perm j = i then 1 else 0

noncomputable def perm_graph' (perm : equiv.perm (fin n)) : (fin n × fin n) →₀ ℕ := ∑ i : fin n, finsupp.single (perm i, i) 1

lemma perm_graph'_apply (perm : equiv.perm (fin n)) (ij : fin n × fin n) : perm_graph' perm ij =
if perm (ij.snd) = ij.fst then 1 else 0 :=
begin
  rw perm_graph',
  by_cases hij : perm ij.snd = ij.fst,
  have : ∀ x : fin n, (perm x, x) = ij ↔ x = ij.snd :=
  begin
    intro x,
    split,
    intro h,
    rw ← h,
    intro h,
    rw h,
    rw hij,
    rw prod.mk.eta,
  end,
  simp only [if_true, eq_self_iff_true, hij],
  rw finsupp.finset_sum_apply,
  simp_rw finsupp.single_apply,
  simp_rw this,
  rw finset.sum_ite_eq',
  simp only [finset.mem_univ, if_true, eq_self_iff_true],
  simp only [hij, if_false],
  rw finsupp.finset_sum_apply,
  simp_rw finsupp.single_apply,
  have : ∀ x : fin n, (perm x, x) ≠ ij :=
  begin
    intros x heq,
    rw prod.ext_iff at heq,
    by_cases h : x = ij.snd,
    rw h at heq,
    simp only [and_true, eq_self_iff_true] at heq,
    exact hij heq,
    simp only [] at heq,
    exact h heq.2,
  end,
  simp only [this, eq_self_iff_true, if_false, finset.sum_const_zero, finset.sum_congr],
end

def perm_graph_supp (perm : equiv.perm (fin n)) : set (fin n × fin n) := {ij | perm (ij.snd) = ij.fst}

noncomputable def perm_monomial {n : ℕ} (perm : equiv.perm (fin n)) : mv_polynomial (fin n × fin n) ℝ :=
mv_polynomial.monomial (perm_graph' perm) 1

noncomputable def det_as_poly' (n : ℕ) : mv_polynomial (fin n × fin n) ℝ :=
∑ p : equiv.perm (fin n), (p.sign : ℝ) • perm_monomial p

noncomputable def poly_map_of_var_map {σ τ : Type*} (f : σ → τ) (p : mv_polynomial σ ℝ) : mv_polynomial τ ℝ :=
p.eval₂ mv_polynomial.C (mv_polynomial.X ∘ f)

lemma poly_map_of_var_map.eval {σ τ : Type*} (f : σ → τ) (v : τ → ℝ) (p : mv_polynomial σ ℝ) :
  mv_polynomial.eval (v ∘ f) p = mv_polynomial.eval v (poly_map_of_var_map f p) :=
begin
  rw poly_map_of_var_map,
  rw mv_polynomial.eval,
  rw mv_polynomial.eval,
  rw mv_polynomial.eval₂_comp_right,
  simp only [mv_polynomial.coe_eval₂_hom],
  simp only [mv_polynomial.eval₂_hom_comp_C, mv_polynomial.eval₂_map],
  congr' 1,
  ext i,
  simp only [eq_self_iff_true, function.comp_app, mv_polynomial.eval₂_X],
end

lemma poly_map_of_var_map.comp {σ τ ρ : Type*} (f : σ → τ) (g : τ → ρ) (p : mv_polynomial σ ℝ) :
  poly_map_of_var_map g (poly_map_of_var_map f p) = poly_map_of_var_map (g ∘ f) p :=
begin
  rw poly_map_of_var_map,
  rw poly_map_of_var_map,
  rw poly_map_of_var_map,
  rw ← mv_polynomial.eval₂_assoc,
  simp,
end

noncomputable def mul_det_as_poly (n N : ℕ) : mv_polynomial (fin N × fin n) ℝ :=
∏ (f : fin n → fin N) in finset.univ.filter function.injective,
  poly_map_of_var_map (prod.map f id) (det_as_poly' n)
  -- (det_as_poly' n).eval₂ mv_polynomial.C (mv_polynomial.X ∘ (prod.map f id))

lemma det_as_poly_eq_det' (n : ℕ) (M : matrix (fin n) (fin n) ℝ) :
 mv_polynomial.eval (function.uncurry M) (det_as_poly' n) = M.det :=
begin
  rw det_as_poly',
  rw mv_polynomial.eval_sum,
  simp_rw [mv_polynomial.smul_eval, perm_monomial, mv_polynomial.eval_monomial],
  simp only [one_mul, finsupp.prod_pow, finset.sum_congr, coe_coe, perm_graph'_apply],
  simp only [finset.prod_congr, pow_boole, finset.sum_congr],
  rw matrix.det_apply,
  congr' 1,
  ext p,
  rw ← finset.univ_product_univ,
  simp_rw function.uncurry,
  let f : fin n → fin n → ℝ := λ i j, ite (p j = i) (M i j) 1,
  have : ∀ i j : fin n, f i j = ite (p j = i) (M i j) 1 := λ i j, rfl,
  simp_rw ← this,
  rw @finset.prod_product_right' ℝ (fin n) (fin n) _ finset.univ finset.univ _,
  simp only [f],
  have foo : ∀ (b : ℝ), p.sign • b = (p.sign : ℝ) * b :=
  begin
    intros,
    simp only [coe_coe],
    rw ← zsmul_eq_mul,
    congr,
  end,
  rw [foo, coe_coe],
  congr' 2,
  ext,
  rw finset.prod_ite_eq,
  simp only [finset.mem_univ, if_true, eq_self_iff_true],
end

lemma mul_det_poly_eval (n N : ℕ) (M : matrix (fin N) (fin n) ℝ) :
  mv_polynomial.eval (function.uncurry M) (mul_det_as_poly n N) =
  ∏ (f : fin n → fin N) in finset.univ.filter function.injective, matrix.det (M ∘ f) :=
begin
  rw mul_det_as_poly,
  rw mv_polynomial.eval_prod,
  simp_rw ← poly_map_of_var_map.eval,
  simp_rw ← det_as_poly_eq_det',
  congr' 1,
end
-- instance : topological_space (fin n) := ⊥
-- instance : discrete_topology (fin n) := ⟨ rfl ⟩

variables {N : ℕ}
-- noncomputable instance foo : semi_normed_group (matrix (fin n) (fin n) ℝ) := matrix.semi_normed_group
noncomputable instance foo2 : normed_group (matrix (fin n) (fin n) ℝ) := matrix.normed_group
noncomputable instance foo3 : normed_group (matrix (fin N) (fin n) ℝ) := matrix.normed_group

def matrix_homeomorph_prod_fun (n N: ℕ) : matrix (fin N) (fin n) ℝ ≃ₜ (fin N × fin n → ℝ) :=
{
  to_equiv :={
  to_fun := function.uncurry,
  inv_fun := function.curry,
  left_inv := λ x, by {rw function.curry_uncurry},
  right_inv := λ x, by {rw function.uncurry_curry},
  },
  continuous_to_fun :=
  begin
    simp only [],
    rw metric.continuous_iff,
    intros,
    use ε,
    split,
    exact H,
    intros a ha,
    rw dist_eq_norm,
    simp only [norm],
    rw dist_eq_norm at ha,
    simp_rw [pi.sub_apply, function.uncurry_def],
    rw ← real.coe_to_nnreal ε (le_of_lt H),
    rw nnreal.coe_lt_coe,
    rw finset.sup_lt_iff,
    intros ij _,
    rw pi_norm_lt_iff H at ha,
    specialize ha ij.fst,
    rw pi.sub_apply at ha,
    rw pi_norm_lt_iff H at ha,
    specialize ha ij.snd,
    rw real.norm_eq_abs at ha,
    rw ← nnreal.coe_lt_coe,
    simp only [real.coe_to_nnreal', lt_max_iff, coe_nnnorm, real.norm_eq_abs],
    left,
    exact ha,
    rw ← nnreal.coe_lt_coe,
    simp only [lt_self_iff_false, real.coe_to_nnreal', nonneg.coe_zero, lt_max_iff, bot_eq_zero, or_false],
    exact H,
  end,
  continuous_inv_fun :=
  begin
    simp only [],
    rw metric.continuous_iff,
    intros,
    use ε,
    split,
    exact H,
    intros a ha,
    rw dist_eq_norm,
    rw dist_eq_norm at ha,
    simp only [norm] at ha,
    simp_rw [pi.sub_apply] at ha,
    rw pi_norm_lt_iff H,
    intro i,
    rw pi_norm_lt_iff H,
    intro j,
    simp_rw [pi.sub_apply],
    rw real.norm_eq_abs,
    rw ← real.coe_to_nnreal ε (le_of_lt H) at ha,
    rw nnreal.coe_lt_coe at ha,
    rw finset.sup_lt_iff at ha,
    specialize ha (i, j),
    simp_rw function.curry_apply,
    simp_rw ← nnreal.coe_lt_coe at ha,
    simp only [lt_self_iff_false, real.coe_to_nnreal', nonneg.coe_zero, lt_max_iff, bot_eq_zero, or_false, coe_nnnorm, real.norm_eq_abs, abs_nonneg] at ha,
    specialize ha (finset.mem_univ _),
    have : ¬ |a (i, j) - b (i, j) | < 0 := by {simp only [not_lt], exact abs_nonneg _},
    simp only [this, or_false] at ha,
    exact ha,
    simp only [real.to_nnreal_pos, bot_eq_zero],
    exact H,
  end,
}

lemma matrix_homeomorph_prod_fun.symm_apply {n N : ℕ} (σ : (fin N) × (fin n) → ℝ) : (matrix_homeomorph_prod_fun n N).symm σ = function.curry σ := rfl
lemma matrix_homeomorph_prod_fun.apply {n N : ℕ} (m : matrix (fin N) (fin n) ℝ) : (matrix_homeomorph_prod_fun n N) m = function.uncurry m := rfl

lemma matrix_curry_map_nonzero (n N : ℕ)
  (f : (fin N → fin n → ℝ) → ℝ) :
  {σ : fin N × fin n → ℝ | f (function.curry σ) ≠ 0} =
    ⇑(matrix_homeomorph_prod_fun n N) ''
      {m : matrix (fin N) (fin n) ℝ | f m ≠ 0} :=
begin
  rw set.image,
  ext,
  iterate{rw set.mem_set_of_eq,},
  split,
  intro h,
  use (matrix_homeomorph_prod_fun n N).symm x,
  rw set.mem_set_of_eq,
  simp only [homeomorph.apply_symm_apply, and_true, eq_self_iff_true],
  rw matrix_homeomorph_prod_fun.symm_apply,
  exact h,
  intro h,
  choose a ha using h,
  rw ← ha.2,
  rw set.mem_set_of_eq at ha,
  rw matrix_homeomorph_prod_fun.apply,
  simp only [function.curry_uncurry],
  exact ha.1,
end

lemma matrix_dense_iff_uncurry_dense (n N : ℕ) (f : ((fin N) → (fin n) → ℝ) → ℝ) :
  dense {m : matrix (fin N) (fin n) ℝ | f m ≠ 0} ↔ dense {σ : fin N × fin n → ℝ | f (function.curry σ) ≠ 0} :=
begin
  -- simp [(matrix_homeomorph_prod_fun n)],
  iterate {rw dense,},
  rw matrix_curry_map_nonzero n,
  rw ← homeomorph.image_closure (matrix_homeomorph_prod_fun n N) _,
  simp_rw set.mem_image,
  split,
  intro h,
  intro x,
  use (matrix_homeomorph_prod_fun n N).symm x,
  specialize h ((matrix_homeomorph_prod_fun n N).symm x),
  simp only [homeomorph.apply_symm_apply, and_true, eq_self_iff_true],
  exact h,
  intros h x,
  specialize h (matrix_homeomorph_prod_fun n N x),
  choose y hy using h,
  rw (matrix_homeomorph_prod_fun n N).injective.eq_iff at hy,
  rw ← hy.2,
  exact hy.1,
end

lemma nonzero_det_dense (n : ℕ) : dense {M : matrix (fin n) (fin n) ℝ | M.det ≠ 0} :=
begin
  simp_rw [← det_as_poly_eq_det' n _],
  rw matrix_dense_iff_uncurry_dense,
  simp_rw function.uncurry_curry,
  apply mvpoly_nonzero_dense,
  intro h,
  rw mv_polynomial.funext_iff at h,
  specialize h (function.uncurry (1 : matrix (fin n) (fin n) ℝ)),
  rw [det_as_poly_eq_det', map_zero, matrix.det_one] at h,
  exact one_ne_zero h,
end

lemma det_ne_zero_iff_cols_linear_independent {n : ℕ} (M : matrix (fin n) (fin n) ℝ) :
  M.det ≠ 0 ↔ linear_independent ℝ (λ i : fin n, M i) :=
begin
  rw ← matrix.nondegenerate_iff_det_ne_zero,
  rw fintype.linear_independent_iff,
  split,
  intro h,
  intros g hg,
  have : (∀ (w : fin n → ℝ), g ⬝ᵥ M.mul_vec w = 0) → g = 0 := matrix.nondegenerate.eq_zero_of_ortho h,
  suffices H : g = 0,
  rw H,
  simp only [pi.zero_apply, eq_self_iff_true, implies_true_iff],
  apply this,
  intro w,
  rw matrix.dot_product_mul_vec,
  have : matrix.vec_mul g M = 0 :=
  begin
    ext,
    simp only [pi.zero_apply],
    rw matrix.vec_mul,
    rw matrix.dot_product,
    rw function.funext_iff at hg,
    specialize hg x,
    simp only [algebra.id.smul_eq_mul,
 matrix.transpose_apply,
 pi.zero_apply,
 fintype.sum_apply,
 pi.smul_apply,
 finset.sum_congr] at hg,
 exact hg,
  end,
  rw this,
  simp only [eq_self_iff_true, matrix.zero_dot_product],
  intro H,
  intro v,
  specialize H v,
  intro hv,
  rw function.funext_iff,
  apply H,
  rw function.funext_iff,
  intro a,
  simp only [algebra.id.smul_eq_mul, pi.zero_apply, fintype.sum_apply, pi.smul_apply, finset.sum_congr],
  specialize hv (pi.single a 1),
  rw matrix.dot_product_mul_vec at hv,
  rw matrix.dot_product at hv,
  have : ∀ i : fin n, matrix.vec_mul v M i * pi.single a 1 i = pi.single a (matrix.vec_mul v M a) i :=
  begin
    intro i,
    by_cases hi : a = i,
    rw hi,
    simp only [mul_one, eq_self_iff_true, pi.single_eq_same],
    iterate{rw pi.single_eq_of_ne' hi,},
    rw mul_zero,
  end,
  simp_rw this at hv,
  rw finset.sum_pi_single' a (matrix.vec_mul v M a) _ at hv,
  simp only [finset.mem_univ, if_true] at hv,
  simpa using hv,
end

lemma exists_nearby_matrix (M : matrix (fin n) (fin n) ℝ) (ε : ℝ) (hε : ε > 0):
  ∃ M' : matrix (fin n) (fin n) ℝ, M'.det ≠ 0 ∧ dist M M' < ε :=
begin
  have matrix_nonzero_dense := nonzero_det_dense n,
  rw dense at matrix_nonzero_dense,
  specialize matrix_nonzero_dense M,
  rw metric.mem_closure_iff at matrix_nonzero_dense,
  specialize matrix_nonzero_dense ε hε,
  simp_rw set.mem_set_of_eq at matrix_nonzero_dense,
  simpa using matrix_nonzero_dense,
end

noncomputable def nonzero_det_matrix_nearby {n : ℕ} (M : matrix (fin n) (fin n) ℝ) (ε : ℝ) (hε : ε > 0) :
  matrix (fin n) (fin n) ℝ :=
classical.some (exists_nearby_matrix M ε hε)

lemma nonzero_det_matrix_nearby_apply {n : ℕ} (M : matrix (fin n) (fin n) ℝ) (ε : ℝ) (hε : ε > 0) :
  (nonzero_det_matrix_nearby M ε hε).det ≠ 0 :=
(classical.some_spec (exists_nearby_matrix M ε hε)).1

lemma nonzero_det_matrix_nearby_apply' {n : ℕ} (M : matrix (fin n) (fin n) ℝ) (ε : ℝ) (hε : ε > 0) :
  dist M (nonzero_det_matrix_nearby M ε hε) < ε :=
(classical.some_spec (exists_nearby_matrix M ε hε)).2

-- Warm up? (Not necessary)
noncomputable def perturbations' (ε : ℝ) (hε : ε > 0) (u : matrix (fin n) (fin n) ℝ) : matrix (fin n) (fin n) ℝ :=
nonzero_det_matrix_nearby u ε hε - u

lemma perturbations'_apply (ε : ℝ) (hε : ε > 0) (u : matrix (fin n) (fin n) ℝ) :
  linear_independent ℝ (λ i : fin n, uᵀ i + (perturbations' ε hε u)ᵀ i) :=
begin
  rw perturbations',
  rw ← det_ne_zero_iff_cols_linear_independent,
  simpa using (nonzero_det_matrix_nearby_apply _ _ _),
end

lemma perturbations'_bound (ε : ℝ) (hε : ε > 0) (u : matrix (fin n) (fin n) ℝ) :
  ∀ i : fin n, ∥ perturbations' ε hε u i ∥ ≤ ε :=
begin
  rw perturbations',
  have : dist u (nonzero_det_matrix_nearby u ε hε) < ε := nonzero_det_matrix_nearby_apply' _ _ _,
  rw dist_eq_norm at this,
  rw pi_norm_lt_iff hε at this,
  intro i,
  specialize this i,
  rw pi.sub_apply,
  rw pi.sub_apply at this,
  rw norm_sub_rev,
  exact le_of_lt this,
end

-- def perturbation_small (ε : ℝ) (u v : fin d → ℝ) : fin d → ℝ  := sorry

-- lemma perturbation_small_apply (ε : ℝ) (u v : fin d → ℝ) :
--   linear_independent ℝ (λ i : bool, if i then u + perturbation_small ε u v else v):=
-- begin

-- end

lemma subset_linear_independent_iff_all_dets_ne_zero {n N : ℕ} (hN : n ≤ N) (v : fin N → fin n → ℝ) :
  (∀ f : fin n → fin N, function.injective f → linear_independent ℝ (v ∘ f)) ↔
    ∏ (f : fin n → fin N) in finset.univ.filter function.injective, matrix.det (v ∘ f) ≠ 0 :=
begin
  rw finset.prod_ne_zero_iff,
  split,
  intros h a,
  rw finset.mem_filter,
  simp only [finset.mem_univ, true_and],
  intro ha,
  specialize h a ha,
  rw det_ne_zero_iff_cols_linear_independent,
  exact h,
  intros h f hf,
  rw ← det_ne_zero_iff_cols_linear_independent,
  specialize h f,
  rw finset.mem_filter at h,
  simp only [finset.mem_univ, true_and] at h,
  specialize h hf,
  exact h,
end

-- lemma set_of_sub_matrix_eq_set_of_matrix {n N : ℕ} (hN : n ≤ N) (s : fin n → fin N) (hs : function.injective s) :
--   { M : matrix (fin N) (fin n) ℝ }

lemma det_ne_zero_dense_for_Nn_matrix {n N : ℕ} (hn : 0 < n) (hN : n ≤ N) :
  dense { M : matrix (fin N) (fin n) ℝ | ∏ (f : fin n → fin N) in finset.univ.filter function.injective, matrix.det (M ∘ f) ≠ 0 } :=
begin
  simp_rw ← mul_det_poly_eval,
  rw matrix_dense_iff_uncurry_dense,
  simp_rw function.uncurry_curry,
  apply mvpoly_nonzero_dense,
  rw mul_det_as_poly,
  rw finset.prod_ne_zero_iff,
  intros f hf hzero,

  haveI := fin.pos_iff_nonempty.1 hn,
  rw mv_polynomial.funext_iff at hzero,
  simp only [true_and, finset.mem_univ, finset.mem_filter] at hf,
  rw function.injective_iff_has_left_inverse at hf,
  choose f_inv hf_inv using hf,

  let one := (function.uncurry (1 : matrix (fin n) (fin n) ℝ)) ∘ (prod.map f_inv id),
  specialize hzero one,
  rw poly_map_of_var_map.eval at hzero,
  rw poly_map_of_var_map.comp at hzero,
  rw prod.map_comp_map at hzero,
  rw function.left_inverse_iff_comp.1 hf_inv at hzero,
  simp only [function.comp.right_id, map_zero] at hzero,
  have : prod.map (id : fin n → fin n) (id : fin n → fin n) = id :=
  begin
    ext,
    simp only [prod.mk.eta, id.def, prod_map, eq_self_iff_true],
    simp only [prod.mk.eta, id.def, prod_map, eq_self_iff_true],
  end,
  rw this at hzero,
  rw poly_map_of_var_map at hzero,
  simp only [function.comp.right_id, mv_polynomial.eval₂_eta] at hzero,
  rw det_as_poly_eq_det' at hzero,
  rw matrix.det_one at hzero,
  exact one_ne_zero hzero,
end

lemma exists_nearby_wide_matrix (hn : 0 < n) (hN : n ≤ N) (M : matrix (fin N) (fin n) ℝ) (ε : ℝ) (hε : ε > 0):
  ∃ M' : matrix (fin N) (fin n) ℝ,
  ∏ (f : fin n → fin N) in finset.univ.filter function.injective, matrix.det (M' ∘ f) ≠ 0 ∧ dist M M' < ε :=
begin
  have matrix_dense := det_ne_zero_dense_for_Nn_matrix hn hN,
  rw dense at matrix_dense,
  specialize matrix_dense M,
  rw metric.mem_closure_iff at matrix_dense,
  specialize matrix_dense ε hε,
  simp_rw set.mem_set_of_eq at matrix_dense,
  choose M' hM' using matrix_dense,
  use M',
  exact hM',
end

noncomputable def nonzero_wide_det_matrix_nearby {n : ℕ} (hn : 0 < n) (hN : n ≤ N) (M : matrix (fin N) (fin n) ℝ) (ε : ℝ) (hε : ε > 0) :
  matrix (fin N) (fin n) ℝ :=
classical.some (exists_nearby_wide_matrix hn hN M ε hε)

lemma nonzero_wide_det_matrix_nearby_apply {n : ℕ} (hn : 0 < n) (hN : n ≤ N) (M : matrix (fin N) (fin n) ℝ) (ε : ℝ) (hε : ε > 0)  :
  ∏ (f : fin n → fin N) in finset.univ.filter function.injective, matrix.det ((nonzero_wide_det_matrix_nearby hn hN M ε hε) ∘ f) ≠ 0 :=
(classical.some_spec (exists_nearby_wide_matrix hn hN M ε hε)).1

lemma nonzero_wide_det_matrix_nearby_apply' {n : ℕ} (hn : 0 < n) (hN : n ≤ N) (M : matrix (fin N) (fin n) ℝ) (ε : ℝ) (hε : ε > 0)  :
  dist M (nonzero_wide_det_matrix_nearby hn hN M ε hε) < ε :=
(classical.some_spec (exists_nearby_wide_matrix hn hN M ε hε)).2

--What we actually need:
noncomputable def perturbations (hn : 0 < n) (hN : n ≤ N) (u : fin N → fin n → ℝ) (ε : ℝ) (hε : ε > 0) : fin N → fin n → ℝ :=
nonzero_wide_det_matrix_nearby hn hN u ε hε - u

/-- The perturbations make -/
lemma perturbations_apply (hn : 0 < n) (hN : n ≤ N) (ε : ℝ) (hε : ε > 0) (u : fin N → fin n → ℝ) {f : (fin n) → (fin N)} (hf : function.injective f) :
  linear_independent ℝ (λ i : fin n, u (f i) + perturbations hn hN u ε hε (f i)) :=
begin
  rw perturbations,
  simp,
  rw ← det_ne_zero_iff_cols_linear_independent,
  have := nonzero_wide_det_matrix_nearby_apply hn hN u ε hε,
  simp_rw [finset.prod_ne_zero_iff, finset.mem_filter, finset.mem_univ, true_and] at this,
  specialize this f hf,
  exact this,
end

lemma perturbations_bound (hn : 0 < n) (hN : n ≤ N) (ε : ℝ) (hε : ε > 0) (u : fin N → fin n → ℝ) :
∀ i : fin N, ∥ perturbations hn hN u ε hε i ∥ ≤ ε :=
begin
  rw perturbations,
  have := nonzero_wide_det_matrix_nearby_apply' hn hN u ε hε,
  rw dist_eq_norm at this,
  rw pi_norm_lt_iff hε at this,
  simp_rw [pi.sub_apply, norm_sub_rev, ← pi.sub_apply],
  exact (λ i, le_of_lt (this i)),
end


import set_tactic.make_ring
namespace freealg

/----------------------------------------------------------------------------------

 Builds a 'free boolean algebra' whose elements are commutative sums of squarefree
 monomials in n-1 indeterminates X₀, X₁, ... with coefficients mod 2. These elements
 are encoded internally as boolean vectors, via a map under which addition is 'xor' and
 multiplication is 'and', both coordinate-wise.

------------------------------------------------------------------------------------/

variables {α : Type*} [boolean_algebra α]

def freealg : nat → Type
| 0 := bool
| (n+1) := (freealg n) × (freealg n)

def zero : forall {n : nat}, (freealg n)
| 0 := ff
| (n+1) := (zero, zero)

def one : forall {n : nat}, (freealg n)
| 0 := tt
| (n+1) := (one, one)

def var : forall {n : nat} (i : nat), (i < n) → (freealg n)
| 0 i Hi := false.elim (nat.not_lt_zero i Hi)
| (n+1) 0 Hi := (one, zero)
| (n+1) (i+1) Hi := let coeff : freealg n := var i (nat.lt_of_succ_lt_succ Hi) in (coeff, coeff)

def add : forall {n : nat}, (freealg n) → (freealg n) → (freealg n)
| 0 a b := bxor a b
| (n+1) a b := (add a.1 b.1, add a.2 b.2)

def mul : forall {n : nat}, (freealg n) → (freealg n) → (freealg n)
| 0 a b := band a b
| (n+1) a b := (mul a.1 b.1, mul a.2 b.2)

def map : forall {n : nat} (V : vector α n), (freealg n) → α
| 0 V ff := 0
| 0 V tt := 1
| (n+1) V a := (map V.tail a.1) * V.head + (map V.tail a.2) * (V.head + 1)

lemma on_zero : forall {n : nat} (V : vector α n),
0 = map V zero
  | 0 V := rfl
  | (n+1) V := calc
      0   = 0 * V.head + 0 * (V.head + 1)                                 : by ring
      ... = (map V.tail zero) * V.head + (map V.tail zero) * (V.head + 1) : by rw on_zero

lemma on_one : forall {n : nat} (V : vector α n),
1 = map V one
  | 0 V := rfl
  | (n+1) V := calc
      1   = V.head + (V.head + 1)                                       : (add_self_left _ _).symm
      ... = 1 * V.head + 1 * (V.head + 1)                               : by ring
      ... = (map V.tail one) * V.head + (map V.tail one) * (V.head + 1) : by rw on_one

lemma on_var : forall {n : nat} (V : vector α n) (i : nat) (Hi : i < n),
V.nth ⟨i, Hi⟩ = map V (var i Hi)
  | 0 V i Hi := false.elim (nat.not_lt_zero i Hi)
  | (n+1) V 0 Hi := calc
      V.nth ⟨0, Hi⟩ = V.head                                                       : by simp
      ...           = 1 * V.head + 0 * (V.head + 1)                                : by ring
      ...           = (map V.tail one) * V.head + (map V.tail zero) * (V.head + 1) : by rw [on_zero, on_one]
  | (n+1) V (i+1) Hi := let
      Hip : (i < n) := nat.lt_of_succ_lt_succ Hi,
      tail_var := map V.tail (var i Hip)
      in calc V.nth ⟨i + 1, Hi⟩ = V.tail.nth ⟨i, Hip⟩                         : by rw [vector.nth_tail, fin.succ.equations._eqn_1]
      ...                       = tail_var                                    : on_var _ _ _
      ...                       = _                                           : (add_self_left (tail_var * V.head) _).symm
      ...                       = tail_var * V.head + tail_var * (V.head + 1) : by ring

lemma on_add : forall {n : nat} (V : vector α n) (a b : freealg n),
(map V a) + (map V b) = map V (add a b)
  | 0 V a b :=
      begin
        cases a; cases b; unfold map add bxor; ring,
        exact two_eq_zero,
      end
  | (n+1) V a b :=
      begin
        unfold map add,
        rw [←on_add V.tail a.1 b.1, ←on_add V.tail a.2 b.2],
        ring,
      end

lemma on_mul : forall {n : nat} (V : vector α n) (a b : freealg n),
(map V a) * (map V b) = map V (mul a b)
  | 0 V a b := by cases a; cases b; unfold map add band; ring
  | (n+1) V a b :=
      begin
        unfold map mul,
        rw [←on_mul V.tail a.1 b.1, ←on_mul V.tail a.2 b.2,←expand_product],
      end

end /-namespace-/ freealg


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

! This file was ported from Lean 3 source module data.finsupp.pointwise
! leanprover-community/mathlib commit 68d1483e8a718ec63219f0e227ca3f0140361086
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Finsupp.Defs
import Mathbin.Algebra.Ring.Pi

/-!
# The pointwise product on `finsupp`.

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

For the convolution product on `finsupp` when the domain has a binary operation,
see the type synonyms `add_monoid_algebra`
(which is in turn used to define `polynomial` and `mv_polynomial`)
and `monoid_algebra`.
-/

noncomputable section

open Finset

universe u₁ u₂ u₃ u₄ u₅

variable {α : Type u₁} {β : Type u₂} {γ : Type u₃} {δ : Type u₄} {ι : Type u₅}

namespace Finsupp

/-! ### Declarations about the pointwise product on `finsupp`s -/

section

variable [MulZeroClass β]

/-- The product of `f g : α →₀ β` is the finitely supported function
  whose value at `a` is `f a * g a`. -/
instance : Mul (α →₀ β) :=
  ⟨zipWith (· * ·) (MulZeroClass.mul_zero 0)⟩

/- warning: finsupp.coe_mul -> Finsupp.coe_mul is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : MulZeroClass.{u2} β] (g₁ : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (g₂ : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)), Eq.{succ (max u1 u2)} (α -> β) (coeFn.{succ (max u1 u2), succ (max u1 u2)} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (fun (_x : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) => α -> β) (Finsupp.coeFun.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (instHMul.{max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (Finsupp.hasMul.{u1, u2} α β _inst_1)) g₁ g₂)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (α -> β) (α -> β) (α -> β) (instHMul.{max u1 u2} (α -> β) (Pi.instMul.{u1, u2} α (fun (ᾰ : α) => β) (fun (i : α) => MulZeroClass.toHasMul.{u2} β _inst_1))) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (fun (_x : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) => α -> β) (Finsupp.coeFun.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) g₁) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (fun (_x : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) => α -> β) (Finsupp.coeFun.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) g₂))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : MulZeroClass.{u2} β] (g₁ : Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (g₂ : Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)), Eq.{max (succ u1) (succ u2)} (forall (ᾰ : α), (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) ᾰ) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) α (fun (_x : α) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) _x) (Finsupp.funLike.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (instHMul.{max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (Finsupp.instMulFinsuppToZero.{u1, u2} α β _inst_1)) g₁ g₂)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (forall (ᾰ : α), (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) ᾰ) (forall (ᾰ : α), (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) ᾰ) (forall (ᾰ : α), (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) ᾰ) (instHMul.{max u1 u2} (forall (ᾰ : α), (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) ᾰ) (Pi.instMul.{u1, u2} α (fun (ᾰ : α) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) ᾰ) (fun (i : α) => MulZeroClass.toMul.{u2} ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) i) _inst_1))) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) α (fun (_x : α) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) _x) (Finsupp.funLike.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) g₁) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) α (fun (_x : α) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) _x) (Finsupp.funLike.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) g₂))
Case conversion may be inaccurate. Consider using '#align finsupp.coe_mul Finsupp.coe_mulₓ'. -/
theorem coe_mul (g₁ g₂ : α →₀ β) : ⇑(g₁ * g₂) = g₁ * g₂ :=
  rfl
#align finsupp.coe_mul Finsupp.coe_mul

/- warning: finsupp.mul_apply -> Finsupp.mul_apply is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : MulZeroClass.{u2} β] {g₁ : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)} {g₂ : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)} {a : α}, Eq.{succ u2} β (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (fun (_x : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) => α -> β) (Finsupp.coeFun.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (instHMul.{max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (Finsupp.hasMul.{u1, u2} α β _inst_1)) g₁ g₂) a) (HMul.hMul.{u2, u2, u2} β β β (instHMul.{u2} β (MulZeroClass.toHasMul.{u2} β _inst_1)) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (fun (_x : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) => α -> β) (Finsupp.coeFun.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) g₁ a) (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (fun (_x : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) => α -> β) (Finsupp.coeFun.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) g₂ a))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : MulZeroClass.{u2} β] {g₁ : Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)} {g₂ : Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)} {a : α}, Eq.{succ u2} ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) a) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) α (fun (_x : α) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) _x) (Finsupp.funLike.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (instHMul.{max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (Finsupp.instMulFinsuppToZero.{u1, u2} α β _inst_1)) g₁ g₂) a) (HMul.hMul.{u2, u2, u2} ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) a) ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) a) ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) a) (instHMul.{u2} ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) a) (MulZeroClass.toMul.{u2} ((fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) a) _inst_1)) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) α (fun (_x : α) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) _x) (Finsupp.funLike.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) g₁ a) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) α (fun (_x : α) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) _x) (Finsupp.funLike.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) g₂ a))
Case conversion may be inaccurate. Consider using '#align finsupp.mul_apply Finsupp.mul_applyₓ'. -/
@[simp]
theorem mul_apply {g₁ g₂ : α →₀ β} {a : α} : (g₁ * g₂) a = g₁ a * g₂ a :=
  rfl
#align finsupp.mul_apply Finsupp.mul_apply

/- warning: finsupp.support_mul -> Finsupp.support_mul is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : MulZeroClass.{u2} β] [_inst_2 : DecidableEq.{succ u1} α] {g₁ : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)} {g₂ : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)}, HasSubset.Subset.{u1} (Finset.{u1} α) (Finset.hasSubset.{u1} α) (Finsupp.support.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (instHMul.{max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1)) (Finsupp.hasMul.{u1, u2} α β _inst_1)) g₁ g₂)) (Inter.inter.{u1} (Finset.{u1} α) (Finset.hasInter.{u1} α (fun (a : α) (b : α) => _inst_2 a b)) (Finsupp.support.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1) g₁) (Finsupp.support.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β _inst_1) g₂))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : MulZeroClass.{u2} β] [_inst_2 : DecidableEq.{succ u1} α] {g₁ : Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)} {g₂ : Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)}, HasSubset.Subset.{u1} (Finset.{u1} α) (Finset.instHasSubsetFinset.{u1} α) (Finsupp.support.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1) (HMul.hMul.{max u1 u2, max u1 u2, max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (instHMul.{max u1 u2} (Finsupp.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1)) (Finsupp.instMulFinsuppToZero.{u1, u2} α β _inst_1)) g₁ g₂)) (Inter.inter.{u1} (Finset.{u1} α) (Finset.instInterFinset.{u1} α (fun (a : α) (b : α) => _inst_2 a b)) (Finsupp.support.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1) g₁) (Finsupp.support.{u1, u2} α β (MulZeroClass.toZero.{u2} β _inst_1) g₂))
Case conversion may be inaccurate. Consider using '#align finsupp.support_mul Finsupp.support_mulₓ'. -/
theorem support_mul [DecidableEq α] {g₁ g₂ : α →₀ β} :
    (g₁ * g₂).support ⊆ g₁.support ∩ g₂.support :=
  by
  intro a h
  simp only [mul_apply, mem_support_iff] at h
  simp only [mem_support_iff, mem_inter, Ne.def]
  rw [← not_or]
  intro w
  apply h
  cases w <;>
    · rw [w]
      simp
#align finsupp.support_mul Finsupp.support_mul

instance : MulZeroClass (α →₀ β) :=
  Finsupp.coeFn_injective.MulZeroClass _ coe_zero coe_mul

end

instance [SemigroupWithZero β] : SemigroupWithZero (α →₀ β) :=
  Finsupp.coeFn_injective.SemigroupWithZero _ coe_zero coe_mul

instance [NonUnitalNonAssocSemiring β] : NonUnitalNonAssocSemiring (α →₀ β) :=
  Finsupp.coeFn_injective.NonUnitalNonAssocSemiring _ coe_zero coe_add coe_mul fun _ _ => rfl

instance [NonUnitalSemiring β] : NonUnitalSemiring (α →₀ β) :=
  Finsupp.coeFn_injective.NonUnitalSemiring _ coe_zero coe_add coe_mul fun _ _ => rfl

instance [NonUnitalCommSemiring β] : NonUnitalCommSemiring (α →₀ β) :=
  Finsupp.coeFn_injective.NonUnitalCommSemiring _ coe_zero coe_add coe_mul fun _ _ => rfl

instance [NonUnitalNonAssocRing β] : NonUnitalNonAssocRing (α →₀ β) :=
  Finsupp.coeFn_injective.NonUnitalNonAssocRing _ coe_zero coe_add coe_mul coe_neg coe_sub
    (fun _ _ => rfl) fun _ _ => rfl

instance [NonUnitalRing β] : NonUnitalRing (α →₀ β) :=
  Finsupp.coeFn_injective.NonUnitalRing _ coe_zero coe_add coe_mul coe_neg coe_sub (fun _ _ => rfl)
    fun _ _ => rfl

instance [NonUnitalCommRing β] : NonUnitalCommRing (α →₀ β) :=
  Finsupp.coeFn_injective.NonUnitalCommRing _ coe_zero coe_add coe_mul coe_neg coe_sub
    (fun _ _ => rfl) fun _ _ => rfl

/- warning: finsupp.pointwise_scalar -> Finsupp.pointwiseScalar is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Semiring.{u2} β], SMul.{max u1 u2, max u1 u2} (α -> β) (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1)))))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Semiring.{u2} β], SMul.{max u1 u2, max u2 u1} (α -> β) (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1)))
Case conversion may be inaccurate. Consider using '#align finsupp.pointwise_scalar Finsupp.pointwiseScalarₓ'. -/
-- TODO can this be generalized in the direction of `pi.has_smul'`
-- (i.e. dependent functions and finsupps)
-- TODO in theory this could be generalised, we only really need `smul_zero` for the definition
instance pointwiseScalar [Semiring β] : SMul (α → β) (α →₀ β)
    where smul f g :=
    Finsupp.ofSupportFinite (fun a => f a • g a)
      (by
        apply Set.Finite.subset g.finite_support
        simp only [Function.support_subset_iff, Finsupp.mem_support_iff, Ne.def,
          Finsupp.fun_support_eq, Finset.mem_coe]
        intro x hx h
        apply hx
        rw [h, smul_zero])
#align finsupp.pointwise_scalar Finsupp.pointwiseScalar

/- warning: finsupp.coe_pointwise_smul -> Finsupp.coe_pointwise_smul is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Semiring.{u2} β] (f : α -> β) (g : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))), Eq.{succ (max u1 u2)} (α -> β) (coeFn.{succ (max u1 u2), succ (max u1 u2)} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))) (fun (_x : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))) => α -> β) (Finsupp.coeFun.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))) (SMul.smul.{max u1 u2, max u1 u2} (α -> β) (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))) (Finsupp.pointwiseScalar.{u1, u2} α β _inst_1) f g)) (SMul.smul.{max u1 u2, max u1 u2} (α -> β) (α -> β) (Pi.smul'.{u1, u2, u2} α (fun (ᾰ : α) => β) (fun (ᾰ : α) => β) (fun (i : α) => Mul.toSMul.{u2} β (Distrib.toHasMul.{u2} β (NonUnitalNonAssocSemiring.toDistrib.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1)))))) f (coeFn.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))) (fun (_x : Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))) => α -> β) (Finsupp.coeFun.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))) g))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Semiring.{u2} β] (f : α -> β) (g : Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))), Eq.{max (succ u1) (succ u2)} (α -> β) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) α (fun (_x : α) => β) (Finsupp.funLike.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) (HSMul.hSMul.{max u1 u2, max u1 u2, max u1 u2} (α -> β) (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) (instHSMul.{max u1 u2, max u1 u2} (α -> β) (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) (Finsupp.pointwiseScalar.{u1, u2} α β _inst_1)) f g)) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) α (fun (a : α) => (fun (x._@.Mathlib.Data.Finsupp.Defs._hyg.779 : α) => β) a) (Finsupp.funLike.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) (HSMul.hSMul.{max u1 u2, max u1 u2, max u1 u2} (α -> β) (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) (instHSMul.{max u1 u2, max u1 u2} (α -> β) (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) (Finsupp.pointwiseScalar.{u1, u2} α β _inst_1)) f g))
Case conversion may be inaccurate. Consider using '#align finsupp.coe_pointwise_smul Finsupp.coe_pointwise_smulₓ'. -/
@[simp]
theorem coe_pointwise_smul [Semiring β] (f : α → β) (g : α →₀ β) : ⇑(f • g) = f • g :=
  rfl
#align finsupp.coe_pointwise_smul Finsupp.coe_pointwise_smul

/- warning: finsupp.pointwise_module -> Finsupp.pointwiseModule is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Semiring.{u2} β], Module.{max u1 u2, max u1 u2} (α -> β) (Finsupp.{u1, u2} α β (MulZeroClass.toHasZero.{u2} β (NonUnitalNonAssocSemiring.toMulZeroClass.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))) (Pi.semiring.{u1, u2} α (fun (ᾰ : α) => β) (fun (i : α) => _inst_1)) (Finsupp.addCommMonoid.{u1, u2} α β (NonUnitalNonAssocSemiring.toAddCommMonoid.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Semiring.{u2} β], Module.{max u1 u2, max u2 u1} (α -> β) (Finsupp.{u1, u2} α β (MonoidWithZero.toZero.{u2} β (Semiring.toMonoidWithZero.{u2} β _inst_1))) (Pi.semiring.{u1, u2} α (fun (ᾰ : α) => β) (fun (i : α) => _inst_1)) (Finsupp.addCommMonoid.{u1, u2} α β (NonUnitalNonAssocSemiring.toAddCommMonoid.{u2} β (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u2} β (Semiring.toNonAssocSemiring.{u2} β _inst_1))))
Case conversion may be inaccurate. Consider using '#align finsupp.pointwise_module Finsupp.pointwiseModuleₓ'. -/
/-- The pointwise multiplicative action of functions on finitely supported functions -/
instance pointwiseModule [Semiring β] : Module (α → β) (α →₀ β) :=
  Function.Injective.module _ coeFnAddHom coeFn_injective coe_pointwise_smul
#align finsupp.pointwise_module Finsupp.pointwiseModule

end Finsupp


/-
Copyright (c) 2020 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen, Devon Tuma
-/

import ring_theory.polynomial.basic
import ring_theory.non_zero_divisors

/-!
# Scaling the roots of a polynomial

This file defines `scale_roots p s` for a polynomial `p` in one variable and a ring element `s` to
be the polynomial with root `r * s` for each root `r` of `p` and proves some basic results about it.
-/

section scale_roots

variables {A K R S : Type*} [integral_domain A] [field K] [comm_ring R] [comm_ring S]
variables {M : submonoid A}

open finsupp polynomial
open_locale big_operators

/-- `scale_roots p s` is a polynomial with root `r * s` for each root `r` of `p`. -/
noncomputable def scale_roots (p : polynomial R) (s : R) : polynomial R :=
∑ i in p.support, monomial i (p.coeff i * s ^ (p.nat_degree - i))

@[simp] lemma coeff_scale_roots (p : polynomial R) (s : R) (i : ℕ) :
  (scale_roots p s).coeff i = coeff p i * s ^ (p.nat_degree - i) :=
by simp [scale_roots, coeff_monomial] {contextual := tt}

@[simp] lemma zero_scale_roots (s : R) : scale_roots 0 s = 0 := by { ext, simp }

lemma scale_roots_ne_zero {p : polynomial R} (hp : p ≠ 0) (s : R) :
  scale_roots p s ≠ 0 :=
begin
  intro h,
  have : p.coeff p.nat_degree ≠ 0 := mt leading_coeff_eq_zero.mp hp,
  have : (scale_roots p s).coeff p.nat_degree = 0 :=
    congr_fun (congr_arg (coeff : polynomial R → ℕ → R) h) p.nat_degree,
  rw [coeff_scale_roots_nat_degree] at this,
  contradiction
end

lemma support_scale_roots_le (p : polynomial R) (s : R) :
  (scale_roots p s).support ≤ p.support :=
by { intro, simpa using left_ne_zero_of_mul }

lemma support_scale_roots_eq (p : polynomial R) {s : R} (hs : s ∈ non_zero_divisors R) :
  (scale_roots p s).support = p.support :=
le_antisymm (support_scale_roots_le p s)
  begin
    intro i,
    simp only [coeff_scale_roots, polynomial.mem_support_iff],
    intros p_ne_zero ps_zero,
    have := ((non_zero_divisors R).pow_mem hs (p.nat_degree - i)) _ ps_zero,
    contradiction
  end

@[simp] lemma degree_scale_roots (p : polynomial R) {s : R} :
  degree (scale_roots p s) = degree p :=
begin
  haveI := classical.prop_decidable,
  by_cases hp : p = 0,
  { rw [hp, zero_scale_roots] },
  have := scale_roots_ne_zero hp s,
  refine le_antisymm (finset.sup_mono (support_scale_roots_le p s)) (degree_le_degree _),
  rw coeff_scale_roots_nat_degree,
  intro h,
  have := leading_coeff_eq_zero.mp h,
  contradiction,
end

@[simp] lemma nat_degree_scale_roots (p : polynomial R) (s : R) :
  nat_degree (scale_roots p s) = nat_degree p :=
by simp only [nat_degree, degree_scale_roots]

lemma monic_scale_roots_iff {p : polynomial R} (s : R) :
  monic (scale_roots p s) ↔ monic p :=
by simp only [monic, leading_coeff, nat_degree_scale_roots, coeff_scale_roots_nat_degree]

lemma scale_roots_eval₂_eq_zero {p : polynomial S} (f : S →+* R)
  {r : R} {s : S} (hr : eval₂ f r p = 0) :
  eval₂ f (f s * r) (scale_roots p s) = 0 :=
calc eval₂ f (f s * r) (scale_roots p s) =
  (scale_roots p s).support.sum (λ i, f (coeff p i * s ^ (p.nat_degree - i)) * (f s * r) ^ i) :
  by simp [eval₂_eq_sum, sum_def]
... = p.support.sum (λ i, f (coeff p i * s ^ (p.nat_degree - i)) * (f s * r) ^ i) :
  finset.sum_subset (support_scale_roots_le p s)
  (λ i hi hi', let this : coeff p i * s ^ (p.nat_degree - i) = 0 :=
  by simpa using hi' in by simp [this])
... = p.support.sum (λ (i : ℕ), f (p.coeff i) * f s ^ (p.nat_degree - i + i) * r ^ i) :
  finset.sum_congr rfl
  (λ i hi, by simp_rw [f.map_mul, f.map_pow, pow_add, mul_pow, mul_assoc])
... = p.support.sum (λ (i : ℕ), f s ^ p.nat_degree * (f (p.coeff i) * r ^ i)) :
  finset.sum_congr rfl
  (λ i hi, by { rw [mul_assoc, mul_left_comm, nat.sub_add_cancel],
                exact le_nat_degree_of_ne_zero (polynomial.mem_support_iff.mp hi) })
... = f s ^ p.nat_degree * p.support.sum (λ (i : ℕ), (f (p.coeff i) * r ^ i)) : finset.mul_sum.symm
... = f s ^ p.nat_degree * eval₂ f r p : by { simp [eval₂_eq_sum, sum_def] }
... = 0 : by rw [hr, _root_.mul_zero]

lemma scale_roots_aeval_eq_zero [algebra S R] {p : polynomial S}
  {r : R} {s : S} (hr : aeval r p = 0) :
  aeval (algebra_map S R s * r) (scale_roots p s) = 0 :=
scale_roots_eval₂_eq_zero (algebra_map S R) hr

lemma scale_roots_eval₂_eq_zero_of_eval₂_div_eq_zero
  {p : polynomial A} {f : A →+* K} (hf : function.injective f)
  {r s : A} (hr : eval₂ f (f r / f s) p = 0) (hs : s ∈ non_zero_divisors A) :
  eval₂ f (f r) (scale_roots p s) = 0 :=
begin
  convert scale_roots_eval₂_eq_zero f hr,
  rw [←mul_div_assoc, mul_comm, mul_div_cancel],
  exact @map_ne_zero_of_mem_non_zero_divisors _ _ _ _ _ _ hf ⟨s, hs⟩
end

lemma scale_roots_aeval_eq_zero_of_aeval_div_eq_zero [algebra A K]
  (inj : function.injective (algebra_map A K)) {p : polynomial A} {r s : A}
  (hr : aeval (algebra_map A K r / algebra_map A K s) p = 0) (hs : s ∈ non_zero_divisors A) :
  aeval (algebra_map A K r) (scale_roots p s) = 0 :=
scale_roots_eval₂_eq_zero_of_eval₂_div_eq_zero inj hr hs

end scale_roots


import topology.instances.real

open filter real
open_locale topological_space

theorem problem_6 (f : ℕ → ℝ) (hf : ∀ n, f (n + 1) ≤ f n)
  (hf_bdd : ∃ a, ∀ n, f n ≤ a) :
  ∃ a, tendsto f at_top (𝓝 a) :=
begin
  sorry
end


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

open_locale nat rat real big_operators topological_space

theorem mathd_algebra_246
  (a b : ℝ)
  (f : ℝ → ℝ)
  (h₀ : ∀ x, f x = a * x^4 - b * x^2 + x + 5)
  (h₂ : f (-3) = 2) :
  f 3 = 8 :=
begin
  rw h₀ at h₂,
  simp at h₂,
  rw h₀,
  linarith,
end


import Mathlib.Data.Prod
import Mathlib.Logic.Function.Basic

import SciLean.Algebra

namespace SciLean

variable {α β γ : Type}
variable {X Y Z : Type} [Vec X] [Vec Y] [Vec Z]
variable {U V W : Type} [Hilbert U] [Hilbert V] [Hilbert W]

variable (u : U)
variable (r : ℝ)

@[simp] theorem add_zero (x : X) : x + 0 = x := by simp
@[simp] theorem zero_add (x : X) : 0 + x = x := by simp

@[simp] theorem sub_zero (x : X) : x - 0 = x := sorry
@[simp] theorem zero_sub (x : X) : 0 - x = -x := sorry

@[simp] theorem zero_mul (x : X) : (0 : ℝ)*x = (0 : X) := sorry
-- @[simp] theorem mul_zero (r : ℝ) : r*(0:ℝ) = (0 : ℝ) := by simp

@[simp] theorem zero_eval [Zero β] : (0 : α → β) a = 0 := by simp[Zero.zero,OfNat.ofNat]; done

@[simp] theorem mul_one' (x : ℝ) : x * 1 = x := by simp
@[simp] theorem one_mul (x : X) : (1:ℝ) * x = x := sorry
@[simp] theorem neg_one_mul (x : X) : (-1:ℝ) * x = -x := sorry

@[simp] theorem div_one (x : ℝ) : x/1 = x := sorry

-- @[simp] theorem mul_one (r : ℝ) : r * (1:ℝ) = r := sorry

-- @[simp] theorem neg_neg (x : X) : - - x = x := by simp

@[simp] theorem sub_neg (x y : X) : x - (-y) = x + y := sorry
@[simp] theorem add_neg (x y : X) : x + (-y) = x - y := sorry
@[simp] theorem neg_sub (x y : X) : -x - y = -(x + y) := sorry

@[simp] theorem eval_neg (f : α → X) (x : α) : (-f) x = -(f x) := sorry

@[simp] theorem mul_neg_neg (r : ℝ) (x : X) : (-r) * (-x) = r * x := sorry
theorem mul_neg_1 (r : ℝ) (x : X) : (-r) * x = -(r * x) := sorry
theorem mul_neg_2 (r : ℝ) (x : X) : r * (-x) = -(r * x) := sorry
theorem smul_smul_mul (r s: ℝ) (x : X) : r * (s * x) = ((r * s) * x) := sorry

@[simp] theorem pair_mul (r : ℝ) (x : X) (y : Y) : r * (x, y) = (r * x, r * y) := sorry

@[simp] theorem inner_mul_1 {X} [SemiHilbert X] (r : ℝ) (x y : X) Ω : ⟪r * x, y⟫[Ω] = r * ⟪x,y⟫[Ω] := sorry
@[simp] theorem inner_mul_2 {X} [SemiHilbert X] (r : ℝ) (x y : X) Ω : ⟪x, r * y⟫[Ω] = r * ⟪x,y⟫[Ω] := sorry
@[simp] theorem inner_zero1 {X} [SemiHilbert X] (x : X) Ω : ⟪(0 : X), x⟫[Ω] = 0 := sorry
@[simp] theorem inner_zero2 {X} [SemiHilbert X] (x : X) Ω : ⟪x, (0 : X)⟫[Ω] = 0 := sorry

@[simp] theorem inner_prod (u u' : U) (v v' : V) : ⟪(u,v), (u',v')⟫ = ⟪u,u'⟫ + ⟪v,v'⟫ := sorry
@[simp] theorem inner_real (x y : ℝ) : ⟪x, y⟫ = x * y := sorry
@[simp] theorem square_sqrt_square (x : ℝ) : (Math.sqrt (x * x))^2 = x * x := sorry
@[simp] theorem inner_zero_x (x : U) : ⟪0, x⟫ = 0 := sorry
@[simp] theorem inner_x_zero (x : U) : ⟪0, x⟫ = 0 := sorry

@[simp] theorem add_same_1 (a b : ℝ) (x : X) : a*x + b*x = (a+b)*x := sorry
@[simp] theorem add_same_2 (a : ℝ) (x : X) : a*x + x = (a+1)*x := sorry
@[simp] theorem add_same_3 (a : ℝ) (x : X) : x + a*x = (1+a)*x := sorry
@[simp] theorem add_same_4 (x : X) : x + x = (2:ℝ)*x := sorry

-- @[simp] theorem smul_smul_mul (a b : ℝ) (x : X) : a * (b * x) = (a*b) * x := sorry

@[simp] theorem prod_sum (x x' : X) (y y' : Y) : (x, y) + (x', y') = (x + x', y + y') := sorry
@[simp] theorem prod_fst_hmul (x : X) (y : Y) (r : ℝ) : (r*(x,y)).fst = r*x := sorry
@[simp] theorem prod_snd_hmul (x : X) (y : Y) (r : ℝ) : (r*(x,y)).snd = r*y := sorry

@[simp] theorem add_normalize [Add α] (a b : α) : Add.add a b = a + b := by simp[HAdd.hAdd] done
@[simp] theorem sub_normalize [Sub α] (a b : α) : Sub.sub a b = a - b := by simp[HSub.hSub] done
@[simp] theorem mul_normalize [Mul α] (a b : α) : Mul.mul a b = a * b := by simp[HMul.hMul] done
@[simp] theorem div_normalize [Div α] (a b : α) : Div.div a b = a / b := by simp[HDiv.hDiv] done


import YatimaStdLib.Bit
import YatimaStdLib.Nat
import Std.Data.Int.Basic

namespace Int

open Nat hiding bitwise

def toUInt8 (x : Int) : UInt8 := Bit.bitsToUInt8 x.toBits

section bitwise

/-! Some bitwise arithmetics for `Int`s, assuming twos complement. -/

def bdiff a b := a && not b

def bitwise (f : Bool → Bool → Bool) (m' n' : Int) : Int :=
  let go f' m n :=
    if f' false false
      then .negSucc $ Nat.bitwise (fun x y => not $ f' x y) m n
      else .ofNat   $ Nat.bitwise f' m n
  match m', n' with
  | .ofNat m, .ofNat n     => go f m n
  | .ofNat m, .negSucc n   => go (fun x y => f x (not y)) m n
  | .negSucc m, .ofNat n   => go (fun x y => f (not x) y) m n
  | .negSucc m, .negSucc n => go (fun x y => f (not x) (not y)) m n

def lnot : Int → Int
  | .ofNat m   => .negSucc m
  | .negSucc m => .ofNat m

def land : Int → Int → Int
  | .ofNat m,   .ofNat n   => m &&& n
  | .ofNat m,   .negSucc n => .ofNat $ Nat.bitwise bdiff m n
  | .negSucc m, .ofNat n   => .ofNat $ Nat.bitwise bdiff n m
  | .negSucc m, .negSucc n => .negSucc $ m ||| n

def lor : Int → Int → Int
  | .ofNat m,   .ofNat n   => m ||| n
  | .ofNat m,   .negSucc n => .negSucc $ Nat.bitwise bdiff n m
  | .negSucc m, .ofNat n   => .negSucc $ Nat.bitwise bdiff m n
  | .negSucc m, .negSucc n => .negSucc $ m &&& n

def lxor : Int → Int → Int
  | .ofNat m,   .ofNat n   => m ^^^ n
  | .ofNat m,   .negSucc n => .negSucc $ m ^^^ n
  | .negSucc m, .ofNat n   => .negSucc $ m ^^^ n
  | .negSucc m, .negSucc n => m ^^^ n

def shiftLeft : Int → Int → Int
  | .ofNat m,   .ofNat n   => m <<< n
  | .ofNat m,   .negSucc n => m >>> (n+1)
  | .negSucc m, .ofNat n   => .negSucc $ shiftLeft1 m n
  | .negSucc m, .negSucc n => .negSucc $ m >>> (n+1)

def shiftRight m n := shiftLeft m (-n)

instance : AndOp Int := ⟨land⟩
instance : OrOp Int := ⟨lor⟩
instance : Xor Int := ⟨lxor⟩
instance : ShiftLeft  Int := ⟨shiftLeft⟩
instance : ShiftRight Int := ⟨shiftRight⟩

/-- Turn a negative integer into a positive by taking its bit representation
and interpreting it as unsigned. `size` is the number of bits to assume. -/
def unsign (i : Int) (size : Nat := 64) : Int :=
  match i with
  | .ofNat m => m
  | .negSucc _ => i + ((2 : Int) ^ size)

end bitwise

/--
Return `(x, y, g)` where `g` is the greatest common divisor of `a` and `b`, and `x`, `y` satisfy

`x * a + y * b = g`
-/
def gcdExtNat (a : Nat) (b : Nat) : Int × Int × Int :=
  match h : b with
    | 0 => (1, 0, a)
    | k + 1 =>
      let p := quotRem a b
      let q := p.1
      let r := p.2
      have : r < k.succ := by
        have h2 := k.succ_ne_zero
        rw [← h] at *
        apply Nat.mod_lt
        exact Nat.zero_lt_of_ne_zero h2
      let (s, t, g) := gcdExtNat b r
      (t, s - q * t, g)
  termination_by _ => b

def gcdExt (a : Int) (b : Int) : Int × Int × Int :=
  gcdExtNat (Int.natAbs a) (Int.natAbs b)

def modInv (a : Int) (m : Int) : Int :=
  let (i, _, g) := Int.gcdExt a m
  let mkPos (x : Int) := if x < 0 then x + m else x
  if g == 1 then mkPos i else 0

def modToNat : Int → Nat → Nat
  | .ofNat x,   n => x % n
  | .negSucc x, n => n - x % n - 1

theorem modToNat_ofNat : modToNat (ofNat a) n = a % n := rfl
theorem modToNat_negSucc : modToNat (negSucc a) n = n - a % n - 1 := rfl

theorem modToNat_le {n : Nat} : modToNat a n.succ < n.succ := by
  cases a with
  | ofNat x => simp only [modToNat_ofNat, x.mod_lt (n.succ_pos)]
  | negSucc x =>
    let y := x % n.succ
    have : n.succ - y - 1 ≤ n := by
      have : n.succ - y - 1 = n - y := n.add_sub_add_right 1 y
      rw [this]
      exact n.sub_le y
    exact Nat.lt_succ_of_le this

end Int