import MyNat.le
import MyNat.addition_world
import MyNat.advanced_addition_world
import MyNat.advanced_proposition_world

open MyNat

lemma one_add_le_self (x : ℕ) : x ≤ 1 + x := by
  exists 1
  rewrite [add_comm]
  rfl

lemma le_refl (x : ℕ) : x ≤ x := by
  exists 0

theorem le_succ (a b : ℕ) : a ≤ b → a ≤ succ b := by
  intro h
  cases h with
  | intro c h' =>
    exists (succ c)
    rewrite [h', add_succ]
    rfl

lemma zero_le (a : ℕ) : 0 ≤ a := by
  induction a with
  | zero => exact le_refl zero
  | succ a' h => exact le_succ zero a' h

theorem le_trans (a b c : ℕ)
  (hab : a ≤ b) (hbc : b ≤ c) : a ≤ c := by
  cases hab with
  | intro d hab' =>
  cases hbc with
  | intro e hbc' =>
  exists (d + e)
  rewrite [←add_assoc, ←hab']
  exact hbc'

theorem le_antisem (a b : ℕ)
  (hab : a ≤ b) (hba : b ≤ a) : a = b := by
  cases hab with
  | intro c hab =>
  cases hba with
  | intro c' hba =>
  rewrite [hab, add_assoc,]at hba
  have h1 := eq_zero_of_add_right_eq_self _ _ (Eq.symm hba)
  have h2 := add_left_eq_zero _ _ h1
  rewrite [h2, zero_equal_numeral, add_zero] at h1
  rewrite [h1, add_zero] at hab
  exact (Eq.symm hab)

theorem le_zero (a : ℕ) (h : a ≤ 0) : a = 0 := by
  cases h with
  | intro _ h =>
  exact add_right_eq_zero _ _ (Eq.symm h)

theorem le_total (a b : ℕ) : a ≤ b ∨ b ≤ a := by
  induction b with
  | zero => exact (Or.inr (zero_le a))
  | succ b ih =>
  exact (
    Or.elim
    ih
    (fun a_le_b => Or.inl (le_succ _ _ a_le_b))
    (fun b_le_a => by
      rewrite [le_iff_exists_add] at b_le_a
      cases b_le_a with
      | intro c h =>
        cases c with
        | zero =>
          rewrite [add_zero] at h
          rewrite [h, succ_eq_add_one, add_comm]
          exact (Or.inl (one_add_le_self b))
        | succ c =>
          apply Or.inr
          exists c
          rewrite [add_succ, add_comm, ←add_succ, ←add_comm] at h
          trivial
    )
  )

lemma le_succ_self (a : ℕ) : a ≤ succ a := by
  rewrite [←add_one_eq_succ, add_comm]
  exact one_add_le_self a

theorem add_le_add_right (a b : ℕ) :
  a ≤ b → ∀ t, (a + t) ≤ (b + t) := by
  intro h
  intro t
  cases h with
  | intro c h =>
    exists c
    rewrite [h, add_right_comm]
    rfl

theorem le_of_succ_le_succ (a b : ℕ) :
  succ a ≤ succ b → a ≤ b := by
  intro h
  cases h with
  | intro c h =>
    exists c
    rewrite [succ_add] at h
    exact succ_inj b (a + c) h

theorem not_succ_le_self (a : ℕ) : ¬ (succ a ≤ a) := by
  rewrite [succ_eq_add_one, ←add_zero a, add_assoc, zero_add]
  intro h
  cases h with
  | intro c h =>
    rewrite [add_assoc] at h
    let f := add_left_cancel a zero (1 + c) h
    rewrite [add_comm, ←succ_eq_add_one] at f
    exact zero_ne_succ c f

theorem add_le_add_left (a b t : ℕ)
  (h : a ≤ b) : t + a ≤ t + b := by
  cases h with
  | intro c h =>
    exists c
    rewrite [h, add_assoc]
    rfl


/-
In Lean, set is a type constructor. It takes
a type, T, as an argument and returns, as a
result, the type, set T, which  we read as the
type of any set with elements of type, T. The
type of set is thus Type → Type. Lean tells
us that this type constructor can take a type
in any type universe, i.e., Type, Type 1, etc.
We needn't be concerned with that here.
-/

#check set

/-
EXERCISE: Is set a type? Discuss.
-/

#check set nat

/-
The type, set nat, or set T more
generally, represents a property
of value of type nat (or T): the
property of "being in a given set!"
-/

#reduce set nat

/-
We now define e to be the empty
set of natural numbers. Lean gives
us ordinary set display notation
(using curly braces around a list
of elements) for this purpose.
-/

def e: set nat := { }

/-
The symbol, ∅, is often used
to represent the empty set (of
values of some type).
-/

def e': set nat := ∅

/-
We can't just say "e : set := {}"",
because then Lean does not have
enough context to infer the type
of the elements.
-/

/-
EXERCISE: What is the property of
natural numbers that characterizes
the empty set of natural numbers?
-/

#reduce e

/-
Study that carefully. The predicate
that defines the empty set is, as
we've alreadydiscussed, false(n):
i.e., the function of type ℕ → Prop
that for any value, n : ℕ, returns
the proposition false. No natural
number can makes the (proposition
derived from the) predicate true,
so no natural number is in the set
that it represents.
-/

/-
We can also represent the empty
set using set builder notation.
-/

def e'' : set nat := { n | false }

def ev(n : ℕ):Prop := ∃ m, m + m = n

def v : set nat := { n | ev n }
/-
We read the right hand side as
"the set of values, n, for which
the predicate, false, is true."
As there is no value that makes
false true, the set is empty. It
has no elements.
-/

/-
Here's another set of ℕ, containing
only the number, 1. We call such a
set a singleton set.
-/

def x: set nat := { 1 }

/-
EXERCISE: What property of natural
numbers defines the property of being
in this set? Try to come up with the
answer before you look!
-/

#reduce x

/-
The answer is a little surprising.
The predicate λ n, n = 1, would do
to define this set, but instead Lean
uses λ n, n = 1 ∨ false. The basic
intuition is that, were we to remove
the 1 from this set, we'd be left
with the empty set, the predicate
for which is that predicate false.
-/

def x' := { n | n = 1 }

#reduce x'

/-
The two notations give rise to
slightly different but equivalent
predicates.
-/

-- SET MEMBERSHIP

/-
By the notation 1 ∈ x we mean the
proposition that "1 is in, or is
a member of the set, x." This is
simplythe proposition obtained
by applying the predicate, x, to
the value, 1. The proposition is
literally the value (x 1). Recall
that function application works
by substituting the argument (1)
for the parameter (a) in the body
of the predicate (function).  In
this case, the predicate, x, is
λ (n : ℕ), n = 1. Applying this
predicate/function the value, 1,
reduces to the proposition that:
1 = 1 ∨ false. This proposition,
in turn, is easy to prove, and so,
yes, indeed, 1 is in the set x.
-/

#reduce 1 ∈ x

example : 1 ∈ x :=
-- 1 = 1 ∨ false
begin
apply or.intro_left,
exact rfl,
end

/-
Because the proposition, 1 ∈ x,
is defined to be the disjunction,
(1 = 1 ∨ false), you can ask Lean
to change the format of the goal
accordingly. If doing this makes
it easier for you to see how to
proceed with the proof, feel free
to use it. You can cut and paste
the disjunction from the string
that #reduce 3 ∈ u prints out.
-/

example : 1 ∈ x :=
-- 1 = 1
begin
change 1 = 1 ∨ false,
-- now or.intro_left, but with a shortcut
left,
-- and now exact rfl, but with a shortcut
trivial,
end

-- ANOTHER EXAMPLE

/-
Here's two sets with three
elements each.
-/

def y : set nat := { 1, 2, 3 }
def z : set nat := { 2, 3, 4 }

/-
EXERCISE: What is a predicate
that characterizes membership in
the set, y?
-/

#reduce y

/-
EXERCISE: Define the same set, y,
with the name, y', using set builder
notation.
-/

def y' : set nat := { n |
    n = 1 ∨ n = 2 ∨ n = 3 }

#reduce y

/-
With these basics in hand, we can
define, understand, and work with
the full range of set operations.
Set operations are like operations
with numbers but their operands and
results are sets.
-/

-- SET UNION

/-
The union of two sets, y and z,
which we denote as y ∪ z, is the
combined set of values from y and
z.

An element is either in or not in
a given, but cannot be in a more
than one time (otherwise you have
what is called a multiset). The
union of y and z as defined above
is thus the set { 1, 2, 3, 4 }.
-/

def u := y ∪ z

/-
EXERCISE: What predicate defines
the set that is the union of y and z?
-/

#reduce u

/-
Answer: It is the predicate that
defines what it means to be in y
or to be in z. That is, it is the
disjunction of the predicates that
define y and z, respectively. Union
corresponds to "or."
-/

/-
Let's prove that 3 ∈ u. Let's
start by reminding ourselves of
the predicate that defines u and
of the proposition represented
by 3 ∈ u.
-/

#reduce u

/-
The set, u, is defined as a
predicate that takes a : ℕ and
returns the proposition that
that a is one of the values
in the set, expressed as a
somewhat long disjunction. Lean
selects the variable name, a,
for purposes of printing out
the value of u. There is no
special meaning to a; it is
just an otherwise unbound name.
-/

/-
Now that we know that 3 ∈ u is
just a proposition involving a
bunch of disjunctions, it's easy
to prove. -/

example : 3 ∈ u :=
begin
/-
Notice again that Lean leaves the
goal written using set membership
notation. Just bear in mind that
the goal is just the disjunction,
(3 = 3 ∨ 3 = 2 ∨ 3 = 1 ∨ false) ∨
3 = 4 ∨ 3 = 3 ∨ 3 = 2 ∨ false.
-/
left,
left,
trivial,
end

/-
Or, if you prefer, make the goal
explicit as a disjunction.
-/
example : 3 ∈ y ∪ z :=
begin
change (3 = 3 ∨ 3 = 2 ∨ 3 = 1 ∨ false) ∨ 3 = 4 ∨ 3 = 3 ∨ 3 = 2 ∨ false,
apply or.inl,
apply or.inl,
trivial,
end

-- SET INTERSECTION

/-
The intersection of two sets, y and
z, which we denote as y ∩ z, is the
set containing those values that are
in y and that are in z. Intersection
thus corresponds to the conjunction
of the predicates defining the two
individual sets.
-/

def w := y ∩ z

#reduce w

example : 2 ∈ y ∩ z :=
-- (a = 3 ∨ a = 2 ∨ a = 1 ∨ false) ∧ (a = 4 ∨ a = 3 ∨ a = 2 ∨ false)
begin
apply and.intro,
right,
left,
trivial,
right,
right,
left,
trivial,
end

-- SET DIFFERENCE

/-
The set difference y - z, also
writen as y \ z, is the set of
values that are in y but not in
z. Think of the subtraction as
saying that from y you take away
z, and the result is what is left
of y.

EXERCISE: What predicate defines
a set difference, y \ z?
-/

#reduce y \ z

example : 1 ∈ y \ z :=
begin
-- apply and.intro,
split,
right,
right,
left,
trivial,
/-
The goal looks funny, but think
about what it means. It is the
predicate, (λ (a : ℕ), a ∉ z),
applied to the value, 1, which
is to say it's the proposition,
1 ∉ z. That in turn is ¬ 1 ∈ z.
And that, in turn, is just the
proposition that 1 ∈ z → false.
So assume 1 ∈ z and show false
to prove it. What is 1 ∈ z? It's
the proposition that 1 is one of
the elements in the set, written
as a disjunction, so use case
analysis!
-/
assume pf,
cases pf,
/-
Now we need a proof that 1 ≠ 4. The
dec_trivial tactic defined in the Lean's
standard library "decides" many purely
arithmetic propositions. That is, it
generates either a proof that such a
proposition is true if it's true. It
will also generate a proof that its
negation is true if that is the case.
The dec_trivial tactic implements a
"decision procedure" for sufficiently
simple propositions involved numbers.
Here we use it to give us a proof of
1 ≠ 4. We can then use that to get a
proof of false and use false elim to
eliminate the current case on grounds
that it is based on contradictory
assumptions (and thus can't happen).
-/
have h : 1 ≠ 4 := dec_trivial,
/-
The contradiction tactic looks for a
explicit contradiction in the context
and if it finds one, applies false.elim
to finish proving the goal.
-/
contradiction,
cases pf,
have h : 1 ≠ 3 := dec_trivial,
contradiction,
cases pf,
have h : 1 ≠ 2 := dec_trivial,
contradiction,
have f : false := pf,
contradiction,
end

-- SUMMARY SO FAR

/-
A set can be characterized by
a predicate: one that is true
for each member of the set and
false otherwise.

The union of two sets is given
by the disjunction (or) of the
predicates:
(a ∈ y ∪ z) = (a ∈ y) ∨ (a ∈ z)

The conjunction is defined by
their conjunction:
(x ∈ y ∩ z) = (x ∈ y ∧ a ∈ z)

Their difference is defined by
the conjunction of the first and
the negation of the second:
(a ∈ y \ z) = ( a ∈ y) ∧ (¬ a ∈ z)
-/

-- PART II

/-
Now we introduce addition basic
set theory concepts: subsets,
power sets, and product sets.
-/

-- Subset

/-
Subset, denoted ⊂, is a binary
relation on sets, denoted X ⊂ Y,
where X and Y are sets, that is
true iff every element of X is
also in (an element of) Y.

So, { 1, 2 } ⊂ { 1, 2, 3 } but
¬ { 1, 2 } ⊂ { 1, 3, 4}. In the
first case, every element of the
set, { 1, 2 }, is also in the set
{ 1, 2, 3 }, so { 1, 2 } is a
subset of { 1, 2, 3 }, but that
is not the case for { 1, 2 } and
{ 1, 3, 4 }.

Remember that in Lean, "set" is
not a type but a type constructor,
a.k.a., a polymorphic type. Rather,
for any type, T, set T is a type,
namely the type of sets containing
elements of type T. Even the empty
set always has an element type, so,
for example, the empty set of ℕ is
not the same as the empty set of
strings.
-/

/-
EXERCISE: List all of the subsets
of each of the following sets of ℕ.

* ∅
* { 1 }
* { 1, 2 }
* { 1, 2, 3 }

EXERCISE: How many subsets are there
f a set containing n elements. Does
your formula work even for the empty
set, containing 0 elements?
-/

theorem foo : x ⊂ y := _


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

import tactic -- imports all the Lean tactics
import data.real.basic -- the reals
/-!

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

If we define

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

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

`{ n : ℕ | is_even n }`

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

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

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

/-

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

-/

open set

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

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

example : 74 ∈ {n : ℕ | is_even n} :=
begin
  sorry,
end

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

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

-- likewise, the theory of positive negative real numbers is not interesting
example : ∀ x, x ∉ {r : ℝ | 0 < r ∧ r < 0} :=
begin
  sorry,
end


/-
# References

1. Levin, Oscar. Discrete Mathematics: An Open Introduction. 3rd ed., n.d.
   https://discrete.openmathbooks.org/pdfs/dmoi3-tablet.pdf.
-/

import Mathlib.Tactic.NormNum
import Mathlib.Tactic.Ring

/--[1]
A 0th-indexed geometric sequence.
-/
structure Geometric where
  a₀ : Int
  r : Int

namespace Geometric

/--[1]
The value of the `n`th term of an geometric sequence.
-/
def termClosed (seq : Geometric) (n : Nat) : Int := seq.a₀ * seq.r ^ n

/--[1]
The value of the `n`th term of an geometric sequence.
-/
def termRecursive : Geometric → Nat → Int
  | seq,       0 => seq.a₀
  | seq, (n + 1) => seq.r * (seq.termRecursive n)

/--[1]
The recursive definition and closed definitions of a geometric sequence are
equivalent.
-/
theorem term_recursive_closed (seq : Geometric) (n : Nat)
        : seq.termRecursive n = seq.termClosed n :=
  Nat.recOn
    n
    (by unfold termClosed termRecursive; norm_num)
    (fun n ih => calc
      seq.termRecursive (n + 1)
          = seq.r * (seq.termRecursive n) := rfl
        _ = seq.r * (seq.termClosed n) := by rw [ih]
        _ = seq.r * (seq.a₀ * seq.r ^ n) := rfl
        _ = seq.a₀ * seq.r ^ (n + 1) := by ring
        _ = seq.termClosed (n + 1) := rfl)

/--[1]
Summation of the first `n` terms of a geometric sequence.
-/
def sum : Geometric → Nat → Int
  |   _,       0 => 0
  | seq, (n + 1) => seq.termClosed n + seq.sum n

end Geometric


import data.real.basic

/-
In the previous file, we saw how to rewrite using equalities.
The analogue operation with mathematical statements is rewriting using
equivalences. This is also done using the `rw` tactic.
Lean uses ↔ to denote equivalence instead of ⇔.

In the following exercises we will use the lemma:

  sub_nonneg {x y : ℝ} : 0 ≤ y - x ↔ x ≤ y

The curly braces around x and y instead of parentheses mean Lean will always try to figure out what
x and y are from context, unless we really insist on telling it (we'll see how to insist much later).
Let's not worry about that for now.

In order to announce an intermediate statement we use:

  have my_name : my statement,

This triggers the apparition of a new goal: proving the statement. After this is done,
the statement becomes available under the name `my_name`.
We can focus on the current goal by typing tactics between curly braces.
-/

example {a b c : ℝ} (hab : a ≤ b) : c + a ≤ c + b :=
begin
  rw ← sub_nonneg,
  have key : (c + b) - (c + a) = b - a, -- Here we introduce an intermediate statement named key
  { ring, },  -- and prove it between curly braces
  rw key, -- we can now use the key statement
  rw sub_nonneg,
  exact hab,
end

/-
Of course the previous lemma is already in the core library, named `add_le_add_left`, so we can use it below.

Let's prove a variation (without invoking commutativity of addition since this would spoil our fun).
-/

-- 0009
example {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c :=
begin
  -- `linarith,` -- Finish him!
  suffices : (b + c) - (a + c) = b - a,
  { rw ← sub_nonneg,
  rw this,
  apply sub_nonneg.2 hab },
  ring,
end

/-
Let's see how we could use this lemma. It is already in the core library, under the name `add_le_add_right`:

  add_le_add_right {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c

This can be read as: "add_le_add_right is a function that will take as input real numbers a and b, an
assumption `hab` claiming a ≤ b and a real number c, and will output a proof of a + c ≤ b + c".

In addition, recall that curly braces around a b mean Lean will figure out those arguments unless we
insist to help. This is because they can be deduced from the next argument `hab`.
So it will be sufficient to feed `hab` and c to this function.
-/

example {a b : ℝ}  (ha : 0 ≤ a) : b ≤ a + b :=
begin
  calc b = 0 + b : by ring
     ... ≤ a + b : by exact add_le_add_right ha b,
end

/-
In the second line of the above proof, we need to prove 0 + b ≤ a + b.
The proof after the colon says: this is exactly lemma `add_le_add_right` applied to ha and b.
Actually the `calc` block expects proof terms, and the `by` keyword is used to tell Lean we will use tactics
to build such a proof term. But since the only tactic used in this block is `exact`, we can skip
tactics entirely, and write:
-/

example (a b : ℝ)  (ha : 0 ≤ a) : b ≤ a + b :=
begin
  calc b = 0 + b : by ring
     ... ≤ a + b : add_le_add_right ha b,
end

/- Let's do a variant. -/

-- 0010
example (a b : ℝ) (hb : 0 ≤ b) : a ≤ a + b :=
begin
  sorry
end

/-
The two preceding examples are in the core library :

  le_add_of_nonneg_left  {a b : ℝ} (ha : 0 ≤ a) : b ≤ a + b
  le_add_of_nonneg_right {a b : ℝ} (hb : 0 ≤ b) : a ≤ a + b

Again, there won't be any need to memorize those names, we will
soon see how to get rid of such goals automatically.
But we can already try to understand how their names are built:
"le_add" describe the conclusion "less or equal than some addition"
It comes first because we are focussed on proving stuff, and
auto-completion works by looking at the beginning of words.
"of" introduces assumptions. "nonneg" is Lean's abbreviation for non-negative.
"left" or "right" disambiguates between the two variations.

Let's use those lemmas by hand for now.

Note that you can have several inequalities steps in a `calc` block,
transitivity of inequalities will be used automatically to assemble
the pieces.
-/

-- 0011
example (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
begin
  calc
  0   ≤ 0 : le_refl 0
  ... ≤ a : ha
  ... ≤ a + b : le_add_of_nonneg_right hb,
end

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

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

/-
In the above examples, we prepared proofs of assumptions of our lemmas beforehand, so
that we could feed them to the lemmas. This is called forward reasoning.
The `calc` proofs also belong to this category.

We can also announce the use of a lemma, and provide proofs after the fact, using
the `apply` tactic. This is called backward reasoning because we get the conclusion
first, and provide proofs later. Using `rw` on the goal (rather than on an assumption
from the local context) is also backward reasoning.

Let's do that using the lemma

  mul_nonneg {x y : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) : 0 ≤ x*y
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  rw ← sub_nonneg,
  have key : b*c - a*c = (b - a)*c,
  { ring },
  rw key,
  apply mul_nonneg, -- Here we don't provide proofs for the lemma's assumptions
  -- Now we need to provide the proofs.
  { rw sub_nonneg,
    exact hab },
  { exact hc },
end

/-
Let's prove the same statement using only forward reasoning: announcing stuff,
proving it by working with known facts, moving forward.
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  have hab' : 0 ≤ b - a,
  { rw ← sub_nonneg at hab,
    exact hab, },
  have h₁ : 0 ≤ (b - a)*c,
  { exact mul_nonneg hab' hc },
  have h₂ : (b - a)*c = b*c - a*c,
  { ring, },
  have h₃ : 0 ≤ b*c - a*c,
  { rw h₂ at h₁,
    exact h₁, },
  rw sub_nonneg at h₃,
  exact h₃,
end

/-
One reason why the backward reasoning proof is shorter is because Lean can
infer of lot of things by comparing the goal and the lemma statement. Indeed
in the `apply mul_nonneg` line, we didn't need to tell Lean that x = b - a
and y = c in the lemma. It was infered by "unification" between the lemma
statement and the goal.

To be fair to the forward reasoning version, we should introduce a convenient
variation on `rw`. The `rwa` tactic performs rewrite and then looks for an
assumption matching the goal. We can use it to rewrite our latest proof as:
-/

example (a b c  : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  have hab' : 0 ≤ b - a,
  { rwa ← sub_nonneg at hab, },
  have h₁ : 0 ≤ (b - a)*c,
  { exact mul_nonneg hab' hc },
  have h₂ : (b - a)*c = b*c - a*c,
  { ring, },
  have h₃ : 0 ≤ b*c - a*c,
  { rwa h₂ at h₁, },
  rwa sub_nonneg at h₃,
end

/-
Let's now combine forward and backward reasoning, to get our most
efficient proof of this statement. Note in particular how unification is used
to know what to prove inside the parentheses in the `mul_nonneg` arguments.
-/
example (a b c : ℝ) (hc : 0 ≤ c) (hab : a ≤ b) : a*c ≤ b*c :=
begin
  rw ← sub_nonneg,
  calc 0 ≤ (b - a)*c  : mul_nonneg (by rwa sub_nonneg) hc
     ... =  b*c - a*c : by ring,
end

/-
Let's now practice all three styles using:

  mul_nonneg_of_nonpos_of_nonpos {a b : α} (ha : a ≤ 0) (hb : b ≤ 0) : 0 ≤ a * b

  sub_nonpos {a b : α} : a - b ≤ 0 ↔ a ≤ b
-/

/- First using mostly backward reasoning -/
-- 0013
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  sorry
end

/- Using forward reasoning -/
-- 0014
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  sorry
end

/-- Using a combination of both, with a `calc` block -/
-- 0015
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  sorry
end

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

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

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

But this can be rephrased as

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

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

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

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

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

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

Let's practise using `intros`. -/

-- 0016
example (a b : ℝ): 0 ≤ b → a ≤ a + b :=
begin
  intros hb,
  exact le_add_of_nonneg_right hb,
end

/-
What about lemmas having more than one assumption? For instance:

  add_nonneg {a b : ℝ} (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b

A natural idea is to use the conjunction operator (logical AND), which Lean denotes
by ∧. Assumptions built using this operator can be decomposed using the `cases` tactic,
which is a very general assumption-decomposing tactic.
-/
example {a b : ℝ} : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b :=
begin
  intros hyp,
  cases hyp with ha hb,
  exact add_nonneg ha hb,
end

/-
Needing that intermediate line invoking `cases` shows this formulation is not what is used by
Lean. It rather sees `add_nonneg` as two nested implications:
if a is non-negative then if b is non-negative then a+b is non-negative.
It reads funny, but it is much more convenient to use in practice.
-/
example {a b : ℝ} : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
add_nonneg

/-
The above pattern is so common that implications are defined as right-associative operators,
hence parentheses are not needed above.

Let's prove that the naive conjunction version implies the funny Lean version. For this we need
to know how to prove a conjunction. The `split` tactic creates two goals from a conjunction goal.
It can also be used to create two implication goals from an equivalence goal.
-/
example {a b : ℝ} (H : (0 ≤ a ∧ 0 ≤ b) → 0 ≤ a + b) : 0 ≤ a → (0 ≤ b → 0 ≤ a + b) :=
begin
  intros ha,
  intros hb,
  apply H,
  split,
  exact ha,
  exact hb,
end

/-
Let's practice `cases` and `split`. In the next exercise, P, Q and R denote
unspecified mathematical statements.
-/

-- 0017
example (P Q R : Prop) : P ∧ Q → Q ∧ P :=
begin
  intros hpq,
  -- exact ⟨hpq.2, hpq.1⟩, -- Finish him!
  cases hpq,
  split,
  exacts [hpq_right, hpq_left],
end

example (P Q R : Prop) : P ∧ Q → Q ∧ P := λhpq, ⟨hpq.2, hpq.1⟩

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

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

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

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

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

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

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

-- 0019
example (P Q R : Prop) : (P ∧ Q → R) ↔ (P → (Q → R)) :=
begin
  split,
  { exact λpqr p q, pqr ⟨p,q⟩ },
  { exact λpqr pq, pqr pq.left pq.right }
end

-- One-liner
example (P Q R : Prop) : (P ∧ Q → R) ↔ (P → (Q → R)) :=
⟨ λpqr p q, pqr ⟨p,q⟩, λpqr pq, pqr pq.left pq.right ⟩

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

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

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

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

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

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

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

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

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

/-
Final exercise

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

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

  dvd_refl (a : ℕ) : a ∣ a

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

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

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

-- 0022
example (a b : ℕ) : a ∣ b ↔ gcd a b = a :=
begin
  split,
  { intro hab,
    apply dvd_antisymm,
    swap,
    apply dvd_gcd_iff.2 ⟨dvd_refl _, _⟩,
    assumption,
    have hdv : gcd a b ∣ a ∧ gcd a b ∣ b := dvd_gcd_iff.1 (dvd_refl $ gcd a b),
    exact hdv.left},
  intro hab,
  have hdv : a ∣ gcd a b → a ∣ a ∧ a ∣ b := dvd_gcd_iff.1,
  rw hab at hdv,
  exact (hdv (dvd_refl a)).right,
end


-- Desigualdad_con_logaritmos.lean
-- Si a, b ∈ ℝ tales que a ≤ b, entonces log(1 + eᵃ) ≤ log(1 + eᵇ).
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 23-septiembre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que si a, b ∈ ℝ tales que
--    a ≤ b
-- entonces
--    log (1 + exp a) ≤ log (1 + exp b) :=
-- ----------------------------------------------------------------------

import analysis.special_functions.log.basic
open real
variables a b : ℝ

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

example
  (h : a ≤ b)
  : log (1 + exp a) ≤ log (1 + exp b) :=
begin
  have h₀ : 0 < 1 + exp a,
  { apply add_pos,
    exact one_pos,
    apply exp_pos, },
  have h₁ : 0 < 1 + exp b,
  { apply add_pos,
    exact one_pos,
    apply exp_pos },
  apply (log_le_log h₀ h₁).mpr,
  apply add_le_add,
   apply le_refl,
  apply exp_le_exp.mpr h,
end

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

example
  (h : a ≤ b)
  : log (1 + exp a) ≤ log (1 + exp b) :=
begin
  have h₀ : 0 < 1 + exp a := add_pos one_pos (exp_pos a),
  have h₁ : 0 < 1 + exp b := add_pos one_pos (exp_pos b),
  exact (log_le_log h₀ h₁).mpr (add_le_add rfl.ge (exp_le_exp.mpr h))
end

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

lemma aux : 0 < 1 + exp a :=
add_pos one_pos (exp_pos a)

example
  (h : a ≤ b)
  : log (1 + exp a) ≤ log (1 + exp b) :=
begin
  have h₀ : 0 < 1 + exp a := aux a,
  have h₁ : 0 < 1 + exp b := aux b,
  exact (log_le_log h₀ h₁).mpr (add_le_add rfl.ge (exp_le_exp.mpr h))
end

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

example
  (h : a ≤ b)
  : log (1 + exp a) ≤ log (1 + exp b) :=
(log_le_log (aux a) (aux b)).mpr (add_le_add rfl.ge (exp_le_exp.mpr h))


/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Chris Hughes, Floris van Doorn
-/
import data.nat.basic
/-!
# The factorial function

-/

namespace nat
variables {m n : ℕ}

/-- `nat.factorial n` is the factorial of `n`. -/
@[simp] def factorial : nat → nat
| 0        := 1
| (succ n) := succ n * factorial n

localized "notation n `!`:10000 := nat.factorial n" in nat

@[simp] theorem factorial_zero : 0! = 1! := rfl

@[simp] theorem factorial_succ (n : ℕ) : n.succ! = succ n * n! := rfl

@[simp] theorem factorial_one : 1! = 1 := rfl

theorem mul_factorial_pred (hn : 0 < n) : n * (n - 1)! = n! :=
nat.sub_add_cancel hn ▸ rfl

theorem factorial_pos : ∀ n, 0 < n!
| 0        := zero_lt_one
| (succ n) := mul_pos (succ_pos _) (factorial_pos n)

theorem factorial_ne_zero (n : ℕ) : n! ≠ 0 := ne_of_gt (factorial_pos _)

theorem factorial_dvd_factorial {m n} (h : m ≤ n) : m! ∣ n! :=
begin
  induction n with n IH; simp,
  { have := eq_zero_of_le_zero h, subst m, simp },
  { cases eq_or_lt_of_le h with he hl,
    { subst m, simp },
    { apply dvd_mul_of_dvd_right (IH (le_of_lt_succ hl)) } }
end

theorem dvd_factorial : ∀ {m n}, 0 < m → m ≤ n → m ∣ n!
| (succ m) n _ h := dvd_of_mul_right_dvd (factorial_dvd_factorial h)

@[mono] theorem factorial_le {m n} (h : m ≤ n) : m! ≤ n! :=
le_of_dvd (factorial_pos _) (factorial_dvd_factorial h)

lemma factorial_mul_pow_le_factorial : ∀ {m n : ℕ}, m! * m.succ ^ n ≤ (m + n)!
| m 0     := by simp
| m (n+1) :=
by  rw [← add_assoc, nat.factorial_succ, mul_comm (nat.succ _), pow_succ', ← mul_assoc];
  exact mul_le_mul factorial_mul_pow_le_factorial
    (nat.succ_le_succ (nat.le_add_right _ _)) (nat.zero_le _) (nat.zero_le _)

lemma monotone_factorial : monotone factorial := λ n m, factorial_le

lemma factorial_lt (h0 : 0 < n) : n! < m! ↔ n < m :=
begin
  split; intro h,
  { rw [← not_le], intro hmn, apply not_le_of_lt h (factorial_le hmn) },
  { have : ∀(n : ℕ), 0 < n → n! < n.succ!,
    { intros k hk, rw [factorial_succ, succ_mul, lt_add_iff_pos_left],
      apply mul_pos hk (factorial_pos k) },
    induction h with k hnk generalizing h0,
    { exact this _ h0, },
    { refine lt_trans (h_ih h0) (this _ _), exact lt_trans h0 (lt_of_succ_le hnk) }}
end

lemma one_lt_factorial : 1 < n! ↔ 1 < n :=
by { convert factorial_lt _, refl, exact one_pos }

lemma factorial_eq_one : n! = 1 ↔ n ≤ 1 :=
begin
  split; intro h,
  { rw [← not_lt, ← one_lt_factorial, h], apply lt_irrefl },
  { cases h with h h, refl, cases h, refl }
end

lemma factorial_inj (h0 : 1 < n!) : n! = m! ↔ n = m :=
begin
  split; intro h,
  { rcases lt_trichotomy n m with hnm|hnm|hnm,
    { exfalso, rw [← factorial_lt, h] at hnm, exact lt_irrefl _ hnm,
      rw [one_lt_factorial] at h0, exact lt_trans one_pos h0 },
    { exact hnm },
    { exfalso, rw [← factorial_lt, h] at hnm, exact lt_irrefl _ hnm,
      rw [h, one_lt_factorial] at h0, exact lt_trans one_pos h0 }},
  { rw h }
end

lemma self_le_factorial : ∀ n : ℕ, n ≤ n!
| 0 := zero_le_one
| (k+1) := le_mul_of_one_le_right k.zero_lt_succ.le (nat.one_le_of_lt $ nat.factorial_pos _)

lemma lt_factorial_self {n : ℕ} (hi : 3 ≤ n) : n < n! :=
begin
  rw [← succ_pred_eq_of_pos ((zero_lt_two.trans (lt.base 2)).trans_le hi), factorial_succ],
  exact lt_mul_of_one_lt_right ((pred n).succ_pos) ((one_lt_two.trans_le
    (le_pred_of_lt (succ_le_iff.mp hi))).trans_le (self_le_factorial _)),
end

lemma add_factorial_succ_lt_factorial_add_succ {i : ℕ} (n : ℕ) (hi : 2 ≤ i) :
  i + (n + 1)! < (i + n + 1)! :=
begin
  rw [factorial_succ (i + _), succ_eq_add_one, add_mul, one_mul],
  have : i ≤ i + n := le.intro rfl,
  exact add_lt_add_of_lt_of_le (this.trans_lt ((lt_mul_iff_one_lt_right (zero_lt_two.trans_le
    (hi.trans this))).mpr (lt_iff_le_and_ne.mpr ⟨(i + n).factorial_pos, λ g,
    nat.not_succ_le_self 1 ((hi.trans this).trans (factorial_eq_one.mp g.symm))⟩))) (factorial_le
    ((le_of_eq (add_comm n 1)).trans ((add_le_add_iff_right n).mpr (one_le_two.trans hi)))),
end

lemma add_factorial_lt_factorial_add {i n : ℕ} (hi : 2 ≤ i) (hn : 1 ≤ n) :
  i + n! < (i + n)! :=
begin
  cases hn,
  { rw factorial_one,
    exact lt_factorial_self (succ_le_succ hi) },
  { exact add_factorial_succ_lt_factorial_add_succ _ hi }
end

lemma add_factorial_succ_le_factorial_add_succ (i : ℕ) (n : ℕ) :
  i + (n + 1)! ≤ (i + (n + 1))! :=
begin
  by_cases i2 : 2 ≤ i,
  { exact (n.add_factorial_succ_lt_factorial_add_succ i2).le },
  { cases (not_le.mp i2) with _ i0,
    { change 1 + (n + 1)! ≤ (1 + n + 1) * (1 + n)!,
      rw [add_mul, one_mul, add_comm 1 n],
      exact (add_le_add_iff_right _).mpr (one_le_mul (nat.le_add_left 1 n) (n + 1).factorial_pos) },
    { rw [nat.le_zero_iff.mp (nat.succ_le_succ_iff.mp i0), zero_add, zero_add] } }
end

lemma add_factorial_le_factorial_add (i : ℕ) {n : ℕ} (n1 : 1 ≤ n) :
  i + n! ≤ (i + n)! :=
begin
  cases n1 with h,
  { exact self_le_factorial _ },
  { exact add_factorial_succ_le_factorial_add_succ i h }
end

end nat


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

import tactic
import data.real.basic
import analysis.calculus.parametric_integral

/-

# Smooth functions

-/

noncomputable def φ₁ : ℝ → ℝ × ℝ :=
λ x, (real.cos x, real.sin x)

example : cont_diff_on ℝ ⊤ φ₁ (set.Icc 0 1) :=
begin
  apply cont_diff_on.prod,
  { apply cont_diff.cont_diff_on,
    exact real.cont_diff_cos,
  },
  { apply cont_diff.cont_diff_on,
    exact real.cont_diff_sin,
  },
end

open real
noncomputable def φ₂ : ℝ → ℝ × ℝ × ℝ :=
λ x, (real.sin x, x^4+37*x^2+1, abs x)

example : cont_diff_on ℝ ⊤ φ₂ (set.Icc 0 1) :=
cont_diff_sin.cont_diff_on.prod $
  (((cont_diff_id.pow 4).add (cont_diff_const.mul (cont_diff_id.pow 2))).add
  cont_diff_const).cont_diff_on.prod $
  cont_diff_on_id.congr (λ x hx, abs_of_nonneg hx.1)

/- Let `a≤b` and `c≤d` be reals. Let φ : [a,b] → [c,d] and ψ : [c,d] → [a,b]
  be inverse bijections, and assume φ is smooth and φ' is nonvanishing
  on [a,b]. Then ψ is smooth and ψ' is nonvanishing on [c,d],
  and ψ'(y)*φ'(ψ(y))=1.
-/
example (φ : ℝ → ℝ) (ψ : ℝ → ℝ) (a b c d : ℝ)
  (hab : a ≤ b) (hcd : c ≤ d) (hφ : ∀ x, x ∈ set.Icc a b → φ x ∈ set.Icc c d)
  (hψ : ∀ y, y ∈ set.Icc c d → ψ y ∈ set.Icc a b)
  (left_inv : ∀ x, x ∈ set.Icc a b → ψ (φ x) = x)
  (right_inv : ∀ y, y ∈ set.Icc c d → φ (ψ y) = y)
  (hφdiff : cont_diff_on ℝ ⊤ φ (set.Icc a b))
  (hφregular : ∀ x, x ∈ set.Icc a b → fderiv ℝ φ x ≠ 0) :
  cont_diff_on ℝ ⊤ ψ (set.Icc c d) ∧
  ∀ y, y ∈ set.Icc c d → ∀ z, fderiv ℝ ψ y (fderiv ℝ φ (ψ(y)) z) = z :=
sorry

/-
Heather Macbeth: @Kevin Buzzard This is a toy case of the inverse function theorem,
but you might need to glue together several related results. Some starting points:
docs#cont_diff_at.to_local_inverse, docs#has_strict_fderiv_at.local_inverse_unique

Heather Macbeth: If you want to construct the inverse, and you want to avoid invoking
the inverse function theorem on Banach spaces, you can also route through order theory
for a purely one-dimensional construction. Look at the construction of docs#real.arctan
for a model; it uses docs#strict_mono_on.order_iso
-/


import tactic data.nat.basic data.nat.prime
open nat
open function

-- 3. Infinitely Many Primes

-- Today we will prove that there are infinitely many primes using mathlib library.
-- Our focus will be on how to use the library to prove more complicated theorems.

-- 3.1. Equality

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

  ``rw``

    If ``f`` is a term of type ``P = Q`` (or ``P ↔ Q``), then
    ``rw f`` replaces ``P`` with ``Q`` in the target.
    Other variants:
      ``rw f at hp``, ``rw ←f``, ``rw ←f at hr``.

  Delete the ``sorry,`` below and replace them with a legitimate proof.

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

theorem add_self_self_eq_double(x : ℕ) : x + x = 2 * x :=
begin
  ring,
end

/-
For the following problem, use
  mul_comm a b : a * b = b * a
-/

example (a b c d : ℕ)
  (hyp : c = d * a + b)
  (hyp' : b = a * d)
: c = 2 * (a * d) :=
begin
  rw hyp,
  rw hyp',
  ring,
end

-- The above was solved, but comment above says to use mul_comm, so here's another go
example (a b c d : ℕ)
  (hyp : c = d * a + b)
  (hyp' : b = a * d)
: c = 2 * (a * d) :=
begin
  -- In order to use mul_comm I had to eliminate the 2 * first by using two_mul
  rw two_mul,
  rw hyp,
  rw hyp',
  rw mul_comm, -- then can use mul_comm with tries refl afterwards and this solves the goal.
end

/-
For the following problem, use
  sub_self (x : ℕ) : x - x = 0
-/

example (a b c d : ℕ)
  (hyp : c = b * a - d)
  (hyp' : d = a * b)
: c = 0 :=
begin
  rw hyp,
  rw hyp',
  -- goal is now b * a - a * b, so we can't yet apply rw sub_self
  rw mul_comm, -- now have a * b - a * b, but
  -- rw ← hyp', -- this replaces both sets of a * b to d
  -- rw nat.sub_self d,
  rw nat.sub_self _, -- you don't actually need to reduce the a * b to d
  -- I'm not sure why I needed nat.sub_self. But just using rw sub_self didn't work.
end

-- 3.1.1. Surjective functions

--  Recall that a function f : X → Y is surjective if for every y : Y there exists a term x : X such that
--  f(x) = y. In type theory, for every function f we can define a corresponding proposition
--  surjective (f) := ∀ y, ∃ x, f x = y and a function being surjective is equivalent to saying
--  that the proposition surjective(f) is inhabited.

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

``unfold``

  If it gets hard to keep track of the definition of ``surjective``,
  you can use ``unfold surjective,`` or ``unfold surjective at h,``
  to get rid of it.

Delete the ``sorry,`` below and replace them with a legitimate proof.

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

variables X Y Z : Type
variables (f : X → Y) (g : Y → Z)

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

example
  (hf : surjective f)
  (hg : surjective g)
  : surjective (g ∘ f) :=
begin
  -- These unfolds aren't necessary, bu they help to see the proof.
  unfold surjective,
  unfold surjective at hf,
  unfold surjective at hg,
  -- our goal is ∀ (b : Z) .., so we can introduce an arbitary z.
  intro z,
  -- we can now use hg with this z, to give use a y : Y
  -- we do this be creating the hypothesis that there must exist a y : Y
  have h1 : ∃ y : Y, g y = z, from hg z,
  -- and then uses cases on this hypothesis to obtain an arbitary y : Y
  cases h1 with y,
  -- similar as above, we now use hf with this y to first creat the hypothesis
  -- that an x : X must exist
  have h2 : ∃ x : X, f x = y, from hf y,
  -- then use cases to obtiain an arbitary x : X
  cases h2 with x,
  -- finally our goal is in sight, the goal is to prove
  -- ∃ (a : X), (g ∘ f) a = z
  -- and we can just use the x : X that we have proved exists above.
  use x,
  -- the finish tatic will now finish this proof.
  finish,
  -- If you use suggest rather than finish above, it suggest the following to finish the proof.
  -- exact (eq.congr_right h1_h).mp (congr_arg g (eq.symm h2_h)).symm,
 end

example
  (hgf : surjective (g ∘ f))
  : surjective g :=
begin
  -- The following proof we'll use is
  -- take any z ∈ Z,
  -- since g ∘ f is surjective, ∃ x ∈ X, s.t. (g ∘ f) x = z,
  -- so g(f(x)) = z,
  -- set y = f(x) , note y ∈ Y.
  -- then, g(b) = g(f(x)) = y
  -- see https://www.youtube.com/watch?v=bTKOC3Rst8c

  -- Again these unfolds aren't required but they help.
  unfold surjective,
  unfold surjective at hgf,
  -- take any z : Z
  intro z,
  -- since g ∘ f is surjective, ∃ x ∈ X, s.t. (g ∘ f) x = z,
  have h1 : ∃ (a : X), (g ∘ f) a = z, from hgf z,
  -- so (g ∘ f) x = z
  cases h1 with x h2,

  -- In order to set y = f x, we first prove that there exists a y : Y
  have h2 : ∃ (y : Y), y = f x :=
  begin
    use f x,
  end,
  -- this this we have our y.
  cases h2 with y h3,

  -- our proof is these exists, and y is the value to use.
  use y,
  -- hint, tells use finish will finish the goal
  --finish,
  -- But this doesn't teach us anything. These two rewrites will close the goal.
  rw ← h2,
  rw h3,
end

-- 3.2. Creating subgoals
/--------------------------------------------------------------------------

Often when we write a long proof in math, we break it up into simpler problems. This is done in
Lean using the have tactic.

``have``

  ``have hp : P,`` creates a new goal with target ``P`` and
  adds ``hp : P`` as a hypothesis to the original goal.

have is crucial for being able to use theorems from the library. To use these theorems you have to
create terms that match the hypothesis exactly. Consider the following example. The type n > 0
is not the same as 0 < n. If you need a term of type ``n > 0 `` and you only have ``hn : 0 < n``,
then you can use ``have hn2 : n > 0, linarith,`` and you will have constructed a term hn2 of type n > 0.

Warning: If you need to type the divisibility symbol, type \mid. This is not the vertical line
on your keyboard.

You'll need the following theorem from the library:

nat.dvd_sub : n ≤ m → k ∣ m → k ∣ n → k ∣ m - n

   (Note that you don't need to provide n m k as inputs to dvd_sub
   Lean can infer these from the rest of the expression.
   More on this tomorrow.)

Delete the ``sorry,`` below and replace it with a legitimate proof.

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

theorem dvd_sub_one {p a : ℕ} : (p ∣ a) → (p ∣ a + 1) → (p ∣ 1) :=
begin
  intro h1, -- intro h1 : p ∣ a
  intro h2, -- intro h2 : p ∣ a + 1
  -- Our proof strategy is the to apply the proof
  --    nat.dvd_sub : n ≤ m → k ∣ m → k ∣ n → k ∣ m - n
  -- for this we have the follow
  --     n = a
  --     m = a + 1
  --     k = p
  -- so
  -- nat.dvd_sub : a ≤ a + 1 → p ∣ a + 1 → p ∣ a → p ∣ a + 1 - a
  -- Thus before we can apply this, we need to have these hypotheses
  --have h3 : p ∣ a + 1 - a := begin
  have h3 : p ∣ a + 1 - a := begin
    refine dvd_sub _ h2 h1,
    linarith,
  end,
  -- This is showing off the power of finish, but it teaches us less.
  -- finish,
  -- suggest gives the folowing one line proof, which tells us more but is not simple
  -- exact (nat.dvd_add_right h1).mp h2,
  -- A far more wordy way to prove it, but it teaches us much more.
  -- First show that a + 1 - a = 1 which is easily prooved by norm_num
  have h4 : a + 1 - a = 1, by norm_num,
  -- rw this at h3 to give
  -- h3 : p ∣ 1
  rw h4 at h3,
  -- our goal is h3, one of our assumptions and so we are done.
  -- assumption,
  -- exact h3 would also do and is possibly cleaner.
  exact h3,
end

-- 3.3. Infinitely many primes

-- We’ll now prove that there are infinitely many primes. The strategy is to show that there is a
-- prime greater than n, for every natural number n. We will choose this prime to be smallest
-- non-trivial factor of n! + 1. We’ll need the following definitions and theorems from the library.

-- From aobve, `dvd_sub_one : (p ∣ a) → (p ∣ a + 1) → (p ∣ 1)`

-- Primes
--        `m ∣ n := ∃ k : ℕ, m * k = n`
--        `m.prime :=  2 ≤ p ∧ (∀ (m : ℕ), m ∣ p → m = 1 ∨ m = p)`
--        `prime.not_dvd_one : (prime p) → ¬ p ∣ 1`

-- Factorials
--        `n.fact := n!  --n factorial`
--        `fact_pos : ∀ (n : ℕ), 0 < n.fact`
--        `dvd_fact : 0 < m → m ≤ n → m ∣ n.fact`

-- Smallest factor
--        `n.min_fac := smallest non-trivial factor of n`
--        `min_fac_prime : n ≠ 1 → n.min_fac.prime`
--        `min_fac_pos : ∀ (n : ℕ), 0 < n.min_fac`
--        `min_fac_dvd : ∀ (n : ℕ), n.min_fac ∣ n`

noncomputable theory
open_locale classical

theorem exists_infinite_primes (n : ℕ) : ∃ p, prime p ∧ p ≥ n :=
begin
  -- This first line was given with no explanation.
  set p:= (n.factorial + 1).min_fac,

  -- Our first goal is to prove this this p is prime. To do this we'll use min_fac_prime
  -- but from notes above, this requies a proof that (n.factorial + 1) ≠ 1
  have h : factorial n + 1 ≠ 1 := begin
    apply ne_of_gt,
    apply succ_lt_succ,
    apply factorial_pos _,
  end,
  -- we can now prove p is prime as required.
  have pp : prime p, from min_fac_prime h,
  -- we now want to prove that p ≥ n
  have np : p ≥ n := begin
    by_contradiction,
    have h₁ : p ∣ factorial n + 1 := min_fac_dvd (factorial n + 1),
    have h₂ : p ∣ factorial n :=
    begin
      refine pp.dvd_factorial.mpr _,
      exact le_of_not_ge h,
    end,
    have h : p ∣ 1 := dvd_sub_one h₂ h₁,
    exact prime.not_dvd_one pp h,
  end,
  -- finally, the proof is straightforward.
  -- The goal is an existance proof, and p is it. So use p is the logical step
  use p,
  -- goal is now prime p ∧ p ≥ n which is exactly pp ∧ np
  exact ⟨pp, np⟩,
end

-- alternative proof : Ref: https://www.youtube.com/watch?v=b59fpAJ8Mfs
theorem infinitude_of_primes : ∀ n, ∃ p ≥ n, prime p :=
begin
  -- this proof says something for all n : N, we let n be an arbitary ℕ
  intro n,

  let m := factorial n + 1,
  let p := min_fac m,

  have pp : prime p :=
  begin
    refine min_fac_prime _,
    have h : factorial n > 0 := factorial_pos n,
    linarith,
  end,

  use p,
  split,
  {
    by_contradiction,
    have h₁ : p ∣ factorial n + 1 := min_fac_dvd m,
    have h₂ : p ∣ factorial n :=
    begin
      refine pp.dvd_factorial.mpr _,
      exact le_of_not_ge h,
    end,
    have h : p ∣ 1 := dvd_sub_one h₂ h₁,
    exact prime.not_dvd_one pp h,
  },
  {
    exact pp,
  }
end

-- For reference, here's a copy of the proof in mathlib
theorem exists_infinite_primes' (n : ℕ) : ∃ p, n ≤ p ∧ prime p :=
let p := min_fac (factorial n + 1) in
have f1 : factorial n + 1 ≠ 1, from ne_of_gt $ succ_lt_succ $ factorial_pos _,
have pp : prime p, from min_fac_prime f1,
have np : n ≤ p, from le_of_not_ge $ λ h,
  have h₁ : p ∣ factorial n, from dvd_factorial (min_fac_pos _) h,
  have h₂ : p ∣ 1, from (nat.dvd_add_iff_right h₁).2 (min_fac_dvd _),
  pp.not_dvd_one h₂,
⟨p, np, pp⟩


import tactic.interval_cases
import data.real.basic

-- Let B,C,D,E be the following sets
def B : set ℝ := {x | x^2 < 4}
def C : set ℝ := {x | 0 ≤ x ∧ x < 2}
def D : set ℝ := {x | ∃ n : ℤ, x = n ∧ x^2 < 1}
def E : set ℝ := {1}

lemma hD : D = ({0} : set ℝ) :=
begin
  simp_rw D,
  ext,
  split,
  { rintro ⟨n, rfl, hn⟩,
    simp_rw sq_lt_one_iff_abs_lt_one at hn,
    rw set.mem_singleton_iff,
    rw abs_lt at hn,
    cases hn with hl hr,
    norm_cast at *,
    interval_cases n, },
  intro hx,
  rw set.mem_singleton_iff at hx,
  simp_rw [hx, sq_lt_one_iff_abs_lt_one, exists_and_distrib_right, set.mem_set_of_eq, abs_zero, zero_lt_one, and_true],
  use (0:ℤ),
  norm_num,
end

-- Which pair of these sets has the property that neither is contained in the other?
lemma parta : ∃ S T ∈ ({B, C, D, E} : set (set ℝ)), ¬ (S ⊆ T) ∧ ¬ (T ⊆ S) :=
begin
  use D,
  split,
  { simp, },
  { use E,
    split,
    { simp, },
    { split,
      { rw set.subset_def,
        push_neg,
        use 0,
        split,
        { rw D,
          use 0,
          norm_num, },
        { simp[E], }, },
      { rw set.subset_def,
        push_neg,
        use 1,
        split,
        { simp[E], },
        { simp[D], }, }, }, },
end

-- If X is either B,C,D,E and E ⊆ X ⊆ B, what else can we deduce about X?
-- Note that figuring out what to prove is part of the question
lemma partb (X : set ℝ) (hX : X ∈ ({B,C,D,E} : set (set ℝ))) (h1 : E ⊆ X)
(h2 : X ⊆ B) : X = B ∨ X = C ∨ X = E:=
begin
  simp at hX,
  cases hX with hb hc,
  { left,
    apply hb, },
  { cases hc with h3 h4,
    right,
    left,
    apply h3,
    { cases h4 with h5 h6,
      { left,
        subst h5,
        rw D at h1,
        rw E at h1,
        finish, },
      { right,
        subst h6,
        right,
        refl, }, }, },
end

lemma partb' (X : set ℝ) (hX : X ∈ ({B,C,D,E} : set (set ℝ))) (h1 : E ⊆ X)
(h2 : X ⊆ B) : X ≠ D :=
begin
  intro hX',
  rw hX' at *,
  have hD2 : ¬ (E ⊆ D),
  { exfalso,
    simp_rw [E, hD] at h1,
    simp at h1,
    exact h1, },
  contradiction,
end


import algebra.module
import linear_algebra.basic
--infix  ` * `     := linear_map.comp
universes u v
variables {R : Type u}[comm_ring R]{M : Type v}[add_comm_group M] [module R M ]
open linear_map
def  Projector (p : M →ₗ[R] M) := p * p = p

/--
    if `p² = p` then `(1-p)² = 1 - p - p +p² = ... = 1-p`
-/
lemma Complementary (p : M →ₗ[R]M) [Projector p] : Projector (1 - p) := begin
    unfold Projector,
    rw mul_sub_left_distrib,
    iterate 2 { rw mul_sub_right_distrib,rw mul_one},
    unfold Projector at *,
    rw _inst_4,
    rw one_mul,
    simp,
end
variables (p : M→ₗ[R]M)
lemma ker_eq_im_complementary (hyp : Projector p ) : range p = ker (1-p) := begin
    apply submodule.ext,
    intros x,
    split,
        intros x_in_range,
        rw mem_range at x_in_range,
        rcases x_in_range with ⟨y,hyp_y⟩,
        rw mem_ker,
        rw ← hyp_y,
        simp,
        rw ← function.comp_apply ⇑p,
        unfold Projector at hyp,
        erw hyp,       --- here  do you have an idea to make the coersion of the  composition easy ?
end


/-
If P is an arbitrary proposition, then the
negation introduction rule tells you how to
prove ¬P. You do it by showing that P → false.

In English, you do it by *assuming* that P is
true and then *showing* that that leads to a
contradiction from which a proof of false can
be obtained. Because there is no proof of
false, the assumption that P is true has to
be wrong, so there must be no proof of P, and
that is what justifies the conclusion, ¬P.
-/

/-
As a simple example, suppose P = false. To
prove ¬ P, we just have to show P → false,
which means false → false. This is easy: if
we assume false, then we can conclude false.
-/

lemma not_false' : ¬ false := λ (f : false), f

-- Note: Lean already defines not_false

#check not_false
#check not_false'

/-
As another example, suppose we want to show
that 0 ≠ 1. This is notation for ¬ 0 = 1,
which in turn is defined as 0 = 1 → false.
-/

example : 0 ≠ 1 :=
λ (h : 0 = 1), _
/-
Here, h is assumed to be a proof of a
proposition for which *there is no proof*.
There is no equality (eq) constructor that
can produce this proof, h. There is only
eq.refl, and it can't possibly construct
a proof that two different values are equal
because it only takes one value as an argument!
Case analysis of h thus works to finish the
proof: we want to show that no matter which
eq constructor  produced h, that a proof of
false can be produced; but there is no case
in which an eq constructor (the only one being
eq.refl) could have produced h, so there are
no cases to consider, and we're done!

This is essentially false elimination: doing
a case analysis on a proof of false reveals
that there are no cases to consider and so
the conclusion is trivially true *for all of
the ways in which such a proof could have
been constructed* (of which there are none).
-/

example : false → 0 = 1 :=
λ f, match f with /- no cases!-/ end

/-
Now for the elimination rule for false. We
just saw it: if one is given or can construct
a proof of false (from inconsistent assumptions)
then one can finish off any proof by showing
that the conclusion, whatever it is, is true
for *all* of the ways that that proof could
have been constructive, again of which there
are none. False elimination is essentially
done by case analysis on a proof of false.
-/

example : ∀ (P : Prop), false → P :=
λ (P : Prop) (f : false),
    match f with /- no cases -/ end

/-
We can also use false.elim as a shorthand
-/

example : ∀ (P : Prop), false → P :=
λ (P : Prop) (f : false), false.elim f

/-
A key pattern in proof construction is
thus first to derive a contradiction, in
the sense that for some proposition, P,
one has both a proof of P and a proof of
¬ P, then to *apply* the proof of ¬ P (a
proof of P → false) to the proof of P
to derive a proof of false, and finally
then to use false elimination to finish
off the proof.
-/

example : ∀ (P Q : Prop), P → ¬ P → Q :=
λ P Q p np, false.elim (np p)

/-
Such contradictions often arise in case
analysis of other proofs. For example, if
one applies the law of the excluded middle
to derive from a proposition, P, a proof of
P ∨ ¬ P, and then does case analysis on
this proof, it will often happen that one
of the cases is self-contradictory. You
have to recognize the contradition in your
context, and then you can finish the proof
using the preceding "proof pattern".

In fact, this form of reasoning is so
common that Lean provides a shorthand
"tactic" to deal with it. If you ever
get into a situation in which you have
both (p : P) and (np : ¬ P) you can
finish off your proof "by contradition".
-/

example : ∀ (P Q : Prop), P → ¬ P → Q :=
λ P Q p np, by contradiction

/-
You may use this "tactic" if you wish on
the exam, but won't be required to do so.
-/

/-
Finally we come to the elimination rule for
negation (not). In predicate logic, this is
the rule that allows us to deduce the truth
of any proposition, P, from a proof of ¬ ¬ P.

As we've seen, this rule *not* valid in the
constructive logic of Lean but can be made
valid by accepting the axiom of the excluded
middle.
-/

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

/-
In English, suppose that for any proposition,
P, P ∨ ¬ P is true (and that you have a proof
of this disjunction). Prove that given any P,
¬ ¬ P → P.

Proof: By case analysis on the assumed proof
of P ∨ ¬ P. First consider the case in which
it is true because P is true. In this case,
the conclusion, P, is obviously true.

Now consider the case in which P ∨ ¬ P is true
because ¬ P is true. But this case can't happen
because we've already assumed that ¬ P is false
(¬(¬P) is true). In other words, this case leads
to a contradiction, and so can be ignored). QED.
-/

/-
Here's a key proof strategy. It's called "proof
by contradiction". Suppose we want to prove P.
One way to do it is to assume ¬ P and show that
this leads to a contradiction. This shows that
¬ P must be false, therefore ¬ ¬ P is true by
negation. But (assuming the law of the excluded
middle), ¬ ¬ P → P, which proves P.

Be able to state this principle clearly: To
prove P, assume that it is false, show that
this assumption leads to a contradition, which
shows ¬ ¬ P is true, and then deduce using the
principle of negation elimination (which relies
on the law of the excluded middle) that P must
be true.
-/

/-
Let's accept negation elimination as a theorem.
-/

theorem neg_elim : ∀ {P : Prop}, ¬ ¬ P → P :=
λ P nnp,
    match (classical.em P) with
    | or.inl p := p
    | or.inr np := false.elim (nnp np)
    end

/-
Exercise: Prove 0 = 0 using proof by contradiction.
-/

example : 0 = 0 :=
neg_elim _
-- Note carefully what neg_elim needs.

-- Answer: (λ nzeqz, nzeqz (eq.refl 0))

/-
We apply neg_elim, which is the strategy of
proof by contradiction.

What it requires is a proof of ¬¬0=0. It
transforms the goal 0=0 into the goal ¬¬0=0!

That is, it requires a proof of ¬0=0 → false.
To prove this, we assume ¬0=0 (the negation
of the proposition to be proved) and show that
this assumption leads to a contradiction and so
must be false. This is done by using refl to
construct a proof of 0=0 and by seeing that
this directly contradicts the assumption ¬0=0.
So the assumption is show to be false and we
conclude that ¬¬0=0 is true. Finally by the
the (classical) rule of negation elimination
we have proved 0=0. QED.
-/

/-
Proof Strategies:

Proof by negation. To prove ¬ P,  show that
assuming P leads to a contradiction, so P
must be false. That proves ¬ P.

Proof by contradiction. To prove P (notice the
difference), show that assuming ¬ P leads to a
contradiction. Conclude that ¬ P is false, that
is ¬ ¬ P is true. Then use negation elimination
to deduce that P must be true.
-/


/-
Copyright (c) 2023 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : María Inés de Frutos-Fernández
-/

import topology.basic

/-!

# API para el `interior` de un conjunto.

Fijamos un espacio topológico `X` y un subconjunto `S : set X`.

En Lean, `interior S` denota el interior de `S`, es decir, el mayor
subconjunto abierto de `S`. Sin embargo, para practicar parte
de lo introducido en el fichero `conjuntos_2.lean`, en este
fichero definiremos de nuevo esta noción (que denotaremos
`interior' S`) y crearemos su API básica.

-/

--Fijamos un espacio topológico `X` y un subconjunto `S : set X`.
variables {X : Type} [topological_space X] (S : set X)

/-

## API para espacios topológicos

`is_open S : Prop` es el predicado que dice que `S : set X` es abierto.

Los siguientes lemas de `mathlib` serán útiles en estos ejercicios:
* is_open_univ
* is_open_Union, is_open_bUnion
* is_open.inter

Los nombres son descriptivos, pero si queréis aseguraros de que un
lema dice lo que creéis, podéis utilizar #check o buscarlo en la
documentación de mathlib (https://leanprover-community.github.io/mathlib_docs/).
-/

open set

/-- El interior de `S` es la unión de todos los subconjuntos abiertos de `X`
  contenidos en `S`. -/
def interior' (S : set X) : set X := ⋃ (U ∈ {V : set X | is_open V ∧ V ⊆ S}), U

-- Lema de reescritura
lemma mem_interior' (x : X) :
  x ∈ interior' S ↔ ∃ (U : set X) (hU : is_open U) (hUS : U ⊆ S), x ∈ U :=
begin
  unfold interior',
  simp only [set.mem_set_of_eq, set.mem_Union, exists_prop],
  tauto,
end

/-- Como hemos definido `interior'` como una `bUnion`, podemos empezar
  la demostración con `apply is_open_bUnion`. -/
lemma interior'_open : is_open (interior' S) :=
begin
  sorry
end

lemma interior'_subset : interior' S ⊆ S :=
begin
  sorry
end

variable {S}

lemma subset_interior' {U : set X} (hU : is_open U) (hUS : U ⊆ S) : U ⊆ interior' S :=
begin
  sorry
end

lemma interior'_mono {S T : set X} (hST : S ⊆ T) : interior' S ⊆ interior' T :=
begin
  sorry
end

/- En lugar de empezar con `ext x`, podéis usar `apply set.subset.antisymm`,
  que dice que si `S ⊆ T` y `T ⊆ S` entonces `S = T`. -/
lemma interior'_interior' : interior' (interior' S) = interior' S :=
begin
  sorry
end

--Ejemplos de interiores:

lemma interior'_empty : interior' (∅ : set X) = ∅ :=
begin
  sorry
end

lemma interior'_univ : interior' (set.univ : set X) = set.univ :=
begin
  sorry
end

lemma interior'_inter (S T : set X) : interior' (S ∩ T) = interior' S ∩ interior' T :=
begin
  sorry
end


-- Definiciones por recursión sobre los naturales
-- ==============================================

import data.nat.basic

-- ----------------------------------------------------
-- Ej. 1. Definir el tipo nat de los números naturales
-- con los constructores zero (para el número cero) y
-- succ (para el sucesor).
-- ----------------------------------------------------

namespace reservado

inductive nat : Type
| zero : nat
| succ : nat → nat

end reservado

-- ----------------------------------------------------
-- Ej. 2. Abrir el espacio de nombre de los números
-- naturales.
-- ----------------------------------------------------

open nat

-- ----------------------------------------------------
-- Ej. 3. Definir la función
--    factorial : ℕ → ℕ
-- tal que (factorial n) es el factorial de n. Por ejemplo,
--    factorial 3 == 6
-- ----------------------------------------------------

-- 1ª definición
def factorial : ℕ → ℕ
| 0        := 1
| (succ n) := (succ n) * factorial n

-- 2ª definición
def factorial2 : ℕ → ℕ
| 0       := 1
| (n + 1) := (n + 1) * factorial2 n

-- ----------------------------------------------------
-- Ej. 4. Calcular el factorial de 3 y de 30.
-- ----------------------------------------------------

-- El cáculo es
--    #eval factorial 3
--    #eval factorial 300

-- ----------------------------------------------------
-- Ej. 5. Demostrar que el factorial de 3 es 6.
-- ----------------------------------------------------

example : factorial 3 = 6 := rfl

-- ----------------------------------------------------
-- Ej. 6. Definir por recurión la función
--    potencia_de_dos : ℕ → ℕ
-- tal que (potencia_de_dos n) es 2^n. Por ejemplo,
-- ----------------------------------------------------

-- 1ª definición
def potencia_de_dos : ℕ → ℕ
| 0        := 1
| (succ n) := 2 * potencia_de_dos n

-- 1ª definición
def potencia_de_dos2 : ℕ → ℕ
| 0       := 1
| (n + 1) := 2 * potencia_de_dos2 n

-- ----------------------------------------------------
-- Ej. 7. Calcular (potencia_de_dos 3) y
-- (potencia_de_dos 10).
-- ----------------------------------------------------

-- El cálculo es
--    #eval potencia_de_dos 3
--    #eval potencia_de_dos 10

-- ----------------------------------------------------
-- Ej. 8. Demostrar que (potencia_de_dos 3) es 8.
-- ----------------------------------------------------

example : potencia_de_dos 3 = 8 := rfl

-- ----------------------------------------------------
-- Ej. 9. Definir la función
--    potencia : ℕ → ℕ → ℕ
-- tal que (potencia m n) es m^n. Por ejemplo,
--    potencia 2 3  =  8
--    potencia 3 2  =  9
-- ----------------------------------------------------

def potencia : ℕ → ℕ → ℕ
| m 0       := 1
| m (n + 1) := potencia m n * m

-- #eval potencia 2 3
-- #eval potencia 3 2

-- #eval pow 2 3
-- #eval pow 3 2

-- #eval 2^3
-- #eval 3^2

-- ----------------------------------------------------
-- Ej. 10. Sean m, n ∈ ℕ. Demostrar,
--    m^0 = 1
--    m^(n+1) = m^n * m
-- ----------------------------------------------------

variables m n : ℕ

-- 1ª demostración
example : m^0 = 1 := pow_zero m

-- 2ª demostración
example : m^0 = 1 := rfl

-- 1ª demostración
example : m^(n+1) = m^n * m := pow_succ' m n

-- ----------------------------------------------------
-- Ej. 11. Definir la función
--    fib : ℕ → ℕ
-- tal que (fib n) es el n-ésimo número de Fibonacci.
-- Por ejemplo,
--    fib 4 = 3
--    fib 5 = 5
--    fib 6 = 8
-- ----------------------------------------------------

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

-- #eval fib 4
-- #eval fib 5
-- #eval fib 6


/-
Copyright (c) 2020 Kyle Miller. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kyle Miller
-/
import data.nat.parity
import data.zmod.basic
/-!
# Relating parity to natural numbers mod 2

This module provides lemmas relating `zmod 2` to `even` and `odd`.

## Tags

parity, zmod, even, odd
-/

namespace zmod

lemma eq_zero_iff_even {n : ℕ} : (n : zmod 2) = 0 ↔ even n :=
(char_p.cast_eq_zero_iff (zmod 2) 2 n).trans even_iff_two_dvd.symm

lemma eq_one_iff_odd {n : ℕ} : (n : zmod 2) = 1 ↔ odd n :=
by { rw [← @nat.cast_one (zmod 2), zmod.eq_iff_modeq_nat, nat.odd_iff, nat.modeq], norm_num }

lemma ne_zero_iff_odd {n : ℕ} : (n : zmod 2) ≠ 0 ↔ odd n :=
by split; { contrapose, simp [eq_zero_iff_even], }

end zmod


/- LoVe Homework 5: Inductive Predicates -/

import .lovelib

namespace LoVe

/- Question 2: Transitive Closure -/

/- In mathematics, the transitive closure `R+` of a binary relation `R` over a
set `A` can be defined as the smallest solution satisfying the following rules:

    (base) for all a, b ∈ A, if a R b, then a R+ b;
    (step) for all a, b, c ∈ A, if a R b and b R+ c, then a R+ c.

In Lean, we can define this concept as follows, by identifying the set `A` with
the type `α`: -/

inductive tc₁ {α : Type} (r : α → α → Prop) : α → α → Prop
| base : ∀a b, r a b → tc₁ a b
| step : ∀a b c, r a b → tc₁ b c → tc₁ a c

/- 2.4 (**optional**). Prove by rule induction on the `tc₁ r a b` premise below
that `(pets)` also holds as a lemma about `tc₁` as defined above.  -/

lemma tc₁_pets {α : Type} (r : α → α → Prop) (c : α) :
  ∀a b, tc₁ r a b → r b c → tc₁ r a c :=
begin
  intros a b tab rbc,
  induction tab,
  case tc₁.base : x y rxy ryc {
    exact tc₁.step _ _ _ rxy (tc₁.base _ _ ryc) },
  case tc₁.step : x y z rxy tyz tyc_of_rzc rzc {
    exact tc₁.step _ _ _ rxy (tyc_of_rzc rzc) }
end

/- 2.5 (**optional**). Prove by rule induction that `(trans)` also holds as a
lemma about `tc₁`. -/

lemma tc₁_trans {α : Type} (r : α → α → Prop) (c : α) :
  ∀a b : α, tc₁ r a b → tc₁ r b c → tc₁ r a c :=
begin
  intros a b tab tbc,
  induction tab,
  case tc₁.base : x y rxy tyc {
    exact tc₁.step _ _ _ rxy tyc },
  case tc₁.step : x y z rxy tyz tyc_of_tzc tzc {
    exact tc₁.step _ _ _ rxy (tyc_of_tzc tzc) }
end

end LoVe


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que para todos los números reales x, y, ε si
--    0 < ε
--    ε ≤ 1
--    abs x < ε
--    abs y < ε
-- entonces
--    abs (x * y) < ε
-- ----------------------------------------------------------------------

import data.real.basic tactic

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

example :
  ∀ {x y ε : ℝ}, 0 < ε → ε ≤ 1 → |x| < ε → |y| < ε → |x * y| < ε :=
begin
  intros x y ε he1 he2 hx hy,
  by_cases h : (|x| = 0),
  { calc |x * y|
         = |x| * |y| : abs_mul x y
     ... = 0 * |y|   : by rw h
     ... = 0         : zero_mul (abs y)
     ... < ε         : he1 },
  { have h1 : 0 < |x|,
    { have h2 : 0 ≤ |x| := abs_nonneg x,
      show 0 < |x|,
        exact lt_of_le_of_ne h2 (ne.symm h) },
    calc |x * y|
         = |x| * |y| : abs_mul x y
     ... < |x| * ε   : (mul_lt_mul_left h1).mpr hy
     ... < ε * ε     : (mul_lt_mul_right he1).mpr hx
     ... ≤ 1 * ε     : (mul_le_mul_right he1).mpr he2
     ... = ε         : one_mul ε },
end

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

example :
  ∀ {x y ε : ℝ}, 0 < ε → ε ≤ 1 → |x| < ε → |y| < ε → |x * y| < ε :=
begin
  intros x y ε he1 he2 hx hy,
  by_cases (abs x = 0),
  { calc abs (x * y) = abs x * abs y : by apply abs_mul
                 ... = 0 * abs y     : by rw h
                 ... = 0             : by apply zero_mul
                 ... < ε             : by apply he1 },
  { have h1 : 0 < abs x,
      { have h2 : 0 ≤ abs x,
          apply abs_nonneg,
        exact lt_of_le_of_ne h2 (ne.symm h) },
    calc
      abs (x * y) = abs x * abs y : by rw abs_mul
              ... < abs x * ε     : by apply (mul_lt_mul_left h1).mpr hy
              ... < ε * ε         : by apply (mul_lt_mul_right he1).mpr hx
              ... ≤ 1 * ε         : by apply (mul_le_mul_right he1).mpr he2
              ... = ε             : by rw [one_mul] },
end


/-
Copyright (c) 2019 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import data.multiset.nodup
import data.list.nat_antidiagonal

/-!
# Antidiagonals in ℕ × ℕ as multisets

This file defines the antidiagonals of ℕ × ℕ as multisets: the `n`-th antidiagonal is the multiset
of pairs `(i, j)` such that `i + j = n`. This is useful for polynomial multiplication and more
generally for sums going from `0` to `n`.

## Notes

This refines file `data.list.nat_antidiagonal` and is further refined by file
`data.finset.nat_antidiagonal`.
-/

namespace multiset
namespace nat

/-- The antidiagonal of a natural number `n` is
    the multiset of pairs `(i, j)` such that `i + j = n`. -/
def antidiagonal (n : ℕ) : multiset (ℕ × ℕ) :=
list.nat.antidiagonal n

/-- A pair (i, j) is contained in the antidiagonal of `n` if and only if `i + j = n`. -/
@[simp] lemma mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} :
  x ∈ antidiagonal n ↔ x.1 + x.2 = n :=
by rw [antidiagonal, mem_coe, list.nat.mem_antidiagonal]

/-- The cardinality of the antidiagonal of `n` is `n+1`. -/
@[simp] lemma card_antidiagonal (n : ℕ) : (antidiagonal n).card = n+1 :=
by rw [antidiagonal, coe_card, list.nat.length_antidiagonal]

/-- The antidiagonal of `0` is the list `[(0, 0)]` -/
@[simp] lemma antidiagonal_zero : antidiagonal 0 = {(0, 0)} :=
rfl

/-- The antidiagonal of `n` does not contain duplicate entries. -/
@[simp] lemma nodup_antidiagonal (n : ℕ) : nodup (antidiagonal n) :=
coe_nodup.2 $ list.nat.nodup_antidiagonal n

@[simp] lemma antidiagonal_succ {n : ℕ} :
  antidiagonal (n + 1) = (0, n + 1) ::ₘ ((antidiagonal n).map (prod.map nat.succ id)) :=
by simp only [antidiagonal, list.nat.antidiagonal_succ, coe_map, cons_coe]

end nat
end multiset


import Mathlib

/-!
# Recursive functions

We have defined functions in terms of other functions. Besides this essentially the only way to define functions is _recursion_. In __Dependent Type Theory__ recursion/induction is very powerful.

In particular, unlike imperative programming all loops are implemented in terms of recursion.

The first example is the factorial function:
-/

/-- The factorial function -/
def fctrl(n : ℕ):  ℕ :=
  match n with
  | 0 => 1
  | n + 1 => (n + 1) * (fctrl n)

/-!
We check by evaluating this

#eval fctrl 5 -- 120
-/
#eval fctrl 5 -- 120

/-!
## Fibonacci numbers:

These are given by the recursive definition

* `fib 0 = 1`
* `fib 1 = 1`
* `fib (n + 2) = fib n + (fib (n + 1))`
-/
def fib : ℕ → ℕ
| 0 => 1
| 1 => 1
| n + 2 => fib n + (fib (n + 1))

/-!
The above definition is not efficient as a single computation is called many times.

We can instead define pairs, so
if  `(a, b) = (fib n, fib (n + 1))`
then `(fib (n + 1), fib (n + 2)) = (b, a+ b)`

To define Fibonacci pairs as above, we have two choices. We can view the pair at `n` as obtained by `n` iterations of the function `g: (a, b) ↦ (b, a + b)` starting with the pair `(1,1)`. Note that we can recursively use either `g^(n + 1) = g^n ∘ g` or `g^(n + 1) = g ∘ g^n`. The former is more efficient as it means the recursive function is called at the end to give a result, with modified arguments. This allows so called _tail call optimization_, where new copies of the function are not created.
-/
def fibAux (a b n : ℕ) : ℕ × ℕ  :=
  match n with
  | 0 => (a, b)
  | k + 1 => fibAux b (a + b) k

def fib'(n) := (fibAux 1 1 n).1

#eval fib' 42

/-!
The following definition is clearly wrong and will loop infinitely. Indeed if we attempt to define

```lean
def silly_fib : ℕ → ℕ
| 0 => 1
| 1 => 1
| n + 2 => silly_fib n + (silly_fib (n + 3))
```

we get the error message

```lean
fail to show termination for
  silly_fib
with errors
argument #1 was not used for structural recursion
  failed to eliminate recursive application
    silly_fib (n + 3)

structural recursion cannot be used

failed to prove termination, use `termination_by` to specify a well-founded relation
```
-/
partial def silly_fib : ℕ → ℕ
| 0 => 1
| 1 => 1
| n + 2 => silly_fib n + (silly_fib (n + 3))

/-!
However even if functions terminate we can still get such an error. This is because Lean does not know that the function is terminating. We can fix this by adding the `partial` keyword but better still we can prove termination.

Thus, the following definition is correct but gives an error.
```lean
def hcf (a b : ℕ) : ℕ :=
  if b < a then hcf b a
  else
    if a = 0 then b
    else hcf a (b - a)

#eval hcf 18 12 -- 6
```
-/

def hcf (a b : ℕ) : ℕ :=
  if c:b < a then hcf b a
  else
    if c'':a = 0 then b
    else
      have _ : b - a < b := by
        apply Nat.sub_lt_of_pos_le
        · apply Nat.pos_of_ne_zero
          assumption
        · apply Nat.le_of_not_lt
          assumption
      hcf a (b - a)
termination_by _ a b => (a, b)

/-!
```lean
partial def wrong(n: ℕ): Empty :=
  wrong (n + 1)
```

gives the error message
```lean
failed to compile partial definition 'wrong', failed to show that type is inhabited and non empty
```
-/
-- partial def wrong(n: ℕ): Empty :=
--   wrong (n + 1)

#eval hcf 18 12 -- 6

#check Nat.pos_of_ne_zero
#check Nat.sub_lt_of_pos_le

/-!
Lean has to allow partial definitions due to deep results of Church-Gödel-Turing-..., which say for example that we cannot prove that a Lean interpreter in Lean terminates.
-/


/-
Copyright (c) 2022 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import linear_algebra.ray
import analysis.normed_space.basic

/-!
# Rays in a real normed vector space

In this file we prove some lemmas about the `same_ray` predicate in case of a real normed space. In
this case, for two vectors `x y` in the same ray, the norm of their sum is equal to the sum of their
norms and `∥y∥ • x = ∥x∥ • y`.
-/

variables {E : Type*} [semi_normed_group E] [normed_space ℝ E]
  {F : Type*} [normed_group F] [normed_space ℝ F]

namespace same_ray

variables {x y : E}

/-- If `x` and `y` are on the same ray, then the triangle inequality becomes the equality: the norm
of `x + y` is the sum of the norms of `x` and `y`. The converse is true for a strictly convex
space. -/
lemma norm_add (h : same_ray ℝ x y) : ∥x + y∥ = ∥x∥ + ∥y∥ :=
begin
  rcases h.exists_eq_smul with ⟨u, a, b, ha, hb, -, rfl, rfl⟩,
  rw [← add_smul, norm_smul_of_nonneg (add_nonneg ha hb), norm_smul_of_nonneg ha,
    norm_smul_of_nonneg hb, add_mul]
end

lemma norm_sub (h : same_ray ℝ x y) : ∥x - y∥ = |∥x∥ - ∥y∥| :=
begin
  rcases h.exists_eq_smul with ⟨u, a, b, ha, hb, -, rfl, rfl⟩,
  wlog hab : b ≤ a := le_total b a using [a b, b a] tactic.skip,
  { rw ← sub_nonneg at hab,
    rw [← sub_smul, norm_smul_of_nonneg hab, norm_smul_of_nonneg ha,
      norm_smul_of_nonneg hb, ← sub_mul, abs_of_nonneg (mul_nonneg hab (norm_nonneg _))] },
  { intros ha hb hab,
    rw [norm_sub_rev, this hb ha hab.symm, abs_sub_comm] }
end

lemma norm_smul_eq (h : same_ray ℝ x y) : ∥x∥ • y = ∥y∥ • x :=
begin
  rcases h.exists_eq_smul with ⟨u, a, b, ha, hb, -, rfl, rfl⟩,
  simp only [norm_smul_of_nonneg, *, mul_smul, smul_comm (∥u∥)],
  apply smul_comm
end

end same_ray

lemma same_ray_iff_norm_smul_eq {x y : F} :
  same_ray ℝ x y ↔ ∥x∥ • y = ∥y∥ • x :=
⟨same_ray.norm_smul_eq, λ h, or_iff_not_imp_left.2 $ λ hx, or_iff_not_imp_left.2 $ λ hy,
  ⟨∥y∥, ∥x∥, norm_pos_iff.2 hy, norm_pos_iff.2 hx, h.symm⟩⟩

/-- Two nonzero vectors `x y` in a real normed space are on the same ray if and only if the unit
vectors `∥x∥⁻¹ • x` and `∥y∥⁻¹ • y` are equal. -/
lemma same_ray_iff_inv_norm_smul_eq_of_ne {x y : F} (hx : x ≠ 0) (hy : y ≠ 0) :
  same_ray ℝ x y ↔ ∥x∥⁻¹ • x = ∥y∥⁻¹ • y :=
begin
  have : ∥x∥⁻¹ * ∥y∥⁻¹ ≠ 0, by simp *,
  rw [same_ray_iff_norm_smul_eq, ← smul_right_inj this]; try { apply_instance },
  rw [smul_comm, mul_smul, mul_smul, smul_inv_smul₀, inv_smul_smul₀, eq_comm],
  exacts [norm_ne_zero_iff.2 hy, norm_ne_zero_iff.2 hx]
end

alias same_ray_iff_inv_norm_smul_eq_of_ne ↔ same_ray.inv_norm_smul_eq _

/-- Two vectors `x y` in a real normed space are on the ray if and only if one of them is zero or
the unit vectors `∥x∥⁻¹ • x` and `∥y∥⁻¹ • y` are equal. -/
lemma same_ray_iff_inv_norm_smul_eq {x y : F} :
  same_ray ℝ x y ↔ x = 0 ∨ y = 0 ∨ ∥x∥⁻¹ • x = ∥y∥⁻¹ • y :=
begin
  rcases eq_or_ne x 0 with rfl|hx, { simp [same_ray.zero_left] },
  rcases eq_or_ne y 0 with rfl|hy, { simp [same_ray.zero_right] },
  simp only [same_ray_iff_inv_norm_smul_eq_of_ne hx hy, *, false_or]
end


-- Razonamiento con tipos enumerados con constructores con parámetros
-- ==================================================================

import data.real.basic

-- ----------------------------------------------------
-- Ejercicio 1. Definir el tipo de datos Figuras con
-- tres constructores: Triangulo (con su base y altura),
-- Circulo (con su radio) y Rectangulo (con su base y
-- altura).
-- ----------------------------------------------------

inductive Figura : Type
| Triangulo  : ℚ → ℚ → Figura
| Circulo    : ℚ → Figura
| Rectangulo : ℚ → ℚ → Figura

-- ----------------------------------------------------
-- Ejercicio 2. Abrir el espacio de nombre de Figura
-- ----------------------------------------------------

namespace Figura

-- ----------------------------------------------------
-- Ejercicio 3. Definir la función
--    cuadrado : ℚ -> Figura
-- tal que (cuadrado n) es el cuadrado de lado n.
-- ----------------------------------------------------

@[simp]
def cuadrado : ℚ -> Figura
| n := Rectangulo n n

-- ----------------------------------------------------
-- Ejercicio 4. Definir pi como 3.1415927
-- ----------------------------------------------------

def pi : ℚ := 3.1415927

-- ----------------------------------------------------
-- Ejercicio 5. Definir la función
--    area : Figura → ℚ
-- tal que (area f) es el área de la figura f. Por
-- ejemplo,
--    area (Circulo 1)      = 31415927/10000000
--    area (Circulo 10)     = 31415927/100000
--    area (Triangulo 2 5)  = 5
--    area (Rectangulo 2 5) = 10
--    area (cuadrado 3)     = 9
-- ----------------------------------------------------

@[simp]
def area : Figura → ℚ
| (Triangulo b h)  := b*h/2
| (Circulo r)      := pi*r^2
| (Rectangulo x y) := x*y

-- #eval area (Circulo 1)      -- = 31415927/10000000
-- #eval area (Circulo 10)     -- = 31415927/100000
-- #eval area (Triangulo 2 5)  -- = 5
-- #eval area (Rectangulo 2 5) -- = 10
-- #eval area (cuadrado 3)     -- = 9

-- ----------------------------------------------------
-- Ejercicio 6. Declarar x como una variable sobre los
-- números racionales
-- ----------------------------------------------------

variable (x : ℚ)

-- ----------------------------------------------------
-- Ejercicio 7. Demostrar que
--    area (cuadrado x) = x^2
-- ----------------------------------------------------

-- 1ª demostración
example :
  area (cuadrado x) = x^2 :=
calc area (cuadrado x)
     = area (Rectangulo x x) :by simp [cuadrado]
 ... = x*x                   :by simp [area]
 ... = x^2                   :by simp [pow_two]

-- 2ª demostración
example :
  area (cuadrado x) = x^2 :=
calc area (cuadrado x)
     = area (Rectangulo x x) :by simp
 ... = x*x                   :by simp
 ... = x^2                   :by simp [pow_two]

-- 3ª demostración
example :
  area (cuadrado x) = x^2 :=
by simp [pow_two]

-- 4ª demostración
example :
  area (cuadrado x) = x^2 :=
by simp ; ring

-- ----------------------------------------------------
-- Ejercicio 8. Cerrar el espacio de nombre Figura.
-- ----------------------------------------------------

end Figura


/-
Exercise 4.
Let us define the binary relation ~> as follow:
Let X and Y be two sets, and f : X → V be some function between the sets, then ∀ x, y ∈ X, x ~> y if and only if f(x) = f(y).

Can you prove that ~> is an equivalence relation?
Replace the sorry for your proof.

Here are some basic tactics that might help : http://wwwf.imperial.ac.uk/~buzzard/xena/html/source/tactics/guide.html
-/

universe u
variables {X V : Type u}

def bin_rel (X) := X → X → Prop
def equivalence_rel (r : bin_rel X) := reflexive r ∧ symmetric r ∧ transitive r
def cls (r : bin_rel X) (s : X) := {x : X | r s x}
def partition (A : set (set X)) : Prop := (∀ x : X, (∃ B ∈ A, x ∈ B ∧ ∀ C ∈ A, x ∈ C → B = C)) ∧ ∅ ∉ A
def rs (A : set (set(X))) (s t : X) := ∃ B ∈ A, s ∈ B ∧ t ∈ B
-- The code above is simply used to esablish the definitions!

theorem partition_equiv_relation -- I have defined rs to be: def rs (A : set (set(X))) (s t : X) := ∃ B ∈ A, s ∈ B ∧ t ∈ B
    (C : set (set X)) (h : partition C) : equivalence_rel (rs C) :=
begin
    sorry
end


import logic.relation

namespace cs2120

section relations

-- Our definitions will be universe-polymorphic

universe u

--
variables
  {α : Sort u}
  (r : α → α → Prop)

/-
Within a section, variable declarations are
implicitly inserted into definitions that use
the variables. This mechanism allows us not to
have to repeat explicit variable declarations
in multiple definitions.
-/

-- PROPERTIES OF RELATIONS

/-
Next we quickly review our definitions of three
fundamental properties of binary relations on a
type of values, α.
-/

-- REFLEXIVE

/-
A binary relation, r, on a type/set of object, α,
is said to be *reflexive* if every value of that
type (or in that set) is related to itself. It is
essential that *every* object be related to itself.
Equality is the easiest example. It is by definition
reflexive, and so whenever you need to prove x=x,
for any x, all you have to do is say "it's true by
(because of) the reflexivity of equality. In Lean,
that's "eq.refl x" or just "rfl" if Lean can infer
x.
-/
def reflexive := ∀ (a), r a a
/-
Note that this definition is equivalent to the following,
with Lean filling in the missing parts of this definition
implicitly based on the *variables* declared above.

reflexive {α : Sort u} (r : α → α → Prop) := ∀ (a : α), r a a
-/

-- SYMMETRIC

/-
A relation, r, is said to be symmetric if, *whenever*
r relates two objects, a to b, it also relates b to a.
-/
def symmetric := ∀ (a b), r a b → r b a
/-
As an example, equality is also symmetric. If a = b
then b = a for any a and b ( of the same type in Lean
and related proof assistants). This fact is not an
axiom but a theorem. That is, we don't just define
(assume) it to be true. Rather we prove it from the
axioms of equality: reflexivity and substitutability
of equals for equals.

Proof: Suppose a = b. We are to prove b = a. By the
substitutability axiom and the fact that a = b, it
will suffice to prove a = a (because if that's true
we can replace the first a with b using the axiom
of substitutability). But a = a by the reflexivity
of equality, and so we have proved that equality is
also *symmetric*.

Another good example of a symmetric relation is the
"friend_of" relation on Facebook. If any person (as
represented by a Facebook account), x, is friends
with any other, y, then y is necessarily also friends
with x.

Challenge: Identify at least one other mathematical
relation that is symmetric. Then identify at least
one other "real world" relation that is symmetric.
Briefly explain your reasoning in each case.

Challenge: If a relation, r, is empty (thus doesn't
relate any object to any other), is r symmetric or
not?

Challenge: Is Twitter's "follows" relation transitive?
Briefly explain your answer.
-/

-- TRANSITIVE

/-
Next, we say that a relation, r, is transitive, if,
whenever r relates any objects a and b, and b and c,
it also relates a to c.
-/
def transitive := ∀ (a b c), r a b → r b c → r a c
/-
Equality is transitive. The proof is very similar to
the proof of symmetric. Here assume the premises a = b
(remember we're talking about the case where r is the
equality relation!), and b = c, and are left to prove
a = c. Use a = b to rewrite a = c to b = c, and the
rest is trivial: we've already assumed b = c, so the
goal is proved.

Challenge: If r is an *empty* relation on values of
some type, α, (r doesn't relate any objects) then is
r transitive?

Challenge: Give an example of another mathematical
relation that's transitive (no proof needed in this
case).

Challenge: Is Facebook's friend relation transitive?
Briefly explain your answer.
-/

-- EQUIVALENCE RELATION

/-
Now we start to see how mathematical definitions
are built up "inductively," with big definitions
assembled from smaller ones. Here we define the
property of a relation, r, being an "equivalence
relation" as the conjunction of being reflexive,
symmetric, and transitive." Our increasing fluency
with logical notations now lets us write such an
idea with great economy and precision.
-/
def equivalence :=
  reflexive r ∧
  symmetric r ∧
  transitive r

/-
Challenge: Formally state and prove the propositions
that equality is a reflexive, symmetric, transitive,
and an equalivalence, relation.
-/

theorem eq_refl : reflexive (@eq α) :=
begin
  -- expand definition of reflexive
  unfold reflexive,

  -- What's really to be proved
  show ∀ (a : α), a = a,

  -- assume a is an arbitrary value
  assume a,

  -- prove a = a "by reflexivity of eq"
  exact eq.refl a,
end

theorem eq_symmetric : symmetric (@eq α) :=
begin
  -- expand definition of symmetric
  unfold symmetric,

  -- let a and be be arbitrary values
  assume a b,

  -- assume a = b
  assume h,

  -- what remains to be shown
  show b = a,

  /-
  It will suffice to show b = b,
  because in that case we can use
  substitutability of equals to
  rewrite the second b as a. Then
  by reflexivity the proof is done.
  -/
  rw h,
end

theorem eq_transitive : transitive (@eq α) :=
begin
-- By the definition of transitive we are to show ...
unfold transitive,

-- We are to ...
show ∀ (a b c : α), a = b → b = c → a = c,

-- To start let a, b, and c be arbitrary values, ...
intros a b c,

-- And assume a = b and b = c
assume h_ab h_bc,

-- Now we need to show a = c
show a = c,

/-
By the substitutability of equals axiom,
we can reduce this goal to showing b = c.
-/
rw h_ab,

/-
But that's already assumed, so the proof
is complete.
-/
assumption,
end

/-
Finally, state and prove the proposition that
equality (on the values of any type, α) is an
equivalence relation.
-/
theorem eq_equivalence {α : Sort u} : equivalence (@eq α) :=
begin
-- By the definition of equivalence ...
unfold equivalence,

-- we are to show ...
show reflexive eq ∧ symmetric eq ∧ transitive eq,

-- By and introduction it will suffice to prove each conjunct
apply and.intro _ (and.intro _ _),
-- whoa, that's cool

/-
Each is true *by the theorems we've already proved*
-/
exact eq_refl,
exact eq_symmetric,
exact eq_transitive,
end

/-
Now we see that in mathematics not only can we build up
complex definitions from less complex definitions, but we
can also build up more complex proofs from less complex
ones, typically referred to as theorems or lemmas. In our
constructive logic, a theorem or a lemma is just a proof
that we've already got in hand. This lesson illustrates
the point clearly. We first prove theorems then use the
proofs to prove more complex theorems. This is really
how mathematics works! FWIW, the Lean math library now
contains proofs of over 100,000 important theorems, in
discrete mathematics and in many other areas of maths.
-/

end relations -- section
end cs2120    -- namespace


import data.finset
import algebra.big_operators
import tactic.ring
import tactic.linarith

namespace mth1001

open_locale big_operators
open finset nat

section induction

/-
Here, the symbol `ℕ`, written `\N` represents the non-negative integers.
Take care: some mathematicans use `ℕ` to represent the postive integers.
-/

/-
In the following definition, `range n` is, for `n > 0`, the set of `n` integers `{0, 1, …, n}`. In
the special case `n = 0`, the quantity `range 0` is the empty set.

The summation symbol `∑` is written `\sum`.
-/

/-
`powers_of_two_sum n` is the sum `1 + 2^1 + 2^2 + ⋯ + 2^(n-1)`, if `n > 0`. If `n=0`, then we are
summing over the empty set. By defintion, any sum over the empty set is `0`.
-/
def powers_of_two_sum (n : ℕ)  := ∑ i in range n, 2^i

#eval powers_of_two_sum 10
#eval powers_of_two_sum 0

/-
We'll prove that `powers_of_two_sum n` is `2^n - 1`, for every non-negative integer `n`.
-/

lemma one_le_two_pow (n : ℕ) : 1 ≤ 2^n :=
calc 1  = 1^n : (nat.one_pow n).symm
    ... ≤ 2^n : nat.pow_le_pow_of_le_left dec_trivial n

example (n : ℕ) : powers_of_two_sum n = 2^n - 1:=
begin
  induction n with k h_ih,
  { exact sum_empty, },
  { unfold powers_of_two_sum at *,
    rw sum_range_succ,
    rw h_ih,
    rw nat.pow_succ,
    calc 2^k + (2^k - 1) = (2^k + 2^k) -1 : (nat.add_sub_assoc (one_le_two_pow k) _).symm
                     ... =  2^k * 2 - 1   : by rw mul_two },
end

def triangle_sum (n : ℕ) := ∑ i in range (n + 1), i

#eval triangle_sum 10

example (n : ℕ) : 2 * triangle_sum n = n * (n+1) :=
begin
  induction n with k h_ih,
  { unfold triangle_sum,
    rw sum_range_one,
    norm_num, },
  { unfold triangle_sum at *,
    rw sum_range_succ,
    rw mul_add,
    rw h_ih,
    rw succ_eq_add_one,
    ring, },
end

lemma sub_lt_of_gt {k c : ℕ} (h₁ : k > c) (h₂ : c > 0): k - c < k :=
begin
  apply nat.sub_lt,
  linarith,
  linarith,
end

example (n : ℕ) : n ≥ 4 → (∃ s t : ℕ, n = 5 * s + 2 * t) :=
begin
  apply nat.strong_induction_on n,
  intros k h hk,
  by_cases h₁ : k > 5,
  { specialize h (k-2),
    have h₂ : k - 2 < k,
    { apply sub_lt_of_gt,
      repeat { linarith }, },
    have h₃ : k - 2 ≥ 4,
    { exact nat.le_sub_left_of_add_le h₁ },
    cases h h₂ h₃ with a ha,
    cases ha with b hb,
    use [a,b+1],
    have h₄ : k - 2 + 2 = k := nat.sub_add_cancel (by linarith),
    have h₅ : k = (5 * a + 2 * b) + 2,
    { rw [←h₄, hb], },
    linarith, },
  { by_cases h₂ : k = 4,
    { rw h₂,
      use [0,2],
      norm_num, },
    { have h₃ : k > 4 := lt_of_le_of_ne hk (ne.symm h₂),
      have h₄ : k = 5 := le_antisymm (by linarith) h₃,
      rw h₄,
      use [1,0],
      norm_num, }, },
end

end induction

end mth1001


import tactic

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la clase `grupo` como una extensión de las clases
-- `has_mul`, `has_one` y `has_inv` que verifica las propiedades
--    asociativa               : ∀ (a b c : G), (a * b) * c = a * (b * c)
--    neutro por la izquierda  : ∀ (a : G), 1 * a = a
--    inverso por la izquierda : ∀ (a : G), a⁻¹ * a = 1
-- ---------------------------------------------------------------------

class grupo (G : Type) extends has_mul G, has_one G, has_inv G :=
(mul_asoc : ∀ (a b c : G), (a * b) * c = a * (b * c))
(uno_mul : ∀ (a : G), 1 * a = a)
(mul_inv_izq : ∀ (a : G), a⁻¹ * a = 1)

-- ---------------------------------------------------------------------
-- Ejercicio. Abrir el espacio de nombres `grupo`
-- ---------------------------------------------------------------------

namespace grupo

-- ---------------------------------------------------------------------
-- Ejercicio. Definir `G` como una variable sobre grupos.
-- ---------------------------------------------------------------------

variables {G : Type} [grupo G]

-- ---------------------------------------------------------------------
-- Ejercicio. Etiquetar como reglas de simplificación los axiomas
-- one_mul (KB-R1), mul_left_inv (KB-R2) y mul_assoc (KB-R3).
-- ---------------------------------------------------------------------

attribute [simp] uno_mul mul_inv_izq mul_asoc

-- ---------------------------------------------------------------------
-- Ejercicio. Definir a, b, c, x e y como variables sobre G.
-- ---------------------------------------------------------------------

variables (a b c x y : G)

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R4). Demostrar que
--    a⁻¹ * (a * b) = b
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  a⁻¹ * (a * b) = b :=
begin
  rw ← mul_asoc,
  simp,
end

-- 2ª demostración
@[simp]
lemma inv_mul_cancel_left :
  a⁻¹ * (a * b) = b :=
begin
  rw ← mul_asoc,
  -- squeeze_simp,
  simp only [uno_mul,
             mul_inv_izq],
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que en los grupos se cumple la propiedad
-- cancelativa por la izquierda
--    a * b = a * c → b = c
-- ---------------------------------------------------------------------

lemma mul_left_cancel
  (Habac : a * b = a * c)
  : b = c :=
begin
 calc b = 1 * b         : by rw uno_mul
    ... = (a⁻¹ * a) * b : by rw mul_inv_izq
    ... = a⁻¹ * (a * b) : by rw mul_asoc
    ... = a⁻¹ * (a * c) : by rw Habac
    ... = (a⁻¹ * a) * c : by rw mul_asoc
    ... = 1 * c         : by rw mul_inv_izq
    ... = c             : by rw uno_mul
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    ∀ a x y : G, x = a⁻¹ * y → a * x = y
-- ---------------------------------------------------------------------

lemma mul_eq_of_eq_inv_mul
  {a x y : G}
  (h : x = a⁻¹ * y)
  : a * x = y :=
begin
  apply mul_left_cancel a⁻¹,
  rw ← mul_asoc,
  rw mul_inv_izq,
  rwa uno_mul,
end

-- ---------------------------------------------------------------------
-- Ejercicio [KB-R11). Demostrar que 1 es neutro por la derecha; es
-- decir,
--    a * 1 = a
-- ---------------------------------------------------------------------

@[simp] theorem mul_one :
  a * 1 = a :=
begin
  apply mul_eq_of_eq_inv_mul,
  rw mul_inv_izq,
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R13). Demostrar que
--    a * a⁻¹ = 1
-- ---------------------------------------------------------------------

@[simp]
theorem mul_right_inv :
  a * a⁻¹ = 1 :=
begin
  apply mul_eq_of_eq_inv_mul,
  rw mul_one,
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R8). Demostrar que
--    (1 : G)⁻¹ = 1
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  (1 : G)⁻¹ = 1 :=
begin
  apply mul_left_cancel (1 : G),
  rw mul_right_inv,
  simp,
end

-- 1ª demostración
@[simp]
lemma one_inv :
  (1 : G)⁻¹ = 1 :=
begin
  apply mul_left_cancel (1 : G),
  rw mul_right_inv,
  -- squeeze_simp,
  simp only [uno_mul],
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R12). Demostrar que
--    (a ⁻¹) ⁻¹ = a
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  (a ⁻¹) ⁻¹ = a :=
begin
  apply mul_left_cancel a⁻¹,
  simp,
end

-- 2ª demostración
@[simp]
lemma inv_inv :
  (a ⁻¹) ⁻¹ = a :=
begin
  apply mul_left_cancel a⁻¹,
  -- squeeze_simp,
  simp only [mul_right_inv,
             mul_inv_izq],
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R14). Demostrar que
--    a * (a⁻¹ * b) = b
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  a * (a⁻¹ * b) = b :=
begin
  rw ← mul_asoc,
  simp,
end

-- 2ª demostración
@[simp] lemma mul_inv_cancel_left :
  a * (a⁻¹ * b) = b :=
begin
  rw ← mul_asoc,
  -- squeeze_simp,
  simp only [uno_mul,
             mul_right_inv],
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R17). Demostrar que
--    (a * b)⁻¹ = b⁻¹ * a⁻¹
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  apply mul_left_cancel (a * b),
  rw mul_right_inv,
  simp,
end

-- 2ª demostración
@[simp]
lemma inv_mul :
  (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  apply mul_left_cancel (a * b),
  rw mul_right_inv,
  -- squeeze_simp,
  simp only [mul_asoc,
             mul_inv_cancel_left,
             mul_right_inv]
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1 :=
  by simp

-- 2ª demostración
example :
  (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1 :=
-- by squeeze_simp
by simp only [mul_one,
              mul_asoc,
              mul_right_inv,
              inv_inv,
              mul_inv_izq]

-- ---------------------------------------------------------------------
-- Ejercicio. Cerrar el espacio de nombres `grupo`
-- ---------------------------------------------------------------------

end grupo