/-
# Inductive Types
In this file, we will learn about induction and inductive types.
Along with dependent type theory, they form the core of Lean. We shall
also see how the natural numbers and integers are defined.

For more information, please refer to Theorem Proving in Lean :
https://leanprover.github.io/theorem_proving_in_lean/theorem_proving_in_lean.pdf

-/

#print nat -- this works without any imports! Why?

/- An inductive type is defined using constructors. What are the constructors for
the natural numbers? -/

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

open nat'
/- An `instance` is made so that Lean remembers the information it is given regarding
the given definition. -/
instance : has_zero nat' := ⟨zero⟩
instance : has_one nat' := ⟨succ zero⟩

/- One can use recursion to define addition on the naturals. -/
def add' : nat' → nat' → nat' -- nat' × nat' → nat'
| a zero := a -- a + 0 := a OR add' a 0 := a
| a (succ b) := succ (add' a b) -- a + succ b := succ (a + b)

instance : has_add nat' := ⟨add'⟩ -- nat' has addition

/- Why does `+` work? -/
lemma add_assoc' (a b c : nat') : a + (b + c) = add' (add' a b) c :=
begin
  show a + (b + c) = (a + b) + c,
  induction c with d hd, -- helps with inductive proofs, can also `cases`
  { refl, },
  { show a + (b + d.succ) = (a + b + d).succ,
    rw ←hd, refl, },
end

/- Given, the naturals, how can we now define the integers? -/
inductive int' : Type
| of_nat' : nat' → int' -- identifies every natural number as an integer
| neg_succ_of_nat' : nat' → int' -- takes n to -(1 + n)

/- We need to be able to identify every natural as an integer. We use
coercions for this purpose, which is denoted by an arrow `↑`. -/
instance : has_coe nat' int' := ⟨int'.of_nat'⟩

protected lemma coe_nat_eq' (n : nat') : ↑n = int'.of_nat' n := rfl

open int'
lemma of_nat_eq_of_nat_iff' (m n : nat') : of_nat' m = of_nat' n ↔ m = n :=
begin
  split,
  { intro h, injection h, },
  { intro h, congr, exact h, },
end

def zero : int' := of_nat' 0
def one  : int' := of_nat' 1

instance : has_zero int' := ⟨of_nat' zero⟩
instance : has_one int' := ⟨one⟩

lemma of_nat_zero' : of_nat' (0 : nat') = (0 : int') := rfl

lemma of_nat_one' : of_nat' (1 : nat') = (1 : int') := rfl

/- Define addition, subtraction, negation and multiplication over
the integers, and show compatibility with naturals. -/


/-
Copyright (c) 2020 Joseph Myers. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joseph Myers, Yury Kudryashov
-/

import analysis.mean_inequalities

/-!
# IMO 2020 Q2

The real numbers `a`, `b`, `c`, `d` are such that `a ≥ b ≥ c ≥ d > 0` and `a + b + c + d = 1`.
Prove that `(a + 2b + 3c + 4d) a^a b^b c^c d^d < 1`.

A solution is to eliminate the powers using weighted AM-GM and replace
`1` by `(a+b+c+d)^3`, leaving a homogeneous inequality that can be
proved in many ways by expanding, rearranging and comparing individual
terms.  The version here using factors such as `a+3b+3c+3d` is from
the official solutions.
-/

open real

theorem imo2020_q2 (a b c d : ℝ) (hd0 : 0 < d) (hdc : d ≤ c) (hcb : c ≤ b) (hba : b ≤ a)
  (h1 : a + b + c + d = 1) :
  (a + 2 * b + 3 * c + 4 * d) * a ^ a * b ^ b * c ^ c * d ^ d < 1 :=
begin
  have hp : a ^ a * b ^ b * c ^ c * d ^ d ≤ a * a + b * b + c * c + d * d,
  by refine geom_mean_le_arith_mean4_weighted _ _ _ _ _ _ _ _ h1; linarith,
  calc  (a + 2 * b + 3 * c + 4 * d) * a ^ a * b ^ b * c ^ c * d ^ d
      = (a + 2 * b + 3 * c + 4 * d) * (a ^ a * b ^ b * c ^ c * d ^ d) : by ac_refl
  ... ≤ (a + 2 * b + 3 * c + 4 * d) * (a * a + b * b + c * c + d * d) :
    mul_le_mul_of_nonneg_left hp (by linarith)
  ... = (a + 2 * b + 3 * c + 4 * d) * (a * a) + (a + 2 * b + 3 * c + 4 * d) * (b * b) +
        (a + 2 * b + 3 * c + 4 * d) * (c * c) + (a + 2 * b + 3 * c + 4 * d) * (d * d) :
    by simp only [mul_add]
  ... ≤ (a + 3 * b + 3 * c + 3 * d) * (a * a) + (3 * a + b + 3 * c + 3 * d) * (b * b) +
        (3 * a + 3 * b + c + 3 * d) * (c * c) + (3 * a + 3 * b + 3 * c + d) * (d * d) :
    by apply_rules [add_le_add]; refine mul_le_mul_of_nonneg_right _ (mul_self_nonneg _); linarith
  ... < (a + 3 * b + 3 * c + 3 * d) * (a * a) + (3 * a + b + 3 * c + 3 * d) * (b * b) +
        (3 * a + 3 * b + c + 3 * d) * (c * c) + (3 * a + 3 * b + 3 * c + d) * (d * d) +
        (6 * a * b * c + 6 * a * b * d + 6 * a * c * d + 6 * b * c * d) :
    lt_add_of_pos_right _ (by apply_rules [add_pos, mul_pos, zero_lt_one]; linarith)
  ... = (a + b + c + d) ^ 3 : by ring
  ... = 1 : by simp [h1]
end


-- Working through section 7.1

namespace hidden

    inductive bool : Type
    | ff : bool
    | tt : bool

    def band (b1 b2 : bool) : bool :=
    bool.rec_on b1 bool.ff b2

    theorem t_and_f_is_f : band bool.tt bool.ff = bool.ff := rfl
    theorem f_and_t_is_f : band bool.ff bool.tt = bool.ff := rfl
    theorem f_and_f_is_f : band bool.ff bool.ff = bool.ff := rfl
    theorem t_and_t_is_t : band bool.tt bool.tt = bool.tt := rfl

    def bor (b1 b2 : bool) : bool :=
    bool.rec_on b1 b2 bool.tt

    theorem t_or_t_is_t : bor bool.tt bool.tt = bool.tt := rfl
    theorem t_or_f_is_t : bor bool.tt bool.ff = bool.tt := rfl
    theorem f_or_t_is_t : bor bool.ff bool.tt = bool.tt := rfl
    theorem f_or_f_is_f : bor bool.ff bool.ff = bool.ff := rfl

    def bnot (b : bool) : bool :=
    bool.rec_on b bool.tt bool.ff

    theorem not_t_is_f : bnot bool.tt = bool.ff := rfl
    theorem not_f_is_t : bnot bool.ff = bool.tt := rfl

end hidden

-- Going through section 7.2
--
-- > As exercises, we encourage you to develop a notion of composition for partial functions from α
-- > to β and β to γ, and show that it behaves as expected. We also encourage you to show that bool
-- > and nat are inhabited, that the product of two inhabited types is inhabited, and that the type
-- > of functions to an inhabited type is inhabited.

namespace chap7_section2

    variables { α β γ : Type }
    variables (a : α) (b : β) (c : γ)

    def and_then (f1 : α → option β) (f2 : β → option γ ) (a : α): option γ :=
    have option β, from f1 a,
    show option γ, from option.rec_on this none (λ b, f2 b)

    def return_none (α β : Type) : α → option β := λ _, none
    def return_some (α β : Type) : β → α → option β := λ b a, some b

    theorem none_none_none : and_then (return_none α β) (return_none β γ) a = none := rfl
    theorem none_some_none : and_then (return_none α β) (return_some β γ c) a = none := rfl
    theorem some_none_none : and_then (return_some α β b) (return_none β γ) a = none := rfl
    theorem some_some_some : and_then (return_some α β b) (return_some β γ c) a = some c := rfl

    theorem bool_inhabited : inhabited bool := inhabited.mk tt
    theorem nat_inhabited : inhabited ℕ := inhabited.mk 0
    theorem product_of_inhabited_is_inhabited : inhabited α → inhabited β → inhabited (α × β) :=
        λ alpha beta, inhabited.mk $ prod.mk alpha.default beta.default

    theorem type_of_functions_to_inhabited_type_is_inhabited : inhabited β → inhabited (α → β) :=
        λ hib, inhabited.mk $ λ a, hib.default

end chap7_section2

namespace ch7_section5
    universe u
    variables { α : Type }

    open list

    theorem append_nil (t : list α) : t ++ nil = t :=
    list.rec_on
        t
        (show nil ++ nil = nil, from nil_append (@nil α))
        (λ (hd: α) (tl: list α) (acc : tl ++ nil = tl),
            have e1 : hd::tl ++ nil = hd::(tl ++ nil), from cons_append hd tl nil,
            have e2 : hd::(tl ++ nil) = hd::tl, by rw [acc],
            show hd::tl ++ nil = hd::tl, by rw [e1, e2])

    theorem append_assoc (r s t : list α) :
    r ++ s ++ t = r ++ (s ++ t) :=
    list.rec_on
        r
        (show nil ++ s ++ t = nil ++ (s ++ t), by rw [nil_append, nil_append])
        (λ (hd : α) (tl : list α) (acc : tl ++ s ++ t = tl ++ (s ++ t)),
            calc
                ((hd::tl) ++ s) ++ t = hd::((tl ++ s) ++ t) : cons_append hd (tl ++ s) t
                                 ... = hd::(tl ++ (s ++ t)) : by rw [acc]
                                 ... = (hd::tl) ++ (s ++ t) : by rw [cons_append hd (tl)] )

    -- > Try also defining the function length : Π {α : Type u}, list α → nat that returns the
    -- > length of a list, and prove that it behaves as expected (for example, length (s ++ t) =
    -- > length s + length t).

    def length : list α → ℕ := λ xs, list.rec 0 (λ item list acc, acc + 1) xs

    theorem length_of_nil_is_zero : length (@nil α) = 0 := by refl
    theorem append_length (l1 l2 : list α) : length (l1 ++ l2) = length l1 + length l2 :=
    list.rec_on
        l1
        (
            have f1 : length (nil ++ l2) = length l2, by rw [nil_append],
            have f2 : length (@nil α) + length l2 = length l2, by rw [length_of_nil_is_zero, zero_add],
            show length (nil ++ l2) = length nil + length l2, by rw [f1, f2]
        )
        (λ (hd : α) (tl : list α) (acc : length (tl ++ l2) = length tl + length l2),
            have length (hd::tl) = length tl + 1, by refl,
            eq.symm $
            calc
                length (hd::tl) + length l2 = length tl + length l2 + 1 : by rw [this, add_assoc, add_comm 1, add_assoc]
                                        ... = length (tl ++ l2) + 1 : by rw [acc]
                                        ... = length (hd::tl ++ l2) : by refl
        )

    theorem cons_increases_len_by_1 (a : α) (l : list α) : length (list.cons a l) = length l + 1 := by refl

end ch7_section5

section ch7_section7
    universe u

    theorem mysymm {α : Type u} {a b : α} (h : eq a b) : eq b a :=
    eq.subst h (eq.refl a)

    theorem mytrans {α : Type u} {a b c : α}
    (h₁ : eq a b) (h₂ : eq b c) : eq a c :=
    eq.rec_on h₂ h₁

    theorem mycongr {α β : Type u} {a b : α} (f : α → β)
    (h : eq a b) : eq (f a) (f b) :=
    eq.rec_on h rfl

end ch7_section7

