section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro p,
  intro np,
  contradiction,

end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro np,
  by_contra hboom,
  contradiction,

end

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

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

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

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

end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro nPouQ,
  intro P,
  cases nPouQ with np q,
  contradiction,
  exact q,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro PouQ,
  intro np,
  cases PouQ with p q,
  contradiction,
  exact q,
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro PQ,
  intro nQ,
  intro p,
  have q : Q := PQ p,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro nQnP,
  intro P,
  by_contra hboom,
  apply nQnP,
  exact hboom,
  exact P,
end

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

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro pqp,
  intro nP,
  apply nP,
  apply pqp,
  intro p,
  contradiction,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro pouq,
  intro npnq,
  cases npnq,
  cases pouq with p q,
  contradiction,
  contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro pq,
  intro npounq,
  cases pq,
  cases npounq with np nq,
  contradiction,
  contradiction,
end

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

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

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro npnq,
  cases npnq with np nq,
  intro pouq,
  cases pouq with p q,
  contradiction,
  contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro npouq,
  by_contra nqp,
  apply npouq,
  split,
  by_contra np,
  apply nqp,
  right,
  exact np,
  by_contra nq,
  apply nqp,
  left,
  exact nq,

end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro npouq,
  intro pq,
  cases pq with p q,
  cases npouq with np nq,
  contradiction,
  contradiction,
end

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

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
  intro npouq,
  split,
  intro p,
  apply npouq,
  left,
  exact p,
  intro q,
  apply npouq,
  right,
  exact q,
  intro npnq,
  intro pouq,
  cases npnq with np nq,
  cases pouq with p q,
  contradiction,
  contradiction,
end

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro pqour,
  cases pqour with p qr,
  cases qr with q r,
  left,
  split,
  exact p,
  exact q,
  right,
  split,
  exact p,
  exact r,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro pqoupr,
  cases pqoupr with pq pr,
  cases pq with p q,
  split,
  exact p,
  left,
  exact q,
  cases pr with p r,
  split,
  exact p,
  right,
  exact r,
end

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

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro pouqpour,
  cases pouqpour with pouq pour,
  cases pouq with p q,
  left,
  exact p,
  cases pour with p r,
  left,
  exact p,
  right,
  split,
  exact q,
  exact r,
end

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

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

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro pqr,
  intro pq,
  cases pq with p q,
  have qr : Q→R := pqr p,
  have r : R := qr q,
  exact r,
end

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

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

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

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

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

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

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

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  intro pp,
  cases pp with p p,
  exact p,
  intro p,
  split,
  exact p,
  exact p,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro poup,
  cases poup with p p,
  exact p,
  exact p,
  intro p,
  right,
  exact p,
end

end propositional

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

section predicate

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

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

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro a,
  intro x,
  intro px,
  apply a,
  existsi x,
  exact px,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro a,
  intro b,
  cases b with x px,
  have npx : ¬P x := a x,
  contradiction,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro a,
  by_contra e,
  apply a,
  intro x,
  by_contra npx,
  apply e,
  existsi x,
  exact npx,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro e,
  intro a,
  cases e with  x npx,
  have px : P x := a x,
  contradiction,

end

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

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

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro e,
  intro a,
  cases e with x px,
  have npx : ¬P x := a x,
  contradiction,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro a,
  intro e,
  cases e with x npx,
  have px : P x := a x,
  contradiction,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro ne,
  intro x,
  by_contra npx,
  apply ne,
  existsi x,
  exact npx,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro na,
  by_contra ne,
  apply na,
  intro x,
  by_contra px,
  apply ne,
  existsi x,
  exact px,
end

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

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

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

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro e,
  cases e with x pxqx,
  cases pxqx with px qx,
  split,
  existsi x,
  exact px,
  existsi x,
  exact qx,
end

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

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro eouf,
  cases eouf with e f,
  cases e with x px,
  existsi x,
  left,
  exact px,
  cases f with x qx,
  existsi x,
  right,
  exact qx,
end

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

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro aeb,
  cases aeb with a b,
  intro x,
  split,
  have px := a x,
  exact px,
  have qx := b x,
  exact qx,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro aoub,
  intro x,
  cases aoub with a b,
  have px := a x,
  left,
  exact px,
  have qx := b x,
  right,
  exact qx,
end

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

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

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

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

end predicate


/-
Copyright (c) 2021 Kexing Ying. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kexing Ying, Eric Wieser
-/
import data.real.basic

/-!
# Real sign function

This file introduces and contains some results about `real.sign` which maps negative
real numbers to -1, positive real numbers to 1, and 0 to 0.

## Main definitions

 * `real.sign r` is $\begin{cases} -1 & \text{if } r < 0, \\
                               ~~\, 0 & \text{if } r = 0, \\
                               ~~\, 1 & \text{if } r > 0. \end{cases}$

## Tags

sign function
-/

namespace real

/-- The sign function that maps negative real numbers to -1, positive numbers to 1, and 0
otherwise. -/
noncomputable
def sign (r : ℝ) : ℝ :=
if r < 0 then -1 else
if 0 < r then 1 else
0

lemma sign_of_neg {r : ℝ} (hr : r < 0) : sign r = -1 :=
by rw [sign, if_pos hr]

lemma sign_of_pos {r : ℝ} (hr : 0 < r) : sign r = 1 :=
by rw [sign, if_pos hr, if_neg hr.not_lt]

@[simp]
lemma sign_zero : sign 0 = 0 :=
by rw [sign, if_neg (lt_irrefl _), if_neg (lt_irrefl _)]

@[simp]
lemma sign_one : sign 1 = 1 :=
sign_of_pos $ by norm_num

lemma sign_apply_eq (r : ℝ) : sign r = -1 ∨ sign r = 0 ∨ sign r = 1 :=
begin
  obtain hn | rfl | hp := lt_trichotomy r (0 : ℝ),
  { exact (or.inl $ sign_of_neg hn) },
  { exact (or.inr $ or.inl $ sign_zero) },
  { exact (or.inr $ or.inr $ sign_of_pos hp) },
end

/-- This lemma is useful for working with `ℝˣ` -/
lemma sign_apply_eq_of_ne_zero (r : ℝ) (h : r ≠ 0) : sign r = -1 ∨ sign r = 1 :=
begin
  obtain hn | rfl | hp := lt_trichotomy r (0 : ℝ),
  { exact (or.inl $ sign_of_neg hn) },
  { exact (h rfl).elim },
  { exact (or.inr $ sign_of_pos hp) },
end

@[simp]
lemma sign_eq_zero_iff {r : ℝ} : sign r = 0 ↔ r = 0 :=
begin
  refine ⟨λ h, _, λ h, h.symm ▸ sign_zero⟩,
  obtain hn | rfl | hp := lt_trichotomy r (0 : ℝ),
  { rw [sign_of_neg hn, neg_eq_zero] at h, exact (one_ne_zero h).elim },
  { refl },
  { rw sign_of_pos hp at h, exact (one_ne_zero h).elim },
end

lemma sign_int_cast (z : ℤ) : sign (z : ℝ) = ↑(int.sign z) :=
begin
  obtain hn | rfl | hp := lt_trichotomy z (0 : ℤ),
  { rw [sign_of_neg (int.cast_lt_zero.mpr hn), int.sign_eq_neg_one_of_neg hn, int.cast_neg,
      int.cast_one], },
  { rw [int.cast_zero, sign_zero, int.sign_zero, int.cast_zero], },
  { rw [sign_of_pos (int.cast_pos.mpr hp), int.sign_eq_one_of_pos hp, int.cast_one] }
end

lemma sign_neg {r : ℝ} : sign (-r) = - sign r :=
begin
  obtain hn | rfl | hp := lt_trichotomy r (0 : ℝ),
  { rw [sign_of_neg hn, sign_of_pos (neg_pos.mpr hn), neg_neg] },
  { rw [sign_zero, neg_zero, sign_zero] },
  { rw [sign_of_pos hp, sign_of_neg (neg_lt_zero.mpr hp)] },
end

lemma sign_mul_nonneg (r : ℝ) : 0 ≤ sign r * r :=
begin
  obtain hn | rfl | hp := lt_trichotomy r (0 : ℝ),
  { rw sign_of_neg hn,
    exact mul_nonneg_of_nonpos_of_nonpos (by norm_num) hn.le },
  { rw mul_zero, },
  { rw [sign_of_pos hp, one_mul],
    exact hp.le }
end

lemma sign_mul_pos_of_ne_zero (r : ℝ) (hr : r ≠ 0) : 0 < sign r * r :=
begin
  refine lt_of_le_of_ne (sign_mul_nonneg r) (λ h, hr _),
  have hs0 := (zero_eq_mul.mp h).resolve_right hr,
  exact sign_eq_zero_iff.mp hs0,
end

@[simp]
lemma inv_sign (r : ℝ) : (sign r)⁻¹ = sign r :=
begin
  obtain (hn | hz | hp) := sign_apply_eq r,
  { rw hn, norm_num },
  { rw hz, exact inv_zero },
  { rw hp, exact inv_one }
end

@[simp]

end real


import data.real.basic
import data.int.parity

/-
In this file, we learn how to handle the ∃ quantifier.

In order to prove `∃ x, P x`, we give some x₀ using tactic `use x₀` and
then prove `P x₀`. This x₀ can be an object from the local context
or a more complicated expression.
-/
example : ∃ n : ℕ, 8 = 2*n :=
begin
  use 4,
  refl,  -- this is the tactic analogue of the rfl proof term
end

/-
In order to use `h : ∃ x, P x`, we use the `cases` tactic to fix
one x₀ that works.

Again h can come straight from the local context or can be a more
complicated expression.
-/
example (n : ℕ) (h : ∃ k : ℕ, n = k + 1) : n > 0 :=
begin
  -- Let's fix k₀ such that n = k₀ + 1.
  cases h with k₀ hk₀,
  -- It now suffices to prove k₀ + 1 > 0.
  rw hk₀,
  -- and we have a lemma about this
  exact nat.succ_pos k₀,
