/-
Produce a proof, pf1, of the proposition
that 0 = 0 ∧ (1 = 1 ∧ 2 = 2).
-/

lemma pf1 : 0 = 0 ∧ (1 = 1 ∧ 2 = 2) :=
    and.intro rfl (and.intro rfl rfl)

/-
Produce a proof, pf2, of the proposition
that (0 = 0 ∧ 1 = 1) ∧ 2 = 2
-/

lemma pf2 : (0 = 0 ∧ 1 = 1) ∧ 2 = 2 :=
    and.intro (and.intro rfl rfl) rfl

/-
Produce a proof, pf3, of the proposition
that 0 = 0 ∧ 1 = 1 ∧ 2 = 2. Hint, one of
the two preceding proofs can be used to
prove this proposition; there's no need
to type out a whole new proof.
-/
lemma pf3 : 0 = 0 ∧ 1 = 1 ∧ 2 = 2 :=
    pf1

/-
An operator, *, is "right associative"
if X * Y * Z means X * (Y * Z), and is
"left associative" if X * Y * Z means
(X * Y) * Y. Is the logical connective,
∧, left or right associative? Explain.
-/

-- It's right associative as we just proved
-- pf3 with pf1, and pf1 is in the form
-- X * (Y * Z).

/-
Use Lean to produce a proof, pf4, that
0 = 0 ∧ 1 = 1 ∧ 2 = 2 is exactly the
same proposition as one of the two
parenthesized forms. It will be a
proof that two propositions are equal.
Put parentheses around each of the
propositions.
-/

lemma pf4 :
    (0 = 0 ∧ 1 = 1 ∧ 2 = 2) =
    (0 = 0 ∧ (1 = 1 ∧ 2 = 2)) :=
        rfl

/-
Given arbitrary propositions, P, Q and
R it should be possible to produce a
proof, pf5, showing that if P ∧ (Q ∧ R)
is true then so is (P ∧ Q) ∧ R. Written
in inference rule form, this would say
the following:

{ P Q R: Prop }, p_qr : P ∧ (Q ∧ R)
---------------------------------- ∧.assoc_1
          pq_r : (P ∧ Q) ∧ R

Proving that this is a valid rule
can be done by defining a function,
let's call it and_assoc_l, that when
given any propositions, P, Q, and R
(implicitly), and when given a proof
of P ∧ (Q ∧ R), constructs and returns
a proof of (P ∧ Q) ∧ R.

Here we give you this function, and
we explain each part in comments.
You will then apply what you learn
by studying this example to solve
the same problem but going in the
other direction. Here's the solution.
-/

-- define the function name
def and_assoc_l
-- specify the arguments and their types
        {P Q R: Prop}
        (pf: P ∧ (Q ∧ R)) : -- note colon

-- the return type
        (P ∧ Q) ∧ R
/-
What we've given so far is what we call
the function signature: its name, the
names and types of the arguments that it
takes, and the type of the return value.
In this case, the return value is of
type (P ∧ Q) ∧ R, and will thus serve
as a proof of this proposition. This is
a function that takes a proof and returns
a (different) proof. It thus provides a
general recipe for turning any proof of
P ∧ (Q ∧ R) into a proof of (P ∧ Q) ∧ R.
-/
        :=  -- now give function body

/-
Usually we'd expect to see an expression
here, involving multiple, nested and.elim
and and.intro expressions. We could write
the function body that way, but it's a bit
tricky to get all the nested expressions
right. Here's a revelation: We can use a
tactic script to produce the same result.

Open your Messages window, put your cursor
on begin, study carefully the tactic state,
notice that the arguments are given in the
context to the left of the turnstile and
the goal remaining to be proved is to the
right. You can use the context values as
arguments to tactics.

Now click through each line of the script
and study very carefully how it changes the
context. By the end of the script, you
should see how we've been able to use
elimination rules take apart the proof
that was given as an argument, giving names
to the parts, and how we can then further
take apart those parts, giving names to the
subparts, and finally how we can intro
rules to put all these pieces together
again into the proof we need.
-/
    begin
    have pfP := and.elim_left pf,
    have pfQR := and.elim_right pf,
    have pfQ := and.elim_left pfQR,
    have pfR := and.elim_right pfQR,
    have pfPQ := and.intro pfP pfQ,
    have pfPQ_R := and.intro pfPQ pfR,
    exact pfPQ_R
    end

/-
Define another function, and_assoc_r,
that goes the other direction: given
a proof of (P ∧ Q) ∧ R it derives and
returns a proof of P ∧ (Q ∧ R). Write
the entire solution yourself.
-/

def and_assoc_r
        {P Q R: Prop}
        (pf: (P ∧ Q) ∧ R) :
        P ∧ (Q ∧ R) :=
    begin
    have pfPQ := and.elim_left pf,
    have pfR := and.elim_right pf,
    have pfP := and.elim_left pfPQ,
    have pfQ := and.elim_right pfPQ,
    have pfQR := and.intro pfQ pfR,
    have pfP_QR := and.intro pfP pfQR,
    exact pfP_QR
    end

/-
It's important to learn how you would
give such proofs in natural langage.
Let's take our first example. Here is
a natural language version.

"Given arbitrary propositions, P, Q, and
R, and the assumption that P ∧ (Q ∧ R) is
true, we are to show that (P ∧ Q) ∧ R is
true.

Given that P ∧ (Q ∧ R) is true, it must
be that P is true and that Q ∧ R is also
true. Given that Q ∧ R is true, it must
be that Q is true, and R is also true.
So we have that P, Q, and R are all true.

From these conclusions we can in turn
deduce that P ∧ Q must be true. And so
we now have that P ∧ Q is true and so is
R, from which, finally we can deduce
that (P ∧ Q) ∧ R must be true as well.

QED."

Now it's your turn: write an English
language proof for the theorem in the
other direction.
-/

/-
"Given arbitrary propositions, P, Q, and
R, and the assumption that (P ∧ Q) ∧ R is
true, we are to show that P ∧ (Q ∧ R) is
true.

Given that (P ∧ Q) ∧ R, it must be that
(P ∧ Q) is true and also that R is true.
Given that (P ∧ Q) is true, then P must
be true, and Q must be true. Therefore
P, Q, and R are all true.

From this conclusion we can deduce that
Q ∧ R must be true. Since P is true and
(Q ∧ R) is true, it follows that P ∧ (Q ∧ R)
must be true as well.

QED."

-/

/-
Use Lean to produce a proof, tnott, of
the proposition that truth isn't truth.
I.e., true is not true. We'll write this
is Lean like this:

theorem tnott: true ≠ true := _.

To make it a little easier to solve
this otherwise difficult problem, we
allow you to stipulate one "axiom" of
your choice, which you can then use
to produce the required proof.
-/

-- You can introduce an axiom here
axiom f: false

-- Now prove the theorem
theorem tnott : true ≠ true := false.elim f

/-
What did you have to accept to be able
to prove that truth isn't truth?
-/

/- We had to accept that there was an axiom
for false, which is an absurdity, which
then allows us to create a proof for a
proposition for which there is not proof
-/


import tuto_lib
import data.int.parity
/-
Negations, proof by contradiction and contraposition.

This file introduces the logical rules and tactics related to negation:
exfalso, by_contradiction, contrapose, by_cases and push_neg.

There is a special statement denoted by `false` which, by definition,
has no proof.

So `false` implies everything. Indeed `false → P` means any proof of
`false` could be turned into a proof of P.
This fact is known by its latin name
"ex falso quod libet" (from false follows whatever you want).
Hence Lean's tactic to invoke this is called `exfalso`.
-/

example : false → 0 = 1 :=
begin
  intro h,
  exfalso,
  exact h,
end

/-
The preceding example suggests that this definition of `false` isn't very useful.
But actually it allows us to define the negation of a statement P as
"P implies false" that we can read as "if P were true, we would get
a contradiction". Lean denotes this by `¬ P`.

One can prove that (¬ P) ↔ (P ↔ false). But in practice we directly
use the definition of `¬ P`.
-/

example {x : ℝ} : ¬ x < x :=
begin
  intro hyp,
  rw lt_iff_le_and_ne at hyp,
  cases hyp with hyp_inf hyp_non,
  clear hyp_inf, -- we won't use that one, so let's discard it
  change x = x → false at hyp_non, -- Lean doesn't need this psychological line
  apply hyp_non,
  refl,
end

open int

-- 0045
example (n : ℤ) (h_even : even n) (h_not_even : ¬ even n) : 0 = 1 :=
begin
  sorry
end

-- 0046
example (P Q : Prop) (h₁ : P ∨ Q) (h₂ : ¬ (P ∧ Q)) : ¬ P ↔ Q :=
begin
  sorry
end

/-
The definition of negation easily implies that, for every statement P,
P → ¬ ¬ P

The excluded middle axiom, which asserts P ∨ ¬ P allows us to
prove the converse implication.

Together those two implications form the principle of double negation elimination.
  not_not {P : Prop} : (¬ ¬ P) ↔ P

The implication `¬ ¬ P → P` is the basis for proofs by contradiction:
in order to prove P, it suffices to prove ¬¬ P, ie `¬ P → false`.

Of course there is no need to keep explaining all this. The tactic
`by_contradiction Hyp` will transform any goal P into `false` and
add Hyp : ¬ P to the local context.

Let's return to a proof from the 5th file: uniqueness of limits for a sequence.
This cannot be proved without using some version of the excluded middle
axiom. We used it secretely in

eq_of_abs_sub_le_all (x y : ℝ) : (∀ ε > 0, |x - y| ≤ ε) → x = y

