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

! This file was ported from Lean 3 source module data.multiset.range
! leanprover-community/mathlib commit 0a0ec35061ed9960bf0e7ffb0335f44447b58977
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Multiset.Basic
import Mathlib.Data.List.Range

/-! # `Multiset.range n` gives `{0, 1, ..., n-1}` as a multiset. -/

open List Nat

namespace Multiset

-- range
/-- `range n` is the multiset lifted from the list `range n`,
  that is, the set `{0, 1, ..., n-1}`. -/
def range (n : ℕ) : Multiset ℕ :=
  List.range n
#align multiset.range Multiset.range

@[simp]
theorem range_zero : range 0 = 0 :=
  rfl
#align multiset.range_zero Multiset.range_zero

@[simp]
theorem range_succ (n : ℕ) : range (succ n) = n ::ₘ range n := by
  rw [range, List.range_succ, ← coe_add, add_comm]; rfl
#align multiset.range_succ Multiset.range_succ

@[simp]
theorem card_range (n : ℕ) : card (range n) = n :=
  length_range _
#align multiset.card_range Multiset.card_range

theorem range_subset {m n : ℕ} : range m ⊆ range n ↔ m ≤ n :=
  List.range_subset
#align multiset.range_subset Multiset.range_subset

@[simp]
theorem mem_range {m n : ℕ} : m ∈ range n ↔ m < n :=
  List.mem_range
#align multiset.mem_range Multiset.mem_range

--Porting note: removing @[simp], `simp` can prove it
theorem not_mem_range_self {n : ℕ} : n ∉ range n :=
  List.not_mem_range_self
#align multiset.not_mem_range_self Multiset.not_mem_range_self

theorem self_mem_range_succ (n : ℕ) : n ∈ range (n + 1) :=
  List.self_mem_range_succ n
#align multiset.self_mem_range_succ Multiset.self_mem_range_succ

theorem range_add (a b : ℕ) : range (a + b) = range a + (range b).map (a + .) :=
  congr_arg ((↑) : List ℕ → Multiset ℕ) (List.range_add _ _)
#align multiset.range_add Multiset.range_add

theorem range_disjoint_map_add (a : ℕ) (m : Multiset ℕ) :
    (range a).Disjoint (m.map (a + .)) :=
  by
  intro x hxa hxb
  rw [range, mem_coe, List.mem_range] at hxa
  obtain ⟨c, _, rfl⟩ := mem_map.1 hxb
  exact (self_le_add_right _ _).not_lt hxa
#align multiset.range_disjoint_map_add Multiset.range_disjoint_map_add

theorem range_add_eq_union (a b : ℕ) : range (a + b) = range a ∪ (range b).map (a + .) :=
  by
  rw [range_add, add_eq_union_iff_disjoint]
  apply range_disjoint_map_add
#align multiset.range_add_eq_union Multiset.range_add_eq_union

end Multiset


/-
Copyright (c) 2021 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker
-/
import topology.continuous_on

/-!
# Left and right continuity

In this file we prove a few lemmas about left and right continuous functions:

* `continuous_within_at_Ioi_iff_Ici`: two definitions of right continuity
  (with `(a, ∞)` and with `[a, ∞)`) are equivalent;
* `continuous_within_at_Iio_iff_Iic`: two definitions of left continuity
  (with `(-∞, a)` and with `(-∞, a]`) are equivalent;
* `continuous_at_iff_continuous_left_right`, `continuous_at_iff_continuous_left'_right'` :
  a function is continuous at `a` if and only if it is left and right continuous at `a`.

## Tags

left continuous, right continuous
-/

open set filter
open_locale topological_space

section partial_order

variables {α β : Type*} [topological_space α] [partial_order α] [topological_space β]

lemma continuous_within_at_Ioi_iff_Ici {a : α} {f : α → β} :
  continuous_within_at f (Ioi a) a ↔ continuous_within_at f (Ici a) a :=
by simp only [← Ici_diff_left, continuous_within_at_diff_self]

lemma continuous_within_at_Iio_iff_Iic {a : α} {f : α → β} :
  continuous_within_at f (Iio a) a ↔ continuous_within_at f (Iic a) a :=
@continuous_within_at_Ioi_iff_Ici αᵒᵈ _ ‹topological_space α› _ _ _ f

end partial_order

variables {α β : Type*} [topological_space α] [linear_order α] [topological_space β]

lemma nhds_left_sup_nhds_right (a : α) :
  𝓝[≤] a ⊔ 𝓝[≥] a = 𝓝 a :=
by rw [← nhds_within_union, Iic_union_Ici, nhds_within_univ]

lemma nhds_left'_sup_nhds_right (a : α) :
  𝓝[<] a ⊔ 𝓝[≥] a = 𝓝 a :=
by rw [← nhds_within_union, Iio_union_Ici, nhds_within_univ]

lemma nhds_left_sup_nhds_right' (a : α) :
  𝓝[≤] a ⊔ 𝓝[>] a = 𝓝 a :=
by rw [← nhds_within_union, Iic_union_Ioi, nhds_within_univ]

lemma continuous_at_iff_continuous_left_right {a : α} {f : α → β} :
  continuous_at f a ↔ continuous_within_at f (Iic a) a ∧ continuous_within_at f (Ici a) a :=
by simp only [continuous_within_at, continuous_at, ← tendsto_sup, nhds_left_sup_nhds_right]

lemma continuous_at_iff_continuous_left'_right' {a : α} {f : α → β} :
  continuous_at f a ↔ continuous_within_at f (Iio a) a ∧ continuous_within_at f (Ioi a) a :=
by rw [continuous_within_at_Ioi_iff_Ici, continuous_within_at_Iio_iff_Iic,
  continuous_at_iff_continuous_left_right]


import data.nat.gcd

open nat
/-1. Let a and b be coprime positive integers (recall that coprime here means gcd(a, b) = 1).
I open a fast food restaurant which sells chicken nuggets in two sizes –
you can either buy a box with a nuggets in, or a box with b nuggets in.
Prove that there is some integer N with the property that for all integers m ≥ N,
it is possible to buy exactly m nuggets. -/
variables {a b N m c d : ℕ}

theorem chicken_mcnugget (hp: gcd a b = 1) (h1: a > 0) (h2 : b >0): ∃ N, ∀ m ≥ N, m = a*c + b*d := sorry


-- https://www.youtube.com/watch?v=8upRv9wjHp0
inductive A
| i : ℤ → A
| pred : A → A
| succ : A → A
| plus : A → A → A
| mult : A → A → A
.
open A

instance : has_one A := ⟨ A.i 1 ⟩
instance : has_add A := ⟨ A.plus ⟩

-- Natural semantics -- Big-step == Binary relation ↓ ⊆ A × ℤ
inductive natsem_A : A → ℤ → Prop
| r1 : ∀ i : ℤ, natsem_A (A.i i) i
| r2 : ∀ e i, natsem_A e i → natsem_A (pred e) (i - 1)
| r3 : ∀ e i, natsem_A e i → natsem_A (succ e) (i + 1)
| r4 : ∀ e₁ e₂ i j, (natsem_A e₁ i) → (natsem_A e₂ j) → natsem_A (plus e₁ e₂) (i + j)
| r5 : ∀ e₁ e₂ i j, (natsem_A e₁ i) → (natsem_A e₂ j) → natsem_A (plus e₁ e₂) (i * j)

notation a ` ⇓ ` b := natsem_A a b

example : (plus 4 (succ 2)) ⇓ 7 := sorry

--Evaluator semantics of A
def eval : A → ℤ
| (i x) := x
| (pred e) := (eval e) - 1
| (succ e) := (eval e) + 1
| (plus e₁ e₂) := (eval e₁) + (eval e₂)
| (mult e₁ e₂) := (eval e₁) * (eval e₂)

#reduce eval (plus 4 (succ 2))

-- SOS semantics of A == Binary relation ⟶ ⊆ A × A
inductive sossem_A : A → A → Prop
--axioms
| sos1 : ∀ i,   sossem_A (pred (A.i i)) (A.i (i - 1))           -- i:ℤ
| sos2 : ∀ i,   sossem_A (succ (A.i i)) (A.i (i + 1))
| sos3 : ∀ i j, sossem_A (plus (A.i i) (A.i j)) (A.i (i + j))
| sos4 : ∀ i j, sossem_A (mult (A.i i) (A.i j)) (A.i (i * j))
--contexts -- these contexts are known are the compatible closure
| sos05 : ∀ e₁ e₂,    sossem_A e₁ e₂ → sossem_A (pred e₁) (pred e₂)
| sos06 : ∀ e₁ e₂,    sossem_A e₁ e₂ → sossem_A (succ e₁) (succ e₂)
| sos07 : ∀ e₁ e₂ e₃, sossem_A e₁ e₂ → sossem_A (plus e₁ e₃) (plus e₂ e₃)
| sos08 : ∀ e₁ e₂ e₃, sossem_A e₁ e₂ → sossem_A (plus e₃ e₁) (plus e₃ e₂)
| sos09 : ∀ e₁ e₂ e₃, sossem_A e₁ e₂ → sossem_A (mult e₁ e₃) (mult e₂ e₃)
| sos10 : ∀ e₁ e₂ e₃, sossem_A e₁ e₂ → sossem_A (mult e₃ e₁) (mult e₃ e₂)

notation a ` ⟶ ` b := sossem_A a b

inductive sos_closure : A → A → Prop
| step : ∀ e₁ e₂, (e₁ ⟶ e₂) → sos_closure e₁ e₂
| reflexive : ∀ e, sos_closure e e
| transitive : ∀ e₁ e₂ e₃, sos_closure e₁ e₂ → sos_closure e₂ e₃ → sos_closure e₁ e₃

notation a ` ⟶* ` b := sos_closure a b

--I need something that extracts the value from A.i i in SOS
lemma natural_imp_sos : ∀ e i, (e ⇓ i) → (e ⟶* (A.i i)) := sorry
lemma sos_imp_natural : ∀ e i, (e ⟶* (A.i i)) → (e ⇓ i) := sorry

theorem natural_equivalent_sos : ∀ e i, (e ⇓ i) ↔ (e ⟶* (A.i i)) :=
begin
    intros, split; apply natural_imp_sos <|> apply sos_imp_natural
end

-- Reduction semantics of A
inductive axiom_redsem_A : A → A → Prop
--axioms
| red1 : ∀ i,   axiom_redsem_A (pred (A.i i)) (A.i (i - 1))           -- i:ℤ
| red2 : ∀ i,   axiom_redsem_A (succ (A.i i)) (A.i (i + 1))
| red3 : ∀ i j, axiom_redsem_A (plus (A.i i) (A.i j)) (A.i (i + j))
| red4 : ∀ i j, axiom_redsem_A (mult (A.i i) (A.i j)) (A.i (i * j))

inductive ctx
| hole
| pred : ctx → ctx
| succ : ctx → ctx
| plus {e}: ctx → e → ctx | Plus {e}: e → ctx → ctx
| mult {e}: ctx → e → ctx | Mult {e}: e → ctx → ctx


/- LoVe Exercise 12: Basic Mathematical Structures -/

import .lovelib
import .love12_basic_mathematical_structures_demo

namespace LoVe

set_option pp.beta true

/- Question 1: Type Classes -/

namespace btree

/- Recall the datatype `btree` we introduced earlier: -/

#check btree

/- The following function takes two trees and attaches copies of the second
tree to each leaf of the first tree. -/

def append {α : Type} : btree α -> btree α -> btree α
| empty          y := y
| (node a x₁ x₂) y := node a (append x₁ y) (append x₂ y)

#reduce append (node 1 empty empty) (node 2 empty empty)

/- 1.1. Prove the following two lemmas by induction on `x`. -/

lemma append_assoc {α : Type} (x y z : btree α) :
  append (append x y) z = append x (append y z) :=
begin
  induction x,
  case btree.empty {
    refl },
  case btree.node : a x₁ x₂ ih₁ ih₂ {
    simp [append, ih₁, ih₂] }
end

lemma append_empty {α : Type} (x : btree α) :
  append x empty = x :=
begin
  induction x,
  case btree.empty {
    refl },
  case btree.node : a x₁ x₂ ih₁ ih₂ {
    simp [append, ih₁, ih₂] }
end

/- 1.2. Declare btree an instance of `add_monoid` using `append` as addition
operator. -/

#print add_monoid

instance {α : Type} : add_monoid (btree α) :=
{ add       := append,
  add_assoc := append_assoc,
  zero      := empty,
  add_zero  := append_empty,
  zero_add  := begin intro x, refl end }

/- 1.3. Explain why `btree` with `append` as addition cannot be declared an
instance of `add_group`. -/

#print add_group

/- If `t₁` is a non-empty tree, `append t₁ t₂` will always yield a nonempty
tree. Therefore, there is no inverse of a non-empty tree. No matter what we
choose `neg`  to be, we will not able to prove `add_left_neg`. -/

/- 1.4. (**optional**) Prove the following lemma illustrating why this does not
constitute an `add_group`. -/

lemma example_no_inverse :
  ∃x : btree ℕ, ∀ y : btree ℕ, append y x ≠ empty :=
begin
  use node 0 empty empty,
  intros y hy,
  cases y,
  cases hy,
  cases hy
end

end btree

/- Question 2: Multisets and Finsets -/

