/-
The purpose of this lecture/assignment is to
develop and deepen your understanding of the
lofical concepts covered so far: equality of
values/terms of any type, and conjunction of
propositions. We present the embedding of the
∨ connective of predicate logic into Lean and
elaborate on the representation of relations,
such as equality, as inductive families of
propositions along with proof-building and
proof-consuming inference rules for them, in
Lean.
-/

/-
Compare and contrast the distinct ways that
we represented these ideas in Lean.

- inductive eq  { α : Sort u }, α → α → Prop
- inductive and (α β : Prop) : Prop

Each of "eq" and and builds "propositions",
but they build them in different ways.

For any given type, eq takes two *values* of
that type, yields a proposition "about" those
values, and provides constructors that define
the only possible proofs of a proposition of
this kind.

By contrast, "and" takes two propositions, i.e.,
*types, not values*, as its arguments, reduces to
another proposition as a result, namely (and P Q),
also written P ∧ Q, and also provide a constructor
(introduction axiom) for building proofs of this
type.

Popping into the everyday language of programming
languages, we can say that "and" is a polymorphic
type; but, by contrast eq, defines a family of binary
(equality) relations *indexed by an arbitrary type*,
α. There's thus a separate binary equality relation
on terms for each distinct type, (α : Sort u).
-/

/-
Ex 1: Embed the concept of the logical or (∨)
connective into Lean in the same style we used
to embed ∧. To do this we'll need to represent
the connective as a family of propositions of
the form P ∨ Q, parameterized by propositions,
P and Q. Then we'll need to define introduction
and elimination rules for ∨ that capture the
intended meaning of this connective: that it's
true (and there's a proof of that) if at least
one of P or Q is true (has a proof).
-/

namespace hidden

/-
Represent ∨ as a family of propositions,
α ∨ β, parameterized by (polymorphic in)
two propositions, α and β, where such a
proposition, α ∨ β has a proof, iff at
least one of α and β has a proof. That is,
we must be able to construct a proof of
α ∨ β from a proof of α or from a proof
of β. We define the introduction rules
for "or" accordingly.
-/
inductive or (α β : Prop) : Prop
| inl (a : α) : or
| inr (b : β) : or

/-
Now we can state and prove disjunctive
(or) propositions. To do so at least one
"disjuct" has to be true, and we have to
pick one and apply the right constructor.
In the following example, it would not
do to use or.intro_right because there
is no proof of that proposition.
-/
example : or (3=3) (3=4) :=
or.inl rfl

/-
Similarly here it wouldn't do to use
or.intro_left, again because the left
disjunct, 3=4, doesn't have a proof.
-/
example : or (3=4) (3=3) :=
or.inr rfl

/-
In this example, we could use either
the left or right introduction rules.
There are thus two distinct proofs in
this case.
-/

lemma left_proof : or (3=3) (4=4) := or.inl rfl
lemma right_proof : or (3=3) (4=4) := or.inr rfl

-- You can see that these are different proofs
#reduce left_proof
#reduce right_proof

/-
Both of these proofs are of type "or (3=3) (4=4)"
and you can see that they're different. And yet,
as you can see next, Lean treats them as equal!
This behavior implements the concept of "proof
irrelevance." Lean enforces the notion that any
proof of a given proposition "will do" by treating
all proofs of the same proposition as being equal.
-/
example : left_proof = right_proof := rfl

end hidden

/-
Now let's see the same ideas using Lean's own
definitions (from its libraries) of "or" and
its related introduction rules.
-/
#check @or
#check or (3=3) (1=0)

example : 3 = 3 ∨ 1 = 0 :=
or.inl rfl

example : 1 = 0 ∨ 3 = 3  :=
or.inr rfl

/-
In the preceding examples, Lean knows
what proposition is to be proved because
it's stated explicitly in each case (right
after the colons). Here's a case where we
leave out the proposition to be proved and
where Lean can't infer it.
-/

-- can't infer second disjunct (return type)
lemma ex1 := or.inl (eq.refl 3)

/-
Here, the term, or.inl (eq.refl 3), is a
proof of "(3=3) ∨ ___", but we have no way
of knowing what that something, the second
disjuct, is. In such cases, we can use an
alternative introduction function, called
or.intro_left, where we specify the right
conjunct explicitly (first argument), and
give a proof of the left, from which the
left conjunct is inferred.
-/

#check or.intro_left (4=5) (eq.refl 3)
--  right proposition ^^^   ^^^^ left proof
#check or.intro_right (4=5) (eq.refl 3)
--    left proposition ^^^   ^^^^ right proof

/-
NEW STUFF: Elim rule for or
-/

#check @or.elim

/-
or.elim : ∀ {a b c : Prop}, a ∨ b → (a → c) → (b → c) → c
-/

axioms
  (Person : Type)
  (Tom : Person)
  (FromCville FromRichmond FromVirginia: Person → Prop)
  (pf : FromCville Tom ∨ FromRichmond Tom)
  (cv : ∀ {p :Person}, FromCville p → FromVirginia p)
  (rv : ∀ {p :Person}, FromRichmond p → FromVirginia p)

  theorem TomIsFromVa : (FromVirginia Tom) :=
  begin
    cases pf,
    exact cv h,
    exact rv h,
  end

  /-
  Properties as single-argument predicates
  -/

  def ev' : ℕ → Prop := λ n, n%2 = 0

inductive ev : ℕ → Prop
| ev_0 : ev 0
| ev_2 : ∀ (n : ℕ), ev n → ev (n + 2)

example : ev 0 := ev.ev_0

example : ev 2 :=
begin
  apply ev.ev_2 _ _,
  exact ev.ev_0,
end

example : ev 8 :=
begin
  apply ev.ev_2 6 _,
    {apply ev.ev_2 4 _,
    apply ev.ev_2 2 _,
    apply ev.ev_2 0 _,
    exact ev.ev_0,}
end

example : ev 1000 :=
begin
  repeat {apply ev.ev_2},
  exact ev.ev_0,
end

inductive lte : ℕ → ℕ → Prop
| base : ∀ {n : ℕ}, lte n n
| step : ∀ {n m : ℕ}, lte n m → lte n (nat.succ m)

example : lte 3 3 := lte.base
example : lte 3 4 :=
begin
  apply lte.step,
  exact lte.base,
end

example : lte 3 6 :=
begin
  apply lte.step,
  apply lte.step,
  apply lte.step,
  exact lte.base,
end

#check nat.le

/-
The propositions, true and false.
-/

namespace hidden

inductive false : Prop
/-
inductive empty : Type
-/

inductive true : Prop
| intro
/-
inductive unit : Type
| star
-/

example : true := true.intro
example : false := _

end hidden
/-
#2. Define an inductive family of propositions
to specify the successor relationship, succ_rel,
between pairs of natural numbers. Given any two
natural numbers, n and m, (n, m) ∈ succ_rel if
and only if m = n + 1. Then state and prove the
lemma, (0,1) ∈ succ_rel.
-/

/-
Before specifying this relation logically, let's
write a *computational* definition. (As a review
of syntactic alternatives for defining functions,
we give three variants.)
-/
def incr (n : ℕ) : ℕ := n + 1
def incr' : ℕ → ℕ := λ (n : ℕ), n + 1
def incr'' : ℕ → ℕ | n := n + 1

/-
The wonderful thing about function definitions in
Lean and other programming languages, is that they
compute. You can apply a function to an argument
and the machine will derive the result. Here for
example, we apply the incr function to the actual
parameter 7. To reduce this application expression,
Lean fetches the body of the function, substitutes
the 7 for each occurence of n, yielding (7 + 1) in
this case. It then reduces that expression, giving
8, as a final result. In this sense, we *computed*
that the pair, (7, 8), belongs to the relation.
-/
#reduce incr 7

/-
By contrast, we *specify* the relation "declaratively"
by giving rules that can be used to prove (or not) that
a given pair is in the relation. The rule in this case
is succ_rel.intro. What it specifies is that if one is
given any two natural numbers, n and m, along with a
proof, h, that (m = n + 1) then (succ_rel n m h) is a
a proof of succ_rel n m.
-/
inductive succ_rel : ℕ → ℕ → Prop
| intro (n m : ℕ) (h : m = n + 1) : succ_rel n m

/-
Note that applying succ_rel to n and m, say 3 and 4,
just returns the *proposition*, (n,m) is in succ_rel.
You can't apply succ_rel to 3 and expect to get a 4
in return as is possible with the incr functions we
defined above.
-/
#check succ_rel 3 4
#check @succ_rel.intro

/-
Rather, with such a declarative specification in hand,
what we can do is to try to prove that certain pairs
of numbers are in this relation. Next, for example, we
first prove that (3, 4) is in this relation, and then
we see that we get stuck if we try to prove that the
pair, (3,5), satisfies the specification of succ_rel.

-/

example : succ_rel 3 4 := succ_rel.intro 3 4 rfl
example : succ_rel 3 5 := succ_rel.intro 3 5 rfl

/-
We've now seen that we can specify a mathematical
function either computationally, as a function, or
declaratively, as a relation. Two questions arise.
First, are these definitions mathematically equivalent?
Second, why would we ever want to use a declarative
specification rather than a "computable" function?
-/

/-
To answer the first question, we formulate the claim
that the two definitions are equivalent as a formal
proposition, and we prove it. We claim and show that
(n,m) ∈ succ_rel if and only if m = incr m. This is
your first example of a kind of program verification,
wherein we prove that a function that we can run will
always give us the answer that the declarative spec
says it should give us.
-/
theorem def_equivalent : ∀ (n m : ℕ), (m = incr n) ↔ succ_rel n m :=
begin
  intros,
  apply iff.intro _ _,  -- hint: try "split" instead

  -- forward
  assume h,
  rw h,
  unfold incr,
  apply succ_rel.intro,
  trivial,
  --
  assume h,
  cases h,
  unfold incr,
  assumption,
end

/-
Yay! Now for the second question: why would we ever
want to use a declarative specification of a binary
relation rather than a function that we can "run" to
get the second element of any pair from the value of
the first?

The answer is that functions in Lean are total, so
if we want to represent partial functions, we need
to use declarative specifications.

To make this idea concrete, consider the partial
function, F, that is exactly the identity function
on natural numbers except that it's undefined for
the input, zero. The mathematical function, F, is
of type ℕ → ℕ, but we can't implement it properly in
Lean as a function, say f1, of type ℕ → ℕ, because
what this type really means is ∀ (n : ℕ), ℕ, which
is to say that it has to return a value of type ℕ
*for any* argument, n, including 0.

There are some workarounds, but none is perfect.
Let's look at a few work-arounds.
-/

/-
Alternative #1. Just use the identify function and
leave the programmer resposible for never applying
it to the value, zero.
-/
def f1 : ℕ → ℕ := λ n, n

/-
Alternative #2. For zero, return an unusual value
as a sort of error flag. This solution also demands
that the programmer understand the "hack" that we're
using here. Clearly g doesn't *really* represent the
partial function
-/
def f2 : ℕ → ℕ :=
  λ n, if (n = 0) then 1000 else n

#reduce f2 2
#reduce f2 1
#reduce f2 0

/-
Alternative #3: Use an option return type. Now we
have an implementation that is more faithful to the
behavior of our mathematical function, F, but at
the cost of (1) changing the type from ℕ → ℕ to
ℕ → option ℕ, and (2) forcing the user of f3 always
to check whether they received an error (none) or
a normal return value (some n).
-/
def f3 : ℕ → option ℕ :=
  λ n, if (n = 0) then none else some n

def demo_f3 : ℕ → string :=
λ n,
  let r := f3 n in      -- temporarily bind r to result
  match r with          -- error check: case analysis on r
    | none := "error"   -- if "none", return "error" string
    | some n := "good"  -- else return "good" string
  end

#eval demo_f3 2
#eval demo_f3 1
#eval demo_f3 0

/-
Whereas f3 requires the caller to check for an
error *after* calling the function, we can also
add an argument to our implementation requiring
the programmer to show *before* the function is
called that the argument is good.
-/
def f4 : ∀ (n : ℕ), (n ≠ 0) → ℕ := λ n h, n

/-
The proof argument establishes a "precondition"
for f4. One cannot even call it without having
a proof that n≠0.  In each of the following
examples, we apply f4 to n and a proof of n≠0,
and we provide the proof argument as the result
of a simple tactic script. In the final case,
of n = 0, you will see that we can't provide
that second proof argument, which prevents us
from calling the function without getting an
error.
-/

