/- Problem 1: Append and Reverse -/

/- Prove the following theorems. If you can't complete a proof,
you can use the tactic `sorry` for any part that you are
unable to complete. -/

/- Note that we again define our own append reverse,
this time using the built in types -/

-- part p1
def app : List Nat -> List Nat -> List Nat
  | List.nil,       bs => bs
  | List.cons a as, bs => List.cons a (app as bs)

def rev : List Nat -> List Nat
  | List.nil => List.nil
  | List.cons a L => app (rev L) (List.cons a List.nil)

-- 6 lines
theorem app_nil : forall (l : List Nat), app l [] = l :=
  by intros l
     induction l
     case nil => rfl
     case cons x xs ih =>
     simp only [app]
     rw [ih]

-- 6 lines
theorem app_assoc : forall (l1 l2 l3 : List Nat),
  app (app l1 l2) l3 = app l1 (app l2 l3) :=
 by intros l3
    induction l3
    case nil =>
      intros l1 l2
      rfl
    case cons l3 IH =>
      intros l1 l2
      simp only [app]
      rw [IH]

-- 8 lines
theorem rev_app_distr: forall l1 l2 : List Nat,
  rev (app l1 l2) = app (rev l2) (rev l1) :=
 by intros l1 l2
    induction l1
    case nil =>
      simp only [app]
      simp only [rev]
      rw [app_nil]
    case cons h t IH =>
      simp only [rev]
      rw [IH]
      rw [app_assoc]

-- 8 lines
theorem rev_involutive : forall l : List Nat,
  rev (rev l) = l :=
 by intros l
    induction l
    case nil => rfl
    case cons h t IH =>
      simp only [rev]
      rw [rev_app_distr]
      rw [IH]
      rfl
-- part p1

/- Problem 2: Evenness (and Relations) -/

/- Prove the following theorems. If you can't complete a proof,
you can use the tactic `sorry` for any part that you are
unable to complete. -/

-- part p2
inductive ev : Nat -> Prop
| O : ev 0
| SS (n : Nat) (H : ev n) : ev (Nat.succ (Nat.succ n))

def double : Nat -> Nat
| 0 => 0
| Nat.succ n => Nat.succ (Nat.succ (double n))

-- 5 lines
theorem ev_double : forall n, ev (double n) :=
 by intro n
    induction n <;> simp only [double]
    constructor
    apply ev.SS
    assumption

-- 15 lines
theorem ev_sum : forall n m, ev n -> ev m -> ev (Nat.add n m) :=
 by intros n m evn evm
    induction evm
    case O =>
      simp only [Nat.add]
      assumption
    case SS =>
    apply ev.SS
    assumption

-- 3 lines
theorem three_not_ev : Not (ev 3) :=
 by intro n
    cases n
    contradiction

inductive ev' : Nat -> Prop :=
  | O : ev' 0
  | SSO : ev' 2
  | sum n m (Hn : ev' n) (Hm : ev' m) : ev' (Nat.add n m)

-- 21 lines
theorem ev'_ev : forall n, ev' n <-> ev n :=
 by intro n
    constructor
    case mp =>
      intros ev'n
      induction ev'n
      case O =>
        apply ev.O
      case SSO =>
        apply ev.SS
        apply ev.O
      case sum =>
      apply ev_sum
      assumption
      assumption
    case mpr =>
      intros evn
      induction evn
      case O =>
        apply ev'.O
      case SS =>
        rw [Nat.succ_eq_add_one]
        rw [Nat.add_assoc]
        apply ev'.sum
        assumption
        apply ev'.SSO

-- part p2

/- Problem 3: Subsequences -/

/- Prove the following theorems. If you can't complete a proof,
you can use the tactic `sorry` for any part that you are
unable to complete. -/

-- part p3
inductive subseq : List Nat -> List Nat -> Prop
| empty : subseq [] []
| include x l1 l2 (H : subseq l1 l2) : subseq (x::l1) (x::l2)
| skip x l1 l2 (H : subseq l1 l2) : subseq l1 (x::l2)

-- 6 lines
theorem subseq_refl : forall (l : List Nat),
  subseq l l :=
 by intros l
    induction l
    case nil => apply subseq.empty
    case cons x xs IH => apply subseq.include x xs xs IH

-- 5 lines
theorem subseq_empty : forall l, subseq [] l :=
 by intros l
    induction l
    case nil => apply subseq.empty
    case cons x xs IH => apply subseq.skip x [] xs IH

-- 13 lines
theorem subseq_app : forall (l1 l2 l3 : List Nat),
  subseq l1 l2 ->
  subseq l1 (List.append l2 l3) :=
 by intros l1 l2 l3 IH
    induction IH
    case empty => apply subseq_empty l3
    case include x l1' _ H' =>
      apply subseq.include
      apply H'
    case skip x l1' _ H' =>
      apply subseq.skip
      apply H'
-- part p3

/- Problem 4: Insertion Sort -/

/- Prove the following theorems. If you can't complete a proof,
you can use the tactic `sorry` for any part that you are
unable to complete. -/

-- part p4
def insert : Nat -> List Nat -> List Nat
| y, [] => [y]
| y, x::xs => if Nat.ble y x
              then y :: x :: xs
              else x :: insert y xs

def isort : List Nat -> List Nat
| []      => []
| x :: xs => insert x (isort xs)

inductive All : {T : Type} -> (T -> Prop) -> List T -> Prop
| nil : All P []
| cons : forall x L, P x -> All P L -> All P (x :: L)

inductive Sorted : List Nat -> Prop
| nil : Sorted []
| cons : forall n l, Sorted l ->
                     All (Nat.le n) l ->
                     Sorted (n :: l)

theorem all_trans : forall (P : T -> Prop) (Q : T -> Prop) L,
  All P L ->
  (forall x, P x -> Q x) ->
  All Q L :=
 by intros P Q L Hall PtoQ
    induction Hall
    case nil => constructor
    case cons HL =>
      constructor
      apply PtoQ
      assumption
      assumption

-- 23 lines
theorem insert_le : forall n x l,
  All (Nat.le n) l ->
  Nat.le n x ->
  All (Nat.le n) (insert x l) :=
 by intros n x l alen nlex
    induction alen
    case nil =>
      simp only [insert]
      constructor
      assumption
      constructor
    case cons  =>
      simp only [insert]
      generalize hyp : Nat.ble x ‹Nat› = B
      cases B
      case false =>
        simp only [ite_false]
        constructor
        assumption
        assumption
      case true =>
        simp only [ite_true]
        constructor
        assumption
        constructor
        assumption
        assumption

theorem ble_inv : forall a b,
                  Nat.ble a b = false
               -> Nat.ble b a = true :=
 by intros a b H
    rw [Nat.ble_eq]
    cases (Nat.le_total b a)
    assumption
    rw [<- Nat.not_lt_eq]
    rw [<- Bool.not_eq_true] at H
    rw [Nat.ble_eq] at H
    contradiction

-- 37 lines
theorem insert_sorted : forall x l,
  Sorted l ->
  Sorted (insert x l) :=
 by intros x l sortedl
    induction sortedl
    case nil =>
      constructor
      constructor
      constructor
    case cons x' l' sortedl' IH1 IH2 =>
      simp only [insert]
      generalize Hyp : Nat.ble x x' = B
      cases B
      case false =>
        simp only [ite_false]
        constructor
        assumption
        apply insert_le
        assumption
        apply Nat.le_of_ble_eq_true
        apply ble_inv
        assumption
      case true =>
        simp only [ite_true]
        constructor
        constructor
        assumption
        assumption
        constructor
        apply Nat.le_of_ble_eq_true
        assumption
        apply all_trans
        assumption
        intros x1 nle
        induction nle
        case a.a.a.refl =>
          apply Nat.le_of_ble_eq_true
          assumption
        case a.a.a.step nle2 =>
          constructor
          assumption

-- 8 lines
theorem isort_sorted : forall l, Sorted (isort l) :=
 by intros l
    induction l
    case nil => constructor
    case cons ht ln H => rw[isort]
                         apply insert_sorted
                         assumption

