import data.set

/-
Review: You've already met the relation that takes any
natural number, n, and yields a proposition asserting
it's even, (isEven n).
-/

def isEven (n : ℕ) : Prop := n%2 = 0

/-
You've already met the idea that this predicate
literally represents the set of even natural numbers:
the set of all and any natural number, n, that satisfies
the condition, n%2 = 0. That's a good basic definition
of the property of evenness of a natural number.
-/

/- *** set comprehension notation *** -/

/-
What we add now is a notation for specifying sets of
objects having specified properties: set comprehension
notation. As an example, we will specify the set of all
even numbers.

Have formalized the isEven predicate, we can now express
this set using *set comprehension notation*.
-/

def evens : set ℕ := { n : ℕ | isEven n }

/-
In an English language, or typeset proof, you'd say,
"let 'evens' be the set of LL natural numbers, n, of
type ℕ, such that n is even." Lean can infer from isEven
that the type of n has to be ℕ, and from that that the
set must have "elements" of type ℕ; so you can shorten
the definition if you wish.
-/

def evens' := { n | isEven n }

/- *** the big trick #for reasoning about sets *** -/

/-
The "big trick" in this class for reasoning about "sets" is
to remember that set comprehension notation is just notation
for the set memberships predicate, itself: here "evens" means
exactly isEven, which in turn is the predicate that takes a
natural number, n, and reduces to the proposition, n%2 = 0.
Remember that sets in Lean are not just defined using, but
are literally equivalent to their "membership" predicates.
-/

/-
First, let's use check and eval to see that a set in our
logic is defined by, and is tantamount to, its membership
predicate.
-/
#check evens      -- set ℕ, looks like a "set"
#check isEven     -- ℕ → Prop, looks like a "predicate"
                  -- yes, the types *look* different, but ...

-- ... they mean the same thing: it's the isEven predicate!
#reduce isEven    -- λ (n : ℕ), 2.mod_core n n = 0
#reduce evens     -- λ (n : ℕ), 2.mod_core n n = 0

/-
Don't worry, "λ (n : ℕ), 2.mod_core n n = 0" is the
function (predicate!) that takes a natural number, n,
and returns the proposition, n%2 = 0. In other words,
the *set*, evens, means exactly the same thing as its
*membership predicate*, isEven.
-/

/-
Among other things, this means that you can *apply* the
"set" symbol, "evens" to an argument, such as 4, to get the
proposition that that value, 4, mod two, equals zero, which
Lean reduces to 0 = 0. When such a proposition is true (has
a proof, as it does here, by rfl), that proves that that
value, here 4, satisfies the membership predicate and so is
"in" the set.
-/

#reduce evens 4   -- true by rfl, so 4 is "in" the evens set
#reduce evens 5   -- an impossibility, so 5 is not in evens

/- *** You have a new mathematical superpower! *** -/

/-
Now you have just acquired a new mathematical superpower:
the ability to precisely and abstractly define particular
sets of objects: just write the predicate that is true for
all and only the elements of the set of interest, and then
use set comprehension notation to specify the set as a good
mathematician would.
-/

/-
Here, for example, is a specification of the set of *all*
string values of length 5.
-/

def strings5 := { s : string | s.length = 5 }

/-
Can we prove that some string, say "Lean!", is in this
set? Remember the big mental trick: Because the "set",
string5, is tantamount to the *predicate*, strings5,
that takes a string, s, and returns the proposition
that s.length = 5, we can apply strings5 to "Lean!" to
form proposition that that (1) "Lean!" satisfies the
strings5 predicate, and so "Lean!" is in the set of
strings that it specifies. You have in a few simple
symbols thus defined the set of *all* length-5 strings!
-/

-- Example: Formally prove that "Lean!" satisfies strings5
example : strings5 "Lean!" :=
begin                     -- strings5 "Lean!"
unfold strings5,          -- {s : string | s.length = 5} "Lean!"
show "Lean!".length = 5,  -- identical meaning
apply rfl,                -- equality proof checks good
end

-- Example: Try and fail to prove that "Huzzoo!" is in this set
example : strings5 "Huzzoo!" :=
begin                        -- strings5 "Lean!"
unfold strings5,             -- {s : string | s.length = 5} "Lean!"
show "Huzzoo!".length = 5,   -- identical meaning
apply rfl,                   -- equality proof check fails
end

-- Example: Prove that "Huzzoo!" is *not* in this set
example : ¬strings5 "Huzzoo!"  :=
begin
assume h,                   -- proof by negation
unfold strings5 at h,       -- expand definition of term
-- Notice the notation now: a set (predicate) "applied" to a value
-- Now "Huzzoo!".length reduces to 7, so we've assumed 7 = 5
cases h,    -- And that's an impossibility
-- So ¬strings5 "Huzzoo!" is true, by negation
end

/- *** set membership notations: ∈, ∉ *** -/

/-
Now we introduce another beautiful new notation: meet ∈,
which we pronounce "is in." For example, we'd pronounce
"Lean!" ∈ strings5 as "the string, Lean!, is in the set,
strings5."

So now instead of a predicate application, strings5 "Lean!",
we write  "Lean!" ∈ strings5. This is the standard notation
that mathematicians write to assert that a value is in a set:
that it satisfies its membership predicate. We now restate
the preceding propositions using set membership notation.
You should not stop studying this file until it's clear in
your mind that "Lean!" ∈ strings5 is simply the proposition
you get by applying the predicate, λ s, s.length = 5, to the
string "Lean!" And propositions are things we can try to prove.
-/

-- Example: Prove "Lean!" is in the set of length-5 strings
example : "Lean!" ∈ strings5 :=
begin                           -- "Lean!" ∈ strings5
unfold strings5,                -- "Lean!" ∈ {s : string | s.length = 5}
show {s : string | s.length = 5} "Lean!", -- means exactly this!
show "Lean!".length = 5,                  -- means exactly this!
show (5 = 5),                             -- means exactly this!
apply rfl,                      -- with an easy proof by rfl
end
/-
The "show" tactic rewrites your goal into any form that is
*equal by definition* to the current one. Lean won't let you
rewrite your goal to any arbitrary form, of course.
-/

-- Example: Same proof, skipping all the nice rewriting
example : "Lean!" ∈ strings5 :=
begin
exact rfl,
end

-- Example: Try and fail to prove that "Huzzoo!" ∈ strings5
example : "Huzzoo!" ∈ strings5 :=
begin
exact rfl,
end
/-
Here we skip all the rewriting and just cut to the chase
-/

-- Formally prove: "Huzzoo!" ∉ (is *not in*) strings5
example : "Huzzoo!" ∉ strings5 :=
begin
show ¬(strings5 "Huzzoo!"), -- equivalent
assume h,                   -- proof by negation
unfold strings5 at h,       -- expand definition of term
                            -- h is asserting that 7 = 5!
cases h,                    -- an impossibility
-- QED, by negation
end

/- *** visualizing sets *** -/

/-
We can visualize a set as the collection of objects
that satisfy the membership predicate. We will often
draw a set as a collection of objects within a circle,
cloud, or other bounding line. The idea is that all the
objects inside the boundary are "in" the set and all
the other objects are outside the set. [See drawing.]
-/

/-
Exercises:

What predicate is true of the elements of a set
of natural numbers containing exactly the numbers,
1, 3, and 5? Answer by completing the definition of
the following predicate.
-/

def set135 := { n : ℕ | n = 1 ∨ n = 3 ∨ n = 5 }
-- Remember: This is a predicate with argument n
-- Predict what you get when you apply it to 5, then check
#reduce set135 5

/-
Now let's prove a few things, starting with the proposition
that the value 5 is "in" this set.
-/

example : 5 ∈ set135 :=
begin
-- applying set135 to 5 yields 5 = 1 ∨ 5 = 3 ∨ 5 = 5
show 5 = 1 ∨ 5 = 3 ∨ 5 = 5, -- which has a proof, to wit:
-- we can prove the right hand side, so let's go
right,    -- new Lean tactic: shorthand for apply or.elim_right
right,    -- new Lean tactic: shorthand apply or.elim_right
exact rfl,-- trivially true by the reflexivity of equality
end
/-
Notice how we used right (and in general will use both
left and right) to navigate through a big disjunction to
pick a disjunct that we can prove. In English you could
just say, the disjunction is true as the disjunct ,5 = 5,
is true.
-/

/-
Not only can we prove that 5 is "in" the set,
{ 1, 3, 5 }, which is to say that 5 satisfies
the membership predicate; we can also prove 4
is not in the set, denoted 4 ∉ set135. That is,
the membership predicate, set135 applied to 4,
yields a proposition that is false.
-/
example : 4 ∉ set135 :=       -- "not in" notation
begin
assume h, -- proof by negation, assume premise
cases h,  -- prove rest by repeated case analysis
cases h,  -- elim left: 4 = 1 is impossible
cases h,  -- elim right: 4 = 3 ∨ 4 = 5 impossible
  cases h,  -- left:  4 = 3 is impossible
  cases h,  -- right: 4 = 5 is impossible
end

/-
A little bit more Lean, optional. A "tactical"
is a tactic that takes another tactic as an
argument and does something with it. The "repeat"
tactical takes any tactic and repeatedly runs it
repeatedly until it fails. As an example, we can
use the repeat tactical to shorten the preceding
proof script.
-/
example : 4 ∉ set135 :=
begin
assume h,
repeat { cases h, }
end

/-
So now that we have ourselves a polymorphic type,
set T, where T is any type of object, we can start
to talk about operations involving objects of this
type. (Yes, after all, they are predicates, but it
is ok to think of them as specifying data objects.)
-/

-- false and the empty set
-- typically denoted by ∅
def my_empty_set (T : Type) := { t : T | false }

#check (∅ : set nat)    --
#reduce (∅ : set nat)   -- the membership predicate
/-
It takes any object (a nat in this case) and returns
the proposition false in all cases. No natural number
satisfies false, so this predicate, λ n, false specifies
the empty set of natural numbers.
-/

example : ∀ (n : nat), n ∉ (∅ : set nat) :=
begin
assume a,
assume h,   -- a ∈ ∅ means ((λ n, false) a), i.e., false
cases h,    -- QED
end

def my_universal_set (T : Type) := { t : T | true}

example : 7 ∈ my_universal_set nat :=
begin
trivial,
end

-- and (∧) and set intersection (∩)
-- a ∈ S ∩ T means a ∈ S ∧ a ∈ T
def my_set_intersection
  {α : Type}
  (S T : set α)
  (a : α):
(a ∈ S ∧ a ∈ T) ↔ (a ∈ S ∩ T) :=  -- learn how to read
begin
split,    -- Lean tactic: applies iff.intro _ _
assume h,
exact h,
assume h,
exact h,
end

-- or (∨) and set union (∪)
-- a ∈ S ∪ T means a ∈ S ∨ a ∈ T
def my_set_union
  { α : Type }
  (S T : set α)
  (a : α) :
a ∈ S ∨ a ∈ T ↔ a ∈ S ∪ T :=   -- learn how to read
begin
split,
assume h,
exact h,
assume h,
exact h,
end

-- not (¬) and set complement (ᶜ)
-- ¬(a ∈ S) ↔ a ∉ S ↔ a ∈ Sᶜ
def my_set_complement
  { α : Type }
  (S : set α)
  (a : α) :
  a ∉ S ↔ a ∈ Sᶜ
  :=
