import analysis.specific_limits
import data.int.parity

attribute [instance] classical.prop_decidable

/-
Lemmas from that file were hidden in my course, or restating things which
were proved without name in previous files.
-/

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

-- The mathlib version is unusable because it is stated in terms of ≤
lemma ge_max_iff {α : Type*} [decidable_linear_order α] {p q r : α} : r ≥ max p q  ↔ r ≥ p ∧ r ≥ q :=
max_le_iff

/- No idea why this is not in mathlib-/
lemma eq_of_abs_sub_le_all (x y : ℝ) : (∀ ε > 0, |x - y| ≤ ε) → x = y :=
begin
  intro h,
  apply  decidable_linear_ordered_add_comm_group.eq_of_abs_sub_nonpos,
  by_contradiction H,
  push_neg at H,
  specialize h ( |x-y|/2) (by linarith),
  linarith,
end

def seq_limit (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

lemma unique_limit {u l l'} : seq_limit u l → seq_limit u l' → l = l' :=
begin
  intros hl hl',
  apply eq_of_abs_sub_le_all,
  intros ε ε_pos,
  specialize hl (ε/2) (by linarith),
  cases hl with N hN,
  specialize hl' (ε/2) (by linarith),
  cases hl' with N' hN',
  specialize hN (max N N') (le_max_left _ _),
  specialize hN' (max N N') (le_max_right _ _),
  calc |l - l'| = |(l-u (max N N')) + (u (max N N') -l')| : by ring
  ... ≤ |l - u (max N N')| + |u (max N N') - l'| : by apply abs_add
  ... =  |u (max N N') - l| + |u (max N N') - l'| : by rw abs_sub
  ... ≤ ε/2 + ε/2 : by linarith
  ... = ε : by ring,
end

def pair (n : ℤ) := ∃ k, n = 2*k

def int.odd (n : ℤ) := ∃ k, n = 2*k + 1

lemma int.not_even_iff_odd {n : ℤ} : ¬ int.even n ↔ int.odd n :=
begin
  rw int.not_even_iff,
  split ; intro h,
  use n/2,
  conv_rhs { rw add_comm, congr, rw ← h },
  exact (int.mod_add_div n 2).symm,
  rcases h with ⟨k, rfl⟩,
  simp [add_comm],
  refl,
end

lemma le_of_le_add_all {x y : ℝ} :
  (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  contrapose!,
  intro h,
  use (y-x)/2,
  split ; linarith,
end

def upper_bound (A : set ℝ) (x : ℝ) := ∀ a ∈ A, a ≤ x

def is_sup (A : set ℝ) (x : ℝ) := upper_bound A x ∧ ∀ y, upper_bound A y → x ≤ y

lemma lt_sup {A : set ℝ} {x : ℝ} (hx : is_sup A x) :
∀ y, y < x → ∃ a ∈ A, y < a :=
begin
  intro y,
  contrapose!,
  exact hx.right y,
end

lemma squeeze {u v w : ℕ → ℝ} {l} (hu : seq_limit u l) (hw : seq_limit w l)
(h : ∀ n, u n ≤ v n)
(h' : ∀ n, v n ≤ w n) : seq_limit v l :=
begin
  intros ε ε_pos,
  cases hu ε ε_pos with N hN,
  cases hw ε ε_pos with N' hN',
  use max N N',
  intros n hn,
  rw ge_max_iff at hn,
  specialize hN n (by linarith),
  specialize hN' n (by linarith),
  specialize h n,
  specialize h' n,
  rw abs_le at *,
  split ; linarith
end

def extraction (φ : ℕ → ℕ) := ∀ n m, n < m → φ n < φ m

def tendsto_infinity (u : ℕ → ℝ) := ∀ A, ∃ N, ∀ n ≥ N, u n ≥ A

lemma lim_le {x y : ℝ} {u : ℕ → ℝ} (hu : seq_limit u x)
  (ineg : ∀ n, u n ≤ y) : x ≤ y :=
begin
  apply le_of_le_add_all,
  intros ε ε_pos,
  cases hu ε ε_pos with N hN,
  specialize hN N (by linarith),
  specialize ineg N,
  rw abs_le at hN,
  linarith,
end

/-
lemma limite_infinie_pas_finie {u : ℕ → ℝ} :
  limite_infinie_suite u → ∀ x, ¬ seq_limit u x :=
begin
  -- sorry
  intros lim_infinie x lim_x,
  cases lim_x 1 (by linarith) with N hN,
  cases lim_infinie (x+2) with N' hN',
  let N₀ := max N N',
  specialize hN N₀ (inferieur_max_gauche _ _),
  specialize hN' N₀ (inferieur_max_droite _ _),
  rw abs_inferieur_ssi at hN,
  linarith',
  -- sorry
end -/

lemma inv_succ_le_all :  ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, 1/(n + 1 : ℝ) ≤ ε :=
begin
  convert metric.tendsto_at_top.mp (tendsto_one_div_add_at_top_nhds_0_nat),
  apply propext,
  simp only [real.dist_eq, sub_zero],
  split,
    intros h ε ε_pos,
    cases h (ε/2) (by linarith) with N hN,
    use N,
    intros n hn,
    rw abs_of_pos (nat.one_div_pos_of_nat : 1/(n+1 : ℝ) > 0),
    specialize hN n hn,
    linarith,
  intros h ε ε_pos,
  cases h ε (by linarith) with N hN,
  use N,
  intros n hn,
  specialize hN n hn,
  rw abs_of_pos (@nat.one_div_pos_of_nat ℝ _ n) at hN,
  linarith,
end

lemma limit_const (x : ℝ) : seq_limit (λ n, x) x :=
λ ε ε_pos, ⟨0, λ _ _, by simp [le_of_lt ε_pos]⟩

lemma limit_of_sub_le_inv_succ {u : ℕ → ℝ} {x : ℝ} (h : ∀ n, |u n - x| ≤ 1/(n+1)) :
seq_limit u x :=
begin
  intros ε ε_pos,
  rcases inv_succ_le_all ε ε_pos with ⟨N, hN⟩,
  use N,
  intros n hn,
  specialize h n,
  specialize hN n hn,
  linarith,
end

lemma limit_const_add_inv_succ (x : ℝ) : seq_limit (λ n, x + 1/(n+1)) x :=
limit_of_sub_le_inv_succ (λ n, by rw abs_of_pos ; linarith [@nat.one_div_pos_of_nat ℝ _ n])

lemma limit_const_sub_inv_succ (x : ℝ) : seq_limit (λ n, x - 1/(n+1)) x :=
begin
  refine limit_of_sub_le_inv_succ (λ n, _),
  rw [show x - 1 / (n + 1) - x = -(1/(n+1)), by ring, abs_neg,  abs_of_pos],
  linarith [@nat.one_div_pos_of_nat ℝ _ n]
end

lemma id_le_extraction {φ}: extraction φ → ∀ n, n ≤ φ n :=
begin
  intros hyp n,
  induction n with n hn,
  { exact nat.zero_le _ },
  { exact nat.succ_le_of_lt (by linarith [hyp n (n+1) (by linarith)]) },
end

lemma seq_limit_id : tendsto_infinity (λ n, n) :=
begin
  intros A,
  cases exists_nat_gt A with N hN,
  use N,
  intros n hn,
  have : (n : ℝ) ≥ N, exact_mod_cast hn,
  linarith,
end

lemma extraction_machine (ψ : ℕ → ℕ) (hψ : ∀ n, ψ n ≥ n) : ∃ f : ℕ → ℕ, extraction (ψ ∘ f) ∧ ∀ n, f n ≥ n :=
begin
  refine ⟨λ n, nat.rec_on n 0 (λ n ih, ψ ih + 1), λ m n h, _, λ n, _⟩,
  { induction h; dsimp [(∘)],
    { exact hψ _ },
    { exact lt_trans h_ih (hψ _) } },
  { induction n, {apply le_refl},
    exact nat.succ_le_succ (le_trans n_ih (hψ _)) }
end

variables {u : ℕ → ℝ} {l : ℝ} {φ : ℕ → ℕ}

lemma limite_extraction_si_limite (h : seq_limit u l) (hφ : extraction φ) :
seq_limit (u ∘ φ) l :=
begin
  -- sorry
  intros ε ε_pos,
  cases h ε ε_pos with N hN,
  use N,
  intros n hn,
  apply hN,
  calc N ≤ n   : hn
     ... ≤ φ n : id_le_extraction hφ n,
  -- sorry
end

def segment (a b : ℝ) := {x | a ≤ x ∧ x ≤ b}

open set filter

def cluster_point (u : ℕ → ℝ) (a : ℝ) :=
∃ φ, extraction φ ∧ seq_limit (u ∘ φ) a

lemma bolzano_weierstrass {a b : ℝ} {u : ℕ → ℝ} (h : ∀ n, u n ∈ Icc a b) :
∃ c ∈ Icc a b, cluster_point u c :=
begin
  have cpct : compact (Icc a b),
    exact compact_Icc,
  have :  map u at_top ≤ principal (Icc a b),
  { change tendsto u _ _,
    rw tendsto_principal,
    filter_upwards [univ_mem_sets],
    intros n hn,
    exact h n },
  rcases cpct (map u at_top) (map_ne_bot at_top_ne_bot) this with ⟨c, h, h'⟩,
  clear this,
  use [c, h],
  unfold cluster_point,
  have : ∀ N, ∃ n ≥ N, |u n -c| ≤ 1/(N+1),
    intro N,
    rw ← forall_sets_nonempty_iff_ne_bot at h',
    specialize h' (u '' {n | n ≥ N} ∩ {x | |x-c| ≤ 1/(N+1)}) _,
    { simp only [set.nonempty,
                set.mem_image,
                set.mem_inter_eq,
                ne.def,
                set.mem_set_of_eq] at h',
      rcases h' with ⟨_, ⟨n, ⟨hn, rfl⟩⟩, ineg⟩,
      use [n, hn, ineg] },
    { apply inter_mem_inf_sets,
      { apply image_mem_map,
        apply mem_at_top },
      { have fact: (0 : ℝ) < 1/(N+2),
          exact_mod_cast (nat.one_div_pos_of_nat : 1/((N+1 : ℕ) + 1 : ℝ) > 0),
        apply mem_sets_of_superset (metric.ball_mem_nhds c fact),
        intros x x_in,
        rw [metric.mem_ball, real.dist_eq] at x_in,
        exact le_of_lt (
          calc |x - c| < 1 / (N + 2) : x_in
                   ... = 1/((N+1)+1) : by { congr' 1, norm_cast }
                   ... ≤ 1 / (N + 1) : nat.one_div_le_one_div (nat.le_succ N)),

       } },
  choose ψ hψ using this,
  cases forall_and_distrib.mp hψ with hψ_id hψ', clear hψ,
  rcases extraction_machine ψ hψ_id with ⟨f, hf, hf'⟩,
  use [ψ ∘ f, hf],
  apply limit_of_sub_le_inv_succ,
  intros n,
  transitivity 1/(f n + 1 : ℝ),
  apply hψ',
  exact nat.one_div_le_one_div (hf' n),
end

lemma not_seq_limit_of_tendstoinfinity {u : ℕ → ℝ} :
  tendsto_infinity u → ∀ x, ¬ seq_limit u x :=
begin
  intros lim_infinie x lim_x,
  cases lim_x 1 (by linarith) with N hN,
  cases lim_infinie (x+2) with N' hN',
  let N₀ := max N N',
  specialize hN N₀ (le_max_left _ _),
  specialize hN' N₀ (le_max_right _ _),
  rw abs_le at hN,
  linarith,
end

open real

lemma sup_segment {a b : ℝ} {A : set ℝ} (hnonvide : ∃ x, x ∈ A) (h : A ⊆ Icc a b) :
  ∃ x ∈ Icc a b, is_sup A x :=
begin
  have b_maj :  ∀ (y : ℝ), y ∈ A → y ≤ b,
    from λ y y_in, (h y_in).2,
  have Sup_maj : upper_bound A (Sup A),
  { intro x,
    apply real.le_Sup,
    use [b, b_maj] } ,
  refine ⟨Sup A, _, _⟩,
  { split,
    { cases hnonvide with x x_in,
      exact le_trans (h x_in).1 (Sup_maj _ x_in) },
    { apply Sup_le_ub A hnonvide b_maj } },
  { use Sup_maj,
    intros y y_in,
    rwa real.Sup_le _ hnonvide ⟨b, b_maj⟩ },
end

lemma subseq_tendsto_of_tendsto (h : seq_limit u l) (hφ : extraction φ) :
seq_limit (u ∘ φ) l :=
begin
  intros ε ε_pos,
  cases h ε ε_pos with N hN,
  use N,
  intros n hn,
  apply hN,
  calc N ≤ n   : hn
     ... ≤ φ n : id_le_extraction hφ n,
end
namespace tactic.interactive
open tactic

meta def check_me : tactic unit :=
`[ { repeat { unfold seq_limit},
   repeat { unfold continue_en },
   push_neg,
   try { simp only [exists_prop] },
   try { exact iff.rfl },
   done } <|> fail "Ce n'est pas cela. Essayez encore." ]

end tactic.interactive


-- M40002 (Analysis I) Chapter 4. Series

import M40002.M40002_C3

namespace M40002

-- Definition of convergent sums
def partial_sum_to (a : ℕ → ℝ) (n : ℕ) := finset.sum (finset.range n) a
notation `∑` a := partial_sum_to a

def sum_converges_to (a : ℕ → ℝ) (l : ℝ) := (partial_sum_to a) ⇒ l
notation a ` ∑⇒ ` l := sum_converges_to a l

/- Maybe use this notation instead
def X (f : real ->Prop) (x : real) := f x
notation `∑` binders `, ` r:(scoped P, sum_converges_to P) := r
infixr ` ⇛ `: 50 := X  -/

def sum_convergent (a : ℕ → ℝ) := ∃ l : ℝ, a ∑⇒ l
notation ` ∑⇒ ` a := sum_convergent a

-- s n - s (n - 1) = a n
lemma successive_diff {a : ℕ → ℝ} {n : ℕ} : (partial_sum_to a (n + 1)) - (partial_sum_to a n) = a n :=
by {unfold partial_sum_to,
    rw finset.sum_range_succ,
    simp
}

-- ∑ a is convergent implies a → 0
theorem test_trivial (a : ℕ → ℝ) : (∑⇒ a) → a ⇒ 0 :=
begin
    intros h ε hε,
    cases h with l hl,
    have : cauchy (partial_sum_to a) :=
        by {rwa cauchy_iff_conv,
        use l, from hl
        },
    cases this ε hε with N hN,
    use N, intros n hn,
    rw sub_zero (a n),
    suffices hα : abs (partial_sum_to a n - partial_sum_to a (n + 1)) < ε,
        {rwa [abs_sub, successive_diff] at hα},
    apply hN n (n + 1),
    have : N ≤ n + 1 := by {linarith},
    from ⟨hn, this⟩
end

lemma succ_sum_def {a : ℕ → ℝ} : ∀ k : ℕ, (∑ a) (nat.succ k) = (∑ a) k + a k :=
by {intro k, unfold partial_sum_to,
    rw finset.sum_range_succ,
    from add_comm (a k) (finset.sum (finset.range k) a)
}

-- If a ≥ 0, then ↑ (∑ a)
lemma sum_mono_increasing {a : ℕ → ℝ} (h : ∀ n : ℕ, 0 ≤ a n) : (∑ a) ↑ :=
begin
    intro n, rw succ_sum_def n,
    have hβ : 0 ≤ a n := h n,
    linarith
end

-- If a ≥ 0, then ∑ a is convergent if ∑ a is bounded above
theorem sum_bdd_abv_conv {a : ℕ → ℝ} (h : ∀ n : ℕ, 0 ≤ a n) : (seq_bounded_above (∑ a)) → (∑⇒ a) :=
begin
    intro hα,
    apply mono_increasing_means_conv (∑ a) _,
    split,
    {from hα},
    {use 0, intro m,
    induction m with k hk,
        {unfold partial_sum_to, simp},
            {rw succ_sum_def k,
            from add_nonneg hk (h k)
            },
        },
    from sum_mono_increasing h
end

-- Comparison Test. If 0 ≤ a ≤ b, and if ∑ b converges then so do ∑ a
theorem test_comparison {a b : ℕ → ℝ} : (∀ n : ℕ, 0 ≤ a n ∧ a n ≤ b n) ∧ (∑⇒ b) → ∑⇒ a :=
begin
    rintro ⟨hα, ⟨l, hl⟩⟩,
    apply sum_bdd_abv_conv,
        {intro n,
        from (hα n).left
        },
        {rcases converge_is_bdd (∑ b) ⟨l, hl⟩ with ⟨⟨N, hN⟩, blw⟩,
        use N, intro n,
        have : (∑ a) n ≤ (∑ b) n :=
            by {induction n with k hk,
            unfold partial_sum_to, simp,
            repeat {rw succ_sum_def},
            have : a k ≤ b k := (hα k).right,
            linarith
            },
        from le_trans this (hN n)
        }
end

-- Algebra of limits for sums
lemma sum_add_sum_split {a b : ℕ → ℝ} : (∑ a) + (∑ b) = (∑ a + b) :=
begin
    rw [seq_add_seq, function.funext_iff],
    intro n,
    unfold partial_sum_to,
    induction n with k hk,
        {simp},
        {repeat {rw finset.sum_range_succ},
        rw [←hk, seq_add_seq], simp
        }
end

theorem add_sum_lim_conv {a b : ℕ → ℝ} {l m : ℝ} (h₁ : a ∑⇒ l) (h₂ : b ∑⇒ m) : (a + b) ∑⇒ (l + m) :=
begin
    unfold sum_converges_to,
    rw ←sum_add_sum_split,
    apply add_lim_conv,
    from ⟨h₁, h₂⟩
end

lemma scalar_mul_sum_split {a : ℕ → ℝ} {m : ℝ} : ((∑ a) × m) = ∑ (a × m) :=
begin
    rw function.funext_iff,
    intro n, rw seq_mul_real,
    unfold partial_sum_to,
    induction n with k hk,
        {simp},
        {rw [finset.sum_range_succ, add_mul, hk],
        have : a k * m = (a × m) k := rfl,
        rwa [this, ←finset.sum_range_succ]
        }
end

theorem scalar_sum_lim_conv {a : ℕ → ℝ} {l m : ℝ} (h₁ : a ∑⇒ l) : (a × m) ∑⇒ l * m :=
begin
    unfold sum_converges_to,
    rw ←scalar_mul_sum_split,
    apply mul_lim_conv,
    from h₁,
    from cons_conv
end

-- Defining absolute convergence
noncomputable def abs_seq (a : ℕ → ℝ) (n : ℕ) := abs (a n)
def abs_sum_converge (a : ℕ → ℝ) := ∑⇒ abs_seq a

-- Absolutely convergen implies normal convergence
lemma sum_diff {a : ℕ → ℝ} {n m : ℕ} (h₁ : n < m) : (∑ a) m - (∑ a) n = finset.sum (finset.Ico n m) a :=
begin
    unfold partial_sum_to,
    induction m with k hk,
        {exfalso, from nat.not_succ_le_zero n h₁},
        {rw [finset.sum_range_succ, finset.sum_Ico_succ_top],
        swap, from nat.lt_succ_iff.mp h₁,
        simp,
        cases nat.lt_succ_iff_lt_or_eq.mp h₁,
            {rw [←sub_eq_add_neg, hk h]},
            {rw h, simp}
        }
end

lemma sum_le (a b : ℕ → ℝ) {n m : ℕ} (h₁ : ∀ n : ℕ, a n ≤ b n) : finset.sum (finset.Ico n m) a ≤ finset.sum (finset.Ico n m) b :=
begin
    induction m with k hk,
        {rw finset.Ico.eq_empty_iff.mpr (zero_le n), simp},
        {cases le_or_lt n k,
            {repeat {rw finset.sum_Ico_succ_top h},
            apply add_le_add hk _,
            from h₁ k
            },
            {rw finset.Ico.eq_empty_iff.mpr (nat.succ_le_iff.mpr h),
            simp
            }
        }
end

lemma sum_pos {a : ℕ → ℝ} {n m : ℕ} (h₁ : ∀ k : ℕ, 0 ≤ a k) : 0 ≤ finset.sum (finset.Ico n m) a :=
begin
    induction m with k hk,
        {rw finset.Ico.eq_empty_iff.mpr (zero_le n), simp},
        {cases le_or_lt n k,
            {rw finset.sum_Ico_succ_top h,
            from add_nonneg hk (h₁ k)
            },
            {rw finset.Ico.eq_empty_iff.mpr (nat.succ_le_iff.mpr h),
            simp
            }
        }
end

lemma sum_triangle {a : ℕ → ℝ} {n m : ℕ} : abs (finset.sum (finset.Ico n m) a) ≤ finset.sum (finset.Ico n m) (abs_seq a) :=
begin
    induction m with k hk,
        {rw finset.Ico.eq_empty_iff.mpr (zero_le n), simp},
        {cases le_or_lt n k,
            {repeat {rw finset.sum_Ico_succ_top h},
            apply le_trans (abs_add (finset.sum (finset.Ico n k) a) (a k)),
            show abs (finset.sum (finset.Ico n k) a) + abs (a k) ≤ finset.sum (finset.Ico n k) (abs_seq a) + abs (a k),
            suffices : abs (finset.sum (finset.Ico n k) a) ≤ finset.sum (finset.Ico n k) (abs_seq a),
                simpa,
            assumption
            },
            {rw finset.Ico.eq_empty_iff.mpr (nat.succ_le_iff.mpr h),
            simp
            }
        }
end

lemma neg_sum {a : ℕ → ℝ} {n m : ℕ} : - finset.sum (finset.Ico n m) a = finset.sum (finset.Ico n m) (a × -1) :=
begin
    induction m with k hk,
        {rw finset.Ico.eq_empty_iff.mpr (zero_le n), simp},
        {cases le_or_lt n k,
            {repeat {rw finset.sum_Ico_succ_top h},
            unfold seq_mul_real, simp
            },
            {rw finset.Ico.eq_empty_iff.mpr (nat.succ_le_iff.mpr h),
            simp
            }
        }
end

theorem abs_conv_to_conv {a : ℕ → ℝ} : abs_sum_converge a → ∑⇒ a :=
begin
    intro h₁,
    suffices : cauchy (∑ a),
        cases cauchy_iff_conv.mp this with l hl,
        use l, from hl,
    have hcauchy : cauchy (∑ abs_seq a) := cauchy_iff_conv.mpr h₁,
    unfold cauchy at hcauchy,
    intros ε hε,
    cases hcauchy ε hε with N hN,
    use N, intros n m h₂,
    replace hN : abs ((∑abs_seq a) n - (∑abs_seq a) m) < ε := hN n m h₂,
    cases lt_trichotomy n m,
        swap, cases h,
        {rw h, simp, from hε},
        {rw sum_diff h,
        rw sum_diff h at hN,
        apply lt_of_le_of_lt _ hN,
        rw abs_le,
        split, swap,
            {have : ∀ k : ℕ, a k ≤ abs_seq a k := λ k : ℕ,
                by {from le_max_left (a k) (- a k)},
            apply le_trans (sum_le a (abs_seq a) this),
            from le_max_left (finset.sum (finset.Ico m n) (abs_seq a)) (-finset.sum (finset.Ico m n) (abs_seq a))
            },
            {rw abs_of_nonneg, swap,
                {apply sum_pos,
                intro k, from abs_nonneg (a k)},
                {rw neg_sum, apply sum_le,
                intro n, unfold seq_mul_real,
                simp, rw neg_le, from le_max_right (a n) (-a n)
                }
            }
        },
        {rw [abs_sub, sum_diff h],
        rw [abs_sub, sum_diff h] at hN,
        apply lt_of_le_of_lt _ hN,
        rw abs_le,
        split, swap,
            {have : ∀ k : ℕ, a k ≤ abs_seq a k := λ k : ℕ,
                by {from le_max_left (a k) (- a k)},
            apply le_trans (sum_le a (abs_seq a) this),
            from le_max_left (finset.sum (finset.Ico n m) (abs_seq a)) (-finset.sum (finset.Ico n m) (abs_seq a))
            },
            {rw abs_of_nonneg, swap,
                {apply sum_pos,
                intro k, from abs_nonneg (a k)},
                {rw neg_sum, apply sum_le,
                intro n, unfold seq_mul_real,
                simp, rw neg_le, from le_max_right (a n) (-a n)
                }
            }
        }
end

-- set_option trace.simplify.rewrite true
-- Sandwich theorem for sums
theorem test_sum_sandwich {a b c : ℕ → ℝ} {h₁ : ∀ n : ℕ, c n ≤ a n ∧ a n ≤ b n} : (∑⇒ c) ∧ (∑⇒ b) → ∑⇒ a :=
begin
    intro h₂,
    have hcauchyb : cauchy ∑ b := conv_to_cauchy (∑ b) h₂.right,
    have hcauchyc : cauchy ∑ c := conv_to_cauchy (∑ c) h₂.left,
    suffices : cauchy (∑ a),
        cases cauchy_iff_conv.mp this with l hl,
        use l, from hl,
    intros ε hε,
    cases hcauchyb ε hε with N₁ hN₁,
    cases hcauchyc ε hε with N₂ hN₂,
    let N : ℕ := max N₁ N₂,
    use N,
    intros n m hnm,
    cases lt_trichotomy n m,
        rw [abs_sub, abs_lt],
        split,
-- These parts are essentially the same
            {have : N₂ ≤ n ∧ N₂ ≤ m :=
                by {split,
                apply le_trans _ hnm.left,
                from le_max_right N₁ N₂,
                apply le_trans _ hnm.right,
                from le_max_right N₁ N₂
                },
            replace h₂ : -ε < (∑c) m - (∑c) n :=
                by {have hα : abs ((∑c) n - (∑c) m) < ε := hN₂ n m this,
                rw abs_sub at hα,
                from (abs_lt.mp hα).left
                },
            apply lt_of_lt_of_le h₂,
            repeat {rw sum_diff},
            apply sum_le, intro n, from (h₁ n).left,
            repeat {assumption}
            },
-- 1
            {have : N₁ ≤ n ∧ N₁ ≤ m :=
                by {split,
                apply le_trans _ hnm.left,
                from le_max_left N₁ N₂,
                apply le_trans _ hnm.right,
                from le_max_left N₁ N₂
                },
            replace h₂ : (∑b) m - (∑b) n < ε :=
                by {have hα : abs ((∑b) n - (∑b) m) < ε := hN₁ n m this,
                rw abs_sub at hα,
                from (abs_lt.mp hα).right
                },
            apply lt_of_le_of_lt _ h₂,
            repeat {rw sum_diff},
            apply sum_le, intro n, from (h₁ n).right,
            repeat {assumption}
            },
        cases h,
            {rw h, simp, from hε},
            {rw abs_lt,
                split,
-- 2
                {have : N₂ ≤ n ∧ N₂ ≤ m :=
                    by {split,
                    apply le_trans _ hnm.left,
                    from le_max_right N₁ N₂,
                    apply le_trans _ hnm.right,
                    from le_max_right N₁ N₂
                    },
                replace h₂ : -ε < (∑c) n - (∑c) m :=
                    by {have hα : abs ((∑c) n - (∑c) m) < ε := hN₂ n m this,
                    from (abs_lt.mp hα).left
                    },
                apply lt_of_lt_of_le h₂,
                repeat {rw sum_diff},
                apply sum_le, intro n, from (h₁ n).left,
                repeat {assumption}
                },
-- 3
                {have : N₁ ≤ n ∧ N₁ ≤ m :=
                    by {split,
                    apply le_trans _ hnm.left,
                    from le_max_left N₁ N₂,
                    apply le_trans _ hnm.right,
                    from le_max_left N₁ N₂
                    },
                replace h₂ : (∑b) n - (∑b) m < ε :=
                    by {have hα : abs ((∑b) n - (∑b) m) < ε := hN₁ n m this,
                    from (abs_lt.mp hα).right
                    },
                apply lt_of_le_of_lt _ h₂,
                repeat {rw sum_diff},
                apply sum_le, intro n, from (h₁ n).right,
                repeat {assumption}
            }
        }
end

end M40002


import data.quot -- equivalence relations and equivalence classes
import tactic.interactive -- for "choose" tactic

-- Sian's definition of an equivalence relation on X corresponding to a function f : X → Y

-- X and Y and f are fixed once and for all in this story, so let's make them parameters
-- in a section.

section -- in this section X and Y and the surjection f will be fixed once and for all

open function -- so I can write "surjective" instead of "function.surjective"

parameters {X : Type} {Y : Type} (f : X → Y) (hf : surjective f)

-- now let's define the binary relation that Sian came up with

def R (x₁ x₂ : X) : Prop := f x₁ = f x₂

-- top tip -- sometimes "unfold R" doesn't work. I don't know why. Try the tactic "delta R"? This always works.

theorem R.equivalence : equivalence R :=
begin
  split,
    delta R,
    unfold reflexive,
    intro,
    refl,
  split,
    delta R,
    unfold symmetric,
    intros,
    rw a,
  delta R,
  unfold transitive,
  intros,
  rw a,
  assumption,
end

-- A "setoid" is Lean's rather pretentious term for
-- the data of a type and an equivalence relation on the type.
-- More precisely, a term of type `setoid X` is a *pair*
-- consisting of
-- 1) a binary relation R on X
-- 2) a proof that R is an equivalence relation.

-- We just proved R was an equivalence relation on X so we can make a setoid.
-- Note that we have to give both the relation and the proof that it's an equivalence relation.

def s : setoid X := ⟨R, R.equivalence⟩ -- pointy brackets is often a constructor for a type which is a pair.

-- let's define Q to be the set of equivalence classes for this equivalence relation.

-- Lean takes the "quotient of the setoid".
def Q := quotient s
-- Now Q is the set of equivalence classes.

-- when you left the office today Friday, I had challenged you to give maps from Q to Y
-- You said "Let S be an equivalence class. Define F(S) = f(x) where x is any element of S."

-- I said "yeah but you need to check that that is well-defined, because what if your definition
-- of F(S) depends on the choice of x?"

-- If you try to make this definition in Lean, as below, then Lean makes precisely the
-- same objection. It wants to know why two things in the same equivalence class give the same answer.

def F : Q → Y := λ q, quotient.lift_on' q (λ (x : X), f x) begin
  show ∀ a b : X, R a b → f a = f b,
  delta R,
  intros,
  assumption,
end

theorem commutes' (x : X) : f x = F (quotient.mk' x) :=
begin
  refl
end

-- technical note: I managed to define a function from Q to somewhere because I knew
-- an "eliminator" for Q, namely quotient.lift_on'

-- def G : Y → Q := sorry -- you never did that bit. How will you do it?

-- you will need to know a "constructor" for Q, namely a way to make elements of Q.
-- Here is a function from X to Q.

example : X → Q := quotient.mk'

-- Using that function, can you make a function from Y to Q? Here's a hint.

include hf -- need surjectivity for this one
noncomputable def G : Y → Q := λ y, quotient.mk' (classical.some (hf y))

-- What shall we do next?

lemma commutes (x : X) : G (f x) = quotient.mk' x :=
begin
  let x' := classical.some (hf (f x)),
  have xh' := classical.some_spec (hf (f x)),
  change f x' = f x at xh',
  change quotient.mk' (x') = _,
  rw quotient.eq',
  show R _ x,
  exact xh',
end

end -- section


import ..prooflab
import lectures.lec2_algebraic_identities
import lectures.lec3_function

/-! # Homework 2
Homework must be done individually.
Replace the placeholders (e.g., `:= sorry`) with your solutions. -/

/-
Tactics we have learned before Homework 2:
1. `refl`
2. `exact`
3. `rw` and its variants
4. `change`
5. `calc`
6. `ring`
7. `linear_combination`
-/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace PROOFS

variable {X : Type}

/-! ## Question 1 (10 points):
Use `rw` tactic together the lemmas `mul_one` and `one_mul` to construct a proof to the statement below. You are only allowed to use `rw` tactic and no other tactic.
-/

#check (mul_one : ∀ (a : ℚ), a * 1 = a)
#check (one_mul : ∀ (a : ℚ), 1 * a = a)

example (x y z : ℚ) (h₁ : x * 1 = 1 * y) (h₂ : y = z)  :
  x = z :=
begin
  sorry,
end

/-! ## Question 2 (20 points):
Use the tactic `calc` in conjunction with the following lemmas to prove that given rational numbers `a`, `b`, and `c`, if `b * a = 1` and `a * c = 1` then `b = c`.
-/

#check mul_one
#check one_mul
#check mul_assoc

example (a b c : ℚ) (h₁ : b * a = 1) (h₂ : a * c = 1) :
  b = c :=
begin
  sorry,
end

/-! ## Question 3 (20 points)
Use `rw` and the lemmas below to comeplete the proof the `example` below.
-/

#check pow_two
#check mul_add
#check add_mul
#check add_assoc
#check add_comm
#check one_mul
#check mul_one
#check two_mul
#check mul_two

example (a : ℝ) :
  (a + 1)^2 = a^2 + 2 * a + 1 :=
begin
  calc (a + 1)^2 = (a + 1) * (a + 1) : by rw pow_two
  ...            = (a + 1) * a + (a + 1) * 1 : sorry
  ...            = (a * a + 1 * a) + (a * 1 + 1 * 1) : sorry
  ...            = (a^2 + a) + (a + 1) : sorry
  ...            = a^2 + 2 * a + 1 : by sorry
end

/-! ## Question 4 (25 points):
Construct a proof for the following statement using any tactics we heav learned (tactics 1-7 in the list at the top of this file) except `ring` and only the lemmas we have learned so far and possibly the lemma `pow_mul` below.
-/

section
variable a : ℝ
#check (pow_mul a : ∀ (m n : ℕ), a ^ (m * n) = (a ^ m) ^ n)
end

example (a b : ℝ) :
  a^4 - b^4 = (a^2 + b^2) * (a + b) * (a - b) :=
begin
  sorry,
end

/-! ## Question 5 (25 points):
1. For a type `X`, Define the function `triple_shuffle` which takes a triple `(a, b, c)` as in input, where `a b c : X`, and returns the triple `(b , c, a)`
-/

def triple_shuffle  : X × X × X → X × X × X :=
sorry

/-
2. Evaluate the application of `triple_shuffle` to `(1,2,3)`.
-/

/-
3. Evaluate the application of `triple_shuffle` to `triple_shuffle (1,2,3)`.
-/

/-
4. Prove that third application of `triple_shuffle` to the triple `(1,2,3)` is equal to `(1,2,3)`.
-/
example :
triple_shuffle (triple_shuffle (triple_shuffle (1,2,3) )) = (1,2,3) :=
begin
  sorry,
end

end PROOFS


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

/-!
#  Naturals pairing function

This file defines a pairing function for the naturals as follows:
```text
 0  1  4  9 16
 2  3  5 10 17
 6  7  8 11 18
12 13 14 15 19
20 21 22 23 24
```

It has the advantage of being monotone in both directions and sending `⟦0, n^2 - 1⟧` to
`⟦0, n - 1⟧²`.
-/

open prod decidable function

namespace nat

/-- Pairing function for the natural numbers. -/
@[pp_nodot] def mkpair (a b : ℕ) : ℕ :=
if a < b then b*b + a else a*a + a + b

/-- Unpairing function for the natural numbers. -/
@[pp_nodot] def unpair (n : ℕ) : ℕ × ℕ :=
let s := sqrt n in
if n - s*s < s then (n - s*s, s) else (s, n - s*s - s)

@[simp] theorem mkpair_unpair (n : ℕ) : mkpair (unpair n).1 (unpair n).2 = n :=
begin
  dsimp only [unpair], set s := sqrt n,
  have sm : s * s + (n - s * s) = n := add_tsub_cancel_of_le (sqrt_le _),
  split_ifs,
  { simp [mkpair, h, sm] },
  { have hl : n - s*s - s ≤ s :=
      tsub_le_iff_left.mpr (tsub_le_iff_left.mpr $
      by rw ← add_assoc; apply sqrt_le_add),
    simp [mkpair, hl.not_lt, add_assoc, add_tsub_cancel_of_le (le_of_not_gt h), sm] }
end

theorem mkpair_unpair' {n a b} (H : unpair n = (a, b)) : mkpair a b = n :=
by simpa [H] using mkpair_unpair n

@[simp] theorem unpair_mkpair (a b : ℕ) : unpair (mkpair a b) = (a, b) :=
begin
  dunfold mkpair, split_ifs,
  { show unpair (b * b + a) = (a, b),
    have be : sqrt (b * b + a) = b,
      from sqrt_add_eq _ (le_trans (le_of_lt h) (nat.le_add_left _ _)),
    simp [unpair, be, add_tsub_cancel_right, h] },
  { show unpair (a * a + a + b) = (a, b),
    have ae : sqrt (a * a + (a + b)) = a,
    { rw sqrt_add_eq, exact add_le_add_left (le_of_not_gt h) _ },
    simp [unpair, ae, nat.not_lt_zero, add_assoc] }
end

/-- An equivalence between `ℕ × ℕ` and `ℕ`. -/
@[simps { fully_applied := ff }] def mkpair_equiv : ℕ × ℕ ≃ ℕ :=
⟨uncurry mkpair, unpair, λ ⟨a, b⟩, unpair_mkpair a b, mkpair_unpair⟩

lemma surjective_unpair : surjective unpair :=
mkpair_equiv.symm.surjective

@[simp] lemma mkpair_eq_mkpair {a b c d : ℕ} : mkpair a b = mkpair c d ↔ a = c ∧ b = d :=
mkpair_equiv.injective.eq_iff.trans (@prod.ext_iff ℕ ℕ (a, b) (c, d))

theorem unpair_lt {n : ℕ} (n1 : 1 ≤ n) : (unpair n).1 < n :=
let s := sqrt n in begin
  simp [unpair], change sqrt n with s,
  by_cases h : n - s * s < s; simp [h],
  { exact lt_of_lt_of_le h (sqrt_le_self _) },
  { simp at h,
    have s0 : 0 < s := sqrt_pos.2 n1,
    exact lt_of_le_of_lt h (tsub_lt_self n1 (mul_pos s0 s0)) }
end

@[simp] lemma unpair_zero : unpair 0 = 0 :=
by { rw unpair, simp }

theorem unpair_left_le : ∀ (n : ℕ), (unpair n).1 ≤ n
| 0     := by simp
| (n+1) := le_of_lt (unpair_lt (nat.succ_pos _))

theorem left_le_mkpair (a b : ℕ) : a ≤ mkpair a b :=
by simpa using unpair_left_le (mkpair a b)

theorem right_le_mkpair (a b : ℕ) : b ≤ mkpair a b :=
begin
  by_cases h : a < b; simp [mkpair, h],
  exact le_trans (le_mul_self _) (nat.le_add_right _ _)
end

theorem unpair_right_le (n : ℕ) : (unpair n).2 ≤ n :=
by simpa using right_le_mkpair n.unpair.1 n.unpair.2

theorem mkpair_lt_mkpair_left {a₁ a₂} (b) (h : a₁ < a₂) : mkpair a₁ b < mkpair a₂ b :=
begin
  by_cases h₁ : a₁ < b; simp [mkpair, h₁, add_assoc],
  { by_cases h₂ : a₂ < b; simp [mkpair, h₂, h],
    simp at h₂,
    apply add_lt_add_of_le_of_lt,
    exact mul_self_le_mul_self h₂,
    exact lt_add_right _ _ _ h },
  { simp at h₁,
    simp [not_lt_of_gt (lt_of_le_of_lt h₁ h)],
    apply add_lt_add,
    exact mul_self_lt_mul_self h,
    apply add_lt_add_right; assumption }
end

theorem mkpair_lt_mkpair_right (a) {b₁ b₂} (h : b₁ < b₂) : mkpair a b₁ < mkpair a b₂ :=
begin
  by_cases h₁ : a < b₁; simp [mkpair, h₁, add_assoc],
  { simp [mkpair, lt_trans h₁ h, h],
    exact mul_self_lt_mul_self h },
  { by_cases h₂ : a < b₂; simp [mkpair, h₂, h],
    simp at h₁,
    rw [add_comm, add_comm _ a, add_assoc, add_lt_add_iff_left],
    rwa [add_comm, ← sqrt_lt, sqrt_add_eq],
    exact le_trans h₁ (nat.le_add_left _ _) }
end

end nat
open nat

section complete_lattice

lemma supr_unpair {α} [complete_lattice α] (f : ℕ → ℕ → α) :
  (⨆ n : ℕ, f n.unpair.1 n.unpair.2) = ⨆ i j : ℕ, f i j :=
by rw [← (supr_prod : (⨆ i : ℕ × ℕ, f i.1 i.2) = _), ← nat.surjective_unpair.supr_comp]

lemma infi_unpair {α} [complete_lattice α] (f : ℕ → ℕ → α) :
  (⨅ n : ℕ, f n.unpair.1 n.unpair.2) = ⨅ i j : ℕ, f i j :=
supr_unpair (show ℕ → ℕ → order_dual α, from f)

end complete_lattice

namespace set

lemma Union_unpair_prod {α β} {s : ℕ → set α} {t : ℕ → set β} :
  (⋃ n : ℕ, (s n.unpair.fst).prod (t n.unpair.snd)) = (⋃ n, s n).prod (⋃ n, t n) :=
by { rw [← Union_prod], convert surjective_unpair.Union_comp _, refl }

lemma Union_unpair {α} (f : ℕ → ℕ → set α) :
  (⋃ n : ℕ, f n.unpair.1 n.unpair.2) = ⋃ i j : ℕ, f i j :=
supr_unpair f

lemma Inter_unpair {α} (f : ℕ → ℕ → set α) :
  (⋂ n : ℕ, f n.unpair.1 n.unpair.2) = ⋂ i j : ℕ, f i j :=
infi_unpair f

end set


import tactic.push_neg
import tactic.suggest

example (a : Prop) : a ∨ true :=
begin
  suggest,
  -- exact or.inr trivial,
  -- exact (or_true a).mpr trivial,
  exact or.intro_right a trivial,
end

example (a : Prop) : ¬(a ↔ ¬a) := (iff_not_self a).1

example (a b : Prop) : a ∧ b → a ∨ b :=
begin
  intro h,
    -- 3 lines below doing nothing
    refine or.imp_left _ _,
    exact a,
    exact id,
  sorry,
end

example : 0 < 2 :=
begin
  refine nat.lt.step _,
  exact nat.lt.base 0,
end

example : 0 < 5 :=
begin
  exact (int.coe_nat_lt_coe_nat_iff 0 5).mp trivial, -- why this works?
end

example (n : nat) : n < n + 2 :=
begin
  -- refine n.lt_add_left 2 n _,
  -- ⊢ n < 2 is not provable
  refine nat.lt.step _,
  -- exact nat.lt.base n, OK
  -- exact nat.lt_succ_self n, OK
  -- refine nat.lt.step _, -- ⊢ n < n is not provable
  -- refine nat.lt_succ_of_lt _, -- same result
  -- refine lt_iff_le_not_le.mpr _, -- ???

  -- refine nat.lt_trans _ _,
  -- exact n, -- ⊢ n < n not provable

  have h : n < n + 1, sorry,
  suggest, -- Try this: exact h
  exact nat.lt.base n,
end

example (n) : n*n ≠ 1 → n ≠ 1 := by {
  contrapose,
  -- refine mt _, -- undo contrapose
  rw [not_not, not_not],
  assume h : n = 1,
  rw [h],
  have h₂ := one_mul 1,
  suggest, -- Try this: exact rfl, but expected h₂
  exact h₂,
}

example {a b c d: nat} (h₁ : a < c) (h₂ : b < d) : max (c + d) (a + b) = (c + d) :=
begin
  suggest 50 [add_lt_add], -- Says: `exact max_eq_left_of_lt (add_lt_add h₁ h₂)`
  exact max_eq_left_of_lt (add_lt_add h₁ h₂),
end

namespace example2

constant lt : ℕ → ℕ → Prop
axiom lt.base {n} : lt n n.succ
axiom lt.base_false {n} : ¬ lt n n
axiom lt.step {n m} : lt n m → lt n m.succ

variables {a b c d : ℕ}

example : lt 0 1 := lt.base
example : lt 0 2 := lt.step lt.base
example : lt 0 3 := lt.step (lt.step lt.base)

example : lt 0 3 :=
begin
  suggest 2, -- Try this: refine lt.step _
  refine lt.step _,
  suggest 2, -- Try this: refine lt.step _
  refine lt.step _,
  suggest 1, -- Try this: refine lt.step _
  suggest 2, -- Try this: exact lt.base
  exact lt.base,
end

example : ¬ lt 1 0 :=
begin
  refine not.intro _,
  intro h,
  have h₂ : ¬ lt 1 1, suggest 22, exact lt.base_false,
  -- 22 is minimum number which produce exact word
  have contraposed : ∀ (n m : ℕ), ¬ lt n m.succ → ¬ lt n m,
  {
    intros n m hyp,
    suggest [lt.step, lt.base_false, lt.base],
    -- Try this: exact id (λ (a : lt n m), false.rec false h.step.base_false)
    exact id (λ (a : lt n m), false.rec false h.step.base_false),
  },
  suggest, -- Try this: exact contraposed 1 0 h₂ h
  exact contraposed 1 0 h₂ h,
end

lemma lt.not_one_zero : ¬ lt 1 0 :=
begin
  suggest [lt.step, lt.base_false, lt.base],
  exact mt lt.step lt.base_false,
  -- exact classical.not_imp_not.mpr lt.step lt.base_false
end

lemma lt.not_two_one : ¬ lt 2 1 := mt lt.step lt.base_false

example : ¬ lt 2 0 :=
begin
  suggest 100 [lt.step, lt.base_false, lt.base, lt.not_one_zero, lt.not_two_one],
  -- one of found terms: refine mt _ _
  refine mt lt.step _, -- try to fill first hole manually
  suggest 100 [lt.not_one_zero, lt.step, lt.base_false, lt.base],
  -- exact mt lt.step lt.base_false
  -- exact classical.not_imp_not.mpr lt.step lt.base_false
  exact lt.not_two_one,
end

lemma lt.succ_not_less (n : ℕ) : ¬ lt n.succ n :=
begin
  suggest 100 [lt.step, lt.base_false, lt.base, lt.not_one_zero, lt.not_two_one],
  -- Try this: exact mt lt.step lt.base_false
  -- Try this: exact classical.not_imp_not.mpr lt.step lt.base_false
  exact mt lt.step lt.base_false,
end

example : ¬ lt 5 4 :=
begin
  suggest 23, -- Try this: exact lt.succ_not_less 4
  exact lt.succ_not_less 4,
end

example (a : Prop): false → a :=
begin
  suggest 22,
  exact false.elim,
  -- exact false.rec a
  -- exact false.drec (λ (n : false), a)
  -- exact false.elim
  -- exact false.rec_on a
end

example : ¬ lt 0 0 :=
begin
  -- refine imp_false.mp _,
  refine (false_iff (lt 0 0)).mp _,
  refine eq.to_iff _,
  refine ((λ {α : Type} {x₁ x₂ y₁ y₂ : α} (h₁ : x₁ = y₁) (h₂ : x₂ = y₂), (h₁.congr h₂).mp) _ _ _).symm,
  all_goals { sorry },
end

end example2


import algebra.big_operators.ring
import data.real.basic

/- TEXT:
.. _section_structures:

Structures
----------

In the broadest sense of the term, a *structure* is a specification
of a collection of data, possibly with contraints that the
data is required to satisfy.
An *instance* of the structure is a particular bundle of data satisfying
the constraints. For example, we can specify that a point is
a tuple of three real numbers:
BOTH: -/
-- QUOTE:
@[ext] structure point := (x : ℝ) (y : ℝ) (z : ℝ)
-- QUOTE.

/- TEXT:
The ``@[ext]`` annotation tells Lean to automatically generate theorems
that can be used to prove that two instances of a structure are equal
when their components are equal, a property known as *extensionality*.
EXAMPLES: -/
-- QUOTE:
#check point.ext

example (a b : point) (hx : a.x = b.x) (hy : a.y = b.y) (hz : a.z = b.z) :
  a = b :=
begin
  ext,
  repeat { assumption }
end
-- QUOTE.

/- TEXT:
We can then define particular instances of the ``point`` structure.
Lean provides multiple ways of doing that.
EXAMPLES: -/
-- QUOTE:
def my_point1 : point :=
{ x := 2,
  y := -1,
  z := 4 }

def my_point2 :=
{ point .
  x := 2,
  y := -1,
  z := 4 }

def my_point3 : point := ⟨2, -1, 4⟩

def my_point4 := point.mk 2 (-1) 4
-- QUOTE.

/- TEXT:
In the first two examples, the fields of the structure are named
explicitly.
In the first case, because Lean knows that the expected type of
``my_point1`` is a ``point``, you can start the definition by
writing an underscore, ``_``. Clicking on the light bulb
that appears nearby in VS Code will then
give you the option of inserting a template definition
with the field names listed for you.

The function ``point.mk`` referred to in the definition of ``my_point4``
is known as the *constructor* for the ``point`` structure, because
it serves to construct elements.
You can specify a different name if you want, like ``build``.
EXAMPLES: -/
-- QUOTE:
structure point' := build :: (x : ℝ) (y : ℝ) (z : ℝ)

#check point'.build 2 (-1) 4
-- QUOTE.

/- TEXT:
The next two examples show how to define functions on structures.
Whereas the second example makes the ``point.mk``
constructor explicit, the first example uses anonymous constructors
for brevity.
Lean can infer the relevant constructor from the indicated type of
``add``.
It is conventional to put definitions and theorems associated
with a structure like ``point`` in a namespace with the same name.
In the example below, because we have opened the ``point``
namespace, the full name of ``add`` is ``point.add``.
When the namespace is not open, we have to use the full name.
But remember that it is often convenient to use
anonymous projection notation,
which allows us to write ``a.add b`` instead of ``point.add a b``.
Lean interprets the former as the latter because ``a`` has type ``point``.
BOTH: -/
-- QUOTE:
namespace point

def add (a b : point) : point := ⟨a.x + b.x, a.y + b.y, a.z + b.z⟩

-- EXAMPLES:
def add' (a b : point) : point :=
{ x := a.x + b.x,
  y := a.y + b.y,
  z := a.z + b.z }

#check add my_point1 my_point2
#check my_point1.add my_point2

end point

#check point.add my_point1 my_point2
#check my_point1.add my_point2
-- QUOTE.

/- TEXT:
Below we will continue to put definitions in the relevant
namespace, but we will leave the namespacing commands out of the quoted
snippets. To prove properties of the addition function,
we can use ``rw`` to expand the definition and ``ext`` to
reduce an equation between two elements of the structure to equations
between the components.
Below we use the ``protected`` keyword so that the name of the
theorem is ``point.add_comm``, even when the namespace is open.
This is helpful when we want to avoid ambiguity with a generic
theorem like ``add_comm``.
EXAMPLES: -/
namespace point

-- QUOTE:
protected theorem add_comm (a b : point) : add a b = add b a :=
begin
  rw [add, add],
  ext; dsimp,
  repeat { apply add_comm }
end

example (a b : point) : add a b = add b a :=
by simp [add, add_comm]
-- QUOTE.

/- TEXT:
Because Lean can unfold definitions and simplify projections
internally, sometimes the equations we want hold definitionally.
EXAMPLES: -/
-- QUOTE:
theorem add_x (a b : point) : (a.add b).x = a.x + b.x := rfl
-- QUOTE.

/- TEXT:
It is also possible to define functions on structures using
pattern matching,
in a manner similar to the way we defined recursive functions in
:numref:`section_induction_and_recursion`.
The definitions ``add_alt`` and ``add_alt'`` below are essentially the
same; the only difference is that we use anonymous constructor notation
in the second.
Although it is sometimes convenient to define functions this way,
the definitional properties are not as convenient.
For example, the expressions ``add_alt a b`` and ``add_alt' a b``
cannot be simplified until we decompose ``a`` and ``b`` into
components, which we can do with ``cases``, ``rcases``, etc.

EXAMPLES: -/
-- QUOTE:
def add_alt : point → point → point
| (point.mk x₁ y₁ z₁) (point.mk x₂ y₂ z₂) := ⟨x₁ + x₂, y₁ + y₂, z₁ + z₂⟩

def add_alt' : point → point → point
| ⟨x₁, y₁, z₁⟩ ⟨x₂, y₂, z₂⟩ := ⟨x₁ + x₂, y₁ + y₂, z₁ + z₂⟩

theorem add_alt_x (a b : point) : (a.add_alt b).x = a.x + b.x :=
by { cases a, cases b, refl }

theorem add_alt_comm (a b : point) : add_alt a b = add_alt b a :=
begin
  rcases a with ⟨xa, ya, za⟩,
  rcases b with ⟨xb, yb, zb⟩,
  rw [add_alt, add_alt],
  ext; dsimp,
  apply add_comm,
  repeat { apply add_comm },
end

example (a b : point) : add_alt a b = add_alt b a :=
begin
  rcases a with ⟨xa, ya, za⟩,
  rcases b with ⟨xb, yb, zb⟩,
  simp [add_alt, add_comm]
end

example : ∀ a b : point, add_alt a b = add_alt b a :=
begin
  rintros ⟨xa, ya, za⟩ ⟨xb, yb, zb⟩,
  simp [add_alt, add_comm]
end

example : ∀ a b : point, add a b = add b a :=
λ ⟨xa, ya, za⟩ ⟨xb, yb, zb⟩, by simp [add, add_comm]
-- QUOTE.

/- TEXT:
Mathematical constructions often involve taking apart bundled information and
putting it together again in different ways.
It therefore makes sense that Lean and mathlib offer so many ways
of doing this efficiently.
As an exercise, try proving that ``point.add`` is associative.
Then define scalar multiplication for a point and show that it
distributes over addition.
BOTH: -/
-- QUOTE:
protected theorem add_assoc (a b c : point) :
  (a.add b).add c = a.add (b.add c) :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
by { simp [add, add_assoc] }
-- BOTH:

def smul (r : ℝ) (a : point) : point :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
⟨r * a.x, r * a.y, r * a.z⟩
-- BOTH:

theorem smul_distrib (r : ℝ) (a b : point) :
  (smul r a).add (smul r b) = smul r (a.add b) :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
by { simp [add, smul, mul_add] }
-- BOTH:
-- QUOTE.

end point

/- TEXT:
Using structures is only the first step on the road to
algebraic abstraction.
We don't yet have a way to link ``point.add`` to the generic ``+`` symbol,
or to connect ``point.add_comm`` and ``point.add_assoc`` to
the generic ``add_comm`` and ``add_assoc`` theorems.
These tasks belong to the *algebraic* aspect of using structures,
and we will explain how to do carry them out in the next section.
For now, just think of a structure as a way of bundling together objects
and information.

It is especially useful that a structure can specify not only
data types but also constraints that the data must satisfy.
In Lean, the latter are represented as fields of type ``Prop``.
For example, the *standard 2-simplex* is defined to be the set of
points :math:`(x, y, z)` satisfying :math:`x ≥ 0`, :math:`y ≥ 0`, :math:`z ≥ 0`,
and :math:`x + y + z = 1`.
If you are not familiar with the notion, you should draw a picture,
and convince yourself that this set is
the equilateral triangle in three-space with vertices
:math:`(1, 0, 0)`, :math:`(0, 1, 0)`, and :math:`(0, 0, 1)`,
together with its interior.
We can represent it in Lean as follows:
BOTH: -/
-- QUOTE:
structure standard_two_simplex :=
(x : ℝ)
(y : ℝ)
(z : ℝ)
(x_nonneg : 0 ≤ x)
(y_nonneg : 0 ≤ y)
(z_nonneg : 0 ≤ z)
(sum_eq   : x + y + z = 1)
-- QUOTE.

/- TEXT:
Notice that the last four fields refer to ``x``, ``y``, and ``z``,
that is, the first three fields.
We can define a map from the two-simplex to itself that swaps ``x`` and ``y``:
BOTH: -/
namespace standard_two_simplex

-- EXAMPLES:
-- QUOTE:
def swap_xy (a : standard_two_simplex) : standard_two_simplex :=
{ x := a.y,
  y := a.x,
  z := a.z,
  x_nonneg := a.y_nonneg,
  y_nonneg := a.x_nonneg,
  z_nonneg := a.z_nonneg,
  sum_eq   := by rw [add_comm a.y a.x, a.sum_eq] }
-- QUOTE.

-- OMIT: (TODO) add a link when we have a good explanation of noncomputable theory.
/- TEXT:
More interestingly, we can compute the midpoint of two points on
the simplex. We need to add ``noncomputable theory`` in order to
use division on the real numbers.
BOTH: -/
-- QUOTE:
noncomputable theory

-- EXAMPLES:
def midpoint (a b : standard_two_simplex) : standard_two_simplex :=
{ x        := (a.x + b.x) / 2,
  y        := (a.y + b.y) / 2,
  z        := (a.z + b.z) / 2,
  x_nonneg := div_nonneg (add_nonneg a.x_nonneg b.x_nonneg) (by norm_num),
  y_nonneg := div_nonneg (add_nonneg a.y_nonneg b.y_nonneg) (by norm_num),
  z_nonneg := div_nonneg (add_nonneg a.z_nonneg b.z_nonneg) (by norm_num),
  sum_eq   := by { field_simp, linarith [a.sum_eq, b.sum_eq]} }
-- QUOTE.

/- TEXT:
Here we have established ``x_nonneg``, ``y_nonneg``, and ``z_nonneg``
with concise proof terms, but establish ``sum_eq`` in tactic mode,
using ``by``. You can just as well use a ``begin ... end`` block
for that purpose.

Given a parameter :math:`\lambda` satisfying :math:`0 \le \lambda \le 1`,
we can take the weighted average :math:`\lambda a + (1 - \lambda) b`
of two points :math:`a` and :math:`b` in the standard 2-simplex.
We challenge you to define that function, in analogy to the ``midpoint``
function above.
BOTH: -/
-- QUOTE:
def weighted_average (lambda : real)
    (lambda_nonneg : 0 ≤ lambda) (lambda_le : lambda ≤ 1)
    (a b : standard_two_simplex) :
  standard_two_simplex :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
{ x        := lambda * a.x + (1 - lambda) * b.x,
  y        := lambda * a.y + (1 - lambda) * b.y,
  z        := lambda * a.z + (1 - lambda) * b.z,
  x_nonneg := add_nonneg (mul_nonneg lambda_nonneg a.x_nonneg)
                (mul_nonneg (by linarith) b.x_nonneg),
  y_nonneg := add_nonneg (mul_nonneg lambda_nonneg a.y_nonneg)
                (mul_nonneg (by linarith) b.y_nonneg),
  z_nonneg := add_nonneg (mul_nonneg lambda_nonneg a.z_nonneg)
                (mul_nonneg (by linarith) b.z_nonneg),
  sum_eq   :=
    begin
      transitivity (a.x + a.y + a.z) * lambda + (b.x + b.y + b.z) * (1 - lambda),
      { ring },
      simp [a.sum_eq, b.sum_eq]
    end }
-- QUOTE.
-- BOTH:

end standard_two_simplex

/- TEXT:
Structures can depend on parameters.
For example, we can generalize the standard 2-simplex to the standard
:math:`n`-simplex for any :math:`n`.
At this stage, you don't have to know anything about the type `fin n`
except that it has :math:`n` elements, and that Lean knows
how to sum over it.
BOTH: -/
-- QUOTE:
open_locale big_operators

structure standard_simplex (n : ℕ) :=
(v          : fin n → ℝ)
(nonneg     : ∀ i : fin n, 0 ≤ v i)
(sum_eq_one : ∑ i, v i = 1)

namespace standard_simplex

def midpoint (n : ℕ) (a b : standard_simplex n) : standard_simplex n :=
{ v := λ i, (a.v i + b.v i) / 2,
  nonneg :=
    begin
      intro i,
      apply div_nonneg,
      { linarith [a.nonneg i, b.nonneg i] },
      norm_num
    end,
  sum_eq_one :=
    begin
      simp [div_eq_mul_inv, ←finset.sum_mul, finset.sum_add_distrib,
        a.sum_eq_one, b.sum_eq_one],
      field_simp
    end  }

end standard_simplex
-- QUOTE.

/- TEXT:
As an exercise, see if you can define the weighted average of
two points in the standard :math:`n`-simplex.
You can use ``finset.sum_add_distrib``
and ``finset.mul_sum`` to manipulate the relevant sums.

SOLUTIONS: -/
namespace standard_simplex

def weighted_average {n : ℕ} (lambda : real)
    (lambda_nonneg : 0 ≤ lambda) (lambda_le : lambda ≤ 1)
    (a b : standard_simplex n) : standard_simplex n :=
{ v          := λ i, lambda * a.v i + (1 - lambda) * b.v i,
  nonneg     := λ i, add_nonneg (mul_nonneg lambda_nonneg (a.nonneg i))
                  (mul_nonneg (by linarith) (b.nonneg i)),
  sum_eq_one :=
    begin
      transitivity lambda * (∑ i, a.v i) + (1 - lambda) * (∑ i, b.v i),
      { rw [finset.sum_add_distrib, finset.mul_sum, finset.mul_sum] },
      simp [a.sum_eq_one, b.sum_eq_one]
    end }

end standard_simplex

/- TEXT:
We have seen that structures can be used to bundle together data
and properties.
Interestingly, they can also be used to bundle together properties
without the data.
For example, the next structure, ``is_linear``, bundles together
the two components of linearity.
EXAMPLES: -/
-- QUOTE:
structure is_linear (f : ℝ → ℝ) :=
(is_additive : ∀ x y, f (x + y) = f x + f y)
(preserves_mul : ∀ x c, f (c * x) = c * f x)

section
variables (f : ℝ → ℝ) (linf : is_linear f)

#check linf.is_additive
#check linf.preserves_mul

end
-- QUOTE.

/- TEXT:
It is worth pointing out that structures are not the only way to bundle
together data.
The ``point`` data structure can be defined using the generic type product,
and ``is_linear`` can be defined with a simple ``and``.
EXAMPLES: -/
-- QUOTE:
def point'' := ℝ × ℝ × ℝ

def is_linear' (f : ℝ → ℝ) :=
(∀ x y, f (x + y) = f x + f y) ∧ (∀ x c, f (c * x) = c * f x)
-- QUOTE.

/- TEXT:
Generic type constructions can even be used in place of structures
with dependencies between their components.
For example, the *subtype* construction combines a piece of data with
a property.
You can think of the type ``preal`` in the next example as being
the type of positive real numbers.
Any ``x : preal`` has two components: the value, and the property of being
positive.
You can access these components as ``x.val``, which has type ``ℝ``,
and ``x.property``, which represents the fact ``0 < x.val``.
EXAMPLES: -/
-- QUOTE:
def preal := { y : ℝ // 0 < y }

section
variable x : preal

#check x.val
#check x.property

#check x.1
#check x.2

end
-- QUOTE.

/- TEXT:
We could have used subtypes to define the standard 2-simplex,
as well as the standard :math:`n`-simplex for an arbitrary :math:`n`.
EXAMPLES: -/
-- QUOTE:
def standard_two_simplex' :=
{ p : ℝ × ℝ × ℝ // 0 ≤ p.1 ∧ 0 ≤ p.2.1 ∧ 0 ≤ p.2.2 ∧ p.1 + p.2.1 + p.2.2 = 1 }

def standard_simplex' (n : ℕ) :=
{ v : fin n → ℝ // (∀ i : fin n, 0 ≤ v i) ∧ (∑ i, v i = 1) }
-- QUOTE.

/- TEXT:
Similarly, *Sigma types* are generalizations of ordered pairs,
whereby the type of the second component depends on the type of
the first.
EXAMPLES: -/
-- QUOTE:
def std_simplex := Σ n : ℕ, standard_simplex n

section
variable s : std_simplex

#check s.fst
#check s.snd

#check s.1
#check s.2

end
-- QUOTE.

/- TEXT:
Given ``s : std_simplex``, the first component ``s.fst`` is a natural
number, and the second component is an element of the corresponding
simplex ``standard_simplex s.fst``.
The difference between a Sigma type and a subtype is that
the second component of a Sigma type is data rather than a proposition.

But even though we can use products, subtypes, and Sigma types
instead of structures, using structures has a number of advantages.
Defining a structure abstracts away the underlying representation
and provides custom names for the functions that access the components.
This makes proofs more robust:
proofs that rely only on the interface to a structure
will generally continue to work when we change the definition,
as long as we redefine the old accessors in terms of the new definition.
Moreover, as we are about to see, Lean provides support for
weaving structures together into a rich, interconnected hierarchy,
and for managing the interactions between them.
TEXT. -/

/- OMIT: (TODO)
Comments from Patrick:
We could make this paragraph much less abstract by showing how to access the components of a point with the definition def point'' := ℝ × ℝ × ℝ. However if we do that it would probably be honest to also mention the possibility of using fin 3 → ℝ as the definition. This interesting anyhow, because I think very few mathematician realize that defining ℝ^n as an iterated cartesian product is a polite lie that would be a nightmare if taken seriously.

By the way, should be include some comment about similarities and differences with object-oriented programming? All the examples from that page would clearly fit very well with classes in python say. And we'll have to face the name-clash between classes in Lean and classes in C++ or python sooner or later. Life would be so much simpler if classes in Lean could use another name...
OMIT. -/


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl
-/
import order.lattice
import data.set.basic

/-!
# Directed indexed families and sets

This file defines directed indexed families and directed sets. An indexed family/set is
directed iff each pair of elements has a shared upper bound.

## Main declarations

* `directed r f`: Predicate stating that the indexed family `f` is `r`-directed.
* `directed_on r s`: Predicate stating that the set `s` is `r`-directed.
* `directed_order α`: Typeclass extending `preorder` for stating that `α` is `≤`-directed.
-/

universes u v w

variables {α : Type u} {β : Type v} {ι : Sort w} (r : α → α → Prop)
local infix ` ≼ ` : 50 := r

/-- A family of elements of α is directed (with respect to a relation `≼` on α)
  if there is a member of the family `≼`-above any pair in the family.  -/
def directed (f : ι → α) := ∀ x y, ∃ z, f x ≼ f z ∧ f y ≼ f z

/-- A subset of α is directed if there is an element of the set `≼`-above any
  pair of elements in the set. -/
def directed_on (s : set α) := ∀ (x ∈ s) (y ∈ s), ∃ z ∈ s, x ≼ z ∧ y ≼ z

variables {r}

theorem directed_on_iff_directed {s} : @directed_on α r s ↔ directed r (coe : s → α) :=
by simp [directed, directed_on]; refine ball_congr (λ x hx, by simp; refl)

alias directed_on_iff_directed ↔ directed_on.directed_coe _

theorem directed_on_image {s} {f : β → α} :
  directed_on r (f '' s) ↔ directed_on (f ⁻¹'o r) s :=
by simp only [directed_on, set.ball_image_iff, set.bex_image_iff, order.preimage]

theorem directed_on.mono {s : set α} (h : directed_on r s)
  {r' : α → α → Prop} (H : ∀ {a b}, r a b → r' a b) :
  directed_on r' s :=
λ x hx y hy, let ⟨z, zs, xz, yz⟩ := h x hx y hy in ⟨z, zs, H xz, H yz⟩

theorem directed_comp {ι} {f : ι → β} {g : β → α} :
  directed r (g ∘ f) ↔ directed (g ⁻¹'o r) f := iff.rfl

theorem directed.mono {s : α → α → Prop} {ι} {f : ι → α}
  (H : ∀ a b, r a b → s a b) (h : directed r f) : directed s f :=
λ a b, let ⟨c, h₁, h₂⟩ := h a b in ⟨c, H _ _ h₁, H _ _ h₂⟩

theorem directed.mono_comp {ι} {rb : β → β → Prop} {g : α → β} {f : ι → α}
  (hg : ∀ ⦃x y⦄, x ≼ y → rb (g x) (g y)) (hf : directed r f) :
  directed rb (g ∘ f) :=
directed_comp.2 $ hf.mono hg

/-- A monotone function on a sup-semilattice is directed. -/
lemma directed_of_sup [semilattice_sup α] {f : α → β} {r : β → β → Prop}
  (H : ∀ ⦃i j⦄, i ≤ j → r (f i) (f j)) : directed r f :=
λ a b, ⟨a ⊔ b, H le_sup_left, H le_sup_right⟩

lemma monotone.directed_le [semilattice_sup α] [preorder β] {f : α → β} :
  monotone f → directed (≤) f :=
directed_of_sup

/-- An antitone function on an inf-semilattice is directed. -/
lemma directed_of_inf [semilattice_inf α] {r : β → β → Prop} {f : α → β}
  (hf : ∀ a₁ a₂, a₁ ≤ a₂ → r (f a₂) (f a₁)) : directed r f :=
λ x y, ⟨x ⊓ y, hf _ _ inf_le_left, hf _ _ inf_le_right⟩

/-- A `preorder` is a `directed_order` if for any two elements `i`, `j`
there is an element `k` such that `i ≤ k` and `j ≤ k`. -/
class directed_order (α : Type u) extends preorder α :=
(directed : ∀ i j : α, ∃ k, i ≤ k ∧ j ≤ k)

@[priority 100]  -- see Note [lower instance priority]
instance linear_order.to_directed_order (α) [linear_order α] : directed_order α :=
⟨λ i j, or.cases_on (le_total i j) (λ hij, ⟨j, hij, le_refl j⟩) (λ hji, ⟨i, le_refl i, hji⟩)⟩


import data.set.basic data.set.lattice data.nat.parity
import tactic.linarith

import data.real.basic

open set nat function

open_locale classical

variables {α : Type*} {β : Type*} {γ : Type*} {I : Type*}

/-!
## Set exercises

These are collected from *Mathematics in Lean*.

We will go over the examples together, and then let you
work on the exercises.

There is more material here than can fit in the sessions,
but we will pick and choose as we go.
-/

section set_variables

variable  x : α
variables s t u : set α

/-!
### Notation
-/

#check s ⊆ t        -- \sub
#check x ∈ s        -- \in or \mem
#check x ∉ s        -- \notin
#check s ∩ t        -- \i or \cap
#check s ∪ t        -- \un or \cup
#check (∅ : set α)  -- \empty

/-!
### Examples
-/

-- Three proofs of the same fact.
-- The first two expand definitions explicitly,
-- while the third forces Lean to do the unfolding.

example (h : s ⊆ t) : s ∩ u ⊆ t ∩ u :=
begin
  rw [subset_def, inter_def, inter_def],
  rw [subset_def] at h,
  finish,
end

example (h : s ⊆ t) : s ∩ u ⊆ t ∩ u :=
begin
  simp only [subset_def, mem_inter_iff] at *,
  rintros x ⟨xs, xu⟩,
  exact ⟨h _ xs, xu⟩,
end

example (h : s ⊆ t) : s ∩ u ⊆ t ∩ u :=
begin
  intros x xsu,
  exact ⟨h xsu.1, xsu.2⟩
end

-- Use `cases` or `rcases` or `rintros` with union.
-- Two proofs of the same fact, one longer and one shorter.

example : s ∩ (t ∪ u) ⊆ (s ∩ t) ∪ (s ∩ u) :=
begin
  intros x hx,
  have xs : x ∈ s := hx.1,
  have xtu : x ∈ t ∪ u := hx.2,
  cases xtu with xt xu,
  { left,
    show x ∈ s ∩ t,
    exact ⟨xs, xt⟩ },
  right,
  show x ∈ s ∩ u,
  exact ⟨xs, xu⟩
end

example : s ∩ (t ∪ u) ⊆ (s ∩ t) ∪ (s ∩ u) :=
begin
  rintros x ⟨xs, xt | xu⟩,
  { left, exact ⟨xs, xt⟩ },
  right, exact ⟨xs, xu⟩
end

-- Two examples with set difference.
-- Type it as ``\\``.
-- ``x ∈ s \ t`` expands to ``x ∈ s ∧ x ∉ t``.

example : s \ t \ u ⊆ s \ (t ∪ u) :=
begin
  intros x xstu,
  have xs : x ∈ s := xstu.1.1,
  have xnt : x ∉ t := xstu.1.2,
  have xnu : x ∉ u := xstu.2,
  split,
  { exact xs }, dsimp,
  intro xtu, -- x ∈ t ∨ x ∈ u
  cases xtu with xt xu,
  { show false, from xnt xt },
  show false, from xnu xu
end

example : s \ t \ u ⊆ s \ (t ∪ u) :=
begin
  rintros x ⟨⟨xs, xnt⟩, xnu⟩,
  use xs,
  rintros (xt | xu); contradiction
end

/-!
### Exercises
-/

example : (s ∩ t) ∪ (s ∩ u) ⊆ s ∩ (t ∪ u):=
sorry

example : s \ (t ∪ u) ⊆ s \ t \ u :=
sorry

/-!
### Proving two sets are equal
-/

-- the ext tactic

example : s ∩ t = t ∩ s :=
begin
  ext x,
  -- simp only [mem_inter_eq],  -- optional.
  split,
  { rintros ⟨xs, xt⟩, exact ⟨xt, xs⟩ },
  rintros ⟨xt, xs⟩, exact ⟨xs, xt⟩
end

example : s ∩ t = t ∩ s :=
by ext x; simp [and.comm]

/-!
### Exercises
-/

example : s ∩ (s ∪ t) = s :=
sorry

example : s ∪ (s ∩ t) = s :=
sorry

example : (s \ t) ∪ t = s ∪ t :=
sorry

example : (s \ t) ∪ (t \ s) = (s ∪ t) \ (s ∩ t) :=
sorry

/-!
### Set-builder notation
-/

def evens : set ℕ := {n | even n}
def odds :  set ℕ := {n | ¬ even n}

example : evens ∪ odds = univ :=
begin
  rw [evens, odds],
  ext n,
  simp,
  apply classical.em
end

example : s ∩ t = {x | x ∈ s ∧ x ∈ t} := rfl
example : s ∪ t = {x | x ∈ s ∨ x ∈ t} := rfl
example : (∅ : set α) = {x | false} := rfl
example : (univ : set α) = {x | true} := rfl

example (x : ℕ) (h : x ∈ (∅ : set ℕ)) : false :=
h

example (x : ℕ) : x ∈ (univ : set ℕ) :=
trivial

/-!
### Exercise
-/

-- Use `intro n` to unfold the definition of subset,
-- and use the simplifier to reduce the
-- set-theoretic constructions to logic.
-- We also recommend using the theorems
-- ``prime.eq_two_or_odd`` and ``even_iff``.

example : { n | nat.prime n } ∩ { n | n > 2} ⊆ { n | ¬ even n } :=
sorry

/-!
Indexed unions
-/

-- See *Mathematics in Lean* for a discussion of
-- bounded quantifiers, which we will skip here.

section

-- We can use any index type in place of ℕ
variables A B : ℕ → set α

example : s ∩ (⋃ i, A i) = ⋃ i, (A i ∩ s) :=
begin
  ext x,
  simp only [mem_inter_iff, mem_Union],
  split,
  { rintros ⟨xs, ⟨i, xAi⟩⟩,
    exact ⟨i, xAi, xs⟩ },
  rintros ⟨i, xAi, xs⟩,
  exact ⟨xs, ⟨i, xAi⟩⟩
end

example : (⋂ i, A i ∩ B i) = (⋂ i, A i) ∩ (⋂ i, B i) :=
begin
  ext x,
  simp only [mem_inter_iff, mem_Inter],
  split,
  { intro h,
    split,
    { intro i,
      exact (h i).1 },
    intro i,
    exact (h i).2 },
  rintros ⟨h1, h2⟩ i,
  split,
  { exact h1 i },
  exact h2 i
end

end

/-!
### Exercise
-/

-- One direction requires classical logic!
-- We recommend using ``by_cases xs : x ∈ s``
-- at an appropriate point in the proof.

section

variables A B : ℕ → set α

example : s ∪ (⋂ i, A i) = ⋂ i, (A i ∪ s) :=
sorry

end

/-
Mathlib also has bounded unions and intersections,
`⋃ x ∈ s, f x` and `⋂ x ∈ s, f x`,
and set unions and intersections, `⋃₀ s` and `⋂₀ s`,
where `s : set α`.

See *Mathematics in Lean* for details.
-/

end set_variables

/-!
### Functions
-/

section function_variables

variable  f : α → β
variables s t : set α
variables u v : set β
variable  A : I → set α
variable  B : I → set β

#check f '' s
#check image f s
#check f ⁻¹' u    -- type as \inv' and then hit space or tab
#check preimage f u

example : f '' s = {y | ∃ x, x ∈ s ∧ f x = y} := rfl
example : f ⁻¹' u = {x | f x ∈ u } := rfl

example : f ⁻¹' (u ∩ v) = f ⁻¹' u ∩ f ⁻¹' v :=
by { ext, refl }

example : f '' (s ∪ t) = f '' s ∪ f '' t :=
begin
  ext y, split,
  { rintros ⟨x, xs | xt, rfl⟩,
    { left, use [x, xs] },
    right, use [x, xt] },
  rintros (⟨x, xs, rfl⟩ | ⟨x, xt, rfl⟩),
  { use [x, or.inl xs] },
  use [x, or.inr xt]
end

example : s ⊆ f ⁻¹' (f '' s) :=
begin
  intros x xs,
  show f x ∈ f '' s,
  use [x, xs]
end

/-!
### Exercises
-/

example : f '' s ⊆ u ↔ s ⊆ f ⁻¹' u :=
sorry

example (h : injective f) : f ⁻¹' (f '' s) ⊆ s :=
sorry

example : f '' (f⁻¹' u) ⊆ u :=
sorry

example (h : surjective f) : u ⊆ f '' (f⁻¹' u) :=
sorry

example (h : s ⊆ t) : f '' s ⊆ f '' t :=
sorry

example (h : u ⊆ v) : f ⁻¹' u ⊆ f ⁻¹' v :=
sorry

example : f ⁻¹' (u ∪ v) = f ⁻¹' u ∪ f ⁻¹' v :=
sorry

example : f '' (s ∩ t) ⊆ f '' s ∩ f '' t :=
sorry

example (h : injective f) : f '' s ∩ f '' t ⊆ f '' (s ∩ t) :=
sorry

example : f '' s \ f '' t ⊆ f '' (s \ t) :=
sorry

example : f ⁻¹' u \ f ⁻¹' v ⊆ f ⁻¹' (u \ v) :=
sorry

example : f '' s ∩ v = f '' (s ∩ f ⁻¹' v) :=
sorry

example : f '' (s ∩ f ⁻¹' u) ⊆ f '' s ∪ u :=
sorry

example : s ∩ f ⁻¹' u ⊆ f ⁻¹' (f '' s ∩ u) :=
sorry

example : s ∪ f ⁻¹' u ⊆ f ⁻¹' (f '' s ∪ u) :=
sorry

example : f '' (⋃ i, A i) = ⋃ i, f '' A i :=
sorry

example : f '' (⋂ i, A i) ⊆ ⋂ i, f '' A i :=
sorry

example (i : I) (injf : injective f) : (⋂ i, f '' A i) ⊆ f '' (⋂ i, A i) :=
sorry

example : f ⁻¹' (⋃ i, B i) = ⋃ i, f ⁻¹' (B i) :=
sorry

example : f ⁻¹' (⋂ i, B i) = ⋂ i, f ⁻¹' (B i) :=
sorry

/-
There is a lot more in *Mathematics in Lean* that we will not have time for!
There is a discussion of injectivity, more exercises on images and ranges,
and a discussion of inverses.

But we will close with on last exercise. Remember that `surjective f`
says `∀ y, ∃ x, f x = y`.

See if you can understand the proof of Cantor's famous theorem that there is no
surjective function from its set to its powerset, and
fill in the two lines that are missing.
-/

theorem Cantor : ∀ f : α → set α, ¬ surjective f :=
begin
  intros f surjf,
  let S := { i | i ∉ f i},
  rcases surjf S with ⟨j, h⟩,
  have h₁ : j ∉ f j,
  { intro h',
    have : j ∉ f j,
      { by rwa h at h' },
    contradiction },
  have h₂ : j ∈ S,
sorry
    ,
  have h₃ : j ∉ S,
sorry
    ,
  contradiction
end

end function_variables


-- La relación menor es irreflexiva en los reales
-- ==============================================

import data.real.basic
import tactic

variable {x : ℝ}

-- ----------------------------------------------------
-- Ejercicio. Demostrar que la relación menor es
-- irreflexiva en los reales.
-- ----------------------------------------------------

-- 1ª demostración
example : ¬ x < x :=
begin
  intro h1,
  rw lt_iff_le_and_ne at h1,
  cases h1 with h2 h3,
  -- clear h2,
  -- change x = x → false at h3,
  apply h3,
  refl,
end

-- 2ª demostración
example : ¬ x < x :=
begin
  intro h1,
  rw lt_iff_le_and_ne at h1,
  cases h1 with h2 h3,
  apply h3,
  refl,
end

-- 3ª demostración
example : ¬ x < x :=
begin
  intro h1,
  cases (lt_iff_le_and_ne.mp h1) with h2 h3,
  apply h3,
  refl,
end

-- 4ª demostración
example : ¬ x < x :=
begin
  intro h1,
  apply (lt_iff_le_and_ne.mp h1).2,
  refl,
end

-- 5ª demostración
example : ¬ x < x :=
begin
  intro h1,
  exact absurd rfl (lt_iff_le_and_ne.mp h1).2,
end

-- 6ª demostración
example : ¬ x < x :=
λ h, absurd rfl (lt_iff_le_and_ne.mp h).2

-- 7ª demostración
example : ¬ x < x :=
assume h1 : x < x,
have h2 : x ≤ x ∧ x ≠ x,
  from lt_iff_le_and_ne.mp h1,
have h3 : x ≠ x,
  from and.right h2,
have h4 : x = x,
  from rfl,
show false,
  from absurd h4 h3

-- 8ª demostración
example : ¬ x < x :=
assume h1 : x < x,
have h2 : x ≤ x ∧ x ≠ x,
  from lt_iff_le_and_ne.mp h1,
absurd rfl (and.right h2)

-- 9ª demostración
example : ¬ x < x :=
assume h1 : x < x,
absurd rfl (and.right (lt_iff_le_and_ne.mp h1))

-- 10ª demostración
example : ¬ x < x :=
assume h1 : x < x,
absurd rfl (lt_iff_le_and_ne.mp h1).2

-- 11ª demostración
example : ¬ x < x :=
λ h, absurd rfl (lt_iff_le_and_ne.mp h).2

-- 12ª demostración
example : ¬ x < x :=
-- by library_search
irrefl x

-- 12ª demostración
example : ¬ x < x :=
-- by hint
by simp

-- 13ª demostración
example : ¬ x < x :=
by finish

-- 14ª demostración
example : ¬ x < x :=
by norm_num

-- 15ª demostración
example : ¬ x < x :=
by linarith

-- 16ª demostración
example : ¬ x < x :=
by nlinarith


-- Suma_de_cubos_de_impares.lean
-- Suma de cubos de impares
-- José A. Alonso Jiménez
-- Sevilla, 26 de septiembre de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que
--    1³ + 3³ + 5³ + ... + (2n-1)³ = n²(2n²-1)
-- ---------------------------------------------------------------------

import algebra.big_operators
import tactic
open finset nat

open_locale big_operators
set_option pp.structure_projections false

variable (n : ℕ)

example :
  ∑ k in Ico 1 (n+1), (2*k-1)^3 = n^2*(2*n^2-1) :=
begin
  induction n with n HI,
  { simp, },
  { calc ∑ k in Ico 1 (succ n + 1), (2*k - 1)^3
         = (∑ k in Ico 1 (succ n), (2*k - 1)^3) + (2 * succ n - 1)^3
             : sum_Ico_succ_top (succ_pos n) (λ k, (2*k - 1)^3)
     ... = n^2 * (2*n^2 - 1) + (2 * succ n - 1)^3
             : by {congr; rw HI}
     ... = n^2 * (2*n^2 - 1) + (2*n + 1)^3
             : by congr
     ... = n^2 * (2*n^2 - 1) + (1 + 6*n + 12*n^2 + 8*n^3)
             : by nlinarith
     ... = (2*n^4 - n^2) + (1 + 6*n + 12*n^2 + 8*n^3)
             : by {congr' 1; zify; sorry}
     ... = 2*n^4 + 8*n^3 + 11*n^2 + 6*n + 1
             : sorry
     ... = succ n ^ 2 * (2 * succ n ^ 2 - 1)
             : sorry },
end

example : n * (n - 1) = n^2 - n := sorry
example : n^2 * (2*n^2 - 1) = n^2*2*n^2 - n^2*1 := sorry

example : (n + 1) ^ 2 = n^2 + 2*n + 1 := by nlinarith

example : (2 * n + 1) ^ 3 = 1 + 6*n + 12*n^2 + 8*n^3 := by nlinarith

-- Referencia: "Sum of sequence of odd cubes" https://bit.ly/3EPWocw en
-- ProofWiki.


/-
Copyright (c) 2019 Neil Strickland. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Neil Strickland

Some basic lemmas about integers.  I should check whether the
new coercion tactic makes them trivial.

-/

import data.fintype.basic data.nat.sqrt

lemma int.lt_succ_iff {n m : ℤ} : n < m + 1 ↔ n ≤ m :=
 ⟨int.le_of_lt_add_one,int.lt_add_one_of_le⟩

lemma nat.square_le {n m : ℕ} : m ^ 2 ≤ n ↔ m ≤ n.sqrt :=
 ⟨λ h0, le_of_not_gt (λ h1, not_le_of_gt ((pow_two m).symm.subst (nat.sqrt_lt.mp h1)) h0),
  λ h0, le_of_not_gt (λ h1,not_le_of_gt (nat.sqrt_lt.mpr ((pow_two m).subst h1)) h0)⟩

lemma nat.square_lt {n m : ℕ} : m ^ 2 < n.succ ↔ m ≤ n.sqrt :=
 (@nat.lt_succ_iff (m ^ 2) n).trans (@nat.square_le n m)

lemma int.abs_le {n m : ℤ} : abs m ≤ n ↔ (- n ≤ m ∧ m ≤ n) :=
begin
 by_cases hm : 0 ≤ m,
 { rw[abs_of_nonneg hm],
   exact ⟨
     λ hmn, ⟨le_trans (neg_nonpos_of_nonneg (le_trans hm hmn)) hm,hmn⟩,
     λ hmn, hmn.right
      ⟩
 },{
   let hma := le_of_lt (lt_of_not_ge hm),
   let hmb := neg_nonneg_of_nonpos hma,
   rw[abs_of_nonpos hma],
   exact ⟨
     λ hmn, ⟨(neg_neg m) ▸ (neg_le_neg hmn),le_trans (le_trans hma hmb) hmn⟩,
     λ hmn, (neg_neg n) ▸ (neg_le_neg hmn.left),
    ⟩
 }
end

lemma int.abs_le' {n : ℕ} {m : ℤ} : m.nat_abs ≤ n ↔ (- (n : ℤ) ≤ m ∧ m ≤ n) :=
begin
 let h := @int.abs_le n m,
 rw[int.abs_eq_nat_abs,int.coe_nat_le] at h,
 exact h,
end

lemma int.abs_square (n : ℤ) : n ^ 2 = (abs n) ^ 2 := begin
 by_cases h0 : n ≥ 0,
 {rw[abs_of_nonneg h0]},
 {rw[abs_of_neg (lt_of_not_ge h0),pow_two,pow_two,neg_mul_neg],}
end

lemma int.abs_square' (n : ℤ) : n ^ 2 = ((int.nat_abs n) ^ 2 : ℕ) :=
 calc
   n ^ 2 = n * n : pow_two n
   ... = ↑ (n.nat_abs * n.nat_abs) : int.nat_abs_mul_self.symm
   ... = ↑ (n.nat_abs ^ 2 : ℕ) : by rw[(pow_two n.nat_abs).symm]

lemma int.square_le {n : ℕ} {m : ℤ} :
 m ^ 2 ≤ n ↔ - (n.sqrt : ℤ) ≤ m ∧ m ≤ n.sqrt :=
begin
 rw[int.abs_square',int.coe_nat_le,nat.square_le,int.abs_le'],
end

lemma int.square_lt {n : ℕ} {m : ℤ} :
 m ^ 2 < n.succ ↔ - (n.sqrt : ℤ) ≤ m ∧ m ≤ n.sqrt :=
begin
 rw[int.abs_square',int.coe_nat_lt,nat.square_lt,int.abs_le'],
end


import .list ...mathlib.data.nat.gcd

namespace nat

def divides (m n : nat) : Prop := ∃ k, m * k = n

lemma divisor_le (m n) : divides m n → n ≠ 0 → m ≤ n :=
begin
  intros h hn, cases h with k hk, cases k with k,
  exfalso, apply hn, rewrite mul_zero at hk, rewrite hk,
  rewrite eq.symm hk, rewrite nat.mul_succ,
  apply nat.le_add_left
end

lemma ex_pred_of_neq_zero (m) : m ≠ 0 → ∃ m', m = nat.succ m' :=
begin
  intro hm, cases m with m', exfalso, apply hm, refl,
  existsi m', refl
end

lemma gt_zero_of_neq_zero (n) : n ≠ 0 → n > 0 :=
begin
  intro h, cases n, exfalso, apply h, refl,
  apply nat.zero_lt_succ
end

lemma gcd_divides : ∀ (m n : nat), divides (nat.gcd m n) m ∧ divides (nat.gcd m n) n
| 0 n :=
  begin
    apply and.intro, existsi 0, rewrite mul_zero,
    unfold nat.gcd, existsi 1, rewrite mul_one
  end
| (m+1) n :=
  have (n % nat.succ m < nat.succ m), from (nat.mod_lt _ $ nat.succ_pos _),
  begin
    unfold nat.gcd, apply and.intro,
    apply and.elim_right (gcd_divides _ _),
    cases (gcd_divides (n % nat.succ m) (nat.succ m)) with h1 h2,
    cases h1 with i hi, cases h2 with j hj,
    existsi (j * (n/(m+1))) + i,
    rewrite mul_add, rewrite hi,
    rewrite eq.symm (mul_assoc _ _ _),
    rewrite hj, rewrite add_comm,
    apply nat.mod_add_div
  end

lemma mul_nonzero {m n : nat} : m ≠ 0 → n ≠ 0 → m * n ≠ 0 :=
begin
  intros hm hn hc, apply hm,
  apply eq.trans, apply eq.symm,
  apply nat.mul_div_cancel,
  apply gt_zero_of_neq_zero, apply hn,
  rewrite hc, apply nat.zero_div
end

theorem gcd_neq_zero : ∀ (m n : nat), m ≠ 0 → n ≠ 0 → (nat.gcd m n) ≠ 0
| 0 n hm hn := begin exfalso, apply hm, refl end
| (nat.succ m) n hm hn :=
  have (n % nat.succ m < nat.succ m), from (nat.mod_lt _ $ nat.succ_pos _),
  begin
    unfold nat.gcd,
    apply (@classical.by_cases (n % nat.succ m ≠ 0) _ _ _); intro h,
    apply gcd_neq_zero, apply h, intro hc, cases hc,
    rewrite (classical.by_contradiction h), intro hc, cases hc
  end

lemma lcm_nonzero (m n : nat) : m ≠ 0 → n ≠ 0 → (nat.lcm m n) ≠ 0 :=
begin
  intros hm hn hc,
  have h := nat.gcd_mul_lcm m n,
  rewrite hc at h, rewrite mul_zero at h,
  apply nat.mul_nonzero hm hn,
  apply eq.symm h
end

lemma of_nat_neq_zero (n : nat) : n ≠ 0 → int.of_nat n ≠ 0 :=
begin
  intro hn, cases n, exfalso, apply hn, refl,
  intro hc, cases hc
end

lemma neq_zero_of_of_nat_neq_zero {n : nat} : int.of_nat n ≠ 0 → n ≠ 0 :=
begin
  cases n, intro hc, exfalso, apply hc, refl,
  intros _ hc, cases hc
end

end nat


set_option trace.simplify.rewrite true
open nat

-- 1.1.4
universe u
constants α β γ : Type

def some_fun_of_type : (α → β → γ) → ((β → α) → β) → α → γ :=
λ f, λ g, λ a, f a (g(λ h, a))

/-
inductive type-name (params1 : type1) . . . (paramsk : typek) : Type
| constructor-name1 : constructor-type1
.
.
.
| constructor-namen : constructor-typen
-/

inductive aexp : Type
| num : ℤ → aexp
| var : string → aexp
| add : aexp → aexp → aexp
| sub : aexp → aexp → aexp
| mul : aexp → aexp → aexp
| div : aexp → aexp → aexp

def eval (env : string → ℤ) : aexp → ℤ
| (aexp.num i) := i
| (aexp.var x) := env x
| (aexp.add e1 e2) := eval e1 + eval e2
| (aexp.sub e1 e2) := eval e1 - eval e2
| (aexp.mul e1 e2) := eval e1 * eval e2
| (aexp.div e1 e2) := eval e1 / eval e2

-- 2.1

lemma fst_of_two_props :
∀a b : Prop, a → b → a :=
begin
intros a b,
intros ha hb,
assumption,
end

lemma fst_of_two_props2 (a b : Prop) (ha : a) (hb : b) :
a :=
begin
exact ha,
end

lemma fst_of_two_props3 (a b : Prop) (ha : a) (hb : b) :
a :=
by apply ha

lemma prop_comp (a b c : Prop) (hab : a → b) (hbc : b → c) :
a → c :=
begin
intro ha,
apply hbc,
apply hab,
apply ha,
end

-- 2.3
lemma and_swap (a b : Prop) :
a ∧ b → b ∧ a :=
begin
intro hab,
apply and.intro,
apply and.elim_right,
exact hab,
apply and.elim_left,
exact hab,
end

lemma and_swap2 :
∀a b : Prop, a ∧ b → b ∧ a :=
begin
intros a b hab,
apply and.intro,
{ exact and.elim_right hab },
{ exact and.elim_left hab }
end

lemma nat_exists_double_iden :
∃n : ℕ, n = n :=
begin
apply exists.intro 0,
refl,
end

-- 2.4
lemma cong_fst_arg {α : Type} (a a' b : α)
(f : α → α → α) (ha : a = a') :
f a b = f a' b :=
begin
apply eq.subst ha,
apply eq.refl,
end

lemma cong_two_args {α : Type} (a a' b b' : α)
(f : α → α → α) (ha : a = a') (hb : b = b') :
f a b = f a' b' :=
begin
apply eq.subst ha,
apply eq.subst hb,
apply eq.refl
end

lemma cong_two_args2 {α : Type} (a a' b b' : α)
(f : α → α → α) (ha : a = a') (hb : b = b') :
f a b = f a' b' :=
begin
rw ha,
rw hb,
end

lemma cong_two_args3 {α : Type} (a a' b b' : α)
(f : α → α → α) (ha : a = a') (hb : b = b') :
f a b = f a' b' :=
begin
simp[*] at *,
end

-- 2.6
namespace myadd

def add : ℕ → ℕ → ℕ
| m nat.zero := m
| m (nat.succ n) := nat.succ (add m n)

lemma add_zero (n : ℕ) :
add 0 n = n :=
begin
induction n,
{ refl },
{ simp [add, n_ih] }
end

lemma add_succ (m n : ℕ) :
add (nat.succ m) n = nat.succ (add m n) :=
begin
induction n,
{ refl },
{ simp [add, n_ih] }
end

lemma add_comm (m n : ℕ) :
add m n = add n m :=
begin
induction n,
{ simp [add, add_zero] },
{ simp [add, add_succ, n_ih] }
end

lemma add_assoc (l m n : ℕ) :
add (add l m) n = add l (add m n) :=
begin
induction n,
{ refl },
{ simp [add, n_ih] }
end

@[instance] def add.is_commutative : is_commutative ℕ add :=
{ comm := add_comm }
@[instance] def add.is_associative : is_associative ℕ add :=
{ assoc := add_assoc }

end myadd


import .love05_inductive_predicates_demo

/-! # LoVe Demo 12: Basic Mathematical Structures

We introduce definitions and proofs about basic mathematical structures such as
groups, fields, and linear orders. -/

set_option pp.beta true

namespace LoVe

/-! ## Type Classes over a Single Binary Operator

Mathematically, a __group__ is a set `G` with a binary operator `• : G × G → G`
with the following properties, called __group axioms__:

* Associativity: For all `a, b, c ∈ G`, we have `(a • b) • c = a • (b • c)`.
* Identity element: There exists an element `e ∈ G` such that for all `a ∈ G`,
  we have `e • a = a`.
* Inverse element: For each `a ∈ G`, there exists an inverse element
  `inv(a) ∈ G` such that `inv(a) • a = e`.

Examples of groups are
* `ℤ` with `+`;
* `ℝ` with `+`;
* `ℝ \ {0}` with `*`.

In Lean, a type class for groups can be defined as follows: -/

namespace monolithic_group

@[class] structure group (α : Type) : Type :=
(mul          : α → α → α)
(one          : α)
(inv          : α → α)
(mul_assoc    : ∀a b c, mul (mul a b) c = mul a (mul b c))
(one_mul      : ∀a, mul one a = a)
(mul_left_inv : ∀a, mul (inv a) a = one)

end monolithic_group

/-! In Lean, however, group is part of a larger hierarchy of algebraic
structures:

Type class               | Properties                               | Examples
------------------------ | -----------------------------------------|-------------------
`semigroup`              | associativity of `*`                     | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`monoid`                 | `semigroup` with unit `1`                | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`left_cancel_semigroup`  | `semigroup` with `c * a = c * b → a = b` |
`right_cancel_semigroup` | `semigroup` with `a * c = b * c → a = b` |
`group`                  | `monoid` with inverse `⁻¹`               |

Most of these structures have commutative versions: `comm_semigroup`,
`comm_monoid`, `comm_group`.

The __multiplicative__ structures (over `*`, `1`, `⁻¹`) are copied to produce
__additive__ versions (over `+`, `0`, `-`):

Type class                   | Properties                                   | Examples
---------------------------- | ---------------------------------------------|-------------------
`add_semigroup`              | associativity of `+`                         | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`add_monoid`                 | `add_semigroup` with unit `0`                | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`add_left_cancel_semigroup`  | `add_semigroup` with `c + a = c + b → a = b` | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`add_right_cancel_semigroup` | `add_semigroup` with `a + c = b + c → a = b` | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`add_group`                  | `add_monoid` with inverse `-`                | `ℝ`, `ℚ`, `ℤ` -/

#print group
#print add_group

/-! Let us define our own type, of integers modulo 2, and register it as an
additive group. -/

inductive ℤ₂ : Type
| zero
| one

def ℤ₂.add : ℤ₂ → ℤ₂ → ℤ₂
| ℤ₂.zero a       := a
| a       ℤ₂.zero := a
| ℤ₂.one  ℤ₂.one  := ℤ₂.zero

@[instance] def ℤ₂.add_group : add_group ℤ₂ :=
{ add          := ℤ₂.add,
  add_assoc    :=
    by intros a b c; simp [(+)]; cases a; cases b; cases c;
      refl,
  zero         := ℤ₂.zero,
  zero_add     := by intro a; cases a; refl,
  add_zero     := by intro a; cases a; refl,
  neg          := λa, a,
  add_left_neg := by intro a; cases a; refl }

#reduce ℤ₂.one + 0 - 0 - ℤ₂.one

lemma ℤ₂.add_right_neg:
  ∀a : ℤ₂, a + - a = 0 :=
add_right_neg

/-! Another example: Lists are an `add_monoid`: -/

@[instance] def list.add_monoid {α : Type} :
  add_monoid (list α) :=
{ zero      := [],
  add       := (++),
  add_assoc := list.append_assoc,
  zero_add  := list.nil_append,
  add_zero  := list.append_nil }

/-! ## Type Classes with Two Binary Operators

Mathematically, a __field__ is a set `F` such that

* `F` forms a commutative group under an operator `+`, called addition, with
  identity element `0`.
* `F\{0}` forms a commutative group under an operator `*`, called
  multiplication.
* Multiplication distributes over addition—i.e.,
  `a * (b + c) = a * b + a * c` for all `a, b, c ∈ F`.

In Lean, fields are also part of a larger hierarchy:

Type class       |  Properties                                         | Examples
-----------------|-----------------------------------------------------|-------------------
`semiring`       | `monoid` and `add_comm_monoid` with distributivity  | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`comm_semiring`  | `semiring` with commutativity of `*`                | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`ring`           | `monoid` and `add_comm_group` with distributivity   | `ℝ`, `ℚ`, `ℤ`
`comm_ring`      | `ring` with commutativity of `*`                    | `ℝ`, `ℚ`, `ℤ`
`division_ring`  | `ring` with multiplicative inverse `⁻¹`             | `ℝ`, `ℚ`
`field`          | `division_ring` with commutativity of `*`           | `ℝ`, `ℚ`
`discrete_field` | `field` with decidable equality and `∀n, n / 0 = 0` | `ℝ`, `ℚ` -/

#print field

/-! Let us continue with our example: -/

def ℤ₂.mul : ℤ₂ → ℤ₂ → ℤ₂
| ℤ₂.one  a       := a
| a       ℤ₂.one  := a
| ℤ₂.zero ℤ₂.zero := ℤ₂.zero

@[instance] def ℤ₂.field : field ℤ₂ :=
{ one            := ℤ₂.one,
  mul            := ℤ₂.mul,
  inv            := λa, a,
  add_comm       := by intros a b; cases a; cases b; refl,
  zero_ne_one    := by finish,
  one_mul        := by intros a; cases a; refl,
  mul_one        := by intros a; cases a; refl,
  mul_inv_cancel := by intros a h; cases a; finish,
  inv_mul_cancel := by intros a h; cases a; finish,
  mul_assoc      :=
    by intros a b c; cases a; cases b; cases c; refl,
  mul_comm       := by intros a b; cases a; cases b; refl,
  left_distrib   :=
    by intros a b c; by cases a; cases b; cases c; refl,
  right_distrib  :=
    by intros a b c; by cases a; cases b; cases c; refl,
  ..ℤ₂.add_group }

#reduce (1 : ℤ₂) * 0 / (0 - 1)

#reduce (3 : ℤ₂)

lemma ring_example (a b : ℤ₂) :
  (a + b) ^ 3 = a ^ 3 + 3 * a ^ 2 * b + 3 * a * b ^ 2 + b ^ 3 :=
by ring

lemma ring_exp_example (a b : ℤ₂) (n : ℕ):
  (a + b) ^ (2 + n) =
  (a + b) ^ n * (a ^ 2 + 2 * a * b + b ^ 2) :=
by ring_exp

/-! `ring` and `ring_exp` prove equalities over commutative rings and semirings
by normalizing expressions. The `ring_exp` variant also normalizes exponents. -/

lemma abel_example (a b : ℤ) :
  a + b + 0 - (b + a + a) = - a :=
by abel

/-! `abel` proves equalities over additive commutative monoids and groups by
normalizing expressions.

## Coercions

When combining numbers form `ℕ`, `ℤ`, `ℚ`, and `ℝ`, we might want to cast from
one type to another. Lean has a mechanism to automatically introduce coercions,
represented by `coe` (syntactic sugar: `↑`). `coe` can be set up to provide
implicit coercions between arbitrary types.

Many coercions are already in place, including the following:

* `coe : ℕ → α` casts `ℕ` to another semiring `α`;
* `coe : ℤ → α` casts `ℤ` to another ring `α`;
* `coe : ℚ → α` casts `ℚ` to another division ring `α`.

For example, this works, although negation `- n` is not defined on natural
numbers: -/

lemma neg_mul_neg_nat (n : ℕ) (z : ℤ) :
  (- z) * (- n) = z * n :=
neg_mul_neg z n

/-! Notice how Lean introduced a `↑` coercion: -/

#print neg_mul_neg_nat

/-! Another example: -/

lemma neg_nat_mul_neg (n : ℕ) (z : ℤ) :
  (- n : ℤ) * (- z) = n * z :=
neg_mul_neg n z

#print neg_nat_mul_neg

/-! In proofs involving coercions, the tactic `norm_cast` can be convenient. -/

lemma norm_cast_example_1 (m n : ℕ) (h : (m : ℤ) = (n : ℤ)) :
  m = n :=
begin
  norm_cast at h,
  exact h
end

lemma norm_cast_example_2 (m n : ℕ) :
  (m : ℤ) + (n : ℤ) = ((m + n : ℕ) : ℤ) :=
by norm_cast

/-! `norm_cast` moves coercions towards the inside of expressions, as a form of
simplification. Like `simp`, it will generally produce a subgoal.

`norm_cast` relies on lemmas such as the following: -/

#check nat.cast_add
#check int.cast_add
#check rat.cast_add

/-! ### Lists, Multisets and Finite Sets

For finite collections of elements different structures are available:

* lists: order and duplicates matter;
* multisets: only duplicates matter;
* finsets: neither order nor duplicates matter. -/

lemma list_duplicates_example :
  [2, 3, 3, 4] ≠ [2, 3, 4] :=
dec_trivial

lemma list_order_example :
  [4, 3, 2] ≠ [2, 3, 4] :=
dec_trivial

lemma multiset_duplicates_example :
  ({2, 3, 3, 4} : multiset ℕ) ≠ {2, 3, 4} :=
dec_trivial

lemma multiset_order_example :
  ({2, 3, 4} : multiset ℕ) = {4, 3, 2} :=
dec_trivial

lemma finset_duplicates_example :
  ({2, 3, 3, 4} : finset ℕ) = {2, 3, 4} :=
dec_trivial

lemma finsetorder_example :
  ({2, 3, 4} : finset ℕ) = {4, 3, 2} :=
dec_trivial

def list.elems : btree ℕ → list ℕ
| btree.empty        := []
| (btree.node a l r) := a :: list.elems l ++ list.elems r

def multiset.elems : btree ℕ → multiset ℕ
| btree.empty        := ∅
| (btree.node a l r) :=
  {a} ∪ (multiset.elems l ∪ multiset.elems r)

def finset.elems : btree ℕ → finset ℕ
| btree.empty        := ∅
| (btree.node a l r) := {a} ∪ (finset.elems l ∪ finset.elems r)

#eval list.sum [2, 3, 4]                          -- result: 9
#eval multiset.sum ({2, 3, 4} : multiset ℕ)       -- result: 9
#eval finset.sum ({2, 3, 4} : finset ℕ) (λn, n)   -- result: 9

#eval list.prod [2, 3, 4]                         -- result: 24
#eval multiset.prod ({2, 3, 4} : multiset ℕ)      -- result: 24
#eval finset.prod ({2, 3, 4} : finset ℕ) (λn, n)  -- result: 24

/-! ## Order Type Classes

Many of the structures introduced above can be ordered. For example, the
well-known order on the natural numbers can be defined as follows: -/

inductive nat.le : ℕ → ℕ → Prop
| refl : ∀a : ℕ, nat.le a a
| step : ∀a b : ℕ, nat.le a b → nat.le a (b + 1)

/-! This is an example of a linear order. A __linear order__ (or
__total order__) is a binary relation `≤` such that for all `a`, `b`, `c`, the
following properties hold:

* Reflexivity: `a ≤ a`.
* Transitivity: If `a ≤ b` and `b ≤ c`, then `a ≤ c`.
* Antisymmetry: If `a ≤ b` and `b ≤ a`, then `a = b`.
* Totality: `a ≤ b` or `b ≤ a`.

If a relation has the first three properties, it is a __partial order__. An
example is `⊆` on sets, finite sets, or multisets. If a relation has the first
two properties, it is a __preorder__. An example is comparing lists by their
length.

In Lean, there are type classes for these different kinds of orders:
`linear_order`, `partial_order`, and `preorder`. The `preorder` class has the
fields

    `le       : α → α → Prop`
    `le_refl  : ∀a : α, le a a`
    `le_trans : ∀a b c : α, le a b → le b c → le a c`

The `partial_order` class also has

    `le_antisymm : ∀a b : α, le a b → le b a → a = b`

and `linear_order` also has

    `le_total : ∀a b : α, le a b ∨ le b a`

We can declare the preorder on lists that compares lists by their length as
follows: -/

@[instance] def list.length.preord {α : Type} :
  preorder (list α) :=
{ le       := λxs ys, list.length xs ≤ list.length ys,
  le_refl  := by intro xs; exact nat.le_refl _,
  le_trans := by intros xs ys zs; exact nat.le_trans }

/-! This instance introduces the infix syntax `≤` and the relations `≥`, `<`,
and `>`: -/

lemma list.length.preord_example {α : Type} (c : α) :
  [c] > [] :=
dec_trivial

/-! Complete lattices (lecture 10) are formalized as another type class,
`complete_lattice`, which inherits from `partial_order`.

Type classes combining orders and algebraic structures are also available:

    `ordered_cancel_comm_monoid`
    `ordered_comm_group ordered_semiring`
    `linear_ordered_semiring`
    `linear_ordered_comm_ring`
    `linear_ordered_field`

All these mathematical structures relate `≤` and `<` with `0`, `1`, `+`, and `*`
by monotonicity rules (e.g., `a ≤ b → c ≤ d → a + c ≤ b + d`) and cancellation
rules (e.g., `c + a ≤ c + b → a ≤ b`). -/

end LoVe


import data.set.basic tactic.interactive

/-
Here's Kevin's problem:

  Let’s say I tell two of my tutees secret non-negative integers, and then I wrote
  two distinct non-negative integers on my whiteboard; one is the sum of the two
  secret integers, and the other one is not. I then take it in turns asking my
  tutees whether they know the other tutee’s number! Prove that eventually one of
  them will (truthfully) say “yes”.

To model this problem, we use a modal framework. A frame is a set of possible
worlds. Each world represents the way things could be. In this case, we
will allow evolution of the set of possible worlds by using a changing predicate
of "live" worlds that represents all the worlds that have not yet been excluded
by some reasoning from a declaration of ignorance.
-/

section
parameters (X Y : nat)

/-- In this model, we will store the things that are not public information
  in the set of possible worlds. So the numbers X,Y on the blackboard are
  parameters because there is no "possibility" about it, everyone knows what
  they are. -/
structure world :=
(A : nat) -- the number given to Alice
(B : nat) -- the number given to Bob
(consistent : A + B = X ∨ A + B = Y) -- the numbers can't contradict the whiteboard

instance world.inhabited : inhabited world := ⟨⟨X, 0, or.inl rfl⟩⟩

/-- Given a set of "live" worlds, we can figure out which numbers
  A thinks are possible for B to have -/
def A_thinks (w : world) (live : set world) : set nat :=
{B' | ∃ w':world, w' ∈ live ∧ w'.A = w.A ∧ w'.B = B'}

/-- Given a set of "live" worlds, we can figure out which numbers
  B thinks are possible for A to have -/
def B_thinks (w : world) (live : set world) : set nat :=
{A' | ∃ w':world, w' ∈ live ∧ w'.B = w.B ∧ w'.A = A'}

/-- A knows the answer if the possible values she has for B's number is a singleton -/
def A_knows (w : world) (live) := ∃ n, A_thinks w live = {n}

/-- B knows the answer if the possible values he has for A's number is a singleton -/
def B_knows (w : world) (live) := ∃ n, B_thinks w live = {n}

/-- This is where the action happens. The set of "live" worlds is a
  representation of the possible worlds that are eliminated in each stage
  assuming that A and B keep saying they don't know the answer. The game
  ends when one says they know the answer, so we only have to worry about
  this path; we're going to show that it can't continue forever. -/
def live : ℕ → set world
| 0     := set.univ -- initially no one knows anything, all possibilities are live
| (n+1) :=
  -- At stage n+1, if we assume that A and B both pass, then neither knows the answer
  -- so all worlds where they figured it out are excluded
  {w ∈ live n | ¬ A_knows w (live n) ∧ ¬ B_knows w (live n)}

/- because we're mathematicians: -/
local attribute [instance] classical.prop_decidable

/- At this point, the main theorem we want to prove is that there is some stage where
  either A or B solves the problem -/
theorem A_or_B_knows (w : world) : ∃ n, w ∈ live n ∧ (A_knows w (live n) ∨ B_knows w (live n)) :=
begin
  /- We'll prove that at each stage, the worlds at the "ends" are lost, sweeping from
    both sides (we only need one side to know that eventually all worlds are excluded). -/
  suffices : ∀ (n) (w':world), w' ∈ live n → w'.A + n ≤ max X Y + w'.B,
  { by_contra H,
    replace H : ∀ n, w ∈ live n,
    { simp [not_exists, not_or_distrib] at H,
      intro n,
      induction n with n; simp [live, *] },
    have : w.A + (max X Y + w.B) < max X Y + w.B := this _ w (H (max X Y + w.B + 1)),
    exact not_le_of_gt this (nat.le_add_left _ _) },
  clear w, intros n w wl,
  induction n with n IH generalizing w,
  { refine add_le_add _ (nat.zero_le _),
    refine le_trans (nat.le_add_right w.A w.B) _,
    cases w.consistent; simp [h, le_max_left, le_max_right] },
  { rcases wl with ⟨wl, HA, HB⟩,
    refine lt_of_le_of_ne (IH _ wl) _,
    intro he,
    have A_thinks_other : ¬ ∀ k ∈ A_thinks w (live n), k = w.B,
    { have A_thinks_B : w.B ∈ A_thinks w (live n) := ⟨_, wl, rfl, rfl⟩,
      refine λ h, HA ⟨w.B, _⟩,
      simp [set.set_eq_def],
      exact λ k, ⟨h k, λ e, e.symm ▸ A_thinks_B⟩ },
    have B_thinks_other : ¬ ∀ k ∈ B_thinks w (live n), k = w.A,
    { have B_thinks_A : w.A ∈ B_thinks w (live n) := ⟨_, wl, rfl, rfl⟩,
      refine λ h, HB ⟨w.A, _⟩,
      simp [set.set_eq_def],
      exact λ k, ⟨h k, λ e, e.symm ▸ B_thinks_A⟩ },
    rcases not_ball.1 A_thinks_other with ⟨B', A_thinks_B', hB'⟩,
    rcases A_thinks_B' with ⟨w₁, w₁l, w₁A, rfl⟩,
    rcases not_ball.1 B_thinks_other with ⟨A', B_thinks_A', hA'⟩,
    rcases B_thinks_A' with ⟨w₂, w₂l, w₂B, rfl⟩,
    have := IH _ w₁l,
    rw [w₁A, he] at this,
    have w₁B := lt_of_le_of_ne (le_of_add_le_add_left this) (ne.symm hB'),
    have := IH _ w₂l,
    rw [w₂B, ← he] at this,
    have w₂A := lt_of_le_of_ne (le_of_add_le_add_right this) hA',
    have lt₁ := add_lt_add_of_le_of_lt (le_of_eq w₁A.symm) w₁B,
    have lt₂ := add_lt_add_of_lt_of_le w₂A (le_of_eq w₂B),
    /- at this point, the contradiction is clear, since A+B of w₁ is
      less than A+B at w, which is less than A+B at w₂, while A+B is
      in the set {X,Y} for any world. But proving this requires a
      lot of case analysis -/
    cases w₁.consistent with w₁h w₁h; rw w₁h at lt₁;
    cases w.consistent with wh wh; rw wh at lt₁ lt₂;
    try { apply lt_irrefl _ lt₁ };
    cases w₂.consistent with w₂h w₂h; rw w₂h at lt₂;
    try { apply lt_irrefl _ lt₂ };
    exact lt_asymm lt₁ lt₂ }
end

end


import MyNat.Addition
import MyNat.Multiplication
namespace MyNat
open MyNat

/-!
# Function world.

## Level 2: the `intro` tactic.

Let's make a function. Let's define the function on the natural
numbers which sends a natural number `n` to `3n+2`. In the example
below you will see that our goal is `⊢ MyNat → MyNat`. A mathematician
might denote this set with some exotic name such as `Hom(ℕ,ℕ)`,
but computer scientists use notation `X → Y` to denote the set of
functions from `X` to `Y` and this name definitely has its merits.
In type theory, `X → Y` is a type (the type of all functions from `X` to `Y`),
and `f : X → Y` means that `f` is a term
of this type, i.e., `f` is a function from `X` to `Y`.

To define a function `X → Y` we need to choose an arbitrary
element `x ∈ X` and then, perhaps using `x`, make an element of `Y`.
The Lean tactic for "let `x ∈ X` be arbitrary" is `intro x`.

## Rule of thumb:

If your goal is `P → Q` then `intro p` will make progress.

To solve the goal below, you have to come up with a function from `MyNat`
to `MyNat`. We can start with `intro n`

(i.e. "let `n ∈ ℕ` be arbitrary") and note that our
local context now looks like this:

```
n : MyNat
⊢ MyNat
```

Our job now is to construct a natural number, which is
allowed to depend on `n`. We can do this using `exact` and
writing a formula for the function we want to define. For example
we imported addition and multiplication at the top of this file,
so `exact 3*n+2`
will close the goal, ultimately defining the function `f(n)=3n+2`.

## Definition

We define a function from MyNat to MyNat.
-/
example : MyNat → MyNat := by
  intro n
  exact 3*n+2

/-!
You can hover your mouse over the tactics `intro` and `exact`
to the documentation on these tactics in case you need a
reminder later on.
See also [intro tactic](../Tactics/intro.lean.md)

Next up [Level 3](./Level3.lean.md)
-/


import MyNat.Power
import MultiplicationWorld.Level2 -- mul_one
import MultiplicationWorld.Level9 -- simp additions
namespace MyNat
open MyNat

/-!

# Power World

## Level 6: `mul_pow`

Here we use the `attribute [simp]` additions we made in
[level 9 of Multiplication World](../MultiplicationWorld/Level9.lean.md)
so that the `simp` tactic can simplify expressions involving `*`.

## Lemma
For all naturals `a`, `b`, `n`, we have `(ab) ^ n = a ^ nb ^ n`.
-/
lemma mul_pow (a b n : MyNat) : (a * b) ^ n = a ^ n * b ^ n := by
  induction n with
  | zero =>
    rw [zero_is_0]
    repeat rw [pow_zero]
    rw [mul_one]
  | succ n ih =>
    repeat rw [pow_succ]
    rw [ih]
    simp

/-!
Next up [Level 7](./Level7.lean.md)
-/