end

/-
The next exercises use divisibility in ℤ (beware the ∣ symbol which is
not ASCII).

By definition, a ∣ b ↔ ∃ k, b = a*k, so you can prove a ∣ b using the
`use` tactic.
-/

-- Until the end of this file, a, b and c will denote integers, unless
-- explicitly stated otherwise
variables (a b c : ℤ)

-- 0029
example (h₁ : a ∣ b) (h₂ : b ∣ c) : a ∣ c :=
begin
  cases h₁ with k hk,
  cases h₂ with l hl,
  use k*l,
  rw [hl,hk],
  ring,
end

/-
A very common pattern is to have an assumption or lemma asserting
  h : ∃ x, y = ...
and this is used through the combo:
  cases h with x hx,
  rw hx at ...
The tactic `rcases` allows us to do recursive `cases`, as indicated by its name,
and also simplifies the above combo when the name hx is replaced by the special
name `rfl`, as in the following example.
It uses the anonymous constructor angle brackets syntax.
-/

example (h1 : a ∣ b) (h2 : a ∣ c) : a ∣ b+c :=
begin
  rcases h1 with ⟨k, rfl⟩,
  ---- Equivalent to:
  -- cases h1 with k hRFL,
  -- rw hRFL at *,
  -- clear hRFL,
  rcases h2 with ⟨l, rfl⟩,
  use k+l,
  ring,
end

/-
You can use the same `rfl` trick with the `rintros` tactic.
-/

example : a ∣ b → a ∣ c → a ∣ b+c :=
begin
  rintros ⟨k, rfl⟩ ⟨l, rfl⟩,
  use k+l,
  ring,
end

-- 0030
example : 0 ∣ a ↔ a = 0 :=
begin
  split,
  rintros ⟨k,rfl⟩,
  ring,
  intro h,
  rw h,
end

/-
We can now start combining quantifiers, using the definition

  surjective (f : X → Y) := ∀ y, ∃ x, f x = y

-/
open function

-- In the remaining of this file, f and g will denote functions from
-- ℝ to ℝ.
variables (f g : ℝ → ℝ)

-- 0031
example (h : surjective (g ∘ f)) : surjective g :=
begin
  intro x,
  rcases h x with ⟨y,rfl⟩,
  use f y,
end

/-
The above exercise can be done in three lines. Try to do the
next exercise in four lines.
-/

-- 0032
example (hf : surjective f) (hg : surjective g) : surjective (g ∘ f) :=
begin
  intro x,
  rcases hg x with ⟨y,rfl⟩,
  rcases hf y with ⟨z,rfl⟩,
  use z,
end


import data.set

/-
Exercise: Prove that for any set, L, L ∩ L = L.
-/

example: ∀ {α : Type} (L : set α), L ∩ L = L :=
begin
  intros α L,

  apply set.ext _ ,
  assume a,
  split,
  --forward
  assume a,
  cases a,
  exact a_left,
  --backward
  assume ainL,
  exact and.intro ainL ainL,
end

/-
Exercise: Give a formal statement and proof, then an
English language proof, that the union operator on
sets is commutative.
-/

-- Formal Statement:
-- The union operator on sets is commutative such that,
-- given two sets X and Y of an arbitrary type, the value
-- a is in set X ∪ Y and in set Y ∪ X.

-- Formal Proof:
example: ∀ {α :Type} (X Y : set α), X ∪ Y = Y ∪ X :=
begin
  intros α X Y,
  apply set.ext _,
  assume a,
  split,
  -- forward
  assume K,
  cases K with j l,
    apply or.intro_right,
    exact j,
      apply or.intro_left,
      exact l,
  -- backward,
  assume K,
  cases K with j l,
    apply or.intro_right,
    exact j,
      apply or.intro_left,
      exact l,
end

-- English Language Proof:
-- Given an arbitrary type α and sets X and Y of type set α,
-- a proof that the union operator is commutative is shown. We first
-- assume that α is of an arbitrary type and that sets X and Y are of type
-- set α. Then we prove under these assumptions that for all values a of type
-- α, a ∈ X ∪ Y ↔ a ∈ Y ∪ X. In the forward direction, we assume
-- a ∈ X ∪ Y. We can deduce from this that a ∈ X ∨ a ∈ Y. Both cases imply
-- a ∈ Y ∪ X. Next, in the backward direction, we assume a ∈ Y ∪ X. We deduce
-- from case analysis that a ∈ Y ∨ a ∈ X. Both cases imply a ∈ Y ∪ X. QED.

/-
Exercise: Prove that ⊆ is reflexive and transitive.
Give a formal statement, a formal proof, and an English
language (informal) proof of this fact.
-/

-- Formal Statement:
-- The subset or equal operator ⊆  is reflexive and transitive,
-- such that, for set X of arbitrary type α, X ⊆ X.
-- Further, for three sets X, Y, and Z, X ⊆ Y → Y ⊆ Z → X ⊆ Z.

-- Formal Proof:

example: ∀ {α : Type} ( X : set α ), X ⊆ X ∧ ∀ {α : Type} ( X Y Z : set α ), X ⊆ Y → Y ⊆ Z → X ⊆ Z :=
begin
  -- reflexive
  assume α X,
  split,
  assume x,
  assume xinX,
  exact xinX,
  -- transitive
  assume α X Y Z xsubY ysubZ,
  assume x,
  assume xinX,
  have xinY := xsubY xinX,
  have xinZ := ysubZ xinY,
  exact xinZ,
end

-- English Language Proofs:
-- We first prove that ⊆ is reflexive. We assume a set X of an arbitrary type.
-- Next, we apply the theorem of subset reflexivity, which states that a set
-- is a subset of itself. QED.
-- Next, we prove that ⊆ is transitive. We assume sets X Y and Z of an arbitrary
-- type, and that X ⊆ Y and Y ⊆ Z. We then prove X ⊆ Z by assuming an arbitrary
-- value that is in X and showing that a value in X is in Y and thus in Z. QED.
/-
Exercise: Prove that ∪ and ∩ are associative.
Give a formal statement, a formal proof, and an
English language (informal) proof of this fact.
-/
-- Formal statement
-- The ∪ and ∩ operators are associative, such that for any three sets
-- X Y and Z of an arbitrary type, (X ∩ Y) ∩ Z = X ∩ (Y ∩ Z) &
-- (X ∪ Y) ∪ Z = X ∪ (Y ∪ Z)

-- Formal proof

example: ∀ {α : Type} (X Y Z : set α), ((X ∩ Y) ∩ Z) = X ∩ (Y ∩ Z) :=
begin
  assume α X Y Z,
  apply set.ext _,
  assume x,
  split,
  --forward
  assume h,
  cases h with XnY z,
  apply and.intro,
  apply and.elim_left XnY,
    exact and.intro (and.elim_right XnY) z,
  --backward
  assume h,
  cases h with x ynz,
  apply and.intro,
  exact and.intro x (and.elim_left ynz),
    exact and.elim_right ynz,
end

example: ∀ {α : Type} (X Y Z : set α), (X ∪ Y) ∪ Z = X ∪ (Y ∪ Z) :=
begin
  assume α X Y Z,
  apply set.ext _,
  assume x,
  split,
  --forward
  assume h,
  cases h with XuY z,
  cases XuY,
  apply or.intro_left,
  exact XuY,
    apply or.intro_right,
    apply or.intro_left,
    exact XuY,
      apply or.intro_right,
      apply or.intro_right,
      exact z,
  --backward
  assume h,
  cases h,
  apply or.intro_left,
  apply or.intro_left,
  exact h,
    cases h,
    apply or.intro_left,
    apply or.intro_right,
    exact h,
      apply or.intro_right,
      exact h,
end

-- English Language Proofs
-- For an arbitrary type α and sets X Y and Z of the type set α, ∩ is
-- associative (meaning (X ∩ Y) ∩ Z = X ∩ (Y ∩ Z) ) by the theorem of
-- and associativity. Further, ∪ is associative (meaning
-- (X ∪ Y) ∪ Z = X ∪ (Y ∪ Z) ) by the theorem of or associativity. QED.
/-
Assignment: read (at least skim) the Sections 1 and 2 of
the Wikipedia page on set identities:
https://en.wikipedia.org/wiki/List_of_set_identities_and_relations
There, , among *many* other facts, you will find definitions
of left and right distributivity. To complete the remainder
of this assignment, you need to understand what it means for
one operator to be left- (or right-) distributive over another.
-/

/-
Exercise: Formally state and prove both formally and
informally that ∩ is left-distributive over ∩.
-/

-- ∩ is left-distributive over ∩ such that for any three sets
-- X Y and Z of the same arbitrary type, X ∩ (Y ∩ Z) = (X ∩ Y) ∩ (X ∩ Z).

example : ∀ {α : Type} (X Y Z : set α), X ∩ (Y ∩ Z) = (X ∩ Y) ∩ (X ∩ Z) :=
begin
  assume α X Y Z,

  apply set.ext _,
  assume x,
  --apply distrib.cases_on,
  split,
  --forward
  assume h,
  cases h with w e,
  cases e with g f,
  apply and.intro,
  show x ∈ X ∧ x ∈ Y,
  exact and.intro w g,
    show x ∈ X ∧ x ∈ Z,
    exact and.intro w f,

  --backward
  assume h,
  cases h with j a,
  cases a with b o,
  cases j with y t,
  show x ∈ X ∧ x ∈ Y ∧ x ∈ Z,
  exact and.intro b (and.intro t o),
end

