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 x hx,
  cases h₂ with y hy,
  rw hx at hy,
  use (x*y),
  linarith,
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,
  rintros ⟨k, rfl⟩,
  ring,
  intro h,
  rw h,
end

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

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

-/
open function

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

-- 0031
example (h : surjective (g ∘ f)) : surjective g :=
begin
  intros a,
  rcases h a with ⟨k, rfl⟩,
  use f(k),
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
  intros a,
  rcases hg a with ⟨k, rfl⟩,
  rcases hf k with ⟨j, rfl⟩,
  use j,
end


import data.real.basic

open_locale classical

/-
Theoretical negations.

This file is for people interested in logic who want to fully understand
negations.

Here we don't use `contrapose` or `push_neg`. The goal is to prove lemmas
that are used by those tactics. Of course we can use
`exfalso`, `by_contradiction` and `by_cases`.

If this doesn't sound like fun then skip ahead to the next file.
-/

section negation_prop

variables P Q : Prop

-- 0055
example : (P → Q) ↔ (¬ Q → ¬ P) :=
begin
  split,
    intros h hnq hnp,
    exact hnq (h hnp),
  intros h p,
  by_contradiction hnq,
  exact h hnq p,
end

-- 0056
lemma non_imp (P Q : Prop) : ¬ (P → Q) ↔ P ∧ ¬ Q :=
begin
  split,
    intros h,
    by_contradiction c,
    apply h,
    intro hp,
    by_contradiction c',
    apply c,
    exact ⟨hp, c'⟩,
  rintro ⟨hp, hnq⟩ h,
  exact hnq (h hp),
end

-- In the next one, let's use the axiom
-- propext {P Q : Prop} : (P ↔ Q) → P = Q

-- 0057
example (P : Prop) : ¬ P ↔ P = false :=
begin
  split,
    intro h,
    apply propext,
    split,
      intro p,
      exact h p,
    intro c,
    exfalso,
    exact c,
  intro h,
  rw h,
  exact id,
end

end negation_prop

section negation_quantifiers
variables (X : Type) (P : X → Prop)

-- 0058
example : ¬ (∀ x, P x) ↔ ∃ x, ¬ P x :=
begin
  split,
    intro h,
    by_contradiction c,
    apply h,
    intro x,
    by_contradiction c',
    apply c,
    use x,
  rintros ⟨x, hx⟩ h,
  exact hx (h x),
end

-- 0059
example : ¬ (∃ x, P x) ↔ ∀ x, ¬ P x :=
begin
  split,
    intros h x,
    by_contradiction c,
    apply h,
    use [x, c],
  rintro h ⟨x, hc⟩,
  exact h x hc,
end

-- 0060
example (P : ℝ → Prop) : ¬ (∃ ε > 0, P ε) ↔ ∀ ε > 0, ¬ P ε :=
begin
  split,
    intros h ε ε_pos c,
    apply h,
    use [ε, ε_pos, c],
  rintro h ⟨ε, ε_pos, c⟩,
  exact h ε ε_pos c,
end

