-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar las siguientes acciones:
--    1. Importar la teoría de grupo.
--    2. Crear el espacio de nombres my_group
--    3. Declarar G como una variable sobre anillos.
--    4. Declarar a y b como variable sobre G.
-- ----------------------------------------------------------------------

import algebra.group            -- 1
variables {G : Type*} [group G] -- 2
namespace my_group              -- 3
variables (a b : G)             -- 4

-- ---------------------------------------------------------------------
-- Ejercicio 2. Demostrar que
--    a * a⁻¹ = 1
-- ----------------------------------------------------------------------

theorem mul_right_inv : a * a⁻¹ = 1 :=
calc
  a * a⁻¹ = 1 * (a * a⁻¹)                : by rw one_mul
      ... = (1 * a) * a⁻¹                : by rw mul_assoc
      ... = (((a⁻¹)⁻¹ * a⁻¹)  * a) * a⁻¹ : by rw mul_left_inv
      ... = ((a⁻¹)⁻¹ * (a⁻¹  * a)) * a⁻¹ : by rw ← mul_assoc
      ... = ((a⁻¹)⁻¹ * 1) * a⁻¹          : by rw mul_left_inv
      ... = (a⁻¹)⁻¹ * (1 * a⁻¹)          : by rw mul_assoc
      ... = (a⁻¹)⁻¹ * a⁻¹                : by rw one_mul
      ... = 1                            : by rw mul_left_inv

-- ---------------------------------------------------------------------
-- Ejercicio 3. Demostrar que
--    a * 1 = a
-- ----------------------------------------------------------------------

theorem mul_one : a * 1 = a :=
calc
  a * 1 = a * (a⁻¹ * a) : by rw mul_left_inv
    ... = (a * a⁻¹) * a : by rw mul_assoc
    ... = 1 * a         : by rw mul_right_inv
    ... = a             : by rw one_mul

-- ---------------------------------------------------------------------
-- Ejercicio 4. Demostrar que si
--     b * a = 1
-- entonces
--    a⁻¹ = b
-- ----------------------------------------------------------------------

lemma inv_eq_of_mul_eq_one
  (h : b * a = 1)
  : a⁻¹ = b :=
calc
  a⁻¹ =  1 * a⁻¹       : by rw one_mul
  ... =  (b * a) * a⁻¹ : by rw h
  ... =  b * (a * a⁻¹) : by rw mul_assoc
  ... =  b * 1         : by rw mul_right_inv
  ... =  b             : by rw mul_one

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

-- En la demostración se usará el siguiente lema:
lemma mul_inv_rev_aux : (b⁻¹ * a⁻¹) * (a * b) = 1 :=
calc
  (b⁻¹ * a⁻¹) * (a * b)
      = b⁻¹ * (a⁻¹ * (a * b)) : by rw mul_assoc
  ... = b⁻¹ * ((a⁻¹ * a) * b) : by rw mul_assoc
  ... = b⁻¹ * (1 * b)         : by rw mul_left_inv
  ... = b⁻¹ * b               : by rw one_mul
  ... = 1                     : by rw mul_left_inv

theorem mul_inv_rev : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  apply inv_eq_of_mul_eq_one,
  rw mul_inv_rev_aux,
end

-- El desarrollo de la prueba es
--
--    G : Type u_1,
--    _inst_1 : group G,
--    a b : G
--    ⊢ (a * b)⁻¹ = b⁻¹ * a⁻¹
-- apply inv_eq_of_mul_eq_one,
--    ⊢ b⁻¹ * a⁻¹ * (a * b) = 1
-- rw mul_inv_rev_aux,
--    no goals

-- ---------------------------------------------------------------------
-- Ejercicio 6.  Cerrar el espacio de nombre my_group.
-- ----------------------------------------------------------------------

end my_group


-- Pruebas de longitud (repite n x) = n
-- ====================================

open nat
open list

variable {α : Type}
variable (x : α)
variable (xs : list α)
variable (n : ℕ)

-- ----------------------------------------------------
-- Nota. Se usará la función longitud y sus propiedades
-- estudiadas anteriormente.
-- ----------------------------------------------------

def longitud : list α → nat
| []        := 0
| (x :: xs) := longitud xs + 1

@[simp]
lemma longitud_nil :
  longitud ([] : list α) = 0 :=
rfl

@[simp]
lemma longitud_cons :
  longitud (x :: xs) = longitud xs + 1 :=
rfl

-- ----------------------------------------------------
-- Ejercicio 1. Definir la función
--    repite :: ℕ → α → list α
-- tal que (repite n x) es la lista formada por n
-- copias del elemento x. Por ejemplo,
--    repite 3 7 = [7,7,7]
-- ----------------------------------------------------

def repite : ℕ → α → list α
| 0 _        := []
| (succ n) x := x :: repite n x

-- #eval repite 3 7

-- ----------------------------------------------------
-- Ejercicio 2. Demostrar los siguientes lemas
-- + repite_cero :
--     repite 0 x = []
-- + repite_suc :
--     repite (succ n) x = x :: repite n x
-- ----------------------------------------------------

@[simp]
lemma repite_cero :
  repite 0 x = [] :=
rfl

@[simp]
lemma repite_suc :
  repite (succ n) x = x :: repite n x :=
rfl

-- ----------------------------------------------------
-- Ejercicio 3. (p. 18) Demostrar que
--    longitud (repite n x) = n
-- ----------------------------------------------------

-- 1ª demostración
example :
  longitud (repite n x) = n :=
begin
  induction n with n HI,
  { calc
      longitud (repite 0 x)
          = longitud []                : by rw repite_cero
      ... = 0                          : by rw longitud_nil },
  { calc
      longitud (repite (succ n) x)
          = longitud (x :: repite n x) : by rw repite_suc
      ... = longitud (repite n x) + 1  : by rw longitud_cons
      ... = n + 1                      : by rw HI
      ... = succ n                     : rfl, },
end

-- 2ª demostración
example : longitud (repite n x) = n :=
begin
  induction n with n HI,
  { rw repite_cero,
    rw longitud_nil, },
  { rw repite_suc,
    rw longitud_cons,
    rw HI, },
end

-- 3ª demostración
example : longitud (repite n x) = n :=
begin
  induction n with n HI,
  { simp only [repite_cero, longitud_nil], },
  { simp only [repite_suc, longitud_cons, HI], },
end

-- 4ª demostración
example : longitud (repite n x) = n :=
begin
  induction n with n HI,
  { simp, },
  { simp [HI], },
end

-- 5ª demostración
example : longitud (repite n x) = n :=
by induction n ; simp [*]

-- 6ª demostración
example : longitud (repite n x) = n :=
nat.rec_on n
  ( show longitud (repite 0 x) = 0, from
      calc
        longitud (repite 0 x)
            = longitud []              : by rw repite_cero
        ... = 0                        : by rw longitud_nil )
  ( assume n,
    assume HI : longitud (repite n x) = n,
    show longitud (repite (succ n) x) = succ n, from
      calc
      longitud (repite (succ n) x)
          = longitud (x :: repite n x) : by rw repite_suc
      ... = longitud (repite n x) + 1  : by rw longitud_cons
      ... = n + 1                      : by rw HI
      ... = succ n                     : rfl )

-- 7ª demostración
example : longitud (repite n x) = n :=
nat.rec_on n
  ( by simp )
  ( λ n HI, by simp [HI])

-- 7ª demostración
lemma longitud_repite_1 :
  ∀ n, longitud (repite n x) = n
| 0 := by calc
    longitud (repite 0 x)
        = longitud ([] : list α) : by rw repite_cero
    ... = 0                      : by rw longitud_nil
| (n+1) := by calc
    longitud (repite (n + 1) x)
        = longitud (x :: repite n x) : by rw repite_suc
    ... = longitud (repite n x) + 1  : by rw longitud_cons
    ... = n + 1                      : by rw longitud_repite_1

-- 8ª demostración
lemma longitud_repite_2 :
  ∀ n, longitud (repite n x) = n
| 0     := by simp
| (n+1) := by simp [*]

