/- Exercise 2.1: Functional Programming — Lists -/

/- Question 1: Counting elements -/

/- 1.1. Define a function `count` that counts the number of occurrences of an element in a list. It
should be similar to `bcount` from the lecture, except that it takes a value `α` instead of a
predicate `α → bool`.

`decidable_eq` is the type class of types whose equality is decidable. -/

def count {α : Type} (a : α) [decidable_eq α] : list α → ℕ
| [] := 0
| (x :: xs) := count xs + (if x = a then 1 else count xs)

def reverse {α: Type}: list α → list α
| [] := []
| (x :: xs) := reverse xs ++ [x]

lemma count_reverse_eq_count {α: Type} (a: α) (xs: list α) [decidable_eq α]: count a (reverse xs) = count xs
begin

end

/- 1.2. Test your definition of `count` on a few examples to convince yourself that it is
correct. This is something you should always do regardless of whether we ask for it.

You can use `#reduce` or (if `#reduce` fails) `#eval` to do this. -/

#reduce count "a" ["a", "a"]
#reduce count 2 [2,2,2,2,2]
#reduce count 0 [0,0,0,0]
#reduce count 9 []

/- 1.3. Complete the following proof (or replace it with a new proof structure of your own).

Hints: Some of the following lemmas might be useful to reason about `≤`. Moreover, if it helps you
carry out the proof, you could reconsider revising your answer to question 1.1. -/

#check nat.le_add_left
#check nat.le_add_right
#check add_le_add_left
#check add_le_add_right

lemma count_le_length {α : Type} (a : α) [decidable_eq α] :
  ∀xs : list α, count a xs ≤ list.length xs
| [] := by refl
| (x :: xs) :=
  calc count a (x :: xs) = count a xs + (if x = a then 1 else 0) :
    by refl
  ... ≤ list.length xs + (if x = a then 1 else 0) :
    begin
      apply add_le_add_right,
      simp[count_le_length xs]
    end
  ... ≤ list.length (x :: xs) :
    begin
      apply add_le_add_left,
      by_cases (x=a),
      simp[h, list.length],
      simp[h, list.length],
      apply nat.le_add_left
    end

/- Question 2: Removing duplicates -/

/- 2.1. Define a predicate `mem x xs` that returns true if `x` is an element of `xs`. -/

def mem {α: Type} (x: α) [decidable_eq α]: list α → bool
| [] := false
| (xs :: xss):= if (xs = x) then true else mem xss

#reduce mem 2 [7,1]

/- The above `mem` predicate is not quite as convenient as the predefined `list.mem`, for which
theorems and a nice notation (infix `∈`) are available, including a proof of decidability. Let's use
`list.mem` from now on.  -/

#print list.has_mem
#check list.mem
#check list.decidable_mem

/- 2.2. Define a function `remdups` that removes duplicate elements in a list. For example,
`remdups [1, 2, 1, 3]` could return either `[1, 2, 3]` or `[2, 1, 3]`, depending of which of the two
occurrences of `1` is kept.

Hint: One of the two behaviors is easier to implement. -/

def remdumps {α: Type} [decidable_eq α]: list α → list α
| [] := []
| (x :: xs) := if list.mem x xs then remdumps xs else x :: remdumps xs

/- 2.3. Test your implementation on more than two input values, using `#reduce` or `#eval`, and put
the expected value in a comment. -/

#reduce remdumps [2,2,2,3,4,4,4] --Expect 2,3,4

/- Do you find yourself copy-pasting the outcome of `#reduce` into the comment? If so, this defeats
the point of writing a test. You should first think for yourself of the expected result, write it
down, and then compare the actual result with it. -/

/- 2.4. Define a function `remdups_adj` that compresses adjacent duplicates. For example, it would
leave `[1, 2, 1, 3]` unchanged but compress `[1, 1, 2, 2, 2, 1, 3, 3]` to `[1, 2, 1, 3]`. -/

