import tactic
import group.definitions

/-
class group (G : Type) extends has_group_notation G :=
(mul_assoc : ∀ (a b c : G), a * b * c = a * (b * c))
(one_mul : ∀ (a : G), 1 * a = a)
(mul_left_inv : ∀ (a : G), a⁻¹ * a = 1)

-- IMPORTANT; KB has some much slicker proofs of these in
-- the Wednesday solutions to his LFTCM talk. Feel
-- free to steal
-/

-- This entire project takes place in the mygroup namespace
namespace mygroup

/- Our first task is to prove `mul_one` and `mul_right_inv`.
   We prove some other things along the way too -- we make
   what a computer scientist would call "an interface for
   the group class".

  Examples of what we prove:

`mul_left_cancel : ∀ (a b c : G), a * b = a * c → b = c`
`mul_eq_of_eq_inv_mul {a x y : G} : x = a⁻¹ * y → a * x = y`
`mul_one (a : G) : a * 1 = a`
`mul_right_inv (a : G) : a * a⁻¹ = 1`

-/
namespace group

variables {G : Type} [group G]

-- We prove left_mul_cancel for group using `calc`.

lemma mul_left_cancel (a b c : G) (Habac : a * b = a * c) : b = c :=
 calc b = 1 * b         : by rw one_mul
    ... = (a⁻¹ * a) * b : by rw mul_left_inv
    ... = a⁻¹ * (a * b) : by rw mul_assoc
    ... = a⁻¹ * (a * c) : by rw Habac
    ... = (a⁻¹ * a) * c : by rw mul_assoc
    ... = 1 * c         : by rw mul_left_inv
    ... = c             : by rw one_mul

-- We can do all this one go:

lemma mul_left_cancel' (a b c : G) (Habac : a * b = a * c) : b = c :=
begin
  rw [←one_mul b, ←mul_left_inv a, mul_assoc, Habac,
      ←mul_assoc, mul_left_inv, one_mul],
end

-- Because the above proof just uses one tactic, we could use `by`
-- instead of `begin ... end`:

lemma mul_left_cancel'' (a b c : G) (Habac : a * b = a * c) : b = c :=
by rw [←one_mul b, ←mul_left_inv a, mul_assoc, Habac,
  ←mul_assoc, mul_left_inv, one_mul]

-- The below is also a useful intermediate lemma

lemma mul_eq_of_eq_inv_mul {a x y : G} (h : x = a⁻¹ * y) : a * x = y :=
begin
  apply mul_left_cancel a⁻¹,
  rw ←mul_assoc,
  rw mul_left_inv,
  rwa one_mul, -- rewrite then assumption
end

-- could prove it in `calc` mode:

lemma mul_eq_of_eq_inv_mul' {a x y : G} (h : x = a⁻¹ * y) : a * x = y :=
begin
  apply mul_left_cancel a⁻¹,
  exact calc
  a⁻¹ * (a * x) = a⁻¹ * a * x : by rw mul_assoc
  ...           = 1 * x       : by rw mul_left_inv
  ...           = x           : by rw one_mul
  ...           = a⁻¹ * y     : by rw h
end

attribute [simp] one_mul mul_left_inv

-- Alternatively, get the simplifier to do some of the work for us
lemma mul_eq_of_eq_inv_mul'' {a x y : G} : x = a⁻¹ * y → a * x = y :=
λ h, mul_left_cancel a⁻¹ _ _ $ by rw ←mul_assoc; simp [h]

-- We can now prove `mul_one`:

-- nice short proof
theorem mul_one (a : G) : a * 1 = a :=
begin
  apply mul_eq_of_eq_inv_mul,
  rw mul_left_inv,
  -- note no refl
end

-- calc example (longer than previous one)
theorem mul_one' : ∀ (a : G), a * 1 = a :=
begin
  intro a, -- goal is a * 1 = a
  apply mul_left_cancel a⁻¹, -- goal now a⁻¹ * (a * 1) = a⁻¹ * a
  exact calc a⁻¹ * (a * 1) = (a⁻¹ * a) * 1 : by rw mul_assoc
          ...               = 1 * 1         : by rw mul_left_inv
          ...               = 1             : by rw one_mul
          ...               = a⁻¹ * a       : by rw mul_left_inv
end

-- term mode proof
theorem mul_one'' (a : G) : a * 1 = a :=
mul_eq_of_eq_inv_mul $ by simp

-- it's also a good simp lemma
attribute [simp] mul_one

-- mul_left_inv is an axiom: here's mul_right_inv.

theorem mul_right_inv (a : G) : a * a⁻¹ = 1 :=
begin
  apply mul_eq_of_eq_inv_mul,
  rw mul_one,
end

-- another good simp lemma
attribute [simp] mul_right_inv

-- We already proved `mul_eq_of_eq_inv_mul` but there are several other
-- similar-looking, but slightly different, versions of this. Here
-- is one.
lemma eq_mul_inv_of_mul_eq {a b c : G} (h : a * c = b) : a = b * c⁻¹ :=
begin
  rw ←h,
  rw mul_assoc,
  rw mul_right_inv,
  rw mul_one
end

-- one-liner proof
lemma eq_mul_inv_of_mul_eq' {a b c : G} (h : a * c = b) : a = b * c⁻¹ :=
by rw [←h, mul_assoc, mul_right_inv, mul_one]

-- proof using automation
lemma eq_mul_inv_of_mul_eq'' {a b c : G} (h : a * c = b) : a = b * c⁻¹ :=
by simp [h.symm, mul_assoc]

lemma eq_inv_mul_of_mul_eq {a b c : G} (h : b * a = c) : a = b⁻¹ * c :=
begin
  rw [←h, ←mul_assoc, mul_left_inv b, one_mul]
end

-- Another useful lemma for the interface:
lemma mul_left_eq_self {a b : G} : a * b = b ↔ a = 1 :=
begin
  split,
  { intro h,
    replace h := eq_mul_inv_of_mul_eq h,
    rw mul_right_inv at h,
    assumption
  },
  { intro h,
    rw h,
    rw one_mul
  }
end

lemma mul_right_eq_self {a b : G} : a * b = a ↔ b = 1 :=
begin
  split,
    intro h,
    from calc b = a⁻¹ * a : by apply eq_inv_mul_of_mul_eq h
           ...  = 1 : by rw mul_left_inv,
    intro h,
    rw [h, mul_one]
end

-- Another useful lemma for the interface.
-- Note use of the powerful `convert` tactic.
-- `eq_mul_inv_of_mul_eq h` says ` a = 1 * b⁻¹` which is
-- equal to our goal; convert creates the goals necessary
-- to prove this
lemma eq_inv_of_mul_eq_one {a b : G} (h : a * b = 1) : a = b⁻¹ :=
begin
  convert eq_mul_inv_of_mul_eq h,
  rw one_mul, -- `simp` would also work
end

-- Another useful lemma for the interface
lemma inv_inv (a : G) : a ⁻¹ ⁻¹ = a :=
begin
  symmetry,
  apply eq_inv_of_mul_eq_one,
  simp,
end

-- Another useful lemma for the interface
lemma inv_eq_of_mul_eq_one {a b : G} (h : a * b = 1) : a⁻¹ = b :=
begin
  -- we can change hypotheses with the `replace` tactic.
  -- h implies a = 1 * b⁻¹
  replace h := eq_mul_inv_of_mul_eq h,
  -- and so a = b⁻¹
  rw one_mul at h,
  -- By substituting in, we have to prove (b⁻¹)⁻¹ = b
  rw h,
  -- and we just did this, it's `inv_inv`
  rw inv_inv,
end

lemma unique_id {e : G} (h : ∀ x : G, e * x = x) : e = 1 :=
calc e = e * 1 : by rw mul_one
  ...  = 1 : by rw h 1

-- Maybe add unique_id but with x * e = x

lemma unique_inv {a b : G} (h : a * b = 1) : b = a⁻¹ :=
begin
  apply mul_left_cancel a,
  rw [h, mul_right_inv]
end

lemma mul_right_cancel (a x y : G) (Habac : x * a = y * a) : x = y :=
calc x = x * 1 : by rw mul_one
  ...  = x * (a * a⁻¹) : by rw mul_right_inv
  ...  = x * a * a⁻¹ : by rw mul_assoc
  ...  = y * a * a⁻¹ : by rw Habac
  ...  = y * (a * a⁻¹) : by rw mul_assoc
  ...  = y * 1 : by rw mul_right_inv
  ...  = y : by rw mul_one

lemma mul_left_cancel_iff (a x y : G) : a * x = a * y ↔ x = y :=
begin
  split,
    from mul_left_cancel a x y,
    intro hxy,
    rwa hxy
end

lemma mul_right_cancel_iff (a x y : G) : x * a = y * a ↔ x = y :=
begin
  split,
    from mul_right_cancel a x y,
    intro hxy,
    rwa hxy
end

@[simp] lemma inv_mul_cancel_left (a b : G) : a⁻¹ * (a * b) = b :=
begin
  rw ←mul_assoc, simp
end

@[simp] lemma mul_inv_cancel_left (a b : G) : a * (a⁻¹ * b) = b :=
begin
  rw ←mul_assoc,
  simp
end

lemma inv_mul (a b : G) : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  apply mul_left_cancel (a * b),
  rw mul_right_inv, simp [mul_assoc]
end

lemma one_inv : (1 : G)⁻¹ = 1 :=
by conv_rhs { rw [←(mul_left_inv (1 : G)), mul_one] }

attribute [simp] mul_left_cancel_iff mul_right_cancel_iff inv_mul inv_inv
  one_inv

@[simp] theorem inv_inj_iff {a b : G}: a⁻¹ = b⁻¹ ↔ a = b :=
begin
  split,
  {  intro h,
    rw ← inv_inv a,
    rw h,
    rw inv_inv b},
  { rintro rfl,
    refl}
end

theorem inv_eq {a b : G}: a⁻¹ = b ↔ b⁻¹ = a :=
begin
  split;
  {rintro rfl,
   rw inv_inv}
end

-- **TODO** is this a good simp lemma? I don't think RHS is
-- strictly simpler than LHS.
@[simp] theorem mul_comm {G : Type} [comm_group G] (g h : G) :
  g * h = h * g := comm_group.mul_comm g h

-- **TODO** open an issue about abel only working with `*`. We
-- have `group` working but not `comm_group`. It
-- would be an interesting exercise to get `abel` working.
end group

end mygroup