-- Informal English Proof
-- We show that ∩ is left-distributive over ∩ such that for three sets
-- X Y and Z of an arbitrary type X ∩ (Y ∩ Z) = (X ∩ Y) ∩ (X ∩ Z). First,
-- we convert the equation to x ∈ X ∩ (Y ∩ Z) ↔ x ∈  (X ∩ Y) ∩ (X ∩ Z).
-- Next, we perform case analysis of the left side to have proofs that x
-- is in all three sets. Then we apply the and introduction rule to prove that
-- x ∈ X ∧ x ∈ Y ∧ x ∈ Z. In the backward direction, we similarly perform case
-- analysis to come to the same derivation. QED.

/-
Exercise: Formally state and prove both formally
and informally that ∪ is left-distributive over ∩.
-/

-- ∪ is left-distributive over ∩ such that for any three sets X Y and Z
-- of an arbitrary type, X ∪ (Y ∩ Z) = (X ∪ Y) ∩ (X ∪ Z).

example : ∀ {α : Type} (X Y Z : set α), X ∪ (Y ∩ Z) = (X ∪ Y) ∩ (X ∪ Z) :=
begin
  assume α X Y Z,
  apply set.ext _,
  assume x,

  apply iff.intro _ _,
  -- forward
  assume h,
  cases h,
  apply and.intro,
  show x ∈ X ∨ x ∈ Y,
  apply or.intro_left,
  exact h,
    show x ∈ X ∨ x ∈ Z,
    apply or.intro_left,
    exact h,
      cases h with y z,
      apply and.intro,
      show x ∈ X ∨ x ∈ Y,
      apply or.intro_right,
      exact y,
        show x ∈ X ∨ x ∈ Z,
        apply or.intro_right,
        exact z,

  -- backward
  assume h,
  have XuY := and.elim_left h,
  have XuZ := and.elim_right h,
  cases XuY,
  cases XuZ,
  apply or.intro_left,
  exact XuY,
    apply or.intro_left,
    exact XuY,
    cases XuZ,
      apply or.intro_left,
      exact XuZ,
        apply or.intro_right,
        exact and.intro XuY XuZ,
end

-- English Language:
-- We show that ∪ is left-distributive over ∩, such that for any three sets
-- of the same arbitrary type, X ∪ (Y ∩ Z) = (X ∪ Y) ∩ (X ∪ Z). By case analysis,
-- we can see that if x ∈ X ∨ x ∈ Y ∧ x ∈ Z, the following must hold: x ∈ Y ∨ X ∈ Z
-- ∧ x ∈ X ∨ x ∈ Z. In the reverse direction, we similarly perform case analysis. QED.


/-
Copyright (c) 2020 Kevin Lacker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Lacker
-/
import algebra.group_power.identities
import data.int.nat_prime
import tactic.linarith
import tactic.norm_cast
import data.set.finite
/-!
# IMO 1969 Q1

Prove that there are infinitely many natural numbers $a$ with the following property:
the number $z = n^4 + a$ is not prime for any natural number $n$.
-/

open int nat

namespace imo1969_q1

/-- `good_nats` is the set of natural numbers satisfying the condition in the problem
statement, namely the `a : ℕ` such that `n^4 + a` is not prime for any `n : ℕ`. -/
def good_nats : set ℕ := {a : ℕ | ∀ n : ℕ, ¬ nat.prime (n^4 + a)}

/-!
The key to the solution is that you can factor $z$ into the product of two polynomials,
if $a = 4*m^4$. This is Sophie Germain's identity, called `pow_four_add_four_mul_pow_four`
in mathlib.
-/
lemma factorization {m n : ℤ} : ((n - m)^2 + m^2) * ((n + m)^2 + m^2) = n^4 + 4*m^4 :=
pow_four_add_four_mul_pow_four.symm

/-!
To show that the product is not prime, we need to show each of the factors is at least 2,
which `nlinarith` can solve since they are each expressed as a sum of squares.
-/
lemma left_factor_large {m : ℤ} (n : ℤ) (h : 1 < m) : 1 < ((n - m)^2 + m^2) := by nlinarith
lemma right_factor_large {m : ℤ} (n : ℤ) (h : 1 < m) : 1 < ((n + m)^2 + m^2) := by nlinarith

/-!
The factorization is over the integers, but we need the nonprimality over the natural numbers.
-/
lemma int_large {m : ℤ} (h : 1 < m) : 1 < m.nat_abs :=
by exact_mod_cast lt_of_lt_of_le h le_nat_abs

lemma not_prime_of_int_mul' {m n : ℤ} {c : ℕ}
  (hm : 1 < m) (hn : 1 < n) (hc : m*n = (c : ℤ)) : ¬ nat.prime c :=
not_prime_of_int_mul (int_large hm) (int_large hn) hc

/-- Every natural number of the form `n^4 + 4*m^4` is not prime. -/
lemma polynomial_not_prime {m : ℕ} (h1 : 1 < m) (n : ℕ) : ¬ nat.prime (n^4 + 4*m^4) :=
have h2 : 1 < (m : ℤ), from coe_nat_lt.mpr h1,
begin
  refine not_prime_of_int_mul' (left_factor_large (n : ℤ) h2) (right_factor_large (n : ℤ) h2) _,
  exact_mod_cast factorization
end

/--
We define $a_{choice}(b) := 4*(2+b)^4$, so that we can take $m = 2+b$ in `polynomial_not_prime`.
-/
def a_choice (b : ℕ) : ℕ := 4*(2+b)^4

lemma a_choice_good (b : ℕ) : a_choice b ∈ good_nats :=
polynomial_not_prime (show 1 < 2+b, by linarith)

/-- `a_choice` is a strictly monotone function; this is easily proven by chaining together lemmas
in the `strict_mono` namespace. -/
lemma a_choice_strict_mono : strict_mono a_choice :=
((strict_mono_id.const_add 2).nat_pow (dec_trivial : 0 < 4)).const_mul (dec_trivial : 0 < 4)

end imo1969_q1

open imo1969_q1

/-- We conclude by using the fact that `a_choice` is an injective function from the natural numbers
to the set `good_nats`. -/
theorem imo1969_q1 : set.infinite {a : ℕ | ∀ n : ℕ, ¬ nat.prime (n^4 + a)} :=
set.infinite_of_injective_forall_mem a_choice_strict_mono.injective a_choice_good


/- Exercise 1.3: Basics — More Logic and Proofs -/

/- Question 1: Logical symbols -/

/- For all of the following exercises, it can help to unfold the definition of `not`. This can be
achieved using the tactic `dunfold not`. -/

/- 1.1. Prove the following propositions, using whichever style you prefer. -/

example (p q : Prop) : q → p → p :=
assume hq: q,
assume hp: p,
show p, from hp

example (p q r : Prop) : (p → q → r) → (p → q) → p → r :=
begin
assume p q r,
apply p,
exact r,
apply q,
exact r
end

/- This one is a bit tricky. You will need to use the assumption twice. -/
lemma weak_peirce (p q : Prop) : ((((p → q) → p) → p) → q) → q :=
begin
assume p,
apply p,
assume k,
apply k,
assume l,
apply p,
assume s,
exact l
end

lemma contrapositive (p q : Prop) : (p → q) → ¬ q → ¬ p :=
begin
dunfold not,
assume p q,
assume k,
apply q,
apply p,
exact k
end

/- 1.2. Intuitionistic logic is extended to classical logic by assuming a classical axiom. There
are several possibilities for the choice of a classical axiom. In this practical work we show the
logical equivalence of three different classical axioms.

For this exercise, avoid using lemmas from Lean's `classical` namespace, because this would defeat
the purpose of the exercise.

Below, Peirce is pronounced like the English word "purse". -/

def excluded_middle := ∀p : Prop, p ∨ ¬ p
def peirce := ∀p q : Prop, ((p → q) → p) → p
def double_negation := ∀p : Prop, ¬¬ p → p

/- Hint: You will need `or.elim` and `false.elim` below. -/

lemma em_imp_peirce : excluded_middle → peirce :=
begin
  simp [excluded_middle, peirce],
  dunfold not,
  intro h,
  intros p q hpqp,
  apply or.elim (h p),
  intro p,
  assumption,
  intro hpp,
  apply hpqp,
  intro hp,
  apply false.elim,
  apply hpp,
  assumption
end

/- Hint: Try instantiating `q` with `false` in Peirce's law. -/

lemma peirce_imp_dn : peirce → double_negation :=
begin
  intros p not q,
  apply p,
  intro s,
  apply false.elim,
  apply q,
  apply s,
  apply p not,
  intro l,
  apply l,
  apply false.elim,
  apply q,
  intro sh,
  apply p,
  intro lh,
  apply s,
  apply lh,
  apply s,
  assumption,
  assumption,
  assumption
end

/- We will do the missing implication in the homework: -/

example : double_negation → excluded_middle := sorry   -- not now

/- Question 2: Calculational proofs -/

/- 2.1. Write the following proof using `calc`.

  (a + b) * (a + b)
  = a * (a + b) + b * (a + b)
  = a * a + a * b + b * a + b * b
  = a * a + a * b + a * b + b * b
  = a * a + 2 * a * b + b * b

Hint: You might need `rw`, `simp`, `ac_refl`, and the lemmas `mul_add`, `add_mul`, and `two_mul`.
-/

lemma binomial_square (a b : ℕ) : (a + b) * (a + b) = a * a + 2 * a * b + b * b :=
calc (a + b) * (a + b)
         = a * (a + b) + b * (a + b)      : by simp[mul_add, add_mul]
     ... = a * a + a * b + b * a + b * b  : by simp[mul_add]
     ... = a * a + a * b + a * b + b * b  : by ac_refl
     ... = a * a + 2 * a * b + b * b      : begin simp[two_mul], simp[add_mul] end

/- 2.2. Fill in the gaps in the following proof.

In part 4 of the course, we will see Lean's mathematical library (mathlib), which contains useful
tactics to reason about arithmetic. Without mathlib, we are forced to take baby steps.

Hint: You might need `rw`, `simp`, `ac_refl`, and the lemmas `mul_add` and `add_mul` (as well as
the induction hypothesis, other local facts, and the definition of `sum_upto`).

