import MyNat.Addition
import AdditionWorld.Level2 -- add_assoc
import AdditionWorld.Level4 -- add_comm
import AdditionWorld.Level5 -- succ_eq_add_one
namespace MyNat
open MyNat

/-!

# Addition World

## Level 6: `add_right_comm`

Lean sometimes writes `a + b + c`. What does it mean? The convention is
that if there are no brackets displayed in an addition formula, the brackets
are around the left most `+` (Lean's addition is "left associative").
So the goal in this level is `(a + b) + c = (a + c) + b`. This isn't
quite `add_assoc` or `add_comm`, it's something you'll have to prove
by putting these two theorems together.

If you hadn't picked up on this already, `rw [add_assoc]` will
change `(x + y) + z` to `x + (y + z)`, but to change it back
you will need `rw [← add_assoc]`. Get the left arrow by typing `\l`
then the space bar (note that this is L for left, not a number 1).
Similarly, if `h : a = b` then `rw [h]` will change `a`'s to `b`'s
and `rw [← h]` will change `b`'s to `a`'s.

Also, you can be (and will need to be, in this level) more precise
about where to rewrite theorems. `rw [add_comm]` will just find the
first `? + ?` it sees and swap it around. You can target more specific
additions like this: `rw [add_comm a]` will swap around
additions of the form `a + ?`, and `rw [add_comm a b]` will only
swap additions of the form `a + b`.

## Where next?

There are thirteen more levels about addition after this one, but before
you can attempt them you need to learn some more tactics. So after this
level you have a choice -- either move on to
[Multiplication World](../MultiplicationWorld.lean.md) (which you can
solve with the tactics you know) or try
[Function World](../FunctionWorld.lean.md) (and learn some new ones).
Other things, perhaps of interest
to some players, are mentioned below the lemma.

## Lemma

For all natural numbers `a, b` and `c`, we have `a + b + c = a + c + b.`
-/
lemma add_right_comm (a b c : MyNat) : a + b + c = a + c + b := by
  rw [add_assoc]
  rw [add_comm b c]
  rw [←add_assoc]

/-!
If you have got this far, then you have become very good at manipulating equalities in Lean. You can
also now connect four handy type class `instances` to your `MyNat` type as follows:

-/
-- instance : AddSemigroup MyNat where
--   add_assoc := add_assoc

-- instance : AddCommSemigroup MyNat where
--   add_comm := add_comm

-- instance : AddMonoid MyNat where
--   nsmul :=  λ x y => (myNatFromNat x) * y
--   nsmul_zero' := MyNat.zero_mul
--   nsmul_succ' n x := by
--     show ofNat (MyNat.succ n) * x = x + MyNat n * x
--     rw [MyNat.ofNat_succ, MyNat.add_mul, MyNat.add_comm, MyNat.one_mul]

-- instance : AddCommMonoid MyNat where
--   zero_add := zero_add
--   add_zero := add_zero
--   add_comm := add_comm
--   nsmul_zero' := ...
--   nsmul_succ' := ...

-- BUGBUG: really? These last two require theorems about multiplication?
-- like add_mul and zero_mul, which is dependent on mul_comm...?
/-!

In Multiplication World you will be able to collect such
advanced collectibles as `MyNat.comm_semiring` and
`MyNat.distrib`, and then move on to [Power World](../PowerWorld.lean.md) and
the famous collectible at the end of it.

One last thing -- didn't you think that solving this level
`add_right_comm` was boring? Check out this AI that can do it for us.

The `simp` AI (it's just an advanced tactic really), and can nail some really
tedious-for-a-human-to-solve goals. For example, check out this one-line proof.
First you need to teach `simp` about the building blocks you have created so far:
-/

lemma add_left_comm (a b c : MyNat) : a + (b + c) = b + (a + c) := by
  rw [←add_assoc]
  rw [add_comm a]
  rw [add_assoc]

attribute [simp] add_assoc add_comm add_left_comm

example (a b c d e : MyNat) :
  (((a+b)+c)+d)+e=(c+((b+e)+a))+d := by
  simp

/-!
Imagine having to do that one by hand! The AI closes the goal because it knows how to use
associativity and commutativity sensibly in a commutative monoid.

For more info see the [simp tactic](../Tactics/simp.lean.md).

You are now done with addition world. You can now decide whether to press on with
[Multiplication World](../MultiplicationWorld.lean.md) and [Power World](../PowerWorld.lean.md)
(which can be solved with `rw`, `rfl` and `induction`), or to go on to
[Function World](../FunctionWorld.lean.md) where you can learn the tactics needed to prove goals of the form `P → Q`, thus
enabling you to solve more advanced addition world levels such as `a + t = b + t → a = b`. Note that
[Function World](../FunctionWorld.lean.md) is more challenging mathematically;
but if you can do Addition World you can surely
do [Multiplication World](../MultiplicationWorld.lean.md) and [Power World](../PowerWorld.lean.md).

-/


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que en los espacios métricos
--    0 ≤ dist x y
-- ----------------------------------------------------------------------

import topology.metric_space.basic

variables {X : Type*} [metric_space X]

variables x y : X

-- 1ª demostración
example : 0 ≤ dist x y :=
  have h1 : 2 * dist x y ≥ 0, by calc
    2 * dist x y = dist x y + dist x y : two_mul (dist x y)
             ... = dist x y + dist y x : by rw [dist_comm x y]
             ... ≥ dist x x            : dist_triangle x y x
             ... = 0                   : dist_self x,
  show 0 ≤ dist x y,
    by exact nonneg_of_mul_nonneg_left h1 zero_lt_two

-- 2ª demostración
example : 0 ≤ dist x y :=
-- by library_search
dist_nonneg


-- Math 52: Week 4

import .utils.int_refl

-- Lakins Definition 1.2.1:
definition is_even (n : ℤ) := ∃ (k : ℤ), n = 2 * k
definition is_odd (n : ℤ) := ∃ (k : ℤ), n = 2 * k + 1

-- Lakins Definition 2.1.1: Let a, b ∈ ℤ.
-- a divides b if there exists n ∈ ℤ such that b = an.
-- We write a ∣ b for "a divides b" and say that a is
-- a divisor of b.
definition divides (a b : ℤ) : Prop := ∃ (n : ℤ), a * n = b

-- The notation `a ∣ b` can be used for `divides a b`
local infix ∣ := divides

-- Lakins Example 2.1.2:
example : 3 ∣ 12 :=
begin
unfold divides,
existsi (4:ℤ),
refl,
end

-- Theorem: For every integer a, a ∣ a.
theorem divides_refl : ∀ (a : ℤ), a ∣ a :=
begin
intro a,
unfold divides,
existsi (1:ℤ),
calc a * 1 = a : by rw mul_one,
end
-- Proof: Let a ∈ ℤ be arbitrary. We must show that a ∣ a;
-- i.e., we must find an integer k such that a = a * k.
-- Since a = a * 1 and 1 is an integer, we see that
-- a ∣ a is true. □

-- Lakins Proposition 2.1.3: For all integers a, b, c,
-- if a ∣ b and b ∣ c, then a ∣ c.
theorem divides_trans : ∀ (a b c : ℤ), a ∣ b ∧ b ∣ c → a ∣ c :=
begin
intros a b c H,
cases H with Hl Hr,
unfold divides,
unfold divides at Hl Hr,
cases Hl with n Summer_sez,
cases Hr with m Thomas_sez,
existsi(n * m :ℤ),
symmetry,
calc c
= b * m : by rw Thomas_sez ...
= (a * n) * m : by rw Summer_sez ...
= a * (n * m) : by ac_refl,
end
-- Proof: Let a,b,c ∈ ℤ be arbitrary and assume that
-- a ∣ b and b ∣ c. We must show that a ∣ c; i.e., we
-- must find an integer k such that c = a * k.
--
-- Since a ∣ b, by Definition 2.1.1 we may fix n ∈ ℤ
-- such that b = a * n. Similarly, since b | c, we may
-- fix m ∈ ℤ such that c = b * m, again by Definition
-- 2.1.1. Then
--   c = b * m = (a * n) * m = a * (n * m),
-- since multiplication of integers is associative
-- (Basic Properties of Integers 1.2.3). Since
-- n * m ∈ ℤ, we have proved that a ∣ c, by
-- Definition 2.1.1, as desired. 􏰟

-- Lakins Exercise 2.1.1: Let a,b, and c be integers.
-- For all integers m and n, if a ∣ b and a ∣ c, then
-- a ∣ (bm + cn).
theorem L211 : ∀ (a b c m n : ℤ), a ∣ b ∧ a ∣ c → a ∣ (b * m + c * n) :=
begin
sorry
end

-- Theorem: For every integer a, a is even if and only if 2 ∣ a.
theorem is_even_iff_two_divides : ∀ (a : ℤ), is_even a ↔ 2 ∣ a :=
begin
sorry
end

-- We will prove this fact later after we discuss induction.
-- For now we take it as an axiom, i.e., as statement that we
-- take as true without proof.
axiom even_or_odd (a : ℤ) : is_even a ∨ is_odd a

-- Lakins Theorem 2.1.9: For all integers a, a(a + 1) is even.
theorem even_product : ∀ (a : ℤ), is_even (a * (a + 1)) :=
begin
sorry
end
-- Proof: Let a ∈ ℤ. We show that a(a + 1) is even by
-- considering two cases.
--
-- Case I: a is even.
-- Then 2 ∣ a, by Definition 1.2.1. Since a ∣ a(a + 1)
-- by Definition 2.1.1, we have that 2 ∣ a(a + 1) since
-- the divisibility relation is transitive (Proposition
-- 2.1.3). Hence a(a + 1) is even.
--
-- Case II: a is not even.
-- Since a is not even, we know that a is odd. Then
-- a+1 is even by Exercise 1.2.2b. Then, using an
-- argument similar to that of Case I, we have that
-- 2 ∣ (a+1) and (a+1) ∣ a(a+1), and hence 2 ∣ a(a+1)
-- by Proposition 2.1.3. Thus a(a + 1) is even.
--
-- Hence, since we have considered all possible cases
-- for the integer a, we have proved that for all
-- integers a, a(a + 1) is even. 􏰟


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_pair : even n) (h_non_pair : ¬ even n) : 0 = 1 :=
begin
  -- sorry
  exfalso,
  exact h_non_pair h_pair,
  -- sorry
end

-- 0046
example (P Q : Prop) (h₁ : P ∨ Q) (h₂ : ¬ (P ∧ Q)) : ¬ P ↔ Q :=
begin
  -- sorry
  split,
  { intro hnP,
    cases h₁ with hP hQ,
    { exfalso,
      exact hnP hP, },
    { exact hQ }, },
  { intros hQ hP,
    exact h₂ ⟨hP, hQ⟩ },
  -- 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).
-/
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_of_ne_zero (sub_ne_zero_of_ne H),
  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', -- this is a new tactic, whose effect should be clear
  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
         ... ≤ |l - u N₀| + |u N₀ - l'|  : by apply abs_add
         ... = |u N₀ - l| + |u N₀ - l'|  : by rw abs_sub
         ... < |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
  intro hP,
  by_contradiction hnQ,
  exact h hnQ hP,
  -- sorry
end

/-
Again Lean doesn't need to be explain this principle. 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
 not_even_iff_odd : ¬ even n ↔ odd n,