-- Comentarios sobre la función (repeat x n)
-- + Es equivalente a la función (repite n x).
-- + Para usarla hay que importar la librería
--   data.list.basic y abrir el espacio de nombre
--   list escribiendo al principio del fichero
--      import data.list.basic
--      open list
-- + Se puede evaluar. Por ejemplo,
--      #eval list.repeat 7 3
-- + Se puede demostrar. Por ejemplo,
--      example : length (repeat x n) = n :=
--      by induction n ; simp [*]
--
--      example : length (repeat x n) = n :=
--      -- by library_search
--      length_repeat x n


/-
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 linear_algebra.basis -- vector spaces and bases

/-!

# Steinitz exchange lemma

-/

-- This says "let `k` be a field and let `V` be a vector space over `k`"
variables (k : Type) [field k] (V : Type) [add_comm_group V] [module k V]

/-
Steinitz Exchange Lemma:

Let `V` be a vector space over the field `k`, and `X` be a subset of `V`.
Suppose `u ∈ Span(X)` but `u ∉ Span(X∖{v})` for some `v ∈ X`, and
let `Y = (X∖{v}) ∪ {u}`.
Then, `Span(X) = Span(Y)`
-/

variables {X Y : set V}
variables {u : V} {v : X}

-- let w be v (w : V)
-- let Z be X - {v}, so X = Z ∪ {v}
-- i.e. X = insert w Z
-- Y is insert u Z
variables (w : V) (Z : set V)

open submodule

lemma steinitz_exchange
  (hu : u ∈ (submodule.span k (insert w Z)))
  (hnu : u ∉ submodule.span k Z) :
  submodule.span k (insert w Z) = submodule.span k (insert u Z) :=
begin
  apply le_antisymm,
    -- key lemma!
  { have hw := mem_span_insert_exchange hu hnu,
    rw span_le,
    rw set.insert_subset,
    refine ⟨hw, _⟩,
    rw ← span_le,
    apply span_mono,
    simp },
  -- Z ⊆ span (w ∪ Z)
  -- and by `hu`, u ∈ span (w ∪ Z)
  -- so span (u ∪ Z) ⊆ span (w ∪ Z)
  {
    rw span_le,
    rw set.insert_subset,
    split,
    { exact hu,
    },
    { rw ← span_le,
      apply span_mono,
      simp }
  }
end

lemma steinitz_exchange'
    (hu : u ∈ (submodule.span k X))
    (hnu : u ∉ submodule.span k (X.diff {v}))
    (hY : Y = (X.diff {v}).union {u}):
    submodule.span k X = submodule.span k Y :=
begin
  cases v with w hw,
  have hX : X = insert w (X \ {w}),
  { simp [hw] },
  convert steinitz_exchange k V w (X \ {w}) _ hnu,
  { change Y = (X \ {w}) ∪ {u} at hY,
    simpa using hY,
  },
  { rwa ← hX }
end

lemma steinitz_exchange''
  (hu : u ∈ (submodule.span k X))
  (hnu : u ∉ submodule.span k (X \ {v}))
    (hY : Y = (X \ {v}) ∪ {u}):
    submodule.span k X = submodule.span k Y :=
begin
  cases v with w hw,
  rw hY,
  simp,
  convert steinitz_exchange k V w (X \ {w}) _ hnu,
  { simp [hw] },
  convert hu,
  simp [hw],
end


-- Distancia_no_negativa.lean
-- Si X es un espacio métrico y x, y ∈ X, entonces dist(x,y) ≥ 0
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 31-octubre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que si X es un espacio métrico y x, y ∈ X, entonces
--    0 ≤ dist x y
-- ----------------------------------------------------------------------

import topology.metric_space.basic

variables {X : Type*} [metric_space X]
variables x y : X

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

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


open list

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

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

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

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

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

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

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

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

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

A. [15 points]

TA: Original had typo (actually due an
an unforeseen name conflict). Have the
students change all appearances of the
name, mfilter, to myfilter.

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

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

/-
B. [10 points]

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

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

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

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

#eval myfilter lt_5 a_list
#eval myfilter evenb a_list
#eval myfilter (λ (n : ℕ), n = 3) a_list

#eval a_list

/-
#2.

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

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

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

/-
A. [10 points]

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

-- Your answer here

/-
TA: Original had typo: ff_apply instead of
f_apply_2. Have students change ff_apply to
f_apply_2.
-/

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

/-
B. [10 points]

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

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

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

def f_apply_k : (ℕ → ℕ) → ℕ → ℕ → ℕ
| f n 0 := n
| f n (nat.succ k') := f (f_apply_k f n k')

-- Answer here

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

-- Answer here

#eval f_apply_k (λ n, n^2) 3 2

/-
C. [Extra Credit]

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

def f_apply_k_fun : (ℕ → ℕ) → ℕ → (ℕ → ℕ)
| f 0 := λ n, n
| f (nat.succ k') := λ n, f (f_apply_k f n k')

def sq_twice := f_apply_k_fun (λ n, n^2) 2
#eval sq_twice 3    -- yay!

/-
#3: [15 points]

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

-- Answer here

def mcompose : (ℕ → ℕ) → (ℕ → ℕ) → (ℕ → ℕ)
| g f := λ n, g (f n)

-- test
#eval (mcompose nat.succ (λ n, n^2)) 4  -- 4^2+1 = 17

/-
#4. Higher-Order Functions

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

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

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

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

-- Answer here

def map_pred : (ℕ → bool) → list nat → list bool
| p [] := []
| p (cons h t) := cons (p h) (map_pred p t)

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

-- example
#reduce bor tt tt

-- Answer here

def reduce_or : list bool → bool
| [] := ff
| (cons h t) := bor h (reduce_or t)

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

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

-- Answer here

def reduce_and : list bool → bool
| [] := tt
| (cons h t) := band h (reduce_and t)

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

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

def all_zero : list nat → bool
| [] := tt
| (cons h t) := band (h=0) (all_zero t)

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


-- Prueba por inducción 6: (∀ m n k ∈ ℕ) m^(n + k) = m^n * m^k
-- ===========================================================

import data.nat.basic
import tactic
open nat

variables  (m n k : ℕ)

-- 1ª demostración
example : m^(n + k) = m^n * m^k :=
begin
  induction k with k HI,
  { rw add_zero,
    rw pow_zero,
    rw mul_one, },
  { rw add_succ,
    rw pow_succ',
    rw HI,
    rw pow_succ',
    rw mul_assoc, },
end

