section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
intro p, -- Suponha P
intro np, -- Suponha ¬P
contradiction, -- Contradição
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro p, -- Suponha ¬¬p
  by_cases h : P, -- "Aplicando feitiço" LEM
  assumption,
  contradiction,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
  intro p,
  by_cases h : P,
  assumption,
  contradiction,
  intro q,
  intro q1,
  contradiction,
end

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

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro hp,
  cases hp,
  right,
  assumption,
  left,
  assumption,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro hp,
  split,
  cases hp,
  assumption,
  cases hp,
  assumption,
end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro hp,
  cases hp,
  intro hhp,
  contradiction,
  intro hhp,
  assumption,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro hp,
  cases hp,
  intro hhp,
  contradiction,
  intro hhp,
  assumption,
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro hp,
  intro hhp,
  intro hhhp,
  have hq : Q := hp hhhp,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro h,
  intro hp,
  by_contradiction hboom,
  have np : ¬P := h hboom,
  contradiction,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
  intro piq,
  intro nq,
  intro p,
  have q : Q := piq p,
  contradiction,
  intro nqinp,
  intro p,
  by_contradiction hboom,
  have np : ¬P := nqinp hboom,
  contradiction,
end

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro n_pounp,
  apply n_pounp,
  left,
  by_contradiction hboom,
  apply n_pounp,
  right,
  assumption,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro piqip,
  by_cases npoup : P,
  intro np,
  contradiction,
  intro np,
  apply np,
  apply piqip,
  intro p,
  contradiction,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro pouq,
  cases pouq,
  intro npenq,
  cases npenq,
  contradiction,
  intro npenq,
  cases npenq,
  contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro peq,
  cases peq,
  intro npounq,
  cases npounq,
  contradiction,
  contradiction,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_pouq,
  split,
  intro p,
  apply n_pouq,
  left,
  by_contradiction hboom,
  contradiction,
  intro nq,
  apply n_pouq,
  right,
  by_contradiction,
  contradiction,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro npenq,
  intro n_pouq,
  cases n_pouq,
  cases npenq,
  contradiction,
  cases npenq,
  contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_peq,
  by_cases h : P,
  left,
  intro p,
  apply n_peq,
  split,
  assumption,
  assumption,
  right,
  assumption,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro nqounp,
  intro n_peq,
  cases n_peq,
  cases nqounp,
  contradiction,
  contradiction,
end

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

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
  intro n_pouq,
  split,
  intro p,
  apply n_pouq,
  left,
  assumption,
  intro q,
  apply n_pouq,
  right,
  assumption,
  intro npenq,
  intro pouq,
  cases npenq,
  cases pouq,
  contradiction,
  contradiction,
end

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro pe_qour,
  cases pe_qour,
  cases pe_qour_right,
  left,
  split,
  assumption,
  assumption,
  right,
  split,
  assumption,
  assumption,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro peq_ou_per,
  cases peq_ou_per,
  cases peq_ou_per,
  split,
  assumption,
  left,
  assumption,
  cases peq_ou_per,
  split,
  assumption,
  right,
  assumption,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro pou_qer,
  cases pou_qer,
  split,
  left,
  assumption,
  left,
  assumption,
  cases pou_qer,
  split,
  right,
  assumption,
  right,
  assumption,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro pouq_e_pour,
  cases pouq_e_pour,
  cases pouq_e_pour_left,
  left,
  assumption,
  cases pouq_e_pour_right,
  left,
  assumption,
  right,
  split,
  assumption,
  assumption,

end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro peqir,
  intro p,
  intro q,
  apply peqir,
  split,
  assumption,
  assumption,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro pi_qir,
  intro peq,
  cases peq,
  apply pi_qir,
  assumption,
  assumption,
end

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

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

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

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

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

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro peq,
  cases peq,
  assumption,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro peq,
  cases peq,
  assumption,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  intro pep,
  cases pep,
  assumption,
  intro p,
  split,
  assumption,
  assumption,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro poup,
  cases poup,
  assumption,
  assumption,
  intro p,
  left,
  assumption,
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
  intro ex_px,
  intro a,
  intro h,
  apply ex_px,
  existsi a,
  assumption,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro Vx_nPx,
  intro ex_px,
  cases ex_px with a U,
  apply Vx_nPx a,
  assumption,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro Vx_px,
  by_contradiction hboom,
  apply Vx_px,
  intro a,
  by_contradiction,
  apply hboom,
  existsi a,
  assumption,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro Ex_nPx,
  intro Vx_px,
  cases Ex_nPx with a Pa,
  apply Pa,
  apply Vx_px a,
end

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

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
  intro nEx_Px,
  intro a,
  by_contradiction,
  apply nEx_Px,
  existsi a,
  assumption,
  intro Vx_nPx,
  intro Ex_Px,
  cases Ex_Px with a U,
  apply Vx_nPx a,
  assumption,
end

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro Ex_Px,
  intro Vx_nPx,
  cases Ex_Px with a Pa,
  apply Vx_nPx a,
  assumption,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro Vx_Px,
  intro Ex_nPx,
  cases Ex_nPx with a Pa,
  apply Pa,
  apply Vx_Px a,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro nEx_nPx,
  intro a,
  by_contradiction,
  apply nEx_nPx,
  existsi a,
  assumption,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro nVx_nPx,
  by_contradiction,
  apply nVx_nPx,
  intro a,
  intro Pa,
  apply h,
  existsi a,
  assumption,
end

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

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
  apply exists_as_neg_forall,
  apply exists_as_neg_forall_converse,
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 Ex_PxeQx,
  split,
  by_contradiction hboom,
  cases Ex_PxeQx with a PaeQa,
  apply hboom,
  existsi a,
  cases PaeQa,
  assumption,
  by_contradiction hboom,
  cases Ex_PxeQx with a PaeQa,
  apply hboom,
  existsi a,
  cases PaeQa,
  assumption,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro Ex_PxouQx,
  cases Ex_PxouQx with a PaouQa,
  cases PaouQa,
  left,
  existsi a,
  assumption,
  right,
  existsi a,
  assumption,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro Ex_Px_ou_Ex_Qx,
  cases Ex_Px_ou_Ex_Qx with Px_ou_Qx,
  by_contradiction hboom,
  cases Px_ou_Qx with a Pa,
  apply hboom,
  existsi a,
  left,
  assumption,
  by_contradiction hboom,
  cases Ex_Px_ou_Ex_Qx with a Qa,
  apply hboom,
  existsi a,
  right,
  assumption,

end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro Vx_PxeQx,
  split,
  intro a,
  apply (Vx_PxeQx a).left,
  intro a,
  apply (Vx_PxeQx a).right,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro Vx_Px_e_Vx_Qx,
  intro a,
  cases Vx_Px_e_Vx_Qx with l r,
  split,
  apply l a,
  apply r a,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro Vx_Px_ou_Vx_Qx,
  intro a,
  cases Vx_Px_ou_Vx_Qx with l r,
  left,
  apply l a,
  right,
  apply r a,
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


import data.finset
import tactic.ring
open nat

def odd : ℕ → ℕ := λ i, 2 * i + 1
def square : ℕ → ℕ := λ i, i ^ 2

#eval odd 20 -- 41
#reduce odd 20 -- 41
#eval square 20 -- 400
#eval square 20 -- 400

#eval [odd 0, odd 1, odd 2] -- [1, 3, 5]
#eval 1 + 3 + 5 -- 9

-- sum of the first three odd positive integers is 3 squared.

example : odd 0 + odd 1 + odd 2 = square 3 := rfl

-- We will look at how to prove that the sum of the first
-- n odd positive integers is n squared. We'll prove it
-- by induction.

-- Here is a proof of the inductive hypothesis.
-- just ignore it; this is an old version of Lean
-- and there are much better ways to do this now.

theorem odd_square_inductive_step (d : ℕ) : square d + odd d = square (succ d) :=
begin unfold square odd nat.pow,rw succ_eq_add_one,
ring,
end

-- Exercise 1.

-- Define a function which takes as input a function
-- f : ℕ → ℕ and returns the function which sends
-- a natural number n to f(0)+f(1)+f(2)+...+f(n-1)

definition my_sum_to_n (summand : ℕ → ℕ) : ℕ → ℕ :=
sorry -- delete this and then fill in your definition here.

-- Exercise 2.

-- The function you defined comes with a law of induction,
-- and you now need to prove the zero and successor results
-- for your function.
theorem my_zero_theorem (summand : ℕ → ℕ) : my_sum_to_n summand 0 = 0 := sorry

theorem my_successor_theorem (summand : ℕ → ℕ) (n : ℕ) :
  my_sum_to_n summand (nat.succ n) = my_sum_to_n summand n + summand n := sorry

-- Exercise 3.

-- Now can you prove my_odd_square_theorem, the fact that the sum of the
-- first n odd numbers is n ^ 2?

theorem my_odd_square_theorem (n : ℕ) : my_sum_to_n odd n = square n := sorry

-- Exercise 4.

-- Finally, find someone who has given a completely different definition
-- of my_sum_to_n (try looking in the comments below, for example) and
-- add their definition to your session (to your "context"):

definition other_sum_to_n (summand : ℕ → ℕ) : ℕ → ℕ :=
sorry

-- and now see if
-- you can prove that their definition is the same as yours.

theorem consistency : my_sum_to_n = other_sum_to_n := sorry

-- You might want to start
-- begin apply funext,intro f,apply funext,intro n,


import .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 :=
begin
  intro h,
  cases' h
end

/- 1.2. Prove that 3, 5, and 7 are odd. -/

lemma odd_3 :
  odd 3 :=
begin
  intro h,
  cases' h,
  cases' h
end

lemma odd_5 :
  odd 5 :=
begin
  intro h,
  cases' h,
  cases' h,
  cases' h
end

lemma odd_7 :
  odd 7 :=
begin
  intro h,
  cases' h,
  cases' h,
  cases' h,
  cases' h
end

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

lemma even_two_times :
  ∀m : ℕ, even (2 * m)
| 0       := even.zero
| (m + 1) :=
  begin
    apply even.add_two,
    simp,
    apply even_two_times
  end

/- 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 {
    cases' ih with w hk,
    apply exists.intro (w + 1),
    rw hk,
    linarith }
end

/- 1.6. 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 :=
begin
  apply iff.intro,
  { apply even_imp_exists_two_times },
  { intro h,
    cases' h,
    simp *,
    apply even_two_times }
end

/- ## Question 2: Binary Trees

2.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 :=
begin
  intros t fmt,
  have fmmt : is_full (mirror (mirror t)) :=
    is_full_mirror _ fmt,
  rw mirror_mirror at fmmt,
  assumption
end

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

def btree.map {α β : Type} (f : α → β) : btree α → btree β
| btree.empty        := btree.empty
| (btree.node a l r) := btree.node (f a) (btree.map l) (btree.map r)

/- 2.3. Prove the following lemma by case distinction. -/

lemma btree.map_eq_empty_iff {α β : Type} (f : α → β) :
  ∀t : btree α, btree.map f t = btree.empty ↔ t = btree.empty
| btree.empty        := by simp [btree.map]
| (btree.node _ _ _) := by simp [btree.map]

/- 2.4. Prove the following lemma by rule induction. -/

lemma btree.map_mirror {α β : Type} (f : α → β) :
  ∀t : btree α, is_full t → is_full (btree.map f t) :=
begin
  intros t hfull,
  induction' hfull,
  case empty {
    simp [btree.map],
    exact is_full.empty },
  case node {
    simp [btree.map],
    apply is_full.node,
    { exact ih_hfull f },
    { exact ih_hfull_1 f },
    { simp [btree.map_eq_empty_iff],
      assumption } }
end

end LoVe


-- 4.4 The Existential Quantifier

open nat

example : ∃ x : ℕ, x > 0 :=
have h : 1 > 0, from zero_lt_succ 0,
exists.intro 1 h

#print zero_lt_succ

example (x : ℕ ) (h : x > 0) : ∃ y, y < x :=
exists.intro 0 h

example (x y z : ℕ ) (hxy : x < y) (hyz : y < z) :
  ∃ w, x < w ∧ w < z :=
exists.intro y (and.intro hxy hyz)

#check @exists.intro

example : ∃ x : ℕ, x > 0 :=
⟨ 1, zero_lt_succ 0 ⟩

example (x : ℕ ) (h : x > 0) : ∃ y, y < x :=
⟨0, h⟩

example (x y z : ℕ ) (hxy : x < y) (hyz : y < z) :
  ∃ w, x < w ∧ w < z :=
⟨y, hxy, hyz⟩

variable g : ℕ → ℕ → ℕ
variable hg : g 0 0 = 0

theorem gex1 : ∃ x, g x x = x := ⟨0, hg⟩
theorem gex2 : ∃ x, g x 0 = x := ⟨0, hg⟩
theorem gex3 : ∃ x, g 0 0 = x := ⟨0, hg⟩
theorem gex4 : ∃ x, g x x = 0 := ⟨0, hg⟩

