/-

HOMEWORK: Read, complete, submit.

We've seen that equality is reflexive.
That is, everything is equal to itself.

It is also symmetric in the sense that
if any value, a, is equal to some other
value, b, i.e., if a = b, then b is also
equal to a, i.e., b = a.

What this means is we have an inference
rule that both expresses the symmetric
property of equality and allows us to
compute a proof of b = a from any proof
of a = b, no matter what a and b are (no
matter what type, T, a and b have, and
no matter what values they have of this
type).
-/

/-
  (T: Type) (a b: T) (ab: a = b)
  ------------------------------ (eq.symm)
           ba: b = a

-/

-- Let's see it in action in five lines
def a := 1
def b := 2 - 1
lemma ab : a = b := rfl
#check ab           -- a proof of a = b
#check (eq.symm ab) -- a proof of b = a!

/-
This is a big success. We understand not
only the property of being symmetric but
that we can use symmetry to derive new
proofs from proofs that we already have.
In fact, eq.symm is a program that does
just this derivation, as we see here!
-/

/-
Finally we come to the notion that
equality is also transitive. That means
that for any values, a, b, and c, if
a = b, and if b = c, then it must be
that consequently a = c as well.

  {T : Type},
  { a b c: T },
  ab: a = b,
  bc: b = c
  ------------ (eq.trans)
   ac: a = c

That is, if given proofs of a = b and
b = c, eq.symm constructs and returns
a proof of a = c.

Let's see it in action. We've already
got variables a and b to work with. We
need one more, c.
-/

def c := 1

/-
We've also already got a proof of a = b.
It's easy to generate one of b = c.
-/

lemma bc : b = c := rfl

/-
And now we can apply eq.trans to these
two premise-proofs and it will construct
and return a proof of the conclusion. The
expression that applies eq.trans to these
two proofs is (eq.trans ab bc). Now for
the fun part!
-/

#check eq.trans ab bc

/-
EXERCISE: First write a textual inference
rule, let's call it eq_snart. It says that
if T is any type; if a, b, and c are values
of this type, T; and you are given proofs
of a = b and c = b then you can  derive a
proof of a = c.
-/

/-
EXERCISE: Now "prove" that this rule is
valid by implementing it as a program
that, when given any argument values of
the specified types, returns a proof of
the specified type (of the conclusion).

Hint: Use eq.trans to construct the proof
of a = c. It's first argument will be ab,
the proof that a = b. It's second argument
has to be a proof of b = c for it to work
to derive a proof of a = c; but all we've
got is a proof of c = b (in the reverse
order). How can we pass a second argument
of type b = c to eq.trans, so that it can
do its job, when we have at hand is a proof
of c = b. Now a major hint: we already
have a way to construct a proof of b = c
from a proof of c = b. Just use it.

Ignore the error message in the following
incomplete code. The problem is simply
that the definition is incomplete, due
to the underscore placeholder. Replace
the underscore with your answer. Leave
parenthesis around your expression so
that it gets evaluated as its own term.
-/

def eq_snart    { T : Type}
                { a b c: T }
                (ab: a = b)
                (cb: c = b) :=
    eq.trans
        ab
        (_)

/-
EXERCISE: Use lean to implement a new
rule that that, from a proof of c = b
and a proof of b = a, derives a proof
of a = c. Call the proof eq_snart'
(why not, it sounds funny).
-/

/-
EXERCISE: Use eq_snart rather than eq.trans directly to prove a = c,
given proofs of a = b and c = b.
-/

lemma cb : c = b := rfl
#check cb
theorem aeqc : a = c := eq_snart _ _

/-
In general, there are many ways to prove
a given theorem. Each distinct proof is
nevertheless an inhabitant of the type of
the proposition that it proves, and each
suffices as evidence to justify a truth
judgment for the proposition. In cases
where one's aim is simply to prove a
proposition, the particular proof object
that is used doesn't matter. We say that
the particular proof is "irrelevant."
-/

/-
SUMMARY: In this section (1) you first
recalled  that the equality relation is
reflexive, symmetric, and transitive.
(2) You saw that in Lean, these are not
just abstract ideas; there are also
inference rules that you can apply to
to arguments of the right types to
build proofs of new propositions. (3)
You also saw that you can prove your own
inference rules by writing programs that
implement them! Such programs can use
already accepted inference rules (such
as eq.refl, eq.symm, eq.trans) in their
implementations. Thereafter, the new
rules are as good as the old, and can
then also be used to construct proofs
that might be needed.
-/


/-
THe exam is graded out of 100 points. The regular
questions provide opportunity earn 105. There is
an extra credit worth 5 points [all or none]. So
the maximum possible grade is 110/100.
-/

/-
#1 [10 points]

[Grading: 2 free points + 1 for each correct answer]

Complete the following definitions to give examples of
literal values of specified types. Use lambda expressions to complete the questions involving function types. Make
functions (lambda expressions) simple: we don't care what
the functions, do, just that they are of the right types.
-/

def n : ℕ := 0
def s : string := ""
def b : bool := tt
def f1 : ℕ → bool := λ n, tt
def f2 : (ℕ → ℕ) → bool := λ f, tt
def f3 : (ℕ → ℕ) → (ℕ → ℕ) := λ f, f
def t1 : Type := nat
def t2 : Type → Type := λ t, t

/-
#2 [10 points]

[Grading: 3 for zero case; 7 for succ case]

Complete the following recursive function definition
so that the function computes the sum of the natural
numbers from 0 to (and including) a given value, n.
-/

def sumto : ℕ → ℕ
| 0 := 0
| (nat.succ n') := (nat.succ n') + sumto n'

/-
#3. [5 points]

[Grading 3 points for spec, 2 points for the impl; -1 for
an answer that is confused but contains the right idea]

We have seen that we can write function *specifications*
in the language of predicate logic, and specifically in
the language of pure functional programming. We also know
we can, and generally do, write *implementations* in the
language of imperative programming (e.g., in Python or in
Java). Complete the following sentence by filling in the
blanks to explain the esssential tradeoff between function
specifications, in the langugae of predicate logic, and
function implementations written in imperative programming
languages, respectively.

Specifications are generally _______ [clear, understandable,
abstract] but less ________ [efficient] while implementations
are generally the opposite: less [clear, understandable,
abstract] ________ but more _________ [efficient].

-/

/-
# 4. [10 points]

[Grading: 5 then 3 then 2 for each correct answer]

Natural languages, such as English and Mandarin, are very
powerful, but they have some fundamental weaknesses when it
comes to writing and verifying precise specifications and
claims about properties of algorithms and programs. It is
for this reason that computer scientists often prefer to
express such things using mathematical logic instead
of natural language.

Name three fundamental weaknesses of natural language when
it comes to carrying out such tasks. You may given one-word
answers if you wish.

A. __________ [ambiguity]
B. __________ [inconsistency]
C. __________ [uncheckability]

-/

/-
#5. [10 points]

[5 for correct equality, 5 for the ∀]

What logical proposition expresses the claim that a given
implementation, I, of a function of type ℕ → ℕ, is correct
with respect to a specification, S, of the same function?

Answer: [∀ (n : ℕ), I n = S n. Answer can be given in English
but must include concept that I n = S n *for all* values of n.]

-/

/-
#6. [10 points]

[Grading: 5 points for each correct answer]

What Boolean functions do the following definitions define?
-/

def mystery1 : bool → bool → bool
| tt tt := ff
| ff ff := ff
| _ _ := tt

/-
Answer: [xor, exclusive or]
-/

def mystery2 : bool → bool → bool
| tt ff := ff
| _ _ := tt

/-
Answer: [implies]
-/

/-
#7. [10 points]

[Grading: 10 points for right answer, no partial credit]

Define a function that takes a string, s, and a natural
number, n, and that returns value of type (list string)
in which s is repeated n times.
-/

def repeat : string → ℕ → list string
| s 0 := list.nil
| s (nat.succ n') := list.cons s (repeat s n')

/-
#8. [10 points]

[Grading: 3 point zero case, 7 points for succ case]

Definea a polymorphic function that takes a type, α, a
value, s : α, and a natural number, n, and that returns
a list in which the value, a, is repeated n times. Make
the type argument to this function implicit.
-/

def poly_repeat {α : Type} : α → ℕ → list α
| a 0 := list.nil
| a (nat.succ n') := list.cons a (poly_repeat a n')

/-
#9. [10 points]

[6 for data type, 4 for the function definition]

Define a data type, an enumerated type, friend_or_foe,
with just two terms, one called friend, one called foe.
Then define a function called eval that takes two terms,
F1 and F2, of this type and returns a term of this type,
where the function implements the following table:

F1      F2      result
friend  friend  friend
friend  foe     foe
foe     friend  foe
foe     foe     friend
-/

inductive friend_or_foe : Type
| friend
| foe

open friend_or_foe

def eval : friend_or_foe → friend_or_foe → friend_or_foe
| friend friend := friend
| friend foe := foe
| foe friend := foe
| foe foe := friend

/-
#10. [10 points]

We studied the higher-order function, map. In particular,
we implemented a version of it, which we called mmap, for functions of type ℕ → ℕ, and for lists of type (list ℕ).
The function is reproduced next for your reference. Read
and recall how the function works, then continue on to the
questions that follow.
-/

def mmap : (ℕ → ℕ) → list ℕ → list ℕ
| f [] := []
| f (list.cons h t) := list.cons (f h) (mmap f t)

-- An example application of this function
#eval mmap (λ n, n + 1) [1, 2, 3, 4, 5]

/-

[Grading: 5 points, 1 for base case, 4 for recursive]

A. Write a polymorphic version of this function, called
pmap, that takes (1) two type arguments, α and β, (2) a
function of type α → β, and (3) a list of values of type
α, and that returns the list of values obtained by
applying the given function to each value in the given
list. Make α and β implicit arguments.
-/

def pmap {α β : Type} : (α → β) → list α → list β
| f [] := []
| f (list.cons h t) := list.cons (f h) (pmap f t)

/-
B.

[Grading: 5 points, all or nothing]

Use #eval to evaluate an application of pmap to a function
of type ℕ to bool and a non-empty list of natural numbers. Use a lambda abstraction to give the function
argument. It does not matter to us what value the
function returns.
-/

#eval pmap (λ n : ℕ, tt) [0,1,2,3]

/-
#11. [10 points]

[ Grading: 5 for data type, 5 for 3 correct functions]

Define a data type, prod3_nat, with one constructor,
triple, that takes three natural numbers as arguments,
yielding a term of type prod3_nat. Then write three
"projection functions", prod3_nat_fst, prod3_nat_snd,
and prod3_nat_thd, each of which takes a prod3_nat value
and returns its corresponding component element. Hint:
look to see how we defined the prod type, its pair
constructor, and its two projection functions.
-/

inductive prod3_nat : Type
| triple : ℕ → ℕ → ℕ → prod3_nat

def prod3_nat_fst : prod3_nat → ℕ
| (prod3_nat.triple f s t) := f

def prod3_nat_snd : prod3_nat → ℕ
| (prod3_nat.triple f s t) := s

def prod3_nat_thd : prod3_nat → ℕ
| (prod3_nat.triple f s t) := t