-- We define an instance giving a Lean group from our home-grown group.
-- Actually, ignore this bit. We get funny name clashes because of it :-(

-- open mygroup.group

-- actually I'm not sure it's a good idea
-- instance mygroup.to_group (G : Type) [mygroup.group G] : group G :=
-- { mul := (*),
--   mul_assoc := mul_assoc,
--   one := 1,
--   one_mul := one_mul,
--   mul_one := mul_one,
--   inv := inv,
--   mul_left_inv := mul_left_inv }

-- to make `group` work, need the simp set for our group.
-- long exercise. Is it interesting for the reader?
-- reference for answer : see my Wednesday talk about algebra hierarchy
-- at LFTCM. Then you can make your own `group` tactic.
-- **TODO** make


/-
Here is the practice exam. It provides you
with a set of problems illustrative of the
material to be tested. This is a homework
assignment due this coming Thursday. We will
review the questions in class.

Copy this file your "work" directory. Work
on it there. Upload to Collab when done.

Note: An incorrect answer above or below
a correct answer can cause Lean to be unable
to process the correct answer. If you are not
able to complete a problem successfully please
comment out your incomplete answer so that we
can see your work but so that your incomplete
work does not cause problems for surrounding
problems and answers.
-/

/-
PART I: Functions. Functions are an essential
element of the language of predicate logic. In
this section, you show that you understand how
to define, use, and reason about functions in
Lean.
-/

/- 1.
Study each of the following definitions,
then answer the associated question about
the types involved in these definitions.
-/

-- Consider this function
def f (n : ℕ) (s : string) := s

/-
a. What is it's return type? Answer:
string
-/

/-
b. What is the type of (f 5)? Answer:
string → string
-/

/-
c. What is the value of (f 0 "yay")
string
-/

/-
d. What is the type of this function?
ℕ → string → string
-/

/- 2.
Define three functions called square,
square', and square'', each of type
ℕ → ℕ. Each function must return the
square of the value to which it is
applied. Write the first function in
"C" style, the second using a tactic
script, and the third using a lambda
abstraction. Declare all argument and
return types explicitly. Answer below.
-/
def square (n: ℕ ) : ℕ := n^2

def square' (n: ℕ ) : ℕ :=
begin
    exact n^2
end

def square'':=
    λ n: ℕ,
        n^2

/- 3.
Construct three proofs to test your
function definitions. The first must
use "lemma" to define a proof, called
square_3_9, of the proposition that
(square 3) equals 9. The second must
use "theorem" to define a proof, called
square'_4_16, of the proposition that
square' applied to 4 reduces to 16. The
third must prove that square'' 5 is
equal to 25. This third proof must not
use the equals sign, =, but must use
"eq" instead to state the proposition
to be proved. Hint on #3, sometimes
you need to use parentheses to express
how you want terms to be grouped. Answer
below.
-/
lemma square_3_9 : square 3 = 9 := rfl

theorem square'_4_16 : square 4 = 16 := rfl

example : eq (square'' 5 ) 25 := rfl

/- 4.
Define a function called last_first.
It must take two string values, called
"first" and "last" (without quotes),
and return a string consisting of "last"
followed by a comma and a space followed
by "first".

For example (last_first "Orson" "Welles")
must return the string, "Welles, Orson".
Write a test case for your function to
prove that (last_first "Orson" "Welles")
is "Welles, Orson". Use "example", to
check the proof. Hint: The ++ operator
implements the string append function.
Answer below.
-/
def last_first (first last: string) : string := last ++ ", " ++ first

example : eq (last_first "Orson" "Welles") "Welles, Orson" := rfl

/- 5.

Complete the following definition of a
function, called apply3. It takes, as an
argument, a function, you might call it f,
of type ℕ → ℕ. It must return a function,
also of type ℕ to ℕ, that, when applied
to a value, n, returns the result of
applying the given function, f, to the
given value, n, three times. The function
returned must compute f(f(f(n))) when it
is applied to an argument, n.
-/

def apply3 : (ℕ → ℕ ) → (ℕ→ ℕ ):=
    λ f : ℕ → ℕ,
        λ n : ℕ, f(f(f(n)))

/- 6.

The Lean libraries define a function,
string.length, that takes a string and
returns its length as a natural number.
Define a function, len2, that takes two
strings and returns the sum of their
lengths. You may use the ++ operator
but not the + operator in your answer.
Follow your function definition with a
test case in the form of a proof using
"example" showing that len2 applied to
"Orson" and "Welles" is 11. Remember:
you might need parenthesis in some
cases to group terms correctly into
larger terms.
-/
def len2 (first last: string) : ℕ :=
    (first++last).length
example : eq(len2 "Orson" "Welles") 11 := rfl

/- 7.
Use "example" to prove that there is a
function of the following type:

uu
example :
((ℕ → ℕ) → (ℕ → ℕ)) →
    ((ℕ → ℕ) → ℕ) →
        ((ℕ → ℕ) → ℕ)
:= λ f g, g

/-
PART II: Functions, revisited. In
mathematics,  functions play a central
role. A function, f, in the mathematical
sense is a triple, f = { D, P, C }, where
D, a set, is the domain of definition of
f, C is the co-domain of f, and P is a
set of ordered pairs, each with a first
element from D and a second element from
C,. In addition, P has one additional
essential  property, the subject of one
of the following questions.
-/

/- 8.

What one additional property is essential to
the definition of what it means for a triple,
{ D, P, C } to be a function?

Name the property. Answer: single-valued

Now explain precisely what it means:  "That there
are no two pairs, (x, y) and (x', y') such that..."

Fill in the blank, and use a logical ∧ in answering.
You might also want to use = or ≠.

Answer: x= x' ^ y = y'
-/

/- 9.

Give names to the following concepts:

The set of all values appearing as the first
element of any pair in P.

Answer: domain

The set of all values appearing as the second
element of any pair in P.

Answer: range

The property that the set of all values
appearing as the first element of P is the
same as the entire set, D.

Answer: total

The property that the set of all
values appearing as the second element of
P is the same as the entire set, C.

Answer: surjective

The property of being one-to-one and onto.

Answer: bijective

-/

/- 10.

What does it mean for a function, f, to be
injective? Give you answer by completing the
following sentences with logical expressions.

"A function, f, is said to be injective if
it has no two pairs, (x, y) and (x', y'),
such that ..."

Answer:

In other words, "If (x, y) and (x', y') are
related by f and x ≠ x' then ..."

Answer:
-/

/- 11.

Suppose that S and T are types and that f
is defined to be a function, *in Lean*, of
type S → T. Which of the following properties,
if any, does f necessarily have?

- injective
- surjective
- bijective
- one-to-many
- one-to-one
- onto
- single-valued
- partial
- total

Answer:

-/

/-
PART III: Logic and Proof.
-/

/- 12a.

Use axiom and/or axioms in Lean to express,
in formal logic, the following assumptions:

- T is a type
- t1 and t2 are values of type T
- t1 = t2

Answer immediately after this comment block.
If you need to introduce a name, use eqt1t2.
-/
-/
axiom T: Type
axioms t1 t2 : T
axiom eqt1t2: t1 = t2

/- 12b.

Use axiom or axioms to represent the
additional assumptions that

- P is a predicate expressing a property of objects of type T
- t1 has property P

If you need to use a name, use Pt1
-/
axiom P : T → Prop
axiom Pt1 : P t1

/- 12 c.

Now use "example" to assert, and then
prove, that t2 also has property P.
-/
example : P t2 := eq.subst eqt1t2 Pt1

/- 13 a.
Define eq_1_0 to be the proposition, 1 = 0.
-/
def eq_1_0 := 1 = 0

/- 13 b.
Define pf_eq_0_0 to be a proof of the
proposition that 0 = 0. Use the lemma
keyword.
-/
lemma pf_eq_0_0 :  0 = 0 := rfl

/- 13 c.
Write a function, w, that takes three
values, a, b, and c of type ℕ, and that
also takes proofs, cb : c = b, and
ba : b = a, and that returns a proof
that a = c.
-/
def w (a b c : ℕ ) (cb : c = b) (ba : b = a) : a = c :=
    eq.trans (eq.symm ba) (eq.symm cb)
#check w

/- 13d.

What is the type of this function?

Answer: ∀ (a b c : ℕ), c = b → b = a → a = c

What is the form of this proposition?

Answer: universal generalization

What's the form the proposition after the
comma?

Answer: Implication

What is the premise of the proposition after
the comma?

Answer:

-/

/- 14.
Complete the following proofs. Give each one
in the form indicated by a comment preceding
the statement of the conjecture to be proved.
When using tactic scripts, remember to write
begin/end pairs right away, so Lean knowns
you want to use a tactic script.
-/

-- lambda expression
example : ∀ (s : string), s = s :=
    λ s, eq.refl s

-- lambda expresion
example : ∀ (n : ℕ), ∀ (m : ℕ), true :=
    λ n m, true.intro

-- tactic script
example : ∀ (T : Type), ∀ (t : T), eq t t :=
begin
    assume T,
    assume t,
    exact eq.refl t
end

-- tactic script
example :
    ∀ (T : Type),
    ∀ (P : T → Prop),
    ∀ t1 t2 : T,
    ∀ Pt1 : P t1,
    ∀ t2t1 : t2 = t1,
    P t2 :=
_

/-
The following problems involve implications.
For example, false → P in an implication. To
prove an implication, just show that there is
(give) a function of the specified type.
-/

-- lambda expression
example : ∀ (P : Prop), false → P :=
_

-- tactic script
example : ∀ (P : Prop), false → P :=
_

-- lambda expression
example : ∀ (P Q : Prop), P ∧ Q → Q ∧ P :=
_

-- tactic script
example : ∀ (P Q : Prop), P ∧ Q → Q ∧ P :=
_

-- tactic script
example :
    ∀ T : Type,
    ∀ (t1 t2 t3 : T),
    t1 = t2 ∧ t2 = t3 → t1 = t3 :=
_

/- 15.

Use Lean to model a world in which there
are Dogs, all Dogs are friendly, and Fido
is a Dog, with a proof that in this world,
Fido must be friendly, too.
-/


-- Math 52: Quiz 5
-- Open this file in a folder that contains 'utils'.

import utils
open classical

definition divides (a b : ℤ) : Prop := ∃ (k : ℤ), b = a * k
local infix ∣ := divides

axiom not_3_divides : ∀ (m : ℤ), ¬ (3 ∣ m) ↔ 3 ∣ m - 1 ∨ 3 ∣ m + 1

lemma not_3_divides_of_3_divides_minus_1 :
∀ (m : ℤ), 3 ∣ m - 1 → ¬ (3 ∣ m) :=
begin
intros m H,
rw not_3_divides,
left,
assumption,
end

lemma not_3_divides_of_3_divides_plus_1 :
∀ (m : ℤ), 3 ∣ m + 1 → ¬ (3 ∣ m) :=
begin
intros m H,
rw not_3_divides,
right,
assumption,
end

theorem main : ∀ (n : ℤ), 3 ∣ n * n - 1 → ¬ (3 ∣ n) :=
begin
sorry
end


import algebra.big_operators data.set.finite

def matrix (α : Type*) (m n : ℕ) := fin m → fin n → α

namespace matrix
variables {α : Type*} [ring α]
variables {l m n o : ℕ}

instance : has_zero (matrix α m n) :=
⟨λ _ _, 0⟩

instance : has_neg (matrix α m n) :=
⟨λ M x y, - M x y⟩

instance : has_add (matrix α m n) :=
⟨λ M N x y, M x y + N x y⟩

@[simp] theorem add_val {M N : matrix α m n} {x : fin m} {y : fin n} : (M + N) x y = M x y + N x y :=
rfl

theorem add_assoc (L : matrix α m n) (M : matrix α m n) (N : matrix α m n) :
  L + (M + N) = (L + M) + N :=
funext $ λ x, funext $ λ y, by simp

def mul (M : matrix α l m) (N : matrix α m n) : matrix α l n :=
λ x z, finset.univ.sum (λ y, M x y * N y z)

@[simp] theorem mul_val {M : matrix α l m} {N : matrix α m n} {x : fin l} {z : fin n} :
  (M.mul N) x z = finset.univ.sum (λ y, M x y * N y z) :=
rfl

theorem mul_assoc (L : matrix α l m) (M : matrix α m n) (N : matrix α n o) :
  L.mul (M.mul N) = (L.mul M).mul N :=
funext $ λ x, funext $ λ z,
  calc finset.univ.sum (λ (y₁ : fin m), L x y₁ * finset.univ.sum (λ (y₂ : fin n), M y₁ y₂ * N y₂ z))
    = finset.univ.sum (λ (y₁ : fin m), finset.univ.sum (λ (y₂ : fin n), L x y₁ * M y₁ y₂ * N y₂ z)) :
      by congr; funext; rw finset.mul_sum; congr; funext; rw mul_assoc
    ... = finset.univ.sum (λ (y₂ : fin n), finset.univ.sum (λ (y₁ : fin m), L x y₁ * M y₁ y₂ * N y₂ z)) :
      by rw finset.sum_comm
    ... = finset.univ.sum (λ (y₂ : fin n), finset.univ.sum (λ (y₁ : fin m), L x y₁ * M y₁ y₂) * N y₂ z) :
      by congr; funext; rw ←finset.sum_mul

end matrix


/-
Copyright (c) 2020 The Xena project. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Buzzard.
Thanks: Imperial College London, leanprover-community
-/

-- We will assume that the real numbers are a field.
import data.real.basic

/-!
# The complex numbers

We define the complex numbers, and prove that they are a ring.

We also "extract some basic API" (e.g. we prove that
two complex numbers are equal iff they have the same
real and imaginary parts)

This file has no `sorry`s in. All of the other levels:

`I.lean`
`conj.lean`
`norm_sq.lean`
`of_real.lean`
`field.lean`
`alg_closed.lean`

have sorrys, indicating puzzles to be solved.

# Main Definitions

`zero` : the complex number 0
`one` : the complex number 1
`add` -- addition of two complex numbers
`neg` -- negation of a complex number
`mul` -- multiplication of two complex numbers

# Main Theorem

`comm_ring` : The complex numbers are a commutative ring.

-/

/-- A complex number is defined to be a structure consisting of two real numbers,
    the real part and the imaginary part of the complex number   . -/
structure complex : Type :=
(re : ℝ) (im : ℝ)

-- Let's use the usual notation for the complex numbers
notation `ℂ` := complex

-- You make the complex number with real part 3 and imaginary part 4 like this:
example : ℂ :=
{ re := 3,
  im := 4 }

-- Or like this:
example : ℂ := complex.mk 3 4

-- or like this:
example : ℂ := ⟨3, 4⟩

-- They all give the same complex number.

example : complex.mk 3 4 = ⟨3, 4⟩ := begin refl end  -- "true by definition"

-- All of our definitions, like `zero` and `one`, will all
-- live in the `complex` namespace.

namespace complex

-- If you have a complex number, then you can get its real and
-- imaginary parts with the `re` and `im` functions.

example : ℝ := re(mk 3 4) -- this term is (3 : ℝ)

example : re(mk 3 4) = 3 := begin refl end

-- Computer scientists prefer the style `z.re` to `re(z)` for some reason.

example : (mk 3 4).re = 3 := begin refl end

example (z : ℂ) : re(z) = z.re := begin refl end

-- Before we start making the basic
-- We now prove the basic theorems and make the basic definitions for
-- complex numbers. For example, we will define addition and multiplication on
-- the complex numbers, and prove that it is a commutative ring.
-- TODO fix

/-! # Defining the ring structure on ℂ -/

-- Our main goal is to prove that the complexes are a ring. Let's
-- define the structure first; the zero, one, addition and multiplication
-- on the complexes.

/-! ## zero (0) -/

/-- The complex number with real part 0 and imaginary part 0 -/
def zero : ℂ := ⟨0, 0⟩

-- Now we set up notation so that `0 : ℂ` will mean `zero`.

/-- notation `0` for `zero` -/
instance : has_zero ℂ := ⟨zero⟩

-- Let's prove the two basic properties, both of which are true by definition,
-- and then tag them with the appropriate attributes.
@[simp] lemma zero_re : re(0 : ℂ) = 0 := begin refl end
@[simp] lemma zero_im : im(0 : ℂ) = 0 := begin refl end

/-! ## one (1) -/

-- Now let's do the same thing for 1.

/-- The complex number with real part 1 and imaginary part 0 -/
def one : ℂ := ⟨1, 0⟩

/-- Notation `1` for `one` -/
instance : has_one ℂ := ⟨one⟩

-- name the basic properties and tag them with `simp`
@[simp] lemma one_re : re(1 : ℂ) = 1 := begin refl end
@[simp] lemma one_im : im(1 : ℂ) = 0 := begin refl end

/-! ## add (+) -/

-- Now let's define addition

/-- addition `z+w` of complex numbers -/
def add (z w : ℂ) : ℂ := ⟨z.re + w.re, z.im + w.im⟩

/-- Notation `+` for addition -/
instance : has_add ℂ := ⟨add⟩

-- basic properties
@[simp] lemma add_re (z w : ℂ) : re(z + w) = re(z) + re(w) := begin refl end
@[simp] lemma add_im (z w : ℂ) : im(z + w) = im(z) + im(w) := begin refl end

/-! ## neg (-) -/

-- negation

/-- The negation `-z` of a complex number `z` -/
def neg (z : ℂ) : ℂ := ⟨-re(z), -im(z)⟩

/-- Notation `-` for negation -/
instance : has_neg ℂ := ⟨neg⟩

-- how neg interacts with re and im
@[simp] lemma neg_re (z : ℂ) : re(-z) = -re(z) := begin refl end
@[simp] lemma neg_im (z : ℂ) : im(-z) = -im(z) := begin refl end

/-! ## mul (*) -/

-- multiplication

/-- Multiplication `z*w` of two complex numbers -/
def mul (z w : ℂ) : ℂ := ⟨re(z) * re(w) - im(z) * im(w), re(z) * im(w) + im(z) * re(w)⟩

/-- Notation `*` for multiplication -/
instance : has_mul ℂ := ⟨mul⟩

-- how `mul` reacts with `re` and `im`
@[simp] lemma mul_re (z w : ℂ) : (z * w).re = z.re * w.re - z.im * w.im := begin refl end
@[simp] lemma mul_im (z w : ℂ) : (z * w).im = z.re * w.im + z.im * w.re := begin refl end

/-! ## Example of what `simp` can now do -/

example (a b c : ℂ) : re(a*(b+c)) = re(a) * (re(b) + re(c)) - im(a) * (im(b) + im(c)) :=
begin
  simp,
end

/-! # `ext` : A mathematical triviality -/

/-
Two complex numbers with the same and imaginary parts are equal.
This is an "extensionality lemma", i.e. a lemma of the form "if two things
are made from the same pieces, they are equal".
This is not hard to prove, but we want to give the result a name
so we can tag it with the `ext` attribute, meaning that the
`ext` tactic will know it. To add to the confusion, let's call the theorem `ext` :-)
-/

/-- If two complex numbers z and w have equal real and imaginary parts, they are equal -/
@[ext] theorem ext {z w : ℂ} (hre : re(z) = re(w)) (him : im(z) = im(w)) : z = w :=
begin
  cases z with zr zi,
  cases w with ww wi,
  simp at *,
  /- goal now a logic puzzle

  hre : zr = ww,
  him : zi = wi
  ⊢ zr = ww ∧ zi = wi

  -/
  cc,
end

/-! # Theorem:  The complex numbers are a commutative ring -/

-- Proof: we've defined all the structure, and every axiom can be checked by reducing it
-- to checking real and imaginary parts with `ext`, expanding everything out with `simp`
-- and then using the fact that the real numbers are a commutative ring.
instance : comm_ring ℂ :=
begin
  -- first the data
  refine_struct {
      zero := (0 : ℂ), add := (+), neg := has_neg.neg, one := 1, mul := (*),
  ..},
  -- now the axioms
  -- of which there seem to be 11
  -- introduce the variables
  all_goals {intros},
  -- we now have to prove an equality between two complex numbers.
  -- It suffices to check on real and imaginary parts
  all_goals {ext},
  -- the simplifier can simplify stuff like re(a+0)
  all_goals {simp},
  -- all the goals now are identities between *real* numbers,
  -- and the reals are already known to be a ring
  all_goals {ring},
end

-- That is the end of the proof that the complexes form a ring. We built
-- a basic API which was honed towards the general idea that to prove
-- certain statements about the complex numbers, for example distributivity,
-- we could just check on real and imaginary parts. We trained the
-- simplifier to expand out things like re(z*w) in terms
-- of re(z), im(z), re(w), im(w).

/-!

# Optional section for mathematicians : more basic infrastructure, and term mode

-/

/-!
## `ext` revisited

Recall extensionality:

`theorem ext {z w : ℂ} (hre : re(z) = re(w)) (him : im(z) = im(w)) : z = w := ...`

Here is another tactic mode proof of extensionality. Note that we have moved
the hypotheses to the other side of the colon; this does not
change the theorem. This proof shows the power
of the `rintros` tactic.
-/

theorem ext' : ∀ z w : ℂ, z.re = w.re → z.im = w.im → z = w :=
begin
  rintros ⟨zr, zi⟩ ⟨_, _⟩ ⟨rfl⟩ ⟨rfl⟩,
  refl,
end

/-
Explanation: `rintros` does `cases` as many times as you like using this cool `⟨ ⟩` syntax
for the case splits. Note that if you say that a proof of `a = b` is `rfl` then
Lean will define a to be b, or b to be a, and not even introduce new notation for it.
-/

-- Here is the same proof in term mode.

theorem ext'' : ∀ {z w : ℂ}, z.re = w.re → z.im = w.im → z = w
| ⟨zr, zi⟩ ⟨_, _⟩ rfl rfl := rfl

/-!
## `eta`
-/

/-
We prove the mathematically obvious statement that the
complex number whose real part is re(z) and whose imaginary
part is im(z) is of course equal to z.
-/

/-- ⟨z.re, z.im⟩ is equal to z -/
@[simp] theorem eta : ∀ z : ℂ, complex.mk z.re z.im = z :=
begin
  intro z,
  cases z with x y,
  /-
    goal now looks complicated, and contains terms which look
    like {re := a, im := b}.re which obviously simplify to a.
    The `dsimp` tactic will do some tidying up for us, although
    it is not logically necessary. `dsimp` does definitional simplification.
  -/
  dsimp,
  -- Now we see the goal can be solved by reflexivity
  refl,
end

/-
The proof was "unfold everything, and it's true by definition".
This proof does not teach a mathematician anything, so we may as well write
it in term mode. Many tactics have term mode equivalent.
The equation compiler does the `intro` and `cases` steps,
and `dsimp` was unnecessary -- the two sides of the equation
were definitionally equal.
-/

theorem eta' : ∀ z : ℂ, complex.mk z.re z.im = z
| ⟨x, y⟩ := rfl

/-!
## ext_iff
-/

/-
Note that `ext` is an implication -- if re(z)=re(w) and im(z)=im(w) then z=w.
The below variant `ext_iff` is the two-way implication: two complex
numbers are equal if and only if they have the same real and imaginary part.
Let's first see a tactic mode proof. See how the `ext` tactic is used?
After it is applied, we have two goals, both of which are hypotheses.
The semicolon means "apply the next tactic to all the goals produced by this one"
-/

theorem ext_iff {z w : ℂ} : z = w ↔ z.re = w.re ∧ z.im = w.im :=
begin
  split,
  { intro H,
    simp [H]},
  {
    rintro ⟨hre, him⟩,
    ext; assumption,
  }
end

-- Again this is easy to write in term mode, and no mathematician
-- wants to read the proof anyway.

theorem ext_iff' {z w : ℂ} : z = w ↔ z.re = w.re ∧ z.im = w.im :=
⟨λ H, by simp [H], and.rec ext⟩

end complex

/-!

# some last comments on the `simp` tactic

Some equalities, even if obvious, had to be given names, because we want `simp`
to be able to use them. In short, the `simp` tactic tries to solve
goals of the form A = B, when `refl` doesn't work (i.e. the goals are
not definitionally equal) but when any mathematician would be able
to simplify A and B via "obvious" steps such as `0 + x = x` or
`⟨z.re, z.im⟩ = z`. These things are sometimes not true by definition,
but they should be tagged as being well-known ways to simplify an equality.
When building our API for the complex numbers, if we prove a theorem of the
form `A = B` where `B` is a bit simpler than `A`, we should probably
tag it with the `@[simp]` attribute, so `simp` can use it.

