/-
Interpretation of memory constraints.
-/
import starkware.cairo.lean.semantics.util

noncomputable theory

open_locale classical big_operators

/-
General facts about polyomials
-/

namespace polynomial_aux

variables {F : Type*} [field F] [fintype F]
variables {n : Type*} [fintype n] (a b : n → F)

open finset polynomial

def mprod := ((univ.val.map a).map (λ r, X - C r)).prod

lemma nat_degree_mprod : (mprod a).nat_degree = fintype.card n :=
by { rw [mprod, nat_degree_multiset_prod_X_sub_C (univ.val.map a), multiset.card_map], refl }

lemma eq_of_mprod_eq (h : mprod a = mprod b) : univ.val.map a = univ.val.map b :=
begin
  have : (mprod a).roots = (mprod b).roots := congr_arg _ h,
  rwa [mprod, mprod, multiset_prod_X_sub_C_roots, multiset_prod_X_sub_C_roots] at this
end

lemma prod_eq_mprod (z : F) : ∏ i : n, (z - a i) = (mprod a).eval z :=
by { simp [mprod], refl }

theorem card_roots_le_or_all_eq [fintype F] :
  card (univ.filter (λ z : F, ∏ i, (z - a i) = ∏ i, (z - b i))) ≤ fintype.card n ∨
    ∀ z, ∏ i, (z - a i) = ∏ i, (z - b i) :=
begin
  have h₀ : ∀ z, (∏ i, (z - a i)) - (∏ i, (z - b i)) = (mprod a - mprod b).eval z,
  { intro z, simp [mprod], refl },
  by_cases h : mprod a - mprod b = 0,
  { right, intro z, rw [←sub_eq_zero, h₀, h, eval_zero] },
  have : univ.filter (λ z : F, ∏ i, (z - a i) = ∏ i, (z - b i)) =
          (mprod a - mprod b).roots.to_finset,
  { ext z; simp [mem_roots h], rw [←sub_eq_zero, h₀, eval_sub] },
  left, rw this,
  apply (multiset.to_finset_card_le _).trans,
  apply (card_roots' _).trans,
  apply (nat_degree_add (mprod a) _).trans,
  rw [nat_degree_neg, nat_degree_mprod, nat_degree_mprod, max_self]
end

def exceptional_set : finset F :=
if ∀ z, ∏ i, (z - a i) = ∏ i, (z - b i) then ∅ else
  univ.filter (λ z : F, ∏ i : n, (z - a i) = ∏ i, (z - b i))

@[simp] theorem exception_set_eq_pos (h : ∀ z, ∏ i, (z - a i) = ∏ i, (z - b i)) :
  exceptional_set a b = ∅ :=
by rw [exceptional_set, if_pos h]

@[simp] theorem exception_set_eq_neg (h : ¬ ∀ z, ∏ i, (z - a i) = ∏ i, (z - b i)) :
  exceptional_set a b = univ.filter (λ z : F, ∏ i : n, (z - a i) = ∏ i, (z - b i)) :=
by rw [exceptional_set, if_neg h]

theorem card_exceptional_set_le : card (exceptional_set a b) ≤ fintype.card n :=
begin
  by_cases h : (∀ z, ∏ i, (z - a i) = ∏ i, (z - b i)); simp [h],
  exact (card_roots_le_or_all_eq a b).resolve_right h
end

theorem all_eq_of_not_mem_exceptional_set {z : F}
    (h₁ : z ∉ exceptional_set a b)
    (h₂ : ∏ i : n, (z - a i) = ∏ i, (z - b i)) :
  ∀ z, ∏ i, (z - a i) = ∏ i, (z - b i) :=
begin
  by_contradiction h,
  simp [exceptional_set, if_neg h] at h₁,
  contradiction
end

end polynomial_aux

section

variables {F : Type*} [field F] {n : ℕ} (a b : fin n → F)

theorem prod_sub_eq_zero_iff (z : F) :
  ∏ i : fin n, (z - a i) = 0 ↔ ∃ j : fin n, z = a j :=
begin
  induction n with n ih,
  { simp },
  rw [fin.prod_univ_cast_succ, mul_eq_zero, ih, fin.exists_fin_cast_succ],
  simp only [sub_eq_zero]
end

variable [fintype F]

theorem exceptional_set_spec {z : F}
    (h₁ : z ∉ polynomial_aux.exceptional_set a b)
    (h₂ : ∏ i : fin n, (z - a i) = ∏ i, (z - b i)) :
  ∀ i, ∃ j, a i = b j :=
begin
  intro i,
  have : ∏ i' : fin n, (a i - a i') = 0,
  { rw prod_sub_eq_zero_iff, use i },
  rw polynomial_aux.all_eq_of_not_mem_exceptional_set a b h₁ h₂ at this,
  rwa ←prod_sub_eq_zero_iff
end

theorem exceptional_set_spec' {z : F}
    (h₁ : z ∉ polynomial_aux.exceptional_set a b)
    (h₂ : ∏ i : fin n, (z - a i) = ∏ i, (z - b i)) :
  ∀ i, ∃ j, b i = a j :=
begin
  intro i,
  have : ∏ i' : fin n, (b i - b i') = 0,
  { rw prod_sub_eq_zero_iff, use i },
  rw ←polynomial_aux.all_eq_of_not_mem_exceptional_set a b h₁ h₂ at this,
  rwa ←prod_sub_eq_zero_iff
end

end

/-
The constraints.

Note: where the whitepaper assumes `n > 0`, we use `n + 1` instead.
-/

section constraints

variables {F : Type*} [field F]

variables {n : ℕ} {a v a' v' p : fin (n + 1) → F}

variables {alpha z : F}

variable h_continuity :
  ∀ i : fin n, (a' i.succ - a' i.cast_succ) * (a' i.succ - a' i.cast_succ - 1) = 0

variable h_single_valued :
  ∀ i : fin n, (v' i.succ - v' i.cast_succ) * (a' i.succ - a' i.cast_succ - 1) = 0

variable h_initial : (z - (a' 0 + alpha * v' 0)) * p 0 = z - (a 0 + alpha * v 0)

variable h_cumulative : ∀ i : fin n, (z - (a' i.succ + alpha * v' i.succ)) * p i.succ =
                                       (z - (a i.succ + alpha * v i.succ)) * p i.cast_succ

variable h_final : p (fin.last n) = 1

/-
See also `hprob₁`, `hprob₂`, and `char_lt` below.
-/

def a'_step (a' : fin (n + 1) → F) (i : fin n) : ℕ :=
if a' i.succ = a' i.cast_succ then 0 else 1

lemma a'_step_of_eq (i : fin n) (h : a' i.succ = a' i.cast_succ) :
  a'_step a' i = 0 :=
