/- LoVe Homework 4: Functional Programming -/

import .love04_functional_programming_demo

namespace LoVe

/- Question 1: Reverse of a List -/

/- Recall the `reverse` operation and the `reverse_append` lemma from the
lecture. -/

#check reverse
#check reverse_append

/- 1.1. Prove the following distributive property, using a calculational proof
for the inductive step. -/

lemma reverse_append₂ {α : Type} :
  ∀xs ys : list α, reverse (xs ++ ys) = reverse ys ++ reverse xs
:= sorry

/- 1.2. Prove the induction step in the proof below using the calculational
style, following this proof sketch:

      reverse (reverse (x :: xs))
    =     { by definition of `reverse` }
      reverse (reverse xs ++ [x])
    =     { using the lemma `reverse_append` }
      reverse [x] ++ reverse (reverse xs)
    =     { by the induction hypothesis }
      reverse [x] ++ xs
    =     { by definition of `++` and `reverse` }
      [x] ++ xs
    =     { by computation }
      x :: xs -/

lemma reverse_reverse₂ {α : Type} :
  ∀xs : list α, reverse (reverse xs) = xs
| []        := by refl
| (x :: xs) :=
sorry

/- Question 2: 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)

/- 2.1. Prove the following lemma, discovered by Carl Friedrich Gauss as a
pupil.

Hints: The `mul_add` and `add_mul` lemmas and the `ac_refl` tactics 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

/- 2.2. Prove the following property of `sum_upto`. -/

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

end LoVe


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

Elegant pairing function.
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.nat.sqrt
import Mathlib.data.set.lattice
import Mathlib.PostPort

universes u_1 u_2

namespace Mathlib

namespace nat

/-- Pairing function for the natural numbers. -/
def mkpair (a : ℕ) (b : ℕ) : ℕ :=
  ite (a < b) (b * b + a) (a * a + a + b)

/-- Unpairing function for the natural numbers. -/
def unpair (n : ℕ) : ℕ × ℕ :=
  let s : ℕ := sqrt n;
  ite (n - s * s < s) (n - s * s, s) (s, n - s * s - s)

@[simp] theorem mkpair_unpair (n : ℕ) : mkpair (prod.fst (unpair n)) (prod.snd (unpair n)) = n := sorry

theorem mkpair_unpair' {n : ℕ} {a : ℕ} {b : ℕ} (H : unpair n = (a, b)) : mkpair a b = n := sorry

@[simp] theorem unpair_mkpair (a : ℕ) (b : ℕ) : unpair (mkpair a b) = (a, b) := sorry

theorem unpair_lt {n : ℕ} (n1 : 1 ≤ n) : prod.fst (unpair n) < n := sorry

theorem unpair_le_left (n : ℕ) : prod.fst (unpair n) ≤ n :=
  nat.cases_on n (idRhs (prod.fst (unpair 0) ≤ 0) (of_as_true trivial))
    fun (n : ℕ) => idRhs (prod.fst (unpair (n + 1)) ≤ n + 1) (le_of_lt (unpair_lt (succ_pos n)))

theorem le_mkpair_left (a : ℕ) (b : ℕ) : a ≤ mkpair a b := sorry

theorem le_mkpair_right (a : ℕ) (b : ℕ) : b ≤ mkpair a b := sorry

theorem unpair_le_right (n : ℕ) : prod.snd (unpair n) ≤ n := sorry

theorem mkpair_lt_mkpair_left {a₁ : ℕ} {a₂ : ℕ} (b : ℕ) (h : a₁ < a₂) : mkpair a₁ b < mkpair a₂ b := sorry

theorem mkpair_lt_mkpair_right (a : ℕ) {b₁ : ℕ} {b₂ : ℕ} (h : b₁ < b₂) : mkpair a b₁ < mkpair a b₂ := sorry

end nat

namespace set

theorem Union_unpair_prod {α : Type u_1} {β : Type u_2} {s : ℕ → set α} {t : ℕ → set β} : (Union fun (n : ℕ) => set.prod (s (prod.fst (nat.unpair n))) (t (prod.snd (nat.unpair n)))) =
  set.prod (Union fun (n : ℕ) => s n) (Union fun (n : ℕ) => t n) := sorry


/-
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 solutions.section03groups.sheet1 -- imports our definition of `mygroup`

/-!

# Challenge sheet

This is an optional "puzzle" sheet, which won't teach you any
new Lean concepts but will give you some practice in rewriteology,
and will teach you pretty much all there is to know about the
question "which axioms of a group can I safely drop?" If you're
not into puzzles like this, just move on to sheet 3.

It turns out that two of the axioms in our definition of a group
are not needed; they can be deduced from the others. Let's define
a "weak group" class, where we only have three of the group axioms.
The question is: can you prove that a weak group is a group?
The last def, `to_mygroup`, does this, but you need to fill in the
sorrys first. Note that the simplifier is less use to you now; we've
trained it to solve problems about `mygroup`s but it doesn't
know anything about `myweakgroup`.

-/

-- removing `mul_one` and `mul_inv_self`
class myweakgroup (G : Type)
  extends has_one G, has_mul G, has_inv G : Type :=
(mul_assoc : ∀ a b c : G, (a * b) * c = a * (b * c))
(one_mul : ∀ a : G, 1 * a = a)
(inv_mul_self : ∀ a : G, a⁻¹ * a = 1)

namespace myweakgroup

variables {G : Type} [myweakgroup G] (a b c : G)

/-

One way of doing it: try proving

`mul_left_cancel : a * b = a * c → b = c`

and then

`mul_eq_of_eq_inv_mul : b = a⁻¹ * c → a * b = c`

first.

-/

lemma mul_one (a : G) : a * 1 = a :=
begin
  sorry,
end

lemma mul_inv_self (a : G) : a * a⁻¹ = 1 :=
begin
  sorry,
end

def to_mygroup (G : Type) [myweakgroup G] : mygroup G :=
{ mul_assoc := mul_assoc,
  one_mul := one_mul,
  mul_one := mul_one,
  inv_mul_self := inv_mul_self,
  mul_inv_self := mul_inv_self }

end myweakgroup

/-
If you want to take this further: prove that if we make
a new class `my_even_weaker_group` by replacing
`one_mul` by `mul_one` in the definition of `myweakgroup`
then it is no longer true that you can prove `mul_inv_self`;
there are structures which satisfy `mul_assoc`, `mul_one`
and `inv_mul_self` but which really are not groups.
Can you find an example? Try it on paper first.
-/

-- claim: not a group in general
class my_even_weaker_group (G : Type)
  extends has_one G, has_mul G, has_inv G : Type :=
(mul_assoc : ∀ a b c : G, (a * b) * c = a * (b * c))
(mul_one : ∀ a : G, a * 1 = a)
(inv_mul_self : ∀ a : G, a⁻¹ * a = 1)


/-  Math40001 : Introduction to university mathematics.

Problem Sheet 1, October 2020.

This is a Lean file. It can be read with the Lean theorem prover.

You can work on this file online at the following URL:

or you can install Lean and its maths library following the
instructions at
https://leanprover-community.github.io/get_started.html

There are advantages to installing Lean on your own computer
(for example it's faster), but it's more hassle than
just using it online.

In the below, delete "sorry" and replace it with some
tactics which prove the result.

-/

/- Question 1.

Let P and Q be Propositions (that is, true/false statements).
Prove that P ∨ Q → Q ∨ P.

-/

lemma question_one (P Q : Prop) : P ∨ Q → Q ∨ P :=
begin
  sorry
end
/-

For question 2, comment out one option (or just delete it)
and prove the other one.
-/

-- Part (a): is → symmetric?

lemma question_2a_true : ∀ P Q : Prop, (P → Q) → (Q → P) :=
begin
  sorry
end

lemma question_2a_false : ¬ (∀ P Q : Prop, (P → Q) → (Q → P)) :=
begin
  sorry
end

-- Part (b) : is ↔ symmetric?

lemma question_2b_true (P Q : Prop) : (P ↔ Q) → (Q ↔ P) :=
begin
  sorry
end

lemma question_2b_false : ¬ (∀ P Q : Prop, (P ↔ Q) → (Q ↔ P)) :=
begin
  sorry
end

/- Question 3.

Say P, Q and R are propositions, and we know:
1) if Q is true then P is true
2) If Q is false then R is false.

Can we deduce that R implies P? Comment out one
option and prove the other. Hint: if you're stuck,
"apply classical.by_contradiction" sometimes helps.
classical.by_contradiction is the theorem that ¬ ¬ P → P.
-/

lemma question_3_true (P Q R : Prop)
  (h1 : Q → P)
  (h2 : ¬ Q → ¬ R) :
R → P :=
begin
  sorry
end

lemma question_3_false : ¬ (∀ P Q R : Prop,
  (Q → P) →
  (¬ Q → ¬ R) →
  R → P) :=
begin
  sorry
end

/- Question 4.

Is it possible to find three true-false statements P , Q and R, such that
(P ∨ Q ∨ R) ∧ (¬P ∨ Q ∨ R) ∧ (¬P ∨ ¬Q ∨ R) ∧ (¬P ∨ ¬Q ∨ ¬R)
is true?

-/

lemma question_4_true : ∃ (P Q R : Prop),
  (P ∨ Q ∨ R) ∧ (¬P ∨ Q ∨ R) ∧ (¬P ∨ ¬Q ∨ R) ∧ (¬P ∨ ¬Q ∨ ¬R) :=
begin
  sorry
end

lemma question_4_false : ∀ (P Q R : Prop),
  ¬ ((P ∨ Q ∨ R) ∧ (¬P ∨ Q ∨ R) ∧ (¬P ∨ ¬Q ∨ R) ∧ (¬P ∨ ¬Q ∨ ¬R)) :=
begin
  sorry
end

/- Question 5.

  Say that for every integer n we have a proposition P n.
  Say we know P n → P (n + 8) for all n, and
  P n → P (n -3) for all n. Prove that the P n are either
  all true, or all false.

This question is harder than the others.
-/
lemma question_5 (P : ℤ → Prop) (h8 : ∀ n, P n → P (n + 8)) (h3 : ∀ n, P n → P (n - 3)) :
(∀ n, P n) ∨ (∀ n, ¬ (P n)) :=
begin
  sorry
end

/-
The first four of these questions can be solved using only the following
tactics:

intro
apply (or, better, refine)
left, right, cases, split
assumption (or, better, exact)
have,
simp,
use,
contradiction (or, better, false.elim)

The fifth question is harder.
-/


import MyNat.Definition
namespace MyNat
open MyNat

/-!
# Function world.

## Level 5: `P → (Q → P)`.

In this level, our goal is to construct a function, like in level 2.

```
⊢ P → (Q → P)
```

So `P` and `Q` are sets, and our goal is to construct a function
which takes an element of `P` and outputs a function from `Q` to `P`.
We don't know anything at all about the sets `P` and `Q`, so initially
this seems like a bit of a tall order. But let's give it a go. Delete
the `sorry` and let's think about how to proceed.

Our goal is `P → X` for some set `X=Hom(Q,P)`, and if our
goal is to construct a function then we almost always want to use the
`intro` tactic from level 2, Lean's version of "let `p ∈ P` be arbitrary."
So let's start with

`intro p`

and we then find ourselves in this state:

```
P Q : Type,
p : P
⊢ Q → P
```

We now have an arbitrary element `p ∈ P` and we are supposed to be constructing
a function `Q → P`. Well, how about the constant function, which sends everything to `p`?
This will work. So let `q ∈ Q` be arbitrary:

`intro q`

and then let's output `p`.

`exact p`

## Definition

We define an element of `Hom(P,Hom(Q,P))`.
-/
example (P Q : Type) : P → (Q → P) := by
  intro p
  intro q
  exact p

/-!
A mathematician would treat the set `P → (Q → P)` as the same as the set `P × Q → P`,
because to give an element of either function space is just to give a rule which takes
an element of `P` and an element of `Q`, and returns an element of `P`. Thinking of the
goal as a function from `P × Q` to `P` we realize that it's just projection onto the first
factor.

## Did you notice?

I wrote `P → (Q → P)` but Lean just writes `P → Q → P`. This is because
computer scientists adopt the convention that `→` is *right associative*,
which is a fancy way of saying "when we write `P → Q → R`, we mean `P → (Q → R)`."
Mathematicians use right associativity as a convention for powers: if
a mathematician says \\(10^{10^{10}}\\) they don't mean \\((10^{10})^{10}=10^{100}\\), they
mean \\(10^{(10^{10})}\\). So `10 ^ 10 ^ 10` in Lean means `10 ^ (10 ^ 10)` and not `(10 ^ 10) ^ 10`.
However they use left associativity as a convention for subtraction: if
a mathematician writes `6 - 2 - 1` they mean `(6 - 2) - 1` and not `6 - (2 - 1)`.

## Pro tip

`intros p q` is the same as `intro p; intro q`.

Next up [Level 6](./Level6.lean.md)
-/


-- Math 52: Week 4

import .utils.int_refl

-- Lakins Definition 1.2.1:
definition is_even (n : ℤ) := ∃ (k : ℤ), n = 2 * k
definition is_odd (n : ℤ) := ∃ (k : ℤ), n = 2 * k + 1

-- Lakins Definition 2.1.1:
definition divides (a b : ℤ) : Prop := ∃ (k : ℤ), a * k = b

-- The notation `a ∣ b` can be used for `divides a b`
local infix ∣ := divides