-- 2ª demostración
example : m^(n + k) = m^n * m^k :=
begin
  induction k with k HI,
  { calc
      m^(n + 0)
          = m^n       : by rw add_zero
      ... = m^n * 1   : by rw mul_one
      ... = m^n * m^0 : by rw pow_zero, },
  { calc
      m^(n + succ k)
          = m^(succ (n + k)) : by rw nat.add_succ
      ... = m^(n + k) * m    : by rw pow_succ'
      ... = m^n * m^k * m    : by rw HI
      ... = m^n * (m^k * m)  : by rw mul_assoc
      ... = m^n * m^(succ k) : by rw pow_succ', },
end

-- 3ª demostración
example : m^(n + k) = m^n * m^k :=
begin
  induction k with k HI,
  { rw [add_zero,
        pow_zero,
        mul_one], },
  { rw [add_succ,
        pow_succ',
        HI,
        pow_succ',
        mul_assoc], },
end

-- 4ª demostración
example : m^(n + k) = m^n * m^k :=
begin
  induction k with k HI,
  { simp only [add_zero,
               pow_zero,
               mul_one], },
  { simp only [add_succ,
               pow_succ',
               HI,
               pow_succ',
               mul_assoc], },
end

-- 5ª demostración
example : m^(n + k) = m^n * m^k :=
begin
  induction k with k HI,
  { simp, },
  { simp [add_succ,
          HI,
          pow_succ',
          mul_assoc], },
end

-- 6ª demostración
example : m^(n + k) = m^n * m^k :=
by induction k; simp [*, add_succ, pow_succ', mul_assoc]

-- 7ª demostración
example : m^(n + k) = m^n * m^k :=
nat.rec_on k
  (show m^(n + 0) = m^n * m^0, from
    calc
      m^(n + 0)
          = m^n       : by rw add_zero
      ... = m^n * 1   : by rw mul_one
      ... = m^n * m^0 : by rw pow_zero)
  (assume k,
    assume HI : m^(n + k) = m^n * m^k,
    show m^(n + succ k) = m^n * m^(succ k), from
      calc
        m^(n + succ k)
            = m^(succ (n + k)) : by rw nat.add_succ
        ... = m^(n + k) * m    : by rw pow_succ'
        ... = m^n * m^k * m    : by rw HI
        ... = m^n * (m^k * m)  : by rw mul_assoc
        ... = m^n * m^(succ k) : by rw pow_succ')

-- 8ª demostración
example : m^(n + k) = m^n * m^k :=
nat.rec_on k
  (by simp)
  (λ n HI, by simp [HI, add_succ, pow_succ', mul_assoc])

-- 9ª demostración
example : m^(n + k) = m^n * m^k :=
-- by library_search
pow_add m n k


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

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

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

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

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

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

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

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

-- 0029
example (h₁ : a ∣ b) (h₂ : b ∣ c) : a ∣ c :=
begin
  cases h₁ with k₁ h₁,
  cases h₂ with k₂ h₂,
  use k₁ * k₂,
  rw h₁ at h₂,
  rwa ←mul_assoc,
end

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

example (h1 : a ∣ b) (h2 : a ∣ c) : a ∣ b+c :=
begin
  rcases h1 with ⟨k, rfl⟩,
  rcases h2 with ⟨l, rfl⟩,
  use k+l,
  ring,
end

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

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

-- 0030
example : 0 ∣ a ↔ a = 0 :=
begin
  split,
    rintro ⟨a, rfl⟩,
    ring,
  rintro rfl,
  use 0,
  refl,
end

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

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

-/
open function

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

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

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

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


-- The short explanation that what makes dependent type theory dependent is that types can depend on parameters.
-- For example the type of list α depends on the argument α
-- this dependence is what distinguishes list ℕ  and list bool
-- Another example, consider the type vec α n, the type of vectors of elements of α of length n.

-- There is something called Pi type or dependent function type where Given α : Type and
-- β : α → Type, think of β as a family of types over α,that is, a type β for each a : α
-- In that case, the type Π x : α, β x denotes the type of functions f with the property that,
-- for each a : α, f a is an element of β a.
-- In other words, the type of the value returned by f depends on its input.

-- in dependent type theory (and in Lean), the Pi construction is fundamental,
--and α → β is just notation for Π x : α, β when β does not depend on x.

-- Using the examples on lists, we can model some basic ops as follows
-- we use namespace hidden to avoid conflicts

namespace hidden

universe u

constant list    : Type u → Type u

constant cons    : Π α : Type u, α → list α → list α
constant nil     : Π α : Type u, list α
constant head    : Π α : Type u, list α → α
constant tail    : Π α : Type u, list α → list α
constant append  : Π α : Type u, list α → list α → list α

end hidden

open list

#check list

#check @cons
#check @nil
#check @head
#check @tail
#check @append

-- There is a subtlety in the definition of head: the type α is required to have at least one element,
-- when passed the empty list, the function must determine a default element of the relevant type

-- Vector operations are handled similarly
universe u
constant vec : Type u → ℕ → Type u

namespace vec
constant empty: Π α : Type u, vec α 0
constant cons :
  Π (α : Type u) (n : ℕ), α → vec α n → vec α (n+1)
constant append :
  Π (α : Type u) (n m : ℕ), vec α m → vec α n → vec α (n+m)
end vec

-- We'll mention one important and illustrative example
-- the Sigma types, Σ x : α, β x, sometimes also known as dependent products.
-- The type Σ x : α, β x denotes the type of pairs sigma.mk a b where a : α and b : β a.
-- in the expression sigma.mk a b, the type of the second element of the pair, b : β a,
-- depends on the first element of the pair, a : α.

variable α : Type
variable β : α → Type
variable a : α
variable b : β a

#check sigma.mk a b
#check (sigma.mk a b).1
#check (sigma.mk a b).2

#reduce (sigma.mk a b).1
#reduce (sigma.mk a b).2


/-
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 6 : pushforward and pullback

## Pushforward of a set along a map

If `f : X → Y` then given a subset `S : set X` of `X` we can push it
forward to make a subset `f(S) : set Y` of `Y`. The definition
of `f(S)` is `{y : Y | ∃ x : X, x ∈ S ∧ f x = y}`.

However `f(S)` doesn't make sense in Lean, because `f` eats
terms of type `X` and not `S`, which has type `set X`.
In Lean we use the notation `f '' S` for this. This is notation
for `set.image` and if you need any API for this, it's likely
to use the word `image`.

## Pullback of a set along a map

If `f : X → Y` then given a subset `T : set Y` of `Y` we can
pull it back to make a subset `f⁻¹(T) : set X` of `X`. The definition
of `f⁻¹(T)` is `{x : X | f x ∈ T}`.

However `f⁻¹(T)` doesn't make sense in Lean either, because
`⁻¹` is notation for `has_inv.inv`, whose type in Lean
is `α → α`. In other words, if `x` has a certain type, then
`x⁻¹` *must* have the same type. The notation was basically designed
for group theory. In Lean we use the notation `f ⁻¹' T` for this pullback.

-/

variables (X Y : Type) (f : X → Y) (S : set X) (T : set Y)

example : S ⊆ f ⁻¹' (f '' S) :=
begin
  intros x hx,
  exact ⟨x, hx, rfl⟩, -- use x, etc etc also works
end

example : f '' (f ⁻¹' T) ⊆ T :=
begin
  rintros _ ⟨x, hx, rfl⟩,
  exact hx,
end

example : f '' S ⊆ T ↔ S ⊆ f ⁻¹' T :=
begin
  split,
  { intros h x hxS,
    refine h ⟨x, hxS, rfl⟩ },
  { rintro h _ ⟨x, hx, rfl⟩,
    refine h hx }
end

-- Pushforward and pullback along the identity map don't change anything

-- pullback is not so hard
example : id ⁻¹' S = S :=
begin
  refl
end

-- pushforward is a little trickier. You might have to `ext x, split`.
example : id '' S = S :=
begin
  ext x,
  split,
  { rintro ⟨y, hyS, rfl⟩,
    exact hyS, },
  { intro hxS,
    use x,
    exact ⟨hxS, rfl⟩, },
end

-- Now let's try composition.
variables (Z : Type) (g : Y → Z) (U : set Z)

-- preimage of preimage is preimage of comp
example : (g ∘ f) ⁻¹' U = f ⁻¹' (g ⁻¹' U) :=
begin
  refl,
end

-- preimage of preimage is preimage of comp
example : (g ∘ f) '' S = g '' (f '' S) :=
begin
  ext x,
  split,
  { rintro ⟨x, hxS, rfl⟩,
    refine ⟨f x, _, rfl⟩,
    exact ⟨x, hxS, rfl⟩, },
  { rintro ⟨y, ⟨x, hxS, rfl⟩, rfl⟩,
    exact ⟨x, hxS, rfl⟩, },
end


import Basics.FunctionDefinitions
/-!
## Theorem Statements

What makes Lean a proof assistant and not only a programming language is that we can state theorems
about the types and constants we define and prove that they hold. We will use the terms theorem,
lemma, corollary, fact, property, and true statement more or less interchangeably. Similarly,
propositions, logical formulas, and statements will all mean the same.

In Lean, propositions are simply terms of type `Prop`. This stands in contrast with most
descriptions of first-order logic, where terms and formulas are separate syntactic entities. A
proposition that can be proved is called a theorem (or lemma, corollary, etc.); otherwise it is a
nontheorem or false statement. Mathematicians sometimes use the term “proposition” as a synonym for
theorem (e.g., “Proposition 3.14”), but in formal logic propositions can also be false. Here are
examples of true statements that can be made about the addition, multiplication, and list reversal
operations defined in [Function Definitions](./FunctionDefinitions.lean.md):

-/
theorem add_comm₁ (m n : Nat) :
  add m n = add n m :=
  sorry

theorem add_assoc₁ (l m n : Nat) :
  add (add l m) n = add l (add m n) :=
  sorry

theorem mul_comm₁ (m n : Nat) :
  mul m n = mul n m :=
  sorry

theorem mul_assoc₁ (l m n : Nat) :
  mul (mul l m) n = mul l (mul m n) :=
  sorry

theorem mul_add₁ (l m n : Nat) :
  mul l (add m n) = add (mul l m) (mul l n) :=
  sorry

theorem reverse_reverse₁ {α : Type} (xs : List α) :
  reverse (reverse xs) = xs :=
  sorry
/-!
The general format is
```lean
theorem name (params1 : type1) . . . (paramsm : typem) :
  statement :=
  proof
```
The `:=` symbol separates the theorem’s statement and its proof. The syntax of theorem is very
similar to that of a `def` command without pattern matching, with `statement` instead of `type` and
`proof` instead of `result`. In the examples above, we put the marker `sorry` as a placeholder for
the actual proof. The marker is quite literally an apology to future readers and to Lean for the
absence of a proof. It is also a reason to _worry_, until we manage to eliminate it. In Chapters 2
and 3, we will see how to achieve this.

The intuitive semantic of a theorem command with a `sorry` proof is, “This proposition should be
provable, but I have not carried out the proof yet—sorry.” Sometimes, we want to express a related
idea, namely, “Let us assume this proposition holds.” Lean provides the axiom command for this,
which is often used in conjunction with constant(s). For example:
-/
axiom a_less_b (a b : ℤ) : a < b

/-!
Here we have no information about a and b beyond their type. The axiom specifies the desired
property about them. The general format of the command is
```lean
axiom name (params₁ : type₁) . . . (paramsₘ : typeₘ) :
  statement
```
Axioms are dangerous, because they rapidly can lead to an inconsistent logic, in which we can derive
false. For example, if we added a second axiom stating that `a = b`, we could easily derive `b < b`,
from which we could derive `false` is true. The history of interactive theorem proving is paved with
inconsistent axioms. An anecdote among many: At the 2020 edition of the Certified Programs and
Proofs (CPP) conference, a submitted paper was rejected due to a flawed axiom, from which one of the
peer reviewers derived `false`. Therefore, we will generally avoid axioms, preferring the benign
combination of `def` and `theorem` to the more dangerous `axiom`.
-/


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

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

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

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

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

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

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

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

open int

-- 0045
example (n : ℤ) (h_even : even n) (h_not_even : ¬ even n) : 0 = 1 :=
begin
  exfalso,
  apply h_not_even,
  exact h_even,
end

-- 0046
example (P Q : Prop) (h₁ : P ∨ Q) (h₂ : ¬ (P ∧ Q)) : ¬ P ↔ Q :=
begin
  split,
  intro h,
  cases h₁ with a b,
  exfalso,
  apply h,
  exact a,
  exact b,
  intros h ha,
  apply h₂,
  exact ⟨ha, h⟩,
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

variables (P Q : Prop)

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

-- 0049
example : ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
begin
  split,
  intro h,
  by_cases hP : P,
  right,
  intro hQ,
  apply h,
  exact ⟨hP, hQ⟩,
  left,
  exact hP,
  intro h,
  by_cases hP : P,
  intro hQ,
  cases h with a b,
  apply a,
  exact hP,
  apply b,
  exact hQ.right,
  intro hQ,
  apply hP,
  exact hQ.left,
end

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

-- 0050
example (n : ℤ) : ¬ (∃ k, n = 2*k) ↔ ∀ k, n ≠ 2*k :=
begin
  split,
  intros h k hk,
  apply h,
  use k,
  exact hk,
  intros h hk,
  cases hk with k ahk,
  specialize h k,
  apply h,
  exact ahk,
end

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

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

-- 0051
example (f : ℝ → ℝ) : ¬ even_fun f ↔ ∃ x, f (-x) ≠ f x :=
begin
  split,
  contrapose,
  rw not_not,
  intros h x,
  by_contradiction H,
  apply h,
  use x,
  contrapose,
  rw not_not,
  intros h hx,
  cases hx with x Hx,
  apply Hx,
  specialize h x,
  exact h,
end

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

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

-- 0052
example (f : ℝ → ℝ) : ¬ even_fun f ↔ ∃ x, f (-x) ≠ f x :=
begin
  split,
  intro h,
  unfold even_fun at h,
  push_neg at h,
  exact h,
  intro h,
  unfold even_fun,
  push_neg,
  exact h,
end

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

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

-- Let's contrapose
-- 0053
example (x : ℝ) : (∀ ε > 0, x ≤ ε) → x ≤ 0 :=
begin
  contrapose,
  intro ha,
  push_neg,
  push_neg at ha,
  use (x/2),
  split,
  linarith,
  linarith,
end

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

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

-- 0054
example (f : ℝ → ℝ) : (∀ x y, x < y → f x < f y) ↔ (∀ x y, (x ≤ y ↔ f x ≤ f y)) :=
begin
  split,
  intros h x y,
  split,
  intro hx,
  have key : x = y ∨ x < y,
  { exact eq_or_lt_of_le hx,},
  cases key with key₁ key₂,
  rw key₁,
  have k : f x < f y,
  { exact h x y key₂,},
  linarith,
  contrapose!,
  exact h y x,
  intros h x y,
  contrapose!,
  specialize h y x,
  rw h,
  intro ob,
  exact ob,
end


/-
Complete exam below. Attest to agreement to honor
code. Submit your exam when done.
-/

namespace hidden        -- you can ignore this

/-
1. [10 points]

We define a polymorphic tree type. A tree of
values of type α  is either empty or it is a node
formed from value of type α along  with two smaller
trees, left and right, of the same kind. The left
and right trees are often called the children of
a given node.

We formalize this definition as a polymorphic
inductive type with two constructors, one, empty,
with no arguments, and one, with three arguments,
as follows.
-/

inductive tree (α : Type) : Type
| empty : tree
| node (value : α) (left : tree) (right : tree) : tree

/-
1A.  Define three values of type "tree nat": the first,
t1, as the empty tree (of nat); the second, as a tree of nat with
one node, containing the value 5 and with two empty children; and
t3, a tree with 1 value in its "top" node, where each child tree is
a node with the value 5 and two empty children.

Note: You need to give a type argment explicitly when you use the
empty constructor, but now when you use the node constructor, as
its value can be inferred in the second case.
-/
open tree
--ANSWER
def t1 : tree nat := tree.empty ℕ
def t2 : tree nat := tree.node 5 t1 t1
def t3 : tree nat := tree.node 1 t2 t2

/-
1b.
Define a function, tree_sum, that takes a value of type
tree nat, and that returns the *sum* of all of the values
in the given tree. Hint: You may want to test your function
using some of the trees you just defined as arguments.
-/

-- ANSWER
def tree_sum : (tree nat) → ℕ
| (tree.empty α) := 0
| (tree.node a l r) := a + (tree_sum l) + (tree_sum r)

#eval tree_sum t3
/- 2. [10 points]

The memory of a computer is an array of memory cells
called words. Each word has a numerical "address", from
zero to the number of words in the memory minus one. If
an address is a represented as a 16-bit value, how big
can the memory of the computer be?
-/

-- ANSWER
-- 2^16
/-
3. [10 points]

Define a polymorphic function, tree_to_list,
that takes a value of type tree α as an argument and returns
a value of type list α, where the returned list contains
all and only the values that appear in the tree: none if
the tree is empty, otherwise the value in the top (given)
node, then all the values in the left child, then all of
the values in the right child.

Hints: Remember to specify α as the type argument to
tree.empty explicitly, and put parentheses around the
"patterns" to the left of := when pattern matching.
Also, the list namespace is not open by default, so
use list.cons and list.nil for the list constructors.
Remember that list.append can be used to append two
lists. Finally, you might want to test your definition
using the tree values you defined above.
-/
open list
-- ANSWER
def tree_to_list {α : Type} : (tree α) → list α
| (tree.empty α) := list.nil
| (tree.node a l r) := list.cons a (tree_to_list l)
/- 4. [15 points]

The next two problems ask you to specify the set of
natural numbers that are multiples of three in two
different ways: first as a simple predicate, and then
as an inductive definition.

4a. Specify a simple predicate (hint, use the mod
function, denoted %) that is true for each multiple
of three and false otherwise.
-/

def mult_three (n: ℕ) : Prop :=
    ∀(n), n%3=0

/-
4b. Specify the set of multiples of three as an
inductively defined family of propositions called
is_mult_three taking a natural number argument.
The proof constructors should provide proofs for
argument values that are multiples of three and
not for values that are not multiples of three.
Hint: zero is a multiple of three, and, for any
n, if n is a multiple of three, then so is n+3.
-/

inductive is_mult_three : ℕ → Prop
| pf_zero : (is_mult_three 0)
| pf_mult_three : ∀(n : ℕ), is_mult_three (n) → is_mult_three(nat.succ(nat.succ(nat.succ n)))

/- 4c. State and prove the proposition that
9 is a multiple of three: is_mult_three 9.
(Don't use the "mod" version here.)
-/

example : is_mult_three 9 :=
    begin
        apply is_mult_three.pf_mult_three,
        apply is_mult_three.pf_mult_three,
        apply is_mult_three.pf_mult_three,
        apply is_mult_three.pf_zero,
    end
/- 5. [5 points].

Consider a binary relation, R, on a set
of values of some arbitrary type, α. In plain
but precise mathematical English, explain what
it means for R to be transitive.
-/

-- Answer

/-
6. [10 points] Here we set up a set of assumptions
and you are to prove a conclusion.
-/

axioms (Raining Sprinkler Wet : Prop)
axiom rw : Raining → Wet
axiom sw : Sprinkler → Wet
axiom h : Raining ∨ Sprinkler

/-
Given these assumptions, prove "Wet". Hint:
it's a one-line proof, very short.
-/
example : Wet :=
    begin
        cases h,
        apply rw h,
        apply sw h,
    end

/- [10 points]
7. Use an inductive definition to formally specify
a ternary (three-place) relation, times_rel,  on ℕ
numbers, such that a triple, (m, n, k) is in the
relation if  and only if k = m * n, then state and
prove the proposition that the triple, (5, 6, 30),
is in the relation. Hint: You need one constructor.
Call it intro.
-/

-- Answer here
inductive times_rel : nat → nat → nat → Prop
| intro : ∀(m n k : ℕ), (k = m*n) → times_rel m n k

example : times_rel 5 6 30 :=
    begin
        apply times_rel.intro,
        apply eq.refl (30),
    end

/- 8. [10 points]

Suppose there are balls; that a ball can be made
of uranium; that a ball can be heavy; and that if
a ball is made from uranium then it is heavy. Prove
that there if there is a ball made of uranium then
there is a ball that is heavy.
-/

-- Here are the assumptions
axiom Ball : Type
axiom Uranium : Ball → Prop
axiom Heavy : Ball → Prop
axiom uh: ∀ (b : Ball), Uranium b → Heavy b

/-
8a. *Informally* (in English) prove the conjecture
that if there is a Ball that is made of uranium then
there is a heavy ball. Identify exactly the rules of
inference (introduction and elimination) that you are
using to reach your conclusion. I.e., give a precise
and complete mathematical proof, without constructing
a formal proof.
-/

-- Answer
/-
    Assume that a ball, a, is made of Uranium. Assume the proposition, uh, that
    if a ball is made of Uranium it is also heavy. These two assume statements
    were reached using introduction. Since it is true that ball a
    is made of urianium, by proposition uh we know that ball a is heavy. This can
    be proven using the definition of implication.
-/

/-
8b. Formally prove the conjecture that if
there is a ball made of uranium, then there is a
heavy ball. Hint: Put parentheses around the major
elements of the proposition to be proved. Second
hint: Lean won't show the preceding axioms as
being in your proof contet, but you may (and
will have to) use at least one of them.
-/

example : (∃ (b : Ball), Uranium b) → ∃ (b : Ball), Heavy b :=
    begin
        intros,
        cases a with b ub,
        have hb := uh b,
        have haevy := hb ub,
        apply exists.intro b,
        assumption,
    end

/- 9. [10 points]

Recall that a binary relation, R, on a set of
values of some type, α, is said to be asymmetric
if whenever a pair (x, y) is in R, the pair (y, x)
is not; and that R is said to be irreflexive if
for all x, (x, x) is not in R. Give a mathematically
precise but informal (English language) proof that
if a given binary relation, R, is asymmetric then
it is irreflexive. As part of your proof, define,
using precise but informal mathematical English,
what each of these properties means. And define
using mathematical notation the conjecture you
are to prove. Identify each inference rule you
use in writing your informal proof.
-/

-- Answer
/-
A binary relation is Asymmetric when the ordering of the
supplied pair affects the outcome of the relation. For example,
if R is asymmetric then whenever a pair (x, y) is in R, the pair (y, x)
is not. In mathematical notation, ∀ (x y : α), R x y → ¬ R y x
A binary relation is Irreflexive if applying the same value as both
arguments to the relation result in a statment that is not true. For
example, a binary relation, R, is irreflexive if the pair (x,x) is not
in R. In mathematical notation, ∀ (x : α), ¬ R x x

If a binary relation is asymmetric it implies that it is also
irreflexive. This is shown with the mathematical expression below
     ∀ (α : Type) (F : α → α → Prop), asymmetric F → irreflexive F
Using the mathematical notation for asymmetric and irreflexive from above
we can write the second half of the Proposition as follows,
    ∀ (x y : α),(R : Bin_op), (R x y → ¬ R y x) → (∀ (x : α),(¬ R x x))
To prove this relation we do the followig
1) Assume there is some value x of type α
2) Assume there is a value rxx that is bin_op R applied to the pair (x,x).
3) Apply value x to the asymmetric definition and call this val rxxnrxx.
    rxxnrxx evaluates to R x x → ¬R x x
4) apply value rxx to the Proposition rxxnrxx. This will yield a value of ¬ R x x
    which we will can nrxx
5) At this point we have a contradiction in our assumptions. A contradiction
    is when two oposite claims are held at the same time. The opposite
    claims that we are holding are rxx and nrxx. Since these can not both
    be true we have proved irreflexive by contradiction.

-/

/-
10. [5 points].

Formally prove the proposition, ¬ false.
-/

example : ¬ false :=
    begin
        apply false.elim,
    end
/-
11. [5 points] Formally prove the proposition,
¬ true → false.
-/

-- Answer
example : ¬ true → false :=
    begin
        assume nt,
        apply not.intro nt,
        apply true.intro,
    end

/- Extra credit.
Suppose P and Q are arbitrary propositions. Formally
prove that,  ¬ ( P ∧ Q) ↔ (¬ P ∨ ¬ Q). Hint: Do you
have to reason classically? Or does it help?
-/

-- Answer

example (P Q : Prop): ¬ ( P ∧ Q) ↔ (¬ P ∨ ¬ Q) :=
    begin
        intros,
        apply iff.intro,
        intros npandq,
        apply or.inl,
        assume p,

    end

end hidden      -- You can ignore this


/- Homework 1.1: Basics — Specifications -/

/- Question 1: Snoc -/

/- 1.1. Define the function `snoc` that appends a single element to the end of
   a list. -/

def snoc {α : Type} : list α → α → list α
| [] l := [] ++ [l]
| (a::b) l := a :: b ++ [l]

-- ZZZ: Jasmins solution using append.
def snoc' {α : Type} : list α → α → list α
| l x    := l ++ [x]

#reduce snoc [] 5
#reduce snoc [2,4,6] 8

/- 1.2. Convince yourself that your definition of `snoc` works by testing it on
   a few examples. -/

-- invoke `#reduce` here

/- Question 2: Map -/

/- 2.1. Define a generic `map` function that applies a function to every
   element in a list. -/

def map {α : Type} {β : Type} (f : α → β) : list α → list β
| [] := []
| (a::b) := (f a) :: (map b)

#reduce map(+10)[2,4,5]
#reduce map(/2)[10,20,30]

/- 2.2. State the so-called functiorial properties of `map` as lemmas.
   Schematically:

     map (λx, x) xs = xs
     map (λx, g (f x)) xs = map g (map f xs)

   Make sure to state the second law as generally as possible, for
   arbitrary types.
-/

lemma m1 {α: Type} (xs: list α): map(λx, x) xs = xs
:=sorry

lemma m2 {α: Type} {β: Type} {γ: Type} (xs: list α) (f: α → β) (g: β → γ): map(λx, g(f x)) xs = map g (map f xs)
:=sorry


-- Razonamiento ecuacional sobre longitudes de listas
-- ==================================================

import data.list.basic
open list

variable {α : Type}
variable x : α
variable (xs : list α)

-- ----------------------------------------------------
-- Ejercicio 1. Definir, por recursión, la función
--    longitud : list α → ℕ
-- tal que (longitud xs) es la longitud de la lista
-- xs. Por ejemplo,
--    longitud [a,c,d] = 3
-- ----------------------------------------------------

def longitud : list α → ℕ
| []        := 0
| (_ :: xs) := longitud xs + 1

-- ----------------------------------------------------
-- Ejercicio 2. Calcular
--    longitud [4,2,5]
-- ----------------------------------------------------

-- #eval longitud [4,2,5]
-- da 3

-- ----------------------------------------------------
-- Ejercicio 3. Demostrar los siguientes lemas
-- + longitud_nil :
--     longitud ([] : list α) = 0
-- + longitud_cons :
--     longitud (x :: xs) = longitud xs + 1
-- ----------------------------------------------------

@[simp]
lemma longitud_nil :
  longitud ([] : list α) = 0 :=
rfl

@[simp]
lemma longitud_cons :
  longitud (x :: xs) = longitud xs + 1 :=
rfl

-- ----------------------------------------------------
-- Ejercicio 4. Demostrar que
--    longitud [a,b,c] = 3
-- ----------------------------------------------------

-- 1ª demostración
example
  (a b c : α)
  : longitud [a,b,c] = 3 :=
calc
  longitud [a,b,c]
      = longitud [b,c] + 1          : by rw longitud_cons
  ... = (longitud [c] + 1) + 1      : by rw longitud_cons
  ... = ((longitud [] + 1) + 1) + 1 : by rw longitud_cons
  ... = ((0 + 1) + 1) + 1           : by rw longitud_nil
  ... = 3                           : rfl

-- 2ª demostración
example
  (a b c : α)
  : longitud [a,b,c] = 3 :=
calc
  longitud [a,b,c]
      = longitud [b,c] + 1          : rfl
  ... = (longitud [c] + 1) + 1      : rfl
  ... = ((longitud [] + 1) + 1) + 1 : rfl
  ... = ((0 + 1) + 1) + 1           : rfl
  ... = 3                           : rfl

-- 3ª demostración
example
  (a b c : α)
  : longitud [a,b,c] = 3 :=
begin
  rw longitud_cons,
  rw longitud_cons,
  rw longitud_cons,
  rw longitud_nil,
end

-- 4ª demostración
example
  (a b c : α)
  : longitud [a,b,c] = 3 :=
by rw [longitud_cons,
       longitud_cons,
       longitud_cons,
       longitud_nil]

-- 5ª demostración
example
  (a b c : α)
  : longitud [a,b,c] = 3 :=
by simp only [longitud_cons,
              longitud_nil]

-- 4ª demostración
example
  (a b c : α)
  : longitud [a,b,c] = 3 :=
by simp

-- 6ª demostración
example
  (a b c : α)
  : longitud [a,b,c] = 3 :=
rfl

-- Comentarios sobre la función length:
-- + Es equivalente a la función longitud.
-- + Para usarla hay que importar la librería
--   data.list.basic y abrir el espacio de nombre
--   list escribiendo al principio del fichero
--      import data.list.basic
--      open list
-- + Se puede evaluar. Por ejemplo,
--      #eval length [4,2,5,7,2]
-- + Se puede demostrar. Por ejemplo,
--      example
--        (a b c : α)
--        : length [a,b,c] = 3 :=
--      rfl


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

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

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

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

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

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

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

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

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

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

{ P Q R: Prop }, pqr_left : P ∧ (Q ∧ R)
---------------------------------- ∧.assoc'
          pqr_right : (P ∧ Q) ∧ R

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

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

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

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

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

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

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

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

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

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

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

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

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

QED."

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

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

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

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

QED."

-/

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

theorem tnott: true ≠ true := _.

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

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

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

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

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


import ..lectures.love05_inductive_predicates_demo

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

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Even and Odd

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

#check even

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/-! ## Question 2: Tennis Games

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

#check score

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

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

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

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

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

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

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

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

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

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

/-! ## Question 3: Binary Trees

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

#check is_full_mirror
#check mirror_mirror

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

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

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

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

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

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

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

end LoVe


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

! This file was ported from Lean 3 source module algebra.group.commutator
! leanprover-community/mathlib commit 448144f7ae193a8990cb7473c9e9a01990f64ac7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Group.Defs
import Mathbin.Data.Bracket

/-!
# The bracket on a group given by commutator.

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.
-/

#print commutatorElement /-
/-- The commutator of two elements `g₁` and `g₂`. -/
instance commutatorElement {G : Type _} [Group G] : Bracket G G :=
  ⟨fun g₁ g₂ => g₁ * g₂ * g₁⁻¹ * g₂⁻¹⟩
#align commutator_element commutatorElement
-/

/- warning: commutator_element_def -> commutatorElement_def is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (g₁ : G) (g₂ : G), Eq.{succ u1} G (Bracket.bracket.{u1, u1} G G (commutatorElement.{u1} G _inst_1) g₁ g₂) (HMul.hMul.{u1, u1, u1} G G G (instHMul.{u1} G (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))))) (HMul.hMul.{u1, u1, u1} G G G (instHMul.{u1} G (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))))) (HMul.hMul.{u1, u1, u1} G G G (instHMul.{u1} G (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))))) g₁ g₂) (Inv.inv.{u1} G (DivInvMonoid.toHasInv.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)) g₁)) (Inv.inv.{u1} G (DivInvMonoid.toHasInv.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)) g₂))
but is expected to have type
  forall {G : Type.{u1}} [_inst_1 : Group.{u1} G] (g₁ : G) (g₂ : G), Eq.{succ u1} G (Bracket.bracket.{u1, u1} G G (commutatorElement.{u1} G _inst_1) g₁ g₂) (HMul.hMul.{u1, u1, u1} G G G (instHMul.{u1} G (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))))) (HMul.hMul.{u1, u1, u1} G G G (instHMul.{u1} G (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))))) (HMul.hMul.{u1, u1, u1} G G G (instHMul.{u1} G (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1))))) g₁ g₂) (Inv.inv.{u1} G (DivInvMonoid.toInv.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)) g₁)) (Inv.inv.{u1} G (DivInvMonoid.toInv.{u1} G (Group.toDivInvMonoid.{u1} G _inst_1)) g₂))