namespace ch7ex1

    def mul (m n : ℕ) : ℕ :=
    nat.rec_on
        n
        nat.zero
        (λ n product, product + m)

    def predecessor (n : ℕ) : ℕ :=
    nat.rec_on
        n
        nat.zero
        (λ n n_pred,
            cond (nat.succ n = 1) nat.zero (nat.succ n_pred)
        )

    def trunc_sub (m n : ℕ) : ℕ :=
    nat.rec_on
        n
        m
        (λ _ running, predecessor running)

    def pow (m n : ℕ) : ℕ :=
    nat.rec_on
        n
        1
        (λ exp running, mul running m)

    theorem n_gt_n_is_absurd (n : ℕ) (n_gt_n : n > n) : false := gt_irrefl n $ n_gt_n

    namespace hidden

        theorem predecessor_succ_n_eq_n (n : ℕ) : predecessor (nat.succ n) = n :=
        nat.rec_on n
            (show predecessor (nat.succ nat.zero) = nat.zero, from rfl)
            (λ n1 eqs, show predecessor (nat.succ (nat.succ n1)) = nat.succ n1, from eq.symm $
                calc
                    nat.succ n1 = nat.succ (predecessor (nat.succ n1)) : by rw eqs
                            ... = predecessor (nat.succ (nat.succ n1)) : by refl
            )

        theorem mul_zero (n : ℕ) : mul n 0 = 0 := rfl
        theorem zero_mul (n : ℕ) : mul 0 n = 0 :=
        nat.rec_on n
            rfl
            (λ n1 eqs,
                calc
                    mul 0 (nat.succ n1) = mul 0 n1 : rfl
                                    ... = 0 : by rw eqs)

        theorem two_times_two : mul 2 2 = 4 := rfl
        theorem two_times_three : mul 2 3 = 6 := rfl

        theorem one_mul (n : ℕ) : mul 1 n = n :=
        nat.rec_on
            n
            (show mul 1 0 = 0, from rfl)
            (λ n eqs,
                calc
                    mul 1 (nat.succ n) = nat.succ (mul 1 n) : rfl
                                   ... = nat.succ n : by rw eqs
            )

        theorem mul_one (n : ℕ) : mul n 1 = n :=
        nat.rec_on n rfl
            (
                λ n eqs,
                    calc
                        mul (nat.succ n) 1 = nat.succ (mul n 1) : rfl
                                       ... = nat.succ n : by rw eqs
            )

        theorem succ_add_eq_add_succ (m n : ℕ) : nat.succ m + n = m + nat.succ n :=
        nat.rec_on n
            (show nat.succ m + nat.zero = nat.succ m, by rw add_zero)
            (
                assume n (hi : nat.succ m + n = m + nat.succ n),
                calc
                    nat.succ m + nat.succ n = m + 1 + nat.succ n : rfl
                        ... = m + (1 + nat.succ n) : by rw add_assoc
                        ... = m + (nat.succ $ nat.succ n) : by rw [add_comm 1]
                        ... = m + nat.succ (nat.succ n) : rfl
            )

        theorem mul_add_once (m n : ℕ) : mul m n + n = mul (nat.succ m) n :=
        nat.rec_on n
            (show mul m 0 + 0 = mul (nat.succ m) 0, from rfl)
            (assume n (hi : mul m n + n = mul (nat.succ m) n),
                -- eq.symm $
                calc
                    mul m (nat.succ n) + (nat.succ n) = mul m n + m + (nat.succ n) : rfl
                        ... = mul m n + (m + nat.succ n) : by rw [add_assoc]
                        ... = mul m n + (nat.succ m + n) : by rw succ_add_eq_add_succ
                        ... = mul m n + n + nat.succ m : by rw [add_comm (nat.succ m) n, add_assoc]
                        ... = mul (nat.succ m) n + nat.succ m : by rw hi
                        ... = mul (nat.succ m) (nat.succ n) : by refl
            )

        theorem mul_comm (m n : ℕ) : mul m n = mul n m :=
        nat.rec_on n
            (show mul m 0 = mul 0 m, by rw [mul_zero, zero_mul])
            (λ n eqs,
                show mul m (nat.succ n) = mul (nat.succ n) m, from calc
                    mul m (nat.succ n) = mul m n + m : rfl
                        ... = (mul n m) + m : by rw eqs
                        ... = mul (nat.succ n) m : by rw mul_add_once
            )

        theorem mul_distrib (l m n : ℕ) : mul l (m + n) = mul l m + mul l n :=
            nat.rec_on n
                (show mul l (m + 0) = mul l m + mul l 0, by repeat { rw add_zero <|> rw mul_zero })
                (assume n (hi : mul l (m + n) = mul l m + mul l n),
                    calc
                        mul l (m + nat.succ n) = mul l (nat.succ (m + n)) : rfl
                            ... = mul l (m + n) + l : rfl
                            ... = (mul l m + mul l n) + l : by rw hi
                            ... = mul l m + (mul l n + l) : by rw add_assoc
                            ... = mul l m + mul l (nat.succ n) : rfl
                )

        theorem mul_assoc (l m n : ℕ) : mul l (mul m n) = mul (mul l m) n :=
        nat.rec_on n
            (show mul l (mul m 0) = mul (mul l m) 0, by { repeat { rw mul_zero <|> rw zero_mul } })
            (assume n (hi : mul l (mul m n) = mul (mul l m) n),
                calc
                    mul l (mul m (nat.succ n)) = mul l ((mul m n) + m) : rfl
                        ... = mul l (mul m n) + mul l m : by rw mul_distrib
                        ... = mul (mul l m) n + mul l m : by rw hi
                        ... = mul (mul l m) (nat.succ n) : rfl
            )

        theorem predecessor_of_zero_is_zero : predecessor 0 = 0 := rfl
        theorem predecessor_of_one_is_zero : predecessor 1 = 0 := rfl
        theorem predecessor_of_two_is_one : predecessor 2 = 1 := rfl
        theorem predecessor_of_thirty_seven_is_thirty_six : predecessor 37 = 36 := rfl

        theorem zero_minus_zero_is_zero : trunc_sub 0 0 = 0 := rfl
        theorem n_minus_zero_is_n (n : ℕ) : trunc_sub n 0 = n := rfl
        theorem thirty_seven_minus_one_is_thirty_six : trunc_sub 37 1 = 36 := rfl
        theorem thirty_seven_minus_twelve_is_twenty_five : trunc_sub 37 12 = 25 := rfl

        theorem pow_2_2_is_4 : pow 2 2 = 4 := rfl
        theorem pow_6_2_is_36 : pow 6 2 = 36 := rfl
        theorem pow_0_1_is_0 : pow 0 1 = 0 := rfl
        theorem pow_n_zero_is_1 (n : ℕ) : pow n 0 = 1 := rfl
        theorem pow_5_3_is_125 : pow 5 3 = 125 := rfl

        theorem pow_n_1_is_n (n : ℕ) : (pow n 1) = n :=
        nat.rec_on n
            (show pow nat.zero 1 = 0, from rfl)
            (λ m m_pow_1_eq_m,
                calc
                    pow (nat.succ m) 1 = mul (pow (nat.succ m) 0) (nat.succ m) : rfl
                                   ... = mul 1 (nat.succ m) : rfl
                                   ... = mul (nat.succ m) 1 : by rw mul_comm
                                   ... = nat.succ m : by rw mul_one
            )

        theorem pow_addition_identity (b m n : ℕ) : pow b (m + n) = mul (pow b m) (pow b n) :=
        nat.rec_on n
            (show pow b (m + 0) = mul (pow b m) (pow b 0), by rw [add_zero, pow_n_zero_is_1, mul_one])
            (assume n (hi : pow b (m + n) = mul (pow b m) (pow b n)),
                calc
                    pow b (m + nat.succ n) = pow b (nat.succ (m + n)) : rfl
                        ... = mul (pow b (m + n)) b : rfl
                        ... = mul (mul (pow b m) (pow b n)) b : by rw hi
                        ... = mul (pow b m) (mul (pow b n) b) : by rw [mul_assoc]
                        ... = mul (pow b m) (pow b (nat.succ n)) : rfl
            )

        example (b m n : ℕ) : pow (pow b m) n = pow b (mul m n) :=
            nat.rec_on n
                (show pow (pow b m) 0 = pow b (mul m 0), by repeat { rw mul_zero <|> rw pow_n_zero_is_1 })
                (assume n (hi : pow (pow b m) n = pow b (mul m n)),
                    calc
                        pow (pow b m) (nat.succ n) = mul (pow (pow b m) n) (pow b m) : rfl
                            ... = mul (pow b (mul m n)) (pow b m) : by rw hi
                            ... = pow b (mul m n + m) : by rw pow_addition_identity
                            ... = pow b (mul m (nat.succ n)) : rfl
                )

        example (b c n : ℕ) : pow (mul b c) n = mul (pow b n) (pow c n) :=
            nat.rec_on n
                (show pow (mul b c) 0 = mul (pow b 0) (pow c 0), by { rw [pow_n_zero_is_1, pow_n_zero_is_1, pow_n_zero_is_1], refl })
                (assume n (hi : pow (mul b c) n = mul (pow b n) (pow c n)),
                    calc
                        pow (mul b c) (nat.succ n) = mul (pow (mul b c) n) (mul b c) : rfl
                            ... = mul (mul (pow b n) (pow c n)) (mul b c) : by rw hi
                            ... = mul (mul (mul (pow c n) (pow b n)) b) c : by rw [mul_assoc, mul_comm (pow b n)]
                            ... = mul (mul (pow c n) (mul (pow b n) b)) c : by rw [mul_assoc]
                            ... = mul (mul (pow c n) (pow b (nat.succ n))) c : rfl
                            ... = mul (mul (pow b (nat.succ n)) (pow c n)) c : by rw [mul_comm (pow b (nat.succ n))]
                            ... = mul (pow b (nat.succ n)) (mul (pow c n) c) : by rw mul_assoc
                            ... = mul (pow b (nat.succ n)) (pow c (nat.succ n)) : rfl

                )

    end hidden

end ch7ex1

namespace ch7ex2
    open ch7_section5

    variables { α : Type }

    def reverse (xs : list α) : list α :=
    list.rec_on xs
        list.nil
        (assume x (xs: list α) (hi : list α), list.append hi (list.cons x list.nil))

    theorem nil_append (xs : list α) : list.nil ++ xs = xs := rfl
    theorem reverse_nil : reverse (@list.nil α) = @list.nil α := rfl

    theorem reverse_append (xs ys : list α) : reverse (xs ++ ys) = reverse ys ++ reverse xs :=
    list.rec_on xs
        (show reverse (list.nil ++ ys) = reverse ys ++ list.nil, by rw [nil_append, append_nil])
        (assume hd (tl : list α) (hi : reverse (tl ++ ys) = reverse ys ++ reverse tl),
            calc
                reverse ((hd::tl) ++ ys) = reverse (hd::(tl ++ ys)) : rfl
                    ... = reverse (tl ++ ys) ++ (hd::list.nil) : rfl
                    ... = (reverse ys ++ reverse tl) ++ (hd::list.nil) : by rw hi
                    ... = reverse ys ++ (reverse tl ++ (hd::list.nil)) : by rw append_assoc
                    ... = reverse ys ++ reverse (hd::tl) : rfl
        )

    theorem basic_append (a b c d : α) :
        reverse (list.cons a (list.cons b (list.cons c (list.cons d list.nil)))) = list.cons d (list.cons c (list.cons b (list.cons a list.nil))) :=
            rfl

    theorem reverse_preserves_length (xs : list α) : length (reverse xs) = length xs :=
    list.rec_on xs
        (show length (reverse list.nil) = length list.nil, by rw reverse_nil)
        (assume hd (tl : list α) (hi : length (reverse tl) = length tl),
            calc
                length (reverse $ hd::tl) = length ((reverse tl) ++ (hd::list.nil)) : rfl
                    ... = length (reverse tl) + length (hd::list.nil) : by rw append_length
                    ... = length tl + 1 : by { rw hi, refl }
                    ... = length (hd::tl) : rfl
        )

    theorem reverse_twice_is_id (xs : list α) : reverse (reverse xs) = xs :=
    list.rec_on xs
        (show reverse (reverse list.nil) = list.nil, from rfl)
        (assume hd (xs : list α) (hi : reverse (reverse xs) = xs),
            calc
                reverse (reverse $ hd::xs) = reverse ((reverse xs) ++ (hd::list.nil)) : rfl
                    ... = reverse (hd::list.nil) ++ (reverse $ reverse xs) : by rw reverse_append
                    ... = hd::(reverse $ reverse xs) : rfl
                    ... = hd::xs : by rw hi
            -- show reverse (reverse $ hd::xs) = hd::xs, from sorry
        )

end ch7ex2