begin
split,
assume h,
exact h,
assume h,
exact h,
end

-- implication (→) and subseteq (⊆)
-- (S ⊆ T) ↔ (a ∈ S → a ∈ T)
def my_subset_of
  {α : Type}
  {S T : set α} :
  (∀ a, a ∈ S → a ∈ T) ↔ (S ⊆ T)  -- parentheses on left are essential here
  :=
begin
split,
assume st,
exact st,    -- don't use same name a for different value
assume h,
assume a,     -- remember S ⊆ T means ∀ a, ..., so assume a
assume as,
exact (h as),
end

/-
set difference, S \ T (all the elements in S that are not in T)
𝐴∖𝐵 = { 𝑥 ∣ 𝑥∈𝐴 ∧ 𝑥∉𝐵}.
-/

def my_set_difference
  {α : Type}
  {S T : set α}
  (a : α ):
  (a ∈ S ∧ a ∉ T) ↔ a ∈ S \ T :=
begin
split,
assume h,
exact h,
assume h,
exact h,
end

/-
Set equality: Next time.
-/

/-
Or maybe relations. Probably relations. Then
equality. Then set equality in particular.
-/

section foobar

#check

variables
  (α β : Type)
  (m: α → β → Prop)
  (a : α)
  (b : β)

end foobar


/-
Copyright (c) 2020 Kevin Lacker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Lacker
-/
import algebra.group_power.identities
import data.int.nat_prime
import tactic.linarith
import tactic.norm_cast
import data.set.finite
/-!
# IMO 1969 Q1

Prove that there are infinitely many natural numbers $a$ with the following property:
the number $z = n^4 + a$ is not prime for any natural number $n$.
-/

open int nat

/-- `good_nats` is the set of natural numbers satisfying the condition in the problem
statement, namely the `a : ℕ` such that `n^4 + a` is not prime for any `n : ℕ`. -/
def good_nats : set ℕ := {a : ℕ | ∀ n : ℕ, ¬ prime (n^4 + a)}

/-!
The key to the solution is that you can factor $z$ into the product of two polynomials,
if $a = 4*m^4$. This is Sophie Germain's identity, called `pow_four_add_four_mul_pow_four`
in mathlib.
-/
lemma factorization {m n : ℤ} : ((n - m)^2 + m^2) * ((n + m)^2 + m^2) = n^4 + 4*m^4 :=
pow_four_add_four_mul_pow_four.symm

/-!
To show that the product is not prime, we need to show each of the factors is at least 2,
which `nlinarith` can solve since they are each expressed as a sum of squares.
-/
lemma left_factor_large {m : ℤ} (n : ℤ) (h : 1 < m) : 1 < ((n - m)^2 + m^2) := by nlinarith
lemma right_factor_large {m : ℤ} (n : ℤ) (h : 1 < m) : 1 < ((n + m)^2 + m^2) := by nlinarith

/-!
The factorization is over the integers, but we need the nonprimality over the natural numbers.
-/
lemma int_large {m : ℤ} (h : 1 < m) : 1 < m.nat_abs :=
by exact_mod_cast lt_of_lt_of_le h le_nat_abs

lemma not_prime_of_int_mul' {m n : ℤ} {c : ℕ}
  (hm : 1 < m) (hn : 1 < n) (hc : m*n = (c : ℤ)) : ¬ prime c :=
not_prime_of_int_mul (int_large hm) (int_large hn) hc

/-- Every natural number of the form `n^4 + 4*m^4` is not prime. -/
lemma polynomial_not_prime {m : ℕ} (h1 : 1 < m) (n : ℕ) : ¬ prime (n^4 + 4*m^4) :=
have h2 : 1 < (m : ℤ), from coe_nat_lt.mpr h1,
begin
  refine not_prime_of_int_mul' (left_factor_large (n : ℤ) h2) (right_factor_large (n : ℤ) h2) _,
  exact_mod_cast factorization
end

/--
We define $a_{choice}(b) := 4*(2+b)^4$, so that we can take $m = 2+b$ in `polynomial_not_prime`.
-/
def a_choice (b : ℕ) : ℕ := 4*(2+b)^4

lemma a_choice_good (b : ℕ) : a_choice b ∈ good_nats :=
polynomial_not_prime (show 1 < 2+b, by linarith)

/-- `a_choice` is a strictly monotone function; this is easily proven by chaining together lemmas
in the `strict_mono` namespace. -/
lemma a_choice_strict_mono : strict_mono a_choice :=
((strict_mono_id.const_add 2).nat_pow (dec_trivial : 0 < 4)).const_mul (dec_trivial : 0 < 4)

/-- We conclude by using the fact that `a_choice` is an injective function from the natural numbers
to the set `good_nats`. -/
theorem imo1969_q1 : set.infinite {a : ℕ | ∀ n : ℕ, ¬ prime (n^4 + a)} :=
set.infinite_of_injective_forall_mem a_choice_strict_mono.injective a_choice_good


import .love01_definitions_and_statements_demo

/- # LoVe Exercise 1: Definitions and Statements

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

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/- ## Question 1: Truncated Subtraction

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

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

def sub : ℕ → ℕ → ℕ :=
sorry

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

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

/- ## Question 2: Arithmetic Expressions

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

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

#check aexp
#check eval

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

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

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

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

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

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

/- 2.3. State (without proving it) the correctness lemma for `simplify`, namely
that the simplified expression should have the same semantics, with respect to
`eval`, as the original expression. -/

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

/- ## Question 3: λ-Terms

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

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

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

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

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

def proj_1st : α → α → α :=
sorry

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

def proj_2nd : α → α → α :=
sorry

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

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

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

end LoVe


/-
Intuitively a set is a collection of objects.
That said, if one is not careful about what
one allows a set to be, paradoxes can arise,
making the logical system inconsistent, and
thus useless. For more details, search for an
explanation of Russell's paradox.

The work needed to repair Russell's original
mistake led to Zermelo-Frankel set theory, the
set theory of everyday mathematics, and also,
at least indirectly to the type theory that
underpins Lean and relate proof assistants.

There are two things to know about how sets
and operations involving sets are reprented
in Lean. First, in Lean, set is what we call
a type constructor. Second, sets are identified
with membership predicates. We discuss each of
these idea next.
-/

-- Type Constructors: set

/-
First, set is a type constructor, not a type.
It takes a type parameter as an argument and
returns a type, one now specialized to the
argument type. Because it takes a type and
returns a type, set (and a type constructor
more generally) is a function:  one of type,
Type → Type. So, for example, set int is the
type of sets with int-valued elements.

Lean tells us that the set type constructor can
actually take a type in any type universe, i.e.,
Type (which is really Type 0), Type 1, Type 2,
etc. We needn't be concerned with that here.
-/

#check set

-- Membership Predicates

/-
Second, sets in Lean are identified with
membership predicates: of type T → Prop,
where T is type of elements in a set. The
membership predicate is true for values in
the set and not true otherwise.
-/

#check set ℕ

#reduce set ℕ

-- Example: the empty set of ℕ

/-
For example the empty set of ℕ values, also
written as ∅ ℕ, is literally defined as the
predicate, λ n : ℕ, false. This predicate is
satisfied for no value of type ℕ, and so the
set it defines is the empty set.
-/

#check (∅ : set ℕ )

/-
We think of the predicate that defines a set
as specifying a property of elements of the
kind in or not in the set. The type, set ℕ,
is thus equated with a predicate on ℕ, which
we consider as defining the property of being
of being a member of the set. Sets (at least)
in Lean are identified with their membership
predicates.

As an example, the empty set of ℕ is defined
by the predicate that is false for every ℕ.
No natural number satisfies this predicate.
The set it denotes is the set of values that
satisfy it, which is the empty set. Study the
following code with care and understand it.
-/

#reduce (∅ : set ℕ)

/-
The predicate  that defines the empty
set is, as we've already discussed,
false(n): i.e., the function of type
ℕ → Prop that for any value, n : ℕ,
returns the proposition false. No
ℕ  can satisfy this predicate by
making it anything other than false.
The set it designates is the empty set.
-/

-- Display Notation

/-
Let's bind and empty set of ℕ to the
identifier, e. We can also write the
empty set using curly braces, or what
we call set display notation.
-/

def e: set ℕ := { }

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

def e': set ℕ := ∅

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

/-
EXERCISE: What is the property of
natural numbers that characterizes
e, the empty set of natural numbers?
Give you answer as a predicate: a
function from ℕ to Prop. Give a λ
abstraction as an answer.
-/

/-
EXERCISE: What predicate defines the
set of all ℕ values?
-/

-- Set Builder Notation

/-
We can also represent the empty
set using set builder notation.
Set builder notation is also called
set comprehension notation.
-/

/-
Here we define the empty set of ℕ again
-/

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

/-
Now we define the entire set of even ℕ
-/

def evs : set ℕ := { n | ∃ m, m + m = n }

-- Singleton Sets

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

def x: set nat := { 1 }

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

#reduce x

/-
The answer is a little surprising.
The predicate λ n, n = 1, would do
to define this set, but instead Lean
uses λ n, n = 1 ∨ false. Lean could
have, and in some cases will, leave
off the (∨ false) at the end. See it
is so in the following example code.
-/

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

/-
The two different notations give rise
to slightly different but equivalent
predicates, and thus to the same sets.
-/

-- SET MEMBERSHIP

/-
So what does set membership mean?
By the notation 1 ∈ x we mean the
proposition that "1 is in, or is
a member of the set, x." This is
simply the proposition obtained
by applying the predicate, x, to
the value, 1. x is the set and it
is the predicate that defines the
set. In Lean they are the same
thing. The proposition 1 ∈ x is
definitionally the same as (x 1).
The predicate, i.e., the set, x,
is defined as  λ (n : ℕ), n = 1.
Applying this predicate/function
to 1 yields the proposition that:
1 = 1 ∨ false. This proposition,
in turn, is easy to prove, and so,
yes, indeed, 1 is in the set x.
-/

/-
Reducing 1 ∈ x reveals the
proposition obtained by applying
the x predicate to the value 1
to get a membership proposition
for 1.
-/
#reduce 1 ∈ x
#reduce x 1

/-
In this case, the membership
proposition, 1 ∈ x, is true, as
we prove next.
-/

example : 1 ∈ x :=
-- 1 = 1 ∨ false
begin
/-
It can be easier to work with proofs
about sets if you use the change tactic
to ask Lean to show you the predicate
that the goal represents. You can use
#reduce to see the proposition that the
goal using set notation denotes.
-/
  change 1 = 1 ∨ false,
  -- the rest is straightforward
  apply or.intro_left,
  exact rfl,
end

/-
Here we use some shorthand tactics to
make it easier to write the proof. It's
good to learn this shortcuts. They make
quick work of some proof goals.
-/

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

-- MORE EXAMPLE

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

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

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

#reduce y

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

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

#reduce y

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

-- SET UNION

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

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

def u := y ∪ z

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

#reduce u

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

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

#reduce u

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

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

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

#reduce 3 ∈ y ∪ z

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

-- SET INTERSECTION

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

def w := y ∩ z

#reduce w

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

    -- 2 ∈ z
    right,
    right,
    left,
    trivial,
end