Warning: Keep an eye on the orange bars indicating nontermination (or very slow proofs).
-/

def sum_upto (f : ℕ → ℕ) : ℕ → ℕ
| 0       := f 0
| (m + 1) := sum_upto m + f (m + 1)

lemma sum_upto_square_eq : ∀m : ℕ, 6 * sum_upto (λn, n * n) m = m * (m + 1) * (2 * m + 1)
| 0       := by refl
| (m + 1) :=
  calc 6 * sum_upto (λn, n * n) (m + 1) =
    6 * sum_upto (λn, n * n) m + 6 * (m + 1) * (m + 1) :
    begin simp[sum_upto, add_mul], refl end
  ... = m * (m + 1) * (2 * m + 1) + 6 * (m + 1) * (m + 1) :
    sorry
  ... = (m + 1) * (m * (2 * m + 1)) + (m + 1) * (6 * (m + 1)) :
    sorry
  ... = (m + 1) * (m * (2 * m + 1) + 6 * (m + 1)) :
    sorry
  ... = (m + 1) * (m + 1 + 1) * (2 * (m + 1) + 1) :
    have m * (2 * m + 1) + 6 * (m + 1) = (m + 1 + 1) * (2 * (m + 1) + 1) :=
      have four : 4 = 2 * 2 :=
        sorry,
      have six : 6 = 2 + 4 :=
        sorry,
      calc m * (2 * m + 1) + 6 * (m + 1) = 2 * m * m + m + 6 * m + 6 :
        sorry
      ... = 2 * m * m + 2 * m + 4 * m + m + 2 + 4 :
        sorry
      ... = (m + 1 + 1) * (2 * (m + 1) + 1) :
        sorry
    by rw this; ac_refl


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

import ring_theory.polynomial
import algebra.big_operators.basic

/-!
# Lagrange interpolation

## Main definitions

* `lagrange.basis s x` where `s : finset F` and `x : F`: the Lagrange basis polynomial
  that evaluates to `1` at `x` and `0` at other elements of `s`.
* `lagrange.interpolate s f` where `s : finset F` and `f : s → F`: the Lagrange interpolant
  that evaluates to `f x` at `x` for `x ∈ s`.

-/

noncomputable theory
open_locale big_operators classical

universe u

namespace lagrange

variables {F : Type u} [decidable_eq F] [field F] (s : finset F)
variables {F' : Type u} [field F'] (s' : finset F')

open polynomial

/-- Lagrange basis polynomials that evaluate to 1 at `x` and 0 at other elements of `s`. -/
def basis (x : F) : polynomial F :=
∏ y in s.erase x, C (x - y)⁻¹ * (X - C y)

@[simp] theorem basis_empty (x : F) : basis ∅ x = 1 :=
rfl

@[simp] theorem eval_basis_self (x : F) : (basis s x).eval x = 1 :=
begin
  rw [basis, ← (s.erase x).prod_hom (eval x), finset.prod_eq_one],
  intros y hy, simp_rw [eval_mul, eval_sub, eval_C, eval_X],
  exact inv_mul_cancel (sub_ne_zero_of_ne (finset.ne_of_mem_erase hy).symm)
end

@[simp] theorem eval_basis_ne (x y : F) (h1 : y ∈ s) (h2 : y ≠ x) : (basis s x).eval y = 0 :=
begin
  rw [basis, ← (s.erase x).prod_hom (eval y), finset.prod_eq_zero (finset.mem_erase.2 ⟨h2, h1⟩)],
  simp_rw [eval_mul, eval_sub, eval_C, eval_X, sub_self, mul_zero]
end

theorem eval_basis (x y : F) (h : y ∈ s) : (basis s x).eval y = if y = x then 1 else 0 :=
by { split_ifs with H, { subst H, apply eval_basis_self }, { exact eval_basis_ne s x y h H } }

@[simp] theorem nat_degree_basis (x : F) (hx : x ∈ s) : (basis s x).nat_degree = s.card - 1 :=
begin
  unfold basis, generalize hsx : s.erase x = sx,
  have : x ∉ sx := hsx ▸ finset.not_mem_erase x s,
  rw [← finset.insert_erase hx, hsx, finset.card_insert_of_not_mem this, nat.add_sub_cancel],
  clear hx hsx s, revert this, apply sx.induction_on,
  { intros hx, rw [finset.prod_empty, nat_degree_one], refl },
  { intros y s hys ih hx, rw [finset.mem_insert, not_or_distrib] at hx,
    have h1 : C (x - y)⁻¹ ≠ C 0 := λ h, hx.1 (eq_of_sub_eq_zero $ inv_eq_zero.1 $ C_inj.1 h),
    have h2 : X ^ 1 - C y ≠ 0 := by convert X_pow_sub_C_ne_zero zero_lt_one y,
    rw C_0 at h1, rw pow_one at h2,
    rw [finset.prod_insert hys, nat_degree_mul (mul_ne_zero h1 h2), ih hx.2,
        finset.card_insert_of_not_mem hys, nat_degree_mul h1 h2,
        nat_degree_C, zero_add, nat_degree, degree_X_sub_C, add_comm], refl,
    rw [ne, finset.prod_eq_zero_iff], rintro ⟨z, hzs, hz⟩,
    rw mul_eq_zero at hz, cases hz with hz hz,
    { rw [← C_0, C_inj, inv_eq_zero, sub_eq_zero] at hz, exact hx.2 (hz.symm ▸ hzs) },
    { rw ← pow_one (X : polynomial F) at hz, exact X_pow_sub_C_ne_zero zero_lt_one _ hz } }
end

variables (f : (↑s : set F) → F)

/-- Lagrange interpolation: given a finset `s` and a function `f : s → F`,
`interpolate s f` is the unique polynomial of degree `< s.card`
that takes value `f x` on all `x` in `s`. -/
def interpolate : polynomial F :=
∑ x in s.attach, C (f x) * basis s x

@[simp] theorem interpolate_empty (f) : interpolate (∅ : finset F) f = 0 :=
rfl

@[simp] theorem eval_interpolate (x) (H : x ∈ s) : eval x (interpolate s f) = f ⟨x, H⟩ :=
begin
  rw [interpolate, ← finset.sum_hom _ (eval x), finset.sum_eq_single (⟨x, H⟩ : { x // x ∈ s })],
  { rw [eval_mul, eval_C, subtype.coe_mk, eval_basis_self, mul_one] },
  { rintros ⟨y, hy⟩ _ hyx, rw [eval_mul, subtype.coe_mk, eval_basis_ne s y x H, mul_zero],
    { rintros rfl, exact hyx rfl } },
  { intro h, exact absurd (finset.mem_attach _ _) h }
end

theorem degree_interpolate_lt : (interpolate s f).degree < s.card :=
if H : s = ∅ then by { subst H, rw [interpolate_empty, degree_zero], exact with_bot.bot_lt_coe _ }
else (degree_sum_le _ _).trans_lt $ (finset.sup_lt_iff $ with_bot.bot_lt_coe s.card).2 $ λ b _,
calc  (C (f b) * basis s b).degree
    ≤ (C (f b)).degree + (basis s b).degree : degree_mul_le _ _
... ≤ 0 + (basis s b).degree : add_le_add_right degree_C_le _
... = (basis s b).degree : zero_add _
... ≤ (basis s b).nat_degree : degree_le_nat_degree
... = (s.card - 1 : ℕ) : by { rw nat_degree_basis s b b.2 }
... < s.card : with_bot.coe_lt_coe.2 (nat.pred_lt $ mt finset.card_eq_zero.1 H)

/-- Linear version of `interpolate`. -/
def linterpolate : ((↑s : set F) → F) →ₗ[F] polynomial F :=
{ to_fun := interpolate s,
  map_add' := λ f g, by { simp_rw [interpolate, ← finset.sum_add_distrib, ← add_mul, ← C_add],
    refl },
  map_smul' := λ c f, by { simp_rw [interpolate, finset.smul_sum, C_mul', smul_smul], refl } }

@[simp] lemma interpolate_add (f g) : interpolate s (f + g) = interpolate s f + interpolate s g :=
(linterpolate s).map_add f g

@[simp] lemma interpolate_zero : interpolate s 0 = 0 :=
(linterpolate s).map_zero

@[simp] lemma interpolate_neg (f) : interpolate s (-f) = -interpolate s f :=
(linterpolate s).map_neg f

@[simp] lemma interpolate_sub (f g) : interpolate s (f - g) = interpolate s f - interpolate s g :=
(linterpolate s).map_sub f g

@[simp] lemma interpolate_smul (c : F) (f) : interpolate s (c • f) = c • interpolate s f :=
(linterpolate s).map_smul c f

