import algebra.big_operators
import analysis.special_functions.exp_log
import to_mathlib_maybe.log

import rnd_var

---- SHANNON ENTROPY

open finset rnd_var

open_locale big_operators  -- this enables the notation

universe x

variables
{ι : Type x} -- indexing type
[fintype ι] -- tell Lean that the set of all elements ι is finite.
[decidable_eq ι] -- and that its elements can be compared for equality

noncomputable theory

/--
Definition: Shannon entropy.
-/
def Shannon_entropy (X : ι → ℝ) [rnd_var X] : ℝ :=
- ∑ i, (X i) * real.log(X i)

-- notation `H(`X`)` := Shannon_entropy X

/--
Theorem (non-negativity): Shannon entropy is non-negative for
any discrete random variable.
-/
theorem Shannon_entropy_nonneg (X : ι → ℝ) [rnd_var X] :
Shannon_entropy X ≥ 0 :=
begin
  intros,
  rw Shannon_entropy,
  -- move the minus inside
  simp only [← mul_neg_eq_neg_mul_symm, ← sum_neg_distrib],
  -- each term in the sum is nonnegative
  have H2 : ∀ i, - (X i) * real.log(X i) ≥ 0, {
    intros,
    have h : - (X i) * real.log(X i) = (X i) * (- real.log(X i)), by linarith,
    rw h,
    apply mul_nonneg,
    {apply probs_nonneg},
    {
      rw neg_nonneg,
      apply real.log_nonpos,
      {apply probs_nonneg},
      {apply probs_le_one},
    },
  },
  -- so we have that the whole sum is nonneg
  apply sum_nonneg,
  simp only [neg_mul_eq_neg_mul_symm, ge_iff_le, mul_neg_eq_neg_mul_symm, neg_nonneg] at *,
  intros i hi,
  exact H2 i,
end

lemma sum_nonneg_zero {f : ι → ℝ} (hf1 : ∑ i, f i = 0) (hf2: ∀ i, 0 ≤ f i) :
∀ i, f i = 0 :=
begin
  intro i,
  have H : ∑ i, f i = 0 ↔ ∀ i, f i = 0, {
    rw sum_eq_zero_iff_of_nonneg,
    {
      finish,
    },
    {
      simp only [hf2],
      finish,
    },
  },
  have H2 : ∀ i, f i = 0, {
    exact H.1 hf1,
  },
  apply H2,
end

lemma helper4 (X : ι → ℝ) :
(∀ (i : ι), X i = 0 ∨ X i = 1) → (∀ (i : ι), X i = 0 ∨ real.log(X i) = 0) :=
begin
  intros hi i,
  have H : X i = 0 ∨ X i = 1, by exact hi i,
  cases H with x0 log0,
  left, exact x0,
  right, simp only [log0, real.log_one],
end

lemma helper5 {X : ι → ℝ} :
(∃ j, ∀ i, ite (i = j) (X i = 1) (X i = 0)) → (∀ i, X i = 0 ∨ X i = 1) :=
begin
  contrapose,
  intro h,
  push_neg,
  push_neg at h,
  cases h with i hi,
  cases hi,
  intro j,
  use i,
  split_ifs,
  exact hi_right,
  exact hi_left,
end

/--
Theorem (Minimum value): Shannon entropy vanishes if and only if
X is a deterministic variable.