Note: `simp` does *not* prove "all simple things". It proves *equalities*.
It proves `A = B` when, and only when, it can do it by applying
its "simplification rules", where a simplification rule is simply a proof
of a theorem of the form `A = B` and `B` is simpler than `A`.
-/


/- Tactic : rw / rwa

## Summary

If `h` is a proof of `X = Y`, then `rw h,` will change
all `X`s in the goal to `Y`s.

**Variants:** `rw ← h` changes
`Y` to `X` and
`rw h at h2` changes `X` to `Y` in hypothesis `h2` instead
of the goal.

**Variant (rw and assumption):** If instead you use `rwa h` or `rwa ← h`, Lean does performs
the `rw` and then looks whether
the goal is exactly one of your assumptions, in which case it closes it.

## Details

The `rw` tactic is a way to do "substituting in". There
are two distinct situations where use this tactics.

1) If `h : A = B` is a hypothesis (i.e., a proof of `A = B`)
in your local context (the box in the top right)
and if your goal contains one or more `A`s, then `rw h`
will change them all to `B`'s.

2) The `rw` tactic will also work with proofs of theorems
which are equalities (look for them in the drop down
menu on the left, within Theorem Statements).

**Important note:** if `h` is not a proof of the form `A = B`
or `A ↔ B` (for example if `h` is a function, an implication,
or perhaps even a proposition itself rather than its proof),
then `rw` is not the tactic you want to use. For example,
`rw (P = Q)` is never correct: `P = Q` is the true-false
statement itself, not the proof.
If `h : P = Q` is its proof, then `rw h` will work.

**Pro tip 1:** If `h : A = B` and you want to change
`B`s to `A`s instead, try `rw ←h` (get the arrow with `\l`,
note that this is a small letter L, not a number 1).

### Example:
If it looks like this in the top right hand box:
```
A B C : set X
h : A = B ∪ C
⊢ A ∪ B = B ∪ C
```

then

`rw h,`

will change the goal into `⊢ B ∪ C ∪ B = B ∪ C`.

### Example:
You can use `rw` to change a hypothesis as well.
For example, if your local context looks like this:
```
A B C D : set X
h1 : A = B ∩ C
h2 : B ∪ A = D
⊢ D = B
```
then `rw h1 at h2` will turn `h2` into `h2 : B ∪ B ∩ C = D` (remember operator precedence).

-/

/-
The next tactic we will learn is *rw* (from rewrite). It rewrites equalities. That is,
if we have a proof `h : A = B` and we want to prove `⊢ A ∩ C = B ∩ C`, then after `rw h` the goal
will become `⊢ A ∩ C = A ∩ C`, which seems reasonable.

After many tactics (and `rw` is one of them) Lean tries to apply `refl`. This is why
in the following proof you may get away with only one tactic application.

-/

