/- Exercise 2.2: Functional Programming — Trees -/

/- Question 1: Even and odd -/

/- 1.1. Define an inductive predicate `even` on `ℕ` that is true for even numbers and false for odd
numbers.

Hint: Your predicate should have two introduction rules, one for the 0 case and one for the `n + 2`
case. -/

inductive even : ℕ → Prop
| zero : even 0
| even_plus_two (n: ℕ) (h: even n): even(n + 2)

/- 1.2. Prove that 0, 2, 4, and 6 are even. -/

lemma iseven0: even 0 := by {apply even.zero}
lemma iseven2: even 2 := by {apply even.even_plus_two; apply even.zero}
lemma iseven4: even 4 := by {repeat{apply even.even_plus_two}; apply even.zero}
lemma iseven6: even 6 := by {repeat{apply even.even_plus_two}; apply even.zero}

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

/- 1.3. 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 ...`. -/

lemma isodd: odd 1 := by {intro h; cases h}

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

lemma isodd3 : odd 3 := by {intro s; cases s, cases s_h}
lemma isodd5 : odd 5 := by {intro s; cases s, cases s_h, cases s_h_h}
lemma isodd7 : odd 7 := by {intro s; cases s, cases s_h, cases s_h_h, cases s_h_h_h}

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

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

/- 1.6. 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
| _ even.zero          := ⟨0, by simp⟩
| _ (even.add_two n h) :=
sorry

/- 1.7. 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 -/

namespace my_bin_tree

/- Recall the binary trees from the lecture. As usual, we omit the proofs using `sorry`. -/

inductive tree (α : Type) : Type
| empty {} : tree
| node (a : α) (l : tree) (r : tree) : tree

export tree (empty node)

def mirror {α : Type} : tree α → tree α
| empty        := empty
| (node a l r) := node a (mirror r) (mirror l)

lemma empty_mirror_iff {α : Type} : ∀t : tree α, mirror t = empty ↔ t = empty := sorry
lemma mirror_mirror {α : Type} : ∀t : tree α, mirror (mirror t) = t := sorry

inductive is_full {α : Type} : tree α → Prop
| empty : is_full empty
| node (a : α) (l r : tree α) (hl : is_full l) (hr : is_full r)
    (empty_iff : l = empty ↔ r = empty) :
    is_full (node a l r)

lemma is_full_singleton {α : Type} (a : α) : is_full (node a empty empty) := sorry
lemma is_full_mirror {α : Type} : ∀t : tree α, is_full t → is_full (mirror t) := sorry

/- 2.1. State and prove the converse of `is_full_mirror`. -/

-- enter your answer here

/- 2.2. Define a function that counts the number of constructors (`empty` or `node`) in a tree. -/

def count {α : Type} : tree α → ℕ
:= sorry

end my_bin_tree

/- Question 3: λ-terms -/

namespace my_lambda_term

/- 3.1. Define an inductive type corresponding to the untyled λ-terms, as given by the following
context-free grammar:

<lam> ::= 'var' <string>
        | 'abs' <string> <lam>
        | 'app' <lam> <lam>
-/

-- enter your definition here

/- 3.2. Define an inductive predicate `is_βnf` that determines whether a lambda term is in β-normal
form (https://en.wikipedia.org/wiki/Beta_normal_form). -/

-- enter your answer here

/- 3.3. Register a textual representation of the type `lam`, as we did for Huffman trees in the
lecture. -/

-- enter your answer here

end my_lambda_term


-- ---------------------------------------------------------------------
-- Ejercicio 1. Importar la librería de los números reales.
-- ----------------------------------------------------------------------

import data.real.basic

-- ---------------------------------------------------------------------
-- Ejercicio 2. Enunciar el lema ej: "para todos los números reales x,
-- y, ε si
--    0 < ε
--    ε ≤ 1
--    abs x < ε
--    abs y < ε
-- entonces
--    abs (x * y) < ε
-- ----------------------------------------------------------------------

-- lemma ej :
--   ∀ x y ε : ℝ,
--   0 < ε →
--   ε ≤ 1 →
--   abs x < ε →
--   abs y < ε →
--   abs (x * y) < ε :=
-- sorry

-- ---------------------------------------------------------------------
-- Ejercicio 3. Crear una sección con las siguientes declaraciones
--    a b δ : ℝ
--    h₀ : 0 < δ
--    h₁ : δ ≤ 1
--    ha : abs a < δ
--    hb : abs b < δ
-- y calcular el tipo de las siguientes expresiones
--    ej a b δ
--    ej a b δ h₀ h₁
--    ej a b δ h₀ h₁ ha hb
-- ----------------------------------------------------------------------

section

variables a b δ : ℝ
variables (h₀ : 0 < δ) (h₁ : δ ≤ 1)
variables (ha : abs a < δ) (hb : abs b < δ)

-- #check ej a b δ
-- #check ej a b δ h₀ h₁
-- #check ej a b δ h₀ h₁ ha hb

-- Comentario: Al colocar el cursor sobre check se obtiene
--    ej a b δ : 0 < δ → δ ≤ 1 → abs a < δ → abs b < δ → abs (a * b) < δ
--    ej a b δ h₀ h₁ : abs a < δ → abs b < δ → abs (a * b) < δ
--    ej a b δ h₀ h₁ ha hb : abs (a * b) < δ

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
  -- sorry
  have key : (b + c) - (a + c) = b - a,
  { ring },
  rw ← sub_nonneg,
  rw key,
  rw sub_nonneg,
  exact hab,
  -- sorry
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
  -- sorry
  calc a = a + 0 : by ring
     ... ≤ a + b : add_le_add_left hb a,
  -- sorry
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
  -- sorry
  calc 0 ≤ a     : ha
     ... ≤ a + b : le_add_of_nonneg_right hb,
  -- sorry
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
  -- sorry
  calc
  a + c ≤ b + c : add_le_add_right hab c
  ...   ≤ b + d : add_le_add_left hcd b,
 -- sorry
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
  -- sorry
  rw ← sub_nonneg,
  have fact : a*c - b*c = (a - b)*c,
    ring,
  rw fact,
  apply mul_nonneg_of_nonpos_of_nonpos,
  { rwa sub_nonpos },
  { exact hc },
  -- sorry
end

/- Using forward reasoning -/
-- 0014
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  -- sorry
  have hab' : a - b ≤ 0,
  { rwa ← sub_nonpos at hab, },
  have h₁ : 0 ≤ (a - b)*c,
  { exact mul_nonneg_of_nonpos_of_nonpos hab' hc },
  have h₂ : (a - b)*c = a*c - b*c,
  { ring, },
  have h₃ : 0 ≤ a*c - b*c,
  { rwa h₂ at h₁, },
  rwa sub_nonneg at h₃,
  -- sorry
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
  -- sorry
  have hab' : a - b ≤ 0,
  { rwa sub_nonpos },
  rw ← sub_nonneg,
  calc 0 ≤ (a - b)*c  : mul_nonneg_of_nonpos_of_nonpos hab' hc
     ... =  a*c - b*c : by ring,
  -- sorry
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
  -- sorry
  intros hb,
  calc a = a + 0 : by ring
     ... ≤ a + b : add_le_add_left hb a,
  -- sorry
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
  -- sorry
  intro hyp,
  cases hyp with hP hQ,
  split,
  exact hQ,
  exact hP,
  -- sorry
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
  -- sorry
  rintros ⟨hP, hQ⟩,
  exact ⟨hQ, hP⟩,
  -- sorry
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
  -- sorry
  split,
  { intros hyp hP hQ,
    exact hyp ⟨hP, hQ⟩ },
  { rintro hyp ⟨hP, hQ⟩,
    exact hyp hP hQ },
  -- sorry
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
  -- sorry
  linarith,
  -- sorry
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
  -- sorry
  linarith,
  -- sorry
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
  -- sorry
  have fact : gcd a b ∣ a ∧ gcd a b ∣ b,
  { rw ← dvd_gcd_iff },
  split,
  { intro h,
    apply dvd_antisymm fact.left,
    rw dvd_gcd_iff,
    exact ⟨dvd_refl a, h⟩ },
  { intro h,
    rw ← h,
    exact fact.right },
  -- sorry
end


/-
Given any two propositions, P and Q, we can form
the proposition, P → Q. That is the syntax of an
implications.

If P and Q are propositions, we read P → Q as
P implies Q. A proof of a proposition, P → Q,
is a program that that converts any proof of P
into a proof of Q. The type of such a program
is P → Q.
-/

/- Elimination Rule for Implication -/

/-
From two premises, (1) that "if it's raining then
the streets are wet," and (2) "it's raining,"" we
can surely derive as a conclusion that the streets
are wet. Combining "raining → streets wet" with
"raining" reduces to "streets wet." This is modus
ponens.

Let's abbreviate the proposition "it's raining",
as R, and let's also abbreviate the proposition,
"the streets are wet as W". We will then abbreviate
the proposition "if it's raining then the streets
are wet" as R → W.

Such a proposition is in the form of what we call
an implication. It can be pronounced as "R implies
W", or simply "if R is true then W must be true."
Note that by this latter reading, in a case where
R is not true, the implication says nothing about
whether W must be true or not. We will thus judge
an implication to be true if either R is false (in
which case the implications does not constrain W
to be any value), or if whenever R is true, W is,
too. The one situation under which we will not be
able to judge R → W to be true is if it can be
the case that R is true and yet W is not, as that
would contradict the meaning of an implication.

With these abbreviations in hand, we can write
an informal inference rule to capture the idea
we started with. If we know that a proposition,
R → W, is true, and we know that the proposition,
R, is true, then we can deduce that W therefore
must also be true. We can write this inference
rule informally like this:

  R → W, R
  -------- (→ elim)
     W

This is the arrow (→) elimination rule?

In the rest of this chapter, we will formalize
this notion of inference by first presenting the
elimination rule for implication. We will see
that this rule not only formalizes Aristotle's
modus ponens rule of reasoning (it is one of
his fundamental "syllogisms"), but it also
corresponds to function application!

EXERCISE: When you apply a function that takes
an argument of type R and returns a value of
type W to a value of type R, what do you get?
-/

/-
Now let's specify that R and W are arbitrary
propositions in the type theory of Lean. And
recall that to judge R → W to be true or to
judge either R or W to be true means that we
have proofs of these propositions. We can now
give a precise rule in type theory capturing
Aristotle's modus ponens: what it means to be
a function, and how function application works.

{ R W : Prop }, pfRtoW : R → W, pfR : R
--------------------------------------- (→-elim)
                   pfW: W

Here it is formalized as a function.
-/

def
arrow_elim
    { R W: Prop } (pfRtopfW : R → W) (pfR : R) :  W :=
        pfRtopfW pfR

/-
This program expresses the inference rule. The
name of the rule (a program) is arrow_elim. The
function takes (1) two propositions, R and W;
(2) a proof of R → W (itself a program that
converts and proof of R into a proof of W;
(3) a proof of R. If promises that if it is
given any values of these types, it will
return a proof of(a value of type) W. Given
values for its arguments it derives a proof
of W by applying that given function to that
given value. The result will be a proof of (a
value of type) W.

We thus now have another way to pronounce this
inference rule: "if you have a function that can
turn any proof of R into a proof of W, and if you
have a proof of R, then you obtain a proof of W,
and you do it in particular by applying the
function to that value."
-/

/- In yet other words, if you've got a function
and you've got an argument value, then you can
eliminate the function (the "arrow") by applying
the function to that value, yielding a value of
the return type.
-/

/-
A concrete example of a program that serves as
a proof of W → R is found  in our program, from
the 03_Conjunction chapter that turns any proof
of P ∧ Q (W) into a proof Q ∧ P (R).

We wrote that code so that for any propositions,
P and Q, for any proof of P ∧  Q, it returns a
proof of Q ∧ P. It can always do this because
from any proof of P ∧ Q, it can obtain separate
proofs of P and Q that it can then re-assembled
into a proof of Q ∧ P. That function is a proof
of this type: ∀ P Q: Prop, P ∧ Q → Q ∧ P.  That
says, for any propositions, P and Q, a function
of this type turns a proof of P ∧ Q into a proof
of Q ∧ P. It thus proves P ∧ Q → Q ∧ P.
-/

/-
We want to give an example of the use of the
arrow-elim rule.  In this example we use a new
(for us) capability of Lean: you can define
variables to be give given types without proofs,
i.e., as axioms.

Here (read the code) we assume that P and Q
are arbitrary propositions. We do not say
what specific propositions they are. Next
we assume that we have a proof that P → Q,
which will be represented as a program
that takes proofs of Ps and returns proofs
of Qs. Third we assume that we have some
proof of P. And finally we check to see
that the result of applying impl to pfP is
of type Q.
-/

variables P Q : Prop
variable impl : P → Q
variable  pfP : P
#check (impl pfP)
#check (arrow_elim impl pfP)

/-
In Lean, a proof of R → W is given as a
program: a "recipe" for making a proof of W
out of a proof of R. With such a program in
hand, if we apply it to a proof of R it will
derive a proof of W.
-/

/-
EXAMPLE: implications involving true and false
-/

/-
Another way to read P → Q is "if P (is true)
then Q (is true)."

We now ask which of the following implications
can be proved?

true → true     -- if true (is true) then true (is true)
true → false    -- if true (is true) then false (is true)
false → true    -- if false (is true) then true (is true)
false → false   -- if false (is true) then false (is true)

EXERCISE: What is your intuition?

Hint: Remember the unit on true and false. Think about
the false elimination rule. Recall how many proofs there
are of the proposition, false.
-/

/- true → true -/

 /-
 Let's see one of the simplest of all possible
 examples to make these abstract ideas concrete.
 Consider the proposition, true → true. We can
 read this as "true implies true". But for our
 purposes, a better way to say it is, "if you
 assume you are given a proof of true, then you
 can construct and return a proof of true."

We can also see this proposition as the type of
any program that turns a proof of true into a
proof of true. That's going to be easy! Here it
is: a simple function definition in Lean. We call
the program timpt (for "true implies true"). It
takes an argument, pf_true, of type true, i.e.,
a proof of true, as an argument, and it builds
and returns a proof of true by just returning
the proof it was given! This function is thus
just the identity function of type true → true.
-/

def timpt ( pf_true: true ) : true := pf_true

theorem timpt_theorem : true → true := timpt

#check timpt

/-
If this program is given a proof, pf, of true,
it can and does return a proof of true. Let's
quickly verify that by looking at the value we
get when we apply the function (implication) to
true.intro, which is the always-available proof
of true.
-/

#reduce (timpt true.intro)

/-
Indeed, this program is in effect a proof
of true → true because if it's given any
proof of true (there's only one), it then
returns a proof of true.

Now we can see explicitly that this program
is a proof of the proposition, true → true,
by formalizing the proposition, true → true,
and giving the program as a proof!
-/

theorem true_imp_true : true → true := timpt

/-
And the type of the program, which we are
now interpreting as a proof of true → true,
is thus true → true. The program is a value
of the proposition, and type, true → true.
-/

#check timpt

/- true → false -/

/-
EXERCISE: Can you prove true → false? If
so, state and prove the theorem. If not,
explain exactly why you think you can't
do it.
-/

-- def timpf (pf_true : true) : false := _

-- theorem timpf_theorem: true → false := _

/- false → true -/

/-
EXERCISE: Prove false → true. The key to
doing this is to remember that applying
false.elim (think of it as a function) to
a proof of false proves anything at all.
-/

def fimpt (f: false) : true := true.intro

theorem fimpt_theorem : false → true := fimpt

/- false → false -/

/-
EXERCISE: Is it true that false → false?
Prove it. Hint: We wrote a program that
turned out to be a proof of true → true.
If you can write such a program, call it
fimpf, then use it to prove the theorem,
false_imp_false: false → false.
-/

def fimpf (f: false) : false := f

theorem fimpf_theorem : false → false := fimpf

def fimpzeqo (f: false) : 0 = 1 := false.elim f

theorem fizeo : false → 0 = 1 := fimpzeqo

/-
We summarize our findings in the following
table for implication.

true  →  true  :  proof, true
true  →  false :  <no proof>
false →  true  :  proof, true
false →  false :  proof, true

What's deeply interesting here is that
we're not just given these judgments as
unexplained pronouncements. We've *proved*
three of these judgments. The fourth we
could not prove, and we made an argument
that it can't be proved, but we haven't
yet formally proved, nor do even have a
way to say yet, that the proposition is
false. The best we can say at this time
is that we don't have a proof.
-/

#check true_imp_true  -- (proof of) implication
#check true.intro     -- (proof of) premise
#check (true_imp_true true.intro) -- conclusion!

/- *** → INTRODUCTION RULE -/

/-
The → introduction rules say that if
assuming that there is proof of P allows
you to derive a proof of Q, then one can
derive a proof of P → Q, discharging the
assumption.

To represent this rule as an inference
rule, we need a notation to represent
the idea that from an assumption that
there is a proof of P one can derive a
proof of Q. The notation used in most
logic books represents this notion as a
vertical dotted line from a P above to
a Q below. If one has such a derivation
then one can conclude P → Q. The idea
is that the derivation is in essence a
program; the program is the proof; and
it is a proof of the proposition, which
is to say, of the type, P → Q.

   P
   |
   |
   Q
 -----
 P → Q

The proof of a proposition, P → Q, in
Lean, is thus a program that takes an
argument of type P and returns a result
of type Q.
-/

/- ** Direct Proofs: Proof Strategy -/

/-
Many of the propositions you need to
prove in practice are implications, of
the form P → Q. It's a way of saying,
"under conditions defined by P it must
be the case that Q is also true. A
direct proof is just what we have
seen: a way to derive a proof of Q
from an assumed proof of P.

To prove P → Q, you thus start with an
assumption that there's a proof of P,
and from that you deduce a proof of Q.
-/

/-
EXAMPLE: Give a direct proof of eqsym:
a = b → b = a. Give it as both a lambda
expression and as an assume-show-from
tactic script.
-/

lemma eqsym :
    ∀ a b : nat, a = b → b = a
        := sorry

lemma eqsym' :
   ∀ a b : nat, a = b → b = a
     := begin
        sorry
        end

/-
http://zimmer.csufresno.edu/~larryc/proofs/proofs.direct.html
-/


import data.real.basic

/-
One of the earliest kind of proofs one encounters while learning mathematics is proving by
a calculation. It may not sound like a proof, but this is actually using lemmas expressing
properties of operations on numbers. It also uses the fundamental property of equality: if two
mathematical objects A and B are equal then, in any statement involving A, one can replace A
by B. This operation is called rewriting, and the Lean "tactic" for this is `rw`.

In the following exercises, we will use the following two lemmas:
  mul_assoc a b c : a * b * c = a * (b * c)
  mul_comm a b : a*b = b*a

Hence the command
  rw mul_assoc a b c,
will replace a*b*c by a*(b*c) in the current goal.

In order to replace backward, we use
  rw ← mul_assoc a b c,
replacing a*(b*c) by a*b*c in the current goal.

Of course we don't want to constantly invoke those lemmas, and we will eventually introduce
more powerful solutions.
-/

-- Uncomment the following line if you want to see parentheses around subexpressions.
-- set_option pp.parens true

example (a b c : ℝ) : (a * b) * c = b * (a * c) :=
begin
  rw mul_comm a b,
  rw mul_assoc b a c,
end

-- 0001
example (a b c : ℝ) : (c * b) * a = b * (a * c) :=
begin
  rw mul_comm c b,
  rw mul_assoc b c a,
  rw mul_comm c a,
end

-- 0002
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  rw ← mul_assoc a b c,
  rw mul_comm a b,
  rw mul_assoc,
end

/-
Now let's return to the preceding example to experiment with what happens
if we don't give arguments to mul_assoc or mul_comm.
For instance, you can start the next proof with
  rw ← mul_assoc,
Try to figure out what happens.
-/

-- 0003
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  rw ← mul_assoc,
  rw mul_comm a b,
  rw mul_assoc,
end

/-
We can also perform rewriting in an assumption of the local context, using for instance
  rw mul_comm a b at hyp,
in order to replace a*b by b*a in assumption hyp.

The next example will use a third lemma:
  two_mul a : 2*a = a + a

Also we use the `exact` tactic, which allows to provide a direct proof term.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  rw hyp' at hyp,
  rw mul_comm d a at hyp,
  rw ← two_mul (a*d) at hyp,
  rw ← mul_assoc 2 a d at hyp,
  exact hyp, -- Our assumption hyp is now exactly what we have to prove
end

/-
And the next one can use:
  sub_self x : x - x = 0
-/

-- 0004
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  calc c = b*a -d : by {rw hyp}
  ... = b*a - a*b : by {rw hyp'}
  ... = b*a - b*a : by {rw mul_comm}
  ... = 0 : by {rw sub_self},
end

/-
What is written in the two preceding example is very far away from what we would write on
paper. Let's now see how to get a more natural layout.
Inside each pair of curly braces below, the goal is to prove equality with the preceding line.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b : by { rw hyp }
  ... = d*a + a*d  : by { rw hyp' }
  ... = a*d + a*d  : by { rw mul_comm d a }
  ... = 2*(a*d)    : by { rw two_mul }
  ... = 2*a*d      : by { rw mul_assoc },
end

/-
Let's note there is no comma at the end of each line of calculation. `calc` is really one
command, and the comma comes only after it's fully done.

From a practical point of view, when writing such a proof, it is convenient to:
* pause the tactic state view update in VScode by clicking the Pause icon button
  in the top right corner of the Lean Goal buffer
* write the full calculation, ending each line with ": by {}"
* resume tactic state update by clicking the Play icon button and fill in proofs between
  curly braces.

Let's return to the other example using this method.
-/

-- 0005
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  calc c = b*a - d : by {rw hyp}
  ... = b*a - a*b : by {rw hyp'}
  ...= b*a - b*a : by {rw mul_comm b a}
  ...= 0 : by {rw sub_self}
end

/-
The preceding proofs have exhausted our supply of "mul_comm" patience. Now it's time
to get the computer to work harder. The `ring` tactic will prove any goal that follows by
applying only the axioms of commutative (semi-)rings, in particular commutativity and
associativity of addition and multiplication, as well as distributivity.

We also note that curly braces are not necessary when we write a single tactic proof, so
let's get rid of them.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b   : by rw hyp
     ... = d*a + a*d : by rw hyp'
     ... = 2*a*d     : by ring,
end

/-
Of course we can use `ring` outside of `calc`. Let's do the next one in one line.
-/

-- 0006
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  calc a * (b * c) = b * (a * c) : by ring,
end

/-
This is too much fun. Let's do it again.
-/

-- 0007
example (a b : ℝ) : (a + b) + a = 2*a + b :=
begin
  calc (a + b) + a = 2*a + b : by ring,
end

/-
Maybe this is cheating. Let's try to do the next computation without ring.
We could use:
pow_two x : x^2 = x*x
mul_sub a b c : a*(b-c) = a*b - a*c
add_mul a b c : (a+b)*c = a*c + b*c
add_sub a b c : a + (b - c) = (a + b) - c
sub_sub a b c : a - b - c = a - (b + c)
add_zero a : a + 0 = a
-/

-- 0008
example (a b : ℝ) : (a + b)*(a - b) = a^2 - b^2 :=
begin
  rw mul_sub (a+b) a b,
  rw add_mul a b a,
  rw mul_comm b a,
  rw add_mul a b b,
  rw ← sub_sub,
  rw ← add_sub,
  rw sub_self,
  rw add_zero (a*a),
  rw pow_two a,
  rw pow_two b,

end

/- Let's stick to ring in the 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
import data.real.basic -- the reals

/-!

# Sets in Lean, sheet 6 : pushforward and pullback

## Pushforward of a set along a map

If `f : X → Y` then given a subset `S : set X` of `X` we can push it
forward to make a subset `f(S) : set Y` of `Y`. The definition
of `f(S)` is `{y : Y | ∃ x : X, x ∈ S ∧ f x = y}`.

However `f(S)` doesn't make sense in Lean, because `f` eats
terms of type `X` and not `S`, which has type `set X`.
In Lean we use the notation `f '' S` for this. This is notation
for `set.image` and if you need any API for this, it's likely
to use the word `image`.

## Pullback of a set along a map

If `f : X → Y` then given a subset `T : set Y` of `Y` we can
pull it back to make a subset `f⁻¹(T) : set X` of `X`. The definition
of `f⁻¹(T)` is `{x : X | f x ∈ T}`.

However `f⁻¹(T)` doesn't make sense in Lean either, because
`⁻¹` is notation for `has_inv.inv`, whose type in Lean
is `α → α`. In other words, if `x` has a certain type, then
`x⁻¹` *must* have the same type. The notation was basically designed
for group theory. In Lean we use the notation `f ⁻¹' T` for this pullback.
This is notation for `set.preimage` and if you need any API for this,
it's likely to use the word `preimage`.