namespace ch7ex3

    inductive Expr : Type
    | const (n : ℕ) : Expr
    | var (n : ℕ) : Expr
    | plus (s t : Expr) : Expr
    | times (s t : Expr) : Expr

    def eval (vars : list ℕ) (expr : Expr) : option ℕ :=
    Expr.rec_on expr
        (λ hn, hn)
        (λ idx, vars.nth idx)
        (λ hs ht hseval hteval,
            have product : option (ℕ × ℕ), from option.bind hseval (λ ha, option.map (λ hb, ⟨ha, hb⟩) hteval ),
            option.map (λ (h : ℕ × ℕ), h.1 + h.2) product
        )
        (λ hs ht hseval hteval,
            have product : option (ℕ × ℕ), from option.bind hseval (λ ha, option.map (λ hb, (ha, hb)) hteval),
            option.map (λ (h : ℕ × ℕ), h.1 * h.2) product
        )

    theorem const_2_is_2 : eval [] (Expr.const 2) = option.some 2 := rfl
    theorem simple_var_assignment : eval [5] (Expr.var 0) = option.some 5 := rfl
    theorem missing_vars_fail_evaluation : eval [5, 8] (Expr.var 4) = option.none := rfl
    theorem addition_with_variable : eval [12] (Expr.plus (Expr.const 2) (Expr.var 0)) = some 14 := rfl
    theorem addition_and_multiplication_with_variables : eval [4, 10] (Expr.times (Expr.var 1) (Expr.plus (Expr.const 5) (Expr.var 0))) = some 90 := rfl

end ch7ex3

namespace ch7ex4

    inductive Proposition : Type
    | false : Proposition
    | var (n : ℕ) : Proposition
    | not (p : Proposition) : Proposition
    | and (p q : Proposition) : Proposition
    | or (p q : Proposition) : Proposition

    def eval (vars : list bool) (formula : Proposition) : option bool :=
    begin
        induction formula,
            case Proposition.false
                { exact bool.ff },
            case Proposition.var : n
                { exact vars.nth n },
            case Proposition.not
                { exact option.map (λ h, bool.rec_on h bool.tt bool.ff) formula_ih },
            case Proposition.and
                {
                    have : option (bool × bool), from option.bind  formula_ih_p (λ hp, option.map (λ hq, (hp, hq)) formula_ih_q),
                    exact option.map (λ (hpq : bool × bool), bool.rec_on hpq.1 bool.ff hpq.2) this
                 },
            case Proposition.or
                {
                    have : option (bool × bool), from option.bind  formula_ih_p (λ hp, option.map (λ hq, (hp, hq)) formula_ih_q),
                    exact option.map (λ (hpq : bool × bool), bool.rec_on hpq.1 hpq.2 bool.tt) this
                 },
    end

    def complexity (formula : Proposition) : ℕ :=
    begin
        induction formula,
            case Proposition.false
                { exact 1 },
            case Proposition.var
                { exact 1 },
            case Proposition.not
                { exact 1 + formula_ih },
            case Proposition.and : p q
                { exact formula_ih_p + formula_ih_q + 1 },
            case Proposition.or : p q
                { exact formula_ih_p + formula_ih_q + 1 },
    end

    def substitute (varnum : ℕ) (formula subst : Proposition) : Proposition :=
    begin
        induction formula,
            case Proposition.false
                { exact Proposition.false },
            case Proposition.var : n
                { exact cond (varnum = n) subst (Proposition.var n) },
            case Proposition.not : p
                { exact Proposition.not formula_ih },
            case Proposition.and : p q
                { exact Proposition.and formula_ih_p formula_ih_q },
            case Proposition.or : p q
                { exact Proposition.or formula_ih_p formula_ih_q },
    end

    lemma substitute_works : substitute 1 (Proposition.or (Proposition.false) (Proposition.var 1)) (Proposition.not Proposition.false) = Proposition.or Proposition.false (Proposition.not Proposition.false) := rfl

end ch7ex4

namespace ch7ex5

    inductive even : ℕ → bool → Prop
    | zero : even 0 tt
    | even_succ : ∀ (n : ℕ), even n ff → even (n + 1) tt
    | odd_succ : ∀ (n : ℕ), even n tt → even (n + 1) ff

    lemma zero_is_even : even 0 tt := even.zero
    lemma one_is_odd : even 1 ff := even.odd_succ 0 even.zero
    lemma two_is_even : even 2 tt := even.even_succ 1 one_is_odd
    lemma three_is_odd : even 3 ff := even.odd_succ 2 two_is_even

end ch7ex5


import .love01_definitions_and_statements_demo

/-! # LoVe Homework 1: Definitions and Statements

Homework must be done individually.

Replace the placeholders (e.g., `:= sorry`) with your solutions. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1 (1 point): Fibonacci Numbers

1.1 (1 point). Define the function `fib` that computes the Fibonacci
numbers. -/

def fib : ℕ → ℕ
| 0                       := 0
| 1                       := 1
| (nat.succ (nat.succ n)) := fib n + fib (nat.succ n)

/-! Alternative solution: -/

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

/-! 1.2 (0 points). Check that your function works as expected. -/

#eval fib 0   -- expected: 0
#eval fib 1   -- expected: 1
#eval fib 2   -- expected: 1
#eval fib 3   -- expected: 2
#eval fib 4   -- expected: 3
#eval fib 5   -- expected: 5
#eval fib 6   -- expected: 8
#eval fib 7   -- expected: 13
#eval fib 8   -- expected: 21

/-! ## Question 2 (3 points): Lists

Consider the type `list` described in the lecture and the `append₂` and
`reverse` functions from the lecture's demo. The `list` type is part of Lean's
core libraries. You will find the definition of `append₂` and `reverse` in
`love01_definitions_and_statements_demo.lean`. One way to find them quickly is
to

1. hold the Control (on Linux and Windows) or Command (on macOS) key pressed;
2. move the cursor to the identifier `list`, `append₂`, or `reverse`;
3. click the identifier. -/

#print list
#check append₂
#check reverse

/-! 2.1 (1 point). Test that `reverse` behaves as expected on a few examples.

In the first example, the type annotation `: list ℕ` is needed to guide Lean's
type inference. -/

#eval reverse ([] : list ℕ)   -- expected: []
#eval reverse [1, 3, 5]       -- expected: [5, 3, 1]
#eval reverse [3]             -- expected: [3]
#eval reverse [3, 2]          -- expected: [2, 3]
#eval reverse [3, 2, 5]       -- expected: [5, 2, 3]
#eval reverse [7, 2, 9, 8]    -- expected: [8, 9, 2, 7]
#eval reverse ["b", "c"]      -- expected: ["c, "b"]

/-! 2.2 (2 points). State (without proving them) the following properties of
`append₂` and `reverse`. Schematically:

    `append₂ (append₂ xs ys) zs = append₂ xs (append₂ ys zs)`
    `reverse (append₂ xs ys) = append₂ (reverse ys) (reverse xs)`

for all lists `xs`, `ys`, `zs`. Try to give meaningful names to your lemmas. If
you wonder how to enter the symbol `₂`, have a look at the table at the end of
the preface in the Hitchhiker's Guide.

Hint: Take a look at `reverse_reverse` from the demonstration file. -/

#check sorry_lemmas.reverse_reverse

lemma append₂_assoc {α : Type} (xs ys zs : list α) :
  append₂ (append₂ xs ys) zs = append₂ xs (append₂ ys zs) :=
sorry

lemma reverse_append₂ {α : Type} (xs ys : list α) :
  reverse (append₂ xs ys) = append₂ (reverse ys) (reverse xs) :=
sorry

/-! ## Question 3 (5 points): λ-Terms

3.1 (2 points). Complete the following definitions, by replacing the `sorry`
placeholders by terms of the expected type.

Please use reasonable names for the bound variables, e.g., `a : α`, `b : β`,
`c : γ`.

Hint: A procedure for doing so systematically is described in Section 1.1.4 of
the Hitchhiker's Guide. As explained there, you can use `_` as a placeholder
while constructing a term. By hovering over `_`, you will see the current
logical context. -/

def B : (α → β) → (γ → α) → γ → β :=
λg f c, g (f c)

def S : (α → β → γ) → (α → β) → α → γ :=
λg f a, g a (f a)

def more_nonsense : (γ → (α → β) → α) → γ → β → α :=
λh c b, h c (λx, b)

def even_more_nonsense : (α → α → β) → (β → γ) → α → β → γ :=
λf g a b, g (f a a)

/-! 3.2 (1 point). Complete the following definition.

This one looks more difficult, but it should be fairly straightforward if you
follow the procedure described in the Hitchhiker's Guide.

Note: Peirce is pronounced like the English word "purse". -/

def weak_peirce : ((((α → β) → α) → α) → β) → β :=
λf, f (λg, g (λa, f (λh, a)))

/-! 3.3 (2 points). Show the typing derivation for your definition of `S` above,
using ASCII or Unicode art. You might find the characters `–` (to draw
horizontal bars) and `⊢` useful.

Feel free to introduce abbreviations to avoid repeating large contexts `C`. -/

/-! Let `C` := `g : α → β → γ, f : α → β, a : α`. We have

    –––––––––––––– Var  –––––––––– Var  –––––––––––––– Var  –––––––––– Var
    C ⊢ g : α → β       C ⊢ a : α       C ⊢ f : α → β       C ⊢ a : α
    –––––––––––––––––––––––––––––– App  –––––––––––––––––––––––––––––– App
    C ⊢ g a : β → γ                     C ⊢ f a : β
    –––––––––––––––––––––––––––––––––––––––––––––––– App
    C ⊢ g a (f a) : γ
    ––––––––––––––––––––––––––––––––––––––––––––––––––– Lam
    g : α → β → γ, f : α → β ⊢ (λa, g a (f a)) : α → γ
    –––––––––––––––––––––––––––––––––––––––––––––––––––– Lam
    g : α → β → γ ⊢ (λf a, g a (f a)) : (α → β) → α → γ
    ––––––––––––––––––––––––––––––––––––––––––––––––––––– Lam
    ⊢ (λg f a, g a (f a)) ⊢ (α → β → γ) → (α → β) → α → γ -/

end LoVe


-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar las siguientes acciones
--    1. Importar la teoría de los anillos ordenados.
--    2. Declarar R como un tipo sobre los anillos ordenados.
--    3. Declarar a, b y c como variables sobre R.
-- ----------------------------------------------------------------------

import algebra.order.ring                -- 1
variables {R : Type*} [ordered_ring R]   -- 2
variables a b c: R                       -- 3

-- ---------------------------------------------------------------------
-- Ejercicio 2. Demostrar que
--    a ≤ b → 0 ≤ b - a
-- ----------------------------------------------------------------------

example : a ≤ b → 0 ≤ b - a :=
begin
  intro h,
  calc
    0   = a - a : (sub_self a).symm
    ... ≤ b - a : sub_le_sub_right h a
end

-- ---------------------------------------------------------------------
-- Ejercicio 3. Demostrar que
--    0 ≤ b - a → a ≤ b
-- ----------------------------------------------------------------------

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

example : 0 ≤ b - a → a ≤ b :=
begin
  intro h,
  calc
    a   = 0 + a       : (zero_add a).symm
    ... ≤ (b - a) + a : add_le_add_right h a
    ... = b           : sub_add_cancel b a
end

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

example : 0 ≤ b - a → a ≤ b :=
-- by library_search
sub_nonneg.mp

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

example : 0 ≤ b - a → a ≤ b :=
-- by hint
by simp

-- ---------------------------------------------------------------------
-- Ejercicio 4. Demostrar que
--    a ≤ b
--    0 ≤ c
-- entonces
--    a * c ≤ b * c
-- ----------------------------------------------------------------------

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

example
  (h1 : a ≤ b)
  (h2 : 0 ≤ c)
  : a * c ≤ b * c :=
begin
  have h3 : 0 ≤ b - a :=
    sub_nonneg.mpr h1,
  have h4 : 0 ≤ (b - a) * c :=
    mul_nonneg h3 h2,
  have h5 : (b - a) * c = b * c - a * c :=
    sub_mul b a c,
  have h6 : 0 ≤ b * c - a * c :=
    eq.trans_ge h5 h4,
  show a * c ≤ b * c,
    by exact sub_nonneg.mp h6,
end

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

open_locale classical

example
  (h1 : a ≤ b)
  (h2 : 0 ≤ c)
  : a * c ≤ b * c :=
