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


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar eue si
--    |x + 3| < 5
-- entonces
--    -8 < x < 2
-- ----------------------------------------------------------------------

import data.real.basic

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

example
  (x y : ℝ)
  : abs (x + 3) < 5 → -8 < x ∧ x < 2 :=
begin
  rw abs_lt,
  intro h,
  split,
   linarith,
  linarith,
end

-- Prueba
-- ======

/-
x y : ℝ
⊢ abs (x + 3) < 5 → -8 < x ∧ x < 2
  >> rw abs_lt,
⊢ -5 < x + 3 ∧ x + 3 < 5 → -8 < x ∧ x < 2
  >> intro h,
h : -5 < x + 3 ∧ x + 3 < 5
⊢ -8 < x ∧ x < 2
  >> split,
| ⊢ -8 < x
  >>  linarith,
⊢ x < 2
  >> linarith,
no goals
-/

-- Comentario: El lema usado es
-- + abs_lt: abs a < b ↔ -b < a ∧ a < b

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

example
  (x y : ℝ)
  : abs (x + 3) < 5 → -8 < x ∧ x < 2 :=
begin
  rw abs_lt,
  intro h,
  split; linarith,
end

-- Comentario: La composición (split; linarith) aplica split y a
-- continuación le aplica linarith a cada subojetivo.


import .love02_backward_proofs_demo

/-! # LoVe Exercise 2: Backward Proofs -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

namespace backward_proofs

/-! ## Question 1: Connectives and Quantifiers

1.1. Carry out the following proofs using basic tactics.

Hint: Some strategies for carrying out such proofs are described at the end of
Section 2.3 in the Hitchhiker's Guide. -/

lemma I (a : Prop) :
  a → a :=
begin
  intro ha,
  exact ha
end

lemma K (a b : Prop) :
  a → b → b :=
begin
  intros ha hb,
  exact hb
end

lemma C (a b c : Prop) :
  (a → b → c) → b → a → c :=
begin
  intros hg hb ha,
  apply hg,
  exact ha,
  exact hb
end

lemma proj_1st (a : Prop) :
  a → a → a :=
begin
  intros ha ha',
  exact ha
end

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

lemma proj_2nd (a : Prop) :
  a → a → a :=
begin
  intros ha ha',
  exact ha'
end

lemma some_nonsense (a b c : Prop) :
  (a → b → c) → a → (a → c) → b → c :=
begin
  intros hg ha hf hb,
  apply hg,
  exact ha,
  exact hb
end

/-! 1.2. Prove the contraposition rule using basic tactics. -/

lemma contrapositive (a b : Prop) :
  (a → b) → ¬ b → ¬ a :=
begin
  intros hab hnb ha,
  apply hnb,
  apply hab,
  apply ha
end

/-! 1.3. Prove the distributivity of `∀` over `∧` using basic tactics.

Hint: This exercise is tricky, especially the right-to-left direction. Some
forward reasoning, like in the proof of `and_swap₂` in the lecture, might be
necessary. -/

lemma forall_and {α : Type} (p q : α → Prop) :
  (∀x, p x ∧ q x) ↔ (∀x, p x) ∧ (∀x, q x) :=
begin
  apply iff.intro,
  { intro h,
    apply and.intro,
    { intro x,
      apply and.elim_left,
      apply h },
    { intro x,
      apply and.elim_right,
      apply h } },
  { intros h x,
    apply and.intro,
    { apply and.elim_left h },
    { apply and.elim_right h } }
end

/-! ## Question 2: Natural Numbers

2.1. Prove the following recursive equations on the first argument of the
`mul` operator defined in lecture 1. -/

#check mul

lemma mul_zero (n : ℕ) :
  mul 0 n = 0 :=
begin
  induction' n,
  { refl },
  { simp [add, mul, ih] }
end

lemma mul_succ (m n : ℕ) :
  mul (nat.succ m) n = add (mul m n) n :=
