section propositional

variables P Q R : Prop

------------------------------------------------
-- Proposições de dupla negaço:
------------------------------------------------

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intros p not_p,
  exact not_p p,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro not_not_p,
  by_cases h: P,
    -- Case P
    exact h,
    -- Case ¬P
    exfalso,
    exact not_not_p h,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
  exact doubleneg_elim P,
  exact doubleneg_intro P,

end

------------------------------------------------
-- Comutatividade dos ∨,∧:
------------------------------------------------

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro p_or_q,
  cases p_or_q with p q,
    --Case P
    right,
    exact p,
    --Case Q
    left,
    exact q,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro p_and_q,
  cases p_and_q with p q,
  split,
  exact q,
  exact p,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos →,∨:
------------------------------------------------

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro np_or_q,
  intro p,
  cases np_or_q with np q,
    --Case ¬P
    exfalso,
    exact np p,
    --Case Q
    exact q,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intros p_or_q np,
  cases p_or_q with p q,
    --Case P
    exfalso,
    exact np p,
    --Case Q
    exact q,
end

------------------------------------------------
-- Proposições de contraposição:
------------------------------------------------

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intros p_imp_q nq p,
  apply nq,
  exact p_imp_q p,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intros h p,
  by_cases q: Q,
    --Case Q
    exact q,
    --Case ¬Q
    have np := h q,
    exfalso,
    exact np p,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
  exact impl_as_contrapositive P Q,
  exact impl_as_contrapositive_converse P Q,
end

------------------------------------------------
-- A irrefutabilidade do LEM:
------------------------------------------------

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro h,
  apply h,
  right,
  intro p,
  have p_or_np: P∨¬P,
    --Demons PV¬P
    left,
    exact p,
  exact h p_or_np,
end

------------------------------------------------
-- A lei de Peirce
------------------------------------------------

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intros h np,
  apply np,
  apply h,
  intro p,
  exfalso,
  exact np p,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∨,∧:
------------------------------------------------

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intros p_or_q np_and_nq,
  cases np_and_nq with np nq,
  cases p_or_q with p q,
    --Case P
    exact np p,
    --Case Q
    exact nq q,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro p_and_q,
  intro np_or_nq,
  cases p_and_q with p q,
  cases np_or_nq with np nq,
    --Case P
    exact np p,
    --Case Q
    exact nq q,
end

------------------------------------------------
-- As leis de De Morgan para ∨,∧:
------------------------------------------------

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_p_or_q,
  split,
  --Parte ¬P
  intro p,
  apply n_p_or_q,
  left,
  exact p,
  --Parte ¬Q
  intro q,
  apply n_p_or_q,
  right,
  exact q,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intros np_and_nq p_or_q,
  cases np_and_nq with np nq,
  cases p_or_q with p q,
    --Case P
    exact np p,
    --Case Q
    exact nq q,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_p_and_q,
  by_cases h: Q,
    --Case Q
    right,
    intro p,
    apply n_p_and_q,
    split,
    exact p,
    exact h,
    --Case ¬Q
    left,
    exact h,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intros nq_or_np p_and_q,
  cases p_and_q with p q,
  cases nq_or_np with nq np,
    --Case P
    exact nq q,
    --Case Q
    exact np p,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
  exact demorgan_conj P Q,
  exact demorgan_conj_converse P Q,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
  exact demorgan_disj P Q,
  exact demorgan_disj_converse P Q,
end

------------------------------------------------
-- Proposições de distributividade dos ∨,∧:
------------------------------------------------

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro h,
  cases h with p q_or_r,
  cases q_or_r with q r,
    --Case Q
    left,
    split,
    exact p,
    exact q,
    --case R
    right,
    split,
    exact p,
    exact r,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro h,
  cases h with p_and_q p_and_r,
    --Case P∧Q
    cases p_and_q with p q,
    split,
    exact p,
    left,
    exact q,
    --Case PVQ
    cases p_and_r with p r,
    split,
    exact p,
    right,
    exact r,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro h,
  cases h with p q_and_r,
    --Case P
    split,
    left,
    exact p,
    left,
    exact p,
    --Case Q∧R
    cases q_and_r with q r,
    split,
    right,
    exact q,
    right,
    exact r,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro h,
  cases h with p_or_q p_or_r,
  cases p_or_q with p q,
    --Case P
    left,
    exact p,
    --Case Q
    cases p_or_r with p r,
      --Case P
      left,
      exact p,
      --Case R
      right,
      split,
      exact q,
      exact r,
end

------------------------------------------------
-- Currificação
------------------------------------------------

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intros h p q,
  exact h ⟨p,q⟩,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intros h p_and_q,
  cases p_and_q with p q,
  apply h,
  exact p,
  exact q,
end

------------------------------------------------
-- Reflexividade da →:
------------------------------------------------

theorem impl_refl :
  P → P  :=
begin
  intro p,
  exact p,
end

------------------------------------------------
-- Weakening and contraction:
------------------------------------------------

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro p,
  left,
  exact p,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro q,
  right,
  exact q,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro p_and_q,
  cases p_and_q with p q,
  exact p,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro p_and_q,
  cases p_and_q with p q,
  exact q,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  exact weaken_conj_right P P,
  intro p,
  split,
  repeat {exact p},
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro p_or_p,
  cases p_or_p with p1 p2,
  --Case P(left)
  exact p1,
  --Case P(right)
  exact p2,
  exact weaken_disj_right P P,
end

end propositional

----------------------------------------------------------------

section predicate

variable U : Type
variables P Q : U -> Prop

------------------------------------------------
-- As leis de De Morgan para ∃,∀:
------------------------------------------------

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intros h x p_x,
  apply h,
  existsi x,
  exact p_x,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intros h1 h2,
  cases h2 with x p_x,
  have n_p_x := h1 x,
  exact n_p_x p_x,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro h1,
  by_contra,
  apply h1,
  intro x,
  by_contra hboom,
  apply h,
  existsi x,
  exact hboom,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intros h1 h2,
  cases h1 with x n_p_x,
  have p_x := h2 x,
  exact n_p_x p_x,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  split,
  exact demorgan_forall U P,
  exact demorgan_forall_converse U P,
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
  exact demorgan_exists U P,
  exact demorgan_exists_converse U P,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∃,∀:
------------------------------------------------

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intros h1 h2,
  cases h1 with x p_x,
  have n_p_x := h2 x,
  exact n_p_x p_x,

end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intros h1 h2,
  cases h2 with x n_p_x,
  have p_x := h1 x,
  exact n_p_x p_x,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intros h x,
  by_contra hboom,
  apply h,
  existsi x,
  exact hboom,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro h,
  by_contra hboom,
  apply h,
  intros x p_x,
  apply hboom,
  existsi x,
  exact p_x,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  split,
  exact forall_as_neg_exists U P,
  exact forall_as_neg_exists_converse U P,
end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
  exact exists_as_neg_forall U P,
  exact exists_as_neg_forall_converse U P,
end

------------------------------------------------
--  Proposições de distributividade de quantificadores:
------------------------------------------------

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro h,
  cases h with x px_and_qx,
  cases px_and_qx with p_x q_x,
  split,
  existsi x,
  exact p_x,
  existsi x,
  exact q_x,

end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro h,
  cases h with x px_or_qx,
  cases px_or_qx with px qx,
    --Case P x
    left,
    existsi x,
    exact px,
    -- Case Q x
    right,
    existsi x,
    exact qx,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro h,
  cases h with p_x q_x,
    --Case ∃x, P x
    cases p_x with x px,
    existsi x,
    left,
    exact px,
    --Case ∃x, Q x
    cases q_x with x qx,
    existsi x,
    right,
    exact qx,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro h,
  split,
  intro x,
  cases h x with px qx,
  exact px,
  intro x,
  cases h x with px qx,
  exact qx,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intros h x,
  cases h with p_x q_x,
  split,
  exact p_x x,
  exact q_x x,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intros h x,
  cases h with p_x q_x,
    --Case ∀x, P x
    left,
    exact p_x x,
    --Case ∀x, Q x
    right,
    exact q_x x,

end

/- NOT THEOREMS --------------------------------

theorem forall_disj_as_disj_forall :
  (∀x, P x ∨ Q x) → (∀x, P x) ∨ (∀x, Q x)  :=
begin
end

theorem exists_conj_as_conj_exists_converse :
  (∃x, P x) ∧ (∃x, Q x) → (∃x, P x ∧ Q x)  :=
begin
end

---------------------------------------------- -/

end predicate


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

! This file was ported from Lean 3 source module data.list.nat_antidiagonal
! leanprover-community/mathlib commit 7b78d1776212a91ecc94cf601f83bdcc46b04213
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.List.Nodup
import Mathlib.Data.List.Range

/-!
# Antidiagonals in ℕ × ℕ as lists

This file defines the antidiagonals of ℕ × ℕ as lists: the `n`-th antidiagonal is the list of
pairs `(i, j)` such that `i + j = n`. This is useful for polynomial multiplication and more
generally for sums going from `0` to `n`.

## Notes

Files `Data.Multiset.NatAntidiagonal` and `Data.Finset.NatAntidiagonal` successively turn the
`List` definition we have here into `Multiset` and `Finset`.
-/

open List Function Nat

namespace List

namespace Nat

/-- The antidiagonal of a natural number `n` is the list of pairs `(i, j)` such that `i + j = n`. -/
def antidiagonal (n : ℕ) : List (ℕ × ℕ) :=
  (range (n + 1)).map fun i ↦ (i, n - i)
#align list.nat.antidiagonal List.Nat.antidiagonal

/-- A pair (i, j) is contained in the antidiagonal of `n` if and only if `i + j = n`. -/
@[simp]
theorem mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} : x ∈ antidiagonal n ↔ x.1 + x.2 = n := by
  rw [antidiagonal, mem_map]; constructor
  · rintro ⟨i, hi, rfl⟩
    rw [mem_range, lt_succ_iff] at hi
    exact add_tsub_cancel_of_le hi
  · rintro rfl
    refine' ⟨x.fst, _, _⟩
    · rw [mem_range, add_assoc, lt_add_iff_pos_right]
      exact zero_lt_succ _
    · exact Prod.ext rfl (by simp only [add_tsub_cancel_left])
#align list.nat.mem_antidiagonal List.Nat.mem_antidiagonal

/-- The length of the antidiagonal of `n` is `n + 1`. -/
@[simp]
theorem length_antidiagonal (n : ℕ) : (antidiagonal n).length = n + 1 := by
  rw [antidiagonal, length_map, length_range]
#align list.nat.length_antidiagonal List.Nat.length_antidiagonal

/-- The antidiagonal of `0` is the list `[(0, 0)]` -/
@[simp]
theorem antidiagonal_zero : antidiagonal 0 = [(0, 0)] :=
  rfl
#align list.nat.antidiagonal_zero List.Nat.antidiagonal_zero

/-- The antidiagonal of `n` does not contain duplicate entries. -/
theorem nodup_antidiagonal (n : ℕ) : Nodup (antidiagonal n) :=
  (nodup_range _).map ((@LeftInverse.injective ℕ (ℕ × ℕ) Prod.fst fun i ↦ (i, n - i)) fun _ ↦ rfl)
#align list.nat.nodup_antidiagonal List.Nat.nodup_antidiagonal

@[simp]
theorem antidiagonal_succ {n : ℕ} :
    antidiagonal (n + 1) = (0, n + 1) :: (antidiagonal n).map (Prod.map Nat.succ id) := by
  simp only [antidiagonal, range_succ_eq_map, map_cons, true_and_iff, Nat.add_succ_sub_one,
    add_zero, id.def, eq_self_iff_true, tsub_zero, map_map, Prod.map_mk]
  apply congr rfl (congr rfl _)
  ext; simp
#align list.nat.antidiagonal_succ List.Nat.antidiagonal_succ

theorem antidiagonal_succ' {n : ℕ} :
    antidiagonal (n + 1) = (antidiagonal n).map (Prod.map id Nat.succ) ++ [(n + 1, 0)] := by
  simp only [antidiagonal, range_succ, add_tsub_cancel_left, map_append, append_assoc, tsub_self,
    singleton_append, map_map, map]
  congr 1
  apply map_congr
  simp (config := { contextual := true }) [le_of_lt, Nat.succ_eq_add_one, Nat.sub_add_comm]
#align list.nat.antidiagonal_succ' List.Nat.antidiagonal_succ'