begin
  by_cases h3 : b ≤ a,
  { have h3a : a = b :=
      le_antisymm h1 h3,
    show a * c ≤ b * c,
      by rw h3a },
  { by_cases h4 : c = 0,
    { calc a * c = a * 0 : by rw h4
             ... = 0     : by rw mul_zero
             ... ≤ 0     : le_refl 0
             ... = b * 0 : by rw mul_zero
             ... = b * c : by {congr ; rw h4}},
    { apply le_of_lt,
      apply mul_lt_mul_of_pos_right,
      { show a < b,
          by exact lt_of_le_not_le h1 h3 },
      { show 0 < c,
          by exact lt_of_le_of_ne h2 (ne.symm h4) }}},
end

-- 3ª demostración
example
  (h1 : a ≤ b)
  (h2 : 0 ≤ c)
  : a * c ≤ b * c :=
-- by library_search
mul_le_mul_of_nonneg_right h1 h2


-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar las siguientes acciones:
-- 1. Importar la teoría de monoides.
-- 2. Declaral α como un tipo.
-- 3. Declarar R como un monoide ordenado cancelativo.
-- 4. Declarar a, b, c y d como variables sobre R.
-- ----------------------------------------------------------------------

import algebra.order.monoid                                         -- 1

variables {α : Type*}                                                -- 2
variables {R : Type*} [ordered_cancel_add_comm_monoid R]             -- 3
variables a b c d : R                                                -- 4

-- ---------------------------------------------------------------------
-- Ejercicio 2. Calcular el tipo de
--     @add_le_add R _ a b c d
-- ----------------------------------------------------------------------

-- #check @add_le_add

-- Comentario: Al colocar el cursor sobre check se obtiene
--    a ≤ b → c ≤ d → a + c ≤ b + d

-- ---------------------------------------------------------------------
-- Ejercicio 3. Definir la función
--    fn_ub (α → R) → R → Prop
-- tal que (fn_ub f a) afirma que a es una cota superior de f.
-- ----------------------------------------------------------------------

def fn_ub (f : α → R) (a : R) : Prop := ∀ x, f x ≤ a

-- ---------------------------------------------------------------------
-- Ejercicio 4. Demostrar que que la suma de una cota superior de f y
-- otra de g es una cota superior de f + g.
-- ----------------------------------------------------------------------

theorem fn_ub_add
  {f g : α → R}
  {a b : R}
  (hfa : fn_ub f a)
  (hgb : fn_ub g b)
  : fn_ub (λ x, f x + g x) (a + b) :=
λ x, add_le_add (hfa x) (hgb x)


-- ---------------------------------------------------------------------
-- Ejercicio 1. Importar la teoría de anillos.
-- ----------------------------------------------------------------------

import algebra.ring

-- ---------------------------------------------------------------------
-- Ejercicio 2. Crear el espacio de nombres my_ring (para evitar
-- conflictos con los nombres).
-- ----------------------------------------------------------------------

namespace my_ring

-- ---------------------------------------------------------------------
-- Ejercicio 2. Declarar R como una variable implícita sobre los anillos.
-- ----------------------------------------------------------------------

variables {R : Type*} [ring R]

-- ---------------------------------------------------------------------
-- Ejercicio 3. Declarar a como una variable sobre R.
-- ----------------------------------------------------------------------

variable (a : R)

-- ---------------------------------------------------------------------
-- Ejercicio 4. Demostrar que
--    a + 0 = a
-- ----------------------------------------------------------------------

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

example : a + 0 = a :=
calc a + 0
     = 0 + a : by rw add_comm
 ... = a     : by rw zero_add

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

example : a + 0 = a :=
begin
  rw add_comm,
  rw zero_add,
end

-- El desarrollo de la prueba es
--
--    R : Type u_1,
--    _inst_1 : ring R,
--    a : R
--    ⊢ a + 0 = a
-- rw add_comm,
--    ⊢ 0 + a = a
-- rw zero_add,
--    no goals

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

theorem add_zero : a + 0 = a :=
by rw [add_comm, zero_add]

-- ---------------------------------------------------------------------
-- Ejercicio 5. Demostrar que
--    a + -a = 0
-- ----------------------------------------------------------------------

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

example : a + -a = 0 :=
calc a + -a
     = -a + a : by rw add_comm
 ... = 0      : by rw add_left_neg

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

example : a + -a = 0 :=
begin
  rw add_comm,
  rw add_left_neg,
end

-- El desarrollo de la prueba es
--
--    R : Type u_1,
--    _inst_1 : ring R,
--    a : R
--    ⊢ a + -a = 0
-- rw add_comm,
--    ⊢ -a + a = 0
-- rw add_left_neg,
--    no goals

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

theorem add_right_neg : a + -a = 0 :=
by rw [add_comm, add_left_neg]

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

end my_ring

-- ---------------------------------------------------------------------
-- Ejercicio 7. Calcular el tipo de @my_ring.add_zero.
-- ----------------------------------------------------------------------

-- #check @my_ring.add_zero

-- Comentario: Al colocar el cursor sobre check se obtiene
--    my_ring.add_zero : ∀ {R : Type u_1} [_inst_1 : ring R] (a : R),
--                       a + 0 = a

-- ---------------------------------------------------------------------
-- Ejercicio 8. Calcular el tipo de @add_zero.
-- ----------------------------------------------------------------------

-- #check @add_zero

-- Comentario: Al colocar el cursor sobre check se obtiene
--    add_zero : ∀ {M : Type u_1} [_inst_1 : add_monoid M] (a : M),
--               a + 0 = a


-- Conmutatividad_del_gcd.lean
-- Si m, n ∈ ℕ, entonces gcd(m,n) = gcd(n,m).
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 11-octubre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar si m, n ∈ ℕ, entonces gcd(m,n) = gcd(n,m).
-- ----------------------------------------------------------------------

import data.nat.gcd

open nat

variables k m n : ℕ

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

example : gcd m n = gcd n m :=
begin
  have h1 : gcd m n ∣ gcd n m,
    { have h1a : gcd m n ∣ n,
        by exact gcd_dvd_right m n,
      have h1b : gcd m n ∣ m,
        by exact gcd_dvd_left m n,
      show gcd m n ∣ gcd n m,
        by exact dvd_gcd h1a h1b, },
  have h2 : gcd n m ∣ gcd m n,
    { have h2a : gcd n m ∣ m,
        by exact gcd_dvd_right n m,
      have h2b : gcd n m ∣ n,
        by exact gcd_dvd_left n m,
      show gcd n m ∣ gcd m n,
        by exact dvd_gcd h2a h2b, },
  show gcd m n = gcd n m,
    by exact dvd_antisymm h1 h2,
end

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

example : gcd m n = gcd n m :=
begin
  apply dvd_antisymm,
  { apply dvd_gcd,
    { exact gcd_dvd_right m n, },
    { exact gcd_dvd_left m n, }},
  { apply dvd_gcd,
    { exact gcd_dvd_right n m, },
    { exact gcd_dvd_left n m, }},
end

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

example : gcd m n = gcd n m :=
begin
  apply dvd_antisymm,
  { apply dvd_gcd (gcd_dvd_right m n) (gcd_dvd_left m n)},
  { apply dvd_gcd (gcd_dvd_right n m) (gcd_dvd_left n m)},
end

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

example : gcd m n = gcd n m :=
dvd_antisymm
  (dvd_gcd (gcd_dvd_right m n) (gcd_dvd_left m n))
  (dvd_gcd (gcd_dvd_right n m) (gcd_dvd_left n m))

-- 5ª demostración
-- ===============

lemma aux : gcd m n ∣ gcd n m :=
begin
  have h1 : gcd m n ∣ n,
    by exact gcd_dvd_right m n,
  have h2 : gcd m n ∣ m,
    by exact gcd_dvd_left m n,
  show gcd m n ∣ gcd n m,
    by exact dvd_gcd h1 h2,
end

example : gcd m n = gcd n m :=
begin
  apply dvd_antisymm,
  { exact aux m n, },
  { exact aux n m, },
end

-- 6ª demostración
-- ===============

lemma aux2 : gcd m n ∣ gcd n m :=
dvd_gcd (gcd_dvd_right m n) (gcd_dvd_left m n)

example : gcd m n = gcd n m :=
dvd_antisymm (aux2 m n) (aux2 n m)

-- 7ª demostración
-- ===============

example : gcd m n = gcd n m :=
-- by library_search
gcd_comm m n


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

import tactic

/-

# Complete lattices

A lattice has sups and infs for all subsets with 2 elements. A *complete lattice* has sups
and infs for *every* subset. An example would be the set of all subsets of a fixed base set
(or, in Lean speak, the type `set X` of all subsets of a type `X`). Other examples: all
subgroups of a group, all subspaces of a vector space (and all subrings of a ring, all
subfields of a field...). A non-example would be the real numbers: we do say that the
reals are "complete" but we're talking about a different kind of completeness here
(an order-theoretic concept, not a metric space concept), and indeed subsets of ℝ like
the natural numbers, or the empty set, do *not* have a sup.

If `L` is a complete lattice, and `S : set L` is a subset, then its sup is `Sup S`
and its inf is `Inf S`. Here's the basic API for `Sup`:

`le_Sup : a ∈ S → a ≤ Sup S`
`Sup_le : (∀ (b : L), b ∈ S → b ≤ a) → Sup S ≤ a`

and you can probably guess the basic API for `Inf` :-)

A special case: the empty set has a `Sup` and and an `Inf`, and if you think carefully
about this then you'll discover that this means that the lattice has a least element and a
greatest element. These elements are called `⊥` and `⊤` respectively, pronounced `bot`
and `top`.

`Sup_empty : Sup ∅ = ⊥`

See if you can prove basic things about `⊥` and `Sup` just using the API for `Sup`.
All these results are in the library, but it's a good exercise to prove them from
the axioms directly.

-/

-- Let `L` be a complete lattice and say `a` is a general element of `L`
variables (L : Type) [complete_lattice L] (a : L)

-- this is called `bot_le`
example : ⊥ ≤ a :=
begin
  sorry,
end            -- solves the goal

-- this is called `le_bot_iff`
example : a ≤ ⊥ ↔ a = ⊥ :=
begin
  sorry,
end

-- this is called Sup_le_Sup
example (S T : set L) : S ⊆ T → Sup S ≤ Sup T :=
begin
  sorry,
end


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

import tactic
import combinatorics.simple_graph.acyclic -- trees and forests
/-

# Trees and forests

A *forest* is a graph with no cycles. A *tree* is a connected forest.

Here's how to do this in Lean. Let `G` be a graph with vertex set `V`.

-/

variables (V : Type) (G : simple_graph V)

-- Here's how to say "G is a forest"

example : Prop := G.is_acyclic

-- It's defined to mean "For all `v : V`, every walk from `v` to `v` is not a cycle. "

example : G.is_acyclic ↔ ∀ (v : V) (p : G.walk v v), ¬ p.is_cycle :=
begin
  refl,
end

-- Here's how to say "G is a tree"

example : Prop := G.is_tree

example : G.is_tree ↔ G.connected ∧ G.is_acyclic:=
begin
  exact G.is_tree_iff,
end

-- Here are some harder theorems from the library. Recall that a *path* is a walk
-- with no repeated vertices.

-- A graph is acyclic iff for all `v w : V`, there's at most one path from `v` to `w`.
example : G.is_acyclic ↔ ∀ (v w : V) (p q : G.path v w), p = q :=
simple_graph.is_acyclic_iff_path_unique

-- A graph is a tree iff `V` is nonempty and for all `v w : V`,
-- there's exactly one path from `v` to `w`.
example : G.is_tree ↔ nonempty V ∧ ∀ v w : V, ∃! (p : G.walk v w), p.is_path :=
simple_graph.is_tree_iff_exists_unique_path

-- If you want a logic puzzle, rephrase this in terms of `G.path`
-- (i.e. use the theorem above and then unpack and repack the RHS)
example : G.is_tree ↔ nonempty V ∧ ∀ v w : V, ∃! (p : G.path v w), true :=
begin
  sorry,
end

/-
If you want a hard graph theory puzzle, prove that in a finite tree,
1 + the number of edges equals the number of vertices.
I don't think this is in the library and it would be a neat project.

Because induction on the size of `V` will be messy (it will involve
changing `V` and them moving between graphs on different types)
I think that the best way to do this would be to prove that for
an acyclic graph on a fixed `V`, #connected components + #edges = #vertices,
by induction on number of edges. But then you'll have to define
connected components.

Note: the solution to this is not in the solutions! Indeed as far as I know
it's never been formalised in Lean before. I heard on the Discord that
Remi Botinelli was working on an API for connected components.
-/

open_locale classical