(we'll prove a variation on this lemma below).

In the proof below, we also take the opportunity to introduce the `let` tactic
which creates a local definition. If needed, it can be unfolded using `dsimp` which
takes a list of definitions to unfold. For instance after using `let N₀ := max N N'`,
you could write `dsimp [N₀] at h` to replace `N₀` by its definition in some
local assumption `h`.
-/
example (u : ℕ → ℝ) (l l' : ℝ) : seq_limit u l → seq_limit u l' → l = l' :=
begin
  intros hl hl',
  by_contradiction H,
  change l ≠ l' at H, -- Lean does not need this line
  have ineg : |l-l'| > 0,
    exact abs_pos.mpr (sub_ne_zero_of_ne H), -- details about that line are not important
  cases hl ( |l-l'|/4 ) (by linarith) with N hN,
  cases hl' ( |l-l'|/4 ) (by linarith) with N' hN',
  let N₀ := max N N',
  specialize hN N₀ (le_max_left _ _),
  specialize hN' N₀ (le_max_right _ _),
  have clef : |l-l'| < |l-l'|,
    calc
    |l - l'| = |(l-u N₀) + (u N₀ -l')|   : by ring_nf
         ... ≤ |l - u N₀| + |u N₀ - l'|  : by apply abs_add
         ... = |u N₀ - l| + |u N₀ - l'|  : by rw abs_sub_comm
         ... < |l-l'|                    : by linarith,
  linarith, -- linarith can also find simple numerical contradictions
end

/-
Another incarnation of the excluded middle axiom is the principle of
contraposition: in order to prove P ⇒ Q, it suffices to prove
non Q ⇒ non P.
-/

-- Using a proof by contradiction, let's prove the contraposition principle
-- 0047
example (P Q : Prop) (h : ¬ Q → ¬ P) : P → Q :=
begin
  sorry
end

/-
Again Lean doesn't need this principle explained to it. We can use the
`contrapose` tactic.
-/

example (P Q : Prop) (h : ¬ Q → ¬ P) : P → Q :=
begin
  contrapose,
  exact h,
end

/-
In the next exercise, we'll use
 odd n : ∃ k, n = 2*k + 1
 int.odd_iff_not_even {n : ℤ} : odd n ↔ ¬ even n
-/
-- 0048
example (n : ℤ) : even (n^2) ↔ even n :=
begin
  sorry
end
/-
As a last step on our law of the excluded middle tour, let's notice that, especially
in pure logic exercises, it can sometimes be useful to use the
excluded middle axiom in its original form:
  classical.em : ∀ P, P ∨ ¬ P

Instead of applying this lemma and then using the `cases` tactic, we
have the shortcut
 by_cases h : P,

combining both steps to create two proof branches: one assuming
h : P, and the other assuming h : ¬ P

For instance, let's prove a reformulation of this implication relation,
which is sometimes used as a definition in other logical foundations,
especially those based on truth tables (hence very strongly using
excluded middle from the very beginning).
-/

variables (P Q : Prop)

example : (P → Q) ↔ (¬ P ∨ Q) :=
begin
  split,
  { intro h,
    by_cases hP : P,
    { right,
      exact h hP },
    { left,
      exact hP } },
  { intros h hP,
    cases h with hnP hQ,
    { exfalso,
      exact hnP hP },
    { exact hQ } },
end

-- 0049
example : ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
begin
  sorry
end

/-
It is crucial to understand negation of quantifiers.
Let's do it by hand for a little while.
In the first exercise, only the definition of negation is needed.
-/

-- 0050
example (n : ℤ) : ¬ (∃ k, n = 2*k) ↔ ∀ k, n ≠ 2*k :=
begin
  sorry
end

/-
Contrary to negation of the existential quantifier, negation of the
universal quantifier requires excluded middle for the first implication.
In order to prove this, we can use either
* a double proof by contradiction
* a contraposition, not_not : (¬ ¬ P) ↔ P) and a proof by contradiction.
-/

def even_fun (f : ℝ → ℝ) := ∀ x, f (-x) = f x

-- 0051
example (f : ℝ → ℝ) : ¬ even_fun f ↔ ∃ x, f (-x) ≠ f x :=
begin
  sorry
end

/-
Of course we can't keep repeating the above proofs, especially the second one.
So we use the `push_neg` tactic.
-/

example : ¬ even_fun (λ x, 2*x) :=
begin
  unfold even_fun, -- Here unfolding is important because push_neg won't do it.
  push_neg,
  use 42,
  linarith,
end

-- 0052
example (f : ℝ → ℝ) : ¬ even_fun f ↔ ∃ x, f (-x) ≠ f x :=
begin
  sorry
end

def bounded_above (f : ℝ → ℝ) := ∃ M, ∀ x, f x ≤ M

example : ¬ bounded_above (λ x, x) :=
begin
  unfold bounded_above,
  push_neg,
  intro M,
  use M + 1,
  linarith,
end

-- Let's contrapose
-- 0053
example (x : ℝ) : (∀ ε > 0, x ≤ ε) → x ≤ 0 :=
begin
  sorry
end

/-
The "contrapose, push_neg" combo is so common that we can abreviate it to
`contrapose!`

Let's use this trick, together with:
  eq_or_lt_of_le : a ≤ b → a = b ∨ a < b
-/

-- 0054
example (f : ℝ → ℝ) : (∀ x y, x < y → f x < f y) ↔ (∀ x y, (x ≤ y ↔ f x ≤ f y)) :=
begin
  sorry
end


import data.set

universes u v   -- Universe levels, don't worry

/-
Homework #6: Functions, Sets, Relations
-/

/- #1. Basic Functions [35 points]-/

/- A [5 points].