-- SET DIFFERENCE

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

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

#reduce y \ z

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

      cases pf,
        have h : 1 ≠ 3 := dec_trivial,
        contradiction,

        cases pf,
          have h : 1 ≠ 2 := dec_trivial,
          contradiction,

          have f : false := pf,
          contradiction,
end

-- SUMMARY SO FAR

/-
The examples in this summary
require you to recall that
previously in this file we
defined x, y,  and z to be the
ℕ sets, { 1 }, { 1, 2, 3 },
and { 2, 3, 4 }.
-/

#print x
#print y
#print z

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

Consider, for example, what it
means for 1 or for 2 to be in the
set, x. We write these propositions
as 1 ∈ x and as 2 ∈ x respectively.
-/

#reduce 1 ∈ x
#reduce 2 ∈ x
#reduce 3 ∈ z

/-
The union of two sets is given
by the disjunction (or, ∨) of the
respective membership predicates:
(a ∈ y ∪ z) means (a ∈ y) ∨ (a ∈ z).
-/

#reduce 1 ∈ (y ∪ z)
#reduce (1 ∈ y) ∨ (1 ∈ z)

/-
The intersection of two sets is
defined by the conjunction of the
respective membership predicates:
(x ∈ y ∩ z) = (x ∈ y ∧ a ∈ z)
-/

#reduce (1 ∈ y ∩ z)

/-The difference of two sets, y \ z,
is defined by the conjunction of the
first and the negation of the second
membership predicates for the sets:
(a ∈ y \ z) = ( a ∈ y) ∧ (¬ a ∈ z).
-/

#reduce 1 ∈ y \ z

-- PART II

/-
Now we introduce additional basic
set theory concepts: these include
notions of subsets, set equality,
power sets, product sets, tuples,
and a function that simulates an
element insertion operator for sets.

In all cases, we see that these
set operations can be understood
as operations on the predicates
that define sets. The connection
of set theory to predicate logic
is thus made clear and explicit.
-/

-- SUBSET

/-
Subset, denoted ⊆, is a binary
relation on sets, denoted X ⊆ Y,
where X and Y are sets. Viewed
as a predicate on such sets, it
is satisfied (made true by X and
Y) iff every member of X is also
a member of Y. Logically, X is a
subset of Y if the property of
being in X implies the property
of being in Y.
-/

#check x ⊆ y
#reduce x ⊆ y

/-
Note that what is displayed when you
hover over the reduce line includes
"script" curly brace characters. These
indicate a slight variant on implicit
arguments that we needn't get in any
detail right now. Just think of them
as indicating implicit arguments.
-/

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

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

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

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

/-
We can now see that the subset relation
on sets has a precise logical meaning.
x ⊆ y means ∀ a, a ∈ x → a ∈ y.
-/

#check x ⊆ y
#reduce x ⊆ y

/-
A quick note on a pattern that appears
often in predicate logic:

Let's look at the definition of the
subset relation again, for sets of ℕ
values, x and y. Here is what it means
for y ⊆ x.

∀ (a : ℕ), a ∈ y → a ∈ z.

Let's translate this to logicky English.

For any natural number, a, if a is in
y then e is in z. That is what is means
for y to be a subset of z.

What's interesting in this formulation
is the combination of a ∀, which picks
out *all* elements of the ℕ type, followed
by a conditional (implication), where the
premise imposes a further constraint on
the elements being considered. It need
only be true that every ℕ that is *also*
and element of y be a member of z for y
to be a subset of z.

This is a common pattern in logic. The
general form is ∀ x : T, P x → Q x. It
is read as saying that for any x *with
property P*, some other property, Q,
must hold. In effect it quantifies over
the values of type T with property P,
and then makes a statement about those
values, in particular: here they they
also have property Q.
-/

/-
Okay, so let assert and prove a
proposition involving the subset
relation. We'll show that x ⊆ y,
i.e., { 1 } ⊆ { 1, 2, 3 }. To do
it we have to proving that if
a ∈ x then a ∈ y. Now remember
what x and a ∈ x are. First, x
is understood to be a set, but
it is specifically a membership
predicate, of type ℕ → Prop, and
a ∈ x is a proposition, namely
the one obtained by applying the
membership predicate to a: (x a).
If (x a), i.e., a ∈ x, is true,
i.e., provable, then a is said
to be a member of the set, x.
-/

/-
Let's have another look at what
the proposition, x ⊆ y, means:
for any a, if a ∈ x then a ∈ y.
-/

#reduce x ⊆ y

/-
So let's prove it's true.
-/

example : x ⊆ y :=
begin
/-
It's sometimes helpful to change
from set notation to the equivalent
propositional notation. The change
tactic will do this for you, as
long as what you're changing the
goal is is "definitionally equal"
to the current goal. You cand find
out what the exact proposition is
using reduce, as we did above.
-/
  change ∀ ⦃a : ℕ⦄, a = 1 ∨ false → a = 3 ∨ a = 2 ∨ a = 1 ∨ false,
