/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Shing Tak Lam, Yury Kudryashov

! This file was ported from Lean 3 source module data.mv_polynomial.pderiv
! leanprover-community/mathlib commit 67dcdef25397eedde255db0876b9c55eab2a62a2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.MvPolynomial.Variables
import Mathbin.Data.MvPolynomial.Derivation

/-!
# Partial derivatives of polynomials

This file defines the notion of the formal *partial derivative* of a polynomial,
the derivative with respect to a single variable.
This derivative is not connected to the notion of derivative from analysis.
It is based purely on the polynomial exponents and coefficients.

## Main declarations

* `mv_polynomial.pderiv i p` : the partial derivative of `p` with respect to `i`, as a bundled
  derivation of `mv_polynomial σ R`.

## Notation

As in other polynomial files, we typically use the notation:

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

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

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

+ `a : R`

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

+ `p : mv_polynomial σ R`

-/

noncomputable section

universe u v

namespace MvPolynomial

open Set Function Finsupp AddMonoidAlgebra

open Classical BigOperators

variable {R : Type u} {σ : Type v} {a a' a₁ a₂ : R} {s : σ →₀ ℕ}

section Pderiv

variable {R} [CommSemiring R]

/-- `pderiv i p` is the partial derivative of `p` with respect to `i` -/
def pderiv (i : σ) : Derivation R (MvPolynomial σ R) (MvPolynomial σ R) :=
  mkDerivation R <| Pi.single i 1
#align mv_polynomial.pderiv MvPolynomial.pderiv

@[simp]
theorem pderiv_monomial {i : σ} : pderiv i (monomial s a) = monomial (s - single i 1) (a * s i) :=
  by
  simp only [pderiv, mk_derivation_monomial, Finsupp.smul_sum, smul_eq_mul, ← smul_mul_assoc, ←
    (monomial _).map_smul]
  refine' (Finset.sum_eq_single i (fun j hj hne => _) fun hi => _).trans _
  · simp [Pi.single_eq_of_ne hne]
  · rw [Finsupp.not_mem_support_iff] at hi
    simp [hi]
  · simp
#align mv_polynomial.pderiv_monomial MvPolynomial.pderiv_monomial

theorem pderiv_c {i : σ} : pderiv i (C a) = 0 :=
  derivation_c _ _
#align mv_polynomial.pderiv_C MvPolynomial.pderiv_c

theorem pderiv_one {i : σ} : pderiv i (1 : MvPolynomial σ R) = 0 :=
  pderiv_c
#align mv_polynomial.pderiv_one MvPolynomial.pderiv_one

@[simp]
theorem pderiv_x [d : DecidableEq σ] (i j : σ) :
    pderiv i (X j : MvPolynomial σ R) = @Pi.single σ _ d _ i 1 j :=
  (mkDerivation_x _ _ _).trans (by congr )
#align mv_polynomial.pderiv_X MvPolynomial.pderiv_x

@[simp]
theorem pderiv_x_self (i : σ) : pderiv i (X i : MvPolynomial σ R) = 1 := by simp
#align mv_polynomial.pderiv_X_self MvPolynomial.pderiv_x_self

@[simp]
theorem pderiv_x_of_ne {i j : σ} (h : j ≠ i) : pderiv i (X j : MvPolynomial σ R) = 0 := by simp [h]
#align mv_polynomial.pderiv_X_of_ne MvPolynomial.pderiv_x_of_ne

theorem pderiv_eq_zero_of_not_mem_vars {i : σ} {f : MvPolynomial σ R} (h : i ∉ f.vars) :
    pderiv i f = 0 :=
  derivation_eq_zero_of_forall_mem_vars fun j hj => pderiv_x_of_ne <| ne_of_mem_of_not_mem hj h
#align mv_polynomial.pderiv_eq_zero_of_not_mem_vars MvPolynomial.pderiv_eq_zero_of_not_mem_vars

theorem pderiv_monomial_single {i : σ} {n : ℕ} :
    pderiv i (monomial (single i n) a) = monomial (single i (n - 1)) (a * n) := by simp
#align mv_polynomial.pderiv_monomial_single MvPolynomial.pderiv_monomial_single

theorem pderiv_mul {i : σ} {f g : MvPolynomial σ R} :
    pderiv i (f * g) = pderiv i f * g + f * pderiv i g := by
  simp only [(pderiv i).leibniz f g, smul_eq_mul, mul_comm, add_comm]
#align mv_polynomial.pderiv_mul MvPolynomial.pderiv_mul

@[simp]
theorem pderiv_c_mul {f : MvPolynomial σ R} {i : σ} : pderiv i (C a * f) = C a * pderiv i f :=
  (derivation_c_mul _ _ _).trans C_mul'.symm
#align mv_polynomial.pderiv_C_mul MvPolynomial.pderiv_c_mul

end Pderiv

end MvPolynomial


/-
Copyright (c) 2020 Joseph Myers. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joseph Myers, Yury Kudryashov
-/
import analysis.normed_space.basic
import analysis.normed.group.add_torsor
import linear_algebra.affine_space.midpoint
import topology.instances.real_vector_space

/-!
# Torsors of normed space actions.

This file contains lemmas about normed additive torsors over normed spaces.
-/

noncomputable theory
open_locale nnreal topological_space
open filter

variables {α V P : Type*} [semi_normed_group V] [pseudo_metric_space P] [normed_add_torsor V P]
variables {W Q : Type*} [normed_group W] [metric_space Q] [normed_add_torsor W Q]

include V

section normed_space

variables {𝕜 : Type*} [normed_field 𝕜] [normed_space 𝕜 V]

open affine_map

@[simp] lemma dist_center_homothety (p₁ p₂ : P) (c : 𝕜) :
  dist p₁ (homothety p₁ c p₂) = ∥c∥ * dist p₁ p₂ :=
by simp [homothety_def, norm_smul, ← dist_eq_norm_vsub, dist_comm]

@[simp] lemma dist_homothety_center (p₁ p₂ : P) (c : 𝕜) :
  dist (homothety p₁ c p₂) p₁ = ∥c∥ * dist p₁ p₂ :=
by rw [dist_comm, dist_center_homothety]

@[simp] lemma dist_line_map_line_map (p₁ p₂ : P) (c₁ c₂ : 𝕜) :
  dist (line_map p₁ p₂ c₁) (line_map p₁ p₂ c₂) = dist c₁ c₂ * dist p₁ p₂ :=
begin
  rw dist_comm p₁ p₂,
  simp only [line_map_apply, dist_eq_norm_vsub, vadd_vsub_vadd_cancel_right, ← sub_smul, norm_smul,
    vsub_eq_sub],
end

lemma lipschitz_with_line_map (p₁ p₂ : P) :
  lipschitz_with (nndist p₁ p₂) (line_map p₁ p₂ : 𝕜 → P) :=
lipschitz_with.of_dist_le_mul $ λ c₁ c₂,
  ((dist_line_map_line_map p₁ p₂ c₁ c₂).trans (mul_comm _ _)).le

@[simp] lemma dist_line_map_left (p₁ p₂ : P) (c : 𝕜) :
  dist (line_map p₁ p₂ c) p₁ = ∥c∥ * dist p₁ p₂ :=
by simpa only [line_map_apply_zero, dist_zero_right] using dist_line_map_line_map p₁ p₂ c 0

@[simp] lemma dist_left_line_map (p₁ p₂ : P) (c : 𝕜) :
  dist p₁ (line_map p₁ p₂ c) = ∥c∥ * dist p₁ p₂ :=
(dist_comm _ _).trans (dist_line_map_left _ _ _)

@[simp] lemma dist_line_map_right (p₁ p₂ : P) (c : 𝕜) :
  dist (line_map p₁ p₂ c) p₂ = ∥1 - c∥ * dist p₁ p₂ :=
by simpa only [line_map_apply_one, dist_eq_norm'] using dist_line_map_line_map p₁ p₂ c 1

@[simp] lemma dist_right_line_map (p₁ p₂ : P) (c : 𝕜) :
  dist p₂ (line_map p₁ p₂ c) = ∥1 - c∥ * dist p₁ p₂ :=
(dist_comm _ _).trans (dist_line_map_right _ _ _)

@[simp] lemma dist_homothety_self (p₁ p₂ : P) (c : 𝕜) :
  dist (homothety p₁ c p₂) p₂ = ∥1 - c∥ * dist p₁ p₂ :=
by rw [homothety_eq_line_map, dist_line_map_right]

@[simp] lemma dist_self_homothety (p₁ p₂ : P) (c : 𝕜) :
  dist p₂ (homothety p₁ c p₂) = ∥1 - c∥ * dist p₁ p₂ :=
by rw [dist_comm, dist_homothety_self]

variables [invertible (2:𝕜)]

@[simp] lemma dist_left_midpoint (p₁ p₂ : P) :
  dist p₁ (midpoint 𝕜 p₁ p₂) = ∥(2:𝕜)∥⁻¹ * dist p₁ p₂ :=
by rw [midpoint, dist_comm, dist_line_map_left, inv_of_eq_inv, ← norm_inv]

@[simp] lemma dist_midpoint_left (p₁ p₂ : P) :
  dist (midpoint 𝕜 p₁ p₂) p₁ = ∥(2:𝕜)∥⁻¹ * dist p₁ p₂ :=
by rw [dist_comm, dist_left_midpoint]

@[simp] lemma dist_midpoint_right (p₁ p₂ : P) :
  dist (midpoint 𝕜 p₁ p₂) p₂ = ∥(2:𝕜)∥⁻¹ * dist p₁ p₂ :=
by rw [midpoint_comm, dist_midpoint_left, dist_comm]

@[simp] lemma dist_right_midpoint (p₁ p₂ : P) :
  dist p₂ (midpoint 𝕜 p₁ p₂) = ∥(2:𝕜)∥⁻¹ * dist p₁ p₂ :=
by rw [dist_comm, dist_midpoint_right]

lemma dist_midpoint_midpoint_le' (p₁ p₂ p₃ p₄ : P) :
  dist (midpoint 𝕜 p₁ p₂) (midpoint 𝕜 p₃ p₄) ≤ (dist p₁ p₃ + dist p₂ p₄) / ∥(2 : 𝕜)∥ :=
begin
  rw [dist_eq_norm_vsub V, dist_eq_norm_vsub V, dist_eq_norm_vsub V, midpoint_vsub_midpoint];
    try { apply_instance },
  rw [midpoint_eq_smul_add, norm_smul, inv_of_eq_inv, norm_inv, ← div_eq_inv_mul],
  exact div_le_div_of_le_of_nonneg (norm_add_le _ _) (norm_nonneg _),
end

end normed_space

variables [normed_space ℝ V] [normed_space ℝ W]

lemma dist_midpoint_midpoint_le (p₁ p₂ p₃ p₄ : V) :
  dist (midpoint ℝ p₁ p₂) (midpoint ℝ p₃ p₄) ≤ (dist p₁ p₃ + dist p₂ p₄) / 2 :=
by simpa using dist_midpoint_midpoint_le' p₁ p₂ p₃ p₄

include W

/-- A continuous map between two normed affine spaces is an affine map provided that
it sends midpoints to midpoints. -/
def affine_map.of_map_midpoint (f : P → Q)
  (h : ∀ x y, f (midpoint ℝ x y) = midpoint ℝ (f x) (f y))
  (hfc : continuous f) :
  P →ᵃ[ℝ] Q :=
affine_map.mk' f
  ↑((add_monoid_hom.of_map_midpoint ℝ ℝ
    ((affine_equiv.vadd_const ℝ (f $ classical.arbitrary P)).symm ∘ f ∘
      (affine_equiv.vadd_const ℝ (classical.arbitrary P))) (by simp)
      (λ x y, by simp [h])).to_real_linear_map $ by apply_rules [continuous.vadd, continuous.vsub,
        continuous_const, hfc.comp, continuous_id])
  (classical.arbitrary P)
  (λ p, by simp)


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Jeremy Avigad, Mario Carneiro

Prime numbers.
-/
import data.nat.sqrt data.nat.gcd data.list.basic data.list.perm
open bool subtype

namespace nat
open decidable

/-- `prime p` means that `p` is a prime number, that is, a natural number
  at least 2 whose only divisors are `p` and `1`. -/
def prime (p : ℕ) := p ≥ 2 ∧ ∀ m ∣ p, m = 1 ∨ m = p

theorem prime.ge_two {p : ℕ} : prime p → p ≥ 2 := and.left

theorem prime.gt_one {p : ℕ} : prime p → p > 1 := prime.ge_two

theorem prime_def_lt {p : ℕ} : prime p ↔ p ≥ 2 ∧ ∀ m < p, m ∣ p → m = 1 :=
and_congr_right $ λ p2, forall_congr $ λ m,
⟨λ h l d, (h d).resolve_right (ne_of_lt l),
 λ h d, (lt_or_eq_of_le $
   le_of_dvd (le_of_succ_le p2) d).imp_left (λ l, h l d)⟩

theorem prime_def_lt' {p : ℕ} : prime p ↔ p ≥ 2 ∧ ∀ m, 2 ≤ m → m < p → ¬ m ∣ p :=
prime_def_lt.trans $ and_congr_right $ λ p2, forall_congr $ λ m,
⟨λ h m2 l d, not_lt_of_ge m2 ((h l d).symm ▸ dec_trivial),
λ h l d, begin
  rcases m with _|_|m,
  { rw eq_zero_of_zero_dvd d at p2, revert p2, exact dec_trivial },
  { refl },
  { exact (h dec_trivial l).elim d }
end⟩

theorem prime_def_le_sqrt {p : ℕ} : prime p ↔ p ≥ 2 ∧
  ∀ m, 2 ≤ m → m ≤ sqrt p → ¬ m ∣ p :=
prime_def_lt'.trans $ and_congr_right $ λ p2,
⟨λ a m m2 l, a m m2 $ lt_of_le_of_lt l $ sqrt_lt_self p2,
 λ a, have ∀ {m k}, m ≤ k → 1 < m → p ≠ m * k, from
  λ m k mk m1 e, a m m1
    (le_sqrt.2 (e.symm ▸ mul_le_mul_left m mk)) ⟨k, e⟩,
  λ m m2 l ⟨k, e⟩, begin
    cases (le_total m k) with mk km,
    { exact this mk m2 e },
    { rw [mul_comm] at e,
      refine this km (lt_of_mul_lt_mul_right _ (zero_le m)) e,
      rwa [one_mul, ← e] }
  end⟩

def decidable_prime_1 (p : ℕ) : decidable (prime p) :=
decidable_of_iff' _ prime_def_lt'
local attribute [instance] decidable_prime_1

lemma prime.ne_zero {n : ℕ} (h : prime n) : n ≠ 0 :=
assume hn : n = 0,
have h2 : ¬ prime 0, from dec_trivial,
h2 (hn ▸ h)

theorem prime.pos {p : ℕ} (pp : prime p) : p > 0 :=
lt_of_succ_lt pp.gt_one

theorem not_prime_zero : ¬ prime 0 := dec_trivial

theorem not_prime_one : ¬ prime 1 := dec_trivial

theorem prime_two : prime 2 := dec_trivial

theorem prime_three : prime 3 := dec_trivial

theorem prime.pred_pos {p : ℕ} (pp : prime p) : pred p > 0 :=
lt_pred_of_succ_lt pp.gt_one

theorem succ_pred_prime {p : ℕ} (pp : prime p) : succ (pred p) = p :=
succ_pred_eq_of_pos pp.pos

theorem dvd_prime {p m : ℕ} (pp : prime p) : m ∣ p ↔ m = 1 ∨ m = p :=
⟨λ d, pp.2 m d, λ h, h.elim (λ e, e.symm ▸ one_dvd _) (λ e, e.symm ▸ dvd_refl _)⟩

theorem dvd_prime_ge_two {p m : ℕ} (pp : prime p) (H : m ≥ 2) : m ∣ p ↔ m = p :=
(dvd_prime pp).trans $ or_iff_right_of_imp $ not.elim $ ne_of_gt H

theorem prime.not_dvd_one {p : ℕ} (pp : prime p) : ¬ p ∣ 1
| d := (not_le_of_gt pp.gt_one) $ le_of_dvd dec_trivial d

theorem not_prime_mul {a b : ℕ} (a1 : 1 < a) (b1 : 1 < b) : ¬ prime (a * b) :=
λ h, ne_of_lt (nat.mul_lt_mul_of_pos_left b1 (lt_of_succ_lt a1)) $
by simpa using (dvd_prime_ge_two h a1).1 (dvd_mul_right _ _)

section min_fac
  private lemma min_fac_lemma (n k : ℕ) (h : ¬ k * k > n) :
    sqrt n - k < sqrt n + 2 - k :=
  (nat.sub_lt_sub_right_iff $ le_sqrt.2 $ le_of_not_gt h).2 $
  nat.lt_add_of_pos_right dec_trivial

  def min_fac_aux (n : ℕ) : ℕ → ℕ | k :=
  if h : n < k * k then n else
  if k ∣ n then k else
  have _, from min_fac_lemma n k h,
  min_fac_aux (k + 2)
  using_well_founded {rel_tac :=
    λ _ _, `[exact ⟨_, measure_wf (λ k, sqrt n + 2 - k)⟩]}

  /-- Returns the smallest prime factor of `n ≠ 1`. -/
  def min_fac : ℕ → ℕ
  | 0 := 2
  | 1 := 1
  | (n+2) := if 2 ∣ n then 2 else min_fac_aux (n + 2) 3

  @[simp] theorem min_fac_zero : min_fac 0 = 2 := rfl
  @[simp] theorem min_fac_one : min_fac 1 = 1 := rfl

  theorem min_fac_eq : ∀ n, min_fac n = if 2 ∣ n then 2 else min_fac_aux n 3
  | 0     := rfl
  | 1     := by simp [show 2≠1, from dec_trivial]; rw min_fac_aux; refl
  | (n+2) :=
    have 2 ∣ n + 2 ↔ 2 ∣ n, from
      (nat.dvd_add_iff_left (by refl)).symm,
    by simp [min_fac, this]; congr

  private def min_fac_prop (n k : ℕ) :=
    k ≥ 2 ∧ k ∣ n ∧ ∀ m ≥ 2, m ∣ n → k ≤ m

  theorem min_fac_aux_has_prop {n : ℕ} (n2 : n ≥ 2) (nd2 : ¬ 2 ∣ n) :
    ∀ k i, k = 2*i+3 → (∀ m ≥ 2, m ∣ n → k ≤ m) → min_fac_prop n (min_fac_aux n k)
  | k := λ i e a, begin
    rw min_fac_aux,
    by_cases h : n < k*k; simp [h],
    { have pp : prime n :=
        prime_def_le_sqrt.2 ⟨n2, λ m m2 l d,
          not_lt_of_ge l $ lt_of_lt_of_le (sqrt_lt.2 h) (a m m2 d)⟩,
      from ⟨n2, dvd_refl _, λ m m2 d, le_of_eq
        ((dvd_prime_ge_two pp m2).1 d).symm⟩ },
    have k2 : 2 ≤ k, { subst e, exact dec_trivial },
    by_cases dk : k ∣ n; simp [dk],
    { exact ⟨k2, dk, a⟩ },
    { refine have _, from min_fac_lemma n k h,
        min_fac_aux_has_prop (k+2) (i+1)
          (by simp [e, left_distrib]) (λ m m2 d, _),
      cases nat.eq_or_lt_of_le (a m m2 d) with me ml,
      { subst me, contradiction },
      apply (nat.eq_or_lt_of_le ml).resolve_left, intro me,
      rw [← me, e] at d, change 2 * (i + 2) ∣ n at d,
      have := dvd_of_mul_right_dvd d, contradiction }
  end
  using_well_founded {rel_tac :=
    λ _ _, `[exact ⟨_, measure_wf (λ k, sqrt n + 2 - k)⟩]}

  theorem min_fac_has_prop {n : ℕ} (n1 : n ≠ 1) :
    min_fac_prop n (min_fac n) :=
  begin
    by_cases n0 : n = 0, {simp [n0, min_fac_prop, ge]},
    have n2 : 2 ≤ n, { revert n0 n1, rcases n with _|_|_; exact dec_trivial },
    simp [min_fac_eq],
    by_cases d2 : 2 ∣ n; simp [d2],
    { exact ⟨le_refl _, d2, λ k k2 d, k2⟩ },
    { refine min_fac_aux_has_prop n2 d2 3 0 rfl
        (λ m m2 d, (nat.eq_or_lt_of_le m2).resolve_left (mt _ d2)),
      exact λ e, e.symm ▸ d }
  end

  theorem min_fac_dvd (n : ℕ) : min_fac n ∣ n :=
  by by_cases n1 : n = 1;
     [exact n1.symm ▸ dec_trivial, exact (min_fac_has_prop n1).2.1]

  theorem min_fac_prime {n : ℕ} (n1 : n ≠ 1) : prime (min_fac n) :=
  let ⟨f2, fd, a⟩ := min_fac_has_prop n1 in
  prime_def_lt'.2 ⟨f2, λ m m2 l d, not_le_of_gt l (a m m2 (dvd_trans d fd))⟩

  theorem min_fac_le_of_dvd {n : ℕ} : ∀ {m : ℕ}, m ≥ 2 → m ∣ n → min_fac n ≤ m :=
  by by_cases n1 : n = 1;
    [exact λ m m2 d, n1.symm ▸ le_trans dec_trivial m2,
     exact (min_fac_has_prop n1).2.2]

  theorem min_fac_pos (n : ℕ) : min_fac n > 0 :=
  by by_cases n1 : n = 1;
     [exact n1.symm ▸ dec_trivial, exact (min_fac_prime n1).pos]

  theorem min_fac_le {n : ℕ} (H : n > 0) : min_fac n ≤ n :=
  le_of_dvd H (min_fac_dvd n)

  theorem prime_def_min_fac {p : ℕ} : prime p ↔ p ≥ 2 ∧ min_fac p = p :=
  ⟨λ pp, ⟨pp.ge_two,
    let ⟨f2, fd, a⟩ := min_fac_has_prop $ ne_of_gt pp.gt_one in
    ((dvd_prime pp).1 fd).resolve_left (ne_of_gt f2)⟩,
   λ ⟨p2, e⟩, e ▸ min_fac_prime (ne_of_gt p2)⟩

  instance decidable_prime (p : ℕ) : decidable (prime p) :=
  decidable_of_iff' _ prime_def_min_fac

  theorem not_prime_iff_min_fac_lt {n : ℕ} (n2 : n ≥ 2) : ¬ prime n ↔ min_fac n < n :=
  (not_congr $ prime_def_min_fac.trans $ and_iff_right n2).trans $
    (lt_iff_le_and_ne.trans $ and_iff_right $ min_fac_le $ le_of_succ_le n2).symm

end min_fac

theorem exists_dvd_of_not_prime {n : ℕ} (n2 : n ≥ 2) (np : ¬ prime n) :
  ∃ m, m ∣ n ∧ m ≠ 1 ∧ m ≠ n :=
⟨min_fac n, min_fac_dvd _, ne_of_gt (min_fac_prime (ne_of_gt n2)).gt_one,
  ne_of_lt $ (not_prime_iff_min_fac_lt n2).1 np⟩

theorem exists_dvd_of_not_prime2 {n : ℕ} (n2 : n ≥ 2) (np : ¬ prime n) :
  ∃ m, m ∣ n ∧ m ≥ 2 ∧ m < n :=
⟨min_fac n, min_fac_dvd _, (min_fac_prime (ne_of_gt n2)).ge_two,
  (not_prime_iff_min_fac_lt n2).1 np⟩

theorem exists_prime_and_dvd {n : ℕ} (n2 : n ≥ 2) : ∃ p, prime p ∧ p ∣ n :=
⟨min_fac n, min_fac_prime (ne_of_gt n2), min_fac_dvd _⟩

theorem exists_infinite_primes (n : ℕ) : ∃ p, p ≥ n ∧ prime p :=
let p := min_fac (fact n + 1) in
have f1 : fact n + 1 ≠ 1, from ne_of_gt $ succ_lt_succ $ fact_pos _,
have pp : prime p, from min_fac_prime f1,
have np : n ≤ p, from le_of_not_ge $ λ h,
  have h₁ : p ∣ fact n, from dvd_fact (min_fac_pos _) h,
  have h₂ : p ∣ 1, from (nat.dvd_add_iff_right h₁).2 (min_fac_dvd _),
  pp.not_dvd_one h₂,
⟨p, np, pp⟩

theorem factors_lemma {k} : (k+2) / min_fac (k+2) < k+2 :=
div_lt_self dec_trivial (min_fac_prime dec_trivial).gt_one

/-- `factors n` is the prime factorization of `n`, listed in increasing order. -/
def factors : ℕ → list ℕ
| 0 := []
| 1 := []
| n@(k+2) :=
  let m := min_fac n in have n / m < n := factors_lemma,
  m :: factors (n / m)

lemma mem_factors : ∀ {n p}, p ∈ factors n → prime p
| 0       := λ p, false.elim
| 1       := λ p, false.elim
| n@(k+2) := λ p h,
  let m := min_fac n in have n / m < n := factors_lemma,
  have h₁ : p = m ∨ p ∈ (factors (n / m)) :=
    (list.mem_cons_iff _ _ _).1 h,
  or.cases_on h₁ (λ h₂, h₂.symm ▸ min_fac_prime dec_trivial)
    mem_factors

lemma prod_factors : ∀ {n}, 0 < n → list.prod (factors n) = n
| 0       := (lt_irrefl _).elim
| 1       := λ h, rfl
| n@(k+2) := λ h,
  let m := min_fac n in have n / m < n := factors_lemma,
  show list.prod (m :: factors (n / m)) = n, from
  have h₁ : 0 < n / m :=
    nat.pos_of_ne_zero $ λ h,
    have n = 0 * m := (nat.div_eq_iff_eq_mul_left (min_fac_pos _) (min_fac_dvd _)).1 h,
    by rw zero_mul at this; exact (show k + 2 ≠ 0, from dec_trivial) this,
  by rw [list.prod_cons, prod_factors h₁, nat.mul_div_cancel' (min_fac_dvd _)]

theorem prime.coprime_iff_not_dvd {p n : ℕ} (pp : prime p) : coprime p n ↔ ¬ p ∣ n :=
⟨λ co d, pp.not_dvd_one $ co.dvd_of_dvd_mul_left (by simp [d]),
 λ nd, coprime_of_dvd $ λ m m2 mp, ((dvd_prime_ge_two pp m2).1 mp).symm ▸ nd⟩

theorem prime.dvd_iff_not_coprime {p n : ℕ} (pp : prime p) : p ∣ n ↔ ¬ coprime p n :=
iff_not_comm.2 pp.coprime_iff_not_dvd

theorem prime.dvd_mul {p m n : ℕ} (pp : prime p) : p ∣ m * n ↔ p ∣ m ∨ p ∣ n :=
⟨λ H, or_iff_not_imp_left.2 $ λ h,
  (pp.coprime_iff_not_dvd.2 h).dvd_of_dvd_mul_left H,
 or.rec (λ h, dvd_mul_of_dvd_left h _) (λ h, dvd_mul_of_dvd_right h _)⟩

theorem prime.not_dvd_mul {p m n : ℕ} (pp : prime p)
  (Hm : ¬ p ∣ m) (Hn : ¬ p ∣ n) : ¬ p ∣ m * n :=
mt pp.dvd_mul.1 $ by simp [Hm, Hn]

theorem prime.dvd_of_dvd_pow {p m n : ℕ} (pp : prime p) (h : p ∣ m^n) : p ∣ m :=
by induction n with n IH;
   [exact pp.not_dvd_one.elim h,
    exact (pp.dvd_mul.1 h).elim IH id]

theorem prime.coprime_pow_of_not_dvd {p m a : ℕ} (pp : prime p) (h : ¬ p ∣ a) : coprime a (p^m) :=
(pp.coprime_iff_not_dvd.2 h).symm.pow_right _

theorem coprime_primes {p q : ℕ} (pp : prime p) (pq : prime q) : coprime p q ↔ p ≠ q :=
pp.coprime_iff_not_dvd.trans $ not_congr $ dvd_prime_ge_two pq pp.ge_two

theorem coprime_pow_primes {p q : ℕ} (n m : ℕ) (pp : prime p) (pq : prime q) (h : p ≠ q) :
  coprime (p^n) (q^m) :=
((coprime_primes pp pq).2 h).pow _ _

theorem coprime_or_dvd_of_prime {p} (pp : prime p) (i : ℕ) : coprime p i ∨ p ∣ i :=
by rw [pp.dvd_iff_not_coprime]; apply em

theorem dvd_prime_pow {p : ℕ} (pp : prime p) {m i : ℕ} : i ∣ (p^m) ↔ ∃ k ≤ m, i = p^k :=
begin
  induction m with m IH generalizing i, {simp [pow_succ, le_zero_iff] at *},
  by_cases p ∣ i,
  { cases h with a e, subst e,
    rw [pow_succ, mul_comm (p^m) p, nat.mul_dvd_mul_iff_left pp.pos, IH],
    split; intro h; rcases h with ⟨k, h, e⟩,
    { exact ⟨succ k, succ_le_succ h, by rw [mul_comm, e]; refl⟩ },
    cases k with k,
    { apply pp.not_dvd_one.elim,
      simp at e, rw ← e, apply dvd_mul_right },
    { refine ⟨k, le_of_succ_le_succ h, _⟩,
      rwa [mul_comm, pow_succ, nat.mul_right_inj pp.pos] at e } },
  { split; intro d,
    { rw (pp.coprime_pow_of_not_dvd h).eq_one_of_dvd d,
      exact ⟨0, zero_le _, rfl⟩ },
    { rcases d with ⟨k, l, e⟩,
      rw e, exact pow_dvd_pow _ l } }
end

section
open list

lemma mem_list_primes_of_dvd_prod {p : ℕ} (hp : prime p) :
  ∀ {l : list ℕ}, (∀ p ∈ l, prime p) → p ∣ prod l → p ∈ l
| []       := λ h₁ h₂, absurd h₂ (prime.not_dvd_one hp)
| (q :: l) := λ h₁ h₂,
  have h₃ : p ∣ q * prod l := @prod_cons _ _ l q ▸ h₂,
  have hq : prime q := h₁ q (mem_cons_self _ _),
  or.cases_on ((prime.dvd_mul hp).1 h₃)
    (λ h, by rw [prime.dvd_iff_not_coprime hp, coprime_primes hp hq, ne.def, not_not] at h;
      exact h ▸ mem_cons_self _ _)
    (λ h, have hl : ∀ p ∈ l, prime p := λ p hlp, h₁ p ((mem_cons_iff _ _ _).2 (or.inr hlp)),
    (mem_cons_iff _ _ _).2 (or.inr (mem_list_primes_of_dvd_prod hl h)))

lemma mem_factors_iff_dvd {n p : ℕ} (hn : 0 < n) (hp : prime p) : p ∈ factors n ↔ p ∣ n :=
⟨λ h, prod_factors hn ▸ list.dvd_prod h,
 λ h, mem_list_primes_of_dvd_prod hp (@mem_factors n) ((prod_factors hn).symm ▸ h)⟩

lemma perm_of_prod_eq_prod : ∀ {l₁ l₂ : list ℕ}, prod l₁ = prod l₂ →
  (∀ p ∈ l₁, prime p) → (∀ p ∈ l₂, prime p) → l₁ ~ l₂
| []        []        _  _  _  := perm.nil
| []        (a :: l)  h₁ h₂ h₃ :=
  have ha : a ∣ 1 := @prod_nil ℕ _ ▸ h₁.symm ▸ (@prod_cons _ _ l a).symm ▸ dvd_mul_right _ _,
  absurd ha (prime.not_dvd_one (h₃ a (mem_cons_self _ _)))
| (a :: l)  []        h₁ h₂ h₃ :=
  have ha : a ∣ 1 := @prod_nil ℕ _ ▸ h₁ ▸ (@prod_cons _ _ l a).symm ▸ dvd_mul_right _ _,
  absurd ha (prime.not_dvd_one (h₂ a (mem_cons_self _ _)))
| (a :: l₁) (b :: l₂) h hl₁ hl₂ :=
  have hl₁' : ∀ p ∈ l₁, prime p := λ p hp, hl₁ p (mem_cons_of_mem _ hp),
  have hl₂' : ∀ p ∈ (b :: l₂).erase a, prime p := λ p hp, hl₂ p (mem_of_mem_erase hp),
  have ha : a ∈ (b :: l₂) := mem_list_primes_of_dvd_prod (hl₁ a (mem_cons_self _ _)) hl₂
    (h ▸ by rw prod_cons; exact dvd_mul_right _ _),
  have hb : b :: l₂ ~ a :: (b :: l₂).erase a := perm_erase ha,
  have hl : prod l₁ = prod ((b :: l₂).erase a) :=
  (nat.mul_left_inj (prime.pos (hl₁ a (mem_cons_self _ _)))).1 $
    by rwa [← prod_cons, ← prod_cons, ← prod_eq_of_perm hb],
  perm.trans (perm.skip _ (perm_of_prod_eq_prod hl hl₁' hl₂')) hb.symm

lemma factors_unique {n : ℕ} {l : list ℕ} (h₁ : prod l = n) (h₂ : ∀ p ∈ l, prime p) : l ~ factors n :=
have hn : 0 < n := nat.pos_of_ne_zero $ λ h, begin
  rw h at *, clear h,
  induction l with a l hi,
  { exact absurd h₁ dec_trivial },
  { rw prod_cons at h₁,
    exact nat.mul_ne_zero (ne_of_lt (prime.pos (h₂ a (mem_cons_self _ _)))).symm
      (hi (λ p hp, h₂ p (mem_cons_of_mem _ hp))) h₁ }
end,
perm_of_prod_eq_prod (by rwa prod_factors hn) h₂ (@mem_factors _)

end

lemma succ_dvd_or_succ_dvd_of_succ_sum_dvd_mul {p : ℕ} (p_prime : prime p) {m n k l : ℕ}
      (hpm : p ^ k ∣ m) (hpn : p ^ l ∣ n) (hpmn : p ^ (k+l+1) ∣ m*n) :
      p ^ (k+1) ∣ m ∨ p ^ (l+1) ∣ n :=
have hpd : p^(k+l) * p ∣ m*n, from hpmn,
have hpd2 : p ∣ (m*n) / p ^ (k+l), from dvd_div_of_mul_dvd hpd,
have hpd3 : p ∣ (m*n) / (p^k * p^l), by simpa [nat.pow_add] using hpd2,
have hpd4 : p ∣ (m / p^k) * (n / p^l), by simpa [nat.div_mul_div hpm hpn] using hpd3,
have hpd5 : p ∣ (m / p^k) ∨ p ∣ (n / p^l), from (prime.dvd_mul p_prime).1 hpd4,
show p^k*p ∣ m ∨ p^l*p ∣ n, from
  hpd5.elim
    (assume : p ∣ m / p ^ k, or.inl $ mul_dvd_of_dvd_div hpm this)
    (assume : p ∣ n / p ^ l, or.inr $ mul_dvd_of_dvd_div hpn this)

end nat


/-
Copyright (c) 2021 Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot
-/
import algebra.order.with_zero
import topology.algebra.order.field

/-!
# The topology on linearly ordered commutative groups with zero

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

Let `Γ₀` be a linearly ordered commutative group to which we have adjoined a zero element.
Then `Γ₀` may naturally be endowed with a topology that turns `Γ₀` into a topological monoid.
Neighborhoods of zero are sets containing `{γ | γ < γ₀}` for some invertible element `γ₀`
and every invertible element is open.
In particular the topology is the following:
"a subset `U ⊆ Γ₀` is open if `0 ∉ U` or if there is an invertible
`γ₀ ∈ Γ₀` such that `{γ | γ < γ₀} ⊆ U`", see `linear_ordered_comm_group_with_zero.is_open_iff`.

We prove this topology is ordered and T₃ (in addition to be compatible with the monoid
structure).

All this is useful to extend a valuation to a completion. This is an abstract version of how the
absolute value (resp. `p`-adic absolute value) on `ℚ` is extended to `ℝ` (resp. `ℚₚ`).

## Implementation notes

This topology is not defined as a global instance since it may not be the desired topology on a
linearly ordered commutative group with zero. You can locally activate this topology using
`open_locale with_zero_topology`.
-/

open_locale topology filter
open topological_space filter set function

namespace with_zero_topology

variables {α Γ₀ : Type*} [linear_ordered_comm_group_with_zero Γ₀] {γ γ₁ γ₂ : Γ₀} {l : filter α}
  {f : α → Γ₀}

/-- The topology on a linearly ordered commutative group with a zero element adjoined.
A subset U is open if 0 ∉ U or if there is an invertible element γ₀ such that {γ | γ < γ₀} ⊆ U. -/
protected def topological_space : topological_space Γ₀ :=
topological_space.mk_of_nhds $ update pure 0 $ ⨅ γ ≠ 0, 𝓟 (Iio γ)

localized "attribute [instance] with_zero_topology.topological_space" in with_zero_topology

lemma nhds_eq_update : (𝓝 : Γ₀ → filter Γ₀) = update pure 0 (⨅ γ ≠ 0, 𝓟 (Iio γ)) :=
funext $ nhds_mk_of_nhds_single $ le_infi₂ $ λ γ h₀, le_principal_iff.2 $ zero_lt_iff.2 h₀

/-!
### Neighbourhoods of zero
-/

lemma nhds_zero : 𝓝 (0 : Γ₀) = ⨅ γ ≠ 0, 𝓟 (Iio γ) := by rw [nhds_eq_update, update_same]

/-- In a linearly ordered group with zero element adjoined, `U` is a neighbourhood of `0` if and
only if there exists a nonzero element `γ₀` such that `Iio γ₀ ⊆ U`. -/
lemma has_basis_nhds_zero : (𝓝 (0 : Γ₀)).has_basis (λ γ : Γ₀, γ ≠ 0) Iio :=
begin
  rw [nhds_zero],
  refine has_basis_binfi_principal _ ⟨1, one_ne_zero⟩,
  exact directed_on_iff_directed.2 (directed_of_inf $ λ a b hab, Iio_subset_Iio hab)
end

lemma Iio_mem_nhds_zero (hγ : γ ≠ 0) : Iio γ ∈ 𝓝 (0 : Γ₀) := has_basis_nhds_zero.mem_of_mem hγ

/-- If `γ` is an invertible element of a linearly ordered group with zero element adjoined, then
`Iio (γ : Γ₀)` is a neighbourhood of `0`. -/
lemma nhds_zero_of_units (γ : Γ₀ˣ) : Iio ↑γ ∈ 𝓝 (0 : Γ₀) := Iio_mem_nhds_zero γ.ne_zero

lemma tendsto_zero : tendsto f l (𝓝 (0 : Γ₀)) ↔ ∀ γ₀ ≠ 0, ∀ᶠ x in l, f x < γ₀ := by simp [nhds_zero]

/-!
### Neighbourhoods of non-zero elements
-/

/-- The neighbourhood filter of a nonzero element consists of all sets containing that
element. -/
@[simp] lemma nhds_of_ne_zero {γ : Γ₀} (h₀ : γ ≠ 0) : 𝓝 γ = pure γ :=
by rw [nhds_eq_update, update_noteq h₀]

/-- The neighbourhood filter of an invertible element consists of all sets containing that
element. -/
lemma nhds_coe_units (γ : Γ₀ˣ) : 𝓝 (γ : Γ₀) = pure (γ : Γ₀) := nhds_of_ne_zero γ.ne_zero

/-- If `γ` is an invertible element of a linearly ordered group with zero element adjoined, then
`{γ}` is a neighbourhood of `γ`. -/
lemma singleton_mem_nhds_of_units (γ : Γ₀ˣ) : ({γ} : set Γ₀) ∈ 𝓝 (γ : Γ₀) := by simp

/-- If `γ` is a nonzero element of a linearly ordered group with zero element adjoined, then `{γ}`
is a neighbourhood of `γ`. -/
lemma singleton_mem_nhds_of_ne_zero (h : γ ≠ 0) : ({γ} : set Γ₀) ∈ 𝓝 (γ : Γ₀) := by simp [h]

lemma has_basis_nhds_of_ne_zero {x : Γ₀} (h : x ≠ 0) :
  has_basis (𝓝 x) (λ i : unit, true) (λ i, {x}) :=
by { rw [nhds_of_ne_zero h], exact has_basis_pure _ }

lemma has_basis_nhds_units (γ : Γ₀ˣ) :
  has_basis (𝓝 (γ : Γ₀)) (λ i : unit, true) (λ i, {γ}) :=
has_basis_nhds_of_ne_zero γ.ne_zero

lemma tendsto_of_ne_zero {γ : Γ₀} (h : γ ≠ 0) : tendsto f l (𝓝 γ) ↔ ∀ᶠ x in l, f x = γ :=
by rw [nhds_of_ne_zero h, tendsto_pure]

lemma tendsto_units {γ₀ : Γ₀ˣ} : tendsto f l (𝓝 (γ₀ : Γ₀)) ↔ ∀ᶠ x in l, f x = γ₀ :=
tendsto_of_ne_zero γ₀.ne_zero

lemma Iio_mem_nhds (h : γ₁ < γ₂) : Iio γ₂ ∈ 𝓝 γ₁ :=
by rcases eq_or_ne γ₁ 0 with rfl|h₀; simp [*, h.ne', Iio_mem_nhds_zero]

/-!
### Open/closed sets
-/

lemma is_open_iff {s : set Γ₀} : is_open s ↔ (0 : Γ₀) ∉ s ∨ ∃ γ ≠ 0, Iio γ ⊆ s :=
begin
  rw [is_open_iff_mem_nhds, ← and_forall_ne (0 : Γ₀)],
  simp [nhds_of_ne_zero, imp_iff_not_or, has_basis_nhds_zero.mem_iff] { contextual := tt }
end

lemma is_closed_iff {s : set Γ₀} : is_closed s ↔ (0 : Γ₀) ∈ s ∨ ∃ γ ≠ 0, s ⊆ Ici γ :=
by simp only [← is_open_compl_iff, is_open_iff, mem_compl_iff, not_not, ← compl_Ici,
  compl_subset_compl]

lemma is_open_Iio {a : Γ₀} : is_open (Iio a) :=
is_open_iff.mpr $ imp_iff_not_or.mp $ λ ha, ⟨a, ne_of_gt ha, subset.rfl⟩

/-!
### Instances
-/

/-- The topology on a linearly ordered group with zero element adjoined is compatible with the order
structure: the set `{p : Γ₀ × Γ₀ | p.1 ≤ p.2}` is closed. -/
protected

localized "attribute [instance] with_zero_topology.order_closed_topology" in with_zero_topology

/-- The topology on a linearly ordered group with zero element adjoined is T₃. -/
lemma t3_space : t3_space Γ₀ :=
{ to_regular_space := regular_space.of_lift'_closure $ λ γ,
    begin
      rcases ne_or_eq γ 0 with h₀|rfl,
      { rw [nhds_of_ne_zero h₀, lift'_pure (monotone_closure Γ₀), closure_singleton,
          principal_singleton] },
      { exact has_basis_nhds_zero.lift'_closure_eq_self
        (λ x hx, is_closed_iff.2 $ or.inl $ zero_lt_iff.2 hx) },
    end }

localized "attribute [instance] with_zero_topology.t3_space" in with_zero_topology

/-- The topology on a linearly ordered group with zero element adjoined makes it a topological
monoid. -/
protected lemma has_continuous_mul : has_continuous_mul Γ₀ :=
⟨begin
  rw continuous_iff_continuous_at,
  rintros ⟨x, y⟩,
  wlog hle : x ≤ y generalizing x y,
  { have := tendsto.comp (this y x (le_of_not_le hle)) (continuous_swap.tendsto (x,y)),
    simpa only [mul_comm, function.comp, prod.swap], },
  rcases eq_or_ne x 0 with rfl|hx; [rcases eq_or_ne y 0 with rfl|hy, skip],
  { rw [continuous_at, zero_mul],
    refine ((has_basis_nhds_zero.prod_nhds has_basis_nhds_zero).tendsto_iff has_basis_nhds_zero).2
      (λ γ hγ, ⟨(γ, 1), ⟨hγ, one_ne_zero⟩, _⟩),
    rintro ⟨x, y⟩ ⟨hx : x < γ, hy : y < 1⟩,
    exact (mul_lt_mul₀ hx hy).trans_eq (mul_one γ) },
  { rw [continuous_at, zero_mul, nhds_prod_eq, nhds_of_ne_zero hy, prod_pure, tendsto_map'_iff],
    refine (has_basis_nhds_zero.tendsto_iff has_basis_nhds_zero).2 (λ γ hγ, _),
    refine ⟨γ / y, div_ne_zero hγ hy, λ x hx, _⟩,
    calc x * y < γ / y * y : mul_lt_right₀ _ hx hy
           ... = γ         : div_mul_cancel _ hy },
  { have hy : y ≠ 0, from ((zero_lt_iff.mpr hx).trans_le hle).ne',
    rw [continuous_at, nhds_prod_eq, nhds_of_ne_zero hx, nhds_of_ne_zero hy, prod_pure_pure],
    exact pure_le_nhds (x * y) }
end⟩

localized "attribute [instance] with_zero_topology.has_continuous_mul" in with_zero_topology

protected lemma has_continuous_inv₀ : has_continuous_inv₀ Γ₀ :=
⟨λ γ h, by { rw [continuous_at, nhds_of_ne_zero h], exact pure_le_nhds γ⁻¹ }⟩

localized "attribute [instance] with_zero_topology.has_continuous_inv₀" in with_zero_topology

end with_zero_topology


/-
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
  sorry
end

example : false → ¬ true :=
begin
  sorry
end

example : ¬ false → true :=
begin
  sorry
end

example : true → ¬ false :=
begin
  sorry
end

example : false → ¬ P :=
begin
  sorry
end

example : P → ¬ P → false :=
begin
  sorry
end

example : P → ¬ (¬ P) :=
begin
  sorry
end

example : (P → Q) → (¬ Q → ¬ P) :=
begin
  sorry
end

example : ¬ ¬ false → false :=
begin
  sorry
end

example : ¬ ¬ P → P :=
begin
  sorry
end

example : (¬ Q → ¬ P) → (P → Q) :=
begin
  sorry,
end


import irrefl
import diagonal
import data.stream

universes u v

namespace cantor

open diagonal

-- Cantor's diagonal argument generalized to types with irreflexive maps
theorem type_cantor {A : Sort u} {B : Sort v} [has_anot B]:
not exists M : A -> (A -> B), forall f : A -> B, exists a : A, f = M a
:= begin
  by_contradiction h,
  exact exists.elim h begin
    intro M,
    assume hm,
    have hfd := hm (dnot M),
    exact exists.elim hfd begin
      intro a,
      assume hd : dnot M = M a,
      have hnd : not (dnot M = M a) := dnot_ne_part M a,
      exact hnd hd
    end
  end
end

/- Specializations of the generalized diagonal argument -/

-- Diagonal Argument for Closed Binary Functions
theorem closed_cantor {A : Sort u} [has_anot A]:
not exists M : A -> (A -> A), forall f : A -> A, exists a, f = M a
:= type_cantor

-- Diagonal Argument for Relations
theorem relational_cantor {A : Sort u}:
not exists R : A -> A -> Prop, forall S : A -> Prop, exists a, S = R a
:= type_cantor

-- Diagonal Argument for Sets
theorem set_cantor {A : Type u}:
not exists R : A -> set A, forall S : set A, exists a, S = R a
:= relational_cantor

-- Diagonal Argument for Streams
theorem stream_cantor {a : Type u} [has_anot a]:
not exists M : stream (stream a), forall S : stream a, exists n, S = M n
:= type_cantor

-- Diagonal Argument for Boolean (Bit) Streams
theorem bool_stream_cantor:
not exists M : stream (stream bool), forall S : stream bool, exists n, S = M n
:= stream_cantor

end cantor


/-
Copyright (c) 2022 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
import combinatorics.simple_graph.connectivity -- paths and walks etc

/-

# Trees

A graph is a tree if it's nonempty and for every pair of vertices
there's a unique path between them.

In this file I show that trees have no cycles.

-/

universe u₀

namespace simple_graph

/-- A graph is a tree if it's nonempty and for every pair of vertices
  there's a unique path between them. -/
def is_tree {V : Type u₀} (G : simple_graph V) : Prop :=
nonempty V ∧ ∀ u v : V, ∃! p : G.walk u v, p.is_path

/-- A graph is connected if it's nonempty and for every pair of vertices
  there's a path between them. -/
def is_connected {V : Type u₀} (G : simple_graph V) : Prop :=
nonempty V ∧ ∀ u v : V, ∃ p : G.walk u v, p.is_path

namespace is_tree

variables {V : Type u₀} {G : simple_graph V}

/-- Trees are connected. -/
lemma is_connected {V : Type u₀} {G : simple_graph V}
  (h : G.is_tree) : G.is_connected :=
begin
  exact ⟨h.1, λ u v, exists_unique.exists (h.2 u v)⟩,
end

open simple_graph.walk

/-- A tree has no cycles. -/
lemma no_cycles [decidable_eq V] (hG : G.is_tree) (u : V) (p : G.walk u u) :
¬ p.is_cycle :=
begin
  intro hp,
  cases p with _ _ v _ huv q,
  { exact hp.ne_nil rfl, },
  { set w1 := cons huv nil with hw1,
    set w2 := q.reverse with hw2,
    have hw1path : w1.is_path,
    { simp,
      intro h,
      rw h at huv,
      exact G.loopless v huv },
    have hw2path : w2.is_path,
    { apply is_path.reverse,
      rw is_path_def,
      simpa using hp.support_nodup },
    have hw := exists_unique.unique (hG.2 u v) hw1path hw2path,
    rw [hw1, hw2] at hw,
    apply_fun reverse at hw,
    rw reverse_reverse at hw,
    rw ← hw at hp,
    simp at hp,
    replace hp := is_circuit.to_trail (is_cycle.to_circuit hp),
    rw is_trail_def at hp,
    simp at hp,
    apply hp,
    apply sym2.rel.swap },
end

end is_tree

end simple_graph


import data.stream tactic
open stream tactic

namespace CTL

variables AP : Type

mutual inductive state_formula, path_formula
with state_formula : Type
| T             : state_formula
| atom (a : AP) : state_formula
| conj (Φ₁ Φ₂ : state_formula ) : state_formula
| neg (Φ : state_formula) : state_formula
| E (φ : path_formula) : state_formula
| A (φ : path_formula) : state_formula
with path_formula : Type
| next (Φ : state_formula) : path_formula
| until (Φ₁ Φ₂ : state_formula) : path_formula
open state_formula
open path_formula

local notation  `∼` Φ := neg Φ
local notation Φ `&` Ψ := conj Φ Ψ
local notation `●` Φ := next Φ
local notation Φ `𝒰` Ψ := until Φ Ψ

def disj (φ ψ : state_formula AP) : state_formula AP :=
∼(∼ φ & ∼ψ)

local notation φ `⅋` ψ := disj _ φ ψ

structure TS :=
(S : Type)
(H1 : inhabited S)
(H2 : decidable_eq S)
(Act : Type)
(TR : set (S × Act × S))
(L  : S → set AP)

def Post_of  {M : TS AP} (s : M.S) (α : M.Act) : set (M.S) := {s' | (s,α,s') ∈ M.TR}

def Post {M : TS AP} (s : M.S) : set (M.S) :=
⋃ α : M.Act, Post_of _ s α

def path {AP : Type} (M : TS AP) : Type :=
{s : stream M.S // ∀ i : ℕ, s (i + 1) ∈ Post _ (s i)}

def paths {AP : Type}
{M : TS AP} (s : M.S) : set (path M) :=
(λ π, s =  π.val.head)

mutual def state_sat, path_sat {M : TS AP}
with state_sat : state_formula AP → M.S → Prop
| T := λ _, true
| (atom a) := λ s, a ∈ M.L s
| (Φ & Ψ) := λ s, state_sat Φ s ∧ state_sat Ψ s
| (∼ Φ) := λ s, ¬ (state_sat Φ s)
| (E φ) := λ s, ∃ π : path M, π ∈ paths s ∧ path_sat φ π
| (A φ) := λ s, ∀ π : path M, π ∈ paths s → path_sat φ π
with path_sat : path_formula AP → path M → Prop
| (● Φ) := λ π, state_sat Φ (π.val 1)
| (Φ 𝒰 Ψ) := λ π,
 ∃ j, state_sat Ψ (π.val j) ∧ (∀ k < j, state_sat Φ (π.val k))

notation s `⊨ₛ` Φ := state_sat _ Φ s
notation π `⊨ₚ ` Φ := path_sat _ Φ π

lemma disj_sat {AP : Type} {M : TS AP}
(φ ψ : state_formula AP)(s : M.S) :
 (s ⊨ₛ (φ ⅋ ψ)) ↔ (s ⊨ₛ φ) ∨ (s ⊨ₛ ψ) :=
by { rw disj, repeat {rw state_sat}, finish}

def potentially (φ : state_formula AP) : state_formula AP :=
E(T 𝒰 φ)
notation `E◆` φ := potentially _ φ

def inevitably (φ : state_formula AP) : state_formula AP :=
A(T 𝒰 φ)
notation `A◆` φ := inevitably _ φ

def potentially_always (φ : state_formula AP) : state_formula AP :=
∼ (A◆(∼φ))
notation `E◾` φ := potentially_always _ φ

def invariantly (φ : state_formula AP) : state_formula AP :=
∼ (E◆(∼φ))
notation `A◾` φ := invariantly _ φ

namespace sat

lemma potentially {AP : Type} {M : TS AP}
(φ : state_formula AP) (s : M.S):
(s ⊨ₛ E◆φ) ↔ ∃ π ∈ paths s, ∃ j:ℕ, (subtype.val π j) ⊨ₛ φ :=
begin
    rw [potentially,state_sat,path_sat], dsimp only,
    split,
    { rintro ⟨π,H,j,Hj1,Hj2⟩,
      use π, exact ⟨H, ⟨j,Hj1⟩⟩},
    { rintro ⟨π,H1,j,H2⟩,
      use π,
      split, {exact H1},
     {use j, split, exact H2, intros, trivial}}
end

lemma inevitably {AP : Type} {M : TS AP}
(φ : state_formula AP) (s : M.S) :
(s ⊨ₛ A◆φ) ↔ ∀ π ∈ paths s, ∃ j : ℕ, (subtype.val π j) ⊨ₛ φ :=
begin
    rw [inevitably,state_sat,path_sat], dsimp only,
    split,
    { intros H1 π H2, replace H1 := H1 π H2,
      cases H1 with j H1,
      use j, exact H1.1},
    { intros H1 π H2, replace H1 := H1 π H2,
      cases H1 with j H1,
      use j, split, {exact H1}, intros, trivial}
end

lemma potentially_always {AP : Type} {M : TS AP}
(φ : state_formula AP) (s : M.S) :
(s ⊨ₛ E◾φ) ↔ (∃ π ∈ paths s, ∀ j : ℕ, (subtype.val π j) ⊨ₛ φ) :=
begin
    rw potentially_always,
    rw state_sat, dsimp only,
    rw [inevitably,state_sat],
    simp,
end

def invariantly {AP : Type} {M : TS AP}
(φ : state_formula AP) (s : M.S) :
(s ⊨ₛ A◾φ) ↔ (∀ π ∈ paths s, ∀ j : ℕ, (subtype.val π j) ⊨ₛ φ) :=
begin
    rw invariantly,
    rw state_sat, dsimp only,
    rw [potentially,state_sat],
    simp,
end

end sat

def sat_set {AP : Type} (M : TS AP) (φ : state_formula AP): set M.S :=
{s | s ⊨ₛ φ}

def equiv {AP : Type} (φ ψ: state_formula AP) : Prop :=
∀ M : TS AP, (sat_set M φ) = (sat_set M ψ)

notation φ ` ≡ ` ψ := equiv φ ψ

lemma forall_until_expansion (φ ψ : state_formula AP) :
    A(φ 𝒰 ψ) ≡ (ψ ⅋ (φ & A●A(φ 𝒰 ψ))) :=
begin
    intro M, ext, repeat {rw sat_set}, simp,
    rw [state_sat, path_sat], simp,
    rw disj_sat, repeat {rw state_sat},
    rw path_sat, rw state_sat, rw path_sat,
    simp, sorry
end

lemma forall_next_dual (φ : state_formula AP) :
    (A●φ) ≡ (∼E(●∼φ)) :=
begin
    intros M, ext, repeat {rw sat_set}, simp,
    rw [state_sat,path_sat],
    repeat {rw state_sat}, rw [path_sat, state_sat],
    simp
end

lemma exists_next_dual (φ : state_formula AP) :
    (E●φ) ≡ (∼A(●∼φ)) :=
begin
    intros M, ext, repeat {rw sat_set}, simp,
    rw [state_sat,path_sat],
    repeat {rw state_sat}, rw [path_sat, state_sat],
    simp
end

lemma potentially_dual (φ : state_formula AP) :
    (E◆φ) ≡ ∼(A◾∼φ) :=
begin
    intros M, ext, repeat {rw sat_set}, simp,
    rw [sat.potentially,state_sat],simp,
    rw [sat.invariantly,state_sat],simp,
end

lemma inevitably_dual (φ : state_formula AP) :
    (A◆φ) ≡ ∼(E◾∼φ) :=
begin
    intros M, ext, repeat {rw sat_set}, simp,
    rw [sat.inevitably,state_sat],simp,
    rw [sat.potentially_always,state_sat],simp,
end

lemma until_dual_fst (φ ψ : state_formula AP) :
    A(φ 𝒰 ψ) ≡ ((∼E(∼ ψ 𝒰 (∼ φ & ∼ ψ))) & ∼ E◾ ∼ψ) :=
    begin
        intros M, ext, repeat {rw sat_set}, simp,
        repeat {rw [state_sat]},
        repeat {rw [path_sat]}, simp,
        split, {
            intro H1, split,{
                intros π H2 i H3,
                repeat {rw state_sat at H3},
                rw state_sat, simp at *,
                replace H1 := H1 π H2,sorry

            },{
                sorry
            }
        },{sorry}
    end

end CTL


/- Tactic : exact

## Summary

If the goal is `⊢ X` then `exact x` will close the goal if
and only if `x` is a term of type `X`.

## Details

Say `P`, `Q` and `R` are types (i.e., what a mathematician
might think of as either sets or propositions),
and the local context looks like this:

```
p : P,
h : P → Q,
j : Q → R
⊢ R
```

If you can spot how to make a term of type `R`, then you
can just make it and say you're done using the `exact` tactic
together with the formula you have spotted. For example the
above goal could be solved with

`exact j(h(p)),`

because `j(h(p))` is easily checked to be a term of type `R`
(i.e., an element of the set `R`, or a proof of the proposition `R`).

-/

/-
In this level we learn the tactic `exact`, which solves a goal that is exactly one of the hypotheses.
The lemma is the same as in the previous level, but we will solve it in a different way.
-/

/- Hint : Click here for a hint, in case you get stuck.
By doing a `rw` you will get the goal to look exactly like one of the hypotheses...
-/

variables {Ω : Type} -- hide

/- Lemma : no-side-bar
If A, B and C are points with A = B and B = C, then A = C.
-/
lemma example_exact (A B C: Ω) (h1 : A = B) (h2 : B = C) : A = C :=
begin
  rw h1,
  exact h2,

end


import plane_separation_world.level06 --hide
open IncidencePlane --hide

/-
# Plane Separation World

## Level 7: on the way to the final level (IV).

This is the fourth of five lemmas that we need to prove before jumping into the final level of the game! You will be provided with
its mathematical proof in paper right below. Remember that you can use all the theorem statements from the left-hand side box.

## Mathematical proof in paper...

**Claim:** If two points A and C are not on the same side of the line ℓ, there exists a point in the segment A·C which is incident with the line ℓ.

**Proof:**

Let us assume that there exists a point P, such that `P ∈ pts (A⬝C) ∧ P ∈ ℓ`. Then, either `P = A` or `A * P * C` or `P = C`, and `P ∈ ℓ`. Now, we proceed with
the proof of by contradiction. If there does not exist such point P, we have to prove that the intersection of the segment `A·C` with the line ℓ is empty, and `P ∉ ℓ`.
That is, there exists a point X such that `x ∈ pts (A⬝C) ∩ ↑ℓ ↔ x ∈ ∅`. Then we have to prove (a) `x ∈ pts (A⬝C) ∩ ↑ℓ → x ∈ ∅` and (b) `x ∈ ∅ → x ∈ pts (A⬝C) ∩ ↑ℓ`.

**Proof (a):** Let us assume that `x ∈ pts (A⬝C) ∩ ↑ℓ`. Then, we have to prove that `x ∈ ∅`. That is,
`x ∈ U → false`, where `U` is the `universal set`. Because of this reason, we assume that `x ∈ U` is true and then it suffices to prove `false`. At this point,
let the point P be `x`, such that `x = A` or `A * x * C` or `x = C`, and `x ∉ ℓ`. If we assume that `A * x * C`, the `finish` tactic will close the goal.
Because the fact that `A * x * C` implies that `x ∈ ℓ`, then we reach a contradiction with `x ∈ ∅`. Therefore, the first case is proved.

**Proof (b):** Note that the point `x` being an element of the empty set cannot imply that `x` is an element of
the intersection between the segment `A·C` and the line ℓ, since this is not an empty set. Then, propositional logic proves this case. (You can use the `tauto` tactic
to solve it in Lean.)

Therefore, we have proved that there exists a point P such that P is an element of the segment `A·C` and that `P ∈ ℓ`.

-/

/- Hint : Click here for a hint, in case you get stuck.
To solve this level, we have used high levels tactics which weren't taught in the Tutorial World.. In case you feel bewildered, do not hesitate
to click on "View source" (located on the top right corner of the game screen) to see the solution.
-/

variables {Ω : Type} [IncidencePlane Ω] --hide
variables {A B C P Q R : Ω} --hide
variables {ℓ r s t : Line Ω} --hide

/- Lemma :
If two points A and C are not on the same side of the line ℓ, there exists a point in the segment A·C which is incident with the line ℓ.
-/
lemma not_same_side_intersection (h : ¬ same_side ℓ A C) : ∃ P , P ∈ pts (A⬝C) ∧ P ∈ ℓ :=
begin
  simp,
  by_contra hlAC,
  push_neg at hlAC,
  apply h,
  unfold same_side,
  ext,
  split,
    {
      intro hx,
      simp,
      specialize hlAC x,
      have hAxC : A*x*C,
      {
        finish,
      },
      apply hlAC,
      {
        tauto,
      },
      {
        finish,
      },
    },
    {
      tauto,
    },

end


import algebra.punit_instances
import algebra.category.Group
import group_theory.subgroup
import group_theory.quotient_group
import .subgroup .quotient_group

open subgroup monoid_hom

structure normal_embedding (G H : Type*) [group G] [group H]
  extends φ : G →* H :=
(inj : function.injective φ)
(norm : φ.range.normal)

structure add_normal_embedding (G H : Type*) [add_group G] [add_group H]
  extends φ : G →+ H :=
(inj : function.injective φ)
(norm : φ.range.normal)

attribute [to_additive add_normal_embedding] normal_embedding

namespace normal_embedding

variables {G H K : Type*} [group G] [group H] [group K]

@[to_additive]
instance normal (f : normal_embedding G H) : f.φ.range.normal := f.norm

/- Coerce a normal embedding to a group homomorphism -/
@[to_additive]
instance : has_coe (normal_embedding G H) (G →* H) := ⟨normal_embedding.φ⟩

/- The unique normal embedding from the trivial group to any group -/
@[to_additive]
def from_subsingleton (hG : subsingleton G) (H : Type*) [group H] : normal_embedding G H :=
⟨1, λ x y _, subsingleton.elim x y, (@monoid_hom.range_one G H _ _).symm ▸ subgroup.bot_normal⟩

@[simp, to_additive]
lemma from_subsingleton_range {hG : subsingleton G} : (from_subsingleton hG H).φ.range = ⊥ :=
le_antisymm (by { rintros x ⟨y, rfl⟩, rw [subsingleton.elim y 1, map_one, mem_bot] }) bot_le

/- A group isomorphism induces a normal embedding -/
@[to_additive]
def of_mul_equiv (h : G ≃* H) : normal_embedding G H :=
⟨h.to_monoid_hom, h.left_inv.injective,
  suffices heq : h.to_monoid_hom.range = ⊤, from heq.substr subgroup.top_normal,
  set_like.ext' (h.to_monoid_hom.coe_range.trans $ h.surjective.range_eq)⟩

/- A normal embedding from `G` to `H` can be composed with a group isomorphism
`H ≃* K` to produce a normal embedding from `G` to `K` -/
@[to_additive]
def comp_mul_equiv (f : normal_embedding G H) (h : H ≃* K) : normal_embedding G K :=
{ φ := h.to_monoid_hom.comp f,
  inj := function.injective.comp h.left_inv.injective f.inj,
  norm := by rw range_comp; exact normal.mul_equiv_map f.norm h }

open quotient_group

@[to_additive]
instance group_quotient (f : normal_embedding G H) : group (quotient f.φ.range) :=
by haveI := f.norm; apply_instance

@[to_additive]
def of_normal_subgroup (N : subgroup G) [N.normal] : normal_embedding N G :=
⟨N.subtype, λ x y hx, by simpa using hx, (range_subtype N).symm ▸ infer_instance⟩

@[simp, to_additive]
lemma range_of_normal_subgroup (N : subgroup G) [N.normal] :
  (of_normal_subgroup N).φ.range = N :=
by simp only [of_normal_subgroup, range_subtype]

@[to_additive]
def of_normal_subgroup_to_subgroup {K N : subgroup G} [N.normal] (h : N ≤ K) :
  normal_embedding N K :=
⟨inclusion h, inclusion_injective, by { rw range_inclusion, apply_instance }⟩

@[to_additive]
noncomputable def equiv_range (f : normal_embedding G H) : G ≃* f.φ.range :=
mul_equiv.of_injective f.inj

@[to_additive]
noncomputable def equiv_quotient_comp_mul_equiv (f : normal_embedding G H) (e : H ≃* K) :
  quotient (comp_mul_equiv f e).φ.range ≃* quotient f.φ.range :=
let ψ : K →* quotient f.φ.range := (mk' f.φ.range).comp e.symm.to_monoid_hom in
have hψ : function.surjective ψ := function.surjective.comp (surjective_quot_mk _) e.symm.surjective,
suffices h : ψ.ker = (comp_mul_equiv f e).φ.range,
  from (equiv_quotient_of_eq h.symm).trans (quotient_ker_equiv_of_surjective ψ hψ),
begin
  simp [ψ, comp_mul_equiv, ←comap_ker],
  have : comap e.symm.to_monoid_hom f.φ.range = map e.to_monoid_hom f.φ.range,
  { symmetry, apply map_eq_comap_of_inverse, exact e.left_inv, exact e.right_inv },
  rw this, simp [range_eq_map, ←map_map], refl,
end

@[to_additive]
noncomputable lemma fintype [fintype G] (f : normal_embedding H G)
  [decidable_pred (λ x, x ∈ f.φ.range)] : fintype H :=
fintype.of_equiv f.φ.range f.equiv_range.to_equiv.symm

variables (f : normal_embedding H G) (g : normal_embedding K G)

@[to_additive]
noncomputable def from_inf_range_left :
  normal_embedding ↥(f.φ.range ⊓ g.φ.range) H :=
comp_mul_equiv (of_normal_subgroup_to_subgroup inf_le_left) (equiv_range f).symm

@[to_additive]
noncomputable def from_inf_range_right :
  normal_embedding ↥(f.φ.range ⊓ g.φ.range) K :=
comp_mul_equiv (of_normal_subgroup_to_subgroup inf_le_right) (equiv_range g).symm

@[to_additive]
noncomputable def quotient_from_inf_range_left (h : f.φ.range ⊔ g.φ.range = ⊤) :
  quotient (from_inf_range_left f g).φ.range ≃* quotient g.φ.range :=
have h1 : quotient (from_inf_range_left f g).φ.range ≃*
  quotient (comap f.φ.range.subtype (f.φ.range ⊓ g.φ.range)),
by { apply (equiv_quotient_of_eq _).trans (equiv_quotient_of_equiv (equiv_range f).symm).symm,
  simp [from_inf_range_left, comp_mul_equiv, range_comp], congr,
  rw [comap_subtype, ←@range_inclusion _ _ _ f.φ.range inf_le_left], refl },
suffices h2 : quotient g.φ.range ≃* quotient (comap (f.φ.range ⊔ g.φ.range).subtype g.φ.range),
from h1.trans $ (quotient_inf_equiv_prod_normal_quotient _ _).trans h2.symm,
by { rw h, apply (equiv_quotient_of_equiv $ equiv_top G).trans (equiv_quotient_of_eq _),
  rw comap_subtype_top }

@[to_additive]
noncomputable def quotient_from_inf_range_right (h : f.φ.range ⊔ g.φ.range = ⊤) :
  quotient (from_inf_range_right f g).φ.range ≃* quotient f.φ.range :=
have h1 : quotient (from_inf_range_right f g).φ.range ≃*
  quotient (comap g.φ.range.subtype (f.φ.range ⊓ g.φ.range)),
by { apply (equiv_quotient_of_eq _).trans (equiv_quotient_of_equiv (equiv_range g).symm).symm,
  simp [from_inf_range_right, comp_mul_equiv, range_comp], congr,
  rw comap_subtype, conv_rhs { rw inf_comm },
  rw ←@range_inclusion _ _ _ g.φ.range inf_le_right, refl },
suffices h2 : quotient f.φ.range ≃* quotient (comap (f.φ.range ⊔ g.φ.range).subtype f.φ.range),
by { apply h1.trans (mul_equiv.trans _ h2.symm), rw sup_comm,
  apply (equiv_quotient_of_eq _).trans (quotient_inf_equiv_prod_normal_quotient _ _),
  rw inf_comm },
by { rw h, apply (equiv_quotient_of_equiv $ equiv_top G).trans (equiv_quotient_of_eq _),
  rw comap_subtype_top }

end normal_embedding


import tactic.linarith
import data.real.basic
import data.complex.exponential
import data.polynomial
import data.nat.choose

/-

M1F May exam 2018, question 1.

-/

universe u

local attribute [instance, priority 0] classical.prop_decidable

open nat

-- Q1(a)(i)
theorem count (n : ℕ) (hn : n ≥ 1) : finset.sum (finset.range (nat.succ n)) (λ m, choose n m) =
  2 ^ n --answer
  :=
begin
  have H := (add_pow (1 : ℕ) 1 n).symm,
  simpa [nat.one_pow, one_mul, one_add_one_eq_two,
    (finset.sum_nat_cast _ _).symm, nat.cast_id] using H,
end

-- Q1(a)(ii)
theorem countdown (n : ℕ) (hn : n ≥ 1) :
  finset.sum (finset.range (nat.succ n)) (λ m, (-1 : ℤ) ^ m * choose n m) =
  0 -- answer
  :=
begin
  have H := (add_pow (-1 : ℤ) 1 n).symm,
  have H2 := @_root_.zero_pow ℤ _ _ hn,
  simpa [nat.one_pow, one_mul, one_add_one_eq_two, nat.zero_pow hn,
    (finset.sum_nat_cast _ _).symm, nat.cast_id, H2] using H,
end

-- Q1(b) preparation
open real polynomial
noncomputable def chebyshev : ℕ → polynomial ℝ
| 0 := C 1
| 1 := X
| (n + 2) := 2 * X * chebyshev (n + 1) - chebyshev n

def chebyshev' : ℕ → polynomial ℤ
| 0 := C 1
| 1 := X
| (n + 2) := 2 * X * chebyshev' (n + 1) - chebyshev' n

lemma polycos_zero (θ : ℝ) : cos (0 * θ) = polynomial.eval (cos θ) (chebyshev 0) :=
by rw [chebyshev, eval_C, zero_mul, cos_zero]

lemma polycos (n : ℕ) (hn : n ≥ 1) : ∀ θ : ℝ, cos (n * θ) = polynomial.eval (cos θ) (chebyshev n) :=
begin
  intro θ,
  apply nat.strong_induction_on n,
  intros k ih,
  have ih1 : cos (↑(k - 1) * θ) = polynomial.eval (cos θ) (chebyshev (k - 1)),
  { by_cases h : k = 0,
    { rw [h, (show (0 - 1 = 0), by refl)], convert polycos_zero θ},
    -- h : k ≠ 0,
    { exact ih (k - 1) (nat.sub_lt (nat.pos_of_ne_zero h) (by norm_num : 0 < 1)) }
  },
  have ih2 : cos (↑(k - 2) * θ) = polynomial.eval (cos θ) (chebyshev (k - 2)),
  { by_cases h : k = 0,
    { rw [h, (show (0 - 2 = 0), from rfl)], convert polycos_zero θ},--, chebyshev, eval_one],
    -- h : k ≠ 0,
    { exact ih (k - 2) (nat.sub_lt (nat.pos_of_ne_zero h) (by norm_num : 0 < 2)) }
  },
  by_cases h1 : k = 0,
    rw h1, exact polycos_zero θ,
  by_cases h2 : k = 1,
  { rw [h2, chebyshev, eval_X],
    congr',
    convert one_mul θ,
    simp},
  have hk : k = (k - 2) + 2, rw nat.sub_add_cancel, swap,
   rw [hk, chebyshev, ←hk, nat.succ_eq_add_one, (_ : k - 2 + 1 = k - 1), two_mul,
        polynomial.eval_sub, polynomial.eval_mul, polynomial.eval_add,
         polynomial.eval_X, ←two_mul, ←ih1, ←ih2],
        rw [←complex.of_real_inj, complex.of_real_sub, complex.of_real_mul, complex.of_real_mul,
            complex.of_real_cos, complex.of_real_cos, complex.of_real_cos, complex.of_real_cos,
            complex.cos, complex.cos, complex.cos, complex.cos],
        simp,
        rw [mul_div_cancel', ←mul_div_assoc, ←neg_div, ←add_div, add_mul, mul_add, mul_add,
            ←complex.exp_add, ←complex.exp_add, ←complex.exp_add, ←complex.exp_add,
            mul_assoc, mul_assoc, mul_assoc],
        rw [←one_mul (↑θ * complex.I)] {occs := occurrences.pos [5, 7]},
        rw [←add_mul, ←sub_eq_add_neg, ←sub_mul, ←neg_one_mul (↑θ * complex.I),
            ←add_mul, ←sub_eq_add_neg, ←sub_mul, @nat.cast_sub _ _ _ 1 k, add_sub, nat.cast_one,
            add_sub_cancel', ←sub_add, sub_add_eq_add_sub, one_add_one_eq_two, add_sub,
            ←sub_add_eq_add_sub, ←neg_add', one_add_one_eq_two, ←sub_add, sub_add_eq_add_sub,
            neg_add_self, zero_sub, nat.cast_sub, neg_mul_eq_neg_mul, neg_mul_eq_neg_mul,
            neg_sub, nat.cast_two, neg_add, sub_eq_neg_add, ←add_assoc, ←neg_add,
            ←sub_eq_neg_add, add_sub_add_right_eq_sub, ←add_assoc, sub_add_cancel],
        all_goals {
            try {
            have H : k ≥ 2,
                apply le_of_not_gt, intro,
                have h12 : k = 0 ∨ k = 1,
                    clear ih ih1 ih2 h1 h2, try { clear hk },
                    revert k a, exact dec_trivial,
                apply or.elim h12 (λ h12, h1 h12) (λ h12, h2 h12) },
                try { exact H }, try { exact le_trans (by norm_num : 1 ≤ 2) H } },
        apply two_ne_zero',
        apply @eq_of_add_eq_add_right _ _ _ 1 _,
        rw [add_assoc, one_add_one_eq_two, nat.sub_add_cancel H,
            nat.sub_add_cancel (le_trans (by norm_num : 1 ≤ 2) H)],
    end

-- Q1(b)(i)
theorem exist_polycos (n : ℕ) (hn : n ≥ 1) :
  ∃ Pn : polynomial ℝ, ∀ θ : ℝ, cos (n * θ) = polynomial.eval (cos θ) Pn := ---ans
Exists.intro (chebyshev n) (polycos n hn)

open polynomial

-- Q1(b)(ii)
example : chebyshev' 4 = 8 * X ^ 4 - 8 * X ^ 2 + 1 := dec_trivial

-- Q1(b)(iii) preparation
lemma useful (k : ℕ) : polynomial.degree (chebyshev' k) = k :=
begin
  apply nat.strong_induction_on k,
  intros n ih,
  have ih1 : polynomial.degree (chebyshev' (n - 1)) = ↑(n - 1),
  { by_cases h : n = 0,
    { rw h, apply degree_C, norm_num },--simp [h, chebyshev'], },
    { exact ih _ (nat.sub_lt (nat.pos_of_ne_zero h) (zero_lt_one)) },
  },
  have ih2 : polynomial.degree (chebyshev' (n - 2)) = ↑(n - 2),
  { by_cases h : n ≤ 1,
    { apply or.elim ((dec_trivial : ∀ j : ℕ, j ≤ 1 → j = 0 ∨ j = 1) n h),
      { intro h0, rw h0, apply degree_C, norm_num },
      { intro h1, rw h1, apply degree_C, norm_num },
    },
    { apply ih _, apply nat.sub_lt (lt_of_not_ge (λ w, h (le_trans w zero_le_one))),
      norm_num },
  },
  by_cases h : n ≥ 2,
  { have H : n - 2 + 2 = n := nat.sub_add_cancel h,
    have H' : nat.succ (n - 2) = n - 1,
    { have W : n ≥ 2,
      { apply le_of_not_gt, intro,
        have h12 : n = 0 ∨ n = 1,
        { clear ih ih1 ih2 H h,
          revert n a, exact dec_trivial },
        apply or.elim h12,
          intro h1, rw h1 at h, revert h, norm_num,
          intro h2, rw h2 at h, revert h, norm_num
      },
      apply @eq_of_add_eq_add_right _ _ _ 1 _,
      show n - 2 + 1 + 1 = n - 1 + 1,
      rw [add_assoc, one_add_one_eq_two, nat.sub_add_cancel W,
        nat.sub_add_cancel (le_of_lt h)]
    },
    rw [←H, chebyshev', H, sub_eq_neg_add, polynomial.degree_add_eq_of_degree_lt,
      polynomial.degree_mul_eq, polynomial.degree_mul_eq, polynomial.degree_X, H', ih1],
    { show (polynomial.degree (polynomial.C 2) + 1 + ↑(n - 1) = ↑n),
      rw [polynomial.degree_C, zero_add, ←with_bot.coe_one, ←with_bot.coe_add,
        add_comm, nat.sub_add_cancel (le_of_lt h)],
      exact two_ne_zero',
    },
    { rw [polynomial.degree_neg, polynomial.degree_mul_eq, polynomial.degree_mul_eq, ih2, H', ih1],
      show (↑(n - 2) < polynomial.degree (polynomial.C 2) + polynomial.degree polynomial.X + ↑(n - 1)),
      rw [polynomial.degree_C, polynomial.degree_X, zero_add, ←with_bot.coe_one, ←with_bot.coe_add,
        add_comm, nat.sub_add_cancel (le_of_lt h), with_bot.coe_lt_coe],
        apply nat.sub_lt (lt_trans zero_lt_one h), norm_num,
      exact two_ne_zero'
    }
  },
  { apply or.elim ((dec_trivial : ∀ j : ℕ, j ≤ 1 → j = 0 ∨ j = 1) n (le_of_not_gt h)),
      intro h0, rw h0, apply degree_C, exact dec_trivial,
      intro h1, rw h1, exact degree_X
  }
end

lemma useful' (k : ℕ) : polynomial.degree (chebyshev' (k - 2)) < 1 + polynomial.degree (chebyshev' (k - 1)) :=
begin
    rw [useful, useful, ←with_bot.coe_one, ←with_bot.coe_add, with_bot.coe_lt_coe, add_comm],
    by_cases h : k ≤ 1,
        apply or.elim ((dec_trivial : ∀ (j : ℕ), j ≤ 1 → j = 0 ∨ j = 1) k h),
            intro h0, simp [h0], exact zero_lt_one,
            intro h1, simp [h1], exact zero_lt_one,
    rw [nat.sub_add_cancel (le_of_not_le h)],
    apply nat.sub_lt (lt_of_lt_of_le zero_lt_one (le_of_not_le h)), norm_num
end

-- Q1(b)(iii)
theorem leading_coeff (n : ℕ) : polynomial.leading_coeff (chebyshev' n) =
  2 ^ (n - 1) := ---ans
begin
    apply nat.strong_induction_on n, intros k hk,
    have h1 : polynomial.leading_coeff (chebyshev' (k - 1)) = 2 ^ (k - 1 - 1),
        by_cases h : k = 0,
          rw h, show (leading_coeff (C (1 : ℤ)) = 1),exact leading_coeff_C (1 : ℤ),
          exact hk (k - 1) (nat.pred_lt h : k - 1 < k),
    have h2 : polynomial.leading_coeff (chebyshev' (k - 2)) = 2 ^ (k - 2 - 1),
        by_cases h : k ≤ 1,
            apply or.elim ((dec_trivial : ∀ j : ℕ, j ≤ 1 → j = 0 ∨ j = 1) k h),
                intro h0, rw h0, exact leading_coeff_C (1 : ℤ),
                intro h1, rw h1, exact leading_coeff_C (1 : ℤ),
            exact hk (k - 2) (nat.sub_lt (lt_of_not_ge (λ w, h (le_trans w zero_le_one))) (by norm_num)),
    by_cases h : k ≥ 2,
        have H : k - 2 + 2 = k := nat.sub_add_cancel h,
        rw [←H, chebyshev', H, (_ : nat.succ (k - 2) = k - 1), sub_eq_add_neg, add_comm,
            polynomial.leading_coeff_add_of_degree_lt, polynomial.leading_coeff_mul,
            polynomial.leading_coeff_mul, ←one_add_one_eq_two,
            polynomial.leading_coeff_add_of_degree_eq rfl, ←polynomial.C_1,
            polynomial.leading_coeff_C, polynomial.leading_coeff_X, h1,
            one_add_one_eq_two, mul_one, (_root_.pow_succ (2 : ℤ) (k - 1 - 1)).symm, nat.sub_add_cancel],
    change 1 ≤ k - 1,
    rwa [nat.le_sub_left_iff_add_le (le_of_lt h), one_add_one_eq_two],
    rw [←polynomial.C_1, polynomial.leading_coeff_C, one_add_one_eq_two], exact two_ne_zero',
    rw [polynomial.degree_neg, polynomial.degree_mul_eq, polynomial.degree_mul_eq,
        ((one_add_one_eq_two).symm : ((2 : polynomial ℤ) = 1 + 1)), ←polynomial.C_1, ←polynomial.C_add,
        one_add_one_eq_two, polynomial.degree_C, zero_add, polynomial.degree_X],
    exact useful' k,
    exact two_ne_zero',
    rw [nat.succ_eq_add_one, eq_comm, ←nat.sub_eq_iff_eq_add, nat.sub_sub, one_add_one_eq_two],
    rwa [nat.le_sub_left_iff_add_le (le_of_lt h), one_add_one_eq_two],
    rw not_lt at h,
    have h' : k = 0 ∨ k = 1, clear hk h1 h2, revert k h, exact dec_trivial,
    cases h',
    { rw h', exact leading_coeff_C (1 : ℤ)},
    { rw h', exact leading_coeff_C (1 : ℤ)}
end

-- Q1(b)(iv)
theorem cheby1 (n : ℕ) (hn : n ≥ 1) : polynomial.eval 1 (chebyshev n) = 1 := ---ans
begin
    have h := (polycos n hn 0).symm,
    rwa [mul_zero, cos_zero] at h,
end