/- Hint : Click here for a hint, in case you get stuck.
Delete `sorry` and type `rw h,` (don't forget the comma!). Lean tries `refl` afterwards,
so you will see that this suffices.
-/

variables {X : Type} -- hide

/- Lemma : no-side-bar
If A, B and C are sets and A = B, then A ∪ C = B ∪ C.
-/
lemma union_eq (A B C: set X) (h : A = B) : A ∪ C = B ∪ C :=
begin
  rw h,

end


import MyNat.Power
namespace MyNat
open MyNat

/-!
## Level 1: `zero_pow_zero`

Given the lemma `pow_zero` which says `m ^ 0 = 1`
you can now prove zero to the power of zero is also one.

## Lemma
`0 ^ 0 = 1`.
-/
lemma zero_pow_zero : (0 : MyNat) ^ (0 : MyNat) = 1 := by
  rw [pow_zero]

/-!
That was easy!  Next up [Level 2](./Level2.lean.md)
-/


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

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

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

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

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


/-
Copyright (c) 2021 Damiano Testa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Damiano Testa
-/
import algebra.smul_with_zero
import algebra.regular
/-!
# Action of regular elements on a module

We introduce `M`-regular elements, in the context of an `R`-module `M`.  The corresponding
predicate is called `is_smul_regular`.

There are very limited typeclass assumptions on `R` and `M`, but the "mathematical" case of interest
is a commutative ring `R` acting an a module `M`. Since the properties are "multiplicative", there
is no actual requirement of having an addition, but there is a zero in both `R` and `M`.
Smultiplications involving `0` are, of course, all trivial.

The defining property is that an element `a ∈ R` is `M`-regular if the smultiplication map
`M → M`, defined by `m ↦ a • m`, is injective.

This property is the direct generalization to modules of the property `is_left_regular` defined in
`algebra/regular`.  Lemma `is_smul_regular.is_left_regular_iff` shows that indeed the two notions
coincide.
-/

variables {R S : Type*} (M : Type*) {a b : R} {s : S}

/-- An `M`-regular element is an element `c` such that multiplication on the left by `c` is an
injective map `M → M`. -/
def is_smul_regular [has_scalar R M] (c : R) := function.injective ((•) c : M → M)

namespace is_smul_regular

variables {M}

section has_scalar

variables [has_scalar R M] [has_scalar R S] [has_scalar S M] [is_scalar_tower R S M]

/-- The product of `M`-regular elements is `M`-regular. -/
lemma smul (ra : is_smul_regular M a) (rs : is_smul_regular M s) :
  is_smul_regular M (a • s) :=
λ a b ab, rs (ra ((smul_assoc _ _ _).symm.trans (ab.trans (smul_assoc _ _ _))))

/-- If an element `b` becomes `M`-regular after multiplying it on the left by an `M`-regular
element, then `b` is `M`-regular. -/
lemma of_smul (a : R) (ab : is_smul_regular M (a • s)) :
  is_smul_regular M s :=
@function.injective.of_comp _ _ _ (λ m : M, a • m) _ (λ c d cd, ab
  (by rwa [smul_assoc, smul_assoc]))

/-- An element is `M`-regular if and only if multiplying it on the left by an `M`-regular element
is `M`-regular. -/
@[simp] lemma smul_iff (b : S) (ha : is_smul_regular M a) :
  is_smul_regular M (a • b) ↔ is_smul_regular M b :=
⟨of_smul _, ha.smul⟩

end has_scalar

section monoid

variables [monoid R] [mul_action R M]

/-- Left-regularity in a `monoid R` is equivalent to `M`-regularity, when the
`R`-module `M` is `R`. -/
lemma is_left_regular_iff (a : R) : is_left_regular a ↔ is_smul_regular R a :=
iff.rfl

variable (M)

/-- One is `M`-regular always. -/
@[simp] lemma one : is_smul_regular M (1 : R) :=
λ a b ab, by rwa [one_smul, one_smul] at ab

variable {M}

lemma mul (ra : is_smul_regular M a) (rb : is_smul_regular M b) :
  is_smul_regular M (a * b) :=
ra.smul rb

lemma of_mul (ab : is_smul_regular M (a * b)) :
  is_smul_regular M b :=
by { rw ← smul_eq_mul at ab, exact ab.of_smul _ }

@[simp] lemma mul_iff_right  (ha : is_smul_regular M a) :
  is_smul_regular M (a * b) ↔ is_smul_regular M b :=
⟨of_mul, ha.mul⟩

/-- Two elements `a` and `b` are `M`-regular if and only if both products `a * b` and `b * a`
are `M`-regular. -/
lemma mul_and_mul_iff : is_smul_regular M (a * b) ∧ is_smul_regular M (b * a) ↔
  is_smul_regular M a ∧ is_smul_regular M b :=
begin
  refine ⟨_, _⟩,
  { rintros ⟨ab, ba⟩,
    refine ⟨ba.of_mul, ab.of_mul⟩ },
  { rintros ⟨ha, hb⟩,
    exact ⟨ha.mul hb, hb.mul ha⟩ }
end

/-- Any power of an `M`-regular element is `M`-regular. -/
lemma pow (n : ℕ) (ra : is_smul_regular M a) : is_smul_regular M (a ^ n) :=
begin
  induction n with n hn,
  { simp only [one, pow_zero] },
  { rw pow_succ, exact (ra.smul_iff (a ^ n)).mpr hn }
end

/-- An element `a` is `M`-regular if and only if a positive power of `a` is `M`-regular. -/
lemma pow_iff {n : ℕ} (n0 : 0 < n) :
  is_smul_regular M (a ^ n) ↔ is_smul_regular M a :=
begin
  refine ⟨_, pow n⟩,
  rw [← nat.succ_pred_eq_of_pos n0, pow_succ', ← smul_eq_mul],
  exact of_smul _,
end

end monoid

section monoid_with_zero

variables [monoid_with_zero R] [monoid_with_zero S] [has_zero M] [mul_action_with_zero R M]
  [mul_action_with_zero R S] [mul_action_with_zero S M] [is_scalar_tower R S M]

/-- The element `0` is `M`-regular if and only if `M` is trivial. -/
protected lemma subsingleton (h : is_smul_regular M (0 : R)) : subsingleton M :=
⟨λ a b, h (by repeat { rw mul_action_with_zero.zero_smul })⟩

/-- The element `0` is `M`-regular if and only if `M` is trivial. -/
lemma zero_iff_subsingleton : is_smul_regular M (0 : R) ↔ subsingleton M :=
⟨λ h, h.subsingleton, λ H a b h, @subsingleton.elim _ H a b⟩

/-- The `0` element is not `M`-regular, on a non-trivial module. -/
lemma not_zero_iff : ¬ is_smul_regular M (0 : R) ↔ nontrivial M :=
begin
  rw [nontrivial_iff, not_iff_comm, zero_iff_subsingleton, subsingleton_iff],
  push_neg,
  exact iff.rfl
end

/-- The element `0` is `M`-regular when `M` is trivial. -/
lemma zero [sM : subsingleton M] : is_smul_regular M (0 : R) :=
zero_iff_subsingleton.mpr sM

/-- The `0` element is not `M`-regular, on a non-trivial module. -/
lemma not_zero [nM : nontrivial M] : ¬ is_smul_regular M (0 : R) :=
not_zero_iff.mpr nM

/-- An element of `S` admitting a left inverse in `R` is `M`-regular. -/
lemma of_smul_eq_one (h : a • s = 1) : is_smul_regular M s :=
of_smul a (by { rw h, exact one M })

/-- An element of `R` admitting a left inverse is `M`-regular. -/
lemma of_mul_eq_one (h : a * b = 1) : is_smul_regular M b :=
of_mul (by { rw h, exact one M })

end monoid_with_zero

section comm_monoid

variables [comm_monoid R] [mul_action R M]

/-- A product is `M`-regular if and only if the factors are. -/
lemma mul_iff : is_smul_regular M (a * b) ↔
  is_smul_regular M a ∧ is_smul_regular M b :=
begin
  rw ← mul_and_mul_iff,
  exact ⟨λ ab, ⟨ab, by rwa mul_comm⟩, λ rab, rab.1⟩
end

end comm_monoid

end is_smul_regular

variables [monoid_with_zero R] [has_zero M] [mul_action_with_zero R M]

/-- Any element in `units R` is `M`-regular. -/
lemma units.is_smul_regular (a : units R) : is_smul_regular M (a : R)
:=
is_smul_regular.of_mul_eq_one a.inv_val

/-- A unit is `M`-regular. -/
lemma is_unit.is_smul_regular (ua : is_unit a) : is_smul_regular M a :=
begin
  rcases ua with ⟨a, rfl⟩,
  exact a.is_smul_regular M
end


import ..prooflab
import lectures.lec0_intro

/-! # Homework 0
Homework must be done individually.
Replace the placeholders `sorry` with your proofs only using tactics `refl`, `exact` and `rw`.
-/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace PROOFS

/-! ## Question 1  -/

example (x y : ℕ) :
  y + 0 = y :=
begin
  refl,
end

/-! ## Question 2 -/

example (m n : ℕ) (h₁ : n = 4) (h₂: m^2 = n) :
  n = m^2 :=
begin
 rw h₂,
end

/-! ## Question 3 -/

example (x y : ℕ) (h₁ : y = x) (h₂ : y - 1 = 0) :
 5^(y - 1) = (2 + 3)^(x - 1) :=
begin
  rw h₁,
end

/-! ## Question 4 -/

example (x y : ℕ) (h₁ : y = x) (h₂ : x - 1 = 0) :
 5^(y - 1) = 5^0 :=
begin
  rw h₁, -- this changes `y` to `x` in the left hand side of the goal. Therefore, our new goal is 5^(x-1) = 5 ^ 0
  rw h₂, -- this turns `x-1` into `0` by virtue of `h₂`
end

/-! ## Question 5 -/

example (a b c x y z : ℕ) (h₁ : 26 = x^2 + y^2 + z^2)
(h₂ : x^2 = 2 * a) (h₃ : y^2 = b) (h₄ : z^2 = 2) :
2 * a + b + 2 - z = 26 - z :=
begin
 rw ← h₂,
 rw ← h₃,
 rw h₁,
 rw h₄,
end

end PROOFS


import algebra.ring
import data.real.basic
import tactic

section
variables (R : Type*) [ring R]

#check (add_assoc : ∀ a b c : R, a + b + c = a + (b + c))
#check (add_comm : ∀ a b : R, a + b = b + a)
#check (zero_add : ∀ a : R, 0 + a = a)
#check (add_left_neg : ∀ a : R, -a + a = 0)
#check (mul_assoc : ∀ a b c : R, a * b * c = a * (b * c))
#check (mul_one : ∀ a : R, a * 1 = a)
#check (one_mul : ∀ a : R, 1 * a = a)
#check (mul_add : ∀ a b c : R, a * (b + c) = a * b + a * c)
#check (add_mul : ∀ a b c : R, (a + b) * c = a * c + b * c)
end

section
variables (R : Type*) [comm_ring R]
variables a b c d : R

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

example : (a + b) * (a + b) = a * a + 2 * (a * b) + b * b :=
by ring

example : (a + b) * (a - b) = a^2 - b^2 :=
by ring

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

namespace my_ring
variables {R : Type*} [ring R]

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

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

#check @my_ring.add_zero
#check @add_zero

end my_ring

namespace my_ring
variables {R : Type*} [ring R]

theorem neg_add_cancel_left {a b : R} : -a + (a + b) = b :=
  by rw [←add_assoc, add_left_neg, zero_add]

theorem add_neg_cancel_right {a b : R} : (a + b) + -b = a :=
  by rw [add_assoc, add_right_neg, add_zero]

theorem add_left_cancel {a b c : R} (h : a + b = a + c) : b = c :=
begin
  have : -a + (a + b) = -a +(a + c) := by rw [h],
  rw [neg_add_cancel_left, neg_add_cancel_left] at this,
  exact this,
end

theorem add_right_cancel {a b c : R} (h : a + b = c + b) : a = c :=
begin
  have : b + a = b + c := by rw [add_comm a b, add_comm c b, h] at h; exact h,
  exact add_left_cancel this
end

theorem mul_zero (a : R) : a * 0 = 0 :=
begin
  have h : a * 0 + a * 0 = a * 0 + 0,
  { rw [←mul_add, add_zero, add_zero] },
  rw add_left_cancel h
end

theorem zero_mul (a : R) : 0 * a = 0 :=
begin
  have : 0 * a + 0 * a = 0 * a + 0,
  { rw [←add_mul, add_zero, add_zero]},
  exact add_left_cancel this,
end

theorem neg_eq_of_add_eq_zero {a b : R} (h : a + b = 0) : -a = b :=
begin
  have : -a + (a + b) = -a + 0 := by rw[h],
  have : b = -a := by rw [neg_add_cancel_left, add_zero] at this; exact this,
  by rw this,
end

theorem eq_neg_of_add_eq_zero {a b : R} (h : a + b = 0) : a = -b :=
begin
  have : (a + b) + -b = 0 + -b := by rw [h],
  rw [add_neg_cancel_right, zero_add] at this,
  exact this,
end

theorem neg_zero : (-0 : R) = 0 :=
begin
  apply neg_eq_of_add_eq_zero,
  rw add_zero
end

theorem neg_neg (a : R) : -(-a) = a :=
begin
  have : -(-a) + -a = 0 := by rw add_left_neg,
  have : (-(-a) + -a) + a = 0 + a := by rw [this],
  have : -(-a) + (-a + a) = a := by rw [zero_add, add_assoc] at this; exact this,
  rw [add_left_neg, add_zero] at this,
  exact this
end

end my_ring

namespace my_ring

variables {R : Type*} [ring R]

theorem self_sub (a : R) : a - a = 0 :=
begin
  calc
    a - a
        = (0 + a) - a :
          by rw [zero_add]
    ... = 0 :
          by rw [sub_eq_add_neg, add_neg_cancel_right]
end

lemma one_add_one_eq_two : 1 + 1 = (2 : R) :=
by refl

theorem two_mul (a : R) : 2 * a = a + a :=
begin
  calc
    2 * a
        = (1 + 1) * a : by rw [one_add_one_eq_two]
    ... = a + a : by rw [add_mul, one_mul]
end

end my_ring

section
variables (A : Type*) [add_group A]

#check (add_assoc : ∀ a b c : A, a + b + c = a + (b + c))
#check (zero_add : ∀ a : A, 0 + a = a)
#check (add_left_neg : ∀ a : A, -a + a = 0)
end

section
variables {G : Type*} [group G]

#check (mul_assoc : ∀ a b c : G, a * b * c = a * (b * c))
#check (one_mul : ∀ a : G, 1 * a = a)
#check (mul_left_inv : ∀ a : G, a⁻¹ * a = 1)

namespace my_group

lemma mul_left_cancel {a b c : G} : a * b = a * c → b = c :=
begin
  intros h,
  have h : a⁻¹ * (a * b) = a⁻¹ * (a * c) := by rw [h],
  have h : (a⁻¹ * a) * b = (a⁻¹ * a) * c :=
    by rw [h, ←mul_assoc, ←mul_assoc] at h; exact h,
  have : 1 * b = 1 * c :=
    by rw [h, mul_left_inv] at h; exact h,
  rw [one_mul, one_mul] at this,
  exact this,
end

theorem mul_right_inv {a : G} : a * a⁻¹ = 1 :=
begin
  have : (a * a⁻¹)⁻¹ * ((a * a⁻¹) * (a * a⁻¹)) = 1 :=
    by rw [mul_assoc, ←mul_assoc a⁻¹ a, mul_left_inv, one_mul, mul_left_inv],
  rw [←this, ←mul_assoc, mul_left_inv, one_mul],
end

theorem mul_one {a : G} : a * 1 = a :=
begin
  calc
    a * 1
        = a * (a⁻¹ * a) : by rw [mul_left_inv]
    ... = (a * a⁻¹) * a : by rw [mul_assoc]
    ... = a : by rw [mul_right_inv, one_mul]
end

lemma mul_right_cancel {a b c : G} : a * c = b * c → a = b :=
begin
  intros h,
  have : (a * c) * c⁻¹ = (b * c) * c⁻¹ := by rw [h],
  have : a * (c * c⁻¹) = b * (c * c⁻¹) :=
    by rw [mul_assoc, mul_assoc] at this; exact this,
  have : a * 1 = b * 1 :=
    by rw [mul_right_inv] at this; exact this,
  rw [mul_one, mul_one] at this; exact this,
end

theorem mul_inv_rev (a b : G) : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  have : b⁻¹ * a⁻¹ = (a * b)⁻¹,
    calc
      b⁻¹ * a⁻¹
          = (a * b)⁻¹ * (a * b) * (b⁻¹ * a⁻¹) :
            by rw [←one_mul (b⁻¹ * a⁻¹), ←mul_left_inv (a * b), mul_left_inv, one_mul, one_mul]
      ... = (a * b)⁻¹ * (a * (b * (b⁻¹ * a⁻¹))) :
        by rw [mul_assoc, mul_assoc]
      ... = (a * b)⁻¹ * a * (b * b⁻¹) * a⁻¹ :
        by simp only [mul_assoc]
      ... = (a * b)⁻¹ * (a * a ⁻¹) :
        by simp only [mul_right_inv, mul_one, mul_assoc]
      ... = (a * b)⁻¹ :
        by simp only [mul_right_inv, mul_one],
  rw [this],
end

end my_group
end


/-
 -----------------------------------------------------------
  Negligible functions.

  TO-DO connect with security parameter, (or not, as in Nowak),
  and refactor proofs/improve variable naming
 -----------------------------------------------------------
-/

import analysis.special_functions.exp_log
import analysis.special_functions.pow
import data.nat.basic
import data.real.basic

/-
  A function f : ℤ≥1 → ℝ is called negligible if
  for all c ∈ ℝ>0 there exists n₀ ∈ ℤ≥1 such that
  n₀ ≤ n →  |f(n)| < 1/n^c
-/
def negligible (f : ℕ → ℝ) :=
  ∀ c > 0, ∃ n₀, ∀ n,
  n₀ ≤ n → abs (f n) <  1 / (n : ℝ)^c

def negligible' (f : ℕ → ℝ) :=
  ∀ (c : ℝ), ∃ (n₀ : ℕ), ∀ (n : ℕ),
  0 < c → n₀ ≤ n → abs (f n) < 1 / n^c

lemma negl_equiv (f : ℕ → ℝ) : negligible f ↔ negligible' f :=
begin
  split,
  {-- Forward direction
    intros h c,
    have arch := exists_nat_gt c,
    cases arch with k hk,
    let k₀ := max k 1,
    have k_leq_k₀ : k ≤ k₀ := le_max_left k 1,
    have kr_leq_k₀r : (k:ℝ) ≤ k₀ := nat.cast_le.mpr k_leq_k₀,
    have k₀_pos : 0 < k₀ := by {apply le_max_right k 1},
    have a := h k₀ k₀_pos,
    cases a with n' hn₀,
    let n₀ := max n' 1,
    have n₀_pos : 0 < n₀ := by apply le_max_right n' 1,
    have n'_leq_n₀ : n' ≤ n₀ := le_max_left n' 1,
    use n₀,
    intros n c_pos hn,
    have hnnn : n' ≤ n := by linarith,

    have b : (n : ℝ)^c ≤ (n : ℝ)^(k₀ : ℝ) :=
    begin
      apply real.rpow_le_rpow_of_exponent_le,
      norm_cast,
      linarith,
      linarith,
    end,
    have daf : (n : ℝ)^(k₀ : ℝ) = (n : ℝ)^k₀ := (n : ℝ).rpow_nat_cast k₀,
    rw daf at b,
    have d : 1 / (n : ℝ)^k₀ ≤ 1 / n^c :=
    begin
      apply one_div_le_one_div_of_le,
      { -- Proving 0 < (n:ℝ) ^ c
        apply real.rpow_pos_of_pos,
        norm_cast,
        linarith,
      },
      {exact b},
    end,
    have goal :  abs (f n) < 1 / n^c :=
    calc
      abs(f n) < 1 / (n : ℝ)^k₀ : hn₀ n hnnn
           ... ≤ 1 / n^c : d,
    exact goal,
  },

  {-- Reverse direction
    intros h c hc,
    cases h c with n₀ hn₀,
    use n₀,
    intros n hn,
    have goal := hn₀ n (nat.cast_pos.mpr hc) hn,
    rw (n : ℝ).rpow_nat_cast c at goal,
    exact goal,
  },
end

lemma zero_negl : negligible (λn, 0) :=
begin
  intros c hc,
  use 1,
  intros n hn,
  norm_num,
  apply one_div_pos.mpr,
  apply pow_pos,
  have h : 0 < n := by linarith,
  exact nat.cast_pos.mpr h,
end

lemma negl_add_negl_negl {f g : ℕ → ℝ} : negligible f → negligible g → negligible (f + g) :=
begin
  intros hf hg,
  intros c hc,
  have hc1 : (c+1) > 0 := nat.lt.step hc,
  have hf2 := hf (c+1) hc1,
  have hg2 := hg (c+1) hc1,
  cases hf2 with nf hnf,
  cases hg2 with ng hng,
  let n₀ := max (max nf ng) 2,
  use n₀,
  intros n hn,
  let nr := (n:ℝ),
  have n_eq_nr : (n:ℝ) = nr := by refl,

  have tn : max nf ng ≤ n₀ := le_max_left (max nf ng) 2,
  have t2n₀ : 2 ≤ n₀ := le_max_right (max nf ng) 2,
  have t2n : 2 ≤ n := by linarith,
  have t2nr : 2 ≤ nr :=
  begin
    have j := nat.cast_le.mpr t2n,
    rw n_eq_nr at j,
    norm_num at j,
    exact j,
    exact real.nontrivial,
  end,
  have tnr_pos : 0 < nr := by linarith,

  have t2na : (1 / nr) * (1/nr^c) ≤ (1 / (2 : ℝ)) * (1 / nr^c) :=
  begin
    have ht2 : 0 < (1 / nr^c) := by {apply one_div_pos.mpr, exact pow_pos tnr_pos c},
    apply (mul_le_mul_right ht2).mpr,
    apply one_div_le_one_div_of_le,
    exact zero_lt_two,
    exact t2nr,
  end,

  have tnr2 : 1 / nr^(c + 1) ≤ (1 / (2 : ℝ)) * (1 / nr^c) :=
  calc
    1 / nr ^ (c + 1) = (1 / nr)^(c + 1) : by rw one_div_pow
                 ... = (1 / nr) * (1 / nr)^c  : pow_succ (1 / nr) c
                 ... = (1 / nr) * (1 / nr^c) : by rw one_div_pow
                 ... ≤ (1 / (2 : ℝ)) * (1 / nr^c) : t2na,

  have tnf : nf ≤ n :=
  calc
    nf  ≤ n₀ : le_of_max_le_left tn
    ... ≤ n : hn,
  have tfn := hnf n tnf,
  have tf : abs (f n) < (1 / (2 : ℝ)) * (1 / nr^c) := by linarith,

  have tng : ng ≤ n :=
  calc ng  ≤ n₀ : le_of_max_le_right tn
       ... ≤ n : hn,
  have tgn := hng n tng,
  have tg : abs (g n) < (1/(2:ℝ)) * (1/nr^c) := by linarith,

  have goal : abs ((f + g) n) < 1 / nr ^ c :=
  calc
    abs ((f + g) n) = abs (f n + g n) : by rw pi.add_apply f g n
                ... ≤ abs (f n) + abs (g n) : abs_add (f n) (g n)
                ... < (1/(2:ℝ)) * (1/nr^c) + abs (g n): by linarith
                ... < (1/(2:ℝ)) * (1/nr^c) + (1/(2:ℝ)) * (1/nr^c) : by linarith
                ... = 1/nr^c : by ring_nf,
  exact goal,
end

lemma bounded_negl_negl {f g : ℕ → ℝ} (hg : negligible g):
(∀ n, abs (f n) ≤ abs (g n)) → negligible f :=
begin
  intro h,
  intros c hc,
  have hh := hg c hc,
  cases hh with n₀ hn₀,
  use n₀,
  intros n hn,
  have goal : abs (f n) < 1 / (n : ℝ) ^ c :=
  calc
    abs (f n) ≤ abs (g n) : h n
          ... < 1 / (n : ℝ)^c: hn₀ n hn,
  exact goal,
end

lemma nat_mul_negl_negl {f : ℕ → ℝ} (m : ℕ):
negligible f → negligible (λn, m * (f n)) :=
begin
  intros hf,
  induction m with k hk,
  { -- Base case
    norm_num,
    exact zero_negl,
  },
  { -- Inductive step
    norm_num,
    have d : (λn, ((k : ℝ) + 1) * (f n)) = (λn, (k : ℝ) * (f n)) + (λn, f n) :=
      by repeat {ring_nf},
    rw d,
    apply negl_add_negl_negl,
    exact hk,
    exact hf,
  },
end

lemma const_mul_negl_negl {f : ℕ → ℝ} (m : ℝ) :
negligible f → negligible (λn, m * (f n)) :=
begin
  intro hf,
  -- Use Archimedian property to get arch : ℕ with abs m < arch
  have arch := exists_nat_gt (abs m),
  cases arch with k hk,
  apply bounded_negl_negl,

  { -- Demonstrate a negligible function kf
    have kf_negl := nat_mul_negl_negl k hf,
    exact kf_negl,
  },

  { -- Show kf bounds mf from above
    intro n,
    have h : abs m ≤ abs (k : ℝ) :=
    calc
      abs m ≤ (k : ℝ) : le_of_lt hk
        ... = abs (k : ℝ) : (nat.abs_cast k).symm,

    have goal : abs (m * f n) ≤ abs ((k : ℝ) * f n) :=
    calc
      abs (m * f n) = abs m * abs (f n) : by rw abs_mul
                ... ≤ abs (k : ℝ) * abs (f n) : mul_mono_nonneg (abs_nonneg (f n)) h
                ... = abs ((k : ℝ) * f n) : by rw <- abs_mul,

    exact goal,
  },
end

theorem neg_exp_negl : negligible ((λn, (1 : ℝ) / 2^n) : ℕ → ℝ) := by sorry

-- Need to prove lim n^c/2^n = 0 by induction on c using L'Hopital's rule to apply inductive
-- hypothesis
/-
begin
  let m := 2,
  have hm : 0 < 2 := zero_lt_two,
  have c2_negl := c2_mul_neg_exp_is_negl 2 hm,
  have r : (λ (n : ℕ), 16 * (1 / (2 ^ n * 16)): ℕ → ℝ) = ((λn, (1:ℝ)/2^n): ℕ → ℝ) :=
  begin
    funext,
    have h : (1:ℝ) / 2^n / 16 = (1:ℝ) / (2^n * 16) := div_div_eq_div_mul 1 (2^n) 16,
    rw <- h,
    ring_nf,
  end,

  have goal := const_mul_negl_is_negl 16 c2_negl,
  norm_num at goal,
  rw <-r,
  exact goal,
end -/


import Mathlib.Data.Real.Basic

/-
Lean is a programming language which can be used to prove maths theorems.

Here we will prove a theorem from 1st year analysis:

  If `xₙ → s` and `yₙ → t` then `xₙ + yₙ → s + t`

(Don't worry if the code below doesn't mean anything to you, this example is
simply intended to show you what Lean can do.)

-/

def limit (x : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ K, ∀ n, n ≥ K → |x n - l| < ε

theorem sum_limits (x y : ℕ → ℝ) (s t : ℝ) (hx : limit x s) (hy : limit y t) :
  limit (λ n => x n + y n) (s + t) :=
by
  intros ε hε                    -- Given ε ∈ ℝ satisyfing ε > 0
  dsimp                           -- simplify for the reader
  specialize hx (ε/2)            -- use the hypothesis xₙ → s with ε/2
  specialize hy (ε/2)            -- use the hypothesis yₙ → t with ε/2
  have : (ε/2) > 0 := half_pos hε -- need to check that ε/2 > 0
  cases' hx this with A hA         -- obtain A ∈ ℕ using ε/2 > 0 and xₙ → s
  cases' hy this with B hB         -- obtain B ∈ ℕ using ε/2 > 0 and yₙ → t
  clear hx hε hy this             -- clear statements we no longer need
  use max A B                     -- use the max(A,B) as our "K"
  intros n hn                     -- given n ∈ ℕ with n ≥ max(A,B) need to prove..
  -- we can prove intermediate results and use them later
  have AleM : A ≤ max A B := le_max_left A B -- A ≤ max(A,B)
  -- We now have `A ≤ max(A,B)`and `max(A,B) ≤ n` so Lean can deduce `A ≤ n`
  have Alen : A ≤ n := AleM.trans hn
  specialize hA n Alen
  specialize hB n (le_trans (le_max_right A B) hn)
  -- Need to rearrange terms -- use the `ring` tactic
  have rearrange: x n + y n  - (s + t) = x n - s + (y n - t) := add_sub_add_comm _ _ _ _
  rw [rearrange]  -- rewrite this rearranged expression in the goal
  -- Now apply triangle-inequality
  have tri: |x n  - s + (y n - t)| ≤ |x n - s| + |y n - t| := abs_add _ _
  apply lt_of_le_of_lt tri
  rw [← add_halves ε]
  apply add_lt_add hA hB

#print sum_limits


-- let X, Y, Z be sets.
variables {X Y Z : Type}

-- a function f : X → Y is *injective* if f(a) = f(b) → a = b for all a,b in X.
def injective (f : X → Y)  : Prop :=
∀ a b : X, f(a) = f(b) → a = b

-- challenge: the composite of two injective functions is injective
theorem challenge1
  (f : X → Y) (hf : injective f)
  (g : Y → Z) (hg : injective g) :
injective (g ∘ f) :=
begin
  sorry
end


/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import init.logic init.classical init.meta.name init.algebra.classes
/- Make sure instances defined in this file have lower priority than the ones
   defined for concrete structures -/
set_option default_priority 100

set_option old_structure_cmd true

universe u
variables {α : Type u}

set_option auto_param.check_exists false

section preorder

/-!
### Definition of `preorder` and lemmas about types with a `preorder`
-/

/-- A preorder is a reflexive, transitive relation `≤` with `a < b` defined in the obvious way. -/
class preorder (α : Type u) extends has_le α, has_lt α :=
(le_refl : ∀ a : α, a ≤ a)
(le_trans : ∀ a b c : α, a ≤ b → b ≤ c → a ≤ c)
(lt := λ a b, a ≤ b ∧ ¬ b ≤ a)
(lt_iff_le_not_le : ∀ a b : α, a < b ↔ (a ≤ b ∧ ¬ b ≤ a) . order_laws_tac)

variables [preorder α]

/-- The relation `≤` on a preorder is reflexive. -/
@[refl] lemma le_refl : ∀ a : α, a ≤ a :=
preorder.le_refl

/-- The relation `≤` on a preorder is transitive. -/
@[trans] lemma le_trans : ∀ {a b c : α}, a ≤ b → b ≤ c → a ≤ c :=
preorder.le_trans

lemma lt_iff_le_not_le : ∀ {a b : α}, a < b ↔ (a ≤ b ∧ ¬ b ≤ a) :=
preorder.lt_iff_le_not_le

lemma lt_of_le_not_le : ∀ {a b : α}, a ≤ b → ¬ b ≤ a → a < b
| a b hab hba := lt_iff_le_not_le.mpr ⟨hab, hba⟩

lemma le_not_le_of_lt : ∀ {a b : α}, a < b → a ≤ b ∧ ¬ b ≤ a
| a b hab := lt_iff_le_not_le.mp hab

lemma le_of_eq {a b : α} : a = b → a ≤ b :=
λ h, h ▸ le_refl a

@[trans] lemma ge_trans : ∀ {a b c : α}, a ≥ b → b ≥ c → a ≥ c :=
λ a b c h₁ h₂, le_trans h₂ h₁

lemma lt_irrefl : ∀ a : α, ¬ a < a
| a haa := match le_not_le_of_lt haa with
  | ⟨h1, h2⟩ := false.rec _ (h2 h1)
  end

lemma gt_irrefl : ∀ a : α, ¬ a > a :=
lt_irrefl

@[trans] lemma lt_trans : ∀ {a b c : α}, a < b → b < c → a < c
| a b c hab hbc :=
  match le_not_le_of_lt hab, le_not_le_of_lt hbc with
  | ⟨hab, hba⟩, ⟨hbc, hcb⟩ := lt_of_le_not_le (le_trans hab hbc) (λ hca, hcb (le_trans hca hab))
  end

@[trans] lemma gt_trans : ∀ {a b c : α}, a > b → b > c → a > c :=
λ a b c h₁ h₂, lt_trans h₂ h₁

lemma ne_of_lt {a b : α} (h : a < b) : a ≠ b :=
λ he, absurd h (he ▸ lt_irrefl a)

lemma ne_of_gt {a b : α} (h : b < a) : a ≠ b :=
λ he, absurd h (he ▸ lt_irrefl a)

lemma lt_asymm {a b : α} (h : a < b) : ¬ b < a :=
λ h1 : b < a, lt_irrefl a (lt_trans h h1)

lemma le_of_lt : ∀ {a b : α}, a < b → a ≤ b
| a b hab := (le_not_le_of_lt hab).left

@[trans] lemma lt_of_lt_of_le : ∀ {a b c : α}, a < b → b ≤ c → a < c
| a b c hab hbc :=
  let ⟨hab, hba⟩ := le_not_le_of_lt hab in
  lt_of_le_not_le (le_trans hab hbc) $ λ hca, hba (le_trans hbc hca)

@[trans] lemma lt_of_le_of_lt : ∀ {a b c : α}, a ≤ b → b < c → a < c
| a b c hab hbc :=
  let ⟨hbc, hcb⟩ := le_not_le_of_lt hbc in
  lt_of_le_not_le (le_trans hab hbc) $ λ hca, hcb (le_trans hca hab)

@[trans] lemma gt_of_gt_of_ge {a b c : α} (h₁ : a > b) (h₂ : b ≥ c) : a > c :=
lt_of_le_of_lt h₂ h₁

@[trans] lemma gt_of_ge_of_gt {a b c : α} (h₁ : a ≥ b) (h₂ : b > c) : a > c :=
lt_of_lt_of_le h₂ h₁

lemma not_le_of_gt {a b : α} (h : a > b) : ¬ a ≤ b :=
(le_not_le_of_lt h).right

lemma not_lt_of_ge {a b : α} (h : a ≥ b) : ¬ a < b :=
λ hab, not_le_of_gt hab h

lemma le_of_lt_or_eq : ∀ {a b : α}, (a < b ∨ a = b) → a ≤ b
| a b (or.inl hab) := le_of_lt hab
| a b (or.inr hab) := hab ▸ le_refl _

lemma le_of_eq_or_lt {a b : α} (h : a = b ∨ a < b) : a ≤ b :=
or.elim h le_of_eq le_of_lt

instance decidable_lt_of_decidable_le [decidable_rel ((≤) : α → α → Prop)] :
  decidable_rel ((<) : α → α → Prop)
| a b :=
  if hab : a ≤ b then
    if hba : b ≤ a then
      is_false $ λ hab', not_le_of_gt hab' hba
    else
      is_true $ lt_of_le_not_le hab hba
  else
    is_false $ λ hab', hab (le_of_lt hab')

end preorder

section partial_order

/-!
### Definition of `partial_order` and lemmas about types with a partial order
-/

/-- A partial order is a reflexive, transitive, antisymmetric relation `≤`. -/
class partial_order (α : Type u) extends preorder α :=
(le_antisymm : ∀ a b : α, a ≤ b → b ≤ a → a = b)

variables [partial_order α]

lemma le_antisymm : ∀ {a b : α}, a ≤ b → b ≤ a → a = b :=
partial_order.le_antisymm

lemma le_antisymm_iff {a b : α} : a = b ↔ a ≤ b ∧ b ≤ a :=
⟨λe, ⟨le_of_eq e, le_of_eq e.symm⟩, λ⟨h1, h2⟩, le_antisymm h1 h2⟩

lemma lt_of_le_of_ne {a b : α} : a ≤ b → a ≠ b → a < b :=
λ h₁ h₂, lt_of_le_not_le h₁ $ mt (le_antisymm h₁) h₂

instance decidable_eq_of_decidable_le [decidable_rel ((≤) : α → α → Prop)] :
  decidable_eq α
| a b :=
  if hab : a ≤ b then
    if hba : b ≤ a then
      is_true (le_antisymm hab hba)
    else
      is_false (λ heq, hba (heq ▸ le_refl _))
  else
    is_false (λ heq, hab (heq ▸ le_refl _))

namespace decidable

variables [@decidable_rel α (≤)]

lemma lt_or_eq_of_le {a b : α} (hab : a ≤ b) : a < b ∨ a = b :=
if hba : b ≤ a then or.inr (le_antisymm hab hba)
else or.inl (lt_of_le_not_le hab hba)

lemma eq_or_lt_of_le {a b : α} (hab : a ≤ b) : a = b ∨ a < b :=
(lt_or_eq_of_le hab).swap

lemma le_iff_lt_or_eq {a b : α} : a ≤ b ↔ a < b ∨ a = b :=
⟨lt_or_eq_of_le, le_of_lt_or_eq⟩

end decidable

local attribute [instance] classical.prop_decidable

lemma lt_or_eq_of_le {a b : α} : a ≤ b → a < b ∨ a = b := decidable.lt_or_eq_of_le

lemma le_iff_lt_or_eq {a b : α} : a ≤ b ↔ a < b ∨ a = b := decidable.le_iff_lt_or_eq

end partial_order

section linear_order

/-!
### Definition of `linear_order` and lemmas about types with a linear order
-/

/-- Default definition of `max`. -/
def max_default {α : Type u} [has_le α] [decidable_rel ((≤) : α → α → Prop)] (a b : α) :=
if b ≤ a then a else b

/-- Default definition of `min`. -/
def min_default {α : Type u} [has_le α] [decidable_rel ((≤) : α → α → Prop)] (a b : α) :=
if a ≤ b then a else b

/-- A linear order is reflexive, transitive, antisymmetric and total relation `≤`.
We assume that every linear ordered type has decidable `(≤)`, `(<)`, and `(=)`. -/
class linear_order (α : Type u) extends partial_order α :=
(le_total : ∀ a b : α, a ≤ b ∨ b ≤ a)
(decidable_le : decidable_rel (≤))
(decidable_eq : decidable_eq α := @decidable_eq_of_decidable_le _ _ decidable_le)
(decidable_lt : decidable_rel ((<) : α → α → Prop) :=
    @decidable_lt_of_decidable_le _ _ decidable_le)
(max : α → α → α := @max_default α _ _)
(max_def : max = @max_default α _ decidable_le . tactic.interactive.reflexivity)
(min : α → α → α := @min_default α _ _)
(min_def : min = @min_default α _ decidable_le . tactic.interactive.reflexivity)

variables [linear_order α]

local attribute [instance] linear_order.decidable_le

lemma le_total : ∀ a b : α, a ≤ b ∨ b ≤ a :=
linear_order.le_total

lemma le_of_not_ge {a b : α} : ¬ a ≥ b → a ≤ b :=
or.resolve_left (le_total b a)

lemma le_of_not_le {a b : α} : ¬ a ≤ b → b ≤ a :=
or.resolve_left (le_total a b)

lemma not_lt_of_gt {a b : α} (h : a > b) : ¬ a < b :=
lt_asymm h

lemma lt_trichotomy (a b : α) : a < b ∨ a = b ∨ b < a :=
or.elim (le_total a b)
  (λ h : a ≤ b, or.elim (decidable.lt_or_eq_of_le h)
    (λ h : a < b, or.inl h)
    (λ h : a = b, or.inr (or.inl h)))
  (λ h : b ≤ a, or.elim (decidable.lt_or_eq_of_le h)
    (λ h : b < a, or.inr (or.inr h))
    (λ h : b = a, or.inr (or.inl h.symm)))

lemma le_of_not_lt {a b : α} (h : ¬ b < a) : a ≤ b :=
match lt_trichotomy a b with
| or.inl hlt          := le_of_lt hlt
| or.inr (or.inl heq) := heq ▸ le_refl a
| or.inr (or.inr hgt) := absurd hgt h
end

lemma le_of_not_gt {a b : α} : ¬ a > b → a ≤ b := le_of_not_lt

lemma lt_of_not_ge {a b : α} (h : ¬ a ≥ b) : a < b :=
lt_of_le_not_le ((le_total _ _).resolve_right h) h

lemma lt_or_le (a b : α) : a < b ∨ b ≤ a :=
if hba : b ≤ a then or.inr hba else or.inl $ lt_of_not_ge hba

lemma le_or_lt (a b : α) : a ≤ b ∨ b < a :=
(lt_or_le b a).swap

lemma lt_or_ge : ∀ (a b : α), a < b ∨ a ≥ b := lt_or_le
lemma le_or_gt : ∀ (a b : α), a ≤ b ∨ a > b := le_or_lt

lemma lt_or_gt_of_ne {a b : α} (h : a ≠ b) : a < b ∨ a > b :=
match lt_trichotomy a b with
| or.inl hlt          := or.inl hlt
| or.inr (or.inl heq) := absurd heq h
| or.inr (or.inr hgt) := or.inr hgt
end

lemma ne_iff_lt_or_gt {a b : α} : a ≠ b ↔ a < b ∨ a > b :=
⟨lt_or_gt_of_ne, λo, or.elim o ne_of_lt ne_of_gt⟩

lemma lt_iff_not_ge (x y : α) : x < y ↔ ¬ x ≥ y :=
⟨not_le_of_gt, lt_of_not_ge⟩

@[simp] lemma not_lt {a b : α} : ¬ a < b ↔ b ≤ a := ⟨le_of_not_gt, not_lt_of_ge⟩

@[simp] lemma not_le {a b : α} : ¬ a ≤ b ↔ b < a := (lt_iff_not_ge _ _).symm

instance (a b : α) : decidable (a < b) :=
linear_order.decidable_lt a b

instance (a b : α) : decidable (a ≤ b) :=
linear_order.decidable_le a b

instance (a b : α) : decidable (a = b) :=
linear_order.decidable_eq a b

lemma eq_or_lt_of_not_lt {a b : α} (h : ¬ a < b) : a = b ∨ b < a :=
if h₁ : a = b then or.inl h₁
else or.inr (lt_of_not_ge (λ hge, h (lt_of_le_of_ne hge h₁)))

instance : is_total_preorder α (≤) :=
{trans := @le_trans _ _, total := le_total}

/- TODO(Leo): decide whether we should keep this instance or not -/
instance is_strict_weak_order_of_linear_order : is_strict_weak_order α (<) :=
is_strict_weak_order_of_is_total_preorder lt_iff_not_ge

/- TODO(Leo): decide whether we should keep this instance or not -/
instance is_strict_total_order_of_linear_order : is_strict_total_order α (<) :=
{ trichotomous := lt_trichotomy }

/-- Perform a case-split on the ordering of `x` and `y` in a decidable linear order. -/
def lt_by_cases (x y : α) {P : Sort*}
  (h₁ : x < y → P) (h₂ : x = y → P) (h₃ : y < x → P) : P :=
if h : x < y then h₁ h else
if h' : y < x then h₃ h' else
h₂ (le_antisymm (le_of_not_gt h') (le_of_not_gt h))

lemma le_imp_le_of_lt_imp_lt {β} [preorder α] [linear_order β]
  {a b : α} {c d : β} (H : d < c → b < a) (h : a ≤ b) : c ≤ d :=
le_of_not_lt $ λ h', not_le_of_gt (H h') h

end linear_order


/-
| All possible ways to choose @k@ elements from a list, /with repetitions/.
\"Symmetric power\" for lists. See also "Math.Combinat.Compositions".
From Math.Combinat.Sets

>>> combine 3 ['a','b','c']
["aaa","aab","aac","abb","abc","acc","bbb","bbc","bcc","ccc"]

>>> combine 4 ['a', 'b']
["aaaa","aaab","aabb","abbb","bbbb"]

combine :: Int -> [a] -> [[a]]
combine 0 _  = [[]]
combine k [] = []
combine k xxs@(x:xs) = map (x:) (combine (k-1) xxs) ++ combine k xs

| All possible ways to choose @k@ elements from a list, without
repetitions. \"Antisymmetric power\" for lists. Synonym for 'kSublists'.
*Perms> choose 2 [1,2,3,4]
[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]

choose :: Int -> [a] -> [[a]]
choose 0 _  = [[]]
choose k [] = []
choose k (x:xs) = map (x:) (choose (k-1) xs) ++ choose k xs
-/

open list

#check @map -- (α → β) → list α → list β

constant  α : Type
constant combine : ℕ → list α → list (list α)
constant choose  : ℕ → list α → list (list α)

-- noncomputable instance : has_add α := ⟨list.append⟩

axiom combine.zero (xs : list α) : combine 0 xs = [[]]
axiom combine.empty (k : ℕ) : combine k [] = []
axiom combine.step (k : ℕ) (x : α) (xs : list α) :
  combine (k+1) (x::xs) = map (cons x) (combine k (x::xs)) ++ combine (k+1) xs

example : (λ (x : ℕ), x + 1) 5 = 6 := rfl
example : (λ (xs : list ℕ), 100 :: xs) [] = [100] := rfl

attribute [simp] combine.zero combine.empty

namespace helper
variable x : α
#reduce map (λ (ys : list α), x :: ys) [nil] ++ nil -- [[x]]
end helper

example : combine 0 [] = [[]] := combine.zero nil
example (x : α) : combine 1 [x] = [[x]] := by {
  rw [ combine.step, combine.zero, combine.empty ],
  refl,
}

set_option trace.simplify.rewrite true

namespace vars
variables (x y z : α) (xs ys zs : list α)

-- combine (k+1) (x:xs) = map (x:) (combine k (x:xs)) ++ combine (k+1) xs
-- combine 1 [x] = map (x:) (combine 0 [x]) ++ combine 1 [] = map (x:) [[]] ++ [] = [[x]]
example : combine 1 [x]       = [[x]]           := by simp [combine.step]
example : combine 1 [x, y]    = [[x], [y]]      := by simp [combine.step]
example : combine 1 [x, y, z] = [[x], [y], [z]] := by simp [combine.step]
example : combine 1 [x, y, z] = [[x], [y], [z]] := by iterate 3 { rw combine.step, simp }

example : combine 2 [x, y] = [[x, x], [x, y], [y, y]]                     := by simp [combine.step]
example : combine 2 [x, y, z] = [[x,x],[x,y],[x,z],[y,y],[y,z],[z,z]]     := by simp [combine.step]
example : combine 3 [x, y] = [[x, x, x], [x, x, y], [x, y, y], [y, y, y]] := by simp [combine.step]
end vars
-- [combine, choose] :: Int -> [a] -> [[a]]
-- [combine, choose] 0 _  = [[]]
-- [combine, choose] k [] = []

-- combine (k+1) (x:xs) = map (x:) (combine k (x:xs)) ++ combine (k+1) xs
-- choose  (k+1) (x:xs) = map (x:) (choose  k    xs ) ++ choose  (k+1) xs

-- axiom combine.step (k : ℕ) (x : α) (xs : list α) :
--   combine (k+1) (x::xs) = map (λ ys, x::ys) (combine k (x::xs)) ++ combine (k+1) xs

-- this two axioms may have contradiction ???
-- axiom choose.zero (xs : list α) : choose 0 xs = [[]]
-- axiom choose.empty (k : ℕ) : choose k [] = []

axiom choose.zero (xs : list α) : choose 0 xs = [[]]
axiom choose.empty (k : ℕ) : k ≥ 1 → choose k [] = []
axiom choose.step (k : ℕ) (x : α) (xs : list α) :
  choose (k+1) (x::xs) = map (cons x) (choose k xs) ++ choose (k+1) xs

attribute [simp] choose.zero choose.empty

example : choose 0 [] = [[]] := choose.zero nil
lemma one_nil   : choose 1 [] = [] := by rw [choose.empty]; repeat { apply nat.less_than_or_equal.refl <|> apply nat.less_than_or_equal.step }
lemma two_nil   : choose 2 [] = [] := by rw [choose.empty]; repeat { apply nat.less_than_or_equal.refl <|> apply nat.less_than_or_equal.step }
lemma three_nil : choose 3 [] = [] := by rw [choose.empty]; repeat { apply nat.less_than_or_equal.refl <|> apply nat.less_than_or_equal.step }

example (x : α) : choose 0 [x] = [[]] := choose.zero [x]
example (x : α) : choose 1 [x] = [[x]] := by {
  rw [ choose.step, choose.zero, choose.empty ],
  refl,
  exact nat.le_refl 1, -- <=> @nat.less_than_or_equal.refl 1
}
example (x : α) : choose 2 [x] = [] := by {
  simp [choose.step],
  iterate 2 { rw choose.empty },
  simp,
  iterate 3 { apply nat.less_than_or_equal.refl <|> apply nat.less_than_or_equal.step },
}

#check nat.less_than_or_equal.step

namespace vars2
variables (x y z w : α) (xs ys zs ws : list α)
-- choose (k+1) (x:xs) = map (x:) (choose k xs) ++ choose (k+1) xs
-- choose 1 [x] = map (x:) (choose 0 []) ++ choose 1 [] = map (x:) [] ++ [] = []

-- choose k (x:xs) = map (x:) (choose (k-1) xs) ++ choose k xs
-- choose 1 [x] = map (x:) (choose 0 []) ++ choose 1 [] =
example : choose 1 [x] = [[x]] := by {
  simp [choose.step]; rw [choose.empty],
  apply nat.less_than_or_equal.refl <|> apply nat.less_than_or_equal.step,
}

example : choose 1 [x]       = [[x]]           := by simp [choose.step, one_nil]
example : choose 1 [x, y]    = [[x], [y]]      := by simp [choose.step, one_nil]
example : choose 1 [x, y, z] = [[x], [y], [z]] := by simp [choose.step, one_nil]
-- example : choose 1 [x, y, z] = [[x], [y]] := by iterate 3 { rw choose.step, simp }

-- induction forcing method:
-- 1. find symmetries, rules, shortest rules (requires minimum meta-rules that can generate rules), optimal path (list) of rules, "optimal path (method)" of calculating optimal paths(lists)...
-- 2. create set of hypothesis
-- 3. prove it or find a structure that simplifies task of proof in more general cases

-- group of steps for forcing cool_lemma1
example : choose 0 [] = [[]] := by simp [choose.step]
example : choose 1 [x] = [[x]] := by simp [choose.step, one_nil]
example : choose 2 [x, y] = [[x, y]] := by simp [choose.step, one_nil, two_nil]
example : choose 3 [x, y, z] = [[x, y, z]] := by simp [choose.step, one_nil, two_nil, three_nil]
lemma cool_lemma1 (n : ℕ): choose n xs = [xs] := sorry

example : choose 3 [x, y] = [] := by {
  repeat {
    simp [choose.step, one_nil],
    rw [choose.empty]; repeat { apply nat.less_than_or_equal.refl <|> apply nat.less_than_or_equal.step },
  },
}

example : choose 2 [x, y, z, w] = [[x,y],[x,z],[x,w],[y,z],[y,w],[z,w]] := by {
  repeat {
    simp [choose.step, one_nil],
    rw [choose.empty]; repeat { apply nat.less_than_or_equal.refl <|> apply nat.less_than_or_equal.step },
  },
}

example : choose 3 [x, y, z, w] = [[x,y,z],[x,y,w],[x,z,w],[y,z,w]] := by {
  repeat {
    simp [choose.step, one_nil],
    rw [choose.empty]; repeat { apply nat.less_than_or_equal.refl <|> apply nat.less_than_or_equal.step },
  },
}

end vars2


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

/-!
# Logic

A Lean companion to the "Logic" part of the intro module.

We develop the basic theory of the five symbols
→, ¬, ∧, ↔, ∨

(in that order)

# Background

It is hard to ask you difficult questions
about the basic theory of these logical operators,
because every question can be proved by "check all the cases".

However, there is this cool theorem, that says that if
a theorem in the basic theory of logical propositions can be proved
by "check all the cases", then it can be proved in the Lean theorem
prover using only the eight constructive tactics `intro`, `apply`,
`assumption`, `exfalso`, `split`, `cases`, `have`, `left` and `right`,
as well as one extra rule called the Law of the Excluded Middle,
which in Lean is the tactic `by_cases`. Note that the tactic `finish`
is a general "check all the cases" tactic, and it uses `by_cases`.

## Reference

* The first half of section 1 of the M40001/40009 course notes.

-/

namespace xena

variables (P Q R : Prop)

/-

### implies

Some basic practice of `intro`, `apply` and `exact`
-/

/-- Every proposition implies itself. -/
def id : P → P :=
begin
  -- assume P is true. Call this hypotbesis hP.
  intro hP,
  -- then we know that P is true by hypothesis hP.
  exact hP,
end

-- implication isn't associative!
-- Try it when P, Q, R are all false.
example : (false → (false → false)) ↔ true := by simp
example : ((false → false) → false) ↔ false := by simp

-- in Lean, `P → Q → R` is _defined_ to be `P → (Q → R)`
-- Here's a proof of what I just said.
example : (P → Q → R) ↔ (P → (Q → R)) :=
begin
  -- ⊢ P → Q → R ↔ P → Q → R
  refl
end

example : P → Q → P :=
begin
  -- remember that by definition the goal is P → (Q → P),
  -- so it's P implies something, so let's assume
  -- that P is true and call this hypothesis hP.
  intro hP,
  -- Now we have to prove that Q implies P, so let's
  -- assume that Q is true, and let's call this hypothesis hQ
  intro hQ,
  -- We now have to prove that P is true.
  -- But this is exactly our hypothesis hP.
  exact hP,
end

/-- If we know `P`, and we also know `P → Q`, we can deduce `Q`. -/
lemma modus_ponens : P → (P → Q) → Q :=
begin
  -- remember this means "P implies that ((P implies Q) implies Q)"
  -- so let's assume P is true
  intro hP,
  -- and let's assume hypothesis hPQ, that P implies Q
  intro hPQ,
  -- now `hPQ` says `P → Q` and we're trying to prove `Q`!
  -- So by applying the hypothesis `hPQ`, we can reduce
  -- this puzzle to proving `P`.
  apply hPQ,
  -- Now we have to prove `P`. But this is just an assumption
  exact hP, -- or `assumption`
end

lemma trans : (P → Q) → (Q → R) → (P → R) :=
begin
  intros hPQ hQR hP,
  apply hQR,
  apply hPQ,
  exact hP
end

-- This one is a "relative modus ponens" -- in the
-- presence of P, if Q -> R and Q then R.
example : (P → Q → R) → (P → Q) → (P → R) :=
begin
  -- Let `hPQR` be the hypothesis that `P → Q → R`.
  intro hPQR,
  -- We now need to prove that `(P → Q)` implies something.
  -- So let `hPQ` be hypothesis that `P → Q`
  intro hPQ,
  -- We now need to prove that `P` implies something, so
  -- let `hP` be the hypothesis that `P` is true.
  intro hP,
  -- We now have to prove `R`.
  -- We know the hypothesis `hPQR : P → (Q → R)`.
  apply hPQR,
    -- we now have two goals, so I indent for a second
    -- The first goal is just to prove P, and this is an assumption
    exact hP,
  -- The number of goals is just one again.
  -- the remaining goal is to prove `Q`.
  -- But recall that `hPQ` is the hypothesis that `P` implies `Q`
  -- so by applying it,
  apply hPQ,
  -- we change our goal to proving `P`. And this is a hypothesis
  exact hP,
end

/-

### not

`not P`, with notation `¬ P`, is defined to mean `P → false` in Lean,
i.e., the proposition that P implies false. You can easily check with
a truth table that P → false and ¬ P are equivalent, but we need to
remember the fact that in Lean ¬ P was *defined* to mean `P → false`
and not any other way

We develop a basic interface.
-/

theorem not_not_intro : P → ¬ (¬ P) :=
begin
  -- we have to prove that P implies (not (not P)),
  -- so let's assume P is true, and let's call this assumption hP
  intro hP,
  -- now we have to prove `not (not P)`, a.k.a. `¬ (¬ P)`, and
  -- by definition this means we have to prove `(¬ P) → false`
  -- So let's let hnP be the hypothesis that `¬ P` is true.
  intro hnP,
  -- and now we have to prove `false`!
  -- Sometimes this can be difficult, but it's OK if you have
  -- *contradictory hypotheses*, because with contradictory
  -- assumptions you can prove false conclusions, and once you've
  -- proved one false thing you've proved all false things because
  -- you've made mathematics collapse.

  -- How are we going to use hypothesis `hnP : ¬ P`?

  -- Well, what does it _mean_? It means `P → false`,
  -- and our _goal_ is false, so why don't we apply
  -- hypothesis hnP, which will reduce our problem
  -- to proving `P`.

  apply hnP,

  -- now our goal is `P`, and this is an assumption!
  exact hP
end

theorem not_not_intro'' : P → ¬ (¬ P) :=
begin
  apply modus_ponens,
end

-- lambda calculus proof
theorem not_not_intro' : P → ¬ (¬ P) :=
λ hP hnP, hnP hP

theorem contra : (P → Q) → (¬ Q → ¬ P) :=
begin
  intro hPQ,
  intro hnQ,
  intro hP, -- we take the assumptions in a some order
  apply hnQ,
  apply hPQ,
  exact hP, -- and then we put them back in a different order
end

/-!

### and

The hypothesis `hPaQ : P ∧ Q` in Lean, is equivalent to
hypotheses `hP : P` and `hQ : Q`.

If you have `hPaQ` as a hypothesis, and you want to get to
`hP` and `hQ`, you can use the `cases` tactic.

If you have `⊢ P ∧ Q` as a goal, and want to turn the goal
into two goals `⊢ P` and `⊢ Q`, then use the `split` tactic.

Note that after `split` it's good etiquette to use braces
e.g.

example (hP : P) (hQ : Q) : P ∧ Q :=
begin
  split,
  { exact hP },
  { exact hQ }
end

but for this sort of stuff I think principled indentation
is OK

```
example (hP : P) (hQ : Q) : P ∧ Q :=
begin
  split,
    exact hP,
  exact hQ
end
```

-/

theorem and.elim_left : P ∧ Q → P :=
begin
  intro hPaQ,
  cases hPaQ with hP hQ,
  exact hP,
end

theorem and.elim_right : P ∧ Q → Q := λ hPaQ, hPaQ.2

theorem and.intro : P → Q → P ∧ Q :=
begin
  intro hP,
  intro hQ,
  split; assumption
end

-- the "eliminator for and" -- if you know `P ∧ Q` you
-- can deduce that something implies something else
-- with no ands
theorem and.elim : P ∧ Q → (P → Q → R) → R :=
begin
  intro hPaQ,
  cases hPaQ with hP hQ,
  intro hPQR,
  apply hPQR; assumption
end

theorem and.rec : (P → Q → R) → P ∧ Q → R :=
begin
  intro hPQR,
  rintro ⟨hP, hQ⟩,
  apply hPQR; assumption
end

-- joke proof
theorem and.elim' : P ∧ Q → (P → Q → R) → R :=
begin
  intro hPaQ,
  intro hPQR,
  apply and.rec, -- anarchy
    exact hPQR,
  exact hPaQ,
end

theorem and.symm : P ∧ Q → Q ∧ P :=
begin
  -- goal is `⊢ P ∧ Q → Q ∧ P`
    intro h, -- `h : P ∧ Q`
    cases h with hP hQ, -- `hP : P` and `hQ : Q`
    split, -- two goals now, `⊢ Q` and `⊢ P`
    { exact hQ },
    { exact hP },
end

-- term mode proof
theorem and.symm' : P ∧ Q → Q ∧ P :=
λ ⟨P, Q⟩, ⟨Q, P⟩

theorem and.trans : (P ∧ Q) → (Q ∧ R) → (P ∧ R) :=
begin
  rintro ⟨hP, hQ⟩,
  rintro ⟨hQ2, hR⟩,
  split; assumption
end

/-
Extra credit

Recall that the convention for the implies sign →
is that it is _right associative_, by which
I mean that `P → Q → R` means `P → (Q → R)` by definition.
This does actually simplify! If `P` implies `Q → R`
then this means that `P` and `Q` together, imply `R`,
so `P → Q → R` is logically equivalent to `(P ∧ Q) → R`.

We proved that `P → Q → R` implied `(P ∧ Q) → R`; this was `and.rec`.
Let's go the other way.
-/

example : ((P ∧ Q) → R) → (P → Q → R) :=
begin
  intro hPaQR,
  intro hP,
  intro hQ,
  apply hPaQR,
  split; assumption
end

/-!

### iff

The basic theory of `iff`.

In Lean, `P ↔ Q` is *defined to mean* `(P → Q) ∧ (Q → P)`.

It is _not_ defined by a truth table.

This changes the way we think about things.
-/

/-- `P ↔ P` is true for all propositions `P`. -/
def iff.refl : P ↔ P :=
begin
  -- By Lean's definition I need to prove (P → P) ∧ (P → P)
  split,
  { -- need to prove P → P
    apply id },
  { -- need to prove P → P
    apply id }
end

-- If you get stuck, there is always the "truth table" tactic `tauto!`
def iff.refl' : P ↔ P :=
begin
  tauto!, -- the "truth table" tactic.
end

-- refl tactic also works
def iff.refl'' : P ↔ P :=
begin
  refl
end

def iff.symm : (P ↔ Q) → (Q ↔ P) :=
begin
  -- assume P ↔ Q is true. Call this hypothesis hPiQ.
  intro hPiQ,
  -- by definition, hPiQ means that P → Q is true and Q → P is true.
  -- Let's call these assumptions hPQ and hQP.
  cases hPiQ with hPQ hQP,
  --  We want to prove Q ↔ P
  -- but by definition this just means (Q → P) ∧ (P → Q)
  -- We split this goal, and then both goals are assumptions
  -- (one is hPQ, one is hQP)
  split; assumption,
end

def iff.symm' : (P ↔ Q) → (Q ↔ P) :=
begin
  intro h,
  -- introduction of the rewrite tactic
  rw h,
  -- refl automatically applied
end

-- Instead of begin/end blocks, which many mathematicians prefer,
-- one can write proofs in the lambda calculus, with some
-- computer scientists like better

def iff.symm'' : (P ↔ Q) → (Q ↔ P) :=
λ ⟨hPQ, hQP⟩, ⟨hQP, hPQ⟩

-- That's a full proof.

def iff.comm : (P ↔ Q) ↔ (Q ↔ P) :=
begin
  split;
  apply iff.symm,
end

-- without rw or cc this is ugly
def iff.trans :  (P ↔ Q) → (Q ↔ R) → (P ↔ R) :=
begin
  rintro ⟨hPQ, hQP⟩,
  rintro ⟨hQR, hRQ⟩,
  split, -- split; cc finishes it
    intro hP,
    apply hQR,
    apply hPQ,
    exact hP,
  intro hR,
  apply hQP,
  apply hRQ,
  exact hR,
end

def iff.trans' :  (P ↔ Q) → (Q ↔ R) → (P ↔ R) :=
begin
  intro hPiQ,
  intro hQiR,
  rw hPiQ,
  assumption
end

def iff.boss : ¬ (P ↔ ¬ P) :=
begin
  rintro ⟨h1, h2⟩,
  have hnp : ¬ P,
    intro hP,
    apply h1; assumption,
  apply hnp,
  apply h2,
  exact hnp,

end

-- Now we have iff we can go back to and.

/-! ### ↔ and ∧ -/

theorem and_comm : P ∧ Q ↔ Q ∧ P :=
begin
  split,
    apply and.symm,
  apply and.symm
end

theorem and_comm' : P ∧ Q ↔ Q ∧ P :=
⟨and.symm _ _, and.symm _ _⟩

-- ∧ is "right associative" in Lean, which means
-- that `P ∧ Q ∧ R` is _defined to mean_ `P ∧ (Q ∧ R)`.
-- Associativity can hence be written like this:
theorem and_assoc : ((P ∧ Q) ∧ R) ↔ (P ∧ Q ∧ R) :=
begin
  split,
  { rintros ⟨⟨hP, hQ⟩, hR⟩,
    exact ⟨hP, hQ, hR⟩ },
  { rintros ⟨hP, hQ, hR⟩,
    exact ⟨⟨hP, hQ⟩, hR⟩ },
end

/-!

## Or

`P ∨ Q` is true when at least one of `P` and `Q` are true.
Here is how to work with `∨` in Lean.

If you have a hypothesis `hPoQ : P ∨ Q` then you
can break into the two cases `hP : P` and `hQ : Q` using
`cases hPoQ with hP hQ`

If you have a _goal_ of the form `⊢ P ∨ Q` then you
need to decide whether you're going to prove `P` or `Q`.
If you want to prove `P` then use the `left` tactic,
and if you want to prove `Q` then use the `right` tactic.

-/

-- recall that P, Q, R are Propositions. We'll need S for this one.
variable (S : Prop)

-- use the `left` tactic to reduce from `⊢ P ∨ Q` to `⊢ P`
theorem or.intro_left : P → P ∨ Q :=
begin
  intro hP,
  -- ⊢ P ∨ Q
  left,
  -- ⊢ P
  exact hP
end

-- use the `right` tactic to reduce from `⊢ P ∨ Q`
theorem or.intro_right : Q → P ∨ Q :=
begin
  sorry,
end

theorem or.elim : P ∨ Q → (P → R) → (Q → R) → R :=
begin
  intro h,
  intros hpq hqr,
  cases h,
  sorry, sorry
end

theorem or.symm : P ∨ Q → Q ∨ P :=
begin
  intro hPoQ,
  cases hPoQ with hP hQ,
    right,
    assumption,
  left,
  assumption
end

theorem or.comm : P ∨ Q ↔ Q ∨ P :=
begin
  split,
    apply or.symm,
  apply or.symm
end

-- good luck!

theorem or.assoc : (P ∨ Q) ∨ R ↔ P ∨ Q ∨ R :=
begin
  split,
    rintro (⟨hP | hQ⟩ | hR),
    { left, assumption},
    { right, left, assumption},
    { right, right, assumption},
    -- don't get lost. Hover over `rintro` to see the docs.
  rintro (hP | hQ | hR),
    { left, left, assumption},
    { left, right, assumption},
    { right, assumption},
end

theorem or.cases_on : P ∨ Q → (P → R) → (Q → R) → R :=
begin
  rintro (hP | hQ),
  cc,cc,
end

theorem or.imp : (P → R) → (Q → S) → P ∨ Q → R ∨ S :=
begin
  rintros hPR hQS (hP | hQ),
    left, cc,
  right, cc
end

theorem or.imp_left : (P → Q) → P ∨ R → Q ∨ R :=
begin
  rintros hPQ (hP | hR),
    left, cc,
    right, assumption
end

theorem or.imp_right : (P → Q) → R ∨ P → R ∨ Q :=
begin
  rintros hPQ (hP | hR),
    left, cc,
    right, cc,
end

theorem or.left_comm : P ∨ Q ∨ R ↔ Q ∨ P ∨ R :=
begin
  rw or.comm,
  rw or.assoc,
  rw or.comm R,
  -- (refl)
end

theorem or.rec : (P → R) → (Q → R) → P ∨ Q → R :=
begin
  rintros _ _ (_ | _);
  cc
end

theorem or.resolve_left : P ∨ Q → ¬P → Q :=
begin
  rintros (hP | hQ) hnP,
    contradiction,
  assumption
end

theorem or_congr : (P ↔ R) → (Q ↔ S) → (P ∨ Q ↔ R ∨ S) :=
begin
  rintros hPR hQS,
  rw hPR,
  rw hQS,
end

theorem or_false : P ∨ false ↔ P :=
begin
  simp,
end

/-!

# Classical logic

-/

-- useful lemma about false
theorem false.elim' : false → P :=
begin
  -- Let's assume that a false proposition is true. Let's
  -- call this assumption h.
  intro h,
  -- We now have to prove P.
  -- The `exfalso` tactic changes any goal to `false`.
  exfalso,
  -- Now our goal is an assumption! It's exactly `h`.
  exact h,
end

-- This one cannot be proved using the tactics we know
-- which are constructive. This one needs the assumption
-- that every LEM blah
theorem double_negation_elimination : ¬ (¬ P) → P :=
begin
  -- `tauto!` works
  classical,
  by_cases hP : P,
    intro h37,
    assumption,
  intro hnnP,
  exfalso,
  apply hnnP,
  exact hP,
end

end xena


/-
Copyright (c) 2020 Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Zhouhang Zhou, Yury Kudryashov
-/
import analysis.normed.group.basic
import algebra.indicator_function

/-!
# Indicator function and norm

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

This file contains a few simple lemmas about `set.indicator` and `norm`.

## Tags
indicator, norm
-/

variables {α E : Type*} [seminormed_add_comm_group E] {s t : set α} (f : α → E) (a : α)

open set

lemma norm_indicator_eq_indicator_norm :
  ‖indicator s f a‖ = indicator s (λa, ‖f a‖) a :=
flip congr_fun a (indicator_comp_of_zero norm_zero).symm

lemma nnnorm_indicator_eq_indicator_nnnorm :
  ‖indicator s f a‖₊ = indicator s (λa, ‖f a‖₊) a :=
flip congr_fun a (indicator_comp_of_zero nnnorm_zero).symm

lemma norm_indicator_le_of_subset (h : s ⊆ t) (f : α → E) (a : α) :
  ‖indicator s f a‖ ≤ ‖indicator t f a‖ :=
begin
  simp only [norm_indicator_eq_indicator_norm],
  exact indicator_le_indicator_of_subset ‹_› (λ _, norm_nonneg _) _
end

lemma indicator_norm_le_norm_self : indicator s (λa, ‖f a‖) a ≤ ‖f a‖ :=
indicator_le_self' (λ _ _, norm_nonneg _) a

lemma norm_indicator_le_norm_self : ‖indicator s f a‖ ≤ ‖f a‖ :=
by { rw norm_indicator_eq_indicator_norm, apply indicator_norm_le_norm_self }


-- ---------------------------------------------------------------------
-- Ejercicio. Importar la librería de tácticas.
-- ---------------------------------------------------------------------

import tactic

-- Nota. La mónada opcional está definida por
--    inductive option (α : Type u)
--    | none         : option
--    | some (x : α) : option

-- Nota. Para cada tipo `X` la función `some : X → option X` es
-- inyectiva.
--    some_injective (α : Type*) : function.injective (@some α)
-- que es un corolario de
--    some_inj {a b : α} : some a = some b ↔ a = b

-- Nota. Los elementos opcionales son distintos de none:
--    some_ne_none (x : α) : some x ≠ none

-- Nota: Para definir una función `option X → Y` hay que definir un
-- elemento de `Y` para cada `some x` con `x : X` y también un elemento
-- de `Y`para `none` goes. Se puede definir con el recursor de `option`,
-- llamado `option.rec`,
--    Π {α : Type u} {C : option α → Sort l},
--      C none → (Π (val : α), C (some val)) → Π (n : option α), C n

-- ---------------------------------------------------------------------
-- Ejercicio. Declarar X e Y como variables sobre tipos.
-- ---------------------------------------------------------------------

variables {X Y : Type}

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    g : Y → (X → Y) → (option X → Y)
-- tal que (g y f) es la función que asigna
-- + y a none y
-- + (f x) a (some x).
-- ---------------------------------------------------------------------

def g : Y → (X → Y) → option X → Y :=
λ y f, λ t, option.rec y f t

-- ---------------------------------------------------------------------
-- Ejercicio. Declarar
-- + f como una variable para funciones de X en Y.
-- + x como una variable sobre X.
-- + y como una variable sobre Y.
-- ---------------------------------------------------------------------

variable (f : X → Y)
variable (x : X)
variable (y : Y)

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    (g y f) none
-- ---------------------------------------------------------------------

example :
  (g y f) none = y :=
begin
  refl
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    (g y f) (some x) = f x
-- ---------------------------------------------------------------------

example :
  (g y f) (some x) = f x :=
begin
  refl
end

-- ---------------------------------------------------------------------
-- Ejercicio. Definir
--    option_func : (X → Y) → (option X → option Y) :=
-- tal que (option_func f) es el functor correspondiente a f.
-- ---------------------------------------------------------------------

def option_func : (X → Y) → (option X → option Y) :=
λ f, λ t, option.rec none (some ∘ f) t

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que que option_func verifica el axioma de los
-- functores para la identidad.
-- ---------------------------------------------------------------------

lemma option_id
  (ox : option X)
  : option_func id ox = ox :=
begin
  cases ox with x,
  { refl },
  { refl }
end

-- ---------------------------------------------------------------------
-- Ejercicio. Declarar Z como una variable de tipos.
-- ---------------------------------------------------------------------

variable (Z : Type)

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que que option_func verifica el axioma de los
-- functores para la composición
-- ---------------------------------------------------------------------

lemma option_comp
  (f : X → Y)
  (g : Y → Z)
  (ox : option X) :
  option_func (g ∘ f) ox = (option_func g) (option_func f ox) :=
begin
  cases ox with x,
  { refl },
  { refl },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    eta : X → option X
-- como la función some.
-- ---------------------------------------------------------------------

def eta : X → option X :=
some

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que la función eta es una transformación
-- natural.
-- ---------------------------------------------------------------------

lemma eta_nat
  (f : X → Y)
  (x : X)
  : option_func f (eta x) = eta (f x) :=
begin
  refl,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    mu : option (option X) → option X
-- que transforma `none` en `none` y `some ox` en `ox`.
-- ---------------------------------------------------------------------

def mu : option (option X) → option X :=
λ t, option.rec none id t

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que la función mu es una transformación
-- natural.
-- ---------------------------------------------------------------------

lemma mu_nat (
  f : X → Y)
  (oox : option (option X))
  : option_func f (mu oox) = mu (option_func (option_func f) oox) :=
begin
  cases oox,
  { refl },
  { refl }
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    mu ((option_func mu) ooox) = mu (mu ooox)
-- ---------------------------------------------------------------------

lemma coherence1
  (ooox : option (option (option X)))
  : mu ((option_func mu) ooox) = mu (mu ooox) :=
begin
  cases ooox,
  { refl },
  { refl },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    mu (eta ox) = ox
-- ---------------------------------------------------------------------

lemma coherence2a
  (ox : option X)
  : mu (eta ox) = ox :=
begin
  refl
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    mu (option_func eta ox) = ox
-- ---------------------------------------------------------------------

lemma coherence2b
  (ox : option X)
  : mu (option_func eta ox) = ox :=
begin
  cases ox,
  { refl },
  { refl },
end


-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar las siguientes acciones:
-- 1. Importar la librería de tácticas.
-- 2. Declarar α como una variables sobre preórdenes.
-- 3. Declarar a, b y c como variables sobre elementos de α.
-- ----------------------------------------------------------------------

import tactic                       -- 1
variables {α : Type*} [preorder α]  -- 2
variables a b c : α                 -- 3

-- ---------------------------------------------------------------------
-- Ejercicio 1. Demostrar que que la relación menor es irreflexiva.
-- ----------------------------------------------------------------------

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

example : ¬ a < a :=
begin
  rw lt_iff_le_not_le,
  rintros ⟨h1, h2⟩,
  apply h2 h1,
end

-- Prueba
-- ======

/-
α : Type u_1,
_inst_1 : preorder α,
a : α
⊢ ¬a < a
  >> rw lt_iff_le_not_le,
⊢ ¬(a ≤ a ∧ ¬a ≤ a)
  >> rintros ⟨h1, h2⟩,
h1 : a ≤ a,
h2 : ¬a ≤ a
⊢ false
  >> apply h2 h1,
no goals
-/

-- Comentarios:
-- + La táctica (rintros ⟨h1, h2⟩), si el objetivo es de la forma ¬(P ∧ Q),
--   añade  las hipótesis (h1 : P) y (h2 : Q) y cambia el objetivo a false.
-- + Se ha usado el lema
--      lt_iff_le_not_le : a < b ↔ a ≤ b ∧ ¬b ≤ a

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

example : ¬ a < a :=
irrefl a

-- ---------------------------------------------------------------------
-- Ejercicio 3. Demostrar que que la relación menor es transitiva.
-- ----------------------------------------------------------------------

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

example : a < b → b < c → a < c :=
begin
  simp only [lt_iff_le_not_le],
  rintros ⟨h1, h2⟩ ⟨h3, h4⟩,
  split,
    apply le_trans h1 h3,
  contrapose ! h4,
  apply le_trans h4 h1,
end

-- Prueba
-- ======

/-
α : Type u_1,
_inst_1 : preorder α,
a b c : α
⊢ a < b → b < c → a < c
  >> simp only [lt_iff_le_not_le],
⊢ a ≤ b ∧ ¬b ≤ a → b ≤ c ∧ ¬c ≤ b → a ≤ c ∧ ¬c ≤ a
  >> rintros ⟨h1, h2⟩ ⟨h3, h4⟩,
h1 : a ≤ b,
h2 : ¬b ≤ a,
h3 : b ≤ c,
h4 : ¬c ≤ b
⊢ a ≤ c ∧ ¬c ≤ a
  >> split,
| ⊢ a ≤ c
  >>   apply le_trans h1 h3,
⊢ ¬c ≤ a
  >> contrapose ! h4,
h4 : c ≤ a
⊢ c ≤ b
  >> apply le_trans h4 h1,
no goals
-/

-- Comentario: Se ha aplicado los lemas
-- + lt_iff_le_not_le : a < b ↔ a ≤ b ∧ ¬b ≤ a
-- + le_trans : a ≤ b → b ≤ c → a ≤ c

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

example : a < b → b < c → a < c :=
lt_trans


import tactic

-- example (n : nat) : n < n + 1 :=
-- begin
--   suggest,
--   sorry
-- end

example (n : nat) : n < n + 1 :=
by exact lt_add_one n

-- Al colocar el cursor sobre suggest muestra las siguientes sugerencias
--    Try this: exact lt_add_one n
--    Try this: exact nat.lt.base n
--    Try this: exact nat.lt_succ_self n
--    Try this: refine not_le.mp _
--    Try this: refine gt_iff_lt.mp _
--    Try this: refine nat.lt.step _
--    Try this: refine set.mem_Ioi.mp _
--    Try this: refine set.mem_Iio.mp _
--    Try this: refine lt_of_not_ge _
--    Try this: refine bit1_lt_bit1.mp _
--    Try this: refine bit0_lt_bit0.mp _
--    Try this: refine lt_of_not_ge' _
--    Try this: refine (lt_iff_not_ge n (n + 1)).mpr _
--    Try this: refine list.mem_range.mp _
--    Try this: refine int.coe_nat_lt.mp _
--    Try this: refine lt_iff_not_ge'.mpr _
--    Try this: refine n.lt_add_left 1 n _
--    Try this: refine nat.lt_succ_iff.mpr _
--    Try this: refine enat.coe_lt_coe.mp _
--    Try this: refine nat.succ_le_iff.mp _
--    Try this: refine lt_iff_le_not_le.mpr _
--    Try this: refine set.nonempty_Ico.mp _
--    Try this: refine set.nonempty_Ioc.mp _
--    Try this: refine set.left_mem_Ico.mp _
--    Try this: refine lt_iff_le_and_ne.mpr _
--    Try this: refine finset.mem_range.mp _
--    Try this: refine n.lt_add_right n 1 _
--    Try this: refine (n.psub_eq_none (n + 1)).mp _
--    Try this: refine (nat.fact_lt _).mp _
--    Try this: refine lt_of_le_of_ne _ _
--    Try this: refine lt_of_le_not_le _ _
--    Try this: refine buffer.lt_aux_1 _
--    Try this: refine lt_of_le_of_ne' _ _
--    Try this: refine gt.trans _ _
--    Try this: refine lt.trans _ _
--    Try this: refine lt_trans _ _
--    Try this: refine gt_trans _ _
--    Try this: refine nat.lt_trans _ _
--    Try this: refine (lt_is_glb_iff _).mpr _
--    Try this: refine (is_glb_lt_iff _).mpr _
--    Try this: refine (is_lub_lt_iff _).mpr _
--    Try this: refine (lt_is_lub_iff _).mpr _
--    Try this: refine (pnat.mk_lt_mk n (n + 1) _ _).mp _
--    Try this: refine gt_of_ge_of_gt _ _
--    Try this: refine gt_of_gt_of_ge _ _
--    Try this: refine lt_of_lt_of_le _ _
--    Try this: refine lt_of_le_of_lt _ _
--    Try this: refine (mul_lt_mul_left _).mp _
--    Try this: refine forall_lt_iff_le.mpr _ _
--    Try this: refine (mul_lt_mul_right _).mp _

-- Referencia:
-- Ver https://bit.ly/2Vkvrsu