Write a function, isZero, using "by cases" syntax,
that takes any natural number, n, as an argument and
returns a Boolean result, true if n = 0 and false if
n > 0 (i.e., n = n' + 1 = (nat.succ n') for some n').
(Don't get distracted by unnecessary details.)
-/

def isZero : ℕ → bool
| 0 := tt
| _ := ff

-- These test cases should pass except the last
example : isZero 0 = tt := rfl
example : isZero 1 = ff := rfl
example : isZero 2 = ff := rfl
example : isZero 3 = tt := rfl    -- tester tester

/- B [10 points].

Here's a function, fib, using "by cases" syntax, that
takes any natural number, n, as an argument and that
returns the n'th Fibonacci number. The n'th Fibonacci
number is defined by cases: when n is 0, it's 0; when
n is 1 (nat.succ nat.zero), it's 1, otherwise, in the
case where n = n' + 2, it's fib n' + fib n'+1
-/

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

-- These test cases should pass except the last
example : fib 0 = 0 := rfl
example : fib 1 = 1 := rfl
example : fib 2 = 1 := rfl
example : fib 3 = 2 := rfl
example : fib 4 = 3 := rfl
example : fib 5 = 5 := rfl
example : fib 10 = 55 := rfl
example : fib 2 = 2 := rfl    -- tester tester

/- C [10 points]
Write a function, add, that takes two natural
numbers and computes their sum. You may *not*
use the addition function that Lean defines in
its libraries. Your addition function should
be defined by cases. Given arguments n and m,
if m is zero, just return n; otherwise, if m
is (m' + 1), return the sum of n and m' plus
one. (That's still the right answer, right!)
-/

def add : ℕ → ℕ → ℕ
| n 0 := n
| n (m' + 1) := (add n m') + 1

-- These test cases should pass except the last
example : add 0 0 = 0 := rfl
example : add 5 0 = 5 := rfl
example : add 0 5 = 5 := rfl
example : add 1 1 = 2 := rfl
example : add 1 2 = 3 := rfl
example : add 2 2 = 4 := rfl
example : add 3 2 = 4 := rfl  -- tester tester

/- D [10 points]

A function is said to be involutive, or an
involution, if applying it twice to *any* value
(of the right type) returns that original value.
For example, if f is a function, and a is any
argument, then f is involutive if f (f a) = a.
Formally define involutive as a property of
any function, f : α → α.
-/

def involutive {α : Sort u} (f : α → α) :=
  ∀ (a : α), f (f a) = a

/-
Now prove the proposition that the Boolean negation
function, called bnot in Lean, is an involution. The
trick here is simply to pick the right proof strategy.
Ask, how can I prove it? Hint: It's just a bool!
-/

example : involutive bnot :=
begin
-- By the definition of involutive ...
unfold involutive,

-- ... we are to
show  ∀ (b : bool), !!b = b,

-- To start we assume b to be an arbitrary bool value.
assume b,

-- We then show that in all cases (b=tt, b=ff) that !!b = b
cases b,

-- In all cases the conclusion is true.
exact rfl,
exact rfl,

-- QED
end

/- 2 Sets [35 points] -/

/- A [5 points].

Formally define a predicate, perfectSquare,
satisfied by any natural number that is the
square of some other natural number. 25 is a
perfect square, for example, because it's the
square of the natural number 5. Fill in both
underscores in the following skeleton to give
your answer. Remember: Predicates take values
to propositions.
-/

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

/- B [5 points].

Define the set of all perfect squares using
set comprehension (set builder) notation and
the perfectSquare predicate.
-/

def perfectSquares : set ℕ := { n | perfectSquare n }

/- C [5 point].

State and prove the proposition that 25 is in
the set of perfect squares. Use set membership
notation in writing your proposition.
-/

example : 25 ∈ perfectSquares :=
begin
-- Expanding the definition of perfectSquares, ...
unfold perfectSquares,

-- We are to ...
show perfectSquare 25,

-- Expanding the definition of perfectSquare, ...
unfold perfectSquare,

-- We are to show that
show ∃ (m : ℕ), m * m = 25,

-- The challenge in proving a ∃ is to find/compute a witness
-- Here it's easy to see that the witness has to be 5
apply exists.intro 5,

-- The rest is by simple algebra and reflexivity of equality
trivial,
end

/- D [20 points].
Consider the following sets of natural numbers:

X = { 2, 3, 4}
Y = { 4, 5, 6}
-/

def X : set ℕ := { 2, 3, 4 }
def Y : set ℕ := { 4, 5, 6 }

/-
Formally state and prove the following propositions
using set and set operator notations.

1. 4 is "in" the intersection of X and Y
2. 4 is in the union of X and Y
3. 4 is not in the set difference, X \ Y
4. 10 is in the complement of X
-/

-- 1
example : 4 ∈ X :=
begin
-- It's easier if we expand the definition of X
unfold X,

-- What we now need to show is
show 4 = 2 ∨ 4 = 3 ∨ 4 = 4,

-- Remembering ∨ is right associative, we can prove the right side
right,

-- And again
right,

-- And the rest is trivial
exact rfl,

-- QED
end

-- 2
example : 4 ∈ (X ∪ Y) :=
begin
-- Unfolding the definitions of X and Y ...
unfold X Y,

-- we are to show
show (4 = 2 ∨ 4 = 3 ∨ 4 = 4) ∨ (4 = 4 ∨ 4 = 5 ∨ 4 = 6),

-- We can prove either side, but the right is easier
right,

-- We can prove the left disjunct
left,
exact rfl,

-- And we're done. QED.
end

/-
Hint: IN the following problem, you
might want to fact that 4 ∈ {4, 5, 6}
without stopping to prove it.  Here's a
nice trick/tactic:

let n : 4 ∈ {4, 5, 6} := _

This will put the assumption, n, in your
context, but with a note attached saying
that you still need a proof of it. Don't
worry about the crazy goal set after you
use this hint (if you do). Just do what
you've learned to do looking at the top
goal carefully and choosing the right
next oroof strategy.
-/

-- 3
example : 4 ∉ (X \ Y) :=
begin
-- Unfolding X and Y we see that ...
unfold X Y,

-- ... we must
show 4 ∈ {2, 3, 4} \ {4, 5, 6}  → false,

-- The proof of this is by negation. We assume the premise
assume h,

-- and show that that leads to a contradiction

/-
A case analysis on this assumption indicates that
we have to prove 4 is in the left and not in the
right set
-/
cases h with x y,

/-
But 4 is in the right-hand set. So that's going
to produce a contradiction. Let's just assert that
4 is in the right hand set (and we'll prove it later).
-/
let n : 4 ∈ {4, 5, 6} := _, -- _ is the missing proof

-- We now have our contradiction
contradiction,

-- All we need now is that delayed proof ...
left,
exact rfl,

-- That's easy to prove. QED.
end

-- 4. 10 ∈ Xᶜ

example : 10 ∈ Xᶜ :=
begin
-- We'll start by expanding the definition of X
unfold X,

-- What we must now show is that ¬ (10 ∈ {2, 3, 4}).
-- The prove is by negation.
assume h,

-- But 10 is not in {2, 3, 4},
-- as revealed by repeated analysis of h
repeat { cases h },
end

/- 3. Relations [30 points] -/

/- A [15 points].

State and prove the proposition that any
function, f, in Lean, from any type α to any
type β, is single valued.
-/

/- 1 [5 point].
Formally define single-valuedness for any
binary relation r : α → β → Prop. If r is
single valued then r x can have two values,
say y and z, only if y = z. Think about this
hard enough that it really makes sense to you.
-/

def single_valued   -- predicate on relations
{α β : Type}
(r : α → β → Prop)
: Prop :=
  ∀ a x y, r a x → r a y → x = y

/- 2 [5 points]

Define a relation sqrs : ℕ → ℕ → Prop where sq x y
means that y is the square of x.
-/

def sqrs : ℕ → ℕ → Prop
| x y := y = x * x

/- 3 [5 points]

Now formally state and prove the proposition
that the sqrs relation is single-valued.
-/

example : single_valued sqrs :=
begin
-- By expanding the definitions of single_valued and sqrs ...
unfold single_valued sqrs,

-- ... we see that we are to ,,,
show ∀ (a x y : ℕ), x = a * a → y = a * a → x = y,

-- We assume the premises
assume a x y hx hy,

-- and now need to show that x = y, which is done
-- by rewriting both x and y to a*a (using eq elimination)
rw hx,
rw hy,

-- A final (Lean-automated) application of reflexivity of
-- equality finishes the proof. QED.
end

/- B [5 points]

A mathematical function, f : α → β, is said
to be injective if it never relates different
α (first) values to the same β (second) value.
The only way an injective function f can relate
a to x and also b to x, is if a = b. A function
that satisfies this constraint is injective.
-/

def injective
(α : Sort u)        -- any type from any universe
(β : Sort v)        -- any type from any universe
(r : α → β → Prop)  -- any relation on α and β
(a b : α)           -- any two arbitrary α values
(x : β) :           -- any β value
Prop :=
  -- Answer
  r a x →           -- if r relates a to x, and
  r b x →           -- if r relates b to x, then
  a = b             -- injectivity demands a = b

/- C [10 points].

A mathematical function, f : α → β, is said to be
a total function if it's defined (there is some
β value) for all α values. ∀ (a : α), β. Indeed,
this is the meaning of the notation α → β in Lean
and similar logics. The upshot is that functions
in Lean can only be total.  Complete the following
proof that every function in Lean is total.
-/

example :
  ∀ (α β : Type)
  (f : α → β),
    ∀ (a : α),
      ∃ (b : β),
        f a = b :=
begin
-- Assume the given conditions
assume α β f a,

/-
Given a, and given the fact that "functions" in Lean
must be total, we can of course compute a b such that
f a = b, and that done by just computing f a. Totality
of f means that this application must and will return
result of type β. So we can just use that value as a
witness to prove  that ∃ (b : β), f a = b.
-/
apply exists.intro (f a),

-- The rest is ...
trivial,

-- QED
end


-- ---------------------------------------------------------------------
-- Ejercicio. Sean a y b números reales. Demostrar que
--    2*a*b ≤ a^2 + b^2
-- ----------------------------------------------------------------------

import data.real.basic
import tactic

variables a b : ℝ

-- 1ª demostración
example : 2*a*b ≤ a^2 + b^2 :=
begin
  have : 0 ≤ (a - b)^2 := sq_nonneg (a - b),
  have : 0 ≤ a^2 - 2*a*b + b^2, by linarith,
  show 2*a*b ≤ a^2 + b^2, by linarith,
end

-- 2ª demostración
example : 2*a*b ≤ a^2 + b^2 :=
begin
  have h : 0 ≤ a^2 - 2*a*b + b^2,
  { calc a^2 - 2*a*b + b^2
        = (a - b)^2                   : by ring
    ... ≥ 0                           : by apply pow_two_nonneg },
  calc 2*a*b
       = 2*a*b + 0                   : by ring
   ... ≤ 2*a*b + (a^2 - 2*a*b + b^2) : add_le_add (le_refl _) h
   ... = a^2 + b^2                   : by ring
end

-- 3ª demostración
example : 2*a*b ≤ a^2 + b^2 :=
begin
  have : 0 ≤ a^2 - 2*a*b + b^2,
  { calc a^2 - 2*a*b + b^2
         = (a - b)^2       : by ring
     ... ≥ 0               : by apply pow_two_nonneg },
  linarith,
end

-- 4ª demostración
example : 2*a*b ≤ a^2 + b^2 :=
-- by library_search
two_mul_le_add_sq a b


-- BOTH:
import data.real.basic

/- TEXT:
.. _sequences_and_convergence:

Sequences and Convergence
-------------------------

We now have enough skills at our disposal to do some real mathematics.
In Lean, we can represent a sequence :math:`s_0, s_1, s_2, \ldots` of
real numbers as a function ``s : ℕ → ℝ``.
Such a sequence is said to *converge* to a number :math:`a` if for every
:math:`\varepsilon > 0` there is a point beyond which the sequence
remains within :math:`\varepsilon` of :math:`a`,
that is, there is a number :math:`N` such that for every
:math:`n \ge N`, :math:`| s_n - a | < \varepsilon`.
In Lean, we can render this as follows:
BOTH: -/
-- QUOTE:
def converges_to (s : ℕ → ℝ) (a : ℝ) :=
∀ ε > 0, ∃ N, ∀ n ≥ N, abs (s n - a) < ε
-- QUOTE.

/- TEXT:
The notation ``∀ ε > 0, ...`` is a convenient abbreviation
for ``∀ ε, ε > 0 → ...``, and, similarly,
``∀ n ≥ N, ...`` abbreviates ``∀ n, n ≥ N →  ...``.
And remember that ``ε > 0``, in turn, is defined as ``0 < ε``,
and ``n ≥ N`` is defined as ``N ≤ n``.

.. index:: extentionality, ext, tactics ; ext

In this section, we'll establish some properties of convergence.
But first, we will discuss three tactics for working with equality
that will prove useful.
The first, the ``ext`` tactic,
gives us a way of proving that two functions are equal.
Let :math:`f(x) = x + 1` and :math:`g(x) = 1 + x`
be functions from reals to reals.
Then, of course, :math:`f = g`, because they return the same
value for every :math:`x`.
The ``ext`` tactic enables us to prove an equation between functions
by proving that their values are the same
at all the values of their arguments.
TEXT. -/
-- QUOTE:
example : (λ x y : ℝ, (x + y)^2) = (λ x y : ℝ, x^2 + 2*x*y + y^2) :=
by { ext, ring }
-- QUOTE.

/- TEXT:
.. index:: congr, tactics ; congr

We'll see later that ``ext`` is actually more general, and also one can
specify the name of the variables that appear.
For instance you can try to replace ``ext`` with ``ext u v`` in the
above proof.
The second tactic, the ``congr`` tactic,
allows us to prove an equation between two expressions
by reconciling the parts that are different:
TEXT. -/
-- QUOTE:
example (a b : ℝ) : abs a = abs (a - b + b) :=
by  { congr, ring }
-- QUOTE.

/- TEXT:
Here the ``congr`` tactic peels off the ``abs`` on each side,
leaving us to prove ``a = a - b + b``.

.. index:: convert, tactics ; convert

Finally, the ``convert`` tactic is used to apply a theorem
to a goal when the conclusion of the theorem doesn't quite match.
For example, suppose we want to prove ``a < a * a`` from ``1 < a``.
A theorem in the library, ``mul_lt_mul_right``,
will let us prove ``1 * a < a * a``.
One possibility is to work backwards and rewrite the goal
so that it has that form.
Instead, the ``convert`` tactic lets us apply the theorem
as it is,
and leaves us with the task of proving the equations that
are needed to make the goal match.
TEXT. -/
-- QUOTE:
example {a : ℝ} (h : 1 < a) : a < a * a :=
begin
  convert (mul_lt_mul_right _).2 h,
  { rw [one_mul] },
  exact lt_trans zero_lt_one h
end
-- QUOTE.

/- TEXT:
This example illustrates another useful trick: when we apply an
expression with an underscore
and Lean can't fill it in for us automatically,
it simply leaves it for us as another goal.

The following shows that any constant sequence :math:`a, a, a, \ldots`
converges.
BOTH: -/
-- QUOTE:
theorem converges_to_const (a : ℝ) : converges_to (λ x : ℕ, a) a :=
begin
  intros ε εpos,
  use 0,
  intros n nge, dsimp,
  rw [sub_self, abs_zero],
  apply εpos
end
-- QUOTE.

/- TEXT:
.. TODO: reference to the simplifier

Lean has a tactic, ``simp``, which can often save you the
trouble of carrying out steps like ``rw [sub_self, abs_zero]``
by hand.
We will tell you more about it soon.

For a more interesting theorem, let's show that if ``s``
converges to ``a`` and ``t`` converges to ``b``, then
``λ n, s n + t n`` converges to ``a + b``.
It is helpful to have a clear pen-and-paper
proof in mind before you start writing a formal one.
Given ``ε`` greater than ``0``,
the idea is to use the hypotheses to obtain an ``Ns``
such that beyond that point, ``s`` is within ``ε / 2``
of ``a``,
and an ``Nt`` such that beyond that point, ``t`` is within
``ε / 2`` of ``b``.
Then, whenever ``n`` is greater than or equal to the
maximum of ``Ns`` and ``Nt``,
the sequence ``λ n, s n + t n`` should be within ``ε``
of ``a + b``.
The following example begins to implement this strategy.
See if you can finish it off.
TEXT. -/
-- QUOTE:
theorem converges_to_add {s t : ℕ → ℝ} {a b : ℝ}
  (cs : converges_to s a) (ct : converges_to t b):
converges_to (λ n, s n + t n) (a + b) :=
begin
  intros ε εpos, dsimp,
  have ε2pos : 0 < ε / 2,
  { linarith },
  cases cs (ε / 2) ε2pos with Ns hs,
  cases ct (ε / 2) ε2pos with Nt ht,
  use max Ns Nt,
  sorry
end
-- QUOTE.

-- SOLUTIONS:
theorem converges_to_addαα {s t : ℕ → ℝ} {a b : ℝ}
  (cs : converges_to s a) (ct : converges_to t b):
converges_to (λ n, s n + t n) (a + b) :=
begin
  intros ε εpos, dsimp,
  have ε2pos : 0 < ε / 2,
  { linarith },
  cases cs (ε / 2) ε2pos with Ns hs,
  cases ct (ε / 2) ε2pos with Nt ht,
  use max Ns Nt,
  intros n hn,
  have ngeNs : n ≥ Ns := le_of_max_le_left hn,
  have ngeNt : n ≥ Nt := le_of_max_le_right hn,
  calc
    |s n + t n - (a + b)| = | s n - a + (t n - b) | :
      by { congr, ring }
    ... ≤  | s n - a | + | (t n - b) | :
      abs_add _ _
    ... < ε / 2 + ε / 2 : add_lt_add (hs n ngeNs) (ht n ngeNt)
    ... = ε : by norm_num
end

/- TEXT:
As hints, you can use ``le_of_max_le_left`` and ``le_of_max_le_right``,
and ``norm_num`` can prove ``ε / 2 + ε / 2 = ε``.
Also, it is helpful to use the ``congr`` tactic to
show that ``abs (s n + t n - (a + b))`` is equal to
``abs ((s n - a) + (t n - b)),``
since then you can use the triangle inequality.
Notice that we marked all the variables ``s``, ``t``, ``a``, and ``b``
implicit because they can be inferred from the hypotheses.

Proving the same theorem with multiplication in place
of addition is tricky.
We will get there by proving some auxiliary statements first.
See if you can also finish off the next proof,
which shows that if ``s`` converges to ``a``,
then ``λ n, c * s n`` converges to ``c * a``.
It is helpful to split into cases depending on whether ``c``
is equal to zero or not.
We have taken care of the zero case,
and we have left you to prove the result with
the extra assumption that ``c`` is nonzero.
TEXT. -/
-- QUOTE:
theorem converges_to_mul_const {s : ℕ → ℝ} {a : ℝ}
    (c : ℝ) (cs : converges_to s a) :
  converges_to (λ n, c * s n) (c * a) :=
begin
  by_cases h : c = 0,
  { convert converges_to_const 0,
    { ext, rw [h, zero_mul] },
    rw [h, zero_mul] },
  have acpos : 0 < abs c,
    from abs_pos.mpr h,
  sorry
end
-- QUOTE.

-- SOLUTIONS:
theorem converges_to_mul_constαα {s : ℕ → ℝ} {a : ℝ}
    (c : ℝ) (cs : converges_to s a) :
  converges_to (λ n, c * s n) (c * a) :=
begin
  by_cases h : c = 0,
  { convert converges_to_const 0,
    { ext, rw [h, zero_mul] },
    rw [h, zero_mul] },
  have acpos : 0 < abs c,
    from abs_pos.mpr h,
  intros ε εpos, dsimp,
  have εcpos : 0 < ε / abs c,
  { apply div_pos εpos acpos },
  cases cs (ε / abs c) εcpos with Ns hs,
  use Ns,
  intros n ngt,
  calc
    |c * s n - c * a| = |c| * |s n - a| :
      by { rw [←abs_mul, mul_sub] }
    ... < |c| * (ε / |c|) :
      mul_lt_mul_of_pos_left (hs n ngt) acpos
    ... = ε : mul_div_cancel' _ (ne_of_lt acpos).symm
end

/- TEXT:
The next theorem is also independently interesting:
it shows that a convergent sequence is eventually bounded
in absolute value.
We have started you off; see if you can finish it.
TEXT. -/
-- QUOTE:
theorem exists_abs_le_of_converges_to {s : ℕ → ℝ} {a : ℝ}
    (cs : converges_to s a) :
  ∃ N b, ∀ n, N ≤ n → abs (s n) < b :=
begin
  cases cs 1 zero_lt_one with N h,
  use [N, abs a + 1],
  sorry
end
-- QUOTE.

-- SOLUTIONS:
theorem exists_abs_le_of_converges_toαα {s : ℕ → ℝ} {a : ℝ}
    (cs : converges_to s a) :
  ∃ N b, ∀ n, N ≤ n → abs (s n) < b :=
begin
  cases cs 1 zero_lt_one with N h,
  use [N, abs a + 1],
  intros n ngt,
  calc
    |s n| = |s n - a + a| : by { congr, abel }
    ... ≤ |s n - a| + |a| : abs_add _ _
    ... < |a| + 1 : by linarith [h n ngt]
end

/- TEXT:
In fact, the theorem could be strengthened to assert
that there is a bound ``b`` that holds for all values of ``n``.
But this version is strong enough for our purposes,
and we will see at the end of this section that it
holds more generally.

The next lemma is auxiliary: we prove that if
``s`` converges to ``a`` and ``t`` converges to ``0``,
then ``λ n, s n * t n`` converges to ``0``.
To do so, we use the previous theorem to find a ``B``
that bounds ``s`` beyond some point ``N₀``.
See if you can understand the strategy we have outlined
and finish the proof.
TEXT. -/
-- QUOTE:
lemma aux {s t : ℕ → ℝ} {a : ℝ}
    (cs : converges_to s a) (ct : converges_to t 0) :
  converges_to (λ n, s n * t n) 0 :=
begin
  intros ε εpos, dsimp,
  rcases exists_abs_le_of_converges_to cs with ⟨N₀, B, h₀⟩,
  have Bpos : 0 < B,
    from lt_of_le_of_lt (abs_nonneg _) (h₀ N₀ (le_refl _)),
  have pos₀ : ε / B > 0,
    from div_pos εpos Bpos,
  cases ct _ pos₀ with N₁ h₁,
  sorry
end
-- QUOTE.

-- SOLUTIONS:
lemma auxαα {s t : ℕ → ℝ} {a : ℝ}
    (cs : converges_to s a) (ct : converges_to t 0) :
  converges_to (λ n, s n * t n) 0 :=
begin
  intros ε εpos, dsimp,
  rcases exists_abs_le_of_converges_to cs with ⟨N₀, B, h₀⟩,
  have Bpos : 0 < B,
    from lt_of_le_of_lt (abs_nonneg _) (h₀ N₀ (le_refl _)),
  have pos₀ : ε / B > 0,
    from div_pos εpos Bpos,
  cases ct _ pos₀ with N₁ h₁,
  use max N₀ N₁,
  intros n ngt,
  have ngeN₀ : n ≥ N₀ := le_of_max_le_left ngt,
  have ngeN₁ : n ≥ N₁ := le_of_max_le_right ngt,
  calc
    |s n * t n - 0| = |s n| * |t n - 0| :
      by rw [sub_zero, abs_mul, sub_zero]
    ... < B * (ε / B) :
      mul_lt_mul'' (h₀ n ngeN₀) (h₁ n ngeN₁) (abs_nonneg _) (abs_nonneg _)
    ... = ε : mul_div_cancel' _ (ne_of_lt Bpos).symm
end

/- TEXT:
If you have made it this far, congratulations!
We are now within striking distance of our theorem.
The following proof finishes it off.
TEXT. -/
-- QUOTE:
theorem converges_to_mul {s t : ℕ → ℝ} {a b : ℝ}
    (cs : converges_to s a) (ct : converges_to t b):
  converges_to (λ n, s n * t n) (a * b) :=
begin
  have h₁ : converges_to (λ n, s n * (t n - b)) 0,
  { apply aux cs,
    convert converges_to_add ct (converges_to_const (-b)),
    ring },
  convert (converges_to_add h₁ (converges_to_mul_const b cs)),
  { ext, ring },
  ring
end
-- QUOTE.

-- SOLUTIONS:
theorem converges_to_muLαα {s t : ℕ → ℝ} {a b : ℝ}
    (cs : converges_to s a) (ct : converges_to t b):
  converges_to (λ n, s n * t n) (a * b) :=
begin
  have h₁ : converges_to (λ n, s n * (t n - b)) 0,
  { apply aux cs,
    convert converges_to_add ct (converges_to_const (-b)),
    ring },
  convert (converges_to_add h₁ (converges_to_mul_const b cs)),
  { ext, ring },
  ring
end

/- TEXT:
For another challenging exercise,
try filling out the following sketch of a proof that limits
are unique.
(If you are feeling bold,
you can delete the proof sketch and try proving it from scratch.)
TEXT. -/
-- QUOTE:
theorem converges_to_unique {s : ℕ → ℝ} {a b : ℝ}
    (sa : converges_to s a) (sb : converges_to s b) :
  a = b :=
begin
  by_contradiction abne,
  have : abs (a - b) > 0,
  { sorry },
  let ε := abs (a - b) / 2,
  have εpos : ε > 0,
  { change abs (a - b) / 2 > 0, linarith },
  cases sa ε εpos with Na hNa,
  cases sb ε εpos with Nb hNb,
  let N := max Na Nb,
  have absa : abs (s N - a) < ε,
  { sorry },
  have absb : abs (s N - b) < ε,
  { sorry },
  have : abs (a - b) < abs (a - b),
  { sorry },
  exact lt_irrefl _ this
end
-- QUOTE.

-- SOLUTIONS:
theorem converges_to_uniqueαα {s : ℕ → ℝ} {a b : ℝ}
    (sa : converges_to s a) (sb : converges_to s b) :
  a = b :=
begin
  by_contradiction abne,
  have : abs (a - b) > 0,
  { apply lt_of_le_of_ne,
    { apply abs_nonneg },
      intro h'',
      apply abne,
      apply eq_of_abs_sub_eq_zero h''.symm, },
  let ε := abs (a - b) / 2,
  have εpos : ε > 0,
  { change abs (a - b) / 2 > 0, linarith },
  cases sa ε εpos with Na hNa,
  cases sb ε εpos with Nb hNb,
  let N := max Na Nb,
  have absa : abs (s N - a) < ε,
  { apply hNa, apply le_max_left },
  have absb : abs (s N - b) < ε,
  { apply hNb, apply le_max_right },
  have : abs (a - b) < abs (a - b),
    calc
      abs (a - b) = abs (- (s N - a) + (s N - b)) :
        by { congr, ring }
      ... ≤ abs (- (s N - a)) + abs (s N - b) :
        abs_add _ _
      ... = abs (s N - a) + abs (s N - b) :
        by rw [abs_neg]
      ... < ε + ε : add_lt_add absa absb
      ... = abs (a - b) : by norm_num,
  exact lt_irrefl _ this
end

/- TEXT:
We close the section with the observation that our proofs can be generalized.
For example, the only properties that we have used of the
natural numbers is that their structure carries a partial order
with ``min`` and ``max``.
You can check that everything still works if you replace ``ℕ``
everywhere by any linear order ``α``:
TEXT. -/
section
-- QUOTE:
variables {α : Type*} [linear_order α]

def converges_to' (s : α → ℝ) (a : ℝ) :=
∀ ε > 0, ∃ N, ∀ n ≥ N, abs (s n - a) < ε
-- QUOTE.

end

/- TEXT:
.. TODO: reference to later chapter

In a later chapter, we will see that mathlib has mechanisms
for dealing with convergence in vastly more general terms,
not only abstracting away particular features of the domain
and codomain,
but also abstracting over different types of convergence.
TEXT. -/


import PnP2023.Labs.Lab02.SelectionSort

/-!
# Selection Sort (continued)

In this lab you will show that `selectionSort` defined in the previous lab is correct, i.e. the result is sorted and preserves membership.

__Note:__ If the list has duplicates the sorted list will erase these. For example, `[1, 1, 2, 3]` will be sorted to `[1, 2, 3]`. For the purposes of this exercise, this is fine.

We first define recursively when a list is sorted. -/

/-- A direct definition of an element being below all members of a list. -/
def List.le_all (a : ℕ) (l : List ℕ) : Prop := ∀ b: ℕ, b ∈ l → a ≤ b

/-- Whether a given list is sorted. -/
def List.sorted: List ℕ →  Prop
| [] => True
| h :: t => (t.le_all h) ∧ (t.sorted)

-- For your convenience, here are some definitions/theorems that may be useful in the two labs. Clicking on them takes you to the source, where you may find other useful results.
#check List.remove -- {α : Type u_1} → [inst : DecidableEq α] → α → List α → List α
#check List.length_cons -- ∀ {α : Type u_1} (a : α) (as : List α), List.length (a :: as) = Nat.succ (List.length as)
#check List.mem_cons -- ∀ {α : Type u_1} {a b : α} {l : List α}, a ∈ b :: l ↔ a = b ∨ a ∈ l
#check List.mem_of_mem_remove -- ∀ {α : Type u_1} [inst : DecidableEq α] {a b : α} {as : List α}, b ∈ List.remove a as → b ∈ as
#check List.remove_eq_of_not_mem -- ∀ {α : Type u_1} [inst : DecidableEq α] {a : α} {as : List α}, ¬a ∈ as → List.remove a as = as
#check List.mem_remove_iff -- ∀ {α : Type u_1} [inst : DecidableEq α] {a b : α} {as : List α}, b ∈ List.remove a as ↔ b ∈ as ∧ b ≠ a

/-!
- Problem 1: show that members of the given list are members of the sorted list(remove sorry).
-/

/-- Members of a list are members of the given sorted list -/
theorem selectionSort_mem_of_mem {a : ℕ} {l : List ℕ} (hyp : a ∈ l) : a ∈ selectionSort l := by
  sorry

/-!
- Problem 2: show that members of the sorted list are members of the given list(remove sorry).
-/

/-- Members of the sorted list are members of the given list -/
theorem selectionSort_mem_mem {a : ℕ} {l : List ℕ} (hyp : a ∈ selectionSort l) : a ∈ l := by
  sorry

theorem selectionSort_mem (l : List ℕ) (a : ℕ) : a ∈ l ↔ a ∈ selectionSort l := by
  apply Iff.intro
  · apply selectionSort_mem_of_mem
  · apply selectionSort_mem_mem

/-!
- Problems 3: Prove that the results of `selectionSort` is sorted (remove the sorry).
-/

/-- The result of `selectionSort` is sorted -/
theorem selectionSort_sorted (l : List ℕ) : (selectionSort l).sorted :=
    by sorry


/-

# Advanced proposition world.

## Level 2: the `cases` tactic.

If `P ∧ Q` is in the goal, then we can make progress with `split`.
But what if `P ∧ Q` is a hypothesis? In this case, the `cases` tactic will enable
us to extract proofs of `P` and `Q` from this hypothesis.

The lemma below asks us to prove `P ∧ Q → Q ∧ P`, that is,
symmetry of the "and" relation. The obvious first move is

`intro h,`

because the goal is an implication and this tactic is guaranteed
to make progress. Now `h : P ∧ Q` is a hypothesis, and

`cases h with p q,`

will change `h`, the proof of `P ∧ Q`, into two proofs `p : P`
and `q : Q`. From there, `split` and `exact` will get you home.
-/

/- Lemma
If $P$ and $Q$ are true/false statements, then $P\land Q\implies Q\land P$.
-/
lemma and_symm (P Q : Prop) : P ∧ Q → Q ∧ P :=
begin
  intro h,
  cases h with p q,
  split,
  exact q,
  exact p,

end

/- Tactic : cases

## Summary:

`cases` is a tactic which works on hypotheses.
If `h : P ∧ Q` or `h : P ↔ Q` is a hypothesis then `cases h with h1 h2` will remove `h`
from the list of hypotheses and replace it with the "ingredients" of `h`,
i.e. `h1 : P` and `h2 : Q`, or `h1 : P → Q` and `h2 : Q → P`. Also
works with `h : P ∨ Q` and `n : mynat`.

## Details

How does one prove `P ∧ Q`? The way to do it is to prove `P` and to
prove `Q`. There are hence two ingredients which go into a proof of
`P ∧ Q`, and the `cases` tactic extracts them.

More precisely, if the local context contains
```
h : P ∧ Q`
```

then after the tactic `cases h with p q,` the local context will
change to
```
p : P,
q : Q
```
and `h` will disappear.

Similarly `h : P ↔ Q` is proved by proving `P → Q` and `Q → P`,
and `cases h with hpq hqp` will delete our assumption `h` and
replace it with
```
hpq : P → Q,
hqp : Q → P
```

Be warned though -- `rw h` works with `h : P ↔ Q` (`rw` works with
`=` and `↔`), whereas you cannot rewrite with an implication.

`cases` also works with hypotheses of the form `P ∨ Q` and even
with `n : mynat`. Here the situation is different however.
To prove `P ∨ Q` you need to give either a proof of `P` *or* a proof
of `Q`, so if `h : P ∨ Q` then `cases h with p q` will change one goal
into two, one with `p : P` and the other with `q : Q`. Similarly, each
natural is either `0` or `succ(d)` for `d` another natural, so if
`n : mynat` then `cases n with d` also turns one goal into two,
one with `n = 0` and the other with `d : mynat` and `n = succ(d)`.
-/


import Arithmetic.definition

open Nat_

theorem add_zero (m : Nat_) : m + zero = m := rfl

theorem add_succ (m n : Nat_) : m + succ n = succ (m + n) := rfl

theorem zero_add (m : Nat_) : zero + m = m := by
  induction m with
  | zero => rfl
  | succ m hyp =>
    rw [add_succ]
    rw [hyp]

theorem succ_add (m n : Nat_) : succ m + n = succ (m + n) := by
  induction n with
  | zero =>
    rfl
  | succ n hyp =>
    rw [add_succ]
    rw [hyp]
    rw [←add_succ]

@[simp]
theorem add_comm (m n : Nat_) : m + n = n + m := by
  induction n with
  | zero =>
    rw [zero_add]
    rfl
  | succ n hyp =>
    rw [succ_add]
    rw [add_succ]
    rw [hyp]

theorem add_assoc (l m n : Nat_) : (l + m) + n = l + (m + n) := by
  induction n with
  | zero =>
    repeat (rw [add_zero])
  | succ n hyp =>
    repeat (rw [add_succ])
    rw [hyp]

theorem mul_succ (m n : Nat_) : m * (succ n) = m + m * n := rfl

theorem mul_zero (n : Nat_) : n * zero = zero := rfl

theorem zero_mul (n : Nat_) : zero * n = zero := by
  induction n with
  | zero => rfl
  | succ n hyp =>
    rw [mul_succ]
    rw [hyp]
    rfl

theorem succ_mul (m n : Nat_) : (succ m) * n = n + m * n := by
  induction n with
  | zero => rfl
  | succ n hyp =>
    rw [mul_succ]
    rw [hyp]
    rw [mul_succ]
    rw [succ_add]
    rw [succ_add]
    rw [←add_assoc]
    rw [←add_assoc]
    simp

theorem mul_comm (m n : Nat_) : m * n = n * m := by
  induction m with
  | zero =>
    rw [zero_mul]
    rfl
  | succ m hyp =>
    rw [mul_succ]
    rw [succ_mul]
    rw [hyp]

theorem distr_r (l m n : Nat_) : (l + m) * n = l * n + m * n := by
  induction n with
  | zero =>
    repeat (rw [mul_zero])
    rfl
  | succ n hyp =>
    repeat (rw [mul_succ])
    rw [hyp]
    rw [add_assoc l (l*n) _]
    rw [add_comm (l*n) (m + m * n)]
    rw [add_assoc m (m*n) _]
    rw [add_comm (m * n) (l * n)]
    rw [←add_assoc l m _]

theorem distr_l (l m n : Nat_) : l * (m + n) = l * m + l * n := by
  rw [mul_comm]
  rw [distr_r]
  rw [mul_comm]
  rw [mul_comm n]

theorem mul_assoc (l m n : Nat_) : (l * m) * n = l * (m * n) := by
  induction n with
  | zero =>
    repeat (rw [mul_zero])
  | succ n hyp =>
    rw [mul_succ m]
    rw [mul_succ]
    rw [distr_l]
    rw [hyp]


import game.order.level05
import data.real.basic
open real

namespace xena -- hide

/-
# Chapter 2 : Order

## Level 6

An interesting result to prove.
-/

-- You will need to use lemma
--mul_le_mul (hac : a ≤ c) (hbd : b ≤ d) (nn_b : 0 ≤ b) (nn_c : 0 ≤ c) :
--a * b ≤ c * d

theorem pow_nonneg {a : ℝ } (H : 0 ≤ a) : ∀ (n : ℕ), 0 ≤ a ^ n
| 0     := zero_le_one
| (n+1) := mul_nonneg H (pow_nonneg _)

/- Lemma
For any two non-negative real numbers $a$ and $b$, we have that
$$a \le b \iff a^2 \le b^2 $$.
-/
theorem le_iff_sq_le (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b): a ≤ b ↔ a^2 ≤ b^2:=
begin
    split,
    intro h,
    have h1 : a^2 ≤ a * b,
    have h11 : a ≤ a,
    linarith,
    --hint next line
    have h2 := mul_le_mul h11 h ha ha,
    have h3 : a * a = a ^ 2,
    ring,
    rw h3 at h2,
    exact h2,
    have h5 : a * b ≤ b ^ 2,
    have h2 : b ≤ b,
    linarith,
    have h4 := mul_le_mul h2 h ha hb,
    rw mul_comm at h4,
    have h6 : b * b = b ^ 2,
    ring,
    rw h6 at h4,
    exact h4,

    exact le_trans h1 h5,
    intro j,

    have h1 : 0 ≤ a ^ 2, exact pow_nonneg ha 2,
    have h2 : 0 ≤ b ^ 2, exact pow_nonneg hb 2,
    have h3 := (sqrt_le h1 h2).2 j,
    have h4 := sqrt_sqr ha,
    have h5 := sqrt_sqr hb,
    rw h4 at h3,
    rw h5 at h3,
    exact h3,
end

end xena -- hide

/-
split,
    intro h,
    have h1 : a^2 ≤ a * b,
        have h11 : a ≤ a, linarith,
        have h12 := mul_le_mul h11 h ha ha,
        have h13 : a * a = a^2, ring,
        rw h13 at h12, exact h12,
    have h2 : a * b ≤ b^2,
        have h21 : b ≤ b, linarith,
        have h22 := mul_le_mul h21 h ha hb,
        rw mul_comm at h22,
        have h23 : b * b = b^2, ring,
        rw h23 at h22, exact h22,
    exact le_trans h1 h2,
    intro h,
    have ha2 : 0 ≤ a^2, exact pow_nonneg ha 2,
    have hb2 : 0 ≤ b^2, exact pow_nonneg hb 2,
    have h1 := (sqrt_le ha2 hb2).mpr h,
    have h2a := sqrt_sqr ha,
    have h2b := sqrt_sqr hb,
    rw h2a at h1, rw h2b at h1, exact h1, done
-/


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que los productos de los números naturales por
-- números pares son pares.
-- ---------------------------------------------------------------------

import data.nat.parity
import tactic

open nat

-- 1ª demostración
example : ∀ m n : ℕ, even n → even (m * n) :=
begin
  intros m n hn,
  unfold even at *,
  cases hn with k hk,
  use m * k,
  calc m * n
       = m * (k + k)   : congr_arg (has_mul.mul m) hk
   ... = m * k + m * k : mul_add m k k
end

-- 2ª demostración
example : ∀ m n : ℕ, even n → even (m * n) :=
begin
  intros m n hn,
  cases hn with k hk,
  use m * k,
  calc m * n
       = m * (k + k)   : congr_arg (has_mul.mul m) hk
   ... = m * k + m * k : mul_add m k k
end

-- 3ª demostración
example : ∀ m n : ℕ, even n → even (m * n) :=
begin
  rintros m n ⟨k, hk⟩,
  use m * k,
  calc m * n
       = m * (k + k)   : congr_arg (has_mul.mul m) hk
   ... = m * k + m * k : mul_add m k k
end

-- 4ª demostración
example : ∀ m n : ℕ, even n → even (m * n) :=
begin
  rintros m n ⟨k, hk⟩,
  use m * k,
  rw hk,
  exact mul_add m k k,
end

-- 5ª demostración
example : ∀ m n : ℕ, even n → even (m * n) :=
begin
  rintros m n ⟨k, hk⟩,
  use m * k,
  rw [hk, mul_add]
end

-- 6ª demostración
example : ∀ m n : ℕ, even n → even (m * n) :=
begin
  rintros m n ⟨k, hk⟩,
  exact ⟨m * k, by rw [hk, mul_add]⟩
end

-- 7ª demostración
example : ∀ m n : ℕ, even n → even (m * n) :=
λ m n ⟨k, hk⟩, ⟨m * k, by rw [hk, mul_add]⟩

-- 8ª demostración
example : ∀ m n : ℕ, even n → even (m * n) :=
  assume m n ⟨k, (hk : n = k + k)⟩,
  have hmn : m * n = m * k + m * k,
    by rw [hk, mul_add],
  show ∃ l, m * n = l + l,
    from ⟨_, hmn⟩

-- Comentarios:
-- 1. Al poner el curso en la línea 1 sobre data.nat.parity y pulsar M-.
--    se abre la teoría correspondiente.
-- 2. Se activa la ventana de objetivos (*Lean Goal*) pulsando C-c C-g
-- 3. Al mover el cursor sobre las pruebas se actualiza la ventana de
--    objetivos.


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


open list

/-
#1. You are to implement a function
that takes a "predicate" function and a
list and that returns a new list: namely
a list that contains all and only those
elements of the first list for which the
given predicate function returns true.

We start by giving you two predicate
functions. Each takes an argument, n,
of type ℕ. The first returns true if
and only if n < 5. The second returns
true if and only if n is even.
-/

/-
Here's a "predicate" function that
takes a natural number as an argument
and returns true if the number is less
than 5 otherwise it returns false.
-/
def lt_5 (n : ℕ) : bool :=
    n < 5

-- example cases
#eval lt_5 2
#eval lt_5 6

/-
Here's another predicate function, one
that returns true if a given nat is even,
and false otherwise. Note that we have
defined this function recursively. Zero
is even, one is not, and (2 + n') is if
and only if n' is.

You can think of a predicate function
as a function that answers the question,
does some value (here a nat) have some
property? The properties in these two
cases are (1) the property of being less
than 5, and (2) the property of being
even.
-/
def evenb : ℕ → bool
| 0 := tt
| 1 := ff
| (n' + 2) := evenb n'

#eval evenb 0
#eval evenb 3
#eval evenb 4
#eval evenb 5

/-
We call the function you are going to
implement a "filter" function, because
it takes a list and returns a "filtered"
version of the list. Call you function
"mfilter".

If you filter an empty list you always
get an empty list as a result. If you
filter a non-empty list, l = (cons h t),
the returned list has h at its head if
and only if the predicate function applied
to h returns true, otherwise the returned
value is just the filtered version of t.

A. [15 points]

Your  task is to complete an incomplete
version of the definition of the mfilter
function. We use a Lean construct new to
you: the if ... then ... else. It works
as you would expect from your work with
other programming languages. Replace the
underscores to complete the definition.
-/

def mfilter : (ℕ → bool) → list ℕ → list ℕ
| p [] := []
| p (cons h t) :=
    if _
    then (cons _ _)
    else _

/-
Here's the definition of a simple list.
-/

def a_list := [0,1,2,3,4,5,6,7,8,9]

/-
B. [10 points]

Replace the underscores in the first two
eval commands below as follows. Replace
the first one with an expression in which
mfilter is used to compute a new list that
contains the numbers in a_list that are
less than 5. Replace the second one with
an expression in which mfilter is used to
compute a list containing even elements of
a_list. You may use the predicate functions
we defined above.

Replace the third underscore with a similar
expression but where you use a λ expression
to specify a predicate function that takes
a nat, n, and returns tt if n is equal to
three and false otherwise. Hint: n=3 is
an expression that will return the desired
bool.
-/

#eval _
#eval _
#eval _

/-
#2.

Here's a function that takes a function, f,
from ℕ to ℕ, and a value, n, of type ℕ, and
that returns the value that is obtained by
simply applying f to n.
-/

def f_apply : (ℕ → ℕ) → ℕ → ℕ
| f n := (f n)

-- examples of its use
#eval f_apply nat.succ 3
#eval f_apply (λ n : ℕ, n * n) 3

/-
A. [10 points]

Write a function, f_apply_2, that takes a
function, f, from ℕ to ℕ, and a value, n,
of type ℕ, and that returns (read this
carefully) the value obtained by applying
f twice to n: the result of applying f to
the result of applying f to n. For example,
if f is the function that squares its nat
argument, then (f_apply_2 f 3) returns 81,
as f applied to 3 is 9 and f applied to 9
is 81.
-/

-- Your answer here

def ff_apply : (ℕ → ℕ) → ℕ → ℕ
| f n := _

/-
B. [10 points]

Write a function f_apply_k that takes (1)
a function, f, of type ℕ to ℕ, (2) a value,
n, of type ℕ, and (3) a value, k of type ℕ,
and that returns the result of applying f
to n k times.

Note that f_apply applies f to n once and
ff_apply applies f to n twice. Your job is
to write a function that is general in the
sense that you specify by a parameter, k,
how many times to apply f.

Hint 1: Use recursion. Note: The result of
applying any function, f, to n, zero times
is just n.
-/

-- Answer here

/-
Use #eval to evaluate an expression in
which the squaring function, expressed
as a λ expression, is applied to 3 two
times. You should be able to confirm that
you get the same answer given by using
the ff_apply function in the example above.
-/

-- Answer here

/-
C. [Extra Credit]

Write a function f_apply_k_fun that takes (1)
a function, f, of type ℕ to ℕ, (2) a value,
k of type ℕ, and that returns a function that,
when applied to a natural number, n, returns
the result of applying f to n k times.
-/

/-
#3: [15 points]

Write a function, mcompose, that
takes two functions, g and f (in that
order), each of type ℕ → ℕ, and that
returns *a function* that, when applied
to an argument, n, of type ℕ, returns
the result of applying g to the result
of applying f to n.
-/

-- Answer here

/-
#4. Higher-Order Functions

4A. [10 points] Provide an implementatation of
a function, map_pred that takes as its arguments
(1) a predicate function of type ℕ → bool, (2) a
list of natural numbers (of type "list nat"),
and that returns a list in which each ℕ value,
n,in the argument list is replaced by true (tt)
if the predicate returns true for a, otherwise
false (ff).

For example, if the predicate function returns
true if and only if its argument is zero, then
applying map to this function and to the list
[0,1,2,0,1,0] must return [tt,ff,ff,tt,ff,tt].

Test your code by using #eval or #reduce to evaluate
an expression in which map_pred is applied to
such an "is_zero" predicate function and to the
list 0,1,2,0,1,0]. Express the predicate function
as a lambda abstraction within the #eval command.

NOTE: You will have to use list.nil and list.cons
to refer to the nil and cons constructors of the
library-provided list data type, as you already
have definitions for list and cons in the current
namespace.
-/

-- Answer here

/-
4B. [10 points] Implement a function, reduce_or,
that takes as its argument a list of Boolean values
and that reduces the list to a single Boolean value:
tt if there is at least one true value in the list,
otherwise ff. Note: the Lean libraries provide the
function "bor" to compute "b1 or b2", where b1 and
b2 are Booleans. We recommend that you include
tests of your solution.
-/

-- example
#reduce bor tt tt

-- Answer here

/-
4C. [10points] Implement a function, reduce_and,
that takes as its argument a list of Boolean values
and that reduces the list to a single Boolean value:
tt if every value in the list is true, otherwise ff.
-/

-- Note: band implements the Boolean "and" function
#reduce band tt tt

-- Answer here

/-
4D. [10 points] Define a function, all_zero, that
takes a list of nat values and returns true if and
only if they are all zero. Express your answer using
map and reduce functions that you have previously
defined above. Again we recommend that you test your
solution.
-/

-- Answer here (replace the _'s as needed)

def all_zero : list nat → bool
| [] := _
| _ := _

/-
Some tests
-/
#reduce all_zero []
#reduce all_zero [0,0,0,0]
#reduce all_zero [1,0,0,0]
#reduce all_zero [0,1,0,0]
#reduce all_zero [1,0,0,1]


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

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

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

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

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

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

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

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

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

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

#check sub_self

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

-- Important remark:
-- Why this task was not automated?
-- Example:
-- `search solution in graph of states` using set_of_lemmas
--   [sub_self as rewrite_rule, mul_sub as rewrite_rule, -default_addition_set, ...]
-- `-`lemma_name means not using this lemma
-- More general:
-- Metarules₁ = [action ∈ {sub_self, mul_sub} ∪ default_boolean]
-- search_in_lib using Metarules₁
--
-- * Also we can write proof about Metarules like for ordinary theorems

set_option trace.simplify.rewrite true

example (x : ℤ) : x * 0 = 0 :=
calc
    x * 0 = 0 : by simp
    -- [mul_zero]: x * 0 ==> 0
    -- [eq_self_iff_true]: 0 = 0 ==> true


/-
Key differences: Predicate vs propositional logic.

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

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

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

Here's an example of a proposition, in predicate
logic, that illustrates all three points. It
asserts, in precise mathematical terms, that
every natural number is equal to itself. Even
in this simple example, we see that predicate
logic includes (1) quantifiers, (2) variables
that can range over domains other than bool,
and (3) predicates. Here eq is a 2-argument
predicate that, when applied to two values,
yields the proposition that the first one
equals the second. THe more common notation
for eq a b is a = b.
-/
def every_nat_eq_itself := ∀ (n : ℕ), eq n n

-- (_ = _) is an infix notation for (eq _ _)
def every_nat_eq_itself' := ∀ (n : ℕ), n = n

/-
This example illustrates all three points. First,
the logical variable, n, ranges over all values of
type nat. Second, the proposition has a universal
quantifier. Third, in the context of the "binding"
of n to some arbitrary but specific natural number
by the ∀, the overall proposition then asserts that
the proposition n = n is true, using a two-argument
predicate, eq, applied to n and n as arguments, to
state this proposition.
-/

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

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

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

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

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

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

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

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

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

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

/-
One of the last two propositions is valid.
Which one?

In predicate logic, we can no longer evaluate
the validity of a proposition using truth
tables. What we need will instead is a proof.

A proof is an object that we will accept as
compelling evidence for the truth of a given
proposition.

It will also be important for you to learn how
to express proofs in both natural language and
formally, in terms of the "inference rules" of
what we call "natural deduction."

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

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

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

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

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

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

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

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

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

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


/-
As discussed in the last unit, a
predicate is a proposition with
one more more arguments.

In the last unit, we studied
predicates with one argument. We
saw that such a predicate can be
seen as specifying a *property*
of values of its argument type.
Examples included the properties
of being even, being equal to 0,
being a perfect square, or of (a
person) being from Cville.

We now look at predicates with
multiple arguments. We emphasize
the idea that such predicates can
be used to specify properties of
*tuples* (e.g., pairs) of values.
We refer to properties involving
tuples as relations.

As an example we can consider
equality as a relation between
two values. A relation involving
two values is said to be a binary
relation. We could represent the
"equals" relation as a predicate
with two arguments, n and m, and
rules that provide proofs of the
proposition "equals m n" if and
only if m = n. We can represent
this predicate as a function
that now takes two arguments and
returns Prop.
-/

def areEqual : ℕ → ℕ → Prop :=
    λ n m, n = m

#check areEqual 0 0
#reduce areEqual 0 0

lemma zeqz : areEqual 0 0 := eq.refl 0

#check areEqual 2 3
#reduce areEqual 2 3

/-
Be sure you're comfortable with
these ideas. We define areEqual
as a function that takes n and m as
arguments (of type ℕ) and that then
returns the *proposition*, n = m.
Be clear that this function does
NOT return a proof. It returns a
proposition that might or might
not be true!

The single predicate, which most
mathematicians would write as
areEqual(n,m), with two arguments,
defines an infinity of propositions,
each one about one specific pair of
natural numbers.

Some of these propositions have
proofs and thus are true. Others
do not, and are not true. Such a
predicate thus "picks out" a subset
of all possible pairs, namely those
for which its proposition is true.
These pairs include (0, 0) but not
(2, 3).

Such a set of pairs is what we think
of as the relation that the predicate
specifies. In particular, areEqual
specifies a "binary" relation, the
set of all pairs of natural numbers
that are equal to themselves.

Let's prove that a few elements are
in, and that another one is not in,
the areEqual(n,m) relation.
-/

-- (1, 1) is in the relation
example : areEqual 1 1 :=
    eq.refl 1

-- (2, 2) is in the relation
example : areEqual 2 2 :=
    eq.refl 2

-- but (0, 1) is not in it
example : ¬(areEqual 0 1) :=
    λ zeqo, nat.no_confusion zeqo

/-
EXERCISE [Worked]: Define a predicate
isSquare that expresses the relation
between two natural numbers that the
second natural number is the square
of the first and prove that the pair,
(3, 9), is in the isSquare, relation.
-/

-- Answer

def isSquare: ℕ → ℕ → Prop :=
    λ n m, n * n = m

example : isSquare 3 9 :=
begin
unfold isSquare,
exact rfl,
end

/-
EXERCISE: In lean, the function,
string.length returns the length
of a given string. Specify a new
predicate sHasLenN taking a string
and a natural number as arguments
that asserts that a given string
has the given length.  Write the
function using lambda notation to
make the type of the predicate as
clear as possible.
-/

#eval string.length "Hello"

-- answer here

def sHasLenN (s :string) (n : ℕ) : Prop :=
    s.length = n

example : sHasLenN "Hello" 5 :=
begin
unfold sHasLenN,
exact rfl,
end

def pythagoreanTriple : ℕ → ℕ → ℕ → Prop :=
    λ a b h, a^2 + b^2 = h^2

example : pythagoreanTriple 3 4 5 :=
begin
unfold pythagoreanTriple,
exact rfl,
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
  have key : c + a ≤ c + b, {apply add_le_add_left hab},
  rw add_comm c a at key,
  rw add_comm c b at key,
  exact key
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 rw zero_add b
     ... ≤ 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
  have key : a+0  ≤ a+b, {by apply add_le_add_left hb a},
  rw (add_zero a) at key,
  exact key
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.
-/

-- 0011
example (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
begin
  calc
      0 ≤ b     : by exact hb
    ... ≤ a + b : by apply le_add_of_nonneg_left ha
end

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

-- 0012
example (a b x y : ℝ) (hab : a ≤ b) (hcd : x ≤ y) : a + x ≤ b + y :=
begin
 calc
  a + x ≤ b + x   : by exact add_le_add_right hab x
   ... ≤ b + y : by exact add_le_add_left hcd b
end

/-
In the above examples, we prepared proofs of assumptions of our lemmas beforehand, so
that we could feed them to the lemmas. This is called forward reasonning.
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 reasonning 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 reasonning.

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 reasonning: 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, by
  { 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 reasonning, 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 reasonning -/
-- 0013
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  rw ← sub_nonneg,
  have key : a*c - b*c = (a - b)*c,
  { ring },
  rw key,
  apply mul_nonneg_of_nonpos_of_nonpos,
    {rwa [<-sub_nonpos] at hab,},
    {exact hc}
end

/- Using forward reasonning -/
-- 0014
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  rw ← sub_nonneg,
  have key : a*c - b*c = (a - b)*c, { ring },
  rw key,
  have  h₁ : a - b ≤ 0, by rwa [<- sub_nonpos] at hab,
  exact mul_nonneg_of_nonpos_of_nonpos h₁ hc,
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
end

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

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

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

But this can be rephrased as

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

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

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

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

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

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

Let's practise using `intros`. -/

-- 0016
example (a b : ℝ): 0 ≤ b → a ≤ a + b :=
begin
  intros ha,
  exact le_add_of_nonneg_right ha
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 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
  intro hpq,
  cases hpq with hp hq,
  split,
  exact hq,
  exact hp,
end

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

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

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

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

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

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

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

-- 0019
example (P Q R : Prop) : (P ∧ Q → R) ↔ (P → (Q → R)) :=
begin
  split,
    assume (hpqr : P ∧ Q → R) (hp : P) (hq : Q),
      show R, by {apply hpqr, exact ⟨hp,hq⟩},
    assume (hpqr : P → (Q → R)) (hpq : P ∧ Q),
      show R, by {exact hpqr hpq.1 hpq.2}
end

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

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

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

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

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

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

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

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

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

/-
Final exercise

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

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

  dvd_refl (a : ℕ) : a ∣ a

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

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

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

-- 0022
example (a b : ℕ) : a ∣ b ↔ gcd a b = a :=
begin
  have hg : gcd a b ∣ gcd a b, from dvd_refl (gcd a b),
  rw dvd_gcd_iff at hg,
  cases hg with hg1 hg2,
  split,
    {assume hab : a ∣ b,
      have h2 : a ∣ gcd a b, from dvd_gcd_iff.mpr ⟨dvd_refl a, hab⟩,
      exact dvd_antisymm hg1 h2
    },
    {assume hgab : gcd a b = a,
      rw hgab at hg2,
      exact hg2,
    }
end


import .love05_inductive_predicates_demo

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

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Even and Odd

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

#check even

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

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

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

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

@[simp] lemma odd_1 :
  odd 1 :=
begin
  intro h,
  cases' h
end

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

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

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

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

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

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

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

lemma even_imp_exists_two_times :
  ∀n : ℕ, even n → ∃m, n = 2 * m :=
begin
  intros n hen,
  induction' hen,
  case zero {
    apply exists.intro 0,
    refl },
  case add_two : k hek ih {
    cases' ih with w hk,
    apply exists.intro (w + 1),
    rw hk,
    linarith }
end

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

lemma even_iff_exists_two_times (n : ℕ) :
  even n ↔ ∃m, n = 2 * m :=
begin
  apply iff.intro,
  { apply even_imp_exists_two_times },
  { intro h,
    cases' h,
    simp *,
    apply even_two_times }
end

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

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

def even_rec : nat → bool
| 0       := tt
| (n + 1) := not (even_rec n)

#eval even_rec 0
#eval even_rec 1
#eval even_rec 2
#eval even_rec 3
#eval even_rec 4

/-! ## Question 2: Tennis Games

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

#check score

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

inductive srv_ahead : score → Prop
| vs {m n : ℕ} : m > n → srv_ahead (score.vs m n)
| adv_srv      : srv_ahead score.adv_srv
| game_srv     : srv_ahead score.game_srv

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

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

lemma srv_ahead_adv_srv :
  srv_ahead score.adv_srv :=
srv_ahead.adv_srv

lemma not_srv_ahead_adv_rcv :
  ¬ srv_ahead score.adv_rcv :=
begin
  intro h,
  cases' h
end

lemma srv_ahead_game_srv :
  srv_ahead score.game_srv :=
srv_ahead.game_srv

lemma not_srv_ahead_game_rcv :
  ¬ srv_ahead score.game_rcv :=
begin
  intro h,
  cases' h
end

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

/-! The positive lemmas correspond exactly to the introduction rules of the
definition. By contrast, the negative lemmas have no counterparts in the
definition. -/

/-! ## Question 3: Binary Trees

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

#check is_full_mirror
#check mirror_mirror

lemma mirror_is_full {α : Type} :
  ∀t : btree α, is_full (mirror t) → is_full t :=
begin
  intros t fmt,
  have fmmt : is_full (mirror (mirror t)) :=
    is_full_mirror _ fmt,
  rw mirror_mirror at fmmt,
  assumption
end

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

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

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

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

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

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

end LoVe


import tactic
set_option pp.structure_projections false

theorem binom : ∀ x y : ℕ, (x + y)^2 = x^2 + 2*x*y + y^2 :=
begin
  assume a b,
  ring,
end

set_option pp.structure_projections false
open nat

--multiplication
def mul : ℕ → ℕ → ℕ
| m 0     := 0
| m (succ n) := (mul m n) + m

-- m * 0 = n
-- m * (n + 1) = m * n + m

local notation m * n := mul m n

#reduce mul 5 3

----ring
--semi ring
/--
A semiring is a set together with two binary operators S(+,*) satisfying the following conditions:

1. Additive associativity: For all a,b,c in S, (a+b)+c=a+(b+c),

2. Additive commutativity: For all a,b in S, a+b=b+a, --left and right neutral

3. Multiplicative associativity: For all a,b,c in S, (a*b)*c=a*(b*c),

4. Left and right distributivity: For all a,b,c in S, a*(b+c)=(a*b)+(a*c) and (b+c)*a=(b*a)+(c*a).

--/

--ring with 4. Additive inverse: For every a in S there exists -a in S such that a+(-a)=(-a)+a=0,

/--
The field axioms are generally written in additive and multiplicative pairs.

name	addition	multiplication
associativity	(a+b)+c=a+(b+c)	(ab)c=a(bc)
commutativity	a+b=b+a	ab=ba
distributivity	a(b+c)=ab+ac	(a+b)c=ac+bc
identity	a+0=a=0+a	a·1=a=1·a
inverses	a+(-a)=0=(-a)+a	aa^(-1)=1=a^(-1)a if a!=0
--/

/--
Natural numbers (ℕ)	Semiring
Integers (ℤ)	Ring
Rational numbers (ℚ)	Field
Real numbers (ℝ)	Complete Field
Complex numbers (ℂ)	Algebraically complete Field
--/

def exp : ℕ → ℕ → ℕ
| n zero := 1
| n (succ m) := exp n m * n

--order ≤ , m ≤ n
def le (m n : ℕ) : Prop := ∃ k : ℕ , k + m = n

local notation x ≤ y := le x y
local notation x ≥  y := le x y

theorem le_refll : ∀ x : ℕ , x ≤ x :=
begin
   assume x,
   dsimp[(≤),le],
   existsi 0,
   ring,
end

theorem le_transs : ∀ x y z : ℕ , x ≤ y → y ≤ z → x ≤ z :=
begin
   assume x y z ,
   assume xy yz,
   dsimp[(≤ ),le],
   dsimp[(≤ ),le] at xy,
   dsimp[(≤ ),le] at yz,

   cases xy with k p, -- ≤ is an exist statement
   cases yz with l q,
   existsi (k+l),
   rewrite← q,
   rewrite← p,
   ring,
end

--partial order: a partial order is a relation which is reflexive, transitive and antisymmetric.
--reflexive (∀ x : A, x≤x),
--transitive (∀ x y z : A, x≤y → y≤z → x≤z)
--antisymmetry (∀ x y : ℕ , x ≤ y → y ≤ x → x = y)

-- decidability
-- x,y : ℕ
-- x = y : Prop
set_option pp.structure_projections false

example: 3=3 :=
begin
  reflexivity,
end

example : ¬ (3 = 5) :=
begin
  assume n,
  have h : 2=4,
  injection n,
  have k : 1=3,
  injection h,
  have l : 0 = 2,
  injection k,
  contradiction,
end

example : ¬ (3 = 5) :=
begin
  assume n,
  cases n,
end

open nat

def eq_nat : ℕ → ℕ → bool
| zero zero := tt
| zero (succ n) := ff
| (succ m) zero := ff
| (succ m) (succ n) := eq_nat m n

#reduce eq_nat 3 3

#reduce eq_nat 3 5

lemma eq_nat_1 : ∀ m : ℕ, eq_nat m m = tt :=
begin
  assume m,
  induction m with n' ih,
  dsimp [eq_nat],
  reflexivity,
  dsimp [eq_nat],
  exact ih,
end

lemma eq_nat_2 : ∀ m n : ℕ, eq_nat m n = tt → m = n :=
begin
  assume m,
  induction m with m' ih_m,
  assume n,
  cases n, -- n be 0 and succ n
  assume h,
  reflexivity,

  assume h,
  dsimp [eq_nat] at h,
  contradiction,

  assume n h,
  cases n,
  dsimp [eq_nat] at h,
  contradiction,
  have g : m'=n,
  apply ih_m,
  dsimp [eq_nat] at h,
  exact h,
  rewrite g,
end

theorem eq_nat_dec : ∀ m n : ℕ, m=n ↔ eq_nat m n = tt :=
begin
  assume m n,
  constructor,
  assume e,
  rewrite e,
  apply eq_nat_1,
  apply eq_nat_2,
end

-- ok equality for natural numbers is decidable
-- are all relations decidable, are all equalities decidable?

/--
Certainly not all relations or predicates are decidable. An example for an undecidable relation is equality of functions that is given f g : ℕ → ℕ is f = g? Two functions are equal iff they agree on all arguments f = g ↔ ∀ n :ℕ,f n = g n. It is clear that we cannot decide this, i.e. define a function into bool, because we would have to check infinitely many inputs.
--/

-- there are undecidable predicates
-- TM : Set
-- Halts : TM → Prop
-- halts : TM → bool -- there is no such function, see Halting problem

-- f g : ℕ → ℕ
-- f = g ?
-- ∀ n : ℕ, f n = g n
-- eq_fun : (ℕ → ℕ) → (ℕ → ℕ) → bool -- is undecidable
-- halts_after_n_steps : TM → ℕ → bool -- is definable
-- Halts tm := ¬ (halts_after_n_step = λ x, ff)

-- h : succ m = succ n
-- ⊢ m = n
-- injection h,

-- a = b
-- calc
--   a = c : by ..
--   ... = d : by ..
--   ... = b : by ..


/-
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 -- imports the real numbers

/-

# Limits of sequences in Lean

We give the standard `ε`, `N` definition of the limit of a sequence
and prove some theorems about them.

## lambda (λ) notation for functions

Here's how we define the functions from the naturals to the reals
sending n to n^2 + 3:

-/

def f : ℕ → ℝ := λ n, n^2+3

/-

Mathematicians might write `n ↦ n^2+3` for this functions; indeed `λ` is
just prefix notation for the infix notation `↦` (i.e. you write `λ` at
the front and `↦` in the middle but they mean the same thing). You can
read more about function types in the "three kinds of types" section
of Part B of the course book.

Sometimes you might find yourself with a lambda-defined function
evaluated at a number. For example, you might see something like
`(λ n, n^2 + 3) 37`, which means "take the function sending
`n` to `n^2+3` and then evaluate it at 37". You can use the `dsimp`
(or `dsimp only`) tactic to simplify this to `37^2+3`.

The reason we need to know about function notation for this sheet
is that a sequence `x₀, x₁, x₂, …` of reals on this sheet will
be encoded as a function from `ℕ` to `ℝ` sending `0` to `x₀`, `1` to `x₁`
and so on.

## Limit of a sequence.

Here's the definition of the limit of a sequence.
-/

/-- If `a(n)` is a sequence of reals and `t` is a real, `tendsto a t`
is the assertion that the limit of `a(n)` as `n → ∞` is `t`. -/
def tendsto (a : ℕ → ℝ) (t : ℝ) : Prop :=
∀ ε > 0, ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε

/-

We've made a definition, so it's our job to now make the API
for the definition, i.e. prove some basic theorems about it.
-/

-- If your goal is `tendsto a t` and you want to replace it with
-- `∀ ε > 0, ∃ B, …` then you can do this with `rw tendsto_def`.
theorem tendsto_def {a : ℕ → ℝ} {t : ℝ} :
  tendsto a t ↔ ∀ ε, 0 < ε → ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε :=
begin
  -- true by definition
  refl
end

-- the eagle-eyed viewers amongst you might have spotted
-- that `∀ ε > 0, ...` and `∀ ε, ε > 0 → ...` and `∀ ε, 0 < ε → ...`
-- are all definitionally equal, so `refl` sees through them.

/-

## The questions

Here are some basic results about limits of sequences.
See if you can fill in the `sorry`s with Lean proofs.
Note that `norm_num` can work with `|x|` if `x` is a numeral like 37,
but it can't do anything with it if it's a variable.
-/

/-- The limit of the constant sequence with value 37 is 37. -/
theorem tendsto_thirtyseven : tendsto (λ n, 37) 37 :=
begin
  sorry,
end

/-- The limit of the constant sequence with value `c` is `c`. -/
theorem tendsto_const (c : ℝ) : tendsto (λ n, c) c :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` then `a(n) + c` tends to `t + c` -/
theorem tendsto_add_const {a : ℕ → ℝ} {t : ℝ} (c : ℝ)
  (h : tendsto a t) :
  tendsto (λ n, a n + c) (t + c) :=
begin
  sorry,
  -- hints: make sure you know the maths proof!
  -- use `cases` to deconstruct an `exists`
  -- hypothesis, and `specialize` to specialize
  -- a `forall` hypothesis to specific values.
  -- Look up the explanations of these tactics in Part C
  -- of the course notes.
end

/-- If `a(n)` tends to `t` then `-a(n)` tends to `-t`.  -/
example {a : ℕ → ℝ} {t : ℝ} (ha : tendsto a t) :
  tendsto (λ n, - a n) (-t) :=
begin
  sorry,
  -- Try this one. Where do you get stuck?
  -- The problem is that you probably don't
  -- know any API for the absolute value function |.|.
  -- We need to figure out how to prove |(-x)| = |x|,
  -- or |a - b| = |b - a| or something like that.
  -- Find out how in sheet 4.
end


/-
To show that something (such as n = n)
is true for *all* values of some type
(such as nat), assume that you have an
*arbitrary* value of that type and in
that context show that that something
is true for that arbitrary n. Because
it's true for an n selected arbitrarily
it must be true for every n. (Think
about that.) This is the introduction
rule for ∀. In Lean, to prove a ∀ we
define a function: one that *assumes*
it's given an arbitrary argument of a
given type and that, in that context,
produces a value of a desired type.
So you already know how to prove a ∀
proposition in Lean: write a function.
-/

#check ∀ (n : nat), n = n

lemma all1 : ∀ (n : nat), n = n := λ n, (eq.refl n)

lemma all2 (n : nat) : n = n := rfl

#check ∀ (n : nat), n = n

-- Introduction rule for forall

/-
Now *given* a proof of a forall, you
*use* it by *applying* it to get a
proof for any particular instance of
a universally quantified proposition.
Suppse you know that ∀ n, n = n and
you need a proof of 5 = 5. You can
just *apply* the "proof" to 5 to get
what you need. This is the elimination
rule for ∀.
-/

example : 5 = 5 := all1 5

#check all1 5

axioms  (Person : Type)
        (Friendly : Person → Prop)
        (allFriendly : ∀ (p : Person), Friendly p)
        (John : Person)

example : Friendly John := allFriendly John

/-
Proofs are just "programs" -- functions, data structures
-/