-- Chapter 4 Quantifiers and Equality

#print "=================================="
#print "Section 4.1 The Universal Quantifier"
#print " "
/- Notice that if α is any type, we can represent a unary predicate p on α as an
   object of type α → Prop. In that case, if x : α, then p x denotes the assertion
   that p holds of x. Similarly, an object r : α → α → Prop denotes a binary
   relation on α: if x y : α, then r x y denotes the assertion that x is related to y.

   The universal quantifier, ∀ x : α, p x is supposed to denote the assertion that
   "for every x : α, p x" holds. As with the propositional connectives, in systems of
   natural deduction, "forall" is governed by an introduction and elimination rule.

   Informally, the introduction rule states:
   Given a proof of p x, in a context where x : α is arbitrary, we obtain a proof
   ∀ x : α, p x.

   The elimination rule states:
   Given a proof ∀ x : α, p x and any term t : α, we obtain a proof of p t.

   The propositions-as-types interpretation now comes into play. Remember the introduction
   and elimination rules for Pi types:

   Introduction Rule for Pi types:
   Given a term t of type β x, in a context where x : α is arbitrary, we have
   (λ x : α, t) : Π x : α, β x.

   Elimination rule for Pi types:
   Given a term s : Π x : α, β x and any term x : α, we have s x : β x.

   If p x has type Prop, and if we replace Π x : α, β x with ∀ x : α, p x, then we can
   read these as the correct rules for building proofs involving the universal quantifier.

   The Calculus of Constructions therefore identifies Π and ∀ in this way.
   If p is any expression, ∀ x : α, p is simply alternative notation for Π x : α, p,
   with the idea that the former is more natural than the latter in cases where p
   is a proposition.

   Typically, the expression p will depend on x : α. Recall that, in the case of ordinary
   function spaces, we could interpret α → β as the special case of Π x : α, β in which β
   does not depend on x. Similarly, we can think of an implication p → q between propositions
   as the special case of ∀ x : p, q in which the expression q does not depend on x.
-/

namespace Sec_4_1

  -- Example: how the propositions-as-types correspondence is used in practice.
  namespace example1
    variables (α : Type) (p q : α → Prop)
    example : (∀ x : α, p x ∧ q x) → ∀ y : α, p y  :=
    assume h : ∀ x : α, p x ∧ q x,
    assume y : α,
    show p y, from (h y).left

    -- alternative version of the last example
    example : (∀ x : α, p x ∧ q x) → ∀ y : α, p y :=
      λ (h : ∀ x : α, p x ∧ q x) (y : α), and.left (h y)

  end example1

  -- Example: expressing the fact that a relation r is transitive.
  namespace example2
    variables (α : Type) (r : α → α → Prop)
    variable trans_r : ∀ x y z : α, r x y → r y z → r x z
    variables a b c : α
    variables (h₁ : r a b) ( h₂ : r b c)
    #check trans_r                 -- ∀ (x y z : α), r x y → r y z → r x z
    #check trans_r a b c           -- r a b → r b c → r a c
    #check trans_r a b c h₁        -- r b c → r a c
    #check trans_r a b c h₁ h₂     -- r a c

  end example2

  -- Example: impicit parameters
  namespace example3
    /- It can be tedious to supply the arguments a b c, when they can be inferred
      from hab hbc. For that reason, it is common to make these arguments implicit: -/
      universe u
      variables (α : Type) (r : α → α → Prop)
      variable trans_r : ∀ {x y z : α}, r x y → r y z → r x z
      variables a b c : α
      variables (h₁ : r a b) (h₂ : r b c)
      #check trans_r             -- trans_r : r ?M_1 ?M_2 → r ?M_2 ?M_3 → r ?M_1 ?M_3
      #check trans_r h₁          -- trans_r h₁ : r b ?M_1 → r a ?M_1
      #check trans_r h₁ h₂       -- trans_r h₁ h₂ : r a c

      -- The advantage is that we can now write `trans_r h₁ h₂` as a proof of `r a c`.

  end example3

  -- Example: elementary reasoning with an equivalence relation.
  namespace example4

    variables (α : Type) (r : α → α → Prop)
    variable refl_r : ∀ {x : α}, r x x
    variable symm_r : ∀ {x y : α}, r x y → r y x
    variable trans_r : ∀ {x y z : α}, r x y → r y z → r x z

    example (a b c d : α) (h₁ : r a b) (h₂ : r c b) (h₃ : r c d) : r a d :=
      have h₄ : r b c, from (symm_r h₂),
      have h₅ : r a c, from (trans_r h₁ h₄),
      show r a d, from (trans_r h₅ h₃)

    -- after working out a proof in simple steps, we can make it more concise:
    example (a b c d : α) (h₁ : r a b) (h₂ : r c b) (h₃ : r c d) : r a d :=
      (trans_r (trans_r h₁ (symm_r h₂)) h₃)

  end example4

  /- It is the typing rule for Pi types, and the universal quantifier in particular,
     that distinguishes Prop from other types. Suppose we have α : Sort i and
     β : Sort j, where the expression β may depend on a variable x : α.
     Then Π x : α, β is an element of Type (imax i j), where imax i j is the maximum
     of i and j if j is not 0, and 0 otherwise.

     The idea is as follows: If j is not 0, then Π x : α, β is an element of
     Sort (max i j). In other words, the type of dependent functions from α to β
     "lives" in the universe whose index is the maximum of i and j. Suppose, however,
     that β is of Sort 0, that is, an element of Prop. In that case, Π x : α, β is
     an element of Sort 0 as well, no matter which type universe α lives in.
     In other words, if β is a proposition depending on α, then ∀ x : α, β is
     again a proposition. This reflects the interpretation of Prop as the type of
     propositions rather than data, and it is what makes Prop *impredicative*.

     Impredicativity of Prop Type:
     If α is a type, we can form the type α → Prop of all predicates on α (aka
     the "power type" of α). The impredicativity of Prop means that we can form
     propositions (i.e., inhabitants of Prop) that quantify over α → Prop. In
     particular, we can define predicates on α by quantifying over all predicates
     on α; this is the type of circularity that was once considered problematic.
  -/