#reduce f4 2 begin assume k, cases k, end
#reduce f4 1 begin assume k, cases k, end
#reduce f4 0 begin assume k, cases k, end

/-
In the last case we're prevented from
calling the function properly by the
non-existence of the required proof
argument.

This solution shows that in Lean we can
incorporate precondition specifications
and a kind of precondition checking by
defining functions that require proofs
be given as arguments to show that the
required conditions for using a function
are satisfied.
-/

/-
For the sake of completeness, another
similar solution is based on the notion
of a subtype in Lean. Here we define a
subtype, positive, of the natural numbers,
then we use pos as the argument type.
-/

def positive : Type := { n : ℕ // n ≠ 0 }

/-
A value of this type is a pair, comprising
a natural number, n, and a proof that n ≠ 0.
We then implement F as a total function from
positive to ℕ, knowing that if we do get a
value of type, positive, as input, that it's
a number and a proof that that number is not
zero. So we just extract the number from the
pair and return it.

But this is still not perfect. The domain
of F is ℕ, even though the domain on which
it's defined (it's domain of definition) does
not include 0. So mathematically speaking, F
is a partial function from ℕ to ℕ, not a total
function from positive to nat. Our code does
not quite express the nature of the concept
(F) that we're implementing.
-/

def f5 : positive → ℕ :=
  λ n, match n with
    | ⟨ n, pf ⟩ := n
  end

/-
In many cases, the most practical solution will
be to switch from a "computable" function to a
declarative specification of a relation. That is
what we've done in this file.
-/

inductive F : ℕ → ℕ → Prop
| intro : ∀ (n m : ℕ), (n≠0) → (n=m) → F n m

/-
Now instead of computing m from n in any given
case, we assert and prove that a given m relates
to a given n. And of course we require that n≠0
in any case.
-/

-- (2,2) is in the identity relation on pos nats
example : F 2 2 :=
  F.intro 2 2 (begin intro h, cases h end) rfl

-- (1,1) is in the identity relation on pos nats
example : F 1 1 :=
  F.intro 1 1 (begin intro h, cases h end) rfl

-- (1,2) is not in the relation: the check that m=n fails
example : F 1 2 :=
  F.intro 1 2 (begin intro h, cases h end) rfl

-- (0,0) is not in the relation: the check that n≠0 fails
example : F 0 0 :=
  F.intro 0 0 (begin intro h, cases h end) rfl


import data.int.modeq

/-
Show that every square is congruent to 0, 1, or -1 modulo 5, and is congruent to 0, 1, or 4 modulo 8.

Suppose n is a positive integer such that 2n + 1 and 3n + 1 are squares. Prove that n is divisible by 40.

Find a value of n such that 2n + 1 and 3n + 1 are squares. Can you find another value? (Calculators allowed!)
-/

lemma sq_mod_five (n : ℤ) : n^2 ≡ 0 [ZMOD 5] ∨ n^2 ≡ 1 [ZMOD 5] ∨ n^2 ≡ -1 [ZMOD 5] :=
begin
  sorry
end

lemma sq_mod_eight (n : ℤ) : n^2 ≡ 0 [ZMOD 8] ∨ n^2 ≡ 1 [ZMOD 8] ∨ n^2 ≡ 4 [ZMOD 8] :=
begin
  sorry
end

lemma div_by_forty (n : ℤ) (hn : n > 0) (h₁ : ∃k : ℤ, k^2 = 2 * n + 1) (h₂ : ∃l : ℤ, l^2 = 3 * n + 1) : 40 ∣ n :=
begin
  sorry
end

lemma exist_sq : ∃n : ℤ, n > 0 ∧ (∃k : ℤ, k^2 = 2 * n + 1) ∧ (∃l : ℤ, l^2 = 3 * n + 1) :=
begin
  sorry
end


-- Un número es par syss lo es su cuadrado
-- =======================================

import data.int.parity
open int

variable (n : ℤ)

-- ----------------------------------------------------
-- Ejercicio. Demostrar que un número es par syss lo es
-- su cuadrado.
-- ----------------------------------------------------

-- 1ª demostración
example :
  even (n^2) ↔ even n :=
begin
  split,
  { contrapose,
    rw ← odd_iff_not_even,
    rw ← odd_iff_not_even,
    unfold odd,
    intro h,
    cases h with k hk,
    use 2*k*(k+1),
    rw hk,
    ring, },
  { unfold even,
    intro h,
    cases h with k hk,
    use 2*k^2,
    rw hk,
    ring, },
end

-- 2ª demostración
example :
  even (n^2) ↔ even n :=
begin
  split,
  { contrapose,
    rw ← odd_iff_not_even,
    rw ← odd_iff_not_even,
    rintro ⟨k, rfl⟩,
    use 2*k*(k+1),
    ring, },
  { rintro ⟨k, rfl⟩,
    use 2*k^2,
    ring, },
end

-- 3ª demostración
example :
  even (n^2) ↔ even n :=
iff.intro
  ( have h : ¬even n → ¬even (n^2),
      { assume h1 : ¬even n,
        have h2 : odd n,
          from odd_iff_not_even.mpr h1,
        have h3: odd (n^2), from
          exists.elim h2
            ( assume k,
              assume hk : n = 2*k+1,
              have h4 : n^2 = 2*(2*k*(k+1))+1, from
                calc  n^2
                    = (2*k+1)^2       : by rw hk
                ... = 4*k^2+4*k+1     : by ring
                ... = 2*(2*k*(k+1))+1 : by ring,
              show odd (n^2),
                from exists.intro (2*k*(k+1)) h4),
        show ¬even (n^2),
          from odd_iff_not_even.mp h3 },
    show even (n^2) → even n,
      from not_imp_not.mp h )
  ( assume h1 : even n,
    show even (n^2), from
      exists.elim h1
        ( assume k,
          assume hk : n = 2*k ,
          have h2 : n^2 = 2*(2*k^2), from
            calc  n^2
                = (2*k)^2   : by rw hk
            ... = 2*(2*k^2) : by ring,
          show even (n^2),
            from exists.intro (2*k^2) h2 ))


inductive Expr where
  | const : Nat → Expr
  | var : Nat → Expr
  | plus : Expr → Expr → Expr
  | times : Expr → Expr → Expr
  deriving Repr

open Expr

def sampleExpr : Expr := plus (times (var 0) (const 7)) (times (const 2) (var 1))

def eval (v : Nat → Nat) : Expr → Nat
  | const n     => n
  | var n       => v n
  | plus e₁ e₂  => (eval v e₁) + (eval v e₂)
  | times e₁ e₂ => (eval v e₁) * (eval v e₂)

def sampleVal : Nat → Nat
  | 0 => 5
  | 1 => 6
  | _ => 0

#eval (eval sampleVal sampleExpr)

def simpConst : Expr → Expr
  | plus (const n₁) (const n₂)  => const (n₁ + n₂)
  | times (const n₁) (const n₂) => const (n₁ * n₂)
  | e                           => e

def fuse : Expr → Expr
 | plus e₁ e₂ => simpConst (plus (fuse e₁) (fuse e₂))
 | times e₁ e₂ => simpConst (times (fuse e₁) (fuse e₂))
 | e => e

def anotherExpr : Expr := times (plus (const 1) (const 2)) (plus (const 5) (var 0))

#eval (fuse anotherExpr)

theorem simpConst_eq (v : Nat → Nat) : ∀ e : Expr, eval v (simpConst e) = eval v e := by sorry

theorem fuse_eq (v : Nat → Nat) : ∀ e : Expr, eval v (fuse e) = eval v e := by sorry


import lib.m154
open m154

/- # Le quantificateur existentiel

Dans ce fichier, on aborde la manipulation du quantificateur existentiel.
Pour démontrer un énoncé de la forme `∃ x, P x`,
on fournit un `x₀` qui convient à l'aide de la commande `Montrons que x₀ convient`.
Ce `x₀` peut être un objet du contexte, une constante ou une expression plus
compliquée. Ensuite il faut démontrer l'énoncé `P x₀`.
-/
example : ∃ x : ℝ, x*x = 4 :=
begin
  Montrons que -2 convient,  -- Est-ce la seule possibilité ?
  On calcule,
end

example : ∃ x : ℝ, x*x = 16 :=
begin
  sorry
end

/-
Pour utiliser un énoncé de la forme `h : ∃ x, P x`, on utilise la commande
`Par h on obtient x₀ tel que hx₀ : P x₀`
qui fixe un `x₀` et une hypothèse `hx₀` affirmant `P x₀`.

Ici `h` peut être directement un énoncé du contexte ou une expression plus
compliquée. On peut aussi utiliser la forme
`Par h appliqué à args on obtient ...` où `args` est un argument ou une liste d'arguments.
-/

example (n : ℕ) (h : ∃ n', n = n' + 1) : n > 0 :=
begin
  Par h on obtient n' tel que hn' : n = n' + 1,
  On réécrit via hn',
  On calcule,
end

example (n : ℕ) (h : ∃ n', n = n' + 3) : n > 0 :=
begin
  sorry
end

/-
Il faut bien noter qu'en général il y a plusieurs `x` qui conviennent. Dans
l'exemple suivant, il y en a deux (sauf si y est nul).
Pour insister sur le fait qu'on en a fixé un, on l'appelle `x₀`.
On utilise aussi les lemmes `pos_pos` et `neg_neg` de la feuille 2, et le lemme
`le_total` de la feuille 6.
-/
example (y : ℝ) (h : ∃ x, y = x*x) : y ≥ 0 :=
begin
  Par h on obtient x₀ tel que hy₀ : y = x₀*x₀,
  On réécrit via hy₀,
  On discute en utilisant (le_total 0 x₀ : 0 ≤ x₀ ∨ 0 ≥ x₀),
    Supposons h' : 0 ≤ x₀,
    On conclut par pos_pos appliqué à [h', h'],
  Supposons h' : 0 ≥ x₀,
  On conclut par neg_neg appliqué à [h', h'],
end

/-
Les exercices suivants utilisent la relation de divisibilité dans ℤ
(attention à la barre de divisibilité qui n'est pas celle du clavier
mais obtenue par ,|).
Contrairement au contexte de la feuille 4, le seul lemme utile est ici
`divise_def (a b : ℤ) : a ∣ b ↔ ∃ k, b = a*k`
-/

-- Dans toute la suite, a b et c désignent des entiers relatifs
variables (a b c : ℤ)

/-
Dans des démonstrations un peu longues, il peut être utile de rappeler au lecteur
ce que « convient » signifie dans dans « Montrons que x₀ convient ».
Pour cela on peut écrire : `Montrons que x₀ convient : P x₀` comme dans l'exmple suivant.

Cet exemple illustre aussi la commande `On renomme` qui permet de renommer une variable
liée dans le but. On peut aussi renommer dans une hypothèse `hyp` en terminant
la commande par `dans hyp`. Ce renommage n'est jamais utile pour Lean, mais permet
de gagner en lisibilité.
-/

example (h1 : a ∣ b) (h2 : b ∣ c) : a ∣ c :=
begin
  Par h1 on obtient k tel que hk : b = a*k, -- (1)
  Par h2 on obtient l tel que hl : c = b*l, -- (2)
  -- Pour montrer que a ∣ c, il suffit de trouver m tel que c = a*m
  On réécrit via divise_def,
  -- Comme nous avons déjà fixé un entier nommé `k`, le nom de la variable muette
  -- dans le but est perturbant.
  On renomme k en m,

  Montrons que k*l convient : c = a * (k * l),
  calc c = b*l     : by On conclut par hl -- par (2)
     ... = (a*k)*l : by On réécrit via hk -- par (1)
     ... = a*(k*l) : by On calcule,
end

/-
Dans la démonstration précédente, la commande `On renomme ... en ... (dans ...)`
est purement psychologique, elle renomme une variable liée pour éviter
un conflit apparent avec le k fixé dans le contexte.

En fait l'utilisation du lemme divise_def est aussi psychologique, Lean
sait que c'est la définition de la divisibilité.
Vous pouvez essayer de commenter ces deux lignes psychologiques en ajoutant
« -- » en début de ligne.

Voyons maintenant un autre énoncé semblable.
-/

example (h1 : a ∣ b) (h2 : a ∣ c) : a ∣ b+c :=
begin
  sorry
end

example : 0 ∣ a ↔ a = 0 :=
begin
  sorry
end

/-
L'exercice suivant utilise les définitions « pair » et « impair »

`def pair (n : ℤ) := ∃ k, n = 2*k`
`def impair (n : ℤ) := ∃ k, n = 2*k + 1`

et le lemme :