begin
  induction' n,
  { refl },
  { simp [add, add_succ, add_assoc, mul, ih] }
end

/-! 2.2. Prove commutativity and associativity of multiplication using the
`induction'` tactic. Choose the induction variable carefully. -/

lemma mul_comm (m n : ℕ) :
  mul m n = mul n m :=
begin
  induction' m,
  { simp [mul, mul_zero] },
  { simp [mul, mul_succ, ih],
    cc }
end

lemma mul_assoc (l m n : ℕ) :
  mul (mul l m) n = mul l (mul m n) :=
begin
  induction' n,
  { refl },
  { simp [mul, mul_add, ih] }
end

/-! 2.3. Prove the symmetric variant of `mul_add` using `rw`. To apply
commutativity at a specific position, instantiate the rule by passing some
arguments (e.g., `mul_comm _ l`). -/

lemma add_mul (l m n : ℕ) :
  mul (add l m) n = add (mul n l) (mul n m) :=
begin
  rw mul_comm _ n,
  rw mul_add
end

/-! ## Question 3 (**optional**): Intuitionistic Logic

Intuitionistic logic is extended to classical logic by assuming a classical
axiom. There are several possibilities for the choice of axiom. In this
question, we are concerned with the logical equivalence of three different
axioms: -/

def excluded_middle : Prop :=
∀a : Prop, a ∨ ¬ a

def peirce : Prop :=
∀a b : Prop, ((a → b) → a) → a

def double_negation : Prop :=
∀a : Prop, (¬¬ a) → a

/-! For the proofs below, please avoid using lemmas from Lean's `classical`
namespace, as this would defeat the purpose of the exercise.

3.1 (**optional**). Prove the following implication using tactics.

Hint: You will need `or.elim` and `false.elim`. You can use
`rw excluded_middle` to unfold the definition of `excluded_middle`,
and similarly for `peirce`. -/

lemma peirce_of_em :
  excluded_middle → peirce :=
begin
  rw excluded_middle,
  rw peirce,
  intro hem,
  intros a b haba,
  apply or.elim (hem a),
  { intro,
    assumption },
  { intro hna,
    apply haba,
    intro ha,
    apply false.elim,
    apply hna,
    assumption }
end

/-! 3.2 (**optional**). Prove the following implication using tactics. -/

lemma dn_of_peirce :
  peirce → double_negation :=
begin
  rw peirce,
  rw double_negation,
  intros hpeirce a hnna,
  apply hpeirce a false,
  intro hna,
  apply false.elim,
  apply hnna,
  exact hna
end

/-! We leave the remaining implication for the homework: -/

namespace sorry_lemmas

lemma em_of_dn :
  double_negation → excluded_middle :=
sorry

end sorry_lemmas

end backward_proofs

end LoVe


/-
Justin Ngo
jmn4fms
4/12/20
Sullivan 2102-001
-/

import .satisfiability
import .rules_of_reasoning

/-
CS2102 Exam #2: Propositional Logic
-/

/-
#1. Define a predicate function, is_satisfiable: pExp → bool
that returns true iff the given proposition is satisfiable.
Hint: Model your answer on our definition of is_valid. You
may need to define one or more helper functions. You should
test your solution, but we will only grade your definition.
Please write test cases in a separate file. Do not submit
that file.
-/

-- Answer here

open pExp