end Sec_4_1

#print "=================================="
#print "Section 4.2 Equality"
#print " "
  /- Chapter 7 explains how equality is defined from primitives of Lean's
     logical framework. Here we just see how to use equality. -/

  -- Of course, equality is an equivalence relation:

namespace Sec_4_2

  namespace example1
    #check eq.refl          -- ∀ (a : ?M_1), a = a
    #check eq.symm          -- ?M_2 = ?M_3 → ?M_3 = ?M_2
    #check eq.trans         -- ?M_2 = ?M_3 → ?M_3 = ?M_4 → ?M_2 = ?M_4

    /- Output is easier to read if we have Lean instantiate implicit arguments at a
       particular universe u. -/

    universe u

    #check @eq.refl.{u}   -- ∀ {α : Sort u} (a : α), a = a
    #check @eq.symm.{u}   -- ∀ {α : Sort u} {a b : α}, a = b → b = a
    #check @eq.trans.{u}  -- ∀ {α : Sort u} {a b c : α}, a = b → b = c → a = c

  end example1

  /- Reflexivity is more powerful than it looks. Recall that the Calculus of
     Constructions treats terms with a common reduct as equal. As a result, some
     nontrivial identities can be proved by reflexivity. -/

  namespace example2
    universe u
    variables (α β : Type u)
    example (f : α → β) (a : α) : (λ x : α, f x) a = f a := eq.refl _
    example (a₁ : α) (a₂ : α) : (a₁, a₂).1 = a₁ := eq.refl _
    example : 2 + 3 = 5 := eq.refl _

  -- This feature is so important Lean defines a notation `rfl` for `eq.refl _`.
    example (f : α → β) (a : α) : (λ x : α, f x) a = f a := rfl

  end example2

  /- Equality is more than just an equivalence relation. It is a congruence
     (the smallest congruence) in every congruence lattice; thus, it has the
     property that every assertion respects the equivalence.  -/

  /- For instance, given `h₁ : a = b` and `h₂ : p a`, we can construct a proof
     for `p b` using substitution: `eq.subst h₁ h₂`. -/
  namespace example3
    universe u
    example (α : Type u) (a₁ a₂ : α) (p : α → Prop)
      (h₁ : a₁ = a₂) (h₂ : p a₁) : p a₂ := eq.subst h₁ h₂

  -- Lean has a shorthand for eq.subst as well; it's ▸, as shown here.
    example (α : Type u) (a₁ a₂ : α) (p : α → Prop)
      (h₁ : a₁ = a₂) (h₂ : p a₁) : p a₂ := h₁ ▸ h₂

  end example3

  /- `eq.subst` is used to define the following auxiliary rules, which carry out
     more explicit substitutions. They deal with applicative terms (of form s t).
     Specifically, we use `congr_arg` to replace the argument, `congr_fun` to
     replace the term that is being applied, and `congr` to replace both at once. -/

  namespace example4
    variable α : Type
    variables a₁ a₂ : α
    variables f g : α → ℕ
    variable h₁ : a₁ = a₂
    variable h₂ : f = g

    example : f a₁ = f a₂ := congr_arg f h₁
    example : f a₁ = g a₁ := congr_fun h₂ a₁
    example : f a₁ = g a₂ := congr h₂ h₁

  end example4

  namespace example5
    variables a b c d : ℤ
    example : a + 0 = a := add_zero a
    example : 0 + a = a := zero_add a
    example : a * 1 = a := mul_one a
    example : 1 * a = a := one_mul a
    example : -a + a = 0 := neg_add_self a
    example : a + -a = 0 := add_neg_self a
    example : a - a = 0 := sub_self a
    example : a + b = b + a := add_comm a b
    example : a + b + c = a + (b + c) := add_assoc a b c
    example : a * b = b * a := mul_comm a b
    example : a * b * c = a * (b * c) := mul_assoc a b c
    example : a * (b + c) = a * b + a * c := mul_add a b c
    example : a * (b + c) = a * b + a * c := left_distrib a b c
    example : (a + b) * c = a * c + b * c := add_mul a b c
    example : (a + b) * c = a * c + b * c := right_distrib a b c
    example : a * (b - c) = a * b - a * c := mul_sub a b c
    example : (a - b) * c = a * c - b * c := sub_mul a b c
  end example5

  /- Identities likes these work in arbitrary instances of the relevant
     algebraic structures, using the type class mechanism described in Chapter 10.
     Chapter 6 provides shows how to find theorems like this in the library. -/

  namespace example6
    example (x y z : ℕ) : x * (y + z) = x * y + x * z := mul_add x y z

    example (x y z : ℕ) : x + y + z = x + (y + z) := add_assoc x y z

    example (x y : ℕ) : (x + y) * (x + y) = x * x + y * x + x * y + y * y :=  --sorry
      have h₁ : (x + y) * (x + y) = x * (x + y) + y * (x + y), from add_mul x y (x + y),
      have h₃ : x * (x + y)  = x * x + x * y, from mul_add x x y,
      have h₄ : y * (x + y) = y * x + y * y, from mul_add y x y,
      calc
        (x + y) * (x + y) = x * (x + y) + y * (x + y) : by rw h₁
                      ... = (x*x + x*y) + (y*x + y*y) : by rw [h₃, h₄]
                      ... = x*x + y*x + x*y + y*y   : by simp
  end example6

  /- The 2nd implicit parameter to ▸ provides the context in which the subst occurs.
     This parameter has type α → Prop, so inferring this predicate requires an
     instance of *higher-order unification*.  The problem of determining whether
     a higher-order unifier exists is undecidable, and Lean can at best provide
     imperfect and approximate solutions. As a result, eq.subst doesn't always do
     what you want; this is discussed in greater detail in Section 6.10. -/

  /- IMPORTANT (for project proposal)
     Because equational reasoning is so common and important, Lean provides a
     number of mechanisms to carry it out more effectively. The next section offers
     syntax that allow you to write calculational proofs in a more natural and
     perspicuous way. But, more importantly, equational reasoning is supported by
     a term rewriter, a simplifier, and other kinds of automation. The term rewriter
     and simplifier are described briefly in the next setion, and then in greater
     detail in the next chapter. -/

