/-
Copyright (c) 2020 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Mario Carneiro, Scott Morrison, Ainsley Pahljina
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.tactic.ring_exp
import Mathlib.tactic.interval_cases
import Mathlib.data.nat.parity
import Mathlib.data.zmod.basic
import Mathlib.group_theory.order_of_element
import Mathlib.ring_theory.fintype
import Mathlib.PostPort

namespace Mathlib

/-!
# The Lucas-Lehmer test for Mersenne primes.

We define `lucas_lehmer_residue : Π p : ℕ, zmod (2^p - 1)`, and
prove `lucas_lehmer_residue p = 0 → prime (mersenne p)`.

We construct a tactic `lucas_lehmer.run_test`, which iteratively certifies the arithmetic
required to calculate the residue, and enables us to prove

```
example : prime (mersenne 127) :=
lucas_lehmer_sufficiency _ (by norm_num) (by lucas_lehmer.run_test)
```

## TODO

- Show reverse implication.
- Speed up the calculations using `n ≡ (n % 2^p) + (n / 2^p) [MOD 2^p - 1]`.
- Find some bigger primes!

## History

This development began as a student project by Ainsley Pahljina,
and was then cleaned up for mathlib by Scott Morrison.
The tactic for certified computation of Lucas-Lehmer residues was provided by Mario Carneiro.
-/

/-- The Mersenne numbers, 2^p - 1. -/
def mersenne (p : ℕ) : ℕ := bit0 1 ^ p - 1

theorem mersenne_pos {p : ℕ} (h : 0 < p) : 0 < mersenne p := sorry

@[simp] theorem succ_mersenne (k : ℕ) : mersenne k + 1 = bit0 1 ^ k := sorry

namespace lucas_lehmer

/-!
We now define three(!) different versions of the recurrence
`s (i+1) = (s i)^2 - 2`.

These versions take values either in `ℤ`, in `zmod (2^p - 1)`, or
in `ℤ` but applying `% (2^p - 1)` at each step.

They are each useful at different points in the proof,
so we take a moment setting up the lemmas relating them.
-/

/-- The recurrence `s (i+1) = (s i)^2 - 2` in `ℤ`. -/
def s : ℕ → ℤ := sorry

/-- The recurrence `s (i+1) = (s i)^2 - 2` in `zmod (2^p - 1)`. -/
def s_zmod (p : ℕ) : ℕ → zmod (bit0 1 ^ p - 1) := sorry

/-- The recurrence `s (i+1) = ((s i)^2 - 2) % (2^p - 1)` in `ℤ`. -/
def s_mod (p : ℕ) : ℕ → ℤ := sorry

theorem mersenne_int_ne_zero (p : ℕ) (w : 0 < p) : bit0 1 ^ p - 1 ≠ 0 := sorry

theorem s_mod_nonneg (p : ℕ) (w : 0 < p) (i : ℕ) : 0 ≤ s_mod p i :=
  nat.cases_on i (id (iff.mp sup_eq_left rfl))
    fun (i : ℕ) => id (int.mod_nonneg (s_mod p i ^ bit0 1 - bit0 1) (mersenne_int_ne_zero p w))

theorem s_mod_mod (p : ℕ) (i : ℕ) : s_mod p i % (bit0 1 ^ p - 1) = s_mod p i := sorry

theorem s_mod_lt (p : ℕ) (w : 0 < p) (i : ℕ) : s_mod p i < bit0 1 ^ p - 1 := sorry

theorem s_zmod_eq_s (p' : ℕ) (i : ℕ) : s_zmod (p' + bit0 1) i = ↑(s i) := sorry

-- These next two don't make good `norm_cast` lemmas.

theorem int.coe_nat_pow_pred (b : ℕ) (p : ℕ) (w : 0 < b) : ↑(b ^ p - 1) = ↑b ^ p - 1 := sorry

theorem int.coe_nat_two_pow_pred (p : ℕ) : ↑(bit0 1 ^ p - 1) = bit0 1 ^ p - 1 :=
  int.coe_nat_pow_pred (bit0 1) p (of_as_true trivial)

theorem s_zmod_eq_s_mod (p : ℕ) (i : ℕ) : s_zmod p i = ↑(s_mod p i) := sorry

/-- The Lucas-Lehmer residue is `s p (p-2)` in `zmod (2^p - 1)`. -/
def lucas_lehmer_residue (p : ℕ) : zmod (bit0 1 ^ p - 1) := s_zmod p (p - bit0 1)

theorem residue_eq_zero_iff_s_mod_eq_zero (p : ℕ) (w : 1 < p) :
    lucas_lehmer_residue p = 0 ↔ s_mod p (p - bit0 1) = 0 :=
  sorry

/--
A Mersenne number `2^p-1` is prime if and only if
the Lucas-Lehmer residue `s p (p-2) % (2^p - 1)` is zero.
-/
def lucas_lehmer_test (p : ℕ) := lucas_lehmer_residue p = 0

/-- `q` is defined as the minimum factor of `mersenne p`, bundled as an `ℕ+`. -/
def q (p : ℕ) : ℕ+ := { val := nat.min_fac (mersenne p), property := sorry }

protected instance fact_pnat_pos (q : ℕ+) : fact (0 < ↑q) := subtype.property q

/-- We construct the ring `X q` as ℤ/qℤ + √3 ℤ/qℤ. -/
-- It would be nice to define this as (ℤ/qℤ)[x] / (x^2 - 3),

-- obtaining the ring structure for free,

-- but that seems to be more trouble than it's worth;

-- if it were easy to make the definition,

-- cardinality calculations would be somewhat more involved, too.

def X (q : ℕ+) := zmod ↑q × zmod ↑q

namespace X

theorem ext {q : ℕ+} {x : X q} {y : X q} (h₁ : prod.fst x = prod.fst y)
    (h₂ : prod.snd x = prod.snd y) : x = y :=
  sorry

@[simp] theorem add_fst {q : ℕ+} (x : X q) (y : X q) : prod.fst (x + y) = prod.fst x + prod.fst y :=
  rfl

@[simp] theorem add_snd {q : ℕ+} (x : X q) (y : X q) : prod.snd (x + y) = prod.snd x + prod.snd y :=
  rfl

@[simp] theorem neg_fst {q : ℕ+} (x : X q) : prod.fst (-x) = -prod.fst x := rfl

@[simp] theorem neg_snd {q : ℕ+} (x : X q) : prod.snd (-x) = -prod.snd x := rfl

protected instance has_mul {q : ℕ+} : Mul (X q) :=
  { mul :=
      fun (x y : X q) =>
        (prod.fst x * prod.fst y + bit1 1 * prod.snd x * prod.snd y,
        prod.fst x * prod.snd y + prod.snd x * prod.fst y) }

@[simp] theorem mul_fst {q : ℕ+} (x : X q) (y : X q) :
    prod.fst (x * y) = prod.fst x * prod.fst y + bit1 1 * prod.snd x * prod.snd y :=
  rfl

@[simp] theorem mul_snd {q : ℕ+} (x : X q) (y : X q) :
    prod.snd (x * y) = prod.fst x * prod.snd y + prod.snd x * prod.fst y :=
  rfl

protected instance has_one {q : ℕ+} : HasOne (X q) := { one := (1, 0) }

@[simp] theorem one_fst {q : ℕ+} : prod.fst 1 = 1 := rfl

@[simp] theorem one_snd {q : ℕ+} : prod.snd 1 = 0 := rfl

@[simp] theorem bit0_fst {q : ℕ+} (x : X q) : prod.fst (bit0 x) = bit0 (prod.fst x) := rfl

@[simp] theorem bit0_snd {q : ℕ+} (x : X q) : prod.snd (bit0 x) = bit0 (prod.snd x) := rfl

@[simp] theorem bit1_fst {q : ℕ+} (x : X q) : prod.fst (bit1 x) = bit1 (prod.fst x) := rfl

@[simp] theorem bit1_snd {q : ℕ+} (x : X q) : prod.snd (bit1 x) = bit0 (prod.snd x) := sorry

protected instance monoid {q : ℕ+} : monoid (X q) := monoid.mk Mul.mul sorry (1, 0) sorry sorry

theorem left_distrib {q : ℕ+} (x : X q) (y : X q) (z : X q) : x * (y + z) = x * y + x * z := sorry

theorem right_distrib {q : ℕ+} (x : X q) (y : X q) (z : X q) : (x + y) * z = x * z + y * z := sorry

protected instance ring {q : ℕ+} : ring (X q) :=
  ring.mk add_comm_group.add sorry add_comm_group.zero sorry sorry add_comm_group.neg
    add_comm_group.sub sorry sorry monoid.mul sorry monoid.one sorry sorry left_distrib
    right_distrib

protected instance comm_ring {q : ℕ+} : comm_ring (X q) :=
  comm_ring.mk ring.add sorry ring.zero sorry sorry ring.neg ring.sub sorry sorry ring.mul sorry
    ring.one sorry sorry sorry sorry sorry

protected instance nontrivial {q : ℕ+} [fact (1 < ↑q)] : nontrivial (X q) :=
  nontrivial.mk
    (Exists.intro 0
      (Exists.intro 1
        fun (h : 0 = 1) => prod.mk.inj_arrow h fun (h1 : 0 = 1) (ᾰ : 0 = 0) => zero_ne_one h1))

