/-
Copyright (c) 2021 Ivan Sadofschi Costa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Ivan Sadofschi Costa.
-/
import data.mv_polynomial.basic
import data.mv_polynomial.variables
import algebra.algebra.basic
import data.mv_polynomial.comm_ring
import data.nat.basic
import degree

universes u v

variables {α : Type v}

open set function finsupp add_monoid_algebra

open_locale big_operators

namespace mv_polynomial

variables {R : Type*} {σ : Type*}

/-

  New definitions: monomial_degree, max_degree_monomial, dominant_monomial

-/

-- this def is also given in flt-regular
def monomial_degree {s : Type*} (t : s →₀ ℕ) : ℕ := t.sum (λ _ e, e)

lemma nat.term_le_sum {s : finset α } (f : α → ℕ){j : α} (hj : j ∈ s) : f j ≤ s.sum f :=
begin
  revert j,
  apply finset.cons_induction_on s,
  { simp },
  { clear s,
    intros x s hx hj j hc,
    rw finset.sum_cons,
    simp only [finset.mem_cons] at hc,
    cases hc with j_eq_x j_in_s,
    { simp [j_eq_x] },
    { simp [(hj j_in_s).trans] } },
end

lemma le_monomial_degree {s : Type*} (t : s →₀ ℕ) (j : s) : t j ≤ monomial_degree t :=
begin
  by_cases c : j ∈ t.support,
  { exact nat.term_le_sum _ c },
  { simp only [not_not, finsupp.mem_support_iff] at c,
    simp [c] },
end

-- this holds for [ordered_add_comm_monoid N] if 0 ≤ n forall n ∈ N
lemma finsupp.support_subset_of_le {s : Type*} {f g : s →₀ ℕ} (h : f ≤ g) :
f.support ⊆ g.support :=
begin
  simp only [has_subset.subset, finsupp.mem_support_iff, ne.def],
  intros a ha,
  by_contra c,
  simpa [c] using lt_of_lt_of_le (nat.pos_of_ne_zero ha) (h a),
end

-- this holds for [ordered_add_comm_monoid N] (with a different proof)
lemma finsupp.sum_le_sum {s : Type*} {f g : s →₀ ℕ} (h : f ≤ g) :
f.sum (λ x y , y) ≤ g.sum (λ x y , y) :=
begin
  rw [sum_of_support_subset f (finsupp.support_subset_of_le h) (λ x y, y) (by simp), finsupp.sum],
  apply finset.sum_le_sum,
  intros i hi,
  simp only [h i],
end

lemma monomial_degree_le_of_le {σ : Type*} {m m' : σ →₀ ℕ} (h : m' ≤ m) :
  monomial_degree m' ≤ monomial_degree m :=
by simpa [monomial_degree] using finsupp.sum_le_sum h