-- Lakins Example 2.1.2:
example : 3 ∣ 12 :=
begin
unfold divides,
existsi (4:ℤ),
refl,
end

theorem divides_refl : ∀ (a : ℤ), a ∣ a :=
begin
intro a,
unfold divides,
existsi (1:ℤ),
calc a * 1 = a : by rw mul_one,
end
-- Proof: Let a ∈ ℤ be arbitrary. We must show that a ∣ a;
-- i.e., we must find an integer k such that a = a * k.
-- Since a = a * 1 and 1 is an integer, we see that
-- a ∣ a is true. □

-- Lakins Proposition 2.1.3:
theorem divides_trans : ∀ (a b c : ℤ), a ∣ b ∧ b ∣ c → a ∣ c :=
begin
sorry
end
-- Proof: Let a,b,c ∈ ℤ be arbitrary and assume that
-- a ∣ b and b ∣ c. We must show that a ∣ c; i.e., we
-- must find an integer k such that c = a * k.
--
-- Since a ∣ b, by Definition 2.1.1 we may fix n ∈ ℤ
-- such that b = a * n. Similarly, since b | c, we may
-- fix m ∈ ℤ such that c = b * m, again by Definition
-- 2.1.1. Then
--   c = b * m = (a * n) * m = a * (n * m),
-- since multiplication of integers is associative
-- (Basic Properties of Integers 1.2.3). Since
-- n * m ∈ ℤ, we have proved that a ∣ c, by
-- Definition 2.1.1, as desired. 􏰟

-- Lakins Exercise 2.1.1:
theorem L211 : ∀ (a b c m n : ℤ), a ∣ b ∧ a ∣ c → a ∣ (b * m + c * n) :=
begin
sorry
end

theorem is_even_iff_two_divides : ∀ (n : ℤ), is_even n ↔ 2 ∣ n :=
begin
sorry
end

-- We will prove this fact later after we discuss induction.
axiom even_or_odd (n : ℤ) : is_even n ∨ is_odd n

-- Lakins Theorem 2.1.9 (reworded)
theorem even_product : ∀ (a : ℤ), is_even (a * (a + 1)) :=
begin
sorry
end


/-
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.
Authors: María Inés de Frutos-Fernández
-/
import topology.instances.nnreal

/-!
# Limits of monotone and antitone sequences

We prove some auxiliary results about limits of `ℝ`-valued and `ℝ≥0`-valued sequences.

## Main Results

* `real.tendsto_of_is_bounded_antitone` : an antitone, bounded below sequence `f : ℕ → ℝ` has a
  finite limit.
* `nnreal.tendsto_of_is_bounded_antitone` : an antitone sequence `f : ℕ → ℝ≥0` has a finite limit.

## Tags

glb, monotone, antitone, tendsto
-/

open_locale filter topological_space

/-- A nonempty, bounded below set of real numbers has a greatest lower bound. -/
theorem real.exists_is_glb {S : set ℝ} (hne : S.nonempty) (hbdd : bdd_below S) :
  ∃ x, is_glb S x :=
begin
  set T := - S with hT,
  have hT_ne : T.nonempty := set.nonempty_neg.mpr hne,
  have hT_bdd : bdd_above T := bdd_above_neg.mpr hbdd,
  use -classical.some (real.exists_is_lub T hT_ne hT_bdd),
  simpa [← is_lub_neg] using (classical.some_spec (real.exists_is_lub T hT_ne hT_bdd)),
end

/-- An monotone, bounded above sequence `f : ℕ → ℝ` has a finite limit. -/
lemma filter.tendsto_of_is_bounded_monotone {f : ℕ → ℝ} (h_bdd : bdd_above (set.range f))
  (h_mon : monotone f) : ∃ r : ℝ, filter.tendsto f filter.at_top (𝓝 r) :=
begin
  obtain ⟨B, hB⟩ := (real.exists_is_lub ((set.range f)) (set.range_nonempty f) h_bdd),
  exact ⟨B, tendsto_at_top_is_lub h_mon hB⟩
end

/-- An antitone, bounded below sequence `f : ℕ → ℝ` has a finite limit. -/
lemma real.tendsto_of_is_bounded_antitone {f : ℕ → ℝ} (h_bdd : bdd_below (set.range f))
  (h_ant : antitone f) : ∃ r : ℝ, filter.tendsto f filter.at_top (𝓝 r) :=
begin
  obtain ⟨B, hB⟩ := (real.exists_is_glb (set.range_nonempty f) h_bdd),
  exact ⟨B, tendsto_at_top_is_glb h_ant hB⟩,
end

/-- An antitone sequence `f : ℕ → ℝ≥0` has a finite limit. -/
lemma nnreal.tendsto_of_is_bounded_antitone {f : ℕ → nnreal} (h_ant : antitone f) :
  ∃ r : nnreal, filter.tendsto f filter.at_top (𝓝 r) :=
begin
  have h_bdd_0 : (0 : ℝ) ∈ lower_bounds (set.range (λ (n : ℕ), (f n : ℝ))),
  { intros r hr,
    obtain ⟨n, hn⟩ := set.mem_range.mpr hr,
    simp_rw [← hn],
    exact nnreal.coe_nonneg _ },
  have h_bdd : bdd_below (set.range (λ n, (f n : ℝ))) := ⟨0, h_bdd_0⟩,
  obtain ⟨L, hL⟩ := real.tendsto_of_is_bounded_antitone h_bdd h_ant,
  have hL0 : 0 ≤ L,
  { have h_glb : is_glb (set.range (λ n, (f n : ℝ))) L := is_glb_of_tendsto_at_top h_ant hL,
    exact (le_is_glb_iff h_glb).mpr h_bdd_0 },
  use ⟨L, hL0⟩,
  rw ← nnreal.tendsto_coe,
  exact hL,
end


open Classical

inductive Cnat where
  | zero : Cnat
  | succ : Cnat → Cnat

def add (a b : Cnat) : Cnat :=
  match a with
  | Cnat.zero => b
  | Cnat.succ c => Cnat.succ (add c b)

theorem add_zero_right (x : Cnat) : add x Cnat.zero = x := by
  induction x
  case zero => rfl
  case succ x' ih => simp [ih, add]

theorem add_suc_right (x y : Cnat) : add x (Cnat.succ y) = Cnat.succ (add x y) := by
  induction x
  case zero => rfl
  case succ x' ih => simp [ih, add]

theorem add_comm (x y : Cnat) : add x y = add y x := by
  induction x
  case zero => simp [add, add_zero_right]
  case succ x' ih => simp [ih, add, add_suc_right]

theorem add_assoc (x y z : Cnat) : add (add x y) z = add x (add y z) := by
  induction x
  case zero => simp [add]
  case succ x' ih => simp [ih, add]

def mul (a b : Cnat) : Cnat :=
  match a with
  | Cnat.zero => Cnat.zero
  | Cnat.succ c => add b (mul c b)

theorem mul_zero_right (x : Cnat) : mul x Cnat.zero = Cnat.zero := by
  induction x
  case zero => rfl
  case succ x' ih => simp [ih, mul, add]

theorem mul_suc_right (x y : Cnat) : mul x (Cnat.succ y) = add x (mul x y) := by
  induction x
  case zero => simp [mul, add]
  case succ x' ih => simp [mul, add, ih, <-add_assoc, add_comm]

theorem mul_comm (x y : Cnat) : mul x y = mul y x := by
  induction x
  case zero => simp [mul, mul_zero_right]
  case succ x' ih => simp [mul, mul_suc_right, ih]

