/- NATURAL NUMBERS

We saw the definition of the natural numbers `Nat` and the addition on them on the slides.
Since we want to define it ourselves and not use Lean's built-in version, we will call ours `Nat'`.
 -/
inductive Nat' : Type
  | zero : Nat'
  | succ : Nat' → Nat'
deriving Repr

open Nat'

def add (m n : Nat') : Nat' :=
  match n with
  | zero   => m
  | succ n => succ (add m n)

-- Let us now try to recursively define multiplication on the `Nat'`.
def mul (m n : Nat') : Nat' :=
  match n with
  | zero => zero
  | succ n => add m (mul m n)

-- Next, check your definition of `mul` by evaluating a few examples. This can be done with the
-- command `#eval`.

#eval mul (Nat'.succ (Nat'.succ Nat'.zero)) (Nat'.succ (Nat'.succ Nat'.zero))

-- Let's define `≤` as an inductive predicate on `Nat'`s!
inductive LE' : Nat' → Nat' → Prop where
  | refl (n : Nat') : LE' n n
  -- TODO: We're off to a good start since `≤` is certainly reflexive, but something is still
  -- missing (or else we'd just redefine `Eq` for `Nat'`s). Can you think of one more constructor
  -- that makes it so `LE' n m` holds whenever we would intuitively expect `n ≤ m` to hold?
  -- hint: it remains to be shown that `LE' n m` should hold when `m` is *greater* than `n`, so
  -- the new constructor should probably involve `Nat'`'s `succ` constructor to reach those greater numbers.
  -- hint: it should be an *inductive* case, meaning using another `LE'` application as an assumption
  -- note: `LE'` could also be defined in terms of `add`, but that makes working with it awkward,
  -- so let's not do that.
  | succ : LE' n m -> LE' n (succ m)

-- Now let's prove some things about `LE'`. But first we'll give it the standard notation.
infix:50 " ≤ " => LE'

example (n : Nat') : n ≤ succ (succ n) := LE'.succ (LE'.succ (LE'.refl _))

-- This one is a bit harder: we will need induction!
-- As described on the slides, induction is just a special case of recursion
theorem le_add (m n : Nat') : m ≤ add m n :=
  match n with
  -- This is the base case
  | zero => LE'.refl _
  -- This is the inductive case. You probably want to use `le_add m n` (the inductive hypothesis) somewhere inside of it!
  | succ n =>
    -- Lean automatically converts `add m (succ n)` to `succ (add m n)` for us when necessary, but it can help
    -- to make the conversion explicit. `show` simply lets us restate the goal, using any definitionally equal type.
    show m ≤ succ (add m n) from
    LE'.succ (le_add m n)

#check @Eq.subst

-- Now try proving this theorem on `add` using the same induction scheme
theorem zero_add (n : Nat') : add zero n = add n zero :=
  match n with
  | zero => rfl
  | succ m => -- show add zero (succ n) = add (succ n) zero
      show succ (add zero m) = succ (add m zero) from
      (zero_add m) ▸ rfl

/-
LISTS

The type `List α` of lists on a Type α is defined to be the type on the constructors
`nil : List α`
and
`cons : α → List α → List α`.

We can use `[]` as a notation for `nil` and `x :: xs` as a notation for `cons x xs`.
-/
open List

-- Let's define something we can't in most other functional languages:
-- a function returning the first element of a list given a proof that is is non-empty! (`xs ≠ []` is shorthand for `¬ (xs = [])`)
def hd (xs : List α) : xs ≠ [] → α := fun _ =>
  match xs with
  | cons x _ => x
-- Did you notice that `hd` is a dependent function? The *type/proposition* of the second parameter depends on the *value* of the first.
-- We can write `hd`s type as `∀ (xs : List α), xs ≠ [] → α` or, equivalently, `(xs : List α) → xs ≠ [] → α`.

/-
TREES

Define a type `Tree α` of binary trees with node labels of type `α`. Each tree is either a labelled leaf or a labelled inner node with two trees attached to it.
 -/

inductive Tree (α : Type) : Type where
  | leaf : α -> Tree α
  | branch : Tree α -> α -> Tree α -> Tree α

open Tree

-- Now, let us define the depth and the size of a tree. You can use the function `Nat.max` to get the maximum of two natural numbers. The depth of a leaf is 1.
def depth (t : Tree α) : Nat :=
  match t with
  | leaf _ => 1
  | branch l _ r => Nat.max (depth l) (depth r) + 1

def size (t : Tree α) : Nat :=
  match t with
  | leaf _ => 1
  | branch l _ r => (size l) + (size r) + 1

-- We can turn a tree into a list by traversing it in various ways, depending on whether we add the root
-- before its subtrees (preOrder), between its subtrees (inOrder) or after its subtrees (postOrder).
-- Define preOrder, inOrder, and postOrder as functions `Tree α → List α`.
def preOrder (t : Tree α) : List α :=
  match t with
  | leaf v => [v]
  | branch l v r => v :: preOrder l ++ preOrder r

def inOrder (t : Tree α) : List α :=
  match t with
  | leaf v => [v]
  | branch l v r => preOrder l ++ [v] ++ preOrder r

def postOrder (t : Tree α) : List α :=
    match t with
  | leaf v => [v]
  | branch l v r => preOrder l ++ preOrder r ++ [v]

-- Define a function which returns the mirror image of a given tree.
def mirror (t : Tree α) : Tree α := match t with
  | leaf v => leaf v
  | branch l v r => branch (mirror r) v (mirror l)

-- Now we prove two facts about this function:
-- First, we prove that it is involutive (mirroring a tree twice returns the original tree).
-- Then, we prove that the mirror image of two trees is equal, if and only if the trees themselves are.

-- Useful lemmas, if you don't want to use ▸ as much (but it is also just as doable with some `have`s and ▸):
#check @Eq.trans
#check @Eq.symm
#check @congrArg

theorem mirror_involutive (t : Tree α) : mirror (mirror t) = t :=
  match t with
  | leaf _ => rfl
  | branch l _ r => show branch .. = _ from -- .. = _ _ _
    Eq.symm (mirror_involutive l) ▸ Eq.symm (mirror_involutive r) ▸ rfl

-- hint: try using `mirror_involutive`
theorem mirror_eq (s t : Tree α) : mirror s = mirror t ↔ s = t :=
  Iff.intro
  (fun h =>
    have mmsmmt : mirror (mirror s) = mirror (mirror t) := h ▸ rfl
    mirror_involutive s ▸ mirror_involutive t ▸ mmsmmt)
  (fun h => congrArg mirror h)

/- STRUCTURES -/

-- Define the structure `Semigroup α` for a semigroup on a type `α`.
-- Reminder: A semigroup is an algebraic structure with an associative binary operation `mul`.
structure Semigroup (α : Type) where
  mul : α -> α -> α
  assoc : mul (mul a b) c = mul a (mul b c)

-- Now extend the structure to one for a monoid on α.
-- Reminder : A monoid is a semigroup with an element which acts as the left and right identity on `mul`.
structure Monoid (α : Type) extends Semigroup α where
  e : α -- element
  e_mul : mul e a = e -- left id in mul
  mul_e : mul a e = e -- right id in mul

-- Now try to instantiate the type `Nat'` as a monoid.
-- Leave out the three proofs (associativity, left and right inverse), we'll learn better ways to write such proofs next week.
def Nat'Monoid : Monoid Nat' :=
  {
    mul := mul,
    assoc := sorry,
    e := succ zero,
    e_mul := sorry,
    mul_e := sorry
  }


/-
# Propositions as Types.
In this file, we shall be introduced to the paradigm of
*Propositions as Types* and how they are handled in Lean.
We shall also use this oppurtunity to get familiar with
Lean's syntax.

Acknowledgements : Heavily borrowed from Jalex Stark and
Apurva Nakade's tutorial.

Import commands go right at the top of a Lean file.
-/

import data.nat.pow
import data.nat.prime

/-
  Before that, let us delve into type theory.
 *All* objects in Lean have a *type*.
  Things which have a type are called *terms*.
  This simple stratification is more powerful than you may first think.
  You can query the type of a term using `#check`.

  Inspect the following commands:
-/

#check 1
#check ℕ -- type using \nat
#check Type
#check Type 1 -- the number indicates a *universe level*
#check and
#check tt
#check ff

/-
  Here is an important type. The type of all propositions.
  Roughly speaking, a proposition is something which can be
  subjected to a proof.
-/
#check Prop

/-
In type theory, "a has type A" is written as "a:A".
You can read this in two ways:

  (1) a belongs to the set A
  (2) a is a proof of A

Here are some silly propositions. All of these are terms of type Prop.
-/

#check 1 + 1 = 2 -- equality
#check ∀ (x : ℕ), x = 10
#check ∃ (k : ℕ), 10 = 2*k
#check ∀ (x : bool), x = tt ∨ x = ff
#check ∀ (x y : ℕ), x = y → y = x
#check ∀ (x y z n : ℕ), n > 2 → (x*y*z ≠ 0) → (x^n + y^n = z^n)
#check ∃ k : ℕ, 10 = 2*k -- existential quantification
#check ∀ (x : bool), x = tt ∨ x = ff -- disjunction
#check ∀ (x : bool), x = tt ∧ x = ff -- conjunction
#check ∀ (x y : ℕ), x = y → y = x -- implication
#check ¬(1 = 0) -- negation \not

-- here is a not so silly proposition
-- (XX : State Fermat's Last Theorem/Goldbach)

/-
As we've said, propositions can be subjected to *(dis)proof*.
Here is how this is done in Lean: since every term has a type,
and Propositions are types too, we should read their terms as
their proofs.

This points to (2) above.
To repeat ourselves again, we read `p : 1+1 = 2` as `p` is a
proof of the proposition that 1+1 = 2.
Let us check if our propositions are provable or not.

We will try to prove more of our silly props later.

XX : First proof, syntax explanation, term mode/tactic mode.
-/

theorem one_plus_one_two : 1+1 = 1+1 :=
begin
  refl,
end

theorem ten_is_even :  ∃ k m : ℕ, m = 2*k :=
begin
  use 5,
  use 10,
  refl,
end

theorem bool_thm :  ∀ (x : bool), x = tt ∨ x = ff :=
begin
  assume x,
  cases x,
  {
    right,
    refl,
  },
  {
    sorry,
  }
end

/-
# Tactics

The terms inside the `begin ... end` block you see here are
called *tactics*.

As soon as you write out a valid lemma and write a `begin ... end`
below it, Lean wakes up and tries to solve the goal you stated, and
complains if it can't.

You look at it's complaints and help it along, until it
complains no more. This is the *interactive* part of interactive
theorem proving.

The next incantation defines two abstract propositions and adds
it to the context henceforth.

-/

namespace tactics

variables (p q : Prop)

/-
``exact``

  If ``p`` is the target of the current goal and
  ``hp`` is a term of type ``p``, then
  ``exact hp,`` will close the goal.

``intro``

  If the target of the current goal is a function ``p → q``, then
  ``intro hp,`` will produce a hypothesis
  ``hp : P`` and change the target to  ``q``.

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

-/

theorem tautology' (hp : p) : p :=
begin
  exact hp,
end

theorem tautology'' : p → p :=
begin
  assume hp,
  exact hp,
end

example : (p → (q → p)) :=
begin
  assume hp,
  assume hq,
  exact hp,
end

-- Can you find two different ways of proving the following?
example (p q : Prop) : ((q → p) → (q → p)) :=
begin
  sorry,
end

/-

``have``

  If ``f`` is a term of type ``p → q`` and
  ``hp`` is a term of type ``p``, then
  ``have hq := f(hp),`` creates the hypothesis ``hq : q`` .

``apply``

  If the target of the current goal is ``q`` and
  ``f`` is a term of type ``p → q``, then
  ``apply f,`` changes target to ``p``.

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

-/

example (p q r : Prop) (hp : p) (f : p → q) (g : q → r) : r :=
begin
  have hq : q,
  {
    exact (f hp),
  },
  exact (g hq),
end

example (p q r : Prop) (hp : p) (f : p → q) (g : q → r) : r :=
begin
  apply g,
  apply f,
  exact hp,
end

theorem quux (P Q R S T U: Type)
(hpq : P → Q)
(hqr : Q → R)
(hrs : R → S)
(hst : S → T)
(htu : T → U)
: P → U :=
begin
  assume hp,
  apply htu,
  apply hst,
  apply hrs,
  apply hqr,
  apply hpq,
  exact hp,
end

#print quux

/-

``cases``

  ``cases`` is a general tactic that breaks up complicated terms.
  If ``hpq`` is a term of type ``P ∧ Q`` or ``P ∨ Q`` or ``P ↔ Q``,
  then use ``cases hpq with hp hq,``.

``split``

  If the target of the current goal is ``P ∧ Q`` or ``P ↔ Q``,
  then use ``split,``.

``left``/``right``

  If the target of the current goal is ``P ∨ Q``, then use
  either ``left,`` or ``right,`` (choose wisely).

``exfalso``

  Changes the target of the current goal to ``false``.

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

-/

example (P Q : Prop) : P ∧ Q → Q ∧ P :=
begin
  assume hpq,
  split,
  {
    cases hpq with hp hq,
    exact hq,
  },
  {
    cases hpq with hp hq,
    exact hp,
  }
end

example (P Q : Prop) : P ∧ Q → Q ∧ P :=
begin
  assume hpq, -- this is a comment in Lean
  split,
  all_goals {
    cases hpq with hp hq,
    assumption,
  },
end

/- This is another comment. -/
example (P Q : Prop) : P ∨ Q → Q ∨ P :=
begin
  assume hpq,
  cases hpq,
  {
    right,
    exact hpq,
  },
  {
    left,
    exact hpq,
  }
end

#check @false.elim

example (P Q R : Prop) : P ∧ false ↔ false :=
begin
  split,
  {
    assume hpf,
    cases hpf with hp hf,
    exact hf,
  },
  {
    assume hf,
    exfalso,
    exact hf,
  }
end

theorem principle_of_explosion (P Q : Prop) : P ∧ ¬ P → Q :=
begin
  sorry,
end

/-

``exfalso``

  Changes the target of the current goal to ``false``.

``push_neg``

  ``push_neg,`` simplifies negations in the target.
  You can push negations across a hypothesis ``hp : P`` using
  ``push_neg at hp,``.

``contrapose!``

  If the target of the current goal is  ``P → Q``,
  then ``contrapose!,`` changes the target to  ``¬ Q → ¬ P``.

  If the target of the current goal is ``Q`` and
  one of the hypotheses is ``hp : P``, then
  ``contrapose! hp,`` changes the target to  ``¬ P`` and
  changes the hypothesis to ``hp : ¬ Q``.

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

-/

theorem not_not_self_imp_self (P : Prop) : ¬ ¬ P → P:=
begin
  sorry,
end

theorem contrapositive_converse (P Q : Prop) : (¬Q → ¬P) → (P → Q) :=
begin
  sorry,
end

example (P : Prop) : ¬ P → ¬ ¬ ¬ P :=
begin
  sorry,
end

theorem principle_of_explosion' (P Q : Prop) : P → (¬ P → Q) :=
begin
  sorry,
end

end tactics

/-
  Using the tactics above, let's try to prove some of the
  silly propositions we wrote out before:

  ∀ (x : ℕ), x = 10 -- universal quantification
  ∃ k : ℕ, 10 = 2*k -- existential quantification
  ∀ (x : bool), x = tt ∨ x = ff -- disjunction
  ∀ (x : bool), x = tt ∧ x = ff -- conjunction
  ∀ (x y : ℕ), x = y → y = x -- implication
  ¬(1 = 0) -- negation

-/
example : ∀ (x : bool), x = tt ∨ x = ff :=
begin
  intros x, -- pick an arbitrary boolean x
  cases x, -- prove the goal two cases: when x is true and when x is false.
  {
    -- we are in the false branch now
    right,
    refl,
  },
  {
    -- we are in the true branch now
    left,
    refl,
  }
end

/-
Lemmas and theorems can be named so that they can be referred to in future proofs.
-/

lemma ten_even : ∃ k : ℕ, 10 = 2*k :=
begin
  use 5,
  refl,
end

#check ten_even -- ten_even is a proof that `∃ k : ℕ, 10 = 2*k`
#check ∃ k : ℕ, 10 = 2*k -- which is a proposition

/-
Prove these:
-/
-- Use rw.
lemma eq_symm : ∀ (x y : ℕ), x = y → y = x :=
begin
  assume x y Hxy,
  rewrite Hxy,
end

-- XX: Show what proving a negation means. Use injection.
lemma one_ne_zero' : ¬(1 = 0) :=
begin
  assume onezero,
  injection onezero,
end

/-
# Exercises

Using the tactics from above, try to prove as many of the
following as you can:
-/
namespace exercises

variables p q r : Prop

-- commutativity of ∧ and ∨
example : p ∧ q ↔ q ∧ p := sorry
example : p ∨ q ↔ q ∨ p := sorry

-- associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := sorry
example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) := sorry

-- distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := sorry
example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := sorry

-- other properties
example : (p → (q → r)) ↔ (p ∧ q → r) := sorry
example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) := sorry
example : ¬(p ∨ q) ↔ ¬p ∧ ¬q := sorry
example : ¬p ∨ ¬q → ¬(p ∧ q) := sorry
example : ¬(p ∧ ¬p) := sorry
example : p ∧ ¬q → ¬(p → q) := sorry
example : ¬p → (p → q) := sorry
example : (¬p ∨ q) → (p → q) := sorry
example : p ∨ false ↔ p := sorry
example : p ∧ false ↔ false := sorry
example : (p → q) → (¬q → ¬p) := sorry