end Sec_4_2

#print "=================================="
#print "Section 4.3 Calculational Proofs"
#print " "
  /- A calculational proof (or "proof sequence") is a chain of intermediate
     results meant to be composed by basic principles such as the transitivity
     of equality. In Lean, such proofs start with the keyword `calc`, and have
     the following syntax:

           calc
             <expr>_0  'op_1'  <expr>_1  ':'  <proof>_1
             '...'   'op_2'  <expr>_2  ':'  <proof>_2
              ...
             '...'   'op_n'  <expr>_n  ':'  <proof>_n

     where each `<proof>_i` is a proof for `<expr>_{i-1} op_i <expr>_i`. -/

namespace Sec_4_3
  namespace example1
    variables a b c d e : ℕ
    variables h₁ : a = b
    variables h₂ : b = c + 1
    variables h₃ : c = d
    variables h₄ : e = 1 + d

  theorem T : a = e :=
    calc
      a     = b     : h₁
        ... = c + 1 : h₂
        ... = d + 1 : congr_arg _ h₃
        ... = 1 + d : add_comm d ( 1 : ℕ)
        ... = e     : eq.symm h₄

  end example1

  /- The style is most effective when used in conjunction with `simp` and `rewrite`
     tactics, discussed in greater detail in Ch 5. -/

  namespace example2
    variables a b c d e : ℕ
    variables h₁ : a = b
    variables h₂ : b = c + 1
    variables h₃ : c = d
    variables h₄ : e = 1 + d

  include h₁ h₂ h₃ h₄
  theorem T₁ : a = e :=
    calc
      a     = b      : h₁
        ... = c + 1  : h₂
        ... = d + 1  : by rw h₃
        ... = 1 + d  : by rw add_comm
        ... = e      : by rw h₄

  -- Rewrites can applied sequentially, so the above can be shortened as follows:
  theorem T₂ : a = e :=
    calc
      a     = d + 1 : by rw [h₁, h₂, h₃]
        ... = 1 + d : by rw add_comm
        ... = e     : by rw h₄

  -- ...or even this
  theorem T₃ : a = e := by rw [h₁, h₂, h₃, add_comm, h₄]

  end example2

end Sec_4_3

#print "=================================="
#print "Section 4.4 The Existential Quantifier"
#print " "

  /- Finally, consider the existential quantifier, which can be written as either
     `exists x : α, p x` or `∃ x : α, p x`. Both versions are actually abbreviations
     for the expression `Exists (λ x : α, p x)`, defined in Lean's library. -/

  /- As usual, the library includes both an intro and elim rule for exists.
     INTRO: to prove `∃ x : α, p x`, it suffices to provide a term `t : α` and
     a proof of `p t`. -/