`pair_ou_impair (n : ℤ) : pair n ∨ impair n`
-/

example (n : ℤ) : pair (n*(n+1)) :=
begin
  sorry
end

/-
On commence maintenant à combiner explicitement les quantificateurs,
avec la définition de la surjectivité.
-/

def surjective (f : ℝ → ℝ) := ∀ y, ∃ x, f x = y

/-
Dans la démonstration suivante, il sera psychologiquement confortable de demander
à Lean de déplier cette définition, à l'aide de la commande `On déplie`.
-/

-- Dans la suite f et g sont des fonctions de ℝ dans ℝ.
variables(f g : ℝ → ℝ)

-- On suppose que g ∘ f est surjective. Alors g est surjective.
example (h : surjective (g ∘ f)) : surjective g :=
/- On commence par donner une démonstration très bavarde, mais
   cette discussion est très importante pour comprendre l'enjeu des
   variables liées et variables libres. -/
begin
  -- Il s'agit de montrer que ∀ y, ∃ x, g(x) = y
  On déplie surjective, -- On pourrait aussi écrire `Montrons que ∀ y, ∃ x, g x = y`
  -- Soit y un réel. On veut trouver x tel que g(x) = y
  Soit y,
  -- Or notre hypothèse sur g ∘ f garantit que ∀ y, ∃ x, (g ∘ f)(x) = y
  On déplie surjective dans h,
  -- A priori le symbole y apparaissant dans cette hypothèse n'a rien à voir
  -- avec le y que nous avons fixé. C'est une variable liée (ou muette),
  -- on peut la renommer z.
  On renomme y en z dans h,
  -- On a également des variables nommées x dans l'hypothèse et dans le but.
  -- Mais ce sont toutes deux des variables liées, n'ayant aucun rapport
  -- entre elles. En fait le (ou les) x promis par l'hypothèse ne conviennent
  -- pas pour notre but.
  -- Renommons donc la variable liée de l'hypothèse pour clarifier la situation.
  On renomme x en w dans h,
  -- On peut spécialiser l'hypothèse h au y que nous avons fixé,
  -- ce qui fournit w tel que (g ∘ f)(w) = y
  Par h appliqué à y on obtient w tel que hw : g (f w) = y,
  -- Montrons que f(w) convient pour notre but
  Montrons que f w convient,
  -- Il s'agit de montrer que g(f(w)) = y mais ce n'est rien d'autre
  -- que notre hypothèse sur w, par définition de la composition.
  -- Sur papier on écrirait simplement « f(w) convient », sans le
  -- « Montrons que ».
  On conclut par hw,
end
/-
Bien sûr l'essentiel de la discussion ci-dessus est inutile d'un point
de vue logique. Ni Lean ni une rédaction pour experts n'ont besoin
de tous ces discours. Voici la même démonstration sans bavardage.
-/
example (h : surjective (g ∘ f)) : surjective g :=
begin
  Soit y,
  Par h appliqué à y on obtient w tel que hw : (g ∘ f) w = y,
  Montrons que f w convient,
  On conclut par hw,
end

/-
La démonstration précédente est sans doute trop concise. Avant de la transcrire sur papier,
il vaut mieux ajouter une ligne inutile explicitant quel est le but après la première ligne.
On peut aussi ajouter quelques annotations pour nos lecteurs distraits qui auraient oublié
que f et g sont des fonctions de ℝ dans ℝ. De plus l'hypothèse h ne serait
sans doute pas nommée dans une version papier de cet exercice donc on ajoute aussi
une annotation au moment de l'utiliser.
-/

example (h : surjective (g ∘ f)) : surjective g :=
begin
  Soit y : ℝ,
  Montrons que ∃ x, g x = y,
  Par (h : surjective (g ∘ f)) appliqué à y on obtient (w : ℝ) tel que hw : (g ∘ f) w = y,
  Montrons que f w convient,
  On conclut par hw,
end

/-
Et on peut passer à la rédaction « sur papier », par exemple :

  Soit y réel. Montrons qu'il existe x réel tel que g(x) = y.
  Par l'hypothèse de surjectivité de g ∘ f appliquée à y, on obtient w réel tel que
  g ∘ f(w) = y. Le réel f(w) convient.
-/

example (hf : surjective f) (hg : surjective g) : surjective (g ∘ f) :=
begin
  sorry
end


/-
Derek Johnson dej3tc
11/1/2019
-/

import .prop_logic
import .bool_sat

open prop_logic
open prop_logic.var
open prop_logic.pExp

/-
An example: The 0th, 1st, and
2nd bits from the right in 100,
the binary numeral for decimal
4, are 0, 0, and 1, respectively.
-/
#eval mth_bit_from_right_in_n 2 4

/-
#1. Write and evaluate expressions
(using eval) to determine what is
the bit in the 9th position from
the right in the binary expansion
of the decimal number 8485672345?

Hint: don't use reduce here. Eval
uses hardware (machine) int values
to represent nats, while reduce
uses the unary nat representation.

Self-test: How much memory might
it take to represent the decimal
number, 8485672345, as a ℕ value
in unary?
-/

#eval mth_bit_from_right_in_n 9 8485672345

/-
The next section presents examples
to set up test cases for definitions
to follow.
-/

/-
We define a few variables to use
in the rest of this assignment.
-/
def P : pExp:= varExp (mkVar 0)
def Q: pExp := varExp (mkVar 1)
def R : pExp := varExp (mkVar 2)

/-
We set parameter values used in
some function definitions to follow.
-/
def max_var_index := 2
def num_vars := max_var_index + 1

/-
An example of a propositional logic
expression.
-/
def theExpr : pExp := (P ⇒ (P ∧ R))

/-
An example using the truth_table_results
function to compute and return a list of
the truth values of theExpr under each of
its possible interpretations.
-/
#eval truth_table_results theExpr num_vars

/-
#2. Define interp5 to be the interpretation
in the six row (m=5) of the truth table that
our interps_for_n_vars functions returns for
our three variables (P, Q, and R).

Hint: use the mth_interp_n_vars function.
Definitely check out the definition of the
function, and any specification text, even
if informal, given with the formal definition.
-/

def interp5 := mth_interp_n_vars 5 3

/-
#3. What Boolean values are assigned to
P, Q, and R by this interpretation (interp5)?
Use three #eval commands to compute answers by
evaluating each variable expressions under the
interp5 interpretation.
-/

#eval pEval P interp5
#eval pEval Q interp5
#eval pEval R interp5

#eval pEval R (mth_interp_n_vars 7 3)
/-
#4. Write a truth table within this
comment block showing the values for
P, Q, and R, in each row in the truth
table, represented by a corresponding
valule in the list of interpretations
returned by interps_for_n_vars. Label
your columns as R, Q, and P, in that
order. (Try to understand why.)
Hint: Don't just write what you think
the answers are:, evaluate each of
the three variable expression under
each interpretation. You can use the
mth_interp_n_vars function if you want
to obtain interpretation functions for
each of the rows individually if you
want.

Answer:
   R     Q       P
   -     -       -
   f     f       f
   f     f       t
   f     t       f
   f     t       t
   t     f       f
   t     f       t
   t     t       f
   t     t       t

-/

/-
#5. Write an expression here to
compute the "results" column of
the truth table for "theExpr" as
defined above.
-/

#eval truth_table_results theExpr 2

/-
#6. Copy and paste the truth table
from question #4 here and extend it
with the results you just obtained.
Check the results for correctness.

Answer here:
R     Q       P     Out
-     -       -     -
f     f       f     t
f     f       t     t
f     t       f     t
f     t       t     t
t     f       f     f
t     f       t     t
t     t       f     f
t     t       t     t

-/

/-
#7.

Write a "predicate" function, isModel,
that takes a propositional logic
expression, e, and an interpretation,
i, as its arguments and that returns
the Boolean true (tt) value if and only
if i is a model of e (otherwise false).
-/

def isModel :pExp → (var → bool) → bool
| e i := if(pEval e i)
            then tt
        else ff

#eval  mth_interp_n_vars 3 2( mkVar 0)
#eval isModel P (mth_interp_n_vars 3 2)
/-
#7. Write a one-line implementation
of a function, is_valid, that takes as
its arguments a propositional expression,
e, and the number of variables, n, in its
truth table, and that returns true if and
only if it is valid, construed to mean
tha every result in the list returned by
the truth_table_results function is true.

To do so, define and use a fold function
to reduce returned lists of Boolean truth
values to single truth values. Define and
use a bool-specific function, fold_bool :
    (bool → bool → bool) →
    bool →
    (list bool) →
    bool,
where the arguments are, respectively,
a binary operator, an identity element
for that operator, and a list of bools
to be reduced to a single bool result.
-/
open list
def fold_bool :
    (bool → bool → bool) → bool → (list bool) → bool
| i val nil := val
| i val (cons h t) :=
    i h (fold_bool i val t)

def is_valid : pExp → ℕ → bool
| p n := fold_bool band tt (truth_table_results p n)

/-
Write similar one-line implementations of the
functions, is_satisfiable and is_unsatisfiable,
respectively. Do not use fold (directly) in your
implementation of is_unsatisfiable.
-/

def is_satisfiable : pExp → ℕ → bool
| p n := fold_bool bor ff (truth_table_results p n)

def is_unsatisfiable : pExp → ℕ → bool
| p n := if (is_satisfiable p n) then ff else tt

/-
8. Use your is_valid function to determine which
of the following putative valid laws of reasoning
really are valid, and which ones are not. For each
one that is not, give a real-world scenario that
shows that the rule doesn't always lead to a valid
deduction. Use #eval to evaluate the validity of
each proposition. Use -- to put a comment after
each of the following definitions indicating
either "-- valid" or "-- NOT valid".
-/

def true_intro : pExp := pFalse -- Valid
def false_elim := pFalse ⇒ P -- valid
def and_intro := P ⇒ Q ⇒ (P ∧ Q)   --valid
def and_elim_left := (P ∧ Q) ⇒ P   --valid
def and_elim_right := (P ∧ Q) ⇒ Q  --valid
def or_intro_left := P ⇒ (P ∨ Q) --valid
def or_intro_right := Q ⇒ (P ∨ Q) -- valid
def or_elim := (P ∨ Q) ⇒ (P ⇒ R) ⇒ (Q ⇒ R) ⇒ R -- valid
def iff_intro := (P ⇒ Q) ⇒ (Q ⇒ P) ⇒ (P ↔ Q) -- valid
def iff_elim_left := (P ↔ Q) ⇒ (P ⇒ Q) -- valid
def iff_elim_right := (P ↔ Q) ⇒ (Q ⇒ P) -- valid
def arrow_elim := (P ⇒ Q) ⇒ P ⇒ Q -- valid
def affirm_consequence := (P ⇒ Q) ⇒ Q ⇒ P  --not valid Q=ff P=tt
-- if it is raining then it is wet. Then, if it is wet then it is raining
def resolution := (P ∨ Q) ⇒ (¬ Q ∨ R) ⇒ (P ∨ R) -- valid
def unit_resolution := (P ∨ Q) ⇒ (¬ Q) ⇒ P -- valid
def syllogism := (P ⇒ Q) ⇒ (Q ⇒ R) ⇒ (P ⇒ R) -- valid
def modus_tollens := (P ⇒ Q) ⇒ ¬ Q ⇒ ¬ P -- valid
def neg_elim := ¬ ¬ P ⇒ P -- valid
def excluded_middle := P ∨ ¬ P -- valid
def neg_intro := (P ⇒ pFalse) ⇒ ¬ P -- valid
def affirm_disjunct := (P ∨ Q) ⇒ P ⇒ ¬ Q --Not Valid Q==ff P==ff
-- if it is raining and it is wet, then if it is raining then it is not wet.
def deny_antecedent := (P ⇒ Q) ⇒ (¬ P ⇒ ¬ Q) --Not Valid Q==ff P==ff
-- if it is raining then it is wet. Therefore, if it is not raining it implies it is not wet

-- Answer below
#eval is_valid true_intro 1
#eval is_valid false_elim 1
#eval is_valid and_elim_left 2
#eval is_valid and_elim_right 2
#eval is_valid and_intro 2
#eval is_valid or_intro_left 2
#eval is_valid or_intro_right 2
#eval is_valid or_elim 3
#eval is_valid iff_intro 2
#eval is_valid iff_elim_left 2
#eval is_valid iff_elim_right 2
#eval is_valid arrow_elim 2
#eval is_valid affirm_consequence 2
#eval is_valid resolution 3
#eval is_valid unit_resolution 2
#eval is_valid syllogism 3
#eval is_valid modus_tollens 2
#eval is_valid neg_elim 1
#eval is_valid excluded_middle 1
#eval is_valid neg_intro 1
#eval is_valid affirm_disjunct 2
#eval is_valid deny_antecedent 2