def foldr_or' : list bool → bool
| [] := ff
| (h::t) := bor h (foldr_or' t)

def is_satisfiable (e : pExp) : bool :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    let rs := map_pEval_over_interps e is in
        foldr_or' rs

/-
#2. Clearly the propositions in rules_of_reasoning.lean that
are valid are also satisfiable. But what about the following?
Apply your satisfiability predicate function to decide whether
or not each of the following formulae is satisfiable or not.
Write an "#eval is_satisfiable e" command for each expression.

A. The "fallacies" in that file (the ones that aren't valid).

B. The proposition, pFalse.

C. The following propositions:

1. P ∧ ¬ P
2. P ∨ ¬ P
3. = (¬x1 ∨ x2) ∧ (¬x2 ∨ x3 ∨ ¬x4) ∧ (x1 ∨ x2 ∨ x3 ∨ ¬x4)

Note that for 3. you will have to define four new variables.
Call them x1, x2, x3, and x4.
-/

-- Answers here
-- A.
#eval is_satisfiable true_imp
#eval is_satisfiable affirm_consequence
#eval is_satisfiable affirm_disjunct
#eval is_satisfiable deny_antecedent

--B.
#eval is_satisfiable pFalse

--C.
#eval is_satisfiable (P ∧ ¬ P)
#eval is_satisfiable (P ∨ ¬ P)

def x1 := pVar (var.mk 0)
def x2 := pVar (var.mk 1)
def x3 := pVar (var.mk 2)
def x4 := pVar (var.mk 3)
#eval is_satisfiable ((¬x1 ∨ x2) ∧ (¬x2 ∨ x3 ∨ ¬x4) ∧ (x1 ∨ x2 ∨ x3 ∨ ¬x4))

/-
3. In the previous problems, you defined a satisfiability
predicate function that returns true or false depending on
whether a given formula is satisfiable or not. Often we will
want to know not only whether there exists a solution but an
actual example of a solution, if there is one. Define a function
called sat_solver : pExp → option (var → bool) that returns a
satisfying interpretation (as "some interpretation") if there
is one, or "none" otherwise. Hint: Model your solution on our
validity checker.

1. First compute the list of interpretations for
a given expression, e,
2. then reduce that list to a value of type
option (var → bool).
3. Evaluate e under each interpretation in
the list until either (A) you find one, call it i, for which e
evaluates to true, or until you reach the empty list, empty
handed.
-/

-- Answer here

def i := pEval

def itr_find : (list bool) → (option (var → bool))
| [] := none
| (h :: t) := match h with
                | ff := option.some i
                -- option.some interp
                | _ := itr_find t
                end

def sat_solver (e : pExp) : option (var → bool) :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    let rs := map_pEval_over_interps e is in
    let x :=  itr_find rs in
        foldr_and rs

/-
3.4. determine if there is at least one interpretation of a proposition
that makes a certain proposition true,
and if so return that interpretation
-/

def itr_find' : (list bool) → (option (var → bool))
| [] := none
| (h :: t) := match h with
                | tt := option.some i
                -- option.some interp
                | _ := itr_find t
                end

def sat_solver' (e : pExp) : option (var → bool) :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    let rs := map_pEval_over_interps e is in
    let x :=  itr_find' rs in
        foldr_and rs

/-
4. Define a predicate function, is_unsatisfiable : pExp → bool,
that takes a propositional logic formula, e, and returns true
iff e is unsatisfiable. Hint: Easy. Build on what you have.
-/

-- Answer here
def foldr_or'' : list bool → bool
| [] := ff
| (h::t) := bor h (foldr_or'' t)

def is_unsatisfiable (e : pExp) : bool :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    let rs := map_pEval_over_interps e is in
    let x := foldr_or'' rs in
        not x

/-
5. Given a propositional logic expression, e, and an incorrect
claim that it's valid, we often want to produce a counter-example
to that claim. Such a counter example is an interpretation under
which the expression is not true. Equivalently, a counterexample
is an interpretation under which the *negation* of the expression
*is* true.

Define a function, counterexample : pExp → option (var → bool),
that takes any expression e and returns either a counterexample
(as "some interpretation") or "none" if there isn't one. Hint:
given e, try to find a model for the expression, ¬ e.
-/

-- Answer here
-- option is a type, look at notes on polymorphism
def counterexample : pExp → option (var → bool)

def counter (e : pExp) : option (var → bool) :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    let rs := map_pEval_over_interps e is in
    let x :=  counterexample rs in
        foldr_and rs

/-
6. Give an English language example for every valid rule of
reasoning in rules_of_reasoning.lean, and also give an English
language counter-example for each fallacy.

For example, for the rule, P >> Q >> P ∧ Q, you could say,
"If the ball is red then if (in addition to that) the box
is blue, then (in this context) the ball is red AND the box
is blue." It will be easiest is you re-use the same words
for P, Q, and R, in all of your examples. E.g., P means "the
ball is red", Q means "the box is blue", etc.

Copy the contents of rules_of_reasoning.lean into this comment
block and under each rule give your English language sentence.

P: Ball is red
Q: Box is blue
R: Cake is sweet

def true_intro : pExp := pTrue
- The ball is a ball

def false_elim := pFalse >> P
- If the ball is not a ball, then the ball is red

def true_imp := pTrue >> P
- If there is a ball, then the ball is red
- Not always true because not all balls are red

def and_intro := P >> Q >> P ∧ Q
- If the ball is red, then if the box is blue, then the ball
is red and the box is blue

def and_elim_left := P ∧ Q >> P
- If the ball is red and the box is blue, then the ball is red

def and_elim_right := P ∧ Q >> Q
- If the ball is red and the box is blue, then the box is blue

def or_intro_left := P >> P ∨ Q
- If the ball is red, then the ball is red or the box is blue

def or_intro_right := Q >> P ∨ Q
- If the box is blue, then the ball is red or the box is blue

def or_elim := P ∨ Q >> (P >> R) >> (Q >> R) >> R
- If the ball is red or the box is blue, then, if the ball is red
then the cake is sweet, then, if the box is blue then the cake is sweet,
then the cake is sweet

def iff_intro := (P >> Q) >> (Q >> P) >> (P ↔ Q)
- If the ball is red then the box is blue, then, if the box is blue
then the ball is red, then, the ball is red implies the box is blue
(a red ball implies a blue box)

def iff_intro' := (P >> Q) ∧ (Q >> P) >> (P ↔ Q)
- If the ball is red then the box is blue, and if the box is blue
then the ball is red, then the ball is red implies
the box is blue (a red ball implies a blue box)

def iff_elim_left := (P ↔ Q) >> (P >> Q)
- If the ball is red implies the box is blue, then if the
ball is red then the box is blue

def iff_elim_right := (P ↔ Q) >> (Q >> P)
- If the ball is red implies the box is blue, then if the box
is blue then the ball is red

def arrow_elim := (P >> Q) >> (P >> Q)
- If the ball is red then the box is blue, then, if the ball
is red then the box is blue

def resolution := (P ∨ Q) >> (¬ Q ∨ R) >> (P ∨ R)
- If the ball is red or the box is blue, then, if the box is not
blue or the cake is sweet, then the ball is red or the cake is sweet

def unit_resolution := (P ∨ Q) >> ((¬ Q) >> P)
- If the ball is red or the box is blue, then, if the box is not blue
then the ball is red

def syllogism := (P >> Q) >> (Q >> R) >> (P >> R)
- If the box is red then the ball is blue, then, if the ball is blue
then the cake is sweet, then if the box is red then the cake is sweet

def modus_tollens := (P >> Q) >> (¬ Q >> ¬ P)
- If the box is red then the ball is blue, then, if the ball is not blue
then the box is not red

def neg_elim := (¬ ¬ P) >> P
- If the box is not red and that is not true, then the box is red.

def excluded_middle := P ∨ (¬ P)
- The box is red or the box is not red

def neg_intro := (P >> pFalse) >> (¬ P)
- If the box is red then the box is not a box,
then the box is not red

-----
P: It is raining outside
Q: The streets are wet

def affirm_consequence := (P >> Q) >> (Q >> P)
- If it is raining outside then the streets are wet, then, if the streets
are wet it is raining outside
- Not always true because the streets can be wet without it having to rain
-- if it is NOT raining outside, then the streets can be wet = TRUE
-- if the streets are wet, then it is NOT raining outside = FALSE
-- true >> false = false

def affirm_disjunct := (P ∨ Q) >> (P >> ¬ Q)
- If it is raining outside or the streets are wet, then, if it is raining
outside then the streets are not wet
- Not always true becuase if it is raining the streets can be wet
-- if it is raining outside OR the streets are wet = TRUE
-- if if its raining outside, then the streets are NOT wet = FALSE
-- true >> false = false

def deny_antecedent := (P >> Q) >> (¬ P >> ¬ Q)
- If it is raining outside then the streets are wet, then, if it
is not raining outside then the streets are not wet
- Not always true because the streets can be wet for other reasons
besides rain
-- if it is raining outside  the streets are wet = TRUE
-- if it is NOT raining outside, then the streets can NOT be wet = FALSE
-- true >> false = false

-/


class group (G : Type u) :=
  (id : G)
  (inv : G → G)
  (binop : G → G → G)
  binop_assoc : ∀ (a b c : G), binop (binop a b) c = binop a (binop b c)
  id_binop : ∀ (a : G), binop id a = a
  binop_left_inv (a : G) : binop (inv a) a = id

attribute [simp] group.id_binop group.binop_left_inv
infixl : 75 "⊙" => group.binop
variable {S : Type u} [group S]

namespace group

@[simp] theorem binop_left_cancel (a b c : S) (h1 : a ⊙ b = a ⊙ c) : b = c :=
calc b = id ⊙ b := by rw [id_binop b]
     _ = ((inv a) ⊙ a) ⊙ b := by rw [binop_left_inv]
     _ = (inv a) ⊙ (a ⊙ b) := by rw [binop_assoc]
     _ = (inv a) ⊙ (a ⊙ c) := by rw [h1]
     _ = ((inv a) ⊙ a) ⊙ c := by rw [binop_assoc]
     _ = id ⊙ c := by rw [binop_left_inv]
     _ = c := by rw [id_binop]

theorem binop_eq_of_eq_inv_binop (a x y : S) (h1 : x = (inv a) ⊙ y) : a ⊙ x = y := by
apply binop_left_cancel (inv a)
rw [← binop_assoc,binop_left_inv,id_binop]
exact h1

@[simp] theorem binop_id (a : S) : a ⊙ id = a := by
apply binop_eq_of_eq_inv_binop
rw [binop_left_inv]

@[simp] theorem binop_right_inv (a : S) : a ⊙ (inv a) = id := by
apply binop_eq_of_eq_inv_binop
rw [binop_id]

@[simp] theorem inv_binop_cancel_left (a b : S) : (inv a) ⊙ (a ⊙ b) = b := by
rw [← binop_assoc]
simp

@[simp] theorem binop_inv_cancel_left (a b : S) : a ⊙ ((inv a) ⊙ b) = b := by
rw [← binop_assoc]
simp

@[simp] theorem inv_binop (a b : S) : inv (a ⊙ b) = (inv b) ⊙ (inv a) := by
rw [← id_binop (inv b), binop_assoc]
rw [← binop_left_inv (a ⊙ b),binop_assoc]
rw [binop_assoc]
simp

@[simp] theorem id_inv : inv id = (id : S) := by
rw [← binop_id (inv id)]
rw [binop_left_inv]

@[simp] theorem inv_inv (a : S) : inv (inv a) = a := by
apply binop_left_cancel (inv a)
simp

class abelian_group (A : Type u) extends group A :=
  binop_comm : ∀ (a b : A), binop a b = binop b a

namespace abelian_group
variable (K : Type u) [abelian_group K]

@[simp] theorem group_commutator_eq_id (a b : K) : (inv a) ⊙ (inv b) ⊙ a ⊙ b = id := by
rw [← binop_comm a, ← binop_assoc a]
simp