example (V : Type) [fintype V] (G : simple_graph V) (hG : G.is_tree) :
  1 + finset.card (G.edge_finset) = fintype.card V :=
sorry


/- LoVe Exercise 12: Basic Mathematical Structures -/

import .lovelib
import .love12_basic_mathematical_structures_demo

namespace LoVe

set_option pp.beta true

/- Question 1: Type Classes -/

namespace btree

/- Recall the datatype `btree` we introduced earlier: -/

#check btree

/- The following function takes two trees and attaches copies of the second
tree to each leaf of the first tree. -/

def append {α : Type} : btree α -> btree α -> btree α
| empty          y := y
| (node a x₁ x₂) y := node a (append x₁ y) (append x₂ y)

#reduce append (node 1 empty empty) (node 2 empty empty)

/- 1.1. Prove the following two lemmas by induction on `x`. -/

lemma append_assoc {α : Type} (x y z : btree α) :
  append (append x y) z = append x (append y z) :=
sorry

lemma append_empty {α : Type} (x : btree α) :
  append x empty = x :=
sorry

/- 1.2. Declare btree an instance of `add_monoid` using `append` as addition
operator. -/

#print add_monoid

instance {α : Type} : add_monoid (btree α) :=
sorry

/- 1.3. Explain why `btree` with `append` as addition cannot be declared an
instance of `add_group`. -/

#print add_group

/- 1.4. (**optional**) Prove the following lemma illustrating why this does not
constitute an `add_group`. -/

lemma example_no_inverse :
  ∃x : btree ℕ, ∀ y : btree ℕ, append y x ≠ empty :=
sorry

end btree

/- Question 2: Multisets and Finsets -/

/- Recall the following definitions from the lecture: -/

#check nodes_multiset
#check nodes_finset
#check nodes_list

/- 2.1. Prove that the multiset of nodes does not change when mirroring a tree.

Hint: Use induction on t and `ac_refl`. -/

lemma nodes_multiset_mirror (t : btree ℕ) :
  nodes_multiset (mirror t) = nodes_multiset t :=
sorry

/- 2.2. Prove that the finset of nodes does not change when mirroring a tree.

Hint: Use induction on t and `ac_refl`. -/

example (t : btree ℕ) :
  nodes_finset (mirror t) = nodes_finset t :=
sorry

/- 2.3. Prove that this does not hold for the list of nodes by providing an
example of a btree `t` for which `nodes_list t ≠ nodes_list (mirror t)`.

Hint: If you define a suitable counterexample, the proof below will succeed
without modifying it. -/

def counterexample : btree ℕ :=
sorry

#reduce nodes_list counterexample
#reduce nodes_list (mirror counterexample)

example :
  ∃t : btree ℕ, nodes_list t ≠ nodes_list (mirror t) :=
begin
  use counterexample,
  exact dec_trivial
end

end LoVe


namespace one
    /-
    Prove these equivalences:

    You should also try to understand why the reverse implication is not derivable in the last example.
    -/

    variables (α : Type) (p q : α → Prop)

    lemma forall_and_to_and_forall : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
    iff.intro
        (assume h : ∀ x, p x ∧ q x,
        and.intro
            (assume x : α, show p x, from (h x).left)
            (assume x : α, show q x, from (h x).right))
        (assume h : (∀ x, p x) ∧ (∀ x, q x),
        assume x : α,
        and.intro (h.left x) (h.right x))

    example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
        assume h₀ : ∀ x, p x → q x,
        assume h₁ : (∀ x, p x),
        assume x : α,
        have hpx : p x, from h₁ x,
        show q x, from h₀ x hpx

    example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
        assume h₀ : (∀ x, p x) ∨ (∀ x, q x),
        assume x : α,
        or.elim h₀
            (assume h₁ : (∀ x, p x), or.intro_left (q x) (h₁ x))
            (assume h₁ : (∀ x, q x), or.intro_right (p x) (h₁ x))

end one

namespace two
    /-
    It is often possible to bring a component of a formula outside a universal quantifier,
    when it does not depend on the quantified variable.
    Try proving these (one direction of the second of these requires classical logic):
    -/
    open classical

    variables (α : Type) (p q : α → Prop)
    variable r : Prop

    example : α → ((∀ x : α, r) ↔ r) :=
        assume x : α,
        iff.intro
            (assume h : (∀ x : α, r), h x)
            (assume hr : r, assume y : α, hr)

    example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
        iff.intro
            (assume h : (∀ x, p x ∨ r),
            by_cases
                (assume hr : r, or.intro_right (∀ x, p x) hr)
                (assume nr : ¬r,
                have h₀ : (∀ x, p x), from
                    (assume x : α,
                    have h₁ : (p x ∨ r), from h x,
                    or.resolve_right h₁ nr),
                or.intro_left r h₀))
            (assume h : (∀ x, p x) ∨ r,
            or.elim h
                (assume h₀ : ∀ x, p x,
                assume x : α, or.intro_left r (h₀ x))
                (assume h₀ : r,
                assume x : α, or.intro_right (p x) h₀))

    example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
        iff.intro
            (assume h : (∀ x, r → p x),
            assume hr : r,
            (assume x : α, h x hr))
            (assume h : (r → ∀ x, p x),
            assume x : α,
            assume hr : r,
            h hr x)

end two

namespace three
    /-
    Consider the “barber paradox,” that is, the claim that in a certain town there
    is a (male) barber that shaves all and only the men who do not shave themselves.
    Prove that this is a contradiction:
    -/
    open classical

    variables (men : Type) (barber : men)
    variable  (shaves : men → men → Prop)

    example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : false :=
        have h_iff_barber : shaves barber barber ↔ ¬ shaves barber barber, from h barber,
        by_cases
            (assume h_barber : shaves barber barber, absurd h_barber (iff.elim_left h_iff_barber h_barber))
            (assume n_barber : ¬shaves barber barber, absurd (iff.elim_right h_iff_barber n_barber) n_barber)

end three

namespace four
    /-
    Below, we have put definitions of divides and even in a special namespace to
    avoid conflicts with definitions in the library.
    The instance declaration make it possible to use the notation m | n for divides m n.
    Don’t worry about how it works; you will learn about that later.
    -/

    namespace hidden

    def divides (m n : ℕ) : Prop := ∃ k, m * k = n

    instance : has_dvd nat := ⟨divides⟩

    def even (n : ℕ) : Prop := 2 ∣ n

    section
    variables m n : ℕ

    #check m ∣ n
    #check m^n
    #check even (m^n +3)

    /-
    Remember that, without any parameters, an expression of type Prop is just an assertion.
    Fill in the definitions of prime and Fermat_prime below, and construct the given assertion.
    For example, you can say that there are infinitely many primes by asserting that for every natural number n,
    there is a prime number greater than n.
    Goldbach’s weak conjecture states that every odd number greater than 5 is the sum of three primes.
    Look up the definition of a Fermat prime or any of the other statements, if necessary.
    -/

    def prime (n : ℕ) : Prop := ∀ x : ℕ, divides x n → x = n ∨ x = 1

    def infinitely_many_primes : Prop := ∀ p : ℕ, prime p → ∃ p' : ℕ, prime p' ∧ p' > p

    -- A Fermat prime, is a prime of the form 2^(2^k)) + 1, where k is a nonnegative integer
    def Fermat_prime (n : ℕ) : Prop := prime n ∧ ∃ k : ℕ, n = 2^(2^k) + 1

    def infinitely_many_Fermat_primes : Prop := ∀ p : ℕ, Fermat_prime p → ∃ p' : ℕ, Fermat_prime p' ∧ p' > p

    -- Every integer greater than 2 can be written as the sum of three primes.
    def goldbach_conjecture : Prop := ∀ a : ℕ, a > 2 → ∃ x z y : ℕ, a = x + y + z

    -- Every odd number greater than 5 is the sum of three primes
    def Goldbach's_weak_conjecture : Prop := ∀ a : ℕ, ¬even a ∧ a > 5 → ∃ x z y : ℕ, a = x + y + z

    -- No three positive integers a, b, and c satisfy the equation a^n + b^n = c^n for any integer value of n greater than 2
    def Fermat's_last_theorem : Prop := ∀ n : ℕ, n > 2 → ∀ a b c : ℕ, a > 0 ∧ b > 0 ∧ c > 0 → ¬(a^n + b^n = c^n)

    end
    end hidden

end four