lemma monomial_degree_add {σ : Type*} (m m' : σ →₀ ℕ) :
  monomial_degree (m + m') = monomial_degree m + monomial_degree m' :=
by simp [monomial_degree, sum_add_index]

lemma monomial_degree_sub {σ : Type*} {m m' : σ →₀ ℕ} (h : m' ≤ m) :
  monomial_degree (m - m') = monomial_degree m - monomial_degree m' :=
begin
  rw [eq_tsub_iff_add_eq_of_le (monomial_degree_le_of_le h), ← monomial_degree_add],
  congr,
  ext a,
  rw le_def at h,
  simp only [pi.add_apply, coe_tsub, coe_add, pi.sub_apply, nat.sub_add_cancel (h a)],
end

-- is this on mathlib? name?
lemma nat_lemma_2 { a b c : ℕ} (h' : c - a ≤ b):  c ≤ b + a :=
begin
  by_cases h : a ≤ c,
  { zify,
    rw [←sub_le_iff_le_add, ←int.coe_nat_sub],
    { rw int.coe_nat_le,
      exact h' },
    { exact h } },
  { linarith }
end

lemma nat_lemma_1 {a b c : ℕ} (h : c ≤ b) (h' : b - c ≤ a) : a - (b - c) = a + c - b :=
begin
  zify,
  rw int.coe_nat_sub,
  { rw [int.coe_nat_add, ←sub_add, sub_add_eq_add_sub] },
  { apply nat_lemma_2 h' }
end

lemma monomial_lemma_2 { σ : Type*} { m m' a: σ →₀ ℕ} (c : a - m ≤ m') :  a ≤ m' + m :=
begin
  intro i,
  simp only [pi.add_apply, coe_add],
  apply nat_lemma_2 (c i),
end

lemma monomial_lemma_3 { σ : Type*} { m m' : σ →₀ ℕ} : m ≤ (m - m') + m' :=
monomial_lemma_2 (le_refl _)

lemma monomial_lemma_1 { σ : Type*} { m m' a: σ →₀ ℕ}
  (h_m_le_a : m ≤ a) (c : a - m ≤ m'):  m' - (a - m) = m' + m - a :=
begin
  ext i,
  simp only [pi.add_apply, coe_tsub, coe_add, pi.sub_apply],
  apply nat_lemma_1 (h_m_le_a i) (c i),
end

lemma monomial_degree_sub_le {σ : Type*} (m m' : σ →₀ ℕ) :
  monomial_degree m - monomial_degree m' ≤ monomial_degree (m - m') :=
begin
  simp only [tsub_le_iff_right, ←monomial_degree_add],
  apply monomial_degree_le_of_le,
  apply monomial_lemma_3,
end

lemma monomial_degree_zero_iff {σ : Type*} {m : σ →₀ ℕ} : monomial_degree m = 0 ↔ m = 0 :=
begin
  split,
  { intro h,
    ext i,
    apply nat.eq_zero_of_le_zero _,
    apply (le_monomial_degree m i).trans,
    rw h, },
  { intro h,
    simp [h, monomial_degree], },
end

-- This depends on flt-regular. Use total_degree_monomial once its merged into mathlib
lemma total_degree_monomial_eq_monomial_degree {σ R : Type*} [comm_semiring R] {m : σ →₀ ℕ} {a : R}
  (h : a ≠ 0): total_degree (monomial m a) = monomial_degree m :=
by convert total_degree_monomial m h

-- Use monomial instead of single!
lemma monomial_degree_single {σ : Type*} {j : σ} {d : ℕ} : monomial_degree (single j d) = d :=
by simp [monomial_degree]

lemma eq_single_of_monomial_degree_eq {σ : Type*}
(m :  σ →₀ ℕ) (i : σ) : monomial_degree m = m i → m = single i (m i) :=
begin
  intro h,
  rw monomial_degree at h,
  have h0 : single i (m i) ≤ m := by simp,
  suffices y : ∀ j ∈ m.support, m j ≤ single i (m i) j,
  { ext,
    by_cases c : a ∈ m.support,
    { exact le_antisymm (y a c) (h0 a) },
    { by_cases c' : i = a,
      { simp only [c', single_eq_same] },
      { simpa [c', single_eq_of_ne, ne.def, not_false_iff] using c, } } },
  by_contra c,
  simp only [not_le, not_forall] at c,
  suffices x : m.sum (λ (_x : σ) (e : ℕ), e) < m.support.sum ⇑m,
  by simpa [finsupp.sum] using x,
  simpa only [h, ←sum_of_support_subset _ (finsupp.support_subset_of_le h0) (λ x y, y) (by simp),
              sum_single_index]
    using @finset.sum_lt_sum σ ℕ _ (single i (m i)) m m.support (λ i h, h0 i) c,
end

lemma monomial_degree_le_iff_eq_single {σ : Type*}
  (m :  σ →₀ ℕ) (i : σ) : monomial_degree m ≤ m i ↔ m = single i (m i) :=
begin
  apply iff.intro,
  { intro h,
    exact eq_single_of_monomial_degree_eq m i (le_antisymm (le_monomial_degree m i) h).symm },
  { intro h,
    rw h,
    simp [monomial_degree_single] },
end

lemma monomial_degree_le_total_degree {σ R : Type*}[comm_semiring R] {m : σ →₀ ℕ} {f : mv_polynomial σ R}
  (h : m ∈ f.support) : monomial_degree m ≤ total_degree f :=
by simp [total_degree, monomial_degree, finset.le_sup h]

lemma le_total_degree {R σ : Type*} [comm_semiring R] {i: σ} {p : mv_polynomial σ R}
  {m: σ →₀ ℕ} (h_m : m ∈ p.support) : m i ≤ p.total_degree
:= (le_monomial_degree m i).trans $ monomial_degree_le_total_degree h_m

lemma coeff_zero_of_degree_greater_than_total_degree {R : Type*} [comm_semiring R]  (t : σ →₀ ℕ)
  (f : mv_polynomial σ R) : monomial_degree t > total_degree f → coeff t f = 0 :=
begin
  intro h,
  by_cases c: t ∈ f.support,
  { exfalso,
    simpa using lt_of_le_of_lt (monomial_degree_le_total_degree c) h },
  { simp only [not_not, mem_support_iff] at c,
    exact c },
end

def max_degree_monomial {R : Type*} [comm_semiring R] (t : σ →₀ ℕ) (f : mv_polynomial σ R) : Prop
:= t ∈ f.support ∧ monomial_degree t = total_degree f

-- this uses a lemma from flt-regular
lemma support_nonempty_iff {R σ: Type*} [comm_semiring R]  {f : mv_polynomial σ R} :
  f.support.nonempty ↔ f ≠ 0 :=
begin
  rw iff_not_comm,
  simp only [support_eq_empty, finset.not_nonempty_iff_eq_empty],
end

-- see also flt-regular's exists_coeff_ne_zero_total_degree
lemma exists_max_degree_monomial {R : Type*} [comm_semiring R]
  {f : mv_polynomial σ R} (h : f ≠ 0) : ∃ t, max_degree_monomial t f :=
begin
  simp only [max_degree_monomial, total_degree, monomial_degree],
  cases finset.exists_mem_eq_sup (f.support) (support_nonempty_iff.2 h)
    (λ (s : σ →₀ ℕ), s.sum (λ (n : σ) (e : ℕ), e)) with m hm,
  exact ⟨m, ⟨hm.1, hm.2.symm⟩⟩,
end

lemma eq_and_eq_of_le_add_le_eq {a1 a2 b1 b2 : ℕ} (h1: a1 ≤ b1) (h2 : a2 ≤ b2)
  (h : a1 + a2 = b1 + b2) : a1 = b1 ∧ a2 = b2 :=
begin
  apply and.intro,
  { by_cases c : a1 < b1,
    { simpa [h] using add_lt_add_of_lt_of_le c h2 },
    { exact le_antisymm h1 (not_lt.1 c) } },
  { by_cases c : a2 < b2,
    { simpa [h] using add_lt_add_of_le_of_lt h1 c },
    { exact le_antisymm h2 (not_lt.1 c) } },
end

lemma max_degree_monomial_mul {σ R : Type*}[comm_ring R][is_domain R] {f g : mv_polynomial σ R}
  {m : σ →₀ ℕ} (hf : f ≠ 0) (hg : g ≠ 0) (h : max_degree_monomial m (f * g)) :
  ∃ mf mg, max_degree_monomial mf f ∧ max_degree_monomial mg g ∧ mf + mg = m :=
begin
  rw max_degree_monomial at h,
  rcases support_mul'' h.1 with ⟨mf, ⟨mg, h'⟩⟩,
  use mf,
  use mg,
  suffices x : monomial_degree mf = f.total_degree ∧ monomial_degree mg = g.total_degree,
  { exact ⟨ ⟨h'.1, x.1⟩ , ⟨h'.2.1, x.2⟩, h'.2.2 ⟩, },
  apply eq_and_eq_of_le_add_le_eq (monomial_degree_le_total_degree h'.1)
    (monomial_degree_le_total_degree h'.2.1),
  simpa [ h.2, h'.2.2, ←monomial_degree_add] using total_degree_mul' hf hg,
end

def dominant_monomial {R : Type*} [comm_semiring R]  (t : σ →₀ ℕ) (f : mv_polynomial σ R) : Prop :=
max_degree_monomial t f ∧ (∀ t' : σ →₀ ℕ, max_degree_monomial t' f → t' = t)

lemma dominant_monomial_of_factor_is_factor_of_max_degree_monomial
  {R : Type*} [comm_ring R] [is_domain R] (S : finset R) (t t' : σ →₀ ℕ )
  (f g : mv_polynomial σ R) (hfg : max_degree_monomial t (f*g))
  (hf : f ≠ 0) (hg : dominant_monomial t' g) : t' ≤ t :=
begin
  by_cases c : g = 0,
  { rw [c, dominant_monomial, max_degree_monomial] at hg,
    simpa using hg.1.1 },
  { rcases max_degree_monomial_mul hf c hfg with ⟨mf, ⟨mg,h⟩⟩,
    rw dominant_monomial at hg,
    simp [←hg.2 mg h.2.1, ← h.2.2] },
end

-- near total_degree_eq
lemma total_degree_eq' {R σ : Type*} [comm_semiring R] (p : mv_polynomial σ R) :
  p.total_degree = p.support.sup (monomial_degree) :=
begin
  rw [total_degree],
  congr, funext m,
end

lemma total_degree_lt_iff {R σ : Type*} [comm_semiring R] {f : mv_polynomial σ R} {d : ℕ} (h : 0 < d) :
  total_degree f < d ↔ ∀ m : σ →₀ ℕ, m ∈ f.support → monomial_degree m < d :=
by rwa [total_degree_eq', finset.sup_lt_iff]

lemma total_degree_sub_lt {R σ : Type*} [comm_ring R] [is_domain R]
{f g : mv_polynomial σ R} {k : ℕ} (h : 0 < k)
  (hf : ∀ (m : σ →₀ ℕ), m ∈ f.support → (k ≤ monomial_degree m) → coeff m f = coeff m g)
  (hg : ∀ (m : σ →₀ ℕ), m ∈ g.support → (k ≤ monomial_degree m) → coeff m f = coeff m g) :
  total_degree (f - g) < k :=
begin
  rw total_degree_lt_iff h,
  intros m hm,
  by_contra hc,
  simp only [not_lt] at hc,
  have h' := support_sub σ f g hm,
  simp only [mem_support_iff, ne.def, coeff_sub, sub_eq_zero] at hm,
  simp [mem_union] at h',
  cases h' with cf cg,
  { exact hm (hf m (by simpa using cf) hc) },
  { exact hm (hg m (by simpa using cg) hc) }
end

lemma max_degree_monomial_iff_of_eq_degree' {R σ : Type*} [comm_semiring R] (p : mv_polynomial σ R)
 {m m' : σ →₀ ℕ} (hm' : m' ∈ p.support) (h : monomial_degree m = monomial_degree m' ) :
 max_degree_monomial m p → max_degree_monomial m' p :=
begin
  intro h',
  split,
  { exact hm' },
  { rw ← h,
    exact h'.2 }
 end

lemma max_degree_monomial_iff_of_eq_degree {R σ : Type*} [comm_semiring R] (p : mv_polynomial σ R)
 {m m' : σ →₀ ℕ} (hm : m ∈ p.support) (hm' : m' ∈ p.support) (h : monomial_degree m = monomial_degree m') :
 max_degree_monomial m p ↔ max_degree_monomial m' p :=
begin
  split,
  { apply max_degree_monomial_iff_of_eq_degree',
    { exact hm' },
    { exact h } },
  { apply max_degree_monomial_iff_of_eq_degree',
    { exact hm },
    { exact h.symm } }
 end

lemma max_degree_monomial_iff {R σ : Type*} [comm_ring R]
{f : mv_polynomial σ R} { m : σ →₀ ℕ} :
max_degree_monomial m f ↔ m ∈ f.support ∧ ∀ m' ∈ f.support,
  monomial_degree m' ≤ monomial_degree m :=
begin
  split,
  { intro h,
    split,
    { exact h.1 },
    { intros m' hm',
      have t := h.2,
      rw total_degree_eq' at t,
      rw t,
      apply finset.le_sup hm' } },
  { intro h,
    split,
    { exact h.1 },
    { rw total_degree_eq',
      rw ← finset.sup'_eq_sup,
      { apply le_antisymm,
        { apply finset.le_sup',
          exact h.1 },
        { apply finset.sup'_le,
          exact h.2 } } } },
end

lemma dominant_monomial_iff {R σ : Type*} [comm_ring R]  {f : mv_polynomial σ R} { m : σ →₀ ℕ} :
  dominant_monomial m f → ∀ m' ∈ f.support, monomial_degree m' ≤ monomial_degree m
    ∧ (monomial_degree m' = monomial_degree m → m' = m) :=
begin
  intros h m' hm',
  split,
  { apply (max_degree_monomial_iff.1 h.1).2,
    exact hm' },
  { intro h1,
    apply h.2,
    rw max_degree_monomial_iff_of_eq_degree f hm' h.1.1 h1,
    exact h.1}
end

lemma induction_on_total_degree {R σ : Type*} [comm_semiring R] {M : mv_polynomial σ R → Prop}
 (p : mv_polynomial σ R) (h : ∀ (p' : mv_polynomial σ R),
   (∀ q,  total_degree q < total_degree p' → M q) → M p') : M p :=
begin
  let P : ℕ → Prop := λ n, ∀ p : mv_polynomial σ R, total_degree p ≤ n → M p,
  suffices l' : ∀ n, P n,
  { apply l' (total_degree p),
    refl },
  { intro n,
    induction n with d hd,
    { intros p hp,
      apply h p,
      intros q hq,
      simpa using lt_of_lt_of_le hq hp },
    { intros p hp,
      apply h p,
      intros q hq,
      exact hd q (nat.le_of_lt_succ (lt_of_lt_of_le hq hp)) } },
end

local attribute [instance] classical.prop_decidable

lemma induction_on_new {R σ : Type*} [comm_semiring R] {M : mv_polynomial σ R → Prop} (p : mv_polynomial σ R)
  (h_add_weak : ∀ (a : σ →₀ ℕ) (b : R) (f : (σ →₀ ℕ) →₀ R),
    a ∉ f.support → b ≠ 0 → M f → M (monomial a b) → M (monomial a b + f))
  (h_monomial : ∀ m : σ →₀ ℕ, ∀ b : R,
    (∀ p : mv_polynomial σ R, total_degree p < monomial_degree m → M p) → M (monomial m b)) : M p :=
  begin
    apply induction_on_total_degree,
    { intros p,
      apply induction_on''' p,
      { intros a h,
        apply h_monomial,
        intros x h2,
        simpa [monomial_degree] using h2 },
      { intros a b f ha hb hMf h,
        apply h_add_weak a b f ha hb (hMf _),
        { apply h_monomial,
          intros p' hp',
          suffices h' : p'.total_degree < (monomial a b).total_degree,
          { apply (h p') ∘ (lt_of_lt_of_le h'),
            rw total_degree_add_eq_of_disjoint_support,
            { simp only [le_refl, true_or, le_max_iff] },
            { simpa only [ support_monomial, hb, not_not, mem_support_iff, if_false,
                          finset.disjoint_singleton_left, not_not, finsupp.mem_support_iff]
                using ha} },
          rw total_degree_monomial_eq_monomial_degree,
          { exact hp' },
          { exact hb } },
        { intros q hq,
          apply (h q) ∘ (lt_of_lt_of_le hq),
          rw total_degree_add_eq_of_disjoint_support,
          { simp only [le_refl, or_true, le_max_iff] },
          { simpa only [support_monomial, hb, not_not, mem_support_iff, if_false,
                        finset.disjoint_singleton_left, not_not, finsupp.mem_support_iff]
             using ha} } } },
  end

end mv_polynomial


import data.real.basic analysis.special_functions.pow

namespace IMOSL
namespace extra

/-!
# Natural root in ℝ

We define the `n`th root in ℝ where `n` is a natural number.
For `n` even, we define the `n`th root of a negative real number `x` as `-(x^(1/n))`.
It has useful properties especially when `n` is odd.

TODO:
1. Prove properties when `n` is a `bit0`.
2. Prove more properties in general.
-/

open real function
open_locale classical

namespace real

/-- odd-function `n`th root in ℝ -/
noncomputable def nat_root (n : ℕ) (x : ℝ) := ite (0 ≤ x) (x ^ (n : ℝ)⁻¹) (- ((-x) ^ (n : ℝ)⁻¹))

lemma nat_root_pow_self_bit1 (n : ℕ) (x : ℝ) : nat_root (bit1 n) (x ^ (bit1 n)) = x :=
begin
  simp only [nat_root, pow_bit1_nonneg_iff],
  by_cases h : 0 ≤ x,
  rw if_pos h; exact pow_nat_rpow_nat_inv h (nat.bit1_ne_zero n),
  rw [if_neg h, ← neg_pow_bit1, pow_nat_rpow_nat_inv _ (nat.bit1_ne_zero n), neg_neg],
  rw [← lt_iff_not_le, ← neg_pos] at h,
  exact le_of_lt h
end

lemma pow_nat_root_self_bit1 (n : ℕ) (x : ℝ) : (nat_root (bit1 n) x) ^ (bit1 n) = x :=
begin
  dsimp only [nat_root],
  by_cases h : 0 ≤ x,
  rw if_pos h; exact rpow_nat_inv_pow_nat h (nat.bit1_ne_zero n),
  rw [if_neg h, neg_pow_bit1, rpow_nat_inv_pow_nat _ (nat.bit1_ne_zero n), neg_neg],
  rw [← lt_iff_not_le, ← neg_pos] at h,
  exact le_of_lt h
end

lemma pow_bit1_inj (n : ℕ) : injective (λ x : ℝ, x ^ (bit1 n)) :=
begin
  intros x y h; simp only [] at h,
  rw [← nat_root_pow_self_bit1 n x, h, nat_root_pow_self_bit1]
end

lemma nat_root_bit1_inj (n : ℕ) : injective (nat_root (bit1 n)) :=
  λ x y h, by rw [← pow_nat_root_self_bit1 n x, h, pow_nat_root_self_bit1]

lemma nat_root_bit1_mul (n : ℕ) (x y : ℝ) :
    nat_root (bit1 n) (x * y) = nat_root (bit1 n) x * nat_root (bit1 n) y :=
  pow_bit1_inj n (by simp only [mul_pow, pow_nat_root_self_bit1])

lemma nat_root_bit1_inv (n : ℕ) (x : ℝ) : nat_root (bit1 n) x⁻¹ = (nat_root (bit1 n) x)⁻¹ :=
  pow_bit1_inj n (by simp only [inv_pow, pow_nat_root_self_bit1])

lemma nat_root_bit1_one (n : ℕ) : nat_root (bit1 n) 1 = 1 :=
  pow_bit1_inj n (by simp only [pow_nat_root_self_bit1, one_pow])

lemma nat_root_bit1_zero (n : ℕ) : nat_root (bit1 n) 0 = 0 :=
  pow_bit1_inj n (by simp only [pow_nat_root_self_bit1, zero_pow (nat.zero_lt_bit1 n)])

lemma nat_root_bit1_neg (n : ℕ) (x : ℝ) : nat_root (bit1 n) (-x) = -(nat_root (bit1 n) x) :=
  pow_bit1_inj n (by simp only [pow_nat_root_self_bit1, neg_pow_bit1])

lemma nat_root_bit1_pow (n : ℕ) (x : ℝ) (k : ℕ) :
  nat_root (bit1 n) (x ^ k) = nat_root (bit1 n) x ^ k :=
begin
  induction k with k k_ih,
  rw [pow_zero, pow_zero, nat_root_bit1_one],
  rw [pow_succ, nat_root_bit1_mul, k_ih, pow_succ]
end

lemma nat_root_bit1_left_mul (n : ℕ) (x y : ℝ) :
    x * nat_root (bit1 n) y = nat_root (bit1 n) (x ^ (bit1 n) * y) :=
  by rw [nat_root_bit1_mul, nat_root_pow_self_bit1]

lemma nat_root_bit1_ne_zero (n : ℕ) {x : ℝ} (h : x ≠ 0) : nat_root (bit1 n) x ≠ 0 :=
  by contrapose! h; apply nat_root_bit1_inj; rw [h, nat_root_bit1_zero]

lemma nat_root_bit1_eq_one_iff (n : ℕ) (x : ℝ) : nat_root (bit1 n) x = 1 ↔ x = 1 :=
  ⟨λ h, nat_root_bit1_inj n (by rw [h, nat_root_bit1_one]), λ h, by rw [h, nat_root_bit1_one]⟩

lemma pow_bit1_eq_one_iff (n : ℕ) (x : ℝ) : x ^ (bit1 n) = 1 ↔ x = 1 :=
  ⟨λ h, pow_bit1_inj n (by simp only [h, one_pow]), λ h, by rw [h, one_pow]⟩

end real

end extra
end IMOSL


import data.set.finite group_theory.coset

universes u v
variables {α : Type u} {β : Type v} [group α]

class is_group_action (f : α → β → β) : Prop :=
(one : ∀ a : β, f (1 : α) a = a)
(mul : ∀ (x y : α) (a : β), f (x * y) a = f x (f y a))

namespace is_group_action

variables (f : α → β → β) [is_group_action f]

@[simp] lemma one_apply (a : β) : f 1 a = a := is_group_action.one f a

lemma mul_apply (x y : α) (a : β) : f (x * y) a = f x (f y a) := is_group_action.mul _ _ _ _

lemma bijective (g : α) : function.bijective (f g) :=
function.bijective_iff_has_inverse.2 ⟨f (g⁻¹),
  λ a, by rw [← mul_apply f, inv_mul_self, one_apply f],
  λ a, by rw [← mul_apply f, mul_inv_self, one_apply f]⟩

def orbit (a : β) := set.range (λ x : α, f x a)

lemma mem_orbit_iff {f : α → β → β} [is_group_action f] {a b : β} :
  b ∈ orbit f a ↔ ∃ x : α, f x a = b :=
iff.rfl

@[simp] lemma mem_orbit (a : β) (x : α) :
  f x a ∈ orbit f a :=
⟨x, rfl⟩

@[simp] lemma mem_orbit_self (a : β) :
  a ∈ orbit f a :=
⟨1, show f 1 a = a, by simp [one_apply f]⟩

lemma orbit_eq_iff {f : α → β → β} [is_group_action f] {a b : β} :
  a ∈ orbit f b ↔ orbit f a = orbit f b :=
⟨λ ⟨x, (hx : f x b = a)⟩, set.ext (λ c, ⟨λ ⟨y, (hy : f y a = c)⟩, ⟨y * x,
  show f (y * x) b = c, by rwa [mul_apply f, hx]⟩,
λ ⟨y, (hy : f y b = c)⟩, ⟨y * x⁻¹,
  show f (y * x⁻¹) a = c, by
    conv {to_rhs, rw [← hy, ← mul_one y, ← inv_mul_self x, ← mul_assoc,
      mul_apply f, hx]}⟩⟩), λ h, h ▸ mem_orbit_self _ _⟩

instance orbit_fintype (a : β) [fintype α] [decidable_eq β] :
fintype (orbit f a) := set.fintype_range _

def stabilizer (a : β) : set α :=
{x : α | f x a = a}

lemma mem_stabilizer_iff {f : α → β → β} [is_group_action f] {a : β} {x : α} :
  x ∈ stabilizer f a ↔ f x a = a :=
iff.rfl

instance (a : β) : is_subgroup (stabilizer f a) :=
{ one_mem := one_apply _ _,
  mul_mem := λ x y (hx : f x a = a) (hy : f y a = a),
    show f (x * y) a = a, by rw mul_apply f; simp *,
  inv_mem := λ x (hx : f x a = a), show f x⁻¹ a = a,
    by rw [← hx, ← mul_apply f, inv_mul_self, one_apply f, hx] }

noncomputable lemma orbit_equiv_left_cosets (a : β) :
  orbit f a ≃ left_cosets (stabilizer f a) :=
by letI := left_rel (stabilizer f a); exact
equiv.symm (@equiv.of_bijective _ _
  (λ x : left_cosets (stabilizer f a), quotient.lift_on x
    (λ x, (⟨f x a, mem_orbit _ _ _⟩ : orbit f a))
    (λ g h (H : _ = _), subtype.eq $ (is_group_action.bijective f (g⁻¹)).1
      $ show f g⁻¹ (f g a) = f g⁻¹ (f h a),
      by rw [← mul_apply f, ← mul_apply f, H, inv_mul_self, one_apply f]))
⟨λ g h, quotient.induction_on₂ g h (λ g h H, quotient.sound $
  have H : f g a = f h a := subtype.mk.inj H,
  show f (g⁻¹ * h) a = a,
  by rw [mul_apply f, ← H, ← mul_apply f, inv_mul_self, one_apply f]),
  λ ⟨b, ⟨g, hgb⟩⟩, ⟨⟦g⟧, subtype.eq hgb⟩⟩)

def fixed_points : set β := {a : β | ∀ x, x ∈ stabilizer f a}

lemma mem_fixed_points {f : α → β → β} [is_group_action f] {a : β} :
  a ∈ fixed_points f ↔ ∀ x : α, f x a = a := iff.rfl

lemma mem_fixed_points' {f : α → β → β} [is_group_action f] {a : β} : a ∈ fixed_points f ↔
  (∀ b, b ∈ orbit f a → b = a) :=
⟨λ h b h₁, let ⟨x, hx⟩ := mem_orbit_iff.1 h₁ in hx ▸ h x,
λ h b, mem_stabilizer_iff.2 (h _ (mem_orbit _ _ _))⟩

end is_group_action


import data.real.basic
import algebra.pi_instances

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

/-
In this file we manipulate the elementary definition of limits of
sequences of real numbers.
mathlib has a much more general definition of limits, but here
we want to practice using the logical operators and relations
covered in the previous files.
A sequence u is a function from ℕ to ℝ, hence Lean says
u : ℕ → ℝ
The definition we'll be using is:
-- Definition of « u tends to l »
def seq_limit (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε
Note the use of `∀ ε > 0, ...` which is an abbreviation of
`∀ ε, ε > 0 → ... `
In particular, a statement like `h : ∀ ε > 0, ...`
can be specialized to a given ε₀ by
  `specialize h ε₀ hε₀`
where hε₀ is a proof of ε₀ > 0.
Also recall that, wherever Lean expects some proof term, we can
start a tactic mode proof using the keyword `by` (followed by curly braces
if you need more than one tactic invocation).
For instance, if the local context contains:
δ : ℝ
δ_pos : δ > 0
h : ∀ ε > 0, ...
then we can specialize h to the real number δ/2 using:
  `specialize h (δ/2) (by linarith)`
where `by linarith` will provide the proof of `δ/2 > 0` expected by Lean.
We'll take this opportunity to use two new tactics:
`norm_num` will perform numerical normalization on the goal and `norm_num at h`
will do the same in assumption `h`. This will get rid of trivial calculations on numbers,
like replacing |l - l| by zero in the next exercise.
`congr'` will try to prove equalities between applications of functions by recursively
proving the arguments are the same.
For instance, if the goal is `f x + g y = f z + g t` then congr will replace it by
two goals: `x = z` and `y = t`.
You can limit the recursion depth by specifying a natural number after `congr'`.
For instance, in the above example, `congr' 1` will give new goals
`f x = f z` and `g y = g t`, which only inspect arguments of the addition and not deeper.
-/

variables (u v w : ℕ → ℝ) (l l' : ℝ)

-- If u is constant with value l then u tends to l
example : (∀ n, u n = 1) → seq_limit u l :=
begin
  sorry,
end


/-
Copyright (c) 2021 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen
-/
import algebra.order.euclidean_absolute_value
import data.polynomial.field_division

/-!
# Absolute value on polynomials over a finite field.

Let `Fq` be a finite field of cardinality `q`, then the map sending a polynomial `p`
to `q ^ degree p` (where `q ^ degree 0 = 0`) is an absolute value.

## Main definitions

 * `polynomial.card_pow_degree` is an absolute value on `𝔽_q[t]`, the ring of
   polynomials over a finite field of cardinality `q`, mapping a polynomial `p`
   to `q ^ degree p` (where `q ^ degree 0 = 0`)

## Main results
 * `polynomial.card_pow_degree_is_euclidean`: `card_pow_degree` respects the
   Euclidean domain structure on the ring of polynomials

-/

namespace polynomial

variables {Fq : Type*} [field Fq] [fintype Fq]

open absolute_value

open_locale classical

/-- `card_pow_degree` is the absolute value on `𝔽_q[t]` sending `f` to `q ^ degree f`.

`card_pow_degree 0` is defined to be `0`. -/
noncomputable def card_pow_degree :
  absolute_value (polynomial Fq) ℤ :=
have card_pos : 0 < fintype.card Fq := fintype.card_pos_iff.mpr infer_instance,
have pow_pos : ∀ n, 0 < (fintype.card Fq : ℤ) ^ n := λ n, pow_pos (int.coe_nat_pos.mpr card_pos) n,
{ to_fun := λ p, if p = 0 then 0 else fintype.card Fq ^ p.nat_degree,
  nonneg' := λ p, by { dsimp, split_ifs, { refl }, exact pow_nonneg (int.coe_zero_le _) _ },
  eq_zero' := λ p, ite_eq_left_iff.trans $ ⟨λ h, by { contrapose! h, exact ⟨h, (pow_pos _).ne'⟩ },
    absurd⟩,
  add_le' := λ p q, begin
    by_cases hp : p = 0, { simp [hp] },
    by_cases hq : q = 0, { simp [hq] },
    by_cases hpq : p + q = 0,
    { simp only [hpq, hp, hq, eq_self_iff_true, if_true, if_false],
      exact add_nonneg (pow_pos _).le (pow_pos _).le },
    simp only [hpq, hp, hq, if_false],
    refine le_trans (pow_le_pow (by linarith) (polynomial.nat_degree_add_le _ _)) _,
    refine le_trans (le_max_iff.mpr _)
      (max_le_add_of_nonneg (pow_nonneg (by linarith) _) (pow_nonneg (by linarith) _)),
    exact (max_choice p.nat_degree q.nat_degree).imp (λ h, by rw [h]) (λ h, by rw [h])
  end,
  map_mul' := λ p q, begin
    by_cases hp : p = 0, { simp [hp] },
    by_cases hq : q = 0, { simp [hq] },
    have hpq : p * q ≠ 0 := mul_ne_zero hp hq,
    simp only [hpq, hp, hq, eq_self_iff_true, if_true, if_false,
      polynomial.nat_degree_mul hp hq, pow_add],
  end }

lemma card_pow_degree_apply (p : polynomial Fq) :
  card_pow_degree p = if p = 0 then 0 else fintype.card Fq ^ nat_degree p := rfl

@[simp] lemma card_pow_degree_zero : card_pow_degree (0 : polynomial Fq) = 0 := if_pos rfl

@[simp] lemma card_pow_degree_nonzero (p : polynomial Fq) (hp : p ≠ 0) :
  card_pow_degree p = fintype.card Fq ^ p.nat_degree :=
if_neg hp

lemma card_pow_degree_is_euclidean :
  is_euclidean (card_pow_degree : absolute_value (polynomial Fq) ℤ) :=
have card_pos : 0 < fintype.card Fq := fintype.card_pos_iff.mpr infer_instance,
have pow_pos : ∀ n, 0 < (fintype.card Fq : ℤ) ^ n := λ n, pow_pos (int.coe_nat_pos.mpr card_pos) n,
{ map_lt_map_iff' := λ p q, begin
    simp only [euclidean_domain.r, card_pow_degree_apply],
    split_ifs with hp hq hq,
    { simp only [hp, hq, lt_self_iff_false] },
    { simp only [hp, hq, degree_zero, ne.def, bot_lt_iff_ne_bot,
        degree_eq_bot, pow_pos, not_false_iff] },
    { simp only [hp, hq, degree_zero, not_lt_bot, (pow_pos _).not_lt] },
    { rw [degree_eq_nat_degree hp, degree_eq_nat_degree hq, with_bot.coe_lt_coe, pow_lt_pow_iff],
      exact_mod_cast @fintype.one_lt_card Fq _ _ },
  end }

end polynomial


import order.complete_lattice

lemma le_supr'' {α : Type*} {ι : Sort*} [_inst_1 : complete_lattice α] (s : ι → α) (i : ι) (x:α):
  (x ≤ s i) → x ≤ supr s :=
begin
  intro A1,
  apply le_trans A1,
  apply le_supr,
end

lemma infi_prop_def {α:Type*} [complete_lattice α]
    {v:α} {P:Prop} (H:P):(⨅ (H2:P), v) = v :=
begin
  apply le_antisymm,
  { apply infi_le, apply H },
  { apply @le_infi, intro A1, apply le_refl v },
end

lemma supr_prop_def {α:Type*} [complete_lattice α]
    {v:α} {P:Prop} (H:P):(⨆ (H2:P), v) = v :=
begin
  apply le_antisymm,
  {
    apply supr_le,
    intro A1,
    apply le_refl v,
  },
  {
    apply @le_supr α P _ (λ H2:P, v),
    apply H,
  },
end

lemma supr_prop_false {α:Type*} [complete_lattice α]
    {v:α} {P:Prop} (H:¬P):(⨆ (H2:P), v) = ⊥ :=
begin
  apply le_antisymm,
  {
    apply supr_le,
    intro A1,
    exfalso,
    apply H,
    apply A1,
  },
  {
    apply bot_le,
  },
end

lemma infi_prop_false {α:Type*} [complete_lattice α]
    {v:α} {P:Prop} (H:¬P):(⨅ (H2:P), v) = ⊤ :=
begin
  apply le_antisymm,
  {
    apply le_top,
  },
  {
    apply le_infi,
    intro A1,
    exfalso,
    apply H,
    apply A1,
  },
end

lemma infi_le_trans {α β:Type*} [complete_lattice β] (a:α) (f:α → β)
    (b:β):(f a ≤ b) → (⨅ (c:α), (f c)) ≤ b :=
begin
  intros A1,
  apply le_trans _ A1,
  apply @infi_le _ _ _,
end

/-
  I saw this pattern a bunch below. It could be more widely used.
 -/
lemma infi_set_le_trans {α β:Type*} [complete_lattice β] (a:α) (P:α → Prop) (f:α → β)
    (b:β):(P a) → (f a ≤ b) → (⨅ (c:α) (H:P c), f c) ≤ b :=
begin
  intros A1 A2,
  apply infi_le_trans a,
  rw infi_prop_def A1,
  apply A2,
end

lemma infi_set_image {α β γ:Type*} [complete_lattice γ] (S:set α) (f:α → β)
    (g:β → γ):(⨅ (c∈ (f '' S)), g c) = ⨅  (a∈ S), (g ∘ f) a :=
begin
  apply le_antisymm;simp,
  {
    intros a B1,
    apply infi_le_trans (f a),
    apply infi_le_trans a,
    rw infi_prop_def,
    apply and.intro B1,
    refl,
  },
  {
    intros b h_b,
    apply infi_le_of_le b,
    apply infi_le_of_le h_b,
    apply le_refl,
  },
end


-- let's define the real numbers to be a number system which satisfies
-- the basic properties of the real numbers which we will need.

noncomputable theory
constant real : Type
@[instance] constant real_field : linear_ordered_field real

-- This piece of magic means that "real" now behaves a lot like
-- the real numbers. In particular we now have a bunch
-- of theorems:

example : ∀ x y : real, x * y = y * x := mul_comm

variable x : real
variable n : nat

-- We do _not_ have powers though. So we need to make them.

open nat

definition natural_power : real → nat → real
| x 0 := 1
| x (succ n) := (natural_power x n) * x

theorem T : ∀ x:real, ∀ m n:nat, natural_power x (m+n) = natural_power x m *natural_power x n :=
begin
assume x m n,
induction n with n H,
unfold natural_power,
rw [add_zero, mul_one],
unfold natural_power,
rw [H, mul_assoc],
end

theorem T2 : ∀ x: real, ∀ m n : nat, natural_power (natural_power x m) n = natural_power x (m*n) :=
begin
assume x m n,
induction n with n H,
unfold natural_power,
rw [mul_zero, eq_comm],
unfold natural_power,
rw [succ_eq_add_one, mul_add, mul_one, add_one],
unfold natural_power,
rw[T, H],
end

theorem T3 : ∀ x y:real, ∀ n:nat, (natural_power x n) * (natural_power y n) = natural_power (x*y) n :=
begin
assume x y n,
induction n with n H,
unfold natural_power,
simp,
unfold natural_power,
cc,
end

constant nth_root (x : real) (n : nat) : (x>0) → (n>0) → real

axiom is_nth_root (x : real) (n : nat) (Hx : x>0) (Hn : n>0) : natural_power (nth_root x n Hx Hn) n = x

definition rational_power_v0 (x : real) (n : nat) (d : nat) (Hx : x > 0) (Hd : d > 0) : real :=
natural_power (nth_root x d Hx Hd) n

theorem T4 : ∀ x y:real,∀ Hx:x≥0,∀ Hy:y≥0,∀ n:nat,natural_power x (n+1) = natural_power y (n+1) → x = y:=
begin
assume x y Hx Hy n,
induction n with n H,
    rw[add_one],
    unfold natural_power,
    rw[mul_comm,mul_one,mul_comm,mul_one],
    assume H1,
    exact(H1),
    unfold natural_power,
    assume H2,
    calc
    natural_power x n * x * x = natural_power y n * y * y : H2
    ... =

end
/-
theorem T5 : ∀ x:real, ∀ n d k:nat, ∀ Hx: x > 0,
∀ Hd: d >0, ∀ Hkd: (k*d) >0,
rational_power_v0 x n d Hx Hd = rational_power_v0 x (k*n) (k*d) Hx Hkd:=
begin
assume x n d k Hx Hd Hkd,
unfold rational_power_v0,
rw[← T2],
have H1 :natural_power (rational_power_v0 x n d Hx Hd) d= natural_power x n :=
begin
unfold rational_power_v0,
rw[T2, mul_comm,← T2,is_nth_root],
end,
let y:real := x,
have H1 y=x,
begin

end
end
-/


/-
Copyright (c) 2020 The Xena project. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Kevin Buzzard
Thanks: Imperial College London, leanprover-community
-/

-- import level 1
import complex.Level_01_of_real

/-!

# Level 2: I

I find it unbelievable that we have written quite a lot of code about the
complex numbers and we've still never defined i, or j, or I, or $$\sqrt{-1}$$,
or whatever it's called.  Why don't you supply the definition, and make the
basic API?

All the proofs below are sorried. You can try them in tactic mode
by replacing `sorry` with `begin end` and then starting to write
tactics in the `begin end` block.
-/

namespace complex

/-- complex.I is the square root of -1 above the imaginary axis -/
def I : ℂ := sorry

/-

 Easy lemmas, tagged with `simp` so Lean can prove things about `I` by equating
 real and imaginary parts.

-/

/-- re(I) = 0 -/
@[simp] lemma I_re : re(I) = 0 :=
begin
  sorry
end

/-- im(I) = 1 -/
@[simp] lemma I_im : im(I) = 1 :=
begin
  sorry
end

/-- I*I = -1 -/
@[simp] lemma I_mul_I : I * I = -1 :=
begin
  sorry
end

lemma mk_eq_add_mul_I (a b : ℝ) : (⟨a, b⟩ : ℂ) = a + b * I := sorry

@[simp] lemma re_add_im (z : ℂ) : (z.re : ℂ) + z.im * I = z := sorry

/-
  Bonus level. Hint: don't forget ext_iff. It's defined
  in complex.basic and its type is below.

  ext_iff : ∀ {z w : ℂ}, z = w ↔ z.re = w.re ∧ z.im = w.im
-/

/-- I is non-zero -/
lemma I_ne_zero : (I : ℂ) ≠ 0 :=
begin
  sorry
end

end complex


open classical tactic expr list

section prop_equivalence

  local attribute [instance] prop_decidable
  variables {a b : Prop}

  theorem not_not_iff (a : Prop) : ¬¬a ↔ a :=
  iff.intro classical.by_contradiction not_not_intro

  theorem implies_iff_not_or (a b : Prop) : (a → b) ↔ (¬ a ∨ b) :=
  iff.intro
    (λ h, if ha : a then or.inr (h ha) else or.inl ha)
    (λ h, or.elim h (λ hna ha, absurd ha hna) (λ hb ha, hb))

  theorem not_and_of_not_or_not (h : ¬ a ∨ ¬ b) : ¬ (a ∧ b) :=
  assume ⟨ha, hb⟩, or.elim h (assume hna, hna ha) (assume hnb, hnb hb)

  theorem not_or_not_of_not_and (h : ¬ (a ∧ b)) : ¬ a ∨ ¬ b :=
  if ha : a then
    or.inr (show ¬ b, from assume hb, h ⟨ha, hb⟩)
  else
    or.inl ha

  theorem not_and_iff (a b : Prop) : ¬ (a ∧ b) ↔ ¬a ∨ ¬b :=
  iff.intro not_or_not_of_not_and not_and_of_not_or_not

  theorem not_or_of_not_and_not (h : ¬ a ∧ ¬ b) : ¬ (a ∨ b) :=
  assume h₁, or.elim h₁ (assume ha, h^.left ha) (assume hb, h^.right hb)

  theorem not_and_not_of_not_or (h : ¬ (a ∨ b)) : ¬ a ∧ ¬ b :=
  and.intro (assume ha, h (or.inl ha)) (assume hb, h (or.inr hb))

  theorem not_or_iff (a b : Prop) : ¬ (a ∨ b) ↔ ¬ a ∧ ¬ b :=
  iff.intro not_and_not_of_not_or not_or_of_not_and_not

  theorem iff_eq (a b : Prop) : (a ↔ b) = ((a → b) ∧ (b → a)) := rfl

  check and.comm
  check or.comm

end prop_equivalence

section prenex_equivalence

  variables {A : Type}
  variables {p : Prop}
  variables {q : A → Prop}
  variable [inhabited A]

  theorem forall_not_of_not_exists : (¬ ∃ x, q x) → ∀ x, ¬ q x :=
  λ h x hqx, h (exists.intro x hqx)

  theorem exists_not_of_not_forall : (¬ ∀ x, q x) → ∃ x, ¬ q x :=
  λ h, by_contradiction
  (λ neg, have ∀ x, ¬ ¬ q x, from forall_not_of_not_exists neg,
    have ∀ x, q x, from λ x, iff.elim_left (not_not_iff (q x)) (this x),
  show _, from h this)

  theorem forall_p_iff_p : (∀ x : A, p) ↔ p :=
  iff.intro (λ h, h (default A)) (λ h x, h)

  theorem exists_p_iff_p : (∃ x : A, p) ↔ p :=
  iff.intro (λ h, exists.elim h (λ x hx, hx)) (λ h, exists.intro (default A) h)

  theorem forall_of_forall_and_left : (∀ x, q x) ∧ p ↔ ∀ x, q x ∧ p :=
  iff.intro (λ h x, and.intro (h^.left x) h^.right)
  (λ h, and.intro (λ x, (h x)^.left) (h (default A))^.right)

  theorem forall_of_forall_and_right : p ∧ (∀ x, q x) ↔ ∀ x, q x ∧ p :=
  iff.trans and.comm forall_of_forall_and_left

  theorem forall_of_forall_or_left : (∀ x, q x) ∨ p ↔ ∀ x, q x ∨ p :=
  iff.intro (λ h x, or.elim h (λ l, or.inl (l x)) (λ r, or.inr r))
  (λ h, or.elim (em p) (λ l, or.inr l) (λ r, or.inl (λ x, or.resolve_right (h x) r)))

  theorem forall_of_forall_or_right : p ∨ (∀ x, q x) ↔ ∀ x, q x ∨ p :=
  iff.trans or.comm forall_of_forall_or_left

  theorem exists_of_exists_and_left : (∃ x, q x) ∧ p ↔ ∃ x, q x ∧ p :=
  iff.intro (λ h, exists.elim h^.left (λ x hx, exists.intro x (and.intro hx h^.right)) )
  (λ h, exists.elim h (λ x hx, and.intro (exists.intro x hx^.left) hx^.right))

  theorem exists_of_exists_and_right : p ∧ (∃ x, q x) ↔ ∃ x, q x ∧ p :=
  iff.trans and.comm exists_of_exists_and_left

  theorem exists_of_exists_or_left : (∃ x, q x) ∨ p ↔ ∃ x, q x ∨ p :=
  iff.intro (λ h, or.elim h (λ l, exists.elim l (λ x hx, exists.intro x (or.inl hx)))
    (λ r, exists.intro (default A) (or.inr r)))
  (λ h, exists.elim h (λ x hx, or.elim hx (λ l, or.inl (exists.intro x l)) (λ r, or.inr r)))

  theorem exists_of_exists_or_right : p ∨ (∃ x, q x) ↔ ∃ x, q x ∨ p :=
  iff.trans or.comm exists_of_exists_or_left

end prenex_equivalence

meta def nnf_lemmas : list name := [``forall_not_of_not_exists, ``exists_not_of_not_forall,
                                ``iff_eq, ``implies_iff_not_or, ``not_and_iff, ``not_or_iff,
                                ``not_not_iff, ``not_true_iff, ``not_false_iff]

meta def normalize_hyp (lemmas : list expr) (hyp : expr) : tactic unit :=
do try (simp_at_using lemmas hyp)

meta def nnf_hyps : tactic unit :=
do hyps ← local_context,
   lemmas ← monad.mapm mk_const nnf_lemmas,
   monad.for' hyps (normalize_hyp lemmas)

meta def pnf_lemmas : list name := [``forall_p_iff_p, ``exists_p_iff_p, ``forall_of_forall_and_left,
                             ``forall_of_forall_and_right, ``forall_of_forall_or_left,
                             ``forall_of_forall_or_right, ``exists_of_exists_and_left,
                             ``exists_of_exists_and_right, ``exists_of_exists_or_left, ``exists_of_exists_or_right]

meta def pnf_hyps : tactic unit :=
do hyps ← local_context,
   pnf_lemmas ← monad.mapm mk_const pnf_lemmas,
   monad.for' hyps (normalize_hyp pnf_lemmas)

example (p q r : Prop) (h₁ : ¬ (p ↔ (q ∧ ¬ r))) (h₂ : ¬ (p → (q → ¬ r))) : true :=
by do nnf_hyps,
      trace_state,
      triv

-- TODO: think about the order of applying simp rules.

example (A : Type) (p : Prop) (q : A → Prop) (h₁ : ¬ p → ((∀ x, q x) → p)) [inhabited A] : true :=
by do nnf_hyps,
      trace_state,
      pnf_hyps,
      trace_state,
      triv

print simplify


/-
Copyright (c) 2020 Fox Thomson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Fox Thomson
-/
import data.set.lattice
import data.list.basic

/-!
# Languages

This file contains the definition and operations on formal languages over an alphabet. Note strings
are implemented as lists over the alphabet.
The operations in this file define a [Kleene algebra](https://en.wikipedia.org/wiki/Kleene_algebra)
over the languages.
-/

universes u v

variables {α : Type u}

/-- A language is a set of strings over an alphabet. -/
@[derive [has_mem (list α), has_singleton (list α), has_insert (list α), complete_boolean_algebra]]
def language (α) := set (list α)

namespace language

local attribute [reducible] language

/-- Zero language has no elements. -/
instance : has_zero (language α) := ⟨(∅ : set _)⟩
/-- `1 : language α` contains only one element `[]`. -/
instance : has_one (language α) := ⟨{[]}⟩

instance : inhabited (language α) := ⟨0⟩

/-- The sum of two languages is the union of  -/
instance : has_add (language α) := ⟨set.union⟩
instance : has_mul (language α) := ⟨set.image2 (++)⟩

lemma zero_def : (0 : language α) = (∅ : set _) := rfl
lemma one_def : (1 : language α) = {[]} := rfl

lemma add_def (l m : language α) : l + m = l ∪ m := rfl
lemma mul_def (l m : language α) : l * m = set.image2 (++) l m := rfl

/-- The star of a language `L` is the set of all strings which can be written by concatenating
  strings from `L`. -/
def star (l : language α) : language α :=
{ x | ∃ S : list (list α), x = S.join ∧ ∀ y ∈ S, y ∈ l}

lemma star_def (l : language α) :
  l.star = { x | ∃ S : list (list α), x = S.join ∧ ∀ y ∈ S, y ∈ l} := rfl

@[simp] lemma mem_one (x : list α) : x ∈ (1 : language α) ↔ x = [] := by refl
@[simp] lemma mem_add (l m : language α) (x : list α) : x ∈ l + m ↔ x ∈ l ∨ x ∈ m :=
by simp [add_def]
lemma mem_mul (l m : language α) (x : list α) : x ∈ l * m ↔ ∃ a b, a ∈ l ∧ b ∈ m ∧ a ++ b = x :=
by simp [mul_def]
lemma mem_star (l : language α) (x : list α) :
  x ∈ l.star ↔ ∃ S : list (list α), x = S.join ∧ ∀ y ∈ S, y ∈ l :=
iff.rfl

instance : semiring (language α) :=
{ add := (+),
  add_assoc := set.union_assoc,
  zero := 0,
  zero_add := set.empty_union,
  add_zero := set.union_empty,
  add_comm := set.union_comm,
  mul := (*),
  mul_assoc := λ l m n,
    by simp only [mul_def, set.image2_image2_left, set.image2_image2_right, list.append_assoc],
  zero_mul := by simp [zero_def, mul_def],
  mul_zero := by simp [zero_def, mul_def],
  one := 1,
  one_mul := λ l, by simp [mul_def, one_def],
  mul_one := λ l, by simp [mul_def, one_def],
  left_distrib := λ l m n, by simp only [mul_def, add_def, set.image2_union_right],
  right_distrib := λ l m n, by simp only [mul_def, add_def, set.image2_union_left] }

@[simp] lemma add_self (l : language α) : l + l = l := sup_idem

lemma star_def_nonempty (l : language α) :
  l.star = {x | ∃ S : list (list α), x = S.join ∧ ∀ y ∈ S, y ∈ l ∧ y ≠ []} :=
begin
  ext x,
  split,
  { rintro ⟨S, rfl, h⟩,
    refine ⟨S.filter (λ l, ¬list.empty l), by simp, λ y hy, _⟩,
    rw [list.mem_filter, list.empty_iff_eq_nil] at hy,
    exact ⟨h y hy.1, hy.2⟩ },
  { rintro ⟨S, hx, h⟩,
    exact ⟨S, hx, λ y hy, (h y hy).1⟩ }
end

lemma le_mul_congr {l₁ l₂ m₁ m₂ : language α} : l₁ ≤ m₁ → l₂ ≤ m₂ → l₁ * l₂ ≤ m₁ * m₂ :=
begin
  intros h₁ h₂ x hx,
  simp only [mul_def, exists_and_distrib_left, set.mem_image2, set.image_prod] at hx ⊢,
  tauto
end

lemma le_add_congr {l₁ l₂ m₁ m₂ : language α} : l₁ ≤ m₁ → l₂ ≤ m₂ → l₁ + l₂ ≤ m₁ + m₂ := sup_le_sup

lemma mem_supr {ι : Sort v} {l : ι → language α} {x : list α} :
  x ∈ (⨆ i, l i) ↔ ∃ i, x ∈ l i :=
set.mem_Union

lemma supr_mul {ι : Sort v} (l : ι → language α) (m : language α) :
  (⨆ i, l i) * m = ⨆ i, l i * m :=
set.image2_Union_left _ _ _

lemma mul_supr {ι : Sort v} (l : ι → language α) (m : language α) :
  m * (⨆ i, l i) = ⨆ i, m * l i :=
set.image2_Union_right _ _ _

lemma supr_add {ι : Sort v} [nonempty ι] (l : ι → language α) (m : language α) :
  (⨆ i, l i) + m = ⨆ i, l i + m := supr_sup

lemma add_supr {ι : Sort v} [nonempty ι] (l : ι → language α) (m : language α) :
  m + (⨆ i, l i) = ⨆ i, m + l i := sup_supr

lemma mem_pow {l : language α} {x : list α} {n : ℕ} :
  x ∈ l ^ n ↔ ∃ S : list (list α), x = S.join ∧ S.length = n ∧ ∀ y ∈ S, y ∈ l :=
begin
  induction n with n ihn generalizing x,
  { simp only [mem_one, pow_zero, list.length_eq_zero],
    split,
    { rintro rfl, exact ⟨[], rfl, rfl, λ y h, h.elim⟩ },
    { rintro ⟨_, rfl, rfl, _⟩, refl } },
  { simp only [pow_succ, mem_mul, ihn],
    split,
    { rintro ⟨a, b, ha, ⟨S, rfl, rfl, hS⟩, rfl⟩,
      exact ⟨a :: S, rfl, rfl, list.forall_mem_cons.2 ⟨ha, hS⟩⟩ },
    { rintro ⟨_|⟨a, S⟩, rfl, hn, hS⟩; cases hn,
      rw list.forall_mem_cons at hS,
      exact ⟨a, _, hS.1, ⟨S, rfl, rfl, hS.2⟩, rfl⟩ } }
end

lemma star_eq_supr_pow (l : language α) : l.star = ⨆ i : ℕ, l ^ i :=
begin
  ext x,
  simp only [mem_star, mem_supr, mem_pow],
  split,
  { rintro ⟨S, rfl, hS⟩, exact ⟨_, S, rfl, rfl, hS⟩ },
  { rintro ⟨_, S, rfl, rfl, hS⟩, exact ⟨S, rfl, hS⟩ }
end

lemma mul_self_star_comm (l : language α) : l.star * l = l * l.star :=
by simp only [star_eq_supr_pow, mul_supr, supr_mul, ← pow_succ, ← pow_succ']

@[simp] lemma one_add_self_mul_star_eq_star (l : language α) : 1 + l * l.star = l.star :=
begin
  simp only [star_eq_supr_pow, mul_supr, ← pow_succ, ← pow_zero l],
  exact sup_supr_nat_succ _
end

@[simp] lemma one_add_star_mul_self_eq_star (l : language α) : 1 + l.star * l = l.star :=
by rw [mul_self_star_comm, one_add_self_mul_star_eq_star]

lemma star_mul_le_right_of_mul_le_right (l m : language α) : l * m ≤ m → l.star * m ≤ m :=
begin
  intro h,
  rw [star_eq_supr_pow, supr_mul],
  refine supr_le _,
  intro n,
  induction n with n ih,
  { simp },
  rw [pow_succ', mul_assoc (l^n) l m],
  exact le_trans (le_mul_congr (le_refl _) h) ih,
end

lemma star_mul_le_left_of_mul_le_left (l m : language α) : m * l ≤ m → m * l.star ≤ m :=
begin
  intro h,
  rw [star_eq_supr_pow, mul_supr],
  refine supr_le _,
  intro n,
  induction n with n ih,
  { simp },
  rw [pow_succ, ←mul_assoc m l (l^n)],
  exact le_trans (le_mul_congr h (le_refl _)) ih
end

end language


/-
Copyright (c) 2022 Michael Stoll. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Michael Stoll
-/
import number_theory.legendre_symbol.quadratic_reciprocity

/-!
# The Jacobi Symbol

We define the Jacobi symbol and prove its main properties.

## Main definitions

We define the Jacobi symbol, `jacobi_sym a b`, for integers `a` and natural numbers `b`
as the product over the prime factors `p` of `b` of the Legendre symbols `legendre_sym p a`.
This agrees with the mathematical definition when `b` is odd.

The prime factors are obtained via `nat.factors`. Since `nat.factors 0 = []`,
this implies in particular that `jacobi_sym a 0 = 1` for all `a`.

## Main statements

We prove the main properties of the Jacobi symbol, including the following.

* Multiplicativity in both arguments (`jacobi_sym.mul_left`, `jacobi_sym.mul_right`)

* The value of the symbol is `1` or `-1` when the arguments are coprime
  (`jacobi_sym.eq_one_or_neg_one`)

* The symbol vanishes if and only if `b ≠ 0` and the arguments are not coprime
  (`jacobi_sym.eq_zero_iff`)

* If the symbol has the value `-1`, then `a : zmod b` is not a square
  (`zmod.nonsquare_of_jacobi_sym_eq_neg_one`); the converse holds when `b = p` is a prime
  (`zmod.nonsquare_iff_jacobi_sym_eq_neg_one`); in particular, in this case `a` is a
  square mod `p` when the symbol has the value `1` (`zmod.is_square_of_jacobi_sym_eq_one`).

* Quadratic reciprocity (`jacobi_sym.quadratic_reciprocity`,
  `jacobi_sym.quadratic_reciprocity_one_mod_four`,
  `jacobi_sym.quadratic_reciprocity_three_mod_four`)

* The supplementary laws for `a = -1`, `a = 2`, `a = -2` (`jacobi_sym.at_neg_one`,
  `jacobi_sym.at_two`, `jacobi_sym.at_neg_two`)

* The symbol depends on `a` only via its residue class mod `b` (`jacobi_sym.mod_left`)
  and on `b` only via its residue class mod `4*a` (`jacobi_sym.mod_right`)

## Notations

We define the notation `J(a | b)` for `jacobi_sym a b`, localized to `number_theory_symbols`.

## Tags
Jacobi symbol, quadratic reciprocity
-/

section jacobi

/-!
### Definition of the Jacobi symbol

We define the Jacobi symbol $\Bigl(\frac{a}{b}\Bigr)$ for integers `a` and natural numbers `b`
as the product of the Legendre symbols $\Bigl(\frac{a}{p}\Bigr)$, where `p` runs through the
prime divisors (with multiplicity) of `b`, as provided by `b.factors`. This agrees with the
Jacobi symbol when `b` is odd and gives less meaningful values when it is not (e.g., the symbol
is `1` when `b = 0`). This is called `jacobi_sym a b`.

We define localized notation (locale `number_theory_symbols`) `J(a | b)` for the Jacobi
symbol `jacobi_sym a b`.
-/

open nat zmod

/-- The Jacobi symbol of `a` and `b` -/
-- Since we need the fact that the factors are prime, we use `list.pmap`.
def jacobi_sym (a : ℤ) (b : ℕ) : ℤ :=
(b.factors.pmap (λ p pp, @legendre_sym p ⟨pp⟩ a) (λ p pf, prime_of_mem_factors pf)).prod

-- Notation for the Jacobi symbol.
localized "notation `J(` a ` | ` b `)` := jacobi_sym a b" in number_theory_symbols

/-!
### Properties of the Jacobi symbol
-/
namespace jacobi_sym

/-- The symbol `J(a | 0)` has the value `1`. -/
@[simp] lemma zero_right (a : ℤ) : J(a | 0) = 1 :=
by simp only [jacobi_sym, factors_zero, list.prod_nil, list.pmap]

/-- The symbol `J(a | 1)` has the value `1`. -/
@[simp] lemma one_right (a : ℤ) : J(a | 1) = 1 :=
by simp only [jacobi_sym, factors_one, list.prod_nil, list.pmap]

/-- The Legendre symbol `legendre_sym p a` with an integer `a` and a prime number `p`
is the same as the Jacobi symbol `J(a | p)`. -/
lemma _root_.legendre_sym.to_jacobi_sym (p : ℕ) [fp : fact p.prime] (a : ℤ) :
  legendre_sym p a = J(a | p) :=
by simp only [jacobi_sym, factors_prime fp.1, list.prod_cons, list.prod_nil, mul_one, list.pmap]

/-- The Jacobi symbol is multiplicative in its second argument. -/
lemma mul_right' (a : ℤ) {b₁ b₂ : ℕ} (hb₁ : b₁ ≠ 0) (hb₂ : b₂ ≠ 0) :
  J(a | b₁ * b₂) = J(a | b₁) * J(a | b₂) :=
begin
  rw [jacobi_sym, ((perm_factors_mul hb₁ hb₂).pmap _).prod_eq, list.pmap_append, list.prod_append],
  exacts [rfl, λ p hp, (list.mem_append.mp hp).elim prime_of_mem_factors prime_of_mem_factors],
end

/-- The Jacobi symbol is multiplicative in its second argument. -/
lemma mul_right (a : ℤ) (b₁ b₂ : ℕ) [ne_zero b₁] [ne_zero b₂] :
  J(a | b₁ * b₂) = J(a | b₁) * J(a | b₂) :=
mul_right' a (ne_zero.ne b₁) (ne_zero.ne b₂)

/-- The Jacobi symbol takes only the values `0`, `1` and `-1`. -/
lemma trichotomy (a : ℤ) (b : ℕ) : J(a | b) = 0 ∨ J(a | b) = 1 ∨ J(a | b) = -1 :=
((@sign_type.cast_hom ℤ _ _).to_monoid_hom.mrange.copy {0, 1, -1} $
  by {rw set.pair_comm, exact (sign_type.range_eq sign_type.cast_hom).symm}).list_prod_mem
begin
  intros _ ha',
  rcases list.mem_pmap.mp ha' with ⟨p, hp, rfl⟩,
  haveI : fact p.prime := ⟨prime_of_mem_factors hp⟩,
  exact quadratic_char_is_quadratic (zmod p) a,
end

/-- The symbol `J(1 | b)` has the value `1`. -/
@[simp] lemma one_left (b : ℕ) : J(1 | b) = 1 :=
list.prod_eq_one (λ z hz,
                  let ⟨p, hp, he⟩ := list.mem_pmap.1 hz in by rw [← he, legendre_sym.at_one])

/-- The Jacobi symbol is multiplicative in its first argument. -/
lemma mul_left (a₁ a₂ : ℤ) (b : ℕ) : J(a₁ * a₂ | b) = J(a₁ | b) * J(a₂ | b) :=
by { simp_rw [jacobi_sym, list.pmap_eq_map_attach, legendre_sym.mul], exact list.prod_map_mul }

/-- The symbol `J(a | b)` vanishes iff `a` and `b` are not coprime (assuming `b ≠ 0`). -/
lemma eq_zero_iff_not_coprime {a : ℤ} {b : ℕ} [ne_zero b] : J(a | b) = 0 ↔ a.gcd b ≠ 1 :=
list.prod_eq_zero_iff.trans begin
  rw [list.mem_pmap, int.gcd_eq_nat_abs, ne, prime.not_coprime_iff_dvd],
  simp_rw [legendre_sym.eq_zero_iff, int_coe_zmod_eq_zero_iff_dvd, mem_factors (ne_zero.ne b),
    ← int.coe_nat_dvd_left, int.coe_nat_dvd, exists_prop, and_assoc, and_comm],
end

/-- The symbol `J(a | b)` is nonzero when `a` and `b` are coprime. -/
protected
lemma ne_zero {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ≠ 0 :=
begin
  casesI eq_zero_or_ne_zero b with hb,
  { rw [hb, zero_right],
    exact one_ne_zero },
  { contrapose! h, exact eq_zero_iff_not_coprime.1 h },
end

/-- The symbol `J(a | b)` vanishes if and only if `b ≠ 0` and `a` and `b` are not coprime. -/
lemma eq_zero_iff {a : ℤ} {b : ℕ} : J(a | b) = 0 ↔ b ≠ 0 ∧ a.gcd b ≠ 1 :=
⟨λ h, begin
  casesI eq_or_ne b 0 with hb hb,
  { rw [hb, zero_right] at h, cases h },
  exact ⟨hb, mt jacobi_sym.ne_zero $ not_not.2 h⟩,
end, λ ⟨hb, h⟩, by { rw ← ne_zero_iff at hb, exactI eq_zero_iff_not_coprime.2 h }⟩

/-- The symbol `J(0 | b)` vanishes when `b > 1`. -/
lemma zero_left {b : ℕ} (hb : 1 < b) : J(0 | b) = 0 :=
(@eq_zero_iff_not_coprime 0 b ⟨ne_zero_of_lt hb⟩).mpr $
  by { rw [int.gcd_zero_left, int.nat_abs_of_nat], exact hb.ne' }

/-- The symbol `J(a | b)` takes the value `1` or `-1` if `a` and `b` are coprime. -/
lemma eq_one_or_neg_one {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) = 1 ∨ J(a | b) = -1 :=
(trichotomy a b).resolve_left $ jacobi_sym.ne_zero h

/-- We have that `J(a^e | b) = J(a | b)^e`. -/
lemma pow_left (a : ℤ) (e b : ℕ) : J(a ^ e | b) = J(a | b) ^ e :=
nat.rec_on e (by rw [pow_zero, pow_zero, one_left]) $
  λ _ ih, by rw [pow_succ, pow_succ, mul_left, ih]

/-- We have that `J(a | b^e) = J(a | b)^e`. -/
lemma pow_right (a : ℤ) (b e : ℕ) : J(a | b ^ e) = J(a | b) ^ e :=
begin
  induction e with e ih,
  { rw [pow_zero, pow_zero, one_right], },
  { casesI eq_zero_or_ne_zero b with hb,
    { rw [hb, zero_pow (succ_pos e), zero_right, one_pow], },
    { rw [pow_succ, pow_succ, mul_right, ih], } }
end

/-- The square of `J(a | b)` is `1` when `a` and `b` are coprime. -/
lemma sq_one {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a | b) ^ 2 = 1 :=
by cases eq_one_or_neg_one h with h₁ h₁; rw h₁; refl

/-- The symbol `J(a^2 | b)` is `1` when `a` and `b` are coprime. -/
lemma sq_one' {a : ℤ} {b : ℕ} (h : a.gcd b = 1) : J(a ^ 2 | b) = 1 :=
by rw [pow_left, sq_one h]

/-- The symbol `J(a | b)` depends only on `a` mod `b`. -/
lemma mod_left (a : ℤ) (b : ℕ) : J(a | b) = J(a % b | b) :=
congr_arg list.prod $ list.pmap_congr _ begin
  rintro p hp _ _,
  conv_rhs { rw [legendre_sym.mod, int.mod_mod_of_dvd _
    (int.coe_nat_dvd.2 $ dvd_of_mem_factors hp), ← legendre_sym.mod] },
end

/-- The symbol `J(a | b)` depends only on `a` mod `b`. -/
lemma mod_left' {a₁ a₂ : ℤ} {b : ℕ} (h : a₁ % b = a₂ % b) : J(a₁ | b) = J(a₂ | b) :=
by rw [mod_left, h, ← mod_left]

end jacobi_sym

namespace zmod

open jacobi_sym

/-- If `J(a | b)` is `-1`, then `a` is not a square modulo `b`. -/
lemma nonsquare_of_jacobi_sym_eq_neg_one {a : ℤ} {b : ℕ} (h : J(a | b) = -1) :
  ¬ is_square (a : zmod b) :=
λ ⟨r, ha⟩, begin
  rw [← r.coe_val_min_abs, ← int.cast_mul, int_coe_eq_int_coe_iff', ← sq] at ha,
  apply (by norm_num : ¬ (0 : ℤ) ≤ -1),
  rw [← h, mod_left, ha, ← mod_left, pow_left],
  apply sq_nonneg,
end

/-- If `p` is prime, then `J(a | p)` is `-1` iff `a` is not a square modulo `p`. -/
lemma nonsquare_iff_jacobi_sym_eq_neg_one {a : ℤ} {p : ℕ} [fact p.prime] :
  J(a | p) = -1 ↔ ¬ is_square (a : zmod p) :=
by { rw [← legendre_sym.to_jacobi_sym], exact legendre_sym.eq_neg_one_iff p }

/-- If `p` is prime and `J(a | p) = 1`, then `a` is q square mod `p`. -/
lemma is_square_of_jacobi_sym_eq_one {a : ℤ} {p : ℕ} [fact p.prime] (h : J(a | p) = 1) :
  is_square (a : zmod p) :=
not_not.mp $ by { rw [← nonsquare_iff_jacobi_sym_eq_neg_one, h], dec_trivial }

end zmod

/-!
### Values at `-1`, `2` and `-2`
-/

namespace jacobi_sym

/-- If `χ` is a multiplicative function such that `J(a | p) = χ p` for all odd primes `p`,
then `J(a | b)` equals `χ b` for all odd natural numbers `b`. -/
lemma value_at (a : ℤ) {R : Type*} [comm_semiring R] (χ : R →* ℤ)
  (hp : ∀ (p : ℕ) (pp : p.prime) (h2 : p ≠ 2), @legendre_sym p ⟨pp⟩ a = χ p) {b : ℕ} (hb : odd b) :
  J(a | b) = χ b :=
begin
  conv_rhs { rw [← prod_factors hb.pos.ne', cast_list_prod, χ.map_list_prod] },
  rw [jacobi_sym, list.map_map, ← list.pmap_eq_map nat.prime _ _ (λ _, prime_of_mem_factors)],
  congr' 1, apply list.pmap_congr,
  exact λ p h pp _, hp p pp (hb.ne_two_of_dvd_nat $ dvd_of_mem_factors h)
end

/-- If `b` is odd, then `J(-1 | b)` is given by `χ₄ b`. -/
lemma at_neg_one {b : ℕ} (hb : odd b) : J(-1 | b) = χ₄ b :=
value_at (-1) χ₄ (λ p pp, @legendre_sym.at_neg_one p ⟨pp⟩) hb

/-- If `b` is odd, then `J(-a | b) = χ₄ b * J(a | b)`. -/
protected
lemma neg (a : ℤ) {b : ℕ} (hb : odd b) : J(-a | b) = χ₄ b * J(a | b) :=
by rw [neg_eq_neg_one_mul, mul_left, at_neg_one hb]

/-- If `b` is odd, then `J(2 | b)` is given by `χ₈ b`. -/
lemma at_two {b : ℕ} (hb : odd b) : J(2 | b) = χ₈ b :=
value_at 2 χ₈ (λ p pp, @legendre_sym.at_two p ⟨pp⟩) hb

/-- If `b` is odd, then `J(-2 | b)` is given by `χ₈' b`. -/
lemma at_neg_two {b : ℕ} (hb : odd b) : J(-2 | b) = χ₈' b :=
value_at (-2) χ₈' (λ p pp, @legendre_sym.at_neg_two p ⟨pp⟩) hb

end jacobi_sym

/-!
### Quadratic Reciprocity
-/

/-- The bi-multiplicative map giving the sign in the Law of Quadratic Reciprocity -/
def qr_sign (m n : ℕ) : ℤ := J(χ₄ m | n)

namespace qr_sign

/-- We can express `qr_sign m n` as a power of `-1` when `m` and `n` are odd. -/
lemma neg_one_pow {m n : ℕ} (hm : odd m) (hn : odd n) :
  qr_sign m n = (-1) ^ ((m / 2) * (n / 2)) :=
begin
  rw [qr_sign, pow_mul, ← χ₄_eq_neg_one_pow (odd_iff.mp hm)],
  cases odd_mod_four_iff.mp (odd_iff.mp hm) with h h,
  { rw [χ₄_nat_one_mod_four h, jacobi_sym.one_left, one_pow], },
  { rw [χ₄_nat_three_mod_four h, ← χ₄_eq_neg_one_pow (odd_iff.mp hn), jacobi_sym.at_neg_one hn], }
end

/-- When `m` and `n` are odd, then the square of `qr_sign m n` is `1`. -/
lemma sq_eq_one {m n : ℕ} (hm : odd m) (hn : odd n) : (qr_sign m n) ^ 2 = 1 :=
by rw [neg_one_pow hm hn, ← pow_mul, mul_comm, pow_mul, neg_one_sq, one_pow]

/-- `qr_sign` is multiplicative in the first argument. -/
lemma mul_left (m₁ m₂ n : ℕ) : qr_sign (m₁ * m₂) n = qr_sign m₁ n * qr_sign m₂ n :=
by simp_rw [qr_sign, nat.cast_mul, map_mul, jacobi_sym.mul_left]

/-- `qr_sign` is multiplicative in the second argument. -/
lemma mul_right (m n₁ n₂ : ℕ) [ne_zero n₁] [ne_zero n₂] :
  qr_sign m (n₁ * n₂) = qr_sign m n₁ * qr_sign m n₂ :=
jacobi_sym.mul_right (χ₄ m) n₁ n₂

/-- `qr_sign` is symmetric when both arguments are odd. -/
protected
lemma symm {m n : ℕ} (hm : odd m) (hn : odd n) : qr_sign m n = qr_sign n m :=
by rw [neg_one_pow hm hn, neg_one_pow hn hm, mul_comm (m / 2)]

/-- We can move `qr_sign m n` from one side of an equality to the other when `m` and `n` are odd. -/
lemma eq_iff_eq {m n : ℕ} (hm : odd m) (hn : odd n) (x y : ℤ) :
  qr_sign m n * x = y ↔ x = qr_sign m n * y :=
by refine ⟨λ h', let h := h'.symm in _, λ h, _⟩;
   rw [h, ← mul_assoc, ← pow_two, sq_eq_one hm hn, one_mul]

end qr_sign

namespace jacobi_sym

/-- The Law of Quadratic Reciprocity for the Jacobi symbol, version with `qr_sign` -/
lemma quadratic_reciprocity' {a b : ℕ} (ha : odd a) (hb : odd b) :
  J(a | b) = qr_sign b a * J(b | a) :=
begin
  -- define the right hand side for fixed `a` as a `ℕ →* ℤ`
  let rhs : ℕ → ℕ →* ℤ := λ a,
  { to_fun := λ x, qr_sign x a * J(x | a),
    map_one' := by { convert ← mul_one _, symmetry, all_goals { apply one_left } },
    map_mul' := λ x y, by rw [qr_sign.mul_left, nat.cast_mul, mul_left,
                              mul_mul_mul_comm] },
  have rhs_apply : ∀ (a b : ℕ), rhs a b = qr_sign b a * J(b | a) := λ a b, rfl,
  refine value_at a (rhs a) (λ p pp hp, eq.symm _) hb,
  have hpo := pp.eq_two_or_odd'.resolve_left hp,
  rw [@legendre_sym.to_jacobi_sym p ⟨pp⟩, rhs_apply, nat.cast_id,
      qr_sign.eq_iff_eq hpo ha, qr_sign.symm hpo ha],
  refine value_at p (rhs p) (λ q pq hq, _) ha,
  have hqo := pq.eq_two_or_odd'.resolve_left hq,
  rw [rhs_apply, nat.cast_id, ← @legendre_sym.to_jacobi_sym p ⟨pp⟩, qr_sign.symm hqo hpo,
      qr_sign.neg_one_pow hpo hqo, @legendre_sym.quadratic_reciprocity' p q ⟨pp⟩ ⟨pq⟩ hp hq],
end

/-- The Law of Quadratic Reciprocity for the Jacobi symbol -/
lemma quadratic_reciprocity {a b : ℕ} (ha : odd a) (hb : odd b) :
  J(a | b) = (-1) ^ ((a / 2) * (b / 2)) * J(b | a) :=
by rw [← qr_sign.neg_one_pow ha hb, qr_sign.symm ha hb, quadratic_reciprocity' ha hb]

/-- The Law of Quadratic Reciprocity for the Jacobi symbol: if `a` and `b` are natural numbers
with `a % 4 = 1` and `b` odd, then `J(a | b) = J(b | a)`. -/
theorem quadratic_reciprocity_one_mod_four {a b : ℕ} (ha : a % 4 = 1) (hb : odd b) :
  J(a | b) = J(b | a) :=
by rw [quadratic_reciprocity (odd_iff.mpr (odd_of_mod_four_eq_one ha)) hb,
       pow_mul, neg_one_pow_div_two_of_one_mod_four ha, one_pow, one_mul]

/-- The Law of Quadratic Reciprocity for the Jacobi symbol: if `a` and `b` are natural numbers
with `a` odd and `b % 4 = 1`, then `J(a | b) = J(b | a)`. -/
theorem quadratic_reciprocity_one_mod_four' {a b : ℕ} (ha : odd a) (hb : b % 4 = 1) :
  J(a | b) = J(b | a) :=
(quadratic_reciprocity_one_mod_four hb ha).symm

/-- The Law of Quadratic Reciprocityfor the Jacobi symbol: if `a` and `b` are natural numbers
both congruent to `3` mod `4`, then `J(a | b) = -J(b | a)`. -/
theorem quadratic_reciprocity_three_mod_four {a b : ℕ} (ha : a % 4 = 3) (hb : b % 4 = 3) :
  J(a | b) = - J(b | a) :=
let nop := @neg_one_pow_div_two_of_three_mod_four in begin
  rw [quadratic_reciprocity, pow_mul, nop ha, nop hb, neg_one_mul];
  rwa [odd_iff, odd_of_mod_four_eq_three],
end

/-- The Jacobi symbol `J(a | b)` depends only on `b` mod `4*a` (version for `a : ℕ`). -/
lemma mod_right' (a : ℕ) {b : ℕ} (hb : odd b) : J(a | b) = J(a | b % (4 * a)) :=
begin
  rcases eq_or_ne a 0 with rfl | ha₀,
  { rw [mul_zero, mod_zero], },
  have hb' : odd (b % (4 * a)) := hb.mod_even (even.mul_right (by norm_num) _),
  rcases exists_eq_pow_mul_and_not_dvd ha₀ 2 (by norm_num) with ⟨e, a', ha₁', ha₂⟩,
  have ha₁ := odd_iff.mpr (two_dvd_ne_zero.mp ha₁'),
  nth_rewrite 1 [ha₂], nth_rewrite 0 [ha₂],
  rw [nat.cast_mul, mul_left, mul_left, quadratic_reciprocity' ha₁ hb,
      quadratic_reciprocity' ha₁ hb', nat.cast_pow, pow_left, pow_left,
      nat.cast_two, at_two hb, at_two hb'],
  congr' 1, swap, congr' 1,
  { simp_rw [qr_sign],
    rw [χ₄_nat_mod_four, χ₄_nat_mod_four (b % (4 * a)), mod_mod_of_dvd b (dvd_mul_right 4 a) ] },
  { rw [mod_left ↑(b % _), mod_left b, int.coe_nat_mod, int.mod_mod_of_dvd b],
    simp only [ha₂, nat.cast_mul, ← mul_assoc],
    exact dvd_mul_left a' _, },
  cases e, { refl },
  { rw [χ₈_nat_mod_eight, χ₈_nat_mod_eight (b % (4 * a)), mod_mod_of_dvd b],
    use 2 ^ e * a', rw [ha₂, pow_succ], ring, }
end

/-- The Jacobi symbol `J(a | b)` depends only on `b` mod `4*a`. -/
lemma mod_right (a : ℤ) {b : ℕ} (hb : odd b) : J(a | b) = J(a | b % (4 * a.nat_abs)) :=
begin
  cases int.nat_abs_eq a with ha ha; nth_rewrite 1 [ha]; nth_rewrite 0 [ha],
  { exact mod_right' a.nat_abs hb, },
  { have hb' : odd (b % (4 * a.nat_abs)) := hb.mod_even (even.mul_right (by norm_num) _),
    rw [jacobi_sym.neg _ hb, jacobi_sym.neg _ hb', mod_right' _ hb, χ₄_nat_mod_four,
        χ₄_nat_mod_four (b % (4 * _)), mod_mod_of_dvd b (dvd_mul_right 4 _)], }
end

end jacobi_sym

end jacobi


/-
Copyright (c) 2017 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Buzzard, Mario Carneiro

The complex numbers, modelled as R^2 in the obvious way.
-/
import data.real.basic tactic.ring algebra.field

structure complex : Type :=
(re : ℝ) (im : ℝ)

notation `ℂ` := complex

namespace complex

@[simp] theorem eta : ∀ z : ℂ, complex.mk z.re z.im = z
| ⟨a, b⟩ := rfl

theorem ext : ∀ {z w : ℂ}, z.re = w.re → z.im = w.im → z = w
| ⟨zr, zi⟩ ⟨_, _⟩ rfl rfl := rfl

theorem ext_iff {z w : ℂ} : z = w ↔ z.re = w.re ∧ z.im = w.im :=
⟨λ H, by simp [H], and.rec ext⟩

def of_real (r : ℝ) : ℂ := ⟨r, 0⟩
instance : has_coe ℝ ℂ := ⟨of_real⟩
@[simp] lemma of_real_eq_coe (r : ℝ) : of_real r = r := rfl

@[simp] lemma of_real_re (r : ℝ) : (r : ℂ).re = r := rfl
@[simp] lemma of_real_im (r : ℝ) : (r : ℂ).im = 0 := rfl

@[simp] theorem of_real_inj {z w : ℝ} : (z : ℂ) = w ↔ z = w :=
⟨congr_arg re, congr_arg _⟩

instance : has_zero ℂ := ⟨(0 : ℝ)⟩
instance : inhabited ℂ := ⟨0⟩

@[simp] lemma zero_re : (0 : ℂ).re = 0 := rfl
@[simp] lemma zero_im : (0 : ℂ).im = 0 := rfl
lemma of_real_zero : ((0 : ℝ) : ℂ) = 0 := rfl

@[simp] theorem of_real_eq_zero {z : ℝ} : (z : ℂ) = 0 ↔ z = 0 := of_real_inj
@[simp] theorem of_real_ne_zero {z : ℝ} : (z : ℂ) ≠ 0 ↔ z ≠ 0 := not_congr of_real_eq_zero

instance : has_one ℂ := ⟨(1 : ℝ)⟩

@[simp] lemma one_re : (1 : ℂ).re = 1 := rfl
@[simp] lemma one_im : (1 : ℂ).im = 0 := rfl
@[simp] lemma of_real_one : ((1 : ℝ) : ℂ) = 1 := rfl

def I : ℂ := ⟨0, 1⟩

@[simp] lemma I_re : I.re = 0 := rfl
@[simp] lemma I_im : I.im = 1 := rfl

instance : has_add ℂ := ⟨λ z w, ⟨z.re + w.re, z.im + w.im⟩⟩

@[simp] lemma add_re (z w : ℂ) : (z + w).re = z.re + w.re := rfl
@[simp] lemma add_im (z w : ℂ) : (z + w).im = z.im + w.im := rfl
@[simp] lemma of_real_add (r s : ℝ) : ((r + s : ℝ) : ℂ) = r + s := rfl

@[simp] lemma of_real_bit0 (r : ℝ) : ((bit0 r : ℝ) : ℂ) = bit0 r := rfl
@[simp] lemma of_real_bit1 (r : ℝ) : ((bit1 r : ℝ) : ℂ) = bit1 r := rfl

instance : has_neg ℂ := ⟨λ z, ⟨-z.re, -z.im⟩⟩

@[simp] lemma neg_re (z : ℂ) : (-z).re = -z.re := rfl
@[simp] lemma neg_im (z : ℂ) : (-z).im = -z.im := rfl
@[simp] lemma of_real_neg (r : ℝ) : ((-r : ℝ) : ℂ) = -r := ext_iff.2 $ by simp

instance : has_mul ℂ := ⟨λ z w, ⟨z.re * w.re - z.im * w.im, z.re * w.im + z.im * w.re⟩⟩

@[simp] lemma mul_re (z w : ℂ) : (z * w).re = z.re * w.re - z.im * w.im := rfl
@[simp] lemma mul_im (z w : ℂ) : (z * w).im = z.re * w.im + z.im * w.re := rfl
@[simp] lemma of_real_mul (r s : ℝ) : ((r * s : ℝ) : ℂ) = r * s := ext_iff.2 $ by simp

lemma mk_eq_add_mul_I (a b : ℝ) : complex.mk a b = a + b * I :=
ext_iff.2 $ by simp

@[simp] lemma re_add_im (z : ℂ) : (z.re : ℂ) + z.im * I = z :=
ext_iff.2 $ by simp

def conj (z : ℂ) : ℂ := ⟨z.re, -z.im⟩

@[simp] lemma conj_re (z : ℂ) : (conj z).re = z.re := rfl
@[simp] lemma conj_im (z : ℂ) : (conj z).im = -z.im := rfl

@[simp] lemma conj_of_real (r : ℝ) : conj r = r :=
ext_iff.2 $ by simp

@[simp] lemma conj_zero : conj 0 = 0 := conj_of_real 0
@[simp] lemma conj_one : conj 1 = 1 := conj_of_real 1
@[simp] lemma conj_I : conj I = -I := ext_iff.2 $ by simp

@[simp] lemma conj_add (z w : ℂ) : conj (z + w) = conj z + conj w :=
ext_iff.2 $ by simp

@[simp] lemma conj_neg (z : ℂ) : conj (-z) = -conj z :=
ext_iff.2 $ by simp

@[simp] lemma conj_mul (z w : ℂ) : conj (z * w) = conj z * conj w :=
ext_iff.2 $ by simp

@[simp] lemma conj_conj (z : ℂ) : conj (conj z) = z :=
ext_iff.2 $ by simp

lemma conj_bijective : function.bijective conj :=
⟨function.injective_of_has_left_inverse ⟨conj, conj_conj⟩,
 function.surjective_of_has_right_inverse ⟨conj, conj_conj⟩⟩

lemma conj_inj {z w : ℂ} : conj z = conj w ↔ z = w :=
conj_bijective.1.eq_iff

@[simp] lemma conj_eq_zero {z : ℂ} : conj z = 0 ↔ z = 0 :=
by simpa using @conj_inj z 0

@[simp] lemma eq_conj_iff_real (z : ℂ) : conj z = z ↔ ∃ r : ℝ, z = r :=
⟨λ h, ⟨z.re, ext rfl $ eq_zero_of_neg_eq (congr_arg im h)⟩,
 λ ⟨h, e⟩, e.symm ▸ rfl⟩

def norm_sq (z : ℂ) : ℝ := z.re * z.re + z.im * z.im

@[simp] lemma norm_sq_of_real (r : ℝ) : norm_sq r = r * r :=
by simp [norm_sq]

@[simp] lemma norm_sq_zero : norm_sq 0 = 0 := by simp [norm_sq]
@[simp] lemma norm_sq_one : norm_sq 1 = 1 := by simp [norm_sq]
@[simp] lemma norm_sq_I : norm_sq I = 1 := by simp [norm_sq]

lemma norm_sq_nonneg (z : ℂ) : 0 ≤ norm_sq z :=
add_nonneg (mul_self_nonneg _) (mul_self_nonneg _)

@[simp] lemma norm_sq_eq_zero {z : ℂ} : norm_sq z = 0 ↔ z = 0 :=
⟨λ h, ext
  (eq_zero_of_mul_self_add_mul_self_eq_zero h)
  (eq_zero_of_mul_self_add_mul_self_eq_zero $ (add_comm _ _).trans h),
 λ h, h.symm ▸ norm_sq_zero⟩

@[simp] lemma norm_sq_pos {z : ℂ} : 0 < norm_sq z ↔ z ≠ 0 :=
by rw [lt_iff_le_and_ne, ne, eq_comm]; simp [norm_sq_nonneg]

@[simp] lemma norm_sq_neg (z : ℂ) : norm_sq (-z) = norm_sq z :=
by simp [norm_sq]

@[simp] lemma norm_sq_conj (z : ℂ) : norm_sq (conj z) = norm_sq z :=
by simp [norm_sq]

@[simp] lemma norm_sq_mul (z w : ℂ) : norm_sq (z * w) = norm_sq z * norm_sq w :=
by dsimp [norm_sq]; ring

lemma norm_sq_add (z w : ℂ) : norm_sq (z + w) =
  norm_sq z + norm_sq w + 2 * (z * conj w).re :=
by dsimp [norm_sq]; ring

lemma re_sq_le_norm_sq (z : ℂ) : z.re * z.re ≤ norm_sq z :=
le_add_of_nonneg_right (mul_self_nonneg _)

lemma im_sq_le_norm_sq (z : ℂ) : z.im * z.im ≤ norm_sq z :=
le_add_of_nonneg_left (mul_self_nonneg _)

theorem mul_conj (z : ℂ) : z * conj z = norm_sq z :=
ext_iff.2 $ by simp [norm_sq, mul_comm]

theorem add_conj (z : ℂ) : z + conj z = (2 * z.re : ℝ) :=
ext_iff.2 $ by simp [two_mul]

instance : comm_ring ℂ :=
by refine { zero := 0, add := (+), neg := has_neg.neg, one := 1, mul := (*), ..};
   { intros, apply ext_iff.2; split; simp; ring }

@[simp] lemma sub_re (z w : ℂ) : (z - w).re = z.re - w.re := rfl
@[simp] lemma sub_im (z w : ℂ) : (z - w).im = z.im - w.im := rfl
@[simp] lemma of_real_sub (r s : ℝ) : ((r - s : ℝ) : ℂ) = r - s := rfl

theorem sub_conj (z : ℂ) : z - conj z = (2 * z.im : ℝ) * I :=
ext_iff.2 $ by simp [two_mul]

lemma norm_sq_sub (z w : ℂ) : norm_sq (z - w) =
  norm_sq z + norm_sq w - 2 * (z * conj w).re :=
by rw [sub_eq_add_neg, norm_sq_add]; simp [-mul_re]

noncomputable instance : has_inv ℂ := ⟨λ z, conj z * ((norm_sq z)⁻¹:ℝ)⟩

theorem inv_def (z : ℂ) : z⁻¹ = conj z * ((norm_sq z)⁻¹:ℝ) := rfl
@[simp] lemma inv_re (z : ℂ) : (z⁻¹).re = z.re / norm_sq z := by simp [inv_def, division_def]
@[simp] lemma inv_im (z : ℂ) : (z⁻¹).im = -z.im / norm_sq z := by simp [inv_def, division_def]

lemma of_real_inv (r : ℝ) : ((r⁻¹ : ℝ) : ℂ) = r⁻¹ :=
ext_iff.2 $ begin
  simp,
  by_cases r = 0, {simp [h]},
  rw [← div_div_eq_div_mul, div_self h, one_div_eq_inv]
end

lemma inv_zero : (0⁻¹ : ℂ) = 0 :=
by rw [← of_real_zero, ← of_real_inv, inv_zero]

theorem mul_inv_cancel {z : ℂ} (h : z ≠ 0) : z * z⁻¹ = 1 :=
by rw [inv_def, ← mul_assoc, mul_conj, ← of_real_mul,
  mul_inv_cancel (mt norm_sq_eq_zero.1 h), of_real_one]

noncomputable instance : discrete_field ℂ :=
{ inv := has_inv.inv,
  zero_ne_one := mt (congr_arg re) zero_ne_one,
  mul_inv_cancel := @mul_inv_cancel,
  inv_mul_cancel := λ z h, by rw [mul_comm, mul_inv_cancel h],
  inv_zero := inv_zero,
  has_decidable_eq := classical.dec_eq _,
  ..complex.comm_ring }

@[simp] lemma of_real_div (r s : ℝ) : ((r / s : ℝ) : ℂ) = r / s :=
by rw [division_def, of_real_mul, division_def, of_real_inv]

@[simp] theorem of_real_int_cast : ∀ n : ℤ, ((n : ℝ) : ℂ) = n :=
int.eq_cast (λ n, ((n : ℝ) : ℂ)) rfl (by simp)

@[simp] theorem of_real_nat_cast (n : ℕ) : ((n : ℝ) : ℂ) = n :=
by rw [← int.cast_coe_nat, of_real_int_cast]; refl

@[simp] lemma conj_inv (z : ℂ) : conj z⁻¹ = (conj z)⁻¹ :=
if h : z = 0 then by simp [h] else
(domain.mul_left_inj (mt conj_eq_zero.1 h)).1 $
by rw [← conj_mul]; simp [h, -conj_mul]

@[simp] lemma conj_div (z w : ℂ) : conj (z / w) = conj z / conj w :=
by rw [division_def, conj_mul, conj_inv]; refl

@[simp] lemma norm_sq_inv (z : ℂ) : norm_sq z⁻¹ = (norm_sq z)⁻¹ :=
if h : z = 0 then by simp [h] else
(domain.mul_left_inj (mt norm_sq_eq_zero.1 h)).1 $
by rw [← norm_sq_mul]; simp [h, -norm_sq_mul]

@[simp] lemma norm_sq_div (z w : ℂ) : norm_sq (z / w) = norm_sq z / norm_sq w :=
by rw [division_def, norm_sq_mul, norm_sq_inv]; refl

instance char_zero_complex : char_zero ℂ :=
add_group.char_zero_of_inj_zero $ λ n h,
by rwa [← of_real_nat_cast, of_real_eq_zero, nat.cast_eq_zero] at h

@[simp] theorem of_real_rat_cast : ∀ n : ℚ, ((n : ℝ) : ℂ) = n :=
by apply rat.eq_cast (λ n, ((n : ℝ) : ℂ)); simp

theorem re_eq_add_conj (z : ℂ) : (z.re : ℂ) = (z + conj z) / 2 :=
by rw [add_conj]; simp; rw [mul_div_cancel_left (z.re:ℂ) two_ne_zero']

noncomputable def abs (z : ℂ) : ℝ := (norm_sq z).sqrt

local notation `abs'` := _root_.abs

@[simp] lemma abs_of_real (r : ℝ) : abs r = abs' r :=
by simp [abs, norm_sq_of_real, real.sqrt_mul_self_eq_abs]

lemma abs_of_nonneg {r : ℝ} (h : 0 ≤ r) : abs r = r :=
(abs_of_real _).trans (abs_of_nonneg h)

lemma mul_self_abs (z : ℂ) : abs z * abs z = norm_sq z :=
real.mul_self_sqrt (norm_sq_nonneg _)

@[simp] lemma abs_zero : abs 0 = 0 := by simp [abs]
@[simp] lemma abs_one : abs 1 = 1 := by simp [abs]
@[simp] lemma abs_I : abs I = 1 := by simp [abs]

lemma abs_nonneg (z : ℂ) : 0 ≤ abs z :=
real.sqrt_nonneg _

@[simp] lemma abs_eq_zero {z : ℂ} : abs z = 0 ↔ z = 0 :=
(real.sqrt_eq_zero $ norm_sq_nonneg _).trans norm_sq_eq_zero

@[simp] lemma abs_conj (z : ℂ) : abs (conj z) = abs z :=
by simp [abs]

@[simp] lemma abs_mul (z w : ℂ) : abs (z * w) = abs z * abs w :=
by rw [abs, norm_sq_mul, real.sqrt_mul (norm_sq_nonneg _)]; refl

lemma abs_re_le_abs (z : ℂ) : abs' z.re ≤ abs z :=
by rw [mul_self_le_mul_self_iff (_root_.abs_nonneg z.re) (abs_nonneg _),
       abs_mul_abs_self, mul_self_abs];
   apply re_sq_le_norm_sq

lemma abs_im_le_abs (z : ℂ) : abs' z.im ≤ abs z :=
by rw [mul_self_le_mul_self_iff (_root_.abs_nonneg z.im) (abs_nonneg _),
       abs_mul_abs_self, mul_self_abs];
   apply im_sq_le_norm_sq

lemma re_le_abs (z : ℂ) : z.re ≤ abs z :=
(abs_le.1 (abs_re_le_abs _)).2

lemma im_le_abs (z : ℂ) : z.im ≤ abs z :=
(abs_le.1 (abs_im_le_abs _)).2

lemma abs_add (z w : ℂ) : abs (z + w) ≤ abs z + abs w :=
(mul_self_le_mul_self_iff (abs_nonneg _)
  (add_nonneg (abs_nonneg _) (abs_nonneg _))).2 $
begin
  rw [mul_self_abs, add_mul_self_eq, mul_self_abs, mul_self_abs,
      add_right_comm, norm_sq_add, add_le_add_iff_left,
      mul_assoc, mul_le_mul_left (@two_pos ℝ _)],
  simpa [-mul_re] using re_le_abs (z * conj w)
end

instance : is_absolute_value abs :=
{ abv_nonneg  := abs_nonneg,
  abv_eq_zero := λ _, abs_eq_zero,
  abv_add     := abs_add,
  abv_mul     := abs_mul }
open is_absolute_value

@[simp] lemma abs_abs (z : ℂ) : abs' (abs z) = abs z :=
_root_.abs_of_nonneg (abs_nonneg _)

@[simp] lemma abs_pos {z : ℂ} : 0 < abs z ↔ z ≠ 0 := abv_pos abs
@[simp] lemma abs_neg : ∀ z, abs (-z) = abs z := abv_neg abs
lemma abs_sub : ∀ z w, abs (z - w) = abs (w - z) := abv_sub abs
lemma abs_sub_le : ∀ a b c, abs (a - c) ≤ abs (a - b) + abs (b - c) := abv_sub_le abs
@[simp] theorem abs_inv : ∀ z, abs z⁻¹ = (abs z)⁻¹ := abv_inv abs
@[simp] theorem abs_div : ∀ z w, abs (z / w) = abs z / abs w := abv_div abs
lemma abs_abs_sub_le_abs_sub : ∀ z w, abs' (abs z - abs w) ≤ abs (z - w) := abs_abv_sub_le_abv_sub abs

lemma abs_le_abs_re_add_abs_im (z : ℂ) : abs z ≤ abs' z.re + abs' z.im :=
by simpa [re_add_im] using abs_add z.re (z.im * I)

noncomputable def lim (f : ℕ → ℂ) : ℂ :=
⟨real.lim (λ n, (f n).re), real.lim (λ n, (f n).im)⟩

theorem is_cau_seq_re (f : cau_seq ℂ abs) : is_cau_seq abs' (λ n, (f n).re) :=
λ ε ε0, (f.cauchy ε0).imp $ λ i H j ij,
lt_of_le_of_lt (by simpa using abs_re_le_abs (f j - f i)) (H _ ij)

theorem is_cau_seq_im (f : cau_seq ℂ abs) : is_cau_seq abs' (λ n, (f n).im) :=
λ ε ε0, (f.cauchy ε0).imp $ λ i H j ij,
lt_of_le_of_lt (by simpa using abs_im_le_abs (f j - f i)) (H _ ij)

theorem equiv_lim (f : cau_seq ℂ abs) : f ≈ cau_seq.const abs (lim f) :=
λ ε ε0, (exists_forall_ge_and
  (real.equiv_lim ⟨_, is_cau_seq_re f⟩ _ (half_pos ε0))
  (real.equiv_lim ⟨_, is_cau_seq_im f⟩ _ (half_pos ε0))).imp $
λ i H j ij, begin
  cases H _ ij with H₁ H₂,
  apply lt_of_le_of_lt (abs_le_abs_re_add_abs_im _),
  simpa using add_lt_add H₁ H₂
end

end complex


---------------
-- *Tactics* --
---------------

--------------------------
-- Entering Tactic Mode --
--------------------------

theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by
  apply And.intro
  exact hp
  apply And.intro
  exact hq
  exact hp

/- The *apply tactic* applies an expression, viewed as denoting a function with
zero or more arguments. It unifies the conclusion with the expression in the
current goal, and creates new goals for the remaining arguments, provided that
no later arguments depend on them. In the example above, the command
*apply And.intro* yields two subgoals:

    case left
    p : Prop,
    q : Prop,
    hp : p,
    hq : q
    ⊢ p

    case right
    p : Prop,
    q : Prop,
    hp : p,
    hq : q
    ⊢ q ∧ p

-/

/- The first goal is met with the command *exact hp*. The exact command is just a
variant of apply which signals that the expression given should fill the goal
exactly. It is good form to use it in a tactic proof, since its failure signals
that something has gone wrong. It is also more robust than apply, since the
elaborator takes the expected type, given by the target of the goal, into
account when processing the expression that is being applied. In this case,
however, apply would work just as well.-/

/- You can see the resulting proof term with the *#print* command:-/

#print test

/- Tactic commands can take compound expressions, not just single identifiers.
The following is a shorter version of the preceding proof: -/

theorem test' (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by
  apply And.intro hp
  exact And.intro hq hp

/- Tactics that may produce multiple subgoals often tag them. For example, the
tactic apply And.intro tagged the first sugoal as left, and the second as right.
In the case of the apply tactic, the tags are inferred from the parameters
names used in the And.intro declaration. You can structure your tactics using
the notation case <tag> => <tactics>. The following is a structured version of
our first tactic proof in this chapter. -/

theorem test'' (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by
  apply And.intro
  case left => exact hp
  case right =>
    apply And.intro
    case left => exact hq
    case right => exact hp

/- For simple sugoals, it may not be worth selecting a subgoal using its tag, but you may still
 want to structure the proof. Lean also provides the "bullet" notation
 . <tactics> (or · <tactics>) for structuring proof. -/

theorem test''' (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by
  apply And.intro
  . exact hp
  . apply And.intro
    . exact hq
    . exact hp

-------------------
-- Basic Tactics --
-------------------

/- In addition to apply and exact, another useful tactic is *intro*, which
introduces a hypothesis. What follows is an example of an identity from
propositional logic that we proved in a previous chapter, now proved using
tactics. -/

example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by
  apply Iff.intro
  . intro h
    apply Or.elim (And.right h)
    . intro hq
      apply Or.inl
      apply And.intro
      . exact And.left h
      . exact hq
    . intro hr
      apply Or.inr
      apply And.intro
      . exact And.left h
      . exact hr
  . intro h
    apply Or.elim h
    . intro hpq
      apply And.intro
      . exact And.left hpq
      . apply Or.inl
        exact And.right hpq
    . intro hpr
      apply And.intro
      . exact And.left hpr
      . apply Or.inr
        exact And.right hpr

/- The intro command can more generally be used to introduce a variable of any type: -/

example (α : Type) : α → α := by
  intro a
  exact a

example (α : Type) : ∀ x : α, x = x := by
  intro x
  exact rfl

/- You can use it to introduce several variables: -/

example : ∀ a b c : Nat, a = b → a = c → c = b := by
  intro a b c h₁ h₂
  exact Eq.trans (Eq.symm h₂) h₁

/- As the apply tactic is a command for constructing function applications
interactively, the intro tactic is a command for constructing function
abstractions interactively (i.e., terms of the form fun x => e). As with
lambda abstraction notation, the intro tactic allows us to use an implicit match.-/

example (α : Type) (p q : α → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
  intro ⟨w, hpw, hqw⟩
  exact ⟨w, hqw, hpw⟩

/- You can also provide multiple alternatives like in the match expression.-/
example (α : Type) (p q : α → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
  intro
  | ⟨w, hpw, hqw⟩ => exact ⟨w, hqw, hpw⟩

example (α : Type) (p q : α → Prop) : (∃ x, p x ∨ q x) → ∃ x, q x ∨ p x := by
  intro
    | ⟨w, Or.inl h⟩ => exact ⟨w, Or.inr h⟩
    | ⟨w, Or.inr h⟩ => exact ⟨w, Or.inl h⟩

/- The *assumption* tactic looks through the assumptions in context of the
current goal, and if there is one matching the conclusion, it applies it. -/

example (x y z w : Nat) (h₁ : x = y) (h₂ : y = z) (h₃ : z = w) : x = w := by
  apply Eq.trans h₁
  apply Eq.trans h₂
  assumption

example (x y z w : Nat) (h₁ : x = y) (h₂ : y = z) (h₃ : z = w) : x = w := by
  apply Eq.trans h₁
  apply Eq.trans h₂
  apply Eq.trans h₃
  exact rfl

/- The following example uses the intros command to introduce the three
variables and two hypotheses automatically: -/

example : ∀ a b c : Nat, a = b → a = c → c = b := by
  intros
  apply Eq.trans
  apply Eq.symm
  assumption
  assumption

/- The rfl tactic is syntax sugar for exact rfl -/
example (y : Nat) : (fun x : Nat => 0) y = 0 :=
  by rfl

/- The repeat combinator can be used to apply a tactic several times. -/
example : ∀ a b c : Nat, a = b → a = c → c = b := by
  intros
  apply Eq.trans
  apply Eq.symm
  repeat assumption

/- Another tactic that is sometimes useful is the *revert* tactic,
which is, in a sense, an inverse to intro.-/

example (x : Nat) : x = x := by
  revert x -- goal is ⊢ ∀ (x : Nat), x = x
  intro y -- goal is y : Nat ⊢ y = y
  rfl

example (x : Nat) : x = x := by rfl

/- Moving a hypothesis into the goal yields an implication: -/
example (x y : Nat) (h : x = y) : y = x := by
  apply Eq.symm h

example (x y : Nat) (h : x = y) : y = x := by
  revert h
  intro h₁
  apply Eq.symm
  assumption

/- But revert is even more clever, in that it will revert not only an element of
the context but also all the subsequent elements of the context that depend on it.
For example, reverting x in the example above brings h along with it: -/

example (x y : Nat) (h : x = y) : y = x := by
  revert x -- goal is y : Nat ⊢ ∀ (x : Nat), x = y → y = x
  intros
  apply Eq.symm
  assumption

/- The mnemonic in the notation above is that you are generalizing the goal by
setting 3 to an arbitrary variable x. Be careful: not every generalization
preserves the validity of the goal. Here, *generalize* replaces a goal that could
be proved using rfl with one that is not provable:-/

example : 2 + 3 = 5 := by rfl

example : 2 + 3 = 5 := by
  generalize  3 = x -- goal is x : Nat ⊢ 2 + x = 5
  admit

/- In this example, the admit tactic is the analogue of the sorry proof term.
It closes the current goal, producing the usual warning that sorry has been used.
To preserve the validity of the previous goal, the generalize tactic allows us
to record the fact that 3 has been replaced by x. All you need to do is to
provide a label, and generalize uses it to store the assignment in the local
context:-/

example : 2 + 3 = 5 := by
  generalize h : 3 = x -- goal is x : Nat, h : 3 = x ⊢ 2 + x = 5
  rw [← h]

--------------------
-- *More Tactics* --
--------------------

/- Some additional tactics are useful for constructing and destructing propositions
and data. For example, when applied to a goal of the form p ∨ q, you use tactics
such as apply Or.inl and apply Or.inr. Conversely, the cases tactic can be used to
decompose a disjunction. -/

example (p q : Prop) : p ∨ q → q ∨ p := by
  intro h
  cases h with
  | inl hp => apply Or.inr; exact hp
  | inr hq => apply Or.inl; exact hq

/- You can also use a *(unstructured)* cases without the with and a tactic for
each alternative. -/

example (p q : Prop) : p ∨ q → q ∨ p := by
  intro h
  cases h
  apply Or.inr
  assumption
  apply Or.inl
  assumption

/- The (unstructured) cases is particularly useful when you can close several
subgoals using the same tactic. -/

-- example (p q : Prop) : p ∨ q → q ∨ p := by
--   intro h
--   cases h
--   repeat assumption

/- The cases tactic can also be used to decompose a conjunction. -/

example (p q : Prop) : p ∧ q → q ∧ p := by
  intro h
  cases h with
  | intro hp hq => constructor; exact hq; exact hp

/- You can use cases and constructor with an existential quantifier: -/

example (p q : Nat → Prop) : (∃ x, p x) → ∃ x, p x ∨ q x := by
  intro h
  cases h with
  | intro x px => constructor; apply Or.inl; exact px

/- Here, the constructor tactic leaves the first component of the existential
assertion, the value of x, implicit. It is represented by a metavariable, which
should be instantiated later on. In the previous example, the proper value of the
metavariable is determined by the tactic exact px, since px has type p x. If you
want to specify a witness to the existential quantifier explicitly, you can use
the *exists tactic* instead -/

example (p q : Nat → Prop) : (∃ x, p x) → ∃ x, p x ∨ q x := by
  intro h
  cases h with
  | intro x px => exists x; apply Or.inl; exact px

example (p q : Nat → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
  intro h
  cases h with
  | intro x hpq =>
    cases hpq with
    | intro hp hq => exists x; exact And.intro hq hp

example (p q : Nat → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
  intro h
  cases h with
  | intro x hpq =>
    cases hpq with
    | intro hp hq =>
      exists x
      constructor <;> assumption

/- These tactics can be used on data just as well as propositions. In the next
two examples, they are used to define functions which swap the components of the
product and sum types: -/

def swap_pair : α × β → β × α := by
  intro p
  cases p
  constructor <;> assumption

def swap_sum : Sum α β → Sum β α := by
  intro p
  cases p
  . apply Sum.inr; assumption
  . apply Sum.inl; assumption

/- Note that up to the names we have chosen for the variables, the definitions
are identical to the proofs of the analogous propositions for conjunction and
disjunction. The cases tactic will also do a case distinction on a natural number: -/

open Nat
example (P : Nat → Prop) (h₀ : P 0) (h₁ : ∀ n, P (succ n)) (m : Nat) : P m := by
  cases m with
  | zero => exact h₀
  | succ m' => exact h₁ m'

/- The *contradiction* tactic searches for a contradiction among the hypotheses of
the current goal: -/

example (p q : Prop) : p ∧ ¬ p → q := by
  intro h
  cases h
  contradiction

/- You can also use match in tactic blocks. -/

example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by
  apply Iff.intro
  . intro
  | ⟨hp, Or.inl hq⟩ => exact Or.inl ⟨hp, hq⟩
  | ⟨hp, Or.inr hr⟩ => exact Or.inr ⟨hp, hr⟩
  . intro
  | Or.inl ⟨hp, hq⟩ => exact ⟨hp, Or.inl hq⟩
  | Or.inr ⟨hp, hr⟩ => exact ⟨hp, Or.inr hr⟩

example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by
  apply Iff.intro
  . intro
     | ⟨hp, Or.inl hq⟩ => apply Or.inl; constructor <;> assumption
     | ⟨hp, Or.inr hr⟩ => apply Or.inr; constructor <;> assumption
  . intro
     | Or.inl ⟨hp, hq⟩ => constructor; assumption; apply Or.inl; assumption
     | Or.inr ⟨hp, hr⟩ => constructor; assumption; apply Or.inr; assumption

---------------------------------
-- *Structuring Tactic Proofs* --
---------------------------------

/- Tactics often provide an efficient way of building a proof, but long sequences
of instructions can obscure the structure of the argument. In this section, we
describe some means that help provide structure to a *tactic-style proof*, making
such proofs more readable and robust. -/

/- One thing that is nice about Lean's proof-writing syntax is that it is
possible to *mix term-style and tactic-style proofs*, and pass between the two
freely. For example, the tactics apply and exact expect arbitrary terms, which
you can write using have, show, and so on. Conversely, when writing an arbitrary
Lean term, you can always invoke the tactic mode *by inserting a by block*. The
following is a somewhat toy example: -/

example (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) := by
  intro h
  exact
    have hp : p := h.left
    have hqr : q ∨ r := h.right
    show (p ∧ q) ∨ (p ∧ r) by
      cases hqr with
      | inl hq => exact Or.inl ⟨hp, hq⟩
      | inr hr => exact Or.inr ⟨hp, hr⟩

-- The following is a more natural example:
example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by
  apply Iff.intro
  . intro h
    cases h.right with
    | inl hq =>
      show (p ∧ q) ∨ (p ∧ r)
      exact Or.inl ⟨h.left, hq⟩
    | inr hr =>
      show (p ∧ q) ∨ (p ∧ r)
      exact Or.inr ⟨h.left, hr⟩
  . intro h
    cases h with
    | inl hpq =>
      show p ∧ (q ∨ r)
      exact ⟨hpq.left, Or.inl hpq.right⟩
    | inr hpr =>
      show p ∧ (q ∨ r)
      exact ⟨hpr.left, Or.inr hpr.right⟩

/- The show tactic can actually be used to rewrite a goal to something
definitionally equivalent: -/

example (n : Nat) : n + 1 = Nat.succ n := by rfl

example (n : Nat) : n + 1 = Nat.succ n := by
  show Nat.succ n = Nat.succ n
  rfl

/- There is also a *have tactic*, which introduces a new subgoal, just as when
writing proof terms: -/

example (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) := by
  intro ⟨hp, hqr⟩
  show (p ∧ q) ∨ (p ∧ r)
  cases hqr with
  | inl hq =>
    have hpq : p ∧ q := And.intro hp hq
    apply Or.inl
    exact hpq
  | inr hr =>
    have hpr : p ∧ r := And.intro hp hr
    apply Or.inr
    exact hpr

/- As with proof terms, you can omit the label in the have tactic, in which case,
the default label this is used: -/

example (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) := by
  intro ⟨hp, hqr⟩
  show (p ∧ q) ∨ (p ∧ r)
  cases hqr with
  | inl hq =>
    have : p ∧ q := And.intro hp hq
    apply Or.inl
    exact this
  | inr hr =>
    have : p ∧ r := And.intro hp hr
    apply Or.inr
    exact this

/- The types in a have tactic can be omitted, so you can write have
*hp := h.left* and have *hqr := h.right*. In fact, with this notation, you can
even omit both the type and the label, in which case the new fact is introduced
with the label this. -/

example (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) := by
  intro ⟨hp, hqr⟩
  cases hqr with
  | inl hq =>
    have := And.intro hp hq
    apply Or.inl; exact this
  | inr hr =>
    have := And.intro hp hr
    apply Or.inr; exact this

/- Lean also has a *let* tactic, which is similar to the *have* tactic, but is
used to introduce local definitions instead of auxiliary facts. It is the tactic
analogue of a let in a proof term. -/

example : ∃ x, x + 2 = 8 := by
  let a : Nat := 6
  exists a
  rfl

/- We have used . to create nested tactic blocks. In a nested block, Lean focuses
on the first goal, and generates an error if it has not been fully solved at the
end of the block. This can be helpful in indicating the separate proofs of
multiple subgoals introduced by a tactic. The notation . is whitespace sensitive
and relies on the indentation to detect whether the tactic block ends.
Alternatively, you can define tactic blocks usind curly braces and semicolons. -/

example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by
  apply Iff.intro
  { intro h;
    cases h.right;
    { show (p ∧ q) ∨ (p ∧ r);
      exact Or.inl ⟨h.left, ‹q›⟩ }
    { show (p ∧ q) ∨ (p ∧ r);
      exact Or.inr ⟨h.left, ‹r›⟩ } }
  { intro h;
    cases h;
    { show p ∧ (q ∨ r);
      rename_i hpq;
      exact ⟨hpq.left, Or.inl hpq.right⟩ }
    { show p ∧ (q ∨ r);
      rename_i hpr;
      exact ⟨hpr.left, Or.inr hpr.right⟩ } }

--------------------------
-- *Tactic Combinators* --
--------------------------

/- *Tactic combinators* are operations that form new tactics from old ones.
A sequencing combinator is already implicit in the by block: -/

example (p q : Prop) (hp : p) : p ∨ q := by
  apply Or.inl; assumption

/- Here, apply Or.inl; assumption is functionally *equivalent to a single tactic*
which first applies apply Or.inl and then applies assumption.

In *t₁ <;> t₂*, the <;> operator provides a parallel version of the sequencing
operation: *t₁ is applied to the current goal, and then t₂ is applied to all*
the resulting subgoals:
-/

example (p q : Prop) (hp : p) (hq : q) : p ∧ q :=
  by constructor <;> assumption

/- This is especially useful when the resulting goals can be finished off in a
uniform way, or, at least, when it is possible to make progress on all of them
uniformly.

The *first | t₁ | t₂ | ... | tn* applies each tᵢ until one succeeds, or else fails: -/

example (p q : Prop) (hp : p) : p ∨ q := by
  first | apply Or.inl; assumption | apply Or.inr; assumption

example (p q : Prop) (hp : p) : p ∨ q := by
  first | apply Or.inl; assumption

/- In the first example, the left branch succeeds, whereas in the second one, it
is the right one that succeeds. In the next three examples, the same compound
tactic succeeds in each case. -/

example (p q r : Prop) (hp : p) : p ∨ q ∨ r :=
  by repeat (first | apply Or.inl; assumption | apply Or.inr | assumption)

example (p q r : Prop) (hq : q) : p ∨ q ∨ r :=
  by repeat (first | apply Or.inl; assumption | apply Or.inr | assumption)

example (p q r : Prop) (hr : r) : p ∨ q ∨ r :=
  by repeat (first | apply Or.inl; assumption | apply Or.inr | assumption)

-- *Be careful: repeat (try t) will loop forever, because the inner tactic never fails.*

/- In a proof, there are often multiple goals outstanding. Parallel sequencing is
one way to arrange it so that a single tactic is applied to multiple goals, but
there are other ways to do this. For example, *all_goals t* applies t to all open
goals: -/

example (p q r : Prop) (hp : p) (hq : q) (hr : r) : p ∧ q ∧ r := by
  constructor
  all_goals (try constructor)
  all_goals assumption

/- In this case, the *any_goals* tactic provides a more robust solution. It is
similar to all_goals, except it fails unless its argument succeeds on at
least one goal. -/

example (p q r : Prop) (hp : p) (hq : q) (hr : r) : p ∧ q ∧ r := by
  constructor
  any_goals constructor
  any_goals assumption

-- The first tactic in the by block below repeatedly splits conjunctions:
example (p q r : Prop) (hp : p) (hq : q) (hr : r) :
    p ∧ ((p ∧ q) ∧ r) ∧ (q ∧ r ∧ p) := by
  repeat (any_goals constructor)
  all_goals assumption

-- In fact, we can compress the full tactic down to one line:
example (p q r : Prop) (hp : p) (hq : q) (hr : r) :
      p ∧ ((p ∧ q) ∧ r) ∧ (q ∧ r ∧ p) := by
  repeat (any_goals (first | constructor | assumption))

-----------------
-- *Rewriting* --
-----------------

-- Here are some more examples with lists:
open List

example (xs : List Nat)
        : reverse (xs ++ [1, 2, 3]) = [3, 2, 1] ++ reverse xs := by
  simp

example (xs ys : List α)
        : length (reverse (xs ++ ys)) = length xs + length ys := by
  simp [Nat.add_comm]

example (xs ys : List α)
        : length (reverse (xs ++ ys)) = length xs + length ys := by
  simp
  apply Nat.add_comm