end exercises


/-
# References

1. Avigad, Jeremy. ‘Theorem Proving in Lean’, n.d.
-/

-- Exercise 1
--
-- Try defining other operations on the natural numbers, such as multiplication,
-- the predecessor function (with `pred 0 = 0`), truncated subtraction (with
-- `n - m = 0` when `m` is greater than or equal to `n`), and exponentiation.
-- Then try proving some of their basic properties, building on the theorems we
-- have already proved.
--
-- Since many of these are already defined in Lean’s core library, you should
-- work within a namespace named hide, or something like that, in order to avoid
-- name clashes.
namespace ex1

-- As defined in the book.
inductive Nat where
  | zero : Nat
  | succ : Nat → Nat

namespace Nat

def add (m n : Nat) : Nat :=
  match n with
  | Nat.zero => m
  | Nat.succ n => Nat.succ (add m n)

instance : Add Nat where
  add := add

theorem add_zero (m : Nat) : m + Nat.zero = m :=
  rfl

theorem add_succ (m n : Nat) : m + n.succ = (m + n).succ :=
  rfl

theorem zero_add (n : Nat) : Nat.zero + n = n :=
Nat.recOn (motive := fun x => Nat.zero + x = x)
  n
  (show Nat.zero + Nat.zero = Nat.zero from rfl)
  (fun (n : Nat) (ih : Nat.zero + n = n) =>
    show Nat.zero + n.succ = n.succ from
    calc
      Nat.zero + n.succ = (Nat.zero + n).succ := add_succ Nat.zero n
                      _ = n.succ := by rw [ih])

-- Additional definitions.
def mul (m n : Nat) : Nat :=
  match n with
  | Nat.zero => Nat.zero
  | Nat.succ n => m + mul m n

def pred (n : Nat) : Nat :=
  match n with
  | Nat.zero => Nat.zero
  | Nat.succ n => n

def sub (m n : Nat) : Nat :=
  match n with
  | Nat.zero => m
  | Nat.succ n => sub (pred m) n

def exp (m n : Nat) : Nat :=
  match n with
  | Nat.zero => Nat.zero.succ
  | Nat.succ n => mul m (exp m n)

end Nat

end ex1

-- Exercise 2
--
-- Define some operations on lists, like a `length` function or the `reverse`
-- function. Prove some properties, such as the following:
--
-- a. `length (s ++ t) = length s + length t`
-- b. `length (reverse t) = length t`
-- c. `reverse (reverse t) = t`
namespace ex2

variable {α : Type _}

theorem length_sum (s t : List α)
        : List.length (s ++ t) = List.length s + List.length t :=
  List.recOn s
    (by rw [List.nil_append, List.length, Nat.zero_add])
    (fun hd tl ih => by rw [
      List.length,
      List.cons_append,
      List.length,
      ih,
      Nat.add_assoc,
      Nat.add_comm t.length,
      Nat.add_assoc
    ])

theorem length_inject_anywhere (x : α) (as bs : List α)
        : List.length (as ++ [x] ++ bs) = List.length as + List.length bs + 1 := by
  induction as with
  | nil => simp
  | cons head tail ih => calc
      List.length (head :: tail ++ [x] ++ bs)
          = List.length (tail ++ [x] ++ bs) + 1 := rfl
        _ = List.length tail + List.length bs + 1 + 1 := by rw [ih]
        _ = List.length tail + (List.length bs + 1) + 1 := by rw [Nat.add_assoc (List.length tail)]
        _ = List.length tail + (1 + List.length bs) + 1 := by rw [Nat.add_comm (List.length bs)]
        _ = List.length tail + 1 + List.length bs + 1 := by rw [Nat.add_assoc (List.length tail) 1]
        _ = List.length (head :: tail) + List.length bs + 1 := rfl

theorem list_reverse_aux_append (as bs : List α)
        : List.reverseAux as bs = List.reverse as ++ bs := by
  induction as generalizing bs with
  | nil => rw [List.reverseAux, List.reverse, List.reverseAux, List.nil_append]
  | cons head tail ih => calc
      List.reverseAux (head :: tail) bs
          = List.reverseAux tail (head :: bs) := rfl
        _ = List.reverse tail ++ (head :: bs) := by rw [ih]
        _ = List.reverse tail ++ ([head] ++ bs) := rfl
        _ = List.reverse tail ++ [head] ++ bs := by rw [List.append_assoc]
        _ = List.reverseAux tail [head] ++ bs := by rw [ih]
        _ = List.reverseAux (head :: tail) [] ++ bs := rfl

theorem length_reverse (t : List α)
        : List.length (List.reverse t) = List.length t := by
  induction t with
  | nil => simp
  | cons head tail ih => calc
      List.length (List.reverse (head :: tail))
          = List.length (List.reverseAux tail [head]) := rfl
        _ = List.length (List.reverse tail ++ [head]) := by rw [list_reverse_aux_append]
        _ = List.length (List.reverse tail) + List.length [head] := by simp
        _ = List.length tail + List.length [head] := by rw [ih]
        _ = List.length tail + 1 := rfl
        _ = List.length [] + List.length tail + 1 := by simp
        _ = List.length ([] ++ [head] ++ tail) := by rw [length_inject_anywhere]
        _ = List.length (head :: tail) := rfl

theorem reverse_reverse_aux (as bs : List α)
        : List.reverse (as ++ bs) = List.reverse bs ++ List.reverse as := by
  induction as generalizing bs with
  | nil => simp
  | cons head tail ih => calc
      List.reverse (head :: tail ++ bs)
          = List.reverseAux (head :: tail ++ bs) [] := rfl
        _ = List.reverseAux (tail ++ bs) [head] := rfl
        _ = List.reverse (tail ++ bs) ++ [head] := by rw [list_reverse_aux_append]
        _ = List.reverse bs ++ List.reverse tail ++ [head] := by rw [ih]
        _ = List.reverse bs ++ (List.reverse tail ++ [head]) := by rw [List.append_assoc]
        _ = List.reverse bs ++ (List.reverseAux tail [head]) := by rw [list_reverse_aux_append]
        _ = List.reverse bs ++ (List.reverseAux (head :: tail) []) := rfl
        _ = List.reverse bs ++ List.reverse (head :: tail) := rfl

theorem reverse_reverse (t : List α)
        : List.reverse (List.reverse t) = t := by
  induction t with
  | nil => simp
  | cons head tail ih => calc
      List.reverse (List.reverse (head :: tail))
          = List.reverse (List.reverseAux tail [head]) := rfl
        _ = List.reverse (List.reverse tail ++ [head]) := by rw [list_reverse_aux_append]
        _ = List.reverse [head] ++ List.reverse (List.reverse tail) := by rw [reverse_reverse_aux]
        _ = List.reverse [head] ++ tail := by rw [ih]
        _ = [head] ++ tail := by simp
        _ = head :: tail := rfl

end ex2

-- Exercise 3
--
-- Define an inductive data type consisting of terms built up from the following
-- constructors:
--
-- • `const n`, a constant denoting the natural number `n`
-- • `var n`, a variable, numbered `n`
-- • `plus s t`, denoting the sum of `s` and `t`
-- • `times s t`, denoting the product of `s` and `t`
--
-- Recursively define a function that evaluates any such term with respect to an
-- assignment of values to the variables.
namespace ex3

inductive Foo : Type _
  | const : Nat → Foo
  | var : Nat → Foo
  | plus : Foo → Foo → Foo
  | times : Foo → Foo → Foo

def value_at : Nat → List Nat → Nat
| _,       [] => default
| 0,       vs => List.head! vs
| (i + 1), vs => value_at i (List.tail! vs)

-- The provided "variables" are supplied in a 0-indexed list.
def eval_foo : Foo → List Nat → Nat
  | (Foo.const n)  , _  => n
  | (Foo.var n)    , vs => value_at n vs
  | (Foo.plus m n) , vs => eval_foo m vs + eval_foo n vs
  | (Foo.times m n), vs => eval_foo m vs * eval_foo n vs

end ex3


import analysis.topology.continuity
import analysis.topology.topological_space
import analysis.topology.infinite_sum
import analysis.topology.topological_structures
import analysis.topology.uniform_space

universe u

open set filter lattice classical

#print definition set

#check empty
def empty_set_topology : topological_space empty :=
{
is_open := λ (a : set empty),
true, is_open_univ := begin trivial, end,
is_open_inter := begin intros h1 h2 h3 h4, trivial end,
is_open_sUnion := begin intros h1 h2, trivial end
}
#print univ
#check empty_set_topology.is_open

#print set

definition is_open_sets {α : Type u} (is_open : set α → Prop) :=
  is_open univ ∧ (∀s t, is_open s → is_open t → is_open (s ∩ t)) ∧ (∀s, (∀t∈s, is_open t) → is_open (⋃₀ s))

definition is_to_top {α : Type u} (is_open : set α → Prop) (H : is_open_sets (is_open)) : topological_space α :=
{ is_open := is_open,
  is_open_univ := H.left,
  is_open_inter := H.right.left,
  is_open_sUnion := H.right.right
}

definition top_to_is {α : Type u} (T : topological_space α) : is_open_sets (T.is_open) :=
⟨T.is_open_univ,T.is_open_inter,T.is_open_sUnion⟩

theorem Proposition7_2 {α : Type u} {X : topological_space α} {U : set α} : X.is_open U ↔ ∀ x ∈ U, ∃ Ux : set α, Ux ⊆ U ∧ X.is_open Ux ∧ x ∈ Ux  :=
begin
split,
intro H1,
assume x,
intro H2,
existsi U,
exact ⟨subset.refl U, H1, H2⟩,

intro H1,
let S := {Ux : set α | Ux ⊆ U ∧ X.is_open Ux ∧  ∃ x: α ,  x ∈ Ux},