/-
Extra credit. Define prod3 as a version
of prod3_nat that is polymorphic in each of
its three components; define polymorphic
projection functions; and then use them to
define a function, rotate_right, that takes
a triple, (a, b, c), and returns the triple
(c, a, b).  (Call your type arguments α,
β, and γ - alpha, beta, and gamma).
-/

-- Answer here

inductive prod3 (α β γ : Type) : Type
| triple : α → β → γ → prod3

def prod3_fst {α β γ : Type}: prod3 α β γ → α
| (prod3.triple f s t) := f

def prod3_snd {α β γ : Type} : prod3 α β γ → β
| (prod3.triple f s t) := s

def prod3_thd {α β γ : Type} : prod3 α β γ → γ
| (prod3.triple f s t) := t

def rotate_right {α β γ : Type} : prod3 α β γ → prod3 γ α β
| (prod3.triple a b c) := prod3.triple c a b


import tactic
import data.nat.basic
import data.nat.prime

noncomputable theory
open_locale classical

open nat

-- 4. Sqrt 2 is irrational

-- 4.1. Implicit arguments

/-
Consider the following theorem which says that the smallest non-trivial factor of a natural
number greater than 1 is a prime number.

 `min_fac_prime : n ≠ 1 → n.min_fac.prime`

It needs only one argument, namely a term of type `n ≠ 1`. But we have not told Lean what `n` is!
That’s because if we pass a term, say `hp : 2 ≠ 1` to `min_fac_prime` then from `hp` Lean can infer
that `n = 2`. `n` is called an implicit argument.
An argument is made implicit by using curly brackets { and } instead of the usual ( and ) while
defining the theorem.

  `theorem min_fac_prime {n : ℕ} (hne1 : n ≠ 1) : n.min_fac.prime := ...`

Sometimes the notation is ambiguous and Lean is unable to infer the implicit arguments.
In such a case, you can force all the arguments to become explicit by putting an `@` symbol in from
on the theorem. For example,

  `@min_fac_prime : (n : ℕ) → n ≠ 1 → n.min_fac.prime`

Use this sparingly as this makes the proof very hard to read and debug
-/

-- 4.2. The two haves

/-
We have seen two slightly different variants of the have tactic.

  `have hq := ...`
  `have hq : ...`

In the first case, we are defining `hq` to be the term on the right hand side.
In the second case, we are saying that we do not know what the term hq is but we know it’s type.

Let’s consider the example of `min_fac_prime` again. Suppose we want to conclude that the smallest
factor of 10 is a prime. We will need a term of type `10.min_fac.prime`. If this is the target,
we can use apply `min_fac_prime`,. If not, we need a proof of `10 ≠ 1` to provide as input to
`min_fac_prime`. For this we’ll use

  `have ten_ne_zero : 10 ≠ 1,`

which will open up a goal with target `10 ≠ 1`. If on the other hand, you have another hypothesis,
say `f : P →  (10 ≠ 1)` and a term `hp : P`, then

  `have ten_ne_zero := f(hp)`

will immediately create a term of type `10 ≠ 1`. More generally, remember that

    “:=” stands for definition. x := ... means that x is defined to be the right hand side.

    “:” is a way of specifying type. x : ... means that the type of x is the right hand side.

    “=” is only ever used in propositions and has nothing to do with terms or types.

-/

-- 4.3. Sqrt(2) is irrational

/-
We will show that there do not exist non-zero natural numbers `m` and `n` such that

  `2 * m ^ 2 = n ^ 2  -- (*)`

The crux of the proof is very easy. You simply have to start with the assumption that `m` and `n`
are coprime without any loss of generality and derive a contradiction.
But proving that without a loss of generality is a valid argument requires quite a bit of effort.
This proof is broken down into several parts. The first two parts prove (*) assuming that `m` and `n`
are coprime. The rest of the parts prove the without loss of generality part.

For this problem you’ll need the following definitions.

        `m.gcd n : ℕ is the gcd of m and n.`

        `m.coprime n is defined to be the proposition m.gcd n = 1.`

The descriptions of the library theorems that you’ll be needing are included as comments. Have fun!
-/

-- 4.3.1. Lemmas for proving (*) assuming m and n are coprime.

/-
prime.dvd_of_dvd_pow : ∀ {p m n : ℕ}, p.prime → p ∣ m ^ n → p ∣ m
-/
lemma two_dvd_of_two_dvd_sq {k : ℕ} (hk : 2 ∣ k ^ 2) : 2 ∣ k :=
begin
  apply prime.dvd_of_dvd_pow,
  -- norm_num does most of the work here
  -- { norm_num, },
  { exact nat.prime_two }, -- alternatively the goal of prime 2 is already in mathlib
  exact hk,
end

-- to switch the target from ``P = Q`` to ``Q = P``,
-- use the tactic ``symmetry,``
lemma division_lemma_n {m n : ℕ}
  (hmn : 2 * m ^ 2 = n ^ 2)
: 2 ∣ n :=
begin
  -- Here the goal is 2 | n and what we just proved above is that
  -- given the hypothesis that 2 ∣ k^2 we have 2 ∣ k
  -- so from our goal of 2 ∣ n, we apply this to get the goal of
  -- 2 ∣ n^2
  apply two_dvd_of_two_dvd_sq,
  -- Here we have to remember that the division is defined as `m ∣ n := ∃ k : ℕ, m * k = n`
  -- so our goal of 2 ∣ n^2 is to show the existance of a k such that 2 * k = n^2
  -- out hypothesis tells us this is simply m^2
  use m^2,
  -- Goal is now the symmetry of hmn, so lets use the symmetry hint followed by exact.
  symmetry,
  exact hmn,
end

lemma div_2 {m n : ℕ} (hnm : 2 * m = 2 * n) : (m = n) :=
begin
  linarith,
end

lemma division_lemma_m {m n : ℕ} (hmn : 2 * m ^ 2 = n ^ 2) : 2 ∣ m :=
begin
  apply two_dvd_of_two_dvd_sq,
  -- goal is now 2 ∣ m^2 , which is is find k such that 2 * k = m^2
  -- We need to use the division_lemma_n which will take a hypothesis of hmn
  -- and produce a proof that 2 | n
  have hn := division_lemma_n hmn, -- we now have hn : 2 ∣ n
  -- since 2 ∣ n, n must be some multiple of 2 and so performing cases...
  cases hn with k hk,
  -- gives hk : n = 2 * k
  -- we now know ...n = 2k, and 2m^2 = n^2 = (2k)^2 = 4 * k^2
  -- and so m^2 = 2 * k^2 and so the k we want is k^2
  use k^2,
  -- goal is now m^2 = 2 * k^2
  -- we can't proove this directly
  -- but we have
  --   hmn : 2 * m^2 = n^2
  --   hk  : n = 2 * k
  -- and so
  rw hk at hmn,
  -- gives, hmn : 2 * m^2 = (2*k)^2
  -- which linarith can solve
  linarith,
end

-- 4.3.2. Prove (*) assuming m and n are coprime.

/-
theorem nat.not_coprime_of_dvd_of_dvd  : 1 < d → d ∣ m → d ∣ n → ¬m.coprime n
-/

theorem sqrt2_irrational' : ¬ ∃ (m n : ℕ), 2 * m^2 = n^2 ∧ m.coprime n :=
begin
  -- goal is ¬∃ (m n : ℕ), 2 * m ^ 2 = n ^ 2 ∧ m.coprime n
  by_contradiction,
  -- now have h : ∃ (m n : ℕ), 2 * m ^ 2 = n ^ 2 ∧ m.coprime n
  -- and goal is false
  -- rcases on this h gives
  --    m n : ℕ
  --   hmn : 2 * m ^ 2 = n ^ 2
  --   h_cop : m.coprime n
  rcases h with ⟨m, n, hmn, h_cop⟩,
  -- rcases is a way of doing cases iteratively
  -- you get the brackets by typing ``\langle`` and ``\rangle``
  -- We need to use the proofs above
  -- using the have syntax we're given above
  -- first, the division algorithm gives us  2 ∣ m
  have h_m_even : 2 ∣ m := division_lemma_m(hmn),
  -- we have h_cop : m.coprime m
  -- to apply the not_coprime_of_dvd_of_dvd above we want ¬ m.coprime
  -- so lets use contrapose on it
  contrapose h_cop,
  -- goal is now ¬m.coprime n and so
  -- we can now apply not_coprime_of_dvd_of_dvd
  apply not_coprime_of_dvd_of_dvd,
  -- we now have a number of goals all the ones to the left of the of the ¬m.coprime n in
  -- the definition of theorem nat.not_coprime_of_dvd_of_dvd
  -- so these are 1 < d → d ∣ m → d ∣ n
  {
    -- here the goal is 1 < d, but actually we have 2 ∣ m and so d is 2
    -- the goal is written as 1 < ?m_1
    -- but it's actually 2 or 1+1 and this is
    exact lt_add_one 1,
  },
  {
    -- goal here is 1 + 1 | m, and this is
    exact h_m_even,
  },
  {
    -- goal here is 1 + 1 ∣ n, this is exactly the division_lemma_m just proved on hmn
    exact division_lemma_n hmn,
  }
end

-- alternatively
example : ¬ ∃ (m n : ℕ), 2 * m^2 = n^2 ∧ m.coprime n :=
begin
  -- goal is ¬∃ (m n : ℕ), 2 * m ^ 2 = n ^ 2 ∧ m.coprime n
  by_contradiction,
  -- now have h : ∃ (m n : ℕ), 2 * m ^ 2 = n ^ 2 ∧ m.coprime n
  -- and goal is false
  -- rcases on this h gives
  --    m n : ℕ
  --   hmn : 2 * m ^ 2 = n ^ 2
  --   h_cop : m.coprime n
  rcases h with ⟨m, n, hmn, h_cop⟩,
  -- rcases is a way of doing cases iteratively
  -- you get the brackets by typing ``\langle`` and ``\rangle``
  -- We need to use the proofs above
  -- using the have syntax we're given above
  -- first, the division_lemma_m gives us  2 ∣ m
  have h_m_even : 2 ∣ m := division_lemma_m(hmn),
  -- secondly, the division_lemma_n gives us 2 ∣ n
  have h_n_even : 2 ∣ n := division_lemma_n(hmn),
  -- we have h_cop : m.coprime m
  -- to apply the not_coprime_of_dvd_of_dvd above we want ¬ m.coprime
  -- so lets use contrapose on it
  contrapose h_cop,
  -- goal is now ¬m.coprime n and so
  -- we can now apply not_coprime_of_dvd_of_dvd
  apply not_coprime_of_dvd_of_dvd,
  -- we now have a number of goals all the ones to the left of the of the ¬m.coprime n in
  -- the definition of theorem nat.not_coprime_of_dvd_of_dvd
  -- so these are 1 < d → d ∣ m → d ∣ n
  exact lt_add_one 1,
  exact h_m_even,
  exact h_n_even,
end

-- 4.3.3. Lemmas for proving (*) assuming m ≠ 0

lemma ne_zero_ge_zero {n : ℕ} (hne : n ≠ 0) : (0 < n) :=
begin
  -- this following line is given in mc2020 notes
  contrapose! hne,
  -- linarith then closes the goal
  --linarith,
  -- but a library_search finds the exact mathlib proof.
  exact le_zero_iff.mp hne,