theorem map_swap_antidiagonal {n : ℕ} :
    (antidiagonal n).map Prod.swap = (antidiagonal n).reverse := by
  rw [antidiagonal, map_map, ← List.map_reverse, range_eq_range', reverse_range', ←
    range_eq_range', map_map]
  apply map_congr
  simp (config := { contextual := true }) [Nat.sub_sub_self, lt_succ_iff]
#align list.nat.map_swap_antidiagonal List.Nat.map_swap_antidiagonal

end Nat

end List


import missing.linear_algebra.matrix.pos_def

open real
open matrix
open_locale real
open_locale matrix
open_locale big_operators

noncomputable def matrix.quad_form {n : ℕ} (R : matrix (fin n) (fin n) ℝ) (x : fin n → ℝ) : ℝ :=
  x ⬝ᵥ R.mul_vec x

noncomputable def gaussian_pdf {n : ℕ} (R : matrix (fin n) (fin n) ℝ) (x : fin n → ℝ) : ℝ :=
  (1 / sqrt (((2 * π) ^ n) * R.det)) * exp  (- R⁻¹.quad_form x / 2)

noncomputable def covariance_matrix {N n : ℕ} (y : fin N → fin n → ℝ) : matrix (fin n) (fin n) ℝ :=
  λ i j : fin n, (∑ k : fin N, y k i * y k j) / N

lemma gaussian_pdf_pos {n : ℕ} (R : matrix (fin n) (fin n) ℝ) (y : fin n → ℝ) (h : R.pos_def):
  0 < gaussian_pdf R y :=
mul_pos (div_pos zero_lt_one
  (sqrt_pos.2 (mul_pos (pow_pos (mul_pos (by linarith) pi_pos) _) h.det_pos))) (exp_pos _)

lemma prod_gaussian_pdf_pos {N n : ℕ} (R : matrix (fin n) (fin n) ℝ) (y : fin N → fin n → ℝ)
    (h : R.pos_def):
  0 < ∏ i : fin N, gaussian_pdf R (y i) :=
finset.prod_pos (λ i hi, gaussian_pdf_pos _ _ h)

lemma log_prod_gaussianPdf {N n : ℕ} (y : fin N → fin n → ℝ) (R : matrix (fin n) (fin n) ℝ) (hR : R.pos_def) :
    (log ∏ i : fin N, gaussian_pdf R (y i))
    = ∑ i : fin N, (- (log (sqrt ((2 * π) ^ n)) + log (sqrt (det R))) + - R⁻¹.quad_form (y i) / 2) :=
begin
    have : ∀ i,
      i ∈ finset.univ → gaussian_pdf R (y i) ≠ 0 := λ i hi, ne_of_gt (gaussian_pdf_pos _ _ hR),
    have sqrt_2_pi_n_R_det_ne_zero: sqrt ((2 * π) ^ n * R.det) ≠ 0 :=
      ne_of_gt (sqrt_pos.2 (mul_pos (pow_pos (mul_pos (by simp) pi_pos) _) hR.det_pos)),
    rw [log_prod finset.univ (λ i, gaussian_pdf R (y i)) this],
    unfold gaussian_pdf,
    apply congr_arg (finset.sum finset.univ),
    ext i,
    rw [log_mul, log_div, sqrt_mul, log_mul, log_exp, log_one, zero_sub],
    exact ne_of_gt (sqrt_pos.2 (pow_pos (mul_pos (by simp) pi_pos) _)),
    exact ne_of_gt (sqrt_pos.2 hR.det_pos),
    exact pow_nonneg (mul_nonneg (by simp) (le_of_lt pi_pos)) _,
    by linarith,
    exact sqrt_2_pi_n_R_det_ne_zero,
    refine div_ne_zero (by simp) sqrt_2_pi_n_R_det_ne_zero,
    exact exp_ne_zero _
end

lemma sum_quad_form {n : ℕ} (R : matrix (fin n) (fin n) ℝ) {m : ℕ} (y : fin m → fin n → ℝ) :
  (∑ i, R.quad_form (y i))
  = m * (covariance_matrix y ⬝ Rᵀ).trace :=
begin
  by_cases h : m = 0,
  { subst h, simp },
  simp only [matrix.quad_form, matrix.trace, covariance_matrix, diag, mul_apply, finset.sum_mul,
    finset.sum_div],
  simp_rw [@finset.sum_comm _ (fin m), finset.mul_sum],
  apply congr_arg,
  ext i,
  unfold dot_product,
  have : (m : ℝ) ≠ 0 := by simp [h],
  simp_rw [← mul_assoc, mul_div_cancel' _ this],
  apply congr_arg,
  ext j,
  simp_rw [mul_assoc, ← finset.mul_sum],
  apply congr_arg,
  unfold matrix.mul_vec,
  unfold dot_product,
  simp_rw [mul_comm],
  congr',
  ext j,
  rw [mul_comm, matrix.transpose]
end

lemma real.inverse_eq_inv (a : ℝ) : ring.inverse a = a⁻¹ :=
by simp


-- begin header
import limits

namespace M1P1
-- end header

/- Section
The sandwich theorem
-/

/- Sub-section
Introduction
-/

/-
The sandwich theorem, or squeeze theorem, for real sequences is the statement that if $(a_n)$, $(b_n)$, and $(c_n)$ are three real-valued sequences satisfying $a_n≤ b_n≤ c_n$ for all $n$, and if furthermore $a_n→ℓ$ and $c_n→ℓ$, then $b_n→ℓ$. The idea of the proof is straightforward -- if we want to ensure that $|b_n-ℓ|<ε$ then it suffices to show that $|a_n-ℓ|<ε$ and $|c_n-ℓ|<ε$, and we can choose $N$ large enough such that this is true for all $n≥ N$.
-/

/- Lemma
If $(a_n)$, $(b_n)$, and $(c_n)$ are three real-valued sequences satisfying $a_n ≤ b_n ≤ c_n$ for all $n$, and if furthermore $a_n→ℓ$ and $c_n→ℓ$, then $b_n→ℓ$.
-/
theorem sandwich (a b c : ℕ → ℝ)
  (l : ℝ) (ha : is_limit a l) (hc : is_limit c l)
  (hab : ∀ n, a n ≤ b n) (hbc : ∀ n, b n ≤ c n) : is_limit b l :=
begin
  -- We need to show that for all $ε>0$ there exists $N$ such that $n≥N$ implies $|b_n-ℓ|<ε$. So choose ε > 0.
  intros ε Hε,
  -- We now need an $N$. As usual it is the max of two other N's, one coming from $(a_n)$ and one from $(c_n)$. Choose $N_a$ and $N_c$ such that $|a_n - l| < ε$ for $n ≥ N_a$ and $|c_n - l| < ε$ for $n ≥ N_c$.
  cases ha ε Hε with Na Ha,
  cases hc ε Hε with Nc Hc,
  -- Now let $N$ be the max of $N_a$ and $N_c$; we claim that this works.
  let N := max Na Nc,
  use N,
  -- Note that $N ≥ N_a$ and $N ≥ N_c$,
  have HNa : Na ≤ N := by obvious_ineq,
  have HNc : Nc ≤ N := by obvious_ineq,
  -- so for all $n ≥ N$,
  intros n Hn,
  -- we have $a_n ≤ b_n ≤ c_n$, and $|a_n - l|, |b_n - l|$ are both less than $ε$.
  have h1 : a n ≤ b n := hab n,
  have h2 : b n ≤ c n := hbc n,
  have h3 : |a n - l| < ε := Ha n (le_trans HNa Hn),
  have h4 : |c n - l| < ε := Hc n (le_trans HNc Hn),
  -- The result now follows easily from these inequalities (as $l-ε < a_n ≤ b_n ≤ c_n < l+ε$ ).
  rw abs_sub_lt_iff at h3 h4 ⊢,
  cases h3, cases h4,
  split;linarith
end

/-
Note that Lean finds the last line of the proof automatically -- we do not need to explicitly tell it the inequality argument.
-/
end M1P1


/-
Copyright (c) 2022 Julian Kuelshammer. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Julian Kuelshammer
-/

import hard_mode.sheet08

/-

# The other way round, a function from an equivalence relation

On the previous sheet we saw that given a (surjective) function `f : X → Y` we can produce
an equivalence relation `∼` on `X` such that `x ≈ x'` iff `f (x) = f (x')`. On this sheet
we will see that Lean can go the other way round. For every type `X` and any
equivalence relation `≈` there exists a type `Y` and

- a surjective function `q : X → Y` such that
- `x ≈ x'` iff `q (x) = q (x')`.

There are different ways to construct such a `Y`, depending on the situation some are
easier than others. Lean (and that seems to be special compared to other interactive
theorem provers) has an inbuilt construction `quotient s` for a setoid `s` (recall that
a setoid is just a fancy way of saying a set together with an equivalence relation on it).
What is important here is not how Lean's choice of implementation of such a `Y` and `g`
is, but the API connected to it is.
-/

/- Let's set notation for our model of the integers as the quotient of the setoid s
  we defined on sheet07. -/

notation `myint` := quotient s

-- `setoid` is a class, so `s` should not just be a definition, we should
-- make it an instance by tagging it with the `instance` attribute
attribute [instance] s

-- Now we can use the notation `≈` for the equivalence relations `R`
-- and `⟦x⟧` for the element of `myint` corresponding to the pair `(a,b)` in `ℕ²`.

-- For a type `X` and a setoid structure `s` on `X` there is a function
-- `quotient.mk : X → quotient s`, the function `q` we were after. The brackets ⟦ ⟧ we
-- were using before are just notation for this function, i.e. `quotient.mk x = ⟦x⟧`.
-- Let's see that this is true by definition.

example (x : ℕ × ℕ) : quotient.mk x = ⟦x⟧ :=
begin
  sorry
end

-- Furthermore, `≈` is just notation for `R`
-- (this is handy to know; let's give it a name so we can rewrite it)
lemma equiv_def (r s : ℕ × ℕ) : r ≈ s ↔ r.1 + s.2 = s.1 + r.2 :=
begin
  sorry
end

-- One of our demands was that the function `q` is surjective. Let's try to prove that.
lemma q_surj : function.surjective (λ (r : ℕ × ℕ), quotient.mk r) :=
begin
  sorry
end

-- The second property we were after, namely that `x ≈ x'` iff `q(x) = q(x')` is called `quotient.eq`.

example (r s : ℕ × ℕ) : ⟦r⟧ = ⟦s⟧ ↔ r ≈ s :=
begin
  sorry -- so `rw quotient.eq` is often useful
end

-- Both implications also have names

example (r s : ℕ × ℕ) : ⟦r⟧ = ⟦s⟧ → r ≈ s :=
begin
  sorry
end

example (r s : ℕ × ℕ) : r ≈ s → ⟦r⟧ = ⟦s⟧ :=
begin
  sorry
end


import Mathlib

namespace Quadratic

/-Defining the two roots of quadratic equation-/
noncomputable def root₁ (a b c : ℝ) : ℝ :=
 (-b + Real.sqrt (b^2 - 4*a*c)) / (2*a)

/-Defining the two roots of quadratic equation-/
noncomputable def root₂(a b c : ℝ) (h:(b^2 - 4*a*c) ≥ 0): ℝ :=
 (-b - Real.sqrt (b^2 - 4*a*c)) / (2*a)

def two_mul_two (R : Type) [Ring R] : (4: R) = (2 : R) * (2: R) := by /-proof that 2(ℝ) * 2(ℝ) = 4(ℝ) -/
    have : (2: R) = (1: R) + (1: R) := by norm_cast
    rw [this, mul_add, mul_one]
    norm_cast
/-- Simplifying the term with power 1 of the root₁.
The simplification in RHS is done by hand (Expected).
In LHS theorems are used to simplify into the expected term.
For this lemma h' is required as there is a stage in simplification where 2*a is multiplied and divided, hence proved that given h', 2*a ≠ 0 -/
private lemma l₁ (a b c : ℝ) (h': a≠0): (b * ((-b + Real.sqrt (b * b - 4*a*c)) / (2 * a))) = ((-(2*a*b*b)/(4*a*a)) + ((2*a*b*(Real.sqrt (b * b - 4*a*c)))/(4*a*a))) := by

  have h' : 2*a ≠ 0 := by
    simp only [ne_eq, mul_eq_zero, OfNat.ofNat_ne_zero, h', or_self, not_false_iff]
  conv=>
    lhs
    simp only [add_div,mul_div,mul_add, add_mul]
    rw [←mul_div_mul_right (c:=2*a) (hc:=h')] /-divide and multiply by 2*a -/
    rw [←mul_div_mul_right (c:=2*a) (a:=b * Real.sqrt (b * b - 4 * a * c)) (b := 2*a) (hc:=h')] /-divide and multiply by 2*a -/
    simp [mul_left_comm (a:=b) (b:=2) (c:=a)]
    rw [mul_left_comm,mul_right_comm,mul_assoc]
    rw [←mul_left_comm (a:=a) (b:=b) (c:=b)]
    rw [mul_mul_mul_comm]
    rw [mul_rotate (a:=b) (b:=(Real.sqrt (b * b - 4 * a * c))) (c:=2*a)]
    rw [mul_rotate (a:=Real.sqrt (b * b - 4 * a * c)) (b:=2*a) (c:=b)]
    simp only [←mul_assoc]
    simp [←two_mul_two]

/--Simplifying the term with power 2 of root₁.
The simplification RHS is the expected simplification (done by hand).
So, using theorems and ring tactic (on LHS) it is proved that the term actually reduces to hand simplified term-/

private lemma l₂ (a b c : ℝ) (h:0 ≤ (b*b - 4*a*c)):
  a *((-b * -b + Real.sqrt (b * b - 4 * a * c) * -b +
        (-b * Real.sqrt (b * b - 4 * a * c) +
          Real.sqrt (b * b - 4 * a * c) * Real.sqrt (b * b - 4 * a * c))) /
              (2 * (2 * a * a))) =
                (2*a*b*b - 4*a*a*c - 2*a*b*Real.sqrt (b*b - 4*a*c))/(4*a*a) := by
                conv=>
                  lhs
                  rw [Real.mul_self_sqrt h]
                  simp only [mul_div,mul_add,mul_sub]
                  simp only [←mul_assoc]
                  simp [←two_mul_two]
                  rw [mul_right_comm (a:=a) (b:=Real.sqrt (b*b - 4*a*c) ) (c:=b)]
                  rw [←add_assoc]
                ring

/-- Proof that root₁ is actually the root of quadratic.
Essentially it is simplification using various theorems, and split into major 3 terms out of which two are bigger terms
and are simplified in private lemmas l₁ (specifically pass h:= a ≠ 0 as it involves multiplication and addition of 2*a) and l₂.
Once the major 2 terms are simplified, its rewritten and then ring_nf tactic is applied along with some more simplification theorems-/

theorem root₁_is_root (a b c : ℝ) (h:(b^2 - 4*a*c) ≥ 0) (h': a≠0): a*(root₁ a b c)^2 + b*(root₁ a b c) + c = 0 := by
  simp only [root₁, div_pow]
  simp only [pow_two, mul_left_comm, mul_add, add_mul] at h ⊢
  rw [l₁ (h':=h')]
  rw [l₂]
  ring_nf
  simp only [pow_two]
  simp [←mul_rotate (a:=a⁻¹*a⁻¹) (b:=a*a) (c:=c)]
  rw [mul_mul_mul_comm]
  rw[←one_div]
  rw [one_div_mul_cancel (h:=h')]
  rw [mul_one,one_mul]
  rw [neg_add_self]
  assumption

/-- Simplifying term with power 1 of root₂.
The RHS is expected simplification (Done by hand).
LHS indeed gets simplified to the expected term.
This lemma requires h' and also proof that 2*a ≠ 0, as it is being multiplied and divided by 2*a -/

private lemma l'₁ (a b c : ℝ) (h:(b*b - 4*a*c) ≥ 0) (h': a≠0): (b * ((-b - Real.sqrt (b * b - 4*a*c)) / (2 * a))) = ((-(2*a*b*b)/(4*a*a)) - ((2*a*b*(Real.sqrt (b * b - 4*a*c)))/(4*a*a))) := by

  have h' : 2*a ≠ 0 := by
    simp only [ne_eq, mul_eq_zero, OfNat.ofNat_ne_zero, h', or_self, not_false_iff]
  conv=>
    lhs
    simp only [sub_div,mul_div,mul_sub, add_mul]
    rw [←mul_div_mul_right (c:=2*a) (hc:=h')] /-divide and multiply by 2*a -/
    rw [←mul_div_mul_right (c:=2*a) (a:=b * Real.sqrt (b * b - 4 * a * c)) (b := 2*a) (hc:=h')] /-divide and multiply by 2*a -/
    simp [mul_left_comm (a:=b) (b:=2) (c:=a)]
    rw [mul_left_comm,mul_right_comm,mul_assoc]
    rw [←mul_left_comm (a:=a) (b:=b) (c:=b)]
    rw [mul_mul_mul_comm]
    rw [mul_rotate (a:=b) (b:=(Real.sqrt (b * b - 4 * a * c))) (c:=2*a)]
    rw [mul_rotate (a:=Real.sqrt (b * b - 4 * a * c)) (b:=2*a) (c:=b)]
    simp only [←mul_assoc]
    simp [←two_mul_two]

/-- Simplifying term with power 2 of root₂.
The RHS is the expected hand simplified term.
The LHS gets simplified to expected term-/

private lemma l'₂ (a b c : ℝ) (h:(b*b - 4*a*c) ≥ 0):
  a *((-b * -b - Real.sqrt (b * b - 4 * a * c) * -b -
          (-b * Real.sqrt (b * b - 4 * a * c) -
            Real.sqrt (b * b - 4 * a * c) * Real.sqrt (b * b - 4 * a * c))) /
                (2 * (2 * a * a))) =
                  (2*a*b*b - 4*a*a*c + 2*a*b*Real.sqrt (b*b - 4*a*c))/(4*a*a) := by
                    conv=>
                      lhs
                      rw [Real.mul_self_sqrt h]
                      simp only [mul_div,mul_add,mul_sub]
                      simp only [←mul_assoc]
                      simp [←two_mul_two]
                      rw [sub_eq_add_neg]
                      rw [neg_sub]
                      rw [sub_neg_eq_add]
                      rw [←add_assoc]
                      rw [add_rotate]
                      rw [add_rotate']
                      rw [add_rotate']
                      simp [←add_assoc]
                      rw [add_assoc]
                      rw[add_add_add_comm]
                      rw [← mul_rotate]
                      rw[mul_comm b a]
                      rw [←mul_two (n :=a * b * Real.sqrt (b * b - 4 * a * c))]
                      rw [sub_eq_add_neg (a * b * b)]
                      rw [← add_rotate]
                      rw [← mul_two]
                      rw [add_rotate']
                      rw[←add_assoc]
                      rw [←sub_eq_add_neg]
                      rw [mul_rotate (a:=a) (b:=4) (c:=a)]
                      rw [mul_rotate]
                      rw [mul_comm b 2,mul_mul_mul_comm]
                      rw [←mul_assoc]
                      rw [mul_comm (a * b * (Real.sqrt (b * b - 4 * a * c))) 2]
                      rw [←mul_assoc]
                      rw [←mul_assoc]

/-- Proof that root₂ is actually the root of quadratic.
Essentially it is simplification using various theorems, and split into major 3 terms out of which two are bigger terms
and are simplified in private lemmas l'₁ (specifically pass h:= a ≠ 0 as it involves multiplication and addition of 2*a) and l'₂.
Once the major 2 terms are simplified, its rewritten and then ring_nf tactic is applied along with some more simplification theorems-/

theorem root₂_is_root (a b c : ℝ) (h:(b^2 - 4*a*c) ≥ 0) (h': a≠0): a*(root₂ a b c h)^2 + b*(root₂ a b c h) + c = 0 := by
  simp only [root₂, div_pow]
  simp only [pow_two, mul_left_comm, mul_sub, sub_mul] at h ⊢
  rw [l'₁ (h':=h')]
  rw [l'₂]
  ring_nf
  simp only [pow_two]
  simp [←mul_rotate (a:=a⁻¹*a⁻¹) (b:=a*a) (c:=c)]
  rw [mul_mul_mul_comm]
  rw[←one_div]
  rw [one_div_mul_cancel (h:=h')]
  rw [mul_one,one_mul]
  rw [neg_add_self]
  assumption
  assumption

end Quadratic


-- Unión e intersección de familias de conjuntos
-- =============================================

-- ----------------------------------------------------
-- Ej. 1. Declarar I y U como variables de tipo.
-- ----------------------------------------------------

variables {I U : Type}

-- ----------------------------------------------------
-- Ej. 2. Definir la función
--    Union : (I → set U) → set U
-- tal que (Union A) es la unión de de los conjuntos de
-- la familia A.
-- ----------------------------------------------------

def Union (A : I → set U) : set U :=
  { x | ∃ i : I, x ∈ A i }

-- ----------------------------------------------------
-- Ej. 3. Definir la función
--    Inter : (I → set U) → set U
-- tal que (Inter A) es la intersección de de los
-- conjuntos de la familia A.
-- ----------------------------------------------------

def Inter (A : I → set U) : set U :=
  { x | ∀ i : I, x ∈ A i }

-- ----------------------------------------------------
-- Ej. 4. Declarar
-- + x como una variable sobre U y
-- + A como una variable sobre familas de conjuntos de
--   U con índice en A.
-- ----------------------------------------------------

variable x : U
variable (A : I → set U)

-- ----------------------------------------------------
-- Ej. 5. Demostrar que
--    x ∈ Union A ⊢ ∃ i, x ∈ A i
-- ----------------------------------------------------

example
  (h : x ∈ Union A)
  : ∃ i, x ∈ A i :=
h

-- ----------------------------------------------------
-- Ej. 6. Demostrar que
--    x ∈ x ∈ Inter A ⊢ ∀ i, x ∈ A i
-- ----------------------------------------------------

example
  (h : x ∈ Inter A)
  : ∀ i, x ∈ A i :=
h

-- ----------------------------------------------------
-- Ej 7. Usar (⋃ i, A i) como notación para (Union A).
-- ----------------------------------------------------

notation `⋃` binders `, ` r:(scoped f, Union f) := r

-- ----------------------------------------------------
-- Ej 8. Usar (⋂ i, A i) como notación para (Inter A).
-- ----------------------------------------------------

notation `⋂` binders `, ` r:(scoped f, Inter f) := r

-- ----------------------------------------------------
-- Ej. 9. Demostrar que
--    x ∈ ⋃ i, A i ⊢ ∃ i, x ∈ A i
-- ----------------------------------------------------

example
  (h : x ∈ ⋃ i, A i)
  : ∃ i, x ∈ A i :=
h

-- ----------------------------------------------------
-- Ej. 10. Demostrar que
--    x ∈ x ∈ ⋂ i, A i ⊢ ∀ i, x ∈ A i
-- ----------------------------------------------------

example
  (h : x ∈ ⋂ i, A i)
  : ∀ i, x ∈ A i :=
h


import MyNat.Addition -- imports addition.
namespace MyNat
open MyNat
/-!
# Addition World

## Level 3: `succ_add`

Oh no! On the way to `add_comm`, a wild `succ_add` appears. `succ_add`
is the proof that `(succ a) + b = succ (a + b)` for `a` and `b` in your
natural number type. You need to prove this now, because you will need
to use this result in our proof that `a + b = b + a` in the next level.

Think about why computer scientists called this result `succ_add` .
There is a logic to all the names.

Note that if you want to be more precise about exactly where you want
to rewrite something like `add_succ` (the proof you already have),
you can do things like `rw [add_succ (succ a)]` or
`[rw add_succ (succ a) d]`, telling Lean explicitly what to use for
the input variables for the function `add_succ`. Indeed, `add_succ`
is a function -- it takes as input two variables `a` and `b` and outputs a proof
that `a + succ b = succ (a + b)`. The tactic `rw [add_succ]` just says to Lean "guess
what the variables are".

**Lemma**

For all natural numbers `a, b`, we have ` (succ a) + b = succ (a + b). `
-/
lemma succ_add (a b : MyNat) : succ a + b = succ (a + b) := by
  induction b with
  | zero => rfl
  | succ b ih =>
    rw [add_succ]
    rw [ih]
    rw [add_succ]

/-!
On to [Level 4](./Level4.lean.md).
-/


/-
Copyright (c) 2020. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jalex Stark, Yury Kudryashov
-/
import data.real.basic

/-!
# Sum of the Reciprocals of the Triangular Numbers

This file proves Theorem 42 from the [100 Theorems List](https://www.cs.ru.nl/~freek/100/).

We interpret “triangular numbers” as naturals of the form $\frac{k(k+1)}{2}$ for natural `k`.
We prove that the sum of the reciprocals of the first `n` triangular numbers is $2 - \frac2n$.

## Tags

discrete_sum
-/

open_locale big_operators
open finset

/-- **Sum of the Reciprocals of the Triangular Numbers** -/
lemma inverse_triangle_sum :
  ∀ n, ∑ k in range n, (2 : ℚ) / (k * (k + 1)) = if n = 0 then 0 else 2 - (2 : ℚ) / n :=
begin
  refine sum_range_induction _ _ (if_pos rfl) _,
  rintro (_|n), { rw [if_neg, if_pos]; norm_num },
  simp_rw [if_neg (nat.succ_ne_zero _), nat.succ_eq_add_one],
  have A : (n + 1 + 1 : ℚ) ≠ 0, by { norm_cast, norm_num },
  push_cast,
  field_simp [nat.cast_add_one_ne_zero],
  ring
end


-- Si |x| ≤ ε, para todo ε > 0, entonces x = 0
-- ===========================================

import data.real.basic

variable (x : ℝ)

-- ----------------------------------------------------
-- Ejercicio 1. Definir la notación |x| para el valor
-- absoluto de x.
-- ----------------------------------------------------

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

-- ----------------------------------------------------
-- Ejercicio 2. Demostrar que si |x| ≤ ε, para todo
-- ε > 0, entonces x = 0
-- ----------------------------------------------------

-- 1ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense,
  { exact abs_nonneg x, },
  { intros ε hε,
    exact h ε hε, },
end

-- 2ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense,
  { exact abs_nonneg x, },
  { intros ε hε,
    exact h ε hε, },
end

-- 3ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense,
  { exact abs_nonneg x, },
  { exact λ ε hε, h ε hε, },
end

-- 4ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense
        (abs_nonneg x)
        h,
end

-- 5ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
abs_eq_zero.mp
  (eq_of_le_of_forall_le_of_dense (abs_nonneg x) h)

-- 6ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
have h2 : |x| = 0,
  from eq_of_le_of_forall_le_of_dense h1 h,
show x = 0,
  from abs_eq_zero.mp h2

-- 7ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
have h2 : |x| = 0,
  from eq_of_le_of_forall_le_of_dense h1 h,
abs_eq_zero.mp h2

-- 8ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
abs_eq_zero.mp (eq_of_le_of_forall_le_of_dense h1 h)

-- 9ª demostración
lemma cero_de_abs_mn_todos
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
abs_eq_zero.mp
  (eq_of_le_of_forall_le_of_dense (abs_nonneg x) h)


import chris_hughes_various.zmod data.fintype data.nat.prime data.nat.gcd data.nat.modeq algebra.ring M3P14.order_zmodn_kmb

open nat
open fintype

--defining the phi function

definition phi (n : ℕ) := ((finset.range n).filter (nat.coprime n)).card
local notation `φ`  := phi

-- theorems

theorem phi_n (n : ℕ) [pos_nat n] : phi n = card (units (zmod n)) := sorry

theorem phi_p (p : ℕ) (hp: prime p) : φ p = p-1 := sorry

theorem strong_mul (m n : ℕ) : φ(m*n) = (φ m) * (φ n) * (gcd m n / φ (gcd m n)) := sorry

theorem phi_mul (n m : ℕ) (hp: gcd n m = 1) : φ (n*m) = (φ n) * (φ m) :=
begin
    rw [nat.mul_comm, strong_mul],
    rw [nat.gcd_comm, hp],
    have h : φ 1 = 1, from dec_trivial,
    rw h,
    simp,
    rwa mul_comm,
end

theorem phi_odd_twice_eq_n (n : ℕ) (hp : gcd 2 n = 1) : φ (2*n) = φ n :=
begin
    rw phi_mul 2 n,
    rw phi_p 2,
    simp,
    unfold prime,
    split,
    exact dec_trivial,
    intros,
    rwa ← dvd_prime (prime_two),
    assumption,
end

theorem phi_even_twice_eq_twice_n (n : ℕ) (hp : gcd 2 n = 2) : φ (2*n) = 2 * φ n :=
begin
rw strong_mul 2 n,
rw hp,
rw phi_p 2,
simp,
rw mul_comm,
unfold prime,
split,
exact dec_trivial,
intros,
rwa ← dvd_prime (prime_two),
end

theorem power_p_phi (p k : ℕ) (hp: prime p) : φ (p^k) = p^(k-1)*(p-1) := sorry

theorem dvd_phi (m n : ℕ) (hp : m > 0) : (m ∣ n) → (φ m ∣ φ n) :=
begin
intros,
have eq_n_m : n = m * (n / m),
{
    rw ←nat.mul_div_assoc m a,
    rw mul_comm,
    rwa nat.mul_div_cancel,
},
have eq_2: φ m * φ (n / m) * (gcd m (n / m) / φ (gcd m (n / m))) = φ (m * (n / m)), from eq.symm (strong_mul m (n/m)),
have h5 : φ m ∣ φ m * (φ (n/m) * (gcd m (n/m) / φ (gcd m (n/m)))), from dvd_mul_right _ _,
have h6 : φ m * (φ (n/m) * (gcd m (n/m) / φ (gcd m (n/m)))) = φ m * φ (n / m) * (gcd m (n / m) / φ (gcd m (n / m))), by rw mul_assoc,
have h7 : φ m ∣ φ m * φ (n / m) * (gcd m (n / m) / φ (gcd m (n / m))), from eq.subst h6 h5,
have h8 : φ m ∣ φ (m * (n / m)), from eq.subst (strong_mul m (n/m)).symm h7,
exact eq.subst eq_n_m.symm h8,
end

theorem dvd_a_power (a n : ℕ) : n ∣ φ (a^n - 1) := sorry

theorem gcd_phi_eq_lcm_phi (m n d l : ℕ) (hp : d = gcd m n) (hq : l = lcm m n)  : φ l * φ d = φ m * φ n := sorry

theorem euler_phi_thm (a n : ℕ) (hp: gcd a n = 1) : a^(φ n) ≡ 1 [MOD n] := sorry

theorem flittlet (a p : ℕ) (hp : prime p) : a^(p-1) ≡ 1 [MOD p] := sorry

-- conjectures

theorem lehmers_conj (n : ℕ) (hp: ¬ (prime n)) : φ n ∣ n := sorry


/-
Copyright (c) 2023 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : María Inés de Frutos-Fernández
-/

import data.real.basic -- Importamos los números reales.

/-!
Algunos de estos ejercicios se han adaptado de la charla de estructuras y classes de LftCM 2022:
<https://icerm.brown.edu/video_archive/?play=2897>
-/

open nat

noncomputable theory

/-! # Estructuras

Una estructura es una colección de campos de datos, posiblemente con restricciones que dichos
datos deben satisfacer.

Una instancia de la estructura es una colección concreta de datos que satisfacen las restricciones.
-/

/-- Un elemento de ℝ³ es una tupla de tres números reales. -/
@[ext] structure R3 :=
(x : ℝ)
(y : ℝ)
(z : ℝ)

/- Marcar la definición con la etiqueta `@[ext]` hace que Lean genere teoremas para demostrar
que dos términos de `R3` son iguales cuando sus componentes lo son. También nos permitirá
utlizar la táctica `ext` en metas de ese tipo. -/
/- Cuál es el tipo de `R3`? -/
--#check R3

/- Hay varias formas de crear una instancia de `R3`? -/

/- Si no sabemos qué campos debemos proporcionar para crear una instancia de una
estructura, podemos reemplazar el sorry por `{!}`, pulsar en la bombilla, y seleccionar
la opción "Generate a skeleton for the structure under construction".-/
def R3_ex : R3 :=
sorry

-- Esta sintaxis también nos permite especificar los argumentos en otro orden.
--#check ({x := 1, z := 3,  y := 2,} : R3)

-- El constructor por defecto se llama `R3.mk`.
--#check R3.mk 1 2 3

-- Podemos utilizar la sintaxis de constructor anónimo.
--#check (⟨1, 2, 3⟩ : R3)

/- Dos instancias de `R3` son iguales cuando todas sus componentes coinciden. -/

example (a b : R3) (hx : a.x = b.x) (hy : a.y = b.y) (hz : a.z = b.z) : a = b :=
begin
  ext,
  exacts [hx, hy, hz], -- esta sintaxis nos permite cerrar las 3 metas a la vez.
end

/- Dada una instancia de `R3`, podemos acceder a cada una de las coordenadas: -/

def v : R3 := ⟨1, 2, 3⟩
--#check R3.x
--#check v.z

/- Las estructuras también pueden tener campos proposicionales. -/

@[ext] structure R3_plus :=
(x y z : ℝ)
(x_pos : x > 0)
(y_pos : y > 0)
(z_pos : z > 0)

--#check R3_plus.mk

/- Cuándo son dos términos de tipo `R3_plus` iguales? -/

example (a b : R3_plus) (hx : a.x = b.x) (hy : a.y = b.y) (hz : a.z = b.z) : a = b :=
begin
  ext,
  exacts [hx, hy, hz],
end

/- Por último, también es posible crear estructuras con parámetros. -/

/-- Definimos ℝ^n, donde `n` es un número natural -/
structure Rn (n : ℕ) :=
(coeff : fin n → ℝ)

--variables (n :  ℕ)
--#check Rn n

--#check Rn.mk
--#check Rn.mk (λ (k : fin 5), k)
--def ex : Rn 5 := ⟨(λ k, k)⟩

/-- La siguiente definición es distinta: incluye n-tuplas de números reales para cualquier `n`. -/
structure Rn' :=
(n : ℕ)
(coeff : fin n → ℝ)

--#check Rn'.mk 5 (λ k, k)

/-!
## Ejercicios de creación de estructuras
En los siguientes ejercicios, se pide crear una estructura en Lean para representar cada uno de
los objetos matemáticos descritos. Las soluciones propuestas no siempre son únicas, pregunta si
tienes dudas sobre si tu solución es correcta.
-/

/-
Crea un tipo `even_natural_number`, compuesto por pares cuya primera componente es un número
natural y la segunda una demostración de que el número es par.
-/
--structure even_natural_number :=

/- Define una estructura de secuencias `ℕ → ℕ` eventualmente constantes. El primer campo de la
estructura será una secuencia `seq : ℕ → ℕ`, y el segundo el enunciado de que `seq` satisface
la propiedad deseada. -/
--structure event_const_seq :=

/- Define una estructura cuyos términos sean dos números naturales distintos. -/
--structure two_distinct_points :=

/- Define una estructura de grupo multiplicativo sobre un tipo `G`. Para ello, necesitarás campos
de datos para indicar las operaciones y el elemento identidad, y campos proposicionales para
establecer los axiomas deseados.
-/
--structure Group (G : Type*) :=


import natural.definition natural.addition

namespace N

def mul : N -> N -> N
| a 0 := 0
| a (succ b) := (mul a b) + a

instance : has_mul N := ⟨ mul ⟩

lemma mul_zero (a : N) : a * 0 = 0 := rfl
lemma mul_succ (a b : N) : a * (succ b) = a * b + a := rfl

lemma zero_mul (a : N) : 0 * a = 0 :=
begin
  induction a with d hd,
  rw zero_eq_zero,
  rw mul_zero,
  rw mul_succ,
  rw add_zero,
  rw hd,
end

lemma succ_mul (a b : N) : (succ a) * b = a * b + b :=
begin
  induction b with d hd,
  rw zero_eq_zero,
  rw mul_zero,
  rw mul_zero,
  rw add_zero,
  rw mul_succ,
  rw hd,
  rw mul_succ,
  repeat {rw add_succ},
  rw add_assoc,
  rw add_comm d a,
  rw ← add_assoc,
end

theorem mul_left_distrib_add (a b c : N) : a * (b + c) = a * b + a * c :=
begin
  induction a with d hd,
  rw zero_eq_zero,
  repeat {rw zero_mul},
  rw add_zero,
  repeat {rw succ_mul},
  rw hd,
  rw add_assoc (d * b) b (d * c + c),
  rw ← add_assoc b (d * c) c,
  rw add_comm b (d * c),
  repeat {rw ← add_assoc},
end

theorem mul_assoc (a b c : N) : (a * b) * c = a * (b * c) :=
begin
  induction c with d hd,
  rw zero_eq_zero,
  repeat {rw mul_zero},
  rw mul_succ,
  rw hd,
  rw mul_succ,
  rw mul_left_distrib_add,
end

theorem mul_comm (a b : N) : a * b = b * a :=
begin
  induction b with d hd,
  rw zero_eq_zero,
  rw mul_zero,
  rw zero_mul,
  rw mul_succ,
  rw succ_mul,
  rw hd,
end

theorem mul_right_distrib_add (a b c : N) : (a + b) * c = a * c + b * c :=
begin
  rw mul_comm (a + b),
  rw mul_left_distrib_add,
  rw mul_comm c a,
  rw mul_comm c b,
end

lemma mul_one (a : N) : a * 1 = a :=
begin
  rw ← one_eq_one,
  rw one,
  rw mul_succ,
  rw mul_zero,
  rw zero_add,
end

lemma one_mul (a : N) : 1 * a = a :=
begin
  rw mul_comm,
  rw mul_one,
end

lemma mul_two (a : N) : a * 2 = a + a :=
begin
  rw ← two_eq_two,
  rw two,
  rw mul_succ,
  rw mul_one,
end

lemma two_mul (a : N) : 2 * a = a + a :=
begin
  rw mul_comm,
  rw mul_two,
end

theorem mul_one_idemp (a : N) : a * 1 * 1 = a * 1 :=
begin
  rw mul_one,
end

end N


import ..lectures.love05_inductive_predicates_demo

/-! # LoVe Exercise 5: Inductive Predicates -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Even and Odd

The `even` predicate is true for even numbers and false for odd numbers. -/

#check even

/-! We define `odd` as the negation of `even`: -/

def odd (n : ℕ) : Prop :=
  ¬ even n

/-! 1.1. Prove that 1 is odd and register this fact as a simp rule.

Hint: `cases'` is useful to reason about hypotheses of the form `even …`. -/

@[simp] lemma odd_1 :
  odd 1 :=
sorry

/-! 1.2. Prove that 3 and 5 are odd. -/

-- enter your answer here

/-! 1.3. Complete the following proof by structural induction. -/

lemma even_two_times :
  ∀m : ℕ, even (2 * m)
| 0       := even.zero
| (m + 1) :=
  sorry

/-! 1.4. Complete the following proof by rule induction.

Hint: You can use the `cases'` tactic (or `match … with`) to destruct an
existential quantifier and extract the witness. -/

lemma even_imp_exists_two_times :
  ∀n : ℕ, even n → ∃m, n = 2 * m :=
begin
  intros n hen,
  induction' hen,
  case zero {
    apply exists.intro 0,
    refl },
  case add_two : k hek ih {
    sorry }
end

/-! 1.5. Using `even_two_times` and `even_imp_exists_two_times`, prove the
following equivalence. -/

lemma even_iff_exists_two_times (n : ℕ) :
  even n ↔ ∃m, n = 2 * m :=
sorry

/-! 1.6 (**optional**). Give a structurally recursive definition of `even` and
test it with `#eval`.

Hint: The negation operator on `bool` is called `not`. -/

def even_rec : nat → bool :=
sorry

/-! ## Question 2: Tennis Games

Recall the inductive type of tennis scores from the demo: -/

#check score

/-! 2.1. Define an inductive predicate that returns true if the server is ahead
of the receiver and that returns false otherwise. -/

inductive srv_ahead : score → Prop
-- enter the missing cases here

/-! 2.2. Validate your predicate definition by proving the following lemmas. -/

lemma srv_ahead_vs {m n : ℕ} (hgt : m > n) :
  srv_ahead (score.vs m n) :=
sorry

lemma srv_ahead_adv_srv :
  srv_ahead score.adv_srv :=
sorry

lemma not_srv_ahead_adv_rcv :
  ¬ srv_ahead score.adv_rcv :=
sorry

lemma srv_ahead_game_srv :
  srv_ahead score.game_srv :=
sorry

lemma not_srv_ahead_game_rcv :
  ¬ srv_ahead score.game_rcv :=
sorry

/-! 2.3. Compare the above lemma statements with your definition. What do you
observe? -/

-- enter your answer here

/-! ## Question 3: Binary Trees

3.1. Prove the converse of `is_full_mirror`. You may exploit already proved
lemmas (e.g., `is_full_mirror`, `mirror_mirror`). -/

#check is_full_mirror
#check mirror_mirror

lemma mirror_is_full {α : Type} :
  ∀t : btree α, is_full (mirror t) → is_full t :=
sorry

/-! 3.2. Define a `map` function on binary trees, similar to `list.map`. -/

def map_btree {α β : Type} (f : α → β) : btree α → btree β
| _ := sorry   -- remove this dummy case and enter the missing cases

/-! 3.3. Prove the following lemma by case distinction. -/

lemma map_btree_eq_empty_iff {α β : Type} (f : α → β) :
  ∀t : btree α, map_btree f t = btree.empty ↔ t = btree.empty :=
sorry

/-! 3.4 (**optional**). Prove the following lemma by rule induction. -/

lemma map_btree_mirror {α β : Type} (f : α → β) :
  ∀t : btree α, is_full t → is_full (map_btree f t) :=
sorry

end LoVe


/-
    Stalk (of types) on basis.

    https://stacks.math.columbia.edu/tag/009H
-/

import to_mathlib.opens
import sheaves.presheaf_on_basis

open topological_space

universe u

variables {α : Type u} [topological_space α]
variables {B : set (opens α )} {HB : opens.is_basis B}
variables (F : presheaf_on_basis α HB) (x : α)

structure stalk_on_basis.elem :=
(U  : opens α)
(BU : U ∈ B)
(Hx : x ∈ U)
(s  : F BU)

-- Equivalence relation on the set of pairs. (U,s) ~ (V,t) iff there exists W
-- open s.t. x ∈ W ⊆ U ∩ V, and s|W = t|W.

def stalk_on_basis.relation : stalk_on_basis.elem F x → stalk_on_basis.elem F x → Prop :=
λ Us Vt,
    ∃ W (BW : W ∈ B) (HxW : x ∈ W) (HWU : W ⊆ Us.U) (HWV : W ⊆ Vt.U),
    F.res Us.BU BW HWU Us.s = F.res Vt.BU BW HWV Vt.s

lemma stalk_on_basis.relation.reflexive : reflexive (stalk_on_basis.relation F x) :=
λ ⟨U, OU, HxU, s⟩, ⟨U, OU, HxU, set.subset.refl _, set.subset.refl _, rfl⟩

lemma stalk_on_basis.relation.symmetric : symmetric (stalk_on_basis.relation F x) :=
λ Us Vt ⟨W, OW, HxW, HWU, HWV, Hres⟩, ⟨W, OW, HxW, HWV, HWU, Hres.symm⟩

lemma stalk_on_basis.relation.transitive : transitive (stalk_on_basis.relation F x) :=
λ ⟨U, BU, HxU, sU⟩ ⟨V, BV, HxV, sV⟩ ⟨W, BW, HxW, sW⟩,
λ ⟨R, BR, HxR, HRU, HRV, HresR⟩ ⟨S, BS, HxS, HSV, HSW, HresS⟩,
have HxRS : x ∈ R ∩ S := ⟨HxR, HxS⟩,
let ⟨T, BT, HxT, HTRS⟩ := opens.is_basis_iff_nbhd.1 HB HxRS in
⟨T, BT, HxT, λ y Hy, HRU (HTRS Hy).1, λ y Hy, HSW (HTRS Hy).2,
have HTR : T ⊆ R := λ y Hy, (HTRS Hy).1,
have HTS : T ⊆ S := λ y Hy, (HTRS Hy).2,
have HURRS : _ := F.Hcomp BU BR BT HTR HRU,
have HVRRS : _ := F.Hcomp BV BR BT HTR HRV,
have HVSRS : _ := F.Hcomp BV BS BT HTS HSV,
have HWSRS : _ := F.Hcomp BW BS BT HTS HSW,
calc  F.res BU BT _ sU
    = F.res BR BT _ (F.res BU BR _ sU) : congr_fun HURRS sU
... = F.res BR BT _ (F.res BV BR _ sV) : congr_arg _ HresR
... = F.res BV BT _ sV                 : congr_fun HVRRS.symm sV
... = F.res BS BT _ (F.res BV BS _ sV) : congr_fun HVSRS sV
... = F.res BS BT _ (F.res BW BS _ sW) : congr_arg _ HresS
... = F.res BW BT _ sW                 : congr_fun HWSRS.symm sW⟩

lemma stalk_on_basis.relation.equivalence : equivalence (stalk_on_basis.relation F x) :=
⟨stalk_on_basis.relation.reflexive F x,
stalk_on_basis.relation.symmetric F x,
stalk_on_basis.relation.transitive F x⟩

instance stalk_on_basis.setoid : setoid (stalk_on_basis.elem F x) :=
{ r := stalk_on_basis.relation F x,
  iseqv := stalk_on_basis.relation.equivalence F x }

-- We define a stalk as the set of stalk elements under the defined relation.

definition stalk_on_basis := quotient (stalk_on_basis.setoid F x)


theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
begin
  apply and.intro hp,
  exact and.intro hq hp
end

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

#print test

example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
  apply iff.intro,
  intro h,
  apply or.elim (and.right h),
    intro hq,
    apply or.inl,
    apply and.intro,
      exact h.left,
    exact hq,
   intro hr,
   apply or.inr,
   apply and.intro,
     exact h.left,
     exact hr,
   intro h,
   apply or.elim h,
     intro hpq,
     apply and.intro,
     exact hpq.left,
     apply or.inl,
     exact hpq.right,
   intro hpr,
   apply and.intro,
     exact hpr.left,
   apply or.inr,
   exact hpr.right
end

example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
  intros a b c h1 h2,
  exact eq.trans (eq.symm h2) h1,
end

variables x y z w : ℕ
example (h1 : x = y) (h2 : y = z) (h3 : z = w) : x = w :=
begin
  apply eq.trans h1,
  apply eq.trans h2,
  assumption
end

example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
  intros,
  transitivity a,
  symmetry,
  repeat { assumption},
end

example : ∃ a : ℕ, 5 = a :=
begin
  apply exists.intro,
  reflexivity,
end

example : ∃ a : ℕ, a = a :=
begin
  fapply exists.intro,
  exact 0,
  reflexivity
end

example (x : ℕ) : x = x :=
begin
  revert x,
  intro y,
  refl,
end

example (p q : Prop) : p ∨ q → q ∨ p :=
begin
  intro h,
  cases h with hp hq,
  right, exact hp,
  left, exact hq,
end

example (p q : Prop) : p ∧ q → q ∧ p :=
begin
  intro h,
  cases h with hp hq,
  constructor, exact hq, exact hp,
end

example (p q : ℕ → Prop) : (∃ x, p x) → ∃ x, p x ∨ q x :=
begin
  intro h,
  cases h with x px,
  constructor, left, exact px,
end

universes u v
def swap_pair {α : Type u} {β : Type v} : α × β → β × α :=
begin
  intro p,
  cases p with ha hb,
  constructor, exact hb, exact ha
end

def swap_sum {α : Type u} {β : Type v} : α ⊕ β → β ⊕ α :=
begin
  intro p,
  cases p with ha hb,
  right, exact ha,
  left, exact hb
end

open nat
example (P : ℕ → Prop) (h0 : P 0) (h1 : ∀ n, P (succ n)) (m : ℕ) :
  P m :=
begin
  cases m with m', exact h0,
  exact h1 m',
end

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


import data.nat.prime
import algebra.big_operators
import tactic

open_locale big_operators

/- TEXT:
.. _section_infinitely_many_primes:

Infinitely Many Primes
----------------------

Let us continue our exploration of induction and recursion with another
mathematical standard: a proof that there are infinitely many primes.
One way to formulate this is as the statement that
for every natural number
:math:`n`, there is a prime number greater than :math:`n`.
To prove this, let :math:`p` be any prime factor of :math:`n! + 1`.
If :math:`p` is less than :math:`n`, it divides :math:`n!`.
Since it also divides :math:`n! + 1`, it divides 1, a contradiction.
Hence :math:`p` is greater than :math:`n`.

To formalize that proof, we need to show that any number greater than or equal
to 2 has a prime factor.
To do that, we will need to show that any natural number that is
not equal to 0 or 1 is greater-than or equal to 2.
And this brings us to a quirky feature of formalization:
it is often trivial statements like this that are among the most
annoying to formalize.
Here we consider a few ways to do it.

To start with, we can use the ``cases`` tactic and the fact that the
successor function respects the ordering on the natural numbers.
BOTH: -/
-- QUOTE:
theorem two_le {m : ℕ} (h0 : m ≠ 0) (h1 : m ≠ 1) : 2 ≤ m :=
begin
  cases m, contradiction,
  cases m, contradiction,
  repeat { apply nat.succ_le_succ },
  apply zero_le
end
-- QUOTE.

/- TEXT:
Another strategy is to use the tactic ``interval_cases``,
which automatically splits the goal into cases when
the variable in question is contained in an interval
of natural numbers or integers.
Remember that you can hover over it to see its documentation.
EXAMPLES: -/
-- QUOTE:
example {m : ℕ} (h0 : m ≠ 0) (h1 : m ≠ 1) : 2 ≤ m :=
begin
  by_contradiction h,
  push_neg at h,
  interval_cases m; contradiction
end
-- QUOTE.

/- TEXT:
Recall that the semicolon after ``interval_cases m`` means
that the next tactic is applied to each of the cases that it generates.
Yet another option is to use the tactic, ``dec_trivial``, which tries
to find a decision procedure to solve the problem.
Lean knows that you can decide the truth value of a statement that
begins with a bounded quantifier ``∀ x, x < n → ...`` or ``∃ x, x < n ∧ ...``
by deciding each of the finitely many instances.
EXAMPLES: -/
-- QUOTE:
example {m : ℕ} (h0 : m ≠ 0) (h1 : m ≠ 1) : 2 ≤ m :=
begin
  by_contradiction h,
  push_neg at h,
  revert m h h0 h1,
  dec_trivial
end
-- QUOTE.

/- TEXT:
In fact, the variant ``dec_trivial!`` will revert all the hypotheses
that contain a variable that is found in the target.
EXAMPLES: -/
-- QUOTE:
example {m : ℕ} (h : m < 2) : m = 0 ∨ m = 1 :=
by dec_trivial!
-- QUOTE.

/- TEXT:
Finally, in this case we can use the ``omega`` tactic, which is designed
to reason about linear expressions in the natural numbers.
EXAMPLES: -/
-- QUOTE:
example {m : ℕ} (h0 : m ≠ 0) (h1 : m ≠ 1) : 2 ≤ m :=
by omega
-- QUOTE.

/- TEXT:
With the theorem ``two_le`` in hand, let's start by showing that every
natural number greater than two has a prime divisor.
Mathlib contains a function ``nat.min_fac`` that
returns the smallest prime divisor,
but for the sake of learning new parts of the library,
we'll avoid using it and prove the theorem directly.

Here, ordinary induction isn't enough.
We want to use *strong induction*, which allows us to prove
that every natural number :math:`n` has a property :math:`P`
by showing that for every number :math:`n`, if :math:`P` holds
of all values less than :math:`n`, it holds at :math:`n` as well.
In Lean, this principle is called ``nat.strong_induction_on``,
and we can use the ``with`` keyword to tell the induction tactic
to use it.
Notice that when we do that, there is no base case; it is subsumed
by the general induction step.

The argument is simply as follows. Assuming :math:`n ≥ 2`,
if :math:`n` is prime, we're done. If it isn't,
then by one of the characterizations of what it means to be a prime number,
it has a nontrivial factor, :math:`m`,
and we can apply the inductive hypothesis to that.
Step through the next proof to see how that plays out.
The line ``dsimp at ih`` simplifies the expression of the
inductive hypothesis to make it more readable.
The proof still works if you delete that line.
BOTH: -/
-- QUOTE:
theorem exists_prime_factor {n : nat} (h : 2 ≤ n) :
  ∃ p : nat, p.prime ∧ p ∣ n :=
begin
  by_cases np : n.prime,
  { use [n, np, dvd_rfl] },
  induction n using nat.strong_induction_on with n ih,
  dsimp at ih,
  rw nat.prime_def_lt at np,
  push_neg at np,
  rcases np h with ⟨m, mltn, mdvdn, mne1⟩,
  have : m ≠ 0,
  { intro mz,
    rw [mz, zero_dvd_iff] at mdvdn,
    linarith },
  have mgt2 : 2 ≤ m := two_le this mne1,
  by_cases mp : m.prime,
  { use [m, mp, mdvdn] },
  rcases ih m mltn mgt2 mp with ⟨p, pp, pdvd⟩,
  use [p, pp, pdvd.trans mdvdn]
end
-- QUOTE.

/- TEXT:
We can now prove the following formulation of our theorem.
See if you can fill out the sketch.
You can use ``nat.factorial_pos``, ``nat.dvd_factorial``,
and ``nat.dvd_sub``.
BOTH: -/
-- QUOTE:
theorem primes_infinite : ∀ n, ∃ p > n, nat.prime p :=
begin
  intro n,
  have : 2 ≤ nat.factorial (n + 1) + 1,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { apply nat.succ_le_succ,
    exact nat.succ_le_of_lt (nat.factorial_pos _) },
-- BOTH:
  rcases exists_prime_factor this with ⟨p, pp, pdvd⟩,
  refine ⟨p, _, pp⟩,
  show p > n,
  by_contradiction ple, push_neg at ple,
  have : p ∣ nat.factorial (n + 1),
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { apply nat.dvd_factorial,
    apply pp.pos,
    linarith },
-- BOTH:
  have : p ∣ 1,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { convert nat.dvd_sub' pdvd this, simp },
-- BOTH:
  show false,
/- EXAMPLES:
    sorry
SOLUTIONS: -/
  have := nat.le_of_dvd zero_lt_one this,
  linarith [pp.two_le]
-- BOTH:
end
-- QUOTE.

/- TEXT:
Let's consider a variation of the proof above, where instead
of using the factorial function,
we suppose that we are given by a finite set
:math:`\{ p_1, \ldots, p_n \}` and we consider a prime factor of
:math:`\prod_{i = 1}^n p_i + 1`.
That prime factor has to be distinct from each
:math:`p_i`, showing that there is no finite set that contains
all the prime numbers.

Formalizing this argument requires us to reason about finite
sets. In Lean, for any type ``α``, the type ``finset α``
represents finite sets of elements of type ``α``.
Reasoning about finite sets computationally requires having
a procedure to test equality on ``α``, which is why the snippet
below includes the assumption ``[decidable_eq α]``.
For concrete data types like ``ℕ``, ``ℤ``, and ``ℚ``,
the assumption is satisfied automatically. When reasoning about
the real numbers, it can be satisfied using classical logic
and abandoning the computational interpretation.

We use the command ``open finset`` to avail ourselves of shorter names
for the relevant theorems. Unlike the case with sets,
most equivalences involving finsets do not hold definitionally,
so they need to be expanded manually using equivalances like
``finset.subset_iff``, ``finset.mem_union``, ``finset.mem_inter``,
and ``finset.mem_sdiff``. The ``ext`` tactic can still be used
to reduce show that two finite sets are equal by showing
that every element of one is an element of the other.
BOTH: -/
-- QUOTE:
open finset

-- EXAMPLES:
section
variables {α : Type*} [decidable_eq α] (r s t : finset α)

example : r ∩ (s ∪ t) ⊆ (r ∩ s) ∪ (r ∩ t) :=
begin
  rw subset_iff,
  intro x,
  rw [mem_inter, mem_union, mem_union, mem_inter, mem_inter],
  tauto
end

example : r ∩ (s ∪ t) ⊆ (r ∩ s) ∪ (r ∩ t) :=
by { simp [subset_iff], intro x, tauto }

example : (r ∩ s) ∪ (r ∩ t) ⊆ r ∩ (s ∪ t) :=
by { simp [subset_iff], intro x, tauto }

example : (r ∩ s) ∪ (r ∩ t) = r ∩ (s ∪ t) :=
by { ext x, simp, tauto }

end
-- QUOTE.

/- TEXT:
We have used a new trick: the ``tauto`` tactic (and a strengthened
version, ``tauto!``, which uses classical logic) can be used to
dispense with propositional tautologies. See if you can use
these methods to prove the two examples below.
BOTH: -/
section
variables {α : Type*} [decidable_eq α] (r s t : finset α)

-- QUOTE:
example : (r ∪ s) ∩ (r ∪ t) = r ∪ (s ∩ t) :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  ext x,
  rw [mem_inter, mem_union, mem_union, mem_union, mem_inter],
  tauto
end

example : (r ∪ s) ∩ (r ∪ t) = r ∪ (s ∩ t) :=
by { ext x, simp, tauto }
-- BOTH:

example : (r \ s \ t) = r \ (s ∪ t) :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  ext x,
  rw [mem_sdiff, mem_sdiff, mem_sdiff, mem_union],
  tauto
end

example : (r \ s \ t) = r \ (s ∪ t) :=
by { ext x, simp, tauto }
-- QUOTE.
-- BOTH:

end
/- TEXT:
The theorem ``finset.dvd_prod_of_mem`` tells us that if an
``n`` is an element of a finite set ``s``, then ``n`` divides
``∏ i in s, i``.
EXAMPLES: -/
-- QUOTE:
example (s : finset ℕ) (n : ℕ) (h : n ∈ s) : n ∣ (∏ i in s, i) :=
finset.dvd_prod_of_mem _ h
-- QUOTE.

/- TEXT:
We also need to know that the converse holds in the case where
``n`` is prime and ``s`` is a set of primes.
To show that, we need the following lemma, which you should
be able to prove using the theorem ``nat.prime.eq_one_or_self_of_dvd``.
BOTH: -/
-- QUOTE:
theorem nat.prime.eq_of_dvd_of_prime {p q : ℕ}
    (prime_p : nat.prime p) (prime_q : nat.prime q) (h : p ∣ q) :
  p = q :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  cases prime_q.eq_one_or_self_of_dvd _ h,
  { linarith [prime_p.two_le] },
  assumption
end
-- QUOTE.
-- BOTH:

/- TEXT:
We can use this lemma to show that if a prime ``p`` divides a product of a finite
set of primes, then it divides one of them.
Mathlib provides a useful principle of induction on finite sets:
to show that a property holds of an arbitrary finite set ``s``,
show that it holds of the empty set, and show that it is preserved
when we add a single new element ``a ∉ s``.
The principle is known as ``finset.induction_on``.
When we tell the induction tactic to use it, we can also specify the names
``a`` and ``s``, the name for the assumption ``a ∉ s`` in the inductive step,
and the name of the inductive hypothesis.
The expression ``finset.insert a s`` denotes the union of ``s`` with the singleton ``a``.
The identities ``finset.prod_empty`` and ``finset.prod_insert`` then provide
the relevant rewrite rules for the product.
In the proof below, the first ``simp`` applies ``finset.prod_empty``.
Step through the beginning of the proof to see the induction unfold,
and then finish it off.
BOTH: -/
-- QUOTE:
theorem mem_of_dvd_prod_primes {s : finset ℕ} {p : ℕ} (prime_p : p.prime) :
  (∀ n ∈ s, nat.prime n) →  (p ∣ ∏ n in s, n) → p ∈ s :=
begin
  intros h₀ h₁,
  induction s using finset.induction_on with a s ans ih,
  { simp at h₁,
    linarith [prime_p.two_le] },
  simp [finset.prod_insert ans, prime_p.dvd_mul] at h₀ h₁,
  rw mem_insert,
/- EXAMPLES:
  sorry
SOLUTIONS: -/
  cases h₁ with h₁ h₁,
  { left, exact prime_p.eq_of_dvd_of_prime h₀.1 h₁ },
  right,
  exact ih h₀.2 h₁
-- BOTH:
end
-- QUOTE.

/- TEXT:
We need one last property of finite sets.
Given an element ``s : set α`` and a predicate
``P`` on ``α``, in  :numref:`Chapter %s <sets_and_functions>`
we wrote ``{ x ∈ s | P x }`` for the set of
elements of ``s`` that satisfy ``P``.
Given ``s : finset α``,
the analogous notion is written ``s.filter P``.
EXAMPLES: -/
-- QUOTE:
example (s : finset ℕ) (x : ℕ) : x ∈ s.filter nat.prime ↔ x ∈ s ∧ x.prime :=
mem_filter
-- QUOTE.

/- TEXT:
We now prove an alternative formulation of the statement that there are infinitely many
primes, namely, that given any ``s : finset ℕ``, there is a prime ``p`` that is not
an element of ``s``.
Aiming for a contradiction, we assume that all the primes are in ``s``, and then
cut down to a set ``s'`` that contains all and only the primes.
Taking the product of that set, adding one, and finding a prime factor
of the result
leads to the contradiction we are looking for.
See if you can complete the sketch below.
You can use ``finset.prod_pos`` in the proof of the first ``have``.
BOTH: -/
-- QUOTE:
theorem primes_infinite' : ∀ (s : finset nat), ∃ p, nat.prime p ∧ p ∉ s :=
begin
  intro s,
  by_contradiction h,
  push_neg at h,
  set s' := s.filter nat.prime with s'_def,
  have mem_s' : ∀ {n : ℕ}, n ∈ s' ↔ n.prime,
  { intro n,
    simp [s'_def],
    apply h },
  have : 2 ≤ (∏ i in s', i) + 1,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { apply nat.succ_le_succ,
    apply nat.succ_le_of_lt,
    apply finset.prod_pos,
    intros n ns',
    apply (mem_s'.mp ns').pos },
-- BOTH:
  rcases exists_prime_factor this with ⟨p, pp, pdvd⟩,
  have : p ∣ (∏ i in s', i),
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { apply dvd_prod_of_mem,
    rw mem_s',
    apply pp },
-- BOTH:
  have : p ∣ 1,
  { convert nat.dvd_sub' pdvd this, simp },
  show false,
/- EXAMPLES:
    sorry
SOLUTIONS: -/
  have := nat.le_of_dvd zero_lt_one this,
  linarith [pp.two_le]
-- BOTH:
end
-- QUOTE.

/- TEXT:
We have thus seen two ways of saying that there are infinitely many primes:
saying that they are not bounded by any ``n``, and saying that they are
not contained in any finite set ``s``.
The two proofs below show that these formulations are equivalent.
In the second, in order to form ``s.filter Q``, we have to assume that there
is a procedure for deciding whether or not ``Q`` holds. Lean knows that there
is a procedure for ``nat.prime``. In general, if we use classical logic
by writing ``open_locale classical``,
we can dispense with the assumption.

In mathlib, ``finset.sup s f`` denotes the supremum of the values of ``f x`` as ``x``
ranges over ``s``, returning ``0`` in the case where ``s`` is empty and
the codomain of ``f`` is ``ℕ``. In the first proof, we use ``s.sup id``,
where ``id`` is the identity function, to refer to the maximum value in ``s``.
BOTH: -/
-- QUOTE:
theorem bounded_of_ex_finset (Q : ℕ → Prop):
  (∃ s : finset ℕ, ∀ k, Q k → k ∈ s) → ∃ n, ∀ k, Q k → k < n :=
begin
  rintros ⟨s, hs⟩,
  use s.sup id + 1,
  intros k Qk,
  apply nat.lt_succ_of_le,
  show id k ≤ s.sup id,
  apply le_sup (hs k Qk)
end

theorem ex_finset_of_bounded (Q : ℕ → Prop) [decidable_pred Q] :
  (∃ n, ∀ k, Q k → k ≤ n) → (∃ s : finset ℕ, ∀ k, Q k ↔ k ∈ s) :=
begin
  rintros ⟨n, hn⟩,
  use (range (n + 1)).filter Q,
  intro k,
  simp [nat.lt_succ_iff],
  exact hn k
end
-- QUOTE.

/- TEXT:
A small variation on our second proof that there are infinitely many primes
shows that there are infinitely many primes congruent to 3 modulo 4.
The argument goes as follows.
First, notice that if the product of two numbers :math:`m` and :math:`n`
is equal to 3 modulo 4, then one of the two numbers is congruent to three modulo 4.
After all, both have to be odd, and if they are both congruent to 1 modulo 4,
so is their product.
We can use this observation to show that if some number
greater than 2 is congruent to 3 modulo 4,
then that number has a prime divisor that is also congruent to 3 modulo 4.

Now suppose there are only finitely many prime numbers congruent to 3
modulo 4, say, :math:`p_1, \ldots, p_k`.
Without loss of generality, we can assume that :math:`p_1 = 3`.
Consider the product :math:`4 \prod_{i = 2}^k p_i + 3`.
It is easy to see that this is congruent to 3 modulo 4, so it has
a prime factor :math:`p` congruent to 3 modulo 4.
It can't be the case that :math:`p = 3`; since :math:`p`
divides :math:`4 \prod_{i = 2}^k p_i + 3`, if :math:`p`
were equal to 3 then it would also divide :math:`\prod_{i = 2}^k p_i`,
which implies that :math:`p` is equal to
one of the :math:`p_i` for :math:`i = 2, \ldots, k`;
and we have excluded 3 from this list.
So :math:`p` has to be one of the other elements :math:`p_i`.
But in that case, :math:`p` divides :math:`4 \prod_{i = 2}^k p_i`
and hence 3, which contradicts the fact that it is not 3.

In Lean, the notation ``n % m``, read "``n`` modulo ``m``,"
denotes the remainder of the division of ``n`` by ``m``.
EXAMPLES: -/
-- QUOTE:
example : 27 % 4 = 3 := by norm_num
-- QUOTE.

/- TEXT:
We can then render the statment "``n`` is congruent to 3 modulo 4"
as ``n % 4 = 3``. The following example and theorems sum up
the facts about this function that we will need to use below.
The first named theorem is another illustration of reasoning by
a small number of cases.
In the second named theorem, remember that the semicolon means that
the subsequent tactic block is applied to both of the goals
that result from the application of ``two_le``.
EXAMPLES: -/
-- QUOTE:
example (n : ℕ) : (4 * n + 3) % 4 = 3 :=
by { rw [add_comm, nat.add_mul_mod_self_left], norm_num }

-- BOTH:
theorem mod_4_eq_3_or_mod_4_eq_3 {m n : ℕ} (h : m * n % 4 = 3) :
  m % 4 = 3 ∨ n % 4 = 3 :=
begin
  revert h,
  rw [nat.mul_mod],
  have : m % 4 < 4 := nat.mod_lt m (by norm_num),
  interval_cases m % 4 with hm; simp [hm],
  have : n % 4 < 4 := nat.mod_lt n (by norm_num),
  interval_cases n % 4 with hn; simp [hn]; norm_num
end

theorem two_le_of_mod_4_eq_3 {n : ℕ} (h : n % 4 = 3) : 2 ≤ n :=
by apply two_le; { intro neq, rw neq at h, norm_num at h }
-- QUOTE.

/- TEXT:
We will also need the following fact, which says that if
``m`` is a nontrivial divisor of ``n``, then so is ``n / m``.
See if you can complete the proof using ``nat.div_dvd_of_dvd``
and ``nat.div_lt_self``.
BOTH: -/
-- QUOTE:
theorem aux {m n : ℕ} (h₀ : m ∣ n) (h₁ : 2 ≤ m) (h₂ : m < n) :
  (n / m) ∣ n ∧ n / m < n :=
/- EXAMPLES:
sorry
SOLUTIONS: -/
begin
  split,
  { exact nat.div_dvd_of_dvd h₀ },
  exact nat.div_lt_self (lt_of_le_of_lt (zero_le _) h₂) h₁
end
-- QUOTE.
-- BOTH:

/- TEXT:
Now put all the pieces together to prove that any
number congruent to 3 modulo 4 has a prime divisor with that
same property.
BOTH: -/
-- QUOTE:
theorem exists_prime_factor_mod_4_eq_3 {n : nat} (h : n % 4 = 3) :
  ∃ p : nat, p.prime ∧ p ∣ n ∧ p % 4 = 3 :=
begin
  by_cases np : n.prime,
  { use [n, np, dvd_rfl, h] },
  induction n using nat.strong_induction_on with n ih,
  dsimp at ih,
  rw nat.prime_def_lt at np,
  push_neg at np,
  rcases np (two_le_of_mod_4_eq_3 h) with ⟨m, mltn, mdvdn, mne1⟩,
  have mge2 : 2 ≤ m,
  { apply two_le _ mne1,
    intro mz,
    rw [mz, zero_dvd_iff] at mdvdn, linarith },
  have neq : m * (n / m) = n := nat.mul_div_cancel' mdvdn,
  have : m % 4 = 3 ∨ (n / m) % 4 = 3,
  { apply mod_4_eq_3_or_mod_4_eq_3, rw [neq, h] },
  cases this with h1 h1,
/- EXAMPLES:
  { sorry },
  sorry
SOLUTIONS: -/
  { by_cases mp : m.prime,
    { use [m, mp, mdvdn, h1] },
    rcases ih m mltn h1 mp with ⟨p, pp, pdvd, p4eq⟩,
    use [p, pp, pdvd.trans mdvdn, p4eq] },
  obtain ⟨nmdvdn, nmltn⟩ := aux mdvdn mge2 mltn,
  by_cases nmp : (n / m).prime,
    { use [n / m, nmp, nmdvdn, h1] },
    rcases ih (n / m) nmltn h1 nmp with ⟨p, pp, pdvd, p4eq⟩,
    use [p, pp, pdvd.trans nmdvdn, p4eq]
-- BOTH:
end
-- QUOTE.

/- TEXT:
We are in the home stretch. Given a set ``s`` of prime
numbers, we need to talk about the result of removing 3 from that
set, if it is present. The function ``finset.erase`` handles that.
EXAMPLES: -/
-- QUOTE:
example (m n : ℕ) (s : finset ℕ) (h : m ∈ erase s n) : m ≠ n ∧ m ∈ s :=
by rwa mem_erase at h

example (m n : ℕ) (s : finset ℕ) (h : m ∈ erase s n) : m ≠ n ∧ m ∈ s :=
by { simp at h, assumption }
-- QUOTE.

/- TEXT:
We are now ready to prove that there are infinitely many primes
congruent to 3 modulo 4.
Fill in the missing parts below.
Our solution uses ``nat.dvd_add_iff_left`` and ``nat.dvd_sub'``
along the way.
BOTH: -/
-- QUOTE:
theorem primes_mod_4_eq_3_infinite : ∀ n, ∃ p > n, nat.prime p ∧ p % 4 = 3 :=
begin
  by_contradiction h,
  push_neg at h,
  cases h with n hn,
  have : ∃ s : finset nat, ∀ p : ℕ, p.prime ∧ p % 4 = 3 ↔ p ∈ s,
  { apply ex_finset_of_bounded,
    use n,
    contrapose! hn,
    rcases hn with ⟨p, ⟨pp, p4⟩, pltn⟩,
    exact ⟨p, pltn, pp, p4⟩ },
  cases this with s hs,
  have h₀ : 2 ≤ 4 * (∏ i in erase s 3, i) + 3,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { apply le_add_left, norm_num },
-- BOTH:
  have h₁ : (4 * (∏ i in erase s 3, i) + 3) % 4 = 3,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { rw [add_comm, nat.add_mul_mod_self_left], norm_num },
-- BOTH:
  rcases exists_prime_factor_mod_4_eq_3 h₁ with ⟨p, pp, pdvd, p4eq⟩,
  have ps : p ∈ s,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { rw ←hs p, exact ⟨pp, p4eq⟩ },
-- BOTH:
  have pne3 : p ≠ 3,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { intro peq,
    rw [peq, ←nat.dvd_add_iff_left (dvd_refl 3)] at pdvd,
    rw nat.prime_three.dvd_mul at pdvd,
    norm_num at pdvd,
    have : 3 ∈ s.erase 3,
    { apply mem_of_dvd_prod_primes nat.prime_three _ pdvd,
      intro n, simp [← hs n], tauto },
    simp at this,
    exact this },
-- BOTH:
  have : p ∣ 4 * (∏ i in erase s 3, i),
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { apply dvd_trans _ (dvd_mul_left _ _),
    apply dvd_prod_of_mem,
    simp, split; assumption },
-- BOTH:
  have : p ∣ 3,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { convert nat.dvd_sub' pdvd this, simp },
-- BOTH:
  have : p = 3,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { apply pp.eq_of_dvd_of_prime nat.prime_three this },
-- BOTH:
  contradiction
end
-- QUOTE.

/- TEXT:
If you managed to complete the proof, congratulations! This has been a serious
feat of formalization.
TEXT. -/

-- OMIT:

/-
Later:
o fibonacci numbers
o binomial coefficients

(The former is a good example of having more than one base case.)

TODO: mention ``local attribute`` at some point.
-/


import .finset.to_set .finite data.set
open nat classical

namespace set

variable {A : Type}

noncomputable definition card (s : set A) := finset.card (set.to_finset s)

theorem card_to_set (s : finset A) : card (finset.to_set s) = finset.card s :=
by simp [card, (to_finset_to_set s)]

theorem card_of_not_finite {s : set A} (nfins : ¬ finite s) : card s = 0 :=
by simp [card, (to_finset_of_not_finite nfins)]; apply finset.card_empty

theorem card_empty : card (∅ : set A) = 0 :=
by rw [-finset.to_set_empty, card_to_set]; apply finset.card_empty

theorem card_insert_of_mem {a : A} {s : set A} (H : a ∈ s) : card (insert a s) = card s :=
if fins : finite s then
by simp [card]; rw to_finset_insert; rw -mem_to_finset_eq at H; rw [finset.card_insert_of_mem H]
else
  (have ¬ finite (insert a s), from suppose _, absurd (finite_of_finite_insert this) fins,
by rw [card_of_not_finite fins, card_of_not_finite this])

theorem card_insert_of_not_mem {a : A} {s : set A} [finite s] (H : ¬ a ∈ s) :
  card (insert a s) = card s + 1 :=
begin
simp [card]; rw [to_finset_insert, finset.card_insert_of_not_mem], simp,
rw -mem_to_finset_eq at H, exact H
end

theorem card_insert_le (a : A) (s : set A) [finite s] :
  card (insert a s) ≤ card s + 1 :=
if H : a ∈ s then by rewrite [card_insert_of_mem H]; apply le_succ
else by rewrite [card_insert_of_not_mem H]

theorem card_singleton (a : A) : card (insert a (∅:set A)) = 1 :=
have ¬ a ∈ ∅, from not_mem_empty _,
begin rw [card_insert_of_not_mem this, card_empty] end

-- Note : have to provide the predicate explicitly.

theorem eq_empty_of_card_eq_zero {s : set A} [finite s] : card s = 0 → s = ∅ :=
@induction_on_finite _ (λ x, card x = 0 → x = ∅) s _
  (by intro H; exact rfl)
  (begin
    intros a s' fins' anins IH H,
    rewrite (card_insert_of_not_mem anins) at H,
    contradiction
  end)

theorem card_upto (n : ℕ) : card {i | i < n} = n :=
by simp [card]; rw [to_finset_upto, finset.card_upto]

-- theorem card_add_card (s₁ s₂ : set A) [finite s₁] [finite s₂] :
--   card s₁ + card s₂ = card (s₁ ∪ s₂) + card (s₁ ∩ s₂) :=
-- begin
--   rw [-to_set_to_finset s₁, -to_set_to_finset s₂],
--   rw [-finset.to_set_union, -finset.to_set_inter],
--   repeat {rw card_to_set},
--   -- apply finset.card_add_card
-- end

end set


-- Suma_con_no_negativo_es_mayor_o_igual.lean
-- Suma con no negativo es mayor o igual
-- José A. Alonso Jiménez
-- Sevilla, 14 de agosto de 2020
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar si a y b son números naturales y a es no
-- negativo, entonces b ≤ a + b
-- ----------------------------------------------------------------------

import data.real.basic
variables {a b : ℝ}

-- 1ª demostración
-- ===============

example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
begin
  calc b = 0 + b : by rw zero_add
     ... ≤ a + b : by exact add_le_add_right ha b,
end

-- Comentario: Se ha usado el lema
-- + zero_add : 0 + a = a

-- 2ª demostración
-- ===============

example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
begin
  calc b = 0 + b : (zero_add b).symm
     ... ≤ a + b : add_le_add_right ha b,
end

-- 3ª demostración
-- ===============

example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
begin
  calc b = 0 + b : by ring
     ... ≤ a + b : by exact add_le_add_right ha b,
end

-- 4ª demostración
-- ===============

example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
by simp [ha]

-- 5ª demostración
-- ===============

example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
by linarith

-- 6ª demostración
-- ===============

example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
by finish

-- 7ª demostración
-- ===============

example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
le_add_of_nonneg_left ha

-- Comentario: Se ha usado el lema
-- + le_add_of_nonneg_left : 0 ≤ b → a ≤ b + a


structure vector (R : Type) :=
vec :: (x : R) (y : R) (z : R)

namespace vector

variables {R : Type} [comm_ring R]

def zero : vector R := vec 0 0 0

def add (a b : vector R) :=
vec (a.x + b.x) (a.y + b.y) (a.z + b.z)

def neg (a : vector R) :=
vec (-a.x) (-a.y) (-a.z)

def cross_prod (a b : vector R) :=
vec (a.y * b.z - a.z * b.y) (a.x * b.z - a.z * b.x) (a.x * b.y - a.y * b.x)

def inner_prod (a b : vector R) :=
a.x * b.x + a.y * b.y +a.z * b.z

def scalar (n : R) (a : vector R) :=
vec (n * a.x) (n * a.y) (n * a.z)

theorem vector.ext (α : Type) (a b c d e f : α) :
a = d → b = e → c = f → vec a b c = vec d e f :=
begin
  intro Had, intro Hbe, intro Hcf,
  rw [Had,Hbe,Hcf]
end

theorem vector.add_comm :
 ∀ (a b : vector R) ,
 add a b = add b a :=
begin
  intro a , intro b ,
  unfold add,
  simp
end

theorem vector.add_asso :
 ∀ (a b c : vector R) ,
 add a (add b c) = add (add a b) c :=
begin
  intro a , intro b , intro c ,
  unfold add,
  simp
end

theorem vector.inner_prod_comm :
 ∀ (a b : vector R) ,
 inner_prod a b = inner_prod b a :=
begin
  intro a ,
  intro b ,
  unfold inner_prod,
  rw [mul_comm, add_assoc, add_comm],
  rw [mul_comm, add_assoc, add_comm],
  rw mul_comm, simp
end

theorem vector.zero_zero :
 ∀ (a : vector R) ,
 add zero a = add a zero :=
begin
  intro a,
  unfold add, unfold zero,
  simp,
end

theorem vector.zero_add :
 ∀ (a : vector R) ,
 add zero a = a :=
begin
  intro a,
  unfold add, unfold zero,
  cases a with a.x a.y a.z,
  dsimp, congr,
  rw zero_add, rw zero_add, rw zero_add
end

theorem vector.add_zero :
 ∀ (a : vector R) ,
 add a zero = a :=
begin
  intro a,
  unfold add, unfold zero,
  cases a with a.x a.y a.z,
  dsimp, simp
end

theorem vector.vec_assoc :
 ∀ (a b : vector R) (n : R) ,
 scalar n (add a b) = add (scalar n a) (scalar n b) :=
begin
  intro a, intro b, intro n,
  unfold add, unfold scalar,
  dsimp, congr,
  rw mul_add, rw mul_add, rw mul_add
end

theorem vector.num_assoc :
 ∀ (a : vector R) (m n : R) ,
 scalar (m + n) a = add (scalar m a) (scalar n a) :=
begin
  intro a, intro m, intro n,
  unfold add, unfold scalar,
  dsimp, congr,
  rw add_mul, rw add_mul, rw add_mul
end

theorem vector.cross_pord_comm :
 ∀ (a b : vector R) ,
 neg (cross_prod b a) = cross_prod a b :=
begin
  intro a, intro b,
  unfold neg, unfold cross_prod,
  dsimp, congr, simp,
  rw [mul_comm, add_comm, mul_comm], simp,
  rw [mul_comm, add_comm, mul_comm], simp,
  rw [mul_comm, add_comm, mul_comm], simp,
end

theorem mul_comm1 :
 ∀ (a b c : R) ,
 a * b * c = c * a * b :=
begin
 intro a, intro b, intro c,
 rw [mul_comm, ← mul_assoc]
end

theorem mul_comm2 :
 ∀ (a b c : R) ,
 a * b * c = b * c *a :=
begin
 intro a, intro b, intro c,
 rw [mul_assoc, mul_comm]
end

theorem Jacobian_Identity :
  ∀ (a b c : vector R) ,
 add (cross_prod a (cross_prod b c)) (add (cross_prod b (cross_prod c a)) (cross_prod c (cross_prod a b))) = zero :=
begin
  intro a, intro b, intro c,
  unfold add, unfold cross_prod, unfold zero,
  dsimp, congr,
  rw [mul_add, mul_add, mul_add, mul_add, mul_add, mul_add],
  rw mul_comm, simp, rw mul_comm, simp, rw mul_comm, simp,
  rw mul_comm, simp, rw mul_comm, simp, rw mul_comm, simp,
  rw [mul_comm1, add_comm, add_assoc],
  rw [mul_comm1, add_comm, add_assoc, add_assoc],
  rw [mul_comm1, add_comm, add_assoc, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc],
  rw [mul_comm, add_comm], simp,
  rw [mul_add, mul_add, mul_add, mul_add, mul_add, mul_add],
  rw mul_comm, simp, rw mul_comm, simp, rw mul_comm, simp,
  rw mul_comm, simp, rw mul_comm, simp, rw mul_comm, simp,
  rw [mul_comm2, add_comm, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc],
  rw [mul_comm, add_comm], simp,
  rw [mul_add, mul_add, mul_add, mul_add, mul_add, mul_add],
  rw mul_comm, simp, rw mul_comm, simp, rw mul_comm, simp,
  rw mul_comm, simp, rw mul_comm, simp, rw mul_comm, simp,
  rw [mul_comm2, add_comm, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm2, add_comm, add_assoc, add_assoc],
  rw [mul_comm1, add_comm, add_assoc, add_assoc],
  rw [mul_comm1, add_comm, add_assoc, add_assoc],
  rw [mul_comm1, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc, add_assoc],
  rw [mul_comm, add_comm, add_assoc],
  rw [mul_comm, add_comm], simp,
end

end vector


import tactic
import data.int.basic

-- a rw puzzle?
example
  (p : ℕ → Prop) (n : ℕ) (hn : p n)
  (h8 : ∀ n, p n ↔ p (n + 8))
  (h3 : ∀ n, p (n + 3) ↔ p n) :
p (n + 1) :=
begin
  rw ← h3 at hn,
  rw ← h3 at hn,
  rw ← h3 at hn,
  rw h8,
  exact hn,
end

--
example : fin 0 ≠ fin 1 :=
begin
  have : ∃ b : fin 1, true,
  use 0,
  intro h,
  rw ← h at this,
  cases this with k hk,
  cases k, linarith,
end

-- when i started i thought this would be a rw puzzle, but it's not
theorem reflexive_of_symmetric_and_transitive (r : ℕ → ℕ → Prop)
  (h_symm : symmetric r) (h_trans : transitive r)
  (h_connected : ∀ x, ∃ y, r x y) :
reflexive r :=
begin
  intro x,
  have hxy := h_connected x,
  cases hxy with y hy,
  apply h_trans hy,
  apply h_symm hy,
end

lemma even_or_odd (n : ℕ) :
(∃ k, n = 2 * k) ∨ ∃ k, n = 2 * k + 1 :=
begin
  induction n with d hd,
  { use 0, simp },
  -- you should ~always do this rw after opening the successor case of induction on the naturals
  rw nat.succ_eq_add_one,
  cases hd with h_even h_odd,
  { cases h_even with k hk,
    right, use k, rw hk },
  cases h_odd with k hk,
  left, use k+1, rw hk, ring,
end

-- I don't think I can do this without coercions
example
  (p : ℤ → Prop)
  (p_succ : ∀ n, p n → p (n + 1))
  (p_pred : ∀ n, p n → p (n - 1)) :
(∀ n, p n) ↔ p 0 :=
begin
  have key1 : ∀ n, p n ↔ p (n+1),
  { intro, split; intro h,
    { apply p_succ, assumption },
    have := p_pred (n+1),
    ring at this, apply this, assumption },
  split; intro h, { apply h },
  intro n, cases n, -- rintro ⟨n⟩ also works
  { induction n with d hd, {simpa},
    rw nat.succ_eq_add_one,
    simp only [int.coe_nat_succ, int.of_nat_eq_coe],
    rwa ← key1 },
  induction n with d hd, { rw key1, simpa },
  rw nat.succ_eq_add_one,
  rw key1, simpa,
end

-- by landing in ℤ, we avoid the perils of nat subtraction
def f : ℕ → ℕ
| 0 := 0
| (n + 1) := n + 1 + f n

example : f 1 = 1 :=
begin
  unfold f,
end

example (n : ℕ) : 2 * f n = n * (n + 1) :=
begin
  induction n with d hd,
  -- base case
  { unfold f, simp },
  rw nat.succ_eq_add_one,
  unfold f, ring,
  rw hd, ring,
end

open_locale big_operators
open finset
variables {R : Type*} [comm_ring R]

example (n : ℕ) (a : R) :
(1 - a) * ∑ k in range n, a ^ k = 1 - a ^ n :=
begin
  rw mul_sum, apply sum_range_induction,
  { simp },
  intro, ring,
end

-- doing your induction "by hand"
example (n : ℕ) (a : R) :
(1 - a) * ∑ k in range n, a^k = 1 - a ^n :=
begin
  induction n with d hd,
  { simp },
  rw [sum_range_succ, mul_add, hd, nat.succ_eq_add_one],
  ring_exp,
end


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import topology.uniform_space.completion
import topology.metric_space.isometry
import topology.instances.real

/-!
# The completion of a metric space

Completion of uniform spaces are already defined in `topology.uniform_space.completion`. We show
here that the uniform space completion of a metric space inherits a metric space structure,
by extending the distance to the completion and checking that it is indeed a distance, and that
it defines the same uniformity as the already defined uniform structure on the completion
-/

open set filter uniform_space uniform_space.completion
open_locale filter
noncomputable theory

universes u
variables {α : Type u} [pseudo_metric_space α]

namespace metric

/-- The distance on the completion is obtained by extending the distance on the original space,
by uniform continuity. -/
instance : has_dist (completion α) :=
⟨completion.extension₂ dist⟩

/-- The new distance is uniformly continuous. -/
protected lemma completion.uniform_continuous_dist :
  uniform_continuous (λp:completion α × completion α, dist p.1 p.2) :=
uniform_continuous_extension₂ dist

/-- The new distance is an extension of the original distance. -/
protected lemma completion.dist_eq (x y : α) : dist (x : completion α) y = dist x y :=
completion.extension₂_coe_coe uniform_continuous_dist _ _

/- Let us check that the new distance satisfies the axioms of a distance, by starting from the
properties on α and extending them to `completion α` by continuity. -/
protected lemma completion.dist_self (x : completion α) : dist x x = 0 :=
begin
  apply induction_on x,
  { refine is_closed_eq _ continuous_const,
    exact (completion.uniform_continuous_dist.continuous.comp
             (continuous.prod_mk continuous_id continuous_id : _) : _) },
  { assume a,
    rw [completion.dist_eq, dist_self] }
end

protected lemma completion.dist_comm (x y : completion α) : dist x y = dist y x :=
begin
  apply induction_on₂ x y,
  { refine is_closed_eq completion.uniform_continuous_dist.continuous _,
    exact completion.uniform_continuous_dist.continuous.comp
      (@continuous_swap (completion α) (completion α) _ _) },
  { assume a b,
    rw [completion.dist_eq, completion.dist_eq, dist_comm] }
end

protected lemma completion.dist_triangle (x y z : completion α) : dist x z ≤ dist x y + dist y z :=
begin
  apply induction_on₃ x y z,
  { refine is_closed_le _ (continuous.add _ _),
    { have : continuous (λp : completion α × completion α × completion α, (p.1, p.2.2)) :=
        continuous.prod_mk continuous_fst (continuous.comp continuous_snd continuous_snd),
      exact (completion.uniform_continuous_dist.continuous.comp this : _) },
    { have : continuous (λp : completion α × completion α × completion α, (p.1, p.2.1)) :=
        continuous.prod_mk continuous_fst (continuous_fst.comp continuous_snd),
      exact (completion.uniform_continuous_dist.continuous.comp this : _) },
    { have : continuous (λp : completion α × completion α × completion α, (p.2.1, p.2.2)) :=
        continuous.prod_mk (continuous_fst.comp continuous_snd)
                           (continuous.comp continuous_snd continuous_snd),
      exact (continuous.comp completion.uniform_continuous_dist.continuous this : _) } },
  { assume a b c,
    rw [completion.dist_eq, completion.dist_eq, completion.dist_eq],
    exact dist_triangle a b c }
end

/-- Elements of the uniformity (defined generally for completions) can be characterized in terms
of the distance. -/
protected lemma completion.mem_uniformity_dist (s : set (completion α × completion α)) :
  s ∈ uniformity (completion α) ↔ (∃ε>0, ∀{a b}, dist a b < ε → (a, b) ∈ s) :=
begin
  split,
  { /- Start from an entourage `s`. It contains a closed entourage `t`. Its pullback in α is an
    entourage, so it contains an ε-neighborhood of the diagonal by definition of the entourages
    in metric spaces. Then `t` contains an ε-neighborhood of the diagonal in `completion α`, as
    closed properties pass to the completion. -/
    assume hs,
    rcases mem_uniformity_is_closed hs with ⟨t, ht, ⟨tclosed, ts⟩⟩,
    have A : {x : α × α | (coe (x.1), coe (x.2)) ∈ t} ∈ uniformity α :=
      uniform_continuous_def.1 (uniform_continuous_coe α) t ht,
    rcases mem_uniformity_dist.1 A with ⟨ε, εpos, hε⟩,
    refine ⟨ε, εpos, λx y hxy, _⟩,
    have : ε ≤ dist x y ∨ (x, y) ∈ t,
    { apply induction_on₂ x y,
      { have : {x : completion α × completion α | ε ≤ dist (x.fst) (x.snd) ∨ (x.fst, x.snd) ∈ t}
               = {p : completion α × completion α | ε ≤ dist p.1 p.2} ∪ t, by ext; simp,
        rw this,
        apply is_closed.union _ tclosed,
        exact is_closed_le continuous_const completion.uniform_continuous_dist.continuous },
      { assume x y,
        rw completion.dist_eq,
        by_cases h : ε ≤ dist x y,
        { exact or.inl h },
        { have Z := hε (not_le.1 h),
          simp only [set.mem_set_of_eq] at Z,
          exact or.inr Z }}},
    simp only [not_le.mpr hxy, false_or, not_le] at this,
    exact ts this },
  { /- Start from a set `s` containing an ε-neighborhood of the diagonal in `completion α`. To show
    that it is an entourage, we use the fact that `dist` is uniformly continuous on
    `completion α × completion α` (this is a general property of the extension of uniformly
    continuous functions). Therefore, the preimage of the ε-neighborhood of the diagonal in ℝ
    is an entourage in `completion α × completion α`. Massaging this property, it follows that
    the ε-neighborhood of the diagonal is an entourage in `completion α`, and therefore this is
    also the case of `s`. -/
    rintros ⟨ε, εpos, hε⟩,
    let r : set (ℝ × ℝ) := {p | dist p.1 p.2 < ε},
    have : r ∈ uniformity ℝ := metric.dist_mem_uniformity εpos,
    have T := uniform_continuous_def.1 (@completion.uniform_continuous_dist α _) r this,
    simp only [uniformity_prod_eq_prod, mem_prod_iff, exists_prop,
               filter.mem_map, set.mem_set_of_eq] at T,
    rcases T with ⟨t1, ht1, t2, ht2, ht⟩,
    refine mem_of_superset ht1 _,
    have A : ∀a b : completion α, (a, b) ∈ t1 → dist a b < ε,
    { assume a b hab,
      have : ((a, b), (a, a)) ∈ set.prod t1 t2 := ⟨hab, refl_mem_uniformity ht2⟩,
      have I := ht this,
      simp [completion.dist_self, real.dist_eq, completion.dist_comm] at I,
      exact lt_of_le_of_lt (le_abs_self _) I },
    show t1 ⊆ s,
    { rintros ⟨a, b⟩ hp,
      have : dist a b < ε := A a b hp,
      exact hε this }}
end

/-- If two points are at distance 0, then they coincide. -/
protected lemma completion.eq_of_dist_eq_zero (x y : completion α) (h : dist x y = 0) : x = y :=
begin
  /- This follows from the separation of `completion α` and from the description of
  entourages in terms of the distance. -/
  have : separated_space (completion α) := by apply_instance,
  refine separated_def.1 this x y (λs hs, _),
  rcases (completion.mem_uniformity_dist s).1 hs with ⟨ε, εpos, hε⟩,
  rw ← h at εpos,
  exact hε εpos
end

/-- Reformulate `completion.mem_uniformity_dist` in terms that are suitable for the definition
of the metric space structure. -/
protected lemma completion.uniformity_dist' :
  uniformity (completion α) = (⨅ε:{ε : ℝ // 0 < ε}, 𝓟 {p | dist p.1 p.2 < ε.val}) :=
begin
  ext s, rw mem_infi_of_directed,
  { simp [completion.mem_uniformity_dist, subset_def] },
  { rintro ⟨r, hr⟩ ⟨p, hp⟩, use ⟨min r p, lt_min hr hp⟩,
    simp [lt_min_iff, (≥)] {contextual := tt} }
end

protected lemma completion.uniformity_dist :
  uniformity (completion α) = (⨅ ε>0, 𝓟 {p | dist p.1 p.2 < ε}) :=
by simpa [infi_subtype] using @completion.uniformity_dist' α _

/-- Metric space structure on the completion of a pseudo_metric space. -/
instance completion.metric_space : metric_space (completion α) :=
{ dist_self          := completion.dist_self,
  eq_of_dist_eq_zero := completion.eq_of_dist_eq_zero,
  dist_comm          := completion.dist_comm,
  dist_triangle      := completion.dist_triangle,
  to_uniform_space   := by apply_instance,
  uniformity_dist    := completion.uniformity_dist }

/-- The embedding of a metric space in its completion is an isometry. -/
lemma completion.coe_isometry : isometry (coe : α → completion α) :=
isometry_emetric_iff_metric.2 completion.dist_eq

end metric