inductive Permutation : {T : Type} -> List T -> List T -> Prop
| nil   : Permutation [] []
| skip  : forall (x : A) (l l' : List A),
          Permutation l l' ->
          Permutation (x :: l) (x :: l')
| swap  : forall (x y : A) (l : List A),
          Permutation (y :: x :: l) (x :: y :: l)
| trans : forall l l' l'' : List A,
          Permutation l l' ->
          Permutation l' l'' ->
          Permutation l l''

example : Permutation [true,true,false] [false,true,true] :=
 by apply Permutation.trans (l' := [true,false,true])
    . apply Permutation.skip
      apply Permutation.swap
    . apply Permutation.swap

-- 6 lines
theorem perm_refl : forall {T : Type} (l : List T),
  Permutation l l :=
 by intros t l
    induction l
    case nil =>
     apply Permutation.nil
    case cons x xs ih =>
    constructor
    assumption

-- 10 lines
theorem perm_length : forall {T : Type} (l1 l2 : List T),
  Permutation l1 l2 -> l1.length = l2.length :=
 by intros t l1 l2 Perml1l2
    induction Perml1l2
    case nil => constructor
    case skip p l =>
      simp
      assumption
    case swap => constructor
    case trans p1 p2 =>
      rw [p1]
      assumption

-- 12 lines
theorem perm_sym : forall {T : Type} (l1 l2 : List T),
  Permutation l1 l2 -> Permutation l2 l1 :=
 by intros t L1 L2 Perm
    induction Perm
    case nil =>
      constructor
    case skip =>
      constructor
      assumption
    case swap =>
      constructor
    case trans =>
      constructor
      assumption
      assumption

-- 18 lines
theorem insert_perm : forall x l,
  Permutation (x :: l) (insert x l) :=
 by intros x l
    induction l
    case nil => simp only[insert]
                constructor
                constructor
    case cons f r ih => simp only [insert]
                        generalize H : Nat.ble x f = Z
                        cases Z
                        case true => simp
                                     apply perm_refl
                        case false => simp
                                      apply Permutation.trans (l' := (f :: x :: r))
                                      apply Permutation.swap
                                      apply Permutation.skip
                                      assumption

-- 10 lines
theorem isort_perm : forall l, Permutation l (isort l) :=
 by intros l
    induction l
    case nil =>
      simp only [isort]
      constructor
    case cons h t IH =>
      constructor
      apply Permutation.skip
      assumption
      simp only [isort]
      apply insert_perm

-- part p4


import MyNat.Addition
import MyNat.Multiplication
import AdditionWorld.Level6 -- add_right_comm
namespace MyNat
open MyNat

/-!
# Multiplication World

## Level 6: `succ_mul`

We now begin our journey to `mul_comm`, the proof that `a * b = b * a`.
We'll get there in level 8. Until we're there, it is frustrating
but true that we cannot assume commutativity. We have `mul_succ`
but we're going to need `succ_mul` (guess what it says -- maybe you
are getting the hang of Lean's naming conventions).

Remember also that we have tools like

* `add_right_comm a b c : a + b + c = a + c + b`

These things are the tools we need to slowly build up the results
which we will need to do mathematics "normally".
We also now have access to Lean's `simp` tactic,
which will solve any goal which just needs a bunch
of rewrites of `add_assoc` and `add_comm`. Use if
you're getting lazy!

## Lemma
For all natural numbers `a` and `b`, we have `succ a * b = ab + b.`
-/
lemma succ_mul (a b : MyNat) : succ a * b = a * b + b := by
  induction b with
  | zero =>
    rw [zero_is_0]
    rw [mul_zero]
    rw [mul_zero]
    rw [add_zero]
  | succ b ih =>
    rw [mul_succ]
    rw [mul_succ]
    rw [ih]
    rw [add_succ]
    rw [add_succ]
    rw [add_right_comm]

/-!
Next up is [Multiplication Level 7](./Level7.lean.md).
-/


/-
REESE ALLEN (rga2uz)
-/

import .satisfiability
import .rules_of_reasoning

/-
CS2102 Exam #2: Propositional Logic
-/

/-
#1. Define a predicate function, is_satisfiable: pExp → bool
that returns true iff the given proposition is satisfiable.
Hint: Model your answer on our definition of is_valid. You
may need to define one or more helper functions. You should
test your solution, but we will only grade your definition.
Please write test cases in a separate file. Do not submit
that file.
-/

-- both of these helper functions do the same thing
def contains_tt : list bool → bool
| [] := ff
| (list.cons tt l) := tt
| (list.cons b l) := contains_tt l

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

def is_satisfiable (e : pExp) : bool :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    let rs := map_pEval_over_interps e is in
    foldr_or rs

/-
#2. Clearly the propositions in rules_of_reasoning.lean that
are valid are also satisfiable. But what about the following?
Apply your satisfiability predicate function to decide whether
or not each of the following formulae is satisfiable or not.
Write an "#eval is_satisfiable e" command for each expression.

A. The "fallacies" in that file (the ones that aren't valid).

B. The proposition, pFalse.

C. The following propositions:
1. P ∧ ¬ P
2. P ∨ ¬ P
3. (¬x1 ∨ x2) ∧ (¬x2 ∨ x3 ∨ ¬x4) ∧ (x1 ∨ x2 ∨ x3 ∨ ¬x4)

Note that for 3. you will have to define four new variables.
Call them x1, x2, x3, and x4.
-/

open pExp

-- A.
#eval is_satisfiable affirm_consequence -- tt
#eval is_satisfiable affirm_disjunct -- tt
#eval is_satisfiable deny_antecedent -- tt

-- B.
#eval is_satisfiable pFalse -- ff
def false_intro : pExp := pFalse
#eval is_satisfiable false_intro -- ff

-- C.
def x1 := pVar (var.mk 0)
def x2 := pVar (var.mk 1)
def x3 := pVar (var.mk 2)
def x4 := pVar (var.mk 3)

#eval is_satisfiable (P ∧ ¬ P) -- ff
#eval is_satisfiable (P ∨ ¬ P) -- tt
#eval is_satisfiable ((¬x1 ∨ x2) ∧ (¬x2 ∨ x3 ∨ ¬x4) ∧ (x1 ∨ x2 ∨ x3 ∨ ¬x4)) -- tt

def e1 := P ∧ ¬ P
def e2 := P ∨ ¬ P
def e3 := (¬x1 ∨ x2) ∧ (¬x2 ∨ x3 ∨ ¬x4) ∧ (x1 ∨ x2 ∨ x3 ∨ ¬x4)

#eval is_satisfiable e1 -- ff
#eval is_satisfiable e2 -- tt
#eval is_satisfiable e3 -- tt

/-
3. In the previous problems, you defined a satisfiability
predicate function that returns true or false depending on
whether a given formula is satisfiable or not. Often we will
want to know not only whether there exists a solution but an
actual example of a solution, if there is one.

Define a function
called sat_solver : pExp → option (var → bool) that returns a
satisfying interpretation (as "some interpretation") if there
is one, or "none" otherwise. Hint: Model your solution on our
validity checker. First compute the list of interpretations for
a given expression, e, then reduce that list to a value of type
option (var → bool). Evaluate e under each interpretation in
the list until either (A) you find one, call it i, for which e
evaluates to true, or until you reach the empty list, empty
handed.
-/

def sat_helper : pExp → list (var → bool) → option(var → bool)
| e [] := option.none
| e (h::t) := match pEval e h with
            | tt := option.some h
            | ff := sat_helper e t
            end

def sat_solver (e : pExp) : option (var → bool) :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    sat_helper e is

/-
4. Define a predicate function, is_unsatisfiable : pExp → bool,
that takes a propositional logic formula, e, and returns true
iff e is unsatisfiable. Hint: Easy. Build on what you have.
-/

def is_unsatisfiable (e : pExp) : bool :=
    let tf := is_satisfiable e in
    (match tf with
    | ff := tt
    | tt := ff
    end)

/-
5. Given a propositional logic expression, e, and an incorrect
claim that it's valid, we often want to produce a counter-example
to that claim. Such a counter example is an interpretation under
which the expression is not true. Equivalently, a counterexample
is an interpretation under which the *negation* of the expression
*is* true.

Define a function, counterexample : pExp → option (var → bool),
that takes any expression e and returns either a counterexample
(as "some interpretation") or "none" if there isn't one. Hint:
given e, try to find a model for the expression, ¬ e.
-/

/- you can either 1. iterate over the list of interpretations for a given
expression until finding an interpretation i for which the expression is false
and return option(i) otherwise none, OR 2. negate the expression and iterate
over a list of intepretations until finding one i for which ¬ expression
is true and return option(i) otherwise none  -- both of these should lead
to the same return value
-/

def counter_helper : pExp → list (var → bool) → option(var → bool)
| e [] := option.none
| e (h::t) := match pEval e h with
            | ff := option.some h
            | tt := counter_helper e t
            end

def counterexample (e : pExp) : option (var → bool) :=
    let n := number_of_variables_in_expression e in
    let is := interpretations_for_n_vars n in
    counter_helper e is

/-
6. Give an English language example for every valid rule of
reasoning in rules_of_reasoning.lean, and also give an English
language counter-example for each fallacy.

For example, for the rule, P >> Q >> P ∧ Q, you could say,
"If the ball is red then if (in addition to that) the box
is blue, then (in this context) the ball is red AND the box
is blue." It will be easiest is you re-use the same words
for P, Q, and R, in all of your examples. E.g., P means "the
ball is red", Q means "the box is blue", etc.

Copy the contents of rules_of_reasoning.lean into this comment
block and under each rule give your English language sentence.

def true_intro : pExp := pTrue
One water molecule is composed of two hydrogen atoms and one oxygen atom.
-- statement that is objectively/ always true

def false_elim := pFalse >> P
If it is true that his leg is broken, then we walks with a limp

def true_imp := pTrue >> P
If his leg is broken, then he walks with a limp.
-- If it is objectively/ always true that his leg is broken, and he
walks with a limp, then the implication is true. If he does not walk with
a limp, then the implication is false.

def and_intro := P >> Q >> P ∧ Q
If we assume his leg is broken, then, in addition, we assume he walks with
a limp, then, in this context, his leg is broken and he walks with a limp

def and_elim_left := P ∧ Q >> P
If we assume that his leg is broken and he walks with a limp, then it must
be true that his leg is broken.

def and_elim_right := P ∧ Q >> Q
If we assume that his leg is broken and he walks with a limp, then it must
be true that he walks with a limp.

def or_intro_left := P >> P ∨ Q
If we waasume that his leg is broken, then it it must be true that his leg
is broken or he walks with a limp

def or_intro_right := Q >> P ∨ Q
If we assume that he walks with a limp, then it must be true that his leg
is broken or we walks with a limp.

def or_elim := P ∨ Q >> (P >> R) >> (Q >> R) >> R
If we assume that it is true that he walks with a limp or his leg is broken,
then, in addition to that, we assume it is true that his leg being broken
means that he is in pain, then, in addition to that, we assume him
walking with a limp means he is in pain, we can deduce that he is in pain.

def iff_intro := (P >> Q) >> (Q >> P) >> (P ↔ Q)
If his leg is broken, then he walks with a limp, and, in additon to that, if
he walks with a limp his leg is broken, then we can deduce that his leg is
broken if and only if he walks with a limp.

def iff_intro' := (P >> Q) ∧ (Q >> P) >> (P ↔ Q)
If we assume that his leg is broken, then he walks with a limp, and
we assume that if he walks with a limp, then his leg is broken, it must be
true that his leg is broken if and only if he walks with a limp.

def iff_elim_left := (P ↔ Q) >> (P >> Q)
If we assume that his leg is broken if and only if he walks with a limp, then
it must be true that, if we assume his leg is broken, he walks with a limp.

def iff_elim_right := (P ↔ Q) >> (Q >> P)
If we assume that his leg is broken if and only if he walks with a limp, then
it must be true that, if we assume he walks with a limp, his leg is broken

def arrow_elim := (P >> Q) >> (P >> Q)
If we assume that if his leg is broken, then he walks with a limp, then
it must be true that if we assume his leg is broken, he walks with a limp.

def resolution := (P ∨ Q) >> (¬ Q ∨ R) >> (P ∨ R)
If we assume that his leg is broken or he walks with a limp, and, in
addition to that, if we assume that his leg s not broken or he is in pain,
then it must be true that his leg is broken or he is in pain.

def unit_resolution := (P ∨ Q) >> ((¬ Q) >> P)
If we assume that his leg is broken or he he walks with a limp, then we can
deduce that, assuming his leg is not broken, it must be true he walks with a limp.

def syllogism := (P >> Q) >> (Q >> R) >> (P >> R)
If we assume his leg being broken means that he walks with a limp, and in
addition to that we assume that him walking with a limp means he is in pain,
then it must be true that his leg being broken means he is in pain.

def modus_tollens := (P >> Q) >> (¬ Q >> ¬ P)
If his leg being broken means that he is in pain, then it must be true
that him not being in pain means that his leg is not broken.

def neg_elim := (¬ ¬ P) >> P
If it is not true that his leg his not broken, then it must be true that
his leg is broken.

def excluded_middle := P ∨ (¬ P)
His leg is broken or his leg is not broken.

def neg_intro := (P >> pFalse) >> (¬ P)
If we assume that his leg being broken is false, then it must be true that
his leg is not broken.

def affirm_consequence := (P >> Q) >> (Q >> P)
If he walks with a limp, then his leg is broken; therefore, if his leg
is broken, he walks with a limp. F

def affirm_disjunct := (P ∨ Q) >> (P >> ¬ Q)
If we assume that his leg is broken or he walks with a limp, then it must
be true that his leg being broken means he walks with a limp. F

def deny_antecedent := (P >> Q) >> (¬ P >> ¬ Q)
If we assume that his leg not being broken implies that he walks with a limp,
then it must be true that his leg being broken means he does not walk
with a limp. F

-/


import ..lectures.love01_definitions_and_statements_demo

/-! # LoVe Exercise 1: Definitions and Statements

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

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Truncated Subtraction

1.1. Define the function `sub` that implements truncated subtraction on natural
numbers by recursion. "Truncated" means that results that mathematically would
be negative are represented by 0. For example:

    `sub 7 2 = 5`
    `sub 2 7 = 0` -/

def sub : ℕ → ℕ → ℕ :=
sorry

/-! 1.2. Check that your function works as expected. -/

#eval sub 0 0   -- expected: 0
#eval sub 0 1   -- expected: 0
#eval sub 0 7   -- expected: 0
#eval sub 1 0   -- expected: 1
#eval sub 1 1   -- expected: 0
#eval sub 3 0   -- expected: 3
#eval sub 2 7   -- expected: 0
#eval sub 3 1   -- expected: 2
#eval sub 3 3   -- expected: 0
#eval sub 3 7   -- expected: 0
#eval sub 7 2   -- expected: 5

/-! ## Question 2: Arithmetic Expressions

Consider the type `aexp` from the lecture and the function `eval` that
computes the value of an expression. You will find the definitions in the file
`love01_definitions_and_statements_demo.lean`. One way to find them quickly is
to

1. hold the Control (on Linux and Windows) or Command (on macOS) key pressed;
2. move the cursor to the identifier `aexp` or `eval`;
3. click the identifier. -/

#check aexp
#check eval

/-! 2.1. Test that `eval` behaves as expected. Make sure to exercise each
constructor at least once. You can use the following environment in your tests.
What happens if you divide by zero?

Make sure to use `#eval`. For technical reasons, `#reduce` does not work well
here. Note that `#eval` (Lean's evaluation command) and `eval` (our evaluation
function on `aexp`) are unrelated. -/

def some_env : string → ℤ
| "x" := 3
| "y" := 17
| _   := 201

#eval eval some_env (aexp.var "x")   -- expected: 3
-- invoke `#eval` here

/-! 2.2. The following function simplifies arithmetic expressions involving
addition. It simplifies `0 + e` and `e + 0` to `e`. Complete the definition so
that it also simplifies expressions involving the other three binary
operators. -/

def simplify : aexp → aexp
| (aexp.add (aexp.num 0) e₂) := simplify e₂
| (aexp.add e₁ (aexp.num 0)) := simplify e₁
-- insert the missing cases here
-- catch-all cases below
| (aexp.num i)               := aexp.num i
| (aexp.var x)               := aexp.var x
| (aexp.add e₁ e₂)           := aexp.add (simplify e₁) (simplify e₂)
| (aexp.sub e₁ e₂)           := aexp.sub (simplify e₁) (simplify e₂)
| (aexp.mul e₁ e₂)           := aexp.mul (simplify e₁) (simplify e₂)
| (aexp.div e₁ e₂)           := aexp.div (simplify e₁) (simplify e₂)

/-! 2.3. Is the `simplify` function correct? In fact, what would it mean for it
to be correct or not? Intuitively, for `simplify` to be correct, it must
return an arithmetic expression that yields the same numeric value when
evaluated as the original expression.

Given an environment `env` and an expression `e`, state (without proving it)
the property that the value of `e` after simplification is the same as the
value of `e` before. -/

lemma simplify_correct (env : string → ℤ) (e : aexp) :
  true :=   -- replace `true` by your lemma statement
sorry

/-! ## Question 3: λ-Terms

3.1. Complete the following definitions, by replacing the `sorry` markers by
terms of the expected type.

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

def I : α → α :=
λa, a

def K : α → β → α :=
λa b, a

def C : (α → β → γ) → β → α → γ :=
sorry

def proj_1st : α → α → α :=
sorry

/-! Please give a different answer than for `proj_1st`. -/

def proj_2nd : α → α → α :=
sorry

def some_nonsense : (α → β → γ) → α → (α → γ) → β → γ :=
sorry

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

-- write your solution in a comment here or on paper

end LoVe


import data.real.basic
import algebra.pi_instances -- to make "a + b" work

/-- is_limit a t means that t is the limit of the sequence a(0), a(1),...-/
def is_limit (a : ℕ → ℝ) (t : ℝ) : Prop :=
∀ ε, (0 : ℝ) < ε → ∃ N, ∀ n, N ≤ n → abs (a n - t) ≤ ε

/-
A note on lambda notation.

λ n, n^2 is the squaring function. A mathematician might write it
  n ↦ n^2 . Note that the advantage of these notations is that you
  can talk about the function without ever giving it a name; if you
  say "f(x)=x^2" then you just used up f.

(λ n, c) is the function that takes n as input, and then always returns c.
So it's a constant function.
-/

-- abs_zero is the lemma that abs 0 = 0

theorem limit_const (c : ℝ) : is_limit (λ n, c) c :=
begin
  sorry
end

-- For this one you might find `abs_le` useful.

-- sum of the limits is the limit of the sums

theorem limit_add (a b : ℕ → ℝ) (s t : ℝ) :
  is_limit a s → is_limit b t → is_limit (a + b) (s + t) :=
begin
  sorry,
end

-- A sequence has at most one limit

theorem limit_unique (a : ℕ → ℝ) (s t : ℝ) :
  is_limit a s → is_limit a t → s = t :=
begin
  sorry,
end

/-
Hints:
1) This might be trickier than you think.
2) Prove by contradiction. WLOG s < t
3) Use linarith liberally
4) `abs_le` is helpful
-/


/-
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 group_theory.subgroup.basic -- imports the theory of subgroups

/-!

# Subgroups

In Lean, a subgroup is not a group. This is for the same reason that a subset
is not a type. The way subgroups work is that if `G` is a group:

`variables (G : Type) [group G]`

then the type of *all* subgroups of `G` is called `subgroup G`. So if you want
one subgroup of G:

`variable (H : subgroup G)`

then, as you can see, `H` is not a type (we don't write `H : Type`), it's a term.
So how do we do elements of `H` then? It's just the same as sets: an element `x` of
`H` is a term `x` of type `G`, such that the proposition `x ∈ H` holds.

Here's the basic API for subgroups.

-/

-- Let `G` be a group, let `a` and `b` be elements of `H`, and let `H` and `K` be subgroups of `G`.
variables (G : Type) [group G] (a b : G) (H K : subgroup G)

-- The basic API for subgroups
example : (1 : G) ∈ H := one_mem H
example (ha : a ∈ H) : a⁻¹ ∈ H := inv_mem ha
example (ha : a ∈ H) (hb : b ∈ H) : a * b ∈ H := mul_mem ha hb

-- Let's use these axioms to make more API for subgroups.

-- First, see if you can put the axioms together to prove subgroups are closed under "division".
example (ha : a ∈ H) (hb : b ∈ H) : a * b⁻¹ ∈ H :=
begin
  apply mul_mem ha,
  exact inv_mem hb,
end

-- Now try these. You might want to remind yourself of the API for groups as explained
-- in an earlier section, or make use of the `group` tactic.

-- This lemma is called `inv_mem_iff` but try proving it yourself
example : a⁻¹ ∈ H ↔ a ∈ H :=
begin
  split,
  { intro h,
    -- a bit of advanced `rw` syntax: instead of `have h2 : a = a⁻¹⁻¹, group, rw h2` you can
    -- do the rewrite without ever defining `h2`.
    rw (show a = a⁻¹⁻¹, by group),
    exact inv_mem h, },
  -- this is the easier way
  { exact inv_mem, },
end

-- this is `mul_mem_cancel_left` but see if you can do it from the axioms of subgroups.
-- Again feel free to use the `group` tactic.
example (ha : a ∈ H) : a * b ∈ H ↔ b ∈ H :=
begin
  split,
  { intro hab,
    rw ← inv_mem_iff,
    rw (show b⁻¹ = (a * b)⁻¹ * a, by group),
    refine mul_mem _ ha,
    exact inv_mem hab, },
  { exact mul_mem ha, },
end

/-

## Complete lattice structure on subgroups of G

Subgroups of a group form a complete lattice. Let's just check this:

-/

example : complete_lattice (subgroup G) := infer_instance

/-

The "type class inference system" (the system which deals with square bracket inputs to
functions) already knows this. `infer_instance` means "find this construction in the
database".

Because subgroups are a complete lattice, there will be a smallest subgroup `⊥` of `G`
and a largest subgroup `⊤`. You can guess what these are (note that `⊥` isn't the empty set,
this isn't a subgroup). Let's get the hang of these subgroups. Here's their API
(by the way, I don't have a clue about these APIs, I don't have them all committed to memory,
I just write down the natural statements and then either guess the names of the proofs or
use `library_search`):

-/

example : a ∈ (⊥ : subgroup G) ↔ a = 1 := subgroup.mem_bot
example : a ∈ (⊤ : subgroup G) := subgroup.mem_top a

/-

# Conjugating a subgroup by an element.

Let's define the conjugate `xHx⁻¹` of a subgroup `H` by an element `x`. To do this we
are going to have to know how to *make* subgroups, not just prove things about subgroups.

To create a subgroup of `G`, you need to give a subset of `G` and then a proof
that the subset satisfies the three axioms `one_mem`, `inv_mem` and `mul_mem` for subgroups.
If `H : subgroup G` and `x : G` then the *conjugate* of `H` by `x` is
the set `{a : G | ∃ h, h ∈ H ∧ a = x * h * x⁻¹}`. So let's show that this set
satisfies the axioms for a subgroup.

-/

variables {G H} {x : G}

variables (y z : G)

lemma conjugate.one_mem : (1 : G) ∈ {a : G | ∃ h, h ∈ H ∧ a = x * h * x⁻¹} :=
begin
  use 1,
  split,
  { exact one_mem H, },
  { group, },
end

lemma conjugate.inv_mem (hy : y ∈ {a : G | ∃ h, h ∈ H ∧ a = x * h * x⁻¹}) :
  y⁻¹ ∈ {a : G | ∃ h, h ∈ H ∧ a = x * h * x⁻¹} :=
begin
  rcases hy with ⟨g, hg, rfl⟩,
  use [g⁻¹, inv_mem hg],
  group,
end

lemma conjugate.mul_mem (hy : y ∈ {a : G | ∃ h, h ∈ H ∧ a = x * h * x⁻¹})
  (hz : z ∈ {a : G | ∃ h, h ∈ H ∧ a = x * h * x⁻¹}) :
  y * z ∈ {a : G | ∃ h, h ∈ H ∧ a = x * h * x⁻¹} :=
begin
  rcases hy with ⟨g, hg, rfl⟩,
  rcases hz with ⟨k, hk, rfl⟩,
  use [g * k, mul_mem hg hk],
  group,
end

-- Now here's the way to put everything together:
def conjugate (H : subgroup G) (x : G) : subgroup G :=
{ carrier := {a : G | ∃ h, h ∈ H ∧ a = x * h * x⁻¹},
  one_mem' := conjugate.one_mem,
  inv_mem' := conjugate.inv_mem,
  mul_mem' := conjugate.mul_mem, }

/-

## The cost of a definition

You might think "we're done, we made conjugates"! But not so fast!

If we were putting the definition of `conjugate` into mathlib then the next thing we would have to
do would be to prove a whole bunch of things about it. Every definition in a formalised system
comes with a cost. If you just make the definition and don't prove theorems about it,
then other people can't use your definition easily in their theorems.

What kind of theorems would we want to prove about conjugates? We might want to prove
that if `H ≤ K` then `conjugate H x ≤ conjugate K x`. We might want to prove
that `conjugate ⊥ x = ⊥` and `conjugate ⊤ x = ⊤`. And we might want to prove
that if `G` is abelian then `conjugate H x = H` for all `H`. Before we embark on this,
I had better tell you how to prove that two subgroups of a group are equal in Lean.
To check two subgroups are equal it suffices to prove they have the same elements:
this is called "extensionality" for subgroups, and you can make this step using the `ext`
tactic. I'll show you below.

Let's make some API for conjugates. I'll suggest some names for the lemmas.

-/

-- This one is always handy: you will be able to `rw` it when faced with goals
-- of the form `a ∈ conjugate H x`.

lemma mem_conjugate_iff : a ∈ conjugate H x ↔ ∃ h, h ∈ H ∧ a = x * h * x⁻¹ :=
begin
  -- true by definition!
  refl,
end

lemma conjugate_mono (H K : subgroup G) (h : H ≤ K) : conjugate H x ≤ conjugate K x :=
begin
  -- start with `intro g` because the goal is definitionally
  -- `∀ g, g ∈ conjugate H x → g ∈ conjugate K x`
  intro g,
  rintro ⟨t, ht, rfl⟩,
  exact ⟨t, h ht, rfl⟩,
end

lemma conjugate_bot : conjugate ⊥ x = ⊥ :=
begin
  -- recall that ⊥ is the trivial subgroup and I showed you the basic API for it above.
  -- Start this proof with `ext a`.
  ext a,
  rw mem_conjugate_iff,
  rw subgroup.mem_bot,
  split,
  { rintro ⟨b, hb, rfl⟩,
    rw subgroup.mem_bot at hb,
    rw hb,
    group, },
  { rintro rfl,
    use 1,
    split,
    { rw subgroup.mem_bot, },
    { group, } } ,
end

lemma conjugate_top : conjugate ⊤ x = ⊤ :=
begin
  ext a,
  rw mem_conjugate_iff,
  split,
  { intro h,
    exact subgroup.mem_top a, },
  { intro h,
    use x⁻¹ * a * x,
    split,
    { apply subgroup.mem_top, },
    { group, } },
end

lemma conjugate_eq_of_abelian (habelian : ∀ a b : G, a * b = b * a) : conjugate H x = H :=
begin
  ext a,
  rw mem_conjugate_iff,
  split,
  { rintro ⟨b, hb, rfl⟩,
    rw habelian,
    rwa (show x⁻¹ * (x * b) = b, by group), },
  { intro ha,
    use x⁻¹ * a * x,
    refine ⟨_, by group⟩,
    rw habelian,
    rwa (show x * (x⁻¹ * a) = a, by group), },
end


/-
Copyright (c) 2022 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import analysis.complex.basic
import topology.fiber_bundle

/-!
# Closure, interior, and frontier of preimages under `re` and `im`

In this fact we use the fact that `ℂ` is naturally homeomorphic to `ℝ × ℝ` to deduce some
topological properties of `complex.re` and `complex.im`.

## Main statements

Each statement about `complex.re` listed below has a counterpart about `complex.im`.

* `complex.is_trivial_topological_fiber_bundle_re`: `complex.re` turns `ℂ` into a trivial
  topological fiber bundle over `ℝ`;
* `complex.is_open_map_re`, `complex.quotient_map_re`: in particular, `complex.re` is an open map
  and is a quotient map;
* `complex.interior_preimage_re`, `complex.closure_preimage_re`, `complex.frontier_preimage_re`:
  formulas for `interior (complex.re ⁻¹' s)` etc;
* `complex.interior_set_of_re_le` etc: particular cases of the above formulas in the cases when `s`
  is one of the infinite intervals `set.Ioi a`, `set.Ici a`, `set.Iio a`, and `set.Iic a`,
  formulated as `interior {z : ℂ | z.re ≤ a} = {z | z.re < a}` etc.

## Tags

complex, real part, imaginary part, closure, interior, frontier
-/

open topological_fiber_bundle set
noncomputable theory

namespace complex

/-- `complex.re` turns `ℂ` into a trivial topological fiber bundle over `ℝ`. -/
lemma is_trivial_topological_fiber_bundle_re : is_trivial_topological_fiber_bundle ℝ re :=
⟨equiv_real_prodₗ.to_homeomorph, λ z, rfl⟩

/-- `complex.im` turns `ℂ` into a trivial topological fiber bundle over `ℝ`. -/
lemma is_trivial_topological_fiber_bundle_im : is_trivial_topological_fiber_bundle ℝ im :=
⟨equiv_real_prodₗ.to_homeomorph.trans (homeomorph.prod_comm ℝ ℝ), λ z, rfl⟩

lemma is_topological_fiber_bundle_re : is_topological_fiber_bundle ℝ re :=
is_trivial_topological_fiber_bundle_re.is_topological_fiber_bundle

lemma is_topological_fiber_bundle_im : is_topological_fiber_bundle ℝ im :=
is_trivial_topological_fiber_bundle_im.is_topological_fiber_bundle

lemma is_open_map_re : is_open_map re := is_topological_fiber_bundle_re.is_open_map_proj
lemma is_open_map_im : is_open_map im := is_topological_fiber_bundle_im.is_open_map_proj

lemma quotient_map_re : quotient_map re := is_topological_fiber_bundle_re.quotient_map_proj
lemma quotient_map_im : quotient_map im := is_topological_fiber_bundle_im.quotient_map_proj

lemma interior_preimage_re (s : set ℝ) : interior (re ⁻¹' s) = re ⁻¹' (interior s) :=
(is_open_map_re.preimage_interior_eq_interior_preimage continuous_re _).symm

lemma interior_preimage_im (s : set ℝ) : interior (im ⁻¹' s) = im ⁻¹' (interior s) :=
(is_open_map_im.preimage_interior_eq_interior_preimage continuous_im _).symm

lemma closure_preimage_re (s : set ℝ) : closure (re ⁻¹' s) = re ⁻¹' (closure s) :=
(is_open_map_re.preimage_closure_eq_closure_preimage continuous_re _).symm

lemma closure_preimage_im (s : set ℝ) : closure (im ⁻¹' s) = im ⁻¹' (closure s) :=
(is_open_map_im.preimage_closure_eq_closure_preimage continuous_im _).symm

lemma frontier_preimage_re (s : set ℝ) : frontier (re ⁻¹' s) = re ⁻¹' (frontier s) :=
(is_open_map_re.preimage_frontier_eq_frontier_preimage continuous_re _).symm

lemma frontier_preimage_im (s : set ℝ) : frontier (im ⁻¹' s) = im ⁻¹' (frontier s) :=
(is_open_map_im.preimage_frontier_eq_frontier_preimage continuous_im _).symm

@[simp] lemma interior_set_of_re_le (a : ℝ) : interior {z : ℂ | z.re ≤ a} = {z | z.re < a} :=
by simpa only [interior_Iic] using interior_preimage_re (Iic a)

@[simp] lemma interior_set_of_im_le (a : ℝ) : interior {z : ℂ | z.im ≤ a} = {z | z.im < a} :=
by simpa only [interior_Iic] using interior_preimage_im (Iic a)

@[simp] lemma interior_set_of_le_re (a : ℝ) : interior {z : ℂ | a ≤ z.re} = {z | a < z.re} :=
by simpa only [interior_Ici] using interior_preimage_re (Ici a)

@[simp] lemma interior_set_of_le_im (a : ℝ) : interior {z : ℂ | a ≤ z.im} = {z | a < z.im} :=
by simpa only [interior_Ici] using interior_preimage_im (Ici a)

@[simp] lemma closure_set_of_re_lt (a : ℝ) : closure {z : ℂ | z.re < a} = {z | z.re ≤ a} :=
by simpa only [closure_Iio] using closure_preimage_re (Iio a)

@[simp] lemma closure_set_of_im_lt (a : ℝ) : closure {z : ℂ | z.im < a} = {z | z.im ≤ a} :=
by simpa only [closure_Iio] using closure_preimage_im (Iio a)

@[simp] lemma closure_set_of_lt_re (a : ℝ) : closure {z : ℂ | a < z.re} = {z | a ≤ z.re} :=
by simpa only [closure_Ioi] using closure_preimage_re (Ioi a)

@[simp] lemma closure_set_of_lt_im (a : ℝ) : closure {z : ℂ | a < z.im} = {z | a ≤ z.im} :=
by simpa only [closure_Ioi] using closure_preimage_im (Ioi a)

@[simp] lemma frontier_set_of_re_le (a : ℝ) : frontier {z : ℂ | z.re ≤ a} = {z | z.re = a} :=
by simpa only [frontier_Iic] using frontier_preimage_re (Iic a)

@[simp] lemma frontier_set_of_im_le (a : ℝ) : frontier {z : ℂ | z.im ≤ a} = {z | z.im = a} :=
by simpa only [frontier_Iic] using frontier_preimage_im (Iic a)

@[simp] lemma frontier_set_of_le_re (a : ℝ) : frontier {z : ℂ | a ≤ z.re} = {z | z.re = a} :=
by simpa only [frontier_Ici] using frontier_preimage_re (Ici a)

@[simp] lemma frontier_set_of_le_im (a : ℝ) : frontier {z : ℂ | a ≤ z.im} = {z | z.im = a} :=
by simpa only [frontier_Ici] using frontier_preimage_im (Ici a)

@[simp] lemma frontier_set_of_re_lt (a : ℝ) : frontier {z : ℂ | z.re < a} = {z | z.re = a} :=
by simpa only [frontier_Iio] using frontier_preimage_re (Iio a)

@[simp] lemma frontier_set_of_im_lt (a : ℝ) : frontier {z : ℂ | z.im < a} = {z | z.im = a} :=
by simpa only [frontier_Iio] using frontier_preimage_im (Iio a)

@[simp] lemma frontier_set_of_lt_re (a : ℝ) : frontier {z : ℂ | a < z.re} = {z | z.re = a} :=
by simpa only [frontier_Ioi] using frontier_preimage_re (Ioi a)

@[simp] lemma frontier_set_of_lt_im (a : ℝ) : frontier {z : ℂ | a < z.im} = {z | z.im = a} :=
by simpa only [frontier_Ioi] using frontier_preimage_im (Ioi a)

lemma closure_preimage_re_inter_preimage_im (s t : set ℝ) :
  closure (re ⁻¹' s ∩ im ⁻¹' t) = re ⁻¹' (closure s) ∩ im ⁻¹' (closure t) :=
by simpa only [← preimage_eq_preimage equiv_real_prodₗ.symm.to_homeomorph.surjective,
  equiv_real_prodₗ.symm.to_homeomorph.preimage_closure]
  using @closure_prod_eq _ _ _ _ s t

lemma interior_preimage_re_inter_preimage_im (s t : set ℝ) :
  interior (re ⁻¹' s ∩ im ⁻¹' t) = re ⁻¹' (interior s) ∩ im ⁻¹' (interior t) :=
by rw [interior_inter, interior_preimage_re, interior_preimage_im]

lemma frontier_preimage_re_inter_preimage_im (s t : set ℝ) :
  frontier (re ⁻¹' s ∩ im ⁻¹' t) =
    re ⁻¹' (closure s) ∩ im ⁻¹' (frontier t) ∪ re ⁻¹' (frontier s) ∩ im ⁻¹' (closure t) :=
by simpa only [← preimage_eq_preimage equiv_real_prodₗ.symm.to_homeomorph.surjective,
  equiv_real_prodₗ.symm.to_homeomorph.preimage_frontier]
  using frontier_prod_eq s t

lemma frontier_set_of_le_re_and_le_im (a b : ℝ) :
  frontier {z | a ≤ re z ∧ b ≤ im z} = {z | a ≤ re z ∧ im z = b ∨ re z = a ∧ b ≤ im z} :=
by simpa only [closure_Ici, frontier_Ici]
  using frontier_preimage_re_inter_preimage_im (Ici a) (Ici b)

lemma frontier_set_of_le_re_and_im_le (a b : ℝ) :
  frontier {z | a ≤ re z ∧ im z ≤ b} = {z | a ≤ re z ∧ im z = b ∨ re z = a ∧ im z ≤ b} :=
by simpa only [closure_Ici, closure_Iic, frontier_Ici, frontier_Iic]
  using frontier_preimage_re_inter_preimage_im (Ici a) (Iic b)

end complex

open complex

lemma is_open.re_prod_im {s t : set ℝ} (hs : is_open s) (ht : is_open t) :
  is_open (re ⁻¹' s ∩ im ⁻¹' t) :=
(hs.preimage continuous_re).inter (ht.preimage continuous_im)

lemma is_closed.re_prod_im {s t : set ℝ} (hs : is_closed s) (ht : is_closed t) :
  is_closed (re ⁻¹' s ∩ im ⁻¹' t) :=
(hs.preimage continuous_re).inter (ht.preimage continuous_im)


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

import tactic -- imports all the Lean tactics
import data.real.basic -- imports the real numbers
import solutions.section02reals.sheet3 -- import the definition of `tends_to` from a previous sheet

-- to get a proof from a type class
-- apply_instance tactic should figure it out
-- so can write have : linear_order ℝ := {apply_instance,},

-- you can maybe do this one now
theorem tends_to_neg {a : ℕ → ℝ} {t : ℝ} (ha : tends_to a t) :
  tends_to (λ n, - a n) (-t) :=
begin
  rw tends_to at *,
  intros ε hε,
  specialize ha ε hε, -- try to do linear rewriting?
  cases ha with B ha,
  use B,
  intros n hB,
  specialize ha n hB,
  -- or simp, rw abs_sub_comm, exact ha,
  rw [←abs_neg] at ha,
  rw ←neg_add',
  exact ha,
end

/-
`tends_to_add` is quite a challenge. In a few weeks' time I'll
show you a two-line proof using filters, but right now
as you're still learning I think it's important that you
try and suffer and struggle through the first principles proof.
BIG piece of advice: write down a complete maths proof first,
with all the details there. Then, once you know the maths
proof, try translating it into Lean. Note that a bunch
of the results we proved in sheet 4 will be helpful.
-/

/-- If `a(n)` tends to `t` and `b(n)` tends to `u` then `a(n) + b(n)`
tends to `t + u`. -/
theorem tends_to_add {a b : ℕ → ℝ} {t u : ℝ}
  (ha : tends_to a t) (hb : tends_to b u) :
  tends_to (λ n, a n + b n) (t + u) :=
begin
  rw tends_to at *,
  intros e he,
  specialize ha (e/2) (half_pos he),
  specialize hb (e/2) (half_pos he),
  cases ha with k ha,
  cases hb with j hb,
  use (max k j),
  intros n hn,
  specialize ha n (le_of_max_le_left hn),
  specialize hb n (le_of_max_le_right hn),
  have h3 : a n - t + (b n - u) = a n + b n - (t + u), -- how to get rid of this?
  { ring, },
  rw [←h3, ←(add_halves e)],
  exact lt_of_le_of_lt (abs_add (a n - t) (b n - u)) (add_lt_add ha hb),
end

-- what is simp_rw -- rw but deeper

/-- If `a(n)` tends to t and `b(n)` tends to `u` then `a(n) - b(n)`
tends to `t - u`. -/
theorem tends_to_sub {a b : ℕ → ℝ} {t u : ℝ}
  (ha : tends_to a t) (hb : tends_to b u) :
  tends_to (λ n, a n - b n) (t - u) :=
begin
  exact tends_to_add ha (tends_to_neg hb),
end


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

import solutions.sheet08

/-

# The other way round, a function from an equivalence relation

On the previous sheet we saw that given a (surjective) function `f : X → Y` we can produce
an equivalence relation `∼` on `X` such that `x ≈ x'` iff `f (x) = f (x')`. On this sheet
we will see that Lean can go the other way round. For every type `X` and any
equivalence relation `≈` there exists a type `Y` and

- a surjective function `q : X → Y` such that
- `x ≈ x'` iff `q (x) = q (x')`.

There are different ways to construct such a `Y`, depending on the situation some are
easier than others. Lean (and that seems to be special compared to other interactive
theorem provers) has an inbuilt construction `quotient s` for a setoid `s` (recall that
a setoid is just a fancy way of saying a set together with an equivalence relation on it).
What is important here is not how Lean's choice of implementation of such a `Y` and `g`
is, but the API connected to it is.
-/

/- Let's set notation for our model of the integers as the quotient of the setoid s
  we defined on sheet07. -/

notation `myint` := quotient s

-- `setoid` is a class, so `s` should not just be a definition, we should
-- make it an instance by tagging it with the `instance` attribute
attribute [instance] s

-- Now we can use the notation `≈` for the equivalence relations `R`
-- and `⟦x⟧` for the element of `myint` corresponding to the pair `(a,b)` in `ℕ²`.

-- For a type `X` and a setoid structure `s` on `X` there is a function
-- `quotient.mk : X → quotient s`, the function `q` we were after. The brackets ⟦ ⟧ we
-- were using before are just notation for this function, i.e. `quotient.mk x = ⟦x⟧`.
-- Let's see that this is true by definition.

example (x : ℕ × ℕ) : quotient.mk x = ⟦x⟧ :=
begin
  refl
end

-- Furthermore, `≈` is just notation for `R`
-- (this is handy to know; let's give it a name so we can rewrite it)
lemma equiv_def (r s : ℕ × ℕ) : r ≈ s ↔ r.1 + s.2 =  s.1 + r.2 :=
begin
  refl
end

-- One of our demands was that the function `q` is surjective. Let's try to prove that.
lemma q_surj : function.surjective (λ (r : ℕ × ℕ), quotient.mk r) :=
begin
  exact surjective_quotient_mk _,
end

-- The second property we were after, namely that `x ≈ x'` iff `q(x) = q(x')` is called `quotient.eq`.

example (r s : ℕ × ℕ) : ⟦r⟧ = ⟦s⟧ ↔ r ≈ s :=
begin
  exact quotient.eq -- so `rw quotient.eq` is often useful
end

-- Both implications also have names

example (r s : ℕ × ℕ) : ⟦r⟧ = ⟦s⟧ → r ≈ s :=
begin
  exact quotient.exact
end

example (r s : ℕ × ℕ) : r ≈ s → ⟦r⟧ = ⟦s⟧ :=
begin
  exact quotient.sound
end


-- La suma de no negativos es expansiva
-- ====================================

-- Demostrar si a y b son números reales y a es no
-- negativo, entonces b ≤ a + b

import data.real.basic

variables {a b : ℝ}

-- 1ª demostración
example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
begin
  calc b = 0 + b : by rw zero_add
     ... ≤ a + b : by exact add_le_add_right ha b,
end

-- 2ª demostración
example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
begin
  calc b = 0 + b : (zero_add b).symm
     ... ≤ a + b : add_le_add_right ha b,
end

-- 3ª demostración
example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
begin
  calc b = 0 + b : by ring
     ... ≤ a + b : add_le_add_right ha b,
end

-- 4ª demostración
example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
by simp [ha]

-- 5ª demostración
example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
le_add_of_nonneg_left ha

-- 6ª demostración
example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
by linarith

-- 7ª demostración
example
  (ha : 0 ≤ a)
  : b ≤ a + b :=
by finish

-- Demostrar si a y b son números reales y b es no
-- negativo, entonces a ≤ a + b

-- 1ª demostración
example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
begin
  calc a = a + 0 : by rw add_zero
     ... ≤ a + b : by exact add_le_add_left hb a,
end

-- 2ª demostración
example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
begin
  calc a = a + 0 : (add_zero a).symm
     ... ≤ a + b : add_le_add_left hb a,
end

-- 3ª demostración
example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
begin
  calc a = a + 0 : by ring
     ... ≤ a + b : add_le_add_left hb a,
end

-- 4ª demostración
example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
by simp [hb]

-- 5ª demostración
example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
le_add_of_nonneg_right hb

-- 6ª demostración
example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
by linarith

-- 7ª demostración
example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
by finish

-- 8ª demostración
example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
begin
  rw add_comm,
  apply le_add_of_nonneg_left,
  exact hb,
end

-- 9ª demostración
example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
begin
  rw add_comm,
  exact le_add_of_nonneg_left hb,
end


import MyNat.Definition
namespace MyNat
open MyNat
/-!

# Proposition world.

## Level 1: the `exact` tactic.

The local context (or tactic state) at the beginning of
the example below looks like this:

```
P Q : Prop,
p : P,
h : P → Q
⊢ Q
```

In this situation, we have true/false statements `P` and `Q`,
a proof `p` of `P`, and `h` is the hypothesis that `P → Q` (P implies Q).
Our goal is to construct a proof of `Q`. It's clear what to do
*mathematically* to solve this goal, `P` is true and `P` implies `Q`
so `Q` is true. But how to do it in Lean?

Adopting a point of view wholly unfamiliar to many mathematicians,
Lean interprets the hypothesis `h` as a function from proofs
of `P` to proofs of `Q`, so the rather surprising approach

`exact h p`

works to close the goal!

Note that `exact h P` (with a capital P) won't work;
this is a common error by beginners. "We're trying to solve `P`
so it's exactly `P`". The goal states the *theorem*, your job is to
construct the *proof*. `P` is not a proof of `P`, it's `p` that is a proof of `P`.

The analogy would be like trying to call a function "sin X" with "Float" instead of a number 3.1415.

## Lemma

If `P` is true, and `P → Q` is also true, then `Q` is true.
-/
example (P Q : Prop) (p : P) (h : P → Q) : Q := by
  exact h p

/-!
Look familiar? The reason this works so elegantly is because the Lean programming language
has unified Propositions with the Type system of the language.

Next up [Level 2](./Level2.lean.md)
-/


/-
Copyright (c) 2022 Eric Rodriguez. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Rodriguez
-/
import algebra.ring.ulift
import data.mv_polynomial.cardinal
import data.rat.denumerable
import field_theory.finite.galois_field
import logic.equiv.transfer_instance
import ring_theory.localization.cardinality
import set_theory.cardinal_divisibility

/-!
# Cardinality of Fields

In this file we show all the possible cardinalities of fields. All infinite cardinals can harbour
a field structure, and so can all types with prime power cardinalities, and this is sharp.

## Main statements

* `fintype.nonempty_field_iff`: A `fintype` can be given a field structure iff its cardinality is a
  prime power.
* `infinite.nonempty_field` : Any infinite type can be endowed a field structure.
* `field.nonempty_iff` : There is a field structure on type iff its cardinality is a prime power.

-/

local notation `‖` x `‖` := fintype.card x

open_locale cardinal non_zero_divisors

universe u

/-- A finite field has prime power cardinality. -/
lemma fintype.is_prime_pow_card_of_field {α} [fintype α] [field α] : is_prime_pow (‖α‖) :=
begin
  casesI char_p.exists α with p _,
  haveI hp := fact.mk (char_p.char_is_prime α p),
  let b := is_noetherian.finset_basis (zmod p) α,
  rw [module.card_fintype b, zmod.card, is_prime_pow_pow_iff],
  { exact hp.1.is_prime_pow },
  rw ←finite_dimensional.finrank_eq_card_basis b,
  exact finite_dimensional.finrank_pos.ne'
end

/-- A `fintype` can be given a field structure iff its cardinality is a prime power. -/
lemma fintype.nonempty_field_iff {α} [fintype α] : nonempty (field α) ↔ is_prime_pow (‖α‖) :=
begin
  refine ⟨λ ⟨h⟩, by exactI fintype.is_prime_pow_card_of_field, _⟩,
  rintros ⟨p, n, hp, hn, hα⟩,
  haveI := fact.mk (nat.prime_iff.mpr hp),
  exact ⟨(fintype.equiv_of_card_eq ((galois_field.card p n hn.ne').trans hα)).symm.field⟩,
end

lemma fintype.not_is_field_of_card_not_prime_pow {α} [fintype α] [ring α] :
  ¬ is_prime_pow (‖α‖) → ¬ is_field α :=
mt $ λ h, fintype.nonempty_field_iff.mp ⟨h.to_field⟩

/-- Any infinite type can be endowed a field structure. -/
lemma infinite.nonempty_field {α : Type u} [infinite α] : nonempty (field α) :=
begin
  letI K := fraction_ring (mv_polynomial α $ ulift.{u} ℚ),
  suffices : #α = #K,
  { obtain ⟨e⟩ := cardinal.eq.1 this,
    exact ⟨e.field⟩ },
  rw ←is_localization.card (mv_polynomial α $ ulift.{u} ℚ)⁰ K le_rfl,
  apply le_antisymm,
  { refine ⟨⟨λ a, mv_polynomial.monomial (finsupp.single a 1) (1 : ulift.{u} ℚ), λ x y h, _⟩⟩,
    simpa [mv_polynomial.monomial_eq_monomial_iff, finsupp.single_eq_single_iff] using h },
  { simpa using @mv_polynomial.cardinal_mk_le_max α (ulift.{u} ℚ) _ }
end

/-- There is a field structure on type if and only if its cardinality is a prime power. -/
lemma field.nonempty_iff {α : Type u} : nonempty (field α) ↔ is_prime_pow (#α) :=
begin
  rw cardinal.is_prime_pow_iff,
  casesI fintype_or_infinite α with h h,
  { simpa only [cardinal.mk_fintype, nat.cast_inj, exists_eq_left',
        (cardinal.nat_lt_omega _).not_le, false_or]
      using fintype.nonempty_field_iff },
  { simpa only [← cardinal.infinite_iff, h, true_or, iff_true]
      using infinite.nonempty_field },
end


/-
Copyright (c) 2022 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import analysis.complex.basic
import topology.fiber_bundle.is_homeomorphic_trivial_bundle

/-!
# Closure, interior, and frontier of preimages under `re` and `im`

In this fact we use the fact that `ℂ` is naturally homeomorphic to `ℝ × ℝ` to deduce some
topological properties of `complex.re` and `complex.im`.

## Main statements

Each statement about `complex.re` listed below has a counterpart about `complex.im`.

* `complex.is_homeomorphic_trivial_fiber_bundle_re`: `complex.re` turns `ℂ` into a trivial
  topological fiber bundle over `ℝ`;
* `complex.is_open_map_re`, `complex.quotient_map_re`: in particular, `complex.re` is an open map
  and is a quotient map;
* `complex.interior_preimage_re`, `complex.closure_preimage_re`, `complex.frontier_preimage_re`:
  formulas for `interior (complex.re ⁻¹' s)` etc;
* `complex.interior_set_of_re_le` etc: particular cases of the above formulas in the cases when `s`
  is one of the infinite intervals `set.Ioi a`, `set.Ici a`, `set.Iio a`, and `set.Iic a`,
  formulated as `interior {z : ℂ | z.re ≤ a} = {z | z.re < a}` etc.

## Tags

complex, real part, imaginary part, closure, interior, frontier
-/

open set
noncomputable theory

namespace complex

/-- `complex.re` turns `ℂ` into a trivial topological fiber bundle over `ℝ`. -/
lemma is_homeomorphic_trivial_fiber_bundle_re : is_homeomorphic_trivial_fiber_bundle ℝ re :=
⟨equiv_real_prod_clm.to_homeomorph, λ z, rfl⟩

/-- `complex.im` turns `ℂ` into a trivial topological fiber bundle over `ℝ`. -/
lemma is_homeomorphic_trivial_fiber_bundle_im : is_homeomorphic_trivial_fiber_bundle ℝ im :=
⟨equiv_real_prod_clm.to_homeomorph.trans (homeomorph.prod_comm ℝ ℝ), λ z, rfl⟩

lemma is_open_map_re : is_open_map re := is_homeomorphic_trivial_fiber_bundle_re.is_open_map_proj
lemma is_open_map_im : is_open_map im := is_homeomorphic_trivial_fiber_bundle_im.is_open_map_proj

lemma quotient_map_re : quotient_map re := is_homeomorphic_trivial_fiber_bundle_re.quotient_map_proj
lemma quotient_map_im : quotient_map im := is_homeomorphic_trivial_fiber_bundle_im.quotient_map_proj

lemma interior_preimage_re (s : set ℝ) : interior (re ⁻¹' s) = re ⁻¹' (interior s) :=
(is_open_map_re.preimage_interior_eq_interior_preimage continuous_re _).symm

lemma interior_preimage_im (s : set ℝ) : interior (im ⁻¹' s) = im ⁻¹' (interior s) :=
(is_open_map_im.preimage_interior_eq_interior_preimage continuous_im _).symm

lemma closure_preimage_re (s : set ℝ) : closure (re ⁻¹' s) = re ⁻¹' (closure s) :=
(is_open_map_re.preimage_closure_eq_closure_preimage continuous_re _).symm

lemma closure_preimage_im (s : set ℝ) : closure (im ⁻¹' s) = im ⁻¹' (closure s) :=
(is_open_map_im.preimage_closure_eq_closure_preimage continuous_im _).symm

lemma frontier_preimage_re (s : set ℝ) : frontier (re ⁻¹' s) = re ⁻¹' (frontier s) :=
(is_open_map_re.preimage_frontier_eq_frontier_preimage continuous_re _).symm

lemma frontier_preimage_im (s : set ℝ) : frontier (im ⁻¹' s) = im ⁻¹' (frontier s) :=
(is_open_map_im.preimage_frontier_eq_frontier_preimage continuous_im _).symm

@[simp] lemma interior_set_of_re_le (a : ℝ) : interior {z : ℂ | z.re ≤ a} = {z | z.re < a} :=
by simpa only [interior_Iic] using interior_preimage_re (Iic a)

@[simp] lemma interior_set_of_im_le (a : ℝ) : interior {z : ℂ | z.im ≤ a} = {z | z.im < a} :=
by simpa only [interior_Iic] using interior_preimage_im (Iic a)

@[simp] lemma interior_set_of_le_re (a : ℝ) : interior {z : ℂ | a ≤ z.re} = {z | a < z.re} :=
by simpa only [interior_Ici] using interior_preimage_re (Ici a)

@[simp] lemma interior_set_of_le_im (a : ℝ) : interior {z : ℂ | a ≤ z.im} = {z | a < z.im} :=
by simpa only [interior_Ici] using interior_preimage_im (Ici a)

@[simp] lemma closure_set_of_re_lt (a : ℝ) : closure {z : ℂ | z.re < a} = {z | z.re ≤ a} :=
by simpa only [closure_Iio] using closure_preimage_re (Iio a)

@[simp] lemma closure_set_of_im_lt (a : ℝ) : closure {z : ℂ | z.im < a} = {z | z.im ≤ a} :=
by simpa only [closure_Iio] using closure_preimage_im (Iio a)

@[simp] lemma closure_set_of_lt_re (a : ℝ) : closure {z : ℂ | a < z.re} = {z | a ≤ z.re} :=
by simpa only [closure_Ioi] using closure_preimage_re (Ioi a)

@[simp] lemma closure_set_of_lt_im (a : ℝ) : closure {z : ℂ | a < z.im} = {z | a ≤ z.im} :=
by simpa only [closure_Ioi] using closure_preimage_im (Ioi a)

@[simp] lemma frontier_set_of_re_le (a : ℝ) : frontier {z : ℂ | z.re ≤ a} = {z | z.re = a} :=
by simpa only [frontier_Iic] using frontier_preimage_re (Iic a)

@[simp] lemma frontier_set_of_im_le (a : ℝ) : frontier {z : ℂ | z.im ≤ a} = {z | z.im = a} :=
by simpa only [frontier_Iic] using frontier_preimage_im (Iic a)

@[simp] lemma frontier_set_of_le_re (a : ℝ) : frontier {z : ℂ | a ≤ z.re} = {z | z.re = a} :=
by simpa only [frontier_Ici] using frontier_preimage_re (Ici a)

@[simp] lemma frontier_set_of_le_im (a : ℝ) : frontier {z : ℂ | a ≤ z.im} = {z | z.im = a} :=
by simpa only [frontier_Ici] using frontier_preimage_im (Ici a)

@[simp] lemma frontier_set_of_re_lt (a : ℝ) : frontier {z : ℂ | z.re < a} = {z | z.re = a} :=
by simpa only [frontier_Iio] using frontier_preimage_re (Iio a)

@[simp] lemma frontier_set_of_im_lt (a : ℝ) : frontier {z : ℂ | z.im < a} = {z | z.im = a} :=
by simpa only [frontier_Iio] using frontier_preimage_im (Iio a)

@[simp] lemma frontier_set_of_lt_re (a : ℝ) : frontier {z : ℂ | a < z.re} = {z | z.re = a} :=
by simpa only [frontier_Ioi] using frontier_preimage_re (Ioi a)

@[simp] lemma frontier_set_of_lt_im (a : ℝ) : frontier {z : ℂ | a < z.im} = {z | z.im = a} :=
by simpa only [frontier_Ioi] using frontier_preimage_im (Ioi a)

lemma closure_re_prod_im (s t : set ℝ) : closure (s ×ℂ t) = closure s ×ℂ closure t :=
by simpa only [← preimage_eq_preimage equiv_real_prod_clm.symm.to_homeomorph.surjective,
  equiv_real_prod_clm.symm.to_homeomorph.preimage_closure]
  using @closure_prod_eq _ _ _ _ s t

lemma interior_re_prod_im (s t : set ℝ) : interior (s ×ℂ t) = interior s ×ℂ interior t :=
by rw [re_prod_im, re_prod_im, interior_inter, interior_preimage_re, interior_preimage_im]

lemma frontier_re_prod_im (s t : set ℝ) :
  frontier (s ×ℂ t) = (closure s ×ℂ frontier t) ∪ (frontier s ×ℂ closure t) :=
by simpa only [← preimage_eq_preimage equiv_real_prod_clm.symm.to_homeomorph.surjective,
  equiv_real_prod_clm.symm.to_homeomorph.preimage_frontier]
  using frontier_prod_eq s t

lemma frontier_set_of_le_re_and_le_im (a b : ℝ) :
  frontier {z | a ≤ re z ∧ b ≤ im z} = {z | a ≤ re z ∧ im z = b ∨ re z = a ∧ b ≤ im z} :=
by simpa only [closure_Ici, frontier_Ici] using frontier_re_prod_im (Ici a) (Ici b)

lemma frontier_set_of_le_re_and_im_le (a b : ℝ) :
  frontier {z | a ≤ re z ∧ im z ≤ b} = {z | a ≤ re z ∧ im z = b ∨ re z = a ∧ im z ≤ b} :=
by simpa only [closure_Ici, closure_Iic, frontier_Ici, frontier_Iic]
  using frontier_re_prod_im (Ici a) (Iic b)

end complex

open complex metric

variables {s t : set ℝ}

lemma is_open.re_prod_im (hs : is_open s) (ht : is_open t) : is_open (s ×ℂ t) :=
(hs.preimage continuous_re).inter (ht.preimage continuous_im)

lemma is_closed.re_prod_im (hs : is_closed s) (ht : is_closed t) : is_closed (s ×ℂ t) :=
(hs.preimage continuous_re).inter (ht.preimage continuous_im)

lemma metric.bounded.re_prod_im (hs : bounded s) (ht : bounded t) : bounded (s ×ℂ t) :=
antilipschitz_equiv_real_prod.bounded_preimage (hs.prod ht)


-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar las siguientes acciones
-- 1. Importar la librería de los reales.
-- 2. Declarar f como variable de las funciones de ℝ en ℝ.
-- 3. Declarar a y b como variables sobre los reales.
-- ----------------------------------------------------------------------

import data.real.basic   -- 1
variables (f : ℝ → ℝ)    -- 2
variables (a b : ℝ)      -- 3

-- ---------------------------------------------------------------------
-- Ejercicio 2. Demostrar que si f es monótona y f(a) < f(b), entonces
-- a < b
-- ----------------------------------------------------------------------

example
  (h : monotone f)
  (h' : f a < f b)
  : a < b :=
begin
  apply lt_of_not_ge,
  intro hab,
  have : f a ≥ f b,
    from h hab,
  linarith,
end

-- Prueba
-- ======

-- f : ℝ → ℝ,
-- a b : ℝ,
-- h : monotone f,
-- h' : f a < f b
-- ⊢ a < b
--    >> apply lt_of_not_ge,
-- ⊢ ¬a ≥ b
--    >> intro hab,
-- hab : a ≥ b
-- ⊢ false
--    >> have : f a ≥ f b,
--    >>   from h hab,
-- this : f a ≥ f b
-- ⊢ false
--    >> linarith,
-- no goals

-- ---------------------------------------------------------------------
-- Ejercicio 3. Demostrar que si
--    a ≤ b
--    f b < f a
-- entonces f no es monótona.
-- ----------------------------------------------------------------------

example
  (h : a ≤ b)
  (h' : f b < f a)
  : ¬ monotone f :=
begin
  intro h1,
  have : f a ≤ f b,
    from h1 h,
  linarith,
end

-- Prueba
-- ======

-- f : ℝ → ℝ,
-- a b : ℝ,
-- h : a ≤ b,
-- h' : f b < f a
-- ⊢ ¬monotone f
--    >> intro h1,
-- h1 : monotone f
-- ⊢ false
--    >> have : f a ≤ f b,
--    >>   from h1 h,
-- this : f a ≤ f b
-- ⊢ false
--    >> linarith,
-- no goals


-- ---------------------------------------------------------------------
-- Ejercicio. Realizar las siguientes acciones:
-- 1. Importar la librería data.nat.prime data.nat.parity
-- 2. Abrir el espacio de nombres nat.
-- 3. Declarar s como variable sobre conjuntos de números naturales.
-- ----------------------------------------------------------------------

import data.nat.prime data.nat.parity   -- 1
open nat                                -- 2
variable (s : set ℕ)                    -- 3

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si los elementos de s no son pares y si los
-- elementos de s son primos, entonces los elementos de s no son pares
-- pero sí son primpos.
-- ----------------------------------------------------------------------

example
  (h₀ : ∀ x ∈ s, ¬ even x)
  (h₁ : ∀ x ∈ s, prime x)
  : ∀ x ∈ s, ¬ even x ∧ prime x :=
begin
  intros x xs,
  split,
  { apply h₀ x xs },
  { apply h₁ x xs },
end

-- Prueba
-- ======

/-
s : set ℕ,
h₀ : ∀ (x : ℕ), x ∈ s → ¬x.even,
h₁ : ∀ (x : ℕ), x ∈ s → x.prime
⊢ ∀ (x : ℕ), x ∈ s → ¬x.even ∧ x.prime
  >> intros x xs,
x : ℕ,
xs : x ∈ s
⊢ ¬x.even ∧ x.prime
  >> split,
| ⊢ ¬x.even
|   >> { apply h₀ x xs },
⊢ x.prime
  >> { apply h₁ x xs },
no goals
-/

-- Comentario: La táctica (intros x xs) si la conclusión es (∀ y ∈ s, P y)
-- y una hipótesis es (s : set α), entonces añade las hipótesis (x : α)
-- y (xs : x ∈ s) y cambia la conclusión a (P x).

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si s tiene algún elemento primo impar,
-- entonces tiene algún elemento primo.
-- ----------------------------------------------------------------------

example
  (h : ∃ x ∈ s, ¬ even x ∧ prime x)
  : ∃ x ∈ s, prime x :=
begin
  rcases h with ⟨x, xs, _, prime_x⟩,
  use [x, xs, prime_x],
end

-- Prueba
-- ======

/-
s : set ℕ,
h : ∃ (x : ℕ) (H : x ∈ s), ¬x.even ∧ x.prime
⊢ ∃ (x : ℕ) (H : x ∈ s), x.prime
  >> rcases h with ⟨x, xs, _, prime_x⟩,
xs : x ∈ s,
h_h_h_left : ¬x.even,
prime_x : x.prime
⊢ ∃ (x : ℕ) (H : x ∈ s), x.prime
  >> use [x, xs, prime_x],
no goals
-/

-- Comentarios:
-- 1. La táctica (cases h with ⟨x, xs, h1, h2⟩) si la
--    hipótesis es (∃ y ∈ s, P y ∧ Q y) y una hipótesis es (s : set α),
--    entonces quita h y añade las hipótesis (x : s), (h1 : P x) y
--    (h2 : Q x).
-- 2. La táctica (use [x, xs, h]) resuelve la conclusión
--    (∃ x ∈ s, P x) si xs es una prueba de (x ∈ s) y h lo es de (P x).


import topology.basic
import topology.separation
open topological_space
open set filter classical
variables {X Y : Type} [topological_space X] [topological_space Y]

/-
I saw Ethan's solution to the exercise 2.4.14 and I very much liked it.
I wanted to make sure that his solution works so I wrote this code.
If there is a copyright to the solution of a math question, it belongs to Ethan.
-/

-- Exercise 2.4.14. [⋆] Show that if (𝑋, 𝒪) is Hausdorff and 𝑓 ∶ 𝑋 → 𝑋 is continuous,
-- then the set of fixed points of 𝑓 is closed.
theorem fixed_points_closed (f : X → X): t2_space X → continuous f → is_closed {x | f x = x }:=
begin
  intros Hausdorff cont,
  apply is_open_compl_iff.mp,
  apply is_open_iff_forall_mem_open.mpr,
  intros x H,
  -- fixed points are closed since its compliment is union of open sets

  have H1 : f x ≠ x := H ;                                                                 clear H,
  have H_def := Hausdorff.t2 ;                                                             clear Hausdorff,
  have := H_def x (f x) (ne.symm H1);                                                      clear H_def H1,
  rcases this with ⟨ u, v, u_open, v_open, x_in_u, fx_in_v, u_v_disjoint ⟩ ,
  -- Use Hausdorff to get open sets around x and f x

  let U := u ∩ f ⁻¹' v,
  use U,
  -- claim that U is such open set

  refine ⟨_, _, mem_inter x_in_u fx_in_v⟩,
  -- x is obivously in U since x is in both u and f ⁻¹' v (v includes f x)

  {
    clear' u_open v_open x_in_u fx_in_v cont x,
    intros y y_in_U f_y_eq_y,
    -- suppose there is y in U s.t. f y = y

    have y_in_u:          y ∈ u         := mem_of_mem_inter_left y_in_U,
    have y_in_preim_v:    y ∈ f ⁻¹' v   := mem_of_mem_inter_right y_in_U;                  clear y_in_U U,
    have f_y_in_v:      f y ∈ v         := y_in_preim_v;                                   clear y_in_preim_v,
    have y_in_v :         y ∈ v         := mem_of_eq_of_mem (eq.symm f_y_eq_y) f_y_in_v;   clear f_y_in_v f_y_eq_y f,
    -- Contradiction! since u and v are disjoint

    have y_set_in_u:    {y} ≤ u         := singleton_subset_iff.mpr y_in_u;                 clear y_in_u,
    have y_set_in_v:    {y} ≤ v         := singleton_subset_iff.mpr y_in_v;                 clear y_in_v,
    have y_set_eq_bot                   := u_v_disjoint y_set_in_u y_set_in_v;              clear y_set_in_u y_set_in_v u_v_disjoint u v,
    have y_in_y_set:     y ∈ {y}        := mem_singleton y,
    exact y_set_eq_bot y_in_y_set,
  },

  -- U is open because u & v both open and f is continuous so f ⁻¹' v also open
  refine (continuous_on_open_iff u_open).mp _ v v_open,
  exact continuous.continuous_on cont,

end


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

import tactic.interval_cases
import data.nat.modeq

/-!
# IMO 1964 Q1

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

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

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

/-!
## Intermediate lemmas
-/

open nat

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

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

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

/-!
## The question
-/

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

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

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

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


section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
intro p, -- Suponha P
intro np, -- Suponha ¬P
contradiction, -- Contradição
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro p, -- Suponha ¬¬p
  by_cases h : P, -- "Aplicando feitiço" LEM
  assumption,
  contradiction,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
  intro p,
  by_cases h : P,
  assumption,
  contradiction,
  intro q,
  intro q1,
  contradiction,
end

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

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro hp,
  cases hp,
  right,
  assumption,
  left,
  assumption,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro hp,
  split,
  cases hp,
  assumption,
  cases hp,
  assumption,
end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro hp,
  cases hp,
  intro hhp,
  contradiction,
  intro hhp,
  assumption,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro hp,
  cases hp,
  intro hhp,
  contradiction,
  intro hhp,
  assumption,
end

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

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

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

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
  intro piq,
  intro nq,
  intro p,
  have q : Q := piq p,
  contradiction,
  intro nqinp,
  intro p,
  by_contradiction hboom,
  have np : ¬P := nqinp hboom,
  contradiction,
end

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro n_pounp,
  apply n_pounp,
  left,
  by_contradiction hboom,
  apply n_pounp,
  right,
  assumption,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro piqip,
  by_cases npoup : P,
  intro np,
  contradiction,
  intro np,
  apply np,
  apply piqip,
  intro p,
  contradiction,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro pouq,
  cases pouq,
  intro npenq,
  cases npenq,
  contradiction,
  intro npenq,
  cases npenq,
  contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro peq,
  cases peq,
  intro npounq,
  cases npounq,
  contradiction,
  contradiction,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_pouq,
  split,
  intro p,
  apply n_pouq,
  left,
  by_contradiction hboom,
  contradiction,
  intro nq,
  apply n_pouq,
  right,
  by_contradiction,
  contradiction,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro npenq,
  intro n_pouq,
  cases n_pouq,
  cases npenq,
  contradiction,
  cases npenq,
  contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_peq,
  by_cases h : P,
  left,
  intro p,
  apply n_peq,
  split,
  assumption,
  assumption,
  right,
  assumption,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intro nqounp,
  intro n_peq,
  cases n_peq,
  cases nqounp,
  contradiction,
  contradiction,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
  apply demorgan_conj,
  apply demorgan_conj_converse,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
  intro n_pouq,
  split,
  intro p,
  apply n_pouq,
  left,
  assumption,
  intro q,
  apply n_pouq,
  right,
  assumption,
  intro npenq,
  intro pouq,
  cases npenq,
  cases pouq,
  contradiction,
  contradiction,
end

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro pe_qour,
  cases pe_qour,
  cases pe_qour_right,
  left,
  split,
  assumption,
  assumption,
  right,
  split,
  assumption,
  assumption,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro peq_ou_per,
  cases peq_ou_per,
  cases peq_ou_per,
  split,
  assumption,
  left,
  assumption,
  cases peq_ou_per,
  split,
  assumption,
  right,
  assumption,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro pou_qer,
  cases pou_qer,
  split,
  left,
  assumption,
  left,
  assumption,
  cases pou_qer,
  split,
  right,
  assumption,
  right,
  assumption,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro pouq_e_pour,
  cases pouq_e_pour,
  cases pouq_e_pour_left,
  left,
  assumption,
  cases pouq_e_pour_right,
  left,
  assumption,
  right,
  split,
  assumption,
  assumption,

end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro peqir,
  intro p,
  intro q,
  apply peqir,
  split,
  assumption,
  assumption,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro pi_qir,
  intro peq,
  cases peq,
  apply pi_qir,
  assumption,
  assumption,
end

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

theorem impl_refl :
  P → P  :=
begin
  intro p,
  assumption,
end

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

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro p,
  left,
  assumption,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro q,
  right,
  assumption,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro peq,
  cases peq,
  assumption,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro peq,
  cases peq,
  assumption,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  intro pep,
  cases pep,
  assumption,
  intro p,
  split,
  assumption,
  assumption,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro poup,
  cases poup,
  assumption,
  assumption,
  intro p,
  left,
  assumption,
end

end propositional

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

section predicate

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

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

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro ex_px,
  intro a,
  intro h,
  apply ex_px,
  existsi a,
  assumption,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro Vx_nPx,
  intro ex_px,
  cases ex_px with a U,
  apply Vx_nPx a,
  assumption,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro Vx_px,
  by_contradiction hboom,
  apply Vx_px,
  intro a,
  by_contradiction,
  apply hboom,
  existsi a,
  assumption,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro Ex_nPx,
  intro Vx_px,
  cases Ex_nPx with a Pa,
  apply Pa,
  apply Vx_px a,
end

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

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
  intro nEx_Px,
  intro a,
  by_contradiction,
  apply nEx_Px,
  existsi a,
  assumption,
  intro Vx_nPx,
  intro Ex_Px,
  cases Ex_Px with a U,
  apply Vx_nPx a,
  assumption,
end

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro Ex_Px,
  intro Vx_nPx,
  cases Ex_Px with a Pa,
  apply Vx_nPx a,
  assumption,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro Vx_Px,
  intro Ex_nPx,
  cases Ex_nPx with a Pa,
  apply Pa,
  apply Vx_Px a,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro nEx_nPx,
  intro a,
  by_contradiction,
  apply nEx_nPx,
  existsi a,
  assumption,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro nVx_nPx,
  by_contradiction,
  apply nVx_nPx,
  intro a,
  intro Pa,
  apply h,
  existsi a,
  assumption,
end

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

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

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

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro Ex_PxeQx,
  split,
  by_contradiction hboom,
  cases Ex_PxeQx with a PaeQa,
  apply hboom,
  existsi a,
  cases PaeQa,
  assumption,
  by_contradiction hboom,
  cases Ex_PxeQx with a PaeQa,
  apply hboom,
  existsi a,
  cases PaeQa,
  assumption,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro Ex_PxouQx,
  cases Ex_PxouQx with a PaouQa,
  cases PaouQa,
  left,
  existsi a,
  assumption,
  right,
  existsi a,
  assumption,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro Ex_Px_ou_Ex_Qx,
  cases Ex_Px_ou_Ex_Qx with Px_ou_Qx,
  by_contradiction hboom,
  cases Px_ou_Qx with a Pa,
  apply hboom,
  existsi a,
  left,
  assumption,
  by_contradiction hboom,
  cases Ex_Px_ou_Ex_Qx with a Qa,
  apply hboom,
  existsi a,
  right,
  assumption,

end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro Vx_PxeQx,
  split,
  intro a,
  apply (Vx_PxeQx a).left,
  intro a,
  apply (Vx_PxeQx a).right,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro Vx_Px_e_Vx_Qx,
  intro a,
  cases Vx_Px_e_Vx_Qx with l r,
  split,
  apply l a,
  apply r a,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intro Vx_Px_ou_Vx_Qx,
  intro a,
  cases Vx_Px_ou_Vx_Qx with l r,
  left,
  apply l a,
  right,
  apply r a,
end

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

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

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

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

end predicate


import data.finset
import tactic.ring
open nat

def odd : ℕ → ℕ := λ i, 2 * i + 1
def square : ℕ → ℕ := λ i, i ^ 2

#eval odd 20 -- 41
#reduce odd 20 -- 41
#eval square 20 -- 400
#eval square 20 -- 400

#eval [odd 0, odd 1, odd 2] -- [1, 3, 5]
#eval 1 + 3 + 5 -- 9

-- sum of the first three odd positive integers is 3 squared.

example : odd 0 + odd 1 + odd 2 = square 3 := rfl

-- We will look at how to prove that the sum of the first
-- n odd positive integers is n squared. We'll prove it
-- by induction.

-- Here is a proof of the inductive hypothesis.
-- just ignore it; this is an old version of Lean
-- and there are much better ways to do this now.

theorem odd_square_inductive_step (d : ℕ) : square d + odd d = square (succ d) :=
begin unfold square odd nat.pow,rw succ_eq_add_one,
ring,
end

-- Exercise 1.

-- Define a function which takes as input a function
-- f : ℕ → ℕ and returns the function which sends
-- a natural number n to f(0)+f(1)+f(2)+...+f(n-1)

definition my_sum_to_n (summand : ℕ → ℕ) : ℕ → ℕ :=
sorry -- delete this and then fill in your definition here.

-- Exercise 2.

-- The function you defined comes with a law of induction,
-- and you now need to prove the zero and successor results
-- for your function.
theorem my_zero_theorem (summand : ℕ → ℕ) : my_sum_to_n summand 0 = 0 := sorry

theorem my_successor_theorem (summand : ℕ → ℕ) (n : ℕ) :
  my_sum_to_n summand (nat.succ n) = my_sum_to_n summand n + summand n := sorry

-- Exercise 3.

-- Now can you prove my_odd_square_theorem, the fact that the sum of the
-- first n odd numbers is n ^ 2?

theorem my_odd_square_theorem (n : ℕ) : my_sum_to_n odd n = square n := sorry

-- Exercise 4.

-- Finally, find someone who has given a completely different definition
-- of my_sum_to_n (try looking in the comments below, for example) and
-- add their definition to your session (to your "context"):

definition other_sum_to_n (summand : ℕ → ℕ) : ℕ → ℕ :=
sorry

-- and now see if
-- you can prove that their definition is the same as yours.

theorem consistency : my_sum_to_n = other_sum_to_n := sorry

-- You might want to start
-- begin apply funext,intro f,apply funext,intro n,


import .love05_inductive_predicates_demo

/- # LoVe Exercise 5: Inductive Predicates -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/- ## Question 1: Even and Odd

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

#check even

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

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

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

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

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

/- 1.2. Prove that 3, 5, and 7 are odd. -/

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

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

lemma odd_7 :
  odd 7 :=
begin
  intro h,
  cases' h,
  cases' h,
  cases' h,
  cases' h
end

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

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

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

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

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

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

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

/- ## Question 2: Binary Trees

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

#check is_full_mirror
#check mirror_mirror

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

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

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

/- 2.3. Prove the following lemma by case distinction. -/

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

/- 2.4. Prove the following lemma by rule induction. -/

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

end LoVe


-- 4.4 The Existential Quantifier

open nat

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

#print zero_lt_succ

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

example (x y z : ℕ ) (hxy : x < y) (hyz : y < z) :
  ∃ w, x < w ∧ w < z :=
exists.intro y (and.intro hxy hyz)

#check @exists.intro

example : ∃ x : ℕ, x > 0 :=
⟨ 1, zero_lt_succ 0 ⟩

example (x : ℕ ) (h : x > 0) : ∃ y, y < x :=
⟨0, h⟩

example (x y z : ℕ ) (hxy : x < y) (hyz : y < z) :
  ∃ w, x < w ∧ w < z :=
⟨y, hxy, hyz⟩

variable g : ℕ → ℕ → ℕ
variable hg : g 0 0 = 0

theorem gex1 : ∃ x, g x x = x := ⟨0, hg⟩
theorem gex2 : ∃ x, g x 0 = x := ⟨0, hg⟩
theorem gex3 : ∃ x, g 0 0 = x := ⟨0, hg⟩
theorem gex4 : ∃ x, g x x = 0 := ⟨0, hg⟩

set_option pp.implicit true -- display implicit arguments
#print gex1
#print gex2
#print gex3
#print gex4

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⟩ )

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

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

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
match h with ⟨ w, ⟨ hpw, hqw ⟩ ⟩ :=
  ⟨ w, ⟨ hqw, hpw ⟩ ⟩
end

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
let ⟨w, hpw, hqw⟩ := h in ⟨w, hqw, hpw⟩

example : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x :=
assume ⟨w, hpw, hqw⟩, ⟨w, hqw, hpw⟩


import analysis.real tactic.norm_num algebra.group_power

def S (a : {n : ℕ // n ≥ 1} → ℝ)
  (n : ℕ) (H : n ≥ 1) := { r: ℝ | ∃ (m : ℕ) (Hm : m ≥ n), r = a ⟨m,ge_trans Hm H⟩ }

theorem Q6a1 (a : {n : ℕ // n ≥ 1} → ℝ)
  (HB : ∃ B : ℝ, ∀ n : ℕ, ∀ H : n ≥ 1, a ⟨n,H⟩ ≤ B) : ∀ (n : ℕ) (H : n ≥ 1),
  (∃ r : ℝ, r ∈ S a n H) ∧ (∃ B : ℝ, ∀ x : ℝ, x ∈ S a n H → x ≤ B) := sorry

theorem Q6a2 (a : {n : ℕ // n ≥ 1} → ℝ)
  (HB : ∃ B : ℝ, ∀ n : ℕ, ∀ H : n ≥ 1, a ⟨n,H⟩ ≤ B) (n : ℕ) (H : n ≥ 1) :
  ∃ x : ℝ, is_lub (S a n H) x := sorry

theorem Q6b1 (a : {n : ℕ // n ≥ 1} → ℝ)
  (HB : ∃ B : ℝ, ∀ n : ℕ, ∀ H : n ≥ 1, a ⟨n,H⟩ ≤ B) (n : ℕ) (H : n ≥ 1) :
  ∀ bnp1 bn : ℝ, is_lub (S a n H) bn ∧ is_lub (S a (n+1) (le_trans H (nat.le_succ _))) bnp1
  → bnp1 ≤ bn := sorry

def limsup (a : {n : ℕ // n ≥ 1} → ℝ) (lsup : ℝ) : Prop :=
  ∃ b : { n : ℕ // n ≥ 1} → ℝ, is_glb { x : ℝ | ∃ (n : ℕ) (H : n ≥ 1), x = b ⟨n,H⟩} lsup
  ∧ ∀ (n : ℕ) (H : n ≥ 1), is_lub (S a n H) (b ⟨n,H⟩)

def a1 : {n : ℕ // n ≥ 1} → ℝ := λ _, 1

theorem Q6c1 : limsup a1 1 := sorry

noncomputable def a2 : {n : ℕ // n ≥ 1} → ℝ := λ N, 1/N.val

theorem Q6c2 : limsup a2 0 := sorry

noncomputable def a3 : {n : ℕ // n ≥ 1} → ℝ := λ N, 1 - ((((N.val % (2:ℕ))):ℤ):ℝ)

theorem Q6c3 : limsup a3 1 := sorry

def liminf (a : {n : ℕ // n ≥ 1} → ℝ) (linf : ℝ) : Prop :=
  ∃ c : { n : ℕ // n ≥ 1} → ℝ, is_lub { x : ℝ | ∃ (n : ℕ) (H : n ≥ 1), x = c ⟨n,H⟩} linf
  ∧ ∀ (n : ℕ) (H : n ≥ 1), is_glb (S a n H) (c ⟨n,H⟩)

theorem Q6c1' : liminf a1 1 := sorry

theorem Q6c2' : liminf a2 0 := sorry

theorem Q6c3' : liminf a3 0 := sorry


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Jeremy Avigad

Extensional equality for functions, and a proof of function extensionality from quotients.
-/
prelude
import init.data.quot init.logic

open quotient

universes u v
variables {α : Sort u} {β : α → Sort v}

namespace function

/-- The relation stating that two functions are pointwise equal. -/
protected def equiv (f₁ f₂ : Π x : α, β x) : Prop := ∀ x, f₁ x = f₂ x

local infix ` ~ `:50 := function.equiv

protected theorem equiv.refl (f : Π x : α, β x) : f ~ f := assume x, rfl

protected theorem equiv.symm {f₁ f₂ : Π x: α, β x} : f₁ ~ f₂ → f₂ ~ f₁ :=
λ h x, eq.symm (h x)

protected theorem equiv.trans {f₁ f₂ f₃ : Π x: α, β x} : f₁ ~ f₂ → f₂ ~ f₃ → f₁ ~ f₃ :=
λ h₁ h₂ x, eq.trans (h₁ x) (h₂ x)

protected theorem equiv.is_equivalence (α : Sort u) (β : α → Sort v) : equivalence (@function.equiv α β) :=
mk_equivalence (@function.equiv α β) (@equiv.refl α β) (@equiv.symm α β) (@equiv.trans α β)

/-- The setoid generated by pointwise equality. -/
local attribute [instance]
def fun_setoid (α : Sort u) (β : α → Sort v) : setoid (Π x : α, β x) :=
setoid.mk (@function.equiv α β) (function.equiv.is_equivalence α β)

/-- The quotient of the function type by pointwise equality. -/
def extfun (α : Sort u) (β : α → Sort v) : Sort (imax u v) :=
quotient (fun_setoid α β)

/-- The map from functions into the qquotient by pointwise equality. -/
def fun_to_extfun (f : Π x : α, β x) : extfun α β :=
⟦f⟧

/-- From an element of `extfun` we can retrieve an actual function. -/
def extfun_app (f : extfun α β) : Π x : α, β x :=
assume x,
quot.lift_on f
  (λ f : Π x : α, β x, f x)
  (λ f₁ f₂ h, h x)

end function
open function

local attribute [instance] fun_setoid

/-- Function extensionality, proven using quotients. -/
theorem funext {f₁ f₂ : Π x : α, β x} (h : ∀ x, f₁ x = f₂ x) : f₁ = f₂ :=
show extfun_app ⟦f₁⟧ = extfun_app ⟦f₂⟧, from
congr_arg extfun_app (sound h)

attribute [intro!] funext

local infix ` ~ `:50 := function.equiv

instance pi.subsingleton [∀ a, subsingleton (β a)] : subsingleton (Π a, β a) :=
⟨λ f₁ f₂, funext (λ a, subsingleton.elim (f₁ a) (f₂ a))⟩