have Union : U = ⋃₀ S,
apply set.subset.antisymm_iff.2,
split,
swap,
apply set.sUnion_subset,
intros V H2,
rw set.mem_def at H2,
exact H2.left,
assume x,
intro x_in_U,
have H2 : (∃ (Ux : set α), Ux ⊆ U ∧ topological_space.is_open X Ux ∧  x ∈ Ux),
exact H1 x x_in_U,
cases H2 with Ux HUx,
rw set.mem_sUnion_eq,
existsi Ux,
have Ux_in_S : Ux ∈ S,
apply set.mem_def.2,
have : ∃ (x : α), x ∈ Ux,
existsi x,
exact HUx.right.right,
exact ⟨HUx.1, HUx.2.1, this⟩,
existsi Ux_in_S,
exact HUx.2.2,
rw Union,
have : (∀ V∈S, X.is_open V),
intros V H2,
rewrite set.mem_def at H2,
exact H2.2.1,
exact X.is_open_sUnion S this,
end

#print prefix set

--


-- Computational domain
#check nat
#check 1

#check string
#check "hello"

#check bool
#check tt

-- "computational" types of type, Type (aka Sort 1)

-- Logical domain
#check true
#print true

def a_proof_of_nat : ℕ := 8
theorem a_proof_of_true : true := true.intro

#check false

#check (eq 1) 1   -- eq predicate applied to 1 and 2
#check (eq 1)
#check 1 = 1    -- infix notation for eq

#print eq

lemma pf_one_eq_one : 1 = 1 := eq.refl 1
lemma pf_one_eq_two : 1 = 2 := (eq.refl 2)

#check eq.refl 3

#check 3 ∈ { n : ℕ | n % 2 = 1}

#check ∃ (a b c : ℕ), a^2 + b^2 = c^2

-- propositions formalized as logical types: of type, Prop (Sort 0)

-- Values of computational types
#check 1
#check "hello"
#check tt
-- values of computational types are just ordinary data

-- Values of logical types (aka propositions)
#check (eq.refl 1)
-- values of logical types are accepted as proofs

-- eq defined as polymorphic *logical* type
-- takes two values, v1 and v2, of any type, α
-- yields a proposition, eq v1 v2, of type Prop
-- this type has just on constructor, called refl
-- takes argument, a : α, returns a value of type a=a
-- this value is accepted as a *proof*
#print eq

-- exercises involving proofs of equality
def pf1 : 1 = 1 := eq.refl 1
def pf2 : "hello" = "hello" := eq.refl "hello"

#reduce 2 = nat.pred 3

lemma pf3 : 2 = nat.pred 3 := eq.refl (nat.pred 3)     -- vocabulary: minor result

theorem pf4 : tt = tt := eq.refl tt          -- vocabulary: major result
theorem pf5 : 1 = 0 := _                -- StUCk!!!

def sq (n : ℕ) : ℕ := n^2
#reduce (sq 3 = 9)

lemma pf6 : sq 3 = 9 := eq.refl 9           -- equality of reduced values
lemma pf7 : sq 5 = 25 := eq.refl 25

example : sq 3 = 9 := eq.refl _
example : ℕ := 8

-- A logical type that has no values is false.
-- That's why the proposition, "false", in Lean is false.
-- a type with no constructors/values is said to be uninhabited
#print false
theorem pf8 : false := _

-- The proposition, "true", has one constant constructor/value/proof
#print true
theorem pf9 : true := true.intro

/-
What we've seen so far is that some propositions are
formalized as inductive types, the values of which, as
built using available constructors, are taken to be proofs.
Indeed you can represent a wide variety of propositions
and proofs this way.
-/

inductive MaryIsASoftwareEngineer : Prop
| knowsFormalMethods
| crackProgrammer

open MaryIsASoftwareEngineer

def me1 : MaryIsASoftwareEngineer := knowsFormalMethods
theorem me2 : MaryIsASoftwareEngineer := crackProgrammer

/-
Lean implements a logic in which all proofs of a given
proposition are considered to be not only equally good
but actually equal. This treatment of logical values is
fundamentally different from that of computational data
values, where values built by different constructors are
*never* equal. The principle that Lean implements here
is called the principle of "proof irrelevance." To show
that a proposition is true, you just have to exhibit *any*
proof/value of the given proposition/type.
-/

lemma m1 : me1 = me2 := eq.refl me1

theorem bad_news : 0 = 1 := _

/-
Not every logical proposition or predicate is implemented
as an inductive type. In particular, proofs of ∀, →, and ¬
propositions are not elementary data values functions.
-/

-- more to come


/-
Copyright (c) 2021 Manuel Candales. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Manuel Candales
-/
import data.real.basic
import tactic.positivity

/-!
# IMO 2005 Q3
Let `x`, `y` and `z` be positive real numbers such that `xyz ≥ 1`. Prove that:
`(x^5 - x^2)/(x^5 + y^2 + z^2) + (y^5 - y^2)/(y^5 + z^2 + x^2) + (z^5 - z^2)/(z^5 + x^2 + y^2) ≥ 0`

# Solution
The solution by Iurie Boreico from Moldova is presented, which won a special prize during the exam.
The key insight is that `(x^5-x^2)/(x^5+y^2+z^2) ≥ (x^2-y*z)/(x^2+y^2+z^2)`, which is proven by
factoring `(x^5-x^2)/(x^5+y^2+z^2) - (x^5-x^2)/(x^3*(x^2+y^2+z^2))` into a non-negative expression
and then making use of `xyz ≥ 1` to show `(x^5-x^2)/(x^3*(x^2+y^2+z^2)) ≥ (x^2-y*z)/(x^2+y^2+z^2)`.
-/

lemma key_insight (x y z : ℝ) (hx : x > 0) (hy : y > 0) (hz : z > 0) (h : x*y*z ≥ 1) :
  (x^5-x^2)/(x^5+y^2+z^2) ≥ (x^2-y*z)/(x^2+y^2+z^2) :=
begin
  have h₁ : 0 < x^5+y^2+z^2 := by positivity,
  have h₂ : 0 < x^3 := by positivity,
  have h₃ : 0 < x^2+y^2+z^2 := by positivity,
  have h₄ : 0 < x^3*(x^2+y^2+z^2) := by positivity,

  have key : (x^5-x^2)/(x^5+y^2+z^2) - (x^5-x^2)/(x^3*(x^2+y^2+z^2))
           = ((x^3 - 1)^2*x^2*(y^2 + z^2))/((x^5+y^2+z^2)*(x^3*(x^2+y^2+z^2))),
  { field_simp [h₁.ne', h₄.ne'],
    ring },

  have h₅ : ((x^3 - 1)^2*x^2*(y^2 + z^2))/((x^5+y^2+z^2)*(x^3*(x^2+y^2+z^2))) ≥ 0 := by positivity,

  calc  (x^5-x^2)/(x^5+y^2+z^2)
      ≥ (x^5-x^2)/(x^3*(x^2+y^2+z^2)) : by linarith [key, h₅]
  ... ≥ (x^5-x^2*(x*y*z))/(x^3*(x^2+y^2+z^2)) :
        by { refine (div_le_div_right h₄).mpr _, simp,
             exact (le_mul_iff_one_le_right (pow_pos hx 2)).mpr h }
  ... = (x^2-y*z)/(x^2+y^2+z^2) :
        by { field_simp [h₂.ne', h₃.ne'], ring },
end

theorem imo2005_q3 (x y z : ℝ) (hx : x > 0) (hy : y > 0) (hz : z > 0) (h : x*y*z ≥ 1) :
  (x^5-x^2)/(x^5+y^2+z^2) + (y^5-y^2)/(y^5+z^2+x^2) + (z^5-z^2)/(z^5+x^2+y^2) ≥ 0 :=
begin
  calc  (x^5-x^2)/(x^5+y^2+z^2) + (y^5-y^2)/(y^5+z^2+x^2) + (z^5-z^2)/(z^5+x^2+y^2)
      ≥ (x^2-y*z)/(x^2+y^2+z^2) + (y^2-z*x)/(y^2+z^2+x^2) + (z^2-x*y)/(z^2+x^2+y^2) :
        by { linarith [key_insight x y z hx hy hz h,
                       key_insight y z x hy hz hx (by linarith [h]),
                       key_insight z x y hz hx hy (by linarith [h])] }
  ... = 1/2*( (x-y)^2 + (y-z)^2 + (z-x)^2 ) / (x^2+y^2+z^2) :
        by { have h₁ : y^2+z^2+x^2 = x^2+y^2+z^2, ring,
             have h₂ : z^2+x^2+y^2 = x^2+y^2+z^2, ring,
             rw [h₁, h₂], ring }
  ... ≥ 0 :
        by { exact div_nonneg
                (by linarith [sq_nonneg (x-y), sq_nonneg (y-z), sq_nonneg (z-x)])
                (by linarith [sq_nonneg x, sq_nonneg y, sq_nonneg z]) },
end


import data.equiv.basic

/-

data.equiv.basic  is the import which gives you the type `equiv X Y`, the type of
bijections from X to Y.

Here's the definition of equiv from that file.

structure equiv (α : Sort*) (β : Sort*) :=
(to_fun    : α → β)
(inv_fun   : β → α)
(left_inv  : left_inverse inv_fun to_fun)
(right_inv : right_inverse inv_fun to_fun)

To make a term of type `equiv α β` you have to supply a function α → β,
a function β → α, and proofs that both composites are the identity function.

Let's see how to create the bijection ℤ → ℤ sending x to -x.
-/
-- let's prove that x ↦ -x can be extended to
example : equiv ℤ ℤ :=
{ to_fun := λ x, -x, -- this is data
  inv_fun := λ x, -x,  -- this is data
  left_inv := begin -- this is a proof
    change ∀ (x : ℤ), - -x = x, -- that's the question
    exact neg_neg, -- note: I guessed what this function was called.
                   -- If it had been called "lemma 12" I would not have been able to guess
  end,
  right_inv := neg_neg -- another proof, this time in term mode
}

/-
Q1 Define the type of partitions of a type.
A partition of X is a set of subsets of X with the property that each subset
is non-empty and each element of X is in precisely one of the subsets.
NB : this is one of the harder questions here.
-/

structure partition (X : Type*). -- remove `.`  and fill in -- look at def of equiv above

/-
Equivalence relations are in core Lean -- we don't need any imports.
Here's an example: I'll prove that the "always true" relation on a set is
an equivalence relation.

-/

def always_true (X : Type*) : X → X → Prop := λ a b, true

-- and now here's the proof that it's an equivalence relation.

theorem always_true_refl (X) : reflexive (always_true X) :=
begin
  intro x,
  trivial
end

theorem always_true_symm (X) : symmetric (always_true X) :=
begin
  intros a b H,
  trivial
end

theorem always_true_trans (X) : transitive (always_true X) :=
begin
  intros a b c Hab Hbc,
  trivial
end

-- note pointy brackets to make a term of type "A ∧ B ∧ C"
theorem always_true_equiv (X): equivalence (always_true X) :=
⟨always_true_refl X, always_true_symm X, always_true_trans X⟩
-- autocomplete made that proof really easy to type. It's really
-- lucky that I didn't call these lemmas lemma 12, lemma 13 and lemma 14.

-- if X is a type, then `setoid X` is is the type of equivalence relations on X.
-- I'll now make a term of type `setoid X` corresponding to that equivalence
-- relation above.

-- note squiggly brackets and commas at the end of each definition to make a structure
def always_true_setoid (X : Type*) : setoid X :=
{ r := always_true X,
  iseqv := always_true_equiv X }

/-
Q2 : If X is a type then `setoid X` is the type of equivalence relations on X,
and `partitions X` is the type of partitions of X. These two concepts are in
some sort of "canonical" bijection with each other (interesting exercise: make
this statement mathematically meaningful -- I know we all say it, but what
does it *mean*?).

Let's prove that these sets biject with each other by defining
a term of type equiv (setoid X) (partitions X)
-/

variable {X : Type*}

def F (S : setoid X) : partition X := sorry

/-
Q3 : now define a map the other way
-/

def G (P : partition X) : setoid X := sorry

/-
Q4 : now finally prove that the composite of maps in both directions
is the identity
-/

theorem FG_eq_id (P : partition X) : F (G P) = P := sorry
theorem GF_eq_id (S : setoid X) : G (F S) = S := sorry

/-
Q5 : now finally construct the term we seek.
-/

def partitions_biject_with_equivalence_relations :
  equiv (setoid X) (partition X) := sorry


/-
Copyright (c) 2021 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

/-!

# Sets in Lean, sheet 5 : subset (`⊆`), union (`∪`) and intersection (`∩`)

In this sheet we learn how to manipulate `⊆`, `∪` and `∩` in Lean.

Here are some mathematical facts:

`A ⊆ B` is equivalent to `∀ x, x ∈ A → x ∈ B`;
`x ∈ A ∪ B` is equivalent to `x ∈ A ∨ x ∈ B`;
`x ∈ A ∩ B` is equivalent to `x ∈ A ∧ x ∈ B`.

All of these things are true *by definition* in Lean, which means
that you can switch from one to the other with `change`, or you
can just treat something on the left hand side as if it said
what it said on the right hand side.

For example if your goal is `⊢ x ∈ A ∩ B` then you could write
`change x ∈ A ∧ x ∈ B,` to change the goal to `⊢ x ∈ A ∧ x ∈ B`, but you can
also use the `split,` tactic directly, and this will immediately turn the goal
into two goals `⊢ x ∈ A` and `⊢ x ∈ B`.

## New tactics you will need

You don't need to know any new tactics to solve this sheet. I've mentioned
the `change` tactic. You don't have to use it, and if you use it your
proofs will get longer. So in return I'll tell you about two other
tactics, `rcases` and `rintro`, which you don't have to use either
but if you use them they'll make your proofs shorter.