/-
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

/-!

# Sets in Lean, example sheet 4 : exists (`∃`)

In this sheet we learn how to manipulate `∃` in Lean. We will see
statements of the form `∃ x, x ∈ A`. We will need to learn two new
tactics -- one for making progress when the goal is `⊢ ∃ x, x ∈ A`,
and one for making progress when we have a hypothesis `h : ∃ x, x ∈ A`.

Now we have `∃` and `∀`, and `∈` and `∉`, we can finally get going
with some harder levels.

## New tactics you will need to know

`cases` or `rcases` -- to get the `x` from a hypothesis `h : ∃ x, ...`
`use` -- to make progress on goals of the form `⊢ ∃ x, ...`

### The `cases` tactic

We've seen this tactic before to take apart `h : P ∧ Q`. We can also
use it to take apart `h : ∃ t : X, F t`: if `h` is such a hypothesis
then `cases h with x hx,` will give us `x : X` and `hx : F x`

### The `use` tactic

If we have a goal `⊢ ∃ x : X, F x` and a term `a : X` which we know
will work, then `use a` will change the goal to `F a`. By the way,
`use` tries `refl` so it might magically close goals early

-/

open set

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

example : x ∈ A → ∃ t, t ∈ A :=
begin
  sorry
end

example : (∀ x, x ∈ A) ↔ ¬ (∃ x, x ∉ A) :=
begin
  sorry
end

example : (∃ x, x ∈ A) ↔ ¬ (∀ x, x ∉ A) :=
begin
  sorry
end


/-
Copyright (c) 2020 James Arthur. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: James Arthur, Chris Hughes, Shing Tak Lam
-/
import analysis.special_functions.trigonometric

/-!
# Inverse of the sinh function

In this file we prove that sinh is bijective and hence has an
inverse, arsinh.

## Main Results

- `sinh_injective`: The proof that `sinh` is injective
- `sinh_surjective`: The proof that `sinh` is surjective
- `sinh_bijective`: The proof `sinh` is bijective
- `arsinh`: The inverse function of `sinh`

## Tags

arsinh, arcsinh, argsinh, asinh, sinh injective, sinh bijective, sinh surjective
-/
noncomputable theory

namespace real

/-- `arsinh` is defined using a logarithm, `arsinh x = log (x + sqrt(1 + x^2))`. -/
@[pp_nodot] def arsinh (x : ℝ) := log (x + sqrt (1 + x^2))

/-- `sinh` is injective, `∀ a b, sinh a = sinh b → a = b`. -/
lemma sinh_injective : function.injective sinh := sinh_strict_mono.injective

private lemma aux_lemma (x : ℝ) : 1 / (x + sqrt (1 + x ^ 2)) = -x + sqrt (1 + x ^ 2) :=
begin
  refine (eq_one_div_of_mul_eq_one _).symm,
  have : 0 ≤ 1 + x ^ 2 := add_nonneg zero_le_one (sq_nonneg x),
  rw [add_comm, ← sub_eq_neg_add, ← mul_self_sub_mul_self,
      mul_self_sqrt this, sq, add_sub_cancel]
end

private lemma b_lt_sqrt_b_one_add_sq (b : ℝ) : b < sqrt (1 + b ^ 2) :=
calc  b
    ≤ sqrt (b ^ 2)     : le_sqrt_of_sq_le $ le_refl _
... < sqrt (1 + b ^ 2) : (sqrt_lt (sq_nonneg _)).2 (lt_one_add _)

private lemma add_sqrt_one_add_sq_pos (b : ℝ) : 0 < b + sqrt (1 + b ^ 2) :=
by { rw [← neg_neg b, ← sub_eq_neg_add, sub_pos, sq, neg_mul_neg, ← sq],
  exact b_lt_sqrt_b_one_add_sq (-b) }

/-- `arsinh` is the right inverse of `sinh`. -/
lemma sinh_arsinh (x : ℝ) : sinh (arsinh x) = x :=
by rw [sinh_eq, arsinh, ← log_inv, exp_log (add_sqrt_one_add_sq_pos x),
      exp_log (inv_pos.2 (add_sqrt_one_add_sq_pos x)),
      inv_eq_one_div, aux_lemma x, sub_eq_add_neg, neg_add, neg_neg, ← sub_eq_add_neg,
      add_add_sub_cancel, add_self_div_two]

/-- `sinh` is surjective, `∀ b, ∃ a, sinh a = b`. In this case, we use `a = arsinh b`. -/
lemma sinh_surjective : function.surjective sinh := function.left_inverse.surjective sinh_arsinh

/-- `sinh` is bijective, both injective and surjective. -/
lemma sinh_bijective : function.bijective sinh :=
⟨sinh_injective, sinh_surjective⟩

/-- A rearrangement and `sqrt` of `real.cosh_sq_sub_sinh_sq`. -/
lemma sqrt_one_add_sinh_sq (x : ℝ): sqrt (1 + sinh x ^ 2) = cosh x :=
begin
  have H := real.cosh_sq_sub_sinh_sq x,
  have G : cosh x ^ 2 - sinh x ^ 2 + sinh x ^ 2 = 1 + sinh x ^ 2 := by rw H,
  rw sub_add_cancel at G,
  rw [←G, sqrt_sq],
  exact le_of_lt (cosh_pos x),
end

/-- `arsinh` is the left inverse of `sinh`. -/
lemma arsinh_sinh (x : ℝ) : arsinh (sinh x) = x :=
function.right_inverse_of_injective_of_left_inverse sinh_injective sinh_arsinh x

end real


import Playground.Data.BinaryTree.Basic
import Playground.Data.List.Is_Sorted
import Mathlib.Data.List.Perm
import Mathlib.Init.Algebra.Order

namespace Data.BinaryTree

section
  variable {type} [LinearOrder type]

  inductive IsBST : BinaryTree type → Prop
  | nil : nil.IsBST
  | node {value left right}
    (left_IsBST : left.IsBST) (left_le : ∀ x ∈ left, x ≤ value)
    (right_IsBST : right.IsBST) (right_ge : ∀ x ∈ right, value ≤ x)
    : (node value left right).IsBST

  theorem IsBST_iff (value : type) (left right) : (node value left right).IsBST ↔
    (left.IsBST ∧ ∀ x ∈ left, x ≤ value) ∧ (right.IsBST ∧ ∀ x ∈ right, value ≤ x) := by
    constructor
    . intro h; cases h with | node _ _ _ _ =>
      constructor
      . constructor; assumption; assumption
      . constructor; assumption; assumption
    . intro ⟨⟨_, _⟩, ⟨_, _⟩⟩
      apply IsBST.node; assumption; assumption; assumption; assumption

  theorem IsBST_iff_infixList_IsSorted (tree : BinaryTree type)
    : tree.IsBST ↔ tree.infixList.IsSorted := by
    induction tree with
    | nil =>
      constructor
      . intro _; constructor
      . intro _; constructor
    | node value left right left_rec right_rec =>
      rw [IsBST_iff]
      show _ ↔ (left.infixList ++ value :: right.infixList).IsSorted
      rw [List.append_cons_IsSorted_iff_left_IsSorted_and_forall_le_and_right_IsSorted_and_forall_ge]
      rw [left_rec, left.forall_iff_forall_infixList]
      rw [right_rec, right.forall_iff_forall_infixList]
end

section
  variable {type} [LinearOrder type]

  def insertAsInBST (tree : BinaryTree type) (x : type) : BinaryTree type
  := match tree with
  | nil => node x nil nil
  | node value left right =>
      if x ≤ value then node value (left.insertAsInBST x) right
      else node value left (right.insertAsInBST x)

  theorem Mem_insertAsInBST_iff (tree : BinaryTree type) (v : type) (x : type)
  : x ∈ tree.insertAsInBST v ↔ x ∈ tree ∨ x = v
  := match tree with
  | nil => ⟨λ (.eq hx) => .inr hx, λ (.inr hx) => .eq hx⟩
  | node value left right =>
  have left_rec : x ∈ _ ↔ x ∈ _ ∨ x = v := left.Mem_insertAsInBST_iff _ _
  have right_rec : x ∈ _ ↔ x ∈ _ ∨ x = v := right.Mem_insertAsInBST_iff _ _
  show x ∈ ite .. ↔ _ from
  if h_v : v ≤ value then if_pos h_v ▸ ⟨
      λ | .eq hx => .inl (Mem.eq hx)
        | .left hx => match left_rec.mp hx with
          | .inl hx => .inl hx.left
          | .inr hx => .inr hx
        | .right hx => .inl hx.right
    , λ | .inl hx => match hx with
          | .eq hx => .eq hx
          | .left hx => .left $ left_rec.mpr $ .inl hx
          | .right hx => hx.right
        | .inr hx => .left $ left_rec.mpr $ .inr hx⟩
  else if_neg h_v ▸ ⟨
      λ | .eq hx => .inl (Mem.eq hx)
        | .left hx => .inl hx.left
        | .right hx => match right_rec.mp hx with
          | .inl hx => .inl hx.right
          | .inr hx => .inr hx
    , λ | .inl hx => match hx with
          | .eq hx => .eq hx
          | .left hx => hx.left
          | .right hx => .right $ right_rec.mpr $ .inl hx
        | .inr hx => .right $ right_rec.mpr $ .inr hx⟩

  theorem insertAsInBST_IsBST_of_IsBST {tree : BinaryTree type} (h_tree : tree.IsBST) (x : type)
  : (tree.insertAsInBST x).IsBST
  := match tree, h_tree with
  | nil, _ => .node .nil (λ _ => λ.) .nil (λ _ => λ.)
  | node value left right,
    .node left_IsBST left_le right_IsBST right_ge =>
    show IsBST (ite ..) from
    if h_value : x ≤ value then if_pos h_value ▸
      have left_IsBST := left.insertAsInBST_IsBST_of_IsBST left_IsBST x
      have left_le := λ x hx => match (left.Mem_insertAsInBST_iff ..).mp hx with
        | .inl hx => left_le x hx
        | .inr hx => hx ▸ h_value
      .node left_IsBST left_le right_IsBST right_ge
    else if_neg h_value ▸
      have h_value := le_of_lt (lt_of_not_ge h_value)
      have right_IsBST := right.insertAsInBST_IsBST_of_IsBST right_IsBST x
      have right_ge := λ x hx => match (right.Mem_insertAsInBST_iff ..).mp hx with
        | .inl hx => right_ge x hx
        | .inr hx => hx ▸ h_value
      .node left_IsBST left_le right_IsBST right_ge

  theorem insertAsInBST_infixList_Perm_cons_infixList (tree : BinaryTree type) (x : type)
  : (tree.insertAsInBST x).infixList ~ x :: tree.infixList
  :=
  open List in
  match tree with
  | nil => List.Perm.refl [x]
  | node value _ _ =>
    show infixList (ite ..) ~ _ from
    if h_value : x ≤ value then if_pos h_value ▸
      (insertAsInBST_infixList_Perm_cons_infixList ..).append_right _
    else if_neg h_value ▸
      ((((insertAsInBST_infixList_Perm_cons_infixList ..).cons _).trans
        (List.Perm.swap ..).symm).append_left _).trans List.perm_middle

  def searchAsInBST (tree : BinaryTree type) (x : type) : Bool
  := match tree with
  | .nil => false
  | .node value left right =>
    if value = x then true
    else if x < value then left.searchAsInBST x
    else right.searchAsInBST x

  theorem searchAsInBST_eq_decide_Mem_of_IsBST {tree : BinaryTree type} (h_tree : tree.IsBST)
  (x : type) : tree.searchAsInBST x = decide (x ∈ tree)
  := match tree, h_tree with
  | nil, _ => rfl
  | node value left right,
    .node left_is_bst left_le right_is_bst right_ge =>
    show ite .. = ite .. from
    if h_e : value = x then if_pos h_e ▸ if_pos (Mem.eq h_e.symm) ▸ rfl
    else if h_l : x < value then if_neg h_e ▸ if_pos h_l ▸
      searchAsInBST_eq_decide_Mem_of_IsBST left_is_bst _ ▸
      show ite .. = _ from
      if v_in_left : x ∈ left then if_pos v_in_left ▸ if_pos v_in_left.left ▸ rfl
      else if_neg v_in_left ▸ if_neg
      (show ¬x ∈ node value left right from
      λ | .eq h => h_e h.symm
        | .left h => v_in_left h
        | .right h => not_le_of_gt h_l $ right_ge x h
      ) ▸ rfl
    else have h_r : value < x := lt_of_le_of_ne (le_of_not_lt h_l) h_e
      if_neg h_e ▸ if_neg h_l ▸
      searchAsInBST_eq_decide_Mem_of_IsBST right_is_bst _ ▸
      show ite .. = _ from
      if v_in_right : x ∈ right then if_pos v_in_right ▸ if_pos v_in_right.right ▸ rfl
      else if_neg v_in_right ▸ if_neg
      (show ¬x ∈ node value left right from
      λ | .eq h => h_e h.symm
        | .left h => not_le_of_gt h_r $ left_le x h
        | .right h => v_in_right h
      ) ▸ rfl