end

/-
nat.pow_pos : ∀ {p : ℕ}, 0 < p → ∀ (n : ℕ), 0 < p ^ n
-/
lemma ge_zero_sq_ge_zero {n : ℕ} (hne : 0 < n) : (0 < n^2)
:=
begin
  -- again library_search is useful here to find the exact proof in mathlib
  -- exact pow_pos hne 2,
  -- for a more manual process.
  cases n, -- n is either 0 or n.succ for some other n
  {
    -- if n is zero
    rw zero_pow,
    exact hne,
    exact zero_lt_two,
  },
  {
    -- if n is n.succ for some other n
    rw pow_succ,
    rw pow_one;
    exact mul_pos hne hne,
  }
end

lemma cancellation_lemma {k m n : ℕ} (hk_pos : 0 < k^2) (hmn : 2 * (m * k) ^ 2 = (n * k) ^ 2) : 2 * m ^ 2 = n ^ 2 :=
begin
  -- this following line is given in the mc2020 notes.
  apply (nat.mul_left_inj hk_pos).mp,
  -- mc2020 notes also give the following two lines, but these fail to prove this lemma
  --ring at *,
  --exact hmn,
  -- however linarith is sufficient.
  linarith,
end

-- 4.3.4. Prove (*) assuming m ≠ 0

/-
gcd_pos_of_pos_left : ∀ {m : ℕ} (n : ℕ), 0 < m → 0 < m.gcd n
gcd_pos_of_pos_right : ∀ (m : ℕ) {n : ℕ}, 0 < n → 0 < m.gcd n
exists_coprime : ∀ {m n : ℕ}, 0 < m.gcd n → (∃ (m' n' : ℕ), m'.coprime n' ∧ m = m' * m.gcd n ∧ n = n' * m.gcd n)
-/
theorem wlog_coprime : (∃ (m n : ℕ), 2 * m^2 = n^2 ∧ m ≠ 0 ) → (∃ (m' n' : ℕ), 2 * m'^2 = n'^2 ∧ m'.coprime n' ) :=
begin
  intro key,
  rcases key with ⟨m, n, hmn, hme0⟩,
  set k := m.gcd n with hk,
  -- might be useful to declutter
  -- you can replace all the ``m.gcd n`` with ``k`` using ``rw ←hk,`` if needed
  -- The lines above this one are given in the mc2020 notes.
  have hmge : 0 < m := by exact ne.bot_lt hme0,
  have hge : 0 < m.gcd n := by exact pos_of_dvd_of_pos (gcd_dvd_left m n) hmge,
  have he := exists_coprime(hge),
  cases he,
  set m' := m / k,
  set n' := n / k;
  cases he_h with h1 h2,
  use (he_w * k),
  use (h1 * k),
  cases h2 with h2_1 h2_2,
  rw ← hk at h2_2,
  rw h2_2.1 at hmn,
  rw h2_2.2 at hmn,
  split,
  exact hmn,
  -- need to prove  (he_w * k).coprime (h1 * k)
  -- this can't be right (he_w * k) is coprime with (h1 * k) only if k is one
  repeat {sorry},
end

theorem sqrt2_irrational'' : ¬ ∃ (m n : ℕ), 2 * m^2 = n^2 ∧ m ≠ 0 :=
begin
  sorry,
end


-- begin header
import limits

namespace M1P1
-- end header

/- Section
The sandwich theorem
-/

/- Sub-section
Introdiction
-/

/-
The sandwich theorem, or squeeze theorem, for sequences is the statement that if $(a_n)$, $(b_n)$, and $(c_n)$ are three real-valued sequences satisfying $a_n≤ b_n≤ c_n$ for all $n$, and if furthermore $a_n→ℓ$ and $c_n→ℓ$, then $b_n→ℓ$. The idea of the proof is straightforward -- if we want to ensure that $|b_n-ℓ|<ε$ then it suffices to show that $|a_n-ℓ|<ε$ and $|c_n-ℓ|<ε$.
-/

/- Theorem
If $(a_n)$, $(b_n)$, and $(c_n)$ are three real-valued sequences satisfying $a_n ≤ b_n ≤ c_n$ for all $n$, and if furthermore $a_n→ℓ$ and $c_n→ℓ$, then $b_n→ℓ$.
-/
theorem sandwich (a b c : ℕ → ℝ)
  (l : ℝ) (ha : is_limit a l) (hc : is_limit c l)
  (hab : ∀ n, a n ≤ b n) (hbc : ∀ n, b n ≤ c n) : is_limit b l :=
begin
  -- We need to show that for all $ε>0$ there exists $N$ such that $n≥N$ implies $|b_n-ℓ|<ε$. So choose ε > 0.
  intros ε Hε,
  -- we now need an $N$. As usual it is the max of two other N's, one coming from $(a_n)$ and one from $(c_n)$. Choose $N_a$ and $N_c$ such that $|aₙ - l| < ε$ for $n ≥ Na$ and $|cₙ - l| < ε$ for $n ≥ Nc$.
  cases ha ε Hε with Na Ha,
  cases hc ε Hε with Nc Hc,
  -- Now let $N$ be the max of $N_a$ and $N_c$; we claim that this works.
  let N := max Na Nc,
  use N,
  -- Note that N ≥ Na and N ≥ Nc,
  have HNa : Na ≤ N := by obvious_ineq,
  have HNc : Nc ≤ N := by obvious_ineq,
  -- so for all n ≥ N,
  intros n Hn,
  -- we have $n≥ N_a$ and $n\geq N_c$, so $aₙ ≤ bₙ ≤ cₙ$, and $|aₙ - l|, |bₙ - l|$ are both less than $\epsilon$.
  have h1 : a n ≤ b n := hab n,
  have h2 : b n ≤ c n := hbc n,
  have h3 : |a n - l| < ε := Ha n (le_trans HNa Hn),
  have h4 : |c n - l| < ε := Hc n (le_trans HNc Hn),
  -- The result now follows easily from these inequalities (as $l-ε<a_n≤b_n≤c_n<l+ε$).
  revert h3,revert h4,
  unfold abs,unfold max,
  split_ifs;intros;linarith,
end

/-
Note that Lean finds the last line of the proof automatically -- we do not need to explicitly tell it the inequality argument.
-/
end M1P1


/-
Try defining other operations on the natural numbers, such as multiplication,
the predecessor function (with pred 0 = 0), truncated subtraction
(with n - m = 0 when m is greater than or equal to n), and exponentiation.
Then try proving some of their basic properties,
building on the theorems we have already proved.

Since many of these are already defined in Lean's core library,
you should work within a namespace named Hidden, or something like that,
in order to avoid name clashes.
-/