theorem eq_zero_of_eval_eq_zero {f : polynomial F'} (hf1 : f.degree < s'.card)
  (hf2 : ∀ x ∈ s', f.eval x = 0) : f = 0 :=
by_contradiction $ λ hf3, not_le_of_lt hf1 $
calc  (s'.card : with_bot ℕ)
    ≤ f.roots.to_finset.card : with_bot.coe_le_coe.2 $ finset.card_le_of_subset $ λ x hx,
        (multiset.mem_to_finset).mpr $ (mem_roots hf3).2 $ hf2 x hx
... ≤ f.roots.card : with_bot.coe_le_coe.2 $ f.roots.to_finset_card_le
... ≤ f.degree : card_roots hf3

theorem eq_of_eval_eq {f g : polynomial F'} (hf : f.degree < s'.card) (hg : g.degree < s'.card)
  (hfg : ∀ x ∈ s', f.eval x = g.eval x) : f = g :=
eq_of_sub_eq_zero $ eq_zero_of_eval_eq_zero s'
  (lt_of_le_of_lt (degree_sub_le f g) $ max_lt hf hg)
  (λ x hx, by rw [eval_sub, hfg x hx, sub_self])

theorem eq_interpolate (f : polynomial F) (hf : f.degree < s.card) :
  interpolate s (λ x, f.eval x) = f :=
eq_of_eval_eq s (degree_interpolate_lt s _) hf $ λ x hx, eval_interpolate s _ x hx

/-- Lagrange interpolation induces isomorphism between functions from `s` and polynomials
of degree less than `s.card`. -/
def fun_equiv_degree_lt : degree_lt F s.card ≃ₗ[F] ((↑s : set F) → F) :=
{ to_fun := λ f x, f.1.eval x,
  map_add' := λ f g, funext $ λ x, eval_add,
  map_smul' := λ c f, funext $ λ x, by { rw [pi.smul_apply, smul_eq_mul, ← @eval_C F c _ x,
      ← eval_mul, eval_C, C_mul'], refl },
  inv_fun := λ f, ⟨interpolate s f, mem_degree_lt.2 $ degree_interpolate_lt s f⟩,
  left_inv := λ f, subtype.eq $ eq_interpolate s f $ mem_degree_lt.1 f.2,
  right_inv := λ f, funext $ λ ⟨x, hx⟩, eval_interpolate s f x hx }

end lagrange


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

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 1 : "implies" (`→`)

We learn about propositions, and implications `P → Q` between them. You can get
this arrow by typing `\to` or `\r`. Mathematicians usually write the
implication arrow as `P ⇒ Q` but Lean prefers a single arrow.

## The absolute basics

`P : Prop` means that `P` is a true-false statement. `h : P` means
that `h` is a proof that `P` is true, or you can regard `h` as an
assumption that `P` is true; logically these are the same. Stuff above
the `⊢` symbol is your assumptions. The statement to the right of it is
the goal. Your job is to prove the goal from the assumptions.

## Tactics you will need

To solve the levels on this sheet you will need to know how to use the
following three tactics:

* `intro`
* `exact`
* `apply`

You can read the descriptions of these tactics in Part C of the online course
notes here https://www.ma.imperial.ac.uk/~buzzard/xena/formalising-mathematics-2023/Part_C/tactics/tactics.html
In this course we'll be learning about 30 tactics in total; the goal of this
first logic section is to get you up to speed with ten very basic ones.

## Worked examples

Click around in the proofs to see the tactic state (on the right) change.
The tactic is implemented and the state changes just before the comma.
I will use the following conventions: variables with capital
letters like `P`, `Q`, `R` denote propositions
(i.e. true/false statements) and variables whose names begin
with `h` like `h1` or `hP` are proofs or hypotheses.

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.
variables (P Q R : Prop)

-- Here are some examples of `intro`, `exact` and `apply` being used.

-- Assume that `P` and `Q` and `R` are all true. Deduce that `P` is true.
example (hP : P) (hQ : Q) (hR : R) : P :=
begin
  -- note that `exact P` does *not* work. `P` is the proposition, `hP` is the proof.
  exact hP,
end

-- Same example: assume that `P` and `Q` and `R` are true, but this time
-- give the assumptions silly names. Deduce that `P` is true.
example (fish : P) (giraffe : Q) (dodecahedron : R) : P :=
begin
  exact fish -- `fish` is the name of the assumption that `P` is true (but `hP` is a better name)
end

-- Assume `Q` is true. Prove that `P → Q`.
example (hQ : Q) : P → Q :=
begin
  -- The goal is of the form `X → Y` so we can use `intro`
  intro h,
  -- now `h` is the hypothesis that `P` is true.
  -- Our goal is now the same as a hypothesis so we can use `exact`
  exact hQ,
  -- note `exact Q` doesn't work: `exact` takes the *term*, not the type.
end

-- Assume `P → Q` and `P` is true. Deduce `Q`.
example (h : P → Q) (hP : P) : Q :=
begin
  -- our goal is `⊢ Q` which matches with the conclusion of `h` so `apply` works
  apply h,
  -- now our goal has changed to `P` which is an assumption
  exact hP,
end

/-

## Examples for you to try

Delete the `sorry`s and replace them with comma-separated tactic proofs
using `intro`, `exact` and `apply`.

-/

/-- Every proposition implies itself. -/
example : P → P :=
begin
  sorry
end

/-

Note that `→` is not associative: in general `P → (Q → R)` and `(P → Q) → R`
might not be equivalent. This is like subtraction on numbers -- in general
`a - (b - c)` and `(a - b) - c` might not be equal.

So if we write `P → Q → R` then we'd better know what this means.
The convention in Lean is that it means `P → (Q → R)`. If you think
about it, this means that to deduce `R` you will need to prove both `P`
and `Q`. In general to prove `P1 → P2 → P3 → ... Pn` you can assume
`P1`, `P2`,...,`P(n-1)` and then you have to prove `Pn`.

So the next level is asking you prove that `P → (Q → P)`.

-/
example : P → Q → P :=
begin
  sorry
end

/-- If we know `P`, and we also know `P → Q`, we can deduce `Q`.
This is called "Modus Ponens" by logicians. -/
example : P → (P → Q) → Q :=
begin
  sorry
end

/-- `→` is transitive. That is, if `P → Q` and `Q → R` are true, then
  so is `P → R`. -/
example : (P → Q) → (Q → R) → (P → R) :=
begin
  sorry,
end

-- If `h : P → Q → R` with goal `⊢ R` and you `apply h`, you'll get
-- two goals! Note that tactics operate on only the first goal.
example : (P → Q → R) → (P → Q) → (P → R) :=
begin
  sorry
end

/-

Here are some harder puzzles. They won't teach you anything new about
Lean, they're just trickier. If you're not into logic puzzles
and you feel like you understand `intro`, `exact` and `apply`
then you can just skip these and move onto the next sheet
in this section, where you'll learn some more tactics.

-/

variables (S T : Prop)

example : (P → R) → (S → Q) → (R → T) → (Q → R) → S → T :=
begin
  sorry
end

example : (P → Q) → ((P → Q) → P) → Q :=
begin
  sorry
end

example : ((P → Q) → R) → ((Q → R) → P) → ((R → P) → Q) → P :=
begin
  sorry
end

example : ((Q → P) → P) → (Q → R) → (R → P) → P :=
begin
  sorry
end

example : (((P → Q) → Q) → Q) → (P → Q) :=
begin
  sorry
end

example :
  (((P → Q → Q) → ((P → Q) → Q)) → R) →
  ((((P → P) → Q) → (P → P → Q)) → R) →
  (((P → P → Q) → ((P → P) → Q)) → R) → R :=
begin
  sorry
end


import MyNat.Addition
import MyNat.Multiplication
import MultiplicationWorld.Level1 -- zero_mul
import MultiplicationWorld.Level6 -- succ_mul
namespace MyNat
open MyNat

/-!
# Multiplication World

## Level 7: `add_mul`

We proved `mul_add` already, but because we don't have commutativity yet
we also need to prove `add_mul`. We have a bunch of tools now, so this won't
be too hard. You know what -- you can do this one by induction on any of
the variables. Try them all! Which works best? If you can't face
doing all the commutativity and associativity, remember the high-powered
`simp` tactic mentioned at the bottom of Addition World level 6,
which will solve any puzzle which needs only commutativity
and associativity. If your goal looks like `a+(b+c)=c+b+a` or something,
don't mess around doing it explicitly with `add_comm` and `add_assoc`,
just try `simp`.

##  Lemma

Addition is distributive over multiplication.
In other words, for all natural numbers `a`, `b` and `t`, we have
`(a + b) * t = at + bt.`
-/
lemma add_mul (a b t : MyNat) : (a + b) * t = a * t + b * t := by
  induction b with
  | zero =>
    rw [zero_is_0]
    rw [zero_mul]
    rw [add_zero]
    rw [add_zero]
  | succ b ih =>
    rw [add_succ]
    rw [succ_mul]
    rw [ih]
    rw [succ_mul]
    rw [add_assoc]

/-!
A mathematician would now say that you have proved that the natural
numbers are a semiring. This sounds like a respectable result.
-/

def right_distrib := add_mul -- alternative name

-- instance : semiring MyNat := by structure_helper
-- BUGBUG

/-!
Lean would add that you have also proved that they are a `distrib`.
However this concept has no mathematical name at all -- this says something
about the regard with which mathematicians hold this collectible.
This is an artifact of the set-up of collectibles in Lean. You consider politely
declining Lean's offer of a `distrib` collectible.
You are dreaming of the big collectible at the end of [Power World](../PowerWorld.lean).
-/

-- instance : distrib MyNat := by structure_helper --
-- BUGBUG

/-!
On to [Level 8](./Level8.lean.md)
-/


/-
If P and Q are propositions, then
P ∨ Q is also a proposition. It is
read as "P or Q" and is called a
disjunction. P and Q are called the
disjuncts of the disjunction P ∨ Q.
In constructive logic, one can prove
P ∨ Q from either a proof of P or a
proof of Q. This unit presents and
discusses the basic introduction and
elimination rules for or.
-/

/-
We start by making some assumptions
that we use in the rest of this unit.
-/

variables P Q R X Y Z: Prop
variable pfP : P
variable pfQ : Q
variable pfR : R

/-
  **************************
  *** INTRODUCTION RULES ***
  **************************
-/

/-
In constructive logic, P ∨ Q is
true if either a proof of P or
a proof of Q can be given. Having
a proof of both of course allows
one to give either proof to prove
P ∨ Q.

Here are the introduction rules
written as inference rules.

  { P } Q : Prop, pfP: P
  ---------------------- (or.intro_left)
           P ∨ Q

Notice in the preceding rule that while
P can be inferred from pfP, Q has to be
given explicitly, because otherwise there
is no way to know what it is.

  P { Q } : Prop, pfQ:  Q
  ----------------------- (or.intro_right)
            P ∨ Q

Now P has to be given explicitly, while
Q can be inferred.
-/

/-
The or.intro_left rule takes
propositions P and Q (P implicitly)
constructs a proof of P ∨ Q. No proof
of Q is needed. P need not be given
explicitly because it can be inferred
from the proof of P; however, because
no proof of Q is given, from which Q
could be inferred, Q must be given
explicitly.

The or.intro_right rule takes
a proof of Q explicitly along with a
proposition P, from which P is inferred,
and constructs a proof of P ∨ Q. No proof
of P is required, but the proposition P
has to be given explicitly, as there's
no proof to infer it from.
-/

/-
To prove a disjunction, such as
0 = 0 ∨ 0 = 1, we have to pick which
side of the disjunction we will give
a proof for in order to apply or.intro.
Here's an example. We pick the side
to prove that actually has a proof.
There is no proof of the right side.
-/

theorem goodSide : 0 = 0 ∨ 0 = 1 :=
   or.intro_left (0=1) (eq.refl 0)
  ---------------- Q ---pfP

/-
Recall that we can use "example" in
Lean to state a theorem without giving
its proof a name.

Here we use example to state and prove
that for any proposition, P, P or false
is equivalent to P. (Remember that we
assumed above that P is defined to be a
Prop, and pfP is defined to be a proof
of P.)
-/
example : P ∨ false :=
  or.intro_left false pfP

example : false ∨ Q :=
  or.intro_right false pfQ

/-
EXERCISE: Prove 0 = 1 ∨ 0 = 0.
-/

example : 0 = 1 ∨ 0 = 0 :=
  or.intro_right (0 = 1) rfl

/-
Note: inl and inr are shorthands for
intro_left and intro_right, and the are
defined so that the Prop parameter is
implicit.
-/

example : 0 = 1 ∨ 0 = 0 :=
  or.inr rfl

/-
Here's a proof that P or true
is always true. The key idea is
that we can always choose to give
a proof for the true side, which
is trivial.
-/
theorem zero_right_or :
  ∀ P : Prop, P ∨ true :=
    λ P,
      or.intro_right P true.intro

/-
EXERCISE: Prove that true ∨ Q is
always true as well, but use one
of the shorthands to write your
proof.
-/

example : ∀ Q : Prop, true ∨ Q :=
  λ Q, or.inl true.intro

/-
  **************************
  **** Elimination RULE ****
  **************************
-/

/-
The or.elim rule gives us an indirect way
to prove a proposition, R, (the goal) by
showing first that at least one of two
conditions holds (P ∨ Q), and in either
case W must be true, because both (P → R)
and (Q → R). If P ∨ Q, then if both P → R
and Q → R, then R.

Here's the inference rule in plain text.

pfPQ: P ∨ Q, pfPR: P → R, pfQR: Q → R
-------------------------------------- or.elim
                 R

-/

/-
As an example, suppose that (1) when it
is raining (R) the grass is wet (R → W); (2)
when the sprinkler (S) is on, the grass
is also wet (S → W); and it is raining *or*
the sprinkler is on (R ∨ S). Then the grass
must be wet (W).

Going in the other direction, if our aim
is to prove W, we can do it using or.elim
by showing that for some propositions, R
and S, that R ∨ S is true, and that *in
either case*, W must also be true.

The reasoning is by considering each case,
each possible way to have proven R ∨ S,
separately. R ∨ S means that at least one
of R and S is true, and for whichever one
is true, there must be a proof.

We first consider a case where R is true.
Then we consider the case where S is true.
In the case where R is true, we show that
W follows from R → W. In case where S is
true, we show that W follows from S → W.
W holds in either case and so W holds.
-/

/-
Now let's see it working in Lean. We
make a few basic assumptions and then
show how to combine them using the or
elimination rule. We assume ...

1. a proof of P ∨ Q
2. a proof of P → R
3. a proof of Q → R
-/

variable porq : P ∨ Q
variable p2r : P → R
variable q2r : Q → R

/-
Now we derive and check a proof of R
by applying or.elim to these terms..
-/

#check or.elim porq p2r q2r

/-
Here's the same example using more
suggestive names for propositions.
-/

-- Raining, Sprinkling, Wet are Props
variables Raining Sprinkling Wet : Prop

-- rors proves it's Raining ∨ Sprinkling.
variable rors: Raining ∨ Sprinkling

-- r2w proves if Raining then Wet
variable r2w: Raining → Wet

-- s2w proves if Sprinkling then Wet
variable s2w: Sprinkling → Wet

-- Put it all together to show Wet
theorem wet : Wet :=
  or.elim rors r2w s2w

/-
The following program make the arguments to
and the result of or.elim clear, and it
gives an example of the use of or.elim.
-/
theorem orElim :
  ∀ R S W: Prop,
    (R ∨ S) → (R → W) → (S → W) → W
:=
begin
  assume R S W rors r2w s2w,
  show W,
  from or.elim rors r2w s2w
end

/-
This example constructs the same
proof but illustrates how we can
apply inference rules, leaving out
some arguments, to be filled in, in
the style of backward reasoning.
-/
theorem orElim' :
  ∀ R S W: Prop,
    (R ∨ S) → (R → W) → (S → W) → W :=
begin
  assume R S W rors r2w s2w,
  -- apply or.elim
  apply or.elim,
  -- reduce goal to two subgoals
  -- one for each required implication
    exact rors,
    assumption,
    exact s2w,
end

/-
Notice the subtle difference between using
or.elim and cases. The or.elim rule takes
the disjunction and the two implications as
arguments. The cases tactic, on the other
hand, sets you up to apply one or the other
implication depending on the case being
considered.
-/
theorem wet''' :
  ∀ R S W: Prop,
    (R ∨ S) → (R → W) → (S → W) → W :=
begin
  assume R S W r_or_s r2w s2w,
  /-
  We want to show that if W follows from R
  and if W follows from S, then W follows
  R ∨ S.
  -/
  show W, from
    begin
    /-
    What we need to show is that W follows
    from R ∨ S, whether because R is true and
    W follows from R or because S is true and
    W follows from S.  We consider each case
    in turn.
    -/
    cases r_or_s with r s,
    -- W follows from (r : R) and r2w
    show W, from r2w r,
    -- W follows from (r : R) and s2w
    show W, from s2w s,
    -- Thus W follows.
    end
    -- QED
end

/-
We recommend approaching proofs from
disjunctions, as here, using the cases
tactic. It clearly shows that what is
happening is a case analysis. That if
the disjunction is true, then one way
or another we can reach the desired
conclusion.
-/

/-
Here's a proof that false is a right
identity for disjunction. That's a fancy
way of saying that P ∨ false is true if
and only if P is true. They're equivalent
propositions.
-/

theorem id_right_or :
  ∀ P : Prop, P ∨ false ↔ P
  :=
  λ P,
  begin
    -- consider each direction separately
    apply iff.intro,
    -- note: missing args become subgoals

      -- Forward direction: P ∨ false → P
      -- assume a proof of P ∨ false
      assume pfPorfalse,
      -- show P by case analysis on this disjunction
      cases pfPorfalse with pfP pfFalse,
        -- case where we have a proof of P
        assumption,
        -- case with proof of false
        exact false.elim pfFalse,

      -- Reverse direction, easy
      assume p,
      exact or.inl p,
  end

/-
Another example. The proof of another
standard rule of reasoning in natural
deduction.
-/
theorem disjunctiveSyllogism :
  ∀ P Q : Prop, P ∨ Q → ¬Q → P :=
begin
assume P Q porq,
assume nq,
cases porq with p q,
-- first case
assumption,
-- second case
exact (false.elim (nq q)),
end

/-
Exercise: Prove that false is also a
*left* identity for disjunction. That
is: false ∨ P ↔ P (false is on the left).
-/

/-
We now turn to the question, how do
and, or, not, and implies interact?
We prove both of DeMorgan's laws, and
we also show that P → Q ↔ ¬ P ∨ Q. One
of DeMorgan's law and this identity
about → are both classical, and not
valid/provable without the axiom of
the excluded middle (em).
-/

/-
De Morgan had two important laws. They
explain how negations distribute over
disjunctions and conjunctions.

1) ¬P ∧ ¬Q ↔ ¬(P ∨ Q)
2) ¬P ∨ ¬Q ↔ ¬(P ∧ Q)

Consider what the first of DeMorgan's
laws is saying. Start on the right. If
P ∨ Q is not true, then both P and Q
must be false, and if P and Q are both
false, then so is P ∨ Q.

Now the second law. If P and Q is false,
then at least one of P, Q must be false,
so ¬ P ∨ ¬ Q must be true. And if that is
true, then certainly P ∧ Q is false.

Proving that the first law is valid even
in constructive logic is homework 4. We
can only prove the second law using the
axiom of the excluded middle, which we
now proceed to do.
-/

/-
We could declare our own version of the
axiom of the excluded middle, as we have
seen.
-/
axiom excluded_middle: ∀ (P: Prop), P ∨ ¬P

/-
Or, better, is simply to open the classical
namespace in Lean, which gives us access to
the definition, em, of this axiom. We do this
now and then show that the two propositions
are the same. From now on, prefer to use em.
-/
open classical -- "em" is excl. middle. axiom
theorem emIse_m: em = excluded_middle := rfl

/-
And now for DeMorgan's second law.
-/
theorem DemorganLaw2 :
  ∀ P Q: Prop, ¬P ∨ ¬ Q ↔ ¬(P ∧ Q) :=
begin

  show ∀ P Q: Prop, ¬P ∨ ¬ Q ↔ ¬(P ∧ Q), from
  begin
  -- introduce assumptions of P and Q
  assume P Q,

  -- prove implication in each direction
  apply iff.intro,

  -- first, implication in forward direction
  show ¬P ∨ ¬Q → ¬(P ∧ Q), from
    begin
    -- goal is an implication
    -- so assume premise, and ...
    assume pf_np_or_nq,
    -- ... show conclusion
    show ¬(P ∧ Q), from
      begin
      -- remember ¬ is an implication in Lean
      -- so, assume premise, and ...
      assume pf_p_and_q,
      -- show conclusion
      show false, from begin
        -- by case analysis on ¬P ∨ ¬Q
        cases pf_np_or_nq with pf_np pf_nq,
        -- consider case where ¬P is true
          have pf_p := pf_p_and_q.left,
          exact pf_np pf_p,
        -- case where ¬Q is true
          have pf_q := pf_p_and_q.right,
          exact pf_nq pf_q,
        end
      end,
    end,

  -- now implication in reverse direction
    show ¬(P ∧ Q) → ¬P ∨ ¬Q, from
      begin
      -- assume premise
      assume pf_n_p_and_q,
      show ¬P ∨ ¬Q, from
        begin
        -- consider cases for P using em
        cases (em P) with pf_p pf_np,
          -- consider cases for Q using em
          cases em Q with pf_q pf_nq,
            -- P true, Q true
            have pf_p_and_q := and.intro pf_p pf_q,
            have pf_false := pf_n_p_and_q pf_p_and_q,
            exact false.elim pf_false,
            -- P true, Q false
            exact or.intro_right (¬P) pf_nq,
          -- P false
          exact or.intro_left (¬Q) pf_np,
      end,
    end,
  end,
end

/-
Assuming the axiom of the excluded middle,
we can also show that P → Q is equivalent
to ¬P ∨ Q. Think about what P → Q means:
whenever P is true, Q is true. So either
P is false, in which case P → Q is true,
or Q is true (along with P). Here we use
the axiom of the excluded middle to limit
the possibilities for P to these cases.

In this proof we take a more spartan
approach to using show/from. It's really
a matter of purpose and style to use a
lot of show/from or not. It does make
proofs more self-explanatory, but it can
also make them unnecessarily verbose in
cases where they should be clear enough
in any case to one who knows how to read
this kind of code.

Still, in the first half of this code,
we include English language comments. In
the second half, we just leave them out.
For now we recommend that you at least
include English language comments. In
many cases, you could then edit them
end end up with a good English language
proof, forgetting the formalism! So you
are on your way now to understanding how
to write informal proofs.
-/

theorem altImplication:
  ∀ P Q: Prop, (P → Q) ↔ (¬P ∨ Q) :=
begin
  -- to show ∀ P Q: Prop, (P → Q) ↔ (¬P ∨ Q)
  -- we first introduce assumptions of P and Q
  assume P Q,
  -- next we prove implication in each direction
  apply iff.intro,
    -- first we prove forward direction
    -- we start by assuming the premise
    assume pf_p_imp_q,
    -- note that without em, we're stuck
    -- key is case analysis on P given em
    cases (em P) with pf_p pf_np,
      -- case where P is true
      -- construct a proof of Q
      have pf_q := (pf_p_imp_q pf_p),
      -- then prove goal by or intro
      --exact or.intro_right (¬P) pf_q,
      exact or.inr pf_q, -- shorhand!
      -- case where ¬ P is true is easy
      exact or.inl pf_np, -- shorthand

    -- prove implication reverse direction
    assume pf_np_or_q,
    assume pf_p,
    cases pf_np_or_q with pf_np pf_q,
      have pf_false := (pf_np pf_p),
      exact false.elim pf_false,
      assumption,
end

theorem altEquivalence :
∀ { P Q : Prop }, (P ↔  Q) ↔ ((P ∧ Q) ∨ (¬P ∧ ¬Q))
:=
begin
assume P Q,
apply iff.intro,

intros,
have pq := iff.elim_left a,
have qp := iff.elim_right a,
cases (em P) with p np,
cases (em Q) with q nq,

exact or.inl (and.intro p q),

exact false.elim (nq (pq p)),

cases (em Q) with q nq,
exact false.elim (np (qp q)),
exact or.inr (and.intro np nq),

intros,
cases a,

apply iff.intro,
intro,
exact a.2,

intro,
exact a.1,

apply iff.intro,
intro,
exact (false.elim (a.1 a_1)),

intro,
exact (false.elim (a.2 a_1)),
end

/-
Recall, that once we've prove a theorem,
we can then henceforth use it in larger
proofs. Here's an example showing how we
could use iffExplained, just proved, to
transform a goal, P ↔ Q, into the form
of a corresponding disjunction.
-/
example : ∀ P Q : Prop, P ↔ Q :=
begin
-- Assume P and Q are propositions
intros,
-- Apply backwards implication
-- Goal matches right side of implication
-- reducing goal to left side with substs
apply altEquivalence.elim_right,
-- abandon, never meant to complete proof
end


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

import tactic -- imports all the Lean tactics

/-!

# Sets in Lean, example sheet 3 : not in (`∉`)

The definition in Lean of `x ∉ A` is `¬ (x ∈ A)`. In other words,
`x ∉ A`, `¬ (x ∈ A)` and `(x ∈ A) → false` are all equal *by definition*
in Lean. This means that they are all interchangeable, and you can
change between them using the `change` tactic, or you can just keep this
in mind. For example, if you have a hypothesis `h : x ∉ A` and your goal
is `false`, then `apply h` will work and will change the goal to `x ∈ A`.

## Tactics

You can do all these levels just with `intros`, `apply` and `exact`!

-/

open set

variables
  (X : Type) -- Everything will be a subset of `X`
  (A B C D E : set X) -- A,B,C,D,E are subsets of `X`
  (x y z : X) -- x,y,z are elements of `X` or, more precisely, terms of type `X`

example : x ∉ A → (x ∈ A → false) :=
begin
  sorry
end

example : x ∈ A → (x ∉ A → false) :=
begin
  sorry
end

example : (∀ t, t ∈ A → t ∈ B) → x ∉ B → x ∉ A :=
begin
  sorry
end

example : x ∉ (∅ : set X) :=
begin
  sorry
end


-- Exercise 1
variables (α : Type) (p q : α → Prop)

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
begin
    apply iff.intro,
        assume forward,
        apply and.intro,
            assume x,
            show p x, from (forward x).1,

            assume x,
            show q x, from (forward x).2,

        assume backward,
        assume x,
        apply and.intro,
            show p x, from backward.1 x,
            show q x, from backward.2 x
end

example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
begin
        assume forward,
        assume backward,
        assume a,
        exact forward a (backward a),
end

example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
begin
    assume forward,
    assume x,
    cases forward with px qx,
        show p x ∨ q x, from or.inl (px x),
        show p x ∨ q x, from or.inr (qx x)
end

-- Exercise 2
-- It is often possible to bring a component of a formula outside
-- a universal quantifier, when it does not depend on the quantified variable.
-- Try proving these (one direction of the second of these requires classical logic):
variable r : Prop

example : α → ((∀ x : α, r) ↔ r) :=
begin
    assume a,
    apply iff.intro,
        assume ar,
        show r, from ar a,

        assume pfr a,
        show r, from pfr
end

section
    open classical
    example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
    begin
        apply iff.intro,
            assume forward,
            cases em r with pfr pfnr, -- this direction requires classical resoning
                show (∀ x, p x) ∨ r, from or.inr pfr,

                apply or.inl,
                    assume a,
                    have pa : p a ∨ r := forward a,
                    cases pa with pfpa pfr,
                        show p a, from pfpa,
                        contradiction,

            assume backward,
            assume a,
            cases backward with pfpx pfr,
                show p a ∨ r, from or.inl (pfpx a),
                show p a ∨ r, from or.inr pfr
    end
end

example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
begin
    apply iff.intro,
        assume forward,
        assume pfr,
        assume a,
        show p a, from forward a pfr,

        assume backward,
        assume a,
        assume pfr,
        show p a, from backward pfr a,
end

-- Exercise 3
-- Consider the “barber paradox,” that is, the claim that
-- in a certain town there is a (male) barber that shaves all
-- and only the men who do not shave themselves.
-- Prove that this is a contradiction:
variables (men : Type) (barber : men)
variable (shaves : men → men → Prop)

example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : false
    :=
    begin
        have h1 := h barber,
        -- same as the proof of ¬(p ↔ ¬p) in chap3 exercises
        have forward := iff.elim_left h1,
        have backward := iff.elim_right h1,
        have nf : ¬shaves barber barber := λ a, (forward a) a,
        show false, from nf (backward nf)
    end

--Exercise 4
-- Below, we have put definitions of divides and even in a
-- special namespace to avoid conflicts with definitions in the library.
-- The instance declaration make it possible to use the notation
-- m | n for divides m n. Don’t worry about how it works;
-- you will learn about that later.
namespace hidden

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

instance : has_dvd nat := ⟨divides⟩

def even (n : ℕ) : Prop := 2 ∣ n

section
  variables m n : ℕ

  #check m ∣ n
  #check m^n
  #check even (m^n +3)
end

-- Remember that, without any parameters, an expression of
-- type Prop is just an assertion. Fill in the definitions of prime
-- and Fermat_prime below, and construct the given assertion.
-- For example, you can say that there are infinitely many primes
-- by asserting that for every natural number n, there is a prime number
-- greater than n. Goldbach’s weak conjecture states that
-- every odd number greater than 5 is the sum of three primes.
-- Look up the definition of a Fermat prime or any of the other statements,
-- if necessary.

def prime (n : ℕ) : Prop := n ≥ 2 ∧ ∀ x ∣ n, x = 1 ∨ x = n

def infinitely_many_primes : Prop := ∀ n, prime n → ∃ x, x > n

def Fermat_prime (n : ℕ) : Prop := ∀ n ≥ 0, prime (2 ^ 2 ^ n + 1)

def infinitely_many_Fermat_primes : Prop := ∀ n, Fermat_prime n → ∃ x, x > n

def goldbach_conjecture : Prop :=
   ∀ n ≥ 2, even n → (∃ x y, prime x → prime y → n = x + y)

def Goldbach's_weak_conjecture : Prop :=
    ∀ n, ¬ (even n) → (∃ x y z, prime x → prime y → prime z → n = x + y + z)

def Fermat's_last_theorem : Prop :=
    ∀ n ≥ 3, ¬ (∃ x y z, x ≥ 1 → y ≥ 1 → z ≥ 1 → x^n + y^n = z^n)

end hidden

-- Exercise 5

open classical
variable a : α -- required in the second one and the last two

example : (∃ x : α, r) → r :=
begin
    assume : ∃ (x : α), r,
    apply exists.elim this,
    intros, assumption
end

section
    include a
    example : r → (∃ x : α, r) :=
    begin
        assume pfr,
        apply exists.intro a,
        assumption
    end
end

example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
begin
    apply iff.intro,
        assume h,
        apply and.intro,
            apply exists.elim h,
                assume a : α,
                assume par : p a ∧ r,
                exact ⟨a, par.1⟩,
            apply exists.elim h,
                assume a : α,
                assume par : p a ∧ r,
                show r, from par.2,

        assume h,
        apply exists.elim h.1,
            assume a : α,
            assume pa : p a,
            apply exists.intro a,
                apply and.intro,
                    assumption,
                    show r, from h.2
end

example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
begin
    apply iff.intro,
        assume h : ∃ (x : α), p x ∨ q x,
        apply exists.elim h,
            assume a,
            assume : p a ∨ q a,
            cases this with pfpa pfqa,
                apply or.inl,
                    exact ⟨a, pfpa⟩,
                apply or.inr,
                    exact ⟨a, pfqa⟩,

        assume h : (∃ (x : α), p x) ∨ ∃ (x : α), q x,
        cases h with epx eqx,
            apply exists.elim epx,
                assume a pa,
                apply exists.intro a,
                    show p a ∨ q a, from or.inl pa,

            apply exists.elim eqx,
                assume a qa,
                apply exists.intro a,
                   show p a ∨ q a, from or.inr qa,
end

example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
begin
    apply iff.intro,
        assume h : ∀ (x : α), p x,
        assume e : ∃ (x : α), ¬p x,
        show false,
            apply exists.elim e,
                assume a,
                assume npa : ¬ p a,
                have : p a := h a,
                show false, from npa this,

        assume ne : ¬∃ (x : α), ¬p x,
        assume a,
        apply by_contradiction,
            assume npa : ¬ p a,
            have : ∃ (x : α), ¬ p x := ⟨a, npa⟩,
            contradiction,
end

theorem e_na_nt : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
begin
    apply iff.intro,
        assume h,
        assume f,
        apply exists.elim h,
            assume a,
            assume pa : p a,
            have : ¬ p a, from f a,
            contradiction,

        assume h : ¬∀ (x : α), ¬p x,
            apply by_contradiction,
                assume notE : ¬∃ (x : α), p x,
                have : ∀ (x : α), ¬ p x,
                    assume x,
                    assume px,
                    have : ∃ (x : α), p x,
                        from ⟨x, px⟩,
                    contradiction,
                contradiction
end

example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) :=
begin
    apply iff.intro,
        assume h,
        assume a,
        assume pa,
        show false, from h ⟨a, pa⟩,

        assume h,
        assume e,
        apply exists.elim e,
            assume a,
            assume pa : p a,
            have : ¬ p a, from h a,
            contradiction,
end

example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) :=
begin
    apply iff.intro,
        assume h : ¬∀ (x : α), p x,
        apply by_contradiction,
            assume ne : ¬∃ (x : α), ¬p x,
            have : ∀ (x : α), p x,
                assume x,
                apply by_contradiction,
                    assume npx,
                    have : ∃ (x : α), ¬p x, from ⟨x, npx⟩,
                    contradiction,
            contradiction,

        assume h : (∃ (x : α), ¬p x),
        assume f : ∀ (x : α), p x,
        apply exists.elim h,
            assume a,
            assume npa : ¬ p a,
            have : p a, from f a,
            contradiction,
end

example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
begin
    apply iff.intro,
        assume h,
        assume e,
        apply exists.elim e,
            assume a,
            assume pa : p a,
            have : p a → r, from h a,
            show r, from this pa,

        assume e : (∃ (x : α), p x) → r,
        assume a,
        assume pa,
        have : (∃ (x : α), p x), from ⟨a, pa⟩,
        show r, from e this
end

-- This is one of the chapter 3 exercises,
-- used in the proof of ((∀ x, p x) → r) → (∃ x, p x → r)
theorem npq_pandnq : ∀ {p q : Prop}, ¬(p → q) → p ∧ ¬q :=
begin
    assume p q,
    assume notpq : ¬(p → q),
    apply and.intro,
        apply by_contradiction,
            assume np : ¬ p,
            have pq : p → q,
                assume pfp,
                contradiction,
            contradiction,

        assume pfq,
        have pq : p → q := assume pfp, pfq,
        contradiction,
end

include a
example : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
begin
    apply iff.intro,
        assume e f,
        apply exists.elim e,
            assume x,
            assume pxr,
            have : p x, from f x,
            show r, from pxr this,

        -- this one is really hard
        assume h : (∀ (x : α), p x) → r,
            apply iff.elim_right (e_na_nt α (λ x : α, p x → r)),
                assume npxr : ∀ (x : α), ¬(p x → r),
                have xpx: (∀ (x : α), p x),
                    assume x,
                    have : ¬(p x → r), from npxr x,
                    have : p x ∧ ¬r, from npq_pandnq this,
                    show p x, from this.1,
                have npar: ¬(p a → r), from npxr a,
                have pfr : r, from h xpx,
                have : p a → r,
                    assume _, assumption,
                show false, from npar this
end

example : (∃ x, r → p x) ↔ (r → ∃ x, p x) :=
begin
    apply iff.intro,
        assume e,
        assume pfr,
        apply exists.elim e,
            assume x,
            assume rpx : r → p x,
            have : p x, from rpx pfr,
            show ∃ (x : α), p x, from ⟨x, this⟩,

        assume re : r → (∃ (x : α), p x),
        cases em r with pfr pfnr,
            have : (∃ (x : α), p x), from re pfr,
            apply exists.elim this,
                assume x px,
                show ∃ (x : α), r → p x,
                    from ⟨x, assume r, px⟩,

            apply exists.intro a,
                assume pfr,
                contradiction,
end

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

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

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

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

theorem log_mul {x y : real} (hx : x > 0) (hy : y > 0) :
    log (x * y) = log x + log y :=
begin
    have z : exp (log (x * y)) = exp (log x + log y) :=
        calc
        exp (log (x * y)) = x * y : by rw exp_log_eq (mul_pos hx hy)
        ... = exp (log x) * exp (log y) : by rw [exp_log_eq hx, exp_log_eq hy]
        ... = exp (log x + log y) : by rw exp_add,

    have pf : log (exp (log (x * y))) = log (exp (log x + log y)) :=
    congr_arg log z,

    have lhs := calc
        log (exp (log (x * y))) = log (x * y) : by rw log_exp_eq,

    have rhs := calc
        log (exp (log x + log y)) = log x + log y : by rw log_exp_eq,

    show log (x * y) = log x + log y, from calc
        log (x * y) = log x + log y : by rw [eq.symm(lhs), pf, rhs],
end

-- Exercise 7
-- Prove the theorem below, using only the
-- ring properties of ℤ enumerated in Section 4.2 and the theorem sub_self.
#check sub_self

example (x : ℤ) : x * 0 = 0 :=
calc
    x * 0 = x * (x - x) : by rw sub_self
    ... = x * x - x * x : by rw mul_sub
    ... = 0 : by rw sub_self

example (x : ℤ) : x * 0 = 0 :=
calc
    x * 0 = 0 : by simp


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

import tactic -- imports all the Lean tactics

/-!

# Sets in Lean, example sheet 4 : exists (`∃`)

In this sheet we learn how to manipulate `∃` in Lean. We will see
statements of the form `∃ x, x ∈ A`. We will need to learn two new
tactics -- one for making progress when the goal is `⊢ ∃ x, x ∈ A`,
and one for making progress when we have a hypothesis `h : ∃ x, x ∈ A`.

Now we have `∃` and `∀`, and `∈` and `∉`, we can finally get going
with some harder levels.

## New tactics you will need to know

`cases` or `rcases` -- to get the `x` from a hypothesis `h : ∃ x, ...`
`use` -- to make progress on goals of the form `⊢ ∃ x, ...`

### The `cases` tactic

We've seen this tactic before to take apart `h : P ∧ Q`. We can also
use it to take apart `h : ∃ t : X, F t`: if `h` is such a hypothesis
then `cases h with x hx,` will give us `x : X` and `hx : F x`

### The `use` tactic

If we have a goal `⊢ ∃ x : X, F x` and a term `a : X` which we know
will work, then `use a` will change the goal to `F a`. By the way,
`use` tries `refl` so it might magically close goals early

-/

open set

variables
  (X : Type) -- Everything will be a subset of `X`
  (A B C D E : set X) -- A,B,C,D,E are subsets of `X`
  (x y z : X) -- x,y,z are elements of `X` or, more precisely, terms of type `X`

example : x ∈ A → ∃ t, t ∈ A :=
begin
  intro h,
  use x, exact h,
end

example : (∀ x, x ∈ A) ↔ ¬ (∃ x, x ∉ A) :=
begin
  split,
  {intro h,
  change (∃ x, x ∉ A) → false,
  intro h',
  cases h' with x hx,
  specialize h x,
  trivial,},
  intro h₁,
  change (∃ x, x ∉ A) → false at h₁,
  intro x,
  by_cases (x ∈ A),
  exact h,
  have h1: ∃ x, x ∉ A, {use x,},
  exfalso,
  exact h₁ h1,
end

example : (∃ x, x ∈ A) ↔ ¬ (∀ x, x ∉ A) :=
begin
  split,
  {intro h,
  change (∀ x, x ∉ A) → false,
  intro h1,
  cases h with x hx,
  specialize h1 x,
  trivial,},
  intros h,
  by_contra h1,
  apply h,
  intro x,
  by_cases (x ∈ A),
  {exfalso,
  have h2: ∃ x : X, x ∈ A, {use x, exact h,},
  trivial,},
  exact h,
end