@[simp] theorem nat_coe_fst {q : ℕ+} (n : ℕ) : prod.fst ↑n = ↑n :=
  Nat.rec (Eq.refl (prod.fst ↑0))
    (fun (n_n : ℕ) (n_ih : prod.fst ↑n_n = ↑n_n) =>
      id (eq.mpr (id (propext (add_left_inj 1))) n_ih))
    n

@[simp] theorem nat_coe_snd {q : ℕ+} (n : ℕ) : prod.snd ↑n = 0 := sorry

@[simp] theorem int_coe_fst {q : ℕ+} (n : ℤ) : prod.fst ↑n = ↑n := sorry

@[simp] theorem int_coe_snd {q : ℕ+} (n : ℤ) : prod.snd ↑n = 0 := sorry

theorem coe_mul {q : ℕ+} (n : ℤ) (m : ℤ) : ↑(n * m) = ↑n * ↑m := sorry

theorem coe_nat {q : ℕ+} (n : ℕ) : ↑↑n = ↑n := sorry

/-- The cardinality of `X` is `q^2`. -/
theorem X_card {q : ℕ+} : fintype.card (X q) = ↑q ^ bit0 1 := sorry

/-- There are strictly fewer than `q^2` units, since `0` is not a unit. -/
theorem units_card {q : ℕ+} (w : 1 < q) : fintype.card (units (X q)) < ↑q ^ bit0 1 := sorry

/-- We define `ω = 2 + √3`. -/
/-- We define `ωb = 2 - √3`, which is the inverse of `ω`. -/
def ω {q : ℕ+} : X q := (bit0 1, 1)

def ωb {q : ℕ+} : X q := (bit0 1, -1)

theorem ω_mul_ωb (q : ℕ+) : ω * ωb = 1 := sorry

theorem ωb_mul_ω (q : ℕ+) : ωb * ω = 1 := sorry

/-- A closed form for the recurrence relation. -/
theorem closed_form {q : ℕ+} (i : ℕ) : ↑(s i) = ω ^ bit0 1 ^ i + ωb ^ bit0 1 ^ i := sorry

end X

/-!
Here and below, we introduce `p' = p - 2`, in order to avoid using subtraction in `ℕ`.
-/

/-- If `1 < p`, then `q p`, the smallest prime factor of `mersenne p`, is more than 2. -/
theorem two_lt_q (p' : ℕ) : bit0 1 < q (p' + bit0 1) := sorry

theorem ω_pow_formula (p' : ℕ) (h : lucas_lehmer_residue (p' + bit0 1) = 0) :
    ∃ (k : ℤ), X.ω ^ bit0 1 ^ (p' + 1) = ↑k * ↑(mersenne (p' + bit0 1)) * X.ω ^ bit0 1 ^ p' - 1 :=
  sorry

/-- `q` is the minimum factor of `mersenne p`, so `M p = 0` in `X q`. -/
theorem mersenne_coe_X (p : ℕ) : ↑(mersenne p) = 0 := sorry