This is property 10.1.4 here (https://arxiv.org/pdf/1106.1445.pdf)
-/
theorem Shannon_entropy_zero_iff_deterministic (X : ι → ℝ) [rnd_var X] :
Shannon_entropy X = 0 ↔ is_deterministic X :=
begin
  -- we begin by asking Lean to provide the meanings of the
  -- words we've taught it. We use the rewrite tactic for that.
  rw [is_deterministic, Shannon_entropy],
  -- We split the two directions of the iff.
  split,
  {
    -- we prove one direction
    intro H0,
    -- rewrite in a more convenient form
    simp only [← real.log_inv, ← mul_neg_eq_neg_mul_symm, ← sum_neg_distrib] at H0,
    -- show each term in the sum must be zero
    have H2 : ∀ (i : ι), (X i) * real.log((X i)⁻¹) = 0,
    {
      apply sum_nonneg_zero,
      exact H0,
      intro i,
      rw mul_nonneg_iff,
      left,
      split, {apply probs_nonneg}, {apply real.log_inv_nonneg, apply probs_nonneg, apply probs_le_one},
    },
    -- if the product is zero then one of the factors must be zero
    have H3 : ∀ (i : ι), (X i = 0) ∨ (real.log((X i)⁻¹) = 0),
    {
      intros i,
      specialize H2 i,
      rw mul_eq_zero at H2,
      exact H2,
    },
    have H4 : ∀ (i : ι), (X i = 0) ∨ (X i = 1),
    {
      intros i,
      specialize H3 i,
      cases H3 with l r,
      left, exact l,
      have hxi : 0 ≤ X i,
      {
        apply probs_nonneg i,
        exact _inst_3,
      },
      replace hxi := le_iff_lt_or_eq.mp hxi,
      cases hxi with xi0 xi1,
      right,
      rw real.log_inv at r,
      norm_num at r,
      exact real.one_of_log_zero_of_pos (X i) xi0 r,
      left,
      linarith,
    },
    apply delta_if_det, exact H4,
  },
  {
    -- we prove the other direction
    intro h,
    simp only [← sum_neg_distrib, ← mul_neg_eq_neg_mul_symm, ← real.log_inv],
    rw sum_eq_zero, norm_num,
    apply helper4,
    apply helper5,
    cases h with j hj,
    use j,
    finish,
  },
end

lemma
sum_of_constant_fun {f : ι → ℝ} {c : ℝ} (h1 : ∀ i, f i = c) :
∑ i, f i = c * fintype.card ι :=
begin
  have h' : ∑ i, f i = ∑ i : ι, c, {finish,},
  have h'' : ∑ i : ι, c = fintype.card(ι) * c,
  {
    rw finset.sum_const,
    finish,
  },
  finish,
end

/--
Theorem: The Shannon entropy of a uniform
random variable is log(n).
-/
theorem Shannon_entropy_of_uniform (X : ι → ℝ) [rnd_var X] :
is_uniform X → Shannon_entropy X = real.log(fintype.card ι) :=
begin
  intro hX,
  rw Shannon_entropy,
  have H : - ∑ i, X i * real.log (X i) = ∑ i, X i * real.log (1 / (X i)),
    by simp only [one_div, real.log_inv, mul_neg_eq_neg_mul_symm, sum_neg_distrib],
  unfold is_uniform at hX,
  -- we need to subsitute 1 / fintype.card ι for (X i).
  -- have H : ∀ i, X i * real.log (X i) = (1 / fintype.card ι) * real.log (1 / fintype.card ι), {
  --   sorry
  -- },
  -- have H2 :
  -- ∑ i, X i = ((1 / fintype.card ι) * real.log (1 / fintype.card ι)) * fintype.card ι, {
  --   apply sum_of_constant_fun X,
  -- },
  let f : ι → ℝ := λ i, (X i) * real.log (X i),
  let c : ℝ := (1 / fintype.card ι) * real.log (1 / fintype.card ι),
  have hX2 :
  ∀ i, f i = c, {
    sorry -- should be easy
  },
  -- should be easy from there.
  sorry
end


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

import tactic -- imports all the Lean tactics
import data.real.basic -- imports the real numbers

/-

# Limits of sequences in Lean

We give the standard `ε`, `N` definition of the limit of a sequence
and prove some theorems about them.

## lambda (λ) notation for functions

Here's how we define the functions from the naturals to the naturals
sending n to n^2 + 3:

-/

def f : ℕ → ℝ := λ n, n^2+3

/-

Mathematicians might write `n ↦ n^2+3` for this functions; indeed `λ` is
just prefix notation for the infix notation `↦` (i.e. you write `λ` at
the front and `↦` in the middle but they mean the same thing). You can
read more about function types in the "three kinds of types" section
of Part B of the course book.

Sometimes you might find yourself with a lambda-defined function
evaluated at a number. For example, you might see something like
`(λ n, n^2 + 3) 37`, which means "take the function sending
`n` to `n^2+3` and then evaluate it at 37". You can use the `dsimp`
(or `dsimp only`) tactic to simplify this to `37^2+3`.

The reason we need to know about function notation for this sheet
is that a sequence `x₀, x₁, x₂, …` of reals on this sheet will
be encoded as a function from `ℕ` to `ℝ` sending `0` to `x₀`, `1` to `x₁`
and so on.

## Limit of a sequence.

Here's the definition of the limit of a sequence.
-/

/-- If `a(n)` is a sequence of reals and `t` is a real, `tendsto a t`
is the assertion that the limit of `a(n)` as `n → ∞` is `t`. -/
def tendsto (a : ℕ → ℝ) (t : ℝ) : Prop :=
∀ ε > 0, ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε

/-

We've made a definition, so it's our job to now make the API
for the definition, i.e. prove some basic theorems about it.
-/

-- If your goal is `tendsto a t` and you want to replace it with
-- `∀ ε > 0, ∃ B, …` then you can do this with `rw tendsto_def`.
theorem tendsto_def {a : ℕ → ℝ} {t : ℝ} :
  tendsto a t ↔ ∀ ε, 0 < ε → ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε :=
begin
  -- true by definition
  refl,
end

-- the eagle-eyed viewers amongst you might have spotted
-- that `∀ ε > 0, ...` and `∀ ε, ε > 0 → ...` and `∀ ε, 0 < ε → ...`
-- are all definitionally equal, so `refl` sees through them.

/-

## The questions

Here are some basic results about limits of sequences.
See if you can fill in the `sorry`s with Lean proofs.
Note that `norm_num` can work with `|x|` if `x` is a numeral like 37,
but it can't do anything with it if it's a variable.
-/

/-- The limit of the constant sequence with value 37 is 37. -/
theorem tendsto_thirtyseven : tendsto (λ n, 37) 37 :=
begin
  rw tendsto_def,
  intros ε hε,
  use 100,
  intros n hn,
  norm_num,
  exact hε,
end

/-- The limit of the constant sequence with value `c` is `c`. -/
theorem tendsto_const (c : ℝ) : tendsto (λ n, c) c :=
begin
  intros ε hε,
  dsimp only,
  use 37,
  intros n hn,
  ring_nf,
  norm_num,
  exact hε,
end

/-- If `a(n)` tends to `t` then `a(n) + c` tends to `t + c` -/
theorem tendsto_add_const {a : ℕ → ℝ} {t : ℝ} (c : ℝ)
  (h : tendsto a t) :
  tendsto (λ n, a n + c) (t + c) :=
begin
  rw tendsto_def at h ⊢,
  ring_nf,
  exact h,
end

/-- If `a(n)` tends to `t` then `-a(n)` tends to `-t`.  -/
example {a : ℕ → ℝ} {t : ℝ} (ha : tendsto a t) :
  tendsto (λ n, - a n) (-t) :=
begin
  rw tendsto_def at ha ⊢,
  ring_nf,
  intros ε hε,
  specialize ha ε hε,
  cases ha with B hB,
  use B,
  intros n hn,
  specialize hB n hn,
  ring_nf at hB ⊢,
  have h : ∀ (x : ℝ), |(-x)| = |x|,
    sorry, -- how to prove basic results about |.|? See next sheet!
  rw ← h,
  ring_nf at hB ⊢,
  exact hB,
end


import data.set.basic
open set

/-
This problem tests your ability to
contruct proofs using case analysis
on a disjunction, ∀ elimination, and
or introduction.

Remember, To eliminate a ∀ in a goal,
of the form, ∀ x, P x, introduce an
arbitrary value x into your context
(using intro or assume) and then show
P x for that x. (Of course you will
pick a sensible name instead of x for
the value being introduced into the
context.) The idea is that by showing
P x for an arbitrarily assumed value,
x, you thereby show P x for any x, and
thus you show P x for ∀ x.
-/

example :
    ∀ (x : Type),
    ∀ (p q : x → Prop),
    (∀ x, p x) ∨ (∀ x, q x) →
    ∀ x, p x ∨ q x :=
_


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

import tactic

/-

# Prove that there exists infinitely many positive integers n such that
# 4n² + 1 is divisible both by 5 and 13.

This is the third question in Sierpinski's book "250 elementary problems
in number theory".

Maths proof: if n=4 then 4n^2+1=65 is divisible by both 5 and 13
so if n is congruent to 4 mod 5 and mod 13 (i.e if n=4+65*t)
then this will work.

There are various ways to formalise the statement that some set
of naturals is infinite. We suggest two here (although proving
they're the same is fiddly)

-/

-- The number-theoretic heart of the argument.
-- Note that "divides" is `\|` not `|`
lemma divides_of_cong_four (t : ℕ) : 5 ∣ 4 * (65 * t + 4)^2 + 1 ∧
13 ∣ 4 * (65 * t + 4)^2 + 1 :=
begin
  split,
  { use 3380*t^2 + 416*t + 13,
    ring },
  { use 1300*t^2 + 160*t + 5,
    ring }
end

-- There are arbitrarily large solutions to `5 ∣ 4*n^2+1 ∧ 13 ∣ 4*n^2+1`
lemma arb_large_soln : ∀ N : ℕ, ∃ n > N, 5 ∣ 4*n^2+1 ∧ 13 ∣ 4*n^2+1 :=
begin
  intro N,
  -- need to find t such that 65t+4>N. Just set t=N for simplicity
  use 65 * N + 4,
  split,
  { linarith },
  { apply divides_of_cong_four }
end

-- This is not number theory any more, it's switching between two
-- interpretations of "this set of naturals is infinite"
lemma infinite_iff_arb_large (S : set ℕ) : S.infinite ↔ ∀ N, ∃ n > N, n ∈ S :=
begin
  split,
  { intro h,
    have h2 := set.infinite.exists_nat_lt h,
    intro n,
    rcases h2 n with ⟨m, hm, h3⟩,
    use m,
    exact ⟨h3, hm⟩,
  },
  { contrapose!,
    intro h,
    rw set.not_infinite at h,
    let S2 : finset ℕ := set.finite.to_finset h,
    have h2 : ∃ B, ∀n ∈ S2, n ≤ B,
    { use finset.sup S2 id,
      intros,
      apply finset.le_sup H },
    cases h2 with N hN,
    use N,
    have h3 : ∀n : ℕ, n ∈ S ↔ n ∈ S2,
      intro n,
      exact (set.finite.mem_to_finset h).symm,
    intros n hn h4,
    rw h3 at h4,
    specialize hN n h4,
    linarith,
  }
end

-- Another way of stating the question (note different "|" symbols:
-- there's `|` for "such that" in set theory and `\|` for "divides" in number theory)
lemma infinite_set_of_solutions : {n : ℕ | 5 ∣ 4*n^2+1 ∧ 13 ∣ 4*n^2+1}.infinite :=
begin
  rw infinite_iff_arb_large,
  exact arb_large_soln,
end


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

import tactic -- imports all the Lean tactics

/-!

# Sets in Lean, sheet 7 : the sets API

It might have occurred to you that if you had a prove
things like commutativity of `∩` every time you needed it,
then things would get boring quite quickly.

Fortunately, the writers of Lean's mathematics library
already proved these things and *gave the proofs names*.
On the previous sheet you showed that you could do these
things yourself, so on this sheet I'll tell you the names
of the Lean versions of the proofs, and show you how to use them.

Here are some examples of proof names in Lean.

`set.union_comm A B : A ∪ B = B ∪ A`
`set.inter_comm A B : A ∩ B = B ∩ A`
-- note `A ∪ B ∪ C` means `(A ∪ B) ∪ C`
`set.union_assoc A B C : A ∪ B ∪ C = A ∪ (B ∪ C)`
 -- similar comment for `∩`
`set.inter_assoc A B C : A ∩ B ∩ C = A ∩ (B ∩ C)`

(A technical point: note that `∪` and `∩` are "left associative",
in contrast to `→` which is right associative: `P → Q → R` means `P → (Q → R)`).

These lemmas are all in the `set` namespace (i.e. their full names
all begin with `set.`). It's annoying having to type `set.` all the time
so we `open set` which means you can skip it and just write things
like `union_comm A B`.

Note that `union_comm A B` is the *proof* that `A ∪ B = B ∪ A`.
In the language of type theory, `union_comm A B` is a
"term of type `A ∪ B = B ∪ A`". Let's just compare this with what we
were seeing in the logic sheet:

```
P : Prop -- P is a proposition
hP : P -- hP is a proof of P
```

Similarly here we have
```
A ∪ B = B ∪ A : Prop -- this is the *statement*
union_comm A B : A ∪ B = B ∪ A -- this is the *proof*
```

## New tactics you will need

* `rw` ("rewrite")

### The `rw` tactic (again)

We've seen `rw h,` being used if `h : P ↔ Q`; it changed all `P`s to `Q`s
in the goal. But `rw h,` also works for `h : A = B` -- it changes all
`A`s to `B`s in the goal. So `rw` is a "substitute in" command.

After the substitution has occurred, Lean tries `refl` just to see if it works.
For example if `A`, `B`, `C` are sets, and our context is

```
h : A = B
⊢ A ∩ C = B ∩ C
```

then `rw h` changes the goal into `B ∩ C = B ∩ C` and then solves
the goal automatically, because `refl` works.

`rw` doesn't just work for hypotheses -- if there is a theorem
in Lean's maths library (like `inter_comm A B`, which is a proof
that `A ∩ B = B ∩ A`) then you can `rw inter_comm A B` and it
will change `A ∩ B` in the goal to `B ∩ A`.

`rw` is a smart tactic. If the goal is

```
⊢ (A ∪ B) ∩ C = D
```

and you want to change it to `⊢ (B ∪ A) ∩ C = D` then you don't
have to write `rw union_comm A B`, you can write `rw union_comm`
and Lean will figure out what you meant. You can also write
`rw union_comm A` or even `rw union_comm _ B` if you want to give
it hints about exactly which union to commute.

-/

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`

-- You can solve this one with `exact union_comm A B` or `rw union_comm`
example : A ∪ B = B ∪ A :=
begin
  sorry
end

example : (A ∪ B) ∩ C = C ∩ (B ∪ A) :=
begin
  sorry
end

/-
Note that `rw union_comm A` will change `A ∪ ?` to
`? ∪ A` for some set `?`. You can even do `rw union_comm _ B` if you
want to change `? ∪ B` to `B ∪ ?`
-/

example : A ∪ B ∪ C = C ∪ B ∪ A :=
begin
  sorry
end

example : x ∈ A ∩ B ∩ C ↔ x ∈ B ∩ C ∩ A :=
begin
  sorry
end

example : ((A ∪ B) ∪ C) ∪ D = A ∪ (B ∪ (C ∪ D)) :=
begin
  sorry
end

example : A ∪ B = C ∩ D → B ∪ A ∪ E = E ∪ (C ∩ D) :=
begin
  sorry
end

/-

Here are some distributivity results.

`set.union_distrib_left A B C : A ∪ (B ∩ C) = (A ∪ B) ∩ (A ∪ C)`
`set.union_distrib_right A B C : (A ∩ B) ∪ C = (A ∪ C) ∩ (B ∪ C)`
`set.inter_distrib_left A B C : A ∩ (B ∪ C) = (A ∩ B) ∪ (A ∩ C)`
`set.inter_distrib_right A B C : (A ∪ B) ∩ C = (A ∩ C) ∪ (B ∩ C)`

Which ones will you use to do the following? Note that because of the `open set`
line much earlier, you don't have to write the `set.` part of the name.

-/

example : (A ∪ B) ∩ (C ∪ D) = (A ∩ C) ∪ (B ∩ C) ∪ (A ∩ D) ∪ (B ∩ D) :=
begin
  sorry
end

/-

## Top tips

How the heck are we supposed to remember all the names of these lemmas
in Lean's maths library??

Method 1) There is a logic to the naming system! You will slowly
get used to the logic.

Method 2) Until then, just use the `library_search` tactic
to look up proofs.

If you run the below code

```
example : A ∩ B = B ∩ A :=
begin
  library_search
end
```

you'll see some output on the right hand side of the screen containing
the name of the lemma which proves this example.

-/


import .love04_functional_programming_demo

/-! # LoVe Homework 4: Functional Programming

Homework must be done individually. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1 (2 points): Maps

1.1 (1 point). Complete the following definition. The `map_btree` function
should apply its argument `f` to all values of type `α` stored in the tree and
otherwise preserve the tree's structure. -/

def map_btree {α β : Type} (f : α → β) : btree α → btree β :=
sorry

/-! 1.2 (1 point). Prove the following lemma about your `map_btree` function. -/

lemma map_btree_iden {α : Type} :
  ∀t : btree α, map_btree (λa, a) t = t :=
sorry

/-! ## Question 2 (4 points): Tail-Recursive Factorials

Recall the definition of the factorial functions: -/

#check fact

/-! 2.1 (2 points). Experienced functional programmers tend to avoid definitions
such as the above, because they lead to a deep call stack. Tail recursion can be
used to avoid this. Results are accumulated in an argument and returned. This
can be optimized by compilers. For factorials, this gives the following
definition: -/

def accufact : ℕ → ℕ → ℕ
| a 0       := a
| a (n + 1) := accufact ((n + 1) * a) n

/-! Prove that, given 1 as the accumulator `a`, `accufact` computes `fact`.

Hint: You will need to prove a generalized version of the statement as a
separate lemma or `have`, because the desired property fixes `a` to 1, which
yields a too weak induction hypothesis. -/

lemma accufact_1_eq_fact (n : ℕ) :
  accufact 1 n = fact n :=
sorry

/-! 2.2 (2 points). Prove the same property as above again, this time as a
"paper" proof. Follow the guidelines given in question 1.4 of the exercise. -/

-- enter your paper proof here

/-! ## Question 3 (3 points): Gauss's Summation Formula -/

-- `sum_upto f n = f 0 + f 1 + ⋯ + f n`
def sum_upto (f : ℕ → ℕ) : ℕ → ℕ
| 0       := f 0
| (m + 1) := sum_upto m + f (m + 1)

/-! 3.1 (2 point). Prove the following lemma, discovered by Carl Friedrich Gauss
as a pupil.

Hint: The `mul_add` and `add_mul` lemmas might be useful to reason about
multiplication. -/

#check mul_add
#check add_mul

lemma sum_upto_eq :
  ∀m : ℕ, 2 * sum_upto (λi, i) m = m * (m + 1) :=
sorry

/-! 3.2 (1 point). Prove the following property of `sum_upto`. -/

lemma sum_upto_mul (f g : ℕ → ℕ) :
  ∀n : ℕ, sum_upto (λi, f i + g i) n = sum_upto f n + sum_upto g n :=
sorry

end LoVe


/-
Copyright (c) 2021 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import topology.urysohns_lemma
import topology.continuous_function.bounded

/-!
# Urysohn's lemma for bounded continuous functions

In this file we reformulate Urysohn's lemma `exists_continuous_zero_one_of_closed` in terms of
bounded continuous functions `X →ᵇ ℝ`. These lemmas live in a separate file because
`topology.continuous_function.bounded` imports too many other files.

## Tags

Urysohn's lemma, normal topological space
-/

open_locale bounded_continuous_function
open set function

/-- Urysohns lemma: if `s` and `t` are two disjoint closed sets in a normal topological space `X`,
then there exists a continuous function `f : X → ℝ` such that

* `f` equals zero on `s`;
* `f` equals one on `t`;
* `0 ≤ f x ≤ 1` for all `x`.
-/
lemma exists_bounded_zero_one_of_closed {X : Type*} [topological_space X] [normal_space X]
  {s t : set X} (hs : is_closed s) (ht : is_closed t)
  (hd : disjoint s t) :
  ∃ f : X →ᵇ ℝ, eq_on f 0 s ∧ eq_on f 1 t ∧ ∀ x, f x ∈ Icc (0 : ℝ) 1 :=
let ⟨f, hfs, hft, hf⟩ := exists_continuous_zero_one_of_closed hs ht hd
in ⟨⟨f, 1, λ x y, real.dist_le_of_mem_Icc_01 (hf _) (hf _)⟩, hfs, hft, hf⟩

/-- Urysohns lemma: if `s` and `t` are two disjoint closed sets in a normal topological space `X`,
and `a ≤ b` are two real numbers, then there exists a continuous function `f : X → ℝ` such that

* `f` equals `a` on `s`;
* `f` equals `b` on `t`;
* `a ≤ f x ≤ b` for all `x`.
-/
lemma exists_bounded_mem_Icc_of_closed_of_le {X : Type*} [topological_space X] [normal_space X]
  {s t : set X} (hs : is_closed s) (ht : is_closed t) (hd : disjoint s t)
  {a b : ℝ} (hle : a ≤ b) :
  ∃ f : X →ᵇ ℝ, eq_on f (const X a) s ∧ eq_on f (const X b) t ∧ ∀ x, f x ∈ Icc a b :=
let ⟨f, hfs, hft, hf01⟩ := exists_bounded_zero_one_of_closed hs ht hd
in ⟨bounded_continuous_function.const X a + (b - a) • f,
  λ x hx, by simp [hfs hx], λ x hx, by simp [hft hx],
  λ x, ⟨by dsimp; nlinarith [(hf01 x).1], by dsimp; nlinarith [(hf01 x).2]⟩⟩


/- The expression And.intro h1 h2 builds a proof of p ∧ q using proofs
h1 : p and h2 : q
The order of operations is as follows: unary negation ¬ binds most strongly, then ∧, then ∨, then →, and finally ↔
-/

variable (p q : Prop)

--The example command states a theorem without naming it or storing it in the permanent context. Essentially, it just checks that the given term has the indicated type.
example (hp : p) (hq : q) : p ∧ q := And.intro hp hq

#check fun (hp : p) (hq : q) => And.intro hp hq

-- The expression And.left h creates a proof of p from a proof h : p ∧ q
example (h : p ∧ q) : p := And.left h
example (h : p ∧ q) : q := And.right h

-- We can now prove p ∧ q → q ∧ p with the following proof term.
example (h : p ∧ q) : q ∧ p  :=
  And.intro (And.right h) (And.left h)


-- Las partes simétricas de las reflexivas son reflexivas
-- ======================================================

-- ----------------------------------------------------
-- Ej. 1. La parte simétrica de una relación R es la
-- relación S definida por
--    S x y := R x y ∧ R y x
--
-- Demostrar que la parte simétrica de una relación
-- reflexiva es reflexiva.
-- ----------------------------------------------------

section

parameter A : Type
parameter R : A → A → Prop
parameter reflR : reflexive R

include reflR

def S (x y : A) := R x y ∧ R y x

-- 1ª demostración
example : reflexive S :=
begin
  intro x,
  split,
  { exact (reflR x), },
  { exact (reflR x), },
end

-- 2ª demostración
example : reflexive S :=
assume x,
have R x x, from reflR x,
show S x x, from and.intro this this

-- 3ª demostración
example : reflexive S :=
assume x,
show S x x, from and.intro (reflR x) (reflR x)

-- 4ª demostración
example : reflexive S :=
assume x,
and.intro (reflR x) (reflR x)

-- 5ª demostración
example : reflexive S :=
λ x, ⟨reflR x, reflR x⟩

end


variables p q r : Prop

namespace mth1001

section theorems

-- Exercise 028:
/-
A theorem is a mechanism for naming a result. By _applying_ a theorem, we can produce new results.
Here is a theorem with its proof left as an exercise.
-/
theorem and_of_and : p ∧ q → q ∧ p :=
begin
  sorry
end

/-
The _type_ of any expression can be printed to the Infoview using `#check`.
The type of `and_of_and` is
  `∀ (p q : Prop), p ∧ q → q ∧ p`
This means that the theorem takes `p` and `q` propositions (i.e. terms of
type `Prop`) and returns a proof of `p ∧ q → q ∧ p`.

The symbol `∀` is read as 'for all' or 'for every' and is called the
universal quantifier. We'll return to a full explanation of `∀` later.
-/
#check and_of_and

/-
We apply this theorem by subsituting other quantities for `p` and `q`.]
Below, we take `q → r` for `p` and `q ∧ p` for `q` in `and_of_and`.
-/
example : (q → r) ∧ (q ∧ p) → (q ∧ p) ∧ (q → r) :=
and_of_and (q → r) (q ∧ p)

-- Even better, Lean can often infer the arguments to theorems if they
-- are replaced with the `_` placeholder:
example : (q → r) ∧ (q ∧ p) → (q ∧ p) ∧ (q → r) :=
and_of_and _ _

-- We can apply theorems in tactic mode. Using `exact` mimics the term-style proof.
example : (q → r) ∧ (q ∧ p) → (q ∧ p) ∧ (q → r) :=
begin
  exact and_of_and _ _,
end

/-
We don't need to use `_` here to explicitly request argument inference; the `apply` tactic
does this automatically.
-/
example : (q → r) ∧ (q ∧ p) → (q ∧ p) ∧ (q → r) :=
begin
  apply and_of_and,
end

-- Exercise 029:
-- Give either a tactic-style or term-style proof of the following.
theorem and_assoc1 : (p ∧ q) ∧ r → p ∧ (q ∧ r) :=
sorry

-- Exercise 030:
-- Using the previous theorem, give a one-line proof of the following.
example (a b : Prop) : ((a → b) ∧ b) ∧ (b → a) → (a → b) ∧ (b ∧ (b → a)) :=
sorry

-- Exercise 031:
/-
The next example is more challenging. Use `intro`, `apply`, `exact`,
and the previous two theorems.
-/
example (a b : Prop) : ((a → b) ∧ b) ∧ (b → a) → (b ∧ (b → a)) ∧ (a → b) :=
begin
  sorry  end

variables s t u : Prop

-- Exercise 032:
/-
Complete the following proof. The only tactics you are permitted to use are `apply` and `exact`
(each as many times as you like). You can only use these tactics with `and_of_and`,
`and_assoc`, or `h`.
-/
theorem and_assoc2 : s ∧ (t ∧ u) → (s ∧ t) ∧ u :=
begin
  intro h,
  sorry
end

end theorems

/-
SUMMARY:

* Theorems in Lean.
* `#check` to find the type of a theorem.
* Using theorems.
-/

end mth1001


/-
The connective, ∧, in predicate as in
propositional logic builds a proposition
that asserts that each of two propositions
is true. Given propositions P, Q, P ∧ Q
is also a proposition, and we judge it to
be true iff we judge P to be true and we
judge Q to be true. We judge P and Q to
be true iff we have evidence that they
are true, in the form of proofs. Our rule
for constructing evidence for P ∧ Q thus
demands evidence for P and evidence for
Q and yields evidence for P ∧ Q.
-/

#check and

/-
structure and (a b : Prop) : Prop :=
intro :: (left : a) (right : b)
-/

-- A polymorphic product type (in Prop)!

axioms (P Q : Prop) (p : P) (q : Q)

example : P := p
example : Q := q
example : P ∧ Q := and.intro p q
lemma paq : P ∧ Q := ⟨ p, q ⟩

#check paq
#reduce paq

#check and.elim_left

/-
def and.elim_left {a b : Prop} (h : and a b) : a := h.1
def and.elim_right {a b : Prop} (h : and a b) : b := h.2
-/

#reduce and.elim_left paq
#reduce and.elim_right paq

example : 0 = 0 ∧ 1 = 0 :=
and.intro (eq.refl 0) (eq.refl _)   -- stuck

lemma zzoo : 0 = 0 ∧ 1 = 1 :=
and.intro rfl rfl

#reduce zzoo.right

/-
Proof that and commutes and is
associative (you can regroup at
will, by associativity).
-/

example : P ∧ Q → Q ∧ P :=
λ (h : P ∧ Q), and.intro h.right h.left

/-
And is commutative and associative.
Prove it.
-/
axiom R : Prop

lemma and_assoc_forward : (P ∧ Q) ∧ R -> P ∧ (Q ∧ R) :=
λ h,
  (and.intro
    h.left.left
   (and.intro h.left.right h.right)
  )

/-
Full proof. and commutes
-/
example : P ∧ Q ↔ Q ∧ P :=

-- proof is pair: forward/backward proofs
iff.intro
  -- proof forward
  (λ h, ⟨ h.right, h.left ⟩ )
  -- proof backwards
  (λ h, ⟨ h.right, h.left ⟩)

/-
Full proof, modulo reverse direction,
that and is associative.
-/
example : (P ∧ Q) ∧ R ↔ P ∧ (Q ∧ R) :=
iff.intro
  (and_assoc_forward)
  (_)


section propositional

variables P Q R : Prop

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

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intros p not_p,
  exact not_p p,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro not_not_p,
  by_cases h: P,
    -- Case P
    exact h,
    -- Case ¬P
    exfalso,
    exact not_not_p h,
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 p_or_q,
  cases p_or_q with p q,
    --Case P
    right,
    exact p,
    --Case Q
    left,
    exact q,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro p_and_q,
  cases p_and_q with p q,
  split,
  exact q,
  exact p,
end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro np_or_q,
  intro p,
  cases np_or_q with np q,
    --Case ¬P
    exfalso,
    exact np p,
    --Case Q
    exact q,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intros p_or_q np,
  cases p_or_q with p q,
    --Case P
    exfalso,
    exact np p,
    --Case Q
    exact q,
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intros p_imp_q nq p,
  apply nq,
  exact p_imp_q p,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intros h p,
  by_cases q: Q,
    --Case Q
    exact q,
    --Case ¬Q
    have np := h q,
    exfalso,
    exact np p,
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 h,
  apply h,
  right,
  intro p,
  have p_or_np: P∨¬P,
    --Demons PV¬P
    left,
    exact p,
  exact h p_or_np,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intros h np,
  apply np,
  apply h,
  intro p,
  exfalso,
  exact np p,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intros p_or_q np_and_nq,
  cases np_and_nq with np nq,
  cases p_or_q with p q,
    --Case P
    exact np p,
    --Case Q
    exact nq q,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intro p_and_q,
  intro np_or_nq,
  cases p_and_q with p q,
  cases np_or_nq with np nq,
    --Case P
    exact np p,
    --Case Q
    exact nq q,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro n_p_or_q,
  split,
  --Parte ¬P
  intro p,
  apply n_p_or_q,
  left,
  exact p,
  --Parte ¬Q
  intro q,
  apply n_p_or_q,
  right,
  exact q,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intros np_and_nq p_or_q,
  cases np_and_nq with np nq,
  cases p_or_q with p q,
    --Case P
    exact np p,
    --Case Q
    exact nq q,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro n_p_and_q,
  by_cases h: Q,
    --Case Q
    right,
    intro p,
    apply n_p_and_q,
    split,
    exact p,
    exact h,
    --Case ¬Q
    left,
    exact h,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intros nq_or_np p_and_q,
  cases p_and_q with p q,
  cases nq_or_np with nq np,
    --Case P
    exact nq q,
    --Case Q
    exact np p,
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 h,
  cases h with p q_or_r,
  cases q_or_r with q r,
    --Case Q
    left,
    split,
    exact p,
    exact q,
    --case R
    right,
    split,
    exact p,
    exact r,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro h,
  cases h with p_and_q p_and_r,
    --Case P∧Q
    cases p_and_q with p q,
    split,
    exact p,
    left,
    exact q,
    --Case PVQ
    cases p_and_r with p r,
    split,
    exact p,
    right,
    exact r,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro h,
  cases h with p q_and_r,
    --Case P
    split,
    left,
    exact p,
    left,
    exact p,
    --Case Q∧R
    cases q_and_r with q r,
    split,
    right,
    exact q,
    right,
    exact r,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro h,
  cases h with p_or_q p_or_r,
  cases p_or_q with p q,
    --Case P
    left,
    exact p,
    --Case Q
    cases p_or_r with p r,
      --Case P
      left,
      exact p,
      --Case R
      right,
      split,
      exact q,
      exact r,
end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intros h p q,
  exact h ⟨p,q⟩,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intros h p_and_q,
  cases p_and_q with p q,
  apply h,
  exact p,
  exact q,
end

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

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

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

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

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

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro p_and_q,
  cases p_and_q with p q,
  exact p,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro p_and_q,
  cases p_and_q with p q,
  exact q,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  exact weaken_conj_right P P,
  intro p,
  split,
  repeat {exact p},
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  intro p_or_p,
  cases p_or_p with p1 p2,
  --Case P(left)
  exact p1,
  --Case P(right)
  exact p2,
  exact weaken_disj_right P P,
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
  intros h x p_x,
  apply h,
  existsi x,
  exact p_x,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intros h1 h2,
  cases h2 with x p_x,
  have n_p_x := h1 x,
  exact n_p_x p_x,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro h1,
  by_contra,
  apply h1,
  intro x,
  by_contra hboom,
  apply h,
  existsi x,
  exact hboom,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intros h1 h2,
  cases h1 with x n_p_x,
  have p_x := h2 x,
  exact n_p_x p_x,
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
  intros h1 h2,
  cases h1 with x p_x,
  have n_p_x := h2 x,
  exact n_p_x p_x,

end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intros h1 h2,
  cases h2 with x n_p_x,
  have p_x := h1 x,
  exact n_p_x p_x,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intros h x,
  by_contra hboom,
  apply h,
  existsi x,
  exact hboom,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro h,
  by_contra hboom,
  apply h,
  intros x p_x,
  apply hboom,
  existsi x,
  exact p_x,
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
  intro h,
  cases h with x px_and_qx,
  cases px_and_qx with p_x q_x,
  split,
  existsi x,
  exact p_x,
  existsi x,
  exact q_x,

end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro h,
  cases h with x px_or_qx,
  cases px_or_qx with px qx,
    --Case P x
    left,
    existsi x,
    exact px,
    -- Case Q x
    right,
    existsi x,
    exact qx,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro h,
  cases h with p_x q_x,
    --Case ∃x, P x
    cases p_x with x px,
    existsi x,
    left,
    exact px,
    --Case ∃x, Q x
    cases q_x with x qx,
    existsi x,
    right,
    exact qx,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro h,
  split,
  intro x,
  cases h x with px qx,
  exact px,
  intro x,
  cases h x with px qx,
  exact qx,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intros h x,
  cases h with p_x q_x,
  split,
  exact p_x x,
  exact q_x x,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intros h x,
  cases h with p_x q_x,
    --Case ∀x, P x
    left,
    exact p_x x,
    --Case ∀x, Q x
    right,
    exact q_x x,

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


/-
Copyright (c) 2019 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin

! This file was ported from Lean 3 source module data.list.nat_antidiagonal
! leanprover-community/mathlib commit 7b78d1776212a91ecc94cf601f83bdcc46b04213
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.List.Nodup
import Mathlib.Data.List.Range

/-!
# Antidiagonals in ℕ × ℕ as lists

This file defines the antidiagonals of ℕ × ℕ as lists: the `n`-th antidiagonal is the list of
pairs `(i, j)` such that `i + j = n`. This is useful for polynomial multiplication and more
generally for sums going from `0` to `n`.

## Notes

Files `Data.Multiset.NatAntidiagonal` and `Data.Finset.NatAntidiagonal` successively turn the
`List` definition we have here into `Multiset` and `Finset`.
-/

open List Function Nat

namespace List

namespace Nat

/-- The antidiagonal of a natural number `n` is the list of pairs `(i, j)` such that `i + j = n`. -/
def antidiagonal (n : ℕ) : List (ℕ × ℕ) :=
  (range (n + 1)).map fun i ↦ (i, n - i)
#align list.nat.antidiagonal List.Nat.antidiagonal

/-- A pair (i, j) is contained in the antidiagonal of `n` if and only if `i + j = n`. -/
@[simp]
theorem mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} : x ∈ antidiagonal n ↔ x.1 + x.2 = n := by
  rw [antidiagonal, mem_map]; constructor
  · rintro ⟨i, hi, rfl⟩
    rw [mem_range, lt_succ_iff] at hi
    exact add_tsub_cancel_of_le hi
  · rintro rfl
    refine' ⟨x.fst, _, _⟩
    · rw [mem_range, add_assoc, lt_add_iff_pos_right]
      exact zero_lt_succ _
    · exact Prod.ext rfl (by simp only [add_tsub_cancel_left])