### The `rcases` tactic

`rcases` is a souped-up version of `cases`. It has slightly different
syntax. If you have a hypothesis `h : P ∧ Q` then `cases h with hP hQ,`
and `rcases h with ⟨hP, hQ⟩,` do the same thing. However, if you
have a hypothesis `h : P ∧ Q ∧ R` then Lean interprets it as `P ∧ (Q ∧ R)`
so if you want to destruct it with `cases` you have to do

```
cases h with hP hQR,
cases hQR with hQ hR
```

You can do this all in one go with `rcases h with ⟨hP, hQ, hR⟩,`. The
name `rcases` stands for "recursive cases".

`rcases` can also be used for `or` hypotheses too; here the syntax is that if
we have
```
h : P ∨ Q
```
then `rcases h with (hP | hQ),` will turn our goal into two goals, one with
`hP : P` and the other with `hQ : Q`.

Even better, `rcases` works on `h : false`. Here there are no cases at all!
So `rcases h with ⟨⟩,` solves the goal.

### The `rintro` tactic

It's quite common to find yourself doing `intro` then `cases` or,
more generally, `intro` then `rcases`. The `rintro` tactic does
these both at once! So for example if your goal is

```
⊢ (P ∧ Q) → R
```

then `rintro ⟨hP, hQ⟩,` leaves you at

```
hP : P
hQ : Q
⊢ R
```

i.e. the same as `intro h, cases h with hP hQ,`

You can introduce more than one hypothesis at once -- `rintro` generalises
`intros` as well. For example if your goal is

```
⊢ P → Q ∧ R → S
```

then `rintro hP ⟨hQ, hR⟩,` turns it into

```
hP : P
hQ : Q
hR : R
⊢ S
```

-/

open set

variables
  (X : Type) -- Everything will be a subset of `X`
  (A B C D E : set X) -- A,B,C,D,E are subsets of `X`
  (x y z : X) -- x,y,z are elements of `X` or, more precisely, terms of type `X`

example : A ⊆ A :=
begin
  sorry,
end

example : ∅ ⊆ A :=
begin
  sorry,
end

example : A ⊆ univ :=
begin
  sorry,
end

example : A ⊆ B → B ⊆ C → A ⊆ C :=
begin
  sorry,
end

example : A ⊆ A ∪ B :=
begin
  sorry,
end

example : A ∩ B ⊆ A :=
begin
  sorry,
end

example : A ⊆ B → A ⊆ C → A ⊆ (B ∩ C) :=
begin
  sorry,
end

example : B ⊆ A → C ⊆ A → B ∪ C ⊆ A :=
begin
  sorry,
end

example : A ⊆ B → C ⊆ D → A ∪ C ⊆ B ∪ D :=
begin
  sorry,
end

example : A ⊆ B → C ⊆ D → A ∩ C ⊆ B ∩ D :=
begin
  sorry,
end


import tactic
import topology.basic

-- #check topological_space

/- From https://leanprover-community.github.io/theories/topology.html#filters:
  given a map f from ℕ to a topological space X, one can check that the resulting
  sequence f 0, f 1, f 2... tends to x ∈ X if and only if the pre-image of any
  element in the filter 𝓝 x is in the cofinite filter on ℕ -- this is just
  another way of saying that given any open set U containing x, there exists N
  such that for all n ≥ N, f n ∈ U. -/