namespace five
    /-
    Prove as many of the identities listed in Section 4.4 as you can.
    -/
    open classical
    open not

    variables (α : Type) (p q : α → Prop)
    variable a : α
    variables r s : Prop

    example : (∃ x : α, r) → r :=
    assume h : ∃ x : α, r,
    match h with ⟨w, hw⟩ :=
        show r, from hw
    end

    example : (∃ x : α, r) → r :=
    assume h : ∃ x : α, r,
    let ⟨w, hw⟩ := h in hw

    lemma notnot : r ↔ ¬¬r :=
    iff.intro
        not_not_intro
        (assume nrr : ¬¬r,by_contradiction (assume nr : ¬r, absurd nr nrr))

    lemma not_exists : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) :=
    iff.intro
        (assume ne : ¬ ∃ x, p x,
        assume x : α,
        show ¬p x, from by_contradiction (
            assume nnp : ¬¬p x,
            have hp : p x, from by_contradiction (assume np : ¬p x, absurd np nnp),
            have he : (∃ x, p x), from exists.intro x hp,
            absurd he ne))
        (assume ha : (∀ x, ¬ p x),
        by_contradiction (
        assume nne : ¬(¬ ∃ x, p x),
        have he : (∃ x, p x), from by_contradiction (assume ne : ¬(∃ x, p x), absurd ne nne),
        let ⟨w, hw⟩ := he in (
            have nw : ¬p w, from ha w,
            absurd hw nw)))

    lemma not_and_to_imp : ¬(r ∧ ¬s) → (r → s) :=
    assume h : ¬(r ∧ ¬s),
    assume hp : r,
    show s, from
    or.elim (em s)
        (assume hq : s, hq)
        (assume hnq : ¬s, absurd (and.intro hp hnq) h)
    lemma not_imp : ¬(r → s) → r ∧ ¬s :=
    assume h : ¬(r → s),
    by_contradiction
        (assume n₀ : ¬(r ∧ ¬s),
        have n₁ : r → s, from not_and_to_imp r s n₀,
        show false, from absurd n₁ h)

    -- use the fact that there is at least one element of α, namely a
    example : r → (∃ x : α, r) :=
    assume hr : r,
    by_contradiction
    (assume n: ¬(∃ x : α, r),
    have h: (∀ x : α, ¬r), from iff.elim_left (not_exists α (λ a : α, r)) n,
    have nr : ¬r, from h a,
    absurd hr nr)

    lemma not_or_and_not : ¬(r ∨ s) ↔ ¬r ∧ ¬s :=
    iff.intro
        (assume h : ¬(r ∨ s), and.intro
            (assume hr: r,
            have hrs: r ∨ s, from or.intro_left s hr,
            show false, from absurd hrs h)
            (assume hs: s,
            have hrs: r ∨ s, from or.intro_right r hs,
            show false, from absurd hrs h))
        (assume nrs: ¬r ∧ ¬s,
        assume hrs: r ∨ s,
        have nr: ¬r, from nrs.left,
        have ns: ¬s, from nrs.right,
        show false, from or.elim hrs
            (assume hr: r, absurd hr nr)
            (assume hs: s, absurd hs ns))

    lemma not_and_iff_or_not : ¬ (r ∧ s) ↔ ¬ r ∨ ¬ s :=
    iff.intro
    (λ h : ¬ (r ∧ s),
    by_cases
        (assume hr : r,
        by_contradiction
            (assume n : ¬(¬ r ∨ ¬ s),
            have nns : ¬¬s, from (iff.elim_left (not_or_and_not (¬r) (¬s)) n).right,
            have hs : s, from by_contradiction (assume ns : ¬s, absurd ns nns),
            absurd (and.intro hr hs) h))
        (assume nr : ¬r, or.inl nr))
    (λ h ⟨hr, hs⟩, or.elim h (λ h, h hr) (λ h, h hs))

    example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
    iff.intro
        (assume h : ∃ x, p x ∧ r,
        by_contradiction
        (assume n : ¬((∃ x, p x) ∧ r),
        have h1 : ¬(∃ x, p x) ∨ ¬r, from iff.elim_left (not_and_iff_or_not (∃ x, p x) r) n,
        match h with ⟨w, hw⟩ :=
            have hr : r, from hw.right,
            have hw : p w, from hw.left,
            or.elim h1
                (assume h2 : ¬(∃ x, p x), absurd (exists.intro w hw) h2)
                (assume nr : ¬r, absurd hr nr)
        end))
        (assume h : (∃ x, p x) ∧ r,
        have hr : r, from h.right,
        have he : (∃ x, p x), from h.left,
        let ⟨w, hw⟩ := he in exists.intro w (and.intro hw hr))

    example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
    iff.intro
    (assume h : (∃ x, p x ∨ q x),
    match h with ⟨w, hw⟩ :=
    show (∃ x, p x) ∨ (∃ x, q x), from or.elim hw
            (assume hpw : p w, show (∃ x, p x) ∨ (∃ x, q x), from or.intro_left (∃ x, q x) (exists.intro w hpw))
            (assume hqw : q w, show (∃ x, p x) ∨ (∃ x, q x), from or.intro_right (∃ x, p x) (exists.intro w hqw))
    end)
    (assume h : (∃ x, p x) ∨ (∃ x, q x),
    or.elim h
        (assume h1 : (∃ x, p x), let ⟨w, hw⟩ := h1 in exists.intro w (or.inl hw))
        (assume h1 : (∃ x, q x), let ⟨w, hw⟩ := h1 in exists.intro w (or.inr hw)))

    lemma universal_notnot_arg : (∀ x, p x) ↔ (∀ x, ¬¬ p x) :=
    iff.intro
        (assume h : (∀ x, p x), assume x, not_not_intro (h x))
        (assume h : (∀ x, ¬¬ p x), assume x, by_contradiction (assume np : ¬p x, absurd np (h x)))

    lemma exists_notnot_arg : (∃ x, p x) ↔ (∃ x, ¬¬ p x) :=
    iff.intro
        (assume h : (∃ x, p x), let ⟨w, hw⟩ := h in exists.intro w (not_not_intro hw))
        (assume h : (∃ x, ¬¬ p x), match h with ⟨w, hw⟩ :=
            have hp : p w, from by_contradiction (assume np : ¬p w, absurd np hw),
            exists.intro w hp
        end)

    lemma not_exists_not : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
        have h: ¬ (∃ x, ¬ p x) ↔ (∀ x, ¬¬ p x), from not_exists α (λ x, ¬ p x),
        have h1 : (∀ x, ¬¬ p x) ↔ ¬ (∃ x, ¬ p x), from iff.symm h,
        have h2 : (∀ x, p x) ↔ (∀ x, ¬¬ p x), from universal_notnot_arg _ _,
        show (∀ x, p x) ↔ ¬ (∃ x, ¬ p x), from iff.trans h2 h1

    lemma not_universal_not : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
    have h₀ : (∀ x, ¬p x) ↔ ¬(∃ x, ¬¬ p x), from not_exists_not _ _,
    have h₁ : ¬(∃ x, ¬¬ p x) ↔ ¬(∃ x, p x), from not_congr (iff.symm (exists_notnot_arg _ _)),
    have h₂ : ¬(∀ x, ¬p x) ↔ ¬¬(∃ x, p x), from not_congr (iff.trans h₀ h₁),
    have h₃ : ¬¬(∃ x, p x) ↔ (∃ x, p x), from
        (iff.intro (assume nrr : ¬¬(∃ x, p x), by_contradiction (assume nr : ¬(∃ x, p x), absurd nr nrr))
         not_not_intro),
    iff.symm (iff.trans h₂ h₃)

    lemma not_universal_iff_exists_not : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) :=
    have h₀ : (∃ x, ¬p x) ↔ ¬ (∀ x, ¬¬ p x), from not_universal_not _ _,
    have h₁ : ¬¬(∀ x, ¬¬ p x) ↔ ¬(∃ x, ¬ p x), from iff.symm (not_congr h₀),
    have h₂ : (∀ x, ¬¬ p x) ↔ ¬¬(∀ x, ¬¬ p x), from
        (iff.intro not_not_intro
        (assume nrr : ¬¬(∀ x, ¬¬ p x), by_contradiction (assume nr : ¬(∀ x, ¬¬ p x), absurd nr nrr))),
    have h₃ : (∀ x, p x) ↔ ¬(∃ x, ¬ p x), from iff.trans (universal_notnot_arg _ _) (iff.trans h₂ h₁),
    have h₄ : ¬¬(∃ x, ¬ p x) ↔ (∃ x, ¬ p x), from
        (iff.intro (assume nrr : ¬¬(∃ x, ¬ p x), by_contradiction (assume nr : ¬(∃ x, ¬ p x), absurd nr nrr))
         not_not_intro),
    iff.trans (not_congr h₃) h₄

    example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
    iff.intro
    (assume h1 : (∀ x, p x → r),
    assume h2 : (∃ x, p x),
    let ⟨w, hw⟩ :=  h2 in h1 w hw)
    (assume h1 : (∃ x, p x) → r,
    by_contradiction
        (assume h2 : ¬(∀ x, p x → r),
        have h3 : ∃ x, ¬(p x → r), from iff.elim_left (not_universal_iff_exists_not _ _) h2,
        match h3 with ⟨w, hw⟩ :=
            have h4 : p w ∧ ¬ r, from not_imp _ _ hw,
            have h5 : (∃ x, p x), from exists.intro w h4.left,
            absurd (h1 h5) h4.right
        end))

    -- use the fact that there is at least one element of α, namely a
    example : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
    iff.intro
    (assume h1 : (∃ x, p x → r) ,
    assume h2 : (∀ x, p x),
    let ⟨w, hw⟩ :=  h1 in hw (h2 w))
    (assume h : (∀ x, p x) → r,
    by_contradiction
        (assume h1 : ¬(∃ x, p x → r),
        have h2 : (∀ x, ¬(p x → r)), from iff.elim_left (not_exists _ _) h1,
        have h3 : (∀ x, p x ∧ ¬r), from (assume x : α, not_imp _ _ (h2 x)),
        have h4 : (∀ x, p x), from (assume x, (h3 x).left),
        have hr : r, from h h4,
        have nr : ¬r, from (h3 a).right,
        absurd hr nr))

    lemma false_imp_any : ¬r → r → s :=
    assume nr : ¬r,
    assume hr : r,
    show s, from absurd hr nr

    -- use the fact that there is at least one element of α, namely a
    example : (∃ x, r → p x) ↔ (r → ∃ x, p x) :=
    iff.intro
    (assume h1 : (∃ x, r → p x),
    assume hr : r,
    let ⟨w, hrw⟩ := h1 in exists.intro w (hrw hr))
    (assume h1 : (r → ∃ x, p x),
    by_cases
        (assume hr : r,
        have ex : ∃ x, p x, from h1 hr,
        let ⟨w, hrw⟩ := (h1 hr) in exists.intro w (λ r, hrw))
        (assume nr : ¬r,
        -- here we use that fact that a : α exists
        exists.intro a (false_imp_any _ _ nr)))

end five

namespace six
    /-
    Give a calculational proof of the theorem log_mul below.
    -/

    variables (real : Type) [ordered_ring real]
    variables (log exp : real → real)
    variable  log_exp_eq : ∀ x, log (exp x) = x
    variable  exp_log_eq : ∀ {x}, x > 0 → exp (log x) = x
    variable  exp_pos    : ∀ x, exp x > 0
    variable  exp_add    : ∀ x y, exp (x + y) = exp x * exp y

    -- this ensures the assumptions are available in tactic proofs
    include log_exp_eq exp_log_eq exp_pos exp_add

    example (x y z : real) :
    exp (x + y + z) = exp x * exp y * exp z :=
    by rw [exp_add, exp_add]

    example (y : real) (h : y > 0)  : exp (log y) = y :=
    exp_log_eq h

    example {x y : real} (hx : x > 0) (hy : y > 0) : exp (log x + log y) = x * y
    := calc
    exp (log x + log y) = exp (log x) * exp (log y) : by rw exp_add
    ... = x * y : by rw [exp_log_eq hx, exp_log_eq hy]

    theorem log_mul {x y : real} (hx : x > 0) (hy : y > 0) :
    log (x * y) = log x + log y :=
    calc
    log (x * y) = log (exp (log x) * exp (log y)) : by rw [exp_log_eq hx, exp_log_eq hy]
    ... = log (exp (log x + log y)) : by rw exp_add
    ... = log x + log y : by rw log_exp_eq
end six

namespace seven
    /-
    Prove the theorem below, using only the ring properties of ℤ enumerated in Section 4.2
    and the theorem sub_self.
    -/
    #check sub_self

    example (x : ℤ) : x * 0 = 0 :=
    calc
    x * 0 = x * (0 - 0) : by rw sub_self
    ... = x * 0 - x * 0 : by rw mul_sub
    ... = 0 * x - 0 * x : by rw mul_comm
    ... = 0 * (x - x) : by rw mul_sub
    ... = 0 * 0 : by rw sub_self
    ... = 0 : by refl

end seven


import algebra.big_operators data.real.cau_seq tactic.ring algebra.archimedean data.nat.choose analysis.limits

open nat is_absolute_value

variables {α : Type*} {β : Type*} (f g : ℕ → α) (n m : ℕ)

local infix `^` := monoid.pow
local notation f ` ∑ ` : 90 n : 90  := finset.sum (finset.range n) f

section sum_range
variable [add_comm_monoid α]

lemma sum_range_succ : f ∑ succ n = f n + f ∑ n :=
have h : n ∉ finset.range n := by rw finset.mem_range; exact lt_irrefl _,
by rw [finset.range_succ, finset.sum_insert h]