Case conversion may be inaccurate. Consider using '#align commutator_element_def commutatorElement_defₓ'. -/
theorem commutatorElement_def {G : Type _} [Group G] (g₁ g₂ : G) :
    ⁅g₁, g₂⁆ = g₁ * g₂ * g₁⁻¹ * g₂⁻¹ :=
  rfl
#align commutator_element_def commutatorElement_def


import algebra.group_power tactic.norm_num algebra.big_operators

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

def is_even (n : ℕ) : Prop := ∃ k, n=2*k
def is_odd (n : ℕ) : Prop := ∃ k, n=2*k+1

lemma even_of_even_add_even (a b : ℕ) : is_even a → is_even b → is_even (a+b) :=
begin
intros Ha Hb,
cases Ha with k Hk,
cases Hb with l Hl,
existsi k+l,
simp [Hk,Hl,add_mul],
end

lemma odd_of_odd_add_even {a b : ℕ} : is_odd a → is_even b → is_odd (a+b) :=
begin
intros Ha Hb,
cases Ha with k Hk,
cases Hb with l Hl,
existsi k+l,
simp [Hk,Hl,add_mul],
end

lemma odd_of_even_add_odd {a b : ℕ} : is_even a → is_odd b → is_odd (a+b) :=
λ h1 h2, (add_comm b a) ▸ (odd_of_odd_add_even h2 h1)