theorem ω_pow_eq_neg_one (p' : ℕ) (h : lucas_lehmer_residue (p' + bit0 1) = 0) :
    X.ω ^ bit0 1 ^ (p' + 1) = -1 :=
  sorry

theorem ω_pow_eq_one (p' : ℕ) (h : lucas_lehmer_residue (p' + bit0 1) = 0) :
    X.ω ^ bit0 1 ^ (p' + bit0 1) = 1 :=
  sorry

/-- `ω` as an element of the group of units. -/
def ω_unit (p : ℕ) : units (X (q p)) := units.mk X.ω X.ωb sorry sorry

@[simp] theorem ω_unit_coe (p : ℕ) : ↑(ω_unit p) = X.ω := rfl

/-- The order of `ω` in the unit group is exactly `2^p`. -/
theorem order_ω (p' : ℕ) (h : lucas_lehmer_residue (p' + bit0 1) = 0) :
    order_of (ω_unit (p' + bit0 1)) = bit0 1 ^ (p' + bit0 1) :=
  sorry

theorem order_ineq (p' : ℕ) (h : lucas_lehmer_residue (p' + bit0 1) = 0) :
    bit0 1 ^ (p' + bit0 1) < ↑(q (p' + bit0 1)) ^ bit0 1 :=
  lt_of_le_of_lt (trans_rel_right LessEq (Eq.symm (order_ω p' h)) order_of_le_card_univ)
    (X.units_card (nat.lt_of_succ_lt (two_lt_q p')))

end lucas_lehmer

theorem lucas_lehmer_sufficiency (p : ℕ) (w : 1 < p) :
    lucas_lehmer_test p → nat.prime (mersenne p) :=
  sorry

-- Here we calculate the residue, very inefficiently, using `dec_trivial`. We can do much better.

-- Next we use `norm_num` to calculate each `s p i`.

namespace lucas_lehmer

theorem s_mod_succ {p : ℕ} {a : ℤ} {i : ℕ} {b : ℤ} {c : ℤ} (h1 : bit0 1 ^ p - 1 = a)
    (h2 : s_mod p i = b) (h3 : (b * b - bit0 1) % a = c) : s_mod p (i + 1) = c :=
  sorry

/--
Given a goal of the form `lucas_lehmer_test p`,
attempt to do the calculation using `norm_num` to certify each step.
-/
end lucas_lehmer

/-- We verify that the tactic works to prove `127.prime`. -/
/-!
This implementation works successfully to prove `(2^127 - 1).prime`,
and all the Mersenne primes up to this point appear in [archive/examples/mersenne_primes.lean].

`(2^127 - 1).prime` takes about 5 minutes to run (depending on your CPU!),
and unfortunately the next Mersenne prime `(2^521 - 1)`,
which was the first "computer era" prime,
is out of reach with the current implementation.

There's still low hanging fruit available to do faster computations
based on the formula
  n ≡ (n % 2^p) + (n / 2^p) [MOD 2^p - 1]
and the fact that `% 2^p` and `/ 2^p` can be very efficient on the binary representation.
Someone should do this, too!
-/

-- See https://leanprover.zulipchat.com/#narrow/stream/113489-new-members/topic/help.20finding.20a.20lemma/near/177698446

theorem modeq_mersenne (n : ℕ) (k : ℕ) :
    nat.modeq (bit0 1 ^ n - 1) k (k / bit0 1 ^ n + k % bit0 1 ^ n) :=
  sorry

end Mathlib


/-
Copyright (c) 2018  Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot, Chris Hughes, Michael Howes
-/
import algebra.group.semiconj
import algebra.group_with_zero.basic
import algebra.hom.aut
import algebra.hom.group
import data.fintype.basic

/-!
# Conjugacy of group elements

See also `mul_aut.conj` and `quandle.conj`.
-/

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

section monoid

variables [monoid α] [monoid β]

/-- We say that `a` is conjugate to `b` if for some unit `c` we have `c * a * c⁻¹ = b`. -/
def is_conj (a b : α) := ∃ c : αˣ, semiconj_by ↑c a b

@[refl] lemma is_conj.refl (a : α) : is_conj a a :=
⟨1, semiconj_by.one_left a⟩

@[symm] lemma is_conj.symm {a b : α} : is_conj a b → is_conj b a
| ⟨c, hc⟩ := ⟨c⁻¹, hc.units_inv_symm_left⟩

@[trans] lemma is_conj.trans {a b c : α} : is_conj a b → is_conj b c → is_conj a c
| ⟨c₁, hc₁⟩ ⟨c₂, hc₂⟩ := ⟨c₂ * c₁, hc₂.mul_left hc₁⟩

@[simp] lemma is_conj_iff_eq {α : Type*} [comm_monoid α] {a b : α} : is_conj a b ↔ a = b :=
⟨λ ⟨c, hc⟩, begin
  rw [semiconj_by, mul_comm, ← units.mul_inv_eq_iff_eq_mul, mul_assoc, c.mul_inv, mul_one] at hc,
  exact hc,
end, λ h, by rw h⟩

protected lemma monoid_hom.map_is_conj (f : α →* β) {a b : α} : is_conj a b → is_conj (f a) (f b)
| ⟨c, hc⟩ := ⟨units.map f c, by rw [units.coe_map, semiconj_by, ← f.map_mul, hc.eq, f.map_mul]⟩

end monoid

section cancel_monoid
variables [cancel_monoid α]
-- These lemmas hold for either `left_cancel_monoid` or `right_cancel_monoid`,
-- with slightly different proofs; so far these don't seem necessary.

@[simp] lemma is_conj_one_right {a : α} : is_conj 1 a  ↔ a = 1 :=
⟨λ ⟨c, hc⟩, mul_right_cancel (hc.symm.trans ((mul_one _).trans (one_mul _).symm)), λ h, by rw [h]⟩

@[simp] lemma is_conj_one_left {a : α} : is_conj a 1 ↔ a = 1 :=
calc is_conj a 1 ↔ is_conj 1 a : ⟨is_conj.symm, is_conj.symm⟩
... ↔ a = 1 : is_conj_one_right

end cancel_monoid

section group

variables [group α]

@[simp] lemma is_conj_iff {a b : α} :
  is_conj a b ↔ ∃ c : α, c * a * c⁻¹ = b :=
⟨λ ⟨c, hc⟩, ⟨c, mul_inv_eq_iff_eq_mul.2 hc⟩, λ ⟨c, hc⟩,
  ⟨⟨c, c⁻¹, mul_inv_self c, inv_mul_self c⟩, mul_inv_eq_iff_eq_mul.1 hc⟩⟩

@[simp] lemma conj_inv {a b : α} : (b * a * b⁻¹)⁻¹ = b * a⁻¹ * b⁻¹ :=
((mul_aut.conj b).map_inv a).symm

@[simp] lemma conj_mul {a b c : α} : (b * a * b⁻¹) * (b * c * b⁻¹) = b * (a * c) * b⁻¹ :=
((mul_aut.conj b).map_mul a c).symm

@[simp] lemma conj_pow {i : ℕ} {a b : α} : (a * b * a⁻¹) ^ i = a * (b ^ i) * a⁻¹ :=
begin
  induction i with i hi,
  { simp },
  { simp [pow_succ, hi] }
end

@[simp] lemma conj_zpow {i : ℤ} {a b : α} : (a * b * a⁻¹) ^ i = a * (b ^ i) * a⁻¹ :=
begin
  induction i,
  { simp },
  { simp [zpow_neg_succ_of_nat, conj_pow] }
end

lemma conj_injective {x : α} : function.injective (λ (g : α), x * g * x⁻¹) :=
(mul_aut.conj x).injective

end group

@[simp] lemma is_conj_iff₀ [group_with_zero α] {a b : α} :
  is_conj a b ↔ ∃ c : α, c ≠ 0 ∧ c * a * c⁻¹ = b :=
⟨λ ⟨c, hc⟩, ⟨c, begin
    rw [← units.coe_inv', units.mul_inv_eq_iff_eq_mul],
    exact ⟨c.ne_zero, hc⟩,
  end⟩, λ ⟨c, c0, hc⟩,
  ⟨units.mk0 c c0, begin
    rw [semiconj_by, ← units.mul_inv_eq_iff_eq_mul, units.coe_inv', units.coe_mk0],
    exact hc
  end⟩⟩

namespace is_conj
/- This small quotient API is largely copied from the API of `associates`;
where possible, try to keep them in sync -/

/-- The setoid of the relation `is_conj` iff there is a unit `u` such that `u * x = y * u` -/
protected def setoid (α : Type*) [monoid α] : setoid α :=
{ r := is_conj, iseqv := ⟨is_conj.refl, λa b, is_conj.symm, λa b c, is_conj.trans⟩ }

end is_conj

local attribute [instance, priority 100] is_conj.setoid

/-- The quotient type of conjugacy classes of a group. -/
def conj_classes (α : Type*) [monoid α] : Type* :=
quotient (is_conj.setoid α)

namespace conj_classes

section monoid
variables [monoid α] [monoid β]

/-- The canonical quotient map from a monoid `α` into the `conj_classes` of `α` -/
protected def mk {α : Type*} [monoid α] (a : α) : conj_classes α :=
⟦a⟧

instance : inhabited (conj_classes α) := ⟨⟦1⟧⟩

theorem mk_eq_mk_iff_is_conj {a b : α} :
  conj_classes.mk a = conj_classes.mk b ↔ is_conj a b :=
iff.intro quotient.exact quot.sound

theorem quotient_mk_eq_mk (a : α) : ⟦ a ⟧ = conj_classes.mk a := rfl

theorem quot_mk_eq_mk (a : α) : quot.mk setoid.r a = conj_classes.mk a := rfl

theorem forall_is_conj {p : conj_classes α → Prop} :
  (∀a, p a) ↔ (∀a, p (conj_classes.mk a)) :=
iff.intro
  (assume h a, h _)
  (assume h a, quotient.induction_on a h)

theorem mk_surjective : function.surjective (@conj_classes.mk α _) :=
forall_is_conj.2 (λ a, ⟨a, rfl⟩)

instance : has_one (conj_classes α) := ⟨⟦ 1 ⟧⟩

theorem one_eq_mk_one : (1 : conj_classes α) = conj_classes.mk 1 := rfl

lemma exists_rep (a : conj_classes α) : ∃ a0 : α, conj_classes.mk a0 = a :=
quot.exists_rep a

/-- A `monoid_hom` maps conjugacy classes of one group to conjugacy classes of another. -/
def map (f : α →* β) : conj_classes α → conj_classes β :=
quotient.lift (conj_classes.mk ∘ f) (λ a b ab, mk_eq_mk_iff_is_conj.2 (f.map_is_conj ab))

lemma map_surjective {f : α →* β} (hf : function.surjective f) :
  function.surjective (conj_classes.map f) :=
begin
  intros b,
  obtain ⟨b, rfl⟩ := conj_classes.mk_surjective b,
  obtain ⟨a, rfl⟩ := hf b,
  exact ⟨conj_classes.mk a, rfl⟩,
end

instance [fintype α] [decidable_rel (is_conj : α → α → Prop)] :
  fintype (conj_classes α) :=
quotient.fintype (is_conj.setoid α)

end monoid

section comm_monoid
variable [comm_monoid α]

lemma mk_injective : function.injective (@conj_classes.mk α _) :=
λ _ _, (mk_eq_mk_iff_is_conj.trans is_conj_iff_eq).1

lemma mk_bijective : function.bijective (@conj_classes.mk α _) :=
⟨mk_injective, mk_surjective⟩

/-- The bijection between a `comm_group` and its `conj_classes`. -/
def mk_equiv : α ≃ conj_classes α :=
⟨conj_classes.mk, quotient.lift id (λ (a : α) b, is_conj_iff_eq.1), quotient.lift_mk _ _,
  begin
    rw [function.right_inverse, function.left_inverse, forall_is_conj],
    intro x,
    rw [← quotient_mk_eq_mk, ← quotient_mk_eq_mk, quotient.lift_mk, id.def],
  end⟩

end comm_monoid
end conj_classes

section monoid

variables [monoid α]

/-- Given an element `a`, `conjugates a` is the set of conjugates. -/
def conjugates_of (a : α) : set α := {b | is_conj a b}

lemma mem_conjugates_of_self {a : α} : a ∈ conjugates_of a := is_conj.refl _

lemma is_conj.conjugates_of_eq {a b : α} (ab : is_conj a b) :
  conjugates_of a = conjugates_of b :=
set.ext (λ g, ⟨λ ag, (ab.symm).trans ag, λ bg, ab.trans bg⟩)

lemma is_conj_iff_conjugates_of_eq {a b : α} :
  is_conj a b ↔ conjugates_of a = conjugates_of b :=
⟨is_conj.conjugates_of_eq, λ h, begin
  have ha := mem_conjugates_of_self,
  rwa ← h at ha,
end⟩

end monoid

namespace conj_classes

variables [monoid α]

local attribute [instance] is_conj.setoid

/-- Given a conjugacy class `a`, `carrier a` is the set it represents. -/
def carrier : conj_classes α → set α :=
quotient.lift conjugates_of (λ (a : α) b ab, is_conj.conjugates_of_eq ab)

lemma mem_carrier_mk {a : α} : a ∈ carrier (conj_classes.mk a) := is_conj.refl _

lemma mem_carrier_iff_mk_eq {a : α} {b : conj_classes α} :
  a ∈ carrier b ↔ conj_classes.mk a = b :=
begin
  revert b,
  rw forall_is_conj,
  intro b,
  rw [carrier, eq_comm, mk_eq_mk_iff_is_conj, ← quotient_mk_eq_mk, quotient.lift_mk],
  refl,
end

lemma carrier_eq_preimage_mk {a : conj_classes α} :
  a.carrier = conj_classes.mk ⁻¹' {a} :=
set.ext (λ x, mem_carrier_iff_mk_eq)

end conj_classes


-- Disjunction

variables p q : Prop

example (hp : p) : p ∨ q := or.intro_left q hp
example (hq : q) : p ∨ q := or.intro_right p hq

example (h : p ∨ q) : q ∨ p :=
or.elim h
  (assume hp : p,
    show q ∨ p, from or.intro_right q hp)
  (assume hq : q,
    show q ∨ p, from or.intro_left p hq)

example (h : p ∨ q) : q ∨ p :=
or.elim h (λ hp, or.inr hp) (λ hq, or.inl hq)

example (h : p ∨ q) : q ∨ p :=
h.elim
  (assume hp : p, or.inr hp)
  (assume hq : q, or.inl hq)


import data.set.basic -- hide
open set -- hide

/-
There is an equivalence between set theory and logic, and Lean identifies these two.

In this equivalence, a logic statement P : X → true/false corresponds to the set
$A = \{ x : X | P(x) \}$. Under this equivalence, logical implications P → Q translate into
set inclusions A ⊆ B, and so on.

The goal of this lemma is to prove transitivity of set inclusion, giving almost the same
proof as in the previous lemma.
-/

/- Hint : Click here for a hint, in case you get stuck.
Start with `intro x,`, then do exactly as in the previous level.
-/

variables {X Y : Type} -- hide

/- Lemma : no-side-bar
If A, B and C are sets and, and we know A ⊆ B and B ⊆ C, then
we have A ⊆ C.
-/
lemma subset_transitive (A B C : set X) (hAB : A ⊆ B) (hBC : B ⊆ C) :
  A ⊆ C :=
begin
  intro x,
  intro hA,
  apply hBC,
  apply hAB,
  exact hA,

end


import data.polynomial.basic
import data.polynomial.eval
import data.mv_polynomial.basic
import data.real.basic
import topology.basic
import data.polynomial.ring_division
import topology.instances.real
import analysis.special_functions.pow
import data.set.prod
import data.polynomial.eval
import data.finsupp.pointwise
import data.mv_polynomial.funext

-- you have a polynomial p that is 0 in a neighborhood N of the origin.
-- pick any nonzero v in N and consider the single variable polynomial p(tv) in t
-- by your previous stuff, this is identically 0
-- in general, if you want to know what p(x) is for any x,
-- take a small enough r so that rx is in N, then consider the single variable polynomial p(trx)
--  and observe that it's zero, so p(x)=p((1/r)rx)=0

open_locale polynomial classical big_operators

variables {σ : Type*} [fintype σ] (P : mv_polynomial σ ℝ)

noncomputable def mv_poly_along_line (P : mv_polynomial σ ℝ) (v : σ → ℝ) : ℝ[X] :=
{
  to_finsupp :=
  ∑ exps : σ →₀ ℕ in P.support, finsupp.single (∑ i : σ, exps i) (P.coeff exps * ∏ i : σ, (v i)^(exps i)),
}

noncomputable def mv_polynomial.recenter (P : mv_polynomial σ ℝ) (v : σ → ℝ) : mv_polynomial σ ℝ :=
mv_polynomial.eval₂ mv_polynomial.C (λ i, mv_polynomial.X i - mv_polynomial.C (v i)) P

lemma mv_polynomial.recenter_eval (P : mv_polynomial σ ℝ) (v₀ v₁ : σ → ℝ) :
  mv_polynomial.eval v₁ (mv_polynomial.recenter P v₀) = mv_polynomial.eval (v₁ - v₀) P :=
begin
  rw mv_polynomial.recenter,
  rw mv_polynomial.eval₂_eq,
  rw mv_polynomial.eval_sum,
  simp only [mv_polynomial.eval_C, map_mul, finset.sum_congr],
  simp_rw mv_polynomial.eval_prod,
  simp only [mv_polynomial.eval_X, map_pow, mv_polynomial.eval_C, finset.prod_congr, finset.sum_congr, map_sub],
  rw mv_polynomial.eval_eq,
  simp only [eq_self_iff_true, finset.prod_congr, finset.sum_congr, pi.sub_apply],
end

lemma mv_polynomial.eq_zero_of_recenter_eq_zero (P : mv_polynomial σ ℝ) (v₀ : σ → ℝ) :
  P.recenter v₀ = 0 → P = 0 :=
begin
  intro h,
  rw mv_polynomial.funext_iff,
  intro x,
  rw mv_polynomial.funext_iff at h,
  specialize h (x + v₀),
  rw mv_polynomial.recenter_eval at h,
  simp only [add_sub_cancel, map_zero] at h,
  rw h,
  simp only [eq_self_iff_true, map_zero],
end

lemma mv_polynomial.eq_zero_iff_recenter_eq_zero (P : mv_polynomial σ ℝ) (v₀ : σ → ℝ) :
  P.recenter v₀ = 0 ↔ P = 0 :=
begin
  split,
  exact mv_polynomial.eq_zero_of_recenter_eq_zero P v₀,
  intro h,
  rw mv_polynomial.funext_iff,
  intro x,
  rw mv_polynomial.recenter_eval,
  rw h,
  simp only [eq_self_iff_true, map_zero],
end

lemma sum_supported_foo (P Q : mv_polynomial σ ℝ) (v : σ → ℝ) (n : ℕ) :
  ∑ (x : σ →₀ ℕ) in ((P + Q).support \ P.support),
    (finsupp.single (finset.univ.sum x) (mv_polynomial.coeff x P * ∏ (i : σ), v i ^ x i)) n = 0 :=
begin
  apply finset.sum_eq_zero,
  intros x hx,
  simp only [mv_polynomial.coeff_add, not_not, finset.mem_sdiff, ne.def, mv_polynomial.mem_support_iff] at hx,
  rw hx.2,
  simp only [finsupp.coe_zero, zero_mul, pi.zero_apply, eq_self_iff_true, finsupp.single_zero],
end

lemma in_supp_not_add_supp (P Q : mv_polynomial σ ℝ) (x : σ →₀ ℕ) :
  x ∈ (P.support \ (Q + P).support) → mv_polynomial.coeff x P = - mv_polynomial.coeff x Q :=
begin
  intro h,
  simp only [mv_polynomial.coeff_add, not_not, finset.mem_sdiff, ne.def, mv_polynomial.mem_support_iff] at h,
  rw add_comm at h,
  rw ← add_eq_zero_iff_eq_neg,
  exact h.2,
end

lemma in_supp_not_add_supp' (P Q : mv_polynomial σ ℝ) (v : σ → ℝ) (n : ℕ) (x : σ →₀ ℕ) :
  x ∈ (P.support \ (Q + P).support) →
    finsupp.single (finset.univ.sum x) (mv_polynomial.coeff x P * ∏ (i : σ), v i ^ x i)
    n
    = - finsupp.single (finset.univ.sum x) (mv_polynomial.coeff x Q * ∏ (i : σ), v i ^ x i)
    n :=
begin
  intro h,
  rw in_supp_not_add_supp P Q x h,
  simp only [finsupp.single_neg, neg_mul, pi.neg_apply, eq_self_iff_true, neg_inj, finsupp.coe_neg],
end

lemma supp_minus_add_supp_eq_supp_minus_add_supp (P Q : mv_polynomial σ ℝ) :
  (P.support \ (Q + P).support) = (Q.support \ (Q + P).support) :=
begin
  ext,
  simp only [mv_polynomial.coeff_add,
 not_not,
 and.congr_left_iff,
 finset.mem_sdiff,
 ne.def,
 mv_polynomial.mem_support_iff],
 intro h,
 rw add_eq_zero_iff_neg_eq at h,
 simp_rw ← h,
 simp only [iff_self, neg_eq_zero],
end

lemma mv_poly_along_line_add (P Q : mv_polynomial σ ℝ) (v : σ → ℝ) :
  mv_poly_along_line (P + Q) v = mv_poly_along_line P v + mv_poly_along_line Q v :=
begin
  iterate { rw mv_poly_along_line,},
  apply polynomial.ext,
  intro n,
  rw polynomial.add_to_finsupp,
  iterate {rw polynomial.coeff,},
  rw finsupp.add_apply,
  iterate {rw finsupp.finset_sum_apply,},
  simp_rw mv_polynomial.coeff_add,
  simp_rw add_mul,
  simp_rw finsupp.single_add,
  simp_rw finsupp.coe_add,
  simp_rw pi.add_apply,
  rw finset.sum_add_distrib,
  rw ← finset.sum_inter_add_sum_diff (P + Q).support P.support,
  rw sum_supported_foo P Q,
  rw ← finset.sum_inter_add_sum_diff (P + Q).support Q.support,
  rw add_comm P Q,
  rw sum_supported_foo Q P,
  rw add_zero,
  rw add_zero,
  have hP : P.support = (P.support \ (Q + P).support) ∪ (P.support ∩ (Q + P).support) := (finset.sdiff_union_inter _ _).symm,
  have hQ : Q.support = (Q.support \ (P + Q).support) ∪ (Q.support ∩ (P + Q).support) := (finset.sdiff_union_inter _ _).symm,
  conv_rhs {rw hP, rw hQ},
  rw finset.sum_union,
  rw finset.sum_union,
  rw finset.inter_comm _ P.support,
  rw finset.inter_comm _ Q.support,
  conv_rhs {rw add_assoc, rw add_comm, rw add_assoc,},
  rw add_right_inj _,
  conv_rhs {rw add_assoc, rw add_comm, rw add_comm P Q, rw add_assoc},
  apply self_eq_add_right.2,
  rw finset.sum_congr rfl (in_supp_not_add_supp' P Q v n),
  rw supp_minus_add_supp_eq_supp_minus_add_supp,
  simp only [eq_self_iff_true, add_left_neg, finset.sum_neg_distrib],
  rw disjoint_iff,
  simp only [finset.inf_eq_inter, finset.bot_eq_empty],
  rw finset.inter_comm,
  simp only [finset.inter_assoc, finset.inter_sdiff_self, eq_self_iff_true, finset.inter_empty],
  rw disjoint_iff,
  simp only [finset.inf_eq_inter, finset.bot_eq_empty],
  rw finset.inter_comm,
  simp only [finset.inter_assoc, finset.inter_sdiff_self, eq_self_iff_true, finset.inter_empty],
end

lemma ite_fact (a b : ℝ) : ite (0 = 0) a b = a :=
begin
  simp only [if_true, eq_self_iff_true],
end

lemma mv_polynomial.support_C (a : ℝ) : ((mv_polynomial.C a): mv_polynomial σ ℝ).support = ite (a = 0) ∅ {0} :=
begin
  rw mv_polynomial.C_apply,
  rw mv_polynomial.support_monomial,
end

lemma mv_polynomial.support_C' (a : ℝ) (ha : a ≠ 0) : ((mv_polynomial.C a): mv_polynomial σ ℝ).support = {0} :=
begin
  rw mv_polynomial.C_apply,
  rw mv_polynomial.support_monomial,
  simp only [ite_eq_right_iff],
  intro h,
  exfalso,
  exact ha h,
end

lemma mv_polynomial.pairwise_induction_on {M : mv_polynomial σ ℝ → mv_polynomial σ ℝ → Prop} (P Q : mv_polynomial σ ℝ)
  (h_mono : ∀ (u₁ u₂ : σ →₀ ℕ) (a₁ a₂ : ℝ), M (mv_polynomial.monomial u₁ a₁) (mv_polynomial.monomial u₂ a₂))
  (h_add₁ : ∀ p₁ p₂ q : mv_polynomial σ ℝ, M p₁ q → M p₂ q → M (p₁ + p₂) q)
  (h_add₂ : ∀ p q₁ q₂ : mv_polynomial σ ℝ, M p q₁ → M p q₂ → M p (q₁ + q₂)) :
  M P Q :=
begin
  apply mv_polynomial.induction_on' P,
  apply mv_polynomial.induction_on' Q,
  exact λ u a v b, h_mono v u b a,
  intros p q hp hq u a,
  have := h_add₂ (mv_polynomial.monomial u a) p q _ _,
  exact this,
  exact hp _ _,
  exact hq _ _,
  intros p q hp hq,
  have := h_add₁ p q Q _ _,
  exact this,
  exact hp,
  exact hq,
end

lemma mv_polynomial.pairwise_induction_on' {M : mv_polynomial σ ℝ → mv_polynomial σ ℝ → Prop} (P Q : mv_polynomial σ ℝ)
  (h_mono : ∀ (u₁ u₂ : σ →₀ ℕ) (a₁ a₂ : ℝ), M (mv_polynomial.monomial u₁ a₁) (mv_polynomial.monomial u₂ a₂))
  (h_add : ∀ p₁ p₂ q₁ q₂ : mv_polynomial σ ℝ, M p₁ p₂ → M q₁ q₂ → M (p₁ + q₁) (p₂ + q₂)) :
  M P Q :=
begin
  apply mv_polynomial.induction_on' P,
  apply mv_polynomial.induction_on' Q,
  exact λ u a v b, h_mono v u b a,
  intros p q hp hq u a,
  have := h_add (mv_polynomial.monomial u a) p 0 q _ _,
  rw add_zero at this,
  exact this,
  exact hp _ _,
  rw [← mv_polynomial.C_0, mv_polynomial.C_apply],
  exact hq 0 0,
  intros p q hp hq,
  have := h_add p Q q 0 _ _,
  rw add_zero at this,
  exact this,
  exact hp,
  apply mv_polynomial.induction_on' q,
  intros u a,
  rw [← mv_polynomial.C_0, mv_polynomial.C_apply],
  exact h_mono _ _ _ _,
  intros p q hp hq,
  have := h_add p 0 q 0 _ _,
  rw add_zero at this,
  exact this,
  exact hp,
  exact hq,
end

lemma mv_poly_along_line_mul (P Q : mv_polynomial σ ℝ) (v : σ → ℝ) :
  mv_poly_along_line (P * Q) v = mv_poly_along_line P v * mv_poly_along_line Q v :=
begin
  apply mv_polynomial.pairwise_induction_on P Q,
  intros,

  rw mv_polynomial.monomial_mul,
  iterate{rw mv_poly_along_line,},
  rw polynomial.mul_to_finsupp,
  iterate{rw mv_polynomial.support_monomial},
  by_cases ha₁ : a₁ = 0,
  rw ha₁,
  simp only [mv_polynomial.monomial_zero, if_true, mv_polynomial.coeff_monomial, mv_polynomial.coeff_zero, zero_mul,
 eq_self_iff_true, finsupp.single_zero, ite_mul, finset.sum_const_zero, finset.sum_congr],
  by_cases ha₂ : a₂ = 0,
  rw ha₂,
  simp only [mv_polynomial.monomial_zero, if_true, mv_polynomial.coeff_monomial, mv_polynomial.coeff_zero, zero_mul,
 eq_self_iff_true, finsupp.single_zero, ite_mul, finset.sum_const_zero, finset.sum_congr],
  simp only [if_t_t, if_true, zero_mul, eq_self_iff_true, finsupp.single_zero, finset.sum_const_zero, mul_zero, finset.sum_congr],
  have ha₁₂ : a₁ * a₂ ≠ 0 := mul_ne_zero ha₁ ha₂,
  simp only [ha₁, ha₂, if_false, mul_ne_zero, ha₁₂],
  iterate {rw finset.sum_singleton,},
  rw add_monoid_algebra.single_mul_single,
  iterate {rw mv_polynomial.coeff_monomial,},
  simp only [if_true, eq_self_iff_true],
  simp only [pi.add_apply, finsupp.coe_add, finset.sum_congr],
  rw finset.sum_add_distrib,
  congr' 1,
  iterate {rw ←  mul_assoc,},
  conv_rhs {rw mul_comm _ a₂},
  iterate {rw mul_assoc},
  rw ← finset.prod_mul_distrib,
  simp_rw ← pow_add,
  iterate {rw ← mul_assoc},
  rw mul_comm a₁ a₂,

  intros p₁ p₂ q h₁ h₂,
  rw add_mul,
  iterate {rw mv_poly_along_line_add},
  rw h₁,
  rw h₂,
  rw add_mul,
  intros p q₁ q₂ h₁ h₂,
  rw mul_add,
  iterate {rw mv_poly_along_line_add},
  rw mul_add,
  rw h₁,
  rw h₂,
end

def eval_along_line_eq_eval' (P : mv_polynomial σ ℝ) : Prop :=
  ∀ (v : σ → ℝ) (t : ℝ), (mv_poly_along_line P v).eval t = mv_polynomial.eval (t • v) P

lemma eval_along_line_eq_eval'_cons : ∀ r : ℝ, eval_along_line_eq_eval' (@mv_polynomial.C ℝ σ _ r) :=
begin
  intro r,
  rw eval_along_line_eq_eval',
  intros v t,
  rw mv_polynomial.eval_eq,
  rw mv_poly_along_line,
  rw polynomial.eval_eq_sum,
  simp only [finset.prod_const_one,
 finsupp.coe_zero,
 mul_one,
 algebra.id.smul_eq_mul,
 zero_mul,
 pi.zero_apply,
 mv_polynomial.coeff_C,
 ite_mul,
 ne.def,
 finsupp.support_zero,
 finset.prod_congr,
 mv_polynomial.coeff_zero_C,
 pi.smul_apply,
 pow_zero,
 finset.sum_congr,
 finset.sum_ite_eq,
 ite_not,
 mv_polynomial.mem_support_iff],
 split_ifs,
 rw h,
 simp only [if_t_t,
 mv_polynomial.C_0,
 zero_mul,
 finsupp.single_zero,
 finset.sum_const_zero,
 mv_polynomial.support_zero,
 finset.sum_congr],
 change (0 : ℝ[X]).sum (λ (e : ℕ) (a : ℝ), a * t ^ e) = 0,
 rw polynomial.sum_zero_index,
 rw ← mv_polynomial.monomial_zero',
 rw mv_polynomial.support_monomial,
 split_ifs,
 exfalso,
 exact h h_1,
 simp only [finset.prod_const_one,
 finsupp.coe_zero,
 mul_one,
 if_true,
 pi.zero_apply,
 eq_self_iff_true,
 finset.sum_const_zero,
 finset.sum_singleton,
 finset.prod_congr,
 pow_zero,
 finset.sum_congr],

  rw polynomial.sum,
  rw polynomial.support,
  have : (finsupp.single 0 r) 0 ≠ 0 :=
  begin
    simp only [finsupp.single_eq_same, ne.def],
    exact h,
  end,
  rw (finsupp.support_eq_singleton.2 ⟨ this, (by simp only [finsupp.single_eq_same, eq_self_iff_true])⟩),
  simp only [mul_one, finset.sum_singleton, pow_zero],
  rw polynomial.coeff,
  simp only [finsupp.single_eq_same, eq_self_iff_true],
end

lemma eval_along_line_eq_eval'_add : ∀ p q : mv_polynomial σ ℝ, eval_along_line_eq_eval' p →
    eval_along_line_eq_eval' q → eval_along_line_eq_eval' (p + q) :=
begin
  intros p q hp hq v t,
  simp_rw [mv_poly_along_line_add, polynomial.eval_add],
  rw [hp, hq],
  rw mv_polynomial.eval,
  simp only [add_left_inj, mv_polynomial.coe_eval₂_hom, eq_self_iff_true, mv_polynomial.eval₂_add],
end

lemma eval_along_line_eq_eval'x : ∀ (p : mv_polynomial σ ℝ) (n : σ),
    eval_along_line_eq_eval' p → eval_along_line_eq_eval' (p * mv_polynomial.X n) :=
begin
  intros p n hp v t,
  rw mv_polynomial.eval,
  simp only [algebra.id.smul_eq_mul,
 mv_polynomial.eval₂_mul,
 mv_polynomial.coe_eval₂_hom,
 pi.smul_apply,
 mv_polynomial.eval₂_X],
 rw mv_poly_along_line_mul,
 rw polynomial.eval_mul,
 rw hp,
 simp_rw mv_polynomial.eval,
 simp only [mv_polynomial.coe_eval₂_hom, mul_eq_mul_left_iff],
 left,
 rw mv_poly_along_line,
 rw polynomial.eval_eq_sum,
 have : (@mv_polynomial.X ℝ σ _ n).support = {finsupp.single n 1} :=
 begin
   rw mv_polynomial.X,
   rw mv_polynomial.support,
   rw mv_polynomial.monomial,
   simp only [finsupp.lsingle_apply],
   apply finsupp.support_eq_singleton.2,
   split,
   simp only [one_ne_zero, finsupp.single_eq_same, ne.def, not_false_iff],
   simp only [finsupp.single_eq_same, eq_self_iff_true],
 end,
 rw this,
 simp only [one_mul, mv_polynomial.coeff_X, finset.sum_singleton],
 rw polynomial.sum,
 simp_rw polynomial.support,
  by_cases hv : v n ≠ 0,
  rw finsupp.support_single_ne_zero _,
  simp only [finset.sum_singleton],
  rw polynomial.coeff,
  simp only [finsupp.single_eq_same],
  simp_rw finsupp.single_apply,
  simp only [finset.mem_univ,
 if_true,
 finset.sum_boole,
 nat.cast_id,
 finset.prod_ite_eq,
 finset.prod_congr,
 pow_boole],
 rw finset.filter_eq,
 simp only [finset.mem_univ, if_true, pow_one, finset.card_singleton],
 rw mul_comm,
 intro H,
 apply hv,
 simp_rw finsupp.single_apply at H,
 simp only [finset.mem_univ, if_true, finset.prod_ite_eq, finset.prod_congr, pow_boole] at H,
 exact H,
 simp only [not_not, ne.def] at hv,
 simp_rw finsupp.single_apply,
 simp only [finset.mem_univ,
 if_true,
 finset.sum_boole,
 nat.cast_id,
 finset.prod_ite_eq,
 finset.prod_congr,
 pow_boole,
 finset.sum_congr],
 rw hv,
 simp only [finset.sum_empty,
 eq_self_iff_true,
 finsupp.single_zero,
 finsupp.support_zero,
 mul_zero,
 finset.sum_congr],
end

lemma eval_along_line_eq_eval (P : mv_polynomial σ ℝ) (v : σ → ℝ) (t : ℝ) :
  (mv_poly_along_line P v).eval t = mv_polynomial.eval (t • v) P :=
begin
  have hcons : ∀ r : ℝ, eval_along_line_eq_eval' (mv_polynomial.C r) := λ r v t, eval_along_line_eq_eval'_cons r v t,
  have hadd : ∀ p q : mv_polynomial σ ℝ, eval_along_line_eq_eval' p →
    eval_along_line_eq_eval' q → eval_along_line_eq_eval' (p + q) := eval_along_line_eq_eval'_add,
  have hx : ∀ (p : mv_polynomial σ ℝ) (n : σ),
    eval_along_line_eq_eval' p → eval_along_line_eq_eval' (p * mv_polynomial.X n) := eval_along_line_eq_eval'x,

  have := @mv_polynomial.induction_on _ _ _ eval_along_line_eq_eval' P hcons hadd hx,
  exact this v t,
end

lemma eval_along_line_eq_eval'' (P : mv_polynomial σ ℝ) (v : σ → ℝ):
  (mv_poly_along_line P v).eval 1 = mv_polynomial.eval (v) P :=
begin
  rw eval_along_line_eq_eval P v 1,
  rw one_smul,
end

lemma mv_poly_along_line_eq_zero_iff_mv_poly_eq_zero (P : mv_polynomial σ ℝ) :
  (∀ (v : σ → ℝ), mv_poly_along_line P v = 0) ↔ P = 0 :=
  begin
    split,
    swap,
    intros hP v,
    rw mv_poly_along_line,
    rw hP,
    simp only [mv_polynomial.coeff_zero, zero_mul, finsupp.single_zero, finset.sum_const_zero, mv_polynomial.support_zero, finset.sum_congr],
    rw polynomial.has_zero,
    intro h,
    apply mv_polynomial.funext,
    intro v,
    rw ← eval_along_line_eq_eval'',
    rw h v,
    rw polynomial.eval_zero,
    simp only [eq_self_iff_true, map_zero],
  end


/-
Copyright (c) 2020 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Floris van Doorn, Yury Kudryashov
-/
import topology.instances.nnreal
import topology.algebra.ordered.monotone_continuity

/-!
# Square root of a real number

In this file we define

* `nnreal.sqrt` to be the square root of a nonnegative real number.
* `real.sqrt` to be the square root of a real number, defined to be zero on negative numbers.

Then we prove some basic properties of these functions.

## Implementation notes

We define `nnreal.sqrt` as the noncomputable inverse to the function `x ↦ x * x`. We use general
theory of inverses of strictly monotone functions to prove that `nnreal.sqrt x` exists. As a side
effect, `nnreal.sqrt` is a bundled `order_iso`, so for `nnreal` numbers we get continuity as well as
theorems like `sqrt x ≤ y ↔ x * x ≤ y` for free.

Then we define `real.sqrt x` to be `nnreal.sqrt (real.to_nnreal x)`. We also define a Cauchy
sequence `real.sqrt_aux (f : cau_seq ℚ abs)` which converges to `sqrt (mk f)` but do not prove (yet)
that this sequence actually converges to `sqrt (mk f)`.

## Tags

square root
-/

open set filter
open_locale filter nnreal topological_space

namespace nnreal

variables {x y : ℝ≥0}

/-- Square root of a nonnegative real number. -/
@[pp_nodot] noncomputable def sqrt : ℝ≥0 ≃o ℝ≥0 :=
order_iso.symm $ strict_mono.order_iso_of_surjective (λ x, x * x)
  (λ x y h, mul_self_lt_mul_self x.2 h) $
  (continuous_id.mul continuous_id).surjective tendsto_mul_self_at_top $
    by simp [order_bot.at_bot_eq]

lemma sqrt_le_sqrt_iff : sqrt x ≤ sqrt y ↔ x ≤ y :=
sqrt.le_iff_le

lemma sqrt_lt_sqrt_iff : sqrt x < sqrt y ↔ x < y :=
sqrt.lt_iff_lt

lemma sqrt_eq_iff_sq_eq : sqrt x = y ↔ y * y = x :=
sqrt.to_equiv.apply_eq_iff_eq_symm_apply.trans eq_comm

lemma sqrt_le_iff : sqrt x ≤ y ↔ x ≤ y * y :=
sqrt.to_galois_connection _ _

lemma le_sqrt_iff : x ≤ sqrt y ↔ x * x ≤ y :=
(sqrt.symm.to_galois_connection _ _).symm

@[simp] lemma sqrt_eq_zero : sqrt x = 0 ↔ x = 0 :=
sqrt_eq_iff_sq_eq.trans $ by rw [eq_comm, zero_mul]

@[simp] lemma sqrt_zero : sqrt 0 = 0 := sqrt_eq_zero.2 rfl

@[simp] lemma sqrt_one : sqrt 1 = 1 := sqrt_eq_iff_sq_eq.2 $ mul_one 1

@[simp] lemma mul_self_sqrt (x : ℝ≥0) : sqrt x * sqrt x = x :=
sqrt.symm_apply_apply x

@[simp] lemma sqrt_mul_self (x : ℝ≥0) : sqrt (x * x) = x := sqrt.apply_symm_apply x

@[simp] lemma sq_sqrt (x : ℝ≥0) : (sqrt x)^2 = x :=
by rw [sq, mul_self_sqrt x]

@[simp] lemma sqrt_sq (x : ℝ≥0) : sqrt (x^2) = x :=
by rw [sq, sqrt_mul_self x]

lemma sqrt_mul (x y : ℝ≥0) : sqrt (x * y) = sqrt x * sqrt y :=
by rw [sqrt_eq_iff_sq_eq, mul_mul_mul_comm, mul_self_sqrt, mul_self_sqrt]

/-- `nnreal.sqrt` as a `monoid_with_zero_hom`. -/
noncomputable def sqrt_hom : monoid_with_zero_hom ℝ≥0 ℝ≥0 := ⟨sqrt, sqrt_zero, sqrt_one, sqrt_mul⟩

lemma sqrt_inv (x : ℝ≥0) : sqrt (x⁻¹) = (sqrt x)⁻¹ := sqrt_hom.map_inv x

lemma sqrt_div (x y : ℝ≥0) : sqrt (x / y) = sqrt x / sqrt y := sqrt_hom.map_div x y

lemma continuous_sqrt : continuous sqrt := sqrt.continuous

end nnreal

namespace real

/-- An auxiliary sequence of rational numbers that converges to `real.sqrt (mk f)`.
Currently this sequence is not used in `mathlib`.  -/
def sqrt_aux (f : cau_seq ℚ abs) : ℕ → ℚ
| 0       := rat.mk_nat (f 0).num.to_nat.sqrt (f 0).denom.sqrt
| (n + 1) := let s := sqrt_aux n in max 0 $ (s + f (n+1) / s) / 2

theorem sqrt_aux_nonneg (f : cau_seq ℚ abs) : ∀ i : ℕ, 0 ≤ sqrt_aux f i
| 0       := by rw [sqrt_aux, rat.mk_nat_eq, rat.mk_eq_div];
  apply div_nonneg; exact int.cast_nonneg.2 (int.of_nat_nonneg _)
| (n + 1) := le_max_left _ _

/- TODO(Mario): finish the proof
theorem sqrt_aux_converges (f : cau_seq ℚ abs) : ∃ h x, 0 ≤ x ∧ x * x = max 0 (mk f) ∧
  mk ⟨sqrt_aux f, h⟩ = x :=
begin
  rcases sqrt_exists (le_max_left 0 (mk f)) with ⟨x, x0, hx⟩,
  suffices : ∃ h, mk ⟨sqrt_aux f, h⟩ = x,
  { exact this.imp (λ h e, ⟨x, x0, hx, e⟩) },
  apply of_near,

  suffices : ∃ δ > 0, ∀ i, abs (↑(sqrt_aux f i) - x) < δ / 2 ^ i,
  { rcases this with ⟨δ, δ0, hδ⟩,
    intros }
end -/

/-- The square root of a real number. This returns 0 for negative inputs. -/
@[pp_nodot] noncomputable def sqrt (x : ℝ) : ℝ :=
nnreal.sqrt (real.to_nnreal x)
/-quotient.lift_on x
  (λ f, mk ⟨sqrt_aux f, (sqrt_aux_converges f).fst⟩)
  (λ f g e, begin
    rcases sqrt_aux_converges f with ⟨hf, x, x0, xf, xs⟩,
    rcases sqrt_aux_converges g with ⟨hg, y, y0, yg, ys⟩,
    refine xs.trans (eq.trans _ ys.symm),
    rw [← @mul_self_inj_of_nonneg ℝ _ x y x0 y0, xf, yg],
    congr' 1, exact quotient.sound e
  end)-/

variables {x y : ℝ}

@[simp, norm_cast] lemma coe_sqrt {x : ℝ≥0} : (nnreal.sqrt x : ℝ) = real.sqrt x :=
by rw [real.sqrt, real.to_nnreal_coe]

@[continuity]
lemma continuous_sqrt : continuous sqrt :=
nnreal.continuous_coe.comp $ nnreal.sqrt.continuous.comp nnreal.continuous_of_real

theorem sqrt_eq_zero_of_nonpos (h : x ≤ 0) : sqrt x = 0 :=
by simp [sqrt, real.to_nnreal_eq_zero.2 h]

theorem sqrt_nonneg (x : ℝ) : 0 ≤ sqrt x := nnreal.coe_nonneg _

@[simp] theorem mul_self_sqrt (h : 0 ≤ x) : sqrt x * sqrt x = x :=
by rw [sqrt, ← nnreal.coe_mul, nnreal.mul_self_sqrt, real.coe_to_nnreal _ h]

@[simp] theorem sqrt_mul_self (h : 0 ≤ x) : sqrt (x * x) = x :=
(mul_self_inj_of_nonneg (sqrt_nonneg _) h).1 (mul_self_sqrt (mul_self_nonneg _))

theorem sqrt_eq_iff_mul_self_eq (hx : 0 ≤ x) (hy : 0 ≤ y) :
  sqrt x = y ↔ y * y = x :=
⟨λ h, by rw [← h, mul_self_sqrt hx], λ h, by rw [← h, sqrt_mul_self hy]⟩

@[simp] theorem sq_sqrt (h : 0 ≤ x) : (sqrt x)^2 = x :=
by rw [sq, mul_self_sqrt h]

@[simp] theorem sqrt_sq (h : 0 ≤ x) : sqrt (x ^ 2) = x :=
by rw [sq, sqrt_mul_self h]

theorem sqrt_eq_iff_sq_eq (hx : 0 ≤ x) (hy : 0 ≤ y) :
  sqrt x = y ↔ y ^ 2 = x :=
by rw [sq, sqrt_eq_iff_mul_self_eq hx hy]

theorem sqrt_mul_self_eq_abs (x : ℝ) : sqrt (x * x) = |x| :=
by rw [← abs_mul_abs_self x, sqrt_mul_self (abs_nonneg _)]

theorem sqrt_sq_eq_abs (x : ℝ) : sqrt (x ^ 2) = |x| :=
by rw [sq, sqrt_mul_self_eq_abs]

@[simp] theorem sqrt_zero : sqrt 0 = 0 := by simp [sqrt]

@[simp] theorem sqrt_one : sqrt 1 = 1 := by simp [sqrt]

@[simp] theorem sqrt_le_sqrt_iff (hy : 0 ≤ y) : sqrt x ≤ sqrt y ↔ x ≤ y :=
by rw [sqrt, sqrt, nnreal.coe_le_coe, nnreal.sqrt_le_sqrt_iff, real.to_nnreal_le_to_nnreal_iff hy]

@[simp] theorem sqrt_lt_sqrt_iff (hx : 0 ≤ x) : sqrt x < sqrt y ↔ x < y :=
lt_iff_lt_of_le_iff_le (sqrt_le_sqrt_iff hx)

theorem sqrt_lt_sqrt_iff_of_pos (hy : 0 < y) : sqrt x < sqrt y ↔ x < y :=
by rw [sqrt, sqrt, nnreal.coe_lt_coe, nnreal.sqrt_lt_sqrt_iff, to_nnreal_lt_to_nnreal_iff hy]

theorem sqrt_le_sqrt (h : x ≤ y) : sqrt x ≤ sqrt y :=
by { rw [sqrt, sqrt, nnreal.coe_le_coe, nnreal.sqrt_le_sqrt_iff], exact to_nnreal_le_to_nnreal h }

theorem sqrt_lt_sqrt (hx : 0 ≤ x) (h : x < y) : sqrt x < sqrt y :=
(sqrt_lt_sqrt_iff hx).2 h

theorem sqrt_le_iff : sqrt x ≤ y ↔ 0 ≤ y ∧ x ≤ y ^ 2 :=
begin
  rw [← and_iff_right_of_imp (λ h, (sqrt_nonneg x).trans h), and.congr_right_iff],
  exact sqrt_le_left
end

/- note: if you want to conclude `x ≤ sqrt y`, then use `le_sqrt_of_sq_le`.
   if you have `x > 0`, consider using `le_sqrt'` -/
theorem le_sqrt (hx : 0 ≤ x) (hy : 0 ≤ y) : x ≤ sqrt y ↔ x ^ 2 ≤ y :=
by rw [mul_self_le_mul_self_iff hx (sqrt_nonneg _), sq, mul_self_sqrt hy]

theorem le_sqrt' (hx : 0 < x) : x ≤ sqrt y ↔ x ^ 2 ≤ y :=
by { rw [sqrt, ← nnreal.coe_mk x hx.le, nnreal.coe_le_coe, nnreal.le_sqrt_iff,
  real.le_to_nnreal_iff_coe_le', sq, nnreal.coe_mul], exact mul_pos hx hx }

theorem abs_le_sqrt (h : x^2 ≤ y) : |x| ≤ sqrt y :=
by rw ← sqrt_sq_eq_abs; exact sqrt_le_sqrt h

theorem sq_le (h : 0 ≤ y) : x^2 ≤ y ↔ -sqrt y ≤ x ∧ x ≤ sqrt y :=
begin
  split,
  { simpa only [abs_le] using abs_le_sqrt },
  { rw [← abs_le, ← sq_abs],
    exact (le_sqrt (abs_nonneg x) h).mp },
end

theorem neg_sqrt_le_of_sq_le (h : x^2 ≤ y) : -sqrt y ≤ x :=
((sq_le ((sq_nonneg x).trans h)).mp h).1

theorem le_sqrt_of_sq_le (h : x^2 ≤ y) : x ≤ sqrt y :=
((sq_le ((sq_nonneg x).trans h)).mp h).2

@[simp] theorem sqrt_inj (hx : 0 ≤ x) (hy : 0 ≤ y) : sqrt x = sqrt y ↔ x = y :=
by simp [le_antisymm_iff, hx, hy]

@[simp] theorem sqrt_eq_zero (h : 0 ≤ x) : sqrt x = 0 ↔ x = 0 :=
by simpa using sqrt_inj h (le_refl _)

theorem sqrt_eq_zero' : sqrt x = 0 ↔ x ≤ 0 :=
by rw [sqrt, nnreal.coe_eq_zero, nnreal.sqrt_eq_zero, real.to_nnreal_eq_zero]

theorem sqrt_ne_zero (h : 0 ≤ x) : sqrt x ≠ 0 ↔ x ≠ 0 :=
by rw [not_iff_not, sqrt_eq_zero h]

theorem sqrt_ne_zero' : sqrt x ≠ 0 ↔ 0 < x :=
by rw [← not_le, not_iff_not, sqrt_eq_zero']

@[simp] theorem sqrt_pos : 0 < sqrt x ↔ 0 < x :=
lt_iff_lt_of_le_iff_le (iff.trans
  (by simp [le_antisymm_iff, sqrt_nonneg]) sqrt_eq_zero')

@[simp] theorem sqrt_mul (hx : 0 ≤ x) (y : ℝ) : sqrt (x * y) = sqrt x * sqrt y :=
by simp_rw [sqrt, ← nnreal.coe_mul, nnreal.coe_eq, real.to_nnreal_mul hx, nnreal.sqrt_mul]

@[simp] theorem sqrt_mul' (x) {y : ℝ} (hy : 0 ≤ y) : sqrt (x * y) = sqrt x * sqrt y :=
by rw [mul_comm, sqrt_mul hy, mul_comm]

@[simp] theorem sqrt_inv (x : ℝ) : sqrt x⁻¹ = (sqrt x)⁻¹ :=
by rw [sqrt, real.to_nnreal_inv, nnreal.sqrt_inv, nnreal.coe_inv, sqrt]

@[simp] theorem sqrt_div (hx : 0 ≤ x) (y : ℝ) : sqrt (x / y) = sqrt x / sqrt y :=
by rw [division_def, sqrt_mul hx, sqrt_inv, division_def]

@[simp] theorem div_sqrt : x / sqrt x = sqrt x :=
begin
  cases le_or_lt x 0,
  { rw [sqrt_eq_zero'.mpr h, div_zero] },
  { rw [div_eq_iff (sqrt_ne_zero'.mpr h), mul_self_sqrt h.le] },
end

theorem sqrt_div_self' : sqrt x / x = 1 / sqrt x :=
by rw [←div_sqrt, one_div_div, div_sqrt]

theorem sqrt_div_self : sqrt x / x = (sqrt x)⁻¹ :=
by rw [sqrt_div_self', one_div]

theorem lt_sqrt (hx : 0 ≤ x) (hy : 0 ≤ y) : x < sqrt y ↔ x ^ 2 < y :=
by rw [mul_self_lt_mul_self_iff hx (sqrt_nonneg y), sq, mul_self_sqrt hy]

theorem sq_lt : x^2 < y ↔ -sqrt y < x ∧ x < sqrt y :=
begin
  split,
  { simpa only [← sqrt_lt_sqrt_iff (sq_nonneg x), sqrt_sq_eq_abs] using abs_lt.mp },
  { rw [← abs_lt, ← sq_abs],
    exact λ h, (lt_sqrt (abs_nonneg x) (sqrt_pos.mp (lt_of_le_of_lt (abs_nonneg x) h)).le).mp h },
end

theorem neg_sqrt_lt_of_sq_lt (h : x^2 < y) : -sqrt y < x := (sq_lt.mp h).1

theorem lt_sqrt_of_sq_lt (h : x^2 < y) : x < sqrt y := (sq_lt.mp h).2

end real

open real

variables {α : Type*}

lemma filter.tendsto.sqrt {f : α → ℝ} {l : filter α} {x : ℝ} (h : tendsto f l (𝓝 x)) :
  tendsto (λ x, sqrt (f x)) l (𝓝 (sqrt x)) :=
(continuous_sqrt.tendsto _).comp h

variables [topological_space α] {f : α → ℝ} {s : set α} {x : α}

lemma continuous_within_at.sqrt (h : continuous_within_at f s x) :
  continuous_within_at (λ x, sqrt (f x)) s x :=
h.sqrt

lemma continuous_at.sqrt (h : continuous_at f x) : continuous_at (λ x, sqrt (f x)) x := h.sqrt

lemma continuous_on.sqrt (h : continuous_on f s) : continuous_on (λ x, sqrt (f x)) s :=
λ x hx, (h x hx).sqrt

@[continuity]
lemma continuous.sqrt (h : continuous f) : continuous (λ x, sqrt (f x)) := continuous_sqrt.comp h


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

namespace Mathlib

/-!
# Divisibility properties of binomial coefficients
-/

namespace nat

namespace prime

theorem dvd_choose_add {p : ℕ} {a : ℕ} {b : ℕ} (hap : a < p) (hbp : b < p) (h : p ≤ a + b) (hp : prime p) : p ∣ choose (a + b) a := sorry

theorem dvd_choose_self {p : ℕ} {k : ℕ} (hk : 0 < k) (hkp : k < p) (hp : prime p) : p ∣ choose p k := sorry


/-
Copyright (c) 2021 Ashvni Narayanan. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Ashvni Narayanan
-/
import tendsto_zero_of_sum_even_char
--import p_adic_L_function_def
-- import general_bernoulli_number.basic
-- import topology.algebra.nonarchimedean.bases
-- import chinese_remainder_units
/-!
# A convergence property regarding (ℤ/dp^n ℤ)ˣ
This file proves the third sum in the proof of Theorem 12.2 in Introduction to Cyclotomic Fields, Washington.
It gives a convergence property relating to generalized Bernoulli numbers.

# Main Theorems
 * `W`

## Tags
p-adic, L-function, Bernoulli measure, Dirichlet character
-/
open_locale big_operators
local attribute [instance] zmod.topological_space

open filter dirichlet_character
open_locale topological_space

open_locale big_operators

variables {p : ℕ} [fact (nat.prime p)] {d : ℕ} [fact (0 < d)] {R : Type*} [normed_comm_ring R] (m : ℕ)
(hd : d.gcd p = 1) (χ : dirichlet_character R (d*(p^m))) {c : ℕ} (hc : c.gcd p = 1)
(hc' : c.gcd d = 1) (na : ∀ (n : ℕ) (f : ℕ → R), ∥ ∑ (i : ℕ) in finset.range n, f i∥ ≤ ⨆ (i : zmod n), ∥f i.val∥)
variables (p d R) [complete_space R] [char_zero R]
open continuous_map

variables [normed_algebra ℚ_[p] R] [fact (0 < m)]
variable [fact (0 < d)]
open dirichlet_character
variable (hd)
open zmod
variable (c)
variables (hc) (hc')

lemma W [no_zero_divisors R] [algebra ℚ R] [norm_one_class R] (hd : d.coprime p) (hp : 2 < p)
  (na' : ∀ (n : ℕ) (f : (zmod n)ˣ → R), ∥∑ i : (zmod n)ˣ, f i∥ ≤ ⨆ (i : (zmod n)ˣ), ∥f i∥)
  (na : ∀ (n : ℕ) (f : ℕ → R), ∥∑ i in finset.range n, f i∥ ≤ ⨆ (i : zmod n), ∥f i.val∥)
  (n : ℕ) (hn : 1 < n) (hχ : χ.is_even) :
  filter.tendsto (λ j : ℕ, ∑ (x : (zmod (d * p ^ j))ˣ),
  ((asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R^n)) x : R) *
  ((((x : zmod (d * p^j))).val)^(n - 1) : R)) • (algebra_map ℚ R) ((↑c - 1) / 2)) filter.at_top (nhds 0) :=
begin
  simp_rw [smul_eq_mul, ← finset.sum_mul],
  conv { congr, skip, skip, congr, rw ← zero_mul ((algebra_map ℚ R) ((↑c - 1) / 2)), },
  apply tendsto.mul_const,
  simp_rw zmod.nat_cast_val, simp_rw ← coe_coe,
  convert (tendsto_congr' _).1 (sum_even_character_tendsto_zero_of_units na' hn hχ hp),
  rw eventually_eq, rw eventually_at_top,
  refine ⟨m, λ y hy, _⟩,
  apply finset.sum_congr rfl,
  intros z hz,
  conv_lhs { congr, rw coe_coe, rw ← zmod.nat_cast_val, },
  rw zmod.nat_cast_val, rw ← coe_coe,
end


import .form

variables {α : Type}

namespace form

@[fol] def push_neg : form → form
| ⊤*        := ⊥*
| ⊥*        := ⊤*
| (k ** ts) := ¬* (k ** ts)
| (p ∨* q)  := push_neg p ∧* push_neg q
| (p ∧* q)  := push_neg p ∨* push_neg q
| (∀* p)    := ∃* (push_neg p)
| (∃* p)    := ∀* (push_neg p)
| (¬* p)    := p

@[fol] def nnf : form → form
| ⊤*        := ⊤*
| ⊥*        := ⊥*
| (k ** ts) := (k ** ts)
| (¬* p)    := push_neg (nnf p)
| (p ∨* q) := (nnf p) ∨* (nnf q)
| (p ∧* q) := (nnf p) ∧* (nnf q)
| (∀* p) := ∀* (nnf p)
| (∃* p) := ∃* (nnf p)

open tactic

local attribute [instance] classical.prop_decidable

lemma push_neg_equiv : ∀ p, equiv α (push_neg p) (¬* p) :=
begin
  induce `[intros M v, try {simp_fol}], simp, simp, simp,
  { rw not_or_distrib, apply and_iff_and, apply ihp, apply ihq },
  { rw not_and_distrib, apply or_iff_or, apply ihp, apply ihq },
  { rw not_forall, apply exists_iff_exists, intro x, apply ih },
  { rw not_exists, apply forall_iff_forall, intro x, apply ih }
end

lemma nnf_equiv : ∀ p, equiv α (nnf p) p :=
begin
  induce `[intros M v, try {simp_fol}, try {trivial}],
  { apply iff.trans, apply push_neg_equiv,
    apply not_iff_not_of_iff, apply ih },
  { apply or_iff_or, apply ihp, apply ihq },
  { apply and_iff_and, apply ihp, apply ihq },
  { apply forall_iff_forall, intro x, apply ih },
  { apply exists_iff_exists, intro x, apply ih }
end

end form