namespace hidden

  inductive Nat where
  | zero : Nat
  | succ : Nat → Nat
  deriving Repr

  namespace Nat

    /- Addition -/
    def add (m n : Nat) : Nat :=
      match n with
      | zero => m
      | succ n' => Nat.succ (add m n')

    notation:65 lhs:65 "+" rhs:66 => add lhs rhs
    notation:max "one" => succ zero

    theorem add_zero (n : Nat) : n + zero = n := by rfl

    theorem add_succ (m n : Nat) : m + succ n = succ (m + n) := by rfl

    theorem succ_eq_add_one (n : Nat) : n + one = succ n := by
      calc
        n + one = succ (n + zero) := by simp only [add_succ]
        _ = succ n := by simp only [add_zero]

    theorem zero_add (n: Nat) : zero + n = n :=
      Nat.recOn (motive := fun x => zero + x = x)
        n
        (show zero + zero = zero from rfl)
        (fun (n : Nat) (ih : zero + n = n) =>
          show zero + succ n = succ n from
            calc
              zero + succ n = succ (zero + n) := rfl
              _ = succ n := by rw [ih])

    theorem add_assoc (m n k : Nat) : (m + n) + k = m + (n + k) :=
      Nat.recOn (motive := fun x => (m + n) + x = m + (n + x)) k
        (show (m + n) + zero = m + (n + zero) by rfl)
        (fun k ih => by
          calc
            (m + n) + succ k = succ ((m + n) + k) := by rfl
            _ = succ (m + (n + k)) := by rw [ih])

    theorem succ_add (m n : Nat) : succ n + m = succ (n + m) :=
      Nat.recOn (motive := fun x => succ n + x = succ (n + x)) m
        (by rfl)
        (fun m ih => by simp only [add_succ, *])

    theorem add_comm (m n : Nat) : m + n = n + m :=
      Nat.recOn (motive := fun x => m + x = x + m) n
        (show m + zero = zero + m by simp [zero_add, add])
        (fun n ih => by calc
          m + succ n = succ (m + n) := by rfl
          _ = succ (n + m) := by rw [ih]
          _ = succ n + m := by simp only [succ_add])

    theorem succ_eq (m n : Nat) : succ m = succ n → m = n := by
      intro h
      match h with
      | rfl => rfl

    theorem left_add_cancel (m n k : Nat) : m + k = n + k → m = n := by
      intro h
      induction k with
      | zero => simp only [add_zero]; assumption
      | succ k ih =>
        have : succ (m + k) = succ (n + k) := by simp only [add]; assumption
        have : m + k = n + k := by apply succ_eq; assumption
        show m = n; simp only [ih, *]

    theorem not_and_iff_or_not (p q : Prop) : ¬ (p ∧ q) ↔ ¬ p ∨ ¬ q := by
      apply Iff.intro
      . intro
        cases (Classical.em p) with
        | inl => cases (Classical.em q) with
          | inl =>
            have : p ∧ q := by constructor <;> assumption
            contradiction
          | inr => apply Or.inr; assumption
        | inr => apply Or.inl; assumption
      . intro h
        intro
        | And.intro _ _ =>
          cases h <;> contradiction

    theorem zero_sum_elim (m n : Nat) : m + n = zero → m = zero ∧ n = zero := by
      intro h
      apply Classical.byContradiction
      intro hn
      have : ¬ (m = zero) ∨ ¬ (n = zero) := Iff.mp (not_and_iff_or_not ..) hn
      have : m ≠ zero ∨ n ≠ zero := by simp [*]
      cases this with
      | inl => match m with
        | zero => contradiction
        | succ m' =>
          have : succ (m' + n) = zero := by simp [succ_add, *] at h
          match (succ (m' + n)) with
          | zero => contradiction
          | succ a => contradiction
      | inr => match n with
        | zero => contradiction
        | succ n' =>
          have : succ (m + n') = zero := by simp [add, *] at h
          cases (succ (m + n')) <;> contradiction

    /- Predecesor -/
    def pred (n : Nat) : Nat :=
      match n with
      | zero => zero
      | succ n' => n'

    theorem succ_pred_self (n : Nat) (h : n ≠ zero) : succ (pred n) = n :=
      match n with
      | zero => by contradiction
      | succ n' => by rfl

    /- Multiplication -/
    def mul (m n : Nat) : Nat :=
      match n with
      | zero => zero
      | succ n' => mul m n' + m

    notation:75 lhs:75 "*" rhs:76 => mul lhs rhs

    theorem mul_zero (n : Nat) : n * zero = zero := by rfl

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

    theorem zero_mul (n : Nat) : zero * n = zero := by
      induction n with
      | zero => rfl
      | succ n ih => calc
        zero * succ n = zero * n + zero := by rw [mul_succ]
        _ = zero + zero := by simp only [ih]
        _ = zero := by simp only [add]

    theorem mul_one (n : Nat) : n * one = n := by
      calc
        n * one = n * zero + n := by simp only [mul]
        _ = n := by simp only [mul_zero, zero_add]

    theorem mul_add (m n k : Nat) : m * (n + k) = m * n + m * k := by
      induction k with
      | zero => rfl
      | succ k ih => calc
        m * (n + succ k) = m * succ (n + k) := by simp only [add]
        _ = m * (n + k) + m := by simp only [mul]
        _ = m * n + m * k + m := by rw [ih]
        _ = m * n + m * succ k := by simp only [mul_succ, add_assoc]

    theorem add_mul (m n k : Nat) : (m + n) * k = m * k + n * k := by
      induction k with
      | zero => rfl
      | succ k ih => calc
        (m + n) * succ k = (m + n) * k + (m + n) := by simp only [mul_succ]
        _ = m * k + n * k + (m + n) := by rw [ih]
        _ = (m * k + m) + (n * k + n) := by simp only [←add_assoc, add_comm]
        _ = m * succ k + n * succ k := by simp only [mul_succ]

    theorem one_mul (n : Nat) : one * n = n := by
      induction n with
      | zero => rfl
      | succ n ih => calc
        one * succ n = one * n + one := by simp only [mul]
        _ = n + one := by rw [ih]
        _ = succ n := by rw [succ_eq_add_one]

    theorem mul_comm (m n : Nat) : m * n = n * m := by
      induction n with
      | zero => simp only [mul_zero, zero_mul]
      | succ n ih => calc
        m * succ n = m * n + m := by simp only [mul]
        _ = n * m + one * m := by simp only [ih, one_mul]
        _ = (n + one) * m := by simp only [add_mul]
        _ = succ n * m := by simp only [succ_eq_add_one]

    theorem mul_assoc (m n k : Nat) : m * (n * k) = (m * n) * k := by
      induction k with
      | zero => simp only [mul]
      | succ k ih => calc
        m * (n * succ k) = m * (n * k + n) := by simp only [mul]
        _ = m * (n * k) + m * n := by simp only [mul_add]
        _ = (m * n) * k + (m * n) * one := by simp only [ih, mul_one]
        _ = (m * n) * (k + one) := by simp only [mul_add]
        _ = (m * n) * succ k := by simp only [succ_eq_add_one]

    theorem not_or_iff_and_not (p q : Prop) : ¬ (p ∨ q) ↔ ¬ p ∧ ¬ q := by
      apply Iff.intro
      . intro
        cases (Classical.em p) with
        | inl =>
          have : p ∨ q := by apply Or.inl; assumption
          contradiction
        | inr => cases (Classical.em q) with
          | inl =>
            have : p ∨ q := by apply Or.inr; assumption
            contradiction
          | inr => constructor <;> assumption
      . intro h
        intro ho
        cases ho with
        | inl => apply h.left; assumption
        | inr => apply h.right; assumption

    theorem zero_mul_elim (m n : Nat) : zero = m * n → m = zero ∨ n = zero := by
      intro h
      apply Classical.byContradiction
      intro
      have : m ≠ zero ∧ n ≠ zero := by
        apply Iff.mp (not_or_iff_and_not ..); assumption
      match this with
      | And.intro _ _ =>
        match n with
        | zero => contradiction
        | succ n => match m with
          | zero => contradiction
          | succ m =>
            have : zero = succ (succ m * n + m) := by calc
              zero = succ m * succ n := by simp only [*]
              _ = succ m * n + succ m := by simp only [mul]
              _ = succ (succ m * n + m) := by simp only [add]
            contradiction

    theorem left_mul_cancel (n m k : Nat) : k ≠ zero → m * k = n * k → m = n := by
      intro hk
      intro he
      admit

    #check Eq.symm

  end Nat
end hidden

/-
Define some operations on lists, like a length function or the reverse function. Prove some properties, such as the following:

a. length (s ++ t) = length s + length t

b. length (reverse t) = length t

c. reverse (reverse t) = t
-/

namespace hidden1

  inductive List (α : Type u) where
  | nil : List α
  | cons : α → List α → List α
  deriving Repr

  namespace List

    def length (as : List α) : Nat :=
      match as with
      | nil => 0
      | cons a as' => length as' + 1

    def append (as bs : List α) : List α :=
      match as with
      | nil => bs
      | cons a as' => cons a (append as' bs)

    notation:66 as:66 "**" bs:67 => append as bs

    theorem nil_append (as : List α) : nil ** as = as := by rfl

    theorem cons_append (as bs : List α) (a : α)
        : cons a (as ** bs) = (cons a as) ** bs := by rfl

    theorem append_nil (as : List α) : as ** nil = as := by
      induction as with
      | nil => rfl
      | cons a as ih => calc
        (cons a as) ** nil = cons a (as ** nil) := by simp only [cons_append]
        _ = cons a as := by simp only [ih]

    theorem append_assoc (as bs cs : List α)
        : (as ** bs) ** cs = as ** (bs ** cs) := by
      induction as with
      | nil => rfl
      | cons a as ih => calc
        ((cons a as) ** bs) ** cs = (cons a (as ** bs)) ** cs := by simp only [cons_append]
        _ = cons a ((as ** bs) ** cs) := by simp only [cons_append]
        _ = cons a (as ** (bs ** cs)) := by simp only [ih]
        _ = ((cons a as) ** (bs ** cs)) := by simp only [cons_append]

    theorem length_dist (as bs : List α) : length (as ** bs) = length as + length bs := by
      induction as with
      | nil => simp [length, nil_append]
      | cons a as ih => calc
        length ((cons a as) ** bs) = length (cons a (as ** bs)) := by simp only [cons_append]
        _ = length (as ** bs) + 1 := by simp only [length]
        _ = length as + length bs + 1 := by simp only [ih]
        _ = length (cons a as) + length bs := by simp_arith only [length]

    def reverse (as : List α) :=
      match as with
      | nil => as
      | cons a as' => (reverse as') ** (cons a nil)

    theorem length_reverse (as : List α)
        : length (reverse as) = length as := by
      induction as with
      | nil => rfl
      | cons a as ih => calc
        length (reverse (cons a as)) = length ((reverse as) ** (cons a nil)) := by simp [reverse]
        _ = length (reverse as) + length (cons a nil) := by simp [length_dist]
        _ = length as + 1 := by simp [length, ih]

    theorem reverse_append (as bs : List α)
        : reverse (as ** bs) = reverse bs ** reverse as := by
      induction as with
      | nil => simp [reverse, append_nil]; rfl
      | cons a as ih => calc
        reverse ((cons a as) ** bs) = reverse (cons a (as ** bs)) := by
          simp only [cons_append]
        _ = (reverse (as ** bs)) ** (cons a nil) := by simp [reverse]
        _ = (reverse bs ** reverse as) ** (cons a nil) := by simp [ih]
        _ = reverse bs ** (reverse as ** (cons a nil)) := by simp [append_assoc]
        _ = reverse bs ** reverse (cons a as) := by simp [reverse]

    theorem reverse_rev (as : List α) : reverse (reverse as) = as := by
      induction as with
      | nil => rfl
      | cons a as ih => calc
        reverse (reverse (cons a as)) = reverse ((reverse as) ** (cons a nil)) := by
          simp only [reverse]
        _ = reverse (cons a nil) ** reverse (reverse as) := by simp [reverse_append]
        _ = reverse (cons a nil) ** as := by simp [ih]
        _ = (cons a nil) ** as := by rfl
        _ = cons a as := by rfl

  end List

end hidden1


/-
Copyright (c) 2020 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker, Alexey Soloyev, Junyan Xu
-/
import data.real.irrational
import data.nat.fib
import data.fin.vec_notation
import tactic.ring_exp
import algebra.linear_recurrence

/-!
# The golden ratio and its conjugate

This file defines the golden ratio `φ := (1 + √5)/2` and its conjugate
`ψ := (1 - √5)/2`, which are the two real roots of `X² - X - 1`.

Along with various computational facts about them, we prove their
irrationality, and we link them to the Fibonacci sequence by proving
Binet's formula.
-/

noncomputable theory

/-- The golden ratio `φ := (1 + √5)/2`. -/
@[reducible] def golden_ratio := (1 + real.sqrt 5)/2

/-- The conjugate of the golden ratio `ψ := (1 - √5)/2`. -/
@[reducible] def golden_conj := (1 - real.sqrt 5)/2

localized "notation `φ` := golden_ratio" in real
localized "notation `ψ` := golden_conj" in real

/-- The inverse of the golden ratio is the opposite of its conjugate. -/
lemma inv_gold : φ⁻¹ = -ψ :=
begin
  have : 1 + real.sqrt 5 ≠ 0,
    from ne_of_gt (add_pos (by norm_num) $ real.sqrt_pos.mpr (by norm_num)),
  field_simp [sub_mul, mul_add],
  norm_num
end

/-- The opposite of the golden ratio is the inverse of its conjugate. -/
lemma inv_gold_conj : ψ⁻¹ = -φ :=
begin
  rw [inv_eq_iff, ← neg_inv, neg_eq_iff_neg_eq],
  exact inv_gold.symm,
end

@[simp] lemma gold_mul_gold_conj : φ * ψ = -1 :=
by {field_simp, rw ← sq_sub_sq, norm_num}

@[simp] lemma gold_conj_mul_gold : ψ * φ = -1 :=
by {rw mul_comm, exact gold_mul_gold_conj}

@[simp] lemma gold_add_gold_conj : φ + ψ = 1 := by {rw [golden_ratio, golden_conj], ring}

lemma one_sub_gold_conj : 1 - φ = ψ := by linarith [gold_add_gold_conj]

lemma one_sub_gold : 1 - ψ = φ := by linarith [gold_add_gold_conj]

@[simp] lemma gold_sub_gold_conj : φ - ψ = real.sqrt 5 := by {rw [golden_ratio, golden_conj], ring}

@[simp] lemma gold_sq : φ^2 = φ + 1 :=
begin
  rw [golden_ratio, ←sub_eq_zero],
  ring_exp,
  rw real.sq_sqrt; norm_num,
end

@[simp] lemma gold_conj_sq : ψ^2 = ψ + 1 :=
begin
  rw [golden_conj, ←sub_eq_zero],
  ring_exp,
  rw real.sq_sqrt; norm_num,
end

lemma gold_pos : 0 < φ :=
mul_pos (by apply add_pos; norm_num) $ inv_pos.2 zero_lt_two

lemma gold_ne_zero : φ ≠ 0 := ne_of_gt gold_pos

lemma one_lt_gold : 1 < φ :=
begin
  refine lt_of_mul_lt_mul_left _ (le_of_lt gold_pos),
  simp [← sq, gold_pos, zero_lt_one]
end

lemma gold_conj_neg : ψ < 0 := by linarith [one_sub_gold_conj, one_lt_gold]

lemma gold_conj_ne_zero : ψ ≠ 0 := ne_of_lt gold_conj_neg

lemma neg_one_lt_gold_conj : -1 < ψ :=
begin
  rw [neg_lt, ← inv_gold],
  exact inv_lt_one one_lt_gold,
end

/-!
## Irrationality
-/

/-- The golden ratio is irrational. -/
theorem gold_irrational : irrational φ :=
begin
  have := nat.prime.irrational_sqrt (show nat.prime 5, by norm_num),
  have := this.rat_add 1,
  have := this.rat_mul (show (0.5 : ℚ) ≠ 0, by norm_num),
  convert this,
  field_simp
end

/-- The conjugate of the golden ratio is irrational. -/
theorem gold_conj_irrational : irrational ψ :=
begin
  have := nat.prime.irrational_sqrt (show nat.prime 5, by norm_num),
  have := this.rat_sub 1,
  have := this.rat_mul (show (0.5 : ℚ) ≠ 0, by norm_num),
  convert this,
  field_simp
end

/-!
## Links with Fibonacci sequence
-/

section fibrec

variables {α : Type*} [comm_semiring α]

/-- The recurrence relation satisfied by the Fibonacci sequence. -/
def fib_rec : linear_recurrence α :=
{ order := 2,
  coeffs := ![1, 1]}

section poly

open polynomial

/-- The characteristic polynomial of `fib_rec` is `X² - (X + 1)`. -/
lemma fib_rec_char_poly_eq {β : Type*} [comm_ring β] :
  fib_rec.char_poly = X^2 - (X + (1 : polynomial β)) :=
begin
  rw [fib_rec, linear_recurrence.char_poly],
  simp [finset.sum_fin_eq_sum_range, finset.sum_range_succ', monomial_eq_smul_X]
end

end poly

/-- As expected, the Fibonacci sequence is a solution of `fib_rec`. -/
lemma fib_is_sol_fib_rec : fib_rec.is_solution (λ x, x.fib : ℕ → α) :=
begin
  rw fib_rec,
  intros n,
  simp only,
  rw [nat.fib_add_two, add_comm],
  simp [finset.sum_fin_eq_sum_range, finset.sum_range_succ'],
end

/-- The geometric sequence `λ n, φ^n` is a solution of `fib_rec`. -/
lemma geom_gold_is_sol_fib_rec : fib_rec.is_solution (pow φ) :=
begin
  rw [fib_rec.geom_sol_iff_root_char_poly, fib_rec_char_poly_eq],
  simp [sub_eq_zero]
end

/-- The geometric sequence `λ n, ψ^n` is a solution of `fib_rec`. -/
lemma geom_gold_conj_is_sol_fib_rec : fib_rec.is_solution (pow ψ) :=
begin
  rw [fib_rec.geom_sol_iff_root_char_poly, fib_rec_char_poly_eq],
  simp [sub_eq_zero]
end

end fibrec

/-- Binet's formula as a function equality. -/
theorem real.coe_fib_eq' : (λ n, nat.fib n : ℕ → ℝ) = λ n, (φ^n - ψ^n) / real.sqrt 5 :=
begin
  rw fib_rec.sol_eq_of_eq_init,
  { intros i hi,
    fin_cases hi,
    { simp },
    { simp only [golden_ratio, golden_conj], ring_exp, rw mul_inv_cancel; norm_num } },
  { exact fib_is_sol_fib_rec },
  { ring_nf,
    exact (@fib_rec ℝ _).sol_space.sub_mem
            (submodule.smul_mem fib_rec.sol_space (real.sqrt 5)⁻¹ geom_gold_is_sol_fib_rec)
            (submodule.smul_mem fib_rec.sol_space (real.sqrt 5)⁻¹ geom_gold_conj_is_sol_fib_rec) }
end

/-- Binet's formula as a dependent equality. -/
theorem real.coe_fib_eq : ∀ n, (nat.fib n : ℝ) = (φ^n - ψ^n) / real.sqrt 5 :=
by rw [← function.funext_iff, real.coe_fib_eq']


import linearAlgebra.basic

-- initiating the file
universes u v
variables {F : Type u} {V : Type v}
variables [field F] [add_comm_group V] [vector_space F V] (u v w : V) (a b c : F)

open vector_space

-- Let's define some basic results we are going to need, these are from our
-- definition of a vector space,

lemma left_dist (a : F) (u v : V) : a • ( u + v ) = a • u + a • v := left_dist a u v
lemma right_dist (a b : F) (u : V) : (a + b) • u = a • u + b • u := right_dist a b u
lemma smul_smul' (a b : F) (u : V) : a • (b • u) = (a * b) • u := smul_smul a b u
lemma smul_id (u : V) : (1 : F) • u = u := smul_id u

section question1

/- Prove the fact that 0 • u = 0, where 0 is the zero vector  -/
lemma zero_smul' (u : V) : (0 : V) = (0 : F) • u :=
begin
  sorry
end

end question1

section question2

/- What about the other way? -/
lemma smul_zero'' (u : V) : a • (0 : V) = (0 : V) :=
begin
  sorry
end

end question2

section question3

/- This one is slightly harder, try to prove that -u is just -1 • u -/
lemma inv_eq_neg (u : V) (x : F) : -u = (-1 : F) • u :=
begin
  sorry
end

end question3


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

import tactic -- imports all the Lean tactics

/-!

# Group theory in Lean

Lean has groups. But let's make our own anyway. In this sheet we will
learn about the `class` command, which is one of the ways to make
a theory of groups in Lean (and indeed the way it's done in `mathlib`)

We will also learn about `simp`, a tactic which does long series of
rewrites automatically. We'll even learn how to train it to solve a
certain kind of problem.

## Definition of a group

`group G` is already defined in `mathlib`, so let's define a new
type `mygroup G`. Here `G : Type` is a type, and `mygroup G` is the type of
group structures on `G`.

-/

/-- `mygroup G` is the type of group structures on the type `G`. -/
class mygroup (G : Type)
  extends has_one G, has_mul G, has_inv G : Type :=
(mul_assoc : ∀ a b c : G, (a * b) * c = a * (b * c))
(one_mul : ∀ a : G, 1 * a = a)
(mul_one : ∀ a : G, a * 1 = a)
(inv_mul_self : ∀ a : G, a⁻¹ * a = 1)
(mul_inv_self : ∀ a : G, a * a⁻¹ = 1)

/-

Right now the axioms are called `mygroup.mul_assoc`, `mygroup.one_mul` etc.
Furthermore we want to build some lemmas called things like `mygroup.one_inv`
and stuff. The fix? Let's move into the `mygroup` namespace, where they're
just caled `mul_assoc`, `one_inv` etc.

-/

namespace mygroup

-- tag all the axioms for groups with the `@[simp]` attribute.
-- Note that we can drop the `mygroup` prefix now as we're in the namespace.

attribute [simp] mul_assoc one_mul mul_one inv_mul_self mul_inv_self

-- Throughout our work in this namespace, let `G` be a group and let
-- `a`, `b` and `c` be elements of `G`.
variables {G : Type} [mygroup G] (a b c : G)

/-

Five of the next seven lemmas are tagged with `@[simp]` as well.
See if you can prove all seven using (for the most part) the `rw` tactic.

-/

@[simp] lemma inv_mul_cancel_left : a⁻¹ * (a * b) = b :=
begin
  sorry
end

@[simp] lemma mul_inv_cancel_left : a * (a⁻¹ * b) = b :=
begin
  sorry
end

lemma left_inv_eq_right_inv {a b c : G} (h1 : b * a = 1) (h2 : a * c = 1) :
  b = c :=
begin
  -- hint for this one : establish the auxiliary fact
  -- that `b * (a * c) = (b * a) * c` with the `have` tactic.
  sorry,
end

lemma mul_eq_one_iff_eq_inv : a * b = 1 ↔ a⁻¹ = b :=
begin
  sorry,
end

@[simp] lemma one_inv : (1 : G)⁻¹ = 1 :=
begin
  sorry,
end

@[simp] lemma inv_inv : (a⁻¹)⁻¹ = a :=
begin
  sorry,
end

@[simp] lemma mul_inv_rev : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  sorry,
end

/-

Remember the `ring` tactic which didn't look at hypotheses but which could
prove hypothesis-free identities in commutative rings? A theorem of Knuth
and Bendix says that we have just trained Lean's simplifier to prove
arbitrary true hypothesis-free identities in groups; those ten lemmas
we tagged with `@[simp]` are all you need. We've made a Noetherian
confluent rewrite system for group theory!

-/
-- example of Knuth-Bendix theorem
example (G : Type) [mygroup G] (a b : G) :
  (b⁻¹ * a⁻¹)⁻¹ * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1 := by simp

-- bonus puzzle : if g^2=1 for all g in G, then G is abelian
example (G : Type) [mygroup G] (h : ∀ g : G, g * g = 1) :
  ∀ g h : G, g * h = h * g :=
begin
  sorry
end

end mygroup


-- Producto_de_un_positivo_por_una_funcion_monotona.lean
-- Si f es monótona y c ≥ 0, entonces c·f es monótona.
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 9-noviembre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que si c es no negativo y f es monótona, entonces c * f es
-- monótona.
-- ----------------------------------------------------------------------

import data.real.basic

variables (f : ℝ → ℝ)
variable  {c : ℝ}

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

example
  (mf : monotone f)
  (nnc : 0 ≤ c)
  : monotone (λ x, c * f x) :=
begin
  have h1 : ∀ a b, a ≤ b → (λ x, c * f x) a ≤ (λ x, c * f x) b,
    { intros a b hab,
      have h2 : f a ≤ f b := mf hab,
      have h3 : c * f a ≤ c * f b := mul_le_mul_of_nonneg_left h2 nnc,
      show (λ x, c * f x) a ≤ (λ x, c * f x) b,
        by exact h3, },
  show monotone (λ x, c * f x),
    by exact h1,
end

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

example
  (mf : monotone f)
  (nnc : 0 ≤ c)
  : monotone (λ x, c * f x) :=
begin
  intros a b hab,
  apply mul_le_mul_of_nonneg_left,
  apply mf hab,
  apply nnc
end

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

example (mf : monotone f) (nnc : 0 ≤ c) :
  monotone (λ x, c * f x) :=
λ a b hab, mul_le_mul_of_nonneg_left (mf hab) nnc


import BrownCs22.Library.Tactics

/-

# Induction in Lean

There are tactics corresponding to the induction principles we saw in class.
Think about it: how many goals should each induction principle produce?
What hypotheses do we have in each goal?

* `basic_induction` is the normal, one-step, starting-from-0 induction:

-/

example (P : ℕ → Prop) : ∀ n, P n := by
  basic_induction
  { sorry } -- show `P 0`
  { intro n
    intro hn
    sorry } -- assuming `P n`, show `P (n + 1)`

/-

* `induction_from_starting_point`: again, as the name implies, this lets
  us start a normal induction proof from a value besides 0.

-/

example (P : ℕ → Prop) : ∀ n ≥ 4, P n := by
  induction_from_starting_point
  { sorry } -- prove `P 4`
  { intro n
    intro hn4
    intro hpn
    sorry } -- assuming `4 ≤ n` and `P n`, show `P (n + 1)`

/-

Here's an example we did in class, typed into Lean!

-/

example : ∀ n ≥ 5, 2^n > n^2 := by
  induction_from_starting_point
  { numbers }
  { intro n
    intro hn
    intro ih
    have hle : 2*n + 1 ≤ n^2
    { nlinarith }
    calc (n+1)^2 = n^2 + 2*n + 1 := by linarith
               _ ≤ n^2 + n^2     := by linarith
               _ < 2^n + 2^n     := by linarith
               _ = 2^n * 2       := by linarith
               _ = 2 ^ (n + 1)   := by rw [pow_add, pow_one]
  }

/-

* `strong_induction` is, as the name implies, an induction principle
  that lets us assume `P` holds of *all* smaller numbers.
  Why is there no base case here?

-/

example (P : ℕ → Prop) : ∀ n, P n := by
  strong_induction
  intro n
  intro hn
  sorry -- assuming `∀ (m : ℕ), m < n → P m`, show `P n`

/-

There's no base case because if we can prove the strong induction step
for every `n`, it must hold for `0`, which implies `P 0`:

-/

example (P : ℕ → Prop) (hn : ∀ (n : ℕ), (∀ (m : ℕ), m < n → P m) → P n) :
    P 0 := by
  have hn0 : (∀ (m : ℕ), (m < 0 → P m)) → P 0 := hn 0
  apply hn0
  intro m hm
  contradiction


import analysis.normed_space.banach_steinhaus
import analysis.normed_space.finite_dimension

import analysis.calculus.inverse

open set filter
open_locale topological_space filter

noncomputable theory

/- TEXT:
.. index:: normed space

.. _normed_spaces:

Differential Calculus in Normed Spaces
--------------------------------------

Normed spaces
^^^^^^^^^^^^^

Differentiation can be generalized beyond ``ℝ`` using the notion of a
*normed vector space*, which encapsulates both direction and distance.
We start with the notion of a *normed group*, which as an additive commutative
group equipped with a real-valued norm function
satisfying the following conditions.
EXAMPLES: -/
section
-- QUOTE:
variables {E : Type*} [normed_group E]

example (x : E) : 0 ≤ ∥x∥ :=
norm_nonneg x

example {x : E} : ∥x∥ = 0 ↔ x = 0 :=
norm_eq_zero

example (x y : E) : ∥x + y∥ ≤ ∥x∥ + ∥y∥ :=
norm_add_le x y
-- QUOTE.

/- TEXT:
Every normed space is a metric space with distance function
:math:`d(x, y) = \| x - y \|`, and hence it is also a topological space.
Lean and mathlib know this.
EXAMPLES: -/
-- QUOTE:
example : metric_space E := by apply_instance

example {X : Type*} [topological_space X] {f : X → E} (hf : continuous f) :
  continuous (λ x, ∥f x∥) :=
hf.norm
-- QUOTE.

/- TEXT:
In order to use the notion of a norm with concepts from linear algebra,
we add the assumption ``normed_space ℝ E`` on top of ``normed_group E``.
This stipulates that ``E`` is a vector space over ``ℝ`` and that
scalar multiplication satsifies the following condition.
EXAMPLES: -/
-- QUOTE:
variables [normed_space ℝ E]

example (a : ℝ) (x : E) : ∥a • x∥ = |a| * ∥x∥ :=
norm_smul a x
-- QUOTE.

/- TEXT:
A complete normed space is known as a *Banach space*.
Every finite-dimensional vector space is complete.
EXAMPLES: -/
-- QUOTE:
example [finite_dimensional ℝ E] : complete_space E :=
by apply_instance
-- QUOTE.

/- TEXT:
In all the previous examples, we used the real numbers as the base field.
More generally, we can make sense of calculus with a vector space over any
*non-discrete normed field*. These are fields that are equipped with a
real-valued norm that is multiplicative and has the property that
not every element has norm zero or one
(equivalently, there is an element whose norm is bigger than one).
EXAMPLES: -/
-- QUOTE:
example (𝕜 : Type*) [nondiscrete_normed_field 𝕜] (x y : 𝕜) : ∥x * y∥ = ∥x∥ * ∥y∥ :=
norm_mul x y

example (𝕜 : Type*) [nondiscrete_normed_field 𝕜] : ∃ x : 𝕜, 1 < ∥x∥ :=
normed_field.exists_one_lt_norm 𝕜
-- QUOTE.

/- TEXT:
A finite-dimensional vector space over a nondiscrete normed field is
complete as long as the field itself is complete.
EXAMPLES: -/
-- QUOTE:
example (𝕜 : Type*) [nondiscrete_normed_field 𝕜] (E : Type*) [normed_group E]
  [normed_space 𝕜 E] [complete_space 𝕜] [finite_dimensional 𝕜 E] : complete_space E :=
finite_dimensional.complete 𝕜 E
-- QUOTE.

end

/- TEXT:
Continuous linear maps
^^^^^^^^^^^^^^^^^^^^^^

We now turn to the morphisms in the category of normed spaces, namely,
continuous linear maps.
In mathlib, the type of ``𝕜``-linear continuous maps between normed spaces
``E`` and ``F`` is written ``E →L[𝕜] F``.
They are implemented as *bundled maps*, which means that an element of this type
a structure that that includes the function itself and the properties
of being linear and continuous.
Lean will insert a coercion so that a continuous linear map can be treated
as a function.
EXAMPLES: -/
section
-- QUOTE:
variables {𝕜 : Type*} [nondiscrete_normed_field 𝕜]
          {E : Type*} [normed_group E] [normed_space 𝕜 E]
          {F : Type*} [normed_group F] [normed_space 𝕜 F]

example : E →L[𝕜] E := continuous_linear_map.id 𝕜 E

example (f : E →L[𝕜] F) : E → F :=
f

example (f : E →L[𝕜] F) : continuous f :=
f.cont

example (f : E →L[𝕜] F) (x y : E) : f (x + y) = f x + f y :=
f.map_add x y

example (f : E →L[𝕜] F) (a : 𝕜) (x : E) : f (a • x) = a • f x :=
f.map_smul a x
-- QUOTE.

/- TEXT:
Continuous linear maps have an operator norm that is characterized by the
following properties.
EXAMPLES: -/
-- QUOTE:
variables (f : E →L[𝕜] F)

example (x : E) : ∥f x∥ ≤ ∥f∥ * ∥x∥ :=
f.le_op_norm x

example {M : ℝ} (hMp: 0 ≤ M) (hM : ∀ x, ∥f x∥ ≤ M * ∥x∥) :
  ∥f∥ ≤ M :=
f.op_norm_le_bound hMp hM
-- QUOTE.

end

/- TEXT:
There is also a notion of bundled continuous linear *isomorphism*.
Their type of such isomorphisms is ``E ≃L[𝕜] F``.

As a challenging exercise, you can prove the Banach-Steinhaus theorem, also
known as the Uniform Boundedness Principle.
The principle states that a family of continuous linear maps from a Banach space
into a normed space is pointwise
bounded, then the norms of these linear maps are uniformly bounded.
The main ingredient is Baire's theorem
``nonempty_interior_of_Union_of_closed.`` (You proved a version of this in the topology chapter.)
Minor ingredients include ``continuous_linear_map.op_norm_le_of_shell``,
``interior_subset`` and ``interior_Inter_subset`` and ``is_closed_le``.
BOTH: -/
section
-- QUOTE:
variables
  {𝕜 : Type*} [nondiscrete_normed_field 𝕜]
  {E : Type*} [normed_group E] [normed_space 𝕜 E]
  {F : Type*} [normed_group F] [normed_space 𝕜 F]

open metric

-- EXAMPLES:
example {ι : Type*} [complete_space E] {g : ι → E →L[𝕜] F}
  (h : ∀ x, ∃ C, ∀ i, ∥g i x∥ ≤ C) :
  ∃ C', ∀ i, ∥g i∥ ≤ C' :=
begin
  /- sequence of subsets consisting of those `x : E` with norms `∥g i x∥` bounded by `n` -/
  let e : ℕ → set E := λ n, ⋂ i : ι, { x : E | ∥g i x∥ ≤ n },
  /- each of these sets is closed -/
  have hc : ∀ n : ℕ, is_closed (e n),
  sorry,
  /- the union is the entire space; this is where we use `h` -/
  have hU : (⋃ n : ℕ, e n) = univ,
  sorry,
  /- apply the Baire category theorem to conclude that for some `m : ℕ`,
     `e m` contains some `x` -/
  obtain ⟨m, x, hx⟩ : ∃ m, ∃ x, x ∈ interior (e m) := sorry,
  obtain ⟨ε, ε_pos, hε⟩ : ∃ ε > 0, ball x ε ⊆ interior (e m) := sorry,
  obtain ⟨k, hk⟩ : ∃ k : 𝕜, 1 < ∥k∥ := sorry,
  /- show all elements in the ball have norm bounded by `m` after applying any `g i` -/
  have real_norm_le : ∀ (z ∈ ball x ε) (i : ι), ∥g i z∥ ≤ m,
  sorry,
  have εk_pos : 0 < ε / ∥k∥ := sorry,
  refine ⟨(m + m : ℕ) / (ε / ∥k∥),
           λ i, continuous_linear_map.op_norm_le_of_shell ε_pos _ hk _⟩,
  sorry,
  sorry
end
-- QUOTE.

-- SOLUTIONS:
example {ι : Type*} [complete_space E] {g : ι → E →L[𝕜] F}
  (h : ∀ x, ∃ C, ∀ i, ∥g i x∥ ≤ C) :
  ∃ C', ∀ i, ∥g i∥ ≤ C' :=
begin
  /- sequence of subsets consisting of those `x : E` with norms `∥g i x∥` bounded by `n` -/
  let e : ℕ → set E := λ n, ⋂ i : ι, { x : E | ∥g i x∥ ≤ n },
  /- each of these sets is closed -/
  have hc : ∀ n : ℕ, is_closed (e n),
  from λ i, is_closed_Inter (λ i, is_closed_le (g i).cont.norm continuous_const),
  /- the union is the entire space; this is where we use `h` -/
  have hU : (⋃ n : ℕ, e n) = univ,
  { refine eq_univ_of_forall (λ x, _),
    cases h x with C hC,
    obtain ⟨m, hm⟩ := exists_nat_ge C,
    exact ⟨e m, mem_range_self m, mem_Inter.mpr (λ i, le_trans (hC i) hm)⟩ },
  /- apply the Baire category theorem to conclude that for some `m : ℕ`,
     `e m` contains some `x` -/
  obtain ⟨m : ℕ, x : E, hx : x ∈ interior (e m)⟩ := nonempty_interior_of_Union_of_closed hc hU,
  obtain ⟨ε, ε_pos, hε : ball x ε ⊆ interior (e m)⟩ := is_open_iff.mp is_open_interior x hx,
  obtain ⟨k : 𝕜, hk : 1 < ∥k∥⟩ := normed_field.exists_one_lt_norm 𝕜,
  /- show all elements in the ball have norm bounded by `m` after applying any `g i` -/
  have real_norm_le : ∀ (z ∈ ball x ε) (i : ι), ∥g i z∥ ≤ m,
  { intros z hz i,
    replace hz := mem_Inter.mp (interior_Inter_subset _ (hε hz)) i,
    apply interior_subset hz },
  have εk_pos : 0 < ε / ∥k∥ := div_pos ε_pos (zero_lt_one.trans hk),
  refine ⟨(m + m : ℕ) / (ε / ∥k∥), λ i, continuous_linear_map.op_norm_le_of_shell ε_pos _ hk _⟩,
  { exact div_nonneg (nat.cast_nonneg _) εk_pos.le },
  intros y le_y y_lt,
  calc ∥g i y∥
      = ∥g i (y + x) - g i x∥   : by rw [(g i).map_add, add_sub_cancel]
  ... ≤ ∥g i (y + x)∥ + ∥g i x∥ : norm_sub_le _ _
  ... ≤ m + m : add_le_add (real_norm_le (y + x) (by rwa [add_comm, add_mem_ball_iff_norm]) i)
          (real_norm_le x (mem_ball_self ε_pos) i)
  ... = (m + m : ℕ) : by norm_cast
  ... ≤ (m + m : ℕ) * (∥y∥ / (ε / ∥k∥))
      : le_mul_of_one_le_right (nat.cast_nonneg _)
          ((one_le_div $ div_pos ε_pos (zero_lt_one.trans hk)).2 le_y)
  ... = (m + m : ℕ) / (ε / ∥k∥) * ∥y∥ : (mul_comm_div _ _ _).symm,
end

-- BOTH:
end

/- TEXT:
Asymptotic comparisons
^^^^^^^^^^^^^^^^^^^^^^

Defining differentiability also requires asymptotic comparisons.
Mathlib has an extensive library covering the big O and little o relations,
whose definitions are shown below.
Opening the ``asymptotics`` locale allows us to use the corresponding
notation.
Here we will only use little o to define differentiability.
EXAMPLES: -/
-- QUOTE:
open asymptotics
open_locale asymptotics

example {α : Type*} {E : Type*} [normed_group E] {F : Type*} [normed_group F]
  (c : ℝ) (l : filter α) (f : α → E) (g : α → F) :
  is_O_with c l f g ↔ ∀ᶠ x in l, ∥ f x ∥ ≤ c * ∥ g x ∥ :=
is_O_with_iff

example {α : Type*} {E : Type*} [normed_group E] {F : Type*} [normed_group F]
  (c : ℝ) (l : filter α) (f : α → E) (g : α → F) :
  f =O[l] g ↔ ∃ C, is_O_with C l f g :=
is_O_iff_is_O_with

example {α : Type*} {E : Type*} [normed_group E] {F : Type*} [normed_group F]
  (c : ℝ) (l : filter α) (f : α → E) (g : α → F) :
  f =o[l] g ↔ ∀ C > 0, is_O_with C l f g :=
is_o_iff_forall_is_O_with

example {α : Type*} {E : Type*} [normed_group E] (c : ℝ) (l : filter α) (f g : α → E) :
  f ~[l] g ↔ (f - g) =o[l] g :=
iff.rfl
-- QUOTE.

/- TEXT:
Differentiability
^^^^^^^^^^^^^^^^^

We are now ready to discuss differentiable functions between normed spaces.
In analogy the elementary one-dimensional,
mathlib defines a predicate ``has_fderiv_at`` and a function ``fderiv``.
Here the letter
"f" stands for *Fréchet*.
EXAMPLES: -/
section
-- QUOTE:
variables
  {𝕜 : Type*} [nondiscrete_normed_field 𝕜]
  {E : Type*} [normed_group E] [normed_space 𝕜 E]
  {F : Type*} [normed_group F] [normed_space 𝕜 F]

example (f : E → F) (f' : E →L[𝕜] F) (x₀ : E) :
  has_fderiv_at f f' x₀ ↔ (λ x, f x - f x₀ - f' (x - x₀)) =o[𝓝 x₀] (λ x, x - x₀) :=
iff.rfl

example (f : E → F) (f' : E →L[𝕜] F) (x₀ : E) (hff' : has_fderiv_at f f' x₀) :
  fderiv 𝕜 f x₀ = f' :=
hff'.fderiv
-- QUOTE.

/- TEXT:
We also have iterated derivatives that take values in the type of multilinear maps
``E [×n]→L[𝕜] F``,
and we have continuously differential functions.
The type ``with_top ℕ`` is ``ℕ`` with an additional element ``⊤`` that
is bigger than every natural number.
So :math:`\mathcal{C}^\infty` functions are functions ``f`` that satisfy
``cont_diff 𝕜 ⊤ f``.
EXAMPLES: -/
-- QUOTE:
example (n : ℕ) (f : E → F) : E → (E [×n]→L[𝕜] F) :=
iterated_fderiv 𝕜 n f

example (n : with_top ℕ) {f : E → F} :
  cont_diff 𝕜 n f ↔
    (∀ (m : ℕ), (m : with_top ℕ) ≤ n → continuous (λ x, iterated_fderiv 𝕜 m f x))
  ∧ (∀ (m : ℕ), (m : with_top ℕ) < n → differentiable 𝕜 (λ x, iterated_fderiv 𝕜 m f x)) :=
cont_diff_iff_continuous_differentiable
-- QUOTE.

/- TEXT:
There is a stricter notion of differentiability called
``has_strict_fderiv_at``, which is used in the statement
of the inverse function theorem and the statement of the implicit function
theorem, both of which are in mathlib.
Over ``ℝ`` or ``ℂ``, continuously differentiable
functions are strictly differentiable.
EXAMPLES: -/
-- QUOTE:
example {𝕂 : Type*} [is_R_or_C 𝕂] {E : Type*} [normed_group E] [normed_space 𝕂 E]
  {F : Type*} [normed_group F] [normed_space 𝕂 F]
  {f : E → F} {x : E} {n : with_top ℕ}
  (hf : cont_diff_at 𝕂 n f x) (hn : 1 ≤ n) :
  has_strict_fderiv_at f (fderiv 𝕂 f x) x :=
hf.has_strict_fderiv_at hn
-- QUOTE.

/- TEXT:
The local inverse theorem is stated using an operation that produces an
inverse function from a
function and the assumptions that the function is strictly differentiable at a
point ``a`` and that its derivative is an isomorphism.

The first example below gets this local inverse.
The next one states that it is indeed a local inverse
from the left and from the right, and that it is strictly differentiable.
EXAMPLES: -/
-- QUOTE:
section local_inverse
variables [complete_space E] {f : E → F} {f' : E ≃L[𝕜] F} {a : E}

example (hf : has_strict_fderiv_at f ↑f' a) : F → E :=
has_strict_fderiv_at.local_inverse f f' a hf

example  (hf : has_strict_fderiv_at f (f' : E →L[𝕜] F) a) :
  ∀ᶠ x in 𝓝 a, hf.local_inverse f f' a (f x) = x :=
hf.eventually_left_inverse

example  (hf : has_strict_fderiv_at f (f' : E →L[𝕜] F) a) :
  ∀ᶠ x in 𝓝 (f a), f (hf.local_inverse f f' a x) = x :=
hf.eventually_right_inverse

example [complete_space E] {f : E → F} {f' : E ≃L[𝕜] F} {a : E}
    (hf : has_strict_fderiv_at f ↑f' a) :
  has_strict_fderiv_at (has_strict_fderiv_at.local_inverse f f' a hf)
    (f'.symm : F →L[𝕜] E) (f a) :=
has_strict_fderiv_at.to_local_inverse hf

end local_inverse
-- QUOTE.

/- TEXT:
This has been only a quick tour of the differential calculus in mathlib.
The library contains many variations that we have not discussed.
For example, you may want to use one-sided derivatives in the
one-dimensional setting. The means to do so are found in mathlib in a more
general context;
see ``has_fderiv_within_at`` or the even more general ``has_fderiv_at_filter``.
EXAMPLES: -/
#check has_fderiv_within_at
#check has_fderiv_at_filter

end


-- 14.lean
open nat

def mod2 : ℕ → ℕ
| 0 := 0
| (succ n) :=
    match mod2 n with
    | 0 := 1
    | (succ n) := 0
    end

inductive even : ℕ → Prop
| even_zero : even 0
| even_succ : ∀ n, even n → even (n + 2)

inductive odd : ℕ → Prop
| odd_succ : ∀ n, even n → odd (n + 1)

lemma even_or_odd : ∀n, even n ∨ odd n := begin
    intro n,
    induction n,
        left,
        apply even.even_zero,
    cases n_ih,
        right,
        apply odd.odd_succ,
        assumption,
    left,
    induction n_n,
    cases n_ih,
    apply even.even_succ,
    cases n_ih,
    assumption
end

lemma not_even_and_odd : ∀n, even n → odd n → false := begin
    intros n e_n,
    induction e_n,
        intro o_zero,
        cases o_zero,
    intro o_e_2,
    cases o_e_2,
    cases o_e_2_a,
    simp [nat.add] at *,
    induction o_e_2_a,
    apply e_n_ih,
    apply odd.odd_succ,
    assumption,
    assumption
end

lemma even_plus_even_is_even : ∀n, even n → ∀m, even m → even (n + m) := begin
    intros n e_n,
    induction e_n,
        intros,
        simp,
        assumption,
    intros m em,
    have comm : e_n_n + 2 + m = (e_n_n + m) + 2 := by simp,
    rw comm,
    apply even.even_succ,
    apply e_n_ih,
    assumption
end

lemma even_is_two_times : ∀n, even (2 * n) := begin
    intro n,
    induction n,
        simp,
        apply even.even_zero,
    rw mul_succ,
    have two_is_even : even 2 := even.even_succ 0 even.even_zero,
    apply even_plus_even_is_even;
    assumption
end

lemma even_times_is_even : ∀n, even n → ∀m, even (n * m) := begin
    intros n e_n,
    induction e_n,
        intros,
        simp,
        apply even.even_zero,
    intro m,
    rw right_distrib,
    have e_2_m : even (2 * m) := even_is_two_times m,
    apply even_plus_even_is_even,
    apply e_n_ih,
    assumption
end

lemma even_plus_odd_is_odd(n m : ℕ) : even n → odd m → odd (n + m) := begin
    intro e_n,
    induction e_n,
        intro,
        simp [nat.add],
        assumption,
    intro o_m,
    have comm1 : e_n_n + 2 = 2 + e_n_n := nat.add_comm e_n_n 2,
    have comm : e_n_n + 2 + m = nat.succ(nat.succ(e_n_n + m)) := calc
        e_n_n + 2 + m = 2 + e_n_n + m : by rw comm1
        ...           = 2 + (e_n_n + m) : by rw nat.add_assoc
        ...           = nat.succ (nat.succ (e_n_n + m)) : begin
            rw nat.succ_add,
            congr,
            apply one_add
        end,
    rw comm,
    apply odd.odd_succ,
    induction o_m,
    have comm2 : (e_n_n + (o_m_n + 1)) = succ (e_n_n + o_m_n) := calc
        (e_n_n + (o_m_n + 1)) = e_n_n + (succ o_m_n) : by rw nat.add_succ
        ...                   = succ (e_n_n + o_m_n) : by rw nat.add_succ,
    rw comm2,
    apply even.even_succ,
    apply even_plus_even_is_even,
    assumption,
    assumption
end

lemma odd_times_odd_is_odd (n m : ℕ) : odd(n) → odd(m) → odd(n * m) := begin
    intro n,
    induction n,
    intro o_m,
    rw right_distrib,
    simp,
    rw add_comm,
    apply even_plus_odd_is_odd,
    apply even_times_is_even,
    assumption,
    assumption
end

theorem evenness_is_idempotent : ∀n, even n ↔ even (n * n) := begin
    intro n,
    apply iff.intro,
        intro e_n,
        apply even_times_is_even,
        assumption,
    have n_e_o : even n ∨ odd n := even_or_odd n,
    cases n_e_o,
        intros,
        assumption,
        have n_e_o_sq : odd (n * n) := begin
            apply odd_times_odd_is_odd; assumption
        end,
        intro e_n_sq,
        apply false.elim,
        apply not_even_and_odd,
        assumption,
        assumption
end

lemma even_is_0_in_mod2 (n : ℕ) : even n → mod2 n = 0 := begin
    intro e_n,
    induction e_n,
    simp [mod2],
    rw add_comm,
    simp [mod2],
    rw e_n_ih,
    simp [mod2]
end

lemma odd_is_1_in_mod2 (n : ℕ) : odd n → mod2 n = 1 := begin
    intro o_n,
    induction o_n,
    simp [mod2],
    have p : mod2 o_n_n = 0 := begin
        apply even_is_0_in_mod2,
        assumption
    end,
    rw p,
    simp [mod2]
end

theorem multiplication_in_f2_is_idempotent(n : ℕ) : mod2 n = mod2 (n * n) := begin
    have n_is_even_or_odd : even n ∨ odd n := by apply even_or_odd,
    cases n_is_even_or_odd,
        have e_n_sq : even (n * n) := begin
            apply (iff.elim_left (evenness_is_idempotent n)),
            assumption
        end,
        have mod2_n_0 : mod2 n = 0 := even_is_0_in_mod2 n n_is_even_or_odd,
        have mod2_n_sq_0 : mod2 (n * n) = 0 := even_is_0_in_mod2 (n * n) e_n_sq,
        rw mod2_n_0,
        rw mod2_n_sq_0,
    have o_n_sq : odd(n * n) := odd_times_odd_is_odd n n n_is_even_or_odd n_is_even_or_odd,
    have mod2_n_1 : mod2 n = 1 := odd_is_1_in_mod2 n n_is_even_or_odd,
    have mod2_n_sq_1 : mod2 (n * n) = 1 := odd_is_1_in_mod2 (n * n) o_n_sq,
    rw mod2_n_1,
    rw mod2_n_sq_1
end


-- Prueba con hipótesis y uso de lemas
-- ===================================

-- Sean a, b, c y d números reales. Demostrar que si
--    c = d * a + b
--    b = a + d
-- entonces c = 2 * a * d.

import data.real.basic

variables (a b c d : ℝ)

-- 1ª demostración
example
  (h1 : c = d * a + b)
  (h2 : b = a * d)
  : c = 2 * a * d :=
begin
  rw h2 at h1,
  rw mul_comm at h1,
  rw ← two_mul (a * d) at h1,
  rw ← mul_assoc at h1,
  exact h1,
end

-- 2ª demostración
example
  (h1 : c = d * a + b)
  (h2 : b = a * d)
  : c = 2 * a * d :=
begin
  calc
    c   = d * a + b     : by exact h1
    ... = d * a + a * d : by rw h2
    ... = a * d + a * d : by rw mul_comm
    ... = 2 * (a * d)   : by rw two_mul (a * d)
    ... = 2 * a * d     : by rw mul_assoc,
end

-- 3ª demostración
example
  (h1 : c = d * a + b)
  (h2 : b = a * d)
  : c = 2 * a * d :=
begin
  calc
    c   = d * a + b     : by exact h1
    ... = d * a + a * d : by rw h2
    ... = 2 * a * d     : by ring,
end

-- 4ª demostración
example
  (h1 : c = d * a + b)
  (h2 : b = a * d)
  : c = 2 * a * d :=
by linarith


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
-/
import algebra.big_operators.basic

open finset
open_locale big_operators

namespace nat

/-- Euler's totient function. This counts the number of positive integers less than `n` which are
coprime with `n`. -/
def totient (n : ℕ) : ℕ := ((range n).filter (nat.coprime n)).card

localized "notation `φ` := nat.totient" in nat

@[simp] theorem totient_zero : φ 0 = 0 := rfl

lemma totient_le (n : ℕ) : φ n ≤ n :=
calc totient n ≤ (range n).card : card_filter_le _ _
           ... = n              : card_range _

lemma totient_pos : ∀ {n : ℕ}, 0 < n → 0 < φ n
| 0 := dec_trivial
| 1 := by simp [totient]
| (n+2) := λ h, card_pos.2 ⟨1, mem_filter.2 ⟨mem_range.2 dec_trivial, coprime_one_right _⟩⟩

end nat


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

import tactic -- imports all the Lean tactics

/-!

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

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

## The absolute basics

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

## Tactics you will need

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

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

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

## Worked examples

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

-/

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

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

-- Assume that `P` and `Q` and `R` are all true. Deduce that `P` is true.
example (hP : P) (hQ : Q) (hR : R) : P :=
begin
  exact hP,
end

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

-- Assume `Q` is true. Prove that `P → Q`.
example (hQ : Q) : P → Q :=
begin
  intro fish,
  exact hQ,
end

-- Assume `P → Q` and `P` is true. Deduce `Q`.
example (h : P → Q) (hP : P) : Q :=
begin
  apply h,
  exact hP,
end

/-

## Examples for you to try

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

-/

/-- Every proposition implies itself. -/
example : P → P :=
begin
  intro banana,
  exact banana,
end

/-

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

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

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

-/
example : P → Q → P :=
begin
  intro hP,
  intro hQ,
  -- the `assumption` tactic will close a goal if
  -- it's exactly equal to one of the hypotheses.
  assumption,
end

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

/-- `→` is transitive. That is, if `P → Q` and `Q → R` are true, then
  so is `P → R`. -/
example : (P → Q) → (Q → R) → (P → R) :=
begin
  intros hPQ hQR hP,
  apply hQR,
  apply hPQ,
  exact hP,
end

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

/-

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

-/

variables (S T : Prop)

example : (P → R) → (S → Q) → (R → T) → (Q → R) → S → T :=
begin
  intros hPR hSQ hRT hQR hS,
  apply hRT,
  clear hPR,
  apply hQR,
  apply hSQ,
  exact hS,
end

example : (P → Q) → ((P → Q) → P) → Q :=
begin
  intros hPQ hPQP,
  apply hPQ,
  apply hPQP,
  exact hPQ,
end

example : ((P → Q) → R) → ((Q → R) → P) → ((R → P) → Q) → P :=
begin
  intros h1 h2 h3,
  apply h2,
  intro hQ,
  apply h1,
  intro hP,
  exact hQ,
end

example : ((Q → P) → P) → (Q → R) → (R → P) → P :=
begin
  intros h1 h2 h3,
  apply h1,
  intro hQ,
  apply h3,
  apply h2,
  exact hQ,
end

example : (((P → Q) → Q) → Q) → (P → Q) :=
begin
  intros h1 hP,
  apply h1,
  intro hPQ,
  exact hPQ hP,
end

example :
  (((P → Q → Q) → ((P → Q) → Q)) → R) →
  ((((P → P) → Q) → (P → P → Q)) → R) →
  (((P → P → Q) → ((P → P) → Q)) → R) → R :=
begin
  intros h1 h2 h3,
  apply h2,
  intros h1 hP h2,
  apply h1,
  intro hP,
  exact h2,
end

example :
  (((P → Q → Q) → ((P → Q) → Q)) → R) →
  ((((P → P) → Q) → (P → P → Q)) → R) →
  (((P → P → Q) → ((P → P) → Q)) → R) → R :=
begin
  tauto!, -- another approach
end


/-
Copyright (c) 2022 Julian Berman. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Julian Berman
-/

import group_theory.exponent
import group_theory.order_of_element
import group_theory.quotient_group

/-!
# Torsion groups

This file defines torsion groups, i.e. groups where all elements have finite order.

## Main definitions

* `monoid.is_torsion` is a predicate asserting a monoid `G` is a torsion monoid, i.e. that all
  elements are of finite order. Torsion groups are also known as periodic groups.
* `add_monoid.is_torsion` the additive version of `monoid.is_torsion`.

## Future work

* Define `tor G` for the torsion subgroup of a group
* torsion-free groups
-/

universe u

variable {G : Type u}

open_locale classical

namespace monoid

variables (G) [monoid G]

/--A predicate on a monoid saying that all elements are of finite order.-/
@[to_additive "A predicate on an additive monoid saying that all elements are of finite order."]
def is_torsion := ∀ g : G, is_of_fin_order g

end monoid

open monoid

/-- Torsion monoids are really groups. -/
@[to_additive "Torsion additive monoids are really additive groups"]
noncomputable def is_torsion.group [monoid G] (tG : is_torsion G) : group G :=
{ inv := λ g, g ^ (order_of g - 1),
  mul_left_inv := λ g,
  begin
    erw [←pow_succ', tsub_add_cancel_of_le, pow_order_of_eq_one],
    exact order_of_pos' (tG g)
  end,
  ..‹monoid G› }

variables [group G] {N : subgroup G}

/-* Subgroups of torsion groups are torsion groups. *-/
@[to_additive "Subgroups of additive torsion groups are additive torsion groups."]
lemma is_torsion.subgroup (tG : is_torsion G) (H : subgroup G) : is_torsion H :=
λ h, (is_of_fin_order_iff_coe _ h).mpr $ tG h

/-* Quotient groups of torsion groups are torsion groups. *-/
@[to_additive "Quotient groups of additive torsion groups are additive torsion groups."]
lemma is_torsion.quotient_group [nN : N.normal] (tG : is_torsion G) : is_torsion (G ⧸ N) :=
λ h, quotient_group.induction_on' h $ λ g, (tG g).quotient N g

/-* If a group exponent exists, the group is torsion. *-/
@[to_additive exponent_exists.is_add_torsion]
lemma exponent_exists.is_torsion (h : exponent_exists G) : is_torsion G := begin
  obtain ⟨n, npos, hn⟩ := h,
  intro g,
  exact (is_of_fin_order_iff_pow_eq_one g).mpr ⟨n, npos, hn g⟩,
end

/-* The group exponent exists for any bounded torsion group. *-/
@[to_additive is_add_torsion.exponent_exists]
lemma is_torsion.exponent_exists
  (tG : is_torsion G) (bounded : (set.range (λ g : G, order_of g)).finite) :
  exponent_exists G :=
exponent_exists_iff_ne_zero.mpr $
  (exponent_ne_zero_iff_range_order_of_finite (λ g, order_of_pos' (tG g))).mpr bounded

/-* Finite groups are torsion groups. *-/
@[to_additive is_add_torsion_of_fintype]
lemma is_torsion_of_fintype [fintype G] : is_torsion G :=
exponent_exists.is_torsion $ exponent_exists_iff_ne_zero.mpr exponent_ne_zero_of_fintype


section propositional

variables P Q R : Prop

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

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

end

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

end

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

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

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

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

end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

end propositional

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

section predicate

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

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

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

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

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

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

end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

end predicate


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

/-!
# Real sign function

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

## Main definitions

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

## Tags

sign function
-/

namespace real

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

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

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

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

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

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

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

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

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

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

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

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

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

@[simp]

end real


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-/
open function

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

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

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

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