theorem distrib (x y z : Cnat) : mul x (add y z) = add (mul x y) (mul x z) := by
  induction x
  case zero => simp [mul, add]
  case succ x' ih => rw [mul, mul, mul, add_assoc, ih, add_assoc,
                         <- add_assoc (mul x' y), <- add_assoc z, add_comm z]

theorem mul_assoc (x y z : Cnat) : mul (mul x y) z = mul x (mul y z) := by
  induction x
  case zero => simp [mul]
  case succ x' ih => simp [mul, mul_comm, distrib, <- ih]

def lt (a b : Cnat) : Prop :=
  match b with
  | Cnat.zero => False
  | Cnat.succ d => match a with
                   | Cnat.zero => True
                   | Cnat.succ c => lt c d

theorem lt_not_ref (x : Cnat) : ¬ lt x x := by
  induction x
  case zero => simp [lt]
  case succ x' ih => simp [lt, ih]

theorem lt_not_symm (a b : Cnat) : lt a b → ¬ lt b a := by
  induction a generalizing b
  case zero => simp [lt]
  case succ a' ih => match b with
    | Cnat.zero => simp [lt]
    | Cnat.succ b' => simp [lt]; apply ih

theorem lt_trans (a b c : Cnat) : lt a b ∧ lt b c → lt a c := by
  induction c generalizing a b
  case zero => simp [lt]
  case succ c' ih => match a with
    | Cnat.zero => simp [lt]
    | Cnat.succ a' => match b with
      | Cnat.zero => simp [lt]
      | Cnat.succ b' => simp [lt]; apply ih

theorem lt_to_sub (a b : Cnat) : lt a b → ∃ c, b = add a c := by
  induction a generalizing b
  case zero => simp [add]; intro; apply Exists.intro b; rfl
  case succ a ih =>
    simp [add]
    match b with
      | Cnat.zero => simp [lt]
      | Cnat.succ b' => simp [lt]; apply ih

theorem lt_add_suc (a b : Cnat) : lt a (add a b.succ) := by
  induction a generalizing b
  case zero => simp [add, lt]
  case succ a ih => simp [add, lt]; apply ih

theorem add_cancels_left (a b c : Cnat) : add a b = add a c → b = c := by
  induction a generalizing b
  case zero => simp [add]; intro h; exact h
  case succ a ih => simp [add]; apply ih

theorem add_cancels_right (a b c : Cnat) : add a c = add b c → a = b := by
  induction c
  case zero => simp [add_zero_right]; intro h; exact h
  case succ c ih => simp [add_suc_right]; exact ih

theorem lt_suc (a b : Cnat) (h1: lt a b) : a.succ = b ∨ lt a.succ b := by
  let ⟨c, h2⟩ := lt_to_sub a b h1
  match c with
    | Cnat.zero => {
      simp [add_zero_right] at h2; rw [h2] at h1; simp [lt_not_ref] at h1
    }
    | Cnat.succ c' => match c' with
      | Cnat.zero => {
        simp [add_suc_right, add_zero_right] at h2
        simp [h2]
      }
      | Cnat.succ c'' => {
        apply Or.inr
        rw [add_suc_right] at h2
        rw [h2]
        simp [lt]
        exact lt_add_suc _ _
      }

theorem division_theorem (m n : Cnat) (h1: lt Cnat.zero n) :
  ∃ q r, lt r n ∧ m = add (mul q n) r := by
  induction m
  case zero => {
    apply Exists.intro Cnat.zero
    apply Exists.intro Cnat.zero
    simp [h1, mul, add]
  }
  case succ m ih => {
    let ⟨q, r, h2, h3⟩ := ih
    apply Or.elim (em (r.succ = n))
    case left => {
      intro h4
      apply Exists.intro q.succ
      apply Exists.intro Cnat.zero
      simp [h1, add_zero_right, h3, mul]
      rw [<- h4, add, add_comm]
    }
    case right => {
      intro h5
      apply Exists.intro q
      apply Exists.intro r.succ
      apply And.intro
      case left => {
        apply Or.elim (lt_suc r n h2)
        case left => simp [h5]
        case right => intro h6; exact h6
      }
      case right => {
        rw [h3]
        simp [add_suc_right]
      }
    }
  }

def is_prime (p : Cnat) : Prop := sorry


/-
Homework 2 : Introduction to theorems and proofs in Lean
-/

/-
For each of the following propositions write down a correct formalization
that is error-free.

For example, if you want to formalize the statement
that for all natural numbers `n`, `n < n + 1` you would _define_ a term of
type Prop, ie a propositions as

def example : Prop := ∀ (n:Nat), n < n+1

-/

-- Problem 1. Let m and n be natural numbers. If n ≤ m, then n^2 ≤ m^2.

-- Problem 2. Let s and t be strings. Then the length of s++t is equal to the
-- the length of s plus the length of t.

-- Problem 3. For any tyoe α and any list l of α's, if we reverse l twice, then
-- we get l.

/-
For the next part, use the tactics you have seen to prove proofs.
-/

theorem prob04 {p q r : Prop} (h : p → q) (h' : p → r) : p → q ∧ r := sorry

theorem prob05 {p q : Prop} (h : p ∨ q) : q ∨ p := sorry

theorem prob06 {p : Prop} (h : p) : ¬ ¬ p := sorry


/-
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
import combinatorics.simple_graph.acyclic -- trees and forests
/-

# Trees and forests

A *forest* is a graph with no cycles. A *tree* is a connected forest.

Here's how to do this in Lean. Let `G` be a graph with vertex set `V`.

-/

variables (V : Type) (G : simple_graph V)

-- Here's how to say "G is a forest"

example : Prop := G.is_acyclic

-- It's defined to mean "For all `v : V`, every walk from `v` to `v` is not a cycle. "

example : G.is_acyclic ↔ ∀ (v : V) (p : G.walk v v), ¬ p.is_cycle :=
begin
  refl,
end

-- Here's how to say "G is a tree"

example : Prop := G.is_tree

example : G.is_tree ↔ G.connected ∧ G.is_acyclic:=
begin
  exact G.is_tree_iff,
end

-- Here are some harder theorems from the library. Recall that a *path* is a walk
-- with no repeated vertices.

-- A graph is acyclic iff for all `v w : V`, there's at most one path from `v` to `w`.
example : G.is_acyclic ↔ ∀ (v w : V) (p q : G.path v w), p = q :=
simple_graph.is_acyclic_iff_path_unique

-- A graph is a tree iff `V` is nonempty and for all `v w : V`,
-- there's exactly one path from `v` to `w`.
example : G.is_tree ↔ nonempty V ∧ ∀ v w : V, ∃! (p : G.walk v w), p.is_path :=
simple_graph.is_tree_iff_exists_unique_path

-- If you want a logic puzzle, rephrase this in terms of `G.path`
example : G.is_tree ↔ nonempty V ∧ ∀ v w : V, ∃! (p : G.path v w), true :=
begin
  rw simple_graph.is_tree_iff_exists_unique_path,
  apply and_congr iff.rfl,
  apply forall_congr, intro v,
  apply forall_congr, intro w,
  split,
  { rintro ⟨p,hp,hp2⟩,
    refine ⟨⟨p, hp⟩, true.intro, _⟩,
    rintro ⟨q, hq⟩ -,
    ext,
    exact hp2 _ hq, },
  { rintro ⟨⟨p, hp⟩, -, h2⟩,
    refine ⟨p, hp, λ q hq, _⟩,
    specialize h2 ⟨q, hq⟩ true.intro,
    cases h2,
    refl, },
end

/-
If you want a hard graph theory puzzle, prove that in a finite tree,
1 + the number of edges equals the number of vertices.
I don't think this is in the library and it would be a neat project.

Because induction on the size of `V` will be messy (it will involve
changing `V` and them moving between graphs on different types)
I think that the best way to do this would be to prove that for
an acyclic graph on a fixed `V`, #connected components + #edges = #vertices,
by induction on number of edges.

Note: the solution to this is not in the solutions!
-/

open_locale classical

example (V : Type) [fintype V] (G : simple_graph V) (hG : G.is_tree) :
  1 + finset.card (G.edge_finset) = fintype.card V :=
sorry


section propositional

variables P Q R : Prop

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

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

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro double_not_p,
  by_cases not_p : P,
    apply not_p,
  have := double_not_p not_p,
  contradiction,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
  -- ⊢ ¬¬P → P
  have doubleneg_elim := doubleneg_elim,
  apply doubleneg_elim,
  -- ⊢ P → ¬¬P
  have doubleneg_intro := doubleneg_intro,
  apply doubleneg_intro,
end

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

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

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro p_and_q,
  cases p_and_q,
  split,
  -- ⊢ Q
  apply p_and_q_right,
  -- ⊢ P
  apply p_and_q_left,
end

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

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intros not_p_or_q p,
  cases not_p_or_q,
  -- Case (-P)
  contradiction,
  -- Case (Q)
  apply not_p_or_q,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intros p_or_q not_p,
  cases p_or_q,
  -- Case (P)
  contradiction,
  -- Case (Q),
  apply p_or_q,
end

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

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intros p_impl_q not_q p,
  have q : Q := p_impl_q p,
  contradiction,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intros not_q_impl_not_p p,
  by_cases not_q : Q,
  apply not_q,
  have not_p : ¬P := not_q_impl_not_p not_q,
  contradiction,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
  -- ⊢ (P → Q) → ¬Q → ¬P
  have impl_as_contrapositive := impl_as_contrapositive,
  apply impl_as_contrapositive,
  -- ⊢ (¬Q → ¬P) → P → Q
  have impl_as_contrapositive_converse := impl_as_contrapositive_converse,
  apply impl_as_contrapositive_converse,
end

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

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro not_p_or_not_p,
  have p_or_not_p : P ∨ ¬P,
    right,
    intro p,
    have p_or_not_p' : P ∨ ¬P,
      left,
      apply p,
      contradiction,
    contradiction,
end

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

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intros p_impl_q_impl_p not_p,
  have p_impl_q : (P → Q),
    intro p,
    contradiction,
  have p : P := p_impl_q_impl_p p_impl_q,
  contradiction,
end

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

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intros p_or_q not_p_and_not_q,
  cases not_p_and_not_q,
  cases p_or_q,
  -- Case (P)
  contradiction,
  -- Case (Q)
  contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intros p_or_q not_p_and_not_q,
  cases p_or_q,
  cases not_p_and_not_q,
  -- Case (¬P)
  contradiction,
  -- Case (¬Q)
  contradiction,
end

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

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro not_p_or_q,
  split,
  -- ⊢ ¬P
  intro p,
  have p_or_q : P∨Q,
    left,
    apply p,
  contradiction,
  -- ⊢ ¬Q
  intro q,
  have p_or_q : P∨Q,
    right,
    apply q,
  contradiction,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intros not_p_and_not_q p_or_q,
  cases not_p_and_not_q,
  cases p_or_q,
  -- Case (P)
  contradiction,
  -- Case (Q)
  contradiction,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro not_p_and_q,
  by_cases not_p : P,
  left,
  intro q,
  have p_and_q : P∧Q,
    split,
    -- ⊢ P
    apply not_p,
    -- ⊢ Q
    apply q,
    contradiction,
  right,
  contradiction,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intros not_q_or_not_p p_and_q,
  cases p_and_q,
  cases not_q_or_not_p,
  -- Case (¬Q)
  contradiction,
  -- Case (¬P)
  contradiction,
end

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

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
  -- ⊢ ¬(P ∨ Q) → ¬P ∧ ¬Q
  have demorgan_disj := demorgan_disj,
  apply demorgan_disj,
  -- ⊢ ¬P ∧ ¬Q → ¬(P ∨ Q)
  have demorgan_disj_converse := demorgan_disj_converse,
  apply demorgan_disj_converse,
end

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

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
    intro p_and_q_or_r,
    cases p_and_q_or_r,
    cases p_and_q_or_r_right,
    -- Case (Q)
        left,
        split,
        -- ⊢ P
        apply p_and_q_or_r_left,
        -- ⊢ Q
        apply p_and_q_or_r_right,
    -- Case (R)
        right,
        split,
        -- ⊢ P
        apply p_and_q_or_r_left,
        -- ⊢ R
        apply p_and_q_or_r_right,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro p_and_q_or_p_and_r,
  cases p_and_q_or_p_and_r,
  -- Case (P ∧ Q)
    cases p_and_q_or_p_and_r,
    split,
    apply p_and_q_or_p_and_r_left,
    left,
    apply p_and_q_or_p_and_r_right,
  -- Case (P ∧ R)
    cases p_and_q_or_p_and_r,
    split,
    apply p_and_q_or_p_and_r_left,
    right,
    apply p_and_q_or_p_and_r_right,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro p_or_q_and_r,
  cases p_or_q_and_r,
  -- Case (P)
    split,
    -- ⊢ P ∨ Q
    left,
    apply p_or_q_and_r,
    -- ⊢ P ∨ R
    left,
    apply p_or_q_and_r,
  -- Case (Q ∧ R)
    cases p_or_q_and_r,
    split,
    -- ⊢ P ∨ Q
    right,
    apply p_or_q_and_r_left,
    -- ⊢ P ∨ R
    right,
    apply p_or_q_and_r_right,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro p_or_q_and_p_or_r,
  cases p_or_q_and_p_or_r,
  cases p_or_q_and_p_or_r_left,
  -- Case (P)
    left,
    apply p_or_q_and_p_or_r_left,
  -- Case (Q)
    cases p_or_q_and_p_or_r_right,
    -- Case (P)
      left,
      apply p_or_q_and_p_or_r_right,
    -- Case (R)
      right,
      split,
      -- ⊢ Q
      apply p_or_q_and_p_or_r_left,
      -- ⊢ R
      apply p_or_q_and_p_or_r_right,
end

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

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intros p_and_q_impl_r p q,
  have p_and_q : P∧Q,
    split,
    -- ⊢ P
    apply p,
    -- ⊢ Q
    apply q,
  have r : R := p_and_q_impl_r p_and_q,
  apply r,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intros p_impl_q_impl_r p_and_q,
  cases p_and_q,
  have q_impl_r : Q→R :=  p_impl_q_impl_r p_and_q_left,
  have r : R := q_impl_r p_and_q_right,
  apply r,
end

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

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

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

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

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

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro p_and_q,
  cases p_and_q,
  apply p_and_q_left,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro p_and_q,
  cases p_and_q,
  apply p_and_q_right,
end

theorem conj_idempot :
  (P∧P) ↔ P :=
begin
  split,
  -- ⊢ P ∧ P → P
  intro p_and_p,
  cases p_and_p,
  apply p_and_p_left,
  -- ⊢ P → P ∧ P
  intro p,
  split,
  apply p,
  apply p,
end

theorem disj_idempot :
  (P∨P) ↔ P  :=
begin
  split,
  -- ⊢ P ∨ P → P
  intro p_or_p,
  cases p_or_p,
  apply p_or_p,
  apply p_or_p,
  -- ⊢ P → P ∨ P
  intro p,
  left,
  apply 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 not_exists_px x px,
  have contr_not_exists_px : (∃x, P x),
    existsi x,
    apply px,
  contradiction,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intros forall_not_px exists_px,
  cases exists_px with x,
  have := forall_not_px x,
  contradiction,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro not_forall_px,
  by_contradiction contr_not_forall_px,
  apply not_forall_px,
  intro forall_px,
  by_contradiction contr_forall_px,
  apply contr_not_forall_px,
  existsi forall_px,
  intro not_contr_forall_px,
  apply contr_forall_px not_contr_forall_px,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intros exists_not_px forall_px,
  cases exists_not_px with x,
  have := forall_px x,
  contradiction,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  split,
  -- ⊢ (¬∀ (x : U), P x) → (∃ (x : U), ¬P x)
  have demorgan_forall := demorgan_forall,
  apply demorgan_forall,
  -- ⊢ (∃ (x : U), ¬P x) → (¬∀ (x : U), P x)
  have demorgan_forall_converse := demorgan_forall_converse,
  apply demorgan_forall_converse,
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
  -- ⊢ (¬∃ (x : U), P x) → ∀ (x : U), ¬P x
  have demorgan_exists := demorgan_exists,
  apply demorgan_exists,
  -- ⊢ (∀ (x : U), ¬P x) → (¬∃ (x : U), P x)
  have demorgan_exists_converse := demorgan_exists_converse,
  apply demorgan_exists_converse,
end

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

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intros exists_px forall_not_px,
  cases exists_px with x,
  have := forall_not_px x,
  contradiction,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intros forall_px exists_not_px,
  cases exists_not_px with x,
  have := forall_px x,
  contradiction,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intros not_exists_not_px x,
  by_contradiction contr_not_exists_not_px,
  have exists_not_px : (∃x, ¬P x),
    existsi x,
    apply contr_not_exists_not_px,
  contradiction,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intro not_forall_not_px,
  by_contradiction contr_not_forall_not_px,
  have forall_not_px : (∀x, ¬P x),
    intro x,
    intro px,
    have exists_px : (∃x, P x),
      existsi x,
      apply px,
    contradiction,
  contradiction,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  split,
  -- ⊢ (∀ (x : U), P x) → (¬∃ (x : U), ¬P x)
  have forall_as_neg_exists := forall_as_neg_exists,
  apply forall_as_neg_exists,
  -- ⊢ (¬∃ (x : U), ¬P x) → ∀ (x : U), P x
  have forall_as_neg_exists_converse := forall_as_neg_exists_converse,
  apply forall_as_neg_exists_converse,
end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
  -- ⊢ (∃ (x : U), P x) → (¬∀ (x : U), ¬P x)
  have exists_as_neg_forall := exists_as_neg_forall,
  apply exists_as_neg_forall,
  -- ⊢ (¬∀ (x : U), ¬P x) → (∃ (x : U), P x)
  have exists_as_neg_forall_converse := exists_as_neg_forall_converse,
  apply exists_as_neg_forall_converse,
end

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

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intro exists_px_and_qx,
  cases exists_px_and_qx,
  split,
  -- ⊢ ∃ (x : U), P x
    existsi exists_px_and_qx_w,
    cases exists_px_and_qx_h,
      apply exists_px_and_qx_h_left,
  -- ⊢ ∃ (x : U), Q x
    existsi exists_px_and_qx_w,
    cases exists_px_and_qx_h,
      apply exists_px_and_qx_h_right,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intro exists_px_or_qx,
  cases exists_px_or_qx,
  cases exists_px_or_qx_h,
  -- Case (P exists_px_or_qx_w)
    left,
    existsi exists_px_or_qx_w,
    apply exists_px_or_qx_h,
  -- Case (Q exists_px_or_qx_w)
    right,
    existsi exists_px_or_qx_w,
    apply exists_px_or_qx_h,

end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intro exists_px_or_exists_qx,
  cases exists_px_or_exists_qx,
  -- Case (∃ (x : U), P x)
    cases exists_px_or_exists_qx,
      existsi exists_px_or_exists_qx_w,
      left,
      apply exists_px_or_exists_qx_h,
  -- Case (∃ (x : U), Q x)
    cases exists_px_or_exists_qx,
      existsi exists_px_or_exists_qx_w,
      right,
      apply exists_px_or_exists_qx_h,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intro forall_px_and_qx,
  split,
  -- ⊢ ∀ (x : U), P x
  intro x,
  have px_and_qx : (P x ∧ Q x) := forall_px_and_qx x,
    cases px_and_qx,
      apply px_and_qx_left,
  -- ⊢ ∀ (x : U), Q x
  intro x,
  have px_and_qx : (P x ∧ Q x) := forall_px_and_qx x,
    cases px_and_qx,
      apply px_and_qx_right,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intro forall_px_and_forall_qx,
  cases forall_px_and_forall_qx,
  intro forall_px_and_qx,
  split,
  -- ⊢ P forall_px_and_qx
  apply forall_px_and_forall_qx_left,
  -- ⊢ Q forall_px_and_qx
  apply forall_px_and_forall_qx_right,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intros forall_px_or_forall_qx forall_px_or_qx,
  cases forall_px_or_forall_qx,
  -- Case (∀ (x : U), P x)
    left,
    apply forall_px_or_forall_qx,
  -- Case (∀ (x : U), Q x)
    right,
    apply forall_px_or_forall_qx,
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


/- Assorted trivialities. -/

import data.nat.basic
import tactic.ring

open nat

theorem nat.pow_two {a : ℕ} : a^2 = a * a := begin rw [(^), (^)], simp end

lemma sqrt_eq {x y : ℕ} : x * x = y * y → x = y := begin
  intro H,
  apply le_antisymm; { apply nat.mul_self_le_mul_self_iff.mpr, rw H }
end

lemma pos_of_square_pos {x : ℕ} (H : x^2 > 0) : x > 0 :=
  (eq_zero_or_pos x).resolve_left (λ x0, have H' : 0^2 > 0 := by rwa x0 at H,
  (dec_trivial : ¬(0^2 > 0)) H')

-- @[simp] lemma nat_pow_is_pow {a i : ℕ} : a^i = monoid.pow a i := sorry

-- #check mul_pow
lemma nat.mul_pow {x y i : ℕ} : (x * y)^i = x^i * y^i := begin
  induction i with j ih,
  { reflexivity },
  { unfold nat.pow, rw ih, ac_refl }
end

lemma square_twice {x : ℕ} : (x^2)^2 = x^4 :=
calc (x^2)^2 = x^2 * x^2  : by rw nat.pow_two
     ...     = x^(2 + 2)  : by rw nat.pow_add

lemma self_divides_square {x : ℕ} : x ∣ x^2 :=
calc   x = x^1  : (nat.pow_one x).symm
     ... ∣ x^2  : pow_dvd_pow x dec_trivial

lemma self_divides_pow {x n : ℕ} : n > 0 → x ∣ x^n := λ Hn,
calc   x = x^1  : (nat.pow_one x).symm
     ... ∣ x^n  : pow_dvd_pow x Hn

lemma pow_dvd_pow_of_dvd {a b n : ℕ} : a ∣ b → a^n ∣ b^n := begin
  intro H,
  induction n with m ih,
  { simp },
  { exact mul_dvd_mul ih H }
end

lemma one_if_dvd_coprime {a b : ℕ} (a_b : coprime a b) (a_div_b : a ∣ b) : a = 1 :=
  have p : a ∣ 1 :=
  calc   a ∣ gcd a b  : dvd_gcd (dvd_refl a) a_div_b
       ... = 1        : a_b,
  eq_one_of_dvd_one p

lemma one_if_pow_one {a n : ℕ} (Hn : n > 0) (H : a^n = 1) : a = 1 :=
  dvd_antisymm (H ▸ self_divides_pow Hn) (one_dvd a)

lemma dvd_of_pow_dvd_pow {a b n : ℕ} (Hn : n > 0) (H : a^n ∣ b^n) : a ∣ b := begin
  cases eq_zero_or_pos b with b_zero b_pos,
  { rw b_zero, exact dvd_zero _ },
  let g := gcd a b,
  have gcd_pos : g > 0 := gcd_pos_of_pos_right a b_pos,
  rcases exists_coprime gcd_pos with ⟨a', b', a'_b', Ha', Hb'⟩,
  change a = a' * g at Ha',
  change b = b' * g at Hb',
  rw [Ha', Hb', nat.mul_pow, nat.mul_pow] at H,
  have := dvd_of_mul_dvd_mul_right (pos_pow_of_pos n gcd_pos) H,
  have a'n_b'n := coprime.pow n n a'_b',
  have := one_if_dvd_coprime a'n_b'n this,
  have := one_if_pow_one Hn this,
  have :=
  calc b = b' * g  : Hb'
     ... = b' * (1 * g)   : by simp
     ... = b' * (a' * g)  : by rw this
     ... = b' * a         : by rw Ha',
  rw this,
  exact dvd_mul_left a b'
end

lemma polarization {x y : ℤ}
  : 2 * (x*x + y*y) = (x + y)*(x + y) + (x - y)*(x - y) := by ring

lemma nat.polarization {x y : ℕ} (H : x ≥ y)
  : 2 * (x^2 + y^2) = (x + y)^2 + (x - y)^2 := begin
  repeat { rw nat.pow_two },
  have p := @polarization (int.of_nat x) (int.of_nat y),
  have two : 2 = int.of_nat 2 := rfl,
  rw two at p,
  rw [←int.of_nat_sub H] at p,
  exact int.of_nat_inj p
end

lemma pos_of_pos_mul {k n : ℕ} : k * n > 0 → n > 0 := begin
  intro kn_pos,
  apply (eq_zero_or_pos n).resolve_left,
  intro n_zero,
  simp [n_zero] at kn_pos,
  exact lt_irrefl _ kn_pos
end

lemma le_square {n : ℕ} : n ≤ n^2 := by rw nat.pow_two; exact le_mul_self n


-- Let's first prove some simple example lemmas with the logical connectives we learned in the lecture.
section
-- Variables in sections determine the type of certain variables for the remainder of the section, saving us a bit of space.
variable (p q r : Prop)

example : p → p := _

example : p → (q → p) := _

-- Note: `→` associates to the right, so the above proposition is equivalent to
-- `p → q → p`

example : (p → False) → (p → q) := _

example : (p ∨ p) → p := _

example : (p → q → r) → (p ∧ q → r) := _

example : (p ∧ q → r) → (p → q → r) := _

example : p → (p → q) → p ∧ q := _

theorem imp_and : (p → q ∧ r) → (p → q) ∧ (p → r) := _

-- Matching on `And.intro` can quickly become tedious, so you can use the following helper functions from now on:
#check And.left
#check And.right

/- BIIMPLICATION -/

-- Biimplication ("if and only if") is written \iff.
-- It is defined as a data type on the constructor
-- Iff.intro : (A → B) → (B → A) → (A ↔ B)
-- Can you recover the proof for A → B by a match expression?

example (hpq : p ↔ q) : (p → q) := _

-- Like for `And`, we have names for both directions of the biimplication:
#check Iff.mp
#check Iff.mpr

-- Prove the following biimplications using the threorem from above!
-- Note: `↔` is defined to bind less tightly than other connectives such as `∧` or `∨`.

theorem iff_and : (p → q ∧ r) ↔ (p → q) ∧ (p → r) := _

theorem or_and : (p ∨ q → r) ↔ (p → r) ∧ (q → r) := _

theorem iff_and_false : False ↔ p ∧ False := _

/- NEGATION -/

-- Negation is defined by ¬ A := (A → False).
-- How is that a good choice? Let us check some basic properties of negation:

example : ¬False := _

theorem imp_not_not : p → ¬¬p := _

example : ¬(p ∧ ¬p) := _

theorem not_or_not : (¬p ∨ ¬q) → ¬(p ∧ q) := _

-- The following ones are a harder to prove. Don't hesitate to skip them or ask your
-- tutors if you get stuck

example : ¬(p ↔ ¬p) := _

example : ¬¬(¬¬p → p) := _

/- CLASSICAL AXIOMS -/

-- Some tautologies about negation cannot be proven by just assuming the logical
-- connectives as algebraic propositions. Instead, we need to assume these facts
-- as axioms. To have access to these, we need to open a namespace called "Classical".
-- We will learn more about constructive versus classical logic in week 5.
open Classical

-- The following statement is called the _law of excluded middle_.
-- It is assumed as an axiom, which means that it doesn't have to be proved.

#check em p

-- Now use the law of excluded middle to show that the theorem not_or_not is reversible:

theorem not_and : ¬(p ∧ q) → (¬ p ∨ ¬ q) := _

-- Also, we can now deal better with double negations:

example : ¬¬p ↔ p := _

end


open classical

variables (p q r : Prop)
variable (α : Type)
variables (a w : α)

/--
  Commutativity of ∧ and ∨
-/
example : p ∧ q ↔ q ∧ p :=
  iff.intro
    (fun (h1 : p /\ q),
      have hp : p := and.left h1,
      have hq : q := and.right h1,
      show q /\ p ,from and.intro hq hp
    )

    (fun (h2 : q /\ p),
      have hp : p := and.right h2,
      have hq : q := and.left h2,
      show p /\ q ,from and.intro hp hq
    )

example : p ∨ q ↔ q ∨ p :=
  iff.intro
    (show p ∨ q -> q ∨ p ,from
      fun h1 : p ∨ q ,
        or.elim
          h1
          (fun hp : p ,show q ∨ p ,from or.intro_right q hp)
          (fun hq : q ,show q ∨ p ,from or.intro_left p hq)
    )
    (show q ∨ p -> p ∨ q ,from
      fun h2 : q ∨ p ,
        or.elim
          h2
          (fun hq : q ,show p ∨ q ,from or.intro_right p hq)
          (fun hp : p ,show p ∨ q ,from or.intro_left q hp)
    )

/--
  Associativity of ∧ and ∨
-/
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
  iff.intro
    (show (p ∧ q) ∧ r -> p ∧ (q ∧ r) ,from
      fun h1 : (p ∧ q) ∧ r ,
        have hp : p := and.left (and.left h1),
        have hq : q := and.right (and.left h1),
        have hr : r := and.right h1,
        and.intro hp (and.intro hq hr)
    )
    (show p ∧ (q ∧ r) -> (p ∧ q) ∧ r ,from
      fun h1 : p ∧ (q ∧ r),
        have hp : p := and.left h1,
        have hq : q := and.left (and.right h1),
        have hr : r := and.right (and.right h1),
        and.intro (and.intro hp hq) hr
    )

/--
  Distributivity of ∧ and ∨
-/
theorem dist_and_over_or {p q r : Prop} : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
  iff.intro
    (show p ∧ (q ∨ r) -> (p ∧ q) ∨ (p ∧ r) ,from
      (fun h : p ∧ (q ∨ r) ,
        have hp : p := and.left h,
        have hq_or_r : (q ∨ r) := and.right h,
        or.elim
          hq_or_r
          (fun hq : q , or.intro_left  (p /\ r) ( and.intro hp hq ) )
          (fun hr : r , or.intro_right (p /\ q) ( and.intro hp hr ) )
      )
    )
    (show (p ∧ q) ∨ (p ∧ r) -> p ∧ (q ∨ r) ,from
      (fun h : (p ∧ q) ∨ (p ∧ r) ,
        have hp : p :=
          or.elim
            h
            (fun h1 : (p ∧ q) , and.left h1)
            (fun h1 : (p ∧ r) , and.left h1),
        have hq_or_r : q ∨ r :=
          or.elim
            h
            (fun h1: (p ∧ q) , or.intro_left r (and.right h1) )
            (fun h1: (p ∧ r) , or.intro_right q (and.right h1) ),
        and.intro hp hq_or_r
      )
    )

theorem dist_or_over_and {p q r : Prop} :   p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
  iff.intro
  (show  p ∨ (q ∧ r) -> (p ∨ q) ∧ (p ∨ r) ,from
    (fun h: p ∨ (q ∧ r) ,
      or.elim
        h
        (fun hp : p , and.intro (or.intro_left q hp) (or.intro_left r hp) )
        (fun hq_and_r : q /\ r , and.intro (or.intro_right p (and.left hq_and_r) ) (or.intro_right p (and.right hq_and_r) ))
    )
  )
  (show  (p ∨ q) ∧ (p ∨ r) -> p ∨ (q ∧ r) ,from
    (fun h : (p ∨ q) ∧ (p ∨ r) ,
      have hp_or_q : p ∨ q := h.left,
      have hp_or_r : p ∨ r := h.right,
      or.elim
        hp_or_q
        (fun hp: p , or.intro_left (q /\ r) hp)
        (fun hq: q ,
          or.elim
            hp_or_r
            (fun hp: p , or.intro_left (q /\ r) hp)
            (fun hr: r , or.intro_right p (and.intro hq hr))
        )
    )
  )

/--
  Relations between -> and /\ and \/
-/
example : (p → (q → r)) ↔ (p ∧ q → r) :=
  iff.intro
  (show (p → (q → r)) -> (p ∧ q → r) ,from
    (fun h : (p → (q → r)) ,
      (fun hp_and_q : p ∧ q ,
        have hp : p := and.left hp_and_q,
        have hq : q := and.right hp_and_q,
        h hp hq
      )
    )
  )
  (show (p ∧ q → r) -> (p → (q → r)) ,from
    (fun h : (p ∧ q → r) ,
      (fun hp : p ,
        (fun hq : q ,
          have hp_and_q : p /\ q := and.intro hp hq,
          h hp_and_q
        )
      )
    )
  )

example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) :=
  iff.intro
  (show ((p ∨ q) → r) -> (p → r) ∧ (q → r) ,from
    (fun h : ((p ∨ q) → r) ,
      and.intro
        (fun hp : p , h (or.intro_left q hp) )
        (fun hq : q , h (or.intro_right p hq) )
    )
  )
  (show (p → r) ∧ (q → r) -> ((p ∨ q) → r) ,from
    (fun h : (p → r) ∧ (q → r) ,
      (fun hp_or_q : (p ∨ q) ,
        or.elim
          hp_or_q
          (fun hp : p , (and.left h) hp )
          (fun hq : q , (and.right h) hq )
      )
    )
  )

/--
  Double negation properties
-/

theorem dne {p : Prop}  : ¬¬p <-> p :=
  iff.intro
    (fun hnnp : ¬¬ p,
      classical.by_contradiction
        (fun h1 : ¬p , absurd h1 hnnp)
    )
    (fun hp : p ,
      (fun h1 : ¬p , absurd hp h1)
    )

theorem dne_intro {p : Prop} (q : Prop) : ¬ p -> ¬ (p ∧ q):=
  (fun hnp : ¬ p ,
    classical.by_contradiction
      (fun h_nn_p_and_q : ¬¬ (p ∧ q),
      have hp_and_q : p ∧ q := dne.mp h_nn_p_and_q,
        (hnp (and.left hp_and_q))
      )
  )

theorem dne_lintro {p : Prop} (q : Prop) : ¬ p -> ¬ ( q ∧ p ):=
  (fun hnp : ¬ p ,
    classical.by_contradiction
      (fun h_nn_q_and_p : ¬¬ (q ∧ p),
      have hp_and_q : q ∧ p := dne.mp h_nn_q_and_p,
        (hnp (and.right hp_and_q))
      )
  )

/--
  Negation distribution over ∨ and ∧ and ->
-/

theorem dist_neg_over_or {p q :Prop} : ¬(p ∨ q) <-> ( ¬p /\ ¬q ) :=
  iff.intro
    ( fun h : ¬(p ∨ q) ,
      have hnp : ¬p :=
        or.elim
          (classical.em p)
          (fun hp : p , absurd (or.intro_left q hp) h)
          (fun hnp :¬p , hnp ),
      have hnq : ¬q :=
        or.elim
          (classical.em q)
          (fun hq : q , absurd (or.intro_right p hq) h)
          (fun hnq :¬q , hnq ),
      and.intro hnp hnq
    )
    ( fun h : ( ¬p /\ ¬q ) ,
        or.elim
          (classical.em (p ∨ q))
          (fun h_p_or_q : p ∨ q ,
            or.elim
              h_p_or_q
              (fun hp : p , absurd hp (and.left h))
              (fun hq : q , absurd hq (and.right h))
          )
          (fun hnp :¬(p ∨ q) , hnp )
    )

theorem dist_neg_over_and {p q :Prop} : ¬(p /\ q) -> ( ¬p ∨ ¬q ) :=
  ( fun h : ¬(p /\ q) ,
    or.elim
    (classical.em ( ¬p ∨ ¬q ))
    (fun h1 : ( ¬p ∨ ¬q ) , h1)
    (fun h2 : ¬( ¬p ∨ ¬q ) ,
      have h3 : ¬¬p /\ ¬¬q := dist_neg_over_or.mp h2,
      have hp : p := dne.mp h3.left,
      have hq : q := dne.mp h3.right,
      absurd (and.intro hp hq) h
    )
  )

theorem not_cunj_to_impl {p q : Prop} : ¬(p ∧ ¬q) → (p → q) :=
  fun h : ¬(p ∧ ¬q) ,
  fun hp : p ,
  show q ,from
    or.elim (em q)
      (fun hq : q , hq)
      (fun hnq : ¬q , absurd (and.intro hp hnq) h)

theorem not_impl_to_cunj {p q : Prop} : ¬(p → q) → p ∧ ¬q :=
  (fun h: ¬(p → q) ,
    or.elim
      (em (p ∧ ¬q))
      (fun h1: (p ∧ ¬q) , h1)
      (fun h2: ¬(p ∧ ¬q) ,
        absurd (not_cunj_to_impl h2) h
      )
  )

/--
  Negation relation with ∨ and ∧ and ->
-/

example : (p → q) → (¬q → ¬p) :=
  (fun h : p -> q ,
    (fun hnq : ¬q ,
      or.elim
      (em p)
      (fun hp : p , absurd (h hp) (hnq) )
      (fun hnp : ¬ p , hnp )
    )
  )

example : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
  iff.intro
  (show ¬(p ∨ q)  -> ¬p ∧ ¬q ,from
    (fun h : ¬(p ∨ q) ,
      and.intro
      (fun hp : p , show false ,from h (or.intro_left q hp) )
      (fun hq : q , show false ,from h (or.intro_right p hq) )
    )
  )
  (show ¬p ∧ ¬q -> ¬(p ∨ q)  ,from
    (fun h : ¬p ∧ ¬q ,
      (fun hp_or_q : (p ∨ q) , show false ,from
        or.elim
          hp_or_q
          (fun hp : p , (and.left h) hp )
          (fun hq : q , (and.right h) hq )
      )
    )
  )

example : ¬p ∨ ¬q → ¬(p ∧ q) :=
  (fun h : ¬p ∨ ¬q ,
    (fun hp_and_q : (p ∧ q) , show false ,from
      or.elim
        h
        (fun hnp : ¬p , hnp (and.left hp_and_q) )
        (fun hnq : ¬q , hnq (and.right hp_and_q) )
    )
  )

example : ¬(p ∧ ¬p) :=
  (fun hp_and_not_p : (p ∧ ¬p) ,
    absurd (and.left hp_and_not_p)  (and.right hp_and_not_p)
  )

example : p ∧ ¬q → ¬(p → q) :=
  (fun hp_and_notq : (p ∧ ¬q) ,
      (fun( hp_give_q : p → q ) ,
          show false ,from absurd (hp_give_q (and.left hp_and_notq)) (and.right hp_and_notq)
      )
  )

example : ¬p → (p → q) :=
  (fun hnotp : (¬p) ,
    (fun hp : p ,
      absurd hp hnotp
    )
  )

theorem not_disj_to_impl : (¬p ∨ q) → (p → q) :=
  (fun hnotp_or_q : (¬p ∨ q) ,
    (fun hp : p ,
      or.elim
        hnotp_or_q
        (fun hnp : ¬p  , absurd hp hnp  )
        (fun hq : q , hq )
    )
  )

/--
  False relation with ∨ and ∧ and ->
-/

example : p ∨ false ↔ p :=
  iff.intro
  (show p ∨ false -> p ,from
    (fun hp_or_false :  p ∨ false ,
      or.elim
        hp_or_false
        (fun hp : p , hp )
        false.elim
    )
  )
  (show p -> p ∨ false ,from
    (fun hp : p ,
      or.intro_left false hp
    )
  )

example : p ∧ false ↔ false :=
  iff.intro
  (show p ∧ false -> false ,from
    (fun hp_and_false : p /\ false , and.right hp_and_false )
  )
  (show false -> p ∧ false , from false.elim )


import tactic

/-

# Prove that for positive integer n we have that
# 169 | 3^{3n+3}-26n-27

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

Proof: induction on n; base case n=0 works fine
inductive step 3^{3(n+1)+3}-26(n+1)-27 - 3^{3n+3}-26n-27 is

know 169 | 3^{3n+3}-26n-27
then it divides 27 times this
which is 3^{3(n+1)+3}-26*27*n-27*27
and we want it to divide 3^{3(n+1)+3}-26(n+1)-27

so we're done if it divides the difference, which is
-26n-26-27+26*27n+27*27
=26*26n+26*26 = 13*13*something
-/

example (n : ℕ) (hn : 0 < n) : -- remark; not going to use hn
(169 : ℤ) ∣ 3^(3*n+3)-26*n-27 :=
begin
  clear hn, -- told you
  induction n with d hd, { norm_num },
  rw nat.succ_eq_add_one,
  have h := dvd_mul_of_dvd_right hd ((3 : ℤ)^3),
  rw [mul_sub, mul_sub, ← pow_add] at h,
  rw [mul_add, mul_one, add_comm],
  have h2 : (169 : ℤ) ∣ 169*(4*d+4),
    exact dvd.intro (4 * ↑d + 4) rfl,
  convert dvd_add h h2,
  push_cast,
  ring_exp,
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

/-!

# Basic lemmas

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

-/

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

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

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

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

example : Y → X := e.symm

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

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

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

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

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

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

-- this is called `e.injective` but can you prove it from first principles?
-- equivalences are injective
example : function.injective (e : X → Y) :=
begin
  sorry,
end

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

/-

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

example (G H : Type) [group G] [group H] (e : G ≃ H)
  (he : ∀ a b : G, e (a * b) = e a * e b) :
  ∀ x y : H, e.symm (x * y) = e.symm x * e.symm y :=
begin
  sorry,
end


/-
Copyright (c) 2021 Ivan Sadofschi Costa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Ivan Sadofschi Costa.
-/
import data.mv_polynomial.basic
import data.mv_polynomial.variables
import algebra.algebra.basic
import data.mv_polynomial.comm_ring
import data.nat.basic
import from_flt_regular.homogenization

universes u v

variables {α : Type v}

open set function finsupp add_monoid_algebra

open_locale big_operators

/-

  Total_degree, etc

-/

namespace mv_polynomial
variables {R : Type*} {σ : Type*}
local attribute [instance] classical.prop_decidable

lemma X_ne_zero [comm_semiring R] [nontrivial R] (j : σ) : (X j : mv_polynomial σ R) ≠ 0 :=
  ne_zero_iff.2 ⟨single j 1, by simp⟩

lemma total_degree_sum [comm_semiring R] (s : finset α) (p : α → mv_polynomial σ R) :
  total_degree (∑ i : α in s, p i) ≤ s.sup (λ i , total_degree(p i)) :=
begin
  apply finset.cons_induction_on s,
  simp,
  clear s,
  intros a s h_a_in_s h_ind,
  repeat {rw finset.sum_cons},
  apply (total_degree_add _ _).trans,
  simp [h_ind],
end

lemma total_degree_mul_X_le [comm_semiring R] [nontrivial R] (f : mv_polynomial σ R) (j : σ) :
  total_degree (f * X j) ≤ total_degree f + 1 :=
begin
  apply (total_degree_mul f (X j)).trans,
  simp only [total_degree_X],
end

-- The following depends on https://github.com/leanprover-community/flt-regular

-- this appears in flt-regular as support_add_eq
lemma support_add_disjoint [comm_semiring R] {f g : mv_polynomial σ R}
  (h : disjoint f.support g.support) : (f + g).support = f.support ∪ g.support :=
finsupp.support_add_eq h

lemma total_degree_add_eq_of_disjoint_support [comm_semiring R] {f g : mv_polynomial σ R}
  (h : disjoint f.support g.support) : total_degree (f + g) = max f.total_degree g.total_degree :=
begin
  simp only [total_degree, support_add_disjoint h],
  apply finset.sup_union,
end

lemma total_degree_add_monomial [comm_semiring R] (f : mv_polynomial σ R) (a : σ →₀ ℕ) (b : R)
  (h : a ∉ f.support) (h_b: b ≠ 0) : total_degree (monomial a b + f)
    = linear_order.max (total_degree (monomial a b)) (total_degree f) :=
begin
  apply total_degree_add_eq_of_disjoint_support,
  simp [support_monomial, h_b, h],
end

open_locale pointwise

-- this uses code from flt-regular
lemma support_mul1 [comm_semiring R] [decidable_eq σ] (p q : mv_polynomial σ R) :
  (p * q).support ⊆ p.support + q.support := by convert support_mul' p q

lemma support_mul'' [comm_ring R] {f g : mv_polynomial σ R}{m : σ →₀ ℕ}( h : m ∈ (f * g).support) :
  ∃ mf mg, mf ∈ f.support ∧ mg ∈ g.support ∧ mf + mg = m :=  finset.mem_add.1 $ support_mul1 f g h

-- this uses code from flt-regular
lemma total_degree_mul' [comm_ring R] [is_domain R] {f g : mv_polynomial σ R} (hf : f ≠ 0)
  (hg : g ≠ 0) : total_degree (f * g) = total_degree f + total_degree g :=
total_degree_mul_eq hf hg

lemma total_degree_mul_X [comm_ring R] [is_domain R] {f : mv_polynomial σ R} (h : f ≠ 0)
  (j : σ) : total_degree (f * X j) = total_degree f + 1 :=
by simp [total_degree_mul' h (X_ne_zero j)]

end mv_polynomial


def fact: nat -> nat
| 0 := 1
| (n+1) := (n+1) * (fact n)

example: fact 5 = 120 := rfl

def c: nat -> nat
| 0 := 1
| 1 := 1
| (n+1) := (c n) * 2*(2*n + 1)/(n+2)

#eval c 1
#eval c 5
example: c 5 = 42 := rfl
#eval c 10

lemma lt_pred (x: nat) (h: 0 < x): (x-1) < x := nat.sub_lt h (nat.lt.base 0)
def fact' := well_founded.fix nat.lt_wf (λ x f, if h : x > 0 then x * (f (x-1) (lt_pred x h) ) else 1)
#eval fact' 8
example: fact' 8 = fact 8 := rfl.

lemma fact'_succ (n: nat): fact' (n+1) = (n+1)*(fact' n) := rfl.
theorem commutes (n: nat): fact n = fact' n := begin
induction n, reflexivity,
case nat.succ: n ih {
  simp [fact, fact'_succ, ih],
}
end


import .love03_forward_proofs_demo
import .love04_functional_programming_demo

/-! # LoVe Demo 5: Inductive Predicates

__Inductive predicates__, or inductively defined propositions, are a convenient
way to specify functions of type `⋯ → Prop`. They are reminiscent of formal
systems and of the Horn clauses of Prolog, the logic programming language par
excellence.

A possible view of Lean:

    Lean = typed functional programming + logic programming + more logic -/

set_option pp.beta true

namespace LoVe

/-! ## Introductory Examples

### Even Numbers

Mathematicians often define sets as the smallest that meets some criteria. For
example:

    The set `E` of even natural numbers is defined as the smallest set closed
    under the following rules: (1) `0 ∈ E` and (2) for every `k ∈ ℕ`, if
    `k ∈ E`, then `k + 2 ∈ E`.

In Lean, we can define the corresponding "is even" predicate as follows: -/

inductive even : ℕ → Prop
| zero    : even 0
| add_two : ∀k : ℕ, even k → even (k + 2)

/-! This should look familiar. We have used the same syntax, except with `Type`
instead of `Prop`, for inductive types.

The above command introduces a new unary predicate `even` as well as two
constructors, `even.zero` and `even.add_two`, which can be used to build proof
terms. Thanks to the "no junk" guarantee of inductive definitions, `even.zero`
and `even.add_two` are the only two ways to construct proofs of `even`.

By the Curry–Howard correspondence, `even` can be seen as a data type, the
values being the proof terms. -/

lemma even_4 :
  even 4 :=
have even_0 : even 0 :=
  even.zero,
have even_2 : even 2 :=
  even.add_two _ even_0,
show even 4, from
  even.add_two _ even_2

/-! Why cannot we simply define `even` recursively? Indeed, why not? -/

def even₂ : ℕ → bool
| 0       := tt
| 1       := ff
| (k + 2) := even₂ k

/-! There are advantages and disadvantages to both styles.

The recursive version requires us to specify a false case (1), and it requires
us to worry about termination. On the other hand, because it is computational,
it works well with `refl`, `simp`, `#reduce`, and `#eval`.

The inductive version is often considered more abstract and elegant. Each rule
is stated independently of the others.

Yet another way to define `even` is as a nonrecursive definition: -/

def even₃ (k : ℕ) : bool :=
k % 2 = 0

/-! Mathematicians would probably find this the most satisfactory definition.
But the inductive version is a convenient, intuitive example that is typical of
many realistic inductive definitions.

### Tennis Games

Transition systems consists of transition rules, which together specify a
binary predicate connecting a "before" and an "after" state. As a simple
specimen of a transition system, we consider the possible transitions, in a game
of tennis, starting from 0–0. -/

inductive score : Type
| vs       : ℕ → ℕ → score
| adv_srv  : score
| adv_rcv  : score
| game_srv : score
| game_rcv : score

infixr ` – ` : 10 := score.vs

inductive step : score → score → Prop
| srv_0_15    : ∀n, step (0–n) (15–n)
| srv_15_30   : ∀n, step (15–n) (30–n)
| srv_30_40   : ∀n, step (30–n) (40–n)
| srv_40_game : ∀n, n < 40 → step (40–n) score.game_srv
| srv_40_adv  : step (40–40) score.adv_srv
| rcv_0_15    : ∀n, step (n–0) (n–15)
| rcv_15_30   : ∀n, step (n–15) (n–30)
| rcv_30_40   : ∀n, step (n–30) (n–40)
| rcv_40_game : ∀n, n < 40 → step (n–40) score.game_rcv
| rcv_40_adv  : step (40–40) score.adv_rcv

infixr ` ⇒ ` := step

/-! We can ask—and formally answer—questions such as: Is this transition system
confluent? Does it always terminate? Can the score 65–15 be reached from 0–0?

### Reflexive Transitive Closure

Our last introductory example is the reflexive transitive closure of a
relation `r`, modeled as a binary predicate `star r`. -/

inductive star {α : Type} (r : α → α → Prop) : α → α → Prop
| base (a b : α)    : r a b → star a b
| refl (a : α)      : star a a
| trans (a b c : α) : star a b → star b c → star a c

/-! The first rule embeds `r` into `star r`. The second rule achieves the
reflexive closure. The third rule achieves the transitive closure.

The definition is truly elegant. If you doubt this, try implementing `star` as a
recursive function: -/

def star₂ {α : Type} (r : α → α → Prop) : α → α → Prop :=
sorry

/-! ### A Nonexample

Not all inductive definitions admit a least solution. -/

-- fails
inductive illegal : Prop
| intro : ¬ illegal → illegal

/-! ## Logical Symbols

The truth values `false` and `true`, the connectives `∧` and `∨`, the
`∃`-quantifier, and the equality predicate `=` are all defined as inductive
propositions or predicates. In contrast, `∀` (= `Π`) and `→` are built into
the logic.

Syntactic sugar:

    `∃x : α, p` := `Exists (λx : α, p)`
    `x = y`     := `eq x y` -/

namespace logical_symbols

inductive and (a b : Prop) : Prop
| intro : a → b → and

inductive or (a b : Prop) : Prop
| intro_left  : a → or
| intro_right : b → or

inductive iff (a b : Prop) : Prop
| intro : (a → b) → (b → a) → iff

inductive Exists {α : Type} (p : α → Prop) : Prop
| intro : ∀a : α, p a → Exists

inductive true : Prop
| intro : true

inductive false : Prop

inductive eq {α : Type} : α → α → Prop
| refl : ∀a : α, eq a a

end logical_symbols

#print and
#print or
#print iff
#print Exists
#print true
#print false
#print eq

/-! ## Rule Induction

Just as we can perform induction on a term, we can perform induction on a proof
term.

This is called __rule induction__, because the induction is on the introduction
rules (i.e., the constructors of the proof term). Thanks to the Curry–Howard
correspondence, this works as expected. -/

lemma mod_two_eq_zero_of_even (n : ℕ) (h : even n) :
  n % 2 = 0 :=
begin
  induction h,
  case even.zero {
    refl },
  case even.add_two : k hk ih {
    simp [ih] }
end

lemma star_star_iff_star {α : Type} (r : α → α → Prop)
    (a b : α) :
  star (star r) a b ↔ star r a b :=
begin
  apply iff.intro,
  { intro h,
    induction h,
    case star.base : a b hab {
      exact hab },
    case star.refl : a {
      apply star.refl },
    case star.trans : a b c hab hbc ihab ihbc {
      apply star.trans a b,
      { exact ihab },
      { exact ihbc } } },
  { intro h,
    apply star.base,
    exact h }
end

@[simp] lemma star_star_eq_star {α : Type}
    (r : α → α → Prop) :
  star (star r) = star r :=
begin
  apply funext,
  intro a,
  apply funext,
  intro b,
  apply propext,
  apply star_star_iff_star
end

#check funext
#check propext

/-! ## Rule Induction Pitfalls

Inductive predicates often have arguments that evolve through the induction.
Some care is necessary. -/

lemma p_of_even (p : ℕ → Prop) (n : ℕ) :
  even n → p n :=
begin
  intro h,
  induction h,
  case even.zero {
    sorry },   -- looks reasonable
  case even.add_two {
    sorry }    -- looks reasonable
end

lemma not_even_2_mul_add_1_sorry (n : ℕ) :
  ¬ even (2 * n + 1) :=
begin
  intro h,
  induction h,
  case even.zero {
    sorry },   -- unprovable
  case even.add_two : k hk ih {
    exact ih }
end

lemma not_even_2_mul_add_1_sorry₂ (n : ℕ) :
  ¬ even (2 * n + 1) :=
begin
  generalize hx : 2 * n + 1 = x,
  intro h,
  induction h,
  case even.zero {
    cases hx },
  case even.add_two : k hk ih {
    apply ih,
    rewrite hx,
    sorry }   -- unprovable
end

lemma not_even_2_mul_add_1 (n : ℕ) :
  ¬ even (2 * n + 1) :=
begin
  generalize hx : 2 * n + 1 = x,
  intro h,
  induction h generalizing n,
  case even.zero {
    cases hx },
  case even.add_two : k hk ih {
    apply ih (n - 1),
    cases n,
    case nat.zero {
      linarith },
    case nat.succ : m {
      simp [nat.succ_eq_add_one] at *,
      linarith } }
end

/-! `linarith` proves goals involving linear arithmetic equalities or
inequalities. "Linear" means it works only with `+` and `-`, not `*` and `/`
(but multiplication by a constant is supported). -/

lemma linarith_example (i : ℤ) (hi : i > 5) :
  2 * i + 3 > 11 :=
by linarith

/-! ## Elimination

Given an inductive predicate `p`, its introduction rules typically have the form
`∀…, ⋯ → p …` and can be used to prove goals of the form `⊢ p …`.

Elimination works the other way around: It extracts information from a lemma or
hypothesis of the form `p …`. Elimination takes various forms: pattern matching,
the `cases` and `induction` tactics, and custom elimination rules (e.g.,
`and.elim_left`).

* `cases` works roughly like `induction` but without induction hypothesis.

* `match` is available as well, but it corresponds to dependently typed pattern
  matching (cf. `vector` in lecture 4).

Now we can finally analyze how `cases h`, where `h : l = r`, and how
`cases classical.em h` work. -/

#print eq

lemma cases_eq_example {α : Type} (l r : α) (h : l = r)
    (p : α → α → Prop) :
  p l r :=
begin
  cases h,
  sorry
end

#check classical.em
#print or

lemma cases_classical_em_example {α : Type} (a : α)
    (p q : α → Prop) :
  q a :=
begin
  have h : p a ∨ ¬ p a :=
    classical.em (p a),
  cases h,
  case or.inl {
    sorry },
  case or.inr {
    sorry }
end

/-! Often it is convenient to rewrite concrete terms of the form `p (c …)`,
where `c` is typically a constructor. We can state and prove an
__inversion rule__ to support such eliminative reasoning.

Typical inversion rule:

    `∀x y, p (c x y) → (∃…, ⋯ ∧ ⋯) ∨ ⋯ ∨ (∃…, ⋯ ∧ ⋯)`

It can be useful to combine introduction and elimination into a single lemma,
which can be used for rewriting both the hypotheses and conclusions of goals:

    `∀x y, p (c x y) ↔ (∃…, ⋯ ∧ ⋯) ∨ ⋯ ∨ (∃…, ⋯ ∧ ⋯)` -/

lemma even_iff (n : ℕ) :
  even n ↔ n = 0 ∨ (∃m : ℕ, n = m + 2 ∧ even m) :=
begin
  apply iff.intro,
  { intro hn,
    cases hn,
    case even.zero {
      simp },
    case even.add_two : k hk {
      apply or.intro_right,
      apply exists.intro k,
      simp [hk] } },
  { intro hor,
    cases hor,
    case or.inl : heq {
      simp [heq, even.zero] },
    case or.inr : hex {
      cases hex with k hand,
      cases hand with heq hk,
      simp [heq, even.add_two _ hk] } }
end

lemma even_iff₂ (n : ℕ) :
  even n ↔ n = 0 ∨ (∃m : ℕ, n = m + 2 ∧ even m) :=
iff.intro
  (assume hn : even n,
   match n, hn with
   | _, even.zero         :=
     show 0 = 0 ∨ _, from
       by simp
   | _, even.add_two k hk :=
     show _ ∨ (∃m, k + 2 = m + 2 ∧ even m), from
       or.intro_right _ (exists.intro k (by simp [*]))
   end)
  (assume hor : n = 0 ∨ (∃m, n = m + 2 ∧ even m),
   match hor with
   | or.intro_left _ heq :=
     show even n, from
       by simp [heq, even.zero]
   | or.intro_right _ hex :=
     match hex with
     | Exists.intro m hand :=
       match hand with
       | and.intro heq hm :=
         show even n, from
           by simp [heq, even.add_two _ hm]
       end
     end
   end)

/-! ## Further Examples

### Sorted Lists -/

inductive sorted : list ℕ → Prop
| nil : sorted []
| single {x : ℕ} : sorted [x]
| two_or_more {x y : ℕ} {zs : list ℕ} (hle : x ≤ y)
    (hsorted : sorted (y :: zs)) :
  sorted (x :: y :: zs)

lemma sorted_nil :
  sorted [] :=
sorted.nil

lemma sorted_2 :
  sorted [2] :=
sorted.single

lemma sorted_3_5 :
  sorted [3, 5] :=
begin
  apply sorted.two_or_more,
  { exact dec_trivial },
  { exact sorted.single }
end

lemma sorted_3_5₂ :
  sorted [3, 5] :=
sorted.two_or_more dec_trivial sorted.single

lemma sorted_7_9_9_11 :
  sorted [7, 9, 9, 11] :=
sorted.two_or_more dec_trivial
  (sorted.two_or_more dec_trivial
     (sorted.two_or_more dec_trivial
        sorted.single))

lemma not_sorted_17_13 :
  ¬ sorted [17, 13] :=
assume h : sorted [17, 13],
have 17 ≤ 13 :=
  match h with
  | sorted.two_or_more hle _ := hle
  end,
have ¬ 17 ≤ 13 :=
  dec_trivial,
show false, from
  by cc

/-! ### Palindromes -/

inductive palindrome {α : Type} : list α → Prop
| nil : palindrome []
| single (x : α) : palindrome [x]
| sandwich (x : α) (xs : list α) (hxs : palindrome xs) :
  palindrome ([x] ++ xs ++ [x])

-- fails
def palindrome₂ {α : Type} : list α → Prop
| []                 := true
| [_]                := true
| ([x] ++ xs ++ [x]) := palindrome₂ xs
| _                  := false

lemma palindrome_aa {α : Type} (a : α) :
  palindrome [a, a] :=
palindrome.sandwich a _ palindrome.nil

lemma palindrome_aba {α : Type} (a b : α) :
  palindrome [a, b, a] :=
palindrome.sandwich a _ (palindrome.single b)

lemma reverse_palindrome {α : Type} (xs : list α)
    (hxs : palindrome xs) :
  palindrome (reverse xs) :=
begin
  induction hxs,
  case palindrome.nil {
    exact palindrome.nil },
  case palindrome.single : x {
    exact palindrome.single x },
  case palindrome.sandwich : x xs hxs ih {
    simp [reverse, reverse_append],
    exact palindrome.sandwich _ _ ih }
end

/-! ### Full Binary Trees -/

#check btree

inductive is_full {α : Type} : btree α → Prop
| empty : is_full btree.empty
| node (a : α) (l r : btree α)
    (hl : is_full l) (hr : is_full r)
    (hiff : l = btree.empty ↔ r = btree.empty) :
  is_full (btree.node a l r)

lemma is_full_singleton {α : Type} (a : α) :
  is_full (btree.node a btree.empty btree.empty) :=
begin
  apply is_full.node,
  { exact is_full.empty },
  { exact is_full.empty },
  { refl }
end

lemma is_full_mirror {α : Type} (t : btree α)
    (ht : is_full t) :
  is_full (mirror t) :=
begin
  induction ht,
  case is_full.empty {
    exact is_full.empty },
  case is_full.node : a l r hl hr hiff ih_l ih_r {
    rewrite mirror,
    apply is_full.node,
    { exact ih_r },
    { exact ih_l },
    { simp [mirror_eq_empty_iff, *] } }
end

lemma is_full_mirror₂ {α : Type} :
  ∀t : btree α, is_full t → is_full (mirror t)
| btree.empty        :=
  begin
    intro ht,
    exact ht
  end
| (btree.node a l r) :=
  begin
    intro ht,
    cases ht with _ _ _ hl hr hiff,
    rewrite mirror,
    apply is_full.node,
    { exact is_full_mirror₂ _ hr },
    { apply is_full_mirror₂ _ hl },
    { simp [mirror_eq_empty_iff, *] }
  end

/-! ### First-Order Terms -/

inductive term (α β : Type) : Type
| var {} : β → term
| fn     : α → list term → term

inductive well_formed {α β : Type} (arity : α → ℕ) :
  term α β → Prop
| var (x : β) : well_formed (term.var x)
| fn (f : α) (ts : list (term α β))
    (hargs : ∀t ∈ ts, well_formed t)
    (hlen : list.length ts = arity f) :
  well_formed (term.fn f ts)

inductive variable_free {α β : Type} : term α β → Prop
| fn (f : α) (ts : list (term α β))
    (hargs : ∀t ∈ ts, variable_free t) :
  variable_free (term.fn f ts)

end LoVe


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

/-!

# Logic in Lean, example sheet 5 : "iff" (`↔`)

We learn about how to manipulate `P ↔ Q` in Lean.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following two new tactics:

* `refl`
* `rw`

-/

variables (P Q R S : Prop)

example : P ↔ P :=
begin
  refl,
end

example : P ↔ P :=
begin
  exact iff.rfl, -- this is what the above has found
end

example : P ↔ P :=
begin
  split,
  { exact id, }, -- identity function
  { intro hP,
    exact hP, },
end

example : (P ↔ Q) → (Q ↔ P) :=
begin
  intro hPQ,
  cases hPQ with hPQ hQP,
  split,
  exact hQP,
  exact hPQ,
end

example : (P ↔ Q) → (Q ↔ P) :=
begin
  intro hPQ,
  cases hPQ with hPQ hQP,
  split; -- do exactly the same to both goals.
  assumption,
end

example : (P ↔ Q) → (Q ↔ P) :=
begin
  intro hPQ,
  rw hPQ,
end

example : (P ↔ Q) → (Q ↔ P) :=
begin
  rintro ⟨hPQ, hQP⟩, --intro then cases
  exact ⟨hQP, hPQ⟩,
end

-- very intriguing... why does this work? rw has something to
-- close goals in it
example : (P ↔ Q) ↔ (Q ↔ P) :=
begin
  split,
  intro hPQ,
  rw hPQ,
  intro hQP,
  rw hQP,
end

example : (P ↔ Q) → (Q ↔ R) → (P ↔ R) :=
begin
  intros hPQ hQR,
  rw [hPQ, hQR], -- how cute
end

example : P ∧ Q ↔ Q ∧ P :=
begin
  split,
  rintro ⟨hP, hQ⟩,
  exact ⟨hQ, hP⟩,
  rintro ⟨hQ, hP⟩,
  exact ⟨hP, hQ⟩,
end

example : P ∧ Q ↔ Q ∧ P :=
begin
  split;
  exact (λ hPQ, ⟨hPQ.right, hPQ.left⟩)
end

example : ((P ∧ Q) ∧ R) ↔ (P ∧ (Q ∧ R)) :=
begin
  split,
  { rintro ⟨⟨hP, hQ⟩, hR⟩,
    exact ⟨hP, hQ, hR⟩, },
  { rintro ⟨hP, hQ, hR⟩,
    exact ⟨⟨hP, hQ⟩, hR⟩, },
end

example : P ↔ (P ∧ true) :=
begin
  split,
  { intro hP,
    exact ⟨hP, true.intro⟩ },
  { rintro ⟨hP, _⟩,
    exact hP, },
end

example : false ↔ (P ∧ false) :=
begin
  sorry
end

example : (P ↔ Q) → (R ↔ S) → (P ∧ R ↔ Q ∧ S) :=
begin
  sorry
end

example : ¬ (P ↔ ¬ P) :=
begin
  sorry,
end


def f : Nat → Nat := fun n => 2*n^3 + 4*n + 3
def g (n : Nat) : Nat := n^2 + 1

#check g ∘ f
#eval f 1
#eval g (f 1)
#eval (g ∘ f) 1

#eval f (g 1)

variable (α β γ δ : Type)
variable (f : α → β) (g : β → γ) (h : γ → δ)

theorem Assoc : (h ∘ g) ∘ f = h ∘ (g ∘ f) := by
  apply funext
  intro x
  rfl

theorem comp_id : f ∘ (@id α) = f := by
  apply funext
  intro x
  rfl

theorem id_comp : (@id β) ∘ f = f := by
  apply funext
  intro x
  rfl

variable (f₁ f₂ : α → β)

#check @congrFun
#check @congrArg

example (h : f₁ = f₂) (a : α) : f₁ a = f₂ a := by
  exact congrFun h a

example (h : ∃ a, f₁ a ≠ f₂ a) : f₁ ≠ f₂  := by
  intro n
  have ⟨a,h₁⟩ := h
  have : f₁ a = f₂ a := congrFun n a
  exact h₁ this


import tactic.ring
import tactic.norm_num

/- #1 Formalizing propositions in English

Translate the following English statements
into formal propositions. We give an example
first. The prove the proposion formally.
Finally write an English language proof.
-/

/-
For any natural number, n, if n ≠ 0, then there
is another natural number, call it n', such that
n is the successor of n' (n = nat.succ n'). You
should see that n' will have to be one less than n.
-/

def p1a : Prop :=
  ∀ (n : ℕ),            -- For any natural number n
    n ≠ 0 →             -- If n ≠ 0 then
    ∃ (n' : ℕ),         -- there is some number n'
      n = nat.succ n'   -- such than n is one more than n'

/-
The proof is by case analysis on n. What you
need to know, again, is that there are only
two possible cases for a natural number, n. It
is either 0 or the successor of another number,
let's call it n'.

The first case contradicts our assumption that
n = 0, and so can be dismissed. In the second
case, there's only one value for n' that will
satisfy the predicate, so give it as a witness
to the ∃ then prove it satisfies the predicate.
Note that Lean will use the notation, n.succ,
as a shorthand for nat.succ n.
-/
example : p1a :=        -- We are to prove p1a
begin

-- By the definition of p1a
unfold p1a,

-- We are to
show ∀ (n : ℕ), n ≠ 0 → (∃ (n' : ℕ), n = n' + 1),

-- To start let n be an arbitrary nat and assume n ≠ 0
assume n h,

-- It remains for us to ...
show ∃ (n' : ℕ), n = n' + 1,

-- solve for n' : n + 1 = n' + 1

/-
The remaining proof is by case analysis
on n. The cases (by the definition of nat,
which we are about to see) are n = 0 and
n is the successor of a one-smaller number,
n'.
-/
cases n with x,

/- In the first case, we replace n with 0,
reducing h to 0 ≠ 0, which is a contradiction.
-/
contradiction,

-- In the second case, we need to ...
show ∃ (n' : ℕ), x.succ = n' + 1,

/-
Work hard to understand this goal. It requires
that we show that there exists a number n' such
that n'+1 = n = succ x. In other words, we need
n' such that n' + 1 = n = x + 1. It should now be
clear: x satisfies this equation, so we can use
it as the witness to prove the existential
proposition that remains to be proved.
-/
apply exists.intro x _,

/-
All that now remains to prove is the equality
(yielding the second argument to exists.intro).
-/
apply rfl,
end

/-
-/

/- #1A.
State and prove the proposition that there's some
natural number whose square is 144.
-/

-- Pick n=12 then show 144 = 12*12, by arithmetic
example : ∃ n, n*n = 144 :=
begin
apply exists.intro 12,
exact rfl,
end

/- #1B.
State and prove the proposition that there is
some string, s, such that s ++ "!" is the string,
"I love logic!." Note: In Lean, ++ is notation
for string.append, the function for gluing two
strings together into one.
-/

-- Proof strategy is same as previous problem
example : ∃ s, s ++ "!" = "I love logic!" :=
  exists.intro "I love logic" rfl

-- Extra examples

example : ∃ s : string, s.length = 5 :=
exists.intro "Lean!" rfl

example : ∃ n, n*n + 5 = 30 := exists.intro 5 rfl

example : ∃ n : int, n*n = 1 := exists.intro 1 rfl

/-
There's an important lesson here: When you have
to prove an ∃ proposition, you have to *search*
for and come up with a witness that satisfies the
given predicate. In general, this can be hard.
It often involves doing a bunch of math and/or
other reasoning "on the side." Here for example,
you have to "search for" a string that satisfies
the predicate. It's easy in this case but often
isn't so trivial. The important point is that to
prove an ∃ proposition often involves solving a
problem, such as a set of equations, to find a
witness that will "work."
-/

example : ∃ n, n*n - 1 = 0 := exists.intro 1 rfl

/- #1C.

Formally state and prove the proposition that
there are three natural numbers, x, y, and z,
such that x*x + y*y = z*z. Hint: exists.intro
takes just one witness as a time, so you will
have to apply it more than once.
-/

-- example : ∃ (x y z : nat), x*x + y*y = z*z :=
example : ∃ x : ℕ, ∃ y, ∃ z, x*x + y*y = z*z :=
begin
/-
The key, simple, insight that unlocks a proof
of this theorem is to know that ∃ (x y z), ...
means ∃ (x), ∃ (y), ∃ (z), ... You thus need
to give a witness for x, then prove the rest,
which means giving a witness for y then proving
the rest, which means giving a witness for z
then proving the rest, which is finally done
by arithmetical simplification and reflexivity
of equality.
-/
apply exists.intro 3 _,
apply exists.intro 4 _,
apply exists.intro 5 _,
exact rfl,
end

/- #1D
Define a predicate called pythag_triple taking
three natural number arguments, x, y, and z,
yielding the proposition that x*x + y*y = z*z.
-/

def pythag_triple (x y z : ℕ) : Prop := x*x + y*y = z*z

#check pythag_triple 1 2 3
#reduce pythag_triple 3 4 5

/- #1E
State the proposition that there exist x, y, z,
natural numbers, that satisfy the pythag_triple,
predicate, then prove it. (Use "example : ...")
-/

-- Know how to use predicates to form propositions!
example : ∃ (x y z), pythag_triple x y z :=
begin
/-
The rest is d simple application of #1C.
-/
apply exists.intro 3,
apply exists.intro 4,
apply exists.intro 5,
-- unfold pythag_triple, -- not necessary
-- show 25 = 25,
exact rfl,
end

example : ∃ (x y z), z ≠ 5 ∧ pythag_triple x y z :=
begin
apply exists.intro 5,
apply exists.intro 12,
apply exists.intro 13,
unfold pythag_triple,
apply and.intro _ _,
-- prove 13 ≠ 5 by negation
{
  show ¬ 13 = 5,
  show 13 = 5 → false,
  assume h,
  cases h,
},
-- prove the remaining equality proposition
{
  exact rfl,
}
end

-- REVIEW 12/7 stopped here

/- #2A

Define a predicate, (multiple_of n m), true
if and only if n is a multiple of m. Hint:
What has to be true for this to be the case?
-/

def multiple_of (n m : ℕ) := ∃ (k), n = m * k

/- #2B

Using the predicate, multiple_of, state and
prove the proposition that every natural number
that is a multiple of 6 is also a multiple of
3. Hint: you can use "unfold multiple_of at h,"
to expand the definition of multiple_of in the
hypothesis, h (assuming you call it that).

Hint: Put the argument you will give to exists
intro in parentheses (needed for correct syntax).

Hint: You might end up with n = 3 * (2 * w)
as a goal. The "ring" tactic in Lean will
simplify this expression to n = 6 * w.

Before you do the work, let's talk a little
more about the "ring" tactic. First, where does
the name come from? Second, what does it do?

A "ring" in college-level algebra (and beyond)
is any set of values (such as natural numbers)
with + and * operations that satisfy the usual
rules of arithmetic (such as the distributive
laws, the associativity of + and *, etc). Not
only the natural numbers form a ring, but so
do polynomials, and many other kinds of math
objects as well.

The ring tactic is used to put any expression
involing any ring" into a "normal" form. What
"normal" means in this context is that if you
put two mathematically equivalent but different
expressions in normal form, then you get the
same "normalized" expression in both cases,
making it easy to compare them for equality.

So, in particular, if you want to know whether
a+(b+c)=(a+b)+c, with these "ring" expressions
on both sides of the = sign, put both expresions
in normal form and see if they are equal (which
again they are if + is addition in any "ring").

A good English translation of the use of the
ring tactic is "by basic algebra."

Here's an example. Is ℕ addition associative?
-/

example (n m k : ℕ) : n + (m + k) = (n + k) + m :=
begin
ring
end

/-
Whoa! It's that easy to prove addition
associative? Yep, "by simple algebra." Thankfully
someone else has written this beautiful tactic so
you don't have to do all the algebra yourself.
-/

/-
As a small aside on Lean syntax, if a tactic script is
just one tactic long, you can use "by <tactic>" instead
wrapping the tactic in a begin-end block.
-/
example (n m k : ℕ) : n + (m + k) = (n + k) + m := by ring

/-
Ok, with that background in place, let's return to the
problem we were discussing. Is it true that if any natural
number if a multiple of 6 then it's also a multiple of 3?

Before you even consider writing a proof, whether in Lean
or in English, figure out yourself whether the proposition
appears true or not. Try to prove it "mentally" first.

The key question here is, what does it even mean for a
number, n, to be a multiple of 6. Well, n is a multiple
of 6 if there's some number, say k, such that n = k * 6,
right? Now you should be able to formally write, and then
prove, the proposition on the table. Is it true that for
any n, if n is a multiple of 6 then it's a multiple of 3?
Why, exactly? Well, there would have to be another number
that makes n when you multiply it by 3. The trick here is
to figure out what that number has to be so you can prove
that there does exist such a number.
-/

/-
We want to prove the proposition that if any n is a
multiple of 6 then it's also a multiple of 3. You are
now expected to understand how to write propositions
that use predicates that you yourselves have defined,
and of course to translate "every" and "if ... then"
into predicate logic.
-/
example : ∀ (n), multiple_of n 6 → multiple_of n 3 :=
begin
-- Assume n is arbitrary
intros n,

-- Assume in addition that it's a multiple of 6
assume h,

-- Unfold the definition of multiple_of in h for clarity
unfold multiple_of at h,

-- From h deduce that there's some w such that n = 6 * w
cases h with w pf,

-- By the definition of multiple_of we are to ...
unfold multiple_of,

-- Show
show ∃ (k : ℕ), n = 3 * k,

/-
Now the lesson about having to search
or solve for an appropriate witness is
the key! Given what you have to work with,
what number will satsfy the predicate of
the remaining existentially quantified
goal? Well if n = 6 * w and we want to
show that n = 3 * w' (where w' will be
our witness for the remaining proof),
what does w' have to be? You have to
solve a simple set of equations here
to answer the question.

n = 6 * w
  = (3 * 2) * w
  = 3 * (2 * w)
  = 3 * w' where w' = 2 * w
That's it!
-/
apply exists.intro (2*w) _, -- figure out what witness to give!

/-
We have a proof that n = 6 * w and we need
to show that n = 3 * (2 * w). Formally speaking
3 * (2 * w) = (3 * 2) * w by the associativity
of multiplication, which then reduces to 6 * w,
and we already have a proof that n = 6 * w. We
can avoid having to explicitly apply low-level
arithemtical axioms and theorems by using the
"ring" tactic in Lean, as explained above. In
English, again, you can just say, "then by basic
arithmetic, n = 6 * w."
-/
ring,

/-
Which is proved by the fact that it's already
an assumption that n is a multiple of 6.
-/
assumption,               -- and done. QED.
end

/- #2C.

Is it true that if n is a multiple of h, and h
is a multiple of k, that n is a multiple of k?
Formally state and then prove the proposition.

In writing this proof, you might need to use one
of the two fundamental axioms of equality, via
the "rewrite" tactic (abbreviated rw). Here again
is how it works:

If you've already proved/know, and so have in
your context, a proof of an equality, such as
h : m = k, and if  m appears in your goal, then
you can replace the m with k by using "rw h".
The rewriting of m to k in your goal is justified
by the fact, pf, that m = k.

The rw tactic is applying the second axiom of
equality, which states that if m = k and you
have a proof, h : P m, for some predicate P,
then you can conclude that P k is true, too.

In other words, "you can substitute equal terms
for each other, and that is valid."

Similarly, if you have h : m = k and a goal with
*k* in it, you can rewrite the k to m using
rw <- h. The left pointing arrow indicates you
want to use the equality right to left rather
than left to right.

With all that in mind, here we go.
-/

-- Write the proposition involving your own predicates
example (n h k) : multiple_of n h → multiple_of h k → multiple_of n k :=
begin
-- We've already assumed that n h and k are arbitrary natural numbers
-- Now assume n is a multiple of h and h is a multiple of k.
assume monh mohk,

-- What remains is to ...
show multiple_of n k,

/-
From the fact n is a multiple of h we
can deduce that there's some natural
number, a witness to this fact, let's
call it w_nh, along with a proof that
it is a witness.
-/
cases monh with w_nh pf_nh,

/-
Similarly, we deduce a witness, w_hk,
to the fact that h is a multiple of k,
with a corresponding proof.
-/
cases mohk with w_hk pf_hk,

/-
Now you're at the point where you have
to do some algebra to figure out what
witness will work to prove the conclusion
of the conjecture (the proposition to be
proved).

From n = h * w_nh and h = k * w _hk we
deduce n = (k * w_hk) * w_nh. Then we can
see tgat by associativity of multiplication
this equals k * (w_hk * w_nh), so n is a
multiple of k, with witness w_hk * w_nh.
-/
apply exists.intro (w_nh * w_hk),

-- We can now expand the definition of n
rw pf_nh,

-- Then expand the definition of h
rw pf_hk,

/-
And finally "normalize" both sides, of
the equation (which in this case involves
applying associativity of multiplication)
to show that both sides are equal. I.e.,
"Finally, by simple arithmetic QED."
-/
ring,
end

/-
Next is practice with exists.elimination
-/

/- #3A

Formally state and prove that if everyone
who knows logic is cool and someone knows
logic, then someone is cool.

Once again the trick in proving an exists
(the conclusion of the implication) is to
"figure out" what/who to use as a witness.

In this case, before you even think about
using Lean, just think about what the logic
says.

Someone knows logic. The principle of exists
elimination says you can give that person a
name, e.g., w, and have a proof that w knows
logic. Furthermore, you can assume everyone
who knows logic is cool. *So you know at least
one person who must be cool* Who is it? Once
you answer this question, the proof, whether
in English or in Lean, is easy.
-/

/-
There are several ways to write the proposition
formally. In this solution, we introduce all of
the assumptions as arguments (before the colon),
implicitly creating the context in which the
rest of the proof is to be given.
-/

example
  -- Represent people as objects of a type, Person
  (Person : Type)

  -- Represent "knows logic" as a predicate on people
  (KnowsLogic : Person → Prop)

  -- Similarly represent "is cool" as a predicate on people
  (isCool : Person → Prop)

  -- Formalize the idea that if anyone knows logic they are cool
  (LogicMakesCool : ∀ (p), KnowsLogic p → isCool p)

  -- Formalize the idea that someone knows logic
  (SomeoneKnowsLogic : ∃ (p), KnowsLogic p) : -- here's the colon!
  -- In this context show that there's someone who is cool
  ∃ (p), isCool p :=
begin
  -- Someone knows logic, so give that person a name, w, and get a proof w knows logic
  cases SomeoneKnowsLogic with w pf,

  -- Now
  show ∃ (p : Person), isCool p,

  -- We know at least one person who must be cool
  apply exists.intro w _,

/-
Finally since anyone who knows logic
is cool, and w knows logic, w must be
cool, by universal specialization (∀
elimination).
-/
  apply LogicMakesCool w pf,
end

/- #3B
Formally state and prove the proposition that if
someone is not happy then not everyone is happy.
-/

example
  -- Let's talk about people
  (Person : Type)
  -- And that people can be happy
  (Happy : Person → Prop) :
  /-
  In this context, show that if there is
  someone who is unhappy (there must now
  be at least one!) then it's not true
  that everyone is happy.
  -/
  (∃ (p), ¬(Happy p)) →
  (¬ (∀ (p), Happy p))
  :=
begin
  -- Start by assuming that someone is not happy
  intros h,

  -- By exists elim given them a name, w, and get of proof of their unhappiness
  cases h with w w_unhappy,

  -- The rest is a proof by negation. Assume that everyone *is* happy
  assume everyone_happy,

  -- Derive a proof that w is happy
  let w_happy := everyone_happy w,

  -- But that contradicts
  contradiction,
end

/- #3C
Formally state and prove that stating that
everyone  is happy is equivalent to saying
that no one is unhappy.

Hint: In one direction, you might need
to use classical reasoning; and remember
you can get a proposition (on which to do
classical case anaysis) by applying a
predicate to the right arguments. And
a final hint: Sometimes you have to use
information you have to prove something
you don't yet have in order to make it
clear that there's a contradiction in
your set of assumptions.
-/
example
  (Person : Type)
  (Happy : Person → Prop) :
  /-
  Now prove this equivalence. Do us parens
  around the two sides of the biconditional.
  It never hurts logically to add parens to
  be sure you're not writing a proposition
  other than the one you mean to prove.
  -/
  (∀ (a), Happy a) ↔ (¬ ∃ (a), ¬ Happy a) :=
begin

  /-
  By iff introduction it will suffice to prove
  the implication in each direction. We now do
  that.
  -/
apply iff.intro,

  -- Forward direction

  -- Assume everyone's happy
  assume everyone_happy,

  -- By negation: assume someone's unhappy and ...
  assume someone_unhappy,

  -- Prove that that's a contradiction
  show false,

  -- Someone's unhappy! Call them w and get a proof they're unhappy.
  cases someone_unhappy with w w_unhappy,

  -- Apply "everyone's happy" to w to prove w's happy
  apply w_unhappy (everyone_happy w),

  -- And that's a contradiction

  -- Backward direction

  -- Assume no one is unhappy, then ...
  assume noone_unhappy,

  -- Show that everyone is happy
  show ∀ (a : Person), Happy a,

  -- Let p be an arbitrary person, and ...
  assume p,

  -- Show that p is happy

  /-
  Now you're stuck. Or are you?
  Constructively yes, classically no!
  Assume a person can only be happy or
  not happy!
  -/
  cases (classical.em (Happy p)) with p_happy p_unhappy,

  -- Case #1: P is happy. By assumption.
  assumption,

  -- Case #2: P is unhappy.

  /-
  The trick here is to see that you have a proof
  that p is unhappy and you also have a proof that
  there's no one who's unhappy. There has to be a
  contradiction there! All you need to do now is
  to use the fact that p is unhappy to prove that
  someone is unhappy. That make the contradiction
  explicit.
  -/

  let someone_unhappy := (exists.intro p p_unhappy),
  /-
  Sometimes you get a funny looking definition
  at this point, but the key observation (please
  look carefully!) is that you have a proof that
  someone is unhappy and you have a proof that no
  one is unhappy. That's a contradiction. QED.
  -/
  contradiction,
end

/- #3D

Formall state and prove the proposition
that if there doesn't exist an object of
some type T with some property, P, then
any object of that type has the property
¬P. Hint: Again we represent a "property"
of objects of a certain type as a predicate
taking objects of that type.
-/
example
  (T : Type)
  (P : T → Prop) :
  (¬∃ (x), P x) →
  ∀ (x), ¬(P x) :=
begin
-- assume there;s no x with property p
intros h,
-- let x be an arbitrary value
assume x,
-- by negation: assume P x, show contradiction
assume px,
show false,
-- from x and Px show ∃ x, P x
let ex_x_Px := exists.intro x px,
-- that's a direct contradiction
-- therefore ∀ x, ¬P x
contradiction,
end

/- #3D
Formally state and prove that if there's
an object with the property of "having
property P or property Q" then there's
an object with property P or there's an
object with property with property Q.
Remaining exercise: fill in English
comments for each step in the formal
proof then string them together with
appropriate language to render a great
natural language proof.
-/

example
  (α : Type)
  (P : α → Prop)
  (Q : α → Prop):
  (∃ (x), ((P x) ∨ (Q x))) →
  (∃ (x), P x) ∨ (∃ (x), Q x) :=
begin
-- You: Comment script; extract natural language proof.
assume h,
cases h with w pworqw,
cases pworqw with pw qw,
left,
exact (exists.intro w pw),
right,
exact (exists.intro w qw),
end