lemma sum_range_succ' : ∀ n : ℕ, f ∑ succ n = (λ m, f (succ m)) ∑ n + f 0 :=
| 0        := by simp
| (succ n) := by rw [sum_range_succ (λ m, f (succ m)), add_assoc, ← sum_range_succ'];
                 exact sum_range_succ _ _

lemma sum_range_comm : f ∑ n = (λ m, f (n - (succ m))) ∑ n :=
begin
  induction n with n hi,
  { simp },
  { rw [sum_range_succ, sum_range_succ', hi, succ_sub_one, add_comm],
    simp [succ_sub_succ] }
end

end sum_range

theorem binomial [comm_semiring α] (x y : α) : ∀ n : ℕ,
    (x + y)^n = (λ m, x^m * y^(n - m) * choose n m) ∑ succ n
| 0        := by simp
| (succ n) :=
begin
  rw [_root_.pow_succ, binomial, add_mul, finset.mul_sum, finset.mul_sum, sum_range_succ, sum_range_succ',
      sum_range_succ, sum_range_succ', add_assoc, ← add_assoc (_ ∑ n), ← finset.sum_add_distrib],
  have h₁ : x * (x^n * y^(n - n) * choose n n) = x^succ n * y^(succ n - succ n)
      * choose (succ n) (succ n) :=
    by simp [_root_.pow_succ, mul_assoc, mul_comm, mul_left_comm],
  have  h₂ : y * (x^0 * y^(n - 0) * choose n 0) = x^0 * y^(succ n - 0) * choose (succ n) 0 :=
    by simp [_root_.pow_succ, mul_assoc, mul_comm, mul_left_comm],
  have h₃ : (λ m, x * (x^m * y^(n - m) * choose n m) + y * (x^succ m * y^(n - succ m)
      * choose n (succ m))) ∑ n
      = (λ m, x^succ m * y^(succ n - succ m) * ↑(choose (succ n) (succ m))) ∑ n :=
    finset.sum_congr rfl
      begin
        assume m hm,
        rw finset.mem_range at hm,
        rw [← mul_assoc y, ← mul_assoc y, mul_right_comm y, ← _root_.pow_succ, add_one, ← succ_sub hm],
        simp [succ_sub_succ, _root_.pow_succ, choose_succ_succ, mul_add, mul_comm,
            mul_assoc, mul_left_comm]
      end,
  rw [h₁, h₂, h₃]
end


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que la composición de dos funciones monótonas es
-- monótona.
-- ----------------------------------------------------------------------

import data.real.basic

variables (f g : ℝ → ℝ)

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

example
  (mf : monotone f)
  (mg : monotone g)
  : monotone (f ∘ g) :=
begin
  have h1 : ∀ a b, a ≤ b → (f ∘ g) a ≤ (f ∘ g) b,
    { intros a b hab,
      have h1 : g a ≤ g b := mg hab,
      have h2 : f (g a) ≤ f (g b) := mf h1,
      show (f ∘ g) a ≤ (f ∘ g) b,
        by exact h2, },
  show monotone (f ∘ g),
    by exact h1,
end

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

example
  (mf : monotone f)
  (mg : monotone g)
  : monotone (f ∘ g) :=
begin
  intros a b hab,
  apply mf,
  apply mg,
  apply hab
end

-- Su desarrollo es
--
-- f g : ℝ → ℝ,
-- mf : monotone f,
-- mg : monotone g
-- ⊢ monotone (λ (x : ℝ), f (g x))
--    >> intros a b hab,
-- a b : ℝ,
-- hab : a ≤ b
-- ⊢ (λ (x : ℝ), f (g x)) a ≤ (λ (x : ℝ), f (g x)) b
--    >> apply mf,
-- ⊢ g a ≤ g b
--    >> apply mg,
-- ⊢ a ≤ b
--    >> apply hab
-- no goals

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

example (mf : monotone f) (mg : monotone g) :
  monotone (f ∘ g) :=
λ a b hab, mf (mg hab)


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

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

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

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

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

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

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

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

open int

-- 0045
example (n : ℤ) (h_pair : even n) (h_non_pair : ¬ even n) : 0 = 1 :=
begin
  contradiction,
end

-- 0046
example (P Q : Prop) (h₁ : P ∨ Q) (h₂ : ¬ (P ∧ Q)) : ¬ P ↔ Q :=
begin
  split,
    intro p,
    cases h₁ with hp hq,
      contradiction,
    exact hq,
  intros q p,
  exact h₂ ⟨p, q⟩,
end

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

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

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

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

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

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

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

(we'll prove a variation on this lemma below).
-/
example (u : ℕ → ℝ) (l l' : ℝ) : seq_limit u l → seq_limit u l' → l = l' :=
begin
  intros hl hl',
  by_contradiction H,
  change l ≠ l' at H, -- Lean does not need this line
  have ineg : |l-l'| > 0,
    exact abs_pos_of_ne_zero (sub_ne_zero_of_ne H),
  cases hl ( |l-l'|/4 ) (by linarith) with N hN,
  cases hl' ( |l-l'|/4 ) (by linarith) with N' hN',
  let N₀ := max N N', -- this is a new tactic, whose effect should be clear
  specialize hN N₀ (le_max_left _ _),
  specialize hN' N₀ (le_max_right _ _),
  have clef : |l-l'| < |l-l'|,
    calc
    |l - l'| = |(l-u N₀) + (u N₀ -l')|   : by ring
         ... ≤ |l - u N₀| + |u N₀ - l'|  : by apply abs_add
         ... = |u N₀ - l| + |u N₀ - l'|  : by rw abs_sub
         ... < |l-l'|                    : by linarith,
  linarith, -- linarith can also find simple numerical contradictions
end

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

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

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

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

/-
In the next exercise, we'll use
 odd n : ∃ k, n = 2*k + 1
 not_even_iff_odd : ¬ even n ↔ odd n,
-/
-- 0048
example (n : ℤ) : even (n^2) ↔ even n :=
begin
  split,
    contrapose,
    repeat {rw not_even_iff_odd},
    rintro ⟨n', rfl⟩,
    exact ⟨2 * n' * (n' + 1), by ring⟩,
  intro h,
  cases h with n' hn',
  subst hn',
  exact ⟨2 * n'^2, by 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 :=
by split; contrapose!; exact id

/-
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 c,
    exact h ⟨k, c⟩,
  rintro h ⟨k, hk⟩,
  exact h k hk,
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,
    intros h,
    by_contradiction c,
    apply h,
    intro x,
    by_contradiction c',
    apply c,
    use x,
  rintro ⟨x, hx⟩ h,
  exact hx (h x),
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
  unfold even_fun,
  push_neg,
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,
  push_neg,
  intro hx,
  use x/2,
  split; 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 hxy,
      cases eq_or_lt_of_le hxy with hxy hxy,
        rw hxy,
      exact le_of_lt (h x y hxy),
    contrapose!,
    exact h y x,
  intros h x y,
  contrapose!,
  intro hf,
  rwa h,
end


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

import tactic.interval_cases
import data.nat.modeq

/-!
# IMO 1964 Q1

(a) Find all positive integers $n$ for which $2^n-1$ is divisible by $7$.

(b) Prove that there is no positive integer $n$ for which $2^n+1$ is divisible by $7$.

We define a predicate for the solutions in (a), and prove that it is the set of positive
integers which are a multiple of 3.
-/

/-!
## Intermediate lemmas
-/

open nat

namespace imo1964_q1

lemma two_pow_three_mul_mod_seven (m : ℕ) : 2 ^ (3 * m) ≡ 1 [MOD 7] :=
begin
  rw pow_mul,
  have h : 8 ≡ 1 [MOD 7] := modeq_of_dvd (by {use -1, norm_num }),
  convert h.pow _,
  simp,
end

lemma two_pow_three_mul_add_one_mod_seven (m : ℕ) : 2 ^ (3 * m + 1) ≡ 2 [MOD 7] :=
begin
  rw pow_add,
  exact (two_pow_three_mul_mod_seven m).mul_right _,
end

lemma two_pow_three_mul_add_two_mod_seven (m : ℕ) : 2 ^ (3 * m + 2) ≡ 4 [MOD 7] :=
begin
  rw pow_add,
  exact (two_pow_three_mul_mod_seven m).mul_right _,
end

/-!
## The question
-/

def problem_predicate (n : ℕ) : Prop := 7 ∣ 2 ^ n - 1

lemma aux (n : ℕ) : problem_predicate n ↔ 2 ^ n ≡ 1 [MOD 7] :=
begin
  rw nat.modeq.comm,
  apply (modeq_iff_dvd' _).symm,
  apply nat.one_le_pow'
end

theorem imo1964_q1a (n : ℕ) (hn : 0 < n) : problem_predicate n ↔ 3 ∣ n :=
begin
  rw aux,
  split,
  { intro h,
    let t := n % 3,
    rw [(show n = 3 * (n / 3) + t, from (nat.div_add_mod n 3).symm)] at h,
    have ht : t < 3 := nat.mod_lt _ dec_trivial,
    interval_cases t with hr; rw hr at h,
    { exact nat.dvd_of_mod_eq_zero hr },
    { exfalso,
      have nonsense := (two_pow_three_mul_add_one_mod_seven _).symm.trans h,
      rw modeq_iff_dvd at nonsense,
      norm_num at nonsense },
    { exfalso,
      have nonsense := (two_pow_three_mul_add_two_mod_seven _).symm.trans h,
      rw modeq_iff_dvd at nonsense,
      norm_num at nonsense } },
  { rintro ⟨m, rfl⟩,
    apply two_pow_three_mul_mod_seven }
end

end imo1964_q1

open imo1964_q1

theorem imo1964_q1b (n : ℕ) : ¬ (7 ∣ 2 ^ n + 1) :=
begin
  let t := n % 3,
  rw [← modeq_zero_iff_dvd, (show n = 3 * (n / 3) + t, from (nat.div_add_mod n 3).symm)],
  have ht : t < 3 := nat.mod_lt _ dec_trivial,
  interval_cases t with hr; rw hr,
  { rw add_zero,
    intro h,
    have := h.symm.trans ((two_pow_three_mul_mod_seven _).add_right _),
    rw modeq_iff_dvd at this,
    norm_num at this },
  { intro h,
    have := h.symm.trans ((two_pow_three_mul_add_one_mod_seven _).add_right _),
    rw modeq_iff_dvd at this,
    norm_num at this },
  { intro h,
    have := h.symm.trans ((two_pow_three_mul_add_two_mod_seven _).add_right _),
    rw modeq_iff_dvd at this,
    norm_num at this },
end


/- ****************** -/
/- *** PREDICATES *** -/
/- ****************** -/

/-
Define a function called isOdd that
takes an argument, n : ℕ, and returns a
proposition that asserts that n is odd.
The function will thus be a predicate on
values of type ℕ. Hint: a number is odd
if it's one more than an even number.
-/

def isOdd (n :ℕ) : Prop :=
  ∃ m : nat, 2 * m + 1 = n

/-
To test your predicate, use "example"
to write and prove isOdd(15).
-/

example : isOdd 15 :=
begin
unfold isOdd,
apply exists.intro 7,
apply rfl,
end

/-
Define isSmall : ℕ → Prop, to be a
predicate that is true exactly when the
argument, n, is such that n = 0 ∨ n = 1
∨ n = 2 ∨ n = 3 ∨ n = 4 ∨ n = 5. (Don't
try to rewrite this proposition as an
inequality; just use it as is.)
-/

def isSmall (n :ℕ) : Prop := n = 0 ∨ n = 1
∨ n = 2 ∨ n = 3 ∨ n = 4 ∨ n = 5

/-
Define a predicate, isBoth(n:ℕ) that
is true exacly when n satisfies both the
isOdd and isSmall predicates. Use isOdd
and isSmall in your answer.
-/

def isBoth (n : ℕ) : Prop := isOdd n ∧ isSmall n

/- ******************* -/
/- *** DISJUNCTIONS ***-/
/- ******************* -/

/-
Jane walks to school or she carries
her lunch. In either case, she uses
a bag. If she walks, she uses a bag for
her books. If she carries her lunch, she
uses a bag to carry her food. So if she
walks, she uses a bag, and if she carries
her lunch, she uses a bag. From the fact
that she walks or carries her lunch, and
from the added facts that in either case
she uses a bag, we can conclude that she
uses a bag.

Using Walks, Carries, and Bag as names
of propositions, write a Lean example
that asserts the following proposition;
then prove it.

If Walks, Carries, and Bag are
propositions, then if (Walks ∨ Carries)
is true, and then if (Walks implies Bag)
is true, and then if (Carries implies Bag)
is true, then Bag is true.
-/

example : ∀ (Walks Carries Bag : Prop), ((Walks ∨ Carries) ∧ (Walks → Bag)
∧ (Carries → Bag)) → Bag :=
begin
    assume W C B f,
    have f1 := f.1,
    cases f1 with w c,
    exact f.2.1 w,
    exact f.2.2 c,
end

/-
Prove the following proposition.
-/

example :
    ∀ (P Q R : Prop),
    (P ∧ Q) → (Q ∨ R) → (P ∨ R) :=
begin
    assume P Q R paq qar,
    exact or.inl paq.1,
end

/- ********************* -/
/- *** EXISTENTIALS  *** -/
/- ********************* -/

/-
Referring to the isBoth predicate you
defined in question #1, state and prove
the proposition that there *exists* a
number, n, that satisfies isBoth. Remember
that you can use the unfold tactic to
expand the name of a predicate in a goal.
Use "example" to state the proposition.
-/

lemma pf3isOdd : isOdd 3 :=
begin
unfold isOdd,
apply exists.intro 1,
apply rfl,
end

lemma pf3isSmall : isSmall 3 :=
begin
unfold isSmall,
apply or.intro_right,
apply or.intro_right,
apply or.intro_right,
apply or.intro_left,
apply rfl,
end

example : ∃ n : ℕ, isBoth n :=
begin
unfold isBoth,
apply exists.intro 3,
have pfOdd := pf3isOdd,
have pfSmall := pf3isSmall,
exact and.intro pfOdd pfSmall,
end

/-
Suppose that Heavy and Round are
predicates on values of any type, T.
Prove the proposition that if every
t : T is Heavy (satisfies the Heavy
predicate) and if there exists some
t : T that is Round (satisfies the
Round predicate) then there exists
some t : T is both Heavy and Round
(satisfies the conjunction of the
two properties).
-/

example :
∀ T : Type, ∀ (Heavy Round : T → Prop),
(∀ t, Heavy t) → (∃ t, Round t) →
(∃ t, Heavy t ∧ Round t) :=
begin
    assume T h r aht ert,
    apply exists.elim ert,
    assume x rt,
    apply exists.intro x,
    exact and.intro (aht x) rt,
end


/-
For this homework, we will talk about equivalence relations.
These are relations `R : α → α → Prop` which are
- reflexive
- symmetric : ∀ a a', R a a' → R a' a
- transitive
-/

#print Equivalence

/-
The most common equivalence relation is equality but can be
made in many other ways.
-/

/-- Two strings are related if they have the same length -/
def EqLen (s t : String) : Prop := s.length = t.length

/-- We use the notation \sim for `EqLen` -/
infixl:60 " ∼ " => EqLen

example : Equivalence EqLen := by
  have refl : ∀ (s : String), s ∼ s := sorry
  have symm : ∀ { s s' : String }, s ∼ s' → s' ∼ s := sorry
  have trans : ∀ { s t u : String }, s ∼ t → t ∼ u → s ∼ u := sorry
  exact ⟨refl,symm,trans⟩

/-
Instead of filling the `sorry`'s here we will note that this is part
of a more general pattern. Given a type `α` and a function `f : α → β`,
the relation a ∼ a' if and only if f a = f a' is an equivalence
relation.
-/

variable {α β : Type} (f : α → β)

def QuotRel (a a' : α) : Prop := f a = f a'

notation:65 x:65 " ∼ " y:65 " mod " z:65 => QuotRel z x y

theorem quotRel_refl (a : α) : a ∼ a mod f:= sorry

theorem quotRel_symm (a a' : α) (h : a ∼ a' mod f) : a' ∼ a mod f := sorry

theorem quotRel_trans (a₁ a₂ a₃ : α) (h₁ : a₁ ∼ a₂ mod f) (h₂ : a₂ ∼ a₃ mod f)
    : a₁ ∼ a₃ mod f := sorry

/- For this one, Lean can get confused between `(a : α)` and `{a : α}` in
arguments. Suppose we have a `F : ∀ {a : α}, ...`. Then `@F` foces you to
supply `a` in the argument. Conversely if you believe Lean should be able
"to figure it out" for a particular argument you can use a underscore `_` -/
theorem quotRel_equiv : Equivalence (· ∼ · mod f) := sorry

/-
Given an equivalence relation, it is natural to consider its equivalence
classes. We can do that using subtypes.
-/

/-- `Cl R a` should be read as the type whose terms are terms `(a':α)`
which satisfy `R a' a` -/
def Cl {α : Type} (R : α → α → Prop) (a : α) : Type := { a' : α // R a' a }

/- Given `(a' : Cl R a)` you can access the underlying term of type `α`
as `a'.val` and the proof of `R a'.val a` as `a'.property`-/

#print Subtype

/- All strings of length 4 -/
#check Cl EqLen "Lean"

variable (R : α → α → Prop) (h : Equivalence R)

/- If two equivalence classes share an element, then they coincide -/
theorem cl_disjoint (a₁ a₂ : α) (a : Cl R a₁) (a' : Cl R a₂) (h : a.val = a'.val)
    : a₁ = a₂ := sorry


open Nat

namespace Relations

-- True and False under propositions as types

inductive True : Prop where
  | tt : True

inductive False : Prop where
  -- this line deliberately left blank

theorem ex_quod_libet : ∀ (P : Prop), False → P
  := by
       intros P f
       cases f

-- Inequality

inductive le : Nat → Nat → Prop
| refl : ∀ {m}, le m m
| step : ∀ {m n}, le m n → le m (succ n)

instance : LE Nat where
  le := le

theorem zero_le_n : ∀ {n : Nat}, 0 ≤ n
  := by
      intros n
      induction n with
      | zero =>
          exact le.refl
      | succ _ ih =>
          apply le.step
          exact ih

theorem succ_le_succ : ∀ {m n : Nat}, m ≤ n → succ m ≤ succ n
  := by
      intros m n m_le_n
      induction m_le_n with
      | refl =>
         exact le.refl
      | step _ ih =>
         apply le.step
         exact ih

theorem invert : ∀ {m n : Nat}, succ m ≤ succ n → m ≤ n
  := by
      intros m n sm_le_sn
      cases sm_le_sn with
      | refl =>
         exact le.refl
      | step sm_le_n =>
         cases n with
         | zero => contradiction
         | succ n => apply le.step; apply invert; assumption

  -- Proof is by induction, but induction tactic does not apply.
  -- Note explicit application of "invert"

theorem trans_le : ∀ {m n p : Nat}, m ≤ n → n ≤ p → m ≤ p
  := by
       intros m n p m_le_n n_le_p
       induction n_le_p with
       | refl => assumption
       | step _ ih => apply le.step; exact ih

instance : Trans (.≤. : Nat → Nat → Prop)
                 (.≤. : Nat → Nat → Prop)
                 (.≤. : Nat → Nat → Prop) where
  trans := trans_le

example : 2 ≤ 7 :=
  calc
    2 ≤ 4 := le.step (le.step le.refl)
    _ ≤ 5 := le.step le.refl
    _ ≤ 7 := le.step (le.step le.refl)

-- Strict inequality

def lt (n m : Nat) := le (Nat.succ n) m

instance : LT Nat where
  lt := lt

theorem lt_succ : ∀ {n : Nat}, n < succ n
  := by
      intros
      exact le.refl

theorem le_of_lt : ∀ {m n : Nat}, m < n → m ≤ n
  := by
      intros
      apply invert
      apply le.step
      assumption

theorem trans_lt_of_lt_of_le : ∀ {m n p : Nat}, m < n → n ≤ p → m < p
  := by
      intros
      apply trans_le
      assumption
      assumption

theorem trans_lt_of_le_of_lt : ∀ {m n p : Nat}, m ≤ n → n < p → m < p
  := by
      intros
      apply trans_le
      apply succ_le_succ
      assumption
      assumption

theorem trans_lt : ∀ {m n p : Nat}, m < n → n < p → m < p
  := by
      intros
      apply trans_lt_of_lt_of_le
      assumption
      apply le_of_lt
      assumption

instance : Trans (.<. : Nat → Nat → Prop)
                 (.<. : Nat → Nat → Prop)
                 (.<. : Nat → Nat → Prop) where
  trans := trans_lt

instance : Trans (.≤. : Nat → Nat → Prop)
                 (.<. : Nat → Nat → Prop)
                 (.<. : Nat → Nat → Prop) where
  trans := trans_lt_of_le_of_lt

instance : Trans (.<. : Nat → Nat → Prop)
                 (.≤. : Nat → Nat → Prop)
                 (.<. : Nat → Nat → Prop) where
  trans := trans_lt_of_lt_of_le

example : 2 < 7 :=
  calc
    2 ≤ 4 := le.step (le.step le.refl)
    _ < 5 := lt_succ
    _ ≤ 7 := le.step (le.step le.refl)

-- Exercise.
-- Here is a different definition of ≤.
-- Prove the two definitions equivalent,
-- that is, that each implies the other.

inductive le2 : Nat → Nat → Prop
| z_le_n : ∀ {n : Nat},
      ----------
      le2 zero n
| s_le_s : ∀ {m n : Nat},
      le2 m n
      ---------------------
    → le2 (succ m) (succ n)

open le2

theorem antisymm : ∀ {m n : Nat}, le2 m n → le2 n m → m = n
  := by
      intros _ _ m_le_n n_le_m
      induction m_le_n with
      | z_le_n =>
        cases n_le_m with
        | z_le_n =>
            rfl
      | s_le_s _ ih =>
        cases n_le_m with
        | s_le_s n_le_m =>
            rw [ih n_le_m]

theorem antisymm' : ∀ {m n : Nat}, le2 m n → le2 n m → m = n
| _ , _ , z_le_n , z_le_n => rfl
| _ , _ , s_le_s m_le_n , s_le_s n_le_m => by rw [antisymm' m_le_n n_le_m]

-- Exercise.
-- Prove lt, lt2, and lt3 equivalent.

inductive lt2 : Nat → Nat → Prop where
| z_lt_s : ∀ {n}, lt2 zero (succ n)
| s_lt_s : ∀ {m n},
      lt2 m n
      ---------------------
    → lt2 (succ m) (succ n)

inductive lt3 : Nat → Nat → Prop where
| m_lt_sm : ∀ {m}, lt3 m (succ m)
| m_lt_sn : ∀ {m n},
      lt3 m n
      ---------------------
    → lt3 m (succ n)

-- Exercise. Prove the following.
--   m ≤ n iff ∃ p, m + p = n
--   m < n iff ∃ p, p ≠ 0 ∧ m + p = n


/- # LoVe Homework 1: Definitions and Statements
Homework must be done individually.
Replace the placeholders (e.g., `:= sorry`) with your solutions. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/- ## Question 1 (1 point): Fibonacci Numbers
1.1 (1 point). Define the function `fib` that computes the Fibonacci
numbers. -/

def fib : ℕ → ℕ
| 0             := 0
| (nat.succ 0)  := 1
| (nat.succ (nat.succ m))  := fib m + fib (nat.succ m)

/- 1.2 (0 points). Check that your function works as expected. -/

#eval fib 0   -- expected: 0
#eval fib 1   -- expected: 1
#eval fib 2   -- expected: 1
#eval fib 3   -- expected: 2
#eval fib 4   -- expected: 3
#eval fib 5   -- expected: 5
#eval fib 6   -- expected: 8
#eval fib 7   -- expected: 13
#eval fib 8   -- expected: 21

/- ## Question 2 (3 points): Lists
Consider the type `list` described in the lecture and the `append₂` and
`reverse` functions from the lecture's demo. The `list` type is part of Lean's
core libraries. You will find the definition of `append₂` and `reverse` in
`love01_definitions_and_statements_demo.lean`. One way to find them quickly is
to
1. hold the Control (on Linux and Windows) or Command (on macOS) key pressed;
2. move the cursor to the identifier `list`, `append₂`, or `reverse`;
3. click the identifier. -/

inductive list (α : Type) : Type
| nil  : list
| cons : α → list → list

def append₂ {α : Type} : list α → list α → list α
| list.nil         ys := ys
| (list.cons x xs) ys := list.cons x (append₂ xs ys)

def reverse {α : Type} : list α → list α
| list.nil          := list.nil
| (list.cons x xs)  := append₂ (reverse xs) (list.cons x list.nil)

#print list
#check append₂
#check reverse

/- 2.1 (1 point). Test that `reverse` behaves as expected on a few examples.
In the first example, the type annotation `: list ℕ` is needed to guide Lean's
type inference. -/

#eval reverse (list.nil : list ℕ)   -- expected: []
#eval reverse [1, 3, 5]       -- expected: [5, 3, 1]
-- invoke `#eval` here

/- 2.2 (2 points). State (without proving them) the following properties of
`append₂` and `reverse`. Schematically:
    `append₂ (append₂ xs ys) zs = append₂ xs (append₂ ys zs)`
    `reverse (append₂ xs ys) = append₂ (reverse ys) (reverse xs)`
for all lists `xs`, `ys`, `zs`. Try to give meaningful names to your lemmas. If
you wonder how to enter the symbol `₂`, have a look at the table at the end of
the preface in the Hitchhiker's Guide.
Hint: Take a look at `reverse_reverse` from the demonstration file. -/

#check sorry_lemmas.reverse_reverse

-- enter your lemma statements here

/- ## Question 3 (5 points): λ-Terms
3.1 (2 points). Complete the following definitions, by replacing the `sorry`
placeholders by terms of the expected type.
Please use reasonable names for the bound variables, e.g., `a : α`, `b : β`,
`c : γ`.
Hint: A procedure for doing so systematically is described in Section 1.1.4 of
the Hitchhiker's Guide. As explained there, you can use `_` as a placeholder
while constructing a term. By hovering over `_`, you will see the current
logical context. -/
constants α β γ : Prop

def B : (α → β) → (γ → α) → γ → β :=
λ f, λ g, λ c, f(g(c))

def S : (α → β → γ) → (α → β) → α → γ :=
λ f, λ g, λ a, f a (g a)

def more_nonsense : (γ → (α → β) → α) → γ → β → α :=
λ f, λ c, λ b, f c (λ a, b)

def even_more_nonsense : (α → α → β) → (β → γ) → α → β → γ :=
λ f, λ g, λ a, λ b, g(b)

/- 3.2 (1 point). Complete the following definition.
This one looks more difficult, but it should be fairly straightforward if you
follow the procedure described in the Hitchhiker's Guide.
Note: Peirce is pronounced like the English word "purse". -/

def weak_peirce : ((((α → β) → α) → α) → β) → β :=
λ f, f (λ g, g (λ a, f (λ h, a))

/- 3.3 (2 points). Show the typing derivation for your definition of `S` above,
using ASCII or Unicode art. You might find the characters `–` (to draw
horizontal bars) and `⊢` useful.
Feel free to introduce abbreviations to avoid repeating large contexts `C`. -/

-- write your solution here

end LoVe