/- Recall the following definitions from the lecture: -/

#check nodes_multiset
#check nodes_finset
#check nodes_list

/- 2.1. Prove that the multiset of nodes does not change when mirroring a tree.

Hint: Use induction on t and `ac_refl`. -/

lemma nodes_multiset_mirror (t : btree ℕ) :
  nodes_multiset (mirror t) = nodes_multiset t :=
begin
  induction t with a t₁ t₂ h₁ h₂,
  refl,
  rw nodes_multiset,
  rw mirror,
  rw [←h₁, ←h₂],
  rw nodes_multiset,
  ac_refl
end

/- 2.2. Prove that the finset of nodes does not change when mirroring a tree.

Hint: Use induction on t and `ac_refl`. -/

example (t : btree ℕ) :
  nodes_finset (mirror t) = nodes_finset t :=
begin
  induction t with a t₁ t₂ h₁ h₂,
  refl,
  rw nodes_finset,
  rw mirror,
  rw [←h₁, ←h₂],
  rw nodes_finset,
  ac_refl
end

/- 2.3. Prove that this does not hold for the list of nodes by providing an
example of a btree `t` for which `nodes_list t ≠ nodes_list (mirror t)`.

Hint: If you define a suitable counterexample, the proof below will succeed
without modifying it. -/

def counterexample : btree ℕ :=
node 0 (node 1 empty empty) (node 2 empty empty)

#reduce nodes_list counterexample
#reduce nodes_list (mirror counterexample)

example :
  ∃t : btree ℕ, nodes_list t ≠ nodes_list (mirror t) :=
begin
  use counterexample,
  exact dec_trivial
end

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 data.nat.sqrt
open prod decidable

namespace nat

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

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