namespace Sec_4_4

  namespace example1
    open nat
    example : ∃ x : ℕ, x > 0 :=
      have h : 1 > 0, from zero_lt_succ 0, exists.intro 1 h

    example (x : ℕ) (h : x > 0) : ∃ y : ℕ, y < x := exists.intro 0 h

    example (x y z : ℕ) (h₁ : x < y) (h₂ : y < z) : ∃ w, x < w ∧ w < z :=
      exists.intro y (and.intro h₁ h₂)

    #check @exists.intro

  end example1

  namespace example2
    variable g : ℕ → ℕ → ℕ
    variable h₁ : g 0 0 = 0

    theorem g₁ : ∃ x, g x x = x := ⟨0, h₁⟩
    theorem g₂ : ∃ x, g x 0 = x := ⟨0, h₁⟩
    theorem g₃ : ∃ x, g 0 0 = x := ⟨0, h₁⟩
    theorem g₄ : ∃ x, g x x = 0 := ⟨0, h₁⟩

    set_option pp.implicit true    -- (to display implicit arguments)
    #check g₁
    #check g₂
    #check g₃
    #check g₄

  end example2

  /- We can view `exists.intro` as an information-hiding operation, since it hides
     the witness to the body of the assertion.

     The existential elimination rule, `exists.elim`, performs the opposite operation.
     It allows us to prove a proposition `q` from `∃ x : α, p x`, by showing that `q` follows
     from `p w` for an arbitrary value `w`.

     Roughly speaking, since we know there is some `x` satisfying `p x`, we can give it a name,
     say, `w`. If `q` does not mention `w`, then showing that `q` follows from `p w` is tantamount
     to showing that `q` follows from the existence of any such `x`.  -/

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

    example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
      exists.elim h
      (assume w,
        assume hw : p w ∧ q w,
        show ∃ x, q x ∧ p x, from ⟨w, hw.right, hw.left⟩)

    /- IMPORTANT NOTE:
       The anonymous constructor notation ⟨w, hw.right, hw.left⟩ abbreviates a nested application.
       Equivalently, ⟨w, ⟨hw.right, hw.left⟩⟩. -/

  end example3

  /- It may be helpful to compare the exists-elimination rule to the or-elimination rule:
     the assertion ∃ x : α, p x can be thought of as a big disjunction of the propositions p a,
     as a ranges over all the elements of α. -/

  /- An existential proposition is very similar to a sigma type (Sec 2.8). The difference is that, given
     `a : α` and `h : p a`, the term
                                      `exists.intro a h` has type  `(∃ x : α, p x) : Prop`
     whereas the term
                                      `sigma.mk a h`     has type  `(Σ x : α, p x) : Type`
     The similarity between ∃ and Σ is another instance of Curry-Howard. -/

  -- PATTERN MATCHING --
  namespace example4
    variables (α : Type) (p q : α → Prop)

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

    -- We can (and should, imho) annotate the types used in the match for greater clarity.
    example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
      match h with ⟨(w: α), (hw : p w ∧ q w)⟩ :=
      ⟨w, hw.right, hw.left⟩
      end

    -- We can even use match to decompose the conjunction at the same time.
    example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
      match h with ⟨(w: α), (hwp : p w), (hwq : q w)⟩ := ⟨w, hwq, hwp⟩ end

    -- Lean will even allow us to use an implicit match in the assume statement:
    example : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x :=
      assume ⟨(w: α), (hwp : p w), (hwq : q w)⟩, ⟨w, hwq, hwp⟩

    -- In Ch 8 we see that all these variations are instances of a more general pattern-matching construct.

  end example4

  /- Next, we define `even a` as `∃ b, a = 2*b`, and then show the sum of two even numbers is even. -/
  namespace example5
    def is_even (a : ℕ) := ∃ b, a = 2 * b

    theorem even_plus_even_is_even {a b : ℕ}
      (h₁ : is_even a) (h₂ : is_even b) : is_even (a + b) :=
      exists.elim h₁
        (assume w₁, assume hw1 : a = 2 * w₁,
          exists.elim h₂
            (assume w₂, assume hw2 : b = 2 * w₂,
              exists.intro (w₁ + w₂)
                (calc
                  a + b = 2 * w₁ + 2 * w₂ : by rw [hw1, hw2]
                    ... = 2 * (w₁ + w₂)   : by rw mul_add)))

    -- we can rewrite the last proof more concisely...
    theorem even_plus_even {a b : ℕ}
      (h₁ : is_even a) (h₂ : is_even b) : is_even (a + b) :=
    match h₁, h₂ with
      ⟨w₁, hw1⟩, ⟨w₂, hw2⟩ := ⟨w₁ + w₂, by rw [hw1, hw2, mul_add]⟩
    end

    -- ...again, it's clearer to annotate the types.
    theorem even_plus_even' {a b : ℕ}
      (h₁ : is_even a) (h₂ : is_even b) : is_even (a + b) :=
    match h₁, h₂ with
      ⟨(w₁ : ℕ), (hw1: a = 2 * w₁)⟩, ⟨(w₂ : ℕ), (hw2 : b = 2 * w₂)⟩ :=
      ⟨w₁ + w₂, by rw [hw1, hw2, mul_add]⟩
    end
  end example5

  /- Just as the constructive "or" is stronger than the classical "or,"
     so, too, is the constructive "exists" stronger than the classical "exists".
     For example, the following implication requires classical reasoning because,
     from a constructive standpoint, knowing that it is not the case that every x
     satisfies `¬ p x` is not the same as having a particular `x` that satisfies `p x`.
  -/

  namespace example6
    open classical
    variables (α : Type) (p : α → Prop)
    example (h : ¬ ∀ x, ¬ p x) : ∃ x, p x :=
      by_contradiction
        (assume h₁ : ¬ ∃ x, p x,
          have h₂ : ∀ x, ¬ p x, from
            assume x,
            assume h₃ : p x,
            have h₄ : ∃ x, p x, from ⟨x, h₃⟩,
            show false, from h₁ h₄,
          show false, from h h₂)
  end example6

  /- Here are some common identities involving the existential quantifier. (Prove as many as you can and
     determine which are nonconstructive, and hence require some form of classical reasoning.) -/

  namespace constructive_examples

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

  example : (∃ x : α, r) → r := assume ⟨(w : α), hr⟩, hr
  example : r → (∃ x : α, r) := exists.intro a
  example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r := iff.intro
    -- ⇒
    (assume ⟨w, (h₁ : p w ∧ r)⟩, ⟨⟨w, h₁.left⟩, h₁.right⟩)
    -- ⇐
    (assume (h : (∃ x, p x) ∧ r),
      exists.elim h.left
      (assume w,
        assume hpw : p w,
         show (∃ x, p x ∧ r), from ⟨w, hpw, h.right⟩
      )
    )

  example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) := iff.intro
    -- ⇒
    (assume ⟨w, (h₁ : p w ∨ q w)⟩,
      or.elim h₁
        (assume hpw : p w, or.inl ⟨w, hpw⟩)
        (assume hqw : q w, or.inr ⟨w, hqw⟩))
    -- ⇐
    (assume h : (∃ x, p x) ∨ (∃ x, q x),
      or.elim h
        (assume ⟨w, hpw⟩, ⟨w, or.inl hpw⟩)
        (assume ⟨w, hpq⟩, ⟨w, or.inr hpq⟩))

  end constructive_examples

  namespace example1
  /-  (∀ x, p x) ↔ ¬ (∃ x, ¬ p x)

      NOTE: for this example, only one direction require classical axioms.
      We split the proof up in two in order to highlight this fact. -/

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

  -- Constructive direction:
    theorem constructive₁ : (∀ x, p x) → ¬ (∃ x, ¬ p x) :=
      assume h : ∀ x, p x,
      assume h' : (∃ x, ¬ p x),  -- then reach a contradiction, to conclude `¬ (∃ x, ¬ p x)`
        exists.elim h'
          (assume w,
            assume hw': ¬ p w,
            have hw : p w, from h w,
            show false, from hw' hw)

  -- Classical direction:

    open classical

    lemma dne : Π {α : Type} {x : α} {p : α → Prop}, (¬ ¬ (p x)) → (p x) :=
      λ (α : Type) (x : α) (p : α → Prop) (h : ¬ ¬ p x),
        or.elim (em (p x))
        (assume h₂ : p x, h₂)  -- alternatively,  (assume h₂ : ¬p, absurd h₂ h)
        (assume h₃ : ¬ p x, false.elim (h h₃))

    #check dne

    theorem classical₁ : ¬ (∃ x, ¬ p x) → (∀ x, p x) :=
      assume h₁ : ¬ (∃ x, ¬ p x),
      by_contradiction
        (assume hc : ¬ (∀ x, p x), -- (reach a contradiction to prove `∀ x, p x`)
         have hcc : ∀ x, ¬ ¬ p x, from
           assume w,
           assume h₂ : ¬ p w, -- (reach a contradiction to prove hcc)
           have h₃ : ∃ x, ¬ p x, from ⟨w, h₂⟩,
           show false, from h₁ h₃, -- (done proving hcc)
         have hz : ∀ x, p x, from
           assume z,
           show p z, from dne (hcc z),
         show false, from hc hz)

    #check classical₁

  end example1

  namespace example2
  /- We are asked to prove the following equivalence:
       (∃ x, p x) ↔ ¬ (∀ x, ¬ p x)
     As above, we split it up to show one direction can be done constructively. -/

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

    theorem constructive₂ : (∃ x, p x) → ¬ (∀ x, ¬ p x) :=
      assume h : ∃ x, p x,
      assume h' : ∀ x, ¬ p x,  -- reach a contradiction (this is how we prove a negation)
      exists.elim h
        (assume w,
        assume hc : p w,
        have hnc : ¬ p w, from h' w,
        show false, from hnc hc)

    #check constructive₂

    open classical

    theorem classical₂ : ¬ (∀ x, ¬ p x) → (∃ x, p x) :=
      assume h : ¬ (∀ x, ¬ p x),
        by_contradiction (
          assume h₁ : ¬ (∃ x, p x),  -- (reach a contradiction to prove `∃ x, p x`)
            have hcc : ∀ x, ¬ p x, from
            assume w,
            assume h₂ : p w, -- (reach a contradiction to prove hcc)
            have h₃ : ∃ x, p x, from ⟨w, h₂⟩,
            show false, from h₁ h₃, -- (done proving hcc)
          show false, from h hcc)

  end example2

  namespace example3
  /- We are asked to prove the following equivalence:
       (¬ ∃ x, p x) ↔ (∀ x, ¬ p x)
     Again, we split it up to show one direction can be done without classical axioms. -/

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

    theorem constructive₃ : (∀ x, ¬ p x) → (¬ ∃ x, p x) :=
      assume h : ∀ x, ¬ p x,
      assume hn : ∃ x, p x,
        exists.elim hn (
          assume w,
          assume hw : p w,
          have hwc : ¬ p w, from h w,
          show false, from hwc hw)

    open classical
    theorem classical₃ : (¬ ∃ x, p x) → (∀ x, ¬ p x) :=
      assume h : ¬ ∃ x, p x,
      by_contradiction (
        assume hc : ¬ (∀ x, ¬ p x),
        have hcc : ∃ x, p x, from
          example2.classical₂ α p hc,
        show false, from h hcc)

  end example3

  namespace example4
  /- `(¬ ∀ x, p x) ↔ (∃ x, ¬ p x)` splits into constructive/classical directions -/
    variables (α :Type) (p q : α → Prop)
    variable a : α
    variable r : Prop
    theorem constructive₄ : (∃ x, ¬ p x) → (¬ ∀ x, p x) :=
      assume h : ∃ x, ¬ p x,
      assume hn : ∀ x, p x,
        exists.elim h
          (assume w,
            assume hnw : ¬ p w,
            have hw : p w, from hn w,
            show false, from hnw hw)

    open classical
    theorem classical₄ : (¬ ∀ x, p x) → (∃ x, ¬ p x) :=
      assume h : ¬ ∀ x, p x,
      by_contradiction (
        assume hn : ¬ (∃ x, ¬ p x),
        have hc : ∀ x, p x, from example1.classical₁ α p hn,
        show false, from h hc)

   end example4

  namespace example5
  /- (∀ x, p x → r) ↔ (∃ x, p x) → r
     In this case, neither direction requires classical axioms. -/

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

    theorem constructive₅ : (∀ x, (p x → r)) ↔ (∃ x, p x) → r := iff.intro

      (assume h₁ : ∀ x, (p x → r),
        assume h₂ : ∃ x, p x,
        exists.elim h₂
          (assume w,
            assume hw : p w,
            show r, from h₁ w hw))

      (assume h : ((∃ x, p x) → r),
        assume w,
        assume hw : p w,
        show r, from h ⟨w, hw⟩)
  end example5

  namespace example6
  /- (∃ x, p x → r) ↔ (∀ x, p x) → r := -/

    theorem constructive₆ : (∃ x, p x → r) → (∀ x, p x) → r :=
      (assume h : ∃ x, p x → r,
        assume h' : ∀ x, p x,
        exists.elim h
          ( assume w,
            assume hw : p w → r,
            show r, from hw (h' w) )
      )

   theorem classical₆ : (∀ x, p x) → r → (∃ x, p x → r) :=  sorry

  end example6

  namespace example7
   example : (∃ x, r → p x) ↔ (r → ∃ x, p x) := sorry
  end example7

end Sec_4_4

#print "=================================="
#print "Section 4.5 More on the Proof Language"
#print " "

namespace Sec_4_5
end Sec_4_5

#print "=================================="
#print "Section 4.6 Exercises"
#print " "

namespace Sec_4_6
end Sec_4_6


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

import tactic

/-!

# Funciones en Lean.

La notación para funciones es la habitual en matemáticas: dados dos tipos `X` e `Y`, `f : X → Y`
denota una función de `X` a `Y`.

Internamente, `X → Y` denota el tipo de funciones de `X` a `Y`, y `f : X → Y` indica que  `f` es un
término de tipo `X → Y`, es decir, una  función de `X` a `Y`.

NOTACIÓN: dado `x : X` y `f : X → Y`, para denotar la evaluación  `f(x)` es posible omitir los
paréntesis, escribiendo simplemente `f x`. Sin embargo, para expresiones más complicadas los
paréntesis son necesarios. Por ejemplo, dados `x : X`, `f : X → Y` y `g : Y → Z`, para evaluar
la composición `g(f(x))` necesitamos al menos los paréntesis exteriores: `g(f x)`.

## Inyectividad y sobreyectividad

Lean conoce las definiciones de función injectiva (`function.injective`) y sobreyectiva
(`function.surjective`). Dada cualquier función `f : X → Y`, `function.injective f`
y `function.surjective f` son proposiciones (verdaderas o falsas dependiendo de `f`).

-/

/- Si abrimos el `namespace` "function", no es necesario escribir `function.`; podemos escribir
`injective f` y `surjective f`. -/

open function

/- Fijamos tres tipos `X`, `Y`, `Z` (podemos pensar en ellos como conjuntos) y dos funciones
`f : X → Y`, `g : Y → Z`. -/
variables {X Y Z : Type} {f : X → Y} {g : Y → Z}

/- Sean `a,b,x` elementos de `X`, `y ∈ Y`, `z ∈ Z`. -/
variables (a b x : X) (y : Y) (z : Z)

-- Abrimos un `namespace` propio para evitar colisiones de nomenclatura con lemas existentes.
namespace funciones

/-!
# Funciones inyectivas
-/

/- Comprobamos que en Lean la definición de función sobreyectiva es la esperada. -/
lemma injective_def : injective f ↔ ∀ a b : X, f a = f b → a = b :=
begin
  refl -- cierto por definición
end

-- La táctica `rw injective_def` cambia `injective f` por su definición.

/- Comprobamos que la función identidad ` id : X → X` está definida como `id(x) = x`. -/
lemma id_eval : id x = x :=
begin
  refl -- cierto por definición
end

-- La composición de funciones se denota mediante `∘`. Por definición, `(g ∘ f) (x) = g(f(x))`.
lemma comp_eval : (g ∘ f) x = g (f x) :=
begin
  refl
end

/- La razón por la que demostramos estos teoremas demostrables "por definición" (`refl`) es que
esto nos permite utilizar la táctica  `rw` para reemplazar términos por su definición. -/

/- Por ejemplo, podemos empezar la siguiente demostración con `rw injective_def`, y más adelante
utilizar `rw id_eval`. -/
example : injective (id : X → X) :=
begin
  rw injective_def,
  sorry
end

/- Si eliminamos la instrucción  `rw injective_def`, nada cambia. El motivo es que esta instrucción
no es realmente necesaria para Lean (ya que sólo está reescribiendo una definición), pero puede
ser útil para la persona escribiendo la demostración. Prueba a hacer lo mismo con `rw id_eval`. -/

/-- La composición de dos funciones inyectivas es inyectiva. -/
lemma injective_comp (hf : injective f) (hg : injective g) : injective (g ∘ f) :=
begin
  sorry,
end

/- Ejercicio -/
example (f : X → Y) (g : Y → Z) :
  injective (g ∘ f) → injective f :=
begin
  sorry
end

/-!

### Funciones sobreyectivas

-/

/- Comprobamos que en Lean la definición de función sobreyectiva es la esperada. -/
lemma surjective_def : surjective f ↔ ∀ y : Y, ∃ x : X, f x = y :=
begin
  refl
end

/-- La función identidad es sobreyectiva. -/
lemma surjective_id : surjective (id : X → X) :=
begin
  sorry,
end

/-- Una composición de funciones sobreyectivas es sobreyectiva. -/
lemma surjective_comp (hf : surjective f) (hg : surjective g) : surjective (g ∘ f) :=
begin
  sorry,
end

/- Ejercicio -/
example (f : X → Y) (g : Y → Z) :
  surjective (g ∘ f) → surjective g :=
begin
  sorry
end

/-!
### Funciones biyectivas
-/

/- En Lean, una función biyectiva es, por definición, una función inyectiva y sobreyectiva. -/
lemma bijective_def : bijective f ↔ injective f ∧ surjective f :=
begin
  refl
end

/-- La función identidad es biyectiva. -/
lemma bijective_id : bijective (id : X → X) :=
begin
  sorry,
end

/-- Una composición de funciones biyectivas es biyectiva. -/
lemma bijective_comp (hf : bijective f) (hg : bijective g) : bijective (g ∘ f) :=
begin
  sorry,
end

end funciones


import Scripts.topology
import Scripts.finite
--===================================--
------ DEFINICIONES DE TOPOLOGÍA ------
--===================================--

-------------------------------
--- Interior de un conjunto ---
-------------------------------

def interior [topological_space α] (s : Set α) : Set α :=
  ⋃₀ (fun o => o ⊆ s ∧ is_open o)

-- El interior de un conjunto es abierto
theorem is_open_interior [T : topological_space α] (s : Set α) : is_open (interior s) :=
  T.is_open_union (fun o => o ⊆ s ∧ is_open o) (fun _ h => h.right)

-- Un punto 'a' es interior a un conjunto 's' si existe un abierto 'g'
-- que contenga a 'a' contenido en 's'
def interior_point [topological_space α] (a : α) (s : Set α): Prop :=
  ∃ (g : Set α), g ⊆ s ∧ is_open g ∧ a ∈ g

-- Un punto es interior a un conjunto si y solo si está en su interior
theorem interior_point_iff_in_interior [topological_space α] (s : Set α) (a : α) :
  interior_point a s ↔ a ∈ (interior s) :=
    ⟨fun h => h.elim (fun g ⟨h_subgs,h_openg,h_ag⟩ => Exists.intro g ⟨⟨h_subgs,h_openg⟩,h_ag⟩),
    fun h => h.elim (fun g ⟨⟨h_subgs,h_openg⟩,h_ag⟩ => Exists.intro g ⟨h_subgs,h_openg,h_ag⟩)⟩

----------------------------
--- Sucesión convergente ---
----------------------------

def sequence_limit [topological_space α] (x : Nat → α) (x₀ : α) : Prop :=
  ∀ {g : Set α}, is_open g → x₀ ∈ g → (∃ (n : Nat), ∀ m : Nat, n ≤ m → (x m) ∈ g )

-- El límite de una sucesión convergente contenida en un cerrado
-- pertenece al cerrado
theorem convergent_limit_in_closed [topological_space α]
        (c : Set α) (h_cc : is_closed c)
        (x : Nat → α) (h_cx : ∀ n : Nat, (x n) ∈ c)
        (x₀ : α) (h_xconv : sequence_limit x x₀) :
        x₀ ∈ c :=
  Classical.byContradiction
    (fun hncx0 =>
      have h_exn : (∃ (n : Nat), ∀ m : Nat, n ≤ m → x m ∈ (-c)) := h_xconv h_cc hncx0
      h_exn.elim (fun n hn =>
        have h_ccxn : x n ∈ (-c) := hn n (Nat.le_refl n)
        have h_cn : x n ∈ c := h_cx n
        (h_ccxn h_cn).elim))

---------------------------
--- Funciones continuas ---
---------------------------

def continuous [topological_space α] [topological_space β] (f : α → β) : Prop :=
  ∀ s : Set β, is_open s → is_open (f⁻¹(s))

structure continuous_functions (α : Type u) (β : Type v)
  [topological_space α] [topological_space β] where
    function : α → β
    is_continuous : continuous function

notation "C(" α ","  β ")" => continuous_functions α β

theorem continuous_functions_eq
        [topological_space α] [topological_space β]
        {f g : C(α,β)} :
        f.function = g.function → f = g :=
  fun heq => show (⟨f.function, f.is_continuous⟩ : C(α,β)) =
                   ⟨g.function, g.is_continuous⟩
             from by simp [heq]

------------------------------------------
--- Topología generada por una subbase ---
------------------------------------------

inductive generated_topology_open (B : Set (Set (α))) : Set α → Prop where
  | basic : (b : Set α) → B b → generated_topology_open B b
  | univ : generated_topology_open B total
  | inter : (b1 b2 : Set α) → generated_topology_open B b1 →
      generated_topology_open B b2 → generated_topology_open B (b1 ∩ b2)
  | unionF : (Fb : Set (Set α)) → (∀ b, Fb b → generated_topology_open B b) →
      generated_topology_open B (⋃₀ Fb)

def generated_topology (B : Set (Set α)) : topological_space α :=
  {
    is_open := generated_topology_open B,
    is_open_univ := generated_topology_open.univ,
    is_open_inter := generated_topology_open.inter,
    is_open_union := generated_topology_open.unionF
  }

-------------------------------------
--- Compacidad por recubrimientos ---
-------------------------------------

-- Recubrimientos
def cover (R : Set (Set α)) (s : Set α) : Prop := s ⊆ ⋃₀ R

-- Compactos
def compact [topological_space α] (k : Set α) : Prop :=
  ∀ R, cover R k → (∀ s, R s → is_open s) →
    (∃ R', R' ⊆ R ∧ is_finite R' ∧  cover R' k)

-- El vacío es compacto
theorem compact_empty [topological_space α] : compact (∅ : Set α) :=
  fun R _ _ =>
    let R' : Set (Set α) := ∅
    Exists.intro R' ⟨empty_subset R, is_finite_empty, empty_subset (⋃₀ R')⟩

-- Los conjuntos unitarios son compactos
theorem compact_singleton [topological_space α] (a : α) : compact { a } :=
  fun R hRcover _ =>
    have huniona : a ∈ ⋃₀ R := hRcover rfl
    huniona.elim (fun s ⟨hRs,hsa⟩ =>
      let R' : Set (Set α) := { s }
      have hsub : R' ⊆ R := (@in_set_iff_singleton_subset _ R s).mp hRs
      have hfinite : is_finite R' := is_finite_singleton s
      have hcover : cover R' { a } := unionF_singleton s ▸ (in_set_iff_singleton_subset.mp hsa)
      Exists.intro R' ⟨hsub, hfinite, hcover⟩)

-- La unión finita de compactos es compacto
theorem compact_union [topological_space α] {k k' : Set α} : compact k → compact k' → compact (k ∪ k') :=
  fun hk hk' => fun R hRcover hRabto =>
    have hRcoverk : cover R k :=  (iff_union_of_subsets.mp hRcover).left
    have hRcoverk' : cover R k' :=  (iff_union_of_subsets.mp hRcover).right
    (hk R hRcoverk hRabto).elim (fun Rk ⟨hsubRk, hfinitek, hRkcover⟩ =>
      (hk' R hRcoverk' hRabto).elim (fun Rk' ⟨hsubRk', hfinitek', hRk'cover⟩ =>
        let R' := Rk ∪ Rk'
        have hsub : R' ⊆ R := iff_union_of_subsets.mpr ⟨hsubRk,hsubRk'⟩
        have hfinite : is_finite R' := is_finite_union hfinitek hfinitek'
        have hcoverk : k ⊆ ⋃₀ R' := eq_unionF_of_union ▸ (subset_of_union (⋃₀ Rk') hRkcover).left
        have hcoverk' : k' ⊆ ⋃₀ R' := eq_unionF_of_union ▸ (subset_of_union (⋃₀ Rk) hRk'cover).right
        have hcover : cover R' (k ∪ k') := iff_union_of_subsets.mpr ⟨hcoverk, hcoverk'⟩
        Exists.intro R' ⟨hsub, hfinite, hcover⟩))

-- Los conjuntos finitos son compactos

theorem compact_finite_alt [topological_space α] {s : Set α} : is_finite_alt s → compact s :=
  fun h =>
    match h with
    | is_finite_alt.empty => compact_empty
    | is_finite_alt.singleton _ => compact_singleton _
    | is_finite_alt.union ha hb => compact_union (compact_finite_alt ha) (compact_finite_alt hb)

theorem compact_finite [topological_space α] {s : Set α} : is_finite s → compact s :=
  fun h => compact_finite_alt (is_finite_alt_of_is_finite h)

----------------------------
--- Espacio de Hausdorff ---
----------------------------

def hausdorff_space (α : Type u) [topological_space α] : Prop :=
  ∀ x y : α, x ≠ y → ∃ (u v : Set α), is_open u ∧ is_open v ∧ x ∈ u ∧ y ∈ v ∧ disjoint u v

----------------------------------
--- Topología compacto-abierta ---
----------------------------------

def compact_open_subbasis [topological_space α] [topological_space β] :
  Set (Set C(α,β)) :=
    fun (s : Set C(α,β)) => ∃ (k : Set α) (_ : compact k) (u : Set β) (_ : is_open u),
      (∀ f : C(α,β), f ∈ s →  Im(f.function,k) ⊆ u)

instance compact_open_topology [topological_space α] [topological_space β] :
  topological_space C(α,β) := generated_topology compact_open_subbasis

-- Si β es un espacio de Hausdorff, entonces C(α,β) con la topología compacto-abierta, también
theorem compacto_abierta_hausdorff [T : topological_space α] [topological_space β] (h : hausdorff_space β) :
  @hausdorff_space C(α,β) compact_open_topology :=
  let B : Set (Set C(α,β)) := compact_open_subbasis
  (fun f g hneq =>
    have hneqfun : f.function ≠ g.function := contrapositive continuous_functions_eq hneq
    (funext_converse hneqfun).elim
    (fun x hnfxgx =>
      let fx := f.function x
      let gx := g.function x
      (h fx gx hnfxgx).elim
      (fun u hexv => hexv.elim
      (fun v ⟨hopenu,hopenv,hufx,hvgx,hdisj⟩ =>
        let k := { x }
        have hcompact : compact k := compact_singleton x
        let uf : Set C(α,β) := fun t => Im(t.function, k) ⊆ u
        have huff : f ∈ uf := image_singleton f.function x ▸ in_set_iff_singleton_subset.mp hufx
        let ug : Set C(α,β) := fun t => Im(t.function, k) ⊆ v
        have hugg : g ∈ ug := image_singleton g.function x ▸ in_set_iff_singleton_subset.mp hvgx
        have hBuf : uf ∈ B := ⟨k,hcompact,u,hopenu, fun _ huft => huft⟩
        have hBug : ug ∈ B := ⟨k,hcompact,v,hopenv, fun _ hugt => hugt⟩
        have hopenuf : is_open uf := generated_topology_open.basic uf hBuf
        have hopenug : is_open ug := generated_topology_open.basic ug hBug
        have hdisj2 : disjoint uf ug :=
          fun {t} hinter =>
            have hnotempty := image_of_nonempty t.function (nonempty_singleton x)
            have hempty := empty_of_subset_of_disjoints hdisj hinter.left hinter.right
            hnotempty hempty
        ⟨uf,ug, hopenuf, hopenug, huff, hugg, hdisj2⟩))))


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

open MyNat

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

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

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

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

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

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

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

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

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

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

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

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

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


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

#check set

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

#check set nat

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

#reduce set nat

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

def e: set nat := { }

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

def e': set nat := ∅

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

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

#reduce e

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

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

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

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

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

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

def x: set nat := { 1 }

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

#reduce x

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

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

#reduce x'

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

-- SET MEMBERSHIP

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

#reduce 1 ∈ x

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

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

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

-- ANOTHER EXAMPLE

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

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

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

#reduce y

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

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

#reduce y

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

-- SET UNION

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

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

def u := y ∪ z

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

#reduce u

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

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

#reduce u

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

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

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

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

-- SET INTERSECTION

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

def w := y ∩ z

#reduce w

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

-- SET DIFFERENCE

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

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

#reduce y \ z

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

-- SUMMARY SO FAR

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

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

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

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

-- PART II

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

-- Subset

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

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

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

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

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

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

theorem foo : x ⊂ y := _


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

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

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

If we define

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

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

`{ n : ℕ | is_even n }`

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

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

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

/-

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

-/

open set

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

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

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

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

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

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


/-
# References

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

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

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

namespace Geometric

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

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

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

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

end Geometric


import data.real.basic

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

In the following exercises we will use the lemma:

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

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

In order to announce an intermediate statement we use:

  have my_name : my statement,

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

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

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

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

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

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

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

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

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

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

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

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

/- Let's do a variant. -/

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

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

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

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

Let's use those lemmas by hand for now.

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

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

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

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

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

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

Let's do that using the lemma

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

But this can be rephrased as

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

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

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

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

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

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

Let's practise using `intros`. -/

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/-
Final exercise

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

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

  dvd_refl (a : ℕ) : a ∣ a

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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


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

-/

namespace nat
variables {m n : ℕ}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

end nat


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

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

/-

# Smooth functions

-/

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

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

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

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

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

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

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


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

-- 3. Infinitely Many Primes

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

-- 3.1. Equality

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

  ``rw``

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

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

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

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

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

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

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

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

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

-- 3.1.1. Surjective functions

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

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

``unfold``

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

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

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

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

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

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

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

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

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

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

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

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

``have``

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

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

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

You'll need the following theorem from the library:

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

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

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

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

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

-- 3.3. Infinitely many primes

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

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

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

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

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

noncomputable theory
open_locale classical

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

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

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

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

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

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

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


import tactic.interval_cases
import data.real.basic

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

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

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

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

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


/- LoVe Exercise 2: Tactical Proofs -/

import .love02_tactical_proofs_demo

namespace LoVe

/- Question 1: Connectives and Quantifiers -/

/- 1.1. Carry out the following proofs using basic tactics. -/

lemma I (a : Prop) :
  a → a :=
sorry

lemma K (a b : Prop) :
  a → b → b :=
sorry

lemma C (a b c : Prop) :
  (a → b → c) → b → a → c :=
sorry

lemma proj_1st (a : Prop) :
  a → a → a :=
sorry

-- please give a different answer than for `proj_1st`
lemma proj_2nd (a : Prop) :
  a → a → a :=
sorry

lemma some_nonsense (a b c : Prop) :
  (a → b → c) → a → (a → c) → b → c :=
sorry

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

lemma contrapositive (a b : Prop) :
  (a → b) → ¬ b → ¬ a :=
sorry

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

lemma forall_and {α : Type} (p q : α → Prop) :
  (∀x, p x ∧ q x) ↔ (∀x, p x) ∧ (∀x, q x) :=
sorry

/- Question 2: Natural Numbers -/

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

#check mul

lemma mul_zero (n : ℕ) :
  mul 0 n = 0 :=
sorry

lemma mul_succ (m n : ℕ) :
  mul (nat.succ m) n = add (mul m n) n :=
sorry

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

lemma mul_comm (m n : ℕ) :
  mul m n = mul n m :=
sorry

lemma mul_assoc (l m n : ℕ) :
  mul (mul l m) n = mul l (mul m n) :=
:= sorry

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

lemma add_mul (l m n : ℕ) : mul (add l m) n = add (mul n l) (mul n m) :=
sorry

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

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

def excluded_middle := ∀a : Prop, a ∨ ¬ a
def peirce := ∀a b : Prop, ((a → b) → a) → a
def double_negation := ∀a : Prop, ¬¬ a → a

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

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

Hint: You will need `or.elim` and `false.elim`. -/

lemma peirce_of_em : excluded_middle → peirce :=
sorry

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

Hint: Try instantiating `b` with `false` in Peirce's law. -/

lemma dn_of_peirce : peirce → double_negation :=
sorry

/- We leave the missing implication for the homework: -/

namespace sorry_lemmas

lemma em_of_dn :
  double_negation → excluded_middle :=
sorry

end sorry_lemmas

end LoVe


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

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