#align list.nat.mem_antidiagonal List.Nat.mem_antidiagonal

/-- The length of the antidiagonal of `n` is `n + 1`. -/
@[simp]
theorem length_antidiagonal (n : ℕ) : (antidiagonal n).length = n + 1 := by
  rw [antidiagonal, length_map, length_range]
#align list.nat.length_antidiagonal List.Nat.length_antidiagonal

/-- The antidiagonal of `0` is the list `[(0, 0)]` -/
@[simp]
theorem antidiagonal_zero : antidiagonal 0 = [(0, 0)] :=
  rfl
#align list.nat.antidiagonal_zero List.Nat.antidiagonal_zero

/-- The antidiagonal of `n` does not contain duplicate entries. -/
theorem nodup_antidiagonal (n : ℕ) : Nodup (antidiagonal n) :=
  (nodup_range _).map ((@LeftInverse.injective ℕ (ℕ × ℕ) Prod.fst fun i ↦ (i, n - i)) fun _ ↦ rfl)
#align list.nat.nodup_antidiagonal List.Nat.nodup_antidiagonal

@[simp]
theorem antidiagonal_succ {n : ℕ} :
    antidiagonal (n + 1) = (0, n + 1) :: (antidiagonal n).map (Prod.map Nat.succ id) := by
  simp only [antidiagonal, range_succ_eq_map, map_cons, true_and_iff, Nat.add_succ_sub_one,
    add_zero, id.def, eq_self_iff_true, tsub_zero, map_map, Prod.map_mk]
  apply congr rfl (congr rfl _)
  ext; simp