-/

variables (X Y : Type) (f : X → Y) (S : set X) (T : set Y)

example : S ⊆ f ⁻¹' (f '' S) :=
begin
  sorry
end

example : f '' (f ⁻¹' T) ⊆ T :=
begin
  sorry
end

-- `library_search` will do this but see if you can do it yourself.
example : f '' S ⊆ T ↔ S ⊆ f ⁻¹' T :=
begin
  sorry
end

-- Pushforward and pullback along the identity map don't change anything

-- pullback is not so hard
example : id ⁻¹' S = S :=
begin
  sorry
end

-- pushforward is a little trickier. You might have to `ext x, split`.
example : id '' S = S :=
begin
  sorry
end

-- Now let's try composition.
variables (Z : Type) (g : Y → Z) (U : set Z)

-- preimage of preimage is preimage of comp
example : (g ∘ f) ⁻¹' U = f ⁻¹' (g ⁻¹' U) :=
begin
  sorry
end

-- preimage of preimage is preimage of comp
example : (g ∘ f) '' S = g '' (f '' S) :=
begin
  sorry
end


import data.real.basic

/-
One of the earliest kind of proofs one encounters while learning mathematics is proving by
a calculation. It may not sound like a proof, but this is actually using lemmas expressing
properties of operations on numbers. It also uses the fundamental property of equality: if two
mathematical objects A and B are equal then, in any statement involving A, one can replace A
by B. This operation is called rewriting, and the Lean "tactic" for this is `rw`.

In the following exercises, we will use the following two lemmas:
  mul_assoc a b c : a * b * c = a * (b * c)
  mul_comm a b : a*b = b*a

Hence the command
  rw mul_assoc a b c,
will replace a*b*c by a*(b*c) in the current goal.

In order to replace backward, we use
  rw ← mul_assoc a b c,
replacing a*(b*c) by a*b*c in the current goal.

Of course we don't want to constantly invoke those lemmas, and we will eventually introduce
more powerful solutions.
-/

-- Uncomment the following line if you want to see parentheses around subexpressions.
-- set_option pp.parens true

example (a b c : ℝ) : (a * b) * c = b * (a * c) :=
begin
  rw mul_comm a b,
  rw mul_assoc b a c,
end

-- 0001
example (a b c : ℝ) : (c * b) * a = b * (a * c) :=
begin
  rw mul_comm c b,
  rw mul_assoc b c a,
  rw mul_comm c a,
end

-- 0002
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  rw ← mul_assoc a b c,
  rw mul_comm a b,
  rw mul_assoc b a c,
end

/-
Now let's return to the preceding example to experiment with what happens
if we don't give arguments to mul_assoc or mul_comm.
For instance, you can start the next proof with
  rw ← mul_assoc,
Try to figure out what happens.
-/

-- 0003
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  rw ← mul_assoc,
  rw mul_comm a b,
  rw mul_assoc,
end

/-
We can also perform rewriting in an assumption of the local context, using for instance
  rw mul_comm a b at hyp,
in order to replace a*b by b*a in assumption hyp.

The next example will use a third lemma:
  two_mul a : 2*a = a + a

Also we use the `exact` tactic, which allows to provide a direct proof term.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  rw hyp' at hyp,
  rw mul_comm d a at hyp,
  rw ← two_mul (a*d) at hyp,
  rw ← mul_assoc 2 a d at hyp,
  exact hyp, -- Our assumption hyp is now exactly what we have to prove
end

/-
And the next one can use:
  sub_self x : x - x = 0
-/

-- 0004
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  rw hyp' at hyp,
  rw mul_comm b a at hyp,
  rw sub_self at hyp,
  exact hyp,
end

/-
What is written in the two preceding example is very far away from what we would write on
paper. Let's now see how to get a more natural layout.
Inside each pair of curly braces below, the goal is to prove equality with the preceding line.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b : by { rw hyp }
  ... = d*a + a*d  : by { rw hyp' }
  ... = a*d + a*d  : by { rw mul_comm d a }
  ... = 2*(a*d)    : by { rw two_mul }
  ... = 2*a*d      : by { rw mul_assoc },
end

/-
Let's note there is no comma at the end of each line of calculation. `calc` is really one
command, and the comma comes only after it's fully done.

From a practical point of view, when writing such a proof, it is convenient to:
* pause the tactic state view update in VScode by clicking the Pause icon button
  in the top right corner of the Lean Goal buffer
* write the full calculation, ending each line with ": by {}"
* resume tactic state update by clicking the Play icon button and fill in proofs between
  curly braces.

Let's return to the other example using this method.
-/

-- 0005
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  calc c = b*a-d : by { rw hyp }
  ... = b*a - a*b : by { rw hyp' }
  ... = b*a - b*a : by { rw mul_comm }
  ... = 0 : by { rw sub_self },
end

/-
The preceding proofs have exhausted our supply of "mul_comm" patience. Now it's time
to get the computer to work harder. The `ring` tactic will prove any goal that follows by
applying only the axioms of commutative (semi-)rings, in particular commutativity and
associativity of addition and multiplication, as well as distributivity.

We also note that curly braces are not necessary when we write a single tactic proof, so
let's get rid of them.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b   : by rw hyp
     ... = d*a + a*d : by rw hyp'
     ... = 2*a*d     : by ring,
end

/-
Of course we can use `ring` outside of `calc`. Let's do the next one in one line.
-/

-- 0006
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  ring,
end

/-
This is too much fun. Let's do it again.
-/

-- 0007
example (a b : ℝ) : (a + b) + a = 2*a + b :=
begin
  ring,
end

/-
Maybe this is cheating. Let's try to do the next computation without ring.
We could use:
pow_two x : x^2 = x*x
mul_sub a b c : a*(b-c) = a*b - a*c
add_mul a b c : (a+b)*c = a*c + b*c
add_sub a b c : a + (b - c) = (a + b) - c
sub_sub a b c : a - b - c = a - (b + c)
add_zero a : a + 0 = a
-/

-- 0008
example (a b : ℝ) : (a + b)*(a - b) = a^2 - b^2 :=
begin
  -- ring,

  -- calc (a+b)*(a-b) = a*(a-b) + b*(a-b) : by rw add_mul
  -- ... = a*a - a*b + b*(a-b) : by rw mul_sub
  -- ... = a*a - a*b + (b*a - b*b) : by rw mul_sub
  -- ... = a*a - a*b + b*a - b*b : by rw add_sub
  -- ... = a*a - a*b + a*b - b*b : by rw mul_comm b a
  -- ... = a*a + (a*b - a*b) - b*b : by sorry
  -- ... = a*a + 0 - b*b : by sub_self
  -- ... = a*a - b*b : by rw add_zero
  -- ... = a^2 - b*b : by rw pow_two a
  -- ... = a^2 - b^2 : by rw pow_two b,

  rw pow_two,
  rw pow_two,
  rw mul_sub,
  rw add_mul,
  rw add_mul,
  rw ← add_sub,
  rw ← sub_sub,
  rw mul_comm b a,
  rw sub_self,
  rw add_sub,
  rw add_zero,
end

/- Let's stick to ring in the end. -/


/-  Math40001 : Introduction to university mathematics.

Problem Sheet 1, October 2020.

-/

/- Question 1.

Let P and Q be Propositions (that is, true/false statements).
Prove that P ∨ Q → Q ∨ P.

-/
import tactic

lemma question_one (P Q : Prop) : P ∨ Q → Q ∨ P :=
begin
 intro hPorQ,
 cases hPorQ with hP hQ,
 right,
 exact hP,
 left,
 exact hQ,
end
/-

For question 2, comment out one option (or just delete it)
and prove the other one.
-/

-- Part (a): is → symmetric?

lemma question_2a_false : ¬ (∀ P Q : Prop, (P → Q) → (Q → P)) :=
begin
 intro hPQ,

end

-- Part (b) : is ↔ symmetric?

lemma question_2b_true (P Q : Prop) : (P ↔ Q) → (Q ↔ P) :=
begin
  sorry
end

lemma question_2b_false : ¬ (∀ P Q : Prop, (P ↔ Q) → (Q ↔ P)) :=
begin
  sorry
end

/- Question 3.

Say P, Q and R are propositions, and we know:
1) if Q is true then P is true
2) If Q is false then R is false.

Can we deduce that R implies P? Comment out one
option and prove the other. Hint: if you're stuck,
"apply classical.by_contradiction" sometimes helps.
classical.by_contradiction is the theorem that ¬ ¬ P → P.
-/

lemma question_3_true (P Q R : Prop)
  (h1 : Q → P)
  (h2 : ¬ Q → ¬ R) :
R → P :=
begin
  sorry
end

lemma question_3_false : ¬ (∀ P Q R : Prop,
  (Q → P) →
  (¬ Q → ¬ R) →
  R → P) :=
begin
  sorry
end

/- Question 4.

Is it possible to find three true-false statements P , Q and R, such that
(P ∨ Q ∨ R) ∧ (¬P ∨ Q ∨ R) ∧ (¬P ∨ ¬Q ∨ R) ∧ (¬P ∨ ¬Q ∨ ¬R)
is true?

-/

lemma question_4_true : ∃ (P Q R : Prop),
  (P ∨ Q ∨ R) ∧ (¬P ∨ Q ∨ R) ∧ (¬P ∨ ¬Q ∨ R) ∧ (¬P ∨ ¬Q ∨ ¬R) :=
begin
  sorry
end

lemma question_4_false : ∀ (P Q R : Prop),
  ¬ ((P ∨ Q ∨ R) ∧ (¬P ∨ Q ∨ R) ∧ (¬P ∨ ¬Q ∨ R) ∧ (¬P ∨ ¬Q ∨ ¬R)) :=
begin
  sorry
end

/- Question 5.

  Say that for every integer n we have a proposition P n.
  Say we know P n → P (n + 8) for all n, and
  P n → P (n -3) for all n. Prove that the P n are either
  all true, or all false.

This question is harder than the others.
-/
lemma question_5 (P : ℤ → Prop) (h8 : ∀ n, P n → P (n + 8)) (h3 : ∀ n, P n → P (n - 3)) :
(∀ n, P n) ∨ (∀ n, ¬ (P n)) :=
begin
  sorry
end

/-
The first four of these questions can be solved using only the following
tactics:

intro
apply (or, better, refine)
left, right, cases, split
assumption (or, better, exact)
have,
simp,
use,
contradiction (or, better, false.elim)

The fifth question is harder.
-/


-- Cantor's theorem in Lean

import tactic.interactive

open function

/- Theorem: If X is any type, then there is no bijective function f
   from X to the power set of X.
-/

theorem no_bijection_to_power_set (X : Type) :
∀ f : X → set X, ¬ (bijective f) :=
begin
  -- Proof by Kevin Buzzard

  -- let f be an arbitrary function from X to the power set of X
  intro f,
  -- Assume, for a contradiction, that f is bijective
  intro Hf,
  -- f is bijective, so it's surjective.
  cases Hf with Hi Hs,
  -- it's also injective, but I don't even care
  clear Hi,
  -- Let S be the usual cunning set
  let S : set X := {x : X | x ∉ f x},
  -- what is the definition of surjective?
  unfold surjective at Hs,
  -- What does surjectivity of f say when applied to S?
  have HCantor := Hs S,
  -- It tells us that there's x in X with f x = S!
  cases HCantor with x Hx,
  -- That means x is in f x if and only if x has is in S.
  have Hconclusion_so_far : x ∈ f x ↔ x ∈ S := by rw [Hx],
  -- but this means (x ∈ f x) ↔ ¬ (x ∈ f x)
  have Hlogical_nonsense : (x ∈ f x) ↔ ¬ (x ∈ f x) := Hconclusion_so_far,
  -- automation can now take over.
  revert Hlogical_nonsense,
  simp,
  -- Other proofs welcome. Get in touch. Write a function.
  -- xenaproject.wordpress.com
end

-- folding up the proof gives this
theorem no_bijection_to_power_set'
  (X : Type)
  (f : X → set X) :
bijective f → false :=
λ ⟨Hi, Hs⟩,
let ⟨x,Hx⟩ := Hs {x : X | x ∉ f x} in
have Hconclusion_so_far : x ∈ f x ↔ x ∈ _ := by rw [Hx],
begin clear _fun_match,clear _let_match,tauto! end

-- NB this function is in mathlib already, it's called
-- cantor_surjective. The proof is quite compressed: two lines!
theorem cantor_surjective
  (α : Type)
  (f : α → α → Prop)
  (h : function.surjective f) :
false :=
let ⟨D, e⟩ := h (λ a, ¬ f a a) in
(iff_not_self (f D D)).1 $ iff_of_eq (congr_fun e D)

-- When unravelled, the mathlib proof looks like this:
theorem cantor_surjective'
  (X : Type)
  (f : X → set X)
  (Hs : surjective f) :
false :=
begin
have Hcantor := Hs {x : X | x ∉ f x},
cases Hcantor with x Hx,
  apply (iff_not_self (x ∈ f x)).1,
  apply iff_of_eq,
  apply congr_fun Hx x,
end

/-
  NB I renamed the variables in the mathlib proof
  α → X
  h → Hf
  D → x
  e → Hx,
-/


/- Homework 2.1: Functional Programming — Lists -/

/- Question 1: replicateting an element -/

/- 1.1. Define a function `replicate` that takes an element `a` of type `α` and a natural number `n`
and that returns a list of length `n` consisting of `n` occurrences of `a`: `[a, ..., a]` -/

def replicate {α : Type} (a: α):  ℕ  → list α
|  0  := []
|  (x+1) :=  a :: replicate x

#reduce replicate 2 9
#reduce replicate 1 9
#reduce replicate 2 2

#check list.length

/- 1.2. State and prove that `replicate a n` has length `n`. -/
lemma same_length  {α : Type} : ∀(n : ℕ), ∀(a: α), list.length(replicate a n) =  n
| 0 a := begin simp[replicate] end
| (n + 1) a := by simp[replicate, list.length, same_length]

#check list.map

/- 1.3. State and prove that `list.map f (replicate a n)` is the same as `replicate (f a) n`. Make
sure to state the result as generally as possible. -/

lemma replicate_map {α β : Type} (f : α → β): ∀ (n : ℕ), ∀ (a: α), list.map f (replicate a n) = replicate (f a) n
| 0 a := by simp[replicate]
| (n + 1) a := begin rw[replicate], rw[replicate], simp[replicate_map] end

/- 1.4. State and prove that `replicate a m ++ replicate a n` equals `replicate a (m + n)`.

There are many ways to prove this. A calculational proof might be useful here to manipulate the
expression step by step (but is not mandatory). -/
lemma replicate_append {α : Type}: ∀ (n m : ℕ), ∀ (a: α), replicate a m ++ replicate a n = replicate a (m + n)
| n 0 a:= by simp[replicate]
| n (m+1) a := begin simp[replicate], rw[<-add_assoc], rw[replicate_append], rw[<-replicate_append], rw[<-replicate_append], simp[replicate], rw[replicate_append], simp[replicate], sorry end

lemma replicate_append' {α : Type}: ∀ (n m : ℕ), ∀ (a: α), replicate a m ++ replicate a n = replicate a (m + n):=
begin
intros m n as,
induction n,
repeat{simp[replicate]},
rw[n_ih],
simp[replicate],
refl
end

def map {α β: Type} (f: α → β): list α → list β
| [] := []
| (x :: xs) := f x :: map xs

def concat {α: Type}: list(list α) → list α
| [] := []
| (xs :: xss) := xs ++ concat xss

lemma map_concat {α β: Type} (f :α →β) :∀xss : list (list α), map f (concat xss) = concat (map (map f) xss)
| [] := by refl
| (xs :: xss) := begin simp[map], simp[concat], rw[<-map_concat],  end
/- 1.5. State and prove that `reverse` has no effect on `replicate a n`.

Hint: If you get stuck in the induction step, this may indicate that you first need to prove
another lemma, also by induction. -/

def reverse {α : Type} : list α → list α
| []        := []
| (x :: xs) := reverse xs ++ [x]

lemma replicate_reverse_append {α : Type}: ∀ (n : ℕ), ∀ (a: α), replicate a n ++ [a]= a :: replicate a n
| 0 a := by simp[replicate]
| (n + 1) a := begin simp[replicate], rw[replicate_reverse_append]  end

lemma replicate_reverse {α : Type}: ∀ (n : ℕ), ∀ (a: α), reverse (replicate a n) = replicate a n
| 0 a := begin simp[replicate], simp[reverse] end
| (n + 1) a := begin simp[replicate], simp[reverse], rw[replicate_reverse], rw[replicate_reverse_append] end

/- Question 2: Binary relations -/

/- The following questions concern modeling properties of binary relations (actually, curried binary
predicates). To avoid repeating `α` and `R` in each definition, we use a section and fix them
locally in the section. -/

section

parameter {α : Type}
parameter R : α → α → Prop

def irreflexive_rel := ∀x : α, ¬ R x x
def antisymmetric_rel := ∀x y : α, R x y → ¬ R y x
def transitive_rel := ∀x y z : α, R x y → R y z → R x z

/- 2.1. Prove that antisymmetry implies irreflexivity.

Hint: It may help to first think abstractly why this is the case before trying to prove it in Lean.
Alternatively, try to see this as a video game and use `intros` and `apply` until you have defeated
the boss. There exists a fairly short `apply`-style proof. -/

lemma antisymmetric_rel_implies_irreflexive_rel : antisymmetric_rel → irreflexive_rel :=
begin
intros a x rx,
apply a,
apply rx,
assumption
end

/- 2.2. Define `symmetric_rel` as the proposition stating that `R` is symmetric—that is, if `x`
and `y` are related via `R`, then `y` and `x` are related via `R`. -/

def symmetric_rel := ∀x y : α, R x y → R y x

/- 2.3. State and prove the property that any irreflexive and transitive relation is
antisymmetric. -/

lemma irreflexive_transitive_antisymmetric : irreflexive_rel → transitive_rel → antisymmetric_rel:=
begin
intros x y hx hy,
intro relx,
intro rely,
apply x,
apply y,
apply relx,
assumption
end

/- Notice that each of the definition takes `α` as implicit argument and `R` as explicit argument.
Everything we have defined and proved above is now available in a very general form. -/

#check irreflexive_rel
#check antisymmetric_rel
#check transitive_rel

#check antisymmetric_rel_implies_irreflexive_rel

end


/-
Let's look at three functions, each transforming
one list into another by converting each element
in the argument list, through the application of
a function, into a corresponding element in the
resulting list. Try to see the commonalities and
the dimesions of variation.
-/

-- map each n in argument list to n+1 in result
-- e.g., add_1 [1, 2, 3] = [2, 3, 4]
def add_1 : list nat → list nat
| [] := []
| (h::t) := (h+1)::add_1 t

example : add_1 [] = [] := rfl
example : add_1 [1, 2, 3] = [2, 3, 4] := rfl

-- map each n in argument list to n+2 in result
-- e.g., add_2 [1, 2, 3] = [3, 4, 5]
def add_2 : list nat → list nat
| [] := []
| (h::t) := (h+2)::add_2 t

example : add_2 [] = [] := rfl
example : add_2 [1, 2, 3] = [3, 4, 5] := rfl

-- map each n in argument list to n^2 in result
-- e.g., add_2 [1, 2, 3] = [1, 4, 9]
def sqr_list : list nat → list nat
| [] := []
| (h::t) := (h*h)::sqr_list t

example : sqr_list [1, 2, 3] = [1, 4, 9] := rfl

/-
One *dimension of variation* is in the function applied
to each element of the given argument list to compute
the  corresponding element of the result list. We abstract
from these variations, replacing the applications of these
with applications of the function passed as an argument.
The result is a map function that can convert any list of
natural numbers to another list of natural numbers by
applying a given function (of type nat → nat) to each
element in the given list to compute its corresponding
value in the list to be returned.
-/
def map_nat : (nat → nat) → list nat → list nat
| fn [] := []
| fn (h::t) := (fn h)::(map_nat fn t)

example : map_nat (nat.succ) [1, 2, 3] = [2, 3, 4] := rfl
example : map_nat (λ n, n * n) [1, 2, 3] = [1, 4, 9] := rfl

/-
A second, orthogonal, dimension of variation is in the
types of elements in the argument and results lists. In
the general case, a map function can take in a list of
values of any type, α, and convert it to a corresponding
list of elements of type β by applying a function of type
α → β to each element of the first list to calculate the
corresponding elements of the second list.
-/
universe u
def map {α β : Type u} : (α → β) → list α → list β
| fn [] := []
| fn (h::t) := (fn h)::(map fn t)

example: map nat.succ [1, 2, 3] = [2, 3, 4] := rfl

/-
Practice: write an expression to convert a list of nat to
a list of bool, where each bool is the parity (oddness on
a scale of {0, 1}) of its corresponding value.
-/

-- Answer

example :
  map (λ n, n%2) [1, 2, 3, 4, 5] = [1, 0, 1, 0, 1]
:= rfl

 /-
 Practice: write an expression that uses map to convert
 a list of strings to a list of their lengths.
 -/

 example :
  map (λ s, string.length s) ["Hello", "Lean"] = [5, 4]
:= rfl

/-
*******************************************
The foldr, or reduce, higher-order function
*******************************************
-/

/-
As before, we'll start with a few concrete examples
and from them will work to a general definition of
the folr ("right fold") operation on lists of values.
-/

/-
Example #1 Reduce a list of natural numbers to the sum
of all its elements.
-/

def sum_of_elts : list nat → nat
| [] := 0
| (h::t) := h + sum_of_elts t

example : sum_of_elts [0, 1, 2, 3, 4, 5] = 15 := rfl

def prod_of_elts : list nat → nat
| [] := 1
| (h::t) := h * sum_of_elts t

-- we can write tests like this
-- but then we have to use our eyes
#reduce prod_of_elts []     -- expect 1
#reduce prod_of_elts [2]    -- expect 2

-- it's better to let the type checker do the work
example : prod_of_elts [] = 1 := rfl
-- The following tests catch an error I made in class!
example : prod_of_elts [2] = 2 := rfl
example : prod_of_elts [1, 2, 3, 4, 5] = 120 := rfl
-- Practice: find and fix the bug

/-
Clearly the two reduction functions we've looked at,
sum_of_elts and prod_of_elts vary *dependently* in two
dimensions: (1) the value returned for the base case of
an empty list, (2) the function used to produce a final
answer by combining the head of a non-empty list with
the result of reducing the tail of the list (which we
do recursively).
-/
def reduce_elts : (nat → nat → nat) → nat → list nat → nat
| f id [] := id
| f id (h::t) := f h (reduce_elts f id t)

example : reduce_elts nat.add 0 [1,2,3,4,5] = 15 := rfl
example : reduce_elts nat.mul 1 [1,2,3,4,5] = 120 := rfl

/-
We can also generalize over the type of list elements
and the type of the reduced return result. As an example
of a case where list elements are of one type and the
result of the reduction is of another type, think of a
function, all_even, that takes a list of natural numbers
and returns true if and only if all are even (otherwise
false). The key to understanding our algorithm is to see
it as applying one operation one time, with two cases.
If the list is empty, return a "base case" value for
this case, otherwise apply a function to two aguments,
the head of the list and the reduction of the tail to
compute the final result. That's it. (The computation
of the result for the tail is by recursion.) The operation
that combines the head of the list with the answer for
the rest of the list thus takes two arguments, the first
of the input list element type, the second, the answer
for the rest of the list, which will be of the second
type: bool, in our example.
-/
def reduce {α β : Type u} : (α → β → β) → β → list α → β
| f id [] := id
| f id (h::t) := f h (reduce f id t)

/-
As an example, use reduce to implement a function
that reduces any list of strings to a single number
that is the sum of their lengths.
-/

/-
First let's write the reduce operator, which will
take the string at the head of a list of strings
along with the reduction of the rest of the list
(computed by recursion) and that will then return
the answer for the whole list: both head and tail.
-/

def red_strs : string → nat → nat
| s n := s.length + n

-- example: sum of lengths of list of strings
example :
  reduce red_strs 0 ["hello", "lean", "!"] = 10
:= rfl

-- example: number of odd numbers in a list of nats
example :
  reduce nat.add 0 (map (λ n, n%2) [1,2,3,4,5]) = 3
:= rfl

/-
Finally a generalized map/reduce function, taking
a reduction operator, its value for when a list is
empty, an element conversion function, and a list
of elements, and that (1) maps it by applying the
element conversion function elementwise to produce
a new list, which (2) is then reduced to a final
result.
-/
def map_reduce { α β γ : Type u} : (β → γ → γ) → γ → (α → β) → list α → γ
| red_op id map_fn l := reduce red_op id (map map_fn l)

-- Use reduce to compute number of odd values in a list
#reduce map_reduce nat.add 0 (λ n, n%2) [1,2,3,4,5]

/-
Use partial evaluation to define a funciton that
takes any list of natural numbers to the number of
odd values in the list.
-/

def count_odds := map_reduce nat.add 0 (λ n, n%2)
example : count_odds [1,2,3,4,5] = 3 := rfl

/-
The map and reduce functions are function-building
machines. Here we've applied map_reduce to (1) a
reduction operator, (2) it's identity, and (3) an
element mapping function to produce a function that
counts the number of odd values in a list of nats.
map_reduce nat.add 0 (λ n, n%2)
-/

/-
Practice: use map_reduce to convert a list of
interpretations for an expression, e,
-/

/-
Practice: Identify the places in the SAT solver
code that I/Kevin will provide where special cases
of map or reduce functions are used and replace
them with uses of the higher-order list map and
reduce functions while maintaining correctness
of the code.

The versions in the Lean library are called
list.map and list.foldr (for "fold right"). You
may, and should, use Lean's versions for this
assignment.
-/

#check @list.map
#check @list.foldr

-- Yay!


import .love01_definitions_and_statements_demo

/-! # LoVe Exercise 1: Definitions and Statements

Replace the placeholders (e.g., `:= sorry`) with your solutions. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Truncated Subtraction

1.1. Define the function `sub` that implements truncated subtraction on natural
numbers by recursion. "Truncated" means that results that mathematically would
be negative are represented by 0. For example:

    `sub 7 2 = 5`
    `sub 2 7 = 0` -/

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

/-! 1.2. Check that your function works as expected. -/

#eval sub 0 0   -- expected: 0
#eval sub 0 1   -- expected: 0
#eval sub 0 7   -- expected: 0
#eval sub 1 0   -- expected: 1
#eval sub 1 1   -- expected: 0
#eval sub 3 0   -- expected: 3
#eval sub 2 7   -- expected: 0
#eval sub 3 1   -- expected: 2
#eval sub 3 3   -- expected: 0
#eval sub 3 7   -- expected: 0
#eval sub 7 2   -- expected: 5

/-! ## Question 2: Arithmetic Expressions

Consider the type `aexp` from the lecture and the function `eval` that
computes the value of an expression. You will find the definitions in the file
`love01_definitions_and_statements_demo.lean`. One way to find them quickly is
to

1. hold the Control (on Linux and Windows) or Command (on macOS) key pressed;
2. move the cursor to the identifier `aexp` or `eval`;
3. click the identifier. -/

#check aexp
#check eval

/-! 2.1. Test that `eval` behaves as expected. Make sure to exercise each
constructor at least once. You can use the following environment in your tests.
What happens if you divide by zero?

Make sure to use `#eval`. For technical reasons, `#reduce` does not work well
here. Note that `#eval` (Lean's evaluation command) and `eval` (our evaluation
function on `aexp`) are unrelated. -/

def some_env : string → ℤ
| "x" := 3
| "y" := 17
| _   := 201

#eval eval some_env (aexp.var "x")   -- expected: 3
-- invoke `#eval` here

/-! 2.2. The following function simplifies arithmetic expressions involving
addition. It simplifies `0 + e` and `e + 0` to `e`. Complete the definition so
that it also simplifies expressions involving the other three binary
operators. -/

def simplify : aexp → aexp
| (aexp.add (aexp.num 0) e₂) := simplify e₂
| (aexp.add e₁ (aexp.num 0)) := simplify e₁
-- insert the missing cases here
-- catch-all cases below
| (aexp.num i)               := aexp.num i
| (aexp.var x)               := aexp.var x
| (aexp.add e₁ e₂)           := aexp.add (simplify e₁) (simplify e₂)
| (aexp.sub e₁ e₂)           := aexp.sub (simplify e₁) (simplify e₂)
| (aexp.mul e₁ e₂)           := aexp.mul (simplify e₁) (simplify e₂)
| (aexp.div e₁ e₂)           := aexp.div (simplify e₁) (simplify e₂)

/-! 2.3. Is the `simplify` function correct? In fact, what would it mean for it
to be correct or not? Intuitively, for `simplify` to be correct, it must
return an arithmetic expression that yields the same numeric value when
evaluated as the original expression.

Given an environment `env` and an expression `e`, state (without proving it)
the property that the value of `e` after simplification is the same as the
value of `e` before. -/

lemma simplify_correct (env : string → ℤ) (e : aexp) :
  eval env e = eval env (simplify e) :=
sorry

/-! ## Question 3: λ-Terms

3.1. Complete the following definitions, by replacing the `sorry` markers by
terms of the expected type.

Hint: A procedure for doing so systematically is described in Section 1.1.4 of
the Hitchhiker's Guide. As explained there, you can use `_` as a placeholder
while constructing a term. By hovering over `_`, you will see the current
logical context. -/

def I : α → α :=
λa, a

def K : α → β → α :=
λa b, a

def C : (α → β → γ) → β → α → γ :=
λf b a, (f a b)

def proj_1st : α → α → α :=
λx y, x

/-! Please give a different answer than for `proj_1st`. -/

def proj_2nd : α → α → α :=
λx y, y

def some_nonsense : (α → β → γ) → α → (α → γ) → β → γ :=
λ_ a g _, g a

/-! 3.2. Show the typing derivation for your definition of `C` above, on paper
or using ASCII or Unicode art. You might find the characters `–` (to draw
horizontal bars) and `⊢` useful. -/

-- write your solution in a comment here or on paper

end LoVe


/-
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

/-- `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)

/-- 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


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
Mostly based on Jeremy Avigad's choose file in lean 2
-/

import data.nat.basic

open nat

def choose : ℕ → ℕ → ℕ
| _             0 := 1
| 0       (k + 1) := 0
| (n + 1) (k + 1) := choose n k + choose n (succ k)

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

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

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

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

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

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

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

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

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

lemma choose_mul_fact_mul_fact : ∀ {n k}, k ≤ n → choose n k * fact k * fact (n - k) = fact n
| 0              _ hk := by simp [eq_zero_of_le_zero hk]
| (n + 1)        0 hk := by simp
| (n + 1) (succ k) hk :=
begin
  cases lt_or_eq_of_le hk with hk₁ hk₁,
  { have h : choose n k * fact (succ k) * fact (n - k) = succ k * fact n :=
      by rw ← choose_mul_fact_mul_fact (le_of_succ_le_succ hk);
      simp [fact_succ, mul_comm, mul_left_comm],
    have h₁ : fact (n - k) = (n - k) * fact (n - succ k) :=
      by rw [← succ_sub_succ, succ_sub (le_of_lt_succ hk₁), fact_succ],
    have h₂ : choose n (succ k) * fact (succ k) * ((n - k) * fact (n - succ k)) = (n - k) * fact n :=
      by rw ← choose_mul_fact_mul_fact (le_of_lt_succ hk₁);
      simp [fact_succ, mul_comm, mul_left_comm, mul_assoc],
    have h₃ : k * fact n ≤ n * fact n := mul_le_mul_right _ (le_of_succ_le_succ hk),
  rw [choose_succ_succ, add_mul, add_mul, succ_sub_succ, h, h₁, h₂, ← add_one, add_mul, nat.mul_sub_right_distrib,
      fact_succ, ← nat.add_sub_assoc h₃, add_assoc, ← add_mul, nat.add_sub_cancel_left, add_comm] },
  { simp [hk₁, mul_comm, choose, nat.sub_self] }
end

theorem choose_eq_fact_div_fact {n k : ℕ} (hk : k ≤ n) : choose n k = fact n / (fact k * fact (n - k)) :=
begin
  have : fact n = choose n k * (fact k * fact (n - k)) :=
    by rw ← mul_assoc; exact (choose_mul_fact_mul_fact hk).symm,
  exact (nat.div_eq_of_eq_mul_left (mul_pos (fact_pos _) (fact_pos _)) this).symm
end

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


import .love01_definitions_and_statements_demo

/-! # LoVe Exercise 1: Definitions and Statements

Replace the placeholders (e.g., `:= sorry`) with your solutions. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Truncated Subtraction

1.1. Define the function `sub` that implements truncated subtraction on natural
numbers by recursion. "Truncated" means that results that mathematically would
be negative are represented by 0. For example:

    `sub 7 2 = 5`
    `sub 2 7 = 0` -/

def sub : ℕ → ℕ → ℕ :=
sorry

/-! 1.2. Check that your function works as expected. -/

#eval sub 0 0   -- expected: 0
#eval sub 0 1   -- expected: 0
#eval sub 0 7   -- expected: 0
#eval sub 1 0   -- expected: 1
#eval sub 1 1   -- expected: 0
#eval sub 3 0   -- expected: 3
#eval sub 2 7   -- expected: 0
#eval sub 3 1   -- expected: 2
#eval sub 3 3   -- expected: 0
#eval sub 3 7   -- expected: 0
#eval sub 7 2   -- expected: 5

/-! ## Question 2: Arithmetic Expressions

Consider the type `aexp` from the lecture and the function `eval` that
computes the value of an expression. You will find the definitions in the file
`love01_definitions_and_statements_demo.lean`. One way to find them quickly is
to

1. hold the Control (on Linux and Windows) or Command (on macOS) key pressed;
2. move the cursor to the identifier `aexp` or `eval`;
3. click the identifier. -/

#check aexp
#check eval

/-! 2.1. Test that `eval` behaves as expected. Make sure to exercise each
constructor at least once. You can use the following environment in your tests.
What happens if you divide by zero?

Make sure to use `#eval`. For technical reasons, `#reduce` does not work well
here. Note that `#eval` (Lean's evaluation command) and `eval` (our evaluation
function on `aexp`) are unrelated. -/

def some_env : string → ℤ
| "x" := 3
| "y" := 17
| _   := 201

#eval eval some_env (aexp.var "x")   -- expected: 3
-- invoke `#eval` here

/-! 2.2. The following function simplifies arithmetic expressions involving
addition. It simplifies `0 + e` and `e + 0` to `e`. Complete the definition so
that it also simplifies expressions involving the other three binary
operators. -/

def simplify : aexp → aexp
| (aexp.add (aexp.num 0) e₂) := simplify e₂
| (aexp.add e₁ (aexp.num 0)) := simplify e₁
-- insert the missing cases here
-- catch-all cases below
| (aexp.num i)               := aexp.num i
| (aexp.var x)               := aexp.var x
| (aexp.add e₁ e₂)           := aexp.add (simplify e₁) (simplify e₂)
| (aexp.sub e₁ e₂)           := aexp.sub (simplify e₁) (simplify e₂)
| (aexp.mul e₁ e₂)           := aexp.mul (simplify e₁) (simplify e₂)
| (aexp.div e₁ e₂)           := aexp.div (simplify e₁) (simplify e₂)

/-! 2.3. Is the `simplify` function correct? In fact, what would it mean for it
to be correct or not? Intuitively, for `simplify` to be correct, it must
return an arithmetic expression that yields the same numeric value when
evaluated as the original expression.

Given an environment `env` and an expression `e`, state (without proving it)
the property that the value of `e` after simplification is the same as the
value of `e` before. -/

lemma simplify_correct (env : string → ℤ) (e : aexp) :
  true :=   -- replace `true` by your lemma statement
sorry

/-! ## Question 3: λ-Terms

3.1. Complete the following definitions, by replacing the `sorry` markers by
terms of the expected type.

Hint: A procedure for doing so systematically is described in Section 1.1.4 of
the Hitchhiker's Guide. As explained there, you can use `_` as a placeholder
while constructing a term. By hovering over `_`, you will see the current
logical context. -/

def I : α → α :=
λa, a

def K : α → β → α :=
λa b, a

def C : (α → β → γ) → β → α → γ :=
sorry

def proj_1st : α → α → α :=
sorry

/-! Please give a different answer than for `proj_1st`. -/

def proj_2nd : α → α → α :=
sorry

def some_nonsense : (α → β → γ) → α → (α → γ) → β → γ :=
sorry

/-! 3.2. Show the typing derivation for your definition of `C` above, on paper
or using ASCII or Unicode art. You might find the characters `–` (to draw
horizontal bars) and `⊢` useful. -/

-- write your solution in a comment here or on paper

end LoVe


import algebra.group group_theory.order_of_element
import data.nat.parity
/-!
# Group theory basics (drawn from Sheet 1 of the MTH2010 Groups, Rings, and Fields module)
-/

/-!
## Commutative groups
-/

def is_comm (G : Type) [group G] : Prop := ∀ g h : G, g * h = h * g

variables {G H : Type} [group G] [group H]

/--
Prove that a product `G × H` of groups is commutatitive if and only if each of `G` and `H` are.
-/
lemma commutative_prod_iff : is_comm (G × H) ↔ (is_comm G) ∧ (is_comm H) :=
sorry

/-!
## Group orders

Let `G` be a group and let `x : G`. We write `order_of x` for the order of `x` in `G`.

Useful theorems regarding group orders:

1. `pow_order_of_eq_one x` states that `x` raised to the power `order_of x` equals
the identity element in the group. That is, `x ^ order_of x = 1`.

2. `order_of_dvd_of_pow_eq_one` states that if `x ^ n = 1`, then `order_of x ∣ n`.

3. `nat.dvd_antisymm` is the result that if `n ∣ m` and `m ∣ n`, then `n = m`. This is useful for
proving `order_of y = z` as it reduces the problem to proving `order_of y ∣ z` and `z ∣ order_of y`.

4. `pow mul a m n` states `a ^ (m * n) = (a ^ m) ^ n`.

5. `nat.mul_dvd_mul_iff_left` states that if `0 < a`, then `a * b ∣ a * c ↔ b ∣ c`.

6. `inv_pow a n` states that `a⁻¹ ^ n = (a ^ n)⁻¹`.

7. `one_inv` states that `1⁻¹ = 1`.

8. `inv_inv a` states that `(a⁻¹)⁻¹ = a`.

9. `conj_pow` states that `(a * b * a⁻¹) ^ i = a * (b ^ i) * a⁻¹`.

-/

lemma order_of_pow_of_order_of_eq_mul
  (x : G) (s t : ℕ) (h : order_of x = s * t) (hspos : s > 0) (htpos : t > 0) :
  order_of (x ^ s) = t :=
sorry

lemma order_of_inv (x : G) : order_of x⁻¹ = order_of x :=
sorry

lemma order_of_conj (x y : G) : order_of x = order_of (y * x * y⁻¹) :=
sorry

lemma order_comm (a b : G) : order_of (a * b) = order_of (b * a) :=
sorry

open_locale classical

lemma exists_even_order_of_of_even_card [fintype G] (h : even (fintype.card G)) :
  ∃ x : G, even (order_of x) := sorry


/-
6. (a) Find an onto function from ℕ to ℤ.
(b) Find a 1-1 function from ℤ to ℕ.
-/

import tactic

open function

def f (n : ℕ) : ℤ := if 2 ∣ n then n / 2 else - (n+1) / 2 -- replace 37 with a surjective function

lemma f_surj : surjective f :=
begin
  intro z,
  rcases lt_trichotomy z 0 with h1 | rfl | h3,
  { use (-2*z-1).nat_abs,
    unfold f,
    split_ifs,
  { exfalso,
    rw int.coe_nat_dvd_left.symm at h,
    simp only [int.coe_nat_bit0, nat.cast_one, neg_mul] at h,
    rw (show  -(2 * z) - 1 = 2 * (- z - 1) + 1, by ring) at h,
    rw dvd_add_right at h,
    norm_num at h,
    apply dvd_mul_right, },
  { apply int.div_eq_of_eq_mul_left,
    norm_num,
    rw int.nat_abs_of_nonneg (show ((-2) * z - 1) ≥ 0, by linarith),
    ring, }, },
  { use 0,
    unfold f,
    split_ifs,
    simp only [nat.cast_zero, euclidean_domain.zero_div],
    exfalso,
    norm_num at h, },
  { use (2 * z).nat_abs,
    unfold f,
    split_ifs,
    { apply int.div_eq_of_eq_mul_left,
      norm_num,
      rw int.nat_abs_of_nonneg (show 2 * z ≥ 0, by linarith),
      ring, },
    exfalso,
    apply h,
    rw ← int.coe_nat_dvd_left,
    norm_num, },
end

def g (z : ℤ) : ℕ := if 0 < z then 2 * z.nat_abs else 2 * z.nat_abs + 1 -- replace 37 with an injective function

lemma g_inj : injective g :=
begin
  intros a b hab,
  rcases nat.even_or_odd (g a) with h1 | h2,
  { have ha : 0 < a,
    { by_contra,
      push_neg at h,
      unfold g at h1,
      rw [if_neg h.not_lt, even_iff_two_dvd, nat.dvd_add_right] at h1,
      norm_num at h1,
      apply dvd_mul_right, },
    have hgb : even (g b),
    { rw ← hab, exact h1 },
    have hb : 0 < b,
    { by_contra,
      push_neg at h,
      unfold g at hgb,
      rw [if_neg h.not_lt, even_iff_two_dvd, nat.dvd_add_right] at hgb,
      norm_num at hgb,
      apply dvd_mul_right, },
    unfold g at hab,
    simp only [if_pos ha, if_pos hb, mul_eq_mul_left_iff, bit0_eq_zero, nat.one_ne_zero, or_false] at hab,
    zify at hab,
    rwa [int.nat_abs_of_nonneg ha.le, int.nat_abs_of_nonneg hb.le] at hab, },
  { have ha : a ≤ 0,
    { by_contra,
      push_neg at h,
      unfold g at h2,
      rw [if_pos h, nat.odd_iff_not_even] at h2,
      apply h2,
      rw even_iff_two_dvd,
      apply dvd_mul_right, },
    have hgb : odd (g b),
    { rw ← hab, exact h2 },
      have hb : b ≤ 0,
    { by_contra,
      push_neg at h,
      unfold g at hgb,
      rw [if_pos h, nat.odd_iff_not_even] at hgb,
      apply hgb,
      rw even_iff_two_dvd,
      apply dvd_mul_right, },
    unfold g at hab,
    simp only [if_neg ha.not_lt, if_neg hb.not_lt, add_left_inj, mul_eq_mul_left_iff, bit0_eq_zero, nat.one_ne_zero, or_false] at hab,
    zify at hab,
    simpa [int.of_nat_nat_abs_of_nonpos ha, int.of_nat_nat_abs_of_nonpos hb] using hab, },
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
import data.real.basic -- the reals
/-!

# Sets in Lean, sheet 4 : making sets from predicates

If we define

`def is_even (n : ℕ) : Prop := ∃ t, n = 2 * t`

then for `n` a natural, `is_even n` is a true-false statement,
i.e., a proposition. This means that `is_even : ℕ → Prop` is
a function taking naturals to true-false statements (also known as
a "predicate" on naturals), so we should be able to make the subset
of naturals where this predicate is true. In Lean the syntax for
this is

`{ n : ℕ | is_even n }`

The big question you would need to know about sets constructed in this
way is: how do you get from `t ∈ { n : ℕ | is_even n }` to `is_even t`?
And the answer is that these are equal by definition.

The general case: if you have a type `X` and a predicate `P : X → Prop`
then the subset of `X` consisting of the terms where the predicate is
true, is `{ x : X | P x }`, and the proof that `a ∈ { x : X | P x } ↔ P a` is `refl`.
Let's check:
-/

lemma mem_def (X : Type) (P : X → Prop) (a : X) : a ∈ { x : X | P x } ↔ P a :=
begin
  refl
end

/-

If you want, you can `rw mem_def` instead.

-/

open set

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

-- note that this is *syntactically* equal to `is_even : ℕ → Prop := λ n, ∃ t, n = 2 * t`
-- but the way I've written it is perhaps easier to follow.

example : 74 ∈ {n : ℕ | is_even n} :=
begin
  change ∃ (t : ℕ), 74 = 2 * t,
  -- exact ⟨37, by norm_num⟩, -- works
  use 37,
  norm_num,
end

-- Let's develop a theory of even real numbers
def real.is_even (r : ℝ) := ∃ t : ℝ, r = 2 * t

-- Turns out it's not interesting
example : ∀ x , x ∈ {r : ℝ | real.is_even r} :=
begin
  intro x,
  use x/2,
  ring,
end

-- likewise, the theory of positive negative real numbers is not interesting
example : ∀ x, x ∉ {r : ℝ | 0 < r ∧ r < 0} :=
begin
  -- quick way to change the type of `hx` to something definitionally equal
  rintro x (hx : 0 < x ∧ x < 0),
  -- `linarith` is happy to use ∧ hypotheses
  linarith,
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

/-!

# Logic in Lean, example sheet 4 : "and" (`∧`)

We learn about how to manipulate `P ∧ Q` in Lean.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following tactics:

* `cases`
* `split`

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.

variables (P Q R : Prop)

example : P ∧ Q → P :=
begin
  intro hPQ,
  exact hPQ.left,
end

example : P ∧ Q → P :=
begin
  rintro ⟨hP, hQ⟩,
  exact hP,
end

-- Alternative solution
example : P ∧ Q → P :=
begin
  intro hPQ,
  cases hPQ with hP _,
  exact hP,
end

example : P ∧ Q → Q :=
begin
  intro hPQ,
  cases hPQ with _ hQ,
  exact hQ,
end

example : (P → Q → R) → (P ∧ Q → R) :=
begin
  intros hPQR hPQ,
  exact hPQR hPQ.left hPQ.right,
end

example : P → Q → P ∧ Q :=
begin
  intros hP hQ,
  exact ⟨hP, hQ⟩,
end

example : P → Q → P ∧ Q :=
begin
  intros hP hQ,
  split,
  exact hP, -- DON'T DO THIS! Will dock marks. After split, put each goal in { } or exacts if it's this simple
  exact hQ,
end

/-- `∧` is symmetric -/
example : P ∧ Q → Q ∧ P :=
begin
  intro hPQ,
  exact ⟨hPQ.right, hPQ.left⟩,
end

example : P ∧ Q → Q ∧ P :=
begin
  rintro ⟨hP, hQ⟩,
  split,
  exacts [hQ, hP],
end

example : P → P ∧ true :=
begin
  intro hP,
  split, -- gross
  exact hP,
  triv,
end

example : P → P ∧ true :=
begin
  intro hP,
  exact ⟨hP, true.intro⟩,
end

example : P → P ∧ true :=
λ hP, ⟨hP, true.intro⟩ -- yum

example : false → P ∧ false :=
begin
  intro hF,
  split,
  exfalso,
  exacts [hF, hF],
end

-- TODO: improve this
example : false → P ∧ false :=
begin
  intro hF,
  split, -- how do I do the thing where you solve part of a goal
  exfalso, -- and then the rest becomes the rest of the goal?
  { exact hF, },
  { exact hF, },
end

/-- `∧` is transitive -/
example : (P ∧ Q) → (Q ∧ R) → (P ∧ R) :=
begin
  intros hPQ hQR,
  exact ⟨hPQ.left, hQR.right⟩,
end

example : ((P ∧ Q) → R) → (P → Q → R) :=
begin
  intros hPQR hP hQ,
  exact hPQR ⟨hP, hQ⟩,
end


import Mathlib.Init.Data.Nat.Basic
/-!
## Pattern Matching Expressions

Pattern matching is possible not only at the top level of a `def` command but also
deeply within terms, via a `match` expression. The construct has the following general syntax:

```lean
match term₁, . . ., termm with
| pattern₁₁, . . ., pattern₁ₘ => result₁
.
.
.
| patternn₁, . . ., patternₙₘ => resultₙ
```

The patterns may contain variables, constructors, and nameless
placeholders (_). The `resultᵢ` expressions may refer to the variables introduced
in the corresponding patterns.

The following function definition demonstrates the syntax of pattern matching
within expressions:
-/
def bcount {α : Type} (p : α → Bool) : List α → ℕ
| [] => 0
| (x :: xs) =>
  match p x with
  | true => (bcount p xs) + 1
  | false => bcount p xs

#eval bcount (λ x : Nat => x >= 5) (List.range 10) -- 5

/-!
The `bcount` function counts the number of elements in a list that satisfy the given
predicate `p`. The predicate’s codomain is `Bool`. As a general rule, we will use type
`Bool`, of Booleans, within programs and use the type `Prop`, of propositions, when
stating properties of programs.

The connectives are called `or` (infix: `||`), `and` (infix: `&&`), and `not` (`¬`) or (`!`).

BUGBUG: the book also adds `xor` but I couldn't find that in Lean, I only found HXor.hXor?

Here's an example that shows how to match on two variables:
-/
def extract  {α : Type} : List α → ℕ → List α
  | _, 0 => []
  | [], _ => []
  | (x :: xs), i + 1 => x :: (extract xs i)

#eval extract (List.range 10) 3  -- [0, 1, 2]

/-!
Notice that the variables are extracted in order from the return type.
This example also shows how to use an induction technique matching `i + 1` so that
the count is decremented by one on each recursive call.

You can write this with full match expression if you prefer to name the arguments for
documentation reasons:
-/
def extract₂ {α : Type} (xs : List α) (count : ℕ) : List α :=
  match xs, count with
    | _, 0 => []
    | [], _ => []
    | (x :: xs), i + 1 => x :: (extract xs i)
/-!
We cannot match on a proposition of type `Prop`, but we can use if–then–else
instead. For example, the min operator on natural numbers operator can be defined as follows:

-/
def minimum (a b : ℕ) : ℕ :=
  if a ≤ b then a else b

#eval minimum 5 9   -- 5

/-!
This works only for propositions that are decidable (i.e., executable). This is the
case for `≤`: Given concrete values for the arguments, such as `35` and `49`, Lean can
reduce `35 ≤ 49` to true. Lean keeps track of decidability using a mechanism called
type classes, which will be explained below.
-/


/-
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
import data.nat.modeq

/-!
# IMO 1964 Q1

(a) Find all positive integers $n$ for which $2^n-1$ is divisible by $7$.

(b) Prove that there is no positive integer $n$ for which $2^n+1$ is divisible by $7$.

We define a predicate for the solutions in (a), and prove that it is the set of positive
integers which are a multiple of 3.
-/

/-!
## Intermediate lemmas
-/

open nat.modeq

lemma two_pow_three_mul_mod_seven (m : ℕ) : 2 ^ (3 * m) ≡ 1 [MOD 7] :=
begin
  rw pow_mul,
  have h : 8 ≡ 1 [MOD 7] := modeq_of_dvd (by {use -1, norm_num }),
  convert modeq_pow _ h,
  simp,
end

lemma two_pow_three_mul_add_one_mod_seven (m : ℕ) : 2 ^ (3 * m + 1) ≡ 2 [MOD 7] :=
begin
  rw pow_add,
  exact modeq_mul (two_pow_three_mul_mod_seven m) (show 2 ^ 1 ≡ 2 [MOD 7], by refl),
end

lemma two_pow_three_mul_add_two_mod_seven (m : ℕ) : 2 ^ (3 * m + 2) ≡ 4 [MOD 7] :=
begin
  rw pow_add,
  exact modeq_mul (two_pow_three_mul_mod_seven m) (show 2 ^ 2 ≡ 4 [MOD 7], by refl),
end

/-!
## The question
-/

def problem_predicate (n : ℕ) : Prop := 7 ∣ 2 ^ n - 1

lemma aux (n : ℕ) : problem_predicate n ↔ 2 ^ n ≡ 1 [MOD 7] :=
begin
  rw nat.modeq.comm,
  apply (modeq_iff_dvd' _).symm,
  apply nat.one_le_pow'
end

theorem imo1964_q1a (n : ℕ) (hn : 0 < n) : problem_predicate n ↔ 3 ∣ n :=
begin
  rw aux,
  split,
  { intro h,
    let t := n % 3,
    rw [(show n = 3 * (n / 3) + t, from (nat.div_add_mod n 3).symm)] at h,
    have ht : t < 3 := nat.mod_lt _ dec_trivial,
    interval_cases t with hr; rw hr at h,
    { exact nat.dvd_of_mod_eq_zero hr },
    { exfalso,
      have nonsense := (two_pow_three_mul_add_one_mod_seven _).symm.trans h,
      rw modeq_iff_dvd at nonsense,
      norm_num at nonsense },
    { exfalso,
      have nonsense := (two_pow_three_mul_add_two_mod_seven _).symm.trans h,
      rw modeq_iff_dvd at nonsense,
      norm_num at nonsense } },
  { rintro ⟨m, rfl⟩,
    apply two_pow_three_mul_mod_seven }
end

theorem imo1964_q1b (n : ℕ) : ¬ (7 ∣ 2 ^ n + 1) :=
begin
  let t := n % 3,
  rw [← modeq_zero_iff, (show n = 3 * (n / 3) + t, from (nat.div_add_mod n 3).symm)],
  have ht : t < 3 := nat.mod_lt _ dec_trivial,
  interval_cases t with hr; rw hr,
  { rw add_zero,
    intro h,
    have := h.symm.trans (modeq_add (two_pow_three_mul_mod_seven _) (nat.modeq.refl _)),
    rw modeq_iff_dvd at this,
    norm_num at this },
  { intro h,
    have := h.symm.trans (modeq_add (two_pow_three_mul_add_one_mod_seven _) (nat.modeq.refl _)),
    rw modeq_iff_dvd at this,
    norm_num at this },
  { intro h,
    have := h.symm.trans (modeq_add (two_pow_three_mul_add_two_mod_seven _) (nat.modeq.refl _)),
    rw modeq_iff_dvd at this,
    norm_num at this },
end


/- ∀ introduction

Suppose P and Q are arbitrary propositions.
To prove a proposition given in the form of
a universal generalization, ∀ (p : P), Q,
assume that P is proved and that you have an
arbitrary but specific proof p' of P, and
show that, in this context, Q must be true,
by showing you can construct a proof of it.

Here's an example. The proposition says that
for all propositions, P and Q, if P ∨ Q is
proven, then Q ∨ P can be proven. To prove it,
*assume* P and Q are arbitrary but specific,
propositions, then, in this context, prove
the rest (that P ∨ Q → Q ∨ P).

In short, to prove ∀ (p : P), Q, assume that
you have a proof of P and show that in this
context you can then prove Q. This is the rule
of ∀ introduction.
-/

theorem or_commutative : ∀ {P Q : Prop}, P ∨ Q → Q ∨ P :=
λ (P Q : Prop),     -- assume arbitrary values of P, Q
                    -- in this context, prove the rest
                    -- for completeness, here's the rest
    λ (porq : P ∨ Q),   -- by → introduction
        match porq with -- by case analysis
        | or.inl p := or.inr p  -- true in this case
        | or.inr q := or.inl q  -- true in other case
        end                     -- QED

/- ∀ elimination

Suppose that in the set of proofs you already have
or that you have assumed, you have a proof, all_p_q,
of ∀ (p : P), Q. The rule of ∀ elimination allows
you to *apply* this generalization to any specific
value, p' : P, to obtain a corresponding proof of Q.

To put it simply, once you've proved a *general*
theorem, you can apply that result to any particular
instance. For example, we just proved that the ∨
connective of predicate logic is commutative, *in
general*. So now suppose we're given a proof of
1=1 ∨ 2=3 and what we need to prove is 2 = 3 ∨ 1 = 1.
We do *not* need to prove this special case from
scratch; rather we can simply *apply* the theorem
and be done with it.
-/

-- First, let's get a proof of (1 = 1 ∨ 2 = 3)

lemma l1 : 1=1 ∨ 2=3 := or.inl rfl

-- Just apply theorem to get proof of 2 = 3 ∨ 1 = 1
#check or_commutative l1

example : 2 = 3 ∨ 1 = 1 := or_commutative l1

/-
It would be hard to overstate the importance of the
idea of ∀ elimination. It's just a fancy way to say
that if you've proven a general theorem, you can use
it immediately without having to re-prove anything to
prove any particular special case that matches the
conditions that it demands (here that P and Q are
any propositions and that you have a proof of P ∨ Q).

You can think of a theorem as a subroutine that you
have already implemented that you can apply whenever
you need it! This idea is what allows mathematics to
work, so not every proof has to start from scratch.

In the constructive logic of Lean, indeed a proof of
∀ (p : P), Q, *is* a function: one that, if given any
proof (or equivalently, a value) of P returns a proof
(a value) of Q.
-/