by rw [a'_step, if_pos h]

lemma a'_step_of_eq_add_one (i : fin n) (h : a' i.succ = a' i.cast_succ + 1) :
  a'_step a' i = 1 :=
by rw [a'_step, if_neg]; simp [h]

def a'_nat_offset (a' : fin (n + 1) → F) (i : fin (n + 1)) : ℕ :=
∑ j in fin.range i, (a'_step a') j

@[simp] lemma a'_nat_offset_zero : a'_nat_offset a' 0 = 0 := fin.sum_range_zero _

@[simp] lemma a'_nat_offset_succ (i : fin n) :
  a'_nat_offset a' i.succ = a'_nat_offset a' i.cast_succ + a'_step a' i :=
by rw [a'_nat_offset, fin.sum_range_succ _ _, add_comm]; refl

lemma monotone_a'_nat_offset : monotone (a'_nat_offset a') :=
by { intros i j ilej, apply finset.sum_mono_set, apply fin.range_subset.mpr ilej }

lemma a'_nat_offset_le (i : fin (n + 1)) : a'_nat_offset a' i ≤ ↑i :=
begin
  apply fin.induction_on i; simp,
  intros i' ih,
  apply add_le_add ih _,
  by_cases h : a' i'.succ = a' i'.cast_succ; simp [a'_step, h]
end

lemma a'_nat_offset_le' (i : fin (n + 1)) : a'_nat_offset a' i ≤ n :=
le_trans (monotone_a'_nat_offset (fin.le_last i)) (a'_nat_offset_le _)

section h_continuity
include h_continuity

lemma a'_succ_eq (i : fin n) : a' i.succ = a' i.cast_succ ∨ a' i.succ = a' i.cast_succ + 1 :=
begin
  cases (eq_zero_or_eq_zero_of_mul_eq_zero $ h_continuity i) with h h,
  { left, apply eq_of_sub_eq_zero h },
  right, apply eq_of_sub_eq_zero, rw ←h, abel
end

lemma a'_succ_eq' (i : fin n) : a' i.succ = a' i.cast_succ + a'_step a' i :=
begin
  cases (a'_succ_eq h_continuity i) with h h,
  { simp [a'_step_of_eq _ h, h] },
  simp [a'_step_of_eq_add_one _ h, h]
end

lemma a'_nat_offset_spec (i : fin (n + 1)) : a' i = a' 0 + a'_nat_offset a' i :=
begin
  apply fin.induction_on i; simp [a'_succ_eq' h_continuity],
  intros i' ih, rw [ih, add_assoc]
end

lemma nat_offset_eq (h_n_lt : n < ring_char F) {i j : fin (n + 1)} (h : a' i = a' j) :
  a'_nat_offset a' i = a'_nat_offset a' j :=
begin
  have : ↑i < ring_char F := lt_of_le_of_lt (nat.le_of_lt_succ i.property) h_n_lt,
  have : ↑j < ring_char F := lt_of_le_of_lt (nat.le_of_lt_succ j.property) h_n_lt,
  rw [a'_nat_offset_spec h_continuity i, a'_nat_offset_spec h_continuity j] at h,
  apply nat.cast_inj_of_lt_char _ _ (add_left_cancel h);
  { apply lt_of_le_of_lt (a'_nat_offset_le _), assumption }
end

lemma a'_continuous_aux (i : fin (n + 1)) :
  ∀ k ≤ a'_nat_offset a' i, ∃ j ≤ i, a'_nat_offset a' j = k :=
begin
  apply fin.induction_on i; simp,
  { use [0, le_refl _], simp },
  intros i' ih k,
  cases (a'_succ_eq h_continuity i') with h h,
  { simp [a'_step_of_eq _ h, h],
    intro hk,
    rcases ih k hk with ⟨j, hj, hj'⟩,
    exact ⟨j, le_trans hj (le_of_lt (fin.cast_succ_lt_succ _)), hj'⟩ },
   simp [a'_step_of_eq_add_one _ h, h],
   intro hk,
   cases (nat.of_le_succ hk) with hk' hk',
   { rcases ih k hk' with ⟨j, hj, hj'⟩,
     exact⟨j, le_trans hj (le_of_lt (fin.cast_succ_lt_succ _)), hj'⟩ },
   refine ⟨i'.succ, le_refl _, _⟩,
  rw [hk', a'_nat_offset_succ, a'_step_of_eq_add_one _ h]
end

lemma a'_continuous (k : ℕ) (hk : k ≤ a'_nat_offset a' (fin.last n)) :
  ∃ j, a'_nat_offset a' j = k :=
begin
  rcases a'_continuous_aux h_continuity _ _ hk with ⟨j, _, hj⟩,
  exact ⟨j, hj⟩
end

section single_valued
include h_single_valued

lemma a'_single_valued_aux {i : fin n} (h : a' i.succ = a' i.cast_succ) :
  v' i.succ = v' i.cast_succ :=
begin
  have := h_single_valued i,
  simp [h] at this,
  symmetry,
  exact eq_of_sub_eq_zero this
end

lemma a'_single_valued_aux' (h_n_lt : n < ring_char F) (i : fin (n + 1)) :
  ∀ j < i, a' i = a' j → v' i = v' j :=
begin
  apply i.induction_on,
  { intros j hj, exfalso, apply fin.not_lt_zero _ hj },
  intros i' ih j hj a'eq,
  have hj' : j ≤ i'.cast_succ := fin.le_of_lt_succ hj,
  have a'eq2 : a' i'.succ = a' i'.cast_succ,
  { rw [a'eq, a'_nat_offset_spec h_continuity, a'_nat_offset_spec h_continuity i'.cast_succ],
    congr' 2, apply le_antisymm (monotone_a'_nat_offset $ fin.le_of_lt_succ hj),
    rw ←nat_offset_eq h_continuity h_n_lt a'eq,
    apply monotone_a'_nat_offset (le_of_lt $ fin.cast_succ_lt_succ _) },
  rw [a'_single_valued_aux h_continuity h_single_valued a'eq2],
  cases (lt_or_eq_of_le hj') with h h,
  { exact ih j h (a'eq2.symm.trans a'eq) },
  rw h
end

lemma a'_single_valued (h_n_lt : n < ring_char F) {i j : fin (n + 1)} :
  a' i = a' j → v' i = v' j :=
begin
  have : i < j ∨ i = j ∨ j < i := trichotomous i j,
  rcases this with h | rfl | h,
  { intro h', symmetry,
    exact a'_single_valued_aux' h_continuity h_single_valued h_n_lt _ _ h h'.symm },
  { intro _, refl },
  exact a'_single_valued_aux' h_continuity h_single_valued h_n_lt _ _ h
end

end single_valued

end h_continuity

section

variable [fintype F]

def bad_set_1 (a v a' v' : fin (n + 1) → F) : finset F :=
finset.univ.filter (λ alpha, ∃ i j, v i ≠ v' j ∧ a i + alpha * v i = a' j + alpha * v' j)

@[simp] theorem mem_bad_set_1 {a v a' v' : fin (n + 1) → F} (alpha : F) :
  alpha ∈ bad_set_1 a v a' v' ↔ ∃ i j, v i ≠ v' j ∧ a i + alpha * v i = a' j + alpha * v' j :=
by { rw [bad_set_1, finset.mem_filter], simp }

theorem card_bad_set_1_le  : (bad_set_1 a v a' v').card ≤ (n + 1) * (n + 1) :=
let f := λ p : fin (n + 1) × fin (n + 1), (a p.1 - a' p.2) / (v' p.2 - v p.1) in
calc
    (bad_set_1 a v a' v').card ≤ (finset.image f finset.univ).card :
      begin
        apply finset.card_le_of_subset,
        intros alpha,
        simp,
        intros i j hv ha,
        use [i, j], dsimp [f],
        have : v' j - v i ≠ 0,
        { intro h, apply hv, symmetry, apply eq_of_sub_eq_zero h },
        rw [div_eq_iff this, mul_sub, sub_eq_iff_eq_add, sub_add_eq_add_sub, add_comm, ←ha,
             add_sub_cancel]
      end
    ... ≤ (finset.univ : finset (fin (n + 1) × fin (n + 1))).card : finset.card_image_le
    ... = fintype.card (fin (n + 1) × fin (n + 1))                : rfl
    ... = (n + 1) * (n + 1)                                       : by simp

theorem bad_set_1_spec (h : alpha ∉ bad_set_1 a v a' v') {i j : fin (n + 1)}
    (h' : a i + alpha * v i = a' j + alpha * v' j) :
  v i = v' j ∧ a i = a' j :=
begin
  simp at h,
  specialize h i j,
  have : v i = v' j := by_contradiction (λ h₀, h h₀ h'),
  split, { exact this },
  rw this at h',
  exact add_right_cancel h'
end

def bad_set_2 (a v a' v' : fin (n + 1) → F) (alpha : F) : finset F :=
polynomial_aux.exceptional_set (λ i, a i + alpha * v i) (λ i, a' i + alpha * v' i)

theorem card_bad_set_2_le : (bad_set_2 a v a' v' alpha).card ≤ n + 1 :=
by { transitivity, apply polynomial_aux.card_exceptional_set_le, simp }

theorem bad_set_2_spec {z : F}
    (h₁ : z ∉ bad_set_2 a v a' v' alpha)
    (h₂ : ∏ i : fin (n + 1), (z - (a i + alpha * v i)) = ∏ i, (z - (a' i + alpha * v' i))) :
  ∀ i, ∃ j, a i + alpha * v i = a' j + alpha * v' j :=
exceptional_set_spec _ _ h₁ h₂

theorem bad_set_2_spec' {z : F}
    (h₁ : z ∉ bad_set_2 a v a' v' alpha)
    (h₂ : ∏ i : fin (n + 1), (z - (a i + alpha * v i)) = ∏ i, (z - (a' i + alpha * v' i))) :
  ∀ i, ∃ j, a' i + alpha * v' i = a j + alpha * v j :=
exceptional_set_spec' _ _ h₁ h₂

end

section permutation

include h_initial h_cumulative

lemma permutation_aux (j : fin (n + 1)) :
  ∏ i in fin.range j.succ, (z - (a i + alpha * v i)) =
    (∏ i in fin.range j.succ, (z - (a' i + alpha * v' i))) * p j :=
begin
  apply fin.induction_on j,
  { rw [←fin.one_eq_succ_zero, fin.prod_range_one, fin.prod_range_one, h_initial] },
  intros j ih,
  rw [fin.prod_range_succ, fin.prod_range_succ, ←fin.succ_cast_succ, ih, ←mul_assoc],
  conv { to_rhs, rw [mul_right_comm, h_cumulative, mul_right_comm] }
end

include h_final

lemma permutation_prod_eq :
  ∏ i, (z - (a i + alpha * v i)) = ∏ i, (z - (a' i + alpha * v' i)) :=
by rw [←fin.range_last, ←fin.succ_last, permutation_aux h_initial h_cumulative,
    h_final, mul_one]

end permutation

/-
Put it all together!
-/

variable [fintype F]
variable hprob₁ : alpha ∉ bad_set_1 a v a' v'
variable hprob₂ : z ∉ bad_set_2 a v a' v' alpha

include hprob₁ hprob₂

lemma permutation (h : ∏ (i : fin (n + 1)), (z - (a i + alpha * v i)) =
                         ∏ (i : fin (n + 1)), (z - (a' i + alpha * v' i))) :
  ∀ i, ∃ j, v i = v' j ∧ a i = a' j :=
begin
  intro i,
  have : ∃ j, a i + alpha * v i = a' j + alpha * v' j := bad_set_2_spec hprob₂ h i,
  cases this with j hj,
  use j,
  show v i = v' j ∧ a i = a' j,
    from bad_set_1_spec hprob₁ hj
end

lemma permutation'  (h : ∏ (i : fin (n + 1)), (z - (a i + alpha * v i)) =
                         ∏ (i : fin (n + 1)), (z - (a' i + alpha * v' i))) :
  ∀ i, ∃ j, v' i = v j ∧ a' i = a j :=
begin
  intro i,
  have := bad_set_2_spec' hprob₂ h i,
  cases this with j hj,
  have := bad_set_1_spec hprob₁ hj.symm,
  use j, simp [this]
end

include h_continuity h_initial h_cumulative h_final

lemma a_continuous : ∃ base : F, ∃ m : ℕ,
    (∀ i, ∃ k ≤ m, a i = base + k) ∧ (∀ k ≤ m, ∃ i, a i = base + k) :=
begin
  have h : ∏ (i : fin (n + 1)), (z - (a i + alpha * v i)) =
             ∏ (i : fin (n + 1)), (z - (a' i + alpha * v' i)) :=
    permutation_prod_eq h_initial h_cumulative h_final,
  have perm := permutation hprob₁ hprob₂ h,
  use a' 0,
  use a'_nat_offset a' (fin.last n),
  split,
  { intro i,
    rcases perm i with ⟨j, veq, aeq⟩,
    use a'_nat_offset a' j,
    split,
    { apply monotone_a'_nat_offset, apply fin.le_last },
    rw aeq,
    exact a'_nat_offset_spec h_continuity j },
  have perm' := permutation' hprob₁ hprob₂ h,
  intros k kle,
  rcases a'_continuous h_continuity k kle with ⟨j, hj⟩,
  rcases perm' j with ⟨i, v'eq, a'eq⟩,
  use i,
  rw [←a'eq, ←hj],
  exact a'_nat_offset_spec h_continuity j
end

include h_single_valued

lemma a_single_valued (h_char_lt : n < ring_char F) : ∀ i i', a i = a i' → v i = v i' :=
begin
  intros i i' aieq,
  have h : ∏ (i : fin (n + 1)), (z - (a i + alpha * v i)) =
             ∏ (i : fin (n + 1)), (z - (a' i + alpha * v' i)) :=
    permutation_prod_eq h_initial h_cumulative h_final,
  have perm := permutation hprob₁ hprob₂ h,
  rcases perm i with ⟨j, veq, aeq⟩,
  rcases perm i' with ⟨j', veq', aeq'⟩,
  rw [veq, veq'],
  apply a'_single_valued h_continuity h_single_valued h_char_lt,
  rw [←aeq, ←aeq', aieq]
end

end constraints

-- #lint


import Architectural.LACU

inductive LANG
| T : LANG
| atom : PORTS → LANG
| lt : PORTS → PORTS → LANG
| neg : LANG → LANG
| conj : LANG → LANG → LANG
| disj : LANG → LANG → LANG
| always : LANG → LANG

namespace LANG

instance : has_top LANG := ⟨LANG.T⟩

@[simp]
def sem : LANG → set (Trace (PORTS))
| (LANG.atom x) := { σ | (σ 0) x ≠ 0 }
| (LANG.lt x v) := {σ  |  (σ 0) x < (σ 0) v}
| (LANG.neg A) := (set.univ) \ (sem A)
| (LANG.conj A B) := (sem A) ∩ (sem B)
| (LANG.disj A B) := (sem A) ∪ (sem B)
| (LANG.always A) := {σ | ∀ i, (σ.drop i) ∈ (sem A)}
| T := set.univ

@[simp]
def compl_def : ∀ A : LANG, (sem (neg A)) = (set.univ) \ (sem A) := λ A, rfl
@[simp]
def conj_def : ∀ A B : LANG,   (sem (conj A B)) =  (sem A) ∩ (sem B) := λ A B, rfl

@[simp]
def disj_def : ∀ A B : LANG,   (sem (disj A B)) =  (sem A) ∪ (sem B) := λ A B , rfl

@[simp] theorem disj_comm :  ∀ A B : LANG,  (sem (disj A B)) = sem (disj B A) :=
begin
  intros, rw sem, rw sem, rw set.union_comm,
end

@[simp]
def always_def : ∀ A : LANG, sem (A.always) =  {σ | ∀ i, (σ.drop i) ∈ (sem A)} := λ A, rfl

@[reducible,simp]
def sub (x y : PORTS) : LANG → LANG
| (LANG.atom a) := if a = x then LANG.atom y else LANG.atom a
| (LANG.lt a v) := if a = x then LANG.lt y v else LANG.lt a v
| (LANG.neg A) := LANG.neg (sub A)
| (LANG.conj A B) := LANG.conj (sub A) (sub B)
| (LANG.disj A B) :=  LANG.disj (sub A) (sub B)
| (LANG.always A) :=  (LANG.always (sub A))
| T := ⊤

end LANG

instance : AssertionLang LANG PORTS :=
{ sem := LANG.sem,
  T_def := by {unfold has_top.top, rw LANG.sem,},
  compl := LANG.neg,
  conj := LANG.conj,
  disj := LANG.disj,
  compl_def := LANG.compl_def,
  conj_def := LANG.conj_def,
  disj_def := LANG.disj_def, }

axiom synchronize {φ : LANG} {σ : Trace PORTS} : ∀ v1 v2 : PORTS,
(v1,v2) ∈ LACU_ARCH_MODEL.delegation ∨ (v2,v1) ∈ LACU_ARCH_MODEL.delegation →
σ ∈ (@AssertionLang.sem LANG PORTS _ _ _ φ) ↔
σ ∈ (@AssertionLang.sem LANG PORTS _ _ _ (φ.sub v1 v2))

theorem forall_conj_distrib : ∀ A B : LANG,
(@AssertionLang.sem LANG PORTS _ _ _  (LANG.conj A B).always) = (@AssertionLang.sem LANG PORTS _ _ _  ((A.always).conj (B.always))) :=
begin
  intros, unfold AssertionLang.sem, simp, rw set.inter_def, simp,
  ext, simp,
  rw forall_and_distrib,
end

theorem forall_conj_distrib_mem {x : Trace PORTS} : ∀ A B : LANG, x ∈
(@AssertionLang.sem LANG PORTS _ _ _ (LANG.conj A B).always) ↔ x ∈ (@AssertionLang.sem LANG PORTS _ _ _  ((A.always).conj (B.always))) :=
begin
  intros, unfold AssertionLang.sem, split, simp, rintros a, split,
   intro i, apply (a i).1, intro i, apply (a i).2,
   simp, intros a b i, split, apply a i, apply b i,
end

theorem forall_conj_distrib' : ∀ A B : LANG,
(LANG.sem  (LANG.conj A B).always) = (LANG.sem ((A.always).conj (B.always))) :=
begin
  intros, simp, rw set.inter_def, simp,
  ext, simp,
  rw forall_and_distrib,
end

theorem forall_conj_distrib_mem' {x : Trace PORTS} : ∀ A B : LANG, x ∈
(LANG.sem (LANG.conj A B).always) ↔ x ∈ (LANG.sem ((A.always).conj (B.always))) :=
begin
  intros, split, simp, rintros a, split,
   intro i, apply (a i).1, intro i, apply (a i).2,
   simp, intros a b i, split, apply a i, apply b i,
end


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

This file is followed Linear Algebra Done Right and inspired by Johannes Hölzl's
implementation of linear algebra in mathlib.

The thing we improved is this file describes finite dimentional vector spaces
-/

import algebra.module -- for definition of vector_space
import linear_algebra.basic -- for definition of is_basis
import data.list.basic
import analysis.real
universes u v

class finite_dimensional_vector_space (k : Type u) (V : Type v) [field k]
  extends vector_space k V :=
(ordered_basis : list V)
(is_ordered_basis : is_basis {v : V | v ∈ ordered_basis})

variables {k : Type u} {V : Type v}
variable [field k]
variable [module k V]
variables {a : k} {b : V}
include k

definition f_dimention
(k : Type u) (V : Type v) [field k] (fvs : finite_dimensional_vector_space k V) : ℕ :=
fvs.ordered_basis.length

def f_span (l : list V) : set V :=
span {vc : V | vc ∈ l}

def f_linear_independent (l : list V) : Prop :=
linear_independent {vc : V | vc ∈ l}

-- helper function to check whether two basis are equal
def are_span_the_same (l₀ : list V) (l₁ : list V) : Prop :=
∀vc : V, vc ∈ (f_span l₀) ∧ vc ∈ (f_span l₁)

def is_basis_of_vecsp (l : list V) (fvs : finite_dimensional_vector_space k V) : Prop :=
(f_span l = f_span fvs.ordered_basis) ∧ (f_linear_independent l)

def is_in_vecsp (v : V) (fvs : finite_dimensional_vector_space k V) : Prop :=
v ∈ span {v₁ : V | v₁ ∈ fvs.ordered_basis}

section basic_property
variables (x y : V)
variable (fvs : finite_dimensional_vector_space k V)

end basic_property

section span_liid
variables (v₀ v₁ v₂ vc: V)
variables (l₀ l₁: list V)
variables (h₀ h₁ res₀ res₁ : Prop)
variable (fvs : finite_dimensional_vector_space k V)

-- 2.4
theorem linear_dependence_th (l : list V)
  (h₀ : ¬ (f_linear_independent l))
  (h₁ : (vc ∈ l) ∧ (vc ≠ (0:V)))
  : ∃ v₀, (v₀ ∈ l) ∧ (v₀ ≠ vc) ∧ (v₀ ∈ span {vr : V | vr ∈ l ∧ vr ≠ v₀})
  ∧ (span {vr : V | vr ∈ l ∧ vr ≠ v₀} = f_span l) :=
  sorry
  -- begin
  --   apply exists.intro,
  --   split,
  --   intro v₀,
  --   assume h₂ : v₀ ∈ l ∧ v₀ ≠ vc,

  --   -- ⟨finsupp.single v₀ 1, by simp [finsupp.sum_single_index, this] {contextual := tt}⟩
  --   sorry

  -- end

-- 2.5 In a finite-dimensional vector space, the length of
-- every linearly independent list of vectors is less
-- than or equal to the length of every spanning list of vectors.
theorem len_of_lide_le_dimention (fvs : finite_dimensional_vector_space k V) (l : list V)
  (h₀ : f_linear_independent l ∧ f_span l ⊆ f_span fvs.ordered_basis)
  (h₁ : f_span l₀ = f_span fvs.ordered_basis)
  : l.length <= l₀.length :=
  -- begin
  sorry
  -- end

-- 2.8
theorem is_f_basis (l : list V) (fvs : finite_dimensional_vector_space k V):
  (∀ v₀, (is_in_vecsp v₀ fvs) ∧  (v₀ ∈ f_span l)) ↔ (is_basis_of_vecsp l fvs) :=
  sorry
  -- begin
  -- apply iff.intro,
  -- -- A -> B
  -- intro h,
  -- split,

  -- -- B -> A

  -- end

-- 2.10 Every spanning list in a vector space can be reduced to a basis of the vector space.
theorem span_set_can_be_basis (fvs : finite_dimensional_vector_space k V) :
  ∀l₀, (f_span l₀  = f_span fvs.ordered_basis) → ∃l₁ ⊆ l₀, is_basis_of_vecsp l₁ fvs := sorry

-- 2.12 Every linearly independent list of vectors in a finite-dimensional vector space
-- can be extended to a basis of the vector space.
theorem liide_list_can_be_basis (fvs : finite_dimensional_vector_space k V) :
  ∀l₀, linear_independent {vc : V | vc ∈ l₀} → ∃l₁, is_basis_of_vecsp (l₀ ++ l₁) fvs :=
  begin
  sorry
  end

-- Any two bases of a finite-dimensional vector space have the same length.
theorem any_basis_have_len:
  ∀l₀ l₁, (is_basis_of_vecsp l₀ fvs) ∧ (is_basis_of_vecsp l₁ fvs) →
   (l₀.length = l₁.length) := sorry

-- If V is finite dimensional, then every spanning list of vectors in V with length dim V is a basis of V.
theorem span_with_dim_is_basis:
  ∀l₀ , (are_span_the_same l₀ fvs.ordered_basis ∧ l₀.length = f_dimention k V fvs)
  → is_basis_of_vecsp l₀ fvs := sorry

theorem liide_list_with_dim_is_basis:
  ∀(l₀ : list V) , (f_linear_independent l₀ ∧ l₀.length = f_dimention k V fvs)
  → is_basis_of_vecsp l₀ fvs := sorry

end span_liid

-- define subspace

-- theorem: If V is finite dimensional and U is a subspace of V, then dimU ≤ dimV.

-- Proposition: Suppose V is finite dimensional and U is a subspace of V.
-- Then there is a subspace W of V such that V = U ⊕ W.

-- Theorem: If U1 and U2 are subspaces of a finite-dimensional vector space, then
-- dim(U1 +U2)=dimU1 +dimU2 −dim(U1 ∩U2).

-- 2.19 Proposition: Suppose V is finite dimensional and U1 , . . . , Um are subspaces of V such that
-- 2.20 V=U1+···+Um
-- 2.21 dimV =dimU1 +···+dimUm. Then V = U1 ⊕ · · · ⊕ Um.

-- define linear map

-- ∀l : lc α β, (∀x∉s, l x = 0) → l.sum (λv c, c • v) = 0 → l = 0
-- variable α : Type
-- variable i : α
-- theorem not_for_all (p q: α → Prop) (h : ¬ (∀x : α, p x → q x)) :
-- ∃x : α, p x ∧ ¬ (q x) :=
-- begin
-- apply exists.intro,
-- split,
-- apply exists.elim ((p i) ∨ ¬ (p i))
-- end


section  -- Propositions and Proofs
  variables p q r s : Prop

  -- commutativity of ∧ and ∨
  example : p ∧ q ↔ q ∧ p :=
  begin
    split;
    intro h;
    cases h with hl hr;
    exact and.intro hr hl
  end

  example : p ∨ q ↔ q ∨ p :=
  begin
    apply iff.intro;
    intro h;
    cases h with hl hl;
    { right, exact hl } <|> { left, exact hl }
  end

  -- associativity of ∧ and ∨
  example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
  begin
    apply iff.intro;
    intro h;
    repeat { split },
    all_goals {
      cases h with h_left h_right,
      cases h_left with hp hq <|> cases h_right with hq hr,
      assumption
    }
  end

  example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
  begin
    split;
    intro h, {
      cases h with h1 h2,
      cases h1 with hp hq,
      all_goals {
        repeat { { left, assumption} <|> right <|> assumption }
      }
    }, {
      cases h with h1 h1,
      { left, left, assumption },
      cases h1 with hq hr,
      try { left }, all_goals { right, assumption },
    }
  end

  -- distributivity
  example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
  begin
    split; intro h, {
      cases h.right,
      left, exact and.intro h.left h_1,
      right, exact and.intro h.left h_1,
    }, {
      cases h;
      split; cases h,
      all_goals { assumption <|> {left, assumption} <|> {right, assumption }}
    },
  end
  example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
  begin
    split; intro h, {
      cases h;
      split,
      any_goals { {left, assumption} },
      all_goals { right },
      exact h.left, exact h.right
    }, {
      cases h,
      cases h_left;
      cases h_right,
      any_goals { left, assumption },
      right, exact and.intro h_left h_right,
    }
  end

  -- other properties
  example : (p → (q → r)) ↔ (p ∧ q → r) :=
  begin
    split; intro h,
    {
      intro pq,
      show r, from (h (pq.left)) pq.right,
    }, {
      intro p, intro q,
      exact h ⟨p, q⟩
    }
  end

  example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) :=
  begin
    split; intro h, {
      split; intro h1,
        exact h (or.inl h1),
        exact h (or.inr h1),
    }, {
      intro pq,
      cases pq,
        exact h.left pq,
        exact h.right pq,
    }
  end

  example : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
  begin
    split; intro h, {
      split; intro n,
        have : p ∨ q, from or.inl n, contradiction,
        have : p ∨ q, from or.inr n, contradiction,
    }, {
      intro n, cases n,
        have : ¬p, from h.left, contradiction,
        have : ¬q, from h.right, contradiction,
    }
  end
  example : ¬p ∨ ¬q → ¬(p ∧ q) :=
  begin
    intro h,
    intro pq,
    have : p, from pq.left,
    have : q, from pq.right,
    cases h; contradiction
  end

  example : ¬(p ∧ ¬p) :=
  begin
    intro h,
    cases h, contradiction
  end

  example : p ∧ ¬q → ¬(p → q) :=
  begin
    intro h,
    cases h with hl hr,
    intro pq,
    have : q, from pq hl,
    contradiction
  end

  example : ¬p → (p → q) :=
  begin
    repeat { intro },
    contradiction
  end

  example : (¬p ∨ q) → (p → q) :=
  begin
    repeat { intro },
    cases a,
    contradiction,
    assumption
  end

  example : p ∨ false ↔ p :=
  begin
    split; intro h, {
      cases h,
        assumption,
        contradiction
    }, {
      exact or.inl h
    }
  end

  example : p ∧ false ↔ false :=
  begin
    split; intro h,
    cases h,
    all_goals { contradiction }
  end

  example : ¬(p ↔ ¬p) :=
  begin
    intro h,
    cases h,
    have : p → false, {
      intro hp,
      have : ¬p, from h_mp hp,
      contradiction
    },
    have : p, from h_mpr this,
    have : ¬p, from h_mp this,
    contradiction
  end

  example : (p → q) → (¬q → ¬p) :=
  begin
    repeat { intro },
    have : q, from a a_2,
    contradiction
  end

  -- these require classical reasoning
  example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
  begin
    intro h,
    cases classical.em r, {
      have : p → r, { intro , assumption },
      left, assumption,
    }, {
      have : p → s, {
        intro,
        have : r ∨ s, from h a,
        cases this, contradiction, assumption
      },
      right, assumption
    }
  end

  example : ¬(p ∧ q) → ¬p ∨ ¬q :=
  begin
    intro h,
    cases classical.em p, {
      have : ¬q, {
        intro,
        have : p ∧ q, from and.intro h_1 a,
        contradiction
      },
      exact or.inr this
    }, {
      left, assumption
    }
  end

  example : ¬(p → q) → p ∧ ¬q :=
  begin
    intro h,
    split, {
      from classical.by_contradiction
        begin
          repeat { intro },
          have : p → q, {
            intro h, contradiction,
          },
          contradiction
        end
    }, {
      intro,
      have : p → q, intro, assumption,
      contradiction,
    }
  end

  example : (p → q) → (¬p ∨ q) :=
  begin
    intro h,
    cases classical.em p,
      right, from h h_1,
      left, assumption
  end

  example : (¬q → ¬p) → (p → q) :=
  begin
    repeat { intro },
    from classical.by_contradiction
      begin
        intro nq,
        have : ¬p, from a nq,
        contradiction
      end
  end

  example : p ∨ ¬p := classical.em p

  example : (((p → q) → p) → p) :=
  begin
    intro,
    cases classical.em p,
      { assumption },
      { have : p → q, intro, contradiction,
        from a this,
      }
  end
end

------ Quantifiers and Expressions
section  -- 4.1
  variables (α : Type) (p q : α → Prop)

  example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
  begin
    split; intro h, {
      split; intro x,
      from (h x).left,
      from (h x).right,
    }, {
      cases h,
      intro x,
      split, from h_left x, from h_right x,
    }
  end

  example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
  begin
    intros h h₁ x,
    have : p x, from h₁ x,
    from (h x) this
  end

  example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
  begin
    intros h x,
    cases h,
      { left, from h x },
      { right, from h x },
  end
end

section -- 4.2
  variables (α : Type) (p q : α → Prop)
  variable r : Prop

  example : α → ((∀ x : α, r) ↔ r) :=
  begin
    intro α,
    split; intro h,
      { from h α },
      { intro, from h }
  end
  example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
  begin
    split; intro h, {
      apply classical.by_cases,
        intro, right, assumption,
        intro nr, left, intro x, cases h x,
          assumption,
          contradiction
    }, {
      intro x,
      cases h,
        left, from h x,
        right, assumption
    }
  end

  example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
  begin
    split;
    intros h h₁ h₂;
    from h h₂ h₁
  end
end

section -- 4.3
  variables (men : Type) (barber : men)
  variable  (shaves : men → men → Prop)

  example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : false :=
  begin
    have shave_barber, from h barber,
    have : ∀ p: Prop, (p ↔ ¬p) → false, {
      intros p h,
      cases h,
      have : ¬p, {
        intro hp,
        have : ¬p, from h_mp hp,
        contradiction
      },
      have : p, from h_mpr this,
      contradiction
    },
    from (this (shaves barber barber)) shave_barber
  end
end

section -- 4.5
  variables (α : Type) (p q : α → Prop)
  variable a : α
  variable r : Prop

  example : (∃ x : α, r) → r :=
  begin
    intro h,
    cases h with x hr,
    from hr
  end

  include a
  example : r → (∃ x : α, r) :=
  begin
    intro hr, split; assumption
  end
  example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
  begin
    split; intro h, {
      cases h with x pr,
      cases pr,
      repeat { split },
      all_goals { assumption }
    }, {
      cases h, cases h_left,
      repeat { split },
      all_goals { assumption },
    }
  end

  example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
  begin
    split; intro h, {
      cases h with x pq,
      cases pq,
        { left, split; assumption },
        { right, split; assumption },
    }, {
      cases h; cases h with x h;
      split,
        { left, from h },
        { right, from h },
    }
  end

  example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
  begin
    split; intro h, {
      intro ex,
      cases ex with x np,
      have : p x, from h x,
      contradiction
    }, {
      intro x,
      from classical.by_contradiction
        (by { intro np, from h ⟨x, np⟩ })
    }
  end

  example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
  begin
    split; intro h, {
      intro apx,
      cases h with x px,
      have : ¬p x, from apx x,
      contradiction
    }, {
      from classical.by_contradiction
      begin
        intro epx,
        have : ∀ x, ¬p x, {
          intros x px,
          from epx ⟨x, px⟩
        },
        contradiction
      end
    }
  end

  example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) :=
  begin
    split; intro h, {
      intros x px, from h ⟨x, px⟩
    }, {
      intro ex,
      cases ex with x px,
      have : ¬p x, from h x,
      contradiction
    }
  end

  example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) :=
  begin
    split; intro h, {
      from classical.by_contradiction
      begin
        intro ex,
        have : ∀ x, p x, {
          intro x,
          from classical.by_contradiction
            ( by { intro npx, from ex ⟨x, npx⟩})
        },
        contradiction
      end
    }, {
      cases h with x npx,
      intros apx,
      have : p x, from apx x,
      contradiction
    }
  end

  example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
  begin
    split; intro h, {
      intro epx,
      cases epx with x px,
      from (h x) px
    }, {
      intros x px,
      from h ⟨x, px⟩
    }
  end

  example : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
  begin
    split; intro h, {
      intro apx,
      cases h with x pxr,
      from pxr (apx x)
    }, {
      apply classical.by_cases, {
        intro hapx,
        have : r, from h hapx,
        existsi a, intro, assumption
      }, {
        intro napx,
        have : ∃x, ¬p x, apply classical.by_contradiction, {
          intro nepx,
          have : ∀ x, p x, {
            intro x,
            apply classical.by_contradiction,
            intro npx,
            have : ∃x, ¬p x, from exists.intro x npx,
            contradiction
          },
          contradiction
        },
        cases this with x npx,
        existsi x,
        intro, contradiction
      }
    }
  end

  example : (∃ x, r → p x) ↔ (r → ∃ x, p x) :=
  begin
    split; intro h, {
      intro hr,
      cases h with x rpx,
      existsi x, from rpx hr,
    }, {
      apply classical.by_cases, {
        intro hr,
        cases (h hr) with x px,
        existsi x, intro, assumption,
      }, {
        intro hr,
        existsi a, intro, contradiction,
      }
    }
  end
end

section -- 4.6
  variables (real : Type) [ordered_ring real]
  variables (log exp : real → real)
  variable  log_exp_eq : ∀ x, log (exp x) = x
  variable  exp_log_eq : ∀ {x}, x > 0 → exp (log x) = x
  variable  exp_pos    : ∀ x, exp x > 0
  variable  exp_add    : ∀ x y, exp (x + y) = exp x * exp y

  -- this ensures the assumptions are available in tactic proofs
  include log_exp_eq exp_log_eq exp_pos exp_add

  example (x y z : real) :
    exp (x + y + z) = exp x * exp y * exp z :=
  by rw [exp_add, exp_add]

  example (y : real) (h : y > 0)  : exp (log y) = y :=
  exp_log_eq h

  theorem log_mul {x y : real} (hx : x > 0) (hy : y > 0) :
    log (x * y) = log x + log y :=
    begin
      rw [←(exp_log_eq hx), ←(exp_log_eq hy)],
      rw ←exp_add,
      repeat { rw log_exp_eq },
    end
end

section -- 4.7
  example (x : ℤ) : x * 0 = 0 :=
  begin
    simp
  end
end

section -- 5.8
  example (p q r : Prop) (hp : p) :
  (p ∨ q ∨ r) ∧ (q ∨ p ∨ r) ∧ (q ∨ r ∨ p) :=
  by { repeat { split }, repeat { {left, assumption} <|> right <|> assumption } }
end


import algebra.order.field.basic tactic.by_contra

/-! # IMO 2009 A5 -/

namespace IMOSL
namespace IMO2009A5

theorem final_solution {F : Type*} [linear_ordered_field F] (f : F → F) :
  ∃ x y : F, y * f x + x < f (x - f y) :=
begin
  ---- Assume contradiction, and start with `f(t) ≤ t + f(0)` for all `t : F`
  by_contra' h,
  have h0 : ∀ t : F, f t ≤ t + f 0 :=
    λ t, by replace h := h (t + f 0) 0; rwa [add_sub_cancel, zero_mul, zero_add] at h,
  by_cases h1 : ∀ x : F, f x ≤ 0,

  ---- Case 1: `f(x) ≤ 0` for all `x : F`
  { replace h0 : ∀ t : F, f t ≤ t :=
      λ t, le_trans (h0 t) (add_le_of_nonpos_right $ h1 0),
    cases exists_gt (max 0 (- 1 - f (-1))) with t h2,
    rw max_lt_iff at h2; cases h2 with h2 h3,
    revert h3; rw [imp_false, not_lt, le_sub_iff_add_le, ← le_sub_iff_add_le', ← neg_add'],
    replace h := h (f t - 1) t,
    rw sub_sub_cancel_left at h,
    refine le_trans h _; clear h,
    rw ← le_sub_iff_add_le,
    refine le_trans ((mul_le_mul_left h2).mpr $ h0 _) _,
    rw [le_sub_iff_add_le, ← add_one_mul, add_comm,
        le_neg_iff_add_nonpos_right, ← mul_add_one, sub_add_cancel],
    exact mul_nonpos_of_nonneg_of_nonpos (le_of_lt $ add_pos one_pos h2) (h1 t) },

  ---- Case 2: `f(c) > 0` for some `c : F`
  { rw not_forall at h1,
    cases h1 with c h1; rw not_le at h1,
    cases exists_lt (min (c - f 0) ((- 1 - c - f 0) / f c)) with t h2,
    rw lt_min_iff at h2; cases h2 with h2 h3,
    rw [lt_div_iff h1, sub_right_comm, lt_sub_iff_add_lt] at h3,
    revert h3; clear h1; rw [imp_false, not_lt],
    refine le_trans _ (h c t),
    rw sub_le_iff_le_add; refine le_trans _ (h0 _),
    rw lt_sub_iff_add_lt at h2,
    replace h2 := lt_of_le_of_lt (h0 t) h2,
    rw ← sub_pos at h2,
    generalize_hyp : c - f t = y at h2 ⊢,

    -- Remains to prove `f(f(y)) ≥ -1` for all `y > 0`
    replace h := le_trans (h (f y) y) (add_le_add_left (h0 y) _),
    rw [sub_self, ← add_assoc, le_add_iff_nonneg_left, ← mul_add_one] at h,
    rwa [neg_le_iff_add_nonneg, ← zero_le_mul_left h2] }
end

end IMO2009A5
end IMOSL


-- begin header

import M40002.countability

namespace completeness

variables {X Y : Type}
-- end header

/- Sub-section
The Completeness Axiom
-/

/- Theorem
If a set $S ⊂ ℝ$ has maximums $a$ and $b$, then $a = b$, i.e. the maximum of a set is unique.
-/
theorem unique_max (S : set ℝ) : ∀ a b ∈ S, (∀ x ∈ S, x ≤ a ∧ x ≤ b) → a = b :=
begin
  intros a b ha hb hc,
  have : a ≤ b := (hc a ha).right,
  cases lt_or_eq_of_le this,
    {have : b ≤ a := (hc b hb).left,
    rw ←not_lt at this,
    contradiction
    },
    {assumption}
end

/- Theorem
If a set $S ⊂ ℝ$ has minimums $a$ and $b$, then $a = b$, i.e. the minimum of a set is unique.
-/
theorem neg_set_min (S : set ℝ) (s : ℝ) (h0 : s ∈ S) (h1 : ∀ x ∈ S, x ≤ s):
  ∀ x ∈ {t : ℝ | -t ∈ S}, -s ≤ x ∧ -s ∈ {t : ℝ | -t ∈ S} :=
begin
  intros x hx,
  split,
  {rwa neg_le,
  rw set.mem_set_of_eq at hx,
  apply h1, assumption
  },
  {rwa set.mem_set_of_eq,
  simpa
  }
end

/- Definition
A set $S ⊂ ℝ$ is bounded above if and only if $∃ M ∈ ℝ, ∀ s ∈ S, s ≤ M$
-/
def bounded_above (S : set ℝ) := ∃ M : ℝ, ∀ s ∈ S, s ≤ M

/- Definition
We call $M$ a upper bound of $S ⊂ ℝ$ if and only if $∀ s ∈ S, s ≤ M$.
-/
def upper_bound (S : set ℝ) (M : ℝ) := ∀ s ∈ S, s ≤ M

/-
We can deduce some properties straight away from these definitions.
-/

/- Corollary
A set $S ⊂ ℝ$ is bounded above if and only if there exists a $M ∈ ℝ$, $M$ is an upper bound of $S$
-/
theorem bdd_above_iff_have_upr_bd (S : set ℝ) : (∃ M : ℝ, upper_bound S M) ↔ bounded_above S :=
by {split, all_goals {rintro ⟨M, hM⟩, use M, assumption} }

/- Corollary
If $S$ has an upperbound $M$, then $∀ x ∈ R, x ≥ M$ implies $x$ is a upper bound of $S$
-/
theorem bigger_upperbound (S : set ℝ) (s : ℝ) (h : upper_bound S s) :
  ∀ x : ℝ, s ≤ x → upper_bound S x :=
by {intros x hx y hy, from le_trans (h y hy) hx}

/-
We will define lower bounds and bounded below in a similar fashion.
-/

/- Definition
A set $S ⊂ ℝ$ is bounded below if and only if $∃ M ∈ ℝ, ∀ s ∈ S, s ≥ M$
-/
def bounded_below (S : set ℝ) := ∃ M : ℝ, ∀ s ∈ S, M ≤ s

/- Definition
We call $M$ a lower bound of $S ⊂ ℝ$ if and only if $∀ s ∈ S, s ≥ M$.
-/
def lower_bound (S : set ℝ) (M : ℝ) := ∀ s ∈ S, M ≤ s

/- Corollary
A set $S ⊂ ℝ$ is bounded below if and only if there exists a $M ∈ ℝ$, $M$ is an lower bound of $S$
-/
theorem bdd_below_iff_have_lwr_bd (S : set ℝ) : (∃ M : ℝ, lower_bound S M) ↔ bounded_below S :=
by {split, all_goals {rintro ⟨M, hM⟩, use M, assumption} }

/- Exercise
If $s ∈ ℝ$ is an upper bound of a set $S ⊂ ℝ$, then $-s$ is a lower bound of the set ${t ∈ ℝ | -t ∈ S}$.
-/
theorem upr_bd_neg_set_lwr_bd (S : set ℝ) (s : ℝ) : upper_bound S s ↔ lower_bound {t : ℝ | -t ∈ S} (-s) :=
begin
  split,
    all_goals {intros h x hx},
    {rw set.mem_set_of_eq at hx,
    suffices : (-x) ≤ s, rwa neg_le,
    from h (-x) hx
    },
    unfold lower_bound at h,
    suffices : (-s) ≤ (-x), simp at this, assumption,
    have : (-x) ∈ {t : ℝ | -t ∈ S} := by {rwa set.mem_set_of_eq, simp, assumption},
    from h (-x) this
end

/- Definition
We call a set $S ⊂ ℝ$ bounded if it is bounded above and below.
-/
def bounded (S : set ℝ) := bounded_above S ∧ bounded_below S

-- Okay, so I've switched around the definition of supremums but dw, the two definitions are equiv.
def sup (S : set ℝ) (x : ℝ) := upper_bound S x ∧ (∀ y : ℝ, y < x → ¬ (upper_bound S y)) -- Check out sup_def for the definition from the lecture notes
def inf (S : set ℝ) (x : ℝ) := lower_bound S x ∧ (∀ y : ℝ, x < y → ¬ (lower_bound S y))

-- Exercise 2.24
theorem unique_sup (S : set ℝ) : ∀ a b ∈ S, sup S a ∧ sup S b → a = b :=
begin
  rintros a b ha hb ⟨⟨bda, supa⟩, ⟨bdb,supb⟩⟩,
  have hc : ∀ s ∈ S, s ≤ a ∧ s ≤ b := by {intros s hs, from ⟨bda s hs, bdb s hs⟩},
  from unique_max S a b ha hb hc
end

theorem sup_non_empty (S : set ℝ) (s : ℝ) (h : sup S s) : S ≠ ∅ :=
begin
  cases h with ha hb,
  intro,
  have hc : upper_bound S (s - 1) :=
    by {intros x hx,
    rw a at hx,
    simp at hx, contradiction
    },
  have hd : s - 1 < s := by linarith,
  replace hb : ¬ upper_bound S (s - 1) := by {apply hb (s - 1) hd},
  contradiction
end

theorem neg_set_inf (S : set ℝ) (s : ℝ) (h : sup S s) :
  inf {t : ℝ | -t ∈ S} (-s) :=
begin
  cases h with hbd hlub,
  split,
    {intros x hx,
    apply classical.by_contradiction,
    intro h, push_neg at h,
    have : -s ≤ x := by {rw neg_le, from (hbd (-x) hx)},
    apply not_le_of_lt h, assumption
    },
    {intros y hy hlbd,
    have : upper_bound S (-y) :=
      by {intros x hx,
      apply classical.by_contradiction,
      intro h, push_neg at h,
      unfold lower_bound at hlbd,
      have : y ≤ -x :=
        by {replace hx : -x ∈ {t : ℝ | -t ∈ S},
          rw set.mem_set_of_eq, simp, assumption,
        from hlbd (-x) hx
        },
      apply not_le_of_lt h, rwa le_neg
      },
    replace hy : -y < s := by {rwa neg_lt},
    from hlub (-y) hy this
    }
end

theorem sup_def (S : set ℝ) (s : ℝ) : sup S s ↔ upper_bound S s ∧ ∀ x : ℝ, (upper_bound S x → s ≤ x) :=
begin
  split,
    {rintros ⟨ha, hb⟩,
    split,
      {intros x hx,
      from ha x hx
      },
      {intros x hx,
      suffices : ¬ x < s, revert this, simp,
      intro, apply hb x, repeat {assumption}}
    },
    {rintros ⟨ha, hb⟩, split,
      {assumption},
      {intros x hx hc,
      replace hx : ¬ s ≤ x := by {push_neg, assumption},
      from hx (hb x hc)
      }
    }
end

theorem inf_def (S : set ℝ) (s : ℝ) : inf S s ↔ lower_bound S s ∧ ∀ x : ℝ, (lower_bound S x → x ≤ s) :=
begin -- proof essentially identical to that of sup_def
    split,
    {rintros ⟨ha, hb⟩,
    split,
      {intros x hx,
      from ha x hx
      },
      {intros x hx,
      suffices : ¬ s < x, revert this, simp,
      intro, apply hb x, repeat {assumption}}
    },
    {rintros ⟨ha, hb⟩, split,
      {assumption},
      {intros x hx hc,
      replace hx : ¬ x ≤ s := by {push_neg, assumption},
      from hx (hb x hc)
      }
    }
end

-- Defining the Completeness axiom
axiom completeness (S : set ℝ) (h : bounded_above S) (h1 : S ≠ ∅) : ∃ s : ℝ, sup S s

lemma neg_bdd_above_of_bdd_below {S : set ℝ}
(h : bounded_below S) : bounded_above {t | -t ∈ S} :=
begin
-- Since S is bounded below let b be its lower.
  cases h with b hb,
-- I now claim that -b is a lower bound of {t | -t ∈ S}.
  refine ⟨-b, λ s hs, _⟩,
-- Let -s ∈ S. But then from b being an lower bound of S, b ≤ -s → s ≤ -b as required!
  linarith [hb (-s) hs]
end

open set

-- Exercise 2.29
theorem completeness_below (S : set ℝ) (h : bounded_below S) (h1 : S ≠ ∅) : ∃ s : ℝ, inf S s :=
begin
-- As we have S is not an empty set, there exists s ∈ S.
  cases ne_empty_iff_nonempty.1 h1 with s hs,
-- Now let's consider the set T := {t : ℝ | -t ∈ S}.
-- This set is bounded above by our previous lemma so by completeness, it has a supremum (lets call it b).
  cases completeness {t : ℝ | -t ∈ S}
  (neg_bdd_above_of_bdd_below h)
  (ne_empty_iff_nonempty.2 ⟨-s, by simp [hs]⟩) with b hb,
-- I claim that -b is the infimum of S.
  refine ⟨-b, _⟩,
-- As we have previously proven that if s is the supremum of S then -s is the infimum of {t : ℝ | -t ∈ S},
-- it suffices to show that S = {t : ℝ | -t ∈ {t : ℝ | -t ∈ S}}. But this is trivial, so we are done!
  convert neg_set_inf {t : ℝ | -t ∈ S} _ _,
  simp, exact hb
end

open classical

-- Mentimeter Q 9
theorem equality_def (a x : ℝ) : (∀ ε : ℝ, 0 < ε → abs (x - a) < ε) ↔ x = a :=
begin
-- Since this is an if and only if question we need to prove both directions of the equation.
  split,
-- Let use prove the forward direction first.
-- Suppose otherwise. Then x ≠ a.
  intro h, by_contra h1,
-- It suffices to prove abs (x - a) <  abs (x - a) since that's obviously false.
  suffices : abs (x - a) <  abs (x - a), linarith,
-- So, by choosing ε = abs (x - a), the contradiction follows easily.
  refine h _ (abs_pos_iff.2 $ λ h2, _),
  rw sub_eq_zero at h2, contradiction,
-- For the other direction it is much easier.
-- If x = a then abs (x - a) = 0 < ε by construction so we are done!
  intro h, rw h, simp
end

end completeness


namespace Algebra

section
  universe u
  variable (X : Type u)
  class Op where op : X → X → X
  scoped infixl:70 " ⋆ " => Op.op
end
section
  variable {X} (data : Op X)
  namespace Op
  class IsComm : Prop where comm : ∀ x y : X, x ⋆ y = y ⋆ x
  export IsComm (comm)
  class IsSemigroup : Prop where assoc : ∀ x y z : X, (x ⋆ y) ⋆ z = x ⋆ (y ⋆ z)
  export IsSemigroup (assoc)
  class IsCommSemigroup extends data.IsSemigroup, data.IsComm : Prop
  end Op
end
section
  variable {X Y}
  variable {src : Op X} {dst : Op Y}
  namespace Op
  structure Function (src : Op X) (dst : Op Y) where map : X → Y
  namespace Function
  instance : CoeFun (Function src dst) (λ _ => X → Y) where coe f := f.map
  class IsSemigroupMorphism [src.IsSemigroup] [dst.IsSemigroup] (f : Function src dst) : Prop where
    op_law : ∀ x y, f (x ⋆ y) = f x ⋆ f y
  attribute [simp] IsSemigroupMorphism.op_law
  end Function
  end Op
end

section
  universe u
  variable (X : Type u)
  class Identity where identity : X
  scoped notation:max "𝟙" => Identity.identity
  class Zero where zero : X
  scoped notation:max "𝟬" => Zero.zero
  class One where one : X
  scoped notation:max "𝟭" => One.one
  class OpId extends Op X, Identity X
  attribute [reducible] OpId.toOp OpId.toIdentity
end
section
  variable {X} (data : OpId X)
  namespace OpId
  class IsUnital : Prop where
    id_op : ∀ x : X, 𝟙 ⋆ x = x
    op_id : ∀ x : X, x ⋆ 𝟙 = x
  export IsUnital (id_op op_id)
  attribute [simp] id_op op_id
  class IsMonoid extends data.IsSemigroup, data.IsUnital : Prop
  class IsAddMonoid (X) [Add X] [Zero X] extends IsMonoid { op := Add.add, identity := (Zero.zero : X) } : Prop
  class IsCommMonoid extends data.IsMonoid, data.IsComm : Prop
  end OpId
end
section
  variable {X Y}
  variable {src : OpId X} {dst : OpId Y}
  namespace OpId
  structure Function (src : OpId X) (dst : OpId Y) where map : X → Y
  namespace Function
  instance : CoeFun (Function src dst) (λ _ => X → Y) where coe f := f.map
  abbrev toOp (f : Function src dst) : Op.Function src.toOp dst.toOp := ⟨f.1⟩
  class IsMonoidMorphism [src.IsMonoid] [dst.IsMonoid] (f : Function src dst)
  extends f.toOp.IsSemigroupMorphism : Prop where
    id_law : f 𝟙 = 𝟙
  attribute [simp] IsMonoidMorphism.id_law
  end Function
  end OpId
end

section
  universe u
  variable (X : Type u)
  class Sym where sym : X → X
  scoped postfix:max "⁻ⁱ" => Sym.sym
  class Neg where neg : X → X
  scoped prefix:max "-" => Neg.neg
  class Inv where inv : X → X
  scoped postfix:max "⁻¹" => Inv.inv
  class OpIdSym extends OpId X, Sym X
  attribute [reducible] OpIdSym.toOpId OpIdSym.toSym
end
section
  variable {X} (data : OpIdSym X)
  namespace OpIdSym
  class IsSymmetric : Prop where
    op_sym : ∀ x : X, x ⋆ x⁻ⁱ = 𝟙
    sym_op : ∀ x : X, x⁻ⁱ ⋆ x = 𝟙
  export IsSymmetric (op_sym sym_op)
  attribute [simp] op_sym sym_op
  class IsGroup extends OpId.IsMonoid data.toOpId, IsSymmetric data  : Prop
  class IsCommGroup extends IsGroup data, Op.IsComm data.toOp : Prop
  end OpIdSym
end
section
  variable {X Y}
  variable {src : OpIdSym X} {dst : OpIdSym Y}
  namespace OpIdSym
  structure Function (src : OpIdSym X) (dst : OpIdSym Y) where map : X → Y
  namespace Function
  instance : CoeFun (Function src dst) (λ _ => X → Y) where coe f := f.map
  abbrev toOpId (f : Function src dst) : OpId.Function src.toOpId dst.toOpId := ⟨f.1⟩
  abbrev toOp (f : Function src dst) : Op.Function src.toOp dst.toOp := f.toOpId.toOp
  class IsGroupMorphism [src.IsGroup] [dst.IsGroup] (f : Function src dst)
  extends f.toOpId.IsMonoidMorphism : Prop
  end Function
  end OpIdSym
end

section
  universe u
  variable (X : Type u)
  class AddZeroMulOne extends Add X, Zero X, Mul X, One X
  attribute [reducible] AddZeroMulOne.toAdd AddZeroMulOne.toZero AddZeroMulOne.toMul AddZeroMulOne.toOne
end
section
  variable {X} (data : AddZeroMulOne X)
  namespace AddZeroMulOne
  abbrev toAddZero : OpId X := { op := data.add, identity := data.zero }
  abbrev toMulOne : OpId X := { op := data.mul, identity := data.one }

  class IsSemiring : Prop where
    addZero_IsCommMonoid : data.toAddZero.IsCommMonoid
    mulOne_IsMonoid : data.toMulOne.IsMonoid
    mul_add : ∀ x y z : X, x * (y + z) = x * y + x * z
    add_mul : ∀ x y z : X, (x + y) * z = x * z + y * z
    mul_zero : ∀ x : X, x * 𝟬 = 𝟬
    zero_mul : ∀ x : X, 𝟬 * x = 𝟬
  export IsSemiring (addZero_IsCommMonoid mulOne_IsMonoid mul_add add_mul mul_zero zero_mul)
  instance [data.IsSemiring] : data.toAddZero.IsCommMonoid := addZero_IsCommMonoid
  instance [data.IsSemiring] : data.toMulOne.IsMonoid := mulOne_IsMonoid

  class IsCommSemiring extends data.IsSemiring, data.toMulOne.IsComm : Prop where
  instance [data.IsCommSemiring] : data.toMulOne.IsCommMonoid := {}

  end AddZeroMulOne
end
section
  variable {X Y}
  variable {src : AddZeroMulOne X} {dst : AddZeroMulOne Y}
  namespace AddZeroMulOne
  structure Function (src : AddZeroMulOne X) (dst : AddZeroMulOne Y) where map : X → Y
  namespace Function
  instance : CoeFun (Function src dst) (λ _ => X → Y) where coe f := f.map
  abbrev toAddZero (f : Function src dst) : OpId.Function src.toAddZero dst.toAddZero := ⟨f.1⟩
  abbrev toMulOne (f : Function src dst) : OpId.Function src.toMulOne dst.toMulOne := ⟨f.1⟩
  class IsSemiringMorphism [src.IsSemiring] [dst.IsSemiring] (f : Function src dst) : Prop where
    addZero_Morphism : f.toAddZero.IsMonoidMorphism
    mulOne_Morphism : f.toMulOne.IsMonoidMorphism
  end Function
  end AddZeroMulOne
end

section
  universe u
  variable (X : Type u)
  class AddZeroNegMulOne extends AddZeroMulOne X, Neg X
  attribute [reducible] AddZeroNegMulOne.toAddZeroMulOne AddZeroNegMulOne.toNeg
end
section
  variable {X} (data : AddZeroNegMulOne X)
  namespace AddZeroNegMulOne
  abbrev toAddZero : OpId X := data.toAddZeroMulOne.toAddZero
  abbrev toMulOne : OpId X := data.toAddZeroMulOne.toMulOne
  abbrev toAddZeroNeg : OpIdSym X := { toOpId := data.toAddZero, sym := data.neg }

  class IsRing extends data.IsSemiring : Prop where
    addZeroNeg_IsSymmetric : data.toAddZeroNeg.IsSymmetric
  instance [IsRing data] : data.toAddZeroNeg.IsSymmetric  := IsRing.addZeroNeg_IsSymmetric
  instance [IsRing data] : data.IsSemiring := inferInstance
  instance [IsRing data] : data.toAddZeroNeg.IsCommGroup := {
    toIsComm := OpId.IsCommMonoid.toIsComm -- why cant it infer it?
  }
  end AddZeroNegMulOne
end
section
  variable {X Y}
  variable {src : AddZeroNegMulOne X} {dst : AddZeroNegMulOne Y}
  namespace AddZeroNegMulOne
  structure Function (src : AddZeroNegMulOne X) (dst : AddZeroNegMulOne Y) where map : X → Y
  namespace Function
  instance : CoeFun (Function src dst) (λ _ => X → Y) where coe f := f.map
  abbrev toAddZeroMulOne (f : Function src dst) : AddZeroMulOne.Function src.toAddZeroMulOne dst.toAddZeroMulOne := ⟨f.1⟩
  abbrev toAddZeroNeg (f : Function src dst) : OpIdSym.Function src.toAddZeroNeg dst.toAddZeroNeg := ⟨f.1⟩
  abbrev toAddZero (f : Function src dst) : OpId.Function src.toAddZero dst.toAddZero := f.toAddZeroMulOne.toAddZero
  abbrev toMulOne (f : Function src dst) : OpId.Function src.toMulOne dst.toMulOne := f.toAddZeroMulOne.toMulOne
  class IsRingMorphism [src.IsRing] [dst.IsRing] (f : Function src dst)
  extends f.toAddZeroMulOne.IsSemiringMorphism : Prop
  end Function
  end AddZeroNegMulOne
end

example (X) (data : OpIdSym X) [data.IsCommGroup] : ∀ x y : X, x ⋆ y = y ⋆ x := Op.comm
example (X) (data : OpIdSym X) [data.IsCommGroup] : ∀ x : X, x ⋆ 𝟙 = x := by simp
example (X) (data : OpIdSym X) [data.IsCommGroup] : ∀ x : X, x ⋆ x⁻ⁱ = 𝟙 := by simp
example (X) (data : AddZeroMulOne X) [data.IsSemiring] : ∀ x : X, x + 𝟬 = x :=
  let _ := data.toAddZero
  show ∀ x : X, x ⋆ 𝟙 = x from
  by simp

def kernel {X Y} {src : OpId X} {dst : OpId Y} [src.IsMonoid] [dst.IsMonoid] (f : OpId.Function src dst) : X → Prop
  := λ x => f x = 𝟙

example {X Y} {src : AddZeroNegMulOne X} {dst : AddZeroNegMulOne Y} [src.IsRing] [dst.IsRing]
  (f : AddZeroNegMulOne.Function src dst) (h : f.IsRingMorphism)
  : kernel f.toAddZero 𝟬 := h.addZero_Morphism.id_law
example {X Y} {src : AddZeroNegMulOne X} {dst : AddZeroNegMulOne Y} [src.IsRing] [dst.IsRing]
  (f : AddZeroNegMulOne.Function src dst) (h : f.IsRingMorphism)
  : kernel f.toMulOne 𝟭 := h.mulOne_Morphism.id_law

end Algebra


import data.list.basic
open list
universe u
variables {α : Type} (x y z : α) (xs ys zs : list α)

def mk_symm (xs : list α) := xs ++ reverse xs

theorem reverse_mk_symm (xs : list α) :
  reverse (mk_symm xs) = mk_symm xs :=
  by simp [mk_symm]

section
local attribute [simp] reverse_mk_symm

example (xs ys : list ℕ) :
  reverse (xs ++ mk_symm ys) = mk_symm ys ++ reverse xs :=
  by simp

example (xs ys : list ℕ) (p : list ℕ → Prop) (h : p (reverse (xs ++ (mk_symm ys)))) :
  p (mk_symm ys ++ reverse xs) :=
  by simp at h; assumption
end


/-
Copyright (c) 2022 Frédéric Dupuis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Frédéric Dupuis

! This file was ported from Lean 3 source module analysis.normed_space.algebra
! leanprover-community/mathlib commit 17ef379e997badd73e5eabb4d38f11919ab3c4b3
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Topology.Algebra.Module.CharacterSpace
import Mathbin.Analysis.NormedSpace.WeakDual
import Mathbin.Analysis.NormedSpace.Spectrum

/-!
# Normed algebras

This file contains basic facts about normed algebras.

## Main results

* We show that the character space of a normed algebra is compact using the Banach-Alaoglu theorem.

## TODO

* Show compactness for topological vector spaces; this requires the TVS version of Banach-Alaoglu.

## Tags

normed algebra, character space, continuous functional calculus

-/

variable {𝕜 : Type _} {A : Type _}

namespace WeakDual

namespace CharacterSpace

variable [NontriviallyNormedField 𝕜] [NormedRing A] [NormedAlgebra 𝕜 A] [CompleteSpace A]

theorem norm_le_norm_one (φ : characterSpace 𝕜 A) : ‖toNormedDual (φ : WeakDual 𝕜 A)‖ ≤ ‖(1 : A)‖ :=
  ContinuousLinearMap.op_norm_le_bound _ (norm_nonneg (1 : A)) fun a =>
    mul_comm ‖a‖ ‖(1 : A)‖ ▸ spectrum.norm_le_norm_mul_of_mem (apply_mem_spectrum φ a)
#align weak_dual.character_space.norm_le_norm_one WeakDual.characterSpace.norm_le_norm_one

instance [ProperSpace 𝕜] : CompactSpace (characterSpace 𝕜 A) :=
  by
  rw [← isCompact_iff_compactSpace]
  have h : character_space 𝕜 A ⊆ to_normed_dual ⁻¹' Metric.closedBall 0 ‖(1 : A)‖ :=
    by
    intro φ hφ
    rw [Set.mem_preimage, mem_closedBall_zero_iff]
    exact (norm_le_norm_one ⟨φ, ⟨hφ.1, hφ.2⟩⟩ : _)
  exact isCompact_of_isClosed_subset (is_compact_closed_ball 𝕜 0 _) character_space.is_closed h

end CharacterSpace

end WeakDual


/-
Copyright (c) 2022 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov
-/
import analysis.complex.cauchy_integral
import analysis.convex.integral
import analysis.normed_space.completion
import topology.algebra.order.extr_closure

/-!
# Maximum modulus principle

In this file we prove several versions of the maximum modulus principle.

There are several statements that can be called "the maximum modulus principle" for maps between
normed complex spaces.

In the most general case, see `complex.norm_eventually_eq_of_is_local_max`, we can only say that for
a differentiable function `f : E → F`, if the norm has a local maximum at `z`, then *the norm* is
constant in a neighborhood of `z`.

If the domain is a nontrivial finite dimensional space, then this implies the following version of
the maximum modulus principle, see `complex.exists_mem_frontier_is_max_on_norm`. If `f : E → F` is
complex differentiable on a nonempty compact set `K`, then there exists a point `z ∈ frontier K`
such that `λ z, ∥f z∥` takes it maximum value on `K` at `z`.

Finally, if the codomain is a strictly convex space, then the function cannot have a local maximum
of the norm unless the function (not only its norm) is a constant. This version is not formalized
yet.
-/

open topological_space metric set filter asymptotics function measure_theory affine_map
open_locale topological_space filter nnreal real

universes u v w
variables {E : Type u} [normed_group E] [normed_space ℂ E]
  {F : Type v} [normed_group F] [normed_space ℂ F]

local postfix `̂`:100 := uniform_space.completion

namespace complex

/-!
### Auxiliary lemmas

We split the proof into a series of lemmas. First we prove the principle for a function `f : ℂ → F`
with an additional assumption that `F` is a complete space, then drop unneeded assumptions one by
one.

The only "public API" lemmas in this section are TODO and
`complex.norm_eq_norm_of_is_max_on_of_closed_ball_subset`.
-/

lemma norm_max_aux₁ [complete_space F] {f : ℂ → F} {z w : ℂ}
  (hd : diff_on_int_cont ℂ f (closed_ball z (dist w z)))
  (hz : is_max_on (norm ∘ f) (closed_ball z (dist w z)) z) :
  ∥f w∥ = ∥f z∥ :=
begin
  /- Consider a circle of radius `r = dist w z`. -/
  set r : ℝ := dist w z,
  have hw : w ∈ closed_ball z r, from mem_closed_ball.2 le_rfl,
  /- Assume the converse. Since `∥f w∥ ≤ ∥f z∥`, we have `∥f w∥ < ∥f z∥`. -/
  refine (is_max_on_iff.1 hz _ hw).antisymm (not_lt.1 _),
  rintro hw_lt : ∥f w∥ < ∥f z∥,
  have hr : 0 < r, from dist_pos.2 (ne_of_apply_ne (norm ∘ f) hw_lt.ne),
  /- Due to Cauchy integral formula, it suffices to prove the following inequality. -/
  suffices : ∥∮ ζ in C(z, r), (ζ - z)⁻¹ • f ζ∥ < 2 * π * ∥f z∥,
  { refine this.ne _,
    have A : ∮ ζ in C(z, r), (ζ - z)⁻¹ • f ζ = (2 * π * I : ℂ) • f z :=
      hd.circle_integral_sub_inv_smul (mem_ball_self hr),
    simp [A, norm_smul, real.pi_pos.le] },
  suffices : ∥∮ ζ in C(z, r), (ζ - z)⁻¹ • f ζ∥ < 2 * π * r * (∥f z∥ / r),
    by rwa [mul_assoc, mul_div_cancel' _ hr.ne'] at this,
  /- This inequality is true because `∥(ζ - z)⁻¹ • f ζ∥ ≤ ∥f z∥ / r` for all `ζ` on the circle and
  this inequality is strict at `ζ = w`. -/
  have hsub : sphere z r ⊆ closed_ball z r, from sphere_subset_closed_ball,
  refine circle_integral.norm_integral_lt_of_norm_le_const_of_lt hr _ _ ⟨w, rfl, _⟩,
  show continuous_on (λ (ζ : ℂ), (ζ - z)⁻¹ • f ζ) (sphere z r),
  { refine ((continuous_on_id.sub continuous_on_const).inv₀ _).smul (hd.continuous_on.mono hsub),
    exact λ ζ hζ, sub_ne_zero.2 (ne_of_mem_sphere hζ hr.ne') },
  show ∀ ζ ∈ sphere z r, ∥(ζ - z)⁻¹ • f ζ∥ ≤ ∥f z∥ / r,
  { rintros ζ (hζ : abs (ζ - z) = r),
    rw [le_div_iff hr, norm_smul, norm_inv, norm_eq_abs, hζ, mul_comm, mul_inv_cancel_left₀ hr.ne'],
    exact hz (hsub hζ) },
  show ∥(w - z)⁻¹ • f w∥ < ∥f z∥ / r,
  { rw [norm_smul, norm_inv, norm_eq_abs, ← div_eq_inv_mul],
    exact (div_lt_div_right hr).2 hw_lt }
end

/-!
Now we drop the assumption `complete_space F` by embedding `F` into its completion.
-/

lemma norm_max_aux₂ {f : ℂ → F} {z w : ℂ} (hd : diff_on_int_cont ℂ f (closed_ball z (dist w z)))
  (hz : is_max_on (norm ∘ f) (closed_ball z (dist w z)) z) :
  ∥f w∥ = ∥f z∥ :=
begin
  set e : F →L[ℂ] F̂ := uniform_space.completion.to_complL,
  have he : ∀ x, ∥e x∥ = ∥x∥, from uniform_space.completion.norm_coe,
  replace hz : is_max_on (norm ∘ (e ∘ f)) (closed_ball z (dist w z)) z,
    by simpa only [is_max_on, (∘), he] using hz,
  simpa only [he] using norm_max_aux₁ (e.differentiable.comp_diff_on_int_cont hd) hz
end

/-!
Then we replace the assumption `is_max_on (norm ∘ f) (closed_ball z r) z` with a seemingly weaker
assumption `is_max_on (norm ∘ f) (ball z r) z`.
-/

lemma norm_max_aux₃ {f : ℂ → F} {z w : ℂ} {r : ℝ} (hr : dist w z = r)
  (hd : diff_on_int_cont ℂ f (closed_ball z r)) (hz : is_max_on (norm ∘ f) (ball z r) z) :
  ∥f w∥ = ∥f z∥ :=
begin
  subst r,
  rcases eq_or_ne w z with rfl|hne, { refl },
  have : closure (ball z (dist w z)) = closed_ball z (dist w z),
    from closure_ball z (dist_ne_zero.2 hne),
  exact norm_max_aux₂ hd (this ▸ hz.closure (this.symm ▸ hd.continuous_on.norm))
end

/-!
Finally, we generalize the theorem from a disk in `ℂ` to a closed ball in any normed space.
-/

/-- **Maximum modulus principle** on a closed ball: if `f : E → F` is continuous on a closed ball,
is complex differentiable on the corresponding open ball, and the norm `∥f w∥` takes its maximum
value on the open ball at its center, then the norm `∥f w∥` is constant on the closed ball.  -/
lemma norm_eq_on_closed_ball_of_is_max_on {f : E → F} {z : E} {r : ℝ}
  (hd : diff_on_int_cont ℂ f (closed_ball z r)) (hz : is_max_on (norm ∘ f) (ball z r) z) :
  eq_on (norm ∘ f) (const E ∥f z∥) (closed_ball z r) :=
begin
  intros w hw,
  rw [mem_closed_ball, dist_comm] at hw,
  rcases eq_or_ne z w with rfl|hne, { refl },
  set e : ℂ → E := line_map z w,
  have hde : differentiable ℂ e := (differentiable_id.smul_const (w - z)).add_const z,
  suffices : ∥(f ∘ e) (1 : ℂ)∥ = ∥(f ∘ e) (0 : ℂ)∥, by simpa [e],
  have hr : dist (1 : ℂ) 0 = 1, by simp,
  have hball : maps_to e (ball 0 1) (ball z r),
  { refine ((lipschitz_with_line_map z w).maps_to_ball
      (mt nndist_eq_zero.1 hne) 0 1).mono subset.rfl _,
    simpa only [line_map_apply_zero, mul_one, coe_nndist] using ball_subset_ball hw },
  refine norm_max_aux₃ hr (diff_on_int_cont.mk_ball
    (hd.differentiable_on_ball.comp hde.differentiable_on hball)
    (hd.continuous_on.comp hde.continuous.continuous_on _)) _,
  { refine ((lipschitz_with_line_map z w).maps_to_closed_ball 0 1).mono_right _,
    simpa only [line_map_apply_zero, mul_one, coe_nndist] using closed_ball_subset_closed_ball hw },
  { exact hz.comp_maps_to hball (line_map_apply_zero z w) }
end

/-!
### Different forms of the maximum modulus principle
-/

/-- **Maximum modulus principle**: if `f : E → F` is complex differentiable on a set `s`, the norm
of `f` takes it maximum on `s` at `z` and `w` is a point such that the closed ball with center `z`
and radius `dist w z` is included in `s`, then `∥f w∥ = ∥f z∥`. -/
lemma norm_eq_norm_of_is_max_on_of_closed_ball_subset {f : E → F} {s : set E} {z w : E}
  (hd : diff_on_int_cont ℂ f s) (hz : is_max_on (norm ∘ f) s z)
  (hsub : closed_ball z (dist w z) ⊆ s) :
  ∥f w∥ = ∥f z∥ :=
norm_eq_on_closed_ball_of_is_max_on (hd.mono hsub)
  (hz.on_subset $ ball_subset_closed_ball.trans hsub) (mem_closed_ball.2 le_rfl)

/-- **Maximum modulus principle**: if `f : E → F` is complex differentiable in a neighborhood of `c`
and the norm `∥f z∥` has a local maximum at `c`, then `∥f z∥` is locally constant in a neighborhood
of `c`. -/
lemma norm_eventually_eq_of_is_local_max {f : E → F} {c : E}
  (hd : ∀ᶠ z in 𝓝 c, differentiable_at ℂ f z) (hc : is_local_max (norm ∘ f) c) :
  ∀ᶠ y in 𝓝 c, ∥f y∥ = ∥f c∥ :=
begin
  rcases nhds_basis_closed_ball.eventually_iff.1 (hd.and hc) with ⟨r, hr₀, hr⟩,
  exact nhds_basis_closed_ball.eventually_iff.2 ⟨r, hr₀, norm_eq_on_closed_ball_of_is_max_on
    (differentiable_on.diff_on_int_cont $ λ x hx, (hr hx).1.differentiable_within_at) $
    λ x hx, (hr $ ball_subset_closed_ball hx).2⟩
end

lemma is_open_set_of_mem_nhds_and_is_max_on_norm {f : E → F} {s : set E}
  (hd : differentiable_on ℂ f s) :
  is_open {z | s ∈ 𝓝 z ∧ is_max_on (norm ∘ f) s z} :=
begin
  refine is_open_iff_mem_nhds.2 (λ z hz, (eventually_eventually_nhds.2 hz.1).and _),
  replace hd : ∀ᶠ w in 𝓝 z, differentiable_at ℂ f w, from hd.eventually_differentiable_at hz.1,
  exact (norm_eventually_eq_of_is_local_max hd $ (hz.2.is_local_max hz.1)).mono
    (λ x hx y hy, le_trans (hz.2 hy) hx.ge)
end

/-- **Maximum modulus principle**: if `f : E → F` is complex differentiable on a nonempty compact
set `K`, then there exists a point `z ∈ frontier K` such that `λ z, ∥f z∥` takes it maximum value on
`K` at `z`. -/
lemma exists_mem_frontier_is_max_on_norm [nontrivial E] {f : E → F} {K : set E} (hK : is_compact K)
  (hne : K.nonempty) (hd : diff_on_int_cont ℂ f K) :
  ∃ z ∈ frontier K, is_max_on (norm ∘ f) K z :=
begin
  rcases hK.exists_forall_ge hne hd.continuous_on.norm with ⟨w, hwK, hle⟩,
  rcases hK.exists_mem_frontier_inf_dist_compl_eq_dist hwK with ⟨z, hzK, hzw⟩,
  refine ⟨z, hzK, λ x hx, (hle x hx).trans_eq _⟩,
  refine (norm_eq_norm_of_is_max_on_of_closed_ball_subset hd hle _).symm,
  calc closed_ball w (dist z w) = closed_ball w (inf_dist w Kᶜ) : by rw [hzw, dist_comm]
  ... ⊆ closure K : closed_ball_inf_dist_compl_subset_closure hwK
  ... = K : hK.is_closed.closure_eq
end

/-- **Maximum modulus principle**: if `f : E → F` is complex differentiable on a compact set `K` and
`∥f z∥ ≤ C` for any `z ∈ frontier K`, then the same is true for any `z ∈ K`. -/
lemma norm_le_of_forall_mem_frontier_norm_le [nontrivial E] {f : E → F} {K : set E}
  (hK : is_compact K) (hd : diff_on_int_cont ℂ f K)
  {C : ℝ} (hC : ∀ z ∈ frontier K, ∥f z∥ ≤ C) {z : E} (hz : z ∈ K) :
  ∥f z∥ ≤ C :=
let ⟨w, hwK, hw⟩ := exists_mem_frontier_is_max_on_norm hK ⟨z, hz⟩ hd
in le_trans (hw hz) (hC w hwK)

/-- If two complex differentiable functions `f g : E → F` are equal on the boundary of a compact set
`K`, then they are equal on `K`. -/
lemma eq_on_of_eq_on_frontier [nontrivial E] {f g : E → F} {K : set E} (hK : is_compact K)
  (hf : diff_on_int_cont ℂ f K) (hg : diff_on_int_cont ℂ g K) (hfg : eq_on f g (frontier K)) :
  eq_on f g K :=
begin
  suffices H : ∀ z ∈ K, ∥f z - g z∥ ≤ 0, by simpa [sub_eq_zero] using H,
  convert λ z hz, norm_le_of_forall_mem_frontier_norm_le hK (hf.sub hg) _ hz,
  simpa [sub_eq_zero]
end

end complex


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

/-!
# Binomial coefficients

This file defines binomial coefficients and proves simple lemmas (i.e. those not
requiring more imports).

## Main definition and results

* `nat.choose`: binomial coefficients, defined inductively
* `nat.choose_eq_factorial_div_factorial`: a proof that `choose n k = n! / (k! * (n - k)!)`
* `nat.choose_symm`: symmetry of binomial coefficients
* `nat.choose_le_succ_of_lt_half_left`: `choose n k` is increasing for small values of `k`
* `nat.choose_le_middle`: `choose n r` is maximised when `r` is `n/2`
* `nat.desc_factorial_eq_factorial_mul_choose`: Relates binomial coefficients to the descending
  factorial. This is used to prove `nat.choose_le_pow` and variants. We provide similar statements
  for the ascending factorial.

-/

open_locale nat

namespace nat

/-- `choose n k` is the number of `k`-element subsets in an `n`-element set. Also known as binomial
coefficients. -/
def choose : ℕ → ℕ → ℕ
| _             0 := 1
| 0       (k + 1) := 0
| (n + 1) (k + 1) := choose n k + choose n (k + 1)

@[simp] lemma choose_zero_right (n : ℕ) : choose n 0 = 1 := by cases n; refl

@[simp] lemma choose_zero_succ (k : ℕ) : choose 0 (succ k) = 0 := rfl

lemma choose_succ_succ (n k : ℕ) : choose (succ n) (succ k) = choose n k + choose n (succ k) := rfl

lemma choose_eq_zero_of_lt : ∀ {n k}, n < k → choose n k = 0
| _             0 hk := absurd hk dec_trivial
| 0       (k + 1) hk := choose_zero_succ _
| (n + 1) (k + 1) hk :=
  have hnk : n < k, from lt_of_succ_lt_succ hk,
  have hnk1 : n < k + 1, from lt_of_succ_lt hk,
  by rw [choose_succ_succ, choose_eq_zero_of_lt hnk, choose_eq_zero_of_lt hnk1]

@[simp] lemma choose_self (n : ℕ) : choose n n = 1 :=
by induction n; simp [*, choose, choose_eq_zero_of_lt (lt_succ_self _)]

@[simp] lemma choose_succ_self (n : ℕ) : choose n (succ n) = 0 :=
choose_eq_zero_of_lt (lt_succ_self _)

@[simp] lemma choose_one_right (n : ℕ) : choose n 1 = n :=
by induction n; simp [*, choose, add_comm]

/- The `n+1`-st triangle number is `n` more than the `n`-th triangle number -/
lemma triangle_succ (n : ℕ) : (n + 1) * ((n + 1) - 1) / 2 = n * (n - 1) / 2 + n :=
begin
  rw [← add_mul_div_left, mul_comm 2 n, ← mul_add, add_tsub_cancel_right, mul_comm],
  cases n; refl, apply zero_lt_succ
end

/-- `choose n 2` is the `n`-th triangle number. -/
lemma choose_two_right (n : ℕ) : choose n 2 = n * (n - 1) / 2 :=
begin
  induction n with n ih,
  simp,
  {rw triangle_succ n, simp [choose, ih], rw add_comm},
end

lemma choose_pos : ∀ {n k}, k ≤ n → 0 < choose n k
| 0             _ hk := by rw [nat.eq_zero_of_le_zero hk]; exact dec_trivial
| (n + 1)       0 hk := by simp; exact dec_trivial
| (n + 1) (k + 1) hk := by rw choose_succ_succ;
    exact add_pos_of_pos_of_nonneg (choose_pos (le_of_succ_le_succ hk)) (nat.zero_le _)

lemma succ_mul_choose_eq : ∀ n k, succ n * choose n k = choose (succ n) (succ k) * succ k
| 0             0 := dec_trivial
| 0       (k + 1) := by simp [choose]
| (n + 1)       0 := by simp
| (n + 1) (k + 1) :=
  by rw [choose_succ_succ (succ n) (succ k), add_mul, ←succ_mul_choose_eq, mul_succ,
  ←succ_mul_choose_eq, add_right_comm, ←mul_add, ←choose_succ_succ, ←succ_mul]

lemma choose_mul_factorial_mul_factorial : ∀ {n k}, k ≤ n → choose n k * k! * (n - k)! = n!
| 0              _ hk := by simp [nat.eq_zero_of_le_zero hk]
| (n + 1)        0 hk := by simp
| (n + 1) (succ k) hk :=
begin
  cases lt_or_eq_of_le hk with hk₁ hk₁,
  { have h : choose n k * k.succ! * (n-k)! = (k + 1) * n! :=
      by rw ← choose_mul_factorial_mul_factorial (le_of_succ_le_succ hk);
      simp [factorial_succ, mul_comm, mul_left_comm],
    have h₁ : (n - k)! = (n - k) * (n - k.succ)! :=
      by rw [← succ_sub_succ, succ_sub (le_of_lt_succ hk₁), factorial_succ],
    have h₂ : choose n (succ k) * k.succ! * ((n - k) * (n - k.succ)!) = (n - k) * n! :=
      by rw ← choose_mul_factorial_mul_factorial (le_of_lt_succ hk₁);
      simp [factorial_succ, mul_comm, mul_left_comm, mul_assoc],
    have h₃ : k * n! ≤ n * n! := nat.mul_le_mul_right _ (le_of_succ_le_succ hk),
    rw [choose_succ_succ, add_mul, add_mul, succ_sub_succ, h, h₁, h₂, add_mul,
      tsub_mul, factorial_succ, ← add_tsub_assoc_of_le h₃, add_assoc, ← add_mul,
      add_tsub_cancel_left, add_comm] },
  { simp [hk₁, mul_comm, choose, tsub_self] }
end

lemma choose_mul {n k s : ℕ} (hkn : k ≤ n) (hsk : s ≤ k) :
  n.choose k * k.choose s = n.choose s * (n - s).choose (k - s) :=
begin
  have h : 0 < (n - k)! * (k - s)! * s! :=
    mul_pos (mul_pos (factorial_pos _) (factorial_pos _)) (factorial_pos _),
  refine eq_of_mul_eq_mul_right h _,
  calc
    n.choose k * k.choose s * ((n - k)! * (k - s)! * s!)
        = n.choose k * (k.choose s * s! * (k - s)!) * (n - k)!
        : by rw [mul_assoc, mul_assoc, mul_assoc, mul_assoc _ s!, mul_assoc, mul_comm (n - k)!,
              mul_comm s!]
    ... = n!
        : by rw [choose_mul_factorial_mul_factorial hsk, choose_mul_factorial_mul_factorial hkn]
    ... = n.choose s * s! * ((n - s).choose (k - s) * (k - s)! * (n - s - (k - s))!)
        : by rw [choose_mul_factorial_mul_factorial (tsub_le_tsub_right hkn _),
              choose_mul_factorial_mul_factorial (hsk.trans hkn)]
    ... = n.choose s * (n - s).choose (k - s) * ((n - k)! * (k - s)! * s!)
        : by rw [tsub_tsub_tsub_cancel_right hsk, mul_assoc, mul_left_comm s!, mul_assoc,
              mul_comm (k - s)!, mul_comm s!, mul_right_comm, ←mul_assoc]
end

theorem choose_eq_factorial_div_factorial {n k : ℕ} (hk : k ≤ n) :
  choose n k = n! / (k! * (n - k)!) :=
begin
  rw [← choose_mul_factorial_mul_factorial hk, mul_assoc],
  exact (mul_div_left _ (mul_pos (factorial_pos _) (factorial_pos _))).symm
end

lemma add_choose (i j : ℕ) : (i + j).choose j = (i + j)! / (i! * j!) :=
by rw [choose_eq_factorial_div_factorial (nat.le_add_left j i), add_tsub_cancel_right, mul_comm]

theorem factorial_mul_factorial_dvd_factorial {n k : ℕ} (hk : k ≤ n) : k! * (n - k)! ∣ n! :=
by rw [←choose_mul_factorial_mul_factorial hk, mul_assoc]; exact dvd_mul_left _ _

lemma factorial_mul_factorial_dvd_factorial_add (i j : ℕ) :
  i! * j! ∣ (i + j)! :=
begin
  convert factorial_mul_factorial_dvd_factorial (le.intro rfl),
  rw add_tsub_cancel_left
end

@[simp] lemma choose_symm {n k : ℕ} (hk : k ≤ n) : choose n (n-k) = choose n k :=
by rw [choose_eq_factorial_div_factorial hk, choose_eq_factorial_div_factorial (nat.sub_le _ _),
  tsub_tsub_cancel_of_le hk, mul_comm]

lemma choose_symm_of_eq_add {n a b : ℕ} (h : n = a + b) : nat.choose n a = nat.choose n b :=
by { convert nat.choose_symm (nat.le_add_left _ _), rw add_tsub_cancel_right}

lemma choose_symm_add {a b : ℕ} : choose (a+b) a = choose (a+b) b :=
choose_symm_of_eq_add rfl

lemma choose_symm_half (m : ℕ) : choose (2 * m + 1) (m + 1) = choose (2 * m + 1) m :=
by { apply choose_symm_of_eq_add,
  rw [add_comm m 1, add_assoc 1 m m, add_comm (2 * m) 1, two_mul m] }

lemma choose_succ_right_eq (n k : ℕ) : choose n (k + 1) * (k + 1) = choose n k * (n - k) :=
begin
  have e : (n+1) * choose n k = choose n k * (k+1) + choose n (k+1) * (k+1),
    rw [← right_distrib, ← choose_succ_succ, succ_mul_choose_eq],
  rw [← tsub_eq_of_eq_add_rev e, mul_comm, ← mul_tsub, add_tsub_add_eq_tsub_right]
end

@[simp] lemma choose_succ_self_right : ∀ (n:ℕ), (n+1).choose n = n+1
| 0     := rfl
| (n+1) := by rw [choose_succ_succ, choose_succ_self_right, choose_self]

lemma choose_mul_succ_eq (n k : ℕ) :
  (n.choose k) * (n + 1) = ((n+1).choose k) * (n + 1 - k) :=
begin
  induction k with k ih, { simp },
  obtain hk | hk := le_or_lt (k + 1) (n + 1),
  { rw [choose_succ_succ, add_mul, succ_sub_succ, ←choose_succ_right_eq, ←succ_sub_succ,
      mul_tsub, add_tsub_cancel_of_le (nat.mul_le_mul_left _ hk)] },
  rw [choose_eq_zero_of_lt hk, choose_eq_zero_of_lt (n.lt_succ_self.trans hk), zero_mul, zero_mul],
end

lemma asc_factorial_eq_factorial_mul_choose (n k : ℕ) :
  n.asc_factorial k = k! * (n + k).choose k :=
begin
  rw mul_comm,
  apply mul_right_cancel₀ (factorial_ne_zero (n + k - k)),
  rw [choose_mul_factorial_mul_factorial, add_tsub_cancel_right, ←factorial_mul_asc_factorial,
    mul_comm],
  exact nat.le_add_left k n,
end

lemma factorial_dvd_asc_factorial (n k : ℕ) : k! ∣ n.asc_factorial k :=
⟨(n+k).choose k, asc_factorial_eq_factorial_mul_choose _ _⟩

lemma choose_eq_asc_factorial_div_factorial (n k : ℕ) :
  (n + k).choose k = n.asc_factorial k / k! :=
begin
  apply mul_left_cancel₀ (factorial_ne_zero k),
  rw ←asc_factorial_eq_factorial_mul_choose,
  exact (nat.mul_div_cancel' $ factorial_dvd_asc_factorial _ _).symm,
end

lemma desc_factorial_eq_factorial_mul_choose (n k : ℕ) : n.desc_factorial k = k! * n.choose k :=
begin
  obtain h | h := nat.lt_or_ge n k,
  { rw [desc_factorial_eq_zero_iff_lt.2 h, choose_eq_zero_of_lt h, mul_zero] },
  rw mul_comm,
  apply mul_right_cancel₀ (factorial_ne_zero (n - k)),
  rw [choose_mul_factorial_mul_factorial h, ←factorial_mul_desc_factorial h, mul_comm],
end

lemma factorial_dvd_desc_factorial (n k : ℕ) : k! ∣ n.desc_factorial k :=
⟨n.choose k, desc_factorial_eq_factorial_mul_choose _ _⟩

lemma choose_eq_desc_factorial_div_factorial (n k : ℕ) : n.choose k = n.desc_factorial k / k! :=
begin
  apply mul_left_cancel₀ (factorial_ne_zero k),
  rw ←desc_factorial_eq_factorial_mul_choose,
  exact (nat.mul_div_cancel' $ factorial_dvd_desc_factorial _ _).symm,
end

/-! ### Inequalities -/

/-- Show that `nat.choose` is increasing for small values of the right argument. -/
lemma choose_le_succ_of_lt_half_left {r n : ℕ} (h : r < n/2) :
  choose n r ≤ choose n (r+1) :=
begin
  refine le_of_mul_le_mul_right _ (lt_tsub_iff_left.mpr (lt_of_lt_of_le h (n.div_le_self 2))),
  rw ← choose_succ_right_eq,
  apply nat.mul_le_mul_left,
  rw [← nat.lt_iff_add_one_le, lt_tsub_iff_left, ← mul_two],
  exact lt_of_lt_of_le (mul_lt_mul_of_pos_right h zero_lt_two) (n.div_mul_le_self 2),
end

/-- Show that for small values of the right argument, the middle value is largest. -/
private lemma choose_le_middle_of_le_half_left {n r : ℕ} (hr : r ≤ n/2) :
  choose n r ≤ choose n (n/2) :=
decreasing_induction
  (λ _ k a,
      (eq_or_lt_of_le a).elim
        (λ t, t.symm ▸ le_refl _)
        (λ h, (choose_le_succ_of_lt_half_left h).trans (k h)))
  hr (λ _, le_rfl) hr

/-- `choose n r` is maximised when `r` is `n/2`. -/
lemma choose_le_middle (r n : ℕ) : choose n r ≤ choose n (n/2) :=
begin
  cases le_or_gt r n with b b,
  { cases le_or_lt r (n/2) with a h,
    { apply choose_le_middle_of_le_half_left a },
    { rw ← choose_symm b,
      apply choose_le_middle_of_le_half_left,
      rw [div_lt_iff_lt_mul' zero_lt_two] at h,
      rw [le_div_iff_mul_le' zero_lt_two, tsub_mul, tsub_le_iff_tsub_le,
          mul_two, add_tsub_cancel_right],
      exact le_of_lt h } },
  { rw choose_eq_zero_of_lt b,
    apply zero_le }
end

/-! #### Inequalities about increasing the first argument -/

lemma choose_le_succ (a c : ℕ) : choose a c ≤ choose a.succ c :=
by cases c; simp [nat.choose_succ_succ]

lemma choose_le_add (a b c : ℕ) : choose a c ≤ choose (a + b) c :=
begin
  induction b with b_n b_ih,
  { simp, },
  exact le_trans b_ih (choose_le_succ (a + b_n) c),
end

lemma choose_le_choose {a b : ℕ} (c : ℕ) (h : a ≤ b) : choose a c ≤ choose b c :=
(add_tsub_cancel_of_le h) ▸ choose_le_add a (b - a) c

lemma choose_mono (b : ℕ) : monotone (λ a, choose a b) := λ _ _, choose_le_choose b

end nat


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau
-/
import data.nat.sqrt

namespace int

/-- `sqrt n` is the square root of an integer `n`. If `n` is not a
  perfect square, and is positive, it returns the largest `k:ℤ` such
  that `k*k ≤ n`. If it is negative, it returns 0. For example,
  `sqrt 2 = 1` and `sqrt 1 = 1` and `sqrt (-1) = 0` -/
@[pp_nodot] def sqrt (n : ℤ) : ℤ :=
nat.sqrt $ int.to_nat n

theorem sqrt_eq (n : ℤ) : sqrt (n*n) = n.nat_abs :=
by rw [sqrt, ← nat_abs_mul_self, to_nat_coe_nat, nat.sqrt_eq]

theorem exists_mul_self (x : ℤ) :
  (∃ n, n * n = x) ↔ sqrt x * sqrt x = x :=
⟨λ ⟨n, hn⟩, by rw [← hn, sqrt_eq, ← int.coe_nat_mul, nat_abs_mul_self],
λ h, ⟨sqrt x, h⟩⟩

theorem sqrt_nonneg (n : ℤ) : 0 ≤ sqrt n := coe_nat_nonneg _

end int


/-
Copyright (c) 2022 Bolton Bailey. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bolton Bailey, Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne
-/
import analysis.special_functions.log.basic
import analysis.special_functions.pow
import data.int.log

/-!
# Real logarithm base `b`

In this file we define `real.logb` to be the logarithm of a real number in a given base `b`. We
define this as the division of the natural logarithms of the argument and the base, so that we have
a globally defined function with `logb b 0 = 0`, `logb b (-x) = logb b x` `logb 0 x = 0` and
`logb (-b) x = logb b x`.

We prove some basic properties of this function and its relation to `rpow`.

## Tags

logarithm, continuity
-/

open set filter function
open_locale topology
noncomputable theory

namespace real

variables {b x y : ℝ}

/-- The real logarithm in a given base. As with the natural logarithm, we define `logb b x` to
be `logb b |x|` for `x < 0`, and `0` for `x = 0`.-/
@[pp_nodot] noncomputable def logb (b x : ℝ) : ℝ := log x / log b

lemma log_div_log : log x / log b = logb b x := rfl

@[simp] lemma logb_zero : logb b 0 = 0 := by simp [logb]

@[simp] lemma logb_one : logb b 1 = 0 := by simp [logb]

@[simp] lemma logb_abs (x : ℝ) : logb b (|x|) = logb b x := by rw [logb, logb, log_abs]

@[simp] lemma logb_neg_eq_logb (x : ℝ) : logb b (-x) = logb b x :=
by rw [← logb_abs x, ← logb_abs (-x), abs_neg]

lemma logb_mul (hx : x ≠ 0) (hy : y ≠ 0) : logb b (x * y) = logb b x + logb b y :=
by simp_rw [logb, log_mul hx hy, add_div]

lemma logb_div (hx : x ≠ 0) (hy : y ≠ 0) : logb b (x / y) = logb b x - logb b y :=
by simp_rw [logb, log_div hx hy, sub_div]

@[simp] lemma logb_inv (x : ℝ) : logb b (x⁻¹) = -logb b x := by simp [logb, neg_div]

section b_pos_and_ne_one

variable (b_pos : 0 < b)
variable (b_ne_one : b ≠ 1)
include b_pos b_ne_one

private lemma log_b_ne_zero : log b ≠ 0 :=
begin
  have b_ne_zero : b ≠ 0, linarith,
  have b_ne_minus_one : b ≠ -1, linarith,
  simp [b_ne_one, b_ne_zero, b_ne_minus_one],
end

@[simp] lemma logb_rpow :
  logb b (b ^ x) = x :=
begin
  rw [logb, div_eq_iff, log_rpow b_pos],
  exact log_b_ne_zero b_pos b_ne_one,
end

lemma rpow_logb_eq_abs (hx : x ≠ 0) : b ^ (logb b x) = |x| :=
begin
  apply log_inj_on_pos,
  simp only [set.mem_Ioi],
  apply rpow_pos_of_pos b_pos,
  simp only [abs_pos, mem_Ioi, ne.def, hx, not_false_iff],
  rw [log_rpow b_pos, logb, log_abs],
  field_simp [log_b_ne_zero b_pos b_ne_one],
end

@[simp] lemma rpow_logb (hx : 0 < x) : b ^ (logb b x) = x :=
by { rw rpow_logb_eq_abs b_pos b_ne_one (hx.ne'), exact abs_of_pos hx, }

lemma rpow_logb_of_neg (hx : x < 0) : b ^ (logb b x) = -x :=
by { rw rpow_logb_eq_abs b_pos b_ne_one (ne_of_lt hx), exact abs_of_neg hx }

lemma surj_on_logb : surj_on (logb b) (Ioi 0) univ :=
λ x _, ⟨rpow b x, rpow_pos_of_pos b_pos x, logb_rpow b_pos b_ne_one⟩

lemma logb_surjective : surjective (logb b) :=
λ x, ⟨b ^ x, logb_rpow b_pos b_ne_one⟩

@[simp] lemma range_logb : range (logb b) = univ :=
(logb_surjective b_pos b_ne_one).range_eq

lemma surj_on_logb' : surj_on (logb b) (Iio 0) univ :=
begin
  intros x x_in_univ,
  use -b ^ x,
  split,
  { simp only [right.neg_neg_iff, set.mem_Iio], apply rpow_pos_of_pos b_pos, },
  { rw [logb_neg_eq_logb, logb_rpow b_pos b_ne_one], },
end

end b_pos_and_ne_one

section one_lt_b

variable (hb : 1 < b)
include hb

private lemma b_pos : 0 < b := by linarith

private

@[simp] lemma logb_le_logb (h : 0 < x) (h₁ : 0 < y) :
  logb b x ≤ logb b y ↔ x ≤ y :=
by { rw [logb, logb, div_le_div_right (log_pos hb), log_le_log h h₁], }

lemma logb_lt_logb (hx : 0 < x) (hxy : x < y) : logb b x < logb b y :=
by { rw [logb, logb, div_lt_div_right (log_pos hb)], exact log_lt_log hx hxy, }

@[simp] lemma logb_lt_logb_iff (hx : 0 < x) (hy : 0 < y) :
  logb b x < logb b y ↔ x < y :=
by { rw [logb, logb, div_lt_div_right (log_pos hb)], exact log_lt_log_iff hx hy, }

lemma logb_le_iff_le_rpow (hx : 0 < x) : logb b x ≤ y ↔ x ≤ b ^ y :=
by rw [←rpow_le_rpow_left_iff hb, rpow_logb (b_pos hb) (b_ne_one hb) hx]

lemma logb_lt_iff_lt_rpow (hx : 0 < x) : logb b x < y ↔ x < b ^ y :=
by rw [←rpow_lt_rpow_left_iff hb, rpow_logb (b_pos hb) (b_ne_one hb) hx]

lemma le_logb_iff_rpow_le (hy : 0 < y) : x ≤ logb b y ↔ b ^ x ≤ y :=
by rw [←rpow_le_rpow_left_iff hb, rpow_logb (b_pos hb) (b_ne_one hb) hy]

lemma lt_logb_iff_rpow_lt (hy : 0 < y) : x < logb b y ↔ b ^ x < y :=
by rw [←rpow_lt_rpow_left_iff hb, rpow_logb (b_pos hb) (b_ne_one hb) hy]

lemma logb_pos_iff (hx : 0 < x) : 0 < logb b x ↔ 1 < x :=
by { rw ← @logb_one b, rw logb_lt_logb_iff hb zero_lt_one hx, }

lemma logb_pos (hx : 1 < x) : 0 < logb b x :=
by { rw logb_pos_iff hb (lt_trans zero_lt_one hx), exact hx, }

lemma logb_neg_iff (h : 0 < x) : logb b x < 0 ↔ x < 1 :=
by { rw ← logb_one, exact logb_lt_logb_iff hb h zero_lt_one, }

lemma logb_neg (h0 : 0 < x) (h1 : x < 1) : logb b x < 0 :=
(logb_neg_iff hb h0).2 h1

lemma logb_nonneg_iff (hx : 0 < x) : 0 ≤ logb b x ↔ 1 ≤ x :=
by rw [← not_lt, logb_neg_iff hb hx, not_lt]

lemma logb_nonneg (hx : 1 ≤ x) : 0 ≤ logb b x :=
(logb_nonneg_iff hb (zero_lt_one.trans_le hx)).2 hx

lemma logb_nonpos_iff (hx : 0 < x) : logb b x ≤ 0 ↔ x ≤ 1 :=
by rw [← not_lt, logb_pos_iff hb hx, not_lt]

lemma logb_nonpos_iff' (hx : 0 ≤ x) : logb b x ≤ 0 ↔ x ≤ 1 :=
begin
  rcases hx.eq_or_lt with (rfl|hx),
  { simp [le_refl, zero_le_one] },
  exact logb_nonpos_iff hb hx,
end

lemma logb_nonpos (hx : 0 ≤ x) (h'x : x ≤ 1) : logb b x ≤ 0 :=
(logb_nonpos_iff' hb hx).2 h'x

lemma strict_mono_on_logb : strict_mono_on (logb b) (set.Ioi 0) :=
λ x hx y hy hxy, logb_lt_logb hb hx hxy

lemma strict_anti_on_logb : strict_anti_on (logb b) (set.Iio 0) :=
begin
  rintros x (hx : x < 0) y (hy : y < 0) hxy,
  rw [← logb_abs y, ← logb_abs x],
  refine logb_lt_logb hb (abs_pos.2 hy.ne) _,
  rwa [abs_of_neg hy, abs_of_neg hx, neg_lt_neg_iff],
end

lemma logb_inj_on_pos : set.inj_on (logb b) (set.Ioi 0) :=
(strict_mono_on_logb hb).inj_on

lemma eq_one_of_pos_of_logb_eq_zero (h₁ : 0 < x) (h₂ : logb b x = 0) :
x = 1 :=
logb_inj_on_pos hb (set.mem_Ioi.2 h₁) (set.mem_Ioi.2 zero_lt_one)
  (h₂.trans real.logb_one.symm)

lemma logb_ne_zero_of_pos_of_ne_one (hx_pos : 0 < x) (hx : x ≠ 1) :
  logb b x ≠ 0 :=
mt (eq_one_of_pos_of_logb_eq_zero hb hx_pos) hx

lemma tendsto_logb_at_top : tendsto (logb b) at_top at_top :=
tendsto.at_top_div_const (log_pos hb) tendsto_log_at_top

end one_lt_b

section b_pos_and_b_lt_one

variable (b_pos : 0 < b)
variable (b_lt_one : b < 1)
include b_lt_one

private lemma b_ne_one : b ≠ 1 := by linarith

include b_pos

@[simp] lemma logb_le_logb_of_base_lt_one (h : 0 < x) (h₁ : 0 < y) :
  logb b x ≤ logb b y ↔ y ≤ x :=
by { rw [logb, logb, div_le_div_right_of_neg (log_neg b_pos b_lt_one), log_le_log h₁ h], }

lemma logb_lt_logb_of_base_lt_one (hx : 0 < x) (hxy : x < y) : logb b y < logb b x :=
by { rw [logb, logb, div_lt_div_right_of_neg (log_neg b_pos b_lt_one)], exact log_lt_log hx hxy, }

@[simp] lemma logb_lt_logb_iff_of_base_lt_one (hx : 0 < x) (hy : 0 < y) :
  logb b x < logb b y ↔ y < x :=
by { rw [logb, logb, div_lt_div_right_of_neg (log_neg b_pos b_lt_one)], exact log_lt_log_iff hy hx }

lemma logb_le_iff_le_rpow_of_base_lt_one (hx : 0 < x) : logb b x ≤ y ↔ b ^ y ≤ x :=
by rw [←rpow_le_rpow_left_iff_of_base_lt_one b_pos b_lt_one, rpow_logb b_pos (b_ne_one b_lt_one) hx]

lemma logb_lt_iff_lt_rpow_of_base_lt_one (hx : 0 < x) : logb b x < y ↔ b ^ y < x :=
by rw [←rpow_lt_rpow_left_iff_of_base_lt_one b_pos b_lt_one, rpow_logb b_pos (b_ne_one b_lt_one) hx]

lemma le_logb_iff_rpow_le_of_base_lt_one (hy : 0 < y) : x ≤ logb b y ↔ y ≤ b ^ x :=
by rw [←rpow_le_rpow_left_iff_of_base_lt_one b_pos b_lt_one, rpow_logb b_pos (b_ne_one b_lt_one) hy]

lemma lt_logb_iff_rpow_lt_of_base_lt_one (hy : 0 < y) : x < logb b y ↔ y < b ^ x :=
by rw [←rpow_lt_rpow_left_iff_of_base_lt_one b_pos b_lt_one, rpow_logb b_pos (b_ne_one b_lt_one) hy]

lemma logb_pos_iff_of_base_lt_one (hx : 0 < x) : 0 < logb b x ↔ x < 1 :=
by rw [← @logb_one b, logb_lt_logb_iff_of_base_lt_one b_pos b_lt_one zero_lt_one hx]

lemma logb_pos_of_base_lt_one (hx : 0 < x) (hx' : x < 1) : 0 < logb b x :=
by { rw logb_pos_iff_of_base_lt_one b_pos b_lt_one hx, exact hx', }

lemma logb_neg_iff_of_base_lt_one (h : 0 < x) : logb b x < 0 ↔ 1 < x :=
by rw [← @logb_one b, logb_lt_logb_iff_of_base_lt_one b_pos b_lt_one h zero_lt_one]

lemma logb_neg_of_base_lt_one (h1 : 1 < x) : logb b x < 0 :=
(logb_neg_iff_of_base_lt_one b_pos b_lt_one (lt_trans zero_lt_one h1)).2 h1

lemma logb_nonneg_iff_of_base_lt_one (hx : 0 < x) : 0 ≤ logb b x ↔ x ≤ 1 :=
by rw [← not_lt, logb_neg_iff_of_base_lt_one b_pos b_lt_one hx, not_lt]

lemma logb_nonneg_of_base_lt_one (hx : 0 < x) (hx' : x ≤ 1) : 0 ≤ logb b x :=
by {rw [logb_nonneg_iff_of_base_lt_one b_pos b_lt_one hx], exact hx' }

lemma logb_nonpos_iff_of_base_lt_one (hx : 0 < x) : logb b x ≤ 0 ↔ 1 ≤ x :=
by rw [← not_lt, logb_pos_iff_of_base_lt_one b_pos b_lt_one hx, not_lt]

lemma strict_anti_on_logb_of_base_lt_one : strict_anti_on (logb b) (set.Ioi 0) :=
λ x hx y hy hxy, logb_lt_logb_of_base_lt_one b_pos b_lt_one hx hxy

lemma strict_mono_on_logb_of_base_lt_one : strict_mono_on (logb b) (set.Iio 0) :=
begin
  rintros x (hx : x < 0) y (hy : y < 0) hxy,
  rw [← logb_abs y, ← logb_abs x],
  refine logb_lt_logb_of_base_lt_one b_pos b_lt_one (abs_pos.2 hy.ne) _,
  rwa [abs_of_neg hy, abs_of_neg hx, neg_lt_neg_iff],
end

lemma logb_inj_on_pos_of_base_lt_one : set.inj_on (logb b) (set.Ioi 0) :=
(strict_anti_on_logb_of_base_lt_one b_pos b_lt_one).inj_on

lemma eq_one_of_pos_of_logb_eq_zero_of_base_lt_one (h₁ : 0 < x) (h₂ : logb b x = 0) :
x = 1 :=
logb_inj_on_pos_of_base_lt_one b_pos b_lt_one (set.mem_Ioi.2 h₁) (set.mem_Ioi.2 zero_lt_one)
  (h₂.trans real.logb_one.symm)

lemma logb_ne_zero_of_pos_of_ne_one_of_base_lt_one (hx_pos : 0 < x) (hx : x ≠ 1) :
  logb b x ≠ 0 :=
mt (eq_one_of_pos_of_logb_eq_zero_of_base_lt_one b_pos b_lt_one hx_pos) hx

lemma tendsto_logb_at_top_of_base_lt_one : tendsto (logb b) at_top at_bot :=
begin
  rw tendsto_at_top_at_bot,
  intro e,
  use 1 ⊔ b ^ e,
  intro a,
  simp only [and_imp, sup_le_iff],
  intro ha,
  rw logb_le_iff_le_rpow_of_base_lt_one b_pos b_lt_one,
  tauto,
  exact lt_of_lt_of_le zero_lt_one ha,
end

end b_pos_and_b_lt_one

lemma floor_logb_nat_cast {b : ℕ} {r : ℝ} (hb : 1 < b) (hr : 0 ≤ r) : ⌊logb b r⌋ = int.log b r :=
begin
  obtain rfl | hr := hr.eq_or_lt,
  { rw [logb_zero, int.log_zero_right, int.floor_zero] },
  have hb1' : 1 < (b : ℝ) := nat.one_lt_cast.mpr hb,
  apply le_antisymm,
  { rw [←int.zpow_le_iff_le_log hb hr, ←rpow_int_cast b],
    refine le_of_le_of_eq _ (rpow_logb (zero_lt_one.trans hb1') hb1'.ne' hr),
    exact rpow_le_rpow_of_exponent_le hb1'.le (int.floor_le _) },
  { rw [int.le_floor, le_logb_iff_rpow_le hb1' hr, rpow_int_cast],
    exact int.zpow_log_le_self hb hr }
end

lemma ceil_logb_nat_cast {b : ℕ} {r : ℝ} (hb : 1 < b) (hr : 0 ≤ r) : ⌈logb b r⌉ = int.clog b r :=
begin
  obtain rfl | hr := hr.eq_or_lt,
  { rw [logb_zero, int.clog_zero_right, int.ceil_zero] },
  have hb1' : 1 < (b : ℝ) := nat.one_lt_cast.mpr hb,
  apply le_antisymm,
  { rw [int.ceil_le, logb_le_iff_le_rpow hb1' hr, rpow_int_cast],
    refine int.self_le_zpow_clog hb r },
  { rw [←int.le_zpow_iff_clog_le hb hr, ←rpow_int_cast b],
    refine (rpow_logb (zero_lt_one.trans hb1') hb1'.ne' hr).symm.trans_le _,
    exact rpow_le_rpow_of_exponent_le hb1'.le (int.le_ceil _) },
end

@[simp] lemma logb_eq_zero :
  logb b x = 0 ↔ b = 0 ∨ b = 1 ∨ b = -1 ∨ x = 0 ∨ x = 1 ∨ x = -1 :=
begin
  simp_rw [logb, div_eq_zero_iff, log_eq_zero],
  tauto,
end

/- TODO add other limits and continuous API lemmas analogous to those in log.lean -/

open_locale big_operators

lemma logb_prod {α : Type*} (s : finset α) (f : α → ℝ) (hf : ∀ x ∈ s, f x ≠ 0):
  logb b (∏ i in s, f i) = ∑ i in s, logb b (f i) :=
begin
  classical,
  induction s using finset.induction_on with a s ha ih,
  { simp },
  simp only [finset.mem_insert, forall_eq_or_imp] at hf,
  simp [ha, ih hf.2, logb_mul hf.1 (finset.prod_ne_zero_iff.2 hf.2)],
end

end real