set_option pp.implicit true -- display implicit arguments
#print gex1
#print gex2
#print gex3
#print gex4

variables (α : Type*) (p q : α → Prop)

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
exists.elim h
  (assume w,
    assume hw : p w ∧ q w,
    show ∃ x, q x ∧ p x,
    from ⟨ w, hw.right, hw.left⟩ )

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
match h with ⟨ w, hw⟩ :=
  ⟨ w, ⟨ hw.right, hw.left⟩ ⟩
end

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
match h with ⟨ (w : α ), (hw : p w ∧ q w) ⟩ :=
  ⟨ w, ⟨ hw.right, hw.left ⟩ ⟩
end

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
match h with ⟨ w, ⟨ hpw, hqw ⟩ ⟩ :=
  ⟨ w, ⟨ hqw, hpw ⟩ ⟩
end

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
let ⟨w, hpw, hqw⟩ := h in ⟨w, hqw, hpw⟩

example : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x :=
assume ⟨w, hpw, hqw⟩, ⟨w, hqw, hpw⟩


import analysis.real tactic.norm_num algebra.group_power

def S (a : {n : ℕ // n ≥ 1} → ℝ)
  (n : ℕ) (H : n ≥ 1) := { r: ℝ | ∃ (m : ℕ) (Hm : m ≥ n), r = a ⟨m,ge_trans Hm H⟩ }

theorem Q6a1 (a : {n : ℕ // n ≥ 1} → ℝ)
  (HB : ∃ B : ℝ, ∀ n : ℕ, ∀ H : n ≥ 1, a ⟨n,H⟩ ≤ B) : ∀ (n : ℕ) (H : n ≥ 1),
  (∃ r : ℝ, r ∈ S a n H) ∧ (∃ B : ℝ, ∀ x : ℝ, x ∈ S a n H → x ≤ B) := sorry

theorem Q6a2 (a : {n : ℕ // n ≥ 1} → ℝ)
  (HB : ∃ B : ℝ, ∀ n : ℕ, ∀ H : n ≥ 1, a ⟨n,H⟩ ≤ B) (n : ℕ) (H : n ≥ 1) :
  ∃ x : ℝ, is_lub (S a n H) x := sorry

theorem Q6b1 (a : {n : ℕ // n ≥ 1} → ℝ)
  (HB : ∃ B : ℝ, ∀ n : ℕ, ∀ H : n ≥ 1, a ⟨n,H⟩ ≤ B) (n : ℕ) (H : n ≥ 1) :
  ∀ bnp1 bn : ℝ, is_lub (S a n H) bn ∧ is_lub (S a (n+1) (le_trans H (nat.le_succ _))) bnp1
  → bnp1 ≤ bn := sorry

def limsup (a : {n : ℕ // n ≥ 1} → ℝ) (lsup : ℝ) : Prop :=
  ∃ b : { n : ℕ // n ≥ 1} → ℝ, is_glb { x : ℝ | ∃ (n : ℕ) (H : n ≥ 1), x = b ⟨n,H⟩} lsup
  ∧ ∀ (n : ℕ) (H : n ≥ 1), is_lub (S a n H) (b ⟨n,H⟩)

def a1 : {n : ℕ // n ≥ 1} → ℝ := λ _, 1

theorem Q6c1 : limsup a1 1 := sorry

noncomputable def a2 : {n : ℕ // n ≥ 1} → ℝ := λ N, 1/N.val

theorem Q6c2 : limsup a2 0 := sorry

noncomputable def a3 : {n : ℕ // n ≥ 1} → ℝ := λ N, 1 - ((((N.val % (2:ℕ))):ℤ):ℝ)

theorem Q6c3 : limsup a3 1 := sorry

def liminf (a : {n : ℕ // n ≥ 1} → ℝ) (linf : ℝ) : Prop :=
  ∃ c : { n : ℕ // n ≥ 1} → ℝ, is_lub { x : ℝ | ∃ (n : ℕ) (H : n ≥ 1), x = c ⟨n,H⟩} linf
  ∧ ∀ (n : ℕ) (H : n ≥ 1), is_glb (S a n H) (c ⟨n,H⟩)

theorem Q6c1' : liminf a1 1 := sorry

theorem Q6c2' : liminf a2 0 := sorry

theorem Q6c3' : liminf a3 0 := sorry


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Jeremy Avigad

Extensional equality for functions, and a proof of function extensionality from quotients.
-/
prelude
import init.data.quot init.logic

open quotient

universes u v
variables {α : Sort u} {β : α → Sort v}

namespace function

/-- The relation stating that two functions are pointwise equal. -/
protected def equiv (f₁ f₂ : Π x : α, β x) : Prop := ∀ x, f₁ x = f₂ x

local infix ` ~ `:50 := function.equiv

protected theorem equiv.refl (f : Π x : α, β x) : f ~ f := assume x, rfl

protected theorem equiv.symm {f₁ f₂ : Π x: α, β x} : f₁ ~ f₂ → f₂ ~ f₁ :=
λ h x, eq.symm (h x)

protected theorem equiv.trans {f₁ f₂ f₃ : Π x: α, β x} : f₁ ~ f₂ → f₂ ~ f₃ → f₁ ~ f₃ :=
λ h₁ h₂ x, eq.trans (h₁ x) (h₂ x)

protected theorem equiv.is_equivalence (α : Sort u) (β : α → Sort v) : equivalence (@function.equiv α β) :=
mk_equivalence (@function.equiv α β) (@equiv.refl α β) (@equiv.symm α β) (@equiv.trans α β)

/-- The setoid generated by pointwise equality. -/
local attribute [instance]
def fun_setoid (α : Sort u) (β : α → Sort v) : setoid (Π x : α, β x) :=
setoid.mk (@function.equiv α β) (function.equiv.is_equivalence α β)

/-- The quotient of the function type by pointwise equality. -/
def extfun (α : Sort u) (β : α → Sort v) : Sort (imax u v) :=
quotient (fun_setoid α β)

/-- The map from functions into the qquotient by pointwise equality. -/
def fun_to_extfun (f : Π x : α, β x) : extfun α β :=
⟦f⟧

/-- From an element of `extfun` we can retrieve an actual function. -/
def extfun_app (f : extfun α β) : Π x : α, β x :=
assume x,
quot.lift_on f
  (λ f : Π x : α, β x, f x)
  (λ f₁ f₂ h, h x)

end function
open function

local attribute [instance] fun_setoid

/-- Function extensionality, proven using quotients. -/
theorem funext {f₁ f₂ : Π x : α, β x} (h : ∀ x, f₁ x = f₂ x) : f₁ = f₂ :=
show extfun_app ⟦f₁⟧ = extfun_app ⟦f₂⟧, from
congr_arg extfun_app (sound h)

attribute [intro!] funext

local infix ` ~ `:50 := function.equiv

instance pi.subsingleton [∀ a, subsingleton (β a)] : subsingleton (Π a, β a) :=
⟨λ f₁ f₂, funext (λ a, subsingleton.elim (f₁ a) (f₂ a))⟩


-- Graham, Knuth, Patashnik
-- Concrete Mathematics

import tactic
import data.real.basic
import data.num.basic

-- Chapter 1
def T_hanoi : ℕ → ℤ
| nat.zero     := 0
| (nat.succ x) := (2* T_hanoi x) + 1

example : T_hanoi 10 = 2^10 - 1 :=
begin
  repeat { rw T_hanoi },
  comp_val
end

example (n : ℕ) : T_hanoi n = 2^n - 1 :=
begin
  induction n,
  comp_val,
  rw T_hanoi,
  rw n_ih,
  rw pow_succ,
  ring,
end

def L_steiner : ℕ → ℕ
| 0            := 0
| (x+1)        := (L_steiner x) + x + 1

example : L_steiner 1 = 1 :=
begin
  repeat { rw L_steiner },
end

-- I copied this proof from mathlib/big_operators.lean; however there it is stated using ∑ notation

lemma twice_steiner_closed (n : ℕ) : L_steiner n * 2 = n*(n+1) :=
begin
  induction n,
  norm_num,
  rw L_steiner,
  rw L_steiner,
  rw add_mul,
  rw add_mul,
  rw n_ih,
  rw nat.succ_eq_add_one,
  ring,
end

lemma steiner_closed (n : ℕ) : L_steiner n = n*(n+1) / 2 :=
begin
  rw ← twice_steiner_closed,
  have two_pos : 2 > 0,
  comp_val,
  rw @nat.mul_div_cancel (L_steiner n) 2 two_pos,
end

-- 1.3 The Josephus Problem
-- We take equation 1.8 as the definition of the function J:
--    J(2*n)   = 2*J(n) - 1
--    J(2*n+1) = 2*J(n) + 1
-- Actually, the real definition of J works on lists of naturals, e.g. to derive the even case of eqn 1.8 we say that J [1, 2, ... 2n] = J [1, 3, 5 ... 2n - 1].
-- It seems rather inconvenient to write a function ℕ → ℤ directly, so we use structural recursion on pos_num

def J_joseph₁ : pos_num → ℤ
| 1 := 1
| (pos_num.bit0 n) := 2*(J_joseph₁ n) - 1     -- J(2*n)   = 2*J(n) - 1
| (pos_num.bit1 n) := 2*(J_joseph₁ n) + 1     -- J(2*n+1) = 2*J(n) + 1

def J_joseph : ℕ → ℤ
| 0 := 0
| (n+1) := J_joseph₁ (n:num).succ'

lemma J_joseph_even (n : ℕ) (h : n ≥ 1) : J_joseph(2*n) = 2*J_joseph(n) - 1 :=
  sorry

-- todo: this needs n ≥ 1
lemma J_joseph_odd (n : ℕ) : J_joseph(2*n + 1) = 2*J_joseph(n) + 1 :=
  sorry

example : J_joseph 100 = 73 :=
begin
  comp_val,
end

-- lemma statement by Kevin Buzzard

def is_even (n : ℕ) := ∃ a, n = 2 * a

def is_odd (n : ℕ) := ∃ b, n = 2 * b + 1

lemma even_or_odd (n : ℕ) : is_even(n) ∨ is_odd(n) :=
begin
  induction n,
  left,
  use 0,
  linarith,
  cases n_ih,
  right,
  cases n_ih with a h_a,
  use a,
  rw nat.succ_eq_add_one,
  rw h_a,
  cases n_ih with a h_a,
  left,
  use a+1,
  rw nat.succ_eq_add_one,
  rw h_a,
  ring,
end

lemma pow_pos2 ( m: ℕ ) : 2^m ≥ 1 :=
begin
  induction m,
  rw nat.pow_zero,
  linarith,
  rw nat.pow_succ,
  linarith,
end

-- Closed form of J for perfect powers of 2
theorem J_pow_2 (m : ℕ) : J_joseph(2 ^ m) = 1 :=
begin
  induction m,
  rw nat.pow_zero,
  comp_val,
  rw nat.pow_succ,
  rw nat.mul_comm,
  rw J_joseph_even,
  rw m_ih,
  comp_val,
  -- 2 ^ m_n > 1
  exact pow_pos2 m_n,
end

lemma bdd (m : ℕ) : (m < 1) → m = 0 :=
begin
  exact nat.sub_eq_zero_of_le,
end

-- Kevin Buzzard
lemma lt_mul_cancel (n m k : ℕ) : (k*m + 1 < k*n) → (m < n) :=
begin
  intro h,
  refine lt_of_mul_lt_mul_left _ (show 0 ≤ k, by linarith),
  refine lt_trans _ h,
  omega,
end

-- eqn 1.9
-- If we imagine J to be defined as a function of two variables m and l, with l < 2^m, and draw the DP table for calculating J via the recurrence (each row representing m fixed and l varying), we treat each cell as depending on the entire previous row
-- There is a slick proof in the margin that probably requires us to work with J defined on lists in order to relate J(m, l) and J(m, l+1). Once related we can prove it using induction on l and J_pow_2.
lemma J_closed (m l : ℕ) : (l < 2^m) → J_joseph(2^m + l) = 2*l + 1 :=
begin
  induction m generalizing l, -- draw the DP table!
  intro h,
  simp at h,
  have h2 := bdd l h,
  rw h2,
  comp_val,
  -- inductive step
  rw nat.pow_succ,
  have key := even_or_odd l,
  cases key,
  -- l even
  cases key with w h_w,
  rw h_w,
  intro h,
  rw mul_comm,
  rw ← nat.left_distrib,
  rw J_joseph_even,
  ring at h,
  have two_pos : 0 < 2,
  comp_val,
  have h := (mul_lt_mul_left two_pos).mp h,
  specialize m_ih w,
  rw m_ih h,
  rw int.distrib_left, -- why isn't this called left_distrib?
  rw int.coe_nat_mul,
  ring,
  have h2 := pow_pos2 m_n,
  linarith,
  -- l odd
  cases key with w h_w,
  rw h_w,
  intro h,
  rw mul_comm,
  rw ← add_assoc _ _ 1,
  rw ← nat.left_distrib,
  rw J_joseph_odd,
  rw mul_comm _ 2 at h,
  have h := lt_mul_cancel (2 ^ m_n) w 2 h,
  specialize m_ih w,
  rw m_ih h,
  ring,
  rw int.coe_nat_add,
  rw int.coe_nat_mul,
  ring,
end

-- alternative closed form using mathlib functions, by Mario Carneiro
def J_joseph₂ (n : ℕ) : ℤ := (2 * n + 1) - (2 ^ nat.size n : ℕ)

-- knuth generalizes J here and considers iterating it until it reaches its fixpoint. This involves considering it as acting on binary digits.

-- Chapter 5

#reduce nat.choose 5 2

-- 5.3 is equivalent to choose_symm from mathlib

-- 5.4

-- Chapter 6

-- From TPIL

def fib : ℕ → ℤ
| 0     := 0
| 1     := 1
| (n+2) := fib (n+1) + fib n

lemma helper (n : ℕ) : (-1:ℤ)^(n+2) = (-1) * (-1)^(n+1) :=
begin
  exact rfl,
end

theorem cassini (n : ℕ) : (n > 0) → (fib (n+1)) * (fib (n-1)) - (fib (n)) ^ 2 = (-1)^n :=
begin
  cases n,
  intro h,
  linarith,
  -- now n is replaced by nat.succ n
  induction n,
  -- the base case has actual content now
  intro h,
  simp,
  rw fib,
  rw fib,
  rw fib,
  norm_num,
  -- inductive case
  intro h,
  have p : nat.succ n_n > 0,
  exact nat.succ_pos n_n,
  have n_ih := n_ih p,
  rw nat.succ_eq_add_one at n_ih,
  rw nat.succ_eq_add_one,
  rw nat.succ_eq_add_one,
  simp,
  simp at n_ih,
  -- we express everything in terms of F_n and F_{n+1}
  have h2 : fib (n_n+2) = fib (n_n+1) + fib n_n,
  rw fib,
  have h3 : fib (n_n+3) = fib (n_n+2) + fib (n_n + 1),
  rw fib,
  rw h3,
  rw h2,
  rw h2 at n_ih,
  ring,
  have helper := helper n_n,
  rw helper,
  ring,
  rw ← n_ih,
  ring,
end

theorem k_future (n k : ℕ) : (k > 0) → fib (n+k) = (fib k)*(fib (n+1)) + (fib (k-1))*(fib n) :=
begin
  cases k,
  intro h,
  linarith,
  intro h,
  induction k generalizing n,
  rw fib,
  simp,
  rw fib,
  simp,
  -- inductive
  have p : nat.succ k_n > 0,
  exact nat.succ_pos k_n,
  have k_ih := k_ih p,
  rw nat.succ_eq_add_one at k_ih,
  repeat { rw nat.succ_eq_add_one },
  simp,
  simp at k_ih,
  rw ← add_assoc,
  specialize k_ih (n+1),
  ring at k_ih,
  rw ← add_assoc at k_ih,
  rw k_ih,
  simp,
  rw fib,
  rw nat.succ_eq_add_one,
  rw fib,
  rw nat.succ_eq_add_one,
  ring,
end

lemma dichotomy (a : ℕ) : a > 0 ∨ a = 0 :=
begin
  have h := nat.eq_zero_or_pos a,
  exact or.swap h,
end

-- 6.110
theorem f_div ( n k : ℕ) : fib n ∣ fib (k*n) :=
begin
  induction k,
  simp,
  rw fib,
  exact dvd_zero (fib n),

  have key := dichotomy (n*k_n),
  cases key,

  -- case 1
  have h := k_future n (k_n*n) _,
  rw nat.succ_eq_add_one,
  ring,
  rw add_comm,
  rw mul_comm,
  rw h,
  cases k_ih,
  rw k_ih_h,
  use (k_ih_w * fib (n + 1)) + fib (k_n * n - 1),
  ring,
  rw mul_comm,

  -- case 2
  exact key,
  rw nat.succ_eq_add_one,
  ring,
  rw key,
  simp,
end

-- exercise 27
example (m n : ℕ) : int.gcd (fib m) (fib n) = nat.gcd m n :=
begin
  sorry
end

-- todo: converse of f_div

-- matijasevich's lemma
example (m n : ℕ) (h : n > 2): (fib n)^2 ∣ fib m ↔ (n:ℤ)*(fib n) ∣ (m:ℤ) := sorry

-- todo: Zeckendorf's theorem states that every positive integer can be represented uniquely as the sum of one or more distinct Fibonacci numbers in such a way that the sum does not include any two consecutive Fibonacci numbers.
-- looks hard


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

import tactic

/-

# Prove that for all positive integers n we have that
# 169 | 3^{3n+3}-26n-27

This is the fourth question in Sierpinski's book "250 elementary problems
in number theory".

Proof: note that n=0 also works :-) In general use induction on n.

Base case n=0 works fine.

Inductive step: we assume `169 ∣ 3^{3d+3}-26d-27`
So it divides 27 times this
which is `3^{3(d+1)+3}-26*27*d-27*27`
and we want it to divide `3^{3(d+1)+3}-26(d+1)-27`

so we're done if it divides the difference, which is
`-26d-26-27+26*27d+27*27`
which is `26*26n+26*26 = 13*13*something`
-/

-- The statement has subtraction in, so we use integers.
example (n : ℕ) (hn : 0 < n) : -- remark; not going to use hn
(169 : ℤ) ∣ 3^(3*n+3)-26*n-27 :=
begin
  clear hn, -- told you
  induction n with d hd, { norm_num },
  rw nat.succ_eq_add_one,
  have h := dvd_mul_of_dvd_right hd ((3 : ℤ)^3),
  rw [mul_sub, mul_sub, ← pow_add] at h,
  rw [mul_add, mul_one, add_comm],
  have h2 : (169 : ℤ) ∣ 169*(4*d+4),
    exact dvd.intro (4 * ↑d + 4) rfl,
  convert dvd_add h h2,
  push_cast,
  ring_exp,
end


import data.real.basic

/-
In the previous file, we saw how to rewrite using equalities.
The analogue operation with mathematical statements is rewriting using
equivalences. This is also done using the `rw` tactic.
Lean uses ↔ to denote equivalence instead of ⇔.

In the following exercises we will use the lemma:

  sub_nonneg {x y : ℝ} : 0 ≤ y - x ↔ x ≤ y

The curly braces around x and y instead of parentheses mean Lean will always try to figure out what
x and y are from context, unless we really insist on telling it (we'll see how to insist much later).
Let's not worry about that for now.

In order to announce an intermediate statement we use:

  have my_name : my statement,

This triggers the apparition of a new goal: proving the statement. After this is done,
the statement becomes available under the name `my_name`.
We can focus on the current goal by typing tactics between curly braces.
-/

example {a b c : ℝ} (hab : a ≤ b) : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  have key : (c + b) - (c + a) = b - a, -- Here we introduce an intermediate statement named key
  { ring, },  -- and prove it between curly braces
  rw key, -- we can now use the key statement
  rw sub_nonneg,
  exact hab,
end

/-
Of course the previous lemma is already in the core library, named `add_le_add_left`, so we can use it below.

Let's prove a variation (without invoking commutativity of addition since this would spoil our fun).
-/

-- 0009
example {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c :=
begin
  rw <- sub_nonneg,
  have key : (b + c) - (a + c) = b - a,
  {ring,},
  rw key,
  rw sub_nonneg,
  exact hab,
end

/-
Let's see how we could use this lemma. It is already in the core library, under the name `add_le_add_right`:

  add_le_add_right {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c

This can be read as: "add_le_add_right is a function that will take as input real numbers a and b, an
assumption `hab` claiming a ≤ b and a real number c, and will output a proof of a + c ≤ b + c".

In addition, recall that curly braces around a b mean Lean will figure out those arguments unless we
insist to help. This is because they can be deduced from the next argument `hab`.
So it will be sufficient to feed `hab` and c to this function.
-/

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

/-
In the second line of the above proof, we need to prove 0 + b ≤ a + b.
The proof after the colon says: this is exactly lemma `add_le_add_right` applied to ha and b.
Actually the `calc` block expects proof terms, and the `by` keyword is used to tell Lean we will use tactics
to build such a proof term. But since the only tactic used in this block is `exact`, we can skip
tactics entirely, and write:
-/

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

/- Let's do a variant. -/

-- 0010
example (a b : ℝ) (hb : 0 ≤ b) : a ≤ a + b :=
begin
  calc a = a + 0 : by ring
     ... ≤ a + b : add_le_add_left hb a,
end

/-
The two preceding examples are in the core library :

  le_add_of_nonneg_left  {a b : ℝ} (ha : 0 ≤ a) : b ≤ a + b
  le_add_of_nonneg_right {a b : ℝ} (hb : 0 ≤ b) : a ≤ a + b

Again, there won't be any need to memorize those names, we will
soon see how to get rid of such goals automatically.
But we can already try to understand how their names are built:
"le_add" describe the conclusion "less or equal than some addition"
It comes first because we are focussed on proving stuff, and
auto-completion works by looking at the beginning of words.
"of" introduces assumptions. "nonneg" is Lean's abbreviation for non-negative.
"left" or "right" disambiguates between the two variations.

Let's use those lemmas by hand for now.

Note that you can have several inequalities steps in a `calc` block,
transitivity of inequalities will be used automatically to assemble
the pieces.
-/

-- 0011
example (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
begin
  calc 0 ≤ a     : ha
     ... ≤ a + b : le_add_of_nonneg_right hb,
end

/- And let's combine with our earlier lemmas. -/

-- 0012
example (a b c d : ℝ) (hab : a ≤ b) (hcd : c ≤ d) : a + c ≤ b + d :=
begin
  calc a + c ≤ b + c : add_le_add_right hab c
     ... ≤ b + d : add_le_add_left hcd b,
end

/-
In the above examples, we prepared proofs of assumptions of our lemmas beforehand, so
that we could feed them to the lemmas. This is called forward reasoning.
The `calc` proofs also belong to this category.

We can also announce the use of a lemma, and provide proofs after the fact, using
the `apply` tactic. This is called backward reasoning because we get the conclusion
first, and provide proofs later. Using `rw` on the goal (rather than on an assumption
from the local context) is also backward reasoning.

Let's do that using the lemma

  mul_nonneg {x y : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) : 0 ≤ x*y
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  rw ← sub_nonneg,
  have key : b*c - a*c = (b - a)*c,
  { ring },
  rw key,
  apply mul_nonneg, -- Here we don't provide proofs for the lemma's assumptions
  -- Now we need to provide the proofs.
  { rw sub_nonneg,
    exact hab },
  { exact hc },
end

/-
Let's prove the same statement using only forward reasoning: announcing stuff,
proving it by working with known facts, moving forward.
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  have hab' : 0 ≤ b - a,
  { rw ← sub_nonneg at hab,
    exact hab, },
  have h₁ : 0 ≤ (b - a)*c,
  { exact mul_nonneg hab' hc },
  have h₂ : (b - a)*c = b*c - a*c,
  { ring, },
  have h₃ : 0 ≤ b*c - a*c,
  { rw h₂ at h₁,
    exact h₁, },
  rw sub_nonneg at h₃,
  exact h₃,
end

/-
One reason why the backward reasoning proof is shorter is because Lean can
infer of lot of things by comparing the goal and the lemma statement. Indeed
in the `apply mul_nonneg` line, we didn't need to tell Lean that x = b - a
and y = c in the lemma. It was infered by "unification" between the lemma
statement and the goal.

To be fair to the forward reasoning version, we should introduce a convenient
variation on `rw`. The `rwa` tactic performs rewrite and then looks for an
assumption matching the goal. We can use it to rewrite our latest proof as:
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  have hab' : 0 ≤ b - a,
  { rwa ← sub_nonneg at hab, },
  have h₁ : 0 ≤ (b - a)*c,
  { exact mul_nonneg hab' hc, },
  have h₂ : (b - a)*c = b*c - a*c,
  { ring, },
  have h₃ : 0 ≤ b*c - a*c,
  { rwa h₂ at h₁, },
  rwa sub_nonneg at h₃,
end

/-
Let's now combine forward and backward reasoning, to get our most
efficient proof of this statement. Note in particular how unification is used
to know what to prove inside the parentheses in the `mul_nonneg` arguments.
-/
example (a b c : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  rw ← sub_nonneg,
  calc 0 ≤ (b - a)*c  : mul_nonneg (by rwa sub_nonneg) hc
     ... =  b*c - a*c : by ring,
end

/-
Let's now practice all three styles using:

  mul_nonneg_of_nonpos_of_nonpos {a b : α} (ha : a ≤ 0) (hb : b ≤ 0) : 0 ≤ a * b

  sub_nonpos {a b : α} : a - b ≤ 0 ↔ a ≤ b
-/

/- First using mostly backward reasoning -/
-- 0013
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  rw ← sub_nonneg,
  have key : a*c - b*c = c*(a - b),
  { ring, },
  rw key,
  have key' : a - b ≤ 0,
  { rw ← sub_nonpos at hab,
    exact hab, },
  apply mul_nonneg_of_nonpos_of_nonpos,
  { exact hc, },
  { exact key', },
end

/- Using forward reasoning -/
-- 0014
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
    rw ← sub_nonneg,
  have key : a*c - b*c = c*(a - b),
  { ring, },
  rw key,
  have key' : a - b ≤ 0,
  { rw ← sub_nonpos at hab,
    exact hab, },
  exact mul_nonneg_of_nonpos_of_nonpos hc key',
end

/-- Using a combination of both, with a `calc` block -/
-- 0015
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  rw ← sub_nonneg,
  calc 0 ≤ c*(a-b) : mul_nonneg_of_nonpos_of_nonpos hc (by rwa sub_nonpos)
      ... = a*c - b*c : by ring,
end

/-
Let's now move to proving implications. Lean denotes implications using
a simple arrow →, the same it uses for functions (say denoting the type of functions
from ℕ to ℕ by ℕ → ℕ). This is because it sees a proof of P ⇒ Q as a function turning
a proof of P into a proof Q.

Many of the examples that we already met are implications under the hood. For instance we proved

  le_add_of_nonneg_left (a b : ℝ) (ha : 0 ≤ a) : b ≤ a + b

But this can be rephrased as

  le_add_of_nonneg_left (a b : ℝ) : 0 ≤ a → b ≤ a + b

In order to prove P → Q, we use the tactic `intros`, followed by an assumption name.
This creates an assumption with that name asserting that P holds, and turns the goal into Q.

Let's check we can go from our old version of `le_add_of_nonneg_left` to the new one.

-/
example (a b : ℝ): 0 ≤ a → b ≤ a + b :=
begin
  intros ha,
  exact le_add_of_nonneg_left ha,
end

/-
Actually Lean doesn't make any difference between those two versions. It is also happy with
-/
example (a b : ℝ): 0 ≤ a → b ≤ a + b :=
le_add_of_nonneg_left

/- No tactic state is shown in the above line because we don't even need to enter
tactic mode using `begin` or `by`.

Let's practise using `intros`. -/

-- 0016
example (a b : ℝ): 0 ≤ b → a ≤ a + b :=
begin
  intros h,
  exact le_add_of_nonneg_right h,
end

/-
What about lemmas having more than one assumption? For instance:

  add_nonneg {a b : ℝ} (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b

A natural idea is to use the conjunction operator (logical AND), which Lean denotes
by ∧. Assumptions built using this operator can be decomposed using the `cases` tactic,
which is a very general assumption-decomposing tactic.
-/
example {a b : ℝ} : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b :=
begin
  intros hyp,
  cases hyp with ha hb,
  exact add_nonneg ha hb,
end

/-
Needing that intermediate line invoking `cases` shows this formulation is not what is used by
Lean. It rather sees `add_nonneg` as two nested implications:
if a is non-negative then if b is non-negative then a+b is non-negative.
It reads funny, but it is much more convenient to use in practice.
-/
example {a b : ℝ} : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
add_nonneg

/-
The above pattern is so common that implications are defined as right-associative operators,
hence parentheses are not needed above.

Let's prove that the naive conjunction version implies the funny Lean version. For this we need
to know how to prove a conjunction. The `split` tactic creates two goals from a conjunction goal.
It can also be used to create two implication goals from an equivalence goal.
-/
example {a b : ℝ} (H : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b) : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
begin
  intros ha,
  intros hb,
  apply H,
  split,
  exact ha,
  exact hb,
end

/-
Let's practice `cases` and `split`. In the next exercise, P, Q and R denote
unspecified mathematical statements.
-/

-- 0017
example (P Q R : Prop) : P ∧ Q → Q ∧ P :=
begin
  intros h,
  cases h with ha hb,
  split,
  exact hb,
  exact ha,
end

/-
Of course using `split` only to be able to use `exact` twice in a row feels silly. One can
also use the anonymous constructor syntax: ⟨ ⟩
Beware those are not parentheses but angle brackets. This is a generic way of providing
compound objects to Lean when Lean already has a very clear idea of what it is waiting for.

So we could have replaced the last three lines by:
  exact ⟨hQ, hP⟩

We can also combine the `intros` steps. We can now compress our earlier proof to:
-/

example {a b : ℝ} (H : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b) : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
begin
  intros ha hb,
  exact H ⟨ha, hb⟩,
end

/-
The anonymous contructor trick actually also works in `intros` provided we use
its recursive version `rintros`. So we can replace
  intro h,
  cases h with h₁ h₂
by
  rintros ⟨h₁, h₂⟩,
Now redo the previous exercise using all those compressing techniques, in exactly two lines. -/

-- 0018
example (P Q R : Prop): P ∧ Q → Q ∧ P :=
begin
  rintros ⟨ha, hb⟩,
  exact ⟨hb, ha⟩,
end

/-
We are ready to come back to the equivalence between the different formulations of
lemmas having two assumptions. Remember the `split` tactic can be used to split
an equivalence into two implications.
-/

-- 0019
example (P Q R : Prop) : (P ∧ Q → R) ↔ (P → (Q → R)) :=
begin
  split,
  intros h i j,
  exact h ⟨i, j⟩,
  rintros h ⟨i, j⟩,
  exact h i j,
end

/-
If you used more than five lines in the above exercise then try to compress things
(without simply removing line ends).

One last compression technique: given a proof h of a conjunction P ∧ Q, one can get
a proof of P using h.left and a proof of Q using h.right, without using cases.
One can also use the more generic (but less legible) names h.1 and h.2.

Similarly, given a proof h of P ↔ Q, one can get a proof of P → Q using h.mp
and a proof of Q → P using h.mpr (or the generic h.1 and h.2 that are even less legible
in this case).

Before the final exercise in this file, let's make sure we'll be able to leave
without learning 10 lemma names. The `linarith` tactic will prove any equality or
inequality or contradiction that follows by linear combinations of assumptions from the
context (with constant coefficients).
-/

example (a b : ℝ) (hb : 0 ≤ b) : a ≤ a + b :=
begin
  linarith,
end

/-
Now let's enjoy this for a while.
-/

-- 0020
example (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
begin
  linarith,
end

/- And let's combine with our earlier lemmas. -/

-- 0021
example (a b c d : ℝ) (hab : a ≤ b) (hcd : c ≤ d) : a + c ≤ b + d :=
begin
  linarith,
end

/-
Final exercise

In the last exercise of this file, we will use the divisibility relation on ℕ,
denoted by ∣ (beware this is a unicode divisibility bar, not the ASCII pipe character),
and the gcd function.

The definitions are the usual ones, but our goal is to avoid using these definitions and
only use the following three lemmas:

  dvd_refl (a : ℕ) : a ∣ a

  dvd_antisymm {a b : ℕ} : a ∣ b → b ∣ a → a = b :=

  dvd_gcd_iff {a b c : ℕ} : c ∣ gcd a b ↔ c ∣ a ∧ c ∣ b
-/

-- All functions and lemmas below are about natural numbers.
open nat

-- 0022
example (a b : ℕ) : a ∣ b ↔ gcd a b = a :=
begin
  split,
  intros h,
  apply dvd_antisymm,
  have key : ((gcd a b) ∣ a ) ∧ ((gcd a b) ∣ b),
  apply dvd_gcd_iff.1,
  apply dvd_refl,
  apply key.1,
  apply dvd_gcd_iff.2,
  exact ⟨dvd_refl a, h⟩,
  intros h,
  have key : ((gcd a b) ∣ a ) ∧ ((gcd a b) ∣ b),
  apply dvd_gcd_iff.1,
  apply dvd_refl,
  cases key with key1 key2,
  rw h at key2,
  exact key2,
end


/-
Source: https://primes.utm.edu/notes/proofs/infinite/euclids.html
Theorem.
  There are more primes than found in any finite list of primes
Proof.
  Call the primes in our finite list p_1, p_2, ..., p_r.
    Let P be any common multiple of these primes plus one
  (for example P = p_1 * p_2 * ... * p_r + 1).
  Now P is either prime or it is not. If it is prime,
  then P is a prime that was not in out list.
  If P is not prime, then it is divisible by some prime, call it p.
  Notice p can not be any of p_1, p_2, ..., p_r, otherwise
  p would divide 1, which is impossible.
    So this prime p is some prime that was not in our original list.
  Either way, the original list was incomplete.
-/

import data.nat.prime
import tactic.find

namespace hidden

open nat

-- source: https://github.com/semorrison/lean-tidy/blob/master/examples/primes.lean
theorem exists_infinite_primes : ∀ n : ℕ, ∃ p, p ≥ n ∧ prime p :=
λ n,
  let p := min_fac (fact n + 1) in
  have fp : fact n > 0 := fact_pos n,
  have f1 : fact n + 1 ≠ 1, from ne_of_gt $ succ_lt_succ $ fact_pos n,
  have pp : prime p, from min_fac_prime f1,
  have w : n ≤ p, from le_of_not_ge $ λ h,
    have h₁ : p ∣ fact n, from dvd_fact (min_fac_pos (fact n + 1)) h,
    have h₂ : p ∣ 1, from (nat.dvd_add_iff_right h₁).2 (min_fac_dvd (fact n + 1)),
    pp.not_dvd_one h₂,
  ⟨p, w, pp⟩

end hidden

-- #find needs "import data.nat.prime" line
-- OR
-- #find needs "import tactic.find" line
-- #find (_ : nat) + _ = _ + _ -- waiting time ~= 20 seconds
-- nat.add_assoc: ∀ (n m k : ℕ), n + m + k = n + (m + k)
-- nat.add_left_comm: ∀ (n m k : ℕ), n + (m + k) = m + (n + k)
-- nat.add_comm: ∀ (n m : ℕ), n + m = m + n
-- nat.succ_add_eq_succ_add: ∀ (n m : ℕ), n.succ + m = n + m.succ
-- nat.add_right_comm: ∀ (n m k : ℕ), n + m + k = n + k + m


/-
7. (a) Let S = {1, 2, 3} and T = {1, 2, 3, 4, 5}. How many functions are
there from S to T ? How many of these are 1-1?
(b) Let |S| = m, |T | = n with m ≤ n. Show that the number of 1-1 func-
tions from S to T is equal to n(n − 1)(n − 2) · · ·(n − m + 1).
-/
import tactic
import data.fintype.card_embedding

open_locale classical

-- replace 37 with the right number
lemma parta (S T : Type) [fintype S] [fintype T] (hS : fintype.card S = 3) (hT : fintype.card T = 5) :
  fintype.card (S → T) = 125 :=
begin
  simp [fintype.card_fun, hS, hT],
  norm_num,
end

-- replace 37 with the right number
lemma parta' (S T : Type) [fintype S] [fintype T] (hS : fintype.card S = 3) (hT : fintype.card T = 5) :
  fintype.card {f : S → T // function.injective f} = 60 :=
begin
  have h : fintype.card {f : S → T // function.injective f} = fintype.card (S ↪ T),
  { apply fintype.card_congr,
    exact equiv.subtype_injective_equiv_embedding S T, },
  rw h,
  simpa [fintype.card_embedding_eq, hS, hT],
end

open_locale big_operators -- for Π

lemma partb (S T : Type) [fintype S] [fintype T] (m n : ℕ) (hmn : m ≤ n) (hS : fintype.card S = m) (hT : fintype.card T = n):
  fintype.card {f : S → T // function.injective f} = ∏ i in finset.Icc (n - m + 1) n, i :=
begin
  have h : fintype.card {f : S → T // function.injective f} = fintype.card (S ↪ T),
  { apply fintype.card_congr,
    exact equiv.subtype_injective_equiv_embedding S T, },
  rw h,
  simp [fintype.card_embedding_eq, hS, hT],
  clear hS hT h _inst_1 _inst_2,
  induction m,
  simp,
  have hm : m_n ≤ n := nat.le_of_succ_le hmn,
  specialize m_ih hm,
  simp [nat.desc_factorial_succ, m_ih, nat.succ_eq_add_one],
  have p : insert (n - m_n) (finset.Icc (n - m_n + 1) n) = finset.Icc (n - (m_n + 1) + 1) n,
  { ext c,
    split,
    { intro hc,
      simp at *,
      cases hc with hc1 hc2,
      { subst hc1,
        simp,
        linarith, },
      { simp [hc2],
        linarith [hc2.1], }, },
    { intro hc,
      simp at *,
      simp [hc],
      cases hc with hc1 hc2,
      by_cases n - m_n + 1 ≤ c,
      { right,
        exact h, },
      { push_neg at h,
        left,
        linarith, }, }, },
  rw ← p,
  simp,
end


import .love05_inductive_predicates_demo

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

set_option pp.beta true

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, 5, and 7 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 even.zero {
    apply exists.intro 0,
    refl },
  case even.add_two : k hek ih {
    sorry }
end

/-! 1.6. 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

/-! ## Question 2: Binary Trees

2.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

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

def btree.map {α β : Type} (f : α → β) : btree α → btree β
-- enter the missing cases here

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

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

/-! 2.4. Prove the following lemma by rule induction. -/

lemma btree.map_mirror {α β : Type} (f : α → β) :
  ∀t : btree α, is_full t → is_full (btree.map f t) :=
sorry

end LoVe


/-
Lean has a robust type theory. The foremost example of this are
its _inductive types_. To get a sense of these, let's look at
some examples
-/

-- We open a namespace to avoid conflict with existing ones.
namespace Notes

-- This is what the natural numbers in Lean look like.
inductive Nat : Type where
  | zero
  | succ (n : Nat)

/-
The keyword _inductive_ tells Lean we are going to define an
inductive type. Then we provide the name for the type, here Nat,
and can provide its type.

The _where_ clause starts the list of constructors used to define
the type.

In this example, Nat has two constructors
- Nat.zero
- Nat.succ n with n : Nat

-/

#check Nat.zero

#check Nat.succ

/-
Every term of type Nat is built using these constructors. We can
enumerate the terms:
Nat.zero, Nat.succ Nat.zero, Nat.succ (Nat.succ Nat.zero),...

These would correspond to 0,1,2,... in our familiar notation.

In general, the terms of an inductive type are all terms you
can build from iterated application of the constructors. All
sequences constructor applications yield distinct terms.

-/

inductive Example where
 | base
 | X (eg : Example)
 | Y (eg : Example)

-- example : Example.X (Example.Y base) = Example.Y (Example.X base) := by
--   rfl

-- Here is List
inductive List (α : Type) where
  | nil
  | cons (a:α) (l:List α)

variable (α : Type)

#check List α

#check List.nil

#check List.cons

/-
We can also define the integers using an inductive type.
-/

inductive Int : Type where
  | ofNat (n : Nat)
  | negSucc (n : Nat)

/-
Here OfNat n is the natural number n viewed as non-negative integer
while NegSucc n is -n-1.
-/

/-
Option α represents possible value of α or failure.
-/

inductive Option (α : Type) where
  | some (a : α)
  | none

/-
We can represent products also
-/

inductive Prod (α β : Type) where
  | mk (a : α) (b : β)

/-
Or what are often call sum types
-/

inductive Sum (α β : Type) where
  | left (a : α)
  | right (b : β)

end Notes

-- We step out of the name space and compare with the existing defs.

variable (α : Type)

#print Nat

#print List

#check List.nil

#check List.cons

#print Int

/-
Core inductive types have special syntax to make use more ergonomic.
In particular, Nat and List have familiar syntax.
-/

#check 1 + 2

#check [0,1,2]

#check 0::[1,2,3]

#check [0,1]++[2,3]

/-
To specify a function from an inductive type, we need to specify a
value for each constructor.
-/

inductive CoinFlip where
  | heads
  | tails
deriving Repr -- allows #eval to display the following

#eval CoinFlip.heads

-- This is the _equation compiler_ syntax for defining functions
def CoinFlip.odds : CoinFlip → Float
  | .heads => 0.5
  | .tails => 0.5

#eval CoinFlip.heads.odds

-- Here is another example. What does it do?

def Nat.minus : Nat → Nat → Nat
  | 0, _ => 0
  | n, 0 => n
  | n+1, m+1 => Nat.minus n m

#eval Nat.minus 10 5
#eval Nat.minus 5 10

/-
Note that inductive types are heavily recursive. This is evident in the
previous example where we have two base cases: n = 0 and m = 0. Then we
treat the case of n+1 and m+1 using the definition at n and m.
-/

def List.shuffle {α : Type} : List α → List α → List α
  | [], l => l
  | l, [] => l
  | x::xs, y::ys => x::y::List.shuffle xs ys

#eval [1,2,3].shuffle [4,5,6]

/-
We can also include parameters in the function definitions
-/

def exp (base : Nat) : Nat → Nat
  | 0 => 1
  | n+1 => (exp base n)*base

def exp' : Nat → Nat → Nat
  | _, 0 => 1
  | base, n+1 => (exp' base n)*base

#eval exp 2 10
#eval exp' 2 10

-- These are the same so we should prove it
-- theorem exp_eq (n m : Nat) : exp n m = exp' n m := sorry
-- More on this next time.


import .love11_logical_foundations_of_mathematics_demo

/-! # LoVe Exercise 11: Logical Foundations of Mathematics -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Vectors as Subtypes

Recall the definition of vectors from the demo: -/

#check vector

/-! The following function adds two lists of integers elementwise. If one
function is longer than the other, the tail of the longer function is
truncated. -/

def list.add : list ℤ → list ℤ → list ℤ
| []        []        := []
| (x :: xs) (y :: ys) := (x + y) :: list.add xs ys
| []        (y :: ys) := []
| (x :: xs) []        := []

/-! 1.1. Show that if the lists have the same length, the resulting list also
has that length. -/

lemma list.length_add :
  ∀(xs : list ℤ) (ys : list ℤ) (h : list.length xs = list.length ys),
    list.length (list.add xs ys) = list.length xs
| []        []        :=
  by simp [list.add]
| (x :: xs) (y :: ys) :=
  begin
    simp [list.add, length],
    intro h,
    rw list.length_add xs ys h
  end
| []        (y :: ys) :=
  begin
    intro h,
    cases' h
  end
| (x :: xs) []        :=
  begin
    intro h,
    cases' h
  end

/-! 1.2. Define componentwise addition on vectors using `list.add` and
`list.length_add`. -/

def vector.add {n : ℕ} : vector ℤ n → vector ℤ n → vector ℤ n
| u v := subtype.mk (list.add (subtype.val u) (subtype.val v))
  begin
    rw list.length_add,
    { exact subtype.property u },
    { rw subtype.property u, rw subtype.property v }
  end

/-! 1.3. Show that `list.add` and `vector.add` are commutative. -/

lemma list.add.comm :
  ∀(xs : list ℤ) (ys : list ℤ), list.add xs ys = list.add ys xs
| []        []        := by refl
| (x :: xs) (y :: ys) := by simp [list.add, add_comm]; exact list.add.comm xs ys
| []        (y :: ys) := by refl
| (x :: xs) []        := by refl

lemma vector.add.comm {n : ℕ} (x y : vector ℤ n) :
  vector.add x y = vector.add y x :=
begin
  apply subtype.eq,
  simp [vector.add],
  apply list.add.comm
end

/-! ## Question 2: Integers as Quotients

Recall the construction of integers from the lecture, not to be confused with
Lean's predefined type `int` (= `ℤ`): -/

#check int.rel
#check int.rel_iff
#check int

/-! 2.1. Define negation on these integers. -/

def int.neg : int → int :=
quotient.lift (λpn : ℕ × ℕ, ⟦(prod.snd pn, prod.fst pn)⟧)
  begin
    intros a a' ha,
    cases' a with p n,
    cases' a' with p' n',
    apply quotient.sound,
    simp at *,
    rw int.rel_iff at *,
    cc
  end

/-! 2.2. Prove the following lemmas about negation. -/

lemma int.neg_eq (p n : ℕ) :
  int.neg ⟦(p, n)⟧ = ⟦(n, p)⟧ :=
by refl

lemma int.neg_neg (a : int) :
  int.neg (int.neg a) = a :=
begin
  apply quotient.induction_on a,
  intro a,
  cases' a,
  simp [int.neg_eq]
end

end LoVe


/-
Copyright (c) 2020 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Buzzard
-/
import tactic.linarith

/-!
# IMO 2019 Q1

Determine all functions `f : ℤ → ℤ` such that, for all integers `a` and `b`,
`f(2a) + 2f(b) = f(f(a+b))`.

The desired theorem is that either:
  -  `f = λ _, 0`
  -  `∃ c, f = λ x, 2 * x + c`

Note that there is a much more compact proof of this fact in Isabelle/HOL
  - http://downthetypehole.de/paste/4YbGgqb4
-/

theorem imo2019Q1 (f : ℤ → ℤ) :
  (∀ a b : ℤ, f (2 * a) + 2 * (f b) = f (f (a + b))) ↔
    (f = 0) ∨ ∃ c, f = λ x, 2 * x + c :=
begin
  split, swap,
  -- easy way: f(x)=0 and f(x)=2x+c work.
  { rintros (rfl|⟨c, rfl⟩); intros; simp only [pi.zero_apply]; ring },
  -- hard way.
  intro hf, -- functional equation
  -- Using `h` for `(0, b)` and `(-1, b + 1)`, we get `f (b + 1) = f b + m`
  obtain ⟨m, H⟩ : ∃ m, ∀ b, f (b + 1) = f b + m,
  { refine ⟨(f 0 - f (-2)) / 2, λ b, _⟩,
    refine sub_eq_iff_eq_add'.1 (int.eq_div_of_mul_eq_right two_ne_zero _),
    have h1 : f 0 + 2 * f b = f (f b) := by simpa using hf 0 b,
    have h2 : f (-2) + 2 * f (b + 1) = f (f b) := by simpa using hf (-1) (b + 1),
    linarith },
  -- Hence, `f` is an affine map, `f b = f 0 + m * b`
  obtain ⟨c, H⟩ : ∃ c, ∀ b, f b = c + m * b,
  { refine ⟨f 0, λ b, _⟩,
    induction b using int.induction_on with b ihb b ihb,
    { simp },
    { simp [H, ihb, mul_add, add_assoc] },
    { rw ← sub_eq_of_eq_add (H _),
      simp [ihb]; ring } },
  -- Now use `hf 0 0` and `hf 0 1` to show that `m ∈ {0, 2}`
  have H3 : 2 * c = m * c := by simpa [H, mul_add] using hf 0 0,
  obtain (rfl|rfl) : 2 = m ∨ m = 0 := by simpa [H, mul_add, H3] using hf 0 1,
  { right, use c, ext b, simp [H, add_comm] },
  { left, ext b, simpa [H, two_ne_zero] using H3 }
end


/-
Copyright (c) 2021 Chris Hughes, Junyan Xu. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Junyan Xu

! This file was ported from Lean 3 source module data.mv_polynomial.cardinal
! leanprover-community/mathlib commit 3cd7b577c6acf365f59a6376c5867533124eff6b
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Finsupp.Fintype
import Mathlib.Data.MvPolynomial.Equiv
import Mathlib.SetTheory.Cardinal.Ordinal

/-!
# Cardinality of Multivariate Polynomial Ring

The main result in this file is `MvPolynomial.cardinal_mk_le_max`, which says that
the cardinality of `MvPolynomial σ R` is bounded above by the maximum of `#R`, `#σ`
and `ℵ₀`.
-/

universe u v

open Cardinal

open Cardinal

namespace MvPolynomial

section TwoUniverses

variable {σ : Type u} {R : Type v} [CommSemiring R]

@[simp]
theorem cardinal_mk_eq_max_lift [Nonempty σ] [Nontrivial R] :
    (#MvPolynomial σ R) = max (max (Cardinal.lift.{u} <| (#R)) <| Cardinal.lift.{v} <| (#σ)) ℵ₀ :=
  (mk_finsupp_lift_of_infinite _ R).trans <| by
    rw [mk_finsupp_nat, max_assoc, lift_max, lift_aleph0, max_comm]
#align mv_polynomial.cardinal_mk_eq_max_lift MvPolynomial.cardinal_mk_eq_max_lift

@[simp]
theorem cardinal_mk_eq_lift [IsEmpty σ] : (#MvPolynomial σ R) = Cardinal.lift.{u} (#R) :=
  ((isEmptyRingEquiv R σ).toEquiv.trans Equiv.ulift.{u}.symm).cardinal_eq
#align mv_polynomial.cardinal_mk_eq_lift MvPolynomial.cardinal_mk_eq_lift

theorem cardinal_lift_mk_le_max {σ : Type u} {R : Type v} [CommSemiring R] : (#MvPolynomial σ R) ≤
    max (max (Cardinal.lift.{u} <| (#R)) <| Cardinal.lift.{v} <| (#σ)) ℵ₀ := by
  cases subsingleton_or_nontrivial R
  · exact (mk_eq_one _).trans_le (le_max_of_le_right one_le_aleph0)
  cases isEmpty_or_nonempty σ
  · exact cardinal_mk_eq_lift.trans_le (le_max_of_le_left <| le_max_left _ _)
  · exact cardinal_mk_eq_max_lift.le
#align mv_polynomial.cardinal_lift_mk_le_max MvPolynomial.cardinal_lift_mk_le_max

end TwoUniverses

variable {σ R : Type u} [CommSemiring R]

/-- The cardinality of the multivariate polynomial ring, `MvPolynomial σ R` is at most the maximum
of `#R`, `#σ` and `ℵ₀` -/
theorem cardinal_mk_le_max : (#MvPolynomial σ R) ≤ max (max (#R) (#σ)) ℵ₀ :=
  cardinal_lift_mk_le_max.trans <| by rw [lift_id, lift_id]
#align mv_polynomial.cardinal_mk_le_max MvPolynomial.cardinal_mk_le_max

end MvPolynomial


import tactic
import data.real.basic
import number_theory.padics
import data.int.gcd

/-!

## Exercises about numbers and casts

-/

/-!

## First exercises

These first examples are just to get you comfortable with
`norm_num`, `norm_cast`, and friends.

-/

example : 12345 < 67890 :=
begin
  sorry
end

example {α : Type} [linear_ordered_field α] : 123 + 45 < 67890/3 :=
begin
  sorry
end

example : nat.prime 17 :=
begin
  sorry
end

-- prove either this or its negation!
example : 7/3 > 2 :=
begin
  sorry
end

example (x : ℝ) (hx : x < 50*50) : x < 25*100 :=
begin
  sorry
end

example (x : ℤ) (hx : (x : ℝ) < 25*100) : x < 25*100 :=
begin
  sorry
end

example (x : ℤ) (hx : (x : ℝ) < 2500) : x < 25*100 :=
begin
  sorry
end

example (p q r : ℕ) (h : r < p - q) (hpq : q ≤ p) : (r : ℝ) < p - q :=
begin
  sorry
end

example (p q r : ℕ) (hr : r < p + 2 - p) : (r : ℤ) < 5 :=
begin
  sorry
end

/-!

## Exercise 2

This comes from the development of the p-adic numbers.
`norm_cast` is very useful here, since we need to talk about values in
ℕ, ℤ, ℚ, ℚ_p, and ℤ_p.

We've done some work to get you started. You might look for the lemmas:
-/

open padic_val_rat

#check zpow_le_of_le
#check zpow_nonneg
#check of_int_multiplicity

example {p n : ℕ} (hp : p.prime) {z : ℤ} (hd : ↑(p^n) ∣ z) : padic_norm p z ≤ ↑p ^ (-n : ℤ) :=
begin
  -- This lemma will be useful later in the proof.
  -- Ignore the "inst" argument; just use `apply aux_lemma` when you need it!
  -- Note that we haven't finished it. Fill in that final sorry.
  have aux_lemma : ∀ inst, (n : ℤ) ≤ (multiplicity ↑p z).get inst,
  { intro,
    norm_cast,
    rw [← part_enat.coe_le_coe, part_enat.coe_get],
    apply multiplicity.le_multiplicity_of_pow_dvd,
    sorry },

  unfold padic_norm, split_ifs with hz hz,
  { sorry },
  { sorry }
end

/-!

## Exercise 3

This seems like a very natural way to write the theorem
"If `a` and `b` are coprime, then there are coefficients `u` and `v` such that `u*a + v*b = 1`."

But I've made a mistake! What did I do wrong? Correct the statement of the theorem and prove it.

I've started you off with a lemma that will be useful.
You might find the `specialize` tactic to be handy as well:
if you have `h : ∀ (x y : T), R x y` and `a, b : T` in the context, then
`specialize h a b` will change the type of `h` to `R a b`.

-/

example (a b : ℕ) (h : nat.coprime a b) : ∃ u v, u*a+v*b = 1 :=
begin
  have := nat.gcd_eq_gcd_ab,
  sorry
end

/-!

## Exercise 4

We did an example together that was similar to this.

This one takes a bit more arithmetic work.
To save you some time, here are some lemmas that may be useful!
(You may not need all of them, depending on how you approach it.)

Remember you can also use `library_search` to try to find useful lemmas.

A hint: you might find it helpful to do this once you've introduced `n`.
```
have n_pos: 0 < n,
{ ... }
```

-/

#check sub_le_iff_le_add
#check add_le_add_iff_left
#check div_le_iff
#check mul_one_div_cancel
#check mul_le_mul_left

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

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

example : seq_limit (λ n : ℕ, (n+1)/n) 1 :=
begin
  sorry
end


/- https://github.com/coq-community/corn/tree/master/order
  http://matt.might.net/articles/partial-orders/ -/

namespace Ordering

universes u v

def dual_relation (A : Type u) (R : A -> A -> Prop) :=
  ∀ x y : A, R x y ↔ R y x

/- Preorder is reflexive and transitive -/
class preorder (A : Type u) (R : A -> A -> Prop) :=
(Hreflexive : ∀ x : A, R x x)
(Htransitive : ∀ x y z, R x y → R y z → R x z)

class total_preorder (A : Type u) (R : A → A → Prop) :=
(Hp : preorder A R)
(Htotal : ∀ x y : A, R x y ∨ R y x)

/- Equivalence is a preorder set with symmetry -/
class equivalence (A : Type u) (R : A -> A -> Prop) :=
(Hp : preorder A R)
(Hsymmetric : ∀ x y : A, R x y → R y x)

/- Partial order is -/
class partial_order (A : Type u) (R : A -> A -> Prop) :=
(Hp : preorder A R)
(Hantisymmetric : ∀ x y, R x y → R y x -> x = y)

/- Meet semilattic is poset with meet -/
class meet_semilattice (A : Type u) (R : A -> A -> Prop)
(meet : A -> A -> A) :=
(Hp : partial_order A R)
(H₁ : ∀ x y : A, R (meet x y) x)
(H₂ : ∀ x y : A, R (meet x y) y)
(H₃ : ∀ x y z : A, R z x → R z y →  R z (meet x y))

lemma meet_idempotent : forall (A : Type u) (R : A -> A -> Prop)
  (x : A) (meet : A → A → A),
  meet_semilattice A R meet → meet x x = x  :=
  begin
  intros A R x meet Hmeet,
  apply Hmeet.Hp.Hantisymmetric,
  apply Hmeet.H₁,
  apply Hmeet.H₃;
  apply Hmeet.Hp.Hp.Hreflexive
  end

lemma meet_commutative : ∀  (A : Type u) (R : A -> A -> Prop)
  (x y : A) (meet : A → A → A),
  meet_semilattice A R meet →  meet x y = meet y x :=
  begin
  intros A R x y meet Hmeet,
  apply Hmeet.Hp.Hantisymmetric,
  /- How to combine top and below one in one go ? -/
  apply Hmeet.H₃, apply Hmeet.H₂,
  apply Hmeet.H₁,
  apply Hmeet.H₃, apply Hmeet.H₂,
  apply Hmeet.H₁,
  end

lemma meet_associative : ∀  (A : Type u) (R : A -> A -> Prop)
  (x y z : A) (meet : A → A → A),
  meet_semilattice A R meet ->  meet x (meet y z) = meet (meet x y) z :=
  begin
   sorry
  end

class join_semilattice (A : Type u) (R : A -> A -> Prop)
(join : A -> A -> A) :=
(Hp : partial_order A R)
(H₁ : ∀ x y : A, R x (join x y))
(H₂ : ∀ x y : A, R y (join x y))
(H₃ : ∀ x y z : A, R x z -> R y z -> R (join x y) z)

lemma join_idempotent : forall (A : Type u) (R : A -> A -> Prop)
  (x : A) (join : A → A → A),
  join_semilattice A R join → join x x = x  :=
  begin
  intros A R x join Hjoin,
  apply Hjoin.Hp.Hantisymmetric,
  apply Hjoin.H₃;
  apply Hjoin.Hp.Hp.Hreflexive,
  apply Hjoin.H₁
  end

/- Can I pull some category theoretic duality notation
   about meet and join. This proof is same as meet_commutative -/
lemma join_commutative : ∀  (A : Type u) (R : A -> A -> Prop)
  (x y : A) (join : A → A → A),
  join_semilattice A R join →  join x y = join y x :=
  begin
  intros A R x y join Hjoin,
  apply Hjoin.Hp.Hantisymmetric,
  /- How to combine top and below one in one go ? -/
  apply Hjoin.H₃, apply Hjoin.H₂,
  apply Hjoin.H₁,
  apply Hjoin.H₃, apply Hjoin.H₂,
  apply Hjoin.H₁,
  end

lemma join_associative : ∀  (A : Type u) (R : A -> A -> Prop)
  (x y z : A) (join : A → A → A),
  meet_semilattice A R join ->  join x (join y z) = join (join x y) z :=
  begin
  /- Write a pen and paper proof first -/
   sorry
  end

class lattice (A : Type u) (R : A -> A -> Prop)
(meet : A -> A -> A) (join : A -> A -> A) :=
(Hmeet : meet_semilattice A R meet)
(Hjoin : join_semilattice A R join)

class bounded_lattic (A : Type u) (R : A -> A -> Prop)  (Top : A) (Bot : A)
(meet : A -> A -> A) (join : A -> A -> A) :=
(Hla : lattice A R meet join)
(Hb : ∀ x, R Bot x)
(Ht : ∀ x, R x Top)

/- f is subset of g -/
def subset (A : Type u) (f : A -> Prop) (g : A -> Prop) :=
   ∀ x : A, f x -> g x

class complete_lattic (A : Type u) (R : A -> A -> Prop)

def monotone_fun (A : Type u) (B : Type v) (f : A -> B)
  (Ra : A -> A -> Prop) (Rb : B -> B -> Prop) :=
  partial_order A Ra -> partial_order B Rb ->
  ∀ (x y : A), Ra x y -> Rb (f x) (f y)

def fixed_point (A : Type u) (f : A -> A) (x : A) :=
  f x = x

end Ordering


/-
Copyright (c) 2019 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import 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.nat_antidiagonal` and `data.finset.nat_antidiagonal` 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 (λ i, (i, n - i))

/-- A pair (i, j) is contained in the antidiagonal of `n` if and only if `i + j = n`. -/
@[simp] lemma mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} :
  x ∈ antidiagonal n ↔ x.1 + x.2 = n :=
begin
  rw [antidiagonal, mem_map], split,
  { rintros ⟨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 (add_tsub_cancel_left _ _) } }
end

/-- The length of the antidiagonal of `n` is `n + 1`. -/
@[simp] lemma length_antidiagonal (n : ℕ) : (antidiagonal n).length = n+1 :=
by rw [antidiagonal, length_map, length_range]

/-- The antidiagonal of `0` is the list `[(0, 0)]` -/
@[simp] lemma antidiagonal_zero : antidiagonal 0 = [(0, 0)] :=
rfl

/-- The antidiagonal of `n` does not contain duplicate entries. -/
lemma nodup_antidiagonal (n : ℕ) : nodup (antidiagonal n) :=
(nodup_range _).map (@left_inverse.injective ℕ (ℕ × ℕ) prod.fst (λ i, (i, n-i)) $ λ i, rfl)

@[simp] lemma antidiagonal_succ {n : ℕ} :
  antidiagonal (n + 1) = (0, n + 1) :: ((antidiagonal n).map (prod.map nat.succ id)) :=
begin
  simp only [antidiagonal, range_succ_eq_map, map_cons, true_and, nat.add_succ_sub_one, add_zero,
    id.def, eq_self_iff_true, tsub_zero, map_map, prod.map_mk],
  apply congr (congr rfl _) rfl,
  ext; simp,
end

lemma antidiagonal_succ_succ' {n : ℕ} :
  antidiagonal (n + 2) =
  (0, n + 2) :: ((antidiagonal n).map (prod.map nat.succ nat.succ)) ++ [(n + 2, 0)] :=
by { rw antidiagonal_succ', simpa }

lemma map_swap_antidiagonal {n : ℕ} :
  (antidiagonal n).map prod.swap = (antidiagonal n).reverse :=
begin
  rw [antidiagonal, map_map, prod.swap, ← list.map_reverse,
    range_eq_range', reverse_range', ← range_eq_range', map_map],
  apply map_congr,
  simp [nat.sub_sub_self, lt_succ_iff] { contextual := tt },
end

end nat
end list


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

-- import levels 1 and 2
import complex.kb_solutions.Level_02_I

/-! # Level 3: Complex conjugation -/

namespace complex

-- First complete the definition using `complex.mk` or `⟨x, y⟩` notation

/-- The complex conjugate of a complex number -/
def conj (z : ℂ) : ℂ := ⟨re(z),-im(z)⟩

-- Now prove how it interacts with everything else

/-! ## Real and imaginary parts -/

-- true by definition; teach it to the simplifier
@[simp] lemma conj_re (z : ℂ) : re(conj z) = re(z) := rfl
@[simp] lemma conj_im (z : ℂ) : im(conj z) = -im(z) := rfl

/-! ## Behaviour with respect to 0, 1 and I -/

-- these will help the simplifier to do basic calculations
-- because all the proofs are of the form "equate real and imaginary parts",
-- I am going to teach `simp` about `ext_iff` for this file only

local attribute [simp] ext_iff

@[simp] lemma conj_zero : conj 0 = 0 := by simp
@[simp] lemma conj_one : conj 1 = 1 := by simp
@[simp] lemma conj_I : conj I = -I := by simp
@[simp] lemma conj_neg_I : conj (-I) = I := by simp

/-! ## Behaviour with respect to +, - and * -/

@[simp] lemma conj_add (z w : ℂ) : conj (z + w) = conj z + conj w :=
by simp [add_comm] -- "simp" by itself fails, and the error tells you it didn't know add_comm

@[simp] lemma conj_neg (z : ℂ) : conj (-z) = -conj z := by simp

@[simp] lemma conj_mul (z w : ℂ) : conj (z * w) = conj z * conj w :=
by simp; ring -- algebra too hard for the simplifier

/-! ## Behaviour with respect to real numbers -/

@[simp] lemma conj_of_real (r : ℝ) : conj r = r := by simp

lemma im_eq_zero_of_eq_conj {z : ℂ} : conj z = z → im z = 0 := by simp; intros; linarith

lemma eq_conj_iff_real {z : ℂ} : conj z = z ↔ ∃ r : ℝ, z = r :=
begin
  split,
  { intro h,
  use re(z),
  have h2 := im_eq_zero_of_eq_conj h,
  simp * at *},
  { rintro ⟨r, hr⟩,
    simp * at *},
end

lemma eq_conj_iff_re {z : ℂ} : conj z = z ↔ (z.re : ℂ) = z :=
by simp; split; intros; linarith

theorem add_conj (z : ℂ) : z + conj z = (2 * z.re : ℝ) :=
by  simp [bit0]; intros; linarith

/-! ## Properties of the `conj` map -/

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

lemma conj_inj {z w : ℂ} : conj z = conj w ↔ z = w :=
by simp

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

/-

A ring homomorphism in Lean is the following collection
of data: a map, and the proof that it commutes with
the ring structures in the obvious way. Here we observe
that the work we've done is enough to define the
ring homomorphism complex conjugation.

-/

/-- the ring homomorphism complex conjugation -/
def Conj : ℂ →+* ℂ :=
{ to_fun := conj,
  map_zero' := conj_zero,
  map_one' := conj_one,
  map_add' := conj_add,
  map_mul' := conj_mul
}

-- Two optional lemmas which computer scientists like,
-- giving us easy access to some basic properties
-- of conj

open function

lemma conj_involutive : involutive conj := conj_conj

lemma conj_bijective : bijective conj := involutive.bijective conj_involutive

end complex


-- Pertenencia a uniones e intersecciones de familias
-- ==================================================

import data.set

open set

variables {I U : Type}
variables {A : I → set U}
variable  {x : U}

-- ----------------------------------------------------
-- Ej. 1. Demostrar que
--    (x ∈ ⋃ i, A i) ↔ (∃ i, x ∈ A i)
-- ----------------------------------------------------

-- 1ª demostración
example :
  (x ∈ ⋃ i, A i) ↔ (∃ i, x ∈ A i) :=
-- by library_search
mem_Union

-- 2ª demostración
example :
  (x ∈ ⋃ i, A i) ↔ (∃ i, x ∈ A i) :=
by simp

-- ----------------------------------------------------
-- Ej. 2. Demostrar que
--    (x ∈ ⋂ i, A i) ↔ (∀ i, x ∈ A i)
-- ----------------------------------------------------

-- 1ª demostración
example :
  (x ∈ ⋂ i, A i) ↔ (∀ i, x ∈ A i) :=
-- by library_search
mem_Inter

-- 2ª demostración
example :
  (x ∈ ⋂ i, A i) ↔ (∀ i, x ∈ A i) :=
by simp


-- Pruebas de la antisimetría de la inclusión de conjuntos
-- =======================================================

-- ----------------------------------------------------
-- Ej. 1. Demostrar
--    A ⊆ B, B ⊆ A ⊢ A = B
-- ----------------------------------------------------

import data.set

variable  U : Type
variables A B : set U

open set

-- 1ª demostración
example
  (h1 : A ⊆ B)
  (h2 : B ⊆ A)
  : A = B :=
begin
  ext,
  split,
  { intro h,
    exact h1 h, },
  { intro h,
    exact h2 h, },
end

-- 2ª demostración
example
  (h1 : A ⊆ B)
  (h2 : B ⊆ A)
  : A = B :=
ext
( assume x,
  iff.intro
  ( assume h : x ∈ A,
    show x ∈ B, from h1 h)
  ( assume h : x ∈ B,
    show x ∈ A, from h2 h))

-- 3ª demostración
example
  (h1 : A ⊆ B)
  (h2 : B ⊆ A)
  : A = B :=
ext
(λ x,
 iff.intro
 (λ h, h1 h)
 (λ h, h2 h))

-- 4ª demostración
example
  (h1 : A ⊆ B)
  (h2 : B ⊆ A)
  : A = B :=
eq_of_subset_of_subset
  ( assume x,
    assume h : x ∈ A,
    show x ∈ B, from h1 h)
  ( assume x,
    assume h : x ∈ B,
    show x ∈ A, from h2 h)

-- 5ª demostración
example
  (h1 : A ⊆ B)
  (h2 : B ⊆ A)
  : A = B :=
eq_of_subset_of_subset h1 h2

-- 6ª demostración
example
  (h1 : A ⊆ B)
  (h2 : B ⊆ A)
  : A = B :=
-- by library_search
subset.antisymm h1 h2


/-
Key differences: Predicate vs propositional logic.

(1) In propositional logic, variables range over
Boolean truth values. A variable is either Boolean
true or Boolean false. In predicate logic, variables
can range over values of many types. A variable can
represent a Boolean truth value, but also a natural
number, a string, a person, a chemical, or whatever.
Predicate logic thus has far greater expressiveness
than propositional logic.

(2) Predicate logic has quantifiers: forall (∀) and
exists (∃). These allow one to express the ideas
that some proposition is true of, every, or of at
least one, object of some kind.

(3) Predicate logic has predicates, which you can
think of as propositions with parameters, such that
the application of a predicate to argument values
of its parameter types gives rise to a specific
proposition about those specific argument values.
In general, some of these propositions will be true
and some won't be.

Here's an example of a proposition in predicate
logic that illustrates all three points. It states
in precise mathematical terms that every natural
number is equal to itself.
-/

def every_nat_eq_itself := ∀ (n : ℕ), eq n n

/-
This example illustrates all three points. First,
the logical variable, n, ranges over all values of
type nat. Second, the proposition has a universal
quantifier, asserting a subsequent claim about all
values in the given domain of natural numbers. And,
third, in the context of the "binding" of n to some
arbitrary but specific natural number by the ∀, the
proposition then asserts that a proposition about n
it true using a two-argument predicate, eq. This is
a predicate that asserts equality of two terms, and
is usually written in infix notation as =. We can
thus write the same proposition more naturally as
follows.
-/

def every_nat_eq_itself' := ∀ (n : ℕ), n = n.

/-
It will be important for you to learn both how
to translate predicate logical expressions into
natural language (here, English), and to express
logical concepts in English in predicate logic.

Let's start with some expressions in predicate
logic. We imagine a world in which there are
people and a relation between people that we
will called "likes _ _", where the proposition
"likes p q" asserts that person p likes person
q.

What, then, do the following proposition mean,
translating from predicate logic to English?

∀ (p : Person), ∃ (q : Person), likes p q

∀ (p : Person), ∃ (q : Person), ¬ likes p q

∃ (p : Person), ∀ (q : Person), likes p q

∃ (p : Person), ∀ (q : Person), likes q p

∃ (p : Person), ∀ (q : Person), ¬ likes q p

(∀ (p : Person), ∃ (q : Person), ¬ likes p q) →
(∃ (p : Person), ∀ (q : Person), ¬ likes q p)

(∃ (p : Person), ∀ (q : Person), ¬ likes q p) →
(∀ (p : Person), ∃ (q : Person), ¬ likes p q)
-/

/-
One of the last two propositions is valid, in
the sense that it's true no matter what Person
and likes mean. Which is it?

In predicate logic, we no longer evaluate the
validity of a proposition using truth tables.
What we need instead is a proof. A proof is a
kind of mathematical object that we accept as
compelling evidence for the truth of a given
proposition.

It will be important for you likewise to learn
to express proofs in natural language, and to
understand that, just like propositions and
predicates, they be formalized as mathematical
expressions/objects/values.

Let's go for a natural language proof of this:

(∃ (p : Person), ∀ (q : Person), ¬ likes q p) →
(∀ (p : Person), ∃ (q : Person), ¬ likes p q)

What is says is that if there is a person that
no one likes, then everyone dislikes somebody.

To prove such an implication we will assume
that  there is a proof of the premise (and
that the premise is therefore true), and we
will then show that given this assumption we
can construct a proof of the conclusion.

Step 1: To prove the proposition, we assume
(∃ (p : Person), ∀ (q : Person), ¬ likes q p).
Given (in the context of) this assumption, it
remains to be proved that (∀ (p : Person),
∃ (q : Person), ¬ likes p q).

Step 2: We've assumed that there is someone
no one likes. We give that someone (whoever it
is) a name: let's say, w. What we know about
w is that ∀ (q : Person), ¬ likes q w. That
is, we know everyone dislikes w in particular.

Step 3: To prove our remaining goal, that
(∀ (p : Person), ∃ (q : Person), ¬ likes p q),
for each person p, we select w as a "witness"
for q: a person that we expect will make the
remaining proposition true. All that remains
to be proved now is that ¬ likes p w.

Step 4: To prove this, we just apply the fact
from step 2 that ∀ (q : Person), ¬ likes q w,
i.e., that *everyone* dislikes w, to conclude
that p, in particular, dislikes w.

We've thus shown that if there is someone
everyone dislikes then everyone dislikes
someone. For every (∀) person, p, it is
enough to point to w to show there is (∃)
someone that p dislikes. While p might
dislike other people, we can be sure that
he or she dislikes w because w is someone
whom everyone dislikes.

QED. [Latin for Quod Est Demonstratum. Thus
it is shown/proved/demonstrated.]
-/

/-
So what are the *valid* forms of reasoning
that we are allowed to use in constructing
proofs of propositions in predicate logic?
The answer is that we already have a fairly
complete set from propositional logic that
we are now validating! Warning: a few of the
following rules are not valid. We will take
the valid ones as *axioms* for use in proofs
of propositions in predicate logic.

def true_intro : pExp := pTrue
def false_elim := pFalse ⇒ P
def and_intro := P ⇒ Q ⇒ (P ∧ Q)
def and_elim_left := (P ∧ Q) ⇒ P
def and_elim_right := (P ∧ Q) ⇒ Q
def or_intro_left := P ⇒ (P ∨ Q)
def or_intro_right := Q ⇒ (P ∨ Q)
def or_elim := (P ∨ Q) ⇒ (P ⇒ R) ⇒ (Q ⇒ R) ⇒ R
def iff_intro := (P ⇒ Q) ⇒ (Q ⇒ P) ⇒ (P ↔ Q)
def iff_elim_left := (P ↔ Q) ⇒ (P ⇒ Q)
def iff_elim_right := (P ↔ Q) ⇒ (Q ⇒ P)
def arrow_elim := (P ⇒ Q) ⇒ P ⇒ Q
def affirm_consequence := (P ⇒ Q) ⇒ Q ⇒ P
def resolution := (P ∨ Q) ⇒ (¬ Q ∨ R) ⇒ (P ∨ R)
def unit_resolution := (P ∨ Q) ⇒ (¬ Q) ⇒ P
def syllogism := (P ⇒ Q) ⇒ (Q ⇒ R) ⇒ (P ⇒ R)
def modus_tollens := (P ⇒ Q) ⇒ ¬ Q ⇒ ¬ P
def neg_elim := ¬ ¬ P ⇒ P
def excluded_middle := P ∨ ¬ P
def neg_intro := (P ⇒ pFalse) ⇒ ¬ P
def affirm_disjunct := (P ∨ Q) ⇒ P ⇒ ¬ Q
def deny_antecedent := (P ⇒ Q) ⇒ (¬ P ⇒ ¬ Q)

-/

/-
Finally, we get to the main idea behind automation
of logic in tools like Lean: we represent predicate
logic propositions as special types and values of
these types as proofs of the propositions. Each of
the propositions above then describes the type of
a function that operates on such proofs, taking
proofs and propositions as arguments and returning
them as results.
-/

/-
That sounds super-complicated but let's make it
super-easy. We'll do this by defining an ordinary
type (day) and its values, and by then defining
a logical type -- a proposition represented as a
type -- and its values, its proofs. Here we go.
-/

/-
We define day to be a new type.
-/
inductive day : Type
/-
Type is the type of *computational* types.
Constructors define the values of the type.
-/
| mon
| tue
| wed
| thu
| fri
| sat
| sun

/-
Now we can define variables of this type.
-/
def d : day := day.mon

/-
A proposition with proof constructors.
-/
inductive emily's_from_cville : Prop
/-
Constructors define the proofs of the proposition
Proofs in Lean are values of *logical* types.
-/
| drivers_license
| passport
| utility_bill

open emily's_from_cville

def a_proof : emily's_from_cville  :=
    passport

theorem a_proof' : emily's_from_cville  :=
    drivers_license

/-
Another data (computational) type.
-/
inductive person : Type
| mari
| jose
| jane
| bill

open person

/-
A predicate is a proposition with a parameter.
When applied to an argument of the right type,
the result is a proposition. The constructors
define the set of proofs of the corresponding
propositions.
-/

inductive is_from_cville : person → Prop
| jose_birth_cert : is_from_cville jose
| jane_birth_cert : is_from_cville jane

open is_from_cville

theorem
jane's_from_cville :
is_from_cville jane :=                              jane_birth_cert

/-
Some comments about function definitions.
Consider the following simple definition.
-/

def evenb (n : ℕ) : bool :=
    n % 2 = 0

#eval evenb 3
#eval evenb 4

#check evenb

def evenb' : ℕ → bool :=
    λ n, n % 2 = 0

def evenb'' : Π (n : ℕ), bool :=
    λ n, n % 2 = 0

#eval evenb'' 5

def evenb''' : ∀ (n : ℕ), bool :=
    λ n, n % 2 = 0

#eval evenb''' 6

/-
Predicates are often used to represent properties
of objects, here, a property of people, namely
the property of a person being from Cville.
-/

inductive is_zero : ℕ → Prop
| zmk: ∀ (n : ℕ), n = 0 → is_zero n

open is_zero

theorem zero_is_zero_0 : is_zero 0 :=
    zmk 0 rfl

/-
Even: inductively defined proofs
-/

inductive is_even : ℕ → Prop
| pf_zero_is_even : (is_even 0)
| pf_n_minus_two_is_even :
    ∀ (n : ℕ), is_even n → is_even (nat.succ (nat.succ n))

open is_even

theorem zero_is_even : is_even 0 :=
    pf_zero_is_even

theorem zero_is_even' : is_even 0 :=
begin
    exact pf_zero_is_even
end

/-
Inductive
-/

theorem two_is_even : is_even 2 :=
    pf_n_minus_two_is_even 0 zero_is_even

theorem two_is_even' : is_even 2 :=
begin
    apply pf_n_minus_two_is_even 0 zero_is_even,
end

theorem two_is_even'' : is_even 2 :=
begin
    apply pf_n_minus_two_is_even 0 _,
    exact zero_is_even,
end

theorem two_is_even''' : is_even 2 :=
begin
    apply pf_n_minus_two_is_even _ _,
    exact zero_is_even,
end

theorem two_is_even'''' : is_even 2 :=
begin
    apply pf_n_minus_two_is_even,
    exact zero_is_even,
end

theorem four_is_even : is_even 4 :=
    pf_n_minus_two_is_even 2 two_is_even

theorem four_is_even' : is_even 4 :=
begin
    apply pf_n_minus_two_is_even 2 two_is_even,
end

theorem four_is_even'' : is_even 4 :=
begin
    apply pf_n_minus_two_is_even,
    apply two_is_even,
end

theorem four_is_even''' : is_even 4 :=
begin
    apply pf_n_minus_two_is_even,
    apply pf_n_minus_two_is_even,
    apply zero_is_even,
end

theorem ten_is_even' : is_even 10 :=
begin
    apply pf_n_minus_two_is_even,
    apply pf_n_minus_two_is_even,
    apply pf_n_minus_two_is_even,
    apply pf_n_minus_two_is_even,
    apply pf_n_minus_two_is_even,
    exact zero_is_even,
end

#print ten_is_even'

theorem ten_is_even'' : is_even 10 :=
begin
    repeat {apply pf_n_minus_two_is_even},
    exact zero_is_even,
end

inductive successor_of : ℕ → ℕ → Prop
| mk : ∀ (n : ℕ), successor_of (nat.succ n) n

theorem five_succ_four : successor_of 5 4 :=
    successor_of.mk 4

inductive equal_to_nat : ℕ → ℕ → Prop
| refl : ∀ (n : ℕ), equal_to_nat n n

theorem five_equals_five : equal_to_nat 5 5 :=
    equal_to_nat.refl 5

theorem five_equals_five' : equal_to_nat 5 5 :=
begin
    apply equal_to_nat.refl _,
end

inductive equal_to_nat' (n : ℕ) :  ℕ → Prop
| refl : ∀ (n : ℕ), equal_to_nat' n

#check equal_to_nat
#check equal_to_nat'

theorem five_eq_five'' : equal_to_nat' 5 5 :=
    equal_to_nat'.refl 5 5

inductive equal_to {α : Type} : α → α → Prop
| refl : ∀ (a : α), equal_to a a


--Propositions as Types
namespace mine
constant and : Prop -> Prop -> Prop
constant Proof : Prop -> Type
constant implies : Prop → Prop → Prop
constant and_comm : Π p q : Prop, Proof (implies (and p q ) (and q p))
variables p q : Prop
#check and_comm p q
end mine

--Introducing Theorems
constants p q :Prop
theorem t1 (hp :p) (hq :q) : p :=hp
#check t1

-- using lemma/theorem and show and assume and from

theorem t5 : p->q->p:=
assume hp :p,
assume hq:q,
show p ,from hp

axiom hp : p
theorem t2 : q -> p:= t1 hp

#check t2

--Declaring types within theorem

theorem t3 (p q :Prop)(hp:p)(hq:q):p:=hp
#check t3

--The above holds true for any propositions p and q
variables p q r s :Prop
#check t3 (r->s)(s->r)

--Propositional Logic

--and

example (hp :p )(hq : q) : p∧q := and.intro hp hq
#check λ (hp :p )(hq : q),and.intro hp hq

-- and.elim_left and and.elim_right

example (hp : p∧q) : p := and.left hp
#check λ(hp : p∧q),and.left hp

example (hp : p∧q) : q := and.right hp
#check λ(hp : p∧q),and.right hp

--proving q and p from p and q

example (hp : p ∧ q) : q ∧ p := and.intro (and.right hp) (and.left hp)

--angular brackets for and.intro

example (hp : p ∧ q):q ∧ p := ⟨ hp.right , hp.left ⟩

--Disjunction

--or intro rules : proving (p ∨ q ) from p or q

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

--or elimination rules : proving q ∨ p from p ∨ q , takes 3 arguments h : p∨q , either p true or q true

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

--Negation

--The proof ¬ p is derived from p->false. To show a contradiction we do hp:p hnp :¬ p
--thus proving false

--Proving (p->q)-> ¬q -> ¬p

example (hpq : p -> q)(hnq : ¬ q) : ¬ p :=
(assume hp :p , show false, from hnq (hpq hp))

--Proving anything follows from a contradiction using absurd
-- Proving p -> ¬ p -> q

example (hp : p)( hnp : ¬ p):q := absurd hp hnp

--Proof of ¬p → q → (q → p) → r using absurd

example (hnp : ¬ p)(hq : q)(hqp : q->p ):r:=
absurd (hqp hq) hnp

--Equivalence
--iff.intro h1 h2  proves p ↔ q from h1:p->q and h2: q->p

--Proving p∧q ↔ q∧p

theorem and_iff : p∧q ↔ q∧p:= iff.intro
(assume h:p∧q,show q∧p , from and.intro (and.right h ) (and.left h))
(assume h:q∧ p,show p∧ q,from and.intro (and.right h) (and.left h))

#check and_iff p q


import tactic.core
import data.nat.basic
import .nats_add_mul

/-  ℕ is just one example of an inductive type, Lean has many more.

The definition of ≤ "less_than_or_equal" on ℕ is itself inductive:

inductive less_than_or_equal (a : ℕ) : ℕ → Prop
| refl : less_than_or_equal a
| step : Π {b}, less_than_or_equal b → less_than_or_equal (succ b)

We can interpret this as follows. Given `a : ℕ`
refl : a ≤ a (reflexivity)
step : ∀ b, a ≤ b → a ≤ b + 1  (whenever a ≤ b we have a ≤ b + 1)

Below we develop some of the basic results about ≤ for ℕ, again we use the
same names as in mathlib but capitalised to avoid clashes.

-/
lemma Le_refl (n : ℕ) : n ≤ n :=
begin
  exact nat.less_than_or_equal.refl, -- refl
end

lemma Le_succ (n : ℕ) : n ≤ n.succ :=
begin
  apply nat.less_than_or_equal.step, -- reduces the problem to showing n ≤ n
  exact Le_refl _,                   -- which is refl
end

/- The next proof uses a useful version of induction on ℕ starting from any m : ℕ -/
lemma Succ_le_succ (m n : ℕ) : m ≤ n → m.succ ≤ n.succ :=
begin
  apply nat.le_induction,
  refl,
  intros n h,
  exact nat.less_than_or_equal.step,
end

-- An alternative proof of the previous result uses induction on the inductive definition of ≤
lemma Succ_le_succ' (m n : ℕ) : m ≤ n → m.succ ≤ n.succ :=
begin
  -- We want to prove ∀ m n, m ≤ n → m.succ ≤ n.succ
  intro h, -- assume m ≤ n
  induction h with k h1 h,  -- this is doing induction on `m ≤ n`
  -- First need to prove m.succ ≤ m.succ (corresponding to the refl case of ≤ )
  refl,
  -- now have  m ≤ k and `m + 1 ≤ k + 1` and need to prove `m + 1 ≤ k + 2`, which is easy
  apply nat.less_than_or_equal.step h,
end

lemma Zero_le (n : ℕ) : 0 ≤ n :=
begin
  induction n with n hn, -- this is just standard induction
  {
    sorry,
  },
  {
    sorry,
  },
end

lemma Le_add_right (n k : ℕ) : n ≤ n + k:=
begin
  induction k with k hk,
  {
    sorry,
  },
  {
    sorry,
  },
end

lemma Le_add_left (n k : ℕ) : n ≤ k + n:=
begin
  sorry,
end

--The next proof again uses induction on the inductive definition of ≤
lemma Le.dest {n m : ℕ}:  n ≤ m → ∃ k, n + k = m:=
begin
  apply nat.le_induction,
  {
    sorry,
  },
  {
    sorry,
  },
end

lemma Le.intro {n m k : ℕ} (h :  n + k = m) : n ≤ m :=
begin
  sorry,
end

lemma Le_trans {a b c : ℕ} : a ≤ b → b ≤ c → a ≤ c:=
begin
  sorry,
end

lemma  Add_left_cancel {n m k : ℕ} (h : m + n = m + k) : n = k :=
begin -- Succ.inj may be useful here
  induction m with m hm,
  {
    sorry},
  {
    sorry},
end

lemma  Eq_zero_of_add_eq_zero_right {n m :ℕ} (h : n + m = 0) : n = 0:=
begin
  cases m,
  {
    sorry},
  {
    sorry},
end

lemma Le_antisymm {m n : ℕ} : m ≤ n → n ≤ m → m = n:=
begin
  sorry,
end

-- We have established quite a lot of the proof that ℕ is a partial order

--#print partial_order
--#print instances linear_order
-- To complete this we would need to consider the definition of <
lemma Lt (m n : ℕ) : m < n ↔ m.succ ≤ n:=
begin
  refl,
end


/-
Copyright (c) 2022 Eric Rodriguez. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Rodriguez
-/
import ring_theory.integral_domain
import ring_theory.localization.basic
import set_theory.cardinal.ordinal

/-!
# Cardinality of localizations

In this file, we establish the cardinality of localizations. In most cases, a localization has
cardinality equal to the base ring. If there are zero-divisors, however, this is no longer true -
for example, `zmod 6` localized at `{2, 4}` is equal to `zmod 3`, and if you have zero in your
submonoid, then your localization is trivial (see `is_localization.unique_of_zero_mem`).

## Main statements

* `is_localization.card_le`: A localization has cardinality no larger than the base ring.
* `is_localization.card`: If you don't localize at zero-divisors, the localization of a ring has
  cardinality equal to its base ring,

-/

open_locale cardinal non_zero_divisors

universes u v

namespace is_localization

variables {R : Type u} [comm_ring R] (S : submonoid R) {L : Type u} [comm_ring L]
          [algebra R L] [is_localization S L]
include S

/-- Localizing a finite ring can only reduce the amount of elements. -/
lemma algebra_map_surjective_of_fintype [fintype R] : function.surjective (algebra_map R L) :=
begin
  classical,
  haveI : fintype L := is_localization.fintype' S L,
  intro x,
  obtain ⟨⟨r, s⟩, h : x * (algebra_map R L) ↑s = (algebra_map R L) r⟩ := is_localization.surj S x,
  obtain ⟨n, hn, hp⟩ :=
    (is_of_fin_order_iff_pow_eq_one _).1 (exists_pow_eq_one (is_localization.map_units L s).unit),
  rw [units.ext_iff, units.coe_pow, is_unit.unit_spec, ←nat.succ_pred_eq_of_pos hn, pow_succ] at hp,
  exact ⟨r * s ^ (n - 1), by erw [map_mul, map_pow, ←h, mul_assoc, hp, mul_one]⟩
end

/-- A localization always has cardinality less than or equal to the base ring. -/
lemma card_le : #L ≤ #R :=
begin
  classical,
  casesI fintype_or_infinite R,
  { exact cardinal.mk_le_of_surjective (algebra_map_surjective_of_fintype S) },
  erw [←cardinal.mul_eq_self $ cardinal.aleph_0_le_mk R],
  set f : R × R → L := λ aa, is_localization.mk' _ aa.1 (if h : aa.2 ∈ S then ⟨aa.2, h⟩ else 1),
  refine @cardinal.mk_le_of_surjective _ _ f (λ a, _),
  obtain ⟨x, y, h⟩ := is_localization.mk'_surjective S a,
  use (x, y),
  dsimp [f],
  rwa [dif_pos $ show ↑y ∈ S, from y.2, set_like.eta]
end

variables (L)

/-- If you do not localize at any zero-divisors, localization preserves cardinality. -/
lemma card (hS : S ≤ R⁰) : #R = #L :=
(cardinal.mk_le_of_injective (is_localization.injective L hS)).antisymm (card_le S)

end is_localization