def remdumps_adj {α: Type} [decidable_eq α]: list α → list α
| [] := []
| (x :: xs) := if list.mem x xs && (list.take (list.length xs - list.length(remdumps_adj xs)) = x then remdumps_adj xs else x :: remdumps_adj xs

/- 2.5. Test your implementation on the same values as you used for question 2.3. -/

#reduce remdumps_adj [2,2,2,3,4,3,4]

/- Do the tests detect any difference in behavior between `remdups` and `remdups_adj`? If the answer
is no, this is a strong indication that your tests were incomplete to start with. -/

/- 2.6. Prove that `remdups` does not influence the behavior of `list.mem`.

Hint: Use `by_cases` to distinguish between the case where a given element is kept and the case
where it is removed. -/

@[simp] lemma mem_remdups {α : Type} (a : α) [decidable_eq α] :
  ∀xs : list α, a ∈ remdumps xs ↔ a ∈ xs
| [] := by refl
| (x :: xs) := begin simp[remdumps], by_cases list.mem x xs, { simp [h, mem_remdups xs],
      apply iff.intro,
      { intro, apply or.intro_right, assumption },
      { intro hor,
        apply hor.elim,
        { intro a_eq_x, rw a_eq_x, assumption },
        { intro, assumption } } },
    { simp [h, mem_remdups xs] } end

-- begin
-- intro xs,
-- apply iff.intro,
-- intro l,
-- by_cases a ∈ xs,
-- assumption,
-- cases xs,
-- simp,
-- apply l,

-- end

/- 2.7. State and prove that `remdups_adj` does not influence the behavior of `list.mem`. -/
lemma test {α: Type} (xs: list α) (x: α) [decidable_eq α]: remdumps_adj xs = xs :=
begin
cases xs,
refl,
simp[remdumps_adj],
by_cases
end

/- 2.8. State and prove that `remdups` is idempotent (i.e., that applying it twice has the same
effect as applying it only once). -/

-- enter your answer here

/- 2.9 (**optional bonus**). State and prove that `remdups_adj` is idempotent.

Warning: This one is difficult. -/

-- enter your answer here


-- Valor_absoluto_del_producto_menor_media_de_cuadrados.lean
-- Si a, b ∈ ℝ, entonces |ab| ≤ (a^2 + b^2)/2
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 28-septiembre-2022
-- ---------------------------------------------------------------------

import data.real.basic
import tactic

variables a b : ℝ

-- 1ª demostración
example : abs (a*b) ≤ (a^2 + b^2) / 2 :=
begin
  apply abs_le.mpr,
  split,
  { have h1 : 0 ≤ a^2 + 2*a*b + b^2,
      calc 0 ≤ (a+b)^2                : by exact pow_two_nonneg (a + b)
         ... = a^2+2*a*b+b^2          : by ring,
    have h2 : -2*(a*b) ≤ a^2 + b^2,
      calc -2*(a*b)
           ≤ -2*(a*b)+(a^2+2*a*b+b^2) : by exact le_add_of_nonneg_right h1
       ... = a^2 + b^2                : by ring,
    show -((a^2 + b^2) / 2) ≤ a*b,      by linarith [h2] },
  { have h3 : 0 ≤ a^2 - 2*a*b + b^2,
      calc 0 ≤ (a-b)^2                : by exact pow_two_nonneg (a - b)
         ... = a^2-2*a*b+b^2          : by ring,
    have h4 : 2*(a*b) ≤ a^2 + b^2,
      calc 2*(a*b)
           ≤ 2*(a*b)+(a^2-2*a*b+b^2)  : by exact le_add_of_nonneg_right h3
       ... = a^2 + b^2                : by ring,
    show a * b ≤ (a^2 + b^2)/2,         by linarith [h4] },
end

-- 2ª demostración
example : abs (a*b) ≤ (a^2 + b^2) / 2 :=
begin
  apply abs_le.mpr,
  split,
  { have h1 : 0 ≤ a^2 + 2*a*b + b^2,
      calc 0 ≤ (a+b)^2                : by exact pow_two_nonneg (a + b)
         ... = a^2+2*a*b+b^2          : by ring,
    have h2 : -2*(a*b) ≤ a^2 + b^2,
      calc -2*(a*b)
           ≤ -2*(a*b)+(a^2+2*a*b+b^2) : by exact le_add_of_nonneg_right h1
       ... = a^2 + b^2                : by ring,
    show -((a^2 + b^2) / 2) ≤ a*b,      by linarith [h2] },
  { have h4 : 2*a*b ≤ a^2 + b^2       := two_mul_le_add_sq a b,
    show a * b ≤ (a^2 + b^2)/2,         by linarith [h4] },
end

-- 3ª demostración
example : abs (a*b) ≤ (a^2 + b^2) / 2 :=
begin
  apply abs_le.mpr,
  split,
  { have h1 : 0 ≤ a^2 + 2*a*b + b^2,
      calc 0 ≤ (a+b)^2                : by exact pow_two_nonneg (a + b)
         ... = a^2+2*a*b+b^2          : by ring,
    have h2 : -2*(a*b) ≤ a^2 + b^2,
      calc -2*(a*b)
           ≤ -2*(a*b)+(a^2+2*a*b+b^2) : by exact le_add_of_nonneg_right h1
       ... = a^2 + b^2                : by ring,
    show -((a^2 + b^2) / 2) ≤ a*b,      by linarith [h2] },
  { show a * b ≤ (a^2 + b^2)/2,         by linarith [two_mul_le_add_sq a b] },
end


/-
Copyright (c) 2021 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 3 : "not" (`¬`)

We learn about how to manipulate `¬ P` in Lean.

# Important : the definition of `¬ P`

In Lean, `¬ P` is *defined* to mean `P → false`. So `¬ P` and `P → false`
are *definitionally equal*. Check out the explanation of definitional
equality in the "equality" section of Part B of the course notes.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following tactics:

* `change`
* `by_contra`
* `by_cases`

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.

variables (P Q R : Prop)

example : ¬ true → false :=
begin
  intro h,
  change true → false at h,
  apply h,
  triv,
end

example : false → ¬ true :=
begin
  intro h,
  intro h2,
  exact h,
end

example : ¬ false → true :=
begin
  intro h,
  triv,
end

example : true → ¬ false :=
begin
  intro h,
  intro h2,
  exact h2,
end

example : false → ¬ P :=
begin
  intro h,
  intro hP,
  exact h,
end

example : P → ¬ P → false :=
begin
  intro hP,
  intro hnP,
  apply hnP,
  exact hP,
end

example : P → ¬ (¬ P) :=
begin
  intro hP,
  intro hnP,
  apply hnP,
  exact hP,
end

example : (P → Q) → (¬ Q → ¬ P) :=
begin
  intros hPQ hnQ hP,
  apply hnQ,
  apply hPQ,
  assumption,
end

example : ¬ ¬ false → false :=
begin
  intro h,
  apply h,
  intro h2,
  exact h2,
end

example : ¬ ¬ P → P :=
begin
  intro h,
  by_contra h2,
  apply h,
  exact h2,
end

example : (¬ Q → ¬ P) → (P → Q) :=
begin
  intros h hP,
  change (Q → false) → (P → false) at h,
  by_contra hnQ,
  apply h,
  { exact hnQ, },
  { exact hP }
end


/-
Copyright (c) 2021 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import data.nat.parity
import order.filter.at_top_bot

/-!
# Numbers are frequently modeq to fixed numbers

In this file we prove that `m ≡ d [MOD n]` frequently as `m → ∞`.
-/

open filter

namespace nat

/-- Infinitely many natural numbers are equal to `d` mod `n`. -/

lemma frequently_mod_eq {d n : ℕ} (h : d < n) : ∃ᶠ m in at_top, m % n = d :=
by simpa only [nat.modeq, mod_eq_of_lt h] using frequently_modeq h.ne_bot d

lemma frequently_even : ∃ᶠ m : ℕ in at_top, even m :=
by simpa only [even_iff] using frequently_mod_eq zero_lt_two

lemma frequently_odd : ∃ᶠ m : ℕ in at_top, odd m :=
by simpa only [odd_iff] using frequently_mod_eq one_lt_two

end nat


import Mathlib.Algebra.Ring.Basic
import Mathlib.Tactic.Ring
import Playground.Data.Fin.Family.Basic

namespace Fin.Family
section
  def sum {α n} [Add α] (a : α) (l : Family α n) : α := foldr Add.add a l

  theorem sum_eq_add_sum {α n} [Add α] (a : α) (l : Family α (n + 1))
  : l.sum a = l.head + l.tail.sum a := rfl

  theorem sum_ge {n} (l : Family ℕ n) : ∀ k, l.sum 0 ≥ l k :=
    match n with
    | 0 => f0
    | n + 1 => λ k => match k with
      | ⟨0, _⟩ => show l.head + l.tail.sum _ ≥ l.head from Nat.le_add_right l.head _
      | ⟨k + 1, c⟩ =>
        have := l.tail.sum_ge ⟨k, Nat.lt_of_succ_lt_succ c⟩
        show l.head + l.tail.sum _ ≥ l.tail ⟨k, Nat.lt_of_succ_lt_succ c⟩ from
        (Nat.zero_add (l.tail _)).symm ▸ Nat.add_le_add (Nat.zero_le _) this

  theorem sum_add_eq_add_sum {α n} [CommSemiring α] (f g : Family α n)
  : (of λ i => f i + g i).sum 0 = f.sum 0 + g.sum 0 :=
    match n with
    | 0 => (zero_add (0 : α)).symm ▸ rfl
    | n + 1 =>
      show (f.head + g.head) + (of λ i => f i + g i).tail.sum 0
        = f.head + f.tail.sum 0 + (g.head + g.tail.sum 0) from
      sum_add_eq_add_sum f.tail g.tail ▸ by ring
end
end Fin.Family


/-
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.degree.lemmas
! 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.Eval

/-!
# Theory of degrees of polynomials

Some of the main results include
- `natDegree_comp_le` : The degree of the composition is at most the product of degrees

-/

noncomputable section

open Classical Polynomial

open Finsupp Finset

namespace Polynomial

universe u v w

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

section Semiring

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

section Degree

theorem natDegree_comp_le : natDegree (p.comp q) ≤ natDegree p * natDegree q :=
  if h0 : p.comp q = 0 then by rw [h0, natDegree_zero]; exact Nat.zero_le _
  else
    WithBot.coe_le_coe.1 <|
      calc
        ↑(natDegree (p.comp q)) = degree (p.comp q) := (degree_eq_natDegree h0).symm
        _ = _ := (congr_arg degree comp_eq_sum_left)
        _ ≤ _ := (degree_sum_le _ _)
        _ ≤ _ :=
          Finset.sup_le fun n hn =>
            calc
              degree (C (coeff p n) * q ^ n) ≤ degree (C (coeff p n)) + degree (q ^ n) :=
                degree_mul_le _ _
              _ ≤ natDegree (C (coeff p n)) + n • degree q :=
                (add_le_add degree_le_natDegree (degree_pow_le _ _))
              _ ≤ natDegree (C (coeff p n)) + n • ↑(natDegree q) :=
                (add_le_add_left (nsmul_le_nsmul_of_le_right (@degree_le_natDegree _ _ q) n) _)
              _ = (n * natDegree q : ℕ) := by
                rw [natDegree_C, Nat.cast_zero, zero_add, nsmul_eq_mul];
                  simp
              _ ≤ (natDegree p * natDegree q : ℕ) :=
                WithBot.coe_le_coe.2 <|
                  mul_le_mul_of_nonneg_right (le_natDegree_of_ne_zero (mem_support_iff.1 hn))
                    (Nat.zero_le _)
#align polynomial.nat_degree_comp_le Polynomial.natDegree_comp_le

theorem degree_pos_of_root {p : R[X]} (hp : p ≠ 0) (h : IsRoot p a) : 0 < degree p :=
  lt_of_not_ge fun hlt => by
    have := eq_C_of_degree_le_zero hlt
    rw [IsRoot, this, eval_C] at h
    simp only [h, RingHom.map_zero] at this
    exact hp this
#align polynomial.degree_pos_of_root Polynomial.degree_pos_of_root

theorem natDegree_le_iff_coeff_eq_zero : p.natDegree ≤ n ↔ ∀ N : ℕ, n < N → p.coeff N = 0 := by
  simp_rw [natDegree_le_iff_degree_le, degree_le_iff_coeff_zero, Nat.cast_withBot,
    WithBot.coe_lt_coe]
#align polynomial.nat_degree_le_iff_coeff_eq_zero Polynomial.natDegree_le_iff_coeff_eq_zero

theorem natDegree_add_le_iff_left {n : ℕ} (p q : R[X]) (qn : q.natDegree ≤ n) :
    (p + q).natDegree ≤ n ↔ p.natDegree ≤ n := by
  refine' ⟨fun h => _, fun h => natDegree_add_le_of_degree_le h qn⟩
  refine' natDegree_le_iff_coeff_eq_zero.mpr fun m hm => _
  convert natDegree_le_iff_coeff_eq_zero.mp h m hm using 1
  rw [coeff_add, natDegree_le_iff_coeff_eq_zero.mp qn _ hm, add_zero]
#align polynomial.nat_degree_add_le_iff_left Polynomial.natDegree_add_le_iff_left

theorem natDegree_add_le_iff_right {n : ℕ} (p q : R[X]) (pn : p.natDegree ≤ n) :
    (p + q).natDegree ≤ n ↔ q.natDegree ≤ n := by
  rw [add_comm]
  exact natDegree_add_le_iff_left _ _ pn
#align polynomial.nat_degree_add_le_iff_right Polynomial.natDegree_add_le_iff_right

theorem natDegree_C_mul_le (a : R) (f : R[X]) : (C a * f).natDegree ≤ f.natDegree :=
  calc
    (C a * f).natDegree ≤ (C a).natDegree + f.natDegree := natDegree_mul_le
    _ = 0 + f.natDegree := by rw [natDegree_C a]
    _ = f.natDegree := zero_add _
set_option linter.uppercaseLean3 false in
#align polynomial.nat_degree_C_mul_le Polynomial.natDegree_C_mul_le

theorem natDegree_mul_C_le (f : R[X]) (a : R) : (f * C a).natDegree ≤ f.natDegree :=
  calc
    (f * C a).natDegree ≤ f.natDegree + (C a).natDegree := natDegree_mul_le
    _ = f.natDegree + 0 := by rw [natDegree_C a]
    _ = f.natDegree := add_zero _
set_option linter.uppercaseLean3 false in
#align polynomial.nat_degree_mul_C_le Polynomial.natDegree_mul_C_le

theorem eq_natDegree_of_le_mem_support (pn : p.natDegree ≤ n) (ns : n ∈ p.support) :
    p.natDegree = n :=
  le_antisymm pn (le_natDegree_of_mem_supp _ ns)
#align polynomial.eq_nat_degree_of_le_mem_support Polynomial.eq_natDegree_of_le_mem_support

theorem natDegree_C_mul_eq_of_mul_eq_one {ai : R} (au : ai * a = 1) :
    (C a * p).natDegree = p.natDegree :=
  le_antisymm (natDegree_C_mul_le a p)
    (calc
      p.natDegree = (1 * p).natDegree := by nth_rw 1 [← one_mul p]
      _ = (C ai * (C a * p)).natDegree := by rw [← C_1, ← au, RingHom.map_mul, ← mul_assoc]
      _ ≤ (C a * p).natDegree := natDegree_C_mul_le ai (C a * p))
set_option linter.uppercaseLean3 false in
#align polynomial.nat_degree_C_mul_eq_of_mul_eq_one Polynomial.natDegree_C_mul_eq_of_mul_eq_one

theorem natDegree_mul_C_eq_of_mul_eq_one {ai : R} (au : a * ai = 1) :
    (p * C a).natDegree = p.natDegree :=
  le_antisymm (natDegree_mul_C_le p a)
    (calc
      p.natDegree = (p * 1).natDegree := by nth_rw 1 [← mul_one p]
      _ = (p * C a * C ai).natDegree := by rw [← C_1, ← au, RingHom.map_mul, ← mul_assoc]
      _ ≤ (p * C a).natDegree := natDegree_mul_C_le (p * C a) ai)
set_option linter.uppercaseLean3 false in
#align polynomial.nat_degree_mul_C_eq_of_mul_eq_one Polynomial.natDegree_mul_C_eq_of_mul_eq_one

/-- Although not explicitly stated, the assumptions of lemma `nat_degree_mul_C_eq_of_mul_ne_zero`
force the polynomial `p` to be non-zero, via `p.leading_coeff ≠ 0`.
-/
theorem natDegree_mul_c_eq_of_mul_ne_zero (h : p.leadingCoeff * a ≠ 0) :
    (p * C a).natDegree = p.natDegree := by
  refine' eq_natDegree_of_le_mem_support (natDegree_mul_C_le p a) _
  refine' mem_support_iff.mpr _
  rwa [coeff_mul_C]
set_option linter.uppercaseLean3 false in
#align polynomial.nat_degree_mul_C_eq_of_mul_ne_zero Polynomial.natDegree_mul_c_eq_of_mul_ne_zero

/-- Although not explicitly stated, the assumptions of lemma `nat_degree_C_mul_eq_of_mul_ne_zero`
force the polynomial `p` to be non-zero, via `p.leading_coeff ≠ 0`.
-/
theorem natDegree_c_mul_eq_of_mul_ne_zero (h : a * p.leadingCoeff ≠ 0) :
    (C a * p).natDegree = p.natDegree := by
  refine' eq_natDegree_of_le_mem_support (natDegree_C_mul_le a p) _
  refine' mem_support_iff.mpr _
  rwa [coeff_C_mul]
set_option linter.uppercaseLean3 false in
#align polynomial.nat_degree_C_mul_eq_of_mul_ne_zero Polynomial.natDegree_c_mul_eq_of_mul_ne_zero

theorem natDegree_add_coeff_mul (f g : R[X]) :
    (f * g).coeff (f.natDegree + g.natDegree) = f.coeff f.natDegree * g.coeff g.natDegree := by
  simp only [coeff_natDegree, coeff_mul_degree_add_degree]
#align polynomial.nat_degree_add_coeff_mul Polynomial.natDegree_add_coeff_mul

theorem natDegree_lt_coeff_mul (h : p.natDegree + q.natDegree < m + n) :
    (p * q).coeff (m + n) = 0 :=
  coeff_eq_zero_of_natDegree_lt (natDegree_mul_le.trans_lt h)
#align polynomial.nat_degree_lt_coeff_mul Polynomial.natDegree_lt_coeff_mul

theorem coeff_mul_of_natDegree_le (pm : p.natDegree ≤ m) (qn : q.natDegree ≤ n) :
    (p * q).coeff (m + n) = p.coeff m * q.coeff n := by
  rcases eq_or_lt_of_le pm with (rfl | hm) <;> rcases eq_or_lt_of_le qn with (rfl | hn)
  · exact natDegree_add_coeff_mul _ _
  · rw [coeff_eq_zero_of_natDegree_lt hn, mul_zero]
    exact natDegree_lt_coeff_mul (add_lt_add_left hn _)
  · rw [coeff_eq_zero_of_natDegree_lt hm, zero_mul]
    exact natDegree_lt_coeff_mul (add_lt_add_right hm _)
  · rw [coeff_eq_zero_of_natDegree_lt hn, mul_zero]
    exact natDegree_lt_coeff_mul (add_lt_add hm hn)
#align polynomial.coeff_mul_of_nat_degree_le Polynomial.coeff_mul_of_natDegree_le

theorem coeff_pow_of_natDegree_le (pn : p.natDegree ≤ n) : (p ^ m).coeff (n * m) = p.coeff n ^ m :=
  by
  induction' m with m hm
  · simp
  · rw [pow_succ', pow_succ', ← hm, Nat.mul_succ, coeff_mul_of_natDegree_le _ pn]
    refine' natDegree_pow_le.trans (le_trans _ (mul_comm _ _).le)
    exact mul_le_mul_of_nonneg_left pn m.zero_le
#align polynomial.coeff_pow_of_nat_degree_le Polynomial.coeff_pow_of_natDegree_le

theorem coeff_add_eq_left_of_lt (qn : q.natDegree < n) : (p + q).coeff n = p.coeff n :=
  (coeff_add _ _ _).trans <|
    (congr_arg _ <| coeff_eq_zero_of_natDegree_lt <| qn).trans <| add_zero _
#align polynomial.coeff_add_eq_left_of_lt Polynomial.coeff_add_eq_left_of_lt

theorem coeff_add_eq_right_of_lt (pn : p.natDegree < n) : (p + q).coeff n = q.coeff n := by
  rw [add_comm]
  exact coeff_add_eq_left_of_lt pn
#align polynomial.coeff_add_eq_right_of_lt Polynomial.coeff_add_eq_right_of_lt

theorem degree_sum_eq_of_disjoint (f : S → R[X]) (s : Finset S)
    (h : Set.Pairwise { i | i ∈ s ∧ f i ≠ 0 } (Ne on degree ∘ f)) :
    degree (s.sum f) = s.sup fun i => degree (f i) := by
  induction' s using Finset.induction_on with x s hx IH
  · simp
  · simp only [hx, Finset.sum_insert, not_false_iff, Finset.sup_insert]
    specialize IH (h.mono fun _ => by simp (config := { contextual := true }))
    rcases lt_trichotomy (degree (f x)) (degree (s.sum f)) with (H | H | H)
    · rw [← IH, sup_eq_right.mpr H.le, degree_add_eq_right_of_degree_lt H]
    · rcases s.eq_empty_or_nonempty with (rfl | hs)
      · simp
      obtain ⟨y, hy, hy'⟩ := Finset.exists_mem_eq_sup s hs fun i => degree (f i)
      rw [IH, hy'] at H
      by_cases hx0 : f x = 0
      · simp [hx0, IH]
      have hy0 : f y ≠ 0 := by
        contrapose! H
        simpa [H, degree_eq_bot] using hx0
      refine' absurd H (h _ _ fun H => hx _)
      · simp [hx0]
      · simp [hy, hy0]
      · exact H.symm ▸ hy
    · rw [← IH, sup_eq_left.mpr H.le, degree_add_eq_left_of_degree_lt H]
#align polynomial.degree_sum_eq_of_disjoint Polynomial.degree_sum_eq_of_disjoint

theorem natDegree_sum_eq_of_disjoint (f : S → R[X]) (s : Finset S)
    (h : Set.Pairwise { i | i ∈ s ∧ f i ≠ 0 } (Ne on natDegree ∘ f)) :
    natDegree (s.sum f) = s.sup fun i => natDegree (f i) := by
  by_cases H : ∃ x ∈ s, f x ≠ 0
  · obtain ⟨x, hx, hx'⟩ := H
    have hs : s.Nonempty := ⟨x, hx⟩
    refine' natDegree_eq_of_degree_eq_some _
    rw [degree_sum_eq_of_disjoint]
    · dsimp
      rw [← Finset.sup'_eq_sup hs, ← Finset.sup'_eq_sup hs,
        Nat.cast_withBot, Finset.coe_sup' hs, ←
        Finset.sup'_eq_sup hs]
      refine' le_antisymm _ _
      · rw [Finset.sup'_le_iff]
        intro b hb
        by_cases hb' : f b = 0
        · simpa [hb'] using hs
        rw [degree_eq_natDegree hb', Nat.cast_withBot]
        exact Finset.le_sup' (fun i : S => (natDegree (f i) : WithBot ℕ)) hb
      · rw [Finset.sup'_le_iff]
        intro b hb
        simp only [Finset.le_sup'_iff, exists_prop, Function.comp_apply]
        by_cases hb' : f b = 0
        · refine' ⟨x, hx, _⟩
          contrapose! hx'
          simpa [← Nat.cast_withBot, hb', degree_eq_bot] using hx'
        exact ⟨b, hb, (degree_eq_natDegree hb').ge⟩
    · exact h.imp fun x y hxy hxy' => hxy (natDegree_eq_of_degree_eq hxy')
  · push_neg  at H
    rw [Finset.sum_eq_zero H, natDegree_zero, eq_comm, show 0 = ⊥ from rfl, Finset.sup_eq_bot_iff]
    intro x hx
    simp [H x hx]
#align polynomial.nat_degree_sum_eq_of_disjoint Polynomial.natDegree_sum_eq_of_disjoint

set_option linter.deprecated false in

set_option linter.deprecated false in
theorem natDegree_bit1 (a : R[X]) : (bit1 a).natDegree ≤ a.natDegree :=
  (natDegree_add_le _ _).trans (by simp [natDegree_bit0])
#align polynomial.nat_degree_bit1 Polynomial.natDegree_bit1

variable [Semiring S]

theorem natDegree_pos_of_eval₂_root {p : R[X]} (hp : p ≠ 0) (f : R →+* S) {z : S}
    (hz : eval₂ f z p = 0) (inj : ∀ x : R, f x = 0 → x = 0) : 0 < natDegree p :=
  lt_of_not_ge fun hlt =>
    by
    have A : p = C (p.coeff 0) := eq_C_of_natDegree_le_zero hlt
    rw [A, eval₂_C] at hz
    simp only [inj (p.coeff 0) hz, RingHom.map_zero] at A
    exact hp A
#align polynomial.nat_degree_pos_of_eval₂_root Polynomial.natDegree_pos_of_eval₂_root

theorem degree_pos_of_eval₂_root {p : R[X]} (hp : p ≠ 0) (f : R →+* S) {z : S}
    (hz : eval₂ f z p = 0) (inj : ∀ x : R, f x = 0 → x = 0) : 0 < degree p :=
  natDegree_pos_iff_degree_pos.mp (natDegree_pos_of_eval₂_root hp f hz inj)
#align polynomial.degree_pos_of_eval₂_root Polynomial.degree_pos_of_eval₂_root

@[simp]
theorem coe_lt_degree {p : R[X]} {n : ℕ} : (n : WithBot ℕ) < degree p ↔ n < natDegree p := by
  by_cases h : p = 0
  · simp [h]
  simp [degree_eq_natDegree h, Nat.cast_withBot, WithBot.coe_lt_coe]
#align polynomial.coe_lt_degree Polynomial.coe_lt_degree

end Degree

end Semiring

section Ring

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

theorem natDegree_sub : (p - q).natDegree = (q - p).natDegree := by rw [← natDegree_neg, neg_sub]
#align polynomial.nat_degree_sub Polynomial.natDegree_sub

theorem natDegree_sub_le_iff_left (qn : q.natDegree ≤ n) :
    (p - q).natDegree ≤ n ↔ p.natDegree ≤ n := by
  rw [← natDegree_neg] at qn
  rw [sub_eq_add_neg, natDegree_add_le_iff_left _ _ qn]
#align polynomial.nat_degree_sub_le_iff_left Polynomial.natDegree_sub_le_iff_left

theorem natDegree_sub_le_iff_right (pn : p.natDegree ≤ n) :
    (p - q).natDegree ≤ n ↔ q.natDegree ≤ n := by rwa [natDegree_sub, natDegree_sub_le_iff_left]
#align polynomial.nat_degree_sub_le_iff_right Polynomial.natDegree_sub_le_iff_right

theorem coeff_sub_eq_left_of_lt (dg : q.natDegree < n) : (p - q).coeff n = p.coeff n := by
  rw [← natDegree_neg] at dg
  rw [sub_eq_add_neg, coeff_add_eq_left_of_lt dg]
#align polynomial.coeff_sub_eq_left_of_lt Polynomial.coeff_sub_eq_left_of_lt

theorem coeff_sub_eq_neg_right_of_lt (df : p.natDegree < n) : (p - q).coeff n = -q.coeff n := by
  rwa [sub_eq_add_neg, coeff_add_eq_right_of_lt, coeff_neg]
#align polynomial.coeff_sub_eq_neg_right_of_lt Polynomial.coeff_sub_eq_neg_right_of_lt

end Ring

section NoZeroDivisors

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

theorem degree_mul_C (a0 : a ≠ 0) : (p * C a).degree = p.degree := by
  rw [degree_mul, degree_C a0, add_zero]
set_option linter.uppercaseLean3 false in
#align polynomial.degree_mul_C Polynomial.degree_mul_C

theorem degree_C_mul (a0 : a ≠ 0) : (C a * p).degree = p.degree := by
  rw [degree_mul, degree_C a0, zero_add]
set_option linter.uppercaseLean3 false in
#align polynomial.degree_C_mul Polynomial.degree_C_mul

theorem natDegree_mul_C (a0 : a ≠ 0) : (p * C a).natDegree = p.natDegree := by
  simp only [natDegree, degree_mul_C a0]
set_option linter.uppercaseLean3 false in
#align polynomial.natDegree_mul_C Polynomial.natDegree_mul_C

theorem natDegree_C_mul (a0 : a ≠ 0) : (C a * p).natDegree = p.natDegree := by
  simp only [natDegree, degree_C_mul a0]
set_option linter.uppercaseLean3 false in
#align polynomial.nat_degree_C_mul Polynomial.natDegree_C_mul

theorem natDegree_comp : natDegree (p.comp q) = natDegree p * natDegree q := by
  by_cases q0 : q.natDegree = 0
  ·
    rw [degree_le_zero_iff.mp (natDegree_eq_zero_iff_degree_le_zero.mp q0), comp_C, natDegree_C,
      natDegree_C, mul_zero]
  · by_cases p0 : p = 0
    · simp only [p0, zero_comp, natDegree_zero, zero_mul]
    refine' le_antisymm natDegree_comp_le (le_natDegree_of_ne_zero _)
    simp only [coeff_comp_degree_mul_degree q0, p0, mul_eq_zero, leadingCoeff_eq_zero, or_self_iff,
      ne_zero_of_natDegree_gt (Nat.pos_of_ne_zero q0), pow_ne_zero, Ne.def, not_false_iff]
#align polynomial.nat_degree_comp Polynomial.natDegree_comp

@[simp]
theorem natDegree_iterate_comp (k : ℕ) :
    ((p.comp^[k]) q).natDegree = p.natDegree ^ k * q.natDegree := by
  induction' k with k IH
  · simp
  · rw [Function.iterate_succ_apply', natDegree_comp, IH, pow_succ, mul_assoc]
#align polynomial.nat_degree_iterate_comp Polynomial.natDegree_iterate_comp

theorem leadingCoeff_comp (hq : natDegree q ≠ 0) :
    leadingCoeff (p.comp q) = leadingCoeff p * leadingCoeff q ^ natDegree p := by
  rw [← coeff_comp_degree_mul_degree hq, ← natDegree_comp, coeff_natDegree]
#align polynomial.leading_coeff_comp Polynomial.leadingCoeff_comp

end NoZeroDivisors

end Polynomial


import tactic.group

variables {G : Type} [group G]

example (a b c d : G) : c *(a*b)*(b⁻¹*a⁻¹)*c = c*c :=
by group

example (a b c d : G) : (b*c⁻¹)*c *(a*b)*(b⁻¹*a⁻¹)*c = b*c :=
by group

example (a b c d : G) : c⁻¹*(b*c⁻¹)*c *(a*b)*(b⁻¹*a⁻¹*b⁻¹)*c = 1 :=
by group

def commutator {G} [group G] : G → G → G := λ g h, g * h * g⁻¹ * h⁻¹

def commutator3 {G} [group G] : G → G → G → G := λ g h k, commutator (commutator g h) k

-- The following is known as the Hall-Witt identity,
-- see e.g.
-- https://en.wikipedia.org/wiki/Three_subgroups_lemma#Proof_and_the_Hall%E2%80%93Witt_identity
example (g h k : G) :
  g * (commutator3 g⁻¹ h k) * g⁻¹ * k * (commutator3 k⁻¹ g h) * k⁻¹ *
    h * (commutator3 h⁻¹ k g) * h⁻¹ = 1 :=
by { dsimp [commutator3, commutator], group }

example (a : G) : a^2*a = a^3 :=
by group

example (n m : ℕ) (a : G) : a^n*a^m = a^(n+m) :=
by group

example (a b c d : G) : c *(a*b^2)*((b*b)⁻¹*a⁻¹)*c = c*c :=
by group

example (n m : ℕ) (a : G) : a^n*(a⁻¹)^n = 1 :=
by group

example (n m : ℕ) (a : G) : a^2*a⁻¹*a⁻¹ = 1 :=
by group

example (n m : ℕ) (a : G) : a^n*a^m = a^(m+n) :=
by group

example (n : ℕ) (a : G) : a^(n-n) = 1 :=
by group

example (n : ℤ) (a : G) : a^(n-n) = 1 :=
by group

example (n : ℤ) (a : G) (h : a^(n*(n+1)-n -n^2) = a) : a = 1 :=
begin
 group at h,
 exact h.symm,
 end

example (a b c d : G) (h : c = (a*b^2)*((b*b)⁻¹*a⁻¹)*d) : a*c*d⁻¹ = a :=
begin
  group at h,
  rw h,
  group,
end

-- The next example can be expand to require an arbitrarily high number of alternation
-- between simp and ring

example (n m : ℤ) (a b : G) : a^n*b^n*a^n*a^n*a^-n*a^-n*b^-n*a^-n = 1 :=
by group

-- Test that group deals with `1⁻¹` properly

example (x y : G) : (x⁻¹ * (x * y) * y⁻¹)⁻¹ = 1 :=
by group

example (x : G) (h : x = 1) : x = 1 :=
begin
  group,
  exact h,
end


import .prop_logic
import .bool_sat

open prop_logic
open prop_logic.var
open prop_logic.pExp

/-
An example: The 0th, 1st, and
2nd bits from the right in 100,
the binary numeral for decimal
4, are 0, 0, and 1, respectively.
-/
#eval mth_bit_from_right_in_n 2 4

/-
#1. Write and evaluate expressions
(using eval) to determine what is
the bit in the 9th position from
the right in the binary expansion
of the decimal number 8485672345?

Hint: don't use reduce here. Eval
uses hardware (machine) int values
to represent nats, while reduce
uses the unary nat representation.

Self-test: How much memory might
it take to represent the decimal
number, 8485672345, as a ℕ value
in unary?
-/

#eval _

/-
The next section presents examples
to set up test cases for definitions
to follow.
-/

/-
We define a few variables to use
in the rest of this assignment.
-/
def P : pExp:= varExp (mkVar 0)
def Q: pExp := varExp (mkVar 1)
def R : pExp := varExp (mkVar 2)

/-
We set parameter values used in
some function definitions to follow.
-/
def max_var_index := 2
def num_vars := max_var_index + 1

/-
An example of a propositional logic
expression.
-/
def theExpr : pExp := (P ⇒ (P ∧ R))

/-
An example using the truth_table_results
function to compute and return a list of
the truth values of theExpr under each of
its possible interpretations.
-/
#eval truth_table_results theExpr num_vars

/-
#2. Define interp5 to be the interpretation
in the six row (m=5) of the truth table that
our interps_for_n_vars functions returns for
our three variables (P, Q, and R).

Hint: use the mth_interp_n_vars function.
Definitely check out the definition of the
function, and any specification text, even
if informal, given with the formal definition.
-/

def interp5 := _

/-
#3. What Boolean values are assigned to
P, Q, and R by this interpretation (interp5)?
Use three #eval commands to compute answers by
evaluating each variable expressions under the
interp5 interpretation.
-/

#eval _
#eval _
#eval _

/-
#4. Write a truth table within this
comment block showing the values for
P, Q, and R, in each row in the truth
table, represented by a corresponding
valule in the list of interpretations
returned by interps_for_n_vars. Label
your columns as R, Q, and P, in that
order. (Try to understand why.)
Hint: Don't just write what you think
the answers are:, evaluate each of
the three variable expression under
each interpretation. You can use the
mth_interp_n_vars function if you want
to obtain interpretation functions for
each of the rows individually if you
want.

Answer:

-/

/-
#5. Write an expression here to
compute the "results" column of
the truth table for "theExpr" as
defined above.
-/

#eval _

/-
#6. Copy and paste the truth table
from question #4 here and extend it
with the results you just obtained.
Check the results for correctness.

Answer here:

-/

/-
#7.

Write a "predicate" function, isModel,
that takes a propositional logic
expression, e, and an interpretation,
i, as its arguments and that returns
the Boolean true (tt) value if and only
if i is a model of e (otherwise false).
-/

def isModel :pExp → (var → bool) → bool

/-
#7. Write a one-line implementation
of a function, is_valid, that takes as
its arguments a propositional expression,
e, and the number of variables, n, in its
truth table, and that returns true if and
only if it is valid, construed to mean
tha every result in the list returned by
the truth_table_results function is true.

To do so, define and use a fold function
to reduce returned lists of Boolean truth
values to single truth values. Define and
use a bool-specific function, fold_bool :
    (bool → bool → bool) →
    bool →
    (list bool) →
    bool,
where the arguments are, respectively,
a binary operator, an identity element
for that operator, and a list of bools
to be reduced to a single bool result.
-/

def fold_bool :
    (bool → bool → bool) → bool → (list bool) → bool

def is_valid : pExp → ℕ → bool

/-
Write similar one-line implementations of the
functions, is_satisfiable and is_unsatisfiable,
respectively. Do not use fold (directly) in your
implementation of is_unsatisfiable.
-/

def is_satisfiable : pExp → ℕ → bool

def is_unsatisfiable : pExp → ℕ → bool

/-
8. Use your is_valid function to determine which
of the following putative valid laws of reasoning
really are valid, and which ones are not. For each
one that is not, give a real-world scenario that
shows that the rule doesn't always lead to a valid
deduction. Use #eval to evaluate the validity of
each proposition. Use -- to put a comment after
each of the following definitions indicating
either "-- valid" or "-- NOT valid".
-/

def true_intro : pExp := pTrue
def false_elim := pFalse ⇒ P
def and_intro := P ⇒ Q ⇒ (P ∧ Q)
def and_elim_left := (P ∧ Q) ⇒ P
def and_elim_right := (P ∧ Q) ⇒ Q
def or_intro_left := P ⇒ (P ∨ Q)
def or_intro_right := Q ⇒ (P ∨ Q)
def or_elim := (P ∨ Q) ⇒ (P ⇒ R) ⇒ (Q ⇒ R) ⇒ R
def iff_intro := (P ⇒ Q) ⇒ (Q ⇒ P) ⇒ (P ↔ Q)
def iff_elim_left := (P ↔ Q) ⇒ (P ⇒ Q)
def iff_elim_right := (P ↔ Q) ⇒ (Q ⇒ P)
def arrow_elim := (P ⇒ Q) ⇒ P ⇒ Q
def affirm_consequence := (P ⇒ Q) ⇒ Q ⇒ P
def resolution := (P ∨ Q) ⇒ (¬ Q ∨ R) ⇒ (P ∨ R)
def unit_resolution := (P ∨ Q) ⇒ (¬ Q) ⇒ P
def syllogism := (P ⇒ Q) ⇒ (Q ⇒ R) ⇒ (P ⇒ R)
def modus_tollens := (P ⇒ Q) ⇒ ¬ Q ⇒ ¬ P
def neg_elim := ¬ ¬ P ⇒ P
def excluded_middle := P ∨ ¬ P
def neg_intro := (P ⇒ pFalse) ⇒ ¬ P
def affirm_disjunct := (P ∨ Q) ⇒ P ⇒ ¬ Q
def deny_antecedent := (P ⇒ Q) ⇒ (¬ P ⇒ ¬ Q)

-- Answer below


-- import solutions to level 1
import complex.kb_solutions.I

/-!

# Level 2: Complex conjugation

-/

namespace complex

-- First complete the definition using `complex.mk` or `⟨x, y⟩` notation

/-- The complex conjugate of a complex number -/
def conj (z : ℂ) : ℂ := ⟨re(z), -im(z)⟩

-- Now prove how it interacts with everything else

/-! ## Real and imaginary parts -/

@[simp] lemma conj_re (z : ℂ) : re(conj z) = re(z) := rfl -- short for "begin refl end"
@[simp] lemma conj_im (z : ℂ) : im(conj z) = -im(z) := rfl

/-! ## Behaviour with respect to 0, 1 and I -/

@[simp] lemma conj_zero : conj 0 = 0 := by ext; simp -- short for "begin ext; simp end"
@[simp] lemma conj_one : conj 1 = 1 := by ext; simp
@[simp] lemma conj_I : conj I = -I := by ext; simp
@[simp] lemma conj_neg_I : conj (-I) = I := by ext; simp

/-! # Behaviour with respect to +, - and * -/

@[simp] lemma conj_add (z w : ℂ) : conj (z + w) = conj z + conj w :=
by ext; -- check on real and imag parts
   simp; -- question becomes to check that a+b=b+a for certain real numbers a and b
   ring -- true because this is obvious in a ring

@[simp] lemma conj_neg (z : ℂ) : conj (-z) = -conj z :=
by ext; -- check on real and imag parts
   refl -- happens to be true by definition

@[simp] lemma conj_mul (z w : ℂ) : conj (z * w) = conj z * conj w :=
by ext; -- check on real and imag parts
   simp; -- it's some random statement involving ring theory of the real numbers
   ring -- solve it

/-! # Properties of the `conj` map -/

@[simp] lemma conj_conj (z : ℂ) : conj (conj z) = z :=
by ext; -- check on real and imag parts
   simp -- ext had reduced question to -(-x)=x for reals

attribute [simp] ext_iff -- cheeky trick to make the next proof work

lemma conj_inj {z w : ℂ} : conj z = conj w ↔ z = w :=
begin
  simp * at *
end

@[simp] lemma conj_eq_zero {z : ℂ} : conj z = 0 ↔ z = 0 :=
begin
  simp * at *
end

/-- the ring homomorphism complex conjugation -/
def Conj : ℂ →+* ℂ :=
{ to_fun := conj,
  map_zero' := conj_zero,
  map_one' := conj_one,
  map_add' := conj_add,
  map_mul' := conj_mul,
}

open function

lemma conj_involutive : involutive conj := conj_conj

lemma conj_bijective : bijective conj := conj_involutive.bijective

end complex


/-
Copyright (c) 2022 Tomaz Gomes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Tomaz Gomes.
-/
import tactic
import data.nat.log
/-
# Log lemmas
  This file defines and proves some lemmas about the logarithm function (with base set fixed to 2)
  and other arithmetic relations that will be used to prove merge sort's time complexity.
## Main Results
  - log_pred : ∀ (a : ℕ) , nat.log 2 a - 1 = nat.log 2 (a / 2)
  - log_2_times : ∀ (a : ℕ), 2 * nat.log 2 (a + 2) ≤ a + 2
-/

lemma log_pred : ∀ (a : ℕ) , nat.log 2 a - 1 = nat.log 2 (a / 2)
| 0 := by simp only [nat.log_zero_right, nat.zero_div]
| 1 := by norm_num
| (a + 2) :=
begin
  rw nat.log,
  split_ifs,
  { simp only [nat.add_succ_sub_one, add_zero], },
  simp only [le_refl, not_true, zero_le, nat.one_lt_bit0_iff, and_self, le_add_iff_nonneg_left]
       at h,
  cases h,
end

lemma log_2_val : nat.log 2 2 = 1 :=
begin
  rw nat.log,
  split_ifs,
  { simp only [nat.succ_pos', nat.log_one_right, nat.div_self], },
  simp only [le_refl, not_true, nat.one_lt_bit0_iff, and_self]
       at h,
  cases h,
end

lemma sum_2b (a b : ℕ) : a ≤ 2 * b → a + 2 * b ≤ 4 * b :=
begin
  intro h,
  calc a + 2 * b ≤ 2 * b + 2 * b : by { refine add_le_add h _, exact rfl.ge }
       ...       = 4 * b : by linarith
end

lemma log_2_times : ∀ (a : ℕ), 2 * nat.log 2 (a + 2) ≤ a + 2
| 0       := by { rw log_2_val, norm_num, }
| (a + 1) := have (a + 1) / 2 < a + 1, from nat.div_lt_self' a 0,
begin
  rw nat.log,
  split_ifs,
  { have IH := log_2_times ((a + 1) / 2),
    rw mul_add,
    cases a,
    { norm_num, },
    cases a,
    { norm_num, rw log_2_val, simp, },
    norm_num,
    have add_one :
      2 * nat.log 2 ((a.succ.succ + 1) / 2).succ ≤
      2 * nat.log 2 ((a.succ.succ + 1) / 2 + 2), from
      begin
        refine mul_le_mul le_rfl _ bot_le bot_le,
        refine @nat.log_monotone 2 ((a.succ.succ + 1) / 2).succ (((a.succ.succ + 1) / 2) + 2) _,
        exact nat.le_succ ((a.succ.succ + 1) / 2 + 1),
      end,
    refine le_trans add_one _,
    refine le_trans IH _,
    have succ_succ_two : a.succ.succ + 1 = a + 3 := rfl,
    have two_div_two: ∀ {y}, (y + 2) / 2 = y / 2 + 1 :=
      by { intro, exact (y + 2).div_def 2, },
    have three_eq_one_plus_two : ∀ {y}, y + 3 = y + 1 + 2 :=
      by { intro, exact rfl, },
    rw [ succ_succ_two
       , two_div_two
       , three_eq_one_plus_two
       , ← three_eq_one_plus_two
       ],
    refine add_le_add _ (le_refl 3),
    exact nat.lt_succ_iff.mp (nat.div_lt_self' a 0),
  },
  exact bot_le,
end

lemma div_two (b a : ℕ) : 2 * a ≤ b → a ≤ b / 2 :=
  by simp_rw [nat.le_div_iff_mul_le _ _ zero_lt_two, mul_comm, imp_self]


import Mathlib.Data.Nat.Basic

namespace Nat
/-
In Lean the natural numbers `ℕ` are defined as follows:

inductive nat
| zero : nat
| succ (n : nat) : nat

This means that there are two ways to construct a natural number `n:ℕ`
Either `n` is `zero` or it is the successor of a previously constructed
natural number `succ n`. -/

--#check zero
--#reduce succ 3
--#check succ (succ zero)
--#check zero.succ

#check Nat.zero_add
/-
Addition is defined inductively in Lean:

def add : ℕ → ℕ → ℕ
| a  zero     := a                    --  a + 0 := a
| a  (succ b) := succ (add a b)       --  a + (b + 1) := (a + b) + 1

-/

-- We will use the `dot` notation for the successor function.
/--  n + 1 = n.succ -/
lemma Succ_eq_add_one (n : ℕ) :  n.succ = n + 1  :=
by rfl

/-
We are now proving lemmas/theorems that already exist in `mathlib`.

To avoid clashes we will use the same names as `mathlib` but they will be Capitalised.

We won't use high level tactics such as `norm_num` or `linarith` but we will
need to use earlier results as we progress (mainly with `rw` and `apply` tactics).

For a much more complete tour of the natural numbers check out the Natural Numbers Game:

https://www.ma.imperial.ac.uk/~buzzard/xena/natural_number_game/

-/

lemma Add_zero (n : ℕ) : n + 0 = n :=
by rfl

/-- a + (b + 1) = (a + b) + 1 -/
lemma Add_succ (a b : ℕ) : a + b.succ = (a + b).succ:=
by rfl

/-
# New tactic for ℕ: induction

If we want to prove `∀ (n:ℕ), P n` then we can use
`induction n` which requires us to prove two things:
`P 0` and `P n →  P n.succ`

# New tactic for structured proofs: have

Sometimes we want to prove intermediate results within a proof.

We can do this using the `have` tactic.

If we need to prove `h : P` (ie a proof that the proposition P is true)
then we can do this as follows:

.... middle of a proof
have h : P,
{
  proof of P,
},
.... continue proof

We now have `h : P` in the local context.

# Variant of rw : rwa

If we have term in the local context `h1 : P` and after `rw h2` our goal becomes `⊢ P`
then `rwa h2` will do the `rw h2, exact h1` in one step.

(The `a` in `rwa` stands for `assumption` which is yet another tactic that will work whenever
our goal is closed by a term in the local context.) -/

lemma Zero_add  :∀ n:ℕ, 0 + n = n
  | zero => rfl
  | n+1 => congrArg succ (Zero_add n)

lemma Succ_add (a b : ℕ) : a.succ + b = (a + b).succ:= by
cases b with
| zero => rfl
| succ b =>  exact congrArg succ (Succ_add a b)

/- Digression: how do we know that 0 ≠ 1?
This is one of the axioms of the natural numbers (Peano arithmetic)
and it is built into Lean's model of ℕ.  -/

theorem Succ_ne_zero (n : ℕ) : n.succ ≠ 0 :=by
  intro hn
  contradiction

-- Lean also knows that the successor function is injective (by definition)
theorem Succ.inj (n m : ℕ) : n.succ = m.succ → n = m :=by
  exact Nat.succ.inj

/- Our next result says that `+` is `associative`

In Lean `a + b + c` is defined as `(a + b) + c` so whenever you see an expression such as `a + b + c + d`
you need to remember how this is read by Lean: `((a + b) + c) + d`

We know that the brackets aren't required, but in Lean you need to prove this.
-/
--#check 1 + (2 + 4) -- brackets
--#check (1 + 2) + 4 -- no brackets

lemma Add_assoc (a b c : ℕ) : (a + b) + c = a + (b + c):=by
  induction' c with n ih
  rfl
  rw [Add_succ,ih,Add_succ,Add_succ]

lemma Add_comm (a b : ℕ) : a + b = b + a :=by
  induction' b with b ib
  rw [Zero_add,Add_zero]
  rw [Succ_add,Add_succ,ib]

-- /-
-- Multiplication is also defined inductively in Lean.

-- def mul : ℕ → ℕ → ℕ
-- | a 0     := 0                      --  a * 0 := 0
-- | a (b + 1) := (mul a b) + a        --  a * (b + 1) = (a * b) + a  -/

lemma Mul_zero (n : ℕ) : n * 0 = 0:=by
rfl

lemma Mul_succ (m n : ℕ) : m * n.succ = m * n + m:=by
rfl

lemma Succ_mul (m n : ℕ) : m.succ * n = m * n + n:=by
induction' n with n hn
rfl
rw [Mul_succ,hn,Mul_succ,Add_assoc,Add_assoc,Add_succ,Add_succ m,Add_comm n]

lemma Zero_mul (n : ℕ) : 0 * n = 0:=by
induction' n with n hn
rfl
rw [Mul_succ, hn]

lemma Mul_one (n : ℕ) : n * 1 = n:=by
rw [Mul_succ,Mul_zero,Zero_add]

lemma One_mul (n : ℕ) : 1 * n = n:=by
induction' n with n hn
rfl
rw [Mul_succ,hn]

lemma Mul_add (a b c: ℕ) : a*(b + c) = a*b + a*c:=by
induction' a with a ha
rw [Zero_mul,Zero_mul,Zero_mul]
rw [Succ_mul,ha,Succ_mul,Succ_mul,Add_assoc,Add_assoc, Add_comm (a*c),Add_comm (a*c),Add_assoc]

lemma Add_mul (a b c: ℕ) : (b + c)*a = b*a +c*a:=by
induction' a with a ha
rfl
rw [Mul_succ,ha,Mul_succ,Mul_succ,Add_assoc,Add_assoc, Add_comm (c*a),Add_comm (c*a),Add_assoc]

lemma Mul_comm (a b : ℕ) : a * b = b * a :=by
induction' b with b hb
rw [Zero_mul]
rfl
rw [Mul_succ,hb,Succ_mul]

lemma Mul_assoc (a b c : ℕ) : a * b * c = a * (b * c):=by
induction' c with c hc
rw [Mul_zero]
rfl
rw [Mul_succ,Mul_succ,hc,Mul_add]

lemma Pow_zero (n : ℕ) : n ^ 0 = 1:=by
rfl

lemma Pow_succ (a b : ℕ) : a^b.succ= a^b * a:=by
rfl

lemma Pow_one (n : ℕ) : n ^ 1 = n:=by
rw [Pow_succ,Pow_zero,One_mul]

-- /-
-- # New use of tactic : cases

-- We don't need induction to prove our next result, but we do need to consider the cases of zero and
-- successor separately. The `cases n` tactic does exactly this.   -/

lemma Zero_pow (n : ℕ) (h : n ≠ 0): 0 ^ n = 0:=by
  cases' n with n
  contradiction
  rw [Pow_succ,Mul_zero]

lemma One_pow (n : ℕ) : 1 ^ n = 1:=by
induction' n with n hn
rw [Pow_zero]
rwa [Pow_succ,Mul_one]

lemma Pow_add (a b c: ℕ): a^(b + c)=a^b*a^c:=by
induction' c with c hc
rw [Add_zero,Pow_zero,Mul_one]
rw[Add_succ,Pow_succ,Pow_succ,← Mul_assoc,← hc]

lemma Pow_mul (a b c : ℕ) : a^(b * c) = (a^b)^c :=by
induction' c with c hc
rfl
rw [Pow_succ,Mul_succ,Pow_add,hc]

lemma Two_eq_one_add_one : 2 = 1 + 1:=by
rfl

lemma Two_mul (n : ℕ) : 2*n = n + n:=by
rw [Two_eq_one_add_one,Add_mul,One_mul]

lemma Three_mul (n : ℕ) : 3*n = n + n + n:=
have h3: 3 = 2 + 1:= by rfl
by rw [h3,Add_mul,One_mul,Two_mul]

lemma Pow_two (n : ℕ) : n^2 = n*n:=by
rw [Two_eq_one_add_one,Pow_add,Pow_one]

lemma Add_sq (a b : ℕ) : (a + b)^2 = a^2 + 2*a*b + b^2 :=by
rw [Pow_two,Pow_two,Pow_two,Add_mul,Mul_add,Mul_add,Two_mul,
    Add_mul,Mul_comm b a,Add_assoc,Add_assoc,Add_assoc]

lemma Pow_three (n : ℕ) : n^3 = n*n*n:=by
  have h3: 3 = 2 + 1 := by rfl
  rw [h3,Pow_add,Pow_two,Pow_one]

lemma Add_cube (a b : ℕ) : (a + b)^3 = a^3 + 3*a^2*b + 3*a*b^2 + b^3:=by
simp_rw [Pow_three,Pow_two,Add_mul,Mul_add,Add_mul,Three_mul,
        Add_mul,Add_assoc]
apply congrArg (λ x => a*a*a+x)
rw [Mul_comm, Mul_assoc]
apply congrArg (λ x => a*(a*b)+x)
apply congrArg (λ x => a*(a*b)+x)
simp_rw [←Add_assoc,Add_comm,Mul_comm b,Mul_assoc,Add_assoc]
apply congrArg (λ x => a*(b*b)+x)
apply congrArg (λ x => a*(b*b)+x)
simp_rw [Mul_comm b a,Add_assoc]
apply congrArg (λ x => a*(a*b)+x)
simp_rw [←Mul_assoc, Mul_comm b a]


import group_theory.subgroup.basic
import group_theory.group_action.conj_act
import group_theory.group_action.group
import tactic.group

namespace test
variables
  (G : Type*)  -- "Let G be a set"
  [group G] -- "And endow G with the structure of a group"

example : mul_action G G := infer_instance
example (a b : G) : a • b = a * b := rfl

example : conj_act G = G := rfl
example (a : conj_act G) (b : G) : a • b = a * b * a⁻¹ := rfl

@[derive group]
def Aut (G : Type*) [group G] := G ≃* G

instance : has_coe_to_fun (Aut G) (λ _, G → G) := ⟨λ e, e.to_fun⟩

def ρ : G →* Aut G :=
{ to_fun := λ g,
  { to_fun := λ x, g * x * g⁻¹,
    inv_fun := λ x, g⁻¹ * x * g,
    left_inv := λ x, by group,
    right_inv := λ x, by group,
    map_mul' := λ x y, by group },
  map_one' := by tidy,
  map_mul' := begin
    intros g h,
    ext x,
    dsimp,
    group,
  end }

def image_of_ρ : subgroup (Aut G) := (ρ G).range

theorem problem_3_on_homework_3 (t : image_of_ρ G) (e : Aut G) :
  e * t * e⁻¹ ∈ image_of_ρ G :=
begin
  obtain ⟨_,g,rfl⟩ := t,
  change ∃ h, _,
  let h : G := e g,
  use h,
  ext x,
  dsimp [ρ],
  simp,
end
end test

variables {G : Type*} [group G]
variable (X : Type*) -- "Let X be a set"
variable [mul_action (G × G) X] -- "Suppose that G × G acts on X."

def σ : G × G ≃* G × G :=
{ to_fun := λ g, ⟨g.2,g.1⟩,
  inv_fun := λ g, ⟨g.2, g.1⟩,
  left_inv := by tidy,
  right_inv := by tidy,
  map_mul' := by tidy }

theorem problem_1_on_homework_3
  (h : ∀ (x : X) (P : G × G), P • x = (σ P) • x) :
  ∀ a b ∈ (mul_action.to_perm_hom (G × G) X).range, a * b = b * a :=
begin
  intros a ha b hb,
  let ρ := (mul_action.to_perm_hom (G × G) X),
  have key_claim : ∀ g : G × G, ρ g = ρ (σ g),
  { intros g, ext x,
    dsimp [ρ,σ],
    apply h },
  obtain ⟨⟨g1,g2⟩,(rfl : ρ _ = _)⟩ := ha,
  obtain ⟨⟨h1,h2⟩,(rfl : ρ _ = _)⟩ := hb,
  have hg : (g1,g2) = (g1,1) * (1,g2), by tidy,
  have hh : (h1,h2) = (h1,1) * (1,h2), by tidy,
  rewrite [hg,hh],
  simp_rw [ρ.map_mul, ←mul_assoc],
  have claim1 : ρ (g1, 1) * ρ (1, g2) * ρ (h1, 1) * ρ (1, h2) =
    ρ (g1, 1) * ρ (h1, 1) * ρ (1, g2)  * ρ (1, h2),
  { congr' 1,
    simp_rw [mul_assoc],
    congr' 1,
    simp_rw [← ρ.map_mul],
    congr' 1,
    ext,
    dsimp,
    simp,
    dsimp,
    simp },
  rewrite [claim1],
  have claim2 : ρ (g1, 1) * ρ (h1, 1) = ρ (h1,1) * ρ (g1,1),
  { rw key_claim (g1,1),
    simp_rw [← ρ.map_mul],
    congr' 1,
    dsimp [σ], simp },
  rewrite [claim2],
  have claim3 : ρ (h1, 1) * ρ (g1, 1) * ρ (1, g2) * ρ (1, h2) =
    ρ (h1, 1) * ρ (g1, 1) * ρ (1, h2) * ρ (1, g2),
  { simp_rw [mul_assoc],
    congr' 2,
    rw key_claim (1,g2),
    simp_rw [← ρ.map_mul],
    congr' 1,
    dsimp [σ], simp },
  rewrite [claim3],
  have claim4 : ρ (h1, 1) * ρ (g1, 1) * ρ (1, h2) =
    ρ (h1, 1) * ρ (1, h2) * ρ (g1, 1),
  { simp_rw [← ρ.map_mul], congr' 1, simp, },
  rewrite [claim4],
end

#check mul_action.to_perm_hom


-- Las partes simétricas son simétricas
-- ====================================

-- ----------------------------------------------------
-- Ej. 1. La parte simétrica de una relación R es la
-- relación S definida por
--    S x y := R x y ∧ R y x
--
-- Demostrar que la parte simétrica de cualquier
-- relación es simétrica.
-- ----------------------------------------------------

section
parameter A : Type
parameter R : A → A → Prop

def S (x y : A) := R x y ∧ R y x

-- 1ª demostración
example : symmetric S :=
begin
  intros x y h,
  split,
  { exact h.right, },
  { exact h.left, },
end

-- 2ª demostración
example : symmetric S :=
begin
  intros x y h,
  exact ⟨h.right, h.left⟩,
end

-- 3ª demostración
example : symmetric S :=
λ x y h, ⟨h.right, h.left⟩

-- 4ª demostración
example : symmetric S :=
assume x y,
assume h : S x y,
have h1 : R x y, from h.left,
have h2 : R y x, from h.right,
show S y x, from ⟨h2, h1⟩

-- 5ª demostración
example : symmetric S :=
assume x y,
assume h : S x y,
show S y x, from ⟨h.right, h.left⟩

-- 6ª demostración
example : symmetric S :=
λ x y h, ⟨h.right, h.left⟩

end


/-
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
-/
import data.nat.with_bot
import data.polynomial.induction
import data.polynomial.monomial

/-!
# Theory of univariate polynomials

The definitions include
`degree`, `monic`, `leading_coeff`

Results include
- `degree_mul` : The degree of the product is the sum of degrees
- `leading_coeff_add_of_degree_eq` and `leading_coeff_add_of_degree_lt` :
    The leading_coefficient of a sum is determined by the leading coefficients and degrees
-/

noncomputable theory

open finsupp finset
open_locale big_operators classical

namespace polynomial
universes u v
variables {R : Type u} {S : Type v} {a b : R} {n m : ℕ}

section semiring
variables [semiring R] {p q r : polynomial R}

/-- `degree p` is the degree of the polynomial `p`, i.e. the largest `X`-exponent in `p`.
`degree p = some n` when `p ≠ 0` and `n` is the highest power of `X` that appears in `p`, otherwise
`degree 0 = ⊥`. -/
def degree (p : polynomial R) : with_bot ℕ := p.support.sup some

lemma degree_lt_wf : well_founded (λp q : polynomial R, degree p < degree q) :=
inv_image.wf degree (with_bot.well_founded_lt nat.lt_wf)

instance : has_well_founded (polynomial R) := ⟨_, degree_lt_wf⟩

/-- `nat_degree p` forces `degree p` to ℕ, by defining nat_degree 0 = 0. -/
def nat_degree (p : polynomial R) : ℕ := (degree p).get_or_else 0

/-- `leading_coeff p` gives the coefficient of the highest power of `X` in `p`-/
def leading_coeff (p : polynomial R) : R := coeff p (nat_degree p)

/-- a polynomial is `monic` if its leading coefficient is 1 -/
def monic (p : polynomial R) := leading_coeff p = (1 : R)

@[nontriviality] lemma monic_of_subsingleton [subsingleton R] (p : polynomial R) : monic p :=
subsingleton.elim _ _

lemma monic.def : monic p ↔ leading_coeff p = 1 := iff.rfl

instance monic.decidable [decidable_eq R] : decidable (monic p) :=
by unfold monic; apply_instance

@[simp] lemma monic.leading_coeff {p : polynomial R} (hp : p.monic) :
  leading_coeff p = 1 := hp

lemma monic.coeff_nat_degree {p : polynomial R} (hp : p.monic) : p.coeff p.nat_degree = 1 := hp

@[simp] lemma degree_zero : degree (0 : polynomial R) = ⊥ := rfl

@[simp] lemma nat_degree_zero : nat_degree (0 : polynomial R) = 0 := rfl

@[simp] lemma coeff_nat_degree : coeff p (nat_degree p) = leading_coeff p := rfl

lemma degree_eq_bot : degree p = ⊥ ↔ p = 0 :=
⟨λ h, by rw [degree, ← max_eq_sup_with_bot] at h;
  exact support_eq_empty.1 (max_eq_none.1 h),
λ h, h.symm ▸ rfl⟩

@[nontriviality] lemma degree_of_subsingleton [subsingleton R] : degree p = ⊥ :=
by rw [subsingleton.elim p 0, degree_zero]

@[nontriviality] lemma nat_degree_of_subsingleton [subsingleton R] : nat_degree p = 0 :=
by rw [subsingleton.elim p 0, nat_degree_zero]

lemma degree_eq_nat_degree (hp : p ≠ 0) : degree p = (nat_degree p : with_bot ℕ) :=
let ⟨n, hn⟩ :=
  not_forall.1 (mt option.eq_none_iff_forall_not_mem.2 (mt degree_eq_bot.1 hp)) in
have hn : degree p = some n := not_not.1 hn,
by rw [nat_degree, hn]; refl

lemma degree_eq_iff_nat_degree_eq {p : polynomial R} {n : ℕ} (hp : p ≠ 0) :
  p.degree = n ↔ p.nat_degree = n :=
by rw [degree_eq_nat_degree hp, with_bot.coe_eq_coe]

lemma degree_eq_iff_nat_degree_eq_of_pos {p : polynomial R} {n : ℕ} (hn : 0 < n) :
  p.degree = n ↔ p.nat_degree = n :=
begin
  split,
  { intro H, rwa ← degree_eq_iff_nat_degree_eq, rintro rfl,
    rw degree_zero at H, exact option.no_confusion H },
  { intro H, rwa degree_eq_iff_nat_degree_eq, rintro rfl,
    rw nat_degree_zero at H, rw H at hn, exact lt_irrefl _ hn }
end

lemma nat_degree_eq_of_degree_eq_some {p : polynomial R} {n : ℕ}
  (h : degree p = n) : nat_degree p = n :=
have hp0 : p ≠ 0, from λ hp0, by rw hp0 at h; exact option.no_confusion h,
option.some_inj.1 $ show (nat_degree p : with_bot ℕ) = n,
  by rwa [← degree_eq_nat_degree hp0]

@[simp] lemma degree_le_nat_degree : degree p ≤ nat_degree p :=
with_bot.gi_get_or_else_bot.gc.le_u_l _

lemma nat_degree_eq_of_degree_eq [semiring S] {q : polynomial S} (h : degree p = degree q) :
  nat_degree p = nat_degree q :=
by unfold nat_degree; rw h

lemma le_degree_of_ne_zero (h : coeff p n ≠ 0) : (n : with_bot ℕ) ≤ degree p :=
show @has_le.le (with_bot ℕ) _ (some n : with_bot ℕ) (p.support.sup some : with_bot ℕ),
from finset.le_sup (mem_support_iff.2 h)

lemma le_nat_degree_of_ne_zero (h : coeff p n ≠ 0) : n ≤ nat_degree p :=
begin
  rw [← with_bot.coe_le_coe, ← degree_eq_nat_degree],
  exact le_degree_of_ne_zero h,
  { assume h, subst h, exact h rfl }
end

lemma le_nat_degree_of_mem_supp (a : ℕ) :
  a ∈ p.support → a ≤ nat_degree p:=
le_nat_degree_of_ne_zero ∘ mem_support_iff.mp

lemma supp_subset_range (h : nat_degree p < m) : p.support ⊆ finset.range m :=
λ n hn, mem_range.2 $ (le_nat_degree_of_mem_supp _ hn).trans_lt h

lemma supp_subset_range_nat_degree_succ : p.support ⊆ finset.range (nat_degree p + 1) :=
supp_subset_range (nat.lt_succ_self _)

lemma degree_le_degree (h : coeff q (nat_degree p) ≠ 0) : degree p ≤ degree q :=
begin
  by_cases hp : p = 0,
  { rw hp, exact bot_le },
  { rw degree_eq_nat_degree hp, exact le_degree_of_ne_zero h }
end

lemma degree_ne_of_nat_degree_ne {n : ℕ} :
  p.nat_degree ≠ n → degree p ≠ n :=
mt $ λ h, by rw [nat_degree, h, option.get_or_else_coe]

theorem nat_degree_le_iff_degree_le {n : ℕ} : nat_degree p ≤ n ↔ degree p ≤ n :=
with_bot.get_or_else_bot_le_iff

alias polynomial.nat_degree_le_iff_degree_le ↔ . .

lemma nat_degree_le_nat_degree [semiring S] {q : polynomial S} (hpq : p.degree ≤ q.degree) :
  p.nat_degree ≤ q.nat_degree :=
with_bot.gi_get_or_else_bot.gc.monotone_l hpq

@[simp] lemma degree_C (ha : a ≠ 0) : degree (C a) = (0 : with_bot ℕ) :=
by { rw [degree, ← monomial_zero_left, support_monomial 0 _ ha, sup_singleton], refl }

lemma degree_C_le : degree (C a) ≤ 0 :=
by by_cases h : a = 0; [rw [h, C_0], rw [degree_C h]]; [exact bot_le, exact le_refl _]

lemma degree_one_le : degree (1 : polynomial R) ≤ (0 : with_bot ℕ) :=
by rw [← C_1]; exact degree_C_le

@[simp] lemma nat_degree_C (a : R) : nat_degree (C a) = 0 :=
begin
  by_cases ha : a = 0,
  { have : C a = 0, { rw [ha, C_0] },
    rw [nat_degree, degree_eq_bot.2 this],
    refl },
  { rw [nat_degree, degree_C ha], refl }
end

@[simp] lemma nat_degree_one : nat_degree (1 : polynomial R) = 0 := nat_degree_C 1

@[simp] lemma nat_degree_nat_cast (n : ℕ) : nat_degree (n : polynomial R) = 0 :=
by simp only [←C_eq_nat_cast, nat_degree_C]

@[simp] lemma degree_monomial (n : ℕ) (ha : a ≠ 0) : degree (monomial n a) = n :=
by rw [degree, support_monomial _ _ ha]; refl

@[simp] lemma degree_C_mul_X_pow (n : ℕ) (ha : a ≠ 0) : degree (C a * X ^ n) = n :=
by rw [← monomial_eq_C_mul_X, degree_monomial n ha]

lemma degree_monomial_le (n : ℕ) (a : R) : degree (monomial n a) ≤ n :=
if h : a = 0 then by rw [h, (monomial n).map_zero]; exact bot_le else le_of_eq (degree_monomial n h)

lemma degree_C_mul_X_pow_le (n : ℕ) (a : R) : degree (C a * X ^ n) ≤ n :=
by { rw C_mul_X_pow_eq_monomial, apply degree_monomial_le }

lemma degree_C_mul_X_le (a : R) : degree (C a * X) ≤ 1 :=
by simpa only [pow_one] using degree_C_mul_X_pow_le 1 a

@[simp] lemma nat_degree_C_mul_X_pow (n : ℕ) (a : R) (ha : a ≠ 0) : nat_degree (C a * X ^ n) = n :=
nat_degree_eq_of_degree_eq_some (degree_C_mul_X_pow n ha)

@[simp] lemma nat_degree_C_mul_X (a : R) (ha : a ≠ 0) : nat_degree (C a * X) = 1 :=
by simpa only [pow_one] using nat_degree_C_mul_X_pow 1 a ha

@[simp] lemma nat_degree_monomial (i : ℕ) (r : R) (hr : r ≠ 0) :
  nat_degree (monomial i r) = i :=
by rw [← C_mul_X_pow_eq_monomial, nat_degree_C_mul_X_pow i r hr]

lemma coeff_eq_zero_of_degree_lt (h : degree p < n) : coeff p n = 0 :=
not_not.1 (mt le_degree_of_ne_zero (not_le_of_gt h))

lemma coeff_eq_zero_of_nat_degree_lt {p : polynomial R} {n : ℕ} (h : p.nat_degree < n) :
  p.coeff n = 0 :=
begin
  apply coeff_eq_zero_of_degree_lt,
  by_cases hp : p = 0,
  { subst hp, exact with_bot.bot_lt_coe n },
  { rwa [degree_eq_nat_degree hp, with_bot.coe_lt_coe] }
end

@[simp] lemma coeff_nat_degree_succ_eq_zero {p : polynomial R} : p.coeff (p.nat_degree + 1) = 0 :=
coeff_eq_zero_of_nat_degree_lt (lt_add_one _)

-- We need the explicit `decidable` argument here because an exotic one shows up in a moment!
lemma ite_le_nat_degree_coeff (p : polynomial R) (n : ℕ) (I : decidable (n < 1 + nat_degree p)) :
  @ite _ (n < 1 + nat_degree p) I (coeff p n) 0 = coeff p n :=
begin
  split_ifs,
  { refl },
  { exact (coeff_eq_zero_of_nat_degree_lt (not_le.1 (λ w, h (nat.lt_one_add_iff.2 w)))).symm, }
end

lemma as_sum_support (p : polynomial R) :
  p = ∑ i in p.support, monomial i (p.coeff i) :=
(sum_monomial_eq p).symm

lemma as_sum_support_C_mul_X_pow (p : polynomial R) :
  p = ∑ i in p.support, C (p.coeff i) * X^i :=
trans p.as_sum_support $ by simp only [C_mul_X_pow_eq_monomial]

/--
We can reexpress a sum over `p.support` as a sum over `range n`,
for any `n` satisfying `p.nat_degree < n`.
-/
lemma sum_over_range' [add_comm_monoid S] (p : polynomial R) {f : ℕ → R → S} (h : ∀ n, f n 0 = 0)
  (n : ℕ) (w : p.nat_degree < n) :
  p.sum f = ∑ (a : ℕ) in range n, f a (coeff p a) :=
begin
  rcases p,
  have := supp_subset_range w,
  simp only [polynomial.sum, support, coeff, nat_degree, degree] at ⊢ this,
  exact finsupp.sum_of_support_subset _ this _ (λ n hn, h n)
end

/--
We can reexpress a sum over `p.support` as a sum over `range (p.nat_degree + 1)`.
-/
lemma sum_over_range [add_comm_monoid S] (p : polynomial R) {f : ℕ → R → S} (h : ∀ n, f n 0 = 0) :
  p.sum f = ∑ (a : ℕ) in range (p.nat_degree + 1), f a (coeff p a) :=
sum_over_range' p h (p.nat_degree + 1) (lt_add_one _)

lemma as_sum_range' (p : polynomial R) (n : ℕ) (w : p.nat_degree < n) :
  p = ∑ i in range n, monomial i (coeff p i) :=
p.sum_monomial_eq.symm.trans $ p.sum_over_range' monomial_zero_right _ w

lemma as_sum_range (p : polynomial R) :
  p = ∑ i in range (p.nat_degree + 1), monomial i (coeff p i) :=
p.sum_monomial_eq.symm.trans $ p.sum_over_range $ monomial_zero_right

lemma as_sum_range_C_mul_X_pow (p : polynomial R) :
  p = ∑ i in range (p.nat_degree + 1), C (coeff p i) * X ^ i :=
p.as_sum_range.trans $ by simp only [C_mul_X_pow_eq_monomial]

lemma coeff_ne_zero_of_eq_degree (hn : degree p = n) :
  coeff p n ≠ 0 :=
λ h, mem_support_iff.mp (mem_of_max hn) h

lemma eq_X_add_C_of_degree_le_one (h : degree p ≤ 1) :
  p = C (p.coeff 1) * X + C (p.coeff 0) :=
ext (λ n, nat.cases_on n (by simp)
  (λ n, nat.cases_on n (by simp [coeff_C])
    (λ m, have degree p < m.succ.succ, from lt_of_le_of_lt h dec_trivial,
      by simp [coeff_eq_zero_of_degree_lt this, coeff_C, nat.succ_ne_zero, coeff_X,
        nat.succ_inj', @eq_comm ℕ 0])))

lemma eq_X_add_C_of_degree_eq_one (h : degree p = 1) :
  p = C (p.leading_coeff) * X + C (p.coeff 0) :=
(eq_X_add_C_of_degree_le_one (show degree p ≤ 1, from h ▸ le_refl _)).trans
  (by simp [leading_coeff, nat_degree_eq_of_degree_eq_some h])

lemma eq_X_add_C_of_nat_degree_le_one (h : nat_degree p ≤ 1) :
  p = C (p.coeff 1) * X + C (p.coeff 0) :=
eq_X_add_C_of_degree_le_one $ degree_le_of_nat_degree_le h

lemma exists_eq_X_add_C_of_nat_degree_le_one (h : nat_degree p ≤ 1) :
  ∃ a b, p = C a * X + C b :=
⟨p.coeff 1, p.coeff 0, eq_X_add_C_of_nat_degree_le_one h⟩

theorem degree_X_pow_le (n : ℕ) : degree (X^n : polynomial R) ≤ n :=
by simpa only [C_1, one_mul] using degree_C_mul_X_pow_le n (1:R)

theorem degree_X_le : degree (X : polynomial R) ≤ 1 :=
degree_monomial_le _ _

lemma nat_degree_X_le : (X : polynomial R).nat_degree ≤ 1 :=
nat_degree_le_of_degree_le degree_X_le

lemma support_C_mul_X_pow (c : R) (n : ℕ) : (C c * X ^ n).support ⊆ singleton n :=
begin
  rw [C_mul_X_pow_eq_monomial],
  exact support_monomial' _ _
end

lemma mem_support_C_mul_X_pow {n a : ℕ} {c : R} (h : a ∈ (C c * X ^ n).support) : a = n :=
mem_singleton.1 $ support_C_mul_X_pow _ _ h

lemma card_support_C_mul_X_pow_le_one {c : R} {n : ℕ} : (C c * X ^ n).support.card ≤ 1 :=
begin
  rw ← card_singleton n,
  apply card_le_of_subset (support_C_mul_X_pow c n),
end

lemma card_supp_le_succ_nat_degree (p : polynomial R) : p.support.card ≤ p.nat_degree + 1 :=
begin
  rw ← finset.card_range (p.nat_degree + 1),
  exact finset.card_le_of_subset supp_subset_range_nat_degree_succ,
end

lemma le_degree_of_mem_supp (a : ℕ) :
  a ∈ p.support → ↑a ≤ degree p :=
le_degree_of_ne_zero ∘ mem_support_iff.mp

lemma nonempty_support_iff : p.support.nonempty ↔ p ≠ 0 :=
by rw [ne.def, nonempty_iff_ne_empty, ne.def, ← support_eq_empty]

lemma support_C_mul_X_pow_nonzero {c : R} {n : ℕ} (h : c ≠ 0) :
  (C c * X ^ n).support = singleton n :=
begin
  rw [C_mul_X_pow_eq_monomial],
  exact support_monomial _ _ h
end

end semiring

section nonzero_semiring
variables [semiring R] [nontrivial R] {p q : polynomial R}

@[simp] lemma degree_one : degree (1 : polynomial R) = (0 : with_bot ℕ) :=
degree_C (show (1 : R) ≠ 0, from zero_ne_one.symm)

@[simp] lemma degree_X : degree (X : polynomial R) = 1 :=
degree_monomial _ one_ne_zero

@[simp] lemma nat_degree_X : (X : polynomial R).nat_degree = 1 :=
nat_degree_eq_of_degree_eq_some degree_X

end nonzero_semiring

section ring
variables [ring R]

lemma coeff_mul_X_sub_C {p : polynomial R} {r : R} {a : ℕ} :
  coeff (p * (X - C r)) (a + 1) = coeff p a - coeff p (a + 1) * r :=
by simp [mul_sub]

lemma C_eq_int_cast (n : ℤ) : C (n : R) = n :=
(C : R →+* _).map_int_cast n

@[simp] lemma degree_neg (p : polynomial R) : degree (-p) = degree p :=
by unfold degree; rw support_neg

@[simp] lemma nat_degree_neg (p : polynomial R) : nat_degree (-p) = nat_degree p :=
by simp [nat_degree]

@[simp] lemma nat_degree_int_cast (n : ℤ) : nat_degree (n : polynomial R) = 0 :=
by simp only [←C_eq_int_cast, nat_degree_C]

end ring

section semiring
variables [semiring R]

/-- The second-highest coefficient, or 0 for constants -/
def next_coeff (p : polynomial R) : R :=
if p.nat_degree = 0 then 0 else p.coeff (p.nat_degree - 1)

@[simp]
lemma next_coeff_C_eq_zero (c : R) :
  next_coeff (C c) = 0 := by { rw next_coeff, simp }

lemma next_coeff_of_pos_nat_degree (p : polynomial R) (hp : 0 < p.nat_degree) :
  next_coeff p = p.coeff (p.nat_degree - 1) :=
by { rw [next_coeff, if_neg], contrapose! hp, simpa }

end semiring

section semiring
variables [semiring R] {p q : polynomial R} {ι : Type*}

lemma coeff_nat_degree_eq_zero_of_degree_lt (h : degree p < degree q) :
  coeff p (nat_degree q) = 0 :=
coeff_eq_zero_of_degree_lt (lt_of_lt_of_le h degree_le_nat_degree)

lemma ne_zero_of_degree_gt {n : with_bot ℕ} (h : n < degree p) : p ≠ 0 :=
mt degree_eq_bot.2 (ne.symm (ne_of_lt (lt_of_le_of_lt bot_le h)))

lemma ne_zero_of_degree_ge_degree (hpq : p.degree ≤ q.degree) (hp : p ≠ 0) : q ≠ 0 :=
polynomial.ne_zero_of_degree_gt (lt_of_lt_of_le (bot_lt_iff_ne_bot.mpr
  (by rwa [ne.def, polynomial.degree_eq_bot])) hpq : q.degree > ⊥)

lemma ne_zero_of_nat_degree_gt {n : ℕ} (h : n < nat_degree p) : p ≠ 0 :=
λ H, by simpa [H, nat.not_lt_zero] using h

lemma degree_lt_degree (h : nat_degree p < nat_degree q) : degree p < degree q :=
begin
  by_cases hp : p = 0,
  { simp [hp],
    rw bot_lt_iff_ne_bot,
    intro hq,
    simpa [hp, degree_eq_bot.mp hq, lt_irrefl] using h },
  { rw [degree_eq_nat_degree hp, degree_eq_nat_degree $ ne_zero_of_nat_degree_gt h],
    exact_mod_cast h }
end

lemma nat_degree_lt_nat_degree_iff (hp : p ≠ 0) :
  nat_degree p < nat_degree q ↔ degree p < degree q :=
⟨degree_lt_degree, begin
  intro h,
  have hq : q ≠ 0 := ne_zero_of_degree_gt h,
  rw [degree_eq_nat_degree hp, degree_eq_nat_degree hq] at h,
  exact_mod_cast h
end⟩

lemma eq_C_of_degree_le_zero (h : degree p ≤ 0) : p = C (coeff p 0) :=
begin
  ext (_|n), { simp },
  rw [coeff_C, if_neg (nat.succ_ne_zero _), coeff_eq_zero_of_degree_lt],
  exact h.trans_lt (with_bot.some_lt_some.2 n.succ_pos),
end

lemma eq_C_of_degree_eq_zero (h : degree p = 0) : p = C (coeff p 0) :=
eq_C_of_degree_le_zero (h ▸ le_refl _)

lemma degree_le_zero_iff : degree p ≤ 0 ↔ p = C (coeff p 0) :=
⟨eq_C_of_degree_le_zero, λ h, h.symm ▸ degree_C_le⟩

lemma degree_add_le (p q : polynomial R) : degree (p + q) ≤ max (degree p) (degree q) :=
calc degree (p + q) = ((p + q).support).sup some : rfl
  ... ≤ (p.support ∪ q.support).sup some : sup_mono support_add
  ... = p.support.sup some ⊔ q.support.sup some : sup_union

lemma nat_degree_add_le (p q : polynomial R) :
  nat_degree (p + q) ≤ max (nat_degree p) (nat_degree q) :=
begin
  cases le_max_iff.1 (degree_add_le p q);
  simp [nat_degree_le_nat_degree h]
end

@[simp] lemma leading_coeff_zero : leading_coeff (0 : polynomial R) = 0 := rfl

@[simp] lemma leading_coeff_eq_zero : leading_coeff p = 0 ↔ p = 0 :=
⟨λ h, by_contradiction $ λ hp, mt mem_support_iff.1
  (not_not.2 h) (mem_of_max (degree_eq_nat_degree hp)),
λ h, h.symm ▸ leading_coeff_zero⟩

lemma leading_coeff_ne_zero : leading_coeff p ≠ 0 ↔ p ≠ 0 :=
by rw [ne.def, leading_coeff_eq_zero]

lemma leading_coeff_eq_zero_iff_deg_eq_bot : leading_coeff p = 0 ↔ degree p = ⊥ :=
by rw [leading_coeff_eq_zero, degree_eq_bot]

lemma nat_degree_mem_support_of_nonzero (H : p ≠ 0) : p.nat_degree ∈ p.support :=
by { rw mem_support_iff, exact (not_congr leading_coeff_eq_zero).mpr H }

lemma nat_degree_eq_support_max' (h : p ≠ 0) :
  p.nat_degree = p.support.max' (nonempty_support_iff.mpr h) :=
(le_max' _ _ $ nat_degree_mem_support_of_nonzero h).antisymm $
  max'_le _ _ _ le_nat_degree_of_mem_supp

lemma nat_degree_C_mul_X_pow_le (a : R) (n : ℕ) : nat_degree (C a * X ^ n) ≤ n :=
nat_degree_le_iff_degree_le.2 $ degree_C_mul_X_pow_le _ _

lemma degree_add_eq_left_of_degree_lt (h : degree q < degree p) : degree (p + q) = degree p :=
le_antisymm (max_eq_left_of_lt h ▸ degree_add_le _ _) $ degree_le_degree $
  begin
    rw [coeff_add, coeff_nat_degree_eq_zero_of_degree_lt h, add_zero],
    exact mt leading_coeff_eq_zero.1 (ne_zero_of_degree_gt h)
  end

lemma degree_add_eq_right_of_degree_lt (h : degree p < degree q) : degree (p + q) = degree q :=
by rw [add_comm, degree_add_eq_left_of_degree_lt h]

lemma degree_add_C (hp : 0 < degree p) : degree (p + C a) = degree p :=
add_comm (C a) p ▸ degree_add_eq_right_of_degree_lt $ lt_of_le_of_lt degree_C_le hp

lemma degree_add_eq_of_leading_coeff_add_ne_zero (h : leading_coeff p + leading_coeff q ≠ 0) :
  degree (p + q) = max p.degree q.degree :=
le_antisymm (degree_add_le _ _) $
  match lt_trichotomy (degree p) (degree q) with
  | or.inl hlt :=
    by rw [degree_add_eq_right_of_degree_lt hlt, max_eq_right_of_lt hlt]; exact le_refl _
  | or.inr (or.inl heq) :=
    le_of_not_gt $
      assume hlt : max (degree p) (degree q) > degree (p + q),
      h $ show leading_coeff p + leading_coeff q = 0,
      begin
        rw [heq, max_self] at hlt,
        rw [leading_coeff, leading_coeff, nat_degree_eq_of_degree_eq heq, ← coeff_add],
        exact coeff_nat_degree_eq_zero_of_degree_lt hlt
      end
  | or.inr (or.inr hlt) :=
    by rw [degree_add_eq_left_of_degree_lt hlt, max_eq_left_of_lt hlt]; exact le_refl _
  end

lemma degree_erase_le (p : polynomial R) (n : ℕ) : degree (p.erase n) ≤ degree p :=
by { rcases p, simp only [erase, degree, coeff, support], convert sup_mono (erase_subset _ _) }

lemma degree_erase_lt (hp : p ≠ 0) : degree (p.erase (nat_degree p)) < degree p :=
begin
  apply lt_of_le_of_ne (degree_erase_le _ _),
  rw [degree_eq_nat_degree hp, degree, support_erase],
  exact λ h, not_mem_erase _ _ (mem_of_max h),
end

lemma degree_update_le (p : polynomial R) (n : ℕ) (a : R) :
  degree (p.update n a) ≤ max (degree p) n :=
begin
  simp only [degree, coeff_update_apply, le_max_iff, finset.sup_le_iff, mem_support_iff],
  intros b hb,
  split_ifs at hb with h,
  { subst b,
    exact or.inr le_rfl },
  { exact or.inl (le_degree_of_ne_zero hb) }
end

lemma degree_sum_le (s : finset ι) (f : ι → polynomial R) :
  degree (∑ i in s, f i) ≤ s.sup (λ b, degree (f b)) :=
finset.induction_on s (by simp only [sum_empty, sup_empty, degree_zero, le_refl]) $
  assume a s has ih,
  calc degree (∑ i in insert a s, f i) ≤ max (degree (f a)) (degree (∑ i in s, f i)) :
    by rw sum_insert has; exact degree_add_le _ _
  ... ≤ _ : by rw [sup_insert, sup_eq_max]; exact max_le_max le_rfl ih

lemma degree_mul_le (p q : polynomial R) : degree (p * q) ≤ degree p + degree q :=
calc degree (p * q) ≤ (p.support).sup (λi, degree (sum q (λj a, C (coeff p i * a) * X ^ (i + j)))) :
    begin
      simp only [monomial_eq_C_mul_X.symm],
      convert degree_sum_le _ _,
      exact mul_eq_sum_sum
    end
  ... ≤ p.support.sup (λi, q.support.sup (λj, degree (C (coeff p i * coeff q j) * X ^ (i + j)))) :
    finset.sup_mono_fun (assume i hi,  degree_sum_le _ _)
  ... ≤ degree p + degree q :
    begin
      refine finset.sup_le (λ a ha, finset.sup_le (λ b hb, le_trans (degree_C_mul_X_pow_le _ _) _)),
      rw [with_bot.coe_add],
      rw mem_support_iff at ha hb,
      exact add_le_add (le_degree_of_ne_zero ha) (le_degree_of_ne_zero hb)
    end

lemma degree_pow_le (p : polynomial R) : ∀ (n : ℕ), degree (p ^ n) ≤ n • (degree p)
| 0     := by rw [pow_zero, zero_nsmul]; exact degree_one_le
| (n+1) := calc degree (p ^ (n + 1)) ≤ degree p + degree (p ^ n) :
    by rw pow_succ; exact degree_mul_le _ _
  ... ≤ _ : by rw succ_nsmul; exact add_le_add (le_refl _) (degree_pow_le _)

@[simp] lemma leading_coeff_monomial (a : R) (n : ℕ) : leading_coeff (monomial n a) = a :=
begin
  by_cases ha : a = 0,
  { simp only [ha, (monomial n).map_zero, leading_coeff_zero] },
  { rw [leading_coeff, nat_degree_monomial _ _ ha, coeff_monomial], simp }
end

lemma leading_coeff_C_mul_X_pow (a : R) (n : ℕ) : leading_coeff (C a * X ^ n) = a :=
by rw [C_mul_X_pow_eq_monomial, leading_coeff_monomial]

@[simp]

@[simp] lemma leading_coeff_X_pow (n : ℕ) : leading_coeff ((X : polynomial R) ^ n) = 1 :=
by simpa only [C_1, one_mul] using leading_coeff_C_mul_X_pow (1 : R) n

@[simp] lemma leading_coeff_X : leading_coeff (X : polynomial R) = 1 :=
by simpa only [pow_one] using @leading_coeff_X_pow R _ 1

@[simp] lemma monic_X_pow (n : ℕ) : monic (X ^ n : polynomial R) := leading_coeff_X_pow n

@[simp] lemma monic_X : monic (X : polynomial R) := leading_coeff_X

@[simp] lemma leading_coeff_one : leading_coeff (1 : polynomial R) = 1 :=
leading_coeff_C 1

@[simp] lemma monic_one : monic (1 : polynomial R) := leading_coeff_C _

lemma monic.ne_zero {R : Type*} [semiring R] [nontrivial R] {p : polynomial R} (hp : p.monic) :
  p ≠ 0 :=
by { rintro rfl, simpa [monic] using hp }

lemma monic.ne_zero_of_ne (h : (0:R) ≠ 1) {p : polynomial R} (hp : p.monic) :
  p ≠ 0 :=
by { nontriviality R, exact hp.ne_zero }

lemma monic.ne_zero_of_polynomial_ne {r} (hp : monic p) (hne : q ≠ r) : p ≠ 0 :=
by { haveI := nontrivial.of_polynomial_ne hne, exact hp.ne_zero }

lemma leading_coeff_add_of_degree_lt (h : degree p < degree q) :
  leading_coeff (p + q) = leading_coeff q :=
have coeff p (nat_degree q) = 0, from coeff_nat_degree_eq_zero_of_degree_lt h,
by simp only [leading_coeff, nat_degree_eq_of_degree_eq (degree_add_eq_right_of_degree_lt h),
  this, coeff_add, zero_add]

lemma leading_coeff_add_of_degree_eq (h : degree p = degree q)
  (hlc : leading_coeff p + leading_coeff q ≠ 0) :
  leading_coeff (p + q) = leading_coeff p + leading_coeff q :=
have nat_degree (p + q) = nat_degree p,
  by apply nat_degree_eq_of_degree_eq;
    rw [degree_add_eq_of_leading_coeff_add_ne_zero hlc, h, max_self],
by simp only [leading_coeff, this, nat_degree_eq_of_degree_eq h, coeff_add]

@[simp] lemma coeff_mul_degree_add_degree (p q : polynomial R) :
  coeff (p * q) (nat_degree p + nat_degree q) = leading_coeff p * leading_coeff q :=
calc coeff (p * q) (nat_degree p + nat_degree q) =
    ∑ x in nat.antidiagonal (nat_degree p + nat_degree q),
    coeff p x.1 * coeff q x.2 : coeff_mul _ _ _
... = coeff p (nat_degree p) * coeff q (nat_degree q) :
  begin
    refine finset.sum_eq_single (nat_degree p, nat_degree q) _ _,
    { rintro ⟨i,j⟩ h₁ h₂, rw nat.mem_antidiagonal at h₁,
      by_cases H : nat_degree p < i,
      { rw [coeff_eq_zero_of_degree_lt
          (lt_of_le_of_lt degree_le_nat_degree (with_bot.coe_lt_coe.2 H)), zero_mul] },
      { rw not_lt_iff_eq_or_lt at H, cases H,
        { subst H, rw add_left_cancel_iff at h₁, dsimp at h₁, subst h₁, exfalso, exact h₂ rfl },
        { suffices : nat_degree q < j,
          { rw [coeff_eq_zero_of_degree_lt
              (lt_of_le_of_lt degree_le_nat_degree (with_bot.coe_lt_coe.2 this)), mul_zero] },
          { by_contra H', rw not_lt at H',
            exact ne_of_lt (nat.lt_of_lt_of_le
              (nat.add_lt_add_right H j) (nat.add_le_add_left H' _)) h₁ } } } },
    { intro H, exfalso, apply H, rw nat.mem_antidiagonal }
  end

lemma degree_mul' (h : leading_coeff p * leading_coeff q ≠ 0) :
  degree (p * q) = degree p + degree q :=
have hp : p ≠ 0 := by refine mt _ h; exact λ hp, by rw [hp, leading_coeff_zero, zero_mul],
have hq : q ≠ 0 := by refine mt _ h; exact λ hq, by rw [hq, leading_coeff_zero, mul_zero],
le_antisymm (degree_mul_le _ _)
begin
  rw [degree_eq_nat_degree hp, degree_eq_nat_degree hq],
  refine le_degree_of_ne_zero _,
  rwa coeff_mul_degree_add_degree
end

lemma monic.degree_mul (hq : monic q) : degree (p * q) = degree p + degree q :=
if hp : p = 0 then by simp [hp]
else degree_mul' $ by rwa [hq.leading_coeff, mul_one, ne.def, leading_coeff_eq_zero]

lemma nat_degree_mul' (h : leading_coeff p * leading_coeff q ≠ 0) :
  nat_degree (p * q) = nat_degree p + nat_degree q :=
have hp : p ≠ 0 := mt leading_coeff_eq_zero.2 (λ h₁, h $ by rw [h₁, zero_mul]),
have hq : q ≠ 0 := mt leading_coeff_eq_zero.2 (λ h₁, h $ by rw [h₁, mul_zero]),
nat_degree_eq_of_degree_eq_some $
  by rw [degree_mul' h, with_bot.coe_add, degree_eq_nat_degree hp, degree_eq_nat_degree hq]

lemma leading_coeff_mul' (h : leading_coeff p * leading_coeff q ≠ 0) :
  leading_coeff (p * q) = leading_coeff p * leading_coeff q :=
begin
  unfold leading_coeff,
  rw [nat_degree_mul' h, coeff_mul_degree_add_degree],
  refl
end

lemma monomial_nat_degree_leading_coeff_eq_self (h : p.support.card ≤ 1) :
  monomial p.nat_degree p.leading_coeff = p :=
begin
  rcases card_support_le_one_iff_monomial.1 h with ⟨n, a, rfl⟩,
  by_cases ha : a = 0;
  simp [ha]
end

lemma C_mul_X_pow_eq_self (h : p.support.card ≤ 1) :
  C p.leading_coeff * X^p.nat_degree = p :=
by rw [C_mul_X_pow_eq_monomial, monomial_nat_degree_leading_coeff_eq_self h]

lemma leading_coeff_pow' : leading_coeff p ^ n ≠ 0 →
  leading_coeff (p ^ n) = leading_coeff p ^ n :=
nat.rec_on n (by simp) $
λ n ih h,
have h₁ : leading_coeff p ^ n ≠ 0 :=
  λ h₁, h $ by rw [pow_succ, h₁, mul_zero],
have h₂ : leading_coeff p * leading_coeff (p ^ n) ≠ 0 :=
  by rwa [pow_succ, ← ih h₁] at h,
by rw [pow_succ, pow_succ, leading_coeff_mul' h₂, ih h₁]

lemma degree_pow' : ∀ {n : ℕ}, leading_coeff p ^ n ≠ 0 →
  degree (p ^ n) = n • (degree p)
| 0     := λ h, by rw [pow_zero, ← C_1] at *;
  rw [degree_C h, zero_nsmul]
| (n+1) := λ h,
have h₁ : leading_coeff p ^ n ≠ 0 := λ h₁, h $
  by rw [pow_succ, h₁, mul_zero],
have h₂ : leading_coeff p * leading_coeff (p ^ n) ≠ 0 :=
  by rwa [pow_succ, ← leading_coeff_pow' h₁] at h,
by rw [pow_succ, degree_mul' h₂, succ_nsmul, degree_pow' h₁]

lemma nat_degree_pow' {n : ℕ} (h : leading_coeff p ^ n ≠ 0) :
  nat_degree (p ^ n) = n * nat_degree p :=
if hp0 : p = 0 then
  if hn0 : n = 0 then by simp *
  else by rw [hp0, zero_pow (nat.pos_of_ne_zero hn0)]; simp
else
have hpn : p ^ n ≠ 0, from λ hpn0,  have h1 : _ := h,
  by rw [← leading_coeff_pow' h1, hpn0, leading_coeff_zero] at h;
  exact h rfl,
option.some_inj.1 $ show (nat_degree (p ^ n) : with_bot ℕ) = (n * nat_degree p : ℕ),
  by rw [← degree_eq_nat_degree hpn, degree_pow' h, degree_eq_nat_degree hp0,
    ← with_bot.coe_nsmul]; simp

theorem leading_coeff_mul_monic {p q : polynomial R} (hq : monic q) :
  leading_coeff (p * q) = leading_coeff p :=
decidable.by_cases
  (λ H : leading_coeff p = 0, by rw [H, leading_coeff_eq_zero.1 H, zero_mul, leading_coeff_zero])
  (λ H : leading_coeff p ≠ 0,
    by rw [leading_coeff_mul', hq.leading_coeff, mul_one];
      rwa [hq.leading_coeff, mul_one])

@[simp] theorem leading_coeff_mul_X_pow {p : polynomial R} {n : ℕ} :
  leading_coeff (p * X ^ n) = leading_coeff p :=
leading_coeff_mul_monic (monic_X_pow n)

@[simp] theorem leading_coeff_mul_X {p : polynomial R} :
  leading_coeff (p * X) = leading_coeff p :=
leading_coeff_mul_monic monic_X

lemma nat_degree_mul_le {p q : polynomial R} : nat_degree (p * q) ≤ nat_degree p + nat_degree q :=
begin
  apply nat_degree_le_of_degree_le,
  apply le_trans (degree_mul_le p q),
  rw with_bot.coe_add,
  refine add_le_add _ _; apply degree_le_nat_degree,
end

lemma subsingleton_of_monic_zero (h : monic (0 : polynomial R)) :
  (∀ p q : polynomial R, p = q) ∧ (∀ a b : R, a = b) :=
by rw [monic.def, leading_coeff_zero] at h;
  exact ⟨λ p q, by rw [← mul_one p, ← mul_one q, ← C_1, ← h, C_0, mul_zero, mul_zero],
    λ a b, by rw [← mul_one a, ← mul_one b, ← h, mul_zero, mul_zero]⟩

lemma zero_le_degree_iff {p : polynomial R} : 0 ≤ degree p ↔ p ≠ 0 :=
by rw [ne.def, ← degree_eq_bot];
  cases degree p; exact dec_trivial

lemma degree_nonneg_iff_ne_zero : 0 ≤ degree p ↔ p ≠ 0 :=
⟨λ h0p hp0, absurd h0p (by rw [hp0, degree_zero]; exact dec_trivial),
  λ hp0, le_of_not_gt (λ h, by simp [gt, degree_eq_bot, *] at *)⟩

lemma nat_degree_eq_zero_iff_degree_le_zero : p.nat_degree = 0 ↔ p.degree ≤ 0 :=
by rw [← nonpos_iff_eq_zero, nat_degree_le_iff_degree_le, with_bot.coe_zero]

theorem degree_le_iff_coeff_zero (f : polynomial R) (n : with_bot ℕ) :
  degree f ≤ n ↔ ∀ m : ℕ, n < m → coeff f m = 0 :=
⟨λ (H : finset.sup (f.support) some ≤ n) m (Hm : n < (m : with_bot ℕ)), decidable.of_not_not $ λ H4,
  have H1 : m ∉ f.support,
    from λ H2, not_lt_of_ge ((finset.sup_le_iff.1 H) m H2 : ((m : with_bot ℕ) ≤ n)) Hm,
  H1 $ mem_support_iff.2 H4,
λ H, finset.sup_le $ λ b Hb, decidable.of_not_not $ λ Hn,
  mem_support_iff.1 Hb $ H b $ lt_of_not_ge Hn⟩

theorem degree_lt_iff_coeff_zero (f : polynomial R) (n : ℕ) :
  degree f < n ↔ ∀ m : ℕ, n ≤ m → coeff f m = 0 :=
begin
  refine ⟨λ hf m hm, coeff_eq_zero_of_degree_lt (lt_of_lt_of_le hf (with_bot.coe_le_coe.2 hm)), _⟩,
  simp only [degree, finset.sup_lt_iff (with_bot.bot_lt_coe n), mem_support_iff,
    with_bot.some_eq_coe, with_bot.coe_lt_coe, ← @not_le ℕ],
  exact λ h m, mt (h m),
end

lemma degree_smul_le (a : R) (p : polynomial R) : degree (a • p) ≤ degree p :=
begin
  apply (degree_le_iff_coeff_zero _ _).2 (λ m hm, _),
  rw degree_lt_iff_coeff_zero at hm,
  simp [hm m (le_refl _)],
end

lemma nat_degree_smul_le (a : R) (p : polynomial R) : nat_degree (a • p) ≤ nat_degree p :=
nat_degree_le_nat_degree (degree_smul_le a p)

lemma degree_lt_degree_mul_X (hp : p ≠ 0) : p.degree < (p * X).degree :=
by haveI := nontrivial.of_polynomial_ne hp; exact
have leading_coeff p * leading_coeff X ≠ 0, by simpa,
by erw [degree_mul' this, degree_eq_nat_degree hp,
    degree_X, ← with_bot.coe_one, ← with_bot.coe_add, with_bot.coe_lt_coe];
  exact nat.lt_succ_self _

lemma nat_degree_pos_iff_degree_pos :
  0 < nat_degree p ↔ 0 < degree p :=
lt_iff_lt_of_le_iff_le nat_degree_le_iff_degree_le

lemma eq_C_of_nat_degree_le_zero (h : nat_degree p ≤ 0) : p = C (coeff p 0) :=
eq_C_of_degree_le_zero $ degree_le_of_nat_degree_le h

lemma eq_C_of_nat_degree_eq_zero (h : nat_degree p = 0) : p = C (coeff p 0) :=
eq_C_of_nat_degree_le_zero h.le

lemma ne_zero_of_coe_le_degree (hdeg : ↑n ≤ p.degree) : p ≠ 0 :=
by rw ← degree_nonneg_iff_ne_zero; exact trans (by exact_mod_cast n.zero_le) hdeg

lemma le_nat_degree_of_coe_le_degree (hdeg : ↑n ≤ p.degree) :
  n ≤ p.nat_degree :=
with_bot.coe_le_coe.mp ((degree_eq_nat_degree $ ne_zero_of_coe_le_degree hdeg) ▸ hdeg)

end semiring

section nontrivial_semiring
variables [semiring R] [nontrivial R] {p q : polynomial R}

@[simp] lemma degree_X_pow (n : ℕ) : degree ((X : polynomial R) ^ n) = n :=
by rw [X_pow_eq_monomial, degree_monomial _ (@one_ne_zero R _ _)]

@[simp] lemma nat_degree_X_pow (n : ℕ) : nat_degree ((X : polynomial R) ^ n) = n :=
nat_degree_eq_of_degree_eq_some (degree_X_pow n)

theorem not_is_unit_X : ¬ is_unit (X : polynomial R) :=
λ ⟨⟨_, g, hfg, hgf⟩, rfl⟩, @zero_ne_one R _ _ $
by { change g * monomial 1 1 = 1 at hgf, rw [← coeff_one_zero, ← hgf], simp }

@[simp] lemma degree_mul_X : degree (p * X) = degree p + 1 := by simp [monic_X.degree_mul]

@[simp] lemma degree_mul_X_pow : degree (p * X ^ n) = degree p + n :=
by simp [(monic_X_pow n).degree_mul]

end nontrivial_semiring

section ring
variables [ring R] {p q : polynomial R}

lemma degree_sub_le (p q : polynomial R) : degree (p - q) ≤ max (degree p) (degree q) :=
by simpa only [sub_eq_add_neg, degree_neg q] using degree_add_le p (-q)

lemma degree_sub_lt (hd : degree p = degree q)
  (hp0 : p ≠ 0) (hlc : leading_coeff p = leading_coeff q) :
  degree (p - q) < degree p :=
have hp : monomial (nat_degree p) (leading_coeff p) + p.erase (nat_degree p) = p :=
  monomial_add_erase _ _,
have hq : monomial (nat_degree q) (leading_coeff q) + q.erase (nat_degree q) = q :=
  monomial_add_erase _ _,
have hd' : nat_degree p = nat_degree q := by unfold nat_degree; rw hd,
have hq0 : q ≠ 0 := mt degree_eq_bot.2 (hd ▸ mt degree_eq_bot.1 hp0),
calc degree (p - q) = degree (erase (nat_degree q) p + -erase (nat_degree q) q) :
  by conv { to_lhs, rw [← hp, ← hq, hlc, hd', add_sub_add_left_eq_sub, sub_eq_add_neg] }
... ≤ max (degree (erase (nat_degree q) p)) (degree (erase (nat_degree q) q))
  : degree_neg (erase (nat_degree q) q) ▸ degree_add_le _ _
... < degree p : max_lt_iff.2 ⟨hd' ▸ degree_erase_lt hp0, hd.symm ▸ degree_erase_lt hq0⟩

lemma nat_degree_X_sub_C_le {r : R} : (X - C r).nat_degree ≤ 1 :=
nat_degree_le_iff_degree_le.2 $ le_trans (degree_sub_le _ _) $ max_le degree_X_le $
le_trans degree_C_le $ with_bot.coe_le_coe.2 zero_le_one

lemma degree_sum_fin_lt {n : ℕ} (f : fin n → R) :
  degree (∑ i : fin n, C (f i) * X ^ (i : ℕ)) < n :=
begin
  haveI : is_commutative (with_bot ℕ) max := ⟨max_comm⟩,
  haveI : is_associative (with_bot ℕ) max := ⟨max_assoc⟩,
  calc  (∑ i, C (f i) * X ^ (i : ℕ)).degree
      ≤ finset.univ.fold (⊔) ⊥ (λ i, (C (f i) * X ^ (i : ℕ)).degree) : degree_sum_le _ _
  ... = finset.univ.fold max ⊥ (λ i, (C (f i) * X ^ (i : ℕ)).degree) : rfl
  ... < n : (finset.fold_max_lt (n : with_bot ℕ)).mpr ⟨with_bot.bot_lt_coe _, _⟩,

  rintros ⟨i, hi⟩ -,
  calc (C (f ⟨i, hi⟩) * X ^ i).degree
      ≤ (C _).degree + (X ^ i).degree : degree_mul_le _ _
  ... ≤ 0 + i : add_le_add degree_C_le (degree_X_pow_le i)
  ... = i : zero_add _
  ... < n : with_bot.some_lt_some.mpr hi,
end

lemma degree_sub_eq_left_of_degree_lt (h : degree q < degree p) : degree (p - q) = degree p :=
by { rw ← degree_neg q at h, rw [sub_eq_add_neg, degree_add_eq_left_of_degree_lt h] }

lemma degree_sub_eq_right_of_degree_lt (h : degree p < degree q) : degree (p - q) = degree q :=
by { rw ← degree_neg q at h, rw [sub_eq_add_neg, degree_add_eq_right_of_degree_lt h, degree_neg] }

end ring

section nonzero_ring
variables [nontrivial R] [ring R]

@[simp] lemma degree_X_sub_C (a : R) : degree (X - C a) = 1 :=
have degree (C a) < degree (X : polynomial R),
from calc degree (C a) ≤ 0 : degree_C_le
                   ... < 1 : with_bot.some_lt_some.mpr zero_lt_one
                   ... = degree X : degree_X.symm,
by rw [degree_sub_eq_left_of_degree_lt this, degree_X]

@[simp] lemma degree_X_add_C (a : R) : degree (X + C a) = 1 :=
have degree (C a) < degree (X : polynomial R),
from calc degree (C a) ≤ 0 : degree_C_le
                   ... < 1 : with_bot.some_lt_some.mpr zero_lt_one
                   ... = degree X : degree_X.symm,
by rw [degree_add_eq_left_of_degree_lt this, degree_X]

@[simp] lemma nat_degree_X_sub_C (x : R) : (X - C x).nat_degree = 1 :=
nat_degree_eq_of_degree_eq_some $ degree_X_sub_C x

@[simp]
lemma next_coeff_X_sub_C (c : R) : next_coeff (X - C c) = - c :=
by simp [next_coeff_of_pos_nat_degree]

lemma degree_X_pow_sub_C {n : ℕ} (hn : 0 < n) (a : R) :
  degree ((X : polynomial R) ^ n - C a) = n :=
have degree (C a) < degree ((X : polynomial R) ^ n),
  from calc degree (C a) ≤ 0 : degree_C_le
  ... < degree ((X : polynomial R) ^ n) : by rwa [degree_X_pow];
    exact with_bot.coe_lt_coe.2 hn,
by rw [degree_sub_eq_left_of_degree_lt this, degree_X_pow]

lemma X_pow_sub_C_ne_zero {n : ℕ} (hn : 0 < n) (a : R) :
  (X : polynomial R) ^ n - C a ≠ 0 :=
mt degree_eq_bot.2 (show degree ((X : polynomial R) ^ n - C a) ≠ ⊥,
  by rw degree_X_pow_sub_C hn a; exact dec_trivial)

theorem X_sub_C_ne_zero (r : R) : X - C r ≠ 0 :=
pow_one (X : polynomial R) ▸ X_pow_sub_C_ne_zero zero_lt_one r

theorem zero_nmem_multiset_map_X_sub_C {α : Type*} (m : multiset α) (f : α → R) :
  (0 : polynomial R) ∉ m.map (λ a, X - C (f a)) :=
λ mem, let ⟨a, _, ha⟩ := multiset.mem_map.mp mem in X_sub_C_ne_zero _ ha

lemma nat_degree_X_pow_sub_C {n : ℕ} {r : R} :
  (X ^ n - C r).nat_degree = n :=
begin
  by_cases hn : n = 0,
  { rw [hn, pow_zero, ←C_1, ←ring_hom.map_sub, nat_degree_C] },
  { exact nat_degree_eq_of_degree_eq_some (degree_X_pow_sub_C (pos_iff_ne_zero.mpr hn) r) },
end

@[simp] lemma leading_coeff_X_pow_sub_C {n : ℕ} (hn : 0 < n) {r : R} :
  (X ^ n - C r).leading_coeff = 1 :=
by rw [leading_coeff, nat_degree_X_pow_sub_C, coeff_sub, coeff_X_pow_self,
  coeff_C, if_neg (pos_iff_ne_zero.mp hn), sub_zero]

@[simp] lemma leading_coeff_X_pow_sub_one {n : ℕ} (hn : 0 < n) :
  (X ^ n - 1 : polynomial R).leading_coeff = 1 :=
leading_coeff_X_pow_sub_C hn

end nonzero_ring

section no_zero_divisors
variables [semiring R] [no_zero_divisors R] {p q : polynomial R}

@[simp] lemma degree_mul : degree (p * q) = degree p + degree q :=
if hp0 : p = 0 then by simp only [hp0, degree_zero, zero_mul, with_bot.bot_add]
else if hq0 : q = 0 then  by simp only [hq0, degree_zero, mul_zero, with_bot.add_bot]
else degree_mul' $ mul_ne_zero (mt leading_coeff_eq_zero.1 hp0)
    (mt leading_coeff_eq_zero.1 hq0)

@[simp] lemma degree_pow [nontrivial R] (p : polynomial R) (n : ℕ) :
  degree (p ^ n) = n • (degree p) :=
by induction n; [simp only [pow_zero, degree_one, zero_nsmul],
simp only [*, pow_succ, succ_nsmul, degree_mul]]

@[simp] lemma leading_coeff_mul (p q : polynomial R) : leading_coeff (p * q) =
  leading_coeff p * leading_coeff q :=
begin
  by_cases hp : p = 0,
  { simp only [hp, zero_mul, leading_coeff_zero] },
  { by_cases hq : q = 0,
    { simp only [hq, mul_zero, leading_coeff_zero] },
    { rw [leading_coeff_mul'],
      exact mul_ne_zero (mt leading_coeff_eq_zero.1 hp) (mt leading_coeff_eq_zero.1 hq) } }
end

@[simp] lemma leading_coeff_X_add_C [nontrivial R] (a b : R) (ha : a ≠ 0):
  leading_coeff (C a * X + C b) = a :=
begin
  rw [add_comm, leading_coeff_add_of_degree_lt],
  { simp },
  { simpa [degree_C ha] using lt_of_le_of_lt degree_C_le (with_bot.coe_lt_coe.2 zero_lt_one)}
end

/-- `polynomial.leading_coeff` bundled as a `monoid_hom` when `R` has `no_zero_divisors`, and thus
  `leading_coeff` is multiplicative -/
def leading_coeff_hom : polynomial R →* R :=
{ to_fun := leading_coeff,
  map_one' := by simp,
  map_mul' := leading_coeff_mul }

@[simp] lemma leading_coeff_hom_apply (p : polynomial R) :
  leading_coeff_hom p = leading_coeff p := rfl

@[simp] lemma leading_coeff_pow (p : polynomial R) (n : ℕ) :
  leading_coeff (p ^ n) = leading_coeff p ^ n :=
(leading_coeff_hom : polynomial R →* R).map_pow p n

end no_zero_divisors

end polynomial


/-
Copyright (c) 2022 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov, Yaël Dillies
-/
import linear_algebra.ray
import analysis.normed_space.basic

/-!
# Rays in a real normed vector space

In this file we prove some lemmas about the `same_ray` predicate in case of a real normed space. In
this case, for two vectors `x y` in the same ray, the norm of their sum is equal to the sum of their
norms and `∥y∥ • x = ∥x∥ • y`.
-/

open real

variables {E : Type*} [semi_normed_group E] [normed_space ℝ E]
  {F : Type*} [normed_group F] [normed_space ℝ F]

namespace same_ray

variables {x y : E}

/-- If `x` and `y` are on the same ray, then the triangle inequality becomes the equality: the norm
of `x + y` is the sum of the norms of `x` and `y`. The converse is true for a strictly convex
space. -/
lemma norm_add (h : same_ray ℝ x y) : ∥x + y∥ = ∥x∥ + ∥y∥ :=
begin
  rcases h.exists_eq_smul with ⟨u, a, b, ha, hb, -, rfl, rfl⟩,
  rw [← add_smul, norm_smul_of_nonneg (add_nonneg ha hb), norm_smul_of_nonneg ha,
    norm_smul_of_nonneg hb, add_mul]
end

lemma norm_sub (h : same_ray ℝ x y) : ∥x - y∥ = |∥x∥ - ∥y∥| :=
begin
  rcases h.exists_eq_smul with ⟨u, a, b, ha, hb, -, rfl, rfl⟩,
  wlog hab : b ≤ a := le_total b a using [a b, b a] tactic.skip,
  { rw ← sub_nonneg at hab,
    rw [← sub_smul, norm_smul_of_nonneg hab, norm_smul_of_nonneg ha,
      norm_smul_of_nonneg hb, ← sub_mul, abs_of_nonneg (mul_nonneg hab (norm_nonneg _))] },
  { intros ha hb hab,
    rw [norm_sub_rev, this hb ha hab.symm, abs_sub_comm] }
end

lemma norm_smul_eq (h : same_ray ℝ x y) : ∥x∥ • y = ∥y∥ • x :=
begin
  rcases h.exists_eq_smul with ⟨u, a, b, ha, hb, -, rfl, rfl⟩,
  simp only [norm_smul_of_nonneg, *, mul_smul, smul_comm (∥u∥)],
  apply smul_comm
end

end same_ray

variables {x y : F}

lemma norm_inj_on_ray_left (hx : x ≠ 0) : {y | same_ray ℝ x y}.inj_on norm :=
begin
  rintro y hy z hz h,
  rcases hy.exists_nonneg_left hx with ⟨r, hr, rfl⟩,
  rcases hz.exists_nonneg_left hx with ⟨s, hs, rfl⟩,
  rw [norm_smul, norm_smul, mul_left_inj' (norm_ne_zero_iff.2 hx), norm_of_nonneg hr,
    norm_of_nonneg hs] at h,
  rw h
end

lemma norm_inj_on_ray_right (hy : y ≠ 0) : {x | same_ray ℝ x y}.inj_on norm :=
by simpa only [same_ray_comm] using norm_inj_on_ray_left hy

lemma same_ray_iff_norm_smul_eq : same_ray ℝ x y ↔ ∥x∥ • y = ∥y∥ • x :=
⟨same_ray.norm_smul_eq, λ h, or_iff_not_imp_left.2 $ λ hx, or_iff_not_imp_left.2 $ λ hy,
  ⟨∥y∥, ∥x∥, norm_pos_iff.2 hy, norm_pos_iff.2 hx, h.symm⟩⟩

/-- Two nonzero vectors `x y` in a real normed space are on the same ray if and only if the unit
vectors `∥x∥⁻¹ • x` and `∥y∥⁻¹ • y` are equal. -/
lemma same_ray_iff_inv_norm_smul_eq_of_ne (hx : x ≠ 0) (hy : y ≠ 0) :
  same_ray ℝ x y ↔ ∥x∥⁻¹ • x = ∥y∥⁻¹ • y :=
by rw [inv_smul_eq_iff₀, smul_comm, eq_comm, inv_smul_eq_iff₀, same_ray_iff_norm_smul_eq];
    rwa norm_ne_zero_iff

alias same_ray_iff_inv_norm_smul_eq_of_ne ↔ same_ray.inv_norm_smul_eq _

/-- Two vectors `x y` in a real normed space are on the ray if and only if one of them is zero or
the unit vectors `∥x∥⁻¹ • x` and `∥y∥⁻¹ • y` are equal. -/
lemma same_ray_iff_inv_norm_smul_eq : same_ray ℝ x y ↔ x = 0 ∨ y = 0 ∨ ∥x∥⁻¹ • x = ∥y∥⁻¹ • y :=
begin
  rcases eq_or_ne x 0 with rfl|hx, { simp [same_ray.zero_left] },
  rcases eq_or_ne y 0 with rfl|hy, { simp [same_ray.zero_right] },
  simp only [same_ray_iff_inv_norm_smul_eq_of_ne hx hy, *, false_or]
end

lemma same_ray_iff_of_norm_eq (h : ∥x∥ = ∥y∥) : same_ray ℝ x y ↔ x = y :=
begin
  obtain rfl | hy := eq_or_ne y 0,
  { rw [norm_zero, norm_eq_zero] at h,
    exact iff_of_true (same_ray.zero_right _) h },
  { exact ⟨λ hxy, norm_inj_on_ray_right hy hxy same_ray.rfl h, λ hxy, hxy ▸ same_ray.rfl⟩ }
end

lemma not_same_ray_iff_of_norm_eq (h : ∥x∥ = ∥y∥) : ¬ same_ray ℝ x y ↔ x ≠ y :=
(same_ray_iff_of_norm_eq h).not