example {X : Type*}[topological_space X]{f : ℕ → X}{x : X} :
(∀ U : set X, is_open U ∧ x ∈ U → ∃ N : ℕ, ∀ n > N, f n ∈ U) ↔
(∀ S : set X, S ∈ nhds x → f⁻¹' S ∈ (@filter.cofinite ℕ) ) :=
begin
  split,
  { -- Convergence implies preimage of neighborhood in the cofinite filter
    intros h S S_in,
    rw mem_nhds_iff at S_in,
    rcases S_in with ⟨U, U_ss_S, U_open, x_in_U⟩,
    rcases h U ⟨U_open, x_in_U⟩ with ⟨N, hN⟩,
    rw filter.mem_cofinite,
    have : f⁻¹' U ⊆ f⁻¹'S := set.preimage_mono U_ss_S,
    rw ← set.compl_subset_compl at this,
    suffices hs : (f⁻¹' U)ᶜ.finite, { exact set.finite.subset hs this, },
    -- Now we prove the complement of f⁻¹(U) is finite. The idea is that the
    -- complement is contained in {1, ..., N} because for n > N, n ∈ f⁻¹(U).
    -- So it must be finite.
    have hc : (f⁻¹' U)ᶜ ⊆ {n : ℕ | n ≤ N},
    { rw set.compl_subset_comm,
      intros n n_in,
      have hn : n > N, { rw set.compl_set_of at n_in; exact lt_of_not_ge n_in },
      rw set.compl_set_of at n_in,
      exact hN n hn, },
    exact set.finite.subset (set.finite_le_nat N) hc, },
  { -- Preimage of neighborhood in cofinite filter implies conergence
    rintros h U ⟨U_open, x_in_U⟩,
    have : U ∈ nhds x, { exact is_open.mem_nhds U_open x_in_U },
    specialize h U this,
    rw filter.mem_cofinite at h,
    -- Since (f ⁻¹' U)ᶜ ⊆ ℕ is finite, it is bounded above by some N ∈ ℕ.
    have compl_bdd := bdd_above_def.1 (set.finite.bdd_above h),
    cases compl_bdd with N hN,
    -- We claim that this N works. It is becaues for all n > N, n is not in the
    -- complement of f⁻¹(U), so f(n) ∈ f⁻¹(U).
    use N,
    intros n hn,
    specialize hN n,
    have : n ∉ (f⁻¹' U)ᶜ := mt hN (by linarith [hn]),
    rwa set.not_mem_compl_iff at this },
end

-- The principal filter is an ultrafilter
-- https://leanprover-community.github.io/mathlib_docs/order/filter/basic.html#filter.principal_le_iff

-- If f : X → Y is continuous, ℱ is a filter of X, and ℱ → x ∈ X, then f_*(ℱ) → f(x)
def filter_converge {X : Type*}[topological_space X](F : filter X)(x : X) := ∀ (U : set X), is_open U → x ∈ U → U ∈ F
#check filter_converge

example {X Y: Type*}[topological_space X][topological_space Y]{F : filter X}{f : X → Y}{x : X} :
continuous f → filter_converge F x → filter_converge (filter.map f F) (f x) :=
begin
  intros fcts F_to_x fU fU_open fx_in_fU,
  let U := f⁻¹' fU,
  have U_open : is_open U := is_open.preimage fcts fU_open,
  have x_in_U : x ∈ U := fx_in_fU,
  have U_in_F : U ∈ F := F_to_x U U_open x_in_U,
  exact filter.mem_map.mpr U_in_F,
end

-- If for every filter ℱ in X, ℱ → x ∈ X implies f_*(ℱ) → f(x), then f is continuous
example {X Y : Type*}[topological_space X][topological_space Y]{f : X → Y} :
(∀ (F : filter X) (x : X), filter_converge F x → filter_converge (filter.map f F) (f x))
→ (continuous f) :=
begin
  -- sorry
  have Nx_to_x : ∀ x : X, filter_converge (nhds x) x,
  { intros x U U_open x_in_U,
    exact is_open.mem_nhds U_open x_in_U },
  intros h,
  rw continuous_iff_continuous_at,
  intros x,
  rw continuous_at_def,
  intros A A_in_nhd,
  specialize h (nhds x) x (Nx_to_x x),
  rcases mem_nhds_iff.mp A_in_nhd with ⟨V, V_in, V_open, fx_in_V⟩,
  have V_in_pushforward : V ∈ filter.map f (nhds x) := h V V_open fx_in_V,
  have preimV_in_nhd : f⁻¹' V ∈ nhds x := by rwa filter.mem_map at V_in_pushforward,
  rcases mem_nhds_iff.mp preimV_in_nhd with ⟨U, U_in, U_open, x_in_U⟩,
  have : U ⊆ f⁻¹' A := subset_trans U_in (set.preimage_mono V_in),
  exact mem_nhds_iff.mpr ⟨U, this, U_open, x_in_U⟩,
end

-- From the previous two examples, we conclude that f is continuous iff
-- for all filter ℱ on X, ℱ → x implies f_*(ℱ) → f(x)
example {X Y : Type*}[topological_space X][topological_space Y]{f : X → Y} :
continuous f ↔ (∀ (F : filter X) (x : X), filter_converge F x → filter_converge (filter.map f F) (f x)) :=
sorry

-- It turns out ℱ → x is the same as filter.tendsto id ℱ (nhds x)
example {X : Type*}[topological_space X]{F : filter X}{x : X} :
filter_converge F x ↔ filter.tendsto id F (nhds x) :=
begin
  have filter_converge_iff_contain_nhds : filter_converge F x ↔ F ≤ (nhds x), -- ℱ ≤ 𝒩(x) means 𝒩(x) ⊆ ℱ
  { split,
    { intros F_to_x N N_nhd,
      rcases mem_nhds_iff.mp N_nhd with ⟨U, U_ss_N, U_open, x_in_U⟩,
      have U_in_F : U ∈ F := F_to_x U U_open x_in_U,
      exact filter.mem_of_superset U_in_F U_ss_N },
    { intros nhd_in_F U U_open x_in_U,
      have U_nhd : U ∈ (nhds x) :=  is_open.mem_nhds U_open x_in_U,
      exact nhd_in_F U_nhd } },
  rw filter_converge_iff_contain_nhds,
  unfold filter.tendsto,
  -- Since f is the identity, f⁻¹(S) = S for every S ⊆ X. So f_*(ℱ) = {S ⊆ X : f⁻¹(S) ∈ ℱ } = ℱ
  rwa filter.map_id,
end

-- As a result of filter_converge_iff_contain_nhds, if 𝒩(x) is an ultrafilter,
-- then it is the only filter that converges to x.

-- If S ⊆ T, then 𝒫(S) ⊇ 𝒫(T).
example {X : Type*}[topological_space X]{S T : set X} : S ⊆ T → filter.principal S ≤ filter.principal T :=
begin
  intros S_ss_T F F_in_PT, -- want to show F ∈ 𝒫(S)
  have S_ss_F : S ⊆ F :=
    calc S ⊆ T : S_ss_T
       ... ⊆ F : filter.mem_principal.mp F_in_PT,
  exact filter.mem_principal.mpr S_ss_F,
end

-- Conversely, if 𝒫(S) ⊇ 𝒫(T) then S ⊆ T.
example {X : Type*}[topological_space X]{S T : set X} : filter.principal S ≤ filter.principal T → S ⊆ T :=
begin
  intro S_le_T,
  -- Since T ∈ 𝒫(T) ⊆ 𝒫(S), we have T ∈ 𝒫(S), and thus T ⊇ S
  have T_in_PT : T ∈ filter.principal T := filter.mem_principal_self T,
  have T_in_PS : T ∈ filter.principal S := filter.le_def.mp S_le_T T T_in_PT,
  exact filter.mem_principal.mp T_in_PS,
end

-- And in mathlib this is filter.principal_mono
#check @filter.principal_mono

open filter

-- f_*(𝒫(S)) = 𝒫(f(S))
example {X Y : Type*}[topological_space X][topological_space Y]{f : X → Y}{S : set X} : filter.map f (principal S) = principal (f '' S) :=
begin
  -- simp, -- This alone will solve the goal, but let's work out the details
  apply filter.ext,             -- We want to show for all set T, T ∈ f_*(𝒫(S)) ↔ T ∈ 𝒫(f(S))
  intro T,
  rw [mem_map, mem_principal],  -- But T ∈ f_*(𝒫(S)) means f⁻¹(T) ∈ 𝒫(S) and f⁻¹(T) ⊇ S
  rw ← set.image_subset_iff,    -- But f⁻¹(T) ⊇ S exactly when T ⊇ S
  rw ← mem_principal,           -- So we can conclude T ∈ 𝒫(S)
end

-- The same example but simplified proof
example {X Y : Type*}[topological_space X][topological_space Y]{f : X → Y}{S : set X} : filter.map f (principal S) = principal (f '' S) :=
begin
  -- simp, -- This alone will solve the goal, but let's work out the details
  apply filter.ext,             -- We want to show for all set T, T ∈ f_*(𝒫(S)) ↔ T ∈ 𝒫(f(S))
  dsimp,                                                 -- That is, f⁻¹(T) ⊇ S ↔ T ⊇ f(S) by definitions
  intro T,
  rw ← set.image_subset_iff,
end

-- In mathlib it is filter.map_principal
#check @filter.map_principal


-- Prueba por inducción 3: ∀ m n : ℕ, succ m + n = succ (m + n)
-- ============================================================

-- ----------------------------------------------------
-- Ej. 1. Sean m y n números naturales. Demostrar que
--    succ m + n = succ (m + n)
-- ----------------------------------------------------

import tactic

open nat

variables (m n : ℕ)

-- 1ª demostración
example : succ m + n = succ (m + n) :=
begin
  induction n with n HI,
  { rw nat.add_zero,
    rw nat.add_zero, },
  { rw add_succ,
    rw HI, },
end

-- 2ª demostración
example : succ m + n = succ (m + n) :=
begin
  induction n with n HI,
  { simp only [nat.add_zero], },
  { simp only [add_succ, HI], },
end

-- 3ª demostración
example : succ m + n = succ (m + n) :=
by induction n;
   simp only [*, nat.add_zero, add_succ]

-- 4ª demostración
example : succ m + n = succ (m + n) :=
by induction n;
   simp [*, add_succ]

-- 5ª demostración
example : succ m + n = succ (m + n) :=
nat.rec_on n
  (show succ m + 0 = succ (m + 0), from
    calc
      succ m + 0
          = succ m       : by rw nat.add_zero
      ... = succ (m + 0) : by rw nat.add_zero)
  (assume n,
    assume HI : succ m + n = succ (m + n),
    show succ m + succ n = succ (m + succ n), from
      calc
        succ m + succ n
            = succ (succ m + n)   : by rw add_succ
        ... = succ (succ (m + n)) : by rw HI
        ... = succ (m + succ n)   : by rw add_succ)

-- 6ª demostración
example : succ m + n = succ (m + n) :=
nat.rec_on n
  (show succ m + 0 = succ (m + 0), from rfl)
  (assume n,
    assume HI : succ m + n = succ (m + n),
    show succ m + succ n = succ (m + succ n), from
      calc
        succ m + succ n
            = succ (succ m + n)   : rfl
        ... = succ (succ (m + n)) : by rw HI
        ... = succ (m + succ n)   : rfl)

-- 7ª demostración
example : succ m + n = succ (m + n) :=
nat.rec_on n rfl (λ n HI, by simp only [add_succ, HI])

-- 8ª demostración
example : succ m + n = succ (m + n) :=
-- by library_search
succ_add m n

-- 9ª demostración
example : succ m + n = succ (m + n) :=
-- by hint
by omega

-- 10ª demostración
lemma suc_suma : ∀ n m : ℕ, (succ n) + m = succ (n + m)
| n 0     := rfl
| n (m+1) := congr_arg succ (suc_suma n m)

-- 11ª demostración
lemma suc_suma2 : ∀ n m : ℕ, (succ n) + m = succ (n + m)
| n 0     := by simp
| n (m+1) := by simp [add_succ, suc_suma2 n m]

-- 12ª demostración
lemma suc_suma3 : ∀ n m : ℕ, (succ n) + m = succ (n + m)
| n 0     := by simp only [add_zero]
| n (m+1) := by simp only [add_zero, add_succ, suc_suma3 n m]


import topology.instances.real
import analysis.normed_space.banach_steinhaus

open set filter
open_locale topological_space filter

/- TEXT:
.. index:: metric space

.. _metric_spaces:

Metric spaces
--------------

Examples in the previous section focus on sequences of real numbers. In this section we will go up a bit in generality and focus on
metric spaces. A metric space is a type ``X`` equipped with a distance function ``dist : X → X → ℝ`` which is a generalization of
the function ``λ x y, |x - y|`` from the case where ``X = ℝ``.

Introducing such a space is easy and we will check all properties required from the distance function.
BOTH: -/
-- QUOTE:

variables {X : Type*} [metric_space X] (a b c : X)

#check (dist a b : ℝ)

#check (dist_nonneg : 0 ≤ dist a b)

#check (dist_eq_zero : dist a b  = 0 ↔ a = b)

#check (dist_comm a b : dist a b  = dist b a)

#check (dist_triangle a b c : dist a c ≤ dist a b + dist b c)
-- QUOTE.

/- TEXT:
Note we also have variants where the distance can be infinite or where ``dist a b`` can be zero without having ``a = b`` or both.
They are called ``emetric_space``, ``pseudo_metric_space`` and ``pseudo_emetric_space`` respectively (here "e" stands for "extended").

BOTH: -/

-- Note the next three lines are not quoted, their purpose is to make sure those things don't get renamed while we're looking elsewhere.
#check emetric_space
#check pseudo_metric_space
#check pseudo_emetric_space

/- TEXT:
Note that our journey from ``ℝ`` to metric spaces jumped over the special case of normed spaces that also require linear algebra and
will be explained as part of the calculus chapter.

Convergence and continuity
^^^^^^^^^^^^^^^^^^^^^^^^^^

Using distance functions, we can already define convergent sequences and continuous functions between metric spaces.
They are actually defined in a more general setting covered in the next section,
but we have lemmas recasting the definition is terms of distances.
BOTH: -/
-- QUOTE:

example {u : ℕ → X} {a : X} :
  tendsto u at_top (𝓝 a) ↔ ∀ ε > 0, ∃ N, ∀ n ≥ N, dist (u n) a < ε :=
metric.tendsto_at_top

example {X Y : Type*} [metric_space X] [metric_space Y] {f : X → Y} :
  continuous f ↔
  ∀ x : X, ∀ ε > 0, ∃ δ > 0, ∀ x', dist x' x < δ → dist (f x') (f x) < ε :=
metric.continuous_iff

-- QUOTE.

/- TEXT:
.. index:: continuity, tactics ; continuity

A *lot* of lemmas have some continuity assumptions, no we end up proving a lot of continuity results and there
is a ``continuity`` tactic devoted to this task. Let's prove a continuity statement that will be needed
in an exercise below. Notice that Lean knows how to treat a product of two metric spaces as a metric space, so
it makes sense to consider continuous functions from ``X × X`` to ``ℝ``.
In particular the (uncurried version of the) distance function is such a function.

BOTH: -/

-- QUOTE:
example {X Y : Type*} [metric_space X] [metric_space Y] {f : X → Y} (hf : continuous f) :
  continuous (λ p : X × X, dist (f p.1) (f p.2)) :=
by continuity
-- QUOTE.

/- TEXT:
This tactic is a bit slow, so it is also useful to know
how to do it by hand. We first need to use that ``λ p : X × X, f p.1`` is continuous because it
is the composition of ``f``, which is continuous by assumption ``hf``, and the projection ``prod.fst`` whose continuity
is the content of the lemma ``continuous_fst``. The composition property is ``continuous.comp`` which is
in the ``continuous`` namespace so we can use dot notation to compress
``continuous.comp hf continuous_fst`` into ``hf.comp continuous_fst`` which is actually more readable
since it really reads as composing our assumption and our lemma.
We can do the same for the second component to get continuity of ``λ p : X × X, f p.2``. We then assemble
those two continuities using ``continuous.prod_mk`` to get
``(hf.comp continuous_fst).prod_mk (hf.comp continuous_snd) : continuous (λ p : X × X, (f p.1, f p.2))``
and compose once more to get our full proof.
BOTH: -/
-- QUOTE:

example {X Y : Type*} [metric_space X] [metric_space Y] {f : X → Y} (hf : continuous f) :
  continuous (λ p : X × X, dist (f p.1) (f p.2)) :=
continuous_dist.comp ((hf.comp continuous_fst).prod_mk (hf.comp continuous_snd))

-- QUOTE.

/- TEXT:
The combination of ``continuous.prod_mk`` and ``continuous_dist`` via ``continuous.comp`` feels clunky,
even when heavily using dot notation as above. A more serious issue is that this nice proof requires a lot of
planning. Lean accepts the above proof term because it is a full term proving a statement which is
definitionally equivalent to our goal, the crucial definition to unfold being that of a composition of functions.
Indeed our target function ``λ p : X × X, dist (f p.1) (f p.2)`` is not presented as a composition.
The proof term we provided proves continuity of ``dist ∘ (λ p : X × X, (f p.1, f p.2))`` which happens
to be definitionally equal to our target function. But if we try to build this proof gradually using
tactics starting with ``apply continuous_dist.comp`` then Lean's elaborator will fail to recognize a
composition and refuse to apply this lemma. It is especially bad at this when products of types are involved.

A better lemma to apply here is
``continuous.dist {f g : X → Y} : continuous f → continuous g → continuous (λ x, dist (f x) (g x))``
which is nicer to Lean's elaborator and also provides a shorter proof when directly providing a full
proof term, as can be seen from the following two new proofs of the above statement:
BOTH: -/

-- QUOTE:

example {X Y : Type*} [metric_space X] [metric_space Y] {f : X → Y} (hf : continuous f) :
  continuous (λ p : X × X, dist (f p.1) (f p.2)) :=
begin
  apply continuous.dist,
  exact hf.comp continuous_fst,
  exact hf.comp continuous_snd
end

example {X Y : Type*} [metric_space X] [metric_space Y] {f : X → Y} (hf : continuous f) :
  continuous (λ p : X × X, dist (f p.1) (f p.2)) :=
(hf.comp continuous_fst).dist (hf.comp continuous_snd)

-- QUOTE.

/- TEXT:
Note that, without the elaboration issue coming from composition, another way to compress
our proof would be to use ``continuous.prod_map`` which is sometimes useful and gives
as an alternate proof term ``continuous_dist.comp (hf.prod_map hf)`` which even shorter to type.

Since it is sad to decide between a version which is better for elaboration and a version which is shorter
to type, let us wrap this discussion with a last bit of compression offered
by ``continuous.fst'`` which allows to compress ``hf.comp continuous_fst`` to ``hf.fst'`` (and the same with ``snd``)
and get our final proof, now bordering obfuscation.

BOTH: -/

-- QUOTE:
example {X Y : Type*} [metric_space X] [metric_space Y] {f : X → Y} (hf : continuous f) :
  continuous (λ p : X × X, dist (f p.1) (f p.2)) :=
hf.fst'.dist hf.snd'

-- QUOTE.

/- TEXT:
It's your turn now to prove some continuity lemma. After trying the continuity tactic, you will need
``continuous.add``, ``continuous_pow`` and ``continuous_id`` to do it by hand.

BOTH: -/

-- QUOTE:
example {f : ℝ → X} (hf : continuous f) : continuous (λ x : ℝ, f (x^2 + x)) :=
sorry

-- QUOTE.

-- SOLUTIONS:
example {f : ℝ → X} (hf : continuous f) : continuous (λ x : ℝ, f (x^2 + x)) :=
hf.comp $ (continuous_pow 2).add continuous_id

/- TEXT:
So far we saw continuity as a global notion, but one can also define continuity at a point.
BOTH: -/

-- QUOTE:

example {X Y : Type*} [metric_space X] [metric_space Y] (f : X → Y) (a : X) :
continuous_at f a ↔ ∀ ε > 0, ∃ δ > 0, ∀ {x}, dist x a < δ → dist (f x) (f a) < ε :=
metric.continuous_at_iff

-- QUOTE.

/- TEXT:

Balls, open sets and closed sets
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Once we have a distance function, the most important geometric definitions are (open) balls and closed balls.

BOTH: -/
-- QUOTE:

variables r : ℝ

example : metric.ball a r = {b | dist b a < r} := rfl

example : metric.closed_ball a r = {b | dist b a ≤ r} := rfl

-- QUOTE.

/- TEXT:
Note that `r` is any real number here, there is no sign restriction. Of course some statements do require a radius condition.
BOTH: -/
-- QUOTE:

example (hr : 0 < r) : a ∈ metric.ball a r := metric.mem_ball_self hr

example (hr : 0 ≤ r) : a ∈ metric.closed_ball a r := metric.mem_closed_ball_self hr

-- QUOTE.
/- TEXT:
Once we have balls, we can define open sets. They are actually defined in a more general setting covered in the next section,
but we have lemmas recasting the definition is terms of balls.

BOTH: -/

-- QUOTE:
example (s : set X) : is_open s ↔ ∀ x ∈ s, ∃ ε > 0, metric.ball x ε ⊆ s :=
metric.is_open_iff

-- QUOTE.

/- TEXT:
Then closed sets are sets whose complement is open. Their important property is they are closed under limits. The closure of a set is the smallest subset containing it.
BOTH: -/
-- QUOTE:

example {s : set X} : is_closed s ↔ is_open sᶜ :=
is_open_compl_iff.symm

example {s : set X} (hs : is_closed s) {u : ℕ → X} (hu : tendsto u at_top (𝓝 a))
  (hus : ∀ n, u n ∈ s) : a ∈ s :=
hs.mem_of_tendsto hu (eventually_of_forall hus)

example {s : set X} : a ∈ closure s ↔ ∀ ε > 0, ∃ b ∈ s, a ∈ metric.ball b ε :=
metric.mem_closure_iff
-- QUOTE.

/- TEXT:
Do the next exercise without using `mem_closure_iff_seq_limit`
BOTH: -/

-- QUOTE:
example {u : ℕ → X} (hu : tendsto u at_top (𝓝 a)) {s : set X} (hs : ∀ n, u n ∈ s) :
  a ∈ closure s :=
sorry

-- QUOTE.

-- SOLUTIONS:
example {u : ℕ → X} (hu : tendsto u at_top (𝓝 a)) {s : set X} (hs : ∀ n, u n ∈ s) :
  a ∈ closure s :=
begin
  rw metric.tendsto_at_top at hu,
  rw metric.mem_closure_iff,
  intros ε ε_pos,
  rcases hu ε ε_pos with ⟨N, hN⟩,
  refine ⟨u N, hs _, _⟩,
  rw dist_comm,
  exact hN N le_rfl
end

/- TEXT:

Remember from the filters sections that neighborhood filters play a big role in mathlib.
In the metric space context, the crucial point is that balls provide bases for those filters.
The main lemmas here are ``metric.nhds_basis_ball`` and ``metric.nhds_basis_closed_ball``
that claim this for open and closed balls with positive radius. The center point is an implicit
argument so we can invoke ``filter.has_basis.mem_iff`` as in the following example.

BOTH: -/

-- QUOTE:
example {x : X} {s : set X} : s ∈ 𝓝 x ↔ ∃ ε > 0, metric.ball x ε ⊆ s :=
metric.nhds_basis_ball.mem_iff

example {x : X} {s : set X} : s ∈ 𝓝 x ↔ ∃ ε > 0, metric.closed_ball x ε ⊆ s :=
metric.nhds_basis_closed_ball.mem_iff

-- QUOTE.

/- TEXT:

Compactness
^^^^^^^^^^^

Compactness is an important topological notion. It distinguishes subsets of a metric space
that enjoy the same kind of properties as segments in reals compared to other intervals:

* Any sequence taking value in a compact set has a subsequence that converges in this set
* Any continuous function on a nonempty compact set with values in real numbers is bounded and achieves its bounds somewhere (this is called the extreme values theorem).
* Compact sets are closed sets.

Let us first check that the unit interval in reals is indeed a compact set, and then check the above
claims for compact sets in general metric spaces. In the second statement we only
need continuity on the given set so we will use ``continuous_on`` instead of ``continuous``, and
we will give separate statements for the minimum and the maximum. Of course all these results
are deduced from more general versions, some of which will be discussed in later sections.

BOTH: -/

-- QUOTE:

example : is_compact (set.Icc 0 1 : set ℝ) :=
is_compact_Icc

example {s : set X} (hs : is_compact s) {u : ℕ → X} (hu : ∀ n, u n ∈ s) :
  ∃ a ∈ s, ∃ φ : ℕ → ℕ, strict_mono φ ∧ tendsto (u ∘ φ) at_top (𝓝 a) :=
hs.tendsto_subseq hu

example {s : set X} (hs : is_compact s) (hs' : s.nonempty)
  {f : X → ℝ} (hfs : continuous_on f s) :
  ∃ x ∈ s, ∀ y ∈ s, f x ≤ f y :=
hs.exists_forall_le hs' hfs

example {s : set X} (hs : is_compact s) (hs' : s.nonempty)
  {f : X → ℝ} (hfs : continuous_on f s) :
  ∃ x ∈ s, ∀ y ∈ s, f y ≤ f x :=
hs.exists_forall_ge hs' hfs

example {s : set X} (hs : is_compact s) : is_closed s :=
hs.is_closed

-- QUOTE.

/- TEXT:

We can also metric spaces which are globally compact, using an extra ``Prop``-valued type class:

BOTH: -/

-- QUOTE:
example {X : Type*} [metric_space X] [compact_space X] : is_compact (univ : set X) :=
compact_univ
-- QUOTE.

/- TEXT:

In a compact metric space any closed set is compact, this is ``is_compact.is_closed``.

BOTH: -/

#check is_compact.is_closed

/- TEXT:
Uniformly continuous functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

We now turn to uniformity notions on metric spaces : uniformly continuous functions, Cauchy sequences and completeness.
Again those are defined in a more general context but we have lemmas in the metric name space to access their elementary definitions.
We start with uniform continuity.

BOTH: -/

-- QUOTE:
example {X : Type*} [metric_space X] {Y : Type*} [metric_space Y] {f : X → Y} :
  uniform_continuous f ↔ ∀ ε > 0, ∃ δ > 0, ∀ {a b : X}, dist a b < δ → dist (f a) (f b) < ε :=
metric.uniform_continuous_iff

-- QUOTE.

/- TEXT:
In order to practice manipulating all those definitions, we will prove that continuous
functions from a compact metric space to a metric space are uniformly continuous
(we will see a more general version in a later section).

We will first give an informal sketch. Let ``f : X → Y`` be a continuous function from
a compact metric space to a metric space.
We fix ``ε > 0`` and start looking for some ``δ``.

Let ``φ : X × X → ℝ := λ p, dist (f p.1) (f p.2)`` and let ``K := { p : X × X | ε ≤ φ p }``.
Observe ``φ`` is continuous since ``f`` and distance are continuous.
And ``K`` is clearly closed (use ``is_closed_le``) hence compact since ``X`` is compact.

Then we discuss two possibilities using ``eq_empty_or_nonempty``.
If ``K`` is empty then we are clearly done (we can set ``δ = 1`` for instance).
So let's assume ``K`` is not empty, and use the extreme value theorem to choose ``(x₀, x₁)`` attaining the infimum
of ``φ`` on ``K``. We can then set ``δ = dist x₀ x₁`` and check everything works.

BOTH: -/

-- QUOTE:
example {X : Type*} [metric_space X] [compact_space X] {Y : Type*} [metric_space Y]
  {f : X → Y} (hf : continuous f) : uniform_continuous f :=
sorry

-- QUOTE.

-- SOLUTIONS:
example {X : Type*} [metric_space X] [compact_space X] {Y : Type*} [metric_space Y]
  {f : X → Y} (hf : continuous f) : uniform_continuous f :=
begin
  rw metric.uniform_continuous_iff,
  intros ε ε_pos,
  let φ : X × X → ℝ := λ p, dist (f p.1) (f p.2),
  have φ_cont : continuous φ := hf.fst'.dist hf.snd',
  let K := { p : X × X | ε ≤ φ p },
  have K_closed : is_closed K := is_closed_le continuous_const φ_cont,
  have K_cpct : is_compact K := K_closed.is_compact,
  cases eq_empty_or_nonempty K with hK hK,
  { use [1, by norm_num],
    intros x y hxy,
    have : (x, y) ∉ K, by simp [hK],
    simpa [K] },
  { rcases K_cpct.exists_forall_le hK continuous_dist.continuous_on with ⟨⟨x₀, x₁⟩, xx_in, H⟩,
    use dist x₀ x₁,
    split,
    { change _ < _,
      rw dist_pos,
      intro h,
      have : ε ≤ 0, by simpa [*] using xx_in,
      linarith },
    { intros x x',
      contrapose!,
      intros hxx',
      exact H (x, x') hxx' } },
end

/- TEXT:
Completeness
^^^^^^^^^^^^

A Cauchy sequence in a metric space is a sequence whose terms get closer and closer to each other.
There are a couple of equivalent ways to state that idea.
In particular converging sequences are Cauchy. The converse is true only in so-called *complete*
spaces.

BOTH: -/

-- QUOTE:

example (u : ℕ → X) : cauchy_seq u ↔ ∀ ε > 0, ∃ N : ℕ, ∀ m ≥ N,  ∀ n ≥ N, dist (u m) (u n) < ε :=
metric.cauchy_seq_iff

example (u : ℕ → X) : cauchy_seq u ↔ ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, dist (u n) (u N) < ε :=
metric.cauchy_seq_iff'

example [complete_space X] (u : ℕ → X) (hu : cauchy_seq u) : ∃ x, tendsto u at_top (𝓝 x) :=
cauchy_seq_tendsto_of_complete hu

-- QUOTE.

/- TEXT:

We'll practice using this definition by proving a convenient criterion which is a special case of a
criterion appearing in mathlib. This is also a good opportunity to practive using big sums in
a geometric context. In addition to the explanations from the filters section, you will probably need
``tendsto_pow_at_top_nhds_0_of_lt_1``, ``tendsto.mul`` and ``dist_le_range_sum_dist``.
BOTH: -/

open_locale big_operators
open finset

-- QUOTE:

lemma cauchy_seq_of_le_geometric_two' {u : ℕ → X} (hu : ∀ (n : ℕ), dist (u n) (u (n + 1)) ≤ (1 / 2) ^ n) :
  cauchy_seq u :=
begin
  rw metric.cauchy_seq_iff',
  intros ε ε_pos,
  obtain ⟨N, hN⟩ : ∃ N : ℕ, 1 / 2 ^ N * 2 < ε,
  { sorry },
  use N,
  intros n hn,
  obtain ⟨k, rfl : n = N + k⟩ := le_iff_exists_add.mp hn,
  calc dist (u (N + k)) (u N) = dist (u (N+0)) (u (N + k)) : sorry
  ... ≤ ∑ i in range k, dist (u (N + i)) (u (N + (i + 1))) : sorry
  ... ≤ ∑ i in range k, (1/2 : ℝ)^(N+i) : sorry
  ... = 1/2^N*∑ i in range k, (1 / 2) ^ i : sorry
  ... ≤ 1/2^N*2 : sorry
  ... < ε : sorry
end
-- QUOTE.

-- SOLUTIONS:
example {u : ℕ → X} (hu : ∀ (n : ℕ), dist (u n) (u (n + 1)) ≤ (1 / 2) ^ n) :
  cauchy_seq u :=
begin
  rw metric.cauchy_seq_iff',
  intros ε ε_pos,
  obtain ⟨N, hN⟩ : ∃ N : ℕ, 1 / 2 ^ N * 2 < ε,
  { have : tendsto (λ N : ℕ, (1 / 2 ^ N * 2 : ℝ)) at_top (𝓝 0),
    { rw ← zero_mul (2 : ℝ),
      apply tendsto.mul,
      simp_rw ← one_div_pow (2 : ℝ),
      apply tendsto_pow_at_top_nhds_0_of_lt_1 ; linarith,
      exact tendsto_const_nhds },
    rcases (at_top_basis.tendsto_iff (nhds_basis_Ioo_pos (0 : ℝ))).mp this ε ε_pos with ⟨N, H, hN⟩,
    exact  ⟨N, by simpa using (hN N le_rfl).2⟩ },
  use N,
  intros n hn,
  obtain ⟨k, rfl : n = N + k⟩ := le_iff_exists_add.mp hn,
  calc dist (u (N + k)) (u N) = dist (u (N+0)) (u (N + k)) : by rw [dist_comm, add_zero]
  ... ≤ ∑ i in range k, dist (u (N + i)) (u (N + (i + 1))) : dist_le_range_sum_dist (λ i, u (N+i)) k
  ... ≤ ∑ i in range k, (1/2 : ℝ) ^ (N+i) : sum_le_sum (λ i hi, hu $ N+i)
  ... = 1/2^N*∑ i in range k, (1 / 2)^i : by simp_rw [← one_div_pow, pow_add, ← mul_sum]
  ... ≤ 1/2^N*2 : mul_le_mul_of_nonneg_left (sum_geometric_two_le _) (one_div_nonneg.mpr (pow_nonneg (zero_le_two : (0 : ℝ) ≤ 2)_))
  ... < ε : hN
end

/- TEXT:

We are ready for the final boss of this section: Baire's theorem for complete metric spaces!
The proof skeleton below shows interesting techniques. It uses the ``choose`` tactic in its exclamation
mark variant (you should experiment with removing this exclamation mark) and it shows how to
define something inductively in the middle of a proof using ``nat.rec_on``.

BOTH: -/

-- QUOTE:
open metric

example [complete_space X] (f : ℕ → set X) (ho : ∀ n, is_open (f n)) (hd : ∀ n, dense (f n)) : dense (⋂n, f n) :=
begin
  let B : ℕ → ℝ := λ n, (1/2)^n,
  have Bpos : ∀ n, 0 < B n, sorry,
  /- Translate the density assumption into two functions `center` and `radius` associating
  to any n, x, δ, δpos a center and a positive radius such that
  `closed_ball center radius` is included both in `f n` and in `closed_ball x δ`.
  We can also require `radius ≤ (1/2)^(n+1)`, to ensure we get a Cauchy sequence later. -/
  have : ∀ (n : ℕ)  (x : X) (δ > 0), ∃ (y : X) (r > 0), r ≤ B (n+1) ∧ closed_ball y r ⊆ (closed_ball x δ) ∩ f n,
  { sorry },
  choose! center radius Hpos HB Hball using this,
  intros x,
  rw mem_closure_iff_nhds_basis nhds_basis_closed_ball,
  intros ε εpos,
  /- `ε` is positive. We have to find a point in the ball of radius `ε` around `x` belonging to all
  `f n`. For this, we construct inductively a sequence `F n = (c n, r n)` such that the closed ball
  `closed_ball (c n) (r n)` is included in the previous ball and in `f n`, and such that
  `r n` is small enough to ensure that `c n` is a Cauchy sequence. Then `c n` converges to a
  limit which belongs to all the `f n`. -/
  let F : ℕ → (X × ℝ) := λn, nat.rec_on n (prod.mk x (min ε (B 0)))
                              (λn p, prod.mk (center n p.1 p.2) (radius n p.1 p.2)),
  let c : ℕ → X := λ n, (F n).1,
  let r : ℕ → ℝ := λ n, (F n).2,
  have rpos : ∀ n, 0 < r n,
  { sorry },

  have rB : ∀n, r n ≤ B n,
  { sorry },
  have incl : ∀n, closed_ball (c (n+1)) (r (n+1)) ⊆ (closed_ball (c n) (r n)) ∩ (f n),
  { sorry },
  have cdist : ∀ n, dist (c n) (c (n+1)) ≤ B n,
  { sorry },
  have : cauchy_seq c, from cauchy_seq_of_le_geometric_two' cdist,
  -- as the sequence `c n` is Cauchy in a complete space, it converges to a limit `y`.
  rcases cauchy_seq_tendsto_of_complete this with ⟨y, ylim⟩,
  -- this point `y` will be the desired point. We will check that it belongs to all
  -- `f n` and to `ball x ε`.
  use y,
  have I : ∀n, ∀ m ≥ n, closed_ball (c m) (r m) ⊆ closed_ball (c n) (r n),
  { sorry },
  have yball : ∀n, y ∈ closed_ball (c n) (r n),
  { sorry },
  sorry
end

-- QUOTE.

-- SOLUTIONS:
example [complete_space X] (f : ℕ → set X) (ho : ∀ n, is_open (f n)) (hd : ∀ n, dense (f n)) : dense (⋂n, f n) :=
begin
  let B : ℕ → ℝ := λ n, (1/2)^n,
  have Bpos : ∀ n, 0 < B n, from λ n, (pow_pos sorry n),
  /- Translate the density assumption into two functions `center` and `radius` associating
  to any n, x, δ, δpos a center and a positive radius such that
  `closed_ball center radius` is included both in `f n` and in `closed_ball x δ`.
  We can also require `radius ≤ (1/2)^(n+1)`, to ensure we get a Cauchy sequence later. -/
  have : ∀ (n : ℕ)  (x : X) (δ > 0), ∃ (y : X) (r > 0), r ≤ B (n+1) ∧ closed_ball y r ⊆ (closed_ball x δ) ∩ f n,
  { intros n x δ δpos,
    have : x ∈ closure (f n) := hd n x,
    rcases metric.mem_closure_iff.1 this (δ/2) (half_pos δpos) with ⟨y, ys, xy⟩,
    rw dist_comm at xy,
    obtain ⟨r, rpos, hr⟩ : ∃ r > 0, closed_ball y r ⊆ f n :=
      nhds_basis_closed_ball.mem_iff.1 (is_open_iff_mem_nhds.1 (ho n) y ys),
    refine ⟨y, min (min (δ/2) r) (B (n+1)), _, _, λz hz, ⟨_, _⟩⟩,
    show 0 < min (min (δ / 2) r) (B (n+1)),
      from lt_min (lt_min (half_pos δpos) rpos) (Bpos (n+1)),
    show min (min (δ / 2) r) (B (n+1)) ≤ B (n+1), from min_le_right _ _,
    show z ∈ closed_ball x δ, from calc
      dist z x ≤ dist z y + dist y x : dist_triangle _ _ _
      ... ≤ (min (min (δ / 2) r) (B (n+1))) + (δ/2) : add_le_add hz xy.le
      ... ≤ δ/2 + δ/2 : add_le_add_right ((min_le_left _ _).trans (min_le_left _ _)) _
      ... = δ : add_halves δ,
    show z ∈ f n, from hr (calc
      dist z y ≤ min (min (δ / 2) r) (B (n+1)) : hz
      ... ≤ r : (min_le_left _ _).trans (min_le_right _ _)) },
  choose! center radius Hpos HB Hball using this,
  refine λ x, (mem_closure_iff_nhds_basis nhds_basis_closed_ball).2 (λ ε εpos, _),
  /- `ε` is positive. We have to find a point in the ball of radius `ε` around `x` belonging to all
  `f n`. For this, we construct inductively a sequence `F n = (c n, r n)` such that the closed ball
  `closed_ball (c n) (r n)` is included in the previous ball and in `f n`, and such that
  `r n` is small enough to ensure that `c n` is a Cauchy sequence. Then `c n` converges to a
  limit which belongs to all the `f n`. -/
  let F : ℕ → (X × ℝ) := λn, nat.rec_on n (prod.mk x (min ε (B 0)))
                              (λn p, prod.mk (center n p.1 p.2) (radius n p.1 p.2)),
  let c : ℕ → X := λ n, (F n).1,
  let r : ℕ → ℝ := λ n, (F n).2,
  have rpos : ∀ n, 0 < r n,
  { assume n,
    induction n with n hn,
    exact lt_min εpos (Bpos 0),
    exact Hpos n (c n) (r n) hn },

  have rB : ∀n, r n ≤ B n,
  { assume n,
    induction n with n hn,
    exact min_le_right _ _,
    exact HB n (c n) (r n) (rpos n) },
  have incl : ∀n, closed_ball (c (n+1)) (r (n+1)) ⊆ (closed_ball (c n) (r n)) ∩ (f n) :=
    λ n, Hball n (c n) (r n) (rpos n),
  have cdist : ∀ n, dist (c n) (c (n+1)) ≤ B n,
  { assume n,
    rw dist_comm,
    have A : c (n+1) ∈ closed_ball (c (n+1)) (r (n+1)) := mem_closed_ball_self (rpos $ n +1).le,
    have I := calc
      closed_ball (c (n+1)) (r (n+1)) ⊆ closed_ball (c n) (r n) :
         (incl n).trans (inter_subset_left _ _)
      ... ⊆ closed_ball (c n) (B n) : closed_ball_subset_closed_ball (rB n),
    exact I A },
  have : cauchy_seq c,
  from cauchy_seq_of_le_geometric_two' cdist,
  -- as the sequence `c n` is Cauchy in a complete space, it converges to a limit `y`.
  rcases cauchy_seq_tendsto_of_complete this with ⟨y, ylim⟩,
  -- this point `y` will be the desired point. We will check that it belongs to all
  -- `f n` and to `ball x ε`.
  use y,

  have I : ∀n, ∀ m ≥ n, closed_ball (c m) (r m) ⊆ closed_ball (c n) (r n),
  { assume n,
    refine nat.le_induction _ (λm hnm h, _),
    { exact subset.rfl },
    { exact (incl m).trans ((set.inter_subset_left _ _).trans h) }},
  have yball : ∀n, y ∈ closed_ball (c n) (r n),
  { assume n,
    refine is_closed_ball.mem_of_tendsto ylim _,
    refine (filter.eventually_ge_at_top n).mono (λ m hm, _),
    exact I n m hm (mem_closed_ball_self (rpos _).le) },
  split,
  { suffices : ∀ n, y ∈ f n, by rwa set.mem_Inter,
    intro n,
    have : closed_ball (c (n+1)) (r (n+1)) ⊆ f n := subset.trans (incl n) (inter_subset_right _ _),
    exact this (yball (n+1)) },
  calc dist y x ≤ r 0 : yball 0
            ... ≤ ε : min_le_left _ _,
end

/- TEXT:

BOTH: -/


-- https://leanprover.github.io/theorem_proving_in_lean/propositions_and_proofs.html#exercises
open classical

variables p q r s : Prop

example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
assume (h: p -> r ∨ s),
by_cases (
  assume hp: p,
  have hrs: r ∨ s, from h hp,
  hrs.elim (λ hr: r, or.inl (λ hp: p, hr)) (λ hs: s, or.inr (λ hp:p, hs))
) (
  assume hnp: ¬ p,
  have hpr: p -> r, from (λ hp: p, absurd hp hnp),
  or.inl hpr
)

example : ¬(p ∧ q) → ¬p ∨ ¬q :=
assume (h: ¬ (p ∧ q)),
by_cases (
  assume hp: p,
  have hnq: ¬ q, from
    assume hq: q, h ⟨hp, hq⟩,
  or.inr hnq
) (
  assume hnp: ¬ p,
  or.inl hnp
)

lemma dne {p: Prop} (h: ¬ ¬ p) : p :=
or.elim (em p)
  (assume hp: p, hp)
  (assume hnp: ¬ p, absurd hnp h)

lemma L : ¬(p → q) → p ∧ ¬q :=
assume h: ¬ (p -> q),
by_contradiction (
  assume h': ¬ (p ∧ ¬ q),
  have h'': p -> (¬ q) -> false, from λ (hp) (hnq), h' ⟨hp, hnq⟩,
  show false, from h (
    show p -> q, from
    assume hp: p,
    dne (h'' hp)
  )
)
example : ¬(p → q) → p ∧ ¬q := L p q

example : (p → q) → (¬p ∨ q) :=
assume h: p -> q,
by_cases (λ hp, or.inr (h hp)) (λ hnp, or.inl hnp)

example : (¬q → ¬p) → (p → q) :=
assume h: ¬ q -> ¬ p,
by_cases (
  assume hnq: ¬ q,
  have hnp: ¬ p, from h hnq,
  λ hp, absurd hp hnp
) (
  assume hnnq: ¬ ¬ q,
  have hq: q, from dne hnnq,
  λ hp, hq
)

example : p ∨ ¬p := em p

example : (((p → q) → p) → p) :=
assume h: (p -> q) -> p,
show p, from
by_cases (
  assume hpq: p -> q, h hpq
) (
  assume hnpq: ¬ (p -> q),
  have hpnq: p ∧ ¬ q, from L p q hnpq,
  hpnq.left
)


import topology.basic data.set.intervals analysis.exponential data.complex.basic tactic.ring
open real set
open complex

-- * Example 4: y(x)=1/(x^2-2x+3) is continuous over [1,2].

-- Here's a way to start sketching, anyway.

constants A B C : ℝ

-- some specific quadratic
noncomputable def quadratic := (λ (x:ℝ), A * (x^2) + B * x + C)

-- take in some coefficients and generate the corresponding quadratic polynomial
def quadratic_poly := (λ (a b c : ℝ), (λ (x:ℝ), a * (x^2) + b * x + c))

-- Here's a useful lemma: also easy to prove.  We'll use this to prove the nonmonic case as well.
lemma cs_monic (b c : ℝ) : ∀ (x:ℝ), x^2 + b*x + c = (x + b/2)^2 + (c-(b^2)/4) :=
begin
intro,
ring,
end
-- use this for an example, e.g. x^2-2x+3, to show this one is continuous

-- As a quick warm-up, the polynomial itself is continuous.
-- A more general version of this result would say that *all* such polynomials are continuous.
-- Is that a known result?  Anyway, this will do for now
lemma cont_example_quad : continuous (quadratic_poly 1 (-2) 3) :=
begin
unfold quadratic_poly,
refine continuous_add _ _,
refine continuous_add _ _,
refine continuous_mul _ _,
exact continuous_const,
exact continuous_pow 2,
refine continuous_mul _ _,
exact continuous_const,
exact continuous_id,
exact continuous_const,
end

-- Now let's set up 1/quad
#check (λ (x:ℝ), 1/x)
#check function.comp (λ (x:ℝ), 1/x) (quadratic_poly 1 (-2) 3)

lemma example_inv_quad : continuous (function.comp (λ (x:ℝ), 1/x) (quadratic_poly 1 (-2) 3)) :=
begin
unfold quadratic_poly function.comp,
-- the setup looks good.
-- adjust it so that we can apply the lemma:
simp only [one_div_eq_inv],
refine real.continuous_inv _ _,
intro x,
rw one_mul,
rw cs_monic,
-- The first summand is (x + (-2) / 2) ^ 2
-- which we deem to be nonnegative because it is a square;
-- so it is sufficient to show the second summand is positive.
-- But that's easy:
-- (3 - (-2) ^ 2 / 4) = (3 - 4 / 4) = (3 - 1) = 2 > 0
-- So let's apply these ideas.
apply ne_of_gt,                 -- this is a sufficient condition
apply add_pos_of_nonneg_of_pos, -- Break the proof into parts at the + sign
rw pow_two,                     -- rewrite the square
apply mul_self_nonneg,          -- draw the first required conclusion
-- For the next part, we need to do a bit of arithmetic
-- but we can let Lean do that for us.
ring,
norm_num,
-- the last goal is the continuity of the polynomial
exact cont_example_quad,
end


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

import tactic -- imports all the Lean tactics
import measure_theory.measurable_space

/-

# The extended nonnegative reals [0,∞]

The big dilemma when a designer is faced with "minor modifications"
of a standard type, is whether to just stick with the standard type
and make do, or whether to make a new type and then be faced with the
problem of having to make all the API for that type. Example: in measure
theory a key role is played by the "extended non-negative reals",
namely {x : ℝ | 0 ≤ x} ∪ {∞}. In Lean these are their own type,
called `ennreal`. There is a "locale" containing standard notation
associated for this type. Let's open it.

localized "notation (name := ennreal) `ℝ≥0∞` := ennreal" in ennreal
localized "notation (name := ennreal.top) `∞` := (⊤ : ennreal)" in ennreal
-/

open_locale ennreal

#print notation ℝ≥0∞
#check ennreal

#check ℝ≥0∞ -- [0,∞]

#check ∞ -- it's the ∞ in ℝ≥0∞

-- What can we do with extended non-negative reals?

variables (a b : ℝ≥0∞)

#check a + b
#check a - b -- surprising?
#check a * b -- what is 0 * ∞ then?
#check a / b -- is 1 / 0 = 0 or ∞? In ℝ it's 0 but here there's another possibility

example : (0 : ℝ≥0∞) * ∞ = 0 :=
begin
  norm_num,
end

example : (1 : ℝ≥0∞) / 0 = ∞ :=
begin
  simp,
end

example (a b c : ℝ≥0∞) : (a + b) * c = a * c + b * c :=
begin
  ring,
end


import algebra.big_operators.ring
import data.real.basic

@[ext] structure point := (x : ℝ) (y : ℝ) (z : ℝ)

#check point.ext

example (a b : point) (hx : a.x = b.x) (hy : a.y = b.y) (hz : a.z = b.z) :
  a = b :=
begin
  ext,
  assumption',
end

def my_point1 : point :=
{ x := 2,
  y := -1,
  z := 4 }

def my_point2 :=
{ point .
  x := 2,
  y := -1,
  z := 4 }

def my_point3 : point := ⟨2, -1, 4⟩

def my_point4 := point.mk 2 (-1) 4

structure point' := build :: (x : ℝ) (y : ℝ) (z : ℝ)

#check point'.build 2 (-1) 4

namespace point

def add (a b : point) : point := ⟨a.x + b.x, a.y + b.y, a.z + b.z⟩

protected theorem add_comm (a b : point) : add a b = add b a :=
begin
  rw [add, add],
  ext; dsimp,
  repeat { apply add_comm },
end

theorem add_x (a b : point) : (a.add b).x = a.x + b.x := rfl

def add_alt : point → point → point
| ⟨x₁, y₁, z₁⟩ ⟨x₂, y₂, z₂⟩ := ⟨x₁ + x₂, y₁ + y₂, z₁ + z₂⟩

theorem add_alt_comm (a b : point) : add_alt a b = add_alt b a :=
begin
  rcases a with ⟨xa, ya, za⟩,
  rcases b with ⟨xb, yb, zb⟩,
  simp [add_alt, add_comm],
end

protected theorem add_assoc (a b c : point) :
  (a.add b).add c = a.add (b.add c) :=
by { simp [add, add_assoc]}

def smul (r : ℝ) (a : point) : point :=
  ⟨r * a.x, r * a.y, r * a.z⟩

theorem smul_distrib (r : ℝ) (a b : point) :
  (smul r a).add (smul r b) = smul r (a.add b) :=
by { simp [add, smul, mul_add],}

end point

structure standard_two_simplex :=
  (x y z: ℝ)
  (x_nonneg : 0 ≤ x)
  (y_nonneg : 0 ≤ y)
  (z_nonneg : 0 ≤ z)
  (sum_eq : x + y + z = 1)

namespace standard_two_simplex

def swap_xy (a : standard_two_simplex) : standard_two_simplex :=
{
  x := a.y,
  y := a.x,
  z := a.z,
  x_nonneg := a.y_nonneg,
  y_nonneg := a.x_nonneg,
  z_nonneg := a.z_nonneg,
  sum_eq := by rw [add_comm a.y a.x, a.sum_eq],
}

noncomputable theory

def midpoint (a b : standard_two_simplex) : standard_two_simplex :=
{
  x := (a.x + b.x) / 2,
  y := (a.y + b.y) / 2,
  z := (a.z + b.z) / 2,
  x_nonneg := div_nonneg (add_nonneg a.x_nonneg b.x_nonneg) (by norm_num),
  y_nonneg := div_nonneg (add_nonneg a.y_nonneg b.y_nonneg) (by norm_num),
  z_nonneg := div_nonneg (add_nonneg a.z_nonneg b.z_nonneg) (by norm_num),
  sum_eq := by { field_simp, linarith [a.sum_eq, b.sum_eq]}
}

def weighted_average (lambda : real)
    (lambda_nonneg : 0 ≤ lambda) (lambda_le : lambda ≤ 1)
    (a b : standard_two_simplex) :
  standard_two_simplex :=
{
  x := lambda * a.x + (1 - lambda) * b.x,
  y := lambda * a.y + (1 - lambda) * b.y,
  z := lambda * a.z + (1 - lambda) * b.z,
  x_nonneg := by { apply add_nonneg, all_goals { apply mul_nonneg}, assumption,
    from a.x_nonneg, linarith [lambda_le], from b.x_nonneg },
  y_nonneg := by { apply add_nonneg, all_goals { apply mul_nonneg}, assumption,
    from a.y_nonneg, linarith [lambda_le], from b.y_nonneg },
  z_nonneg := by { apply add_nonneg, all_goals { apply mul_nonneg}, assumption,
    from a.z_nonneg, linarith [lambda_le], from b.z_nonneg },
  sum_eq :=
    begin
      transitivity (a.x + a.y + a.z) * lambda + (b.x + b.y + b.z) * (1 - lambda),
      { ring },
      simp [a.sum_eq, b.sum_eq]
    end
}

end standard_two_simplex

open_locale big_operators

structure standard_simplex (n : ℕ) :=
  (v : fin n → ℝ)
  (nonneg : ∀ i : fin n, 0 ≤ v i)
  (sum_eq_one : ∑ i, v i = 1)

namespace standard_simplex

def midpoint (n : ℕ) (a b : standard_simplex n) : standard_simplex n :=
{ v := λ i, (a.v i + b.v i) / 2,
  nonneg :=
    begin
      intro i,
      apply div_nonneg,
      { linarith [a.nonneg i, b.nonneg i] },
      norm_num
    end,
  sum_eq_one :=
    begin
      simp [div_eq_mul_inv, ←finset.sum_mul, finset.sum_add_distrib,
        a.sum_eq_one, b.sum_eq_one],
      field_simp
    end  }

end standard_simplex

structure is_linear (f : ℝ → ℝ) :=
(is_additive : ∀ x y, f (x + y) = f x + f y)
(preserves_mul : ∀ x c, f (c * x) = c * f x)

section
variables (f : ℝ → ℝ) (linf : is_linear f)

#check linf.is_additive
#check linf.preserves_mul

end

def preal := { y : ℝ | 0 < y}

section
variable x : preal

#check x.val
#check x.property

end

def standard_two_simplex' :=
{ p : ℝ × ℝ × ℝ // 0 ≤ p.1 ∧ 0 ≤ p.2.1 ∧ 0 ≤ p.2.2 ∧ p.1 + p.2.1 + p.2.2 = 1 }

def standard_simplex' (n : ℕ) :=
{ v : fin n → ℝ // (∀ i : fin n, 0 ≤ v i) ∧ (∑ i, v i = 1) }

def std_simplex := Σ n : ℕ, standard_simplex n

section
variable s : std_simplex

#check s.fst
#check s.snd

#check s.1
#check s.2

end


/-
This file contains the definition of the "explode" operation, which
creates a "powerset" of literals from a list of variables. Associated
theorems dealing with the contents of explode are also included.

Authors: Cayden Codel, Jeremy Avigad, Marijn Heule
Carnegie Mellon University
-/

import cnf.literal
import cnf.clause
import cnf.cnf

import data.nat.basic
import data.nat.pow

universe u

-- Represents the parametric type of the variable stored in the literal
variables {V : Type*}

open literal clause
open list function

namespace explode

variables {v : V} {l : list V} {c : clause V}

/-! # Explode -/

-- Produces a list of all possible polarities on a list of variables, maintaining order
def explode : list V → list (clause V)
| []        := [[]]
| (v :: l) := (explode l).map (cons (Pos v)) ++ (explode l).map (cons (Neg v))

@[simp] theorem explode_nil : explode ([] : list V) = [[]] := rfl

@[simp] theorem explode_singleton (v : V) : explode [v] = [[Pos v], [Neg v]] := rfl

theorem length_explode (l : list V) : length (explode l) = 2^(length l) :=
begin
  induction l with v vs ih,
  { refl },
  { simp only [explode, length_cons, length_append, length_map, pow_succ, two_mul, ih] }
end

theorem length_explode_pos (l : list V) : length (explode l) > 0 :=
by { rw length_explode, exact pow_pos zero_lt_two _ }

theorem exists_mem_explode (l : list V) : ∃ (c : clause V), c ∈ explode l :=
exists_mem_of_length_pos (length_explode_pos _)

theorem length_eq_of_mem_explode : c ∈ explode l → length c = length l :=
begin
  induction l with v vs ih generalizing c,
  { rw [explode_nil, mem_singleton],
    rintro rfl, refl },
  { simp only [explode, mem_append, mem_map],
    rintros (⟨c, hc, rfl⟩ | ⟨c, hc, rfl⟩);
    { simp [ih hc, length_cons] } }
end

theorem cons_mem_explode_cons_of_mem_explode (lit : literal V) :
  c ∈ explode l → (lit :: c) ∈ explode (lit.var :: l) :=
assume hc, by { cases lit; simp [explode, literal.var, hc] }

variable [decidable_eq V]

theorem mem_of_mem_explode_of_mem_vars : c ∈ explode l → v ∈ c.vars → v ∈ l :=
begin
  induction l with v vs ih generalizing c,
  { rw [explode_nil, mem_singleton],
    rintros rfl hv,
    rw clause.vars_nil at hv,
    exact absurd hv (finset.not_mem_empty _) },
  { simp only [explode, mem_append, mem_map],
    rintros (⟨a, ha, rfl⟩ | ⟨a, ha, rfl⟩);
    { rw [clause.vars, finset.mem_union, mem_cons_iff],
      rintros (hv | hv),
      { rw [var, finset.mem_singleton] at hv,
        exact or.inl hv },
      { exact or.inr (ih ha hv) } } }
end

theorem mem_vars_of_mem_explode_of_mem : c ∈ explode l → v ∈ l → v ∈ c.vars :=
begin
  induction l with v vs ih generalizing c,
  { intros _ hv, exact absurd hv (not_mem_nil v) } ,
  { simp only [explode, mem_append, mem_map],
    rintros (⟨a, ha, rfl⟩ | ⟨a, ha, rfl⟩) hv;
    { unfold clause.vars var,
      rcases eq_or_mem_of_mem_cons hv with rfl | hv,
      { exact finset.mem_union.mpr (or.inl (mem_singleton_self _)) },
      { exact finset.mem_union.mpr (or.inr (ih ha hv)) } } }
end

theorem not_mem_vars_of_mem_explode_of_not_mem : c ∈ explode l → v ∉ l → v ∉ c.vars :=
λ h hv, mt (mem_of_mem_explode_of_mem_vars h) hv

theorem not_mem_of_mem_explode_of_not_mem_vars : c ∈ explode l → v ∉ c.vars → v ∉ l :=
λ h hv, mt (mem_vars_of_mem_explode_of_mem h) hv

theorem pos_or_neg_of_mem_explode_of_mem : c ∈ explode l → v ∈ l → Pos v ∈ c ∨ Neg v ∈ c :=
λ h hv, mem_vars_iff_pos_or_neg_mem_clause.mp (mem_vars_of_mem_explode_of_mem h hv)

theorem pos_and_neg_not_mem_of_mem_explode_of_not_mem : c ∈ explode l → v ∉ l → Pos v ∉ c ∧ Neg v ∉ c :=
begin
  intro h,
  contrapose,
  rw not_and_distrib,
  simp only [not_not],
  intro hv,
  exact mem_of_mem_explode_of_mem_vars h
    (mem_vars_iff_pos_or_neg_mem_clause.mpr hv)
end

-- If the variables in a clause match the input to explode, then it's in explode
theorem map_var_eq_iff_mem_explode : c.map var = l ↔ c ∈ explode l :=
begin
  induction l with v vs ih generalizing c,
  { simp only [map_eq_nil, explode_nil, mem_singleton] },
  { split,
    { intro h,
      rcases exists_cons_of_map_cons h with ⟨l, ls, rfl, hl, hls⟩,
      cases l; simp only [explode, mem_append, mem_map],
      { rw var at hl, subst hl, left, use [ls, ih.mp hls] },
      { rw var at hl, subst hl, right, use [ls, ih.mp hls] } },
    { simp only [explode, mem_map, mem_append],
      rintros (⟨a, ha, rfl⟩ | ⟨a, ha, rfl⟩);
      { simp only [ih.mpr ha, var, map, eq_self_iff_true, and_self] } } }
end

/-! # nodup properties of explode -/

theorem explode_nodup (l : list V) : nodup (explode l) :=
begin
  induction l with v vs ih,
  { rw explode_nil, exact nodup_singleton nil },
  { simp only [explode, nodup_cons, nodup_append, (nodup_map_iff (cons_injective)).mpr ih, true_and],
    intros x hxp hxn,
    rcases mem_map.mp hxp with ⟨c, _, rfl⟩,
    rcases mem_map.mp hxn with ⟨d, _, h⟩,
    have := head_eq_of_cons_eq h,
    contradiction }
end

theorem nodup_of_nodup_of_mem (h : nodup l) : c ∈ explode l → nodup c :=
begin
  induction l with n ns ih generalizing c,
  { rw [explode_nil, mem_singleton], rintro rfl, exact nodup_nil },
  { simp only [explode, mem_append, mem_map],
    rintro (⟨a, ha, rfl⟩ | ⟨a, ha, rfl⟩);
    rw nodup_cons;
    have := pos_and_neg_not_mem_of_mem_explode_of_not_mem ha ((nodup_cons.mp h).1),
    { exact ⟨this.1, ih (nodup_cons.mp h).2 ha⟩ },
    { exact ⟨this.2, ih (nodup_cons.mp h).2 ha⟩ } }
end

theorem xor_pos_neg_mem_clause_of_nodup_of_mem_explode_of_mem (h : nodup l) :
  c ∈ explode l → v ∈ l → xor (Pos v ∈ c) (Neg v ∈ c) :=
begin
  induction l with v vs ih generalizing c,
  { intros _ h, exact absurd h (not_mem_nil _) },
  { simp only [explode, mem_append, mem_map],
    rintros (⟨a, ha, rfl⟩ | ⟨a, ha, rfl⟩) hc;
    { rcases eq_or_mem_of_mem_cons hc with (rfl | hv),
      { simp [pos_and_neg_not_mem_of_mem_explode_of_not_mem ha (nodup_cons.mp h).1] },
      { rcases ih (nodup_cons.mp h).2 ha hv with ⟨hp, hn⟩ | ⟨hp, hn⟩;
        { simp [hp, hn, ne_of_mem_of_not_mem hv (nodup_cons.mp h).1] } } } }
end

end explode


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

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 3 : "not" (`¬`)

We learn about how to manipulate `¬ P` in Lean.

# The definition of `¬ P`

In Lean, `¬ P` is *defined* to mean `P → false`. So `¬ P` and `P → false`
are *definitionally equal*. Check out the explanation of definitional
equality in the "equality" section of Part B of the course notes:
https://www.ma.imperial.ac.uk/~buzzard/xena/formalising-mathematics-2023/Part_B/equality.html

## Tactics

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

* `change`
* `by_contra`
* `by_cases`

-/

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

variables (P Q R : Prop)

example : ¬ true → false :=
begin
  intro h,
  change true → false at h,
  apply h,
  triv,
end

example : false → ¬ true :=
begin
  intro h,
  intro h2,
  exact h,
end

example : ¬ false → true :=
begin
  intro h,
  triv,
end

example : true → ¬ false :=
begin
  intro h,
  intro h2,
  exact h2,
end

example : false → ¬ P :=
begin
  intro h,
  intro hP,
  exact h,
end

example : P → ¬ P → false :=
begin
  intro hP,
  intro hnP,
  apply hnP,
  exact hP,
end

example : P → ¬ (¬ P) :=
begin
  intro hP,
  intro hnP,
  apply hnP,
  exact hP,
end

example : (P → Q) → (¬ Q → ¬ P) :=
begin
  intros hPQ hnQ hP,
  apply hnQ,
  apply hPQ,
  assumption,
end

example : ¬ ¬ false → false :=
begin
  intro h,
  apply h,
  intro h2,
  exact h2,
end

example : ¬ ¬ P → P :=
begin
  intro h,
  by_contra h2,
  apply h,
  exact h2,
end

example : (¬ Q → ¬ P) → (P → Q) :=
begin
  intros h hP,
  change (Q → false) → (P → false) at h,
  by_contra hnQ,
  apply h,
  { exact hnQ, },
  { exact hP }
end