-- 0061
example (P : ℝ → Prop) : ¬ (∀ x > 0, P x) ↔ ∃ x > 0, ¬ P x :=
begin
  split,
    intro h,
    by_contradiction c,
    apply h,
    intros x hx,
    by_contradiction c',
    apply c,
    use [x, hx, c'],
  rintro ⟨x, hx, hx'⟩ c,
  exact hx' (c x hx),
end

end negation_quantifiers


-- Asociatividad_del_minimo.lean
-- Si a, b, c ∈ ℝ, entonces min(min(a,b),c) = min(a,min(b,c))
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 3-octubre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Sean a, b y c números reales. Demostrar que
--    min (min a b) c = min a (min b c)
-- ----------------------------------------------------------------------

import data.real.basic tactic

variables a b c : ℝ

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

-- Se usará el siguiente lema auxilar.
lemma aux1 : min (min a b) c ≤ min a (min b c) :=
begin
  apply le_min,
  { show min (min a b) c ≤ a,
      calc min (min a b) c
           ≤ min a b : min_le_left (min a b) c
       ... ≤ a       : min_le_left a b },
  { show min (min a b) c ≤ min b c,
    apply le_min,
    { show min (min a b) c ≤ b,
        calc min (min a b) c
             ≤ min a b  : min_le_left (min a b) c
         ... ≤ b        : min_le_right a b },
    { show min (min a b) c ≤ c,
      { by exact min_le_right (min a b) c }}},
end

example :
  min (min a b) c = min a (min b c) :=
begin
  apply le_antisymm,
  { show min (min a b) c ≤ min a (min b c),
      by exact aux1 a b c },
  { show min a (min b c) ≤ min (min a b) c,
      calc min a (min b c)
           = min (min b c) a : by apply min_comm
       ... = min (min c b) a : by {congr' 1, apply min_comm}
       ... ≤ min c (min b a) : by apply aux1
       ... = min c (min a b) : by {congr' 1, apply min_comm}
       ... = min (min a b) c : by apply min_comm },
end

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

-- Se usará el siguiente lema auxilar.
lemma aux2 : min (min a b) c ≤ min a (min b c) :=
begin
  apply le_min,
  { calc min (min a b) c
         ≤ min a b : by simp [min_le_left]
     ... ≤ a       : by simp [min_le_left] },
  { apply le_min,
    { calc min (min a b) c
           ≤ min a b  : by simp [min_le_left]
       ... ≤ b        : by simp [min_le_right] },
    { simp [min_le_right] }},
end

example : min (min a b) c = min a (min b c) :=
begin
  apply le_antisymm,
  { by exact aux2 a b c },
  { calc min a (min b c)
         = min (min b c) a : by simp [min_comm]
     ... = min (min c b) a : by simp [min_comm]
     ... ≤ min c (min b a) : by simp [aux2]
     ... = min c (min a b) : by simp [min_comm]
     ... = min (min a b) c : by simp [min_comm] },
end

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

example : min (min a b) c = min a (min b c) :=
begin
  apply le_antisymm,
  { by exact aux2 a b c },
  { calc min a (min b c)
         = min (min c b) a : by simp [min_comm]
     ... ≤ min c (min b a) : by simp [aux2]
     ... = min (min a b) c : by simp [min_comm] },
end


-- Exercise 1
--
-- Prove these equivalences:
section
  variables (α : Type) (p q : α → Prop)

  example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
  begin
    split,

    {
      assume h,
      split,

      {
        assume hx,
        exact and.left (h hx),
      },

      {
        assume hx,
        exact and.right (h hx),
      },
    },

    {
      assume h,
      assume hx,

      exact and.intro (and.left h hx) (and.right h hx),
    },
  end

  example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
  begin
    assume hpq,
    assume hp,
    assume x,

    exact hpq x (hp x),
  end

  example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
  begin
    have f : (∀ x, p x) → ∀ x, p x ∨ q x,
    {
      assume hpx,
      assume hx,

      exact or.intro_left (q hx) (hpx hx),
    },

    have g : (∀ x, q x) → ∀ x, p x ∨ q x,
    {
      assume hqx,
      assume hx,

      exact or.intro_right (p hx) (hqx hx),
    },

    assume h,
    exact or.elim h f g,

    -- NOTE: Not derivable because there exist situations in which
    --       (∀ x, p x ∨ q x) is true, but (∀ x, p x) ∨ (∀ x, q x) is not. The
    --       former can mix truthiness between the two, but the latter requires
    --       that p must hold for all x, or q must hold for all x.
  end
end

-- Exercise 2
--
-- 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):
section
  universe u
  variables (α : Type u) (p q : α → Prop)
  variable r : Prop

  example : α → ((∀ x : α, r) ↔ r) :=
  begin
    assume ha,
    split,

    assume har,
    exact har ha,

    assume hr,
    assume haa,
    exact hr,
  end

  variable x : α

  example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
  begin
    split,

    -- hphx : p hx = evidence of the predicate p holding on an element of x
    -- p hx : Prop = a property that may or may not be true of hx

    {
      assume h,

      have pos : r → (∀ x, p x) ∨ r,
      {
        assume hr,
        exact or.inr hr,
      },

      have neg : ¬r → (∀ x, p x) ∨ r,
      {
        assume hnr,

        apply or.inl,
        assume hx,

        exact or.elim (h hx) id (λ hr, absurd hr hnr),
      },

      exact classical.by_cases pos neg,
    },

    {
      assume h,
      assume hx,

      exact
        or.elim
        h
        (λ hl, or.intro_left r (hl hx))
        (λ hr, or.intro_right (p hx) hr),
    },
  end

  example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
  begin
    split,

    {
      assume h,
      assume hr,
      assume ha,
      exact (h ha) hr,
    },

    {
      assume h,
      assume ha,
      assume hr,
      exact (h hr) ha,
    },
  end
end

-- Exercise 3
--
-- 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:
section
  variables (men : Type) (barber : men)
  variable  (shaves : men → men → Prop)
  variable (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x)

  example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : false :=
  begin
    have hiff : (shaves barber barber) ↔ ¬(shaves barber barber), from h barber,

    have f : shaves barber barber → false,
    {
      assume hf,
      exact (hiff.mp hf) hf,
    },

    have g : ¬shaves barber barber → false,
    {
      assume hg,
      exact f (hiff.mpr hg),
    },

    exact classical.by_cases f g,
  end
end

-- Exercise 4
--
-- Below, we have put definitions of divides and even in a special namespace to
-- avoid conflicts with definitions in the library. The instance declaration
-- makes 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 -- You can enter the '∣' character by typing \mid

  section
  variables m n : ℕ

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

-- 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.
section
  def prime (n : ℕ) : Prop :=
    ¬∃m: ℕ, m < n ∧ hidden.divides m n

  def infinitely_many_primes : Prop :=
    ∀x : ℕ, ∃y : ℕ, y > x ∧ prime y

  def Fermat_prime (n : ℕ) : Prop :=
    ∃k : ℕ, n = 2^(2 ^ k) + 1

  def infinitely_many_Fermat_primes : Prop :=
    ∀x : ℕ, ∃y : ℕ, y > x ∧ Fermat_prime y

  def goldbach_conjecture : Prop :=
    ∀x : ℕ, x > 2 → ∃y z : ℕ, x = y + z ∧ prime y ∧ prime z

  def Goldbach's_weak_conjecture : Prop :=
    ∀x : ℕ, ¬hidden.even x ∧ x > 5 → ∃a b c : ℕ, x = a + b + c ∧ prime a ∧ prime b ∧ prime c

  def Fermat's_last_theorem : Prop :=
    ¬∃a b c n : ℕ, n > 2 ∧ a^n = b^n + c^n
end

-- Exercise 5
--
-- Prove as many of the identities listed in Section 4.4 as you can.
section
  open classical

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

  include a

  example : (∃ x : α, r) → r :=
  begin
    assume h,

    have hq : (∀a : α, r → r),
    assume a,
    assume hr,
    exact hr,

    exact exists.elim h hq,
  end

  example : r → (∃ x : α, r) :=
  begin
    assume hr,

    exact exists.intro a hr
  end

  example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
  begin
    split,

    {
      assume h,

      have hq : ∀(a : α), p a ∧ r → (∃(x : α), p x) ∧ r,
      {
        assume ha,
        assume conj,

        apply and.intro,
        exact exists.intro ha (and.left conj),
        exact and.right conj,
      },

      exact exists.elim h hq,
    },

    {
      assume h,
      have hl, from and.left h,
      have hr, from and.right h,

      have hq : ∀(a : α), p a → (∃x : α, p x ∧ r),
      {
        assume ha,
        assume h',
        apply exists.intro,

        exact and.intro h' hr,
      },

      exact exists.elim hl hq,
    },
  end

  example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
  match h with ⟨w, hw⟩ :=
    ⟨w, hw.right, hw.left⟩
  end

  example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
  begin
    split,

    {
      assume h,

      have f : Π a : α, p a → ∃ (x : α), p x,
      exact exists.intro,

      have g : Π a : α, q a → ∃ (x : α), q x,
      exact exists.intro,

      apply exists.elim h,
      assume ha,
      assume hor,

      exact
        or.elim
        hor
        (λ hpa, or.inl (f ha hpa))
        (λ hqa, or.inr (g ha hqa)),
    },

    {
      assume hor,

      have f : (∃ (x : α), p x) → ∃ (x : α), p x ∨ q x,
      assume hex,
      exact (
        match hex with ⟨ha, hpx⟩ :=
          ⟨ha, or.inl hpx⟩
        end
      ),

      have g : (∃ (x : α), q x) → ∃ (x : α), p x ∨ q x,
      assume hex,
      exact (
        match hex with ⟨ha, hqx⟩ :=
          ⟨ha, or.inr hqx⟩
        end
      ),

      exact or.elim hor f g,
    },
  end

  example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
  begin
    split,

    {
      assume hfa,
      assume hex,

      have f : (∀ (a : α), (λ (x : α), ¬p x) a → false),
      assume a,
      assume hnpa,
      exact hnpa (hfa a),

      exact exists.elim hex f,
    },

    {
      assume hne,
      assume a,

      apply by_contradiction,
      assume hnpa,

      have he : ∃x : α, ¬p x,
      exact exists.intro a hnpa,

      exact hne he,
    },
  end

  example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
  begin
    split,

    {
      assume hex,
      assume hfa,

      have f : (∀ (a : α), (λ (x : α), p x) a → false),
      assume ha,
      assume hpa,
      exact (hfa ha) hpa,

      exact exists.elim hex f,
    },

    {
      assume hnfa,
      apply by_contradiction,
      assume hnex,

      have hfa : ∀(x : α), ¬p x,
      assume ha,
      assume hpa,
      exact hnex (exists.intro ha hpa),

      exact hnfa hfa,
    },
  end

  example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) :=
  begin
    split,

    {
      assume hnex,
      assume ha,
      assume hpa,

      have hex : ∃(x : α), p x,
      exact exists.intro ha hpa,

      exact hnex hex,
    },

    {
      assume hfa,
      assume hex,

      have f : (∀ x : α, (λ a : α, p x) a → false),
      assume ha,
      assume hpa,
      exact (hfa ha) hpa,

      exact exists.elim hex f,
    },
  end

  example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) :=
  begin
    split,

    {
      assume hnfa,
      apply by_contradiction,
      assume hnex,

      have hfa : ∀ (x : α), p x,
      assume ha,
      apply by_contradiction,
      assume hpnha,
      exact hnex ⟨ha, hpnha⟩,

      exact hnfa hfa,
    },

    {
      assume hex,
      assume hfa,

      exact (
        match hex with ⟨ha, hnpa⟩ :=
          hnpa (hfa ha)
        end
      ),
    },
  end

  example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
  begin
    split,

    {
      assume hfa,
      assume hex,

      have f : (∀ (x : α), (λ (y : α), p y) x → r),
      assume ha,
      assume hpa,
      exact (hfa ha) hpa,

      exact exists.elim hex f,
    },

    {
      assume h,
      assume ha,
      assume hpa,

      exact h (exists.intro ha hpa),
    },
  end

  example : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
  begin
    split,

    {
      assume hex,
      assume hfa,

      have f : (∀ (a : α), (λ (x : α), p x → r) a → r),
      assume ha,
      assume hpar,
      apply hpar,
      exact hfa ha,

      exact exists.elim hex f,
    },

    {
      assume h,

      eapply by_cases,

      assume hfa : ∀ (x : α), p x,
      exact ⟨a, (λ hpa, h hfa)⟩,

      assume hnfa,
      have hnex : ∃ (x : α), ¬p x,
      {
        apply by_contradiction,
        assume hnex,
        have hfa : ∀ (x : α), p x,
        {
          assume ha,
          apply by_contradiction,
          assume hnpa,
          exact hnex ⟨ha, hnpa⟩,
        },
        exact hnfa hfa,
      },

      exact (
        match hnex with ⟨x, hnpx⟩ :=
          ⟨x, (λ hpx : p x, absurd hpx hnpx)⟩
        end
      ),
    },
  end

  example : (∃ x, r → p x) ↔ (r → ∃ x, p x) :=
  begin
    split,

    {
      assume hex,
      assume hr,

      have f : (∀ (a : α), (λ (x : α), r → p x) a → (∃ y : α, p  y)),
      assume ha,
      assume hrpha,
      exact exists.intro ha (hrpha hr),

      exact exists.elim hex f,
    },

    {
      assume h,

      eapply by_cases,

      assume hr : r,
      exact (
        match h hr with ⟨ha, hpa⟩ :=
          ⟨ha, (λ hr' : r, hpa)⟩
        end
      ),

      assume hnr : ¬r,
      exact ⟨a, (λ hr : r, absurd hr hnr)⟩,
    },
  end
end

-- Exercise 6
--
-- Give a calculational proof of the theorem log_mul below.
section
  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

  theorem log_mul {x y : real} (hx : x > 0) (hy : y > 0) :
    log (x * y) = log x + log y :=
    begin
      rw <- exp_log_eq hx,
      rw <- exp_log_eq hy,
      rw <- exp_add,
      repeat {rw log_exp_eq},
    end
end

-- Exericse 7
--
-- Prove the theorem below, using only the ring properties of ℤ enumerated in
-- Section 4.2 and the theorem sub_self.
section
  #check sub_self

  example (x : ℤ) : x * 0 = 0 :=
  begin
    -- NOTE: Is this supposed to be harder? I think that mul_zero is a property
    --       of rings.
    rw mul_zero,
  end
end


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

/-!
# Antidiagonals in ℕ × ℕ as multisets

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

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

## Notes

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

namespace multiset
namespace nat

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

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

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

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

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

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

lemma antidiagonal_succ' {n : ℕ} :
  antidiagonal (n + 1) = (n + 1, 0) ::ₘ ((antidiagonal n).map (prod.map id nat.succ)) :=
by rw [antidiagonal, list.nat.antidiagonal_succ', ← coe_add, add_comm, antidiagonal, coe_map,
  coe_add, list.singleton_append, cons_coe]

lemma antidiagonal_succ_succ' {n : ℕ} :
  antidiagonal (n + 2) =
  (0, n + 2) ::ₘ (n + 2, 0) ::ₘ ((antidiagonal n).map (prod.map nat.succ nat.succ)) :=
by { rw [antidiagonal_succ, antidiagonal_succ', map_cons, map_map, prod_map], refl }

lemma map_swap_antidiagonal {n : ℕ} :
  (antidiagonal n).map prod.swap = antidiagonal n :=
by rw [antidiagonal, coe_map, list.nat.map_swap_antidiagonal, coe_reverse]

end nat
end multiset


import algebra.module.basic
import data.real.basic

/-
This is EXAM #1 for UVa CS 4501/6501, Spring
2021.

Ground rules are as follows:
- open book, notes, code, WWW
- no communication except with instructors
- don't post any part of an answer on Piazza
- use Piazza only to ask about errors or to request clarifications

The exam is due before midnight on Friday, Apr 2.

Read the following and follow the instructions.
Submit your completed lin2K_test.lean file. That's it!B
-/

/-
There are at least two important advantages
of defining a typeclass instance for a given
algebraic structure. First, if you actually
write the proofs, then you can be sure you
have made no mistakes. Second, in practical
terms, you also get a well organized set of
operations that allow you to "program in the
algebra."

This problem will evaluate your ability to
read and understand typeclass definitions
and instances. In particular, we will build
an instsance of the Lean-give "vector_space"
typeclass to enable programming in a 2-D
vector space over any field K. Vectors will
be pairs of K values. Scalars will be single
K values. The two key operations are vector
vector addition and scalar multiplication of
a vector.

More explanation follows, especially for
those students who've not had any linear
algebra. Don't worry, it's pretty easy.
-/

/-
We'll start by assuming that K is a non-empty
field.
-/

universes u
variables {K : Type u} [field K] [inhabited K]

/-
Here's the typeclass we're going to instantiate.
The two type arguments, K and K × K, specify the
types of the scalars (K) and vectors (pairs of K
values).
-/
#check vector_space K (K × K)

/-
Again, scalars of the vector space will
be values of type K; while the vectors
will be pairs of scalar values. We will
also have vector-vector addition and
scalar-vector multiplication defined
"as usual":

(k11,k12)+(k21,k22) = (k11+k21, k12+k22)
k • (k11,k12) =(k * k11, k * k12) The +
notation between two vectors is overloaded
to mean vector addition as we've defined
it here. The + operator between two scalars
is addition in the scalar field. The • is
infix for scalar vector multiplication
(smul). The * operator is multiplication
of scalars in the scalar field.
-/

/-
In a nutshell, then, this module lets
you write "2-D vector space code over
any field, K" with the standard vector
space operators of vector-vector add
and scalar vector multiplication along
with the addition and multiplication of
scalars themselves.
-/

/-
At this point the best way to understand what
follows is to jump to the end, see where we're
going, read the associated comments, and work
backwards. You can actually skip ths step on a
first readings and just use the vector-space
API that this module provides.

We are not asking you to write typeclasses and
instances on this question. Your real focus
should be on understanding (1) that creating a
typeclass instances let's you "write good code
in a given algebraic structure," (2) each of
the properties that the vector space and field
operators have to have to really have a vector
space.

Once you've understood what's in this file,
please go to the lin2k_test.file, and answer
the questions there.
-/

/-
Now please JUMP to the end of this file (look
for "JUMP"). That's where we instantiate the
vector-space API for ordered pairs of field
values with the usual addition and scaling
operations. From there you will see what we
had to build by way of operations, "helper"
instances, and proofs (all stubbed out using
sorry), in order to create the vector_space
instance for values of type K and K × K. Work
your way back up to the comment just below
and you will have finished understanding what
we've done. Then go over to lin2k_test.lean
to answer the test questions.
-/

/-
Your UPWAWRD reading has now ended, too.
-/

/-
Ordered pair addition is already overloaded
for values of type prod α β, courtesy of the
Lean library, provided that α and β themselves
have overloaded add (+) operations. The +
notation is Lean-provided for *any* type that
implements has-add. Here we use it to add
vectors in the form ordered pairs of K values.

Here is what it says in the Lean library. You
can always right click on a name and select
"go to definition" to see its definition. Try
it for has_add below. Here's what you'll find.

class has_add (α : Type u) := (add : α → α → α)
...
infix +        := has_add.add

That's the class definition *and* the + notation.
-/
#check has_add  -- right click, go to definition

/-
In addition to vector-vector addition, a
vector space also has a scalar-vector
multiplication operator. Lean defines •
as an infix notation for multiplying a
scalar by a vector (scalar on left). So,
for example, if 5 is a scalar and (2, 3)
is a vector, you can write 5 • (2, 3) to
express the "scaling" of (2, 3) by 5.

Here are details from the Lean library.
/-- Scalar multiplication operation, denoted `•` (`\bu`) -/
class has_scalar (α : Type u) (γ : Type v) := (smul : α → γ → γ)
infixr ` • `:73 := has_scalar.smul
-/
def scalar_vector_mul : K → K × K → K × K
| a (f,s) := ⟨ a * f, a * s ⟩

-- For our scalars smul will be our smul
instance : has_scalar K (K × K) := ⟨ scalar_vector_mul ⟩

/-
Rules for scalar multiplication.
- scalar multiplication by one
- scalar multiplication by produt of scalars

class mul_action (α : Type u) (β : Type v) [monoid α] extends has_scalar α β :=
(one_smul : ∀ b : β, (1 : α) • b = b)
(mul_smul : ∀ (x y : α) (b : β), (x * y) • b = x • y • b)
-/
lemma one_smul_l2  {K : Type u} [field K] [inhabited K] : ∀ v : K × K, (1 : K) • v = v := sorry
lemma mul_smul_l2 : ∀ (x y : K) (b : K × K), (x * y) • b = x • y • b := sorry
instance : mul_action K (K × K) := ⟨ one_smul_l2, mul_smul_l2 ⟩

/-
Rules for scalar multiplication:
- multiplying scalar by sum of vectors
- multiplying scalar by zero vector

class distrib_mul_action (α : Type u) (β : Type v) [monoid α] [add_monoid β]
  extends mul_action α β :=
(smul_add : ∀(r : α) (x y : β), r • (x + y) = r • x + r • y)
(smul_zero : ∀(r : α), r • (0 : β) = 0)
-/
lemma smul_add_l2 : ∀ (r : K) (x y : K × K), r • (x + y) = r • x + r • y := sorry
lemma smul_zero_l2 : ∀ (r : K), r • (0 : K × K) = 0 := sorry
instance : distrib_mul_action K (K × K) := ⟨ smul_add_l2, smul_zero_l2⟩

/-
- scalar multiplication by a sum of scalars
- scalar multiplication by scalar zero

class semimodule (R : Type u) (M : Type v) [semiring R]
  [add_comm_monoid M] extends distrib_mul_action R M :=
(add_smul : ∀(r s : R) (x : M), (r + s) • x = r • x + s • x)
(zero_smul : ∀x : M, (0 : R) • x = 0)

Note that because K (R here) is assumed to be a field
(see the top of this file), it is already equipped with
a semiring and an additive monoid structure, so we don't
need to specify those ourselves. They're already "there."
-/
lemma add_smul_l2 : ∀ (r s : K) (x : K × K), (r + s) • x = r • x + s • x := sorry
lemma zero_smul_l2 : ∀ (x : K × K), (0 : K) • x = 0 := sorry
instance semimodule_K_Kpair : semimodule K (K × K) := ⟨ add_smul_l2, zero_smul_l2 ⟩

/-
Hi! JUMP down to here from above.
-/
/-
Instantiating the vector_space API for scalars of type
K and vectors of type K × K will provide us with nice
notations for writing typechecked algebraic expressions
in this space. For examples, see linear2K_test.lean. To
see what was required to instantiate this API/typeclass,
see the comments below the live Lean code. They unpack
the definition of the vector_space typeclass from the
lean algebra library, revealing the prerequisites for
completing the job here. As you drill recursively down
through lower-level abstractions, you'll encounter new
algebraic structure names (things like "semimodule").
Now you have a good sense of what each of these classes
looks like: maybe some new data or operations, maybe
some new structuring constraints/rules/invariants.
-/
instance : vector_space K (K × K) := semimodule_K_Kpair
/-
vector_space :
  Π (R : Type u_1)                -- ring of scalars
    (M : Type u_2)                -- set of vectoids
    [_inst_1 : field R]           -- implicit
    [_inst_2 : add_comm_group M], -- implicit
  Type (max u_1 u_2) :=
    semimodule R M      -- a vector space R M is a semimodule R M
-/

/-
Yeah! We've now specified that we can treat
ordered pairs over a field K a vector space,
with the following operations.

+   vector vector addition
•   scalar vector multiplication

Now proceed to linear2K_test.lean to show that
you understand how to use this algebraic structure.
-/


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

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 6 : "or" (∨`)

We learn about how to manipulate `P ∨ Q` in Lean.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following tactics

* `left` and `right`
* `cases` (new functionality)

-/

-- Throughout this sheet, `P`, `Q`, `R` and `S` will denote propositions.

variables (P Q R S : Prop)

example : P → P ∨ Q :=
begin
  intro q,left,assumption,
end

example : Q → P ∨ Q :=
begin
  intro q, right,assumption,
end

example : P ∨ Q → (P → R) → (Q → R) → R :=
begin
  intros q w e,cases q,
  {apply w, assumption},
  {apply e,assumption},
end

-- symmetry of `or`
example : P ∨ Q → Q ∨ P :=
begin
  intro q,cases q with w e,right,assumption,left,assumption,
end

-- associativity of `or`
example : (P ∨ Q) ∨ R ↔ P ∨ (Q ∨ R) :=
begin
  split,intro q,cases q with w e,{cases w,{left,assumption,},{right,left,assumption,}},
  {right,right,assumption,},
  intro q,cases q,left,left,assumption,cases q,left,right, assumption,right,assumption,
end

example : (P → R) → (Q → S) → P ∨ Q → R ∨ S :=
begin
  rintro q w e,cases e,{left,apply q, assumption,},
  {right,apply w,assumption},
end

example : (P → Q) → P ∨ R → Q ∨ R :=
begin
  intros q w,cases w,left,apply q, assumption,right,assumption,
end

example : (P ↔ R) → (Q ↔ S) → (P ∨ Q ↔ R ∨ S) :=
begin
  intros q w,split,intro e,cases e,{cases q,left,apply q_mp,assumption,},{cases w,right,apply w_mp,assumption,},
  intro e,cases w,cases e,cases q,left,apply q_mpr,assumption,right,apply w_mpr,assumption,
end

-- de Morgan's laws
example : ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
begin
  split,intro q,change (P∨Q)->false at q,split,intro w,apply q,left,assumption,intro w,apply q,right,assumption,
  intro q,change (P∨Q)->false,intro w,change (P->false)∧(Q->false) at q,cases q,cases w,apply q_left,assumption,apply q_right,assumption,
end

example : ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
begin
  split,intro q,{by_cases Q,left,change (P∧ Q)->false at q,change P->false,intro w,apply q,split,assumption,assumption,right,assumption},
  {intro q,change(P∧Q)->false, intro w,by_cases P,cases q,apply q,assumption,apply q,cases w,assumption,apply h,cases w,assumption,},
end


import BrownCs22.Library.Tactics
import BrownCs22.Library.Defs

/-

(Note: we'll actually cover this in lecture 7, so it's duplicated
in `Lecture07.lean`.)

# First order logic in Lean

We were a little vague in class about what function symbols, predicate symbols,
etc. we were going to allow in our formulas.
In Lean, we need to be more precise.

Symbols in Lean must have *types*:
a predicate holds on `ℕ`, or on `ℤ`, or on some arbitrary type `T`,
but not on all at once.

We can introduce a new predicate symbol that takes one natural number argument
as follows:

-/

variable (P : ℕ → Prop)

#check P 3

/-

Notice that we don't need to write parentheses around the argument.
We can, though:

-/

#check P (3)

/-

If we want a predicate symbol that takes two arguments:

-/

variable (R : ℕ → ℕ → Prop)

#check R 3 4

-- this doesn't work!
-- #check R (3, 4)

/-

Finally, Lean has lots of predicates and function symbols built in.
Want to use `+`, `*`, `=`, all that familiar stuff? It's here already.

-/

/-

## Universal introduction

We said that to prove `∀ x : T, ...` goals, we got to introduce a new
`x : T`. The tactic for this proof rule is familiar. The pattern here
sounds a lot like implication: move something from the goal to the context.

We use the tactic `intro` again!

The `reflexivity` tactic will prove things equal to themselves.
This is a very basic property of `=`.

-/

example : ∀ x : ℤ, x = x := by
  intro x
  reflexivity

 -- Every even integer greater than two is the sum of two primes.
example : ∀ n : ℕ, Even n ∧ (n > 2) → ∃p q : ℕ, Prime p ∧ Prime q ∧ (n = p + q) := by
  intro n
  intro h_and
  sorry

/-

## Universal elimination

If we know `hall : ∀ x : T, H x`, we can conclude `H t` for any `t : T`.
We again do this just like implication elimination:
`have ht : H t := hall t`.

We can also use `apply`.

Note: I've started this example with the hypothesis `hxp` "pre-intro'ed."
Putting it left of the `:` means it is in my context at the beginning.
This saves us a line.

-/

example (hxp : ∀ x : ℕ, P x) : P 5 ∧ P 6 := by
  split_goal

  -- method 1: using `have`
  { have hP5 : P 5 := hxp 5
    assumption }

  -- method 2: using apply
  { apply hxp }

/-

## Existential introduction

To prove an `exists` goal, we provide a witness.
The tactic for giving a witness is `existsi` (for "exists introduction," creatively).

The tactic `numbers` proves silly obvious facts about explicit numbers.
(It doesn't know anything about variables, like `x`.)

-/

example : ∃ x : ℕ, x > 10 := by
  existsi 1000
  numbers

/-

## Existential elimination

This will be familiar again!
To use an `exists` hypothesis, we can use the good old `eliminate` tactic
to get a witness and a hypothesis about that witness.

-/

example (hex : ∃ x : ℕ, P x) : 0 = 0 := by
  eliminate hex with k hPk
  numbers

example (hex : ∃ x : ℕ, P x) (hall : ∀ x : ℕ, P x → P (x + 1)) :
    ∃ x : ℕ, P x ∧ P (x + 1) := by
  eliminate hex with k hPk
  have hk : P k → P (k + 1) := hall k
  have hPk1 : P (k + 1) := hk hPk
  existsi k
  split_goal
  { assumption }
  { assumption }


import mini_crush

namespace hide
-- *Introducing Inductive Types

-- **Proof Terms
#check (fun x : ℕ, x)

#check (fun x : true, x)

#check true.intro

#check (λ _ : false, true.intro)

#check (λ x : false, x)

-- **Enumerations

-- unit
inductive unit : Type
| tt

#check unit
#check unit.tt

theorem unit_singleton_verbose (x : unit) : x = unit.tt :=
by induction x; refl

theorem unit_singleton_concise : ∀ x : unit, x = unit.tt
| unit.tt := rfl

-- TODO: is cases_on like unit_ind?
-- TODO: hovering over #check gives a different type than hovering over the expression itself (which yields the "correct" answer)
#check unit.cases_on

-- empty
inductive empty : Type

-- TODO: cases vs induction?
theorem the_sky_is_falling (x : empty) : 2 + 2 = 5 :=
by cases x

#check empty.cases_on

-- empty to unit
def e2u (e : empty) : unit := match e with end

-- booleans
inductive bool : Type
| true
| false

-- bool negation
def negb : bool → bool
| bool.true  := bool.false
| bool.false := bool.true

-- TODO: negb' ? is there a version of ite that works for any type w/ 2 constructors?

-- proof that negb is its own inverse operation
theorem negb_inverse : ∀ b : bool, negb (negb b) = b
| bool.true  := rfl
| bool.false := rfl

-- proof that negb has no fixpoint
theorem negb_ineq (b : bool) : negb b ≠ b :=
by cases b; contradiction

-- **Simple Recursive Types

-- nat and some functions
inductive nat : Type
| O : nat
| S : nat → nat

def is_zero : nat → bool
| nat.O     := bool.true
| (nat.S _) := bool.false

def pred : nat → nat
| nat.O     := nat.O
| (nat.S n) := n

def plus : nat → nat → nat
| nat.O m     := m
| (nat.S n) m := nat.S (plus n m)

theorem O_plus_n : ∀ n : nat, plus nat.O n = n :=
begin
simp [plus]
end

theorem O_plus_n_crush : ∀ n : nat, plus nat.O n = n :=
by mini_crush

theorem n_plus_O : ∀ n : nat, plus n nat.O = n :=
begin
intro n,
induction n,
{refl},
simp [plus],
rw ih_1
end

theorem n_plus_O_crush : ∀ n : nat, plus n nat.O = n :=
by mini_crush

theorem s_inj : ∀ n m : nat, nat.S n = nat.S m → n = m :=
begin
intros n m succ_eq,
injection succ_eq
end

theorem s_inj_crush : ∀ n m : nat, nat.S n = nat.S m → n = m :=
by mini_crush

inductive nat_list : Type
| NNil  : nat_list
| NCons : nat → nat_list → nat_list

-- length
def nlength : nat_list → nat
| nat_list.NNil         := nat.O
| (nat_list.NCons _ ls) := nat.S (nlength ls)

-- apppend
def napp : nat_list → nat_list → nat_list
| nat_list.NNil          ls2 := ls2
| (nat_list.NCons n ls1) ls2 := nat_list.NCons n (napp ls1 ls2)

-- the length of an appended list made from ls1 and ls2 is the sum of the lengths of ls1 and ls2
theorem nlength_napp : ∀ ls1 ls2 : nat_list, nlength (napp ls1 ls2) = plus (nlength ls1) (nlength ls2) :=
begin
intros,
induction ls1; simp [nlength, plus, napp],
rw ih_1
end

theorem nlength_napp_crush : ∀ ls1 ls2 : nat_list, nlength (napp ls1 ls2) = plus (nlength ls1) (nlength ls2) :=
by mini_crush

-- binary tree
inductive nat_btree : Type
| NLeaf : nat_btree
| NNode : nat_btree → nat → nat_btree → nat_btree

def nsize : nat_btree → nat
| nat_btree.NLeaf             := nat.S nat.O
| (nat_btree.NNode tr1 _ tr2) := plus (nsize tr1) (nsize tr2)

def nsplice : nat_btree → nat_btree → nat_btree
| nat_btree.NLeaf               tr2 := nat_btree.NNode tr2 nat.O nat_btree.NLeaf
| (nat_btree.NNode tr1' n tr2') tr2 := nat_btree.NNode (nsplice tr1' tr2) n tr2'

theorem plus_assoc : ∀ n1 n2 n3 : nat, plus (plus n1 n2) n3 = plus n1 (plus n2 n3) :=
begin
intros,
induction n1; simp [plus],
rw ih_1
end

theorem nsize_nsplice : ∀ tr1 tr2 : nat_btree, nsize (nsplice tr1 tr2) = plus (nsize tr2) (nsize tr1) :=
begin
intros,
induction tr1; simp [nsize, nsplice],
simp [ih_1, ih_2, plus_assoc]
end

#check nat_btree.cases_on

-- **Parameterized Types
-- TODO: is there a way to remove type annotations nearly everywhere ala CPDT p. 50?

universe variable u

inductive list (α : Type u) : Type u
| Nil {} : list
| Cons   : α → list → list

def length {α : Type u} : list α → nat
| list.Nil        := nat.O
| (list.Cons _ l) := nat.S (length l)

def app {α : Type u} : list α → list α → list α
| list.Nil          ls2 := ls2
| (list.Cons x ls1) ls2 := list.Cons x (app ls1 ls2)

-- nearly identical to nlength_napp
theorem length_app : Π (α : Type u), ∀ ls1 ls2 :
list α, length (app ls1 ls2) = plus (length ls1) (length ls2) :=
begin
intros,
induction ls1; simp [length, plus, app],
rw ih_1
end

theorem length_app_crush : Π (α : Type u), ∀ ls1 ls2 :
list α, length (app ls1 ls2) = plus (length ls1) (length ls2) :=
by mini_crush

-- **Mutually Inductive Types

-- even and odd lists
mutual inductive even_list, odd_list
with even_list : Type
| ENil  : even_list
| ECons : nat → odd_list → even_list
with odd_list : Type
| OCons : nat → even_list → odd_list

mutual def elength, olength
with elength : even_list → nat
| even_list.ENil         := nat.O
| (even_list.ECons _ ol) := nat.S (olength ol)
with olength : odd_list → nat
| (odd_list.OCons _ el) := nat.S (elength el)

mutual def eapp, oapp
with eapp : even_list → even_list → even_list
| even_list.ENil         el2 := el2
| (even_list.ECons n ol) el2 := even_list.ECons n (oapp ol el2)
with oapp : odd_list → even_list → odd_list
| (odd_list.OCons n el') el := odd_list.OCons n (eapp el' el)

-- TODO: come back to this one
theorem elength_eapp : ∀ el1 el2 : even_list,
elength (eapp el1 el2) = plus (elength el1) (elength el2) :=
begin
intros,
induction el1; simp [elength, eapp, plus],
admit
end

-- **Reflexive Types

-- a reflexive type A includes at least one constructor that takes an argument of type B → A

-- subset of predicate logic
inductive pformula : Type
| Truth : pformula
| Falsehood : pformula
| Conjunction : pformula → pformula → pformula

-- pformula denotational semantics
def pformulaDenote : pformula → Prop
| pformula.Truth := true
| pformula.Falsehood := false
| (pformula.Conjunction f1 f2) := pformulaDenote f1 ∧ pformulaDenote f2

inductive formula : Type
| Eq : nat → nat → formula
| And : formula → formula → formula
| Forall : (nat → formula) → formula
-- variables implicitly defined using lean functions
-- e.g. ∀ x : nat, x = x is encoded as
example forall_refl : formula := formula.Forall (fun x, formula.Eq x x)

-- formula denotational semantics
def formulaDenote : formula → Prop
| (formula.Eq n1 n2)  := n1 = n2
| (formula.And f1 f2) := formulaDenote f1 ∧ formulaDenote f2
| (formula.Forall f)  := ∀ n : nat, formulaDenote (f n)

-- a trivial formula transformation
def swapper : formula → formula
| (formula.Eq n1 n2)  := formula.Eq n2 n1
| (formula.And f1 f2) := formula.And (swapper f2) (swapper f1)
| (formula.Forall f)  := formula.Forall (fun n, swapper (f n))

theorem swapper_preserves_truth (f : formula) : formulaDenote f → formulaDenote (swapper f) :=
by mini_crush

#check formula.cases_on

/-
inductive term : Type
| App : term → term → term
| Abs : (term → term) → term
open term

def uhoh (t : term) : term
| (Abs f) := f t
| _ := t
-/

-- **An Interlude on Induction Principles

-- TODO: equiv to nat_ind? nat_rect? nat_rec?
#check @nat.rec
#check @nat.cases_on

-- skipping the rest of this section for now

-- **Nested Inductive Types

-- tree with arbitrary finite branching
inductive nat_tree : Type
| NNode' : nat → list nat_tree → nat_tree

#check nat_tree.rec

section All
variable T : Type
variable P : T → Prop

def All : list T → Prop
| list.Nil        := true
| (list.Cons h t) := P h ∧ All t

end All

#print true
#print ∧
#print and

section nat_tree_ind'
variable P : nat_tree → Prop

-- TODO

end nat_tree_ind'

-- TODO: rest of 3.8

-- **Manual Proofs About Constructors

-- todo
theorem true_neq_false : tt ≠ ff :=
begin
change ¬(tt = ff), -- TODO: replace this with something else?
change (tt = ff) → false,
intro H,
admit -- TODO: need a version of ite that takes any type w/ 2 constructors
end

theorem S_inj' : ∀ n m : nat, nat.S n = nat.S m → n = m :=
begin
intros n m H,
change (pred (nat.S n) = pred (nat.S m)),
rw H
end

end hide


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que los números reales tienen la siguente
-- propiedad
--    (a * b) * c = b * (a * c)
-- ---------------------------------------------------------------------

import data.real.basic

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

example (a b c : ℝ) : (a * b) * c = b * (a * c) :=
begin
  rw mul_comm a b,
  rw mul_assoc b a c,
end

-- Comentarios:
-- 1. Al colocar el cursor sobre el nombre de un lema se ve su enunciado.
-- 2. Para completar el nombre de un lema basta escribir parte de su
--    nombre y completar con S-SPC (es decir, simultáneamente las teclas
--    de mayúscula y la de espacio).
-- 3. los lemas usados son
--    + mul_com   : (∀ a b : G),   a * b = b * a
--    + mul_assoc : (∀ a b c : G), (a * b) * c = a * (b * c)
-- 4. La táctica (rw e) (ver https://bit.ly/3eUxbjD) reescribe una
--    expresión usando la ecuación e.

-- El desarrollo de la prueba es:
--
--    inicio
--       a b c : ℝ
--       ⊢ (a * b) * c = b * (a * c)
--    rw mul_comm a b,
--       a b c : ℝ
--       ⊢ (a * b) * c = b * (a * c)
--    rw mul_assoc b a c
--       no goals

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

example
  (a b c : ℝ)
  : (a * b) * c = b * (a * c) :=
calc
  (a * b) * c = (b * a) * c : by rw mul_comm a b
          ... = b * (a * c) : by rw mul_assoc b a c

-- Comentario: El entorno calc (ver https://bit.ly/2NRxiAU) permite
-- escribir demostraciones ecuacionales.

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

example (a b c : ℝ) : (a * b) * c = b * (a * c) :=
by ring

-- Comentario: La táctica ring demuestra ecuaciones aplicando las
-- propiedades de anillos.


import data.set

/- #1

Prove that if there's an object, a, of some type, α,
having some property (satisfying a predicate), P, then
not every object of type α fails to have property, P.
-/

example (α : Type) (P : α → Prop) : (∃ a, P a) → (¬(∀ x, ¬ P x)) :=
begin
assume h,
cases h with w pw,
assume k,
have npw := k w,
contradiction,
end

/- Extra credit.

The converse of this proposition is clasically true. If
not every object has propery, P, then there must be some
object that has it. If you try to prove the converse in
our constructive logic, what happens? Show you work, and
then briefly but clearly explain exactly what goes wrong.
-/

/- #2

Consider the following binary relation, r, with domain
and the co-domain both being ℕ.

( domain = ℕ, r = {(0,0),(1,1),(2,2)}, co-domain=ℕ )

A. Is this relation reflexive? Explain your answer.

No. Not *every* object of the domain is related to itself.

B. Is this relation symmetric? Explain your answer.

Yes, the reverse of every pair in the relation is also
in the relation.

C. Is this relation transitive? Explain your answer.

Yes. In all cases (there are none!) in which an a is
related to a b, and a b is related to a c that a is
related to that c. Again there are *no* such cases, so,
again, the it's true for all of them!

D. Is this relation an equivalence relation? Explain.

No. It's not reflexive.

-/

/- #3

A binary relation, r, is said to be *anti-symetric*
if, for all values in its domain, a and b, if r a b
and if r b a then a = b. Give an example of a familiar
arithmetic relation that's anti-symmetric, and briefly
explain why it's so.

Answer: less than or equals is an anti-symmetric
relation. If a <= b and b <= a then it must be that
case that a = b.
-/

/- #4
A binary relation, r, is said to be asymmetric if
whenever, for any a and b, if r a b then ¬ r b a.
-/

/- A.

First, express this idea formally by completing the
following definition.
-/

def is_asymmetric
  {α : Type}
  (r : α → α → Prop) :
  Prop
  :=
∀ (a b : α), r a b → ¬ r b a

/- B.

Name a familar arithmetic relation that's asymmetric
and briefly explain why you think it's asymmetric.

Answer here: The less than relation (on natural numbers)
is asymmetric because for any a, b, if a < b then ¬ b < a.

-/

/- C:

An object cannot be related to itself in an asymmetric
relation. First, complete the following informal proof
of this statement.

Proof: Assume α, r, and a are as given (and in particular
assume that r is asymmetric). Now assume r a a.

Answer here (rest of proof): From the fact that r is
asymmetric and given the assumption that r a a, we can
conclude ¬ r a a. That's is a contradiction. The r a a
cannot have been true. Since a was arbitrary, no a can
be related to itself.
-/

/- D.

Now prove a closely related proposition formally.
Add a comment to each line of your formal proof
so as to construct a good skeleton for a fluent
English language proof.
-/

example
  (α : Type)
  (r : α → α → Prop)
  (h : is_asymmetric r) :
¬ ∃ (a : α), r a a :=
begin
-- proof by negation
-- suppose there is such an a
assume h,
-- let's call it w with a proof of r w w
cases h with w pf,
-- apply generalization h to w and w to deduce h w w
let contra := h w w,
-- apply resulting implication to proof of r w w
let contra2 := contra pf,
-- that gives a proof of ¬ r w w
-- and that is a ...
contradiction,
-- So the assumption there is such an a is false
-- QED.
end

/- #5
Prove that equality on an inhabited (non-empty) type
is not assymetric.
-/

example (α : Type) (a : α): ¬ is_asymmetric (@eq α) :=
begin
-- assume equality is asymmetric
assume h,

-- expand definition
unfold is_asymmetric at h,

-- apply h to derive a proof of
-- of a = b → a ≠ b
let x := h a a,

-- apply it to a proof of a = a
let y := x (eq.refl a),

-- now we have a contradiction
contradiction,

-- so equality must not be asymmetric
-- if α is an inhabited (non-empty) type
-- which the argument, a, assures is so
end

/- #6
Two natural numbers, p and q, are said to be
"equivalent mod m" if p % m = q % m, which makes
"equivalence mod m" a binary relation on natural
numbers. Here's a formal definition of this binary
relation on the natural numbers (given an m).
-/

def equiv_mod_m (m : ℕ) : ℕ → ℕ → Prop :=
  -- given m, equiv_mod_m is a binary predicate
  λ p q : ℕ, p % m = q % m

/-
Prove using Lean's definitions "equivalence" that
equiv_mod_m is an equivalence relation for any natural
number, m. Here you'll use Lean's definitions of
equivalence, reflexive, symmetric, and transitive.
-/

example : ∀ m : ℕ, equivalence (equiv_mod_m m) :=
begin
-- assume arbitrary "modulus"
intro m,

-- expand definition
unfold equivalence,

-- split goal into three conjuncts
apply (and.intro _ (and.intro _ _)),

-- prove reflexivity
unfold reflexive,
assume x,
unfold equiv_mod_m,

-- prove symmetry
unfold symmetric,
assume x y h,
unfold equiv_mod_m at h,
unfold equiv_mod_m,
rw h,

-- prove transitivity
unfold transitive,
assume a b c,
assume h k,
unfold equiv_mod_m at h,
unfold equiv_mod_m at k,
unfold equiv_mod_m,
rw h,
rw k,
-- That suffices to prove the theorem!
end

/- #7
Consider the relation, tin_rel, that associates people
with U.S. taxpayer id numbers, which we'll represent as
natural numbers here.

Assume this relation is single-valued. Which of the
following properties does this relation have? Give
a very brief justification of each answer. Assume
the domain is all living persons, and the co-domain
is all natural numbers.

-- it's a function: Yes, person has (should have) more than one ssn/tin
-- it's total: No, not every person has an ssn/tin
-- it's injective: yes, no two people can have the same ssn/tin
-- it's surjective (where the co-domain is all ℕ): no, not every ℕ is an ssn
-- it's strictly partial: yes, there are people without ssns
-- it's bijective: no, it's not surjective
-/

/- #8
Suppose r is the empty relation on the natural
numbers. Which of the following properties does
it have? Explain each answer enough to show you
know why your answer is correct.

-- reflexive: no, because not every number is related to itself in r
-- symmetric: yes, because it's a generalization over an empty set
-- transitive: yes, because its a generalization over an empty set
-/

/- #9
Here's a formal definition of this empty relation.
That there are no constructors means there are no
proofs, which is to say that no pair can be proved
to be in this relation, so it must be empty.
-/

inductive empty_rel : ℕ → ℕ → Prop

/-
Formally state and prove you answer for each of the
three properties. That is, for each property assert
that either empty_rel does or does not have it, then
prove your assertion. Include English-language comments
on each line to give the essential elements of a good
English language proof.
-/

example : ¬reflexive empty_rel :=
begin
unfold reflexive,
assume h,
let x := h 0,
cases x,
end

example : symmetric empty_rel :=
begin
unfold symmetric,
assume a b h,
cases h,
end

example : transitive empty_rel :=
begin
assume a b c h k,
cases h,
end

/- #10
A relation, r, is said to have the property of being
a *partial order* if it's reflexive, anti-symmetric,
and transitive. Give a brief English language proof
that the subset relation on the subsets of any set,
S (of objects of some type), is a partial order.

Pf:
Suppose S is a set, with a ⊆ S and b ⊆ S subsets. Then

1. every such a is a subset of itself, so reflexive
2. if a ⊆ b and b ⊆ a then a = b, so anti-symmetric
3. if a ⊆ b and b ⊆ c, then a ⊆ c, so transitive
    given x ∈ a, a ⊆ b means x ∈ b, and similarly
    because x ∈ b then x ∈ c, so x ∈ a → x ∈ c,
    which is the definition of a ⊆ c. QED.
-/

/- #11
Finally we return again to DeMorgan's laws, but
now for sets. You'll recall that these "laws" as we
have seent them so far tell us how to distribute ¬
over ∨ and over ∧. You will also recall that set
intersection (∩) is defined by the conjunction (∧)
of the membership predicates, set union (∪) by the
disjunction (∨), and set complement (Sᶜ for a set,
S), by negation (¬). It makes sense to hypothesize
that we can recast DeMorgan's laws in terms of the
distribution of complement over set union and set
intersection. Indeed we can. Your job is to state
and prove (formally) the first DeMorgan laws for
sets, which states that the complement of a union
of any two sets equals the intersection of their
complements.

Hint: To prove that two sets are equal, S = T, use
the ext tactic. It applies an axiom that states it
suffices to prove S ↔ T, viewing the sets as being
defined by their logical membership predicates. You
know how to handle bi-implications. The rest you
can do by seeing "not," "and," and "or" in place
of complement, conjunction, and disjuction, resp.
-/

example
  (α : Type)
  (a b: set α) :
(a ∪ b)ᶜ = aᶜ ∩ bᶜ :=
begin
-- To prove two sets equal we can show their membership predicates are equivalent
ext,

-- Prove implications in both directions
split,

-- forwards

-- assume premise
assume h,

-- eliminate ∧/∩ in h
-- h says x ∈ a ∨ x ∈ b → false
-- show x ∈ aᶜ and x ∈ bᶜ

apply and.intro,
assume k,
let foo: (x ∈ a ∪ b) :=
  begin left, assumption end,
contradiction,

assume k,
let foo: (x ∈ a ∪ b) :=
  begin right, assumption end,
contradiction,

-- backwards
assume h,
assume k,
cases k,
cases h,
contradiction,
cases h,
contradiction,
end

/-
Post-HW exercise: Write a fluent English language proof
of the preceding proposition. Hint: Model it on the formal
proof.
-/


import tactic --hide

universe u --hide

-- Level name : apply tactic

/-

## The `apply` tactic

If your lemma has the following state:

```
h : P → Q
⊢ Q
```

Here, we assume that `P` implies `Q` and our goal is to prove `Q`. Then the tactic  <mark style ="background-color : #ebdef0 ">`apply h,`</mark>
will change our goal to `P` and leave our assumptions unchanged, i.e. your new state will be

```
h : P → Q
⊢ P
```

The `apply` tactic is useful for *arguing backwards*. It reduces the goal to a potentially easier
goal, without changing any hypotheses.

Here's an example we have already done, but try and do it using the `apply` tactic:

-/

/- Lemma
Let $P,Q$ be logical statements. If $P$ implies $Q$ and $P$ is true, then $Q$ is true.
-/

lemma implications_one_again (P Q : Prop) (h : P → Q) (p : P) : Q :=
begin
  apply h,
  exact p,

end

/- Tactic : apply

## The `apply` tactic

If your lemma looks like

```
h : P → Q
⊢ Q
```

then the tactic `apply h,` will change it to

```
h : P → Q
⊢ P
```

The `apply` tactic is useful for *arguing backwards*. It reduces the goal to a potentially easier
goal, without changing any hypotheses.

-/


section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro hp,
  intro np,
  apply np hp,
end

theorem doubleneg_elim : -- "manchado" pela RAA (Redução ao absurdo)
  ¬¬P → P  :=
begin
  intro nnp,
  by_contradiction pboom,
  apply nnp pboom,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
    -- parte ¬¬P → P:
    apply doubleneg_elim P,
    -- parte P → ¬¬P:
    apply doubleneg_intro P,
end

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

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro p_ou_q,
  cases p_ou_q with hp hq,
    -- caso P:
    right,
    exact hp,
    -- caso Q:
    left,
    exact hq,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro p_e_q,
  cases p_e_q with hp hq,
  split,
    -- parte q:
    exact hq,
    -- parte P:
    exact hp,
end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro np_ou_q,
  intro hp,
  cases np_ou_q with np hq,

    -- caso ¬P:
    have boom : false := np hp,
    exfalso,
    exact boom,

    -- caso Q:
    exact hq,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro p_ou_q,
  intro np,
  cases p_ou_q with hp hq,
    -- caso P:
    contradiction,

    -- caso Q:
    exact hq,
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro p_implies_q,
  intro nq,
  intro hp,
  have hq : Q := p_implies_q hp,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro nq_implies_np,
  intro hp,
  by_contradiction nq,
  have np : ¬P := nq_implies_np nq,
  contradiction,
end

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

    -- parte (¬Q → ¬P) → P → Q:
    exact impl_as_contrapositive_converse P Q,
end

------------------------------------------------
-- A irrefutabilidade do LEM:
------------------------------------------------

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro nn_p_ou_np,
  apply nn_p_ou_np,
  right,
  intro hp,
  apply nn_p_ou_np,
  left,
  exact hp,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro hi,
  intro np,
  apply np,
  apply hi,
  intro hp,
  contradiction,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro p_ou_q,
  intro n_np_e_nq,
  cases n_np_e_nq with np nq,
  cases p_ou_q with hp hq,
    -- caso P:
    contradiction,

    -- caso Q:
    contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro p_e_q,
  cases p_e_q with hp hq,
  intro np_ou_nq,
  cases np_ou_nq with np nq,
    -- caso ¬P:
    contradiction,

    -- caso ¬Q:
    contradiction,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_p_ou_q,
  split,
    -- parte ¬P:
    intro hp,
    have p_ou_q : (P∨Q),
      left,
      exact hp,
    have boom : false := n_p_ou_q p_ou_q,
    exact boom,

    -- parte ¬Q:
    intro hq,
    have p_ou_q: (P∨Q),
      right,
      exact hq,
    have boom : false := n_p_ou_q p_ou_q,
    exact boom,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro np_e_nq,
  cases np_e_nq with np nq,
  intro p_ou_q,
  cases p_ou_q with hp hq,
    -- caso P:
      have boom : false := np hp,
      exact boom,

    -- caso Q:
      have boom : false := nq hq,
      exact boom,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_p_e_q,
  by_cases hq : Q,
    -- caso Q:
    right,
    intro hp,
    have p_e_q : (P∧Q),
      split,
      -- parte P:
      exact hp,

      -- parte Q:
      exact hq,
    have boom : false := n_p_e_q p_e_q,
    exact boom,

    -- caso ¬Q:
    left,
    exact hq,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro nq_ou_np,
  intro p_e_q,
  cases p_e_q with hp hq, -- uso do ∧
  cases nq_ou_np with nq np, -- uso do ∨
    -- caso ¬Q:
    contradiction,

    -- caso ¬P:
    contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
    -- parte ¬(P ∧ Q) → ¬Q ∨ ¬P:
    exact demorgan_conj P Q,

    -- parte ¬Q ∨ ¬P → ¬(P ∧ Q):
    exact demorgan_conj_converse P Q,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
    -- parte ¬(P ∨ Q) → ¬P ∧ ¬Q:
    exact demorgan_disj P Q,

    -- parte ¬P ∧ ¬Q → ¬(P ∨ Q):
    exact demorgan_disj_converse P Q,
end

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro p_e_qour,
  cases p_e_qour with hp q_ou_r, -- Uso da conjunção (Extrair)
  cases q_ou_r with hq hr,
    -- caso Q:
    left,
    split, -- P∧Q:
      -- parte P:
      exact hp,
      -- parte Q:
      exact hq,

    -- caso R:
    right,
    split, -- P∧R:
      -- parte P:
      exact hp,
      -- parte R:
      exact hr,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro pEq_ou_pEr,
  cases pEq_ou_pEr with p_e_q p_e_r,
    -- caso P∧Q:
    cases p_e_q with hp hq,
    split,
      -- parte P:
      exact hp,
      -- parte Q∨R:
      left,
      exact hq,

    -- caso P∧R:
    cases p_e_r with hp hr,
    split,
      -- parte P:
      exact hp,
      -- parte Q∨R:
      right,
      exact hr,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro p_ou_qEr,
  cases p_ou_qEr with hp q_e_r,
    -- caso P:
    split,
      -- parte P∨Q:
      left,
      exact hp,
      -- parte P∨R:
      left,
      exact hp,

    -- caso Q∧R:
    cases q_e_r with hq hr,
    split,
      -- parte P∨Q:
      right,
      exact hq,
      -- parte P∨R:
      right,
      exact hr,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro pOUq_e_pOUr,
  cases pOUq_e_pOUr with p_ou_q p_ou_r,
  cases p_ou_q with hp hq,
    -- caso P:
    cases p_ou_r with hp hr,
      -- caso P:
      left,
      exact hp,
      -- caso R:
      left,
      exact hp,
    -- caso Q:
    cases p_ou_r with hp hr,
      -- caso P:
      left,
      exact hp,
      -- caso R:
      right,
      split,
        -- parte Q:
        exact hq,
        -- parte R:
        exact hr,
end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro pEq_implies_r,
  intro hp,
  intro hq,
  have p_e_q : (P∧Q),
    split,
      -- parte P:
      exact hp,
      -- parte Q:
      exact hq,
  apply pEq_implies_r p_e_q,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro h1,
  intro p_e_q,
  cases p_e_q with hp hq,
  apply h1 hp hq,
end

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

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

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

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

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

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro p_e_q,
  cases p_e_q with hp hq,
  exact hp,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro p_e_q,
  cases p_e_q with hp hq,
  exact hq,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
    -- parte P ∧ P → P:
    intro p_e_p,
    cases p_e_p with hp hp,
    exact hp,

    -- parte P → P ∧ P:
    intro hp,
    split,
      -- parte P:
      exact hp,
      -- parte P:
      exact hp,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
    -- parte P ∨ P → P:
    intro p_ou_p,
    cases p_ou_p with hp hp,
      -- caso P:
      exact hp,
      -- caso P:
      exact hp,

    -- parte P → P ∨ P:
    intro hp,
    left,
    exact hp,
end

end propositional

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

section predicate

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

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

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

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro h1,
  intro h2,
  cases h2 with x px,
  apply h1 x px,
end

theorem demorgan_forall : -- Manchado (RAA)
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro h1,
  by_contradiction h2,
  apply h1,
  intro x,
  by_contradiction npx,
  apply h2,
  existsi x,
  exact npx,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro h1,
  intro h2,
  cases h1 with x npx,
  have px : P x := h2 x,
  apply npx px,
end

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

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
    -- parte (¬∃ (x : U), P x) → ∀ (x : U), ¬P x:
    apply demorgan_exists U P,
    -- parte (∀ (x : U), ¬P x) → (¬∃ (x : U), P x):
    apply demorgan_exists_converse U P,
end

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro h1,
  intro h2,
  cases h1 with x px,
  apply h2 x px,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro h1,
  intro h2,
  cases h2 with x npx,
  have px : P x := h1 x,
  have boom: false := npx px,
  exact boom,
end

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

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro h1,
  by_contradiction h2,
  apply h1,
  intro x,
  intro px,
  apply h2,
  existsi x,
  exact px,
end

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

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

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

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro h1,
  cases h1 with x pqx,
  cases pqx with px qx,
  split,
    -- parte (∃x, P x):
    existsi x,
    exact px,
    -- parte (∃x, Q x):
    existsi x,
    exact qx,
end

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

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro h1,
  cases h1 with epx eqx,
    -- caso ∃x, P x:
    cases epx with x px,
    existsi x,
    left,
    exact px,
    -- caso ∃x, Q x:
    cases eqx with x qx,
    existsi x,
    right,
    exact qx,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro h1,
  split,
    -- parte ∀x, P x:
    intro x,
    have pxEqx : P x ∧ Q x := h1 x,
    cases pxEqx with px qx,
    exact px,
    -- parte ∀x, Q x:
    intro x,
    have pxEqx : P x ∧ Q x := h1 x,
    cases pxEqx with px qx,
    exact qx,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro h1,
  cases h1 with apx aqx,
  intro x,
  split,
    -- parte ∀x, P x:
    have px : P x := apx x,
    exact px,
    -- parte ∀x, Q x:
    have qx : Q x := aqx x,
    exact qx,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro h1,
  cases h1 with apx aqx,
    -- caso ∀x, P x:
    intro x,
    left,
    have px : P x := apx x,
    exact px,
    -- caso ∀x, Q x:
    intro x,
    right,
    have qx : Q x := aqx x,
    exact qx,
end

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

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

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

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

end predicate


/-
Copyright (c) 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 -- imports the real numbers

/-!

# The real numbers in Lean

Lean has a copy of of the real numbers. It's called `real`,
but we use the usual notation `ℝ`. Put your cursor on the `ℝ` to find
out how to type it in VS Code.

In this sheet you will prove some basic equalities and inequalities
between "numerical expressions" in Lean. A numeral is something like `37`,
and a numerical expression is something like `(37 + 6) / 4`. To make
things a bit harder, I will throw in some `∃` statements. To make
progress on an `∃` goal, use the `use` tactic.

## Tactics

New tactics you'll need to know about:

* `norm_num` (proves equalities and inequalities involving numerical expressions)
* `use` (if the goal is `∃ x, x + 37 = 42` then `use 8` will change the goal
*        to `8 + 37 = 42`, and `use 10` will change it to `10 + 37 = 42`.

-/

example : (2 : ℝ) + 2 = 4 :=
begin
  sorry
end

example : (2 : ℝ) + 2 ≠ 5 :=
begin
  sorry
end

example : (2 : ℝ) + 2 < 5 :=
begin
  sorry
end

example : ∃ (x : ℝ), 3 * x + 7 = 12 :=
begin
  sorry
end

example : ∃ (x : ℝ), 3 * x + 7 ≠ 12 :=
begin
  sorry
end

example : ∃ (x y : ℝ), 2 * x + 3 * y = 7 ∧ x + 2 * y = 4 :=
begin
  sorry,
end


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes
Mostly based on Jeremy Avigad's choose file in lean 2
-/
import data.nat.basic data.nat.prime
import algebra.big_operators

open nat

def choose : ℕ → ℕ → ℕ
| _             0 := 1
| 0       (k + 1) := 0
| (n + 1) (k + 1) := choose n k + choose n (succ k)

@[simp] lemma choose_zero_right (n : ℕ) : choose n 0 = 1 := by cases n; refl

@[simp] lemma choose_zero_succ (k : ℕ) : choose 0 (succ k) = 0 := rfl

lemma choose_succ_succ (n k : ℕ) : choose (succ n) (succ k) = choose n k + choose n (succ k) := rfl

lemma choose_eq_zero_of_lt : ∀ {n k}, n < k → choose n k = 0
| _             0 hk := absurd hk dec_trivial
| 0       (k + 1) hk := choose_zero_succ _
| (n + 1) (k + 1) hk :=
  have hnk : n < k, from lt_of_succ_lt_succ hk,
  have hnk1 : n < k + 1, from lt_of_succ_lt hk,
  by rw [choose_succ_succ, choose_eq_zero_of_lt hnk, choose_eq_zero_of_lt hnk1]

@[simp] lemma choose_self (n : ℕ) : choose n n = 1 :=
by induction n; simp [*, choose, choose_eq_zero_of_lt (lt_succ_self _)]

@[simp] lemma choose_succ_self (n : ℕ) : choose n (succ n) = 0 :=
choose_eq_zero_of_lt (lt_succ_self _)

@[simp] lemma choose_one_right (n : ℕ) : choose n 1 = n :=
by induction n; simp [*, choose]

lemma choose_pos : ∀ {n k}, k ≤ n → 0 < choose n k
| 0             _ hk := by rw [eq_zero_of_le_zero hk]; exact dec_trivial
| (n + 1)       0 hk := by simp; exact dec_trivial
| (n + 1) (k + 1) hk := by rw choose_succ_succ;
    exact add_pos_of_pos_of_nonneg (choose_pos (le_of_succ_le_succ hk)) (nat.zero_le _)

lemma succ_mul_choose_eq : ∀ n k, succ n * choose n k = choose (succ n) (succ k) * succ k
| 0             0 := dec_trivial
| 0       (k + 1) := by simp [choose]
| (n + 1)       0 := by simp
| (n + 1) (k + 1) :=
  by rw [choose_succ_succ (succ n) (succ k), add_mul, ←succ_mul_choose_eq, mul_succ,
  ←succ_mul_choose_eq, add_right_comm, ←mul_add, ←choose_succ_succ, ←succ_mul]

lemma choose_mul_fact_mul_fact : ∀ {n k}, k ≤ n → choose n k * fact k * fact (n - k) = fact n
| 0              _ hk := by simp [eq_zero_of_le_zero hk]
| (n + 1)        0 hk := by simp
| (n + 1) (succ k) hk :=
begin
  cases lt_or_eq_of_le hk with hk₁ hk₁,
  { have h : choose n k * fact (succ k) * fact (n - k) = succ k * fact n :=
      by rw ← choose_mul_fact_mul_fact (le_of_succ_le_succ hk);
      simp [fact_succ, mul_comm, mul_left_comm],
    have h₁ : fact (n - k) = (n - k) * fact (n - succ k) :=
      by rw [← succ_sub_succ, succ_sub (le_of_lt_succ hk₁), fact_succ],
    have h₂ : choose n (succ k) * fact (succ k) * ((n - k) * fact (n - succ k)) = (n - k) * fact n :=
      by rw ← choose_mul_fact_mul_fact (le_of_lt_succ hk₁);
      simp [fact_succ, mul_comm, mul_left_comm, mul_assoc],
    have h₃ : k * fact n ≤ n * fact n := mul_le_mul_right _ (le_of_succ_le_succ hk),
  rw [choose_succ_succ, add_mul, add_mul, succ_sub_succ, h, h₁, h₂, ← add_one, add_mul, nat.mul_sub_right_distrib,
      fact_succ, ← nat.add_sub_assoc h₃, add_assoc, ← add_mul, nat.add_sub_cancel_left, add_comm] },
  { simp [hk₁, mul_comm, choose, nat.sub_self] }
end

theorem choose_eq_fact_div_fact {n k : ℕ} (hk : k ≤ n) : choose n k = fact n / (fact k * fact (n - k)) :=
begin
  have : fact n = choose n k * (fact k * fact (n - k)) :=
    by rw ← mul_assoc; exact (choose_mul_fact_mul_fact hk).symm,
  exact (nat.div_eq_of_eq_mul_left (mul_pos (fact_pos _) (fact_pos _)) this).symm
end

theorem fact_mul_fact_dvd_fact {n k : ℕ} (hk : k ≤ n) : fact k * fact (n - k) ∣ fact n :=
by rw [←choose_mul_fact_mul_fact hk, mul_assoc]; exact dvd_mul_left _ _

lemma nat.prime.dvd_choose {p k : ℕ} (hk : 0 < k) (hkp : k < p) (hp : prime p) : p ∣ choose p k :=
have h₁ : p ∣ fact p, from hp.dvd_fact.2 (le_refl _),
have h₂ : ¬p ∣ fact k, from mt hp.dvd_fact.1 (not_le_of_gt hkp),
have h₃ : ¬p ∣ fact (p - k), from mt hp.dvd_fact.1 (not_le_of_gt (nat.sub_lt_self hp.pos hk)),
by rw [← choose_mul_fact_mul_fact (le_of_lt hkp), mul_assoc, hp.dvd_mul, hp.dvd_mul] at h₁;
  exact h₁.resolve_right (not_or_distrib.2 ⟨h₂, h₃⟩)

section binomial
open finset

variables {α : Type*} [comm_semiring α] (x y : α)

/-- The binomial theorem -/
theorem add_pow :
  ∀ n : ℕ, (x + y) ^ n = (range (succ n)).sum (λ m, x ^ m * y ^ (n - m) * choose n m)
| 0        := by simp [range_succ]
| (succ n) :=
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₃ : (range n).sum (λ m, x * (x ^ m * y ^ (n - m) * choose n m) + y *
    (x ^ succ m * y ^ (n - succ m) * choose n (succ m))) =
    (range n).sum (λ m, x ^ succ m * y ^ (succ n - succ m) * ↑(choose (succ n) (succ m))),
  from finset.sum_congr rfl $ λ m hm,
    begin
      simp only [mul_assoc, mul_left_comm y, mul_left_comm (y ^ (n - succ m)), mul_comm y],
      rw [← _root_.pow_succ', add_one, ← succ_sub (mem_range.1 hm)],
      simp [choose_succ_succ, mul_comm, mul_assoc, mul_left_comm, add_mul, mul_add, _root_.pow_succ]
    end,
by rw [_root_.pow_succ, add_pow, add_mul, finset.mul_sum, finset.mul_sum, sum_range_succ, sum_range_succ',
    sum_range_succ, sum_range_succ', add_assoc, ← add_assoc ((range n).sum _),
    ← finset.sum_add_distrib, h₁, h₂, h₃]

end binomial


/-
A predicate is a "proposition with
parameters." For example, a predicate,
fromCharlottesville(p) has a parameter,
p, and asserts that p (for some value
of p) is from Charlottesville.

In Lean, we represent a predicate as
a function that takes one or more more
arguments and reduces to a proposition,
and generally to one that is "about" the
arguments that were given.

So, speaking informally, for example,
fromCharlottesville(Kevin) could be read
as the proposition "Kevin is from Cville".
fromCharlottesville(Mary) is a different
proposition, "Mary is from Cville." A
predicate thus gives rise to a whole
family of propositions, one for each
possible combination of argument
values.

Not every proposition derived by applying
a predicate to one or more arguments will
be true. Rather, the predicate in effect
"picks out" the argument values for which
the corresponding proposition is true, and
thereby identifies them as having a
property of interest, such as the property
of "being from Cville."
-/

/-
Consider a predicate called even(n): that,
for any given natural number value of n,
asserts that n is even. Given a specific
value for n, say 3, this predicate would
reduce to the proposition (even 3), a
proposition that asserts that 3 is even.
Of course 3 isn't even, so (even 3) is
false; but (even 4), on the other hand,
is true, as long as even(n) is properly
defined to express the property of being
even. The key idea is this: the numbers
that have the property are exactly the
numbers for which the corresponding
propositions are true. The predicate
"picks out" the set of numbers with the
given property.
-/

/-
Here's one way to define an evenness
predicate. It uses the existential
quantifier, ∃, which we study in detail
in the next unit. You can read this as
saying  n is even if there exists an m
such that 2 * m = n.
-/
def isEven (n :ℕ) : Prop :=
  ∃ m : nat, 2 * m = n

/-
Note: isEven isn't a proposition, it's
a property!
-/

#check isEven

/-
On the other hand, (isEven 6) is a
proposition, namely the proposition
that there is some value, m, such
that 2 * m = n.
-/

#reduce isEven 6

/-
We can even prove it.
-/

example : isEven 6 :=
begin
unfold isEven,
apply exists.intro 3,
apply rfl,
end

/-
We define a predicate as a function
that takes one or more arguments and
that returns a proposition that, in
general, involves those values.

Consider the following simple example:
a predicate that expresses the property
of a natural number of "being equal to
zero." We write this predicate as a
function that takes a natural number,
n, and returns the proposition that
that number is equal to zero.

Here's such a predicate/function.
-/

def isZero (n : ℕ): Prop := (0 = n)

def nil (n : ℕ) : Prop :=
  false

def allN (n : ℕ) := true

example : allN 8 := true.intro

example : ¬ (nil 8) :=
begin
assume p,
exact false.elim p,
end

/-
First, let's check the type of the
isZero predicate.
-/

#check isZero

/-
The type of isZero is ℕ → Prop. It is
absolutely clear that a predicate is
not a proposition, but is instead a
function that takes one or more values
as arguments (here just one) that that
reduces to a proposition. In general,
such a proposition is "about" those
values. It asserts that they have some
particular property.

Such a proposition need not be true,
and will not be true if the arguments
do not have the property in question.

For example, (isZero 3) is false. On
the other hand, (isZero 0) is true.
-/

example : isZero 0 := rfl

example : ¬(isZero 3) :=
    λ zeq3, nat.no_confusion zeq3

example : ¬(isZero 1) :=
begin
  assume zeq1,
  exact nat.no_confusion zeq1
end

/-
Think carefully about what kind of value
this predicate/function returns when
applied to a particular argument value,
e.g., 3. The result is a proposition,
in particular, a proposition about 3,
namely one that asserts that 0 = 3.

To be clear, isZero is a predicate:
a function from a ℕ-valued argument
to Prop. By contrast, (isZero 3) is
a proposition. Look at the types of
the following expressions.
-/

#check isZero 0 -- a proposition
#check isZero 1 -- a proposition
#check isZero 2 -- a proposition

/-
Now look at the specific propositions
that this predicate/function returns
when applied to particular arguments.
Study this until it's clear to you that
applying a predicate to an argument
returns a proposition about that value.
-/

#reduce isZero 0 -- the proposition 0 = 0
#reduce isZero 1 -- the proposition 0 = 1
#reduce isZero 2 -- the proposition 0 = 2

/-
We can rewrite this function into
an equivalent form in the usual way,
to make the type of the predicate
clear as a function type. The type
of isZero is ℕ → Prop.
-/

def isZero' : ℕ → Prop :=
    λ n, 0 = n

/-
Again, you can think of a predicate
as specifying a *property* of its
argument: in this case the property
of "being equal to zero". Argument
values for which the corresponding
propositions are true have the given
property, and those for which the
corresponding proposition is false,
do not have the property.
-/

-- 0 has the isZero property
theorem zeqz : isZero 0 :=
    eq.refl 0

-- 1 does not have this property
theorem oneeqz : ¬(isZero 1) :=
begin
  unfold isZero,
  assume eq,
  exact nat.no_confusion eq,
end

/-
A predicate basically asserts that
the value given as its argument has
some property. Not every value has a
given property (e.g., 3 does not have
the property of being even). In general
a given predicate is not true for
every value of its argument(s).

EXERCISE: What are other properties
of natural numbers that could be
expressed as predicates?
-/

def isMultOf8: ℕ → Prop :=
  λ n : nat,
    ∃ m : nat, 8 * m = n

def nIsMultOfm: ℕ → ℕ → Prop :=
  λ n m,
    ∃ k, n = k * m

example : isMultOf8 64 :=
begin
unfold isMultOf8,
apply exists.intro 8,
apply rfl,
end

example : nIsMultOfm 64 8 :=
begin
unfold nIsMultOfm,
apply exists.intro 8,
apply rfl,
end

/-

EXERCISE: Define a predicate that is
true for every natural number.
-/

/-
EXERCISE: Define a predicate that is
false for every natural number.
-/

def is_absorbed_by_zero(n: ℕ): Prop := n * 0 = 0

def equals_self_plus_one(n: ℕ): Prop := n = n + 1

/-
EXAMPLE: Thank goodness it's the
weekend.

Below we define a new datatype,
day, with the names of the seven
days of the week as values. Given
this datatype, specify the property
of being a weekend day.
-/

inductive day : Type
| Monday : day
| Tuesday : day
| Wednesday
| Thursday
| Friday
| Saturday
| Sunday

#check day.Tuesday

open day -- no longer need to day. prefix

#check Tuesday

-- Answer

def isWeekend : day → Prop :=
    λ d, d = Saturday ∨ d = Sunday

-- EXERCISE: Show Saturday's a weekend day
theorem satIsWeekend: isWeekend Saturday :=
begin
  unfold isWeekend,   -- unfold tactic
  apply or.intro_left,-- backwards reasoning
  apply rfl  ,         -- finally, equality
end

/-
EXERCISE: Express the property,
of natural numbers, of being a
perfect square. For example, 9
is a perfect square, because 3
is a natural number such that
3 * 3 = 9. By contrast, 12 is
not a perfect square, as there
does not exist a natural number
that squares to 12.

State and prove the proposition
that 9 is a perfect square.
-/

-- Answer

def isASquare: ℕ → Prop :=
    λ n, exists m, n = m ^ 2

theorem isPS9 : isASquare 9 :=
begin
  unfold isASquare,
  exact exists.intro 3 (eq.refl 9)
end

/-
As a final important concept that we just
introduce in this section, we note that we
can think of a predicate as specifying a
set of values: the set of values for which
the predicate (corresponding proposition)
is true.

For example, the predicate, isZero(n),
specifies the set containing only the
natural number, zero. A predicate,
isEven(n), could specify the set of
even natural numbers. Etc. We explore
this idea in more depth in the unit on
sets to come shortly.

EXERCISE: Think of a few more
properties that you could specify
as predicates. What are the domains
of values on which your properties
are defined? What sets do your
predicates define?
-/


/-
© 2021 by the Rector and Visitors of the University of Virginia
-/

/-
In this file we construct a typeclass instance
carrying a proof of the lemma that if K is a
non-empty field then K × K has the structure
of a module. The rationale for this construction
is that (currently) we're representing 1-d affine
space points and vectors as 2-d linear vectors,
in the usual manner.
-/

import algebra.module.basic
import linear_algebra.affine_space.affine_equiv

-- Let K be the carrier set of an arbitrary non-empty field
universes u
variables
  (K : Type u)
  [field K]
  [inhabited K]

--
lemma add_smul_l2 :
  ∀ (r s : K) (x : K × K), (r + s) • x = r • x + s • x :=
begin
  intros,
  ext,
  simp *,
  exact right_distrib r s _,
  simp *,
  exact right_distrib r s _,
end

lemma zero_smul_l2 : ∀ (x : K × K), (0 : K) • x = 0 :=
begin
  intros,
  ext,
  simp *,
  simp *,
end

-- lemma proved by construction of typeclass instance
instance module_K_KxK : module K (K × K) :=
⟨ add_smul_l2 K, zero_smul_l2 K ⟩


import algebra.group

-- BEGIN

variables {G : Type*} [group G]
variables a b c : G

#check one_mul
#check mul_assoc
#check inv_mul_self
#check mul_left_inv
#check mul_right_cancel
#check eq.symm
#check (one_mul b).symm
#check (mul_left_inv a).symm

namespace my_group

theorem mul_right_inv (a : G) : a * a⁻¹ = 1 :=
begin
  have h : a * a⁻¹ * a = 1 * a,
    rw mul_assoc,
    rw mul_left_inv a,
    rw mul_one,
    rw one_mul,
  apply mul_right_cancel h,
end

theorem mul_one (a : G) : a * 1 = a :=
begin
  rw ← mul_left_inv a,
  rw ← mul_assoc a,
  rw mul_right_inv a,
  rw one_mul,
end

theorem mul_inv_rev (a b : G) : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  have h : (a * b)⁻¹ * a * b = b⁻¹ * a⁻¹ * a * b,
    rw mul_assoc b⁻¹ a⁻¹ a,
    rw mul_left_inv a,
    rw mul_one b⁻¹,
    rw mul_left_inv b,
    rw mul_assoc (a * b)⁻¹ a b,
    rw mul_left_inv (a * b),
  rw mul_assoc at h,
  rw mul_assoc at h,
  apply mul_right_cancel h,
end

/- Alternatively, starting with proving mul_cancel_left
and mul_inv_left -/

theorem mul_cancel_left (a b c : G) (h : a * b = a * c) : b = c :=
begin
  rw ← one_mul b,
  rw ← one_mul c,
  rw ← mul_left_inv a,
  repeat {rw mul_assoc},
  rw h,
end

theorem mul_inv_left (a b c : G) (h : b = a⁻¹ * c) : a * b = c :=
begin
  apply mul_cancel_left a⁻¹,
  rw ← mul_assoc,
  rw mul_left_inv,
  rw one_mul,
  rw h,
end

example (a : G) : a * 1 = a :=
begin
  apply mul_inv_left,
  exact (mul_left_inv a).symm, /- it is eqivelant to 'rw mul_left_inv' -/
end

example (a : G) : a * a⁻¹ = 1 :=
begin
  apply mul_inv_left,
  rw mul_one a⁻¹, /- 'exact (mul_one a⁻¹).symm' works too -/
end

example (a b : G) : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  apply mul_cancel_left (a * b),
  rw mul_right_inv,
  rw mul_assoc,
  rw ← mul_assoc b b⁻¹,
  rw mul_right_inv,
  rw one_mul,
  rw mul_right_inv,
end

end my_group

#check mul_right_inv
#check mul_one
#check mul_inv_rev

-- END


import Mathlib.Tactic.NormNum
import Mathlib.Tactic.Linarith
import Mathlib.Data.Nat.Prime
variable (P Q  : Prop)
variable (α β : Type)
variable (R S: ℕ → Prop)
open Function Nat

/-
# Quantifiers

# Existential ∃ tactics: use / cases

If our goal is `⊢ ∃(x:A), P` (where P may dep on x) and we know that `a : A` is a term
with the required property, then we can tell Lean to `use a` and our goal will change to `⊢ P`.
In fact if we can prove `P` using `refl` then Lean will do this automatically.

If we have `h : ∃(x:A), P` in the local context then  `cases h with x hx`
will give us `x : A` and `hx : P` in our local context.

[Notice how similar this is to how we used `cases h with hp hq` with `h : P ∧ Q`]

# Universal ∀ tactics: intro, apply, specialize

If we have a goal `⊢ ∀(n:ℕ), P n` then we can use `intro n` and the our goal will
change to `⊢ P n`. (For Lean a `∀` statement is basically a function.)

If we have `(h : ∀(x:A), P x)` and we want to use this hypotesis for a particular choice
of `x` then we can use `specialize h x` or (if goal is `⊢ P x`) just `apply h`

# New tactics for numerical/arithmetic proofs: norm_num and linarith

`norm_num` can prove results involving actual numerical expressions such as `4*3^2 < 50`

`linarith` can prove results involving linear arithmetic including inequalities.

# New tactics to help us make sense of definitions: unfold and dsimp

`unfold blah` will unfold a definition of `blah`in the goal.
Use unfold when you're not sure what a definition means.

`dsimp` will simplify the goal using definitions.
Use dsimp if the goal looks unreadable.  -/

-- 01
example : ∃(n:ℕ), n = 20 :=by
  use 20

-- 02
example : ∃(n:ℕ), 200 < n:=by
  use 201
  norm_num

-- 03
example : ∃(P:Prop), P:=by
use True
trivial

-- 04
example : ∃ (P:Prop), ¬ P → P:=by
use True
intro _
trivial

-- 05
example (h: ∃(n:ℕ), n.Prime ∧ 10 < n) : ∃(k:ℕ), k.Prime ∧ 7 < k :=by
obtain ⟨n,hn1,hn2⟩:= h
use n
apply And.intro hn1
linarith

-- 06 Hint : `Even n` is defined to be `∃(r:ℕ), n = r + r`
example (n : ℕ) : Even (4 * n):=by
  rw  [Even]
  use (2*n)
  rw [← two_mul,←mul_assoc ]
  rfl

-- 07
example (h: ∃(n:ℕ), Even n ∧ Even (n+1)) : ∃(k:ℕ), Even (2*k + 1) :=by
rcases h with ⟨n,hn1,hn2⟩
use n
rw [Even] at *
rcases hn1 with ⟨r,hr⟩
rcases hn2 with ⟨s,hs⟩
use (r+s)
rw [two_mul, ← add_assoc, add_comm, add_assoc,add_assoc, add_comm s,←add_assoc,←add_assoc,←add_assoc]
rw[← hr,add_assoc,add_assoc,← hs,add_comm,add_assoc]

-- 08
example : ∀ (n:ℕ), ∃ (k:ℕ), n < k:=by
intro n
use n.succ
exact lt_succ_self n

-- 09
example : ∀(n:ℕ), R n ∧ S n → ∃(k:ℕ), S k:=by
intro n hn
exact ⟨n,hn.2⟩

-- 10
example (f: ℕ → ℕ) (k : ℕ): Surjective f → ∃(n:ℕ), f n = 2023^k := by
intro hf
apply hf

-- 11 Hint: if we have `h : ∀ (n : ℕ), f n = g n` then `rw (h n)` will replace `f n` by `g n` in the goal.
example (f g: ℕ → ℕ) (h : ∀ n, f n = g n):  Injective f → Injective g:=by
intro hf
intro a b hab
apply hf
rwa [h a,h b]

-- 12
example (f : ℕ → ℕ) (m n : ℕ): Bijective f → f m = f n → m = n:=by
intro hf heq
apply hf.1
exact heq

/-
If (a b: ℕ) then `a ∣ b` means `a divides b`, ie. `∃(c:ℕ), b = a*c`
Note that this symbol is `\|` not `|` which is obviously not the same...-/

-- 13
example (h : ∀n:ℕ, 3 ≤ n → 2 ∣ n → ¬n.Prime ) :∀(k:ℕ), Even k → 3 ≤ k → ¬ k.Prime :=by
  intro k hk1 hk2
  apply h
  exact hk2
  cases' hk1 with r hr
  use r
  rwa [two_mul]

-- We can use `⟨n,h⟩` notation for `∃ n, h`
-- 14
example (n : ℕ)(h : n.Prime) : ∃ (k : ℕ), k.Prime:=by
  exact ⟨n,h⟩

-- 15 what if we don't have the hypothesis `n.Prime`
example  : ∃ (k : ℕ), k.Prime:=by
  refine' ⟨2,Nat.prime_two⟩

-- 16 To complete the next proof we need to show 2027 is Prime. Luckily `norm_num` will do it.
example  : ∃ (k : ℕ), 2026 < k ∧ k.Prime:=by
  refine'  ⟨2027,by norm_num,_⟩
  --norm_num -- the primality proving part of norm_num does not seem to exist in Lean4
  norm_num
  sorry

-- /-
-- # New tactic: push_neg

-- Given a proposition with quantifiers involving a negation  `push_neg` will push the negation inside
-- the expression as far as possible

-- Like `rw` we can also use it to simplify terms in the local context eg `push_neg at h`  -/

-- 17
example : ¬ ∃ (n:ℕ), ∀ (k:ℕ), k < n:=by
  push_neg
  intro n
  use n

-- 18 Hint: If m,n ∈ ℕ then `max m n` is their maximum, `norm_num` may be useful again here
example : ∀ (m: ℕ), ∀ (n: ℕ), ∃ (k:ℕ), m ≤ k ∧ n ≤ k:=by
intro m n
refine' ⟨m+n,_,_⟩
apply Nat.le_add_right m
apply Nat.le_add_left n

variable (pr : α → Prop)
/- A function from a type to Prop is usually called a `predicate`
   For example `nat.Prime: ℕ → Prop` -/

-- 19
example : (∀ (a:α), pr a) ↔ ¬ ∃ (b:α), ¬ pr b:=by
apply Iff.intro
· intro h
  push_neg
  exact h
· intro h
  push_neg at h
  exact h

/-
In the previous example we had a `split` and the two parts used almost the same
code. One way to do such proofs is to use the `try {code}` tactic, which tries the
`code` but doesn't complain if it fails.
-/
-- 20
-- example : (∀ (a:α), pr a) ↔ ¬ ∃ b:α, ¬ pr b:=by
-- apply Iff.intro
-- repeat
--   intro h
--   try {push_neg}  -- this succeeds the 1st time but silently fails the 2nd time
--   try {push_neg at h}  --this succeeds the 2nd time but silently fails the 1st time
--   exact h

-- 21
example : ∃ (n: ℕ), n + 2 = 5 ∧ 2 * n = 6:=by
use 3
norm_num

-- 22 beware the divides `∣` symbol is not `|` but `\|`
example : ¬(∀ n: ℕ, n.Prime ∨ (2 ∣ n)):=by
intro hf
contrapose hf
push_neg
use 9
norm_num
intro h2

sorry

-- -- 23 Hint: Even n := ∃(r:ℕ), n = r + r
-- example : ∀ (n m:ℕ), Even n ∧ Even m → Even (n+m):=
-- by
--  admit

-- -- A relation r on ℕ
-- variable (r : ℕ → ℕ → Prop)
-- -- 24
-- example:  reflexive r → r 17 17:=
-- by
--   unfold reflexive,
--   admit

-- -- 25
-- example : symmetric r →  r 13 2 → r 2 13:=
-- by
--   unfold symmetric,
--   admit

-- -- 26
-- example (a b c : ℕ) : equivalence r → r a b →  r b c →  r a c:=
-- by
--   unfold equivalence transitive,
--   admit

-- -- 27
-- example (a b c : ℕ) : equivalence r → r a b →  r b c →  r c a:=
-- by
--   admit

-- -- 28
-- example (h1 : symmetric r) (h2: ∃ (a b:ℕ), r a b ∧ a ≠ b) :
-- (∀ {m n: ℕ}, r m n → r n m → m = n) → false :=
-- by
--   admit

-- -- 29 Hint: dsimp may be useful here (you'll know when to use it!)
-- example : ∃! (n:ℕ), ∀ (k:ℕ), n ≤ k :=
-- by
--   admit

-- -- 30
-- example : ¬∃ (n:ℕ), ∀ (k:ℕ), k ≤ n :=
-- by
--   admit

-- /- If we want to define a function and then use it we use the `def` keyword

--  We can define the relation `parity` on the integers ℤ
--  `parity a b` holds iff `a + b` is Even -/
-- def parity : ℤ → ℤ → Prop :=
-- by
--   intros a b,
--   exact Even (a+b),

-- -- Lets prove this is an equivalence relation
-- -- 31
-- example : equivalence parity :=
-- by
--   split,
--   {
--     admit
--   },
--   split,
--   {
--     admit
--   },
--   {
--     admit
--   },

-- -- The relation `r a b` iff `a = b mod m` iff `m ∣ (b-a)`
-- def mod (m: ℤ) : ℤ → ℤ → Prop :=
-- by
--   intros a b,
--   exact m ∣ (b-a),

-- -- 32
-- example :∀ (m:ℤ), equivalence (mod m) :=
-- by
--   admit


import Mathlib.Data.Nat.Basic
import Notes4.IndTypes

/-
One of the features that sets Lean apart from other programming languages, and
motivates this course, is the ability to reason your written code in a
computational way.

We can state theorems expressing invariants of code and then write more
code to produce a proof verifying that theorem.

For example, we expect, implemented correctly, taking lenghts of
lists is additive under appending an element, ie length a::l = 1 + length l.
-/

theorem list_length_add_of_cons {α : Type} (a : α) (l: List α) :
    (a::l).length = 1 + l.length := sorry

/-
Theorems are terms of type Prop. Prop is a special type in Lean whose terms
are propositions in the sense of mathematical logic. For example,

- There are infinitely many prime numbers.
- If f and g are differentiable, then so is f + g.
- There is at most one path between any two vertices in a tree.
- All natural numbers are odd.

Propositions can have proofs or not. These proofs are built by stringing
together existing proofs to make new ones using basic rules of inference.
Thanks to this computational nature, one can model propositional (and
higher) logic in Lean and other languages like Coq, Agda, HOL, Isabelle,
F-star, Idris,...

This provides the ability to use the _same tools_ to create proofs as
to program. Assuming the underlying implementation sound, then proving a
theorem universally assures that correctness of the code, with regards
to that particular statement. For list_length_add_of_append, giving a proof
assures that our implementation of append is _always_ additive with
respect to lengths of lists. There are no edge cases and no bugs.

What does a proof look like?
-/

example {α : Type} (a : α) (l: List α) : (a::l).length = 1 + l.length := by
  match l with
  | [] => simp
  | a'::as =>
    simp only [List.length]
    ac_rfl

/-
_You do not have to have any understanding of the actual tactics in
use in these examples right now._
Most of the course will devoted to obtaining facility
in constructing proofs using these tactics.

Right now, you should familiarize yourself with the structure of a proof.

Let's step through this proof to get a sense of the pieces involved. Every
proof starts with either an `example` or `theorem` keyword. If we use the
theorem keyword, we need to provide a unique name. Otherwise, we do not.

Then comes the hypotheses of the statement
`{α : Type} (a : α) (l: List α)` which say we have
- some type α which Lean should try to fill in from the context,
- a term `a` of type `α`, and
- a list `l` with elements of type `α`.

At the end of the hypotheses, we find a `:` to indicate the type of
proposition will follow. Here the type is `(a::l).length = 1 + l.length`.
This is the proposition we are trying to prove.

Next, we have `:=` which tells Lean that the statement of the type is done
and next we will provide the term of that term, here a proof.

Immediately after `:=` we have a `by` which enters _tactic mode_. We
will start out using proving tactic mode to construct proofs.

Tactics are a method of automation to construct terms of desired types.
As such, they fall in the realm of _meta_-programming. A well-written
tactic can be incredibly useful and great time-saver.

The remainder of the proof consists of calls to appropriate tactics:
- `match` allows to reduce to providing proofs for each constructor
of List, nil and cons.
- In the nil, [], branch, we call on Lean's simplifier _simp_ which is
a extensiable rewriting system.
- In the cons branch, we again called simp but using the definition of
List.length to simplify the goal. Then we called the `ac_rfl` tactic
which closes goals using associativity, commutativity, and reflexivity.

In general, there is a zoo of tactics of which we will see only a corner.
If time allows, we will discuss how to write tactics.

Let's see another example of the structure of a theorem and its proof.
-/

theorem exp_eq_exp' (b e : Nat) : exp b e = exp' b e := by
  match e with
  | 0 => simp [exp,exp']
  | n+1 =>
    simp only [exp,exp']
    simp only [Nat.add]
    rw [exp_eq_exp' b n]

/-
Now we know from `exp_eq_exp'` that our two definition of natural
number exponentiation always produce the same outputs.

Here we saw another tactic `rw` which is also a rewriting tactic.

Now that we know that both version of exp are equal let's prove a
basic property of exponention.
-/

example : ∀ a b c : Nat, exp a (b + c) = (exp a b)*(exp a c) := by
  intro a b c
  induction' b with n ih
  · simp [exp]
  · rw [Nat.succ_eq_add_one]
    rw [Nat.add_assoc,Nat.add_comm 1 c,←Nat.add_assoc]
    simp [exp]
    rw [Nat.mul_assoc,Nat.mul_comm a _,←Nat.mul_assoc]
    rw [ih]