lemma even_of_odd_add_odd {a b : ℕ} : is_odd a → is_odd b → is_even (a+b) :=
begin
intros Ha Hb,
cases Ha with k Hk,
cases Hb with l Hl,
existsi k+l+1,
-- simp [mul_add,Hk,Hl,one_add_one_eq_two] -- fails!
rw [Hk,Hl,mul_add,mul_add],
change 2 with 1+1,
simp [mul_add,add_mul],
end

theorem Q1a : ∀ n : ℕ, n ≥ 1 →
  is_odd (Fib (3*n-2)) ∧ is_odd (Fib (3*n-1)) ∧ is_even (Fib (3*n)) := sorry

theorem Q1b : is_odd (Fib (2017)) := sorry


import data.nat.prime
import data.nat.parity
import tactic
open nat

lemma eq_two_of_prime_and_even
  {n : ℕ}
  (hn : even n)
  (hn' : nat.prime n)
  : n = 2 :=
begin
  symmetry,
  rw ← prime_dvd_prime_iff_eq prime_two hn',
  exact even_iff_two_dvd.mp hn
end

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

lemma eq_two_of_prime_and_even2
  (n : ℕ)
  (hn : even n)
  (hn' : nat.prime n)
  : n = 2 :=
begin
  contrapose! hn,
  rw [← odd_iff_not_even, odd_iff],
  exact or.resolve_left (prime.eq_two_or_odd hn') hn,
end

github-repos/lean/jaalonso-Demostraciones-con-Lean/Demostraciones-con-Lean-034ee559250cf55e28d10d8e65f1a4642e284251/src/Si n es un primo par entonces es igual a 2.lean


/-
We've met but haven't yet been
properly introduced to ∀.

Assuming P is a Type and Q is a
proposition, What does (∀ p : P,
Q) mean?

For example, what does this mean?

    ∀ n : nat, n + 1 ≠ 0

First, you can read ∀ n : nat
"for all values, n, of type nat."
This proposition thus asserts that,
for all natural numbers, n, of type
nat, (i.e., "for any natural number,
n") the successor of n is not 0.

More generally, with P as any type,
and Q as a proposition, (∀ p : P, Q)
states that for every value, p, in
the set of values defined by the type,
P, one can derive a proof of Q.

Here's our concrete example stated
and proved in Lean.
-/

example : ∀ n : nat, n + 1 ≠ 0 :=
    -- assume an arbitrary nat, n
    λ n : nat,
        -- assume proof of n + 1 = 0
        λ h : (n + 1 = 0),
            -- derive a contradiction
            (nat.no_confusion h : false)
    -- therefore n + 1 ≠ 0

/-
Note that the parentheses around (n: nat)
are not required by Lean, but they might
help the human reader to understand the
intention better.
-/

/-
Here's another example, which you
have seen before (without a proof).
It says that for any proposition,
P (in the set of all propositions
defined by the type, Prop), it is
not possible to have both P and
¬ P be true at the same time.
-/

example : ∀ P : Prop, ¬ (P ∧ ¬ P) :=
    -- assume an arbitrary proposition
    λ P : Prop,
        -- assume it's both true and false
        λ h : (P ∧ ¬ P),
            -- derive a contradiction
            (h.right h.left)
    -- thereby proving ¬ h

#check (3)
/-
The ∀ parts of these propositions,
before the commas, give names to
arbitrary elements of a type. The
parts after the commas then state
a claim (that is usually) about the
named element.

Look at the preceding examples. In
the case of ∀ n : nat, n + 1 ≠ 0, the
part before the comma asserts that
the proposition following the comma
is true of any arbitrary (and thus
of every) element, n, of type nat.

We say that the ∀ "binds" the name
to an arbitrary value of the given
type. The binding is operative for
the remainder of the  proposition.

We note that successive bindings
accumulate, defining a context in
which the remainder of a proposition
is stated and proved.

Here's an example that states that
for any two natural numbers, n and
m, either n = m or n ≠ m. We use the
connective, ∨, to denote disjunction
(logical or). More about that later.
We "stub out" the simple proof for
now.
-/

example : ∀ (n : nat), ∀ (m : nat),
    m = n ∨ m ≠ n :=
begin
    assume n : nat,
    -- the context now includes n
    assume m : nat,
    -- the context now also has m
    sorry
end
-- the bindings no longer hold here

/-
So that should give an understanding
of the use and meaning of the universal
quantifier, ∀, in predicate logic.

What does (∀ (p : P), Q) mean in the
constructive logic of Lean?

Let's check! First we'll assume two
arbitrary propositions, P and Q, and
a proof of ∀ p : P, Q. Then we can ask
what is the type of ∀ p : P, Q. It is
a proposition, after all, and in Lean,
propositions are types. What type is
it?
-/

-- Assume P and Q are propositions
variables P Q : Prop

-- What is the type of (∀ p : P, Q)
#check (∀ p : P, Q)

#check ∀ n: nat, nat

/-
What? the proposition/type,
(∀ p : P, Q) is really just
the proposition/type, P → Q.
-/

/-
If that's true, we should be able
to assume proof of (∀ p : P, Q)
and then use it as a function.
Let's see.
-/

-- Assume a proof of ∀ (p : P), Q.
variable ap2q : (∀ p : P, Q)

-- Assume a proof of P.
variable p : P

-- What is the type of (ap2q p). Q
#check ap2q p

-- They're the same types! Here's a proof.
theorem same : (∀ p : P, Q) = (P → Q) := rfl

/-
So why not just use → instead of ∀? The
answer is that ∀ let's us bind a name to
an assumed value, a name that we can then
use in the remainder of the expression.
The → connective doesn't let us bind a
name to a value.

The following example thus defines a type
of function that, given a natural number,
n, reduces to (returns) the proposition
that that particular n is either 0 or not
0. This is a function type.
-/

#check ∀ n : nat, n = 0 ∨ n ≠ 0.

/-
A proof of this proposition/type is then
a function that, if given any nat value, n,
returns a value of type (a proof that) that
particular n is either 0 or not 0.

The binding of a name to an assumed value
effected by a ∀ provides us a context in
which we can state the remainder of the
universally quantified proposition.
-/

/-
Some work done in class
-/

def inc : ℕ → ℕ :=
    λ n : nat,
        n + 1

def inc' : ∀ n: nat, nat :=
    λ n : nat, n + 1

def add : ∀ n : nat, ∀ m :nat, nat :=
    λ n m, n + m

def add' : ∀ n : nat, (∀ m :nat, nat) :=
    λ n : nat,
        λ m : nat,
            n + m

#eval add 3 4
#eval add' 3 4

#check add' 3

#eval (add' 3)

def add3 := (add' 3)

#check add3

#eval add3 4

/- ** Further explanation ** -/

/-
So now we can see at least three ways to
read (∀ p : P, Q).

(1) As a universally quantified logical
proposition. As such, it asserts that if
p is any value of type P, then from it
one can derive a proof of the proposition,
Q, which will typically involve p.

(2) As a logical implication, P → Q. This
is read simply as P implies Q.

(3) As the type of total functions from
P to Q.

By a total function, we mean a function
that is defined "for all" values of its
argument types. It cannot be a "partial"
function that is undefined on some values
of its argument type.

The concepts of "for all" and of total
functions are intimately related here.
The ∀ explicitly requires that such a
function be defined "for all" values of
the designated type.

That functions are total is fundamental
to constructive logic. We will explain
why later.
-/

/- ** Further examples ** -/

/-

#1. Nested ∀ bindings.

Let' assume we have another proposition, R.
-/

variable R : Prop

/-
So, what does this mean: ∀ p : P, ∀ q : Q, R?
The ∀ is right-associative so we read this as
∀ p : P, (∀ q : Q, R). Using what we learned
above, we can see that it means P → Q → R!
-/

#check ∀ (p : P), (∀ q : Q, R)

/-
There are at least three ways to think about
this construct.

(1) It is the universally quantified proposition
that holds that if one assumes any proof P and
then further assumes any proof of Q, then in
that context one can derive a  proof of R.

(2) It is the logical proposition, P implies
Q implies R, written as P → Q → R.

(3) Is it the function type, P → Q → R. As
we've discussed, → is right associative,
so this is the function type is P → (Q → R).
-/

#check ∀(n: ℕ), (∀(m: ℕ), m + n >= 0)

/- ** Chained implications ** -/

/-
What is the function type, P → Q → R? It
is the type of function that takes a value
of type P as an argument and reduces to a
function of type (Q → R), a function that
takes a value of type Q, and that finally
reduces to a value of type R.

Of course, you can just think of this as a
function that takes two arguments, the first
of type P and the second of type Q, and that
then reduces to a value of type R.

In fact in Lean and in most functional
languages, you can treat any function that
takes multiple arguments as one that takes
one argument (the first) and then reduces
to a function that takes the next argument,
and so on until the last argument has been
consumed, at which point it reduces to a
result of the type at the end of the chain.

Let's look at an example involving the
+ operator applied to two natural numbers.
The + operator is a shorthand for nat.add.
-/

#check nat.add 2 3
#check (nat.add 2) 3 -- same thing
#check nat.add 2
#check nat.add

/-
Function application is left-associative.
Here, for example, nat.add consumes a 2 in
the first line and reduces to a function
(one that "bakes in the 2") that consumes
the 3 and finally reduces to 5.
-/

/-
#2

Let's return to an example we saw on the
recent in-class no-contradiction exercise:
no_contradiction: ∀ P : Prop, ¬ (P ∧ ¬ P).

We can now this type as a function type for
functions that take a proposition, P, and that
reduce to the proposition, ¬ (P ∧ ¬ P), which
is to say, to (P ∧ ¬ P) → false. We thus have
a function type like this:

(P : Prop) → (P ∧ ¬ P) → false.

But we can't write it like that because Lean
doesn't allow us to bind names to value in this
way. We have to use ∀ instead.

∀ P : Prop, (P ∧ ¬ P) → false.

Nevertheless, if we have a value of this type,
it will be a function whose first argument is
any proposition. Let's see this in action.
-/

-- Assume a proof/function of the given type
variable no_contra : ∀ (P : Prop), ¬ (P ∧ ¬ P)

/-
Now look at what we get when we apply this
function to any proposition, P. We get back
a value of type (the proposition), P ∧ ¬ P.
-/

#check no_contra (0 = 0)
#check no_contra ¬ P
#check no_contra (P → Q)

/-
Of course each of the results is a value
of type ¬ (P ∧ ¬ P), which is to say it's
another function: from proofs of (P ∧ ¬ P)
to false.

For a little (logic-destroying) fun,let's
assume we have a proof of 0 = 0 ∧ ¬ 0 = 0
and produce a proof of false.
-/

-- Assume a proof of  0 = 0 ∧ 0 ≠ 0,
variable inconsistency : 0 = 0 ∧ 0 ≠ 0

--Apply the function, contra (0 = 0), to it
#check (no_contra (0 = 0)) inconsistency

/-
Voila! A proof of false. Of course we
never could have constructed it without
having made an absurd assumption.
-/

/-
   ** Conclusion **
-/

/-
Now we know that (∀ p : P, Q) means P → Q,
but it also binds a name to an assumed value
of type P that we can use in expressing Q.
An easy way to think about this is that a
binding of a name to a value of type P is
exactly like the declaration of an argument
to a function, and Q is like the body of
the function in which the name P can be
used.
-/


import .lovelib

/- # LoVe Homework 5: Inductive Predicates

Homework must be done individually. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/- ## Question 1 (3 points): A Type of λ-Terms

Recall the following type of λ-terms from question 3 of exercise 4. -/

inductive term : Type
| var : string → term
| lam : string → term → term
| app : term → term → term

/- 1.1 (1 point). Define an inductive predicate `is_app` that returns true if
and only if its argument has an `app` constructor at the top level. -/

-- enter your definition here

/- 1.2 (2 points). Define an inductive predicate `is_abs_free` that is true if
and only if its argument is a λ-term that contains no λ-expressions. -/

-- enter your definition here

/- ## Question 2 (6 points): Even and Odd

Consider the following inductive definition of even numbers: -/

inductive even : ℕ → Prop
| zero            : even 0
| add_two {n : ℕ} : even n → even (n + 2)

/- 2.1 (1 point). Define a similar predicate for odd numbers, by completing the
Lean definition below. The definition should distinguish two cases, like `even`,
and should not rely on `even`. -/

inductive odd : ℕ → Prop
-- supply the missing cases here

/- 2.2 (1 point). Give proof terms for the following propositions, based on
your answer to question 2.1. -/

lemma odd_3 :
  odd 3 :=
sorry

lemma odd_5 :
  odd 5 :=
sorry

/- 2.3 (2 points). Prove the following lemma by rule induction, as a "paper"
proof. This is a good exercise to develop a deeper understanding of how rule
induction works (and is good practice for the final exam).

    lemma even_odd {n : ℕ} (heven : even n) :
      odd (n + 1) :=

Guidelines for paper proofs:

We expect detailed, rigorous, mathematical proofs. You are welcome to use
standard mathematical notation or Lean structured commands (e.g., `assume`,
`have`, `show`, `calc`). You can also use tactical proofs (e.g., `intro`,
`apply`), but then please indicate some of the intermediate goals, so that we
can follow the chain of reasoning.

Major proof steps, including applications of induction and invocation of the
induction hypothesis, must be stated explicitly. For each case of a proof by
induction, you must list the inductive hypotheses assumed (if any) and the goal
to be proved. Minor proof steps corresponding to `refl`, `simp`, or `linarith`
need not be justified if you think they are obvious (to humans), but you should
say which key lemmas they depend on. You should be explicit whenever you use a
function definition or an introduction rule for an inductive predicate. -/

-- enter your paper proof here

/- 2.4 (1 point). Prove the same lemma again, but this time in Lean: -/

lemma even_odd {n : ℕ} (heven : even n) :
  odd (n + 1) :=
sorry

/- 2.5 (1 point). Prove the following lemma in Lean.

Hint: Recall that `¬ a` is defined as `a → false`. -/

lemma even_not_odd {n : ℕ} (heven : even n) :
  ¬ odd n :=
sorry

end LoVe


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

example {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c :=
by linarith

-- 0009
example {a b : ℝ} (hab : a ≤ b) (c : ℝ) : a + c ≤ b + c :=
begin
  rwa [←sub_nonneg, show (b + c) - (a + c) = b - a, by ring, sub_nonneg],
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
  calc
  a   = a + 0 : by ring
  ... ≤ a + b : add_le_add_left hb a,
end

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

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

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

Let's use those lemmas by hand for now.
-/

-- 0011
example (a b : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) : 0 ≤ a + b :=
begin
  calc
  0   ≤ 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
  calc
  a + c ≤ b + c : add_le_add_right hab c
  ...   ≤ b + d : add_le_add_left hcd b,
end

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

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

Let's do that using the lemma

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

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

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

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

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

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

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

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

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

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

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

/- First using mostly backward reasonning -/
-- 0013
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  rw ←sub_nonneg,
  rw (show a * c - b * c = (a - b) * c, by ring),
  apply mul_nonneg_of_nonpos_of_nonpos _ hc,
  rwa sub_nonpos,
end

/- Using forward reasonning -/
-- 0014
example (a b c : ℝ) (hc : c ≤ 0) (hab :  a ≤ b) : b*c ≤ a*c :=
begin
  have : a - b ≤ 0, by rwa sub_nonpos,
  have : 0 ≤ (a - b) * c, from mul_nonneg_of_nonpos_of_nonpos this hc,
  have : a * c - b * c = (a - b) * c, by ring,
  have : 0 ≤ a * c - b * c, by rwa this,
  show b * c ≤ a * c, by rwa ←sub_nonneg,
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
  rw ←sub_nonneg,
  calc
  0   ≤ (a - b) * c   : mul_nonneg_of_nonpos_of_nonpos (by rwa sub_nonpos) hc
  ... = a * c - b * c : by ring,
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 l`e_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
  rintros ⟨p, q⟩,
  split; assumption,
end

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

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

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

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

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

-- 0018
example (P Q R : Prop): P ∧ Q → Q ∧ P :=
by { rintros ⟨p, q⟩, exact ⟨q, p⟩ }

/-
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,
  { intros h p q, exact h ⟨p, q⟩ },
  { rintros h ⟨p, q⟩, exact h p q },
end

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

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

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

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

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

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

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

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

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

/-
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_antisym {a b : ℕ} : a ∣ b → b ∣ a → a = b :=

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

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

-- 0022
example (a b : ℕ) : a ∣ b ↔ gcd a b = a :=
begin
  have : gcd a b ∣ a ∧ gcd a b ∣ b, by rw ←dvd_gcd_iff,
  split; intro h,
  { apply dvd_antisymm,
    show gcd a b ∣ a, from this.left,
    show a ∣ gcd a b, from dvd_gcd_iff.mpr ⟨by refl, h⟩, },
  { rw ←h, exact this.right },
end