-/
-- 0048
example (n : ℤ) : even (n^2) ↔ even n :=
begin
  -- sorry
  split,
  { contrapose,
    rw not_even_iff_odd,
    rw not_even_iff_odd,
    rintro ⟨k, rfl⟩,
    use 2*k*(k+1),
    ring },
  { rintro ⟨k, rfl⟩,
    use 2*k^2,
    ring },
  -- 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
  split,
  { intro h,
    by_cases hP : P,
    { right,
      intro hQ,
      exact h ⟨hP, hQ⟩ },
    { left,
      exact hP } },
  { rintros h ⟨hP, hQ⟩,
    cases h with hnP hnQ,
    { exact hnP hP },
    { exact hnQ hQ } },
  -- 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
  split,
  { intros hyp k hk,
    exact hyp ⟨k, hk⟩ },
  { rintros hyp ⟨k, rfl⟩,
    exact hyp k rfl },
  -- 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
  split,
  { contrapose,
    intro h,
    rw not_not,
    intro x,
    by_contradiction H,
    apply h,
    use x,
    /- Alternative version
    intro h,
    by_contradiction H,
    apply h,
    intro x,
    by_contradiction H',
    apply H,
    use x, -/ },
  { rintros ⟨x, hx⟩ h',
    exact hx (h' x) },
  -- 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
  unfold even_fun,
  push_neg,
  -- 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
  contrapose,
  push_neg,
  intro h,
  use x/2,
  split ; linarith,
  -- 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
  split,
  { intros hf x y,
    split,
    { intros hxy,
      cases eq_or_lt_of_le hxy with hxy hxy,
      { rw hxy },
      { linarith [hf x y hxy]} },
    { contrapose!,
      apply hf } },
  { intros hf x y,
    contrapose!,
    intro h,
    rwa hf, }
  -- sorry
end


/-
Copyright (c) 2021 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
-/
import set_theory.cardinal_ordinal
import data.W.basic
/-!
# Cardinality of W-types

This file proves some theorems about the cardinality of W-types. The main result is
`cardinal_mk_le_max_omega_of_fintype` which says that if for any `a : α`,
`β a` is finite, then the cardinality of `W_type β` is at most the maximum of the
cardinality of `α` and `cardinal.omega`.
This can be used to prove theorems about the cardinality of algebraic constructions such as
polynomials. There is a surjection from a `W_type` to `mv_polynomial` for example, and
this surjection can be used to put an upper bound on the cardinality of `mv_polynomial`.

## Tags

W, W type, cardinal, first order
-/
universe u

variables {α : Type u} {β : α → Type u}

noncomputable theory

namespace W_type

open_locale cardinal

open cardinal

lemma cardinal_mk_eq_sum : #(W_type β) = sum (λ a : α, #(W_type β) ^ #(β a)) :=
begin
  simp only [cardinal.power_def, ← cardinal.mk_sigma],
  exact mk_congr (equiv_sigma β)
end

/-- `#(W_type β)` is the least cardinal `κ` such that `sum (λ a : α, κ ^ #(β a)) ≤ κ` -/
lemma cardinal_mk_le_of_le {κ : cardinal.{u}} (hκ : sum (λ a : α, κ ^ #(β a)) ≤ κ) :
  #(W_type β) ≤ κ :=
begin
  induction κ using cardinal.induction_on with γ,
  simp only [cardinal.power_def, ← cardinal.mk_sigma, cardinal.le_def] at hκ,
  cases hκ,
  exact cardinal.mk_le_of_injective (elim_injective _ hκ.1 hκ.2)
end

/-- If, for any `a : α`, `β a` is finite, then the cardinality of `W_type β`
  is at most the maximum of the cardinality of `α` and `ω`  -/
lemma cardinal_mk_le_max_omega_of_fintype [Π a, fintype (β a)] : #(W_type β) ≤ max (#α) ω :=
(is_empty_or_nonempty α).elim
  (begin
    introI h,
    rw [cardinal.mk_eq_zero (W_type β)],
    exact zero_le _
  end) $
λ hn,
let m := max (#α) ω in
cardinal_mk_le_of_le $
calc cardinal.sum (λ a : α, m ^ #(β a))
    ≤ #α * cardinal.sup.{u u}
      (λ a : α, m ^ cardinal.mk (β a)) :
  cardinal.sum_le_sup _
... ≤ m * cardinal.sup.{u u}
      (λ a : α, m ^ #(β a)) :
  mul_le_mul' (le_max_left _ _) (le_refl _)
... = m : mul_eq_left.{u} (le_max_right _ _)
  (cardinal.sup_le.2 (λ i, begin
    cases lt_omega.1 (lt_omega_iff_fintype.2 ⟨show fintype (β i), by apply_instance⟩) with n hn,
    rw [hn],
    exact power_nat_le (le_max_right _ _)
  end))
  (pos_iff_ne_zero.1 (succ_le.1
    begin
      rw [succ_zero],
      obtain ⟨a⟩ : nonempty α, from hn,
      refine le_trans _ (le_sup _ a),
      rw [← @power_zero m],
      exact power_le_power_left (pos_iff_ne_zero.1
        (lt_of_lt_of_le omega_pos (le_max_right _ _))) (zero_le _)
    end))

end W_type


-- Define a function `factorial` that takes a natural number `n` and returns its factorial
def factorial : ℕ → ℕ
| 0 := 1 -- The factorial of 0 is defined to be 1 by convention
| (n + 1) := (n + 1) * factorial n -- For a positive integer n+1, compute the factorial of n and multiply it by n+1

-- Define a theorem `factorial_correct` that states that `factorial n` returns the correct value for any natural number `n`
theorem factorial_correct (n : ℕ) : factorial n = n.fact :=
nat.rec_on n -- Perform structural induction on `n`
  (show factorial 0 = 0.fact, by simp [factorial, nat.fact]) -- The base case is n = 0, for which factorial 0 = 1 and 0! = 1 by convention
  (assume k ih,
    have h₁ : factorial (k + 1) = (k + 1) * k.fact, from by simp [factorial, ih],
    have h₂ : (k + 1).fact = (k + 1) * k.fact, from by simp [nat.fact_succ, h₁],
    show factorial (k + 1) = (k + 1).fact, by simp [h₁, h₂])


import tactic
import data.nat.prime
import data.nat.pow
import data.nat.parity

open nat

/- An inductive definition of the factorial function -/
def fct : ℕ → ℕ
| 0       := 1
| (n + 1) := (n + 1) * fct n

/- Fill in the inductive definition of 2 to the power n -/
def pw2 : ℕ → ℕ
| 0       := sorry
| (n + 1) := sorry

/- Fill in the inductive definition of the Fibonacci numbers -/
def fib : ℕ → ℕ
| 0       := sorry
| 1       := sorry
| (n + 2) := sorry

/-  Prove the following four results using induction. The terminology is `induction n with n ih`
    to use induction over `n`, where the variable is also called `n`, and the inductive hypothesis
    is called `ih`. The results should be in increasing order of difficulty. -/
lemma one_le_pw2 (n : ℕ) : 1 ≤ pw2 n :=
begin
  sorry,
end

theorem n_le_pw2 (n : ℕ) : n ≤ pw2 n :=
begin
  sorry,
end

theorem pw_2_le_fct (n : ℕ) : pw2 n ≤ fct (n + 1) :=
begin
  sorry,
end

theorem sum_fib_squares (n : ℕ) : (finset.range (n + 1)).sum (λ i, (fib i) * (fib i)) =
  (fib n) * (fib (n + 1)) :=
begin
  sorry,
end

/-  `n % m` means n modulo n. The `ring` tactic is useful proving identities in ℕ using addition and
    multiplication. Use `by_cases even n` to split into cases depending on if `n` is even or not.
    The hypothesis `even n` means `∃ (k : ℕ), n = k + k`. Use the `obtain` tactic on that hypothesis
    to access such a `k` (`obtain ⟨k, h⟩ := h,`).  Some useful theorems:
    - nat.mul_mod_right
    - nat.odd_iff_not_even
    - nat.add_mul_mod_self_left
    (look them up in the API) -/
theorem squares_mod_four (n : ℕ) : (n ^ 2) % 4 = 0 ∨ (n ^ 2) % 4 = 1 :=
begin
  sorry,
end

/-  You can continue this project by formalising any result you like in elementary number theory.
    The book "250 problems in elementary number theory" by Sierpinski is a good place to look for
    projects. You can also try to find easier competition-style problems and solve them in Lean,
    like the one below. -/

/-  `nat.prime p` is a proposition saying that the natural number p is prime.  -/
/-  The following result is the answer to Problem 4 on the Icelandic national mathematics
    competition 2014: find all pairs (p,q) of primes such that p^q + q^p is prime.
    Hint: use the `by_cases` tactic to divide into cases depending on whether p is even.
    In the case where p is even, consider the expression mod 3.  -/
theorem pq (p q : ℕ) (hpq : p ≤ q) [nat.prime p] [nat.prime q] :
  nat.prime (p ^ q + q ^ p) → p = 2 ∧ q = 3 :=
begin
  sorry,
end


-- ---------------------------------------------------------------------
-- Ejercicio. Sean a y b números reales. Demostrar que
--    max a b = max b a
-- ----------------------------------------------------------------------

import data.real.basic

variables a b : ℝ

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

example : max a b = max b a :=
begin
  apply le_antisymm,
  { show max a b ≤ max b a,
    apply max_le,
    { apply le_max_right },
    { apply le_max_left }},
  { show max b a ≤ max a b,
    apply max_le,
    { apply le_max_right },
    { apply le_max_left }},
end

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

example : max a b = max b a :=
begin
  have h : ∀ x y, max x y ≤ max y x,
  { intros x y,
    apply max_le,
    { apply le_max_right },
    { apply le_max_left }},
  apply le_antisymm,
  apply h,
  apply h
end

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

example : max a b = max b a :=
begin
  have h : ∀ {x y : ℝ}, max x y ≤ max y x,
  { intros x y,
    exact max_le (le_max_right y x) (le_max_left y x),},
  exact le_antisymm h h,
end

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

example : max a b = max b a :=
begin
  apply le_antisymm,
  repeat {
    apply max_le,
    apply le_max_right,
    apply le_max_left },
end

-- Lemas usados
-- ============

-- #check (le_max_left a b : a ≤ max a b)
-- #check (le_max_right a b : b ≤ max a b)
-- #check (max_le : a ≤ c → b ≤ c → max a b ≤ c)


-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar las siguientes acciones:
--    1. Importar la teoría de los números reales
--    2. Declarar x, y y z como variables sobre R.
-- ----------------------------------------------------------------------

import data.real.basic

variables (x y z : ℝ)

-- ---------------------------------------------------------------------
-- Ejercicio 2. Demostrar que si
--    x ≤ y
--    y ≤ z
-- entonces
--    x ≤ z
-- ----------------------------------------------------------------------

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

example
  (h1 : x ≤ y)
  (h2 : y ≤ z)
  : x ≤ z :=
begin
  apply le_trans,
  { apply h1, },
  apply h2,
end

-- El desarrollo de la prueba es
--
--    x y z : ℝ,
--    h1 : x ≤ y,
--    h2 : y ≤ z
--    ⊢ x ≤ z
-- apply le_trans,
--    ⊢ x ≤ ?m_1
-- { apply h1 },
--    ⊢ y ≤ z
-- apply h2,
--    no goals

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

example
  (h1 : x ≤ y)
  (h2 : y ≤ z)
  : x ≤ z :=
begin
  apply le_trans h1,
  apply h2,
end

-- El desarrollo de la prueba es
--
--    x y z : ℝ,
--    h1 : x ≤ y,
--    h2 : y ≤ z
--    ⊢ x ≤ z
-- apply le_trans h1,
--    ⊢ y ≤ z
-- apply h2,
--    no goals

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

example
  (h1 : x ≤ y)
  (h2 : y ≤ z)
  : x ≤ z :=
by exact le_trans h1 h2

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

example
  (h1 : x ≤ y)
  (h2 : y ≤ z)
  : x ≤ z :=
le_trans h1 h2

-- ---------------------------------------------------------------------
-- Ejercicio 4. Demostrar que
--    x ≤ x
-- ----------------------------------------------------------------------

-- 1ª demostración
example : x ≤ x :=
by apply le_refl

-- 2ª demostración
example : x ≤ x :=
by exact le_refl x

-- 3ª demostración
example (x : ℝ) : x ≤ x :=
le_refl x


import lib.m154
open m154

/- # Le quantificateur existentiel

Dans ce fichier, on aborde la manipulation du quantificateur existentiel.
Pour démontrer un énoncé de la forme `∃ x, P x`,
on fournit un `x₀` qui convient à l'aide de la commande `Montrons que x₀ convient`.
Ce `x₀` peut être un objet du contexte, une constante ou une expression plus
compliquée. Ensuite il faut démontrer l'énoncé `P x₀`.
-/
example : ∃ x : ℝ, x*x = 4 :=
begin
  Montrons que -2 convient,  -- Est-ce la seule possibilité ?
  On calcule,
end

/-
Pour utiliser un énoncé de la forme « h : ∃ x, P x», on utilise la commande
« Par h on obtient x₀ tel que hx₀ : P x₀ »
qui fixe un x₀ et une hypothèse hx₀ affirmant P x₀.

Ici h peut être directement un énoncé du contexte ou une expression plus
compliquée. On peut aussi utiliser la forme
`Par h appliqué à args on obtient ...` où `args` est un argument ou une liste d'arguments.

Il faut bien noter qu'en général il y a plusieurs `x` qui conviennent. Dans
l'exemple suivant, il y en a deux.
Pour insister sur le fait qu'on en a fixé un, on l'appelle `x₀`.
On utilise aussi les lemmes `pos_pos` et `neg_neg` de la feuille 2, et le lemme
`le_total` de la feuille 3.
-/
example (y : ℝ) (h : ∃ x, y = x*x) : y ≥ 0 :=
begin
  Par h on obtient x₀ tel que hy₀ : y = x₀*x₀,
  On réécrit via hy₀,
  On discute en utilisant (le_total 0 x₀ : 0 ≤ x₀ ∨ 0 ≥ x₀),
    Supposons h' : 0 ≤ x₀,
    On conclut par pos_pos appliqué à [h', h'],
  Supposons h' : 0 ≥ x₀,
  On conclut par neg_neg appliqué à [h', h']
end

/-
Les exercices suivants utilisent la relation de divisibilité dans ℤ
(attention à la barre de divisibilité qui n'est pas celle du clavier
mais obtenue par ,|).
Contrairement au contexte de la feuille 2, le seul lemme utile est ici
divise_def (a b : ℤ) : a ∣ b ↔ ∃ k, b = a*k
-/

-- Dans toute la suite, a b et c désignent des entiers relatifs
variables (a b c : ℤ)

/-
Dans des démonstrations un peu longues, il peut être utile de rappeler au lecteur
ce que « convient » signifie dans dans « Montrons que x₀ convient ».
Pour cela on peut écrire : `Montrons que x₀ convient : P x₀` comme dans l'exmple suivant.

Cet exemple illustre aussi la commande `On renomme` qui permet de renommer une variable
liée dans le but. On peut aussi renommer dans une hypothèse `hyp` en terminant
la commande par `dans hyp`. Ce renommage n'est jamais utile pour Lean, mais permet
de gagner en lisibilité.
-/

example (h1 : a ∣ b) (h2 : b ∣ c) : a ∣ c :=
begin
  Par h1 on obtient k tel que hk : b = a*k,
  Par h2 on obtient l tel que hl : c = b*l,
  -- Pour montrer que a ∣ c, il suffit de trouver m tel que c = a*m
  On réécrit via divise_def,
  -- Comme nous avons déjà fixé un entier nommé `k`, le nom de la variable muette
  -- dans le but est perturbant.
  On renomme k en m,

  Montrons que k*l convient : c = a * (k * l),
  calc c = b*l     : by On conclut par hl -- par (2)
     ... = (a*k)*l : by On réécrit via hk    -- par (1)
     ... = a*(k*l) : by On calcule,
end

/-
Dans la démonstration précédente, la commande « On renomme ... en ... (dans ...) »
est purement psychologique, elle renomme une variable liée pour éviter
un conflit apparent avec le k fixé dans le contexte.

En fait l'utilisation du lemme divise_def est aussi psychologique, Lean
sait que c'est la définition de la divisibilité.
Vous pouvez essayer de commenter ces deux lignes psychologiques en ajoutant
« -- » en début de ligne.

Voyons maintenant un autre énoncé semblable.
-/

example (h1 : a ∣ b) (h2 : a ∣ c) : a ∣ b+c :=
begin
  sorry
end

example : 0 ∣ a ↔ a = 0 :=
begin
  sorry
end

/-
L'exercice suivant utilise les définitions « pair » et « impair »

def pair (n : ℤ) := ∃ k, n = 2*k
def impair (n : ℤ) := ∃ k, n = 2*k + 1

et le lemme :

pair_ou_impair (n : ℤ) : pair n ∨ impair n
-/

example (n : ℤ) : pair (n*(n+1)) :=
begin
  sorry
end

/-
On commence maintenant à combiner explicitement les quantificateurs,
avec la définition de la surjectivité.
-/

def surjective (f : ℝ → ℝ) := ∀ y, ∃ x, f x = y

/-
Dans la démonstration suivante, il sera psychologiquement confortable de demander
à Lean de déplier cette définition, à l'aide de la commande `On déplie`.
-/

-- Dans la suite f et g sont des fonctions de ℝ dans ℝ.
variables(f g : ℝ → ℝ)

-- On suppose que g ∘ f est surjective. Alors g est surjective.
example (h : surjective (g ∘ f)) : surjective g :=
/- On commence par donner une démonstration très bavarde, mais
   cette discussion est très importante pour comprendre l'enjeu des
   variables liées et variables libres. -/
begin
  -- Il s'agit de montrer que ∀ y, ∃ x, g(x) = y
  On déplie surjective, -- On pourrait aussi écrire `Montrons que ∀ y, ∃ x, g x = y`
  -- Soit y un réel. On veut trouver x tel que g(x) = y
  Soit y,
  -- Or notre hypothèse sur g ∘ f garantit que ∀ y, ∃ x, (g ∘ f)(x) = y
  On déplie surjective dans h,
  -- A priori le symbole y apparaissant dans cette hypothèse n'a rien à voir
  -- avec le y que nous avons fixé. C'est une variable liée (ou muette),
  -- on peut la renommer z.
  On renomme y en z dans h,
  -- On a également des variables nommées x dans l'hypothèse et dans le but.
  -- Mais ce sont toutes deux des variables liées, n'ayant aucun rapport
  -- entre elles. En fait le (ou les) x promis par l'hypothèse ne conviennent
  -- pas pour notre but.
  -- Renommons donc la variable liée de l'hypothèse pour clarifier la situation.
  On renomme x en w dans h,
  -- On peut spécialiser l'hypothèse h au y que nous avons fixé,
  -- ce qui fournit w tel que (g ∘ f)(w) = y
  Par h appliqué à y on obtient w tel que hw : g (f w) = y,
  -- Montrons que f(w) convient pour notre but
  Montrons que f w convient,
  -- Il s'agit de montrer que g(f(w)) = y mais ce n'est rien d'autre
  -- que notre hypothèse sur w, par définition de la composition.
  -- Sur papier on écrirait simplement « f(w) convient », sans le
  -- « Montrons que ».
  On conclut par hw,
end
/-
Bien sûr l'essentiel de la discussion ci-dessus est inutile d'un point
de vue logique. Ni Lean ni une rédaction pour experts n'ont besoin
de tous ces discours. Voici la même démonstration sans bavardage.
-/
example (h : surjective (g ∘ f)) : surjective g :=
begin
  Soit y,
  Par h appliqué à y on obtient w tel que hw : (g ∘ f) w = y,
  Montrons que f w convient,
  On conclut par hw,
end

/-
La démonstration précédente est sans doute trop concise. Avant de la transcrire sur papier,
il vaut mieux ajouter une ligne inutile explicitant quel est le but après la première ligne.
On peut aussi ajouter quelques annotations pour nos lecteurs distraits qui auraient oublié
que f et g sont des fonctions de ℝ dans ℝ. De plus l'hypothèse h ne serait
sans pas nommée dans une version papier de cet exercice donc on ajoute aussi
une annotation au moment de l'utiliser.
-/

example (h : surjective (g ∘ f)) : surjective g :=
begin
  Soit y : ℝ,
  Montrons que ∃ x, g x = y,
  Par (h : surjective (g ∘ f)) appliqué à y on obtient (w : ℝ) tel que hw : (g ∘ f) w = y,
  Montrons que f w convient,
  On conclut par hw,
end

/-
Et on peut passer à la rédaction « sur papier », par exemple :

  Soit y réel. Montrons qu'il existe x réel tel que g(x) = y.
  Par l'hypothèse de surjectivité de g ∘ f appliquée à y, on obtient w réel tel que
  g ∘ f(w) = y. Le réel f(w) convient.
-/

example (hf : surjective f) (hg : surjective g) : surjective (g ∘ f) :=
begin
  sorry
end


import data.real.basic

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

 Recall that (fin n) is the type of pairs ⟨i,h⟩, where i is a natural
 number and h is a proof that i < n.  In this file we prove some
 additional facts about these types, which might perhaps be added
 to the standard library at some point.
-/

import data.list data.fintype

namespace fin
open fin finset

def pred_alt {n : ℕ} {i : ℕ} (h : i.succ < n.succ) : fin n :=
 ⟨i,nat.lt_of_succ_lt_succ h⟩

/- The successor function (fin n) → (fin (n + 1)) is injective -/
lemma succ_inj : ∀ {n : ℕ} (i j : fin n) (h : i.succ = j.succ), i = j
| n ⟨i_val,_⟩ ⟨j_val,_⟩ h :=
   begin
    let h0 := @congr_arg (fin n.succ) ℕ _ _ (@fin.val n.succ) h,
    dsimp[fin.succ] at h0,
    apply fin.eq_of_veq,
    exact nat.succ_inj h0,
   end

/- Zero is not a successor -/
lemma zero_ne_succ : ∀ {n : ℕ} {i : fin n}, (0 : fin n.succ) ≠ i.succ
| n ⟨i_val,_⟩ := λ e, nat.no_confusion (congr_arg fin.val e)

/- Zero is less than any successor -/
lemma zero_lt_succ : ∀ {n : ℕ} {i : fin n}, (0 : fin n.succ) < i.succ
| n ⟨i_val,_⟩ := nat.zero_lt_succ i_val

/- The successor map preserves order -/
lemma succ_le_succ {n : ℕ} {i j : fin n} (h : i ≤ j) : i.succ ≤ j.succ :=
 by {cases i,cases j,dsimp[has_le.le,fin.le],apply nat.succ_le_succ h,}

/- The successor map preserves strict order -/
lemma succ_lt_succ {n : ℕ} {i j : fin n} (h : i < j) : i.succ < j.succ :=
 by {cases i,cases j,dsimp[has_lt.lt,fin.lt],apply nat.succ_lt_succ h,}

/- The successor map reflects order -/
lemma le_of_succ_le_succ {n : ℕ} {i j : fin n} (h : i.succ ≤ j.succ) : i ≤ j :=
 by {cases i,cases j,dsimp[has_le.le,fin.le],apply nat.le_of_succ_le_succ h,}

/- The successor map reflects strict order -/
lemma lt_of_succ_lt_succ {n : ℕ} {i j : fin n} (h : i.succ < j.succ) : i < j :=
 by {cases i,cases j,dsimp[has_lt.lt,fin.lt],apply nat.lt_of_succ_lt_succ h,}

/- Nothing is less than zero -/
lemma not_lt_zero {n : ℕ} (i : fin n.succ) : ¬ i < 0 :=
 by {cases i,dsimp[has_lt.lt,fin.lt],exact nat.not_lt_zero i_val,}

/- This function produces a list of all the elements of (fin n) -/
def elems_list : ∀ (n : ℕ), list (fin n)
| 0 := @list.nil _
| (n + 1) := list.cons 0 ((elems_list n).map fin.succ)

/-The number of elements of (fin n) is n -/
lemma elems_list_length : ∀ (n : ℕ), (elems_list n).length = n
| 0 := rfl
| (n + 1) := congr_arg nat.succ
             ((list.length_map fin.succ (elems_list n)).trans
              (elems_list_length n))

/- The list of elements has no duplicates -/
def elems_list_nodup : ∀ (n : ℕ), list.nodup (elems_list n)
| 0 := @list.pairwise.nil _ ne
| (n + 1) := begin
  let old_list := (elems_list n).map fin.succ,
  have old_nodup : list.nodup old_list :=
   list.nodup_map fin.succ_inj (elems_list_nodup n),
  have not_mem_old : ∀ k ∈ old_list, (0 : fin n.succ) ≠ k :=
  begin
   intros k k_in_old zero_eq_k,
   let h1 := (list.mem_map.mp k_in_old),
   exact exists.elim h1 (λ k0 h2,zero_ne_succ (zero_eq_k.trans h2.right.symm)),
  end,
  exact list.pairwise.cons (not_mem_old) (old_nodup)
 end

/- Every element occurs in our list of elements -/
def elems_list_complete {n : ℕ} (k : fin n) : k ∈ (elems_list n) :=
begin
 induction n with n0 ih,
 {exact fin.elim0 k,},
 {dsimp[elems_list],
  rw[list.mem_cons_eq],
  cases k,
  cases k_val with k0_val,
  {left,apply fin.eq_of_veq,refl,},
  {have k0_is_lt : k0_val < n0 := nat.lt_of_succ_lt_succ k_is_lt,
   let k0 : fin n0 := ⟨k0_val,k0_is_lt⟩,
   have h0 : k0.succ = ⟨k0_val.succ,k_is_lt⟩ :=
   by {apply fin.eq_of_veq,refl,},
   right,
   apply list.mem_map.mpr,
   existsi k0,
   exact ⟨ih k0,h0⟩,
  }
 }
end

/- Convert our list of elements to a set -/
def elems_finset (n : ℕ) : finset (fin n) :=
{ val := elems_list n,
  nodup := elems_list_nodup n
}

/- Every element is in our set of elements -/
def elems_finset_complete {n : ℕ} (k : fin n) : k ∈ (elems_finset n) :=
 elems_list_complete k

/-
 This function returns the least element of (fin n) satisfying a given
 predicate p.  For this to work, we need a decision procedure for p
 (given as an implicit typeclass argument) and also a proof that at
 least one element of (fin n) satisfies p.  Moreover, we return the
 least element k packaged together with proofs of its properties.
 Thus, if this function returns x, then x.val is the relevant value
 of k, and x.property.left is a proof of (p k), and x.property.right
 is a proof that k is minimal with respect to this.
-/
def least_element {n : ℕ} (p : fin n → Prop) [d : decidable_pred p] (e : ∃ k, p k) :
 {k : fin n // p k ∧ ∀ j, p j → k ≤ j} :=
begin
 tactic.unfreeze_local_instances,
 induction n with n0 ih,
 {exfalso, cases e with i _,exact fin.elim0 i,},
 {
   by_cases pz : (p 0),
    {exact ⟨0, pz, λ j _,fin.zero_le j⟩},
    {let p0 : fin n0 → Prop := λ k0, p k0.succ,
     let d0 : decidable_pred p0 := λ k0, d k0.succ,
     have e0 : ∃ k0, p0 k0 :=
      begin
       rcases e with ⟨⟨k_val,k_is_lt⟩,p_k⟩,
       rcases k_val with _ | k0_val,
        {contradiction},
        {have k0_is_lt : k0_val < n0 := nat.lt_of_succ_lt_succ k_is_lt,
         let k0 : fin n0 := ⟨k0_val,k0_is_lt⟩,
         existsi k0,assumption
        }
      end,
     rcases (@ih p0 d0 e0) with ⟨k0,p0_k0,k0_min⟩,
     let k := fin.succ k0,
     have p_k : p k := p0_k0,
     have k_min : ∀ j, p j → k ≤ j :=
     begin
      intro j,
      cases j,rcases j_val with _ | j0_val;
       simp[has_le.le,fin.le],
      {intro pz0, exact false.elim (pz pz0),},
      {have j0_is_lt : j0_val < n0 := nat.lt_of_succ_lt_succ j_is_lt,
       let j0 : fin n0 := ⟨j0_val,j0_is_lt⟩,
       intro p0_j0,
       exact nat.succ_le_succ (k0_min j0 p0_j0),
       }
     end,
     exact ⟨k,p_k,k_min⟩
    }
 }
end

/-
 This is essentially the same as the previous function, but formulated
 in terms of a finite subset of (fin n) rather than a predicate.
-/
def finset_least_element {n : ℕ} (s : finset (fin n)) (e : s ≠ ∅ ) :
 {k : fin n // k ∈ s ∧ ∀ j, j ∈ s → k ≤ j} :=
  least_element (λ k,k ∈ s) (exists_mem_of_ne_empty e)

/-
 We now have a similar function for finding the largest element of
 a finite subset of (fin n).  For technical reasons we have found it
 more convenient to do this directly with finite sets rather than
 predicates.
-/
def finset_largest_element {n : ℕ} (s : finset (fin n)) (e : s ≠ ∅) :
 {k : fin n // k ∈ s ∧ ∀ j, j ∈ s → j ≤ k} :=
begin
 tactic.unfreeze_local_instances,
 induction n with n0 ih,
 {exfalso,
  cases (exists_mem_of_ne_empty e) with i _,
  exact fin.elim0 i,},
 {let s0 := univ.filter (λ i0 : fin n0, i0.succ ∈ s),
  by_cases e0 : s0 ≠ ∅,
  {rcases (ih s0 e0) with ⟨⟨k0_val,k0_is_lt⟩,⟨k0_in_s0,k0_max⟩⟩,
   let k0 : fin n0 := ⟨k0_val,k0_is_lt⟩,
   let k := k0.succ,
   let k_in_s : k ∈ s := (mem_filter.mp k0_in_s0).right,
   let k_max : ∀ j, j ∈ s → j ≤ k := begin
    intros j j_in_s,
    cases j, cases j_val with j0_val,
    {exact fin.zero_le k,},
    {have j0_is_lt : j0_val < n0 := nat.lt_of_succ_lt_succ j_is_lt,
     let j0 : fin n0 := ⟨j0_val,j0_is_lt⟩,
     have j0_in_s0 : j0 ∈ s0 := mem_filter.mpr ⟨mem_univ j0,j_in_s⟩,
     dsimp[has_le.le,fin.le,k,fin.succ],
     exact nat.succ_le_succ (k0_max j0 j0_in_s0),
    }
   end,
   exact ⟨k,⟨k_in_s,k_max⟩⟩,
  },{
   let z : fin n0.succ := 0,
   have zero_in_s : z ∈ s := begin
    let h : ∀ k, k ∈ s → z ∈ s := begin
     intro k,cases k,cases k_val with k0_val,
     {intro z_in_s,exact z_in_s,},
     {intro k_in_s,exfalso,
      have k0_is_lt : k0_val < n0 := nat.lt_of_succ_lt_succ k_is_lt,
      let k0 : fin n0 := ⟨k0_val,k0_is_lt⟩,
      exact e0 (ne_empty_of_mem (mem_filter.mpr ⟨mem_univ k0,k_in_s⟩)),
     }
    end,
    exact exists.elim (exists_mem_of_ne_empty e) h,
   end,
   have zero_max : ∀ j, j ∈ s → j ≤ z :=
   begin
    intros j j_in_s,
    cases j, cases j_val with j0_val,
    {exact fin.zero_le z},
    {exfalso,
     have j0_is_lt : j0_val < n0 := nat.lt_of_succ_lt_succ j_is_lt,
     let j0 : fin n0 := ⟨j0_val,j0_is_lt⟩,
     have j0_in_s0 : j0 ∈ s0 := mem_filter.mpr ⟨mem_univ j0,j_in_s⟩,
     exact e0 (ne_empty_of_mem j0_in_s0),
    }
   end,
   exact ⟨z,zero_in_s,zero_max⟩,
  }
 }
end

end fin


import tactic.linarith

namespace mth1001

section mixing_quantifiers
/-
Many mathematical results mix quantifiers. The results below states that for every integer
`x`, there exists an integer `y` such that `x + y = 0`.

Here the scope of the universal quantifier is the expression `∃ y : ℤ, x + y = 0`,
whereas the scope of the existential quantifier is the expression `x + y = 0`.

The proof follows the form of the statement. We start by introducing the universal
quantifier with `intro x`, then introduce the existential quantifier with `use (-x)`.

Note: after issuing `intro x`, the goal is simplified to `∃ y : ℤ, x + y = 0`. The
quantity `x` is fixed and our task is to find `y` for which `x + y = 0`. Clearly, `-x` is
such a value for `y`.
-/
example : ∀ x : ℤ, ∃ y : ℤ, x + y = 0 :=
begin
  intro x,  -- We let `x` be an arbitrary integer.
  use (-x), -- Take `y` to be `-x`.
  linarith,     -- This closes the goal `x + (-x) = 0`.
end

/-
Changing the order of quantifiers (usually) changes the meaning of a statement.

Understanding this is very important, so I'll repeat it.

Changing the order of quantifiers (usually) changes the meaning of a statement.

The statement `∃ y : ℤ, ∀ x : ℤ, x + y = 0`, in which I've merely swapped the order
of quantification in the above result, is false.

To prove this statement would be to find an integer `y` such that for every integer
`x`, `x + y = 0`.

In greater detail, the scope of the existential quantifier is the expression
`∀ x : ℤ, x + y = 0`. The quantity `y` in this statement is *fixed*. To prove this
statement is to show *for every `x`* that `x + y = 0`.

In particular, taking `x` to be `0`, we must have `0 + y = 0`. But taking `x` to be `1`, we
must have `1 + y = 0`. So `y = 0` *and* `y = -1`. This is a contradiction.
-/

-- Below is a Lean version of the argument above.
example : ¬(∃ y : ℤ, ∀ x : ℤ, x + y = 0) :=
begin
  intro h,
  cases h with y hy,
  have h₁ : y = 0,
  { specialize hy 0,
    linarith, },
  have h₂ : y = -1,
  { specialize hy 1,
    linarith, },
  linarith,
end

-- Exercise 086:
/- Alternatively, we can use `push_neg` to transform the initial goal into
`∀ y : ℤ, ∃ x : ℤ, x + y ≠ 0`. Having done this, we would let `y` be an aribtrary integer and
then choose `x` (which may depend on `y`) so that `x + y ≠ 0`. Can you think of an `x`, depending
on `y`, for which `x + y ≠ 0`?
-/
example : ¬(∃ y : ℤ, ∀ x : ℤ, x + y = 0) :=
begin
  push_neg,
  sorry
end

-- Exercise 087:
example : ∃ x : ℤ, ∀ y : ℤ, x + y = y :=
begin
  sorry
end

-- Here, `∃ x y : ℤ` is an abbreviation of `∃ x : ℤ, ∃ y : ℤ`.
example : ∃ x y : ℤ, x + y = y + 1 :=
begin
  use [1, 0], -- This is equivalent to `use 1, use 0`.
  linarith,
end

-- Exercise 088:
example : ∃ a : ℤ, ∀ b : ℤ, a + b = b :=
begin
  sorry
end

-- Exercise 089:
-- Note `∀ x y : ℤ` is an abbreviation of `∀ x : ℤ, ∀ y : ℤ`.
example : ∀ x y : ℤ, x + y = y + x :=
begin
  sorry
end

/-
Use `push_neg` in proving the next three results.
-/

-- Exercise 090:
example : ¬(∀ x y : ℤ, x + y = x) :=
begin
  sorry
end

-- Exercise 091:
example : ¬(∀ x : ℤ, ∃ y, x + y = y) :=
begin
  sorry
end

-- The next exercise is quite challenging.
-- Exercise 092:
example : ¬(∃ x y : ℤ, (x + y = 0) ∧ (x * x - y * y = 1)) :=
begin
  sorry
end

end mixing_quantifiers

end mth1001


/-
  A ring homomorphism is injective iff the kernel is trivial.
-/

import algebra.ring
import ring_theory.ideal_operations
import linear_algebra.basic
import to_mathlib.ideals

universes u v

variables {α : Type u} {β : Type v} [comm_ring α] [comm_ring β] (f : α → β) [is_ring_hom f]

lemma is_ring_hom.trivial_ker_def
: ker f = ⊥ ↔ (∀ {x}, f x = 0 → x = 0) :=
begin
  split,
  { intros Hker x Hx,
    replace Hx : x ∈ ker f := Hx,
    rw Hker at Hx,
    rw ←set.mem_singleton_iff,
    exact Hx, },
  { intros Hfx,
    apply ideal.ext,
    intros x,
    split,
    { intros Hx,
      replace Hx : f x = 0 := Hx,
      erw set.mem_singleton_iff,
      exact (Hfx Hx), },
    { intros Hx,
      erw set.mem_singleton_iff at Hx,
      rw Hx,
      exact is_ring_hom.map_zero f, } },
end

lemma is_ring_hom.inj_iff_trivial_ker
: (∀ {x}, f x = 0 → x = 0) ↔ function.injective f :=
begin
  split,
  { intros H x y Hxy,
    rw [←sub_eq_zero_iff_eq, ←is_ring_hom.map_sub f] at Hxy,
    exact sub_eq_zero_iff_eq.1 (H Hxy), },
  { intros Hinj,
    intros x Hx,
    have Hfx : f x = f 0 := (is_ring_hom.map_zero f).symm ▸ Hx,
    exact Hinj Hfx, }
end

lemma is_ring_hom.inj_iff_trivial_ker'
: ker f = ⊥ ↔ function.injective f :=
iff.trans (is_ring_hom.trivial_ker_def f) (is_ring_hom.inj_iff_trivial_ker f)


import ..lectures.love05_inductive_predicates_demo

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

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Even and Odd

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

#check even

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

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

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

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

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

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

-- enter your answer here

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

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

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

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

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

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

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

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

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

def even_rec : nat → bool :=
sorry

/-! ## Question 2: Tennis Games

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

#check score

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

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

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

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

lemma srv_ahead_adv_srv :
  srv_ahead score.adv_srv :=
sorry

lemma not_srv_ahead_adv_rcv :
  ¬ srv_ahead score.adv_rcv :=
sorry

lemma srv_ahead_game_srv :
  srv_ahead score.game_srv :=
sorry

lemma not_srv_ahead_game_rcv :
  ¬ srv_ahead score.game_rcv :=
sorry

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

-- enter your answer here

/-! ## Question 3: Binary Trees

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

#check is_full_mirror
#check mirror_mirror

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

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

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

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

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

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

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

end LoVe


/-
HOMEWORK #9

There is no need to import our previous definitions.
For this homework you will just use Lean's built-in
notations and definitions.
-/

/-
Prove the following. Note that you can read each of
the propositions to be proved as either a logical
statement or as simply a function definition. Use
what you already know about the latter to arrive
at a proof, and then understand the proof as one
that shows that the logical statement is true.
-/

theorem t1 {P Q : Prop} (p2q : P → Q) (p : P) : Q :=
  _

theorem t2 {P Q R : Prop} (p2q : P → Q) (q2r : Q → R): P → R :=
  _

/-
Use "example" to state and prove the preceding two
theorems but using "cases" style notation rather
than C-style. Remember, "example" is a way to state
a proposition/type and give an example of a value.
Here's an example of the use of "example". Give
your answers following this example.
-/

-- example
example : ℕ := 5

-- Your two answers here

/-
Now give English-language versions of your two proofs.
-/

/-
Prove the following using case analysis on one
of the arguments (i.e., use match...with...end
at a key point in your proof). Use "cases" style
notation.
-/

theorem t3 : ∀ (P : Prop), false → P
| P f := _

/-
Prove false → true by applying t3 to a proposition.
You have to figure out which one.
-/
theorem t4 : false → true := t3 _

/-
Define t5 to be the same as t3 but with P taken as
an implicit argument.
-/

theorem t5 : ∀ {P : Prop}, false → P
| P f := _

/-
Define t6 to be a proof of false → true by
applying t5 to the right argument(s).
-/

theorem t6 : false → true := _

/-
That is almost magic. In English, t3 proves
that false implies *any* proposition, so just
*apply* t3 to *true* in particular, but use t5
instead of t3.

What you see here is really important: Once
we've proved a general theorem (a ∀ proposition)
we can *apply the proof* to any *particular* case
to yield a proof for that specific case. This is
the elimination rule for ∀. It is also known as
universal instantiation (UI).
-/

/-
Next we see the idea that test cases are really
just equality propositions to be proved. Here,
for example, is a definition of the factorial
function.
-/

def fac : ℕ → ℕ
| 0 := 1
| (n' + 1) := (n' + 1) * fac n'

/-
Use "example" to write test cases for the
first ten natural number arguments to this
function.
-/

example : fac 0 = 1 := eq.refl 1
example : fac 1 = 1 := eq.refl _  -- Inferred
example : fac 2 = 2 := rfl        -- Shorthand
#check @rfl           -- infers type and value

-- The rest of your answers here

/-
Insight: A test case is an equality proposition.
It is proved by "running" the program under test
to reduce the application of the function to input
arguments to produce an output that is then asserted
to be equal to an expected output.

In many cases, all we have to do is to simplify
the expressions on each side of the eq to see if
they reduce to exactly the same value. If so, we
can *apply* eq.refl (a universal generalization!)
to that value. Using rfl we can avoid even having
to type that value in cases where Lean can infer
it.
-/

/-
The next problem requires thave you give a proof of
a bi-implication, a proposition whose connective is
↔. To prove a bi-implication requires that one prove
an implication in each direction.

Here you are asked to prove P ∧ Q ↔ Q ∧ P. What this
formula asserts is that ∧ is commutative. To construct
a proof of this proposition you will have to apply
iff.intro to two smaller proofs, one of P → Q and
and of Q → P.

Start by "assuming" that P and Q are arbitary but
specific propositions (∀ introduction), then apply
iff.intro to two "stubbed out" arguments (underscores).
We suggest that you put the underscores in parentheses
on different lines. Then recursively fill in each of
these stubs with the required types of proofs. Study
the context that Lean shows you in its Messages panel
to see what you have to work with at each point in
your proof constructions.
-/
theorem t7 : ∀ {P Q : Prop}, P ∧ Q ↔ Q ∧ P :=
_

/-
In English, when asked to prove P ↔ Q, one says, "it
will suffice to show P → Q and then to show Q → P." One
then goes on to give a proof of each implication. It
then follows from iff.intro that a proof of P ↔ Q can
be constructed, proving the bi-implication.
-/

/-
The trick here is to do case analysis on porq
(use match ... with ... end) and to show that
a proof of R can be constructed *in either case*.
-/
theorem t8 {P Q R : Prop} (p2r : P → R) (q2r : Q → R) (porq : P ∨ Q) : R := _

/-
We suggest that you use  "let ... in" to give
names to intermediate results that you then combine
in a final expression to finish the proof.
-/
theorem t9 : ∀ (P Q: Prop), (P → Q) → ¬ (P ∧ ¬ Q) :=
_

theorem neg_elim' : ∀ (P : Prop), ¬ ¬ P → P :=
λ P,
λ nnp,
_           -- STUCK!!

theorem neg_elim : ∀ (P : Prop), (P ∨ ¬ P) → (¬ ¬ P → P):=
λ P,
    λ h,
        λ nnp,
            match h with
            | or.inl p := p
            | or.inr np := false.elim (nnp np)  -- false elimination
            end

-- nnp : (¬ P) → false
-- np : ¬ P
-- nnp np = false!

-- Let's use H to mean There is a sub-exponential time algorithm for Boolean sat.
-- ¬ H means that there's not one.
-- H ∨ ¬H

-- make Lean into a classical logic
-- axiom em : ∀ (P : Prop), P ∨ ¬ P
#check classical.em

theorem t10 : ∀ (P : Prop), P ∨ ¬ P :=
_

#check @or.inl
#check @or.inr

theorem t11 : ∀ (P Q : Prop), ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
λ P Q,
    iff.intro
        (λ not_porq,
            match (classical.em P) with
            | or.inl p := false.elim (not_porq (or.inl p))
            | or.inr np := match (classical.em Q) with
                           | or.inl q := false.elim (not_porq (or.inr q))
                           | or.inr nq := and.intro np nq
                           end
            end
        )
        _

theorem t12 : ∀ (P Q : Prop), ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
_

/-
For the following exercises, we assume that there is
a type called Person and a binary relation, Likes, on
pairs of people.
-/
axiom Person : Type
axiom Likes : Person → Person → Prop

/-
Prove the following
-/
theorem t13 :
  (∃ (p : Person), ∀ (q : Person), Likes q p) →
  (∀ (p : Person), ∃ (q : Person), Likes p q) :=
λ h,
    match h with
    | exists.intro p pf :=
        λ q,
            (exists.intro p (pf q))
    end


import data.set
open set

variables {T : Type}
variables {A B C : set T}

definition powerset (xs : set T) : set (set T) := λ ys, ys ⊆ xs

-- (All easy to prove)
lemma set_ext : A ⊆ B → B ⊆ A → A = B := sorry
lemma cap_ss_left (A B : set T) : A ∩ B ⊆ A := sorry
lemma cap_ss_right (A B : set T) : A ∩ B ⊆ B := sorry
lemma ss_trans : A ⊆ B → B ⊆ C → A ⊆ C := sorry
lemma ss_cap : A ⊆ B → A ⊆ C → A ⊆ B ∩ C := sorry

--Theorem. A ∩ B ⊆ P(A) ∩ P(B) for all sets A and B.
theorem inter_ss_P : A ∩ B ∈ powerset A ∩ powerset B :=
  -- Proof. Let A and B be arbitrary sets.
  -- A ∩ B ⊆ A, so A ∩ B ∈ P(A) by the definition of powerset
  have AcapB_ss_P_A : A ∩ B ∈ powerset A, from cap_ss_left A B,
  -- Similarly, A ∩ B ∈ P(B).
  have AcapB_ss_P_B : A ∩ B ∈ powerset B, from cap_ss_right A B,
  -- Hence A ∩ B ⊆ P(A) ∩ P(B).
  show A ∩ B ∈ powerset A ∩ powerset B, from and.intro AcapB_ss_P_A AcapB_ss_P_B

theorem powerset_inter : powerset (A ∩ B) = powerset A ∩ powerset B :=
  -- Proof. Let A and B be arbitrary sets. We prove set inclusion in both directions.
  set_ext
  (-- First, to see that P(A ∩ B) ⊆ P(A) ∩ P(B),
   show powerset (A ∩ B) ⊆ powerset A ∩ powerset B, from
   -- let S be an arbitrary member of P(A ∩ B).
     assume (S : set T) (S_in_P_AcapB : S ∈ powerset (A ∩ B)),
     -- Then S ⊆ A ∩ B,
     have S_ss_AcapB : S ⊆ A ∩ B, from S_in_P_AcapB,
     -- but A ∩ B ⊆ A,
     have AcapB_ss_A : A ∩ B ⊆ A, from cap_ss_left A B,
     -- so S ⊆ A.
     have S_ss_A : S ⊆ A, from ss_trans S_ss_AcapB AcapB_ss_A,
     -- and so, by the definition of powerset, S ∈ P(A).
     have S_in_P_A : S ∈ powerset A, from S_ss_A,
     -- Similarly, S ∈ P(B),
     have S_in_P_B : S ∈ powerset B, from ss_trans S_ss_AcapB (cap_ss_right A B),
     -- so S ∈ P(A) ∩ P(B).
     show S ∈ powerset A ∩ powerset B, from and.intro S_in_P_A S_in_P_B
     -- Hence, we have shown that S ∈ P(A ∩ B) → S ∈ P(A) ∩ P(B).
     -- Since S was chosen arbitrarily, we have P(A ∩ B) ⊆ P(A) ∩ P(B)
  )
  (-- Now, to see that P(A ∩ B) ⊇ P(A)∩P(B),
   show powerset A ∩ powerset B ⊆ powerset (A ∩ B), from
   -- let S be an arbitrary member of P(A) ∩ P(B),
   assume (S : set T) (S_in_PA_cap_PB : S ∈ powerset A ∩ powerset B),
   -- so S ∈ P(A) and S ∈ P(B). Then S ⊆ A and S ⊆ B.
   have S_ss_A : S ⊆ A, from and.left S_in_PA_cap_PB,
   have S_ss_B : S ⊆ B, from and.right S_in_PA_cap_PB,
  -- But then S ⊆ A ∩ B, so S ∈ P(A ∩ B).
   show S ∈ powerset (A ∩ B), from ss_cap S_ss_A S_ss_B)


import BrownCs22.Library.Tactics

namespace Lecture04

/-

Recall from lecture:
* A *proof state* is a sequence of goals,
  each with an associated list of hypotheses.
* We can manipulate a proof state by applying *proof rules*.
  Proof rules change the goals and/or hypotheses.
* The objective of proving is to apply proof rules
  until there are no remaining goals.

* *introduction rules* tell us how to show goals of certain shapes.
* *elimination rules* tell us how to use hypotheses of certain shapes.

-/

-- let `p, q, r` be propositions.
variable (p q r : Prop)

/-

To start a proof, we write `example :` or `theorem theorem_name :`,
followed by the proposition we are trying to prove,
followed by `:= by`.

What follows after that is a list of proof rules ("tactics").
If we put the cursor at the end of any line, we see the current proof state
after applying that rule.

The lines above the `⊢` symbol are our *context*, i.e. our hypotheses.
The line after the `⊢` is our current goal.

-/

example : p → p := by
  intro hp
  assumption

/-
Here are some useful tactics, corresponding to the intro rules we've seen.

* and introduction: `split_goal`
* or introduction: `left`, `right`
* implication intro: `intro h` (you get to name the new hypothesis)
* iff intro: `split_goal`
* atom: `assumption` (if your goal matches a hypothesis)

* `sorry` proves the goal automatically, no matter what it is.
  This is cheating! :)
-/

example : p → q → p ∧ q := by
  intro hp
  intro hq
  split_goal
  { assumption } -- when we have multiple goals, we sometimes put each subproof in {...}
  { assumption }  -- but this is mainly for style!

example : p → p ∨ q := by
  intro hp
  left
  assumption

-- try it yourself:

example : q → p ∨ q := by
  sorry

example : (p ∧ q) → (p ↔ q) := by
  sorry

/-
*Elimination rules* tell us how we can use hypotheses. To start:

* and elimination: `eliminate h with h1 h2`,
  when `h : p ∧ q` is in the context. Creates new hypotheses
  `h1 : p` and `h2 : q`.
* or elimination: `eliminate h with h1 h2`,
  when `h : p ∨ q` is in the context. Creates new goals,
  one with `h1 : p` in the context and one with `h2 : q` in the context.

-/

example : p ∧ q → q ∧ p := by
  intro hpq
  eliminate hpq with hp hq
  split_goal
  . assumption
  . assumption

example : (p ∨ q) → (p ∨ r) ∨ (q ∨ r) := by
  intro hpq
  eliminate hpq with hp hq
  { left
    left
    assumption }
  { right
    left
    assumption }

/-

Using an iff is easy: `eliminate` will turn it into two "imply"s.

-/

example : (p ↔ q) → (p → q) := by
  intro h_iff
  eliminate h_iff with hpq hqp
  assumption

/-

Using an "imply" is a little trickier:
if we know `hpq : p → q`, and our goal is to prove `q`,
then `apply hpq` will change the goal to `p`.

-/

example : p → (p → q) → q := by
  intro hp
  intro hpq
  apply hpq
  assumption

/-

Alternatively, if we know `hpq : p → q` and `hp : p`,
we can "reason forward" by writing `have hq : q := hpq hp`.
This adds a new hypothesis `hq : q` without changing the goal.
This syntax is a little clunky, but we'll use it in a more general setting later.

-/

example : p → (p → q) → q ∧ p := by
  intro hp
  intro hpq
  have hq : q := hpq hp
  split_goal
  { assumption }
  { assumption }

/- Here's a little logic puzzle.

(h1) Alan likes acorns, and either Betty likes begonias or Carl likes cacti.

(h2) If Betty likes begonias, then Alan doesn’t like acorns.

(h3) If Carl likes cacti, then Betty likes begonias.

Show that these hypotheses are contradictory.

-/

variable (al_ac : Prop) -- the proposition "Alan likes acorns"
variable (betty_beg : Prop) -- the proposition "Betty likes begonias"
variable (carl_cac : Prop) -- the proposition "Carl likes cacti"

theorem these_are_contradictory
    (h1 : al_ac ∧ (betty_beg ∨ carl_cac))
    (h2 : betty_beg → ¬ al_ac)
    (h3 : carl_cac → betty_beg) :
    False := by
  eliminate h1 with h4 h5      -- from h1, we know
                               -- (h4) Alan likes acorns and
                               -- (h5) either Betty likes begonias or Carl likes cacti.
  eliminate h5 with h6 h7      -- let's reason by cases on h5.

  { have h8 : ¬ al_ac := h2 h6 -- First, suppose Betty likes begonias.
                               -- Then, by modus ponens with h2, Alan does not like acorns.
    contradiction }            -- This contradicts h4!

  { have h9 : betty_beg := h3 h7 -- Now suppose Carl likes cacti.
                                 -- By modus ponens with h3, Betty likes begonias.
    have h10 : ¬ al_ac := h2 h9  -- But now again by modus ponens with h2, Alan does not like acorns.
    contradiction }              -- Another contradiction!
                                 -- We've finished all our goals.

/-

You've just seen the missing connective.
We skipped rules for negation introduction and elimination before.

This example just showed how to *use* a negation:
if we have hypotheses `hp : p` and `hnp : ¬ p`, then
`contradiction` will prove any goal.

How to introduce a negation? This uses a technique called
"proof by contradiction", which we'll motivate in class soon.

-/

end Lecture04


import Mathlib
import PnP2023.Lec_02_03.InductiveTypes

/-!
Vectors, i.e., lists of a fixed length, can be defined in (at least) two ways. One way is as an indexed inductive type `Vec`, as we saw in lecture and is in the file `InductiveTypes.lean`.

A different definition is as a subtype `Vector` of lists consisting of those of a fixed length. This is the definition used in `mathlib` and is recalled below.

```lean
/-- `Vector α n` is the type of lists of length `n` with elements of type `α`. -/
def Vector (α : Type u) (n : ℕ) :=
  { l : List α // l.length = n }
```

In this lab, you will relate the two definitions by constructing functions that convert between the two definitions and prove that these functions are inverses of each other.
-/
universe u

/-- Convert a `Vector` to a `Vec` -/
def Vec.ofVector {α : Type u}: (n : ℕ) →  Vector α n → Vec α n
| _, _ => sorry

/-- Convert a `Vec` to a `Vector` -/
def Vec.toVector {α : Type u}: (n : ℕ) →  Vec α n → Vector α n
| _, _ => sorry

/-- Mapping a `Vec` to a `Vector` and back gives the original `Vec` -/
theorem Vec.ofVector.toVector {α : Type u} (n : ℕ) (v : Vec α n) :
  Vec.ofVector n (Vec.toVector n v) = v := sorry

/-- Mapping a `Vector` to a `Vec` and back gives the original `Vector` -/
theorem Vec.toVector.ofVector {α : Type u} (n : ℕ) (v : Vector α n) :
  Vec.toVector n (Vec.ofVector n v) = v := sorry


/-
This exercise will guide you through a proof that there are
infinitely many primes.
NOTE: You will need to install mathlib for this.

A good way to confirm if mathlib is installed is to see if
the following imports work.

This tells Lean to import facts about prime numbers.
-/
import tactic
import data.nat.prime

open nat

/-
We shall break up the proof into a bunch of lemmas
and use these lemmas in the final proof.

The first lemma `not_dvd_succ` says that if a ≠ 1 divides b,
then it does not divide b+1.

Lemmas/Hints which may be useful for this exercise:
  * nat.dvd_add_right
  * nat.dvd_one
  * Try to use the rewrite tactic.

(Of course, this is not the only way to prove this exercise.
Look at the file data.nat.prime to see if you can find other
exercises which may help you.

You can browse through
https://leanprover-community.github.io/mathlib_docs/data/nat/prime.html
for more facts.)
-/

lemma not_dvd_succ {a b : ℕ} (ha : a ≠ 1) (hab : a ∣ b) :
 ¬ (a ∣ b + 1) :=
begin
  sorry,
end

/-
 Before we move on to the main theorem,
 it is good to understand how the `linarith` tactic works.

`linarith` is a high-powered tactic which can be used to
 prove linear (in)equalities.

 Read : https://leanprover-community.github.io/mathlib_docs/tactics.html#linarith
-/

example (x : ℕ) (hx₁ : x > 15) (hx₂ : x ≤ 20) : (x < 25) :=
begin
  linarith,
end

example (x y z : ℕ) (h₁ : x > y + 1) (h₂ : y > 2*z + 2): (x > z) :=
begin
  linarith,
end

/- Shorter proofs can simply use the `by` keyword. -/
example (x y z : ℚ) (h1 : 2*x  < 3*y) (h2 : -4*x + 2*z < 0)
        (h3 : 12*y - 4* z < 0)  : false := by linarith

example(a b d : ℕ) (h: 2*a ≤ 3*b)(h': 1≤ a)(h'': d = 2):
d + a ≤ 5*b := by linarith

/-
Let us try to recall how the proof works:

The factorial n! of a positive integer n is divisible by
very integer from 2 to n, as it is the product of all of them.

Hence, n! + 1 is not divisible by any of the integers from 2
to n, inclusive (it gives a remainder of 1 when divided by
each).

Hence n! + 1 is either prime or divisible by a prime larger
than n.
In either case, for every positive integer n, there is at
least one prime bigger than n.
The conclusion is that the number of primes is infinite.

We break up the above sketch into little steps in the proof
below.

-/

theorem inf_many_primes : ∀ n : ℕ, ∃ p, p ≥ n ∧ prime p :=
begin
  -- assume an arbitrary n
  assume n,
  -- define N = n! + 1
  let N := n.factorial + 1,
  /- Prove first that N ≥ 2.
     The tactic `linarith` and lemmas `factorial_le`
     and `zero_le` may be helpful.-/
  have Nge2 : N ≥ 2,
  {
    sorry,
  },
  /-
  Next we prove that the numbers from 2 to n divide n!.
   The tactic `linarith` and lemmas `dvd_factorial`
   may be helpful.
  -/
  have dvd₁ : ∀ k n : ℕ, 2 ≤ k ∧ k ≤ n → k ∣ n.factorial,
  {
    sorry,
  },
  /-
  Next we prove that the numbers from 2 to n do NOT divide
  n! + 1. Use the previous lemmas we proved: `dvd₁` and
  `not_dvd_succ`.
  -/
  have dvd₂ : ∀ k n : ℕ, 2 ≤ k ∧ k ≤ n → ¬ (k ∣ n.factorial + 1),
  {
    sorry,
  },
  -- clear some hypothesis to avoid clutter
  clear dvd₁,
  /-
  The number N := n! + 1 is either prime or not.
  Note for nerds: This tactic uses *classical* logic.
  It is good to keep this in mind even if you don't care
  about it.
  -/
  by_cases (prime N),
  {
    /-
    in the case where N is prime, we can prove the main goal
    easily.
    Try using `self_le_factorial` and `linarith`.
    -/
    use N,
    sorry,
  },
  /-
  In the case where N is not prime, we first add the fact
  that there is a prime which divides it.
  This is the fact `exists_prime_and_dvd`.
  -/
  have HN := exists_prime_and_dvd Nge2,
  /- Here is another tactic which makes breaking up
    hypotheses easier. -/
  rcases HN with ⟨p,⟨Hp₁,Hp₂⟩⟩,
  -- Use p now to finish the proof.
  use p,
  sorry,
end

/-

Final words of advice:
* Keep the tactic cheatsheet open next to you if you are lost:
https://leanprover-community.github.io//img/lean-tactics.pdf
* Search for lemmas in mathlib.
* Always try to get to the goal on paper first. Once that is
  clear, then move onto formalizing.
-/


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

import tactic -- imports all the Lean tactics

/-!

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

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

## Tactics

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

-/

open set

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

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

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

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

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


-- This import gives us a working copy of the real numbers ℝ,
-- and functions such as abs : ℝ → ℝ
import data.real.basic

-- This next import gives us several tactics of use to mathematicians:
-- (1) norm_num [to prove basic facts about reals like 2+2 = 4]
-- (2) ring [to prove basic algebra identities like (a+b)^2 = a^2+2ab+b^2]
-- (3) linarith [to prove basic inequalities like x > 0 -> x/2 > 0]
import tactic.linarith

-- These lines switch Lean into "maths proof mode" -- don't worry about them.
-- Basically they tell Lean to use the axiom of choice and the
-- law of the excluded middle, two standard maths facts.
noncomputable theory
local attribute [instance, priority 0] classical.prop_decidable

-- the maths starts here.

-- We introduce the usual mathematical notation for absolute value
local notation `|` x `|` := abs x

theorem Q1a (x y : ℝ) : | x + y | ≤ | x | + | y | :=
begin
  -- Lean's definition of abs is abs x = max (x, -x)
  -- [or max x (-x), as the computer scientists would write it]
  unfold abs,
  -- lean's definition of max a b is "if a<=b then b else a"
  unfold max,
  -- We now have a complicated statement with three "if"s in.
  split_ifs,
  -- We now have 2^3=8 goals corresponding to all the possibilities
  -- x>=0 or x<0, y>=0 or y<0, (x+y)>=0 or (x+y)<0.
  repeat {linarith},
  -- all of them are easily solvable using the linarith tactic.
end

-- We can solve the remaining parts using part (a).
theorem Q1b (x y : ℝ) : |x + y| ≥ |x| - |y| :=
begin
  -- Apply Q1a to x+y and -y, then follow your nose.
  have h := Q1a (x + y) (-y),
  simp at h,
  linarith,
end

theorem Q1c (x y : ℝ) : |x + y| ≥ |y| - |x| :=
begin
  -- Apply Q1a to x+y and -x, then follow your nose.
  have h := Q1a (x + y) (-x),
  simp at h,
  linarith,
end

theorem Q1d (x y : ℝ) : |x - y| ≥ | |x| - |y| | :=
begin
  -- Lean prefers ≤ to ≥
  show _ ≤ _,
  -- for this one we need to apply the result that |X| ≤ B ↔ -B ≤ X and X ≤ B
  rw abs_le,
  -- Now we have two goals:
  -- first -|x - y| ≤ |x| - |y|
  -- and second |x| - |y| ≤ |x - y|.
  -- So we need to split.
  split,
  { -- -|x - y| ≤ |x| - |y|
    have h := Q1a (x - y) (-x),
    simp at *,
    linarith },
  { -- |x| - |y| ≤ |x - y|
    have h := Q1a (x - y) y,
    simp at *,
    linarith}
end

theorem Q1e (x y : ℝ) : |x| ≤ |y| + |x - y| :=
begin
  have h := Q1a y (x - y),
  simp * at *,
end

theorem Q1f (x y : ℝ) : |x| ≥ |y| - |x - y| :=
begin
  have h := Q1a (x - y) (-x),
  simp * at *,
end

theorem Q1g (x y z : ℝ) : |x - y| ≤ |x - z| + |y - z| :=
begin
  have h := Q1a (x - z) (z - y),
  -- Lean needs more hints with this one.
  -- First let's change that y - z into z - y,
  rw ←abs_neg (y - z),
  -- now use automation
  simp * at *,
end


import data.nat.gcd
import algebra.big_operators
import data.int.gcd
import algebra.euclidean_domain
import data.zmod.basic

def divides (d n: ℤ):=  ∃l:ℤ, n = d*l
def is_congruent_mod_n (a b n: ℤ) := divides n (b-a)
def relatively_prime(a b:ℤ) :=  nat.coprime (int.nat_abs a) (int.nat_abs b)

theorem relatively_prime_sum_to_one (a b : ℤ) (h : relatively_prime a b) :
∃ m n: ℤ, m* a + n * b = 1 :=
begin
  existsi [int.sign a * nat.gcd_a (int.nat_abs a) (int.nat_abs b),
           int.sign b * nat.gcd_b (int.nat_abs a) (int.nat_abs b)],
  rw [mul_right_comm, mul_comm _ a, int.mul_sign,
      mul_right_comm, mul_comm _ b, int.mul_sign,
      ← nat.gcd_eq_gcd_ab,
      show nat.gcd (int.nat_abs a) (int.nat_abs b) = 1, from h,
      int.coe_nat_one],
end

theorem divides_in_product (a b c: ℤ): a*b = c ↔ divides a c:=
  begin
    split,
    {
      intros,
      rw divides,
      cases a_1 with ell,
      exact Exists.intro b rfl,
    },
    {
      intros,
      cases a_1 with ell,
      by library_search,
      --exact Exists.intro ell (eq.symm a_1_h),
    },
  end

--Generalized Euclid Lemma --
theorem divides_product_coprime_and_not_coprime (a b n:ℤ) (c: divides n (a*b))
(d: relatively_prime a n):
divides n b:=
begin
  -- cases breaks down existential qualifiers
  cases c with l k,
  cases relatively_prime_sum_to_one a n d with m h,
  cases h with q manb_eq_one,

  -- multiplies both sides by b
  have := congr_arg (has_mul.mul b) manb_eq_one,
  rw [mul_one] at this,
  -- this : b * (m * a + q * n) = b

  -- order-changing
  rw [mul_add] at this,
  rw [mul_comm] at this,
  rw [mul_assoc] at this,
  rw k at this,
  rw [mul_comm] at this,
  rw [mul_assoc] at this,
  rw [add_comm] at this,
  rw [mul_comm] at this,
  rw [mul_assoc] at this,
  rw [mul_left_comm] at this,
  rw <- mul_add at this,

  rw divides_in_product n (q*b+l*m) b at this,
  apply this,

end

theorem cancel_out_mod_n (a b c n: ℤ) (p: is_congruent_mod_n (a*b) (a*c) n)
(q: relatively_prime a n): is_congruent_mod_n b c n :=

begin
  cases p with j k,
  rw <-mul_sub at k,
  rw eq_comm at k,
  rw divides_in_product n j (a*(c-b)) at k,
  apply divides_product_coprime_and_not_coprime a (c-b) n,
  {
    apply k,
  },
  {
    apply q,
  },

end


section propositional

variables P Q R : Prop

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

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

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro nnp,
  by_cases p: P,
    --case P
    exact p,
    --case ¬P
    have ct := nnp p,
    contradiction,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  have fst := doubleneg_elim P,
  have snd := doubleneg_intro P,
  split,
    --part ¬¬P → P
    exact fst,
    --part P → ¬¬P
    exact snd,
end

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

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro disj,
  cases disj,
    -- case P
    right,
      exact disj,
    -- case Q
    left,
      exact disj,
end

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

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro disj,
  intro p,
  cases disj,
    --case ¬P
    have contra := disj p,
    contradiction,
    --case Q
    exact disj,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro disj,
  intro np,
  cases disj,
    --case P
    have contra := np disj,
    contradiction,
    --case Q
    exact disj,
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro impl,
  intro nq,
  intro p,
  have q := impl p,
  have contra := nq q,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro impl,
  intro p,
  by_cases q: Q,
    --case Q
    exact q,
    --case ¬Q
    have np := impl q,
    have contra := np p,
    contradiction,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  have fst := impl_as_contrapositive P Q,
  have snd := impl_as_contrapositive_converse P Q,
  split,
    --part (P → Q) → (¬Q → ¬P)
    exact fst,
    --part (¬Q → ¬P) → (P → Q)
    exact snd,
end

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro n_lem,
  have lem: P ∨ ¬P,
    right,
    intro p,
    have disj: P ∨ ¬P,
      left,
      exact p,
    have ct := n_lem disj,
    contradiction,
  have ct := n_lem lem,
  contradiction,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro pierce,
  intro np,
  have impl: P → Q,
    intro p,
    contradiction,
  have p := pierce impl,
  have ct := np p,
  contradiction,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro disj,
  intro n_conj,
  cases n_conj,
  cases disj,
    --case P
    have contra := n_conj_left disj,
    contradiction,
    --case Q
    have contra := n_conj_right disj,
    contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro conj,
  intro neg_disj,
  cases conj,
  cases neg_disj,
    --case ¬P
    have ct := neg_disj conj_left,
    contradiction,
    --case ¬Q
    have ct := neg_disj conj_right,
    contradiction,
end

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

--Adicionado Pelo Ian--
theorem disj_impl :
  (∀ p q: Prop, p → p ∨ q) :=
begin
  intro p,
  intro q,
  intro p_impl,
  left,
  exact p_impl,
end
----------------

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro neg_disj,
  split,
    --part ¬P
    intro p,
    have impl := disj_impl P Q,
    have disj := impl p,
    have ct := neg_disj disj,
    contradiction,
    --part ¬Q
    intro q,
    have impl := disj_impl Q P,
    have wr_disj := impl q,
    have disj_cm := disj_comm Q P,
    have rght_disj := disj_cm wr_disj,
    have ct := neg_disj rght_disj,
    contradiction,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro conj_negs,
  intro disj,
  cases conj_negs with np nq,
  cases disj with p q,
    --case P
    have ct := np p,
    contradiction,
    --case Q
    have ct := nq q,
    contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_conj,
  by_cases P,
    --case P
    left,
    intro q,
    have conj: P∧Q,
      split,
      --part P
      exact h,
      --part Q
      exact q,
    have ct := n_conj conj,
    contradiction,

    --case ¬P
    right,
    exact h,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro disj_negs,
  intro conj,
  cases conj with p q,
  cases disj_negs with nq np,
    --case ¬Q
    have ct := nq q,
    contradiction,
    --case ¬P
    have ct := np p,
    contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  have dmg_conj := demorgan_conj P Q,
  have dmg_conj_cnv := demorgan_conj_converse P Q,
  split,
    --part ¬(P∧Q) → (¬Q ∨ ¬P)
    exact dmg_conj,
    --part (¬Q ∨ ¬P) → ¬(P ∧ Q)
    exact dmg_conj_cnv,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  have dmg_disj := demorgan_disj P Q,
  have dmg_disj_con := demorgan_disj_converse P Q,
  split,
    --part ¬(P ∨ Q) → (¬P ∧ ¬Q)
    exact dmg_disj,
    --part (¬P ∧ ¬Q) → ¬(P ∨ Q)
    exact dmg_disj_con,
end

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

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

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro disj_conj,
  cases disj_conj with conj_pq conj_pr,
    --case P∧Q
    cases conj_pq with p q,
    split,
      --part P
      exact p,
      --part Q∨R
      left,
        exact q,
    --case P∧R
    cases conj_pr with p r,
    split,
      --part P
      exact p,
      --part Q∨R
      right,
        exact r,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro distr_disj,
  split,
    --part P∨Q
    cases distr_disj with p conj_qr,
      --case P
      left,
        exact p,
      --case Q∧R
      right,
        cases conj_qr with q r,
        exact q,

    --part P∨R
    cases distr_disj with p conj_qr,
      --case P
      left,
        exact p,
      --case Q∧R
      right,
        cases conj_qr with q r,
        exact r,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro distr_conj,
  cases distr_conj with disj_pq disj_pr,
  cases disj_pq with p q,
    --case P
    left,
      exact p,
    --case Q
    cases disj_pr with p r,
      --case P
      left,
        exact p,
      --case R
      right,
        split,
          --part Q
          exact q,
          --part R
          exact r,
end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro cur,
  intro p,
  intro q,
  have conj : P ∧ Q,
    split,
      --part P
      exact p,
      --part Q
      exact q,

  have r := cur conj,
  exact r,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro cur,
  intro conj,
  cases conj with p q,
  have impl := cur p,
  have r := impl q,
  exact r,
end

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

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

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

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

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

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

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

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  have left := weaken_conj_left P P,
  split,
    --part P∧P → P
    exact left,
    --part P → P∧P
    intro p,
    split,
      --part P
      exact p,
      --part P
      exact p,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
    --part P∨P → P
    intro disj,
    cases disj with p p,
      --case P
      exact p,
      --case P
      exact p,
    --part P → P∨P
    have wk_disj := weaken_disj_left P P,
    exact wk_disj,
end

end propositional

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

section predicate

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

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

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro not_exists,
  intro x,
  intro px,
  have exist: ∃ x, P x,
    existsi x,
    exact px,
  have ct := not_exists exist,
  contradiction,
end

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

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  have contrapos: ¬(∃x, ¬P x) → (∀x, P x),
    intros neg_exist x,
    by_contradiction npx,
    have exist: ∃x, ¬P x,
      existsi x,
      exact npx,
    exact neg_exist exist,

  have impl := contrapositive_law (¬(∃x, ¬P x)) (∀x, P x),
  cases impl with l r,
  have contrapos_conv := l contrapos,

  intro for_all,
  have neg_neg_exist := contrapos_conv for_all,
  have db_neg := doubleneg_elim (∃x, ¬P x),
  exact db_neg neg_neg_exist,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro exist,
  intro for_all,
  cases exist with x npx,
  have px := for_all x,
  have ct := npx px,
  contradiction,
end

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

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  have dmg_exist := demorgan_exists U P,
  have dmg_exist_cn := demorgan_exists_converse U P,
  split,
    --part ¬(∃x, P x) → (∀x, ¬P x)
    exact dmg_exist,
    --part (∀x, ¬P x) → ¬(∃x, P x)
    exact dmg_exist_cn,
end

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

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

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

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro n_exist,
  intro x,

  --Disapointing magic--
  by_cases P x,
    --case P x
    exact h,

    --case ¬P x
    have exist: ∃(x: U), ¬P x,
    existsi x,
    exact h,
    have ct := n_exist exist,
    contradiction,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro neg_forall,
  by_contradiction neg_exist,
  have dmg_exists := demorgan_exists U P,
  have for_all := dmg_exists neg_exist,
  exact neg_forall for_all,
end

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

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

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

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro exists_conj,
  cases exists_conj with x conj_x,
  cases conj_x with px qx,
  split,
    --part (∃x, P x)
    existsi x,
    exact px,
    --part (∃x, Q x)
    existsi x,
    exact qx,
end

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

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro exists_disj,
  cases exists_disj with exists_px exists_qx,
    --case (∃x, P x)
    cases exists_px with x px,
    existsi x,
    left,
    exact px,
    --case (∃x, Q x)
    cases exists_qx with x qx,
    existsi x,
    right,
    exact qx,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro forall_conj,
  split,
    --part (∀x, P x)
    intro x,
    have conj := forall_conj x,
    exact conj.left,
    --part (∀x, Q x)
    intro x,
    have conj := forall_conj x,
    exact conj.right,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro forall_conj,
  cases forall_conj with forall_px forall_qx,
  intro x,
  split,
    --part P x
    exact forall_px x,
    --part Q x
    exact forall_qx x,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro forall_disj,
  intro x,
  cases forall_disj with forall_px forall_qx,
    --case (∀x, P x)
    left,
    exact forall_px x,
    --case (∀x, Q x)
    right,
    exact forall_qx x,
end

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

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

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

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

end predicate


import ..lectures.love03_forward_proofs_demo

/-! # LoVe Homework 4: Functional Programming

Homework must be done individually. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1 (6 points): Reverse of a List

Recall the `reverse` operation and the `reverse_append` and `reverse_reverse`
lemmas from the demo of lecture 3. -/

#check reverse
#check reverse_append
#check reverse_append₂
#check reverse_reverse

/-! 1.1 (3 points). Prove the following distributive property using the
calculational style for the induction step. -/

lemma reverse_append₃ {α : Type} :
  ∀xs ys : list α, reverse (xs ++ ys) = reverse ys ++ reverse xs :=
sorry

/-! 1.2 (3 points). Prove the induction step in the proof below using the
calculational style, following this proof sketch:

      reverse (reverse (x :: xs))
    =     { by definition of `reverse` }
      reverse (reverse xs ++ [x])
    =     { using the lemma `reverse_append` }
      reverse [x] ++ reverse (reverse xs)
    =     { by the induction hypothesis }
      reverse [x] ++ xs
    =     { by definition of `++` and `reverse` }
      [x] ++ xs
    =     { by computation }
      x :: xs -/

lemma reverse_reverse₂ {α : Type} :
  ∀xs : list α, reverse (reverse xs) = xs
| []        := by refl
| (x :: xs) :=
sorry

/-! ## Question 2 (3 points): Gauss's Summation Formula -/

-- `sum_upto f n = f 0 + f 1 + ⋯ + f n`
def sum_upto (f : ℕ → ℕ) : ℕ → ℕ
| 0       := f 0
| (m + 1) := sum_upto m + f (m + 1)

/-! 3.1 (2 point). Prove the following lemma, discovered by Carl Friedrich Gauss
as a pupil.

Hint: The `mul_add` and `add_mul` lemmas might be useful to reason about
multiplication. -/

#check mul_add
#check add_mul

lemma sum_upto_eq :
  ∀m : ℕ, 2 * sum_upto (λi, i) m = m * (m + 1) :=
sorry

/-! 3.2 (1 point). Prove the following property of `sum_upto`. -/

lemma sum_upto_mul (f g : ℕ → ℕ) :
  ∀n : ℕ, sum_upto (λi, f i + g i) n = sum_upto f n + sum_upto g n :=
sorry

/-! ## Question 3 (3 points): Structures and type classes

3.1 (1 point). Using the `structure` command, define the type ℝ³,
three-dimensional Euclidean space. (A point in ℝ³ has x, y, and z coordinates.)
-/
#check ℝ

structure r3 :=
sorry

/-!
3.2 (1 point). Define an addition function on ℝ³.
-/

def r3.add (p q : r3) : r3 :=
sorry

/-!
3.3 (1 point). Write an instance that shows ℝ³ instantiates the `has_add` type class.
-/

#print has_add

end LoVe