@[simp] theorem mkpair_unpair (n : ℕ) : mkpair (unpair n).1 (unpair n).2 = n :=
let s := sqrt n in begin
  dsimp [unpair], change sqrt n with s,
  have sm : s * s + (n - s * s) = n := nat.add_sub_cancel' (sqrt_le _),
  by_cases h : n - s * s < s; simp [h, mkpair],
  { exact sm },
  { have hl : n - s*s - s ≤ s :=
      nat.sub_le_left_of_le_add (nat.sub_le_left_of_le_add $
      by rw ← add_assoc; apply sqrt_le_add),
    suffices : s * s + (s + (n - s * s - s)) = n, {simpa [not_lt_of_ge hl]},
    rwa [nat.add_sub_cancel' (le_of_not_gt h)] }
end

theorem mkpair_unpair' {n a b} (H : unpair n = (a, b)) : mkpair a b = n :=
by simpa [H] using mkpair_unpair n

@[simp] theorem unpair_mkpair (a b : ℕ) : unpair (mkpair a b) = (a, b) :=
begin
  by_cases a < b; simp [h, mkpair],
  { show unpair (a + b * b) = (a, b),
    have be : sqrt (a + b * b) = b,
    { rw [add_comm, sqrt_add_eq],
      exact le_trans (le_of_lt h) (le_add_left _ _) },
    simp [unpair, be, nat.add_sub_cancel, h] },
  { show unpair (a + (b + a * a)) = (a, b),
    have ae : sqrt (a + (b + a * a)) = a,
    { rw [← add_assoc, add_comm, sqrt_add_eq],
      exact add_le_add_left (le_of_not_gt h) _ },
    have : a ≤ a + (b + a * a) - a * a,
    { rw nat.add_sub_assoc (nat.le_add_left _ _), apply nat.le_add_right },
    simp [unpair, ae, not_lt_of_ge this],
    show a + (b + a * a) - a * a - a = b,
    rw [nat.add_sub_assoc (nat.le_add_left _ _),
        nat.add_sub_cancel, nat.add_sub_cancel_left] }
end

theorem unpair_lt {n : ℕ} (n1 : n ≥ 1) : (unpair n).1 < n :=
let s := sqrt n in begin
  simp [unpair], change sqrt n with s,
  by_cases h : n - s * s < s; simp [h],
  { exact lt_of_lt_of_le h (sqrt_le_self _) },
  { simp at h,
    have s0 : s > 0 := sqrt_pos.2 n1,
    exact lt_of_le_of_lt h (nat.sub_lt_self n1 (mul_pos s0 s0)) }
end

theorem unpair_le_left : ∀ (n : ℕ), (unpair n).1 ≤ n
| 0     := dec_trivial
| (n+1) := le_of_lt (unpair_lt (nat.succ_pos _))

theorem le_mkpair_left (a b : ℕ) : a ≤ mkpair a b :=
by simpa using unpair_le_left (mkpair a b)

theorem le_mkpair_right (a b : ℕ) : b ≤ mkpair a b :=
by by_cases h : a < b; simp [mkpair, h];
   [exact le_trans (le_mul_self _) (le_add_left _ _),
    exact le_trans (le_add_right _ _) (le_add_left _ _)]

theorem unpair_le_right (n : ℕ) : (unpair n).2 ≤ n :=
by simpa using le_mkpair_right n.unpair.1 n.unpair.2

theorem mkpair_lt_mkpair_left {a₁ a₂} (b) (h : a₁ < a₂) : mkpair a₁ b < mkpair a₂ b :=
begin
  by_cases h₁ : a₁ < b; simp [mkpair, h₁],
  { by_cases h₂ : a₂ < b; simp [mkpair, h₂, h],
    simp at h₂,
    exact add_lt_add_of_lt_of_le (lt_of_lt_of_le h₁ h₂)
      (le_trans (mul_self_le_mul_self h₂) (le_add_left _ _)) },
  { simp at h₁,
    simp [not_lt_of_gt (lt_of_le_of_lt h₁ h)],
    exact add_lt_add h (add_lt_add_left (mul_self_lt_mul_self h) _) }
end

theorem mkpair_lt_mkpair_right (a) {b₁ b₂} (h : b₁ < b₂) : mkpair a b₁ < mkpair a b₂ :=
begin
  by_cases h₁ : a < b₁; simp [mkpair, h₁],
  { simp [mkpair, lt_trans h₁ h, h],
    exact mul_self_lt_mul_self h },
  { by_cases h₂ : a < b₂; simp [mkpair, h₂, h],
    simp at h₁,
    rwa [add_comm, ← sqrt_lt, sqrt_add_eq],
    exact le_trans h₁ (le_add_left _ _) }
end

end nat


import tactic
import data.real.basic
import data.int.gcd
import number_theory.padics
import data.nat.prime_norm_num

/-!

## Exercises about numbers and casts

-/

/-!

## First exercises

These first examples are just to get you comfortable with
`norm_num`, `norm_cast`, and friends.

-/

example : 12345 < 67890 :=
begin
  norm_num
end

example {α : Type} [linear_ordered_field α] : 123 + 45 < 67890/3 :=
begin
  norm_num
end

example : nat.prime 17 :=
begin
  norm_num
end

-- prove either this or its negation!
example : ¬ 7/3 > 2 :=
begin
  norm_num
end

example (x : ℝ) (hx : x < 50*50) : x < 25*100 :=
begin
  norm_num at hx ⊢,
  assumption
end

example (x : ℤ) (hx : (x : ℝ) < 25*100) : x < 25*100 :=
begin
  assumption_mod_cast
end

example (x : ℤ) (hx : (x : ℝ) < 2500) : x < 25*100 :=
begin
  norm_num,
  assumption_mod_cast
end

example (p q r : ℕ) (h : r < p - q) (hpq : q ≤ p) : (r : ℝ) < p - q :=
begin
  exact_mod_cast h,
end

example (p q r : ℕ) (hr : r < p + 2 - p) : (r : ℤ) < 5 :=
begin
  have : p ≤ p + 2, by linarith,
  zify [this] at hr,
  linarith
end

/-!

## Exercise 2

This comes from the development of the p-adic numbers.
`norm_cast` is very useful here, since we need to talk about values in
ℕ, ℤ, ℚ, ℚ_p, and ℤ_p.

We've done some work to get you started. You might look for the lemmas:
-/

open padic_val_rat

#check zpow_le_of_le
#check zpow_nonneg
#check of_int_multiplicity

example {p n : ℕ} (hp : p.prime) {z : ℤ} (hd : ↑(p^n) ∣ z) : padic_norm p z ≤ ↑p ^ (-n : ℤ) :=
begin
  -- this lemma will be useful later in the proof.
  -- ignore the "inst" argument; just use `apply aux_lemma` when you need it!
  have aux_lemma : ∀ inst, (n : ℤ) ≤ (multiplicity ↑p z).get inst,
  { intro,
    norm_cast,
    rw [← part_enat.coe_le_coe, part_enat.coe_get],
    apply multiplicity.le_multiplicity_of_pow_dvd,
    assumption_mod_cast },

  unfold padic_norm, split_ifs with hz hz,
  { apply zpow_nonneg,
    exact_mod_cast le_of_lt hp.pos },
  { apply zpow_le_of_le,
    exact_mod_cast le_of_lt hp.one_lt,
    apply neg_le_neg,
    rw of_int_multiplicity hp.ne_one _,
    { apply aux_lemma },
    { assumption_mod_cast } }
end

/-!

## Exercise 3

This seems like a very natural way to write the theorem
"If `a` and `b` are coprime, then there are coefficients `u` and `v` such that `u*a + v*b = 1`."

But I've made a mistake! What did I do wrong? Correct the statement of the theorem and prove it.

I've started you off with a lemma that will be useful.
You might find the `specialize` tactic to be handy as well:
if you have `h : ∀ (x y : T), R x y` and `a, b : T` in the context, then
`specialize h a b` will change the type of `h` to `R a b`.

If you want to peek at the definition of coprime, you can use
`unfold nat.coprime at h`.

-/

-- notice that I've changed the existential to be over ℤ!
example (p q : ℕ) (h : nat.coprime p q) : ∃ u v : ℤ, u*p+v*q = 1 :=
begin
  have := nat.gcd_eq_gcd_ab,
  specialize this p q,
  unfold nat.coprime at h,
  rw h at this,
  norm_cast at this,
  use [p.gcd_a q, p.gcd_b q],
  rw this,
  ring
end

/-!

## Exercise 4

We did an example together that was similar to this.

This one takes a bit more arithmetic work.
To save you some time, here are some lemmas that may be useful!
(You may not need all of them, depending on how you approach it.)

Remember you can also use `library_search` to try to find useful lemmas.

A hint: you might find it helpful to do this once you've introduced `n`.
```
have n_pos: 0 < n,
{ ... }
```

-/

#check sub_le_iff_le_add
#check add_le_add_iff_left
#check div_le_iff
#check mul_one_div_cancel
#check mul_le_mul_left

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

def seq_limit (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

example : seq_limit (λ n : ℕ, (n+1)/n) 1 :=
begin
  -- we start like before
  intros ε ε_pos,
  dsimp,

  -- the witness to the existential must be a nat: use `nat.ceil` instead of `ceil`!
  use nat.ceil (1/ε),
  intros n hn,

  -- this proof is used a few times below, so we separate it as an assumption
  have n_pos : 0 < n,
    -- I've written the proofs of the calc block separately, but you could inline them
  { calc 0 < nat.ceil (1/ε) : _
       ... ≤ n : _,
    { rw nat.lt_ceil, simp, assumption },
    { assumption } },

  -- this chain of rewrites generates some side conditions that we get rid of below
  rw [abs_of_nonneg, sub_le_iff_le_add, div_le_iff, add_mul, one_mul,
      add_comm _ (n : ℝ), add_le_add_iff_left],

  -- this calc proof finishes the "main goal"
  { calc 1 = ε * (1/ε) : _
       ... ≤ ε * nat.ceil (1/ε) : _
       ... ≤ ε * n : _,
    { symmetry, apply mul_one_div_cancel, linarith },
    { rw mul_le_mul_left ε_pos, apply nat.le_ceil },
    { rw mul_le_mul_left ε_pos, exact_mod_cast hn } },

  -- we try to discharge the side conditions with as little work as possible
  { assumption_mod_cast },
  { field_simp,
    rw one_le_div; norm_cast; linarith }
end


import data.real.basic
import order.filter.at_top_bot
import order.liminf_limsup
import topology.metric_space.basic
import topology.algebra.order.monotone_convergence
import topology.instances.nnreal

open_locale filter topological_space

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

/- lemma le_of_is_lub {α : Type*} [preorder α] (S : set α) (B : α) (hB : is_lub S B) :
  ∀ s ∈ S, s ≤ B :=
begin
  intros s hs,
  simp [is_lub, is_least, mem_upper_bounds] at hB,
  exact hB.1 s hs,
end
 -/
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),
  use B,
  exact tendsto_at_top_is_lub h_mon hB,
end

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),
  use B,
  exact tendsto_at_top_is_glb h_ant hB,
end

lemma nnreal.tendsto_of_is_bounded_antitone {f : ℕ → nnreal} (h_bdd : bdd_below (set.range f))
  (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


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

end Mathlib


/- LoVe Homework 5: Inductive Predicates -/

import .lovelib

namespace LoVe

/- Question 1: Lambda-Terms -/

/- Recall the following type of λ-terms from question 3 of exercise 4. -/

inductive lam : Type
| var : string → lam
| abs : string → lam → lam
| app : lam → lam → lam

export lam (var abs app)

/- 1.1. Define an inductive predicate `is_abs` that returns true if and only if
its argument has an `abs` constructor at the top level. -/

-- enter your definition here

/- 1.2. Define an inductive predicate `is_βnf` that determines whether a lambda
term is in β-normal form (https://en.wikipedia.org/wiki/Beta_normal_form).

Hint: Use `is_abs` somewhere. -/

-- enter your definition here

/- Question 2: Transitive Closure -/

/- In mathematics, the transitive closure `R+` of a binary relation `R` over a
set `A` can be defined as the smallest solution satisfying the following rules:

    (base) for all a, b ∈ A, if a R b, then a R+ b;
    (step) for all a, b, c ∈ A, if a R b and b R+ c, then a R+ c.

In Lean, we can define this concept as follows, by identifying the set `A` with
the type `α`: -/

inductive tc₁ {α : Type} (r : α → α → Prop) : α → α → Prop
| base : ∀a b, r a b → tc₁ a b
| step : ∀a b c, r a b → tc₁ b c → tc₁ a c

/- 2.1. Rule `(step)` makes it convenient to extend transitive chains by adding
links to the left. Another way to define the transitive closure `R+` would use
the following rule instead of `(step)`, with a preference for the right:

    (pets) for all a, b, c ∈ A, if a R+ b and b R c, then a R+ c.

Define a predicate `tc₂` that embodies this alternative definition. -/

-- enter your definition here

/- 2.2. Yet another definition of the transitive closure `R+` would use the
following symmetric rule instead of `(step)` or `(pets)`:

    (trans) for all a, b, c ∈ A, if a R+ b and b R+ c, then a R+ c.

Define a predicate `tc₃` that embodies this alternative definition. -/

-- enter your definition here

/- 2.3. Prove that `(step)` also holds as a lemma about `tc₃`. -/

lemma tc₃_step {α : Type} (r : α → α → Prop) (a b c : α) (rab : r a b)
    (tbc : tc₃ r b c) :
  tc₃ r a c :=
sorry

/- 2.4 (**optional**). Prove by rule induction on the `tc₁ r a b` premise below
that `(pets)` also holds as a lemma about `tc₁` as defined above.  -/

lemma tc₁_pets {α : Type} (r : α → α → Prop) (c : α) :
  ∀a b, tc₁ r a b → r b c → tc₁ r a c :=
sorry

/- 2.5 (**optional**). Prove by rule induction that `(trans)` also holds as a
lemma about `tc₁`. -/

lemma tc₁_trans {α : Type} (r : α → α → Prop) (c : α) :
  ∀a b : α, tc₁ r a b → tc₁ r b c → tc₁ r a c :=
sorry

end LoVe


import .love10_denotational_semantics_demo

/- # LoVe Exercise 10: Denotational Semantics -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/- ## Question 1: Monotonicity

1.1. Prove the following lemma from the lecture. -/

lemma monotone_comp {α β : Type} [partial_order α] (f g : α → set (β × β))
    (hf : monotone f) (hg : monotone g) :
  monotone (λa, f a ◯ g a) :=
sorry

/- 1.2. Prove its cousin. -/

lemma monotone_restrict {α β : Type} [partial_order α] (f : α → set (β × β))
    (p : β → Prop) (hf : monotone f) :
  monotone (λa, f a ⇃ p) :=
sorry

/- ## Question 2: Regular Expressions

__Regular expressions__, or __regexes__, are a highly popular tool for software
development, to analyze textual inputs. Regexes are generated by the following
grammar:

    R ::= ∅
        | ε
        | a
        | R ⬝ R
        | R + R
        | R*

Informally, the semantics of regular expressions is as follows:

* `∅` accepts nothing;
* `ε` accepts the empty string;
* `a` accepts the atom `a`;
* `R ⬝ R` accepts the concatenation of two regexes;
* `R + R` accepts either of two regexes;
* `R*` accepts arbitrary many repetitions of a regex.

Notice the rough correspondence with a WHILE language:

    `∅` ~ diverging statement (e.g., `while true do skip`)
    `ε` ~ `skip`
    `a` ~ `:=`
    `⬝` ~ `;`
    `+` ~ `if then else`
    `*` ~ `while` loop -/

inductive regex (α : Type) : Type
| nothing {} : regex
| empty {}   : regex
| atom       : α → regex
| concat     : regex → regex → regex
| alt        : regex → regex → regex
| star       : regex → regex

/- In this exercise, we explore an alternative semantics of regular
expressions. Namely, we can imagine that the atoms represent binary relations,
instead of letters or symbols. Concatenation corresponds to composition of
relations, and alternation is union. Mathematically, regexes and binary
relations are both instances of Kleene algebras.

2.1. Complete the following translation of regular expressions to relations.

Hint: Exploit the correspondence with the WHILE language. -/

def rel_of_regex {α : Type} : regex (set (α × α)) → set (α × α)
| regex.nothing        := ∅
| regex.empty          := Id
| (regex.atom s)       := s
-- enter the missing cases here

/- 2.2. Prove the following recursive equation about your definition. -/

lemma rel_of_regex_star {α : Type} (r : regex (set (α × α))) :
  rel_of_regex (regex.star r) =
  rel_of_regex (regex.alt (regex.concat r (regex.star r)) regex.empty) :=
sorry

end LoVe


import data.nat.basic
open nat

-- 1
example : ∀ m n k : nat, m * (n + k) = m * n + m * k :=
assume m n k,
nat.rec_on k
    (show m*(n + 0) = m*n + m*0, from calc
    m*(n + 0) = m*n : by rw add_zero
          ... = m*n + 0 : by rw add_zero
          ... = m*n + m*0 : by rw mul_zero
    )
    (assume k,
    assume ih: m*(n + k) = m*n + m*k,
    show m*(n + succ k) = m*n + m*(succ k), from calc
    m*(n + succ k) = m*(succ(n + k)) : by rw add_succ
               ... = m*(n + k) + m : by rw mul_succ
               ... = m*n + m*k + m : by rw ih
               ... = m*n + (m*k + m) : by rw add_assoc
               ... = m*n + m*(succ k) : by rw mul_succ
    )

-- 2
example : ∀ n : nat, 0 * n = 0 :=
assume n,
nat.rec_on n
    (show 0*0 = 0, by rw mul_zero)
    (assume n,
    assume ih: 0*n = 0,
    show 0*(succ n) = 0, from calc
    0*(succ n) = 0*n + 0 : by rw mul_succ
         ...   = 0 + 0 : by rw ih
         ...   = 0 : by rw add_zero
    )

-- 3
example : ∀ n : nat, 1 * n = n :=
assume n,
nat.rec_on n
    (show 1*0 = 0, from mul_zero 1)
      (assume n,
    assume ih: 1*n = n,
    show 1*(succ n) = succ n, from calc
    1*(succ n) = 1*n + 1 : by rw mul_succ
           ... = n + 1 : by rw ih
           ... = succ n : by simp
      )

-- 4
example : ∀ m n k : nat, (m * n) * k = m * (n * k) :=
assume m n k,
nat.rec_on k
    (show (m*n)*0 = m*(n*0), by rw
    [mul_zero,mul_zero,mul_zero])
    (assume k,
    assume ih : (m * n) * k = m * (n * k),
    show (m * n) * (succ k) = m * (n * (succ k)), from calc
    (m * n) * (succ k) = (m*n)*k + (m*n) : by rw mul_succ
                   ... = m * (n * k) + m * n : by rw ih
                   ... = m * (n*k + n) : by rw left_distrib
                   ... = m * (n*(succ k)) : by rw mul_succ
                   ... = m * (n * succ k) : by simp
    )

-- 5
example : ∀ m n : nat, m * n = n * m :=
assume m n,
nat.rec_on n
    (show m*0 = 0*m, by rw [mul_zero,zero_mul])
    (assume n,
    assume ih : m*n = n*m,
    show m*(succ n) = (succ n)*m, from calc
    m*(succ n) = m*n + m : by rw mul_succ
           ... = n*m + m : by rw ih
           ... = n*m + 1*m : by rw one_mul
           ... = (n + 1)*m : by rw right_distrib
           ... = (succ n)*m : by simp
    )


/-
HOMEWORK #9
REESE ALLEN (rga2uz)
-/

/-
Prove the following. Note that you can read each of
the propositions to be proved as either a logical
statement or as simply a function definition. Use
what you already know about the latter to arrive
at a proof, and then understand the proof as one
that shows that the logical statement is true.
-/

theorem t1 {P Q : Prop} (p2q : P → Q) (p : P) : Q :=
  p2q p

theorem t2 {P Q R : Prop} (p2q : P → Q) (q2r : Q → R): P → R :=
  assume p : P,
  q2r (p2q p)

/-
Use "example" to state and prove the preceding two
theorems but using "cases" style notation rather
than C-style. Remember, "example" is a way to state
a proposition/type and give an example of a value.
Here's an example of the use of "example". Give
your answers following this example.
-/

example : ∀ (P Q : Prop), (P → Q) → P → Q :=
λ P Q p2q p, p2q p

example : ∀ ( P Q R: Prop), (P → Q) → (Q → R) → P → R :=
λ P Q R p2q q2r p, q2r (p2q p)

-- could also structure examples like this..
example : ∀ ( P Q : Prop), (P → Q) → P → Q
| P Q p2q p := p2q p

example : ∀ (P Q R : Prop), (P → Q) → (Q → R) → P → R
| P Q R p2q q2r p := q2r (p2q p)

/-
Now give English-language versions of your two proofs.

theorem t1:
If we want to prove for any two arbitrary propositions P and Q that if P implies
Q then, given P, we can derive a proof of Q (or P implies Q), we assume that we
are given a proof of P to Q and a proof of P. We apply the proof of P to Q to the
proof of P to get a proof of Q.

"If we assume that Jim's leg being broken means that he walks with a limp,
then if we know it is true that Jim's leg is broken, we can deduce that he
walks with a limp."

theorem t2:
If we want to prove for any propositions P, Q, and R that if P implies Q and
Q implies R then P implies R, we assume that we are given three arbitray propositions
and that we have a proof of P implies Q and a proof of Q implies R. Now to show that P
implies R, we assume P is true and that we have a proof of it. We apply the proof of
P to Q to the proof of P to get a proof of Q. Now that we have a proof of Q, we appply
the proof of Q to R to the proof of Q to finally arrive at a proof of R.

"If we assume that Jim's leg being broken means he walks with a limp and that Jim walking
with a limp means that he is in pain, then if we know Jim's leg is broken, we can deduce
that Jim is in pain"
-/

/-
Prove the following using case analysis on one
of the arguments (i.e., use match...with...end
at a key point in your proof). Use "cases" style
notation.
-/
theorem t3 : ∀ (P : Prop), false → P
| P f := match f with
        | f := false.elim f
        end

-- could also do this..
theorem t3' : ∀ (P : Prop), false → P
| P f := match f with end

-- or this..
theorem t3'' : ∀ (P : Prop), false → P
| P f := false.elim f

/-
Prove false → true by applying t3 to a proposition.
You have to figure out which one.
-/
theorem t4 : false → true := t3 true

/-
Define t5 to be the same as t3 but with P taken as
an implicit argument.
-/
theorem t5 : ∀ {P : Prop}, false → P
| P f := false.elim f

/-
Define t6 to be a proof of false → true by
applying t5 to the right argument(s).
-/
theorem t6 : false → true := λ (p : false), t5 p

/-
That is almost magic. In English, t3 proves
that false implies *any* proposition, so just
*apply* t3 to *true* in particular, but use t5
instead of t3.

What you see here is really important: Once
we've proved a general theorem (a ∀ proposition)
we can *apply the proof* to any *particular* case
to yield a proof for that specific case. This is
the elimination rule for ∀. It is also known as
universal instantiation (UI).
-/

/-
Next we see the idea that test cases are really
just equality propositions to be proved. Here,
for example, is a definition of the factorial
function.
-/

def fac : ℕ → ℕ
| 0 := 1
| (n' + 1) := (n' + 1) * fac n'

/-
Use "example" to write test cases for the
first ten natural number arguments to this
function.
-/

example : fac 0 = 1 := eq.refl 1
example : fac 1 = 1 := eq.refl _  -- Inferred
example : fac 2 = 2 := rfl        -- Shorthand
#check @rfl           -- infers type and value

example : fac 3 = 6 := rfl
example : fac 4 = 24 := rfl
example : fac 5 = 120 := rfl

/-
Insight: A test case is an equality proposition.
It is proved by "running" the program under test
to reduce the application of the function to input
arguments to produce an output that is then asserted
to be equal to an expected output.

In many cases, all we have to do is to simplify
the expressions on each side of the eq to see if
they reduce to exactly the same value. If so, we
can *apply* eq.refl (a universal generalization!)
to that value. Using rfl we can avoid even having
to type that value in cases where Lean can infer
it.
-/

/-
The next problem requires that you give a proof of
a bi-implication, a proposition whose connective is
↔. To prove a bi-implication requires that one prove
an implication in each direction.

Here you are asked to prove P ∧ Q ↔ Q ∧ P. What this
formula asserts is that ∧ is commutative. To construct
a proof of this proposition you will have to apply
iff.intro to two smaller proofs, one of P → Q and
and of Q → P.

Start by "assuming" that P and Q are arbitary but
specific propositions (∀ introduction), then apply
iff.intro to two "stubbed out" arguments (underscores).
We suggest that you put the underscores in parentheses
on different lines. Then recursively fill in each of
these stubs with the required types of proofs. Study
the context that Lean shows you in its Messages panel
to see what you have to work with at each point in
your proof constructions.
-/
theorem t7 : ∀ {P Q : Prop}, P ∧ Q ↔ Q ∧ P :=
λ (P Q : Prop),
iff.intro
(λ (pq : and P Q), and.intro (pq.right) (pq.left))
(λ (qp : and Q P), and.intro (qp.right) (qp.left))

/-
In English, when asked to prove P ↔ Q, one says, "it
will suffice to show P → Q and then to show Q → P." One
then goes on to give a proof of each implication. It
then follows from iff.intro that a proof of P ↔ Q can
be constructed, proving the bi-implication.
-/

/-
The trick here is to do case analysis on porq
(use match ... with ... end) and to show that
a proof of R can be constructed *in either case*.
-/
theorem t8 {P Q R : Prop} (p2r : P → R) (q2r : Q → R) (porq : P ∨ Q) : R :=
match porq with
|or.inl P := p2r P
|or.inr Q := q2r Q
end

theorem t8' {P Q R : Prop} (p2r : P → R) (q2r : Q → R) (porq : P ∨ Q) : R :=
or.elim porq p2r q2r

/-
We suggest that you use  "let ... in" to give
names to intermediate results that you then combine
in a final expression to finish the proof.
-/
-- if P implies Q, then it can't be that P is true and not Q is true
-- ¬ (P ∧ ¬ Q) means (P ∧ ¬ Q) → false

theorem t9 : ∀ (P Q: Prop), (P → Q) → ¬ (P ∧ ¬ Q) :=
λ P Q,
λ (p2q : P → Q),
λ (pnq),
let p := pnq.left in
let nq := pnq.right in
let q := p2q p in
nq q

theorem neg_elim' : ∀ (P : Prop), ¬ ¬ P → P :=
λ P,
λ nnp,
_           -- STUCK!!

theorem neg_elim : ∀ (P : Prop), (P ∨ ¬ P) → (¬ ¬ P → P):=
λ P,
    λ h : (P ∨ ¬ P),
        λ (nnp : ¬ ¬ P),
            match h with
            | or.inl p := p
            | or.inr np := false.elim (nnp np)
            end

theorem t10 : ∀ (P : Prop), P ∨ ¬ P :=
classical.em

theorem t11 : ∀ (P Q : Prop), ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
λ P Q,
    iff.intro
        (λ not_porq,
            match (classical.em P) with
            | or.inl p := false.elim (not_porq (or.inl p))
            | or.inr np := match (classical.em Q) with
                           | or.inl q := false.elim (not_porq (or.inr q))
                           | or.inr nq := and.intro np nq
                           end
            end
        )
        (
          λ npandnq,
            match npandnq with
            | and.intro np nq := λ (porq : P ∨ Q),
                          match porq with
                          | or.inl p := np p
                          | or.inr q := nq q
                          end
          end
        )

theorem t12 : ∀ (P Q : Prop), ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
λ P Q,
iff.intro
(
λ not_pandq,
    match (classical.em P) with
    | or.inl p := match (classical.em Q) with
                  | or.inl q := false.elim (not_pandq (and.intro p q) )
                  | or.inr nq := or.inr nq
                  end
    | or.inr np := or.inl np
    end
)
(
λ npandnq,
λ porq,
  match npandnq with
  |or.inl np := let p := porq.left in
      np p
  |or.inr nq := let q := porq.right in
      nq q
  end
)

/-
For the following exercises, we assume that there is
a type called Person and a binary relation, Likes, on
pairs of people.
-/
axiom Person : Type
axiom Likes : Person → Person → Prop

theorem t13 :
  (∃ (p : Person), ∀ (q : Person), Likes q p) →
  (∀ (p : Person), ∃ (q : Person), Likes p q) :=
λ h,
    match h with
    | exists.intro p pf :=
        λ q,
            (exists.intro p (pf q))
    end


import data.matrix.basic
import linear_algebra.matrix
import linear_algebra.eigenspace

variables {γ : Type*} [fintype γ] [decidable_eq γ]
variables {μ : Type*} [fintype μ] [decidable_eq μ]
variables {R : Type*}

open_locale big_operators
open_locale matrix

namespace matrix

-- Properties of symmetric matrices.

def symmetric (M : matrix γ γ R) : Prop :=
∀ i j, M i j = M j i

lemma symmetric_diagonal [ring R] (D : γ → R)
: symmetric (diagonal D) :=
begin
  intros i j, unfold diagonal, by_cases (i = j),
  { split_ifs, { rw h, }, { exfalso, exact h_1 h.symm, }, },
  { split_ifs, { exfalso, exact h h_1.symm, }, { refl, }, },
end

lemma symmetric_sum
  [ring R]
  {A B : matrix γ γ R}
  (hA : symmetric A) (hB : symmetric B)
: symmetric (A + B) :=
begin
  intros i j, simp, rw [hA i j, hB i j],
end

lemma symmetric_LDLT
  [comm_ring R] (L : matrix γ γ R) (h : symmetric L) (D : γ → R)
: symmetric (Lᵀ ⬝ diagonal D ⬝ L) :=
begin
  intros i j, rw ←matrix.transpose_apply (Lᵀ ⬝ diagonal D ⬝ L) j i,
  rw [matrix.transpose_mul _ L, matrix.transpose_mul Lᵀ _],
  rw [matrix.diagonal_transpose, matrix.transpose_transpose],
  rw [matrix.mul_assoc],
end

-- Properties of the dot product.
-- TODO: to_mathlib

variable [linear_ordered_comm_ring R]

lemma dot_product_of_nonneg {v w : γ → R} (hv : 0 ≤ v) (hw : 0 ≤ w) : 0 ≤ dot_product v w :=
begin
  simp only [dot_product], apply finset.sum_nonneg, intros x hx,
  exact mul_nonneg (hv x) (hw x),
end

lemma vec_mul_self_nonneg (v : γ → R) : 0 ≤ v * v :=
λ i, mul_self_nonneg (v i)

lemma dot_product_diagonal_mul_vec_eq (d v w : γ → R) :
dot_product ((diagonal d).mul_vec v) w = dot_product d (v * w) :=
begin
  simp only [dot_product], congr, ext i,
  rw [mul_vec_diagonal, pi.mul_apply, mul_assoc],
end

lemma dot_product_self_nonneg (v : γ → R) : 0 ≤ dot_product v v :=
begin
  simp only [dot_product], apply finset.sum_nonneg, intros x hx,
  exact mul_self_nonneg (v x),
end

lemma dot_product_self_eq_zero_iff (v : γ → R) : dot_product v v = 0 ↔ v = 0 :=
begin
  have hsumz := finset.sum_eq_zero_iff_of_nonneg (λ x hx, mul_self_nonneg (v x)), split,
  { intros hdp, ext x,
    have hvx := (hsumz.1 hdp) x (finset.mem_univ x),
    exact (mul_self_eq_zero.1 hvx), },
  { intros hvz, erw hsumz, intros x hx, dsimp,
    erw [congr_fun hvz x], simp, }
end

lemma dot_product_self_pos_of_nonzero (v : γ → R) (hvnz : v ≠ 0) : dot_product v v > 0 :=
begin
  have h1 := dot_product_self_nonneg v,
  have h2 := ne_comm.1 ((not_iff_not_of_iff (dot_product_self_eq_zero_iff v)).2 hvnz),
  exact (lt_of_le_of_ne h1 h2),
end

lemma dot_product_transpose (v : γ → R) (K L : matrix μ γ R)
: dot_product v ((Kᵀ ⬝ L).mul_vec v) = dot_product (K.mul_vec v) (L.mul_vec v) :=
begin
  rw ←mul_vec_mul_vec, dsimp [mul_vec, dot_product],
  have : ∑ i, (v i) * (∑ j, K j i * ∑ k, L j k * v k) =
         ∑ i, (∑ j, (v i) * K j i * ∑ k, L j k * v k),
  { congr, ext i, rw [finset.mul_sum], congr, ext j, ring, },
  rw this,
  have : ∑ i j, v i * K j i * ∑ k, L j k * v k =
         ∑ i j k, v i * K j i * L j k * v k,
  { congr, ext i, congr, ext j, rw [finset.mul_sum], congr, ext k, ring, },
  rw this,
  have : ∑ i, (∑ j, K i j * v j) * ∑ k, L i k * v k =
         ∑ i, (∑ j, K i j * v j * ∑ k, L i k * v k),
  { congr, ext i, rw [finset.sum_mul], },
  rw this,
  have : ∑ i j, K i j * v j * ∑ k, L i k * v k =
         ∑ i j k, K i j * v j * L i k * v k,
  { congr, ext i, congr, ext j, rw [finset.mul_sum], congr, ext k, ring, },
  rw this,
  rw [finset.sum_comm],
  congr, ext i, congr, ext j, congr, ext k, ring,
end

end matrix


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin, Kenny Lau, Robert Y. Lewis
-/
import algebra.group.basic

/-!
# Eckmann-Hilton argument

The Eckmann-Hilton argument says that if a type carries two monoid structures that distribute
over one another, then they are equal, and in addition commutative.
The main application lies in proving that higher homotopy groups (`πₙ` for `n ≥ 2`) are commutative.

## Main declarations

* `eckmann_hilton.comm_monoid`: If a type carries a unital magma structure that distributes
  over a unital binary operation, then the magma is a commutative monoid.
* `eckmann_hilton.comm_group`: If a type carries a group structure that distributes
  over a unital binary operation, then the group is commutative.

-/

universe u

namespace eckmann_hilton
variables {X : Type u}

local notation a `<`m`>` b := m a b

/-- `is_unital m e` expresses that `e : X` is a left and right unit
for the binary operation `m : X → X → X`. -/
structure is_unital (m : X → X → X) (e : X) extends is_left_id _ m e, is_right_id _ m e : Prop.

@[to_additive eckmann_hilton.add_zero_class.is_unital]
lemma mul_one_class.is_unital [G : mul_one_class X] : is_unital (*) (1 : X) :=
is_unital.mk (by apply_instance) (by apply_instance)

variables {m₁ m₂ : X → X → X} {e₁ e₂ : X}
variables (h₁ : is_unital m₁ e₁) (h₂ : is_unital m₂ e₂)
variables (distrib : ∀ a b c d, ((a <m₂> b) <m₁> (c <m₂> d)) = ((a <m₁> c) <m₂> (b <m₁> d)))
include h₁ h₂ distrib

/-- If a type carries two unital binary operations that distribute over each other,
then they have the same unit elements.

In fact, the two operations are the same, and give a commutative monoid structure,
see `eckmann_hilton.comm_monoid`. -/
lemma one : e₁ = e₂ :=
by simpa only [h₁.left_id, h₁.right_id, h₂.left_id, h₂.right_id] using distrib e₂ e₁ e₁ e₂

/-- If a type carries two unital binary operations that distribute over each other,
then these operations are equal.

In fact, they give a commutative monoid structure, see `eckmann_hilton.comm_monoid`. -/
lemma mul : m₁ = m₂ :=
begin
  funext a b,
  calc m₁ a b = m₁ (m₂ a e₁) (m₂ e₁ b) :
    by simp only [one h₁ h₂ distrib, h₁.left_id, h₁.right_id, h₂.left_id, h₂.right_id]
          ... = m₂ a b :
    by simp only [distrib, h₁.left_id, h₁.right_id, h₂.left_id, h₂.right_id]
end

/-- If a type carries two unital binary operations that distribute over each other,
then these operations are commutative.

In fact, they give a commutative monoid structure, see `eckmann_hilton.comm_monoid`. -/
lemma mul_comm : is_commutative _ m₂ :=
⟨λ a b, by simpa [mul h₁ h₂ distrib, h₂.left_id, h₂.right_id] using distrib e₂ a b e₂⟩

/-- If a type carries two unital binary operations that distribute over each other,
then these operations are associative.

In fact, they give a commutative monoid structure, see `eckmann_hilton.comm_monoid`. -/
lemma mul_assoc : is_associative _ m₂ :=
⟨λ a b c, by simpa [mul h₁ h₂ distrib, h₂.left_id, h₂.right_id] using distrib a b e₂ c⟩

omit h₁ h₂ distrib

/-- If a type carries a unital magma structure that distributes over a unital binary
operations, then the magma structure is a commutative monoid. -/
@[to_additive "If a type carries a unital additive magma structure that distributes over a
unital binary operations, then the additive magma structure is a commutative additive monoid."]
def comm_monoid [h : mul_one_class X]
  (distrib : ∀ a b c d, ((a * b) <m₁> (c * d)) = ((a <m₁> c) * (b <m₁> d))) : comm_monoid X :=
{ mul := (*),
  one := 1,
  mul_comm := (mul_comm h₁ mul_one_class.is_unital distrib).comm,
  mul_assoc := (mul_assoc h₁ mul_one_class.is_unital distrib).assoc,
  ..h }

/-- If a type carries a group structure that distributes over a unital binary operation,
then the group is commutative. -/
@[to_additive "If a type carries an additive group structure that distributes
over a unital binary operation, then the additive group is commutative."]
def comm_group [G : group X]
  (distrib : ∀ a b c d, ((a * b) <m₁> (c * d)) = ((a <m₁> c) * (b <m₁> d))) : comm_group X :=
{ ..(eckmann_hilton.comm_monoid h₁ distrib),
  ..G }

end eckmann_hilton


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

/-

# Class groups

In Lean we don't talk about the class group of a number field, we talk about
the class group of its integer ring.

-/

variables (R : Type) [comm_ring R] [is_domain R]

example : Type := class_group R

noncomputable example : comm_group (class_group R) := infer_instance

/-

So what is this group?

A *fractional ideal* of `R` is an `R`-submodule `J` of the field of fractions of `R`
such that there exists a nonzero element `a` of `R` such that `a * J ⊆ R`.

-/

open_locale non_zero_divisors -- to get notation R⁰ for the submonoid of nonzero divisors of R
-- (of course in this case it's just R \ {0}).

-- the fractional ideals of R
example : Type := fractional_ideal (R⁰) (fraction_ring R)

-- Note that (0) is a fractional ideal with this definition. So fractional ideals aren't
-- a group under multiplication, only a monoid.

example : comm_monoid (fractional_ideal (R⁰) (fraction_ring R)) := infer_instance

-- However the invertible fractional ideals (for a number field this is the same as the
-- nonzero fractional ideals) are a group:

example : comm_group (fractional_ideal (R⁰) (fraction_ring R))ˣ := infer_instance

-- There's a group homomorphism from the units of `fraction_ring R` to the invertible
-- fractional ideals

noncomputable example :
  (fraction_ring R)ˣ →* (fractional_ideal (R⁰) (fraction_ring R))ˣ :=
to_principal_ideal R _

-- And the class group of `R` is defined to be the quotient of the invertible fractional
-- ideals by the image of this map.

example : class_group R =
  ((fractional_ideal R⁰ (fraction_ring R))ˣ ⧸ (to_principal_ideal R (fraction_ring R)).range) := rfl

-- For a general integral domain, the class group may be infinite. But the class group
-- of the integers of a number field is known by Lean to be finite.

open number_field

noncomputable example (K : Type) [field K] [number_field K] :
  fintype (class_group (ring_of_integers K)) :=
infer_instance

-- Proved in 2021 in Lean. See https://arxiv.org/abs/2102.02600 to see how it was done.
-- My PhD student Ashvni Narayanan was one of the people involved in the proof.


/- NATURAL NUMBERS

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

open Nat'

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

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

-- Next, check your definition of `mul` by evaluating a few examples. This can be done with the
-- command `#eval`.

#eval mul (Nat'.succ (Nat'.succ Nat'.zero)) (Nat'.succ (Nat'.succ Nat'.zero))

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

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

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

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

#check @Eq.subst

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

/-
LISTS

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

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

-- Let's define something we can't in most other functional languages:
-- a function returning the first element of a list given a proof that is is non-empty! (`xs ≠ []` is shorthand for `¬ (xs = [])`)
def hd (xs : List α) : xs ≠ [] → α := fun _ =>
  match xs with
  | cons x _ => x
-- Did you notice that `hd` is a dependent function? The *type/proposition* of the second parameter depends on the *value* of the first.
-- We can write `hd`s type as `∀ (xs : List α), xs ≠ [] → α` or, equivalently, `(xs : List α) → xs ≠ [] → α`.

/-
TREES

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

inductive Tree (α : Type) : Type where
  | leaf : α -> Tree α
  | branch : Tree α -> α -> Tree α -> Tree α

open Tree

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

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

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

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

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

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

-- Now we prove two facts about this function:
-- First, we prove that it is involutive (mirroring a tree twice returns the original tree).
-- Then, we prove that the mirror image of two trees is equal, if and only if the trees themselves are.

-- Useful lemmas, if you don't want to use ▸ as much (but it is also just as doable with some `have`s and ▸):
#check @Eq.trans
#check @Eq.symm
#check @congrArg

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

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

/- STRUCTURES -/

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

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

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


/-
# Propositions as Types.
In this file, we shall be introduced to the paradigm of
*Propositions as Types* and how they are handled in Lean.
We shall also use this oppurtunity to get familiar with
Lean's syntax.

Acknowledgements : Heavily borrowed from Jalex Stark and
Apurva Nakade's tutorial.

Import commands go right at the top of a Lean file.
-/

import data.nat.pow
import data.nat.prime

/-
  Before that, let us delve into type theory.
 *All* objects in Lean have a *type*.
  Things which have a type are called *terms*.
  This simple stratification is more powerful than you may first think.
  You can query the type of a term using `#check`.

  Inspect the following commands:
-/

#check 1
#check ℕ -- type using \nat
#check Type
#check Type 1 -- the number indicates a *universe level*
#check and
#check tt
#check ff

/-
  Here is an important type. The type of all propositions.
  Roughly speaking, a proposition is something which can be
  subjected to a proof.
-/
#check Prop

/-
In type theory, "a has type A" is written as "a:A".
You can read this in two ways:

  (1) a belongs to the set A
  (2) a is a proof of A

Here are some silly propositions. All of these are terms of type Prop.
-/

#check 1 + 1 = 2 -- equality
#check ∀ (x : ℕ), x = 10
#check ∃ (k : ℕ), 10 = 2*k
#check ∀ (x : bool), x = tt ∨ x = ff
#check ∀ (x y : ℕ), x = y → y = x
#check ∀ (x y z n : ℕ), n > 2 → (x*y*z ≠ 0) → (x^n + y^n = z^n)
#check ∃ k : ℕ, 10 = 2*k -- existential quantification
#check ∀ (x : bool), x = tt ∨ x = ff -- disjunction
#check ∀ (x : bool), x = tt ∧ x = ff -- conjunction
#check ∀ (x y : ℕ), x = y → y = x -- implication
#check ¬(1 = 0) -- negation \not

-- here is a not so silly proposition
-- (XX : State Fermat's Last Theorem/Goldbach)

/-
As we've said, propositions can be subjected to *(dis)proof*.
Here is how this is done in Lean: since every term has a type,
and Propositions are types too, we should read their terms as
their proofs.

This points to (2) above.
To repeat ourselves again, we read `p : 1+1 = 2` as `p` is a
proof of the proposition that 1+1 = 2.
Let us check if our propositions are provable or not.

We will try to prove more of our silly props later.

XX : First proof, syntax explanation, term mode/tactic mode.
-/

theorem one_plus_one_two : 1+1 = 1+1 :=
begin
  refl,
end

theorem ten_is_even :  ∃ k m : ℕ, m = 2*k :=
begin
  use 5,
  use 10,
  refl,
end

theorem bool_thm :  ∀ (x : bool), x = tt ∨ x = ff :=
begin
  assume x,
  cases x,
  {
    right,
    refl,
  },
  {
    sorry,
  }
end

/-
# Tactics

The terms inside the `begin ... end` block you see here are
called *tactics*.

As soon as you write out a valid lemma and write a `begin ... end`
below it, Lean wakes up and tries to solve the goal you stated, and
complains if it can't.

You look at it's complaints and help it along, until it
complains no more. This is the *interactive* part of interactive
theorem proving.

The next incantation defines two abstract propositions and adds
it to the context henceforth.

-/

namespace tactics

variables (p q : Prop)

/-
``exact``

  If ``p`` is the target of the current goal and
  ``hp`` is a term of type ``p``, then
  ``exact hp,`` will close the goal.

``intro``

  If the target of the current goal is a function ``p → q``, then
  ``intro hp,`` will produce a hypothesis
  ``hp : P`` and change the target to  ``q``.

Delete the ``sorry,`` below and replace them with a legitimate proof.

-/

theorem tautology' (hp : p) : p :=
begin
  exact hp,
end

theorem tautology'' : p → p :=
begin
  assume hp,
  exact hp,
end

example : (p → (q → p)) :=
begin
  assume hp,
  assume hq,
  exact hp,
end

-- Can you find two different ways of proving the following?
example (p q : Prop) : ((q → p) → (q → p)) :=
begin
  sorry,
end

/-

``have``

  If ``f`` is a term of type ``p → q`` and
  ``hp`` is a term of type ``p``, then
  ``have hq := f(hp),`` creates the hypothesis ``hq : q`` .

``apply``

  If the target of the current goal is ``q`` and
  ``f`` is a term of type ``p → q``, then
  ``apply f,`` changes target to ``p``.

Delete the ``sorry,`` below and replace them with a legitimate proof.

-/

example (p q r : Prop) (hp : p) (f : p → q) (g : q → r) : r :=
begin
  have hq : q,
  {
    exact (f hp),
  },
  exact (g hq),
end

example (p q r : Prop) (hp : p) (f : p → q) (g : q → r) : r :=
begin
  apply g,
  apply f,
  exact hp,
end

theorem quux (P Q R S T U: Type)
(hpq : P → Q)
(hqr : Q → R)
(hrs : R → S)
(hst : S → T)
(htu : T → U)
: P → U :=
begin
  assume hp,
  apply htu,
  apply hst,
  apply hrs,
  apply hqr,
  apply hpq,
  exact hp,
end

#print quux

/-

``cases``

  ``cases`` is a general tactic that breaks up complicated terms.
  If ``hpq`` is a term of type ``P ∧ Q`` or ``P ∨ Q`` or ``P ↔ Q``,
  then use ``cases hpq with hp hq,``.

``split``

  If the target of the current goal is ``P ∧ Q`` or ``P ↔ Q``,
  then use ``split,``.

``left``/``right``

  If the target of the current goal is ``P ∨ Q``, then use
  either ``left,`` or ``right,`` (choose wisely).

``exfalso``

  Changes the target of the current goal to ``false``.

Delete the ``sorry,`` below and replace them with a legitimate proof.

-/

example (P Q : Prop) : P ∧ Q → Q ∧ P :=
begin
  assume hpq,
  split,
  {
    cases hpq with hp hq,
    exact hq,
  },
  {
    cases hpq with hp hq,
    exact hp,
  }
end

example (P Q : Prop) : P ∧ Q → Q ∧ P :=
begin
  assume hpq, -- this is a comment in Lean
  split,
  all_goals {
    cases hpq with hp hq,
    assumption,
  },
end

/- This is another comment. -/
example (P Q : Prop) : P ∨ Q → Q ∨ P :=
begin
  assume hpq,
  cases hpq,
  {
    right,
    exact hpq,
  },
  {
    left,
    exact hpq,
  }
end

#check @false.elim

example (P Q R : Prop) : P ∧ false ↔ false :=
begin
  split,
  {
    assume hpf,
    cases hpf with hp hf,
    exact hf,
  },
  {
    assume hf,
    exfalso,
    exact hf,
  }
end

theorem principle_of_explosion (P Q : Prop) : P ∧ ¬ P → Q :=
begin
  sorry,
end

/-

``exfalso``

  Changes the target of the current goal to ``false``.

``push_neg``

  ``push_neg,`` simplifies negations in the target.
  You can push negations across a hypothesis ``hp : P`` using
  ``push_neg at hp,``.

``contrapose!``

  If the target of the current goal is  ``P → Q``,
  then ``contrapose!,`` changes the target to  ``¬ Q → ¬ P``.

  If the target of the current goal is ``Q`` and
  one of the hypotheses is ``hp : P``, then
  ``contrapose! hp,`` changes the target to  ``¬ P`` and
  changes the hypothesis to ``hp : ¬ Q``.

Delete the ``sorry,`` below and replace them with a legitimate proof.

-/

theorem not_not_self_imp_self (P : Prop) : ¬ ¬ P → P:=
begin
  sorry,
end

theorem contrapositive_converse (P Q : Prop) : (¬Q → ¬P) → (P → Q) :=
begin
  sorry,
end

example (P : Prop) : ¬ P → ¬ ¬ ¬ P :=
begin
  sorry,
end

theorem principle_of_explosion' (P Q : Prop) : P → (¬ P → Q) :=
begin
  sorry,
end

end tactics

/-
  Using the tactics above, let's try to prove some of the
  silly propositions we wrote out before:

  ∀ (x : ℕ), x = 10 -- universal quantification
  ∃ k : ℕ, 10 = 2*k -- existential quantification
  ∀ (x : bool), x = tt ∨ x = ff -- disjunction
  ∀ (x : bool), x = tt ∧ x = ff -- conjunction
  ∀ (x y : ℕ), x = y → y = x -- implication
  ¬(1 = 0) -- negation

-/
example : ∀ (x : bool), x = tt ∨ x = ff :=
begin
  intros x, -- pick an arbitrary boolean x
  cases x, -- prove the goal two cases: when x is true and when x is false.
  {
    -- we are in the false branch now
    right,
    refl,
  },
  {
    -- we are in the true branch now
    left,
    refl,
  }
end

/-
Lemmas and theorems can be named so that they can be referred to in future proofs.
-/

lemma ten_even : ∃ k : ℕ, 10 = 2*k :=
begin
  use 5,
  refl,
end

#check ten_even -- ten_even is a proof that `∃ k : ℕ, 10 = 2*k`
#check ∃ k : ℕ, 10 = 2*k -- which is a proposition

/-
Prove these:
-/
-- Use rw.
lemma eq_symm : ∀ (x y : ℕ), x = y → y = x :=
begin
  assume x y Hxy,
  rewrite Hxy,
end

-- XX: Show what proving a negation means. Use injection.
lemma one_ne_zero' : ¬(1 = 0) :=
begin
  assume onezero,
  injection onezero,
end

/-
# Exercises

Using the tactics from above, try to prove as many of the
following as you can:
-/
namespace exercises

variables p q r : Prop

-- commutativity of ∧ and ∨
example : p ∧ q ↔ q ∧ p := sorry
example : p ∨ q ↔ q ∨ p := sorry

-- associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := sorry
example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) := sorry

-- distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := sorry
example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := sorry

-- other properties
example : (p → (q → r)) ↔ (p ∧ q → r) := sorry
example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) := sorry
example : ¬(p ∨ q) ↔ ¬p ∧ ¬q := sorry
example : ¬p ∨ ¬q → ¬(p ∧ q) := sorry
example : ¬(p ∧ ¬p) := sorry
example : p ∧ ¬q → ¬(p → q) := sorry
example : ¬p → (p → q) := sorry
example : (¬p ∨ q) → (p → q) := sorry
example : p ∨ false ↔ p := sorry
example : p ∧ false ↔ false := sorry
example : (p → q) → (¬q → ¬p) := sorry

end exercises


/-
# References

1. Avigad, Jeremy. ‘Theorem Proving in Lean’, n.d.
-/

-- Exercise 1
--
-- Try defining other operations on the natural numbers, such as multiplication,
-- the predecessor function (with `pred 0 = 0`), truncated subtraction (with
-- `n - m = 0` when `m` is greater than or equal to `n`), and exponentiation.
-- Then try proving some of their basic properties, building on the theorems we
-- have already proved.
--
-- Since many of these are already defined in Lean’s core library, you should
-- work within a namespace named hide, or something like that, in order to avoid
-- name clashes.
namespace ex1

-- As defined in the book.
inductive Nat where
  | zero : Nat
  | succ : Nat → Nat

namespace Nat

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

instance : Add Nat where
  add := add

theorem add_zero (m : Nat) : m + Nat.zero = m :=
  rfl

theorem add_succ (m n : Nat) : m + n.succ = (m + n).succ :=
  rfl

theorem zero_add (n : Nat) : Nat.zero + n = n :=
Nat.recOn (motive := fun x => Nat.zero + x = x)
  n
  (show Nat.zero + Nat.zero = Nat.zero from rfl)
  (fun (n : Nat) (ih : Nat.zero + n = n) =>
    show Nat.zero + n.succ = n.succ from
    calc
      Nat.zero + n.succ = (Nat.zero + n).succ := add_succ Nat.zero n
                      _ = n.succ := by rw [ih])

-- Additional definitions.
def mul (m n : Nat) : Nat :=
  match n with
  | Nat.zero => Nat.zero
  | Nat.succ n => m + mul m n

def pred (n : Nat) : Nat :=
  match n with
  | Nat.zero => Nat.zero
  | Nat.succ n => n

def sub (m n : Nat) : Nat :=
  match n with
  | Nat.zero => m
  | Nat.succ n => sub (pred m) n

def exp (m n : Nat) : Nat :=
  match n with
  | Nat.zero => Nat.zero.succ
  | Nat.succ n => mul m (exp m n)

end Nat

end ex1

-- Exercise 2
--
-- Define some operations on lists, like a `length` function or the `reverse`
-- function. Prove some properties, such as the following:
--
-- a. `length (s ++ t) = length s + length t`
-- b. `length (reverse t) = length t`
-- c. `reverse (reverse t) = t`
namespace ex2

variable {α : Type _}

theorem length_sum (s t : List α)
        : List.length (s ++ t) = List.length s + List.length t :=
  List.recOn s
    (by rw [List.nil_append, List.length, Nat.zero_add])
    (fun hd tl ih => by rw [
      List.length,
      List.cons_append,
      List.length,
      ih,
      Nat.add_assoc,
      Nat.add_comm t.length,
      Nat.add_assoc
    ])

theorem length_inject_anywhere (x : α) (as bs : List α)
        : List.length (as ++ [x] ++ bs) = List.length as + List.length bs + 1 := by
  induction as with
  | nil => simp
  | cons head tail ih => calc
      List.length (head :: tail ++ [x] ++ bs)
          = List.length (tail ++ [x] ++ bs) + 1 := rfl
        _ = List.length tail + List.length bs + 1 + 1 := by rw [ih]
        _ = List.length tail + (List.length bs + 1) + 1 := by rw [Nat.add_assoc (List.length tail)]
        _ = List.length tail + (1 + List.length bs) + 1 := by rw [Nat.add_comm (List.length bs)]
        _ = List.length tail + 1 + List.length bs + 1 := by rw [Nat.add_assoc (List.length tail) 1]
        _ = List.length (head :: tail) + List.length bs + 1 := rfl

theorem list_reverse_aux_append (as bs : List α)
        : List.reverseAux as bs = List.reverse as ++ bs := by
  induction as generalizing bs with
  | nil => rw [List.reverseAux, List.reverse, List.reverseAux, List.nil_append]
  | cons head tail ih => calc
      List.reverseAux (head :: tail) bs
          = List.reverseAux tail (head :: bs) := rfl
        _ = List.reverse tail ++ (head :: bs) := by rw [ih]
        _ = List.reverse tail ++ ([head] ++ bs) := rfl
        _ = List.reverse tail ++ [head] ++ bs := by rw [List.append_assoc]
        _ = List.reverseAux tail [head] ++ bs := by rw [ih]
        _ = List.reverseAux (head :: tail) [] ++ bs := rfl

theorem length_reverse (t : List α)
        : List.length (List.reverse t) = List.length t := by
  induction t with
  | nil => simp
  | cons head tail ih => calc
      List.length (List.reverse (head :: tail))
          = List.length (List.reverseAux tail [head]) := rfl
        _ = List.length (List.reverse tail ++ [head]) := by rw [list_reverse_aux_append]
        _ = List.length (List.reverse tail) + List.length [head] := by simp
        _ = List.length tail + List.length [head] := by rw [ih]
        _ = List.length tail + 1 := rfl
        _ = List.length [] + List.length tail + 1 := by simp
        _ = List.length ([] ++ [head] ++ tail) := by rw [length_inject_anywhere]
        _ = List.length (head :: tail) := rfl

theorem reverse_reverse_aux (as bs : List α)
        : List.reverse (as ++ bs) = List.reverse bs ++ List.reverse as := by
  induction as generalizing bs with
  | nil => simp
  | cons head tail ih => calc
      List.reverse (head :: tail ++ bs)
          = List.reverseAux (head :: tail ++ bs) [] := rfl
        _ = List.reverseAux (tail ++ bs) [head] := rfl
        _ = List.reverse (tail ++ bs) ++ [head] := by rw [list_reverse_aux_append]
        _ = List.reverse bs ++ List.reverse tail ++ [head] := by rw [ih]
        _ = List.reverse bs ++ (List.reverse tail ++ [head]) := by rw [List.append_assoc]
        _ = List.reverse bs ++ (List.reverseAux tail [head]) := by rw [list_reverse_aux_append]
        _ = List.reverse bs ++ (List.reverseAux (head :: tail) []) := rfl
        _ = List.reverse bs ++ List.reverse (head :: tail) := rfl

theorem reverse_reverse (t : List α)
        : List.reverse (List.reverse t) = t := by
  induction t with
  | nil => simp
  | cons head tail ih => calc
      List.reverse (List.reverse (head :: tail))
          = List.reverse (List.reverseAux tail [head]) := rfl
        _ = List.reverse (List.reverse tail ++ [head]) := by rw [list_reverse_aux_append]
        _ = List.reverse [head] ++ List.reverse (List.reverse tail) := by rw [reverse_reverse_aux]
        _ = List.reverse [head] ++ tail := by rw [ih]
        _ = [head] ++ tail := by simp
        _ = head :: tail := rfl

end ex2

-- Exercise 3
--
-- Define an inductive data type consisting of terms built up from the following
-- constructors:
--
-- • `const n`, a constant denoting the natural number `n`
-- • `var n`, a variable, numbered `n`
-- • `plus s t`, denoting the sum of `s` and `t`
-- • `times s t`, denoting the product of `s` and `t`
--
-- Recursively define a function that evaluates any such term with respect to an
-- assignment of values to the variables.
namespace ex3

inductive Foo : Type _
  | const : Nat → Foo
  | var : Nat → Foo
  | plus : Foo → Foo → Foo
  | times : Foo → Foo → Foo

def value_at : Nat → List Nat → Nat
| _,       [] => default
| 0,       vs => List.head! vs
| (i + 1), vs => value_at i (List.tail! vs)

-- The provided "variables" are supplied in a 0-indexed list.
def eval_foo : Foo → List Nat → Nat
  | (Foo.const n)  , _  => n
  | (Foo.var n)    , vs => value_at n vs
  | (Foo.plus m n) , vs => eval_foo m vs + eval_foo n vs
  | (Foo.times m n), vs => eval_foo m vs * eval_foo n vs

end ex3


import analysis.topology.continuity
import analysis.topology.topological_space
import analysis.topology.infinite_sum
import analysis.topology.topological_structures
import analysis.topology.uniform_space

universe u

open set filter lattice classical

#print definition set

#check empty
def empty_set_topology : topological_space empty :=
{
is_open := λ (a : set empty),
true, is_open_univ := begin trivial, end,
is_open_inter := begin intros h1 h2 h3 h4, trivial end,
is_open_sUnion := begin intros h1 h2, trivial end
}
#print univ
#check empty_set_topology.is_open

#print set

definition is_open_sets {α : Type u} (is_open : set α → Prop) :=
  is_open univ ∧ (∀s t, is_open s → is_open t → is_open (s ∩ t)) ∧ (∀s, (∀t∈s, is_open t) → is_open (⋃₀ s))

definition is_to_top {α : Type u} (is_open : set α → Prop) (H : is_open_sets (is_open)) : topological_space α :=
{ is_open := is_open,
  is_open_univ := H.left,
  is_open_inter := H.right.left,
  is_open_sUnion := H.right.right
}

definition top_to_is {α : Type u} (T : topological_space α) : is_open_sets (T.is_open) :=
⟨T.is_open_univ,T.is_open_inter,T.is_open_sUnion⟩

theorem Proposition7_2 {α : Type u} {X : topological_space α} {U : set α} : X.is_open U ↔ ∀ x ∈ U, ∃ Ux : set α, Ux ⊆ U ∧ X.is_open Ux ∧ x ∈ Ux  :=
begin
split,
intro H1,
assume x,
intro H2,
existsi U,
exact ⟨subset.refl U, H1, H2⟩,

intro H1,
let S := {Ux : set α | Ux ⊆ U ∧ X.is_open Ux ∧  ∃ x: α ,  x ∈ Ux},

have Union : U = ⋃₀ S,
apply set.subset.antisymm_iff.2,
split,
swap,
apply set.sUnion_subset,
intros V H2,
rw set.mem_def at H2,
exact H2.left,
assume x,
intro x_in_U,
have H2 : (∃ (Ux : set α), Ux ⊆ U ∧ topological_space.is_open X Ux ∧  x ∈ Ux),
exact H1 x x_in_U,
cases H2 with Ux HUx,
rw set.mem_sUnion_eq,
existsi Ux,
have Ux_in_S : Ux ∈ S,
apply set.mem_def.2,
have : ∃ (x : α), x ∈ Ux,
existsi x,
exact HUx.right.right,
exact ⟨HUx.1, HUx.2.1, this⟩,
existsi Ux_in_S,
exact HUx.2.2,
rw Union,
have : (∀ V∈S, X.is_open V),
intros V H2,
rewrite set.mem_def at H2,
exact H2.2.1,
exact X.is_open_sUnion S this,
end

#print prefix set

--


-- Computational domain
#check nat
#check 1

#check string
#check "hello"

#check bool
#check tt

-- "computational" types of type, Type (aka Sort 1)

-- Logical domain
#check true
#print true

def a_proof_of_nat : ℕ := 8
theorem a_proof_of_true : true := true.intro

#check false

#check (eq 1) 1   -- eq predicate applied to 1 and 2
#check (eq 1)
#check 1 = 1    -- infix notation for eq

#print eq

lemma pf_one_eq_one : 1 = 1 := eq.refl 1
lemma pf_one_eq_two : 1 = 2 := (eq.refl 2)

#check eq.refl 3

#check 3 ∈ { n : ℕ | n % 2 = 1}

#check ∃ (a b c : ℕ), a^2 + b^2 = c^2

-- propositions formalized as logical types: of type, Prop (Sort 0)

-- Values of computational types
#check 1
#check "hello"
#check tt
-- values of computational types are just ordinary data

-- Values of logical types (aka propositions)
#check (eq.refl 1)
-- values of logical types are accepted as proofs

-- eq defined as polymorphic *logical* type
-- takes two values, v1 and v2, of any type, α
-- yields a proposition, eq v1 v2, of type Prop
-- this type has just on constructor, called refl
-- takes argument, a : α, returns a value of type a=a
-- this value is accepted as a *proof*
#print eq

-- exercises involving proofs of equality
def pf1 : 1 = 1 := eq.refl 1
def pf2 : "hello" = "hello" := eq.refl "hello"

#reduce 2 = nat.pred 3

lemma pf3 : 2 = nat.pred 3 := eq.refl (nat.pred 3)     -- vocabulary: minor result

theorem pf4 : tt = tt := eq.refl tt          -- vocabulary: major result
theorem pf5 : 1 = 0 := _                -- StUCk!!!

def sq (n : ℕ) : ℕ := n^2
#reduce (sq 3 = 9)

lemma pf6 : sq 3 = 9 := eq.refl 9           -- equality of reduced values
lemma pf7 : sq 5 = 25 := eq.refl 25

example : sq 3 = 9 := eq.refl _
example : ℕ := 8

-- A logical type that has no values is false.
-- That's why the proposition, "false", in Lean is false.
-- a type with no constructors/values is said to be uninhabited
#print false
theorem pf8 : false := _

-- The proposition, "true", has one constant constructor/value/proof
#print true
theorem pf9 : true := true.intro

/-
What we've seen so far is that some propositions are
formalized as inductive types, the values of which, as
built using available constructors, are taken to be proofs.
Indeed you can represent a wide variety of propositions
and proofs this way.
-/

inductive MaryIsASoftwareEngineer : Prop
| knowsFormalMethods
| crackProgrammer

open MaryIsASoftwareEngineer

def me1 : MaryIsASoftwareEngineer := knowsFormalMethods
theorem me2 : MaryIsASoftwareEngineer := crackProgrammer

/-
Lean implements a logic in which all proofs of a given
proposition are considered to be not only equally good
but actually equal. This treatment of logical values is
fundamentally different from that of computational data
values, where values built by different constructors are
*never* equal. The principle that Lean implements here
is called the principle of "proof irrelevance." To show
that a proposition is true, you just have to exhibit *any*
proof/value of the given proposition/type.
-/

lemma m1 : me1 = me2 := eq.refl me1

theorem bad_news : 0 = 1 := _

/-
Not every logical proposition or predicate is implemented
as an inductive type. In particular, proofs of ∀, →, and ¬
propositions are not elementary data values functions.
-/

-- more to come


/-
Copyright (c) 2021 Manuel Candales. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Manuel Candales
-/
import data.real.basic
import tactic.positivity

/-!
# IMO 2005 Q3
Let `x`, `y` and `z` be positive real numbers such that `xyz ≥ 1`. Prove that:
`(x^5 - x^2)/(x^5 + y^2 + z^2) + (y^5 - y^2)/(y^5 + z^2 + x^2) + (z^5 - z^2)/(z^5 + x^2 + y^2) ≥ 0`

# Solution
The solution by Iurie Boreico from Moldova is presented, which won a special prize during the exam.
The key insight is that `(x^5-x^2)/(x^5+y^2+z^2) ≥ (x^2-y*z)/(x^2+y^2+z^2)`, which is proven by
factoring `(x^5-x^2)/(x^5+y^2+z^2) - (x^5-x^2)/(x^3*(x^2+y^2+z^2))` into a non-negative expression
and then making use of `xyz ≥ 1` to show `(x^5-x^2)/(x^3*(x^2+y^2+z^2)) ≥ (x^2-y*z)/(x^2+y^2+z^2)`.
-/

lemma key_insight (x y z : ℝ) (hx : x > 0) (hy : y > 0) (hz : z > 0) (h : x*y*z ≥ 1) :
  (x^5-x^2)/(x^5+y^2+z^2) ≥ (x^2-y*z)/(x^2+y^2+z^2) :=
begin
  have h₁ : 0 < x^5+y^2+z^2 := by positivity,
  have h₂ : 0 < x^3 := by positivity,
  have h₃ : 0 < x^2+y^2+z^2 := by positivity,
  have h₄ : 0 < x^3*(x^2+y^2+z^2) := by positivity,

  have key : (x^5-x^2)/(x^5+y^2+z^2) - (x^5-x^2)/(x^3*(x^2+y^2+z^2))
           = ((x^3 - 1)^2*x^2*(y^2 + z^2))/((x^5+y^2+z^2)*(x^3*(x^2+y^2+z^2))),
  { field_simp [h₁.ne', h₄.ne'],
    ring },

  have h₅ : ((x^3 - 1)^2*x^2*(y^2 + z^2))/((x^5+y^2+z^2)*(x^3*(x^2+y^2+z^2))) ≥ 0 := by positivity,

  calc  (x^5-x^2)/(x^5+y^2+z^2)
      ≥ (x^5-x^2)/(x^3*(x^2+y^2+z^2)) : by linarith [key, h₅]
  ... ≥ (x^5-x^2*(x*y*z))/(x^3*(x^2+y^2+z^2)) :
        by { refine (div_le_div_right h₄).mpr _, simp,
             exact (le_mul_iff_one_le_right (pow_pos hx 2)).mpr h }
  ... = (x^2-y*z)/(x^2+y^2+z^2) :
        by { field_simp [h₂.ne', h₃.ne'], ring },
end

theorem imo2005_q3 (x y z : ℝ) (hx : x > 0) (hy : y > 0) (hz : z > 0) (h : x*y*z ≥ 1) :
  (x^5-x^2)/(x^5+y^2+z^2) + (y^5-y^2)/(y^5+z^2+x^2) + (z^5-z^2)/(z^5+x^2+y^2) ≥ 0 :=
begin
  calc  (x^5-x^2)/(x^5+y^2+z^2) + (y^5-y^2)/(y^5+z^2+x^2) + (z^5-z^2)/(z^5+x^2+y^2)
      ≥ (x^2-y*z)/(x^2+y^2+z^2) + (y^2-z*x)/(y^2+z^2+x^2) + (z^2-x*y)/(z^2+x^2+y^2) :
        by { linarith [key_insight x y z hx hy hz h,
                       key_insight y z x hy hz hx (by linarith [h]),
                       key_insight z x y hz hx hy (by linarith [h])] }
  ... = 1/2*( (x-y)^2 + (y-z)^2 + (z-x)^2 ) / (x^2+y^2+z^2) :
        by { have h₁ : y^2+z^2+x^2 = x^2+y^2+z^2, ring,
             have h₂ : z^2+x^2+y^2 = x^2+y^2+z^2, ring,
             rw [h₁, h₂], ring }
  ... ≥ 0 :
        by { exact div_nonneg
                (by linarith [sq_nonneg (x-y), sq_nonneg (y-z), sq_nonneg (z-x)])
                (by linarith [sq_nonneg x, sq_nonneg y, sq_nonneg z]) },
end


import data.equiv.basic

/-

data.equiv.basic  is the import which gives you the type `equiv X Y`, the type of
bijections from X to Y.

Here's the definition of equiv from that file.

structure equiv (α : Sort*) (β : Sort*) :=
(to_fun    : α → β)
(inv_fun   : β → α)
(left_inv  : left_inverse inv_fun to_fun)
(right_inv : right_inverse inv_fun to_fun)

To make a term of type `equiv α β` you have to supply a function α → β,
a function β → α, and proofs that both composites are the identity function.

Let's see how to create the bijection ℤ → ℤ sending x to -x.
-/
-- let's prove that x ↦ -x can be extended to
example : equiv ℤ ℤ :=
{ to_fun := λ x, -x, -- this is data
  inv_fun := λ x, -x,  -- this is data
  left_inv := begin -- this is a proof
    change ∀ (x : ℤ), - -x = x, -- that's the question
    exact neg_neg, -- note: I guessed what this function was called.
                   -- If it had been called "lemma 12" I would not have been able to guess
  end,
  right_inv := neg_neg -- another proof, this time in term mode
}

/-
Q1 Define the type of partitions of a type.
A partition of X is a set of subsets of X with the property that each subset
is non-empty and each element of X is in precisely one of the subsets.
NB : this is one of the harder questions here.
-/

structure partition (X : Type*). -- remove `.`  and fill in -- look at def of equiv above

/-
Equivalence relations are in core Lean -- we don't need any imports.
Here's an example: I'll prove that the "always true" relation on a set is
an equivalence relation.

-/

def always_true (X : Type*) : X → X → Prop := λ a b, true

-- and now here's the proof that it's an equivalence relation.

theorem always_true_refl (X) : reflexive (always_true X) :=
begin
  intro x,
  trivial
end

theorem always_true_symm (X) : symmetric (always_true X) :=
begin
  intros a b H,
  trivial
end

theorem always_true_trans (X) : transitive (always_true X) :=
begin
  intros a b c Hab Hbc,
  trivial
end

-- note pointy brackets to make a term of type "A ∧ B ∧ C"
theorem always_true_equiv (X): equivalence (always_true X) :=
⟨always_true_refl X, always_true_symm X, always_true_trans X⟩
-- autocomplete made that proof really easy to type. It's really
-- lucky that I didn't call these lemmas lemma 12, lemma 13 and lemma 14.

-- if X is a type, then `setoid X` is is the type of equivalence relations on X.
-- I'll now make a term of type `setoid X` corresponding to that equivalence
-- relation above.

-- note squiggly brackets and commas at the end of each definition to make a structure
def always_true_setoid (X : Type*) : setoid X :=
{ r := always_true X,
  iseqv := always_true_equiv X }

/-
Q2 : If X is a type then `setoid X` is the type of equivalence relations on X,
and `partitions X` is the type of partitions of X. These two concepts are in
some sort of "canonical" bijection with each other (interesting exercise: make
this statement mathematically meaningful -- I know we all say it, but what
does it *mean*?).

Let's prove that these sets biject with each other by defining
a term of type equiv (setoid X) (partitions X)
-/

variable {X : Type*}

def F (S : setoid X) : partition X := sorry

/-
Q3 : now define a map the other way
-/

def G (P : partition X) : setoid X := sorry

/-
Q4 : now finally prove that the composite of maps in both directions
is the identity
-/

theorem FG_eq_id (P : partition X) : F (G P) = P := sorry
theorem GF_eq_id (S : setoid X) : G (F S) = S := sorry

/-
Q5 : now finally construct the term we seek.
-/

def partitions_biject_with_equivalence_relations :
  equiv (setoid X) (partition X) := sorry


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

import tactic -- imports all the Lean tactics

/-!

# Sets in Lean, sheet 5 : subset (`⊆`), union (`∪`) and intersection (`∩`)

In this sheet we learn how to manipulate `⊆`, `∪` and `∩` in Lean.

Here are some mathematical facts:

`A ⊆ B` is equivalent to `∀ x, x ∈ A → x ∈ B`;
`x ∈ A ∪ B` is equivalent to `x ∈ A ∨ x ∈ B`;
`x ∈ A ∩ B` is equivalent to `x ∈ A ∧ x ∈ B`.

All of these things are true *by definition* in Lean, which means
that you can switch from one to the other with `change`, or you
can just treat something on the left hand side as if it said
what it said on the right hand side.

For example if your goal is `⊢ x ∈ A ∩ B` then you could write
`change x ∈ A ∧ x ∈ B,` to change the goal to `⊢ x ∈ A ∧ x ∈ B`, but you can
also use the `split,` tactic directly, and this will immediately turn the goal
into two goals `⊢ x ∈ A` and `⊢ x ∈ B`.

## New tactics you will need

You don't need to know any new tactics to solve this sheet. I've mentioned
the `change` tactic. You don't have to use it, and if you use it your
proofs will get longer. So in return I'll tell you about two other
tactics, `rcases` and `rintro`, which you don't have to use either
but if you use them they'll make your proofs shorter.

### The `rcases` tactic

`rcases` is a souped-up version of `cases`. It has slightly different
syntax. If you have a hypothesis `h : P ∧ Q` then `cases h with hP hQ,`
and `rcases h with ⟨hP, hQ⟩,` do the same thing. However, if you
have a hypothesis `h : P ∧ Q ∧ R` then Lean interprets it as `P ∧ (Q ∧ R)`
so if you want to destruct it with `cases` you have to do

```
cases h with hP hQR,
cases hQR with hQ hR
```

You can do this all in one go with `rcases h with ⟨hP, hQ, hR⟩,`. The
name `rcases` stands for "recursive cases".

`rcases` can also be used for `or` hypotheses too; here the syntax is that if
we have
```
h : P ∨ Q
```
then `rcases h with (hP | hQ),` will turn our goal into two goals, one with
`hP : P` and the other with `hQ : Q`.

Even better, `rcases` works on `h : false`. Here there are no cases at all!
So `rcases h with ⟨⟩,` solves the goal.

### The `rintro` tactic

It's quite common to find yourself doing `intro` then `cases` or,
more generally, `intro` then `rcases`. The `rintro` tactic does
these both at once! So for example if your goal is

```
⊢ (P ∧ Q) → R
```

then `rintro ⟨hP, hQ⟩,` leaves you at

```
hP : P
hQ : Q
⊢ R
```

i.e. the same as `intro h, cases h with hP hQ,`

You can introduce more than one hypothesis at once -- `rintro` generalises
`intros` as well. For example if your goal is

```
⊢ P → Q ∧ R → S
```

then `rintro hP ⟨hQ, hR⟩,` turns it into

```
hP : P
hQ : Q
hR : R
⊢ S
```

-/

open set

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

example : A ⊆ A :=
begin
  sorry,
end

example : ∅ ⊆ A :=
begin
  sorry,
end

example : A ⊆ univ :=
begin
  sorry,
end

example : A ⊆ B → B ⊆ C → A ⊆ C :=
begin
  sorry,
end

example : A ⊆ A ∪ B :=
begin
  sorry,
end

example : A ∩ B ⊆ A :=
begin
  sorry,
end

example : A ⊆ B → A ⊆ C → A ⊆ (B ∩ C) :=
begin
  sorry,
end

example : B ⊆ A → C ⊆ A → B ∪ C ⊆ A :=
begin
  sorry,
end

example : A ⊆ B → C ⊆ D → A ∪ C ⊆ B ∪ D :=
begin
  sorry,
end

example : A ⊆ B → C ⊆ D → A ∩ C ⊆ B ∩ D :=
begin
  sorry,
end


import tactic
import topology.basic

-- #check topological_space

/- From https://leanprover-community.github.io/theories/topology.html#filters:
  given a map f from ℕ to a topological space X, one can check that the resulting
  sequence f 0, f 1, f 2... tends to x ∈ X if and only if the pre-image of any
  element in the filter 𝓝 x is in the cofinite filter on ℕ -- this is just
  another way of saying that given any open set U containing x, there exists N
  such that for all n ≥ N, f n ∈ U. -/
example {X : Type*}[topological_space X]{f : ℕ → X}{x : X} :
(∀ U : set X, is_open U ∧ x ∈ U → ∃ N : ℕ, ∀ n > N, f n ∈ U) ↔
(∀ S : set X, S ∈ nhds x → f⁻¹' S ∈ (@filter.cofinite ℕ) ) :=
begin
  split,
  { -- Convergence implies preimage of neighborhood in the cofinite filter
    intros h S S_in,
    rw mem_nhds_iff at S_in,
    rcases S_in with ⟨U, U_ss_S, U_open, x_in_U⟩,
    rcases h U ⟨U_open, x_in_U⟩ with ⟨N, hN⟩,
    rw filter.mem_cofinite,
    have : f⁻¹' U ⊆ f⁻¹'S := set.preimage_mono U_ss_S,
    rw ← set.compl_subset_compl at this,
    suffices hs : (f⁻¹' U)ᶜ.finite, { exact set.finite.subset hs this, },
    -- Now we prove the complement of f⁻¹(U) is finite. The idea is that the
    -- complement is contained in {1, ..., N} because for n > N, n ∈ f⁻¹(U).
    -- So it must be finite.
    have hc : (f⁻¹' U)ᶜ ⊆ {n : ℕ | n ≤ N},
    { rw set.compl_subset_comm,
      intros n n_in,
      have hn : n > N, { rw set.compl_set_of at n_in; exact lt_of_not_ge n_in },
      rw set.compl_set_of at n_in,
      exact hN n hn, },
    exact set.finite.subset (set.finite_le_nat N) hc, },
  { -- Preimage of neighborhood in cofinite filter implies conergence
    rintros h U ⟨U_open, x_in_U⟩,
    have : U ∈ nhds x, { exact is_open.mem_nhds U_open x_in_U },
    specialize h U this,
    rw filter.mem_cofinite at h,
    -- Since (f ⁻¹' U)ᶜ ⊆ ℕ is finite, it is bounded above by some N ∈ ℕ.
    have compl_bdd := bdd_above_def.1 (set.finite.bdd_above h),
    cases compl_bdd with N hN,
    -- We claim that this N works. It is becaues for all n > N, n is not in the
    -- complement of f⁻¹(U), so f(n) ∈ f⁻¹(U).
    use N,
    intros n hn,
    specialize hN n,
    have : n ∉ (f⁻¹' U)ᶜ := mt hN (by linarith [hn]),
    rwa set.not_mem_compl_iff at this },
end

-- The principal filter is an ultrafilter
-- https://leanprover-community.github.io/mathlib_docs/order/filter/basic.html#filter.principal_le_iff

-- If f : X → Y is continuous, ℱ is a filter of X, and ℱ → x ∈ X, then f_*(ℱ) → f(x)
def filter_converge {X : Type*}[topological_space X](F : filter X)(x : X) := ∀ (U : set X), is_open U → x ∈ U → U ∈ F
#check filter_converge

example {X Y: Type*}[topological_space X][topological_space Y]{F : filter X}{f : X → Y}{x : X} :
continuous f → filter_converge F x → filter_converge (filter.map f F) (f x) :=
begin
  intros fcts F_to_x fU fU_open fx_in_fU,
  let U := f⁻¹' fU,
  have U_open : is_open U := is_open.preimage fcts fU_open,
  have x_in_U : x ∈ U := fx_in_fU,
  have U_in_F : U ∈ F := F_to_x U U_open x_in_U,
  exact filter.mem_map.mpr U_in_F,
end

-- If for every filter ℱ in X, ℱ → x ∈ X implies f_*(ℱ) → f(x), then f is continuous
example {X Y : Type*}[topological_space X][topological_space Y]{f : X → Y} :
(∀ (F : filter X) (x : X), filter_converge F x → filter_converge (filter.map f F) (f x))
→ (continuous f) :=
begin
  -- sorry
  have Nx_to_x : ∀ x : X, filter_converge (nhds x) x,
  { intros x U U_open x_in_U,
    exact is_open.mem_nhds U_open x_in_U },
  intros h,
  rw continuous_iff_continuous_at,
  intros x,
  rw continuous_at_def,
  intros A A_in_nhd,
  specialize h (nhds x) x (Nx_to_x x),
  rcases mem_nhds_iff.mp A_in_nhd with ⟨V, V_in, V_open, fx_in_V⟩,
  have V_in_pushforward : V ∈ filter.map f (nhds x) := h V V_open fx_in_V,
  have preimV_in_nhd : f⁻¹' V ∈ nhds x := by rwa filter.mem_map at V_in_pushforward,
  rcases mem_nhds_iff.mp preimV_in_nhd with ⟨U, U_in, U_open, x_in_U⟩,
  have : U ⊆ f⁻¹' A := subset_trans U_in (set.preimage_mono V_in),
  exact mem_nhds_iff.mpr ⟨U, this, U_open, x_in_U⟩,
end

-- From the previous two examples, we conclude that f is continuous iff
-- for all filter ℱ on X, ℱ → x implies f_*(ℱ) → f(x)
example {X Y : Type*}[topological_space X][topological_space Y]{f : X → Y} :
continuous f ↔ (∀ (F : filter X) (x : X), filter_converge F x → filter_converge (filter.map f F) (f x)) :=
sorry

-- It turns out ℱ → x is the same as filter.tendsto id ℱ (nhds x)
example {X : Type*}[topological_space X]{F : filter X}{x : X} :
filter_converge F x ↔ filter.tendsto id F (nhds x) :=
begin
  have filter_converge_iff_contain_nhds : filter_converge F x ↔ F ≤ (nhds x), -- ℱ ≤ 𝒩(x) means 𝒩(x) ⊆ ℱ
  { split,
    { intros F_to_x N N_nhd,
      rcases mem_nhds_iff.mp N_nhd with ⟨U, U_ss_N, U_open, x_in_U⟩,
      have U_in_F : U ∈ F := F_to_x U U_open x_in_U,
      exact filter.mem_of_superset U_in_F U_ss_N },
    { intros nhd_in_F U U_open x_in_U,
      have U_nhd : U ∈ (nhds x) :=  is_open.mem_nhds U_open x_in_U,
      exact nhd_in_F U_nhd } },
  rw filter_converge_iff_contain_nhds,
  unfold filter.tendsto,
  -- Since f is the identity, f⁻¹(S) = S for every S ⊆ X. So f_*(ℱ) = {S ⊆ X : f⁻¹(S) ∈ ℱ } = ℱ
  rwa filter.map_id,
end

-- As a result of filter_converge_iff_contain_nhds, if 𝒩(x) is an ultrafilter,
-- then it is the only filter that converges to x.

-- If S ⊆ T, then 𝒫(S) ⊇ 𝒫(T).
example {X : Type*}[topological_space X]{S T : set X} : S ⊆ T → filter.principal S ≤ filter.principal T :=
begin
  intros S_ss_T F F_in_PT, -- want to show F ∈ 𝒫(S)
  have S_ss_F : S ⊆ F :=
    calc S ⊆ T : S_ss_T
       ... ⊆ F : filter.mem_principal.mp F_in_PT,
  exact filter.mem_principal.mpr S_ss_F,
end

-- Conversely, if 𝒫(S) ⊇ 𝒫(T) then S ⊆ T.
example {X : Type*}[topological_space X]{S T : set X} : filter.principal S ≤ filter.principal T → S ⊆ T :=
begin
  intro S_le_T,
  -- Since T ∈ 𝒫(T) ⊆ 𝒫(S), we have T ∈ 𝒫(S), and thus T ⊇ S
  have T_in_PT : T ∈ filter.principal T := filter.mem_principal_self T,
  have T_in_PS : T ∈ filter.principal S := filter.le_def.mp S_le_T T T_in_PT,
  exact filter.mem_principal.mp T_in_PS,
end

-- And in mathlib this is filter.principal_mono
#check @filter.principal_mono

open filter

-- f_*(𝒫(S)) = 𝒫(f(S))
example {X Y : Type*}[topological_space X][topological_space Y]{f : X → Y}{S : set X} : filter.map f (principal S) = principal (f '' S) :=
begin
  -- simp, -- This alone will solve the goal, but let's work out the details
  apply filter.ext,             -- We want to show for all set T, T ∈ f_*(𝒫(S)) ↔ T ∈ 𝒫(f(S))
  intro T,
  rw [mem_map, mem_principal],  -- But T ∈ f_*(𝒫(S)) means f⁻¹(T) ∈ 𝒫(S) and f⁻¹(T) ⊇ S
  rw ← set.image_subset_iff,    -- But f⁻¹(T) ⊇ S exactly when T ⊇ S
  rw ← mem_principal,           -- So we can conclude T ∈ 𝒫(S)
end

-- The same example but simplified proof
example {X Y : Type*}[topological_space X][topological_space Y]{f : X → Y}{S : set X} : filter.map f (principal S) = principal (f '' S) :=
begin
  -- simp, -- This alone will solve the goal, but let's work out the details
  apply filter.ext,             -- We want to show for all set T, T ∈ f_*(𝒫(S)) ↔ T ∈ 𝒫(f(S))
  dsimp,                                                 -- That is, f⁻¹(T) ⊇ S ↔ T ⊇ f(S) by definitions
  intro T,
  rw ← set.image_subset_iff,
end

-- In mathlib it is filter.map_principal
#check @filter.map_principal


-- Prueba por inducción 3: ∀ m n : ℕ, succ m + n = succ (m + n)
-- ============================================================

-- ----------------------------------------------------
-- Ej. 1. Sean m y n números naturales. Demostrar que
--    succ m + n = succ (m + n)
-- ----------------------------------------------------

import tactic

open nat

variables (m n : ℕ)

-- 1ª demostración
example : succ m + n = succ (m + n) :=
begin
  induction n with n HI,
  { rw nat.add_zero,
    rw nat.add_zero, },
  { rw add_succ,
    rw HI, },
end

-- 2ª demostración
example : succ m + n = succ (m + n) :=
begin
  induction n with n HI,
  { simp only [nat.add_zero], },
  { simp only [add_succ, HI], },
end

-- 3ª demostración
example : succ m + n = succ (m + n) :=
by induction n;
   simp only [*, nat.add_zero, add_succ]

-- 4ª demostración
example : succ m + n = succ (m + n) :=
by induction n;
   simp [*, add_succ]

-- 5ª demostración
example : succ m + n = succ (m + n) :=
nat.rec_on n
  (show succ m + 0 = succ (m + 0), from
    calc
      succ m + 0
          = succ m       : by rw nat.add_zero
      ... = succ (m + 0) : by rw nat.add_zero)
  (assume n,
    assume HI : succ m + n = succ (m + n),
    show succ m + succ n = succ (m + succ n), from
      calc
        succ m + succ n
            = succ (succ m + n)   : by rw add_succ
        ... = succ (succ (m + n)) : by rw HI
        ... = succ (m + succ n)   : by rw add_succ)

-- 6ª demostración
example : succ m + n = succ (m + n) :=
nat.rec_on n
  (show succ m + 0 = succ (m + 0), from rfl)
  (assume n,
    assume HI : succ m + n = succ (m + n),
    show succ m + succ n = succ (m + succ n), from
      calc
        succ m + succ n
            = succ (succ m + n)   : rfl
        ... = succ (succ (m + n)) : by rw HI
        ... = succ (m + succ n)   : rfl)

-- 7ª demostración
example : succ m + n = succ (m + n) :=
nat.rec_on n rfl (λ n HI, by simp only [add_succ, HI])

-- 8ª demostración
example : succ m + n = succ (m + n) :=
-- by library_search
succ_add m n

-- 9ª demostración
example : succ m + n = succ (m + n) :=
-- by hint
by omega

-- 10ª demostración
lemma suc_suma : ∀ n m : ℕ, (succ n) + m = succ (n + m)
| n 0     := rfl
| n (m+1) := congr_arg succ (suc_suma n m)

-- 11ª demostración
lemma suc_suma2 : ∀ n m : ℕ, (succ n) + m = succ (n + m)
| n 0     := by simp
| n (m+1) := by simp [add_succ, suc_suma2 n m]

-- 12ª demostración
lemma suc_suma3 : ∀ n m : ℕ, (succ n) + m = succ (n + m)
| n 0     := by simp only [add_zero]
| n (m+1) := by simp only [add_zero, add_succ, suc_suma3 n m]