end

end Data.BinaryTree


/- 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' := _

-- 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.
  | _

-- 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) := _

-- 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 => _
  -- 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
    _

-- Now try proving this theorem on `add` using the same induction scheme
theorem zero_add (n : Nat') : add zero n = add n zero := _

/-
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 ≠ [] → α := _

/-
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

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 := _

def size (t : Tree α) : Nat := _

-- 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 α := _

def inOrder (t : Tree α) : List α := _

def postOrder (t : Tree α) : List α := _

-- Define a function which returns the mirror image of a given tree.
def mirror (t : Tree α) : Tree α := _

-- 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 := _

theorem mirror_eq (s t : Tree α) : mirror s = mirror t ↔ s = t := _

/- 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

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

-- 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' := _


section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro hp,
  intro hnp,
  contradiction, --tenho p e np nos meus dados
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro hnnp,
  by_cases hp: P, --magia para ganhar p ou np
    exact hp,--tenho p

    have contr := hnnp hp,
    contradiction, -- tenho np e nnp nos meus dados
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
    exact doubleneg_elim P,
    exact doubleneg_intro P,
end

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

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro disj_pq,
  cases disj_pq,--separo minha disj em casos
    right,--escolho-R
      exact disj_pq,
    left,--escolho-L
      exact disj_pq,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro conj_pq,
  cases conj_pq with hp hq,--desmembro a conj dos dados
  split,--split'o a conj do alvo
    exact hq,
    exact hp,
end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro disj_pq,
  intro hp,
  cases disj_pq,--separo minha disj em casos
    have contr := disj_pq hp,--tenho p e np nos meus dados
    contradiction,

    exact disj_pq,--tenho o proprio alvo nos dados
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intro disj_pq,
  intro hnp,
  cases disj_pq,--separo minha disj em casos
    have contr := hnp disj_pq,--tenho p e np nos meus dados
    contradiction,

    exact disj_pq,--tenho o proprio alvo nos dados
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intro impl_pq,
  intro hnq,
  intro hp,
    have hq := impl_pq hp,
    have contr := hnq hq,
    contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intro impl_nqnp,
  intro hp,
  by_cases hq: Q,--magia para q ou nq nos dados
    exact hq,

    have hnp := impl_nqnp hq,
    have contr := hnp hp,
    contradiction,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
    exact impl_as_contrapositive P Q,
    exact impl_as_contrapositive_converse P Q,
end

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro nlem,
  have lem: P \/ ¬P,--sub demonstracao da lem
    right,
    intro hp,
    have disj_pnp: P \/ ¬P,--sub dem da sub demonstracao da lem
      left,
      exact hp,
    have contr := nlem disj_pnp,
    contradiction,
  have contr := nlem lem,
  contradiction,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intro pq_impl_p,
  intro hnp,
  have impl_pq: P →  Q,--sub demonstracao
    intro hp,
    contradiction,--tenho p e np nos meus dados da sub
  have hp := pq_impl_p impl_pq,--obtenho p
  have contr := hnp hp,
  contradiction,--tenho p e np nos meus dados
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intro disj_pq,
  intro n_conj_npnq,
  cases n_conj_npnq,--extraio minha conj
  cases disj_pq,--separo minha disj em casos
    have contr := n_conj_npnq_left disj_pq,
    contradiction,
    have contr := n_conj_npnq_right disj_pq,
    contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro conj_pq,
  intro n_disj_pq,
  cases conj_pq,
  cases n_disj_pq,
    have contr := n_disj_pq conj_pq_left,
    contradiction,
    have contr := n_disj_pq conj_pq_right,
    contradiction,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_disj_pq,
  split,
    intro hp,
    --bugou a cabeça

    --intro hq,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro conj_np_nq,
  intro disj_pq,
  cases disj_pq,--separo minha disj em partes
    cases conj_np_nq,--extraio minha conj
      contradiction,
    cases conj_np_nq,
      contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_conj_pq,
  by_cases h: P,
    left,
    intro hq,
    have conj_pq: P /\ Q,
      split,
      exact h,
      exact hq,
    have contr := n_conj_pq conj_pq,
    contradiction,

    right,
    exact h,
end

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

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

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
    exact demorgan_disj P Q,
    exact demorgan_disj_converse P Q,
end

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro conj_pdisj,
  cases conj_pdisj with hp disj,
  cases disj with hq hr,
    left,
    split,
      exact hp,
      exact hq,
    right,
    split,
      exact hp,
      exact hr,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro disj_conjs,
  cases disj_conjs with conj_pq conj_pr,
  cases conj_pq with hp hq,
    split,
      exact hp,
      left,
        exact hq,
  cases conj_pr with hp hr,
    split,
    exact hp,
    right,
    exact hr,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro disj_conj_pqr,
  split,
    cases disj_conj_pqr with hp conj_qr,
      left,
        exact hp,
      right,
        cases conj_qr with hq hr,
          exact hq,
    cases disj_conj_pqr with hp conj_qr,
      left,
        exact hp,
      right,
        cases conj_qr with hq hr,
          exact hr,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro conj_disj,
  cases conj_disj with disj_pq disj_pr,
  cases disj_pq with hp hq,
    left,
      exact hp,
    cases disj_pr with hp hr,
      left,
        exact hp,
      right,
        split,
          exact hq,
          exact hr,
end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro curry,
  intro hp,
  intro hq,

  have conj_pq: P /\ Q,--sub demonstracao da conj P Q
    split,
      exact hp,
      exact hq,

  have R := curry conj_pq,--consigo o R
    exact R,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intro uncurry,
  intro conj_pq,
  cases conj_pq with hp hq,
    have implic := uncurry hp,--uso o P na implicacao P Q R, obtenho implicacao Q R
    have R := implic hq,--agora uso o Q na implicacao Q R obtenho assim o R
    exact R,
end

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

theorem impl_refl :
  P → P  :=
begin
  intro hp,
  exact hp,
end

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

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro hp,
  left,
  exact hp,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro hq,
  right,
  exact hq,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro conj_pq,
  cases conj_pq with hp hq,
  exact hp,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro conj_pq,
  cases conj_pq with hp hq,
  exact hq,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  have theor_left := weaken_conj_left P P,
  split,
    exact theor_left,

    intro hp,
    split,
      exact hp,
      exact hp,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro disj_pp,
  cases disj_pp with hp hp,
    exact hp,
    exact hp,
  have theor_right := weaken_disj_left P P,
    exact theor_right,
end

end propositional

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

section predicate

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

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

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intro nexist,
  intro hx,
  intro hpx,
  apply nexist,
  existsi hx,
  exact hpx,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intro for_all,
  intro exist,
  cases exist with hx hpx,
  have n_hpx := for_all hx,
  have contr := n_hpx hpx,
  contradiction,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro n_for_all,
  --

end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intro exist,
  intro for_all,
  cases exist with hx hnpx,
  have hpx := for_all hx,
  have contr := hnpx hpx,
  contradiction,
end

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

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
    exact demorgan_exists U P,
    exact demorgan_exists_converse U P,
end

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intro exist,
  intro for_all,
  cases exist with hx hpx,
  have hnpx := for_all hx,
  have contr := hnpx hpx,
  contradiction,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intro for_all,
  intro exist,
  cases exist with hx hnpx,
  have hpx := for_all hx,
  have contr := hnpx hpx,
  contradiction,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intro n_exist,
  intro hx,
  by_cases P hx,
  exact h,
    have exist: ∃(x: U), ¬P x,
    existsi hx,
    exact h,
    have contr := n_exist exist,
    contradiction,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro n_for_all,
  by_contradiction n_exist,
  have demorgan_exist := demorgan_exists U P,
  have for_all := demorgan_exist n_exist, --utilizo a implicação
  exact n_for_all for_all,
end

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

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

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

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

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  sorry,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  sorry,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  sorry,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  sorry,
end

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

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

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

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

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

end predicate


def divides (a : Nat) (b : Nat) := ∃ k, a * k = b

infix:40 " | " => divides

theorem divides_def : a | b ↔  ∃ k, a * k = b := Iff.rfl
theorem divides_intro (k: Nat) : a * k = b → a | b := by
  intro h
  apply Exists.intro k
  exact h

theorem all_divide_zero : ∀ n, n | 0 := by
  intro n
  apply divides_intro 0
  simp

theorem divides_refl : ∀ n, n | n := by
  intro n
  apply divides_intro 1
  simp

theorem one_divides_all : ∀ n, 1 | n := by
  intro n
  apply divides_intro n
  simp

def common_divisor (g : Nat) (a: Nat) (b: Nat) := g | a ∧ g | b
def gcd_of (g : Nat) (a: Nat) (b: Nat) := (common_divisor g a b) ∧ ∀ t, common_divisor t a b → t | g
def coprime (a : Nat) (b : Nat) := gcd_of 1 a b
def prime (p : Nat) := 2 ≤ p ∧ ∀ n, n | p → (n = 1 ∨ n = p)

def common_divisor_def : common_divisor g a b ↔ g | a ∧ g | b := Iff.rfl
def common_divisor_intro : g | a → g | b → common_divisor g a b := And.intro
def gcd_of_def : gcd_of g a b ↔ (common_divisor g a b) ∧ ∀ t, common_divisor t a b → t | g := Iff.rfl
def gcd_of_intro : (common_divisor g a b) → (∀ t, common_divisor t a b → t | g) → gcd_of g a b := And.intro
def coprime_def : coprime a b ↔ gcd_of 1 a b := Iff.rfl
def prime_def : prime p ↔ 2 ≤ p ∧ ∀ n, n | p → (n = 1 ∨ n = p) := Iff.rfl
def prime_intro : 2 ≤ p → (∀ n, n | p → (n = 1 ∨ n = p)) → prime p := And.intro

-- TODO: lcm
-- TODO: constructive definition of gcd, lcm would be better
-- TODO: Theorems from the script


/-
From the axioms of reflexivity and substitutabilty
we now prove two *theorems*. A theorem is a proven
proposition. You can even think of the theorem as a
proof of a given proposition. A proof establishes a
conjecture as a theorem. And once it's a theorem,
you can use it like any other inference rule. In
this way, you can build up vast, layered libraries
of theorems. That's what we call mathematics. It's
what mathematicians do.

In this file, we provide both formal and informal
proofs of two theorems:

[1] Equality is symmetric.
[2] Equality is transitive.
-/

/-
To master these concepts, you must appreciate
the distinction between propositions and proofs.
Propositions are "statements of fact" that might
or might not be true. Proofs are objects that
show such propositions to be true. Propositions
that are not true, have no proofs (otherwise of
course they'd be true, as we just said).

So, first, you have to be able to *state* the
conjecture in the formal language of predicate
logic. We'll use an implementation of a specific
version of predicate logic provided by the Lean
Prover.
-/

universe u
def eq_symm : ∀ (α : Sort u) (x y : α), x = y → y = x :=
_
/-
λ α x y h_eq,
  let pf := _ in _ ... ???
_
-/
#check @eq.subst
/-
∀ {α : Sort u_1} {P : α → Prop} {a b : α}, a = b → P a → P b
-/

def eq_symm' : ∀ (α : Sort u) (x y : α), x = y → y = x :=
begin
  intros α x y h_eq,
  rw <-h_eq,
end

axioms (foo bar : ℕ) (h : foo = bar)

def xyz : bar = foo := eq_symm' nat foo bar h
#check xyz

def eq_trans :
  ∀ (a b c : Sort u), a = b → b = c → a = c :=
begin
  assume a b c h_ab h_bc,
  rw <- h_ab at h_bc,
  --exact h_bc,
  --apply h_bc,
  assumption
end

example :
  ∀ (a b c : Sort u), a = b → b = c → a = c :=
begin
  assume a b c h_ab h_bc,
  rw h_ab,
  assumption,
end

/-
Second, you must understand how to construct
proofs of such propositions, if proofs actually
exist, which they might or might not for given
propositions. Not all propositions, statements
asserting "facts", are true. The sky is magenta.
-/

/-
Theorems
-/

-- We define eq_symm to be the proposition at issue
-- Note: Prop is the type of all propositions in Lean
-- And each proposition is itself a type: of it proofs
def eq_symm : Prop :=
  ∀ (T : Type)
    (x y : T),
    x = y →
    y = x

-- We define eq_trans formally in the same basic way
def eq_trans : Prop :=
  ∀ (T : Type)
    (x y z : T),
    x = y →
    y = z →
    x = z

/-
Proofs
-/

/-
In the logic of Lean, a proposition is a type,
and the values of that type, if there are any,
are its proofs.

Just as we can give an example value of a data
type, such as ℕ, we can also give example values
of propositions that are represented as types,
i.e., as "proof terms," or just "proofs."
-/

-- give a value of the nat type
example : ℕ := _

-- give a proof of the proposition 1 = 1
example : 1 = 1 := _

/-
We will now present formal proofs of our two
theorems in this style.
-/

/-
Proof: equality is symmetric.
-/

example : eq_symm :=
begin
  unfold eq_symm, -- replace name with definition
  assume T x y e, -- assume arbitrary values
  rw e,           -- rw uses eq.subst to replace x with y
                  -- and then applies eq.refl automatically
  -- QED.
end

/-
Proof: equality is transitive.
-/

example : eq_trans :=
begin
  unfold eq_trans,
  assume T,
  assume x y z,
  assume e1,
  assume e2,
  rw e1,
  exact e2,
end

/-
A fundamental idea is that any proof is as good as any
other for establishing the truth of a proposition. Here
is an equally good alternative proof (or to be correct,
proof-generating scripts in the "proof tactic" language"
of the Lean Prover.
-/

example : eq_symm :=
begin
  unfold eq_symm,   -- replace name with definition
  assume T x y e,   -- assume arbitrary values
  apply eq.subst e, -- apply axiom 2, substitutability
  exact eq.refl x,  -- apply axiom 1, reflexivity
  -- QED.
end

/-
There. That's a nice proof (script), as it closely
models a typical English language proof. To wit:

Theorem: Equality is symmetric.
Proof: Assume we're given arbitrary values of T,
x, y, and a proof of x = y. What remains to be
proved is y = x. Apply substitutability to write
x as y or y as x. The result is trivially true
by the reflexive property of equality.
-/


import BrownCs22.Library.Defs
import BrownCs22.Library.Tactics
import Mathlib.Data.Nat.ModEq
import Mathlib.Data.Int.GCD

open BrownCs22

/-

Let's check computationally that our RSA algorithm works.

-/

-- given a public key and a modulus `n`, we can encrypt a message.
def rsa_encrypt (public_key : ℕ) (n : ℕ) (message : ℕ) : ℕ :=
  (message ^ public_key) % n

-- given a private key and a modulus `n`, we can decrypt a message.
def rsa_decrypt (private_key : ℕ) (n : ℕ) (encrypted_message : ℕ) : ℕ :=
  (encrypted_message ^ private_key) % n

-- let's choose `n` to be the product of two primes.
def p := 113
def q := 37
def n := p * q

#eval Nat.Prime p
#eval Nat.Prime q

-- We choose our public key that is relatively prime to `(p - 1)*(q - 1)`.
def public_key := 13

#eval Nat.gcd public_key ((p - 1)*(q - 1))

-- Now we need an inverse to the public key mod `(p - 1)*(q - 1)`.
-- We get this from the extended Euclidean algorithm.
#eval Nat.xgcd public_key ((p - 1)*(q - 1))

def private_key := 1861

-- double check it's an inverse
#eval private_key * public_key  % ((p - 1)*(q - 1))

-- Okay! Let's choose a message.
def message := 1034

def encrypted_message := rsa_encrypt public_key n message

#eval encrypted_message

def decrypted_message := rsa_decrypt private_key n encrypted_message

-- encrypting and decrypting the message produces the same output!
#eval decrypted_message

-- We can state, and (mostly) prove, the partial correctness theorem from class!
theorem rsa_correct
  (p q : ℕ) (public_key private_key : ℕ) (message : ℕ)
  (hp : Prime p) (hq : Prime q)
  (h_pub_pri : public_key * private_key ≡ 1 [MOD (p - 1)*(q - 1)])
  (h_msg : message < p*q)
  (h_rel_prime : Nat.gcd msg (p*q) = 1) :

    rsa_decrypt private_key (p*q)
      (rsa_encrypt public_key (p*q) message)
        = message :=
  by
  dsimp [rsa_decrypt, rsa_encrypt]

  have h_lin_combo : ∃ k, public_key * private_key = 1 + (p - 1)*(q - 1)*k :=
    sorry

  eliminate h_lin_combo with k hk

  calc
    (message ^ public_key % (p * q)) ^ private_key % (p * q)
      = (message ^ public_key) ^ private_key % (p * q)      := by rw [← Nat.pow_mod]
    _ = message ^ (public_key * private_key) % (p * q)      := by rw [pow_mul]
    _ = message ^ (1 + (p - 1)*(q - 1)*k) % (p * q)         := by rw [hk]
    _ = message ^ (1 + totient (p*q)*k) % (p * q)           := by sorry
    _ = (message * (message ^ totient (p * q))^k) % (p * q) := by rw [pow_add, pow_one, pow_mul]
    _ = (message % (p * q)) * ((message ^ totient (p * q))^k % (p * q)) % (p * q)
                                                            := by rw [Nat.mul_mod]
    _ = (message % (p * q)) * 1 % (p * q)                   := by sorry
    _ = message % (p * q)                                   := by rw [mul_one, Nat.mod_mod]
    _ = message                                             := by rw [Nat.mod_eq_of_lt h_msg]


import ..lectures.love11_logical_foundations_of_mathematics_demo

/-! # LoVe Exercise 11: Logical Foundations of Mathematics -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Vectors as Subtypes

Recall the definition of vectors from the demo: -/

#check vector

/-! The following function adds two lists of integers elementwise. If one
function is longer than the other, the tail of the longer function is
truncated. -/

def list.add : list ℤ → list ℤ → list ℤ
| []        []        := []
| (x :: xs) (y :: ys) := (x + y) :: list.add xs ys
| []        (y :: ys) := []
| (x :: xs) []        := []

/-! 1.1. Show that if the lists have the same length, the resulting list also
has that length. -/

lemma list.length_add :
  ∀(xs : list ℤ) (ys : list ℤ) (h : list.length xs = list.length ys),
    list.length (list.add xs ys) = list.length xs
| []        []        :=
  sorry
| (x :: xs) (y :: ys) :=
  sorry
| []        (y :: ys) :=
  sorry
| (x :: xs) []        :=
  sorry

/-! 1.2. Define componentwise addition on vectors using `list.add` and
`list.length_add`. -/

def vector.add {n : ℕ} : vector ℤ n → vector ℤ n → vector ℤ n :=
sorry

/-! 1.3. Show that `list.add` and `vector.add` are commutative. -/

lemma list.add.comm :
  ∀(xs : list ℤ) (ys : list ℤ), list.add xs ys = list.add ys xs :=
sorry

lemma vector.add.comm {n : ℕ} (x y : vector ℤ n) :
  vector.add x y = vector.add y x :=
sorry

/-! ## Question 2: Integers as Quotients

Recall the construction of integers from the lecture, not to be confused with
Lean's predefined type `int` (= `ℤ`): -/

#check int.rel
#check int.rel_iff
#check int

/-! 2.1. Define negation on these integers. -/

def int.neg : int → int :=
sorry

/-! 2.2. Prove the following lemmas about negation. -/

lemma int.neg_eq (p n : ℕ) :
  int.neg ⟦(p, n)⟧ = ⟦(n, p)⟧ :=
sorry

lemma int.neg_neg (a : int) :
  int.neg (int.neg a) = a :=
sorry

end LoVe


/-
  Basic constructions involving ideals.

  TODO : Check how much of this isn't actually somehwere in the mathlib.
-/

import data.finset
import linear_algebra.finsupp
import ring_theory.ideal_operations

universes u v

variables {α : Type u} {β : Type v} [comm_ring α] [comm_ring β] (f : α → β) [is_ring_hom f]

def ideal.mk (I : set α) (J : ideal α) (H : I = J) : ideal α :=
{ carrier := I,
  zero := H.symm ▸ J.zero,
  add := H.symm ▸ J.add,
  smul := H.symm ▸ J.smul }

def ker : ideal α :=
ideal.mk {x | f x = 0} (ideal.comap f ⊥) $
set.ext $ λ x, by simp

-- If R is not the zero ring, then the zero ideal is not the whole ring.

lemma zero_ne_one_bot_ne_top : (0 : α) ≠ 1 → (⊥ : ideal α) ≠ ⊤ :=
begin
  intros Hzno,
  have Honz : (1 : α) ∉ ({0} : set α),
    intros HC,
    rw set.mem_singleton_iff at HC,
    exact Hzno HC.symm,
  intros HC,
  replace HC := (ideal.eq_top_iff_one ⊥).1 HC,
  exact (Honz HC),
end

-- Every nonzero ring has a maximal ideal.

lemma ideal.exists_maximal : (0 : α) ≠ 1 → ∃ S : ideal α, ideal.is_maximal S :=
begin
  intros Hzno,
  have HTnB : (⊥ : ideal α) ≠ ⊤ := zero_ne_one_bot_ne_top Hzno,
  rcases (ideal.exists_le_maximal ⊥ HTnB) with ⟨M, ⟨HM, HBM⟩⟩,
  exact ⟨M, HM⟩,
end

-- I need this but I really wish I didn't.

lemma ideal.span_mem_finset {R : Type u} [decidable_eq R] [comm_ring R] (S : finset R) (f : R → R)
: finset.sum S (λ a, f a * a) ∈ (@ideal.span R _ ↑S) :=
begin
  unfold ideal.span,
  apply finset.induction_on S,
  { dsimp; simp, },
  { intros a S Ha IH,
    rw finset.coe_insert,
    rw submodule.mem_span_insert',
    rw finset.sum_insert Ha,
    use [-f a],
    simp [IH], }
end

-- Element in ideal span is a finite linear combination.

lemma ideal.mem_span_iff_total {R : Type u} [comm_ring R] {s : set R} {x : R}:
  x ∈ ideal.span s → ∃ l ∈ finsupp.supported R R s, finsupp.total R R R id l = x :=
begin
  intro H,
  have Hfs := (@finsupp.mem_span_iff_total R _ R _ _ _ id s x).1,
  simp at Hfs,
  rcases (Hfs H) with ⟨l, ⟨Hl, Hlfs⟩⟩,
  use ⟨l, ⟨Hl, Hlfs⟩⟩,
end


/-
Copyright (c) 2022 Violeta Hernández Palacios. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Violeta Hernández Palacios
-/

import data.complex.basic
import data.real.cardinality

/-!
# The cardinality of the complex numbers

This file shows that the complex numbers have cardinality continuum, i.e. `#ℂ = 𝔠`.
-/

open cardinal set

open_locale cardinal

/-- The cardinality of the complex numbers, as a type. -/
@[simp] theorem mk_complex : #ℂ = 𝔠 :=
by rw [mk_congr complex.equiv_real_prod, mk_prod, lift_id, mk_real, continuum_mul_self]

/-- The cardinality of the complex numbers, as a set. -/
@[simp] lemma mk_univ_complex : #(set.univ : set ℂ) = 𝔠 :=
by rw [mk_univ, mk_complex]

/-- The complex numbers are not countable. -/
lemma not_countable_complex : ¬ countable (set.univ : set ℂ) :=
by { rw [← mk_set_le_omega, not_le, mk_univ_complex], apply cantor }


/-
Copyright (c) 2021 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/
import group_theory.submonoid.operations
import data.fintype.basic

/-!
# Centers of magmas and monoids

## Main definitions

* `set.center`: the center of a magma
* `submonoid.center`: the center of a monoid
* `set.add_center`: the center of an additive magma
* `add_submonoid.center`: the center of an additive monoid

We provide `subgroup.center`, `add_subgroup.center`, `subsemiring.center`, and `subring.center` in
other files.
-/

variables {M : Type*}

namespace set

variables (M)

/-- The center of a magma. -/
@[to_additive add_center /-" The center of an additive magma. "-/]
def center [has_mul M] : set M := {z | ∀ m, m * z = z * m}

@[to_additive mem_add_center]
lemma mem_center_iff [has_mul M] {z : M} : z ∈ center M ↔ ∀ g, g * z = z * g := iff.rfl

instance decidable_mem_center [has_mul M] [decidable_eq M] [fintype M] :
  decidable_pred (∈ center M) :=
λ _, decidable_of_iff' _ (mem_center_iff M)

@[simp, to_additive zero_mem_add_center]
lemma one_mem_center [mul_one_class M] : (1 : M) ∈ set.center M := by simp [mem_center_iff]

@[simp]
lemma zero_mem_center [mul_zero_class M] : (0 : M) ∈ set.center M := by simp [mem_center_iff]

variables {M}

@[simp, to_additive add_mem_add_center]
lemma mul_mem_center [semigroup M] {a b : M}
  (ha : a ∈ set.center M) (hb : b ∈ set.center M) : a * b ∈ set.center M :=
λ g, by rw [mul_assoc, ←hb g, ← mul_assoc, ha g, mul_assoc]

@[simp, to_additive neg_mem_add_center]
lemma inv_mem_center [group M] {a : M} (ha : a ∈ set.center M) : a⁻¹ ∈ set.center M :=
λ g, by rw [← inv_inj, mul_inv_rev, inv_inv, ← ha, mul_inv_rev, inv_inv]

@[simp]
lemma add_mem_center [distrib M] {a b : M}
  (ha : a ∈ set.center M) (hb : b ∈ set.center M) : a + b ∈ set.center M :=
λ c, by rw [add_mul, mul_add, ha c, hb c]

@[simp]
lemma neg_mem_center [ring M] {a : M} (ha : a ∈ set.center M) : -a ∈ set.center M :=
λ c, by rw [←neg_mul_comm, ha (-c), neg_mul_comm]

@[to_additive subset_add_center_add_units]
lemma subset_center_units [monoid M] :
  (coe : units M → M) ⁻¹' center M ⊆ set.center (units M) :=
λ a ha b, units.ext $ ha _

lemma center_units_subset [group_with_zero M] :
  set.center (units M) ⊆ (coe : units M → M) ⁻¹' center M :=
λ a ha b, begin
  obtain rfl | hb := eq_or_ne b 0,
  { rw [zero_mul, mul_zero], },
  { exact units.ext_iff.mp (ha (units.mk0 _ hb)) }
end

/-- In a group with zero, the center of the units is the preimage of the center. -/
lemma center_units_eq [group_with_zero M] :
  set.center (units M) = (coe : units M → M) ⁻¹' center M :=
subset.antisymm center_units_subset subset_center_units

@[simp]
lemma inv_mem_center₀ [group_with_zero M] {a : M} (ha : a ∈ set.center M) : a⁻¹ ∈ set.center M :=
begin
  obtain rfl | ha0 := eq_or_ne a 0,
  { rw inv_zero, exact zero_mem_center M },
  lift a to units M using ha0,
  rw ←units.coe_inv',
  exact center_units_subset (inv_mem_center (subset_center_units ha)),
end

@[simp, to_additive sub_mem_add_center]
lemma div_mem_center [group M] {a b : M} (ha : a ∈ set.center M) (hb : b ∈ set.center M) :
  a / b ∈ set.center M :=
begin
  rw [div_eq_mul_inv],
  exact mul_mem_center ha (inv_mem_center hb),
end

@[simp]
lemma div_mem_center₀ [group_with_zero M] {a b : M} (ha : a ∈ set.center M)
  (hb : b ∈ set.center M) : a / b ∈ set.center M :=
begin
  rw div_eq_mul_inv,
  exact mul_mem_center ha (inv_mem_center₀ hb),
end

variables (M)

@[simp, to_additive add_center_eq_univ]
lemma center_eq_univ [comm_semigroup M] : center M = set.univ :=
subset.antisymm (subset_univ _) $ λ x _ y, mul_comm y x

end set

namespace submonoid
section
variables (M) [monoid M]

/-- The center of a monoid `M` is the set of elements that commute with everything in `M` -/
@[to_additive "The center of a monoid `M` is the set of elements that commute with everything in
`M`"]
def center : submonoid M :=
{ carrier := set.center M,
  one_mem' := set.one_mem_center M,
  mul_mem' := λ a b, set.mul_mem_center }

@[to_additive] lemma coe_center : ↑(center M) = set.center M := rfl

variables {M}

@[to_additive] lemma mem_center_iff {z : M} : z ∈ center M ↔ ∀ g, g * z = z * g := iff.rfl

instance decidable_mem_center [decidable_eq M] [fintype M] : decidable_pred (∈ center M) :=
λ _, decidable_of_iff' _ mem_center_iff

/-- The center of a monoid is commutative. -/
instance : comm_monoid (center M) :=
{ mul_comm := λ a b, subtype.ext $ b.prop _,
  .. (center M).to_monoid }

end

section
variables (M) [comm_monoid M]

@[simp] lemma center_eq_top : center M = ⊤ :=
set_like.coe_injective (set.center_eq_univ M)

end

end submonoid


import .love11_logical_foundations_of_mathematics_demo

/-! # LoVe Exercise 11: Logical Foundations of Mathematics -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Vectors as Subtypes

Recall the definition of vectors from the demo: -/

#check vector

/-! The following function adds two lists of integers elementwise. If one
function is longer than the other, the tail of the longer function is
truncated. -/

def list.add : list ℤ → list ℤ → list ℤ
| []        []        := []
| (x :: xs) (y :: ys) := (x + y) :: list.add xs ys
| []        (y :: ys) := []
| (x :: xs) []        := []

/-! 1.1. Show that if the lists have the same length, the resulting list also
has that length. -/

lemma list.length_add :
  ∀(xs : list ℤ) (ys : list ℤ) (h : list.length xs = list.length ys),
    list.length (list.add xs ys) = list.length xs
| []        []        :=
  sorry
| (x :: xs) (y :: ys) :=
  sorry
| []        (y :: ys) :=
  sorry
| (x :: xs) []        :=
  sorry

/-! 1.2. Define componentwise addition on vectors using `list.add` and
`list.length_add`. -/

def vector.add {n : ℕ} : vector ℤ n → vector ℤ n → vector ℤ n :=
sorry

/-! 1.3. Show that `list.add` and `vector.add` are commutative. -/

lemma list.add.comm :
  ∀(xs : list ℤ) (ys : list ℤ), list.add xs ys = list.add ys xs :=
sorry

lemma vector.add.comm {n : ℕ} (x y : vector ℤ n) :
  vector.add x y = vector.add y x :=
sorry

/-! ## Question 2: Integers as Quotients

Recall the construction of integers from the lecture, not to be confused with
Lean's predefined type `int` (= `ℤ`): -/

#check int.rel
#check int.rel_iff
#check int

/-! 2.1. Define negation on these integers. -/

def int.neg : int → int :=
sorry

/-! 2.2. Prove the following lemmas about negation. -/

lemma int.neg_eq (p n : ℕ) :
  int.neg ⟦(p, n)⟧ = ⟦(n, p)⟧ :=
sorry

lemma int.neg_neg (a : int) :
  int.neg (int.neg a) = a :=
sorry

end LoVe


import Mathlib
/-!
# Examples of Proofs

We see our first proofs, most of which involve the `≤` relation on natural numbers.

We will see that the natural numbers are "defined" in terms of the `zero` and `succ` constructors.

* `Nat.zero : ℕ`
* `Nat.succ : ℕ → ℕ`

Analogous to this, (modulo renaming) the `≤` relation is defined in terms of the `le_refl` and `le_step` constructors.

* `Nat.le : ℕ → ℕ → Prop`
* `Nat.le_refl : ∀ n : ℕ, n ≤ n`
* `Nat.le_step : ∀ {n m : ℕ}, n ≤ m → n ≤ Nat.succ m`

However, we will first focus on a simpler relation.
-/

#check Nat.zero
#check Nat.succ

#check Nat.le
#check Nat.le_step
#check Nat.le_refl

/-!
### Example: a proof by reflexivity of equality

Our first example is simpler: just a proof of equality. Note that a proof of a theorem in lean is very similar, both conceptually and syntactically, to a definition.

In a definition, we give a name, a type and a value (though sometimes the type is inferred so can be omitted). In a theorem, we give a name, a statement and a proof. The statement is essentially a type, and the proof is a value of that type.

Technically a statement is a proposition which is a special sort of type as we see below, but in these examples it is best to think of it as just a type.
-/

/-- The theorem that `1 + 1 = 2` -/
theorem one_plus_one_is_two : 1 + 1 = 2 := rfl

/-!
We look more closely at the right hand side, i.e., the _proof_. Below is the description in Lean of `rfl`, which corresponds to _reflexivity of equality_, i.e., the fact that any term is equal to itself:

> `rfl : a = a` is the unique constructor of the equality type. This is the same as `Eq.refl` except that it takes `a` implicitly instead of explicitly.

> This is a more powerful theorem than it may appear at first, because although the statement of the theorem is `a = a`, lean will allow anything that is definitionally equal to that type. So, for instance, `2 + 2 = 4` is proven in lean by `rfl`, because both sides are the same up to definitional equality.

To clarify what is meant by _implicit_ and _explicit_ parameters, we look at the types of `rfl` and `Eq.refl`. Both  these represent the statement that if `α` is a type and `a : α` is a term of type `α` then `a = a`.

```lean
#check rfl  -- rfl.{u} {α : Sort u} {a : α} : a = a

#check Eq.refl -- Eq.refl.{u_1} {α : Sort u_1} (a : α) : a = a
```

Notice that in `rfl` _both_ the arguments `α` and `a` are in braces while in `Eq.refl` the argument `α` is in braces while `a` in in parenthesis. When calling a function, the arguments in parenthesis are to be specified, while those in braces are to be _inferred_ (if possible).

If we prepend `@` to a function name then all arguments become explicit. Conversely we can ask Lean to infer arguments by using `_`. The following examples of similar proofs should clarify these.

```lean
/- argument `a` given explicitly -/
example : 2 = 2 := Eq.refl 2

/- notation `@` used and both `α` and `a` are given explicitly-/
example : 3 = 3 := @Eq.refl ℕ 3

/- the argument `a` is inferred-/
example : 1 + 2 = 3 := Eq.refl _

/- notation `@` used but `α` is inferred-/
example : 3  = 3 := @Eq.refl _ 3
```

### Incorrect proofs

Of course the right hand side may not be a proof of the statement in the left hand side. As mentioned above, this is equivalent to the term on the right-hand side having the _type_ specified in the left hand side.

For example, consdier the following (incorrect) proof.
```lean
theorem wrong : 1 + 2 = 2 := rfl
```

We get the following error message:
```lean
type mismatch
  rfl
has type
  1 + 2 = 1 + 2 : Prop
but is expected to have type
  1 + 2 = 2 : Prop
```

This is because Lean inferred that `a` is `1 + 2` (and `α` is `ℕ`) and deduced that the type of the right hand side is `1 + 2 = 1 + 2`. This is not equal to the type specified in the left hand side, `1 + 2 = 2`.

We get a different type of error if the statement we are trying to prove is not an equality. For example, consider the following (incorrect) proof.
```lean
example : 1 ≤ 2 := rfl
```

Here Lean is not able to infer `a` (or even `α`) and so we get the following error message:

```lean
type mismatch
  rfl
has type
  ?m.690 = ?m.690 : Prop
but is expected to have type
  1 ≤ 2 : Prop
```

All that Lean knows about `rfl` is that it is a proof that `a = a`, but the statement is not an equality. So Lean assign a _metavariable_ `?m.690` to `a` and cannot _solve_ for the metavariable, giving an error as above.
-/

#check rfl  -- rfl.{u} {α : Sort u} {a : α} : a = a

#check Eq.refl -- Eq.refl.{u_1} {α : Sort u_1} (a : α) : a = a

-- example : 2 = 2 := 2 = 2

-- theorem wrong : 1 + 2 = 2 := rfl

example : 1 + 2 = 3 := Eq.refl _

-- example : 1 ≤ 2 := rfl

-- def not_a_nat : ℕ := "you cannot fool me"

/-!
### Proofs versus Definitions; Sorts and Types

To clarify the similarities and differences between a definition and a theorem, as well as sorts and types, we look at the following example.
-/
/-- Definition of `two` for comparison -/
def two : ℕ := 2
-- def two' : ℕ := ℕ
/-!
As we have remarked, this is formally very similar to the proof of a theorem. To see the differences, we see some associated types:

```lean
#check 1 = 1 -- 1 = 1 : Prop
#check ℕ -- ℕ : Type

/- The universe of propositions. Prop ≡ Sort 0-/
#check Prop -- Prop : Type

/- A type universe. Type ≡ Type 0, Type u ≡ Sort (u + 1)-/
#check Type -- Type : Type 1
```

The main difference between the two cases is that the type of a theorem is a _proposition_, which is a special _type_ (i.e., a type in `Prop`). The type of `Prop` is `Type`, which is a type in the next universe up and is the type of `ℕ`. The universes form so called _Sorts_, with all but the first being _Types_.

In other closely related foundational systems (such as _Homotopy Type Theory_), this distinction is not present and all uninverses are _Types_. In Lean _propositions_, i.e., elements of `Prop` (such as `1 = 1`) have a special property that two terms (i.e., proofs) of a proposition are equal by definition.

Propositions are taken to be at "Types at level -1", i.e.,  "Sorts at level 0". This makes it convenient to exclude propositions in some cases where it is necessary to do so (as we will see later).
-/

example : 2 = 2 := Eq.refl 2

example : 3 = 3 := @Eq.refl ℕ 3

example : 3  = 3 := @Eq.refl _ 3

#check 1 = 1 -- 1 = 1 : Prop
#check ℕ -- ℕ : Type

/- The universe of propositions. Prop ≡ Sort 0-/
#check Prop -- Prop : Type

/- A type universe. Type ≡ Type 0, Type u ≡ Sort (u + 1)-/
#check Type -- Type : Type 1


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

The complex numbers, modelled as R^2 in the obvious way.
-/
import complex.basic -- tutorial level
/-!

# Level 1: I

I find it unbelievable that we have written quite a lot of code about the complex numbers
and we've still never defined i, or j, or I, or $$\sqrt{-1}$$, or whatever it's called.
Why don't you supply the definition, and make the basic API?

All the proofs below are sorried. You can try them in tactic mode
by replacing `sorry` with `begin end` and then starting to write
tactics in the `begin end` block.
-/

namespace complex

/-- complex.I is the square root of -1 above the imaginary axis -/
def I : ℂ := ⟨0, 1⟩

/-

 Easy lemmas, tagged with `simp` so Lean can prove things about `I` by equating
 real and imaginary parts.

-/

/-- re(I) = 0 -/
@[simp] lemma I_re : re(I) = 0 :=
begin
  refl
end

/-- im(I) = 1 -/
@[simp] lemma I_im : im(I) = 1 :=
begin
  refl
end

/-- I*I = -1 -/
@[simp] lemma I_mul_I : I * I = -1 :=
begin
  -- suffices to check real and imaginary parts
  ext;
  -- do them both at once
  simp,
end

-- Boss level. Hint: don't forget ext_iff

/-- I is non-zero -/
lemma I_ne_zero : (I : ℂ) ≠ 0 :=
begin
  -- by contradiction
  intro h,
  -- what does it mean for two complex numbers to be equal
  rw ext_iff at h,
  -- this is now just a logic puzzle
  simp * at *,
end

end complex


-- La_congruencia_modulo_2_es_una_relacion_de_equivalencia.lean
-- La congruencia módulo 2 es una relación de equivalencia
-- José A. Alonso Jiménez
-- Sevilla, 1 de agosto de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Se define la relación R entre los números enteros de forma que x está
-- relacionado con y si x-y es divisible por 2. Demostrar que R es una
-- relación de equivalencia.
-- ---------------------------------------------------------------------

import data.int.basic
import tactic

def R (m n : ℤ) := 2 ∣ (m - n)

-- 1ª demostración
example : equivalence R :=
begin
  repeat {split},
  { intro x,
    unfold R,
    rw sub_self,
    exact dvd_zero 2, },
  { intros x y hxy,
    unfold R,
    cases hxy with a ha,
    use -a,
    calc y - x
         = -(x - y) : (neg_sub x y).symm
     ... = -(2 * a) : by rw ha
     ... = 2 * -a   : neg_mul_eq_mul_neg 2 a, },
  { intros x y z hxy hyz,
    cases hxy with a ha,
    cases hyz with b hb,
    use a + b,
    calc x - z
         = (x - y) + (y - z) : (sub_add_sub_cancel x y z).symm
     ... = 2 * a + 2 * b     : congr_arg2 ((+)) ha hb
     ... = 2 * (a + b)       : (mul_add 2 a b).symm , },
end

-- 2ª demostración
example : equivalence R :=
begin
  repeat {split},
  { intro x,
    simp [R], },
  { rintros x y ⟨a, ha⟩,
    use -a,
    linarith, },
  { rintros x y z ⟨a, ha⟩ ⟨b, hb⟩,
    use a + b,
    linarith, },
end


/-Proof of the irrationality of root 2 using natural numbers

NOTE : in order to easily access lemmas which I did not the names of,
I used 'library_search' and 'suggest' tactics. The library, mathlib,
contains lemmas have been built up from the axioms by members of the lean community
and are consistent with the axioms of the specific number system and also
of the logical system.

Here is an example of such a theorem:

--theorem modeq_mul_left (a b n c : ℕ) (h : a ≡ b [MOD n]) : c * a ≡ c * b [MOD n] :=
--modeq_of_dvd_of_modeq (dvd_mul_left _ _) $ modeq_mul_left' _ h

I also used simplifiers such as norm_num that can perform basic elementary arithmetic, or
linarith which simplifies inequalities, which help the process somewhat, and prevent the users from having to
recall Peano's axioms every time.
-/

/-First we can import solely the lemmas we want from the library.
For our proof we are concerned with natural numbers, mods, and parities.-/

import tactic.suggest
import data.nat.basic
import data.nat.modeq
import data.nat.parity
import data.nat.gcd

/- While working in namespace, we can refer to identifiers by their shorter
names. For example, here nat.even can simply be written as even. -/
namespace nat
namespace modeq

---Let us start off with a relatively straightforward proof, using forward reasoning.
lemma even_squares_are_multiples_of_4 (x : ℕ ) : x.even → 4 ∣ (x * x) :=
begin
  intros h, --introduces the premise as a hypothesis
  unfold even at h, --mathlib has already defined even numbers in this way; def even (n : nat) : Prop := 2 ∣ n
  exact mul_dvd_mul h h, --exact matches a hypothesis with the desired result; if there is a match, then the proof is complete.
end --when a proof is verified, readers should see 'goals accomplished' in the Lean infoview.

/-In fact, we can make the above proof only one line long. This makes it harder to understand
but its concision is beneficial when compiling a library of such lemmas.
Note that it does not have the begin end structure, meaning that it does not use any
tactics, and this is called a proof term. In fact, this syntax is very similar to
what Lean stores behind the scenes.-/
lemma even_squares_are_multiples_of_4_shorter (x : ℕ ) : x.even → 4 ∣ (x * x) :=
λ x, mul_dvd_mul x x --note the λ from lambda calculus

--Now let us verify that the roots of all even square numbers are even.
lemma root_of_even_is_even (x s : ℕ ) : even (x * x) →  even x :=
begin
  contrapose, --applies a logical theorem that says {p → q} ↔ {not q → not p} in lean 'not' is ¬
  intros h,
  rw not_even_iff at h, /-rw stands for rewrite, and can be used to replace equivalent statements, such as iff-/
  --imported lemma: not_even_iff {n : ℕ} : ¬ even n ↔ n % 2 = 1 :=
  --(meaning not even numbers are 1 mod 2)
  rw not_even_iff,
  have i : x ≡ 1 [MOD 2],/-the have tactic allows us to introduce a hypothesis, which is accepted as such if
  we can prove it within the existing hypotheses we have. We do this in the subsequent curly brackets{}-/
  --Essentially here we are trying to convert h into a usable form for theorem
  {exact h,},
  have j : (x * x ) ≡ x * 1 [MOD 2], --again converting into a usable form for theorem
  {apply modeq_mul_left, /-applied imported theorem modeq_mul_left (c : ℕ) (h : a ≡ b [MOD n]) : c * a ≡ c * b [MOD n] :=
  modeq_of_dvd_of_modeq (dvd_mul_left _ _) $ modeq_mul_left' _ h-/
  /-the apply tactic is a form of backwards reasoning that replaces the
  goal with a premise from a lemma or hypothesis that would need to be proven instead-/
  exact i,},
  simp at j, --simplifier tactic
  have k : x * x ≡ 1 [MOD 2],
  {calc x * x ≡ x [MOD 2] : by apply j
         ... ≡ 1 [MOD 2] : by apply i,},
  exact k,
end

/- The structure of the following proof consists mostly of forwards reasoning where we
introduce an easily provable hypothesis by the tactic have { }, eventually
deriving the hypotheses we need for the final tautology, with what we hope
to be a natural flavour.-/

/- Now we finally have the tools to prove the irrationality of root 2.
This is equivalent to saying that we cannot write root 2 as an irreduicible fraction
a/b where a and b are natural numbers. So a and b must be coprime.-/

lemma root_two_irrational (a b d : ℕ ) : (∀ a b, nat.coprime a b) → (∃ a b, a * a = 2 * b * b) → false :=
begin
  intros p e,
  cases e with a ha, --removes the existential quantifier ∃ by specifying an a
  cases ha with b hb,
  have eaa : even (a * a),
  {use (b * b),
  rw ← mul_assoc,
  exact hb,},
  have ea : even a,
  {apply root_of_even_is_even, --our own lemma, now in usable form
  exact a,
  exact eaa,},
  have foura : 4 ∣ a * a,
  {apply even_squares_are_multiples_of_4, --our own lemma
  exact ea,},
  rw hb at foura,
  have fourb : 2 * 2 ∣ 2 * (b * b),
  {rw ← mul_assoc,
  norm_num,
  exact foura,},
  have ebb : even (b * b),
  {unfold even,
  rw nat.mul_dvd_mul_iff_left _ at fourb,/-imported nat.mul_dvd_mul_iff_left : ∀ {a b c : ℕ},
  0 < a → (a * b ∣ a * c ↔ b ∣ c)-/
  exact fourb,
  norm_num,},
  have eb : even b,
  {apply root_of_even_is_even,
   exact b,
   exact ebb,},
  unfold even at ea eb,
  have : 2 ∣ gcd a b,
  {apply dvd_gcd,
  exact ea,
  exact eb,},
  unfold nat.coprime at p,
  rw p at this,
  have that : ¬ 2 ∣ 1,
  {norm_num,},--another simplifier tactic
  tauto, --short for a tautology → false
end

end modeq
end nat

/-Notice that we have not explicitly been able to define the irrational numbers, but there exists
a section of the library which has already done this, and we leave it here for the reader's
interest:

/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Yury Kudryashov.
-/

def irrational (x : ℝ) := x ∉ set.range (coe : ℚ → ℝ)

/- If `x^n`, `n > 0`, is integer and is not the `n`-th power of an integer, then
`x` is irrational. -/

theorem irrational_nrt_of_notint_nrt {x : ℝ} (n : ℕ) (m : ℤ)
  (hxr : x ^ n = m) (hv : ¬ ∃ y : ℤ, x = y) (hnpos : 0 < n) :
  irrational x :=
begin
  rintros ⟨⟨N, D, P, C⟩, rfl⟩,
  rw [← cast_pow] at hxr,
  have c1 : ((D : ℤ) : ℝ) ≠ 0,
  { rw [int.cast_ne_zero, int.coe_nat_ne_zero], exact ne_of_gt P },
  have c2 : ((D : ℤ) : ℝ) ^ n ≠ 0 := pow_ne_zero _ c1,
  rw [num_denom', cast_pow, cast_mk, div_pow, div_eq_iff_mul_eq c2,
      ← int.cast_pow, ← int.cast_pow, ← int.cast_mul, int.cast_inj] at hxr,
  have hdivn : ↑D ^ n ∣ N ^ n := dvd.intro_left m hxr,
  rw [← int.dvd_nat_abs, ← int.coe_nat_pow, int.coe_nat_dvd, int.nat_abs_pow,
    nat.pow_dvd_pow_iff hnpos] at hdivn,
  have hD : D = 1 := by rw [← nat.gcd_eq_right hdivn, C.gcd_eq_one],
  subst D,
  refine hv ⟨N, _⟩,
  rw [num_denom', int.coe_nat_one, mk_eq_div, int.cast_one, div_one, cast_coe_int]
end
-/