/-
The rest is just an everyday proof.
Note that we can quickly zero in on
the disjunct we need using a series
of left and right tactics. (You do
need to remember that ∨ is right
associative, so left gives you the
left disjunct and right gives you
everything else to the right of the
leftmost disjunct.
-/
  assume a,
  intro h,
  cases h,
  -- case a = 1
    right,
    right,
    left,
    assumption,

  -- case false
    contradiction,
end

section sets
/-
We temporarily assume, within this
section, that T is an arbitrary type,
x is an arbitrary value of type T,
and that A, B, and C are arbitrary
sets of T-type elements.
-/
variable T : Type
variable x : T
variables A B C : set T

/-
We can confirm our understanding
of the subset relation using this
notation. Now A and B are sets, and
in Lean that means that these sets
are represented by their membership
predicates. They are membership
predicates.
-/

#reduce A ⊆ B

/-
EXERCISE: Explain precisely what
the message produced by #reduce is
saying. What is another way that
Lean could have written A a or B a?
-/

-- SET EQUALITY (and extensionality)

/-
The "principle of extensionality" for
sets stipulates that if one can show
that ∀ e, (e ∈ A ↔ e ∈ B) → (A = B).
-/

#check ext

/-When faced with a goal of proving
that two sets, A and B are equal,
i.e., that A = B, one can apply this
principle to reduce the goal to that
of showing that ∀ e, e ∈ A ↔ e ∈ B.
-/

-- set equality
example : A = B :=
begin
  apply ext,
  intro x,
  apply iff.intro,
  intro,
/-
We can proceed no further here, as
we have nothing to use to prove that
A actually does equal B in this case.
A and B are just arbitary sets, so not
equal, in general. What the example is
meant to show is how to use ext and
how to proceed. As for this proof, we
will just abandon it as not possible
to prove.
-/
end

/-
Let's prove that { 1, 2 } = { 2, 1 }.
-/

def p : set ℕ := { 1, 2 }
def q : set ℕ := { 2, 1 }

#reduce 1 ∈ p

theorem oo : p = q  :=
begin
  apply ext,
  intro x,
  apply iff.intro,

  -- forward direction

  intro,
  -- remember that a is a disjunction
  cases a with first rest,
/-
We introduce a new tactic: rewrite,
written as rw h or rw ←h. When applied
to a proof, h : x = y or h : x ↔ y, of
an equality or a bi-implication, it
rewrites any occurrences of the left
side, x, in the goal, with the right
side, y. If you want to rewrite by
replacing occurrences of the right
side, y, with the left, x, use rw ←h.
-/
  rw first,
  right, left, apply rfl,
  cases rest,
  rw rest,
  apply or.inl, apply rfl,
  -- rest is now ((λ n, false) x) = false!
  apply false.elim rest,

  -- backward direction
  intro,
  cases a,
  rw a,
  right, left, apply rfl,
  cases a,
  rw a,
  left, apply rfl,
  apply false.elim a,
end

-- POWERSET

/-
The powerset of a set, A, is the set of all
of the subsets of A.
-/

#check A
#check powerset A
#check 𝒫 A
#reduce 𝒫 A

/-
Note about implicit arguments. In the preceding
definition we see {{ }} brackets, rendered using
the characters, ⦃ ⦄. This states that the argument
is to be inferred from context (is implicit) but
is expected only when it appears before another
implicit argument. This notation tells Lean not
to "eagerly" consume the argument, as soon as it
can, but to wait to consume it until it appears,
implicitly, before another implicit argument in a
list of arguments. This is a notational detail
that it's not worth worry about at the moment.
-/

/-
There are two members we always know are in the
powerset of A: the emptyset and A itself. Of course,
if A is the emptyset, this is technically only one
member, but the proofs are the same.
-/

#check A
#check 𝒫 A
#reduce 𝒫 A

/-
We define the powerset of A, itself a set,
as, λ (t : T → Prop), ∀ ⦃a : T⦄, t a → A a.
Let's analyze this. First, we note that it
is a predicate, as we would expect, given
that we use predicates to define sets. In
particular, this a predicate on values of
type, T → Prop, which is to say, this is a
predicate on predicates that define sets!
It's a predicate that's true whenever its
argument, a set defined by a predicate, is
a subst of A, which is to say that it's true
when any element in the argument (set) is
also in A. When applied to a set, t, this
predicate is satisfied (true) if and only
if every a in t is also in A: formally,
∀ ⦃a : T⦄, t a → A a.
-/

#reduce ∅ ∈ 𝒫 A

/-
Lean is helping us here. We need to
show that if a ∈ ∅ then a ∈ a to show
that ∅ is a subset of A. But a ∈ ∅ is
literally false. To see it, work through
the application of the predicate for ∅
to any value, a. Lean is simplifying
a ∈ ∅ to false.
-/

example: ∅ ∈ 𝒫 A :=
/-
To show that the set, ∅, is in the set 𝒫 A,
we have to show that ∅ is a subset of A. To
do that, we have to show that any t that is
in ∅ is also in A.
-/
begin
  -- change goal to logical form
  change ∀ ⦃a : T⦄, false → A a,
  -- use forall introduction
  intro t,
  -- now it's a trivial proof
  assume t_in_emptyset,
  contradiction,
end

#reduce A ∈ 𝒫 A

/-
To prove this, we need to prove that A
is subset of A, which is to say any a in
A is also in A. It's as simple as that and
the proof is of course trivial.
-/

example: A ∈ 𝒫 A :=
begin
  change ∀ ⦃a : T⦄, A a → A a,
  assume t,
  assume t_in_A,
  assumption
end

/-
Slightly more interesting cases are also
easy to prove. There's nothing involved
here beyond what you already understand.
-/
#reduce ({1, 3}: set ℕ) ∈ 𝒫 ({1, 2, 3}: set ℕ)

/-
One again to prove that {1, 3} is in the power
set of {1, 2, 3} it suffices to show that every
element of {1, 3} is in {1, 2, 3}, because that
is what it means to be a subset. The proof is
straightforward.
-/

example: ({1, 3}: set ℕ) ∈ 𝒫 ({1, 2, 3}: set ℕ) :=
begin
  change ∀ ⦃a : ℕ⦄, a = 3 ∨ a = 1 ∨ false → a = 3 ∨ a = 2 ∨ a = 1 ∨ false,
  -- forall introduction
  intro t,
  -- assume premise of implication to be proved
  assume pf_t_in_1_3,
  -- use or elimination on proof of premise
  cases pf_t_in_1_3 with pf_t_is_3 pf_t_in_1 ,
    -- show 3 from {1, 3} is in {1, 2, 3}
    exact or.inl pf_t_is_3,

    -- show 1 from {1, 3} is in {1, 2, 3}
    right,
    -- an ever so slightly clever or intro
    exact or.inr pf_t_in_1,
end

-- a more involved example; study this one
-- {{1, 2}, {1, 3}, {2, 3}} ⊆ 𝒫 {1, 2, 3}

#reduce ({{1, 2}, {1, 3}, {2, 3}}: set (set nat)) ⊆ 𝒫 ({1, 2, 3})

example :
({{1, 2}, {1, 3}, {2, 3}}) ⊆ 𝒫 ({1, 2, 3} : set nat) :=
begin
  change ∀ ⦃a : ℕ → Prop⦄,
  (a = λ (b : ℕ), b = 3 ∨ b = 2 ∨ false) ∨
    (a = λ (b : ℕ), b = 3 ∨ b = 1 ∨ false) ∨ (a = λ (b : ℕ), b = 2 ∨ b = 1 ∨ false) ∨ false →
  ∀ ⦃a_1 : ℕ⦄, a a_1 → a_1 = 3 ∨ a_1 = 2 ∨ a_1 = 1 ∨ false,
  intro s,
  assume pf_s_in_subset,
  cases pf_s_in_subset with pf_s_is_2_3,
    assume t,
    assume pf_t_in_s,
    cases pf_s_is_2_3 with pf_s_is_3,
      cases pf_t_in_s with pf_t_is_3 pf_t_in_2,
        exact or.inl pf_t_is_3,

        apply or.inr,
        cases pf_t_in_2 with pf_t_in_2 pf_t_in_emptyset,
          exact or.inl pf_t_in_2,
          exact false.elim pf_t_in_emptyset,

  cases pf_s_in_subset with pf_s_is_1_3,
    assume t,
    assume pf_t_in_s,
    cases pf_s_is_1_3 with pf_s_is_3,
      cases pf_t_in_s with pf_t_is_3 pf_t_in_1,
        exact or.inl pf_t_is_3,

        apply or.inr,
        apply or.inr,
        assumption,

  cases pf_s_in_subset with pf_s_is_1_2 pf_s_in_emptyset,
    assume t,
    assume pf_t_in_s,
    cases pf_s_is_1_2 with pf_s_is_2,
      cases pf_t_in_s with pf_t_is_2 pf_t_in_1,
        apply or.inr,
        exact or.inl pf_t_is_2,

        apply or.inr,
        apply or.inr,
        assumption,

    exact false.elim pf_s_in_emptyset,

end

-- Tuples
/-
If S and T are types, then the product type
of S and T, written out as (prod S T) and in
shorthand as S × T, has as its values all of
2-tuples, or ordered pairs, (s, t), where
s : S, and t : T.
-/

/-
In the following code, we see that ℕ × ℕ is
a type, and the 2-tuple, or ordered pair,
(1, 2), is a value of this type.
-/

#check ℕ × ℕ
#check prod ℕ ℕ
#check (1, 2)

/-
We can form product types from any two types.
Note the type of this 2-tuple.
-/

#check ("Hello Lean", 1)

/-
This ordered pair notation in Lean in shorthand
for the appliation of the constructor, prod.mk,
two two arguments. The constructor takes the
type arguments implicitly.
-/
#check prod.mk 1 2 -- long way to write (1, 2)
example : prod.mk 1 2 = (1, 2) := rfl

/-
We can form 3- and larger tuples using nested
2-tuples. Note that × is right associative, as
you can see by studying the type of this term.
-/

#check ("Hello Lean", (10, (tt,1)))

#check ((0,0),(0,0))

-- PRODUCT SET

/-
The Cartesian product set of two sets, A
and B, denoted as A × B in everyday math,
is the set of all ordered pairs, (a, b)
(values of type prod A B), where a ∈ A
and b ∈ B. In Lean, the set product of
sets, A and B, is denoted as set.prod A B.
There is no nice infix operator notation
for set products at this time.

Note carefully: there is a distinction
here between product types and product sets.
Product types are types, while product sets
are sets. And sets are not types in Lean.
Rather they're specified as properties.

This is potentially confusing. It is made
more confusing by the fact that Lean has
a way to convert a set into a special type
called a subset type: the type of elements
in the set, along with proofs of membership.
And if you apply prod to two sets, you'll
get a subset type!
-/

#check set.prod y z     -- product set type
#reduce set.prod y z    -- product set property
#check prod y z         -- oops, a subset type
#check y × z            -- oops, same thing
#reduce prod y z        -- oops, not what we want

/-
A set product is just a set, which is to
say it's defined by a predicate, s. Such a
predicate is true for exactly the members
of the set. That is, (s x) is a proposition
that is true iff x ∈ s. The predicate that
defines a product set is a predicate on
ordered pairs. It's basically defined like
this:
-/

def mysetprod (S T : Type)
  (s : set S) (t : set T) : set (S × T) :=
    { p : S × T | p.1 ∈ s ∧ p.2 ∈ t }

/-
What this says, then, is that the product set
of s (a set of S-type values) and t (a set of
T-type values) is the set of pairs, p, each of
type (prod S T), and each thus an ordered pair,
p = (p.1, p.2), where p.1 ∈ s and p.2 ∈ t.
Lean provides this function as set.prod.
-/

example : (1, 2) ∈ set.prod y z :=
begin
change (λ (p : ℕ × ℕ),
  (p.fst = 3 ∨ p.fst = 2 ∨ p.fst = 1 ∨ false) ∧ (p.snd = 4 ∨ p.snd = 3 ∨ p.snd = 2 ∨ false)) (1,2),
  split,
  right,right,left,apply rfl,
  right,right,left,apply rfl,
end

-- COMPLEMENT

/-
The complement of a set is the set of all
values of the set's type that are not in that
set.

The complement is specified by the "-" sign
-/

#check -y
#reduce -y
#reduce 5 ∈ -y

example: 5 ∈ -y :=
begin
  change 5 = 3 ∨ 5 = 2 ∨ 5 = 1 ∨ false → false,
  assume pf_5_in_y,
  cases pf_5_in_y with pf_5_eq_3 h,
    have pf_5_ne_3: 5 ≠ 3 := dec_trivial,
    contradiction,

  cases h with pf_5_eq_2 h,
    have pf_5_ne_2: 5 ≠ 2 := dec_trivial,
    contradiction,

  cases h with pf_5_eq_1 h,
    have pf_5_ne_1: 5 ≠ 1 := dec_trivial,
    contradiction,

    assumption
end

-- INSERTION

/-
We can define an operation that we can think
of as "inserting" an element into a set: as a
function that takes an element and a set and
returns the set containing that element along
with the elements of the original set. Unlike
in Python or Java, there's no change to a data
structure in this case. In pure functional
languages, such as Lean, there is no concept
of a memory or of "mutable" objects. Rather,
everything is defined by functions, here one
that takes a set and a value and constructs
a new set value just like the old one but with
the new element included as well. -/

def myInsert
{ T : Type } (a : T) (s : set T) : set T :=
    {b | b = a ∨ b ∈ s}

/-
The predicate for the set resulting from
"inserting 5 into { 1, 2, 3, 5 }" admits
that 5 is also a member of the result set.
-/
#reduce myInsert 5 { 1, 2, 3, 4 }

-- The Lean math library defines "insert"
#reduce insert 5 { 1, 2, 3, 4 }

-- MORE EXAMPLES

/-
Several of these examples are adapted
from Jeremy Avigad's book, Logic and
Proof. Prof. Avigad (CMU) is one of the
main contributors to the development of
Lean, and he leads the development of
its mathematical libraries, including
the one you're now using for sets, in
particular.
-/

/-
A is a subset of A ∪ B
-/
example : ∀ T : Type, ∀ s t: set T, s ⊆ s ∪ t :=
begin
  assume T s t x,
  assume h : x ∈ s,
  show x ∈ s ∪ t,
  change s x ∨ t x,
  change s x at h,
  from or.inl h
end

/-
The empty set, ∅, is a subset of any set.
-/
example : ∀ T : Type, ∀ s: set T, ∅ ⊆ s :=
begin
  assume T s x,
  assume h : x ∈ (∅ : set T),
  have f: false := h,
  contradiction,
end

/-
Subset is a transitive relation on sets
-/
example :
    ∀ T : Type, ∀ A B C: set T,
        A ⊆ B → B ⊆ C → A ⊆ C
:=
begin
    assume T s t u,
    assume st tu,
    intro,
    intro,
    have z := st a_1,
    exact (tu z),
end

/-
If an object is in both sets A and B
then it is in their intersection.
-/
example :
∀ T : Type, forall A B : set T,
∀ x, x ∈ A → x ∈ B → x ∈ A ∩ B :=
begin
  assume T A B x,
  assume hA : x ∈ A,
  assume hB : x ∈ B,
  show x ∈ A ∧ x ∈ B, from
  and.intro hA hB,
end

/-
If an object is in set A or is in
set B then it is in their union.
-/
example :
∀ T : Type, forall A B : set T,
∀ x, x ∈ A ∨ x ∈ B → x ∈ A ∪ B :=
begin
  assume T A B x,
  intro dis,
  show x ∈ A ∨ x ∈ B,
  by assumption,
end

/-
A minus B is a subset of A
-/
example : A \ B ⊆ A :=
begin
  assume x,
  assume mem : x ∈ A \ B,
  cases mem,
  from mem_left,
end

/-
A minus B is contained in the complement of B
-/
example : A \ B ⊆ -B :=
begin
  assume x,
  assume mem : x ∈ A \ B,
  change x ∈ A ∧ ¬ x ∈ B at mem,
  change x ∉ B,
  exact mem.right,
end

/-
A \ B is equal to the intersection
of A with the complement of B.
-/
example : A \ B = A ∩ -B :=
begin
  apply ext,
  intro,
  split,
  intro h,
  exact h,
  intro h,
  exact h,
end

end sets


import data.real.basic

/-
One of the earliest kind of proofs one encounters while learning mathematics is proving by
a calculation. It may not sound like a proof, but this is actually using lemmas expressing
properties of operations on numbers. It also uses the fundamental property of equality: if two
mathematical objects A and B are equal then, in any statement involving A, one can replace A
by B. This operation is called rewriting, and the Lean "tactic" for this is `rw`.

In the following exercises, we will use the following two lemmas:
  mul_assoc a b c : a * b * c = a * (b * c)
  mul_comm a b : a*b = b*a

Hence the command
  rw mul_assoc a b c,
will replace a*b*c by a*(b*c) in the current goal.

In order to replace backward, we use
  rw ← mul_assoc a b c,
replacing a*(b*c) by a*b*c in the current goal.

Of course we don't want to constantly invoke those lemmas, and we will eventually introduce
more powerful solutions.
-/

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

-- 0001
example (a b c : ℝ) : (c * b) * a = b * (a * c) :=
begin
  sorry
end

-- 0002
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  sorry
end

/-
Now let's return to the preceding example to experiment with what happens
if we don't give arguments to mul_assoc or mul_comm.
For instance, you can start the next proof with
  rw ← mul_assoc,
Try to figure out what happens.
-/

-- 0003
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  sorry
end

/-
We can also perform rewriting in an assumption of the local context, using for instance
  rw mul_comm a b at hyp,
in order to replace a*b by b*a in assumption hyp.

The next example will use a third lemma:
  two_mul a : 2*a = a + a

Also we use the `exact` tactic, which allows to provide a direct proof term.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  rw hyp' at hyp,
  rw mul_comm d a at hyp,
  rw ← two_mul (a*d) at hyp,
  rw ← mul_assoc 2 a d at hyp,
  exact hyp, -- Our assumption hyp is now exactly what we have to prove
end

/-
And the next one can use:
  sub_self x : x - x = 0
-/

-- 0004
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  sorry
end

/-
What is written in the two preceding example is very far away from what we would write on
paper. Let's now see how to get a more natural layout.
Inside each pair of curly braces below, the goal is to prove equality with the preceding line.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b : by { rw hyp }
  ... = d*a + a*d  : by { rw hyp' }
  ... = a*d + a*d  : by { rw mul_comm d a }
  ... = 2*(a*d)    : by { rw two_mul }
  ... = 2*a*d      : by { rw mul_assoc },
end

/-
Let's note there is no comma at the end of each line of calculation. `calc` is really one
command, and the comma comes only after it's fully done.

From a practical point of view, when writing such a proof, it is convenient to:
* pause the tactic state view update in VScode by clicking the Pause icon button
  in the top right corner of the Lean Goal buffer
* write the full calculation, ending each line with ": by {}"
* resume tactic state update by clicking the Play icon button and fill in proofs between
  curly braces.

Let's return to the other example using this method.
-/

-- 0005
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  sorry
end

/-
The preceding proofs have exhauted our supply of "mul_comm" patience. Now it's time
to get the computer to work harder. The `ring` tactic will prove any goal that follows by
applying only the axioms of commutative (semi-)rings, in particuler commutativity and
associativity of addition and multiplication, as well as distributivity.

We also note that curly braces are not necessary when we write a single tactic proof, so
let's get rid of them.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b   : by rw hyp
     ... = d*a + a*d : by rw hyp'
     ... = 2*a*d     : by ring,
end

/-
Of course we can use `ring` outside of `calc`. Let's do the next one in one line.
-/

-- 0006
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  sorry
end

/-
This is too much fun. Let's do it again.
-/

-- 0007
example (a b : ℝ) : (a + b) + a = 2*a + b :=
begin
  sorry
end

/-
Maybe this is cheating. Let's try to do the next computation without ring.
We could use:
pow_two x : x^2 = x*x
mul_sub a b c : a*(b-c) = a*b - a*c
add_mul a b c : (a+b)*c = a*c + b*c
add_sub a b c : a + (b - c) = (a + b) - c
sub_sub a b c : a - b - c = a - (b + c)
add_zero a : a + 0 = a
-/

-- 0008
example (a b : ℝ) : (a + b)*(a - b) = a^2 - b^2 :=
begin
  sorry
end

/- Let's stick to ring in the end. -/


import data.real.basic

/-
One of the earliest kind of proofs one encounters while learning mathematics is proving by
a calculation. It may not sound like a proof, but this is actually using lemmas expressing
properties of operations on numbers. It also uses the fundamental property of equality: if two
mathematical objects A and B are equal then, in any statement involving A, one can replace A
by B. This operation is called rewriting, and the Lean "tactic" for this is `rw`.

In the following exercises, we will use the following two lemmas:
  mul_assoc a b c : a * b * c = a * (b * c)
  mul_comm a b : a*b = b*a

Hence the command
  rw mul_assoc a b c,
will replace a*b*c by a*(b*c) in the current goal.

In order to replace backward, we use
  rw ← mul_assoc a b c,
replacing a*(b*c) by a*b*c in the current goal.

Of course we don't want to constantly invoke those lemmas, and we will eventually introduce
more powerful solutions.
-/

-- Uncomment the following line if you want to see parentheses around subexpressions.
-- set_option pp.parens true

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

-- 0001
example (a b c : ℝ) : (c * b) * a = b * (a * c) :=
begin
  sorry
end

-- 0002
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  sorry
end

/-
Now let's return to the preceding example to experiment with what happens
if we don't give arguments to mul_assoc or mul_comm.
For instance, you can start the next proof with
  rw ← mul_assoc,
Try to figure out what happens.
-/

-- 0003
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  sorry
end

/-
We can also perform rewriting in an assumption of the local context, using for instance
  rw mul_comm a b at hyp,
in order to replace a*b by b*a in assumption hyp.

The next example will use a third lemma:
  two_mul a : 2*a = a + a

Also we use the `exact` tactic, which allows to provide a direct proof term.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  rw hyp' at hyp,
  rw mul_comm d a at hyp,
  rw ← two_mul (a*d) at hyp,
  rw ← mul_assoc 2 a d at hyp,
  exact hyp, -- Our assumption hyp is now exactly what we have to prove
end

/-
And the next one can use:
  sub_self x : x - x = 0
-/

-- 0004
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  sorry
end

/-
What is written in the two preceding example is very far away from what we would write on
paper. Let's now see how to get a more natural layout.
Inside each pair of curly braces below, the goal is to prove equality with the preceding line.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b : by { rw hyp }
  ... = d*a + a*d  : by { rw hyp' }
  ... = a*d + a*d  : by { rw mul_comm d a }
  ... = 2*(a*d)    : by { rw two_mul }
  ... = 2*a*d      : by { rw mul_assoc },
end

/-
Let's note there is no comma at the end of each line of calculation. `calc` is really one
command, and the comma comes only after it's fully done.

From a practical point of view, when writing such a proof, it is convenient to:
* pause the tactic state view update in VScode by clicking the Pause icon button
  in the top right corner of the Lean Goal buffer
* write the full calculation, ending each line with ": by {}"
* resume tactic state update by clicking the Play icon button and fill in proofs between
  curly braces.

Let's return to the other example using this method.
-/

-- 0005
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  sorry
end

/-
The preceding proofs have exhausted our supply of "mul_comm" patience. Now it's time
to get the computer to work harder. The `ring` tactic will prove any goal that follows by
applying only the axioms of commutative (semi-)rings, in particular commutativity and
associativity of addition and multiplication, as well as distributivity.

We also note that curly braces are not necessary when we write a single tactic proof, so
let's get rid of them.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b   : by rw hyp
     ... = d*a + a*d : by rw hyp'
     ... = 2*a*d     : by ring,
end

/-
Of course we can use `ring` outside of `calc`. Let's do the next one in one line.
-/

-- 0006
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  sorry
end

/-
This is too much fun. Let's do it again.
-/

-- 0007
example (a b : ℝ) : (a + b) + a = 2*a + b :=
begin
  sorry
end

/-
Maybe this is cheating. Let's try to do the next computation without ring.
We could use:
pow_two x : x^2 = x*x
mul_sub a b c : a*(b-c) = a*b - a*c
add_mul a b c : (a+b)*c = a*c + b*c
add_sub a b c : a + (b - c) = (a + b) - c
sub_sub a b c : a - b - c = a - (b + c)
add_zero a : a + 0 = a
-/

-- 0008
example (a b : ℝ) : (a + b)*(a - b) = a^2 - b^2 :=
begin
  sorry
end

/- Let's stick to ring in the end. -/


import data.nat.gcd
import data.real.irrational

/- TEXT:

.. _section_irrational_roots:

Irrational Roots
----------------

Let's start with a fact known to the ancient greeks, namely,
that the square root of 2 is irrational.
If we suppose otherwise,
we can write :math:`\sqrt{2} = a / b` as a fraction
in lowest terms. Squaring both sides yields :math:`a^2 = 2 b^2`,
which implies that :math:`a` is even.
If we write :math:`a = 2c`, then we get :math:`4c^2 = 2 b^2`
and hence :math:`b^2 = 2 c^2`.
This implies that :math:`b` is also even, contradicting
the fact that we have assumed that :math:`a / b` has been
reduced to lowest terms.

Saying that :math:`a / b` is a fraction in lowest terms means
that :math:`a` and :math:`b` do not have any factors in common,
which is to say, they are *coprime*.
Mathlib defines the predicate ``nat.coprime m n`` to be ``nat.gcd m n = 1``.
Using Lean's anonymous projection notation, if ``s`` and ``t`` are
expressions of type ``nat``, we can write ``s.coprime t`` instead of
``nat.coprime s t``, and similarly for ``nat.gcd``.
As usual, Lean will often unfold the definition of ``nat.coprime`` automatically
when necessary,
but we can also do it manually by rewriting or simplifying with
the identifier ``nat.coprime``.
The ``norm_num`` tactic is smart enough to compute concrete values.

EXAMPLES: -/
-- QUOTE:
#print nat.coprime

example (m n : nat) (h : m.coprime n) : m.gcd n = 1 := h

example (m n : nat) (h : m.coprime n) : m.gcd n = 1 :=
by { rw nat.coprime at h, exact h }

example : nat.coprime 12 7 := by norm_num
example : nat.gcd 12 8 = 4 := by norm_num
-- QUOTE.

/- TEXT:
We have already encountered the ``gcd`` function in
:numref:`more_on_order_and_divisibility`.
There is also a version of ``gcd`` for the integers;
we will return to a discussion of the relationship between
different number systems below.
There are even a generic ``gcd`` function and generic
notions of ``prime`` and ``is_coprime``
that make sense in general classes of algebraic structures.
We will come to understand how Lean manages this generality
in the next chapter.
In the meanwhile, in this section, we will restrict attention
to the natural numbers.

We also need the notion of a prime number, ``nat.prime``.
The theorem ``nat.prime_def_lt`` provides one familiar characterization,
and ``nat.prime.eq_one_or_self_of_dvd`` provides another.
EXAMPLES: -/
-- QUOTE:
#check @nat.prime_def_lt

example (p : ℕ) (prime_p : nat.prime p) : 2 ≤ p ∧ ∀ (m : ℕ), m < p → m ∣ p → m = 1 :=
by rwa nat.prime_def_lt at prime_p

#check nat.prime.eq_one_or_self_of_dvd

example (p : ℕ) (prime_p : nat.prime p) : ∀ (m : ℕ), m ∣ p → m = 1 ∨ m = p :=
prime_p.eq_one_or_self_of_dvd

example : nat.prime 17 := by norm_num

-- commonly used
example : nat.prime 2 := nat.prime_two
example : nat.prime 3 := nat.prime_three
-- QUOTE.

/- TEXT:
In the natural numbers, a prime number has the property that it cannot
be written as a product of nontrivial factors.
In a broader mathematical context, an element of a ring that has this property
is said to be *irreducible*.
An element of a ring is said to be *prime* if whenever it divides a product,
it divides one of the factors.
It is an important property of the natural numbers
that in that setting the two notions coincide,
giving rise to the theorem ``nat.prime.dvd_mul``.

We can use this fact to establish a key property in the argument
above:
if the square of a number is even, then that number is even as well.
Mathlib defines the predicate ``even`` in ``data.nat.parity``,
but for reasons that will become clear below,
we will simply use ``2 ∣ m`` to express that ``m`` is even.
EXAMPLES: -/
-- QUOTE:
#check @nat.prime.dvd_mul
#check nat.prime.dvd_mul nat.prime_two
#check nat.prime_two.dvd_mul

-- BOTH:
lemma even_of_even_sqr {m : ℕ} (h : 2 ∣ m^2) : 2 ∣ m :=
begin
  rw [pow_two, nat.prime_two.dvd_mul] at h,
  cases h; assumption
end

-- EXAMPLES:
example {m : ℕ} (h : 2 ∣ m^2) : 2 ∣ m :=
nat.prime.dvd_of_dvd_pow nat.prime_two h
-- QUOTE.

/- TEXT:
As we proceed, you will need to become proficient at finding the facts you
need.
Remember that if you can guess the prefix of the name and
you have imported the relevent library,
you can use tab completion (sometimes with ``ctrl-tab``) to find
what you are looking for.
You can use ``ctrl-click`` on any identifier to jump to the file
where it is defined, which enables you to browse definitions and theorems
nearby.
You can also use the search engine on the
`Lean community web pages <https://leanprover-community.github.io/>`_,
and if all else fails,
don't hesitate to ask on
`Zulip <https://leanprover.zulipchat.com/>`_.
EXAMPLES: -/
-- QUOTE:
example (a b c : nat) (h : a * b = a * c) (h' : a ≠ 0) :
  b = c :=
begin
  -- library_search suggests the following:
  exact (mul_right_inj' h').mp h
end
-- QUOTE.

/- TEXT:
The heart of our proof of the irrationality of the square root of two
is contained in the following theorem.
See if you can fill out the proof sketch, using
``even_of_even_sqr`` and the theorem ``nat.dvd_gcd``.
BOTH: -/
-- QUOTE:
example {m n : ℕ} (coprime_mn : m.coprime n) : m^2 ≠ 2 * n^2 :=
begin
  intro sqr_eq,
  have : 2 ∣ m,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { apply even_of_even_sqr,
    rw sqr_eq,
    apply dvd_mul_right },
-- BOTH:
  obtain ⟨k, meq⟩ := dvd_iff_exists_eq_mul_left.mp this,
  have : 2 * (2 * k^2) = 2 * n^2,
  { rw [←sqr_eq, meq], ring },
  have : 2 * k^2 = n^2,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
    from (mul_right_inj' (by norm_num)).mp this,
-- BOTH:
  have : 2 ∣ n,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { apply even_of_even_sqr,
    rw ←this,
    apply dvd_mul_right },
-- BOTH:
  have : 2 ∣ m.gcd n,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
    by apply nat.dvd_gcd; assumption,
-- BOTH:
  have : 2 ∣ 1,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { convert this, symmetry, exact coprime_mn },
-- BOTH:
  norm_num at this
end
-- QUOTE.

/- TEXT:
In fact, with very few changes, we can replace ``2`` by an arbitrary prime.
Give it a try in the next example.
At the end of the proof, you'll need to derive a contradiction from
``p ∣ 1``.
You can use ``nat.prime.two_le``, which says that
any prime number is greater than or equal to two,
and ``nat.le_of_dvd``.
BOTH: -/
-- QUOTE:
example {m n p : ℕ} (coprime_mn : m.coprime n) (prime_p : p.prime) : m^2 ≠ p * n^2 :=
/- EXAMPLES:
    sorry
SOLUTIONS: -/
begin
  intro sqr_eq,
  have : p ∣ m,
  { apply prime_p.dvd_of_dvd_pow,
    rw sqr_eq,
    apply dvd_mul_right },
  obtain ⟨k, meq⟩ := dvd_iff_exists_eq_mul_left.mp this,
  have : p * (p * k^2) = p * n^2,
  { rw [←sqr_eq, meq], ring },
  have : p * k^2 = n^2,
  { apply (mul_right_inj' _).mp this,
    exact prime_p.ne_zero },
  have : p ∣ n,
  { apply prime_p.dvd_of_dvd_pow,
    rw ←this,
    apply dvd_mul_right },
  have : p ∣ nat.gcd m n,
  { apply nat.dvd_gcd; assumption },
  have : p ∣ 1,
  { convert this, symmetry, exact coprime_mn },
  have : 2 ≤ 1,
  { apply prime_p.two_le.trans,
    exact nat.le_of_dvd zero_lt_one this },
  norm_num at this
end
-- QUOTE.
-- BOTH:

/- TEXT:
Let us consider another approach.
Here is a quick proof that if :math:`p` is prime, then
:math:`m^2 \ne p n^2`: if we assume :math:`m^2 = p n^2`
and consider the factorization of :math:`m` and :math:`n` into primes,
then :math:`p` occurs an even number of times on the left side of the equation
and an odd number of times on the right, a contradiction.
Note that this argument requires that :math:`n` and hence :math:`m`
are not equal to zero.
The formalization below confirms that this assumption is sufficient.

The unique factorization theorem says that any natural number other
than zero can be written as the product of primes in a unique way.
Mathlib contains a formal version of this, expressed in terms of a function
``nat.factors``, which returns the list of
prime factors of a number in nondecreasing order.
The library proves that all the elements of ``nat.factors n``
are prime, that any ``n`` greater than zero is equal to the
product of its factors,
and that if ``n`` is equal to the product of another list of prime numbers,
then that list is a permutation of ``nat.factors n``.
EXAMPLES: -/
-- QUOTE:
#check nat.factors
#check nat.prime_of_mem_factors
#check nat.prod_factors
#check nat.factors_unique
-- QUOTE.

/- TEXT:
You can browse these theorems and others nearby, even though we have not
talked about list membership, products, or permutations yet.
We won't need any of that for the task at hand.
We will instead use the fact that Mathlib has a function ``nat.factorization``,
that represents the same data as a function.
Specifically, ``nat.factorization n p``, which we can also write
``n.factorization p``, returns the multiplicity of ``p`` in the prime
factorization of ``n``. We will use the following three facts.
BOTH: -/
-- QUOTE:

theorem factorization_mul' {m n : ℕ} (mnez : m ≠ 0) (nnez : n ≠ 0) (p : ℕ) :
  (m * n).factorization p = m.factorization p + n.factorization p :=
by { rw nat.factorization_mul mnez nnez, refl }

theorem factorization_pow' (n k p : ℕ) :
  (n^k).factorization p = k * n.factorization p :=
by { rw nat.factorization_pow, refl }

theorem nat.prime.factorization' {p : ℕ} (prime_p : p.prime) :
  p.factorization p = 1 :=
by { rw prime_p.factorization, simp }
-- QUOTE.

/- TEXT:
In fact, ``n.factorization`` is defined in Lean as a function of finite support,
which explains the strange notation you will see as you step through the
proofs above. Don't worry about this now. For our purposes here, we can use
the three theorems above as a black box.

The next example shows that the simplifier is smart enough to replace
``n^2 ≠ 0`` by ``n ≠ 0``. The tactic ``simpa`` just calls ``simp``
followed by ``assumption``.

See if you can use the identities above to fill in the missing parts
of the proof.
BOTH: -/
-- QUOTE:

example {m n p : ℕ} (nnz : n ≠ 0) (prime_p : p.prime) : m^2 ≠ p * n^2 :=
begin
  intro sqr_eq,
  have nsqr_nez : n^2 ≠ 0,
    by simpa,
  have eq1 : nat.factorization (m^2) p = 2 * m.factorization p,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
    by { rw factorization_pow' },
-- BOTH:
  have eq2 : (p * n^2).factorization p = 2 * n.factorization p + 1,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { rw [factorization_mul' prime_p.ne_zero nsqr_nez, prime_p.factorization',
        factorization_pow', add_comm] },
-- BOTH:
  have : (2 * m.factorization p) % 2 = (2 * n.factorization p + 1) % 2,
  { rw [←eq1, sqr_eq, eq2] },
  rw [add_comm, nat.add_mul_mod_self_left, nat.mul_mod_right] at this,
  norm_num at this
end
-- QUOTE.

/- TEXT:
A nice thing about this proof is that it also generalizes. There is
nothing special about ``2``; with small changes, the proof shows that
whenever we write ``m^k = r * n^k``, the multiplicity of any prime ``p``
in ``r`` has to be a multiple of ``k``.

To use ``nat.count_factors_mul_of_pos`` with ``r * n^k``,
we need to know that ``r`` is positive.
But when ``r`` is zero, the theorem below is trivial, and easily
proved by the simplifier.
So the proof is carried out in cases.
The line ``cases r with r`` replaces the goal with two versions:
one in which ``r`` is replaced by ``0``,
and the other in which ``r`` is replaces by ``r.succ``,
the successor of ``r``.
In the second case, we can use the theorem ``r.succ_ne_zero``, which
establishes ``r.succ ≠ 0``.

Notice also that the line that begins ``have : npow_nz`` provides a
short proof-term proof of ``n^k ≠ 0``.
To understand how it works, try replacing it with a tactic proof,
and then think about how the tactics describe the proof term.

See if you can fill in the missing parts of the proof below.
At the very end, you can use ``nat.dvd_sub'`` and ``nat.dvd_mul_right``
to finish it off.
BOTH: -/
-- QUOTE:
example {m n k r : ℕ} (nnz : n ≠ 0) (pow_eq : m^k = r * n^k)
  {p : ℕ} (prime_p : p.prime) : k ∣ r.factorization p :=
begin
  cases r with r,
  { simp },
  have npow_nz : n^k ≠ 0 := λ npowz, nnz (pow_eq_zero npowz),
  have eq1 : (m^k).factorization p = k * m.factorization p,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
    by rw factorization_pow',
-- BOTH:
  have eq2 : (r.succ * n^k).factorization p =
      k * n.factorization p + r.succ.factorization p,
/- EXAMPLES:
    sorry,
SOLUTIONS: -/
  { rw [factorization_mul' r.succ_ne_zero npow_nz, factorization_pow',
         add_comm] },
-- BOTH:
  have : r.succ.factorization p = k * m.factorization p - k * n.factorization p,
  { rw [←eq1, pow_eq, eq2, add_comm, nat.add_sub_cancel] },
  rw this,
/- EXAMPLES:
  sorry
SOLUTIONS: -/
  apply nat.dvd_sub'; apply nat.dvd_mul_right
-- BOTH:
end
-- QUOTE.

/- TEXT:
There are a number of ways in which we might want to improve on these results.
To start with, a proof that the square root of two is irrational
should say something about the square root of two,
which can be understood as an element of the
real or complex numbers.
And stating that it is irrational should say something about the
rational numbers, namely, that no rational number is equal to it.
Moreover, we should extend the theorems in this section to the integers.
Although it is mathematically obvious that if we could write the square root of
two as a quotient of two integers then we could write it as a quotient
of two natural numbers,
proving this formally requires some effort.

In Mathlib, the natural numbers, the integers, the rationals, the reals,
and the complex numbers are represented by separate data types.
Restricting attention to the separate domains is often helpful:
we will see that it is easy to do induction on the natural numbers,
and it is easiest to reason about divisibility of integers when the
real numbers are not part of the picture.
But having to mediate between the different domains is a headache,
one we will have to contend with.
We will return to this issue later in this chapter.

We should also expect to be able to strengthen the conclusion of the
last theorem to say that the number ``r`` is a ``k``-th power,
since its ``k``-th root is just the product of each prime dividing ``r``
raised to its multiplicity in ``r`` divided by ``k``.
To be able to do that we will need better means for reasoning about
products and sums over a finite set,
which is also a topic we will return to.

In fact, the results in this section are all established in much
greater generality in mathlib,
in ``data.real.irrational``.
The notion of ``multiplicity`` is defined for an
arbitrary commutative monoid,
and that it takes values in the extended natural numbers ``enat``,
which adds the value infinity to the natural numbers.
In the next chapter, we will begin to develop the means to
appreciate the way that Lean supports this sort of generality.
EXAMPLES: -/
#check multiplicity
#check @irrational_nrt_of_n_not_dvd_multiplicity
#check irrational_sqrt_two

-- OMIT:

-- TODO: use this in the later section and then delete here.

#check rat.num
#check rat.denom

section

variable (r : ℚ)

#check r.num
#check r.denom
#check r.pos
#check r.cop

#check rat.num_denom'
#check @rat.num_denom r

end

example (r : ℚ) : r^2 ≠ 2 :=
begin
  rw [←r.num_div_denom, div_pow],
  have : (r.denom : ℚ)^2 > 0,
  { norm_cast, apply pow_pos r.pos },
  have := ne.symm (ne_of_lt this),
  intro h,
  field_simp [this] at h,
  norm_cast at h,
  sorry
end


/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura, Mario Carneiro

Definitions and properties of gcd, lcm, and coprime.
-/
prelude
import init.data.nat.lemmas init.meta.well_founded_tactics

open well_founded

namespace nat

/- gcd -/

def gcd : nat → nat → nat
| 0        y := y
| (succ x) y := have y % succ x < succ x, from mod_lt _ $ succ_pos _,
                gcd (y % succ x) (succ x)

/-* Let $x$ be a natural number, then gcd of 0 and $x$ is $x$. *-/
@[simp] theorem gcd_zero_left (x : nat) : gcd 0 x = x := by simp [gcd]

@[simp] theorem gcd_succ (x y : nat) : gcd (succ x) y = gcd (y % succ x) (succ x) :=
by simp [gcd]

/-* Let $n$ be a natural number, then gcd of 1 and $x$ is 1. *-/
@[simp] theorem gcd_one_left (n : ℕ) : gcd 1 n = 1 := by simp [gcd]

/-* Let $x, y$ be two natural numbers, then gcd of $x$ and $y$ is y if $x = 0$,
or else gcd of $y % x$ and $x$. *-/
theorem gcd_def (x y : ℕ) : gcd x y = if x = 0 then y else gcd (y % x) x :=
by cases x; simp [gcd, succ_ne_zero]

/-* gcd of $n$ and $n$ is $n$. *-/
@[simp] theorem gcd_self (n : ℕ) : gcd n n = n :=
by cases n; simp [gcd, mod_self]

/-* gcd of $n$ and 0 is $n$. *-/
@[simp] theorem gcd_zero_right (n : ℕ) : gcd n 0 = n :=
by cases n; simp [gcd]

/-* gcd of $m$ and $n$ equals to gcd of $(n % m)$ and $m$. *-/
theorem gcd_rec (m n : ℕ) : gcd m n = gcd (n % m) m :=
by cases m; simp [gcd]

@[elab_as_eliminator]
theorem gcd.induction {P : ℕ → ℕ → Prop}
                   (m n : ℕ)
                   (H0 : ∀n, P 0 n)
                   (H1 : ∀m n, 0 < m → P (n % m) m → P m n) :
                 P m n :=
@induction _ _ lt_wf (λm, ∀n, P m n) m (λk IH,
  by {induction k with k ih, exact H0,
      exact λn, H1 _ _ (succ_pos _) (IH _ (mod_lt _ (succ_pos _)) _)}) n

/-* Let $m, n$ be two integers, then lcm of $m$ and $n$ equals to $m * n$ over gcd of $m$ and $n$. *-/
def lcm (m n : ℕ) : ℕ := m * n / (gcd m n)

@[reducible] def coprime (m n : ℕ) : Prop := gcd m n = 1

end nat


/- NATURAL NUMBERS

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

open Nat'

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

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

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

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

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

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

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

/-
LISTS

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

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

-- Let's define something we can't in most other functional languages:
-- a function returning the first element of a list given a proof that is is non-empty! (xs ≠ [] is shorthand for ¬ (xs = []))
-- hint: match on `xs` *before* using `fun` so that the `xs` in the assumption is replaced by the match!
def hd (xs : List α) : xs ≠ [] → α :=
  match xs with
  | []      => fun hf => False.elim (hf rfl)
  | x :: xs => fun _ => x

/-
TREES

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

inductive Tree (α : Type) : Type where
  | leaf : α → Tree α
  | node : α → Tree α → Tree α → Tree α

open Tree

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

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

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

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

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

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

-- Now we prove to facts about this function:
-- First, we prove that it is involutive (mirroring a tree twice returns the original tree).
-- Then, we prove that the mirror image of two trees is equal, if and only if the trees themselves are.
-- Useful lemmas, if you don't want to use ▸ as much (but it is also just as doable with some `have`s and ▸):
#check @Eq.trans
#check @Eq.symm
#check @congrArg

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

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

/- STRUCTURES -/

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

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

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


import algebra.group
import tactic

variables {G : Type*} [group G]

namespace my_group

-- Para ver la traza, descomentar la siguiente línea
set_option trace.simplify.rewrite true

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

lemma mul_inv_rev
  (a b : G)
  : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
inv_eq_of_mul_eq_one_left $ by simp [mul_assoc]

-- Situando el cursor sobre simp se obtiene
--    [mul_assoc]: a * b * (b⁻¹ * a⁻¹) ==> a * (b * (b⁻¹ * a⁻¹))
--    [mul_inv_cancel_left]: b * (b⁻¹ * a⁻¹) ==> a⁻¹
--    [mul_right_inv]: a * a⁻¹ ==> 1
--    [eq_self_iff_true]: 1 = 1 ==> true
--    [mul_assoc]: a * b * (b⁻¹ * a⁻¹) ==> a * (b * (b⁻¹ * a⁻¹))
--    [mul_inv_cancel_left]: b * (b⁻¹ * a⁻¹) ==> a⁻¹
--    [mul_right_inv]: a * a⁻¹ ==> 1
--    [eq_self_iff_true]: 1 = 1 ==> true

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

theorem mul_inv_rev2
  (a b : G)
  : (a * b)⁻¹ = b⁻¹ * a ⁻¹ :=
begin
  apply inv_eq_of_mul_eq_one_right,
  simp only [mul_assoc, mul_inv_cancel_left, mul_right_inv],
end

-- Situando el cursor sobre simp se obtiene
--    [mul_assoc]: a * b * (b⁻¹ * a⁻¹) ==> a * (b * (b⁻¹ * a⁻¹))
--    [mul_inv_cancel_left]: b * (b⁻¹ * a⁻¹) ==> a⁻¹
--    [mul_right_inv]: a * a⁻¹ ==> 1
--    [mul_assoc]: a * b * (b⁻¹ * a⁻¹) ==> a * (b * (b⁻¹ * a⁻¹))
--    [mul_inv_cancel_left]: b * (b⁻¹ * a⁻¹) ==> a⁻¹
--    [mul_right_inv]: a * a⁻¹ ==> 1

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

theorem mul_inv_rev3 (a b : G) : (a * b)⁻¹ = b⁻¹ * a ⁻¹ :=
begin
  apply inv_eq_of_mul_eq_one_right,
  rw [mul_assoc, mul_inv_cancel_left, mul_right_inv],
end

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

theorem mul_inv_rev4 (a b : G) : (a * b)⁻¹ = b⁻¹ * a ⁻¹ :=
begin
  apply inv_eq_of_mul_eq_one_right,
  show (a * b) * (b⁻¹ * a⁻¹) = 1,
  calc (a * b) * (b⁻¹ * a⁻¹)
           = a * (b * (b⁻¹ * a⁻¹)) : by rw mul_assoc
       ... = a * a⁻¹               : by {congr, rw mul_inv_cancel_left}
       ... = 1                     : by rw mul_right_inv
end

end my_group


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

import tactic -- imports all the Lean tactics

/-!

# Basic lemmas

Say `e : X ≃ Y`. We know that `e.to_fun` is one way of getting
access to the underlying function `X → Y`. But Lean gives us
another method. There is a coercion from `X ≃ Y`
to `X → Y`, sending `e` to `e.to_fun`. The notation for it is `⇑e`
but you don't even have to write it. Look:

-/

variables (X Y : Type) (e : X ≃ Y)

example : X → Y := e.to_fun -- works, but isn't the syntax which appears in the lemmas

example : X → Y := e -- works too, and is what you should use

-- Because `e.symm : Y ≃ X` we can get the function from `Y` to `X` thus:
-- (the coercion to a function is automatic)

example : Y → X := e.symm

-- This (using `e` instead of `e.to_fun`) is the notation which is used in mathlib's API for `equiv`.
-- So `mathlib` rewrites our basic facts about these maps using this notation.

example (x : X) : e.symm (e x) = x :=
begin
--  exact e.left_inv x, -- would work, but we don't use these.
  exact equiv.symm_apply_apply e x, -- definitionally equal, but syntactically nicer.
end

-- #check equiv.symm_apply_apply -- uncomment this to see the definition.
-- It's definitionally equiv.left_inv but it uses the preferred syntax, a.k.a. the "simp normal form".

-- and the other way
example (y : Y) : e (e.symm y) = y :=
e.apply_symm_apply y -- begin exact end all cancel out; I also use dot notation

-- A useful tactic below is the `apply_fun` tactic. If `h : x = y` is
-- a hypothesis with `x y : A` and if `f : A → B` then `apply_fun f at h`
-- turns `h` into `h : f(x)=f(y)`.

-- Using `equiv.symm_apply_apply` and `equiv.apply_symm_apply` see if
-- you can prove the below. Note that they're both `simp` lemmas.

-- this is called `e.injective` but can you prove it from first principles?
-- equivalences are injective
example : function.injective (e : X → Y) :=
begin
  intros x₁ x₂ h,
  apply_fun e.symm at h,
  simpa [e.symm_apply_apply] using h,
end

-- this is `equiv.surjective` but can you prove it from first principles?
example : function.surjective (e : X → Y) :=
begin
  intro y,
  use e.symm y,
  exact e.apply_symm_apply y,
end

/-

Here's a cute challenge. Say `G` and `H` are groups,
and `e : G ≃ H` has the property that the forward map `G → H`
is a group homomorphism. Can you prove that the inverse map `H → G`
is also a group homomorphism?
-/

example (G H : Type) [group G] [group H] (e : G ≃ H)
  (he : ∀ a b : G, e (a * b) = e a * e b) :
  ∀ x y : H, e.symm (x * y) = e.symm x * e.symm y :=
begin
  intros x y,
  apply e.injective,
  -- rw [he, e.apply_symm_apply, e.apply_symm_apply, e.apply_symm_apply],
  -- or just
  simp [he],
end


import topology.basic
import topology.metric_space.basic

section
open metric

-- Open balls in an ultrametric space are closed
example {X : Type*}[metric_space X] (x : X) (r : ℝ) (r_pos : r > 0) (strong_ti : ∀ x y z : X, dist x z ≤ max (dist x y) (dist y z)) :
is_closed (ball x r) :=
begin
  -- To show B(x, r) is closed, it suffices to show its complement is open
  suffices : is_open (ball x r)ᶜ,
      { rwa ← is_open_compl_iff },
  -- That is, for all y ∈ X \ B(x, r), there is an B(y, ε) ⊆ X \ B(x, r)
  suffices : ∀ y ∈ (ball x r)ᶜ, ∃ ε > 0, (ball y ε) ⊆ (ball x r)ᶜ,
      { rwa is_open_iff },
  intros y y_not_in,      -- We let y be arbiturary with y ∈ X \ B(x, r),
                          -- and we want to show B(y, ε) ⊆ X \ B(x, r)
  use [r, r_pos],         -- We claim that ε = r works. That is, B(y, r) ⊆ X \ B(x, r)
  intros z hz,            -- To show this, we let z ∈ B(y, r), and we wts z ∈ X \ B(x, r),
  change ¬ dist z x < r,  -- that is, to show it is not the case that d(x, z) < r.
  by_contradiction H,     -- We preceed by contradiction
                          -- (directly we know that d(x,y) ≤ max{d(x,z), d(y,z)},
                          --  and d(x,y) ≥ r, d(y,z) < r, so we must have d(x,z) ≥ r,
                          --  but to me the above argument is not as easy to write :/ )
  have hc : dist y x < r :=   -- by our new assumption, we can show d(y,x) < r:
    calc dist y x ≤ max (dist y z) (dist z x) : strong_ti y z x
              ... = max (dist z y) (dist z x) : by rw dist_comm y z
              ... < r                         : max_lt hz H,
  exact y_not_in hc,      -- And that contradicts with the hypothesis that y ∈ X \ B(x, r)
end

-- Closed balls in an ultrametric space are open
example {X : Type*}[metric_space X] (x : X) (r : ℝ) (r_pos : r > 0) (strong_ti : ∀ x y z : X, dist x z ≤ max (dist x y) (dist y z) ):
is_open (closed_ball x r) :=
begin
  -- Our goal is to show the closed ball B̅(x, r) is open,
  -- so it suffices to show that for all y ∈ B̅(x, r), there exists B(y, ε) ⊆ B̅(x, r)
  suffices : ∀ y ∈ (closed_ball x r), ∃ ε > 0, (ball y ε) ⊆ (closed_ball x r),
      { rwa is_open_iff /- The theorem `is_open_iff` proves the above claim -/ },
  intros y y_in,  -- Let y be arbiturary with the assumption that y ∈ B̅(x, r)
                  -- Then we want to find an ε that satisfies B(y, ε) ⊆ B̅(x, r)
  use [r, r_pos], -- We claim that ε = r works.
  intros z z_in,  -- To show that, we let z ∈ B(y, ε),
                  -- and we preceed to show that z ∈ B̅(x, r), that is, d(z, x) ≤ r.
  calc dist z x ≤ max (dist z y) (dist y x) : strong_ti z y x
            ... ≤ max r r                   : max_le_max (le_of_lt z_in) y_in
            ... = r                         : max_self r
end

end -- Ends the section

-- If X Y are topological spaces, Y is Hausdorff, f and g are continuous functions from X to Y,
-- then the set {x ∈ X : f x = g x} is closed.
example {X Y : Type*}[topological_space X][topological_space Y][t2_space Y]{f g : X → Y} :
continuous f → continuous g → is_closed {x : X | f x = g x} :=
begin
  intros hfcts hgcts,
  suffices : is_open {x : X | f x ≠ g x}, { rwa ← is_open_compl_iff },
  rw is_open_iff_forall_mem_open,
  intros x x_in,
  rcases t2_separation (x_in) with ⟨U, V, U_open, V_open, fx_in_U, gx_in_V, UdisjV⟩,
  use (f ⁻¹' U) ∩ (g ⁻¹' V),
  repeat {split},
  { -- (f ⁻¹' U) ∩ (g ⁻¹' V) is contained in the set {x : X | f x ≠ g x}
    show (f ⁻¹' U) ∩ (g ⁻¹' V) ⊆ {x : X | f x ≠ g x},
    intros x' x'_in,
    rw set.mem_inter_iff at x'_in,
    repeat {rw set.mem_preimage at x'_in},
    exact disjoint.ne_of_mem UdisjV x'_in.left x'_in.right },
  { -- (f ⁻¹' U) ∩ (g ⁻¹' V) is open
    show is_open ((f ⁻¹' U) ∩ (g ⁻¹' V)),
    exact is_open.inter (is_open.preimage hfcts U_open) (is_open.preimage hgcts V_open) },
    -- x ∈ f⁻¹(U) and x ∈ f⁻¹(V)
  repeat { rwa set.mem_preimage },
end


import data.set

universes u v   -- Universe levels, don't worry

/-
Homework #6: Functions, Sets, Relations
-/

/- #1. Basic Functions [35 points]-/

/- A [5 points].

Write a function, isZero, using "by cases" syntax,
that takes any natural number, n, as an argument and
returns a Boolean result, true if n = 0 and false if
n > 0 (i.e., n = n' + 1 = (nat.succ n') for some n').
(Don't get distracted by unnecessary details.)
-/

-- Answer

-- These test cases should pass except the last
example : isZero 0 = tt := rfl
example : isZero 1 = ff := rfl
example : isZero 2 = ff := rfl
example : isZero 3 = tt := rfl    -- tester tester

/- B [10 points].

Here's a function, fib, using "by cases" syntax, that
takes any natural number, n, as an argument and that
returns the n'th Fibonacci number. The n'th Fibonacci
number is defined by cases: when n is 0, it's 0; when
n is 1 (nat.succ nat.zero), it's 1, otherwise, in the
case where n = n' + 2, it's fib n' + fib n'+1
-/

def fib : ℕ → ℕ
| 0 := 0
| 1 := 1
| (n' + 2) := _

-- These test cases should pass except the last
example : fib 0 = 0 := rfl
example : fib 1 = 1 := rfl
example : fib 2 = 1 := rfl
example : fib 3 = 2 := rfl
example : fib 4 = 3 := rfl
example : fib 5 = 5 := rfl
example : fib 10 = 55 := rfl
example : fib 2 = 2 := rfl    -- tester tester

/- C [10 points]
Write a function, add, that takes two natural
numbers and computes their sum. You may *not*
use the addition function that Lean defines in
its libraries. Your addition function should
be defined by cases. Given arguments n and m,
if m is zero, just return n; otherwise, if m
is (m' + 1), return the sum of n and m' plus
one. (That's still the right answer, right!)
-/

def add : ℕ → ℕ → ℕ
| n 0 := n
| n (m' + 1) := _

-- These test cases should pass except the last
example : add 0 0 = 0 := rfl
example : add 5 0 = 5 := rfl
example : add 0 5 = 5 := rfl
example : add 1 1 = 2 := rfl
example : add 1 2 = 3 := rfl
example : add 2 2 = 4 := rfl
example : add 3 2 = 4 := rfl  -- tester tester

/- D [10 points]

A function is said to be involutive, or an
involution, if applying it twice to *any* value
(of the right type) returns that original value.
For example, if f is a function, and a is any
argument, then f is involutive if f (f a) = a.
Formally define involutive as a property of
any function, f : α → α.
-/

def involutive {α : Sort u} (f : α → α) :=
  _

/-
Now prove the proposition that the Boolean negation
function, called bnot in Lean, is an involution. The
trick here is simply to pick the right proof strategy.
Ask, how can I prove it? Hint: It's just a bool!
-/

example : involutive bnot :=
begin
end

/- 2 Sets [35 points] -/

/- A [5 points].

Formally define a predicate, perfectSquare,
satisfied by any natural number that is the
square of some other natural number. 25 is a
perfect square, for example, because it's the
square of the natural number 5. Fill in both
underscores in the following skeleton to give
your answer. Remember: Predicates take values
to propositions.
-/

def perfectSquare (n : ℕ) : Prop := _

/- B [5 points].

Define the set of all perfect squares using
set comprehension (set builder) notation and
the perfectSquare predicate.
-/

def perfectSquares : set ℕ := _

/- C [5 point].

State and prove the proposition that 25 is in
the set of perfect squares. Use set membership
notation in writing your proposition.
-/

example : _ :=
begin
end

/- D [20 points].
Consider the following sets of natural numbers:

X = { 2, 3, 4}
Y = { 4, 5, 6}
-/

def X : set ℕ := { 2, 3, 4 }
def Y : set ℕ := { 4, 5, 6 }

/-
Formally state and prove the following propositions
using set and set operator notations.

1. 4 is "in" the intersection of X and Y
2. 4 is in the union of X and Y
3. 4 is not in the set difference, X \ Y
4. 10 is in the complement of X
-/

-- 1. intersection
example : _ :=
begin
_
end

-- 2. union
example : _ :=
begin
end

-- 3. difference
/-
Hint: At some point, you might want to
use the "obviously" true fact that 4 ∈
{4, 5, 6} without stopping to prove it.
Here's a nice trick:

let n : 4 ∈ {4, 5, 6} := _

This will put the assumption, n, in your
context, but with a "note attached" saying
that you still need a proof of it (that is
you get a new subgoal). Don't worry about
the crazy-looking goals after you use this
hint (if you do). Just look at the top-level
goal and proceed accordingly.
-/

-- 3. difference
example : _ :=
begin
_
end

-- 4. complement
example : _ :=
begin
_
end

/- 3. Relations [30 points] -/

/- A [15 points].

In this problem, you will formally define
what it means for a relation to be single-
valued, you will define a particular relation,
and you will prove that it is single-valued.

Part #1 [5 point].

Formally define single-valuedness for any
binary relation r : α → β → Prop so that r
being single valued means that (r x) can
have (be satisfied by) two values, say y
and z, only if y = z. Think about this hard
enough that it really makes sense to you!
In other words, if r x y ... You can fill
in the rest!
-/

def single_valued   -- predicate on relations
{α β : Type}
(r : α → β → Prop)
: Prop := _

/- Part #2 [5 points]

Define a relation, sqrs : ℕ → ℕ → Prop, where
sq x y is satisfied iff y is the square of x.
-/

def sqrs : ℕ → ℕ → Prop
| x y := _

/- Part #3 [5 points]

Now formally prove the proposition that the
sqrs relation is single-valued.
-/

example : single_valued sqrs :=
begin
_
end

/- B [5 points]

A mathematical function, f : α → β, is said
to be injective if it never relates different
α (first) values to the same β (second) value.
The only way an injective function f can relate
a to x and also b to x, is if a = b. A function
that satisfies this constraint is injective.
-/

def injective
(α : Sort u)        -- any type from any universe
(β : Sort v)        -- any type from any universe
(r : α → β → Prop)  -- any relation on α and β
(a b : α)           -- any two arbitrary α values
(x : β) :           -- any β value
Prop := _

/- C [10 points].

A mathematical function, f : α → β, is said to be
a total function if it's defined (there is some
β value) for *all* α values: ∀ (a : α), β. Indeed,
this is the meaning of the notation α → β in Lean
and similar logics. The upshot is that functions
in Lean can only be total.  Complete the following
proof that every function in Lean is total.
-/

example :
  ∀ (α β : Type)
  (f : α → β),
    ∀ (a : α),
      ∃ (b : β),
        f a = b :=
begin
_
end