/-
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.eval
! leanprover-community/mathlib commit 728baa2f54e6062c5879a3e397ac6bac323e506f
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Polynomial.Degree.Definitions
import Mathlib.Data.Polynomial.Induction

/-!
# Theory of univariate polynomials

The main defs here are `eval₂`, `eval`, and `map`.
We give several lemmas about their interaction with each other and with module operations.
-/

set_option linter.uppercaseLean3 false

noncomputable section

open Finset AddMonoidAlgebra

open BigOperators Polynomial

namespace Polynomial

universe u v w y

variable {R : Type u} {S : Type v} {T : Type w} {ι : Type y} {a b : R} {m n : ℕ}

section Semiring

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

section

variable [Semiring S]

variable (f : R →+* S) (x : S)

/-- Evaluate a polynomial `p` given a ring hom `f` from the scalar ring
  to the target and a value `x` for the variable in the target -/
irreducible_def eval₂ (p : R[X]) : S :=
  p.sum fun e a => f a * x ^ e
#align polynomial.eval₂ Polynomial.eval₂

theorem eval₂_eq_sum {f : R →+* S} {x : S} : p.eval₂ f x = p.sum fun e a => f a * x ^ e := by
  rw [eval₂_def]
#align polynomial.eval₂_eq_sum Polynomial.eval₂_eq_sum

theorem eval₂_congr {R S : Type _} [Semiring R] [Semiring S] {f g : R →+* S} {s t : S}
    {φ ψ : R[X]} : f = g → s = t → φ = ψ → eval₂ f s φ = eval₂ g t ψ := by
  rintro rfl rfl rfl; rfl
#align polynomial.eval₂_congr Polynomial.eval₂_congr

@[simp]
theorem eval₂_at_zero : p.eval₂ f 0 = f (coeff p 0) := by
  simp (config := { contextual := true }) only [eval₂_eq_sum, zero_pow_eq, mul_ite, mul_zero,
    mul_one, sum, Classical.not_not, mem_support_iff, sum_ite_eq', ite_eq_left_iff,
    RingHom.map_zero, imp_true_iff, eq_self_iff_true]
#align polynomial.eval₂_at_zero Polynomial.eval₂_at_zero

@[simp]
theorem eval₂_zero : (0 : R[X]).eval₂ f x = 0 := by simp [eval₂_eq_sum]
#align polynomial.eval₂_zero Polynomial.eval₂_zero

@[simp]
theorem eval₂_C : (C a).eval₂ f x = f a := by simp [eval₂_eq_sum]
#align polynomial.eval₂_C Polynomial.eval₂_C

@[simp]
theorem eval₂_X : X.eval₂ f x = x := by simp [eval₂_eq_sum]
#align polynomial.eval₂_X Polynomial.eval₂_X

@[simp]
theorem eval₂_monomial {n : ℕ} {r : R} : (monomial n r).eval₂ f x = f r * x ^ n := by
  simp [eval₂_eq_sum]
#align polynomial.eval₂_monomial Polynomial.eval₂_monomial

@[simp]
theorem eval₂_X_pow {n : ℕ} : (X ^ n).eval₂ f x = x ^ n := by
  rw [X_pow_eq_monomial]
  convert eval₂_monomial f x (n := n) (r := 1)
  simp
#align polynomial.eval₂_X_pow Polynomial.eval₂_X_pow

@[simp]
theorem eval₂_add : (p + q).eval₂ f x = p.eval₂ f x + q.eval₂ f x := by
  simp only [eval₂_eq_sum]
  apply sum_add_index <;> simp [add_mul]
#align polynomial.eval₂_add Polynomial.eval₂_add

@[simp]
theorem eval₂_one : (1 : R[X]).eval₂ f x = 1 := by rw [← C_1, eval₂_C, f.map_one]
#align polynomial.eval₂_one Polynomial.eval₂_one

set_option linter.deprecated false in
@[simp]
theorem eval₂_bit0 : (bit0 p).eval₂ f x = bit0 (p.eval₂ f x) := by rw [bit0, eval₂_add, bit0]
#align polynomial.eval₂_bit0 Polynomial.eval₂_bit0

set_option linter.deprecated false in
@[simp]
theorem eval₂_bit1 : (bit1 p).eval₂ f x = bit1 (p.eval₂ f x) := by
  rw [bit1, eval₂_add, eval₂_bit0, eval₂_one, bit1]
#align polynomial.eval₂_bit1 Polynomial.eval₂_bit1

@[simp]
theorem eval₂_smul (g : R →+* S) (p : R[X]) (x : S) {s : R} :
    eval₂ g x (s • p) = g s * eval₂ g x p := by
  have A : p.natDegree < p.natDegree.succ := Nat.lt_succ_self _
  have B : (s • p).natDegree < p.natDegree.succ := (natDegree_smul_le _ _).trans_lt A
  rw [eval₂_eq_sum, eval₂_eq_sum, sum_over_range' _ _ _ A, sum_over_range' _ _ _ B] <;>
    simp [mul_sum, mul_assoc]
#align polynomial.eval₂_smul Polynomial.eval₂_smul