#align list.nat.antidiagonal_succ List.Nat.antidiagonal_succ

theorem antidiagonal_succ' {n : ℕ} :
    antidiagonal (n + 1) = (antidiagonal n).map (Prod.map id Nat.succ) ++ [(n + 1, 0)] := by
  simp only [antidiagonal, range_succ, add_tsub_cancel_left, map_append, append_assoc, tsub_self,
    singleton_append, map_map, map]
  congr 1
  apply map_congr
  simp (config := { contextual := true }) [le_of_lt, Nat.succ_eq_add_one, Nat.sub_add_comm]
#align list.nat.antidiagonal_succ' List.Nat.antidiagonal_succ'

theorem map_swap_antidiagonal {n : ℕ} :
    (antidiagonal n).map Prod.swap = (antidiagonal n).reverse := by
  rw [antidiagonal, map_map, ← List.map_reverse, range_eq_range', reverse_range', ←
    range_eq_range', map_map]
  apply map_congr
  simp (config := { contextual := true }) [Nat.sub_sub_self, lt_succ_iff]
#align list.nat.map_swap_antidiagonal List.Nat.map_swap_antidiagonal

end Nat

end List


import missing.linear_algebra.matrix.pos_def

open real
open matrix
open_locale real
open_locale matrix
open_locale big_operators

noncomputable def matrix.quad_form {n : ℕ} (R : matrix (fin n) (fin n) ℝ) (x : fin n → ℝ) : ℝ :=
  x ⬝ᵥ R.mul_vec x

noncomputable def gaussian_pdf {n : ℕ} (R : matrix (fin n) (fin n) ℝ) (x : fin n → ℝ) : ℝ :=
  (1 / sqrt (((2 * π) ^ n) * R.det)) * exp  (- R⁻¹.quad_form x / 2)

noncomputable def covariance_matrix {N n : ℕ} (y : fin N → fin n → ℝ) : matrix (fin n) (fin n) ℝ :=
  λ i j : fin n, (∑ k : fin N, y k i * y k j) / N

lemma gaussian_pdf_pos {n : ℕ} (R : matrix (fin n) (fin n) ℝ) (y : fin n → ℝ) (h : R.pos_def):
  0 < gaussian_pdf R y :=
mul_pos (div_pos zero_lt_one
  (sqrt_pos.2 (mul_pos (pow_pos (mul_pos (by linarith) pi_pos) _) h.det_pos))) (exp_pos _)

lemma prod_gaussian_pdf_pos {N n : ℕ} (R : matrix (fin n) (fin n) ℝ) (y : fin N → fin n → ℝ)
    (h : R.pos_def):
  0 < ∏ i : fin N, gaussian_pdf R (y i) :=
finset.prod_pos (λ i hi, gaussian_pdf_pos _ _ h)

lemma log_prod_gaussianPdf {N n : ℕ} (y : fin N → fin n → ℝ) (R : matrix (fin n) (fin n) ℝ) (hR : R.pos_def) :
    (log ∏ i : fin N, gaussian_pdf R (y i))
    = ∑ i : fin N, (- (log (sqrt ((2 * π) ^ n)) + log (sqrt (det R))) + - R⁻¹.quad_form (y i) / 2) :=
begin
    have : ∀ i,
      i ∈ finset.univ → gaussian_pdf R (y i) ≠ 0 := λ i hi, ne_of_gt (gaussian_pdf_pos _ _ hR),
    have sqrt_2_pi_n_R_det_ne_zero: sqrt ((2 * π) ^ n * R.det) ≠ 0 :=
      ne_of_gt (sqrt_pos.2 (mul_pos (pow_pos (mul_pos (by simp) pi_pos) _) hR.det_pos)),
    rw [log_prod finset.univ (λ i, gaussian_pdf R (y i)) this],
    unfold gaussian_pdf,
    apply congr_arg (finset.sum finset.univ),
    ext i,
    rw [log_mul, log_div, sqrt_mul, log_mul, log_exp, log_one, zero_sub],
    exact ne_of_gt (sqrt_pos.2 (pow_pos (mul_pos (by simp) pi_pos) _)),
    exact ne_of_gt (sqrt_pos.2 hR.det_pos),
    exact pow_nonneg (mul_nonneg (by simp) (le_of_lt pi_pos)) _,
    by linarith,
    exact sqrt_2_pi_n_R_det_ne_zero,
    refine div_ne_zero (by simp) sqrt_2_pi_n_R_det_ne_zero,
    exact exp_ne_zero _
end

lemma sum_quad_form {n : ℕ} (R : matrix (fin n) (fin n) ℝ) {m : ℕ} (y : fin m → fin n → ℝ) :
  (∑ i, R.quad_form (y i))
  = m * (covariance_matrix y ⬝ Rᵀ).trace :=
begin
  by_cases h : m = 0,
  { subst h, simp },
  simp only [matrix.quad_form, matrix.trace, covariance_matrix, diag, mul_apply, finset.sum_mul,
    finset.sum_div],
  simp_rw [@finset.sum_comm _ (fin m), finset.mul_sum],
  apply congr_arg,
  ext i,
  unfold dot_product,
  have : (m : ℝ) ≠ 0 := by simp [h],
  simp_rw [← mul_assoc, mul_div_cancel' _ this],
  apply congr_arg,
  ext j,
  simp_rw [mul_assoc, ← finset.mul_sum],
  apply congr_arg,
  unfold matrix.mul_vec,
  unfold dot_product,
  simp_rw [mul_comm],
  congr',
  ext j,
  rw [mul_comm, matrix.transpose]
end

lemma real.inverse_eq_inv (a : ℝ) : ring.inverse a = a⁻¹ :=
by simp


-- begin header
import limits

namespace M1P1
-- end header

/- Section
The sandwich theorem
-/

/- Sub-section
Introduction
-/

/-
The sandwich theorem, or squeeze theorem, for real sequences is the statement that if $(a_n)$, $(b_n)$, and $(c_n)$ are three real-valued sequences satisfying $a_n≤ b_n≤ c_n$ for all $n$, and if furthermore $a_n→ℓ$ and $c_n→ℓ$, then $b_n→ℓ$. The idea of the proof is straightforward -- if we want to ensure that $|b_n-ℓ|<ε$ then it suffices to show that $|a_n-ℓ|<ε$ and $|c_n-ℓ|<ε$, and we can choose $N$ large enough such that this is true for all $n≥ N$.
-/

/- Lemma
If $(a_n)$, $(b_n)$, and $(c_n)$ are three real-valued sequences satisfying $a_n ≤ b_n ≤ c_n$ for all $n$, and if furthermore $a_n→ℓ$ and $c_n→ℓ$, then $b_n→ℓ$.
-/
theorem sandwich (a b c : ℕ → ℝ)
  (l : ℝ) (ha : is_limit a l) (hc : is_limit c l)
  (hab : ∀ n, a n ≤ b n) (hbc : ∀ n, b n ≤ c n) : is_limit b l :=
begin
  -- We need to show that for all $ε>0$ there exists $N$ such that $n≥N$ implies $|b_n-ℓ|<ε$. So choose ε > 0.
  intros ε Hε,
  -- We now need an $N$. As usual it is the max of two other N's, one coming from $(a_n)$ and one from $(c_n)$. Choose $N_a$ and $N_c$ such that $|a_n - l| < ε$ for $n ≥ N_a$ and $|c_n - l| < ε$ for $n ≥ N_c$.
  cases ha ε Hε with Na Ha,
  cases hc ε Hε with Nc Hc,
  -- Now let $N$ be the max of $N_a$ and $N_c$; we claim that this works.
  let N := max Na Nc,
  use N,
  -- Note that $N ≥ N_a$ and $N ≥ N_c$,
  have HNa : Na ≤ N := by obvious_ineq,
  have HNc : Nc ≤ N := by obvious_ineq,
  -- so for all $n ≥ N$,
  intros n Hn,
  -- we have $a_n ≤ b_n ≤ c_n$, and $|a_n - l|, |b_n - l|$ are both less than $ε$.
  have h1 : a n ≤ b n := hab n,
  have h2 : b n ≤ c n := hbc n,
  have h3 : |a n - l| < ε := Ha n (le_trans HNa Hn),
  have h4 : |c n - l| < ε := Hc n (le_trans HNc Hn),
  -- The result now follows easily from these inequalities (as $l-ε < a_n ≤ b_n ≤ c_n < l+ε$ ).
  rw abs_sub_lt_iff at h3 h4 ⊢,
  cases h3, cases h4,
  split;linarith
end

/-
Note that Lean finds the last line of the proof automatically -- we do not need to explicitly tell it the inequality argument.
-/
end M1P1


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

import hard_mode.sheet08

/-

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

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

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

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

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

notation `myint` := quotient s

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

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

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

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

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

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

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

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

-- Both implications also have names

example (r s : ℕ × ℕ) : ⟦r⟧ = ⟦s⟧ → r ≈ s :=
begin
  sorry
end

example (r s : ℕ × ℕ) : r ≈ s → ⟦r⟧ = ⟦s⟧ :=
begin
  sorry
end


import Mathlib

namespace Quadratic

/-Defining the two roots of quadratic equation-/
noncomputable def root₁ (a b c : ℝ) : ℝ :=
 (-b + Real.sqrt (b^2 - 4*a*c)) / (2*a)

/-Defining the two roots of quadratic equation-/
noncomputable def root₂(a b c : ℝ) (h:(b^2 - 4*a*c) ≥ 0): ℝ :=
 (-b - Real.sqrt (b^2 - 4*a*c)) / (2*a)

def two_mul_two (R : Type) [Ring R] : (4: R) = (2 : R) * (2: R) := by /-proof that 2(ℝ) * 2(ℝ) = 4(ℝ) -/
    have : (2: R) = (1: R) + (1: R) := by norm_cast
    rw [this, mul_add, mul_one]
    norm_cast
/-- Simplifying the term with power 1 of the root₁.
The simplification in RHS is done by hand (Expected).
In LHS theorems are used to simplify into the expected term.
For this lemma h' is required as there is a stage in simplification where 2*a is multiplied and divided, hence proved that given h', 2*a ≠ 0 -/
private lemma l₁ (a b c : ℝ) (h': a≠0): (b * ((-b + Real.sqrt (b * b - 4*a*c)) / (2 * a))) = ((-(2*a*b*b)/(4*a*a)) + ((2*a*b*(Real.sqrt (b * b - 4*a*c)))/(4*a*a))) := by

  have h' : 2*a ≠ 0 := by
    simp only [ne_eq, mul_eq_zero, OfNat.ofNat_ne_zero, h', or_self, not_false_iff]
  conv=>
    lhs
    simp only [add_div,mul_div,mul_add, add_mul]
    rw [←mul_div_mul_right (c:=2*a) (hc:=h')] /-divide and multiply by 2*a -/
    rw [←mul_div_mul_right (c:=2*a) (a:=b * Real.sqrt (b * b - 4 * a * c)) (b := 2*a) (hc:=h')] /-divide and multiply by 2*a -/
    simp [mul_left_comm (a:=b) (b:=2) (c:=a)]
    rw [mul_left_comm,mul_right_comm,mul_assoc]
    rw [←mul_left_comm (a:=a) (b:=b) (c:=b)]
    rw [mul_mul_mul_comm]
    rw [mul_rotate (a:=b) (b:=(Real.sqrt (b * b - 4 * a * c))) (c:=2*a)]
    rw [mul_rotate (a:=Real.sqrt (b * b - 4 * a * c)) (b:=2*a) (c:=b)]
    simp only [←mul_assoc]
    simp [←two_mul_two]

/--Simplifying the term with power 2 of root₁.
The simplification RHS is the expected simplification (done by hand).
So, using theorems and ring tactic (on LHS) it is proved that the term actually reduces to hand simplified term-/

private lemma l₂ (a b c : ℝ) (h:0 ≤ (b*b - 4*a*c)):
  a *((-b * -b + Real.sqrt (b * b - 4 * a * c) * -b +
        (-b * Real.sqrt (b * b - 4 * a * c) +
          Real.sqrt (b * b - 4 * a * c) * Real.sqrt (b * b - 4 * a * c))) /
              (2 * (2 * a * a))) =
                (2*a*b*b - 4*a*a*c - 2*a*b*Real.sqrt (b*b - 4*a*c))/(4*a*a) := by
                conv=>
                  lhs
                  rw [Real.mul_self_sqrt h]
                  simp only [mul_div,mul_add,mul_sub]
                  simp only [←mul_assoc]
                  simp [←two_mul_two]
                  rw [mul_right_comm (a:=a) (b:=Real.sqrt (b*b - 4*a*c) ) (c:=b)]
                  rw [←add_assoc]
                ring

/-- Proof that root₁ is actually the root of quadratic.
Essentially it is simplification using various theorems, and split into major 3 terms out of which two are bigger terms
and are simplified in private lemmas l₁ (specifically pass h:= a ≠ 0 as it involves multiplication and addition of 2*a) and l₂.
Once the major 2 terms are simplified, its rewritten and then ring_nf tactic is applied along with some more simplification theorems-/

theorem root₁_is_root (a b c : ℝ) (h:(b^2 - 4*a*c) ≥ 0) (h': a≠0): a*(root₁ a b c)^2 + b*(root₁ a b c) + c = 0 := by
  simp only [root₁, div_pow]
  simp only [pow_two, mul_left_comm, mul_add, add_mul] at h ⊢
  rw [l₁ (h':=h')]
  rw [l₂]
  ring_nf
  simp only [pow_two]
  simp [←mul_rotate (a:=a⁻¹*a⁻¹) (b:=a*a) (c:=c)]
  rw [mul_mul_mul_comm]
  rw[←one_div]
  rw [one_div_mul_cancel (h:=h')]
  rw [mul_one,one_mul]
  rw [neg_add_self]
  assumption

/-- Simplifying term with power 1 of root₂.
The RHS is expected simplification (Done by hand).
LHS indeed gets simplified to the expected term.
This lemma requires h' and also proof that 2*a ≠ 0, as it is being multiplied and divided by 2*a -/

private lemma l'₁ (a b c : ℝ) (h:(b*b - 4*a*c) ≥ 0) (h': a≠0): (b * ((-b - Real.sqrt (b * b - 4*a*c)) / (2 * a))) = ((-(2*a*b*b)/(4*a*a)) - ((2*a*b*(Real.sqrt (b * b - 4*a*c)))/(4*a*a))) := by

  have h' : 2*a ≠ 0 := by
    simp only [ne_eq, mul_eq_zero, OfNat.ofNat_ne_zero, h', or_self, not_false_iff]
  conv=>
    lhs
    simp only [sub_div,mul_div,mul_sub, add_mul]
    rw [←mul_div_mul_right (c:=2*a) (hc:=h')] /-divide and multiply by 2*a -/
    rw [←mul_div_mul_right (c:=2*a) (a:=b * Real.sqrt (b * b - 4 * a * c)) (b := 2*a) (hc:=h')] /-divide and multiply by 2*a -/
    simp [mul_left_comm (a:=b) (b:=2) (c:=a)]
    rw [mul_left_comm,mul_right_comm,mul_assoc]
    rw [←mul_left_comm (a:=a) (b:=b) (c:=b)]
    rw [mul_mul_mul_comm]
    rw [mul_rotate (a:=b) (b:=(Real.sqrt (b * b - 4 * a * c))) (c:=2*a)]
    rw [mul_rotate (a:=Real.sqrt (b * b - 4 * a * c)) (b:=2*a) (c:=b)]
    simp only [←mul_assoc]
    simp [←two_mul_two]

/-- Simplifying term with power 2 of root₂.
The RHS is the expected hand simplified term.
The LHS gets simplified to expected term-/

private lemma l'₂ (a b c : ℝ) (h:(b*b - 4*a*c) ≥ 0):
  a *((-b * -b - Real.sqrt (b * b - 4 * a * c) * -b -
          (-b * Real.sqrt (b * b - 4 * a * c) -
            Real.sqrt (b * b - 4 * a * c) * Real.sqrt (b * b - 4 * a * c))) /
                (2 * (2 * a * a))) =
                  (2*a*b*b - 4*a*a*c + 2*a*b*Real.sqrt (b*b - 4*a*c))/(4*a*a) := by
                    conv=>
                      lhs
                      rw [Real.mul_self_sqrt h]
                      simp only [mul_div,mul_add,mul_sub]
                      simp only [←mul_assoc]
                      simp [←two_mul_two]
                      rw [sub_eq_add_neg]
                      rw [neg_sub]
                      rw [sub_neg_eq_add]
                      rw [←add_assoc]
                      rw [add_rotate]
                      rw [add_rotate']
                      rw [add_rotate']
                      simp [←add_assoc]
                      rw [add_assoc]
                      rw[add_add_add_comm]
                      rw [← mul_rotate]
                      rw[mul_comm b a]
                      rw [←mul_two (n :=a * b * Real.sqrt (b * b - 4 * a * c))]
                      rw [sub_eq_add_neg (a * b * b)]
                      rw [← add_rotate]
                      rw [← mul_two]
                      rw [add_rotate']
                      rw[←add_assoc]
                      rw [←sub_eq_add_neg]
                      rw [mul_rotate (a:=a) (b:=4) (c:=a)]
                      rw [mul_rotate]
                      rw [mul_comm b 2,mul_mul_mul_comm]
                      rw [←mul_assoc]
                      rw [mul_comm (a * b * (Real.sqrt (b * b - 4 * a * c))) 2]
                      rw [←mul_assoc]
                      rw [←mul_assoc]

/-- Proof that root₂ is actually the root of quadratic.
Essentially it is simplification using various theorems, and split into major 3 terms out of which two are bigger terms
and are simplified in private lemmas l'₁ (specifically pass h:= a ≠ 0 as it involves multiplication and addition of 2*a) and l'₂.
Once the major 2 terms are simplified, its rewritten and then ring_nf tactic is applied along with some more simplification theorems-/

theorem root₂_is_root (a b c : ℝ) (h:(b^2 - 4*a*c) ≥ 0) (h': a≠0): a*(root₂ a b c h)^2 + b*(root₂ a b c h) + c = 0 := by
  simp only [root₂, div_pow]
  simp only [pow_two, mul_left_comm, mul_sub, sub_mul] at h ⊢
  rw [l'₁ (h':=h')]
  rw [l'₂]
  ring_nf
  simp only [pow_two]
  simp [←mul_rotate (a:=a⁻¹*a⁻¹) (b:=a*a) (c:=c)]
  rw [mul_mul_mul_comm]
  rw[←one_div]
  rw [one_div_mul_cancel (h:=h')]
  rw [mul_one,one_mul]
  rw [neg_add_self]
  assumption
  assumption

end Quadratic


-- Unión e intersección de familias de conjuntos
-- =============================================

-- ----------------------------------------------------
-- Ej. 1. Declarar I y U como variables de tipo.
-- ----------------------------------------------------

variables {I U : Type}

-- ----------------------------------------------------
-- Ej. 2. Definir la función
--    Union : (I → set U) → set U
-- tal que (Union A) es la unión de de los conjuntos de
-- la familia A.
-- ----------------------------------------------------

def Union (A : I → set U) : set U :=
  { x | ∃ i : I, x ∈ A i }

-- ----------------------------------------------------
-- Ej. 3. Definir la función
--    Inter : (I → set U) → set U
-- tal que (Inter A) es la intersección de de los
-- conjuntos de la familia A.
-- ----------------------------------------------------

def Inter (A : I → set U) : set U :=
  { x | ∀ i : I, x ∈ A i }

-- ----------------------------------------------------
-- Ej. 4. Declarar
-- + x como una variable sobre U y
-- + A como una variable sobre familas de conjuntos de
--   U con índice en A.
-- ----------------------------------------------------

variable x : U
variable (A : I → set U)

-- ----------------------------------------------------
-- Ej. 5. Demostrar que
--    x ∈ Union A ⊢ ∃ i, x ∈ A i
-- ----------------------------------------------------

example
  (h : x ∈ Union A)
  : ∃ i, x ∈ A i :=
h

-- ----------------------------------------------------
-- Ej. 6. Demostrar que
--    x ∈ x ∈ Inter A ⊢ ∀ i, x ∈ A i
-- ----------------------------------------------------

example
  (h : x ∈ Inter A)
  : ∀ i, x ∈ A i :=
h

-- ----------------------------------------------------
-- Ej 7. Usar (⋃ i, A i) como notación para (Union A).
-- ----------------------------------------------------

notation `⋃` binders `, ` r:(scoped f, Union f) := r

-- ----------------------------------------------------
-- Ej 8. Usar (⋂ i, A i) como notación para (Inter A).
-- ----------------------------------------------------

notation `⋂` binders `, ` r:(scoped f, Inter f) := r

-- ----------------------------------------------------
-- Ej. 9. Demostrar que
--    x ∈ ⋃ i, A i ⊢ ∃ i, x ∈ A i
-- ----------------------------------------------------

example
  (h : x ∈ ⋃ i, A i)
  : ∃ i, x ∈ A i :=
h

-- ----------------------------------------------------
-- Ej. 10. Demostrar que
--    x ∈ x ∈ ⋂ i, A i ⊢ ∀ i, x ∈ A i
-- ----------------------------------------------------

example
  (h : x ∈ ⋂ i, A i)
  : ∀ i, x ∈ A i :=
h


import MyNat.Addition -- imports addition.
namespace MyNat
open MyNat
/-!
# Addition World

## Level 3: `succ_add`

Oh no! On the way to `add_comm`, a wild `succ_add` appears. `succ_add`
is the proof that `(succ a) + b = succ (a + b)` for `a` and `b` in your
natural number type. You need to prove this now, because you will need
to use this result in our proof that `a + b = b + a` in the next level.

Think about why computer scientists called this result `succ_add` .
There is a logic to all the names.

Note that if you want to be more precise about exactly where you want
to rewrite something like `add_succ` (the proof you already have),
you can do things like `rw [add_succ (succ a)]` or
`[rw add_succ (succ a) d]`, telling Lean explicitly what to use for
the input variables for the function `add_succ`. Indeed, `add_succ`
is a function -- it takes as input two variables `a` and `b` and outputs a proof
that `a + succ b = succ (a + b)`. The tactic `rw [add_succ]` just says to Lean "guess
what the variables are".

**Lemma**

For all natural numbers `a, b`, we have ` (succ a) + b = succ (a + b). `
-/
lemma succ_add (a b : MyNat) : succ a + b = succ (a + b) := by
  induction b with
  | zero => rfl
  | succ b ih =>
    rw [add_succ]
    rw [ih]
    rw [add_succ]

/-!
On to [Level 4](./Level4.lean.md).
-/


/-
Copyright (c) 2020. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jalex Stark, Yury Kudryashov
-/
import data.real.basic

/-!
# Sum of the Reciprocals of the Triangular Numbers

This file proves Theorem 42 from the [100 Theorems List](https://www.cs.ru.nl/~freek/100/).

We interpret “triangular numbers” as naturals of the form $\frac{k(k+1)}{2}$ for natural `k`.
We prove that the sum of the reciprocals of the first `n` triangular numbers is $2 - \frac2n$.

## Tags

discrete_sum
-/

open_locale big_operators
open finset

/-- **Sum of the Reciprocals of the Triangular Numbers** -/
lemma inverse_triangle_sum :
  ∀ n, ∑ k in range n, (2 : ℚ) / (k * (k + 1)) = if n = 0 then 0 else 2 - (2 : ℚ) / n :=
begin
  refine sum_range_induction _ _ (if_pos rfl) _,
  rintro (_|n), { rw [if_neg, if_pos]; norm_num },
  simp_rw [if_neg (nat.succ_ne_zero _), nat.succ_eq_add_one],
  have A : (n + 1 + 1 : ℚ) ≠ 0, by { norm_cast, norm_num },
  push_cast,
  field_simp [nat.cast_add_one_ne_zero],
  ring
end


-- Si |x| ≤ ε, para todo ε > 0, entonces x = 0
-- ===========================================

import data.real.basic

variable (x : ℝ)

-- ----------------------------------------------------
-- Ejercicio 1. Definir la notación |x| para el valor
-- absoluto de x.
-- ----------------------------------------------------

notation `|`x`|` := abs x

-- ----------------------------------------------------
-- Ejercicio 2. Demostrar que si |x| ≤ ε, para todo
-- ε > 0, entonces x = 0
-- ----------------------------------------------------

-- 1ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense,
  { exact abs_nonneg x, },
  { intros ε hε,
    exact h ε hε, },
end

-- 2ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense,
  { exact abs_nonneg x, },
  { intros ε hε,
    exact h ε hε, },
end

-- 3ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense,
  { exact abs_nonneg x, },
  { exact λ ε hε, h ε hε, },
end

-- 4ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
begin
  rw ← abs_eq_zero,
  apply eq_of_le_of_forall_le_of_dense
        (abs_nonneg x)
        h,
end

-- 5ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
abs_eq_zero.mp
  (eq_of_le_of_forall_le_of_dense (abs_nonneg x) h)

-- 6ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
have h2 : |x| = 0,
  from eq_of_le_of_forall_le_of_dense h1 h,
show x = 0,
  from abs_eq_zero.mp h2

-- 7ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
have h2 : |x| = 0,
  from eq_of_le_of_forall_le_of_dense h1 h,
abs_eq_zero.mp h2

-- 8ª demostración
example
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
have h1 : 0 ≤ |x|,
  from abs_nonneg x,
abs_eq_zero.mp (eq_of_le_of_forall_le_of_dense h1 h)

-- 9ª demostración
lemma cero_de_abs_mn_todos
  (h : ∀ ε > 0, |x| ≤ ε)
  : x = 0 :=
abs_eq_zero.mp
  (eq_of_le_of_forall_le_of_dense (abs_nonneg x) h)


import chris_hughes_various.zmod data.fintype data.nat.prime data.nat.gcd data.nat.modeq algebra.ring M3P14.order_zmodn_kmb

open nat
open fintype

--defining the phi function

definition phi (n : ℕ) := ((finset.range n).filter (nat.coprime n)).card
local notation `φ`  := phi

-- theorems

theorem phi_n (n : ℕ) [pos_nat n] : phi n = card (units (zmod n)) := sorry

theorem phi_p (p : ℕ) (hp: prime p) : φ p = p-1 := sorry

theorem strong_mul (m n : ℕ) : φ(m*n) = (φ m) * (φ n) * (gcd m n / φ (gcd m n)) := sorry

theorem phi_mul (n m : ℕ) (hp: gcd n m = 1) : φ (n*m) = (φ n) * (φ m) :=
begin
    rw [nat.mul_comm, strong_mul],
    rw [nat.gcd_comm, hp],
    have h : φ 1 = 1, from dec_trivial,
    rw h,
    simp,
    rwa mul_comm,
end

theorem phi_odd_twice_eq_n (n : ℕ) (hp : gcd 2 n = 1) : φ (2*n) = φ n :=
begin
    rw phi_mul 2 n,
    rw phi_p 2,
    simp,
    unfold prime,
    split,
    exact dec_trivial,
    intros,
    rwa ← dvd_prime (prime_two),
    assumption,
end

theorem phi_even_twice_eq_twice_n (n : ℕ) (hp : gcd 2 n = 2) : φ (2*n) = 2 * φ n :=
begin
rw strong_mul 2 n,
rw hp,
rw phi_p 2,
simp,
rw mul_comm,
unfold prime,
split,
exact dec_trivial,
intros,
rwa ← dvd_prime (prime_two),
end

theorem power_p_phi (p k : ℕ) (hp: prime p) : φ (p^k) = p^(k-1)*(p-1) := sorry

theorem dvd_phi (m n : ℕ) (hp : m > 0) : (m ∣ n) → (φ m ∣ φ n) :=
begin
intros,
have eq_n_m : n = m * (n / m),
{
    rw ←nat.mul_div_assoc m a,
    rw mul_comm,
    rwa nat.mul_div_cancel,
},
have eq_2: φ m * φ (n / m) * (gcd m (n / m) / φ (gcd m (n / m))) = φ (m * (n / m)), from eq.symm (strong_mul m (n/m)),
have h5 : φ m ∣ φ m * (φ (n/m) * (gcd m (n/m) / φ (gcd m (n/m)))), from dvd_mul_right _ _,
have h6 : φ m * (φ (n/m) * (gcd m (n/m) / φ (gcd m (n/m)))) = φ m * φ (n / m) * (gcd m (n / m) / φ (gcd m (n / m))), by rw mul_assoc,
have h7 : φ m ∣ φ m * φ (n / m) * (gcd m (n / m) / φ (gcd m (n / m))), from eq.subst h6 h5,
have h8 : φ m ∣ φ (m * (n / m)), from eq.subst (strong_mul m (n/m)).symm h7,
exact eq.subst eq_n_m.symm h8,
end

theorem dvd_a_power (a n : ℕ) : n ∣ φ (a^n - 1) := sorry

theorem gcd_phi_eq_lcm_phi (m n d l : ℕ) (hp : d = gcd m n) (hq : l = lcm m n)  : φ l * φ d = φ m * φ n := sorry

theorem euler_phi_thm (a n : ℕ) (hp: gcd a n = 1) : a^(φ n) ≡ 1 [MOD n] := sorry

theorem flittlet (a p : ℕ) (hp : prime p) : a^(p-1) ≡ 1 [MOD p] := sorry

-- conjectures

theorem lehmers_conj (n : ℕ) (hp: ¬ (prime n)) : φ n ∣ n := sorry


/-
Copyright (c) 2023 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : María Inés de Frutos-Fernández
-/

import data.real.basic -- Importamos los números reales.

/-!
Algunos de estos ejercicios se han adaptado de la charla de estructuras y classes de LftCM 2022:
<https://icerm.brown.edu/video_archive/?play=2897>
-/

open nat

noncomputable theory

/-! # Estructuras

Una estructura es una colección de campos de datos, posiblemente con restricciones que dichos
datos deben satisfacer.

Una instancia de la estructura es una colección concreta de datos que satisfacen las restricciones.
-/

/-- Un elemento de ℝ³ es una tupla de tres números reales. -/
@[ext] structure R3 :=
(x : ℝ)
(y : ℝ)
(z : ℝ)

/- Marcar la definición con la etiqueta `@[ext]` hace que Lean genere teoremas para demostrar
que dos términos de `R3` son iguales cuando sus componentes lo son. También nos permitirá
utlizar la táctica `ext` en metas de ese tipo. -/
/- Cuál es el tipo de `R3`? -/
--#check R3

/- Hay varias formas de crear una instancia de `R3`? -/

/- Si no sabemos qué campos debemos proporcionar para crear una instancia de una
estructura, podemos reemplazar el sorry por `{!}`, pulsar en la bombilla, y seleccionar
la opción "Generate a skeleton for the structure under construction".-/
def R3_ex : R3 :=
sorry

-- Esta sintaxis también nos permite especificar los argumentos en otro orden.
--#check ({x := 1, z := 3,  y := 2,} : R3)

-- El constructor por defecto se llama `R3.mk`.
--#check R3.mk 1 2 3

-- Podemos utilizar la sintaxis de constructor anónimo.
--#check (⟨1, 2, 3⟩ : R3)

/- Dos instancias de `R3` son iguales cuando todas sus componentes coinciden. -/

example (a b : R3) (hx : a.x = b.x) (hy : a.y = b.y) (hz : a.z = b.z) : a = b :=
begin
  ext,
  exacts [hx, hy, hz], -- esta sintaxis nos permite cerrar las 3 metas a la vez.
end

/- Dada una instancia de `R3`, podemos acceder a cada una de las coordenadas: -/

def v : R3 := ⟨1, 2, 3⟩
--#check R3.x
--#check v.z

/- Las estructuras también pueden tener campos proposicionales. -/

@[ext] structure R3_plus :=
(x y z : ℝ)
(x_pos : x > 0)
(y_pos : y > 0)
(z_pos : z > 0)

--#check R3_plus.mk

/- Cuándo son dos términos de tipo `R3_plus` iguales? -/

example (a b : R3_plus) (hx : a.x = b.x) (hy : a.y = b.y) (hz : a.z = b.z) : a = b :=
begin
  ext,
  exacts [hx, hy, hz],
end

/- Por último, también es posible crear estructuras con parámetros. -/

/-- Definimos ℝ^n, donde `n` es un número natural -/
structure Rn (n : ℕ) :=
(coeff : fin n → ℝ)

--variables (n :  ℕ)
--#check Rn n

--#check Rn.mk
--#check Rn.mk (λ (k : fin 5), k)
--def ex : Rn 5 := ⟨(λ k, k)⟩

/-- La siguiente definición es distinta: incluye n-tuplas de números reales para cualquier `n`. -/
structure Rn' :=
(n : ℕ)
(coeff : fin n → ℝ)

--#check Rn'.mk 5 (λ k, k)

/-!
## Ejercicios de creación de estructuras
En los siguientes ejercicios, se pide crear una estructura en Lean para representar cada uno de
los objetos matemáticos descritos. Las soluciones propuestas no siempre son únicas, pregunta si
tienes dudas sobre si tu solución es correcta.
-/

/-
Crea un tipo `even_natural_number`, compuesto por pares cuya primera componente es un número
natural y la segunda una demostración de que el número es par.
-/
--structure even_natural_number :=

/- Define una estructura de secuencias `ℕ → ℕ` eventualmente constantes. El primer campo de la
estructura será una secuencia `seq : ℕ → ℕ`, y el segundo el enunciado de que `seq` satisface
la propiedad deseada. -/
--structure event_const_seq :=

/- Define una estructura cuyos términos sean dos números naturales distintos. -/
--structure two_distinct_points :=

/- Define una estructura de grupo multiplicativo sobre un tipo `G`. Para ello, necesitarás campos
de datos para indicar las operaciones y el elemento identidad, y campos proposicionales para
establecer los axiomas deseados.
-/
--structure Group (G : Type*) :=


import natural.definition natural.addition

namespace N

def mul : N -> N -> N
| a 0 := 0
| a (succ b) := (mul a b) + a

instance : has_mul N := ⟨ mul ⟩

lemma mul_zero (a : N) : a * 0 = 0 := rfl
lemma mul_succ (a b : N) : a * (succ b) = a * b + a := rfl

lemma zero_mul (a : N) : 0 * a = 0 :=
begin
  induction a with d hd,
  rw zero_eq_zero,
  rw mul_zero,
  rw mul_succ,
  rw add_zero,
  rw hd,
end

lemma succ_mul (a b : N) : (succ a) * b = a * b + b :=
begin
  induction b with d hd,
  rw zero_eq_zero,
  rw mul_zero,
  rw mul_zero,
  rw add_zero,
  rw mul_succ,
  rw hd,
  rw mul_succ,
  repeat {rw add_succ},
  rw add_assoc,
  rw add_comm d a,
  rw ← add_assoc,
end

theorem mul_left_distrib_add (a b c : N) : a * (b + c) = a * b + a * c :=
begin
  induction a with d hd,
  rw zero_eq_zero,
  repeat {rw zero_mul},
  rw add_zero,
  repeat {rw succ_mul},
  rw hd,
  rw add_assoc (d * b) b (d * c + c),
  rw ← add_assoc b (d * c) c,
  rw add_comm b (d * c),
  repeat {rw ← add_assoc},
end

theorem mul_assoc (a b c : N) : (a * b) * c = a * (b * c) :=
begin
  induction c with d hd,
  rw zero_eq_zero,
  repeat {rw mul_zero},
  rw mul_succ,
  rw hd,
  rw mul_succ,
  rw mul_left_distrib_add,
end

theorem mul_comm (a b : N) : a * b = b * a :=
begin
  induction b with d hd,
  rw zero_eq_zero,
  rw mul_zero,
  rw zero_mul,
  rw mul_succ,
  rw succ_mul,
  rw hd,
end

theorem mul_right_distrib_add (a b c : N) : (a + b) * c = a * c + b * c :=
begin
  rw mul_comm (a + b),
  rw mul_left_distrib_add,
  rw mul_comm c a,
  rw mul_comm c b,
end

lemma mul_one (a : N) : a * 1 = a :=
begin
  rw ← one_eq_one,
  rw one,
  rw mul_succ,
  rw mul_zero,
  rw zero_add,
end

lemma one_mul (a : N) : 1 * a = a :=
begin
  rw mul_comm,
  rw mul_one,
end

lemma mul_two (a : N) : a * 2 = a + a :=
begin
  rw ← two_eq_two,
  rw two,
  rw mul_succ,
  rw mul_one,
end

lemma two_mul (a : N) : 2 * a = a + a :=
begin
  rw mul_comm,
  rw mul_two,
end

theorem mul_one_idemp (a : N) : a * 1 * 1 = a * 1 :=
begin
  rw mul_one,
end

end N


import ..lectures.love05_inductive_predicates_demo

/-! # LoVe Exercise 5: Inductive Predicates -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Even and Odd

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

#check even

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

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

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

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

@[simp] lemma odd_1 :
  odd 1 :=
sorry

/-! 1.2. Prove that 3 and 5 are odd. -/

-- enter your answer here

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

lemma even_two_times :
  ∀m : ℕ, even (2 * m)
| 0       := even.zero
| (m + 1) :=
  sorry

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

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

lemma even_imp_exists_two_times :
  ∀n : ℕ, even n → ∃m, n = 2 * m :=
begin
  intros n hen,
  induction' hen,
  case zero {
    apply exists.intro 0,
    refl },
  case add_two : k hek ih {
    sorry }
end

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

lemma even_iff_exists_two_times (n : ℕ) :
  even n ↔ ∃m, n = 2 * m :=
sorry

/-! 1.6 (**optional**). Give a structurally recursive definition of `even` and
test it with `#eval`.

Hint: The negation operator on `bool` is called `not`. -/

def even_rec : nat → bool :=
sorry

/-! ## Question 2: Tennis Games

Recall the inductive type of tennis scores from the demo: -/

#check score

/-! 2.1. Define an inductive predicate that returns true if the server is ahead
of the receiver and that returns false otherwise. -/

inductive srv_ahead : score → Prop
-- enter the missing cases here

/-! 2.2. Validate your predicate definition by proving the following lemmas. -/

lemma srv_ahead_vs {m n : ℕ} (hgt : m > n) :
  srv_ahead (score.vs m n) :=
sorry

lemma srv_ahead_adv_srv :
  srv_ahead score.adv_srv :=
sorry

lemma not_srv_ahead_adv_rcv :
  ¬ srv_ahead score.adv_rcv :=
sorry

lemma srv_ahead_game_srv :
  srv_ahead score.game_srv :=
sorry

lemma not_srv_ahead_game_rcv :
  ¬ srv_ahead score.game_rcv :=
sorry

/-! 2.3. Compare the above lemma statements with your definition. What do you
observe? -/

-- enter your answer here

/-! ## Question 3: Binary Trees

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

#check is_full_mirror
#check mirror_mirror

lemma mirror_is_full {α : Type} :
  ∀t : btree α, is_full (mirror t) → is_full t :=
sorry

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

def map_btree {α β : Type} (f : α → β) : btree α → btree β
| _ := sorry   -- remove this dummy case and enter the missing cases

/-! 3.3. Prove the following lemma by case distinction. -/

lemma map_btree_eq_empty_iff {α β : Type} (f : α → β) :
  ∀t : btree α, map_btree f t = btree.empty ↔ t = btree.empty :=
sorry

/-! 3.4 (**optional**). Prove the following lemma by rule induction. -/

lemma map_btree_mirror {α β : Type} (f : α → β) :
  ∀t : btree α, is_full t → is_full (map_btree f t) :=
sorry

end LoVe


/-
    Stalk (of types) on basis.

    https://stacks.math.columbia.edu/tag/009H
-/

import to_mathlib.opens
import sheaves.presheaf_on_basis

open topological_space

universe u

variables {α : Type u} [topological_space α]
variables {B : set (opens α )} {HB : opens.is_basis B}
variables (F : presheaf_on_basis α HB) (x : α)

structure stalk_on_basis.elem :=
(U  : opens α)
(BU : U ∈ B)
(Hx : x ∈ U)
(s  : F BU)

-- Equivalence relation on the set of pairs. (U,s) ~ (V,t) iff there exists W
-- open s.t. x ∈ W ⊆ U ∩ V, and s|W = t|W.

def stalk_on_basis.relation : stalk_on_basis.elem F x → stalk_on_basis.elem F x → Prop :=
λ Us Vt,
    ∃ W (BW : W ∈ B) (HxW : x ∈ W) (HWU : W ⊆ Us.U) (HWV : W ⊆ Vt.U),
    F.res Us.BU BW HWU Us.s = F.res Vt.BU BW HWV Vt.s

lemma stalk_on_basis.relation.reflexive : reflexive (stalk_on_basis.relation F x) :=
λ ⟨U, OU, HxU, s⟩, ⟨U, OU, HxU, set.subset.refl _, set.subset.refl _, rfl⟩

lemma stalk_on_basis.relation.symmetric : symmetric (stalk_on_basis.relation F x) :=
λ Us Vt ⟨W, OW, HxW, HWU, HWV, Hres⟩, ⟨W, OW, HxW, HWV, HWU, Hres.symm⟩

lemma stalk_on_basis.relation.transitive : transitive (stalk_on_basis.relation F x) :=
λ ⟨U, BU, HxU, sU⟩ ⟨V, BV, HxV, sV⟩ ⟨W, BW, HxW, sW⟩,
λ ⟨R, BR, HxR, HRU, HRV, HresR⟩ ⟨S, BS, HxS, HSV, HSW, HresS⟩,
have HxRS : x ∈ R ∩ S := ⟨HxR, HxS⟩,
let ⟨T, BT, HxT, HTRS⟩ := opens.is_basis_iff_nbhd.1 HB HxRS in
⟨T, BT, HxT, λ y Hy, HRU (HTRS Hy).1, λ y Hy, HSW (HTRS Hy).2,
have HTR : T ⊆ R := λ y Hy, (HTRS Hy).1,
have HTS : T ⊆ S := λ y Hy, (HTRS Hy).2,
have HURRS : _ := F.Hcomp BU BR BT HTR HRU,
have HVRRS : _ := F.Hcomp BV BR BT HTR HRV,
have HVSRS : _ := F.Hcomp BV BS BT HTS HSV,
have HWSRS : _ := F.Hcomp BW BS BT HTS HSW,
calc  F.res BU BT _ sU
    = F.res BR BT _ (F.res BU BR _ sU) : congr_fun HURRS sU
... = F.res BR BT _ (F.res BV BR _ sV) : congr_arg _ HresR
... = F.res BV BT _ sV                 : congr_fun HVRRS.symm sV
... = F.res BS BT _ (F.res BV BS _ sV) : congr_fun HVSRS sV
... = F.res BS BT _ (F.res BW BS _ sW) : congr_arg _ HresS
... = F.res BW BT _ sW                 : congr_fun HWSRS.symm sW⟩

lemma stalk_on_basis.relation.equivalence : equivalence (stalk_on_basis.relation F x) :=
⟨stalk_on_basis.relation.reflexive F x,
stalk_on_basis.relation.symmetric F x,
stalk_on_basis.relation.transitive F x⟩

instance stalk_on_basis.setoid : setoid (stalk_on_basis.elem F x) :=
{ r := stalk_on_basis.relation F x,
  iseqv := stalk_on_basis.relation.equivalence F x }

-- We define a stalk as the set of stalk elements under the defined relation.

definition stalk_on_basis := quotient (stalk_on_basis.setoid F x)


theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
begin
  apply and.intro hp,
  exact and.intro hq hp
end

theorem test' (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
by exact and.intro hp (and.intro hq hp)

#print test

example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
  apply iff.intro,
  intro h,
  apply or.elim (and.right h),
    intro hq,
    apply or.inl,
    apply and.intro,
      exact h.left,
    exact hq,
   intro hr,
   apply or.inr,
   apply and.intro,
     exact h.left,
     exact hr,
   intro h,
   apply or.elim h,
     intro hpq,
     apply and.intro,
     exact hpq.left,
     apply or.inl,
     exact hpq.right,
   intro hpr,
   apply and.intro,
     exact hpr.left,
   apply or.inr,
   exact hpr.right
end

example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
  intros a b c h1 h2,
  exact eq.trans (eq.symm h2) h1,
end

variables x y z w : ℕ
example (h1 : x = y) (h2 : y = z) (h3 : z = w) : x = w :=
begin
  apply eq.trans h1,
  apply eq.trans h2,
  assumption
end

example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
  intros,
  transitivity a,
  symmetry,
  repeat { assumption},
end

example : ∃ a : ℕ, 5 = a :=
begin
  apply exists.intro,
  reflexivity,
end

example : ∃ a : ℕ, a = a :=
begin
  fapply exists.intro,
  exact 0,
  reflexivity
end

example (x : ℕ) : x = x :=
begin
  revert x,
  intro y,
  refl,
end

example (p q : Prop) : p ∨ q → q ∨ p :=
begin
  intro h,
  cases h with hp hq,
  right, exact hp,
  left, exact hq,
end

example (p q : Prop) : p ∧ q → q ∧ p :=
begin
  intro h,
  cases h with hp hq,
  constructor, exact hq, exact hp,
end

example (p q : ℕ → Prop) : (∃ x, p x) → ∃ x, p x ∨ q x :=
begin
  intro h,
  cases h with x px,
  constructor, left, exact px,
end

universes u v
def swap_pair {α : Type u} {β : Type v} : α × β → β × α :=
begin
  intro p,
  cases p with ha hb,
  constructor, exact hb, exact ha
end

def swap_sum {α : Type u} {β : Type v} : α ⊕ β → β ⊕ α :=
begin
  intro p,
  cases p with ha hb,
  right, exact ha,
  left, exact hb
end

open nat
example (P : ℕ → Prop) (h0 : P 0) (h1 : ∀ n, P (succ n)) (m : ℕ) :
  P m :=
begin
  cases m with m', exact h0,
  exact h1 m',
end

example (p q : Prop) : p ∧ ¬ p → q :=
begin
  intro h, cases h, contradiction
end