@[simp]
theorem eval₂_C_X : eval₂ C X p = p :=
  Polynomial.induction_on' p (fun p q hp hq => by simp [hp, hq]) fun n x => by
    rw [eval₂_monomial, ← smul_X_eq_monomial, C_mul']
#align polynomial.eval₂_C_X Polynomial.eval₂_C_X

/-- `eval₂AddMonoidHom (f : R →+* S) (x : S)` is the `AddMonoidHom` from
`R[X]` to `S` obtained by evaluating the pushforward of `p` along `f` at `x`. -/
@[simps]
def eval₂AddMonoidHom : R[X] →+ S where
  toFun := eval₂ f x
  map_zero' := eval₂_zero _ _
  map_add' _ _ := eval₂_add _ _
#align polynomial.eval₂_add_monoid_hom Polynomial.eval₂AddMonoidHom
#align polynomial.eval₂_add_monoid_hom_apply Polynomial.eval₂AddMonoidHom_apply

@[simp]
theorem eval₂_nat_cast (n : ℕ) : (n : R[X]).eval₂ f x = n := by
  induction' n with n ih
  -- Porting note: `Nat.zero_eq` is required.
  · simp only [eval₂_zero, Nat.cast_zero, Nat.zero_eq]
  · rw [n.cast_succ, eval₂_add, ih, eval₂_one, n.cast_succ]
#align polynomial.eval₂_nat_cast Polynomial.eval₂_nat_cast

variable [Semiring T]

theorem eval₂_sum (p : T[X]) (g : ℕ → T → R[X]) (x : S) :
    (p.sum g).eval₂ f x = p.sum fun n a => (g n a).eval₂ f x := by
  let T : R[X] →+ S :=
    { toFun := eval₂ f x
      map_zero' := eval₂_zero _ _
      map_add' := fun p q => eval₂_add _ _ }
  have A : ∀ y, eval₂ f x y = T y := fun y => rfl
  simp only [A]
  rw [sum, T.map_sum, sum]
#align polynomial.eval₂_sum Polynomial.eval₂_sum

theorem eval₂_list_sum (l : List R[X]) (x : S) : eval₂ f x l.sum = (l.map (eval₂ f x)).sum :=
  map_list_sum (eval₂AddMonoidHom f x) l
#align polynomial.eval₂_list_sum Polynomial.eval₂_list_sum

theorem eval₂_multiset_sum (s : Multiset R[X]) (x : S) :
    eval₂ f x s.sum = (s.map (eval₂ f x)).sum :=
  map_multiset_sum (eval₂AddMonoidHom f x) s
#align polynomial.eval₂_multiset_sum Polynomial.eval₂_multiset_sum

theorem eval₂_finset_sum (s : Finset ι) (g : ι → R[X]) (x : S) :
    (∑ i in s, g i).eval₂ f x = ∑ i in s, (g i).eval₂ f x :=
  map_sum (eval₂AddMonoidHom f x) _ _
#align polynomial.eval₂_finset_sum Polynomial.eval₂_finset_sum

theorem eval₂_ofFinsupp {f : R →+* S} {x : S} {p : AddMonoidAlgebra R ℕ} :
    eval₂ f x (⟨p⟩ : R[X]) = liftNC (↑f) (powersHom S x) p := by
  simp only [eval₂_eq_sum, sum, toFinsupp_sum, support, coeff]
  rfl
#align polynomial.eval₂_of_finsupp Polynomial.eval₂_ofFinsupp

theorem eval₂_mul_noncomm (hf : ∀ k, Commute (f <| q.coeff k) x) :
    eval₂ f x (p * q) = eval₂ f x p * eval₂ f x q := by
  rcases p with ⟨p⟩; rcases q with ⟨q⟩
  simp only [coeff] at hf
  simp only [← ofFinsupp_mul, eval₂_ofFinsupp]
  exact liftNC_mul _ _ p q fun {k n} _hn => (hf k).pow_right n
#align polynomial.eval₂_mul_noncomm Polynomial.eval₂_mul_noncomm

@[simp]
theorem eval₂_mul_X : eval₂ f x (p * X) = eval₂ f x p * x := by
  refine' _root_.trans (eval₂_mul_noncomm _ _ fun k => _) (by rw [eval₂_X])
  rcases em (k = 1) with (rfl | hk)
  · simp
  · simp [coeff_X_of_ne_one hk]
#align polynomial.eval₂_mul_X Polynomial.eval₂_mul_X

@[simp]
theorem eval₂_X_mul : eval₂ f x (X * p) = eval₂ f x p * x := by rw [X_mul, eval₂_mul_X]
#align polynomial.eval₂_X_mul Polynomial.eval₂_X_mul

theorem eval₂_mul_C' (h : Commute (f a) x) : eval₂ f x (p * C a) = eval₂ f x p * f a := by
  rw [eval₂_mul_noncomm, eval₂_C]
  intro k
  by_cases hk : k = 0
  · simp only [hk, h, coeff_C_zero, coeff_C_ne_zero]
  · simp only [coeff_C_ne_zero hk, RingHom.map_zero, Commute.zero_left]
#align polynomial.eval₂_mul_C' Polynomial.eval₂_mul_C'

theorem eval₂_list_prod_noncomm (ps : List R[X])
    (hf : ∀ p ∈ ps, ∀ (k), Commute (f <| coeff p k) x) :
    eval₂ f x ps.prod = (ps.map (Polynomial.eval₂ f x)).prod := by
  induction' ps using List.reverseRecOn with ps p ihp
  · simp
  · simp only [List.forall_mem_append, List.forall_mem_singleton] at hf
    simp [eval₂_mul_noncomm _ _ hf.2, ihp hf.1]
#align polynomial.eval₂_list_prod_noncomm Polynomial.eval₂_list_prod_noncomm

/-- `eval₂` as a `RingHom` for noncommutative rings -/
def eval₂RingHom' (f : R →+* S) (x : S) (hf : ∀ a, Commute (f a) x) : R[X] →+* S where
  toFun := eval₂ f x
  map_add' _ _ := eval₂_add _ _
  map_zero' := eval₂_zero _ _
  map_mul' _p q := eval₂_mul_noncomm f x fun k => hf <| coeff q k
  map_one' := eval₂_one _ _
#align polynomial.eval₂_ring_hom' Polynomial.eval₂RingHom'

end

/-!
We next prove that eval₂ is multiplicative
as long as target ring is commutative
(even if the source ring is not).
-/

section Eval₂

section

variable [Semiring S] (f : R →+* S) (x : S)

theorem eval₂_eq_sum_range :
    p.eval₂ f x = ∑ i in Finset.range (p.natDegree + 1), f (p.coeff i) * x ^ i :=
  _root_.trans (congr_arg _ p.as_sum_range)
    (_root_.trans (eval₂_finset_sum f _ _ x) (congr_arg _ (by simp)))
#align polynomial.eval₂_eq_sum_range Polynomial.eval₂_eq_sum_range

theorem eval₂_eq_sum_range' (f : R →+* S) {p : R[X]} {n : ℕ} (hn : p.natDegree < n) (x : S) :
    eval₂ f x p = ∑ i in Finset.range n, f (p.coeff i) * x ^ i := by
  rw [eval₂_eq_sum, p.sum_over_range' _ _ hn]
  intro i
  rw [f.map_zero, zero_mul]
#align polynomial.eval₂_eq_sum_range' Polynomial.eval₂_eq_sum_range'

end

section

variable [CommSemiring S] (f : R →+* S) (x : S)

@[simp]
theorem eval₂_mul : (p * q).eval₂ f x = p.eval₂ f x * q.eval₂ f x :=
  eval₂_mul_noncomm _ _ fun _k => Commute.all _ _
#align polynomial.eval₂_mul Polynomial.eval₂_mul

theorem eval₂_mul_eq_zero_of_left (q : R[X]) (hp : p.eval₂ f x = 0) : (p * q).eval₂ f x = 0 := by
  rw [eval₂_mul f x]
  exact mul_eq_zero_of_left hp (q.eval₂ f x)
#align polynomial.eval₂_mul_eq_zero_of_left Polynomial.eval₂_mul_eq_zero_of_left

theorem eval₂_mul_eq_zero_of_right (p : R[X]) (hq : q.eval₂ f x = 0) : (p * q).eval₂ f x = 0 := by
  rw [eval₂_mul f x]
  exact mul_eq_zero_of_right (p.eval₂ f x) hq
#align polynomial.eval₂_mul_eq_zero_of_right Polynomial.eval₂_mul_eq_zero_of_right

/-- `eval₂` as a `RingHom` -/
def eval₂RingHom (f : R →+* S) (x : S) : R[X] →+* S :=
  { eval₂AddMonoidHom f x with
    map_one' := eval₂_one _ _
    map_mul' := fun _ _ => eval₂_mul _ _ }
#align polynomial.eval₂_ring_hom Polynomial.eval₂RingHom

@[simp]
theorem coe_eval₂RingHom (f : R →+* S) (x) : ⇑(eval₂RingHom f x) = eval₂ f x :=
  rfl
#align polynomial.coe_eval₂_ring_hom Polynomial.coe_eval₂RingHom

theorem eval₂_pow (n : ℕ) : (p ^ n).eval₂ f x = p.eval₂ f x ^ n :=
  (eval₂RingHom _ _).map_pow _ _
#align polynomial.eval₂_pow Polynomial.eval₂_pow

theorem eval₂_dvd : p ∣ q → eval₂ f x p ∣ eval₂ f x q :=
  (eval₂RingHom f x).map_dvd
#align polynomial.eval₂_dvd Polynomial.eval₂_dvd

theorem eval₂_eq_zero_of_dvd_of_eval₂_eq_zero (h : p ∣ q) (h0 : eval₂ f x p = 0) :
    eval₂ f x q = 0 :=
  zero_dvd_iff.mp (h0 ▸ eval₂_dvd f x h)
#align polynomial.eval₂_eq_zero_of_dvd_of_eval₂_eq_zero Polynomial.eval₂_eq_zero_of_dvd_of_eval₂_eq_zero

theorem eval₂_list_prod (l : List R[X]) (x : S) : eval₂ f x l.prod = (l.map (eval₂ f x)).prod :=
  map_list_prod (eval₂RingHom f x) l
#align polynomial.eval₂_list_prod Polynomial.eval₂_list_prod

end

end Eval₂

section Eval

variable {x : R}

/-- `eval x p` is the evaluation of the polynomial `p` at `x` -/
def eval : R → R[X] → R :=
  eval₂ (RingHom.id _)
#align polynomial.eval Polynomial.eval

theorem eval_eq_sum : p.eval x = p.sum fun e a => a * x ^ e := by
  rw [eval, eval₂_eq_sum]
  rfl
#align polynomial.eval_eq_sum Polynomial.eval_eq_sum

theorem eval_eq_sum_range {p : R[X]} (x : R) :
    p.eval x = ∑ i in Finset.range (p.natDegree + 1), p.coeff i * x ^ i := by
  rw [eval_eq_sum, sum_over_range]; simp
#align polynomial.eval_eq_sum_range Polynomial.eval_eq_sum_range

theorem eval_eq_sum_range' {p : R[X]} {n : ℕ} (hn : p.natDegree < n) (x : R) :
    p.eval x = ∑ i in Finset.range n, p.coeff i * x ^ i := by
  rw [eval_eq_sum, p.sum_over_range' _ _ hn]; simp
#align polynomial.eval_eq_sum_range' Polynomial.eval_eq_sum_range'

@[simp]
theorem eval₂_at_apply {S : Type _} [Semiring S] (f : R →+* S) (r : R) :
    p.eval₂ f (f r) = f (p.eval r) := by
  rw [eval₂_eq_sum, eval_eq_sum, sum, sum, f.map_sum]
  simp only [f.map_mul, f.map_pow]
#align polynomial.eval₂_at_apply Polynomial.eval₂_at_apply

@[simp]
theorem eval₂_at_one {S : Type _} [Semiring S] (f : R →+* S) : p.eval₂ f 1 = f (p.eval 1) := by
  convert eval₂_at_apply (p := p) f 1
  simp
#align polynomial.eval₂_at_one Polynomial.eval₂_at_one

@[simp]
theorem eval₂_at_nat_cast {S : Type _} [Semiring S] (f : R →+* S) (n : ℕ) :
    p.eval₂ f n = f (p.eval n) := by
  convert eval₂_at_apply (p := p) f n
  simp
#align polynomial.eval₂_at_nat_cast Polynomial.eval₂_at_nat_cast

@[simp]
theorem eval_C : (C a).eval x = a :=
  eval₂_C _ _
#align polynomial.eval_C Polynomial.eval_C

@[simp]
theorem eval_nat_cast {n : ℕ} : (n : R[X]).eval x = n := by simp only [← C_eq_nat_cast, eval_C]
#align polynomial.eval_nat_cast Polynomial.eval_nat_cast

@[simp]
theorem eval_X : X.eval x = x :=
  eval₂_X _ _
#align polynomial.eval_X Polynomial.eval_X

@[simp]
theorem eval_monomial {n a} : (monomial n a).eval x = a * x ^ n :=
  eval₂_monomial _ _
#align polynomial.eval_monomial Polynomial.eval_monomial

@[simp]
theorem eval_zero : (0 : R[X]).eval x = 0 :=
  eval₂_zero _ _
#align polynomial.eval_zero Polynomial.eval_zero

@[simp]
theorem eval_add : (p + q).eval x = p.eval x + q.eval x :=
  eval₂_add _ _
#align polynomial.eval_add Polynomial.eval_add

@[simp]
theorem eval_one : (1 : R[X]).eval x = 1 :=
  eval₂_one _ _
#align polynomial.eval_one Polynomial.eval_one

set_option linter.deprecated false in
@[simp]
theorem eval_bit0 : (bit0 p).eval x = bit0 (p.eval x) :=
  eval₂_bit0 _ _
#align polynomial.eval_bit0 Polynomial.eval_bit0

set_option linter.deprecated false in
@[simp]
theorem eval_bit1 : (bit1 p).eval x = bit1 (p.eval x) :=
  eval₂_bit1 _ _
#align polynomial.eval_bit1 Polynomial.eval_bit1

@[simp]
theorem eval_smul [Monoid S] [DistribMulAction S R] [IsScalarTower S R R] (s : S) (p : R[X])
    (x : R) : (s • p).eval x = s • p.eval x := by
  rw [← smul_one_smul R s p, eval, eval₂_smul, RingHom.id_apply, smul_one_mul]
#align polynomial.eval_smul Polynomial.eval_smul

@[simp]
theorem eval_C_mul : (C a * p).eval x = a * p.eval x := by
  -- Porting note: `apply` → `induction`
  induction p using Polynomial.induction_on' with
  | h_add p q ph qh =>
    simp only [mul_add, eval_add, ph, qh]
  | h_monomial n b =>
    simp only [mul_assoc, C_mul_monomial, eval_monomial]
#align polynomial.eval_C_mul Polynomial.eval_C_mul

/-- A reformulation of the expansion of (1 + y)^d:
$$(d + 1) (1 + y)^d - (d + 1)y^d = \sum_{i = 0}^d {d + 1 \choose i} \cdot i \cdot y^{i - 1}.$$
-/
theorem eval_monomial_one_add_sub [CommRing S] (d : ℕ) (y : S) :
    eval (1 + y) (monomial d (d + 1 : S)) - eval y (monomial d (d + 1 : S)) =
      ∑ x_1 : ℕ in range (d + 1), ↑((d + 1).choose x_1) * (↑x_1 * y ^ (x_1 - 1)) := by
  have cast_succ : (d + 1 : S) = ((d.succ : ℕ) : S) := by simp only [Nat.cast_succ]
  rw [cast_succ, eval_monomial, eval_monomial, add_comm, add_pow]
  -- Porting note: `apply_congr` hadn't been ported yet, so `congr` & `ext` is used.
  conv_lhs =>
    congr
    · congr
      · skip
      · congr
        · skip
        · ext
          rw [one_pow, mul_one, mul_comm]
  rw [sum_range_succ, mul_add, Nat.choose_self, Nat.cast_one, one_mul, add_sub_cancel, mul_sum,
    sum_range_succ', Nat.cast_zero, zero_mul, mul_zero, add_zero]
  refine sum_congr rfl fun y _hy => ?_
  rw [← mul_assoc, ← mul_assoc, ← Nat.cast_mul, Nat.succ_mul_choose_eq, Nat.cast_mul,
    Nat.add_sub_cancel]
#align polynomial.eval_monomial_one_add_sub Polynomial.eval_monomial_one_add_sub

/-- `Polynomial.eval` as linear map -/
@[simps]
def leval {R : Type _} [Semiring R] (r : R) : R[X] →ₗ[R] R where
  toFun f := f.eval r
  map_add' _f _g := eval_add
  map_smul' c f := eval_smul c f r
#align polynomial.leval Polynomial.leval
#align polynomial.leval_apply Polynomial.leval_apply

@[simp]
theorem eval_nat_cast_mul {n : ℕ} : ((n : R[X]) * p).eval x = n * p.eval x := by
  rw [← C_eq_nat_cast, eval_C_mul]
#align polynomial.eval_nat_cast_mul Polynomial.eval_nat_cast_mul

@[simp]
theorem eval_mul_X : (p * X).eval x = p.eval x * x := by
  -- Porting note: `apply` → `induction`
  induction p using Polynomial.induction_on' with
  | h_add p q ph qh =>
    simp only [add_mul, eval_add, ph, qh]
  | h_monomial n a =>
    simp only [← monomial_one_one_eq_X, monomial_mul_monomial, eval_monomial, mul_one, pow_succ',
      mul_assoc]
#align polynomial.eval_mul_X Polynomial.eval_mul_X

@[simp]
theorem eval_mul_X_pow {k : ℕ} : (p * X ^ k).eval x = p.eval x * x ^ k := by
  induction' k with k ih
  · simp
  · simp [pow_succ', ← mul_assoc, ih]
#align polynomial.eval_mul_X_pow Polynomial.eval_mul_X_pow

theorem eval_sum (p : R[X]) (f : ℕ → R → R[X]) (x : R) :
    (p.sum f).eval x = p.sum fun n a => (f n a).eval x :=
  eval₂_sum _ _ _ _
#align polynomial.eval_sum Polynomial.eval_sum

theorem eval_finset_sum (s : Finset ι) (g : ι → R[X]) (x : R) :
    (∑ i in s, g i).eval x = ∑ i in s, (g i).eval x :=
  eval₂_finset_sum _ _ _ _
#align polynomial.eval_finset_sum Polynomial.eval_finset_sum

/-- `IsRoot p x` implies `x` is a root of `p`. The evaluation of `p` at `x` is zero -/
def IsRoot (p : R[X]) (a : R) : Prop :=
  p.eval a = 0
#align polynomial.is_root Polynomial.IsRoot

instance IsRoot.decidable [DecidableEq R] : Decidable (IsRoot p a) := by
  unfold IsRoot; infer_instance
#align polynomial.is_root.decidable Polynomial.IsRoot.decidable

@[simp]
theorem IsRoot.def : IsRoot p a ↔ p.eval a = 0 :=
  Iff.rfl
#align polynomial.is_root.def Polynomial.IsRoot.def

theorem IsRoot.eq_zero (h : IsRoot p x) : eval x p = 0 :=
  h
#align polynomial.is_root.eq_zero Polynomial.IsRoot.eq_zero

theorem coeff_zero_eq_eval_zero (p : R[X]) : coeff p 0 = p.eval 0 :=
  calc
    coeff p 0 = coeff p 0 * 0 ^ 0 := by simp
    _ = p.eval 0 := by
      symm
      rw [eval_eq_sum]
      exact
        Finset.sum_eq_single _ (fun b _ hb => by simp [zero_pow (Nat.pos_of_ne_zero hb)]) (by simp)

#align polynomial.coeff_zero_eq_eval_zero Polynomial.coeff_zero_eq_eval_zero

theorem zero_isRoot_of_coeff_zero_eq_zero {p : R[X]} (hp : p.coeff 0 = 0) : IsRoot p 0 := by
  rwa [coeff_zero_eq_eval_zero] at hp
#align polynomial.zero_is_root_of_coeff_zero_eq_zero Polynomial.zero_isRoot_of_coeff_zero_eq_zero

theorem not_isRoot_C (r a : R) (hr : r ≠ 0) : ¬IsRoot (C r) a := by simpa using hr
#align polynomial.not_is_root_C Polynomial.not_isRoot_C

theorem eval_surjective (x : R) : Function.Surjective <| eval x := fun y => ⟨C y, eval_C⟩
#align polynomial.eval_surjective Polynomial.eval_surjective

end Eval

section Comp

/-- The composition of polynomials as a polynomial. -/
def comp (p q : R[X]) : R[X] :=
  p.eval₂ C q
#align polynomial.comp Polynomial.comp

theorem comp_eq_sum_left : p.comp q = p.sum fun e a => C a * q ^ e := by rw [comp, eval₂_eq_sum]
#align polynomial.comp_eq_sum_left Polynomial.comp_eq_sum_left

@[simp]
theorem comp_X : p.comp X = p := by
  simp only [comp, eval₂_def, C_mul_X_pow_eq_monomial]
  exact sum_monomial_eq _
#align polynomial.comp_X Polynomial.comp_X

@[simp]
theorem X_comp : X.comp p = p :=
  eval₂_X _ _
#align polynomial.X_comp Polynomial.X_comp

@[simp]
theorem comp_C : p.comp (C a) = C (p.eval a) := by simp [comp, (C : R →+* _).map_sum]
#align polynomial.comp_C Polynomial.comp_C

@[simp]
theorem C_comp : (C a).comp p = C a :=
  eval₂_C _ _
#align polynomial.C_comp Polynomial.C_comp

@[simp]
theorem nat_cast_comp {n : ℕ} : (n : R[X]).comp p = n := by rw [← C_eq_nat_cast, C_comp]
#align polynomial.nat_cast_comp Polynomial.nat_cast_comp

--Porting note: new theorem
@[simp]
theorem ofNat_comp (n : ℕ) [n.AtLeastTwo] : (OfNat.ofNat n : R[X]).comp p = n :=
  nat_cast_comp

@[simp]
theorem comp_zero : p.comp (0 : R[X]) = C (p.eval 0) := by rw [← C_0, comp_C]
#align polynomial.comp_zero Polynomial.comp_zero

@[simp]
theorem zero_comp : comp (0 : R[X]) p = 0 := by rw [← C_0, C_comp]
#align polynomial.zero_comp Polynomial.zero_comp

@[simp]
theorem comp_one : p.comp 1 = C (p.eval 1) := by rw [← C_1, comp_C]
#align polynomial.comp_one Polynomial.comp_one

@[simp]
theorem one_comp : comp (1 : R[X]) p = 1 := by rw [← C_1, C_comp]
#align polynomial.one_comp Polynomial.one_comp

@[simp]
theorem add_comp : (p + q).comp r = p.comp r + q.comp r :=
  eval₂_add _ _
#align polynomial.add_comp Polynomial.add_comp

@[simp]
theorem monomial_comp (n : ℕ) : (monomial n a).comp p = C a * p ^ n :=
  eval₂_monomial _ _
#align polynomial.monomial_comp Polynomial.monomial_comp

@[simp]
theorem mul_X_comp : (p * X).comp r = p.comp r * r := by
  -- Porting note: `apply` → `induction`
  induction p using Polynomial.induction_on' with
  | h_add p q hp hq =>
    simp only [hp, hq, add_mul, add_comp]
  | h_monomial n b =>
    simp only [pow_succ', mul_assoc, monomial_mul_X, monomial_comp]
#align polynomial.mul_X_comp Polynomial.mul_X_comp

@[simp]
theorem X_pow_comp {k : ℕ} : (X ^ k).comp p = p ^ k := by
  induction' k with k ih
  · simp
  · simp [pow_succ', mul_X_comp, ih]
#align polynomial.X_pow_comp Polynomial.X_pow_comp

@[simp]
theorem mul_X_pow_comp {k : ℕ} : (p * X ^ k).comp r = p.comp r * r ^ k := by
  induction' k with k ih
  · simp
  · simp [ih, pow_succ', ← mul_assoc, mul_X_comp]
#align polynomial.mul_X_pow_comp Polynomial.mul_X_pow_comp

@[simp]
theorem C_mul_comp : (C a * p).comp r = C a * p.comp r := by
  -- Porting note: `apply` → `induction`
  induction p using Polynomial.induction_on' with
  | h_add p q hp hq =>
    simp [hp, hq, mul_add]
  | h_monomial n b =>
    simp [mul_assoc]
#align polynomial.C_mul_comp Polynomial.C_mul_comp

@[simp]
theorem nat_cast_mul_comp {n : ℕ} : ((n : R[X]) * p).comp r = n * p.comp r := by
  rw [← C_eq_nat_cast, C_mul_comp, C_eq_nat_cast]
#align polynomial.nat_cast_mul_comp Polynomial.nat_cast_mul_comp

@[simp]
theorem mul_comp {R : Type _} [CommSemiring R] (p q r : R[X]) :
    (p * q).comp r = p.comp r * q.comp r :=
  eval₂_mul _ _
#align polynomial.mul_comp Polynomial.mul_comp

@[simp]
theorem pow_comp {R : Type _} [CommSemiring R] (p q : R[X]) (n : ℕ) :
    (p ^ n).comp q = p.comp q ^ n :=
  (MonoidHom.mk (OneHom.mk (fun r : R[X] => r.comp q) one_comp) fun r s => mul_comp r s q).map_pow
    p n
#align polynomial.pow_comp Polynomial.pow_comp

set_option linter.deprecated false in
@[simp]
theorem bit0_comp : comp (bit0 p : R[X]) q = bit0 (p.comp q) := by simp only [bit0, add_comp]
#align polynomial.bit0_comp Polynomial.bit0_comp

set_option linter.deprecated false in
@[simp]
theorem bit1_comp : comp (bit1 p : R[X]) q = bit1 (p.comp q) := by
  simp only [bit1, add_comp, bit0_comp, one_comp]
#align polynomial.bit1_comp Polynomial.bit1_comp

@[simp]
theorem smul_comp [Monoid S] [DistribMulAction S R] [IsScalarTower S R R] (s : S) (p q : R[X]) :
    (s • p).comp q = s • p.comp q := by
  rw [← smul_one_smul R s p, comp, comp, eval₂_smul, ← smul_eq_C_mul, smul_assoc, one_smul]
#align polynomial.smul_comp Polynomial.smul_comp

theorem comp_assoc {R : Type _} [CommSemiring R] (φ ψ χ : R[X]) :
    (φ.comp ψ).comp χ = φ.comp (ψ.comp χ) := by
  refine Polynomial.induction_on φ ?_ ?_ ?_ <;>
    · intros
      simp_all only [add_comp, mul_comp, C_comp, X_comp, pow_succ', ← mul_assoc]
#align polynomial.comp_assoc Polynomial.comp_assoc

theorem coeff_comp_degree_mul_degree (hqd0 : natDegree q ≠ 0) :
    coeff (p.comp q) (natDegree p * natDegree q) = leadingCoeff p * leadingCoeff q ^ natDegree p :=
  by
  rw [comp, eval₂_def, coeff_sum]
  -- Porting note: `convert` → `refine`
  refine Eq.trans (Finset.sum_eq_single p.natDegree ?h₀ ?h₁) ?h₂
  case h₂ =>
    simp only [coeff_natDegree, coeff_C_mul, coeff_pow_mul_natDegree]
  case h₀ =>
    intro b hbs hbp
    refine' coeff_eq_zero_of_natDegree_lt (natDegree_mul_le.trans_lt _)
    rw [natDegree_C, zero_add]
    refine' natDegree_pow_le.trans_lt ((mul_lt_mul_right (pos_iff_ne_zero.mpr hqd0)).mpr _)
    exact lt_of_le_of_ne (le_natDegree_of_mem_supp _ hbs) hbp
  case h₁ =>
    simp (config := { contextual := true })
#align polynomial.coeff_comp_degree_mul_degree Polynomial.coeff_comp_degree_mul_degree

end Comp

section Map

variable [Semiring S]

variable (f : R →+* S)

/-- `map f p` maps a polynomial `p` across a ring hom `f` -/
def map : R[X] → S[X] :=
  eval₂ (C.comp f) X
#align polynomial.map Polynomial.map

@[simp]
theorem map_C : (C a).map f = C (f a) :=
  eval₂_C _ _
#align polynomial.map_C Polynomial.map_C

@[simp]
theorem map_X : X.map f = X :=
  eval₂_X _ _
#align polynomial.map_X Polynomial.map_X

@[simp]
theorem map_monomial {n a} : (monomial n a).map f = monomial n (f a) := by
  dsimp only [map]
  rw [eval₂_monomial, ← C_mul_X_pow_eq_monomial]; rfl
#align polynomial.map_monomial Polynomial.map_monomial

@[simp]
protected theorem map_zero : (0 : R[X]).map f = 0 :=
  eval₂_zero _ _
#align polynomial.map_zero Polynomial.map_zero

@[simp]
protected theorem map_add : (p + q).map f = p.map f + q.map f :=
  eval₂_add _ _
#align polynomial.map_add Polynomial.map_add

@[simp]
protected theorem map_one : (1 : R[X]).map f = 1 :=
  eval₂_one _ _
#align polynomial.map_one Polynomial.map_one

@[simp]
protected theorem map_mul : (p * q).map f = p.map f * q.map f := by
  rw [map, eval₂_mul_noncomm]
  exact fun k => (commute_X _).symm
#align polynomial.map_mul Polynomial.map_mul

@[simp]
protected theorem map_smul (r : R) : (r • p).map f = f r • p.map f := by
  rw [map, eval₂_smul, RingHom.comp_apply, C_mul']
#align polynomial.map_smul Polynomial.map_smul

-- `map` is a ring-hom unconditionally, and theoretically the definition could be replaced,
-- but this turns out not to be easy because `p.map f` does not resolve to `Polynomial.map`
-- if `map` is a `RingHom` instead of a plain function; the elaborator does not try to coerce
-- to a function before trying field (dot) notation (this may be technically infeasible);
-- the relevant code is (both lines): https://github.com/leanprover-community/
-- lean/blob/487ac5d7e9b34800502e1ddf3c7c806c01cf9d51/src/frontends/lean/elaborator.cpp#L1876-L1913
/-- `Polynomial.map` as a `RingHom`. -/
def mapRingHom (f : R →+* S) : R[X] →+* S[X] where
  toFun := Polynomial.map f
  map_add' _ _ := Polynomial.map_add f
  map_zero' := Polynomial.map_zero f
  map_mul' _ _ := Polynomial.map_mul f
  map_one' := Polynomial.map_one f
#align polynomial.map_ring_hom Polynomial.mapRingHom

@[simp]
theorem coe_mapRingHom (f : R →+* S) : ⇑(mapRingHom f) = map f :=
  rfl
#align polynomial.coe_map_ring_hom Polynomial.coe_mapRingHom

-- This is protected to not clash with the global `map_nat_cast`.
@[simp]
protected theorem map_nat_cast (n : ℕ) : (n : R[X]).map f = n :=
  map_natCast (mapRingHom f) n
#align polynomial.map_nat_cast Polynomial.map_nat_cast

--Porting note: new theorem
@[simp]
protected theorem map_ofNat (n : ℕ) [n.AtLeastTwo] : (OfNat.ofNat n : R[X]).map f = OfNat.ofNat n :=
  show (n : R[X]).map f = n by rw [Polynomial.map_nat_cast]

set_option linter.deprecated false in
@[simp]
protected theorem map_bit0 : (bit0 p).map f = bit0 (p.map f) :=
  map_bit0 (mapRingHom f) p
#align polynomial.map_bit0 Polynomial.map_bit0

set_option linter.deprecated false in
@[simp]
protected theorem map_bit1 : (bit1 p).map f = bit1 (p.map f) :=
  map_bit1 (mapRingHom f) p
#align polynomial.map_bit1 Polynomial.map_bit1

--TODO rename to `map_dvd_map`
theorem map_dvd (f : R →+* S) {x y : R[X]} : x ∣ y → x.map f ∣ y.map f :=
  (mapRingHom f).map_dvd
#align polynomial.map_dvd Polynomial.map_dvd

@[simp]
theorem coeff_map (n : ℕ) : coeff (p.map f) n = f (coeff p n) := by
  rw [map, eval₂_def, coeff_sum, sum]
  conv_rhs => rw [← sum_C_mul_X_pow_eq p, coeff_sum, sum, map_sum]
  refine' Finset.sum_congr rfl fun x _hx => _
  -- Porting note: Was `simp [Function.comp, coeff_C_mul_X_pow, f.map_mul]`.
  simp [Function.comp, coeff_C_mul_X_pow, - map_mul, - coeff_C_mul]
  split_ifs <;> simp [f.map_zero]
#align polynomial.coeff_map Polynomial.coeff_map

/-- If `R` and `S` are isomorphic, then so are their polynomial rings. -/
@[simps!]
def mapEquiv (e : R ≃+* S) : R[X] ≃+* S[X] :=
  RingEquiv.ofHomInv (mapRingHom (e : R →+* S)) (mapRingHom (e.symm : S →+* R)) (by ext <;> simp)
    (by ext <;> simp)
#align polynomial.map_equiv Polynomial.mapEquiv
#align polynomial.map_equiv_apply Polynomial.mapEquiv_apply
#align polynomial.map_equiv_symm_apply Polynomial.mapEquiv_symm_apply

theorem map_map [Semiring T] (g : S →+* T) (p : R[X]) : (p.map f).map g = p.map (g.comp f) :=
  ext (by simp [coeff_map])
#align polynomial.map_map Polynomial.map_map

@[simp]
theorem map_id : p.map (RingHom.id _) = p := by simp [Polynomial.ext_iff, coeff_map]
#align polynomial.map_id Polynomial.map_id

theorem eval₂_eq_eval_map {x : S} : p.eval₂ f x = (p.map f).eval x := by
  -- Porting note: `apply` → `induction`
  induction p using Polynomial.induction_on' with
  | h_add p q hp hq =>
    simp [hp, hq]
  | h_monomial n r =>
    simp
#align polynomial.eval₂_eq_eval_map Polynomial.eval₂_eq_eval_map

theorem map_injective (hf : Function.Injective f) : Function.Injective (map f) := fun p q h =>
  ext fun m => hf <| by rw [← coeff_map f, ← coeff_map f, h]
#align polynomial.map_injective Polynomial.map_injective

theorem map_surjective (hf : Function.Surjective f) : Function.Surjective (map f) := fun p =>
  Polynomial.induction_on' p
    (fun p q hp hq =>
      let ⟨p', hp'⟩ := hp
      let ⟨q', hq'⟩ := hq
      ⟨p' + q', by rw [Polynomial.map_add f, hp', hq']⟩)
    fun n s =>
    let ⟨r, hr⟩ := hf s
    ⟨monomial n r, by rw [map_monomial f, hr]⟩
#align polynomial.map_surjective Polynomial.map_surjective

theorem degree_map_le (p : R[X]) : degree (p.map f) ≤ degree p := by
  refine (degree_le_iff_coeff_zero _ _).2 fun m hm => ?_
  rw [degree_lt_iff_coeff_zero] at hm
  simp [hm m le_rfl]
#align polynomial.degree_map_le Polynomial.degree_map_le

theorem natDegree_map_le (p : R[X]) : natDegree (p.map f) ≤ natDegree p :=
  natDegree_le_natDegree (degree_map_le f p)
#align polynomial.nat_degree_map_le Polynomial.natDegree_map_le

variable {f}

protected theorem map_eq_zero_iff (hf : Function.Injective f) : p.map f = 0 ↔ p = 0 :=
  map_eq_zero_iff (mapRingHom f) (map_injective f hf)
#align polynomial.map_eq_zero_iff Polynomial.map_eq_zero_iff

protected theorem map_ne_zero_iff (hf : Function.Injective f) : p.map f ≠ 0 ↔ p ≠ 0 :=
  (Polynomial.map_eq_zero_iff hf).not
#align polynomial.map_ne_zero_iff Polynomial.map_ne_zero_iff

theorem map_monic_eq_zero_iff (hp : p.Monic) : p.map f = 0 ↔ ∀ x, f x = 0 :=
  ⟨fun hfp x =>
    calc
      f x = f x * f p.leadingCoeff := by simp only [mul_one, hp.leadingCoeff, f.map_one]
      _ = f x * (p.map f).coeff p.natDegree := (congr_arg _ (coeff_map _ _).symm)
      _ = 0 := by simp only [hfp, mul_zero, coeff_zero]
      ,
    fun h => ext fun n => by simp only [h, coeff_map, coeff_zero]⟩
#align polynomial.map_monic_eq_zero_iff Polynomial.map_monic_eq_zero_iff

theorem map_monic_ne_zero (hp : p.Monic) [Nontrivial S] : p.map f ≠ 0 := fun h =>
  f.map_one_ne_zero ((map_monic_eq_zero_iff hp).mp h _)
#align polynomial.map_monic_ne_zero Polynomial.map_monic_ne_zero

theorem degree_map_eq_of_leadingCoeff_ne_zero (f : R →+* S) (hf : f (leadingCoeff p) ≠ 0) :
    degree (p.map f) = degree p :=
  le_antisymm (degree_map_le f _) <| by
    have hp0 : p ≠ 0 :=
      leadingCoeff_ne_zero.mp fun hp0 => hf (_root_.trans (congr_arg _ hp0) f.map_zero)
    rw [degree_eq_natDegree hp0]
    refine' le_degree_of_ne_zero _
    rw [coeff_map]
    exact hf
#align polynomial.degree_map_eq_of_leading_coeff_ne_zero Polynomial.degree_map_eq_of_leadingCoeff_ne_zero

theorem natDegree_map_of_leadingCoeff_ne_zero (f : R →+* S) (hf : f (leadingCoeff p) ≠ 0) :
    natDegree (p.map f) = natDegree p :=
  natDegree_eq_of_degree_eq (degree_map_eq_of_leadingCoeff_ne_zero f hf)
#align polynomial.nat_degree_map_of_leading_coeff_ne_zero Polynomial.natDegree_map_of_leadingCoeff_ne_zero

theorem leadingCoeff_map_of_leadingCoeff_ne_zero (f : R →+* S) (hf : f (leadingCoeff p) ≠ 0) :
    leadingCoeff (p.map f) = f (leadingCoeff p) := by
  unfold leadingCoeff
  rw [coeff_map, natDegree_map_of_leadingCoeff_ne_zero f hf]
#align polynomial.leading_coeff_map_of_leading_coeff_ne_zero Polynomial.leadingCoeff_map_of_leadingCoeff_ne_zero

variable (f)

@[simp]
theorem mapRingHom_id : mapRingHom (RingHom.id R) = RingHom.id R[X] :=
  RingHom.ext fun _x => map_id
#align polynomial.map_ring_hom_id Polynomial.mapRingHom_id

@[simp]
theorem mapRingHom_comp [Semiring T] (f : S →+* T) (g : R →+* S) :
    (mapRingHom f).comp (mapRingHom g) = mapRingHom (f.comp g) :=
  RingHom.ext <| Polynomial.map_map g f
#align polynomial.map_ring_hom_comp Polynomial.mapRingHom_comp

protected theorem map_list_prod (L : List R[X]) : L.prod.map f = (L.map <| map f).prod :=
  Eq.symm <| List.prod_hom _ (mapRingHom f).toMonoidHom
#align polynomial.map_list_prod Polynomial.map_list_prod

@[simp]
protected theorem map_pow (n : ℕ) : (p ^ n).map f = p.map f ^ n :=
  (mapRingHom f).map_pow _ _
#align polynomial.map_pow Polynomial.map_pow

theorem mem_map_rangeS {p : S[X]} : p ∈ (mapRingHom f).rangeS ↔ ∀ n, p.coeff n ∈ f.rangeS := by
  constructor
  · rintro ⟨p, rfl⟩ n
    rw [coe_mapRingHom, coeff_map]
    exact Set.mem_range_self _
  · intro h
    rw [p.as_sum_range_C_mul_X_pow]
    refine' (mapRingHom f).rangeS.sum_mem _
    intro i _hi
    rcases h i with ⟨c, hc⟩
    use C c * X ^ i
    rw [coe_mapRingHom, Polynomial.map_mul, map_C, hc, Polynomial.map_pow, map_X]
#align polynomial.mem_map_srange Polynomial.mem_map_rangeS

theorem mem_map_range {R S : Type _} [Ring R] [Ring S] (f : R →+* S) {p : S[X]} :
    p ∈ (mapRingHom f).range ↔ ∀ n, p.coeff n ∈ f.range :=
  mem_map_rangeS f
#align polynomial.mem_map_range Polynomial.mem_map_range

theorem eval₂_map [Semiring T] (g : S →+* T) (x : T) : (p.map f).eval₂ g x = p.eval₂ (g.comp f) x :=
  by rw [eval₂_eq_eval_map, eval₂_eq_eval_map, map_map]
#align polynomial.eval₂_map Polynomial.eval₂_map

theorem eval_map (x : S) : (p.map f).eval x = p.eval₂ f x :=
  (eval₂_eq_eval_map f).symm
#align polynomial.eval_map Polynomial.eval_map

protected theorem map_sum {ι : Type _} (g : ι → R[X]) (s : Finset ι) :
    (∑ i in s, g i).map f = ∑ i in s, (g i).map f :=
  (mapRingHom f).map_sum _ _
#align polynomial.map_sum Polynomial.map_sum

theorem map_comp (p q : R[X]) : map f (p.comp q) = (map f p).comp (map f q) :=
  Polynomial.induction_on p (by simp only [map_C, forall_const, C_comp, eq_self_iff_true])
    (by
      simp (config := { contextual := true }) only [Polynomial.map_add, add_comp, forall_const,
        imp_true_iff, eq_self_iff_true])
    (by
      simp (config := { contextual := true }) only [pow_succ', ← mul_assoc, comp, forall_const,
        eval₂_mul_X, imp_true_iff, eq_self_iff_true, map_X, Polynomial.map_mul])
#align polynomial.map_comp Polynomial.map_comp

@[simp]
theorem eval_zero_map (f : R →+* S) (p : R[X]) : (p.map f).eval 0 = f (p.eval 0) := by
  simp [← coeff_zero_eq_eval_zero]
#align polynomial.eval_zero_map Polynomial.eval_zero_map

@[simp]
theorem eval_one_map (f : R →+* S) (p : R[X]) : (p.map f).eval 1 = f (p.eval 1) := by
  -- Porting note: `apply` → `induction`
  induction p using Polynomial.induction_on' with
  | h_add p q hp hq =>
    simp only [hp, hq, Polynomial.map_add, RingHom.map_add, eval_add]
  | h_monomial n r =>
    simp only [one_pow, mul_one, eval_monomial, map_monomial]
#align polynomial.eval_one_map Polynomial.eval_one_map

@[simp]
theorem eval_nat_cast_map (f : R →+* S) (p : R[X]) (n : ℕ) :
    (p.map f).eval (n : S) = f (p.eval n) := by
  -- Porting note: `apply` → `induction`
  induction p using Polynomial.induction_on' with
  | h_add p q hp hq =>
    simp only [hp, hq, Polynomial.map_add, RingHom.map_add, eval_add]
  | h_monomial n r =>
    simp only [map_natCast f, eval_monomial, map_monomial, f.map_pow, f.map_mul]
#align polynomial.eval_nat_cast_map Polynomial.eval_nat_cast_map

@[simp]
theorem eval_int_cast_map {R S : Type _} [Ring R] [Ring S] (f : R →+* S) (p : R[X]) (i : ℤ) :
    (p.map f).eval (i : S) = f (p.eval i) := by
  -- Porting note: `apply` → `induction`
  induction p using Polynomial.induction_on' with
  | h_add p q hp hq =>
    simp only [hp, hq, Polynomial.map_add, RingHom.map_add, eval_add]
  | h_monomial n r =>
    simp only [map_intCast, eval_monomial, map_monomial, map_pow, map_mul]
#align polynomial.eval_int_cast_map Polynomial.eval_int_cast_map

end Map

/-!
we have made `eval₂` irreducible from the start.

Perhaps we can make also `eval`, `comp`, and `map` irreducible too?
-/

section HomEval₂

variable [Semiring S] [Semiring T] (f : R →+* S) (g : S →+* T) (p)

theorem hom_eval₂ (x : S) : g (p.eval₂ f x) = p.eval₂ (g.comp f) (g x) := by
  rw [← eval₂_map, eval₂_at_apply, eval_map]
#align polynomial.hom_eval₂ Polynomial.hom_eval₂

end HomEval₂

end Semiring

section CommSemiring

section Eval

section

variable [Semiring R] {p q : R[X]} {x : R} [Semiring S] (f : R →+* S)

theorem eval₂_hom (x : R) : p.eval₂ f (f x) = f (p.eval x) :=
  RingHom.comp_id f ▸ (hom_eval₂ p (RingHom.id R) f x).symm
#align polynomial.eval₂_hom Polynomial.eval₂_hom

end

section

variable [Semiring R] {p q : R[X]} {x : R} [CommSemiring S] (f : R →+* S)

theorem eval₂_comp {x : S} : eval₂ f x (p.comp q) = eval₂ f (eval₂ f x q) p := by
  rw [comp, p.as_sum_range]; simp [eval₂_finset_sum, eval₂_pow]
#align polynomial.eval₂_comp Polynomial.eval₂_comp

@[simp]
theorem iterate_comp_eval₂ (k : ℕ) (t : S) :
    eval₂ f t ((p.comp^[k]) q) = ((fun x => eval₂ f x p)^[k]) (eval₂ f t q) := by
  induction' k with k IH
  · simp
  · rw [Function.iterate_succ_apply', Function.iterate_succ_apply', eval₂_comp, IH]
#align polynomial.iterate_comp_eval₂ Polynomial.iterate_comp_eval₂

end

section

variable [CommSemiring R] {p q : R[X]} {x : R} [CommSemiring S] (f : R →+* S)

@[simp]
theorem eval_mul : (p * q).eval x = p.eval x * q.eval x :=
  eval₂_mul _ _
#align polynomial.eval_mul Polynomial.eval_mul

/-- `eval r`, regarded as a ring homomorphism from `R[X]` to `R`. -/
def evalRingHom : R → R[X] →+* R :=
  eval₂RingHom (RingHom.id _)
#align polynomial.eval_ring_hom Polynomial.evalRingHom

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

theorem evalRingHom_zero : evalRingHom 0 = constantCoeff :=
  FunLike.ext _ _ fun p => p.coeff_zero_eq_eval_zero.symm
#align polynomial.eval_ring_hom_zero Polynomial.evalRingHom_zero

@[simp]
theorem eval_pow (n : ℕ) : (p ^ n).eval x = p.eval x ^ n :=
  eval₂_pow _ _ _
#align polynomial.eval_pow Polynomial.eval_pow

@[simp]
theorem eval_comp : (p.comp q).eval x = p.eval (q.eval x) := by
  -- Porting note: `apply` → `induction`
  induction p using Polynomial.induction_on' with
  | h_add r s hr hs =>
    simp [add_comp, hr, hs]
  | h_monomial n a =>
    simp
#align polynomial.eval_comp Polynomial.eval_comp

/-- `comp p`, regarded as a ring homomorphism from `R[X]` to itself. -/
def compRingHom : R[X] → R[X] →+* R[X] :=
  eval₂RingHom C
#align polynomial.comp_ring_hom Polynomial.compRingHom

@[simp]
theorem coe_compRingHom (q : R[X]) : (compRingHom q : R[X] → R[X]) = fun p => comp p q :=
  rfl
#align polynomial.coe_comp_ring_hom Polynomial.coe_compRingHom

theorem coe_compRingHom_apply (p q : R[X]) : (compRingHom q : R[X] → R[X]) p = comp p q :=
  rfl
#align polynomial.coe_comp_ring_hom_apply Polynomial.coe_compRingHom_apply

theorem root_mul_left_of_isRoot (p : R[X]) {q : R[X]} : IsRoot q a → IsRoot (p * q) a := fun H => by
  rw [IsRoot, eval_mul, IsRoot.def.1 H, mul_zero]
#align polynomial.root_mul_left_of_is_root Polynomial.root_mul_left_of_isRoot

theorem root_mul_right_of_isRoot {p : R[X]} (q : R[X]) : IsRoot p a → IsRoot (p * q) a := fun H =>
  by rw [IsRoot, eval_mul, IsRoot.def.1 H, zero_mul]
#align polynomial.root_mul_right_of_is_root Polynomial.root_mul_right_of_isRoot

theorem eval₂_multiset_prod (s : Multiset R[X]) (x : S) :
    eval₂ f x s.prod = (s.map (eval₂ f x)).prod :=
  map_multiset_prod (eval₂RingHom f x) s
#align polynomial.eval₂_multiset_prod Polynomial.eval₂_multiset_prod

theorem eval₂_finset_prod (s : Finset ι) (g : ι → R[X]) (x : S) :
    (∏ i in s, g i).eval₂ f x = ∏ i in s, (g i).eval₂ f x :=
  map_prod (eval₂RingHom f x) _ _
#align polynomial.eval₂_finset_prod Polynomial.eval₂_finset_prod

/-- Polynomial evaluation commutes with `List.prod`
-/
theorem eval_list_prod (l : List R[X]) (x : R) : eval x l.prod = (l.map (eval x)).prod :=
  (evalRingHom x).map_list_prod l
#align polynomial.eval_list_prod Polynomial.eval_list_prod

/-- Polynomial evaluation commutes with `Multiset.prod`
-/
theorem eval_multiset_prod (s : Multiset R[X]) (x : R) : eval x s.prod = (s.map (eval x)).prod :=
  (evalRingHom x).map_multiset_prod s
#align polynomial.eval_multiset_prod Polynomial.eval_multiset_prod

/-- Polynomial evaluation commutes with `Finset.prod`
-/
theorem eval_prod {ι : Type _} (s : Finset ι) (p : ι → R[X]) (x : R) :
    eval x (∏ j in s, p j) = ∏ j in s, eval x (p j) :=
  (evalRingHom x).map_prod _ _
#align polynomial.eval_prod Polynomial.eval_prod

theorem list_prod_comp (l : List R[X]) (q : R[X]) :
    l.prod.comp q = (l.map fun p : R[X] => p.comp q).prod :=
  map_list_prod (compRingHom q) _
#align polynomial.list_prod_comp Polynomial.list_prod_comp

theorem multiset_prod_comp (s : Multiset R[X]) (q : R[X]) :
    s.prod.comp q = (s.map fun p : R[X] => p.comp q).prod :=
  map_multiset_prod (compRingHom q) _
#align polynomial.multiset_prod_comp Polynomial.multiset_prod_comp

theorem prod_comp {ι : Type _} (s : Finset ι) (p : ι → R[X]) (q : R[X]) :
    (∏ j in s, p j).comp q = ∏ j in s, (p j).comp q :=
  map_prod (compRingHom q) _ _
#align polynomial.prod_comp Polynomial.prod_comp

theorem isRoot_prod {R} [CommRing R] [IsDomain R] {ι : Type _} (s : Finset ι) (p : ι → R[X])
    (x : R) : IsRoot (∏ j in s, p j) x ↔ ∃ i ∈ s, IsRoot (p i) x := by
  simp only [IsRoot, eval_prod, Finset.prod_eq_zero_iff]
#align polynomial.is_root_prod Polynomial.isRoot_prod

theorem eval_dvd : p ∣ q → eval x p ∣ eval x q :=
  eval₂_dvd _ _
#align polynomial.eval_dvd Polynomial.eval_dvd

theorem eval_eq_zero_of_dvd_of_eval_eq_zero : p ∣ q → eval x p = 0 → eval x q = 0 :=
  eval₂_eq_zero_of_dvd_of_eval₂_eq_zero _ _
#align polynomial.eval_eq_zero_of_dvd_of_eval_eq_zero Polynomial.eval_eq_zero_of_dvd_of_eval_eq_zero

@[simp]
theorem eval_geom_sum {R} [CommSemiring R] {n : ℕ} {x : R} :
    eval x (∑ i in range n, X ^ i) = ∑ i in range n, x ^ i := by simp [eval_finset_sum]
#align polynomial.eval_geom_sum Polynomial.eval_geom_sum

end

end Eval

section Map

theorem support_map_subset [Semiring R] [Semiring S] (f : R →+* S) (p : R[X]) :
    (map f p).support ⊆ p.support := by
  intro x
  contrapose!
  simp (config := { contextual := true })
#align polynomial.support_map_subset Polynomial.support_map_subset

theorem support_map_of_injective [Semiring R] [Semiring S] (p : R[X]) {f : R →+* S}
    (hf : Function.Injective f) : (map f p).support = p.support := by
  simp_rw [Finset.ext_iff, mem_support_iff, coeff_map, ← map_zero f, hf.ne_iff, iff_self_iff,
    forall_const]
#align polynomial.support_map_of_injective Polynomial.support_map_of_injective

variable [CommSemiring R] [CommSemiring S] (f : R →+* S)

protected theorem map_multiset_prod (m : Multiset R[X]) : m.prod.map f = (m.map <| map f).prod :=
  Eq.symm <| Multiset.prod_hom _ (mapRingHom f).toMonoidHom
#align polynomial.map_multiset_prod Polynomial.map_multiset_prod

protected theorem map_prod {ι : Type _} (g : ι → R[X]) (s : Finset ι) :
    (∏ i in s, g i).map f = ∏ i in s, (g i).map f :=
  (mapRingHom f).map_prod _ _
#align polynomial.map_prod Polynomial.map_prod

theorem IsRoot.map {f : R →+* S} {x : R} {p : R[X]} (h : IsRoot p x) : IsRoot (p.map f) (f x) := by
  rw [IsRoot, eval_map, eval₂_hom, h.eq_zero, f.map_zero]
#align polynomial.is_root.map Polynomial.IsRoot.map

theorem IsRoot.of_map {R} [CommRing R] {f : R →+* S} {x : R} {p : R[X]} (h : IsRoot (p.map f) (f x))
    (hf : Function.Injective f) : IsRoot p x := by
  rwa [IsRoot, ← (injective_iff_map_eq_zero' f).mp hf, ← eval₂_hom, ← eval_map]
#align polynomial.is_root.of_map Polynomial.IsRoot.of_map

theorem isRoot_map_iff {R : Type _} [CommRing R] {f : R →+* S} {x : R} {p : R[X]}
    (hf : Function.Injective f) : IsRoot (p.map f) (f x) ↔ IsRoot p x :=
  ⟨fun h => h.of_map hf, fun h => h.map⟩
#align polynomial.is_root_map_iff Polynomial.isRoot_map_iff

end Map

end CommSemiring

section Ring

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

theorem C_neg : C (-a) = -C a :=
  RingHom.map_neg C a
#align polynomial.C_neg Polynomial.C_neg

theorem C_sub : C (a - b) = C a - C b :=
  RingHom.map_sub C a b
#align polynomial.C_sub Polynomial.C_sub

@[simp]
protected theorem map_sub {S} [Ring S] (f : R →+* S) : (p - q).map f = p.map f - q.map f :=
  (mapRingHom f).map_sub p q
#align polynomial.map_sub Polynomial.map_sub

@[simp]
protected theorem map_neg {S} [Ring S] (f : R →+* S) : (-p).map f = -p.map f :=
  (mapRingHom f).map_neg p
#align polynomial.map_neg Polynomial.map_neg

@[simp]
theorem map_int_cast {S} [Ring S] (f : R →+* S) (n : ℤ) : map f ↑n = ↑n :=
  map_intCast (mapRingHom f) n
#align polynomial.map_int_cast Polynomial.map_int_cast

@[simp]
theorem eval_int_cast {n : ℤ} {x : R} : (n : R[X]).eval x = n := by
  simp only [← C_eq_int_cast, eval_C]
#align polynomial.eval_int_cast Polynomial.eval_int_cast

@[simp]
theorem eval₂_neg {S} [Ring S] (f : R →+* S) {x : S} : (-p).eval₂ f x = -p.eval₂ f x := by
  rw [eq_neg_iff_add_eq_zero, ← eval₂_add, add_left_neg, eval₂_zero]
#align polynomial.eval₂_neg Polynomial.eval₂_neg

@[simp]
theorem eval₂_sub {S} [Ring S] (f : R →+* S) {x : S} :
    (p - q).eval₂ f x = p.eval₂ f x - q.eval₂ f x := by
  rw [sub_eq_add_neg, eval₂_add, eval₂_neg, sub_eq_add_neg]
#align polynomial.eval₂_sub Polynomial.eval₂_sub

@[simp]
theorem eval_neg (p : R[X]) (x : R) : (-p).eval x = -p.eval x :=
  eval₂_neg _
#align polynomial.eval_neg Polynomial.eval_neg

@[simp]
theorem eval_sub (p q : R[X]) (x : R) : (p - q).eval x = p.eval x - q.eval x :=
  eval₂_sub _
#align polynomial.eval_sub Polynomial.eval_sub

theorem root_X_sub_C : IsRoot (X - C a) b ↔ a = b := by
  rw [IsRoot.def, eval_sub, eval_X, eval_C, sub_eq_zero, eq_comm]
#align polynomial.root_X_sub_C Polynomial.root_X_sub_C

@[simp]
theorem neg_comp : (-p).comp q = -p.comp q :=
  eval₂_neg _
#align polynomial.neg_comp Polynomial.neg_comp

@[simp]
theorem sub_comp : (p - q).comp r = p.comp r - q.comp r :=
  eval₂_sub _
#align polynomial.sub_comp Polynomial.sub_comp

@[simp]
theorem cast_int_comp (i : ℤ) : comp (i : R[X]) p = i := by cases i <;> simp
#align polynomial.cast_int_comp Polynomial.cast_int_comp

end Ring

end Polynomial


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

/-!
# Divisibility properties of binomial coefficients

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

namespace nat

open_locale nat

namespace prime

lemma dvd_choose_add {p a b : ℕ} (hp : prime p) (hap : a < p) (hbp : b < p) (h : p ≤ a + b) :
  p ∣ choose (a + b) a :=
begin
  have h₁ : p ∣ (a + b)!, from hp.dvd_factorial.2 h,
  rw [← add_choose_mul_factorial_mul_factorial, ← choose_symm_add, hp.dvd_mul, hp.dvd_mul,
    hp.dvd_factorial, hp.dvd_factorial] at h₁,
  exact (h₁.resolve_right hbp.not_le).resolve_right hap.not_le
end

lemma dvd_choose {p a b : ℕ} (hp : prime p) (ha : a < p) (hab : b - a < p) (h : p ≤ b) :
  p ∣ choose b a :=
have a + (b - a) = b := nat.add_sub_of_le (ha.le.trans h),
this ▸ hp.dvd_choose_add ha hab (this.symm ▸ h)

lemma dvd_choose_self {p k : ℕ} (hp : prime p) (hk : k ≠ 0) (hkp : k < p) : p ∣ choose p k :=
hp.dvd_choose hkp (nat.sub_lt ((zero_le _).trans_lt hkp) hk.bot_lt) le_rfl

end prime

end nat


/-
Copyright (c) 2019 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro
-/

import data.rat.order
import data.int.sqrt
/-!
# Square root on rational numbers

This file defines the square root function on rational numbers `rat.sqrt`
and proves several theorems about it.

-/
namespace rat

/-- Square root function on rational numbers, defined by taking the (integer) square root of the
numerator and the square root (on natural numbers) of the denominator. -/
@[pp_nodot] def sqrt (q : ℚ) : ℚ := rat.mk (int.sqrt q.num) (nat.sqrt q.denom)

theorem sqrt_eq (q : ℚ) : rat.sqrt (q*q) = |q| :=
by rw [sqrt, mul_self_num, mul_self_denom, int.sqrt_eq, nat.sqrt_eq, abs_def]

theorem exists_mul_self (x : ℚ) : (∃ q, q * q = x) ↔ rat.sqrt x * rat.sqrt x = x :=
⟨λ ⟨n, hn⟩, by rw [← hn, sqrt_eq, abs_mul_abs_self],
λ h, ⟨rat.sqrt x, h⟩⟩

theorem sqrt_nonneg (q : ℚ) : 0 ≤ rat.sqrt q :=
nonneg_iff_zero_le.1 $ (mk_nonneg _ $ int.coe_nat_pos.2 $
nat.pos_of_ne_zero $ λ H, pos_iff_ne_zero.1 q.pos $ nat.sqrt_eq_zero.1 H).2
$ int.coe_nat_nonneg _

end rat


import tactic
import data.real.basic
import data.int.gcd
import data.padics

------------------------------------------------------------------------
-- § Primeros ejercicios                                              --
------------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Calcular el tipo del 3.
-- ----------------------------------------------------------------------

#check 3

-- Comentario: Al colocar el cursor sobre check se obtiene
--    3 : ℕ
-- que indica que 3 es un número natural.

-- ---------------------------------------------------------------------
-- Ejercicio. Calcular el tipo del (3 : ℤ).
-- ----------------------------------------------------------------------

#check (3 : ℤ)

-- Comentario: Al colocar el cursor sobre check se obtiene
--    3 : ℤ
-- que indica que 3 es un número entero.

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que el número entero 3 es igual que el número
-- natural 3.
-- ----------------------------------------------------------------------

example : (3 : ℤ) = (3 : ℕ) :=
by norm_num

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que el número real 3 es menor que 5.
-- ----------------------------------------------------------------------

example : (3 : ℝ) < 5 :=
by norm_num

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que 3 es menor que 5.
-- ----------------------------------------------------------------------

example : 3 < 5 :=
by norm_num

-- ----------------------------------------------------------------------
-- Ejercicio. Demostrar que 3 más 20 es menor que 5 por 10.
-- ----------------------------------------------------------------------

example : 3 + 20 < 5 * 10 :=
by norm_num

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que 11 es primo.
-- ----------------------------------------------------------------------

example : nat.prime 11 :=
by norm_num

-- ---------------------------------------------------------------------
-- Ejercicio. Calcular el valor de las siguientes expresiones
--    2 - 1
--    2 - 2
--    2 - 3
--    (2 : ℤ) - 3
--    6 / 3
--    6 / 4
--    (6 : ℚ) / 4
-- ----------------------------------------------------------------------

#eval 2 - 1
#eval 2 - 2
#eval 2 - 3
#eval (2 : ℤ) - 3
#eval 6 / 3
#eval 6 / 4
#eval (6 : ℚ) / 4

-- Comentario: Al colocar el cursor sobre eval se obtiene
--    2 - 1       = 1
--    2 - 2       = 0
--    2 - 3       = 0
--    (2 : ℤ) - 3 = -1
--    6 / 3       = 2
--    6 / 4       = 1
--    (6 : ℚ) / 4 = 3/2

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si a, b y c son número naturales y, para
-- todo entero b se tiene que (b + a < c + a), entonces (b < c).
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

example
  (a b c : ℕ)
  (h : (b : ℤ) + a < c + a)
  : b < c :=
begin
  rw ← (sub_lt_sub_iff_right (a : ℤ)) at h,
  rw add_sub_cancel at h,
  rw add_sub_cancel at h,
  norm_cast at h,
  exact h,
end

-- Prueba
-- ======

/-
a b c : ℕ,
h : ↑b + ↑a < ↑c + ↑a
⊢ b < c
  >> rw ← (sub_lt_sub_iff_right (a : ℤ)) at h,
h : ↑b + ↑a - ↑a < ↑c + ↑a - ↑a
⊢ b < c
  >> rw add_sub_cancel at h,
h : ↑b < ↑c + ↑a - ↑a
⊢ b < c
  >> rw add_sub_cancel at h,
h : ↑b < ↑c
⊢ b < c
  >> norm_cast at h,
h : b < c
⊢ b < c
  >> exact h,
no goals
-/

-- Comentarios:
-- 1. Se han usado los lemas
--    + sub_lt_sub_iff_right : a - c < b - c ↔ a < b
--    + add_sub_cancel : a + b - b = a
-- 2. La táctica (norma_cast at h) elimina las conversiones de la
--    hipótesis h.

-- 2ª demostración
-- ===============

example
  (a b c : ℕ)
  (h : (b : ℤ) + a < c + a)
  : b < c :=
begin
  rw ← (sub_lt_sub_iff_right (a : ℤ)) at h,
  rw add_sub_cancel at h,
  rw add_sub_cancel at h,
  exact_mod_cast h,
end

-- Prueba
-- ======

/-
a b c : ℕ,
h : ↑b + ↑a < ↑c + ↑a
⊢ b < c
  >> rw ← (sub_lt_sub_iff_right (a : ℤ)) at h,
h : ↑b + ↑a - ↑a < ↑c + ↑a - ↑a
⊢ b < c
  >> rw add_sub_cancel at h,
h : ↑b < ↑c + ↑a - ↑a
⊢ b < c
  >> rw add_sub_cancel at h,
h : ↑b < ↑c
⊢ b < c
  >> exact_mod_cast h,
no goals
-/

-- Comentarios:
-- 1. La táctica (exact_mod_cast h) normaliza el objetivo y lo resuelve
--    con exact.

-- 3ª demostración
-- ===============

example
  (a b c : ℕ)
  (h : (b : ℤ) + a < c + a)
  : b < c :=
begin
  rw ← (sub_lt_sub_iff_right (a : ℤ)) at h,
  rw add_sub_cancel at h,
  rw add_sub_cancel at h,
  assumption_mod_cast,
end

-- Prueba
-- ======

/-
a b c : ℕ,
h : ↑b + ↑a < ↑c + ↑a
⊢ b < c
  >> rw ← (sub_lt_sub_iff_right (a : ℤ)) at h,
h : ↑b + ↑a - ↑a < ↑c + ↑a - ↑a
⊢ b < c
  >> rw add_sub_cancel at h,
h : ↑b < ↑c + ↑a - ↑a
⊢ b < c
  >> rw add_sub_cancel at h,
h : ↑b < ↑c
⊢ b < c
  >> assumption_mod_cast,
no goals
-/

-- Comentarios:
-- 1. La táctica assumption_cast unifica el objetivo con una de las
--    hipótesis eliminando la conversión de tipos.

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si n es un número primo, entonces
--    1 / n < 1
-- ----------------------------------------------------------------------

variable n : ℕ

-- 1ª demostración
-- ===============

example
  (hn : nat.prime n)
  : 1 / (n : ℝ) < 1 :=
begin
  rw one_div_lt,
  { norm_num,
    norm_cast,
    exact nat.prime.one_lt hn },
  { norm_cast,
    exact nat.prime.pos hn },
  { norm_num },
end

-- Prueba
-- ======

/-
n : ℕ,
hn : nat.prime n
⊢ 1 / ↑n < 1
  >> rw one_div_lt,
| | 3 goals
| | n : ℕ,
| | hn : nat.prime n
| | ⊢ 1 / 1 < ↑n
| |   >> { norm_num,
| | ⊢ 1 < ↑n
| |   >>   norm_cast,
| | ⊢ 1 < n
| |   >>   exact nat.prime.one_lt hn },
| 2 goals
| n : ℕ,
| hn : nat.prime n
| ⊢ 0 < ↑n
|   >> { norm_cast,
| ⊢ 0 < n
|   >>   exact nat.prime.pos hn },
n : ℕ,
hn : nat.prime n
⊢ 0 < 1
  >> { norm_num },
no goals
-/

-- Comentarios:
-- 1. Se han usado los lemas
--    + one_div_lt : 0 < a → 0 < b → (1 / a < b ↔ 1 / b < a)
--    + nat.prime.one_lt : nat.prime n → 1 < n
--    + nat.prime.pos : nat.prime n → 0 < n

-- 2ª demostración
-- ===============

example
  (hn : nat.prime n)
  : 1 / (n : ℝ) < 1 :=
begin
  rw one_div_lt,
  { norm_num,
    apply_mod_cast nat.prime.one_lt,
    assumption },
  { norm_cast,
    exact nat.prime.pos hn },
  { norm_num },
end

-- Comentario: La táctica (apply_mod_cast h) aplica h con conversiones
-- de tipos.

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que en los cuerpos totalmente ordenados,
--    123 + 45 < 67890/3
-- ----------------------------------------------------------------------

example {α : Type} [linear_ordered_field α] : 123 + 45 < 67890/3 :=
by norm_num

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que 7/3 no es mayor que 2.
-- ----------------------------------------------------------------------

example : ¬ 7/3 > 2 :=
by norm_num

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si el número real x es menor que 50 * 50,
-- entonces también es menor que 25 * 100.
-- ----------------------------------------------------------------------

example
  (x : ℝ)
  (hx : x < 50*50)
  : x < 25*100 :=
begin
  norm_num at hx ⊢,
  assumption,
end

-- Prueba
-- ======

/-
x : ℝ,
hx : x < 50 * 50
⊢ x < 25 * 100
  >> norm_num at hx ⊢,
hx : x < 2500
⊢ x < 2500
  >> assumption,
no goals
-/

-- Comentario: La táctica (norm_num at h ⊢) normaliza las expresiones
-- numéricas en la hipótesis h y en la conclusión.

-- ---------------------------------------------------------------------
-- Ejercicio. Sea x un número entero. Demostrar que si x (como número
-- real) es menor que 25*100, entonces x es menor que 25*100.
-- ----------------------------------------------------------------------

example
  (x : ℤ)
  (hx : (x : ℝ) < 25*100)
  : x < 25*100 :=
begin
  assumption_mod_cast,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Sea x un número entero. Demostrar que si x (como número
-- real) es menor que 2500, entonces x es menor que 25*100.
-- ----------------------------------------------------------------------

example
  (x : ℤ)
  (hx : (x : ℝ) < 2500)
  : x < 25*100 :=
begin
  norm_num,
  assumption_mod_cast,
end

-- Prueba
-- ======

/-
x : ℤ,
hx : ↑x < 2500
⊢ x < 25 * 100
  >> norm_num,
hx : ↑x < 2500
⊢ x < 2500
  >> assumption_mod_cast,
no goals
-/

-- ---------------------------------------------------------------------
-- Ejercicio. Sean p, q y r números naturales. Demostrar que si
--    r < p - q
--    q ≤ p
-- entonces r (como número real) es menor que p - q.
-- ----------------------------------------------------------------------

example
  (p q r : ℕ)
  (h : r < p - q)
  (hpq : q ≤ p)
  : (r : ℝ) < p - q :=
begin
  exact_mod_cast h,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Sean p, q y r números naturales tales que (r < p + 2 - p).
-- Demostrar que r (como entero) es menor que 5.
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

example
  (p q r : ℕ)
  (hr : r < p + 2 - p)
  : (r : ℤ) < 5 :=
begin
  have : p ≤ p + 2, by linarith,
  zify [this] at hr,
  linarith,
end

-- Prueba
-- ======

/-
p q r : ℕ,
hr : r < p + 2 - p
⊢ ↑r < 5
  >> have : p ≤ p + 2, by linarith,
this : p ≤ p + 2
⊢ ↑r < 5
  >> zify [this] at hr,
hr : ↑r < ↑p + 2 - ↑p
⊢ ↑r < 5
  >> linarith,
-/

-- 2ª demostración
-- ===============

example
  (p q r : ℕ)
  (hr : r < p + 2 - p)
  : (r : ℤ) < 5 :=
begin
  norm_num at hr,
  norm_cast,
  linarith,
end

-- Prueba
-- ======

/-
p q r : ℕ,
hr : r < p + 2 - p
⊢ ↑r < 5
  >> norm_num at hr,
hr : r < 2
⊢ ↑r < 5
  >> norm_cast,
hr : r < 2
⊢ r < 5
  >> linarith,
no goals
-/

------------------------------------------------------------------------
-- § Números p-ádicos                                                 --
------------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Abrir la teoría padic_val_rat
-- ----------------------------------------------------------------------

open padic_val_rat

-- ---------------------------------------------------------------------
-- Ejercicio. Calcular el tipo de los siguentes lemas
--    fpow_le_of_le
--    fpow_nonneg_of_nonneg
--    padic_val_rat_of_int
-- ----------------------------------------------------------------------

#check fpow_le_of_le
#check fpow_nonneg_of_nonneg
#check padic_val_rat_of_int

-- Comentario: Al colocar el cursor sobre check se obtiene
-- + fpow_le_of_le : 1 ≤ x → ∀ {a b : ℤ}, a ≤ b → x ^ a ≤ x ^ b
-- + fpow_nonneg_of_nonneg : 0 ≤ x → ∀ (z : ℤ), 0 ≤ x ^ z
-- + padic_val_rat_of_int :
--    ∀ (z : ℤ) (hp : x ≠ 1) (hz : z ≠ 0),
--    padic_val_rat x ↑z = ↑((multiplicity ↑x z).get _)

-- ---------------------------------------------------------------------
-- Ejercicio. Sean p y n números naturales y z un número entero.
-- Demostrar que si p es un número primo y p^n divide a z, entonces
--    padic_norm p z ≤ ↑p ^ (-n : ℤ)
-- ----------------------------------------------------------------------

example
  {p n : ℕ}
  (hp : p.prime)
  {z : ℤ}
  (hd : ↑(p^n) ∣ z)
  : padic_norm p z ≤ ↑p ^ (-n : ℤ) :=
begin
  have aux_lemma : ∀ inst, (n : ℤ) ≤ (multiplicity ↑p z).get inst,
  { intro,
    norm_cast,
    rw [← enat.coe_le_coe, enat.coe_get],
    apply multiplicity.le_multiplicity_of_pow_dvd,
    assumption_mod_cast },
  unfold padic_norm,
  split_ifs with hz hz,
  { apply fpow_nonneg_of_nonneg,
    exact_mod_cast le_of_lt hp.pos },
  { apply fpow_le_of_le,
    exact_mod_cast le_of_lt hp.one_lt,
    apply neg_le_neg,
    rw padic_val_rat_of_int _ hp.ne_one _,
    { apply aux_lemma },
    { assumption_mod_cast } }
end

------------------------------------------------------------------------
-- § Coprimos                                                         --
------------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si p y q son coprimos, entonces existen
-- enteros u y v tales que
--    u*p+v*q = 1
-- ----------------------------------------------------------------------

example
  (p q : ℕ)
  (h : nat.coprime p q)
  : ∃ u v : ℤ, u*p+v*q = 1 :=
begin
  have := nat.gcd_eq_gcd_ab,
  specialize this p q,
  unfold nat.coprime at h,
  rw h at this,
  norm_cast at this,
  use [p.gcd_a q, p.gcd_b q],
  rw this,
  ring,
end

-- Prueba
-- ======

/-
p q : ℕ,
h : p.coprime q
⊢ ∃ (u v : ℤ), u * ↑p + v * ↑q = 1
  >> have := nat.gcd_eq_gcd_ab,
this : ∀ (x y : ℕ), ↑(x.gcd y) = ↑x * x.gcd_a y + ↑y * x.gcd_b y
⊢ ∃ (u v : ℤ), u * ↑p + v * ↑q = 1
  >> specialize this p q,
this : ↑(p.gcd q) = ↑p * p.gcd_a q + ↑q * p.gcd_b q
⊢ ∃ (u v : ℤ), u * ↑p + v * ↑q = 1
  >> unfold nat.coprime at h,
h : p.gcd q = 1
⊢ ∃ (u v : ℤ), u * ↑p + v * ↑q = 1
  >> rw h at this,
this : ↑1 = ↑p * p.gcd_a q + ↑q * p.gcd_b q
⊢ ∃ (u v : ℤ), u * ↑p + v * ↑q = 1
  >> norm_cast at this,
this : 1 = ↑p * p.gcd_a q + ↑q * p.gcd_b q
⊢ ∃ (u v : ℤ), u * ↑p + v * ↑q = 1
  >> use [p.gcd_a q, p.gcd_b q],
⊢ p.gcd_a q * ↑p + p.gcd_b q * ↑q = 1
  >> rw this,
⊢ p.gcd_a q * ↑p + p.gcd_b q * ↑q = ↑p * p.gcd_a q + ↑q * p.gcd_b q
  >> ring,
no goals
-/

------------------------------------------------------------------------
-- § Límite de una sucesión                                           --
------------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Representar con |x| el valor absoluto de x.
-- ----------------------------------------------------------------------

notation `|`x`|` := abs x

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--     seq_limit : (ℕ → ℝ) → ℝ → Prop
-- tal que (seq_limit u l) afirma que l es el límite de la sucesión u.
-- ----------------------------------------------------------------------

def seq_limit : (ℕ → ℝ) → ℝ → Prop :=
λ u l, ∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que el límite de la sucesión (n+1)/n es 1.
-- ----------------------------------------------------------------------

example : seq_limit (λ n : ℕ, (n+1)/n) 1 :=
begin
  intros ε ε_pos,
  dsimp,
  use nat_ceil (1/ε),
  intros n hn,
  have n_pos : 0 < n,
    { calc 0 < nat_ceil (1/ε) : _
         ... ≤ n              : _,
      { rw lt_nat_ceil,
        simp,
        assumption },
      { assumption } },
  rw [abs_of_nonneg,
      sub_le_iff_le_add,
      div_le_iff,
      add_mul,
      one_mul,
      add_comm _ (n : ℝ),
      add_le_add_iff_left],
  { calc 1 = ε * (1/ε) : _
       ... ≤ ε * nat_ceil (1/ε) : _
       ... ≤ ε * n : _,
    { symmetry,
      apply mul_one_div_cancel,
      linarith },
    { rw mul_le_mul_left ε_pos,
      apply le_nat_ceil },
    { rw mul_le_mul_left ε_pos,
      exact_mod_cast hn } },
  { assumption_mod_cast },
  { field_simp,
    apply one_le_div_of_le;
    norm_cast;
    linarith }
end

------------------------------------------------------------------------
-- § Referencia                                                       --
------------------------------------------------------------------------

-- Basado en la teoría numbers.lean de Robert Y. Lewis que se
-- encuentra en https://bit.ly/39teUbt y se comenta en el vídeo
-- "Numbers in Lean" que se encuentra en https://youtu.be/iEs2U_kzYy4


/-
Copyright (c) 2020 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.tactic.apply_fun
import Mathlib.ring_theory.matrix_algebra
import Mathlib.ring_theory.polynomial_algebra
import Mathlib.linear_algebra.nonsingular_inverse
import Mathlib.tactic.squeeze
import Mathlib.PostPort

universes u w

namespace Mathlib

/-!
# Characteristic polynomials and the Cayley-Hamilton theorem

We define characteristic polynomials of matrices and
prove the Cayley–Hamilton theorem over arbitrary commutative rings.

## Main definitions

* `char_poly` is the characteristic polynomial of a matrix.

## Implementation details

We follow a nice proof from http://drorbn.net/AcademicPensieve/2015-12/CayleyHamilton.pdf
-/

/--
The "characteristic matrix" of `M : matrix n n R` is the matrix of polynomials $t I - M$.
The determinant of this matrix is the characteristic polynomial.
-/
def char_matrix {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n] [fintype n]
    (M : matrix n n R) : matrix n n (polynomial R) :=
  coe_fn (matrix.scalar n) polynomial.X - coe_fn (ring_hom.map_matrix polynomial.C) M

@[simp] theorem char_matrix_apply_eq {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n]
    [fintype n] (M : matrix n n R) (i : n) :
    char_matrix M i i = polynomial.X - coe_fn polynomial.C (M i i) :=
  sorry

@[simp] theorem char_matrix_apply_ne {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n]
    [fintype n] (M : matrix n n R) (i : n) (j : n) (h : i ≠ j) :
    char_matrix M i j = -coe_fn polynomial.C (M i j) :=
  sorry

theorem mat_poly_equiv_char_matrix {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n]
    [fintype n] (M : matrix n n R) :
    coe_fn mat_poly_equiv (char_matrix M) = polynomial.X - coe_fn polynomial.C M :=
  sorry

/--
The characteristic polynomial of a matrix `M` is given by $\det (t I - M)$.
-/
def char_poly {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n] [fintype n]
    (M : matrix n n R) : polynomial R :=
  matrix.det (char_matrix M)

/--
The Cayley-Hamilton theorem, that the characteristic polynomial of a matrix,
applied to the matrix itself, is zero.

This holds over any commutative ring.
-/
-- This proof follows http://drorbn.net/AcademicPensieve/2015-12/CayleyHamilton.pdf

theorem aeval_self_char_poly {R : Type u} [comm_ring R] {n : Type w} [DecidableEq n] [fintype n]
    (M : matrix n n R) : coe_fn (polynomial.aeval M) (char_poly M) = 0 :=
  sorry

end Mathlib


/-
Copyright (c) 2021 Yakov Pechersky. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yakov Pechersky
-/
import algebra.polynomial.big_operators
import data.polynomial.degree.lemmas
import data.polynomial.eval
import data.polynomial.monic
import linear_algebra.matrix.determinant

/-!
# Matrices of polynomials and polynomials of matrices

In this file, we prove results about matrices over a polynomial ring.
In particular, we give results about the polynomial given by
`det (t * I + A)`.

## References

  * "The trace Cayley-Hamilton theorem" by Darij Grinberg, Section 5.3

## Tags

matrix determinant, polynomial
-/

open_locale matrix big_operators

variables {n α : Type*} [decidable_eq n] [fintype n] [comm_ring α]

open polynomial matrix equiv.perm

namespace polynomial

lemma nat_degree_det_X_add_C_le (A B : matrix n n α) :
  nat_degree (det ((X : polynomial α) • A.map C + B.map C)) ≤ fintype.card n :=
begin
  rw det_apply,
  refine (nat_degree_sum_le _ _).trans _,
  refine (multiset.max_nat_le_of_forall_le _ _ _),
  simp only [forall_apply_eq_imp_iff', true_and, function.comp_app, multiset.map_map,
               multiset.mem_map, exists_imp_distrib, finset.mem_univ_val],
  intro g,
  calc  nat_degree (sign g • ∏ (i : n), (X • A.map C + B.map C) (g i) i)
      ≤ nat_degree (∏ (i : n), (X • A.map C + B.map C) (g i) i) : by
    { cases int.units_eq_one_or (sign g) with sg sg,
        { rw [sg, one_smul] },
        { rw [sg, units.neg_smul, one_smul, nat_degree_neg] } }
  ... ≤ ∑ (i : n), nat_degree (((X : polynomial α) • A.map C + B.map C) (g i) i) :
    nat_degree_prod_le (finset.univ : finset n) (λ (i : n), (X • A.map C + B.map C) (g i) i)
  ... ≤ finset.univ.card • 1 : finset.sum_le_of_forall_le _ _ 1 (λ (i : n) _, _)
  ... ≤ fintype.card n : by simpa,
  calc  nat_degree (((X : polynomial α) • A.map C + B.map C) (g i) i)
      = nat_degree ((X : polynomial α) * C (A (g i) i) + C (B (g i) i)) : by simp
  ... ≤ max (nat_degree ((X : polynomial α) * C (A (g i) i))) (nat_degree (C (B (g i) i))) :
    nat_degree_add_le _ _
  ... = nat_degree ((X : polynomial α) * C (A (g i) i)) :
    max_eq_left ((nat_degree_C _).le.trans (zero_le _))
  ... ≤ nat_degree (X : polynomial α) : nat_degree_mul_C_le _ _
  ... ≤ 1 : nat_degree_X_le
end

lemma coeff_det_X_add_C_zero (A B : matrix n n α) :
  coeff (det ((X : polynomial α) • A.map C + B.map C)) 0 = det B :=
begin
  rw [det_apply, finset_sum_coeff, det_apply],
  refine finset.sum_congr rfl _,
  intros g hg,
  convert coeff_smul (sign g) _ 0,
  rw coeff_zero_prod,
  refine finset.prod_congr rfl _,
  simp
end

lemma coeff_det_X_add_C_card (A B : matrix n n α) :
  coeff (det ((X : polynomial α) • A.map C + B.map C)) (fintype.card n) = det A :=
begin
  rw [det_apply, det_apply, finset_sum_coeff],
  refine finset.sum_congr rfl _,
  simp only [algebra.id.smul_eq_mul, finset.mem_univ, ring_hom.map_matrix_apply, forall_true_left,
             map_apply, pi.smul_apply],
  intros g,
  convert coeff_smul (sign g) _ _,
  rw ←mul_one (fintype.card n),
  convert (coeff_prod_of_nat_degree_le _ _ _ _).symm,
  { ext,
    simp [coeff_C] },
  { intros p hp,
    refine (nat_degree_add_le _ _).trans _,
    simpa using (nat_degree_mul_C_le _ _).trans nat_degree_X_le }
end

lemma leading_coeff_det_X_one_add_C (A : matrix n n α) :
  leading_coeff (det ((X : polynomial α) • (1 : matrix n n (polynomial α)) + A.map C)) = 1 :=
begin
  casesI (subsingleton_or_nontrivial α),
  { simp },
  rw [←@det_one n, ←coeff_det_X_add_C_card _ A, leading_coeff],
  simp only [matrix.map_one, C_eq_zero, ring_hom.map_one],
  cases (nat_degree_det_X_add_C_le 1 A).eq_or_lt with h h,
  { simp only [ring_hom.map_one, matrix.map_one, C_eq_zero] at h,
    rw h },
  { -- contradiction. we have a hypothesis that the degree is less than |n|
    -- but we know that coeff _ n = 1
    have H := coeff_eq_zero_of_nat_degree_lt h,
    rw coeff_det_X_add_C_card at H,
    simpa using H }
end

end polynomial


import tactic

variables (x y : ℕ)

open nat

theorem Q1a : x + y = y + x :=
begin
  induction y with d hd,
  { rw [add_zero, zero_add]},
  { rw [add_succ, succ_add, hd]}
end

theorem Q1b : x + y = x → y = 0 :=
begin
  intro h,
  induction x with d hd,
  { convert h, rw zero_add},
  { apply hd,
    rw succ_add at h,
    rw ← succ_inj',
    assumption,
  }
end

theorem Q1c : x + y = 0 → x = 0 ∧ y = 0 :=
begin
  intro h,
  induction y with d hd,
  { split,
      exact h,
    refl,
  },
  { rw add_succ at h,
    exfalso,
    apply succ_ne_zero (x + d),
    assumption },
end

theorem Q1d : x * y = y * x :=
begin
  induction y with d hd,
  { rw [mul_zero, zero_mul]},
  { rw [mul_succ, succ_mul, hd]},
end

theorem Q2a : 1 * x = x ∧ x = x * 1 :=
begin
  split,
  { induction x with d hd,
      refl,
    rw [mul_succ,hd],
  },
  rw [mul_succ, mul_zero, zero_add],
end

variable z : ℕ

theorem Q2b : (x + y) * z = x * z + y * z :=
begin
  induction z with d hd,
    refl,
  rw [mul_succ, hd, mul_succ, mul_succ],
  ac_refl,
end

theorem Q2c : (x * y) * z = x * (y * z) :=
begin
  induction z with d hd,
  { refl },
  { rw [mul_succ, mul_succ, hd, mul_add] }
end

-- Q3 def
def is_pred (x y : ℕ) := x.succ = y

theorem Q3a : ¬ ∃ x : ℕ, is_pred x 0 :=
begin
  intro h,
  cases h with x hx,
  unfold is_pred at hx,
  apply succ_ne_zero x,
  assumption,
end

theorem Q3b : y ≠ 0 → ∃! x, is_pred x y :=
begin
  intro hy,
  cases y,
    exfalso,
    apply hy,
    refl,
  clear hy,
  use y,
  split,
  { dsimp only,
    unfold is_pred,
  },
  intro z,
  dsimp only [is_pred],
  exact succ_inj'.1,
end

def aux : 0 < y → ∃ x, is_pred x y :=
begin
  intro hy,
  cases Q3b _ (ne_of_lt hy).symm with x hx,
  use x,
  exact hx.1,
end

-- definition of pred' is "choose a random d such that succ(d) = n"
noncomputable def pred' : ℕ+ → ℕ := λ nhn, classical.some (aux nhn nhn.2)

theorem pred'_def : ∀ np : ℕ+, is_pred (pred' np) np :=
λ nhn, classical.some_spec (aux nhn nhn.2)

def succ' : ℕ → ℕ+ :=
λ n, ⟨n.succ, zero_lt_succ n⟩

noncomputable definition Q3c : ℕ+ ≃ ℕ :=
{ to_fun := pred',
  inv_fun := succ',
  left_inv := begin
    rintro np,
    have h := pred'_def,
    unfold succ',
    ext, dsimp,
    unfold is_pred at h,
    rw h,
  end,
  right_inv := begin
    intro n,
    unfold succ',
    have h := pred'_def,
    unfold is_pred at h,
    rw ← succ_inj',
    rw h,
    clear h,
    refl,
  end
   }


/-
Copyright (c) 2022 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/
import algebra.order.floor
import data.nat.log

/-!
# Integer logarithms in a field with respect to a natural base

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

This file defines two `ℤ`-valued analogs of the logarithm of `r : R` with base `b : ℕ`:

* `int.log b r`: Lower logarithm, or floor **log**. Greatest `k` such that `↑b^k ≤ r`.
* `int.clog b r`: Upper logarithm, or **c**eil **log**. Least `k` such that `r ≤ ↑b^k`.

Note that `int.log` gives the position of the left-most non-zero digit:
```lean
#eval (int.log 10 (0.09 : ℚ), int.log 10 (0.10 : ℚ), int.log 10 (0.11 : ℚ))
--    (-2,                    -1,                    -1)
#eval (int.log 10 (9 : ℚ),    int.log 10 (10 : ℚ),   int.log 10 (11 : ℚ))
--    (0,                     1,                     1)
```
which means it can be used for computing digit expansions
```lean
import data.fin.vec_notation

def digits (b : ℕ) (q : ℚ) (n : ℕ) : ℕ :=
⌊q*b^(↑n - int.log b q)⌋₊ % b

#eval digits 10 (1/7) ∘ (coe : fin 8 → ℕ)
-- ![1, 4, 2, 8, 5, 7, 1, 4]
```

## Main results

* For `int.log`:
  * `int.zpow_log_le_self`, `int.lt_zpow_succ_log_self`: the bounds formed by `int.log`,
    `(b : R) ^ log b r ≤ r < (b : R) ^ (log b r + 1)`.
  * `int.zpow_log_gi`: the galois coinsertion between `zpow` and `int.log`.
* For `int.clog`:
  * `int.zpow_pred_clog_lt_self`, `int.self_le_zpow_clog`: the bounds formed by `int.clog`,
    `(b : R) ^ (clog b r - 1) < r ≤ (b : R) ^ clog b r`.
  * `int.clog_zpow_gi`:  the galois insertion between `int.clog` and `zpow`.
* `int.neg_log_inv_eq_clog`, `int.neg_clog_inv_eq_log`: the link between the two definitions.
-/

variables {R : Type*} [linear_ordered_semifield R] [floor_semiring R]

namespace int

/-- The greatest power of `b` such that `b ^ log b r ≤ r`. -/
def log (b : ℕ) (r : R) : ℤ :=
if 1 ≤ r then
  nat.log b ⌊r⌋₊
else
  -nat.clog b ⌈r⁻¹⌉₊

lemma log_of_one_le_right (b : ℕ) {r : R} (hr : 1 ≤ r) : log b r = nat.log b ⌊r⌋₊ :=
if_pos hr

lemma log_of_right_le_one (b : ℕ) {r : R} (hr : r ≤ 1) : log b r = -nat.clog b ⌈r⁻¹⌉₊ :=
begin
  obtain rfl | hr := hr.eq_or_lt,
  { rw [log, if_pos hr, inv_one, nat.ceil_one, nat.floor_one, nat.log_one_right, nat.clog_one_right,
        int.coe_nat_zero, neg_zero], },
  { exact if_neg hr.not_le }
end

@[simp, norm_cast] lemma log_nat_cast (b : ℕ) (n : ℕ) : log b (n : R) = nat.log b n :=
begin
  cases n,
  { simp [log_of_right_le_one _ _, nat.log_zero_right] },
  { have : 1 ≤ (n.succ : R) := by simp,
    simp [log_of_one_le_right _ this, ←nat.cast_succ] }
end

lemma log_of_left_le_one {b : ℕ} (hb : b ≤ 1) (r : R) : log b r = 0 :=
begin
  cases le_total 1 r,
  { rw [log_of_one_le_right _ h, nat.log_of_left_le_one hb, int.coe_nat_zero] },
  { rw [log_of_right_le_one _ h, nat.clog_of_left_le_one hb, int.coe_nat_zero, neg_zero] },
end

lemma log_of_right_le_zero (b : ℕ) {r : R} (hr : r ≤ 0) : log b r = 0 :=
by rw [log_of_right_le_one _ (hr.trans zero_le_one),
    nat.clog_of_right_le_one ((nat.ceil_eq_zero.mpr $ inv_nonpos.2 hr).trans_le zero_le_one),
    int.coe_nat_zero, neg_zero]

lemma zpow_log_le_self {b : ℕ} {r : R} (hb : 1 < b) (hr : 0 < r) :
  (b : R) ^ log b r ≤ r :=
begin
  cases le_total 1 r with hr1 hr1,
  { rw log_of_one_le_right _ hr1,
    rw [zpow_coe_nat, ← nat.cast_pow, ← nat.le_floor_iff hr.le],
    exact nat.pow_log_le_self b (nat.floor_pos.mpr hr1).ne' },
  { rw [log_of_right_le_one _ hr1, zpow_neg, zpow_coe_nat, ← nat.cast_pow],
    exact inv_le_of_inv_le hr (nat.ceil_le.1 $ nat.le_pow_clog hb _) },
end

lemma lt_zpow_succ_log_self {b : ℕ} (hb : 1 < b) (r : R) :
  r < (b : R) ^ (log b r + 1) :=
begin
  cases le_or_lt r 0 with hr hr,
  { rw [log_of_right_le_zero _ hr, zero_add, zpow_one],
    exact hr.trans_lt (zero_lt_one.trans_le $ by exact_mod_cast hb.le) },
  cases le_or_lt 1 r with hr1 hr1,
  { rw log_of_one_le_right _ hr1,
    rw [int.coe_nat_add_one_out, zpow_coe_nat, ←nat.cast_pow],
    apply nat.lt_of_floor_lt,
    exact nat.lt_pow_succ_log_self hb _, },
  { rw log_of_right_le_one _ hr1.le,
    have hcri : 1 < r⁻¹ := one_lt_inv hr hr1,
    have : 1 ≤ nat.clog b ⌈r⁻¹⌉₊ :=
      nat.succ_le_of_lt (nat.clog_pos hb $ nat.one_lt_cast.1 $ hcri.trans_le (nat.le_ceil _)),
    rw [neg_add_eq_sub, ←neg_sub, ←int.coe_nat_one, ← int.coe_nat_sub this,
      zpow_neg, zpow_coe_nat, lt_inv hr (pow_pos (nat.cast_pos.mpr $ zero_lt_one.trans hb) _),
      ←nat.cast_pow],
    refine nat.lt_ceil.1 _,
    exact (nat.pow_pred_clog_lt_self hb $ nat.one_lt_cast.1 $ hcri.trans_le $ nat.le_ceil _), }
end

@[simp] lemma log_zero_right (b : ℕ) : log b (0 : R) = 0 :=
log_of_right_le_zero b le_rfl

@[simp] lemma log_one_right (b : ℕ) : log b (1 : R) = 0 :=
by rw [log_of_one_le_right _ le_rfl, nat.floor_one, nat.log_one_right, int.coe_nat_zero]

lemma log_zpow {b : ℕ} (hb : 1 < b) (z : ℤ) : log b (b ^ z : R) = z :=
begin
  obtain ⟨n, rfl | rfl⟩ := z.eq_coe_or_neg,
  { rw [log_of_one_le_right _ (one_le_zpow_of_nonneg _ $ int.coe_nat_nonneg _),
      zpow_coe_nat, ←nat.cast_pow, nat.floor_coe, nat.log_pow hb],
    exact_mod_cast hb.le, },
  { rw [log_of_right_le_one _ (zpow_le_one_of_nonpos _ $ neg_nonpos.mpr (int.coe_nat_nonneg _)),
      zpow_neg, inv_inv, zpow_coe_nat, ←nat.cast_pow, nat.ceil_nat_cast, nat.clog_pow _ _ hb],
    exact_mod_cast hb.le, },
end

@[mono] lemma log_mono_right {b : ℕ} {r₁ r₂ : R} (h₀ : 0 < r₁) (h : r₁ ≤ r₂) :
  log b r₁ ≤ log b r₂ :=
begin
  cases le_or_lt b 1 with hb hb,
  { rw [log_of_left_le_one hb, log_of_left_le_one hb], },
  cases le_total r₁ 1 with h₁ h₁; cases le_total r₂ 1 with h₂ h₂,
  { rw [log_of_right_le_one _ h₁, log_of_right_le_one _ h₂, neg_le_neg_iff, int.coe_nat_le],
    exact nat.clog_mono_right _ (nat.ceil_mono $ inv_le_inv_of_le h₀ h), },
  { rw [log_of_right_le_one _ h₁, log_of_one_le_right _ h₂],
    exact (neg_nonpos.mpr (int.coe_nat_nonneg _)).trans (int.coe_nat_nonneg _) },
  { obtain rfl := le_antisymm h (h₂.trans h₁), refl, },
  { rw [log_of_one_le_right _ h₁, log_of_one_le_right _ h₂, int.coe_nat_le],
    exact nat.log_mono_right (nat.floor_mono h), },
end

variables (R)

/-- Over suitable subtypes, `zpow` and `int.log` form a galois coinsertion -/
def zpow_log_gi {b : ℕ} (hb : 1 < b) :
  galois_coinsertion
    (λ z : ℤ, subtype.mk ((b : R) ^ z) $ zpow_pos_of_pos (by exact_mod_cast zero_lt_one.trans hb) z)
    (λ r : set.Ioi (0 : R), int.log b (r : R)) :=
galois_coinsertion.monotone_intro
  (λ r₁ r₂, log_mono_right r₁.prop)
  (λ z₁ z₂ hz, subtype.coe_le_coe.mp $ (zpow_strict_mono $ by exact_mod_cast hb).monotone hz)
  (λ r, subtype.coe_le_coe.mp $ zpow_log_le_self hb r.prop)
  (λ _, log_zpow hb _)

variables {R}

/-- `zpow b` and `int.log b` (almost) form a Galois connection. -/
lemma lt_zpow_iff_log_lt {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
  r < (b : R) ^ x ↔ log b r < x :=
@galois_connection.lt_iff_lt _ _ _ _ _ _ (zpow_log_gi R hb).gc x ⟨r, hr⟩

/-- `zpow b` and `int.log b` (almost) form a Galois connection. -/
lemma zpow_le_iff_le_log {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
  (b : R) ^ x ≤ r ↔ x ≤ log b r :=
@galois_connection.le_iff_le _ _ _ _ _ _ (zpow_log_gi R hb).gc x ⟨r, hr⟩

/-- The least power of `b` such that `r ≤ b ^ log b r`. -/
def clog (b : ℕ) (r : R) : ℤ :=
if 1 ≤ r then
  nat.clog b ⌈r⌉₊
else
  -nat.log b ⌊r⁻¹⌋₊

lemma clog_of_one_le_right (b : ℕ) {r : R} (hr : 1 ≤ r) : clog b r = nat.clog b ⌈r⌉₊ :=
if_pos hr

lemma clog_of_right_le_one (b : ℕ) {r : R} (hr : r ≤ 1) : clog b r = -nat.log b ⌊r⁻¹⌋₊ :=
begin
  obtain rfl | hr := hr.eq_or_lt,
  { rw [clog, if_pos hr, inv_one, nat.ceil_one, nat.floor_one, nat.log_one_right,
        nat.clog_one_right, int.coe_nat_zero, neg_zero], },
  { exact if_neg hr.not_le }
end

lemma clog_of_right_le_zero (b : ℕ) {r : R} (hr : r ≤ 0) : clog b r = 0 :=
begin
  rw [clog, if_neg (hr.trans_lt zero_lt_one).not_le, neg_eq_zero, int.coe_nat_eq_zero,
    nat.log_eq_zero_iff],
  cases le_or_lt b 1 with hb hb,
  { exact or.inr hb },
  { refine or.inl (lt_of_le_of_lt _ hb),
    exact nat.floor_le_one_of_le_one ((inv_nonpos.2 hr).trans zero_le_one) },
end

@[simp] lemma clog_inv (b : ℕ) (r : R) : clog b r⁻¹ = -log b r :=
begin
  cases lt_or_le 0 r with hrp hrp,
  { obtain hr | hr := le_total 1 r,
    { rw [clog_of_right_le_one _ (inv_le_one hr), log_of_one_le_right _ hr, inv_inv] },
    { rw [clog_of_one_le_right _ (one_le_inv hrp hr),  log_of_right_le_one _ hr, neg_neg] }, },
  { rw [clog_of_right_le_zero _ (inv_nonpos.mpr hrp), log_of_right_le_zero _ hrp, neg_zero], },
end

@[simp] lemma log_inv (b : ℕ) (r : R) : log b r⁻¹ = -clog b r :=
by rw [←inv_inv r, clog_inv, neg_neg, inv_inv]

-- note this is useful for writing in reverse
lemma neg_log_inv_eq_clog (b : ℕ) (r : R) : -log b r⁻¹ = clog b r :=
by rw [log_inv, neg_neg]

lemma neg_clog_inv_eq_log (b : ℕ) (r : R) : -clog b r⁻¹ = log b r :=
by rw [clog_inv, neg_neg]

@[simp, norm_cast] lemma clog_nat_cast (b : ℕ) (n : ℕ) : clog b (n : R) = nat.clog b n :=
begin
  cases n,
  { simp [clog_of_right_le_one _ _, nat.clog_zero_right] },
  { have : 1 ≤ (n.succ : R) := by simp,
    simp [clog_of_one_le_right _ this, ←nat.cast_succ] }
end

lemma clog_of_left_le_one {b : ℕ} (hb : b ≤ 1) (r : R) : clog b r = 0 :=
by rw [←neg_log_inv_eq_clog, log_of_left_le_one hb, neg_zero]

lemma self_le_zpow_clog {b : ℕ} (hb : 1 < b) (r : R) : r ≤ (b : R) ^ clog b r :=
begin
  cases le_or_lt r 0 with hr hr,
  { rw [clog_of_right_le_zero _ hr, zpow_zero],
    exact hr.trans zero_le_one },
  rw [←neg_log_inv_eq_clog, zpow_neg, le_inv hr (zpow_pos_of_pos _ _)],
  { exact zpow_log_le_self hb (inv_pos.mpr hr), },
  { exact nat.cast_pos.mpr (zero_le_one.trans_lt hb), },
end

lemma zpow_pred_clog_lt_self {b : ℕ} {r : R} (hb : 1 < b) (hr : 0 < r) :
  (b : R) ^ (clog b r - 1) < r :=
begin
  rw [←neg_log_inv_eq_clog, ←neg_add', zpow_neg, inv_lt _ hr],
  { exact lt_zpow_succ_log_self hb _, },
  { exact zpow_pos_of_pos (nat.cast_pos.mpr $ zero_le_one.trans_lt hb) _ }
end

@[simp] lemma clog_zero_right (b : ℕ) : clog b (0 : R) = 0 :=
clog_of_right_le_zero _ le_rfl

@[simp] lemma clog_one_right (b : ℕ) : clog b (1 : R) = 0 :=
by rw [clog_of_one_le_right _ le_rfl, nat.ceil_one, nat.clog_one_right, int.coe_nat_zero]

lemma clog_zpow {b : ℕ} (hb : 1 < b) (z : ℤ) : clog b (b ^ z : R) = z :=
by rw [←neg_log_inv_eq_clog, ←zpow_neg, log_zpow hb, neg_neg]

@[mono] lemma clog_mono_right {b : ℕ} {r₁ r₂ : R} (h₀ : 0 < r₁) (h : r₁ ≤ r₂) :
  clog b r₁ ≤ clog b r₂ :=
begin
  rw [←neg_log_inv_eq_clog, ←neg_log_inv_eq_clog, neg_le_neg_iff],
  exact log_mono_right (inv_pos.mpr $ h₀.trans_le h) (inv_le_inv_of_le h₀ h),
end

variables (R)
/-- Over suitable subtypes, `int.clog` and `zpow` form a galois insertion -/
def clog_zpow_gi {b : ℕ} (hb : 1 < b) :
  galois_insertion
    (λ r : set.Ioi (0 : R), int.clog b (r : R))
    (λ z : ℤ, ⟨(b : R) ^ z, zpow_pos_of_pos (by exact_mod_cast zero_lt_one.trans hb) z⟩) :=
galois_insertion.monotone_intro
  (λ z₁ z₂ hz, subtype.coe_le_coe.mp $ (zpow_strict_mono $ by exact_mod_cast hb).monotone hz)
  (λ r₁ r₂, clog_mono_right r₁.prop)
  (λ r, subtype.coe_le_coe.mp $ self_le_zpow_clog hb _)
  (λ _, clog_zpow hb _)
variables {R}

/-- `int.clog b` and `zpow b` (almost) form a Galois connection. -/

/-- `int.clog b` and `zpow b` (almost) form a Galois connection. -/
lemma le_zpow_iff_clog_le {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
  r ≤ (b : R) ^ x ↔ clog b r ≤ x :=
(@galois_connection.le_iff_le _ _ _ _ _ _ (clog_zpow_gi R hb).gc ⟨r, hr⟩ x).symm

end int


import ..Intuitionism.reckless

example (P Q : Prop) : (P ∨ Q) → ¬(¬P ∧ ¬Q) :=
begin
    intros h₁ h₂,
    cases h₁ with hp hq,
    {
        exact h₂.elim_left hp,
    },
    {
        exact h₂.elim_right hq,
    }
end

lemma LEM_equiv_double_not : (∀ P : Prop, P ∨ ¬P) ↔ ∀ Q : Prop, ¬¬Q → Q :=
begin
    split,
    {
        intros h Q nnq,
        cases h Q with hq nq,
        {
            exact hq,
        },
        {
            exfalso,
            exact nnq nq,
        }
    },
    {
        intros h P,
        apply h (P ∨ ¬P),
        exact reckless.not_not_or P,
    }
end

example (P Q : Prop) (lem : ∀ P : Prop, P ∨ ¬P) : ¬(¬P ∧ ¬Q) → P ∨ Q :=
begin
    intro h,
    cases lem P with hp np,
    {-- case: P
        left,
        exact hp,
    },
    {-- case: ¬P
        right,
        have nn := LEM_equiv_double_not.mp lem,
        apply nn Q,
        intro nq,
        exact h (and.intro np nq),
    }
end

-- For the proof that the contrapositive of this is reckless, see reckless.lean
example (P Q : Prop) : (¬P ∨ Q) → (P → Q) :=
begin
    intros h hp,
    cases h with np hq,
    {-- case: ¬P
        exfalso,
        exact np hp,
    },
    {-- case: Q
        exact hq,
    }
end

example (α : Type) (P : α → Prop) : (¬∃ x, P x) → ∀ x, ¬P x :=
begin
    intros h x hpx,
    apply h,
    use x,
    exact hpx,
end